diff --git a/.github/getversion.cpp b/.github/getversion.cpp new file mode 100644 index 00000000..469a796e --- /dev/null +++ b/.github/getversion.cpp @@ -0,0 +1,9 @@ +#include +#include "./../src/Common/version.h" + +// output current Cemu version for CI workflow. Do not modify +int main() +{ + printf("%d.%d", EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR); + return 0; +} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..e6c0f420 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,202 @@ +name: Build Cemu + +on: + pull_request_target: + types: + - opened + - synchronize + - reopened + push: + branches: + - main + workflow_call: + inputs: + deploymode: + required: false + type: string + +env: + VCPKG_ROOT: "${{github.workspace}}/dependencies/vcpkg" + VCPKG_BINARY_SOURCES: 'clear;nuget,GitHub,readwrite' + +jobs: + build-ubuntu: + runs-on: ubuntu-20.04 + env: + install_vulkan_folder: "$GITHUB_WORKSPACE/vulkan_sdk" + install_vulkan_version: "1.3.216.0" + steps: + - name: "Checkout repo" + uses: actions/checkout@v3 + with: + submodules: "recursive" + + - name: Setup release mode parameters (for deploy) + if: ${{ inputs.deploymode == 'release' }} + run: | + echo "BUILD_MODE=release" >> $GITHUB_ENV + echo "BUILD_FLAGS=-DPUBLIC_RELEASE=ON" >> $GITHUB_ENV + echo "Build mode is release" + + - name: Setup debug mode parameters (for continous build) + if: ${{ inputs.deploymode != 'release' }} + run: | + echo "BUILD_MODE=debug" >> $GITHUB_ENV + echo "BUILD_FLAGS=" >> $GITHUB_ENV + echo "Build mode is debug" + + - name: "Install system dependencies" + run: | + sudo apt update -qq + sudo apt install -y ninja-build cmake libgtk-3-dev libsecret-1-dev libgcrypt20-dev libsystemd-dev freeglut3-dev clang-12 nasm + wget https://sdk.lunarg.com/sdk/download/${{ env.install_vulkan_version }}/linux/vulkansdk-linux-x86_64-${{ env.install_vulkan_version }}.tar.gz -q -O vulkansdk.tar.gz + mkdir -p "${{ env.install_vulkan_folder }}" + tar -xf vulkansdk.tar.gz --directory ${{ env.install_vulkan_folder }} + + - name: "Bootstrap vcpkg" + run: | + export VULKAN_SDK="${{ env.install_vulkan_folder }}/${{ env.install_vulkan_version }}/x86_64" + bash ./dependencies/vcpkg/bootstrap-vcpkg.sh + + - name: 'Setup NuGet Credentials for vcpkg' + shell: 'bash' + run: | + mono `./dependencies/vcpkg/vcpkg fetch nuget | tail -n 1` \ + sources add \ + -source "https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json" \ + -storepasswordincleartext \ + -name "GitHub" \ + -username "${{ github.repository_owner }}" \ + -password "${{ secrets.GITHUB_TOKEN }}" + mono `./dependencies/vcpkg/vcpkg fetch nuget | tail -n 1` \ + setapikey "${{ secrets.GITHUB_TOKEN }}" \ + -source "https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json" + + - name: "cmake" + run: | + export VULKAN_SDK="${{ env.install_vulkan_folder }}/${{ env.install_vulkan_version }}/x86_64" + mkdir -p build + cd build + cmake .. ${{ env.BUILD_FLAGS }} -DCMAKE_BUILD_TYPE=${{ env.BUILD_MODE }} -DCMAKE_C_COMPILER=/usr/bin/clang-12 -DCMAKE_CXX_COMPILER=/usr/bin/clang++-12 -G Ninja -DCMAKE_MAKE_PROGRAM=/usr/bin/ninja + + - name: "Build Cemu" + run: | + cd build + ninja + + - name: Upload artifact + uses: actions/upload-artifact@v3 + if: ${{ inputs.deploymode == 'release' }} + with: + name: cemu-bin-linux-x64 + path: ./bin/Cemu + + + build-windows: + runs-on: windows-2022 + env: + install_vulkan_folder: "$GITHUB_WORKSPACE/vulkan_sdk" + install_vulkan_version: "1.3.216.0" + steps: + - name: "Checkout repo" + uses: actions/checkout@v3 + with: + submodules: "recursive" + + - name: Setup release mode parameters (for deploy) + if: ${{ inputs.deploymode == 'release' }} + run: | + echo "BUILD_MODE=release" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append + echo "BUILD_FLAGS=-DPUBLIC_RELEASE=ON" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append + echo "Build mode is release" + + - name: Setup debug mode parameters (for continous build) + if: ${{ inputs.deploymode != 'release' }} + run: | + echo "BUILD_MODE=debug" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append + echo "BUILD_FLAGS=" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append + echo "Build mode is debug" + + - name: Prepare Vulkan SDK + uses: humbletim/setup-vulkan-sdk@v1.2.0 + with: + vulkan-query-version: 1.3.216.0 + vulkan-components: Vulkan-Headers, Vulkan-Loader + vulkan-use-cache: false + + - name: Workaround + run: | + Set-Location "C:\Program Files (x86)\Microsoft Visual Studio\Installer\" + $InstallPath = "C:\Program Files\Microsoft Visual Studio\2022\Enterprise" + $componentsToRemove= @( + "Microsoft.VisualStudio.Component.VC.14.32.17.2.ARM" + "Microsoft.VisualStudio.Component.VC.14.32.17.2.ARM.Spectre" + "Microsoft.VisualStudio.Component.VC.14.32.17.2.ARM64" + "Microsoft.VisualStudio.Component.VC.14.32.17.2.ARM64.Spectre" + "Microsoft.VisualStudio.Component.VC.14.32.17.2.x86.x64" + "Microsoft.VisualStudio.Component.VC.14.32.17.2.x86.x64.Spectre" + "Microsoft.VisualStudio.Component.VC.14.32.17.2.ATL" + "Microsoft.VisualStudio.Component.VC.14.32.17.2.ATL.Spectre" + "Microsoft.VisualStudio.Component.VC.14.32.17.2.ATL.ARM" + "Microsoft.VisualStudio.Component.VC.14.32.17.2.ATL.ARM.Spectre" + "Microsoft.VisualStudio.Component.VC.14.32.17.2.ATL.ARM64" + "Microsoft.VisualStudio.Component.VC.14.32.17.2.ATL.ARM64.Spectre" + "Microsoft.VisualStudio.Component.VC.14.32.17.2.MFC" + "Microsoft.VisualStudio.Component.VC.14.32.17.2.MFC.Spectre" + "Microsoft.VisualStudio.Component.VC.14.32.17.2.MFC.ARM" + "Microsoft.VisualStudio.Component.VC.14.32.17.2.MFC.ARM.Spectre" + "Microsoft.VisualStudio.Component.VC.14.32.17.2.MFC.ARM64" + "Microsoft.VisualStudio.Component.VC.14.32.17.2.MFC.ARM64.Spectre" + ) + [string]$workloadArgs = $componentsToRemove | ForEach-Object {" --remove " + $_} + $Arguments = ('/c', "vs_installer.exe", 'modify', '--installPath', "`"$InstallPath`"",$workloadArgs, '--quiet', '--norestart', '--nocache') + # should be run twice + $process = Start-Process -FilePath cmd.exe -ArgumentList $Arguments -Wait -PassThru -WindowStyle Hidden + $process = Start-Process -FilePath cmd.exe -ArgumentList $Arguments -Wait -PassThru -WindowStyle Hidden + + - name: Configure MSVC + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: amd64 + toolset: 14.33.31629 + spectre: false + + - name: "Bootstrap vcpkg" + run: | + ./dependencies/vcpkg/bootstrap-vcpkg.bat + + - name: 'Setup NuGet Credentials for vcpkg' + shell: 'bash' + run: | + `./dependencies/vcpkg/vcpkg.exe fetch nuget | tail -n 1` \ + sources add \ + -source "https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json" \ + -storepasswordincleartext \ + -name "GitHub" \ + -username "${{ github.repository_owner }}" \ + -password "${{ secrets.GITHUB_TOKEN }}" + `./dependencies/vcpkg/vcpkg.exe fetch nuget | tail -n 1` \ + setapikey "${{ secrets.GITHUB_TOKEN }}" \ + -source "https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json" + + - name: "cmake" + run: | + mkdir -p build + cd build + cmake .. ${{ env.BUILD_FLAGS }} -DCMAKE_BUILD_TYPE=${{ env.BUILD_MODE }} + + - name: "Free up space" + run: | + rmdir "./dependencies/vcpkg/downloads" /S /Q + + - name: "Build Cemu" + run: | + cd build + cmake --build . --config ${{ env.BUILD_MODE }} -j 2 + + - name: Upload artifact + uses: actions/upload-artifact@v3 + if: ${{ inputs.deploymode == 'release' }} + with: + name: cemu-bin-windows-x64 + path: ./bin/Cemu.exe diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000..7167dcbe --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,69 @@ +name: Create new release +on: + workflow_dispatch: + inputs: + PlaceholderInput: + description: PlaceholderInput + required: false +jobs: + call-release-build: + uses: ./.github/workflows/build.yml + with: + deploymode: release + deploy: + name: Deploy release + runs-on: ubuntu-20.04 + needs: call-release-build + steps: + - uses: actions/checkout@v2 + + - uses: actions/download-artifact@v3 + with: + name: cemu-bin-linux-x64 + path: cemu-bin-linux-x64 + + - uses: actions/download-artifact@v3 + with: + name: cemu-bin-windows-x64 + path: cemu-bin-windows-x64 + + - name: Initialize + run: | + mkdir upload + sudo apt update -qq + sudo apt install -y zip + + - name: Get Cemu release version + run: | + gcc -o getversion .github/getversion.cpp + echo "Cemu CI version: $(./getversion)" + echo "CEMU_FOLDER_NAME=Cemu_$(./getversion)" >> $GITHUB_ENV + echo "CEMU_VERSION=$(./getversion)" >> $GITHUB_ENV + + - name: Create appimage + run: | + echo "to do" + + - name: Create release from windows-bin + run: | + ls ./ + ls ./bin/ + cp -R ./bin ./${{ env.CEMU_FOLDER_NAME }} + mv cemu-bin-windows-x64/Cemu.exe ./${{ env.CEMU_FOLDER_NAME }}/Cemu.exe + zip -9 -r upload/cemu-${{ env.CEMU_VERSION }}-windows-x64.zip ${{ env.CEMU_FOLDER_NAME }} + rm -r ./${{ env.CEMU_FOLDER_NAME }} + + - name: Create release from ubuntu-bin + run: | + ls ./ + ls ./bin/ + cp -R ./bin ./${{ env.CEMU_FOLDER_NAME }} + mv cemu-bin-linux-x64/Cemu ./${{ env.CEMU_FOLDER_NAME }}/Cemu + zip -9 -r upload/cemu-${{ env.CEMU_VERSION }}-ubuntu-20.04-x64.zip ${{ env.CEMU_FOLDER_NAME }} + rm -r ./${{ env.CEMU_FOLDER_NAME }} + + - name: Create release + run: | + wget -O ghr.tar.gz https://github.com/tcnksm/ghr/releases/download/v0.15.0/ghr_v0.15.0_linux_amd64.tar.gz + tar xvzf ghr.tar.gz; rm ghr.tar.gz + ghr_v0.15.0_linux_amd64/ghr -t ${{ secrets.GITHUB_TOKEN }} -n "Cemu ${{ env.CEMU_VERSION }}" -b "Changelog:" v${{ env.CEMU_VERSION }} ./upload diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..fe0c7726 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +*.slo +*.lo +*.o +*.obj +*.gch +*.pch +*.so +*.dylib +*.dll +*.a +*.lib +*.exe +*.out +*.app +.vs + +build/ +out/ + +# Cemu bin files +otp.bin +seeprom.bin diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..04f01186 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,12 @@ +[submodule "dependencies/ZArchive"] + path = dependencies/ZArchive + url = https://github.com/Exzap/ZArchive + shallow = true +[submodule "dependencies/cubeb"] + path = dependencies/cubeb + url = https://github.com/mozilla/cubeb + shallow = true +[submodule "dependencies/vcpkg"] + path = dependencies/vcpkg + url = https://github.com/microsoft/vcpkg + shallow = true diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..6104c175 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,116 @@ +cmake_minimum_required(VERSION 3.21.1) + +option(PUBLIC_RELEASE "Compile with debug asserts disabled and no console" OFF) + +if (PUBLIC_RELEASE) + add_definitions(-DPUBLIC_RELEASE) + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) # enable LTO +endif() + +if(WIN32) +set(VCPKG_TARGET_TRIPLET "x64-windows-static" CACHE STRING "") +endif() + +set(VCPKG_OVERLAY_PORTS "${CMAKE_CURRENT_LIST_DIR}/dependencies/vcpkg_overlay_ports") +set(CMAKE_TOOLCHAIN_FILE "${CMAKE_CURRENT_SOURCE_DIR}/dependencies/vcpkg/scripts/buildsystems/vcpkg.cmake" + CACHE STRING "Vcpkg toolchain file") + +project(Cemu VERSION 0.1) +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +set_property(GLOBAL PROPERTY USE_FOLDERS ON) + +IF(MSVC) + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /fp:precise") # floating point model: precise + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /GT") # fiber safe optimizations +ENDIF() + +IF(MSVC AND PUBLIC_RELEASE) + message(STATUS "Using additional optimization flags for MSVC") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Oi /Ot") # enable intrinsic functions, favor speed +ENDIF() + +option(ENABLE_OPENGL "Enables the OpenGL backend" ON) +option(ENABLE_VULKAN "Enables the Vulkan backend" ON) +option(ENABLE_DISCORD_RPC "Enables the Discord Rich Presence feature" ON) + +if(WIN32) + option(ENABLE_CEMUHOOK "Enables Cemuhook compatibility" ON) +endif() + +# input backends +if(WIN32) + option(ENABLE_XINPUT "Enables the usage of XInput" ON) + option(ENABLE_DIRECTINPUT "Enables the usage of DirectInput" ON) + add_definitions(-DHAS_DIRECTINPUT) +endif() +option(ENABLE_SDL "Enables the SDLController backend" ON) + +# audio backends +if(WIN32) +option(ENABLE_DIRECTAUDIO "Enables the directaudio backend" ON) +option(ENABLE_XAUDIO "Enables the xaudio backend" ON) +endif() +option(ENABLE_CUBEB "Enabled cubeb backend" ON) + +option(ENABLE_WXWIDGETS "Build with wxWidgets UI (Currently required)" ON) + +find_package(SDL2 CONFIG REQUIRED) +find_package(CURL CONFIG REQUIRED) +find_package(pugixml CONFIG REQUIRED) +find_package(imgui CONFIG REQUIRED) +find_package(RapidJSON CONFIG REQUIRED) +find_package(Boost COMPONENTS program_options filesystem nowide REQUIRED) +find_package(libzip REQUIRED) +find_package(glslang REQUIRED) +find_package(ZLIB REQUIRED) +find_package(zstd CONFIG REQUIRED) + +if(ENABLE_VULKAN) + find_package(Vulkan REQUIRED) + include_directories("${Vulkan_INCLUDE_DIRS}") +endif() +if(ENABLE_OPENGL) + find_package(OpenGL REQUIRED) +endif() +if(ENABLE_DISCORD_RPC) + add_definitions(-DENABLE_DISCORD_RPC) + add_subdirectory(dependencies/discord-rpc EXCLUDE_FROM_ALL) + target_include_directories(discord-rpc INTERFACE ./dependencies/discord-rpc/include) +endif() + +if(ENABLE_WXWIDGETS) + find_package(wxWidgets CONFIG REQUIRED) +endif() + +find_package(OpenSSL REQUIRED) +find_package(X11) + +# find a better way to handle this +link_libraries(${Boost_LIBRARIES}) +link_libraries(${X11_LIBRARIES}) +link_libraries(SDL2::SDL2 SDL2::SDL2main SDL2::SDL2-static) +if(ENABLE_WXWIDGETS) +link_libraries(wx::core wx::base) +endif() + +if(ENABLE_CUBEB) + option(BUILD_TESTS "" OFF) + option(BUILD_TOOLS "" OFF) + option(BUNDLE_SPEEX "" OFF) + set(USE_WINMM OFF CACHE BOOL "") + add_subdirectory(dependencies/cubeb) + set_property(TARGET cubeb PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + link_libraries(cubeb) + add_compile_definitions(HAS_CUBEB=1) +endif() + +add_subdirectory(dependencies/ih264d) +add_subdirectory(dependencies/ZArchive) + +add_subdirectory(src) + diff --git a/CMakeSettings.json b/CMakeSettings.json new file mode 100644 index 00000000..fffb8fea --- /dev/null +++ b/CMakeSettings.json @@ -0,0 +1,37 @@ +{ + "configurations": [ + { + "name": "x64-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "inheritEnvironments": [ "msvc_x64_x64" ], + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "" + }, + { + "name": "x64-Release", + "generator": "Ninja", + "configurationType": "RelWithDebInfo", + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "msvc_x64_x64" ] + }, + { + "name": "x64-Public-Release", + "generator": "Ninja", + "configurationType": "RelWithDebInfo", + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "-DPUBLIC_RELEASE=ON", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "msvc_x64_x64" ] + } + ] +} \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010101a00.ini b/bin/gameProfiles/default/0005000010101a00.ini new file mode 100644 index 00000000..dc63ccb5 --- /dev/null +++ b/bin/gameProfiles/default/0005000010101a00.ini @@ -0,0 +1,7 @@ +# LEGO City Undercover (USA) + +[General] +loadSharedLibraries = false + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010101b00.ini b/bin/gameProfiles/default/0005000010101b00.ini new file mode 100644 index 00000000..21aded1d --- /dev/null +++ b/bin/gameProfiles/default/0005000010101b00.ini @@ -0,0 +1,7 @@ +# LEGO City Undercover (EUR) + +[General] +loadSharedLibraries = false + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010104d00.ini b/bin/gameProfiles/default/0005000010104d00.ini new file mode 100644 index 00000000..50a9fa48 --- /dev/null +++ b/bin/gameProfiles/default/0005000010104d00.ini @@ -0,0 +1,5 @@ +# Monster Hunter 3(tri-)GHD Ver. (JPN) + +[Graphics] +GPUBufferCacheAccuracy = 1 +streamoutBufferCacheSize = 48 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010106100.ini b/bin/gameProfiles/default/0005000010106100.ini new file mode 100644 index 00000000..77e7c399 --- /dev/null +++ b/bin/gameProfiles/default/0005000010106100.ini @@ -0,0 +1,7 @@ +# Super Mario 3D World (JPN) + +[CPU] + +[Graphics] +accurateShaderMul = false +GPUBufferCacheAccuracy = 2 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001010EB00.ini b/bin/gameProfiles/default/000500001010EB00.ini new file mode 100644 index 00000000..e4b30b16 --- /dev/null +++ b/bin/gameProfiles/default/000500001010EB00.ini @@ -0,0 +1,7 @@ +# Mario Kart 8 (JPN) + +[CPU] +cpuMode = Singlecore-Recompiler + +[Graphics] +GPUBufferCacheAccuracy = 2 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001010F900.ini b/bin/gameProfiles/default/000500001010F900.ini new file mode 100644 index 00000000..4772db75 --- /dev/null +++ b/bin/gameProfiles/default/000500001010F900.ini @@ -0,0 +1,7 @@ +# Scribblenauts Unlimited (EUR) + +[General] +loadSharedLibraries = true + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001010ac00.ini b/bin/gameProfiles/default/000500001010ac00.ini new file mode 100644 index 00000000..7df76e2f --- /dev/null +++ b/bin/gameProfiles/default/000500001010ac00.ini @@ -0,0 +1,7 @@ +# Ben 10 Omniverse (USA) + +[General] +loadSharedLibraries = false + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001010ad00.ini b/bin/gameProfiles/default/000500001010ad00.ini new file mode 100644 index 00000000..408ad7cb --- /dev/null +++ b/bin/gameProfiles/default/000500001010ad00.ini @@ -0,0 +1,4 @@ +# Darksiders II (USA) + +[CPU] +cpuMode = Singlecore-Recompiler \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001010b100.ini b/bin/gameProfiles/default/000500001010b100.ini new file mode 100644 index 00000000..e2215587 --- /dev/null +++ b/bin/gameProfiles/default/000500001010b100.ini @@ -0,0 +1,4 @@ +# Rayman Legends (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001010b200.ini b/bin/gameProfiles/default/000500001010b200.ini new file mode 100644 index 00000000..d47b4060 --- /dev/null +++ b/bin/gameProfiles/default/000500001010b200.ini @@ -0,0 +1,7 @@ +# Scribblenauts Unlimited (US) + +[General] +loadSharedLibraries = true + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001010dc00.ini b/bin/gameProfiles/default/000500001010dc00.ini new file mode 100644 index 00000000..11d0fd68 --- /dev/null +++ b/bin/gameProfiles/default/000500001010dc00.ini @@ -0,0 +1,4 @@ +# Mass Effect 3 Special Edition (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001010dd00.ini b/bin/gameProfiles/default/000500001010dd00.ini new file mode 100644 index 00000000..5a0620da --- /dev/null +++ b/bin/gameProfiles/default/000500001010dd00.ini @@ -0,0 +1,4 @@ +# ZombiU (US) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001010e600.ini b/bin/gameProfiles/default/000500001010e600.ini new file mode 100644 index 00000000..dc351608 --- /dev/null +++ b/bin/gameProfiles/default/000500001010e600.ini @@ -0,0 +1,4 @@ +# 007 Legends (US) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001010e700.ini b/bin/gameProfiles/default/000500001010e700.ini new file mode 100644 index 00000000..ea118074 --- /dev/null +++ b/bin/gameProfiles/default/000500001010e700.ini @@ -0,0 +1,7 @@ +# Cabela's Dangerous Hunts 2013 (USA) + +[General] +loadSharedLibraries = false + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001010ec00.ini b/bin/gameProfiles/default/000500001010ec00.ini new file mode 100644 index 00000000..babaf249 --- /dev/null +++ b/bin/gameProfiles/default/000500001010ec00.ini @@ -0,0 +1,7 @@ +# Mario Kart 8 (USA) + +[CPU] +cpuMode = Singlecore-Recompiler + +[Graphics] +GPUBufferCacheAccuracy = 2 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001010ed00.ini b/bin/gameProfiles/default/000500001010ed00.ini new file mode 100644 index 00000000..7935ea37 --- /dev/null +++ b/bin/gameProfiles/default/000500001010ed00.ini @@ -0,0 +1,7 @@ +# Mario Kart 8 (EUR) + +[CPU] +cpuMode = Singlecore-Recompiler + +[Graphics] +GPUBufferCacheAccuracy = 2 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001010ef00.ini b/bin/gameProfiles/default/000500001010ef00.ini new file mode 100644 index 00000000..005ba84b --- /dev/null +++ b/bin/gameProfiles/default/000500001010ef00.ini @@ -0,0 +1,4 @@ +# ZombiU (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001010f100.ini b/bin/gameProfiles/default/000500001010f100.ini new file mode 100644 index 00000000..c557498c --- /dev/null +++ b/bin/gameProfiles/default/000500001010f100.ini @@ -0,0 +1,7 @@ +# Rise of the Guardians: The Video Game (EUR) + +[General] +loadSharedLibraries = false + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001010f200.ini b/bin/gameProfiles/default/000500001010f200.ini new file mode 100644 index 00000000..055fda32 --- /dev/null +++ b/bin/gameProfiles/default/000500001010f200.ini @@ -0,0 +1,7 @@ +# Rise of the Guardians: The Video Game (USA) + +[General] +loadSharedLibraries = false + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001010f500.ini b/bin/gameProfiles/default/000500001010f500.ini new file mode 100644 index 00000000..e1a86417 --- /dev/null +++ b/bin/gameProfiles/default/000500001010f500.ini @@ -0,0 +1,4 @@ +# Mass Effect 3 Special Edition (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001011000.ini b/bin/gameProfiles/default/000500001011000.ini new file mode 100644 index 00000000..85ebf4c6 --- /dev/null +++ b/bin/gameProfiles/default/000500001011000.ini @@ -0,0 +1,7 @@ +# Ben 10 Omniverse (EUR) + +[General] +loadSharedLibraries = false + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010110100.ini b/bin/gameProfiles/default/0005000010110100.ini new file mode 100644 index 00000000..69b988cf --- /dev/null +++ b/bin/gameProfiles/default/0005000010110100.ini @@ -0,0 +1,4 @@ +# Nano Assault Neo (USA) + +[Graphics] +GPUBufferCacheAccuracy = 2 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010110600.ini b/bin/gameProfiles/default/0005000010110600.ini new file mode 100644 index 00000000..fb19b607 --- /dev/null +++ b/bin/gameProfiles/default/0005000010110600.ini @@ -0,0 +1,4 @@ +# Nano Assault Neo (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 2 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010110700.ini b/bin/gameProfiles/default/0005000010110700.ini new file mode 100644 index 00000000..e2d361c4 --- /dev/null +++ b/bin/gameProfiles/default/0005000010110700.ini @@ -0,0 +1,4 @@ +# 007 Legends (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010110E00.ini b/bin/gameProfiles/default/0005000010110E00.ini new file mode 100644 index 00000000..3064350f --- /dev/null +++ b/bin/gameProfiles/default/0005000010110E00.ini @@ -0,0 +1,8 @@ +# Super Smash Bros for Wii U (JPN) + +[CPU] +cpuMode = Singlecore-Recompiler + +[Graphics] +streamoutBufferCacheSize = 48 +GPUBufferCacheAccuracy = 2 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010110f00.ini b/bin/gameProfiles/default/0005000010110f00.ini new file mode 100644 index 00000000..81bf9d64 --- /dev/null +++ b/bin/gameProfiles/default/0005000010110f00.ini @@ -0,0 +1,4 @@ +# Darksiders II (EUR) + +[CPU] +cpuMode = Singlecore-Recompiler \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010111400.ini b/bin/gameProfiles/default/0005000010111400.ini new file mode 100644 index 00000000..7fee0ed1 --- /dev/null +++ b/bin/gameProfiles/default/0005000010111400.ini @@ -0,0 +1,4 @@ +# Rayman Legends (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010111600.ini b/bin/gameProfiles/default/0005000010111600.ini new file mode 100644 index 00000000..6b433160 --- /dev/null +++ b/bin/gameProfiles/default/0005000010111600.ini @@ -0,0 +1,7 @@ +# Fast And Furious Showdown (USA) + +[General] +loadSharedLibraries = false + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010112000.ini b/bin/gameProfiles/default/0005000010112000.ini new file mode 100644 index 00000000..853fc36b --- /dev/null +++ b/bin/gameProfiles/default/0005000010112000.ini @@ -0,0 +1,4 @@ +# The Croods: Prehistoric Party! (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010112300.ini b/bin/gameProfiles/default/0005000010112300.ini new file mode 100644 index 00000000..5fe50f24 --- /dev/null +++ b/bin/gameProfiles/default/0005000010112300.ini @@ -0,0 +1,4 @@ +# ZombiU (JPN) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010113000.ini b/bin/gameProfiles/default/0005000010113000.ini new file mode 100644 index 00000000..30330b89 --- /dev/null +++ b/bin/gameProfiles/default/0005000010113000.ini @@ -0,0 +1,4 @@ +# Mass Effect 3 Special Edition (JPN) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010113300.ini b/bin/gameProfiles/default/0005000010113300.ini new file mode 100644 index 00000000..af55851d --- /dev/null +++ b/bin/gameProfiles/default/0005000010113300.ini @@ -0,0 +1,4 @@ +# The Smurfs 2 (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010113d00.ini b/bin/gameProfiles/default/0005000010113d00.ini new file mode 100644 index 00000000..51369705 --- /dev/null +++ b/bin/gameProfiles/default/0005000010113d00.ini @@ -0,0 +1,4 @@ +# Rapala Pro Bass Fishing (US) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010115d00.ini b/bin/gameProfiles/default/0005000010115d00.ini new file mode 100644 index 00000000..5a00554b --- /dev/null +++ b/bin/gameProfiles/default/0005000010115d00.ini @@ -0,0 +1,4 @@ +# The Smurfs 2 (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010116100.ini b/bin/gameProfiles/default/0005000010116100.ini new file mode 100644 index 00000000..8f01e5fa --- /dev/null +++ b/bin/gameProfiles/default/0005000010116100.ini @@ -0,0 +1,8 @@ +# Xenoblade Chronicles X (JPN) + +[CPU] +cpuMode = Singlecore-Recompiler + +[Graphics] +accurateShaderMul = false +GPUBufferCacheAccuracy = 2 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010117200.ini b/bin/gameProfiles/default/0005000010117200.ini new file mode 100644 index 00000000..e802c38e --- /dev/null +++ b/bin/gameProfiles/default/0005000010117200.ini @@ -0,0 +1,5 @@ +# Monster Hunter 3 Ultimate (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 1 +streamoutBufferCacheSize = 48 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010118300.ini b/bin/gameProfiles/default/0005000010118300.ini new file mode 100644 index 00000000..4ba8d937 --- /dev/null +++ b/bin/gameProfiles/default/0005000010118300.ini @@ -0,0 +1,5 @@ +# Monster Hunter 3 Ultimate (USA) + +[Graphics] +GPUBufferCacheAccuracy = 1 +streamoutBufferCacheSize = 48 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001011a600.ini b/bin/gameProfiles/default/000500001011a600.ini new file mode 100644 index 00000000..7c673d05 --- /dev/null +++ b/bin/gameProfiles/default/000500001011a600.ini @@ -0,0 +1,7 @@ +# Cabela's Dangerous Hunts 2013 (EUR) + +[General] +loadSharedLibraries = false + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001011af00.ini b/bin/gameProfiles/default/000500001011af00.ini new file mode 100644 index 00000000..c7bc4826 --- /dev/null +++ b/bin/gameProfiles/default/000500001011af00.ini @@ -0,0 +1,4 @@ +# BIT.TRIP Presents... Runner2: Future Legend of Rhythm Alien (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001011b200.ini b/bin/gameProfiles/default/000500001011b200.ini new file mode 100644 index 00000000..64edf0af --- /dev/null +++ b/bin/gameProfiles/default/000500001011b200.ini @@ -0,0 +1,7 @@ +# Little Inferno (US) + +[CPU] +extendedTextureReadback = true + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010128600.ini b/bin/gameProfiles/default/0005000010128600.ini new file mode 100644 index 00000000..467f0014 --- /dev/null +++ b/bin/gameProfiles/default/0005000010128600.ini @@ -0,0 +1,5 @@ +# Little Inferno (EUR) + +[CPU] +extendedTextureReadback = true +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010129000.ini b/bin/gameProfiles/default/0005000010129000.ini new file mode 100644 index 00000000..e1a9d9c5 --- /dev/null +++ b/bin/gameProfiles/default/0005000010129000.ini @@ -0,0 +1,8 @@ +# DuckTales: Remastered (USA) + +[CPU] +cpuMode = Singlecore-Recompiler + +[Graphics] +GPUBufferCacheAccuracy = 0 +streamoutBufferCacheSize = 48 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010129200.ini b/bin/gameProfiles/default/0005000010129200.ini new file mode 100644 index 00000000..33c2f21a --- /dev/null +++ b/bin/gameProfiles/default/0005000010129200.ini @@ -0,0 +1,8 @@ +# DuckTales: Remastered (EUR) + +[CPU] +cpuMode = Singlecore-Recompiler + +[Graphics] +GPUBufferCacheAccuracy = 0 +streamoutBufferCacheSize = 48 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001012BC00.ini b/bin/gameProfiles/default/000500001012BC00.ini new file mode 100644 index 00000000..c7adb846 --- /dev/null +++ b/bin/gameProfiles/default/000500001012BC00.ini @@ -0,0 +1,8 @@ +# Pikmin 3 (JPN) + + + +[Graphics] + +extendedTextureReadback = true +GPUBufferCacheAccuracy = 2 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001012BD00.ini b/bin/gameProfiles/default/000500001012BD00.ini new file mode 100644 index 00000000..8ab10175 --- /dev/null +++ b/bin/gameProfiles/default/000500001012BD00.ini @@ -0,0 +1,8 @@ +# Pikmin 3 (USA) + + + +[Graphics] + +extendedTextureReadback = true +GPUBufferCacheAccuracy = 2 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001012F000.ini b/bin/gameProfiles/default/000500001012F000.ini new file mode 100644 index 00000000..af1229e6 --- /dev/null +++ b/bin/gameProfiles/default/000500001012F000.ini @@ -0,0 +1,7 @@ +# FAST Racing NEO (USA) + +[CPU] +cpuMode = Singlecore-Recompiler + +[Graphics] +GPUBufferCacheAccuracy = 1 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001012b200.ini b/bin/gameProfiles/default/000500001012b200.ini new file mode 100644 index 00000000..b00c1a04 --- /dev/null +++ b/bin/gameProfiles/default/000500001012b200.ini @@ -0,0 +1,9 @@ +# Deus Ex: Human Revolution – Director's Cut (EUR) + +[General] +loadSharedLibraries = false + +[Graphics] +GPUBufferCacheAccuracy = 0 +extendedTextureReadback = true +streamoutBufferCacheSize = 48 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001012ba00.ini b/bin/gameProfiles/default/000500001012ba00.ini new file mode 100644 index 00000000..7e94a7fd --- /dev/null +++ b/bin/gameProfiles/default/000500001012ba00.ini @@ -0,0 +1,9 @@ +# Deus Ex: Human Revolution – Director's Cut (USA) + +[General] +loadSharedLibraries = false + +[Graphics] +GPUBufferCacheAccuracy = 0 +extendedTextureReadback = true +streamoutBufferCacheSize = 48 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001012be00.ini b/bin/gameProfiles/default/000500001012be00.ini new file mode 100644 index 00000000..2987dd1e --- /dev/null +++ b/bin/gameProfiles/default/000500001012be00.ini @@ -0,0 +1,7 @@ +# Pikmin 3 (EU) + + + +[Graphics] + +extendedTextureReadback = true \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001012c500.ini b/bin/gameProfiles/default/000500001012c500.ini new file mode 100644 index 00000000..9badd49e --- /dev/null +++ b/bin/gameProfiles/default/000500001012c500.ini @@ -0,0 +1,4 @@ +# The Croods: Prehistoric Party! (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001012da00.ini b/bin/gameProfiles/default/000500001012da00.ini new file mode 100644 index 00000000..4f478ace --- /dev/null +++ b/bin/gameProfiles/default/000500001012da00.ini @@ -0,0 +1,7 @@ +# Fast And Furious Showdown (EUR) + +[General] +loadSharedLibraries = false + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010131F00.ini b/bin/gameProfiles/default/0005000010131F00.ini new file mode 100644 index 00000000..9b2dde57 --- /dev/null +++ b/bin/gameProfiles/default/0005000010131F00.ini @@ -0,0 +1,4 @@ +# Yoshi's Woolly World (JPN) + +[Graphics] +GPUBufferCacheAccuracy = 2 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010132c00.ini b/bin/gameProfiles/default/0005000010132c00.ini new file mode 100644 index 00000000..00e83b4c --- /dev/null +++ b/bin/gameProfiles/default/0005000010132c00.ini @@ -0,0 +1,4 @@ +# Scribblenauts Unmasked: A DC Comics Adventure (US) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010132d00.ini b/bin/gameProfiles/default/0005000010132d00.ini new file mode 100644 index 00000000..d516cd8f --- /dev/null +++ b/bin/gameProfiles/default/0005000010132d00.ini @@ -0,0 +1,4 @@ +# Scribblenauts Unmasked: A DC Comics Adventure (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010133b00.ini b/bin/gameProfiles/default/0005000010133b00.ini new file mode 100644 index 00000000..465ef364 --- /dev/null +++ b/bin/gameProfiles/default/0005000010133b00.ini @@ -0,0 +1,4 @@ +# Sniper Elite V2 (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 1 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010134e00.ini b/bin/gameProfiles/default/0005000010134e00.ini new file mode 100644 index 00000000..e04cdd74 --- /dev/null +++ b/bin/gameProfiles/default/0005000010134e00.ini @@ -0,0 +1,4 @@ +# Sniper Elite V2 (USA) + +[Graphics] +GPUBufferCacheAccuracy = 1 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010135500.ini b/bin/gameProfiles/default/0005000010135500.ini new file mode 100644 index 00000000..cc008930 --- /dev/null +++ b/bin/gameProfiles/default/0005000010135500.ini @@ -0,0 +1,7 @@ +# LEGO Batman 2: DC Super Heroes (EUR) + +[General] +loadSharedLibraries = false + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010135e00.ini b/bin/gameProfiles/default/0005000010135e00.ini new file mode 100644 index 00000000..ea135acc --- /dev/null +++ b/bin/gameProfiles/default/0005000010135e00.ini @@ -0,0 +1,5 @@ +# LEGO Batman 2: DC Super Heroes (USA) + +[General] +loadSharedLibraries = false +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010136300.ini b/bin/gameProfiles/default/0005000010136300.ini new file mode 100644 index 00000000..19e3176f --- /dev/null +++ b/bin/gameProfiles/default/0005000010136300.ini @@ -0,0 +1,4 @@ +# BIT.TRIP Presents... Runner2: Future Legend of Rhythm Alien (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010136c00.ini b/bin/gameProfiles/default/0005000010136c00.ini new file mode 100644 index 00000000..8d9d5448 --- /dev/null +++ b/bin/gameProfiles/default/0005000010136c00.ini @@ -0,0 +1,8 @@ +# Batman: Arkham Origins (EUR) + +[CPU] +cpuMode = Singlecore-Recompiler + +[Graphics] +GPUBufferCacheAccuracy = 0 +streamoutBufferCacheSize = 48 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010137F00.ini b/bin/gameProfiles/default/0005000010137F00.ini new file mode 100644 index 00000000..c9cddecc --- /dev/null +++ b/bin/gameProfiles/default/0005000010137F00.ini @@ -0,0 +1,4 @@ +# DKC: Tropical Freeze (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010137c00.ini b/bin/gameProfiles/default/0005000010137c00.ini new file mode 100644 index 00000000..db36989b --- /dev/null +++ b/bin/gameProfiles/default/0005000010137c00.ini @@ -0,0 +1,8 @@ +# Batman: Arkham Origins (USA) + +[CPU] +cpuMode = Singlecore-Recompiler + +[Graphics] +GPUBufferCacheAccuracy = 0 +streamoutBufferCacheSize = 48 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010138300.ini b/bin/gameProfiles/default/0005000010138300.ini new file mode 100644 index 00000000..4385df2e --- /dev/null +++ b/bin/gameProfiles/default/0005000010138300.ini @@ -0,0 +1,4 @@ +# DKC: Tropical Freeze (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010138a00.ini b/bin/gameProfiles/default/0005000010138a00.ini new file mode 100644 index 00000000..8a4f621e --- /dev/null +++ b/bin/gameProfiles/default/0005000010138a00.ini @@ -0,0 +1,4 @@ +# Angry Birds Trilogy (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010138f00.ini b/bin/gameProfiles/default/0005000010138f00.ini new file mode 100644 index 00000000..df1f79ea --- /dev/null +++ b/bin/gameProfiles/default/0005000010138f00.ini @@ -0,0 +1,5 @@ +# Devil's Third (JPN) + +[Graphics] +GPUBufferCacheAccuracy = 0 +streamoutBufferCacheSize = 48 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010140000.ini b/bin/gameProfiles/default/0005000010140000.ini new file mode 100644 index 00000000..9afff7f4 --- /dev/null +++ b/bin/gameProfiles/default/0005000010140000.ini @@ -0,0 +1,4 @@ +# Angry Birds Trilogy (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010143500.ini b/bin/gameProfiles/default/0005000010143500.ini new file mode 100644 index 00000000..a9266121 --- /dev/null +++ b/bin/gameProfiles/default/0005000010143500.ini @@ -0,0 +1,6 @@ +# TLoZ: Wind Waker HD (USA) + +[CPU] + +[Graphics] +GPUBufferCacheAccuracy = 2 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010143600.ini b/bin/gameProfiles/default/0005000010143600.ini new file mode 100644 index 00000000..6ba5a4c0 --- /dev/null +++ b/bin/gameProfiles/default/0005000010143600.ini @@ -0,0 +1,6 @@ +# TLoZ: Wind Waker HD (EUR) + +[CPU] + +[Graphics] +GPUBufferCacheAccuracy = 2 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010144800.ini b/bin/gameProfiles/default/0005000010144800.ini new file mode 100644 index 00000000..55a4e962 --- /dev/null +++ b/bin/gameProfiles/default/0005000010144800.ini @@ -0,0 +1,4 @@ +# DKC: Tropical Freeze (JPN) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010144f00.ini b/bin/gameProfiles/default/0005000010144f00.ini new file mode 100644 index 00000000..6bbc9e28 --- /dev/null +++ b/bin/gameProfiles/default/0005000010144f00.ini @@ -0,0 +1,8 @@ +# Super Smash Bros for Wii U (USA) + +[CPU] +cpuMode = Singlecore-Recompiler + +[Graphics] +streamoutBufferCacheSize = 48 +GPUBufferCacheAccuracy = 2 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010145000.ini b/bin/gameProfiles/default/0005000010145000.ini new file mode 100644 index 00000000..f286269f --- /dev/null +++ b/bin/gameProfiles/default/0005000010145000.ini @@ -0,0 +1,8 @@ +# Super Smash Bros for Wii U (EUR) + +[CPU] +cpuMode = Singlecore-Recompiler + +[Graphics] +streamoutBufferCacheSize = 48 +GPUBufferCacheAccuracy = 2 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010145c00.ini b/bin/gameProfiles/default/0005000010145c00.ini new file mode 100644 index 00000000..b82867e4 --- /dev/null +++ b/bin/gameProfiles/default/0005000010145c00.ini @@ -0,0 +1,7 @@ +# Super Mario 3D World (USA) + +[CPU] + +[Graphics] +accurateShaderMul = false +GPUBufferCacheAccuracy = 2 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010145d00.ini b/bin/gameProfiles/default/0005000010145d00.ini new file mode 100644 index 00000000..99c70dfe --- /dev/null +++ b/bin/gameProfiles/default/0005000010145d00.ini @@ -0,0 +1,7 @@ +# Super Mario 3D World (EUR) + +[CPU] + +[Graphics] +accurateShaderMul = false +GPUBufferCacheAccuracy = 2 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010147e00.ini b/bin/gameProfiles/default/0005000010147e00.ini new file mode 100644 index 00000000..f5f73b9c --- /dev/null +++ b/bin/gameProfiles/default/0005000010147e00.ini @@ -0,0 +1,4 @@ +# Hello Kitty Kruisers (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001014c100.ini b/bin/gameProfiles/default/000500001014c100.ini new file mode 100644 index 00000000..da5f4d3a --- /dev/null +++ b/bin/gameProfiles/default/000500001014c100.ini @@ -0,0 +1,4 @@ +# Transformers: Rise of the Dark Spark (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001014c600.ini b/bin/gameProfiles/default/000500001014c600.ini new file mode 100644 index 00000000..d83ea1d8 --- /dev/null +++ b/bin/gameProfiles/default/000500001014c600.ini @@ -0,0 +1,4 @@ +# Giana Sisters Twisted Dreams (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001014cb00.ini b/bin/gameProfiles/default/000500001014cb00.ini new file mode 100644 index 00000000..24e6e8d9 --- /dev/null +++ b/bin/gameProfiles/default/000500001014cb00.ini @@ -0,0 +1,4 @@ +# Giana Sisters Twisted Dreams (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001014d900.ini b/bin/gameProfiles/default/000500001014d900.ini new file mode 100644 index 00000000..b3fae051 --- /dev/null +++ b/bin/gameProfiles/default/000500001014d900.ini @@ -0,0 +1,4 @@ +# PUYO PUYO TETRIS (JPN) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010154600.ini b/bin/gameProfiles/default/0005000010154600.ini new file mode 100644 index 00000000..8bdd8b98 --- /dev/null +++ b/bin/gameProfiles/default/0005000010154600.ini @@ -0,0 +1,8 @@ +# Batman: Arkham Origins (JPN) + +[CPU] +cpuMode = Singlecore-Recompiler + +[Graphics] +GPUBufferCacheAccuracy = 0 +streamoutBufferCacheSize = 48 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001015b200.ini b/bin/gameProfiles/default/000500001015b200.ini new file mode 100644 index 00000000..fd7aad16 --- /dev/null +++ b/bin/gameProfiles/default/000500001015b200.ini @@ -0,0 +1,4 @@ +# Child of Light (USA) + +[Graphics] +GPUBufferCacheAccuracy = 1 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010161a00.ini b/bin/gameProfiles/default/0005000010161a00.ini new file mode 100644 index 00000000..8bd28664 --- /dev/null +++ b/bin/gameProfiles/default/0005000010161a00.ini @@ -0,0 +1,4 @@ +# How to Train Your Dragon 2 (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010162200.ini b/bin/gameProfiles/default/0005000010162200.ini new file mode 100644 index 00000000..70e5edf4 --- /dev/null +++ b/bin/gameProfiles/default/0005000010162200.ini @@ -0,0 +1,4 @@ +# Monkey Pirates (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010162a00.ini b/bin/gameProfiles/default/0005000010162a00.ini new file mode 100644 index 00000000..e20011d0 --- /dev/null +++ b/bin/gameProfiles/default/0005000010162a00.ini @@ -0,0 +1,4 @@ +# How to Train Your Dragon 2 (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010162b00.ini b/bin/gameProfiles/default/0005000010162b00.ini new file mode 100644 index 00000000..9802ca1d --- /dev/null +++ b/bin/gameProfiles/default/0005000010162b00.ini @@ -0,0 +1,6 @@ +# Splatoon (JPN) + +[CPU] + +[Graphics] +GPUBufferCacheAccuracy = 2 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001016a200.ini b/bin/gameProfiles/default/000500001016a200.ini new file mode 100644 index 00000000..da6a7805 --- /dev/null +++ b/bin/gameProfiles/default/000500001016a200.ini @@ -0,0 +1,4 @@ +# Bombing Bastards (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001016ab00.ini b/bin/gameProfiles/default/000500001016ab00.ini new file mode 100644 index 00000000..0090ca58 --- /dev/null +++ b/bin/gameProfiles/default/000500001016ab00.ini @@ -0,0 +1,4 @@ +# Bombing Bastards (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001016b200.ini b/bin/gameProfiles/default/000500001016b200.ini new file mode 100644 index 00000000..9dd87948 --- /dev/null +++ b/bin/gameProfiles/default/000500001016b200.ini @@ -0,0 +1,11 @@ +# Master Reboot (USA) + +[General] +useRDTSC = false + +[CPU] +cpuTimer = cycleCounter +cpuMode = Singlecore-Interpreter + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001016d400.ini b/bin/gameProfiles/default/000500001016d400.ini new file mode 100644 index 00000000..74f031ec --- /dev/null +++ b/bin/gameProfiles/default/000500001016d400.ini @@ -0,0 +1,4 @@ +# Stick It to the Man (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001016d800.ini b/bin/gameProfiles/default/000500001016d800.ini new file mode 100644 index 00000000..3cd2b3d0 --- /dev/null +++ b/bin/gameProfiles/default/000500001016d800.ini @@ -0,0 +1,4 @@ +# Child of Light (JPN) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001016da00.ini b/bin/gameProfiles/default/000500001016da00.ini new file mode 100644 index 00000000..105b4406 --- /dev/null +++ b/bin/gameProfiles/default/000500001016da00.ini @@ -0,0 +1,7 @@ +# Nihilumbra (EUR) + +[CPU] +cpuMode = Singlecore-Interpreter + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001016e000.ini b/bin/gameProfiles/default/000500001016e000.ini new file mode 100644 index 00000000..871da06e --- /dev/null +++ b/bin/gameProfiles/default/000500001016e000.ini @@ -0,0 +1,4 @@ +# Stick It to the Man (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001016e800.ini b/bin/gameProfiles/default/000500001016e800.ini new file mode 100644 index 00000000..13482456 --- /dev/null +++ b/bin/gameProfiles/default/000500001016e800.ini @@ -0,0 +1,11 @@ +# Master Reboot (EUR) + +[General] +useRDTSC = false + +[CPU] +cpuTimer = cycleCounter +cpuMode = Singlecore-Interpreter + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001016ea00.ini b/bin/gameProfiles/default/000500001016ea00.ini new file mode 100644 index 00000000..6d72c2a2 --- /dev/null +++ b/bin/gameProfiles/default/000500001016ea00.ini @@ -0,0 +1,4 @@ +# Child of Light (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 1 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001016fc00.ini b/bin/gameProfiles/default/000500001016fc00.ini new file mode 100644 index 00000000..edd44103 --- /dev/null +++ b/bin/gameProfiles/default/000500001016fc00.ini @@ -0,0 +1,5 @@ +# Aqua Moto Racing Utopia (USA) + +[GPU] +GPUBufferCacheAccuracy = 0 +streamoutBufferCacheSize = 48 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010170100.ini b/bin/gameProfiles/default/0005000010170100.ini new file mode 100644 index 00000000..848f53ff --- /dev/null +++ b/bin/gameProfiles/default/0005000010170100.ini @@ -0,0 +1,4 @@ +# Monkey Pirates (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010172900.ini b/bin/gameProfiles/default/0005000010172900.ini new file mode 100644 index 00000000..0414258a --- /dev/null +++ b/bin/gameProfiles/default/0005000010172900.ini @@ -0,0 +1,5 @@ +# Aqua Moto Racing Utopia (EUR) + +[GPU] +GPUBufferCacheAccuracy = 0 +streamoutBufferCacheSize = 48 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010173400.ini b/bin/gameProfiles/default/0005000010173400.ini new file mode 100644 index 00000000..99995af4 --- /dev/null +++ b/bin/gameProfiles/default/0005000010173400.ini @@ -0,0 +1,4 @@ +# Transformers: Rise of the Dark Spark (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010176300.ini b/bin/gameProfiles/default/0005000010176300.ini new file mode 100644 index 00000000..c8e0861c --- /dev/null +++ b/bin/gameProfiles/default/0005000010176300.ini @@ -0,0 +1,5 @@ +# Little Inferno (JPN) + +[CPU] +extendedTextureReadback = true +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010176900.ini b/bin/gameProfiles/default/0005000010176900.ini new file mode 100644 index 00000000..d6d36fb8 --- /dev/null +++ b/bin/gameProfiles/default/0005000010176900.ini @@ -0,0 +1,6 @@ +# Splatoon (USA) + +[CPU] + +[Graphics] +GPUBufferCacheAccuracy = 2 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010176a00.ini b/bin/gameProfiles/default/0005000010176a00.ini new file mode 100644 index 00000000..f52d1658 --- /dev/null +++ b/bin/gameProfiles/default/0005000010176a00.ini @@ -0,0 +1,6 @@ +# Splatoon (EUR) + +[CPU] + +[Graphics] +GPUBufferCacheAccuracy = 2 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010177000.ini b/bin/gameProfiles/default/0005000010177000.ini new file mode 100644 index 00000000..1c47b1dc --- /dev/null +++ b/bin/gameProfiles/default/0005000010177000.ini @@ -0,0 +1,4 @@ +# Hello Kitty Kruisers (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010177500.ini b/bin/gameProfiles/default/0005000010177500.ini new file mode 100644 index 00000000..74a0a58e --- /dev/null +++ b/bin/gameProfiles/default/0005000010177500.ini @@ -0,0 +1,7 @@ +# Nihilumbra (USA) + +[CPU] +cpuMode = Singlecore-Interpreter + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010177600.ini b/bin/gameProfiles/default/0005000010177600.ini new file mode 100644 index 00000000..05792513 --- /dev/null +++ b/bin/gameProfiles/default/0005000010177600.ini @@ -0,0 +1,5 @@ +# Devil's Third (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 +streamoutBufferCacheSize = 48 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010177700.ini b/bin/gameProfiles/default/0005000010177700.ini new file mode 100644 index 00000000..072db711 --- /dev/null +++ b/bin/gameProfiles/default/0005000010177700.ini @@ -0,0 +1,5 @@ +# Devil's Third (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 +streamoutBufferCacheSize = 48 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001017d800.ini b/bin/gameProfiles/default/000500001017d800.ini new file mode 100644 index 00000000..76f937c7 --- /dev/null +++ b/bin/gameProfiles/default/000500001017d800.ini @@ -0,0 +1,4 @@ +# Hyrule Warriors (USA) + +[General] +useRDTSC = false \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001017d900.ini b/bin/gameProfiles/default/000500001017d900.ini new file mode 100644 index 00000000..dfd75789 --- /dev/null +++ b/bin/gameProfiles/default/000500001017d900.ini @@ -0,0 +1,4 @@ +# Hyrule Warriors (EUR) + +[General] +useRDTSC = false \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001017da00.ini b/bin/gameProfiles/default/000500001017da00.ini new file mode 100644 index 00000000..6fc7282a --- /dev/null +++ b/bin/gameProfiles/default/000500001017da00.ini @@ -0,0 +1,4 @@ +# Costume Quest 2 (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001017e400.ini b/bin/gameProfiles/default/000500001017e400.ini new file mode 100644 index 00000000..562aa77a --- /dev/null +++ b/bin/gameProfiles/default/000500001017e400.ini @@ -0,0 +1,4 @@ +# Chasing Dead (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 diff --git a/bin/gameProfiles/default/0005000010180500.ini b/bin/gameProfiles/default/0005000010180500.ini new file mode 100644 index 00000000..d9258613 --- /dev/null +++ b/bin/gameProfiles/default/0005000010180500.ini @@ -0,0 +1,7 @@ +# Captain Toad: Treasure Tracker (JPN) + +[CPU] + +[Graphics] +accurateShaderMul = false +GPUBufferCacheAccuracy = 2 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010180600.ini b/bin/gameProfiles/default/0005000010180600.ini new file mode 100644 index 00000000..e7d7e325 --- /dev/null +++ b/bin/gameProfiles/default/0005000010180600.ini @@ -0,0 +1,7 @@ +# Captain Toad: Treasure Tracker (USA) + +[CPU] + +[Graphics] +accurateShaderMul = false +GPUBufferCacheAccuracy = 2 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010180700.ini b/bin/gameProfiles/default/0005000010180700.ini new file mode 100644 index 00000000..d0b7d792 --- /dev/null +++ b/bin/gameProfiles/default/0005000010180700.ini @@ -0,0 +1,7 @@ +# Captain Toad: Treasure Tracker (EUR) + +[CPU] + +[Graphics] +accurateShaderMul = false +GPUBufferCacheAccuracy = 2 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010183000.ini b/bin/gameProfiles/default/0005000010183000.ini new file mode 100644 index 00000000..5b79f8a9 --- /dev/null +++ b/bin/gameProfiles/default/0005000010183000.ini @@ -0,0 +1,7 @@ +# Runbow (USA) + + + +[Graphics] + +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010184900.ini b/bin/gameProfiles/default/0005000010184900.ini new file mode 100644 index 00000000..8c4d5b59 --- /dev/null +++ b/bin/gameProfiles/default/0005000010184900.ini @@ -0,0 +1,4 @@ +# Slender: The Arrival (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010184E00.ini b/bin/gameProfiles/default/0005000010184E00.ini new file mode 100644 index 00000000..3ca24eda --- /dev/null +++ b/bin/gameProfiles/default/0005000010184E00.ini @@ -0,0 +1,6 @@ +# Yoshi's Woolly World (EUR) + +[CPU] + +[Graphics] +GPUBufferCacheAccuracy = 2 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010184d00.ini b/bin/gameProfiles/default/0005000010184d00.ini new file mode 100644 index 00000000..83a64a5e --- /dev/null +++ b/bin/gameProfiles/default/0005000010184d00.ini @@ -0,0 +1,6 @@ +# Yoshi's Woolly World (USA) + +[CPU] + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010185400.ini b/bin/gameProfiles/default/0005000010185400.ini new file mode 100644 index 00000000..c69416ee --- /dev/null +++ b/bin/gameProfiles/default/0005000010185400.ini @@ -0,0 +1,6 @@ +# TLoZ: Wind Waker HD (JPN) + +[CPU] + +[Graphics] +GPUBufferCacheAccuracy = 2 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010189f00.ini b/bin/gameProfiles/default/0005000010189f00.ini new file mode 100644 index 00000000..a88c6891 --- /dev/null +++ b/bin/gameProfiles/default/0005000010189f00.ini @@ -0,0 +1,7 @@ +# Shadow Puppeteer (USA) + +[CPU] +cpuTimer = hostBased + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001018ab00.ini b/bin/gameProfiles/default/000500001018ab00.ini new file mode 100644 index 00000000..9eeef2ff --- /dev/null +++ b/bin/gameProfiles/default/000500001018ab00.ini @@ -0,0 +1,4 @@ +# Affordable Space Adventures (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 diff --git a/bin/gameProfiles/default/000500001018d800.ini b/bin/gameProfiles/default/000500001018d800.ini new file mode 100644 index 00000000..0e311245 --- /dev/null +++ b/bin/gameProfiles/default/000500001018d800.ini @@ -0,0 +1,4 @@ +# SteamWorld Dig (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001018d900.ini b/bin/gameProfiles/default/000500001018d900.ini new file mode 100644 index 00000000..4e8d8ead --- /dev/null +++ b/bin/gameProfiles/default/000500001018d900.ini @@ -0,0 +1,7 @@ +# Lost Reavers (JPN) + +[General] +loadSharedLibraries = false + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001018f100.ini b/bin/gameProfiles/default/000500001018f100.ini new file mode 100644 index 00000000..1a3c5fba --- /dev/null +++ b/bin/gameProfiles/default/000500001018f100.ini @@ -0,0 +1,4 @@ +# SteamWorld Dig (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001018f400.ini b/bin/gameProfiles/default/000500001018f400.ini new file mode 100644 index 00000000..ecf014cf --- /dev/null +++ b/bin/gameProfiles/default/000500001018f400.ini @@ -0,0 +1,4 @@ +# Angry Video Game Nerd Adventures (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001018fd00.ini b/bin/gameProfiles/default/000500001018fd00.ini new file mode 100644 index 00000000..119e00c4 --- /dev/null +++ b/bin/gameProfiles/default/000500001018fd00.ini @@ -0,0 +1,4 @@ +# The Penguins of Madagascar (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010190300.ini b/bin/gameProfiles/default/0005000010190300.ini new file mode 100644 index 00000000..b9a41d4f --- /dev/null +++ b/bin/gameProfiles/default/0005000010190300.ini @@ -0,0 +1,11 @@ +# Mario and Sonic at the Rio 2016 Olympic Games (JPN) + +[General] +useRDTSC = false + +[CPU] +cpuMode = Singlecore-Recompiler +cpuTimer = cycleCounter + +[Graphics] +GPUBufferCacheAccuracy = 1 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010193f00.ini b/bin/gameProfiles/default/0005000010193f00.ini new file mode 100644 index 00000000..9d7f8a63 --- /dev/null +++ b/bin/gameProfiles/default/0005000010193f00.ini @@ -0,0 +1,4 @@ +# The Penguins of Madagascar (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010195e00.ini b/bin/gameProfiles/default/0005000010195e00.ini new file mode 100644 index 00000000..23d72923 --- /dev/null +++ b/bin/gameProfiles/default/0005000010195e00.ini @@ -0,0 +1,4 @@ +# Rock Zombie (US) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010197300.ini b/bin/gameProfiles/default/0005000010197300.ini new file mode 100644 index 00000000..e15f1c7d --- /dev/null +++ b/bin/gameProfiles/default/0005000010197300.ini @@ -0,0 +1,4 @@ +# Electronic Super Joy Groove City (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010197800.ini b/bin/gameProfiles/default/0005000010197800.ini new file mode 100644 index 00000000..92839df2 --- /dev/null +++ b/bin/gameProfiles/default/0005000010197800.ini @@ -0,0 +1,4 @@ +# Costume Quest 2 (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010199e00.ini b/bin/gameProfiles/default/0005000010199e00.ini new file mode 100644 index 00000000..12c31235 --- /dev/null +++ b/bin/gameProfiles/default/0005000010199e00.ini @@ -0,0 +1,4 @@ +# High Strangeness (US) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001019ab00.ini b/bin/gameProfiles/default/000500001019ab00.ini new file mode 100644 index 00000000..12c31235 --- /dev/null +++ b/bin/gameProfiles/default/000500001019ab00.ini @@ -0,0 +1,4 @@ +# High Strangeness (US) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001019b000.ini b/bin/gameProfiles/default/000500001019b000.ini new file mode 100644 index 00000000..23e77a87 --- /dev/null +++ b/bin/gameProfiles/default/000500001019b000.ini @@ -0,0 +1,4 @@ +# Tachyon Project (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001019b200.ini b/bin/gameProfiles/default/000500001019b200.ini new file mode 100644 index 00000000..6790a314 --- /dev/null +++ b/bin/gameProfiles/default/000500001019b200.ini @@ -0,0 +1,4 @@ +# Tachyon Project (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001019ca00.ini b/bin/gameProfiles/default/000500001019ca00.ini new file mode 100644 index 00000000..9187ed88 --- /dev/null +++ b/bin/gameProfiles/default/000500001019ca00.ini @@ -0,0 +1,4 @@ +# Rock Zombie (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001019e500.ini b/bin/gameProfiles/default/000500001019e500.ini new file mode 100644 index 00000000..0ee89ea1 --- /dev/null +++ b/bin/gameProfiles/default/000500001019e500.ini @@ -0,0 +1,6 @@ +# TLoZ: Twilight Princess (US) + +[CPU] + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001019e600.ini b/bin/gameProfiles/default/000500001019e600.ini new file mode 100644 index 00000000..4f3aeecb --- /dev/null +++ b/bin/gameProfiles/default/000500001019e600.ini @@ -0,0 +1,6 @@ +# TLoZ: Twilight Princess (EU) + +[CPU] + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001019ea00.ini b/bin/gameProfiles/default/000500001019ea00.ini new file mode 100644 index 00000000..f69c7740 --- /dev/null +++ b/bin/gameProfiles/default/000500001019ea00.ini @@ -0,0 +1,4 @@ +# Zombeer (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001019ee00.ini b/bin/gameProfiles/default/000500001019ee00.ini new file mode 100644 index 00000000..accf6993 --- /dev/null +++ b/bin/gameProfiles/default/000500001019ee00.ini @@ -0,0 +1,4 @@ +# Zombie Defense (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101C9300.ini b/bin/gameProfiles/default/00050000101C9300.ini new file mode 100644 index 00000000..f3c94e8f --- /dev/null +++ b/bin/gameProfiles/default/00050000101C9300.ini @@ -0,0 +1,6 @@ +# TLoZ: Breath of the Wild (JPN) + +[Graphics] +disableGPUFence = false +accurateShaderMul = true +GPUBufferCacheAccuracy = 2 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101E4100.ini b/bin/gameProfiles/default/00050000101E4100.ini new file mode 100644 index 00000000..71176e83 --- /dev/null +++ b/bin/gameProfiles/default/00050000101E4100.ini @@ -0,0 +1,7 @@ +# FAST Racing NEO (JPN) + +[CPU] +cpuMode = Singlecore-Recompiler + +[Graphics] +GPUBufferCacheAccuracy = 1 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101a1200.ini b/bin/gameProfiles/default/00050000101a1200.ini new file mode 100644 index 00000000..76b1207a --- /dev/null +++ b/bin/gameProfiles/default/00050000101a1200.ini @@ -0,0 +1,5 @@ +# Affordable Space Adventures (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 + diff --git a/bin/gameProfiles/default/00050000101a1800.ini b/bin/gameProfiles/default/00050000101a1800.ini new file mode 100644 index 00000000..94c50e45 --- /dev/null +++ b/bin/gameProfiles/default/00050000101a1800.ini @@ -0,0 +1,4 @@ +# Zombie Defense (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101a3b00.ini b/bin/gameProfiles/default/00050000101a3b00.ini new file mode 100644 index 00000000..2efa5016 --- /dev/null +++ b/bin/gameProfiles/default/00050000101a3b00.ini @@ -0,0 +1,4 @@ +# Life of Pixel (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101a4300.ini b/bin/gameProfiles/default/00050000101a4300.ini new file mode 100644 index 00000000..1c49a9ab --- /dev/null +++ b/bin/gameProfiles/default/00050000101a4300.ini @@ -0,0 +1,4 @@ +# Beatbuddy (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101a4800.ini b/bin/gameProfiles/default/00050000101a4800.ini new file mode 100644 index 00000000..bb446c86 --- /dev/null +++ b/bin/gameProfiles/default/00050000101a4800.ini @@ -0,0 +1,7 @@ +# Lost Reavers (USA) + +[General] +loadSharedLibraries = false + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101a4900.ini b/bin/gameProfiles/default/00050000101a4900.ini new file mode 100644 index 00000000..74fedef3 --- /dev/null +++ b/bin/gameProfiles/default/00050000101a4900.ini @@ -0,0 +1,4 @@ +# Life of Pixel (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101a7f00.ini b/bin/gameProfiles/default/00050000101a7f00.ini new file mode 100644 index 00000000..35f91d55 --- /dev/null +++ b/bin/gameProfiles/default/00050000101a7f00.ini @@ -0,0 +1,4 @@ +# Shiftlings (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101a9c00.ini b/bin/gameProfiles/default/00050000101a9c00.ini new file mode 100644 index 00000000..651c8c01 --- /dev/null +++ b/bin/gameProfiles/default/00050000101a9c00.ini @@ -0,0 +1,4 @@ +# Chompy Chomp Chomp Party (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101a9e00.ini b/bin/gameProfiles/default/00050000101a9e00.ini new file mode 100644 index 00000000..5fbd157c --- /dev/null +++ b/bin/gameProfiles/default/00050000101a9e00.ini @@ -0,0 +1,4 @@ +# Chompy Chomp Chomp Party (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101acc00.ini b/bin/gameProfiles/default/00050000101acc00.ini new file mode 100644 index 00000000..d94071ac --- /dev/null +++ b/bin/gameProfiles/default/00050000101acc00.ini @@ -0,0 +1,4 @@ +# Shiftlings (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101b0100.ini b/bin/gameProfiles/default/00050000101b0100.ini new file mode 100644 index 00000000..ed2f7bf9 --- /dev/null +++ b/bin/gameProfiles/default/00050000101b0100.ini @@ -0,0 +1,4 @@ +# Funk of Titans (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101b4e00.ini b/bin/gameProfiles/default/00050000101b4e00.ini new file mode 100644 index 00000000..38642cbe --- /dev/null +++ b/bin/gameProfiles/default/00050000101b4e00.ini @@ -0,0 +1,4 @@ +# Funk of Titans (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101b9900.ini b/bin/gameProfiles/default/00050000101b9900.ini new file mode 100644 index 00000000..ae410226 --- /dev/null +++ b/bin/gameProfiles/default/00050000101b9900.ini @@ -0,0 +1,7 @@ +# Lost Reavers (EUR) + +[General] +loadSharedLibraries = false + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101bc300.ini b/bin/gameProfiles/default/00050000101bc300.ini new file mode 100644 index 00000000..c7ec1da5 --- /dev/null +++ b/bin/gameProfiles/default/00050000101bc300.ini @@ -0,0 +1,4 @@ +# Beatbuddy (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101c3100.ini b/bin/gameProfiles/default/00050000101c3100.ini new file mode 100644 index 00000000..d7c2e293 --- /dev/null +++ b/bin/gameProfiles/default/00050000101c3100.ini @@ -0,0 +1,4 @@ +# Freedom Planet (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101c4200.ini b/bin/gameProfiles/default/00050000101c4200.ini new file mode 100644 index 00000000..203ce9a7 --- /dev/null +++ b/bin/gameProfiles/default/00050000101c4200.ini @@ -0,0 +1,4 @@ +# The Peanuts Movie: Snoopy's Grand Adventure (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101c4300.ini b/bin/gameProfiles/default/00050000101c4300.ini new file mode 100644 index 00000000..a2a79f38 --- /dev/null +++ b/bin/gameProfiles/default/00050000101c4300.ini @@ -0,0 +1,7 @@ +# Don't Starve: Giant Edition (USA) + +[CPU] +cpuMode = Singlecore-Recompiler + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101c4c00.ini b/bin/gameProfiles/default/00050000101c4c00.ini new file mode 100644 index 00000000..6e8fd71f --- /dev/null +++ b/bin/gameProfiles/default/00050000101c4c00.ini @@ -0,0 +1,8 @@ +# Xenoblade Chronicles X (EUR) + +[CPU] +cpuMode = Singlecore-Recompiler + +[Graphics] +accurateShaderMul = false +GPUBufferCacheAccuracy = 2 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101c4d00.ini b/bin/gameProfiles/default/00050000101c4d00.ini new file mode 100644 index 00000000..83301c0d --- /dev/null +++ b/bin/gameProfiles/default/00050000101c4d00.ini @@ -0,0 +1,8 @@ +# Xenoblade Chronicles X (USA) + +[CPU] +cpuMode = Singlecore-Recompiler + +[Graphics] +accurateShaderMul = false +GPUBufferCacheAccuracy = 2 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101c6c00.ini b/bin/gameProfiles/default/00050000101c6c00.ini new file mode 100644 index 00000000..da9dd712 --- /dev/null +++ b/bin/gameProfiles/default/00050000101c6c00.ini @@ -0,0 +1,4 @@ +# Typoman (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101c7600.ini b/bin/gameProfiles/default/00050000101c7600.ini new file mode 100644 index 00000000..14128c7b --- /dev/null +++ b/bin/gameProfiles/default/00050000101c7600.ini @@ -0,0 +1,4 @@ +# Pumped BMX + (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101c7b00.ini b/bin/gameProfiles/default/00050000101c7b00.ini new file mode 100644 index 00000000..7350d0b5 --- /dev/null +++ b/bin/gameProfiles/default/00050000101c7b00.ini @@ -0,0 +1,4 @@ +# Chronicles of Teddy: Harmony of Exidus (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101c7d00.ini b/bin/gameProfiles/default/00050000101c7d00.ini new file mode 100644 index 00000000..45ce4448 --- /dev/null +++ b/bin/gameProfiles/default/00050000101c7d00.ini @@ -0,0 +1,4 @@ +# Pumped BMX + (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101c9400.ini b/bin/gameProfiles/default/00050000101c9400.ini new file mode 100644 index 00000000..86c1b839 --- /dev/null +++ b/bin/gameProfiles/default/00050000101c9400.ini @@ -0,0 +1,6 @@ +# TLoZ: Breath of the Wild (USA) + +[Graphics] +disableGPUFence = false +accurateShaderMul = true +GPUBufferCacheAccuracy = 2 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101c9500.ini b/bin/gameProfiles/default/00050000101c9500.ini new file mode 100644 index 00000000..4f326b32 --- /dev/null +++ b/bin/gameProfiles/default/00050000101c9500.ini @@ -0,0 +1,6 @@ +# TLoZ: Breath of the Wild (EUR) + +[Graphics] +disableGPUFence = false +accurateShaderMul = true +GPUBufferCacheAccuracy = 2 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101c9a00.ini b/bin/gameProfiles/default/00050000101c9a00.ini new file mode 100644 index 00000000..0cc7525b --- /dev/null +++ b/bin/gameProfiles/default/00050000101c9a00.ini @@ -0,0 +1,7 @@ +# Don't Starve: Giant Edition (EUR) + +[CPU] +cpuMode = Singlecore-Recompiler + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101cc900.ini b/bin/gameProfiles/default/00050000101cc900.ini new file mode 100644 index 00000000..dded48b2 --- /dev/null +++ b/bin/gameProfiles/default/00050000101cc900.ini @@ -0,0 +1,4 @@ +# Freedom Planet (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101ccf00.ini b/bin/gameProfiles/default/00050000101ccf00.ini new file mode 100644 index 00000000..e71dfd8f --- /dev/null +++ b/bin/gameProfiles/default/00050000101ccf00.ini @@ -0,0 +1,4 @@ +# Never Alone (Kisima Ingitchuna) (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101ce000.ini b/bin/gameProfiles/default/00050000101ce000.ini new file mode 100644 index 00000000..69aa9dae --- /dev/null +++ b/bin/gameProfiles/default/00050000101ce000.ini @@ -0,0 +1,4 @@ +# Typoman (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101ce800.ini b/bin/gameProfiles/default/00050000101ce800.ini new file mode 100644 index 00000000..2cc75f8e --- /dev/null +++ b/bin/gameProfiles/default/00050000101ce800.ini @@ -0,0 +1,4 @@ +# Never Alone (Kisima Ingitchuna) (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101d2100.ini b/bin/gameProfiles/default/00050000101d2100.ini new file mode 100644 index 00000000..caac5cdc --- /dev/null +++ b/bin/gameProfiles/default/00050000101d2100.ini @@ -0,0 +1,5 @@ +# Little Inferno (US) + +[CPU] +extendedTextureReadback = true +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101d4500.ini b/bin/gameProfiles/default/00050000101d4500.ini new file mode 100644 index 00000000..8ae9fc73 --- /dev/null +++ b/bin/gameProfiles/default/00050000101d4500.ini @@ -0,0 +1,4 @@ +# Grumpy Reaper (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101d4d00.ini b/bin/gameProfiles/default/00050000101d4d00.ini new file mode 100644 index 00000000..5df2ddbb --- /dev/null +++ b/bin/gameProfiles/default/00050000101d4d00.ini @@ -0,0 +1,4 @@ +# Joe's Diner (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101d5000.ini b/bin/gameProfiles/default/00050000101d5000.ini new file mode 100644 index 00000000..2e1b8356 --- /dev/null +++ b/bin/gameProfiles/default/00050000101d5000.ini @@ -0,0 +1,4 @@ +# The Peanuts Movie: Snoopy's Grand Adventure (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101d5500.ini b/bin/gameProfiles/default/00050000101d5500.ini new file mode 100644 index 00000000..81bcef30 --- /dev/null +++ b/bin/gameProfiles/default/00050000101d5500.ini @@ -0,0 +1,4 @@ +# Joe's Diner (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101d6000.ini b/bin/gameProfiles/default/00050000101d6000.ini new file mode 100644 index 00000000..129477c2 --- /dev/null +++ b/bin/gameProfiles/default/00050000101d6000.ini @@ -0,0 +1,7 @@ +# FAST Racing NEO (EUR) + +[CPU] +cpuMode = Singlecore-Recompiler + +[Graphics] +GPUBufferCacheAccuracy = 1 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101d6d00.ini b/bin/gameProfiles/default/00050000101d6d00.ini new file mode 100644 index 00000000..0bf677ba --- /dev/null +++ b/bin/gameProfiles/default/00050000101d6d00.ini @@ -0,0 +1,7 @@ +# Runbow (EUR) + + + +[Graphics] + +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101d7500.ini b/bin/gameProfiles/default/00050000101d7500.ini new file mode 100644 index 00000000..8707efcd --- /dev/null +++ b/bin/gameProfiles/default/00050000101d7500.ini @@ -0,0 +1,7 @@ +# Minecraft: Wii U Edition (EUR) + +[General] +loadSharedLibraries = false + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101d8900.ini b/bin/gameProfiles/default/00050000101d8900.ini new file mode 100644 index 00000000..29b9698f --- /dev/null +++ b/bin/gameProfiles/default/00050000101d8900.ini @@ -0,0 +1,4 @@ +# Slender: The Arrival (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101d8d00.ini b/bin/gameProfiles/default/00050000101d8d00.ini new file mode 100644 index 00000000..864c446b --- /dev/null +++ b/bin/gameProfiles/default/00050000101d8d00.ini @@ -0,0 +1,4 @@ +# Rock 'N Racing Off Road DX (USA) + +[Graphics] +GPUBufferCacheAccuracy = 1 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101d9600.ini b/bin/gameProfiles/default/00050000101d9600.ini new file mode 100644 index 00000000..c2c62b6a --- /dev/null +++ b/bin/gameProfiles/default/00050000101d9600.ini @@ -0,0 +1,4 @@ +# Rock 'N Racing Off Road DX (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 1 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101d9d00.ini b/bin/gameProfiles/default/00050000101d9d00.ini new file mode 100644 index 00000000..dc54dea2 --- /dev/null +++ b/bin/gameProfiles/default/00050000101d9d00.ini @@ -0,0 +1,7 @@ +# Minecraft: Wii U Edition (USA) + +[General] +loadSharedLibraries = false + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101dac00.ini b/bin/gameProfiles/default/00050000101dac00.ini new file mode 100644 index 00000000..1780613f --- /dev/null +++ b/bin/gameProfiles/default/00050000101dac00.ini @@ -0,0 +1,4 @@ +# Swap Fire + +[GPU] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101daf00.ini b/bin/gameProfiles/default/00050000101daf00.ini new file mode 100644 index 00000000..2c8bef7b --- /dev/null +++ b/bin/gameProfiles/default/00050000101daf00.ini @@ -0,0 +1,4 @@ +# Chronicles of Teddy: Harmony of Exidus (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101db000.ini b/bin/gameProfiles/default/00050000101db000.ini new file mode 100644 index 00000000..94736dbb --- /dev/null +++ b/bin/gameProfiles/default/00050000101db000.ini @@ -0,0 +1,4 @@ +# Oddworld New 'n' Tasty (US) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101dbb00.ini b/bin/gameProfiles/default/00050000101dbb00.ini new file mode 100644 index 00000000..092b54ec --- /dev/null +++ b/bin/gameProfiles/default/00050000101dbb00.ini @@ -0,0 +1,4 @@ +# Oddworld New 'n' Tasty (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101dbc00.ini b/bin/gameProfiles/default/00050000101dbc00.ini new file mode 100644 index 00000000..98c41e22 --- /dev/null +++ b/bin/gameProfiles/default/00050000101dbc00.ini @@ -0,0 +1,4 @@ +# Star Ghost (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101dbe00.ini b/bin/gameProfiles/default/00050000101dbe00.ini new file mode 100644 index 00000000..66ec5de9 --- /dev/null +++ b/bin/gameProfiles/default/00050000101dbe00.ini @@ -0,0 +1,7 @@ +# Minecraft: Wii U Edition (JPN) + +[General] +loadSharedLibraries = false + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101dbf00.ini b/bin/gameProfiles/default/00050000101dbf00.ini new file mode 100644 index 00000000..6f32b59d --- /dev/null +++ b/bin/gameProfiles/default/00050000101dbf00.ini @@ -0,0 +1,4 @@ +# Angry Video Game Nerd Adventures (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101dc000.ini b/bin/gameProfiles/default/00050000101dc000.ini new file mode 100644 index 00000000..b4b8717c --- /dev/null +++ b/bin/gameProfiles/default/00050000101dc000.ini @@ -0,0 +1,4 @@ +# Vektor Wars (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101dc200.ini b/bin/gameProfiles/default/00050000101dc200.ini new file mode 100644 index 00000000..0cec8b6d --- /dev/null +++ b/bin/gameProfiles/default/00050000101dc200.ini @@ -0,0 +1,4 @@ +# Vektor Wars (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101dd000.ini b/bin/gameProfiles/default/00050000101dd000.ini new file mode 100644 index 00000000..8f275d8c --- /dev/null +++ b/bin/gameProfiles/default/00050000101dd000.ini @@ -0,0 +1,4 @@ +# Star Ghost (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101dd600.ini b/bin/gameProfiles/default/00050000101dd600.ini new file mode 100644 index 00000000..7b180555 --- /dev/null +++ b/bin/gameProfiles/default/00050000101dd600.ini @@ -0,0 +1,4 @@ +# BIT.TRIP Presents... Runner2: Future Legend of Rhythm Alien (JPN) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101dd700.ini b/bin/gameProfiles/default/00050000101dd700.ini new file mode 100644 index 00000000..57b3a897 --- /dev/null +++ b/bin/gameProfiles/default/00050000101dd700.ini @@ -0,0 +1,7 @@ +# Runbow (JPN) + + + +[Graphics] + +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101ddf00.ini b/bin/gameProfiles/default/00050000101ddf00.ini new file mode 100644 index 00000000..a3adf2fc --- /dev/null +++ b/bin/gameProfiles/default/00050000101ddf00.ini @@ -0,0 +1,4 @@ +# Hive Jump (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101e0100.ini b/bin/gameProfiles/default/00050000101e0100.ini new file mode 100644 index 00000000..13f74180 --- /dev/null +++ b/bin/gameProfiles/default/00050000101e0100.ini @@ -0,0 +1,6 @@ +# Minecraft: Story Mode (USA) + + + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101e1800.ini b/bin/gameProfiles/default/00050000101e1800.ini new file mode 100644 index 00000000..bf35ca87 --- /dev/null +++ b/bin/gameProfiles/default/00050000101e1800.ini @@ -0,0 +1,4 @@ +# Human Resource Machine (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101e1a00.ini b/bin/gameProfiles/default/00050000101e1a00.ini new file mode 100644 index 00000000..23dca6a5 --- /dev/null +++ b/bin/gameProfiles/default/00050000101e1a00.ini @@ -0,0 +1,4 @@ +# Human Resource Machine (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101e1b00.ini b/bin/gameProfiles/default/00050000101e1b00.ini new file mode 100644 index 00000000..5675969a --- /dev/null +++ b/bin/gameProfiles/default/00050000101e1b00.ini @@ -0,0 +1,7 @@ +# Shadow Puppeteer (EUR) + +[CPU] +cpuTimer = hostBased + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101e3800.ini b/bin/gameProfiles/default/00050000101e3800.ini new file mode 100644 index 00000000..4e6f4859 --- /dev/null +++ b/bin/gameProfiles/default/00050000101e3800.ini @@ -0,0 +1,7 @@ +# Dual Core (USA) + +[General] +loadSharedLibraries = false + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101e4200.ini b/bin/gameProfiles/default/00050000101e4200.ini new file mode 100644 index 00000000..9e6a983b --- /dev/null +++ b/bin/gameProfiles/default/00050000101e4200.ini @@ -0,0 +1,11 @@ +# Master Reboot (JPN) + +[General] +useRDTSC = false + +[CPU] +cpuTimer = cycleCounter +cpuMode = Singlecore-Interpreter + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101e5300.ini b/bin/gameProfiles/default/00050000101e5300.ini new file mode 100644 index 00000000..c6f6674a --- /dev/null +++ b/bin/gameProfiles/default/00050000101e5300.ini @@ -0,0 +1,11 @@ +# Mario and Sonic at the Rio 2016 Olympic Games (USA) + +[General] +useRDTSC = false + +[CPU] +cpuMode = Singlecore-Recompiler +cpuTimer = cycleCounter + +[Graphics] +GPUBufferCacheAccuracy = 1 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101e5400.ini b/bin/gameProfiles/default/00050000101e5400.ini new file mode 100644 index 00000000..299c9e40 --- /dev/null +++ b/bin/gameProfiles/default/00050000101e5400.ini @@ -0,0 +1,11 @@ +# Mario and Sonic at the Rio 2016 Olympic Games (EUR) + +[General] +useRDTSC = false + +[CPU] +cpuMode = Singlecore-Recompiler +cpuTimer = cycleCounter + +[Graphics] +GPUBufferCacheAccuracy = 1 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101e5e00.ini b/bin/gameProfiles/default/00050000101e5e00.ini new file mode 100644 index 00000000..2736143b --- /dev/null +++ b/bin/gameProfiles/default/00050000101e5e00.ini @@ -0,0 +1,4 @@ +# Chasing Dead (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 diff --git a/bin/gameProfiles/default/00050000101e7300.ini b/bin/gameProfiles/default/00050000101e7300.ini new file mode 100644 index 00000000..de2d9ace --- /dev/null +++ b/bin/gameProfiles/default/00050000101e7300.ini @@ -0,0 +1,4 @@ +# The Deer God (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101e7400.ini b/bin/gameProfiles/default/00050000101e7400.ini new file mode 100644 index 00000000..38a64064 --- /dev/null +++ b/bin/gameProfiles/default/00050000101e7400.ini @@ -0,0 +1,4 @@ +# Grumpy Reaper (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101e9300.ini b/bin/gameProfiles/default/00050000101e9300.ini new file mode 100644 index 00000000..a88c29bc --- /dev/null +++ b/bin/gameProfiles/default/00050000101e9300.ini @@ -0,0 +1,4 @@ +# Gear Gauntlet (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101e9400.ini b/bin/gameProfiles/default/00050000101e9400.ini new file mode 100644 index 00000000..6f914578 --- /dev/null +++ b/bin/gameProfiles/default/00050000101e9400.ini @@ -0,0 +1,4 @@ +# Gear Gauntlet (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101eb300.ini b/bin/gameProfiles/default/00050000101eb300.ini new file mode 100644 index 00000000..e5b62760 --- /dev/null +++ b/bin/gameProfiles/default/00050000101eb300.ini @@ -0,0 +1,4 @@ +# The Beggar's Ride (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101ec700.ini b/bin/gameProfiles/default/00050000101ec700.ini new file mode 100644 index 00000000..27e0f560 --- /dev/null +++ b/bin/gameProfiles/default/00050000101ec700.ini @@ -0,0 +1,4 @@ +# The Beggar's Ride (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101ecf00.ini b/bin/gameProfiles/default/00050000101ecf00.ini new file mode 100644 index 00000000..3191607c --- /dev/null +++ b/bin/gameProfiles/default/00050000101ecf00.ini @@ -0,0 +1,4 @@ +# Buddy & Me Dream Edition (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101f1300.ini b/bin/gameProfiles/default/00050000101f1300.ini new file mode 100644 index 00000000..3b17b3a5 --- /dev/null +++ b/bin/gameProfiles/default/00050000101f1300.ini @@ -0,0 +1,4 @@ +# Armikrog (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101f2800.ini b/bin/gameProfiles/default/00050000101f2800.ini new file mode 100644 index 00000000..65c08d62 --- /dev/null +++ b/bin/gameProfiles/default/00050000101f2800.ini @@ -0,0 +1,7 @@ +# 8Bit Hero (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 + +[CPU] +cpuMode = Singlecore-Interpreter \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101f4a00.ini b/bin/gameProfiles/default/00050000101f4a00.ini new file mode 100644 index 00000000..2d9432da --- /dev/null +++ b/bin/gameProfiles/default/00050000101f4a00.ini @@ -0,0 +1,4 @@ +# Buddy & Me Dream Edition (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101f5700.ini b/bin/gameProfiles/default/00050000101f5700.ini new file mode 100644 index 00000000..a2d5119f --- /dev/null +++ b/bin/gameProfiles/default/00050000101f5700.ini @@ -0,0 +1,4 @@ +# Jotun Valhalla Edition (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101f6f00.ini b/bin/gameProfiles/default/00050000101f6f00.ini new file mode 100644 index 00000000..92bf8b9e --- /dev/null +++ b/bin/gameProfiles/default/00050000101f6f00.ini @@ -0,0 +1,4 @@ +# Jotun Valhalla Edition (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101f7600.ini b/bin/gameProfiles/default/00050000101f7600.ini new file mode 100644 index 00000000..88e55d2a --- /dev/null +++ b/bin/gameProfiles/default/00050000101f7600.ini @@ -0,0 +1,7 @@ +# Dual Core (EUR) + +[General] +loadSharedLibraries = false + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101f9700.ini b/bin/gameProfiles/default/00050000101f9700.ini new file mode 100644 index 00000000..34c46581 --- /dev/null +++ b/bin/gameProfiles/default/00050000101f9700.ini @@ -0,0 +1,4 @@ +# Darksiders Warmastered Edition (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101fa600.ini b/bin/gameProfiles/default/00050000101fa600.ini new file mode 100644 index 00000000..89ab8ade --- /dev/null +++ b/bin/gameProfiles/default/00050000101fa600.ini @@ -0,0 +1,4 @@ +# Darksiders Warmastered Edition (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101fd100.ini b/bin/gameProfiles/default/00050000101fd100.ini new file mode 100644 index 00000000..6ce5a608 --- /dev/null +++ b/bin/gameProfiles/default/00050000101fd100.ini @@ -0,0 +1,4 @@ +# Grumpy Reaper (JPN) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101ff200.ini b/bin/gameProfiles/default/00050000101ff200.ini new file mode 100644 index 00000000..54e13b65 --- /dev/null +++ b/bin/gameProfiles/default/00050000101ff200.ini @@ -0,0 +1,4 @@ +# Exile's End (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101ffc00.ini b/bin/gameProfiles/default/00050000101ffc00.ini new file mode 100644 index 00000000..e01c407c --- /dev/null +++ b/bin/gameProfiles/default/00050000101ffc00.ini @@ -0,0 +1,5 @@ +# Ghost Blade HD (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 +streamoutBufferCacheSize = 48 \ No newline at end of file diff --git a/bin/gameProfiles/default/00050000101ffe00.ini b/bin/gameProfiles/default/00050000101ffe00.ini new file mode 100644 index 00000000..9225f393 --- /dev/null +++ b/bin/gameProfiles/default/00050000101ffe00.ini @@ -0,0 +1,4 @@ +# Tetrimos (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010200300.ini b/bin/gameProfiles/default/0005000010200300.ini new file mode 100644 index 00000000..47600923 --- /dev/null +++ b/bin/gameProfiles/default/0005000010200300.ini @@ -0,0 +1,4 @@ +# Armikrog (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010200b00.ini b/bin/gameProfiles/default/0005000010200b00.ini new file mode 100644 index 00000000..8fc9da4b --- /dev/null +++ b/bin/gameProfiles/default/0005000010200b00.ini @@ -0,0 +1,4 @@ +# Tetrimos (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010204a00.ini b/bin/gameProfiles/default/0005000010204a00.ini new file mode 100644 index 00000000..8a0285c3 --- /dev/null +++ b/bin/gameProfiles/default/0005000010204a00.ini @@ -0,0 +1,4 @@ +# Exile's End (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010207300.ini b/bin/gameProfiles/default/0005000010207300.ini new file mode 100644 index 00000000..c678e7d6 --- /dev/null +++ b/bin/gameProfiles/default/0005000010207300.ini @@ -0,0 +1,4 @@ +# Koi DX (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010207500.ini b/bin/gameProfiles/default/0005000010207500.ini new file mode 100644 index 00000000..090be3f2 --- /dev/null +++ b/bin/gameProfiles/default/0005000010207500.ini @@ -0,0 +1,4 @@ +# Koi DX (USA) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001020a200.ini b/bin/gameProfiles/default/000500001020a200.ini new file mode 100644 index 00000000..bef0c82e --- /dev/null +++ b/bin/gameProfiles/default/000500001020a200.ini @@ -0,0 +1,5 @@ +# Minecraft: Story Mode (EUR) + + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500001020b600.ini b/bin/gameProfiles/default/000500001020b600.ini new file mode 100644 index 00000000..74528c38 --- /dev/null +++ b/bin/gameProfiles/default/000500001020b600.ini @@ -0,0 +1,5 @@ +# Ghost Blade HD (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 +streamoutBufferCacheSize = 48 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000010211b00.ini b/bin/gameProfiles/default/0005000010211b00.ini new file mode 100644 index 00000000..69534194 --- /dev/null +++ b/bin/gameProfiles/default/0005000010211b00.ini @@ -0,0 +1,4 @@ +# Sphere Slice (EUR) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000C1012BC00.ini b/bin/gameProfiles/default/0005000C1012BC00.ini new file mode 100644 index 00000000..5c2c09d4 --- /dev/null +++ b/bin/gameProfiles/default/0005000C1012BC00.ini @@ -0,0 +1,8 @@ +# Pikmin 3 (JAP) + + + +[Graphics] + +extendedTextureReadback = true +GPUBufferCacheAccuracy = 2 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000C1012BD00.ini b/bin/gameProfiles/default/0005000C1012BD00.ini new file mode 100644 index 00000000..8ab10175 --- /dev/null +++ b/bin/gameProfiles/default/0005000C1012BD00.ini @@ -0,0 +1,8 @@ +# Pikmin 3 (USA) + + + +[Graphics] + +extendedTextureReadback = true +GPUBufferCacheAccuracy = 2 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000C1012BE00.ini b/bin/gameProfiles/default/0005000C1012BE00.ini new file mode 100644 index 00000000..e7226ef4 --- /dev/null +++ b/bin/gameProfiles/default/0005000C1012BE00.ini @@ -0,0 +1,8 @@ +# Pikmin 3 (EU) + + + +[Graphics] + +extendedTextureReadback = true +GPUBufferCacheAccuracy = 2 \ No newline at end of file diff --git a/bin/gameProfiles/default/0005000e1019c800.ini b/bin/gameProfiles/default/0005000e1019c800.ini new file mode 100644 index 00000000..8c47898c --- /dev/null +++ b/bin/gameProfiles/default/0005000e1019c800.ini @@ -0,0 +1,4 @@ +# TLoZ: Twilight Princess (JPN) + +[Graphics] +GPUBufferCacheAccuracy = 0 \ No newline at end of file diff --git a/bin/gameProfiles/default/000500c01017cd00.ini b/bin/gameProfiles/default/000500c01017cd00.ini new file mode 100644 index 00000000..ce587d16 --- /dev/null +++ b/bin/gameProfiles/default/000500c01017cd00.ini @@ -0,0 +1,4 @@ +# Hyrule Warriors (JPN) + +[General] +useRDTSC = false \ No newline at end of file diff --git a/bin/gameProfiles/example.ini b/bin/gameProfiles/example.ini new file mode 100644 index 00000000..cca6dd87 --- /dev/null +++ b/bin/gameProfiles/example.ini @@ -0,0 +1,27 @@ +# This is an example configuration file. You can use this file as a guideline on how to create your own game profiles. + +# Overview: +# The filename of the game profile can be found by looking at log.txt after a game was launched in Cemu. +# A '#' character starts a one-line comment. Any text afterwards will be ignored. +# If an option is not given in the .ini file, Cemu will use the value from the global settings instead. + +[General] +loadSharedLibraries = true # If set to true, system rpl files will be loaded from /cafeLibs/ if present. Default value is true +useRDTSC = true # Use RDTSC instruction as timer source for emulated CPU, OS and audio + +[Graphics] +accurateShaderMul = true # If set to true, Cemu will correctly emulate the non-IEEE behavior of the shader MUL instruction. Can fix graphic issues but also decreases shader performance and increases shader compile time. Default value is true. +# Since Cemu 1.7.5 the option accurateShaderMul also supports a third mode, enabled by using the value 'min' (e.g. accurateShaderMul = min). In this mode, Cemu will emulate non-ieee MUL instructions in a more GPU-friendly way which generates less complex shaders. However, this mode might not be 100% accurate + +disableGPUFence = false # If set to true, GPU fence operations will be skipped. Default value is false. Enabling this option can lead to instability and crashes + +GPUBufferCacheAccuracy = 0 # Controls the accuracy of vertex and uniform data caching. A higher accuracy means more expensive checks which can slow down rendering. Possible values: 0 = high, 1 = medium, 2 = low + +streamoutBufferCacheSize = 24 # buffer cache size of the streamout buffer in MB. + +extendedTextureReadback = false # If set to true, Cemu will try to mirror data written by GPU operations to CPU RAM (but only if access by CPU is assumed to be likely) Default value is false + +[CPU] +cpuTimer = cycleCounter # Timer source for OS and CPU time. Supported values are 'hostBased' (timers are based on actual OS time) and 'cycleCounter' (timers are based on speed of emulated CPU). +emulateSinglePrecision = true # If set to false, the recompiler won't correctly round the result of single-precision instructions in certain situations. This can introduce gameplay bugs, but might also improve performance. +cpuMode = Singlecore-Recompiler # CPU mode. Possible values: Singlecore-Interpreter, Singlecore-Recompiler, Dualcore-Recompiler, Triplecore-Recompiler \ No newline at end of file diff --git a/bin/resources/ca/cemu.mo b/bin/resources/ca/cemu.mo new file mode 100644 index 00000000..7930c426 Binary files /dev/null and b/bin/resources/ca/cemu.mo differ diff --git a/bin/resources/de/cemu.mo b/bin/resources/de/cemu.mo new file mode 100644 index 00000000..47d6a964 Binary files /dev/null and b/bin/resources/de/cemu.mo differ diff --git a/bin/resources/es/cemu.mo b/bin/resources/es/cemu.mo new file mode 100644 index 00000000..93d8f7e5 Binary files /dev/null and b/bin/resources/es/cemu.mo differ diff --git a/bin/resources/fr/cemu.mo b/bin/resources/fr/cemu.mo new file mode 100644 index 00000000..09bcd9f2 Binary files /dev/null and b/bin/resources/fr/cemu.mo differ diff --git a/bin/resources/hu/cemu.mo b/bin/resources/hu/cemu.mo new file mode 100644 index 00000000..c66dd9d7 Binary files /dev/null and b/bin/resources/hu/cemu.mo differ diff --git a/bin/resources/it/cemu.mo b/bin/resources/it/cemu.mo new file mode 100644 index 00000000..a417f089 Binary files /dev/null and b/bin/resources/it/cemu.mo differ diff --git a/bin/resources/ja/cemu.mo b/bin/resources/ja/cemu.mo new file mode 100644 index 00000000..5044fc61 Binary files /dev/null and b/bin/resources/ja/cemu.mo differ diff --git a/bin/resources/ko/cemu.mo b/bin/resources/ko/cemu.mo new file mode 100644 index 00000000..27e63d6e Binary files /dev/null and b/bin/resources/ko/cemu.mo differ diff --git a/bin/resources/nb/cemu.mo b/bin/resources/nb/cemu.mo new file mode 100644 index 00000000..6b87f8c0 Binary files /dev/null and b/bin/resources/nb/cemu.mo differ diff --git a/bin/resources/nl/cemu.mo b/bin/resources/nl/cemu.mo new file mode 100644 index 00000000..5cbe532f Binary files /dev/null and b/bin/resources/nl/cemu.mo differ diff --git a/bin/resources/pl/cemu.mo b/bin/resources/pl/cemu.mo new file mode 100644 index 00000000..4ff064f9 Binary files /dev/null and b/bin/resources/pl/cemu.mo differ diff --git a/bin/resources/pt/cemu.mo b/bin/resources/pt/cemu.mo new file mode 100644 index 00000000..3b79163c Binary files /dev/null and b/bin/resources/pt/cemu.mo differ diff --git a/bin/resources/ru/cemu.mo b/bin/resources/ru/cemu.mo new file mode 100644 index 00000000..46583b20 Binary files /dev/null and b/bin/resources/ru/cemu.mo differ diff --git a/bin/resources/sharedFonts/CafeCn.ttf b/bin/resources/sharedFonts/CafeCn.ttf new file mode 100644 index 00000000..dab84818 Binary files /dev/null and b/bin/resources/sharedFonts/CafeCn.ttf differ diff --git a/bin/resources/sharedFonts/CafeKr.ttf b/bin/resources/sharedFonts/CafeKr.ttf new file mode 100644 index 00000000..aed5fb86 Binary files /dev/null and b/bin/resources/sharedFonts/CafeKr.ttf differ diff --git a/bin/resources/sharedFonts/CafeStd.ttf b/bin/resources/sharedFonts/CafeStd.ttf new file mode 100644 index 00000000..08a8bac6 Binary files /dev/null and b/bin/resources/sharedFonts/CafeStd.ttf differ diff --git a/bin/resources/sharedFonts/CafeTw.ttf b/bin/resources/sharedFonts/CafeTw.ttf new file mode 100644 index 00000000..09bf3bcd Binary files /dev/null and b/bin/resources/sharedFonts/CafeTw.ttf differ diff --git a/bin/resources/sv/cemu.mo b/bin/resources/sv/cemu.mo new file mode 100644 index 00000000..13a4e827 Binary files /dev/null and b/bin/resources/sv/cemu.mo differ diff --git a/bin/resources/tr/cemu.mo b/bin/resources/tr/cemu.mo new file mode 100644 index 00000000..0a4c3c63 Binary files /dev/null and b/bin/resources/tr/cemu.mo differ diff --git a/bin/resources/zh/cemu.mo b/bin/resources/zh/cemu.mo new file mode 100644 index 00000000..a07eb370 Binary files /dev/null and b/bin/resources/zh/cemu.mo differ diff --git a/bin/shaderCache/info.txt b/bin/shaderCache/info.txt new file mode 100644 index 00000000..962cf88b --- /dev/null +++ b/bin/shaderCache/info.txt @@ -0,0 +1 @@ +If you plan to transfer the shader cache to a different PC or Cemu installation you only need to copy the 'transferable' directory. \ No newline at end of file diff --git a/building.md b/building.md new file mode 100644 index 00000000..47ee7891 --- /dev/null +++ b/building.md @@ -0,0 +1,33 @@ +# Build instructions + +## Windows + +Prerequisites: +- A recent version of Visual Studio 2022 with CMake tools component +- git +- Vulkan SDK ([https://vulkan.lunarg.com/](https://vulkan.lunarg.com/)). Don't forget to restart after installing. + +Instructions: + +1) Run `git clone --recursive https://github.com/cemu-project/Cemu` +2) Launch `Cemu/build_vs_solution.bat`. If you installed VS to a custom location you may need to manually adjust the path inside the bat file +3) Wait until done, then open build/Cemu.sln in Visual Studio +4) Right click 'CemuBin' project -> Set as startup project +5) Then build the solution and once finished you can run and debug it + +You can also skip steps 3-5 and open the root folder of the cloned repo directly in Visual Studio (as a folder) and use the inbuilt cmake support, but be warned that cmake support in VS can be a bit finicky. + +## Linux + +To compile Cemu, a recent enough compiler and STL with C++20 support is required! We use clang 12, other compilers may work as well. + +For ubuntu and most derivatives: + +1) Make sure vulkansdk is installed and the VULKAN_SDK environment variable is set correctly. +2) `sudo apt install -y libgtk-3-dev libsecret-1-dev libgcrypt20-dev libsystemd-dev freeglut3-dev clang-12 nasm` +3) Run `git clone --recursive https://github.com/cemu-project/Cemu` +4) `cd Cemu` +5) `mkdir build && cd build` +6) `cmake .. -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/bin/clang-12 -DCMAKE_CXX_COMPILER=/usr/bin/clang++-12 -G Ninja -DCMAKE_MAKE_PROGRAM=/usr/bin/ninja` + +Build instructions for other distributions will be added in the future! diff --git a/dependencies/DirectX_2010/XAudio2.h b/dependencies/DirectX_2010/XAudio2.h new file mode 100644 index 00000000..db0ebd8f --- /dev/null +++ b/dependencies/DirectX_2010/XAudio2.h @@ -0,0 +1,1282 @@ +/************************************************************************** + * + * Copyright (c) Microsoft Corporation. All rights reserved. + * + * File: xaudio2.h + * Content: Declarations for the XAudio2 game audio API. + * + **************************************************************************/ + +#ifndef __XAUDIO2_INCLUDED__ +#define __XAUDIO2_INCLUDED__ + + +/************************************************************************** + * + * XAudio2 COM object class and interface IDs. + * + **************************************************************************/ + +#include "comdecl.h" // For DEFINE_CLSID and DEFINE_IID + +// XAudio 2.0 (March 2008 SDK) +//DEFINE_CLSID(XAudio2, fac23f48, 31f5, 45a8, b4, 9b, 52, 25, d6, 14, 01, aa); +//DEFINE_CLSID(XAudio2_Debug, fac23f48, 31f5, 45a8, b4, 9b, 52, 25, d6, 14, 01, db); + +// XAudio 2.1 (June 2008 SDK) +//DEFINE_CLSID(XAudio2, e21a7345, eb21, 468e, be, 50, 80, 4d, b9, 7c, f7, 08); +//DEFINE_CLSID(XAudio2_Debug, f7a76c21, 53d4, 46bb, ac, 53, 8b, 45, 9c, ae, 46, bd); + +// XAudio 2.2 (August 2008 SDK) +//DEFINE_CLSID(XAudio2, b802058a, 464a, 42db, bc, 10, b6, 50, d6, f2, 58, 6a); +//DEFINE_CLSID(XAudio2_Debug, 97dfb7e7, 5161, 4015, 87, a9, c7, 9e, 6a, 19, 52, cc); + +// XAudio 2.3 (November 2008 SDK) +//DEFINE_CLSID(XAudio2, 4c5e637a, 16c7, 4de3, 9c, 46, 5e, d2, 21, 81, 96, 2d); +//DEFINE_CLSID(XAudio2_Debug, ef0aa05d, 8075, 4e5d, be, ad, 45, be, 0c, 3c, cb, b3); + +// XAudio 2.4 (March 2009 SDK) +//DEFINE_CLSID(XAudio2, 03219e78, 5bc3, 44d1, b9, 2e, f6, 3d, 89, cc, 65, 26); +//DEFINE_CLSID(XAudio2_Debug, 4256535c, 1ea4, 4d4b, 8a, d5, f9, db, 76, 2e, ca, 9e); + +// XAudio 2.5 (August 2009 SDK) +//DEFINE_CLSID(XAudio2, 4c9b6dde, 6809, 46e6, a2, 78, 9b, 6a, 97, 58, 86, 70); +//DEFINE_CLSID(XAudio2_Debug, 715bdd1a, aa82, 436b, b0, fa, 6a, ce, a3, 9b, d0, a1); + +// XAudio 2.6 (February 2010 SDK) +//DEFINE_CLSID(XAudio2, 3eda9b49, 2085, 498b, 9b, b2, 39, a6, 77, 84, 93, de); +//DEFINE_CLSID(XAudio2_Debug, 47199894, 7cc2, 444d, 98, 73, ce, d2, 56, 2c, c6, 0e); + +// XAudio 2.7 (June 2010 SDK) +DEFINE_CLSID(XAudio2, 5a508685, a254, 4fba, 9b, 82, 9a, 24, b0, 03, 06, af); +DEFINE_CLSID(XAudio2_Debug, db05ea35, 0329, 4d4b, a5, 3a, 6d, ea, d0, 3d, 38, 52); +DEFINE_IID(IXAudio2, 8bcf1f58, 9fe7, 4583, 8a, c6, e2, ad, c4, 65, c8, bb); + + +// Ignore the rest of this header if only the GUID definitions were requested +#ifndef GUID_DEFS_ONLY + +#ifdef _XBOX + #include // Xbox COM declarations (IUnknown, etc) +#else + #include // Windows COM declarations +#endif + +#include // Markers for documenting API semantics +#include "audiodefs.h" // Basic audio data types and constants +#include "xma2defs.h" // Data types and constants for XMA2 audio + +// All structures defined in this file use tight field packing +#pragma pack(push, 1) + + +/************************************************************************** + * + * XAudio2 constants, flags and error codes. + * + **************************************************************************/ + +// Numeric boundary values +#define XAUDIO2_MAX_BUFFER_BYTES 0x80000000 // Maximum bytes allowed in a source buffer +#define XAUDIO2_MAX_QUEUED_BUFFERS 64 // Maximum buffers allowed in a voice queue +#define XAUDIO2_MAX_BUFFERS_SYSTEM 2 // Maximum buffers allowed for system threads (Xbox 360 only) +#define XAUDIO2_MAX_AUDIO_CHANNELS 64 // Maximum channels in an audio stream +#define XAUDIO2_MIN_SAMPLE_RATE 1000 // Minimum audio sample rate supported +#define XAUDIO2_MAX_SAMPLE_RATE 200000 // Maximum audio sample rate supported +#define XAUDIO2_MAX_VOLUME_LEVEL 16777216.0f // Maximum acceptable volume level (2^24) +#define XAUDIO2_MIN_FREQ_RATIO (1/1024.0f) // Minimum SetFrequencyRatio argument +#define XAUDIO2_MAX_FREQ_RATIO 1024.0f // Maximum MaxFrequencyRatio argument +#define XAUDIO2_DEFAULT_FREQ_RATIO 2.0f // Default MaxFrequencyRatio argument +#define XAUDIO2_MAX_FILTER_ONEOVERQ 1.5f // Maximum XAUDIO2_FILTER_PARAMETERS.OneOverQ +#define XAUDIO2_MAX_FILTER_FREQUENCY 1.0f // Maximum XAUDIO2_FILTER_PARAMETERS.Frequency +#define XAUDIO2_MAX_LOOP_COUNT 254 // Maximum non-infinite XAUDIO2_BUFFER.LoopCount +#define XAUDIO2_MAX_INSTANCES 8 // Maximum simultaneous XAudio2 objects on Xbox 360 + +// For XMA voices on Xbox 360 there is an additional restriction on the MaxFrequencyRatio +// argument and the voice's sample rate: the product of these numbers cannot exceed 600000 +// for one-channel voices or 300000 for voices with more than one channel. +#define XAUDIO2_MAX_RATIO_TIMES_RATE_XMA_MONO 600000 +#define XAUDIO2_MAX_RATIO_TIMES_RATE_XMA_MULTICHANNEL 300000 + +// Numeric values with special meanings +#define XAUDIO2_COMMIT_NOW 0 // Used as an OperationSet argument +#define XAUDIO2_COMMIT_ALL 0 // Used in IXAudio2::CommitChanges +#define XAUDIO2_INVALID_OPSET (UINT32)(-1) // Not allowed for OperationSet arguments +#define XAUDIO2_NO_LOOP_REGION 0 // Used in XAUDIO2_BUFFER.LoopCount +#define XAUDIO2_LOOP_INFINITE 255 // Used in XAUDIO2_BUFFER.LoopCount +#define XAUDIO2_DEFAULT_CHANNELS 0 // Used in CreateMasteringVoice +#define XAUDIO2_DEFAULT_SAMPLERATE 0 // Used in CreateMasteringVoice + +// Flags +#define XAUDIO2_DEBUG_ENGINE 0x0001 // Used in XAudio2Create on Windows only +#define XAUDIO2_VOICE_NOPITCH 0x0002 // Used in IXAudio2::CreateSourceVoice +#define XAUDIO2_VOICE_NOSRC 0x0004 // Used in IXAudio2::CreateSourceVoice +#define XAUDIO2_VOICE_USEFILTER 0x0008 // Used in IXAudio2::CreateSource/SubmixVoice +#define XAUDIO2_VOICE_MUSIC 0x0010 // Used in IXAudio2::CreateSourceVoice +#define XAUDIO2_PLAY_TAILS 0x0020 // Used in IXAudio2SourceVoice::Stop +#define XAUDIO2_END_OF_STREAM 0x0040 // Used in XAUDIO2_BUFFER.Flags +#define XAUDIO2_SEND_USEFILTER 0x0080 // Used in XAUDIO2_SEND_DESCRIPTOR.Flags + +// Default parameters for the built-in filter +#define XAUDIO2_DEFAULT_FILTER_TYPE LowPassFilter +#define XAUDIO2_DEFAULT_FILTER_FREQUENCY XAUDIO2_MAX_FILTER_FREQUENCY +#define XAUDIO2_DEFAULT_FILTER_ONEOVERQ 1.0f + +// Internal XAudio2 constants +#ifdef _XBOX + #define XAUDIO2_QUANTUM_NUMERATOR 2 // On Xbox 360, XAudio2 processes audio + #define XAUDIO2_QUANTUM_DENOMINATOR 375 // in 5.333ms chunks (= 2/375 seconds) +#else + #define XAUDIO2_QUANTUM_NUMERATOR 1 // On Windows, XAudio2 processes audio + #define XAUDIO2_QUANTUM_DENOMINATOR 100 // in 10ms chunks (= 1/100 seconds) +#endif +#define XAUDIO2_QUANTUM_MS (1000.0f * XAUDIO2_QUANTUM_NUMERATOR / XAUDIO2_QUANTUM_DENOMINATOR) + +// XAudio2 error codes +#define FACILITY_XAUDIO2 0x896 +#define XAUDIO2_E_INVALID_CALL 0x88960001 // An API call or one of its arguments was illegal +#define XAUDIO2_E_XMA_DECODER_ERROR 0x88960002 // The XMA hardware suffered an unrecoverable error +#define XAUDIO2_E_XAPO_CREATION_FAILED 0x88960003 // XAudio2 failed to initialize an XAPO effect +#define XAUDIO2_E_DEVICE_INVALIDATED 0x88960004 // An audio device became unusable (unplugged, etc) + + +/************************************************************************** + * + * Forward declarations for the XAudio2 interfaces. + * + **************************************************************************/ + +#ifdef __cplusplus + #define FWD_DECLARE(x) interface x +#else + #define FWD_DECLARE(x) typedef interface x x +#endif + +FWD_DECLARE(IXAudio2); +FWD_DECLARE(IXAudio2Voice); +FWD_DECLARE(IXAudio2SourceVoice); +FWD_DECLARE(IXAudio2SubmixVoice); +FWD_DECLARE(IXAudio2MasteringVoice); +FWD_DECLARE(IXAudio2EngineCallback); +FWD_DECLARE(IXAudio2VoiceCallback); + + +/************************************************************************** + * + * XAudio2 structures and enumerations. + * + **************************************************************************/ + +// Used in IXAudio2::Initialize +#ifdef _XBOX + typedef enum XAUDIO2_XBOX_HWTHREAD_SPECIFIER + { + XboxThread0 = 0x01, + XboxThread1 = 0x02, + XboxThread2 = 0x04, + XboxThread3 = 0x08, + XboxThread4 = 0x10, + XboxThread5 = 0x20, + XAUDIO2_ANY_PROCESSOR = XboxThread4, + XAUDIO2_DEFAULT_PROCESSOR = XAUDIO2_ANY_PROCESSOR + } XAUDIO2_XBOX_HWTHREAD_SPECIFIER, XAUDIO2_PROCESSOR; +#else + typedef enum XAUDIO2_WINDOWS_PROCESSOR_SPECIFIER + { + Processor1 = 0x00000001, + Processor2 = 0x00000002, + Processor3 = 0x00000004, + Processor4 = 0x00000008, + Processor5 = 0x00000010, + Processor6 = 0x00000020, + Processor7 = 0x00000040, + Processor8 = 0x00000080, + Processor9 = 0x00000100, + Processor10 = 0x00000200, + Processor11 = 0x00000400, + Processor12 = 0x00000800, + Processor13 = 0x00001000, + Processor14 = 0x00002000, + Processor15 = 0x00004000, + Processor16 = 0x00008000, + Processor17 = 0x00010000, + Processor18 = 0x00020000, + Processor19 = 0x00040000, + Processor20 = 0x00080000, + Processor21 = 0x00100000, + Processor22 = 0x00200000, + Processor23 = 0x00400000, + Processor24 = 0x00800000, + Processor25 = 0x01000000, + Processor26 = 0x02000000, + Processor27 = 0x04000000, + Processor28 = 0x08000000, + Processor29 = 0x10000000, + Processor30 = 0x20000000, + Processor31 = 0x40000000, + Processor32 = 0x80000000, + XAUDIO2_ANY_PROCESSOR = 0xffffffff, + XAUDIO2_DEFAULT_PROCESSOR = XAUDIO2_ANY_PROCESSOR + } XAUDIO2_WINDOWS_PROCESSOR_SPECIFIER, XAUDIO2_PROCESSOR; +#endif + +// Used in XAUDIO2_DEVICE_DETAILS below to describe the types of applications +// that the user has specified each device as a default for. 0 means that the +// device isn't the default for any role. +typedef enum XAUDIO2_DEVICE_ROLE +{ + NotDefaultDevice = 0x0, + DefaultConsoleDevice = 0x1, + DefaultMultimediaDevice = 0x2, + DefaultCommunicationsDevice = 0x4, + DefaultGameDevice = 0x8, + GlobalDefaultDevice = 0xf, + InvalidDeviceRole = ~GlobalDefaultDevice +} XAUDIO2_DEVICE_ROLE; + +// Returned by IXAudio2::GetDeviceDetails +typedef struct XAUDIO2_DEVICE_DETAILS +{ + WCHAR DeviceID[256]; // String identifier for the audio device. + WCHAR DisplayName[256]; // Friendly name suitable for display to a human. + XAUDIO2_DEVICE_ROLE Role; // Roles that the device should be used for. + WAVEFORMATEXTENSIBLE OutputFormat; // The device's native PCM audio output format. +} XAUDIO2_DEVICE_DETAILS; + +// Returned by IXAudio2Voice::GetVoiceDetails +typedef struct XAUDIO2_VOICE_DETAILS +{ + UINT32 CreationFlags; // Flags the voice was created with. + UINT32 InputChannels; // Channels in the voice's input audio. + UINT32 InputSampleRate; // Sample rate of the voice's input audio. +} XAUDIO2_VOICE_DETAILS; + +// Used in XAUDIO2_VOICE_SENDS below +typedef struct XAUDIO2_SEND_DESCRIPTOR +{ + UINT32 Flags; // Either 0 or XAUDIO2_SEND_USEFILTER. + IXAudio2Voice* pOutputVoice; // This send's destination voice. +} XAUDIO2_SEND_DESCRIPTOR; + +// Used in the voice creation functions and in IXAudio2Voice::SetOutputVoices +typedef struct XAUDIO2_VOICE_SENDS +{ + UINT32 SendCount; // Number of sends from this voice. + XAUDIO2_SEND_DESCRIPTOR* pSends; // Array of SendCount send descriptors. +} XAUDIO2_VOICE_SENDS; + +// Used in XAUDIO2_EFFECT_CHAIN below +typedef struct XAUDIO2_EFFECT_DESCRIPTOR +{ + IUnknown* pEffect; // Pointer to the effect object's IUnknown interface. + BOOL InitialState; // TRUE if the effect should begin in the enabled state. + UINT32 OutputChannels; // How many output channels the effect should produce. +} XAUDIO2_EFFECT_DESCRIPTOR; + +// Used in the voice creation functions and in IXAudio2Voice::SetEffectChain +typedef struct XAUDIO2_EFFECT_CHAIN +{ + UINT32 EffectCount; // Number of effects in this voice's effect chain. + XAUDIO2_EFFECT_DESCRIPTOR* pEffectDescriptors; // Array of effect descriptors. +} XAUDIO2_EFFECT_CHAIN; + +// Used in XAUDIO2_FILTER_PARAMETERS below +typedef enum XAUDIO2_FILTER_TYPE +{ + LowPassFilter, // Attenuates frequencies above the cutoff frequency. + BandPassFilter, // Attenuates frequencies outside a given range. + HighPassFilter, // Attenuates frequencies below the cutoff frequency. + NotchFilter // Attenuates frequencies inside a given range. +} XAUDIO2_FILTER_TYPE; + +// Used in IXAudio2Voice::Set/GetFilterParameters and Set/GetOutputFilterParameters +typedef struct XAUDIO2_FILTER_PARAMETERS +{ + XAUDIO2_FILTER_TYPE Type; // Low-pass, band-pass or high-pass. + float Frequency; // Radian frequency (2 * sin(pi*CutoffFrequency/SampleRate)); + // must be >= 0 and <= XAUDIO2_MAX_FILTER_FREQUENCY + // (giving a maximum CutoffFrequency of SampleRate/6). + float OneOverQ; // Reciprocal of the filter's quality factor Q; + // must be > 0 and <= XAUDIO2_MAX_FILTER_ONEOVERQ. +} XAUDIO2_FILTER_PARAMETERS; + +// Used in IXAudio2SourceVoice::SubmitSourceBuffer +typedef struct XAUDIO2_BUFFER +{ + UINT32 Flags; // Either 0 or XAUDIO2_END_OF_STREAM. + UINT32 AudioBytes; // Size of the audio data buffer in bytes. + const BYTE* pAudioData; // Pointer to the audio data buffer. + UINT32 PlayBegin; // First sample in this buffer to be played. + UINT32 PlayLength; // Length of the region to be played in samples, + // or 0 to play the whole buffer. + UINT32 LoopBegin; // First sample of the region to be looped. + UINT32 LoopLength; // Length of the desired loop region in samples, + // or 0 to loop the entire buffer. + UINT32 LoopCount; // Number of times to repeat the loop region, + // or XAUDIO2_LOOP_INFINITE to loop forever. + void* pContext; // Context value to be passed back in callbacks. +} XAUDIO2_BUFFER; + +// Used in IXAudio2SourceVoice::SubmitSourceBuffer when submitting XWMA data. +// NOTE: If an XWMA sound is submitted in more than one buffer, each buffer's +// pDecodedPacketCumulativeBytes[PacketCount-1] value must be subtracted from +// all the entries in the next buffer's pDecodedPacketCumulativeBytes array. +// And whether a sound is submitted in more than one buffer or not, the final +// buffer of the sound should use the XAUDIO2_END_OF_STREAM flag, or else the +// client must call IXAudio2SourceVoice::Discontinuity after submitting it. +typedef struct XAUDIO2_BUFFER_WMA +{ + const UINT32* pDecodedPacketCumulativeBytes; // Decoded packet's cumulative size array. + // Each element is the number of bytes accumulated + // when the corresponding XWMA packet is decoded in + // order. The array must have PacketCount elements. + UINT32 PacketCount; // Number of XWMA packets submitted. Must be >= 1 and + // divide evenly into XAUDIO2_BUFFER.AudioBytes. +} XAUDIO2_BUFFER_WMA; + +// Returned by IXAudio2SourceVoice::GetState +typedef struct XAUDIO2_VOICE_STATE +{ + void* pCurrentBufferContext; // The pContext value provided in the XAUDIO2_BUFFER + // that is currently being processed, or NULL if + // there are no buffers in the queue. + UINT32 BuffersQueued; // Number of buffers currently queued on the voice + // (including the one that is being processed). + UINT64 SamplesPlayed; // Total number of samples produced by the voice since + // it began processing the current audio stream. +} XAUDIO2_VOICE_STATE; + +// Returned by IXAudio2::GetPerformanceData +typedef struct XAUDIO2_PERFORMANCE_DATA +{ + // CPU usage information + UINT64 AudioCyclesSinceLastQuery; // CPU cycles spent on audio processing since the + // last call to StartEngine or GetPerformanceData. + UINT64 TotalCyclesSinceLastQuery; // Total CPU cycles elapsed since the last call + // (only counts the CPU XAudio2 is running on). + UINT32 MinimumCyclesPerQuantum; // Fewest CPU cycles spent processing any one + // audio quantum since the last call. + UINT32 MaximumCyclesPerQuantum; // Most CPU cycles spent processing any one + // audio quantum since the last call. + + // Memory usage information + UINT32 MemoryUsageInBytes; // Total heap space currently in use. + + // Audio latency and glitching information + UINT32 CurrentLatencyInSamples; // Minimum delay from when a sample is read from a + // source buffer to when it reaches the speakers. + UINT32 GlitchesSinceEngineStarted; // Audio dropouts since the engine was started. + + // Data about XAudio2's current workload + UINT32 ActiveSourceVoiceCount; // Source voices currently playing. + UINT32 TotalSourceVoiceCount; // Source voices currently existing. + UINT32 ActiveSubmixVoiceCount; // Submix voices currently playing/existing. + + UINT32 ActiveResamplerCount; // Resample xAPOs currently active. + UINT32 ActiveMatrixMixCount; // MatrixMix xAPOs currently active. + + // Usage of the hardware XMA decoder (Xbox 360 only) + UINT32 ActiveXmaSourceVoices; // Number of source voices decoding XMA data. + UINT32 ActiveXmaStreams; // A voice can use more than one XMA stream. +} XAUDIO2_PERFORMANCE_DATA; + +// Used in IXAudio2::SetDebugConfiguration +typedef struct XAUDIO2_DEBUG_CONFIGURATION +{ + UINT32 TraceMask; // Bitmap of enabled debug message types. + UINT32 BreakMask; // Message types that will break into the debugger. + BOOL LogThreadID; // Whether to log the thread ID with each message. + BOOL LogFileline; // Whether to log the source file and line number. + BOOL LogFunctionName; // Whether to log the function name. + BOOL LogTiming; // Whether to log message timestamps. +} XAUDIO2_DEBUG_CONFIGURATION; + +// Values for the TraceMask and BreakMask bitmaps. Only ERRORS and WARNINGS +// are valid in BreakMask. WARNINGS implies ERRORS, DETAIL implies INFO, and +// FUNC_CALLS implies API_CALLS. By default, TraceMask is ERRORS and WARNINGS +// and all the other settings are zero. +#define XAUDIO2_LOG_ERRORS 0x0001 // For handled errors with serious effects. +#define XAUDIO2_LOG_WARNINGS 0x0002 // For handled errors that may be recoverable. +#define XAUDIO2_LOG_INFO 0x0004 // Informational chit-chat (e.g. state changes). +#define XAUDIO2_LOG_DETAIL 0x0008 // More detailed chit-chat. +#define XAUDIO2_LOG_API_CALLS 0x0010 // Public API function entries and exits. +#define XAUDIO2_LOG_FUNC_CALLS 0x0020 // Internal function entries and exits. +#define XAUDIO2_LOG_TIMING 0x0040 // Delays detected and other timing data. +#define XAUDIO2_LOG_LOCKS 0x0080 // Usage of critical sections and mutexes. +#define XAUDIO2_LOG_MEMORY 0x0100 // Memory heap usage information. +#define XAUDIO2_LOG_STREAMING 0x1000 // Audio streaming information. + + +/************************************************************************** + * + * IXAudio2: Top-level XAudio2 COM interface. + * + **************************************************************************/ + +// Use default arguments if compiling as C++ +#ifdef __cplusplus + #define X2DEFAULT(x) =x +#else + #define X2DEFAULT(x) +#endif + +#undef INTERFACE +#define INTERFACE IXAudio2 +DECLARE_INTERFACE_(IXAudio2, IUnknown) +{ + // NAME: IXAudio2::QueryInterface + // DESCRIPTION: Queries for a given COM interface on the XAudio2 object. + // Only IID_IUnknown and IID_IXAudio2 are supported. + // + // ARGUMENTS: + // riid - IID of the interface to be obtained. + // ppvInterface - Returns a pointer to the requested interface. + // + STDMETHOD(QueryInterface) (THIS_ REFIID riid, __deref_out void** ppvInterface) PURE; + + // NAME: IXAudio2::AddRef + // DESCRIPTION: Adds a reference to the XAudio2 object. + // + STDMETHOD_(ULONG, AddRef) (THIS) PURE; + + // NAME: IXAudio2::Release + // DESCRIPTION: Releases a reference to the XAudio2 object. + // + STDMETHOD_(ULONG, Release) (THIS) PURE; + + // NAME: IXAudio2::GetDeviceCount + // DESCRIPTION: Returns the number of audio output devices available. + // + // ARGUMENTS: + // pCount - Returns the device count. + // + STDMETHOD(GetDeviceCount) (THIS_ __out UINT32* pCount) PURE; + + // NAME: IXAudio2::GetDeviceDetails + // DESCRIPTION: Returns information about the device with the given index. + // + // ARGUMENTS: + // Index - Index of the device to be queried. + // pDeviceDetails - Returns the device details. + // + STDMETHOD(GetDeviceDetails) (THIS_ UINT32 Index, __out XAUDIO2_DEVICE_DETAILS* pDeviceDetails) PURE; + + // NAME: IXAudio2::Initialize + // DESCRIPTION: Sets global XAudio2 parameters and prepares it for use. + // + // ARGUMENTS: + // Flags - Flags specifying the XAudio2 object's behavior. Currently unused. + // XAudio2Processor - An XAUDIO2_PROCESSOR enumeration value that specifies + // the hardware thread (Xbox) or processor (Windows) that XAudio2 will use. + // The enumeration values are platform-specific; platform-independent code + // can use XAUDIO2_DEFAULT_PROCESSOR to use the default on each platform. + // + STDMETHOD(Initialize) (THIS_ UINT32 Flags X2DEFAULT(0), + XAUDIO2_PROCESSOR XAudio2Processor X2DEFAULT(XAUDIO2_DEFAULT_PROCESSOR)) PURE; + + // NAME: IXAudio2::RegisterForCallbacks + // DESCRIPTION: Adds a new client to receive XAudio2's engine callbacks. + // + // ARGUMENTS: + // pCallback - Callback interface to be called during each processing pass. + // + STDMETHOD(RegisterForCallbacks) (__in IXAudio2EngineCallback* pCallback) PURE; + + // NAME: IXAudio2::UnregisterForCallbacks + // DESCRIPTION: Removes an existing receiver of XAudio2 engine callbacks. + // + // ARGUMENTS: + // pCallback - Previously registered callback interface to be removed. + // + STDMETHOD_(void, UnregisterForCallbacks) (__in IXAudio2EngineCallback* pCallback) PURE; + + // NAME: IXAudio2::CreateSourceVoice + // DESCRIPTION: Creates and configures a source voice. + // + // ARGUMENTS: + // ppSourceVoice - Returns the new object's IXAudio2SourceVoice interface. + // pSourceFormat - Format of the audio that will be fed to the voice. + // Flags - XAUDIO2_VOICE flags specifying the source voice's behavior. + // MaxFrequencyRatio - Maximum SetFrequencyRatio argument to be allowed. + // pCallback - Optional pointer to a client-provided callback interface. + // pSendList - Optional list of voices this voice should send audio to. + // pEffectChain - Optional list of effects to apply to the audio data. + // + STDMETHOD(CreateSourceVoice) (THIS_ __deref_out IXAudio2SourceVoice** ppSourceVoice, + __in const WAVEFORMATEX* pSourceFormat, + UINT32 Flags X2DEFAULT(0), + float MaxFrequencyRatio X2DEFAULT(XAUDIO2_DEFAULT_FREQ_RATIO), + __in_opt IXAudio2VoiceCallback* pCallback X2DEFAULT(NULL), + __in_opt const XAUDIO2_VOICE_SENDS* pSendList X2DEFAULT(NULL), + __in_opt const XAUDIO2_EFFECT_CHAIN* pEffectChain X2DEFAULT(NULL)) PURE; + + // NAME: IXAudio2::CreateSubmixVoice + // DESCRIPTION: Creates and configures a submix voice. + // + // ARGUMENTS: + // ppSubmixVoice - Returns the new object's IXAudio2SubmixVoice interface. + // InputChannels - Number of channels in this voice's input audio data. + // InputSampleRate - Sample rate of this voice's input audio data. + // Flags - XAUDIO2_VOICE flags specifying the submix voice's behavior. + // ProcessingStage - Arbitrary number that determines the processing order. + // pSendList - Optional list of voices this voice should send audio to. + // pEffectChain - Optional list of effects to apply to the audio data. + // + STDMETHOD(CreateSubmixVoice) (THIS_ __deref_out IXAudio2SubmixVoice** ppSubmixVoice, + UINT32 InputChannels, UINT32 InputSampleRate, + UINT32 Flags X2DEFAULT(0), UINT32 ProcessingStage X2DEFAULT(0), + __in_opt const XAUDIO2_VOICE_SENDS* pSendList X2DEFAULT(NULL), + __in_opt const XAUDIO2_EFFECT_CHAIN* pEffectChain X2DEFAULT(NULL)) PURE; + + + // NAME: IXAudio2::CreateMasteringVoice + // DESCRIPTION: Creates and configures a mastering voice. + // + // ARGUMENTS: + // ppMasteringVoice - Returns the new object's IXAudio2MasteringVoice interface. + // InputChannels - Number of channels in this voice's input audio data. + // InputSampleRate - Sample rate of this voice's input audio data. + // Flags - XAUDIO2_VOICE flags specifying the mastering voice's behavior. + // DeviceIndex - Identifier of the device to receive the output audio. + // pEffectChain - Optional list of effects to apply to the audio data. + // + STDMETHOD(CreateMasteringVoice) (THIS_ __deref_out IXAudio2MasteringVoice** ppMasteringVoice, + UINT32 InputChannels X2DEFAULT(XAUDIO2_DEFAULT_CHANNELS), + UINT32 InputSampleRate X2DEFAULT(XAUDIO2_DEFAULT_SAMPLERATE), + UINT32 Flags X2DEFAULT(0), UINT32 DeviceIndex X2DEFAULT(0), + __in_opt const XAUDIO2_EFFECT_CHAIN* pEffectChain X2DEFAULT(NULL)) PURE; + + // NAME: IXAudio2::StartEngine + // DESCRIPTION: Creates and starts the audio processing thread. + // + STDMETHOD(StartEngine) (THIS) PURE; + + // NAME: IXAudio2::StopEngine + // DESCRIPTION: Stops and destroys the audio processing thread. + // + STDMETHOD_(void, StopEngine) (THIS) PURE; + + // NAME: IXAudio2::CommitChanges + // DESCRIPTION: Atomically applies a set of operations previously tagged + // with a given identifier. + // + // ARGUMENTS: + // OperationSet - Identifier of the set of operations to be applied. + // + STDMETHOD(CommitChanges) (THIS_ UINT32 OperationSet) PURE; + + // NAME: IXAudio2::GetPerformanceData + // DESCRIPTION: Returns current resource usage details: memory, CPU, etc. + // + // ARGUMENTS: + // pPerfData - Returns the performance data structure. + // + STDMETHOD_(void, GetPerformanceData) (THIS_ __out XAUDIO2_PERFORMANCE_DATA* pPerfData) PURE; + + // NAME: IXAudio2::SetDebugConfiguration + // DESCRIPTION: Configures XAudio2's debug output (in debug builds only). + // + // ARGUMENTS: + // pDebugConfiguration - Structure describing the debug output behavior. + // pReserved - Optional parameter; must be NULL. + // + STDMETHOD_(void, SetDebugConfiguration) (THIS_ __in_opt const XAUDIO2_DEBUG_CONFIGURATION* pDebugConfiguration, + __in_opt __reserved void* pReserved X2DEFAULT(NULL)) PURE; +}; + + +/************************************************************************** + * + * IXAudio2Voice: Base voice management interface. + * + **************************************************************************/ + +#undef INTERFACE +#define INTERFACE IXAudio2Voice +DECLARE_INTERFACE(IXAudio2Voice) +{ + // These methods are declared in a macro so that the same declarations + // can be used in the derived voice types (IXAudio2SourceVoice, etc). + + #define Declare_IXAudio2Voice_Methods() \ + \ + /* NAME: IXAudio2Voice::GetVoiceDetails + // DESCRIPTION: Returns the basic characteristics of this voice. + // + // ARGUMENTS: + // pVoiceDetails - Returns the voice's details. + */\ + STDMETHOD_(void, GetVoiceDetails) (THIS_ __out XAUDIO2_VOICE_DETAILS* pVoiceDetails) PURE; \ + \ + /* NAME: IXAudio2Voice::SetOutputVoices + // DESCRIPTION: Replaces the set of submix/mastering voices that receive + // this voice's output. + // + // ARGUMENTS: + // pSendList - Optional list of voices this voice should send audio to. + */\ + STDMETHOD(SetOutputVoices) (THIS_ __in_opt const XAUDIO2_VOICE_SENDS* pSendList) PURE; \ + \ + /* NAME: IXAudio2Voice::SetEffectChain + // DESCRIPTION: Replaces this voice's current effect chain with a new one. + // + // ARGUMENTS: + // pEffectChain - Structure describing the new effect chain to be used. + */\ + STDMETHOD(SetEffectChain) (THIS_ __in_opt const XAUDIO2_EFFECT_CHAIN* pEffectChain) PURE; \ + \ + /* NAME: IXAudio2Voice::EnableEffect + // DESCRIPTION: Enables an effect in this voice's effect chain. + // + // ARGUMENTS: + // EffectIndex - Index of an effect within this voice's effect chain. + // OperationSet - Used to identify this call as part of a deferred batch. + */\ + STDMETHOD(EnableEffect) (THIS_ UINT32 EffectIndex, \ + UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \ + \ + /* NAME: IXAudio2Voice::DisableEffect + // DESCRIPTION: Disables an effect in this voice's effect chain. + // + // ARGUMENTS: + // EffectIndex - Index of an effect within this voice's effect chain. + // OperationSet - Used to identify this call as part of a deferred batch. + */\ + STDMETHOD(DisableEffect) (THIS_ UINT32 EffectIndex, \ + UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \ + \ + /* NAME: IXAudio2Voice::GetEffectState + // DESCRIPTION: Returns the running state of an effect. + // + // ARGUMENTS: + // EffectIndex - Index of an effect within this voice's effect chain. + // pEnabled - Returns the enabled/disabled state of the given effect. + */\ + STDMETHOD_(void, GetEffectState) (THIS_ UINT32 EffectIndex, __out BOOL* pEnabled) PURE; \ + \ + /* NAME: IXAudio2Voice::SetEffectParameters + // DESCRIPTION: Sets effect-specific parameters. + // + // REMARKS: Unlike IXAPOParameters::SetParameters, this method may + // be called from any thread. XAudio2 implements + // appropriate synchronization to copy the parameters to the + // realtime audio processing thread. + // + // ARGUMENTS: + // EffectIndex - Index of an effect within this voice's effect chain. + // pParameters - Pointer to an effect-specific parameters block. + // ParametersByteSize - Size of the pParameters array in bytes. + // OperationSet - Used to identify this call as part of a deferred batch. + */\ + STDMETHOD(SetEffectParameters) (THIS_ UINT32 EffectIndex, \ + __in_bcount(ParametersByteSize) const void* pParameters, \ + UINT32 ParametersByteSize, \ + UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \ + \ + /* NAME: IXAudio2Voice::GetEffectParameters + // DESCRIPTION: Obtains the current effect-specific parameters. + // + // ARGUMENTS: + // EffectIndex - Index of an effect within this voice's effect chain. + // pParameters - Returns the current values of the effect-specific parameters. + // ParametersByteSize - Size of the pParameters array in bytes. + */\ + STDMETHOD(GetEffectParameters) (THIS_ UINT32 EffectIndex, \ + __out_bcount(ParametersByteSize) void* pParameters, \ + UINT32 ParametersByteSize) PURE; \ + \ + /* NAME: IXAudio2Voice::SetFilterParameters + // DESCRIPTION: Sets this voice's filter parameters. + // + // ARGUMENTS: + // pParameters - Pointer to the filter's parameter structure. + // OperationSet - Used to identify this call as part of a deferred batch. + */\ + STDMETHOD(SetFilterParameters) (THIS_ __in const XAUDIO2_FILTER_PARAMETERS* pParameters, \ + UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \ + \ + /* NAME: IXAudio2Voice::GetFilterParameters + // DESCRIPTION: Returns this voice's current filter parameters. + // + // ARGUMENTS: + // pParameters - Returns the filter parameters. + */\ + STDMETHOD_(void, GetFilterParameters) (THIS_ __out XAUDIO2_FILTER_PARAMETERS* pParameters) PURE; \ + \ + /* NAME: IXAudio2Voice::SetOutputFilterParameters + // DESCRIPTION: Sets the filter parameters on one of this voice's sends. + // + // ARGUMENTS: + // pDestinationVoice - Destination voice of the send whose filter parameters will be set. + // pParameters - Pointer to the filter's parameter structure. + // OperationSet - Used to identify this call as part of a deferred batch. + */\ + STDMETHOD(SetOutputFilterParameters) (THIS_ __in_opt IXAudio2Voice* pDestinationVoice, \ + __in const XAUDIO2_FILTER_PARAMETERS* pParameters, \ + UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \ + \ + /* NAME: IXAudio2Voice::GetOutputFilterParameters + // DESCRIPTION: Returns the filter parameters from one of this voice's sends. + // + // ARGUMENTS: + // pDestinationVoice - Destination voice of the send whose filter parameters will be read. + // pParameters - Returns the filter parameters. + */\ + STDMETHOD_(void, GetOutputFilterParameters) (THIS_ __in_opt IXAudio2Voice* pDestinationVoice, \ + __out XAUDIO2_FILTER_PARAMETERS* pParameters) PURE; \ + \ + /* NAME: IXAudio2Voice::SetVolume + // DESCRIPTION: Sets this voice's overall volume level. + // + // ARGUMENTS: + // Volume - New overall volume level to be used, as an amplitude factor. + // OperationSet - Used to identify this call as part of a deferred batch. + */\ + STDMETHOD(SetVolume) (THIS_ float Volume, \ + UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \ + \ + /* NAME: IXAudio2Voice::GetVolume + // DESCRIPTION: Obtains this voice's current overall volume level. + // + // ARGUMENTS: + // pVolume: Returns the voice's current overall volume level. + */\ + STDMETHOD_(void, GetVolume) (THIS_ __out float* pVolume) PURE; \ + \ + /* NAME: IXAudio2Voice::SetChannelVolumes + // DESCRIPTION: Sets this voice's per-channel volume levels. + // + // ARGUMENTS: + // Channels - Used to confirm the voice's channel count. + // pVolumes - Array of per-channel volume levels to be used. + // OperationSet - Used to identify this call as part of a deferred batch. + */\ + STDMETHOD(SetChannelVolumes) (THIS_ UINT32 Channels, __in_ecount(Channels) const float* pVolumes, \ + UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \ + \ + /* NAME: IXAudio2Voice::GetChannelVolumes + // DESCRIPTION: Returns this voice's current per-channel volume levels. + // + // ARGUMENTS: + // Channels - Used to confirm the voice's channel count. + // pVolumes - Returns an array of the current per-channel volume levels. + */\ + STDMETHOD_(void, GetChannelVolumes) (THIS_ UINT32 Channels, __out_ecount(Channels) float* pVolumes) PURE; \ + \ + /* NAME: IXAudio2Voice::SetOutputMatrix + // DESCRIPTION: Sets the volume levels used to mix from each channel of this + // voice's output audio to each channel of a given destination + // voice's input audio. + // + // ARGUMENTS: + // pDestinationVoice - The destination voice whose mix matrix to change. + // SourceChannels - Used to confirm this voice's output channel count + // (the number of channels produced by the last effect in the chain). + // DestinationChannels - Confirms the destination voice's input channels. + // pLevelMatrix - Array of [SourceChannels * DestinationChannels] send + // levels. The level used to send from source channel S to destination + // channel D should be in pLevelMatrix[S + SourceChannels * D]. + // OperationSet - Used to identify this call as part of a deferred batch. + */\ + STDMETHOD(SetOutputMatrix) (THIS_ __in_opt IXAudio2Voice* pDestinationVoice, \ + UINT32 SourceChannels, UINT32 DestinationChannels, \ + __in_ecount(SourceChannels * DestinationChannels) const float* pLevelMatrix, \ + UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; \ + \ + /* NAME: IXAudio2Voice::GetOutputMatrix + // DESCRIPTION: Obtains the volume levels used to send each channel of this + // voice's output audio to each channel of a given destination + // voice's input audio. + // + // ARGUMENTS: + // pDestinationVoice - The destination voice whose mix matrix to obtain. + // SourceChannels - Used to confirm this voice's output channel count + // (the number of channels produced by the last effect in the chain). + // DestinationChannels - Confirms the destination voice's input channels. + // pLevelMatrix - Array of send levels, as above. + */\ + STDMETHOD_(void, GetOutputMatrix) (THIS_ __in_opt IXAudio2Voice* pDestinationVoice, \ + UINT32 SourceChannels, UINT32 DestinationChannels, \ + __out_ecount(SourceChannels * DestinationChannels) float* pLevelMatrix) PURE; \ + \ + /* NAME: IXAudio2Voice::DestroyVoice + // DESCRIPTION: Destroys this voice, stopping it if necessary and removing + // it from the XAudio2 graph. + */\ + STDMETHOD_(void, DestroyVoice) (THIS) PURE + + Declare_IXAudio2Voice_Methods(); +}; + + +/************************************************************************** + * + * IXAudio2SourceVoice: Source voice management interface. + * + **************************************************************************/ + +#undef INTERFACE +#define INTERFACE IXAudio2SourceVoice +DECLARE_INTERFACE_(IXAudio2SourceVoice, IXAudio2Voice) +{ + // Methods from IXAudio2Voice base interface + Declare_IXAudio2Voice_Methods(); + + // NAME: IXAudio2SourceVoice::Start + // DESCRIPTION: Makes this voice start consuming and processing audio. + // + // ARGUMENTS: + // Flags - Flags controlling how the voice should be started. + // OperationSet - Used to identify this call as part of a deferred batch. + // + STDMETHOD(Start) (THIS_ UINT32 Flags X2DEFAULT(0), UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; + + // NAME: IXAudio2SourceVoice::Stop + // DESCRIPTION: Makes this voice stop consuming audio. + // + // ARGUMENTS: + // Flags - Flags controlling how the voice should be stopped. + // OperationSet - Used to identify this call as part of a deferred batch. + // + STDMETHOD(Stop) (THIS_ UINT32 Flags X2DEFAULT(0), UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; + + // NAME: IXAudio2SourceVoice::SubmitSourceBuffer + // DESCRIPTION: Adds a new audio buffer to this voice's input queue. + // + // ARGUMENTS: + // pBuffer - Pointer to the buffer structure to be queued. + // pBufferWMA - Additional structure used only when submitting XWMA data. + // + STDMETHOD(SubmitSourceBuffer) (THIS_ __in const XAUDIO2_BUFFER* pBuffer, __in_opt const XAUDIO2_BUFFER_WMA* pBufferWMA X2DEFAULT(NULL)) PURE; + + // NAME: IXAudio2SourceVoice::FlushSourceBuffers + // DESCRIPTION: Removes all pending audio buffers from this voice's queue. + // + STDMETHOD(FlushSourceBuffers) (THIS) PURE; + + // NAME: IXAudio2SourceVoice::Discontinuity + // DESCRIPTION: Notifies the voice of an intentional break in the stream of + // audio buffers (e.g. the end of a sound), to prevent XAudio2 + // from interpreting an empty buffer queue as a glitch. + // + STDMETHOD(Discontinuity) (THIS) PURE; + + // NAME: IXAudio2SourceVoice::ExitLoop + // DESCRIPTION: Breaks out of the current loop when its end is reached. + // + // ARGUMENTS: + // OperationSet - Used to identify this call as part of a deferred batch. + // + STDMETHOD(ExitLoop) (THIS_ UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; + + // NAME: IXAudio2SourceVoice::GetState + // DESCRIPTION: Returns the number of buffers currently queued on this voice, + // the pContext value associated with the currently processing + // buffer (if any), and other voice state information. + // + // ARGUMENTS: + // pVoiceState - Returns the state information. + // + STDMETHOD_(void, GetState) (THIS_ __out XAUDIO2_VOICE_STATE* pVoiceState) PURE; + + // NAME: IXAudio2SourceVoice::SetFrequencyRatio + // DESCRIPTION: Sets this voice's frequency adjustment, i.e. its pitch. + // + // ARGUMENTS: + // Ratio - Frequency change, expressed as source frequency / target frequency. + // OperationSet - Used to identify this call as part of a deferred batch. + // + STDMETHOD(SetFrequencyRatio) (THIS_ float Ratio, + UINT32 OperationSet X2DEFAULT(XAUDIO2_COMMIT_NOW)) PURE; + + // NAME: IXAudio2SourceVoice::GetFrequencyRatio + // DESCRIPTION: Returns this voice's current frequency adjustment ratio. + // + // ARGUMENTS: + // pRatio - Returns the frequency adjustment. + // + STDMETHOD_(void, GetFrequencyRatio) (THIS_ __out float* pRatio) PURE; + + // NAME: IXAudio2SourceVoice::SetSourceSampleRate + // DESCRIPTION: Reconfigures this voice to treat its source data as being + // at a different sample rate than the original one specified + // in CreateSourceVoice's pSourceFormat argument. + // + // ARGUMENTS: + // UINT32 - The intended sample rate of further submitted source data. + // + STDMETHOD(SetSourceSampleRate) (THIS_ UINT32 NewSourceSampleRate) PURE; +}; + + +/************************************************************************** + * + * IXAudio2SubmixVoice: Submixing voice management interface. + * + **************************************************************************/ + +#undef INTERFACE +#define INTERFACE IXAudio2SubmixVoice +DECLARE_INTERFACE_(IXAudio2SubmixVoice, IXAudio2Voice) +{ + // Methods from IXAudio2Voice base interface + Declare_IXAudio2Voice_Methods(); + + // There are currently no methods specific to submix voices. +}; + + +/************************************************************************** + * + * IXAudio2MasteringVoice: Mastering voice management interface. + * + **************************************************************************/ + +#undef INTERFACE +#define INTERFACE IXAudio2MasteringVoice +DECLARE_INTERFACE_(IXAudio2MasteringVoice, IXAudio2Voice) +{ + // Methods from IXAudio2Voice base interface + Declare_IXAudio2Voice_Methods(); + + // There are currently no methods specific to mastering voices. +}; + + +/************************************************************************** + * + * IXAudio2EngineCallback: Client notification interface for engine events. + * + * REMARKS: Contains methods to notify the client when certain events happen + * in the XAudio2 engine. This interface should be implemented by + * the client. XAudio2 will call these methods via the interface + * pointer provided by the client when it calls XAudio2Create or + * IXAudio2::Initialize. + * + **************************************************************************/ + +#undef INTERFACE +#define INTERFACE IXAudio2EngineCallback +DECLARE_INTERFACE(IXAudio2EngineCallback) +{ + // Called by XAudio2 just before an audio processing pass begins. + STDMETHOD_(void, OnProcessingPassStart) (THIS) PURE; + + // Called just after an audio processing pass ends. + STDMETHOD_(void, OnProcessingPassEnd) (THIS) PURE; + + // Called in the event of a critical system error which requires XAudio2 + // to be closed down and restarted. The error code is given in Error. + STDMETHOD_(void, OnCriticalError) (THIS_ HRESULT Error) PURE; +}; + + +/************************************************************************** + * + * IXAudio2VoiceCallback: Client notification interface for voice events. + * + * REMARKS: Contains methods to notify the client when certain events happen + * in an XAudio2 voice. This interface should be implemented by the + * client. XAudio2 will call these methods via an interface pointer + * provided by the client in the IXAudio2::CreateSourceVoice call. + * + **************************************************************************/ + +#undef INTERFACE +#define INTERFACE IXAudio2VoiceCallback +DECLARE_INTERFACE(IXAudio2VoiceCallback) +{ + // Called just before this voice's processing pass begins. + STDMETHOD_(void, OnVoiceProcessingPassStart) (THIS_ UINT32 BytesRequired) PURE; + + // Called just after this voice's processing pass ends. + STDMETHOD_(void, OnVoiceProcessingPassEnd) (THIS) PURE; + + // Called when this voice has just finished playing a buffer stream + // (as marked with the XAUDIO2_END_OF_STREAM flag on the last buffer). + STDMETHOD_(void, OnStreamEnd) (THIS) PURE; + + // Called when this voice is about to start processing a new buffer. + STDMETHOD_(void, OnBufferStart) (THIS_ void* pBufferContext) PURE; + + // Called when this voice has just finished processing a buffer. + // The buffer can now be reused or destroyed. + STDMETHOD_(void, OnBufferEnd) (THIS_ void* pBufferContext) PURE; + + // Called when this voice has just reached the end position of a loop. + STDMETHOD_(void, OnLoopEnd) (THIS_ void* pBufferContext) PURE; + + // Called in the event of a critical error during voice processing, + // such as a failing xAPO or an error from the hardware XMA decoder. + // The voice may have to be destroyed and re-created to recover from + // the error. The callback arguments report which buffer was being + // processed when the error occurred, and its HRESULT code. + STDMETHOD_(void, OnVoiceError) (THIS_ void* pBufferContext, HRESULT Error) PURE; +}; + + +/************************************************************************** + * + * Macros to make it easier to use the XAudio2 COM interfaces in C code. + * + **************************************************************************/ + +#ifndef __cplusplus + +// IXAudio2 +#define IXAudio2_QueryInterface(This,riid,ppvInterface) ((This)->lpVtbl->QueryInterface(This,riid,ppvInterface)) +#define IXAudio2_AddRef(This) ((This)->lpVtbl->AddRef(This)) +#define IXAudio2_Release(This) ((This)->lpVtbl->Release(This)) +#define IXAudio2_GetDeviceCount(This,puCount) ((This)->lpVtbl->GetDeviceCount(This,puCount)) +#define IXAudio2_GetDeviceDetails(This,Index,pDeviceDetails) ((This)->lpVtbl->GetDeviceDetails(This,Index,pDeviceDetails)) +#define IXAudio2_Initialize(This,Flags,XAudio2Processor) ((This)->lpVtbl->Initialize(This,Flags,XAudio2Processor)) +#define IXAudio2_CreateSourceVoice(This,ppSourceVoice,pSourceFormat,Flags,MaxFrequencyRatio,pCallback,pSendList,pEffectChain) ((This)->lpVtbl->CreateSourceVoice(This,ppSourceVoice,pSourceFormat,Flags,MaxFrequencyRatio,pCallback,pSendList,pEffectChain)) +#define IXAudio2_CreateSubmixVoice(This,ppSubmixVoice,InputChannels,InputSampleRate,Flags,ProcessingStage,pSendList,pEffectChain) ((This)->lpVtbl->CreateSubmixVoice(This,ppSubmixVoice,InputChannels,InputSampleRate,Flags,ProcessingStage,pSendList,pEffectChain)) +#define IXAudio2_CreateMasteringVoice(This,ppMasteringVoice,InputChannels,InputSampleRate,Flags,DeviceIndex,pEffectChain) ((This)->lpVtbl->CreateMasteringVoice(This,ppMasteringVoice,InputChannels,InputSampleRate,Flags,DeviceIndex,pEffectChain)) +#define IXAudio2_StartEngine(This) ((This)->lpVtbl->StartEngine(This)) +#define IXAudio2_StopEngine(This) ((This)->lpVtbl->StopEngine(This)) +#define IXAudio2_CommitChanges(This,OperationSet) ((This)->lpVtbl->CommitChanges(This,OperationSet)) +#define IXAudio2_GetPerformanceData(This,pPerfData) ((This)->lpVtbl->GetPerformanceData(This,pPerfData)) +#define IXAudio2_SetDebugConfiguration(This,pDebugConfiguration,pReserved) ((This)->lpVtbl->SetDebugConfiguration(This,pDebugConfiguration,pReserved)) + +// IXAudio2Voice +#define IXAudio2Voice_GetVoiceDetails(This,pVoiceDetails) ((This)->lpVtbl->GetVoiceDetails(This,pVoiceDetails)) +#define IXAudio2Voice_SetOutputVoices(This,pSendList) ((This)->lpVtbl->SetOutputVoices(This,pSendList)) +#define IXAudio2Voice_SetEffectChain(This,pEffectChain) ((This)->lpVtbl->SetEffectChain(This,pEffectChain)) +#define IXAudio2Voice_EnableEffect(This,EffectIndex,OperationSet) ((This)->lpVtbl->EnableEffect(This,EffectIndex,OperationSet)) +#define IXAudio2Voice_DisableEffect(This,EffectIndex,OperationSet) ((This)->lpVtbl->DisableEffect(This,EffectIndex,OperationSet)) +#define IXAudio2Voice_GetEffectState(This,EffectIndex,pEnabled) ((This)->lpVtbl->GetEffectState(This,EffectIndex,pEnabled)) +#define IXAudio2Voice_SetEffectParameters(This,EffectIndex,pParameters,ParametersByteSize, OperationSet) ((This)->lpVtbl->SetEffectParameters(This,EffectIndex,pParameters,ParametersByteSize,OperationSet)) +#define IXAudio2Voice_GetEffectParameters(This,EffectIndex,pParameters,ParametersByteSize) ((This)->lpVtbl->GetEffectParameters(This,EffectIndex,pParameters,ParametersByteSize)) +#define IXAudio2Voice_SetFilterParameters(This,pParameters,OperationSet) ((This)->lpVtbl->SetFilterParameters(This,pParameters,OperationSet)) +#define IXAudio2Voice_GetFilterParameters(This,pParameters) ((This)->lpVtbl->GetFilterParameters(This,pParameters)) +#define IXAudio2Voice_SetOutputFilterParameters(This,pDestinationVoice,pParameters,OperationSet) ((This)->lpVtbl->SetOutputFilterParameters(This,pDestinationVoice,pParameters,OperationSet)) +#define IXAudio2Voice_GetOutputFilterParameters(This,pDestinationVoice,pParameters) ((This)->lpVtbl->GetOutputFilterParameters(This,pDestinationVoice,pParameters)) +#define IXAudio2Voice_SetVolume(This,Volume,OperationSet) ((This)->lpVtbl->SetVolume(This,Volume,OperationSet)) +#define IXAudio2Voice_GetVolume(This,pVolume) ((This)->lpVtbl->GetVolume(This,pVolume)) +#define IXAudio2Voice_SetChannelVolumes(This,Channels,pVolumes,OperationSet) ((This)->lpVtbl->SetChannelVolumes(This,Channels,pVolumes,OperationSet)) +#define IXAudio2Voice_GetChannelVolumes(This,Channels,pVolumes) ((This)->lpVtbl->GetChannelVolumes(This,Channels,pVolumes)) +#define IXAudio2Voice_SetOutputMatrix(This,pDestinationVoice,SourceChannels,DestinationChannels,pLevelMatrix,OperationSet) ((This)->lpVtbl->SetOutputMatrix(This,pDestinationVoice,SourceChannels,DestinationChannels,pLevelMatrix,OperationSet)) +#define IXAudio2Voice_GetOutputMatrix(This,pDestinationVoice,SourceChannels,DestinationChannels,pLevelMatrix) ((This)->lpVtbl->GetOutputMatrix(This,pDestinationVoice,SourceChannels,DestinationChannels,pLevelMatrix)) +#define IXAudio2Voice_DestroyVoice(This) ((This)->lpVtbl->DestroyVoice(This)) + +// IXAudio2SourceVoice +#define IXAudio2SourceVoice_GetVoiceDetails IXAudio2Voice_GetVoiceDetails +#define IXAudio2SourceVoice_SetOutputVoices IXAudio2Voice_SetOutputVoices +#define IXAudio2SourceVoice_SetEffectChain IXAudio2Voice_SetEffectChain +#define IXAudio2SourceVoice_EnableEffect IXAudio2Voice_EnableEffect +#define IXAudio2SourceVoice_DisableEffect IXAudio2Voice_DisableEffect +#define IXAudio2SourceVoice_GetEffectState IXAudio2Voice_GetEffectState +#define IXAudio2SourceVoice_SetEffectParameters IXAudio2Voice_SetEffectParameters +#define IXAudio2SourceVoice_GetEffectParameters IXAudio2Voice_GetEffectParameters +#define IXAudio2SourceVoice_SetFilterParameters IXAudio2Voice_SetFilterParameters +#define IXAudio2SourceVoice_GetFilterParameters IXAudio2Voice_GetFilterParameters +#define IXAudio2SourceVoice_SetOutputFilterParameters IXAudio2Voice_SetOutputFilterParameters +#define IXAudio2SourceVoice_GetOutputFilterParameters IXAudio2Voice_GetOutputFilterParameters +#define IXAudio2SourceVoice_SetVolume IXAudio2Voice_SetVolume +#define IXAudio2SourceVoice_GetVolume IXAudio2Voice_GetVolume +#define IXAudio2SourceVoice_SetChannelVolumes IXAudio2Voice_SetChannelVolumes +#define IXAudio2SourceVoice_GetChannelVolumes IXAudio2Voice_GetChannelVolumes +#define IXAudio2SourceVoice_SetOutputMatrix IXAudio2Voice_SetOutputMatrix +#define IXAudio2SourceVoice_GetOutputMatrix IXAudio2Voice_GetOutputMatrix +#define IXAudio2SourceVoice_DestroyVoice IXAudio2Voice_DestroyVoice +#define IXAudio2SourceVoice_Start(This,Flags,OperationSet) ((This)->lpVtbl->Start(This,Flags,OperationSet)) +#define IXAudio2SourceVoice_Stop(This,Flags,OperationSet) ((This)->lpVtbl->Stop(This,Flags,OperationSet)) +#define IXAudio2SourceVoice_SubmitSourceBuffer(This,pBuffer,pBufferWMA) ((This)->lpVtbl->SubmitSourceBuffer(This,pBuffer,pBufferWMA)) +#define IXAudio2SourceVoice_FlushSourceBuffers(This) ((This)->lpVtbl->FlushSourceBuffers(This)) +#define IXAudio2SourceVoice_Discontinuity(This) ((This)->lpVtbl->Discontinuity(This)) +#define IXAudio2SourceVoice_ExitLoop(This,OperationSet) ((This)->lpVtbl->ExitLoop(This,OperationSet)) +#define IXAudio2SourceVoice_GetState(This,pVoiceState) ((This)->lpVtbl->GetState(This,pVoiceState)) +#define IXAudio2SourceVoice_SetFrequencyRatio(This,Ratio,OperationSet) ((This)->lpVtbl->SetFrequencyRatio(This,Ratio,OperationSet)) +#define IXAudio2SourceVoice_GetFrequencyRatio(This,pRatio) ((This)->lpVtbl->GetFrequencyRatio(This,pRatio)) +#define IXAudio2SourceVoice_SetSourceSampleRate(This,NewSourceSampleRate) ((This)->lpVtbl->SetSourceSampleRate(This,NewSourceSampleRate)) + +// IXAudio2SubmixVoice +#define IXAudio2SubmixVoice_GetVoiceDetails IXAudio2Voice_GetVoiceDetails +#define IXAudio2SubmixVoice_SetOutputVoices IXAudio2Voice_SetOutputVoices +#define IXAudio2SubmixVoice_SetEffectChain IXAudio2Voice_SetEffectChain +#define IXAudio2SubmixVoice_EnableEffect IXAudio2Voice_EnableEffect +#define IXAudio2SubmixVoice_DisableEffect IXAudio2Voice_DisableEffect +#define IXAudio2SubmixVoice_GetEffectState IXAudio2Voice_GetEffectState +#define IXAudio2SubmixVoice_SetEffectParameters IXAudio2Voice_SetEffectParameters +#define IXAudio2SubmixVoice_GetEffectParameters IXAudio2Voice_GetEffectParameters +#define IXAudio2SubmixVoice_SetFilterParameters IXAudio2Voice_SetFilterParameters +#define IXAudio2SubmixVoice_GetFilterParameters IXAudio2Voice_GetFilterParameters +#define IXAudio2SubmixVoice_SetOutputFilterParameters IXAudio2Voice_SetOutputFilterParameters +#define IXAudio2SubmixVoice_GetOutputFilterParameters IXAudio2Voice_GetOutputFilterParameters +#define IXAudio2SubmixVoice_SetVolume IXAudio2Voice_SetVolume +#define IXAudio2SubmixVoice_GetVolume IXAudio2Voice_GetVolume +#define IXAudio2SubmixVoice_SetChannelVolumes IXAudio2Voice_SetChannelVolumes +#define IXAudio2SubmixVoice_GetChannelVolumes IXAudio2Voice_GetChannelVolumes +#define IXAudio2SubmixVoice_SetOutputMatrix IXAudio2Voice_SetOutputMatrix +#define IXAudio2SubmixVoice_GetOutputMatrix IXAudio2Voice_GetOutputMatrix +#define IXAudio2SubmixVoice_DestroyVoice IXAudio2Voice_DestroyVoice + +// IXAudio2MasteringVoice +#define IXAudio2MasteringVoice_GetVoiceDetails IXAudio2Voice_GetVoiceDetails +#define IXAudio2MasteringVoice_SetOutputVoices IXAudio2Voice_SetOutputVoices +#define IXAudio2MasteringVoice_SetEffectChain IXAudio2Voice_SetEffectChain +#define IXAudio2MasteringVoice_EnableEffect IXAudio2Voice_EnableEffect +#define IXAudio2MasteringVoice_DisableEffect IXAudio2Voice_DisableEffect +#define IXAudio2MasteringVoice_GetEffectState IXAudio2Voice_GetEffectState +#define IXAudio2MasteringVoice_SetEffectParameters IXAudio2Voice_SetEffectParameters +#define IXAudio2MasteringVoice_GetEffectParameters IXAudio2Voice_GetEffectParameters +#define IXAudio2MasteringVoice_SetFilterParameters IXAudio2Voice_SetFilterParameters +#define IXAudio2MasteringVoice_GetFilterParameters IXAudio2Voice_GetFilterParameters +#define IXAudio2MasteringVoice_SetOutputFilterParameters IXAudio2Voice_SetOutputFilterParameters +#define IXAudio2MasteringVoice_GetOutputFilterParameters IXAudio2Voice_GetOutputFilterParameters +#define IXAudio2MasteringVoice_SetVolume IXAudio2Voice_SetVolume +#define IXAudio2MasteringVoice_GetVolume IXAudio2Voice_GetVolume +#define IXAudio2MasteringVoice_SetChannelVolumes IXAudio2Voice_SetChannelVolumes +#define IXAudio2MasteringVoice_GetChannelVolumes IXAudio2Voice_GetChannelVolumes +#define IXAudio2MasteringVoice_SetOutputMatrix IXAudio2Voice_SetOutputMatrix +#define IXAudio2MasteringVoice_GetOutputMatrix IXAudio2Voice_GetOutputMatrix +#define IXAudio2MasteringVoice_DestroyVoice IXAudio2Voice_DestroyVoice + +#endif // #ifndef __cplusplus + + +/************************************************************************** + * + * Utility functions used to convert from pitch in semitones and volume + * in decibels to the frequency and amplitude ratio units used by XAudio2. + * These are only defined if the client #defines XAUDIO2_HELPER_FUNCTIONS + * prior to #including xaudio2.h. + * + **************************************************************************/ + +#ifdef XAUDIO2_HELPER_FUNCTIONS + +#define _USE_MATH_DEFINES // Make math.h define M_PI +#include // For powf, log10f, sinf and asinf + +// Calculate the argument to SetVolume from a decibel value +__inline float XAudio2DecibelsToAmplitudeRatio(float Decibels) +{ + return powf(10.0f, Decibels / 20.0f); +} + +// Recover a volume in decibels from an amplitude factor +__inline float XAudio2AmplitudeRatioToDecibels(float Volume) +{ + if (Volume == 0) + { + return -3.402823466e+38f; // Smallest float value (-FLT_MAX) + } + return 20.0f * log10f(Volume); +} + +// Calculate the argument to SetFrequencyRatio from a semitone value +__inline float XAudio2SemitonesToFrequencyRatio(float Semitones) +{ + // FrequencyRatio = 2 ^ Octaves + // = 2 ^ (Semitones / 12) + return powf(2.0f, Semitones / 12.0f); +} + +// Recover a pitch in semitones from a frequency ratio +__inline float XAudio2FrequencyRatioToSemitones(float FrequencyRatio) +{ + // Semitones = 12 * log2(FrequencyRatio) + // = 12 * log2(10) * log10(FrequencyRatio) + return 39.86313713864835f * log10f(FrequencyRatio); +} + +// Convert from filter cutoff frequencies expressed in Hertz to the radian +// frequency values used in XAUDIO2_FILTER_PARAMETERS.Frequency. Note that +// the highest CutoffFrequency supported is SampleRate/6. Higher values of +// CutoffFrequency will return XAUDIO2_MAX_FILTER_FREQUENCY. +__inline float XAudio2CutoffFrequencyToRadians(float CutoffFrequency, UINT32 SampleRate) +{ + if ((UINT32)(CutoffFrequency * 6.0f) >= SampleRate) + { + return XAUDIO2_MAX_FILTER_FREQUENCY; + } + return 2.0f * sinf((float)M_PI * CutoffFrequency / SampleRate); +} + +// Convert from radian frequencies back to absolute frequencies in Hertz +__inline float XAudio2RadiansToCutoffFrequency(float Radians, float SampleRate) +{ + return SampleRate * asinf(Radians / 2.0f) / (float)M_PI; +} +#endif // #ifdef XAUDIO2_HELPER_FUNCTIONS + + +/************************************************************************** + * + * XAudio2Create: Top-level function that creates an XAudio2 instance. + * + * On Windows this is just an inline function that calls CoCreateInstance + * and Initialize. The arguments are described above, under Initialize, + * except that the XAUDIO2_DEBUG_ENGINE flag can be used here to select + * the debug version of XAudio2. + * + * On Xbox, this function is implemented in the XAudio2 library, and the + * XAUDIO2_DEBUG_ENGINE flag has no effect; the client must explicitly + * link with the debug version of the library to obtain debug behavior. + * + **************************************************************************/ + +#ifdef _XBOX + +STDAPI XAudio2Create(__deref_out IXAudio2** ppXAudio2, UINT32 Flags X2DEFAULT(0), + XAUDIO2_PROCESSOR XAudio2Processor X2DEFAULT(XAUDIO2_DEFAULT_PROCESSOR)); + +#else // Windows + +__inline HRESULT XAudio2Create(__deref_out IXAudio2** ppXAudio2, UINT32 Flags X2DEFAULT(0), + XAUDIO2_PROCESSOR XAudio2Processor X2DEFAULT(XAUDIO2_DEFAULT_PROCESSOR)) +{ + // Instantiate the appropriate XAudio2 engine + IXAudio2* pXAudio2; + + #ifdef __cplusplus + + HRESULT hr = CoCreateInstance((Flags & XAUDIO2_DEBUG_ENGINE) ? __uuidof(XAudio2_Debug) : __uuidof(XAudio2), + NULL, CLSCTX_INPROC_SERVER, __uuidof(IXAudio2), (void**)&pXAudio2); + if (SUCCEEDED(hr)) + { + hr = pXAudio2->Initialize(Flags, XAudio2Processor); + + if (SUCCEEDED(hr)) + { + *ppXAudio2 = pXAudio2; + } + else + { + pXAudio2->Release(); + } + } + + #else + + HRESULT hr = CoCreateInstance((Flags & XAUDIO2_DEBUG_ENGINE) ? &CLSID_XAudio2_Debug : &CLSID_XAudio2, + NULL, CLSCTX_INPROC_SERVER, &IID_IXAudio2, (void**)&pXAudio2); + if (SUCCEEDED(hr)) + { + hr = pXAudio2->lpVtbl->Initialize(pXAudio2, Flags, XAudio2Processor); + + if (SUCCEEDED(hr)) + { + *ppXAudio2 = pXAudio2; + } + else + { + pXAudio2->lpVtbl->Release(pXAudio2); + } + } + + #endif // #ifdef __cplusplus + + return hr; +} + +#endif // #ifdef _XBOX + + +// Undo the #pragma pack(push, 1) directive at the top of this file +#pragma pack(pop) + +#endif // #ifndef GUID_DEFS_ONLY +#endif // #ifndef __XAUDIO2_INCLUDED__ diff --git a/dependencies/DirectX_2010/audiodefs.h b/dependencies/DirectX_2010/audiodefs.h new file mode 100644 index 00000000..ff995ecc --- /dev/null +++ b/dependencies/DirectX_2010/audiodefs.h @@ -0,0 +1,263 @@ +/*************************************************************************** + * + * Copyright (c) Microsoft Corporation. All rights reserved. + * + * File: audiodefs.h + * Content: Basic constants and data types for audio work. + * + * Remarks: This header file defines all of the audio format constants and + * structures required for XAudio2 and XACT work. Providing these + * in a single location avoids certain dependency problems in the + * legacy audio headers (mmreg.h, mmsystem.h, ksmedia.h). + * + * NOTE: Including the legacy headers after this one may cause a + * compilation error, because they define some of the same types + * defined here without preprocessor guards to avoid multiple + * definitions. If a source file needs one of the old headers, + * it must include it before including audiodefs.h. + * + ***************************************************************************/ + +#ifndef __AUDIODEFS_INCLUDED__ +#define __AUDIODEFS_INCLUDED__ + +#include // For WORD, DWORD, etc. + +#pragma pack(push, 1) // Pack structures to 1-byte boundaries + + +/************************************************************************** + * + * WAVEFORMATEX: Base structure for many audio formats. Format-specific + * extensions can be defined for particular formats by using a non-zero + * cbSize value and adding extra fields to the end of this structure. + * + ***************************************************************************/ + +#ifndef _WAVEFORMATEX_ + + #define _WAVEFORMATEX_ + typedef struct tWAVEFORMATEX + { + WORD wFormatTag; // Integer identifier of the format + WORD nChannels; // Number of audio channels + DWORD nSamplesPerSec; // Audio sample rate + DWORD nAvgBytesPerSec; // Bytes per second (possibly approximate) + WORD nBlockAlign; // Size in bytes of a sample block (all channels) + WORD wBitsPerSample; // Size in bits of a single per-channel sample + WORD cbSize; // Bytes of extra data appended to this struct + } WAVEFORMATEX; + +#endif + +// Defining pointer types outside of the #if block to make sure they are +// defined even if mmreg.h or mmsystem.h is #included before this file + +typedef WAVEFORMATEX *PWAVEFORMATEX, *NPWAVEFORMATEX, *LPWAVEFORMATEX; +typedef const WAVEFORMATEX *PCWAVEFORMATEX, *LPCWAVEFORMATEX; + + +/************************************************************************** + * + * WAVEFORMATEXTENSIBLE: Extended version of WAVEFORMATEX that should be + * used as a basis for all new audio formats. The format tag is replaced + * with a GUID, allowing new formats to be defined without registering a + * format tag with Microsoft. There are also new fields that can be used + * to specify the spatial positions for each channel and the bit packing + * used for wide samples (e.g. 24-bit PCM samples in 32-bit containers). + * + ***************************************************************************/ + +#ifndef _WAVEFORMATEXTENSIBLE_ + + #define _WAVEFORMATEXTENSIBLE_ + typedef struct + { + WAVEFORMATEX Format; // Base WAVEFORMATEX data + union + { + WORD wValidBitsPerSample; // Valid bits in each sample container + WORD wSamplesPerBlock; // Samples per block of audio data; valid + // if wBitsPerSample=0 (but rarely used). + WORD wReserved; // Zero if neither case above applies. + } Samples; + DWORD dwChannelMask; // Positions of the audio channels + GUID SubFormat; // Format identifier GUID + } WAVEFORMATEXTENSIBLE; + +#endif + +typedef WAVEFORMATEXTENSIBLE *PWAVEFORMATEXTENSIBLE, *LPWAVEFORMATEXTENSIBLE; +typedef const WAVEFORMATEXTENSIBLE *PCWAVEFORMATEXTENSIBLE, *LPCWAVEFORMATEXTENSIBLE; + + + +/************************************************************************** + * + * Define the most common wave format tags used in WAVEFORMATEX formats. + * + ***************************************************************************/ + +#ifndef WAVE_FORMAT_PCM // Pulse Code Modulation + + // If WAVE_FORMAT_PCM is not defined, we need to define some legacy types + // for compatibility with the Windows mmreg.h / mmsystem.h header files. + + // Old general format structure (information common to all formats) + typedef struct waveformat_tag + { + WORD wFormatTag; + WORD nChannels; + DWORD nSamplesPerSec; + DWORD nAvgBytesPerSec; + WORD nBlockAlign; + } WAVEFORMAT, *PWAVEFORMAT, NEAR *NPWAVEFORMAT, FAR *LPWAVEFORMAT; + + // Specific format structure for PCM data + typedef struct pcmwaveformat_tag + { + WAVEFORMAT wf; + WORD wBitsPerSample; + } PCMWAVEFORMAT, *PPCMWAVEFORMAT, NEAR *NPPCMWAVEFORMAT, FAR *LPPCMWAVEFORMAT; + + #define WAVE_FORMAT_PCM 0x0001 + +#endif + +#ifndef WAVE_FORMAT_ADPCM // Microsoft Adaptive Differental PCM + + // Replicate the Microsoft ADPCM type definitions from mmreg.h. + + typedef struct adpcmcoef_tag + { + short iCoef1; + short iCoef2; + } ADPCMCOEFSET; + + #pragma warning(push) + #pragma warning(disable:4200) // Disable zero-sized array warnings + + typedef struct adpcmwaveformat_tag { + WAVEFORMATEX wfx; + WORD wSamplesPerBlock; + WORD wNumCoef; + ADPCMCOEFSET aCoef[]; // Always 7 coefficient pairs for MS ADPCM + } ADPCMWAVEFORMAT; + + #pragma warning(pop) + + #define WAVE_FORMAT_ADPCM 0x0002 + +#endif + +// Other frequently used format tags + +#ifndef WAVE_FORMAT_UNKNOWN + #define WAVE_FORMAT_UNKNOWN 0x0000 // Unknown or invalid format tag +#endif + +#ifndef WAVE_FORMAT_IEEE_FLOAT + #define WAVE_FORMAT_IEEE_FLOAT 0x0003 // 32-bit floating-point +#endif + +#ifndef WAVE_FORMAT_MPEGLAYER3 + #define WAVE_FORMAT_MPEGLAYER3 0x0055 // ISO/MPEG Layer3 +#endif + +#ifndef WAVE_FORMAT_DOLBY_AC3_SPDIF + #define WAVE_FORMAT_DOLBY_AC3_SPDIF 0x0092 // Dolby Audio Codec 3 over S/PDIF +#endif + +#ifndef WAVE_FORMAT_WMAUDIO2 + #define WAVE_FORMAT_WMAUDIO2 0x0161 // Windows Media Audio +#endif + +#ifndef WAVE_FORMAT_WMAUDIO3 + #define WAVE_FORMAT_WMAUDIO3 0x0162 // Windows Media Audio Pro +#endif + +#ifndef WAVE_FORMAT_WMASPDIF + #define WAVE_FORMAT_WMASPDIF 0x0164 // Windows Media Audio over S/PDIF +#endif + +#ifndef WAVE_FORMAT_EXTENSIBLE + #define WAVE_FORMAT_EXTENSIBLE 0xFFFE // All WAVEFORMATEXTENSIBLE formats +#endif + + +/************************************************************************** + * + * Define the most common wave format GUIDs used in WAVEFORMATEXTENSIBLE + * formats. Note that including the Windows ksmedia.h header after this + * one will cause build problems; this cannot be avoided, since ksmedia.h + * defines these macros without preprocessor guards. + * + ***************************************************************************/ + +#ifdef __cplusplus // uuid() and __uuidof() are only available in C++ + + #ifndef KSDATAFORMAT_SUBTYPE_PCM + struct __declspec(uuid("00000001-0000-0010-8000-00aa00389b71")) KSDATAFORMAT_SUBTYPE_PCM_STRUCT; + #define KSDATAFORMAT_SUBTYPE_PCM __uuidof(KSDATAFORMAT_SUBTYPE_PCM_STRUCT) + #endif + + #ifndef KSDATAFORMAT_SUBTYPE_ADPCM + struct __declspec(uuid("00000002-0000-0010-8000-00aa00389b71")) KSDATAFORMAT_SUBTYPE_ADPCM_STRUCT; + #define KSDATAFORMAT_SUBTYPE_ADPCM __uuidof(KSDATAFORMAT_SUBTYPE_ADPCM_STRUCT) + #endif + + #ifndef KSDATAFORMAT_SUBTYPE_IEEE_FLOAT + struct __declspec(uuid("00000003-0000-0010-8000-00aa00389b71")) KSDATAFORMAT_SUBTYPE_IEEE_FLOAT_STRUCT; + #define KSDATAFORMAT_SUBTYPE_IEEE_FLOAT __uuidof(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT_STRUCT) + #endif + +#endif + + +/************************************************************************** + * + * Speaker positions used in the WAVEFORMATEXTENSIBLE dwChannelMask field. + * + ***************************************************************************/ + +#ifndef SPEAKER_FRONT_LEFT + #define SPEAKER_FRONT_LEFT 0x00000001 + #define SPEAKER_FRONT_RIGHT 0x00000002 + #define SPEAKER_FRONT_CENTER 0x00000004 + #define SPEAKER_LOW_FREQUENCY 0x00000008 + #define SPEAKER_BACK_LEFT 0x00000010 + #define SPEAKER_BACK_RIGHT 0x00000020 + #define SPEAKER_FRONT_LEFT_OF_CENTER 0x00000040 + #define SPEAKER_FRONT_RIGHT_OF_CENTER 0x00000080 + #define SPEAKER_BACK_CENTER 0x00000100 + #define SPEAKER_SIDE_LEFT 0x00000200 + #define SPEAKER_SIDE_RIGHT 0x00000400 + #define SPEAKER_TOP_CENTER 0x00000800 + #define SPEAKER_TOP_FRONT_LEFT 0x00001000 + #define SPEAKER_TOP_FRONT_CENTER 0x00002000 + #define SPEAKER_TOP_FRONT_RIGHT 0x00004000 + #define SPEAKER_TOP_BACK_LEFT 0x00008000 + #define SPEAKER_TOP_BACK_CENTER 0x00010000 + #define SPEAKER_TOP_BACK_RIGHT 0x00020000 + #define SPEAKER_RESERVED 0x7FFC0000 + #define SPEAKER_ALL 0x80000000 + #define _SPEAKER_POSITIONS_ +#endif + +#ifndef SPEAKER_STEREO + #define SPEAKER_MONO (SPEAKER_FRONT_CENTER) + #define SPEAKER_STEREO (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT) + #define SPEAKER_2POINT1 (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_LOW_FREQUENCY) + #define SPEAKER_SURROUND (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_BACK_CENTER) + #define SPEAKER_QUAD (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT) + #define SPEAKER_4POINT1 (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT) + #define SPEAKER_5POINT1 (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT) + #define SPEAKER_7POINT1 (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_FRONT_LEFT_OF_CENTER | SPEAKER_FRONT_RIGHT_OF_CENTER) + #define SPEAKER_5POINT1_SURROUND (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT) + #define SPEAKER_7POINT1_SURROUND (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT) +#endif + + +#pragma pack(pop) + +#endif // #ifndef __AUDIODEFS_INCLUDED__ diff --git a/dependencies/DirectX_2010/comdecl.h b/dependencies/DirectX_2010/comdecl.h new file mode 100644 index 00000000..2ae9a961 --- /dev/null +++ b/dependencies/DirectX_2010/comdecl.h @@ -0,0 +1,59 @@ +// comdecl.h: Macros to facilitate COM interface and GUID declarations. +// Copyright (c) Microsoft Corporation. All rights reserved. + +#ifndef _COMDECL_H_ +#define _COMDECL_H_ + +#ifndef _XBOX + #include // For standard COM interface macros +#else + #pragma warning(push) + #pragma warning(disable:4061) + #include // Required by xobjbase.h + #include // Special definitions for Xbox build + #pragma warning(pop) +#endif + +// The DEFINE_CLSID() and DEFINE_IID() macros defined below allow COM GUIDs to +// be declared and defined in such a way that clients can obtain the GUIDs using +// either the __uuidof() extension or the old-style CLSID_Foo / IID_IFoo names. +// If using the latter approach, the client can also choose whether to get the +// GUID definitions by defining the INITGUID preprocessor constant or by linking +// to a GUID library. This works in either C or C++. + +#ifdef __cplusplus + + #define DECLSPEC_UUID_WRAPPER(x) __declspec(uuid(#x)) + #ifdef INITGUID + + #define DEFINE_CLSID(className, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + class DECLSPEC_UUID_WRAPPER(l##-##w1##-##w2##-##b1##b2##-##b3##b4##b5##b6##b7##b8) className; \ + EXTERN_C const GUID DECLSPEC_SELECTANY CLSID_##className = __uuidof(className) + + #define DEFINE_IID(interfaceName, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + interface DECLSPEC_UUID_WRAPPER(l##-##w1##-##w2##-##b1##b2##-##b3##b4##b5##b6##b7##b8) interfaceName; \ + EXTERN_C const GUID DECLSPEC_SELECTANY IID_##interfaceName = __uuidof(interfaceName) + + #else // INITGUID + + #define DEFINE_CLSID(className, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + class DECLSPEC_UUID_WRAPPER(l##-##w1##-##w2##-##b1##b2##-##b3##b4##b5##b6##b7##b8) className; \ + EXTERN_C const GUID CLSID_##className + + #define DEFINE_IID(interfaceName, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + interface DECLSPEC_UUID_WRAPPER(l##-##w1##-##w2##-##b1##b2##-##b3##b4##b5##b6##b7##b8) interfaceName; \ + EXTERN_C const GUID IID_##interfaceName + + #endif // INITGUID + +#else // __cplusplus + + #define DEFINE_CLSID(className, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + DEFINE_GUID(CLSID_##className, 0x##l, 0x##w1, 0x##w2, 0x##b1, 0x##b2, 0x##b3, 0x##b4, 0x##b5, 0x##b6, 0x##b7, 0x##b8) + + #define DEFINE_IID(interfaceName, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + DEFINE_GUID(IID_##interfaceName, 0x##l, 0x##w1, 0x##w2, 0x##b1, 0x##b2, 0x##b3, 0x##b4, 0x##b5, 0x##b6, 0x##b7, 0x##b8) + +#endif // __cplusplus + +#endif // #ifndef _COMDECL_H_ diff --git a/dependencies/DirectX_2010/xma2defs.h b/dependencies/DirectX_2010/xma2defs.h new file mode 100644 index 00000000..ce64a61a --- /dev/null +++ b/dependencies/DirectX_2010/xma2defs.h @@ -0,0 +1,718 @@ +/*************************************************************************** + * + * Copyright (c) Microsoft Corporation. All rights reserved. + * + * File: xma2defs.h + * Content: Constants, data types and functions for XMA2 compressed audio. + * + ***************************************************************************/ + +#ifndef __XMA2DEFS_INCLUDED__ +#define __XMA2DEFS_INCLUDED__ + +#include // Markers for documenting API semantics +#include // For S_OK, E_FAIL +#include "audiodefs.h" // Basic data types and constants for audio work + + +/*************************************************************************** + * Overview + ***************************************************************************/ + +// A typical XMA2 file contains these RIFF chunks: +// +// 'fmt' or 'XMA2' chunk (or both): A description of the XMA data's structure +// and characteristics (length, channels, sample rate, loops, block size, etc). +// +// 'seek' chunk: A seek table to help navigate the XMA data. +// +// 'data' chunk: The encoded XMA2 data. +// +// The encoded XMA2 data is structured as a set of BLOCKS, which contain PACKETS, +// which contain FRAMES, which contain SUBFRAMES (roughly speaking). The frames +// in a file may also be divided into several subsets, called STREAMS. +// +// FRAME: A variable-sized segment of XMA data that decodes to exactly 512 mono +// or stereo PCM samples. This is the smallest unit of XMA data that can +// be decoded in isolation. Frames are an arbitrary number of bits in +// length, and need not be byte-aligned. See "XMA frame structure" below. +// +// SUBFRAME: A region of bits in an XMA frame that decodes to 128 mono or stereo +// samples. The XMA decoder cannot decode a subframe in isolation; it needs +// a whole frame to work with. However, it can begin emitting the frame's +// decoded samples at any one of the four subframe boundaries. Subframes +// can be addressed for seeking and looping purposes. +// +// PACKET: A 2Kb region containing a 32-bit header and some XMA frames. Frames +// can (and usually do) span packets. A packet's header includes the offset +// in bits of the first frame that begins within that packet. All of the +// frames that begin in a given packet belong to the same "stream" (see the +// Multichannel Audio section below). +// +// STREAM: A set of packets within an XMA file that all contain data for the +// same mono or stereo component of a PCM file with more than two channels. +// The packets comprising a given stream may be interleaved with each other +// more or less arbitrarily; see Multichannel Audio. +// +// BLOCK: An array of XMA packets; or, to break it down differently, a series of +// consecutive XMA frames, padded at the end with reserved data. A block +// must contain at least one 2Kb packet per stream, and it can hold up to +// 4095 packets (8190Kb), but its size is typically in the 32Kb-128Kb range. +// (The size chosen involves a trade-off between memory use and efficiency +// of reading from permanent storage.) +// +// XMA frames do not span blocks, so a block is guaranteed to begin with a +// set of complete frames, one per stream. Also, a block in a multi-stream +// XMA2 file always contains the same number of samples for each stream; +// see Multichannel Audio. +// +// The 'data' chunk in an XMA2 file is an array of XMA2WAVEFORMAT.BlockCount XMA +// blocks, all the same size (as specified in XMA2WAVEFORMAT.BlockSizeInBytes) +// except for the last one, which may be shorter. + + +// MULTICHANNEL AUDIO: the XMA decoder can only decode raw XMA data into either +// mono or stereo PCM data. In order to encode a 6-channel file (say), the file +// must be deinterleaved into 3 stereo streams that are encoded independently, +// producing 3 encoded XMA data streams. Then the packets in these 3 streams +// are interleaved to produce a single XMA2 file, and some information is added +// to the file so that the original 6-channel audio can be reconstructed at +// decode time. This works using the concept of an XMA stream (see above). +// +// The frames for all the streams in an XMA file are interleaved in an arbitrary +// order. To locate a frame that belongs to a given stream in a given XMA block, +// you must examine the first few packets in the block. Here (and only here) the +// packets are guaranteed to be presented in stream order, so that all frames +// beginning in packet 0 belong to stream 0 (the first stereo pair), etc. +// +// (This means that when decoding multi-stream XMA files, only entire XMA blocks +// should be submitted to the decoder; otherwise it cannot know which frames +// belong to which stream.) +// +// Once you have one frame that belongs to a given stream, you can find the next +// one by looking at the frame's 'NextFrameOffsetBits' value (which is stored in +// its first 15 bits; see XMAFRAME below). The GetXmaFrameBitPosition function +// uses this technique. + + +// SEEKING IN XMA2 FILES: Here is some pseudocode to find the byte position and +// subframe in an XMA2 file which will contain sample S when decoded. +// +// 1. Traverse the seek table to find the XMA2 block containing sample S. The +// seek table is an array of big-endian DWORDs, one per block in the file. +// The Nth DWORD is the total number of PCM samples that would be obtained +// by decoding the entire XMA file up to the end of block N. Hence, the +// block we want is the first one whose seek table entry is greater than S. +// (See the GetXmaBlockContainingSample helper function.) +// +// 2. Calculate which frame F within the block found above contains sample S. +// Since each frame decodes to 512 samples, this is straightforward. The +// first frame in the block produces samples X to X + 512, where X is the +// seek table entry for the prior block. So F is (S - X) / 512. +// +// 3. Find the bit offset within the block where frame F starts. Since frames +// are variable-sized, this can only be done by traversing all the frames in +// the block until we reach frame F. (See GetXmaFrameBitPosition.) +// +// 4. Frame F has four 128-sample subframes. To find the subframe containing S, +// we can use the formula (S % 512) / 128. +// +// In the case of multi-stream XMA files, sample S is a multichannel sample with +// parts coming from several frames, one per stream. To find all these frames, +// steps 2-4 need to be repeated for each stream N, using the knowledge that the +// first packets in a block are presented in stream order. The frame traversal +// in step 3 must be started at the first frame in the Nth packet of the block, +// which will be the first frame for stream N. (And the packet header will tell +// you the first frame's start position within the packet.) +// +// Step 1 can be performed using the GetXmaBlockContainingSample function below, +// and steps 2-4 by calling GetXmaDecodePositionForSample once for each stream. + + + +/*************************************************************************** + * XMA constants + ***************************************************************************/ + +// Size of the PCM samples produced by the XMA decoder +#define XMA_OUTPUT_SAMPLE_BYTES 2u +#define XMA_OUTPUT_SAMPLE_BITS (XMA_OUTPUT_SAMPLE_BYTES * 8u) + +// Size of an XMA packet +#define XMA_BYTES_PER_PACKET 2048u +#define XMA_BITS_PER_PACKET (XMA_BYTES_PER_PACKET * 8u) + +// Size of an XMA packet header +#define XMA_PACKET_HEADER_BYTES 4u +#define XMA_PACKET_HEADER_BITS (XMA_PACKET_HEADER_BYTES * 8u) + +// Sample blocks in a decoded XMA frame +#define XMA_SAMPLES_PER_FRAME 512u + +// Sample blocks in a decoded XMA subframe +#define XMA_SAMPLES_PER_SUBFRAME 128u + +// Maximum encoded data that can be submitted to the XMA decoder at a time +#define XMA_READBUFFER_MAX_PACKETS 4095u +#define XMA_READBUFFER_MAX_BYTES (XMA_READBUFFER_MAX_PACKETS * XMA_BYTES_PER_PACKET) + +// Maximum size allowed for the XMA decoder's output buffers +#define XMA_WRITEBUFFER_MAX_BYTES (31u * 256u) + +// Required byte alignment of the XMA decoder's output buffers +#define XMA_WRITEBUFFER_BYTE_ALIGNMENT 256u + +// Decode chunk sizes for the XMA_PLAYBACK_INIT.subframesToDecode field +#define XMA_MIN_SUBFRAMES_TO_DECODE 1u +#define XMA_MAX_SUBFRAMES_TO_DECODE 8u +#define XMA_OPTIMAL_SUBFRAMES_TO_DECODE 4u + +// LoopCount<255 means finite repetitions; LoopCount=255 means infinite looping +#define XMA_MAX_LOOPCOUNT 254u +#define XMA_INFINITE_LOOP 255u + + + +/*************************************************************************** + * XMA format structures + ***************************************************************************/ + +// The currently recommended way to express format information for XMA2 files +// is the XMA2WAVEFORMATEX structure. This structure is fully compliant with +// the WAVEFORMATEX standard and contains all the information needed to parse +// and manage XMA2 files in a compact way. + +#define WAVE_FORMAT_XMA2 0x166 + +typedef struct XMA2WAVEFORMATEX +{ + WAVEFORMATEX wfx; + // Meaning of the WAVEFORMATEX fields here: + // wFormatTag; // Audio format type; always WAVE_FORMAT_XMA2 + // nChannels; // Channel count of the decoded audio + // nSamplesPerSec; // Sample rate of the decoded audio + // nAvgBytesPerSec; // Used internally by the XMA encoder + // nBlockAlign; // Decoded sample size; channels * wBitsPerSample / 8 + // wBitsPerSample; // Bits per decoded mono sample; always 16 for XMA + // cbSize; // Size in bytes of the rest of this structure (34) + + WORD NumStreams; // Number of audio streams (1 or 2 channels each) + DWORD ChannelMask; // Spatial positions of the channels in this file, + // stored as SPEAKER_xxx values (see audiodefs.h) + DWORD SamplesEncoded; // Total number of PCM samples the file decodes to + DWORD BytesPerBlock; // XMA block size (but the last one may be shorter) + DWORD PlayBegin; // First valid sample in the decoded audio + DWORD PlayLength; // Length of the valid part of the decoded audio + DWORD LoopBegin; // Beginning of the loop region in decoded sample terms + DWORD LoopLength; // Length of the loop region in decoded sample terms + BYTE LoopCount; // Number of loop repetitions; 255 = infinite + BYTE EncoderVersion; // Version of XMA encoder that generated the file + WORD BlockCount; // XMA blocks in file (and entries in its seek table) +} XMA2WAVEFORMATEX, *PXMA2WAVEFORMATEX; + + +// The legacy XMA format structures are described here for reference, but they +// should not be used in new content. XMAWAVEFORMAT was the structure used in +// XMA version 1 files. XMA2WAVEFORMAT was used in early XMA2 files; it is not +// placed in the usual 'fmt' RIFF chunk but in its own 'XMA2' chunk. + +#ifndef WAVE_FORMAT_XMA +#define WAVE_FORMAT_XMA 0x0165 + +// Values used in the ChannelMask fields below. Similar to the SPEAKER_xxx +// values defined in audiodefs.h, but modified to fit in a single byte. +#ifndef XMA_SPEAKER_LEFT + #define XMA_SPEAKER_LEFT 0x01 + #define XMA_SPEAKER_RIGHT 0x02 + #define XMA_SPEAKER_CENTER 0x04 + #define XMA_SPEAKER_LFE 0x08 + #define XMA_SPEAKER_LEFT_SURROUND 0x10 + #define XMA_SPEAKER_RIGHT_SURROUND 0x20 + #define XMA_SPEAKER_LEFT_BACK 0x40 + #define XMA_SPEAKER_RIGHT_BACK 0x80 +#endif + + +// Used in XMAWAVEFORMAT for per-stream data +typedef struct XMASTREAMFORMAT +{ + DWORD PsuedoBytesPerSec; // Used by the XMA encoder (typo preserved for legacy reasons) + DWORD SampleRate; // The stream's decoded sample rate (in XMA2 files, + // this is the same for all streams in the file). + DWORD LoopStart; // Bit offset of the frame containing the loop start + // point, relative to the beginning of the stream. + DWORD LoopEnd; // Bit offset of the frame containing the loop end. + BYTE SubframeData; // Two 4-bit numbers specifying the exact location of + // the loop points within the frames that contain them. + // SubframeEnd: Subframe of the loop end frame where + // the loop ends. Ranges from 0 to 3. + // SubframeSkip: Subframes to skip in the start frame to + // reach the loop. Ranges from 0 to 4. + BYTE Channels; // Number of channels in the stream (1 or 2) + WORD ChannelMask; // Spatial positions of the channels in the stream +} XMASTREAMFORMAT; + +// Legacy XMA1 format structure +typedef struct XMAWAVEFORMAT +{ + WORD FormatTag; // Audio format type (always WAVE_FORMAT_XMA) + WORD BitsPerSample; // Bit depth (currently required to be 16) + WORD EncodeOptions; // Options for XMA encoder/decoder + WORD LargestSkip; // Largest skip used in interleaving streams + WORD NumStreams; // Number of interleaved audio streams + BYTE LoopCount; // Number of loop repetitions; 255 = infinite + BYTE Version; // XMA encoder version that generated the file. + // Always 3 or higher for XMA2 files. + XMASTREAMFORMAT XmaStreams[1]; // Per-stream format information; the actual + // array length is in the NumStreams field. +} XMAWAVEFORMAT; + + +// Used in XMA2WAVEFORMAT for per-stream data +typedef struct XMA2STREAMFORMAT +{ + BYTE Channels; // Number of channels in the stream (1 or 2) + BYTE RESERVED; // Reserved for future use + WORD ChannelMask; // Spatial positions of the channels in the stream +} XMA2STREAMFORMAT; + +// Legacy XMA2 format structure (big-endian byte ordering) +typedef struct XMA2WAVEFORMAT +{ + BYTE Version; // XMA encoder version that generated the file. + // Always 3 or higher for XMA2 files. + BYTE NumStreams; // Number of interleaved audio streams + BYTE RESERVED; // Reserved for future use + BYTE LoopCount; // Number of loop repetitions; 255 = infinite + DWORD LoopBegin; // Loop begin point, in samples + DWORD LoopEnd; // Loop end point, in samples + DWORD SampleRate; // The file's decoded sample rate + DWORD EncodeOptions; // Options for the XMA encoder/decoder + DWORD PsuedoBytesPerSec; // Used internally by the XMA encoder + DWORD BlockSizeInBytes; // Size in bytes of this file's XMA blocks (except + // possibly the last one). Always a multiple of + // 2Kb, since XMA blocks are arrays of 2Kb packets. + DWORD SamplesEncoded; // Total number of PCM samples encoded in this file + DWORD SamplesInSource; // Actual number of PCM samples in the source + // material used to generate this file + DWORD BlockCount; // Number of XMA blocks in this file (and hence + // also the number of entries in its seek table) + XMA2STREAMFORMAT Streams[1]; // Per-stream format information; the actual + // array length is in the NumStreams field. +} XMA2WAVEFORMAT; + +#endif // #ifndef WAVE_FORMAT_XMA + + + +/*************************************************************************** + * XMA packet structure (in big-endian form) + ***************************************************************************/ + +typedef struct XMA2PACKET +{ + int FrameCount : 6; // Number of XMA frames that begin in this packet + int FrameOffsetInBits : 15; // Bit of XmaData where the first complete frame begins + int PacketMetaData : 3; // Metadata stored in the packet (always 1 for XMA2) + int PacketSkipCount : 8; // How many packets belonging to other streams must be + // skipped to find the next packet belonging to this one + BYTE XmaData[XMA_BYTES_PER_PACKET - sizeof(DWORD)]; // XMA encoded data +} XMA2PACKET; + +// E.g. if the first DWORD of a packet is 0x30107902: +// +// 001100 000001000001111 001 00000010 +// | | | |____ Skip 2 packets to find the next one for this stream +// | | |___________ XMA2 signature (always 001) +// | |_____________________ First frame starts 527 bits into packet +// |________________________________ Packet contains 12 frames + + +// Helper functions to extract the fields above from an XMA packet. (Note that +// the bitfields cannot be read directly on little-endian architectures such as +// the Intel x86, as they are laid out in big-endian form.) + +__inline DWORD GetXmaPacketFrameCount(__in_bcount(1) const BYTE* pPacket) +{ + return (DWORD)(pPacket[0] >> 2); +} + +__inline DWORD GetXmaPacketFirstFrameOffsetInBits(__in_bcount(3) const BYTE* pPacket) +{ + return ((DWORD)(pPacket[0] & 0x3) << 13) | + ((DWORD)(pPacket[1]) << 5) | + ((DWORD)(pPacket[2]) >> 3); +} + +__inline DWORD GetXmaPacketMetadata(__in_bcount(3) const BYTE* pPacket) +{ + return (DWORD)(pPacket[2] & 0x7); +} + +__inline DWORD GetXmaPacketSkipCount(__in_bcount(4) const BYTE* pPacket) +{ + return (DWORD)(pPacket[3]); +} + + + +/*************************************************************************** + * XMA frame structure + ***************************************************************************/ + +// There is no way to represent the XMA frame as a C struct, since it is a +// variable-sized string of bits that need not be stored at a byte-aligned +// position in memory. This is the layout: +// +// XMAFRAME +// { +// LengthInBits: A 15-bit number representing the length of this frame. +// XmaData: Encoded XMA data; its size in bits is (LengthInBits - 15). +// } + +// Size in bits of the frame's initial LengthInBits field +#define XMA_BITS_IN_FRAME_LENGTH_FIELD 15 + +// Special LengthInBits value that marks an invalid final frame +#define XMA_FINAL_FRAME_MARKER 0x7FFF + + + +/*************************************************************************** + * XMA helper functions + ***************************************************************************/ + +// We define a local ASSERT macro to equal the global one if it exists. +// You can define XMA2DEFS_ASSERT in advance to override this default. +#ifndef XMA2DEFS_ASSERT + #ifdef ASSERT + #define XMA2DEFS_ASSERT ASSERT + #else + #define XMA2DEFS_ASSERT(a) /* No-op by default */ + #endif +#endif + + +// GetXmaBlockContainingSample: Use a given seek table to find the XMA block +// containing a given decoded sample. Note that the seek table entries in an +// XMA file are stored in big-endian form and may need to be converted prior +// to calling this function. + +__inline HRESULT GetXmaBlockContainingSample +( + DWORD nBlockCount, // Blocks in the file (= seek table entries) + __in_ecount(nBlockCount) const DWORD* pSeekTable, // Pointer to the seek table data + DWORD nDesiredSample, // Decoded sample to locate + __out DWORD* pnBlockContainingSample, // Index of the block containing the sample + __out DWORD* pnSampleOffsetWithinBlock // Position of the sample in this block +) +{ + DWORD nPreviousTotalSamples = 0; + DWORD nBlock; + DWORD nTotalSamplesSoFar; + + XMA2DEFS_ASSERT(pSeekTable); + XMA2DEFS_ASSERT(pnBlockContainingSample); + XMA2DEFS_ASSERT(pnSampleOffsetWithinBlock); + + for (nBlock = 0; nBlock < nBlockCount; ++nBlock) + { + nTotalSamplesSoFar = pSeekTable[nBlock]; + if (nTotalSamplesSoFar > nDesiredSample) + { + *pnBlockContainingSample = nBlock; + *pnSampleOffsetWithinBlock = nDesiredSample - nPreviousTotalSamples; + return S_OK; + } + nPreviousTotalSamples = nTotalSamplesSoFar; + } + + return E_FAIL; +} + + +// GetXmaFrameLengthInBits: Reads a given frame's LengthInBits field. + +__inline DWORD GetXmaFrameLengthInBits +( + __in_bcount(nBitPosition / 8 + 3) + __in const BYTE* pPacket, // Pointer to XMA packet[s] containing the frame + DWORD nBitPosition // Bit offset of the frame within this packet +) +{ + DWORD nRegion; + DWORD nBytePosition = nBitPosition / 8; + DWORD nBitOffset = nBitPosition % 8; + + if (nBitOffset < 2) // Only need to read 2 bytes (and might not be safe to read more) + { + nRegion = (DWORD)(pPacket[nBytePosition+0]) << 8 | + (DWORD)(pPacket[nBytePosition+1]); + return (nRegion >> (1 - nBitOffset)) & 0x7FFF; // Last 15 bits + } + else // Need to read 3 bytes + { + nRegion = (DWORD)(pPacket[nBytePosition+0]) << 16 | + (DWORD)(pPacket[nBytePosition+1]) << 8 | + (DWORD)(pPacket[nBytePosition+2]); + return (nRegion >> (9 - nBitOffset)) & 0x7FFF; // Last 15 bits + } +} + + +// GetXmaFrameBitPosition: Calculates the bit offset of a given frame within +// an XMA block or set of blocks. Returns 0 on failure. + +__inline DWORD GetXmaFrameBitPosition +( + __in_bcount(nXmaDataBytes) const BYTE* pXmaData, // Pointer to XMA block[s] + DWORD nXmaDataBytes, // Size of pXmaData in bytes + DWORD nStreamIndex, // Stream within which to seek + DWORD nDesiredFrame // Frame sought +) +{ + const BYTE* pCurrentPacket; + DWORD nPacketsExamined = 0; + DWORD nFrameCountSoFar = 0; + DWORD nFramesToSkip; + DWORD nFrameBitOffset; + + XMA2DEFS_ASSERT(pXmaData); + XMA2DEFS_ASSERT(nXmaDataBytes % XMA_BYTES_PER_PACKET == 0); + + // Get the first XMA packet belonging to the desired stream, relying on the + // fact that the first packets for each stream are in consecutive order at + // the beginning of an XMA block. + + pCurrentPacket = pXmaData + nStreamIndex * XMA_BYTES_PER_PACKET; + for (;;) + { + // If we have exceeded the size of the XMA data, return failure + if (pCurrentPacket + XMA_BYTES_PER_PACKET > pXmaData + nXmaDataBytes) + { + return 0; + } + + // If the current packet contains the frame we are looking for... + if (nFrameCountSoFar + GetXmaPacketFrameCount(pCurrentPacket) > nDesiredFrame) + { + // See how many frames in this packet we need to skip to get to it + XMA2DEFS_ASSERT(nDesiredFrame >= nFrameCountSoFar); + nFramesToSkip = nDesiredFrame - nFrameCountSoFar; + + // Get the bit offset of the first frame in this packet + nFrameBitOffset = XMA_PACKET_HEADER_BITS + GetXmaPacketFirstFrameOffsetInBits(pCurrentPacket); + + // Advance nFrameBitOffset to the frame of interest + while (nFramesToSkip--) + { + nFrameBitOffset += GetXmaFrameLengthInBits(pCurrentPacket, nFrameBitOffset); + } + + // The bit offset to return is the number of bits from pXmaData to + // pCurrentPacket plus the bit offset of the frame of interest + return (DWORD)(pCurrentPacket - pXmaData) * 8 + nFrameBitOffset; + } + + // If we haven't found the right packet yet, advance our counters + ++nPacketsExamined; + nFrameCountSoFar += GetXmaPacketFrameCount(pCurrentPacket); + + // And skip to the next packet belonging to the same stream + pCurrentPacket += XMA_BYTES_PER_PACKET * (GetXmaPacketSkipCount(pCurrentPacket) + 1); + } +} + + +// GetLastXmaFrameBitPosition: Calculates the bit offset of the last complete +// frame in an XMA block or set of blocks. + +__inline DWORD GetLastXmaFrameBitPosition +( + __in_bcount(nXmaDataBytes) const BYTE* pXmaData, // Pointer to XMA block[s] + DWORD nXmaDataBytes, // Size of pXmaData in bytes + DWORD nStreamIndex // Stream within which to seek +) +{ + const BYTE* pLastPacket; + DWORD nBytesToNextPacket; + DWORD nFrameBitOffset; + DWORD nFramesInLastPacket; + + XMA2DEFS_ASSERT(pXmaData); + XMA2DEFS_ASSERT(nXmaDataBytes % XMA_BYTES_PER_PACKET == 0); + XMA2DEFS_ASSERT(nXmaDataBytes >= XMA_BYTES_PER_PACKET * (nStreamIndex + 1)); + + // Get the first XMA packet belonging to the desired stream, relying on the + // fact that the first packets for each stream are in consecutive order at + // the beginning of an XMA block. + pLastPacket = pXmaData + nStreamIndex * XMA_BYTES_PER_PACKET; + + // Search for the last packet belonging to the desired stream + for (;;) + { + nBytesToNextPacket = XMA_BYTES_PER_PACKET * (GetXmaPacketSkipCount(pLastPacket) + 1); + XMA2DEFS_ASSERT(nBytesToNextPacket); + if (pLastPacket + nBytesToNextPacket + XMA_BYTES_PER_PACKET > pXmaData + nXmaDataBytes) + { + break; // The next packet would extend beyond the end of pXmaData + } + pLastPacket += nBytesToNextPacket; + } + + // The last packet can sometimes have no seekable frames, in which case we + // have to use the previous one + if (GetXmaPacketFrameCount(pLastPacket) == 0) + { + pLastPacket -= nBytesToNextPacket; + } + + // Found the last packet. Get the bit offset of its first frame. + nFrameBitOffset = XMA_PACKET_HEADER_BITS + GetXmaPacketFirstFrameOffsetInBits(pLastPacket); + + // Traverse frames until we reach the last one + nFramesInLastPacket = GetXmaPacketFrameCount(pLastPacket); + while (--nFramesInLastPacket) + { + nFrameBitOffset += GetXmaFrameLengthInBits(pLastPacket, nFrameBitOffset); + } + + // The bit offset to return is the number of bits from pXmaData to + // pLastPacket plus the offset of the last frame in this packet. + return (DWORD)(pLastPacket - pXmaData) * 8 + nFrameBitOffset; +} + + +// GetXmaDecodePositionForSample: Obtains the information needed to make the +// decoder generate audio starting at a given sample position relative to the +// beginning of the given XMA block: the bit offset of the appropriate frame, +// and the right subframe within that frame. This data can be passed directly +// to the XMAPlaybackSetDecodePosition function. + +__inline HRESULT GetXmaDecodePositionForSample +( + __in_bcount(nXmaDataBytes) const BYTE* pXmaData, // Pointer to XMA block[s] + DWORD nXmaDataBytes, // Size of pXmaData in bytes + DWORD nStreamIndex, // Stream within which to seek + DWORD nDesiredSample, // Sample sought + __out DWORD* pnBitOffset, // Returns the bit offset within pXmaData of + // the frame containing the sample sought + __out DWORD* pnSubFrame // Returns the subframe containing the sample +) +{ + DWORD nDesiredFrame = nDesiredSample / XMA_SAMPLES_PER_FRAME; + DWORD nSubFrame = (nDesiredSample % XMA_SAMPLES_PER_FRAME) / XMA_SAMPLES_PER_SUBFRAME; + DWORD nBitOffset = GetXmaFrameBitPosition(pXmaData, nXmaDataBytes, nStreamIndex, nDesiredFrame); + + XMA2DEFS_ASSERT(pnBitOffset); + XMA2DEFS_ASSERT(pnSubFrame); + + if (nBitOffset) + { + *pnBitOffset = nBitOffset; + *pnSubFrame = nSubFrame; + return S_OK; + } + else + { + return E_FAIL; + } +} + + +// GetXmaSampleRate: Obtains the legal XMA sample rate (24, 32, 44.1 or 48Khz) +// corresponding to a generic sample rate. + +__inline DWORD GetXmaSampleRate(DWORD dwGeneralRate) +{ + DWORD dwXmaRate = 48000; // Default XMA rate for all rates above 44100Hz + + if (dwGeneralRate <= 24000) dwXmaRate = 24000; + else if (dwGeneralRate <= 32000) dwXmaRate = 32000; + else if (dwGeneralRate <= 44100) dwXmaRate = 44100; + + return dwXmaRate; +} + + +// Functions to convert between WAVEFORMATEXTENSIBLE channel masks (combinations +// of the SPEAKER_xxx flags defined in audiodefs.h) and XMA channel masks (which +// are limited to eight possible speaker positions: left, right, center, low +// frequency, side left, side right, back left and back right). + +__inline DWORD GetStandardChannelMaskFromXmaMask(BYTE bXmaMask) +{ + DWORD dwStandardMask = 0; + + if (bXmaMask & XMA_SPEAKER_LEFT) dwStandardMask |= SPEAKER_FRONT_LEFT; + if (bXmaMask & XMA_SPEAKER_RIGHT) dwStandardMask |= SPEAKER_FRONT_RIGHT; + if (bXmaMask & XMA_SPEAKER_CENTER) dwStandardMask |= SPEAKER_FRONT_CENTER; + if (bXmaMask & XMA_SPEAKER_LFE) dwStandardMask |= SPEAKER_LOW_FREQUENCY; + if (bXmaMask & XMA_SPEAKER_LEFT_SURROUND) dwStandardMask |= SPEAKER_SIDE_LEFT; + if (bXmaMask & XMA_SPEAKER_RIGHT_SURROUND) dwStandardMask |= SPEAKER_SIDE_RIGHT; + if (bXmaMask & XMA_SPEAKER_LEFT_BACK) dwStandardMask |= SPEAKER_BACK_LEFT; + if (bXmaMask & XMA_SPEAKER_RIGHT_BACK) dwStandardMask |= SPEAKER_BACK_RIGHT; + + return dwStandardMask; +} + +__inline BYTE GetXmaChannelMaskFromStandardMask(DWORD dwStandardMask) +{ + BYTE bXmaMask = 0; + + if (dwStandardMask & SPEAKER_FRONT_LEFT) bXmaMask |= XMA_SPEAKER_LEFT; + if (dwStandardMask & SPEAKER_FRONT_RIGHT) bXmaMask |= XMA_SPEAKER_RIGHT; + if (dwStandardMask & SPEAKER_FRONT_CENTER) bXmaMask |= XMA_SPEAKER_CENTER; + if (dwStandardMask & SPEAKER_LOW_FREQUENCY) bXmaMask |= XMA_SPEAKER_LFE; + if (dwStandardMask & SPEAKER_SIDE_LEFT) bXmaMask |= XMA_SPEAKER_LEFT_SURROUND; + if (dwStandardMask & SPEAKER_SIDE_RIGHT) bXmaMask |= XMA_SPEAKER_RIGHT_SURROUND; + if (dwStandardMask & SPEAKER_BACK_LEFT) bXmaMask |= XMA_SPEAKER_LEFT_BACK; + if (dwStandardMask & SPEAKER_BACK_RIGHT) bXmaMask |= XMA_SPEAKER_RIGHT_BACK; + + return bXmaMask; +} + + +// LocalizeXma2Format: Modifies a XMA2WAVEFORMATEX structure in place to comply +// with the current platform's byte-ordering rules (little- or big-endian). + +__inline HRESULT LocalizeXma2Format(__inout XMA2WAVEFORMATEX* pXma2Format) +{ + #define XMASWAP2BYTES(n) ((WORD)(((n) >> 8) | (((n) & 0xff) << 8))) + #define XMASWAP4BYTES(n) ((DWORD)((n) >> 24 | (n) << 24 | ((n) & 0xff00) << 8 | ((n) & 0xff0000) >> 8)) + + if (pXma2Format->wfx.wFormatTag == WAVE_FORMAT_XMA2) + { + return S_OK; + } + else if (XMASWAP2BYTES(pXma2Format->wfx.wFormatTag) == WAVE_FORMAT_XMA2) + { + pXma2Format->wfx.wFormatTag = XMASWAP2BYTES(pXma2Format->wfx.wFormatTag); + pXma2Format->wfx.nChannels = XMASWAP2BYTES(pXma2Format->wfx.nChannels); + pXma2Format->wfx.nSamplesPerSec = XMASWAP4BYTES(pXma2Format->wfx.nSamplesPerSec); + pXma2Format->wfx.nAvgBytesPerSec = XMASWAP4BYTES(pXma2Format->wfx.nAvgBytesPerSec); + pXma2Format->wfx.nBlockAlign = XMASWAP2BYTES(pXma2Format->wfx.nBlockAlign); + pXma2Format->wfx.wBitsPerSample = XMASWAP2BYTES(pXma2Format->wfx.wBitsPerSample); + pXma2Format->wfx.cbSize = XMASWAP2BYTES(pXma2Format->wfx.cbSize); + pXma2Format->NumStreams = XMASWAP2BYTES(pXma2Format->NumStreams); + pXma2Format->ChannelMask = XMASWAP4BYTES(pXma2Format->ChannelMask); + pXma2Format->SamplesEncoded = XMASWAP4BYTES(pXma2Format->SamplesEncoded); + pXma2Format->BytesPerBlock = XMASWAP4BYTES(pXma2Format->BytesPerBlock); + pXma2Format->PlayBegin = XMASWAP4BYTES(pXma2Format->PlayBegin); + pXma2Format->PlayLength = XMASWAP4BYTES(pXma2Format->PlayLength); + pXma2Format->LoopBegin = XMASWAP4BYTES(pXma2Format->LoopBegin); + pXma2Format->LoopLength = XMASWAP4BYTES(pXma2Format->LoopLength); + pXma2Format->BlockCount = XMASWAP2BYTES(pXma2Format->BlockCount); + return S_OK; + } + else + { + return E_FAIL; // Not a recognizable XMA2 format + } + + #undef XMASWAP2BYTES + #undef XMASWAP4BYTES +} + + +#endif // #ifndef __XMA2DEFS_INCLUDED__ diff --git a/dependencies/discord-rpc/.clang-format b/dependencies/discord-rpc/.clang-format new file mode 100644 index 00000000..1be83906 --- /dev/null +++ b/dependencies/discord-rpc/.clang-format @@ -0,0 +1,92 @@ +--- +AccessModifierOffset: -4 +AlignAfterOpenBracket: true +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Left +AlignOperands: false +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: InlineOnly +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: true +BinPackArguments: false +BinPackParameters: false +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Stroustrup +BreakBeforeInheritanceComma: true +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeComma +BreakStringLiterals: true +ColumnLimit: 100 +CommentPragmas: '' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 2 +ContinuationIndentWidth: 2 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +FixNamespaceComments: true +ForEachMacros: [] +IndentCaseLabels: false +IncludeCategories: + - Regex: '^("|<)stdafx\.h(pp)?("|>)' + Priority: -1 + - Regex: '^<(W|w)indows.h>' + Priority: 1 + - Regex: '^<' + Priority: 2 + - Regex: '.*' + Priority: 3 +IncludeIsMainRegex: '(_test|_win|_linux|_mac|_ios|_osx|_null)?$' +IndentCaseLabels: false +IndentWidth: 4 +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +PenaltyBreakAssignment: 0 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 9999999 +PointerAlignment: Left +ReflowComments: true +SortIncludes: false +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInContainerLiterals: true +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +TabWidth: 4 +UseTab: Never +--- +Language: Cpp +--- +Language: ObjC +ObjCBlockIndentWidth: 4 +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: false +--- +Language: Java +BasedOnStyle: Google +BreakAfterJavaFieldAnnotations: true +... diff --git a/dependencies/discord-rpc/.gitignore b/dependencies/discord-rpc/.gitignore new file mode 100644 index 00000000..223c07d7 --- /dev/null +++ b/dependencies/discord-rpc/.gitignore @@ -0,0 +1,5 @@ +/build*/ +/.vscode/ +/thirdparty/ +.vs/ +.DS_Store \ No newline at end of file diff --git a/dependencies/discord-rpc/.travis.yml b/dependencies/discord-rpc/.travis.yml new file mode 100644 index 00000000..42cc09d5 --- /dev/null +++ b/dependencies/discord-rpc/.travis.yml @@ -0,0 +1,47 @@ +language: cpp + +env: + global: + - CLANG_FORMAT_SUFFIX="-dummy" # don't use formatting on Travis, this is + # needed not to use default 3.5 version + # which is too old. + +matrix: + include: + - os: linux + env: MATRIX_EVAL="CC=gcc-5 && CXX=g++-5" + addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - g++-5 + - os: linux + env: MATRIX_EVAL="CC=clang-4.0 && CXX=clang++-4.0" + addons: + apt: + sources: + - llvm-toolchain-trusty-4.0 + packages: + - clang-4.0 + - os: linux + env: MATRIX_EVAL="CC=clang-5.0 && CXX=clang++-5.0" + addons: + apt: + sources: + - llvm-toolchain-trusty-5.0 + packages: + - clang-5.0 + - os: osx + osx_image: xcode9 + +# prevent Travis from overwriting our CXX variables +before_install: + - eval "${MATRIX_EVAL}" + - echo $CXX + +script: + - mkdir build + - cd build + - cmake -DCLANG_FORMAT_SUFFIX=$CLANG_FORMAT_SUFFIX -DWARNINGS_AS_ERRORS=On --config Release .. + - cmake --build . -- -j2 diff --git a/dependencies/discord-rpc/CMakeLists.txt b/dependencies/discord-rpc/CMakeLists.txt new file mode 100644 index 00000000..4aa11080 --- /dev/null +++ b/dependencies/discord-rpc/CMakeLists.txt @@ -0,0 +1,42 @@ +cmake_minimum_required (VERSION 3.2.0) +project (DiscordRPC) + +include(GNUInstallDirs) + +option(BUILD_EXAMPLES "Build example apps" OFF) + +# format +file(GLOB_RECURSE ALL_SOURCE_FILES + examples/*.cpp examples/*.h examples/*.c + include/*.h + src/*.cpp src/*.h src/*.c +) + +# thirdparty stuff +execute_process( + COMMAND mkdir ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty + ERROR_QUIET +) + +#find_file(RAPIDJSONTEST NAMES rapidjson rapidjson-1.1.0 PATHS ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty CMAKE_FIND_ROOT_PATH_BOTH) +#if (NOT RAPIDJSONTEST) +# message("no rapidjson, download") +# set(RJ_TAR_FILE ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/v1.1.0.tar.gz) +# file(DOWNLOAD https://github.com/miloyip/rapidjson/archive/v1.1.0.tar.gz ${RJ_TAR_FILE}) +# execute_process( +# COMMAND ${CMAKE_COMMAND} -E tar xzf ${RJ_TAR_FILE} +# WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty +# ) +# file(REMOVE ${RJ_TAR_FILE}) +#endif(NOT RAPIDJSONTEST) + +#find_file(RAPIDJSON NAMES rapidjson rapidjson-1.1.0 PATHS ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty CMAKE_FIND_ROOT_PATH_BOTH) + +#add_library(rapidjson STATIC IMPORTED ${RAPIDJSON}) + +# add subdirs + +add_subdirectory(src) +if (BUILD_EXAMPLES) + add_subdirectory(examples/send-presence) +endif(BUILD_EXAMPLES) diff --git a/dependencies/discord-rpc/LICENSE b/dependencies/discord-rpc/LICENSE new file mode 100644 index 00000000..17fca3d5 --- /dev/null +++ b/dependencies/discord-rpc/LICENSE @@ -0,0 +1,19 @@ +Copyright 2017 Discord, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/dependencies/discord-rpc/README.md b/dependencies/discord-rpc/README.md new file mode 100644 index 00000000..1285fb84 --- /dev/null +++ b/dependencies/discord-rpc/README.md @@ -0,0 +1,158 @@ +# Discord RPC + +## Deprecation Notice + +This library has been deprecated in favor of Discord's GameSDK. [Learn more here](https://discordapp.com/developers/docs/game-sdk/sdk-starter-guide) + +--- + +This is a library for interfacing your game with a locally running Discord desktop client. It's known to work on Windows, macOS, and Linux. You can use the lib directly if you like, or use it as a guide to writing your own if it doesn't suit your game as is. PRs/feedback welcome if you have an improvement everyone might want, or can describe how this doesn't meet your needs. + +Included here are some quick demos that implement the very minimal subset to show current status, and +have callbacks for where a more complete game would do more things (joining, spectating, etc). + +## Documentation + +The most up to date documentation for Rich Presence can always be found on our [developer site](https://discordapp.com/developers/docs/rich-presence/how-to)! If you're interested in rolling your own native implementation of Rich Presence via IPC sockets instead of using our SDK—hey, you've got free time, right?—check out the ["Hard Mode" documentation](https://github.com/discordapp/discord-rpc/blob/master/documentation/hard-mode.md). + +## Basic Usage + +Zeroith, you should be set up to build things because you are a game developer, right? + +First, head on over to the [Discord developers site](https://discordapp.com/developers/applications/me) and make yourself an app. Keep track of `Client ID` -- you'll need it here to pass to the init function. + +### Unreal Engine 4 Setup + +To use the Rich Presense plugin with Unreal Engine Projects: + +1. Download the latest [release](https://github.com/discordapp/discord-rpc/releases) for each operating system you are targeting and the zipped source code +2. In the source code zip, copy the UE plugin—`examples/unrealstatus/Plugins/discordrpc`—to your project's plugin directory +3. At `[YOUR_UE_PROJECT]/Plugins/discordrpc/source/ThirdParty/DiscordRpcLibrary/`, create an `Include` folder and copy `discord_rpc.h` and `discord_register.h` to it from the zip +4. Follow the steps below for each OS +5. Build your UE4 project +6. Launch the editor, and enable the Discord plugin. + +#### Windows + +- At `[YOUR_UE_PROJECT]/Plugins/discordrpc/source/ThirdParty/DiscordRpcLibrary/`, create a `Win64` folder +- Copy `lib/discord-rpc.lib` and `bin/discord-rpc.dll` from `[RELEASE_ZIP]/win64-dynamic` to the `Win64` folder + +#### Mac + +- At `[YOUR_UE_PROJECT]/Plugins/discordrpc/source/ThirdParty/DiscordRpcLibrary/`, create a `Mac` folder +- Copy `libdiscord-rpc.dylib` from `[RELEASE_ZIP]/osx-dynamic/lib` to the `Mac` folder + +#### Linux + +- At `[YOUR_UE_PROJECT]/Plugins/discordrpc/source/ThirdParty/DiscordRpcLibrary/`, create a `Linux` folder +- Inside, create another folder `x86_64-unknown-linux-gnu` +- Copy `libdiscord-rpc.so` from `[RELEASE_ZIP]/linux-dynamic/lib` to `Linux/x86_64-unknown-linux-gnu` + +### Unity Setup + +If you're a Unity developer looking to integrate Rich Presence into your game, follow this simple guide to get started towards success: + +1. Download the DLLs for any platform that you need from [our releases](https://github.com/discordapp/discord-rpc/releases) +2. In your Unity project, create a `Plugins` folder inside your `Assets` folder if you don't already have one +3. Copy the file `DiscordRpc.cs` from [here](https://github.com/discordapp/discord-rpc/blob/master/examples/button-clicker/Assets/DiscordRpc.cs) into your `Assets` folder. This is basically your header file for the SDK + +We've got our `Plugins` folder ready, so let's get platform-specific! + +#### Windows + +4. Create `x86` and `x86_64` folders inside `Assets/Plugins/` +5. Copy `discord-rpc-win/win64-dynamic/bin/discord-rpc.dll` to `Assets/Plugins/x86_64/` +6. Copy `discord-rpc-win/win32-dynamic/bin/discord-rpc.dll` to `Assets/Plugins/x86/` +7. Click on both DLLs and make sure they are targetting the correct architectures in the Unity editor properties pane +8. Done! + +#### MacOS + +4. Copy `discord-rpc-osx/osx-dynamic/lib/libdiscord-rpc.dylib` to `Assets/Plugins/` +5. Rename `libdiscord-rpc.dylib` to `discord-rpc.bundle` +6. Done! + +#### Linux + +4. Copy `discord-rpc-linux/linux-dynamic-lib/libdiscord-rpc.so` to `Assets/Plugins/` +5. Done! + +You're ready to roll! For code examples on how to interact with the SDK using the `DiscordRpc.cs` header file, check out [our example](https://github.com/discordapp/discord-rpc/blob/master/examples/button-clicker/Assets/DiscordController.cs) + +### From package + +Download a release package for your platform(s) -- they have subdirs with various prebuilt options, select the one you need add `/include` to your compile includes, `/lib` to your linker paths, and link with `discord-rpc`. For the dynamically linked builds, you'll need to ship the associated file along with your game. + +### From repo + +First-eth, you'll want `CMake`. There's a few different ways to install it on your system, and you should refer to [their website](https://cmake.org/install/). Many package managers provide ways of installing CMake as well. + +To make sure it's installed correctly, type `cmake --version` into your flavor of terminal/cmd. If you get a response with a version number, you're good to go! + +There's a [CMake](https://cmake.org/download/) file that should be able to generate the lib for you; Sometimes I use it like this: + +```sh + cd + mkdir build + cd build + cmake .. -DCMAKE_INSTALL_PREFIX= + cmake --build . --config Release --target install +``` + +There is a wrapper build script `build.py` that runs `cmake` with a few different options. + +Usually, I run `build.py` to get things started, then use the generated project files as I work on things. It does depend on `click` library, so do a quick `pip install click` to make sure you have it if you want to run `build.py`. + +There are some CMake options you might care about: + +| flag | default | does | +| ---------------------------------------------------------------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | +| `ENABLE_IO_THREAD` | `ON` | When enabled, we start up a thread to do io processing, if disabled you should call `Discord_UpdateConnection` yourself. | +| `USE_STATIC_CRT` | `OFF` | (Windows) Enable to statically link the CRT, avoiding requiring users install the redistributable package. (The prebuilt binaries enable this option) | +| [`BUILD_SHARED_LIBS`](https://cmake.org/cmake/help/v3.7/variable/BUILD_SHARED_LIBS.html) | `OFF` | Build library as a DLL | +| `WARNINGS_AS_ERRORS` | `OFF` | When enabled, compiles with `-Werror` (on \*nix platforms). | + +## Continuous Builds + +Why do we have three of these? Three times the fun! + +| CI | badge | +| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | +| TravisCI | [![Build status](https://travis-ci.org/discordapp/discord-rpc.svg?branch=master)](https://travis-ci.org/discordapp/discord-rpc) | +| AppVeyor | [![Build status](https://ci.appveyor.com/api/projects/status/qvkoc0w1c4f4b8tj?svg=true)](https://ci.appveyor.com/project/crmarsh/discord-rpc) | +| Buildkite (internal) | [![Build status](https://badge.buildkite.com/e103d79d247f6776605a15246352a04b8fd83d69211b836111.svg)](https://buildkite.com/discord/discord-rpc) | + +## Sample: send-presence + +This is a text adventure "game" that inits/deinits the connection to Discord, and sends a presence update on each command. + +## Sample: button-clicker + +This is a sample [Unity](https://unity3d.com/) project that wraps a DLL version of the library, and sends presence updates when you click on a button. Run `python build.py unity` in the root directory to build the correct library files and place them in their respective folders. + +## Sample: unrealstatus + +This is a sample [Unreal](https://www.unrealengine.com) project that wraps the DLL version of the library with an Unreal plugin, exposes a blueprint class for interacting with it, and uses that to make a very simple UI. Run `python build.py unreal` in the root directory to build the correct library files and place them in their respective folders. + +## Wrappers and Implementations + +Below is a table of unofficial, community-developed wrappers for and implementations of Rich Presence in various languages. If you would like to have yours added, please make a pull request adding your repository to the table. The repository should include: + +- The code +- A brief ReadMe of how to use it +- A working example + +###### Rich Presence Wrappers and Implementations + +| Name | Language | +| ------------------------------------------------------------------------- | --------------------------------- | +| [Discord RPC C#](https://github.com/Lachee/discord-rpc-csharp) | C# | +| [Discord RPC D](https://github.com/voidblaster/discord-rpc-d) | [D](https://dlang.org/) | +| [discord-rpc.jar](https://github.com/Vatuu/discord-rpc 'Discord-RPC.jar') | Java | +| [java-discord-rpc](https://github.com/MinnDevelopment/java-discord-rpc) | Java | +| [Discord-IPC](https://github.com/jagrosh/DiscordIPC) | Java | +| [Discord Rich Presence](https://npmjs.org/discord-rich-presence) | JavaScript | +| [drpc4k](https://github.com/Bluexin/drpc4k) | [Kotlin](https://kotlinlang.org/) | +| [lua-discordRPC](https://github.com/pfirsich/lua-discordRPC) | LuaJIT (FFI) | +| [pypresence](https://github.com/qwertyquerty/pypresence) | [Python](https://python.org/) | +| [SwordRPC](https://github.com/Azoy/SwordRPC) | [Swift](https://swift.org) | diff --git a/dependencies/discord-rpc/appveyor.yml b/dependencies/discord-rpc/appveyor.yml new file mode 100644 index 00000000..1c328b87 --- /dev/null +++ b/dependencies/discord-rpc/appveyor.yml @@ -0,0 +1,17 @@ +version: '{build}' +install: + - python -m pip install click + +build_script: + - mkdir examples\unrealstatus\Plugins\discordrpc\Binaries\ThirdParty\discordrpcLibrary\Win64 + - python build.py + +artifacts: +- path: builds\install\win32-dynamic + name: win32-dynamic +- path: builds\install\win32-static + name: win32-static +- path: builds\install\win64-dynamic + name: win64-dynamic +- path: builds\install\win64-static + name: win64-static diff --git a/dependencies/discord-rpc/build.py b/dependencies/discord-rpc/build.py new file mode 100644 index 00000000..215d3586 --- /dev/null +++ b/dependencies/discord-rpc/build.py @@ -0,0 +1,304 @@ +#!/usr/bin/env python + +import os +import subprocess +import sys +import shutil +import zipfile +from contextlib import contextmanager +import click + + +def get_platform(): + """ a name for the platform """ + if sys.platform.startswith('win'): + return 'win' + elif sys.platform == 'darwin': + return 'osx' + elif sys.platform.startswith('linux'): + return 'linux' + raise Exception('Unsupported platform ' + sys.platform) + + +SCRIPT_PATH = os.path.dirname(os.path.abspath(__file__)) +# we use Buildkite which sets this env variable by default +IS_BUILD_MACHINE = os.environ.get('CI', '') == 'true' +PLATFORM = get_platform() +INSTALL_ROOT = os.path.join(SCRIPT_PATH, 'builds', 'install') + + +def get_signtool(): + """ get path to code signing tool """ + if PLATFORM == 'win': + sdk_dir = 'c:\\Program Files (x86)\\Windows Kits\\10' # os.environ['WindowsSdkDir'] + return os.path.join(sdk_dir, 'bin', 'x86', 'signtool.exe') + elif PLATFORM == 'osx': + return '/usr/bin/codesign' + + +@contextmanager +def cd(new_dir): + """ Temporarily change current directory """ + if new_dir: + old_dir = os.getcwd() + os.chdir(new_dir) + yield + if new_dir: + os.chdir(old_dir) + + +def mkdir_p(path): + """ mkdir -p """ + if not os.path.isdir(path): + click.secho('Making ' + path, fg='yellow') + os.makedirs(path) + + +@click.group(invoke_without_command=True) +@click.pass_context +@click.option('--clean', is_flag=True) +def cli(ctx, clean): + """ click wrapper for command line stuff """ + if ctx.invoked_subcommand is None: + ctx.invoke(libs, clean=clean) + if IS_BUILD_MACHINE: + ctx.invoke(sign) + ctx.invoke(archive) + + +@cli.command() +@click.pass_context +def unity(ctx): + """ build just dynamic libs for use in unity project """ + ctx.invoke(libs, clean=False, static=False, shared=True, skip_formatter=True, just_release=True) + BUILDS = [] + + click.echo('--- Copying libs and header into unity example') + UNITY_PROJECT_PATH = os.path.join(SCRIPT_PATH, 'examples', 'button-clicker', 'Assets', 'Plugins') + + if sys.platform.startswith('win'): + LIBRARY_NAME = 'discord-rpc.dll' + BUILD_64_BASE_PATH = os.path.join(SCRIPT_PATH, 'builds', 'win64-dynamic', 'src', 'Release') + UNITY_64_DLL_PATH = os.path.join(UNITY_PROJECT_PATH, 'x86_64') + BUILDS.append({BUILD_64_BASE_PATH: UNITY_64_DLL_PATH}) + + BUILD_32_BASE_PATH = os.path.join(SCRIPT_PATH, 'builds', 'win32-dynamic', 'src', 'Release') + UNITY_32_DLL_PATH = os.path.join(UNITY_PROJECT_PATH, 'x86') + BUILDS.append({BUILD_32_BASE_PATH: UNITY_32_DLL_PATH}) + + elif sys.platform == 'darwin': + LIBRARY_NAME = 'discord-rpc.bundle' + BUILD_BASE_PATH = os.path.join(SCRIPT_PATH, 'builds', 'osx-dynamic', 'src') + UNITY_DLL_PATH = UNITY_PROJECT_PATH + os.rename( + os.path.join(BUILD_BASE_PATH, 'libdiscord-rpc.dylib'), os.path.join(BUILD_BASE_PATH, 'discord-rpc.bundle')) + + BUILDS.append({BUILD_BASE_PATH: UNITY_DLL_PATH}) + + elif sys.platform.startswith('linux'): + LIBRARY_NAME = 'discord-rpc.so' + BUILD_BASE_PATH = os.path.join(SCRIPT_PATH, 'builds', 'linux-dynamic', 'src') + UNITY_DLL_PATH = os.path.join(UNITY_PROJECT_PATH, 'x86') + os.rename(os.path.join(BUILD_BASE_PATH, 'libdiscord-rpc.so'), os.path.join(BUILD_BASE_PATH, 'discord-rpc.so')) + + BUILDS.append({BUILD_BASE_PATH: UNITY_DLL_PATH}) + + else: + raise Exception('Unsupported platform ' + sys.platform) + + for build in BUILDS: + for i in build: + mkdir_p(build[i]) + shutil.copy(os.path.join(i, LIBRARY_NAME), build[i]) + + +@cli.command() +@click.pass_context +def unreal(ctx): + """ build libs and copy them into the unreal project """ + ctx.invoke(libs, clean=False, static=False, shared=True, skip_formatter=True, just_release=True) + BUILDS = [] + + click.echo('--- Copying libs and header into unreal example') + UNREAL_PROJECT_PATH = os.path.join(SCRIPT_PATH, 'examples', 'unrealstatus', 'Plugins', 'discordrpc') + UNREAL_INCLUDE_PATH = os.path.join(UNREAL_PROJECT_PATH, 'Source', 'ThirdParty', 'DiscordRpcLibrary', 'Include') + mkdir_p(UNREAL_INCLUDE_PATH) + shutil.copy(os.path.join(SCRIPT_PATH, 'include', 'discord_rpc.h'), UNREAL_INCLUDE_PATH) + + if sys.platform.startswith('win'): + LIBRARY_NAME = 'discord-rpc.lib' + BUILD_64_BASE_PATH = os.path.join(SCRIPT_PATH, 'builds', 'win64-dynamic', 'src', 'Release') + UNREAL_64_DLL_PATH = os.path.join(UNREAL_PROJECT_PATH, 'Source', 'ThirdParty', 'DiscordRpcLibrary', 'Win64') + BUILDS.append({BUILD_64_BASE_PATH: UNREAL_64_DLL_PATH}) + + BUILD_32_BASE_PATH = os.path.join(SCRIPT_PATH, 'builds', 'win32-dynamic', 'src', 'Release') + UNREAL_32_DLL_PATH = os.path.join(UNREAL_PROJECT_PATH, 'Source', 'ThirdParty', 'DiscordRpcLibrary', 'Win32') + BUILDS.append({BUILD_32_BASE_PATH: UNREAL_32_DLL_PATH}) + + elif sys.platform == 'darwin': + LIBRARY_NAME = 'libdiscord-rpc.dylib' + BUILD_BASE_PATH = os.path.join(SCRIPT_PATH, 'builds', 'osx-dynamic', 'src') + UNREAL_DLL_PATH = os.path.join(UNREAL_PROJECT_PATH, 'Source', 'ThirdParty', 'DiscordRpcLibrary', 'Mac') + + BUILDS.append({BUILD_BASE_PATH: UNREAL_DLL_PATH}) + + elif sys.platform.startswith('linux'): + LIBRARY_NAME = 'libdiscord-rpc.so' + BUILD_BASE_PATH = os.path.join(SCRIPT_PATH, 'builds', 'linux-dynamic', 'src') + UNREAL_DLL_PATH = os.path.join(UNREAL_PROJECT_PATH, 'Source', 'ThirdParty', 'DiscordRpcLibrary', 'Linux') + + BUILDS.append({BUILD_BASE_PATH: UNREAL_DLL_PATH}) + + else: + raise Exception('Unsupported platform ' + sys.platform) + + for build in BUILDS: + for i in build: + mkdir_p(build[i]) + shutil.copy(os.path.join(i, LIBRARY_NAME), build[i]) + + +def build_lib(build_name, generator, options, just_release): + """ Create a dir under builds, run build and install in it """ + build_path = os.path.join(SCRIPT_PATH, 'builds', build_name) + install_path = os.path.join(INSTALL_ROOT, build_name) + mkdir_p(build_path) + mkdir_p(install_path) + with cd(build_path): + initial_cmake = ['cmake', SCRIPT_PATH, '-DCMAKE_INSTALL_PREFIX=%s' % os.path.join('..', 'install', build_name)] + if generator: + initial_cmake.extend(['-G', generator]) + for key in options: + val = options[key] + if type(val) is bool: + val = 'ON' if val else 'OFF' + initial_cmake.append('-D%s=%s' % (key, val)) + click.echo('--- Building ' + build_name) + subprocess.check_call(initial_cmake) + if not just_release: + subprocess.check_call(['cmake', '--build', '.', '--config', 'Debug']) + subprocess.check_call(['cmake', '--build', '.', '--config', 'Release', '--target', 'install']) + + +@cli.command() +def archive(): + """ create zip of install dir """ + click.echo('--- Archiving') + archive_file_path = os.path.join(SCRIPT_PATH, 'builds', 'discord-rpc-%s.zip' % get_platform()) + archive_file = zipfile.ZipFile(archive_file_path, 'w', zipfile.ZIP_DEFLATED) + archive_src_base_path = INSTALL_ROOT + archive_dst_base_path = 'discord-rpc' + with cd(archive_src_base_path): + for path, _, filenames in os.walk('.'): + for fname in filenames: + fpath = os.path.join(path, fname) + dst_path = os.path.normpath(os.path.join(archive_dst_base_path, fpath)) + click.echo('Adding ' + dst_path) + archive_file.write(fpath, dst_path) + + +@cli.command() +def sign(): + """ Do code signing within install directory using our cert """ + tool = get_signtool() + signable_extensions = set() + if PLATFORM == 'win': + signable_extensions.add('.dll') + sign_command_base = [ + tool, + 'sign', + '/n', + 'Discord Inc.', + '/a', + '/tr', + 'http://timestamp.digicert.com/rfc3161', + '/as', + '/td', + 'sha256', + '/fd', + 'sha256', + ] + elif PLATFORM == 'osx': + signable_extensions.add('.dylib') + sign_command_base = [ + tool, + '--keychain', + os.path.expanduser('~/Library/Keychains/login.keychain'), + '-vvvv', + '--deep', + '--force', + '--sign', + 'Developer ID Application: Hammer & Chisel Inc. (53Q6R32WPB)', + ] + else: + click.secho('Not signing things on this platform yet', fg='red') + return + + click.echo('--- Signing') + for path, _, filenames in os.walk(INSTALL_ROOT): + for fname in filenames: + ext = os.path.splitext(fname)[1] + if ext not in signable_extensions: + continue + fpath = os.path.join(path, fname) + click.echo('Sign ' + fpath) + sign_command = sign_command_base + [fpath] + subprocess.check_call(sign_command) + + +@cli.command() +@click.option('--clean', is_flag=True) +@click.option('--static', is_flag=True) +@click.option('--shared', is_flag=True) +@click.option('--skip_formatter', is_flag=True) +@click.option('--just_release', is_flag=True) +def libs(clean, static, shared, skip_formatter, just_release): + """ Do all the builds for this platform """ + if clean: + shutil.rmtree('builds', ignore_errors=True) + + mkdir_p('builds') + + if not (static or shared): + static = True + shared = True + + static_options = {} + dynamic_options = { + 'BUILD_SHARED_LIBS': True, + 'USE_STATIC_CRT': True, + } + + if skip_formatter or IS_BUILD_MACHINE: + static_options['CLANG_FORMAT_SUFFIX'] = 'none' + dynamic_options['CLANG_FORMAT_SUFFIX'] = 'none' + + if IS_BUILD_MACHINE: + just_release = True + static_options['WARNINGS_AS_ERRORS'] = True + dynamic_options['WARNINGS_AS_ERRORS'] = True + + if PLATFORM == 'win': + generator32 = 'Visual Studio 14 2015' + generator64 = 'Visual Studio 14 2015 Win64' + if static: + build_lib('win32-static', generator32, static_options, just_release) + build_lib('win64-static', generator64, static_options, just_release) + if shared: + build_lib('win32-dynamic', generator32, dynamic_options, just_release) + build_lib('win64-dynamic', generator64, dynamic_options, just_release) + elif PLATFORM == 'osx': + if static: + build_lib('osx-static', None, static_options, just_release) + if shared: + build_lib('osx-dynamic', None, dynamic_options, just_release) + elif PLATFORM == 'linux': + if static: + build_lib('linux-static', None, static_options, just_release) + if shared: + build_lib('linux-dynamic', None, dynamic_options, just_release) + + +if __name__ == '__main__': + os.chdir(SCRIPT_PATH) + sys.exit(cli()) diff --git a/dependencies/discord-rpc/documentation/hard-mode.md b/dependencies/discord-rpc/documentation/hard-mode.md new file mode 100644 index 00000000..35042cbc --- /dev/null +++ b/dependencies/discord-rpc/documentation/hard-mode.md @@ -0,0 +1,164 @@ +# Hard Mode: Roll Your Own Client + +Discord's Rich Presence feature is designed as an obfuscated addition to our existing [RPC infrastructure](https://discordapp.com/developers/docs/topics/rpc). The standalone library and header files make it easy for any dev to drop it into their game. + +Our library communicates with Discord over the local Discord RPC socket. We've already done the work in connecting properly, handling disconnects and reconnects, and other RPC intracacies, but those who have done this implementation for our private alpha Voice and Chat SDK can simply make use of the new RPC commands and events to implement Rich Presence. + +## Hark! A warning! + +By committing to an RPC-only integration, you decide to forego the work our library and header file have done for you in the way of error handling, state storage, disconnecting and reconnecting, and other quality of life abstractions. While simply implementing the new RPC command and events will enable Rich Presence for your game, we highly suggest that you do your best to mimic the functionality of the SDK the most that you can. It ensure not only code quality on your part, but also an excellent experience on the part of your players. + +## Application Protocol Registration + +One thing that cannot be explicitly done over RPC is registering an application protocol for your game. If you choose to do an RPC-only implementation, you will have to register your application protocol yourself in the format of `discord-[your_app_id]://`. You can use `Discord_Register()` as a good(?) example of how to properly register an application protocol for use with Discord. For OSX and Linux it is probably simpler to handle the protocol registration as part of your install/packaging. + +## New RPC Command + +The new RPC command for Rich Presence is `SET_ACTIVITY`. The fields are similar to what is outlined in the SDK; we've combined similar fields into objects for the sake of less data on the wire. + +The one major difference is the `party.size` field. It is an array with a size of two. The first element is the current party size, `partySize` from the main documentation. The second element is the maximum party size, `partyMax` from the main documentation. + +Below is a full example of a `SET_ACTIVITY` command. Field restrictions like size are the same as outlined in the main documentation. + +``` +{ + "cmd": "SET_ACTIVITY", + "args": { + "pid": 9999, // Your application's process id - required field + "activity": { + "state": "In a Group", + "details": "Competitive | In a Match", + "timestamps": { + "start": time(nullptr), + "end": time(nullptr) + ((60 * 5) + 23) + }, + "assets": { + "large_image": "numbani_map", + "large_text": "Numbani", + "small_image": "pharah_profile", + "small_text": "Pharah" + }, + "party": { + "id": GameEngine.GetPartyId(), + "size": [3, 6] + }, + "secrets": { + "join": "025ed05c71f639de8bfaa0d679d7c94b2fdce12f", + "spectate": "e7eb30d2ee025ed05c71ea495f770b76454ee4e0", + "match": "4b2fdce12f639de8bfa7e3591b71a0d679d7c93f" + }, + "instance": true + } + }, + "nonce": "647d814a-4cf8-4fbb-948f-898abd24f55b" +} +``` + +## New RPC Events + +The three new RPC events for Rich Presence power the ability to join and spectate your friends' games. + +First is the `ACTIVITY_JOIN` event: + +```json +{ + "cmd": "DISPATCH", + "data": { + "secret": "025ed05c71f639de8bfaa0d679d7c94b2fdce12f" + }, + "evt": "ACTIVITY_JOIN" +} +``` + +Second is the `ACTIVITY_SPECTATE` event: + +```json +{ + "cmd": "DISPATCH", + "data": { + "secret": "e7eb30d2ee025ed05c71ea495f770b76454ee4e0" + }, + "evt": "ACTIVITY_SPECTATE" +} +``` + +And third is the `ACTIVITY_JOIN_REQUEST` event: + +```json +{ + "cmd": "DISPATCH", + "data": { + "user": { + "id": "53908232506183680", + "username": "Mason", + "discriminator": "1337", + "avatar": "a_bab14f271d565501444b2ca3be944b25" + } + }, + "evt": "ACTIVITY_JOIN_REQUEST" +} +``` + +In order to receive these events, you need to [subscribe](https://discordapp.com/developers/docs/topics/rpc#subscribe) to them like so: + +```json +{ + "nonce": "be9a6de3-31d0-4767-a8e9-4818c5690015", + "evt": "ACTIVITY_JOIN", + "cmd": "SUBSCRIBE" +} +``` + +```json +{ + "nonce": "ae9qdde3-31d0-8989-a8e9-dnakwy174he", + "evt": "ACTIVITY_SPECTATE", + "cmd": "SUBSCRIBE" +} +``` + +```json +{ + "nonce": "5dc0c062-98c6-47a0-8922-bbb52e9d6afa", + "evt": "ACTIVITY_JOIN_REQUEST", + "cmd": "SUBSCRIBE" +} +``` + +To unsubscribe from these events, resend with the command `UNSUBSCRIBE` + +## Responding +A discord user will request access to the game. If the ACTIVITY_JOIN_REQUEST has been subscribed too, the ACTIVITY_JOIN_REQUEST event will be sent to the host's game. Accept it with following model: +```json +{ + "nonce": "5dc0c062-98c6-47a0-8922-15aerg126", + "cmd": "SEND_ACTIVITY_JOIN_INVITE", + "args": + { + "user_id": "53908232506183680" + } +} +``` + +To reject the request, use `CLOSE_ACTIVITY_REQUEST`: +```json +{ + "nonce": "5dc0c062-98c6-47a0-8922-dasg256eafg", + "cmd": "CLOSE_ACTIVITY_REQUEST", + "args": + { + "user_id": "53908232506183680" + } +} +``` + +## Notes +Here are just some quick notes to help with some common troubleshooting problems. +* IPC will echo back every command you send as a response. Use this as a lock-step feature to avoid flooding messages. Can be used to validate messages such as the Presence or Subscribes. +* The pipe expects for frames to be written in a single byte array. You cannot do multiple `stream.Write(opcode);` `stream.Write(length);` as it will break the pipe. Instead create a buffer, write the data to the buffer, then send the entire buffer to the stream. +* Discord can be on any pipe ranging from `discord-ipc-0` to `discord-ipc-9`. It is a good idea to try and connect to each one and keeping the first one you connect too. For multiple clients (eg Discord and Canary), you might want to add a feature to manually select the pipe so you can more easily debug the application. +* All enums are `lower_snake_case`. +* The opcode and length in the header are `Little Endian Unsigned Integers (32bits)`. In some languages, you must convert them as they can be architecture specific. +* [Discord Rich Presence How-To](https://discordapp.com/developers/docs/rich-presence/how-to) contains a lot of the information this document doesn't. For example, it will tell you about the response payload. +* In the documentation, DISCORD_REPLY_IGNORE is just implemented the same as DISCORD_REPLY_NO. +* You can test the Join / Spectate feature by enabling them in your profile and whitelisting a test account. Use Canary to run 2 accounts on the same machine. diff --git a/dependencies/discord-rpc/documentation/images/rp-dev-dashboard.png b/dependencies/discord-rpc/documentation/images/rp-dev-dashboard.png new file mode 100644 index 00000000..f246cfb2 Binary files /dev/null and b/dependencies/discord-rpc/documentation/images/rp-dev-dashboard.png differ diff --git a/dependencies/discord-rpc/documentation/images/rp-profile-view.png b/dependencies/discord-rpc/documentation/images/rp-profile-view.png new file mode 100644 index 00000000..f9ddc313 Binary files /dev/null and b/dependencies/discord-rpc/documentation/images/rp-profile-view.png differ diff --git a/dependencies/discord-rpc/documentation/images/rp-secret-example.png b/dependencies/discord-rpc/documentation/images/rp-secret-example.png new file mode 100644 index 00000000..4ff21b09 Binary files /dev/null and b/dependencies/discord-rpc/documentation/images/rp-secret-example.png differ diff --git a/dependencies/discord-rpc/examples/button-clicker/.gitignore b/dependencies/discord-rpc/examples/button-clicker/.gitignore new file mode 100644 index 00000000..2695047f --- /dev/null +++ b/dependencies/discord-rpc/examples/button-clicker/.gitignore @@ -0,0 +1,8 @@ +/Library/ +/Temp/ +/obj/ +/Assets/Plugins/ +/Assets/Plugins.meta +*.sln +*.csproj +*.userprefs diff --git a/dependencies/discord-rpc/examples/button-clicker/Assets/DiscordController.cs b/dependencies/discord-rpc/examples/button-clicker/Assets/DiscordController.cs new file mode 100644 index 00000000..7319d5fa --- /dev/null +++ b/dependencies/discord-rpc/examples/button-clicker/Assets/DiscordController.cs @@ -0,0 +1,125 @@ +using UnityEngine; + +[System.Serializable] +public class DiscordJoinEvent : UnityEngine.Events.UnityEvent { } + +[System.Serializable] +public class DiscordSpectateEvent : UnityEngine.Events.UnityEvent { } + +[System.Serializable] +public class DiscordJoinRequestEvent : UnityEngine.Events.UnityEvent { } + +public class DiscordController : MonoBehaviour +{ + public DiscordRpc.RichPresence presence = new DiscordRpc.RichPresence(); + public string applicationId; + public string optionalSteamId; + public int clickCounter; + public DiscordRpc.DiscordUser joinRequest; + public UnityEngine.Events.UnityEvent onConnect; + public UnityEngine.Events.UnityEvent onDisconnect; + public UnityEngine.Events.UnityEvent hasResponded; + public DiscordJoinEvent onJoin; + public DiscordJoinEvent onSpectate; + public DiscordJoinRequestEvent onJoinRequest; + + DiscordRpc.EventHandlers handlers; + + public void OnClick() + { + Debug.Log("Discord: on click!"); + clickCounter++; + + presence.details = string.Format("Button clicked {0} times", clickCounter); + presence.joinSecret = "aSecret"; + presence.partyId = "aPartyId"; + presence.partySize = 1; + presence.partyMax = 3; + presence.partyPrivacy = DiscordRpc.PartyPrivacy.Public; + + DiscordRpc.UpdatePresence(presence); + } + + public void RequestRespondYes() + { + Debug.Log("Discord: responding yes to Ask to Join request"); + DiscordRpc.Respond(joinRequest.userId, DiscordRpc.Reply.Yes); + hasResponded.Invoke(); + } + + public void RequestRespondNo() + { + Debug.Log("Discord: responding no to Ask to Join request"); + DiscordRpc.Respond(joinRequest.userId, DiscordRpc.Reply.No); + hasResponded.Invoke(); + } + + public void ReadyCallback(ref DiscordRpc.DiscordUser connectedUser) + { + Debug.Log(string.Format("Discord: connected to {0}#{1}: {2}", connectedUser.username, connectedUser.discriminator, connectedUser.userId)); + onConnect.Invoke(); + } + + public void DisconnectedCallback(int errorCode, string message) + { + Debug.Log(string.Format("Discord: disconnect {0}: {1}", errorCode, message)); + onDisconnect.Invoke(); + } + + public void ErrorCallback(int errorCode, string message) + { + Debug.Log(string.Format("Discord: error {0}: {1}", errorCode, message)); + } + + public void JoinCallback(string secret) + { + Debug.Log(string.Format("Discord: join ({0})", secret)); + onJoin.Invoke(secret); + } + + public void SpectateCallback(string secret) + { + Debug.Log(string.Format("Discord: spectate ({0})", secret)); + onSpectate.Invoke(secret); + } + + public void RequestCallback(ref DiscordRpc.DiscordUser request) + { + Debug.Log(string.Format("Discord: join request {0}#{1}: {2}", request.username, request.discriminator, request.userId)); + joinRequest = request; + onJoinRequest.Invoke(request); + } + + void Start() + { + } + + void Update() + { + DiscordRpc.RunCallbacks(); + } + + void OnEnable() + { + Debug.Log("Discord: init"); + handlers = new DiscordRpc.EventHandlers(); + handlers.readyCallback += ReadyCallback; + handlers.disconnectedCallback += DisconnectedCallback; + handlers.errorCallback += ErrorCallback; + handlers.joinCallback += JoinCallback; + handlers.spectateCallback += SpectateCallback; + handlers.requestCallback += RequestCallback; + DiscordRpc.Initialize(applicationId, ref handlers, true, optionalSteamId); + } + + void OnDisable() + { + Debug.Log("Discord: shutdown"); + DiscordRpc.Shutdown(); + } + + void OnDestroy() + { + + } +} diff --git a/dependencies/discord-rpc/examples/button-clicker/Assets/DiscordController.cs.meta b/dependencies/discord-rpc/examples/button-clicker/Assets/DiscordController.cs.meta new file mode 100644 index 00000000..ee24c7e4 --- /dev/null +++ b/dependencies/discord-rpc/examples/button-clicker/Assets/DiscordController.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 27f0a5f59ffffa84c86547736e2e730a +timeCreated: 1501697692 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/dependencies/discord-rpc/examples/button-clicker/Assets/DiscordRpc.cs b/dependencies/discord-rpc/examples/button-clicker/Assets/DiscordRpc.cs new file mode 100644 index 00000000..f3b1ee59 --- /dev/null +++ b/dependencies/discord-rpc/examples/button-clicker/Assets/DiscordRpc.cs @@ -0,0 +1,231 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; +using AOT; + +public class DiscordRpc +{ + [MonoPInvokeCallback(typeof(OnReadyInfo))] + public static void ReadyCallback(ref DiscordUser connectedUser) { Callbacks.readyCallback(ref connectedUser); } + public delegate void OnReadyInfo(ref DiscordUser connectedUser); + + [MonoPInvokeCallback(typeof(OnDisconnectedInfo))] + public static void DisconnectedCallback(int errorCode, string message) { Callbacks.disconnectedCallback(errorCode, message); } + public delegate void OnDisconnectedInfo(int errorCode, string message); + + [MonoPInvokeCallback(typeof(OnErrorInfo))] + public static void ErrorCallback(int errorCode, string message) { Callbacks.errorCallback(errorCode, message); } + public delegate void OnErrorInfo(int errorCode, string message); + + [MonoPInvokeCallback(typeof(OnJoinInfo))] + public static void JoinCallback(string secret) { Callbacks.joinCallback(secret); } + public delegate void OnJoinInfo(string secret); + + [MonoPInvokeCallback(typeof(OnSpectateInfo))] + public static void SpectateCallback(string secret) { Callbacks.spectateCallback(secret); } + public delegate void OnSpectateInfo(string secret); + + [MonoPInvokeCallback(typeof(OnRequestInfo))] + public static void RequestCallback(ref DiscordUser request) { Callbacks.requestCallback(ref request); } + public delegate void OnRequestInfo(ref DiscordUser request); + + static EventHandlers Callbacks { get; set; } + + public struct EventHandlers + { + public OnReadyInfo readyCallback; + public OnDisconnectedInfo disconnectedCallback; + public OnErrorInfo errorCallback; + public OnJoinInfo joinCallback; + public OnSpectateInfo spectateCallback; + public OnRequestInfo requestCallback; + } + + [Serializable, StructLayout(LayoutKind.Sequential)] + public struct RichPresenceStruct + { + public IntPtr state; /* max 128 bytes */ + public IntPtr details; /* max 128 bytes */ + public long startTimestamp; + public long endTimestamp; + public IntPtr largeImageKey; /* max 32 bytes */ + public IntPtr largeImageText; /* max 128 bytes */ + public IntPtr smallImageKey; /* max 32 bytes */ + public IntPtr smallImageText; /* max 128 bytes */ + public IntPtr partyId; /* max 128 bytes */ + public int partySize; + public int partyMax; + public int partyPrivacy; + public IntPtr matchSecret; /* max 128 bytes */ + public IntPtr joinSecret; /* max 128 bytes */ + public IntPtr spectateSecret; /* max 128 bytes */ + public bool instance; + } + + [Serializable] + public struct DiscordUser + { + public string userId; + public string username; + public string discriminator; + public string avatar; + } + + public enum Reply + { + No = 0, + Yes = 1, + Ignore = 2 + } + + public enum PartyPrivacy + { + Private = 0, + Public = 1 + } + + public static void Initialize(string applicationId, ref EventHandlers handlers, bool autoRegister, string optionalSteamId) + { + Callbacks = handlers; + + EventHandlers staticEventHandlers = new EventHandlers(); + staticEventHandlers.readyCallback += DiscordRpc.ReadyCallback; + staticEventHandlers.disconnectedCallback += DiscordRpc.DisconnectedCallback; + staticEventHandlers.errorCallback += DiscordRpc.ErrorCallback; + staticEventHandlers.joinCallback += DiscordRpc.JoinCallback; + staticEventHandlers.spectateCallback += DiscordRpc.SpectateCallback; + staticEventHandlers.requestCallback += DiscordRpc.RequestCallback; + + InitializeInternal(applicationId, ref staticEventHandlers, autoRegister, optionalSteamId); + } + + [DllImport("discord-rpc", EntryPoint = "Discord_Initialize", CallingConvention = CallingConvention.Cdecl)] + static extern void InitializeInternal(string applicationId, ref EventHandlers handlers, bool autoRegister, string optionalSteamId); + + [DllImport("discord-rpc", EntryPoint = "Discord_Shutdown", CallingConvention = CallingConvention.Cdecl)] + public static extern void Shutdown(); + + [DllImport("discord-rpc", EntryPoint = "Discord_RunCallbacks", CallingConvention = CallingConvention.Cdecl)] + public static extern void RunCallbacks(); + + [DllImport("discord-rpc", EntryPoint = "Discord_UpdatePresence", CallingConvention = CallingConvention.Cdecl)] + private static extern void UpdatePresenceNative(ref RichPresenceStruct presence); + + [DllImport("discord-rpc", EntryPoint = "Discord_ClearPresence", CallingConvention = CallingConvention.Cdecl)] + public static extern void ClearPresence(); + + [DllImport("discord-rpc", EntryPoint = "Discord_Respond", CallingConvention = CallingConvention.Cdecl)] + public static extern void Respond(string userId, Reply reply); + + [DllImport("discord-rpc", EntryPoint = "Discord_UpdateHandlers", CallingConvention = CallingConvention.Cdecl)] + public static extern void UpdateHandlers(ref EventHandlers handlers); + + public static void UpdatePresence(RichPresence presence) + { + var presencestruct = presence.GetStruct(); + UpdatePresenceNative(ref presencestruct); + presence.FreeMem(); + } + + public class RichPresence + { + private RichPresenceStruct _presence; + private readonly List _buffers = new List(10); + + public string state; /* max 128 bytes */ + public string details; /* max 128 bytes */ + public long startTimestamp; + public long endTimestamp; + public string largeImageKey; /* max 32 bytes */ + public string largeImageText; /* max 128 bytes */ + public string smallImageKey; /* max 32 bytes */ + public string smallImageText; /* max 128 bytes */ + public string partyId; /* max 128 bytes */ + public int partySize; + public int partyMax; + public PartyPrivacy partyPrivacy; + public string matchSecret; /* max 128 bytes */ + public string joinSecret; /* max 128 bytes */ + public string spectateSecret; /* max 128 bytes */ + public bool instance; + + /// + /// Get the reprensentation of this instance + /// + /// reprensentation of this instance + internal RichPresenceStruct GetStruct() + { + if (_buffers.Count > 0) + { + FreeMem(); + } + + _presence.state = StrToPtr(state); + _presence.details = StrToPtr(details); + _presence.startTimestamp = startTimestamp; + _presence.endTimestamp = endTimestamp; + _presence.largeImageKey = StrToPtr(largeImageKey); + _presence.largeImageText = StrToPtr(largeImageText); + _presence.smallImageKey = StrToPtr(smallImageKey); + _presence.smallImageText = StrToPtr(smallImageText); + _presence.partyId = StrToPtr(partyId); + _presence.partySize = partySize; + _presence.partyMax = partyMax; + _presence.partyPrivacy = (int)partyPrivacy; + _presence.matchSecret = StrToPtr(matchSecret); + _presence.joinSecret = StrToPtr(joinSecret); + _presence.spectateSecret = StrToPtr(spectateSecret); + _presence.instance = instance; + + return _presence; + } + + /// + /// Returns a pointer to a representation of the given string with a size of maxbytes + /// + /// String to convert + /// Pointer to the UTF-8 representation of + private IntPtr StrToPtr(string input) + { + if (string.IsNullOrEmpty(input)) return IntPtr.Zero; + var convbytecnt = Encoding.UTF8.GetByteCount(input); + var buffer = Marshal.AllocHGlobal(convbytecnt + 1); + for (int i = 0; i < convbytecnt + 1; i++) + { + Marshal.WriteByte(buffer, i, 0); + } + _buffers.Add(buffer); + Marshal.Copy(Encoding.UTF8.GetBytes(input), 0, buffer, convbytecnt); + return buffer; + } + + /// + /// Convert string to UTF-8 and add null termination + /// + /// string to convert + /// UTF-8 representation of with added null termination + private static string StrToUtf8NullTerm(string toconv) + { + var str = toconv.Trim(); + var bytes = Encoding.Default.GetBytes(str); + if (bytes.Length > 0 && bytes[bytes.Length - 1] != 0) + { + str += "\0\0"; + } + return Encoding.UTF8.GetString(Encoding.UTF8.GetBytes(str)); + } + + /// + /// Free the allocated memory for conversion to + /// + internal void FreeMem() + { + for (var i = _buffers.Count - 1; i >= 0; i--) + { + Marshal.FreeHGlobal(_buffers[i]); + _buffers.RemoveAt(i); + } + } + } +} \ No newline at end of file diff --git a/dependencies/discord-rpc/examples/button-clicker/Assets/DiscordRpc.cs.meta b/dependencies/discord-rpc/examples/button-clicker/Assets/DiscordRpc.cs.meta new file mode 100644 index 00000000..acd04b10 --- /dev/null +++ b/dependencies/discord-rpc/examples/button-clicker/Assets/DiscordRpc.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: b4474a677de9d80409e98c5393ec5b1e +timeCreated: 1501697692 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/dependencies/discord-rpc/examples/button-clicker/Assets/Editor/BuildHelper.cs b/dependencies/discord-rpc/examples/button-clicker/Assets/Editor/BuildHelper.cs new file mode 100644 index 00000000..97774531 --- /dev/null +++ b/dependencies/discord-rpc/examples/button-clicker/Assets/Editor/BuildHelper.cs @@ -0,0 +1,101 @@ +using UnityEditor; +using System.Diagnostics; +using System.IO; + +[InitializeOnLoad] +public class ScriptBatch +{ + static ScriptBatch() + { + EnsureDLL(); + } + + public static bool FileExists(string filename) + { + return new FileInfo(filename).Exists; + } + + public static bool RunRpcBuildScript() + { + UnityEngine.Debug.Log("Try to run build script"); + + Process proc = new Process(); +#if UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX + proc.StartInfo.UseShellExecute = false; + // brew installs cmake in /usr/local/bin, which Unity seems to strip from PATH? + string newPath = proc.StartInfo.EnvironmentVariables["PATH"] + ":/usr/local/bin"; + proc.StartInfo.EnvironmentVariables["PATH"] = newPath; +#endif + proc.StartInfo.FileName = "python"; + proc.StartInfo.Arguments = "build.py unity"; + proc.StartInfo.WorkingDirectory = "../.."; + proc.Start(); + proc.WaitForExit(); + return proc.ExitCode == 0; + } + + public static void EnsureDLL() + { + #if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN + string[] dstDirs = { "Assets/Plugins", "Assets/Plugins/x86", "Assets/Plugins/x86_64" }; + string[] dstDlls = { "Assets/Plugins/x86/discord-rpc.dll", "Assets/Plugins/x86_64/discord-rpc.dll" }; + string[] srcDlls = { "../../builds/install/win64-dynamic/bin/discord-rpc.dll", "../../builds/install/win64-dynamic/bin/discord-rpc.dll" }; + #elif UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX + string[] dstDirs = { "Assets/Plugins" }; + string[] dstDlls = { "Assets/Plugins/discord-rpc.bundle" }; + string[] srcDlls = { "../../builds/install/osx-dynamic/lib/libdiscord-rpc.dylib" }; + #else + string[] dstDirs = { "Assets/Plugins", "Assets/Plugins/x86", "Assets/Plugins/x86_64" }; + string[] dstDlls = { "Assets/Plugins/discord-rpc.so" }; + string[] srcDlls = { "../../builds/install/linux-dynamic/lib/libdiscord-rpc.so" }; + #endif + + Debug.Assert(dstDlls.Length == srcDlls.Length); + + bool exists = true; + foreach (string fname in dstDlls) + { + if (!FileExists(fname)) + { + exists = false; + break; + } + } + + if (exists) + { + return; + } + + exists = true; + foreach (string fname in srcDlls) + { + if (!FileExists(fname)) + { + exists = false; + break; + } + } + + if (!exists) + { + if (!RunRpcBuildScript()) + { + UnityEngine.Debug.LogError("Build failed"); + return; + } + } + + // make sure the dirs exist + foreach (string dirname in dstDirs) + { + Directory.CreateDirectory(dirname); + } + + // Copy dlls + for (int i = 0; i < dstDlls.Length; ++i) + { + FileUtil.CopyFileOrDirectory(srcDlls[i], dstDlls[i]); + } + } +} \ No newline at end of file diff --git a/dependencies/discord-rpc/examples/button-clicker/Assets/Editor/BuildHelper.cs.meta b/dependencies/discord-rpc/examples/button-clicker/Assets/Editor/BuildHelper.cs.meta new file mode 100644 index 00000000..c14e1e8b --- /dev/null +++ b/dependencies/discord-rpc/examples/button-clicker/Assets/Editor/BuildHelper.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: e5aecc4633e5f594b85eaa39f49bb402 +timeCreated: 1512071254 +licenseType: Free +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/dependencies/discord-rpc/examples/button-clicker/Assets/main.unity b/dependencies/discord-rpc/examples/button-clicker/Assets/main.unity new file mode 100644 index 00000000..c5074d55 --- /dev/null +++ b/dependencies/discord-rpc/examples/button-clicker/Assets/main.unity @@ -0,0 +1,1278 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 8 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 3 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 0} + m_IndirectSpecularColor: {r: 0, g: 0, b: 0, a: 1} +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 11 + m_GIWorkflowMode: 1 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_TemporalCoherenceThreshold: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 0 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 9 + m_Resolution: 2 + m_BakeResolution: 40 + m_TextureWidth: 1024 + m_TextureHeight: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_FinalGather: 0 + m_FinalGatherFiltering: 1 + m_FinalGatherRayCount: 256 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 0 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 500 + m_PVRBounces: 2 + m_PVRFiltering: 0 + m_PVRFilteringMode: 1 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousColorSigma: 1 + m_PVRFilteringAtrousNormalSigma: 1 + m_PVRFilteringAtrousPositionSigma: 1 + m_LightingDataAsset: {fileID: 0} + m_UseShadowmask: 1 +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 2 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + accuratePlacement: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &134146651 +GameObject: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + serializedVersion: 5 + m_Component: + - component: {fileID: 134146656} + - component: {fileID: 134146655} + - component: {fileID: 134146654} + - component: {fileID: 134146653} + - component: {fileID: 134146652} + m_Layer: 0 + m_Name: Main Camera + m_TagString: MainCamera + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!81 &134146652 +AudioListener: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 134146651} + m_Enabled: 1 +--- !u!124 &134146653 +Behaviour: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 134146651} + m_Enabled: 1 +--- !u!92 &134146654 +Behaviour: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 134146651} + m_Enabled: 1 +--- !u!20 &134146655 +Camera: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 134146651} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 1 + m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} + m_NormalizedViewPortRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + near clip plane: 0.3 + far clip plane: 1000 + field of view: 60 + orthographic: 1 + orthographic size: 5 + m_Depth: -1 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 1 + m_AllowMSAA: 1 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 + m_StereoMirrorMode: 0 +--- !u!4 &134146656 +Transform: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 134146651} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: -10} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &359174702 +GameObject: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + serializedVersion: 5 + m_Component: + - component: {fileID: 359174703} + - component: {fileID: 359174705} + - component: {fileID: 359174704} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &359174703 +RectTransform: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 359174702} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 1032248339} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &359174704 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 359174702} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 708705254, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI, + Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 14 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 10 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: Button +--- !u!222 &359174705 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 359174702} +--- !u!1 &520806049 +GameObject: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + serializedVersion: 5 + m_Component: + - component: {fileID: 520806050} + - component: {fileID: 520806052} + - component: {fileID: 520806051} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &520806050 +RectTransform: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 520806049} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 806911717} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &520806051 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 520806049} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 708705254, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI, + Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 14 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 10 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: Yes +--- !u!222 &520806052 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 520806049} +--- !u!1 &657463235 +GameObject: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + serializedVersion: 5 + m_Component: + - component: {fileID: 657463238} + - component: {fileID: 657463237} + - component: {fileID: 657463236} + m_Layer: 5 + m_Name: IsConnectedLabel + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &657463236 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 657463235} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 708705254, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.89518255, g: 0.9338235, b: 0.23345588, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI, + Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 24 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 2 + m_MaxSize: 40 + m_Alignment: 0 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: Discord Disconnected +--- !u!222 &657463237 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 657463235} +--- !u!224 &657463238 +RectTransform: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 657463235} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 1766020814} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 1} + m_AnchorMax: {x: 0, y: 1} + m_AnchoredPosition: {x: 16, y: -19.00003} + m_SizeDelta: {x: 239.20001, y: 37.799988} + m_Pivot: {x: 0, y: 1} +--- !u!1 &806911716 +GameObject: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + serializedVersion: 5 + m_Component: + - component: {fileID: 806911717} + - component: {fileID: 806911720} + - component: {fileID: 806911719} + - component: {fileID: 806911718} + m_Layer: 5 + m_Name: ButtonRespondYes + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &806911717 +RectTransform: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 806911716} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: + - {fileID: 520806050} + m_Father: {fileID: 1766020814} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: -129.1, y: -116.3} + m_SizeDelta: {x: 160, y: 30} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &806911718 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 806911716} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 1392445389, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_DisabledTrigger: Disabled + m_Interactable: 0 + m_TargetGraphic: {fileID: 806911719} + m_OnClick: + m_PersistentCalls: + m_Calls: + - m_Target: {fileID: 1929635629} + m_MethodName: RequestRespondYes + m_Mode: 1 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: + m_BoolArgument: 0 + m_CallState: 2 + m_TypeName: UnityEngine.UI.Button+ButtonClickedEvent, UnityEngine.UI, Version=1.0.0.0, + Culture=neutral, PublicKeyToken=null +--- !u!114 &806911719 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 806911716} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: -765806418, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI, + Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 +--- !u!222 &806911720 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 806911716} +--- !u!1 &1032248338 +GameObject: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + serializedVersion: 5 + m_Component: + - component: {fileID: 1032248339} + - component: {fileID: 1032248342} + - component: {fileID: 1032248341} + - component: {fileID: 1032248340} + m_Layer: 5 + m_Name: Button + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1032248339 +RectTransform: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1032248338} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: + - {fileID: 359174703} + m_Father: {fileID: 1766020814} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 160, y: 30} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1032248340 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1032248338} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 1392445389, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 1032248341} + m_OnClick: + m_PersistentCalls: + m_Calls: + - m_Target: {fileID: 1929635629} + m_MethodName: OnClick + m_Mode: 1 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: + m_BoolArgument: 0 + m_CallState: 2 + m_TypeName: UnityEngine.UI.Button+ButtonClickedEvent, UnityEngine.UI, Version=1.0.0.0, + Culture=neutral, PublicKeyToken=null +--- !u!114 &1032248341 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1032248338} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: -765806418, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI, + Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 +--- !u!222 &1032248342 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1032248338} +--- !u!1 &1238162986 +GameObject: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + serializedVersion: 5 + m_Component: + - component: {fileID: 1238162987} + - component: {fileID: 1238162989} + - component: {fileID: 1238162988} + m_Layer: 5 + m_Name: JoinRequestInfo + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1238162987 +RectTransform: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1238162986} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 1766020814} + m_RootOrder: 4 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: -0.0000085831, y: -66.9} + m_SizeDelta: {x: 323.38, y: 55.29} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1238162988 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1238162986} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 708705254, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 0.88965523, b: 0, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI, + Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 14 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 10 + m_MaxSize: 40 + m_Alignment: 1 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: No requests yet +--- !u!222 &1238162989 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1238162986} +--- !u!1 &1470895131 +GameObject: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + serializedVersion: 5 + m_Component: + - component: {fileID: 1470895134} + - component: {fileID: 1470895133} + - component: {fileID: 1470895132} + m_Layer: 0 + m_Name: EventSystem + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1470895132 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1470895131} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 1077351063, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3} + m_Name: + m_EditorClassIdentifier: + m_HorizontalAxis: Horizontal + m_VerticalAxis: Vertical + m_SubmitButton: Submit + m_CancelButton: Cancel + m_InputActionsPerSecond: 10 + m_RepeatDelay: 0.5 + m_ForceModuleActive: 0 +--- !u!114 &1470895133 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1470895131} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: -619905303, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3} + m_Name: + m_EditorClassIdentifier: + m_FirstSelected: {fileID: 0} + m_sendNavigationEvents: 1 + m_DragThreshold: 5 +--- !u!4 &1470895134 +Transform: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1470895131} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1766020810 +GameObject: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + serializedVersion: 5 + m_Component: + - component: {fileID: 1766020814} + - component: {fileID: 1766020813} + - component: {fileID: 1766020812} + - component: {fileID: 1766020811} + m_Layer: 5 + m_Name: Canvas + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1766020811 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1766020810} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 1301386320, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3} + m_Name: + m_EditorClassIdentifier: + m_IgnoreReversedGraphics: 1 + m_BlockingObjects: 0 + m_BlockingMask: + serializedVersion: 2 + m_Bits: 4294967295 +--- !u!114 &1766020812 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1766020810} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 1980459831, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3} + m_Name: + m_EditorClassIdentifier: + m_UiScaleMode: 0 + m_ReferencePixelsPerUnit: 100 + m_ScaleFactor: 1 + m_ReferenceResolution: {x: 800, y: 600} + m_ScreenMatchMode: 0 + m_MatchWidthOrHeight: 0 + m_PhysicalUnit: 3 + m_FallbackScreenDPI: 96 + m_DefaultSpriteDPI: 96 + m_DynamicPixelsPerUnit: 1 +--- !u!223 &1766020813 +Canvas: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1766020810} + m_Enabled: 1 + serializedVersion: 3 + m_RenderMode: 0 + m_Camera: {fileID: 0} + m_PlaneDistance: 100 + m_PixelPerfect: 0 + m_ReceivesEvents: 1 + m_OverrideSorting: 0 + m_OverridePixelPerfect: 0 + m_SortingBucketNormalizedSize: 0 + m_AdditionalShaderChannelsFlag: 0 + m_SortingLayerID: 0 + m_SortingOrder: 0 + m_TargetDisplay: 0 +--- !u!224 &1766020814 +RectTransform: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1766020810} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 0, y: 0, z: 0} + m_Children: + - {fileID: 1032248339} + - {fileID: 657463238} + - {fileID: 806911717} + - {fileID: 1858885002} + - {fileID: 1238162987} + m_Father: {fileID: 0} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0, y: 0} +--- !u!1 &1858885001 +GameObject: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + serializedVersion: 5 + m_Component: + - component: {fileID: 1858885002} + - component: {fileID: 1858885005} + - component: {fileID: 1858885004} + - component: {fileID: 1858885003} + m_Layer: 5 + m_Name: ButtonRespondNo + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1858885002 +RectTransform: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1858885001} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: + - {fileID: 1958982062} + m_Father: {fileID: 1766020814} + m_RootOrder: 3 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 128.7, y: -116.3} + m_SizeDelta: {x: 160, y: 30} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1858885003 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1858885001} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 1392445389, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_DisabledTrigger: Disabled + m_Interactable: 0 + m_TargetGraphic: {fileID: 1858885004} + m_OnClick: + m_PersistentCalls: + m_Calls: + - m_Target: {fileID: 1929635629} + m_MethodName: RequestRespondNo + m_Mode: 1 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: + m_BoolArgument: 0 + m_CallState: 2 + m_TypeName: UnityEngine.UI.Button+ButtonClickedEvent, UnityEngine.UI, Version=1.0.0.0, + Culture=neutral, PublicKeyToken=null +--- !u!114 &1858885004 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1858885001} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: -765806418, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI, + Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 +--- !u!222 &1858885005 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1858885001} +--- !u!1 &1929635628 +GameObject: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + serializedVersion: 5 + m_Component: + - component: {fileID: 1929635630} + - component: {fileID: 1929635629} + m_Layer: 0 + m_Name: Discord + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1929635629 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1929635628} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 27f0a5f59ffffa84c86547736e2e730a, type: 3} + m_Name: + m_EditorClassIdentifier: + presence: + state: Button clicking + details: Best game ever + startTimestamp: 0 + endTimestamp: 0 + largeImageKey: stable-large + largeImageText: + smallImageKey: canary-small + smallImageText: + partyId: abcdefg + partySize: 1 + partyMax: 10 + matchSecret: match_secret + joinSecret: join_secret + spectateSecret: spectate_secret + instance: 1 + applicationId: 345229890980937739 + optionalSteamId: + callbackCalls: 0 + clickCounter: 0 + joinRequest: + userId: + username: + discriminator: + avatar: + onConnect: + m_PersistentCalls: + m_Calls: + - m_Target: {fileID: 657463236} + m_MethodName: set_text + m_Mode: 5 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: Discord Connected + m_BoolArgument: 1 + m_CallState: 2 + m_TypeName: UnityEngine.Events.UnityEvent, UnityEngine, Version=0.0.0.0, Culture=neutral, + PublicKeyToken=null + onDisconnect: + m_PersistentCalls: + m_Calls: + - m_Target: {fileID: 657463236} + m_MethodName: set_text + m_Mode: 5 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: Discord Disconnected + m_BoolArgument: 0 + m_CallState: 2 + m_TypeName: UnityEngine.Events.UnityEvent, UnityEngine, Version=0.0.0.0, Culture=neutral, + PublicKeyToken=null + hasResponded: + m_PersistentCalls: + m_Calls: + - m_Target: {fileID: 1238162988} + m_MethodName: set_text + m_Mode: 5 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: No requests yet + m_BoolArgument: 0 + m_CallState: 2 + - m_Target: {fileID: 806911718} + m_MethodName: set_interactable + m_Mode: 6 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: + m_BoolArgument: 0 + m_CallState: 2 + - m_Target: {fileID: 1858885003} + m_MethodName: set_interactable + m_Mode: 6 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: + m_BoolArgument: 0 + m_CallState: 2 + m_TypeName: UnityEngine.Events.UnityEvent, UnityEngine, Version=0.0.0.0, Culture=neutral, + PublicKeyToken=null + onJoin: + m_PersistentCalls: + m_Calls: [] + m_TypeName: DiscordJoinEvent, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, + PublicKeyToken=null + onSpectate: + m_PersistentCalls: + m_Calls: [] + m_TypeName: DiscordJoinEvent, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, + PublicKeyToken=null + onJoinRequest: + m_PersistentCalls: + m_Calls: + - m_Target: {fileID: 1238162988} + m_MethodName: set_text + m_Mode: 5 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: Someone asked to join! + m_BoolArgument: 0 + m_CallState: 2 + - m_Target: {fileID: 806911718} + m_MethodName: set_interactable + m_Mode: 6 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: + m_BoolArgument: 1 + m_CallState: 2 + - m_Target: {fileID: 1858885003} + m_MethodName: set_interactable + m_Mode: 6 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: + m_BoolArgument: 1 + m_CallState: 2 + m_TypeName: DiscordJoinRequestEvent, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, + PublicKeyToken=null +--- !u!4 &1929635630 +Transform: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1929635628} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 3 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1958982061 +GameObject: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + serializedVersion: 5 + m_Component: + - component: {fileID: 1958982062} + - component: {fileID: 1958982064} + - component: {fileID: 1958982063} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1958982062 +RectTransform: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1958982061} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 1858885002} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1958982063 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1958982061} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 708705254, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI, + Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 14 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 10 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: No +--- !u!222 &1958982064 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1958982061} diff --git a/dependencies/discord-rpc/examples/button-clicker/Assets/main.unity.meta b/dependencies/discord-rpc/examples/button-clicker/Assets/main.unity.meta new file mode 100644 index 00000000..75654f19 --- /dev/null +++ b/dependencies/discord-rpc/examples/button-clicker/Assets/main.unity.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3b03d21bb25fa494e8694cd6e4b6d769 +timeCreated: 1501696924 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/AudioManager.asset b/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/AudioManager.asset new file mode 100644 index 00000000..da611257 --- /dev/null +++ b/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/AudioManager.asset @@ -0,0 +1,17 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!11 &1 +AudioManager: + m_ObjectHideFlags: 0 + m_Volume: 1 + Rolloff Scale: 1 + Doppler Factor: 1 + Default Speaker Mode: 2 + m_SampleRate: 0 + m_DSPBufferSize: 0 + m_VirtualVoiceCount: 512 + m_RealVoiceCount: 32 + m_SpatializerPlugin: + m_AmbisonicDecoderPlugin: + m_DisableAudio: 0 + m_VirtualizeEffects: 1 diff --git a/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/ClusterInputManager.asset b/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/ClusterInputManager.asset new file mode 100644 index 00000000..e7886b26 --- /dev/null +++ b/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/ClusterInputManager.asset @@ -0,0 +1,6 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!236 &1 +ClusterInputManager: + m_ObjectHideFlags: 0 + m_Inputs: [] diff --git a/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/DynamicsManager.asset b/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/DynamicsManager.asset new file mode 100644 index 00000000..0be3d787 --- /dev/null +++ b/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/DynamicsManager.asset @@ -0,0 +1,20 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!55 &1 +PhysicsManager: + m_ObjectHideFlags: 0 + serializedVersion: 3 + m_Gravity: {x: 0, y: -9.81, z: 0} + m_DefaultMaterial: {fileID: 0} + m_BounceThreshold: 2 + m_SleepThreshold: 0.005 + m_DefaultContactOffset: 0.01 + m_DefaultSolverIterations: 6 + m_DefaultSolverVelocityIterations: 1 + m_QueriesHitBackfaces: 0 + m_QueriesHitTriggers: 1 + m_EnableAdaptiveForce: 0 + m_EnablePCM: 1 + m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + m_AutoSimulation: 1 + m_AutoSyncTransforms: 1 diff --git a/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/EditorBuildSettings.asset b/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/EditorBuildSettings.asset new file mode 100644 index 00000000..d8fda848 --- /dev/null +++ b/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/EditorBuildSettings.asset @@ -0,0 +1,10 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1045 &1 +EditorBuildSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Scenes: + - enabled: 1 + path: Assets/main.unity + guid: 3b03d21bb25fa494e8694cd6e4b6d769 diff --git a/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/EditorSettings.asset b/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/EditorSettings.asset new file mode 100644 index 00000000..c0c814fd --- /dev/null +++ b/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/EditorSettings.asset @@ -0,0 +1,16 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!159 &1 +EditorSettings: + m_ObjectHideFlags: 0 + serializedVersion: 4 + m_ExternalVersionControlSupport: Visible Meta Files + m_SerializationMode: 2 + m_DefaultBehaviorMode: 1 + m_SpritePackerMode: 4 + m_SpritePackerPaddingPower: 1 + m_ProjectGenerationIncludedExtensions: txt;xml;fnt;cd + m_ProjectGenerationRootNamespace: + m_UserGeneratedProjectSuffix: + m_CollabEditorSettings: + inProgressEnabled: 1 diff --git a/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/GraphicsSettings.asset b/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/GraphicsSettings.asset new file mode 100644 index 00000000..74d7b532 --- /dev/null +++ b/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/GraphicsSettings.asset @@ -0,0 +1,61 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!30 &1 +GraphicsSettings: + m_ObjectHideFlags: 0 + serializedVersion: 12 + m_Deferred: + m_Mode: 1 + m_Shader: {fileID: 69, guid: 0000000000000000f000000000000000, type: 0} + m_DeferredReflections: + m_Mode: 1 + m_Shader: {fileID: 74, guid: 0000000000000000f000000000000000, type: 0} + m_ScreenSpaceShadows: + m_Mode: 1 + m_Shader: {fileID: 64, guid: 0000000000000000f000000000000000, type: 0} + m_LegacyDeferred: + m_Mode: 1 + m_Shader: {fileID: 63, guid: 0000000000000000f000000000000000, type: 0} + m_DepthNormals: + m_Mode: 1 + m_Shader: {fileID: 62, guid: 0000000000000000f000000000000000, type: 0} + m_MotionVectors: + m_Mode: 1 + m_Shader: {fileID: 75, guid: 0000000000000000f000000000000000, type: 0} + m_LightHalo: + m_Mode: 1 + m_Shader: {fileID: 105, guid: 0000000000000000f000000000000000, type: 0} + m_LensFlare: + m_Mode: 1 + m_Shader: {fileID: 102, guid: 0000000000000000f000000000000000, type: 0} + m_AlwaysIncludedShaders: + - {fileID: 7, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 15104, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 15105, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 15106, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 10770, guid: 0000000000000000f000000000000000, type: 0} + m_PreloadedShaders: [] + m_SpritesDefaultMaterial: {fileID: 10754, guid: 0000000000000000f000000000000000, + type: 0} + m_CustomRenderPipeline: {fileID: 0} + m_TransparencySortMode: 0 + m_TransparencySortAxis: {x: 0, y: 0, z: 1} + m_DefaultRenderingPath: 1 + m_DefaultMobileRenderingPath: 1 + m_TierSettings: [] + m_LightmapStripping: 0 + m_FogStripping: 0 + m_InstancingStripping: 0 + m_LightmapKeepPlain: 1 + m_LightmapKeepDirCombined: 1 + m_LightmapKeepDynamicPlain: 1 + m_LightmapKeepDynamicDirCombined: 1 + m_LightmapKeepShadowMask: 1 + m_LightmapKeepSubtractive: 1 + m_FogKeepLinear: 1 + m_FogKeepExp: 1 + m_FogKeepExp2: 1 + m_AlbedoSwatchInfos: [] + m_LightsUseLinearIntensity: 0 + m_LightsUseColorTemperature: 0 diff --git a/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/InputManager.asset b/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/InputManager.asset new file mode 100644 index 00000000..17c8f538 --- /dev/null +++ b/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/InputManager.asset @@ -0,0 +1,295 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!13 &1 +InputManager: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Axes: + - serializedVersion: 3 + m_Name: Horizontal + descriptiveName: + descriptiveNegativeName: + negativeButton: left + positiveButton: right + altNegativeButton: a + altPositiveButton: d + gravity: 3 + dead: 0.001 + sensitivity: 3 + snap: 1 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Vertical + descriptiveName: + descriptiveNegativeName: + negativeButton: down + positiveButton: up + altNegativeButton: s + altPositiveButton: w + gravity: 3 + dead: 0.001 + sensitivity: 3 + snap: 1 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire1 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: left ctrl + altNegativeButton: + altPositiveButton: mouse 0 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire2 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: left alt + altNegativeButton: + altPositiveButton: mouse 1 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire3 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: left shift + altNegativeButton: + altPositiveButton: mouse 2 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Jump + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: space + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Mouse X + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0 + sensitivity: 0.1 + snap: 0 + invert: 0 + type: 1 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Mouse Y + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0 + sensitivity: 0.1 + snap: 0 + invert: 0 + type: 1 + axis: 1 + joyNum: 0 + - serializedVersion: 3 + m_Name: Mouse ScrollWheel + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0 + sensitivity: 0.1 + snap: 0 + invert: 0 + type: 1 + axis: 2 + joyNum: 0 + - serializedVersion: 3 + m_Name: Horizontal + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0.19 + sensitivity: 1 + snap: 0 + invert: 0 + type: 2 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Vertical + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: + altNegativeButton: + altPositiveButton: + gravity: 0 + dead: 0.19 + sensitivity: 1 + snap: 0 + invert: 1 + type: 2 + axis: 1 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire1 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: joystick button 0 + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire2 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: joystick button 1 + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Fire3 + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: joystick button 2 + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Jump + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: joystick button 3 + altNegativeButton: + altPositiveButton: + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Submit + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: return + altNegativeButton: + altPositiveButton: joystick button 0 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Submit + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: enter + altNegativeButton: + altPositiveButton: space + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 + - serializedVersion: 3 + m_Name: Cancel + descriptiveName: + descriptiveNegativeName: + negativeButton: + positiveButton: escape + altNegativeButton: + altPositiveButton: joystick button 1 + gravity: 1000 + dead: 0.001 + sensitivity: 1000 + snap: 0 + invert: 0 + type: 0 + axis: 0 + joyNum: 0 diff --git a/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/NavMeshAreas.asset b/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/NavMeshAreas.asset new file mode 100644 index 00000000..6dd520f6 --- /dev/null +++ b/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/NavMeshAreas.asset @@ -0,0 +1,89 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!126 &1 +NavMeshProjectSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + areas: + - name: Walkable + cost: 1 + - name: Not Walkable + cost: 1 + - name: Jump + cost: 2 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + - name: + cost: 1 + m_LastAgentTypeID: -887442657 + m_Settings: + - serializedVersion: 2 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.75 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + accuratePlacement: 0 + m_SettingNames: + - Humanoid diff --git a/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/NetworkManager.asset b/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/NetworkManager.asset new file mode 100644 index 00000000..5dc6a831 --- /dev/null +++ b/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/NetworkManager.asset @@ -0,0 +1,8 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!149 &1 +NetworkManager: + m_ObjectHideFlags: 0 + m_DebugLevel: 0 + m_Sendrate: 15 + m_AssetToPrefab: {} diff --git a/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/Physics2DSettings.asset b/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/Physics2DSettings.asset new file mode 100644 index 00000000..132ee6bc --- /dev/null +++ b/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/Physics2DSettings.asset @@ -0,0 +1,37 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!19 &1 +Physics2DSettings: + m_ObjectHideFlags: 0 + serializedVersion: 3 + m_Gravity: {x: 0, y: -9.81} + m_DefaultMaterial: {fileID: 0} + m_VelocityIterations: 8 + m_PositionIterations: 3 + m_VelocityThreshold: 1 + m_MaxLinearCorrection: 0.2 + m_MaxAngularCorrection: 8 + m_MaxTranslationSpeed: 100 + m_MaxRotationSpeed: 360 + m_BaumgarteScale: 0.2 + m_BaumgarteTimeOfImpactScale: 0.75 + m_TimeToSleep: 0.5 + m_LinearSleepTolerance: 0.01 + m_AngularSleepTolerance: 2 + m_DefaultContactOffset: 0.01 + m_AutoSimulation: 1 + m_QueriesHitTriggers: 1 + m_QueriesStartInColliders: 1 + m_ChangeStopsCallbacks: 0 + m_CallbacksOnDisable: 1 + m_AutoSyncTransforms: 1 + m_AlwaysShowColliders: 0 + m_ShowColliderSleep: 1 + m_ShowColliderContacts: 0 + m_ShowColliderAABB: 0 + m_ContactArrowScale: 0.2 + m_ColliderAwakeColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.7529412} + m_ColliderAsleepColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.36078432} + m_ColliderContactColor: {r: 1, g: 0, b: 1, a: 0.6862745} + m_ColliderAABBColor: {r: 1, g: 1, b: 0, a: 0.2509804} + m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff diff --git a/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/ProjectSettings.asset b/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/ProjectSettings.asset new file mode 100644 index 00000000..f60fe83a --- /dev/null +++ b/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/ProjectSettings.asset @@ -0,0 +1,610 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!129 &1 +PlayerSettings: + m_ObjectHideFlags: 0 + serializedVersion: 13 + productGUID: 5eccc60d3e382a346a65f512d6b81b84 + AndroidProfiler: 0 + AndroidFilterTouchesWhenObscured: 0 + defaultScreenOrientation: 4 + targetDevice: 2 + useOnDemandResources: 0 + accelerometerFrequency: 60 + companyName: Discord Inc. + productName: button-clicker + defaultCursor: {fileID: 0} + cursorHotspot: {x: 0, y: 0} + m_SplashScreenBackgroundColor: {r: 0.13725491, g: 0.12156863, b: 0.1254902, a: 1} + m_ShowUnitySplashScreen: 1 + m_ShowUnitySplashLogo: 1 + m_SplashScreenOverlayOpacity: 1 + m_SplashScreenAnimation: 1 + m_SplashScreenLogoStyle: 1 + m_SplashScreenDrawMode: 0 + m_SplashScreenBackgroundAnimationZoom: 1 + m_SplashScreenLogoAnimationZoom: 1 + m_SplashScreenBackgroundLandscapeAspect: 1 + m_SplashScreenBackgroundPortraitAspect: 1 + m_SplashScreenBackgroundLandscapeUvs: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + m_SplashScreenBackgroundPortraitUvs: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + m_SplashScreenLogos: [] + m_VirtualRealitySplashScreen: {fileID: 0} + m_HolographicTrackingLossScreen: {fileID: 0} + defaultScreenWidth: 1024 + defaultScreenHeight: 768 + defaultScreenWidthWeb: 960 + defaultScreenHeightWeb: 600 + m_StereoRenderingPath: 0 + m_ActiveColorSpace: 0 + m_MTRendering: 1 + m_StackTraceTypes: 010000000100000001000000010000000100000001000000 + iosShowActivityIndicatorOnLoading: -1 + androidShowActivityIndicatorOnLoading: -1 + tizenShowActivityIndicatorOnLoading: -1 + iosAppInBackgroundBehavior: 0 + displayResolutionDialog: 1 + iosAllowHTTPDownload: 1 + allowedAutorotateToPortrait: 1 + allowedAutorotateToPortraitUpsideDown: 1 + allowedAutorotateToLandscapeRight: 1 + allowedAutorotateToLandscapeLeft: 1 + useOSAutorotation: 1 + use32BitDisplayBuffer: 1 + disableDepthAndStencilBuffers: 0 + androidBlitType: 0 + defaultIsFullScreen: 1 + defaultIsNativeResolution: 1 + macRetinaSupport: 1 + runInBackground: 0 + captureSingleScreen: 0 + muteOtherAudioSources: 0 + Prepare IOS For Recording: 0 + Force IOS Speakers When Recording: 0 + submitAnalytics: 1 + usePlayerLog: 1 + bakeCollisionMeshes: 0 + forceSingleInstance: 0 + resizableWindow: 0 + useMacAppStoreValidation: 0 + macAppStoreCategory: public.app-category.games + gpuSkinning: 0 + graphicsJobs: 0 + xboxPIXTextureCapture: 0 + xboxEnableAvatar: 0 + xboxEnableKinect: 0 + xboxEnableKinectAutoTracking: 0 + xboxEnableFitness: 0 + visibleInBackground: 1 + allowFullscreenSwitch: 1 + graphicsJobMode: 0 + macFullscreenMode: 2 + d3d9FullscreenMode: 1 + d3d11FullscreenMode: 1 + xboxSpeechDB: 0 + xboxEnableHeadOrientation: 0 + xboxEnableGuest: 0 + xboxEnablePIXSampling: 0 + metalFramebufferOnly: 0 + n3dsDisableStereoscopicView: 0 + n3dsEnableSharedListOpt: 1 + n3dsEnableVSync: 0 + ignoreAlphaClear: 0 + xboxOneResolution: 0 + xboxOneMonoLoggingLevel: 0 + xboxOneLoggingLevel: 1 + xboxOneDisableEsram: 0 + xboxOnePresentImmediateThreshold: 0 + videoMemoryForVertexBuffers: 0 + psp2PowerMode: 0 + psp2AcquireBGM: 1 + wiiUTVResolution: 0 + wiiUGamePadMSAA: 1 + wiiUSupportsNunchuk: 0 + wiiUSupportsClassicController: 0 + wiiUSupportsBalanceBoard: 0 + wiiUSupportsMotionPlus: 0 + wiiUSupportsProController: 0 + wiiUAllowScreenCapture: 1 + wiiUControllerCount: 0 + m_SupportedAspectRatios: + 4:3: 1 + 5:4: 1 + 16:10: 1 + 16:9: 1 + Others: 1 + bundleVersion: 1.0 + preloadedAssets: [] + metroInputSource: 0 + m_HolographicPauseOnTrackingLoss: 1 + xboxOneDisableKinectGpuReservation: 0 + xboxOneEnable7thCore: 0 + vrSettings: + cardboard: + depthFormat: 0 + enableTransitionView: 0 + daydream: + depthFormat: 0 + useSustainedPerformanceMode: 0 + enableVideoLayer: 0 + useProtectedVideoMemory: 0 + hololens: + depthFormat: 1 + protectGraphicsMemory: 0 + useHDRDisplay: 0 + m_ColorGamuts: 00000000 + targetPixelDensity: 0 + resolutionScalingMode: 0 + androidSupportedAspectRatio: 1 + androidMaxAspectRatio: 2.1 + applicationIdentifier: {} + buildNumber: {} + AndroidBundleVersionCode: 1 + AndroidMinSdkVersion: 16 + AndroidTargetSdkVersion: 0 + AndroidPreferredInstallLocation: 1 + aotOptions: + stripEngineCode: 1 + iPhoneStrippingLevel: 0 + iPhoneScriptCallOptimization: 0 + ForceInternetPermission: 0 + ForceSDCardPermission: 0 + CreateWallpaper: 0 + APKExpansionFiles: 0 + keepLoadedShadersAlive: 0 + StripUnusedMeshComponents: 0 + VertexChannelCompressionMask: + serializedVersion: 2 + m_Bits: 238 + iPhoneSdkVersion: 988 + iOSTargetOSVersionString: 7.0 + tvOSSdkVersion: 0 + tvOSRequireExtendedGameController: 0 + tvOSTargetOSVersionString: 9.0 + uIPrerenderedIcon: 0 + uIRequiresPersistentWiFi: 0 + uIRequiresFullScreen: 1 + uIStatusBarHidden: 1 + uIExitOnSuspend: 0 + uIStatusBarStyle: 0 + iPhoneSplashScreen: {fileID: 0} + iPhoneHighResSplashScreen: {fileID: 0} + iPhoneTallHighResSplashScreen: {fileID: 0} + iPhone47inSplashScreen: {fileID: 0} + iPhone55inPortraitSplashScreen: {fileID: 0} + iPhone55inLandscapeSplashScreen: {fileID: 0} + iPadPortraitSplashScreen: {fileID: 0} + iPadHighResPortraitSplashScreen: {fileID: 0} + iPadLandscapeSplashScreen: {fileID: 0} + iPadHighResLandscapeSplashScreen: {fileID: 0} + appleTVSplashScreen: {fileID: 0} + tvOSSmallIconLayers: [] + tvOSLargeIconLayers: [] + tvOSTopShelfImageLayers: [] + tvOSTopShelfImageWideLayers: [] + iOSLaunchScreenType: 0 + iOSLaunchScreenPortrait: {fileID: 0} + iOSLaunchScreenLandscape: {fileID: 0} + iOSLaunchScreenBackgroundColor: + serializedVersion: 2 + rgba: 0 + iOSLaunchScreenFillPct: 100 + iOSLaunchScreenSize: 100 + iOSLaunchScreenCustomXibPath: + iOSLaunchScreeniPadType: 0 + iOSLaunchScreeniPadImage: {fileID: 0} + iOSLaunchScreeniPadBackgroundColor: + serializedVersion: 2 + rgba: 0 + iOSLaunchScreeniPadFillPct: 100 + iOSLaunchScreeniPadSize: 100 + iOSLaunchScreeniPadCustomXibPath: + iOSDeviceRequirements: [] + iOSURLSchemes: [] + iOSBackgroundModes: 0 + iOSMetalForceHardShadows: 0 + metalEditorSupport: 1 + metalAPIValidation: 1 + iOSRenderExtraFrameOnPause: 0 + appleDeveloperTeamID: + iOSManualSigningProvisioningProfileID: + tvOSManualSigningProvisioningProfileID: + appleEnableAutomaticSigning: 0 + AndroidTargetDevice: 0 + AndroidSplashScreenScale: 0 + androidSplashScreen: {fileID: 0} + AndroidKeystoreName: + AndroidKeyaliasName: + AndroidTVCompatibility: 1 + AndroidIsGame: 1 + AndroidEnableTango: 0 + androidEnableBanner: 1 + androidUseLowAccuracyLocation: 0 + m_AndroidBanners: + - width: 320 + height: 180 + banner: {fileID: 0} + androidGamepadSupportLevel: 0 + resolutionDialogBanner: {fileID: 0} + m_BuildTargetIcons: [] + m_BuildTargetBatching: [] + m_BuildTargetGraphicsAPIs: [] + m_BuildTargetVRSettings: [] + m_BuildTargetEnableVuforiaSettings: [] + openGLRequireES31: 0 + openGLRequireES31AEP: 0 + m_TemplateCustomTags: {} + mobileMTRendering: + Android: 1 + iPhone: 1 + tvOS: 1 + wiiUTitleID: 0005000011000000 + wiiUGroupID: 00010000 + wiiUCommonSaveSize: 4096 + wiiUAccountSaveSize: 2048 + wiiUOlvAccessKey: 0 + wiiUTinCode: 0 + wiiUJoinGameId: 0 + wiiUJoinGameModeMask: 0000000000000000 + wiiUCommonBossSize: 0 + wiiUAccountBossSize: 0 + wiiUAddOnUniqueIDs: [] + wiiUMainThreadStackSize: 3072 + wiiULoaderThreadStackSize: 1024 + wiiUSystemHeapSize: 128 + wiiUTVStartupScreen: {fileID: 0} + wiiUGamePadStartupScreen: {fileID: 0} + wiiUDrcBufferDisabled: 0 + wiiUProfilerLibPath: + playModeTestRunnerEnabled: 0 + actionOnDotNetUnhandledException: 1 + enableInternalProfiler: 0 + logObjCUncaughtExceptions: 1 + enableCrashReportAPI: 0 + cameraUsageDescription: + locationUsageDescription: + microphoneUsageDescription: + switchNetLibKey: + switchSocketMemoryPoolSize: 6144 + switchSocketAllocatorPoolSize: 128 + switchSocketConcurrencyLimit: 14 + switchScreenResolutionBehavior: 2 + switchUseCPUProfiler: 0 + switchApplicationID: 0x01004b9000490000 + switchNSODependencies: + switchTitleNames_0: + switchTitleNames_1: + switchTitleNames_2: + switchTitleNames_3: + switchTitleNames_4: + switchTitleNames_5: + switchTitleNames_6: + switchTitleNames_7: + switchTitleNames_8: + switchTitleNames_9: + switchTitleNames_10: + switchTitleNames_11: + switchPublisherNames_0: + switchPublisherNames_1: + switchPublisherNames_2: + switchPublisherNames_3: + switchPublisherNames_4: + switchPublisherNames_5: + switchPublisherNames_6: + switchPublisherNames_7: + switchPublisherNames_8: + switchPublisherNames_9: + switchPublisherNames_10: + switchPublisherNames_11: + switchIcons_0: {fileID: 0} + switchIcons_1: {fileID: 0} + switchIcons_2: {fileID: 0} + switchIcons_3: {fileID: 0} + switchIcons_4: {fileID: 0} + switchIcons_5: {fileID: 0} + switchIcons_6: {fileID: 0} + switchIcons_7: {fileID: 0} + switchIcons_8: {fileID: 0} + switchIcons_9: {fileID: 0} + switchIcons_10: {fileID: 0} + switchIcons_11: {fileID: 0} + switchSmallIcons_0: {fileID: 0} + switchSmallIcons_1: {fileID: 0} + switchSmallIcons_2: {fileID: 0} + switchSmallIcons_3: {fileID: 0} + switchSmallIcons_4: {fileID: 0} + switchSmallIcons_5: {fileID: 0} + switchSmallIcons_6: {fileID: 0} + switchSmallIcons_7: {fileID: 0} + switchSmallIcons_8: {fileID: 0} + switchSmallIcons_9: {fileID: 0} + switchSmallIcons_10: {fileID: 0} + switchSmallIcons_11: {fileID: 0} + switchManualHTML: + switchAccessibleURLs: + switchLegalInformation: + switchMainThreadStackSize: 1048576 + switchPresenceGroupId: 0x01004b9000490000 + switchLogoHandling: 0 + switchReleaseVersion: 0 + switchDisplayVersion: 1.0.0 + switchStartupUserAccount: 0 + switchTouchScreenUsage: 0 + switchSupportedLanguagesMask: 0 + switchLogoType: 0 + switchApplicationErrorCodeCategory: + switchUserAccountSaveDataSize: 0 + switchUserAccountSaveDataJournalSize: 0 + switchApplicationAttribute: 0 + switchCardSpecSize: 4 + switchCardSpecClock: 25 + switchRatingsMask: 0 + switchRatingsInt_0: 0 + switchRatingsInt_1: 0 + switchRatingsInt_2: 0 + switchRatingsInt_3: 0 + switchRatingsInt_4: 0 + switchRatingsInt_5: 0 + switchRatingsInt_6: 0 + switchRatingsInt_7: 0 + switchRatingsInt_8: 0 + switchRatingsInt_9: 0 + switchRatingsInt_10: 0 + switchRatingsInt_11: 0 + switchLocalCommunicationIds_0: 0x01004b9000490000 + switchLocalCommunicationIds_1: + switchLocalCommunicationIds_2: + switchLocalCommunicationIds_3: + switchLocalCommunicationIds_4: + switchLocalCommunicationIds_5: + switchLocalCommunicationIds_6: + switchLocalCommunicationIds_7: + switchParentalControl: 0 + switchAllowsScreenshot: 1 + switchDataLossConfirmation: 0 + switchSupportedNpadStyles: 3 + switchSocketConfigEnabled: 0 + switchTcpInitialSendBufferSize: 32 + switchTcpInitialReceiveBufferSize: 64 + switchTcpAutoSendBufferSizeMax: 256 + switchTcpAutoReceiveBufferSizeMax: 256 + switchUdpSendBufferSize: 9 + switchUdpReceiveBufferSize: 42 + switchSocketBufferEfficiency: 4 + switchSocketInitializeEnabled: 1 + switchNetworkInterfaceManagerInitializeEnabled: 1 + switchPlayerConnectionEnabled: 1 + ps4NPAgeRating: 12 + ps4NPTitleSecret: + ps4NPTrophyPackPath: + ps4ParentalLevel: 11 + ps4ContentID: ED1633-NPXX51362_00-0000000000000000 + ps4Category: 0 + ps4MasterVersion: 01.00 + ps4AppVersion: 01.00 + ps4AppType: 0 + ps4ParamSfxPath: + ps4VideoOutPixelFormat: 0 + ps4VideoOutInitialWidth: 1920 + ps4VideoOutBaseModeInitialWidth: 1920 + ps4VideoOutReprojectionRate: 120 + ps4PronunciationXMLPath: + ps4PronunciationSIGPath: + ps4BackgroundImagePath: + ps4StartupImagePath: + ps4SaveDataImagePath: + ps4SdkOverride: + ps4BGMPath: + ps4ShareFilePath: + ps4ShareOverlayImagePath: + ps4PrivacyGuardImagePath: + ps4NPtitleDatPath: + ps4RemotePlayKeyAssignment: -1 + ps4RemotePlayKeyMappingDir: + ps4PlayTogetherPlayerCount: 0 + ps4EnterButtonAssignment: 1 + ps4ApplicationParam1: 0 + ps4ApplicationParam2: 0 + ps4ApplicationParam3: 0 + ps4ApplicationParam4: 0 + ps4DownloadDataSize: 0 + ps4GarlicHeapSize: 2048 + ps4ProGarlicHeapSize: 2560 + ps4Passcode: frAQBc8Wsa1xVPfvJcrgRYwTiizs2trQ + ps4pnSessions: 1 + ps4pnPresence: 1 + ps4pnFriends: 1 + ps4pnGameCustomData: 1 + playerPrefsSupport: 0 + restrictedAudioUsageRights: 0 + ps4UseResolutionFallback: 0 + ps4ReprojectionSupport: 0 + ps4UseAudio3dBackend: 0 + ps4SocialScreenEnabled: 0 + ps4ScriptOptimizationLevel: 0 + ps4Audio3dVirtualSpeakerCount: 14 + ps4attribCpuUsage: 0 + ps4PatchPkgPath: + ps4PatchLatestPkgPath: + ps4PatchChangeinfoPath: + ps4PatchDayOne: 0 + ps4attribUserManagement: 0 + ps4attribMoveSupport: 0 + ps4attrib3DSupport: 0 + ps4attribShareSupport: 0 + ps4attribExclusiveVR: 0 + ps4disableAutoHideSplash: 0 + ps4videoRecordingFeaturesUsed: 0 + ps4contentSearchFeaturesUsed: 0 + ps4attribEyeToEyeDistanceSettingVR: 0 + ps4IncludedModules: [] + monoEnv: + psp2Splashimage: {fileID: 0} + psp2NPTrophyPackPath: + psp2NPSupportGBMorGJP: 0 + psp2NPAgeRating: 12 + psp2NPTitleDatPath: + psp2NPCommsID: + psp2NPCommunicationsID: + psp2NPCommsPassphrase: + psp2NPCommsSig: + psp2ParamSfxPath: + psp2ManualPath: + psp2LiveAreaGatePath: + psp2LiveAreaBackroundPath: + psp2LiveAreaPath: + psp2LiveAreaTrialPath: + psp2PatchChangeInfoPath: + psp2PatchOriginalPackage: + psp2PackagePassword: F69AzBlax3CF3EDNhm3soLBPh71Yexui + psp2KeystoneFile: + psp2MemoryExpansionMode: 0 + psp2DRMType: 0 + psp2StorageType: 0 + psp2MediaCapacity: 0 + psp2DLCConfigPath: + psp2ThumbnailPath: + psp2BackgroundPath: + psp2SoundPath: + psp2TrophyCommId: + psp2TrophyPackagePath: + psp2PackagedResourcesPath: + psp2SaveDataQuota: 10240 + psp2ParentalLevel: 1 + psp2ShortTitle: Not Set + psp2ContentID: IV0000-ABCD12345_00-0123456789ABCDEF + psp2Category: 0 + psp2MasterVersion: 01.00 + psp2AppVersion: 01.00 + psp2TVBootMode: 0 + psp2EnterButtonAssignment: 2 + psp2TVDisableEmu: 0 + psp2AllowTwitterDialog: 1 + psp2Upgradable: 0 + psp2HealthWarning: 0 + psp2UseLibLocation: 0 + psp2InfoBarOnStartup: 0 + psp2InfoBarColor: 0 + psp2ScriptOptimizationLevel: 0 + psmSplashimage: {fileID: 0} + splashScreenBackgroundSourceLandscape: {fileID: 0} + splashScreenBackgroundSourcePortrait: {fileID: 0} + spritePackerPolicy: + webGLMemorySize: 256 + webGLExceptionSupport: 1 + webGLNameFilesAsHashes: 0 + webGLDataCaching: 0 + webGLDebugSymbols: 0 + webGLEmscriptenArgs: + webGLModulesDirectory: + webGLTemplate: APPLICATION:Default + webGLAnalyzeBuildSize: 0 + webGLUseEmbeddedResources: 0 + webGLUseWasm: 0 + webGLCompressionFormat: 1 + scriptingDefineSymbols: {} + platformArchitecture: {} + scriptingBackend: {} + incrementalIl2cppBuild: {} + additionalIl2CppArgs: + scriptingRuntimeVersion: 0 + apiCompatibilityLevelPerPlatform: {} + m_RenderingPath: 1 + m_MobileRenderingPath: 1 + metroPackageName: button-clicker + metroPackageVersion: + metroCertificatePath: + metroCertificatePassword: + metroCertificateSubject: + metroCertificateIssuer: + metroCertificateNotAfter: 0000000000000000 + metroApplicationDescription: button-clicker + wsaImages: {} + metroTileShortName: + metroCommandLineArgsFile: + metroTileShowName: 0 + metroMediumTileShowName: 0 + metroLargeTileShowName: 0 + metroWideTileShowName: 0 + metroDefaultTileSize: 1 + metroTileForegroundText: 2 + metroTileBackgroundColor: {r: 0.13333334, g: 0.17254902, b: 0.21568628, a: 0} + metroSplashScreenBackgroundColor: {r: 0.12941177, g: 0.17254902, b: 0.21568628, + a: 1} + metroSplashScreenUseBackgroundColor: 0 + platformCapabilities: {} + metroFTAName: + metroFTAFileTypes: [] + metroProtocolName: + metroCompilationOverrides: 1 + tizenProductDescription: + tizenProductURL: + tizenSigningProfileName: + tizenGPSPermissions: 0 + tizenMicrophonePermissions: 0 + tizenDeploymentTarget: + tizenDeploymentTargetType: -1 + tizenMinOSVersion: 1 + n3dsUseExtSaveData: 0 + n3dsCompressStaticMem: 1 + n3dsExtSaveDataNumber: 0x12345 + n3dsStackSize: 131072 + n3dsTargetPlatform: 2 + n3dsRegion: 7 + n3dsMediaSize: 0 + n3dsLogoStyle: 3 + n3dsTitle: GameName + n3dsProductCode: + n3dsApplicationId: 0xFF3FF + stvDeviceAddress: + stvProductDescription: + stvProductAuthor: + stvProductAuthorEmail: + stvProductLink: + stvProductCategory: 0 + XboxOneProductId: + XboxOneUpdateKey: + XboxOneSandboxId: + XboxOneContentId: + XboxOneTitleId: + XboxOneSCId: + XboxOneGameOsOverridePath: + XboxOnePackagingOverridePath: + XboxOneAppManifestOverridePath: + XboxOnePackageEncryption: 0 + XboxOnePackageUpdateGranularity: 2 + XboxOneDescription: + XboxOneLanguage: + - enus + XboxOneCapability: [] + XboxOneGameRating: {} + XboxOneIsContentPackage: 0 + XboxOneEnableGPUVariability: 0 + XboxOneSockets: {} + XboxOneSplashScreen: {fileID: 0} + XboxOneAllowedProductIds: [] + XboxOnePersistentLocalStorageSize: 0 + xboxOneScriptCompiler: 0 + vrEditorSettings: + daydream: + daydreamIconForeground: {fileID: 0} + daydreamIconBackground: {fileID: 0} + cloudServicesEnabled: {} + facebookSdkVersion: 7.9.4 + apiCompatibilityLevel: 2 + cloudProjectId: + projectName: + organizationId: + cloudEnabled: 0 + enableNativePlatformBackendsForNewInputSystem: 0 + disableOldInputManagerSupport: 0 diff --git a/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/ProjectVersion.txt b/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/ProjectVersion.txt new file mode 100644 index 00000000..7a6fffb8 --- /dev/null +++ b/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/ProjectVersion.txt @@ -0,0 +1 @@ +m_EditorVersion: 2017.2.0f3 diff --git a/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/QualitySettings.asset b/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/QualitySettings.asset new file mode 100644 index 00000000..86c047f2 --- /dev/null +++ b/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/QualitySettings.asset @@ -0,0 +1,193 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!47 &1 +QualitySettings: + m_ObjectHideFlags: 0 + serializedVersion: 5 + m_CurrentQuality: 5 + m_QualitySettings: + - serializedVersion: 2 + name: Very Low + pixelLightCount: 0 + shadows: 0 + shadowResolution: 0 + shadowProjection: 1 + shadowCascades: 1 + shadowDistance: 15 + shadowNearPlaneOffset: 3 + shadowCascade2Split: 0.33333334 + shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} + shadowmaskMode: 0 + blendWeights: 1 + textureQuality: 1 + anisotropicTextures: 0 + antiAliasing: 0 + softParticles: 0 + softVegetation: 0 + realtimeReflectionProbes: 0 + billboardsFaceCameraPosition: 0 + vSyncCount: 0 + lodBias: 0.3 + maximumLODLevel: 0 + particleRaycastBudget: 4 + asyncUploadTimeSlice: 2 + asyncUploadBufferSize: 4 + resolutionScalingFixedDPIFactor: 1 + excludedTargetPlatforms: [] + - serializedVersion: 2 + name: Low + pixelLightCount: 0 + shadows: 0 + shadowResolution: 0 + shadowProjection: 1 + shadowCascades: 1 + shadowDistance: 20 + shadowNearPlaneOffset: 3 + shadowCascade2Split: 0.33333334 + shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} + shadowmaskMode: 0 + blendWeights: 2 + textureQuality: 0 + anisotropicTextures: 0 + antiAliasing: 0 + softParticles: 0 + softVegetation: 0 + realtimeReflectionProbes: 0 + billboardsFaceCameraPosition: 0 + vSyncCount: 0 + lodBias: 0.4 + maximumLODLevel: 0 + particleRaycastBudget: 16 + asyncUploadTimeSlice: 2 + asyncUploadBufferSize: 4 + resolutionScalingFixedDPIFactor: 1 + excludedTargetPlatforms: [] + - serializedVersion: 2 + name: Medium + pixelLightCount: 1 + shadows: 1 + shadowResolution: 0 + shadowProjection: 1 + shadowCascades: 1 + shadowDistance: 20 + shadowNearPlaneOffset: 3 + shadowCascade2Split: 0.33333334 + shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} + shadowmaskMode: 0 + blendWeights: 2 + textureQuality: 0 + anisotropicTextures: 1 + antiAliasing: 0 + softParticles: 0 + softVegetation: 0 + realtimeReflectionProbes: 0 + billboardsFaceCameraPosition: 0 + vSyncCount: 1 + lodBias: 0.7 + maximumLODLevel: 0 + particleRaycastBudget: 64 + asyncUploadTimeSlice: 2 + asyncUploadBufferSize: 4 + resolutionScalingFixedDPIFactor: 1 + excludedTargetPlatforms: [] + - serializedVersion: 2 + name: High + pixelLightCount: 2 + shadows: 2 + shadowResolution: 1 + shadowProjection: 1 + shadowCascades: 2 + shadowDistance: 40 + shadowNearPlaneOffset: 3 + shadowCascade2Split: 0.33333334 + shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} + shadowmaskMode: 1 + blendWeights: 2 + textureQuality: 0 + anisotropicTextures: 1 + antiAliasing: 0 + softParticles: 0 + softVegetation: 1 + realtimeReflectionProbes: 1 + billboardsFaceCameraPosition: 1 + vSyncCount: 1 + lodBias: 1 + maximumLODLevel: 0 + particleRaycastBudget: 256 + asyncUploadTimeSlice: 2 + asyncUploadBufferSize: 4 + resolutionScalingFixedDPIFactor: 1 + excludedTargetPlatforms: [] + - serializedVersion: 2 + name: Very High + pixelLightCount: 3 + shadows: 2 + shadowResolution: 2 + shadowProjection: 1 + shadowCascades: 2 + shadowDistance: 70 + shadowNearPlaneOffset: 3 + shadowCascade2Split: 0.33333334 + shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} + shadowmaskMode: 1 + blendWeights: 4 + textureQuality: 0 + anisotropicTextures: 2 + antiAliasing: 2 + softParticles: 1 + softVegetation: 1 + realtimeReflectionProbes: 1 + billboardsFaceCameraPosition: 1 + vSyncCount: 1 + lodBias: 1.5 + maximumLODLevel: 0 + particleRaycastBudget: 1024 + asyncUploadTimeSlice: 2 + asyncUploadBufferSize: 4 + resolutionScalingFixedDPIFactor: 1 + excludedTargetPlatforms: [] + - serializedVersion: 2 + name: Ultra + pixelLightCount: 4 + shadows: 2 + shadowResolution: 2 + shadowProjection: 1 + shadowCascades: 4 + shadowDistance: 150 + shadowNearPlaneOffset: 3 + shadowCascade2Split: 0.33333334 + shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} + shadowmaskMode: 1 + blendWeights: 4 + textureQuality: 0 + anisotropicTextures: 2 + antiAliasing: 2 + softParticles: 1 + softVegetation: 1 + realtimeReflectionProbes: 1 + billboardsFaceCameraPosition: 1 + vSyncCount: 1 + lodBias: 2 + maximumLODLevel: 0 + particleRaycastBudget: 4096 + asyncUploadTimeSlice: 2 + asyncUploadBufferSize: 4 + resolutionScalingFixedDPIFactor: 1 + excludedTargetPlatforms: [] + m_PerPlatformDefaultQuality: + Android: 2 + Nintendo 3DS: 5 + Nintendo Switch: 5 + PS4: 5 + PSM: 5 + PSP2: 2 + Samsung TV: 2 + Standalone: 5 + Tizen: 2 + Web: 5 + WebGL: 3 + WiiU: 5 + Windows Store Apps: 5 + XboxOne: 5 + iPhone: 2 + tvOS: 2 diff --git a/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/TagManager.asset b/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/TagManager.asset new file mode 100644 index 00000000..1c92a784 --- /dev/null +++ b/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/TagManager.asset @@ -0,0 +1,43 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!78 &1 +TagManager: + serializedVersion: 2 + tags: [] + layers: + - Default + - TransparentFX + - Ignore Raycast + - + - Water + - UI + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + m_SortingLayers: + - name: Default + uniqueID: 0 + locked: 0 diff --git a/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/TimeManager.asset b/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/TimeManager.asset new file mode 100644 index 00000000..558a017e --- /dev/null +++ b/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/TimeManager.asset @@ -0,0 +1,9 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!5 &1 +TimeManager: + m_ObjectHideFlags: 0 + Fixed Timestep: 0.02 + Maximum Allowed Timestep: 0.33333334 + m_TimeScale: 1 + Maximum Particle Timestep: 0.03 diff --git a/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/UnityConnectSettings.asset b/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/UnityConnectSettings.asset new file mode 100644 index 00000000..1cc5485b --- /dev/null +++ b/dependencies/discord-rpc/examples/button-clicker/ProjectSettings/UnityConnectSettings.asset @@ -0,0 +1,34 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!310 &1 +UnityConnectSettings: + m_ObjectHideFlags: 0 + m_Enabled: 0 + m_TestMode: 0 + m_TestEventUrl: + m_TestConfigUrl: + m_TestInitMode: 0 + CrashReportingSettings: + m_EventUrl: https://perf-events.cloud.unity3d.com/api/events/crashes + m_Enabled: 0 + m_CaptureEditorExceptions: 1 + UnityPurchasingSettings: + m_Enabled: 0 + m_TestMode: 0 + UnityAnalyticsSettings: + m_Enabled: 0 + m_InitializeOnStartup: 1 + m_TestMode: 0 + m_TestEventUrl: + m_TestConfigUrl: + UnityAdsSettings: + m_Enabled: 0 + m_InitializeOnStartup: 1 + m_TestMode: 0 + m_EnabledPlatforms: 4294967295 + m_IosGameId: + m_AndroidGameId: + m_GameIds: {} + m_GameId: + PerformanceReportingSettings: + m_Enabled: 0 diff --git a/dependencies/discord-rpc/examples/button-clicker/UnityPackageManager/manifest.json b/dependencies/discord-rpc/examples/button-clicker/UnityPackageManager/manifest.json new file mode 100644 index 00000000..526aca60 --- /dev/null +++ b/dependencies/discord-rpc/examples/button-clicker/UnityPackageManager/manifest.json @@ -0,0 +1,4 @@ +{ + "dependencies": { + } +} diff --git a/dependencies/discord-rpc/examples/send-presence/CMakeLists.txt b/dependencies/discord-rpc/examples/send-presence/CMakeLists.txt new file mode 100644 index 00000000..8a67d472 --- /dev/null +++ b/dependencies/discord-rpc/examples/send-presence/CMakeLists.txt @@ -0,0 +1,21 @@ +include_directories(${PROJECT_SOURCE_DIR}/include) +add_executable( + send-presence + MACOSX_BUNDLE + send-presence.c +) +set_target_properties(send-presence PROPERTIES + MACOSX_BUNDLE_BUNDLE_NAME "Send Presence" + MACOSX_BUNDLE_GUI_IDENTIFIER "com.discordapp.examples.send-presence" +) +target_link_libraries(send-presence discord-rpc) + +install( + TARGETS send-presence + RUNTIME + DESTINATION "bin" + CONFIGURATIONS Release + BUNDLE + DESTINATION "bin" + CONFIGURATIONS Release +) \ No newline at end of file diff --git a/dependencies/discord-rpc/examples/send-presence/send-presence.c b/dependencies/discord-rpc/examples/send-presence/send-presence.c new file mode 100644 index 00000000..1b651f2c --- /dev/null +++ b/dependencies/discord-rpc/examples/send-presence/send-presence.c @@ -0,0 +1,206 @@ +/* + This is a simple example in C of using the rich presence API asynchronously. +*/ + +#define _CRT_SECURE_NO_WARNINGS /* thanks Microsoft */ + +#include +#include +#include +#include + +#include "discord_rpc.h" + +static const char* APPLICATION_ID = "345229890980937739"; +static int FrustrationLevel = 0; +static int64_t StartTime; +static int SendPresence = 1; + +static int prompt(char* line, size_t size) +{ + int res; + char* nl; + printf("\n> "); + fflush(stdout); + res = fgets(line, (int)size, stdin) ? 1 : 0; + line[size - 1] = 0; + nl = strchr(line, '\n'); + if (nl) { + *nl = 0; + } + return res; +} + +static void updateDiscordPresence() +{ + if (SendPresence) { + char buffer[256]; + DiscordRichPresence discordPresence; + memset(&discordPresence, 0, sizeof(discordPresence)); + discordPresence.state = "West of House"; + sprintf(buffer, "Frustration level: %d", FrustrationLevel); + discordPresence.details = buffer; + discordPresence.startTimestamp = StartTime; + discordPresence.endTimestamp = time(0) + 5 * 60; + discordPresence.largeImageKey = "canary-large"; + discordPresence.smallImageKey = "ptb-small"; + discordPresence.partyId = "party1234"; + discordPresence.partySize = 1; + discordPresence.partyMax = 6; + discordPresence.partyPrivacy = DISCORD_PARTY_PUBLIC; + discordPresence.matchSecret = "xyzzy"; + discordPresence.joinSecret = "join"; + discordPresence.spectateSecret = "look"; + discordPresence.instance = 0; + Discord_UpdatePresence(&discordPresence); + } + else { + Discord_ClearPresence(); + } +} + +static void handleDiscordReady(const DiscordUser* connectedUser) +{ + printf("\nDiscord: connected to user %s#%s - %s\n", + connectedUser->username, + connectedUser->discriminator, + connectedUser->userId); +} + +static void handleDiscordDisconnected(int errcode, const char* message) +{ + printf("\nDiscord: disconnected (%d: %s)\n", errcode, message); +} + +static void handleDiscordError(int errcode, const char* message) +{ + printf("\nDiscord: error (%d: %s)\n", errcode, message); +} + +static void handleDiscordJoin(const char* secret) +{ + printf("\nDiscord: join (%s)\n", secret); +} + +static void handleDiscordSpectate(const char* secret) +{ + printf("\nDiscord: spectate (%s)\n", secret); +} + +static void handleDiscordJoinRequest(const DiscordUser* request) +{ + int response = -1; + char yn[4]; + printf("\nDiscord: join request from %s#%s - %s\n", + request->username, + request->discriminator, + request->userId); + do { + printf("Accept? (y/n)"); + if (!prompt(yn, sizeof(yn))) { + break; + } + + if (!yn[0]) { + continue; + } + + if (yn[0] == 'y') { + response = DISCORD_REPLY_YES; + break; + } + + if (yn[0] == 'n') { + response = DISCORD_REPLY_NO; + break; + } + } while (1); + if (response != -1) { + Discord_Respond(request->userId, response); + } +} + +static void discordInit() +{ + DiscordEventHandlers handlers; + memset(&handlers, 0, sizeof(handlers)); + handlers.ready = handleDiscordReady; + handlers.disconnected = handleDiscordDisconnected; + handlers.errored = handleDiscordError; + handlers.joinGame = handleDiscordJoin; + handlers.spectateGame = handleDiscordSpectate; + handlers.joinRequest = handleDiscordJoinRequest; + Discord_Initialize(APPLICATION_ID, &handlers, 1, NULL); +} + +static void gameLoop() +{ + char line[512]; + char* space; + + StartTime = time(0); + + printf("You are standing in an open field west of a white house.\n"); + while (prompt(line, sizeof(line))) { + if (line[0]) { + if (line[0] == 'q') { + break; + } + + if (line[0] == 't') { + printf("Shutting off Discord.\n"); + Discord_Shutdown(); + continue; + } + + if (line[0] == 'c') { + if (SendPresence) { + printf("Clearing presence information.\n"); + SendPresence = 0; + } + else { + printf("Restoring presence information.\n"); + SendPresence = 1; + } + updateDiscordPresence(); + continue; + } + + if (line[0] == 'y') { + printf("Reinit Discord.\n"); + discordInit(); + continue; + } + + if (time(NULL) & 1) { + printf("I don't understand that.\n"); + } + else { + space = strchr(line, ' '); + if (space) { + *space = 0; + } + printf("I don't know the word \"%s\".\n", line); + } + + ++FrustrationLevel; + + updateDiscordPresence(); + } + +#ifdef DISCORD_DISABLE_IO_THREAD + Discord_UpdateConnection(); +#endif + Discord_RunCallbacks(); + } +} + +int main(int argc, char* argv[]) +{ + discordInit(); + + gameLoop(); + + Discord_Shutdown(); + return 0; +} diff --git a/dependencies/discord-rpc/examples/unrealstatus/.gitignore b/dependencies/discord-rpc/examples/unrealstatus/.gitignore new file mode 100644 index 00000000..bd472d50 --- /dev/null +++ b/dependencies/discord-rpc/examples/unrealstatus/.gitignore @@ -0,0 +1,78 @@ +# Visual Studio 2015 user specific files +.vs/ + +# Visual Studio 2015 database file +*.VC.db + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app +*.ipa + +# These project files can be generated by the engine +*.xcodeproj +*.xcworkspace +*.sln +*.suo +*.opensdf +*.sdf +*.VC.db +*.VC.opendb + +# Precompiled Assets +SourceArt/**/*.png +SourceArt/**/*.tga + +# Binary Files +Binaries/ + +# Builds +Build/* + +# Whitelist PakBlacklist-.txt files +!Build/*/ +Build/*/** +!Build/*/PakBlacklist*.txt + +# Don't ignore icon files in Build +!Build/**/*.ico + +# Built data for maps +*_BuiltData.uasset + +# Configuration files generated by the Editor +Saved/* + +# Compiled source files for the engine to use +Intermediate/ + +# Cache files for the editor to use +DerivedDataCache/ + +# Library headers must be copied automatically by the build script (build.py unreal) +Plugins/DiscordRpc/Source/ThirdParty/DiscordRpcLibrary/Include diff --git a/dependencies/discord-rpc/examples/unrealstatus/Config/DefaultEditor.ini b/dependencies/discord-rpc/examples/unrealstatus/Config/DefaultEditor.ini new file mode 100644 index 00000000..e69de29b diff --git a/dependencies/discord-rpc/examples/unrealstatus/Config/DefaultEngine.ini b/dependencies/discord-rpc/examples/unrealstatus/Config/DefaultEngine.ini new file mode 100644 index 00000000..84c38f35 --- /dev/null +++ b/dependencies/discord-rpc/examples/unrealstatus/Config/DefaultEngine.ini @@ -0,0 +1,54 @@ +[URL] + +[/Script/HardwareTargeting.HardwareTargetingSettings] +TargetedHardwareClass=Desktop +AppliedTargetedHardwareClass=Desktop +DefaultGraphicsPerformance=Maximum +AppliedDefaultGraphicsPerformance=Maximum + +[/Script/Engine.EndUserSettings] +bSendAnonymousUsageDataToEpic=False + +[/Script/Engine.PhysicsSettings] +DefaultGravityZ=-980.000000 +DefaultTerminalVelocity=4000.000000 +DefaultFluidFriction=0.300000 +SimulateScratchMemorySize=262144 +RagdollAggregateThreshold=4 +TriangleMeshTriangleMinAreaThreshold=5.000000 +bEnableAsyncScene=False +bEnableShapeSharing=False +bEnablePCM=False +bEnableStabilization=False +bWarnMissingLocks=True +bEnable2DPhysics=False +LockedAxis=Invalid +DefaultDegreesOfFreedom=Full3D +BounceThresholdVelocity=200.000000 +FrictionCombineMode=Average +RestitutionCombineMode=Average +MaxAngularVelocity=3600.000000 +MaxDepenetrationVelocity=0.000000 +ContactOffsetMultiplier=0.010000 +MinContactOffset=0.000100 +MaxContactOffset=1.000000 +bSimulateSkeletalMeshOnDedicatedServer=True +DefaultShapeComplexity=CTF_UseSimpleAndComplex +bDefaultHasComplexCollision=True +bSuppressFaceRemapTable=False +bSupportUVFromHitResults=False +bDisableActiveActors=False +bDisableCCD=False +MaxPhysicsDeltaTime=0.033333 +bSubstepping=False +bSubsteppingAsync=False +MaxSubstepDeltaTime=0.016667 +MaxSubsteps=6 +SyncSceneSmoothingFactor=0.000000 +AsyncSceneSmoothingFactor=0.990000 +InitialAverageFrameRate=0.016667 + +[/Script/EngineSettings.GameMapsSettings] +EditorStartupMap=/Game/ShowTheUILevel.ShowTheUILevel +GameDefaultMap=/Game/ShowTheUILevel.ShowTheUILevel + diff --git a/dependencies/discord-rpc/examples/unrealstatus/Config/DefaultGame.ini b/dependencies/discord-rpc/examples/unrealstatus/Config/DefaultGame.ini new file mode 100644 index 00000000..7b5e2dc2 --- /dev/null +++ b/dependencies/discord-rpc/examples/unrealstatus/Config/DefaultGame.ini @@ -0,0 +1,7 @@ +[/Script/EngineSettings.GeneralProjectSettings] +ProjectID=E5977A24492699DF20B8ADBF736AF6C6 +ProjectName=Discord RPC Example +CompanyName=Discord Inc. +Homepage="https://discordapp.com/" +CopyrightNotice= + diff --git a/dependencies/discord-rpc/examples/unrealstatus/Content/MainScreenBP.uasset b/dependencies/discord-rpc/examples/unrealstatus/Content/MainScreenBP.uasset new file mode 100644 index 00000000..e903b538 Binary files /dev/null and b/dependencies/discord-rpc/examples/unrealstatus/Content/MainScreenBP.uasset differ diff --git a/dependencies/discord-rpc/examples/unrealstatus/Content/MouseGameModeBP.uasset b/dependencies/discord-rpc/examples/unrealstatus/Content/MouseGameModeBP.uasset new file mode 100644 index 00000000..410af19a Binary files /dev/null and b/dependencies/discord-rpc/examples/unrealstatus/Content/MouseGameModeBP.uasset differ diff --git a/dependencies/discord-rpc/examples/unrealstatus/Content/MousePlayerControllerBP.uasset b/dependencies/discord-rpc/examples/unrealstatus/Content/MousePlayerControllerBP.uasset new file mode 100644 index 00000000..0aaf50ba Binary files /dev/null and b/dependencies/discord-rpc/examples/unrealstatus/Content/MousePlayerControllerBP.uasset differ diff --git a/dependencies/discord-rpc/examples/unrealstatus/Content/ShowTheUILevel.umap b/dependencies/discord-rpc/examples/unrealstatus/Content/ShowTheUILevel.umap new file mode 100644 index 00000000..bc0e5b7c Binary files /dev/null and b/dependencies/discord-rpc/examples/unrealstatus/Content/ShowTheUILevel.umap differ diff --git a/dependencies/discord-rpc/examples/unrealstatus/Plugins/discordrpc/DiscordRpc.uplugin b/dependencies/discord-rpc/examples/unrealstatus/Plugins/discordrpc/DiscordRpc.uplugin new file mode 100644 index 00000000..309df129 --- /dev/null +++ b/dependencies/discord-rpc/examples/unrealstatus/Plugins/discordrpc/DiscordRpc.uplugin @@ -0,0 +1,29 @@ +{ + "FileVersion": 3, + "Version": 1, + "VersionName": "1.0", + "FriendlyName": "Discord RPC", + "Description": "Wrap the Discord RPC library.", + "Category": "Messaging", + "CreatedBy": "Chris Marsh ", + "CreatedByURL": "https://discordapp.com/", + "DocsURL": "", + "MarketplaceURL": "", + "SupportURL": "", + "CanContainContent": true, + "IsBetaVersion": true, + "Installed": false, + "Modules": [ + { + "Name": "DiscordRpc", + "Type": "Runtime", + "LoadingPhase": "PreDefault", + "WhitelistPlatforms" : + [ + "Win64", + "Linux", + "Mac" + ] + } + ] +} \ No newline at end of file diff --git a/dependencies/discord-rpc/examples/unrealstatus/Plugins/discordrpc/Resources/Icon128.png b/dependencies/discord-rpc/examples/unrealstatus/Plugins/discordrpc/Resources/Icon128.png new file mode 100644 index 00000000..8b7f8e1c Binary files /dev/null and b/dependencies/discord-rpc/examples/unrealstatus/Plugins/discordrpc/Resources/Icon128.png differ diff --git a/dependencies/discord-rpc/examples/unrealstatus/Plugins/discordrpc/Resources/discord.png b/dependencies/discord-rpc/examples/unrealstatus/Plugins/discordrpc/Resources/discord.png new file mode 100644 index 00000000..8b7f8e1c Binary files /dev/null and b/dependencies/discord-rpc/examples/unrealstatus/Plugins/discordrpc/Resources/discord.png differ diff --git a/dependencies/discord-rpc/examples/unrealstatus/Plugins/discordrpc/Source/DiscordRpc/DiscordRpc.Build.cs b/dependencies/discord-rpc/examples/unrealstatus/Plugins/discordrpc/Source/DiscordRpc/DiscordRpc.Build.cs new file mode 100644 index 00000000..3678f985 --- /dev/null +++ b/dependencies/discord-rpc/examples/unrealstatus/Plugins/discordrpc/Source/DiscordRpc/DiscordRpc.Build.cs @@ -0,0 +1,57 @@ +// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; +using System.IO; + +public class DiscordRpc : ModuleRules +{ +#if WITH_FORWARDED_MODULE_RULES_CTOR + public DiscordRpc(ReadOnlyTargetRules Target) : base(Target) +#else + public DiscordRpc(TargetInfo Target) +#endif + { + Definitions.Add("DISCORD_DYNAMIC_LIB=1"); + + PublicIncludePaths.AddRange( + new string[] { + "DiscordRpc/Public" + } + ); + + PrivateIncludePaths.AddRange( + new string[] { + "DiscordRpc/Private" + } + ); + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + "DiscordRpcLibrary" + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "CoreUObject", + "Engine", + "Slate", + "SlateCore", + "Projects" + } + ); + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + // ... add any modules that your module loads dynamically here ... + } + ); + + string BaseDirectory = Path.GetFullPath(Path.Combine(ModuleDirectory, "..", "..", "Source", "ThirdParty", "DiscordRpcLibrary")); + PublicIncludePaths.Add(Path.Combine(BaseDirectory, "Include")); + } +} \ No newline at end of file diff --git a/dependencies/discord-rpc/examples/unrealstatus/Plugins/discordrpc/Source/DiscordRpc/Private/DiscordRpc.cpp b/dependencies/discord-rpc/examples/unrealstatus/Plugins/discordrpc/Source/DiscordRpc/Private/DiscordRpc.cpp new file mode 100644 index 00000000..d539dab0 --- /dev/null +++ b/dependencies/discord-rpc/examples/unrealstatus/Plugins/discordrpc/Source/DiscordRpc/Private/DiscordRpc.cpp @@ -0,0 +1,76 @@ +// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved. + +#include "DiscordRpcPrivatePCH.h" +#include "IPluginManager.h" +#include "ModuleManager.h" + +#define LOCTEXT_NAMESPACE "FDiscordRpcModule" + +void FDiscordRpcModule::StartupModule() +{ +#if !PLATFORM_LINUX +#if defined(DISCORD_DYNAMIC_LIB) + // Get the base directory of this plugin + FString BaseDir = IPluginManager::Get().FindPlugin("DiscordRpc")->GetBaseDir(); + const FString SDKDir = + FPaths::Combine(*BaseDir, TEXT("Source"), TEXT("ThirdParty"), TEXT("DiscordRpcLibrary")); +#if PLATFORM_WINDOWS + const FString LibName = TEXT("discord-rpc"); + const FString LibDir = FPaths::Combine(*SDKDir, TEXT("Win64")); + if (!LoadDependency(LibDir, LibName, DiscordRpcLibraryHandle)) { + FMessageDialog::Open( + EAppMsgType::Ok, + LOCTEXT(LOCTEXT_NAMESPACE, + "Failed to load DiscordRpc plugin. Plug-in will not be functional.")); + FreeDependency(DiscordRpcLibraryHandle); + } +#elif PLATFORM_MAC + const FString LibName = TEXT("libdiscord-rpc"); + const FString LibDir = FPaths::Combine(*SDKDir, TEXT("Mac")); + if (!LoadDependency(LibDir, LibName, DiscordRpcLibraryHandle)) { + FMessageDialog::Open( + EAppMsgType::Ok, + LOCTEXT(LOCTEXT_NAMESPACE, + "Failed to load DiscordRpc plugin. Plug-in will not be functional.")); + FreeDependency(DiscordRpcLibraryHandle); + } +#endif +#endif +#endif +} + +void FDiscordRpcModule::ShutdownModule() +{ + // Free the dll handle +#if !PLATFORM_LINUX +#if defined(DISCORD_DYNAMIC_LIB) + FreeDependency(DiscordRpcLibraryHandle); +#endif +#endif +} + +bool FDiscordRpcModule::LoadDependency(const FString& Dir, const FString& Name, void*& Handle) +{ + FString Lib = Name + TEXT(".") + FPlatformProcess::GetModuleExtension(); + FString Path = Dir.IsEmpty() ? *Lib : FPaths::Combine(*Dir, *Lib); + + Handle = FPlatformProcess::GetDllHandle(*Path); + + if (Handle == nullptr) { + return false; + } + + return true; +} + +void FDiscordRpcModule::FreeDependency(void*& Handle) +{ + if (Handle != nullptr) { + FPlatformProcess::FreeDllHandle(Handle); + Handle = nullptr; + } +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FDiscordRpcModule, DiscordRpc) \ No newline at end of file diff --git a/dependencies/discord-rpc/examples/unrealstatus/Plugins/discordrpc/Source/DiscordRpc/Private/DiscordRpcBlueprint.cpp b/dependencies/discord-rpc/examples/unrealstatus/Plugins/discordrpc/Source/DiscordRpc/Private/DiscordRpcBlueprint.cpp new file mode 100644 index 00000000..8925d8e4 --- /dev/null +++ b/dependencies/discord-rpc/examples/unrealstatus/Plugins/discordrpc/Source/DiscordRpc/Private/DiscordRpcBlueprint.cpp @@ -0,0 +1,172 @@ +#include "DiscordRpcPrivatePCH.h" +#include "DiscordRpcBlueprint.h" +#include "discord_rpc.h" + +DEFINE_LOG_CATEGORY(Discord) + +static UDiscordRpc* self = nullptr; + +static void ReadyHandler(const DiscordUser* connectedUser) +{ + FDiscordUserData ud; + ud.userId = ANSI_TO_TCHAR(connectedUser->userId); + ud.username = ANSI_TO_TCHAR(connectedUser->username); + ud.discriminator = ANSI_TO_TCHAR(connectedUser->discriminator); + ud.avatar = ANSI_TO_TCHAR(connectedUser->avatar); + UE_LOG(Discord, + Log, + TEXT("Discord connected to %s - %s#%s"), + *ud.userId, + *ud.username, + *ud.discriminator); + if (self) { + self->IsConnected = true; + self->OnConnected.Broadcast(ud); + } +} + +static void DisconnectHandler(int errorCode, const char* message) +{ + auto msg = FString(message); + UE_LOG(Discord, Log, TEXT("Discord disconnected (%d): %s"), errorCode, *msg); + if (self) { + self->IsConnected = false; + self->OnDisconnected.Broadcast(errorCode, msg); + } +} + +static void ErroredHandler(int errorCode, const char* message) +{ + auto msg = FString(message); + UE_LOG(Discord, Log, TEXT("Discord error (%d): %s"), errorCode, *msg); + if (self) { + self->OnErrored.Broadcast(errorCode, msg); + } +} + +static void JoinGameHandler(const char* joinSecret) +{ + auto secret = FString(joinSecret); + UE_LOG(Discord, Log, TEXT("Discord join %s"), *secret); + if (self) { + self->OnJoin.Broadcast(secret); + } +} + +static void SpectateGameHandler(const char* spectateSecret) +{ + auto secret = FString(spectateSecret); + UE_LOG(Discord, Log, TEXT("Discord spectate %s"), *secret); + if (self) { + self->OnSpectate.Broadcast(secret); + } +} + +static void JoinRequestHandler(const DiscordUser* request) +{ + FDiscordUserData ud; + ud.userId = ANSI_TO_TCHAR(request->userId); + ud.username = ANSI_TO_TCHAR(request->username); + ud.discriminator = ANSI_TO_TCHAR(request->discriminator); + ud.avatar = ANSI_TO_TCHAR(request->avatar); + UE_LOG(Discord, + Log, + TEXT("Discord join request from %s - %s#%s"), + *ud.userId, + *ud.username, + *ud.discriminator); + if (self) { + self->OnJoinRequest.Broadcast(ud); + } +} + +void UDiscordRpc::Initialize(const FString& applicationId, + bool autoRegister, + const FString& optionalSteamId) +{ + self = this; + IsConnected = false; + DiscordEventHandlers handlers{}; + handlers.ready = ReadyHandler; + handlers.disconnected = DisconnectHandler; + handlers.errored = ErroredHandler; + if (OnJoin.IsBound()) { + handlers.joinGame = JoinGameHandler; + } + if (OnSpectate.IsBound()) { + handlers.spectateGame = SpectateGameHandler; + } + if (OnJoinRequest.IsBound()) { + handlers.joinRequest = JoinRequestHandler; + } + auto appId = StringCast(*applicationId); + auto steamId = StringCast(*optionalSteamId); + Discord_Initialize( + (const char*)appId.Get(), &handlers, autoRegister, (const char*)steamId.Get()); +} + +void UDiscordRpc::Shutdown() +{ + Discord_Shutdown(); + self = nullptr; +} + +void UDiscordRpc::RunCallbacks() +{ + Discord_RunCallbacks(); +} + +void UDiscordRpc::UpdatePresence() +{ + DiscordRichPresence rp{}; + + auto state = StringCast(*RichPresence.state); + rp.state = state.Get(); + + auto details = StringCast(*RichPresence.details); + rp.details = details.Get(); + + auto largeImageKey = StringCast(*RichPresence.largeImageKey); + rp.largeImageKey = largeImageKey.Get(); + + auto largeImageText = StringCast(*RichPresence.largeImageText); + rp.largeImageText = largeImageText.Get(); + + auto smallImageKey = StringCast(*RichPresence.smallImageKey); + rp.smallImageKey = smallImageKey.Get(); + + auto smallImageText = StringCast(*RichPresence.smallImageText); + rp.smallImageText = smallImageText.Get(); + + auto partyId = StringCast(*RichPresence.partyId); + rp.partyId = partyId.Get(); + + auto matchSecret = StringCast(*RichPresence.matchSecret); + rp.matchSecret = matchSecret.Get(); + + auto joinSecret = StringCast(*RichPresence.joinSecret); + rp.joinSecret = joinSecret.Get(); + + auto spectateSecret = StringCast(*RichPresence.spectateSecret); + rp.spectateSecret = spectateSecret.Get(); + rp.startTimestamp = RichPresence.startTimestamp; + rp.endTimestamp = RichPresence.endTimestamp; + rp.partySize = RichPresence.partySize; + rp.partyMax = RichPresence.partyMax; + rp.partyPrivacy = (int)RichPresence.partyPrivacy; + rp.instance = RichPresence.instance; + + Discord_UpdatePresence(&rp); +} + +void UDiscordRpc::ClearPresence() +{ + Discord_ClearPresence(); +} + +void UDiscordRpc::Respond(const FString& userId, int reply) +{ + UE_LOG(Discord, Log, TEXT("Responding %d to join request from %s"), reply, *userId); + FTCHARToUTF8 utf8_userid(*userId); + Discord_Respond(utf8_userid.Get(), reply); +} diff --git a/dependencies/discord-rpc/examples/unrealstatus/Plugins/discordrpc/Source/DiscordRpc/Private/DiscordRpcPrivatePCH.h b/dependencies/discord-rpc/examples/unrealstatus/Plugins/discordrpc/Source/DiscordRpc/Private/DiscordRpcPrivatePCH.h new file mode 100644 index 00000000..11668017 --- /dev/null +++ b/dependencies/discord-rpc/examples/unrealstatus/Plugins/discordrpc/Source/DiscordRpc/Private/DiscordRpcPrivatePCH.h @@ -0,0 +1,2 @@ +#include "Core.h" +#include "DiscordRpc.h" \ No newline at end of file diff --git a/dependencies/discord-rpc/examples/unrealstatus/Plugins/discordrpc/Source/DiscordRpc/Public/DiscordRpc.h b/dependencies/discord-rpc/examples/unrealstatus/Plugins/discordrpc/Source/DiscordRpc/Public/DiscordRpc.h new file mode 100644 index 00000000..727833b0 --- /dev/null +++ b/dependencies/discord-rpc/examples/unrealstatus/Plugins/discordrpc/Source/DiscordRpc/Public/DiscordRpc.h @@ -0,0 +1,20 @@ +// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "ModuleManager.h" + +class FDiscordRpcModule : public IModuleInterface { +public: + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; + +private: + /** Handle to the test dll we will load */ + void* DiscordRpcLibraryHandle; + + /** StartupModule is covered with defines, these functions are the place to put breakpoints */ + static bool LoadDependency(const FString& Dir, const FString& Name, void*& Handle); + static void FreeDependency(void*& Handle); +}; \ No newline at end of file diff --git a/dependencies/discord-rpc/examples/unrealstatus/Plugins/discordrpc/Source/DiscordRpc/Public/DiscordRpcBlueprint.h b/dependencies/discord-rpc/examples/unrealstatus/Plugins/discordrpc/Source/DiscordRpc/Public/DiscordRpcBlueprint.h new file mode 100644 index 00000000..409eee0d --- /dev/null +++ b/dependencies/discord-rpc/examples/unrealstatus/Plugins/discordrpc/Source/DiscordRpc/Public/DiscordRpcBlueprint.h @@ -0,0 +1,181 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Engine.h" +#include "DiscordRpcBlueprint.generated.h" + +// unreal's header tool hates clang-format +// clang-format off + +/** +* Ask to join callback data +*/ +USTRUCT(BlueprintType) +struct FDiscordUserData { + GENERATED_USTRUCT_BODY() + + UPROPERTY(BlueprintReadOnly) + FString userId; + UPROPERTY(BlueprintReadOnly) + FString username; + UPROPERTY(BlueprintReadOnly) + FString discriminator; + UPROPERTY(BlueprintReadOnly) + FString avatar; +}; + +/** +* Valid response codes for Respond function +*/ +UENUM(BlueprintType) +enum class EDiscordJoinResponseCodes : uint8 +{ + DISCORD_REPLY_NO UMETA(DisplayName="No"), + DISCORD_REPLY_YES UMETA(DisplayName="Yes"), + DISCORD_REPLY_IGNORE UMETA(DisplayName="Ignore") +}; + +/** +* Valid party privacy values +*/ +UENUM(BlueprintType) +enum class EDiscordPartyPrivacy: uint8 +{ + DISCORD_PARTY_PRIVATE UMETA(DisplayName="Private"), + DISCORD_PARTY_PUBLIC UMETA(DisplayName="Public") +}; + +DECLARE_LOG_CATEGORY_EXTERN(Discord, Log, All); + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDiscordConnected, const FDiscordUserData&, joinRequest); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FDiscordDisconnected, int, errorCode, const FString&, errorMessage); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FDiscordErrored, int, errorCode, const FString&, errorMessage); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDiscordJoin, const FString&, joinSecret); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDiscordSpectate, const FString&, spectateSecret); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDiscordJoinRequest, const FDiscordUserData&, joinRequest); + +// clang-format on + +/** + * Rich presence data + */ +USTRUCT(BlueprintType) +struct FDiscordRichPresence { + GENERATED_USTRUCT_BODY() + + UPROPERTY(BlueprintReadWrite) + FString state; + UPROPERTY(BlueprintReadWrite) + FString details; + // todo, timestamps are 64bit, does that even matter? + UPROPERTY(BlueprintReadWrite) + int startTimestamp; + UPROPERTY(BlueprintReadWrite) + int endTimestamp; + UPROPERTY(BlueprintReadWrite) + FString largeImageKey; + UPROPERTY(BlueprintReadWrite) + FString largeImageText; + UPROPERTY(BlueprintReadWrite) + FString smallImageKey; + UPROPERTY(BlueprintReadWrite) + FString smallImageText; + UPROPERTY(BlueprintReadWrite) + FString partyId; + UPROPERTY(BlueprintReadWrite) + int partySize; + UPROPERTY(BlueprintReadWrite) + int partyMax; + UPROPERTY(BlueprintReadWrite) + EDiscordPartyPrivacy partyPrivacy; + UPROPERTY(BlueprintReadWrite) + FString matchSecret; + UPROPERTY(BlueprintReadWrite) + FString joinSecret; + UPROPERTY(BlueprintReadWrite) + FString spectateSecret; + UPROPERTY(BlueprintReadWrite) + bool instance; +}; + +/** + * + */ +UCLASS(BlueprintType, meta = (DisplayName = "Discord RPC"), Category = "Discord") +class DISCORDRPC_API UDiscordRpc : public UObject { + GENERATED_BODY() + +public: + UFUNCTION(BlueprintCallable, + meta = (DisplayName = "Initialize connection", Keywords = "Discord rpc"), + Category = "Discord") + void Initialize(const FString& applicationId, + bool autoRegister, + const FString& optionalSteamId); + + UFUNCTION(BlueprintCallable, + meta = (DisplayName = "Shut down connection", Keywords = "Discord rpc"), + Category = "Discord") + void Shutdown(); + + UFUNCTION(BlueprintCallable, + meta = (DisplayName = "Check for callbacks", Keywords = "Discord rpc"), + Category = "Discord") + void RunCallbacks(); + + UFUNCTION(BlueprintCallable, + meta = (DisplayName = "Send presence", Keywords = "Discord rpc"), + Category = "Discord") + void UpdatePresence(); + + UFUNCTION(BlueprintCallable, + meta = (DisplayName = "Clear presence", Keywords = "Discord rpc"), + Category = "Discord") + void ClearPresence(); + + UFUNCTION(BlueprintCallable, + meta = (DisplayName = "Respond to join request", Keywords = "Discord rpc"), + Category = "Discord") + void Respond(const FString& userId, int reply); + + UPROPERTY(BlueprintReadOnly, + meta = (DisplayName = "Is Discord connected", Keywords = "Discord rpc"), + Category = "Discord") + bool IsConnected; + + UPROPERTY(BlueprintAssignable, + meta = (DisplayName = "On connection", Keywords = "Discord rpc"), + Category = "Discord") + FDiscordConnected OnConnected; + + UPROPERTY(BlueprintAssignable, + meta = (DisplayName = "On disconnection", Keywords = "Discord rpc"), + Category = "Discord") + FDiscordDisconnected OnDisconnected; + + UPROPERTY(BlueprintAssignable, + meta = (DisplayName = "On error message", Keywords = "Discord rpc"), + Category = "Discord") + FDiscordErrored OnErrored; + + UPROPERTY(BlueprintAssignable, + meta = (DisplayName = "When Discord user presses join", Keywords = "Discord rpc"), + Category = "Discord") + FDiscordJoin OnJoin; + + UPROPERTY(BlueprintAssignable, + meta = (DisplayName = "When Discord user presses spectate", Keywords = "Discord rpc"), + Category = "Discord") + FDiscordSpectate OnSpectate; + + UPROPERTY(BlueprintAssignable, + meta = (DisplayName = "When Discord another user sends a join request", + Keywords = "Discord rpc"), + Category = "Discord") + FDiscordJoinRequest OnJoinRequest; + + UPROPERTY(BlueprintReadWrite, + meta = (DisplayName = "Rich presence info", Keywords = "Discord rpc"), + Category = "Discord") + FDiscordRichPresence RichPresence; +}; diff --git a/dependencies/discord-rpc/examples/unrealstatus/Plugins/discordrpc/Source/ThirdParty/DiscordRpcLibrary/DiscordRpcLibrary.Build.cs b/dependencies/discord-rpc/examples/unrealstatus/Plugins/discordrpc/Source/ThirdParty/DiscordRpcLibrary/DiscordRpcLibrary.Build.cs new file mode 100644 index 00000000..8591b159 --- /dev/null +++ b/dependencies/discord-rpc/examples/unrealstatus/Plugins/discordrpc/Source/ThirdParty/DiscordRpcLibrary/DiscordRpcLibrary.Build.cs @@ -0,0 +1,59 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +using System.IO; +using UnrealBuildTool; + +public class DiscordRpcLibrary : ModuleRules +{ +#if WITH_FORWARDED_MODULE_RULES_CTOR + public DiscordRpcLibrary(ReadOnlyTargetRules Target) : base(Target) +#else + public DiscordRpcLibrary(TargetInfo Target) +#endif + { + Type = ModuleType.External; + Definitions.Add("DISCORD_DYNAMIC_LIB=1"); + + string BaseDirectory = Path.GetFullPath(Path.Combine(ModuleDirectory, "..", "..", "ThirdParty", "DiscordRpcLibrary")); + + if (Target.Platform == UnrealTargetPlatform.Win64) + { + string lib = Path.Combine(BaseDirectory, "Win64"); + + // Include headers + PublicIncludePaths.Add(Path.Combine(BaseDirectory, "Include")); + + // Add the import library + PublicLibraryPaths.Add(lib); + PublicAdditionalLibraries.Add(Path.Combine(lib, "discord-rpc.lib")); + + // Dynamic + RuntimeDependencies.Add(new RuntimeDependency(Path.Combine(lib, "discord-rpc.dll"))); + PublicDelayLoadDLLs.Add("discord-rpc.dll"); + } + else if (Target.Platform == UnrealTargetPlatform.Linux) + { + string lib = Path.Combine(BaseDirectory, "Linux", "x86_64-unknown-linux-gnu"); + + // Include headers + PublicIncludePaths.Add(Path.Combine(BaseDirectory, "Include")); + + // Add the import library + PublicLibraryPaths.Add(lib); + PublicAdditionalLibraries.Add(Path.Combine(lib, "libdiscord-rpc.so")); + RuntimeDependencies.Add(new RuntimeDependency(Path.Combine(lib, "libdiscord-rpc.so"))); + } + else if (Target.Platform == UnrealTargetPlatform.Mac) + { + string lib = Path.Combine(BaseDirectory, "Mac"); + + // Include headers + PublicIncludePaths.Add(Path.Combine(BaseDirectory, "Include")); + + // Add the import library + PublicLibraryPaths.Add(lib); + PublicAdditionalLibraries.Add(Path.Combine(lib, "libdiscord-rpc.dylib")); + RuntimeDependencies.Add(new RuntimeDependency(Path.Combine(lib, "libdiscord-rpc.dylib"))); + } + } +} \ No newline at end of file diff --git a/dependencies/discord-rpc/examples/unrealstatus/Source/unrealstatus.Target.cs b/dependencies/discord-rpc/examples/unrealstatus/Source/unrealstatus.Target.cs new file mode 100644 index 00000000..0c7c3c88 --- /dev/null +++ b/dependencies/discord-rpc/examples/unrealstatus/Source/unrealstatus.Target.cs @@ -0,0 +1,14 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +using UnrealBuildTool; +using System.Collections.Generic; + +public class unrealstatusTarget : TargetRules +{ + public unrealstatusTarget(TargetInfo Target) : base(Target) + { + Type = TargetType.Game; + + ExtraModuleNames.AddRange( new string[] { "unrealstatus" } ); + } +} diff --git a/dependencies/discord-rpc/examples/unrealstatus/Source/unrealstatus/unrealstatus.Build.cs b/dependencies/discord-rpc/examples/unrealstatus/Source/unrealstatus/unrealstatus.Build.cs new file mode 100644 index 00000000..9560370e --- /dev/null +++ b/dependencies/discord-rpc/examples/unrealstatus/Source/unrealstatus/unrealstatus.Build.cs @@ -0,0 +1,23 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +using UnrealBuildTool; + +public class unrealstatus : ModuleRules +{ + public unrealstatus(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" }); + + PrivateDependencyModuleNames.AddRange(new string[] { }); + + // Uncomment if you are using Slate UI + // PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" }); + + // Uncomment if you are using online features + // PrivateDependencyModuleNames.Add("OnlineSubsystem"); + + // To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true + } +} diff --git a/dependencies/discord-rpc/examples/unrealstatus/Source/unrealstatus/unrealstatus.cpp b/dependencies/discord-rpc/examples/unrealstatus/Source/unrealstatus/unrealstatus.cpp new file mode 100644 index 00000000..9dd53886 --- /dev/null +++ b/dependencies/discord-rpc/examples/unrealstatus/Source/unrealstatus/unrealstatus.cpp @@ -0,0 +1,6 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "unrealstatus.h" +#include "Modules/ModuleManager.h" + +IMPLEMENT_PRIMARY_GAME_MODULE(FDefaultGameModuleImpl, unrealstatus, "unrealstatus"); diff --git a/dependencies/discord-rpc/examples/unrealstatus/Source/unrealstatus/unrealstatus.h b/dependencies/discord-rpc/examples/unrealstatus/Source/unrealstatus/unrealstatus.h new file mode 100644 index 00000000..73407dd1 --- /dev/null +++ b/dependencies/discord-rpc/examples/unrealstatus/Source/unrealstatus/unrealstatus.h @@ -0,0 +1,5 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" diff --git a/dependencies/discord-rpc/examples/unrealstatus/Source/unrealstatus/unrealstatusGameModeBase.cpp b/dependencies/discord-rpc/examples/unrealstatus/Source/unrealstatus/unrealstatusGameModeBase.cpp new file mode 100644 index 00000000..1af30948 --- /dev/null +++ b/dependencies/discord-rpc/examples/unrealstatus/Source/unrealstatus/unrealstatusGameModeBase.cpp @@ -0,0 +1,3 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "unrealstatusGameModeBase.h" diff --git a/dependencies/discord-rpc/examples/unrealstatus/Source/unrealstatus/unrealstatusGameModeBase.h b/dependencies/discord-rpc/examples/unrealstatus/Source/unrealstatus/unrealstatusGameModeBase.h new file mode 100644 index 00000000..8dd9bd47 --- /dev/null +++ b/dependencies/discord-rpc/examples/unrealstatus/Source/unrealstatus/unrealstatusGameModeBase.h @@ -0,0 +1,15 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/GameModeBase.h" +#include "unrealstatusGameModeBase.generated.h" + +/** + * + */ +UCLASS() +class UNREALSTATUS_API AunrealstatusGameModeBase : public AGameModeBase { + GENERATED_BODY() +}; diff --git a/dependencies/discord-rpc/examples/unrealstatus/Source/unrealstatusEditor.Target.cs b/dependencies/discord-rpc/examples/unrealstatus/Source/unrealstatusEditor.Target.cs new file mode 100644 index 00000000..2e8ad022 --- /dev/null +++ b/dependencies/discord-rpc/examples/unrealstatus/Source/unrealstatusEditor.Target.cs @@ -0,0 +1,14 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +using UnrealBuildTool; +using System.Collections.Generic; + +public class unrealstatusEditorTarget : TargetRules +{ + public unrealstatusEditorTarget(TargetInfo Target) : base(Target) + { + Type = TargetType.Editor; + + ExtraModuleNames.AddRange( new string[] { "unrealstatus" } ); + } +} diff --git a/dependencies/discord-rpc/examples/unrealstatus/unrealstatus.uproject b/dependencies/discord-rpc/examples/unrealstatus/unrealstatus.uproject new file mode 100644 index 00000000..8a4a40b4 --- /dev/null +++ b/dependencies/discord-rpc/examples/unrealstatus/unrealstatus.uproject @@ -0,0 +1,19 @@ +{ + "FileVersion": 3, + "EngineAssociation": "4.18", + "Category": "", + "Description": "", + "Modules": [ + { + "Name": "unrealstatus", + "Type": "Runtime", + "LoadingPhase": "Default" + } + ], + "TargetPlatforms": [ + "LinuxNoEditor", + "MacNoEditor", + "WindowsNoEditor", + "AllDesktop" + ] +} \ No newline at end of file diff --git a/dependencies/discord-rpc/include/discord_register.h b/dependencies/discord-rpc/include/discord_register.h new file mode 100644 index 00000000..16fb42f3 --- /dev/null +++ b/dependencies/discord-rpc/include/discord_register.h @@ -0,0 +1,26 @@ +#pragma once + +#if defined(DISCORD_DYNAMIC_LIB) +#if defined(_WIN32) +#if defined(DISCORD_BUILDING_SDK) +#define DISCORD_EXPORT __declspec(dllexport) +#else +#define DISCORD_EXPORT __declspec(dllimport) +#endif +#else +#define DISCORD_EXPORT __attribute__((visibility("default"))) +#endif +#else +#define DISCORD_EXPORT +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +DISCORD_EXPORT void Discord_Register(const char* applicationId, const char* command); +DISCORD_EXPORT void Discord_RegisterSteamGame(const char* applicationId, const char* steamId); + +#ifdef __cplusplus +} +#endif diff --git a/dependencies/discord-rpc/include/discord_rpc.h b/dependencies/discord-rpc/include/discord_rpc.h new file mode 100644 index 00000000..9470434a --- /dev/null +++ b/dependencies/discord-rpc/include/discord_rpc.h @@ -0,0 +1,90 @@ +#pragma once +#include + +// clang-format off + +#if defined(DISCORD_DYNAMIC_LIB) +# if defined(_WIN32) +# if defined(DISCORD_BUILDING_SDK) +# define DISCORD_EXPORT __declspec(dllexport) +# else +# define DISCORD_EXPORT __declspec(dllimport) +# endif +# else +# define DISCORD_EXPORT __attribute__((visibility("default"))) +# endif +#else +# define DISCORD_EXPORT +#endif + +// clang-format on + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct DiscordRichPresence { + const char* state; /* max 128 bytes */ + const char* details; /* max 128 bytes */ + int64_t startTimestamp; + int64_t endTimestamp; + const char* largeImageKey; /* max 32 bytes */ + const char* largeImageText; /* max 128 bytes */ + const char* smallImageKey; /* max 32 bytes */ + const char* smallImageText; /* max 128 bytes */ + const char* partyId; /* max 128 bytes */ + int partySize; + int partyMax; + int partyPrivacy; + const char* matchSecret; /* max 128 bytes */ + const char* joinSecret; /* max 128 bytes */ + const char* spectateSecret; /* max 128 bytes */ + int8_t instance; +} DiscordRichPresence; + +typedef struct DiscordUser { + const char* userId; + const char* username; + const char* discriminator; + const char* avatar; +} DiscordUser; + +typedef struct DiscordEventHandlers { + void (*ready)(const DiscordUser* request); + void (*disconnected)(int errorCode, const char* message); + void (*errored)(int errorCode, const char* message); + void (*joinGame)(const char* joinSecret); + void (*spectateGame)(const char* spectateSecret); + void (*joinRequest)(const DiscordUser* request); +} DiscordEventHandlers; + +#define DISCORD_REPLY_NO 0 +#define DISCORD_REPLY_YES 1 +#define DISCORD_REPLY_IGNORE 2 +#define DISCORD_PARTY_PRIVATE 0 +#define DISCORD_PARTY_PUBLIC 1 + +DISCORD_EXPORT void Discord_Initialize(const char* applicationId, + DiscordEventHandlers* handlers, + int autoRegister, + const char* optionalSteamId); +DISCORD_EXPORT void Discord_Shutdown(void); + +/* checks for incoming messages, dispatches callbacks */ +DISCORD_EXPORT void Discord_RunCallbacks(void); + +/* If you disable the lib starting its own io thread, you'll need to call this from your own */ +#ifdef DISCORD_DISABLE_IO_THREAD +DISCORD_EXPORT void Discord_UpdateConnection(void); +#endif + +DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence* presence); +DISCORD_EXPORT void Discord_ClearPresence(void); + +DISCORD_EXPORT void Discord_Respond(const char* userid, /* DISCORD_REPLY_ */ int reply); + +DISCORD_EXPORT void Discord_UpdateHandlers(DiscordEventHandlers* handlers); + +#ifdef __cplusplus +} /* extern "C" */ +#endif diff --git a/dependencies/discord-rpc/src/CMakeLists.txt b/dependencies/discord-rpc/src/CMakeLists.txt new file mode 100644 index 00000000..2eeacf48 --- /dev/null +++ b/dependencies/discord-rpc/src/CMakeLists.txt @@ -0,0 +1,137 @@ +include_directories(${PROJECT_SOURCE_DIR}/include) + +option(ENABLE_IO_THREAD "Start up a separate I/O thread, otherwise I'd need to call an update function" ON) +option(WARNINGS_AS_ERRORS "When enabled, compiles with `-Werror` (on *nix platforms)." OFF) + +set(CMAKE_CXX_STANDARD 14) + +set(BASE_RPC_SRC + ${PROJECT_SOURCE_DIR}/include/discord_rpc.h + discord_rpc.cpp + ${PROJECT_SOURCE_DIR}/include/discord_register.h + rpc_connection.h + rpc_connection.cpp + serialization.h + serialization.cpp + connection.h + backoff.h + msg_queue.h +) + +if (${BUILD_SHARED_LIBS}) + if(WIN32) + set(BASE_RPC_SRC ${BASE_RPC_SRC} dllmain.cpp) + endif(WIN32) +endif(${BUILD_SHARED_LIBS}) + +if(WIN32) + add_definitions(-DDISCORD_WINDOWS) + set(BASE_RPC_SRC ${BASE_RPC_SRC} connection_win.cpp discord_register_win.cpp) + add_library(discord-rpc ${BASE_RPC_SRC}) + if (MSVC) + set_property(TARGET discord-rpc PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + target_compile_options(discord-rpc PRIVATE /EHsc + /Wall + /wd4100 # unreferenced formal parameter + /wd4514 # unreferenced inline + /wd4625 # copy constructor deleted + /wd5026 # move constructor deleted + /wd4626 # move assignment operator deleted + /wd4668 # not defined preprocessor macro + /wd4710 # function not inlined + /wd4711 # function was inlined + /wd4820 # structure padding + /wd4946 # reinterpret_cast used between related classes + /wd5027 # move assignment operator was implicitly defined as deleted + ) + endif(MSVC) + target_link_libraries(discord-rpc PRIVATE psapi advapi32) +endif(WIN32) + +if(UNIX) + set(BASE_RPC_SRC ${BASE_RPC_SRC} connection_unix.cpp) + + if (APPLE) + add_definitions(-DDISCORD_OSX) + set(BASE_RPC_SRC ${BASE_RPC_SRC} discord_register_osx.m) + else (APPLE) + add_definitions(-DDISCORD_LINUX) + set(BASE_RPC_SRC ${BASE_RPC_SRC} discord_register_linux.cpp) + endif(APPLE) + + add_library(discord-rpc ${BASE_RPC_SRC}) + target_link_libraries(discord-rpc PUBLIC pthread) + + if (APPLE) + target_link_libraries(discord-rpc PRIVATE "-framework AppKit, -mmacosx-version-min=10.10") + endif (APPLE) + + target_compile_options(discord-rpc PRIVATE + -g + -Wall + -Wextra + -Wpedantic + ) + + if (${WARNINGS_AS_ERRORS}) + target_compile_options(discord-rpc PRIVATE -Werror) + endif (${WARNINGS_AS_ERRORS}) + + target_compile_options(discord-rpc PRIVATE + -Wno-unknown-pragmas # pragma push thing doesn't work on clang + -Wno-old-style-cast # it's fine + -Wno-c++98-compat # that was almost 2 decades ago + -Wno-c++98-compat-pedantic + -Wno-missing-noreturn + -Wno-padded # structure padding + -Wno-covered-switch-default + -Wno-exit-time-destructors # not sure about these + -Wno-global-constructors + ) + + if (${BUILD_SHARED_LIBS}) + target_compile_options(discord-rpc PRIVATE -fPIC) + endif (${BUILD_SHARED_LIBS}) + + if (APPLE) + target_link_libraries(discord-rpc PRIVATE "-framework AppKit") + endif (APPLE) +endif(UNIX) + +target_link_libraries(discord-rpc PRIVATE rapidjson) +#target_include_directories(discord-rpc PRIVATE ${RAPIDJSON}/include) + +if (NOT ${ENABLE_IO_THREAD}) + target_compile_definitions(discord-rpc PUBLIC -DDISCORD_DISABLE_IO_THREAD) +endif (NOT ${ENABLE_IO_THREAD}) + +if (${BUILD_SHARED_LIBS}) + target_compile_definitions(discord-rpc PUBLIC -DDISCORD_DYNAMIC_LIB) + target_compile_definitions(discord-rpc PRIVATE -DDISCORD_BUILDING_SDK) +endif(${BUILD_SHARED_LIBS}) + +if (CLANG_FORMAT_CMD) + add_dependencies(discord-rpc clangformat) +endif(CLANG_FORMAT_CMD) + +# install + +install( + TARGETS discord-rpc + EXPORT "discord-rpc" + RUNTIME + DESTINATION "${CMAKE_INSTALL_BINDIR}" + LIBRARY + DESTINATION "${CMAKE_INSTALL_LIBDIR}" + ARCHIVE + DESTINATION "${CMAKE_INSTALL_LIBDIR}" + INCLUDES + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" +) + +install( + FILES + "../include/discord_rpc.h" + "../include/discord_register.h" + DESTINATION "include" +) diff --git a/dependencies/discord-rpc/src/backoff.h b/dependencies/discord-rpc/src/backoff.h new file mode 100644 index 00000000..a3e736fb --- /dev/null +++ b/dependencies/discord-rpc/src/backoff.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include +#include + +struct Backoff { + int64_t minAmount; + int64_t maxAmount; + int64_t current; + int fails; + std::mt19937_64 randGenerator; + std::uniform_real_distribution<> randDistribution; + + double rand01() { return randDistribution(randGenerator); } + + Backoff(int64_t min, int64_t max) + : minAmount(min) + , maxAmount(max) + , current(min) + , fails(0) + , randGenerator((uint64_t)time(0)) + { + } + + void reset() + { + fails = 0; + current = minAmount; + } + + int64_t nextDelay() + { + ++fails; + int64_t delay = (int64_t)((double)current * 2.0 * rand01()); + current = std::min(current + delay, maxAmount); + return current; + } +}; diff --git a/dependencies/discord-rpc/src/connection.h b/dependencies/discord-rpc/src/connection.h new file mode 100644 index 00000000..a8f99b9f --- /dev/null +++ b/dependencies/discord-rpc/src/connection.h @@ -0,0 +1,19 @@ +#pragma once + +// This is to wrap the platform specific kinds of connect/read/write. + +#include +#include + +// not really connectiony, but need per-platform +int GetProcessId(); + +struct BaseConnection { + static BaseConnection* Create(); + static void Destroy(BaseConnection*&); + bool isOpen{false}; + bool Open(); + bool Close(); + bool Write(const void* data, size_t length); + bool Read(void* data, size_t length); +}; diff --git a/dependencies/discord-rpc/src/connection_unix.cpp b/dependencies/discord-rpc/src/connection_unix.cpp new file mode 100644 index 00000000..85dace3c --- /dev/null +++ b/dependencies/discord-rpc/src/connection_unix.cpp @@ -0,0 +1,125 @@ +#include "connection.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +int GetProcessId() +{ + return ::getpid(); +} + +struct BaseConnectionUnix : public BaseConnection { + int sock{-1}; +}; + +static BaseConnectionUnix Connection; +static sockaddr_un PipeAddr{}; +#ifdef MSG_NOSIGNAL +static int MsgFlags = MSG_NOSIGNAL; +#else +static int MsgFlags = 0; +#endif + +static const char* GetTempPath() +{ + const char* temp = getenv("XDG_RUNTIME_DIR"); + temp = temp ? temp : getenv("TMPDIR"); + temp = temp ? temp : getenv("TMP"); + temp = temp ? temp : getenv("TEMP"); + temp = temp ? temp : "/tmp"; + return temp; +} + +/*static*/ BaseConnection* BaseConnection::Create() +{ + PipeAddr.sun_family = AF_UNIX; + return &Connection; +} + +/*static*/ void BaseConnection::Destroy(BaseConnection*& c) +{ + auto self = reinterpret_cast(c); + self->Close(); + c = nullptr; +} + +bool BaseConnection::Open() +{ + const char* tempPath = GetTempPath(); + auto self = reinterpret_cast(this); + self->sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (self->sock == -1) { + return false; + } + fcntl(self->sock, F_SETFL, O_NONBLOCK); +#ifdef SO_NOSIGPIPE + int optval = 1; + setsockopt(self->sock, SOL_SOCKET, SO_NOSIGPIPE, &optval, sizeof(optval)); +#endif + + for (int pipeNum = 0; pipeNum < 10; ++pipeNum) { + snprintf( + PipeAddr.sun_path, sizeof(PipeAddr.sun_path), "%s/discord-ipc-%d", tempPath, pipeNum); + int err = connect(self->sock, (const sockaddr*)&PipeAddr, sizeof(PipeAddr)); + if (err == 0) { + self->isOpen = true; + return true; + } + } + self->Close(); + return false; +} + +bool BaseConnection::Close() +{ + auto self = reinterpret_cast(this); + if (self->sock == -1) { + return false; + } + close(self->sock); + self->sock = -1; + self->isOpen = false; + return true; +} + +bool BaseConnection::Write(const void* data, size_t length) +{ + auto self = reinterpret_cast(this); + + if (self->sock == -1) { + return false; + } + + ssize_t sentBytes = send(self->sock, data, length, MsgFlags); + if (sentBytes < 0) { + Close(); + } + return sentBytes == (ssize_t)length; +} + +bool BaseConnection::Read(void* data, size_t length) +{ + auto self = reinterpret_cast(this); + + if (self->sock == -1) { + return false; + } + + int res = (int)recv(self->sock, data, length, MsgFlags); + if (res < 0) { + if (errno == EAGAIN) { + return false; + } + Close(); + } + else if (res == 0) { + Close(); + } + return res == (int)length; +} diff --git a/dependencies/discord-rpc/src/connection_win.cpp b/dependencies/discord-rpc/src/connection_win.cpp new file mode 100644 index 00000000..2dd2750c --- /dev/null +++ b/dependencies/discord-rpc/src/connection_win.cpp @@ -0,0 +1,128 @@ +#include "connection.h" + +#define WIN32_LEAN_AND_MEAN +#define NOMCX +#define NOSERVICE +#define NOIME +#include +#include + +int GetProcessId() +{ + return (int)::GetCurrentProcessId(); +} + +struct BaseConnectionWin : public BaseConnection { + HANDLE pipe{INVALID_HANDLE_VALUE}; +}; + +static BaseConnectionWin Connection; + +/*static*/ BaseConnection* BaseConnection::Create() +{ + return &Connection; +} + +/*static*/ void BaseConnection::Destroy(BaseConnection*& c) +{ + auto self = reinterpret_cast(c); + self->Close(); + c = nullptr; +} + +bool BaseConnection::Open() +{ + wchar_t pipeName[]{L"\\\\?\\pipe\\discord-ipc-0"}; + const size_t pipeDigit = sizeof(pipeName) / sizeof(wchar_t) - 2; + pipeName[pipeDigit] = L'0'; + auto self = reinterpret_cast(this); + for (;;) { + self->pipe = ::CreateFileW( + pipeName, GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr); + if (self->pipe != INVALID_HANDLE_VALUE) { + self->isOpen = true; + return true; + } + + auto lastError = GetLastError(); + if (lastError == ERROR_FILE_NOT_FOUND) { + if (pipeName[pipeDigit] < L'9') { + pipeName[pipeDigit]++; + continue; + } + } + else if (lastError == ERROR_PIPE_BUSY) { + if (!WaitNamedPipeW(pipeName, 10000)) { + return false; + } + continue; + } + return false; + } +} + +bool BaseConnection::Close() +{ + auto self = reinterpret_cast(this); + ::CloseHandle(self->pipe); + self->pipe = INVALID_HANDLE_VALUE; + self->isOpen = false; + return true; +} + +bool BaseConnection::Write(const void* data, size_t length) +{ + if (length == 0) { + return true; + } + auto self = reinterpret_cast(this); + assert(self); + if (!self) { + return false; + } + if (self->pipe == INVALID_HANDLE_VALUE) { + return false; + } + assert(data); + if (!data) { + return false; + } + const DWORD bytesLength = (DWORD)length; + DWORD bytesWritten = 0; + return ::WriteFile(self->pipe, data, bytesLength, &bytesWritten, nullptr) == TRUE && + bytesWritten == bytesLength; +} + +bool BaseConnection::Read(void* data, size_t length) +{ + assert(data); + if (!data) { + return false; + } + auto self = reinterpret_cast(this); + assert(self); + if (!self) { + return false; + } + if (self->pipe == INVALID_HANDLE_VALUE) { + return false; + } + DWORD bytesAvailable = 0; + if (::PeekNamedPipe(self->pipe, nullptr, 0, nullptr, &bytesAvailable, nullptr)) { + if (bytesAvailable >= length) { + DWORD bytesToRead = (DWORD)length; + DWORD bytesRead = 0; + if (::ReadFile(self->pipe, data, bytesToRead, &bytesRead, nullptr) == TRUE) { + assert(bytesToRead == bytesRead); + return true; + } + else { + Close(); + } + } + } + else { + Close(); + } + return false; +} diff --git a/dependencies/discord-rpc/src/discord_register_linux.cpp b/dependencies/discord-rpc/src/discord_register_linux.cpp new file mode 100644 index 00000000..dd92eea0 --- /dev/null +++ b/dependencies/discord-rpc/src/discord_register_linux.cpp @@ -0,0 +1,102 @@ +#include "discord_rpc.h" +#include "discord_register.h" +#include + +#include +#include +#include +#include +#include +#include + +static bool Mkdir(const char* path) +{ + int result = mkdir(path, 0755); + if (result == 0) { + return true; + } + if (errno == EEXIST) { + return true; + } + return false; +} + +// we want to register games so we can run them from Discord client as discord-:// +extern "C" DISCORD_EXPORT void Discord_Register(const char* applicationId, const char* command) +{ + // Add a desktop file and update some mime handlers so that xdg-open does the right thing. + + const char* home = getenv("HOME"); + if (!home) { + return; + } + + char exePath[1024]; + if (!command || !command[0]) { + ssize_t size = readlink("/proc/self/exe", exePath, sizeof(exePath)); + if (size <= 0 || size >= (ssize_t)sizeof(exePath)) { + return; + } + exePath[size] = '\0'; + command = exePath; + } + + const char* desktopFileFormat = "[Desktop Entry]\n" + "Name=Game %s\n" + "Exec=%s %%u\n" // note: it really wants that %u in there + "Type=Application\n" + "NoDisplay=true\n" + "Categories=Discord;Games;\n" + "MimeType=x-scheme-handler/discord-%s;\n"; + char desktopFile[2048]; + int fileLen = snprintf( + desktopFile, sizeof(desktopFile), desktopFileFormat, applicationId, command, applicationId); + if (fileLen <= 0) { + return; + } + + char desktopFilename[256]; + snprintf(desktopFilename, sizeof(desktopFilename), "/discord-%s.desktop", applicationId); + + char desktopFilePath[1024]; + snprintf(desktopFilePath, sizeof(desktopFilePath), "%s/.local", home); + if (!Mkdir(desktopFilePath)) { + return; + } + strcat(desktopFilePath, "/share"); + if (!Mkdir(desktopFilePath)) { + return; + } + strcat(desktopFilePath, "/applications"); + if (!Mkdir(desktopFilePath)) { + return; + } + strcat(desktopFilePath, desktopFilename); + + FILE* fp = fopen(desktopFilePath, "w"); + if (fp) { + fwrite(desktopFile, 1, fileLen, fp); + fclose(fp); + } + else { + return; + } + + char xdgMimeCommand[1024]; + snprintf(xdgMimeCommand, + sizeof(xdgMimeCommand), + "xdg-mime default discord-%s.desktop x-scheme-handler/discord-%s", + applicationId, + applicationId); + if (system(xdgMimeCommand) < 0) { + fprintf(stderr, "Failed to register mime handler\n"); + } +} + +extern "C" DISCORD_EXPORT void Discord_RegisterSteamGame(const char* applicationId, + const char* steamId) +{ + char command[256]; + sprintf(command, "xdg-open steam://rungameid/%s", steamId); + Discord_Register(applicationId, command); +} diff --git a/dependencies/discord-rpc/src/discord_register_osx.m b/dependencies/discord-rpc/src/discord_register_osx.m new file mode 100644 index 00000000..d7101028 --- /dev/null +++ b/dependencies/discord-rpc/src/discord_register_osx.m @@ -0,0 +1,80 @@ +#include +#include + +#import + +#include "discord_register.h" + +static void RegisterCommand(const char* applicationId, const char* command) +{ + // There does not appear to be a way to register arbitrary commands on OSX, so instead we'll save the command + // to a file in the Discord config path, and when it is needed, Discord can try to load the file there, open + // the command therein (will pass to js's window.open, so requires a url-like thing) + + // Note: will not work for sandboxed apps + NSString *home = NSHomeDirectory(); + if (!home) { + return; + } + + NSString *path = [[[[[[home stringByAppendingPathComponent:@"Library"] + stringByAppendingPathComponent:@"Application Support"] + stringByAppendingPathComponent:@"discord"] + stringByAppendingPathComponent:@"games"] + stringByAppendingPathComponent:[NSString stringWithUTF8String:applicationId]] + stringByAppendingPathExtension:@"json"]; + [[NSFileManager defaultManager] createDirectoryAtPath:[path stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:nil]; + + NSString *jsonBuffer = [NSString stringWithFormat:@"{\"command\": \"%s\"}", command]; + [jsonBuffer writeToFile:path atomically:NO encoding:NSUTF8StringEncoding error:nil]; +} + +static void RegisterURL(const char* applicationId) +{ + char url[256]; + snprintf(url, sizeof(url), "discord-%s", applicationId); + CFStringRef cfURL = CFStringCreateWithCString(NULL, url, kCFStringEncodingUTF8); + + NSString* myBundleId = [[NSBundle mainBundle] bundleIdentifier]; + if (!myBundleId) { + fprintf(stderr, "No bundle id found\n"); + return; + } + + NSURL* myURL = [[NSBundle mainBundle] bundleURL]; + if (!myURL) { + fprintf(stderr, "No bundle url found\n"); + return; + } + + OSStatus status = LSSetDefaultHandlerForURLScheme(cfURL, (__bridge CFStringRef)myBundleId); + if (status != noErr) { + fprintf(stderr, "Error in LSSetDefaultHandlerForURLScheme: %d\n", (int)status); + return; + } + + status = LSRegisterURL((__bridge CFURLRef)myURL, true); + if (status != noErr) { + fprintf(stderr, "Error in LSRegisterURL: %d\n", (int)status); + } +} + +void Discord_Register(const char* applicationId, const char* command) +{ + if (command) { + RegisterCommand(applicationId, command); + } + else { + // raii lite + @autoreleasepool { + RegisterURL(applicationId); + } + } +} + +void Discord_RegisterSteamGame(const char* applicationId, const char* steamId) +{ + char command[256]; + snprintf(command, 256, "steam://rungameid/%s", steamId); + Discord_Register(applicationId, command); +} diff --git a/dependencies/discord-rpc/src/discord_register_win.cpp b/dependencies/discord-rpc/src/discord_register_win.cpp new file mode 100644 index 00000000..0b1c4a13 --- /dev/null +++ b/dependencies/discord-rpc/src/discord_register_win.cpp @@ -0,0 +1,186 @@ +#include "discord_rpc.h" +#include "discord_register.h" + +#define WIN32_LEAN_AND_MEAN +#define NOMCX +#define NOSERVICE +#define NOIME +#include +#include +#include + +/** + * Updated fixes for MinGW and WinXP + * This block is written the way it does not involve changing the rest of the code + * Checked to be compiling + * 1) strsafe.h belongs to Windows SDK and cannot be added to MinGW + * #include guarded, functions redirected to substitutes + * 2) RegSetKeyValueW and LSTATUS are not declared in + * The entire function is rewritten + */ +#ifdef __MINGW32__ +#include +/// strsafe.h fixes +static HRESULT StringCbPrintfW(LPWSTR pszDest, size_t cbDest, LPCWSTR pszFormat, ...) +{ + HRESULT ret; + va_list va; + va_start(va, pszFormat); + cbDest /= 2; // Size is divided by 2 to convert from bytes to wide characters - causes segfault + // othervise + ret = vsnwprintf(pszDest, cbDest, pszFormat, va); + pszDest[cbDest - 1] = 0; // Terminate the string in case a buffer overflow; -1 will be returned + va_end(va); + return ret; +} +#else +#include +#include +#endif // __MINGW32__ + +/// winreg.h fixes +#ifndef LSTATUS +#define LSTATUS LONG +#endif +#ifdef RegSetKeyValueW +#undefine RegSetKeyValueW +#endif +#define RegSetKeyValueW regset +static LSTATUS regset(HKEY hkey, + LPCWSTR subkey, + LPCWSTR name, + DWORD type, + const void* data, + DWORD len) +{ + HKEY htkey = hkey, hsubkey = nullptr; + LSTATUS ret; + if (subkey && subkey[0]) { + if ((ret = RegCreateKeyExW(hkey, subkey, 0, 0, 0, KEY_ALL_ACCESS, 0, &hsubkey, 0)) != + ERROR_SUCCESS) + return ret; + htkey = hsubkey; + } + ret = RegSetValueExW(htkey, name, 0, type, (const BYTE*)data, len); + if (hsubkey && hsubkey != hkey) + RegCloseKey(hsubkey); + return ret; +} + +static void Discord_RegisterW(const wchar_t* applicationId, const wchar_t* command) +{ + // https://msdn.microsoft.com/en-us/library/aa767914(v=vs.85).aspx + // we want to register games so we can run them as discord-:// + // Update the HKEY_CURRENT_USER, because it doesn't seem to require special permissions. + + wchar_t exeFilePath[MAX_PATH]; + DWORD exeLen = GetModuleFileNameW(nullptr, exeFilePath, MAX_PATH); + wchar_t openCommand[1024]; + + if (command && command[0]) { + StringCbPrintfW(openCommand, sizeof(openCommand), L"%s", command); + } + else { + // StringCbCopyW(openCommand, sizeof(openCommand), exeFilePath); + StringCbPrintfW(openCommand, sizeof(openCommand), L"%s", exeFilePath); + } + + wchar_t protocolName[64]; + StringCbPrintfW(protocolName, sizeof(protocolName), L"discord-%s", applicationId); + wchar_t protocolDescription[128]; + StringCbPrintfW( + protocolDescription, sizeof(protocolDescription), L"URL:Run game %s protocol", applicationId); + wchar_t urlProtocol = 0; + + wchar_t keyName[256]; + StringCbPrintfW(keyName, sizeof(keyName), L"Software\\Classes\\%s", protocolName); + HKEY key; + auto status = + RegCreateKeyExW(HKEY_CURRENT_USER, keyName, 0, nullptr, 0, KEY_WRITE, nullptr, &key, nullptr); + if (status != ERROR_SUCCESS) { + fprintf(stderr, "Error creating key\n"); + return; + } + DWORD len; + LSTATUS result; + len = (DWORD)lstrlenW(protocolDescription) + 1; + result = + RegSetKeyValueW(key, nullptr, nullptr, REG_SZ, protocolDescription, len * sizeof(wchar_t)); + if (FAILED(result)) { + fprintf(stderr, "Error writing description\n"); + } + + len = (DWORD)lstrlenW(protocolDescription) + 1; + result = RegSetKeyValueW(key, nullptr, L"URL Protocol", REG_SZ, &urlProtocol, sizeof(wchar_t)); + if (FAILED(result)) { + fprintf(stderr, "Error writing description\n"); + } + + result = RegSetKeyValueW( + key, L"DefaultIcon", nullptr, REG_SZ, exeFilePath, (exeLen + 1) * sizeof(wchar_t)); + if (FAILED(result)) { + fprintf(stderr, "Error writing icon\n"); + } + + len = (DWORD)lstrlenW(openCommand) + 1; + result = RegSetKeyValueW( + key, L"shell\\open\\command", nullptr, REG_SZ, openCommand, len * sizeof(wchar_t)); + if (FAILED(result)) { + fprintf(stderr, "Error writing command\n"); + } + RegCloseKey(key); +} + +extern "C" DISCORD_EXPORT void Discord_Register(const char* applicationId, const char* command) +{ + wchar_t appId[32]; + MultiByteToWideChar(CP_UTF8, 0, applicationId, -1, appId, 32); + + wchar_t openCommand[1024]; + const wchar_t* wcommand = nullptr; + if (command && command[0]) { + const auto commandBufferLen = sizeof(openCommand) / sizeof(*openCommand); + MultiByteToWideChar(CP_UTF8, 0, command, -1, openCommand, commandBufferLen); + wcommand = openCommand; + } + + Discord_RegisterW(appId, wcommand); +} + +extern "C" DISCORD_EXPORT void Discord_RegisterSteamGame(const char* applicationId, + const char* steamId) +{ + wchar_t appId[32]; + MultiByteToWideChar(CP_UTF8, 0, applicationId, -1, appId, 32); + + wchar_t wSteamId[32]; + MultiByteToWideChar(CP_UTF8, 0, steamId, -1, wSteamId, 32); + + HKEY key; + auto status = RegOpenKeyExW(HKEY_CURRENT_USER, L"Software\\Valve\\Steam", 0, KEY_READ, &key); + if (status != ERROR_SUCCESS) { + fprintf(stderr, "Error opening Steam key\n"); + return; + } + + wchar_t steamPath[MAX_PATH]; + DWORD pathBytes = sizeof(steamPath); + status = RegQueryValueExW(key, L"SteamExe", nullptr, nullptr, (BYTE*)steamPath, &pathBytes); + RegCloseKey(key); + if (status != ERROR_SUCCESS || pathBytes < 1) { + fprintf(stderr, "Error reading SteamExe key\n"); + return; + } + + DWORD pathChars = pathBytes / sizeof(wchar_t); + for (DWORD i = 0; i < pathChars; ++i) { + if (steamPath[i] == L'/') { + steamPath[i] = L'\\'; + } + } + + wchar_t command[1024]; + StringCbPrintfW(command, sizeof(command), L"\"%s\" steam://rungameid/%s", steamPath, wSteamId); + + Discord_RegisterW(appId, command); +} diff --git a/dependencies/discord-rpc/src/discord_rpc.cpp b/dependencies/discord-rpc/src/discord_rpc.cpp new file mode 100644 index 00000000..03924538 --- /dev/null +++ b/dependencies/discord-rpc/src/discord_rpc.cpp @@ -0,0 +1,504 @@ +#include "discord_rpc.h" + +#include "backoff.h" +#include "discord_register.h" +#include "msg_queue.h" +#include "rpc_connection.h" +#include "serialization.h" + +#include +#include +#include + +#ifndef DISCORD_DISABLE_IO_THREAD +#include +#include +#endif + +constexpr size_t MaxMessageSize{16 * 1024}; +constexpr size_t MessageQueueSize{8}; +constexpr size_t JoinQueueSize{8}; + +struct QueuedMessage { + size_t length; + char buffer[MaxMessageSize]; + + void Copy(const QueuedMessage& other) + { + length = other.length; + if (length) { + memcpy(buffer, other.buffer, length); + } + } +}; + +struct User { + // snowflake (64bit int), turned into a ascii decimal string, at most 20 chars +1 null + // terminator = 21 + char userId[32]; + // 32 unicode glyphs is max name size => 4 bytes per glyph in the worst case, +1 for null + // terminator = 129 + char username[344]; + // 4 decimal digits + 1 null terminator = 5 + char discriminator[8]; + // optional 'a_' + md5 hex digest (32 bytes) + null terminator = 35 + char avatar[128]; + // Rounded way up because I'm paranoid about games breaking from future changes in these sizes +}; + +static RpcConnection* Connection{nullptr}; +static DiscordEventHandlers QueuedHandlers{}; +static DiscordEventHandlers Handlers{}; +static std::atomic_bool WasJustConnected{false}; +static std::atomic_bool WasJustDisconnected{false}; +static std::atomic_bool GotErrorMessage{false}; +static std::atomic_bool WasJoinGame{false}; +static std::atomic_bool WasSpectateGame{false}; +static std::atomic_bool UpdatePresence{false}; +static char JoinGameSecret[256]; +static char SpectateGameSecret[256]; +static int LastErrorCode{0}; +static char LastErrorMessage[256]; +static int LastDisconnectErrorCode{0}; +static char LastDisconnectErrorMessage[256]; +static std::mutex PresenceMutex; +static std::mutex HandlerMutex; +static QueuedMessage QueuedPresence{}; +static MsgQueue SendQueue; +static MsgQueue JoinAskQueue; +static User connectedUser; + +// We want to auto connect, and retry on failure, but not as fast as possible. This does expoential +// backoff from 0.5 seconds to 1 minute +static Backoff ReconnectTimeMs(500, 60 * 1000); +static auto NextConnect = std::chrono::system_clock::now(); +static int Pid{0}; +static int Nonce{1}; + +#ifndef DISCORD_DISABLE_IO_THREAD +static void Discord_UpdateConnection(void); +class IoThreadHolder { +private: + std::atomic_bool keepRunning{true}; + std::mutex waitForIOMutex; + std::condition_variable waitForIOActivity; + std::thread ioThread; + +public: + void Start() + { + keepRunning.store(true); + ioThread = std::thread([&]() { + const std::chrono::duration maxWait{500LL}; + Discord_UpdateConnection(); + while (keepRunning.load()) { + std::unique_lock lock(waitForIOMutex); + waitForIOActivity.wait_for(lock, maxWait); + Discord_UpdateConnection(); + } + }); + } + + void Notify() { waitForIOActivity.notify_all(); } + + void Stop() + { + keepRunning.exchange(false); + Notify(); + if (ioThread.joinable()) { + ioThread.join(); + } + } + + ~IoThreadHolder() { Stop(); } +}; +#else +class IoThreadHolder { +public: + void Start() {} + void Stop() {} + void Notify() {} +}; +#endif // DISCORD_DISABLE_IO_THREAD +static IoThreadHolder* IoThread{nullptr}; + +static void UpdateReconnectTime() +{ + NextConnect = std::chrono::system_clock::now() + + std::chrono::duration{ReconnectTimeMs.nextDelay()}; +} + +#ifdef DISCORD_DISABLE_IO_THREAD +extern "C" DISCORD_EXPORT void Discord_UpdateConnection(void) +#else +static void Discord_UpdateConnection(void) +#endif +{ + if (!Connection) { + return; + } + + if (!Connection->IsOpen()) { + if (std::chrono::system_clock::now() >= NextConnect) { + UpdateReconnectTime(); + Connection->Open(); + } + } + else { + // reads + + for (;;) { + JsonDocument message; + + if (!Connection->Read(message)) { + break; + } + + const char* evtName = GetStrMember(&message, "evt"); + const char* nonce = GetStrMember(&message, "nonce"); + + if (nonce) { + // in responses only -- should use to match up response when needed. + + if (evtName && strcmp(evtName, "ERROR") == 0) { + auto data = GetObjMember(&message, "data"); + LastErrorCode = GetIntMember(data, "code"); + StringCopy(LastErrorMessage, GetStrMember(data, "message", "")); + GotErrorMessage.store(true); + } + } + else { + // should have evt == name of event, optional data + if (evtName == nullptr) { + continue; + } + + auto data = GetObjMember(&message, "data"); + + if (strcmp(evtName, "ACTIVITY_JOIN") == 0) { + auto secret = GetStrMember(data, "secret"); + if (secret) { + StringCopy(JoinGameSecret, secret); + WasJoinGame.store(true); + } + } + else if (strcmp(evtName, "ACTIVITY_SPECTATE") == 0) { + auto secret = GetStrMember(data, "secret"); + if (secret) { + StringCopy(SpectateGameSecret, secret); + WasSpectateGame.store(true); + } + } + else if (strcmp(evtName, "ACTIVITY_JOIN_REQUEST") == 0) { + auto user = GetObjMember(data, "user"); + auto userId = GetStrMember(user, "id"); + auto username = GetStrMember(user, "username"); + auto avatar = GetStrMember(user, "avatar"); + auto joinReq = JoinAskQueue.GetNextAddMessage(); + if (userId && username && joinReq) { + StringCopy(joinReq->userId, userId); + StringCopy(joinReq->username, username); + auto discriminator = GetStrMember(user, "discriminator"); + if (discriminator) { + StringCopy(joinReq->discriminator, discriminator); + } + if (avatar) { + StringCopy(joinReq->avatar, avatar); + } + else { + joinReq->avatar[0] = 0; + } + JoinAskQueue.CommitAdd(); + } + } + } + } + + // writes + if (UpdatePresence.exchange(false) && QueuedPresence.length) { + QueuedMessage local; + { + std::lock_guard guard(PresenceMutex); + local.Copy(QueuedPresence); + } + if (!Connection->Write(local.buffer, local.length)) { + // if we fail to send, requeue + std::lock_guard guard(PresenceMutex); + QueuedPresence.Copy(local); + UpdatePresence.exchange(true); + } + } + + while (SendQueue.HavePendingSends()) { + auto qmessage = SendQueue.GetNextSendMessage(); + Connection->Write(qmessage->buffer, qmessage->length); + SendQueue.CommitSend(); + } + } +} + +static void SignalIOActivity() +{ + if (IoThread != nullptr) { + IoThread->Notify(); + } +} + +static bool RegisterForEvent(const char* evtName) +{ + auto qmessage = SendQueue.GetNextAddMessage(); + if (qmessage) { + qmessage->length = + JsonWriteSubscribeCommand(qmessage->buffer, sizeof(qmessage->buffer), Nonce++, evtName); + SendQueue.CommitAdd(); + SignalIOActivity(); + return true; + } + return false; +} + +static bool DeregisterForEvent(const char* evtName) +{ + auto qmessage = SendQueue.GetNextAddMessage(); + if (qmessage) { + qmessage->length = + JsonWriteUnsubscribeCommand(qmessage->buffer, sizeof(qmessage->buffer), Nonce++, evtName); + SendQueue.CommitAdd(); + SignalIOActivity(); + return true; + } + return false; +} + +extern "C" DISCORD_EXPORT void Discord_Initialize(const char* applicationId, + DiscordEventHandlers* handlers, + int autoRegister, + const char* optionalSteamId) +{ + IoThread = new (std::nothrow) IoThreadHolder(); + if (IoThread == nullptr) { + return; + } + + if (autoRegister) { + if (optionalSteamId && optionalSteamId[0]) { + Discord_RegisterSteamGame(applicationId, optionalSteamId); + } + else { + Discord_Register(applicationId, nullptr); + } + } + + Pid = GetProcessId(); + + { + std::lock_guard guard(HandlerMutex); + + if (handlers) { + QueuedHandlers = *handlers; + } + else { + QueuedHandlers = {}; + } + + Handlers = {}; + } + + if (Connection) { + return; + } + + Connection = RpcConnection::Create(applicationId); + Connection->onConnect = [](JsonDocument& readyMessage) { + Discord_UpdateHandlers(&QueuedHandlers); + if (QueuedPresence.length > 0) { + UpdatePresence.exchange(true); + SignalIOActivity(); + } + auto data = GetObjMember(&readyMessage, "data"); + auto user = GetObjMember(data, "user"); + auto userId = GetStrMember(user, "id"); + auto username = GetStrMember(user, "username"); + auto avatar = GetStrMember(user, "avatar"); + if (userId && username) { + StringCopy(connectedUser.userId, userId); + StringCopy(connectedUser.username, username); + auto discriminator = GetStrMember(user, "discriminator"); + if (discriminator) { + StringCopy(connectedUser.discriminator, discriminator); + } + if (avatar) { + StringCopy(connectedUser.avatar, avatar); + } + else { + connectedUser.avatar[0] = 0; + } + } + WasJustConnected.exchange(true); + ReconnectTimeMs.reset(); + }; + Connection->onDisconnect = [](int err, const char* message) { + LastDisconnectErrorCode = err; + StringCopy(LastDisconnectErrorMessage, message); + WasJustDisconnected.exchange(true); + UpdateReconnectTime(); + }; + + IoThread->Start(); +} + +extern "C" DISCORD_EXPORT void Discord_Shutdown(void) +{ + if (!Connection) { + return; + } + Connection->onConnect = nullptr; + Connection->onDisconnect = nullptr; + Handlers = {}; + QueuedPresence.length = 0; + UpdatePresence.exchange(false); + if (IoThread != nullptr) { + IoThread->Stop(); + delete IoThread; + IoThread = nullptr; + } + + RpcConnection::Destroy(Connection); +} + +extern "C" DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence* presence) +{ + { + std::lock_guard guard(PresenceMutex); + QueuedPresence.length = JsonWriteRichPresenceObj( + QueuedPresence.buffer, sizeof(QueuedPresence.buffer), Nonce++, Pid, presence); + UpdatePresence.exchange(true); + } + SignalIOActivity(); +} + +extern "C" DISCORD_EXPORT void Discord_ClearPresence(void) +{ + Discord_UpdatePresence(nullptr); +} + +extern "C" DISCORD_EXPORT void Discord_Respond(const char* userId, /* DISCORD_REPLY_ */ int reply) +{ + // if we are not connected, let's not batch up stale messages for later + if (!Connection || !Connection->IsOpen()) { + return; + } + auto qmessage = SendQueue.GetNextAddMessage(); + if (qmessage) { + qmessage->length = + JsonWriteJoinReply(qmessage->buffer, sizeof(qmessage->buffer), userId, reply, Nonce++); + SendQueue.CommitAdd(); + SignalIOActivity(); + } +} + +extern "C" DISCORD_EXPORT void Discord_RunCallbacks(void) +{ + // Note on some weirdness: internally we might connect, get other signals, disconnect any number + // of times inbetween calls here. Externally, we want the sequence to seem sane, so any other + // signals are book-ended by calls to ready and disconnect. + + if (!Connection) { + return; + } + + bool wasDisconnected = WasJustDisconnected.exchange(false); + bool isConnected = Connection->IsOpen(); + + if (isConnected) { + // if we are connected, disconnect cb first + std::lock_guard guard(HandlerMutex); + if (wasDisconnected && Handlers.disconnected) { + Handlers.disconnected(LastDisconnectErrorCode, LastDisconnectErrorMessage); + } + } + + if (WasJustConnected.exchange(false)) { + std::lock_guard guard(HandlerMutex); + if (Handlers.ready) { + DiscordUser du{connectedUser.userId, + connectedUser.username, + connectedUser.discriminator, + connectedUser.avatar}; + Handlers.ready(&du); + } + } + + if (GotErrorMessage.exchange(false)) { + std::lock_guard guard(HandlerMutex); + if (Handlers.errored) { + Handlers.errored(LastErrorCode, LastErrorMessage); + } + } + + if (WasJoinGame.exchange(false)) { + std::lock_guard guard(HandlerMutex); + if (Handlers.joinGame) { + Handlers.joinGame(JoinGameSecret); + } + } + + if (WasSpectateGame.exchange(false)) { + std::lock_guard guard(HandlerMutex); + if (Handlers.spectateGame) { + Handlers.spectateGame(SpectateGameSecret); + } + } + + // Right now this batches up any requests and sends them all in a burst; I could imagine a world + // where the implementer would rather sequentially accept/reject each one before the next invite + // is sent. I left it this way because I could also imagine wanting to process these all and + // maybe show them in one common dialog and/or start fetching the avatars in parallel, and if + // not it should be trivial for the implementer to make a queue themselves. + while (JoinAskQueue.HavePendingSends()) { + auto req = JoinAskQueue.GetNextSendMessage(); + { + std::lock_guard guard(HandlerMutex); + if (Handlers.joinRequest) { + DiscordUser du{req->userId, req->username, req->discriminator, req->avatar}; + Handlers.joinRequest(&du); + } + } + JoinAskQueue.CommitSend(); + } + + if (!isConnected) { + // if we are not connected, disconnect message last + std::lock_guard guard(HandlerMutex); + if (wasDisconnected && Handlers.disconnected) { + Handlers.disconnected(LastDisconnectErrorCode, LastDisconnectErrorMessage); + } + } +} + +extern "C" DISCORD_EXPORT void Discord_UpdateHandlers(DiscordEventHandlers* newHandlers) +{ + if (newHandlers) { +#define HANDLE_EVENT_REGISTRATION(handler_name, event) \ + if (!Handlers.handler_name && newHandlers->handler_name) { \ + RegisterForEvent(event); \ + } \ + else if (Handlers.handler_name && !newHandlers->handler_name) { \ + DeregisterForEvent(event); \ + } + + std::lock_guard guard(HandlerMutex); + HANDLE_EVENT_REGISTRATION(joinGame, "ACTIVITY_JOIN") + HANDLE_EVENT_REGISTRATION(spectateGame, "ACTIVITY_SPECTATE") + HANDLE_EVENT_REGISTRATION(joinRequest, "ACTIVITY_JOIN_REQUEST") + +#undef HANDLE_EVENT_REGISTRATION + + Handlers = *newHandlers; + } + else { + std::lock_guard guard(HandlerMutex); + Handlers = {}; + } + return; +} diff --git a/dependencies/discord-rpc/src/dllmain.cpp b/dependencies/discord-rpc/src/dllmain.cpp new file mode 100644 index 00000000..fbfc2950 --- /dev/null +++ b/dependencies/discord-rpc/src/dllmain.cpp @@ -0,0 +1,8 @@ +#include + +// outsmart GCC's missing-declarations warning +BOOL WINAPI DllMain(HMODULE, DWORD, LPVOID); +BOOL WINAPI DllMain(HMODULE, DWORD, LPVOID) +{ + return TRUE; +} diff --git a/dependencies/discord-rpc/src/msg_queue.h b/dependencies/discord-rpc/src/msg_queue.h new file mode 100644 index 00000000..77f380e7 --- /dev/null +++ b/dependencies/discord-rpc/src/msg_queue.h @@ -0,0 +1,36 @@ +#pragma once + +#include + +// A simple queue. No locks, but only works with a single thread as producer and a single thread as +// a consumer. Mutex up as needed. + +template +class MsgQueue { + ElementType queue_[QueueSize]; + std::atomic_uint nextAdd_{0}; + std::atomic_uint nextSend_{0}; + std::atomic_uint pendingSends_{0}; + +public: + MsgQueue() {} + + ElementType* GetNextAddMessage() + { + // if we are falling behind, bail + if (pendingSends_.load() >= QueueSize) { + return nullptr; + } + auto index = (nextAdd_++) % QueueSize; + return &queue_[index]; + } + void CommitAdd() { ++pendingSends_; } + + bool HavePendingSends() const { return pendingSends_.load() != 0; } + ElementType* GetNextSendMessage() + { + auto index = (nextSend_++) % QueueSize; + return &queue_[index]; + } + void CommitSend() { --pendingSends_; } +}; diff --git a/dependencies/discord-rpc/src/rpc_connection.cpp b/dependencies/discord-rpc/src/rpc_connection.cpp new file mode 100644 index 00000000..09331621 --- /dev/null +++ b/dependencies/discord-rpc/src/rpc_connection.cpp @@ -0,0 +1,137 @@ +#include "rpc_connection.h" +#include "serialization.h" + +#include + +static const int RpcVersion = 1; +static RpcConnection Instance; + +/*static*/ RpcConnection* RpcConnection::Create(const char* applicationId) +{ + Instance.connection = BaseConnection::Create(); + StringCopy(Instance.appId, applicationId); + return &Instance; +} + +/*static*/ void RpcConnection::Destroy(RpcConnection*& c) +{ + c->Close(); + BaseConnection::Destroy(c->connection); + c = nullptr; +} + +void RpcConnection::Open() +{ + if (state == State::Connected) { + return; + } + + if (state == State::Disconnected && !connection->Open()) { + return; + } + + if (state == State::SentHandshake) { + JsonDocument message; + if (Read(message)) { + auto cmd = GetStrMember(&message, "cmd"); + auto evt = GetStrMember(&message, "evt"); + if (cmd && evt && !strcmp(cmd, "DISPATCH") && !strcmp(evt, "READY")) { + state = State::Connected; + if (onConnect) { + onConnect(message); + } + } + } + } + else { + sendFrame.opcode = Opcode::Handshake; + sendFrame.length = (uint32_t)JsonWriteHandshakeObj( + sendFrame.message, sizeof(sendFrame.message), RpcVersion, appId); + + if (connection->Write(&sendFrame, sizeof(MessageFrameHeader) + sendFrame.length)) { + state = State::SentHandshake; + } + else { + Close(); + } + } +} + +void RpcConnection::Close() +{ + if (onDisconnect && (state == State::Connected || state == State::SentHandshake)) { + onDisconnect(lastErrorCode, lastErrorMessage); + } + connection->Close(); + state = State::Disconnected; +} + +bool RpcConnection::Write(const void* data, size_t length) +{ + sendFrame.opcode = Opcode::Frame; + memcpy(sendFrame.message, data, length); + sendFrame.length = (uint32_t)length; + if (!connection->Write(&sendFrame, sizeof(MessageFrameHeader) + length)) { + Close(); + return false; + } + return true; +} + +bool RpcConnection::Read(JsonDocument& message) +{ + if (state != State::Connected && state != State::SentHandshake) { + return false; + } + MessageFrame readFrame; + for (;;) { + bool didRead = connection->Read(&readFrame, sizeof(MessageFrameHeader)); + if (!didRead) { + if (!connection->isOpen) { + lastErrorCode = (int)ErrorCode::PipeClosed; + StringCopy(lastErrorMessage, "Pipe closed"); + Close(); + } + return false; + } + + if (readFrame.length > 0) { + didRead = connection->Read(readFrame.message, readFrame.length); + if (!didRead) { + lastErrorCode = (int)ErrorCode::ReadCorrupt; + StringCopy(lastErrorMessage, "Partial data in frame"); + Close(); + return false; + } + readFrame.message[readFrame.length] = 0; + } + + switch (readFrame.opcode) { + case Opcode::Close: { + message.ParseInsitu(readFrame.message); + lastErrorCode = GetIntMember(&message, "code"); + StringCopy(lastErrorMessage, GetStrMember(&message, "message", "")); + Close(); + return false; + } + case Opcode::Frame: + message.ParseInsitu(readFrame.message); + return true; + case Opcode::Ping: + readFrame.opcode = Opcode::Pong; + if (!connection->Write(&readFrame, sizeof(MessageFrameHeader) + readFrame.length)) { + Close(); + } + break; + case Opcode::Pong: + break; + case Opcode::Handshake: + default: + // something bad happened + lastErrorCode = (int)ErrorCode::ReadCorrupt; + StringCopy(lastErrorMessage, "Bad ipc frame"); + Close(); + return false; + } + } +} diff --git a/dependencies/discord-rpc/src/rpc_connection.h b/dependencies/discord-rpc/src/rpc_connection.h new file mode 100644 index 00000000..bbdd05c7 --- /dev/null +++ b/dependencies/discord-rpc/src/rpc_connection.h @@ -0,0 +1,59 @@ +#pragma once + +#include "connection.h" +#include "serialization.h" + +// I took this from the buffer size libuv uses for named pipes; I suspect ours would usually be much +// smaller. +constexpr size_t MaxRpcFrameSize = 64 * 1024; + +struct RpcConnection { + enum class ErrorCode : int { + Success = 0, + PipeClosed = 1, + ReadCorrupt = 2, + }; + + enum class Opcode : uint32_t { + Handshake = 0, + Frame = 1, + Close = 2, + Ping = 3, + Pong = 4, + }; + + struct MessageFrameHeader { + Opcode opcode; + uint32_t length; + }; + + struct MessageFrame : public MessageFrameHeader { + char message[MaxRpcFrameSize - sizeof(MessageFrameHeader)]; + }; + + enum class State : uint32_t { + Disconnected, + SentHandshake, + AwaitingResponse, + Connected, + }; + + BaseConnection* connection{nullptr}; + State state{State::Disconnected}; + void (*onConnect)(JsonDocument& message){nullptr}; + void (*onDisconnect)(int errorCode, const char* message){nullptr}; + char appId[64]{}; + int lastErrorCode{0}; + char lastErrorMessage[256]{}; + RpcConnection::MessageFrame sendFrame; + + static RpcConnection* Create(const char* applicationId); + static void Destroy(RpcConnection*&); + + inline bool IsOpen() const { return state == State::Connected; } + + void Open(); + void Close(); + bool Write(const void* data, size_t length); + bool Read(JsonDocument& message); +}; diff --git a/dependencies/discord-rpc/src/serialization.cpp b/dependencies/discord-rpc/src/serialization.cpp new file mode 100644 index 00000000..70efa637 --- /dev/null +++ b/dependencies/discord-rpc/src/serialization.cpp @@ -0,0 +1,250 @@ +#include "serialization.h" +#include "connection.h" +#include "discord_rpc.h" + +template +void NumberToString(char* dest, T number) +{ + if (!number) { + *dest++ = '0'; + *dest++ = 0; + return; + } + if (number < 0) { + *dest++ = '-'; + number = -number; + } + char temp[32]; + int place = 0; + while (number) { + auto digit = number % 10; + number = number / 10; + temp[place++] = '0' + (char)digit; + } + for (--place; place >= 0; --place) { + *dest++ = temp[place]; + } + *dest = 0; +} + +// it's ever so slightly faster to not have to strlen the key +template +void WriteKey(JsonWriter& w, T& k) +{ + w.Key(k, sizeof(T) - 1); +} + +struct WriteObject { + JsonWriter& writer; + WriteObject(JsonWriter& w) + : writer(w) + { + writer.StartObject(); + } + template + WriteObject(JsonWriter& w, T& name) + : writer(w) + { + WriteKey(writer, name); + writer.StartObject(); + } + ~WriteObject() { writer.EndObject(); } +}; + +struct WriteArray { + JsonWriter& writer; + template + WriteArray(JsonWriter& w, T& name) + : writer(w) + { + WriteKey(writer, name); + writer.StartArray(); + } + ~WriteArray() { writer.EndArray(); } +}; + +template +void WriteOptionalString(JsonWriter& w, T& k, const char* value) +{ + if (value && value[0]) { + w.Key(k, sizeof(T) - 1); + w.String(value); + } +} + +static void JsonWriteNonce(JsonWriter& writer, int nonce) +{ + WriteKey(writer, "nonce"); + char nonceBuffer[32]; + NumberToString(nonceBuffer, nonce); + writer.String(nonceBuffer); +} + +size_t JsonWriteRichPresenceObj(char* dest, + size_t maxLen, + int nonce, + int pid, + const DiscordRichPresence* presence) +{ + JsonWriter writer(dest, maxLen); + + { + WriteObject top(writer); + + JsonWriteNonce(writer, nonce); + + WriteKey(writer, "cmd"); + writer.String("SET_ACTIVITY"); + + { + WriteObject args(writer, "args"); + + WriteKey(writer, "pid"); + writer.Int(pid); + + if (presence != nullptr) { + WriteObject activity(writer, "activity"); + + WriteOptionalString(writer, "state", presence->state); + WriteOptionalString(writer, "details", presence->details); + + if (presence->startTimestamp || presence->endTimestamp) { + WriteObject timestamps(writer, "timestamps"); + + if (presence->startTimestamp) { + WriteKey(writer, "start"); + writer.Int64(presence->startTimestamp); + } + + if (presence->endTimestamp) { + WriteKey(writer, "end"); + writer.Int64(presence->endTimestamp); + } + } + + if ((presence->largeImageKey && presence->largeImageKey[0]) || + (presence->largeImageText && presence->largeImageText[0]) || + (presence->smallImageKey && presence->smallImageKey[0]) || + (presence->smallImageText && presence->smallImageText[0])) { + WriteObject assets(writer, "assets"); + WriteOptionalString(writer, "large_image", presence->largeImageKey); + WriteOptionalString(writer, "large_text", presence->largeImageText); + WriteOptionalString(writer, "small_image", presence->smallImageKey); + WriteOptionalString(writer, "small_text", presence->smallImageText); + } + + if ((presence->partyId && presence->partyId[0]) || presence->partySize || + presence->partyMax || presence->partyPrivacy) { + WriteObject party(writer, "party"); + WriteOptionalString(writer, "id", presence->partyId); + if (presence->partySize && presence->partyMax) { + WriteArray size(writer, "size"); + writer.Int(presence->partySize); + writer.Int(presence->partyMax); + } + + if (presence->partyPrivacy) { + WriteKey(writer, "privacy"); + writer.Int(presence->partyPrivacy); + } + } + + if ((presence->matchSecret && presence->matchSecret[0]) || + (presence->joinSecret && presence->joinSecret[0]) || + (presence->spectateSecret && presence->spectateSecret[0])) { + WriteObject secrets(writer, "secrets"); + WriteOptionalString(writer, "match", presence->matchSecret); + WriteOptionalString(writer, "join", presence->joinSecret); + WriteOptionalString(writer, "spectate", presence->spectateSecret); + } + + writer.Key("instance"); + writer.Bool(presence->instance != 0); + } + } + } + + return writer.Size(); +} + +size_t JsonWriteHandshakeObj(char* dest, size_t maxLen, int version, const char* applicationId) +{ + JsonWriter writer(dest, maxLen); + + { + WriteObject obj(writer); + WriteKey(writer, "v"); + writer.Int(version); + WriteKey(writer, "client_id"); + writer.String(applicationId); + } + + return writer.Size(); +} + +size_t JsonWriteSubscribeCommand(char* dest, size_t maxLen, int nonce, const char* evtName) +{ + JsonWriter writer(dest, maxLen); + + { + WriteObject obj(writer); + + JsonWriteNonce(writer, nonce); + + WriteKey(writer, "cmd"); + writer.String("SUBSCRIBE"); + + WriteKey(writer, "evt"); + writer.String(evtName); + } + + return writer.Size(); +} + +size_t JsonWriteUnsubscribeCommand(char* dest, size_t maxLen, int nonce, const char* evtName) +{ + JsonWriter writer(dest, maxLen); + + { + WriteObject obj(writer); + + JsonWriteNonce(writer, nonce); + + WriteKey(writer, "cmd"); + writer.String("UNSUBSCRIBE"); + + WriteKey(writer, "evt"); + writer.String(evtName); + } + + return writer.Size(); +} + +size_t JsonWriteJoinReply(char* dest, size_t maxLen, const char* userId, int reply, int nonce) +{ + JsonWriter writer(dest, maxLen); + + { + WriteObject obj(writer); + + WriteKey(writer, "cmd"); + if (reply == DISCORD_REPLY_YES) { + writer.String("SEND_ACTIVITY_JOIN_INVITE"); + } + else { + writer.String("CLOSE_ACTIVITY_JOIN_REQUEST"); + } + + WriteKey(writer, "args"); + { + WriteObject args(writer); + + WriteKey(writer, "user_id"); + writer.String(userId); + } + + JsonWriteNonce(writer, nonce); + } + + return writer.Size(); +} diff --git a/dependencies/discord-rpc/src/serialization.h b/dependencies/discord-rpc/src/serialization.h new file mode 100644 index 00000000..9c462dc2 --- /dev/null +++ b/dependencies/discord-rpc/src/serialization.h @@ -0,0 +1,215 @@ +#pragma once + +#include + +#ifndef __MINGW32__ +#pragma warning(push) + +#pragma warning(disable : 4061) // enum is not explicitly handled by a case label +#pragma warning(disable : 4365) // signed/unsigned mismatch +#pragma warning(disable : 4464) // relative include path contains +#pragma warning(disable : 4668) // is not defined as a preprocessor macro +#pragma warning(disable : 6313) // Incorrect operator +#endif // __MINGW32__ + +#include "rapidjson/document.h" +#include "rapidjson/stringbuffer.h" +#include "rapidjson/writer.h" + +#ifndef __MINGW32__ +#pragma warning(pop) +#endif // __MINGW32__ + +// if only there was a standard library function for this +template +inline size_t StringCopy(char (&dest)[Len], const char* src) +{ + if (!src || !Len) { + return 0; + } + size_t copied; + char* out = dest; + for (copied = 1; *src && copied < Len; ++copied) { + *out++ = *src++; + } + *out = 0; + return copied - 1; +} + +size_t JsonWriteHandshakeObj(char* dest, size_t maxLen, int version, const char* applicationId); + +// Commands +struct DiscordRichPresence; +size_t JsonWriteRichPresenceObj(char* dest, + size_t maxLen, + int nonce, + int pid, + const DiscordRichPresence* presence); +size_t JsonWriteSubscribeCommand(char* dest, size_t maxLen, int nonce, const char* evtName); + +size_t JsonWriteUnsubscribeCommand(char* dest, size_t maxLen, int nonce, const char* evtName); + +size_t JsonWriteJoinReply(char* dest, size_t maxLen, const char* userId, int reply, int nonce); + +// I want to use as few allocations as I can get away with, and to do that with RapidJson, you need +// to supply some of your own allocators for stuff rather than use the defaults + +class LinearAllocator { +public: + char* buffer_; + char* end_; + LinearAllocator() + { + assert(0); // needed for some default case in rapidjson, should not use + } + LinearAllocator(char* buffer, size_t size) + : buffer_(buffer) + , end_(buffer + size) + { + } + static const bool kNeedFree = false; + void* Malloc(size_t size) + { + char* res = buffer_; + buffer_ += size; + if (buffer_ > end_) { + buffer_ = res; + return nullptr; + } + return res; + } + void* Realloc(void* originalPtr, size_t originalSize, size_t newSize) + { + if (newSize == 0) { + return nullptr; + } + // allocate how much you need in the first place + assert(!originalPtr && !originalSize); + // unused parameter warning + (void)(originalPtr); + (void)(originalSize); + return Malloc(newSize); + } + static void Free(void* ptr) + { + /* shrug */ + (void)ptr; + } +}; + +template +class FixedLinearAllocator : public LinearAllocator { +public: + char fixedBuffer_[Size]; + FixedLinearAllocator() + : LinearAllocator(fixedBuffer_, Size) + { + } + static const bool kNeedFree = false; +}; + +// wonder why this isn't a thing already, maybe I missed it +class DirectStringBuffer { +public: + using Ch = char; + char* buffer_; + char* end_; + char* current_; + + DirectStringBuffer(char* buffer, size_t maxLen) + : buffer_(buffer) + , end_(buffer + maxLen) + , current_(buffer) + { + } + + void Put(char c) + { + if (current_ < end_) { + *current_++ = c; + } + } + void Flush() {} + size_t GetSize() const { return (size_t)(current_ - buffer_); } +}; + +using MallocAllocator = rapidjson::CrtAllocator; +using PoolAllocator = rapidjson::MemoryPoolAllocator; +using UTF8 = rapidjson::UTF8; +// Writer appears to need about 16 bytes per nested object level (with 64bit size_t) +using StackAllocator = FixedLinearAllocator<2048>; +constexpr size_t WriterNestingLevels = 2048 / (2 * sizeof(size_t)); +using JsonWriterBase = + rapidjson::Writer; +class JsonWriter : public JsonWriterBase { +public: + DirectStringBuffer stringBuffer_; + StackAllocator stackAlloc_; + + JsonWriter(char* dest, size_t maxLen) + : JsonWriterBase(stringBuffer_, &stackAlloc_, WriterNestingLevels) + , stringBuffer_(dest, maxLen) + , stackAlloc_() + { + } + + size_t Size() const { return stringBuffer_.GetSize(); } +}; + +using JsonDocumentBase = rapidjson::GenericDocument; +class JsonDocument : public JsonDocumentBase { +public: + static const int kDefaultChunkCapacity = 32 * 1024; + // json parser will use this buffer first, then allocate more if needed; I seriously doubt we + // send any messages that would use all of this, though. + char parseBuffer_[32 * 1024]; + MallocAllocator mallocAllocator_; + PoolAllocator poolAllocator_; + StackAllocator stackAllocator_; + JsonDocument() + : JsonDocumentBase(rapidjson::kObjectType, + &poolAllocator_, + sizeof(stackAllocator_.fixedBuffer_), + &stackAllocator_) + , poolAllocator_(parseBuffer_, sizeof(parseBuffer_), kDefaultChunkCapacity, &mallocAllocator_) + , stackAllocator_() + { + } +}; + +using JsonValue = rapidjson::GenericValue; + +inline JsonValue* GetObjMember(JsonValue* obj, const char* name) +{ + if (obj) { + auto member = obj->FindMember(name); + if (member != obj->MemberEnd() && member->value.IsObject()) { + return &member->value; + } + } + return nullptr; +} + +inline int GetIntMember(JsonValue* obj, const char* name, int notFoundDefault = 0) +{ + if (obj) { + auto member = obj->FindMember(name); + if (member != obj->MemberEnd() && member->value.IsInt()) { + return member->value.GetInt(); + } + } + return notFoundDefault; +} + +inline const char* GetStrMember(JsonValue* obj, + const char* name, + const char* notFoundDefault = nullptr) +{ + if (obj) { + auto member = obj->FindMember(name); + if (member != obj->MemberEnd() && member->value.IsString()) { + return member->value.GetString(); + } + } + return notFoundDefault; +} diff --git a/dependencies/ih264d/CHANGES b/dependencies/ih264d/CHANGES new file mode 100644 index 00000000..7bd74fb1 --- /dev/null +++ b/dependencies/ih264d/CHANGES @@ -0,0 +1,6 @@ +Code adjustments made by Team Cemu for Cemu emulator project as of 2022-02-01: +- Stripped encoder, tests and fuzzer. We only need the decoder +- Extended ih264_platform_macros.h to support MSVC instrinsics +- Extended ithread.c to use WinAPI natives instead of requiring pthread.h +- The decoder will output images without regarding SPS crop flag and instead pass the crop values in the decode output structure +- Modified decoder/x86/ih264d_function_selector.c ih264d_init_arch() to automatically choose the best e_processor_arch for the current CPU \ No newline at end of file diff --git a/dependencies/ih264d/CMakeLists.txt b/dependencies/ih264d/CMakeLists.txt new file mode 100644 index 00000000..4f538f69 --- /dev/null +++ b/dependencies/ih264d/CMakeLists.txt @@ -0,0 +1,148 @@ +cmake_minimum_required (VERSION 3.8) + +project ("ih264d") + +set(LIBAVCDEC_X86_INCLUDES "common/x86" "decoder/x86") + +include_directories("common/" "decoder/" ${LIBAVCDEC_X86_INCLUDES}) + +if((CMAKE_C_COMPILER_ID MATCHES "GNU") OR (CMAKE_C_COMPILER_ID MATCHES "Clang")) + add_compile_options(-mssse3 -mavx2) +endif() + +add_library (ih264d +"common/ih264_buf_mgr.c" +"common/ih264_buf_mgr.h" +"common/ih264_cabac_tables.c" +"common/ih264_cabac_tables.h" +"common/ih264_cavlc_tables.c" +"common/ih264_cavlc_tables.h" +"common/ih264_chroma_intra_pred_filters.c" +"common/ih264_common_tables.c" +"common/ih264_common_tables.h" +"common/ih264_deblk_edge_filters.c" +"common/ih264_deblk_edge_filters.h" +"common/ih264_deblk_tables.c" +"common/ih264_deblk_tables.h" +"common/ih264_debug.h" +"common/ih264_defs.h" +"common/ih264_disp_mgr.c" +"common/ih264_disp_mgr.h" +"common/ih264_dpb_mgr.c" +"common/ih264_dpb_mgr.h" +"common/ih264_error.h" +"common/ih264_ihadamard_scaling.c" +"common/ih264_inter_pred_filters.c" +"common/ih264_inter_pred_filters.h" +"common/ih264_intra_pred_filters.h" +"common/ih264_iquant_itrans_recon.c" +"common/ih264_list.c" +"common/ih264_list.h" +"common/ih264_luma_intra_pred_filters.c" +"common/ih264_macros.h" +"common/ih264_mem_fns.c" +"common/ih264_mem_fns.h" +"common/ih264_padding.c" +"common/ih264_padding.h" +"common/ih264_resi_trans.h" +"common/ih264_resi_trans_quant.c" +"common/ih264_size_defs.h" +"common/ih264_structs.h" +"common/ih264_trans_data.c" +"common/ih264_trans_data.h" +"common/ih264_trans_macros.h" +"common/ih264_trans_quant_itrans_iquant.h" +"common/ih264_typedefs.h" +"common/ih264_weighted_pred.c" +"common/ih264_weighted_pred.h" +"common/ithread.c" +"common/ithread.h" +"common/x86/ih264_chroma_intra_pred_filters_ssse3.c" +"common/x86/ih264_deblk_chroma_ssse3.c" +"common/x86/ih264_deblk_luma_ssse3.c" +"common/x86/ih264_ihadamard_scaling_sse42.c" +"common/x86/ih264_ihadamard_scaling_ssse3.c" +"common/x86/ih264_inter_pred_filters_ssse3.c" +"common/x86/ih264_iquant_itrans_recon_dc_ssse3.c" +"common/x86/ih264_iquant_itrans_recon_sse42.c" +"common/x86/ih264_iquant_itrans_recon_ssse3.c" +"common/x86/ih264_luma_intra_pred_filters_ssse3.c" +"common/x86/ih264_mem_fns_ssse3.c" +"common/x86/ih264_padding_ssse3.c" +"common/x86/ih264_platform_macros.h" +"common/x86/ih264_resi_trans_quant_sse42.c" +"common/x86/ih264_weighted_pred_sse42.c" +"decoder/ih264d.h" +"decoder/ih264d_api.c" +"decoder/ih264d_bitstrm.c" +"decoder/ih264d_bitstrm.h" +"decoder/ih264d_cabac.c" +"decoder/ih264d_cabac.h" +"decoder/ih264d_cabac_init_tables.c" +"decoder/ih264d_compute_bs.c" +"decoder/ih264d_deblocking.c" +"decoder/ih264d_deblocking.h" +"decoder/ih264d_debug.h" +"decoder/ih264d_defs.h" +"decoder/ih264d_dpb_manager.h" +"decoder/ih264d_dpb_mgr.c" +"decoder/ih264d_error_handler.h" +"decoder/ih264d_format_conv.c" +"decoder/ih264d_format_conv.h" +"decoder/ih264d_function_selector.h" +"decoder/ih264d_function_selector_generic.c" +"decoder/ih264d_inter_pred.c" +"decoder/ih264d_inter_pred.h" +"decoder/ih264d_mb_utils.c" +"decoder/ih264d_mb_utils.h" +"decoder/ih264d_mem_request.h" +"decoder/ih264d_mvpred.c" +"decoder/ih264d_mvpred.h" +"decoder/ih264d_nal.c" +"decoder/ih264d_nal.h" +"decoder/ih264d_parse_bslice.c" +"decoder/ih264d_parse_cabac.c" +"decoder/ih264d_parse_cabac.h" +"decoder/ih264d_parse_cavlc.c" +"decoder/ih264d_parse_cavlc.h" +"decoder/ih264d_parse_headers.c" +"decoder/ih264d_parse_headers.h" +"decoder/ih264d_parse_islice.c" +"decoder/ih264d_parse_islice.h" +"decoder/ih264d_parse_mb_header.c" +"decoder/ih264d_parse_mb_header.h" +"decoder/ih264d_parse_pslice.c" +"decoder/ih264d_parse_slice.c" +"decoder/ih264d_parse_slice.h" +"decoder/ih264d_process_bslice.c" +"decoder/ih264d_process_bslice.h" +"decoder/ih264d_process_intra_mb.c" +"decoder/ih264d_process_intra_mb.h" +"decoder/ih264d_process_pslice.c" +"decoder/ih264d_process_pslice.h" +"decoder/ih264d_quant_scaling.c" +"decoder/ih264d_quant_scaling.h" +"decoder/ih264d_sei.c" +"decoder/ih264d_sei.h" +"decoder/ih264d_structs.h" +"decoder/ih264d_tables.c" +"decoder/ih264d_tables.h" +"decoder/ih264d_thread_compute_bs.c" +"decoder/ih264d_thread_compute_bs.h" +"decoder/ih264d_thread_parse_decode.c" +"decoder/ih264d_thread_parse_decode.h" +"decoder/ih264d_transfer_address.h" +"decoder/ih264d_utils.c" +"decoder/ih264d_utils.h" +"decoder/ih264d_vui.c" +"decoder/ih264d_vui.h" +"decoder/iv.h" +"decoder/ivd.h" +"decoder/x86/ih264d_function_selector.c" +"decoder/x86/ih264d_function_selector_sse42.c" +"decoder/x86/ih264d_function_selector_ssse3.c" +) + +if(MSVC) +set_property(TARGET ih264d PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") +endif() diff --git a/dependencies/ih264d/CMakeSettings.json b/dependencies/ih264d/CMakeSettings.json new file mode 100644 index 00000000..d2bcfced --- /dev/null +++ b/dependencies/ih264d/CMakeSettings.json @@ -0,0 +1,26 @@ +{ + "configurations": [ + { + "name": "x64-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "inheritEnvironments": [ "msvc_x64_x64" ], + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "" + }, + { + "name": "x64-Release", + "generator": "Ninja", + "configurationType": "Release", + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "msvc_x64_x64" ] + } + ] +} \ No newline at end of file diff --git a/dependencies/ih264d/NOTICE b/dependencies/ih264d/NOTICE new file mode 100644 index 00000000..8a57d10c --- /dev/null +++ b/dependencies/ih264d/NOTICE @@ -0,0 +1,20 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/*****************************************************************************/ diff --git a/dependencies/ih264d/common/arm/ih264_arm_memory_barrier.s b/dependencies/ih264d/common/arm/ih264_arm_memory_barrier.s new file mode 100644 index 00000000..38164095 --- /dev/null +++ b/dependencies/ih264d/common/arm/ih264_arm_memory_barrier.s @@ -0,0 +1,76 @@ +@/****************************************************************************** +@ * +@ * Copyright (C) 2015 The Android Open Source Project +@ * +@ * Licensed under the Apache License, Version 2.0 (the "License"); +@ * you may not use this file except in compliance with the License. +@ * You may obtain a copy of the License at: +@ * +@ * http://www.apache.org/licenses/LICENSE-2.0 +@ * +@ * Unless required by applicable law or agreed to in writing, software +@ * distributed under the License is distributed on an "AS IS" BASIS, +@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@ * See the License for the specific language governing permissions and +@ * limitations under the License. +@ * +@ ***************************************************************************** +@ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +@*/ +@** +@******************************************************************************* +@* @file +@* ih264_arm_memory_barrier.s +@* +@* @brief +@* Contains function definitions for data synchronization. +@* +@* @author +@* Ittiam +@* +@* @par List of Functions: +@* +@* +@* @remarks +@* None +@* +@******************************************************************************* + +.text +.p2align 2 + +@***************************************************************************** +@* +@* Function Name : ih264_arm_dsb +@* Description : Adds DSB +@* Revision History : +@* DD MM YYYY Author(s) Changes +@* 03 07 2008 100355 First version +@* +@***************************************************************************** + + .global ih264_arm_dsb +ih264_arm_dsb: + dsb + bx lr + + + +@***************************************************************************** +@* +@* Function Name : ih264_arm_dmb +@* Description : Adds DMB +@* Revision History : +@* DD MM YYYY Author(s) Changes +@* 03 07 2008 100355 First version +@* +@***************************************************************************** + + .global ih264_arm_dmb + +ih264_arm_dmb: + dmb + bx lr + + + diff --git a/dependencies/ih264d/common/arm/ih264_deblk_chroma_a9.s b/dependencies/ih264d/common/arm/ih264_deblk_chroma_a9.s new file mode 100644 index 00000000..8c9960a7 --- /dev/null +++ b/dependencies/ih264d/common/arm/ih264_deblk_chroma_a9.s @@ -0,0 +1,1337 @@ +@/****************************************************************************** +@ * +@ * Copyright (C) 2015 The Android Open Source Project +@ * +@ * Licensed under the Apache License, Version 2.0 (the "License"); +@ * you may not use this file except in compliance with the License. +@ * You may obtain a copy of the License at: +@ * +@ * http://www.apache.org/licenses/LICENSE-2.0 +@ * +@ * Unless required by applicable law or agreed to in writing, software +@ * distributed under the License is distributed on an "AS IS" BASIS, +@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@ * See the License for the specific language governing permissions and +@ * limitations under the License. +@ * +@ ***************************************************************************** +@ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +@*/ +@/*****************************************************************************/ +@/* */ +@/* File Name : ih264_deblk_chroma_a9.s */ +@/* */ +@/* Description : Contains function definitions for deblocking luma */ +@/* edge. Functions are coded in NEON assembly and can */ +@/* be compiled using ARM RVDS. */ +@/* */ +@/* List of Functions : ih264_deblk_chroma_vert_bs4_bp_a9() */ +@/* ih264_deblk_chroma_vert_bslt4_bp_a9() */ +@/* ih264_deblk_chroma_horz_bs4_bp_a9() */ +@/* ih264_deblk_chroma_horz_bslt4_bp_a9() */ +@/* ih264_deblk_chroma_vert_bs4_mbaff_bp_a9() */ +@/* ih264_deblk_chroma_vert_bslt4_mbaff_bp_a9() */ +@/* ih264_deblk_chroma_vert_bs4_a9() */ +@/* ih264_deblk_chroma_vert_bslt4_a9() */ +@/* ih264_deblk_chroma_horz_bs4_a9() */ +@/* ih264_deblk_chroma_horz_bslt4_a9() */ +@/* ih264_deblk_chroma_vert_bs4_mbaff_a9() */ +@/* ih264_deblk_chroma_vert_bslt4_mbaff_a9() */ +@/* */ +@/* Issues / Problems : None */ +@/* */ +@/* Revision History : */ +@/* */ +@/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +@/* 28 11 2013 Ittiam Draft */ +@/* 05 01 2015 Kaushik Added double-call functions for */ +@/* Senthoor vertical deblocking, and high */ +@/* profile functions. */ +@/* */ +@/*****************************************************************************/ + + +.text +.p2align 2 + +@** +@******************************************************************************* +@* +@* @brief +@* Performs filtering of a chroma block horizontal edge when the +@* boundary strength is set to 4 +@* +@* @par Description: +@* This operation is described in Sec. 8.7.2.4 under the title +@* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. +@* +@* @param[in] r0 - pu1_src +@* Pointer to the src sample q0 +@* +@* @param[in] r1 - src_strd +@* Source stride +@* +@* @param[in] r2 - alpha +@* Alpha Value for the boundary +@* +@* @param[in] r3 - beta +@* Beta Value for the boundary +@* +@* @returns +@* None +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* + + .global ih264_deblk_chroma_horz_bs4_bp_a9 + +ih264_deblk_chroma_horz_bs4_bp_a9: + + stmfd sp!, {r4, lr} @ + vpush {d8 - d15} + sub r0, r0, r1, lsl #1 @R0 = uc_edgePixel pointing to p1 of chroma + vld2.8 {d6, d7}, [r0], r1 @D6 = p1u , D7 = p1v + mov r4, r0 @Keeping a backup of the pointer p0 of chroma + vld2.8 {d4, d5}, [r0], r1 @D4 = p0u , D5 = p0v + vdup.8 q10, r2 @Q10 contains alpha + vld2.8 {d0, d1}, [r0], r1 @D0 = q0u , D1 = q0v + vaddl.u8 q4, d6, d0 @ + vaddl.u8 q5, d7, d1 @Q4,Q5 = q0 + p1 + vmov.i8 d31, #2 @ + vld2.8 {d2, d3}, [r0] @D2 = q1u , D3 = q1v + vabd.u8 q13, q3, q2 @Q13 = ABS(p1 - p0) + vmlal.u8 q4, d2, d31 @ + vmlal.u8 q5, d3, d31 @Q5,Q4 = (X2(q1U) + q0U + p1U) + vabd.u8 q11, q2, q0 @Q11 = ABS(p0 - q0) + vabd.u8 q12, q1, q0 @Q12 = ABS(q1 - q0) + vaddl.u8 q7, d4, d2 @ + vaddl.u8 q14, d5, d3 @Q14,Q7 = P0 + Q1 + vdup.8 q8, r3 @Q8 contains beta + vmlal.u8 q7, d6, d31 @ + vmlal.u8 q14, d7, d31 @Q14,Q7 = (X2(p1U) + p0U + q1U) + vcge.u8 q9, q11, q10 @Q9 = ( ABS(p0 - q0) >= Alpha ) + vcge.u8 q12, q12, q8 @Q12= ( ABS(q1 - q0) >= Beta ) + vcge.u8 q13, q13, q8 @Q13= ( ABS(p1 - p0) >= Beta ) + vrshrn.u16 d8, q4, #2 @ + vrshrn.u16 d9, q5, #2 @Q4 = (X2(q1U) + q0U + p1U + 2) >> 2 + vorr q9, q9, q12 @Q9 = ( ABS(p0 - q0) >= Alpha ) | ( ABS(q1 - q0) >= Beta ) + vrshrn.u16 d10, q7, #2 @ + vrshrn.u16 d11, q14, #2 @Q5 = (X2(p1U) + p0U + q1U + 2) >> 2 + vorr q9, q9, q13 @Q9 = ( ABS(p0 - q0) >= Alpha ) | ( ABS(q1 - q0) >= Beta ) | ( ABS(p1 - p0) >= Beta ) + vbit q5, q2, q9 @ + vbit q4, q0, q9 @ + vst2.8 {d10, d11}, [r4], r1 @ + vst2.8 {d8, d9}, [r4] @ + vpop {d8 - d15} + ldmfd sp!, {r4, pc} @ + + + +@** +@******************************************************************************* +@* +@* @brief +@* Performs filtering of a chroma block vertical edge when the +@* boundary strength is set to 4 +@* +@* @par Description: +@* This operation is described in Sec. 8.7.2.4 under the title +@* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. +@* +@* @param[in] r0 - pu1_src +@* Pointer to the src sample q0 +@* +@* @param[in] r1 - src_strd +@* Source stride +@* +@* @param[in] r2 - alpha +@* Alpha Value for the boundary +@* +@* @param[in] r3 - beta +@* Beta Value for the boundary +@* +@* @returns +@* None +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* + + .global ih264_deblk_chroma_vert_bs4_bp_a9 + +ih264_deblk_chroma_vert_bs4_bp_a9: + + stmfd sp!, {r12, r14} + vpush {d8 - d15} + sub r0, r0, #4 @point r0 to p1u of row0. + mov r12, r0 @keep a back up of r0 for buffer write + + vld4.16 {d0[0], d2[0], d4[0], d6[0]}, [r0], r1 + vld4.16 {d0[1], d2[1], d4[1], d6[1]}, [r0], r1 + vld4.16 {d0[2], d2[2], d4[2], d6[2]}, [r0], r1 + vld4.16 {d0[3], d2[3], d4[3], d6[3]}, [r0], r1 + + vld4.16 {d1[0], d3[0], d5[0], d7[0]}, [r0], r1 + vld4.16 {d1[1], d3[1], d5[1], d7[1]}, [r0], r1 + vld4.16 {d1[2], d3[2], d5[2], d7[2]}, [r0], r1 + vld4.16 {d1[3], d3[3], d5[3], d7[3]}, [r0], r1 + + vdup.8 q11, r2 @Q4 = alpha + vdup.8 q12, r3 @Q5 = beta + vmov.i8 d31, #2 + + vabd.u8 q4, q1, q2 @|p0-q0| + vabd.u8 q5, q3, q2 @|q1-q0| + vabd.u8 q6, q0, q1 @|p1-p0| + vaddl.u8 q7, d2, d6 + vaddl.u8 q8, d3, d7 @(p0 + q1) + vclt.u8 q4, q4, q11 @|p0-q0| < alpha ? + vclt.u8 q5, q5, q12 @|q1-q0| < beta ? + vclt.u8 q6, q6, q12 @|p1-p0| < beta ? + vmlal.u8 q7, d0, d31 + vmlal.u8 q8, d1, d31 @2*p1 + (p0 + q1) + vaddl.u8 q9, d0, d4 + vaddl.u8 q10, d1, d5 @(p1 + q0) + vand.u8 q4, q4, q5 @|p0-q0| < alpha && |q1-q0| < beta + vmlal.u8 q9, d6, d31 + vmlal.u8 q10, d7, d31 @2*q1 + (p1 + q0) + + vrshrn.i16 d14, q7, #2 + vrshrn.i16 d15, q8, #2 @(2*p1 + (p0 + q1) + 2) >> 2 + vand.u8 q4, q4, q6 @|p0-q0| < alpha && |q1-q0| < beta && |p1-p0| < beta + vrshrn.i16 d18, q9, #2 + vrshrn.i16 d19, q10, #2 @(2*q1 + (p1 + q0) + 2) >> 2 + + vbit q1, q7, q4 + vbit q2, q9, q4 + + vst4.16 {d0[0], d2[0], d4[0], d6[0]}, [r12], r1 + vst4.16 {d0[1], d2[1], d4[1], d6[1]}, [r12], r1 + vst4.16 {d0[2], d2[2], d4[2], d6[2]}, [r12], r1 + vst4.16 {d0[3], d2[3], d4[3], d6[3]}, [r12], r1 + + vst4.16 {d1[0], d3[0], d5[0], d7[0]}, [r12], r1 + vst4.16 {d1[1], d3[1], d5[1], d7[1]}, [r12], r1 + vst4.16 {d1[2], d3[2], d5[2], d7[2]}, [r12], r1 + vst4.16 {d1[3], d3[3], d5[3], d7[3]}, [r12], r1 + vpop {d8 - d15} + ldmfd sp!, {r12, pc} + + + +@** +@******************************************************************************* +@* +@* @brief +@* Performs filtering of a chroma block horizontal edge for cases where the +@* boundary strength is less than 4 +@* +@* @par Description: +@* This operation is described in Sec. 8.7.2.4 under the title +@* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. +@* +@* @param[in] r0 - pu1_src +@* Pointer to the src sample q0 +@* +@* @param[in] r1 - src_strd +@* Source stride +@* +@* @param[in] r2 - alpha +@* Alpha Value for the boundary +@* +@* @param[in] r3 - beta +@* Beta Value for the boundary +@* +@* @param[in] sp(0) - u4_bs +@* Packed Boundary strength array +@* +@* @param[in] sp(4) - pu1_cliptab +@* tc0_table +@* +@* @returns +@* None +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* + + .global ih264_deblk_chroma_horz_bslt4_bp_a9 + +ih264_deblk_chroma_horz_bslt4_bp_a9: + + stmfd sp!, {r4-r6, lr} @ + + ldrd r4, r5, [sp, #0x10] @r4 = u4_bs , r5 = pu1_cliptab + vpush {d8 - d15} + sub r0, r0, r1, lsl #1 @R0 = uc_edgePixelU pointing to p2 of chroma U + rev r4, r4 @ + vmov.32 d12[0], r4 @d12[0] = ui_Bs + vld1.32 d16[0], [r5] @D16[0] contains cliptab + vld2.8 {d6, d7}, [r0], r1 @Q3=p1 + vtbl.8 d14, {d16}, d12 @ + vmovl.u8 q6, d12 @q6 = uc_Bs in each 16 bit scalar + mov r6, r0 @Keeping a backup of the pointer to chroma U P0 + vld2.8 {d4, d5}, [r0], r1 @Q2=p0 + vmov.i8 d30, #1 @ + vdup.8 q10, r2 @Q10 contains alpha + vld2.8 {d0, d1}, [r0], r1 @Q0=q0 + vmovl.u8 q7, d14 @ + vld2.8 {d2, d3}, [r0] @Q1=q1 + vsubl.u8 q5, d1, d5 @ + vsubl.u8 q4, d0, d4 @Q5,Q4 = (q0 - p0) + vabd.u8 q13, q3, q2 @Q13 = ABS(p1 - p0) + vshl.i16 q5, q5, #2 @Q5 = (q0 - p0)<<2 + vabd.u8 q11, q2, q0 @Q11 = ABS(p0 - q0) + vshl.i16 q4, q4, #2 @Q4 = (q0 - p0)<<2 + vsli.16 q7, q7, #8 @ + vabd.u8 q12, q1, q0 @Q12 = ABS(q1 - q0) + vcge.u8 q9, q11, q10 @Q9 = ( ABS(p0 - q0) >= Alpha ) + vsubl.u8 q10, d6, d2 @Q10 = (p1 - q1)L + vsubl.u8 q3, d7, d3 @Q3 = (p1 - q1)H + vdup.8 q8, r3 @Q8 contains beta + vadd.i16 q4, q4, q10 @ + vadd.i16 q5, q5, q3 @Q5,Q4 = [ (q0 - p0)<<2 ] + (p1 - q1) + vcge.u8 q12, q12, q8 @Q12= ( ABS(q1 - q0) >= Beta ) + vcgt.s16 d12, d12, #0 @Q6 = (us_Bs > 0) + vqrshrn.s16 d8, q4, #3 @ + vqrshrn.s16 d9, q5, #3 @Q4 = i_macro = (((q0 - p0)<<2) + (p1 - q1) + 4)>>3 + vadd.i8 d14, d14, d30 @Q7 = C = C0+1 + vcge.u8 q13, q13, q8 @Q13= ( ABS(p1 - p0) >= Beta ) + vorr q9, q9, q12 @Q9 = ( ABS(p0 - q0) >= Alpha ) | ( ABS(q1 - q0) >= Beta ) + vabs.s8 q3, q4 @Q4 = ABS (i_macro) + vmov.i8 d15, d14 @ + vmov.i8 d13, d12 @ + vorr q9, q9, q13 @Q9 = ( ABS(p0 - q0) >= Alpha ) | ( ABS(q1 - q0) >= Beta ) | ( ABS(p1 - p0) >= Beta ) + vmin.u8 q7, q3, q7 @Q7 = delta = (ABS(i_macro) > C) ? C : ABS(i_macro) + vbic q6, q6, q9 @final condition + vcge.s8 q4, q4, #0 @Q4 = (i_macro >= 0) + vand q7, q7, q6 @Making delta zero in places where values shouldn be filterd + vqadd.u8 q8, q2, q7 @Q8 = p0 + delta + vqsub.u8 q2, q2, q7 @Q2 = p0 - delta + vqadd.u8 q9, q0, q7 @Q9 = q0 + delta + vqsub.u8 q0, q0, q7 @Q0 = q0 - delta + vbif q8, q2, q4 @Q8 = (i_macro >= 0 ) ? (p0+delta) : (p0-delta) + vbif q0, q9, q4 @Q0 = (i_macro >= 0 ) ? (q0-delta) : (q0+delta) + vst2.8 {d16, d17}, [r6], r1 @ + vst2.8 {d0, d1}, [r6] @ + vpop {d8 - d15} + ldmfd sp!, {r4-r6, pc} @ + + + +@** +@******************************************************************************* +@* +@* @brief +@* Performs filtering of a chroma block vertical edge for cases where the +@* boundary strength is less than 4 +@* +@* @par Description: +@* This operation is described in Sec. 8.7.2.4 under the title +@* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. +@* +@* @param[in] r0 - pu1_src +@* Pointer to the src sample q0 +@* +@* @param[in] r1 - src_strd +@* Source stride +@* +@* @param[in] r2 - alpha +@* Alpha Value for the boundary +@* +@* @param[in] r3 - beta +@* Beta Value for the boundary +@* +@* @param[in] sp(0) - u4_bs +@* Packed Boundary strength array +@* +@* @param[in] sp(4) - pu1_cliptab +@* tc0_table +@* +@* @returns +@* None +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* + + .global ih264_deblk_chroma_vert_bslt4_bp_a9 + +ih264_deblk_chroma_vert_bslt4_bp_a9: + + stmfd sp!, {r10-r12, r14} + + sub r0, r0, #4 @point r0 to p1u of row0. + ldr r11, [sp, #16] @r12 = ui_Bs + + ldr r10, [sp, #20] @r14 = puc_ClipTab + mov r12, r0 @keep a back up of r0 for buffer write + vpush {d8 - d15} + vld4.16 {d0[0], d2[0], d4[0], d6[0]}, [r0], r1 + vld4.16 {d0[1], d2[1], d4[1], d6[1]}, [r0], r1 + vld4.16 {d0[2], d2[2], d4[2], d6[2]}, [r0], r1 + vld4.16 {d0[3], d2[3], d4[3], d6[3]}, [r0], r1 + + vld4.16 {d1[0], d3[0], d5[0], d7[0]}, [r0], r1 + vld4.16 {d1[1], d3[1], d5[1], d7[1]}, [r0], r1 + vld4.16 {d1[2], d3[2], d5[2], d7[2]}, [r0], r1 + vld4.16 {d1[3], d3[3], d5[3], d7[3]}, [r0], r1 + + + vdup.8 q11, r2 @Q4 = alpha + vabd.u8 q4, q1, q2 @|p0-q0| + vdup.8 q12, r3 @Q5 = beta + vabd.u8 q5, q3, q2 @|q1-q0| + vabd.u8 q6, q0, q1 @|p1-p0| + vclt.u8 q4, q4, q11 @|p0-q0| < alpha ? + vsubl.u8 q7, d0, d6 + vclt.u8 q5, q5, q12 @|q1-q0| < beta ? + vsubl.u8 q8, d1, d7 @(p1 - q1) + vclt.u8 q6, q6, q12 @|p1-p0| < beta ? + vsubl.u8 q9, d4, d2 + vand.u8 q4, q4, q5 @|p0-q0| < alpha && |q1-q0| < beta + vsubl.u8 q10, d5, d3 @(q0 - p0) + vmov.u16 q14, #4 + vld1.32 {d24[0]}, [r10] @Load ClipTable + rev r11, r11 @Blocking strengths + vand.u8 q4, q4, q6 @|p0-q0| < alpha && |q1-q0| < beta && |p1-p0| < beta + + vmov.32 d10[0], r11 + + vmla.s16 q7, q9, q14 + vmla.s16 q8, q10, q14 @4*(q0 - p0) + (p1 - q1) + + vmovl.u8 q5, d10 + + + vsli.u16 d10, d10, #8 + vmovl.u16 q5, d10 + vsli.u32 q5, q5, #16 + vtbl.8 d12, {d24}, d10 + vtbl.8 d13, {d24}, d11 @tC0 + vmov.u8 q12, #1 + vadd.u8 q6, q6, q12 @tC0 + 1 + vcge.u8 q5, q5, q12 @u4_bS > 0 ? + vand.u8 q4, q4, q5 @|p0-q0| < alpha && |q1-q0| < beta && |p1-p0| < beta && u4_bs != 0 + + @ Q0 - Q3(inputs), + @ Q4 (|p0-q0| < alpha && |q1-q0| < beta && |p1-p0| < beta && u4_bs != 0), + @ Q6 (tC) + + vrshr.s16 q7, q7, #3 + vrshr.s16 q8, q8, #3 @(((q0 - p0) << 2) + (p1 - q1) + 4) >> 3) + + vcgt.s16 q9, q7, #0 + vcgt.s16 q10, q8, #0 + vmovn.i16 d18, q9 + vmovn.i16 d19, q10 @Q9 = sign(delta) + vabs.s16 q7, q7 + vabs.s16 q8, q8 + vmovn.u16 d14, q7 + vmovn.u16 d15, q8 + vmin.u8 q7, q7, q6 @Q7 = |delta| + + vqadd.u8 q10, q1, q7 @p0+|delta| + vqadd.u8 q11, q2, q7 @q0+|delta| + vqsub.u8 q12, q1, q7 @p0-|delta| + vqsub.u8 q13, q2, q7 @q0-|delta| + + vbit q12, q10, q9 @p0 + delta + vbit q11, q13, q9 @q0 - delta + + vbit q1, q12, q4 + vbit q2, q11, q4 + + vst4.16 {d0[0], d2[0], d4[0], d6[0]}, [r12], r1 + vst4.16 {d0[1], d2[1], d4[1], d6[1]}, [r12], r1 + vst4.16 {d0[2], d2[2], d4[2], d6[2]}, [r12], r1 + vst4.16 {d0[3], d2[3], d4[3], d6[3]}, [r12], r1 + + vst4.16 {d1[0], d3[0], d5[0], d7[0]}, [r12], r1 + vst4.16 {d1[1], d3[1], d5[1], d7[1]}, [r12], r1 + vst4.16 {d1[2], d3[2], d5[2], d7[2]}, [r12], r1 + vst4.16 {d1[3], d3[3], d5[3], d7[3]}, [r12], r1 + vpop {d8 - d15} + ldmfd sp!, {r10-r12, pc} + + + +@** +@******************************************************************************* +@* +@* @brief +@* Performs filtering of a chroma block vertical edge when the +@* boundary strength is set to 4 on calling twice +@* +@* @par Description: +@* This operation is described in Sec. 8.7.2.4 under the title +@* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. +@* +@* @param[in] r0 - pu1_src +@* Pointer to the src sample q0 +@* +@* @param[in] r1 - src_strd +@* Source stride +@* +@* @param[in] r2 - alpha +@* Alpha Value for the boundary +@* +@* @param[in] r3 - beta +@* Beta Value for the boundary +@* +@* @returns +@* None +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* + + .global ih264_deblk_chroma_vert_bs4_mbaff_bp_a9 + +ih264_deblk_chroma_vert_bs4_mbaff_bp_a9: + + stmfd sp!, {r12, r14} + vpush {d8 - d15} + sub r0, r0, #4 @point r0 to p1u of row0. + mov r12, r0 @keep a back up of r0 for buffer write + + vld4.16 {d0[0], d1[0], d2[0], d3[0]}, [r0], r1 + vld4.16 {d0[1], d1[1], d2[1], d3[1]}, [r0], r1 + vld4.16 {d0[2], d1[2], d2[2], d3[2]}, [r0], r1 + vld4.16 {d0[3], d1[3], d2[3], d3[3]}, [r0], r1 + + vdup.8 d11, r2 @D11 = alpha + vdup.8 d12, r3 @D12 = beta + vmov.i8 d31, #2 + + vabd.u8 d4, d1, d2 @|p0-q0| + vabd.u8 d5, d3, d2 @|q1-q0| + vabd.u8 d6, d0, d1 @|p1-p0| + vaddl.u8 q14, d1, d3 @(p0 + q1) + vclt.u8 d4, d4, d11 @|p0-q0| < alpha ? + vclt.u8 d5, d5, d12 @|q1-q0| < beta ? + vclt.u8 d6, d6, d12 @|p1-p0| < beta ? + vmlal.u8 q14, d0, d31 @2*p1 + (p0 + q1) + vaddl.u8 q13, d0, d2 @(p1 + q0) + vand.u8 d4, d4, d5 @|p0-q0| < alpha && |q1-q0| < beta + vmlal.u8 q13, d3, d31 @2*q1 + (p1 + q0) + + vrshrn.i16 d7, q14, #2 @(2*p1 + (p0 + q1) + 2) >> 2 + vand.u8 d4, d4, d6 @|p0-q0| < alpha && |q1-q0| < beta && |p1-p0| < beta + vrshrn.i16 d9, q13, #2 @(2*q1 + (p1 + q0) + 2) >> 2 + + vbit d1, d7, d4 + vbit d2, d9, d4 + + vst4.16 {d0[0], d1[0], d2[0], d3[0]}, [r12], r1 + vst4.16 {d0[1], d1[1], d2[1], d3[1]}, [r12], r1 + vst4.16 {d0[2], d1[2], d2[2], d3[2]}, [r12], r1 + vst4.16 {d0[3], d1[3], d2[3], d3[3]}, [r12], r1 + vpop {d8 - d15} + ldmfd sp!, {r12, pc} + + + +@** +@******************************************************************************* +@* +@* @brief +@* Performs filtering of a chroma block vertical edge for cases where the +@* boundary strength is less than 4 on calling twice +@* +@* @par Description: +@* This operation is described in Sec. 8.7.2.4 under the title +@* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. +@* +@* @param[in] r0 - pu1_src +@* Pointer to the src sample q0 +@* +@* @param[in] r1 - src_strd +@* Source stride +@* +@* @param[in] r2 - alpha +@* Alpha Value for the boundary +@* +@* @param[in] r3 - beta +@* Beta Value for the boundary +@* +@* @param[in] sp(0) - u4_bs +@* Packed Boundary strength array +@* +@* @param[in] sp(4) - pu1_cliptab +@* tc0_table +@* +@* @returns +@* None +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* + + .global ih264_deblk_chroma_vert_bslt4_mbaff_bp_a9 + +ih264_deblk_chroma_vert_bslt4_mbaff_bp_a9: + + stmfd sp!, {r10-r12, r14} + + sub r0, r0, #4 @point r0 to p1u of row0. + ldr r11, [sp, #16] @r11 = ui_Bs + + ldr r10, [sp, #20] @r10 = puc_ClipTab + mov r12, r0 @keep a back up of r0 for buffer write + vpush {d8 - d15} + vld4.16 {d0[0], d1[0], d2[0], d3[0]}, [r0], r1 + vld4.16 {d0[1], d1[1], d2[1], d3[1]}, [r0], r1 + vld4.16 {d0[2], d1[2], d2[2], d3[2]}, [r0], r1 + vld4.16 {d0[3], d1[3], d2[3], d3[3]}, [r0], r1 + + vdup.8 d11, r2 @D11 = alpha + vabd.u8 d4, d1, d2 @|p0-q0| + vdup.8 d12, r3 @D12 = beta + vabd.u8 d5, d3, d2 @|q1-q0| + vabd.u8 d6, d0, d1 @|p1-p0| + vclt.u8 d4, d4, d11 @|p0-q0| < alpha ? + vclt.u8 d5, d5, d12 @|q1-q0| < beta ? + vsubl.u8 q14, d0, d3 @(p1 - q1) + vclt.u8 d6, d6, d12 @|p1-p0| < beta ? + vand.u8 d4, d4, d5 @|p0-q0| < alpha && |q1-q0| < beta + vsubl.u8 q12, d2, d1 @(q0 - p0) + vmov.u16 q10, #4 + + vld1.32 {d31[0]}, [r10] @Load ClipTable + rev r11, r11 @Blocking strengths + vand.u8 d4, d4, d6 @|p0-q0| < alpha && |q1-q0| < beta && |p1-p0| < beta + vmov.32 d22[0], r11 + vmla.s16 q14, q12, q10 @4*(q0 - p0) + (p1 - q1) + vmovl.u8 q11, d22 + vsli.u16 d22, d22, #8 + vtbl.8 d6, {d31}, d22 @tC0 + vmov.u8 d12, #1 + vadd.u8 d6, d6, d12 @tC0 + 1 + vcge.u8 d5, d22, d12 @u4_bS > 0 ? + vand.u8 d4, d4, d5 @|p0-q0| < alpha && |q1-q0| < beta && |p1-p0| < beta && u4_bs != 0 + + @ D0 - D3(inputs), + @ D4 (|p0-q0| < alpha && |q1-q0| < beta && |p1-p0| < beta && u4_bs != 0), + @ D6 (tC) + + vrshr.s16 q14, q14, #3 @(((q0 - p0) << 2) + (p1 - q1) + 4) >> 3) + + vcgt.s16 q13, q14, #0 + vmovn.i16 d9, q13 @D9 = sign(delta) + vabs.s16 q14, q14 + vmovn.u16 d7, q14 + vmin.u8 d7, d7, d6 @D7 = |delta| + + vqadd.u8 d10, d1, d7 @p0+|delta| + vqadd.u8 d11, d2, d7 @q0+|delta| + vqsub.u8 d12, d1, d7 @p0-|delta| + vqsub.u8 d13, d2, d7 @q0-|delta| + + vbit d12, d10, d9 @p0 + delta + vbit d11, d13, d9 @q0 - delta + + vbit d1, d12, d4 + vbit d2, d11, d4 + + vst4.16 {d0[0], d1[0], d2[0], d3[0]}, [r12], r1 + vst4.16 {d0[1], d1[1], d2[1], d3[1]}, [r12], r1 + vst4.16 {d0[2], d1[2], d2[2], d3[2]}, [r12], r1 + vst4.16 {d0[3], d1[3], d2[3], d3[3]}, [r12], r1 + vpop {d8 - d15} + ldmfd sp!, {r10-r12, pc} + + + +@** +@******************************************************************************* +@* +@* @brief +@* Performs filtering of a chroma block horizontal edge when the +@* boundary strength is set to 4 in high profile +@* +@* @par Description: +@* This operation is described in Sec. 8.7.2.4 under the title +@* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. +@* +@* @param[in] r0 - pu1_src +@* Pointer to the src sample q0 +@* +@* @param[in] r1 - src_strd +@* Source stride +@* +@* @param[in] r2 - alpha_cb +@* Alpha Value for the boundary in U +@* +@* @param[in] r3 - beta_cb +@* Beta Value for the boundary in U +@* +@* @param[in] sp(0) - alpha_cr +@* Alpha Value for the boundary in V +@* +@* @param[in] sp(4) - beta_cr +@* Beta Value for the boundary in V +@* +@* @returns +@* None +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* + + .global ih264_deblk_chroma_horz_bs4_a9 + +ih264_deblk_chroma_horz_bs4_a9: + + stmfd sp!, {r4-r6, lr} @ + + ldr r5, [sp, #16] @R5 = alpha_cr + ldr r6, [sp, #20] @R6 = beta_cr + vpush {d8 - d15} + sub r0, r0, r1, lsl #1 @R0 = uc_edgePixel pointing to p1 of chroma + vld2.8 {d6, d7}, [r0], r1 @D6 = p1u , D7 = p1v + mov r4, r0 @Keeping a backup of the pointer p0 of chroma + vld2.8 {d4, d5}, [r0], r1 @D4 = p0u , D5 = p0v + vdup.8 d20, r2 @D20 contains alpha_cb + vdup.8 d21, r5 @D21 contains alpha_cr + vld2.8 {d0, d1}, [r0], r1 @D0 = q0u , D1 = q0v + vaddl.u8 q4, d6, d0 @ + vaddl.u8 q5, d7, d1 @Q4,Q5 = q0 + p1 + vmov.i8 d31, #2 @ + vld2.8 {d2, d3}, [r0] @D2 = q1u , D3 = q1v + vabd.u8 q13, q3, q2 @Q13 = ABS(p1 - p0) + vmlal.u8 q4, d2, d31 @ + vmlal.u8 q5, d3, d31 @Q5,Q4 = (X2(q1U) + q0U + p1U) + vabd.u8 q11, q2, q0 @Q11 = ABS(p0 - q0) + vabd.u8 q12, q1, q0 @Q12 = ABS(q1 - q0) + vaddl.u8 q7, d4, d2 @ + vaddl.u8 q14, d5, d3 @Q14,Q7 = P0 + Q1 + vdup.8 d16, r3 @D16 contains beta_cb + vdup.8 d17, r6 @D17 contains beta_cr + vmlal.u8 q7, d6, d31 @ + vmlal.u8 q14, d7, d31 @Q14,Q7 = (X2(p1U) + p0U + q1U) + vcge.u8 q9, q11, q10 @Q9 = ( ABS(p0 - q0) >= Alpha ) + vcge.u8 q12, q12, q8 @Q12= ( ABS(q1 - q0) >= Beta ) + vcge.u8 q13, q13, q8 @Q13= ( ABS(p1 - p0) >= Beta ) + vrshrn.u16 d8, q4, #2 @ + vrshrn.u16 d9, q5, #2 @Q4 = (X2(q1U) + q0U + p1U + 2) >> 2 + vorr q9, q9, q12 @Q9 = ( ABS(p0 - q0) >= Alpha ) | ( ABS(q1 - q0) >= Beta ) + vrshrn.u16 d10, q7, #2 @ + vrshrn.u16 d11, q14, #2 @Q5 = (X2(p1U) + p0U + q1U + 2) >> 2 + vorr q9, q9, q13 @Q9 = ( ABS(p0 - q0) >= Alpha ) | ( ABS(q1 - q0) >= Beta ) | ( ABS(p1 - p0) >= Beta ) + vbit q5, q2, q9 @ + vbit q4, q0, q9 @ + vst2.8 {d10, d11}, [r4], r1 @ + vst2.8 {d8, d9}, [r4] @ + vpop {d8 - d15} + ldmfd sp!, {r4-r6, pc} @ + + + +@** +@******************************************************************************* +@* +@* @brief +@* Performs filtering of a chroma block vertical edge when the +@* boundary strength is set to 4 in high profile +@* +@* @par Description: +@* This operation is described in Sec. 8.7.2.4 under the title +@* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. +@* +@* @param[in] r0 - pu1_src +@* Pointer to the src sample q0 +@* +@* @param[in] r1 - src_strd +@* Source stride +@* +@* @param[in] r2 - alpha_cb +@* Alpha Value for the boundary in U +@* +@* @param[in] r3 - beta_cb +@* Beta Value for the boundary in U +@* +@* @param[in] sp(0) - alpha_cr +@* Alpha Value for the boundary in V +@* +@* @param[in] sp(4) - beta_cr +@* Beta Value for the boundary in V +@* +@* @returns +@* None +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* + + .global ih264_deblk_chroma_vert_bs4_a9 + +ih264_deblk_chroma_vert_bs4_a9: + + stmfd sp!, {r4, r5, r12, r14} + + sub r0, r0, #4 @point r0 to p1u of row0. + mov r12, r0 @keep a back up of r0 for buffer write + + ldr r4, [sp, #16] @r4 = alpha_cr + ldr r5, [sp, #20] @r5 = beta_cr + add r2, r2, r4, lsl #8 @r2 = (alpha_cr,alpha_cb) + add r3, r3, r5, lsl #8 @r3 = (beta_cr,beta_cb) + vpush {d8 - d15} + vld4.16 {d0[0], d2[0], d4[0], d6[0]}, [r0], r1 + vld4.16 {d0[1], d2[1], d4[1], d6[1]}, [r0], r1 + vld4.16 {d0[2], d2[2], d4[2], d6[2]}, [r0], r1 + vld4.16 {d0[3], d2[3], d4[3], d6[3]}, [r0], r1 + + vld4.16 {d1[0], d3[0], d5[0], d7[0]}, [r0], r1 + vld4.16 {d1[1], d3[1], d5[1], d7[1]}, [r0], r1 + vld4.16 {d1[2], d3[2], d5[2], d7[2]}, [r0], r1 + vld4.16 {d1[3], d3[3], d5[3], d7[3]}, [r0], r1 + + vdup.16 q11, r2 @Q11 = alpha + vdup.16 q12, r3 @Q12 = beta + vmov.i8 d31, #2 + + vabd.u8 q4, q1, q2 @|p0-q0| + vabd.u8 q5, q3, q2 @|q1-q0| + vabd.u8 q6, q0, q1 @|p1-p0| + vaddl.u8 q7, d2, d6 + vaddl.u8 q8, d3, d7 @(p0 + q1) + vclt.u8 q4, q4, q11 @|p0-q0| < alpha ? + vclt.u8 q5, q5, q12 @|q1-q0| < beta ? + vclt.u8 q6, q6, q12 @|p1-p0| < beta ? + vmlal.u8 q7, d0, d31 + vmlal.u8 q8, d1, d31 @2*p1 + (p0 + q1) + vaddl.u8 q9, d0, d4 + vaddl.u8 q10, d1, d5 @(p1 + q0) + vand.u8 q4, q4, q5 @|p0-q0| < alpha && |q1-q0| < beta + vmlal.u8 q9, d6, d31 + vmlal.u8 q10, d7, d31 @2*q1 + (p1 + q0) + + vrshrn.i16 d14, q7, #2 + vrshrn.i16 d15, q8, #2 @(2*p1 + (p0 + q1) + 2) >> 2 + vand.u8 q4, q4, q6 @|p0-q0| < alpha && |q1-q0| < beta && |p1-p0| < beta + vrshrn.i16 d18, q9, #2 + vrshrn.i16 d19, q10, #2 @(2*q1 + (p1 + q0) + 2) >> 2 + + vbit q1, q7, q4 + vbit q2, q9, q4 + + vst4.16 {d0[0], d2[0], d4[0], d6[0]}, [r12], r1 + vst4.16 {d0[1], d2[1], d4[1], d6[1]}, [r12], r1 + vst4.16 {d0[2], d2[2], d4[2], d6[2]}, [r12], r1 + vst4.16 {d0[3], d2[3], d4[3], d6[3]}, [r12], r1 + + vst4.16 {d1[0], d3[0], d5[0], d7[0]}, [r12], r1 + vst4.16 {d1[1], d3[1], d5[1], d7[1]}, [r12], r1 + vst4.16 {d1[2], d3[2], d5[2], d7[2]}, [r12], r1 + vst4.16 {d1[3], d3[3], d5[3], d7[3]}, [r12], r1 + vpop {d8 - d15} + ldmfd sp!, {r4, r5, r12, pc} + + + +@** +@******************************************************************************* +@* +@* @brief +@* Performs filtering of a chroma block horizontal edge for cases where the +@* boundary strength is less than 4 in high profile +@* +@* @par Description: +@* This operation is described in Sec. 8.7.2.4 under the title +@* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. +@* +@* @param[in] r0 - pu1_src +@* Pointer to the src sample q0 +@* +@* @param[in] r1 - src_strd +@* Source stride +@* +@* @param[in] r2 - alpha_cb +@* Alpha Value for the boundary in U +@* +@* @param[in] r3 - beta_cb +@* Beta Value for the boundary in U +@* +@* @param[in] sp(0) - alpha_cr +@* Alpha Value for the boundary in V +@* +@* @param[in] sp(4) - beta_cr +@* Beta Value for the boundary in V +@* +@* @param[in] sp(8) - u4_bs +@* Packed Boundary strength array +@* +@* @param[in] sp(12) - pu1_cliptab_cb +@* tc0_table for U +@* +@* @param[in] sp(16) - pu1_cliptab_cr +@* tc0_table for V +@* +@* @returns +@* None +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* + + .global ih264_deblk_chroma_horz_bslt4_a9 + +ih264_deblk_chroma_horz_bslt4_a9: + + stmfd sp!, {r4-r9, lr} @ + + ldrd r4, r5, [sp, #28] @R4 = alpha_cr , R5 = beta_cr + ldr r7, [sp, #36] @R7 = u4_bs + ldrd r8, r9, [sp, #40] @R8 = pu1_cliptab_cb , R9 = pu1_cliptab_cr + sub r0, r0, r1, lsl #1 @R0 = uc_edgePixelU pointing to p1 of chroma U + vpush {d8 - d15} + rev r7, r7 @ + vmov.32 d12[0], r7 @D12[0] = ui_Bs + + vld1.32 d16[0], [r8] @D16[0] contains cliptab_cb + vld1.32 d17[0], [r9] @D17[0] contains cliptab_cr + vld2.8 {d6, d7}, [r0], r1 @Q3=p1 + vtbl.8 d14, {d16}, d12 @Retreiving cliptab values for U + vtbl.8 d28, {d17}, d12 @Retrieving cliptab values for V + vmovl.u8 q6, d12 @Q6 = uc_Bs in each 16 bit scalar + mov r6, r0 @Keeping a backup of the pointer to chroma U P0 + vld2.8 {d4, d5}, [r0], r1 @Q2=p0 + vmov.i8 d30, #1 @ + vdup.8 d20, r2 @D20 contains alpha_cb + vdup.8 d21, r4 @D21 contains alpha_cr + vld2.8 {d0, d1}, [r0], r1 @Q0=q0 + vmovl.u8 q7, d14 @ + vmovl.u8 q14, d28 @ + vmov.i16 d15, d28 @D14 has cliptab values for U, D15 for V + vld2.8 {d2, d3}, [r0] @Q1=q1 + vsubl.u8 q5, d1, d5 @ + vsubl.u8 q4, d0, d4 @Q5,Q4 = (q0 - p0) + vabd.u8 q13, q3, q2 @Q13 = ABS(p1 - p0) + vshl.i16 q5, q5, #2 @Q5 = (q0 - p0)<<2 + vabd.u8 q11, q2, q0 @Q11 = ABS(p0 - q0) + vshl.i16 q4, q4, #2 @Q4 = (q0 - p0)<<2 + vsli.16 q7, q7, #8 @ + vabd.u8 q12, q1, q0 @Q12 = ABS(q1 - q0) + vcge.u8 q9, q11, q10 @Q9 = ( ABS(p0 - q0) >= Alpha ) + vsubl.u8 q10, d6, d2 @Q10 = (p1 - q1)L + vsubl.u8 q3, d7, d3 @Q3 = (p1 - q1)H + vdup.8 d16, r3 @Q8 contains beta_cb + vdup.8 d17, r5 @Q8 contains beta_cr + vadd.i16 q4, q4, q10 @ + vadd.i16 q5, q5, q3 @Q5,Q4 = [ (q0 - p0)<<2 ] + (p1 - q1) + vcge.u8 q12, q12, q8 @Q12= ( ABS(q1 - q0) >= Beta ) + vcgt.s16 d12, d12, #0 @Q6 = (us_Bs > 0) + vqrshrn.s16 d8, q4, #3 @ + vqrshrn.s16 d9, q5, #3 @Q4 = i_macro = (((q0 - p0)<<2) + (p1 - q1) + 4)>>3 + vadd.i8 d14, d14, d30 @D14 = C = C0+1 for U + vcge.u8 q13, q13, q8 @Q13= ( ABS(p1 - p0) >= Beta ) + vorr q9, q9, q12 @Q9 = ( ABS(p0 - q0) >= Alpha ) | ( ABS(q1 - q0) >= Beta ) + vabs.s8 q3, q4 @Q4 = ABS (i_macro) + vadd.i8 d15, d15, d30 @D15 = C = C0+1 for V + vmov.i8 d13, d12 @ + vorr q9, q9, q13 @Q9 = ( ABS(p0 - q0) >= Alpha ) | ( ABS(q1 - q0) >= Beta ) | ( ABS(p1 - p0) >= Beta ) + vmin.u8 q7, q3, q7 @Q7 = delta = (ABS(i_macro) > C) ? C : ABS(i_macro) + vbic q6, q6, q9 @final condition + vcge.s8 q4, q4, #0 @Q4 = (i_macro >= 0) + vand q7, q7, q6 @Making delta zero in places where values shouldn be filterd + vqadd.u8 q8, q2, q7 @Q8 = p0 + delta + vqsub.u8 q2, q2, q7 @Q2 = p0 - delta + vqadd.u8 q9, q0, q7 @Q9 = q0 + delta + vqsub.u8 q0, q0, q7 @Q0 = q0 - delta + vbif q8, q2, q4 @Q8 = (i_macro >= 0 ) ? (p0+delta) : (p0-delta) + vbif q0, q9, q4 @Q0 = (i_macro >= 0 ) ? (q0-delta) : (q0+delta) + vst2.8 {d16, d17}, [r6], r1 @ + vst2.8 {d0, d1}, [r6] @ + vpop {d8 - d15} + ldmfd sp!, {r4-r9, pc} @ + + + +@** +@******************************************************************************* +@* +@* @brief +@* Performs filtering of a chroma block vertical edge for cases where the +@* boundary strength is less than 4 in high profile +@* +@* @par Description: +@* This operation is described in Sec. 8.7.2.4 under the title +@* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. +@* +@* @param[in] r0 - pu1_src +@* Pointer to the src sample q0 +@* +@* @param[in] r1 - src_strd +@* Source stride +@* +@* @param[in] r2 - alpha_cb +@* Alpha Value for the boundary in U +@* +@* @param[in] r3 - beta_cb +@* Beta Value for the boundary in U +@* +@* @param[in] sp(0) - alpha_cr +@* Alpha Value for the boundary in V +@* +@* @param[in] sp(4) - beta_cr +@* Beta Value for the boundary in V +@* +@* @param[in] sp(8) - u4_bs +@* Packed Boundary strength array +@* +@* @param[in] sp(12) - pu1_cliptab_cb +@* tc0_table for U +@* +@* @param[in] sp(16) - pu1_cliptab_cr +@* tc0_table for V +@* +@* @returns +@* None +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* + + .global ih264_deblk_chroma_vert_bslt4_a9 + +ih264_deblk_chroma_vert_bslt4_a9: + + stmfd sp!, {r4-r7, r10-r12, r14} + + sub r0, r0, #4 @point r0 to p1u of row0. + ldrd r4, r5, [sp, #32] @R4 = alpha_cr , R5 = beta_cr + add r2, r2, r4, lsl #8 + add r3, r3, r5, lsl #8 + ldr r6, [sp, #40] @R6 = u4_bs + ldrd r10, r11, [sp, #44] @R10 = pu1_cliptab_cb , R11 = pu1_cliptab_cr + vpush {d8 - d15} + mov r12, r0 @keep a back up of R0 for buffer write + + vld4.16 {d0[0], d2[0], d4[0], d6[0]}, [r0], r1 + vld4.16 {d0[1], d2[1], d4[1], d6[1]}, [r0], r1 + vld4.16 {d0[2], d2[2], d4[2], d6[2]}, [r0], r1 + vld4.16 {d0[3], d2[3], d4[3], d6[3]}, [r0], r1 + + vld4.16 {d1[0], d3[0], d5[0], d7[0]}, [r0], r1 + vld4.16 {d1[1], d3[1], d5[1], d7[1]}, [r0], r1 + vld4.16 {d1[2], d3[2], d5[2], d7[2]}, [r0], r1 + vld4.16 {d1[3], d3[3], d5[3], d7[3]}, [r0], r1 + + + vdup.16 q11, r2 @Q11 = alpha + vabd.u8 q4, q1, q2 @|p0-q0| + vdup.16 q12, r3 @Q12 = beta + vabd.u8 q5, q3, q2 @|q1-q0| + vabd.u8 q6, q0, q1 @|p1-p0| + vclt.u8 q4, q4, q11 @|p0-q0| < alpha ? + vsubl.u8 q7, d0, d6 + vclt.u8 q5, q5, q12 @|q1-q0| < beta ? + vsubl.u8 q8, d1, d7 @(p1 - q1) + vclt.u8 q6, q6, q12 @|p1-p0| < beta ? + vsubl.u8 q9, d4, d2 + vand.u8 q4, q4, q5 @|p0-q0| < alpha && |q1-q0| < beta + vsubl.u8 q10, d5, d3 @(q0 - p0) + vmov.u16 q14, #4 + vld1.32 {d24[0]}, [r10] @Load ClipTable for U + vld1.32 {d25[0]}, [r11] @Load ClipTable for V + rev r6, r6 @Blocking strengths + vand.u8 q4, q4, q6 @|p0-q0| < alpha && |q1-q0| < beta && |p1-p0| < beta + + vmov.32 d10[0], r6 + + vmla.s16 q7, q9, q14 + vmla.s16 q8, q10, q14 @4*(q0 - p0) + (p1 - q1) + + vmovl.u8 q5, d10 + vsli.u16 d10, d10, #8 + vtbl.8 d12, {d24}, d10 @tC0 for U + vtbl.8 d13, {d25}, d10 @tC0 for V + vzip.8 d12, d13 + vmovl.u16 q5, d10 + vsli.u32 q5, q5, #16 + vmov.u8 q12, #1 + vadd.u8 q6, q6, q12 @tC0 + 1 + vcge.u8 q5, q5, q12 @u4_bS > 0 ? + vand.u8 q4, q4, q5 @|p0-q0| < alpha && |q1-q0| < beta && |p1-p0| < beta && u4_bs != 0 + + @ Q0 - Q3(inputs), + @ Q4 (|p0-q0| < alpha && |q1-q0| < beta && |p1-p0| < beta && u4_bs != 0), + @ Q6 (tC) + + vrshr.s16 q7, q7, #3 + vrshr.s16 q8, q8, #3 @(((q0 - p0) << 2) + (p1 - q1) + 4) >> 3) + + vcgt.s16 q9, q7, #0 + vcgt.s16 q10, q8, #0 + vmovn.i16 d18, q9 + vmovn.i16 d19, q10 @Q9 = sign(delta) + vabs.s16 q7, q7 + vabs.s16 q8, q8 + vmovn.u16 d14, q7 + vmovn.u16 d15, q8 + vmin.u8 q7, q7, q6 @Q7 = |delta| + + vqadd.u8 q10, q1, q7 @p0+|delta| + vqadd.u8 q11, q2, q7 @q0+|delta| + vqsub.u8 q12, q1, q7 @p0-|delta| + vqsub.u8 q13, q2, q7 @q0-|delta| + + vbit q12, q10, q9 @p0 + delta + vbit q11, q13, q9 @q0 - delta + + vbit q1, q12, q4 + vbit q2, q11, q4 + + vst4.16 {d0[0], d2[0], d4[0], d6[0]}, [r12], r1 + vst4.16 {d0[1], d2[1], d4[1], d6[1]}, [r12], r1 + vst4.16 {d0[2], d2[2], d4[2], d6[2]}, [r12], r1 + vst4.16 {d0[3], d2[3], d4[3], d6[3]}, [r12], r1 + + vst4.16 {d1[0], d3[0], d5[0], d7[0]}, [r12], r1 + vst4.16 {d1[1], d3[1], d5[1], d7[1]}, [r12], r1 + vst4.16 {d1[2], d3[2], d5[2], d7[2]}, [r12], r1 + vst4.16 {d1[3], d3[3], d5[3], d7[3]}, [r12], r1 + vpop {d8 - d15} + ldmfd sp!, {r4-r7, r10-r12, pc} + + + +@** +@******************************************************************************* +@* +@* @brief +@* Performs filtering of a chroma block vertical edge when the +@* boundary strength is set to 4 on calling twice in high profile +@* +@* @par Description: +@* This operation is described in Sec. 8.7.2.4 under the title +@* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. +@* +@* @param[in] r0 - pu1_src +@* Pointer to the src sample q0 +@* +@* @param[in] r1 - src_strd +@* Source stride +@* +@* @param[in] r2 - alpha_cb +@* Alpha Value for the boundary in U +@* +@* @param[in] r3 - beta_cb +@* Beta Value for the boundary in U +@* +@* @param[in] sp(0) - alpha_cr +@* Alpha Value for the boundary in V +@* +@* @param[in] sp(4) - beta_cr +@* Beta Value for the boundary in V +@* +@* @returns +@* None +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* + + .global ih264_deblk_chroma_vert_bs4_mbaff_a9 + +ih264_deblk_chroma_vert_bs4_mbaff_a9: + + stmfd sp!, {r4, r5, r12, r14} + + sub r0, r0, #4 @point r0 to p1u of row0. + mov r12, r0 @keep a back up of r0 for buffer write + ldrd r4, r5, [sp, #16] @R4 = alpha_cr , R5 = beta_cr + add r2, r2, r4, lsl #8 + add r3, r3, r5, lsl #8 + vpush {d8 - d15} + vld4.16 {d0[0], d1[0], d2[0], d3[0]}, [r0], r1 + vld4.16 {d0[1], d1[1], d2[1], d3[1]}, [r0], r1 + vld4.16 {d0[2], d1[2], d2[2], d3[2]}, [r0], r1 + vld4.16 {d0[3], d1[3], d2[3], d3[3]}, [r0], r1 + + vdup.16 d11, r2 @D11 = alpha + vdup.16 d12, r3 @D12 = beta + vmov.i8 d31, #2 + + vabd.u8 d4, d1, d2 @|p0-q0| + vabd.u8 d5, d3, d2 @|q1-q0| + vabd.u8 d6, d0, d1 @|p1-p0| + vaddl.u8 q14, d1, d3 @(p0 + q1) + vclt.u8 d4, d4, d11 @|p0-q0| < alpha ? + vclt.u8 d5, d5, d12 @|q1-q0| < beta ? + vclt.u8 d6, d6, d12 @|p1-p0| < beta ? + vmlal.u8 q14, d0, d31 @2*p1 + (p0 + q1) + vaddl.u8 q13, d0, d2 @(p1 + q0) + vand.u8 d4, d4, d5 @|p0-q0| < alpha && |q1-q0| < beta + vmlal.u8 q13, d3, d31 @2*q1 + (p1 + q0) + + vrshrn.i16 d7, q14, #2 @(2*p1 + (p0 + q1) + 2) >> 2 + vand.u8 d4, d4, d6 @|p0-q0| < alpha && |q1-q0| < beta && |p1-p0| < beta + vrshrn.i16 d9, q13, #2 @(2*q1 + (p1 + q0) + 2) >> 2 + + vbit d1, d7, d4 + vbit d2, d9, d4 + + vst4.16 {d0[0], d1[0], d2[0], d3[0]}, [r12], r1 + vst4.16 {d0[1], d1[1], d2[1], d3[1]}, [r12], r1 + vst4.16 {d0[2], d1[2], d2[2], d3[2]}, [r12], r1 + vst4.16 {d0[3], d1[3], d2[3], d3[3]}, [r12], r1 + vpop {d8 - d15} + ldmfd sp!, {r4, r5, r12, pc} + + + +@** +@******************************************************************************* +@* +@* @brief +@* Performs filtering of a chroma block vertical edge for cases where the +@* boundary strength is less than 4 on calling twice in high profile +@* +@* @par Description: +@* This operation is described in Sec. 8.7.2.4 under the title +@* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. +@* +@* @param[in] r0 - pu1_src +@* Pointer to the src sample q0 +@* +@* @param[in] r1 - src_strd +@* Source stride +@* +@* @param[in] r2 - alpha_cb +@* Alpha Value for the boundary in U +@* +@* @param[in] r3 - beta_cb +@* Beta Value for the boundary in U +@* +@* @param[in] sp(0) - alpha_cr +@* Alpha Value for the boundary in V +@* +@* @param[in] sp(4) - beta_cr +@* Beta Value for the boundary in V +@* +@* @param[in] sp(8) - u4_bs +@* Packed Boundary strength array +@* +@* @param[in] sp(12) - pu1_cliptab_cb +@* tc0_table for U +@* +@* @param[in] sp(16) - pu1_cliptab_cr +@* tc0_table for V +@* +@* @returns +@* None +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* + + .global ih264_deblk_chroma_vert_bslt4_mbaff_a9 + +ih264_deblk_chroma_vert_bslt4_mbaff_a9: + + stmfd sp!, {r4-r6, r10-r12, r14} + + sub r0, r0, #4 @point r0 to p1u of row0. + mov r12, r0 @keep a back up of r0 for buffer write + + ldrd r4, r5, [sp, #28] @R4 = alpha_cr , R5 = beta_cr + add r2, r2, r4, lsl #8 + add r3, r3, r5, lsl #8 + ldr r6, [sp, #36] @R6 = u4_bs + ldrd r10, r11, [sp, #40] @R10 = pu1_cliptab_cb , R11 = pu1_cliptab_cr + vpush {d8 - d15} + vld4.16 {d0[0], d1[0], d2[0], d3[0]}, [r0], r1 + vld4.16 {d0[1], d1[1], d2[1], d3[1]}, [r0], r1 + vld4.16 {d0[2], d1[2], d2[2], d3[2]}, [r0], r1 + vld4.16 {d0[3], d1[3], d2[3], d3[3]}, [r0], r1 + + vdup.16 d11, r2 @D11 = alpha + vabd.u8 d4, d1, d2 @|p0-q0| + vdup.16 d12, r3 @D12 = beta + vabd.u8 d5, d3, d2 @|q1-q0| + vabd.u8 d6, d0, d1 @|p1-p0| + vclt.u8 d4, d4, d11 @|p0-q0| < alpha ? + vclt.u8 d5, d5, d12 @|q1-q0| < beta ? + vsubl.u8 q14, d0, d3 @(p1 - q1) + vclt.u8 d6, d6, d12 @|p1-p0| < beta ? + vand.u8 d4, d4, d5 @|p0-q0| < alpha && |q1-q0| < beta + vsubl.u8 q12, d2, d1 @(q0 - p0) + vmov.u16 q10, #4 + + vld1.32 {d31[1]}, [r10] @Load ClipTable for U + vld1.32 {d31[0]}, [r11] @Load ClipTable for V + rev r6, r6 @Blocking strengths + vand.u8 d4, d4, d6 @|p0-q0| < alpha && |q1-q0| < beta && |p1-p0| < beta + vmov.32 d22[0], r6 + vmla.s16 q14, q12, q10 @4*(q0 - p0) + (p1 - q1) + vmovl.u8 q11, d22 + vsli.u16 d22, d22, #8 + vmov.u16 d13, #4 + vadd.u8 d22, d22, d13 + vtbl.8 d6, {d31}, d22 @tC0 + vmov.u8 d12, #1 + vsub.u8 d22, d22, d13 + vadd.u8 d6, d6, d12 @tC0 + 1 + vcge.u8 d5, d22, d12 @u4_bS > 0 ? + vand.u8 d4, d4, d5 @|p0-q0| < alpha && |q1-q0| < beta && |p1-p0| < beta && u4_bs != 0 + + @ D0 - D3(inputs), + @ D4 (|p0-q0| < alpha && |q1-q0| < beta && |p1-p0| < beta && u4_bs != 0), + @ D6 (tC) + + vrshr.s16 q14, q14, #3 @(((q0 - p0) << 2) + (p1 - q1) + 4) >> 3) + + vcgt.s16 q13, q14, #0 + vmovn.i16 d9, q13 @D9 = sign(delta) + vabs.s16 q14, q14 + vmovn.u16 d7, q14 + vmin.u8 d7, d7, d6 @D7 = |delta| + + vqadd.u8 d10, d1, d7 @p0+|delta| + vqadd.u8 d11, d2, d7 @q0+|delta| + vqsub.u8 d12, d1, d7 @p0-|delta| + vqsub.u8 d13, d2, d7 @q0-|delta| + + vbit d12, d10, d9 @p0 + delta + vbit d11, d13, d9 @q0 - delta + + vbit d1, d12, d4 + vbit d2, d11, d4 + + vst4.16 {d0[0], d1[0], d2[0], d3[0]}, [r12], r1 + vst4.16 {d0[1], d1[1], d2[1], d3[1]}, [r12], r1 + vst4.16 {d0[2], d1[2], d2[2], d3[2]}, [r12], r1 + vst4.16 {d0[3], d1[3], d2[3], d3[3]}, [r12], r1 + vpop {d8 - d15} + ldmfd sp!, {r4-r6, r10-r12, pc} + + + diff --git a/dependencies/ih264d/common/arm/ih264_deblk_luma_a9.s b/dependencies/ih264d/common/arm/ih264_deblk_luma_a9.s new file mode 100644 index 00000000..9217ed2e --- /dev/null +++ b/dependencies/ih264d/common/arm/ih264_deblk_luma_a9.s @@ -0,0 +1,1092 @@ +@/****************************************************************************** +@ * +@ * Copyright (C) 2015 The Android Open Source Project +@ * +@ * Licensed under the Apache License, Version 2.0 (the "License"); +@ * you may not use this file except in compliance with the License. +@ * You may obtain a copy of the License at: +@ * +@ * http://www.apache.org/licenses/LICENSE-2.0 +@ * +@ * Unless required by applicable law or agreed to in writing, software +@ * distributed under the License is distributed on an "AS IS" BASIS, +@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@ * See the License for the specific language governing permissions and +@ * limitations under the License. +@ * +@ ***************************************************************************** +@ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +@*/ +@/*****************************************************************************/ +@/* */ +@/* File Name : ih264_deblk_luma_a9.s */ +@/* */ +@/* Description : Contains function definitions for deblocking luma */ +@/* edge. Functions are coded in NEON assembly and can */ +@/* be compiled using ARM RVDS. */ +@/* */ +@/* List of Functions : ih264_deblk_luma_vert_bs4_a9() */ +@/* ih264_deblk_luma_vert_bslt4_a9() */ +@/* ih264_deblk_luma_horz_bs4_a9() */ +@/* ih264_deblk_luma_horz_bslt4_a9() */ +@/* ih264_deblk_luma_vert_bs4_mbaff_a9() */ +@/* ih264_deblk_luma_vert_bslt4_mbaff_a9() */ +@/* */ +@/* Issues / Problems : None */ +@/* */ +@/* Revision History : */ +@/* */ +@/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +@/* 28 11 2013 Ittiam Draft */ +@/* 05 01 2015 Kaushik Added double-call functions for */ +@/* Senthoor vertical deblocking. */ +@/* */ +@/*****************************************************************************/ + + +.text +.p2align 2 + +@** +@******************************************************************************* +@* +@* @brief +@* Performs filtering of a luma block horizontal edge for cases where the +@* boundary strength is less than 4 +@* +@* @par Description: +@* This operation is described in Sec. 8.7.2.4 under the title +@* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. +@* +@* @param[in] r0 - pu1_src +@* Pointer to the src sample q0 +@* +@* @param[in] r1 - src_strd +@* Source stride +@* +@* @param[in] r2 - alpha +@* Alpha Value for the boundary +@* +@* @param[in] r3 - beta +@* Beta Value for the boundary +@* +@* @param[in] sp(0) - u4_bs +@* Packed Boundary strength array +@* +@* @param[in] sp(4) - pu1_cliptab +@* tc0_table +@* +@* @returns +@* None +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* + + .global ih264_deblk_luma_horz_bslt4_a9 + +ih264_deblk_luma_horz_bslt4_a9: + + stmfd sp!, {r4-r7, lr} + + ldrd r4, r5, [sp, #0x14] @r4 = ui_Bs , r5 = *puc_ClpTab + vpush {d8 - d15} + sub r0, r0, r1, lsl #1 @R1 = uc_Horizonpad + sub r0, r0, r1 @r0 pointer to p2 + rev r4, r4 @ + vld1.8 {q5}, [r0], r1 @p2 values are loaded into q5 + vmov.32 d12[0], r4 @d12[0] = ui_Bs + mov r6, r0 @keeping backup of pointer to p1 + vld1.8 {q4}, [r0], r1 @p1 values are loaded into q4 + mov r7, r0 @keeping backup of pointer to p0 + vld1.8 {q3}, [r0], r1 @p0 values are loaded into q3 + vmovl.u8 q6, d12 @q6 = uc_Bs in each 16 bt scalar + vld1.8 {q0}, [r0], r1 @q0 values are loaded into q0 + vabd.u8 q13, q4, q3 @Q13 = ABS(p1 - p0) + vld1.8 {q1}, [r0], r1 @q1 values are loaded into q1 + vabd.u8 q11, q3, q0 @Q11 = ABS(p0 - q0) + vld1.32 d16[0], [r5] @D16[0] contains cliptab + vabd.u8 q12, q1, q0 @Q12 = ABS(q1 - q0) + vld1.8 {q2}, [r0], r1 @q2 values are loaded into q2 + vtbl.8 d14, {d16}, d12 @ + vdup.8 q10, r2 @Q10 contains alpha + vdup.8 q8, r3 @Q8 contains beta + vmovl.u16 q6, d12 @ + vmovl.u16 q7, d14 @ + vabd.u8 q14, q5, q3 @Q14 = Ap = ABS(p2 - p0) + vabd.u8 q15, q2, q0 @Q15 = Aq = ABS(q2 - q0) + vcgt.s32 q6, q6, #0 @Q6 = (us_Bs > 0) + vsli.32 q7, q7, #8 @ + vcge.u8 q9, q11, q10 @Q9 = ( ABS(p0 - q0) >= Alpha ) + vcge.u8 q12, q12, q8 @Q12=( ABS(q1 - q0) >= Beta ) + vcge.u8 q13, q13, q8 @Q13=( ABS(p1 - p0) >= Beta ) + vcgt.u8 q10, q8, q14 @Q10=(Ap= Alpha ) | ( ABS(q1 - q0) >= Beta ) + vsubl.u8 q15, d1, d7 @ + vsubl.u8 q12, d0, d6 @Q15,Q12 = (q0 - p0) + vorr q9, q9, q13 @Q9 = ( ABS(p0 - q0) >= Alpha ) | ( ABS(q1 - q0) >= Beta ) | ( ABS(p1 - p0) >= Beta ) + vsubl.u8 q14, d8, d2 @Q14 = (p1 - q1)L + vshl.i16 q13, q15, #2 @Q13 = (q0 - p0)<<2 + vshl.i16 q12, q12, #2 @Q12 = (q0 - p0)<<2 + vsubl.u8 q15, d9, d3 @Q15 = (p1 - q1)H + vbic q6, q6, q9 @final condition + vadd.i16 q12, q12, q14 @ + vadd.i16 q13, q13, q15 @Q13,Q12 = [ (q0 - p0)<<2 ] + (p1 - q1) + vsub.i8 q9, q7, q10 @Q9 = C0 + (Ap < Beta) + vrhadd.u8 q8, q3, q0 @Q8 = ((p0+q0+1) >> 1) + vqrshrn.s16 d24, q12, #3 @ + vqrshrn.s16 d25, q13, #3 @Q12 = i_macro = (((q0 - p0)<<2) + (p1 - q1) + 4)>>3 + vsub.i8 q9, q9, q11 @Q9 = C0 + (Ap < Beta) + (Aq < Beta) + vand.i8 q10, q10, q6 @ + vand.i8 q11, q11, q6 @ + vabs.s8 q13, q12 @Q13 = ABS (i_macro) + vaddl.u8 q14, d17, d11 @ + vaddl.u8 q5, d16, d10 @Q14,Q5 = p2 + (p0+q0+1)>>1 + vaddl.u8 q15, d17, d5 @ + vmin.u8 q9, q13, q9 @Q9 = delta = (ABS(i_macro) > C) ? C : ABS(i_macro) + vshll.u8 q13, d9, #1 @ + vaddl.u8 q2, d16, d4 @Q15,Q2 = q2 + (p0+q0+1)>>1 + vshll.u8 q8, d8, #1 @Q13,Q8 = (p1<<1) + vand q9, q9, q6 @Making delta zero in places where values shouldn be filterd + vsub.i16 q14, q14, q13 @Q14,Q5 = [p2 + (p0+q0+1)>>1] - (p1<<1) + vsub.i16 q5, q5, q8 @ + vshll.u8 q8, d2, #1 @ + vshll.u8 q13, d3, #1 @Q13,Q8 = (q1<<1) + vqshrn.s16 d29, q14, #1 @ + vqshrn.s16 d28, q5, #1 @Q14 = i_macro_p1 + vsub.i16 q2, q2, q8 @ + vsub.i16 q15, q15, q13 @Q15,Q2 = [q2 + (p0+q0+1)>>1] - (q1<<1) + vneg.s8 q13, q7 @Q13 = -C0 + vmin.s8 q14, q14, q7 @Q14 = min(C0,i_macro_p1) + vcge.s8 q12, q12, #0 @Q12 = (i_macro >= 0) + vqshrn.s16 d31, q15, #1 @ + vqshrn.s16 d30, q2, #1 @Q15 = i_macro_q1 + vmax.s8 q14, q14, q13 @Q14 = max( - C0 , min(C0, i_macro_p1) ) + vqadd.u8 q8, q3, q9 @Q8 = p0 + delta + vqsub.u8 q3, q3, q9 @Q3 = p0 - delta + vmin.s8 q15, q15, q7 @Q15 = min(C0,i_macro_q1) + vand.i8 q14, q10, q14 @condition check Ap= 0 ) ? (p0+delta) : (p0-delta) + vbif q0, q7, q12 @Q0 = (i_macro >= 0 ) ? (q0-delta) : (q0+delta) + vadd.i8 q14, q14, q4 @ + vand.i8 q15, q11, q15 @condition check Aq= Alpha + vcge.u8 q7, q7, q1 @ABS(q1 - q0) >= Beta + vcge.u8 q8, q8, q1 @ABS(p1 - p0) >= Beta + vmov.i8 q10, #2 + vorr q9, q9, q7 @ABS(p0 - q0) >= Alpha || ABS(q1 - q0) >= Beta + vld1.8 {d14, d15}, [r0], r1 @load q2 to Q7, q0 = q0 + src_strd + vorr q9, q9, q8 @ABS(p0 - q0) >= Alpha || ABS(q1 - q0) >= Beta || ABS(p1 - p0) >= Beta + vsra.u8 q10, q0, #2 @((Alpha >> 2) + 2) + vabd.u8 q11, q7, q2 @Aq = ABS(q2 - q0) + vaddl.u8 q12, d4, d6 @p0+q0 L + vaddl.u8 q13, d5, d7 @p0+q0 H + vclt.u8 q11, q11, q1 @Aq < Beta + vclt.u8 q10, q6, q10 @(ABS(p0 - q0) <((Alpha >>2) + 2)) + + @ Deblock Filtering q0', q1', q2' + vaddw.u8 q14, q12, d8 @p0+q0+q1 L + vaddw.u8 q15, q13, d9 @p0+q0+q1 H + vand q11, q11, q10 @(Aq < Beta && ABS(p0 - q0) <((Alpha >>2) + 2)) + @ q0' if (Aq < Beta && ABS(p0 - q0) <((Alpha >>2) + 2)) TRUE + vadd.i16 q8, q14, q14 @2*(p0+q0+q1)L + vadd.i16 q0, q15, q15 @2*(p0+q0+q1)H + vaddw.u8 q8, q8, d14 @2*(p0+q0+q1)+q2 L + vaddw.u8 q0, q0, d15 @2*(p0+q0+q1)+q2 H + vaddw.u8 q8, q8, d10 @2*(p0+q0+q1)+q2 +p1 L + vaddw.u8 q0, q0, d11 @2*(p0+q0+q1)+q2 +p1 H + vrshrn.u16 d12, q8, #3 @(2*(p0+q0+q1)+q2 +p1 +4)>> 3 L [q0'] + vrshrn.u16 d13, q0, #3 @(2*(p0+q0+q1)+q2 +p1 +4)>> 3 H [q0'] + @ q0" if (Aq < Beta && ABS(p0 - q0) <((Alpha >>2) + 2)) FALSE + vaddl.u8 q8, d8, d8 @2*q1 L + vaddl.u8 q0, d9, d9 @2*q1 H + vaddw.u8 q8, q8, d4 @2*q1+q0 L + vaddw.u8 q0, q0, d5 @2*q1+q0 H + vaddw.u8 q8, q8, d10 @2*q1+q0+p1 L + vaddw.u8 q0, q0, d11 @2*q1+q0+p1 H + vrshrn.u16 d16, q8, #2 @(2*q1+q0+p1+2)>>2 L [q0"] + vrshrn.u16 d17, q0, #2 @(2*q1+q0+p1+2)>>2 H [q0"] + @ q1' + vaddw.u8 q14, q14, d14 @p0+q0+q1+q2 L + vaddw.u8 q15, q15, d15 @p0+q0+q1+q2 H + vld1.8 {q0}, [r0], r1 @load q3 to Q0, q0 = q0 + src_strd + vbit q8, q6, q11 @choosing between q0' and q0" depending on condn + sub r0, r0, r1, lsl #2 @pointer to q0 + vbic q11, q11, q9 @((ABS(p0 - q0) >= Alpha || ABS(q1 - q0) >= Beta || ABS(p1 - p0) >= Beta)) + @ && (Aq < Beta && ABS(p0 - q0) <((Alpha >>2) + 2)) + vrshrn.u16 d12, q14, #2 @(p0+q0+q1+q2+2)>>2 L [q1'] + vrshrn.u16 d13, q15, #2 @(p0+q0+q1+q2+2)>>2 H [q1'] + vbif q2, q8, q9 @choose q0 or filtered q0 + @ q2' + vaddl.u8 q8, d14, d0 @q2+q3,L + vaddl.u8 q0, d15, d1 @q2+q3,H + vadd.i16 q14, q14, q8 @p0+q0+q1+2*q2+q3 L + vst1.8 {d4, d5}, [r0], r1 @store q0 + vadd.i16 q15, q15, q0 @p0+q0+q1+2*q2+q3 H + vadd.i16 q14, q14, q8 @p0+q0+q1+3*q2+2*q3 L + vadd.i16 q15, q15, q0 @p0+q0+q1+3*q2+2*q3 H + vrshrn.u16 d0, q14, #3 @(p0+q0+q1+3*q2+2*q3+4)>>3 L [q2'] + vrshrn.u16 d1, q15, #3 @(p0+q0+q1+3*q2+2*q3+4)>>3 H [q2'] + vld1.8 {d30, d31}, [r3] @load p2 to Q15 + vbif q6, q4, q11 @choose q1 or filtered value of q1 + + vabd.u8 q8, q15, q3 @Ap,ABS(p2 - p0) + vaddw.u8 q12, q12, d10 @p0+q0+p1 L + vbif q0, q7, q11 @choose q2 or filtered q2 + vaddw.u8 q13, q13, d11 @p0+q0+p1 H + vst1.8 {d12, d13}, [r0], r1 @store q1 + vclt.u8 q8, q8, q1 @Ap < Beta + vadd.i16 q14, q12, q12 @2*(p0+q0+p1) L + vadd.i16 q2, q13, q13 @2*(p0+q0+p1) H + vst1.8 {d0, d1}, [r0], r1 @store q2 + vand q10, q10, q8 @((Ap < Beta) && (ABS(p0 - q0) <((Alpha >>2) + 2))) + vaddw.u8 q14, q14, d30 @2*(p0+q0+p1)+p2 l + vaddw.u8 q2, q2, d31 @2*(p0+q0+p1)+p2 H + vaddw.u8 q14, q14, d8 @2*(p0+q0+p1)+p2+q1 L + vaddw.u8 q2, q2, d9 @2*(p0+q0+p1)+p2+q1 H + vrshrn.u16 d28, q14, #3 @(2*(p0+q0+p1)+p2+q1+4)>>3 L,p0' + vrshrn.u16 d29, q2, #3 @(2*(p0+q0+p1)+p2+q1+4)>>3 H,p0' + vmov.i8 d0, #2 + vmov.i16 d1, #2 + vaddl.u8 q1, d6, d8 @p0+q1 L + vmlal.u8 q1, d10, d0 @2*p1+p0+q1 L + vaddl.u8 q8, d7, d9 @p0+q1 H + vmlal.u8 q8, d11, d0 @2*p1+p0+q1 H + vaddw.u8 q6, q12, d30 @(p0+q0+p1) +p2 L + vld1.8 {d24, d25}, [r2] @load p3,Q12 + vaddw.u8 q2, q13, d31 @(p0+q0+p1) +p2 H + vaddl.u8 q4, d30, d24 @p2+p3 L + vrshrn.u16 d26, q6, #2 @((p0+q0+p1)+p2 +2)>>2,p1' L + vrshrn.u16 d2, q1, #2 @(2*p1+p0+q1+2)>>2,p0"L + vrshrn.u16 d27, q2, #2 @((p0+q0+p1)+p2 +2)>>2,p1' H + vrshrn.u16 d3, q8, #2 @(2*p1+p0+q1+2)>>2,p0" H + vaddl.u8 q8, d31, d25 @p2+p3 H + vmla.u16 q6, q4, d1[0] @(p0+q0+p1)+3*p2+2*p3 L + vmla.u16 q2, q8, d1[0] @(p0+q0+p1)+3*p2+2*p3 H + vbic q8, q10, q9 @((ABS(p0 - q0) >= Alpha || ABS(q1 - q0) >= Beta || ABS(p1 - p0) >= Beta)) + @&& (Ap < Beta && ABS(p0 - q0) <((Alpha >>2) + 2)) + vbit q1, q14, q10 @choosing between po' and p0" + vrshrn.u16 d12, q6, #3 @((p0+q0+p1)+3*p2+2*p3+4)>>3 L p2' + vrshrn.u16 d13, q2, #3 @((p0+q0+p1)+3*p2+2*p3+4)>>3 H p2' + vbif q3, q1, q9 @choosing between p0 and filtered value of p0 + vbit q5, q13, q8 @choosing between p1 and p1' + vbit q15, q6, q8 @choosing between p2 and p2' + vst1.8 {d6, d7}, [r12] @store p0 + vst1.8 {d10, d11}, [r14] @store p1 + vst1.8 {d30, d31}, [r3] @store p2 + vpop {d8 - d15} + ldmfd sp!, {r12, pc} + + + +@** +@******************************************************************************* +@* +@* @brief +@* Performs filtering of a luma block vertical edge for cases where the +@* boundary strength is less than 4 +@* +@* @par Description: +@* This operation is described in Sec. 8.7.2.4 under the title +@* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. +@* +@* @param[in] r0 - pu1_src +@* Pointer to the src sample q0 +@* +@* @param[in] r1 - src_strd +@* Source stride +@* +@* @param[in] r2 - alpha +@* Alpha Value for the boundary +@* +@* @param[in] r3 - beta +@* Beta Value for the boundary +@* +@* @param[in] sp(0) - u4_bs +@* Packed Boundary strength array +@* +@* @param[in] sp(4) - pu1_cliptab +@* tc0_table +@* +@* @returns +@* None +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* + + .global ih264_deblk_luma_vert_bslt4_a9 + +ih264_deblk_luma_vert_bslt4_a9: + + stmfd sp!, {r12, lr} + + sub r0, r0, #4 @pointer uc_edgePixel-4 + ldr r12, [sp, #8] @r12 = ui_Bs + ldr r14, [sp, #12] @r14 = *puc_ClpTab + vpush {d8 - d15} + @loading p3:p2:p1:p0:q0:q1:q2:q3 for every row + vld1.8 {d0}, [r0], r1 @row1 + vld1.8 d2, [r0], r1 @row2 + vld1.8 d4, [r0], r1 @row3 + rev r12, r12 @reversing ui_bs + vld1.8 d6, [r0], r1 @row4 + vmov.32 d18[0], r12 @d12[0] = ui_Bs + vld1.32 d16[0], [r14] @D16[0] contains cliptab + vld1.8 d8, [r0], r1 @row5 + vmovl.u8 q9, d18 @q6 = uc_Bs in each 16 bt scalar + vld1.8 d10, [r0], r1 @row6 + vld1.8 d12, [r0], r1 @row7 + vtbl.8 d16, {d16}, d18 @puc_ClipTab[uc_Bs] + vld1.8 d14, [r0], r1 @row8 + vld1.8 d1, [r0], r1 @row9 + vmovl.u16 q8, d16 @ + vld1.8 d3, [r0], r1 @row10 + vld1.8 d5, [r0], r1 @row11 + vld1.8 d7, [r0], r1 @row12 + vsli.32 q8, q8, #8 @ + vld1.8 d9, [r0], r1 @row13 + vld1.8 d11, [r0], r1 @row14 + vld1.8 d13, [r0], r1 @row15 + vsli.32 q8, q8, #16 @Q8 = C0 + vld1.8 d15, [r0], r1 @row16 + + @taking two 8x8 transposes + @2X2 transposes + vtrn.8 d0, d2 @row1 &2 + vtrn.8 d4, d6 @row3&row4 + vtrn.8 d8, d10 @row5&6 + vtrn.8 d12, d14 @row7 & 8 + vtrn.8 d1, d3 @row9 &10 + vtrn.8 d5, d7 @row11 & 12 + vtrn.8 d9, d11 @row13 &14 + vtrn.8 d13, d15 @row15 & 16 + @4x4 transposes + vtrn.16 d2, d6 @row2 & row4 + vtrn.16 d10, d14 @row6 & row8 + vtrn.16 d3, d7 @row10 & 12 + vtrn.16 d11, d15 @row14 & row16 + vtrn.32 d6, d14 @row4 & 8 + vtrn.32 d7, d15 @row 12 & 16 + + @now Q3 ->p0 and Q7->q3 + vtrn.16 d0, d4 @row1 & 3 + vtrn.16 d8, d12 @row 5 & 7 + vtrn.16 d1, d5 @row9 & row11 + vtrn.16 d9, d13 @row13 & row15 + vtrn.32 d0, d8 @row1 & row5 + vtrn.32 d1, d9 @row9 & 13 + + @now Q0->p3 & Q4->q0 + @starting processing as p0 and q0 are now ready + vtrn.32 d2, d10 @row2 &6 + vrhadd.u8 q10, q3, q4 @((p0 + q0 + 1) >> 1) + vtrn.32 d3, d11 @row10&row14 + vmov.i8 d19, #2 + @now Q1->p2 & Q5->q1 + vtrn.32 d4, d12 @row3 & 7 + vabd.u8 q11, q3, q4 @ABS(p0 - q0) + vtrn.32 d5, d13 @row11 & row15 + vaddl.u8 q12, d20, d2 @(p2 + ((p0 + q0 + 1) >> 1) L + @now Q2->p1,Q6->q2 + vaddl.u8 q13, d21, d3 @(p2 + ((p0 + q0 + 1) >> 1) H + vmlsl.u8 q12, d4, d19 @(p2 + ((p0 + q0 + 1) >> 1) - (p1 << 1)) L + vmlsl.u8 q13, d5, d19 @(p2 + ((p0 + q0 + 1) >> 1) - (p1 << 1)) H + vdup.8 q14, r2 @alpha + vcle.u8 q11, q14, q11 @ABS(p0 - q0) >= Alpha(Alpha <=ABS(p0 - q0)) + vdup.i8 q14, r3 @beta + vabd.u8 q15, q5, q4 @ABS(q1 - q0) + vqshrn.s16 d24, q12, #1 @((p2 + ((p0 + q0 + 1) >> 1) - (p1 << 1)) >> 1) L + vqshrn.s16 d25 , q13, #1 @((p2 + ((p0 + q0 + 1) >> 1) - (p1 << 1)) >> 1) H + vcge.u8 q15, q15, q14 @ABS(q1 - q0) >= Beta + vabd.u8 q13, q2, q3 @ABS(p1 - p0) + vmin.s8 q12, q12, q8 @min(deltap1 ,C0) + vorr q11, q11, q15 @ABS(q1 - q0) >= Beta ||ABS(p0 - q0) >= Alpha + vneg.s8 q15, q8 @-C0 + vcge.u8 q13, q13, q14 @ABS(p1 - p0) >= Beta + vmax.s8 q12, q12, q15 @max(deltap1,-C0) + vorr q11, q11, q13 @ABS(p0 - q0) >= Alpha || ABS(q1 - q0) >= Beta || ABS(p1 - p0) >= Beta) + vmovl.u16 q13, d18 @ui_bs + vaddl.u8 q9, d20, d12 @q2 + ((p0 + q0 + 1) >> 1) L + vceq.u32 q13, q13, #0 @ui_bs == 0 + vsubw.u8 q9, q9, d10 @(q2 + ((p0 + q0 + 1) >> 1) - q1) L + vaddl.u8 q10, d21, d13 @q2 + ((p0 + q0 + 1) >> 1) H + vsubw.u8 q9, q9, d10 @(q2 + ((p0 + q0 + 1) >> 1) - 2*q1)L + vsubw.u8 q10, q10, d11 @(q2 + ((p0 + q0 + 1) >> 1) - q1) H + vorr q13, q13, q11 @(ABS(p0 - q0) >= Alpha || ABS(q1 - q0) >= Beta || ABS(p1 - p0) >= Beta)) &&(ui_bs) + vsubw.u8 q10, q10, d11 @(q2 + ((p0 + q0 + 1) >> 1) - 2*q1) H + vqshrn.s16 d18, q9, #1 @((q2 + ((p0 + q0 + 1) >> 1) - (q1 << 1)) >> 1) L + vabd.u8 q11, q1, q3 @Ap = ABS(p2 - p0) + vqshrn.s16 d19, q10, #1 @((q2 + ((p0 + q0 + 1) >> 1) - (q1 << 1)) >> 1) H + vabd.u8 q10, q6, q4 @Aq= ABS(q2 - q0) + vclt.u8 q11, q11, q14 @Ap < Beta + vmin.s8 q9, q9, q8 @min(delatq1,C0) + vclt.u8 q10, q10, q14 @Aq > 3); L + vrshrn.s16 d29, q15, #3 @delta = ((((q0 - p0) << 2) + (p1 - q1) + 4) >> 3) H + vsub.u8 q8, q8, q10 @C0 + (Ap < Beta) + (Aq < Beta) + vbic q10, q10, q13 @final condition for q1 + vabs.s8 q15, q14 @abs(delta) + vand q12, q12, q11 @delatp1 + vand q9, q9, q10 @delta q1 + vmin.u8 q15, q15, q8 @min((abs(delta),C) + vadd.i8 q2, q2, q12 @p1+deltap1 + vadd.i8 q5, q5, q9 @q1+deltaq1 + vbic q15, q15, q13 @abs(delta) of pixels to be changed only + vcge.s8 q14, q14, #0 @sign(delta) + vqsub.u8 q11, q3, q15 @clip(p0-delta) + vtrn.8 d0, d2 @row1 &2 + vqadd.u8 q3, q3, q15 @clip(p0+delta) + vtrn.8 d1, d3 @row9 &10 + vqadd.u8 q12, q4, q15 @clip(q0+delta) + vtrn.8 d12, d14 @row7 & 8 + vqsub.u8 q4, q4, q15 @clip(q0-delta) + vtrn.8 d13, d15 @row15 & 16 + vbif q3, q11, q14 @p0 + vbif q4, q12, q14 @q0 + vtrn.8 d4, d6 @row3&row4 + vtrn.8 d8, d10 @row5&6 + vtrn.8 d5, d7 @row11 & 12 + vtrn.8 d9, d11 @row13 &14 + vtrn.16 d2, d6 @row2 & row4 + vtrn.16 d10, d14 @row6 & row8 + vtrn.16 d3, d7 @row10 & 12 + vtrn.16 d11, d15 @row14 & row16 + vtrn.32 d6, d14 @row4 & 8 + vtrn.32 d7, d15 @row 12 & 16 + @now Q3 ->p0 and Q7->q3 + vtrn.16 d0, d4 @row1 & 3 + vtrn.16 d8, d12 @row 5 & 7 + vtrn.16 d1, d5 @row9 & row11 + vtrn.16 d9, d13 @row13 & row15 + sub r0, r0, r1, lsl#4 @restore pointer + vtrn.32 d0, d8 @row1 & row5 + vtrn.32 d1, d9 @row9 & 13 + vtrn.32 d2, d10 @row2 &6 + vtrn.32 d3, d11 @row10&row14 + vtrn.32 d4, d12 @row3 & 7 + vtrn.32 d5, d13 @row11 & row15 + vst1.8 {d0}, [r0], r1 @row1 + vst1.8 d2, [r0], r1 @row2 + vst1.8 d4, [r0], r1 @row3 + vst1.8 d6, [r0], r1 @row4 + vst1.8 d8, [r0], r1 @row5 + vst1.8 d10, [r0], r1 @row6 + vst1.8 d12, [r0], r1 @row7 + vst1.8 d14, [r0], r1 @row8 + vst1.8 d1, [r0], r1 @row9 + vst1.8 d3, [r0], r1 @row10 + vst1.8 d5, [r0], r1 @row11 + vst1.8 d7, [r0], r1 @row12 + vst1.8 d9, [r0], r1 @row13 + vst1.8 d11, [r0], r1 @row14 + vst1.8 d13, [r0], r1 @row15 + vst1.8 d15, [r0], r1 @row16 + vpop {d8 - d15} + ldmfd sp!, {r12, pc} + + + +@** +@******************************************************************************* +@* +@* @brief +@* Performs filtering of a luma block vertical edge when the +@* boundary strength is set to 4 +@* +@* @par Description: +@* This operation is described in Sec. 8.7.2.4 under the title +@* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. +@* +@* @param[in] r0 - pu1_src +@* Pointer to the src sample q0 +@* +@* @param[in] r1 - src_strd +@* Source stride +@* +@* @param[in] r2 - alpha +@* Alpha Value for the boundary +@* +@* @param[in] r3 - beta +@* Beta Value for the boundary +@* +@* @returns +@* None +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* + + .global ih264_deblk_luma_vert_bs4_a9 + +ih264_deblk_luma_vert_bs4_a9: + + stmfd sp!, {r12, lr} + vpush {d8 - d15} + sub r0, r0, #4 @pointer uc_edgePixel-4 + @loading p3:p2:p1:p0:q0:q1:q2:q3 for every row + vld1.8 d0, [r0], r1 @row1 + vld1.8 d2, [r0], r1 @row2 + vld1.8 d4, [r0], r1 @row3 + vld1.8 d6, [r0], r1 @row4 + vld1.8 d8, [r0], r1 @row5 + vld1.8 d10, [r0], r1 @row6 + vld1.8 d12, [r0], r1 @row7 + vld1.8 d14, [r0], r1 @row8 + vld1.8 d1, [r0], r1 @row9 + vld1.8 d3, [r0], r1 @row10 + vld1.8 d5, [r0], r1 @row11 + vld1.8 d7, [r0], r1 @row12 + vld1.8 d9, [r0], r1 @row13 + vld1.8 d11, [r0], r1 @row14 + vld1.8 d13, [r0], r1 @row15 + vld1.8 d15, [r0], r1 @row16 + @taking two 8x8 transposes + @2X2 transposes + vtrn.8 d0, d2 @row1 &2 + vtrn.8 d4, d6 @row3&row4 + vtrn.8 d8, d10 @row5&6 + vtrn.8 d12, d14 @row7 & 8 + vtrn.8 d1, d3 @row9 &10 + vtrn.8 d5, d7 @row11 & 12 + vtrn.8 d9, d11 @row13 &14 + vtrn.8 d13, d15 @row15 & 16 + @4x4 transposes + vtrn.16 d2, d6 @row2 & row4 + vtrn.16 d10, d14 @row6 & row8 + vtrn.16 d3, d7 @row10 & 12 + vtrn.16 d11, d15 @row14 & row16 + vtrn.32 d6, d14 @row4 & 8 + vtrn.32 d7, d15 @row 12 & 16 + @now Q3 ->p0 and Q7->q3 + vtrn.16 d0, d4 @row1 & 3 + vtrn.16 d8, d12 @row 5 & 7 + vtrn.16 d1, d5 @row9 & row11 + vtrn.16 d9, d13 @row13 & row15 + vtrn.32 d0, d8 @row1 & row5 + vtrn.32 d1, d9 @row9 & 13 + @now Q0->p3 & Q4->q0 + @starting processing as p0 and q0 are now ready + @now Q1->p2 & Q5->q1 + vpush {q7} @saving in stack + vtrn.32 d4, d12 @row3 & 7 + vmov.i16 q14, #2 + vtrn.32 d5, d13 @row11 & row15 + vaddl.u8 q8, d6, d8 @p0+q0 L + vtrn.32 d2, d10 @row2 &6 + vaddl.u8 q9, d7, d9 @p0+q0 H + vtrn.32 d3, d11 @row10&row14 + vaddw.u8 q10, q8, d4 @p0+q0+p1 L + vaddw.u8 q11, q9, d5 @p0+q0+p1 H + vaddl.u8 q12, d2, d10 @p2+q1 L + vaddl.u8 q13, d3, d11 @p2+q1 H + vmla.u16 q12, q10, q14 @p2 + X2(p1) + X2(p0) + X2(q0) + q1 L + vmla.u16 q13, q11, q14 @p2 + X2(p1) + X2(p0) + X2(q0) + q1 H + vmov.i8 q14, #2 + vaddw.u8 q8, q10, d2 @p0+q0+p1+p2 L + vaddw.u8 q9, q11, d3 @p0+q0+p1+p2 H + vdup.i8 q15, r2 @duplicate alpha + vrshrn.u16 d20, q8, #2 @(p2 + p1 + p0 + q0 + 2) >> 2)L p1' + vrshrn.u16 d21, q9, #2 @(p2 + p1 + p0 + q0 + 2) >> 2)H p1' + vabd.u8 q11, q3, q4 @ABD(p0-q0) + vsra.u8 q14, q15, #2 @alpha >>2 +2 + vabd.u8 q15, q1, q3 @Ap = ABD(p2-p0) + vrshrn.u16 d24, q12, #3 @((p2 + X2(p1) + X2(p0) + X2(q0) + q1 + 4) >> 3) L p0' + vrshrn.u16 d25, q13, #3 @((p2 + X2(p1) + X2(p0) + X2(q0) + q1 + 4) >> 3) H p0' + vdup.i8 q13, r3 @beta + vcgt.u8 q14, q14, q11 @ABS(p0 - q0) <((Alpha >>2) + 2) + vaddl.u8 q11, d6, d10 @p0+q1 L + vcgt.u8 q7, q13, q15 @beta>Ap + vaddl.u8 q15, d7, d11 @p0+q1 H + vaddw.u8 q11, q11, d4 @p0+q1+p1 L + vaddw.u8 q15, q15, d5 @p0+q1+p1 H + vaddw.u8 q11, q11, d4 @p0+q1+2*p1 L + vaddw.u8 q15, q15, d5 @p0+q1+2*p1 H + vand q7, q7, q14 @(Ap < Beta && ABS(p0 - q0) <((Alpha >>2) + 2) + vrshrn.u16 d22, q11, #2 @((X2(p1) + p0 + q1 + 2) >> 2) L p0" + vrshrn.u16 d23, q15, #2 @((X2(p1) + p0 + q1 + 2) >> 2) H p0" + vaddl.u8 q15, d2, d0 @p2+p3 L + vbif q12, q11, q7 @p0' or p0 " + vaddl.u8 q11, d3, d1 @p2+p3 H + vadd.u16 q15, q15, q15 @2*(p2+p3) L + vadd.u16 q11, q11, q11 @2*(p2+p3)H + vadd.u16 q8, q8, q15 @(X2(p3) + X3(p2) + p1 + p0 + q0) L + vadd.u16 q9, q9, q11 @(X2(p3) + X3(p2) + p1 + p0 + q0) H + vabd.u8 q15, q6, q4 @Aq = abs(q2-q0) + vabd.u8 q11, q5, q4 @ABS(Q1-Q0) + vrshrn.u16 d16, q8, #3 @((X2(p3) + X3(p2) + p1 + p0 + q0 + 4) >> 3); L p2' + vrshrn.u16 d17, q9, #3 @((X2(p3) + X3(p2) + p1 + p0 + q0 + 4) >> 3); H p2' + vabd.u8 q9, q2, q3 @ABS(p1-p0) + vcgt.u8 q15, q13, q15 @Aq < Beta + vcge.u8 q11, q11, q13 @ABS(q1 - q0) >= Beta + vcge.u8 q9, q9, q13 @ABS(p1 - p0) >= beta + vdup.i8 q13, r2 @duplicate alpha + vand q15, q15, q14 @(Aq < Beta && ABS(p0 - q0) <((Alpha >>2) + 2)) + vabd.u8 q14, q3, q4 @abs(p0-q0) + vorr q11, q11, q9 @ABS(p1 - p0) >= Beta || ABS(q1 - q0) >= Beta + vaddl.u8 q9, d6, d8 @p0+q0 L + vcge.u8 q14, q14, q13 @ABS(p0 - q0) >= Alpha + vaddl.u8 q13, d7, d9 @p0+q0 H + vaddw.u8 q9, q9, d10 @p0+q0+q1 L + vorr q11, q11, q14 @ABS(p1 - p0) >= Beta || ABS(q1 - q0) >= Beta||ABS(p0 - q0) >= Alpha + vaddw.u8 q13, q13, d11 @p0+q0+q1 H + vbic q7, q7, q11 @final condn for p's + vmov.i8 q14, #2 + vbif q3, q12, q11 @final p0 + vbit q1, q8, q7 @final p2 + vbif q10, q2, q7 @final p1 + vaddl.u8 q12, d8, d4 @q0+p1 L + vmlal.u8 q12, d10, d28 @X2(q1) + q0 + p1 L + vaddl.u8 q8, d9, d5 @q0+p1 H + vmlal.u8 q8, d11, d28 @X2(q1) + q0 + p1 H + vmov.i16 q14, #2 + vaddl.u8 q7, d4, d12 @p1+q2 L + vmla.u16 q7, q9, q14 @p1 + X2(p0) + X2(q0) + X2(q1) + q2L + vaddl.u8 q2, d5, d13 @p1+q2H + vmla.u16 q2, q13, q14 @p1 + X2(p0) + X2(q0) + X2(q1) + q2H + vrshrn.u16 d24, q12, #2 @(X2(q1) + q0 + p1 + 2) >> 2; L q0' + vrshrn.u16 d25, q8, #2 @(X2(q1) + q0 + p1 + 2) >> 2; H q0' + vaddw.u8 q9, q9, d12 @p0 + q0 + q1 + q2 L + vaddw.u8 q13, q13, d13 @p0 + q0 + q1 + q2 H + vrshrn.u16 d16, q7, #3 @(p1 + X2(p0) + X2(q0) + X2(q1) + q2 + 4) >> 3 L qo" + vpop {q7} + vrshrn.u16 d17, q2, #3 @(p1 + X2(p0) + X2(q0) + X2(q1) + q2 + 4) >> 3 H qo" + vrshrn.u16 d4, q9, #2 @p0 + q0 + q1 + q2 + 2)>>2 L q1' + vrshrn.u16 d5, q13, #2 @p0 + q0 + q1 + q2 + 2)>>2 H q1' + vbit q12, q8, q15 @q0' or q0" + vbic q15, q15, q11 @final condn for q's + vtrn.8 d0, d2 @row1 &2 + vbit q5, q2, q15 @final q1 + vtrn.8 d1, d3 @row9 &10 + vaddl.u8 q8, d12, d14 @q2+q3 L + vtrn.8 d20, d6 @row3&row4 + vaddl.u8 q2, d13, d15 @q2+q3 H + vtrn.8 d21, d7 @row11 & 12 + vmla.u16 q9, q8, q14 @X2(q3) + X3(q2) + q1 + q0 + p0 L + vtrn.16 d2, d6 @row2 & row4 + vmla.u16 q13, q2, q14 @X2(q3) + X3(q2) + q1 + q0 + p0 H + vtrn.16 d3, d7 @row10 & 12 + vbif q4, q12, q11 @final q0 + vtrn.16 d0, d20 @row1 & 3 + vrshrn.u16 d18, q9, #3 @(X2(q3) + X3(q2) + q1 + q0 + p0 + 4) >> 3; L + vtrn.16 d1, d21 @row9 & row11 + vrshrn.u16 d19, q13, #3 @(X2(q3) + X3(q2) + q1 + q0 + p0 + 4) >> 3; H + vtrn.8 d8, d10 @row5&6 + vbit q6, q9, q15 @final q2 + vtrn.8 d9, d11 @row13 &14 + vtrn.8 d12, d14 @row7 & 8 + vtrn.8 d13, d15 @row15 & 16 + vtrn.16 d10, d14 @row6 & row8 + vtrn.16 d11, d15 @row14 & row16 + @now Q3 ->p0 and Q7->q3 + vtrn.16 d8, d12 @row 5 & 7 + vtrn.16 d9, d13 @row13 & row15 + sub r0, r0, r1, lsl#4 @restore pointer + vtrn.32 d6, d14 @row4 & 8 + vtrn.32 d7, d15 @row 12 & 16 + vtrn.32 d0, d8 @row1 & row5 + vtrn.32 d1, d9 @row9 & 13 + vtrn.32 d2, d10 @row2 &6 + vtrn.32 d3, d11 @row10&row14 + vtrn.32 d20, d12 @row3 & 7 + vtrn.32 d21, d13 @row11 & row15 + vst1.8 d0, [r0], r1 @row1 + vst1.8 d2, [r0], r1 @row2 + vst1.8 d20, [r0], r1 @row3 + vst1.8 d6, [r0], r1 @row4 + vst1.8 d8, [r0], r1 @row5 + vst1.8 d10, [r0], r1 @row6 + vst1.8 d12, [r0], r1 @row7 + vst1.8 d14, [r0], r1 @row8 + vst1.8 d1, [r0], r1 @row9 + vst1.8 d3, [r0], r1 @row10 + vst1.8 d21, [r0], r1 @row11 + vst1.8 d7, [r0], r1 @row12 + vst1.8 d9, [r0], r1 @row13 + vst1.8 d11, [r0], r1 @row14 + vst1.8 d13, [r0], r1 @row15 + vst1.8 d15, [r0], r1 @row16 + vpop {d8 - d15} + ldmfd sp!, {r12, pc} + + + +@** +@******************************************************************************* +@* +@* @brief +@* Performs filtering of a luma block vertical edge when the +@* boundary strength is set to 4 on calling twice +@* +@* @par Description: +@* This operation is described in Sec. 8.7.2.4 under the title +@* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. +@* +@* @param[in] r0 - pu1_src +@* Pointer to the src sample q0 +@* +@* @param[in] r1 - src_strd +@* Source stride +@* +@* @param[in] r2 - alpha +@* Alpha Value for the boundary +@* +@* @param[in] r3 - beta +@* Beta Value for the boundary +@* +@* @returns +@* None +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* + + .global ih264_deblk_luma_vert_bs4_mbaff_a9 + +ih264_deblk_luma_vert_bs4_mbaff_a9: + + stmfd sp!, {lr} + + sub r0, r0, #4 @pointer uc_edgePixel-4 + vpush {d8 - d15} + @loading [p3:p2],[p1:p0]:[q0:q1]:[q2:q3] for every row + vld4.16 {d0[0], d2[0], d4[0], d6[0]}, [r0], r1 + vld4.16 {d0[1], d2[1], d4[1], d6[1]}, [r0], r1 + vld4.16 {d0[2], d2[2], d4[2], d6[2]}, [r0], r1 + vld4.16 {d0[3], d2[3], d4[3], d6[3]}, [r0], r1 + vld4.16 {d1[0], d3[0], d5[0], d7[0]}, [r0], r1 + vld4.16 {d1[1], d3[1], d5[1], d7[1]}, [r0], r1 + vld4.16 {d1[2], d3[2], d5[2], d7[2]}, [r0], r1 + vld4.16 {d1[3], d3[3], d5[3], d7[3]}, [r0], r1 + + vuzp.8 d0, d1 @D0->p3, D1->p2 + vuzp.8 d2, d3 @D2->p1, D3->p0 + vuzp.8 d4, d5 @D4->q0, D5->q1 + vuzp.8 d6, d7 @D6->q2, D7->q3 + + vmov.i16 q14, #2 + vaddl.u8 q4, d3, d4 @p0+q0 + vaddw.u8 q5, q4, d2 @p0+q0+p1 + vaddl.u8 q6, d1, d5 @p2+q1 + vmla.u16 q6, q5, q14 @p2 + X2(p1) + X2(p0) + X2(q0) + q1 + + vmov.i8 d14, #2 + vaddw.u8 q4, q5, d1 @p0+q0+p1+p2 + vdup.i8 d15, r2 @duplicate alpha + vrshrn.u16 d10, q4, #2 @(p2 + p1 + p0 + q0 + 2) >> 2) p1' + vabd.u8 d11, d3, d4 @ABD(p0-q0) + vsra.u8 d14, d15, #2 @alpha >>2 +2 + vabd.u8 d15, d1, d3 @Ap = ABD(p2-p0) + vrshrn.u16 d12, q6, #3 @((p2 + X2(p1) + X2(p0) + X2(q0) + q1 + 4) >> 3) p0' + vdup.i8 d13, r3 @beta + vcgt.u8 d14, d14, d11 @ABS(p0 - q0) <((Alpha >>2) + 2) + vaddl.u8 q8, d3, d5 @p0+q1 + vcgt.u8 d26, d13, d15 @beta>Ap + vaddw.u8 q8, q8, d2 @p0+q1+p1 + vaddw.u8 q8, q8, d2 @p0+q1+2*p1 + vand d26, d26, d14 @(Ap < Beta && ABS(p0 - q0) <((Alpha >>2) + 2) + vrshrn.u16 d11, q8, #2 @((X2(p1) + p0 + q1 + 2) >> 2) p0" + vbif d12, d11, d26 @p0' or p0 " + vaddl.u8 q9, d1, d0 @p2+p3 + vadd.u16 q9, q9, q9 @2*(p2+p3) + vadd.u16 q4, q4, q9 @(X2(p3) + X3(p2) + p1 + p0 + q0) + vabd.u8 d15, d6, d4 @Aq = abs(q2-q0) + vabd.u8 d11, d5, d4 @ABS(q1-q0) + vrshrn.u16 d8, q4, #3 @((X2(p3) + X3(p2) + p1 + p0 + q0 + 4) >> 3); p2' + vabd.u8 d9, d2, d3 @ABS(p1-p0) + vcgt.u8 d15, d13, d15 @Aq < Beta + vcge.u8 d11, d11, d13 @ABS(q1 - q0) >= Beta + vcge.u8 d9, d9, d13 @ABS(p1 - p0) >= beta + vdup.i8 d13, r2 @duplicate alpha + vand d15, d15, d14 @(Aq < Beta && ABS(p0 - q0) <((Alpha >>2) + 2)) + vabd.u8 d14, d3, d4 @abs(p0-q0) + vorr d11, d11, d9 @ABS(p1 - p0) >= Beta || ABS(q1 - q0) >= Beta + vcge.u8 d14, d14, d13 @ABS(p0 - q0) >= Alpha + vaddl.u8 q10, d3, d4 @p0+q0 + vorr d11, d11, d14 @ABS(p1 - p0) >= Beta || ABS(q1 - q0) >= Beta||ABS(p0 - q0) >= Alpha + vaddw.u8 q10, q10, d5 @p0+q0+q1 + vbic d26, d26, d11 @final condn for p's + vmov.i8 d14, #2 + vbif d3, d12, d11 @final p0 + vbit d1, d8, d26 @final p2 + vbif d10, d2, d26 @final p1 + vaddl.u8 q6, d4, d2 @q0+p1 + vmlal.u8 q6, d5, d14 @X2(q1) + q0 + p1 + + vaddl.u8 q11, d2, d6 @p1+q2 + vmla.u16 q11, q10, q14 @p1 + X2(p0) + X2(q0) + X2(q1) + q2 + vrshrn.u16 d12, q6, #2 @(X2(q1) + q0 + p1 + 2) >> 2; q0' + vaddw.u8 q10, q10, d6 @p0 + q0 + q1 + q2 + vrshrn.u16 d8, q11, #3 @(p1 + X2(p0) + X2(q0) + X2(q1) + q2 + 4) >> 3 qo" + + vrshrn.u16 d2, q10, #2 @p0 + q0 + q1 + q2 + 2)>>2 q1' + vbit d12, d8, d15 @q0' or q0" + vbic d15, d15, d11 @final condn for q's + vbit d5, d2, d15 @final q1 + vaddl.u8 q12, d6, d7 @q2+q3 + vmla.u16 q10, q12, q14 @X2(q3) + X3(q2) + q1 + q0 + p0 + vbif d4, d12, d11 @final q0 + vrshrn.u16 d9, q10, #3 @(X2(q3) + X3(q2) + q1 + q0 + p0 + 4) >> 3; + vbit d6, d9, d15 @final q2 + vand d2, d10, d10 @D0->p3, D1->p2, D2->p1, D3->p0, D4->q0, D5->q1, D6->q2, D7->q3 + + vzip.8 d0, d1 @D0,D1 -> [p3:p2] + vzip.8 d2, d3 @D2,D3 -> [p1:p0] + vzip.8 d4, d5 @D4,D5 -> [q0:q1] + vzip.8 d6, d7 @D6,D7 -> [q2:q3] + + sub r0, r0, r1, lsl#3 @restore pointer + + @storing [p3:p2],[p1:p0]:[q0:q1]:[q2:q3] in every row + vst4.16 {d0[0], d2[0], d4[0], d6[0]}, [r0], r1 + vst4.16 {d0[1], d2[1], d4[1], d6[1]}, [r0], r1 + vst4.16 {d0[2], d2[2], d4[2], d6[2]}, [r0], r1 + vst4.16 {d0[3], d2[3], d4[3], d6[3]}, [r0], r1 + vst4.16 {d1[0], d3[0], d5[0], d7[0]}, [r0], r1 + vst4.16 {d1[1], d3[1], d5[1], d7[1]}, [r0], r1 + vst4.16 {d1[2], d3[2], d5[2], d7[2]}, [r0], r1 + vst4.16 {d1[3], d3[3], d5[3], d7[3]}, [r0], r1 + vpop {d8 - d15} + ldmfd sp!, {pc} + + + +@** +@******************************************************************************* +@* +@* @brief +@* Performs filtering of a luma block vertical edge for cases where the +@* boundary strength is less than 4 on calling twice +@* +@* @par Description: +@* This operation is described in Sec. 8.7.2.4 under the title +@* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. +@* +@* @param[in] r0 - pu1_src +@* Pointer to the src sample q0 +@* +@* @param[in] r1 - src_strd +@* Source stride +@* +@* @param[in] r2 - alpha +@* Alpha Value for the boundary +@* +@* @param[in] r3 - beta +@* Beta Value for the boundary +@* +@* @param[in] sp(0) - u4_bs +@* Packed Boundary strength array +@* +@* @param[in] sp(4) - pu1_cliptab +@* tc0_table +@* +@* @returns +@* None +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* + + .global ih264_deblk_luma_vert_bslt4_mbaff_a9 + +ih264_deblk_luma_vert_bslt4_mbaff_a9: + + stmfd sp!, {r12, lr} + + sub r0, r0, #4 @pointer uc_edgePixel-4 + ldr r12, [sp, #8] @r12 = ui_Bs + ldr r14, [sp, #12] @r14 = pu1_ClipTab + vpush {d8 - d15} + @loading [p3:p2],[p1:p0]:[q0:q1]:[q2:q3] for every row + vld4.16 {d0[0], d2[0], d4[0], d6[0]}, [r0], r1 + vld4.16 {d0[1], d2[1], d4[1], d6[1]}, [r0], r1 + vld4.16 {d0[2], d2[2], d4[2], d6[2]}, [r0], r1 + vld4.16 {d0[3], d2[3], d4[3], d6[3]}, [r0], r1 + vld4.16 {d1[0], d3[0], d5[0], d7[0]}, [r0], r1 + vld4.16 {d1[1], d3[1], d5[1], d7[1]}, [r0], r1 + vld4.16 {d1[2], d3[2], d5[2], d7[2]}, [r0], r1 + vld4.16 {d1[3], d3[3], d5[3], d7[3]}, [r0], r1 + + vuzp.8 d0, d1 @D0->p3, D1->p2 + vuzp.8 d2, d3 @D2->p1, D3->p0 + vuzp.8 d4, d5 @D4->q0, D5->q1 + vuzp.8 d6, d7 @D6->q2, D7->q3 + + rev r12, r12 @reversing ui_bs + vmov.32 d8[0], r12 @D8[0] = ui_Bs + vld1.32 d9[0], [r14] @D9[0] contains cliptab + vmovl.u8 q15, d8 @D30 = ui_Bs in each 16 bt scalar + vtbl.8 d8, {d9}, d30 @puc_ClipTab[ui_Bs] + vsli.16 d8, d8, #8 @D8 = C0 + + vrhadd.u8 d10, d3, d4 @((p0 + q0 + 1) >> 1) + vmov.i8 d31, #2 + vabd.u8 d11, d3, d4 @ABS(p0 - q0) + vaddl.u8 q6, d10, d1 @(p2 + ((p0 + q0 + 1) >> 1) + vmlsl.u8 q6, d2, d31 @(p2 + ((p0 + q0 + 1) >> 1) - (p1 << 1)) + vdup.8 d14, r2 @alpha + vcle.u8 d11, d14, d11 @ABS(p0 - q0) >= Alpha(Alpha <=ABS(p0 - q0)) + vdup.i8 d14, r3 @beta + vabd.u8 d15, d5, d4 @ABS(q1 - q0) + vqshrn.s16 d12, q6, #1 @((p2 + ((p0 + q0 + 1) >> 1) - (p1 << 1)) >> 1) + vcge.u8 d15, d15, d14 @ABS(q1 - q0) >= Beta + vabd.u8 d13, d2, d3 @ABS(p1 - p0) + vmin.s8 d12, d12, d8 @min(deltap1 ,C0) + vorr d11, d11, d15 @ABS(q1 - q0) >= Beta ||ABS(p0 - q0) >= Alpha + vneg.s8 d15, d8 @-C0 + vcge.u8 d13, d13, d14 @ABS(p1 - p0) >= Beta + vmax.s8 d12, d12, d15 @max(deltap1,-C0) + vorr d11, d11, d13 @ABS(p0 - q0) >= Alpha || ABS(q1 - q0) >= Beta || ABS(p1 - p0) >= Beta) + vceq.u16 d13, d30, #0 @ui_bs == 0 + vaddl.u8 q14, d10, d6 @q2 + ((p0 + q0 + 1) >> 1) + vsubw.u8 q14, q14, d5 @q2 + ((p0 + q0 + 1) >> 1) - q1 + vsubw.u8 q14, q14, d5 @q2 + ((p0 + q0 + 1) >> 1) - 2*q1 + vorr d13, d13, d11 @(ABS(p0 - q0) >= Alpha || ABS(q1 - q0) >= Beta || ABS(p1 - p0) >= Beta)) + @|| (ui_bs == 0) + vqshrn.s16 d9, q14, #1 @(q2 + ((p0 + q0 + 1) >> 1) - (q1 << 1)) >> 1 + vabd.u8 d11, d1, d3 @Ap = ABS(p2 - p0) + vabd.u8 d10, d6, d4 @Aq= ABS(q2 - q0) + vclt.u8 d11, d11, d14 @Ap < Beta + vmin.s8 d9, d9, d8 @min(deltaq1,C0) + vclt.u8 d10, d10, d14 @Aq < Beta + vmax.s8 d9, d9, d15 @max(deltaq1,-C0) + vsubl.u8 q7, d4, d3 @q0 - p0 + vshl.s16 q7, q7, #2 @(q0 - p0) << 2 + vsub.u8 d8, d8, d11 @C0 + (Ap < Beta) + vaddw.u8 q7, q7, d2 @((q0 - p0) << 2) + p1 + vsubw.u8 q7, q7, d5 @((q0 - p0) << 2) + (p1 - q1) + vbic d11, d11, d13 @final condition for p1 + vrshr.s16 q15, q7, #3 @delta = (((q0 - p0) << 2) + (p1 - q1) + 4) >> 3 + vsub.u8 d8, d8, d10 @C0 + (Ap < Beta) + (Aq < Beta) + vbic d10, d10, d13 @final condition for q1 + vabs.s16 q14, q15 + vmovn.i16 d15, q14 @abs(delta) + vand d12, d12, d11 @delatp1 + vand d9, d9, d10 @deltaq1 + vmin.u8 d15, d15, d8 @min((abs(delta),C) + vadd.i8 d2, d2, d12 @p1+deltap1 + vadd.i8 d5, d5, d9 @q1+deltaq1 + vbic d15, d15, d13 @abs(delta) of pixels to be changed only + vcge.s16 q14, q15, #0 + vmovn.i16 d14, q14 @sign(delta) + vqsub.u8 d11, d3, d15 @clip(p0-delta) + vqadd.u8 d3, d3, d15 @clip(p0+delta) + vqadd.u8 d12, d4, d15 @clip(q0+delta) + vqsub.u8 d4, d4, d15 @clip(q0-delta) + vbif d3, d11, d14 @p0 + vbif d4, d12, d14 @q0 + + sub r0, r0, r1, lsl#3 @restore pointer + @D0->p3, D1->p2, D2->p1, D3->p0, D4->q0, D5->q1, D6->q2, D7->q3 + vzip.8 d0, d1 @D0,D1 -> [p3:p2] + vzip.8 d2, d3 @D2,D3 -> [p1:p0] + vzip.8 d4, d5 @D4,D5 -> [q0:q1] + vzip.8 d6, d7 @D6,D7 -> [q2:q3] + + @storing [p3:p2],[p1:p0]:[q0:q1]:[q2:q3] in every row + vst4.16 {d0[0], d2[0], d4[0], d6[0]}, [r0], r1 + vst4.16 {d0[1], d2[1], d4[1], d6[1]}, [r0], r1 + vst4.16 {d0[2], d2[2], d4[2], d6[2]}, [r0], r1 + vst4.16 {d0[3], d2[3], d4[3], d6[3]}, [r0], r1 + vst4.16 {d1[0], d3[0], d5[0], d7[0]}, [r0], r1 + vst4.16 {d1[1], d3[1], d5[1], d7[1]}, [r0], r1 + vst4.16 {d1[2], d3[2], d5[2], d7[2]}, [r0], r1 + vst4.16 {d1[3], d3[3], d5[3], d7[3]}, [r0], r1 + vpop {d8 - d15} + ldmfd sp!, {r12, pc} + + + diff --git a/dependencies/ih264d/common/arm/ih264_default_weighted_pred_a9q.s b/dependencies/ih264d/common/arm/ih264_default_weighted_pred_a9q.s new file mode 100644 index 00000000..a4688f2a --- /dev/null +++ b/dependencies/ih264d/common/arm/ih264_default_weighted_pred_a9q.s @@ -0,0 +1,358 @@ +@/****************************************************************************** +@ * +@ * Copyright (C) 2015 The Android Open Source Project +@ * +@ * Licensed under the Apache License, Version 2.0 (the "License"); +@ * you may not use this file except in compliance with the License. +@ * You may obtain a copy of the License at: +@ * +@ * http://www.apache.org/licenses/LICENSE-2.0 +@ * +@ * Unless required by applicable law or agreed to in writing, software +@ * distributed under the License is distributed on an "AS IS" BASIS, +@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@ * See the License for the specific language governing permissions and +@ * limitations under the License. +@ * +@ ***************************************************************************** +@ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +@*/ +@** +@****************************************************************************** +@* @file +@* ih264_default_weighted_pred_a9q.s +@* +@* @brief +@* Contains function definitions for default weighted prediction. +@* +@* @author +@* Kaushik Senthoor R +@* +@* @par List of Functions: +@* +@* - ih264_default_weighted_pred_luma_a9q() +@* - ih264_default_weighted_pred_chroma_a9q() +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* +@******************************************************************************* +@* @function +@* ih264_default_weighted_pred_luma_a9q() +@* +@* @brief +@* This routine performs the default weighted prediction as described in sec +@* 8.4.2.3.1 titled "Default weighted sample prediction process" for luma. +@* +@* @par Description: +@* This function gets two ht x wd blocks, calculates their rounded-average and +@* stores it in the destination block. +@* +@* @param[in] pu1_src1: +@* UWORD8 Pointer to the buffer containing the first input block. +@* +@* @param[in] pu1_src2: +@* UWORD8 Pointer to the buffer containing the second input block. +@* +@* @param[out] pu1_dst +@* UWORD8 pointer to the destination where the output block is stored. +@* +@* @param[in] src_strd1 +@* Stride of the first input buffer +@* +@* @param[in] src_strd2 +@* Stride of the second input buffer +@* +@* @param[in] dst_strd +@* Stride of the destination buffer +@* +@* @param[in] ht +@* integer height of the array +@* +@* @param[in] wd +@* integer width of the array +@* +@* @returns +@* None +@* +@* @remarks +@* (ht,wd) can be (4,4), (4,8), (8,4), (8,8), (8,16), (16,8) or (16,16). +@* +@******************************************************************************* +@* +@void ih264_default_weighted_pred_luma_a9q(UWORD8 *pu1_src1, +@ UWORD8 *pu1_src2, +@ UWORD8 *pu1_dst, +@ WORD32 src_strd1, +@ WORD32 src_strd2, +@ WORD32 dst_strd, +@ WORD32 ht, +@ WORD32 wd) +@ +@**************Variables Vs Registers***************************************** +@ r0 => pu1_src1 +@ r1 => pu1_src2 +@ r2 => pu1_dst +@ r3 => src_strd1 +@ [sp] => src_strd2 (r4) +@ [sp+4] => dst_strd (r5) +@ [sp+8] => ht (r6) +@ [sp+12] => wd (r7) +@ +.text +.p2align 2 + + .global ih264_default_weighted_pred_luma_a9q + +ih264_default_weighted_pred_luma_a9q: + + stmfd sp!, {r4-r7, r14} @stack stores the values of the arguments + ldr r7, [sp, #32] @Load wd + ldr r4, [sp, #20] @Load src_strd2 + ldr r5, [sp, #24] @Load dst_strd + cmp r7, #16 + ldr r6, [sp, #28] @Load ht + vpush {d8-d15} + beq loop_16 @branch if wd is 16 + cmp r7, #8 + beq loop_8 @branch if wd is 8 + +loop_4: @each iteration processes four rows + + vld1.32 d0[0], [r0], r3 @load row 1 in source 1 + vld1.32 d0[1], [r0], r3 @load row 2 in source 1 + vld1.32 d2[0], [r1], r4 @load row 1 in source 2 + vld1.32 d2[1], [r1], r4 @load row 2 in source 2 + + vld1.32 d1[0], [r0], r3 @load row 3 in source 1 + vld1.32 d1[1], [r0], r3 @load row 4 in source 1 + vrhadd.u8 d0, d0, d2 + vld1.32 d3[0], [r1], r4 @load row 3 in source 2 + vld1.32 d3[1], [r1], r4 @load row 4 in source 2 + + subs r6, r6, #4 @decrement ht by 4 + vst1.32 d0[0], [r2], r5 @load row 1 in destination + vst1.32 d0[1], [r2], r5 @load row 2 in destination + vrhadd.u8 d1, d1, d3 + vst1.32 d1[0], [r2], r5 @load row 3 in destination + vst1.32 d1[1], [r2], r5 @load row 4 in destination + + bgt loop_4 @if greater than 0 repeat the loop again + + b end_loops + +loop_8: @each iteration processes four rows + + vld1.8 d0, [r0], r3 @load row 1 in source 1 + vld1.8 d4, [r1], r4 @load row 1 in source 2 + vld1.8 d1, [r0], r3 @load row 2 in source 1 + vld1.8 d5, [r1], r4 @load row 2 in source 2 + vld1.8 d2, [r0], r3 @load row 3 in source 1 + vrhadd.u8 q0, q0, q2 + vld1.8 d6, [r1], r4 @load row 3 in source 2 + vld1.8 d3, [r0], r3 @load row 4 in source 1 + vrhadd.u8 d2, d2, d6 + vld1.8 d7, [r1], r4 @load row 4 in source 2 + + subs r6, r6, #4 @decrement ht by 4 + vst1.8 d0, [r2], r5 @load row 1 in destination + vrhadd.u8 d3, d3, d7 + vst1.8 d1, [r2], r5 @load row 2 in destination + vst1.8 d2, [r2], r5 @load row 3 in destination + vst1.8 d3, [r2], r5 @load row 4 in destination + + bgt loop_8 @if greater than 0 repeat the loop again + + b end_loops + +loop_16: @each iteration processes eight rows + + vld1.8 {q0}, [r0], r3 @load row 1 in source 1 + vld1.8 {q8}, [r1], r4 @load row 1 in source 2 + vld1.8 {q1}, [r0], r3 @load row 2 in source 1 + vld1.8 {q9}, [r1], r4 @load row 2 in source 2 + vrhadd.u8 q0, q0, q8 + vld1.8 {q2}, [r0], r3 @load row 3 in source 1 + vld1.8 {q10}, [r1], r4 @load row 3 in source 2 + vrhadd.u8 q1, q1, q9 + vld1.8 {q3}, [r0], r3 @load row 4 in source 1 + vld1.8 {q11}, [r1], r4 @load row 4 in source 2 + vrhadd.u8 q2, q2, q10 + vld1.8 {q4}, [r0], r3 @load row 5 in source 1 + vld1.8 {q12}, [r1], r4 @load row 5 in source 2 + vrhadd.u8 q3, q3, q11 + vld1.8 {q5}, [r0], r3 @load row 6 in source 1 + vld1.8 {q13}, [r1], r4 @load row 6 in source 2 + vrhadd.u8 q4, q4, q12 + vld1.8 {q6}, [r0], r3 @load row 7 in source 1 + vld1.8 {q14}, [r1], r4 @load row 7 in source 2 + vrhadd.u8 q5, q5, q13 + vld1.8 {q7}, [r0], r3 @load row 8 in source 1 + vld1.8 {q15}, [r1], r4 @load row 8 in source 2 + + vrhadd.u8 q6, q6, q14 + vst1.8 {q0}, [r2], r5 @load row 1 in destination + vst1.8 {q1}, [r2], r5 @load row 2 in destination + vrhadd.u8 q7, q7, q15 + vst1.8 {q2}, [r2], r5 @load row 3 in destination + vst1.8 {q3}, [r2], r5 @load row 4 in destination + subs r6, r6, #8 @decrement ht by 8 + vst1.8 {q4}, [r2], r5 @load row 5 in destination + vst1.8 {q5}, [r2], r5 @load row 6 in destination + vst1.8 {q6}, [r2], r5 @load row 7 in destination + vst1.8 {q7}, [r2], r5 @load row 8 in destination + + bgt loop_16 @if greater than 0 repeat the loop again + +end_loops: + + vpop {d8-d15} + ldmfd sp!, {r4-r7, r15} @Reload the registers from sp + + +@******************************************************************************* +@* @function +@* ih264_default_weighted_pred_chroma_a9q() +@* +@* @brief +@* This routine performs the default weighted prediction as described in sec +@* 8.4.2.3.1 titled "Default weighted sample prediction process" for chroma. +@* +@* @par Description: +@* This function gets two ht x wd blocks, calculates their rounded-average and +@* stores it in the destination block for U and V. +@* +@* @param[in] pu1_src1: +@* UWORD8 Pointer to the buffer containing the first input block. +@* +@* @param[in] pu1_src2: +@* UWORD8 Pointer to the buffer containing the second input block. +@* +@* @param[out] pu1_dst +@* UWORD8 pointer to the destination where the output block is stored. +@* +@* @param[in] src_strd1 +@* Stride of the first input buffer +@* +@* @param[in] src_strd2 +@* Stride of the second input buffer +@* +@* @param[in] dst_strd +@* Stride of the destination buffer +@* +@* @param[in] ht +@* integer height of the array +@* +@* @param[in] wd +@* integer width of the array +@* +@* @returns +@* None +@* +@* @remarks +@* (ht,wd) can be (2,2), (2,4), (4,2), (4,4), (4,8), (8,4) or (8,8). +@* +@******************************************************************************* +@* +@void ih264_default_weighted_pred_chroma_a9q(UWORD8 *pu1_src1, +@ UWORD8 *pu1_src2, +@ UWORD8 *pu1_dst, +@ WORD32 src_strd1, +@ WORD32 src_strd2, +@ WORD32 dst_strd, +@ WORD32 ht, +@ WORD32 wd) +@ +@**************Variables Vs Registers***************************************** +@ r0 => pu1_src1 +@ r1 => pu1_src2 +@ r2 => pu1_dst +@ r3 => src_strd1 +@ [sp] => src_strd2 (r4) +@ [sp+4] => dst_strd (r5) +@ [sp+8] => ht (r6) +@ [sp+12] => wd (r7) +@ + + + .global ih264_default_weighted_pred_chroma_a9q + +ih264_default_weighted_pred_chroma_a9q: + + stmfd sp!, {r4-r7, r14} @stack stores the values of the arguments + ldr r7, [sp, #32] @Load wd + ldr r4, [sp, #20] @Load src_strd2 + ldr r5, [sp, #24] @Load dst_strd + cmp r7, #8 + ldr r6, [sp, #28] @Load ht + vpush {d8-d15} + beq loop_8_uv @branch if wd is 8 + cmp r7, #4 + beq loop_4_uv @branch if wd is 4 + +loop_2_uv: @each iteration processes two rows + + vld1.32 d0[0], [r0], r3 @load row 1 in source 1 + vld1.32 d0[1], [r0], r3 @load row 2 in source 1 + + vld1.32 d1[0], [r1], r4 @load row 1 in source 2 + vld1.32 d1[1], [r1], r4 @load row 2 in source 2 + + vrhadd.u8 d0, d0, d1 + + subs r6, r6, #2 @decrement ht by 2 + vst1.32 d0[0], [r2], r5 @load row 1 in destination + vst1.32 d0[1], [r2], r5 @load row 2 in destination + + bgt loop_2_uv @if greater than 0 repeat the loop again + + b end_loops_uv + +loop_4_uv: @each iteration processes two rows + + vld1.8 d0, [r0], r3 @load row 1 in source 1 + vld1.8 d2, [r1], r4 @load row 1 in source 2 + vld1.8 d1, [r0], r3 @load row 2 in source 1 + vrhadd.u8 d0, d0, d2 + vld1.8 d3, [r1], r4 @load row 2 in source 2 + + vrhadd.u8 d1, d1, d3 + vst1.8 d0, [r2], r5 @load row 1 in destination + subs r6, r6, #2 @decrement ht by 2 + vst1.8 d1, [r2], r5 @load row 2 in destination + + bgt loop_4_uv @if greater than 0 repeat the loop again + + b end_loops_uv + +loop_8_uv: @each iteration processes four rows + + vld1.8 {q0}, [r0], r3 @load row 1 in source 1 + vld1.8 {q4}, [r1], r4 @load row 1 in source 2 + vld1.8 {q1}, [r0], r3 @load row 2 in source 1 + vrhadd.u8 q0, q0, q4 + vld1.8 {q5}, [r1], r4 @load row 2 in source 2 + vld1.8 {q2}, [r0], r3 @load row 3 in source 1 + vrhadd.u8 q1, q1, q5 + vld1.8 {q6}, [r1], r4 @load row 3 in source 2 + vld1.8 {q3}, [r0], r3 @load row 4 in source 1 + vrhadd.u8 q2, q2, q6 + vld1.8 {q7}, [r1], r4 @load row 4 in source 2 + + vst1.8 {q0}, [r2], r5 @load row 1 in destination + vrhadd.u8 q3, q3, q7 + vst1.8 {q1}, [r2], r5 @load row 2 in destination + subs r6, r6, #4 @decrement ht by 4 + vst1.8 {q2}, [r2], r5 @load row 3 in destination + vst1.8 {q3}, [r2], r5 @load row 4 in destination + + bgt loop_8_uv @if greater than 0 repeat the loop again + +end_loops_uv: + + vpop {d8-d15} + ldmfd sp!, {r4-r7, r15} @Reload the registers from sp + + diff --git a/dependencies/ih264d/common/arm/ih264_ihadamard_scaling_a9.s b/dependencies/ih264d/common/arm/ih264_ihadamard_scaling_a9.s new file mode 100644 index 00000000..c7feddd8 --- /dev/null +++ b/dependencies/ih264d/common/arm/ih264_ihadamard_scaling_a9.s @@ -0,0 +1,250 @@ +@/****************************************************************************** +@ * +@ * Copyright (C) 2015 The Android Open Source Project +@ * +@ * Licensed under the Apache License, Version 2.0 (the "License"); +@ * you may not use this file except in compliance with the License. +@ * You may obtain a copy of the License at: +@ * +@ * http://www.apache.org/licenses/LICENSE-2.0 +@ * +@ * Unless required by applicable law or agreed to in writing, software +@ * distributed under the License is distributed on an "AS IS" BASIS, +@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@ * See the License for the specific language governing permissions and +@ * limitations under the License. +@ * +@ ***************************************************************************** +@ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +@*/ +@** +@ ******************************************************************************* +@ * @file +@ * ih264_ihadamard_scaling_a9.s +@ * +@ * @brief +@ * Contains function definitions for inverse hadamard transform on 4x4 DC outputs +@ * of 16x16 intra-prediction +@ * +@ * @author +@ * Mohit +@ * +@ * @par List of Functions: +@ * - ih264_ihadamard_scaling_4x4_a9() +@ * - ih264_ihadamard_scaling_2x2_uv_a9() +@ * +@ * @remarks +@ * None +@ * +@ ******************************************************************************* +@ * +@ * @brief This function performs a 4x4 inverse hadamard transform on the 4x4 DC coefficients +@ * of a 16x16 intra prediction macroblock, and then performs scaling. +@ * prediction buffer +@ * +@ * @par Description: +@ * The DC coefficients pass through a 2-stage inverse hadamard transform. +@ * This inverse transformed content is scaled to based on Qp value. +@ * +@ * @param[in] pi2_src +@ * input 4x4 block of DC coefficients +@ * +@ * @param[out] pi2_out +@ * output 4x4 block +@ * +@ * @param[in] pu2_iscal_mat +@ * pointer to scaling list +@ * +@ * @param[in] pu2_weigh_mat +@ * pointer to weight matrix +@ * +@ * @param[in] u4_qp_div_6 +@ * Floor (qp/6) +@ * +@ * @param[in] pi4_tmp +@ * temporary buffer of size 1*16 +@ * +@ * @returns none +@ * +@ * @remarks none +@ * +@ ******************************************************************************* +@ * +@ * +@ ******************************************************************************* +@ * +@ void ih264_ihadamard_scaling_4x4(WORD16* pi2_src, +@ WORD16* pi2_out, +@ const UWORD16 *pu2_iscal_mat, +@ const UWORD16 *pu2_weigh_mat, +@ UWORD32 u4_qp_div_6, +@ WORD32* pi4_tmp) +@**************Variables Vs Registers***************************************** +@r0 => *pi2_src +@r1 => *pi2_out +@r2 => *pu2_iscal_mat +@r3 => *pu2_weigh_mat +@r4 => u4_qp_div_6 + +.text +.p2align 2 + + .global ih264_ihadamard_scaling_4x4_a9 + +ih264_ihadamard_scaling_4x4_a9: + +@VLD4.S16 is used because the pointer is incremented by SUB_BLK_WIDTH_4x4 +@If the macro value changes need to change the instruction according to it. +@Only one shift is done in horizontal inverse because, +@if u4_qp_div_6 is lesser than 4 then shift value will be neagative and do negative left shift, in this case rnd_factor has value +@if u4_qp_div_6 is greater than 4 then shift value will be positive and do left shift, here rnd_factor is 0 + + stmfd sp!, {r4-r12, r14} @ stack stores the values of the arguments + ldr r4, [sp, #40] @ Loads u4_qp_div_6 + vdup.s32 q10, r4 @ Populate the u4_qp_div_6 in Q10 + ldrh r6, [r3] @ load pu2_weight_mat[0] , H for unsigned halfword load + ldrh r7, [r2] @ load pu2_iscal_mat[0] , H for unsigned halfword load + mul r6, r6, r7 @ pu2_iscal_mat[0]*pu2_weigh_mat[0] + vdup.s32 q9, r6 @ Populate pu2_iscal_mat[0]*pu2_weigh_mat[0] 32-bit in Q9 + vpush {d8-d15} +@=======================INVERSE HADAMARD TRANSFORM================================ + + vld4.s16 {d0, d1, d2, d3}, [r0] @load x4,x5,x6,x7 + vaddl.s16 q12, d0, d3 @x0 = x4 + x7 + vaddl.s16 q13, d1, d2 @x1 = x5 + x6 + vsubl.s16 q14, d1, d2 @x2 = x5 - x6 + vsubl.s16 q15, d0, d3 @x3 = x4 - x7 + + vadd.s32 q2, q12, q13 @pi4_tmp_ptr[0] = x0 + x1 + vadd.s32 q3, q15, q14 @pi4_tmp_ptr[1] = x3 + x2 + vsub.s32 q4, q12, q13 @pi4_tmp_ptr[2] = x0 - x1 + vsub.s32 q5, q15, q14 @pi4_tmp_ptr[3] = x3 - x2 + + vtrn.32 q2, q3 @Transpose the register for vertical transform + vtrn.32 q4, q5 + + vswp d5, d8 @Q2 = x4, Q4 = x6 + vswp d7, d10 @Q3 = x5, Q5 = x7 + + + vadd.s32 q12, q2, q5 @x0 = x4+x7 + vadd.s32 q13, q3, q4 @x1 = x5+x6 + vsub.s32 q14, q3, q4 @x2 = x5-x6 + vsub.s32 q15, q2, q5 @x3 = x4-x7 + + vadd.s32 q0, q12, q13 @pi4_tmp_ptr[0] = x0 + x1 + vadd.s32 q1, q15, q14 @pi4_tmp_ptr[1] = x3 + x2 + vsub.s32 q2, q12, q13 @pi4_tmp_ptr[2] = x0 - x1 + vsub.s32 q3, q15, q14 @pi4_tmp_ptr[3] = x3 - x2 + + + vmul.s32 q0, q0, q9 @ Q0 = p[i] = (x[i] * trns_coeff[i]) where i = 0..3 + vmul.s32 q1, q1, q9 @ Q1 = p[i] = (x[i] * trns_coeff[i]) where i = 4..7 + vmul.s32 q2, q2, q9 @ Q2 = p[i] = (x[i] * trns_coeff[i]) where i = 8..11 + vmul.s32 q3, q3, q9 @ Q3 = p[i] = (x[i] * trns_coeff[i]) where i = 12..15 + + vshl.s32 q0, q0, q10 @ Q0 = q[i] = (p[i] << (qP/6)) where i = 0..3 + vshl.s32 q1, q1, q10 @ Q1 = q[i] = (p[i] << (qP/6)) where i = 4..7 + vshl.s32 q2, q2, q10 @ Q2 = q[i] = (p[i] << (qP/6)) where i = 8..11 + vshl.s32 q3, q3, q10 @ Q3 = q[i] = (p[i] << (qP/6)) where i = 12..15 + + vqrshrn.s32 d0, q0, #0x6 @ D0 = c[i] = ((q[i] + 32) >> 4) where i = 0..3 + vqrshrn.s32 d1, q1, #0x6 @ D1 = c[i] = ((q[i] + 32) >> 4) where i = 4..7 + vqrshrn.s32 d2, q2, #0x6 @ D2 = c[i] = ((q[i] + 32) >> 4) where i = 8..11 + vqrshrn.s32 d3, q3, #0x6 @ D3 = c[i] = ((q[i] + 32) >> 4) where i = 12..15 + + vst1.s16 {d0, d1, d2, d3}, [r1] @IV row store the value + + vpop {d8-d15} + ldmfd sp!, {r4-r12, r15} @Reload the registers from SP + + + +@ ******************************************************************************* +@ * +@ * @brief This function performs a 2x2 inverse hadamard transform for chroma block +@ * +@ * @par Description: +@ * The DC coefficients pass through a 2-stage inverse hadamard transform. +@ * This inverse transformed content is scaled to based on Qp value. +@ * Both DC blocks of U and v blocks are processesd +@ * +@ * @param[in] pi2_src +@ * input 1x8 block of ceffs. First 4 are from U and next from V +@ * +@ * @param[out] pi2_out +@ * output 1x8 block +@ * +@ * @param[in] pu2_iscal_mat +@ * pointer to scaling list +@ * +@ * @param[in] pu2_weigh_mat +@ * pointer to weight matrix +@ * +@ * @param[in] u4_qp_div_6 +@ * Floor (qp/6) +@ * +@ * @returns none +@ * +@ * @remarks none +@ * +@ ******************************************************************************* +@ * +@ * +@ ******************************************************************************* +@ * +@ void ih264_ihadamard_scaling_2x2_uv(WORD16* pi2_src, +@ WORD16* pi2_out, +@ const UWORD16 *pu2_iscal_mat, +@ const UWORD16 *pu2_weigh_mat, +@ UWORD32 u4_qp_div_6, + + .global ih264_ihadamard_scaling_2x2_uv_a9 +ih264_ihadamard_scaling_2x2_uv_a9: + +@Registers used +@ r0 : *pi2_src +@ r1 : *pi2_out +@ r2 : *pu2_iscal_mat +@ r3 : *pu2_weigh_mat + + vld1.u16 d26[0], [r2] + vld1.u16 d27[0], [r3] + vmull.u16 q15, d26, d27 @pu2_iscal_mat[0] * pu2_weigh_mat[0] + vdup.u32 q15, d30[0] + + vld1.u16 d28[0], [sp] @load qp/6 + + vpush {d8-d15} + + vmov.u16 d29, #5 + vsubl.u16 q14, d28, d29 @qp\6 - 5 + vdup.s32 q14, d28[0] + + vld2.s16 {d0, d1}, [r0] @load 8 dc coeffs + @i2_x4,i2_x6,i2_y4,i1_y6 -> d0 + @i2_x5,i2_x7,i2_y5,i1_y6 -> d1 + + vaddl.s16 q1, d0, d1 @ i4_x0 = i4_x4 + i4_x5;...x2 + vsubl.s16 q2, d0, d1 @ i4_x1 = i4_x4 - i4_x5;...x3 + + vtrn.s32 q1, q2 @i4_x0 i4_x1 -> q1 + + vadd.s32 q3, q1, q2 @i4_x4 = i4_x0+i4_x2;.. i4_x5 + vsub.s32 q1, q1, q2 @i4_x6 = i4_x0-i4_x2;.. i4_x7 + + vmul.s32 q5, q3, q15 + vmul.s32 q6, q1, q15 + + vshl.s32 q7, q5, q14 + vshl.s32 q8, q6, q14 + + vmovn.s32 d18, q7 @i4_x4 i4_x5 i4_y4 i4_y5 + vmovn.s32 d19, q8 @i4_x6 i4_x7 i4_y6 i4_y7 + + vst2.s32 {d18-d19}, [r1] + + vpop {d8-d15} + bx lr + + diff --git a/dependencies/ih264d/common/arm/ih264_inter_pred_chroma_a9q.s b/dependencies/ih264d/common/arm/ih264_inter_pred_chroma_a9q.s new file mode 100644 index 00000000..e2b8c992 --- /dev/null +++ b/dependencies/ih264d/common/arm/ih264_inter_pred_chroma_a9q.s @@ -0,0 +1,252 @@ +@/****************************************************************************** +@ * +@ * Copyright (C) 2015 The Android Open Source Project +@ * +@ * Licensed under the Apache License, Version 2.0 (the "License"); +@ * you may not use this file except in compliance with the License. +@ * You may obtain a copy of the License at: +@ * +@ * http://www.apache.org/licenses/LICENSE-2.0 +@ * +@ * Unless required by applicable law or agreed to in writing, software +@ * distributed under the License is distributed on an "AS IS" BASIS, +@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@ * See the License for the specific language governing permissions and +@ * limitations under the License. +@ * +@ ***************************************************************************** +@ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +@*/ +@** +@****************************************************************************** +@* @file +@* ih264_inter_pred_chroma_a9q.s +@* +@* @brief +@* Contains function definitions for inter prediction interpolation. +@* +@* @author +@* Ittaim +@* +@* @par List of Functions: +@* +@* - ih264_inter_pred_chroma_a9q() +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* + +@* All the functions here are replicated from ih264_inter_pred_filters.c +@ + +@** +@** +@** +@ +@** +@******************************************************************************* +@* +@* @brief +@* Interprediction chroma filter +@* +@* @par Description: +@* Applies filtering to chroma samples as mentioned in +@* sec 8.4.2.2.2 titled "chroma sample interpolation process" +@* +@* @param[in] pu1_src +@* UWORD8 pointer to the source containing alternate U and V samples +@* +@* @param[out] pu1_dst +@* UWORD8 pointer to the destination +@* +@* @param[in] src_strd +@* integer source stride +@* +@* @param[in] dst_strd +@* integer destination stride +@* +@* @param[in]uc_dx +@* dx value where the sample is to be produced(refer sec 8.4.2.2.2 ) +@* +@* @param[in] uc_dy +@* dy value where the sample is to be produced(refer sec 8.4.2.2.2 ) +@* +@* @param[in] ht +@* integer height of the array +@* +@* @param[in] wd +@* integer width of the array +@* +@* @returns +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* + +@void ih264_inter_pred_chroma(UWORD8 *pu1_src, +@ UWORD8 *pu1_dst, +@ WORD32 src_strd, +@ WORD32 dst_strd, +@ WORD32 u1_dx, +@ WORD32 u1_dy, +@ WORD32 ht, +@ WORD32 wd) +@**************Variables Vs Registers***************************************** +@ r0 => *pu1_src +@ r1 => *pu1_dst +@ r2 => src_strd +@ r3 => dst_strd +@ r4 => u1_dx +@ r5 => u1_dy +@ r6 => height +@ r7 => width +@ +.text +.p2align 2 + + .global ih264_inter_pred_chroma_a9q + +ih264_inter_pred_chroma_a9q: + + stmfd sp!, {r4-r12, r14} @store register values to stack + vstmdb sp!, {d8-d15} @push neon registers to stack + ldr r4, [sp, #104] + ldr r5, [sp, #108] + ldr r6, [sp, #112] + ldr r7, [sp, #116] + + rsb r8, r4, #8 @8-u1_dx + rsb r9, r5, #8 @8-u1_dy + mul r10, r8, r9 + mul r11, r4, r9 + + vdup.u8 d28, r10 + vdup.u8 d29, r11 + + mul r10, r8, r5 + mul r11, r4, r5 + + vdup.u8 d30, r10 + vdup.u8 d31, r11 + + subs r12, r7, #2 @if wd=4 branch to loop_4 + beq loop_2 + subs r12, r7, #4 @if wd=8 branch to loop_8 + beq loop_4 + +loop_8: + sub r6, #1 + vld1.8 {d0, d1, d2}, [r0], r2 @ Load row0 + vld1.8 {d5, d6, d7}, [r0], r2 @ Load row1 + vext.8 d3, d0, d1, #2 + vext.8 d8, d5, d6, #2 + + vmull.u8 q5, d0, d28 + vmlal.u8 q5, d5, d30 + vmlal.u8 q5, d3, d29 + vmlal.u8 q5, d8, d31 + vext.8 d9, d6, d7, #2 + vext.8 d4, d1, d2, #2 + +inner_loop_8: + vmull.u8 q6, d6, d30 + vmlal.u8 q6, d1, d28 + vmlal.u8 q6, d9, d31 + vmlal.u8 q6, d4, d29 + vmov d0, d5 + vmov d3, d8 + + vqrshrun.s16 d14, q5, #6 + vmov d1, d6 + vmov d4, d9 + + vld1.8 {d5, d6, d7}, [r0], r2 @ Load row1 + vqrshrun.s16 d15, q6, #6 + + vext.8 d8, d5, d6, #2 + subs r6, #1 + vext.8 d9, d6, d7, #2 + vst1.8 {q7}, [r1], r3 @ Store dest row + + vmull.u8 q5, d0, d28 + vmlal.u8 q5, d5, d30 + vmlal.u8 q5, d3, d29 + vmlal.u8 q5, d8, d31 + bne inner_loop_8 + + vmull.u8 q6, d6, d30 + vmlal.u8 q6, d1, d28 + vmlal.u8 q6, d9, d31 + vmlal.u8 q6, d4, d29 + + vqrshrun.s16 d14, q5, #6 + vqrshrun.s16 d15, q6, #6 + + vst1.8 {q7}, [r1], r3 @ Store dest row + + b end_func + +loop_4: + sub r6, #1 + vld1.8 {d0, d1}, [r0], r2 @ Load row0 + vld1.8 {d2, d3}, [r0], r2 @ Load row1 + vext.8 d1, d0, d1, #2 + vext.8 d3, d2, d3, #2 + + vmull.u8 q2, d2, d30 + vmlal.u8 q2, d0, d28 + vmlal.u8 q2, d3, d31 + vmlal.u8 q2, d1, d29 + +inner_loop_4: + subs r6, #1 + vmov d0, d2 + vmov d1, d3 + + vld1.8 {d2, d3}, [r0], r2 @ Load row1 + vqrshrun.s16 d6, q2, #6 + + vext.8 d3, d2, d3, #2 + vst1.8 {d6}, [r1], r3 @ Store dest row + + vmull.u8 q2, d0, d28 + vmlal.u8 q2, d2, d30 + vmlal.u8 q2, d1, d29 + vmlal.u8 q2, d3, d31 + bne inner_loop_4 + + vqrshrun.s16 d6, q2, #6 + vst1.8 {d6}, [r1], r3 @ Store dest row + + b end_func + +loop_2: + vld1.8 {d0}, [r0], r2 @ Load row0 + vext.8 d1, d0, d0, #2 + vld1.8 {d2}, [r0], r2 @ Load row1 + vext.8 d3, d2, d2, #2 + vmull.u8 q2, d0, d28 + vmlal.u8 q2, d1, d29 + vmlal.u8 q2, d2, d30 + vmlal.u8 q2, d3, d31 + vld1.8 {d6}, [r0] @ Load row2 + vqrshrun.s16 d4, q2, #6 + vext.8 d7, d6, d6, #2 + vst1.32 d4[0], [r1], r3 @ Store dest row0 + vmull.u8 q4, d2, d28 + vmlal.u8 q4, d3, d29 + vmlal.u8 q4, d6, d30 + vmlal.u8 q4, d7, d31 + subs r6, #2 + vqrshrun.s16 d8, q4, #6 + vst1.32 d8[0], [r1], r3 @ Store dest row1 + bne loop_2 @ repeat if ht=2 + +end_func: + vldmia sp!, {d8-d15} @ Restore neon registers that were saved + ldmfd sp!, {r4-r12, pc} @ Restoring registers from stack + diff --git a/dependencies/ih264d/common/arm/ih264_inter_pred_filters_luma_horz_a9q.s b/dependencies/ih264d/common/arm/ih264_inter_pred_filters_luma_horz_a9q.s new file mode 100644 index 00000000..62b4b94c --- /dev/null +++ b/dependencies/ih264d/common/arm/ih264_inter_pred_filters_luma_horz_a9q.s @@ -0,0 +1,248 @@ +@/****************************************************************************** +@ * +@ * Copyright (C) 2015 The Android Open Source Project +@ * +@ * Licensed under the Apache License, Version 2.0 (the "License"); +@ * you may not use this file except in compliance with the License. +@ * You may obtain a copy of the License at: +@ * +@ * http://www.apache.org/licenses/LICENSE-2.0 +@ * +@ * Unless required by applicable law or agreed to in writing, software +@ * distributed under the License is distributed on an "AS IS" BASIS, +@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@ * See the License for the specific language governing permissions and +@ * limitations under the License. +@ * +@ ***************************************************************************** +@ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +@*/ +@** +@****************************************************************************** +@* @file +@* ih264_inter_pred_luma_horz_a9q.s +@* +@* @brief +@* Contains function definitions for inter prediction interpolation. +@* +@* @author +@* Ittiam +@* +@* @par List of Functions: +@* +@* - ih264_inter_pred_luma_horz_a9q() +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* + +@* All the functions here are replicated from ih264_inter_pred_filters.c +@ + +@** +@** +@******************************************************************************* +@* +@* @brief +@* Interprediction luma filter for horizontal input +@* +@* @par Description: +@* Applies a 6 tap horizontal filter .The output is clipped to 8 bits +@* sec 8.4.2.2.1 titled "Luma sample interpolation process" +@* +@* @param[in] pu1_src +@* UWORD8 pointer to the source +@* +@* @param[out] pu1_dst +@* UWORD8 pointer to the destination +@* +@* @param[in] src_strd +@* integer source stride +@* +@* @param[in] dst_strd +@* integer destination stride +@* +@* @param[in] ht +@* integer height of the array +@* +@* @param[in] wd +@* integer width of the array +@* +@* @returns +@* +@ @remarks +@* None +@* +@******************************************************************************* +@* + +@void ih264_inter_pred_luma_horz ( +@ UWORD8 *pu1_src, +@ UWORD8 *pu1_dst, +@ WORD32 src_strd, +@ WORD32 dst_strd, +@ WORD32 ht, +@ WORD32 wd ) + +@**************Variables Vs Registers***************************************** +@ r0 => *pu1_src +@ r1 => *pu1_dst +@ r2 => src_strd +@ r3 => dst_strd +@ r5 => ht +@ r6 => wd + +.text +.p2align 2 + + + .global ih264_inter_pred_luma_horz_a9q + +ih264_inter_pred_luma_horz_a9q: + + + + + stmfd sp!, {r4-r12, r14} @store register values to stack + vstmdb sp!, {d8-d15} @push neon registers to stack + ldr r5, [sp, #104] @Loads ht + sub r0, r0, #2 @pu1_src-2 + ldr r6, [sp, #108] @Loads wd + vmov.i8 d0, #5 @filter coeff + subs r12, r6, #8 @if wd=8 branch to loop_8 + vmov.i8 d1, #20 @filter coeff + beq loop_8 + + subs r12, r6, #4 @if wd=4 branch to loop_4 + beq loop_4 + +loop_16: @when wd=16 + @ Processing row0 and row1 + vld1.8 {d2, d3, d4}, [r0], r2 @// Load row0 ;for checking loop + vext.8 d31, d2, d3, #5 @//extract a[5] (column1,row0) + vld1.8 {d5, d6, d7}, [r0], r2 @// Load row1 + vext.8 d30, d3, d4, #5 @//extract a[5] (column2,row0) + vaddl.u8 q4, d31, d2 @// a0 + a5 (column1,row0) + vext.8 d28, d5, d6, #5 @//extract a[5] (column1,row1) + vaddl.u8 q5, d30, d3 @// a0 + a5 (column2,row0) + vext.8 d27, d6, d7, #5 @//extract a[5] (column2,row1) + vaddl.u8 q7, d28, d5 @// a0 + a5 (column1,row1) + vext.8 d31, d2, d3, #2 @//extract a[2] (column1,row0) + vaddl.u8 q8, d27, d6 @// a0 + a5 (column2,row1) + vext.8 d30, d3, d4, #2 @//extract a[2] (column2,row0) + vmlal.u8 q4, d31, d1 @// a0 + a5 + 20a2 (column1,row0) + vext.8 d28, d5, d6, #2 @//extract a[2] (column1,row1) + vmlal.u8 q5, d30, d1 @// a0 + a5 + 20a2 (column2,row0) + vext.8 d27, d6, d7, #2 @//extract a[2] (column2,row1) + vmlal.u8 q7, d28, d1 @// a0 + a5 + 20a2 (column1,row1) + vext.8 d31, d2, d3, #3 @//extract a[3] (column1,row0) + vmlal.u8 q8, d27, d1 @// a0 + a5 + 20a2 (column2,row1) + vext.8 d30, d3, d4, #3 @//extract a[3] (column2,row0) + vmlal.u8 q4, d31, d1 @// a0 + a5 + 20a2 + 20a3 (column1,row0) + vext.8 d28, d5, d6, #3 @//extract a[3] (column1,row1) + vmlal.u8 q5, d30, d1 @// a0 + a5 + 20a2 + 20a3 (column2,row0) + vext.8 d27, d6, d7, #3 @//extract a[3] (column2,row1) + vmlal.u8 q7, d28, d1 @// a0 + a5 + 20a2 + 20a3 (column1,row1) + vext.8 d31, d2, d3, #1 @//extract a[1] (column1,row0) + vmlal.u8 q8, d27, d1 @// a0 + a5 + 20a2 + 20a3 (column2,row1) + vext.8 d30, d3, d4, #1 @//extract a[1] (column2,row0) + vmlsl.u8 q4, d31, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row0) + vext.8 d28, d5, d6, #1 @//extract a[1] (column1,row1) + vmlsl.u8 q5, d30, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 (column2,row0) + vext.8 d27, d6, d7, #1 @//extract a[1] (column2,row1) + vmlsl.u8 q7, d28, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row1) + vext.8 d31, d2, d3, #4 @//extract a[4] (column1,row0) + vmlsl.u8 q8, d27, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 (column2,row1) + vext.8 d30, d3, d4, #4 @//extract a[4] (column2,row0) + vmlsl.u8 q4, d31, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row0) + vext.8 d28, d5, d6, #4 @//extract a[4] (column1,row1) + vmlsl.u8 q5, d30, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2,row0) + vext.8 d27, d6, d7, #4 @//extract a[4] (column2,row1) + vmlsl.u8 q7, d28, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row1) + vmlsl.u8 q8, d27, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2,row1) + vqrshrun.s16 d20, q4, #5 @// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row0) + vqrshrun.s16 d21, q5, #5 @// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column2,row0) + vext.8 d31, d2, d3, #5 @//extract a[5] (column1,row2) + vst1.8 {d20, d21}, [r1], r3 @//Store dest row0 + vqrshrun.s16 d23, q7, #5 @// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row1) + vext.8 d30, d3, d4, #5 @//extract a[5] (column2,row2) + vqrshrun.s16 d24, q8, #5 @// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column2,row1) + vst1.8 {d23, d24}, [r1], r3 @//Store dest row1 + subs r5, r5, #2 @ 2 rows done, decrement by 2 + + beq end_func + b loop_16 @ loop if height == 8 or 16 + +loop_8: +@ Processing row0 and row1 + vld1.8 {d5, d6}, [r0], r2 @// Load row1 + vext.8 d28, d5, d6, #5 @//extract a[5] (column1,row1) + vld1.8 {d2, d3}, [r0], r2 @// Load row0 + vext.8 d25, d5, d6, #2 @//extract a[2] (column1,row1) + vext.8 d31, d2, d3, #5 @//extract a[5] (column1,row0) + vext.8 d24, d5, d6, #3 @//extract a[3] (column1,row1) + vext.8 d23, d5, d6, #1 @//extract a[1] (column1,row1) + vext.8 d22, d5, d6, #4 @//extract a[4] (column1,row1) + vaddl.u8 q7, d28, d5 @// a0 + a5 (column1,row1) + vext.8 d29, d2, d3, #3 @//extract a[3] (column1,row0) + vmlal.u8 q7, d25, d1 @// a0 + a5 + 20a2 (column1,row1) + vmlal.u8 q7, d24, d1 @// a0 + a5 + 20a2 + 20a3 (column1,row1) + vmlsl.u8 q7, d23, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row1) + vmlsl.u8 q7, d22, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row1) + vext.8 d30, d2, d3, #2 @//extract a[2] (column1,row0) + vaddl.u8 q4, d31, d2 @// a0 + a5 (column1,row0) + vext.8 d27, d2, d3, #1 @//extract a[1] (column1,row0) + vext.8 d26, d2, d3, #4 @//extract a[4] (column1,row0) + vmlal.u8 q4, d29, d1 @// a0 + a5 + 20a2 + 20a3 (column1,row0) + vmlal.u8 q4, d30, d1 @// a0 + a5 + 20a2 (column1,row0) + vmlsl.u8 q4, d27, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row0) + vmlsl.u8 q4, d26, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row0) + vqrshrun.s16 d23, q7, #5 @// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row0) + vst1.8 {d23}, [r1], r3 @//Store dest row0 + vqrshrun.s16 d20, q4, #5 @// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row1) + vst1.8 {d20}, [r1], r3 @//Store dest row1 + subs r5, r5, #2 @ 2 rows done, decrement by 2 + + beq end_func @ Branch if height==4 + + b loop_8 @looping if height =8 or 16 + +loop_4: + vld1.8 {d5, d6}, [r0], r2 @// Load row1 + vext.8 d28, d5, d6, #5 @//extract a[5] (column1,row1) + vld1.8 {d2, d3}, [r0], r2 @// Load row0 + vext.8 d25, d5, d6, #2 @//extract a[2] (column1,row1) + vext.8 d31, d2, d3, #5 @//extract a[5] (column1,row0) + vaddl.u8 q7, d28, d5 @// a0 + a5 (column1,row1) + vext.8 d24, d5, d6, #3 @//extract a[3] (column1,row1) + vext.8 d23, d5, d6, #1 @//extract a[1] (column1,row1) + vext.8 d22, d5, d6, #4 @//extract a[4] (column1,row1) + vext.8 d29, d2, d3, #3 @//extract a[3] (column1,row0) + vmlal.u8 q7, d25, d1 @// a0 + a5 + 20a2 (column1,row1) + vmlal.u8 q7, d24, d1 @// a0 + a5 + 20a2 + 20a3 (column1,row1) + vmlsl.u8 q7, d23, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row1) + vmlsl.u8 q7, d22, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row1) + vaddl.u8 q4, d31, d2 @// a0 + a5 (column1,row0) + vext.8 d30, d2, d3, #2 @//extract a[2] (column1,row0) + vext.8 d27, d2, d3, #1 @//extract a[1] (column1,row0) + vext.8 d26, d2, d3, #4 @//extract a[4] (column1,row0) + vmlal.u8 q4, d29, d1 @// a0 + a5 + 20a2 + 20a3 (column1,row0) + vmlal.u8 q4, d30, d1 @// a0 + a5 + 20a2 (column1,row0) + vmlsl.u8 q4, d27, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row0) + vmlsl.u8 q4, d26, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row0) + vqrshrun.s16 d23, q7, #5 @// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row0) + vst1.32 d23[0], [r1], r3 @//Store dest row0 + vqrshrun.s16 d20, q4, #5 @// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row1) + vst1.32 d20[0], [r1], r3 @//Store dest row1 + subs r5, r5, #2 @ 2 rows done, decrement by 2 + beq end_func + + b loop_4 + +end_func: + vldmia sp!, {d8-d15} @ Restore neon registers that were saved + ldmfd sp!, {r4-r12, pc} @Restoring registers from stack + + diff --git a/dependencies/ih264d/common/arm/ih264_inter_pred_filters_luma_vert_a9q.s b/dependencies/ih264d/common/arm/ih264_inter_pred_filters_luma_vert_a9q.s new file mode 100644 index 00000000..65c40a68 --- /dev/null +++ b/dependencies/ih264d/common/arm/ih264_inter_pred_filters_luma_vert_a9q.s @@ -0,0 +1,301 @@ +@/****************************************************************************** +@ * +@ * Copyright (C) 2015 The Android Open Source Project +@ * +@ * Licensed under the Apache License, Version 2.0 (the "License"); +@ * you may not use this file except in compliance with the License. +@ * You may obtain a copy of the License at: +@ * +@ * http://www.apache.org/licenses/LICENSE-2.0 +@ * +@ * Unless required by applicable law or agreed to in writing, software +@ * distributed under the License is distributed on an "AS IS" BASIS, +@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@ * See the License for the specific language governing permissions and +@ * limitations under the License. +@ * +@ ***************************************************************************** +@ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +@*/ +@** +@****************************************************************************** +@* @file +@* ih264_inter_pred_luma_vert_a9q.s +@* +@* @brief +@* Contains function definitions for inter prediction interpolation. +@* +@* @author +@* Ittiam +@* +@* @par List of Functions: +@* +@* - ih264_inter_pred_luma_vert_a9q() +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* + +@* All the functions here are replicated from ih264_inter_pred_filters.c +@ + +@** +@** +@** +@ ******************************************************************************* +@ * +@ * @brief +@ * Interprediction luma filter for vertical input +@ * +@ * @par Description: +@ * Applies a 6 tap vertcal filter.The output is clipped to 8 bits +@ * sec 8.4.2.2.1 titled "Luma sample interpolation process" +@ * +@ * @param[in] pu1_src +@ * UWORD8 pointer to the source +@ * +@ * @param[out] pu1_dst +@ * UWORD8 pointer to the destination +@ * +@ * @param[in] src_strd +@ * integer source stride +@ * +@ * @param[in] dst_strd +@ * integer destination stride +@ * +@ * @param[in] ht +@ * integer height of the array +@ * +@ * @param[in] wd +@ * integer width of the array +@ * +@ * @returns +@ * +@ * @remarks +@ * None +@ * +@ ******************************************************************************* + +@void ih264_inter_pred_luma_vert ( +@ UWORD8 *pu1_src, +@ UWORD8 *pu1_dst, +@ WORD32 src_strd, +@ WORD32 dst_strd, +@ WORD32 ht, +@ WORD32 wd ) + +@**************Variables Vs Registers***************************************** +@ r0 => *pu1_src +@ r1 => *pu1_dst +@ r2 => src_strd +@ r3 => dst_strd +@ r5 => ht +@ r6 => wd + +.text +.p2align 2 + + + .global ih264_inter_pred_luma_vert_a9q + +ih264_inter_pred_luma_vert_a9q: + + stmfd sp!, {r4-r12, r14} @store register values to stack + vstmdb sp!, {d8-d15} @push neon registers to stack + ldr r5, [sp, #104] @Loads ht + sub r0, r0, r2, lsl #1 @pu1_src-2*src_strd + ldr r6, [sp, #108] @Loads wd + vmov.u16 q11, #20 @ Filter coeff 0x14 into Q11 + + subs r12, r6, #8 @if wd=8 branch to loop_8 + vmov.u16 q12, #5 @ Filter coeff 0x5 into Q12 + beq loop_8 + + subs r12, r6, #4 @if wd=4 branch to loop_4 + beq loop_4 + +loop_16: @when wd=16 + + vld1.u32 {q0}, [r0], r2 @ Vector load from src[0_0] + vld1.u32 {q1}, [r0], r2 @ Vector load from src[1_0] + vld1.u32 {q2}, [r0], r2 @ Vector load from src[2_0] + vld1.u32 {q3}, [r0], r2 @ Vector load from src[3_0] + vld1.u32 {q4}, [r0], r2 @ Vector load from src[4_0] + vaddl.u8 q6, d4, d6 @ temp1 = src[2_0] + src[3_0] + vld1.u32 {q5}, [r0], r2 @ Vector load from src[5_0] + + vaddl.u8 q7, d0, d10 @ temp = src[0_0] + src[5_0] + vaddl.u8 q8, d2, d8 @ temp2 = src[1_0] + src[4_0] + vmla.u16 q7, q6, q11 @ temp += temp1 * 20 + vaddl.u8 q10, d1, d11 @ temp4 = src[0_8] + src[5_8] + vaddl.u8 q9, d5, d7 @ temp3 = src[2_8] + src[3_8] + vmla.u16 q10, q9, q11 @ temp4 += temp3 * 20 + vld1.u32 {q0}, [r0], r2 + vaddl.u8 q13, d3, d9 @ temp5 = src[1_8] + src[4_8] + vaddl.u8 q6, d6, d8 + vmls.u16 q7, q8, q12 @ temp -= temp2 * 5 + vaddl.u8 q8, d2, d0 + vaddl.u8 q9, d4, d10 + vmla.u16 q8, q6, q11 + vmls.u16 q10, q13, q12 @ temp4 -= temp5 * 5 + vaddl.u8 q13, d5, d11 + vaddl.u8 q6, d7, d9 + vqrshrun.s16 d30, q7, #5 @ dst[0_0] = CLIP_U8((temp +16) >> 5) + vaddl.u8 q7, d3, d1 + vld1.u32 {q1}, [r0], r2 + vmla.u16 q7, q6, q11 + vmls.u16 q8, q9, q12 + vqrshrun.s16 d31, q10, #5 @ dst[0_8] = CLIP_U8((temp4 +16) >> 5) + vaddl.u8 q9, d4, d2 + vaddl.u8 q6, d8, d10 + + vst1.u32 {q15}, [r1], r3 @ Vector store to dst[0_0] + vmla.u16 q9, q6, q11 + vaddl.u8 q10, d6, d0 + vmls.u16 q7, q13, q12 + vqrshrun.s16 d30, q8, #5 + vaddl.u8 q6, d9, d11 + vaddl.u8 q8, d5, d3 + vaddl.u8 q13, d7, d1 + vmla.u16 q8, q6, q11 + vmls.u16 q9, q10, q12 + vld1.u32 {q2}, [r0], r2 + + vqrshrun.s16 d31, q7, #5 + vaddl.u8 q6, d10, d0 + vaddl.u8 q7, d6, d4 + vaddl.u8 q10, d8, d2 + vmla.u16 q7, q6, q11 + vmls.u16 q8, q13, q12 + vst1.u32 {q15}, [r1], r3 @store row 1 + vqrshrun.s16 d30, q9, #5 + vaddl.u8 q9, d7, d5 + vaddl.u8 q6, d11, d1 + vmla.u16 q9, q6, q11 + vaddl.u8 q13, d9, d3 + vmls.u16 q7, q10, q12 + + vqrshrun.s16 d31, q8, #5 + vmls.u16 q9, q13, q12 + vaddl.u8 q6, d0, d2 @ temp1 = src[2_0] + src[3_0] + vst1.u32 {q15}, [r1], r3 @store row 2 + vaddl.u8 q8, d10, d4 @ temp2 = src[1_0] + src[4_0] + vaddl.u8 q10, d9, d7 @ temp4 = src[0_8] + src[5_8] + vqrshrun.s16 d30, q7, #5 + vaddl.u8 q13, d5, d11 @ temp5 = src[1_8] + src[4_8] + vaddl.u8 q7, d8, d6 @ temp = src[0_0] + src[5_0] + vqrshrun.s16 d31, q9, #5 + vmla.u16 q7, q6, q11 @ temp += temp1 * 20 + vaddl.u8 q9, d1, d3 @ temp3 = src[2_8] + src[3_8] + vst1.u32 {q15}, [r1], r3 @store row 3 + subs r5, r5, #4 @ 4 rows processed, decrement by 4 + subne r0, r0 , r2, lsl #2 + subne r0, r0, r2 + beq end_func @ Branch if height==4 + + b loop_16 @ looping if height = 8 or 16 + +loop_8: +@ Processing row0 and row1 + + vld1.u32 d0, [r0], r2 @ Vector load from src[0_0] + vld1.u32 d1, [r0], r2 @ Vector load from src[1_0] + vld1.u32 d2, [r0], r2 @ Vector load from src[2_0] + vld1.u32 d3, [r0], r2 @ Vector load from src[3_0] + vld1.u32 d4, [r0], r2 @ Vector load from src[4_0] + vld1.u32 d5, [r0], r2 @ Vector load from src[5_0] + + vaddl.u8 q3, d2, d3 @ temp1 = src[2_0] + src[3_0] + vaddl.u8 q4, d0, d5 @ temp = src[0_0] + src[5_0] + vaddl.u8 q5, d1, d4 @ temp2 = src[1_0] + src[4_0] + vmla.u16 q4, q3, q11 @ temp += temp1 * 20 + vld1.u32 d6, [r0], r2 + vaddl.u8 q7, d3, d4 + vaddl.u8 q8, d1, d6 + vaddl.u8 q9, d2, d5 + vmls.u16 q4, q5, q12 @ temp -= temp2 * 5 + vmla.u16 q8, q7, q11 + vld1.u32 d7, [r0], r2 + vaddl.u8 q10, d4, d5 + vaddl.u8 q6, d2, d7 + vaddl.u8 q5, d3, d6 + vmls.u16 q8, q9, q12 + vqrshrun.s16 d26, q4, #5 @ dst[0_0] = CLIP_U8( (temp + 16) >> 5) + vmla.u16 q6, q10, q11 + vld1.u32 d0, [r0], r2 + vaddl.u8 q7, d5, d6 + vqrshrun.s16 d27, q8, #5 + vaddl.u8 q10, d3, d0 + vmls.u16 q6, q5, q12 + vst1.u32 d26, [r1], r3 @ Vector store to dst[0_0] + vaddl.u8 q9, d4, d7 + vmla.u16 q10, q7, q11 + vst1.u32 d27, [r1], r3 + vqrshrun.s16 d28, q6, #5 + vst1.u32 d28, [r1], r3 + vmls.u16 q10, q9, q12 + vqrshrun.s16 d29, q10, #5 + vst1.u32 d29, [r1], r3 @store row 3 + + subs r5, r5, #4 @ 4 rows processed, decrement by 4 + subne r0, r0 , r2, lsl #2 + subne r0, r0, r2 + beq end_func @ Branch if height==4 + + b loop_8 @looping if height == 8 or 16 + + +loop_4: +@ Processing row0 and row1 + + vld1.u32 d0[0], [r0], r2 @ Vector load from src[0_0] + vld1.u32 d1[0], [r0], r2 @ Vector load from src[1_0] + vld1.u32 d2[0], [r0], r2 @ Vector load from src[2_0] + vld1.u32 d3[0], [r0], r2 @ Vector load from src[3_0] + vld1.u32 d4[0], [r0], r2 @ Vector load from src[4_0] + vld1.u32 d5[0], [r0], r2 @ Vector load from src[5_0] + + vaddl.u8 q3, d2, d3 @ temp1 = src[2_0] + src[3_0] + vaddl.u8 q4, d0, d5 @ temp = src[0_0] + src[5_0] + vaddl.u8 q5, d1, d4 @ temp2 = src[1_0] + src[4_0] + vmla.u16 q4, q3, q11 @ temp += temp1 * 20 + vld1.u32 d6[0], [r0], r2 + vaddl.u8 q7, d3, d4 + vaddl.u8 q8, d1, d6 + vaddl.u8 q9, d2, d5 + vmls.u16 q4, q5, q12 @ temp -= temp2 * 5 + vld1.u32 d7[0], [r0], r2 + vmla.u16 q8, q7, q11 + vaddl.u8 q10, d4, d5 + vaddl.u8 q6, d2, d7 + vaddl.u8 q5, d3, d6 + vmls.u16 q8, q9, q12 + vqrshrun.s16 d26, q4, #5 @ dst[0_0] = CLIP_U8( (temp + 16) >> 5) + vmla.u16 q6, q10, q11 + vld1.u32 d0[0], [r0], r2 + vaddl.u8 q7, d5, d6 + vqrshrun.s16 d27, q8, #5 + vaddl.u8 q10, d3, d0 + vmls.u16 q6, q5, q12 + vst1.u32 d26[0], [r1], r3 @ Vector store to dst[0_0] + vaddl.u8 q9, d4, d7 + vmla.u16 q10, q7, q11 + vst1.u32 d27[0], [r1], r3 + vqrshrun.s16 d28, q6, #5 + vst1.u32 d28[0], [r1], r3 + vmls.u16 q10, q9, q12 + vqrshrun.s16 d29, q10, #5 + vst1.u32 d29[0], [r1], r3 @store row 3 + + subs r5, r5, #8 + subeq r0, r0, r2, lsl #2 + subeq r0, r0, r2 + beq loop_4 @ Loop if height==8 + +end_func: + vldmia sp!, {d8-d15} @ Restore neon registers that were saved + ldmfd sp!, {r4-r12, pc} @Restoring registers from stack + + diff --git a/dependencies/ih264d/common/arm/ih264_inter_pred_luma_bilinear_a9q.s b/dependencies/ih264d/common/arm/ih264_inter_pred_luma_bilinear_a9q.s new file mode 100644 index 00000000..8f049f8c --- /dev/null +++ b/dependencies/ih264d/common/arm/ih264_inter_pred_luma_bilinear_a9q.s @@ -0,0 +1,398 @@ +@/****************************************************************************** +@ * +@ * Copyright (C) 2015 The Android Open Source Project +@ * +@ * Licensed under the Apache License, Version 2.0 (the "License"); +@ * you may not use this file except in compliance with the License. +@ * You may obtain a copy of the License at: +@ * +@ * http://www.apache.org/licenses/LICENSE-2.0 +@ * +@ * Unless required by applicable law or agreed to in writing, software +@ * distributed under the License is distributed on an "AS IS" BASIS, +@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@ * See the License for the specific language governing permissions and +@ * limitations under the License. +@ * +@ ***************************************************************************** +@ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +@*/ +@** +@****************************************************************************** +@* @file +@* ih264_inter_pred_luma_bilinear_a9q.s +@* +@* @brief +@* Contains function definitions for inter prediction interpolation. +@* +@* @author +@* Ittiam +@* +@* @par List of Functions: +@* +@* - ih264_inter_pred_luma_bilinear_a9q() +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* + +@* All the functions here are replicated from ih264_inter_pred_filters.c +@ + +@** +@** +@** +@ ******************************************************************************* +@ * function:ih264_inter_pred_luma_bilinear +@ * +@* @brief +@* This routine applies the bilinear filter to the predictors . +@* The filtering operation is described in +@* sec 8.4.2.2.1 titled "Luma sample interpolation process" +@* +@* @par Description: +@\note +@* This function is called to obtain pixels lying at the following +@* locations (1/4,1), (3/4,1),(1,1/4), (1,3/4) ,(1/4,1/2), (3/4,1/2),(1/2,1/4), (1/2,3/4),(3/4,1/4),(1/4,3/4),(3/4,3/4)&& (1/4,1/4) . +@* The function averages the two adjacent values from the two input arrays in horizontal direction. +@* +@* +@* @param[in] pu1_src1: +@* UWORD8 Pointer to the buffer containing the first input array. +@* +@* @param[in] pu1_src2: +@* UWORD8 Pointer to the buffer containing the second input array. +@* +@* @param[out] pu1_dst +@* UWORD8 pointer to the destination where the output of bilinear filter is stored. +@* +@* @param[in] src_strd1 +@* Stride of the first input buffer +@* +@* @param[in] src_strd2 +@* Stride of the second input buffer +@* +@* @param[in] dst_strd +@* integer destination stride of pu1_dst +@* +@* @param[in] ht +@* integer height of the array +@* +@* @param[in] wd +@* integer width of the array +@* +@* @returns +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* + +@void ih264_inter_pred_luma_bilinear(UWORD8 *pu1_src1, +@ UWORD8 *pu1_src2, +@ UWORD8 *pu1_dst, +@ WORD32 src_strd1, +@ WORD32 src_strd2, +@ WORD32 dst_strd, +@ WORD32 height, +@ WORD32 width) +@ +@**************Variables Vs Registers***************************************** +@ r0 => *pu1_src1 +@ r1 => *pu1_src2 +@ r2 => *pu1_dst +@ r3 => src_strd1 +@ r4 => src_strd2 +@ r5 => dst_strd +@ r6 => height +@ r7 => width +@ +.text +.p2align 2 + + .global ih264_inter_pred_luma_bilinear_a9q + +ih264_inter_pred_luma_bilinear_a9q: + + + + stmfd sp!, {r4-r12, r14} @store register values to stack + vstmdb sp!, {d8-d15} @push neon registers to stack + ldr r4, [sp, #104] + ldr r5, [sp, #108] @ + ldr r6, [sp, #112] + ldr r7, [sp, #116] + + subs r12, r7, #4 @if wd=4 branch to loop_4 + beq loop_4 + subs r12, r7, #8 @if wd=8 branch to loop_8 + beq loop_8 + +loop_16: @when wd=16 + + vld1.8 {q0}, [r0], r3 @// Load row0 ;src1 + vld1.8 {q2}, [r1], r4 @// Load row0 ;src2 + vld1.8 {q1}, [r0], r3 @// Load row1 ;src1 + vaddl.u8 q10, d0, d4 + vld1.8 {q3}, [r1], r4 @// Load row1 ;src2 + vaddl.u8 q11, d1, d5 + vld1.8 {q4}, [r0], r3 @// Load row2 ;src1 + vaddl.u8 q12, d2, d6 + vld1.8 {q5}, [r0], r3 @// Load row3 ;src1 + vaddl.u8 q13, d3, d7 + vld1.8 {q6}, [r1], r4 @// Load row2 ;src2 + vaddl.u8 q8, d8, d12 + vld1.8 {q7}, [r1], r4 @// Load row3 ;src2 + vaddl.u8 q9, d9, d13 + vqrshrun.s16 d28, q10, #1 + vqrshrun.s16 d29, q11, #1 + vaddl.u8 q10, d10, d14 + vqrshrun.s16 d30, q12, #1 + vqrshrun.s16 d31, q13, #1 + vst1.8 {q14}, [r2], r5 @//Store dest row0 + vaddl.u8 q11, d11, d15 + vst1.8 {q15}, [r2], r5 @//Store dest row1 + vqrshrun.s16 d28, q8, #1 + vld1.8 {q0}, [r0], r3 @// Load row4 ;src1 + vqrshrun.s16 d29, q9, #1 + vld1.8 {q1}, [r0], r3 @// Load row5 ;src1 + vqrshrun.s16 d30, q10, #1 + vld1.8 {q2}, [r1], r4 @// Load row4 ;src2 + vqrshrun.s16 d31, q11, #1 + vld1.8 {q3}, [r1], r4 @// Load row5 ;src2 + vaddl.u8 q10, d0, d4 + vst1.8 {q14}, [r2], r5 @//Store dest row2 + vaddl.u8 q13, d3, d7 + vst1.8 {q15}, [r2], r5 @//Store dest row3 + vaddl.u8 q11, d1, d5 + vld1.8 {q4}, [r0], r3 @// Load row6 ;src1 + vaddl.u8 q12, d2, d6 + vld1.8 {q5}, [r0], r3 @// Load row7 ;src1 + vqrshrun.s16 d28, q10, #1 + vld1.8 {q6}, [r1], r4 @// Load row6 ;src2 + vqrshrun.s16 d29, q11, #1 + vld1.8 {q7}, [r1], r4 @// Load row7 ;src2 + vaddl.u8 q8, d8, d12 + vaddl.u8 q9, d9, d13 + vaddl.u8 q10, d10, d14 + vqrshrun.s16 d30, q12, #1 + vqrshrun.s16 d31, q13, #1 + vst1.8 {q14}, [r2], r5 @//Store dest row4 + vaddl.u8 q11, d11, d15 + vst1.8 {q15}, [r2], r5 @//Store dest row5 + vqrshrun.s16 d28, q8, #1 + vqrshrun.s16 d30, q10, #1 + vqrshrun.s16 d29, q9, #1 + vld1.8 {q2}, [r1], r4 @// Load row8 ;src2 + vqrshrun.s16 d31, q11, #1 + vst1.8 {q14}, [r2], r5 @//Store dest row6 + subs r12, r6, #8 + vst1.8 {q15}, [r2], r5 @//Store dest row7 + + beq end_func @ end function if ht=8 + + vld1.8 {q0}, [r0], r3 @// Load row8 ;src1 + vaddl.u8 q10, d0, d4 + vld1.8 {q1}, [r0], r3 @// Load row9 ;src1 + vaddl.u8 q11, d1, d5 + vld1.8 {q3}, [r1], r4 @// Load row9 ;src2 + vqrshrun.s16 d28, q10, #1 + vld1.8 {q4}, [r0], r3 @// Load row10 ;src1 + vqrshrun.s16 d29, q11, #1 + vld1.8 {q5}, [r0], r3 @// Load row11 ;src1 + vaddl.u8 q12, d2, d6 + vld1.8 {q6}, [r1], r4 @// Load row10 ;src2 + vaddl.u8 q13, d3, d7 + vld1.8 {q7}, [r1], r4 @// Load row11 ;src2 + vaddl.u8 q8, d8, d12 + vaddl.u8 q9, d9, d13 + vaddl.u8 q10, d10, d14 + vqrshrun.s16 d30, q12, #1 + vst1.8 {q14}, [r2], r5 @//Store dest row8 + vqrshrun.s16 d31, q13, #1 + vst1.8 {q15}, [r2], r5 @//Store dest row9 + vqrshrun.s16 d28, q8, #1 + vld1.8 {q0}, [r0], r3 @// Load row12 ;src1 + vaddl.u8 q11, d11, d15 + vld1.8 {q1}, [r0], r3 @// Load row13 ;src1 + vqrshrun.s16 d29, q9, #1 + vld1.8 {q2}, [r1], r4 @// Load row12 ;src2 + vqrshrun.s16 d30, q10, #1 + vld1.8 {q3}, [r1], r4 @// Load row13 ;src2 + vqrshrun.s16 d31, q11, #1 + vst1.8 {q14}, [r2], r5 @//Store dest row10 + vaddl.u8 q10, d0, d4 + vst1.8 {q15}, [r2], r5 @//Store dest row11 + vaddl.u8 q11, d1, d5 + vld1.8 {q4}, [r0], r3 @// Load row14 ;src1 + vaddl.u8 q13, d3, d7 + vld1.8 {q5}, [r0], r3 @// Load row15 ;src1 + vaddl.u8 q12, d2, d6 + vld1.8 {q6}, [r1], r4 @// Load row14 ;src2 + vaddl.u8 q8, d8, d12 + vld1.8 {q7}, [r1], r4 @// Load row15 ;src2 + vaddl.u8 q9, d9, d13 + vqrshrun.s16 d28, q10, #1 + vqrshrun.s16 d29, q11, #1 + vaddl.u8 q10, d10, d14 + vst1.8 {q14}, [r2], r5 @//Store dest row12 + vqrshrun.s16 d30, q12, #1 + vqrshrun.s16 d31, q13, #1 + vaddl.u8 q11, d11, d15 + vst1.8 {q15}, [r2], r5 @//Store dest row13 + vqrshrun.s16 d28, q8, #1 + vqrshrun.s16 d29, q9, #1 + vqrshrun.s16 d30, q10, #1 + vst1.8 {q14}, [r2], r5 @//Store dest row14 + vqrshrun.s16 d31, q11, #1 + vst1.8 {q15}, [r2], r5 @//Store dest row15 + b end_func + + + +loop_8: @wd=8; + vld1.8 {d0}, [r0], r3 @// Load row0 ;src1 + vld1.8 {d4}, [r1], r4 @// Load row0 ;src2 + vld1.8 {d1}, [r0], r3 @// Load row1 ;src1 + vaddl.u8 q10, d0, d4 + vld1.8 {d5}, [r1], r4 @// Load row1 ;src2 + vld1.8 {d2}, [r0], r3 @// Load row2 ;src1 + vqrshrun.s16 d28, q10, #1 + vld1.8 {d6}, [r1], r4 @// Load row2 ;src2 + vaddl.u8 q11, d1, d5 + vld1.8 {d3}, [r0], r3 @// Load row3 ;src1 + vaddl.u8 q12, d2, d6 + vst1.8 {d28}, [r2], r5 @//Store dest row0 + vqrshrun.s16 d29, q11, #1 + vld1.8 {d7}, [r1], r4 @// Load row3 ;src2 + vqrshrun.s16 d30, q12, #1 + vst1.8 {d29}, [r2], r5 @//Store dest row1 + vaddl.u8 q13, d3, d7 + vst1.8 {d30}, [r2], r5 @//Store dest row2 + vqrshrun.s16 d31, q13, #1 + subs r12, r6, #4 + vst1.8 {d31}, [r2], r5 @//Store dest row3 + beq end_func @ end function if ht=4 + + vld1.8 {d12}, [r1], r4 @// Load row4 ;src2 + vld1.8 {d8}, [r0], r3 @// Load row4 ;src1 + vld1.8 {d9}, [r0], r3 @// Load row5 ;src1 + vaddl.u8 q8, d8, d12 + vld1.8 {d13}, [r1], r4 @// Load row5 ;src2 + vld1.8 {d10}, [r0], r3 @// Load row6;src1 + vaddl.u8 q9, d9, d13 + vld1.8 {d14}, [r1], r4 @// Load row6 ;src2 + vqrshrun.s16 d28, q8, #1 + vld1.8 {d11}, [r0], r3 @// Load row7 ;src1 + vqrshrun.s16 d29, q9, #1 + vst1.8 {d28}, [r2], r5 @//Store dest row4 + vaddl.u8 q10, d10, d14 + vst1.8 {d29}, [r2], r5 @//Store dest row5 + vqrshrun.s16 d30, q10, #1 + vld1.8 {d15}, [r1], r4 @// Load row7 ;src2 + vaddl.u8 q11, d11, d15 + vst1.8 {d30}, [r2], r5 @//Store dest row6 + vqrshrun.s16 d31, q11, #1 + subs r12, r6, #8 + vst1.8 {d31}, [r2], r5 @//Store dest row7 + beq end_func @ end function if ht=8 + + vld1.8 {d0}, [r0], r3 @// Load row8 ;src1 + vld1.8 {d4}, [r1], r4 @// Load row8 ;src2 + vld1.8 {d1}, [r0], r3 @// Load row9 ;src1 + vaddl.u8 q10, d0, d4 + vld1.8 {d5}, [r1], r4 @// Load row9 ;src2 + vld1.8 {d2}, [r0], r3 @// Load row10 ;src1 + vaddl.u8 q11, d1, d5 + vld1.8 {d6}, [r1], r4 @// Load row10 ;src2 + vqrshrun.s16 d28, q10, #1 + vld1.8 {d3}, [r0], r3 @// Load row11 ;src1 + vaddl.u8 q12, d2, d6 + vld1.8 {d7}, [r1], r4 @// Load row11 ;src2 + vqrshrun.s16 d29, q11, #1 + vld1.8 {d8}, [r0], r3 @// Load row12 ;src1 + vaddl.u8 q13, d3, d7 + vst1.8 {d28}, [r2], r5 @//Store dest row8 + vqrshrun.s16 d30, q12, #1 + vld1.8 {d12}, [r1], r4 @// Load row12 ;src2 + vqrshrun.s16 d31, q13, #1 + vst1.8 {d29}, [r2], r5 @//Store dest row9 + vaddl.u8 q8, d8, d12 + vld1.8 {d9}, [r0], r3 @// Load row13 ;src1 + vqrshrun.s16 d28, q8, #1 + vld1.8 {d13}, [r1], r4 @// Load row13 ;src2 + vld1.8 {d10}, [r0], r3 @// Load row14;src1 + vaddl.u8 q9, d9, d13 + vld1.8 {d11}, [r0], r3 @// Load row15 ;src1 + vld1.8 {d14}, [r1], r4 @// Load row14 ;src2 + vqrshrun.s16 d29, q9, #1 + vld1.8 {d15}, [r1], r4 @// Load roW15 ;src2 + vaddl.u8 q10, d10, d14 + vst1.8 {d30}, [r2], r5 @//Store dest row10 + vaddl.u8 q11, d11, d15 + vst1.8 {d31}, [r2], r5 @//Store dest row11 + vqrshrun.s16 d30, q10, #1 + vst1.8 {d28}, [r2], r5 @//Store dest row12 + vqrshrun.s16 d31, q11, #1 + vst1.8 {d29}, [r2], r5 @//Store dest row13 + vst1.8 {d30}, [r2], r5 @//Store dest row14 + vst1.8 {d31}, [r2], r5 @//Store dest row15 + + b end_func + + + +loop_4: + vld1.32 d0[0], [r0], r3 @// Load row0 ;src1 + vld1.32 d4[0], [r1], r4 @// Load row0 ;src2 + vld1.32 d1[0], [r0], r3 @// Load row1 ;src1 + vaddl.u8 q10, d0, d4 + vld1.32 d5[0], [r1], r4 @// Load row1 ;src2 + vld1.32 d2[0], [r0], r3 @// Load row2 ;src1 + vqrshrun.s16 d28, q10, #1 + vld1.32 d6[0], [r1], r4 @// Load row2 ;src2 + vaddl.u8 q11, d1, d5 + vld1.32 d3[0], [r0], r3 @// Load row3 ;src1 + vaddl.u8 q12, d2, d6 + vst1.32 d28[0], [r2], r5 @//Store dest row0 + vqrshrun.s16 d29, q11, #1 + vld1.32 d7[0], [r1], r4 @// Load row3 ;src2 + vqrshrun.s16 d30, q12, #1 + vst1.32 d29[0], [r2], r5 @//Store dest row1 + vaddl.u8 q13, d3, d7 + vst1.32 d30[0], [r2], r5 @//Store dest row2 + vqrshrun.s16 d31, q13, #1 + subs r12, r6, #4 + vst1.32 d31[0], [r2], r5 @//Store dest row3 + beq end_func @ end function if ht=4 + + vld1.32 d12[0], [r1], r4 @// Load row4 ;src2 + vld1.32 d8[0], [r0], r3 @// Load row4 ;src1 + vld1.32 d9[0], [r0], r3 @// Load row5 ;src1 + vaddl.u8 q8, d8, d12 + vld1.32 d13[0], [r1], r4 @// Load row5 ;src2 + vld1.32 d10[0], [r0], r3 @// Load row6;src1 + vaddl.u8 q9, d9, d13 + vld1.32 d14[0], [r1], r4 @// Load row6 ;src2 + vqrshrun.s16 d28, q8, #1 + vld1.32 d11[0], [r0], r3 @// Load row7 ;src1 + vqrshrun.s16 d29, q9, #1 + vst1.32 d28[0], [r2], r5 @//Store dest row4 + vaddl.u8 q10, d10, d14 + vst1.32 d29[0], [r2], r5 @//Store dest row5 + vqrshrun.s16 d30, q10, #1 + vld1.32 d15[0], [r1], r4 @// Load row7 ;src2 + vaddl.u8 q11, d11, d15 + vst1.32 d30[0], [r2], r5 @//Store dest row6 + vqrshrun.s16 d31, q11, #1 + vst1.32 d31[0], [r2], r5 @//Store dest row7 + +end_func: + + vldmia sp!, {d8-d15} @ Restore neon registers that were saved + ldmfd sp!, {r4-r12, pc} @Restoring registers from stack + + diff --git a/dependencies/ih264d/common/arm/ih264_inter_pred_luma_copy_a9q.s b/dependencies/ih264d/common/arm/ih264_inter_pred_luma_copy_a9q.s new file mode 100644 index 00000000..c0b05686 --- /dev/null +++ b/dependencies/ih264d/common/arm/ih264_inter_pred_luma_copy_a9q.s @@ -0,0 +1,253 @@ +@/****************************************************************************** +@ * +@ * Copyright (C) 2015 The Android Open Source Project +@ * +@ * Licensed under the Apache License, Version 2.0 (the "License"); +@ * you may not use this file except in compliance with the License. +@ * You may obtain a copy of the License at: +@ * +@ * http://www.apache.org/licenses/LICENSE-2.0 +@ * +@ * Unless required by applicable law or agreed to in writing, software +@ * distributed under the License is distributed on an "AS IS" BASIS, +@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@ * See the License for the specific language governing permissions and +@ * limitations under the License. +@ * +@ ***************************************************************************** +@ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +@*/ +@** +@** +@******************************************************************************* +@* +@* @brief +@* Interprediction luma function for copy +@* +@* @par Description: +@* Copies the array of width 'wd' and height 'ht' from the location pointed +@* by 'src' to the location pointed by 'dst' +@* +@* @param[in] pu1_src +@* UWORD8 pointer to the source +@* +@* @param[out] pu1_dst +@* UWORD8 pointer to the destination +@* +@* @param[in] src_strd +@* integer source stride +@* +@* @param[in] dst_strd +@* integer destination stride +@* +@* +@* @param[in] ht +@* integer height of the array +@* +@* @param[in] wd +@* integer width of the array +@* +@* @returns +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* +@void ih264_inter_pred_luma_copy ( +@ UWORD8 *pu1_src, +@ UWORD8 *pu1_dst, +@ WORD32 src_strd, +@ WORD32 dst_strd, +@ WORD32 ht, +@ WORD32 wd ) + +@**************Variables Vs Registers***************************************** +@ r0 => *pu1_src +@ r1 => *pu1_dst +@ r2 => src_strd +@ r3 => dst_strd +@ r7 => ht +@ r12 => wd + +.text +.p2align 2 + + .global ih264_inter_pred_luma_copy_a9q + +ih264_inter_pred_luma_copy_a9q: + stmfd sp!, {r4-r12, r14} @stack stores the values of the arguments + vstmdb sp!, {d8-d15} @push neon registers to stack + ldr r12, [sp, #108] @Loads wd + ldr r7, [sp, #104] @Loads ht + cmp r7, #0 @checks ht == 0 + ble end_loops + tst r12, #15 @checks wd for multiples for 4 & 8 + beq core_loop_wd_16 + tst r12, #7 @checks wd for multiples for 4 & 8 + beq core_loop_wd_8 + sub r11, r12, #4 + +outer_loop_wd_4: + subs r4, r12, #0 @checks wd == 0 + ble end_inner_loop_wd_4 + +inner_loop_wd_4: + vld1.32 {d0[0]}, [r0] @vld1_lane_u32((uint32_t *)pu1_src_tmp, src_tmp, 0) + add r5, r0, r2 @pu1_src_tmp += src_strd + add r6, r1, r3 @pu1_dst_tmp += dst_strd + vst1.32 {d0[0]}, [r1] @vst1_lane_u32((uint32_t *)pu1_dst_tmp, src_tmp, 0) + vld1.32 {d0[0]}, [r5], r2 @vld1_lane_u32((uint32_t *)pu1_src_tmp, src_tmp, 0) + add r0, r0, #4 @pu1_src += 4 + vst1.32 {d0[0]}, [r6], r3 @vst1_lane_u32((uint32_t *)pu1_dst_tmp, src_tmp, 0) + vld1.32 {d0[0]}, [r5], r2 @vld1_lane_u32((uint32_t *)pu1_src_tmp, src_tmp, 0) + subs r4, r4, #4 @(wd -4) + vst1.32 {d0[0]}, [r6], r3 @vst1_lane_u32((uint32_t *)pu1_dst_tmp, src_tmp, 0) + vld1.32 {d0[0]}, [r5], r2 @vld1_lane_u32((uint32_t *)pu1_src_tmp, src_tmp, 0) + add r1, r1, #4 @pu1_dst += 4 + vst1.32 {d0[0]}, [r6], r3 @vst1_lane_u32((uint32_t *)pu1_dst_tmp, src_tmp, 0) + + bgt inner_loop_wd_4 + +end_inner_loop_wd_4: + subs r7, r7, #4 @ht - 4 + sub r0, r5, r11 @pu1_src = pu1_src_tmp + sub r1, r6, r11 @pu1_dst = pu1_dst_tmp + bgt outer_loop_wd_4 + +end_loops: + vldmia sp!, {d8-d15} @ Restore neon registers that were saved + ldmfd sp!, {r4-r12, r15} @Reload the registers from SP + + + +core_loop_wd_8: + sub r11, r12, #8 + +outer_loop_wd_8: + subs r4, r12, #0 @checks wd + ble end_inner_loop_wd_8 + +inner_loop_wd_8: + add r5, r0, r2 @pu1_src_tmp += src_strd + vld1.8 {d0}, [r0]! @vld1_u8(pu1_src_tmp) + add r6, r1, r3 @pu1_dst_tmp += dst_strd + vst1.8 {d0}, [r1]! @vst1_u8(pu1_dst_tmp, tmp_src) + vld1.8 {d1}, [r5], r2 @vld1_u8(pu1_src_tmp) + vst1.8 {d1}, [r6], r3 @vst1_u8(pu1_dst_tmp, tmp_src) + subs r4, r4, #8 @wd - 8(Loop condition) + vld1.8 {d2}, [r5], r2 @vld1_u8(pu1_src_tmp) + vst1.8 {d2}, [r6], r3 @vst1_u8(pu1_dst_tmp, tmp_src) + vld1.8 {d3}, [r5], r2 @vld1_u8(pu1_src_tmp) + vst1.8 {d3}, [r6], r3 @vst1_u8(pu1_dst_tmp, tmp_src) + bgt inner_loop_wd_8 + +end_inner_loop_wd_8: + subs r7, r7, #4 @ht -= 4 + sub r0, r5, r11 @pu1_src = pu1_src_tmp + sub r1, r6, r11 @pu1_dst = pu1_dst_tmp + bgt outer_loop_wd_8 + + vldmia sp!, {d8-d15} @ Restore neon registers that were saved + ldmfd sp!, {r4-r12, r15} @Reload the registers from SP + +core_loop_wd_16: + sub r11, r12, #16 + +outer_loop_wd_16: + subs r4, r12, #0 @checks wd + ble end_inner_loop_wd_16 + +inner_loop_wd_16: + add r5, r0, r2 @pu1_src_tmp += src_strd + vld1.8 {q0}, [r0]! @vld1_u8(pu1_src_tmp) + add r6, r1, r3 @pu1_dst_tmp += dst_strd + vst1.8 {q0}, [r1]! @vst1_u8(pu1_dst_tmp, tmp_src) + vld1.8 {q1}, [r5], r2 @vld1_u8(pu1_src_tmp) + vst1.8 {q1}, [r6], r3 @vst1_u8(pu1_dst_tmp, tmp_src) + subs r4, r4, #16 @wd - 8(Loop condition) + vld1.8 {q2}, [r5], r2 @vld1_u8(pu1_src_tmp) + vst1.8 {q2}, [r6], r3 @vst1_u8(pu1_dst_tmp, tmp_src) + vld1.8 {q3}, [r5], r2 @vld1_u8(pu1_src_tmp) + vst1.8 {q3}, [r6], r3 @vst1_u8(pu1_dst_tmp, tmp_src) + bgt inner_loop_wd_16 + +end_inner_loop_wd_16: + subs r7, r7, #4 @ht -= 4 + sub r0, r5, r11 @pu1_src = pu1_src_tmp + sub r1, r6, r11 @pu1_dst = pu1_dst_tmp + bgt outer_loop_wd_16 + + vldmia sp!, {d8-d15} @ Restore neon registers that were saved + ldmfd sp!, {r4-r12, r15} @Reload the registers from SP + + +@ * +@ ******************************************************************************** +@ * +@ * @brief This function copies a 4x4 block to destination +@ * +@ * @par Description: +@ * Copies a 4x4 block to destination, where both src and dst are interleaved +@ * +@ * @param[in] pi2_src +@ * Source +@ * +@ * @param[in] pu1_out +@ * Output pointer +@ * +@ * @param[in] pred_strd, +@ * Prediction buffer stride +@ * +@ * @param[in] out_strd +@ * output buffer buffer Stride +@ * +@ * @returns none +@ * +@ * @remarks none +@ * Currently wd and height is not used, ie a 4x4 block is always copied +@ * +@ ******************************************************************************* +@ * +@ void ih264_interleave_copy(WORD16 *pi2_src, +@ UWORD8 *pu1_out, +@ WORD32 pred_strd, +@ WORD32 out_strd +@ WORD32 wd +@ WORD32 ht) +@ Register Usage +@ r0 : pi2_src +@ r1 : pu1_out +@ r2 : src_strd +@ r3 : out_strd +@ Neon registers d0-d7, d16-d30 are used +@ No need for pushing arm and neon registers + + .global ih264_interleave_copy_a9 +ih264_interleave_copy_a9: + + vld1.u8 d2, [r0], r2 @load src plane 1 => d2 &pred palne 2 => d3 + vld1.u8 d3, [r0], r2 + vld1.u8 d4, [r0], r2 + vld1.u8 d5, [r0], r2 + + mov r0, r1 + + vld1.u8 d18, [r1], r3 @load out [8 bit size) -8 coeffs + vld1.u8 d19, [r1], r3 + vmov.u16 q15, #0x00ff + vld1.u8 d20, [r1], r3 + vld1.u8 d21, [r1], r3 + + vbit.u8 q9, q1, q15 + vbit.u8 q10, q2, q15 + + vst1.u8 d18, [r0], r3 @store out + vst1.u8 d19, [r0], r3 + vst1.u8 d20, [r0], r3 + vst1.u8 d21, [r0], r3 + + bx lr + + + diff --git a/dependencies/ih264d/common/arm/ih264_inter_pred_luma_horz_hpel_vert_hpel_a9q.s b/dependencies/ih264d/common/arm/ih264_inter_pred_luma_horz_hpel_vert_hpel_a9q.s new file mode 100644 index 00000000..2066a205 --- /dev/null +++ b/dependencies/ih264d/common/arm/ih264_inter_pred_luma_horz_hpel_vert_hpel_a9q.s @@ -0,0 +1,441 @@ +@/****************************************************************************** +@ * +@ * Copyright (C) 2015 The Android Open Source Project +@ * +@ * Licensed under the Apache License, Version 2.0 (the "License"); +@ * you may not use this file except in compliance with the License. +@ * You may obtain a copy of the License at: +@ * +@ * http://www.apache.org/licenses/LICENSE-2.0 +@ * +@ * Unless required by applicable law or agreed to in writing, software +@ * distributed under the License is distributed on an "AS IS" BASIS, +@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@ * See the License for the specific language governing permissions and +@ * limitations under the License. +@ * +@ ***************************************************************************** +@ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +@*/ +@** +@****************************************************************************** +@* @file +@* ih264_inter_pred_luma_horz_hpel_vert_hpel_a9q.s +@* +@* @brief +@* Contains function definitions for inter prediction interpolation. +@* +@* @author +@* Mohit +@* +@* @par List of Functions: +@* +@* - ih264_inter_pred_luma_horz_hpel_vert_hpel_a9q() +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* + +@* All the functions here are replicated from ih264_inter_pred_filters.c +@ + +@** +@** +@** +@******************************************************************************* +@* +@* @brief +@* This function implements a two stage cascaded six tap filter. It +@* applies the six tap filter in the vertical direction on the +@* predictor values, followed by applying the same filter in the +@* horizontal direction on the output of the first stage. The six tap +@* filtering operation is described in sec 8.4.2.2.1 titled "Luma sample +@* interpolation process" +@* +@* @par Description: +@* This function is called to obtain pixels lying at the following +@* location (1/2,1/2). The function interpolates +@* the predictors first in the horizontal direction and then in the +@* vertical direction to output the (1/2,1/2). +@* +@* @param[in] pu1_src +@* UWORD8 pointer to the source +@* +@* @param[out] pu1_dst +@* UWORD8 pointer to the destination +@* +@* @param[in] src_strd +@* integer source stride +@* +@* @param[in] dst_strd +@* integer destination stride +@* +@* @param[in] ht +@* integer height of the array +@* +@* @param[in] wd +@* integer width of the array +@* +@* @param[in] pu1_tmp: temporary buffer +@* +@* @param[in] dydx: x and y reference offset for qpel calculations: UNUSED in this function. +@* +@* @returns +@* +@* @remarks +@* None +@* +@******************************************************************************* +@*; + +@void ih264_inter_pred_luma_horz_hpel_vert_hpel(UWORD8 *pu1_src, +@ UWORD8 *pu1_dst, +@ WORD32 src_strd,, +@ WORD32 dst_strd, +@ WORD32 ht, +@ WORD32 wd, +@ UWORD8* pu1_tmp, +@ UWORD32 dydx) + +@**************Variables Vs Registers***************************************** +@ r0 => *pu1_src +@ r1 => *pu1_dst +@ r2 => src_strd +@ r3 => dst_strd +@ r8 => ht +@ r9 => wd + +.text +.p2align 2 + + .global ih264_inter_pred_luma_horz_hpel_vert_hpel_a9q + +ih264_inter_pred_luma_horz_hpel_vert_hpel_a9q: + + stmfd sp!, {r4-r12, r14} @store register values to stack + vstmdb sp!, {d8-d15} @push neon registers to stack + ldr r8, [sp, #104] @ loads ht + sub r0, r0, r2, lsl #1 @pu1_src-2*src_strd + sub r0, r0, #2 @pu1_src-2 + ldr r9, [sp, #108] @ loads wd + + vmov.s16 d0, #20 @ Filter coeff 20 + vmov.s16 d1, #5 @ Filter coeff 5 + subs r12, r9, #4 @if wd=4 branch to loop_4 + beq loop_4 + subs r12, r9, #8 @if wd=8 branch to loop_8 + beq loop_8 + + mov r10, #8 + sub r7, r3, r10 + @when wd=16 + +loop_16: + vld1.u32 {d2, d3, d4}, [r0], r2 @ Vector load from src[0_0] + vld1.u32 {d5, d6, d7}, [r0], r2 @ Vector load from src[1_0] + vld1.u32 {d8, d9, d10}, [r0], r2 @ Vector load from src[2_0] + vld1.u32 {d11, d12, d13}, [r0], r2 @ Vector load from src[3_0] + vld1.u32 {d14, d15, d16}, [r0], r2 @ Vector load from src[4_0] + vld1.u32 {d17, d18, d19}, [r0], r2 @ Vector load from src[5_0] + + @ vERTICAL FILTERING FOR ROW 0 + vaddl.u8 q10, d8, d11 @ temp1 = src[2_0] + src[3_0] + vaddl.u8 q12, d2, d17 @ temp2 = src[0_0] + src[5_0] + vaddl.u8 q11, d5, d14 @ temp = src[1_0] + src[4_0] + vaddl.u8 q13, d3, d18 @ temp2 = src[0_0] + src[5_0] + vmla.u16 q12, q10, d0[0] @ temp += temp1 * 20 + vmls.s16 q12, q11, d1[0] @ temp -= temp2 * 5 + vaddl.u8 q10, d6, d15 @ temp = src[1_0] + src[4_0] + vaddl.u8 q11, d9, d12 @ temp3 = src[2_0] + src[3_0] + vaddl.u8 q14, d4, d19 @ temp2 = src[0_0] + src[5_0] + vmla.u16 q13, q11, d0[0] @ temp4 += temp3 * 20 + vmls.s16 q13, q10, d1[0] @ temp -= temp2 * 5 + vaddl.u8 q11, d10, d13 @ temp3 = src[2_0] + src[3_0] + vaddl.u8 q10, d7, d16 @ temp = src[1_0] + src[4_0] + vmla.u16 q14, q11, d0[0] @ temp4 += temp3 * 20 + vmls.s16 q14, q10, d1[0] @ temp -= temp2 * 5 + vext.16 q10, q12, q13, #5 @//extract a[5] (column1) + + @Q12,Q13,Q14 HAVE VERTICAL FILTERED VALUES + @CASCADED FILTERING FOR ROW 0 + vext.16 q11, q12, q13, #2 @//extract a[2] (column1) + vaddl.s16 q1, d20, d24 @// a0 + a5 (column1) + vaddl.s16 q15, d21, d25 @// a0 + a5 (column1) + vmlal.s16 q1, d22, d0[0] @// a0 + a5 + 20a2 (column1) + vmlal.s16 q15, d23, d0[0] @// a0 + a5 + 20a2 (column1) + vext.16 q11, q12, q13, #1 @//extract a[1] (column1) + vext.16 q10, q12, q13, #3 @//extract a[3] (column1) + vmlsl.s16 q1, d22, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1) + vmlsl.s16 q15, d23, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1) + vmlal.s16 q1, d20, d0[0] @// a0 + a5 + 20a2 + 20a3 (column1) + vmlal.s16 q15, d21, d0[0] @// a0 + a5 + 20a2 + 20a3 (column1) + vext.16 q11, q12, q13, #4 @//extract a[4] (column1) + vext.16 q10, q13, q14, #5 @//extract a[5] (column2) + vmlsl.s16 q1, d22, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1) + vmlsl.s16 q15, d23, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1) + vqrshrun.s32 d22, q1, #10 + vqrshrun.s32 d23, q15, #10 + vqmovun.s16 d22, q11 + vst1.u8 {d22}, [r1], r10 @//Store dest row0, column 1; (1/2,1/2) + vext.16 q11, q13, q14, #2 @//extract a[2] (column2) + vaddl.s16 q1, d20, d26 @// a0 + a5 (column2) + vaddl.s16 q15, d21, d27 @// a0 + a5 (column2) + vmlal.s16 q1, d22, d0[0] @// a0 + a5 + 20a2 (column2) + vmlal.s16 q15, d23, d0[0] @// a0 + a5 + 20a2 (column2) + vext.16 q10, q13, q14, #3 @//extract a[3] (column2) + vext.16 q11, q13, q14, #1 @//extract a[1] (column2) + vmlal.s16 q1, d20, d0[0] @// a0 + a5 + 20a2 + 20a3 (column2) + vmlal.s16 q15, d21, d0[0] @// a0 + a5 + 20a2 + 20a3 (column2) + vext.16 q10, q13, q14, #4 @//extract a[4] (column2) + vmlsl.s16 q1, d22, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 (column2) + vmlsl.s16 q15, d23, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 (column2) + vmlsl.s16 q1, d20, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2) + vmlsl.s16 q15, d21, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2) + vqrshrun.s32 d20, q1, #10 + vqrshrun.s32 d21, q15, #10 + vld1.u32 {d2, d3, d4}, [r0], r2 @ Vector load from src[6_0] + vqmovun.s16 d22, q10 + vst1.u8 {d22}, [r1], r7 @//Store dest row0 ,column 2; (1/2,1/2) + + @ vERTICAL FILTERING FOR ROW 1 + vaddl.u8 q10, d11, d14 @ temp1 = src[2_0] + src[3_0] + vaddl.u8 q12, d5, d2 @ temp2 = src[0_0] + src[5_0] + vaddl.u8 q11, d8, d17 @ temp = src[1_0] + src[4_0] + vaddl.u8 q13, d6, d3 @ temp2 = src[0_0] + src[5_0] + vmla.u16 q12, q10, d0[0] @ temp += temp1 * 20 + vaddl.u8 q10, d9, d18 @ temp = src[1_0] + src[4_0] + vmls.s16 q12, q11, d1[0] @ temp -= temp2 * 5 + vaddl.u8 q11, d12, d15 @ temp3 = src[2_0] + src[3_0] + vaddl.u8 q14, d7, d4 @ temp2 = src[0_0] + src[5_0] + vmla.u16 q13, q11, d0[0] @ temp4 += temp3 * 20 + vaddl.u8 q11, d13, d16 @ temp3 = src[2_0] + src[3_0] + vmls.s16 q13, q10, d1[0] @ temp -= temp2 * 5 + vmla.u16 q14, q11, d0[0] @ temp4 += temp3 * 20 + vaddl.u8 q10, d10, d19 @ temp = src[1_0] + src[4_0] + vmls.s16 q14, q10, d1[0] @ temp -= temp2 * 5 + vext.16 q10, q12, q13, #5 @//extract a[5] (column1) + + @Q12,Q13,Q14 HAVE VERTICAL FILTERED VALUES + @CASCADED FILTERING FOR ROW 1 + vext.16 q11, q12, q13, #2 @//extract a[2] (column1) + vaddl.s16 q3, d20, d24 @// a0 + a5 (column1) + vaddl.s16 q15, d21, d25 @// a0 + a5 (column1) + vmlal.s16 q3, d22, d0[0] @// a0 + a5 + 20a2 (column1) + vmlal.s16 q15, d23, d0[0] @// a0 + a5 + 20a2 (column1) + vext.16 q11, q12, q13, #1 @//extract a[1] (column1) + vext.16 q10, q12, q13, #3 @//extract a[3] (column1) + vmlsl.s16 q3, d22, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1) + vmlsl.s16 q15, d23, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1) + vmlal.s16 q3, d20, d0[0] @// a0 + a5 + 20a2 + 20a3 (column1) + vmlal.s16 q15, d21, d0[0] @// a0 + a5 + 20a2 + 20a3 (column1) + vext.16 q11, q12, q13, #4 @//extract a[4] (column1) + vext.16 q10, q13, q14, #5 @//extract a[5] (column2) + vmlsl.s16 q3, d22, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1) + vmlsl.s16 q15, d23, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1) + vqrshrun.s32 d22, q3, #10 + vqrshrun.s32 d23, q15, #10 + vqmovun.s16 d22, q11 + vst1.u8 {d22}, [r1], r10 @//Store dest row1, column 1; (1/2,1/2) + vext.16 q11, q13, q14, #2 @//extract a[2] (column2) + vaddl.s16 q3, d20, d26 @// a0 + a5 (column2) + vaddl.s16 q15, d21, d27 @// a0 + a5 (column2) + vmlal.s16 q3, d22, d0[0] @// a0 + a5 + 20a2 (column2) + vmlal.s16 q15, d23, d0[0] @// a0 + a5 + 20a2 (column2) + vext.16 q10, q13, q14, #3 @//extract a[3] (column2) + vext.16 q11, q13, q14, #1 @//extract a[1] (column2) + vmlal.s16 q3, d20, d0[0] @// a0 + a5 + 20a2 + 20a3 (column2) + vmlal.s16 q15, d21, d0[0] @// a0 + a5 + 20a2 + 20a3 (column2) + vext.16 q10, q13, q14, #4 @//extract a[4] (column2) + vmlsl.s16 q3, d22, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 (column2) + vmlsl.s16 q15, d23, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 (column2) + vmlsl.s16 q3, d20, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2) + vmlsl.s16 q15, d21, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2) + vqrshrun.s32 d20, q3, #10 + vqrshrun.s32 d21, q15, #10 + vqmovun.s16 d22, q10 + vst1.u8 {d22}, [r1], r7 @//Store dest row1 ,column 2; (1/2,1/2) + + subs r8, r8, #2 @ 2 rows processed, decrement by 2 + subne r0, r0 , r2, lsl #2 + subne r0, r0, r2 + beq end_func @ Branch if height==4 + + b loop_16 @ looping if height = 8 or 16 + +loop_8: + vld1.u32 {d2, d3}, [r0], r2 @ Vector load from src[0_0] + vld1.u32 {d4, d5}, [r0], r2 @ Vector load from src[1_0] + vld1.u32 {d6, d7}, [r0], r2 @ Vector load from src[2_0] + vld1.u32 {d8, d9}, [r0], r2 @ Vector load from src[3_0] + vld1.u32 {d10, d11}, [r0], r2 @ Vector load from src[4_0] + vld1.u32 {d12, d13}, [r0], r2 @ Vector load from src[5_0] + + @ vERTICAL FILTERING FOR ROW 0 + vaddl.u8 q10, d6, d8 @ temp1 = src[2_0] + src[3_0] + vaddl.u8 q11, d4, d10 @ temp2 = src[1_0] + src4_0] + vaddl.u8 q12, d2, d12 @ temp = src[0_0] + src[5_0] + vaddl.u8 q13, d3, d13 @ temp = src[0_0] + src[5_0] + vaddl.u8 q14, d7, d9 @ temp1 = src[2_0] + src[3_0] + vmla.u16 q12, q10, d0[0] @ temp += temp1 * 20 + vmls.s16 q12, q11, d1[0] @ temp -= temp2 * 5 + vaddl.u8 q15, d5, d11 @ temp2 = src[1_0] + src4_0] + vmla.u16 q13, q14, d0[0] @ temp += temp1 * 20 + vmls.s16 q13, q15, d1[0] @ temp -= temp2 * 5 + @Q12,Q13 HAVE VERTICAL FILTERED VALUES + @CASCADED FILTERING FOR ROW 0 + + vext.16 q10, q12, q13, #5 @//extract a[5] (column1) + vext.16 q11, q12, q13, #2 @//extract a[2] (column1) + vaddl.s16 q14, d20, d24 @// a0 + a5 (column1) + vaddl.s16 q15, d21, d25 @// a0 + a5 (column1) + vext.16 q9, q12, q13, #1 @//extract a[1] (column1) + vext.16 q10, q12, q13, #3 @//extract a[3] (column1) + vext.16 q1, q12, q13, #4 @//extract a[4] (column1) + vmlal.s16 q14, d22, d0[0] @// a0 + a5 + 20a2 (column1) + vmlsl.s16 q14, d18, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1) + vmlal.s16 q14, d20, d0[0] @// a0 + a5 + 20a2 + 20a3 (column1) + vmlsl.s16 q14, d2, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1) + vld1.u32 {d14, d15}, [r0], r2 @ Vector load from src[6_0] + vmlal.s16 q15, d23, d0[0] @// a0 + a5 + 20a2 (column1) + vmlal.s16 q15, d21, d0[0] @// a0 + a5 + 20a2 + 20a3 (column1) + vmlsl.s16 q15, d19, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1) + vmlsl.s16 q15, d3, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1) + + vaddl.u8 q12, d4, d14 @ temp = src[0_0] + src[5_0] + vaddl.u8 q13, d5, d15 @ temp = src[0_0] + src[5_0] + vqrshrun.s32 d18, q14, #10 + vaddl.u8 q14, d9, d11 @ temp1 = src[2_0] + src[3_0] + vaddl.u8 q10, d8, d10 @ temp1 = src[2_0] + src[3_0] + vaddl.u8 q11, d6, d12 @ temp2 = src[1_0] + src4_0] + vqrshrun.s32 d19, q15, #10 + vmla.u16 q12, q10, d0[0] @ temp += temp1 * 20 + vmls.s16 q12, q11, d1[0] @ temp -= temp2 * 5 + vaddl.u8 q15, d7, d13 @ temp2 = src[1_0] + src4_0] + vmla.u16 q13, q14, d0[0] @ temp += temp1 * 20 + vmls.s16 q13, q15, d1[0] @ temp -= temp2 * 5 + vqmovun.s16 d2, q9 + @ vERTICAL FILTERING FOR ROW 1 + + @Q12,Q13 HAVE VERTICAL FILTERED VALUES + @CASCADED FILTERING FOR ROW 1 + vext.16 q10, q12, q13, #5 @//extract a[5] (column1) + vext.16 q11, q12, q13, #2 @//extract a[2] (column1) + vaddl.s16 q14, d20, d24 @// a0 + a5 (column1) + vaddl.s16 q15, d21, d25 @// a0 + a5 (column1) + vst1.u8 {d2}, [r1], r3 @//Store dest row0, column 1; (1/2,1/2) + vext.16 q9, q12, q13, #1 @//extract a[1] (column1) + vext.16 q10, q12, q13, #3 @//extract a[3] (column1) + vext.16 q2, q12, q13, #4 @//extract a[4] (column1) + vmlal.s16 q14, d22, d0[0] @// a0 + a5 + 20a2 (column1) + vmlsl.s16 q14, d18, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1) + vmlal.s16 q14, d20, d0[0] @// a0 + a5 + 20a2 + 20a3 (column1) + vmlsl.s16 q14, d4, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1) + vmlal.s16 q15, d23, d0[0] @// a0 + a5 + 20a2 (column1) + vmlal.s16 q15, d21, d0[0] @// a0 + a5 + 20a2 + 20a3 (column1) + vmlsl.s16 q15, d19, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1) + vmlsl.s16 q15, d5, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1) + vqrshrun.s32 d18, q14, #10 + vqrshrun.s32 d19, q15, #10 + vqmovun.s16 d3, q9 + vst1.u8 {d3}, [r1], r3 @//Store dest row1, column 1; (1/2,1/2) + + subs r8, r8, #2 @ 2 rows processed, decrement by 2 + subne r0, r0 , r2, lsl #2 + subne r0, r0, r2 + beq end_func @ Branch if height==4 + + b loop_8 @looping if height == 8 or 16 + +loop_4: + vld1.u32 {d2, d3}, [r0], r2 @ Vector load from src[0_0] + vld1.u32 {d4, d5}, [r0], r2 @ Vector load from src[1_0] + vld1.u32 {d6, d7}, [r0], r2 @ Vector load from src[2_0] + vld1.u32 {d8, d9}, [r0], r2 @ Vector load from src[3_0] + vld1.u32 {d10, d11}, [r0], r2 @ Vector load from src[4_0] + vld1.u32 {d12, d13}, [r0], r2 @ Vector load from src[5_0] + + @ vERTICAL FILTERING FOR ROW 0 + vaddl.u8 q10, d6, d8 @ temp1 = src[2_0] + src[3_0] + vaddl.u8 q11, d4, d10 @ temp2 = src[1_0] + src4_0] + vaddl.u8 q12, d2, d12 @ temp = src[0_0] + src[5_0] + vaddl.u8 q13, d3, d13 @ temp = src[0_0] + src[5_0] + vaddl.u8 q14, d7, d9 @ temp1 = src[2_0] + src[3_0] + vmla.u16 q12, q10, d0[0] @ temp += temp1 * 20 + vmls.s16 q12, q11, d1[0] @ temp -= temp2 * 5 + vaddl.u8 q15, d5, d11 @ temp2 = src[1_0] + src4_0] + vmla.u16 q13, q14, d0[0] @ temp += temp1 * 20 + vmls.s16 q13, q15, d1[0] @ temp -= temp2 * 5 + @Q12,Q13 HAVE VERTICAL FILTERED VALUES + @CASCADED FILTERING FOR ROW 0 + + vext.16 q10, q12, q13, #5 @//extract a[5] (column1) + vext.16 q11, q12, q13, #2 @//extract a[2] (column1) + vaddl.s16 q14, d20, d24 @// a0 + a5 (column1) + vaddl.s16 q15, d21, d25 @// a0 + a5 (column1) + + vext.16 q1, q12, q13, #4 @//extract a[4] (column1) + vext.16 q9, q12, q13, #1 @//extract a[1] (column1) + vext.16 q10, q12, q13, #3 @//extract a[3] (column1) + + vmlal.s16 q14, d22, d0[0] @// a0 + a5 + 20a2 (column1) + vmlsl.s16 q14, d18, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1) + vmlal.s16 q14, d20, d0[0] @// a0 + a5 + 20a2 + 20a3 (column1) + vmlsl.s16 q14, d2, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1) + vld1.u32 {d14, d15}, [r0], r2 @ Vector load from src[6_0] + vmlal.s16 q15, d23, d0[0] @// a0 + a5 + 20a2 (column1) + vmlal.s16 q15, d21, d0[0] @// a0 + a5 + 20a2 + 20a3 (column1) + vmlsl.s16 q15, d19, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1) + vmlsl.s16 q15, d3, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1) + vaddl.u8 q12, d4, d14 @ temp = src[0_0] + src[5_0] + vaddl.u8 q13, d5, d15 @ temp = src[0_0] + src[5_0] + vqrshrun.s32 d18, q14, #10 + vaddl.u8 q14, d9, d11 @ temp1 = src[2_0] + src[3_0] + vaddl.u8 q11, d6, d12 @ temp2 = src[1_0] + src4_0] + vaddl.u8 q10, d8, d10 @ temp1 = src[2_0] + src[3_0] + vqrshrun.s32 d19, q15, #10 + vmla.u16 q12, q10, d0[0] @ temp += temp1 * 20 + vmls.s16 q12, q11, d1[0] @ temp -= temp2 * 5 + vaddl.u8 q15, d7, d13 @ temp2 = src[1_0] + src4_0] + vqmovun.s16 d2, q9 + vmla.u16 q13, q14, d0[0] @ temp += temp1 * 20 + vmls.s16 q13, q15, d1[0] @ temp -= temp2 * 5 + + @ vERTICAL FILTERING FOR ROW 1 + + @Q12,Q13 HAVE VERTICAL FILTERED VALUES + @CASCADED FILTERING FOR ROW 1 + vext.16 q10, q12, q13, #5 @//extract a[5] (column1) + vext.16 q11, q12, q13, #2 @//extract a[2] (column1) + vst1.u32 {d2[0]}, [r1], r3 @//Store dest row0, column 1; (1/2,1/2) + vaddl.s16 q14, d20, d24 @// a0 + a5 (column1) + vaddl.s16 q15, d21, d25 @// a0 + a5 (column1) + vext.16 q9, q12, q13, #1 @//extract a[1] (column1) + vext.16 q10, q12, q13, #3 @//extract a[3] (column1) + vext.16 q2, q12, q13, #4 @//extract a[4] (column1) + vmlal.s16 q14, d22, d0[0] @// a0 + a5 + 20a2 (column1) + vmlsl.s16 q14, d18, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1) + vmlal.s16 q14, d20, d0[0] @// a0 + a5 + 20a2 + 20a3 (column1) + vmlsl.s16 q14, d4, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1) + vmlal.s16 q15, d23, d0[0] @// a0 + a5 + 20a2 (column1) + vmlal.s16 q15, d21, d0[0] @// a0 + a5 + 20a2 + 20a3 (column1) + vmlsl.s16 q15, d19, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1) + vmlsl.s16 q15, d5, d1[0] @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1) + vqrshrun.s32 d18, q14, #10 + vqrshrun.s32 d19, q15, #10 + vqmovun.s16 d4, q9 + vst1.u32 {d4[0]}, [r1], r3 @//Store dest row1, column 1; (1/2,1/2) + + subs r8, r8, #2 @ 2 rows processed, decrement by 2 + subne r0, r0 , r2, lsl #2 + subne r0, r0, r2 + beq end_func @ Branch if height==4 + + b loop_4 @looping if height == 8 or 16 + +end_func: + vldmia sp!, {d8-d15} @ Restore neon registers that were saved + ldmfd sp!, {r4-r12, pc} @Restoring registers from stack + + diff --git a/dependencies/ih264d/common/arm/ih264_inter_pred_luma_horz_hpel_vert_qpel_a9q.s b/dependencies/ih264d/common/arm/ih264_inter_pred_luma_horz_hpel_vert_qpel_a9q.s new file mode 100644 index 00000000..c8edf384 --- /dev/null +++ b/dependencies/ih264d/common/arm/ih264_inter_pred_luma_horz_hpel_vert_qpel_a9q.s @@ -0,0 +1,1044 @@ +@/****************************************************************************** +@ * +@ * Copyright (C) 2015 The Android Open Source Project +@ * +@ * Licensed under the Apache License, Version 2.0 (the "License"); +@ * you may not use this file except in compliance with the License. +@ * You may obtain a copy of the License at: +@ * +@ * http://www.apache.org/licenses/LICENSE-2.0 +@ * +@ * Unless required by applicable law or agreed to in writing, software +@ * distributed under the License is distributed on an "AS IS" BASIS, +@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@ * See the License for the specific language governing permissions and +@ * limitations under the License. +@ * +@ ***************************************************************************** +@ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +@*/ +@** +@****************************************************************************** +@* @file +@* ih264_inter_pred_luma_horz_hpel_vert_qpel_a9q.s +@* +@* @brief +@* Contains function definitions for inter prediction interpolation. +@* +@* @author +@* Mohit +@* +@* @par List of Functions: +@* +@* - ih264_inter_pred_luma_horz_hpel_vert_qpel_a9q() +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* + +@* All the functions here are replicated from ih264_inter_pred_filters.c +@ + +@** +@** +@** +@******************************************************************************* +@* +@* @brief +@* This function implements a two stage cascaded six tap filter. It +@* applies the six tap filter in the horizontal direction on the +@* predictor values, followed by applying the same filter in the +@* vertical direction on the output of the first stage. It then averages +@* the output of the 1st stage and the output of the 2nd stage to obtain +@* the quarter pel values. The six tap filtering operation is described +@* in sec 8.4.2.2.1 titled "Luma sample interpolation process". +@* +@* @par Description: +@* This function is called to obtain pixels lying at the following +@* location (1/2,1/4) or (1/2,3/4). The function interpolates +@* the predictors first in the horizontal direction and then in the +@* vertical direction to output the (1/2,1/2). It then averages +@* the output of the 2nd stage and (1/2,1/2) value to obtain (1/2,1/4) +@* or (1/2,3/4) depending on the offset. +@* +@* @param[in] pu1_src +@* UWORD8 pointer to the source +@* +@* @param[out] pu1_dst +@* UWORD8 pointer to the destination +@* +@* @param[in] src_strd +@* integer source stride +@* +@* @param[in] dst_strd +@* integer destination stride +@* +@* @param[in] ht +@* integer height of the array +@* +@* @param[in] wd +@* integer width of the array +@* +@* @param[in] pu1_tmp: temporary buffer +@* +@* @param[in] dydx: x and y reference offset for qpel calculations +@* +@* @returns +@* +@* @remarks +@* None +@* +@******************************************************************************* +@*; + +@void ih264_inter_pred_luma_horz_hpel_vert_qpel(UWORD8 *pu1_src, +@ UWORD8 *pu1_dst, +@ WORD32 src_strd,, +@ WORD32 dst_strd, +@ WORD32 ht, +@ WORD32 wd, +@ UWORD8* pu1_tmp, +@ UWORD32 dydx) + +@**************Variables Vs Registers***************************************** +@ r0 => *pu1_src +@ r1 => *pu1_dst +@ r2 => src_strd +@ r3 => dst_strd +@ r4 => ht +@ r5 => wd +@ r7 => dydx +@ r9 => *pu1_tmp + +.text +.p2align 2 + + .global ih264_inter_pred_luma_horz_hpel_vert_qpel_a9q + +ih264_inter_pred_luma_horz_hpel_vert_qpel_a9q: + + stmfd sp!, {r4-r12, r14} @ store register values to stack + vstmdb sp!, {d8-d15} @push neon registers to stack + ldr r4, [sp, #104] @ loads ht + sub r0, r0, r2, lsl #1 @ pu1_src-2*src_strd + sub r0, r0, #2 @ pu1_src-2 + ldr r5, [sp, #108] @ loads wd + ldr r7, [sp, #116] @ loads dydx + lsr r7, r7, #3 @ dydx >> 2 followed by dydx & 0x3 and dydx>>1 to obtain the deciding bit + ldr r9, [sp, #112] @ pu1_tmp + add r7, r7, #2 + mov r6, #48 + mla r7, r7, r6, r9 + + subs r12, r5, #4 @if wd=4 branch to loop_4 + beq loop_4_start + + subs r12, r5, #8 @if wd=8 branch to loop_8 + beq loop_8_start + + @when wd=16 + vmov.u16 q11, #20 @ Filter coeff 0x14 into Q11 + vmov.u16 q12, #5 @ Filter coeff 0x5 into Q12 + add r8, r0, #8 + add r14, r1, #8 + add r10, r9, #8 + mov r12, r4 + add r11, r7, #8 + +loop_16_lowhalf_start: + vld1.32 {q0}, [r0], r2 @ row -2 load for horizontal filter + vext.8 d5, d0, d1, #5 + vaddl.u8 q3, d0, d5 + + vext.8 d2, d0, d1, #2 + vext.8 d3, d0, d1, #3 + vaddl.u8 q4, d2, d3 + vext.8 d4, d0, d1, #4 + vmla.u16 q3, q4, q11 + vext.8 d1, d0, d1, #1 + vaddl.u8 q4, d1, d4 + vld1.32 {q0}, [r0], r2 @ row -1 load for horizontal filter + vmls.u16 q3, q4, q12 + vext.8 d5, d0, d1, #5 + vaddl.u8 q4, d0, d5 + vext.8 d2, d0, d1, #2 + vext.8 d3, d0, d1, #3 + vaddl.u8 q5, d2, d3 + + vst1.32 {q3}, [r9], r6 @ store temp buffer 0 + + vext.8 d4, d0, d1, #4 + vmla.u16 q4, q5, q11 + vext.8 d1, d0, d1, #1 + vaddl.u8 q5, d1, d4 + vld1.32 {q0}, [r0], r2 @ row 0 load for horizontal filter + vmls.u16 q4, q5, q12 + vext.8 d5, d0, d1, #5 + vaddl.u8 q5, d0, d5 + vext.8 d2, d0, d1, #2 + vext.8 d3, d0, d1, #3 + vaddl.u8 q6, d2, d3 + + vst1.32 {q4}, [r9], r6 @ store temp buffer 1 + + vext.8 d4, d0, d1, #4 + vmla.u16 q5, q6, q11 + vext.8 d1, d0, d1, #1 + vaddl.u8 q6, d1, d4 + vld1.32 {q0}, [r0], r2 @ row 1 load for horizontal filter + vmls.u16 q5, q6, q12 + vext.8 d5, d0, d1, #5 + vaddl.u8 q6, d0, d5 + vext.8 d2, d0, d1, #2 + vext.8 d3, d0, d1, #3 + vaddl.u8 q7, d2, d3 + + vst1.32 {q5}, [r9], r6 @ store temp buffer 2 + + vext.8 d4, d0, d1, #4 + vmla.u16 q6, q7, q11 + vext.8 d1, d0, d1, #1 + vaddl.u8 q7, d1, d4 + vld1.32 {q0}, [r0], r2 @ row 2 load for horizontal filter + vmls.u16 q6, q7, q12 + vext.8 d5, d0, d1, #5 + vaddl.u8 q7, d0, d5 + vext.8 d2, d0, d1, #2 + vext.8 d3, d0, d1, #3 + vaddl.u8 q8, d2, d3 + + vst1.32 {q6}, [r9], r6 @ store temp buffer 3 + + vext.8 d4, d0, d1, #4 + vmla.u16 q7, q8, q11 + vext.8 d1, d0, d1, #1 + vaddl.u8 q8, d1, d4 + + vmls.u16 q7, q8, q12 +loop_16_lowhalf: + + vld1.32 {q0}, [r0], r2 @ row 3 load for horizontal filter + vext.8 d5, d0, d1, #5 + vext.8 d2, d0, d1, #2 + vext.8 d3, d0, d1, #3 + vaddl.u8 q8, d0, d5 + + vst1.32 {q7}, [r9], r6 @ store temp buffer 4 + vaddl.u8 q9, d2, d3 + vext.8 d4, d0, d1, #4 + vmla.u16 q8, q9, q11 + vext.8 d1, d0, d1, #1 + vadd.s16 q14, q4, q7 + vaddl.u8 q9, d1, d4 + vadd.s16 q15, q5, q6 + vmls.u16 q8, q9, q12 + vld1.32 {q0}, [r0], r2 @ row 4 load for hoorizontal filter + vext.8 d5, d0, d1, #5 + vext.8 d2, d0, d1, #2 + vext.8 d3, d0, d1, #3 + vaddl.u8 q10, d0, d5 + + vst1.32 {q8}, [r9], r6 @ store temp buffer r5 + + vaddl.s16 q9, d6, d16 + + vld1.32 {q13}, [r7], r6 @ load from temp buffer 0 + + vaddl.s16 q3, d7, d17 + + vqrshrun.s16 d26, q13, #5 + + vmlal.s16 q9, d30, d22 + vmlsl.s16 q9, d28, d24 + vmlal.s16 q3, d31, d22 + vmlsl.s16 q3, d29, d24 + vaddl.u8 q1, d2, d3 + vext.8 d4, d0, d1, #4 + vmla.u16 q10, q1, q11 + vqrshrun.s32 d18, q9, #10 + vext.8 d1, d0, d1, #1 + vqrshrun.s32 d19, q3, #10 + vadd.s16 q14, q5, q8 + vaddl.u8 q1, d1, d4 + vadd.s16 q15, q6, q7 + vmls.u16 q10, q1, q12 + vqmovn.u16 d18, q9 + vld1.32 {q0}, [r0], r2 @ row 5 load for horizontal filter + + vrhadd.u8 d26, d18, d26 + + vext.8 d5, d0, d1, #5 + vext.8 d2, d0, d1, #2 + + vst1.32 {q10}, [r9], r6 @ store temp buffer r6 + + vaddl.s16 q9, d8, d20 + + vaddl.s16 q3, d9, d21 + + vld1.32 {q4}, [r7], r6 @load from temp buffer 1 + + + vst1.32 d26, [r1], r3 @ store row 0 + + vmlal.s16 q9, d30, d22 + vmlsl.s16 q9, d28, d24 + + vqrshrun.s16 d28, q4, #5 + + vmlal.s16 q3, d31, d22 + vmlsl.s16 q3, d29, d24 + vext.8 d3, d0, d1, #3 + vaddl.u8 q4, d0, d5 + vaddl.u8 q1, d2, d3 + vqrshrun.s32 d18, q9, #10 + vext.8 d4, d0, d1, #4 + vqrshrun.s32 d19, q3, #10 + vmla.u16 q4, q1, q11 + vext.8 d1, d0, d1, #1 + vadd.s16 q13, q6, q10 + vaddl.u8 q1, d1, d4 + vqmovn.u16 d18, q9 + vadd.s16 q15, q7, q8 + vmls.u16 q4, q1, q12 + vld1.32 {q0}, [r0], r2 @ row 6 load for horizontal filter + + vrhadd.u8 d28, d28, d18 + + vext.8 d5, d0, d1, #5 + vext.8 d2, d0, d1, #2 + vext.8 d3, d0, d1, #3 + + vst1.32 d28, [r1], r3 @ store row 1 + + vaddl.u8 q14, d0, d5 + + vst1.32 {q4}, [r9], r6 @ store temp buffer r7 + + vaddl.s16 q9, d10, d8 + vaddl.s16 q3, d11, d9 + + vld1.32 {q5}, [r7], r6 @ load from temp buffer 2 + + vmlal.s16 q9, d30, d22 + vmlsl.s16 q9, d26, d24 + vmlal.s16 q3, d31, d22 + + vqrshrun.s16 d26, q5, #5 + + vmlsl.s16 q3, d27, d24 + vaddl.u8 q1, d2, d3 + vext.8 d4, d0, d1, #4 + vmla.u16 q14, q1, q11 + vqrshrun.s32 d18, q9, #10 + vext.8 d1, d0, d1, #1 + vqrshrun.s32 d19, q3, #10 + vadd.s16 q5, q7, q4 + vaddl.u8 q1, d1, d4 + vadd.s16 q15, q8, q10 + vmls.u16 q14, q1, q12 + vqmovn.u16 d27, q9 + + vaddl.s16 q9, d12, d28 + vaddl.s16 q3, d13, d29 + + vrhadd.u8 d26, d26, d27 + + vmlal.s16 q9, d30, d22 + vmlsl.s16 q9, d10, d24 + vmlal.s16 q3, d31, d22 + vmlsl.s16 q3, d11, d24 + + vst1.32 d26, [r1], r3 @ store row 2 + + vst1.32 {q14}, [r9] + + + vqrshrun.s32 d18, q9, #10 + vmov q5, q10 + vld1.32 {q15}, [r7], r6 @ load from temp buffer 3 + + vqrshrun.s32 d19, q3, #10 + subs r4, r4, #4 + + vqrshrun.s16 d30, q15, #5 + + vqmovn.u16 d18, q9 + vmov q6, q4 + vmov q3, q7 + vrhadd.u8 d30, d18, d30 + vmov q4, q8 + vmov q7, q14 + vst1.32 d30, [r1], r3 @ store row 3 + + bgt loop_16_lowhalf @ looping if height =16 + + +loop_16_highhalf_start: + vld1.32 {q0}, [r8], r2 + vext.8 d5, d0, d1, #5 + vaddl.u8 q3, d0, d5 + vext.8 d2, d0, d1, #2 + vext.8 d3, d0, d1, #3 + vaddl.u8 q4, d2, d3 + vext.8 d4, d0, d1, #4 + vmla.u16 q3, q4, q11 + vext.8 d1, d0, d1, #1 + vaddl.u8 q4, d1, d4 + vld1.32 {q0}, [r8], r2 + vmls.u16 q3, q4, q12 + vext.8 d5, d0, d1, #5 + vaddl.u8 q4, d0, d5 + vext.8 d2, d0, d1, #2 + vext.8 d3, d0, d1, #3 + vaddl.u8 q5, d2, d3 + + vst1.32 {q3}, [r10], r6 + + vext.8 d4, d0, d1, #4 + vmla.u16 q4, q5, q11 + vext.8 d1, d0, d1, #1 + vaddl.u8 q5, d1, d4 + vld1.32 {q0}, [r8], r2 + vmls.u16 q4, q5, q12 + vext.8 d5, d0, d1, #5 + vaddl.u8 q5, d0, d5 + vext.8 d2, d0, d1, #2 + vext.8 d3, d0, d1, #3 + vaddl.u8 q6, d2, d3 + + vst1.32 {q4}, [r10], r6 + + vext.8 d4, d0, d1, #4 + vmla.u16 q5, q6, q11 + vext.8 d1, d0, d1, #1 + vaddl.u8 q6, d1, d4 + vld1.32 {q0}, [r8], r2 + vmls.u16 q5, q6, q12 + vext.8 d5, d0, d1, #5 + vaddl.u8 q6, d0, d5 + vext.8 d2, d0, d1, #2 + vext.8 d3, d0, d1, #3 + vaddl.u8 q7, d2, d3 + + vst1.32 {q5}, [r10], r6 + + vext.8 d4, d0, d1, #4 + vmla.u16 q6, q7, q11 + vext.8 d1, d0, d1, #1 + vaddl.u8 q7, d1, d4 + vld1.32 {q0}, [r8], r2 + vmls.u16 q6, q7, q12 + vext.8 d5, d0, d1, #5 + vaddl.u8 q7, d0, d5 + vext.8 d2, d0, d1, #2 + vext.8 d3, d0, d1, #3 + vaddl.u8 q8, d2, d3 + + vst1.32 {q6}, [r10], r6 + + vext.8 d4, d0, d1, #4 + vmla.u16 q7, q8, q11 + vext.8 d1, d0, d1, #1 + vaddl.u8 q8, d1, d4 + + vmls.u16 q7, q8, q12 + +loop_16_highhalf: + + vld1.32 {q0}, [r8], r2 + vext.8 d5, d0, d1, #5 + vext.8 d2, d0, d1, #2 + vext.8 d3, d0, d1, #3 + vaddl.u8 q8, d0, d5 + + vst1.32 {q7}, [r10], r6 + + vaddl.u8 q9, d2, d3 + vext.8 d4, d0, d1, #4 + vmla.u16 q8, q9, q11 + vext.8 d1, d0, d1, #1 + vadd.s16 q14, q4, q7 + vaddl.u8 q9, d1, d4 + vadd.s16 q15, q5, q6 + vmls.u16 q8, q9, q12 + vld1.32 {q0}, [r8], r2 + vext.8 d5, d0, d1, #5 + vext.8 d2, d0, d1, #2 + vext.8 d3, d0, d1, #3 + vaddl.u8 q10, d0, d5 + + vst1.32 {q8}, [r10], r6 + + vaddl.s16 q9, d6, d16 + + vld1.32 {q13}, [r11], r6 + + vaddl.s16 q3, d7, d17 + + vqrshrun.s16 d26, q13, #5 + + vmlal.s16 q9, d30, d22 + vmlsl.s16 q9, d28, d24 + vmlal.s16 q3, d31, d22 + vmlsl.s16 q3, d29, d24 + vaddl.u8 q1, d2, d3 + vext.8 d4, d0, d1, #4 + vmla.u16 q10, q1, q11 + vqrshrun.s32 d18, q9, #10 + vext.8 d1, d0, d1, #1 + vqrshrun.s32 d19, q3, #10 + vadd.s16 q14, q5, q8 + vaddl.u8 q1, d1, d4 + vadd.s16 q15, q6, q7 + vmls.u16 q10, q1, q12 + vqmovn.u16 d18, q9 + vld1.32 {q0}, [r8], r2 + + vrhadd.u8 d26, d18, d26 + + vext.8 d5, d0, d1, #5 + vext.8 d2, d0, d1, #2 + + vst1.32 {q10}, [r10], r6 + + vaddl.s16 q9, d8, d20 + vaddl.s16 q3, d9, d21 + + vld1.32 {q4}, [r11], r6 + + + vst1.32 d26, [r14], r3 @store row 0 + + vmlal.s16 q9, d30, d22 + vmlsl.s16 q9, d28, d24 + + vqrshrun.s16 d28, q4, #5 + + vmlal.s16 q3, d31, d22 + vmlsl.s16 q3, d29, d24 + vext.8 d3, d0, d1, #3 + vaddl.u8 q4, d0, d5 + vaddl.u8 q1, d2, d3 + vqrshrun.s32 d18, q9, #10 + vext.8 d4, d0, d1, #4 + vqrshrun.s32 d19, q3, #10 + vmla.u16 q4, q1, q11 + vext.8 d1, d0, d1, #1 + vadd.s16 q13, q6, q10 + vaddl.u8 q1, d1, d4 + vqmovn.u16 d18, q9 + vadd.s16 q15, q7, q8 + vmls.u16 q4, q1, q12 + vld1.32 {q0}, [r8], r2 + + vrhadd.u8 d28, d28, d18 + + vext.8 d5, d0, d1, #5 + vext.8 d2, d0, d1, #2 + vext.8 d3, d0, d1, #3 + + vst1.32 d28, [r14], r3 @store row 1 + + vaddl.u8 q14, d0, d5 + + vst1.32 {q4}, [r10], r6 + + vaddl.s16 q9, d10, d8 + vaddl.s16 q3, d11, d9 + + vld1.32 {q5}, [r11], r6 + + vmlal.s16 q9, d30, d22 + vmlsl.s16 q9, d26, d24 + vmlal.s16 q3, d31, d22 + + vqrshrun.s16 d26, q5, #5 + + vmlsl.s16 q3, d27, d24 + vaddl.u8 q1, d2, d3 + vext.8 d4, d0, d1, #4 + vmla.u16 q14, q1, q11 + vqrshrun.s32 d18, q9, #10 + vext.8 d1, d0, d1, #1 + vqrshrun.s32 d19, q3, #10 + vadd.s16 q5, q7, q4 + vaddl.u8 q1, d1, d4 + vadd.s16 q15, q8, q10 + vmls.u16 q14, q1, q12 + vqmovn.u16 d27, q9 + + + vaddl.s16 q9, d12, d28 + vaddl.s16 q3, d13, d29 + + vrhadd.u8 d26, d26, d27 + + vmlal.s16 q9, d30, d22 + vmlsl.s16 q9, d10, d24 + vmlal.s16 q3, d31, d22 + vmlsl.s16 q3, d11, d24 + + vst1.32 d26, [r14], r3 @ store row 2 + + vst1.32 {q14}, [r10] + + vqrshrun.s32 d18, q9, #10 + vmov q5, q10 + vld1.32 {q15}, [r11], r6 + + vqrshrun.s32 d19, q3, #10 + subs r12, r12, #4 + + vqrshrun.s16 d30, q15, #5 + + vqmovn.u16 d18, q9 + vmov q6, q4 + vmov q3, q7 + vrhadd.u8 d30, d18, d30 + vmov q4, q8 + vmov q7, q14 + vst1.32 d30, [r14], r3 @ store row 3 + + bgt loop_16_highhalf @ looping if height = 8 or 16 + b end_func + +loop_8_start: + + vmov.u16 q11, #20 @ Filter coeff 20 into Q11 + vmov.u16 q12, #5 @ Filter coeff 5 into Q12 + vld1.32 {q0}, [r0], r2 @ row -2 load for horizontal filter + vext.8 d5, d0, d1, #5 + vaddl.u8 q3, d0, d5 + + vext.8 d2, d0, d1, #2 + vext.8 d3, d0, d1, #3 + vaddl.u8 q4, d2, d3 + vext.8 d4, d0, d1, #4 + vmla.u16 q3, q4, q11 + vext.8 d1, d0, d1, #1 + vaddl.u8 q4, d1, d4 + vld1.32 {q0}, [r0], r2 @ row -1 load for horizontal filter + vmls.u16 q3, q4, q12 + vext.8 d5, d0, d1, #5 + vaddl.u8 q4, d0, d5 + vext.8 d2, d0, d1, #2 + vext.8 d3, d0, d1, #3 + vaddl.u8 q5, d2, d3 + + vst1.32 {q3}, [r9], r6 @ store temp buffer 0 + + vext.8 d4, d0, d1, #4 + vmla.u16 q4, q5, q11 + vext.8 d1, d0, d1, #1 + vaddl.u8 q5, d1, d4 + vld1.32 {q0}, [r0], r2 @ row 0 load for horizontal filter + vmls.u16 q4, q5, q12 + vext.8 d5, d0, d1, #5 + vaddl.u8 q5, d0, d5 + vext.8 d2, d0, d1, #2 + vext.8 d3, d0, d1, #3 + vaddl.u8 q6, d2, d3 + + vst1.32 {q4}, [r9], r6 @ store temp buffer 1 + + vext.8 d4, d0, d1, #4 + vmla.u16 q5, q6, q11 + vext.8 d1, d0, d1, #1 + vaddl.u8 q6, d1, d4 + vld1.32 {q0}, [r0], r2 @ row 1 load for horizontal filter + vmls.u16 q5, q6, q12 + vext.8 d5, d0, d1, #5 + vaddl.u8 q6, d0, d5 + vext.8 d2, d0, d1, #2 + vext.8 d3, d0, d1, #3 + vaddl.u8 q7, d2, d3 + + vst1.32 {q5}, [r9], r6 @ store temp buffer 2 + + vext.8 d4, d0, d1, #4 + vmla.u16 q6, q7, q11 + vext.8 d1, d0, d1, #1 + vaddl.u8 q7, d1, d4 + vld1.32 {q0}, [r0], r2 @ row 2 load for horizontal filter + vmls.u16 q6, q7, q12 + vext.8 d5, d0, d1, #5 + vaddl.u8 q7, d0, d5 + vext.8 d2, d0, d1, #2 + vext.8 d3, d0, d1, #3 + vaddl.u8 q8, d2, d3 + + vst1.32 {q6}, [r9], r6 @ store temp buffer 3 + + vext.8 d4, d0, d1, #4 + vmla.u16 q7, q8, q11 + vext.8 d1, d0, d1, #1 + vaddl.u8 q8, d1, d4 + + vmls.u16 q7, q8, q12 +loop_8: + + vld1.32 {q0}, [r0], r2 @ row 3 load for horizontal filter + vext.8 d5, d0, d1, #5 + vext.8 d2, d0, d1, #2 + vext.8 d3, d0, d1, #3 + vaddl.u8 q8, d0, d5 + + vst1.32 {q7}, [r9], r6 @ store temp buffer 4 + + vaddl.u8 q9, d2, d3 + vext.8 d4, d0, d1, #4 + vmla.u16 q8, q9, q11 + vext.8 d1, d0, d1, #1 + vadd.s16 q14, q4, q7 + vaddl.u8 q9, d1, d4 + vadd.s16 q15, q5, q6 + vmls.u16 q8, q9, q12 + vld1.32 {q0}, [r0], r2 @ row 4 load for hoorizontal filter + vext.8 d5, d0, d1, #5 + vext.8 d2, d0, d1, #2 + vext.8 d3, d0, d1, #3 + vaddl.u8 q10, d0, d5 + + vst1.32 {q8}, [r9], r6 @ store temp buffer r5 + + vaddl.s16 q9, d6, d16 + + vld1.32 {q13}, [r7], r6 @ load from temp buffer 0 + + vaddl.s16 q3, d7, d17 + + vqrshrun.s16 d26, q13, #5 + + vmlal.s16 q9, d30, d22 + vmlsl.s16 q9, d28, d24 + vmlal.s16 q3, d31, d22 + vmlsl.s16 q3, d29, d24 + vaddl.u8 q1, d2, d3 + vext.8 d4, d0, d1, #4 + vmla.u16 q10, q1, q11 + vqrshrun.s32 d18, q9, #10 + vext.8 d1, d0, d1, #1 + vqrshrun.s32 d19, q3, #10 + vadd.s16 q14, q5, q8 + vaddl.u8 q1, d1, d4 + vadd.s16 q15, q6, q7 + vmls.u16 q10, q1, q12 + vqmovn.u16 d18, q9 + vld1.32 {q0}, [r0], r2 @ row 5 load for horizontal filter + + vrhadd.u8 d26, d18, d26 + + vext.8 d5, d0, d1, #5 + vext.8 d2, d0, d1, #2 + + vst1.32 {q10}, [r9], r6 @ store temp buffer r6 + + vaddl.s16 q9, d8, d20 + + vaddl.s16 q3, d9, d21 + + vld1.32 {q4}, [r7], r6 @load from temp buffer 1 + + + vst1.32 d26, [r1], r3 @ store row 0 + + vmlal.s16 q9, d30, d22 + vmlsl.s16 q9, d28, d24 + + vqrshrun.s16 d28, q4, #5 + + vmlal.s16 q3, d31, d22 + vmlsl.s16 q3, d29, d24 + vext.8 d3, d0, d1, #3 + vaddl.u8 q4, d0, d5 + vaddl.u8 q1, d2, d3 + vqrshrun.s32 d18, q9, #10 + vext.8 d4, d0, d1, #4 + vqrshrun.s32 d19, q3, #10 + vmla.u16 q4, q1, q11 + vext.8 d1, d0, d1, #1 + vadd.s16 q13, q6, q10 + vaddl.u8 q1, d1, d4 + vqmovn.u16 d18, q9 + vadd.s16 q15, q7, q8 + vmls.u16 q4, q1, q12 + vld1.32 {q0}, [r0], r2 @ row 6 load for horizontal filter + + vrhadd.u8 d28, d28, d18 + + vext.8 d5, d0, d1, #5 + vext.8 d2, d0, d1, #2 + vext.8 d3, d0, d1, #3 + + vst1.32 d28, [r1], r3 @ store row 1 + + vaddl.u8 q14, d0, d5 + + vst1.32 {q4}, [r9], r6 @ store temp buffer r7 + + vaddl.s16 q9, d10, d8 + vaddl.s16 q3, d11, d9 + + vld1.32 {q5}, [r7], r6 @ load from temp buffer 2 + + vmlal.s16 q9, d30, d22 + vmlsl.s16 q9, d26, d24 + vmlal.s16 q3, d31, d22 + + vqrshrun.s16 d26, q5, #5 + + vmlsl.s16 q3, d27, d24 + vaddl.u8 q1, d2, d3 + vext.8 d4, d0, d1, #4 + vmla.u16 q14, q1, q11 + vqrshrun.s32 d18, q9, #10 + vext.8 d1, d0, d1, #1 + vqrshrun.s32 d19, q3, #10 + vadd.s16 q5, q7, q4 + vaddl.u8 q1, d1, d4 + vadd.s16 q15, q8, q10 + vmls.u16 q14, q1, q12 + vqmovn.u16 d27, q9 + + vaddl.s16 q9, d12, d28 + vaddl.s16 q3, d13, d29 + + vrhadd.u8 d26, d26, d27 + + vmlal.s16 q9, d30, d22 + vmlsl.s16 q9, d10, d24 + vmlal.s16 q3, d31, d22 + vmlsl.s16 q3, d11, d24 + + vst1.32 d26, [r1], r3 @ store row 2 + + vst1.32 {q14}, [r9] + + + vqrshrun.s32 d18, q9, #10 + vmov q5, q10 + vld1.32 {q15}, [r7], r6 @ load from temp buffer 3 + + vqrshrun.s32 d19, q3, #10 + subs r4, r4, #4 + + vqrshrun.s16 d30, q15, #5 + + vqmovn.u16 d18, q9 + vmov q6, q4 + vmov q3, q7 + vrhadd.u8 d30, d18, d30 + vmov q4, q8 + vmov q7, q14 + vst1.32 d30, [r1], r3 @ store row 3 + + bgt loop_8 @if height =8 or 16 loop + b end_func + +loop_4_start: + vmov.u16 d22, #20 @ Filter coeff 20 into D22 + vmov.u16 d23, #5 @ Filter coeff 5 into D23 + + vld1.32 {q0}, [r0], r2 @row -2 load + vext.8 d5, d0, d1, #5 + vaddl.u8 q3, d0, d5 + vext.8 d2, d0, d1, #2 + vext.8 d3, d0, d1, #3 + vaddl.u8 q4, d2, d3 + vext.8 d4, d0, d1, #4 + vmla.u16 d6, d8, d22 + vext.8 d1, d0, d1, #1 + vaddl.u8 q4, d1, d4 + vld1.32 {q0}, [r0], r2 @ row -1 load + vmls.u16 d6, d8, d23 + vext.8 d5, d0, d1, #5 + vaddl.u8 q4, d0, d5 + vext.8 d2, d0, d1, #2 + vext.8 d3, d0, d1, #3 + vaddl.u8 q5, d2, d3 + + vst1.32 d6, [r9], r6 @ store temp buffer 0 + + vext.8 d4, d0, d1, #4 + vmla.u16 d8, d10, d22 + vext.8 d1, d0, d1, #1 + vaddl.u8 q5, d1, d4 + vld1.32 {q0}, [r0], r2 @ row 0 load + vmls.u16 d8, d10, d23 + vext.8 d5, d0, d1, #5 + vaddl.u8 q5, d0, d5 + vext.8 d2, d0, d1, #2 + vext.8 d3, d0, d1, #3 + vaddl.u8 q6, d2, d3 + + vst1.32 d8, [r9], r6 @ store temp buffer 1 + + vext.8 d4, d0, d1, #4 + vmla.u16 d10, d12, d22 + vext.8 d1, d0, d1, #1 + vaddl.u8 q6, d1, d4 + vld1.32 {q0}, [r0], r2 @ row 1 load + vmls.u16 d10, d12, d23 + vext.8 d5, d0, d1, #5 + vaddl.u8 q6, d0, d5 + vext.8 d2, d0, d1, #2 + vext.8 d3, d0, d1, #3 + vaddl.u8 q7, d2, d3 + + vst1.32 d10, [r9], r6 @ store temp buffer 2 + + vext.8 d4, d0, d1, #4 + vmla.u16 d12, d14, d22 + vext.8 d1, d0, d1, #1 + vaddl.u8 q7, d1, d4 + vld1.32 {q0}, [r0], r2 @ row 2 load + vmls.u16 d12, d14, d23 + vext.8 d5, d0, d1, #5 + vaddl.u8 q7, d0, d5 + vext.8 d2, d0, d1, #2 + vext.8 d3, d0, d1, #3 + vaddl.u8 q8, d2, d3 + vext.8 d4, d0, d1, #4 + vmla.u16 d14, d16, d22 + vext.8 d1, d0, d1, #1 + vaddl.u8 q8, d1, d4 + + vst1.32 d12, [r9], r6 @ store temp buffer 3 + + vmls.u16 d14, d16, d23 + +loop_4: + + vld1.32 {q0}, [r0], r2 @ row 3 load + vext.8 d5, d0, d1, #5 + vaddl.u8 q8, d0, d5 + vext.8 d2, d0, d1, #2 + vext.8 d3, d0, d1, #3 + vaddl.u8 q9, d2, d3 + vst1.32 d14, [r9], r6 @ store temp buffer 4 + vext.8 d4, d0, d1, #4 + vmla.u16 d16, d18, d22 + vext.8 d1, d0, d1, #1 + vaddl.u8 q9, d1, d4 + vadd.s16 d2, d10, d12 + vmls.u16 d16, d18, d23 + vadd.s16 d3, d8, d14 + vld1.32 {q9}, [r0], r2 @ row 4 load + vext.8 d25, d18, d19, #5 + vaddl.u8 q13, d18, d25 + vext.8 d20, d18, d19, #2 + + vst1.32 d16, [r9], r6 @ store temp buffer 5 + + vaddl.s16 q0, d6, d16 + vmlal.s16 q0, d2, d22 + vext.8 d21, d18, d19, #3 + vaddl.u8 q14, d20, d21 + vext.8 d24, d18, d19, #4 + vmlsl.s16 q0, d3, d23 + vmla.u16 d26, d28, d22 + vext.8 d19, d18, d19, #1 + vaddl.u8 q14, d19, d24 + vadd.s16 d2, d12, d14 + vmls.u16 d26, d28, d23 + vqrshrun.s32 d0, q0, #0xa + vadd.s16 d3, d10, d16 + vld1.32 {q9}, [r0], r2 @ row 5 load + vext.8 d25, d18, d19, #5 + vqmovn.u16 d11, q0 + vaddl.u8 q14, d18, d25 + + vst1.32 d26, [r9], r6 @ store temp buffer 6 + + @Q3 available here + vld1.32 d6, [r7], r6 @ load from temp buffer 0 + vld1.32 d7, [r7], r6 @ load from temp buffer 1 + vqrshrun.s16 d9, q3, #5 + + vext.8 d20, d18, d19, #2 + + vaddl.s16 q0, d8, d26 + vmlal.s16 q0, d2, d22 + vext.8 d21, d18, d19, #3 + vaddl.u8 q3, d20, d21 + vext.8 d24, d18, d19, #4 + vmlsl.s16 q0, d3, d23 + vmla.u16 d28, d6, d22 + vext.8 d19, d18, d19, #1 + vaddl.u8 q3, d19, d24 + vadd.s16 d2, d14, d16 + vmls.u16 d28, d6, d23 + vqrshrun.s32 d0, q0, #0xa + vadd.s16 d3, d12, d26 + vld1.32 {q9}, [r0], r2 @ row 6 load + vext.8 d25, d18, d19, #5 + vqmovn.u16 d13, q0 + + vtrn.32 d11, d13 + vaddl.s16 q0, d10, d28 + vrhadd.u8 d9, d9, d11 + + vst1.32 d28, [r9], r6 @ store temp buffer 7 + + vmlal.s16 q0, d2, d22 + vaddl.u8 q15, d18, d25 + + vst1.32 d9[0], [r1], r3 @ store row 0 + + vext.8 d20, d18, d19, #2 + + vst1.32 d9[1], [r1], r3 @ store row 1 + + vext.8 d21, d18, d19, #3 + vmlsl.s16 q0, d3, d23 + vaddl.u8 q4, d20, d21 + vext.8 d24, d18, d19, #4 + vmla.u16 d30, d8, d22 + vext.8 d19, d18, d19, #1 + vaddl.u8 q4, d19, d24 + vqrshrun.s32 d0, q0, #0xa + vadd.s16 d2, d16, d26 + vmls.u16 d30, d8, d23 + vqmovn.u16 d4, q0 + + vadd.s16 d3, d14, d28 + + + vaddl.s16 q0, d12, d30 + + vst1.32 d30, [r9] + + vmlal.s16 q0, d2, d22 + + vld1.32 d8, [r7], r6 @ load from temp buffer 2 + vld1.32 d9, [r7], r6 @ load from temp buffer 3 + vmlsl.s16 q0, d3, d23 + subs r4, r4, #4 + vqrshrun.s16 d10, q4, #5 + + vmov d12, d28 + + vqrshrun.s32 d0, q0, #0xa + vmov d6, d14 + vmov d8, d16 + + vqmovn.u16 d5, q0 + + vtrn.32 d4, d5 + vrhadd.u8 d4, d4, d10 + vmov d10, d26 + vmov d14, d30 + + vst1.32 d4[0], [r1], r3 @ store row 2 + vst1.32 d4[1], [r1], r3 @ store row 3 + + bgt loop_4 + +end_func: + vldmia sp!, {d8-d15} @ Restore neon registers that were saved + ldmfd sp!, {r4-r12, pc} @Restoring registers from stack + + diff --git a/dependencies/ih264d/common/arm/ih264_inter_pred_luma_horz_qpel_a9q.s b/dependencies/ih264d/common/arm/ih264_inter_pred_luma_horz_qpel_a9q.s new file mode 100644 index 00000000..ab1d1d14 --- /dev/null +++ b/dependencies/ih264d/common/arm/ih264_inter_pred_luma_horz_qpel_a9q.s @@ -0,0 +1,266 @@ +@/****************************************************************************** +@ * +@ * Copyright (C) 2015 The Android Open Source Project +@ * +@ * Licensed under the Apache License, Version 2.0 (the "License"); +@ * you may not use this file except in compliance with the License. +@ * You may obtain a copy of the License at: +@ * +@ * http://www.apache.org/licenses/LICENSE-2.0 +@ * +@ * Unless required by applicable law or agreed to in writing, software +@ * distributed under the License is distributed on an "AS IS" BASIS, +@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@ * See the License for the specific language governing permissions and +@ * limitations under the License. +@ * +@ ***************************************************************************** +@ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +@*/ +@** +@****************************************************************************** +@* @file +@* ih264_inter_pred_luma_horz_qpel_a9q.s +@* +@* @brief +@* Contains function definitions for inter prediction horizontal quarter pel interpolation. +@* +@* @author +@* Mohit +@* +@* @par List of Functions: +@* +@* - ih264_inter_pred_luma_horz_qpel_a9q() +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* + +@* All the functions here are replicated from ih264_inter_pred_filters.c +@ + +@** +@** +@******************************************************************************* +@* +@* @brief +@* Quarter pel interprediction luma filter for horizontal input +@* +@* @par Description: +@* Applies a 6 tap horizontal filter .The output is clipped to 8 bits +@* sec 8.4.2.2.1 titled "Luma sample interpolation process" +@* +@* @param[in] pu1_src +@* UWORD8 pointer to the source +@* +@* @param[out] pu1_dst +@* UWORD8 pointer to the destination +@* +@* @param[in] src_strd +@* integer source stride +@* +@* @param[in] dst_strd +@* integer destination stride +@* +@* @param[in] ht +@* integer height of the array +@* +@* @param[in] wd +@* integer width of the array +@* +@ @param[in] pu1_tmp: temporary buffer: UNUSED in this function +@* +@* @param[in] dydx: x and y reference offset for qpel calculations. +@* @returns +@* +@ @remarks +@* None +@* +@******************************************************************************* +@* + +@void ih264_inter_pred_luma_horz ( +@ UWORD8 *pu1_src, +@ UWORD8 *pu1_dst, +@ WORD32 src_strd, +@ WORD32 dst_strd, +@ WORD32 ht, +@ WORD32 wd, +@ UWORD8* pu1_tmp, +@ UWORD32 dydx) + +@**************Variables Vs Registers***************************************** +@ r0 => *pu1_src +@ r1 => *pu1_dst +@ r2 => src_strd +@ r3 => dst_strd +@ r5 => ht +@ r6 => wd +@ r7 => dydx + +.text +.p2align 2 + + + .global ih264_inter_pred_luma_horz_qpel_a9q + +ih264_inter_pred_luma_horz_qpel_a9q: + + stmfd sp!, {r4-r12, r14} @store register values to stack + vstmdb sp!, {d8-d15} @push neon registers to stack + ldr r5, [sp, #104] @Loads ht + ldr r6, [sp, #108] @Loads wd + ldr r7, [sp, #116] @Loads dydx + and r7, r7, #3 @Finds x-offset + add r7, r0, r7, lsr #1 @pu1_src + (x_offset>>1) + sub r0, r0, #2 @pu1_src-2 + vmov.i8 d0, #5 @filter coeff + subs r12, r6, #8 @if wd=8 branch to loop_8 + vmov.i8 d1, #20 @filter coeff + + beq loop_8 + + subs r12, r6, #4 @if wd=4 branch to loop_4 + beq loop_4 + +loop_16: @when wd=16 + @ Processing row0 and row1 + vld1.8 {d2, d3, d4}, [r0], r2 @// Load row0 + vext.8 d31, d2, d3, #5 @//extract a[5] (column1,row0) + vld1.8 {d5, d6, d7}, [r0], r2 @// Load row1 + vext.8 d30, d3, d4, #5 @//extract a[5] (column2,row0) + vaddl.u8 q4, d31, d2 @// a0 + a5 (column1,row0) + vext.8 d28, d5, d6, #5 @//extract a[5] (column1,row1) + vaddl.u8 q5, d30, d3 @// a0 + a5 (column2,row0) + vext.8 d27, d6, d7, #5 @//extract a[5] (column2,row1) + vaddl.u8 q7, d28, d5 @// a0 + a5 (column1,row1) + vext.8 d31, d2, d3, #2 @//extract a[2] (column1,row0) + vaddl.u8 q8, d27, d6 @// a0 + a5 (column2,row1) + vext.8 d30, d3, d4, #2 @//extract a[2] (column2,row0) + vmlal.u8 q4, d31, d1 @// a0 + a5 + 20a2 (column1,row0) + vext.8 d28, d5, d6, #2 @//extract a[2] (column1,row1) + vmlal.u8 q5, d30, d1 @// a0 + a5 + 20a2 (column2,row0) + vext.8 d27, d6, d7, #2 @//extract a[2] (column2,row1) + vmlal.u8 q7, d28, d1 @// a0 + a5 + 20a2 (column1,row1) + vext.8 d31, d2, d3, #3 @//extract a[3] (column1,row0) + vmlal.u8 q8, d27, d1 @// a0 + a5 + 20a2 (column2,row1) + vext.8 d30, d3, d4, #3 @//extract a[3] (column2,row0) + vmlal.u8 q4, d31, d1 @// a0 + a5 + 20a2 + 20a3 (column1,row0) + vext.8 d28, d5, d6, #3 @//extract a[3] (column1,row1) + vmlal.u8 q5, d30, d1 @// a0 + a5 + 20a2 + 20a3 (column2,row0) + vext.8 d27, d6, d7, #3 @//extract a[3] (column2,row1) + vmlal.u8 q7, d28, d1 @// a0 + a5 + 20a2 + 20a3 (column1,row1) + vext.8 d31, d2, d3, #1 @//extract a[1] (column1,row0) + vmlal.u8 q8, d27, d1 @// a0 + a5 + 20a2 + 20a3 (column2,row1) + vext.8 d30, d3, d4, #1 @//extract a[1] (column2,row0) + vmlsl.u8 q4, d31, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row0) + vext.8 d28, d5, d6, #1 @//extract a[1] (column1,row1) + vmlsl.u8 q5, d30, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 (column2,row0) + vext.8 d27, d6, d7, #1 @//extract a[1] (column2,row1) + vmlsl.u8 q7, d28, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row1) + vext.8 d31, d2, d3, #4 @//extract a[4] (column1,row0) + vmlsl.u8 q8, d27, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 (column2,row1) + vext.8 d30, d3, d4, #4 @//extract a[4] (column2,row0) + vmlsl.u8 q4, d31, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row0) + vext.8 d28, d5, d6, #4 @//extract a[4] (column1,row1) + vmlsl.u8 q5, d30, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2,row0) + vext.8 d27, d6, d7, #4 @//extract a[4] (column2,row1) + vmlsl.u8 q7, d28, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row1) + vmlsl.u8 q8, d27, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2,row1) + vld1.32 {d12, d13}, [r7], r2 @Load value for interpolation (column1,row0) + vqrshrun.s16 d20, q4, #5 @// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row0) + vqrshrun.s16 d21, q5, #5 @// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column2,row0) + vext.8 d31, d2, d3, #5 @//extract a[5] (column1,row2) + vrhadd.u8 q10, q6, q10 @Interpolation step for qpel calculation + vqrshrun.s16 d18, q7, #5 @// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row1) + vst1.8 {d20, d21}, [r1], r3 @//Store dest row0 + vext.8 d30, d3, d4, #5 @//extract a[5] (column2,row2) + vqrshrun.s16 d19, q8, #5 @// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column2,row1) + vld1.32 {d12, d13}, [r7], r2 @Load value for interpolation (column1,row1) + vrhadd.u8 q9, q6, q9 @Interpolation step for qpel calculation + vst1.8 {d18, d19}, [r1], r3 @//Store dest row1 + subs r5, r5, #2 @ 2 rows done, decrement by 2 + + beq end_func + b loop_16 + +loop_8: +@ Processing row0 and row1 + + vld1.8 {d5, d6}, [r0], r2 @// Load row1 + vext.8 d28, d5, d6, #5 @//extract a[5] (column1,row1) + vld1.8 {d2, d3}, [r0], r2 @// Load row0 + vext.8 d25, d5, d6, #2 @//extract a[2] (column1,row1) + vext.8 d31, d2, d3, #5 @//extract a[5] (column1,row0) + vext.8 d24, d5, d6, #3 @//extract a[3] (column1,row1) + vext.8 d23, d5, d6, #1 @//extract a[1] (column1,row1) + vext.8 d22, d5, d6, #4 @//extract a[4] (column1,row1) + vaddl.u8 q7, d28, d5 @// a0 + a5 (column1,row1) + vext.8 d29, d2, d3, #3 @//extract a[3] (column1,row0) + vmlal.u8 q7, d25, d1 @// a0 + a5 + 20a2 (column1,row1) + vmlal.u8 q7, d24, d1 @// a0 + a5 + 20a2 + 20a3 (column1,row1) + vmlsl.u8 q7, d23, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row1) + vmlsl.u8 q7, d22, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row1) + vext.8 d30, d2, d3, #2 @//extract a[2] (column1,row0) + vaddl.u8 q4, d31, d2 @// a0 + a5 (column1,row0) + vext.8 d27, d2, d3, #1 @//extract a[1] (column1,row0) + vext.8 d26, d2, d3, #4 @//extract a[4] (column1,row0) + vmlal.u8 q4, d29, d1 @// a0 + a5 + 20a2 + 20a3 (column1,row0) + vmlal.u8 q4, d30, d1 @// a0 + a5 + 20a2 (column1,row0) + vmlsl.u8 q4, d27, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row0) + vmlsl.u8 q4, d26, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row0) + vqrshrun.s16 d18, q7, #5 @// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row0) + vld1.32 d12, [r7], r2 @Load value for interpolation (column1,row0) + vld1.32 d13, [r7], r2 @Load value for interpolation (column1,row1) + vqrshrun.s16 d19, q4, #5 @// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row1) + vrhadd.u8 q9, q6, q9 @Interpolation step for qpel calculation + vst1.8 {d18}, [r1], r3 @//Store dest row0 + vst1.8 {d19}, [r1], r3 @//Store dest row1 + subs r5, r5, #2 @ 2 rows done, decrement by 2 + + beq end_func @ Branch if height==4 + b loop_8 @looping if height == 8 or 16 + +loop_4: + vld1.8 {d5, d6}, [r0], r2 @// Load row1 + vext.8 d28, d5, d6, #5 @//extract a[5] (column1,row1) + vld1.8 {d2, d3}, [r0], r2 @// Load row0 + vext.8 d25, d5, d6, #2 @//extract a[2] (column1,row1) + vext.8 d31, d2, d3, #5 @//extract a[5] (column1,row0) + vaddl.u8 q7, d28, d5 @// a0 + a5 (column1,row1) + vext.8 d24, d5, d6, #3 @//extract a[3] (column1,row1) + vext.8 d23, d5, d6, #1 @//extract a[1] (column1,row1) + vext.8 d22, d5, d6, #4 @//extract a[4] (column1,row1) + vext.8 d29, d2, d3, #3 @//extract a[3] (column1,row0) + vmlal.u8 q7, d25, d1 @// a0 + a5 + 20a2 (column1,row1) + vmlal.u8 q7, d24, d1 @// a0 + a5 + 20a2 + 20a3 (column1,row1) + vmlsl.u8 q7, d23, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row1) + vmlsl.u8 q7, d22, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row1) + vaddl.u8 q4, d31, d2 @// a0 + a5 (column1,row0) + vext.8 d30, d2, d3, #2 @//extract a[2] (column1,row0) + vld1.32 d12, [r7], r2 @Load value for interpolation (column1,row0) + vld1.32 d13, [r7], r2 @Load value for interpolation (column1,row1) + vext.8 d27, d2, d3, #1 @//extract a[1] (column1,row0) + vext.8 d26, d2, d3, #4 @//extract a[4] (column1,row0) + vmlal.u8 q4, d29, d1 @// a0 + a5 + 20a2 + 20a3 (column1,row0) + vmlal.u8 q4, d30, d1 @// a0 + a5 + 20a2 (column1,row0) + vmlsl.u8 q4, d27, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row0) + vmlsl.u8 q4, d26, d0 @// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row0) + vqrshrun.s16 d18, q7, #5 @// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row0) + vqrshrun.s16 d19, q4, #5 @// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row1) + vrhadd.u8 q9, q6, q9 @Interpolation step for qpel calculation + vst1.32 d18[0], [r1], r3 @//Store dest row0 + vst1.32 d19[0], [r1], r3 @//Store dest row1 + + subs r5, r5, #2 @ 2 rows done, decrement by 2 + beq end_func + + b loop_4 + +end_func: + vldmia sp!, {d8-d15} @ Restore neon registers that were saved + ldmfd sp!, {r4-r12, pc} @Restoring registers from stack + + diff --git a/dependencies/ih264d/common/arm/ih264_inter_pred_luma_horz_qpel_vert_hpel_a9q.s b/dependencies/ih264d/common/arm/ih264_inter_pred_luma_horz_qpel_vert_hpel_a9q.s new file mode 100644 index 00000000..3c63ca30 --- /dev/null +++ b/dependencies/ih264d/common/arm/ih264_inter_pred_luma_horz_qpel_vert_hpel_a9q.s @@ -0,0 +1,505 @@ +@/****************************************************************************** +@ * +@ * Copyright (C) 2015 The Android Open Source Project +@ * +@ * Licensed under the Apache License, Version 2.0 (the "License"); +@ * you may not use this file except in compliance with the License. +@ * You may obtain a copy of the License at: +@ * +@ * http://www.apache.org/licenses/LICENSE-2.0 +@ * +@ * Unless required by applicable law or agreed to in writing, software +@ * distributed under the License is distributed on an "AS IS" BASIS, +@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@ * See the License for the specific language governing permissions and +@ * limitations under the License. +@ * +@ ***************************************************************************** +@ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +@*/ +@** +@****************************************************************************** +@* @file +@* ih264_inter_pred_luma_horz_qpel_vert_hpel_a9q.s +@* +@* @brief +@* Contains function definitions for inter prediction interpolation. +@* +@* @author +@* Mohit +@* +@* @par List of Functions: +@* +@* - ih264_inter_pred_luma_horz_qpel_vert_hpel_a9q() +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* + +@* All the functions here are replicated from ih264_inter_pred_filters.c +@ + +@** +@** +@** +@******************************************************************************* +@* +@* @brief +@* This function implements a two stage cascaded six tap filter. It +@* applies the six tap filter in the vertical direction on the +@* predictor values, followed by applying the same filter in the +@* horizontal direction on the output of the first stage. It then averages +@* the output of the 1st stage and the final stage to obtain the quarter +@* pel values.The six tap filtering operation is described in sec 8.4.2.2.1 +@* titled "Luma sample interpolation process". +@* +@* @par Description: +@* This function is called to obtain pixels lying at the following +@* location (1/4,1/2) or (3/4,1/2). The function interpolates +@* the predictors first in the verical direction and then in the +@* horizontal direction to output the (1/2,1/2). It then averages +@* the output of the 2nd stage and (1/2,1/2) value to obtain (1/4,1/2) +@* or (3/4,1/2) depending on the offset. +@* +@* @param[in] pu1_src +@* UWORD8 pointer to the source +@* +@* @param[out] pu1_dst +@* UWORD8 pointer to the destination +@* +@* @param[in] src_strd +@* integer source stride +@* +@* @param[in] dst_strd +@* integer destination stride +@* +@* @param[in] ht +@* integer height of the array +@* +@* @param[in] wd +@* integer width of the array +@* +@* @param[in] pu1_tmp: temporary buffer +@* +@* @param[in] dydx: x and y reference offset for qpel calculations +@* +@* @returns +@* +@* @remarks +@* None +@* +@******************************************************************************* +@*; + +@void ih264_inter_pred_luma_horz_qpel_vert_hpel(UWORD8 *pu1_src, +@ UWORD8 *pu1_dst, +@ WORD32 src_strd,, +@ WORD32 dst_strd, +@ WORD32 ht, +@ WORD32 wd, +@ UWORD8* pu1_tmp, +@ UWORD32 dydx) + +@**************Variables Vs Registers***************************************** +@ r0 => *pu1_src +@ r1 => *pu1_dst +@ r2 => src_strd +@ r3 => dst_strd +@ r4 => ht +@ r5 => wd +@ r6 => dydx +@ r9 => *pu1_tmp + +.text +.p2align 2 + + .global ih264_inter_pred_luma_horz_qpel_vert_hpel_a9q + +ih264_inter_pred_luma_horz_qpel_vert_hpel_a9q: + + stmfd sp!, {r4-r12, r14} @store register values to stack + vstmdb sp!, {d8-d15} @push neon registers to stack + ldr r4, [sp, #104] @ loads ht + sub r0, r0, r2, lsl #1 @pu1_src-2*src_strd + sub r0, r0, #2 @pu1_src-2 + ldr r5, [sp, #108] @ loads wd + ldr r6, [sp, #116] @ loads dydx + and r6, r6, #2 @ dydx & 0x3 followed by dydx>>1 and dydx<<1 + ldr r9, [sp, #112] @pu1_tmp + add r7, r9, #4 + add r6, r7, r6 @ pi16_pred1_temp += (x_offset>>1) + + vmov.u16 q13, #0x14 @ Filter coeff 20 into Q13 + vmov.u16 q12, #0x5 @ Filter coeff 5 into Q12 + mov r7, #0x20 + mov r8, #0x30 + subs r12, r5, #4 @if wd=4 branch to loop_4 + beq loop_4 + + subs r12, r5, #8 @if wd=8 branch to loop_8 + beq loop_8 + + @when wd=16 + vmov.u16 q14, #0x14 @ Filter coeff 20 into Q13 + vmov.u16 q15, #0x5 @ Filter coeff 5 into Q12 + add r14, r2, #0 + sub r2, r2, #16 + + +loop_16: + + vld1.u32 {q0}, [r0]! @ Vector load from src[0_0] + vld1.u32 d12, [r0], r2 @ Vector load from src[0_0] + vld1.u32 {q1}, [r0]! @ Vector load from src[1_0] + vld1.u32 d13, [r0], r2 @ Vector load from src[1_0] + vld1.u32 {q2}, [r0]! @ Vector load from src[2_0] + vld1.u32 d14, [r0], r2 @ Vector load from src[2_0] + vld1.u32 {q3}, [r0]! @ Vector load from src[3_0] + vld1.u32 d15, [r0], r2 @ Vector load from src[3_0] + vld1.u32 {q4}, [r0]! @ Vector load from src[4_0] + vld1.u32 d16, [r0], r2 @ Vector load from src[4_0] + + vld1.u32 {q5}, [r0]! @ Vector load from src[5_0] + vld1.u32 d17, [r0], r2 @ Vector load from src[5_0] + + vaddl.u8 q10, d4, d6 + vaddl.u8 q9, d0, d10 + vaddl.u8 q11, d2, d8 + vmla.u16 q9, q10, q14 + vaddl.u8 q12, d5, d7 + vaddl.u8 q10, d1, d11 + vaddl.u8 q13, d3, d9 + vmla.u16 q10, q12, q14 + vaddl.u8 q12, d14, d15 + vmls.u16 q9, q11, q15 + vaddl.u8 q11, d12, d17 + vmls.u16 q10, q13, q15 + vaddl.u8 q13, d13, d16 + vmla.u16 q11, q12, q14 + vmls.u16 q11, q13, q15 + vst1.32 {q9}, [r9]! + vst1.32 {q10}, [r9]! + vext.16 q12, q9, q10, #2 + vext.16 q13, q9, q10, #3 + vst1.32 {q11}, [r9] + vext.16 q11, q9, q10, #5 + vadd.s16 q0, q12, q13 + vext.16 q12, q9, q10, #1 + vext.16 q13, q9, q10, #4 + vadd.s16 q12, q12, q13 + + vaddl.s16 q13, d18, d22 + vmlal.s16 q13, d0, d28 + vmlsl.s16 q13, d24, d30 + + vaddl.s16 q11, d19, d23 + vmlal.s16 q11, d1, d28 + vmlsl.s16 q11, d25, d30 + + vqrshrun.s32 d18, q13, #10 + vqrshrun.s32 d19, q11, #10 + vld1.32 {q11}, [r9]! + vqmovn.u16 d18, q9 + + vext.16 q12, q10, q11, #2 + vext.16 q13, q10, q11, #3 + vext.16 q0, q10, q11, #5 + vst1.32 d18, [r1] + vadd.s16 q9, q12, q13 + vext.16 q12, q10, q11, #1 + vext.16 q13, q10, q11, #4 + vadd.s16 q12, q12, q13 + + vaddl.s16 q13, d0, d20 + vmlal.s16 q13, d18, d28 + vmlsl.s16 q13, d24, d30 + + vaddl.s16 q11, d1, d21 + vmlal.s16 q11, d19, d28 + vmlsl.s16 q11, d25, d30 + + vqrshrun.s32 d18, q13, #10 + vqrshrun.s32 d19, q11, #10 + + vaddl.u8 q12, d7, d9 + vld1.32 {q10}, [r6]! + vld1.32 {q11}, [r6], r7 + + vqmovn.u16 d19, q9 + + vld1.32 d18, [r1] + vqrshrun.s16 d20, q10, #5 + vqrshrun.s16 d21, q11, #5 + vaddl.u8 q11, d4, d10 + vld1.u32 {q0}, [r0]! @ Vector load from src[6_0] + vrhadd.u8 q9, q9, q10 + vld1.u32 d12, [r0], r2 @ Vector load from src[6_0] + vaddl.u8 q10, d6, d8 + vaddl.u8 q13, d5, d11 + vst1.32 {q9}, [r1], r3 @ store row 0 + +@ROW_2 + + vaddl.u8 q9, d2, d0 + + vmla.u16 q9, q10, q14 + + vaddl.u8 q10, d3, d1 + + vmla.u16 q10, q12, q14 + vaddl.u8 q12, d15, d16 + vmls.u16 q9, q11, q15 + vaddl.u8 q11, d13, d12 + vmls.u16 q10, q13, q15 + vaddl.u8 q13, d14, d17 + vmla.u16 q11, q12, q14 + vmls.u16 q11, q13, q15 + vst1.32 {q9}, [r9]! + vst1.32 {q10}, [r9]! + vext.16 q12, q9, q10, #2 + vext.16 q13, q9, q10, #3 + vst1.32 {q11}, [r9] + vext.16 q11, q9, q10, #5 + vadd.s16 q1, q12, q13 + vext.16 q12, q9, q10, #1 + vext.16 q13, q9, q10, #4 + vadd.s16 q12, q12, q13 + + vaddl.s16 q13, d18, d22 + vmlal.s16 q13, d2, d28 + vmlsl.s16 q13, d24, d30 + + vaddl.s16 q11, d19, d23 + vmlal.s16 q11, d3, d28 + vmlsl.s16 q11, d25, d30 + + vqrshrun.s32 d18, q13, #10 + vqrshrun.s32 d19, q11, #10 + vld1.32 {q11}, [r9]! + vqmovn.u16 d18, q9 + + vext.16 q12, q10, q11, #2 + vext.16 q13, q10, q11, #3 + vext.16 q1, q10, q11, #5 + vst1.32 d18, [r1] + vadd.s16 q9, q12, q13 + vext.16 q12, q10, q11, #1 + vext.16 q13, q10, q11, #4 + vadd.s16 q12, q12, q13 + + vaddl.s16 q13, d2, d20 + vmlal.s16 q13, d18, d28 + vmlsl.s16 q13, d24, d30 + + vaddl.s16 q11, d3, d21 + vmlal.s16 q11, d19, d28 + vmlsl.s16 q11, d25, d30 + + vqrshrun.s32 d18, q13, #10 + vqrshrun.s32 d19, q11, #10 + vaddl.u8 q12, d9, d11 + vld1.32 {q10}, [r6]! + vld1.32 {q11}, [r6], r7 + vqmovn.u16 d19, q9 + vld1.32 d18, [r1] + vqrshrun.s16 d20, q10, #5 + vqrshrun.s16 d21, q11, #5 + + vrhadd.u8 q9, q9, q10 + + vst1.32 {q9}, [r1], r3 @ store row 1 + + subs r4, r4, #2 + subne r0, r0 , r14, lsl #2 + subne r0, r0, r14 + + beq end_func @ Branch if height==4 + b loop_16 @ Loop if height==8 + +loop_8: + vld1.u32 {q0}, [r0], r2 @ Vector load from src[0_0] + vld1.u32 {q1}, [r0], r2 @ Vector load from src[1_0] + vld1.u32 {q2}, [r0], r2 @ Vector load from src[2_0] + vld1.u32 {q3}, [r0], r2 @ Vector load from src[3_0] + vld1.u32 {q4}, [r0], r2 @ Vector load from src[4_0] + + vld1.u32 {q5}, [r0], r2 @ Vector load from src[5_0] + vaddl.u8 q7, d4, d6 + vaddl.u8 q6, d0, d10 + vaddl.u8 q8, d2, d8 + vmla.u16 q6, q7, q13 + vaddl.u8 q9, d5, d7 + vaddl.u8 q7, d1, d11 + vaddl.u8 q11, d3, d9 + vmla.u16 q7, q9, q13 + vmls.u16 q6, q8, q12 + vld1.32 {q0}, [r0], r2 @ Vector load from src[6_0] + vaddl.u8 q8, d6, d8 + vmls.u16 q7, q11, q12 + vaddl.u8 q14, d2, d0 + vst1.32 {q6}, [r9]! @ store row 0 to temp buffer: col 0 + vext.16 q11, q6, q7, #5 + vaddl.u8 q9, d4, d10 + vmla.u16 q14, q8, q13 + vaddl.s16 q15, d12, d22 + vst1.32 {q7}, [r9], r7 @ store row 0 to temp buffer: col 1 + vaddl.s16 q11, d13, d23 + vext.16 q8, q6, q7, #2 + vmls.u16 q14, q9, q12 + vext.16 q9, q6, q7, #3 + vext.16 q10, q6, q7, #4 + vext.16 q7, q6, q7, #1 + vadd.s16 q8, q8, q9 + vadd.s16 q9, q7, q10 + vaddl.u8 q10, d7, d9 + vmlal.s16 q15, d16, d26 + vmlsl.s16 q15, d18, d24 + vmlal.s16 q11, d17, d26 + vmlsl.s16 q11, d19, d24 + vaddl.u8 q7, d3, d1 + vst1.32 {q14}, [r9]! @ store row 1 to temp buffer: col 0 + vmla.u16 q7, q10, q13 + vqrshrun.s32 d12, q15, #10 + vaddl.u8 q8, d5, d11 + vqrshrun.s32 d13, q11, #10 + vmls.u16 q7, q8, q12 +@ vld1.32 {q1},[r0],r2 ; Vector load from src[7_0] + vqmovn.u16 d25, q6 + vaddl.u8 q8, d8, d10 + + + vext.16 q11, q14, q7, #5 + vaddl.u8 q10, d4, d2 + vaddl.s16 q15, d28, d22 + vmla.u16 q10, q8, q13 + vst1.32 {q7}, [r9], r7 @ store row 1 to temp buffer: col 1 + vaddl.s16 q11, d29, d23 + vext.16 q8, q14, q7, #2 + vext.16 q9, q14, q7, #3 + vext.16 q6, q14, q7, #4 + vext.16 q7, q14, q7, #1 + vadd.s16 q8, q8, q9 + vadd.s16 q9, q6, q7 + vld1.32 {q7}, [r6], r8 @ load row 0 from temp buffer + vmlal.s16 q15, d16, d26 + vmlsl.s16 q15, d18, d24 + vmlal.s16 q11, d17, d26 + vmlsl.s16 q11, d19, d24 + vqrshrun.s16 d14, q7, #0x5 + vld1.32 {q14}, [r6], r8 @ load row 1 from temp buffer + vaddl.u8 q9, d6, d0 + vqrshrun.s32 d16, q15, #10 + vqrshrun.s16 d15, q14, #0x5 + vqrshrun.s32 d17, q11, #10 + vmov d12, d25 + vmov d25, d24 + + vqmovn.u16 d13, q8 + vrhadd.u8 q6, q6, q7 + + vst1.32 d12, [r1], r3 @ store row 0 + vst1.32 d13, [r1], r3 @ store row 1 + + subs r4, r4, #2 + subne r0, r0 , r2, lsl #2 + subne r0, r0, r2 + + beq end_func @ Branch if height==4 + b loop_8 @ Loop if height==8 + +loop_4: + vld1.u32 {q0}, [r0], r2 @ Vector load from src[0_0] + vld1.u32 {q1}, [r0], r2 @ Vector load from src[1_0] + vld1.u32 {q2}, [r0], r2 @ Vector load from src[2_0] + vld1.u32 {q3}, [r0], r2 @ Vector load from src[3_0] + vld1.u32 {q4}, [r0], r2 @ Vector load from src[4_0] + vld1.u32 {q5}, [r0], r2 @ Vector load from src[5_0] + + vaddl.u8 q7, d4, d6 @ temp1 = src[2_0] + src[3_0] + vaddl.u8 q6, d0, d10 @ temp = src[0_0] + src[5_0] + vaddl.u8 q8, d2, d8 @ temp2 = src[1_0] + src[4_0] + vmla.u16 q6, q7, q13 @ temp += temp1 * 20 + vaddl.u8 q9, d5, d7 @ temp1 = src[2_0] + src[3_0] + vaddl.u8 q7, d1, d11 @ temp = src[0_0] + src[5_0] + vaddl.u8 q11, d3, d9 @ temp2 = src[1_0] + src[4_0] + vmla.u16 q7, q9, q13 @ temp += temp1 * 20 + vmls.u16 q6, q8, q12 @ temp -= temp2 * 5 + vld1.32 {q0}, [r0], r2 @ Vector load from src[6_0] + vaddl.u8 q8, d6, d8 + vmls.u16 q7, q11, q12 @ temp -= temp2 * 5 + @Q6 and Q7 have filtered values + vaddl.u8 q14, d2, d0 + vst1.32 {q6}, [r9]! @ store row 0 to temp buffer: col 0 + vext.16 q11, q6, q7, #5 + vaddl.u8 q9, d4, d10 + vmla.u16 q14, q8, q13 + vaddl.s16 q15, d12, d22 + vst1.32 {q7}, [r9], r7 @ store row 0 to temp buffer: col 1 + vaddl.s16 q11, d13, d23 + vext.16 q8, q6, q7, #2 + vmls.u16 q14, q9, q12 + vext.16 q9, q6, q7, #3 + vext.16 q10, q6, q7, #4 + vext.16 q7, q6, q7, #1 + vadd.s16 q8, q8, q9 + vadd.s16 q9, q7, q10 + vaddl.u8 q10, d7, d9 + vmlal.s16 q15, d16, d26 + vmlsl.s16 q15, d18, d24 + vmlal.s16 q11, d17, d26 + vmlsl.s16 q11, d19, d24 + vaddl.u8 q7, d3, d1 + vst1.32 {q14}, [r9]! @ store row 1 to temp buffer: col 0 + vmla.u16 q7, q10, q13 + vqrshrun.s32 d12, q15, #10 + vaddl.u8 q8, d5, d11 + vqrshrun.s32 d13, q11, #10 + vmls.u16 q7, q8, q12 + vqmovn.u16 d25, q6 + vaddl.u8 q8, d8, d10 + + vext.16 q11, q14, q7, #5 + vaddl.u8 q10, d4, d2 + vaddl.s16 q15, d28, d22 + vmla.u16 q10, q8, q13 + vst1.32 {q7}, [r9], r7 @ store row 1 to temp buffer: col 1 + vaddl.s16 q11, d29, d23 + vext.16 q8, q14, q7, #2 + vext.16 q9, q14, q7, #3 + vext.16 q6, q14, q7, #4 + vext.16 q7, q14, q7, #1 + vadd.s16 q8, q8, q9 + vadd.s16 q9, q6, q7 + vld1.32 d14, [r6], r8 @load row 0 from temp buffer + vmlal.s16 q15, d16, d26 + vmlsl.s16 q15, d18, d24 + vmlal.s16 q11, d17, d26 + vmlsl.s16 q11, d19, d24 + vqrshrun.s16 d14, q7, #0x5 + vld1.32 d28, [r6], r8 @load row 1 from temp buffer + vaddl.u8 q9, d6, d0 + vqrshrun.s32 d16, q15, #10 + vqrshrun.s16 d15, q14, #0x5 + vqrshrun.s32 d17, q11, #10 + vmov d12, d25 + vmov d25, d24 + + vqmovn.u16 d13, q8 + vrhadd.u8 q6, q6, q7 + vst1.32 d12[0], [r1], r3 @ store row 0 + vst1.32 d13[0], [r1], r3 @store row 1 + + subs r4, r4, #2 + subne r0, r0 , r2, lsl #2 + subne r0, r0, r2 + + beq end_func @ Branch if height==4 + b loop_4 @ Loop if height==8 + +end_func: + vldmia sp!, {d8-d15} @ Restore neon registers that were saved + ldmfd sp!, {r4-r12, pc} @Restoring registers from stack + + diff --git a/dependencies/ih264d/common/arm/ih264_inter_pred_luma_horz_qpel_vert_qpel_a9q.s b/dependencies/ih264d/common/arm/ih264_inter_pred_luma_horz_qpel_vert_qpel_a9q.s new file mode 100644 index 00000000..cfe03a09 --- /dev/null +++ b/dependencies/ih264d/common/arm/ih264_inter_pred_luma_horz_qpel_vert_qpel_a9q.s @@ -0,0 +1,352 @@ +@/****************************************************************************** +@ * +@ * Copyright (C) 2015 The Android Open Source Project +@ * +@ * Licensed under the Apache License, Version 2.0 (the "License"); +@ * you may not use this file except in compliance with the License. +@ * You may obtain a copy of the License at: +@ * +@ * http://www.apache.org/licenses/LICENSE-2.0 +@ * +@ * Unless required by applicable law or agreed to in writing, software +@ * distributed under the License is distributed on an "AS IS" BASIS, +@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@ * See the License for the specific language governing permissions and +@ * limitations under the License. +@ * +@ ***************************************************************************** +@ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +@*/ +@** +@****************************************************************************** +@* @file +@* ih264_inter_pred_luma_horz_qpel_vert_qpel_a9q.s +@* +@* @brief +@* Contains function definitions for inter prediction interpolation. +@* +@* @author +@* Mohit +@* +@* @par List of Functions: +@* +@* - ih264_inter_pred_luma_horz_qpel_vert_qpel_a9q() +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* + +@* All the functions here are replicated from ih264_inter_pred_filters.c +@ + +@******************************************************************************* +@* +@* @brief +@* This function implements two six tap filters. It +@* applies the six tap filter in the horizontal direction on the +@* predictor values, then applies the same filter in the +@* vertical direction on the predictor values. It then averages these +@* two outputs to obtain quarter pel values in horizontal and vertical direction. +@* The six tap filtering operation is described in sec 8.4.2.2.1 titled +@* "Luma sample interpolation process" +@* +@* @par Description: +@* This function is called to obtain pixels lying at the following +@* location (1/4,1/4) or (3/4,1/4) or (1/4,3/4) or (3/4,3/4). +@* The function interpolates the predictors first in the horizontal direction +@* and then in the vertical direction, and then averages these two +@* values. +@* +@* @param[in] pu1_src +@* UWORD8 pointer to the source +@* +@* @param[out] pu1_dst +@* UWORD8 pointer to the destination +@* +@* @param[in] src_strd +@* integer source stride +@* +@* @param[in] dst_strd +@* integer destination stride +@* +@* @param[in] ht +@* integer height of the array +@* +@* @param[in] wd +@* integer width of the array +@* +@* @param[in] pu1_tmp: temporary buffer +@* +@* @param[in] dydx: x and y reference offset for qpel calculations +@* +@* @returns +@* +@* @remarks +@* None +@* +@******************************************************************************* +@*; + +@void ih264_inter_pred_luma_horz_qpel_vert_qpel(UWORD8 *pu1_src, +@ UWORD8 *pu1_dst, +@ WORD32 src_strd,, +@ WORD32 dst_strd, +@ WORD32 ht, +@ WORD32 wd, +@ UWORD8* pu1_tmp, +@ UWORD32 dydx) + +@**************Variables Vs Registers***************************************** +@ r0 => *pu1_src +@ r1 => *pu1_dst +@ r2 => src_strd +@ r3 => dst_strd +@ r4 => ht +@ r5 => wd +@ r6 => dydx + +.text +.p2align 2 + + .global ih264_inter_pred_luma_horz_qpel_vert_qpel_a9q + +ih264_inter_pred_luma_horz_qpel_vert_qpel_a9q: + + stmfd sp!, {r4-r12, r14} @store register values to stack + vstmdb sp!, {d8-d15} @push neon registers to stack + ldr r4, [sp, #104] @ loads ht + ldr r5, [sp, #108] @ loads wd + ldr r6, [sp, #116] @dydx + and r7, r6, #3 + add r7, r0, r7, lsr #1 @pu1_pred_vert = pu1_src + (x_offset>>1) + + and r6, r6, #12 @Finds y-offset + lsr r6, r6, #3 @dydx>>3 + mul r6, r2, r6 + add r6, r0, r6 @pu1_pred_horz = pu1_src + (y_offset>>1)*src_strd + sub r7, r7, r2, lsl #1 @pu1_pred_vert-2*src_strd + sub r6, r6, #2 @pu1_pred_horz-2 + vmov.u8 d30, #20 @ Filter coeff 20 + vmov.u8 d31, #5 @ Filter coeff 5 + + subs r12, r5, #4 @if wd=4 branch to loop_4 + beq loop_4 + subs r12, r5, #8 @if wd=8 branch to loop_8 + beq loop_8 + +loop_16: + vld1.32 {q0}, [r7], r2 @ Vector load from src[0_0] + vld1.32 {q1}, [r7], r2 @ Vector load from src[1_0] + vld1.32 {q2}, [r7], r2 @ Vector load from src[2_0] + vld1.32 {q3}, [r7], r2 @ Vector load from src[3_0] + vld1.32 {q4}, [r7], r2 @ Vector load from src[4_0] + add r11, r6, #8 + vld1.32 {q5}, [r7], r2 @ Vector load from src[5_0] + vld1.32 {q9}, [r6], r2 @ horz row0, col 0 + vaddl.u8 q12, d0, d10 + vmlal.u8 q12, d4, d30 + vmlal.u8 q12, d6, d30 + vmlsl.u8 q12, d2, d31 + vmlsl.u8 q12, d8, d31 + vext.8 d23, d18, d19, #5 + vext.8 d20, d18, d19, #2 + vext.8 d21, d18, d19, #3 + vext.8 d22, d18, d19, #4 + vext.8 d19, d18, d19, #1 + vqrshrun.s16 d26, q12, #5 + vaddl.u8 q14, d18, d23 + vmlal.u8 q14, d20, d30 + vmlal.u8 q14, d21, d30 + vmlsl.u8 q14, d19, d31 + vmlsl.u8 q14, d22, d31 + vld1.32 {q9}, [r11], r2 @ horz row 0, col 1 + vaddl.u8 q12, d1, d11 + vmlal.u8 q12, d5, d30 + vmlal.u8 q12, d7, d30 + vmlsl.u8 q12, d3, d31 + vmlsl.u8 q12, d9, d31 + vqrshrun.s16 d28, q14, #5 + vext.8 d23, d18, d19, #5 + vext.8 d20, d18, d19, #2 + vext.8 d21, d18, d19, #3 + vext.8 d22, d18, d19, #4 + vext.8 d19, d18, d19, #1 + vqrshrun.s16 d27, q12, #5 + vld1.32 {q6}, [r7], r2 @ src[6_0] + + vaddl.u8 q12, d18, d23 + vmlal.u8 q12, d20, d30 + vmlal.u8 q12, d21, d30 + vmlsl.u8 q12, d19, d31 + vmlsl.u8 q12, d22, d31 + + vaddl.u8 q8, d2, d12 + vmlal.u8 q8, d6, d30 + vmlal.u8 q8, d8, d30 + vmlsl.u8 q8, d4, d31 + vmlsl.u8 q8, d10, d31 + vqrshrun.s16 d29, q12, #5 + vld1.32 {q9}, [r6], r2 @ horz row 1, col 0 + + vaddl.u8 q12, d3, d13 + vmlal.u8 q12, d7, d30 + vmlal.u8 q12, d9, d30 + vmlsl.u8 q12, d5, d31 + vmlsl.u8 q12, d11, d31 + vrhadd.u8 q14, q14, q13 + vqrshrun.s16 d26, q8, #5 + vext.8 d23, d18, d19, #5 + vext.8 d20, d18, d19, #2 + vext.8 d21, d18, d19, #3 + vext.8 d22, d18, d19, #4 + vst1.32 {q14}, [r1], r3 @ store row 0 + vext.8 d19, d18, d19, #1 + vqrshrun.s16 d27, q12, #5 + + vaddl.u8 q14, d18, d23 + vmlal.u8 q14, d20, d30 + vmlal.u8 q14, d21, d30 + vmlsl.u8 q14, d19, d31 + vmlsl.u8 q14, d22, d31 + + vld1.32 {q9}, [r11], r2 @ horz row 1, col 1 + + vext.8 d23, d18, d19, #5 + vext.8 d20, d18, d19, #2 + vext.8 d21, d18, d19, #3 + vext.8 d22, d18, d19, #4 + vext.8 d19, d18, d19, #1 + + vqrshrun.s16 d28, q14, #5 + vaddl.u8 q12, d18, d23 + vmlal.u8 q12, d20, d30 + vmlal.u8 q12, d21, d30 + vmlsl.u8 q12, d19, d31 + vmlsl.u8 q12, d22, d31 + + vqrshrun.s16 d29, q12, #5 + vrhadd.u8 q14, q14, q13 + vst1.32 {q14}, [r1], r3 @ store row 1 + + subs r4, r4, #2 @ 2 rows processed, decrement by 2 + subne r7, r7 , r2, lsl #2 + subne r7, r7, r2 + beq end_func @ Branch if height==4 + + b loop_16 @ looping if height = 8 or 16 + + +loop_8: + vld1.32 d0, [r7], r2 @ Vector load from src[0_0] + vld1.32 d1, [r7], r2 @ Vector load from src[1_0] + vld1.32 d2, [r7], r2 @ Vector load from src[2_0] + vld1.32 d3, [r7], r2 @ Vector load from src[3_0] + vld1.32 d4, [r7], r2 @ Vector load from src[4_0] + vld1.32 d5, [r7], r2 @ Vector load from src[5_0] + vaddl.u8 q5, d0, d5 + vmlal.u8 q5, d2, d30 + vmlal.u8 q5, d3, d30 + vmlsl.u8 q5, d1, d31 + vmlsl.u8 q5, d4, d31 + vld1.32 {q6}, [r6], r2 @horz row 0 + vext.8 d17, d12, d13, #5 + vext.8 d14, d12, d13, #2 + vext.8 d15, d12, d13, #3 + vext.8 d16, d12, d13, #4 + vext.8 d13, d12, d13, #1 + vqrshrun.s16 d26, q5, #5 + vld1.32 d6, [r7], r2 @ src[6_0] + vaddl.u8 q5, d12, d17 + vmlal.u8 q5, d14, d30 + vmlal.u8 q5, d15, d30 + vmlsl.u8 q5, d13, d31 + vmlsl.u8 q5, d16, d31 + vld1.32 {q6}, [r6], r2 @ horz row 1 + vaddl.u8 q9, d1, d6 + vmlal.u8 q9, d3, d30 + vmlal.u8 q9, d4, d30 + vmlsl.u8 q9, d2, d31 + vmlsl.u8 q9, d5, d31 + vqrshrun.s16 d28, q5, #5 + vext.8 d17, d12, d13, #5 + vext.8 d14, d12, d13, #2 + vext.8 d15, d12, d13, #3 + vext.8 d16, d12, d13, #4 + vext.8 d13, d12, d13, #1 + vqrshrun.s16 d27, q9, #5 + vaddl.u8 q5, d12, d17 + vmlal.u8 q5, d14, d30 + vmlal.u8 q5, d15, d30 + vmlsl.u8 q5, d13, d31 + vmlsl.u8 q5, d16, d31 + vqrshrun.s16 d29, q5, #5 + vrhadd.u8 q13, q13, q14 + vst1.32 d26, [r1], r3 + vst1.32 d27, [r1], r3 + + subs r4, r4, #2 @ 2 rows processed, decrement by 2 + subne r7, r7 , r2, lsl #2 + subne r7, r7, r2 + beq end_func @ Branch if height==4 + b loop_8 @looping if height == 8 or 16 + +loop_4: + vld1.32 d0[0], [r7], r2 @ Vector load from src[0_0] + vld1.32 d1[0], [r7], r2 @ Vector load from src[1_0] + vld1.32 d2[0], [r7], r2 @ Vector load from src[2_0] + vld1.32 d3[0], [r7], r2 @ Vector load from src[3_0] + vld1.32 d4[0], [r7], r2 @ Vector load from src[4_0] + vld1.32 d5[0], [r7], r2 @ Vector load from src[5_0] + vaddl.u8 q5, d0, d5 + vmlal.u8 q5, d2, d30 + vmlal.u8 q5, d3, d30 + vmlsl.u8 q5, d1, d31 + vmlsl.u8 q5, d4, d31 + vld1.32 {q6}, [r6], r2 @load for horz filter row 0 + vext.8 d17, d12, d13, #5 + vext.8 d14, d12, d13, #2 + vext.8 d15, d12, d13, #3 + vext.8 d16, d12, d13, #4 + vext.8 d13, d12, d13, #1 + vqrshrun.s16 d26, q5, #5 + vld1.32 d6[0], [r7], r2 @ Vector load from src[6_0] + vaddl.u8 q5, d12, d17 + vmlal.u8 q5, d14, d30 + vmlal.u8 q5, d15, d30 + vmlsl.u8 q5, d13, d31 + vmlsl.u8 q5, d16, d31 + vld1.32 {q6}, [r6], r2 @horz row 1 + vaddl.u8 q9, d1, d6 + vmlal.u8 q9, d3, d30 + vmlal.u8 q9, d4, d30 + vmlsl.u8 q9, d2, d31 + vmlsl.u8 q9, d5, d31 + vqrshrun.s16 d28, q5, #5 + vext.8 d17, d12, d13, #5 + vext.8 d14, d12, d13, #2 + vext.8 d15, d12, d13, #3 + vext.8 d16, d12, d13, #4 + vext.8 d13, d12, d13, #1 + vqrshrun.s16 d27, q9, #5 + vaddl.u8 q5, d12, d17 + vmlal.u8 q5, d14, d30 + vmlal.u8 q5, d15, d30 + vmlsl.u8 q5, d13, d31 + vmlsl.u8 q5, d16, d31 + vqrshrun.s16 d29, q5, #5 + vrhadd.u8 q13, q13, q14 + vst1.32 d26[0], [r1], r3 + vst1.32 d27[0], [r1], r3 + + subs r4, r4, #2 @ 2 rows processed, decrement by 2 + subne r7, r7 , r2, lsl #2 + subne r7, r7, r2 + beq end_func @ Branch if height==4 + b loop_4 @ Loop if height==8 +end_func: + vldmia sp!, {d8-d15} @ Restore neon registers that were saved + ldmfd sp!, {r4-r12, pc} @Restoring registers from stack + + diff --git a/dependencies/ih264d/common/arm/ih264_inter_pred_luma_vert_qpel_a9q.s b/dependencies/ih264d/common/arm/ih264_inter_pred_luma_vert_qpel_a9q.s new file mode 100644 index 00000000..e2c68ef1 --- /dev/null +++ b/dependencies/ih264d/common/arm/ih264_inter_pred_luma_vert_qpel_a9q.s @@ -0,0 +1,328 @@ +@/****************************************************************************** +@ * +@ * Copyright (C) 2015 The Android Open Source Project +@ * +@ * Licensed under the Apache License, Version 2.0 (the "License"); +@ * you may not use this file except in compliance with the License. +@ * You may obtain a copy of the License at: +@ * +@ * http://www.apache.org/licenses/LICENSE-2.0 +@ * +@ * Unless required by applicable law or agreed to in writing, software +@ * distributed under the License is distributed on an "AS IS" BASIS, +@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@ * See the License for the specific language governing permissions and +@ * limitations under the License. +@ * +@ ***************************************************************************** +@ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +@*/ +@** +@****************************************************************************** +@* @file +@* ih264_inter_pred_luma_vert_qpel_a9q.s +@* +@* @brief +@* Contains function definitions for inter prediction vertical quarter pel interpolation. +@* +@* @author +@* Mohit +@* +@* @par List of Functions: +@* +@* - ih264_inter_pred_luma_vert_qpel_a9q() +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* + +@* All the functions here are replicated from ih264_inter_pred_filters.c +@ + +@******************************************************************************* +@* +@* @brief +@* Quarter pel interprediction luma filter for vertical input +@* +@* @par Description: +@* Applies a 6 tap horizontal filter .The output is clipped to 8 bits +@* sec 8.4.2.2.1 titled "Luma sample interpolation process" +@* +@* @param[in] pu1_src +@* UWORD8 pointer to the source +@* +@* @param[out] pu1_dst +@* UWORD8 pointer to the destination +@* +@* @param[in] src_strd +@* integer source stride +@* +@* @param[in] dst_strd +@* integer destination stride +@* +@* @param[in] ht +@* integer height of the array +@* +@* @param[in] wd +@* integer width of the array +@* +@* @param[in] pu1_tmp: temporary buffer: UNUSED in this function +@* +@* @param[in] dydx: x and y reference offset for qpel calculations. +@* @returns +@* +@ @remarks +@* None +@* +@******************************************************************************* +@* + +@void ih264_inter_pred_luma_vert ( +@ UWORD8 *pu1_src, +@ UWORD8 *pu1_dst, +@ WORD32 src_strd, +@ WORD32 dst_strd, +@ WORD32 ht, +@ WORD32 wd, +@ UWORD8* pu1_tmp, +@ UWORD32 dydx) + +@**************Variables Vs Registers***************************************** +@ r0 => *pu1_src +@ r1 => *pu1_dst +@ r2 => src_strd +@ r3 => dst_strd +@ r5 => ht +@ r6 => wd +@ r7 => dydx + +.text +.p2align 2 + + .global ih264_inter_pred_luma_vert_qpel_a9q + +ih264_inter_pred_luma_vert_qpel_a9q: + + stmfd sp!, {r4-r12, r14} @store register values to stack + vstmdb sp!, {d8-d15} @push neon registers to stack + ldr r5, [sp, #104] @Loads ht + + ldr r6, [sp, #108] @Loads wd + ldr r7, [sp, #116] @Loads dydx + and r7, r7, #12 @Finds y-offset + lsr r7, r7, #3 @dydx>>3 + mul r7, r2, r7 + add r7, r0, r7 @pu1_src + (y_offset>>1)*src_strd + vmov.u16 q11, #20 @ Filter coeff 0x14 into Q11 + sub r0, r0, r2, lsl #1 @pu1_src-2*src_strd + subs r12, r6, #8 @if wd=8 branch to loop_8 + vmov.u16 q12, #5 @ Filter coeff 0x5 into Q12 + beq loop_8 + + subs r12, r6, #4 @if wd=4 branch to loop_4 + beq loop_4 + +loop_16: @when wd=16 + + vld1.u32 {q0}, [r0], r2 @ Vector load from src[0_0] + vld1.u32 {q1}, [r0], r2 @ Vector load from src[1_0] + vld1.u32 {q2}, [r0], r2 @ Vector load from src[2_0] + vld1.u32 {q3}, [r0], r2 @ Vector load from src[3_0] + vld1.u32 {q4}, [r0], r2 @ Vector load from src[4_0] + vaddl.u8 q6, d4, d6 @ temp1 = src[2_0] + src[3_0] + vld1.u32 {q5}, [r0], r2 @ Vector load from src[5_0] + vaddl.u8 q7, d0, d10 @ temp = src[0_0] + src[5_0] + vaddl.u8 q8, d2, d8 @ temp2 = src[1_0] + src[4_0] + vmla.u16 q7, q6, q11 @ temp += temp1 * 20 + vaddl.u8 q10, d1, d11 @ temp4 = src[0_8] + src[5_8] + vaddl.u8 q9, d5, d7 @ temp3 = src[2_8] + src[3_8] + vmla.u16 q10, q9, q11 @ temp4 += temp3 * 20 + vld1.u32 {q0}, [r0], r2 + vaddl.u8 q13, d3, d9 @ temp5 = src[1_8] + src[4_8] + vaddl.u8 q6, d6, d8 + vmls.u16 q7, q8, q12 @ temp -= temp2 * 5 + vaddl.u8 q8, d2, d0 + vaddl.u8 q9, d4, d10 + vmla.u16 q8, q6, q11 + vmls.u16 q10, q13, q12 @ temp4 -= temp5 * 5 + vaddl.u8 q13, d5, d11 + vaddl.u8 q6, d7, d9 + vqrshrun.s16 d30, q7, #5 @ dst[0_0] = CLIP_U8((temp +16) >> 5) + vaddl.u8 q7, d3, d1 + vld1.u32 {q1}, [r0], r2 + vmla.u16 q7, q6, q11 + vmls.u16 q8, q9, q12 + vqrshrun.s16 d31, q10, #5 @ dst[0_8] = CLIP_U8((temp4 +16) >> 5) + vld1.u32 {q10}, [r7], r2 @ Load for interpolation row 0 + vrhadd.u8 q15, q10, q15 @ Interpolation to obtain qpel value + vaddl.u8 q9, d4, d2 + vaddl.u8 q6, d8, d10 + + vst1.u32 {q15}, [r1], r3 @ Vector store to dst[0_0] + vmla.u16 q9, q6, q11 + vaddl.u8 q10, d6, d0 + vmls.u16 q7, q13, q12 + vqrshrun.s16 d30, q8, #5 + vaddl.u8 q6, d9, d11 + vaddl.u8 q8, d5, d3 + vaddl.u8 q13, d7, d1 + vmla.u16 q8, q6, q11 + vmls.u16 q9, q10, q12 + vld1.u32 {q2}, [r0], r2 + + vqrshrun.s16 d31, q7, #5 + vld1.u32 {q7}, [r7], r2 @ Load for interpolation row 1 + vaddl.u8 q6, d10, d0 + vrhadd.u8 q15, q7, q15 @ Interpolation to obtain qpel value + vaddl.u8 q7, d6, d4 + vaddl.u8 q10, d8, d2 + vmla.u16 q7, q6, q11 + vmls.u16 q8, q13, q12 + vst1.u32 {q15}, [r1], r3 @store row 1 + vqrshrun.s16 d30, q9, #5 + vaddl.u8 q9, d7, d5 + vaddl.u8 q6, d11, d1 + vmla.u16 q9, q6, q11 + vaddl.u8 q13, d9, d3 + vmls.u16 q7, q10, q12 + vqrshrun.s16 d31, q8, #5 + vld1.u32 {q8}, [r7], r2 @ Load for interpolation row 2 + vmls.u16 q9, q13, q12 + vrhadd.u8 q15, q8, q15 @ Interpolation to obtain qpel value + vaddl.u8 q6, d0, d2 @ temp1 = src[2_0] + src[3_0] + vst1.u32 {q15}, [r1], r3 @store row 2 + vaddl.u8 q8, d10, d4 @ temp2 = src[1_0] + src[4_0] + vaddl.u8 q10, d9, d7 @ temp4 = src[0_8] + src[5_8] + vqrshrun.s16 d30, q7, #5 + vaddl.u8 q13, d5, d11 @ temp5 = src[1_8] + src[4_8] + vaddl.u8 q7, d8, d6 @ temp = src[0_0] + src[5_0] + vqrshrun.s16 d31, q9, #5 + vld1.u32 {q9}, [r7], r2 @ Load for interpolation row 3 + vmla.u16 q7, q6, q11 @ temp += temp1 * 20 + vrhadd.u8 q15, q9, q15 @ Interpolation to obtain qpel value + vaddl.u8 q9, d1, d3 @ temp3 = src[2_8] + src[3_8] + vst1.u32 {q15}, [r1], r3 @store row 3 + subs r5, r5, #4 @ 4 rows processed, decrement by 4 + subne r0, r0 , r2, lsl #2 + subne r0, r0, r2 + beq end_func @ Branch if height==4 + + b loop_16 @ looping if height = 8 or 16 + + +loop_8: + + @ Processing row0 and row1 + vld1.u32 d0, [r0], r2 @ Vector load from src[0_0] + vld1.u32 d1, [r0], r2 @ Vector load from src[1_0] + vld1.u32 d2, [r0], r2 @ Vector load from src[2_0] + vld1.u32 d3, [r0], r2 @ Vector load from src[3_0] + vld1.u32 d4, [r0], r2 @ Vector load from src[4_0] + vld1.u32 d5, [r0], r2 @ Vector load from src[5_0] + + vaddl.u8 q3, d2, d3 @ temp1 = src[2_0] + src[3_0] + vaddl.u8 q4, d0, d5 @ temp = src[0_0] + src[5_0] + vaddl.u8 q5, d1, d4 @ temp2 = src[1_0] + src[4_0] + vmla.u16 q4, q3, q11 @ temp += temp1 * 20 + vld1.u32 d6, [r0], r2 + vaddl.u8 q7, d3, d4 + vaddl.u8 q8, d1, d6 + vaddl.u8 q9, d2, d5 + vmls.u16 q4, q5, q12 @ temp -= temp2 * 5 + vmla.u16 q8, q7, q11 + vld1.u32 d7, [r0], r2 + vaddl.u8 q10, d4, d5 + vaddl.u8 q6, d2, d7 + vaddl.u8 q5, d3, d6 + vmls.u16 q8, q9, q12 + vqrshrun.s16 d26, q4, #5 @ dst[0_0] = CLIP_U8( (temp + 16) >> 5) + vmla.u16 q6, q10, q11 + vld1.32 d8, [r7], r2 @Load value for interpolation (row0) + vld1.32 d9, [r7], r2 @Load value for interpolation (row1) + vld1.u32 d0, [r0], r2 + vaddl.u8 q7, d5, d6 + vqrshrun.s16 d27, q8, #5 + vrhadd.u8 q13, q4, q13 @ Interpolation step for qpel calculation + vaddl.u8 q10, d3, d0 + vmls.u16 q6, q5, q12 + vst1.u32 d26, [r1], r3 @ Vector store to dst[0_0] + vaddl.u8 q9, d4, d7 + vmla.u16 q10, q7, q11 + vst1.u32 d27, [r1], r3 @ Vector store to dst[1_0] + vqrshrun.s16 d28, q6, #5 + vmls.u16 q10, q9, q12 + vld1.32 d12, [r7], r2 @Load value for interpolation (row2) + vld1.32 d13, [r7], r2 @Load value for interpolation (row3) + vqrshrun.s16 d29, q10, #5 + subs r9, r5, #4 + vrhadd.u8 q14, q6, q14 + vst1.u32 d28, [r1], r3 @store row 2 + vst1.u32 d29, [r1], r3 @store row 3 + + subs r5, r5, #4 @ 4 rows processed, decrement by 4 + subne r0, r0 , r2, lsl #2 + subne r0, r0, r2 + beq end_func @ Branch if height==4 + b loop_8 @looping if height == 8 or 16 + +loop_4: +@ Processing row0 and row1 + + vld1.u32 d0[0], [r0], r2 @ Vector load from src[0_0] + vld1.u32 d1[0], [r0], r2 @ Vector load from src[1_0] + vld1.u32 d2[0], [r0], r2 @ Vector load from src[2_0] + vld1.u32 d3[0], [r0], r2 @ Vector load from src[3_0] + vld1.u32 d4[0], [r0], r2 @ Vector load from src[4_0] + vld1.u32 d5[0], [r0], r2 @ Vector load from src[5_0] + + vaddl.u8 q3, d2, d3 @ temp1 = src[2_0] + src[3_0] + vaddl.u8 q4, d0, d5 @ temp = src[0_0] + src[5_0] + vaddl.u8 q5, d1, d4 @ temp2 = src[1_0] + src[4_0] + vmla.u16 q4, q3, q11 @ temp += temp1 * 20 + vld1.u32 d6, [r0], r2 + vaddl.u8 q7, d3, d4 + vaddl.u8 q8, d1, d6 + vaddl.u8 q9, d2, d5 + vmls.u16 q4, q5, q12 @ temp -= temp2 * 5 + vld1.u32 d7[0], [r0], r2 + vmla.u16 q8, q7, q11 + vaddl.u8 q10, d4, d5 + vaddl.u8 q6, d2, d7 + vaddl.u8 q5, d3, d6 + vmls.u16 q8, q9, q12 + vqrshrun.s16 d26, q4, #5 @ dst[0_0] = CLIP_U8( (temp + 16) >> 5) + vld1.u32 d8[0], [r7], r2 @Load value for interpolation - row 0 + vld1.u32 d9[0], [r7], r2 @Load value for interpolation - row 1 + vmla.u16 q6, q10, q11 + vld1.u32 d0[0], [r0], r2 + vaddl.u8 q7, d5, d6 + vqrshrun.s16 d27, q8, #5 + vaddl.u8 q10, d3, d0 + vrhadd.u8 q13, q13, q4 @Interpolation step for qpel calculation + vmls.u16 q6, q5, q12 + vst1.u32 d26[0], [r1], r3 @ Vector store to dst[0_0] + vaddl.u8 q9, d4, d7 + vmla.u16 q10, q7, q11 + vst1.u32 d27[0], [r1], r3 @ store row 1 + vqrshrun.s16 d28, q6, #5 + vld1.u32 d12[0], [r7], r2 @Load value for interpolation - row 2 + vld1.u32 d13[0], [r7], r2 @Load value for interpolation - row 3 + + vmls.u16 q10, q9, q12 + vqrshrun.s16 d29, q10, #5 + vrhadd.u8 q14, q6, q14 @Interpolation step for qpel calculation + vst1.u32 d28[0], [r1], r3 @store row 2 + vst1.u32 d29[0], [r1], r3 @store row 3 + + subs r5, r5, #8 + subeq r0, r0, r2, lsl #2 + subeq r0, r0, r2 + beq loop_4 @ Loop if height==8 + +end_func: + vldmia sp!, {d8-d15} @ Restore neon registers that were saved + ldmfd sp!, {r4-r12, pc} @Restoring registers from stack + + diff --git a/dependencies/ih264d/common/arm/ih264_intra_pred_chroma_a9q.s b/dependencies/ih264d/common/arm/ih264_intra_pred_chroma_a9q.s new file mode 100644 index 00000000..ccd5c0d6 --- /dev/null +++ b/dependencies/ih264d/common/arm/ih264_intra_pred_chroma_a9q.s @@ -0,0 +1,543 @@ +@/****************************************************************************** +@ * +@ * Copyright (C) 2015 The Android Open Source Project +@ * +@ * Licensed under the Apache License, Version 2.0 (the "License"); +@ * you may not use this file except in compliance with the License. +@ * You may obtain a copy of the License at: +@ * +@ * http://www.apache.org/licenses/LICENSE-2.0 +@ * +@ * Unless required by applicable law or agreed to in writing, software +@ * distributed under the License is distributed on an "AS IS" BASIS, +@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@ * See the License for the specific language governing permissions and +@ * limitations under the License. +@ * +@ ***************************************************************************** +@ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +@*/ +@** +@****************************************************************************** +@* @file +@* ih264_intra_pred_chroma_a9q.s +@* +@* @brief +@* Contains function definitions for intra chroma prediction . +@* +@* @author +@* Ittiam +@* +@* @par List of Functions: +@* +@* - ih264_intra_pred_chroma_mode_horz_a9q() +@* - ih264_intra_pred_chroma_8x8_mode_vert_a9q() +@* - ih264_intra_pred_chroma_mode_dc_a9q() +@* - ih264_intra_pred_chroma_mode_plane_a9q() +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* + +@* All the functions here are replicated from ih264_chroma_intra_pred_filters.c +@ + +.text +.p2align 2 + + .extern ih264_gai1_intrapred_chroma_plane_coeffs1 +.hidden ih264_gai1_intrapred_chroma_plane_coeffs1 + .extern ih264_gai1_intrapred_chroma_plane_coeffs2 +.hidden ih264_gai1_intrapred_chroma_plane_coeffs2 +scratch_chroma_intrapred_addr1: + .long ih264_gai1_intrapred_chroma_plane_coeffs1 - scrlblc1 - 8 + +scratch_intrapred_chroma_plane_addr1: + .long ih264_gai1_intrapred_chroma_plane_coeffs2 - scrlblc2 - 8 +@** +@******************************************************************************* +@* +@*ih264_intra_pred_chroma_8x8_mode_dc +@* +@* @brief +@* Perform Intra prediction for chroma_8x8 mode:DC +@* +@* @par Description: +@* Perform Intra prediction for chroma_8x8 mode:DC ,described in sec 8.3.4.1 +@* +@* @param[in] pu1_src +@* UWORD8 pointer to the source containing alternate U and V samples +@* +@* @param[out] pu1_dst +@* UWORD8 pointer to the destination with alternate U and V samples +@* +@* @param[in] src_strd +@* integer source stride +@* +@* @param[in] dst_strd +@* integer destination stride +@* +@** @param[in] ui_neighboravailability +@* availability of neighbouring pixels +@* +@* @returns +@* +@* @remarks +@* None +@* +@******************************************************************************* +@void ih264_intra_pred_chroma_8x8_mode_dc(UWORD8 *pu1_src, +@ UWORD8 *pu1_dst, +@ WORD32 src_strd, +@ WORD32 dst_strd, +@ WORD32 ui_neighboravailability) + +@**************Variables Vs Registers***************************************** +@ r0 => *pu1_src +@ r1 => *pu1_dst +@ r2 => src_strd +@ r3 => dst_strd +@ r4 => ui_neighboravailability + + .global ih264_intra_pred_chroma_8x8_mode_dc_a9q + +ih264_intra_pred_chroma_8x8_mode_dc_a9q: + + stmfd sp!, {r4, r14} @store register values to stack + ldr r4, [sp, #8] @r4 => ui_neighboravailability + vpush {d8-d15} + + ands r2, r4, #0x01 @CHECKING IF LEFT_AVAILABLE ELSE BRANCHING TO ONLY TOP AVAILABLE + beq top_available + ands r2, r4, #0x04 @CHECKING IF TOP_AVAILABLE ELSE BRANCHING TO ONLY LEFT AVAILABLE + beq left_available + + vld1.u8 {q0}, [r0] @BOTH LEFT AND TOP AVAILABLE + add r0, r0, #18 + vld1.u8 {q1}, [r0] + vaddl.u8 q2, d1, d2 + vaddl.u8 q3, d0, d3 + vmovl.u8 q1, d3 + vmovl.u8 q0, d0 + + vadd.u16 d12, d4, d5 + vadd.u16 d13, d2, d3 + vadd.u16 d15, d6, d7 + vadd.u16 d14, d0, d1 + + vpadd.u32 d12, d12, d15 + vpadd.u32 d14, d13, d14 + vqrshrun.s16 d12, q6, #3 + vqrshrun.s16 d14, q7, #2 + vdup.u16 d8, d12[0] + vdup.u16 d9, d14[0] + vdup.u16 d10, d14[1] + vdup.u16 d11, d12[1] + b str_pred + +top_available: @ONLY TOP AVAILABLE + ands r2, r4, #0x04 @CHECKING TOP AVAILABILTY OR ELSE BRANCH TO NONE AVAILABLE + beq none_available + + add r0, r0, #18 + vld1.u8 {q0}, [r0] + vmovl.u8 q1, d0 + vmovl.u8 q2, d1 + vadd.u16 d0, d2, d3 + vadd.u16 d1, d4, d5 + vpaddl.u32 q0, q0 + vqrshrun.s16 d0, q0, #2 + vdup.u16 d8, d0[0] + vdup.u16 d9, d0[2] + vmov q5, q4 + b str_pred + +left_available: @ONLY LEFT AVAILABLE + vld1.u8 {q0}, [r0] + vmovl.u8 q1, d0 + vmovl.u8 q2, d1 + vadd.u16 d0, d2, d3 + vadd.u16 d1, d4, d5 + vpaddl.u32 q0, q0 + vqrshrun.s16 d0, q0, #2 + vdup.u16 q5, d0[0] + vdup.u16 q4, d0[2] + b str_pred + +none_available: @NONE AVAILABLE + vmov.u8 q4, #128 + vmov.u8 q5, #128 + +str_pred: + vst1.8 {q4}, [r1], r3 + vst1.8 {q4}, [r1], r3 + vst1.8 {q4}, [r1], r3 + vst1.8 {q4}, [r1], r3 + vst1.8 {q5}, [r1], r3 + vst1.8 {q5}, [r1], r3 + vst1.8 {q5}, [r1], r3 + vst1.8 {q5}, [r1], r3 + + vpop {d8-d15} + ldmfd sp!, {r4, pc} @Restoring registers from stack + + + +@****************************************************************************** + + +@** +@******************************************************************************* +@* +@*ih264_intra_pred_chroma_8x8_mode_horz +@* +@* @brief +@* Perform Intra prediction for chroma_8x8 mode:Horizontal +@* +@* @par Description: +@* Perform Intra prediction for chroma_8x8 mode:Horizontal ,described in sec 8.3.4.2 +@* +@* @param[in] pu1_src +@* UWORD8 pointer to the source containing alternate U and V samples +@* +@* @param[out] pu1_dst +@* UWORD8 pointer to the destination with alternate U and V samples +@* +@* @param[in] src_strd +@* integer source stride +@* +@* @param[in] dst_strd +@* integer destination stride +@* +@* @param[in] ui_neighboravailability +@* availability of neighbouring pixels(Not used in this function) +@* +@* @returns +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* +@void ih264_intra_pred_chroma_8x8_mode_horz(UWORD8 *pu1_src, +@ UWORD8 *pu1_dst, +@ WORD32 src_strd, +@ WORD32 dst_strd, +@ WORD32 ui_neighboravailability) +@**************Variables Vs Registers***************************************** +@ r0 => *pu1_src +@ r1 => *pu1_dst +@ r2 => src_strd +@ r3 => dst_strd +@ r4 => ui_neighboravailability + + + .global ih264_intra_pred_chroma_8x8_mode_horz_a9q + +ih264_intra_pred_chroma_8x8_mode_horz_a9q: + + stmfd sp!, {r14} @store register values to stack + + vld1.u8 {q0}, [r0] + mov r2, #6 + + vdup.u16 q1, d1[3] + vdup.u16 q2, d1[2] + vst1.8 {q1}, [r1], r3 + +loop_8x8_horz: + vext.8 q0, q0, q0, #12 + vst1.8 {q2}, [r1], r3 + vdup.u16 q1, d1[3] + subs r2, #2 + vdup.u16 q2, d1[2] + vst1.8 {q1}, [r1], r3 + bne loop_8x8_horz + + vext.8 q0, q0, q0, #12 + vst1.8 {q2}, [r1], r3 + + ldmfd sp!, {pc} @restoring registers from stack + + + + +@** +@******************************************************************************* +@* +@*ih264_intra_pred_chroma_8x8_mode_vert +@* +@* @brief +@* Perform Intra prediction for chroma_8x8 mode:vertical +@* +@* @par Description: +@*Perform Intra prediction for chroma_8x8 mode:vertical ,described in sec 8.3.4.3 +@* +@* @param[in] pu1_src +@* UWORD8 pointer to the source containing alternate U and V samples +@* +@* @param[out] pu1_dst +@* UWORD8 pointer to the destination with alternate U and V samples +@* +@* @param[in] src_strd +@* integer source stride +@* +@* @param[in] dst_strd +@* integer destination stride +@* +@* @param[in] ui_neighboravailability +@* availability of neighbouring pixels(Not used in this function) +@* +@* @returns +@* +@* @remarks +@* None +@* +@******************************************************************************* +@void ih264_intra_pred_chroma_8x8_mode_vert(UWORD8 *pu1_src, +@ UWORD8 *pu1_dst, +@ WORD32 src_strd, +@ WORD32 dst_strd, +@ WORD32 ui_neighboravailability) + +@**************Variables Vs Registers***************************************** +@ r0 => *pu1_src +@ r1 => *pu1_dst +@ r2 => src_strd +@ r3 => dst_strd +@ r4 => ui_neighboravailability + + + .global ih264_intra_pred_chroma_8x8_mode_vert_a9q + +ih264_intra_pred_chroma_8x8_mode_vert_a9q: + + stmfd sp!, {r4-r12, r14} @store register values to stack + + add r0, r0, #18 + vld1.8 {q0}, [r0] + + vst1.8 {q0}, [r1], r3 + vst1.8 {q0}, [r1], r3 + vst1.8 {q0}, [r1], r3 + vst1.8 {q0}, [r1], r3 + vst1.8 {q0}, [r1], r3 + vst1.8 {q0}, [r1], r3 + vst1.8 {q0}, [r1], r3 + vst1.8 {q0}, [r1], r3 + + ldmfd sp!, {r4-r12, pc} @Restoring registers from stack + + + + +@****************************************************************************** + + +@** +@******************************************************************************* +@* +@*ih264_intra_pred_chroma_8x8_mode_plane +@* +@* @brief +@* Perform Intra prediction for chroma_8x8 mode:PLANE +@* +@* @par Description: +@* Perform Intra prediction for chroma_8x8 mode:PLANE ,described in sec 8.3.4.4 +@* +@* @param[in] pu1_src +@* UWORD8 pointer to the source containing alternate U and V samples +@* +@* @param[out] pu1_dst +@* UWORD8 pointer to the destination with alternate U and V samples +@* +@* @param[in] src_strd +@* integer source stride +@* +@* @param[in] dst_strd +@* integer destination stride +@* +@* @param[in] ui_neighboravailability +@* availability of neighbouring pixels +@* +@* @returns +@* +@* @remarks +@* None +@* +@******************************************************************************* +@void ih264_intra_pred_chroma_8x8_mode_plane(UWORD8 *pu1_src, +@ UWORD8 *pu1_dst, +@ WORD32 src_strd, +@ WORD32 dst_strd, +@ WORD32 ui_neighboravailability) + +@**************Variables Vs Registers***************************************** +@ r0 => *pu1_src +@ r1 => *pu1_dst +@ r2 => src_strd +@ r3 => dst_strd +@ r4 => ui_neighboravailability + + .global ih264_intra_pred_chroma_8x8_mode_plane_a9q +ih264_intra_pred_chroma_8x8_mode_plane_a9q: + + stmfd sp!, {r4-r10, r12, lr} + vpush {d8-d15} + + vld1.32 d0, [r0] + add r10, r0, #10 + vld1.32 d1, [r10] + add r10, r10, #6 + vrev64.16 d5, d0 + vld1.32 d2, [r10]! + add r10, r10, #2 + vrev64.16 d7, d2 + vld1.32 d3, [r10] + sub r5, r3, #8 + ldr r12, scratch_chroma_intrapred_addr1 +scrlblc1: + add r12, r12, pc + vsubl.u8 q5, d5, d1 + vld1.64 {q4}, [r12] @ Load multiplication factors 1 to 8 into D3 + vsubl.u8 q6, d3, d7 + vmul.s16 q7, q5, q4 + vmul.s16 q8, q6, q4 + vuzp.16 q7, q8 + + vpadd.s16 d14, d14 + vpadd.s16 d15, d15 + vpadd.s16 d16, d16 + vpadd.s16 d17, d17 + vpadd.s16 d14, d14 + vpadd.s16 d15, d15 + vpadd.s16 d16, d16 + vpadd.s16 d17, d17 + + mov r6, #34 + vdup.16 q9, r6 + + vmull.s16 q11, d14, d18 + vmull.s16 q12, d15, d18 + vmull.s16 q13, d16, d18 + vmull.s16 q14, d17, d18 + + vrshrn.s32 d10, q11, #6 + vrshrn.s32 d12, q12, #6 + vrshrn.s32 d13, q13, #6 + vrshrn.s32 d14, q14, #6 + + + ldrb r6, [r0], #1 + add r10, r0, #31 + ldrb r8, [r0], #1 + ldrb r7, [r10], #1 + ldrb r9, [r10], #1 + + add r6, r6, r7 + add r8, r8, r9 + lsl r6, r6, #4 + lsl r8, r8, #4 + + vdup.16 q0, r6 + vdup.16 q1, r8 + vdup.16 q2, d12[0] + vdup.16 q3, d10[0] + + vdup.16 q12, d14[0] + vdup.16 q13, d13[0] + vzip.16 q2, q12 + vzip.16 q3, q13 + vzip.16 q0, q1 + + ldr r12, scratch_intrapred_chroma_plane_addr1 +scrlblc2: + add r12, r12, pc + vld1.64 {q4}, [r12] + vmov.16 q5, q4 + vmov q11, q4 + vzip.16 q4, q5 + + vmul.s16 q6, q2, q4 + vmul.s16 q8, q2, q5 + vadd.s16 q6, q0, q6 + vadd.s16 q8, q0, q8 + + + vdup.16 q10, d22[0] + vmul.s16 q2, q3, q10 + vdup.16 q15, d22[1] + vmul.s16 q9, q3, q10 + vmul.s16 q7, q3, q15 + vmul.s16 q4, q3, q15 + vadd.s16 q12, q6, q2 + vadd.s16 q0, q8, q9 + vadd.s16 q1, q6, q7 + vqrshrun.s16 d28, q12, #5 + vadd.s16 q13, q8, q4 + vqrshrun.s16 d29, q0, #5 + vdup.16 q10, d22[2] + vst1.8 {q14}, [r1], r3 + vqrshrun.s16 d28, q1, #5 + vqrshrun.s16 d29, q13, #5 + vmul.s16 q2, q3, q10 + vmul.s16 q9, q3, q10 + vst1.8 {q14}, [r1], r3 + vadd.s16 q12, q6, q2 + vadd.s16 q0, q8, q9 + vdup.16 q15, d22[3] + vqrshrun.s16 d28, q12, #5 + vqrshrun.s16 d29, q0, #5 + vmul.s16 q7, q3, q15 + vmul.s16 q4, q3, q15 + vst1.8 {q14}, [r1], r3 + vadd.s16 q1, q6, q7 + vadd.s16 q13, q8, q4 + vdup.16 q10, d23[0] + vqrshrun.s16 d28, q1, #5 + vqrshrun.s16 d29, q13, #5 + vmul.s16 q2, q3, q10 + vmul.s16 q9, q3, q10 + vst1.8 {q14}, [r1], r3 + vadd.s16 q12, q6, q2 + vadd.s16 q0, q8, q9 + vdup.16 q15, d23[1] + vqrshrun.s16 d28, q12, #5 + vqrshrun.s16 d29, q0, #5 + vmul.s16 q7, q3, q15 + vmul.s16 q4, q3, q15 + vst1.8 {q14}, [r1], r3 + vadd.s16 q1, q6, q7 + vadd.s16 q13, q8, q4 + vdup.16 q10, d23[2] + vqrshrun.s16 d28, q1, #5 + vqrshrun.s16 d29, q13, #5 + vmul.s16 q2, q3, q10 + vmul.s16 q9, q3, q10 + vst1.8 {q14}, [r1], r3 + vadd.s16 q12, q6, q2 + vadd.s16 q0, q8, q9 + vdup.16 q15, d23[3] + vqrshrun.s16 d28, q12, #5 + vqrshrun.s16 d29, q0, #5 + vmul.s16 q7, q3, q15 + vmul.s16 q4, q3, q15 + vst1.8 {q14}, [r1], r3 + vadd.s16 q1, q6, q7 + vadd.s16 q13, q8, q4 + vqrshrun.s16 d28, q1, #5 + vqrshrun.s16 d29, q13, #5 + vst1.8 {q14}, [r1], r3 + + + +end_func_plane: + + vpop {d8-d15} + ldmfd sp!, {r4-r10, r12, pc} + + + + diff --git a/dependencies/ih264d/common/arm/ih264_intra_pred_luma_16x16_a9q.s b/dependencies/ih264d/common/arm/ih264_intra_pred_luma_16x16_a9q.s new file mode 100644 index 00000000..75974445 --- /dev/null +++ b/dependencies/ih264d/common/arm/ih264_intra_pred_luma_16x16_a9q.s @@ -0,0 +1,520 @@ +@/****************************************************************************** +@ * +@ * Copyright (C) 2015 The Android Open Source Project +@ * +@ * Licensed under the Apache License, Version 2.0 (the "License"); +@ * you may not use this file except in compliance with the License. +@ * You may obtain a copy of the License at: +@ * +@ * http://www.apache.org/licenses/LICENSE-2.0 +@ * +@ * Unless required by applicable law or agreed to in writing, software +@ * distributed under the License is distributed on an "AS IS" BASIS, +@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@ * See the License for the specific language governing permissions and +@ * limitations under the License. +@ * +@ ***************************************************************************** +@ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +@*/ +@** +@****************************************************************************** +@* @file +@* ih264_intra_pred_luma_16x16_a9q.s +@* +@* @brief +@* Contains function definitions for intra 16x16 Luma prediction . +@* +@* @author +@* Ittiam +@* +@* @par List of Functions: +@* +@* - ih264_intra_pred_luma_16x16_mode_vert_a9q() +@* - ih264_intra_pred_luma_16x16_mode_horz_a9q() +@* - ih264_intra_pred_luma_16x16_mode_dc_a9q() +@* - ih264_intra_pred_luma_16x16_mode_plane_a9q() +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* + +@* All the functions here are replicated from ih264_intra_pred_filters.c +@ + +@** +@** +@** +@ + +.text +.p2align 2 + + + .extern ih264_gai1_intrapred_luma_plane_coeffs +.hidden ih264_gai1_intrapred_luma_plane_coeffs +scratch_intrapred_addr1: + .long ih264_gai1_intrapred_luma_plane_coeffs - scrlbl1 - 8 +@** +@******************************************************************************* +@* +@*ih264_intra_pred_luma_16x16_mode_vert +@* +@* @brief +@* Perform Intra prediction for luma_16x16 mode:vertical +@* +@* @par Description: +@* Perform Intra prediction for luma_16x16 mode:Vertical ,described in sec 8.3.3.1 +@* +@* @param[in] pu1_src +@* UWORD8 pointer to the source +@* +@* @param[out] pu1_dst +@* UWORD8 pointer to the destination +@* +@* @param[in] src_strd +@* integer source stride +@* +@* @param[in] dst_strd +@* integer destination stride +@* +@* @param[in] ui_neighboravailability +@* availability of neighbouring pixels(Not used in this function) +@* +@* @returns +@* +@* @remarks +@* None +@* +@******************************************************************************* +@void ih264_intra_pred_luma_16x16_mode_vert(UWORD8 *pu1_src, +@ UWORD8 *pu1_dst, +@ WORD32 src_strd, +@ WORD32 dst_strd, +@ WORD32 ui_neighboravailability) + +@**************Variables Vs Registers***************************************** +@ r0 => *pu1_src +@ r1 => *pu1_dst +@ r2 => src_strd +@ r3 => dst_strd +@ r4 => ui_neighboravailability + + + .global ih264_intra_pred_luma_16x16_mode_vert_a9q + +ih264_intra_pred_luma_16x16_mode_vert_a9q: + + stmfd sp!, {r4-r12, r14} @store register values to stack + + add r0, r0, #17 + vld1.8 {q0}, [r0] + + vst1.8 {q0}, [r1], r3 + vst1.8 {q0}, [r1], r3 + vst1.8 {q0}, [r1], r3 + vst1.8 {q0}, [r1], r3 + vst1.8 {q0}, [r1], r3 + vst1.8 {q0}, [r1], r3 + vst1.8 {q0}, [r1], r3 + vst1.8 {q0}, [r1], r3 + vst1.8 {q0}, [r1], r3 + vst1.8 {q0}, [r1], r3 + vst1.8 {q0}, [r1], r3 + vst1.8 {q0}, [r1], r3 + vst1.8 {q0}, [r1], r3 + vst1.8 {q0}, [r1], r3 + vst1.8 {q0}, [r1], r3 + vst1.8 {q0}, [r1], r3 + + ldmfd sp!, {r4-r12, pc} @Restoring registers from stack + + + + + +@****************************************************************************** + + +@** +@******************************************************************************* +@* +@*ih264_intra_pred_luma_16x16_mode_horz +@* +@* @brief +@* Perform Intra prediction for luma_16x16 mode:horizontal +@* +@* @par Description: +@* Perform Intra prediction for luma_16x16 mode:horizontal ,described in sec 8.3.3.2 +@* +@* @param[in] pu1_src +@* UWORD8 pointer to the source +@* +@* @param[out] pu1_dst +@* UWORD8 pointer to the destination +@* +@* @param[in] src_strd +@* integer source stride +@* +@* @param[in] dst_strd +@* integer destination stride +@* +@* @param[in] ui_neighboravailability +@* availability of neighbouring pixels(Not used in this function) +@* +@* @returns +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* +@void ih264_intra_pred_luma_16x16_mode_horz(UWORD8 *pu1_src, +@ UWORD8 *pu1_dst, +@ WORD32 src_strd, +@ WORD32 dst_strd, +@ WORD32 ui_neighboravailability) +@**************Variables Vs Registers***************************************** +@ r0 => *pu1_src +@ r1 => *pu1_dst +@ r2 => src_strd +@ r3 => dst_strd +@ r4 => ui_neighboravailability + + .global ih264_intra_pred_luma_16x16_mode_horz_a9q + +ih264_intra_pred_luma_16x16_mode_horz_a9q: + + stmfd sp!, {r14} @store register values to stack + + vld1.u8 {q0}, [r0] + mov r2, #14 + + vdup.u8 q1, d1[7] + vdup.u8 q2, d1[6] + vst1.8 {q1}, [r1], r3 + +loop_16x16_horz: + vext.8 q0, q0, q0, #14 + vst1.8 {q2}, [r1], r3 + vdup.u8 q1, d1[7] + subs r2, #2 + vdup.u8 q2, d1[6] + vst1.8 {q1}, [r1], r3 + bne loop_16x16_horz + + vext.8 q0, q0, q0, #14 + vst1.8 {q2}, [r1], r3 + + ldmfd sp!, {pc} @Restoring registers from stack + + + + +@****************************************************************************** + + +@** +@******************************************************************************* +@* +@*ih264_intra_pred_luma_16x16_mode_dc +@* +@* @brief +@* Perform Intra prediction for luma_16x16 mode:DC +@* +@* @par Description: +@* Perform Intra prediction for luma_16x16 mode:DC ,described in sec 8.3.3.3 +@* +@* @param[in] pu1_src +@* UWORD8 pointer to the source +@* +@* @param[out] pu1_dst +@* UWORD8 pointer to the destination +@* +@* @param[in] src_strd +@* integer source stride +@* +@* @param[in] dst_strd +@* integer destination stride +@* +@* @param[in] ui_neighboravailability +@* availability of neighbouring pixels +@* +@* @returns +@* +@* @remarks +@* None +@* +@******************************************************************************* +@void ih264_intra_pred_luma_16x16_mode_dc(UWORD8 *pu1_src, +@ UWORD8 *pu1_dst, +@ WORD32 src_strd, +@ WORD32 dst_strd, +@ WORD32 ui_neighboravailability) + +@**************Variables Vs Registers***************************************** +@ r0 => *pu1_src +@ r1 => *pu1_dst +@ r2 => src_strd +@ r3 => dst_strd +@ r4 => ui_neighboravailability + + .global ih264_intra_pred_luma_16x16_mode_dc_a9q + +ih264_intra_pred_luma_16x16_mode_dc_a9q: + + stmfd sp!, {r4, r14} @store register values to stack + ldr r4, [sp, #8] @r4 => ui_neighboravailability + + ands r2, r4, #0x01 @CHECKING IF LEFT_AVAILABLE ELSE BRANCHING TO ONLY TOP AVAILABLE + beq top_available + ands r2, r4, #0x04 @CHECKING IF TOP_AVAILABLE ELSE BRANCHING TO ONLY LEFT AVAILABLE + beq left_available + + vld1.u8 {q0}, [r0] @BOTH LEFT AND TOP AVAILABLE + add r0, r0, #17 + vpaddl.u8 q0, q0 + vld1.u8 {q1}, [r0] + vpaddl.u8 q1, q1 + vadd.u16 q0, q0, q1 + vadd.u16 d0, d0, d1 + vpaddl.u16 d0, d0 + vpaddl.u32 d0, d0 + vqrshrun.s16 d0, q0, #5 + vdup.u8 q0, d0[0] + b str_pred + +top_available: @ONLY TOP AVAILABLE + ands r2, r4, #0x04 @CHECKING TOP AVAILABILTY OR ELSE BRANCH TO NONE AVAILABLE + beq none_available + + add r0, r0, #17 + vld1.u8 {q0}, [r0] + vpaddl.u8 q0, q0 + vadd.u16 d0, d0, d1 + vpaddl.u16 d0, d0 + vpaddl.u32 d0, d0 + vqrshrun.s16 d0, q0, #4 + vdup.u8 q0, d0[0] + b str_pred + +left_available: @ONLY LEFT AVAILABLE + vld1.u8 {q0}, [r0] + vpaddl.u8 q0, q0 + vadd.u16 d0, d0, d1 + vpaddl.u16 d0, d0 + vpaddl.u32 d0, d0 + vqrshrun.s16 d0, q0, #4 + vdup.u8 q0, d0[0] + b str_pred + +none_available: @NONE AVAILABLE + vmov.u8 q0, #128 + +str_pred: + vst1.8 {q0}, [r1], r3 + vst1.8 {q0}, [r1], r3 + vst1.8 {q0}, [r1], r3 + vst1.8 {q0}, [r1], r3 + vst1.8 {q0}, [r1], r3 + vst1.8 {q0}, [r1], r3 + vst1.8 {q0}, [r1], r3 + vst1.8 {q0}, [r1], r3 + vst1.8 {q0}, [r1], r3 + vst1.8 {q0}, [r1], r3 + vst1.8 {q0}, [r1], r3 + vst1.8 {q0}, [r1], r3 + vst1.8 {q0}, [r1], r3 + vst1.8 {q0}, [r1], r3 + vst1.8 {q0}, [r1], r3 + vst1.8 {q0}, [r1], r3 + + ldmfd sp!, {r4, pc} @Restoring registers from stack + + + + + +@****************************************************************************** + + +@** +@******************************************************************************* +@* +@*ih264_intra_pred_luma_16x16_mode_plane +@* +@* @brief +@* Perform Intra prediction for luma_16x16 mode:PLANE +@* +@* @par Description: +@* Perform Intra prediction for luma_16x16 mode:PLANE ,described in sec 8.3.3.4 +@* +@* @param[in] pu1_src +@* UWORD8 pointer to the source +@* +@* @param[out] pu1_dst +@* UWORD8 pointer to the destination +@* +@* @param[in] src_strd +@* integer source stride +@* +@* @param[in] dst_strd +@* integer destination stride +@* +@* @param[in] ui_neighboravailability +@* availability of neighbouring pixels +@* +@* @returns +@* +@* @remarks +@* None +@* +@******************************************************************************* +@void ih264_intra_pred_luma_16x16_mode_plane(UWORD8 *pu1_src, +@ UWORD8 *pu1_dst, +@ WORD32 src_strd, +@ WORD32 dst_strd, +@ WORD32 ui_neighboravailability) + +@**************Variables Vs Registers***************************************** +@ r0 => *pu1_src +@ r1 => *pu1_dst +@ r2 => src_strd +@ r3 => dst_strd +@ r4 => ui_neighboravailability + + .global ih264_intra_pred_luma_16x16_mode_plane_a9q +ih264_intra_pred_luma_16x16_mode_plane_a9q: + + stmfd sp!, {r4-r10, r12, lr} + + mov r2, r1 + add r1, r0, #17 + add r0, r0, #15 + + mov r8, #9 + sub r1, r1, #1 + mov r10, r1 @top_left + mov r4, #-1 + vld1.32 d2, [r1], r8 + ldr r7, scratch_intrapred_addr1 +scrlbl1: + add r7, r7, pc + + vld1.32 d0, [r1] + vrev64.8 d2, d2 + vld1.32 {q3}, [r7] + vsubl.u8 q0, d0, d2 + vmovl.u8 q8, d6 + vmul.s16 q0, q0, q8 + vmovl.u8 q9, d7 + + add r7, r0, r4, lsl #3 + sub r0, r7, r4, lsl #1 + neg lr, r4 + + vpadd.s16 d0, d0, d1 + + ldrb r8, [r7], r4 + ldrb r9, [r0], lr + + vpaddl.s16 d0, d0 + sub r12, r8, r9 + + ldrb r8, [r7], r4 + + vpaddl.s32 d0, d0 + ldrb r9, [r0], lr + sub r8, r8, r9 + vshl.s32 d2, d0, #2 + add r12, r12, r8, lsl #1 + + vadd.s32 d0, d0, d2 + ldrb r8, [r7], r4 + ldrb r9, [r0], lr + vrshr.s32 d0, d0, #6 @ i_b = D0[0] + sub r8, r8, r9 + ldrb r5, [r7], r4 + add r8, r8, r8, lsl #1 + + vdup.16 q2, d0[0] + add r12, r12, r8 + ldrb r9, [r0], lr + vmul.s16 q0, q2, q8 + sub r5, r5, r9 + vmul.s16 q1, q2, q9 + add r12, r12, r5, lsl #2 + + ldrb r8, [r7], r4 + ldrb r9, [r0], lr + sub r8, r8, r9 + ldrb r5, [r7], r4 + add r8, r8, r8, lsl #2 + ldrb r6, [r0], lr + add r12, r12, r8 + ldrb r8, [r7], r4 + ldrb r9, [r0], lr + + sub r5, r5, r6 + sub r8, r8, r9 + add r5, r5, r5, lsl #1 + rsb r8, r8, r8, lsl #3 + add r12, r12, r5, lsl #1 + ldrb r5, [r7], r4 + ldrb r6, [r10] @top_left + add r12, r12, r8 + sub r9, r5, r6 + ldrb r6, [r1, #7] + add r12, r12, r9, lsl #3 @ i_c = r12 + add r8, r5, r6 + + add r12, r12, r12, lsl #2 + lsl r8, r8, #4 @ i_a = r8 + + add r12, r12, #0x20 + lsr r12, r12, #6 + + vshl.s16 q14, q2, #3 + vdup.16 q3, r12 + + vdup.16 q15, r8 + vshl.s16 q13, q3, #3 + vsub.s16 q15, q15, q14 + vsub.s16 q15, q15, q13 + vadd.s16 q14, q15, q3 + + mov r0, #14 + vadd.s16 q13, q14, q0 + vadd.s16 q14, q14, q1 + vqrshrun.s16 d20, q13, #5 + vqrshrun.s16 d21, q14, #5 + +loop_16x16_plane: + + vadd.s16 q13, q13, q3 + vadd.s16 q14, q14, q3 + vqrshrun.s16 d22, q13, #5 + vst1.32 {q10}, [r2], r3 + vqrshrun.s16 d23, q14, #5 + + vadd.s16 q13, q13, q3 + subs r0, #2 + vadd.s16 q14, q14, q3 + vqrshrun.s16 d20, q13, #5 + vst1.32 {q11}, [r2], r3 + vqrshrun.s16 d21, q14, #5 + bne loop_16x16_plane + + vadd.s16 q13, q13, q3 + vadd.s16 q14, q14, q3 + vqrshrun.s16 d22, q13, #5 + vst1.32 {q10}, [r2], r3 + vqrshrun.s16 d23, q14, #5 + vst1.32 {q11}, [r2], r3 + + ldmfd sp!, {r4-r10, r12, pc} + + + diff --git a/dependencies/ih264d/common/arm/ih264_intra_pred_luma_4x4_a9q.s b/dependencies/ih264d/common/arm/ih264_intra_pred_luma_4x4_a9q.s new file mode 100644 index 00000000..5cc7e23b --- /dev/null +++ b/dependencies/ih264d/common/arm/ih264_intra_pred_luma_4x4_a9q.s @@ -0,0 +1,837 @@ +@/****************************************************************************** +@ * +@ * Copyright (C) 2015 The Android Open Source Project +@ * +@ * Licensed under the Apache License, Version 2.0 (the "License"); +@ * you may not use this file except in compliance with the License. +@ * You may obtain a copy of the License at: +@ * +@ * http://www.apache.org/licenses/LICENSE-2.0 +@ * +@ * Unless required by applicable law or agreed to in writing, software +@ * distributed under the License is distributed on an "AS IS" BASIS, +@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@ * See the License for the specific language governing permissions and +@ * limitations under the License. +@ * +@ ***************************************************************************** +@ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +@*/ +@** +@****************************************************************************** +@* @file +@* ih264_intra_pred_luma_4x4_a9q.s +@* +@* @brief +@* Contains function definitions for intra 4x4 Luma prediction . +@* +@* @author +@* Ittiam +@* +@* @par List of Functions: +@* +@* -ih264_intra_pred_luma_4x4_mode_vert_a9q +@* -ih264_intra_pred_luma_4x4_mode_horz_a9q +@* -ih264_intra_pred_luma_4x4_mode_dc_a9q +@* -ih264_intra_pred_luma_4x4_mode_diag_dl_a9q +@* -ih264_intra_pred_luma_4x4_mode_diag_dr_a9q +@* -ih264_intra_pred_luma_4x4_mode_vert_r_a9q +@* -ih264_intra_pred_luma_4x4_mode_horz_d_a9q +@* -ih264_intra_pred_luma_4x4_mode_vert_l_a9q +@* -ih264_intra_pred_luma_4x4_mode_horz_u_a9q +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* + +@* All the functions here are replicated from ih264_intra_pred_filters.c +@ + +.text +.p2align 2 + + +@** +@******************************************************************************* +@* +@*ih264_intra_pred_luma_4x4_mode_vert +@* +@* @brief +@* Perform Intra prediction for luma_4x4 mode:vertical +@* +@* @par Description: +@* Perform Intra prediction for luma_4x4 mode:vertical ,described in sec 8.3.1.2.1 +@* +@* @param[in] pu1_src +@* UWORD8 pointer to the source +@* +@* @param[out] pu1_dst +@* UWORD8 pointer to the destination +@* +@* @param[in] src_strd +@* integer source stride +@* +@* @param[in] dst_strd +@* integer destination stride +@* +@* @param[in] ui_neighboravailability +@* availability of neighbouring pixels(Not used in this function) +@* +@* @returns +@* +@* @remarks +@* None +@* +@******************************************************************************* +@void ih264_intra_pred_luma_4x4_mode_vert(UWORD8 *pu1_src, +@ UWORD8 *pu1_dst, +@ WORD32 src_strd, +@ WORD32 dst_strd, +@ WORD32 ui_neighboravailability) + +@**************Variables Vs Registers***************************************** +@ r0 => *pu1_src +@ r1 => *pu1_dst +@ r2 => src_strd +@ r3 => dst_strd +@ r4 => ui_neighboravailability + + .global ih264_intra_pred_luma_4x4_mode_vert_a9q + +ih264_intra_pred_luma_4x4_mode_vert_a9q: + + + + stmfd sp!, {r4-r12, r14} @store register values to stack + + add r0, r0, #5 + + vld1.32 d0[0], [r0] + + vst1.32 d0[0], [r1], r3 + vst1.32 d0[0], [r1], r3 + vst1.32 d0[0], [r1], r3 + vst1.32 d0[0], [r1], r3 + + + + ldmfd sp!, {r4-r12, pc} @Restoring registers from stack + + + + + +@****************************************************************************** + + +@** +@******************************************************************************* +@* +@*ih264_intra_pred_luma_4x4_mode_horz +@* +@* @brief +@* Perform Intra prediction for luma_4x4 mode:horizontal +@* +@* @par Description: +@* Perform Intra prediction for luma_4x4 mode:horizontal ,described in sec 8.3.1.2.2 +@* +@* @param[in] pu1_src +@* UWORD8 pointer to the source +@* +@* @param[out] pu1_dst +@* UWORD8 pointer to the destination +@* +@* @param[in] src_strd +@* integer source stride +@* +@* @param[in] dst_strd +@* integer destination stride +@* +@* @param[in] ui_neighboravailability +@* availability of neighbouring pixels(Not used in this function) +@* +@* @returns +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* +@void ih264_intra_pred_luma_4x4_mode_horz(UWORD8 *pu1_src, +@ UWORD8 *pu1_dst, +@ WORD32 src_strd, +@ WORD32 dst_strd, +@ WORD32 ui_neighboravailability) +@**************Variables Vs Registers***************************************** +@ r0 => *pu1_src +@ r1 => *pu1_dst +@ r2 => src_strd +@ r3 => dst_strd +@ r4 => ui_neighboravailability + + + + .global ih264_intra_pred_luma_4x4_mode_horz_a9q + +ih264_intra_pred_luma_4x4_mode_horz_a9q: + + + + stmfd sp!, {r4-r12, r14} @store register values to stack + add r0, r0, #3 + mov r2 , #-1 + + ldrb r5, [r0], r2 + vdup.u8 d0, r5 + ldrb r6, [r0], r2 + vst1.32 d0[0], [r1], r3 + vdup.u8 d1, r6 + ldrb r7, [r0], r2 + vst1.32 d1[0], [r1], r3 + vdup.u8 d2, r7 + ldrb r8, [r0], r2 + vst1.32 d2[0], [r1], r3 + vdup.u8 d3, r8 + vst1.32 d3[0], [r1], r3 + + + ldmfd sp!, {r4-r12, pc} @Restoring registers from stack + + + + + + + +@****************************************************************************** + + +@** +@******************************************************************************* +@* +@*ih264_intra_pred_luma_4x4_mode_dc +@* +@* @brief +@* Perform Intra prediction for luma_4x4 mode:DC +@* +@* @par Description: +@* Perform Intra prediction for luma_4x4 mode:DC ,described in sec 8.3.1.2.3 +@* +@* @param[in] pu1_src +@* UWORD8 pointer to the source +@* +@* @param[out] pu1_dst +@* UWORD8 pointer to the destination +@* +@* @param[in] src_strd +@* integer source stride +@* +@* @param[in] dst_strd +@* integer destination stride +@* +@* @param[in] ui_neighboravailability +@* availability of neighbouring pixels +@* +@* @returns +@* +@* @remarks +@* None +@* +@******************************************************************************* +@void ih264_intra_pred_luma_4x4_mode_dc(UWORD8 *pu1_src, +@ UWORD8 *pu1_dst, +@ WORD32 src_strd, +@ WORD32 dst_strd, +@ WORD32 ui_neighboravailability) + +@**************Variables Vs Registers***************************************** +@ r0 => *pu1_src +@ r1 => *pu1_dst +@ r2 => src_strd +@ r3 => dst_strd +@ r4 => ui_neighboravailability + + + + .global ih264_intra_pred_luma_4x4_mode_dc_a9q + +ih264_intra_pred_luma_4x4_mode_dc_a9q: + + + + stmfd sp!, {r4-r12, r14} @store register values to stack + ldr r4, [sp, #40] @ r4 => ui_neighboravailability + + ands r5, r4, #0x01 + beq top_available @LEFT NOT AVAILABLE + + add r10, r0, #3 + mov r2, #-1 + ldrb r5, [r10], r2 + ldrb r6, [r10], r2 + ldrb r7, [r10], r2 + add r5, r5, r6 + ldrb r8, [r10], r2 + add r5, r5, r7 + ands r11, r4, #0x04 @ CHECKING IF TOP_AVAILABLE ELSE BRANCHING TO ONLY LEFT AVAILABLE + add r5, r5, r8 + beq left_available + add r10, r0, #5 + @ BOTH LEFT AND TOP AVAILABLE + ldrb r6, [r10], #1 + ldrb r7, [r10], #1 + add r5, r5, r6 + ldrb r8, [r10], #1 + add r5, r5, r7 + ldrb r9, [r10], #1 + add r5, r5, r8 + add r5, r5, r9 + add r5, r5, #4 + lsr r5, r5, #3 + vdup.u8 d0, r5 + vst1.32 d0[0], [r1], r3 + vst1.32 d0[0], [r1], r3 + vst1.32 d0[0], [r1], r3 + vst1.32 d0[0], [r1], r3 + b end_func + +top_available: @ ONLT TOP AVAILABLE + ands r11, r4, #0x04 @ CHECKING TOP AVAILABILTY OR ELSE BRANCH TO NONE AVAILABLE + beq none_available + + add r10, r0, #5 + ldrb r6, [r10], #1 + ldrb r7, [r10], #1 + ldrb r8, [r10], #1 + add r5, r6, r7 + ldrb r9, [r10], #1 + add r5, r5, r8 + add r5, r5, r9 + add r5, r5, #2 + lsr r5, r5, #2 + vdup.u8 d0, r5 + vst1.32 d0[0], [r1], r3 + vst1.32 d0[0], [r1], r3 + vst1.32 d0[0], [r1], r3 + vst1.32 d0[0], [r1], r3 + b end_func + +left_available: @ONLY LEFT AVAILABLE + add r5, r5, #2 + lsr r5, r5, #2 + vdup.u8 d0, r5 + vst1.32 d0[0], [r1], r3 + vst1.32 d0[0], [r1], r3 + vst1.32 d0[0], [r1], r3 + vst1.32 d0[0], [r1], r3 + b end_func + +none_available: @NONE AVAILABLE + mov r5, #128 + vdup.u8 d0, r5 + vst1.32 d0[0], [r1], r3 + vst1.32 d0[0], [r1], r3 + vst1.32 d0[0], [r1], r3 + vst1.32 d0[0], [r1], r3 + b end_func + + +end_func: + ldmfd sp!, {r4-r12, pc} @Restoring registers from stack + + + + + + + +@** +@******************************************************************************* +@* +@*ih264_intra_pred_luma_4x4_mode_diag_dl +@* +@* @brief +@* Perform Intra prediction for luma_4x4 mode:Diagonal_Down_Left +@* +@* @par Description: +@* Perform Intra prediction for luma_4x4 mode:Diagonal_Down_Left ,described in sec 8.3.1.2.4 +@* +@* @param[in] pu1_src +@* UWORD8 pointer to the source +@* +@* @param[out] pu1_dst +@* UWORD8 pointer to the destination +@* +@* @param[in] src_strd +@* integer source stride +@* +@* @param[in] dst_strd +@* integer destination stride +@* +@* @param[in] ui_neighboravailability +@* availability of neighbouring pixels +@* +@* @returns +@* +@* @remarks +@* None +@* +@******************************************************************************* +@void ih264_intra_pred_luma_4x4_mode_diag_dl(UWORD8 *pu1_src, +@ UWORD8 *pu1_dst, +@ WORD32 src_strd, +@ WORD32 dst_strd, +@ WORD32 ui_neighboravailability) + +@**************Variables Vs Registers***************************************** +@ r0 => *pu1_src +@ r1 => *pu1_dst +@ r2 => src_strd +@ r3 => dst_strd +@ r4 => ui_neighboravailability + + + .global ih264_intra_pred_luma_4x4_mode_diag_dl_a9q + +ih264_intra_pred_luma_4x4_mode_diag_dl_a9q: + + stmfd sp!, {r4-r12, r14} @store register values to stack + + add r0, r0, #5 + sub r5, r3, #2 + add r6, r0, #7 + vld1.8 {d0}, [r0] + vext.8 d1, d0, d0, #1 + vext.8 d2, d0, d0, #2 + vld1.8 {d2[6]}, [r6] + vaddl.u8 q10, d0, d1 + vaddl.u8 q11, d1, d2 + vadd.u16 q12, q10, q11 + vqrshrun.s16 d3, q12, #2 + vst1.32 {d3[0]}, [r1], r3 + vext.8 d4, d3, d3, #1 + vst1.32 {d4[0]}, [r1], r3 + vst1.16 {d3[1]}, [r1]! + vst1.16 {d3[2]}, [r1], r5 + vst1.16 {d4[1]}, [r1]! + vst1.16 {d4[2]}, [r1] + +end_func_diag_dl: + ldmfd sp!, {r4-r12, pc} @Restoring registers from stack + + + + + + + + + +@** +@******************************************************************************* +@* +@*ih264_intra_pred_luma_4x4_mode_diag_dr +@* +@* @brief +@* Perform Intra prediction for luma_4x4 mode:Diagonal_Down_Right +@* +@* @par Description: +@* Perform Intra prediction for luma_4x4 mode:Diagonal_Down_Right ,described in sec 8.3.1.2.5 +@* +@* @param[in] pu1_src +@* UWORD8 pointer to the source +@* +@* @param[out] pu1_dst +@* UWORD8 pointer to the destination +@* +@* @param[in] src_strd +@* integer source stride +@* +@* @param[in] dst_strd +@* integer destination stride +@* +@* @param[in] ui_neighboravailability +@* availability of neighbouring pixels +@* +@* @returns +@* +@* @remarks +@* None +@* +@******************************************************************************* +@void ih264_intra_pred_luma_4x4_mode_diag_dr(UWORD8 *pu1_src, +@ UWORD8 *pu1_dst, +@ WORD32 src_strd, +@ WORD32 dst_strd, +@ WORD32 ui_neighboravailability) + +@**************Variables Vs Registers***************************************** +@ r0 => *pu1_src +@ r1 => *pu1_dst +@ r2 => src_strd +@ r3 => dst_strd +@ r4 => ui_neighboravailability + + + .global ih264_intra_pred_luma_4x4_mode_diag_dr_a9q + +ih264_intra_pred_luma_4x4_mode_diag_dr_a9q: + + stmfd sp!, {r4-r12, r14} @store register values to stack + + + vld1.u8 {d0}, [r0] + add r0, r0, #1 + vld1.u8 {d1}, [r0] + vext.8 d2, d1, d1, #1 + vaddl.u8 q10, d0, d1 + vaddl.u8 q11, d1, d2 + vadd.u16 q12, q10, q11 + vqrshrun.s16 d3, q12, #2 + + vext.8 d4, d3, d3, #1 + sub r5, r3, #2 + vst1.16 {d4[1]}, [r1]! + vst1.16 {d4[2]}, [r1], r5 + vst1.16 {d3[1]}, [r1]! + vst1.16 {d3[2]}, [r1], r5 + vst1.32 {d4[0]}, [r1], r3 + vst1.32 {d3[0]}, [r1], r3 + +end_func_diag_dr: + ldmfd sp!, {r4-r12, pc} @Restoring registers from stack + + + + + + + +@** +@******************************************************************************* +@* +@*ih264_intra_pred_luma_4x4_mode_vert_r +@* +@* @brief +@* Perform Intra prediction for luma_4x4 mode:Vertical_Right +@* +@* @par Description: +@* Perform Intra prediction for luma_4x4 mode:Vertical_Right ,described in sec 8.3.1.2.6 +@* +@* @param[in] pu1_src +@* UWORD8 pointer to the source +@* +@* @param[out] pu1_dst +@* UWORD8 pointer to the destination +@* +@* @param[in] src_strd +@* integer source stride +@* +@* @param[in] dst_strd +@* integer destination stride +@* +@* @param[in] ui_neighboravailability +@* availability of neighbouring pixels +@* +@* @returns +@* +@* @remarks +@* None +@* +@******************************************************************************* +@void ih264_intra_pred_luma_4x4_mode_vert_r(UWORD8 *pu1_src, +@ UWORD8 *pu1_dst, +@ WORD32 src_strd, +@ WORD32 dst_strd, +@ WORD32 ui_neighboravailability) + +@**************Variables Vs Registers***************************************** +@ r0 => *pu1_src +@ r1 => *pu1_dst +@ r2 => src_strd +@ r3 => dst_strd +@ r4 => ui_neighboravailability + + + .global ih264_intra_pred_luma_4x4_mode_vert_r_a9q + +ih264_intra_pred_luma_4x4_mode_vert_r_a9q: + + stmfd sp!, {r4-r12, r14} @store register values to stack + + + vld1.u8 {d0}, [r0] + add r0, r0, #1 + vld1.u8 {d1}, [r0] + vext.8 d2, d1, d1, #1 + vaddl.u8 q10, d0, d1 + vaddl.u8 q11, d1, d2 + vadd.u16 q12, q10, q11 + vqrshrun.s16 d4, q10, #1 + vqrshrun.s16 d3, q12, #2 + sub r5, r3, #2 + vext.8 d5, d3, d3, #3 + vst1.32 {d4[1]}, [r1], r3 + vst1.32 {d5[0]}, [r1], r3 + sub r8, r3, #3 + vst1.u8 {d3[2]}, [r1]! + vst1.16 {d4[2]}, [r1]! + vst1.u8 {d4[6]}, [r1], r8 + vst1.u8 {d3[1]}, [r1]! + vst1.16 {d5[0]}, [r1]! + vst1.u8 {d5[2]}, [r1] + + +end_func_vert_r: + ldmfd sp!, {r4-r12, pc} @Restoring registers from stack + + + + + +@** +@******************************************************************************* +@* +@*ih264_intra_pred_luma_4x4_mode_horz_d +@* +@* @brief +@* Perform Intra prediction for luma_4x4 mode:Horizontal_Down +@* +@* @par Description: +@* Perform Intra prediction for luma_4x4 mode:Horizontal_Down ,described in sec 8.3.1.2.7 +@* +@* @param[in] pu1_src +@* UWORD8 pointer to the source +@* +@* @param[out] pu1_dst +@* UWORD8 pointer to the destination +@* +@* @param[in] src_strd +@* integer source stride +@* +@* @param[in] dst_strd +@* integer destination stride +@* +@* @param[in] ui_neighboravailability +@* availability of neighbouring pixels +@* +@* @returns +@* +@* @remarks +@* None +@* +@******************************************************************************* +@void ih264_intra_pred_luma_4x4_mode_horz_d(UWORD8 *pu1_src, +@ UWORD8 *pu1_dst, +@ WORD32 src_strd, +@ WORD32 dst_strd, +@ WORD32 ui_neighboravailability) + +@**************Variables Vs Registers***************************************** +@ r0 => *pu1_src +@ r1 => *pu1_dst +@ r2 => src_strd +@ r3 => dst_strd +@ r4 => ui_neighboravailability + + + .global ih264_intra_pred_luma_4x4_mode_horz_d_a9q + +ih264_intra_pred_luma_4x4_mode_horz_d_a9q: + + stmfd sp!, {r4-r12, r14} @store register values to stack + + vld1.u8 {d0}, [r0] + add r0, r0, #1 + vld1.u8 {d1}, [r0] + vext.8 d2, d1, d0, #1 + vaddl.u8 q10, d0, d1 + vaddl.u8 q11, d1, d2 + vadd.u16 q12, q10, q11 + vqrshrun.s16 d4, q10, #1 + vqrshrun.s16 d5, q12, #2 + sub r5, r3, #2 + vmov.8 d6, d5 + vtrn.8 d4, d5 @ + vst1.u16 {d5[1]}, [r1]! + vst1.16 {d6[2]}, [r1], r5 + vst1.u16 {d4[1]}, [r1]! + vst1.16 {d5[1]}, [r1], r5 + vst1.u16 {d5[0]}, [r1]! + vst1.16 {d4[1]}, [r1], r5 + vst1.u16 {d4[0]}, [r1]! + vst1.16 {d5[0]}, [r1], r5 + +end_func_horz_d: + ldmfd sp!, {r4-r12, pc} @Restoring registers from stack + + + + + + + +@** +@******************************************************************************* +@* +@*ih264_intra_pred_luma_4x4_mode_vert_l +@* +@* @brief +@* Perform Intra prediction for luma_4x4 mode:Vertical_Left +@* +@* @par Description: +@* Perform Intra prediction for luma_4x4 mode:Vertical_Left ,described in sec 8.3.1.2.8 +@* +@* @param[in] pu1_src +@* UWORD8 pointer to the source +@* +@* @param[out] pu1_dst +@* UWORD8 pointer to the destination +@* +@* @param[in] src_strd +@* integer source stride +@* +@* @param[in] dst_strd +@* integer destination stride +@* +@* @param[in] ui_neighboravailability +@* availability of neighbouring pixels +@* +@* @returns +@* +@* @remarks +@* None +@* +@******************************************************************************* +@void ih264_intra_pred_luma_4x4_mode_vert_l(UWORD8 *pu1_src, +@ UWORD8 *pu1_dst, +@ WORD32 src_strd, +@ WORD32 dst_strd, +@ WORD32 ui_neighboravailability) + +@**************Variables Vs Registers***************************************** +@ r0 => *pu1_src +@ r1 => *pu1_dst +@ r2 => src_strd +@ r3 => dst_strd +@ r4 => ui_neighboravailability + + + .global ih264_intra_pred_luma_4x4_mode_vert_l_a9q + +ih264_intra_pred_luma_4x4_mode_vert_l_a9q: + + stmfd sp!, {r4-r12, r14} @store register values to stack + add r0, r0, #4 + vld1.u8 {d0}, [r0] + add r0, r0, #1 + vld1.u8 {d1}, [r0] + vext.8 d2, d1, d0, #1 + vaddl.u8 q10, d0, d1 + vaddl.u8 q11, d1, d2 + vadd.u16 q12, q10, q11 + vqrshrun.s16 d4, q10, #1 + vqrshrun.s16 d5, q12, #2 + vext.8 d6, d4, d4, #1 + vext.8 d7, d5, d5, #1 + vst1.32 {d6[0]}, [r1], r3 + vext.8 d16, d4, d4, #2 + vext.8 d17, d5, d5, #2 + vst1.32 {d7[0]}, [r1], r3 + vst1.32 {d16[0]}, [r1], r3 + vst1.32 {d17[0]}, [r1], r3 + + + +end_func_vert_l: + ldmfd sp!, {r4-r12, pc} @Restoring registers from stack + + + + + + + +@** +@******************************************************************************* +@* +@*ih264_intra_pred_luma_4x4_mode_horz_u +@* +@* @brief +@* Perform Intra prediction for luma_4x4 mode:Horizontal_Up +@* +@* @par Description: +@* Perform Intra prediction for luma_4x4 mode:Horizontal_Up ,described in sec 8.3.1.2.9 +@* +@* @param[in] pu1_src +@* UWORD8 pointer to the source +@* +@* @param[out] pu1_dst +@* UWORD8 pointer to the destination +@* +@* @param[in] src_strd +@* integer source stride +@* +@* @param[in] dst_strd +@* integer destination stride +@* +@* @param[in] ui_neighboravailability +@* availability of neighbouring pixels +@* +@* @returns +@* +@* @remarks +@* None +@* +@******************************************************************************* +@void ih264_intra_pred_luma_4x4_mode_horz_u(UWORD8 *pu1_src, +@ UWORD8 *pu1_dst, +@ WORD32 src_strd, +@ WORD32 dst_strd, +@ WORD32 ui_neighboravailability) + +@**************Variables Vs Registers***************************************** +@ r0 => *pu1_src +@ r1 => *pu1_dst +@ r2 => src_strd +@ r3 => dst_strd +@ r4 => ui_neighboravailability + + + .global ih264_intra_pred_luma_4x4_mode_horz_u_a9q + +ih264_intra_pred_luma_4x4_mode_horz_u_a9q: + + stmfd sp!, {r4-r12, r14} @store register values to stack + mov r10, r0 + vld1.u8 {d0}, [r0] + ldrb r9, [r0], #1 + vext.8 d1, d0, d0, #1 + vld1.u8 {d0[7]}, [r10] + vext.8 d2, d1, d1, #1 + vaddl.u8 q10, d0, d1 + vaddl.u8 q11, d1, d2 + vadd.u16 q12, q10, q11 + vqrshrun.s16 d4, q10, #1 + vqrshrun.s16 d5, q12, #2 + vmov d6, d4 + vext.8 d6, d5, d4, #1 + vst1.8 {d4[2]}, [r1]! + vst1.8 {d6[0]}, [r1]! + vtrn.8 d6, d5 @ + sub r5, r3, #2 + vtrn.8 d4, d6 @ + vdup.8 d7, r9 + vst1.16 {d6[0]}, [r1], r5 + vst1.16 {d6[0]}, [r1]! + vst1.16 {d5[3]}, [r1], r5 + vst1.16 {d5[3]}, [r1]! + vst1.16 {d7[3]}, [r1], r5 + vst1.32 {d7[0]}, [r1], r3 + +end_func_horz_u: + ldmfd sp!, {r4-r12, pc} @Restoring registers from stack + + diff --git a/dependencies/ih264d/common/arm/ih264_intra_pred_luma_8x8_a9q.s b/dependencies/ih264d/common/arm/ih264_intra_pred_luma_8x8_a9q.s new file mode 100644 index 00000000..352d29d8 --- /dev/null +++ b/dependencies/ih264d/common/arm/ih264_intra_pred_luma_8x8_a9q.s @@ -0,0 +1,1032 @@ +@/****************************************************************************** +@ * +@ * Copyright (C) 2015 The Android Open Source Project +@ * +@ * Licensed under the Apache License, Version 2.0 (the "License"); +@ * you may not use this file except in compliance with the License. +@ * You may obtain a copy of the License at: +@ * +@ * http://www.apache.org/licenses/LICENSE-2.0 +@ * +@ * Unless required by applicable law or agreed to in writing, software +@ * distributed under the License is distributed on an "AS IS" BASIS, +@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@ * See the License for the specific language governing permissions and +@ * limitations under the License. +@ * +@ ***************************************************************************** +@ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +@*/ +@** +@****************************************************************************** +@* @file +@* ih264_intra_pred_luma_8x8_a9q.s +@* +@* @brief +@* Contains function definitions for intra 8x8 Luma prediction . +@* +@* @author +@* Ittiam +@* +@* @par List of Functions: +@* +@* -ih264_intra_pred_luma_8x8_mode_ref_filtering_a9q +@* -ih264_intra_pred_luma_8x8_mode_vert_a9q +@* -ih264_intra_pred_luma_8x8_mode_horz_a9q +@* -ih264_intra_pred_luma_8x8_mode_dc_a9q +@* -ih264_intra_pred_luma_8x8_mode_diag_dl_a9q +@* -ih264_intra_pred_luma_8x8_mode_diag_dr_a9q +@* -ih264_intra_pred_luma_8x8_mode_vert_r_a9q +@* -ih264_intra_pred_luma_8x8_mode_horz_d_a9q +@* -ih264_intra_pred_luma_8x8_mode_vert_l_a9q +@* -ih264_intra_pred_luma_8x8_mode_horz_u_a9q +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* + +@* All the functions here are replicated from ih264_intra_pred_filters.c +@ + +.text +.p2align 2 + + .extern ih264_gai1_intrapred_luma_8x8_horz_u +.hidden ih264_gai1_intrapred_luma_8x8_horz_u +scratch_intrapred_addr_8x8: + .long ih264_gai1_intrapred_luma_8x8_horz_u - scrlb8x8l2 - 8 + +@** +@******************************************************************************* +@* +@*ih264_intra_pred_luma_8x8_mode_ref_filtering +@* +@* @brief +@* Reference sample filtering process for Intra_8x8 sample prediction +@* +@* @par Description: +@* Perform Reference sample filtering process for Intra_8x8 sample prediction ,described in sec 8.3.2.2.1 +@* +@* @param[in] pu1_src +@* UWORD8 pointer to the source +@* +@* @param[out] pu1_dst +@* UWORD8 pointer to the destination +@* +@* @param[in] src_strd +@* integer source stride [Not used] +@* +@* @param[in] dst_strd +@* integer destination stride[Not used] +@* +@* @param[in] ui_neighboravailability +@* availability of neighbouring pixels[Not used] +@* +@* @returns +@* +@* @remarks +@* None +@* +@******************************************************************************* +@void ih264_intra_pred_luma_8x8_mode_ref_filtering(UWORD8 *pu1_src, +@ UWORD8 *pu1_dst) + +@**************Variables Vs Registers***************************************** +@ r0 => *pu1_src +@ r1 => *pu1_dst + + + .global ih264_intra_pred_luma_8x8_mode_ref_filtering_a9q + +ih264_intra_pred_luma_8x8_mode_ref_filtering_a9q: + + stmfd sp!, {r4-r12, r14} @store register values to stack + vpush {d8-d15} + + vld1.u8 {q0}, [r0]! @ + vld1.u8 {q1}, [r0] + add r0, r0, #8 @ + vext.8 q2, q0, q1, #1 + vext.8 q3, q1, q1, #1 + vext.8 q4, q2, q3, #1 + vext.8 q5, q3, q3, #1 + vld1.8 {d10[7]}, [r0] @ LOADING SRC[24] AGIN TO THE END FOR p'[ 15, -1 ] = ( p[ 14, -1 ] + 3 * p[ 15, -1 ] + 2 ) >> 2 + vaddl.u8 q10, d0, d4 + vaddl.u8 q7, d0, d0 @ SPECIAL CASE FOR p'[ -1 ,7 ] = ( p[ -1, 6 ] + 3 * p[ -1, 7 ] + 2 ) >> 2 + vadd.u16 q7, q10, q7 + vaddl.u8 q11, d1, d5 + vqrshrun.s16 d14, q7, #2 + vaddl.u8 q12, d4, d8 + vaddl.u8 q13, d5, d9 + vst1.8 {d14[0]}, [r1]! + vadd.u16 q12, q10, q12 + vadd.u16 q13, q11, q13 + vaddl.u8 q9, d2, d6 + vaddl.u8 q8, d6, d10 + vqrshrun.s16 d4, q12, #2 + vqrshrun.s16 d5, q13, #2 + vadd.u16 q6, q8, q9 + vst1.8 {q2}, [r1]! + vqrshrun.s16 d6, q6, #2 + vst1.8 {d6}, [r1] + + +end_func_ref_filt: + + vpop {d8-d15} + ldmfd sp!, {r4-r12, pc} @Restoring registers from stack + + + + + + +@** +@******************************************************************************* +@* +@*ih264_intra_pred_luma_8x8_mode_vert +@* +@* @brief +@* Perform Intra prediction for luma_8x8 mode:vertical +@* +@* @par Description: +@* Perform Intra prediction for luma_8x8 mode:vertical ,described in sec 8.3.2.2.2 +@* +@* @param[in] pu1_src +@* UWORD8 pointer to the source +@* +@* @param[out] pu1_dst +@* UWORD8 pointer to the destination +@* +@* @param[in] src_strd +@* integer source stride +@* +@* @param[in] dst_strd +@* integer destination stride +@* +@* @param[in] ui_neighboravailability +@* availability of neighbouring pixels(Not used in this function) +@* +@* @returns +@* +@* @remarks +@* None +@* +@******************************************************************************* +@void ih264_intra_pred_luma_8x8_mode_vert(UWORD8 *pu1_src, +@ UWORD8 *pu1_dst, +@ WORD32 src_strd, +@ WORD32 dst_strd, +@ WORD32 ui_neighboravailability) + +@**************Variables Vs Registers***************************************** +@ r0 => *pu1_src +@ r1 => *pu1_dst +@ r2 => src_strd +@ r3 => dst_strd +@ r4 => ui_neighboravailability + + + .global ih264_intra_pred_luma_8x8_mode_vert_a9q + +ih264_intra_pred_luma_8x8_mode_vert_a9q: + + stmfd sp!, {r4-r12, r14} @store register values to stack + + add r0, r0, #9 + vld1.8 d0, [r0] + + vst1.8 d0, [r1], r3 + vst1.8 d0, [r1], r3 + vst1.8 d0, [r1], r3 + vst1.8 d0, [r1], r3 + vst1.8 d0, [r1], r3 + vst1.8 d0, [r1], r3 + vst1.8 d0, [r1], r3 + vst1.8 d0, [r1], r3 + + ldmfd sp!, {r4-r12, pc} @Restoring registers from stack + + + + + +@****************************************************************************** + + +@** +@******************************************************************************* +@* +@*ih264_intra_pred_luma_8x8_mode_horz +@* +@* @brief +@* Perform Intra prediction for luma_8x8 mode:horizontal +@* +@* @par Description: +@* Perform Intra prediction for luma_8x8 mode:horizontal ,described in sec 8.3.2.2.2 +@* +@* @param[in] pu1_src +@* UWORD8 pointer to the source +@* +@* @param[out] pu1_dst +@* UWORD8 pointer to the destination +@* +@* @param[in] src_strd +@* integer source stride +@* +@* @param[in] dst_strd +@* integer destination stride +@* +@* @param[in] ui_neighboravailability +@* availability of neighbouring pixels(Not used in this function) +@* +@* @returns +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* +@void ih264_intra_pred_luma_8x8_mode_horz(UWORD8 *pu1_src, +@ UWORD8 *pu1_dst, +@ WORD32 src_strd, +@ WORD32 dst_strd, +@ WORD32 ui_neighboravailability) +@**************Variables Vs Registers***************************************** +@ r0 => *pu1_src +@ r1 => *pu1_dst +@ r2 => src_strd +@ r3 => dst_strd +@ r4 => ui_neighboravailability + + + .global ih264_intra_pred_luma_8x8_mode_horz_a9q + +ih264_intra_pred_luma_8x8_mode_horz_a9q: + + stmfd sp!, {r14} @store register values to stack + + vld1.u8 {d0}, [r0] + mov r2, #6 + + vdup.u8 d1, d0[7] + vdup.u8 d2, d0[6] + vst1.8 {d1}, [r1], r3 + +loop_8x8_horz: + vext.8 d0, d0, d0, #6 + vst1.8 {d2}, [r1], r3 + vdup.u8 d1, d0[7] + subs r2, #2 + vdup.u8 d2, d0[6] + vst1.8 {d1}, [r1], r3 + bne loop_8x8_horz + + vext.8 d0, d0, d0, #6 + vst1.8 {d2}, [r1], r3 + + ldmfd sp!, {pc} @restoring registers from stack + + + + + +@****************************************************************************** + + +@** +@******************************************************************************* +@* +@*ih264_intra_pred_luma_8x8_mode_dc +@* +@* @brief +@* Perform Intra prediction for luma_8x8 mode:DC +@* +@* @par Description: +@* Perform Intra prediction for luma_8x8 mode:DC ,described in sec 8.3.2.2.3 +@* +@* @param[in] pu1_src +@* UWORD8 pointer to the source +@* +@* @param[out] pu1_dst +@* UWORD8 pointer to the destination +@* +@* @param[in] src_strd +@* integer source stride +@* +@* @param[in] dst_strd +@* integer destination stride +@* +@* @param[in] ui_neighboravailability +@* availability of neighbouring pixels +@* +@* @returns +@* +@* @remarks +@* None +@* +@******************************************************************************* +@void ih264_intra_pred_luma_8x8_mode_dc(UWORD8 *pu1_src, +@ UWORD8 *pu1_dst, +@ WORD32 src_strd, +@ WORD32 dst_strd, +@ WORD32 ui_neighboravailability) + +@**************Variables Vs Registers***************************************** +@ r0 => *pu1_src +@ r1 => *pu1_dst +@ r2 => src_strd +@ r3 => dst_strd +@ r4 => ui_neighboravailability + + + .global ih264_intra_pred_luma_8x8_mode_dc_a9q + +ih264_intra_pred_luma_8x8_mode_dc_a9q: + + stmfd sp!, {r4, r14} @store register values to stack + ldr r4, [sp, #8] @r4 => ui_neighboravailability + + ands r2, r4, #0x01 @CHECKING IF LEFT_AVAILABLE ELSE BRANCHING TO ONLY TOP AVAILABLE + beq top_available + ands r2, r4, #0x04 @CHECKING IF TOP_AVAILABLE ELSE BRANCHING TO ONLY LEFT AVAILABLE + beq left_available + + vld1.u8 {d0}, [r0] @BOTH LEFT AND TOP AVAILABLE + add r0, r0, #9 + vld1.u8 {d1}, [r0] + vpaddl.u8 q0, q0 + vadd.u16 d0, d0, d1 + vpaddl.u16 d0, d0 + vpaddl.u32 d0, d0 + vqrshrun.s16 d0, q0, #4 + vdup.u8 d0, d0[0] + b str_pred + +top_available: @ONLY TOP AVAILABLE + ands r2, r4, #0x04 @CHECKING TOP AVAILABILTY OR ELSE BRANCH TO NONE AVAILABLE + beq none_available + + add r0, r0, #9 + vld1.u8 {d0}, [r0] + vpaddl.u8 d0, d0 + vpaddl.u16 d0, d0 + vpaddl.u32 d0, d0 + vqrshrun.s16 d0, q0, #3 + vdup.u8 d0, d0[0] + b str_pred + +left_available: @ONLY LEFT AVAILABLE + vld1.u8 {d0}, [r0] + vpaddl.u8 d0, d0 + vpaddl.u16 d0, d0 + vpaddl.u32 d0, d0 + vqrshrun.s16 d0, q0, #3 + vdup.u8 d0, d0[0] + b str_pred + +none_available: @NONE AVAILABLE + vmov.u8 q0, #128 + +str_pred: + vst1.8 {d0}, [r1], r3 + vst1.8 {d0}, [r1], r3 + vst1.8 {d0}, [r1], r3 + vst1.8 {d0}, [r1], r3 + vst1.8 {d0}, [r1], r3 + vst1.8 {d0}, [r1], r3 + vst1.8 {d0}, [r1], r3 + vst1.8 {d0}, [r1], r3 + + ldmfd sp!, {r4, pc} @Restoring registers from stack + + + + + + +@** +@******************************************************************************* +@* +@*ih264_intra_pred_luma_8x8_mode_diag_dl +@* +@* @brief +@* Perform Intra prediction for luma_8x8 mode:Diagonal_Down_Left +@* +@* @par Description: +@* Perform Intra prediction for luma_8x8 mode:Diagonal_Down_Left ,described in sec 8.3.2.2.4 +@* +@* @param[in] pu1_src +@* UWORD8 pointer to the source +@* +@* @param[out] pu1_dst +@* UWORD8 pointer to the destination +@* +@* @param[in] src_strd +@* integer source stride +@* +@* @param[in] dst_strd +@* integer destination stride +@* +@* @param[in] ui_neighboravailability +@* availability of neighbouring pixels +@* +@* @returns +@* +@* @remarks +@* None +@* +@******************************************************************************* +@void ih264_intra_pred_luma_8x8_mode_diag_dl(UWORD8 *pu1_src, +@ UWORD8 *pu1_dst, +@ WORD32 src_strd, +@ WORD32 dst_strd, +@ WORD32 ui_neighboravailability) + +@**************Variables Vs Registers***************************************** +@ r0 => *pu1_src +@ r1 => *pu1_dst +@ r2 => src_strd +@ r3 => dst_strd +@ r4 => ui_neighboravailability + + .global ih264_intra_pred_luma_8x8_mode_diag_dl_a9q + +ih264_intra_pred_luma_8x8_mode_diag_dl_a9q: + + stmfd sp!, {r4-r12, r14} @store register values to stack + + add r0, r0, #9 + sub r5, r3, #4 + add r6, r0, #15 + vld1.8 {q0}, [r0] + vext.8 q2, q0, q0, #2 + vext.8 q1, q0, q0, #1 + vld1.8 {d5[6]}, [r6] + @ q1 = q0 shifted to left once + @ q2 = q1 shifted to left once + vaddl.u8 q10, d0, d2 @Adding for FILT121 + vaddl.u8 q11, d1, d3 + vaddl.u8 q12, d2, d4 + vaddl.u8 q13, d3, d5 + vadd.u16 q12, q10, q12 + vadd.u16 q13, q11, q13 + + vqrshrun.s16 d4, q12, #2 + vqrshrun.s16 d5, q13, #2 + @Q2 has all FILT121 values + vst1.8 {d4}, [r1], r3 + vext.8 q9, q2, q2, #1 + vext.8 q8, q9, q9, #1 + vst1.8 {d18}, [r1], r3 + vext.8 q15, q8, q8, #1 + vst1.8 {d16}, [r1], r3 + vst1.8 {d30}, [r1], r3 + vst1.32 {d4[1]}, [r1]! + vst1.32 {d5[0]}, [r1], r5 + vst1.32 {d18[1]}, [r1]! + vst1.32 {d19[0]}, [r1], r5 + vst1.32 {d16[1]}, [r1]! + vst1.32 {d17[0]}, [r1], r5 + vst1.32 {d30[1]}, [r1]! + vst1.32 {d31[0]}, [r1], r5 + + +end_func_diag_dl: + ldmfd sp!, {r4-r12, pc} @Restoring registers from stack + + + + +@** +@******************************************************************************* +@* +@*ih264_intra_pred_luma_8x8_mode_diag_dr +@* +@* @brief +@* Perform Intra prediction for luma_8x8 mode:Diagonal_Down_Right +@* +@* @par Description: +@* Perform Intra prediction for luma_8x8 mode:Diagonal_Down_Right ,described in sec 8.3.2.2.5 +@* +@* @param[in] pu1_src +@* UWORD8 pointer to the source +@* +@* @param[out] pu1_dst +@* UWORD8 pointer to the destination +@* +@* @param[in] src_strd +@* integer source stride +@* +@* @param[in] dst_strd +@* integer destination stride +@* +@* @param[in] ui_neighboravailability +@* availability of neighbouring pixels +@* +@* @returns +@* +@* @remarks +@* None +@* +@******************************************************************************* +@void ih264_intra_pred_luma_8x8_mode_diag_dr(UWORD8 *pu1_src, +@ UWORD8 *pu1_dst, +@ WORD32 src_strd, +@ WORD32 dst_strd, +@ WORD32 ui_neighboravailability) + +@**************Variables Vs Registers***************************************** +@ r0 => *pu1_src +@ r1 => *pu1_dst +@ r2 => src_strd +@ r3 => dst_strd +@ r4 => ui_neighboravailability + + + .global ih264_intra_pred_luma_8x8_mode_diag_dr_a9q + +ih264_intra_pred_luma_8x8_mode_diag_dr_a9q: + + stmfd sp!, {r4-r12, r14} @store register values to stack + + + vld1.u8 {q0}, [r0] + add r0, r0, #1 + vld1.u8 {q1}, [r0] + vext.8 q2, q1, q1, #1 + @ q1 = q0 shifted to left once + @ q2 = q1 shifted to left once + vaddl.u8 q10, d0, d2 @Adding for FILT121 + vaddl.u8 q11, d1, d3 + vaddl.u8 q12, d2, d4 + vaddl.u8 q13, d3, d5 + vadd.u16 q12, q10, q12 + vadd.u16 q13, q11, q13 + vqrshrun.s16 d4, q12, #2 + vqrshrun.s16 d5, q13, #2 + @Q2 has all FILT121 values + sub r5, r3, #4 + vext.8 q9, q2, q2, #15 + vst1.8 {d19}, [r1], r3 + vext.8 q8, q9, q9, #15 + vst1.8 {d17}, [r1], r3 + vext.8 q15, q8, q8, #15 + vst1.8 {d31}, [r1], r3 + vst1.32 {d4[1]}, [r1]! + vst1.32 {d5[0]}, [r1], r5 + vst1.32 {d18[1]}, [r1]! + vst1.32 {d19[0]}, [r1], r5 + vst1.32 {d16[1]}, [r1]! + vst1.32 {d17[0]}, [r1], r5 + vst1.32 {d30[1]}, [r1]! + vst1.32 {d31[0]}, [r1], r5 + vst1.8 {d4}, [r1], r3 + +end_func_diag_dr: + ldmfd sp!, {r4-r12, pc} @Restoring registers from stack + + + + +@** +@******************************************************************************* +@* +@*ih264_intra_pred_luma_8x8_mode_vert_r +@* +@* @brief +@* Perform Intra prediction for luma_8x8 mode:Vertical_Right +@* +@* @par Description: +@* Perform Intra prediction for luma_8x8 mode:Vertical_Right ,described in sec 8.3.2.2.6 +@* +@* @param[in] pu1_src +@* UWORD8 pointer to the source +@* +@* @param[out] pu1_dst +@* UWORD8 pointer to the destination +@* +@* @param[in] src_strd +@* integer source stride +@* +@* @param[in] dst_strd +@* integer destination stride +@* +@* @param[in] ui_neighboravailability +@* availability of neighbouring pixels +@* +@* @returns +@* +@* @remarks +@* None +@* +@******************************************************************************* +@void ih264_intra_pred_luma_8x8_mode_vert_r(UWORD8 *pu1_src, +@ UWORD8 *pu1_dst, +@ WORD32 src_strd, +@ WORD32 dst_strd, +@ WORD32 ui_neighboravailability) + +@**************Variables Vs Registers***************************************** +@ r0 => *pu1_src +@ r1 => *pu1_dst +@ r2 => src_strd +@ r3 => dst_strd +@ r4 => ui_neighboravailability + + + .global ih264_intra_pred_luma_8x8_mode_vert_r_a9q + +ih264_intra_pred_luma_8x8_mode_vert_r_a9q: + + stmfd sp!, {r4-r12, r14} @store register values to stack + + vld1.u8 {q0}, [r0] + add r0, r0, #1 + vld1.u8 {q1}, [r0] + vext.8 q2, q1, q1, #1 + @ q1 = q0 shifted to left once + @ q2 = q1 shifted to left once + vaddl.u8 q10, d0, d2 + vaddl.u8 q11, d1, d3 + vaddl.u8 q12, d2, d4 + vaddl.u8 q13, d3, d5 + vadd.u16 q12, q10, q12 + vadd.u16 q13, q11, q13 + + vqrshrun.s16 d4, q10, #1 + vqrshrun.s16 d5, q11, #1 + vqrshrun.s16 d6, q12, #2 + vqrshrun.s16 d7, q13, #2 + @Q2 has all FILT11 values + @Q3 has all FILT121 values + sub r5, r3, #6 + sub r6, r3, #4 + vst1.8 {d5}, [r1], r3 @ row 0 + vext.8 q9, q3, q3, #15 + vmov.8 q11, q9 + vext.8 q8, q2, q2, #1 + vst1.8 {d19}, [r1], r3 @row 1 + + vmov.8 q15, q8 + vext.8 q10, q2, q2, #15 + vuzp.8 q8, q9 + @row 2 + vext.8 q14, q8, q8, #1 + vst1.8 {d21}, [r1] + vst1.8 {d6[6]}, [r1], r3 + @row 3 + + vst1.16 {d29[1]}, [r1]! + vst1.32 {d7[0]}, [r1]! + vst1.16 {d7[2]}, [r1], r5 +@row 4 + vst1.16 {d19[1]}, [r1]! + vst1.32 {d5[0]}, [r1]! + vst1.16 {d5[2]}, [r1], r5 + +@row 5 + vext.8 q13, q9, q9, #1 + vst1.16 {d17[1]}, [r1]! + vst1.32 {d23[0]}, [r1]! + vst1.16 {d23[2]}, [r1], r5 + + +@row 6 + vst1.16 {d27[0]}, [r1]! + vst1.8 {d27[2]}, [r1]! + vst1.8 {d5[0]}, [r1]! + vst1.32 {d31[0]}, [r1], r6 +@row 7 + vst1.32 {d29[0]}, [r1]! + vst1.32 {d7[0]}, [r1]! + + + +end_func_vert_r: + ldmfd sp!, {r4-r12, pc} @Restoring registers from stack + + + + +@** +@******************************************************************************* +@* +@*ih264_intra_pred_luma_8x8_mode_horz_d +@* +@* @brief +@* Perform Intra prediction for luma_8x8 mode:Horizontal_Down +@* +@* @par Description: +@* Perform Intra prediction for luma_8x8 mode:Horizontal_Down ,described in sec 8.3.2.2.7 +@* +@* @param[in] pu1_src +@* UWORD8 pointer to the source +@* +@* @param[out] pu1_dst +@* UWORD8 pointer to the destination +@* +@* @param[in] src_strd +@* integer source stride +@* +@* @param[in] dst_strd +@* integer destination stride +@* +@* @param[in] ui_neighboravailability +@* availability of neighbouring pixels +@* +@* @returns +@* +@* @remarks +@* None +@* +@******************************************************************************* +@void ih264_intra_pred_luma_8x8_mode_horz_d(UWORD8 *pu1_src, +@ UWORD8 *pu1_dst, +@ WORD32 src_strd, +@ WORD32 dst_strd, +@ WORD32 ui_neighboravailability) + +@**************Variables Vs Registers***************************************** +@ r0 => *pu1_src +@ r1 => *pu1_dst +@ r2 => src_strd +@ r3 => dst_strd +@ r4 => ui_neighboravailability + + .global ih264_intra_pred_luma_8x8_mode_horz_d_a9q + +ih264_intra_pred_luma_8x8_mode_horz_d_a9q: + + stmfd sp!, {r4-r12, r14} @store register values to stack + vpush {d8-d15} + + vld1.u8 {q0}, [r0] + add r0, r0, #1 + vld1.u8 {q1}, [r0] + vext.8 q2, q1, q1, #1 + @ q1 = q0 shifted to left once + @ q2 = q1 shifted to left once + vaddl.u8 q10, d0, d2 + vaddl.u8 q11, d1, d3 + vaddl.u8 q12, d2, d4 + vaddl.u8 q13, d3, d5 + vadd.u16 q12, q10, q12 + vadd.u16 q13, q11, q13 + + vqrshrun.s16 d4, q10, #1 + vqrshrun.s16 d5, q11, #1 + vqrshrun.s16 d6, q12, #2 + vqrshrun.s16 d7, q13, #2 + @Q2 has all FILT11 values + @Q3 has all FILT121 values + vmov.8 q4, q2 + vmov.8 q5, q3 + sub r6, r3, #6 + vtrn.8 q4, q5 @ + vmov.8 q6, q4 + vmov.8 q7, q5 + sub r5, r3, #4 + vtrn.16 q6, q7 + vext.8 q8, q3, q3, #14 + @ROW 0 + vst1.8 {d17}, [r1] + vst1.16 {d10[3]}, [r1], r3 + + @ROW 1 + vst1.32 {d14[1]}, [r1]! + vst1.32 {d7[0]}, [r1], r5 + @ROW 2 + vst1.16 {d10[2]}, [r1]! + vst1.32 {d14[1]}, [r1]! + vst1.16 {d7[0]}, [r1], r6 + @ROW 3 + vst1.32 {d12[1]}, [r1]! + vst1.32 {d14[1]}, [r1], r5 + @ROW 4 + vst1.16 {d14[1]}, [r1]! + vst1.32 {d12[1]}, [r1]! + vst1.16 {d14[2]}, [r1], r6 + @ROW 5 + vst1.32 {d14[0]}, [r1]! + vst1.32 {d12[1]}, [r1], r5 + @ROW 6 + vst1.16 {d10[0]}, [r1]! + vst1.16 {d8[1]}, [r1]! + vst1.16 {d14[1]}, [r1]! + vst1.16 {d12[2]}, [r1], r6 + @ROW 7 + vst1.32 {d12[0]}, [r1]! + vst1.32 {d14[0]}, [r1], r5 + +end_func_horz_d: + vpop {d8-d15} + ldmfd sp!, {r4-r12, pc} @Restoring registers from stack + + + + + +@** +@******************************************************************************* +@* +@*ih264_intra_pred_luma_8x8_mode_vert_l +@* +@* @brief +@* Perform Intra prediction for luma_8x8 mode:Vertical_Left +@* +@* @par Description: +@* Perform Intra prediction for luma_8x8 mode:Vertical_Left ,described in sec 8.3.2.2.8 +@* +@* @param[in] pu1_src +@* UWORD8 pointer to the source +@* +@* @param[out] pu1_dst +@* UWORD8 pointer to the destination +@* +@* @param[in] src_strd +@* integer source stride +@* +@* @param[in] dst_strd +@* integer destination stride +@* +@* @param[in] ui_neighboravailability +@* availability of neighbouring pixels +@* +@* @returns +@* +@* @remarks +@* None +@* +@******************************************************************************* +@void ih264_intra_pred_luma_8x8_mode_vert_l(UWORD8 *pu1_src, +@ UWORD8 *pu1_dst, +@ WORD32 src_strd, +@ WORD32 dst_strd, +@ WORD32 ui_neighboravailability) + +@**************Variables Vs Registers***************************************** +@ r0 => *pu1_src +@ r1 => *pu1_dst +@ r2 => src_strd +@ r3 => dst_strd +@ r4 => ui_neighboravailability + + + .global ih264_intra_pred_luma_8x8_mode_vert_l_a9q + +ih264_intra_pred_luma_8x8_mode_vert_l_a9q: + + stmfd sp!, {r4-r12, r14} @Restoring registers from stack + vpush {d8-d15} + + add r0, r0, #9 + vld1.u8 {q0}, [r0] + add r0, r0, #1 + vld1.u8 {q1}, [r0] + vext.8 q2, q1, q1, #1 + vaddl.u8 q10, d0, d2 + vaddl.u8 q11, d1, d3 + vaddl.u8 q12, d2, d4 + vaddl.u8 q13, d3, d5 + vadd.u16 q12, q10, q12 + vadd.u16 q13, q11, q13 + + vqrshrun.s16 d4, q10, #1 + vqrshrun.s16 d5, q11, #1 + vqrshrun.s16 d6, q12, #2 + vext.8 q4, q2, q2, #1 + vqrshrun.s16 d7, q13, #2 + @Q2 has all FILT11 values + @Q3 has all FILT121 values + + vext.8 q5, q3, q3, #1 + @ROW 0,1 + vst1.8 {d4}, [r1], r3 + vst1.8 {d6}, [r1], r3 + + vext.8 q6, q4, q4, #1 + vext.8 q7, q5, q5, #1 + @ROW 2,3 + vst1.8 {d8}, [r1], r3 + vst1.8 {d10}, [r1], r3 + + vext.8 q8, q6, q6, #1 + vext.8 q9, q7, q7, #1 + @ROW 4,5 + vst1.8 {d12}, [r1], r3 + vst1.8 {d14}, [r1], r3 + @ROW 6,7 + vst1.8 {d16}, [r1], r3 + vst1.8 {d18}, [r1], r3 + +end_func_vert_l: + vpop {d8-d15} + ldmfd sp!, {r4-r12, pc} @Restoring registers from stack + + + + + +@** +@******************************************************************************* +@* +@*ih264_intra_pred_luma_8x8_mode_horz_u +@* +@* @brief +@* Perform Intra prediction for luma_8x8 mode:Horizontal_Up +@* +@* @par Description: +@* Perform Intra prediction for luma_8x8 mode:Horizontal_Up ,described in sec 8.3.2.2.9 +@* +@* @param[in] pu1_src +@* UWORD8 pointer to the source +@* +@* @param[out] pu1_dst +@* UWORD8 pointer to the destination +@* +@* @param[in] src_strd +@* integer source stride +@* +@* @param[in] dst_strd +@* integer destination stride +@* +@* @param[in] ui_neighboravailability +@* availability of neighbouring pixels +@* +@* @returns +@* +@* @remarks +@* None +@* +@******************************************************************************* +@void ih264_intra_pred_luma_8x8_mode_horz_u(UWORD8 *pu1_src, +@ UWORD8 *pu1_dst, +@ WORD32 src_strd, +@ WORD32 dst_strd, +@ WORD32 ui_neighboravailability) + +@**************Variables Vs Registers***************************************** +@ r0 => *pu1_src +@ r1 => *pu1_dst +@ r2 => src_strd +@ r3 => dst_strd +@ r4 => ui_neighboravailability + + .global ih264_intra_pred_luma_8x8_mode_horz_u_a9q + +ih264_intra_pred_luma_8x8_mode_horz_u_a9q: + + stmfd sp!, {r4-r12, r14} @store register values to stack + vpush {d8-d15} + + vld1.u8 {q0}, [r0] + vld1.u8 {d1[7]}, [r0] + vext.8 q1, q0, q0, #1 + vext.8 q2, q1, q1, #1 + @ LOADING V TABLE + ldr r12, scratch_intrapred_addr_8x8 +scrlb8x8l2: + add r12, r12, pc + vaddl.u8 q10, d0, d2 + vaddl.u8 q11, d1, d3 + vaddl.u8 q12, d2, d4 + vaddl.u8 q13, d3, d5 + vadd.u16 q12, q10, q12 + vadd.u16 q13, q11, q13 + vld1.u8 {q5}, [r12] + vqrshrun.s16 d4, q10, #1 + vqrshrun.s16 d5, q11, #1 + vqrshrun.s16 d6, q12, #2 + vqrshrun.s16 d7, q13, #2 + @Q2 has all FILT11 values + @Q3 has all FILT121 values + vtbl.u8 d12, {q2, q3}, d10 + vdup.u8 q7, d5[7] @ + vtbl.u8 d13, {q2, q3}, d11 + vext.8 q8, q6, q7, #2 + vext.8 q9, q8, q7, #2 + vst1.8 {d12}, [r1], r3 + vext.8 q10, q9, q7, #2 + vst1.8 {d16}, [r1], r3 + vst1.8 {d18}, [r1], r3 + vst1.8 {d20}, [r1], r3 + vst1.8 {d13}, [r1], r3 + vst1.8 {d17}, [r1], r3 + vst1.8 {d19}, [r1], r3 + vst1.8 {d21}, [r1], r3 + + +end_func_horz_u: + vpop {d8-d15} + ldmfd sp!, {r4-r12, pc} @Restoring registers from stack + + + + + + + + diff --git a/dependencies/ih264d/common/arm/ih264_iquant_itrans_recon_a9.s b/dependencies/ih264d/common/arm/ih264_iquant_itrans_recon_a9.s new file mode 100644 index 00000000..a6af1cbf --- /dev/null +++ b/dependencies/ih264d/common/arm/ih264_iquant_itrans_recon_a9.s @@ -0,0 +1,872 @@ +@/****************************************************************************** +@ * +@ * Copyright (C) 2015 The Android Open Source Project +@ * +@ * Licensed under the Apache License, Version 2.0 (the "License"); +@ * you may not use this file except in compliance with the License. +@ * You may obtain a copy of the License at: +@ * +@ * http://www.apache.org/licenses/LICENSE-2.0 +@ * +@ * Unless required by applicable law or agreed to in writing, software +@ * distributed under the License is distributed on an "AS IS" BASIS, +@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@ * See the License for the specific language governing permissions and +@ * limitations under the License. +@ * +@ ***************************************************************************** +@ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +@*/ +@** +@ ******************************************************************************* +@ * @file +@ * ih264_iquant_itrans_recon_a9.s +@ * +@ * @brief +@ * Contains function definitions for single stage inverse transform +@ * +@ * @author +@ * Mohit +@ * Harinarayanaan +@ * +@ * @par List of Functions: +@ * - ih264_iquant_itrans_recon_4x4_a9() +@ * - ih264_iquant_itrans_recon_8x8_a9() +@ * - ih264_iquant_itrans_recon_chroma_4x4_a9() +@ * +@ * @remarks +@ * None +@ * +@ ******************************************************************************* +@* +@** +@ ******************************************************************************* +@ * +@ * @brief +@ * This function performs inverse quant and Inverse transform type Ci4 for 4*4 block +@ * +@ * @par Description: +@ * Performs inverse transform Ci4 and adds the residue to get the +@ * reconstructed block +@ * +@ * @param[in] pi2_src +@ * Input 4x4 coefficients +@ * +@ * @param[in] pu1_pred +@ * Prediction 4x4 block +@ * +@ * @param[out] pu1_out +@ * Output 4x4 block +@ * +@ * @param[in] u4_qp_div_6 +@ * QP +@ * +@ * @param[in] pu2_weigh_mat +@ * Pointer to weight matrix +@ * +@ * @param[in] pred_strd, +@ * Prediction stride +@ * +@ * @param[in] out_strd +@ * Output Stride +@ * +@ *@param[in] pi2_tmp +@ * temporary buffer of size 1*16 +@ * +@ * @param[in] pu2_iscal_mat +@ * Pointer to the inverse quantization matrix +@ * +@ * @returns Void +@ * +@ * @remarks +@ * None +@ * +@ ******************************************************************************* +@ * +@void ih264_iquant_itrans_recon_4x4(WORD16 *pi2_src, +@ UWORD8 *pu1_pred, +@ UWORD8 *pu1_out, +@ WORD32 pred_strd, +@ WORD32 out_strd, +@ const UWORD16 *pu2_iscal_mat, +@ const UWORD16 *pu2_weigh_mat, +@ UWORD32 u4_qp_div_6, +@ WORD32 *pi4_tmp, +@ WORD32 iq_start_idx +@ WORD16 *pi2_dc_ld_addr) +@**************Variables Vs Registers***************************************** +@r0 => *pi2_src +@r1 => *pu1_pred +@r2 => *pu1_out +@r3 => pred_strd +@r4 => out_strd +@r5 => *pu2_iscal_mat +@r6 => *pu2_weigh_mat +@r7 => u4_qp_div_6 +@r8 => iq_start_idx +@r10=> pi2_dc_ld_addr +.text +.syntax unified +.p2align 2 + + .global ih264_iquant_itrans_recon_4x4_a9 + +ih264_iquant_itrans_recon_4x4_a9: + +@VLD4.S16 is used because the pointer is incremented by SUB_BLK_WIDTH_4x4 +@If the macro value changes need to change the instruction according to it. +@Only one shift is done in horizontal inverse because, +@if u4_qp_div_6 is lesser than 4 then shift value will be neagative and do negative left shift, in this case rnd_factor has value +@if u4_qp_div_6 is greater than 4 then shift value will be positive and do left shift, here rnd_factor is 0 + + stmfd sp!, {r4-r12, r14} @stack stores the values of the arguments + ldr r7, [sp, #52] @Loads u4_qp_div_6 + ldr r4, [sp, #40] @Loads out_strd + vdup.s32 q15, r7 @Populate the u4_qp_div_6 in Q15 + ldr r5, [sp, #44] @Loads *pu2_iscal_mat + + ldr r6, [sp, #48] @Loads *pu2_weigh_mat + + ldr r8, [sp, #60] @Loads iq_start_idx + + ldr r10, [sp, #64] @Load alternate dc address + + vpush {d8-d15} +@=======================DEQUANT FROM HERE=================================== + + vld4.s16 {d20, d21, d22, d23}, [r5] @Load pu2_iscal_mat[i], i =0..15 + vld4.s16 {d26, d27, d28, d29}, [r6] @pu2_weigh_mat[i], i =0..15 + vmul.s16 q10, q10, q13 @x[i]=(scale[i] * dequant[i]) where i = 0..7 + vld4.s16 {d16, d17, d18, d19}, [r0] @pi2_src_tmp[i], i =0..15 + + vmul.s16 q11, q11, q14 @x[i]=(scale[i] * dequant[i]) where i = 8..15 + + subs r8, r8, #1 @ if r8 == 1 => intra case , so result of subtraction is zero and Z flag is set + ldrsheq r9, [r10] @ Loads signed halfword pi2_dc_ld_addr[0], if r8==1 + + vmull.s16 q0, d16, d20 @ Q0 = p[i] = (x[i] * trns_coeff[i]) where i = 0..3 + vmull.s16 q1, d17, d21 @ Q1 = p[i] = (x[i] * trns_coeff[i]) where i = 4..7 + vmull.s16 q2, d18, d22 @ Q2 = p[i] = (x[i] * trns_coeff[i]) where i = 8..11 + vmull.s16 q3, d19, d23 @ Q3 = p[i] = (x[i] * trns_coeff[i]) where i = 12..15 + + vshl.s32 q0, q0, q15 @ Q0 = q[i] = (p[i] << (qP/6)) where i = 0..3 + vshl.s32 q1, q1, q15 @ Q1 = q[i] = (p[i] << (qP/6)) where i = 4..7 + vshl.s32 q2, q2, q15 @ Q2 = q[i] = (p[i] << (qP/6)) where i = 8..11 + vshl.s32 q3, q3, q15 @ Q3 = q[i] = (p[i] << (qP/6)) where i = 12..15 + + vqrshrn.s32 d0, q0, #0x4 @ D0 = c[i] = ((q[i] + 32) >> 4) where i = 0..3 + vqrshrn.s32 d1, q1, #0x4 @ D1 = c[i] = ((q[i] + 32) >> 4) where i = 4..7 + vqrshrn.s32 d2, q2, #0x4 @ D2 = c[i] = ((q[i] + 32) >> 4) where i = 8..11 + vqrshrn.s32 d3, q3, #0x4 @ D3 = c[i] = ((q[i] + 32) >> 4) where i = 12..15 + + vmoveq.16 d0[0], r9 @ Restore dc value in case of intra, i.e. r8 == 1 + +@========= PROCESS IDCT FROM HERE ======= +@Steps for Stage 1: +@------------------ + vld1.32 d30[0], [r1], r3 @I row Load pu1_pred buffer + vadd.s16 d4, d0, d2 @x0 = q0 + q1; + + vsub.s16 d5, d0, d2 @x1 = q0 - q1; + + vshr.s16 d8, d1, #1 @q0>>1 + vshr.s16 d9, d3, #1 @q1>>1 + + vsub.s16 d6, d8, d3 @x2 = (q0 >> 1) - q1; + vadd.s16 d7, d1, d9 @x3 = q0+ (q1 >> 1); + vld1.32 d30[1], [r1], r3 @II row Load pu1_pred buffer + + vswp d6, d7 @Reverse positions of x2 and x3 + + vsub.s16 q6, q2, q3 @x0-x3 and x1-x2 combined + vadd.s16 q5, q2, q3 @x0 + x3 and x1+x2 combined + + vld1.32 d31[0], [r1], r3 @III row Load pu1_pred buf + + vswp d12, d13 +@Steps for Stage 2: +@------------------ + vtrn.16 d10, d11 + vtrn.16 d12, d13 + vtrn.32 d10, d12 + vtrn.32 d11, d13 + vadd.s16 d14, d10, d12 @x0 = q0 + q1; + + vsub.s16 d15, d10, d12 @x1 = q0 - q1; + + vshr.s16 d18, d11, #1 @q0>>1 + vshr.s16 d19, d13, #1 @q1>>1 + + vsub.s16 d16, d18, d13 @x2 = (q0 >> 1) - q1; + vadd.s16 d17, d11, d19 @x3 = q0+ (q1 >> 1); + + vld1.32 d31[1], [r1], r3 @IV row Load pu1_pred buffer + vswp d16, d17 @Reverse positions of x2 and x3 + + vsub.s16 q11, q7, q8 @x0-x3 and x1-x2 combined + vadd.s16 q10, q7, q8 @x0 + x3 and x1+x2 combined + + vswp d22, d23 + + vrshr.s16 q10, q10, #6 @ + vrshr.s16 q11, q11, #6 + + vaddw.u8 q10, q10, d30 + vaddw.u8 q11, q11, d31 + + vqmovun.s16 d0, q10 + vqmovun.s16 d1, q11 + + vst1.32 d0[0], [r2], r4 @I row store the value + vst1.32 d0[1], [r2], r4 @II row store the value + vst1.32 d1[0], [r2], r4 @III row store the value + vst1.32 d1[1], [r2] @IV row store the value + + vpop {d8-d15} + ldmfd sp!, {r4-r12, r15} @Reload the registers from SP + + +@** +@ ******************************************************************************* +@ * +@ * @brief +@ * This function performs inverse quant and Inverse transform type Ci4 for 4*4 block +@ * +@ * @par Description: +@ * Performs inverse transform Ci4 and adds the residue to get the +@ * reconstructed block +@ * +@ * @param[in] pi2_src +@ * Input 4x4 coefficients +@ * +@ * @param[in] pu1_pred +@ * Prediction 4x4 block +@ * +@ * @param[out] pu1_out +@ * Output 4x4 block +@ * +@ * @param[in] u4_qp_div_6 +@ * QP +@ * +@ * @param[in] pu2_weigh_mat +@ * Pointer to weight matrix +@ * +@ * @param[in] pred_strd, +@ * Prediction stride +@ * +@ * @param[in] out_strd +@ * Output Stride +@ * +@ *@param[in] pi2_tmp +@ * temporary buffer of size 1*16 +@ * +@ * @param[in] pu2_iscal_mat +@ * Pointer to the inverse quantization matrix +@ * +@ * @returns Void +@ * +@ * @remarks +@ * None +@ * +@ ******************************************************************************* +@ * +@void ih264_iquant_itrans_recon_chroma_4x4(WORD16 *pi2_src, +@ UWORD8 *pu1_pred, +@ UWORD8 *pu1_out, +@ WORD32 pred_strd, +@ WORD32 out_strd, +@ const UWORD16 *pu2_iscal_mat, +@ const UWORD16 *pu2_weigh_mat, +@ UWORD32 u4_qp_div_6, +@ WORD32 *pi4_tmp +@ WORD16 *pi2_dc_src) +@**************Variables Vs Registers***************************************** +@r0 => *pi2_src +@r1 => *pu1_pred +@r2 => *pu1_out +@r3 => pred_strd +@r4 => out_strd +@r5 => *pu2_iscal_mat +@r6 => *pu2_weigh_mat +@r7 => u4_qp_div_6 + + .global ih264_iquant_itrans_recon_chroma_4x4_a9 +ih264_iquant_itrans_recon_chroma_4x4_a9: + +@VLD4.S16 is used because the pointer is incremented by SUB_BLK_WIDTH_4x4 +@If the macro value changes need to change the instruction according to it. +@Only one shift is done in horizontal inverse because, +@if u4_qp_div_6 is lesser than 4 then shift value will be neagative and do negative left shift, in this case rnd_factor has value +@if u4_qp_div_6 is greater than 4 then shift value will be positive and do left shift, here rnd_factor is 0 + + stmfd sp!, {r4-r12, r14} @stack stores the values of the arguments + ldr r7, [sp, #52] @Loads u4_qp_div_6 + ldr r4, [sp, #40] @Loads out_strd + vdup.s32 q15, r7 @Populate the u4_qp_div_6 in Q15 + ldr r5, [sp, #44] @Loads *pu2_iscal_mat + ldr r6, [sp, #48] @Loads *pu2_weigh_mat + ldr r8, [sp, #60] @loads *pi2_dc_src + + vpush {d8-d15} +@=======================DEQUANT FROM HERE=================================== + + vld4.s16 {d20, d21, d22, d23}, [r5] @Load pu2_iscal_mat[i], i =0..15 + vld4.s16 {d26, d27, d28, d29}, [r6] @pu2_weigh_mat[i], i =0..15 + vmul.s16 q10, q10, q13 @x[i]=(scale[i] * dequant[i]) where i = 0..7 + vld4.s16 {d16, d17, d18, d19}, [r0] @pi2_src_tmp[i], i =0..15 + + vmul.s16 q11, q11, q14 @x[i]=(scale[i] * dequant[i]) where i = 8..15 + + vmull.s16 q0, d16, d20 @ Q0 = p[i] = (x[i] * trns_coeff[i]) where i = 0..3 + vmull.s16 q1, d17, d21 @ Q1 = p[i] = (x[i] * trns_coeff[i]) where i = 4..7 + vmull.s16 q2, d18, d22 @ Q2 = p[i] = (x[i] * trns_coeff[i]) where i = 8..11 + vmull.s16 q3, d19, d23 @ Q3 = p[i] = (x[i] * trns_coeff[i]) where i = 12..15 + + vshl.s32 q0, q0, q15 @ Q0 = q[i] = (p[i] << (qP/6)) where i = 0..3 + vshl.s32 q1, q1, q15 @ Q1 = q[i] = (p[i] << (qP/6)) where i = 4..7 + vshl.s32 q2, q2, q15 @ Q2 = q[i] = (p[i] << (qP/6)) where i = 8..11 + vshl.s32 q3, q3, q15 @ Q3 = q[i] = (p[i] << (qP/6)) where i = 12..15 + + vqrshrn.s32 d0, q0, #0x4 @ D0 = c[i] = ((q[i] + 32) >> 4) where i = 0..3 + vqrshrn.s32 d1, q1, #0x4 @ D1 = c[i] = ((q[i] + 32) >> 4) where i = 4..7 + vqrshrn.s32 d2, q2, #0x4 @ D2 = c[i] = ((q[i] + 32) >> 4) where i = 8..11 + vqrshrn.s32 d3, q3, #0x4 @ D3 = c[i] = ((q[i] + 32) >> 4) where i = 12..15 + + ldrsh r9, [r8] @ Loads signed halfword pi2_dc_src[0] + vmov.16 d0[0], r9 @ Restore dc value since its chroma iq-it + +@========= PROCESS IDCT FROM HERE ======= +@Steps for Stage 1: +@------------------ + vld2.8 {d28, d29}, [r1], r3 @I row Load pu1_pred buffer + vadd.s16 d4, d0, d2 @x0 = q0 + q1; + + vsub.s16 d5, d0, d2 @x1 = q0 - q1; + + vshr.s16 d8, d1, #1 @q0>>1 + vshr.s16 d9, d3, #1 @q1>>1 + + vsub.s16 d6, d8, d3 @x2 = (q0 >> 1) - q1; + vadd.s16 d7, d1, d9 @x3 = q0+ (q1 >> 1); + vld2.8 {d29, d30}, [r1], r3 @II row Load pu1_pred buffer + + vswp d6, d7 @Reverse positions of x2 and x3 + + vsub.s16 q6, q2, q3 @x0-x3 and x1-x2 combined + vtrn.32 d28, d29 @ D28 -- row I and II of pu1_pred_buffer + vadd.s16 q5, q2, q3 @x0 + x3 and x1+x2 combined + + vld2.8 {d29, d30}, [r1], r3 @III row Load pu1_pred buf + + vswp d12, d13 +@Steps for Stage 2: +@------------------ + vtrn.16 d10, d11 + vtrn.16 d12, d13 + vtrn.32 d10, d12 + vtrn.32 d11, d13 + vadd.s16 d14, d10, d12 @x0 = q0 + q1; + + vsub.s16 d15, d10, d12 @x1 = q0 - q1; + + vshr.s16 d18, d11, #1 @q0>>1 + vshr.s16 d19, d13, #1 @q1>>1 + + vsub.s16 d16, d18, d13 @x2 = (q0 >> 1) - q1; + vadd.s16 d17, d11, d19 @x3 = q0+ (q1 >> 1); + + vld2.8 {d30, d31}, [r1], r3 @IV row Load pu1_pred buffer + vswp d16, d17 @Reverse positions of x2 and x3 + + vsub.s16 q11, q7, q8 @x0-x3 and x1-x2 combined + vtrn.32 d29, d30 @ D29 -- row III and IV of pu1_pred_buf + vadd.s16 q10, q7, q8 @x0 + x3 and x1+x2 combined + + vswp d22, d23 + + vrshr.s16 q10, q10, #6 @ + vrshr.s16 q11, q11, #6 + + vaddw.u8 q10, q10, d28 + vaddw.u8 q11, q11, d29 + + vld1.u8 d0, [r2], r4 @Loading out buffer 16 coeffs + vld1.u8 d1, [r2], r4 + vld1.u8 d2, [r2], r4 + vld1.u8 d3, [r2], r4 + + sub r2, r2, r4, lsl #2 + + vqmovun.s16 d20, q10 @Getting quantized coeffs + vqmovun.s16 d22, q11 + + vmovl.u8 q10, d20 @Move the coffs into 16 bit + vmovl.u8 q11, d22 @so that we can use vbit to copy + + vmov.u16 q14, #0x00ff @Copy lsb from qantized(long)coeffs + + vbit.u8 q0, q10, q14 + vbit.u8 q1, q11, q14 + + vst1.u8 d0, [r2], r4 + vst1.u8 d1, [r2], r4 + vst1.u8 d2, [r2], r4 + vst1.u8 d3, [r2] + + vpop {d8-d15} + ldmfd sp!, {r4-r12, r15} @Reload the registers from SP + + +@* +@ ******************************************************************************* +@ * +@ * @brief +@ * This function performs inverse quant and Inverse transform type Ci4 for 8*8 block +@ * +@ * @par Description: +@ * Performs inverse transform Ci8 and adds the residue to get the +@ * reconstructed block +@ * +@ * @param[in] pi2_src +@ * Input 4x4 coefficients +@ * +@ * @param[in] pu1_pred +@ * Prediction 4x4 block +@ * +@ * @param[out] pu1_out +@ * Output 4x4 block +@ * +@ * @param[in] u4_qp_div_6 +@ * QP +@ * +@ * @param[in] pu2_weigh_mat +@ * Pointer to weight matrix +@ * +@ * @param[in] pred_strd, +@ * Prediction stride +@ * +@ * @param[in] out_strd +@ * Output Stride +@ * +@ *@param[in] pi2_tmp +@ * temporary buffer of size 1*64 +@ * +@ * @param[in] pu2_iscal_mat +@ * Pointer to the inverse quantization matrix +@ * +@ * @returns Void +@ * +@ * @remarks +@ * None +@ * +@ ******************************************************************************* +@ * +@void ih264_iquant_itrans_recon_8x8(WORD16 *pi2_src, +@ UWORD8 *pu1_pred, +@ UWORD8 *pu1_out, +@ WORD32 pred_strd, +@ WORD32 out_strd, +@ const UWORD16 *pu2_iscal_mat, +@ const UWORD16 *pu2_weigh_mat, +@ UWORD32 u4_qp_div_6, +@ WORD32 *pi4_tmp, +@ WORD32 iq_start_idx) +@**************Variables Vs Registers***************************************** +@r0 => *pi2_src +@r1 => *pu1_pred +@r2 => *pu1_out +@r3 => pred_strd +@r4 => out_strd +@r5 => *pu2_iscal_mat +@r6 => *pu2_weigh_mat +@r7 => u4_qp_div_6 + + + .global ih264_iquant_itrans_recon_8x8_a9 +ih264_iquant_itrans_recon_8x8_a9: + + stmfd sp!, {r4-r12, r14} @stack stores the values of the arguments + ldr r7, [sp, #52] @Loads u4_qp_div_6 + ldr r4, [sp, #40] @Loads out_strd + + ldr r5, [sp, #44] @Loads *pu2_iscal_mat + ldr r6, [sp, #48] @Loads *pu2_weigh_mat + vdup.s32 q15, r7 @Populate the u4_qp_div_6 in Q15 + vpush {d8-d15} + +idct_8x8_begin: + +@========= DEQUANT FROM HERE =========== + + vld1.32 {q13}, [r5]! @ Q13 = dequant values row 0 + vld1.32 {q10}, [r6]! @ Q10 = scaling factors row 0 + vld1.32 {q14}, [r5]! @ Q14 = dequant values row 1 + vmul.s16 q10, q10, q13 @ Q10 = x[i] = (scale[i] * dequant[i]) where i = 0..7 + vld1.32 {q11}, [r6]! @ Q11 = scaling factors row 1 + vld1.32 {q8}, [r0]! @ Q8 = Source row 0 + vmul.s16 q11, q11, q14 @ Q11 = x[i] = (scale[i] * dequant[i]) where i = 8..15 + vmull.s16 q0, d16, d20 @ Q0 = p[i] = (x[i] * trns_coeff[i]) where i = 0..3 + vld1.32 {q9}, [r0]! @ Q8 = Source row 1 + vmull.s16 q1, d17, d21 @ Q1 = p[i] = (x[i] * trns_coeff[i]) where i = 4..7 + vmull.s16 q2, d18, d22 @ Q2 = p[i] = (x[i] * trns_coeff[i]) where i = 8..11 + vld1.32 {q13}, [r6]! @ Scaling factors row 2 + vmull.s16 q3, d19, d23 @ Q3 = p[i] = (x[i] * trns_coeff[i]) where i = 12..15 + vld1.32 {q14}, [r6]! @ Scaling factors row 3 + vshl.s32 q0, q0, q15 @ Q0 = q[i] = (p[i] << (qP/6)) where i = 0..3 + vld1.32 {q10}, [r5]! @ Q10 = Dequant values row 2 + vshl.s32 q1, q1, q15 @ Q1 = q[i] = (p[i] << (qP/6)) where i = 4..7 + vld1.32 {q8}, [r0]! @ Source Row 2 + vshl.s32 q2, q2, q15 @ Q2 = q[i] = (p[i] << (qP/6)) where i = 8..11 + vld1.32 {q11}, [r5]! @ Q11 = Dequant values row 3 + vshl.s32 q3, q3, q15 @ Q3 = q[i] = (p[i] << (qP/6)) where i = 12..15 + vld1.32 {q9}, [r0]! @ Source Row 3 + vmul.s16 q10, q10, q13 @ Dequant row2*scale matrix row 2 + vmul.s16 q11, q11, q14 @ Dequant row 3*scale matrix row 3 + vld1.32 {q4}, [r6]! @ Scaling factors row 4 + vqrshrn.s32 d0, q0, #0x6 @ D0 = c[i] = ((q[i] + 32) >> 6) where i = 0..3 + vqrshrn.s32 d1, q1, #0x6 @ D1 = c[i] = ((q[i] + 32) >> 6) where i = 4..7 + vld1.32 {q5}, [r6]! @ Scaling factors row 5 + vqrshrn.s32 d2, q2, #0x6 @ D2 = c[i] = ((q[i] + 32) >> 6) where i = 8..11 + vqrshrn.s32 d3, q3, #0x6 @ D3 = c[i] = ((q[i] + 32) >> 6) where i = 12..15 + vld1.32 {q13}, [r5]! @ Q13 = Dequant values row 4 + vmull.s16 q2, d16, d20 @ p[i] = (x[i] * trns_coeff[i]) where i=16..19 + vmull.s16 q3, d17, d21 @ p[i] = (x[i] * trns_coeff[i]) where i=20..23 + vld1.32 {q12}, [r5]! @ Q12 = Dequant values row 5 + vmull.s16 q6, d18, d22 @ p[i] = (x[i] * trns_coeff[i]) where i=24..27 + vmull.s16 q7, d19, d23 @ p[i] = (x[i] * trns_coeff[i]) where i=28..31 + + vld1.32 {q14}, [r0]! @ Source row 4 + vmul.s16 q10, q4, q13 @ Dequant row4*scale matrix row 4 + vmul.s16 q11, q5, q12 @ Dequant row5*scale matrix row 5 + vld1.32 {q9}, [r0]! @ Source row 5 + vshl.s32 q2, q2, q15 @ + vshl.s32 q3, q3, q15 @ + vld1.32 {q13}, [r6]! @ Scaling factors row 6 + vshl.s32 q6, q6, q15 @ + vshl.s32 q7, q7, q15 @ + vmull.s16 q4, d28, d20 @ i = 32..35 + vqrshrn.s32 d4, q2, #0x6 @ D4 = c[i] = ((q[i] + 32) >> 6) where i = 16..19 + vqrshrn.s32 d5, q3, #0x6 @ D5 = c[i] = ((q[i] + 32) >> 6) where i = 20..23 + vmull.s16 q5, d29, d21 @ i =36..39 + vld1.32 {q10}, [r5]! @ Dequant values row 6 + vqrshrn.s32 d6, q6, #0x6 @ D6 = c[i] = ((q[i] + 32) >> 6) where i = 24..27 + vqrshrn.s32 d7, q7, #0x6 @ D7 = c[i] = ((q[i] + 32) >> 6) where i = 28..31 + vld1.32 {q14}, [r6]! @ Scaling factors row 7 + vmull.s16 q6, d18, d22 @ + vld1.32 {q8}, [r0]! @ Source row 6 + vmull.s16 q7, d19, d23 @ + vld1.32 {q11}, [r5]! @ Dequant values row 7 + vshl.s32 q4, q4, q15 @ + vld1.32 {q9}, [r0]! @ Source row 7 + vshl.s32 q5, q5, q15 @ + + vshl.s32 q6, q6, q15 @ + vshl.s32 q7, q7, q15 @ + vmul.s16 q10, q10, q13 @ Dequant*scaling row 6 + vmul.s16 q11, q11, q14 @ Dequant*scaling row 7 + vqrshrn.s32 d8, q4, #0x6 @ D8 = c[i] = ((q[i] + 32) >> 6) where i = 32..35 + vqrshrn.s32 d9, q5, #0x6 @ D9 = c[i] = ((q[i] + 32) >> 6) where i = 36..39 + vqrshrn.s32 d10, q6, #0x6 @ D10 = c[i] = ((q[i] + 32) >> 6) where i = 40..43 + vqrshrn.s32 d11, q7, #0x6 @ D11 = c[i] = ((q[i] + 32) >> 6) where i = 44..47 + vmull.s16 q6, d16, d20 @ i= 48..51 + vmull.s16 q7, d17, d21 @ i= 52..55 + vmull.s16 q8, d18, d22 @ i=56..59 + vmull.s16 q9, d19, d23 @ i=60..63 + vshl.s32 q6, q6, q15 @ + vzip.s16 q0, q1 @Transpose + vshl.s32 q7, q7, q15 @ + vshl.s32 q8, q8, q15 @ + vzip.s16 q2, q3 @ + vshl.s32 q9, q9, q15 @ + vqrshrn.s32 d12, q6, #0x6 @ D12 = c[i] = ((q[i] + 32) >> 6) where i = 48..51 + vzip.s16 q4, q5 @Transpose + vqrshrn.s32 d13, q7, #0x6 @ D13 = c[i] = ((q[i] + 32) >> 6) where i = 52..55 + vqrshrn.s32 d14, q8, #0x6 @ D14 = c[i] = ((q[i] + 32) >> 6) where i = 56..59 + vzip.s32 q0, q2 @Transpose + vqrshrn.s32 d15, q9, #0x6 @ D15 = c[i] = ((q[i] + 32) >> 6) where i = 60..63 + +@========= PROCESS IDCT FROM HERE ======= + +@Steps for Stage 2: +@------------------ + +@ TRANSPOSE 8x8 coeffs to actual order + + vzip.s16 q6, q7 @ + + vzip.s32 q1, q3 @ + vzip.s32 q4, q6 @ + vzip.s32 q5, q7 @ + + vswp d1, d8 @ Q0/Q1 = Row order x0/x1 + vswp d3, d10 @ Q2/Q3 = Row order x2/x3 + vswp d5, d12 @ Q4/Q5 = Row order x4/x5 + vswp d7, d14 @ Q6/Q7 = Row order x6/x7 + + vswp q1, q4 @ + vshr.s16 q10, q2, #0x1 @ + vswp q3, q6 @ + +@Steps for Stage 1: +@------------------ + + vadd.s16 q8, q0, q4 @ Q8 = y0 + vsub.s16 q9, q0, q4 @ Q9 = y2 + + vsra.s16 q2, q6, #0x1 @ Q2 = y6 + vsub.s16 q6, q10, q6 @ Q6 = y4 + + vaddl.s16 q12, d14, d2 @ y3 (0-3) 1+7 + vaddl.s16 q13, d15, d3 @ y3 (4-7) 1+7 + + vsubl.s16 q10, d14, d2 @ y5 (0-3) 7-1 + vsubl.s16 q11, d15, d3 @ y5 (4-7) 7-1 + + vadd.s16 q0, q8, q2 @ Q0 = z0 + vsub.s16 q4, q8, q2 @ Q4 = z6 + + vadd.s16 q8, q9, q6 @ Q8 = z2 + vsub.s16 q2, q9, q6 @ Q2 = z4 + + vsubw.s16 q12, q12, d6 @ y3 (0-3) 1+7-3 + vsubw.s16 q13, q13, d7 @ y3 (0-7) 1+7-3 + + vshr.s16 q6, q3, #0x1 @ + + vaddw.s16 q10, q10, d10 @ + vaddw.s16 q11, q11, d11 @ + + vshr.s16 q9, q5, #0x1 @ + + vsubw.s16 q12, q12, d12 @ + vsubw.s16 q13, q13, d13 @ + + vaddw.s16 q10, q10, d18 @ + vaddw.s16 q11, q11, d19 @ + + vqmovn.s32 d12, q12 @ + vaddl.s16 q12, d10, d6 @ + vqmovn.s32 d13, q13 @ Q6 = y3 + vaddl.s16 q13, d11, d7 @ + vqmovn.s32 d18, q10 @ + vsubl.s16 q10, d10, d6 @ + vqmovn.s32 d19, q11 @ Q9 = y5 + vsubl.s16 q11, d11, d7 @ + + vshr.s16 q3, q6, #0x2 @ + + vsra.s16 q6, q9, #0x2 @ Q6 = z3 + + vaddw.s16 q12, q12, d2 @ + vaddw.s16 q13, q13, d3 @ + + vshr.s16 q1, #0x1 @ + + vsub.s16 q5, q3, q9 @ Q5 = z5 + + vsubw.s16 q10, q10, d14 @ + vsubw.s16 q11, q11, d15 @ + + vshr.s16 q7, #0x1 @ + + vaddw.s16 q12, q12, d2 @ + vaddw.s16 q13, q13, d3 @ + + vsubw.s16 q10, q10, d14 @ + vsubw.s16 q11, q11, d15 @ + + + vqmovn.s32 d14, q12 @ + vadd.s16 q1, q8, q5 @ Q1 = x1 + vqmovn.s32 d15, q13 @ Q7 = y7 + vsub.s16 q3, q8, q5 @ Q3 = x6 + vqmovn.s32 d18, q10 @ + vsub.s16 q5, q2, q6 @ Q5 = x5 + vqmovn.s32 d19, q11 @ Q9 = y1 + vadd.s16 q2, q2, q6 @ Q2 = x2 + + vshr.s16 q12, q9, #0x2 @ + vsra.s16 q9, q7, #0x2 @ Q9 = z1 + + vsub.s16 q11, q7, q12 @ Q11 = z7 + + vadd.s16 q6, q4, q9 @ Q6 = x3 + vsub.s16 q4, q4, q9 @ Q4 = x4 + + vsub.s16 q7, q0, q11 @ Q7 = x7 + vadd.s16 q0, q0, q11 @ Q0 = x0 + + vswp.s16 q3, q6 @ Q3 = x3, Q6 = x6 + + +@Steps for Stage 2: +@------------------ + +@ TRANSPOSE 8x8 coeffs to actual order + + vzip.s16 q0, q1 @ + vzip.s16 q2, q3 @ + vzip.s16 q4, q5 @ + vzip.s16 q6, q7 @ + + vzip.s32 q0, q2 @ + vzip.s32 q1, q3 @ + vzip.s32 q4, q6 @ + vzip.s32 q5, q7 @ + + vswp d1, d8 @ Q0/Q1 = Row order x0/x1 + vswp d3, d10 @ Q2/Q3 = Row order x2/x3 + vswp d5, d12 @ Q4/Q5 = Row order x4/x5 + vswp d7, d14 @ Q6/Q7 = Row order x6/x7 + + vswp q1, q4 @ + vshr.s16 q10, q2, #0x1 @ + vswp q3, q6 @ + +@Steps for Stage 3: +@------------------ + +@Repeat stage 1 again for vertical transform + + vadd.s16 q8, q0, q4 @ Q8 = y0 + vld1.32 d28, [r1], r3 @ Q12 = 0x070605....0x070605.... + vsub.s16 q9, q0, q4 @ Q9 = y2 + + vsra.s16 q2, q6, #0x1 @ Q2 = y6 + vsub.s16 q6, q10, q6 @ Q6 = y4 + + vaddl.s16 q12, d14, d2 @ + vld1.32 d29, [r1], r3 @ Q12 = 0x070605....0x070605.... + vaddl.s16 q13, d15, d3 @ + + vsubl.s16 q10, d14, d2 @ + vld1.32 d30, [r1], r3 @ Q12 = 0x070605....0x070605.... + vsubl.s16 q11, d15, d3 @ + + vadd.s16 q0, q8, q2 @ Q0 = z0 + vld1.32 d31, [r1], r3 @ Q12 = 0x070605....0x070605.... + vsub.s16 q4, q8, q2 @ Q4 = z6 + + vadd.s16 q8, q9, q6 @ Q8 = z2 + vsub.s16 q2, q9, q6 @ Q2 = z4 + + vsubw.s16 q12, q12, d6 @ + vsubw.s16 q13, q13, d7 @ + + vshr.s16 q6, q3, #0x1 @ + + vaddw.s16 q10, q10, d10 @ + vaddw.s16 q11, q11, d11 @ + + vshr.s16 q9, q5, #0x1 @ + + vsubw.s16 q12, q12, d12 @ + vsubw.s16 q13, q13, d13 @ + + vaddw.s16 q10, q10, d18 @ + vaddw.s16 q11, q11, d19 @ + + vqmovn.s32 d12, q12 @ + vaddl.s16 q12, d10, d6 @ + vqmovn.s32 d13, q13 @ Q6 = y3 + vaddl.s16 q13, d11, d7 @ + vqmovn.s32 d18, q10 @ + vsubl.s16 q10, d10, d6 @ + vqmovn.s32 d19, q11 @ Q9 = y5 + vsubl.s16 q11, d11, d7 @ + + vshr.s16 q3, q6, #0x2 @ + + vsra.s16 q6, q9, #0x2 @ Q6 = z3 + + vaddw.s16 q12, q12, d2 @ + vaddw.s16 q13, q13, d3 @ + + vshr.s16 q1, #0x1 @ + + vsub.s16 q5, q3, q9 @ Q5 = z5 + + vsubw.s16 q10, q10, d14 @ + vsubw.s16 q11, q11, d15 @ + + vshr.s16 q7, #0x1 @ + + vaddw.s16 q12, q12, d2 @ + vaddw.s16 q13, q13, d3 @ + + vsubw.s16 q10, q10, d14 @ + vsubw.s16 q11, q11, d15 @ + + vqmovn.s32 d14, q12 @ + vadd.s16 q1, q8, q5 @ Q1 = x1 + vqmovn.s32 d15, q13 @ Q7 = y7 + vsub.s16 q3, q8, q5 @ Q3 = x6 + vqmovn.s32 d18, q10 @ + vsub.s16 q5, q2, q6 @ Q5 = x5 + vqmovn.s32 d19, q11 @ Q9 = y1 + vadd.s16 q2, q2, q6 @ Q2 = x2 + + vshr.s16 q12, q9, #0x2 @ + vsra.s16 q9, q7, #0x2 @ Q9 = z1 + + vsub.s16 q11, q7, q12 @ Q11 = z7 + + vadd.s16 q6, q4, q9 @ Q6 = x3 + vsub.s16 q4, q4, q9 @ Q4 = x4 + + vsub.s16 q7, q0, q11 @ Q7 = x7 + vadd.s16 q0, q0, q11 @ Q0 = x0 + + vswp.s16 q3, q6 @ Q3 <-> Q6 + + vrshr.s16 q1, q1, #6 @ + vld1.32 d16, [r1], r3 @ Q12 = 0x070605....0x070605.... + vrshr.s16 q2, q2, #6 @ + vrshr.s16 q4, q4, #6 @ + vld1.32 d17, [r1], r3 @ Q12 = 0x070605....0x070605.... + vrshr.s16 q5, q5, #6 @ + vrshr.s16 q7, q7, #6 @ + vld1.32 d18, [r1], r3 @ Q12 = 0x070605....0x070605.... + vrshr.s16 q0, q0, #6 @ + vrshr.s16 q3, q3, #6 @ + vld1.32 d19, [r1], r3 @ Q12 = 0x070605....0x070605.... + vrshr.s16 q6, q6, #6 @ + +@ Code Added to pack sign and magnitudes + + vaddw.u8 q0, q0, d28 + vaddw.u8 q1, q1, d29 + vaddw.u8 q2, q2, d30 + vaddw.u8 q3, q3, d31 + vqmovun.s16 d0, q0 + vaddw.u8 q4, q4, d16 + vqmovun.s16 d1, q1 + vaddw.u8 q5, q5, d17 + vqmovun.s16 d2, q2 + vaddw.u8 q6, q6, d18 + vqmovun.s16 d3, q3 + vaddw.u8 q7, q7, d19 + + vqmovun.s16 d4, q4 + vst1.32 d0, [r2], r4 @ Magnitudes of 1st 4x4 block coeffs + vqmovun.s16 d5, q5 + vst1.32 d1, [r2], r4 @ Magnitudes of 1st 4x4 block coeffs + vqmovun.s16 d6, q6 + vst1.32 d2, [r2], r4 @ Magnitudes of 1st 4x4 block coeffs + vqmovun.s16 d7, q7 + vst1.32 d3, [r2], r4 @ Magnitudes of 1st 4x4 block coeffs + vst1.32 d4, [r2], r4 @ Magnitudes of 1st 4x4 block coeffs + + vst1.32 d5, [r2], r4 @ Magnitudes of 1st 4x4 block coeffs + + + vst1.32 d6, [r2], r4 @ Magnitudes of 1st 4x4 block coeffs + + + vst1.32 d7, [r2], r4 @ Magnitudes of 1st 4x4 block coeffs + +idct_8x8_end: + + vpop {d8-d15} + ldmfd sp!, {r4-r12, r15} + diff --git a/dependencies/ih264d/common/arm/ih264_iquant_itrans_recon_dc_a9.s b/dependencies/ih264d/common/arm/ih264_iquant_itrans_recon_dc_a9.s new file mode 100644 index 00000000..d12665f1 --- /dev/null +++ b/dependencies/ih264d/common/arm/ih264_iquant_itrans_recon_dc_a9.s @@ -0,0 +1,401 @@ +@/****************************************************************************** +@ * +@ * Copyright (C) 2015 The Android Open Source Project +@ * +@ * Licensed under the Apache License, Version 2.0 (the "License"); +@ * you may not use this file except in compliance with the License. +@ * You may obtain a copy of the License at: +@ * +@ * http://www.apache.org/licenses/LICENSE-2.0 +@ * +@ * Unless required by applicable law or agreed to in writing, software +@ * distributed under the License is distributed on an "AS IS" BASIS, +@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@ * See the License for the specific language governing permissions and +@ * limitations under the License. +@ * +@ ***************************************************************************** +@ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +@*/ +@** +@ ******************************************************************************* +@ * @file +@ * ih264_iquant_itrans_recon_dc_a9.s +@ * +@ * @brief +@ * Contains function definitions for single stage inverse transform +@ * +@ * @author +@ * Mohit +@ * +@ * @par List of Functions: +@ * - ih264_iquant_itrans_recon_4x4_dc_a9() +@ * - ih264_iquant_itrans_recon_8x8_dc_a9() +@ * - ih264_iquant_itrans_recon_chroma_4x4_dc_a9() +@ * +@ * @remarks +@ * None +@ * +@ ******************************************************************************* +@* +@** +@ ******************************************************************************* +@ * +@ * @brief +@ * This function performs inverse quant and Inverse transform type Ci4 for 4*4 block +@ * for dc input pattern only, i.e. only the (0,0) element of the input 4x4 block is +@ * non-zero. For complete function, refer ih264_iquant_itrans_recon_a9.s +@ * +@ * @par Description: +@ * Performs inverse transform Ci4 and adds the residue to get the +@ * reconstructed block +@ * +@ * @param[in] pi2_src +@ * Input 4x4 coefficients +@ * +@ * @param[in] pu1_pred +@ * Prediction 4x4 block +@ * +@ * @param[out] pu1_out +@ * Output 4x4 block +@ * +@ * @param[in] u4_qp_div_6 +@ * QP +@ * +@ * @param[in] pu2_weigh_mat +@ * Pointer to weight matrix +@ * +@ * @param[in] pred_strd, +@ * Prediction stride +@ * +@ * @param[in] out_strd +@ * Output Stride +@ * +@ *@param[in] pi2_tmp +@ * temporary buffer of size 1*16 +@ * +@ * @param[in] pu2_iscal_mat +@ * Pointer to the inverse quantization matrix +@ * +@ * @returns Void +@ * +@ * @remarks +@ * None +@ * +@ ******************************************************************************* +@ * +@void ih264_iquant_itrans_recon_4x4_dc(WORD16 *pi2_src, +@ UWORD8 *pu1_pred, +@ UWORD8 *pu1_out, +@ WORD32 pred_strd, +@ WORD32 out_strd, +@ const UWORD16 *pu2_iscal_mat, +@ const UWORD16 *pu2_weigh_mat, +@ UWORD32 u4_qp_div_6, +@ WORD32 *pi4_tmp, +@ WORD32 iq_start_idx +@ WORD16 *pi2_dc_ld_addr) +@**************Variables Vs Registers***************************************** +@r0 => *pi2_src +@r1 => *pu1_pred +@r2 => *pu1_out +@r3 => pred_strd +@r4 => out_strd +@r5 => *pu2_iscal_mat +@r6 => *pu2_weigh_mat +@r7 => u4_qp_div_6 +@r9 => iq_start_idx +@unused => pi2_dc_ld_addr + +.text +.syntax unified +.p2align 2 + + .global ih264_iquant_itrans_recon_4x4_dc_a9 + +ih264_iquant_itrans_recon_4x4_dc_a9: + +@Only one shift is done in horizontal inverse because, +@if u4_qp_div_6 is lesser than 4 then shift value will be neagative and do negative left shift, in this case rnd_factor has value +@if u4_qp_div_6 is greater than 4 then shift value will be positive and do left shift, here rnd_factor is 0 + + stmfd sp!, {r4-r10, r14} @stack stores the values of the arguments + ldr r5, [sp, #36] @Loads *pu2_iscal_mat + ldr r6, [sp, #40] @Loads *pu2_weigh_mat + ldrsh r8, [r0] @load pi2_src[0], SH for signed halfword load + ldrh r6, [r6] @load pu2_weight_mat[0] , H for unsigned halfword load + ldrh r5, [r5] @load pu2_iscal_mat[0] , H for unsigned halfword load +@=======================DEQUANT FROM HERE=================================== + mul r6, r6, r5 @pu2_iscal_mat[0]*pu2_weigh_mat[0] + ldr r7, [sp, #44] @Loads u4_qp_div_6 + mul r6, r6, r8 @pi2_src[0]*pu2_iscal_mat[0]*pu2_weigh_mat[0] + ldr r4, [sp, #32] @Loads out_strd + ldr r9, [sp, #52] @Loads iq_start_idx + + lsl r6, r6, r7 @(pi2_src[0]*pu2_iscal_mat[0]*pu2_weigh_mat[0])< intra case , so result of subtraction is zero and Z flag is set + ldrsheq r10, [r0] @ Loads signed halfword pi2_src[0], if r9==1 + moveq r6, r10 @ Restore dc value in case of intra, i.e. r9 == 1 + + add r6, r6, #32 @i_macro = q0 + 32 + asr r6, r6, #6 @i_macro >>6 = DC output of 2-stage transform + vdup.s16 q0, r6 @copy transform output to Q0 + + vld1.32 d30[0], [r1], r3 @I row Load pu1_pred buffer + + vld1.32 d30[1], [r1], r3 @II row Load pu1_pred buffer + + vld1.32 d31[0], [r1], r3 @III row Load pu1_pred buf + + vld1.32 d31[1], [r1], r3 @IV row Load pu1_pred buffer + vaddw.u8 q10, q0, d30 + + vaddw.u8 q11, q0, d31 + + vqmovun.s16 d0, q10 + + vst1.32 d0[0], [r2], r4 @I row store the value + vqmovun.s16 d1, q11 + vst1.32 d0[1], [r2], r4 @II row store the value + vst1.32 d1[0], [r2], r4 @III row store the value + vst1.32 d1[1], [r2] @IV row store the value + + ldmfd sp!, {r4-r10, r15} @Reload the registers from SP + + + + +@* +@ ******************************************************************************* +@ * +@ * @brief +@ * This function performs inverse quant and Inverse transform type Ci4 for 8*8 block +@ * for dc input pattern only, i.e. only the (0,0) element of the input 8x8 block is +@ * non-zero. For complete function, refer ih264_iquant_itrans_recon_a9.s +@ * +@ * @par Description: +@ * Performs inverse transform Ci8 and adds the residue to get the +@ * reconstructed block +@ * +@ * @param[in] pi2_src +@ * Input 4x4 coefficients +@ * +@ * @param[in] pu1_pred +@ * Prediction 4x4 block +@ * +@ * @param[out] pu1_out +@ * Output 4x4 block +@ * +@ * @param[in] u4_qp_div_6 +@ * QP +@ * +@ * @param[in] pu2_weigh_mat +@ * Pointer to weight matrix +@ * +@ * @param[in] pred_strd, +@ * Prediction stride +@ * +@ * @param[in] out_strd +@ * Output Stride +@ * +@ *@param[in] pi2_tmp +@ * temporary buffer of size 1*64 +@ * +@ * @param[in] pu2_iscal_mat +@ * Pointer to the inverse quantization matrix +@ * +@ * @returns Void +@ * +@ * @remarks +@ * None +@ * +@ ******************************************************************************* +@ * +@void ih264_iquant_itrans_recon_8x8_dc(WORD16 *pi2_src, +@ UWORD8 *pu1_pred, +@ UWORD8 *pu1_out, +@ WORD32 pred_strd, +@ WORD32 out_strd, +@ const UWORD16 *pu2_iscal_mat, +@ const UWORD16 *pu2_weigh_mat, +@ UWORD32 u4_qp_div_6, +@ WORD32 *pi4_tmp, +@ WORD32 iq_start_idx) +@**************Variables Vs Registers***************************************** +@r0 => *pi2_src +@r1 => *pu1_pred +@r2 => *pu1_out +@r3 => pred_strd +@r4 => out_strd +@r5 => *pu2_iscal_mat +@r6 => *pu2_weigh_mat +@r7 => u4_qp_div_6 + + + .global ih264_iquant_itrans_recon_8x8_dc_a9 +ih264_iquant_itrans_recon_8x8_dc_a9: + + stmfd sp!, {r4-r8, r14} @stack stores the values of the arguments + ldr r5, [sp, #28] @Loads *pu2_iscal_mat + ldr r6, [sp, #32] @Loads *pu2_weigh_mat + ldrsh r8, [r0] @load pi2_src[0], SH for signed halfword load + ldrh r6, [r6] @load pu2_weight_mat[0] , H for unsigned halfword load + ldrh r5, [r5] @load pu2_iscal_mat[0] , H for unsigned halfword load +@=======================DEQUANT FROM HERE=================================== + mul r6, r6, r5 @pu2_iscal_mat[0]*pu2_weigh_mat[0] + ldr r7, [sp, #36] @Loads u4_qp_div_6 + mul r6, r6, r8 @pi2_src[0]*pu2_iscal_mat[0]*pu2_weigh_mat[0] + ldr r4, [sp, #24] @Loads out_strd + + vpush {d8-d15} + lsl r6, r6, r7 @(pi2_src[0]*pu2_iscal_mat[0]*pu2_weigh_mat[0])<>6 = DC output of 2-stage transform + vdup.s16 q8, r6 @copy transform output to Q0 + + vld1.32 d24, [r1], r3 @ Q12 = 0x070605....0x070605.... + + vld1.32 d25, [r1], r3 @ Q12 = 0x070605....0x070605.... + + vld1.32 d26, [r1], r3 @ Q12 = 0x070605....0x070605.... + vaddw.u8 q0, q8, d24 + vld1.32 d27, [r1], r3 @ Q12 = 0x070605....0x070605.... + vaddw.u8 q1, q8, d25 + vld1.32 d28, [r1], r3 @ Q12 = 0x070605....0x070605.... + vaddw.u8 q2, q8, d26 + vld1.32 d29, [r1], r3 @ Q12 = 0x070605....0x070605.... + vaddw.u8 q3, q8, d27 + vld1.32 d30, [r1], r3 @ Q12 = 0x070605....0x070605.... + vaddw.u8 q4, q8, d28 + vld1.32 d31, [r1], r3 @ Q12 = 0x070605....0x070605.... + +@ Code Added to pack sign and magnitudes + + + vqmovun.s16 d0, q0 + vaddw.u8 q5, q8, d29 + vqmovun.s16 d1, q1 + vaddw.u8 q6, q8, d30 + vqmovun.s16 d2, q2 + vqmovun.s16 d3, q3 + vaddw.u8 q7, q8, d31 + vqmovun.s16 d4, q4 + vqmovun.s16 d5, q5 + vst1.32 d0, [r2], r4 @ Magnitudes of 1st 4x4 block coeffs + vqmovun.s16 d6, q6 + vst1.32 d1, [r2], r4 @ Magnitudes of 1st 4x4 block coeffs + vqmovun.s16 d7, q7 + vst1.32 d2, [r2], r4 @ Magnitudes of 1st 4x4 block coeffs + vst1.32 d3, [r2], r4 @ Magnitudes of 1st 4x4 block coeffs + vst1.32 d4, [r2], r4 @ Magnitudes of 1st 4x4 block coeffs + vst1.32 d5, [r2], r4 @ Magnitudes of 1st 4x4 block coeffs + vst1.32 d6, [r2], r4 @ Magnitudes of 1st 4x4 block coeffs + vst1.32 d7, [r2], r4 @ Magnitudes of 1st 4x4 block coeffs + + vpop {d8-d15} + ldmfd sp!, {r4-r8, r15} + + +@ * +@ ******************************************************************************** +@ * +@ * @brief This function reconstructs a 4x4 sub block from quantized resiude and +@ * prediction buffer if only dc value is present for residue +@ * +@ * @par Description: +@ * The quantized residue is first inverse quantized, +@ * This inverse quantized content is added to the prediction buffer to recon- +@ * struct the end output +@ * +@ * @param[in] pi2_src +@ * quantized dc coeffiient +@ * +@ * @param[in] pu1_pred +@ * prediction 4x4 block in interleaved format +@ * +@ * @param[in] pred_strd, +@ * Prediction buffer stride in interleaved format +@ * +@ * @param[in] out_strd +@ * recon buffer Stride +@ * +@ * @returns none +@ * +@ * @remarks none +@ * +@ ******************************************************************************* +@ * +@ void ih264_iquant_itrans_recon_chroma_4x4_dc(WORD16 *pi2_src, +@ UWORD8 *pu1_pred, +@ UWORD8 *pu1_out, +@ WORD32 pred_strd, +@ WORD32 out_strd, +@ const UWORD16 *pu2_iscal_mat, +@ const UWORD16 *pu2_weigh_mat, +@ UWORD32 u4_qp_div_6, +@ WORD16 *pi2_tmp, +@ WORD16 *pi2_dc_src) +@ Register Usage +@ r0 : pi2_src +@ r1 : pu1_pred +@ r2 : pu1_out +@ r3 : pred_strd +@ Neon registers d0-d7, d16-d30 are used +@ No need for pushing arm and neon registers + .global ih264_iquant_itrans_recon_chroma_4x4_dc_a9 +ih264_iquant_itrans_recon_chroma_4x4_dc_a9: + + ldr r0, [sp, #20] + vld1.s16 d0, [r0] @load pi2_dc_src + + ldr r0, [sp] @load out_strd + + vld2.s8 {d2, d3}, [r1], r3 @load pred plane 1 => d2 &pred palne 2 => d3 + vld2.s8 {d3, d4}, [r1], r3 + vrshr.s16 d0, d0, #6 @i_macro = ((q0 + 32) >> 6); + vld2.s8 {d4, d5}, [r1], r3 + vld2.s8 {d5, d6}, [r1], r3 + + vdup.s16 q0, d0[0] @duplicate pi2_sr[0] + mov r1, r2 @backup pu1_out + + vtrn.32 d2, d3 @mov the 4 coeffs of current block to d2 + vtrn.32 d4, d5 + + vmov.u16 q15, #0x00ff + + + vld1.u8 d18, [r2], r0 @load out [8 bit size) -8 coeffs + vaddw.u8 q1, q0, d2 @Add pred + vld1.u8 d19, [r2], r0 + vaddw.u8 q2, q0, d4 + vld1.u8 d20, [r2], r0 + vld1.u8 d21, [r2], r0 + + vqmovun.s16 d2, q1 + vqmovun.s16 d4, q2 + + vmovl.u8 q1, d2 + vmovl.u8 q2, d4 + + vbit.u8 q9, q1, q15 + vbit.u8 q10, q2, q15 + + vst1.u8 d18, [r1], r0 @store out + vst1.u8 d19, [r1], r0 + vst1.u8 d20, [r1], r0 + vst1.u8 d21, [r1], r0 + + bx lr + + + + + + + diff --git a/dependencies/ih264d/common/arm/ih264_mem_fns_neon.s b/dependencies/ih264d/common/arm/ih264_mem_fns_neon.s new file mode 100644 index 00000000..b9595d70 --- /dev/null +++ b/dependencies/ih264d/common/arm/ih264_mem_fns_neon.s @@ -0,0 +1,272 @@ +@/****************************************************************************** +@ * +@ * Copyright (C) 2015 The Android Open Source Project +@ * +@ * Licensed under the Apache License, Version 2.0 (the "License"); +@ * you may not use this file except in compliance with the License. +@ * You may obtain a copy of the License at: +@ * +@ * http://www.apache.org/licenses/LICENSE-2.0 +@ * +@ * Unless required by applicable law or agreed to in writing, software +@ * distributed under the License is distributed on an "AS IS" BASIS, +@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@ * See the License for the specific language governing permissions and +@ * limitations under the License. +@ * +@ ***************************************************************************** +@ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +@*/ +@** +@ ******************************************************************************* +@ * @file +@ * ih264_mem_fns_neon.s +@ * +@ * @brief +@ * Contains function definitions for memory manipulation +@ * +@ * @author +@ * Naveen SR +@ * +@ * @par List of Functions: +@ * - ih264_memcpy_mul_8_a9q() +@ * - ih264_memcpy_a9q() +@ * - ih264_memset_mul_8_a9q() +@ * - ih264_memset_a9q() +@ * - ih264_memset_16bit_mul_8_a9q() +@ * - ih264_memset_a9q() +@ * +@ * @remarks +@ * None +@ * +@ ******************************************************************************* +@* + +@** +@******************************************************************************* +@* +@* @brief +@* memcpy of a 1d array +@* +@* @par Description: +@* Does memcpy of 8bit data from source to destination for 8,16 or 32 number of bytes +@* +@* @param[in] pu1_dst +@* UWORD8 pointer to the destination +@* +@* @param[in] pu1_src +@* UWORD8 pointer to the source +@* +@* @param[in] num_bytes +@* number of bytes to copy +@* @returns +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* +@void ih264_memcpy_mul_8(UWORD8 *pu1_dst, +@ UWORD8 *pu1_src, +@ UWORD32 num_bytes) +@**************Variables Vs Registers************************* +@ r0 => *pu1_dst +@ r1 => *pu1_src +@ r2 => num_bytes + +.text +.p2align 2 + + + .global ih264_memcpy_mul_8_a9q + +ih264_memcpy_mul_8_a9q: + +loop_neon_memcpy_mul_8: + @ Memcpy 8 bytes + vld1.8 d0, [r1]! + vst1.8 d0, [r0]! + + subs r2, r2, #8 + bne loop_neon_memcpy_mul_8 + bx lr + + + +@******************************************************************************* +@* +@void ih264_memcpy(UWORD8 *pu1_dst, +@ UWORD8 *pu1_src, +@ UWORD32 num_bytes) +@**************Variables Vs Registers************************* +@ r0 => *pu1_dst +@ r1 => *pu1_src +@ r2 => num_bytes + + + + .global ih264_memcpy_a9q + +ih264_memcpy_a9q: + subs r2, #8 + blt memcpy +loop_neon_memcpy: + @ Memcpy 8 bytes + vld1.8 d0, [r1]! + vst1.8 d0, [r0]! + + subs r2, #8 + bge loop_neon_memcpy + cmp r2, #-8 + bxeq lr + +memcpy: + add r2, #8 + +loop_memcpy: + ldrb r3, [r1], #1 + strb r3, [r0], #1 + subs r2, #1 + bne loop_memcpy + bx lr + + + + +@void ih264_memset_mul_8(UWORD8 *pu1_dst, +@ UWORD8 value, +@ UWORD32 num_bytes) +@**************Variables Vs Registers************************* +@ r0 => *pu1_dst +@ r1 => value +@ r2 => num_bytes + + + + + + .global ih264_memset_mul_8_a9q + +ih264_memset_mul_8_a9q: + +@ Assumptions: numbytes is either 8, 16 or 32 + vdup.8 d0, r1 +loop_memset_mul_8: + @ Memset 8 bytes + vst1.8 d0, [r0]! + + subs r2, r2, #8 + bne loop_memset_mul_8 + + bx lr + + + + +@void ih264_memset(UWORD8 *pu1_dst, +@ UWORD8 value, +@ UWORD8 num_bytes) +@**************Variables Vs Registers************************* +@ r0 => *pu1_dst +@ r1 => value +@ r2 => num_bytes + + + + .global ih264_memset_a9q + +ih264_memset_a9q: + subs r2, #8 + blt memset + vdup.8 d0, r1 +loop_neon_memset: + @ Memcpy 8 bytes + vst1.8 d0, [r0]! + + subs r2, #8 + bge loop_neon_memset + cmp r2, #-8 + bxeq lr + +memset: + add r2, #8 + +loop_memset: + strb r1, [r0], #1 + subs r2, #1 + bne loop_memset + bx lr + + + + +@void ih264_memset_16bit_mul_8(UWORD16 *pu2_dst, +@ UWORD16 value, +@ UWORD32 num_words) +@**************Variables Vs Registers************************* +@ r0 => *pu2_dst +@ r1 => value +@ r2 => num_words + + + + + + .global ih264_memset_16bit_mul_8_a9q + +ih264_memset_16bit_mul_8_a9q: + +@ Assumptions: num_words is either 8, 16 or 32 + + @ Memset 8 words + vdup.16 d0, r1 +loop_memset_16bit_mul_8: + vst1.16 d0, [r0]! + vst1.16 d0, [r0]! + + subs r2, r2, #8 + bne loop_memset_16bit_mul_8 + + bx lr + + + + +@void ih264_memset_16bit(UWORD16 *pu2_dst, +@ UWORD16 value, +@ UWORD32 num_words) +@**************Variables Vs Registers************************* +@ r0 => *pu2_dst +@ r1 => value +@ r2 => num_words + + + + .global ih264_memset_16bit_a9q + +ih264_memset_16bit_a9q: + subs r2, #8 + blt memset_16bit + vdup.16 d0, r1 +loop_neon_memset_16bit: + @ Memset 8 words + vst1.16 d0, [r0]! + vst1.16 d0, [r0]! + + subs r2, #8 + bge loop_neon_memset_16bit + cmp r2, #-8 + bxeq lr + +memset_16bit: + add r2, #8 + +loop_memset_16bit: + strh r1, [r0], #2 + subs r2, #1 + bne loop_memset_16bit + bx lr + + + + diff --git a/dependencies/ih264d/common/arm/ih264_padding_neon.s b/dependencies/ih264d/common/arm/ih264_padding_neon.s new file mode 100644 index 00000000..819b0b33 --- /dev/null +++ b/dependencies/ih264d/common/arm/ih264_padding_neon.s @@ -0,0 +1,647 @@ +@/****************************************************************************** +@ * +@ * Copyright (C) 2015 The Android Open Source Project +@ * +@ * Licensed under the Apache License, Version 2.0 (the "License"); +@ * you may not use this file except in compliance with the License. +@ * You may obtain a copy of the License at: +@ * +@ * http://www.apache.org/licenses/LICENSE-2.0 +@ * +@ * Unless required by applicable law or agreed to in writing, software +@ * distributed under the License is distributed on an "AS IS" BASIS, +@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@ * See the License for the specific language governing permissions and +@ * limitations under the License. +@ * +@ ***************************************************************************** +@ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +@*/ +@* +@ ******************************************************************************* +@ * @file +@ * ih264_padding_neon.s +@ * +@ * @brief +@ * Contains function definitions padding +@ * +@ * @author +@ * Ittiam +@ * +@ * @par List of Functions: +@ * - ih264_pad_top_a9q() +@ * - ih264_pad_left_luma_a9q() +@ * - ih264_pad_left_chroma_a9q() +@ * - ih264_pad_right_luma_a9q() +@ * - ih264_pad_right_chroma_a9q() +@ * +@ * @remarks +@ * None +@ * +@ ******************************************************************************* +@* + + +@** +@******************************************************************************* +@* +@* @brief pad at the top of a 2d array +@* +@* @par Description: +@* The top row of a 2d array is replicated for pad_size times at the top +@* +@* @param[in] pu1_src +@* UWORD8 pointer to the source +@* +@* @param[in] src_strd +@* integer source stride +@* +@* @param[in] wd +@* integer width of the array +@* +@* @param[in] pad_size +@* integer -padding size of the array +@* +@* @returns none +@* +@* @remarks none +@* +@******************************************************************************* +@* +@void ih264_pad_top(UWORD8 *pu1_src, +@ WORD32 src_strd, +@ WORD32 wd, +@ WORD32 pad_size) +@**************Variables Vs Registers************************* +@ r0 => *pu1_src +@ r1 => src_strd +@ r2 => wd +@ r3 => pad_size + +.text +.p2align 2 + + .global ih264_pad_top_a9q + +ih264_pad_top_a9q: + + stmfd sp!, {r4-r11, lr} @stack stores the values of the arguments + + sub r5, r0, r1 + neg r6, r1 + +loop_neon_memcpy_mul_16: + @ Load 16 bytes + vld1.8 {d0, d1}, [r0]! + mov r4, r5 + mov r7, r3 + add r5, r5, #16 + +loop_neon_pad_top: + vst1.8 {d0, d1}, [r4], r6 + subs r7, r7, #1 + bne loop_neon_pad_top + + subs r2, r2, #16 + bne loop_neon_memcpy_mul_16 + + ldmfd sp!, {r4-r11, pc} @Reload the registers from SP + + + + +@** +@******************************************************************************* +@* +@* @brief +@* Padding (luma block) at the left of a 2d array +@* +@* @par Description: +@* The left column of a 2d array is replicated for pad_size times at the left +@* +@* +@* @param[in] pu1_src +@* UWORD8 pointer to the source +@* +@* @param[in] src_strd +@* integer source stride +@* +@* @param[in] ht +@* integer height of the array +@* +@* @param[in] wd +@* integer width of the array +@* +@* @param[in] pad_size +@* integer -padding size of the array +@* +@* @param[in] ht +@* integer height of the array +@* +@* @param[in] wd +@* integer width of the array +@* +@* @returns +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* +@#if PAD_LEFT_LUMA == C +@void ih264_pad_left_luma(UWORD8 *pu1_src, +@ WORD32 src_strd, +@ WORD32 ht, +@ WORD32 pad_size) +@**************Variables Vs Registers************************* +@ r0 => *pu1_src +@ r1 => src_strd +@ r2 => ht +@ r3 => pad_size + + + + .global ih264_pad_left_luma_a9q + +ih264_pad_left_luma_a9q: + + stmfd sp!, {r4-r11, lr} @stack stores the values of the arguments + + + sub r4, r0, r3 + sub r6, r1, #16 + subs r5, r3, #16 + bne loop_32 +loop_16: @ /*hard coded for width=16 ,height =8,16*/ + ldrb r8, [r0], r1 + ldrb r9, [r0], r1 + vdup.u8 q0, r8 + ldrb r10, [r0], r1 + vst1.8 {q0}, [r4], r1 @ 16 bytes store + vdup.u8 q1, r9 + vst1.8 {q1}, [r4], r1 @ 16 bytes store + ldrb r11, [r0], r1 + vdup.u8 q2, r10 + vdup.u8 q3, r11 + vst1.8 {q2}, [r4], r1 @ 16 bytes store + ldrb r8, [r0], r1 + vst1.8 {q3}, [r4], r1 @ 16 bytes store + ldrb r9, [r0], r1 + vdup.u8 q0, r8 + ldrb r10, [r0], r1 + vst1.8 {q0}, [r4], r1 @ 16 bytes store + vdup.u8 q1, r9 + ldrb r11, [r0], r1 + vst1.8 {q1}, [r4], r1 @ 16 bytes store + vdup.u8 q2, r10 + vdup.u8 q3, r11 + subs r2, r2, #8 + vst1.8 {q2}, [r4], r1 @ 16 bytes store + vst1.8 {q3}, [r4], r1 @ 16 bytes store + bne loop_16 + b end_func + +loop_32: @ /*hard coded for width=32 ,height =8,16*/ + ldrb r8, [r0], r1 + ldrb r9, [r0], r1 + vdup.u8 q0, r8 + ldrb r10, [r0], r1 + vst1.8 {q0}, [r4]! @ 16 bytes store + vdup.u8 q1, r9 + vst1.8 {q0}, [r4], r6 + vst1.8 {q1}, [r4]! @ 16 bytes store + vdup.u8 q2, r10 + vst1.8 {q1}, [r4], r6 @ 16 bytes store + ldrb r11, [r0], r1 + vst1.8 {q2}, [r4]! @ 16 bytes store + vdup.u8 q3, r11 + vst1.8 {q2}, [r4], r6 @ 16 bytes store + ldrb r8, [r0], r1 + vst1.8 {q3}, [r4]! @ 16 bytes store + vdup.u8 q0, r8 + ldrb r9, [r0], r1 + vst1.8 {q3}, [r4], r6 @ 16 bytes store + ldrb r10, [r0], r1 + vst1.8 {q0}, [r4]! @ 16 bytes store + vdup.u8 q1, r9 + vst1.8 {q0}, [r4], r6 @ 16 bytes store + ldrb r11, [r0], r1 + vst1.8 {q1}, [r4]! @ 16 bytes store + vdup.u8 q2, r10 + vst1.8 {q1}, [r4], r6 @ 16 bytes store + vst1.8 {q2}, [r4]! @ 16 bytes store + vdup.u8 q3, r11 + vst1.8 {q2}, [r4], r6 @ 16 bytes store + subs r2, r2, #8 + vst1.8 {q3}, [r4]! @ 16 bytes store + vst1.8 {q3}, [r4], r6 @ 16 bytes store + bne loop_32 + + + +end_func: + ldmfd sp!, {r4-r11, pc} @Reload the registers from SP + + + + + +@** +@******************************************************************************* +@* +@* @brief +@* Padding (chroma block) at the left of a 2d array +@* +@* @par Description: +@* The left column of a 2d array is replicated for pad_size times at the left +@* +@* +@* @param[in] pu1_src +@* UWORD8 pointer to the source +@* +@* @param[in] src_strd +@* integer source stride +@* +@* @param[in] ht +@* integer height of the array +@* +@* @param[in] wd +@* integer width of the array (each colour component) +@* +@* @param[in] pad_size +@* integer -padding size of the array +@* +@* @param[in] ht +@* integer height of the array +@* +@* @param[in] wd +@* integer width of the array +@* +@* @returns +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* +@#if PAD_LEFT_CHROMA == C +@void ih264_pad_left_chroma(UWORD8 *pu1_src, +@ WORD32 src_strd, +@ WORD32 ht, +@ WORD32 pad_size) +@{ +@ r0 => *pu1_src +@ r1 => src_strd +@ r2 => ht +@ r3 => pad_size + + + + .global ih264_pad_left_chroma_a9q + +ih264_pad_left_chroma_a9q: + + stmfd sp!, {r4-r11, lr} @stack stores the values of the arguments + + sub r4, r0, r3 + sub r6, r1, #16 + + +loop_32_l_c: @ /*hard coded for width=32 ,height =4,8,12*/ + ldrh r8, [r0], r1 + ldrh r9, [r0], r1 + vdup.u16 q0, r8 + ldrh r10, [r0], r1 + vst1.8 {q0}, [r4]! @ 16 bytes store + vdup.u16 q1, r9 + vst1.8 {q0}, [r4], r6 @ 16 bytes store + ldrh r11, [r0], r1 + vst1.8 {q1}, [r4]! @ 16 bytes store + vdup.u16 q2, r10 + vst1.8 {q1}, [r4], r6 @ 16 bytes store + vdup.u16 q3, r11 + vst1.8 {q2}, [r4]! @ 16 bytes store + vst1.8 {q2}, [r4], r6 @ 16 bytes store + subs r2, r2, #4 + vst1.8 {q3}, [r4]! @ 16 bytes store + vst1.8 {q3}, [r4], r6 @ 16 bytes store + + + beq end_func_l_c @/* Branching when ht=4*/ + + ldrh r8, [r0], r1 + ldrh r9, [r0], r1 + vdup.u16 q0, r8 + ldrh r10, [r0], r1 + vst1.8 {q0}, [r4]! @ 16 bytes store + vdup.u16 q1, r9 + vst1.8 {q0}, [r4], r6 + ldrh r11, [r0], r1 + vst1.8 {q1}, [r4]! @ 16 bytes store + vdup.u16 q2, r10 + vst1.8 {q1}, [r4], r6 @ 16 bytes store + vdup.u16 q3, r11 + vst1.8 {q2}, [r4]! @ 16 bytes store + vst1.8 {q2}, [r4], r6 @ 16 bytes store + subs r2, r2, #4 + vst1.8 {q3}, [r4]! @ 16 bytes store + vst1.8 {q3}, [r4], r6 @ 16 bytes store + + beq end_func_l_c @/* Branching when ht=8*/ + bne loop_32_l_c + + ldrh r8, [r0], r1 + ldrh r9, [r0], r1 + vdup.u16 q0, r8 + ldrh r10, [r0], r1 + vst1.8 {q0}, [r4]! @ 16 bytes store + vdup.u16 q1, r9 + vst1.8 {q0}, [r4], r6 + ldrh r11, [r0], r1 + vst1.8 {q1}, [r4]! @ 16 bytes store + vdup.u16 q2, r10 + vst1.8 {q1}, [r4], r6 @ 16 bytes store + vdup.u16 q3, r11 + vst1.8 {q2}, [r4]! @ 16 bytes store + vst1.8 {q2}, [r4], r6 @ 16 bytes store + vst1.8 {q3}, [r4]! @ 16 bytes store + vst1.8 {q3}, [r4], r6 @ 16 bytes store + +end_func_l_c: + ldmfd sp!, {r4-r11, pc} @Reload the registers from SP + + + + + +@** +@******************************************************************************* +@* +@* @brief +@* Padding (luma block) at the right of a 2d array +@* +@* @par Description: +@* The right column of a 2d array is replicated for pad_size times at the right +@* +@* +@* @param[in] pu1_src +@* UWORD8 pointer to the source +@* +@* @param[in] src_strd +@* integer source stride +@* +@* @param[in] ht +@* integer height of the array +@* +@* @param[in] wd +@* integer width of the array +@* +@* @param[in] pad_size +@* integer -padding size of the array +@* +@* @param[in] ht +@* integer height of the array +@* +@* @param[in] wd +@* integer width of the array +@* +@* @returns +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* +@#if PAD_RIGHT_LUMA == C +@void ih264_pad_right_luma(UWORD8 *pu1_src, +@ WORD32 src_strd, +@ WORD32 ht, +@ WORD32 pad_size) +@{ +@ WORD32 row; +@ +@ for(row = 0; row < ht; row++) +@ { +@ memset(pu1_src, *(pu1_src -1), pad_size); +@ +@ pu1_src += src_strd; +@ } +@} +@ +@ r0 => *pu1_src +@ r1 => src_strd +@ r2 => ht +@ r3 => pad_size + + + + .global ih264_pad_right_luma_a9q + +ih264_pad_right_luma_a9q: + + stmfd sp!, {r4-r11, lr} @stack stores the values of the arguments + + mov r4, r0 + sub r6, r1, #16 + sub r0, r0, #1 + subs r5, r3, #16 + bne loop_32 +loop_16_r: @ /*hard coded for width=16 ,height =8,16*/ + ldrb r8, [r0], r1 + ldrb r9, [r0], r1 + vdup.u8 q0, r8 + ldrb r10, [r0], r1 + vst1.8 {q0}, [r4], r1 @ 16 bytes store + vdup.u8 q1, r9 + vst1.8 {q1}, [r4], r1 @ 16 bytes store + ldrb r11, [r0], r1 + vdup.u8 q2, r10 + vdup.u8 q3, r11 + vst1.8 {q2}, [r4], r1 @ 16 bytes store + ldrb r8, [r0], r1 + vst1.8 {q3}, [r4], r1 @ 16 bytes store + ldrb r9, [r0], r1 + vdup.u8 q0, r8 + ldrb r10, [r0], r1 + vst1.8 {q0}, [r4], r1 @ 16 bytes store + vdup.u8 q1, r9 + ldrb r11, [r0], r1 + vst1.8 {q1}, [r4], r1 @ 16 bytes store + vdup.u8 q2, r10 + vdup.u8 q3, r11 + subs r2, r2, #8 + vst1.8 {q2}, [r4], r1 @ 16 bytes store + vst1.8 {q3}, [r4], r1 @ 16 bytes store + bne loop_16_r + b end_func_r + +loop_32_r: @ /*hard coded for width=32 ,height =8,16*/ + ldrb r8, [r0], r1 + ldrb r9, [r0], r1 + vdup.u8 q0, r8 + ldrb r10, [r0], r1 + vst1.8 {q0}, [r4]! @ 16 bytes store + vdup.u8 q1, r9 + vst1.8 {q0}, [r4], r6 + vst1.8 {q1}, [r4]! @ 16 bytes store + vdup.u8 q2, r10 + vst1.8 {q1}, [r4], r6 @ 16 bytes store + ldrb r11, [r0], r1 + vst1.8 {q2}, [r4]! @ 16 bytes store + vdup.u8 q3, r11 + vst1.8 {q2}, [r4], r6 @ 16 bytes store + ldrb r8, [r0], r1 + vst1.8 {q3}, [r4]! @ 16 bytes store + ldrb r9, [r0], r1 + vdup.u8 q0, r8 + vst1.8 {q3}, [r4], r6 @ 16 bytes store + ldrb r10, [r0], r1 + vst1.8 {q0}, [r4]! @ 16 bytes store + vdup.u8 q1, r9 + vst1.8 {q0}, [r4], r6 @ 16 bytes store + ldrb r11, [r0], r1 + vst1.8 {q1}, [r4]! @ 16 bytes store + vdup.u8 q2, r10 + vst1.8 {q1}, [r4], r6 @ 16 bytes store + vst1.8 {q2}, [r4]! @ 16 bytes store + vdup.u8 q3, r11 + vst1.8 {q2}, [r4], r6 @ 16 bytes store + subs r2, r2, #8 + vst1.8 {q3}, [r4]! @ 16 bytes store + vst1.8 {q3}, [r4], r6 @ 16 bytes store + bne loop_32_r + + + +end_func_r: + ldmfd sp!, {r4-r11, pc} @Reload the registers from SP + + + + + +@** +@******************************************************************************* +@* +@* @brief +@;* Padding (chroma block) at the right of a 2d array +@* +@* @par Description: +@* The right column of a 2d array is replicated for pad_size times at the right +@* +@* +@* @param[in] pu1_src +@;* UWORD8 pointer to the source +@* +@* @param[in] src_strd +@* integer source stride +@* +@* @param[in] ht +@;* integer height of the array +@* +@* @param[in] wd +@* integer width of the array (each colour component) +@* +@* @param[in] pad_size +@* integer -padding size of the array +@* +@* @param[in] ht +@;* integer height of the array +@* +@* @param[in] wd +@* integer width of the array +@* +@* @returns +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* +@#if PAD_RIGHT_CHROMA == C +@void ih264_pad_right_chroma(UWORD8 *pu1_src, +@ WORD32 src_strd, +@ WORD32 ht, +@ WORD32 pad_size) +@ r0 => *pu1_src +@ r1 => src_strd +@ r2 => ht +@ r3 => pad_size + + + + .global ih264_pad_right_chroma_a9q + +ih264_pad_right_chroma_a9q: + + stmfd sp!, {r4-r11, lr} @stack stores the values of the arguments + + mov r4, r0 + sub r6, r1, #16 + sub r0, r0, #2 +loop_32_r_c: @ /*hard coded for width=32 ,height =8,4*/ + ldrh r8, [r0], r1 + ldrh r9, [r0], r1 + vdup.u16 q0, r8 + ldrh r10, [r0], r1 + vst1.8 {q0}, [r4]! @ 16 bytes store + vdup.u16 q1, r9 + vst1.8 {q0}, [r4], r6 + vst1.8 {q1}, [r4]! @ 16 bytes store + vdup.u16 q2, r10 + vst1.8 {q1}, [r4], r6 @ 16 bytes store + subs r2, r2, #4 + ldrh r11, [r0], r1 + vst1.8 {q2}, [r4]! @ 16 bytes store + vdup.u16 q3, r11 + vst1.8 {q2}, [r4], r6 @ 16 bytes store + vst1.8 {q3}, [r4]! @ 16 bytes store + vst1.8 {q3}, [r4], r6 @ 16 bytes store + + beq end_func_r_c @/* Branching when ht=4*/ + + ldrh r8, [r0], r1 + vdup.u16 q0, r8 + ldrh r9, [r0], r1 + ldrh r10, [r0], r1 + vst1.8 {q0}, [r4]! @ 16 bytes store + vdup.u16 q1, r9 + vst1.8 {q0}, [r4], r6 @ 16 bytes store + ldrh r11, [r0], r1 + vst1.8 {q1}, [r4]! @ 16 bytes store + vdup.u16 q2, r10 + vst1.8 {q1}, [r4], r6 @ 16 bytes store + vst1.8 {q2}, [r4]! @ 16 bytes store + vdup.u16 q3, r11 + vst1.8 {q2}, [r4], r6 @ 16 bytes store + subs r2, r2, #4 + vst1.8 {q3}, [r4]! @ 16 bytes store + vst1.8 {q3}, [r4], r6 @ 16 bytes store + + beq end_func_r_c @/* Branching when ht=8*/ + bne loop_32_r_c + + ldrh r8, [r0], r1 + vdup.u16 q0, r8 + ldrh r9, [r0], r1 + ldrh r10, [r0], r1 + vst1.8 {q0}, [r4]! @ 16 bytes store + vdup.u16 q1, r9 + vst1.8 {q0}, [r4], r6 @ 16 bytes store + ldrh r11, [r0], r1 + vst1.8 {q1}, [r4]! @ 16 bytes store + vdup.u16 q2, r10 + vst1.8 {q1}, [r4], r6 @ 16 bytes store + vst1.8 {q2}, [r4]! @ 16 bytes store + vdup.u16 q3, r11 + vst1.8 {q2}, [r4], r6 @ 16 bytes store + vst1.8 {q3}, [r4]! @ 16 bytes store + vst1.8 {q3}, [r4], r6 @ 16 bytes store + +end_func_r_c: + ldmfd sp!, {r4-r11, pc} @Reload the registers from SP + + + + + diff --git a/dependencies/ih264d/common/arm/ih264_platform_macros.h b/dependencies/ih264d/common/arm/ih264_platform_macros.h new file mode 100644 index 00000000..a4d7f3a7 --- /dev/null +++ b/dependencies/ih264d/common/arm/ih264_platform_macros.h @@ -0,0 +1,181 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** +******************************************************************************* +* @file +* ih264_platform_macros.h +* +* @brief +* Platform specific Macro definitions used in the codec +* +* @author +* Ittiam +* +* @remarks +* None +* +******************************************************************************* +*/ +#ifndef _IH264_PLATFORM_MACROS_H_ +#define _IH264_PLATFORM_MACROS_H_ + +#include + +#ifndef ARMV8 + +static __inline WORD32 CLIP_U8(WORD32 x) +{ + asm("usat %0, #8, %1" : "=r"(x) : "r"(x)); + return x; +} + +static __inline WORD32 CLIP_S8(WORD32 x) +{ + asm("ssat %0, #8, %1" : "=r"(x) : "r"(x)); + return x; +} + +static __inline WORD32 CLIP_U10(WORD32 x) +{ + asm("usat %0, #10, %1" : "=r"(x) : "r"(x)); + return x; +} + +static __inline WORD32 CLIP_S10(WORD32 x) +{ + asm("ssat %0, #10, %1" : "=r"(x) : "r"(x)); + return x; +} + +static __inline WORD32 CLIP_U11(WORD32 x) +{ + asm("usat %0, #11, %1" : "=r"(x) : "r"(x)); + return x; +} + +static __inline WORD32 CLIP_S11(WORD32 x) +{ + asm("ssat %0, #11, %1" : "=r"(x) : "r"(x)); + return x; +} + +static __inline WORD32 CLIP_U12(WORD32 x) +{ + asm("usat %0, #12, %1" : "=r"(x) : "r"(x)); + return x; +} + +static __inline WORD32 CLIP_S12(WORD32 x) +{ + asm("ssat %0, #12, %1" : "=r"(x) : "r"(x)); + return x; +} + +static __inline WORD32 CLIP_U16(WORD32 x) +{ + asm("usat %0, #16, %1" : "=r"(x) : "r"(x)); + return x; +} +static __inline WORD32 CLIP_S16(WORD32 x) +{ + asm("ssat %0, #16, %1" : "=r"(x) : "r"(x)); + return x; +} + + +static __inline UWORD32 ITT_BIG_ENDIAN(UWORD32 x) +{ + asm("rev %0, %1" : "=r"(x) : "r"(x)); + return x; +} +#define NOP(nop_cnt) {UWORD32 nop_i; for (nop_i = 0; nop_i < nop_cnt; nop_i++) asm("nop");} + +#else + +#define CLIP_U8(x) CLIP3(0, UINT8_MAX, (x)) +#define CLIP_S8(x) CLIP3(INT8_MIN, INT8_MAX, (x)) + +#define CLIP_U10(x) CLIP3(0, 1023, (x)) +#define CLIP_S10(x) CLIP3(-512, 511, (x)) + +#define CLIP_U11(x) CLIP3(0, 2047, (x)) +#define CLIP_S11(x) CLIP3(-1024, 1023, (x)) + +#define CLIP_U12(x) CLIP3(0, 4095, (x)) +#define CLIP_S12(x) CLIP3(-2048, 2047, (x)) + +#define CLIP_U16(x) CLIP3(0, UINT16_MAX, (x)) +#define CLIP_S16(x) CLIP3(INT16_MIN, INT16_MAX, (x)) + +#define ITT_BIG_ENDIAN(x) __asm__("rev %0, %1" : "=r"(x) : "r"(x)); + +#define NOP(nop_cnt) \ +{ \ + UWORD32 nop_i; \ + for (nop_i = 0; nop_i < nop_cnt; nop_i++) \ + __asm__ __volatile__("mov x0, x0"); \ +} + +#endif + +/*saturating instructions are not available for WORD64 in ARMv7, hence we cannot + * use inline assembly like other clips*/ +#define CLIP_U32(x) CLIP3(0, UINT32_MAX, (x)) +#define CLIP_S32(x) CLIP3(INT32_MIN, INT32_MAX, (x)) + +#define DATA_SYNC() __sync_synchronize() + +#define SHL(x,y) (((y) < 32) ? ((x) << (y)) : 0) +#define SHR(x,y) (((y) < 32) ? ((x) >> (y)) : 0) + +#define SHR_NEG(val,shift) ((shift>0)?(val>>shift):(val<<(-shift))) +#define SHL_NEG(val,shift) ((shift<0)?(val>>(-shift)):(val<> 1; + vshrn.s32 d1, q1, #1 @i4_value = (x3 + x2) >> 1; + vshrn.s32 d2, q2, #1 @i4_value = (x0 - x1) >> 1; + vshrn.s32 d3, q3, #1 @i4_value = (x3 - x2) >> 1; + + vabs.s16 q5, q0 + vabs.s16 q6, q1 + + vmov.s32 q8, q7 @Get the round fact + vmov.s32 q9, q7 + vmov.s32 q10, q7 + + vclt.s16 q3, q0, #0 @get the sign row 1,2 + vclt.s16 q4, q1, #0 + + vneg.s32 q11, q11 @-u4_round_factor + + vmlal.u16 q7, d10, d30 + vmlal.u16 q8, d11, d30 + vmlal.u16 q9, d12, d30 + vmlal.u16 q10, d13, d30 + + vshl.u32 q7, q7, q11 + vshl.u32 q8, q8, q11 + vshl.u32 q9, q9, q11 + vshl.u32 q10, q10, q11 + + vqmovn.u32 d22, q7 + vqmovn.u32 d23, q8 + vqmovn.u32 d24, q9 + vqmovn.u32 d25, q10 + + vneg.s16 q13, q11 + vneg.s16 q14, q12 + + vbsl.s16 q3, q13, q11 + vbsl.s16 q4, q14, q12 + + vceq.s16 q5, q11, #0 + vceq.s16 q6, q12, #0 + + vst1.s16 {q3}, [r1]! + + vshrn.u16 d14, q5, #8 + vshrn.u16 d15, q6, #8 + + ldr r3, [sp, #72] @Load *pu1_nnz + + vshr.u8 q7, q7, #7 + + vst1.s16 {q4}, [r1]! + + vadd.u8 d16, d14, d15 + vmov.u8 d20, #16 + vpadd.u8 d17, d16, d16 + vpadd.u8 d18, d17, d17 + vpadd.u8 d19, d18, d18 + vsub.u8 d20, d20, d19 + vst1.u8 d20[0], [r3] + + vpop {d8-d15} + bx lr + + + + +@***************************************************************************** +@* +@* Function Name : ih264_hadamard_quant_2x2_uv_a9 +@* Description : This function does forward hadamard transform and +@* quantization for dc block of chroma for both planes +@* +@* Arguments : R0 :pointer to src buffer +@ R1 :pointer to dst buffer +@ R2 :pu2_scale_matrix +@ R2 :pu2_threshold_matrix +@ STACk : u4_qbits +@ u4_round_factor +@ pu1_nnz +@ Values Returned : NONE +@ +@ Register Usage : +@ Stack Usage : 0 bytes +@ Cycles : Around +@ Interruptiaility : Interruptable +@ +@ Known Limitations +@ \Assumptions : +@ +@ Revision History : +@ DD MM YYYY Author(s) Changes +@ 20 2 2015 100633 First version +@ +@***************************************************************************** +@ ih264_hadamard_quant_2x2_uv_a9(WORD16 *pi2_src, WORD16 *pi2_dst, +@ const UWORD16 *pu2_scale_matrix, +@ const UWORD16 *pu2_threshold_matrix, UWORD32 u4_qbits, +@ UWORD32 u4_round_factor,UWORD8 *pu1_nnz +@ ) + + .global ih264_hadamard_quant_2x2_uv_a9 +ih264_hadamard_quant_2x2_uv_a9: + + vpush {d8-d15} + vld2.s16 {d0-d1}, [r0] @load src + + add r3, sp, #68 @Get address of u4_round_factor + + vaddl.s16 q3, d0, d1 @x0 = x4 + x5;, x2 = x6 + x7; + vld1.u16 d30[0], [r2] @load pu2_scale_matrix[0] + vsubl.s16 q4, d0, d1 @x1 = x4 - x5; x3 = x6 - x7; + + add r0, sp, #64 @Get affress of u4_qbits + vld1.s32 d28[0], [r3] @load u4_round_factor + vtrn.s32 q3, q4 @q1 -> x0 x1, q2 -> x2 x3 + + vadd.s32 q0, q3, q4 @ (x0 + x2) (x1 + x3) (y0 + y2); (y1 + y3); + vld1.s32 d24[0], [r0] @load u4_qbits + vsub.s32 q1, q3, q4 @ (x0 - x2) (x1 - x3) (y0 - y2); (y1 - y3); + + vdup.u16 d30, d30[0] @pu2_scale_matrix + + vabs.s32 q2, q0 + vabs.s32 q3, q1 + + vdup.s32 q14, d28[0] @u4_round_factor + + vmovl.u16 q15, d30 @pu2_scale_matrix + + vclt.s32 q4, q0, #0 @get the sign row 1,2 + vdup.s32 q12, d24[0] @u4_round_factor + vclt.s32 q5, q1, #0 + + vqmovn.u32 d8, q4 + vqmovn.s32 d9, q5 + + vmov.s32 q13, q14 @Get the round fact + vneg.s32 q12, q12 @-u4_round_factor + + vmla.u32 q13, q2, q15 + vmla.u32 q14, q3, q15 + + vshl.u32 q13, q13, q12 @>>qbit + vshl.u32 q14, q14, q12 @>>qbit + + vqmovn.u32 d10, q13 + vqmovn.u32 d11, q14 + + vneg.s16 q6, q5 + + vbsl.s16 q4, q6, q5 @*sign + + vtrn.s32 d8, d9 + + vceq.s16 q7, q4, #0 @Compute nnz + + vshrn.u16 d14, q7, #8 @reduce nnz comparison to 1 bit + + ldr r3, [sp, #72] @Load *pu1_nnz + vshr.u8 d14, d14, #7 @reduce nnz comparison to 1 bit + vmov.u8 d20, #4 @Since we add zeros, we need to subtract from 4 to get nnz + vpadd.u8 d17, d14, d14 @Sum up nnz + + vst1.s16 {q4}, [r1]! @Store the block + + vpadd.u8 d17, d17, d17 @Sum up nnz + vsub.u8 d20, d20, d17 @4- numzeros + vst1.u16 d20[0], [r3] @store nnz + + vpop {d8-d15} + bx lr + + + + + diff --git a/dependencies/ih264d/common/arm/ih264_weighted_bi_pred_a9q.s b/dependencies/ih264d/common/arm/ih264_weighted_bi_pred_a9q.s new file mode 100644 index 00000000..304bd8a0 --- /dev/null +++ b/dependencies/ih264d/common/arm/ih264_weighted_bi_pred_a9q.s @@ -0,0 +1,642 @@ +@/****************************************************************************** +@ * +@ * Copyright (C) 2015 The Android Open Source Project +@ * +@ * Licensed under the Apache License, Version 2.0 (the "License"); +@ * you may not use this file except in compliance with the License. +@ * You may obtain a copy of the License at: +@ * +@ * http://www.apache.org/licenses/LICENSE-2.0 +@ * +@ * Unless required by applicable law or agreed to in writing, software +@ * distributed under the License is distributed on an "AS IS" BASIS, +@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@ * See the License for the specific language governing permissions and +@ * limitations under the License. +@ * +@ ***************************************************************************** +@ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +@*/ +@** +@****************************************************************************** +@* @file +@* ih264_weighted_bi_pred_a9q.s +@* +@* @brief +@* Contains function definitions for weighted biprediction. +@* +@* @author +@* Kaushik Senthoor R +@* +@* @par List of Functions: +@* +@* - ih264_weighted_bi_pred_luma_a9q() +@* - ih264_weighted_bi_pred_chroma_a9q() +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* +@******************************************************************************* +@* @function +@* ih264_weighted_bi_pred_luma_a9q() +@* +@* @brief +@* This routine performs the weighted biprediction as described in sec +@* 8.4.2.3.2 titled "Weighted sample prediction process" for luma. +@* +@* @par Description: +@* This function gets two ht x wd blocks, calculates the weighted samples, +@* rounds off, adds offset and stores it in the destination block. +@* +@* @param[in] pu1_src1 +@* UWORD8 Pointer to the buffer containing the input block 1. +@* +@* @param[in] pu1_src2 +@* UWORD8 Pointer to the buffer containing the input block 2. +@* +@* @param[out] pu1_dst +@* UWORD8 pointer to the destination where the output block is stored. +@* +@* @param[in] src_strd1 +@* Stride of the input buffer 1 +@* +@* @param[in] src_strd2 +@* Stride of the input buffer 2 +@* +@* @param[in] dst_strd +@* Stride of the destination buffer +@* +@* @param[in] log_wd +@* number of bits to be rounded off +@* +@* @param[in] wt1 +@* weight for the weighted prediction +@* +@* @param[in] wt2 +@* weight for the weighted prediction +@* +@* @param[in] ofst1 +@* offset 1 used after rounding off +@* +@* @param[in] ofst2 +@* offset 2 used after rounding off +@* +@* @param[in] ht +@* integer height of the array +@* +@* @param[in] wd +@* integer width of the array +@* +@* @returns +@* None +@* +@* @remarks +@* (ht,wd) can be (4,4), (4,8), (8,4), (8,8), (8,16), (16,8) or (16,16). +@* +@******************************************************************************* +@* +@void ih264_weighted_bi_pred_luma_a9q(UWORD8 *pu1_src1, +@ UWORD8 *pu1_src2, +@ UWORD8 *pu1_dst, +@ WORD32 src_strd1, +@ WORD32 src_strd2, +@ WORD32 dst_strd, +@ WORD32 log_wd, +@ WORD32 wt1, +@ WORD32 wt2, +@ WORD32 ofst1, +@ WORD32 ofst2, +@ WORD32 ht, +@ WORD32 wd) +@ +@**************Variables Vs Registers***************************************** +@ r0 => pu1_src1 +@ r1 => pu1_src2 +@ r2 => pu1_dst +@ r3 => src_strd1 +@ [sp] => src_strd2 (r4) +@ [sp+4] => dst_strd (r5) +@ [sp+8] => log_wd (r6) +@ [sp+12] => wt1 (r7) +@ [sp+16] => wt2 (r8) +@ [sp+20] => ofst1 (r9) +@ [sp+24] => ofst2 (r10) +@ [sp+28] => ht (r11) +@ [sp+32] => wd (r12) +@ +.text +.p2align 2 + + .global ih264_weighted_bi_pred_luma_a9q + +ih264_weighted_bi_pred_luma_a9q: + + stmfd sp!, {r4-r12, r14} @stack stores the values of the arguments + ldr r6, [sp, #48] @Load log_wd in r6 + ldr r7, [sp, #52] @Load wt1 in r7 + ldr r8, [sp, #56] @Load wt2 in r8 + ldr r9, [sp, #60] @Load ofst1 in r9 + + add r6, r6, #1 @r6 = log_wd + 1 + sxtb r7, r7 @sign-extend 16-bit wt1 to 32-bit + ldr r4, [sp, #40] @Load src_strd2 in r4 + ldr r5, [sp, #44] @Load dst_strd in r5 + sxtb r9, r9 @sign-extend 8-bit ofst1 to 32-bit + neg r10, r6 @r10 = -(log_wd + 1) + ldr r11, [sp, #68] @Load ht in r11 + ldr r12, [sp, #72] @Load wd in r12 + vdup.16 q0, r10 @Q0 = -(log_wd + 1) (32-bit) + add r9, r9, #1 @r9 = ofst1 + 1 + + ldr r10, [sp, #64] @Load ofst2 in r10 + sxtb r8, r8 @sign-extend 16-bit wt2 to 32-bit + cmp r12, #16 @check if wd is 16 + vpush {d8-d15} + sxtb r10, r10 @sign-extend 8-bit ofst2 to 32-bit + add r9, r9, r10 @r9 = ofst1 + ofst2 + 1 + vmov d2, r7, r8 @D2 = {wt1(32-bit), wt2(32-bit)} + asr r9, r9, #1 @r9 = ofst = (ofst1 + ofst2 + 1) >> 1 + vdup.8 d3, r9 @D3 = ofst (8-bit) + beq loop_16 @branch if wd is 16 + + cmp r12, #8 @check if wd is 8 + beq loop_8 @branch if wd is 8 + +loop_4: @each iteration processes four rows + + vld1.32 d4[0], [r0], r3 @load row 1 in source 1 + vld1.32 d4[1], [r0], r3 @load row 2 in source 1 + vld1.32 d6[0], [r1], r4 @load row 1 in source 2 + vld1.32 d6[1], [r1], r4 @load row 2 in source 2 + + vmovl.u8 q2, d4 @converting rows 1,2 in source 1 to 16-bit + vld1.32 d8[0], [r0], r3 @load row 3 in source 1 + vld1.32 d8[1], [r0], r3 @load row 4 in source 1 + vmovl.u8 q3, d6 @converting rows 1,2 in source 2 to 16-bit + vld1.32 d10[0], [r1], r4 @load row 3 in source 2 + vld1.32 d10[1], [r1], r4 @load row 4 in source 2 + + vmovl.u8 q4, d8 @converting rows 3,4 in source 1 to 16-bit + vmovl.u8 q5, d10 @converting rows 3,4 in source 2 to 16-bit + + vmul.s16 q2, q2, d2[0] @weight 1 mult. for rows 1,2 + vmla.s16 q2, q3, d2[2] @weight 2 mult. for rows 1,2 + vmul.s16 q4, q4, d2[0] @weight 1 mult. for rows 3,4 + vmla.s16 q4, q5, d2[2] @weight 2 mult. for rows 3,4 + + subs r11, r11, #4 @decrement ht by 4 + vrshl.s16 q2, q2, q0 @rounds off the weighted samples from rows 1,2 + vrshl.s16 q4, q4, q0 @rounds off the weighted samples from rows 3,4 + + vaddw.s8 q2, q2, d3 @adding offset for rows 1,2 + vaddw.s8 q4, q4, d3 @adding offset for rows 3,4 + + vqmovun.s16 d4, q2 @saturating rows 1,2 to unsigned 8-bit + vqmovun.s16 d8, q4 @saturating rows 3,4 to unsigned 8-bit + + vst1.32 d4[0], [r2], r5 @store row 1 in destination + vst1.32 d4[1], [r2], r5 @store row 2 in destination + vst1.32 d8[0], [r2], r5 @store row 3 in destination + vst1.32 d8[1], [r2], r5 @store row 4 in destination + + bgt loop_4 @if greater than 0 repeat the loop again + + b end_loops + +loop_8: @each iteration processes four rows + + vld1.8 d4, [r0], r3 @load row 1 in source 1 + vld1.8 d6, [r1], r4 @load row 1 in source 2 + vld1.8 d8, [r0], r3 @load row 2 in source 1 + vld1.8 d10, [r1], r4 @load row 2 in source 2 + vmovl.u8 q2, d4 @converting row 1 in source 1 to 16-bit + vld1.8 d12, [r0], r3 @load row 3 in source 1 + vld1.8 d14, [r1], r4 @load row 3 in source 2 + vmovl.u8 q3, d6 @converting row 1 in source 2 to 16-bit + vld1.8 d16, [r0], r3 @load row 4 in source 1 + vld1.8 d18, [r1], r4 @load row 4 in source 2 + + vmovl.u8 q4, d8 @converting row 2 in source 1 to 16-bit + vmovl.u8 q5, d10 @converting row 2 in source 2 to 16-bit + + vmul.s16 q2, q2, d2[0] @weight 1 mult. for row 1 + vmla.s16 q2, q3, d2[2] @weight 2 mult. for row 1 + vmovl.u8 q6, d12 @converting row 3 in source 1 to 16-bit + vmovl.u8 q7, d14 @converting row 3 in source 2 to 16-bit + vmul.s16 q4, q4, d2[0] @weight 1 mult. for row 2 + vmla.s16 q4, q5, d2[2] @weight 2 mult. for row 2 + vmovl.u8 q8, d16 @converting row 4 in source 1 to 16-bit + vmovl.u8 q9, d18 @converting row 4 in source 2 to 16-bit + + vmul.s16 q6, q6, d2[0] @weight 1 mult. for row 3 + vmla.s16 q6, q7, d2[2] @weight 2 mult. for row 3 + vmul.s16 q8, q8, d2[0] @weight 1 mult. for row 4 + vmla.s16 q8, q9, d2[2] @weight 2 mult. for row 4 + + vrshl.s16 q2, q2, q0 @rounds off the weighted samples from row 1 + vrshl.s16 q4, q4, q0 @rounds off the weighted samples from row 2 + vrshl.s16 q6, q6, q0 @rounds off the weighted samples from row 3 + vaddw.s8 q2, q2, d3 @adding offset for row 1 + vrshl.s16 q8, q8, q0 @rounds off the weighted samples from row 4 + vaddw.s8 q4, q4, d3 @adding offset for row 2 + + vaddw.s8 q6, q6, d3 @adding offset for row 3 + vqmovun.s16 d4, q2 @saturating row 1 to unsigned 8-bit + vaddw.s8 q8, q8, d3 @adding offset for row 4 + vqmovun.s16 d8, q4 @saturating row 2 to unsigned 8-bit + + vqmovun.s16 d12, q6 @saturating row 3 to unsigned 8-bit + vqmovun.s16 d16, q8 @saturating row 4 to unsigned 8-bit + + vst1.8 d4, [r2], r5 @store row 1 in destination + vst1.8 d8, [r2], r5 @store row 2 in destination + subs r11, r11, #4 @decrement ht by 4 + vst1.8 d12, [r2], r5 @store row 3 in destination + vst1.8 d16, [r2], r5 @store row 4 in destination + + bgt loop_8 @if greater than 0 repeat the loop again + + b end_loops + +loop_16: @each iteration processes two rows + + vld1.8 {q2}, [r0], r3 @load row 1 in source 1 + vld1.8 {q3}, [r1], r4 @load row 1 in source 2 + vld1.8 {q4}, [r0], r3 @load row 2 in source 1 + vld1.8 {q5}, [r1], r4 @load row 2 in source 2 + vmovl.u8 q10, d4 @converting row 1L in source 1 to 16-bit + vld1.8 {q6}, [r0], r3 @load row 3 in source 1 + vld1.8 {q7}, [r1], r4 @load row 3 in source 2 + vmovl.u8 q11, d6 @converting row 1L in source 2 to 16-bit + vld1.8 {q8}, [r0], r3 @load row 4 in source 1 + vld1.8 {q9}, [r1], r4 @load row 4 in source 2 + + vmovl.u8 q2, d5 @converting row 1H in source 1 to 16-bit + vmovl.u8 q3, d7 @converting row 1H in source 2 to 16-bit + + vmul.s16 q10, q10, d2[0] @weight 1 mult. for row 1L + vmla.s16 q10, q11, d2[2] @weight 2 mult. for row 1L + vmovl.u8 q12, d8 @converting row 2L in source 1 to 16-bit + vmovl.u8 q13, d10 @converting row 2L in source 2 to 16-bit + + vmul.s16 q2, q2, d2[0] @weight 1 mult. for row 1H + vmla.s16 q2, q3, d2[2] @weight 2 mult. for row 1H + vmovl.u8 q4, d9 @converting row 2H in source 1 to 16-bit + vmovl.u8 q5, d11 @converting row 2H in source 2 to 16-bit + + vmul.s16 q12, q12, d2[0] @weight 1 mult. for row 2L + vmla.s16 q12, q13, d2[2] @weight 2 mult. for row 2L + vmovl.u8 q14, d12 @converting row 3L in source 1 to 16-bit + vmovl.u8 q15, d14 @converting row 3L in source 2 to 16-bit + + vmul.s16 q4, q4, d2[0] @weight 1 mult. for row 2H + vmla.s16 q4, q5, d2[2] @weight 2 mult. for row 2H + vmovl.u8 q6, d13 @converting row 3H in source 1 to 16-bit + vmovl.u8 q7, d15 @converting row 3H in source 2 to 16-bit + + vmul.s16 q14, q14, d2[0] @weight 1 mult. for row 3L + vmla.s16 q14, q15, d2[2] @weight 2 mult. for row 3L + vmovl.u8 q11, d16 @converting row 4L in source 1 to 16-bit + vmovl.u8 q3, d18 @converting row 4L in source 2 to 16-bit + + vmul.s16 q6, q6, d2[0] @weight 1 mult. for row 3H + vmla.s16 q6, q7, d2[2] @weight 2 mult. for row 3H + vmovl.u8 q8, d17 @converting row 4H in source 1 to 16-bit + vmovl.u8 q9, d19 @converting row 4H in source 2 to 16-bit + + vmul.s16 q11, q11, d2[0] @weight 1 mult. for row 4L + vmla.s16 q11, q3, d2[2] @weight 2 mult. for row 4L + vrshl.s16 q10, q10, q0 @rounds off the weighted samples from row 1L + + vmul.s16 q8, q8, d2[0] @weight 1 mult. for row 4H + vmla.s16 q8, q9, d2[2] @weight 2 mult. for row 4H + vrshl.s16 q2, q2, q0 @rounds off the weighted samples from row 1H + + vrshl.s16 q12, q12, q0 @rounds off the weighted samples from row 2L + vaddw.s8 q10, q10, d3 @adding offset for row 1L + vrshl.s16 q4, q4, q0 @rounds off the weighted samples from row 2H + vaddw.s8 q2, q2, d3 @adding offset for row 1H + vrshl.s16 q14, q14, q0 @rounds off the weighted samples from row 3L + vaddw.s8 q12, q12, d3 @adding offset for row 2L + vrshl.s16 q6, q6, q0 @rounds off the weighted samples from row 3H + vaddw.s8 q4, q4, d3 @adding offset for row 2H + vrshl.s16 q11, q11, q0 @rounds off the weighted samples from row 4L + vaddw.s8 q14, q14, d3 @adding offset for row 3L + vrshl.s16 q8, q8, q0 @rounds off the weighted samples from row 4H + vaddw.s8 q6, q6, d3 @adding offset for row 3H + + vqmovun.s16 d26, q10 @saturating row 1L to unsigned 8-bit + vaddw.s8 q11, q11, d3 @adding offset for row 4L + vqmovun.s16 d27, q2 @saturating row 1H to unsigned 8-bit + vaddw.s8 q8, q8, d3 @adding offset for row 4H + + vqmovun.s16 d10, q12 @saturating row 2L to unsigned 8-bit + vqmovun.s16 d11, q4 @saturating row 2H to unsigned 8-bit + vqmovun.s16 d30, q14 @saturating row 3L to unsigned 8-bit + vqmovun.s16 d31, q6 @saturating row 3H to unsigned 8-bit + vst1.8 {q13}, [r2], r5 @store row 1 in destination + vqmovun.s16 d14, q11 @saturating row 4L to unsigned 8-bit + vqmovun.s16 d15, q8 @saturating row 4H to unsigned 8-bit + + vst1.8 {q5}, [r2], r5 @store row 2 in destination + subs r11, r11, #4 @decrement ht by 4 + vst1.8 {q15}, [r2], r5 @store row 3 in destination + vst1.8 {q7}, [r2], r5 @store row 4 in destination + + bgt loop_16 @if greater than 0 repeat the loop again + +end_loops: + + vpop {d8-d15} + ldmfd sp!, {r4-r12, r15} @Reload the registers from sp + + +@******************************************************************************* +@* @function +@* ih264_weighted_bi_pred_chroma_a9q() +@* +@* @brief +@* This routine performs the default weighted prediction as described in sec +@* 8.4.2.3.2 titled "Weighted sample prediction process" for chroma. +@* +@* @par Description: +@* This function gets two ht x wd blocks, calculates the weighted samples, +@* rounds off, adds offset and stores it in the destination block for U and V. +@* +@* @param[in] pu1_src1 +@* UWORD8 Pointer to the buffer containing the input block 1. +@* +@* @param[in] pu1_src2 +@* UWORD8 Pointer to the buffer containing the input block 2. +@* +@* @param[out] pu1_dst +@* UWORD8 pointer to the destination where the output block is stored. +@* +@* @param[in] src_strd1 +@* Stride of the input buffer 1 +@* +@* @param[in] src_strd2 +@* Stride of the input buffer 2 +@* +@* @param[in] dst_strd +@* Stride of the destination buffer +@* +@* @param[in] log_wd +@* number of bits to be rounded off +@* +@* @param[in] wt1 +@* weights for the weighted prediction in U and V +@* +@* @param[in] wt2 +@* weights for the weighted prediction in U and V +@* +@* @param[in] ofst1 +@* offset 1 used after rounding off for U an dV +@* +@* @param[in] ofst2 +@* offset 2 used after rounding off for U and V +@* +@* @param[in] ht +@* integer height of the array +@* +@* @param[in] wd +@* integer width of the array +@* +@* @returns +@* None +@* +@* @remarks +@* (ht,wd) can be (2,2), (2,4), (4,2), (4,4), (4,8), (8,4) or (8,8). +@* +@******************************************************************************* +@* +@void ih264_weighted_bi_pred_chroma_a9q(UWORD8 *pu1_src1, +@ UWORD8 *pu1_src2, +@ UWORD8 *pu1_dst, +@ WORD32 src_strd1, +@ WORD32 src_strd2, +@ WORD32 dst_strd, +@ WORD32 log_wd, +@ WORD32 wt1, +@ WORD32 wt2, +@ WORD32 ofst1, +@ WORD32 ofst2, +@ WORD32 ht, +@ WORD32 wd) +@ +@**************Variables Vs Registers***************************************** +@ r0 => pu1_src1 +@ r1 => pu1_src2 +@ r2 => pu1_dst +@ r3 => src_strd1 +@ [sp] => src_strd2 (r4) +@ [sp+4] => dst_strd (r5) +@ [sp+8] => log_wd (r6) +@ [sp+12] => wt1 (r7) +@ [sp+16] => wt2 (r8) +@ [sp+20] => ofst1 (r9) +@ [sp+24] => ofst2 (r10) +@ [sp+28] => ht (r11) +@ [sp+32] => wd (r12) +@ + + + .global ih264_weighted_bi_pred_chroma_a9q + +ih264_weighted_bi_pred_chroma_a9q: + + stmfd sp!, {r4-r12, r14} @stack stores the values of the arguments + + ldr r6, [sp, #48] @Load log_wd in r6 + ldr r7, [sp, #52] @Load wt1 in r7 + ldr r8, [sp, #56] @Load wt2 in r8 + add r6, r6, #1 @r6 = log_wd + 1 + ldr r9, [sp, #60] @Load ofst1 in r9 + ldr r10, [sp, #64] @Load ofst2 in r10 + + neg r12, r6 @r12 = -(log_wd + 1) + ldr r4, [sp, #40] @Load src_strd2 in r4 + ldr r5, [sp, #44] @Load dst_strd in r5 + vdup.16 q0, r12 @Q0 = -(log_wd + 1) (16-bit) + + ldr r11, [sp, #68] @Load ht in r11 + vdup.32 q1, r7 @Q1 = (wt1_u, wt1_v) (32-bit) + ldr r12, [sp, #72] @Load wd in r12 + vdup.32 q2, r8 @Q2 = (wt2_u, wt2_v) (32-bit) + asr r7, r9, #8 @r7 = ofst1_v + asr r8, r10, #8 @r8 = ofst2_v + vpush {d8-d15} + sxtb r9, r9 @sign-extend 8-bit ofst1_u to 32-bit + sxtb r10, r10 @sign-extend 8-bit ofst2_u to 32-bit + sxtb r7, r7 @sign-extend 8-bit ofst1_v to 32-bit + sxtb r8, r8 @sign-extend 8-bit ofst2_v to 32-bit + + add r9, r9, #1 @r9 = ofst1_u + 1 + add r7, r7, #1 @r7 = ofst1_v + 1 + add r9, r9, r10 @r9 = ofst1_u + ofst2_u + 1 + add r7, r7, r8 @r7 = ofst1_v + ofst2_v + 1 + asr r9, r9, #1 @r9 = ofst_u = (ofst1_u + ofst2_u + 1) >> 1 + asr r7, r7, #1 @r7 = ofst_v = (ofst1_v + ofst2_v + 1) >> 1 + cmp r12, #8 @check if wd is 8 + pkhbt r9, r9, r7, lsl #16 @r9 = {ofst_u(16-bit), ofst_v(16-bit)} + vdup.32 q3, r9 @Q3 = {ofst_u(16-bit), ofst_v(16-bit)} + beq loop_8_uv @branch if wd is 8 + + cmp r12, #4 @check if wd is 4 + beq loop_4_uv @branch if wd is 4 + +loop_2_uv: @each iteration processes two rows + + vld1.32 d8[0], [r0], r3 @load row 1 in source 1 + vld1.32 d8[1], [r0], r3 @load row 2 in source 1 + vld1.32 d10[0], [r1], r4 @load row 1 in source 2 + vld1.32 d10[1], [r1], r4 @load row 2 in source 2 + + vmovl.u8 q4, d8 @converting rows 1,2 in source 1 to 16-bit + vmovl.u8 q5, d10 @converting rows 1,2 in source 2 to 16-bit + + vmul.s16 q4, q4, q1 @weight 1 mult. for rows 1,2 + vmla.s16 q4, q5, q2 @weight 2 mult. for rows 1,2 + + vrshl.s16 q4, q4, q0 @rounds off the weighted samples from rows 1,2 + + vadd.s16 q4, q4, q3 @adding offset for rows 1,2 + + vqmovun.s16 d8, q4 @saturating rows 1,2 to unsigned 8-bit + + vst1.32 d8[0], [r2], r5 @store row 1 in destination + vst1.32 d8[1], [r2], r5 @store row 2 in destination + + subs r11, r11, #2 @decrement ht by 2 + bgt loop_2_uv @if greater than 0 repeat the loop again + + b end_loops_uv + +loop_4_uv: @each iteration processes two rows + + vld1.8 d8, [r0], r3 @load row 1 in source 1 + vld1.8 d10, [r1], r4 @load row 1 in source 2 + vmovl.u8 q4, d8 @converting row 1 in source 1 to 16-bit + vld1.8 d12, [r0], r3 @load row 2 in source 1 + vmovl.u8 q5, d10 @converting row 1 in source 2 to 16-bit + vld1.8 d14, [r1], r4 @load row 2 in source 2 + + vmovl.u8 q6, d12 @converting row 2 in source 1 to 16-bit + vmul.s16 q4, q4, q1 @weight 1 mult. for row 1 + vmla.s16 q4, q5, q2 @weight 2 mult. for row 1 + vmovl.u8 q7, d14 @converting row 2 in source 2 to 16-bit + + vmul.s16 q6, q6, q1 @weight 1 mult. for row 2 + vmla.s16 q6, q7, q2 @weight 2 mult. for row 2 + + subs r11, r11, #2 @decrement ht by 2 + vrshl.s16 q4, q4, q0 @rounds off the weighted samples from row 1 + vrshl.s16 q6, q6, q0 @rounds off the weighted samples from row 2 + vadd.s16 q4, q4, q3 @adding offset for row 1 + vadd.s16 q6, q6, q3 @adding offset for row 2 + + vqmovun.s16 d8, q4 @saturating row 1 to unsigned 8-bit + vqmovun.s16 d12, q6 @saturating row 2 to unsigned 8-bit + + vst1.8 d8, [r2], r5 @store row 1 in destination + vst1.8 d12, [r2], r5 @store row 2 in destination + + bgt loop_4_uv @if greater than 0 repeat the loop again + + b end_loops_uv + +loop_8_uv: @each iteration processes two rows + + vld1.8 {q4}, [r0], r3 @load row 1 in source 1 + vld1.8 {q5}, [r1], r4 @load row 1 in source 2 + vld1.8 {q6}, [r0], r3 @load row 2 in source 1 + vld1.8 {q7}, [r1], r4 @load row 2 in source 2 + vmovl.u8 q12, d8 @converting row 1L in source 1 to 16-bit + vld1.8 {q8}, [r0], r3 @load row 3 in source 1 + vld1.8 {q9}, [r1], r4 @load row 3 in source 2 + vmovl.u8 q13, d10 @converting row 1L in source 2 to 16-bit + vld1.8 {q10}, [r0], r3 @load row 4 in source 1 + vld1.8 {q11}, [r1], r4 @load row 4 in source 2 + + vmovl.u8 q4, d9 @converting row 1H in source 1 to 16-bit + vmovl.u8 q5, d11 @converting row 1H in source 2 to 16-bit + + vmul.s16 q12, q12, q1 @weight 1 mult. for row 1L + vmla.s16 q12, q13, q2 @weight 2 mult. for row 1L + vmovl.u8 q14, d12 @converting row 2L in source 1 to 16-bit + vmovl.u8 q15, d14 @converting row 2L in source 2 to 16-bit + + vmul.s16 q4, q4, q1 @weight 1 mult. for row 1H + vmla.s16 q4, q5, q2 @weight 2 mult. for row 1H + vmovl.u8 q6, d13 @converting row 2H in source 1 to 16-bit + vmovl.u8 q7, d15 @converting row 2H in source 2 to 16-bit + + vmul.s16 q14, q14, q1 @weight 1 mult. for row 2L + vmla.s16 q14, q15, q2 @weight 2 mult. for row 2L + vmovl.u8 q13, d16 @converting row 3L in source 1 to 16-bit + vmovl.u8 q5, d18 @converting row 3L in source 2 to 16-bit + + vmul.s16 q6, q6, q1 @weight 1 mult. for row 2H + vmla.s16 q6, q7, q2 @weight 2 mult. for row 2H + vmovl.u8 q8, d17 @converting row 3H in source 1 to 16-bit + vmovl.u8 q9, d19 @converting row 3H in source 2 to 16-bit + + vmul.s16 q13, q13, q1 @weight 1 mult. for row 3L + vmla.s16 q13, q5, q2 @weight 2 mult. for row 3L + vmovl.u8 q15, d20 @converting row 4L in source 1 to 16-bit + vmovl.u8 q7, d22 @converting row 4L in source 2 to 16-bit + + vmul.s16 q8, q8, q1 @weight 1 mult. for row 3H + vmla.s16 q8, q9, q2 @weight 2 mult. for row 3H + vmovl.u8 q10, d21 @converting row 4H in source 1 to 16-bit + vmovl.u8 q11, d23 @converting row 4H in source 2 to 16-bit + + vmul.s16 q15, q15, q1 @weight 1 mult. for row 4L + vmla.s16 q15, q7, q2 @weight 2 mult. for row 4L + vrshl.s16 q12, q12, q0 @rounds off the weighted samples from row 1L + + vmul.s16 q10, q10, q1 @weight 1 mult. for row 4H + vmla.s16 q10, q11, q2 @weight 2 mult. for row 4H + vrshl.s16 q4, q4, q0 @rounds off the weighted samples from row 1H + + vrshl.s16 q14, q14, q0 @rounds off the weighted samples from row 2L + vadd.s16 q12, q12, q3 @adding offset for row 1L + vrshl.s16 q6, q6, q0 @rounds off the weighted samples from row 2H + vadd.s16 q4, q4, q3 @adding offset for row 1H + vrshl.s16 q13, q13, q0 @rounds off the weighted samples from row 3L + vadd.s16 q14, q14, q3 @adding offset for row 2L + vrshl.s16 q8, q8, q0 @rounds off the weighted samples from row 3H + vadd.s16 q6, q6, q3 @adding offset for row 2H + vrshl.s16 q15, q15, q0 @rounds off the weighted samples from row 4L + vadd.s16 q13, q13, q3 @adding offset for row 3L + vrshl.s16 q10, q10, q0 @rounds off the weighted samples from row 4H + vadd.s16 q8, q8, q3 @adding offset for row 3H + + vqmovun.s16 d10, q12 @saturating row 1L to unsigned 8-bit + vadd.s16 q15, q15, q3 @adding offset for row 4L + vqmovun.s16 d11, q4 @saturating row 1H to unsigned 8-bit + vadd.s16 q10, q10, q3 @adding offset for row 4H + + vqmovun.s16 d18, q14 @saturating row 2L to unsigned 8-bit + vqmovun.s16 d19, q6 @saturating row 2H to unsigned 8-bit + vqmovun.s16 d14, q13 @saturating row 3L to unsigned 8-bit + vqmovun.s16 d15, q8 @saturating row 3H to unsigned 8-bit + vst1.8 {q5}, [r2], r5 @store row 1 in destination + vqmovun.s16 d22, q15 @saturating row 4L to unsigned 8-bit + vqmovun.s16 d23, q10 @saturating row 4H to unsigned 8-bit + + vst1.8 {q9}, [r2], r5 @store row 2 in destination + subs r11, r11, #4 @decrement ht by 4 + vst1.8 {q7}, [r2], r5 @store row 3 in destination + vst1.8 {q11}, [r2], r5 @store row 4 in destination + + bgt loop_8_uv @if greater than 0 repeat the loop again + +end_loops_uv: + + vpop {d8-d15} + ldmfd sp!, {r4-r12, r15} @Reload the registers from sp + + diff --git a/dependencies/ih264d/common/arm/ih264_weighted_pred_a9q.s b/dependencies/ih264d/common/arm/ih264_weighted_pred_a9q.s new file mode 100644 index 00000000..80c2c6d1 --- /dev/null +++ b/dependencies/ih264d/common/arm/ih264_weighted_pred_a9q.s @@ -0,0 +1,479 @@ +@/****************************************************************************** +@ * +@ * Copyright (C) 2015 The Android Open Source Project +@ * +@ * Licensed under the Apache License, Version 2.0 (the "License"); +@ * you may not use this file except in compliance with the License. +@ * You may obtain a copy of the License at: +@ * +@ * http://www.apache.org/licenses/LICENSE-2.0 +@ * +@ * Unless required by applicable law or agreed to in writing, software +@ * distributed under the License is distributed on an "AS IS" BASIS, +@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@ * See the License for the specific language governing permissions and +@ * limitations under the License. +@ * +@ ***************************************************************************** +@ * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +@*/ +@** +@****************************************************************************** +@* @file +@* ih264_weighted_pred_a9q.s +@* +@* @brief +@* Contains function definitions for weighted prediction. +@* +@* @author +@* Kaushik Senthoor R +@* +@* @par List of Functions: +@* +@* - ih264_weighted_pred_luma_a9q() +@* - ih264_weighted_pred_chroma_a9q() +@* +@* @remarks +@* None +@* +@******************************************************************************* +@* +@******************************************************************************* +@* @function +@* ih264_weighted_pred_luma_a9q() +@* +@* @brief +@* This routine performs the default weighted prediction as described in sec +@* 8.4.2.3.2 titled "Weighted sample prediction process" for luma. +@* +@* @par Description: +@* This function gets a ht x wd block, calculates the weighted sample, rounds +@* off, adds offset and stores it in the destination block. +@* +@* @param[in] pu1_src: +@* UWORD8 Pointer to the buffer containing the input block. +@* +@* @param[out] pu1_dst +@* UWORD8 pointer to the destination where the output block is stored. +@* +@* @param[in] src_strd +@* Stride of the input buffer +@* +@* @param[in] dst_strd +@* Stride of the destination buffer +@* +@* @param[in] log_wd +@* number of bits to be rounded off +@* +@* @param[in] wt +@* weight for the weighted prediction +@* +@* @param[in] ofst +@* offset used after rounding off +@* +@* @param[in] ht +@* integer height of the array +@* +@* @param[in] wd +@* integer width of the array +@* +@* @returns +@* None +@* +@* @remarks +@* (ht,wd) can be (4,4), (4,8), (8,4), (8,8), (8,16), (16,8) or (16,16). +@* +@******************************************************************************* +@* +@void ih264_weighted_pred_luma_a9q(UWORD8 *pu1_src, +@ UWORD8 *pu1_dst, +@ WORD32 src_strd, +@ WORD32 dst_strd, +@ WORD32 log_wd, +@ WORD32 wt, +@ WORD32 ofst, +@ WORD32 ht, +@ WORD32 wd) +@ +@**************Variables Vs Registers***************************************** +@ r0 => pu1_src +@ r1 => pu1_dst +@ r2 => src_strd +@ r3 => dst_strd +@ [sp] => log_wd (r4) +@ [sp+4] => wt (r5) +@ [sp+8] => ofst (r6) +@ [sp+12] => ht (r7) +@ [sp+16] => wd (r8) +@ +.text +.p2align 2 + + .global ih264_weighted_pred_luma_a9q + +ih264_weighted_pred_luma_a9q: + + stmfd sp!, {r4-r9, r14} @stack stores the values of the arguments + ldr r5, [sp, #32] @Load wt + ldr r4, [sp, #28] @Load log_wd in r4 + ldr r6, [sp, #36] @Load ofst + ldr r7, [sp, #40] @Load ht + ldr r8, [sp, #44] @Load wd + vpush {d8-d15} + + vdup.16 d2, r5 @D2 = wt (16-bit) + neg r9, r4 @r9 = -log_wd + vdup.8 d3, r6 @D3 = ofst (8-bit) + cmp r8, #16 @check if wd is 16 + vdup.16 q0, r9 @Q0 = -log_wd (16-bit) + beq loop_16 @branch if wd is 16 + + cmp r8, #8 @check if wd is 8 + beq loop_8 @branch if wd is 8 + +loop_4: @each iteration processes four rows + + vld1.32 d4[0], [r0], r2 @load row 1 in source + vld1.32 d4[1], [r0], r2 @load row 2 in source + vld1.32 d6[0], [r0], r2 @load row 3 in source + vld1.32 d6[1], [r0], r2 @load row 4 in source + + vmovl.u8 q2, d4 @converting rows 1,2 to 16-bit + vmovl.u8 q3, d6 @converting rows 3,4 to 16-bit + + vmul.s16 q2, q2, d2[0] @weight mult. for rows 1,2 + vmul.s16 q3, q3, d2[0] @weight mult. for rows 3,4 + + subs r7, r7, #4 @decrement ht by 4 + vrshl.s16 q2, q2, q0 @rounds off the weighted samples from rows 1,2 + vrshl.s16 q3, q3, q0 @rounds off the weighted samples from rows 3,4 + + vaddw.s8 q2, q2, d3 @adding offset for rows 1,2 + vaddw.s8 q3, q3, d3 @adding offset for rows 3,4 + + vqmovun.s16 d4, q2 @saturating rows 1,2 to unsigned 8-bit + vqmovun.s16 d6, q3 @saturating rows 3,4 to unsigned 8-bit + + vst1.32 d4[0], [r1], r3 @store row 1 in destination + vst1.32 d4[1], [r1], r3 @store row 2 in destination + vst1.32 d6[0], [r1], r3 @store row 3 in destination + vst1.32 d6[1], [r1], r3 @store row 4 in destination + + bgt loop_4 @if greater than 0 repeat the loop again + + b end_loops + +loop_8: @each iteration processes four rows + + vld1.8 d4, [r0], r2 @load row 1 in source + vld1.8 d6, [r0], r2 @load row 2 in source + vld1.8 d8, [r0], r2 @load row 3 in source + vmovl.u8 q2, d4 @converting row 1 to 16-bit + vld1.8 d10, [r0], r2 @load row 4 in source + vmovl.u8 q3, d6 @converting row 2 to 16-bit + + vmovl.u8 q4, d8 @converting row 3 to 16-bit + vmul.s16 q2, q2, d2[0] @weight mult. for row 1 + vmovl.u8 q5, d10 @converting row 4 to 16-bit + vmul.s16 q3, q3, d2[0] @weight mult. for row 2 + vmul.s16 q4, q4, d2[0] @weight mult. for row 3 + vmul.s16 q5, q5, d2[0] @weight mult. for row 4 + + vrshl.s16 q2, q2, q0 @rounds off the weighted samples from row 1 + vrshl.s16 q3, q3, q0 @rounds off the weighted samples from row 2 + vrshl.s16 q4, q4, q0 @rounds off the weighted samples from row 3 + vaddw.s8 q2, q2, d3 @adding offset for row 1 + vrshl.s16 q5, q5, q0 @rounds off the weighted samples from row 4 + vaddw.s8 q3, q3, d3 @adding offset for row 2 + + vaddw.s8 q4, q4, d3 @adding offset for row 3 + vqmovun.s16 d4, q2 @saturating row 1 to unsigned 8-bit + vaddw.s8 q5, q5, d3 @adding offset for row 4 + vqmovun.s16 d6, q3 @saturating row 2 to unsigned 8-bit + vqmovun.s16 d8, q4 @saturating row 3 to unsigned 8-bit + vqmovun.s16 d10, q5 @saturating row 4 to unsigned 8-bit + + vst1.8 d4, [r1], r3 @store row 1 in destination + vst1.8 d6, [r1], r3 @store row 2 in destination + subs r7, r7, #4 @decrement ht by 4 + vst1.8 d8, [r1], r3 @store row 3 in destination + vst1.8 d10, [r1], r3 @store row 4 in destination + + bgt loop_8 @if greater than 0 repeat the loop again + + b end_loops + +loop_16: @each iteration processes two rows + + vld1.8 {q2}, [r0], r2 @load row 1 in source + vld1.8 {q3}, [r0], r2 @load row 2 in source + vmovl.u8 q6, d4 @converting row 1L to 16-bit + vld1.8 {q4}, [r0], r2 @load row 3 in source + vmovl.u8 q7, d5 @converting row 1H to 16-bit + vld1.8 {q5}, [r0], r2 @load row 4 in source + + vmovl.u8 q8, d6 @converting row 2L to 16-bit + vmul.s16 q6, q6, d2[0] @weight mult. for row 1L + vmovl.u8 q9, d7 @converting row 2H to 16-bit + vmul.s16 q7, q7, d2[0] @weight mult. for row 1H + vmovl.u8 q10, d8 @converting row 3L to 16-bit + vmul.s16 q8, q8, d2[0] @weight mult. for row 2L + vmovl.u8 q11, d9 @converting row 3H to 16-bit + vmul.s16 q9, q9, d2[0] @weight mult. for row 2H + vmovl.u8 q12, d10 @converting row 4L to 16-bit + vmul.s16 q10, q10, d2[0] @weight mult. for row 3L + vmovl.u8 q13, d11 @converting row 4H to 16-bit + vmul.s16 q11, q11, d2[0] @weight mult. for row 3H + + vmul.s16 q12, q12, d2[0] @weight mult. for row 4L + vrshl.s16 q6, q6, q0 @rounds off the weighted samples from row 1L + vmul.s16 q13, q13, d2[0] @weight mult. for row 4H + + vrshl.s16 q7, q7, q0 @rounds off the weighted samples from row 1H + vrshl.s16 q8, q8, q0 @rounds off the weighted samples from row 2L + vaddw.s8 q6, q6, d3 @adding offset for row 1L + vrshl.s16 q9, q9, q0 @rounds off the weighted samples from row 2H + vaddw.s8 q7, q7, d3 @adding offset for row 1H + vqmovun.s16 d4, q6 @saturating row 1L to unsigned 8-bit + vrshl.s16 q10, q10, q0 @rounds off the weighted samples from row 3L + vaddw.s8 q8, q8, d3 @adding offset for row 2L + vqmovun.s16 d5, q7 @saturating row 1H to unsigned 8-bit + vrshl.s16 q11, q11, q0 @rounds off the weighted samples from row 3H + vaddw.s8 q9, q9, d3 @adding offset for row 2H + vqmovun.s16 d6, q8 @saturating row 2L to unsigned 8-bit + vrshl.s16 q12, q12, q0 @rounds off the weighted samples from row 4L + vaddw.s8 q10, q10, d3 @adding offset for row 3L + vqmovun.s16 d7, q9 @saturating row 2H to unsigned 8-bit + vrshl.s16 q13, q13, q0 @rounds off the weighted samples from row 4H + vaddw.s8 q11, q11, d3 @adding offset for row 3H + + vqmovun.s16 d8, q10 @saturating row 3L to unsigned 8-bit + vaddw.s8 q12, q12, d3 @adding offset for row 4L + vqmovun.s16 d9, q11 @saturating row 3H to unsigned 8-bit + vaddw.s8 q13, q13, d3 @adding offset for row 4H + + vqmovun.s16 d10, q12 @saturating row 4L to unsigned 8-bit + vst1.8 {q2}, [r1], r3 @store row 1 in destination + vqmovun.s16 d11, q13 @saturating row 4H to unsigned 8-bit + vst1.8 {q3}, [r1], r3 @store row 2 in destination + subs r7, r7, #4 @decrement ht by 4 + vst1.8 {q4}, [r1], r3 @store row 3 in destination + vst1.8 {q5}, [r1], r3 @store row 4 in destination + + bgt loop_16 @if greater than 0 repeat the loop again + +end_loops: + + vpop {d8-d15} + ldmfd sp!, {r4-r9, r15} @Reload the registers from sp + + +@******************************************************************************* +@* @function +@* ih264_weighted_pred_chroma_a9q() +@* +@* @brief +@* This routine performs the default weighted prediction as described in sec +@* 8.4.2.3.2 titled "Weighted sample prediction process" for chroma. +@* +@* @par Description: +@* This function gets a ht x wd block, calculates the weighted sample, rounds +@* off, adds offset and stores it in the destination block for U and V. +@* +@* @param[in] pu1_src: +@* UWORD8 Pointer to the buffer containing the input block. +@* +@* @param[out] pu1_dst +@* UWORD8 pointer to the destination where the output block is stored. +@* +@* @param[in] src_strd +@* Stride of the input buffer +@* +@* @param[in] dst_strd +@* Stride of the destination buffer +@* +@* @param[in] log_wd +@* number of bits to be rounded off +@* +@* @param[in] wt +@* weights for the weighted prediction for U and V +@* +@* @param[in] ofst +@* offsets used after rounding off for U and V +@* +@* @param[in] ht +@* integer height of the array +@* +@* @param[in] wd +@* integer width of the array +@* +@* @returns +@* None +@* +@* @remarks +@* (ht,wd) can be (2,2), (2,4), (4,2), (4,4), (4,8), (8,4) or (8,8). +@* +@******************************************************************************* +@* +@void ih264_weighted_pred_chroma_a9q(UWORD8 *pu1_src, +@ UWORD8 *pu1_dst, +@ WORD32 src_strd, +@ WORD32 dst_strd, +@ WORD32 log_wd, +@ WORD32 wt, +@ WORD32 ofst, +@ WORD32 ht, +@ WORD32 wd) +@ +@**************Variables Vs Registers***************************************** +@ r0 => pu1_src +@ r1 => pu1_dst +@ r2 => src_strd +@ r3 => dst_strd +@ [sp] => log_wd (r4) +@ [sp+4] => wt (r5) +@ [sp+8] => ofst (r6) +@ [sp+12] => ht (r7) +@ [sp+16] => wd (r8) +@ + + + .global ih264_weighted_pred_chroma_a9q + +ih264_weighted_pred_chroma_a9q: + + stmfd sp!, {r4-r9, r14} @stack stores the values of the arguments + + ldr r4, [sp, #28] @Load log_wd in r4 + ldr r5, [sp, #32] @Load wt = {wt_u (16-bit), wt_v (16-bit)} + ldr r6, [sp, #36] @Load ofst = {ofst_u (8-bit), ofst_v (8-bit)} + ldr r8, [sp, #44] @Load wd + + neg r9, r4 @r9 = -log_wd + vdup.32 q1, r5 @Q1 = {wt_u (16-bit), wt_v (16-bit)} + ldr r7, [sp, #40] @Load ht + vpush {d8-d15} + vdup.16 d4, r6 @D4 = {ofst_u (8-bit), ofst_v (8-bit)} + cmp r8, #8 @check if wd is 8 + vdup.16 q0, r9 @Q0 = -log_wd (16-bit) + beq loop_8_uv @branch if wd is 8 + + cmp r8, #4 @check if ws is 4 + beq loop_4_uv @branch if wd is 4 + +loop_2_uv: @each iteration processes two rows + + vld1.32 d6[0], [r0], r2 @load row 1 in source + vld1.32 d6[1], [r0], r2 @load row 2 in source + + vmovl.u8 q3, d6 @converting rows 1,2 to 16-bit + + vmul.s16 q3, q3, q1 @weight mult. for rows 1,2 + + vrshl.s16 q3, q3, q0 @rounds off the weighted samples from rows 1,2 + + vaddw.s8 q3, q3, d4 @adding offset for rows 1,2 + + vqmovun.s16 d6, q3 @saturating rows 1,2 to unsigned 8-bit + + subs r7, r7, #2 @decrement ht by 2 + vst1.32 d6[0], [r1], r3 @store row 1 in destination + vst1.32 d6[1], [r1], r3 @store row 2 in destination + + bgt loop_2_uv @if greater than 0 repeat the loop again + + b end_loops_uv + +loop_4_uv: @each iteration processes two rows + + vld1.8 d6, [r0], r2 @load row 1 in source + vld1.8 d8, [r0], r2 @load row 2 in source + + vmovl.u8 q3, d6 @converting row 1 to 16-bit + vmovl.u8 q4, d8 @converting row 2 to 16-bit + + vmul.s16 q3, q3, q1 @weight mult. for row 1 + vmul.s16 q4, q4, q1 @weight mult. for row 2 + + subs r7, r7, #2 @decrement ht by 2 + vrshl.s16 q3, q3, q0 @rounds off the weighted samples from row 1 + vrshl.s16 q4, q4, q0 @rounds off the weighted samples from row 2 + + vaddw.s8 q3, q3, d4 @adding offset for row 1 + vaddw.s8 q4, q4, d4 @adding offset for row 2 + + vqmovun.s16 d6, q3 @saturating row 1 to unsigned 8-bit + vqmovun.s16 d8, q4 @saturating row 2 to unsigned 8-bit + + vst1.8 d6, [r1], r3 @store row 1 in destination + vst1.8 d8, [r1], r3 @store row 2 in destination + + bgt loop_4_uv @if greater than 0 repeat the loop again + + b end_loops_uv + +loop_8_uv: @each iteration processes two rows + + vld1.8 {q3}, [r0], r2 @load row 1 in source + vld1.8 {q4}, [r0], r2 @load row 2 in source + vmovl.u8 q7, d6 @converting row 1L to 16-bit + vld1.8 {q5}, [r0], r2 @load row 3 in source + vmovl.u8 q8, d7 @converting row 1H to 16-bit + vld1.8 {q6}, [r0], r2 @load row 4 in source + + vmul.s16 q7, q7, q1 @weight mult. for row 1L + vmovl.u8 q9, d8 @converting row 2L to 16-bit + vmul.s16 q8, q8, q1 @weight mult. for row 1H + vmovl.u8 q10, d9 @converting row 2H to 16-bit + vmul.s16 q9, q9, q1 @weight mult. for row 2L + vmovl.u8 q11, d10 @converting row 3L to 16-bit + vmul.s16 q10, q10, q1 @weight mult. for row 2H + vmovl.u8 q12, d11 @converting row 3H to 16-bit + vmul.s16 q11, q11, q1 @weight mult. for row 3L + vmovl.u8 q13, d12 @converting row 4L to 16-bit + vmul.s16 q12, q12, q1 @weight mult. for row 3H + vmovl.u8 q14, d13 @converting row 4H to 16-bit + + vmul.s16 q13, q13, q1 @weight mult. for row 4L + vrshl.s16 q7, q7, q0 @rounds off the weighted samples from row 1L + vmul.s16 q14, q14, q1 @weight mult. for row 4H + + vrshl.s16 q8, q8, q0 @rounds off the weighted samples from row 1H + vrshl.s16 q9, q9, q0 @rounds off the weighted samples from row 2L + vaddw.s8 q7, q7, d4 @adding offset for row 1L + vrshl.s16 q10, q10, q0 @rounds off the weighted samples from row 2H + vaddw.s8 q8, q8, d4 @adding offset for row 1H + vqmovun.s16 d6, q7 @saturating row 1L to unsigned 8-bit + vrshl.s16 q11, q11, q0 @rounds off the weighted samples from row 3L + vaddw.s8 q9, q9, d4 @adding offset for row 2L + vqmovun.s16 d7, q8 @saturating row 1H to unsigned 8-bit + vrshl.s16 q12, q12, q0 @rounds off the weighted samples from row 3H + vaddw.s8 q10, q10, d4 @adding offset for row 2H + vqmovun.s16 d8, q9 @saturating row 2L to unsigned 8-bit + vrshl.s16 q13, q13, q0 @rounds off the weighted samples from row 4L + vaddw.s8 q11, q11, d4 @adding offset for row 3L + vqmovun.s16 d9, q10 @saturating row 2H to unsigned 8-bit + vrshl.s16 q14, q14, q0 @rounds off the weighted samples from row 4H + vaddw.s8 q12, q12, d4 @adding offset for row 3H + + vqmovun.s16 d10, q11 @saturating row 3L to unsigned 8-bit + vaddw.s8 q13, q13, d4 @adding offset for row 4L + vqmovun.s16 d11, q12 @saturating row 3H to unsigned 8-bit + vaddw.s8 q14, q14, d4 @adding offset for row 4H + + vqmovun.s16 d12, q13 @saturating row 4L to unsigned 8-bit + vst1.8 {q3}, [r1], r3 @store row 1 in destination + vqmovun.s16 d13, q14 @saturating row 4H to unsigned 8-bit + vst1.8 {q4}, [r1], r3 @store row 2 in destination + subs r7, r7, #4 @decrement ht by 4 + vst1.8 {q5}, [r1], r3 @store row 3 in destination + vst1.8 {q6}, [r1], r3 @store row 4 in destination + + bgt loop_8_uv @if greater than 0 repeat the loop again + +end_loops_uv: + + vpop {d8-d15} + ldmfd sp!, {r4-r9, r15} @Reload the registers from sp + + diff --git a/dependencies/ih264d/common/armv8/ih264_deblk_chroma_av8.s b/dependencies/ih264d/common/armv8/ih264_deblk_chroma_av8.s new file mode 100644 index 00000000..b7f2d583 --- /dev/null +++ b/dependencies/ih264d/common/armv8/ih264_deblk_chroma_av8.s @@ -0,0 +1,587 @@ +//****************************************************************************** +//* +//* Copyright (C) 2015 The Android Open Source Project +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at: +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. +//* +//***************************************************************************** +//* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +//*/ +///*****************************************************************************/ +///* */ +///* File Name : ih264_deblk_chroma_av8.s */ +///* */ +///* Description : Contains function definitions for deblocking luma */ +///* edge. Functions are coded in NEON assembly and can */ +///* be compiled using ARM RVDS. */ +///* */ +///* List of Functions : ih264_deblk_chroma_vert_bs4_av8() */ +///* ih264_deblk_chroma_vert_bslt4_av8() */ +///* ih264_deblk_chroma_horz_bs4_av8() */ +///* ih264_deblk_chroma_horz_bslt4_av8() */ +///* Issues / Problems : None */ +///* */ +///* Revision History : */ +///* */ +///* DD MM YYYY Author(s) Changes (Describe the changes made) */ +///* 28 11 2013 Ittiam Draft */ +///*****************************************************************************/ + + +.text +.p2align 2 +.include "ih264_neon_macros.s" + +///** +//******************************************************************************* +//* +//* @brief +//* Performs filtering of a chroma block horizontal edge when the +//* boundary strength is set to 4 in high profile +//* +//* @par Description: +//* This operation is described in Sec. 8.7.2.4 under the title +//* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. +//* +//* @param[in] x0 - pu1_src +//* Pointer to the src sample q0 +//* +//* @param[in] w1 - src_strd +//* Source stride +//* +//* @param[in] w2 - alpha_cb +//* Alpha Value for the boundary in U +//* +//* @param[in] w3 - beta_cb +//* Beta Value for the boundary in U +//* +//* @param[in] w4 - alpha_cr +//* Alpha Value for the boundary in V +//* +//* @param[in] w5 - beta_cr +//* Beta Value for the boundary in V +//* +//* @returns +//* None +//* +//* @remarks +//* None +//* +//******************************************************************************* +//*/ + + .global ih264_deblk_chroma_horz_bs4_av8 + +ih264_deblk_chroma_horz_bs4_av8: + + // STMFD sp!,{x4-x6,x14} // + push_v_regs + stp x19, x20, [sp, #-16]! + sxtw x1, w1 + mov x6, x5 + mov x5, x4 + sub x0, x0, x1, lsl #1 //x0 = uc_edgePixel pointing to p1 of chroma + ld2 {v6.8b, v7.8b}, [x0], x1 //D6 = p1u , D7 = p1v + mov x4, x0 //Keeping a backup of the pointer p0 of chroma + ld2 {v4.8b, v5.8b}, [x0], x1 //D4 = p0u , D5 = p0v + dup v20.8b, w2 //D20 contains alpha_cb + dup v21.8b, w5 //D21 contains alpha_cr + mov v20.d[1], v21.d[0] + ld2 {v0.8b, v1.8b}, [x0], x1 //D0 = q0u , D1 = q0v + uaddl v8.8h, v6.8b, v0.8b // + uaddl v10.8h, v7.8b, v1.8b //Q4,Q5 = q0 + p1 + movi v31.8b, #2 // + ld2 {v2.8b, v3.8b}, [x0] //D2 = q1u , D3 = q1v + mov v0.d[1], v1.d[0] + mov v2.d[1], v3.d[0] + mov v4.d[1], v5.d[0] + mov v6.d[1], v7.d[0] + uabd v26.16b, v6.16b , v4.16b //Q13 = ABS(p1 - p0) + umlal v8.8h, v2.8b, v31.8b // + umlal v10.8h, v3.8b, v31.8b //Q5,Q4 = (X2(q1U) + q0U + p1U) + uabd v22.16b, v4.16b , v0.16b //Q11 = ABS(p0 - q0) + uabd v24.16b, v2.16b , v0.16b //Q12 = ABS(q1 - q0) + uaddl v14.8h, v4.8b, v2.8b // + uaddl v28.8h, v5.8b, v3.8b //Q14,Q7 = P0 + Q1 + dup v16.8b, w3 //D16 contains beta_cb + dup v17.8b, w6 //D17 contains beta_cr + mov v16.d[1], v17.d[0] + umlal v14.8h, v6.8b, v31.8b // + umlal v28.8h, v7.8b, v31.8b //Q14,Q7 = (X2(p1U) + p0U + q1U) + cmhs v18.16b, v22.16b, v20.16b + cmhs v24.16b, v24.16b, v16.16b + cmhs v26.16b, v26.16b, v16.16b + rshrn v8.8b, v8.8h, #2 // + rshrn v9.8b, v10.8h, #2 //Q4 = (X2(q1U) + q0U + p1U + 2) >> 2 + mov v8.d[1], v9.d[0] + orr v18.16b, v18.16b , v24.16b //Q9 = ( ABS(p0 - q0) >= Alpha ) | ( ABS(q1 - q0) >= Beta ) + rshrn v10.8b, v14.8h, #2 // + rshrn v11.8b, v28.8h, #2 //Q5 = (X2(p1U) + p0U + q1U + 2) >> 2 + mov v10.d[1], v11.d[0] + orr v18.16b, v18.16b , v26.16b //Q9 = ( ABS(p0 - q0) >= Alpha ) | ( ABS(q1 - q0) >= Beta ) | ( ABS(p1 - p0) >= Beta ) + bit v10.16b, v4.16b , v18.16b // + bit v8.16b, v0.16b , v18.16b // + mov v11.d[0], v10.d[1] + mov v9.d[0], v8.d[1] + st2 {v10.8b, v11.8b}, [x4], x1 // + st2 {v8.8b, v9.8b}, [x4] // + // LDMFD sp!,{x4-x6,pc} // + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + + +///** +//******************************************************************************* +//* +//* @brief +//* Performs filtering of a chroma block vertical edge when the +//* boundary strength is set to 4 in high profile +//* +//* @par Description: +//* This operation is described in Sec. 8.7.2.4 under the title +//* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. +//* +//* @param[in] x0 - pu1_src +//* Pointer to the src sample q0 +//* +//* @param[in] w1 - src_strd +//* Source stride +//* +//* @param[in] w2 - alpha_cb +//* Alpha Value for the boundary in U +//* +//* @param[in] w3 - beta_cb +//* Beta Value for the boundary in U +//* +//* @param[in] w4 - alpha_cr +//* Alpha Value for the boundary in V +//* +//* @param[in] w5 - beta_cr +//* Beta Value for the boundary in V +//* +//* @returns +//* None +//* +//* @remarks +//* None +//* +//******************************************************************************* +//*/ + + .global ih264_deblk_chroma_vert_bs4_av8 + +ih264_deblk_chroma_vert_bs4_av8: + + // STMFD sp!,{x4,x5,x12,x14} + push_v_regs + stp x19, x20, [sp, #-16]! + sxtw x1, w1 + + sub x0, x0, #4 //point x0 to p1u of row0. + mov x12, x0 //keep a back up of x0 for buffer write + + add w2, w2, w4, lsl #8 //w2 = (alpha_cr,alpha_cb) + add w3, w3, w5, lsl #8 //w3 = (beta_cr,beta_cb) + + ld4 {v0.h, v1.h, v2.h, v3.h}[0], [x0], x1 + ld4 {v0.h, v1.h, v2.h, v3.h}[1], [x0], x1 + ld4 {v0.h, v1.h, v2.h, v3.h}[2], [x0], x1 + ld4 {v0.h, v1.h, v2.h, v3.h}[3], [x0], x1 + + ld4 {v4.h, v5.h, v6.h, v7.h}[0], [x0], x1 + ld4 {v4.h, v5.h, v6.h, v7.h}[1], [x0], x1 + ld4 {v4.h, v5.h, v6.h, v7.h}[2], [x0], x1 + ld4 {v4.h, v5.h, v6.h, v7.h}[3], [x0], x1 + + mov v10.16b, v2.16b + mov v2.16b, v1.16b + mov v1.16b, v4.16b + mov v4.16b, v10.16b + mov v10.16b, v6.16b + mov v6.16b, v3.16b + mov v3.16b, v5.16b + mov v5.16b, v10.16b + + dup v22.8h, w2 //Q11 = alpha + dup v24.8h, w3 //Q12 = beta + movi v31.8b, #2 + + mov v0.d[1], v1.d[0] + mov v2.d[1], v3.d[0] + mov v4.d[1], v5.d[0] + mov v6.d[1], v7.d[0] + + uabd v8.16b, v2.16b , v4.16b //|p0-q0| + uabd v10.16b, v6.16b , v4.16b //|q1-q0| + uabd v12.16b, v0.16b , v2.16b //|p1-p0| + uaddl v14.8h, v2.8b, v6.8b + uaddl v16.8h, v3.8b, v7.8b //(p0 + q1) + cmhi v8.16b, v22.16b , v8.16b //|p0-q0| < alpha ? + cmhi v10.16b, v24.16b , v10.16b //|q1-q0| < beta ? + cmhi v12.16b, v24.16b , v12.16b //|p1-p0| < beta ? + umlal v14.8h, v0.8b, v31.8b + umlal v16.8h, v1.8b, v31.8b //2*p1 + (p0 + q1) + uaddl v18.8h, v0.8b, v4.8b + uaddl v20.8h, v1.8b, v5.8b //(p1 + q0) + and v8.16b, v8.16b , v10.16b //|p0-q0| < alpha && |q1-q0| < beta + umlal v18.8h, v6.8b, v31.8b + umlal v20.8h, v7.8b, v31.8b //2*q1 + (p1 + q0) + + rshrn v14.8b, v14.8h, #2 + rshrn v15.8b, v16.8h, #2 //(2*p1 + (p0 + q1) + 2) >> 2 + mov v14.d[1], v15.d[0] + and v8.16b, v8.16b , v12.16b //|p0-q0| < alpha && |q1-q0| < beta && |p1-p0| < beta + rshrn v18.8b, v18.8h, #2 + rshrn v19.8b, v20.8h, #2 //(2*q1 + (p1 + q0) + 2) >> 2 + mov v18.d[1], v19.d[0] + bit v2.16b, v14.16b , v8.16b + bit v4.16b, v18.16b , v8.16b + + mov v1.d[0], v0.d[1] + mov v3.d[0], v2.d[1] + mov v5.d[0], v4.d[1] + mov v7.d[0], v6.d[1] + + mov v10.16b, v1.16b + mov v1.16b, v2.16b + mov v2.16b, v4.16b + mov v4.16b, v10.16b + mov v10.16b, v3.16b + mov v3.16b, v6.16b + mov v6.16b, v5.16b + mov v5.16b, v10.16b + + st4 {v0.h, v1.h, v2.h, v3.h}[0], [x12], x1 + st4 {v0.h, v1.h, v2.h, v3.h}[1], [x12], x1 + st4 {v0.h, v1.h, v2.h, v3.h}[2], [x12], x1 + st4 {v0.h, v1.h, v2.h, v3.h}[3], [x12], x1 + + st4 {v4.h, v5.h, v6.h, v7.h}[0], [x12], x1 + st4 {v4.h, v5.h, v6.h, v7.h}[1], [x12], x1 + st4 {v4.h, v5.h, v6.h, v7.h}[2], [x12], x1 + st4 {v4.h, v5.h, v6.h, v7.h}[3], [x12], x1 + + // LDMFD sp!,{x4,x5,x12,pc} + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + + +///** +//******************************************************************************* +//* +//* @brief +//* Performs filtering of a chroma block horizontal edge for cases where the +//* boundary strength is less than 4 in high profile +//* +//* @par Description: +//* This operation is described in Sec. 8.7.2.4 under the title +//* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. +//* +//* @param[in] x0 - pu1_src +//* Pointer to the src sample q0 +//* +//* @param[in] w1 - src_strd +//* Source stride +//* +//* @param[in] w2 - alpha_cb +//* Alpha Value for the boundary in U +//* +//* @param[in] w3 - beta_cb +//* Beta Value for the boundary in U +//* +//* @param[in] w4 - alpha_cr +//* Alpha Value for the boundary in V +//* +//* @param[in] w5 - beta_cr +//* Beta Value for the boundary in V +//* +//* @param[in] w6 - u4_bs +//* Packed Boundary strength array +//* +//* @param[in] x7 - pu1_cliptab_cb +//* tc0_table for U +//* +//* @param[in] sp(0) - pu1_cliptab_cr +//* tc0_table for V +//* +//* @returns +//* None +//* +//* @remarks +//* None +//* +//******************************************************************************* +//*/ + + .global ih264_deblk_chroma_horz_bslt4_av8 + +ih264_deblk_chroma_horz_bslt4_av8: + + // STMFD sp!,{x4-x9,x14} // + push_v_regs + stp x19, x20, [sp, #-16]! + sxtw x1, w1 + ldr x8, [sp, #80] + sub x0, x0, x1, lsl #1 //x0 = uc_edgePixelU pointing to p1 of chroma U + rev w6, w6 // + mov v12.s[0], w6 //D12[0] = ui_Bs + ld1 {v16.s}[0], [x7] //D16[0] contains cliptab_cb + ld1 {v17.s}[0], [x8] //D17[0] contains cliptab_cr + ld2 {v6.8b, v7.8b}, [x0], x1 //Q3=p1 + tbl v14.8b, {v16.16b}, v12.8b //Retreiving cliptab values for U + tbl v28.8b, {v17.16b}, v12.8b //Retrieving cliptab values for V + uxtl v12.8h, v12.8b //Q6 = uc_Bs in each 16 bit scalar + mov x6, x0 //Keeping a backup of the pointer to chroma U P0 + ld2 {v4.8b, v5.8b}, [x0], x1 //Q2=p0 + movi v30.8b, #1 // + dup v20.8b, w2 //D20 contains alpha_cb + dup v21.8b, w4 //D21 contains alpha_cr + mov v20.d[1], v21.d[0] + ld2 {v0.8b, v1.8b}, [x0], x1 //Q0=q0 + uxtl v14.8h, v14.8b // + uxtl v28.8h, v28.8b // + mov v15.d[0], v28.d[0] //D14 has cliptab values for U, D15 for V + mov v14.d[1], v28.d[0] + ld2 {v2.8b, v3.8b}, [x0] //Q1=q1 + usubl v10.8h, v1.8b, v5.8b // + usubl v8.8h, v0.8b, v4.8b //Q5,Q4 = (q0 - p0) + mov v6.d[1], v7.d[0] + mov v4.d[1], v5.d[0] + uabd v26.16b, v6.16b , v4.16b //Q13 = ABS(p1 - p0) + shl v10.8h, v10.8h, #2 //Q5 = (q0 - p0)<<2 + mov v0.d[1], v1.d[0] + uabd v22.16b, v4.16b , v0.16b //Q11 = ABS(p0 - q0) + shl v8.8h, v8.8h, #2 //Q4 = (q0 - p0)<<2 + mov v14.d[1], v15.d[0] + sli v14.8h, v14.8h, #8 + mov v15.d[0], v14.d[1] + mov v2.d[1], v3.d[0] + uabd v24.16b, v2.16b , v0.16b //Q12 = ABS(q1 - q0) + cmhs v18.16b, v22.16b, v20.16b + usubl v20.8h, v6.8b, v2.8b //Q10 = (p1 - q1)L + usubl v6.8h, v7.8b, v3.8b //Q3 = (p1 - q1)H + dup v16.8b, w3 //Q8 contains beta_cb + dup v17.8b, w5 //Q8 contains beta_cr + mov v16.d[1], v17.d[0] + add v8.8h, v8.8h , v20.8h // + add v10.8h, v10.8h , v6.8h //Q5,Q4 = [ (q0 - p0)<<2 ] + (p1 - q1) + cmhs v24.16b, v24.16b, v16.16b + cmgt v12.4h, v12.4h, #0 + sqrshrn v8.8b, v8.8h, #3 // + sqrshrn v9.8b, v10.8h, #3 //Q4 = i_macro = (((q0 - p0)<<2) + (p1 - q1) + 4)>>3 + mov v8.d[1], v9.d[0] + add v14.8b, v14.8b , v30.8b //D14 = C = C0+1 for U + cmhs v26.16b, v26.16b, v16.16b + orr v18.16b, v18.16b , v24.16b //Q9 = ( ABS(p0 - q0) >= Alpha ) | ( ABS(q1 - q0) >= Beta ) + abs v6.16b, v8.16b //Q4 = ABS (i_macro) + add v15.8b, v15.8b , v30.8b //D15 = C = C0+1 for V + mov v14.d[1], v15.d[0] + mov v13.8b, v12.8b + mov v12.d[1], v13.d[0] // + orr v18.16b, v18.16b , v26.16b //Q9 = ( ABS(p0 - q0) >= Alpha ) | ( ABS(q1 - q0) >= Beta ) | ( ABS(p1 - p0) >= Beta ) + umin v14.16b, v6.16b , v14.16b //Q7 = delta = (ABS(i_macro) > C) ? C : ABS(i_macro) + bic v12.16b, v12.16b , v18.16b //final condition + cmge v8.16b, v8.16b, #0 + and v14.16b, v14.16b , v12.16b //Making delta zero in places where values shouldn be filterd + uqadd v16.16b, v4.16b , v14.16b //Q8 = p0 + delta + uqsub v4.16b, v4.16b , v14.16b //Q2 = p0 - delta + uqadd v18.16b, v0.16b , v14.16b //Q9 = q0 + delta + uqsub v0.16b, v0.16b , v14.16b //Q0 = q0 - delta + bif v16.16b, v4.16b , v8.16b //Q8 = (i_macro >= 0 ) ? (p0+delta) : (p0-delta) + bif v0.16b, v18.16b , v8.16b //Q0 = (i_macro >= 0 ) ? (q0-delta) : (q0+delta) + mov v17.d[0], v16.d[1] + mov v1.d[0], v0.d[1] + st2 {v16.8b, v17.8b}, [x6], x1 // + st2 {v0.8b, v1.8b}, [x6] // + + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + + + +///** +//******************************************************************************* +//* +//* @brief +//* Performs filtering of a chroma block vertical edge for cases where the +//* boundary strength is less than 4 in high profile +//* +//* @par Description: +//* This operation is described in Sec. 8.7.2.4 under the title +//* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. +//* +//* @param[in] x0 - pu1_src +//* Pointer to the src sample q0 +//* +//* @param[in] w1 - src_strd +//* Source stride +//* +//* @param[in] w2 - alpha_cb +//* Alpha Value for the boundary in U +//* +//* @param[in] w3 - beta_cb +//* Beta Value for the boundary in U +//* +//* @param[in] w4 - alpha_cr +//* Alpha Value for the boundary in V +//* +//* @param[in] w5 - beta_cr +//* Beta Value for the boundary in V +//* +//* @param[in] w6 - u4_bs +//* Packed Boundary strength array +//* +//* @param[in] x7 - pu1_cliptab_cb +//* tc0_table for U +//* +//* @param[in] sp(0) - pu1_cliptab_cr +//* tc0_table for V +//* +//* @returns +//* None +//* +//* @remarks +//* None +//* +//******************************************************************************* +//*/ + + .global ih264_deblk_chroma_vert_bslt4_av8 + +ih264_deblk_chroma_vert_bslt4_av8: + + // STMFD sp!,{x4-x7,x10-x12,x14} + push_v_regs + stp x19, x20, [sp, #-16]! + sxtw x1, w1 + mov x10, x7 + ldr x11, [sp, #80] //x11 = u4_bs + sub x0, x0, #4 //point x0 to p1u of row0. + add w2, w2, w4, lsl #8 + add w3, w3, w5, lsl #8 + mov x12, x0 //keep a back up of x0 for buffer write + ld4 {v0.h, v1.h, v2.h, v3.h}[0], [x0], x1 + ld4 {v0.h, v1.h, v2.h, v3.h}[1], [x0], x1 + ld4 {v0.h, v1.h, v2.h, v3.h}[2], [x0], x1 + ld4 {v0.h, v1.h, v2.h, v3.h}[3], [x0], x1 + + ld4 {v4.h, v5.h, v6.h, v7.h}[0], [x0], x1 + ld4 {v4.h, v5.h, v6.h, v7.h}[1], [x0], x1 + ld4 {v4.h, v5.h, v6.h, v7.h}[2], [x0], x1 + ld4 {v4.h, v5.h, v6.h, v7.h}[3], [x0], x1 + + mov v10.16b, v2.16b + mov v2.16b, v1.16b + mov v1.16b, v4.16b + mov v4.16b, v10.16b + mov v10.16b, v6.16b + mov v6.16b, v3.16b + mov v3.16b, v5.16b + mov v5.16b, v10.16b + dup v22.8h, w2 //Q11 = alpha + mov v2.d[1], v3.d[0] + mov v4.d[1], v5.d[0] + uabd v8.16b, v2.16b , v4.16b //|p0-q0| + dup v24.8h, w3 //Q12 = beta + mov v25.d[0], v24.d[1] + mov v6.d[1], v7.d[0] + mov v0.d[1], v1.d[0] + uabd v10.16b, v6.16b , v4.16b //|q1-q0| + uabd v12.16b, v0.16b , v2.16b //|p1-p0| + cmhi v8.16b, v22.16b , v8.16b //|p0-q0| < alpha ? + usubl v14.8h, v0.8b, v6.8b + cmhi v10.16b, v24.16b , v10.16b //|q1-q0| < beta ? + usubl v16.8h, v1.8b, v7.8b //(p1 - q1) + cmhi v12.16b, v24.16b , v12.16b //|p1-p0| < beta ? + usubl v18.8h, v4.8b, v2.8b + and v8.16b, v8.16b , v10.16b //|p0-q0| < alpha && |q1-q0| < beta + usubl v20.8h, v5.8b, v3.8b //(q0 - p0) + movi v28.8h, #4 + ld1 {v24.s}[0], [x10] //Load ClipTable for U + ld1 {v25.s}[0], [x11] //Load ClipTable for V + rev w6, w6 //Blocking strengths + and v8.16b, v8.16b , v12.16b //|p0-q0| < alpha && |q1-q0| < beta && |p1-p0| < beta + mov v10.s[0], w6 + mla v14.8h, v18.8h , v28.8h + mla v16.8h, v20.8h , v28.8h //4*(q0 - p0) + (p1 - q1) + uxtl v10.8h, v10.8b + sli v10.4h, v10.4h, #8 + tbl v12.8b, {v24.16b}, v10.8b //tC0 for U + tbl v13.8b, {v25.16b}, v10.8b //tC0 for V + zip1 v31.8b, v12.8b, v13.8b + zip2 v13.8b, v12.8b, v13.8b + mov v12.8b, v31.8b + mov v12.d[1], v13.d[0] + uxtl v10.4s, v10.4h + sli v10.4s, v10.4s, #16 + movi v24.16b, #1 + add v12.16b, v12.16b , v24.16b //tC0 + 1 + cmhs v10.16b, v10.16b , v24.16b + and v8.16b, v8.16b , v10.16b //|p0-q0| < alpha && |q1-q0| < beta && |p1-p0| < beta && u4_bs != 0 + // Q0 - Q3(inputs), + // Q4 (|p0-q0| < alpha && |q1-q0| < beta && |p1-p0| < beta && u4_bs != 0), + // Q6 (tC) + srshr v14.8h, v14.8h, #3 + srshr v16.8h, v16.8h, #3 //(((q0 - p0) << 2) + (p1 - q1) + 4) >> 3) + cmgt v18.8h, v14.8h , #0 + cmgt v20.8h, v16.8h , #0 + xtn v18.8b, v18.8h + xtn v19.8b, v20.8h //Q9 = sign(delta) + mov v18.d[1], v19.d[0] + abs v14.8h, v14.8h + abs v16.8h, v16.8h + xtn v14.8b, v14.8h + xtn v15.8b, v16.8h + mov v14.d[1], v15.d[0] + umin v14.16b, v14.16b , v12.16b //Q7 = |delta| + uqadd v20.16b, v2.16b , v14.16b //p0+|delta| + uqadd v22.16b, v4.16b , v14.16b //q0+|delta| + uqsub v24.16b, v2.16b , v14.16b //p0-|delta| + uqsub v26.16b, v4.16b , v14.16b //q0-|delta| + bit v24.16b, v20.16b , v18.16b //p0 + delta + bit v22.16b, v26.16b , v18.16b //q0 - delta + bit v2.16b, v24.16b , v8.16b + bit v4.16b, v22.16b , v8.16b + mov v1.d[0], v0.d[1] + mov v3.d[0], v2.d[1] + mov v5.d[0], v4.d[1] + mov v7.d[0], v6.d[1] + mov v10.16b, v1.16b + mov v1.16b, v2.16b + mov v2.16b, v4.16b + mov v4.16b, v10.16b + mov v10.16b, v3.16b + mov v3.16b, v6.16b + mov v6.16b, v5.16b + mov v5.16b, v10.16b + st4 {v0.h, v1.h, v2.h, v3.h}[0], [x12], x1 + st4 {v0.h, v1.h, v2.h, v3.h}[1], [x12], x1 + st4 {v0.h, v1.h, v2.h, v3.h}[2], [x12], x1 + st4 {v0.h, v1.h, v2.h, v3.h}[3], [x12], x1 + + st4 {v4.h, v5.h, v6.h, v7.h}[0], [x12], x1 + st4 {v4.h, v5.h, v6.h, v7.h}[1], [x12], x1 + st4 {v4.h, v5.h, v6.h, v7.h}[2], [x12], x1 + st4 {v4.h, v5.h, v6.h, v7.h}[3], [x12], x1 + + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + diff --git a/dependencies/ih264d/common/armv8/ih264_deblk_luma_av8.s b/dependencies/ih264d/common/armv8/ih264_deblk_luma_av8.s new file mode 100644 index 00000000..7705df25 --- /dev/null +++ b/dependencies/ih264d/common/armv8/ih264_deblk_luma_av8.s @@ -0,0 +1,1087 @@ +//****************************************************************************** +//* +//* Copyright (C) 2015 The Android Open Source Project +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at: +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. +//* +//***************************************************************************** +//* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +//*/ +///*****************************************************************************/ +///* */ +///* File Name : ih264_deblk_luma_av8.s */ +///* */ +///* Description : Contains function definitions for deblocking luma */ +///* edge. Functions are coded in NEON assembly and can */ +///* be compiled using ARM RVDS. */ +///* */ +///* List of Functions : ih264_deblk_luma_vert_bs4_av8() */ +///* ih264_deblk_luma_vert_bslt4_av8() */ +///* ih264_deblk_luma_horz_bs4_av8() */ +///* ih264_deblk_luma_horz_bslt4_av8() */ +///* */ +///* Issues / Problems : None */ +///* */ +///* Revision History : */ +///* */ +///* DD MM YYYY Author(s) Changes (Describe the changes made) */ +///* 28 11 2013 Ittiam Draft */ +///* */ +///*****************************************************************************/ + + +.text +.p2align 2 +.include "ih264_neon_macros.s" + + + +///** +//******************************************************************************* +//* +//* @brief +//* Performs filtering of a luma block horizontal edge for cases where the +//* boundary strength is less than 4 +//* +//* @par Description: +//* This operation is described in Sec. 8.7.2.4 under the title +//* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. +//* +//* @param[in] x0 - pu1_src +//* Pointer to the src sample q0 +//* +//* @param[in] w1 - src_strd +//* Source stride +//* +//* @param[in] w2 - alpha +//* Alpha Value for the boundary +//* +//* @param[in] w3 - beta +//* Beta Value for the boundary +//* +//* @param[in] w4 - u4_bs +//* Packed Boundary strength array +//* +//* @param[in] x5 - pu1_cliptab +//* tc0_table +//* +//* @returns +//* None +//* +//* @remarks +//* None +//* +//******************************************************************************* +//*/ + + .global ih264_deblk_luma_horz_bslt4_av8 + +ih264_deblk_luma_horz_bslt4_av8: + + // STMFD sp!,{x4-x7,x14} + push_v_regs + sxtw x1, w1 + stp x19, x20, [sp, #-16]! + + //LDRD x4,x5,[SP,#0x14] //x4 = ui_Bs , x5 = *puc_ClpTab + sub x0, x0, x1, lsl #1 //x1 = uc_Horizonpad + sub x0, x0, x1 //x0 pointer to p2 + rev w4, w4 // + ld1 {v10.8b, v11.8b}, [x0], x1 //p2 values are loaded into q5 + mov v12.s[0], w4 //d12[0] = ui_Bs + mov x6, x0 //keeping backup of pointer to p1 + ld1 {v8.8b, v9.8b}, [x0], x1 //p1 values are loaded into q4 + mov x7, x0 //keeping backup of pointer to p0 + ld1 {v6.8b, v7.8b}, [x0], x1 //p0 values are loaded into q3 + uxtl v12.8h, v12.8b //q6 = uc_Bs in each 16 bt scalar + ld1 {v0.8b, v1.8b}, [x0], x1 //q0 values are loaded into q0 + mov v10.d[1], v11.d[0] + mov v8.d[1], v9.d[0] + mov v6.d[1], v7.d[0] + uabd v26.16b, v8.16b, v6.16b + ld1 {v2.8b, v3.8b}, [x0], x1 //q1 values are loaded into q1 + mov v0.d[1], v1.d[0] + mov v2.d[1], v3.d[0] + uabd v22.16b, v6.16b, v0.16b + ld1 {v16.s}[0], [x5] //D16[0] contains cliptab + uabd v24.16b, v2.16b, v0.16b + ld1 {v4.8b, v5.8b}, [x0], x1 //q2 values are loaded into q2 + tbl v14.8b, {v16.16b}, v12.8b // + mov v4.d[1], v5.d[0] + dup v20.16b, w2 //Q10 contains alpha + dup v16.16b, w3 //Q8 contains beta + uxtl v12.4s, v12.4h // + uxtl v14.4s, v14.4h // + uabd v28.16b, v10.16b, v6.16b + uabd v30.16b, v4.16b, v0.16b + cmgt v12.4s, v12.4s, #0 + sli v14.4s, v14.4s, #8 + cmhs v18.16b, v22.16b, v20.16b + cmhs v24.16b, v24.16b, v16.16b + cmhs v26.16b, v26.16b, v16.16b + cmhi v20.16b, v16.16b , v28.16b //Q10=(Ap= Alpha ) | ( ABS(q1 - q0) >= Beta ) + usubl v30.8h, v1.8b, v7.8b // + usubl v24.8h, v0.8b, v6.8b //Q15,Q12 = (q0 - p0) + orr v18.16b, v18.16b , v26.16b //Q9 = ( ABS(p0 - q0) >= Alpha ) | ( ABS(q1 - q0) >= Beta ) | ( ABS(p1 - p0) >= Beta ) + usubl v28.8h, v8.8b, v2.8b //Q14 = (p1 - q1)L + shl v26.8h, v30.8h, #2 //Q13 = (q0 - p0)<<2 + shl v24.8h, v24.8h, #2 //Q12 = (q0 - p0)<<2 + usubl v30.8h, v9.8b, v3.8b //Q15 = (p1 - q1)H + bic v12.16b, v12.16b , v18.16b //final condition + add v24.8h, v24.8h , v28.8h // + add v26.8h, v26.8h , v30.8h //Q13,Q12 = [ (q0 - p0)<<2 ] + (p1 - q1) + sub v18.16b, v14.16b , v20.16b //Q9 = C0 + (Ap < Beta) + urhadd v16.16b, v6.16b , v0.16b //Q8 = ((p0+q0+1) >> 1) + mov v17.d[0], v16.d[1] + sqrshrn v24.8b, v24.8h, #3 // + sqrshrn v25.8b, v26.8h, #3 //Q12 = i_macro = (((q0 - p0)<<2) + (p1 - q1) + 4)>>3 + mov v24.d[1], v25.d[0] + sub v18.16b, v18.16b , v22.16b //Q9 = C0 + (Ap < Beta) + (Aq < Beta) + and v20.16b, v20.16b , v12.16b // + and v22.16b, v22.16b , v12.16b // + abs v26.16b, v24.16b //Q13 = ABS (i_macro) + uaddl v28.8h, v17.8b, v11.8b // + uaddl v10.8h, v16.8b, v10.8b //Q14,Q5 = p2 + (p0+q0+1)>>1 + uaddl v30.8h, v17.8b, v5.8b // + umin v18.16b, v26.16b , v18.16b //Q9 = delta = (ABS(i_macro) > C) ? C : ABS(i_macro) + ushll v26.8h, v9.8b, #1 // + uaddl v4.8h, v16.8b, v4.8b //Q15,Q2 = q2 + (p0+q0+1)>>1 + ushll v16.8h, v8.8b, #1 //Q13,Q8 = (p1<<1) + and v18.16b, v18.16b , v12.16b //Making delta zero in places where values shouldn be filterd + sub v28.8h, v28.8h , v26.8h //Q14,Q5 = [p2 + (p0+q0+1)>>1] - (p1<<1) + sub v10.8h, v10.8h , v16.8h // + ushll v16.8h, v2.8b, #1 // + ushll v26.8h, v3.8b, #1 //Q13,Q8 = (q1<<1) + sqshrn v29.8b, v28.8h, #1 // + sqshrn v28.8b, v10.8h, #1 //Q14 = i_macro_p1 + mov v28.d[1], v29.d[0] + sub v4.8h, v4.8h , v16.8h // + sub v30.8h, v30.8h , v26.8h //Q15,Q2 = [q2 + (p0+q0+1)>>1] - (q1<<1) + neg v26.16b, v14.16b //Q13 = -C0 + smin v28.16b, v28.16b , v14.16b //Q14 = min(C0,i_macro_p1) + cmge v24.16b, v24.16b, #0 + sqshrn v31.8b, v30.8h, #1 // + sqshrn v30.8b, v4.8h, #1 //Q15 = i_macro_q1 + mov v30.d[1], v31.d[0] + smax v28.16b, v28.16b , v26.16b //Q14 = max( - C0 , min(C0, i_macro_p1) ) + uqadd v16.16b, v6.16b , v18.16b //Q8 = p0 + delta + uqsub v6.16b, v6.16b , v18.16b //Q3 = p0 - delta + smin v30.16b, v30.16b , v14.16b //Q15 = min(C0,i_macro_q1) + and v28.16b, v20.16b , v28.16b //condition check Ap= 0 ) ? (p0+delta) : (p0-delta) + bif v0.16b, v14.16b , v24.16b //Q0 = (i_macro >= 0 ) ? (q0-delta) : (q0+delta) + add v28.16b, v28.16b , v8.16b // + and v30.16b, v22.16b , v30.16b //condition check Aq= Alpha + cmhs v14.16b, v14.16b , v2.16b //ABS(q1 - q0) >= Beta + cmhs v16.16b, v16.16b , v2.16b //ABS(q1 - q0) >= Beta + movi v20.16b, #2 + orr v18.16b, v18.16b , v14.16b //ABS(p0 - q0) >= Alpha || ABS(q1 - q0) >= Beta + ld1 {v14.8b, v15.8b}, [x0], x1 //load q2 to Q7, q0 = q0 + src_strd + mov v14.d[1] , v15.d[0] + orr v18.16b, v18.16b , v16.16b //ABS(p0 - q0) >= Alpha || ABS(q1 - q0) >= Beta || ABS(p1 - p0) >= Beta + usra v20.16b, v0.16b, #2 //alpha >>2 +2 + uabd v22.16b , v14.16b, v4.16b + uaddl v24.8h, v4.8b, v6.8b //p0+q0 L + uaddl v26.8h, v5.8b, v7.8b //p0+q0 H + cmhi v22.16b, v2.16b , v22.16b //Aq < Beta + cmhi v20.16b, v20.16b , v12.16b //(ABS(p0 - q0) <((Alpha >>2) + 2)) + // Deblock Filtering q0', q1', q2' + uaddw v28.8h, v24.8h , v8.8b //p0+q0+q1 L + uaddw v30.8h, v26.8h , v9.8b //p0+q0+q1 H + and v22.16b, v22.16b , v20.16b //(Aq < Beta && ABS(p0 - q0) <((Alpha >>2) + 2)) + // q0' if (Aq < Beta && ABS(p0 - q0) <((Alpha >>2) + 2)) TRUE + add v16.8h, v28.8h , v28.8h //2*(p0+q0+q1)L + add v0.8h, v30.8h , v30.8h //2*(p0+q0+q1)H + uaddw v16.8h, v16.8h , v14.8b //2*(p0+q0+q1)+q2 L + uaddw v0.8h, v0.8h , v15.8b //2*(p0+q0+q1)+q2 H + uaddw v16.8h, v16.8h , v10.8b //2*(p0+q0+q1)+q2 +p1 L + uaddw v0.8h, v0.8h , v11.8b //2*(p0+q0+q1)+q2 +p1 H + rshrn v12.8b, v16.8h, #3 //(2*(p0+q0+q1)+q2 +p1 +4)>> 3 L [q0'] + rshrn v13.8b, v0.8h, #3 //(2*(p0+q0+q1)+q2 +p1 +4)>> 3 H [q0'] + mov v12.d[1] , v13.d[0] + // q0" if (Aq < Beta && ABS(p0 - q0) <((Alpha >>2) + 2)) FALSE + uaddl v16.8h, v8.8b, v8.8b //2*q1 L + uaddl v0.8h, v9.8b, v9.8b //2*q1 H + uaddw v16.8h, v16.8h , v4.8b //2*q1+q0 L + uaddw v0.8h, v0.8h , v5.8b //2*q1+q0 H + uaddw v16.8h, v16.8h , v10.8b //2*q1+q0+p1 L + uaddw v0.8h, v0.8h , v11.8b //2*q1+q0+p1 H + rshrn v16.8b, v16.8h, #2 //(2*q1+q0+p1+2)>>2 L [q0"] + rshrn v17.8b, v0.8h, #2 //(2*q1+q0+p1+2)>>2 H [q0"] + mov v16.d[1] , v17.d[0] + uaddw v28.8h, v28.8h , v14.8b //p0+q0+q1+q2 L + uaddw v30.8h, v30.8h , v15.8b //p0+q0+q1+q2 H + ld1 {v0.8b, v1.8b}, [x0], x1 //load q3 to Q0, q0 = q0 + src_strd + mov v0.d[1] , v1.d[0] + bit v16.16b, v12.16b , v22.16b //choosing between q0' and q0" depending on condn + sub x0, x0, x1, lsl #2 //pointer to q0 + bic v22.16b, v22.16b , v18.16b //((ABS(p0 - q0) >= Alpha || ABS(q1 - q0) >= Beta || ABS(p1 - p0) >= Beta)) + // && (Aq < Beta && ABS(p0 - q0) <((Alpha >>2) + 2)) + rshrn v12.8b, v28.8h, #2 //(p0+q0+q1+q2+2)>>2 L [q1'] + rshrn v13.8b, v30.8h, #2 //(p0+q0+q1+q2+2)>>2 H [q1'] + mov v12.d[1] , v13.d[0] + bif v4.16b, v16.16b , v18.16b //choose q0 or filtered q0 + mov v5.d[0] , v4.d[1] + uaddl v16.8h, v14.8b, v0.8b //q2+q3,L + uaddl v0.8h, v15.8b, v1.8b //q2+q3,H + add v28.8h, v28.8h , v16.8h //p0+q0+q1+2*q2+q3 L + st1 {v4.8b, v5.8b}, [x0], x1 //store q0 + add v30.8h, v30.8h , v0.8h //p0+q0+q1+2*q2+q3 H + add v28.8h, v28.8h , v16.8h //p0+q0+q1+3*q2+2*q3 L + add v30.8h, v30.8h , v0.8h //p0+q0+q1+3*q2+2*q3 H + rshrn v0.8b, v28.8h, #3 //(p0+q0+q1+3*q2+2*q3+4)>>3 L [q2'] + rshrn v1.8b, v30.8h, #3 //(p0+q0+q1+3*q2+2*q3+4)>>3 H [q2'] + mov v0.d[1] , v1.d[0] + ld1 {v30.8b, v31.8b}, [x3] //load p2 to Q15 + mov v30.d[1] , v31.d[0] + bif v12.16b, v8.16b , v22.16b //choose q1 or filtered value of q1 + mov v13.d[0] , v12.d[1] + uabd v16.16b , v30.16b, v6.16b + uaddw v24.8h, v24.8h , v10.8b //p0+q0+p1 L + bif v0.16b, v14.16b , v22.16b //choose q2 or filtered q2 + mov v1.d[0] , v0.d[1] + uaddw v26.8h, v26.8h , v11.8b //p0+q0+p1 H + st1 {v12.8b, v13.8b}, [x0], x1 //store q1 + cmhi v16.16b, v2.16b , v16.16b //Ap < Beta + add v28.8h, v24.8h , v24.8h //2*(p0+q0+p1) L + add v4.8h, v26.8h , v26.8h //2*(p0+q0+p1) H + st1 {v0.8b, v1.8b}, [x0], x1 //store q2 + and v20.16b, v20.16b , v16.16b //((Ap < Beta) && (ABS(p0 - q0) <((Alpha >>2) + 2))) + uaddw v28.8h, v28.8h , v30.8b //2*(p0+q0+p1)+p2 l + uaddw v4.8h, v4.8h , v31.8b //2*(p0+q0+p1)+p2 H + uaddw v28.8h, v28.8h , v8.8b //2*(p0+q0+p1)+p2+q1 L + uaddw v4.8h, v4.8h , v9.8b //2*(p0+q0+p1)+p2+q1 H + rshrn v28.8b, v28.8h, #3 //(2*(p0+q0+p1)+p2+q1+4)>>3 L,p0' + rshrn v29.8b, v4.8h, #3 //(2*(p0+q0+p1)+p2+q1+4)>>3 H,p0' + mov v28.d[1] , v29.d[0] + movi v0.8b, #2 + movi v1.4h, #2 + uaddl v2.8h, v6.8b, v8.8b //p0+q1 L + umlal v2.8h, v10.8b, v0.8b //2*p1+p0+q1 L + uaddl v16.8h, v7.8b, v9.8b //p0+q1 H + umlal v16.8h, v11.8b, v0.8b //2*p1+p0+q1 H + uaddw v12.8h, v24.8h , v30.8b //(p0+q0+p1) +p2 L + ld1 {v24.8b, v25.8b}, [x2] //load p3,Q12 + mov v24.d[1] , v25.d[0] + uaddw v4.8h, v26.8h , v31.8b //(p0+q0+p1) +p2 H + uaddl v8.8h, v30.8b, v24.8b //p2+p3 L + rshrn v26.8b, v12.8h, #2 //((p0+q0+p1)+p2 +2)>>2,p1' L + rshrn v2.8b, v2.8h, #2 //(2*p1+p0+q1+2)>>2,p0"L + rshrn v27.8b, v4.8h, #2 //((p0+q0+p1)+p2 +2)>>2,p1' H + rshrn v3.8b, v16.8h, #2 //(2*p1+p0+q1+2)>>2,p0" H + mov v26.d[1] , v27.d[0] + mov v2.d[1] , v3.d[0] + uaddl v16.8h, v31.8b, v25.8b //p2+p3 H + mla v12.8h, v8.8h , v1.h[0] //(p0+q0+p1)+3*p2+2*p3 L + mla v4.8h, v16.8h , v1.h[0] //(p0+q0+p1)+3*p2+2*p3 H + bic v16.16b, v20.16b , v18.16b //((ABS(p0 - q0) >= Alpha || ABS(q1 - q0) >= Beta || ABS(p1 - p0) >= Beta)) + mov v17.d[0] , v16.d[1] //&& (Ap < Beta && ABS(p0 - q0) <((Alpha >>2) + 2)) + bit v2.16b, v28.16b , v20.16b //choosing between po' and p0" + mov v3.d[0] , v2.d[1] + rshrn v12.8b, v12.8h, #3 //((p0+q0+p1)+3*p2+2*p3+4)>>3 L p2' + rshrn v13.8b, v4.8h, #3 //((p0+q0+p1)+3*p2+2*p3+4)>>3 H p2' + mov v12.d[1] , v13.d[0] + bif v6.16b, v2.16b , v18.16b //choosing between p0 and filtered value of p0 + bit v10.16b, v26.16b , v16.16b //choosing between p1 and p1' + bit v30.16b, v12.16b , v16.16b //choosing between p2 and p2' + st1 {v6.16b}, [x12] //store p0 + st1 {v10.16b}, [x14] //store p1 + st1 {v30.16b}, [x3] //store p2 + + // LDMFD sp!,{x12,pc} + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + + +///** +//******************************************************************************* +//* +//* @brief +//* Performs filtering of a luma block vertical edge for cases where the +//* boundary strength is less than 4 +//* +//* @par Description: +//* This operation is described in Sec. 8.7.2.4 under the title +//* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. +//* +//* @param[in] x0 - pu1_src +//* Pointer to the src sample q0 +//* +//* @param[in] w1 - src_strd +//* Source stride +//* +//* @param[in] w2 - alpha +//* Alpha Value for the boundary +//* +//* @param[in] w3 - beta +//* Beta Value for the boundary +//* +//* @param[in] w4 - u4_bs +//* Packed Boundary strength array +//* +//* @param[in] x5 - pu1_cliptab +//* tc0_table +//* +//* @returns +//* None +//* +//* @remarks +//* None +//* +//******************************************************************************* +//*/ + + .global ih264_deblk_luma_vert_bslt4_av8 + +ih264_deblk_luma_vert_bslt4_av8: + + // STMFD sp!,{x12,x14} + push_v_regs + stp x19, x20, [sp, #-16]! + sxtw x1, w1 + + sub x0, x0, #4 //pointer uc_edgePixel-4 + mov x12, x4 + mov x14, x5 + mov x17, x0 + //loading p3:p2:p1:p0:q0:q1:q2:q3 for every row + ld1 {v0.8b}, [x0], x1 //row1 + ld1 {v2.8b}, [x0], x1 //row2 + ld1 {v4.8b}, [x0], x1 //row3 + rev w12, w12 //reversing ui_bs + ld1 {v6.8b}, [x0], x1 //row4 + mov v18.s[0], w12 //d12[0] = ui_Bs + ld1 {v16.s}[0], [x14] //D16[0] contains cliptab + ld1 {v8.8b}, [x0], x1 //row5 + uxtl v18.8h, v18.8b //q6 = uc_Bs in each 16 bt scalar + ld1 {v10.8b}, [x0], x1 //row6 + ld1 {v12.8b}, [x0], x1 //row7 + tbl v16.8b, {v16.16b}, v18.8b //puc_ClipTab[uc_Bs] + ld1 {v14.8b}, [x0], x1 //row8 + ld1 {v1.8b}, [x0], x1 //row9 + uxtl v16.4s, v16.4h // + ld1 {v3.8b}, [x0], x1 //row10 + ld1 {v5.8b}, [x0], x1 //row11 + ld1 {v7.8b}, [x0], x1 //row12 + sli v16.4s, v16.4s, #8 // + ld1 {v9.8b}, [x0], x1 //row13 + ld1 {v11.8b}, [x0], x1 //row14 + ld1 {v13.8b}, [x0], x1 //row15 + sli v16.4s, v16.4s, #16 + ld1 {v15.8b}, [x0], x1 //row16 + + + //taking two 8x8 transposes + //2X2 transposes + trn1 v21.8b, v0.8b, v2.8b + trn2 v2.8b, v0.8b, v2.8b //row1 &2 + mov v0.8b, v21.8b + trn1 v21.8b, v4.8b, v6.8b + trn2 v6.8b, v4.8b, v6.8b //row3&row4 + mov v4.8b, v21.8b + trn1 v21.8b, v8.8b, v10.8b + trn2 v10.8b, v8.8b, v10.8b //row5&6 + mov v8.8b, v21.8b + trn1 v21.8b, v12.8b, v14.8b + trn2 v14.8b, v12.8b, v14.8b //row7 & 8 + mov v12.8b, v21.8b + trn1 v21.8b, v1.8b, v3.8b + trn2 v3.8b, v1.8b, v3.8b //row9 &10 + mov v1.8b, v21.8b + trn1 v21.8b, v5.8b, v7.8b + trn2 v7.8b, v5.8b, v7.8b //row11 & 12 + mov v5.8b, v21.8b + trn1 v21.8b, v9.8b, v11.8b + trn2 v11.8b, v9.8b, v11.8b //row13 &14 + mov v9.8b, v21.8b + trn1 v21.8b, v13.8b, v15.8b + trn2 v15.8b, v13.8b, v15.8b //row15 & 16 + mov v13.8b, v21.8b + //4x4 transposes + trn1 v21.4h, v2.4h, v6.4h + trn2 v6.4h, v2.4h, v6.4h //row2 & row4 + mov v2.8b, v21.8b + trn1 v21.4h, v10.4h, v14.4h + trn2 v14.4h, v10.4h, v14.4h //row6 & row8 + mov v10.8b, v21.8b + trn1 v21.4h, v3.4h, v7.4h + trn2 v7.4h, v3.4h, v7.4h //row10 & 12 + mov v3.8b, v21.8b + trn1 v21.4h, v11.4h, v15.4h + trn2 v15.4h, v11.4h, v15.4h //row14 & row16 + mov v11.8b, v21.8b + trn1 v21.2s, v6.2s, v14.2s + trn2 v14.2s, v6.2s, v14.2s //row4 & 8 + mov v6.8b, v21.8b + trn1 v21.2s, v7.2s, v15.2s + trn2 v15.2s, v7.2s, v15.2s //row 12 & 16 + mov v7.8b, v21.8b + //now Q3 ->p0 and Q7->q3 + trn1 v21.4h, v0.4h, v4.4h + trn2 v4.4h, v0.4h, v4.4h //row1 & 3 + mov v0.8b, v21.8b + trn1 v21.4h, v8.4h, v12.4h + trn2 v12.4h, v8.4h, v12.4h //row 5 & 7 + mov v8.8b, v21.8b + trn1 v21.4h, v1.4h, v5.4h + trn2 v5.4h, v1.4h, v5.4h //row9 & row11 + mov v1.8b, v21.8b + trn1 v21.4h, v9.4h, v13.4h + trn2 v13.4h, v9.4h, v13.4h //row13 & row15 + mov v9.8b, v21.8b + trn1 v21.2s, v0.2s, v8.2s + trn2 v8.2s, v0.2s, v8.2s //row1 & row5 + mov v0.8b, v21.8b + trn1 v21.2s, v1.2s, v9.2s + trn2 v9.2s, v1.2s, v9.2s //row9 & 13 + mov v1.8b, v21.8b + //now Q0->p3 & Q4->q0 + //starting processing as p0 and q0 are now ready + trn1 v21.2s, v2.2s, v10.2s + trn2 v10.2s, v2.2s, v10.2s //row2 &6 + mov v2.8b, v21.8b + mov v6.d[1] , v7.d[0] + mov v8.d[1] , v9.d[0] + urhadd v20.16b, v6.16b , v8.16b //((p0 + q0 + 1) >> 1) + mov v21.d[0], v20.d[1] + trn1 v31.2s, v3.2s, v11.2s + trn2 v11.2s, v3.2s, v11.2s //row10&row14 + mov v3.8b, v31.8b + movi v19.8b, #2 + mov v18.d[1], v19.d[0] + //now Q1->p2 & Q5->q1 + trn1 v31.2s, v4.2s, v12.2s + trn2 v12.2s, v4.2s, v12.2s //row3 & 7 + mov v4.8b, v31.8b + uabd v22.16b , v6.16b, v8.16b //ABS(q1 - q0) + trn1 v31.2s, v5.2s, v13.2s + trn2 v13.2s, v5.2s, v13.2s //row11 & row15 + mov v5.8b, v31.8b + mov v0.d[1] , v1.d[0] + mov v2.d[1] , v3.d[0] + mov v4.d[1] , v5.d[0] + mov v10.d[1] , v11.d[0] + mov v12.d[1] , v13.d[0] + mov v14.d[1] , v15.d[0] + uaddl v24.8h, v20.8b, v2.8b //(p2 + ((p0 + q0 + 1) >> 1) L + //now Q2->p1,Q6->q2 + uaddl v26.8h, v21.8b, v3.8b //(p2 + ((p0 + q0 + 1) >> 1) H + umlsl v24.8h, v4.8b, v19.8b //(p2 + ((p0 + q0 + 1) >> 1) - (p1 << 1)) L + umlsl v26.8h, v5.8b, v19.8b //(p2 + ((p0 + q0 + 1) >> 1) - (p1 << 1)) H + dup v28.16b, w2 //alpha + cmhs v22.16b, v22.16b , v28.16b //ABS(p0 - q0) >= Alpha(Alpha <=ABS(p0 - q0)) + dup v28.16b, w3 //beta + uabd v30.16b , v10.16b, v8.16b //ABS(q1 - q0) + sqshrn v24.8b, v24.8h, #1 //((p2 + ((p0 + q0 + 1) >> 1) - (p1 << 1)) >> 1) L + sqshrn v25.8b, v26.8h, #1 //((p2 + ((p0 + q0 + 1) >> 1) - (p1 << 1)) >> 1) H + mov v24.d[1], v25.d[0] + cmhs v30.16b, v30.16b , v28.16b //ABS(p0 - q0) >= Alpha(Alpha <=ABS(p0 - q0)) + uabd v26.16b , v4.16b, v6.16b //ABS(q1 - q0) + + smin v24.16b, v24.16b , v16.16b //min(deltap1 ,C0) + orr v22.16b, v22.16b , v30.16b //ABS(q1 - q0) >= Beta ||ABS(p0 - q0) >= Alpha + neg v30.16b, v16.16b //-C0 + cmhs v26.16b, v26.16b , v28.16b //ABS(p0 - q0) >= Alpha(Alpha <=ABS(p0 - q0)) + smax v24.16b, v24.16b , v30.16b //max(deltap1,-C0) + orr v22.16b, v22.16b , v26.16b //ABS(p0 - q0) >= Alpha || ABS(q1 - q0) >= Beta || ABS(p1 - p0) >= Beta) + uxtl v26.4s, v18.4h //ui_bs + uaddl v18.8h, v20.8b, v12.8b //q2 + ((p0 + q0 + 1) >> 1) L + cmeq v26.4s, v26.4s , #0 //ABS(p0 - q0) >= Alpha(Alpha <=ABS(p0 - q0)) + usubw v18.8h, v18.8h , v10.8b //(q2 + ((p0 + q0 + 1) >> 1) - q1) L + uaddl v20.8h, v21.8b, v13.8b //q2 + ((p0 + q0 + 1) >> 1) H + usubw v18.8h, v18.8h , v10.8b //(q2 + ((p0 + q0 + 1) >> 1) - 2*q1)L + usubw v20.8h, v20.8h , v11.8b //(q2 + ((p0 + q0 + 1) >> 1) - q1) H + orr v26.16b, v26.16b , v22.16b //(ABS(p0 - q0) >= Alpha || ABS(q1 - q0) >= Beta || ABS(p1 - p0) >= Beta)) &&(ui_bs) + usubw v20.8h, v20.8h , v11.8b //(q2 + ((p0 + q0 + 1) >> 1) - 2*q1) H + sqshrn v18.8b, v18.8h, #1 //((q2 + ((p0 + q0 + 1) >> 1) - (q1 << 1)) >> 1) L + uabd v22.16b , v2.16b, v6.16b //ABS(q1 - q0) + sqshrn v19.8b, v20.8h, #1 //((q2 + ((p0 + q0 + 1) >> 1) - (q1 << 1)) >> 1) H + mov v18.d[1], v19.d[0] + uabd v20.16b , v12.16b, v8.16b //ABS(q1 - q0) + cmhi v22.16b, v28.16b , v22.16b //Ap < Beta + smin v18.16b, v18.16b , v16.16b //min(delatq1,C0) + cmhi v20.16b, v28.16b , v20.16b //Aq > 3); L + rshrn v29.8b, v30.8h, #3 //delta = ((((q0 - p0) << 2) + (p1 - q1) + 4) >> 3) H + mov v28.d[1], v29.d[0] + sub v16.16b, v16.16b , v20.16b //C0 + (Ap < Beta) + (Aq < Beta) + bic v20.16b, v20.16b , v26.16b //final condition for q1 + abs v30.16b, v28.16b //abs(delta) + and v24.16b, v24.16b , v22.16b //delatp1 + and v18.16b, v18.16b , v20.16b //delta q1 + umin v30.16b, v30.16b , v16.16b //min((abs(delta),C) + add v4.16b, v4.16b , v24.16b //p1+deltap1 + add v10.16b, v10.16b , v18.16b //q1+deltaq1 + mov v5.d[0], v4.d[1] + mov v11.d[0], v10.d[1] + bic v30.16b, v30.16b , v26.16b //abs(delta) of pixels to be changed only + // VCGE.S8 Q14, Q14,#0 //sign(delta) + cmge v28.16b, v28.16b , #0 + uqsub v22.16b, v6.16b , v30.16b //clip(p0-delta) + + trn1 v21.8b, v0.8b, v2.8b + trn2 v2.8b, v0.8b, v2.8b //row1 &2 + mov v0.8b, v21.8b + uqadd v6.16b, v6.16b , v30.16b //clip(p0+delta) + + trn1 v21.8b, v1.8b, v3.8b + trn2 v3.8b, v1.8b, v3.8b //row9 &10 + mov v1.8b, v21.8b + uqadd v24.16b, v8.16b , v30.16b //clip(q0+delta) + trn1 v21.8b, v12.8b, v14.8b + trn2 v14.8b, v12.8b, v14.8b //row7 & 8 + mov v12.8b, v21.8b + uqsub v8.16b, v8.16b , v30.16b //clip(q0-delta) + trn1 v21.8b, v13.8b, v15.8b + trn2 v15.8b, v13.8b, v15.8b //row15 & 16 + mov v13.8b, v21.8b + bif v6.16b, v22.16b , v28.16b //p0 + bif v8.16b, v24.16b , v28.16b //q0 + mov v7.d[0], v6.d[1] + mov v9.d[0], v8.d[1] + trn1 v21.8b, v4.8b, v6.8b + trn2 v6.8b, v4.8b, v6.8b //row3&row4 + mov v4.8b, v21.8b + trn1 v21.8b, v8.8b, v10.8b + trn2 v10.8b, v8.8b, v10.8b //row5&6 + mov v8.8b, v21.8b + trn1 v21.8b, v5.8b, v7.8b + trn2 v7.8b, v5.8b, v7.8b //row11 & 12 + mov v5.8b, v21.8b + trn1 v21.8b, v9.8b, v11.8b + trn2 v11.8b, v9.8b, v11.8b //row13 &14 + mov v9.8b, v21.8b + trn1 v21.4h, v2.4h, v6.4h + trn2 v6.4h, v2.4h, v6.4h //row2 & row4 + mov v2.8b, v21.8b + trn1 v21.4h, v10.4h, v14.4h + trn2 v14.4h, v10.4h, v14.4h //row6 & row8 + mov v10.8b, v21.8b + trn1 v21.4h, v3.4h, v7.4h + trn2 v7.4h, v3.4h, v7.4h //row10 & 12 + mov v3.8b, v21.8b + trn1 v21.4h, v11.4h, v15.4h + trn2 v15.4h, v11.4h, v15.4h //row14 & row16 + mov v11.8b, v21.8b + trn1 v21.2s, v6.2s, v14.2s + trn2 v14.2s, v6.2s, v14.2s //row4 & 8 + mov v6.8b, v21.8b + trn1 v21.2s, v7.2s, v15.2s + trn2 v15.2s, v7.2s, v15.2s //row 12 & 16 + mov v7.8b, v21.8b + //now Q3 ->p0 and Q7->q3 + trn1 v21.4h, v0.4h, v4.4h + trn2 v4.4h, v0.4h, v4.4h //row1 & 3 + mov v0.8b, v21.8b + trn1 v21.4h, v8.4h, v12.4h + trn2 v12.4h, v8.4h, v12.4h //row 5 & 7 + mov v8.8b, v21.8b + trn1 v21.4h, v1.4h, v5.4h + trn2 v5.4h, v1.4h, v5.4h //row9 & row11 + mov v1.8b, v21.8b + trn1 v21.4h, v9.4h, v13.4h + trn2 v13.4h, v9.4h, v13.4h //row13 & row15 + mov v9.8b, v21.8b + sub x0, x0, x1, lsl#4 //restore pointer + trn1 v21.2s, v0.2s, v8.2s + trn2 v8.2s, v0.2s, v8.2s //row1 & row5 + mov v0.8b, v21.8b + trn1 v21.2s, v1.2s, v9.2s + trn2 v9.2s, v1.2s, v9.2s //row9 & 13 + mov v1.8b, v21.8b + trn1 v21.2s, v2.2s, v10.2s + trn2 v10.2s, v2.2s, v10.2s //row2 &6 + mov v2.8b, v21.8b + trn1 v21.2s, v3.2s, v11.2s + trn2 v11.2s, v3.2s, v11.2s //row10&row14 + mov v3.8b, v21.8b + trn1 v21.2s, v4.2s, v12.2s + trn2 v12.2s, v4.2s, v12.2s //row3 & 7 + mov v4.8b, v21.8b + trn1 v21.2s, v5.2s, v13.2s + trn2 v13.2s, v5.2s, v13.2s //row11 & row15 + mov v5.8b, v21.8b + st1 {v0.8b}, [x0], x1 //row1 + st1 {v2.8b}, [x0], x1 //row2 + st1 {v4.8b}, [x0], x1 //row3 + st1 {v6.8b}, [x0], x1 //row4 + st1 {v8.8b}, [x0], x1 //row5 + st1 {v10.8b}, [x0], x1 //row6 + st1 {v12.8b}, [x0], x1 //row7 + st1 {v14.8b}, [x0], x1 //row8 + st1 {v1.8b}, [x0], x1 //row9 + st1 {v3.8b}, [x0], x1 //row10 + st1 {v5.8b}, [x0], x1 //row11 + st1 {v7.8b}, [x0], x1 //row12 + st1 {v9.8b}, [x0], x1 //row13 + st1 {v11.8b}, [x0], x1 //row14 + st1 {v13.8b}, [x0], x1 //row15 + st1 {v15.8b}, [x0], x1 //row16 + + // LDMFD sp!,{x12,pc} + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + + +///** +//******************************************************************************* +//* +//* @brief +//* Performs filtering of a luma block vertical edge when the +//* boundary strength is set to 4 +//* +//* @par Description: +//* This operation is described in Sec. 8.7.2.4 under the title +//* "Filtering process for edges for bS equal to 4" in ITU T Rec H.264. +//* +//* @param[in] x0 - pu1_src +//* Pointer to the src sample q0 +//* +//* @param[in] w1 - src_strd +//* Source stride +//* +//* @param[in] w2 - alpha +//* Alpha Value for the boundary +//* +//* @param[in] w3 - beta +//* Beta Value for the boundary +//* +//* @returns +//* None +//* +//* @remarks +//* None +//* +//******************************************************************************* +//*/ + + .global ih264_deblk_luma_vert_bs4_av8 + +ih264_deblk_luma_vert_bs4_av8: + + // STMFD sp!,{x12,x14} + push_v_regs + stp x19, x20, [sp, #-16]! + + sub x0, x0, #4 //pointer uc_edgePixel-4 + mov x17, x0 + //loading p3:p2:p1:p0:q0:q1:q2:q3 for every row + ld1 {v0.8b}, [x0], x1 //row1 + ld1 {v2.8b}, [x0], x1 //row2 + ld1 {v4.8b}, [x0], x1 //row3 + ld1 {v6.8b}, [x0], x1 //row4 + ld1 {v8.8b}, [x0], x1 //row5 + ld1 {v10.8b}, [x0], x1 //row6 + ld1 {v12.8b}, [x0], x1 //row7 + ld1 {v14.8b}, [x0], x1 //row8 + ld1 {v1.8b}, [x0], x1 //row9 + ld1 {v3.8b}, [x0], x1 //row10 + ld1 {v5.8b}, [x0], x1 //row11 + ld1 {v7.8b}, [x0], x1 //row12 + ld1 {v9.8b}, [x0], x1 //row13 + ld1 {v11.8b}, [x0], x1 //row14 + ld1 {v13.8b}, [x0], x1 //row15 + ld1 {v15.8b}, [x0], x1 //row16 + + //taking two 8x8 transposes + //2X2 transposes + trn1 v21.8b, v0.8b, v2.8b + trn2 v2.8b, v0.8b, v2.8b //row1 &2 + mov v0.8b, v21.8b + trn1 v21.8b, v4.8b, v6.8b + trn2 v6.8b, v4.8b, v6.8b //row3&row4 + mov v4.8b, v21.8b + trn1 v21.8b, v8.8b, v10.8b + trn2 v10.8b, v8.8b, v10.8b //row5&6 + mov v8.8b, v21.8b + trn1 v21.8b, v12.8b, v14.8b + trn2 v14.8b, v12.8b, v14.8b //row7 & 8 + mov v12.8b, v21.8b + trn1 v21.8b, v1.8b, v3.8b + trn2 v3.8b, v1.8b, v3.8b //row9 &10 + mov v1.8b , v21.8b + trn1 v21.8b, v5.8b, v7.8b + trn2 v7.8b, v5.8b, v7.8b //row11 & 12 + mov v5.8b , v21.8b + trn1 v21.8b, v9.8b, v11.8b + trn2 v11.8b, v9.8b, v11.8b //row13 &14 + mov v9.8b , v21.8b + trn1 v21.8b, v13.8b, v15.8b + trn2 v15.8b, v13.8b, v15.8b //row15 & 16 + mov v13.8b , v21.8b + //4x4 transposes + trn1 v21.4h, v2.4h, v6.4h + trn2 v6.4h, v2.4h, v6.4h //row2 & row4 + mov v2.8b, v21.8b + trn1 v21.4h, v10.4h, v14.4h + trn2 v14.4h, v10.4h, v14.4h //row6 & row8 + mov v10.8b , v21.8b + trn1 v21.4h, v3.4h, v7.4h + trn2 v7.4h, v3.4h, v7.4h //row10 & 12 + mov v3.8b, v21.8b + trn1 v21.4h, v11.4h, v15.4h + trn2 v15.4h, v11.4h, v15.4h //row14 & row16 + mov v11.8b, v21.8b + trn1 v21.2s, v6.2s, v14.2s + trn2 v14.2s, v6.2s, v14.2s //row4 & 8 + mov v6.8b, v21.8b + trn1 v21.2s, v7.2s, v15.2s + trn2 v15.2s, v7.2s, v15.2s //row 12 & 16 + mov v7.8b, v21.8b + //now Q3 ->p0 and Q7->q3 + trn1 v21.4h, v0.4h, v4.4h + trn2 v4.4h, v0.4h, v4.4h //row1 & 3 + mov v0.8b , v21.8b + trn1 v21.4h, v8.4h, v12.4h + trn2 v12.4h, v8.4h, v12.4h //row 5 & 7 + mov v8.8b, v21.8b + trn1 v21.4h, v1.4h, v5.4h + trn2 v5.4h, v1.4h, v5.4h //row9 & row11 + mov v1.8b, v21.8b + trn1 v21.4h, v9.4h, v13.4h + trn2 v13.4h, v9.4h, v13.4h //row13 & row15 + mov v9.8b , v21.8b + trn1 v21.2s, v0.2s, v8.2s + trn2 v8.2s, v0.2s, v8.2s //row1 & row5 + mov v0.8b, v21.8b + trn1 v21.2s, v1.2s, v9.2s + trn2 v9.2s, v1.2s, v9.2s //row9 & 13 + mov v1.8b, v21.8b + //now Q0->p3 & Q4->q0 + //starting processing as p0 and q0 are now ready + //now Q1->p2 & Q5->q1 + mov v31.d[0], v14.d[0] + mov v31.d[1], v15.d[0] + trn1 v21.2s, v4.2s, v12.2s + trn2 v12.2s, v4.2s, v12.2s //row3 & 7 + mov v4.8b, v21.8b + movi v28.8h, #2 + trn1 v21.2s, v5.2s, v13.2s + trn2 v13.2s, v5.2s, v13.2s //row11 & row15 + mov v5.8b, v21.8b + uaddl v16.8h, v6.8b, v8.8b //p0+q0 L + trn1 v21.2s, v2.2s, v10.2s + trn2 v10.2s, v2.2s, v10.2s //row2 &6 + mov v2.8b, v21.8b + uaddl v18.8h, v7.8b, v9.8b //p0+q0 H + trn1 v21.2s, v3.2s, v11.2s + trn2 v11.2s, v3.2s, v11.2s //row10&row14 + mov v3.8b, v21.8b + uaddw v20.8h, v16.8h , v4.8b //p0+q0+p1 L + uaddw v22.8h, v18.8h , v5.8b //p0+q0+p1 H + uaddl v24.8h, v2.8b, v10.8b //p2+q1 L + uaddl v26.8h, v3.8b, v11.8b //p2+q1 H + mla v24.8h, v20.8h , v28.8h //p2 + X2(p1) + X2(p0) + X2(q0) + q1 L + mla v26.8h, v22.8h , v28.8h //p2 + X2(p1) + X2(p0) + X2(q0) + q1 H + movi v28.16b, #2 + uaddw v16.8h, v20.8h , v2.8b //p0+q0+p1+p2 L + uaddw v18.8h, v22.8h , v3.8b //p0+q0+p1+p2 H + dup v30.16b, w2 //duplicate alpha + rshrn v20.8b, v16.8h, #2 //(p2 + p1 + p0 + q0 + 2) >> 2)L p1' + rshrn v21.8b, v18.8h, #2 //(p2 + p1 + p0 + q0 + 2) >> 2)H p1' + mov v20.d[1] , v21.d[0] + mov v0.d[1] , v1.d[0] + mov v2.d[1] , v3.d[0] + mov v4.d[1] , v5.d[0] + mov v6.d[1] , v7.d[0] + mov v8.d[1] , v9.d[0] + mov v10.d[1] , v11.d[0] + mov v12.d[1] , v13.d[0] + mov v14.d[1] , v15.d[0] + uabd v22.16b , v6.16b, v8.16b + usra v28.16b, v30.16b, #2 //alpha >>2 +2 + uabd v30.16b , v2.16b, v6.16b + rshrn v24.8b, v24.8h, #3 //((p2 + X2(p1) + X2(p0) + X2(q0) + q1 + 4) >> 3) L p0' + rshrn v25.8b, v26.8h, #3 //((p2 + X2(p1) + X2(p0) + X2(q0) + q1 + 4) >> 3) H p0' + mov v24.d[1] , v25.d[0] + dup v26.16b, w3 //beta + cmhi v28.16b, v28.16b , v22.16b //ABS(p0 - q0) <((Alpha >>2) + 2) + uaddl v22.8h, v6.8b, v10.8b //p0+q1 L + cmhi v14.16b, v26.16b , v30.16b //beta>Ap + uaddl v30.8h, v7.8b, v11.8b //p0+q1 H + uaddw v22.8h, v22.8h , v4.8b //p0+q1+p1 L + uaddw v30.8h, v30.8h , v5.8b //p0+q1+p1 H + uaddw v22.8h, v22.8h , v4.8b //p0+q1+2*p1 L + uaddw v30.8h, v30.8h , v5.8b //p0+q1+2*p1 H + and v14.16b, v14.16b , v28.16b //(Ap < Beta && ABS(p0 - q0) <((Alpha >>2) + 2) + rshrn v22.8b, v22.8h, #2 //((X2(p1) + p0 + q1 + 2) >> 2) L p0" + rshrn v23.8b, v30.8h, #2 //((X2(p1) + p0 + q1 + 2) >> 2) H p0" + mov v22.d[1] , v23.d[0] + uaddl v30.8h, v2.8b, v0.8b //p2+p3 L + bif v24.16b, v22.16b , v14.16b //p0' or p0 " + uaddl v22.8h, v3.8b, v1.8b //p2+p3 H + add v30.8h, v30.8h , v30.8h //2*(p2+p3) L + add v22.8h, v22.8h , v22.8h //2*(p2+p3)H + add v16.8h, v16.8h , v30.8h //(X2(p3) + X3(p2) + p1 + p0 + q0) L + add v18.8h, v18.8h , v22.8h //(X2(p3) + X3(p2) + p1 + p0 + q0) H + uabd v30.16b , v12.16b, v8.16b + uabd v22.16b , v10.16b, v8.16b + rshrn v16.8b, v16.8h, #3 //((X2(p3) + X3(p2) + p1 + p0 + q0 + 4) >> 3); L p2' + rshrn v17.8b, v18.8h, #3 //((X2(p3) + X3(p2) + p1 + p0 + q0 + 4) >> 3); H p2' + mov v16.d[1] , v17.d[0] + uabd v18.16b , v4.16b, v6.16b + cmhi v30.16b, v26.16b , v30.16b //Aq < Beta + cmhs v22.16b, v22.16b, v26.16b + cmhs v18.16b, v18.16b, v26.16b + dup v26.16b, w2 //duplicate alpha + and v30.16b, v30.16b , v28.16b //(Aq < Beta && ABS(p0 - q0) <((Alpha >>2) + 2)) + uabd v28.16b , v6.16b, v8.16b + orr v22.16b, v22.16b , v18.16b //ABS(p1 - p0) >= Beta || ABS(q1 - q0) >= Beta + uaddl v18.8h, v6.8b, v8.8b //p0+q0 L + cmhs v28.16b, v28.16b, v26.16b + uaddl v26.8h, v7.8b, v9.8b //p0+q0 H + uaddw v18.8h, v18.8h , v10.8b //p0+q0+q1 L + orr v22.16b, v22.16b , v28.16b //ABS(p1 - p0) >= Beta || ABS(q1 - q0) >= Beta||ABS(p0 - q0) >= Alpha + uaddw v26.8h, v26.8h , v11.8b //p0+q0+q1 H + bic v14.16b, v14.16b , v22.16b //final condn for p's + movi v28.16b, #2 + bif v6.16b, v24.16b , v22.16b //final p0 + bit v2.16b, v16.16b , v14.16b //final p2 + bif v20.16b, v4.16b , v14.16b //final p1 + mov v7.d[0] , v6.d[1] + mov v3.d[0] , v2.d[1] + mov v21.d[0] , v20.d[1] + uaddl v24.8h, v8.8b, v4.8b //q0+p1 L + umlal v24.8h, v10.8b, v28.8b //X2(q1) + q0 + p1 L + uaddl v16.8h, v9.8b, v5.8b //q0+p1 H + umlal v16.8h, v11.8b, v28.8b //X2(q1) + q0 + p1 H + movi v28.8h, #2 + uaddl v14.8h, v4.8b, v12.8b //p1+q2 L + mla v14.8h, v18.8h , v28.8h //p1 + X2(p0) + X2(q0) + X2(q1) + q2L + uaddl v4.8h, v5.8b, v13.8b //p1+q2H + mla v4.8h, v26.8h , v28.8h //p1 + X2(p0) + X2(q0) + X2(q1) + q2H + rshrn v24.8b, v24.8h, #2 //(X2(q1) + q0 + p1 + 2) >> 2; L q0' + rshrn v25.8b, v16.8h, #2 //(X2(q1) + q0 + p1 + 2) >> 2; H q0' + mov v24.d[1] , v25.d[0] + uaddw v18.8h, v18.8h , v12.8b //p0 + q0 + q1 + q2 L + uaddw v26.8h, v26.8h , v13.8b //p0 + q0 + q1 + q2 H + rshrn v16.8b, v14.8h, #3 //(p1 + X2(p0) + X2(q0) + X2(q1) + q2 + 4) >> 3 L qo" + mov v14.16b, v31.16b + rshrn v17.8b, v4.8h, #3 //(p1 + X2(p0) + X2(q0) + X2(q1) + q2 + 4) >> 3 H qo" + mov v16.d[1] , v17.d[0] + rshrn v4.8b, v18.8h, #2 //p0 + q0 + q1 + q2 + 2)>>2 L q1' + rshrn v5.8b, v26.8h, #2 //p0 + q0 + q1 + q2 + 2)>>2 H q1' + mov v4.d[1] , v5.d[0] + bit v24.16b, v16.16b , v30.16b //q0' or q0" + bic v30.16b, v30.16b , v22.16b //final condn for q's + trn1 v31.8b, v0.8b, v2.8b + trn2 v2.8b, v0.8b, v2.8b //row1 &2 + mov v0.8b, v31.8b + bit v10.16b, v4.16b , v30.16b + mov v11.d[0] , v10.d[1] + mov v25.d[0] , v24.d[1] + mov v31.d[0] , v30.d[1] + trn1 v31.8b, v1.8b, v3.8b + trn2 v3.8b, v1.8b, v3.8b //row9 &10 + mov v1.8b, v31.8b + uaddl v16.8h, v12.8b, v14.8b //q2+q3 L + trn1 v31.8b, v20.8b, v6.8b + trn2 v6.8b, v20.8b, v6.8b //row3&row4 + mov v20.8b , v31.8b + uaddl v4.8h, v13.8b, v15.8b //q2+q3 H + trn1 v31.8b, v21.8b, v7.8b + trn2 v7.8b, v21.8b, v7.8b //row11 & 12 + mov v21.8b , v31.8b + mla v18.8h, v16.8h , v28.8h //X2(q3) + X3(q2) + q1 + q0 + p0 L + trn1 v31.4h, v2.4h, v6.4h + trn2 v6.4h, v2.4h, v6.4h //row2 & row4 + mov v2.8b, v31.8b + mla v26.8h, v4.8h , v28.8h //X2(q3) + X3(q2) + q1 + q0 + p0 H + trn1 v31.4h, v3.4h, v7.4h + trn2 v7.4h, v3.4h, v7.4h //row10 & 12 + mov v3.8b , v31.8b + bif v8.16b, v24.16b , v22.16b //final q0 + mov v9.d[0] , v8.d[1] + trn1 v31.4h, v0.4h, v20.4h + trn2 v20.4h, v0.4h, v20.4h //row1 & 3 + mov v0.8b , v31.8b + rshrn v18.8b, v18.8h, #3 //(X2(q3) + X3(q2) + q1 + q0 + p0 + 4) >> 3; L + trn1 v31.4h, v1.4h, v21.4h + trn2 v21.4h, v1.4h, v21.4h //row9 & row11 + mov v1.8b, v31.8b + rshrn v19.8b, v26.8h, #3 //(X2(q3) + X3(q2) + q1 + q0 + p0 + 4) >> 3; H + mov v18.d[1] , v19.d[0] + trn1 v31.8b, v8.8b, v10.8b + trn2 v10.8b, v8.8b, v10.8b //row5&6 + mov v8.8b, v31.8b + bit v12.16b, v18.16b , v30.16b //final q2 + mov v13.d[0] , v12.d[1] + trn1 v31.8b, v9.8b, v11.8b + trn2 v11.8b, v9.8b, v11.8b //row13 &14 + mov v9.8b, v31.8b + trn1 v31.8b, v12.8b, v14.8b + trn2 v14.8b, v12.8b, v14.8b //row7 & 8 + mov v12.8b, v31.8b + trn1 v31.8b, v13.8b, v15.8b + trn2 v15.8b, v13.8b, v15.8b //row15 & 16 + mov v13.8b , v31.8b + trn1 v31.4h, v10.4h, v14.4h + trn2 v14.4h, v10.4h, v14.4h //row6 & row8 + mov v10.8b, v31.8b + trn1 v31.4h, v11.4h, v15.4h + trn2 v15.4h, v11.4h, v15.4h //row14 & row16 + mov v11.8b, v31.8b + //now Q3 ->p0 and Q7->q3 + trn1 v31.4h, v8.4h, v12.4h + trn2 v12.4h, v8.4h, v12.4h //row 5 & 7 + mov v8.8b, v31.8b + trn1 v31.4h, v9.4h, v13.4h + trn2 v13.4h, v9.4h, v13.4h //row13 & row15 + mov v9.8b, v31.8b + sub x0, x0, x1, lsl#4 //restore pointer + trn1 v31.2s, v6.2s, v14.2s + trn2 v14.2s, v6.2s, v14.2s //row4 & 8 + mov v6.8b , v31.8b + trn1 v31.2s, v7.2s, v15.2s + trn2 v15.2s, v7.2s, v15.2s //row 12 & 16 + mov v7.8b, v31.8b + trn1 v31.2s, v0.2s, v8.2s + trn2 v8.2s, v0.2s, v8.2s //row1 & row5 + mov v0.8b , v31.8b + trn1 v31.2s, v1.2s, v9.2s + trn2 v9.2s, v1.2s, v9.2s //row9 & 13 + mov v1.8b , v31.8b + trn1 v31.2s, v2.2s, v10.2s + trn2 v10.2s, v2.2s, v10.2s //row2 &6 + mov v2.8b , v31.8b + trn1 v31.2s, v3.2s, v11.2s + trn2 v11.2s, v3.2s, v11.2s //row10&row14 + mov v3.8b , v31.8b + trn1 v31.2s, v20.2s, v12.2s + trn2 v12.2s, v20.2s, v12.2s //row3 & 7 + mov v20.8b , v31.8b + trn1 v31.2s, v21.2s, v13.2s + trn2 v13.2s, v21.2s, v13.2s //row11 & row15 + mov v21.8b, v31.8b + st1 {v0.8b}, [x0], x1 //row1 + st1 {v2.8b}, [x0], x1 //row2 + st1 {v20.8b}, [x0], x1 //row3 + st1 {v6.8b}, [x0], x1 //row4 + st1 {v8.8b}, [x0], x1 //row5 + st1 {v10.8b}, [x0], x1 //row6 + st1 {v12.8b}, [x0], x1 //row7 + st1 {v14.8b}, [x0], x1 //row8 + st1 {v1.8b}, [x0], x1 //row9 + st1 {v3.8b}, [x0], x1 //row10 + st1 {v21.8b}, [x0], x1 //row11 + st1 {v7.8b}, [x0], x1 //row12 + st1 {v9.8b}, [x0], x1 //row13 + st1 {v11.8b}, [x0], x1 //row14 + st1 {v13.8b}, [x0], x1 //row15 + st1 {v15.8b}, [x0], x1 //row16 + + // LDMFD sp!,{x12,pc} + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + diff --git a/dependencies/ih264d/common/armv8/ih264_default_weighted_pred_av8.s b/dependencies/ih264d/common/armv8/ih264_default_weighted_pred_av8.s new file mode 100644 index 00000000..d10047eb --- /dev/null +++ b/dependencies/ih264d/common/armv8/ih264_default_weighted_pred_av8.s @@ -0,0 +1,358 @@ +//****************************************************************************** +//* +//* Copyright (C) 2015 The Android Open Source Project +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at: +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. +//* +//***************************************************************************** +//* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +//*/ +///** +//****************************************************************************** +//* @file +//* ih264_default_weighted_pred_av8.s +//* +//* @brief +//* Contains function definitions for default weighted prediction. +//* +//* @author +//* Kaushik Senthoor R +//* +//* @par List of Functions: +//* +//* - ih264_default_weighted_pred_luma_av8() +//* - ih264_default_weighted_pred_chroma_av8() +//* +//* @remarks +//* None +//* +//******************************************************************************* +//*/ +//******************************************************************************* +//* @function +//* ih264_default_weighted_pred_luma_av8() +//* +//* @brief +//* This routine performs the default weighted prediction as described in sec +//* 8.4.2.3.1 titled "Default weighted sample prediction process" for luma. +//* +//* @par Description: +//* This function gets two ht x wd blocks, calculates their rounded-average and +//* stores it in the destination block. +//* +//* @param[in] puc_src1: +//* UWORD8 Pointer to the buffer containing the first input block. +//* +//* @param[in] puc_src2: +//* UWORD8 Pointer to the buffer containing the second input block. +//* +//* @param[out] puc_dst +//* UWORD8 pointer to the destination where the output block is stored. +//* +//* @param[in] src_strd1 +//* Stride of the first input buffer +//* +//* @param[in] src_strd2 +//* Stride of the second input buffer +//* +//* @param[in] dst_strd +//* Stride of the destination buffer +//* +//* @param[in] ht +//* integer height of the array +//* +//* @param[in] wd +//* integer width of the array +//* +//* @returns +//* None +//* +//* @remarks +//* (ht,wd) can be (4,4), (4,8), (8,4), (8,8), (8,16), (16,8) or (16,16). +//* +//******************************************************************************* +//*/ +//void ih264_default_weighted_pred_luma_av8(UWORD8 *puc_src1, +// UWORD8 *puc_src2, +// UWORD8 *puc_dst, +// WORD32 src_strd1, +// WORD32 src_strd2, +// WORD32 dst_strd, +// WORD32 ht, +// WORD32 wd) +// +//**************Variables Vs Registers***************************************** +// x0 => puc_src1 +// x1 => puc_src2 +// x2 => puc_dst +// w3 => src_strd1 +// w4 => src_strd2 +// w5 => dst_strd +// w6 => ht +// w7 => wd +// +.text +.p2align 2 +.include "ih264_neon_macros.s" + + + + .global ih264_default_weighted_pred_luma_av8 + +ih264_default_weighted_pred_luma_av8: + + push_v_regs + stp x19, x20, [sp, #-16]! + sxtw x3, w3 + sxtw x4, w4 + sxtw x5, w5 + cmp w7, #16 + beq loop_16 //branch if wd is 16 + cmp w7, #8 + beq loop_8 //branch if wd is 8 + +loop_4: //each iteration processes four rows + + ld1 {v0.s}[0], [x0], x3 //load row 1 in source 1 + ld1 {v0.s}[1], [x0], x3 //load row 2 in source 1 + ld1 {v2.s}[0], [x1], x4 //load row 1 in source 2 + ld1 {v2.s}[1], [x1], x4 //load row 2 in source 2 + ld1 {v1.s}[0], [x0], x3 //load row 3 in source 1 + ld1 {v1.s}[1], [x0], x3 //load row 4 in source 1 + urhadd v0.8b, v0.8b , v2.8b + ld1 {v3.s}[0], [x1], x4 //load row 3 in source 2 + ld1 {v3.s}[1], [x1], x4 //load row 4 in source 2 + subs w6, w6, #4 //decrement ht by 4 + st1 {v0.s}[0], [x2], x5 //load row 1 in destination + st1 {v0.s}[1], [x2], x5 //load row 2 in destination + urhadd v1.8b, v1.8b , v3.8b + st1 {v1.s}[0], [x2], x5 //load row 3 in destination + st1 {v1.s}[1], [x2], x5 //load row 4 in destination + bgt loop_4 //if greater than 0 repeat the loop again + b end_loops + +loop_8: //each iteration processes four rows + + ld1 {v0.8b}, [x0], x3 //load row 1 in source 1 + ld1 {v4.8b}, [x1], x4 //load row 1 in source 2 + ld1 {v1.8b}, [x0], x3 //load row 2 in source 1 + ld1 {v5.8b}, [x1], x4 //load row 2 in source 2 + ld1 {v2.8b}, [x0], x3 //load row 3 in source 1 + urhadd v0.16b, v0.16b , v4.16b + urhadd v1.16b, v1.16b , v5.16b + ld1 {v6.8b}, [x1], x4 //load row 3 in source 2 + ld1 {v3.8b}, [x0], x3 //load row 4 in source 1 + urhadd v2.8b, v2.8b , v6.8b + ld1 {v7.8b}, [x1], x4 //load row 4 in source 2 + subs w6, w6, #4 //decrement ht by 4 + st1 {v0.8b}, [x2], x5 //load row 1 in destination + urhadd v3.8b, v3.8b , v7.8b + st1 {v1.8b}, [x2], x5 //load row 2 in destination + st1 {v2.8b}, [x2], x5 //load row 3 in destination + st1 {v3.8b}, [x2], x5 //load row 4 in destination + bgt loop_8 //if greater than 0 repeat the loop again + b end_loops + +loop_16: //each iteration processes eight rows + + ld1 {v0.8b, v1.8b}, [x0], x3 //load row 1 in source 1 + ld1 {v16.8b, v17.8b}, [x1], x4 //load row 1 in source 2 + ld1 {v2.8b, v3.8b}, [x0], x3 //load row 2 in source 1 + ld1 {v18.8b, v19.8b}, [x1], x4 //load row 2 in source 2 + urhadd v0.16b, v0.16b , v16.16b + urhadd v1.16b, v1.16b , v17.16b + ld1 {v4.8b, v5.8b}, [x0], x3 //load row 3 in source 1 + ld1 {v20.8b, v21.8b}, [x1], x4 //load row 3 in source 2 + urhadd v2.16b, v2.16b , v18.16b + urhadd v3.16b, v3.16b , v19.16b + ld1 {v6.8b, v7.8b}, [x0], x3 //load row 4 in source 1 + ld1 {v22.8b, v23.8b}, [x1], x4 //load row 4 in source 2 + urhadd v4.16b, v4.16b , v20.16b + urhadd v5.16b, v5.16b , v21.16b + ld1 {v8.8b, v9.8b}, [x0], x3 //load row 5 in source 1 + ld1 {v24.8b, v25.8b}, [x1], x4 //load row 5 in source 2 + urhadd v6.16b, v6.16b , v22.16b + urhadd v7.16b, v7.16b , v23.16b + ld1 {v10.8b, v11.8b}, [x0], x3 //load row 6 in source 1 + ld1 {v26.8b, v27.8b}, [x1], x4 //load row 6 in source 2 + urhadd v8.16b, v8.16b , v24.16b + urhadd v9.16b, v9.16b , v25.16b + ld1 {v12.8b, v13.8b}, [x0], x3 //load row 7 in source 1 + ld1 {v28.8b, v29.8b}, [x1], x4 //load row 7 in source 2 + urhadd v10.16b, v10.16b , v26.16b + urhadd v11.16b, v11.16b , v27.16b + ld1 {v14.8b, v15.8b}, [x0], x3 //load row 8 in source 1 + ld1 {v30.8b, v31.8b}, [x1], x4 //load row 8 in source 2 + urhadd v12.16b, v12.16b , v28.16b + urhadd v13.16b, v13.16b , v29.16b + st1 {v0.8b, v1.8b}, [x2], x5 //load row 1 in destination + st1 {v2.8b, v3.8b}, [x2], x5 //load row 2 in destination + urhadd v14.16b, v14.16b , v30.16b + urhadd v15.16b, v15.16b , v31.16b + st1 {v4.8b, v5.8b}, [x2], x5 //load row 3 in destination + st1 {v6.8b, v7.8b}, [x2], x5 //load row 4 in destination + subs w6, w6, #8 //decrement ht by 8 + st1 {v8.8b, v9.8b}, [x2], x5 //load row 5 in destination + st1 {v10.8b, v11.8b}, [x2], x5 //load row 6 in destination + st1 {v12.8b, v13.8b}, [x2], x5 //load row 7 in destination + st1 {v14.8b, v15.8b}, [x2], x5 //load row 8 in destination + bgt loop_16 //if greater than 0 repeat the loop again + +end_loops: + + // LDMFD sp!,{x4-x7,x15} //Reload the registers from sp + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + +//******************************************************************************* +//* @function +//* ih264_default_weighted_pred_chroma_av8() +//* +//* @brief +//* This routine performs the default weighted prediction as described in sec +//* 8.4.2.3.1 titled "Default weighted sample prediction process" for chroma. +//* +//* @par Description: +//* This function gets two ht x wd blocks, calculates their rounded-average and +//* stores it in the destination block for U and V. +//* +//* @param[in] puc_src1: +//* UWORD8 Pointer to the buffer containing the first input block. +//* +//* @param[in] puc_src2: +//* UWORD8 Pointer to the buffer containing the second input block. +//* +//* @param[out] puc_dst +//* UWORD8 pointer to the destination where the output block is stored. +//* +//* @param[in] src_strd1 +//* Stride of the first input buffer +//* +//* @param[in] src_strd2 +//* Stride of the second input buffer +//* +//* @param[in] dst_strd +//* Stride of the destination buffer +//* +//* @param[in] ht +//* integer height of the array +//* +//* @param[in] wd +//* integer width of the array +//* +//* @returns +//* None +//* +//* @remarks +//* (ht,wd) can be (2,2), (2,4), (4,2), (4,4), (4,8), (8,4) or (8,8). +//* +//******************************************************************************* +//*/ +//void ih264_default_weighted_pred_chroma_av8(UWORD8 *puc_src1, +// UWORD8 *puc_src2, +// UWORD8 *puc_dst, +// WORD32 src_strd1, +// WORD32 src_strd2, +// WORD32 dst_strd, +// WORD32 ht, +// WORD32 wd) +// +//**************Variables Vs Registers***************************************** +// x0 => puc_src1 +// x1 => puc_src2 +// x2 => puc_dst +// w3 => src_strd1 +// w4 => src_strd2 +// w5 => dst_strd +// w6 => ht +// w7 => wd +// + + + + + .global ih264_default_weighted_pred_chroma_av8 + +ih264_default_weighted_pred_chroma_av8: + + push_v_regs + stp x19, x20, [sp, #-16]! + sxtw x3, w3 + sxtw x4, w4 + sxtw x5, w5 + cmp w7, #8 + beq loop_8_uv //branch if wd is 8 + cmp w7, #4 + beq loop_4_uv //branch if wd is 4 + +loop_2_uv: //each iteration processes two rows + + ld1 {v0.s}[0], [x0], x3 //load row 1 in source 1 + ld1 {v0.s}[1], [x0], x3 //load row 2 in source 1 + ld1 {v1.s}[0], [x1], x4 //load row 1 in source 2 + ld1 {v1.s}[1], [x1], x4 //load row 2 in source 2 + urhadd v0.8b, v0.8b , v1.8b + subs w6, w6, #2 //decrement ht by 2 + st1 {v0.s}[0], [x2], x5 //load row 1 in destination + st1 {v0.s}[1], [x2], x5 //load row 2 in destination + bgt loop_2_uv //if greater than 0 repeat the loop again + b end_loops_uv + +loop_4_uv: //each iteration processes two rows + + ld1 {v0.8b}, [x0], x3 //load row 1 in source 1 + ld1 {v2.8b}, [x1], x4 //load row 1 in source 2 + ld1 {v1.8b}, [x0], x3 //load row 2 in source 1 + urhadd v0.8b, v0.8b , v2.8b + ld1 {v3.8b}, [x1], x4 //load row 2 in source 2 + urhadd v1.8b, v1.8b , v3.8b + st1 {v0.8b}, [x2], x5 //load row 1 in destination + subs w6, w6, #2 //decrement ht by 2 + st1 {v1.8b}, [x2], x5 //load row 2 in destination + bgt loop_4_uv //if greater than 0 repeat the loop again + b end_loops_uv + +loop_8_uv: //each iteration processes four rows + + ld1 {v0.8b, v1.8b}, [x0], x3 //load row 1 in source 1 + ld1 {v8.8b, v9.8b}, [x1], x4 //load row 1 in source 2 + ld1 {v2.8b, v3.8b}, [x0], x3 //load row 2 in source 1 + urhadd v0.16b, v0.16b , v8.16b + urhadd v1.16b, v1.16b , v9.16b + ld1 {v10.8b, v11.8b}, [x1], x4 //load row 2 in source 2 + ld1 {v4.8b, v5.8b}, [x0], x3 //load row 3 in source 1 + urhadd v2.16b, v2.16b , v10.16b + urhadd v3.16b, v3.16b , v11.16b + ld1 {v12.8b, v13.8b}, [x1], x4 //load row 3 in source 2 + ld1 {v6.8b, v7.8b}, [x0], x3 //load row 4 in source 1 + urhadd v4.16b, v4.16b , v12.16b + urhadd v5.16b, v5.16b , v13.16b + ld1 {v14.8b, v15.8b}, [x1], x4 //load row 4 in source 2 + st1 {v0.8b, v1.8b}, [x2], x5 //load row 1 in destination + urhadd v6.16b, v6.16b , v14.16b + urhadd v7.16b, v7.16b , v15.16b + st1 {v2.8b, v3.8b}, [x2], x5 //load row 2 in destination + subs w6, w6, #4 //decrement ht by 4 + st1 {v4.8b, v5.8b}, [x2], x5 //load row 3 in destination + st1 {v6.8b, v7.8b}, [x2], x5 //load row 4 in destination + bgt loop_8_uv //if greater than 0 repeat the loop again + +end_loops_uv: + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + + diff --git a/dependencies/ih264d/common/armv8/ih264_ihadamard_scaling_av8.s b/dependencies/ih264d/common/armv8/ih264_ihadamard_scaling_av8.s new file mode 100644 index 00000000..712c9aeb --- /dev/null +++ b/dependencies/ih264d/common/armv8/ih264_ihadamard_scaling_av8.s @@ -0,0 +1,250 @@ +//****************************************************************************** +//* +//* Copyright (C) 2015 The Android Open Source Project +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at: +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. +//* +//***************************************************************************** +//* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +//*/ +///** +// ******************************************************************************* +// * @file +// * ih264_ihadamard_scaling_av8.s +// * +// * @brief +// * Contains function definitions for inverse hadamard transform on 4x4 DC outputs +// * of 16x16 intra-prediction +// * +// * @author +// * Mohit +// * +// * @par List of Functions: +// * - ih264_ihadamard_scaling_4x4_av8() +// * +// * @remarks +// * None +// * +.include "ih264_neon_macros.s" + +// ******************************************************************************* +// */ +// * @brief This function performs a 4x4 inverse hadamard transform on the 4x4 DC coefficients +// * of a 16x16 intra prediction macroblock, and then performs scaling. +// * prediction buffer +// * +// * @par Description: +// * The DC coefficients pass through a 2-stage inverse hadamard transform. +// * This inverse transformed content is scaled to based on Qp value. +// * +// * @param[in] pi2_src +// * input 4x4 block of DC coefficients +// * +// * @param[out] pi2_out +// * output 4x4 block +// * +// * @param[in] pu2_iscal_mat +// * pointer to scaling list +// * +// * @param[in] pu2_weigh_mat +// * pointer to weight matrix +// * +// * @param[in] u4_qp_div_6 +// * Floor (qp/6) +// * +// * @param[in] pi4_tmp +// * temporary buffer of size 1*16 +// * +// * @returns none +// * +// * @remarks none +// * +// ******************************************************************************* +// */ +// * +// ******************************************************************************* +// */ +// void ih264_ihadamard_scaling_4x4(word16* pi2_src, +// word16* pi2_out, +// const uword16 *pu2_iscal_mat, +// const uword16 *pu2_weigh_mat, +// uword32 u4_qp_div_6, +// word32* pi4_tmp) +//**************variables vs registers***************************************** +//x0 => *pi2_src +//x1 => *pi2_out +//x2 => *pu2_iscal_mat +//x3 => *pu2_weigh_mat +//x4=> u4_qp_div_6 + +.text +.p2align 2 + + .global ih264_ihadamard_scaling_4x4_av8 +ih264_ihadamard_scaling_4x4_av8: + +//only one shift is done in horizontal inverse because, +//if u4_qp_div_6 is lesser than 4 then shift value will be neagative and do negative left shift, in this case rnd_factor has value +//if u4_qp_div_6 is greater than 4 then shift value will be positive and do left shift, here rnd_factor is 0 + push_v_regs + +//=======================inverse hadamard transform================================ + + ld4 {v0.4h-v3.4h}, [x0] //load x4,x5,x6,x7 + + dup v14.4s, w4 // populate the u4_qp_div_6 + ld1 {v15.h}[0], [x3] // pu2_weigh_mat + ld1 {v16.h}[0], [x2] //pu2_iscal_mat + + saddl v4.4s, v0.4h, v3.4h //x0 = x4 + x7 + saddl v5.4s, v1.4h, v2.4h //x1 = x5 + x6 + ssubl v6.4s, v1.4h, v2.4h //x2 = x5 - x6 + ssubl v7.4s, v0.4h, v3.4h //x3 = x4 - x7 + + add v0.4s, v4.4s, v5.4s //pi4_tmp_ptr[0] = x0 + x1 + add v1.4s, v7.4s, v6.4s //pi4_tmp_ptr[1] = x3 + x2 + sub v2.4s, v4.4s, v5.4s //pi4_tmp_ptr[2] = x0 - x1 + sub v3.4s, v7.4s, v6.4s //pi4_tmp_ptr[3] = x3 - x2 + + umull v15.4s, v15.4h, v16.4h + dup v15.4s, v15.s[0] //pu2_weigh_mat[0]*pu2_iscal_mat[0] + + //transpose + trn1 v4.4s, v0.4s, v1.4s + trn2 v5.4s, v0.4s, v1.4s + trn1 v6.4s, v2.4s, v3.4s + trn2 v7.4s, v2.4s, v3.4s + + trn1 v0.2d, v4.2d, v6.2d + trn2 v2.2d, v4.2d, v6.2d + trn1 v1.2d, v5.2d, v7.2d + trn2 v3.2d, v5.2d, v7.2d + //end transpose + + add v4.4s, v0.4s, v3.4s //x0 = x4+x7 + add v5.4s, v1.4s, v2.4s //x1 = x5+x6 + sub v6.4s, v1.4s, v2.4s //x2 = x5-x6 + sub v7.4s, v0.4s, v3.4s //x3 = x4-x7 + + add v0.4s, v4.4s, v5.4s //pi4_tmp_ptr[0] = x0 + x1 + add v1.4s, v7.4s, v6.4s //pi4_tmp_ptr[1] = x3 + x2 + sub v2.4s, v4.4s, v5.4s //pi4_tmp_ptr[2] = x0 - x1 + sub v3.4s, v7.4s, v6.4s //pi4_tmp_ptr[3] = x3 - x2 + + mul v0.4s, v0.4s, v15.4s // q0 = p[i] = (x[i] * trns_coeff[i]) where i = 0..3 + mul v1.4s, v1.4s, v15.4s // q1 = p[i] = (x[i] * trns_coeff[i]) where i = 4..7 + mul v2.4s, v2.4s, v15.4s // q2 = p[i] = (x[i] * trns_coeff[i]) where i = 8..11 + mul v3.4s, v3.4s, v15.4s // q3 = p[i] = (x[i] * trns_coeff[i]) where i = 12..15 + + sshl v0.4s, v0.4s, v14.4s // q0 = q[i] = (p[i] << (qp/6)) where i = 0..3 + sshl v1.4s, v1.4s, v14.4s // q1 = q[i] = (p[i] << (qp/6)) where i = 4..7 + sshl v2.4s, v2.4s, v14.4s // q2 = q[i] = (p[i] << (qp/6)) where i = 8..11 + sshl v3.4s, v3.4s, v14.4s // q3 = q[i] = (p[i] << (qp/6)) where i = 12..15 + + sqrshrn v0.4h, v0.4s, #6 // d0 = c[i] = ((q[i] + 32) >> 4) where i = 0..3 + sqrshrn v1.4h, v1.4s, #6 // d1 = c[i] = ((q[i] + 32) >> 4) where i = 4..7 + sqrshrn v2.4h, v2.4s, #6 // d2 = c[i] = ((q[i] + 32) >> 4) where i = 8..11 + sqrshrn v3.4h, v3.4s, #6 // d3 = c[i] = ((q[i] + 32) >> 4) where i = 12..15 + + st1 {v0.4h-v3.4h}, [x1] //store the result + + pop_v_regs + ret + + +// ******************************************************************************* +// */ +// * @brief This function performs a 2x2 inverse hadamard transform for chroma block +// * +// * @par Description: +// * The DC coefficients pass through a 2-stage inverse hadamard transform. +// * This inverse transformed content is scaled to based on Qp value. +// * Both DC blocks of U and v blocks are processesd +// * +// * @param[in] pi2_src +// * input 1x8 block of ceffs. First 4 are from U and next from V +// * +// * @param[out] pi2_out +// * output 1x8 block +// * +// * @param[in] pu2_iscal_mat +// * pointer to scaling list +// * +// * @param[in] pu2_weigh_mat +// * pointer to weight matrix +// * +// * @param[in] u4_qp_div_6 +// * Floor (qp/6) +// * +// * @returns none +// * +// * @remarks none +// * +// ******************************************************************************* +// */ +// * +// ******************************************************************************* +// */ +// void ih264_ihadamard_scaling_2x2_uv(WORD16* pi2_src, +// WORD16* pi2_out, +// const UWORD16 *pu2_iscal_mat, +// const UWORD16 *pu2_weigh_mat, +// UWORD32 u4_qp_div_6, + + .global ih264_ihadamard_scaling_2x2_uv_av8 +ih264_ihadamard_scaling_2x2_uv_av8: + +//Registers used +// x0 : *pi2_src +// x1 : *pi2_out +// x2 : *pu2_iscal_mat +// x3 : *pu2_weigh_mat +// x4 : u4_qp_div_6 + push_v_regs + ld1 {v26.h}[0], [x2] + ld1 {v27.h}[0], [x3] + + sub w4, w4, #5 //qp/6 - 4 + dup v28.4s, w4 //load qp/6 + + ld2 {v0.4h, v1.4h}, [x0] //load 8 dc coeffs + //i2_x4,i2_x6,i2_y4,i1_y6 -> d0 + //i2_x5,i2_x7,i2_y5,i1_y6 -> d1 + + saddl v2.4s, v0.4h, v1.4h //i4_x0 = i4_x4 + i4_x5;...x2 + ssubl v4.4s, v0.4h, v1.4h //i4_x1 = i4_x4 - i4_x5;...x3 + + umull v30.4s, v26.4h, v27.4h //pu2_iscal_mat[0]*pu2_weigh_mat[0] + dup v30.4s, v30.s[0] + + trn1 v0.4s, v2.4s, v4.4s + trn2 v1.4s, v2.4s, v4.4s //i4_x0 i4_x1 -> q1 + + add v2.4s, v0.4s, v1.4s //i4_x4 = i4_x0+i4_x2;.. i4_x5 + sub v3.4s, v0.4s, v1.4s //i4_x6 = i4_x0-i4_x2;.. i4_x7 + + mul v2.4s, v2.4s, v30.4s + mul v3.4s, v3.4s, v30.4s + + sshl v2.4s, v2.4s, v28.4s + sshl v3.4s, v3.4s, v28.4s + + xtn v0.4h, v2.4s //i4_x4 i4_x5 i4_y4 i4_y5 + xtn v1.4h, v3.4s //i4_x6 i4_x7 i4_y6 i4_y7 + + st2 {v0.4s-v1.4s}, [x1] + pop_v_regs + ret + + + diff --git a/dependencies/ih264d/common/armv8/ih264_inter_pred_chroma_av8.s b/dependencies/ih264d/common/armv8/ih264_inter_pred_chroma_av8.s new file mode 100644 index 00000000..f6aef402 --- /dev/null +++ b/dependencies/ih264d/common/armv8/ih264_inter_pred_chroma_av8.s @@ -0,0 +1,398 @@ +//****************************************************************************** +//* +//* Copyright (C) 2015 The Android Open Source Project +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at: +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. +//* +//***************************************************************************** +//* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +//*/ +///** +//****************************************************************************** +//* @file +//* ih264_inter_pred_chroma_av8.s +//* +//* @brief +//* Contains function definitions for inter prediction interpolation. +//* +//* @author +//* Ittaim +//* +//* @par List of Functions: +//* +//* - ih264_inter_pred_chroma_av8() +//* +//* @remarks +//* None +//* +//******************************************************************************* +//*/ + +///* All the functions here are replicated from ih264_inter_pred_filters.c +// + +///** +///** +///** +// +///** +//******************************************************************************* +//* +//* @brief +//* Interprediction chroma filter +//* +//* @par Description: +//* Applies filtering to chroma samples as mentioned in +//* sec 8.4.2.2.2 titled "chroma sample interpolation process" +//* +//* @param[in] pu1_src +//* UWORD8 pointer to the source containing alternate U and V samples +//* +//* @param[out] pu1_dst +//* UWORD8 pointer to the destination +//* +//* @param[in] src_strd +//* integer source stride +//* +//* @param[in] dst_strd +//* integer destination stride +//* +//* @param[in]uc_dx +//* dx value where the sample is to be produced(refer sec 8.4.2.2.2 ) +//* +//* @param[in] uc_dy +//* dy value where the sample is to be produced(refer sec 8.4.2.2.2 ) +//* +//* @param[in] ht +//* integer height of the array +//* +//* @param[in] wd +//* integer width of the array +//* +//* @returns +//* +//* @remarks +//* None +//* +//******************************************************************************* +//*/ + +//void ih264_inter_pred_chroma(UWORD8 *pu1_src, +// UWORD8 *pu1_dst, +// WORD32 src_strd, +// WORD32 dst_strd, +// WORD32 u1_dx, +// WORD32 u1_dy, +// WORD32 ht, +// WORD32 wd) +//**************Variables Vs Registers***************************************** +// x0 => *pu1_src +// x1 => *pu1_dst +// w2 => src_strd +// w3 => dst_strd +// w4 => u1_dx +// w5 => u1_dy +// w6 => height +// w7 => width +// +.text +.p2align 2 +.include "ih264_neon_macros.s" + + + + .global ih264_inter_pred_chroma_av8 + +ih264_inter_pred_chroma_av8: + + + + // STMFD sp!, {x4-x12, x14} //store register values to stack + push_v_regs + stp x19, x20, [sp, #-16]! + sxtw x2, w2 + sxtw x3, w3 + sxtw x4, w4 + sxtw x5, w5 + sxtw x6, w6 + sxtw x7, w7 + + + + + + sub x20, x4, #8 //8-u1_dx + neg x8, x20 + sub x20, x5, #8 //8-u1_dy + neg x9, x20 + mul x10, x8, x9 // + mul x11, x4, x9 // + + dup v28.8b, w10 + dup v29.8b, w11 + + mul x10, x8, x5 // + mul x11, x4, x5 // + + dup v30.8b, w10 + dup v31.8b, w11 + + subs x12, x7, #2 //if wd=4 branch to loop_4 + beq loop_2 + subs x12, x7, #4 //if wd=8 branch to loop_8 + beq loop_4 + +loop_8: + ld1 {v0.8b, v1.8b, v2.8b}, [x0], x2 //// Load row0 ; + ext v3.8b, v0.8b , v1.8b , #2 + ld1 {v5.8b, v6.8b, v7.8b}, [x0], x2 //// Load row1; + umull v20.8h, v0.8b, v28.8b + ext v8.8b, v5.8b , v6.8b , #2 + umlal v20.8h, v3.8b, v29.8b + ext v9.8b, v6.8b , v7.8b , #2 + umlal v20.8h, v5.8b, v30.8b + ext v4.8b, v1.8b , v2.8b , #2 + umlal v20.8h, v8.8b, v31.8b + sqrshrun v26.8b, v20.8h, #6 + umull v22.8h, v1.8b, v28.8b + ld1 {v10.8b, v11.8b, v12.8b}, [x0], x2 //// Load row2 ; + umlal v22.8h, v4.8b, v29.8b + ext v13.8b, v10.8b , v11.8b , #2 + umlal v22.8h, v6.8b, v30.8b + ext v14.8b, v11.8b , v12.8b , #2 + umlal v22.8h, v9.8b, v31.8b + sqrshrun v27.8b, v22.8h, #6 + umull v24.8h, v5.8b, v28.8b + st1 { v26.8b, v27.8b}, [x1], x3 ////Store dest row + umlal v24.8h, v8.8b, v29.8b + ld1 {v0.8b, v1.8b, v2.8b}, [x0], x2 //// Load row3 ; + umlal v24.8h, v10.8b, v30.8b + ext v3.8b, v0.8b , v1.8b , #2 + umlal v24.8h, v13.8b, v31.8b + ext v4.8b, v1.8b , v2.8b , #2 + umull v16.8h, v6.8b, v28.8b + sqrshrun v18.8b, v24.8h, #6 + umlal v16.8h, v9.8b, v29.8b + umlal v16.8h, v11.8b, v30.8b + umlal v16.8h, v14.8b, v31.8b + sqrshrun v19.8b, v16.8h, #6 + st1 {v18.8b, v19.8b}, [x1], x3 // store row 1 + umull v20.8h, v10.8b, v28.8b + umlal v20.8h, v13.8b, v29.8b + umlal v20.8h, v0.8b, v30.8b + umlal v20.8h, v3.8b, v31.8b + sqrshrun v26.8b, v20.8h, #6 + umull v24.8h, v11.8b, v28.8b + ld1 {v5.8b, v6.8b, v7.8b}, [x0], x2 //// Load row4; + umlal v24.8h, v14.8b, v29.8b + ext v8.8b, v5.8b , v6.8b , #2 + umlal v24.8h, v1.8b, v30.8b + ext v9.8b, v6.8b , v7.8b , #2 + umlal v24.8h, v4.8b, v31.8b + umull v20.8h, v0.8b, v28.8b + sqrshrun v27.8b, v24.8h, #6 + umlal v20.8h, v3.8b, v29.8b + st1 { v26.8b, v27.8b}, [x1], x3 ////Store dest row2 + umlal v20.8h, v5.8b, v30.8b + umlal v20.8h, v8.8b, v31.8b + umull v22.8h, v1.8b, v28.8b + umlal v22.8h, v4.8b, v29.8b + umlal v22.8h, v6.8b, v30.8b + sqrshrun v26.8b, v20.8h, #6 + umlal v22.8h, v9.8b, v31.8b + subs x12, x6, #4 + sqrshrun v27.8b, v22.8h, #6 + st1 { v26.8b, v27.8b}, [x1], x3 ////Store dest row3 + + beq end_func //If ht=4 + + ld1 {v10.8b, v11.8b, v12.8b}, [x0], x2 //// Load row5 + ext v13.8b, v10.8b , v11.8b , #2 + umull v24.8h, v5.8b, v28.8b + ext v14.8b, v11.8b , v12.8b , #2 + ld1 {v0.8b, v1.8b, v2.8b}, [x0], x2 //// Load row6; + umlal v24.8h, v8.8b, v29.8b + umlal v24.8h, v10.8b, v30.8b + umlal v24.8h, v13.8b, v31.8b + ext v3.8b, v0.8b , v1.8b , #2 + umull v16.8h, v6.8b, v28.8b + sqrshrun v18.8b, v24.8h, #6 + umlal v16.8h, v9.8b, v29.8b + umlal v16.8h, v11.8b, v30.8b + umlal v16.8h, v14.8b, v31.8b + ext v4.8b, v1.8b , v2.8b , #2 + sqrshrun v19.8b, v16.8h, #6 + st1 { v18.8b, v19.8b}, [x1], x3 // store row 4 + umull v20.8h, v10.8b, v28.8b + umlal v20.8h, v13.8b, v29.8b + umlal v20.8h, v0.8b, v30.8b + umlal v20.8h, v3.8b, v31.8b + ld1 {v5.8b, v6.8b, v7.8b}, [x0], x2 //// Load row7; + sqrshrun v26.8b, v20.8h, #6 + umull v24.8h, v11.8b, v28.8b + umlal v24.8h, v14.8b, v29.8b + ext v8.8b, v5.8b , v6.8b , #2 + umlal v24.8h, v1.8b, v30.8b + umlal v24.8h, v4.8b, v31.8b + ext v9.8b, v6.8b , v7.8b , #2 + sqrshrun v27.8b, v24.8h, #6 + st1 {v26.8b, v27.8b}, [x1], x3 ////Store dest row5 + umull v20.8h, v0.8b, v28.8b + umlal v20.8h, v3.8b, v29.8b + umlal v20.8h, v5.8b, v30.8b + umlal v20.8h, v8.8b, v31.8b + ld1 {v10.8b, v11.8b, v12.8b}, [x0], x2 //// Load row8 ; + sqrshrun v26.8b, v20.8h, #6 + umull v22.8h, v1.8b, v28.8b + umlal v22.8h, v4.8b, v29.8b + umlal v22.8h, v6.8b, v30.8b + ext v13.8b, v10.8b , v11.8b , #2 + umlal v22.8h, v9.8b, v31.8b + ext v14.8b, v11.8b , v12.8b , #2 + sqrshrun v27.8b, v22.8h, #6 + st1 { v26.8b, v27.8b}, [x1], x3 ////Store dest row6 + umull v24.8h, v5.8b, v28.8b + umlal v24.8h, v8.8b, v29.8b + umlal v24.8h, v10.8b, v30.8b + umlal v24.8h, v13.8b, v31.8b + umull v16.8h, v6.8b, v28.8b + sqrshrun v18.8b, v24.8h, #6 + umlal v16.8h, v9.8b, v29.8b + umlal v16.8h, v11.8b, v30.8b + umlal v16.8h, v14.8b, v31.8b + sqrshrun v19.8b, v16.8h, #6 + st1 { v18.8b, v19.8b}, [x1], x3 // store row 7 + b end_func + +loop_4: + ld1 {v0.8b, v1.8b}, [x0], x2 //// Load row0 ; + ext v2.8b, v0.8b , v1.8b , #2 + ld1 {v3.8b, v4.8b}, [x0], x2 //// Load row1; + ext v5.8b, v3.8b , v4.8b , #2 + umull v20.8h, v0.8b, v28.8b + umlal v20.8h, v2.8b, v29.8b + umlal v20.8h, v3.8b, v30.8b + umlal v20.8h, v5.8b, v31.8b + ld1 {v6.8b, v7.8b}, [x0], x2 //// Load row2 + sqrshrun v26.8b, v20.8h, #6 + ext v8.8b, v6.8b , v7.8b , #2 + st1 {v26.8b}, [x1], x3 ////Store dest row0 + umull v22.8h, v3.8b, v28.8b + umlal v22.8h, v5.8b, v29.8b + umlal v22.8h, v6.8b, v30.8b + umlal v22.8h, v8.8b, v31.8b + subs x12, x6, #2 + sqrshrun v27.8b, v22.8h, #6 + st1 {v27.8b}, [x1], x3 ////Store dest row1 + beq end_func //If ht=2 + + ld1 {v9.8b, v10.8b}, [x0], x2 //// Load row3; + ext v11.8b, v9.8b , v10.8b , #2 + umull v24.8h, v6.8b, v28.8b + umlal v24.8h, v8.8b, v29.8b + umlal v24.8h, v9.8b, v30.8b + umlal v24.8h, v11.8b, v31.8b + ld1 {v0.8b, v1.8b}, [x0], x2 //// Load row4 ; + sqrshrun v16.8b, v24.8h, #6 + ext v2.8b, v0.8b , v1.8b , #2 + st1 {v16.8b}, [x1], x3 ////Store dest row2 + umull v18.8h, v9.8b, v28.8b + umlal v18.8h, v11.8b, v29.8b + umlal v18.8h, v0.8b, v30.8b + umlal v18.8h, v2.8b, v31.8b + subs x12, x6, #4 + sqrshrun v17.8b, v18.8h, #6 + st1 {v17.8b}, [x1], x3 ////Store dest row3 + beq end_func //If ht=4 + + ld1 {v3.8b, v4.8b}, [x0], x2 //// Load row5; + ext v5.8b, v3.8b , v4.8b , #2 + umull v20.8h, v0.8b, v28.8b + umlal v20.8h, v2.8b, v29.8b + umlal v20.8h, v3.8b, v30.8b + umlal v20.8h, v5.8b, v31.8b + ld1 {v6.8b, v7.8b}, [x0], x2 //// Load row6 ; + sqrshrun v26.8b, v20.8h, #6 + ext v8.8b, v6.8b , v7.8b , #2 + st1 {v26.8b}, [x1], x3 ////Store dest row4 + umull v22.8h, v3.8b, v28.8b + umlal v22.8h, v5.8b, v29.8b + umlal v22.8h, v6.8b, v30.8b + umlal v22.8h, v8.8b, v31.8b + ld1 {v9.8b, v10.8b}, [x0], x2 //// Load row7; + sqrshrun v27.8b, v22.8h, #6 + ext v11.8b, v9.8b , v10.8b , #2 + st1 {v27.8b}, [x1], x3 ////Store dest row5 + umull v24.8h, v6.8b, v28.8b + umlal v24.8h, v8.8b, v29.8b + umlal v24.8h, v9.8b, v30.8b + umlal v24.8h, v11.8b, v31.8b + ld1 {v0.8b, v1.8b}, [x0], x2 //// Load row8; + sqrshrun v16.8b, v24.8h, #6 + ext v2.8b, v0.8b , v1.8b , #2 + st1 {v16.8b}, [x1], x3 ////Store dest row6 + umull v18.8h, v9.8b, v28.8b + umlal v18.8h, v11.8b, v29.8b + umlal v18.8h, v0.8b, v30.8b + umlal v18.8h, v2.8b, v31.8b + sqrshrun v17.8b, v18.8h, #6 + st1 {v17.8b}, [x1], x3 ////Store dest row7 + b end_func + +loop_2: + ld1 {v0.8b}, [x0], x2 //// Load row0 ; + ext v2.8b, v0.8b , v0.8b , #2 + ld1 {v3.8b}, [x0], x2 //// Load row1; + ext v5.8b, v3.8b , v3.8b , #2 + umull v20.8h, v0.8b, v28.8b + umlal v20.8h, v2.8b, v29.8b + umlal v20.8h, v3.8b, v30.8b + umlal v20.8h, v5.8b, v31.8b + ld1 {v6.8b}, [x0], x2 //// Load row2 + sqrshrun v26.8b, v20.8h, #6 + ext v8.8b, v6.8b , v6.8b , #2 + st1 {v26.s}[0], [x1], x3 ////Store dest row0 + umull v22.8h, v3.8b, v28.8b + umlal v22.8h, v5.8b, v29.8b + umlal v22.8h, v6.8b, v30.8b + umlal v22.8h, v8.8b, v31.8b + subs x12, x6, #2 + sqrshrun v27.8b, v22.8h, #6 + st1 {v27.s}[0], [x1], x3 ////Store dest row1 + beq end_func //If ht=2 + + ld1 {v9.8b}, [x0], x2 //// Load row3; + ext v11.8b, v9.8b , v9.8b , #2 + umull v24.8h, v6.8b, v28.8b + umlal v24.8h, v8.8b, v29.8b + umlal v24.8h, v9.8b, v30.8b + umlal v24.8h, v11.8b, v31.8b + ld1 {v0.8b}, [x0], x2 //// Load row4 ; + sqrshrun v16.8b, v24.8h, #6 + ext v2.8b, v0.8b , v0.8b , #2 + st1 {v16.s}[0], [x1], x3 ////Store dest row2 + umull v18.8h, v9.8b, v28.8b + umlal v18.8h, v11.8b, v29.8b + umlal v18.8h, v0.8b, v30.8b + umlal v18.8h, v2.8b, v31.8b + sqrshrun v17.8b, v18.8h, #6 + st1 {v17.s}[0], [x1], x3 ////Store dest row3 + + +end_func: + // LDMFD sp!,{x4-x12,PC} //Restoring registers from stack + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + diff --git a/dependencies/ih264d/common/armv8/ih264_inter_pred_filters_luma_horz_av8.s b/dependencies/ih264d/common/armv8/ih264_inter_pred_filters_luma_horz_av8.s new file mode 100644 index 00000000..e7c9f862 --- /dev/null +++ b/dependencies/ih264d/common/armv8/ih264_inter_pred_filters_luma_horz_av8.s @@ -0,0 +1,534 @@ +//****************************************************************************** +//* +//* Copyright (C) 2015 The Android Open Source Project +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at: +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. +//* +//***************************************************************************** +//* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +//*/ +///** +//****************************************************************************** +//* @file +//* ih264_inter_pred_luma_horz_av8.s +//* +//* @brief +//* Contains function definitions for inter prediction interpolation. +//* +//* @author +//* Ittiam +//* +//* @par List of Functions: +//* +//* - ih264_inter_pred_luma_horz_av8() +//* +//* @remarks +//* None +//* +//******************************************************************************* +//*/ + +///* All the functions here are replicated from ih264_inter_pred_filters.c +// + +///** +///** +//******************************************************************************* +//* +//* @brief +//* Interprediction luma filter for horizontal input +//* +//* @par Description: +//* Applies a 6 tap horizontal filter .The output is clipped to 8 bits +//* sec 8.4.2.2.1 titled "Luma sample interpolation process" +//* +//* @param[in] pu1_src +//* UWORD8 pointer to the source +//* +//* @param[out] pu1_dst +//* UWORD8 pointer to the destination +//* +//* @param[in] src_strd +//* integer source stride +//* +//* @param[in] dst_strd +//* integer destination stride +//* +//* @param[in] ht +//* integer height of the array +//* +//* @param[in] wd +//* integer width of the array +//* +//* @returns +//* +// @remarks +//* None +//* +//******************************************************************************* +//*/ + +//void ih264_inter_pred_luma_horz ( +// UWORD8 *pu1_src, +// UWORD8 *pu1_dst, +// WORD32 src_strd, +// WORD32 dst_strd, +// WORD32 ht, +// WORD32 wd ) + +//**************Variables Vs Registers***************************************** +// x0 => *pu1_src +// x1 => *pu1_dst +// w2 => src_strd +// w3 => dst_strd +// w4 => ht +// w5 => wd + +.text +.p2align 2 + +.include "ih264_neon_macros.s" + + + + .global ih264_inter_pred_luma_horz_av8 + +ih264_inter_pred_luma_horz_av8: + + + + + // STMFD sp!, {x4-x12, x14} //store register values to stack + push_v_regs + stp x19, x20, [sp, #-16]! + sxtw x2, w2 + sxtw x3, w3 + sxtw x4, w4 + sxtw x5, w5 + sub x0, x0, #2 //pu1_src-2 + sub x14, x4, #16 + movi v0.8b, #5 //filter coeff + subs x12, x5, #8 //if wd=8 branch to loop_8 + movi v1.8b, #20 //filter coeff + beq loop_8 + + subs x12, x5, #4 //if wd=4 branch to loop_4 + beq loop_4 + +loop_16: //when wd=16 + //// Processing row0 and row1 + ld1 {v2.8b, v3.8b, v4.8b}, [x0], x2 //// Load row0 + add x14, x14, #1 //for checking loop + ext v31.8b, v2.8b , v3.8b, #5 ////extract a[5] (column1,row0) + ld1 {v5.8b, v6.8b, v7.8b}, [x0], x2 //// Load row1 + ext v30.8b, v3.8b , v4.8b, #5 ////extract a[5] (column2,row0) + uaddl v8.8h, v31.8b, v2.8b //// a0 + a5 (column1,row0) + ext v28.8b, v5.8b , v6.8b, #5 ////extract a[5] (column1,row1) + uaddl v10.8h, v30.8b, v3.8b //// a0 + a5 (column2,row0) + ext v27.8b, v6.8b , v7.8b, #5 ////extract a[5] (column2,row1) + uaddl v14.8h, v28.8b, v5.8b //// a0 + a5 (column1,row1) + ext v31.8b, v2.8b , v3.8b, #2 ////extract a[2] (column1,row0) + uaddl v16.8h, v27.8b, v6.8b //// a0 + a5 (column2,row1) + ext v30.8b, v3.8b , v4.8b, #2 ////extract a[2] (column2,row0) + umlal v8.8h, v31.8b, v1.8b //// a0 + a5 + 20a2 (column1,row0) + ext v28.8b, v5.8b , v6.8b, #2 ////extract a[2] (column1,row1) + umlal v10.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 (column2,row0) + ext v27.8b, v6.8b , v7.8b, #2 ////extract a[2] (column2,row1) + umlal v14.8h, v28.8b, v1.8b //// a0 + a5 + 20a2 (column1,row1) + ext v31.8b, v2.8b , v3.8b, #3 ////extract a[3] (column1,row0) + umlal v16.8h, v27.8b, v1.8b //// a0 + a5 + 20a2 (column2,row1) + ext v30.8b, v3.8b , v4.8b, #3 ////extract a[3] (column2,row0) + umlal v8.8h, v31.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row0) + ext v28.8b, v5.8b , v6.8b, #3 ////extract a[3] (column1,row1) + umlal v10.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column2,row0) + ext v27.8b, v6.8b , v7.8b, #3 ////extract a[3] (column2,row1) + umlal v14.8h, v28.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row1) + ext v31.8b, v2.8b , v3.8b, #1 ////extract a[1] (column1,row0) + umlal v16.8h, v27.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column2,row1) + ext v30.8b, v3.8b , v4.8b, #1 ////extract a[1] (column2,row0) + umlsl v8.8h, v31.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row0) + ext v28.8b, v5.8b , v6.8b, #1 ////extract a[1] (column1,row1) + umlsl v10.8h, v30.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column2,row0) + ext v27.8b, v6.8b , v7.8b, #1 ////extract a[1] (column2,row1) + umlsl v14.8h, v28.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row1) + ext v31.8b, v2.8b , v3.8b, #4 ////extract a[4] (column1,row0) + umlsl v16.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column2,row1) + ext v30.8b, v3.8b , v4.8b, #4 ////extract a[4] (column2,row0) + umlsl v8.8h, v31.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row0) + ext v28.8b, v5.8b , v6.8b, #4 ////extract a[4] (column1,row1) + umlsl v10.8h, v30.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2,row0) + ext v27.8b, v6.8b , v7.8b, #4 ////extract a[4] (column2,row1) + umlsl v14.8h, v28.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row1) + ld1 {v2.8b, v3.8b, v4.8b}, [x0], x2 //// Load row2 + umlsl v16.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2,row1) + + sqrshrun v20.8b, v8.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row0) + ld1 {v5.8b, v6.8b, v7.8b}, [x0], x2 //// Load row3 + sqrshrun v21.8b, v10.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column2,row0) + ext v31.8b, v2.8b , v3.8b, #5 ////extract a[5] (column1,row2) + st1 {v20.8b, v21.8b}, [x1], x3 ////Store dest row0 + sqrshrun v23.8b, v14.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row1) + ext v30.8b, v3.8b , v4.8b, #5 ////extract a[5] (column2,row2) + sqrshrun v24.8b, v16.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column2,row1) + + + +//// Processing row2 and row3 + ext v28.8b, v5.8b , v6.8b, #5 ////extract a[5] (column1,row3) + uaddl v8.8h, v31.8b, v2.8b //// a0 + a5 (column1,row2) + st1 {v23.8b, v24.8b}, [x1], x3 ////Store dest row1 + uaddl v10.8h, v30.8b, v3.8b //// a0 + a5 (column2,row2) + ext v27.8b, v6.8b , v7.8b, #5 ////extract a[5] (column2,row3) + uaddl v14.8h, v28.8b, v5.8b //// a0 + a5 (column1,row3) + ext v31.8b, v2.8b , v3.8b, #2 ////extract a[2] (column1,row2) + uaddl v16.8h, v27.8b, v6.8b //// a0 + a5 (column2,row3) + ext v30.8b, v3.8b , v4.8b, #2 ////extract a[2] (column2,row2) + umlal v8.8h, v31.8b, v1.8b //// a0 + a5 + 20a2 (column1,row2) + ext v27.8b, v6.8b , v7.8b, #2 ////extract a[2] (column2,row3) + umlal v10.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 (column2,row2) + ext v28.8b, v5.8b , v6.8b, #2 ////extract a[2] (column1,row3) + umlal v14.8h, v28.8b, v1.8b //// a0 + a5 + 20a2 (column1,row3) + ext v31.8b, v2.8b , v3.8b, #3 ////extract a[3] (column1,row2) + umlal v16.8h, v27.8b, v1.8b //// a0 + a5 + 20a2 (column2,row3) + ext v30.8b, v3.8b , v4.8b, #3 ////extract a[3] (column2,row2) + umlal v8.8h, v31.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row2) + ext v28.8b, v5.8b , v6.8b, #3 ////extract a[3] (column1,row3) + umlal v10.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column2,row2) + ext v27.8b, v6.8b , v7.8b, #3 ////extract a[3] (column2,row3) + umlal v14.8h, v28.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row3) + ext v31.8b, v2.8b , v3.8b, #1 ////extract a[1] (column1,row2) + umlal v16.8h, v27.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column2,row3) + ext v30.8b, v3.8b , v4.8b, #1 ////extract a[1] (column2,row2) + umlsl v8.8h, v31.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row2) + ext v28.8b, v5.8b , v6.8b, #1 ////extract a[1] (column1,row3) + umlsl v10.8h, v30.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column2,row2) + ext v27.8b, v6.8b , v7.8b, #1 ////extract a[1] (column2,row3) + umlsl v14.8h, v28.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row3) + ext v31.8b, v2.8b , v3.8b, #4 ////extract a[4] (column1,row2) + umlsl v16.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column2,row3) + ext v30.8b, v3.8b , v4.8b, #4 ////extract a[4] (column2,row2) + umlsl v8.8h, v31.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row2) + ext v28.8b, v5.8b , v6.8b, #4 ////extract a[4] (column1,row3) + umlsl v10.8h, v30.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2,row2) + ext v27.8b, v6.8b , v7.8b, #4 ////extract a[4] (column2,row3) + umlsl v14.8h, v28.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row3) + ld1 {v2.8b, v3.8b, v4.8b}, [x0], x2 //// Load row4 + umlsl v16.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2,row3) + + sqrshrun v20.8b, v8.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row2) + ld1 {v5.8b, v6.8b, v7.8b}, [x0], x2 //// Load row5 + sqrshrun v21.8b, v10.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column2,row2) + ext v31.8b, v2.8b , v3.8b, #5 ////extract a[5] (column1,row4) + st1 {v20.8b, v21.8b}, [x1], x3 ////Store dest row2 + sqrshrun v23.8b, v14.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row3) + ext v30.8b, v3.8b , v4.8b, #5 ////extract a[5] (column2,row4) + sqrshrun v24.8b, v16.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column2,row3) + + +//// Processing row4 and row5 + ext v28.8b, v5.8b , v6.8b, #5 ////extract a[5] (column1,row5) + uaddl v8.8h, v31.8b, v2.8b //// a0 + a5 (column1,row4) + st1 {v23.8b, v24.8b}, [x1], x3 ////Store dest row3 + uaddl v10.8h, v30.8b, v3.8b //// a0 + a5 (column2,row4) + ext v27.8b, v6.8b , v7.8b, #5 ////extract a[5] (column2,row5) + uaddl v14.8h, v28.8b, v5.8b //// a0 + a5 (column1,row5) + ext v31.8b, v2.8b , v3.8b, #2 ////extract a[2] (column1,row4) + uaddl v16.8h, v27.8b, v6.8b //// a0 + a5 (column2,row5) + ext v30.8b, v3.8b , v4.8b, #2 ////extract a[2] (column2,row4) + umlal v8.8h, v31.8b, v1.8b //// a0 + a5 + 20a2 (column1,row4) + ext v27.8b, v6.8b , v7.8b, #2 ////extract a[2] (column2,row5) + umlal v10.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 (column2,row4) + ext v28.8b, v5.8b , v6.8b, #2 ////extract a[2] (column1,row5) + umlal v14.8h, v28.8b, v1.8b //// a0 + a5 + 20a2 (column1,row5) + ext v31.8b, v2.8b , v3.8b, #3 ////extract a[3] (column1,row4) + umlal v16.8h, v27.8b, v1.8b //// a0 + a5 + 20a2 (column2,row5) + ext v30.8b, v3.8b , v4.8b, #3 ////extract a[3] (column2,row4) + umlal v8.8h, v31.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row4) + ext v28.8b, v5.8b , v6.8b, #3 ////extract a[3] (column1,row5) + umlal v10.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column2,row4) + ext v27.8b, v6.8b , v7.8b, #3 ////extract a[3] (column2,row5) + umlal v14.8h, v28.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row5) + ext v31.8b, v2.8b , v3.8b, #1 ////extract a[1] (column1,row4) + umlal v16.8h, v27.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column2,row5) + ext v30.8b, v3.8b , v4.8b, #1 ////extract a[1] (column2,row4) + umlsl v8.8h, v31.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row4) + ext v28.8b, v5.8b , v6.8b, #1 ////extract a[1] (column1,row5) + umlsl v10.8h, v30.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column2,row4) + ext v27.8b, v6.8b , v7.8b, #1 ////extract a[1] (column2,row5) + umlsl v14.8h, v28.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row4) + ext v31.8b, v2.8b , v3.8b, #4 ////extract a[4] (column1,row4) + umlsl v16.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column2,row5) + ext v30.8b, v3.8b , v4.8b, #4 ////extract a[4] (column2,row4) + umlsl v8.8h, v31.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row4) + ext v28.8b, v5.8b , v6.8b, #4 ////extract a[4] (column1,row5) + umlsl v10.8h, v30.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2,row4) + ext v27.8b, v6.8b , v7.8b, #4 ////extract a[4] (column2,row5) + umlsl v14.8h, v28.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row5) + ld1 {v2.8b, v3.8b, v4.8b}, [x0], x2 //// Load row6 + umlsl v16.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2,row5) + + sqrshrun v20.8b, v8.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row4) + ld1 {v5.8b, v6.8b, v7.8b}, [x0], x2 //// Load row7 + sqrshrun v21.8b, v10.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column2,row4) + ext v31.8b, v2.8b , v3.8b, #5 ////extract a[5] (column1,row6) + st1 {v20.8b, v21.8b}, [x1], x3 ////Store dest row2 + sqrshrun v23.8b, v14.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row5) + ext v30.8b, v3.8b , v4.8b, #5 ////extract a[5] (column2,row6) + sqrshrun v24.8b, v16.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column2,row5) + + + + //// Processing row6 and row7 + + ext v28.8b, v5.8b , v6.8b, #5 ////extract a[5] (column1,row7) + uaddl v8.8h, v31.8b, v2.8b //// a0 + a5 (column1,row6) + st1 {v23.8b, v24.8b}, [x1], x3 ////Store dest row5 + uaddl v10.8h, v30.8b, v3.8b //// a0 + a5 (column2,row6) + ext v27.8b, v6.8b , v7.8b, #5 ////extract a[5] (column2,row7) + uaddl v14.8h, v28.8b, v5.8b //// a0 + a5 (column1,row7) + ext v31.8b, v2.8b , v3.8b, #2 ////extract a[2] (column1,row6) + uaddl v16.8h, v27.8b, v6.8b //// a0 + a5 (column2,row7) + ext v30.8b, v3.8b , v4.8b, #2 ////extract a[2] (column2,row6) + umlal v8.8h, v31.8b, v1.8b //// a0 + a5 + 20a2 (column1,row6) + ext v27.8b, v6.8b , v7.8b, #2 ////extract a[2] (column2,row7) + umlal v10.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 (column2,row6) + ext v28.8b, v5.8b , v6.8b, #2 ////extract a[2] (column1,row7) + umlal v14.8h, v28.8b, v1.8b //// a0 + a5 + 20a2 (column1,row7) + ext v31.8b, v2.8b , v3.8b, #3 ////extract a[3] (column1,row6) + umlal v16.8h, v27.8b, v1.8b //// a0 + a5 + 20a2 (column2,row7) + ext v30.8b, v3.8b , v4.8b, #3 ////extract a[3] (column2,row6) + umlal v8.8h, v31.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row6) + ext v28.8b, v5.8b , v6.8b, #3 ////extract a[3] (column1,row7) + umlal v10.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column2,row6) + ext v27.8b, v6.8b , v7.8b, #3 ////extract a[3] (column2,row7) + umlal v14.8h, v28.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row7) + ext v31.8b, v2.8b , v3.8b, #1 ////extract a[1] (column1,row6) + umlal v16.8h, v27.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column2,row7) + ext v30.8b, v3.8b , v4.8b, #1 ////extract a[1] (column2,row6) + umlsl v8.8h, v31.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row6) + ext v28.8b, v5.8b , v6.8b, #1 ////extract a[1] (column1,row7) + umlsl v10.8h, v30.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column2,row6) + ext v27.8b, v6.8b , v7.8b, #1 ////extract a[1] (column2,row7) + umlsl v14.8h, v28.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row6) + ext v31.8b, v2.8b , v3.8b, #4 ////extract a[4] (column1,row6) + umlsl v16.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column2,row7) + ext v30.8b, v3.8b , v4.8b, #4 ////extract a[4] (column2,row6) + umlsl v8.8h, v31.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row6) + ext v28.8b, v5.8b , v6.8b, #4 ////extract a[4] (column1,row7) + umlsl v10.8h, v30.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2,row6) + ext v27.8b, v6.8b , v7.8b, #4 ////extract a[4] (column2,row6) + + sqrshrun v20.8b, v8.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row6) + umlsl v14.8h, v28.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row7) + sqrshrun v21.8b, v10.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column2,row6) + umlsl v16.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2,row7) + sqrshrun v23.8b, v14.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row7) + st1 {v20.8b, v21.8b}, [x1], x3 ////Store dest row6 + sqrshrun v24.8b, v16.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column2,row7) + subs x12, x14, #1 // if height==16 - looping + st1 {v23.8b, v24.8b}, [x1], x3 ////Store dest row7 + + + + beq loop_16 + b end_func + + + +loop_8: +//// Processing row0 and row1 + + + ld1 {v5.8b, v6.8b}, [x0], x2 //// Load row1 + add x14, x14, #1 //for checking loop + ext v28.8b, v5.8b , v6.8b, #5 ////extract a[5] (column1,row1) + ld1 {v2.8b, v3.8b}, [x0], x2 //// Load row0 + ext v25.8b, v5.8b , v6.8b, #2 ////extract a[2] (column1,row1) + ext v31.8b, v2.8b , v3.8b, #5 ////extract a[5] (column1,row0) + ext v24.8b, v5.8b , v6.8b, #3 ////extract a[3] (column1,row1) + ext v23.8b, v5.8b , v6.8b, #1 ////extract a[1] (column1,row1) + ext v22.8b, v5.8b , v6.8b, #4 ////extract a[4] (column1,row1) + uaddl v14.8h, v28.8b, v5.8b //// a0 + a5 (column1,row1) + ext v29.8b, v2.8b , v3.8b, #3 ////extract a[3] (column1,row0) + umlal v14.8h, v25.8b, v1.8b //// a0 + a5 + 20a2 (column1,row1) + umlal v14.8h, v24.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row1) + umlsl v14.8h, v23.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row1) + umlsl v14.8h, v22.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row1) + ext v30.8b, v2.8b , v3.8b, #2 ////extract a[2] (column1,row0) + uaddl v8.8h, v31.8b, v2.8b //// a0 + a5 (column1,row0) + ext v27.8b, v2.8b , v3.8b, #1 ////extract a[1] (column1,row0) + ext v26.8b, v2.8b , v3.8b, #4 ////extract a[4] (column1,row0) + ld1 {v2.8b, v3.8b}, [x0], x2 //// Load row2 + umlal v8.8h, v29.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row0) + umlal v8.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 (column1,row0) + umlsl v8.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row0) + umlsl v8.8h, v26.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row0) + ld1 {v5.8b, v6.8b}, [x0], x2 //// Load row3 + sqrshrun v23.8b, v14.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row0) + + //// Processing row2 and row3 + ext v28.8b, v5.8b , v6.8b, #5 ////extract a[5] (column1,row3) + ext v25.8b, v5.8b , v6.8b, #2 ////extract a[2] (column1,row3) + ext v31.8b, v2.8b , v3.8b, #5 ////extract a[5] (column1,row2) + uaddl v14.8h, v28.8b, v5.8b //// a0 + a5 (column1,row3) + st1 {v23.8b}, [x1], x3 ////Store dest row0 + ext v24.8b, v5.8b , v6.8b, #3 ////extract a[3] (column1,row2) + ext v23.8b, v5.8b , v6.8b, #1 ////extract a[1] (column1,row3) + sqrshrun v20.8b, v8.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row1) + ext v22.8b, v5.8b , v6.8b, #4 ////extract a[4] (column1,row3) + ext v29.8b, v2.8b , v3.8b, #3 ////extract a[3] (column1,row2) + umlal v14.8h, v25.8b, v1.8b //// a0 + a5 + 20a2 (column1,row3) + umlal v14.8h, v24.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row3) + umlsl v14.8h, v23.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row3) + umlsl v14.8h, v22.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row3) + st1 {v20.8b}, [x1], x3 ////Store dest row1 + uaddl v8.8h, v31.8b, v2.8b //// a0 + a5 (column1,row2) + ext v30.8b, v2.8b , v3.8b, #2 ////extract a[2] (column1,row2) + ext v27.8b, v2.8b , v3.8b, #1 ////extract a[1] (column1,row2) + ext v26.8b, v2.8b , v3.8b, #4 ////extract a[4] (column1,row2) + ld1 {v2.8b, v3.8b}, [x0], x2 //// Load row4 + umlal v8.8h, v29.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row2) + umlal v8.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 (column1,row2) + umlsl v8.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row2) + umlsl v8.8h, v26.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row2) + ld1 {v5.8b, v6.8b}, [x0], x2 //// Load row3 + subs x9, x4, #4 + sqrshrun v23.8b, v14.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row3) + ext v28.8b, v5.8b , v6.8b, #5 ////extract a[5] (column1,row5) + ext v25.8b, v5.8b , v6.8b, #2 ////extract a[2] (column1,row5) + ext v31.8b, v2.8b , v3.8b, #5 ////extract a[5] (column1,row4) + uaddl v14.8h, v28.8b, v5.8b //// a0 + a5 (column1,row5) + ext v24.8b, v5.8b , v6.8b, #3 ////extract a[3] (column1,row5) + sqrshrun v20.8b, v8.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row2) + ext v22.8b, v5.8b , v6.8b, #4 ////extract a[4] (column1,row5) + ext v29.8b, v2.8b , v3.8b, #3 ////extract a[3] (column1,row4) + st1 {v20.8b}, [x1], x3 ////Store dest row2 + ext v30.8b, v2.8b , v3.8b, #2 ////extract a[2] (column1,row4) + uaddl v8.8h, v31.8b, v2.8b //// a0 + a5 (column1,row4) + st1 {v23.8b}, [x1], x3 ////Store dest row3 + beq end_func // Branch if height==4 + +//// Processing row4 and row5 + ext v23.8b, v5.8b , v6.8b, #1 ////extract a[1] (column1,row5) + umlal v14.8h, v25.8b, v1.8b //// a0 + a5 + 20a2 (column1,row5) + umlal v14.8h, v24.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row5) + umlsl v14.8h, v23.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row5) + umlsl v14.8h, v22.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row5) + ext v27.8b, v2.8b , v3.8b, #1 ////extract a[1] (column1,row4) + ext v26.8b, v2.8b , v3.8b, #4 ////extract a[4] (column1,row4) + ld1 {v2.8b, v3.8b}, [x0], x2 //// Load row6 + umlal v8.8h, v29.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row4) + umlal v8.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 (column1,row4) + umlsl v8.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row4) + umlsl v8.8h, v26.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row4) + sqrshrun v23.8b, v14.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row5) + ld1 {v5.8b, v6.8b}, [x0], x2 //// Load row7 + ext v31.8b, v2.8b , v3.8b, #5 ////extract a[5] (column1,row6) + ext v28.8b, v5.8b , v6.8b, #5 ////extract a[5] (column1,row7) + ext v25.8b, v5.8b , v6.8b, #2 ////extract a[2] (column1,row7) + uaddl v14.8h, v28.8b, v5.8b //// a0 + a5 (column1,row7) + ext v24.8b, v5.8b , v6.8b, #3 ////extract a[3] (column1,row7) + ext v22.8b, v5.8b , v6.8b, #4 ////extract a[4] (column1,row7) + sqrshrun v20.8b, v8.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row4) + ext v29.8b, v2.8b , v3.8b, #3 ////extract a[3] (column1,row6) + ext v30.8b, v2.8b , v3.8b, #2 ////extract a[2] (column1,row6) + st1 {v20.8b}, [x1], x3 ////Store dest row4 + ext v27.8b, v2.8b , v3.8b, #1 ////extract a[1] (column1,row6) + uaddl v8.8h, v31.8b, v2.8b //// a0 + a5 (column1,row6) + ext v26.8b, v2.8b , v3.8b, #4 ////extract a[4] (column1,row6) + umlal v8.8h, v29.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row6) + umlal v8.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 (column1,row6) + umlsl v8.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row6) + umlsl v8.8h, v26.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row6) + //// Processing row6 and row7 + st1 {v23.8b}, [x1], x3 ////Store dest row5 + ext v23.8b, v5.8b , v6.8b, #1 ////extract a[1] (column1,row7) + umlal v14.8h, v25.8b, v1.8b //// a0 + a5 + 20a2 (column1,row7) + umlal v14.8h, v24.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row7) + umlsl v14.8h, v23.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row7) + umlsl v14.8h, v22.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row7) + sqrshrun v20.8b, v8.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row6) + subs x12, x14, #1 + st1 {v20.8b}, [x1], x3 ////Store dest row6 + sqrshrun v23.8b, v14.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row7) + st1 {v23.8b}, [x1], x3 ////Store dest row7 + + beq loop_8 //looping if height ==16 + + b end_func +loop_4: + ld1 {v5.8b, v6.8b}, [x0], x2 //// Load row1 + ext v28.8b, v5.8b , v6.8b, #5 ////extract a[5] (column1,row1) + ld1 {v2.8b, v3.8b}, [x0], x2 //// Load row0 + ext v25.8b, v5.8b , v6.8b, #2 ////extract a[2] (column1,row1) + ext v31.8b, v2.8b , v3.8b, #5 ////extract a[5] (column1,row0) + uaddl v14.8h, v28.8b, v5.8b //// a0 + a5 (column1,row1) + ext v24.8b, v5.8b , v6.8b, #3 ////extract a[3] (column1,row1) + ext v23.8b, v5.8b , v6.8b, #1 ////extract a[1] (column1,row1) + ext v22.8b, v5.8b , v6.8b, #4 ////extract a[4] (column1,row1) + ext v29.8b, v2.8b , v3.8b, #3 ////extract a[3] (column1,row0) + umlal v14.8h, v25.8b, v1.8b //// a0 + a5 + 20a2 (column1,row1) + umlal v14.8h, v24.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row1) + umlsl v14.8h, v23.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row1) + umlsl v14.8h, v22.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row1) + uaddl v8.8h, v31.8b, v2.8b //// a0 + a5 (column1,row0) + ext v30.8b, v2.8b , v3.8b, #2 ////extract a[2] (column1,row0) + ext v27.8b, v2.8b , v3.8b, #1 ////extract a[1] (column1,row0) + ext v26.8b, v2.8b , v3.8b, #4 ////extract a[4] (column1,row0) + ld1 {v2.8b, v3.8b}, [x0], x2 //// Load row2 + umlal v8.8h, v29.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row0) + umlal v8.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 (column1,row0) + umlsl v8.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row0) + umlsl v8.8h, v26.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row0) + ld1 {v5.8b, v6.8b}, [x0], x2 //// Load row3 + ext v28.8b, v5.8b , v6.8b, #5 ////extract a[5] (column1,row3) + ext v25.8b, v5.8b , v6.8b, #2 ////extract a[2] (column1,row3) + sqrshrun v23.8b, v14.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row0) + ext v31.8b, v2.8b , v3.8b, #5 ////extract a[5] (column1,row2) + ext v24.8b, v5.8b , v6.8b, #3 ////extract a[3] (column1,row2) + st1 {v23.s}[0], [x1], x3 ////Store dest row0 + ext v23.8b, v5.8b , v6.8b, #1 ////extract a[1] (column1,row3) + ext v22.8b, v5.8b , v6.8b, #4 ////extract a[4] (column1,row3) + ext v29.8b, v2.8b , v3.8b, #3 ////extract a[3] (column1,row2) + sqrshrun v20.8b, v8.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row1) + ext v30.8b, v2.8b , v3.8b, #2 ////extract a[2] (column1,row2) + ext v27.8b, v2.8b , v3.8b, #1 ////extract a[1] (column1,row2) + + //// Processing row2 and row3 + st1 {v20.s}[0], [x1], x3 ////Store dest row1 + uaddl v14.8h, v28.8b, v5.8b //// a0 + a5 (column1,row3) + ext v26.8b, v2.8b , v3.8b, #4 ////extract a[4] (column1,row2) + umlal v14.8h, v25.8b, v1.8b //// a0 + a5 + 20a2 (column1,row3) + umlal v14.8h, v24.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row3) + umlsl v14.8h, v23.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row3) + umlsl v14.8h, v22.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row3) + uaddl v8.8h, v31.8b, v2.8b //// a0 + a5 (column1,row2) + umlal v8.8h, v29.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row2) + umlal v8.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 (column1,row2) + umlsl v8.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row2) + umlsl v8.8h, v26.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row2) + sqrshrun v23.8b, v14.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row3) + sqrshrun v20.8b, v8.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row2) + st1 {v20.s}[0], [x1], x3 ////Store dest row2 + subs x4, x4, #8 // Loop if height =8 + st1 {v23.s}[0], [x1], x3 ////Store dest row3 + beq loop_4 + +end_func: + // LDMFD sp!,{x4-x12,PC} //Restoring registers from stack + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + + diff --git a/dependencies/ih264d/common/armv8/ih264_inter_pred_filters_luma_vert_av8.s b/dependencies/ih264d/common/armv8/ih264_inter_pred_filters_luma_vert_av8.s new file mode 100644 index 00000000..711d73e8 --- /dev/null +++ b/dependencies/ih264d/common/armv8/ih264_inter_pred_filters_luma_vert_av8.s @@ -0,0 +1,456 @@ +//****************************************************************************** +//* +//* Copyright (C) 2015 The Android Open Source Project +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at: +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. +//* +//***************************************************************************** +//* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +//*/ +///** +//****************************************************************************** +//* @file +//* ih264_inter_pred_luma_vert_av8.s +//* +//* @brief +//* Contains function definitions for inter prediction interpolation. +//* +//* @author +//* Ittiam +//* +//* @par List of Functions: +//* +//* - ih264_inter_pred_luma_vert_av8() +//* +//* @remarks +//* None +//* +//******************************************************************************* +//*/ + +///* All the functions here are replicated from ih264_inter_pred_filters.c +// + +///** +///** +///** +// ******************************************************************************* +// * +// * @brief +// * Interprediction luma filter for vertical input +// * +// * @par Description: +// * Applies a 6 tap vertcal filter.The output is clipped to 8 bits +// * sec 8.4.2.2.1 titled "Luma sample interpolation process" +// * +// * @param[in] pu1_src +// * UWORD8 pointer to the source +// * +// * @param[out] pu1_dst +// * UWORD8 pointer to the destination +// * +// * @param[in] src_strd +// * integer source stride +// * +// * @param[in] dst_strd +// * integer destination stride +// * +// * @param[in] ht +// * integer height of the array +// * +// * @param[in] wd +// * integer width of the array +// * +// * @returns +// * +// * @remarks +// * None +// * +// ******************************************************************************* + +//void ih264_inter_pred_luma_vert ( +// UWORD8 *pu1_src, +// UWORD8 *pu1_dst, +// WORD32 src_strd, +// WORD32 dst_strd, +// WORD32 ht, +// WORD32 wd ) + +//**************Variables Vs Registers***************************************** +// x0 => *pu1_src +// x1 => *pu1_dst +// w2 => src_strd +// w3 => dst_strd +// w4 => ht +// w5 => wd + +.text +.p2align 2 +.include "ih264_neon_macros.s" + + + + + .global ih264_inter_pred_luma_vert_av8 + +ih264_inter_pred_luma_vert_av8: + + // STMFD sp!, {x4-x12, x14} //store register values to stack + push_v_regs + stp x19, x20, [sp, #-16]! + sxtw x2, w2 + sxtw x3, w3 + sxtw x4, w4 + sxtw x5, w5 + + sub x0, x0, x2, lsl #1 //pu1_src-2*src_strd + + sub x14, x4, #16 + movi v22.8h, #20 // Filter coeff 0x14 into Q11 + + subs x12, x5, #8 //if wd=8 branch to loop_8 + movi v24.8h, #5 // Filter coeff 0x4 into Q12 + beq loop_8_start + + subs x12, x5, #4 //if wd=4 branch to loop_4 + beq loop_4_start + + + ld1 {v0.2s, v1.2s}, [x0], x2 // Vector load from src[0_0] + ld1 {v2.2s, v3.2s}, [x0], x2 // Vector load from src[1_0] + ld1 {v4.2s, v5.2s}, [x0], x2 // Vector load from src[2_0] + ld1 {v6.2s, v7.2s}, [x0], x2 // Vector load from src[3_0] + add x14, x14, #1 //for checking loop + ld1 {v8.2s, v9.2s}, [x0], x2 // Vector load from src[4_0] + uaddl v12.8h, v4.8b, v6.8b // temp1 = src[2_0] + src[3_0] + ld1 {v10.2s, v11.2s}, [x0], x2 // Vector load from src[5_0] + +loop_16: //when wd=16 + + uaddl v14.8h, v0.8b, v10.8b // temp = src[0_0] + src[5_0] + uaddl v16.8h, v2.8b, v8.8b // temp2 = src[1_0] + src[4_0] + mla v14.8h, v12.8h, v22.8h // temp += temp1 * 20 + uaddl v20.8h, v1.8b, v11.8b // temp4 = src[0_8] + src[5_8] + uaddl v18.8h, v5.8b, v7.8b // temp3 = src[2_8] + src[3_8] + mla v20.8h, v18.8h , v22.8h // temp4 += temp3 * 20 + ld1 {v0.2s, v1.2s}, [x0], x2 + uaddl v26.8h, v3.8b, v9.8b // temp5 = src[1_8] + src[4_8] + uaddl v12.8h, v6.8b, v8.8b + mls v14.8h, v16.8h , v24.8h // temp -= temp2 * 5 + uaddl v16.8h, v2.8b, v0.8b + uaddl v18.8h, v4.8b, v10.8b + mla v16.8h, v12.8h , v22.8h + mls v20.8h, v26.8h , v24.8h // temp4 -= temp5 * 5 + uaddl v26.8h, v5.8b, v11.8b + uaddl v12.8h, v7.8b, v9.8b + sqrshrun v30.8b, v14.8h, #5 // dst[0_0] = CLIP_U8((temp +16) >> 5) + uaddl v14.8h, v3.8b, v1.8b + ld1 {v2.2s, v3.2s}, [x0], x2 + mla v14.8h, v12.8h , v22.8h + mls v16.8h, v18.8h , v24.8h + sqrshrun v31.8b, v20.8h, #5 // dst[0_8] = CLIP_U8((temp4 +16) >> 5) + uaddl v18.8h, v4.8b, v2.8b + uaddl v12.8h, v8.8b, v10.8b + + st1 {v30.2s, v31.2s}, [x1], x3 // Vector store to dst[0_0] + mla v18.8h, v12.8h , v22.8h + uaddl v20.8h, v6.8b, v0.8b + mls v14.8h, v26.8h , v24.8h + sqrshrun v30.8b, v16.8h, #5 + uaddl v12.8h, v9.8b, v11.8b + uaddl v16.8h, v5.8b, v3.8b + uaddl v26.8h, v7.8b, v1.8b + mla v16.8h, v12.8h , v22.8h + mls v18.8h, v20.8h , v24.8h + ld1 {v4.2s, v5.2s}, [x0], x2 + + sqrshrun v31.8b, v14.8h, #5 + uaddl v12.8h, v10.8b, v0.8b + uaddl v14.8h, v6.8b, v4.8b + uaddl v20.8h, v8.8b, v2.8b + mla v14.8h, v12.8h , v22.8h + mls v16.8h, v26.8h , v24.8h + st1 {v30.2s, v31.2s}, [x1], x3 //store row 1 + sqrshrun v30.8b, v18.8h, #5 + uaddl v18.8h, v7.8b, v5.8b + uaddl v12.8h, v11.8b, v1.8b + mla v18.8h, v12.8h , v22.8h + uaddl v26.8h, v9.8b, v3.8b + mls v14.8h, v20.8h , v24.8h + ld1 {v6.2s, v7.2s}, [x0], x2 + sqrshrun v31.8b, v16.8h, #5 + mls v18.8h, v26.8h , v24.8h + uaddl v12.8h, v0.8b, v2.8b // temp1 = src[2_0] + src[3_0] + st1 {v30.2s, v31.2s}, [x1], x3 //store row 2 + uaddl v16.8h, v10.8b, v4.8b // temp2 = src[1_0] + src[4_0] + uaddl v20.8h, v9.8b, v7.8b // temp4 = src[0_8] + src[5_8] + sqrshrun v30.8b, v14.8h, #5 + uaddl v26.8h, v5.8b, v11.8b // temp5 = src[1_8] + src[4_8] + uaddl v14.8h, v8.8b, v6.8b // temp = src[0_0] + src[5_0] + sqrshrun v31.8b, v18.8h, #5 + mla v14.8h, v12.8h , v22.8h // temp += temp1 * 20 + uaddl v18.8h, v1.8b, v3.8b // temp3 = src[2_8] + src[3_8] + st1 {v30.2s, v31.2s}, [x1], x3 //store row 3 + // 4 rows processed + mla v20.8h, v18.8h , v22.8h // temp4 += temp3 * 20 + ld1 {v8.2s, v9.2s}, [x0], x2 + uaddl v12.8h, v2.8b, v4.8b + uaddl v18.8h, v3.8b, v5.8b + mls v14.8h, v16.8h , v24.8h // temp -= temp2 * 5 + uaddl v28.8h, v9.8b, v11.8b + uaddl v16.8h, v6.8b, v0.8b + mla v28.8h, v18.8h , v22.8h // temp4 += temp3 * 20 + mls v20.8h, v26.8h , v24.8h // temp4 -= temp5 * 5 + uaddl v26.8h, v1.8b, v7.8b + uaddl v18.8h, v5.8b, v7.8b + sqrshrun v30.8b, v14.8h, #5 // dst[0_0] = CLIP_U8((temp +16) >> 5) + uaddl v14.8h, v8.8b, v10.8b + + sqrshrun v31.8b, v20.8h, #5 // dst[0_8] = CLIP_U8((temp4 +16) >> 5) + ld1 {v10.2s, v11.2s}, [x0], x2 + mls v28.8h, v26.8h , v24.8h // temp4 -= temp5 * 5 + st1 {v30.2s, v31.2s}, [x1], x3 // store row 4 + mla v14.8h, v12.8h , v22.8h // temp += temp1 * 20 + uaddl v20.8h, v11.8b, v1.8b + uaddl v26.8h, v3.8b, v9.8b + mla v20.8h, v18.8h , v22.8h // temp4 += temp3 * 20 + uaddl v12.8h, v6.8b, v4.8b + uaddl v18.8h, v7.8b, v9.8b + sqrshrun v31.8b, v28.8h, #5 // dst[0_8] = CLIP_U8((temp4 +16) >> 5) + mls v14.8h, v16.8h , v24.8h // temp -= temp2 * 5 + uaddl v16.8h, v8.8b, v2.8b + sqrshrun v30.8b, v14.8h, #5 // dst[0_0] = CLIP_U8((temp +16) >> 5) + mls v20.8h, v26.8h , v24.8h // temp4 -= temp5 * 5 + uaddl v14.8h, v10.8b, v0.8b + st1 {v30.2s, v31.2s}, [x1], x3 // store row 5 + mla v14.8h, v12.8h , v22.8h // temp += temp1 * 20 + ld1 {v0.2s, v1.2s}, [x0], x2 + uaddl v26.8h, v5.8b, v11.8b + uaddl v12.8h, v8.8b, v6.8b + uaddl v28.8h, v0.8b, v2.8b + sqrshrun v31.8b, v20.8h, #5 // dst[0_8] = CLIP_U8((temp4 +16) >> 5) + mla v28.8h, v12.8h , v22.8h // temp += temp1 * 20 + uaddl v20.8h, v1.8b, v3.8b + mls v14.8h, v16.8h , v24.8h // temp -= temp2 * 5 + mla v20.8h, v18.8h , v22.8h // temp4 += temp3 * 20 + uaddl v16.8h, v10.8b, v4.8b + sqrshrun v30.8b, v14.8h, #5 // dst[0_0] = CLIP_U8((temp +16) >> 5) + mov v2.8b, v6.8b + mov v3.8b, v7.8b + mls v28.8h, v16.8h , v24.8h // temp -= temp2 * 5 + st1 {v30.2s, v31.2s}, [x1], x3 // store row 6 + sqrshrun v30.8b, v28.8h, #5 // dst[0_0] = CLIP_U8((temp +16) >> 5) + + swp v0.8b, v4.8b + swp v1.8b, v5.8b + + + + mls v20.8h, v26.8h , v24.8h // temp4 -= temp5 * 5 + mov v6.8b, v10.8b + mov v7.8b, v11.8b + subs x12, x14, #1 // if height==16 - looping + + swp v4.8b, v8.8b + swp v5.8b, v9.8b + + + sqrshrun v31.8b, v20.8h, #5 // dst[0_8] = CLIP_U8((temp4 +16) >> 5) + st1 {v30.2s, v31.2s}, [x1], x3 // store row 7 + bne end_func //if height =8 end function + add x14, x14, #1 //for checking loop + ld1 {v10.2s, v11.2s}, [x0], x2 + uaddl v12.8h, v4.8b, v6.8b // temp1 = src[2_0] + src[3_0] + + b loop_16 // looping if height =16 + +loop_8_start: +//// Processing row0 and row1 + + ld1 {v0.2s}, [x0], x2 // Vector load from src[0_0] + ld1 {v1.2s}, [x0], x2 // Vector load from src[1_0] + ld1 {v2.2s}, [x0], x2 // Vector load from src[2_0] + ld1 {v3.2s}, [x0], x2 // Vector load from src[3_0] + add x14, x14, #1 //for checking loop + ld1 {v4.2s}, [x0], x2 // Vector load from src[4_0] + ld1 {v5.2s}, [x0], x2 // Vector load from src[5_0] + +loop_8: + //for checking loop + uaddl v6.8h, v2.8b, v3.8b // temp1 = src[2_0] + src[3_0] + uaddl v8.8h, v0.8b, v5.8b // temp = src[0_0] + src[5_0] + uaddl v10.8h, v1.8b, v4.8b // temp2 = src[1_0] + src[4_0] + mla v8.8h, v6.8h , v22.8h // temp += temp1 * 20 + ld1 {v6.2s}, [x0], x2 + uaddl v14.8h, v3.8b, v4.8b + uaddl v16.8h, v1.8b, v6.8b + uaddl v18.8h, v2.8b, v5.8b + mls v8.8h, v10.8h , v24.8h // temp -= temp2 * 5 + mla v16.8h, v14.8h , v22.8h + ld1 {v7.2s}, [x0], x2 + uaddl v20.8h, v4.8b, v5.8b + uaddl v12.8h, v2.8b, v7.8b + uaddl v10.8h, v3.8b, v6.8b + mls v16.8h, v18.8h , v24.8h + sqrshrun v26.8b, v8.8h, #5 // dst[0_0] = CLIP_U8( (temp + 16) >> 5) + mla v12.8h, v20.8h , v22.8h + ld1 {v0.2s}, [x0], x2 + uaddl v14.8h, v5.8b, v6.8b + sqrshrun v27.8b, v16.8h, #5 + uaddl v20.8h, v3.8b, v0.8b + mls v12.8h, v10.8h , v24.8h + st1 {v26.2s}, [x1], x3 // Vector store to dst[0_0] + uaddl v18.8h, v4.8b, v7.8b + mla v20.8h, v14.8h , v22.8h + st1 {v27.2s}, [x1], x3 + sqrshrun v28.8b, v12.8h, #5 + st1 {v28.2s}, [x1], x3 + mls v20.8h, v18.8h , v24.8h + ld1 {v1.2s}, [x0], x2 + sqrshrun v29.8b, v20.8h, #5 + subs x9, x4, #4 + st1 {v29.2s}, [x1], x3 //store row 3 + + + beq end_func // Branch if height==4 + + + uaddl v14.8h, v6.8b, v7.8b // temp1 = src[2_0] + src[3_0] + uaddl v16.8h, v0.8b, v5.8b // temp = src[0_0] + src[5_0] + uaddl v18.8h, v1.8b, v4.8b // temp2 = src[1_0] + src[4_0] + mla v18.8h, v14.8h , v22.8h // temp += temp1 * 20 + ld1 {v2.2s}, [x0], x2 + mls v18.8h, v16.8h , v24.8h // temp -= temp2 * 5 + uaddl v8.8h, v0.8b, v7.8b + uaddl v10.8h, v1.8b, v6.8b + uaddl v12.8h, v2.8b, v5.8b + sqrshrun v26.8b, v18.8h, #5 + mla v12.8h, v8.8h , v22.8h + ld1 {v3.2s}, [x0], x2 + mls v12.8h, v10.8h , v24.8h + st1 {v26.2s}, [x1], x3 + sqrshrun v27.8b, v12.8h, #5 + st1 {v27.2s}, [x1], x3 + uaddl v14.8h, v0.8b, v1.8b // temp1 = src[2_0] + src[3_0] + uaddl v16.8h, v2.8b, v7.8b // temp = src[0_0] + src[5_0] + uaddl v18.8h, v3.8b, v6.8b // temp2 = src[1_0] + src[4_0] + mla v18.8h, v14.8h , v22.8h // temp += temp1 * 20 + ld1 {v4.2s}, [x0], x2 + mls v18.8h, v16.8h , v24.8h // temp -= temp2 * 5 + uaddl v8.8h, v2.8b, v1.8b + uaddl v10.8h, v3.8b, v0.8b + uaddl v12.8h, v4.8b, v7.8b + sqrshrun v26.8b, v18.8h, #5 + mla v12.8h, v8.8h , v22.8h + ld1 {v5.2s}, [x0], x2 + mls v12.8h, v10.8h , v24.8h + st1 {v26.2s}, [x1], x3 + sqrshrun v27.8b, v12.8h, #5 + subs x12, x14, #1 + st1 {v27.2s}, [x1], x3 + add x14, x14, #1 + beq loop_8 //looping if height ==16 + + b end_func + + +loop_4_start: +//// Processing row0 and row1 + + + ld1 {v0.s}[0], [x0], x2 // Vector load from src[0_0] + ld1 {v1.s}[0], [x0], x2 // Vector load from src[1_0] + ld1 {v2.s}[0], [x0], x2 // Vector load from src[2_0] + ld1 {v3.s}[0], [x0], x2 // Vector load from src[3_0] + ld1 {v4.s}[0], [x0], x2 // Vector load from src[4_0] + ld1 {v5.s}[0], [x0], x2 // Vector load from src[5_0] + + uaddl v6.8h, v2.8b, v3.8b // temp1 = src[2_0] + src[3_0] + uaddl v8.8h, v0.8b, v5.8b // temp = src[0_0] + src[5_0] + uaddl v10.8h, v1.8b, v4.8b // temp2 = src[1_0] + src[4_0] + mla v8.8h, v6.8h , v22.8h // temp += temp1 * 20 + ld1 {v6.2s}, [x0], x2 + uaddl v14.8h, v3.8b, v4.8b + uaddl v16.8h, v1.8b, v6.8b + uaddl v18.8h, v2.8b, v5.8b + mls v8.8h, v10.8h , v24.8h // temp -= temp2 * 5 + ld1 {v7.s}[0], [x0], x2 + mla v16.8h, v14.8h , v22.8h + uaddl v20.8h, v4.8b, v5.8b + uaddl v12.8h, v2.8b, v7.8b + uaddl v10.8h, v3.8b, v6.8b + mls v16.8h, v18.8h , v24.8h + sqrshrun v26.8b, v8.8h, #5 // dst[0_0] = CLIP_U8( (temp + 16) >> 5) + mla v12.8h, v20.8h , v22.8h + ld1 {v0.s}[0], [x0], x2 + uaddl v14.8h, v5.8b, v6.8b + sqrshrun v27.8b, v16.8h, #5 + uaddl v20.8h, v3.8b, v0.8b + mls v12.8h, v10.8h , v24.8h + st1 {v26.s}[0], [x1], x3 // Vector store to dst[0_0] + uaddl v18.8h, v4.8b, v7.8b + mla v20.8h, v14.8h , v22.8h + st1 {v27.s}[0], [x1], x3 + sqrshrun v28.8b, v12.8h, #5 + st1 {v28.s}[0], [x1], x3 + mls v20.8h, v18.8h , v24.8h + ld1 {v1.s}[0], [x0], x2 + sqrshrun v29.8b, v20.8h, #5 + st1 {v29.s}[0], [x1], x3 //store row 3 + + subs x9, x4, #4 + beq end_func // Branch if height==4 + + + uaddl v14.8h, v6.8b, v7.8b // temp1 = src[2_0] + src[3_0] + uaddl v16.8h, v0.8b, v5.8b // temp = src[0_0] + src[5_0] + uaddl v18.8h, v1.8b, v4.8b // temp2 = src[1_0] + src[4_0] + mla v18.8h, v14.8h , v22.8h // temp += temp1 * 20 + ld1 {v2.s}[0], [x0], x2 + mls v18.8h, v16.8h , v24.8h // temp -= temp2 * 5 + uaddl v8.8h, v0.8b, v7.8b + uaddl v10.8h, v1.8b, v6.8b + uaddl v12.8h, v2.8b, v5.8b + sqrshrun v26.8b, v18.8h, #5 + mla v12.8h, v8.8h , v22.8h + ld1 {v3.s}[0], [x0], x2 + mls v12.8h, v10.8h , v24.8h + st1 {v26.s}[0], [x1], x3 + sqrshrun v27.8b, v12.8h, #5 + st1 {v27.s}[0], [x1], x3 + uaddl v14.8h, v0.8b, v1.8b // temp1 = src[2_0] + src[3_0] + uaddl v16.8h, v2.8b, v7.8b // temp = src[0_0] + src[5_0] + uaddl v18.8h, v3.8b, v6.8b // temp2 = src[1_0] + src[4_0] + mla v18.8h, v14.8h , v22.8h // temp += temp1 * 20 + ld1 {v4.s}[0], [x0], x2 + mls v18.8h, v16.8h , v24.8h // temp -= temp2 * 5 + uaddl v8.8h, v2.8b, v1.8b + uaddl v10.8h, v3.8b, v0.8b + uaddl v12.8h, v4.8b, v7.8b + sqrshrun v26.8b, v18.8h, #5 + mla v12.8h, v8.8h , v22.8h + ld1 {v5.s}[0], [x0], x2 + mls v12.8h, v10.8h , v24.8h + st1 {v26.s}[0], [x1], x3 + sqrshrun v27.8b, v12.8h, #5 + st1 {v27.s}[0], [x1], x3 + + +end_func: + // LDMFD sp!,{x4-x12,PC} //Restoring registers from stack + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + + diff --git a/dependencies/ih264d/common/armv8/ih264_inter_pred_luma_copy_av8.s b/dependencies/ih264d/common/armv8/ih264_inter_pred_luma_copy_av8.s new file mode 100644 index 00000000..007df30a --- /dev/null +++ b/dependencies/ih264d/common/armv8/ih264_inter_pred_luma_copy_av8.s @@ -0,0 +1,273 @@ +//****************************************************************************** +//* +//* Copyright (C) 2015 The Android Open Source Project +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at: +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. +//* +//***************************************************************************** +//* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +//*/ +///** +///** +//******************************************************************************* +//* +//* @brief +//* Interprediction luma function for copy +//* +//* @par Description: +//* Copies the array of width 'wd' and height 'ht' from the location pointed +//* by 'src' to the location pointed by 'dst' +//* +//* @param[in] pu1_src +//* UWORD8 pointer to the source +//* +//* @param[out] pu1_dst +//* UWORD8 pointer to the destination +//* +//* @param[in] src_strd +//* integer source stride +//* +//* @param[in] dst_strd +//* integer destination stride +//* +//* +//* @param[in] ht +//* integer height of the array +//* +//* @param[in] wd +//* integer width of the array +//* +//* @returns +//* +//* @remarks +//* None +//* +//******************************************************************************* +//*/ +//void ih264_inter_pred_luma_copy ( +// UWORD8 *pu1_src, +// UWORD8 *pu1_dst, +// WORD32 src_strd, +// WORD32 dst_strd, +// WORD32 ht, +// WORD32 wd ) + +//**************Variables Vs Registers***************************************** +// x0 => *pu1_src +// x1 => *pu1_dst +// w2 => src_strd +// w3 => dst_strd +// w4 => ht +// w5 => wd + +.text +.p2align 2 +.include "ih264_neon_macros.s" + + + + .global ih264_inter_pred_luma_copy_av8 + +ih264_inter_pred_luma_copy_av8: + + push_v_regs + stp x19, x20, [sp, #-16]! + sxtw x2, w2 + sxtw x3, w3 + sxtw x4, w4 + sxtw x5, w5 + + mov x12, x5 + mov x7, x4 + cmp x7, #0 //checks ht == 0 + ble end_loops + tst x12, #15 //checks wd for multiples for 4 & 8 + beq core_loop_wd_16 + tst x12, #7 //checks wd for multiples for 4 & 8 + beq core_loop_wd_8 + sub x11, x12, #4 + +outer_loop_wd_4: + subs x4, x12, #0 //checks wd == 0 + ble end_inner_loop_wd_4 + +inner_loop_wd_4: + ld1 {v0.s}[0], [x0] //vld1_lane_u32((uint32_t *)pu1_src_tmp, src_tmp, 0) + add x5, x0, x2 //pu1_src_tmp += src_strd + add x6, x1, x3 //pu1_dst_tmp += dst_strd + st1 {v0.s}[0], [x1] //vst1_lane_u32((uint32_t *)pu1_dst_tmp, src_tmp, 0) + ld1 {v0.s}[0], [x5], x2 //vld1_lane_u32((uint32_t *)pu1_src_tmp, src_tmp, 0) + add x0, x0, #4 //pu1_src += 4 + st1 {v0.s}[0], [x6], x3 //vst1_lane_u32((uint32_t *)pu1_dst_tmp, src_tmp, 0) + ld1 {v0.s}[0], [x5], x2 //vld1_lane_u32((uint32_t *)pu1_src_tmp, src_tmp, 0) + subs x4, x4, #4 //(wd -4) + st1 {v0.s}[0], [x6], x3 //vst1_lane_u32((uint32_t *)pu1_dst_tmp, src_tmp, 0) + ld1 {v0.s}[0], [x5], x2 //vld1_lane_u32((uint32_t *)pu1_src_tmp, src_tmp, 0) + add x1, x1, #4 //pu1_dst += 4 + st1 {v0.s}[0], [x6], x3 //vst1_lane_u32((uint32_t *)pu1_dst_tmp, src_tmp, 0) + + bgt inner_loop_wd_4 + +end_inner_loop_wd_4: + subs x7, x7, #4 //ht - 4 + sub x0, x5, x11 //pu1_src = pu1_src_tmp + sub x1, x6, x11 //pu1_dst = pu1_dst_tmp + bgt outer_loop_wd_4 + +end_loops: + // LDMFD sp!,{x4-x12,x15} //Reload the registers from SP + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + +core_loop_wd_8: + sub x11, x12, #8 + +outer_loop_wd_8: + subs x4, x12, #0 //checks wd + ble end_inner_loop_wd_8 + +inner_loop_wd_8: + add x5, x0, x2 //pu1_src_tmp += src_strd + ld1 {v0.8b}, [x0], #8 //vld1_u8(pu1_src_tmp) + add x6, x1, x3 //pu1_dst_tmp += dst_strd + st1 {v0.8b}, [x1], #8 //vst1_u8(pu1_dst_tmp, tmp_src) + ld1 {v1.8b}, [x5], x2 //vld1_u8(pu1_src_tmp) + st1 {v1.8b}, [x6], x3 //vst1_u8(pu1_dst_tmp, tmp_src) + subs x4, x4, #8 //wd - 8(Loop condition) + ld1 {v2.8b}, [x5], x2 //vld1_u8(pu1_src_tmp) + st1 {v2.8b}, [x6], x3 //vst1_u8(pu1_dst_tmp, tmp_src) + ld1 {v3.8b}, [x5], x2 //vld1_u8(pu1_src_tmp) + st1 {v3.8b}, [x6], x3 //vst1_u8(pu1_dst_tmp, tmp_src) + bgt inner_loop_wd_8 + +end_inner_loop_wd_8: + subs x7, x7, #4 //ht -= 4 + sub x0, x5, x11 //pu1_src = pu1_src_tmp + sub x1, x6, x11 //pu1_dst = pu1_dst_tmp + bgt outer_loop_wd_8 + + // LDMFD sp!,{x4-x12,x15} //Reload the registers from SP + ldp x19, x20, [sp], #16 + pop_v_regs + ret + +core_loop_wd_16: + sub x11, x12, #16 + +outer_loop_wd_16: + subs x4, x12, #0 //checks wd + ble end_inner_loop_wd_16 + +inner_loop_wd_16: + add x5, x0, x2 //pu1_src_tmp += src_strd + ld1 { v0.16b}, [x0], #16 //vld1_u8(pu1_src_tmp) + add x6, x1, x3 //pu1_dst_tmp += dst_strd + st1 { v0.16b}, [x1], #16 //vst1_u8(pu1_dst_tmp, tmp_src) + ld1 { v2.16b}, [x5], x2 //vld1_u8(pu1_src_tmp) + st1 { v2.16b}, [x6], x3 //vst1_u8(pu1_dst_tmp, tmp_src) + subs x4, x4, #16 //wd - 8(Loop condition) + ld1 { v4.16b}, [x5], x2 //vld1_u8(pu1_src_tmp) + st1 { v4.16b}, [x6], x3 //vst1_u8(pu1_dst_tmp, tmp_src) + ld1 { v6.16b}, [x5], x2 //vld1_u8(pu1_src_tmp) + st1 { v6.16b}, [x6], x3 //vst1_u8(pu1_dst_tmp, tmp_src) + bgt inner_loop_wd_16 + +end_inner_loop_wd_16: + subs x7, x7, #4 //ht -= 4 + sub x0, x5, x11 //pu1_src = pu1_src_tmp + sub x1, x6, x11 //pu1_dst = pu1_dst_tmp + bgt outer_loop_wd_16 + + + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + +// /* +// ******************************************************************************** +// * +// * @brief This function copies a 4x4 block to destination +// * +// * @par Description: +// * Copies a 4x4 block to destination, where both src and dst are interleaved +// * +// * @param[in] pi2_src +// * Source +// * +// * @param[in] pu1_out +// * Output pointer +// * +// * @param[in] pred_strd, +// * Prediction buffer stride +// * +// * @param[in] out_strd +// * output buffer buffer Stride +// * +// * @returns none +// * +// * @remarks none +// * Currently wd and height is not used, ie a 4x4 block is always copied +// * +// ******************************************************************************* +// */ +// void ih264_interleave_copy(WORD16 *pi2_src, +// UWORD8 *pu1_out, +// WORD32 pred_strd, +// WORD32 out_strd +// WORD32 wd +// WORD32 ht) +// Register Usage +// x0 : pi2_src +// x1 : pu1_out +// w2 : src_strd +// w3 : out_strd +// Neon registers d0-d7, d16-d30 are used +// No need for pushing arm and neon registers + + .global ih264_interleave_copy_av8 +ih264_interleave_copy_av8: + push_v_regs + sxtw x2, w2 + sxtw x3, w3 + ld1 {v2.8b}, [x0], x2 //load src plane 1 => d2 &pred palne 2 => d3 + ld1 {v3.8b}, [x0], x2 + mov v2.d[1], v3.d[0] + ld1 {v4.8b}, [x0], x2 + ld1 {v5.8b}, [x0], x2 + mov v4.d[1], v5.d[0] + + mov x0, x1 + + ld1 {v18.8b}, [x1], x3 //load out [8 bit size) -8 coeffs + ld1 {v19.8b}, [x1], x3 + mov v18.d[1], v19.d[0] + movi v30.8h, #0x00ff + ld1 {v20.8b}, [x1], x3 + ld1 {v21.8b}, [x1], x3 + mov v20.d[1], v21.d[0] + + bit v18.16b, v2.16b , v30.16b + bit v20.16b, v4.16b , v30.16b + + st1 {v18.8b}, [x0], x3 //store out + st1 {v18.d}[1], [x0], x3 + st1 {v20.8b}, [x0], x3 + st1 {v20.d}[1], [x0], x3 + + pop_v_regs + ret + + diff --git a/dependencies/ih264d/common/armv8/ih264_inter_pred_luma_horz_hpel_vert_hpel_av8.s b/dependencies/ih264d/common/armv8/ih264_inter_pred_luma_horz_hpel_vert_hpel_av8.s new file mode 100644 index 00000000..dd4383e9 --- /dev/null +++ b/dependencies/ih264d/common/armv8/ih264_inter_pred_luma_horz_hpel_vert_hpel_av8.s @@ -0,0 +1,824 @@ +//****************************************************************************** +//* +//* Copyright (C) 2015 The Android Open Source Project +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at: +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. +//* +//***************************************************************************** +//* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +//*/ +///** +//****************************************************************************** +//* @file +//* ih264_inter_pred_luma_horz_hpel_vert_hpel_av8.s +//* +//* @brief +//* Contains function definitions for inter prediction interpolation. +//* +//* @author +//* Mohit +//* +//* @par List of Functions: +//* +//* - ih264_inter_pred_luma_horz_hpel_vert_hpel_av8() +//* +//* @remarks +//* None +//* +//******************************************************************************* +//*/ + + + +//void ih264_inter_pred_luma_horz_hpel_vert_hpel(UWORD8 *pu1_src, +// UWORD8 *pu1_dst, +// WORD32 src_strd,, +// WORD32 dst_strd, +// WORD32 ht, +// WORD32 wd, +// UWORD8* pu1_tmp, +// UWORD32 dydx) + +//**************Variables Vs Registers***************************************** +// x0 => *pu1_src +// x1 => *pu1_dst +// w2 => src_strd +// w3 => dst_strd +// w4 => ht +// w5 => wd + + +.text +.p2align 2 +.include "ih264_neon_macros.s" + + + + .global ih264_inter_pred_luma_horz_hpel_vert_hpel_av8 + +ih264_inter_pred_luma_horz_hpel_vert_hpel_av8: + + //store register values to stack + push_v_regs + stp x19, x20, [sp, #-16]! + sxtw x2, w2 + sxtw x3, w3 + sxtw x4, w4 + sxtw x5, w5 + + sub x0, x0, x2, lsl #1 //pu1_src-2*src_strd + sub x0, x0, #2 //pu1_src-2 + + movi v26.8h, #0x14 // Filter coeff 20 into Q13 + movi v24.8h, #0x5 // Filter coeff 5 into Q12 + movi v27.8h, #0x14 // Filter coeff 20 into Q13 + movi v25.8h, #0x5 // Filter coeff 5 into Q12 + mov x7, #0x20 + mov x8, #0x30 + subs x12, x5, #4 //if wd=4 branch to loop_4 + beq loop_4_start + + subs x12, x5, #8 //if wd=8 branch to loop_8 + beq loop_8_start + + //when wd=16 + movi v28.8h, #0x14 // Filter coeff 20 into Q13 + movi v30.8h, #0x5 // Filter coeff 5 into Q12 + sub x2, x2, #16 + ld1 {v0.2s, v1.2s}, [x0], #16 // Vector load from src[0_0] + ld1 {v12.2s}, [x0], x2 // Vector load from src[0_0] + ld1 {v2.2s, v3.2s}, [x0], #16 // Vector load from src[1_0] + ld1 {v13.2s}, [x0], x2 // Vector load from src[1_0] + ld1 {v4.2s, v5.2s}, [x0], #16 // Vector load from src[2_0] + ld1 {v14.2s}, [x0], x2 // Vector load from src[2_0] + ld1 {v6.2s, v7.2s}, [x0], #16 // Vector load from src[3_0] + ld1 {v15.2s}, [x0], x2 // Vector load from src[3_0] + ld1 {v8.2s, v9.2s}, [x0], #16 // Vector load from src[4_0] + ld1 {v16.2s}, [x0], x2 // Vector load from src[4_0] +loop_16: + + ld1 {v10.2s, v11.2s}, [x0], #16 // Vector load from src[5_0] + ld1 {v17.2s}, [x0], x2 // Vector load from src[5_0] + + + uaddl v20.8h, v4.8b, v6.8b + uaddl v18.8h, v0.8b, v10.8b + uaddl v22.8h, v2.8b, v8.8b + mla v18.8h, v20.8h , v28.8h + uaddl v24.8h, v5.8b, v7.8b + uaddl v20.8h, v1.8b, v11.8b + uaddl v26.8h, v3.8b, v9.8b + mla v20.8h, v24.8h , v28.8h + uaddl v24.8h, v14.8b, v15.8b + mls v18.8h, v22.8h , v30.8h + uaddl v22.8h, v12.8b, v17.8b + mls v20.8h, v26.8h , v30.8h + uaddl v26.8h, v13.8b, v16.8b + mla v22.8h, v24.8h , v28.8h + mls v22.8h, v26.8h , v30.8h + + ext v24.16b, v18.16b , v20.16b , #4 + ext v26.16b, v18.16b , v20.16b , #6 + + ext v23.16b, v18.16b , v20.16b , #10 + add v0.8h, v24.8h , v26.8h + ext v24.16b, v18.16b , v20.16b , #2 + ext v26.16b, v18.16b , v20.16b , #8 + add v24.8h, v24.8h , v26.8h + + saddl v26.4s, v18.4h, v23.4h + smlal v26.4s, v0.4h, v28.4h + smlsl v26.4s, v24.4h, v30.4h + + saddl2 v23.4s, v18.8h, v23.8h + smlal2 v23.4s, v0.8h, v28.8h + smlsl2 v23.4s, v24.8h, v30.8h + + sqrshrun v18.4h, v26.4s, #10 + sqrshrun v19.4h, v23.4s, #10 + + + uqxtn v18.8b, v18.8h + uqxtn v19.8b, v19.8h + mov v18.s[1], v19.s[0] + + ext v24.16b, v20.16b , v22.16b , #4 + ext v26.16b, v20.16b , v22.16b , #6 + ext v0.16b, v20.16b , v22.16b , #10 + + add v25.8h, v24.8h , v26.8h + ext v24.16b, v20.16b , v22.16b , #2 + ext v26.16b, v20.16b , v22.16b , #8 + add v24.8h, v24.8h , v26.8h + + saddl v26.4s, v0.4h, v20.4h + smlal v26.4s, v25.4h, v28.4h + smlsl v26.4s, v24.4h, v30.4h + + saddl2 v22.4s, v0.8h, v20.8h + smlal2 v22.4s, v25.8h, v28.8h + smlsl2 v22.4s, v24.8h, v30.8h + + sqrshrun v19.4h, v26.4s, #10 + sqrshrun v25.4h, v22.4s, #10 + + uaddl v24.8h, v7.8b, v9.8b + + + + uqxtn v19.8b, v19.8h + uqxtn v25.8b, v25.8h + mov v19.s[1], v25.s[0] + + uaddl v22.8h, v4.8b, v10.8b + ld1 {v0.2s, v1.2s}, [x0], #16 // Vector load from src[6_0] + + + ld1 {v12.2s}, [x0], x2 // Vector load from src[6_0] + uaddl v20.8h, v6.8b, v8.8b + uaddl v26.8h, v5.8b, v11.8b + st1 {v18.2s, v19.2s}, [x1], x3 // store row 0 + + +//ROW_2 + + + uaddl v18.8h, v2.8b, v0.8b + + mla v18.8h, v20.8h , v28.8h + + uaddl v20.8h, v3.8b, v1.8b + + mla v20.8h, v24.8h , v28.8h + uaddl v24.8h, v15.8b, v16.8b + mls v18.8h, v22.8h , v30.8h + uaddl v22.8h, v13.8b, v12.8b + mls v20.8h, v26.8h , v30.8h + uaddl v26.8h, v14.8b, v17.8b + mla v22.8h, v24.8h , v28.8h + mls v22.8h, v26.8h , v30.8h + + ext v24.16b, v18.16b , v20.16b , #4 + ext v26.16b, v18.16b , v20.16b , #6 + + ext v23.16b, v18.16b , v20.16b , #10 + add v2.8h, v24.8h , v26.8h + ext v24.16b, v18.16b , v20.16b , #2 + ext v26.16b, v18.16b , v20.16b , #8 + add v24.8h, v24.8h , v26.8h + + saddl v26.4s, v18.4h, v23.4h + smlal v26.4s, v2.4h, v28.4h + smlsl v26.4s, v24.4h, v30.4h + + saddl2 v23.4s, v18.8h, v23.8h + smlal2 v23.4s, v2.8h, v28.8h + smlsl2 v23.4s, v24.8h, v30.8h + + sqrshrun v18.4h, v26.4s, #10 + sqrshrun v19.4h, v23.4s, #10 + + + + uqxtn v18.8b, v18.8h + uqxtn v19.8b, v19.8h + mov v18.s[1], v19.s[0] + + ext v24.16b, v20.16b , v22.16b , #4 + ext v26.16b, v20.16b , v22.16b , #6 + ext v2.16b, v20.16b , v22.16b , #10 + + add v25.8h, v24.8h , v26.8h + ext v24.16b, v20.16b , v22.16b , #2 + ext v26.16b, v20.16b , v22.16b , #8 + add v24.8h, v24.8h , v26.8h + + saddl v26.4s, v2.4h, v20.4h + smlal v26.4s, v25.4h, v28.4h + smlsl v26.4s, v24.4h, v30.4h + + saddl2 v22.4s, v2.8h, v20.8h + smlal2 v22.4s, v25.8h, v28.8h + smlsl2 v22.4s, v24.8h, v30.8h + + sqrshrun v19.4h, v26.4s, #10 + sqrshrun v25.4h, v22.4s, #10 + uaddl v24.8h, v9.8b, v11.8b + + uqxtn v19.8b, v19.8h + uqxtn v25.8b, v25.8h + mov v19.s[1], v25.s[0] + + + uaddl v22.8h, v6.8b, v0.8b + ld1 {v2.2s, v3.2s}, [x0], #16 // Vector load from src[7_0] + + + ld1 {v13.2s}, [x0], x2 // Vector load from src[7_0] + uaddl v20.8h, v8.8b, v10.8b + uaddl v26.8h, v7.8b, v1.8b + st1 {v18.2s, v19.2s}, [x1], x3 // store row 1 + +//ROW_3 + + + uaddl v18.8h, v4.8b, v2.8b + + mla v18.8h, v20.8h , v28.8h + + uaddl v20.8h, v5.8b, v3.8b + + mla v20.8h, v24.8h , v28.8h + uaddl v24.8h, v16.8b, v17.8b + mls v18.8h, v22.8h , v30.8h + uaddl v22.8h, v14.8b, v13.8b + mls v20.8h, v26.8h , v30.8h + uaddl v26.8h, v15.8b, v12.8b + mla v22.8h, v24.8h , v28.8h + mls v22.8h, v26.8h , v30.8h + + ext v24.16b, v18.16b , v20.16b , #4 + ext v26.16b, v18.16b , v20.16b , #6 + + ext v23.16b, v18.16b , v20.16b , #10 + add v4.8h, v24.8h , v26.8h + ext v24.16b, v18.16b , v20.16b , #2 + ext v26.16b, v18.16b , v20.16b , #8 + add v24.8h, v24.8h , v26.8h + + saddl v26.4s, v18.4h, v23.4h + smlal v26.4s, v4.4h, v28.4h + smlsl v26.4s, v24.4h, v30.4h + + saddl2 v23.4s, v18.8h, v23.8h + smlal2 v23.4s, v4.8h, v28.8h + smlsl2 v23.4s, v24.8h, v30.8h + + sqrshrun v18.4h, v26.4s, #10 + sqrshrun v19.4h, v23.4s, #10 + + + uqxtn v18.8b, v18.8h + uqxtn v19.8b, v19.8h + mov v18.s[1], v19.s[0] + + + ext v24.16b, v20.16b , v22.16b , #4 + ext v26.16b, v20.16b , v22.16b , #6 + ext v4.16b, v20.16b , v22.16b , #10 + + add v25.8h, v24.8h , v26.8h + ext v24.16b, v20.16b , v22.16b , #2 + ext v26.16b, v20.16b , v22.16b , #8 + add v24.8h, v24.8h , v26.8h + + saddl v26.4s, v4.4h, v20.4h + smlal v26.4s, v25.4h, v28.4h + smlsl v26.4s, v24.4h, v30.4h + + saddl2 v22.4s, v4.8h, v20.8h + smlal2 v22.4s, v25.8h, v28.8h + smlsl2 v22.4s, v24.8h, v30.8h + + sqrshrun v19.4h, v26.4s, #10 + sqrshrun v25.4h, v22.4s, #10 + + uaddl v24.8h, v11.8b, v1.8b + + + uqxtn v19.8b, v19.8h + uqxtn v25.8b, v25.8h + mov v19.s[1], v25.s[0] + + + + uaddl v22.8h, v8.8b, v2.8b + ld1 {v4.2s, v5.2s}, [x0], #16 // Vector load from src[8_0] + + + ld1 {v14.2s}, [x0], x2 // Vector load from src[8_0] + uaddl v20.8h, v10.8b, v0.8b + uaddl v26.8h, v9.8b, v3.8b + st1 {v18.2s, v19.2s}, [x1], x3 // store row 2 + + +//ROW_4 + + uaddl v18.8h, v6.8b, v4.8b + + mla v18.8h, v20.8h , v28.8h + + uaddl v20.8h, v7.8b, v5.8b + + mla v20.8h, v24.8h , v28.8h + uaddl v24.8h, v17.8b, v12.8b + mls v18.8h, v22.8h , v30.8h + uaddl v22.8h, v15.8b, v14.8b + mls v20.8h, v26.8h , v30.8h + uaddl v26.8h, v16.8b, v13.8b + mla v22.8h, v24.8h , v28.8h + mls v22.8h, v26.8h , v30.8h + + ext v24.16b, v18.16b , v20.16b , #4 + ext v26.16b, v18.16b , v20.16b , #6 + + ext v23.16b, v18.16b , v20.16b , #10 + add v6.8h, v24.8h , v26.8h + ext v24.16b, v18.16b , v20.16b , #2 + ext v26.16b, v18.16b , v20.16b , #8 + add v24.8h, v24.8h , v26.8h + + saddl v26.4s, v18.4h, v23.4h + smlal v26.4s, v6.4h, v28.4h + smlsl v26.4s, v24.4h, v30.4h + + saddl2 v23.4s, v18.8h, v23.8h + smlal2 v23.4s, v6.8h, v28.8h + smlsl2 v23.4s, v24.8h, v30.8h + + sqrshrun v18.4h, v26.4s, #10 + sqrshrun v19.4h, v23.4s, #10 + + uqxtn v18.8b, v18.8h + uqxtn v19.8b, v19.8h + mov v18.s[1], v19.s[0] + + + ext v24.16b, v20.16b , v22.16b , #4 + ext v26.16b, v20.16b , v22.16b , #6 + ext v6.16b, v20.16b , v22.16b , #10 + + add v25.8h, v24.8h , v26.8h + ext v24.16b, v20.16b , v22.16b , #2 + ext v26.16b, v20.16b , v22.16b , #8 + add v24.8h, v24.8h , v26.8h + + saddl v26.4s, v6.4h, v20.4h + smlal v26.4s, v25.4h, v28.4h + smlsl v26.4s, v24.4h, v30.4h + + saddl2 v22.4s, v6.8h, v20.8h + smlal2 v22.4s, v25.8h, v28.8h + smlsl2 v22.4s, v24.8h, v30.8h + + mov v6.16b, v2.16b + mov v7.16b, v3.16b + + mov v2.16b, v10.16b + mov v3.16b, v11.16b + + subs x4, x4, #4 + sqrshrun v19.4h, v26.4s, #10 + sqrshrun v25.4h, v22.4s, #10 + mov v10.16b, v0.16b + mov v11.16b, v1.16b + + mov v24.8b, v14.8b + + mov v14.16b, v12.16b + mov v15.16b, v13.16b + + + uqxtn v19.8b, v19.8h + uqxtn v25.8b, v25.8h + mov v19.s[1], v25.s[0] + + + + mov v0.16b, v8.16b + mov v1.16b, v9.16b + + mov v8.16b, v4.16b + mov v9.16b, v5.16b + + mov v12.16b, v16.16b + mov v13.16b, v17.16b + + mov v4.16b, v10.16b + mov v5.16b, v11.16b + + mov v16.8b, v24.8b + st1 {v18.2s, v19.2s}, [x1], x3 // store row 3 + + bgt loop_16 // looping if height =16 + b end_func + +loop_8_start: + ld1 {v0.2s, v1.2s}, [x0], x2 // Vector load from src[0_0] + ld1 {v2.2s, v3.2s}, [x0], x2 // Vector load from src[1_0] + ld1 {v4.2s, v5.2s}, [x0], x2 // Vector load from src[2_0] + ld1 {v6.2s, v7.2s}, [x0], x2 // Vector load from src[3_0] + ld1 {v8.2s, v9.2s}, [x0], x2 // Vector load from src[4_0] + +loop_8: + + ld1 {v10.2s, v11.2s}, [x0], x2 // Vector load from src[5_0] + uaddl v14.8h, v4.8b, v6.8b + uaddl v12.8h, v0.8b, v10.8b + uaddl v16.8h, v2.8b, v8.8b + mla v12.8h, v14.8h , v26.8h + uaddl v18.8h, v5.8b, v7.8b + uaddl v14.8h, v1.8b, v11.8b + uaddl v22.8h, v3.8b, v9.8b + mla v14.8h, v18.8h , v26.8h + mls v12.8h, v16.8h , v24.8h + ld1 {v0.2s, v1.2s}, [x0], x2 // Vector load from src[6_0] + uaddl v16.8h, v6.8b, v8.8b + mls v14.8h, v22.8h , v24.8h + uaddl v28.8h, v2.8b, v0.8b + + ext v22.16b, v12.16b , v14.16b , #10 + uaddl v18.8h, v4.8b, v10.8b + mla v28.8h, v16.8h , v26.8h + saddl v30.4s, v12.4h, v22.4h + + saddl2 v22.4s, v12.8h, v22.8h + ext v16.16b, v12.16b , v14.16b , #4 + mls v28.8h, v18.8h , v24.8h + ext v18.16b, v12.16b , v14.16b , #6 + ext v20.16b, v12.16b , v14.16b , #8 + ext v14.16b, v12.16b , v14.16b , #2 + add v16.8h, v16.8h , v18.8h + add v18.8h, v14.8h , v20.8h + uaddl v20.8h, v7.8b, v9.8b + smlal v30.4s, v16.4h, v26.4h + smlsl v30.4s, v18.4h, v24.4h + smlal2 v22.4s, v16.8h, v26.8h + smlsl2 v22.4s, v18.8h, v24.8h + uaddl v14.8h, v3.8b, v1.8b + + mla v14.8h, v20.8h , v26.8h + sqrshrun v12.4h, v30.4s, #10 + uaddl v16.8h, v5.8b, v11.8b + sqrshrun v13.4h, v22.4s, #10 + mls v14.8h, v16.8h , v24.8h + ld1 {v2.2s, v3.2s}, [x0], x2 // Vector load from src[7_0] + uqxtn v25.8b, v12.8h + uqxtn v13.8b, v13.8h + mov v25.s[1], v13.s[0] + uaddl v16.8h, v8.8b, v10.8b + + + ext v22.16b, v28.16b , v14.16b , #10 + uaddl v20.8h, v4.8b, v2.8b + saddl v30.4s, v28.4h, v22.4h + mla v20.8h, v16.8h , v26.8h + + saddl2 v22.4s, v28.8h, v22.8h + ext v16.16b, v28.16b , v14.16b , #4 + ext v18.16b, v28.16b , v14.16b , #6 + ext v12.16b, v28.16b , v14.16b , #8 + ext v14.16b, v28.16b , v14.16b , #2 + add v16.8h, v16.8h , v18.8h + add v18.8h, v12.8h , v14.8h + + smlal v30.4s, v16.4h, v26.4h + smlsl v30.4s, v18.4h, v24.4h + smlal2 v22.4s, v16.8h, v26.8h + smlsl2 v22.4s, v18.8h, v24.8h + + + uaddl v18.8h, v6.8b, v0.8b + sqrshrun v16.4h, v30.4s, #10 + + sqrshrun v17.4h, v22.4s, #10 + + mov v12.8b, v25.8b + mov v25.8b, v24.8b + + uaddl v28.8h, v9.8b, v11.8b + uqxtn v13.8b, v16.8h + uqxtn v17.8b, v17.8h + mov v13.s[1], v17.s[0] + + + uaddl v14.8h, v5.8b, v3.8b + uaddl v22.8h, v7.8b, v1.8b + mls v20.8h, v18.8h , v24.8h + st1 {v12.2s}, [x1], x3 // store row 0 + mla v14.8h, v28.8h , v26.8h + ld1 {v4.2s, v5.2s}, [x0], x2 // Vector load from src[8_0] + uaddl v30.8h, v10.8b, v0.8b + uaddl v28.8h, v6.8b, v4.8b + mls v14.8h, v22.8h , v24.8h + st1 {v13.2s}, [x1], x3 // store row 1 + mla v28.8h, v30.8h , v26.8h + + ext v22.16b, v20.16b , v14.16b , #10 + saddl v30.4s, v20.4h, v22.4h + + saddl2 v22.4s, v20.8h, v22.8h + ext v16.16b, v20.16b , v14.16b , #4 + ext v18.16b, v20.16b , v14.16b , #6 + ext v12.16b, v20.16b , v14.16b , #8 + ext v14.16b, v20.16b , v14.16b , #2 + add v16.8h, v16.8h , v18.8h + add v18.8h, v14.8h , v12.8h + uaddl v20.8h, v8.8b, v2.8b + smlal v30.4s, v16.4h, v26.4h + smlsl v30.4s, v18.4h, v24.4h + smlal2 v22.4s, v16.8h, v26.8h + smlsl2 v22.4s, v18.8h, v24.8h + uaddl v18.8h, v11.8b, v1.8b + uaddl v16.8h, v7.8b, v5.8b + sqrshrun v12.4h, v30.4s, #10 + uaddl v30.8h, v9.8b, v3.8b + mla v16.8h, v18.8h , v26.8h + sqrshrun v13.4h, v22.4s, #10 + mls v28.8h, v20.8h , v24.8h + + mls v16.8h, v30.8h , v24.8h + uqxtn v27.8b, v12.8h + uqxtn v13.8b, v13.8h + mov v27.s[1], v13.s[0] + + + ext v22.16b, v28.16b , v16.16b , #10 + + saddl v30.4s, v28.4h, v22.4h + + saddl2 v22.4s, v28.8h, v22.8h + ext v12.16b, v28.16b , v16.16b , #4 + ext v18.16b, v28.16b , v16.16b , #6 + ext v20.16b, v28.16b , v16.16b , #8 + ext v28.16b, v28.16b , v16.16b , #2 + add v12.8h, v12.8h , v18.8h + add v18.8h, v28.8h , v20.8h + + smlal v30.4s, v12.4h, v26.4h + smlsl v30.4s, v18.4h, v24.4h + smlal2 v22.4s, v12.8h, v26.8h + smlsl2 v22.4s, v18.8h, v24.8h + + + mov v12.8b, v27.8b + mov v27.8b, v26.8b + + sqrshrun v16.4h, v30.4s, #10 + + mov v6.16b, v2.16b + mov v7.16b, v3.16b + + sqrshrun v17.4h, v22.4s, #10 + + mov v2.16b, v10.16b + mov v3.16b, v11.16b + + mov v10.16b, v0.16b + mov v11.16b, v1.16b + + subs x4, x4, #4 + uqxtn v13.8b, v16.8h + uqxtn v17.8b, v17.8h + mov v13.s[1], v17.s[0] + + + mov v0.16b, v8.16b + mov v1.16b, v9.16b + + mov v8.16b, v4.16b + mov v9.16b, v5.16b + + mov v4.16b, v10.16b + mov v5.16b, v11.16b + + st1 {v12.2s}, [x1], x3 // store row 2 + st1 {v13.2s}, [x1], x3 // store row 3 + + bgt loop_8 //if height =8 loop + b end_func + +loop_4_start: + ld1 {v0.2s, v1.2s}, [x0], x2 // Vector load from src[0_0] + ld1 {v2.2s, v3.2s}, [x0], x2 // Vector load from src[1_0] + ld1 {v4.2s, v5.2s}, [x0], x2 // Vector load from src[2_0] + ld1 {v6.2s, v7.2s}, [x0], x2 // Vector load from src[3_0] + ld1 {v8.2s, v9.2s}, [x0], x2 // Vector load from src[4_0] + +loop_4: + ld1 {v10.2s, v11.2s}, [x0], x2 // Vector load from src[5_0] + uaddl v14.8h, v4.8b, v6.8b // temp1 = src[2_0] + src[3_0] + uaddl v12.8h, v0.8b, v10.8b // temp = src[0_0] + src[5_0] + uaddl v16.8h, v2.8b, v8.8b // temp2 = src[1_0] + src[4_0] + mla v12.8h, v14.8h , v26.8h // temp += temp1 * 20 + uaddl v18.8h, v5.8b, v7.8b // temp1 = src[2_0] + src[3_0] + uaddl v14.8h, v1.8b, v11.8b // temp = src[0_0] + src[5_0] + uaddl v22.8h, v3.8b, v9.8b // temp2 = src[1_0] + src[4_0] + mla v14.8h, v18.8h , v26.8h // temp += temp1 * 20 + mls v12.8h, v16.8h , v24.8h // temp -= temp2 * 5 + ld1 {v0.2s, v1.2s}, [x0], x2 // Vector load from src[6_0] + uaddl v16.8h, v6.8b, v8.8b + mls v14.8h, v22.8h , v24.8h // temp -= temp2 * 5 + //Q6 and Q7 have filtered values + uaddl v28.8h, v2.8b, v0.8b + + ext v22.16b, v12.16b , v14.16b , #10 + uaddl v18.8h, v4.8b, v10.8b + mla v28.8h, v16.8h , v26.8h + saddl v30.4s, v12.4h, v22.4h + + saddl v22.4s, v13.4h, v23.4h + ext v16.16b, v12.16b , v14.16b , #4 + mls v28.8h, v18.8h , v24.8h + ext v18.16b, v12.16b , v14.16b , #6 + ext v20.16b, v12.16b , v14.16b , #8 + ext v14.16b, v12.16b , v14.16b , #2 + add v16.8h, v16.8h , v18.8h + add v18.8h, v14.8h , v20.8h + uaddl v20.8h, v7.8b, v9.8b + smlal v30.4s, v16.4h, v26.4h + smlsl v30.4s, v18.4h, v24.4h + smlal v22.4s, v17.4h, v26.4h + smlsl v22.4s, v19.4h, v24.4h + uaddl v14.8h, v3.8b, v1.8b + + mla v14.8h, v20.8h , v26.8h + sqrshrun v12.4h, v30.4s, #10 + uaddl v16.8h, v5.8b, v11.8b + sqrshrun v13.4h, v22.4s, #10 + mls v14.8h, v16.8h , v24.8h + ld1 {v2.2s, v3.2s}, [x0], x2 // Vector load from src[7_0] + uqxtn v25.8b, v12.8h + uaddl v16.8h, v8.8b, v10.8b + + ext v22.16b, v28.16b , v14.16b , #10 + uaddl v20.8h, v4.8b, v2.8b + saddl v30.4s, v28.4h, v22.4h + mla v20.8h, v16.8h , v26.8h + + saddl v22.4s, v29.4h, v23.4h + ext v16.16b, v28.16b , v14.16b , #4 + ext v18.16b, v28.16b , v14.16b , #6 + ext v12.16b, v28.16b , v14.16b , #8 + ext v14.16b, v28.16b , v14.16b , #2 + add v16.8h, v16.8h , v18.8h + add v18.8h, v12.8h , v14.8h + + smlal v30.4s, v16.4h, v26.4h + smlsl v30.4s, v18.4h, v24.4h + smlal v22.4s, v17.4h, v26.4h + smlsl v22.4s, v19.4h, v24.4h + + + uaddl v18.8h, v6.8b, v0.8b + sqrshrun v16.4h, v30.4s, #10 + + sqrshrun v17.4h, v22.4s, #10 + + mov v12.8b, v25.8b + mov v25.8b, v24.8b + + uaddl v28.8h, v9.8b, v11.8b + uqxtn v13.8b, v16.8h + + + + uaddl v14.8h, v5.8b, v3.8b + uaddl v22.8h, v7.8b, v1.8b + mls v20.8h, v18.8h , v24.8h + st1 {v12.s}[0], [x1], x3 // store row 0 + mla v14.8h, v28.8h , v26.8h + ld1 {v4.2s, v5.2s}, [x0], x2 // Vector load from src[8_0] + uaddl v30.8h, v10.8b, v0.8b + uaddl v28.8h, v6.8b, v4.8b + mls v14.8h, v22.8h , v24.8h + st1 {v13.s}[0], [x1], x3 //store row 1 + mla v28.8h, v30.8h , v26.8h + + ext v22.16b, v20.16b , v14.16b , #10 + saddl v30.4s, v20.4h, v22.4h + + saddl v22.4s, v21.4h, v23.4h + ext v16.16b, v20.16b , v14.16b , #4 + ext v18.16b, v20.16b , v14.16b , #6 + ext v12.16b, v20.16b , v14.16b , #8 + ext v14.16b, v20.16b , v14.16b , #2 + add v16.8h, v16.8h , v18.8h + add v18.8h, v14.8h , v12.8h + uaddl v20.8h, v8.8b, v2.8b + smlal v30.4s, v16.4h, v26.4h + smlsl v30.4s, v18.4h, v24.4h + smlal v22.4s, v17.4h, v26.4h + smlsl v22.4s, v19.4h, v24.4h + uaddl v18.8h, v11.8b, v1.8b + uaddl v16.8h, v7.8b, v5.8b + sqrshrun v12.4h, v30.4s, #10 + uaddl v30.8h, v9.8b, v3.8b + mla v16.8h, v18.8h , v26.8h + sqrshrun v13.4h, v22.4s, #10 + mls v28.8h, v20.8h , v24.8h + + mls v16.8h, v30.8h , v24.8h + uqxtn v27.8b, v12.8h + + ext v22.16b, v28.16b , v16.16b , #10 + + saddl v30.4s, v28.4h, v22.4h + + saddl v22.4s, v29.4h, v23.4h + ext v12.16b, v28.16b , v16.16b , #4 + ext v18.16b, v28.16b , v16.16b , #6 + ext v20.16b, v28.16b , v16.16b , #8 + ext v28.16b, v28.16b , v16.16b , #2 + add v12.8h, v12.8h , v18.8h + add v18.8h, v28.8h , v20.8h + + smlal v30.4s, v12.4h, v26.4h + smlsl v30.4s, v18.4h, v24.4h + smlal v22.4s, v13.4h, v26.4h + smlsl v22.4s, v19.4h, v24.4h + + + mov v12.8b, v27.8b + mov v27.8b, v26.8b + + sqrshrun v16.4h, v30.4s, #10 + + mov v6.16b, v2.16b + mov v7.16b, v3.16b + + sqrshrun v17.4h, v22.4s, #10 + + mov v2.16b, v10.16b + mov v3.16b, v11.16b + + mov v10.16b, v0.16b + mov v11.16b, v1.16b + + subs x4, x4, #4 + uqxtn v13.8b, v16.8h + + mov v0.16b, v8.16b + mov v1.16b, v9.16b + + mov v8.16b, v4.16b + mov v9.16b, v5.16b + + + mov v4.16b, v10.16b + mov v5.16b, v11.16b + + + st1 {v12.s}[0], [x1], x3 // store row 2 + st1 {v13.s}[0], [x1], x3 // store row 3 + + bgt loop_4 + +end_func: + //Restoring registers from stack + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + + diff --git a/dependencies/ih264d/common/armv8/ih264_inter_pred_luma_horz_hpel_vert_qpel_av8.s b/dependencies/ih264d/common/armv8/ih264_inter_pred_luma_horz_hpel_vert_qpel_av8.s new file mode 100644 index 00000000..3563ac04 --- /dev/null +++ b/dependencies/ih264d/common/armv8/ih264_inter_pred_luma_horz_hpel_vert_qpel_av8.s @@ -0,0 +1,1125 @@ +//****************************************************************************** +//* +//* Copyright (C) 2015 The Android Open Source Project +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at: +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. +//* +//***************************************************************************** +//* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +//*/ +///** +//****************************************************************************** +//* @file +//* ih264_inter_pred_luma_horz_hpel_vert_qpel_av8.s +//* +//* @brief +//* Contains function definitions for inter prediction interpolation. +//* +//* @author +//* Mohit +//* +//* @par List of Functions: +//* +//* - ih264_inter_pred_luma_horz_hpel_vert_qpel_av8() +//* +//* @remarks +//* None +//* +//******************************************************************************* +//*/ + +///* All the functions here are replicated from ih264_inter_pred_filters.c +// + +///** +///** +///** +//******************************************************************************* +//* +//* @brief +//* This function implements a two stage cascaded six tap filter. It +//* applies the six tap filter in the horizontal direction on the +//* predictor values, followed by applying the same filter in the +//* vertical direction on the output of the first stage. It then averages +//* the output of the 1st stage and the output of the 2nd stage to obtain +//* the quarter pel values. The six tap filtering operation is described +//* in sec 8.4.2.2.1 titled "Luma sample interpolation process". +//* +//* @par Description: +//* This function is called to obtain pixels lying at the following +//* location (1/2,1/4) or (1/2,3/4). The function interpolates +//* the predictors first in the horizontal direction and then in the +//* vertical direction to output the (1/2,1/2). It then averages +//* the output of the 2nd stage and (1/2,1/2) value to obtain (1/2,1/4) +//* or (1/2,3/4) depending on the offset. +//* +//* @param[in] pu1_src +//* UWORD8 pointer to the source +//* +//* @param[out] pu1_dst +//* UWORD8 pointer to the destination +//* +//* @param[in] src_strd +//* integer source stride +//* +//* @param[in] dst_strd +//* integer destination stride +//* +//* @param[in] ht +//* integer height of the array +//* +//* @param[in] wd +//* integer width of the array +//* +//* @param[in] pu1_tmp: temporary buffer +//* +//* @param[in] dydx: x and y reference offset for qpel calculations +//* +//* @returns +//* +//* @remarks +//* None +//* +//******************************************************************************* +//*/; + +//void ih264_inter_pred_luma_horz_hpel_vert_qpel(UWORD8 *pu1_src, +// UWORD8 *pu1_dst, +// WORD32 src_strd,, +// WORD32 dst_strd, +// WORD32 ht, +// WORD32 wd, +// UWORD8* pu1_tmp, +// UWORD32 dydx) + +//**************Variables Vs Registers***************************************** +// x0 => *pu1_src +// x1 => *pu1_dst +// w2 => src_strd +// w3 => dst_strd +// w4 => ht +// w5 => wd +// x6 => *pu1_tmp +// w7 => dydx + +.text +.p2align 2 +.include "ih264_neon_macros.s" + + + + .global ih264_inter_pred_luma_horz_hpel_vert_qpel_av8 + +ih264_inter_pred_luma_horz_hpel_vert_qpel_av8: + + + // store register values to stack + push_v_regs + stp x19, x20, [sp, #-16]! + sxtw x2, w2 + sxtw x3, w3 + sxtw x4, w4 + sxtw x5, w5 + + + + sub x0, x0, x2, lsl #1 // pu1_src-2*src_strd + sub x0, x0, #2 // pu1_src-2 + + mov x9, x6 + + // by writing to w7 here, we clear the upper half of x7 + lsr w7, w7, #3 // dydx >> 2 followed by dydx & 0x3 and dydx>>1 to obtain the deciding bit + + add x7, x7, #2 + mov x6, #48 + madd x7, x7, x6, x9 + + subs x12, x5, #4 //if wd=4 branch to loop_4 + beq loop_4_start + + subs x12, x5, #8 //if wd=8 branch to loop_8 + beq loop_8_start + + //when wd=16 + movi v22.8h, #20 // Filter coeff 0x14 into Q11 + movi v24.8h, #5 // Filter coeff 0x5 into Q12 + add x8, x0, #8 + add x14, x1, #8 + add x10, x9, #8 + mov x12, x4 + add x11, x7, #8 +loop_16_lowhalf_start: + ld1 {v0.2s, v1.2s}, [x0], x2 // row -2 load for horizontal filter + ext v5.8b, v0.8b , v1.8b , #5 + uaddl v6.8h, v0.8b, v5.8b + + ext v2.8b, v0.8b , v1.8b , #2 + ext v3.8b, v0.8b , v1.8b , #3 + uaddl v8.8h, v2.8b, v3.8b + ext v4.8b, v0.8b , v1.8b , #4 + mla v6.8h, v8.8h , v22.8h + ext v1.8b, v0.8b , v1.8b , #1 + uaddl v8.8h, v1.8b, v4.8b + ld1 {v0.2s, v1.2s}, [x0], x2 // row -1 load for horizontal filter + mls v6.8h, v8.8h , v24.8h + ext v5.8b, v0.8b , v1.8b , #5 + uaddl v8.8h, v0.8b, v5.8b + ext v2.8b, v0.8b , v1.8b , #2 + ext v3.8b, v0.8b , v1.8b , #3 + uaddl v10.8h, v2.8b, v3.8b + + st1 {v6.4s}, [x9], x6 // store temp buffer 0 + + ext v4.8b, v0.8b , v1.8b , #4 + mla v8.8h, v10.8h , v22.8h + ext v1.8b, v0.8b , v1.8b , #1 + uaddl v10.8h, v1.8b, v4.8b + ld1 {v0.2s, v1.2s}, [x0], x2 // row 0 load for horizontal filter + mls v8.8h, v10.8h , v24.8h + ext v5.8b, v0.8b , v1.8b , #5 + uaddl v10.8h, v0.8b, v5.8b + ext v2.8b, v0.8b , v1.8b , #2 + ext v3.8b, v0.8b , v1.8b , #3 + uaddl v12.8h, v2.8b, v3.8b + + st1 {v8.4s}, [x9], x6 // store temp buffer 1 + + ext v4.8b, v0.8b , v1.8b , #4 + mla v10.8h, v12.8h , v22.8h + ext v1.8b, v0.8b , v1.8b , #1 + uaddl v12.8h, v1.8b, v4.8b + ld1 {v0.2s, v1.2s}, [x0], x2 // row 1 load for horizontal filter + mls v10.8h, v12.8h , v24.8h + ext v5.8b, v0.8b , v1.8b , #5 + uaddl v12.8h, v0.8b, v5.8b + ext v2.8b, v0.8b , v1.8b , #2 + ext v3.8b, v0.8b , v1.8b , #3 + uaddl v14.8h, v2.8b, v3.8b + + st1 {v10.4s}, [x9], x6 // store temp buffer 2 + + ext v4.8b, v0.8b , v1.8b , #4 + mla v12.8h, v14.8h , v22.8h + ext v1.8b, v0.8b , v1.8b , #1 + uaddl v14.8h, v1.8b, v4.8b + ld1 {v0.2s, v1.2s}, [x0], x2 // row 2 load for horizontal filter + mls v12.8h, v14.8h , v24.8h + ext v5.8b, v0.8b , v1.8b , #5 + uaddl v14.8h, v0.8b, v5.8b + ext v2.8b, v0.8b , v1.8b , #2 + ext v3.8b, v0.8b , v1.8b , #3 + uaddl v16.8h, v2.8b, v3.8b + + st1 {v12.4s}, [x9], x6 // store temp buffer 3 + + ext v4.8b, v0.8b , v1.8b , #4 + mla v14.8h, v16.8h , v22.8h + ext v1.8b, v0.8b , v1.8b , #1 + uaddl v16.8h, v1.8b, v4.8b + + mls v14.8h, v16.8h , v24.8h +loop_16_lowhalf: + + ld1 {v0.2s, v1.2s}, [x0], x2 // row 3 load for horizontal filter + ext v5.8b, v0.8b , v1.8b , #5 + ext v2.8b, v0.8b , v1.8b , #2 + ext v3.8b, v0.8b , v1.8b , #3 + uaddl v16.8h, v0.8b, v5.8b + + st1 {v14.4s}, [x9], x6 // store temp buffer 4 + + uaddl v18.8h, v2.8b, v3.8b + ext v4.8b, v0.8b , v1.8b , #4 + mla v16.8h, v18.8h , v22.8h + ext v1.8b, v0.8b , v1.8b , #1 + add v28.8h, v8.8h , v14.8h + uaddl v18.8h, v1.8b, v4.8b + add v30.8h, v10.8h , v12.8h + mls v16.8h, v18.8h , v24.8h + ld1 {v0.2s, v1.2s}, [x0], x2 // row 4 load for hoorizontal filter + ext v5.8b, v0.8b , v1.8b , #5 + ext v2.8b, v0.8b , v1.8b , #2 + ext v3.8b, v0.8b , v1.8b , #3 + uaddl v20.8h, v0.8b, v5.8b + + st1 {v16.4s}, [x9], x6 // store temp buffer x5 + + saddl v18.4s, v6.4h, v16.4h + + ld1 {v26.4s}, [x7], x6 // load from temp buffer 0 + + saddl2 v6.4s, v6.8h, v16.8h + + sqrshrun v26.8b, v26.8h, #5 + + smlal v18.4s, v30.4h, v22.4h + smlsl v18.4s, v28.4h, v24.4h + smlal2 v6.4s, v30.8h, v22.8h + smlsl2 v6.4s, v28.8h, v24.8h + uaddl v2.8h, v2.8b, v3.8b + ext v4.8b, v0.8b , v1.8b , #4 + mla v20.8h, v2.8h , v22.8h + sqrshrun v18.4h, v18.4s, #10 + ext v1.8b, v0.8b , v1.8b , #1 + sqrshrun v19.4h, v6.4s, #10 + add v28.8h, v10.8h , v16.8h + uaddl v2.8h, v1.8b, v4.8b + add v30.8h, v12.8h , v14.8h + mls v20.8h, v2.8h , v24.8h + + uqxtn v18.8b, v18.8h + uqxtn v19.8b, v19.8h + mov v18.s[1], v19.s[0] + + ld1 {v0.2s, v1.2s}, [x0], x2 // row 5 load for horizontal filter + + urhadd v26.8b, v18.8b , v26.8b + + ext v5.8b, v0.8b , v1.8b , #5 + ext v2.8b, v0.8b , v1.8b , #2 + + st1 {v20.4s}, [x9], x6 // store temp buffer x6 + + saddl v18.4s, v8.4h, v20.4h + + saddl2 v6.4s, v8.8h, v20.8h + + ld1 {v8.4s}, [x7], x6 //load from temp buffer 1 + + + st1 {v26.2s}, [x1], x3 // store row 0 + + smlal v18.4s, v30.4h, v22.4h + smlsl v18.4s, v28.4h, v24.4h + smlal2 v6.4s, v30.8h, v22.8h + smlsl2 v6.4s, v28.8h, v24.8h + + sqrshrun v28.8b, v8.8h, #5 + ext v3.8b, v0.8b , v1.8b , #3 + uaddl v8.8h, v0.8b, v5.8b + uaddl v2.8h, v2.8b, v3.8b + sqrshrun v18.4h, v18.4s, #10 + ext v4.8b, v0.8b , v1.8b , #4 + sqrshrun v19.4h, v6.4s, #10 + mla v8.8h, v2.8h , v22.8h + ext v1.8b, v0.8b , v1.8b , #1 + add v26.8h, v12.8h , v20.8h + uaddl v2.8h, v1.8b, v4.8b + uqxtn v18.8b, v18.8h + uqxtn v19.8b, v19.8h + mov v18.s[1], v19.s[0] + add v30.8h, v14.8h , v16.8h + mls v8.8h, v2.8h , v24.8h + ld1 {v0.2s, v1.2s}, [x0], x2 // row 6 load for horizontal filter + + urhadd v28.8b, v28.8b , v18.8b + + ext v5.8b, v0.8b , v1.8b , #5 + ext v2.8b, v0.8b , v1.8b , #2 + ext v3.8b, v0.8b , v1.8b , #3 + + st1 {v28.2s}, [x1], x3 // store row 1 + + uaddl v28.8h, v0.8b, v5.8b + + st1 {v8.4s}, [x9], x6 // store temp buffer x7 + + saddl v18.4s, v10.4h, v8.4h + saddl2 v6.4s, v10.8h, v8.8h + + ld1 {v10.4s}, [x7], x6 // load from temp buffer 2 + + smlal v18.4s, v30.4h, v22.4h + smlsl v18.4s, v26.4h, v24.4h + + smlal2 v6.4s, v30.8h, v22.8h + smlsl2 v6.4s, v26.8h, v24.8h + + sqrshrun v26.8b, v10.8h, #5 + + uaddl v2.8h, v2.8b, v3.8b + ext v4.8b, v0.8b , v1.8b , #4 + mla v28.8h, v2.8h , v22.8h + sqrshrun v18.4h, v18.4s, #10 + ext v1.8b, v0.8b , v1.8b , #1 + sqrshrun v19.4h, v6.4s, #10 + add v10.8h, v14.8h , v8.8h + uaddl v2.8h, v1.8b, v4.8b + add v30.8h, v16.8h , v20.8h + mls v28.8h, v2.8h , v24.8h + uqxtn v27.8b, v18.8h + uqxtn v19.8b, v19.8h + mov v27.s[1], v19.s[0] + saddl v18.4s, v12.4h, v28.4h + saddl2 v6.4s, v12.8h, v28.8h + + urhadd v26.8b, v26.8b , v27.8b + + smlal v18.4s, v30.4h, v22.4h + smlsl v18.4s, v10.4h, v24.4h + smlal2 v6.4s, v30.8h, v22.8h + smlsl2 v6.4s, v10.8h, v24.8h + + st1 {v26.2s}, [x1], x3 // store row 2 + + st1 {v28.2s, v29.2s}, [x9] + + + sqrshrun v18.4h, v18.4s, #10 + + mov v10.16b, v20.16b + mov v11.16b, v21.16b + ld1 {v30.4s}, [x7], x6 // load from temp buffer 3 + + sqrshrun v19.4h, v6.4s, #10 + subs x4, x4, #4 + + sqrshrun v30.8b, v30.8h, #5 + + uqxtn v18.8b, v18.8h + uqxtn v19.8b, v19.8h + mov v18.s[1], v19.s[0] + + mov v12.16b, v8.16b + mov v13.16b, v9.16b + mov v6.16b, v14.16b + mov v7.16b, v15.16b + + urhadd v30.8b, v18.8b , v30.8b + + mov v8.16b, v16.16b + mov v9.16b, v17.16b + mov v14.16b, v28.16b + mov v15.16b, v29.16b + + st1 {v30.2s}, [x1], x3 // store row 3 + + bgt loop_16_lowhalf // looping if height =16 + + +loop_16_highhalf_start: + ld1 {v0.2s, v1.2s}, [x8], x2 + ext v5.8b, v0.8b , v1.8b , #5 + uaddl v6.8h, v0.8b, v5.8b + ext v2.8b, v0.8b , v1.8b , #2 + ext v3.8b, v0.8b , v1.8b , #3 + uaddl v8.8h, v2.8b, v3.8b + ext v4.8b, v0.8b , v1.8b , #4 + mla v6.8h, v8.8h , v22.8h + ext v1.8b, v0.8b , v1.8b , #1 + uaddl v8.8h, v1.8b, v4.8b + ld1 {v0.2s, v1.2s}, [x8], x2 + mls v6.8h, v8.8h , v24.8h + ext v5.8b, v0.8b , v1.8b , #5 + uaddl v8.8h, v0.8b, v5.8b + ext v2.8b, v0.8b , v1.8b , #2 + ext v3.8b, v0.8b , v1.8b , #3 + uaddl v10.8h, v2.8b, v3.8b + + st1 {v6.4s}, [x10], x6 + + ext v4.8b, v0.8b , v1.8b , #4 + mla v8.8h, v10.8h , v22.8h + ext v1.8b, v0.8b , v1.8b , #1 + uaddl v10.8h, v1.8b, v4.8b + ld1 {v0.2s, v1.2s}, [x8], x2 + mls v8.8h, v10.8h , v24.8h + ext v5.8b, v0.8b , v1.8b , #5 + uaddl v10.8h, v0.8b, v5.8b + ext v2.8b, v0.8b , v1.8b , #2 + ext v3.8b, v0.8b , v1.8b , #3 + uaddl v12.8h, v2.8b, v3.8b + + st1 {v8.4s}, [x10], x6 + + ext v4.8b, v0.8b , v1.8b , #4 + mla v10.8h, v12.8h , v22.8h + ext v1.8b, v0.8b , v1.8b , #1 + uaddl v12.8h, v1.8b, v4.8b + ld1 {v0.2s, v1.2s}, [x8], x2 + mls v10.8h, v12.8h , v24.8h + ext v5.8b, v0.8b , v1.8b , #5 + uaddl v12.8h, v0.8b, v5.8b + ext v2.8b, v0.8b , v1.8b , #2 + ext v3.8b, v0.8b , v1.8b , #3 + uaddl v14.8h, v2.8b, v3.8b + + st1 {v10.4s}, [x10], x6 + + ext v4.8b, v0.8b , v1.8b , #4 + mla v12.8h, v14.8h , v22.8h + ext v1.8b, v0.8b , v1.8b , #1 + uaddl v14.8h, v1.8b, v4.8b + ld1 {v0.2s, v1.2s}, [x8], x2 + mls v12.8h, v14.8h , v24.8h + ext v5.8b, v0.8b , v1.8b , #5 + uaddl v14.8h, v0.8b, v5.8b + ext v2.8b, v0.8b , v1.8b , #2 + ext v3.8b, v0.8b , v1.8b , #3 + uaddl v16.8h, v2.8b, v3.8b + + st1 {v12.4s}, [x10], x6 + + ext v4.8b, v0.8b , v1.8b , #4 + mla v14.8h, v16.8h , v22.8h + ext v1.8b, v0.8b , v1.8b , #1 + uaddl v16.8h, v1.8b, v4.8b + + mls v14.8h, v16.8h , v24.8h + +loop_16_highhalf: + + ld1 {v0.2s, v1.2s}, [x8], x2 + ext v5.8b, v0.8b , v1.8b , #5 + ext v2.8b, v0.8b , v1.8b , #2 + ext v3.8b, v0.8b , v1.8b , #3 + uaddl v16.8h, v0.8b, v5.8b + + st1 {v14.4s}, [x10], x6 + + uaddl v18.8h, v2.8b, v3.8b + ext v4.8b, v0.8b , v1.8b , #4 + mla v16.8h, v18.8h , v22.8h + ext v1.8b, v0.8b , v1.8b , #1 + add v28.8h, v8.8h , v14.8h + uaddl v18.8h, v1.8b, v4.8b + add v30.8h, v10.8h , v12.8h + mls v16.8h, v18.8h , v24.8h + ld1 {v0.2s, v1.2s}, [x8], x2 + ext v5.8b, v0.8b , v1.8b , #5 + ext v2.8b, v0.8b , v1.8b , #2 + ext v3.8b, v0.8b , v1.8b , #3 + uaddl v20.8h, v0.8b, v5.8b + + st1 {v16.4s}, [x10], x6 + + saddl v18.4s, v6.4h, v16.4h + + ld1 {v26.4s}, [x11], x6 + + saddl2 v6.4s, v6.8h, v16.8h + + sqrshrun v26.8b, v26.8h, #5 + + smlal v18.4s, v30.4h, v22.4h + smlsl v18.4s, v28.4h, v24.4h + smlal2 v6.4s, v30.8h, v22.8h + smlsl2 v6.4s, v28.8h, v24.8h + uaddl v2.8h, v2.8b, v3.8b + ext v4.8b, v0.8b , v1.8b , #4 + mla v20.8h, v2.8h , v22.8h + sqrshrun v18.4h, v18.4s, #10 + ext v1.8b, v0.8b , v1.8b , #1 + sqrshrun v19.4h, v6.4s, #10 + add v28.8h, v10.8h , v16.8h + uaddl v2.8h, v1.8b, v4.8b + add v30.8h, v12.8h , v14.8h + mls v20.8h, v2.8h , v24.8h + uqxtn v18.8b, v18.8h + uqxtn v19.8b, v19.8h + mov v18.s[1], v19.s[0] + ld1 {v0.2s, v1.2s}, [x8], x2 + + urhadd v26.8b, v18.8b , v26.8b + + ext v5.8b, v0.8b , v1.8b , #5 + ext v2.8b, v0.8b , v1.8b , #2 + + st1 {v20.4s}, [x10], x6 + + saddl v18.4s, v8.4h, v20.4h + saddl2 v6.4s, v8.8h, v20.8h + + ld1 {v8.4s}, [x11], x6 + + + st1 {v26.2s}, [x14], x3 //store row 0 + + smlal v18.4s, v30.4h, v22.4h + smlsl v18.4s, v28.4h, v24.4h + smlal2 v6.4s, v30.8h, v22.8h + smlsl2 v6.4s, v28.8h, v24.8h + sqrshrun v28.8b, v8.8h, #5 + ext v3.8b, v0.8b , v1.8b , #3 + uaddl v8.8h, v0.8b, v5.8b + uaddl v2.8h, v2.8b, v3.8b + sqrshrun v18.4h, v18.4s, #10 + ext v4.8b, v0.8b , v1.8b , #4 + sqrshrun v19.4h, v6.4s, #10 + mla v8.8h, v2.8h , v22.8h + ext v1.8b, v0.8b , v1.8b , #1 + add v26.8h, v12.8h , v20.8h + uaddl v2.8h, v1.8b, v4.8b + uqxtn v18.8b, v18.8h + uqxtn v19.8b, v19.8h + mov v18.s[1], v19.s[0] + add v30.8h, v14.8h , v16.8h + mls v8.8h, v2.8h , v24.8h + ld1 {v0.2s, v1.2s}, [x8], x2 + + urhadd v28.8b, v28.8b , v18.8b + + ext v5.8b, v0.8b , v1.8b , #5 + ext v2.8b, v0.8b , v1.8b , #2 + ext v3.8b, v0.8b , v1.8b , #3 + + st1 {v28.2s}, [x14], x3 //store row 1 + + uaddl v28.8h, v0.8b, v5.8b + + st1 {v8.4s}, [x10], x6 + + saddl v18.4s, v10.4h, v8.4h + saddl2 v6.4s, v10.8h, v8.8h + + ld1 {v10.4s}, [x11], x6 + + smlal v18.4s, v30.4h, v22.4h + smlsl v18.4s, v26.4h, v24.4h + smlal2 v6.4s, v30.8h, v22.8h + smlsl2 v6.4s, v26.8h, v24.8h + + sqrshrun v26.8b, v10.8h, #5 + uaddl v2.8h, v2.8b, v3.8b + ext v4.8b, v0.8b , v1.8b , #4 + mla v28.8h, v2.8h , v22.8h + sqrshrun v18.4h, v18.4s, #10 + ext v1.8b, v0.8b , v1.8b , #1 + sqrshrun v19.4h, v6.4s, #10 + add v10.8h, v14.8h , v8.8h + uaddl v2.8h, v1.8b, v4.8b + add v30.8h, v16.8h , v20.8h + mls v28.8h, v2.8h , v24.8h + uqxtn v27.8b, v18.8h + uqxtn v19.8b, v19.8h + mov v27.s[1], v19.s[0] + + + saddl v18.4s, v12.4h, v28.4h + saddl2 v6.4s, v12.8h, v28.8h + + urhadd v26.8b, v26.8b , v27.8b + + smlal v18.4s, v30.4h, v22.4h + smlsl v18.4s, v10.4h, v24.4h + smlal2 v6.4s, v30.8h, v22.8h + smlsl2 v6.4s, v10.8h, v24.8h + + st1 {v26.2s}, [x14], x3 // store row 2 + + st1 {v28.4s}, [x10] + + sqrshrun v18.4h, v18.4s, #10 + mov v10.16b, v20.16b + mov v11.16b, v21.16b + ld1 {v30.4s}, [x11], x6 + + sqrshrun v19.4h, v6.4s, #10 + subs x12, x12, #4 + + sqrshrun v30.8b, v30.8h, #5 + + uqxtn v18.8b, v18.8h + uqxtn v19.8b, v19.8h + mov v18.s[1], v19.s[0] + + mov v12.16b, v8.16b + mov v13.16b, v9.16b + mov v6.16b, v14.16b + mov v7.16b, v15.16b + urhadd v30.8b, v18.8b , v30.8b + + mov v8.16b, v16.16b + mov v9.16b, v17.16b + mov v14.16b, v28.16b + mov v15.16b, v29.16b + st1 {v30.2s}, [x14], x3 // store row 3 + + bgt loop_16_highhalf // looping if height = 8 or 16 + b end_func + +loop_8_start: + + movi v22.8h, #0x14 // Filter coeff 20 into Q11 + movi v24.8h, #5 // Filter coeff 5 into Q12 + ld1 {v0.2s, v1.2s}, [x0], x2 // row -2 load for horizontal filter + ext v5.8b, v0.8b , v1.8b , #5 + uaddl v6.8h, v0.8b, v5.8b + + ext v2.8b, v0.8b , v1.8b , #2 + ext v3.8b, v0.8b , v1.8b , #3 + uaddl v8.8h, v2.8b, v3.8b + ext v4.8b, v0.8b , v1.8b , #4 + mla v6.8h, v8.8h , v22.8h + ext v1.8b, v0.8b , v1.8b , #1 + uaddl v8.8h, v1.8b, v4.8b + ld1 {v0.2s, v1.2s}, [x0], x2 // row -1 load for horizontal filter + mls v6.8h, v8.8h , v24.8h + ext v5.8b, v0.8b , v1.8b , #5 + uaddl v8.8h, v0.8b, v5.8b + ext v2.8b, v0.8b , v1.8b , #2 + ext v3.8b, v0.8b , v1.8b , #3 + uaddl v10.8h, v2.8b, v3.8b + + st1 {v6.4s}, [x9], x6 // store temp buffer 0 + + ext v4.8b, v0.8b , v1.8b , #4 + mla v8.8h, v10.8h , v22.8h + ext v1.8b, v0.8b , v1.8b , #1 + uaddl v10.8h, v1.8b, v4.8b + ld1 {v0.2s, v1.2s}, [x0], x2 // row 0 load for horizontal filter + mls v8.8h, v10.8h , v24.8h + ext v5.8b, v0.8b , v1.8b , #5 + uaddl v10.8h, v0.8b, v5.8b + ext v2.8b, v0.8b , v1.8b , #2 + ext v3.8b, v0.8b , v1.8b , #3 + uaddl v12.8h, v2.8b, v3.8b + + st1 {v8.4s}, [x9], x6 // store temp buffer 1 + + ext v4.8b, v0.8b , v1.8b , #4 + mla v10.8h, v12.8h , v22.8h + ext v1.8b, v0.8b , v1.8b , #1 + uaddl v12.8h, v1.8b, v4.8b + ld1 {v0.2s, v1.2s}, [x0], x2 // row 1 load for horizontal filter + mls v10.8h, v12.8h , v24.8h + ext v5.8b, v0.8b , v1.8b , #5 + uaddl v12.8h, v0.8b, v5.8b + ext v2.8b, v0.8b , v1.8b , #2 + ext v3.8b, v0.8b , v1.8b , #3 + uaddl v14.8h, v2.8b, v3.8b + + st1 {v10.4s}, [x9], x6 // store temp buffer 2 + + ext v4.8b, v0.8b , v1.8b , #4 + mla v12.8h, v14.8h , v22.8h + ext v1.8b, v0.8b , v1.8b , #1 + uaddl v14.8h, v1.8b, v4.8b + ld1 {v0.2s, v1.2s}, [x0], x2 // row 2 load for horizontal filter + mls v12.8h, v14.8h , v24.8h + ext v5.8b, v0.8b , v1.8b , #5 + uaddl v14.8h, v0.8b, v5.8b + ext v2.8b, v0.8b , v1.8b , #2 + ext v3.8b, v0.8b , v1.8b , #3 + uaddl v16.8h, v2.8b, v3.8b + + st1 {v12.4s}, [x9], x6 // store temp buffer 3 + + ext v4.8b, v0.8b , v1.8b , #4 + mla v14.8h, v16.8h , v22.8h + ext v1.8b, v0.8b , v1.8b , #1 + uaddl v16.8h, v1.8b, v4.8b + + mls v14.8h, v16.8h , v24.8h +loop_8: + + ld1 {v0.2s, v1.2s}, [x0], x2 // row 3 load for horizontal filter + ext v5.8b, v0.8b , v1.8b , #5 + ext v2.8b, v0.8b , v1.8b , #2 + ext v3.8b, v0.8b , v1.8b , #3 + uaddl v16.8h, v0.8b, v5.8b + + st1 {v14.4s}, [x9], x6 // store temp buffer 4 + + uaddl v18.8h, v2.8b, v3.8b + ext v4.8b, v0.8b , v1.8b , #4 + mla v16.8h, v18.8h , v22.8h + ext v1.8b, v0.8b , v1.8b , #1 + add v28.8h, v8.8h , v14.8h + uaddl v18.8h, v1.8b, v4.8b + add v30.8h, v10.8h , v12.8h + mls v16.8h, v18.8h , v24.8h + ld1 {v0.2s, v1.2s} , [x0], x2 // row 4 load for hoorizontal filter + ext v5.8b, v0.8b , v1.8b , #5 + ext v2.8b, v0.8b , v1.8b , #2 + ext v3.8b, v0.8b , v1.8b , #3 + uaddl v20.8h, v0.8b, v5.8b + + st1 {v16.4s}, [x9], x6 // store temp buffer x5 + + saddl v18.4s, v6.4h, v16.4h + + ld1 {v26.4s}, [x7], x6 // load from temp buffer 0 + + saddl2 v6.4s, v6.8h, v16.8h + + sqrshrun v26.8b, v26.8h, #5 + + smlal v18.4s, v30.4h, v22.4h + smlsl v18.4s, v28.4h, v24.4h + smlal2 v6.4s, v30.8h, v22.8h + smlsl2 v6.4s, v28.8h, v24.8h + uaddl v2.8h, v2.8b, v3.8b + ext v4.8b, v0.8b , v1.8b , #4 + mla v20.8h, v2.8h , v22.8h + sqrshrun v18.4h, v18.4s, #10 + ext v1.8b, v0.8b , v1.8b , #1 + sqrshrun v19.4h, v6.4s, #10 + add v28.8h, v10.8h , v16.8h + uaddl v2.8h, v1.8b, v4.8b + add v30.8h, v12.8h , v14.8h + mls v20.8h, v2.8h , v24.8h + + uqxtn v18.8b, v18.8h + uqxtn v19.8b, v19.8h + mov v18.s[1], v19.s[0] + + ld1 {v0.2s, v1.2s}, [x0], x2 // row 5 load for horizontal filter + + urhadd v26.8b, v18.8b , v26.8b + + ext v5.8b, v0.8b , v1.8b , #5 + ext v2.8b, v0.8b , v1.8b , #2 + + st1 {v20.4s}, [x9], x6 // store temp buffer x6 + + saddl v18.4s, v8.4h, v20.4h + + saddl2 v6.4s, v8.8h, v20.8h + + ld1 {v8.4s}, [x7], x6 //load from temp buffer 1 + + + st1 {v26.2s}, [x1], x3 // store row 0 + + smlal v18.4s, v30.4h, v22.4h + smlsl v18.4s, v28.4h, v24.4h + + + + smlal2 v6.4s, v30.8h, v22.8h + smlsl2 v6.4s, v28.8h, v24.8h + + sqrshrun v28.8b, v8.8h, #5 + + ext v3.8b, v0.8b , v1.8b , #3 + uaddl v8.8h, v0.8b, v5.8b + uaddl v2.8h, v2.8b, v3.8b + sqrshrun v18.4h, v18.4s, #10 + ext v4.8b, v0.8b , v1.8b , #4 + sqrshrun v19.4h, v6.4s, #10 + mla v8.8h, v2.8h , v22.8h + ext v1.8b, v0.8b , v1.8b , #1 + add v26.8h, v12.8h , v20.8h + uaddl v2.8h, v1.8b, v4.8b + + + uqxtn v18.8b, v18.8h + uqxtn v19.8b, v19.8h + mov v18.s[1], v19.s[0] + + add v30.8h, v14.8h , v16.8h + mls v8.8h, v2.8h , v24.8h + ld1 {v0.2s, v1.2s}, [x0], x2 // row 6 load for horizontal filter + + urhadd v28.8b, v28.8b , v18.8b + + ext v5.8b, v0.8b , v1.8b , #5 + ext v2.8b, v0.8b , v1.8b , #2 + ext v3.8b, v0.8b , v1.8b , #3 + + st1 {v28.2s}, [x1], x3 // store row 1 + + uaddl v28.8h, v0.8b, v5.8b + + st1 {v8.4s}, [x9], x6 // store temp buffer x7 + + saddl v18.4s, v10.4h, v8.4h + saddl2 v6.4s, v10.8h, v8.8h + + ld1 {v10.4s}, [x7], x6 // load from temp buffer 2 + + smlal v18.4s, v30.4h, v22.4h + smlsl v18.4s, v26.4h, v24.4h + smlal2 v6.4s, v30.8h, v22.8h + smlsl2 v6.4s, v26.8h, v24.8h + + sqrshrun v26.8b, v10.8h, #5 + uaddl v2.8h, v2.8b, v3.8b + ext v4.8b, v0.8b , v1.8b , #4 + mla v28.8h, v2.8h , v22.8h + sqrshrun v18.4h, v18.4s, #10 + ext v1.8b, v0.8b , v1.8b , #1 + sqrshrun v19.4h, v6.4s, #10 + add v10.8h, v14.8h , v8.8h + uaddl v2.8h, v1.8b, v4.8b + add v30.8h, v16.8h , v20.8h + mls v28.8h, v2.8h , v24.8h + + uqxtn v27.8b, v18.8h + uqxtn v19.8b, v19.8h + + mov v27.s[1], v19.s[0] + + saddl v18.4s, v12.4h, v28.4h + saddl2 v6.4s, v12.8h, v28.8h + + urhadd v26.8b, v26.8b , v27.8b + + smlal v18.4s, v30.4h, v22.4h + smlsl v18.4s, v10.4h, v24.4h + smlal2 v6.4s, v30.8h, v22.8h + smlsl2 v6.4s, v10.8h, v24.8h + + st1 {v26.2s}, [x1], x3 // store row 2 + + st1 {v28.2s, v29.2s}, [x9] + + + sqrshrun v18.4h, v18.4s, #10 + mov v10.16b, v20.16b + mov v11.16b, v21.16b + ld1 {v30.4s}, [x7], x6 // load from temp buffer 3 + + sqrshrun v19.4h, v6.4s, #10 + subs x4, x4, #4 + + sqrshrun v30.8b, v30.8h, #5 + + + uqxtn v18.8b, v18.8h + uqxtn v19.8b, v19.8h + mov v18.s[1], v19.s[0] + + + mov v12.16b, v8.16b + mov v13.16b, v9.16b + mov v6.16b, v14.16b + mov v7.16b, v15.16b + + urhadd v30.8b, v18.8b , v30.8b + mov v8.16b, v16.16b + mov v9.16b, v17.16b + mov v14.16b, v28.16b + mov v15.16b, v29.16b + st1 {v30.2s}, [x1], x3 // store row 3 + + bgt loop_8 //if height =8 or 16 loop + b end_func + +loop_4_start: + movi v22.8h, #20 // Filter coeff 20 into D22 + movi v23.8h, #5 // Filter coeff 5 into D23 + + ld1 {v0.2s, v1.2s}, [x0], x2 //row -2 load + ext v5.8b, v0.8b , v1.8b , #5 + uaddl v6.8h, v0.8b, v5.8b + ext v2.8b, v0.8b , v1.8b , #2 + ext v3.8b, v0.8b , v1.8b , #3 + uaddl v8.8h, v2.8b, v3.8b + ext v4.8b, v0.8b , v1.8b , #4 + mla v6.4h, v8.4h , v22.4h + ext v1.8b, v0.8b , v1.8b , #1 + uaddl v8.8h, v1.8b, v4.8b + ld1 {v0.2s, v1.2s}, [x0], x2 // row -1 load + mls v6.4h, v8.4h , v23.4h + ext v5.8b, v0.8b , v1.8b , #5 + uaddl v8.8h, v0.8b, v5.8b + ext v2.8b, v0.8b , v1.8b , #2 + ext v3.8b, v0.8b , v1.8b , #3 + uaddl v10.8h, v2.8b, v3.8b + + st1 {v6.2s}, [x9], x6 // store temp buffer 0 + + ext v4.8b, v0.8b , v1.8b , #4 + mla v8.4h, v10.4h , v22.4h + ext v1.8b, v0.8b , v1.8b , #1 + uaddl v10.8h, v1.8b, v4.8b + ld1 {v0.2s, v1.2s}, [x0], x2 // row 0 load + mls v8.4h, v10.4h , v23.4h + ext v5.8b, v0.8b , v1.8b , #5 + uaddl v10.8h, v0.8b, v5.8b + ext v2.8b, v0.8b , v1.8b , #2 + ext v3.8b, v0.8b , v1.8b , #3 + uaddl v12.8h, v2.8b, v3.8b + + st1 {v8.2s}, [x9], x6 // store temp buffer 1 + + ext v4.8b, v0.8b , v1.8b , #4 + mla v10.4h, v12.4h , v22.4h + ext v1.8b, v0.8b , v1.8b , #1 + uaddl v12.8h, v1.8b, v4.8b + ld1 {v0.2s, v1.2s}, [x0], x2 // row 1 load + mls v10.4h, v12.4h , v23.4h + ext v5.8b, v0.8b , v1.8b , #5 + uaddl v12.8h, v0.8b, v5.8b + ext v2.8b, v0.8b , v1.8b , #2 + ext v3.8b, v0.8b , v1.8b , #3 + uaddl v14.8h, v2.8b, v3.8b + + st1 {v10.2s}, [x9], x6 // store temp buffer 2 + + ext v4.8b, v0.8b , v1.8b , #4 + mla v12.4h, v14.4h , v22.4h + ext v1.8b, v0.8b , v1.8b , #1 + uaddl v14.8h, v1.8b, v4.8b + ld1 {v0.2s, v1.2s}, [x0], x2 // row 2 load + mls v12.4h, v14.4h , v23.4h + ext v5.8b, v0.8b , v1.8b , #5 + uaddl v14.8h, v0.8b, v5.8b + ext v2.8b, v0.8b , v1.8b , #2 + ext v3.8b, v0.8b , v1.8b , #3 + uaddl v16.8h, v2.8b, v3.8b + ext v4.8b, v0.8b , v1.8b , #4 + mla v14.4h, v16.4h , v22.4h + ext v1.8b, v0.8b , v1.8b , #1 + uaddl v16.8h, v1.8b, v4.8b + + st1 {v12.2s}, [x9], x6 // store temp buffer 3 + + mls v14.4h, v16.4h , v23.4h + +loop_4: + + ld1 {v0.2s, v1.2s}, [x0], x2 // row 3 load + ext v5.8b, v0.8b , v1.8b , #5 + uaddl v16.8h, v0.8b, v5.8b + ext v2.8b, v0.8b , v1.8b , #2 + ext v3.8b, v0.8b , v1.8b , #3 + uaddl v18.8h, v2.8b, v3.8b + st1 {v14.2s}, [x9], x6 // store temp buffer 4 + ext v4.8b, v0.8b , v1.8b , #4 + mla v16.4h, v18.4h , v22.4h + ext v1.8b, v0.8b , v1.8b , #1 + uaddl v18.8h, v1.8b, v4.8b + add v2.4h, v10.4h , v12.4h + mls v16.4h, v18.4h , v23.4h + add v3.4h, v8.4h , v14.4h + ld1 {v18.2s, v19.2s}, [x0], x2 // row 4 load + ext v25.8b, v18.8b , v19.8b , #5 + uaddl v26.8h, v18.8b, v25.8b + ext v20.8b, v18.8b , v19.8b , #2 + + st1 {v16.2s}, [x9], x6 // store temp buffer 5 + + saddl v0.4s, v6.4h, v16.4h + smlal v0.4s, v2.4h, v22.4h + ext v21.8b, v18.8b , v19.8b , #3 + uaddl v28.8h, v20.8b, v21.8b + ext v24.8b, v18.8b , v19.8b , #4 + smlsl v0.4s, v3.4h, v23.4h + mla v26.4h, v28.4h , v22.4h + ext v19.8b, v18.8b , v19.8b , #1 + uaddl v28.8h, v19.8b, v24.8b + add v2.4h, v12.4h , v14.4h + mls v26.4h, v28.4h , v23.4h + sqrshrun v0.4h, v0.4s, #0xa + add v3.4h, v10.4h , v16.4h + ld1 {v18.2s, v19.2s}, [x0], x2 // row 5 load + ext v25.8b, v18.8b , v19.8b , #5 + uqxtn v11.8b, v0.8h + uaddl v28.8h, v18.8b, v25.8b + + st1 {v26.2s}, [x9], x6 // store temp buffer 6 + + //Q3 available here + ld1 {v6.2s}, [x7], x6 // load from temp buffer 0 + ld1 {v7.2s}, [x7], x6 // load from temp buffer 1 + + sqrshrun v9.8b, v6.8h, #5 + sqrshrun v7.8b, v7.8h, #5 + mov v9.s[1], v7.s[0] + + ext v20.8b, v18.8b , v19.8b , #2 + + saddl v0.4s, v8.4h, v26.4h + smlal v0.4s, v2.4h, v22.4h + ext v21.8b, v18.8b , v19.8b , #3 + uaddl v6.8h, v20.8b, v21.8b + ext v24.8b, v18.8b , v19.8b , #4 + smlsl v0.4s, v3.4h, v23.4h + mla v28.4h, v6.4h , v22.4h + ext v19.8b, v18.8b , v19.8b , #1 + uaddl v6.8h, v19.8b, v24.8b + add v2.4h, v14.4h , v16.4h + mls v28.4h, v6.4h , v23.4h + sqrshrun v0.4h, v0.4s, #0xa + add v3.4h, v12.4h , v26.4h + ld1 {v18.2s, v19.2s}, [x0], x2 // row 6 load + ext v25.8b, v18.8b , v19.8b , #5 + uqxtn v13.8b, v0.8h + + trn1 v11.2s, v11.2s, v13.2s + trn2 v13.2s, v11.2s, v13.2s + saddl v0.4s, v10.4h, v28.4h + urhadd v9.8b, v9.8b , v11.8b + + st1 {v28.2s}, [x9], x6 // store temp buffer 7 + + smlal v0.4s, v2.4h, v22.4h + uaddl v30.8h, v18.8b, v25.8b + + st1 {v9.s}[0], [x1], x3 // store row 0 + + ext v20.8b, v18.8b , v19.8b , #2 + + st1 {v9.s}[1], [x1], x3 // store row 1 + + ext v21.8b, v18.8b , v19.8b , #3 + smlsl v0.4s, v3.4h, v23.4h + uaddl v8.8h, v20.8b, v21.8b + ext v24.8b, v18.8b , v19.8b , #4 + mla v30.4h, v8.4h , v22.4h + ext v19.8b, v18.8b , v19.8b , #1 + uaddl v8.8h, v19.8b, v24.8b + sqrshrun v0.4h, v0.4s, #0xa + add v2.4h, v16.4h , v26.4h + mls v30.4h, v8.4h , v23.4h + uqxtn v4.8b, v0.8h + + add v3.4h, v14.4h , v28.4h + + + saddl v0.4s, v12.4h, v30.4h + + st1 {v30.2s}, [x9] + + smlal v0.4s, v2.4h, v22.4h + + ld1 {v8.2s}, [x7], x6 // load from temp buffer 2 + ld1 {v9.2s}, [x7], x6 // load from temp buffer 3 + smlsl v0.4s, v3.4h, v23.4h + subs x4, x4, #4 + + sqrshrun v10.8b, v8.8h, #5 + sqrshrun v9.8b, v9.8h, #5 + mov v10.s[1], v9.s[0] + + mov v12.8b, v28.8b + + sqrshrun v0.4h, v0.4s, #0xa + mov v6.8b, v14.8b + mov v8.8b, v16.8b + + uqxtn v5.8b, v0.8h + + trn1 v4.2s, v4.2s, v5.2s + trn2 v5.2s, v4.2s, v5.2s + urhadd v4.8b, v4.8b , v10.8b + mov v10.8b, v26.8b + mov v14.8b, v30.8b + + st1 {v4.s}[0], [x1], x3 // store row 2 + st1 {v4.s}[1], [x1], x3 // store row 3 + + bgt loop_4 + +end_func: + //Restoring registers from stack + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + + diff --git a/dependencies/ih264d/common/armv8/ih264_inter_pred_luma_horz_qpel_av8.s b/dependencies/ih264d/common/armv8/ih264_inter_pred_luma_horz_qpel_av8.s new file mode 100644 index 00000000..38268c75 --- /dev/null +++ b/dependencies/ih264d/common/armv8/ih264_inter_pred_luma_horz_qpel_av8.s @@ -0,0 +1,601 @@ +//****************************************************************************** +//* +//* Copyright (C) 2015 The Android Open Source Project +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at: +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. +//* +//***************************************************************************** +//* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +//*/ +///** +//****************************************************************************** +//* @file +//* ih264_inter_pred_luma_horz_qpel_av8.s +//* +//* @brief +//* Contains function definitions for inter prediction horizontal quarter pel interpolation. +//* +//* @author +//* Mohit +//* +//* @par List of Functions: +//* +//* - ih264_inter_pred_luma_horz_qpel_av8() +//* +//* @remarks +//* None +//* +//******************************************************************************* +//*/ + +///* All the functions here are replicated from ih264_inter_pred_filters.c +// + +///** +///** +//******************************************************************************* +//* +//* @brief +//* Quarter pel interprediction luma filter for horizontal input +//* +//* @par Description: +//* Applies a 6 tap horizontal filter .The output is clipped to 8 bits +//* sec 8.4.2.2.1 titled "Luma sample interpolation process" +//* +//* @param[in] pu1_src +//* UWORD8 pointer to the source +//* +//* @param[out] pu1_dst +//* UWORD8 pointer to the destination +//* +//* @param[in] src_strd +//* integer source stride +//* +//* @param[in] dst_strd +//* integer destination stride +//* +//* @param[in] ht +//* integer height of the array +//* +//* @param[in] wd +//* integer width of the array +//* +// @param[in] pu1_tmp: temporary buffer: UNUSED in this function +//* +//* @param[in] dydx: x and y reference offset for qpel calculations. +//* @returns +//* +// @remarks +//* None +//* +//******************************************************************************* +//*/ + +//void ih264_inter_pred_luma_horz ( +// UWORD8 *pu1_src, +// UWORD8 *pu1_dst, +// WORD32 src_strd, +// WORD32 dst_strd, +// WORD32 ht, +// WORD32 wd, +// UWORD8* pu1_tmp, +// UWORD32 dydx) + +//**************Variables Vs Registers***************************************** +// x0 => *pu1_src +// x1 => *pu1_dst +// w2 => src_strd +// w3 => dst_strd +// w4 => ht +// w5 => wd +// w7 => dydx + +.text +.p2align 2 +.include "ih264_neon_macros.s" + + + + + .global ih264_inter_pred_luma_horz_qpel_av8 + +ih264_inter_pred_luma_horz_qpel_av8: + + + push_v_regs + stp x19, x20, [sp, #-16]! + sxtw x2, w2 + sxtw x3, w3 + sxtw x4, w4 + sxtw x5, w5 + + + and x7, x7, #3 //Finds x-offset + add x7, x0, x7, lsr #1 //pu1_src + (x_offset>>1) + sub x0, x0, #2 //pu1_src-2 + sub x14, x4, #16 + movi v0.16b, #5 //filter coeff + subs x12, x5, #8 //if wd=8 branch to loop_8 + movi v1.16b, #20 //filter coeff + + beq loop_8 + + subs x12, x5, #4 //if wd=4 branch to loop_4 + beq loop_4 + +loop_16: //when wd=16 + //// Processing row0 and row1 + ld1 {v2.8b, v3.8b, v4.8b}, [x0], x2 //// Load row0 + add x14, x14, #1 //for checking loop + ext v31.8b, v2.8b , v3.8b , #5 + ld1 {v5.8b, v6.8b, v7.8b}, [x0], x2 //// Load row1 + ext v30.8b, v3.8b , v4.8b , #5 + uaddl v8.8h, v31.8b, v2.8b //// a0 + a5 (column1,row0) + ext v28.8b, v5.8b , v6.8b , #5 + uaddl v10.8h, v30.8b, v3.8b //// a0 + a5 (column2,row0) + ext v27.8b, v6.8b , v7.8b , #5 + uaddl v14.8h, v28.8b, v5.8b //// a0 + a5 (column1,row1) + ext v31.8b, v2.8b , v3.8b , #2 + uaddl v16.8h, v27.8b, v6.8b //// a0 + a5 (column2,row1) + ext v30.8b, v3.8b , v4.8b , #2 + umlal v8.8h, v31.8b, v1.8b //// a0 + a5 + 20a2 (column1,row0) + ext v28.8b, v5.8b , v6.8b , #2 + umlal v10.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 (column2,row0) + ext v27.8b, v6.8b , v7.8b , #2 + umlal v14.8h, v28.8b, v1.8b //// a0 + a5 + 20a2 (column1,row1) + ext v31.8b, v2.8b , v3.8b , #3 + umlal v16.8h, v27.8b, v1.8b //// a0 + a5 + 20a2 (column2,row1) + ext v30.8b, v3.8b , v4.8b , #3 + umlal v8.8h, v31.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row0) + ext v28.8b, v5.8b , v6.8b , #3 + umlal v10.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column2,row0) + ext v27.8b, v6.8b , v7.8b , #3 + umlal v14.8h, v28.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row1) + ext v31.8b, v2.8b , v3.8b , #1 + umlal v16.8h, v27.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column2,row1) + ext v30.8b, v3.8b , v4.8b , #1 + umlsl v8.8h, v31.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row0) + ext v28.8b, v5.8b , v6.8b , #1 + umlsl v10.8h, v30.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column2,row0) + ext v27.8b, v6.8b , v7.8b , #1 + umlsl v14.8h, v28.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row1) + ext v31.8b, v2.8b , v3.8b , #4 + umlsl v16.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column2,row1) + ext v30.8b, v3.8b , v4.8b , #4 + umlsl v8.8h, v31.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row0) + ext v28.8b, v5.8b , v6.8b , #4 + umlsl v10.8h, v30.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2,row0) + ext v27.8b, v6.8b , v7.8b , #4 + umlsl v14.8h, v28.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row1) + ld1 {v2.8b, v3.8b, v4.8b}, [x0], x2 //// Load row2 + umlsl v16.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2,row1) + + ld1 {v12.2s, v13.2s}, [x7], x2 //Load value for interpolation (column1,row0) + sqrshrun v20.8b, v8.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row0) + ld1 {v5.8b, v6.8b, v7.8b}, [x0], x2 //// Load row3 + sqrshrun v21.8b, v10.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column2,row0) + ext v31.8b, v2.8b , v3.8b , #5 + urhadd v20.16b, v12.16b , v20.16b //Interpolation step for qpel calculation + urhadd v21.16b, v13.16b , v21.16b //Interpolation step for qpel calculation + + sqrshrun v18.8b, v14.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row1) + st1 {v20.8b, v21.8b}, [x1], x3 ////Store dest row0 + ext v30.8b, v3.8b , v4.8b , #5 + sqrshrun v19.8b, v16.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column2,row1) + + + +//// Processing row2 and row3 + ld1 {v12.2s, v13.2s}, [x7], x2 //Load value for interpolation (column1,row1) + ext v28.8b, v5.8b , v6.8b , #5 + urhadd v18.16b, v12.16b , v18.16b //Interpolation step for qpel calculation + urhadd v19.16b, v13.16b , v19.16b //Interpolation step for qpel calculation + + uaddl v8.8h, v31.8b, v2.8b //// a0 + a5 (column1,row2) + st1 {v18.8b, v19.8b}, [x1], x3 ////Store dest row1 + uaddl v10.8h, v30.8b, v3.8b //// a0 + a5 (column2,row2) + ext v27.8b, v6.8b , v7.8b , #5 + uaddl v14.8h, v28.8b, v5.8b //// a0 + a5 (column1,row3) + ext v31.8b, v2.8b , v3.8b , #2 + uaddl v16.8h, v27.8b, v6.8b //// a0 + a5 (column2,row3) + ext v30.8b, v3.8b , v4.8b , #2 + umlal v8.8h, v31.8b, v1.8b //// a0 + a5 + 20a2 (column1,row2) + ext v27.8b, v6.8b , v7.8b , #2 + umlal v10.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 (column2,row2) + ext v28.8b, v5.8b , v6.8b , #2 + umlal v14.8h, v28.8b, v1.8b //// a0 + a5 + 20a2 (column1,row3) + ext v31.8b, v2.8b , v3.8b , #3 + umlal v16.8h, v27.8b, v1.8b //// a0 + a5 + 20a2 (column2,row3) + ext v30.8b, v3.8b , v4.8b , #3 + umlal v8.8h, v31.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row2) + ext v28.8b, v5.8b , v6.8b , #3 + umlal v10.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column2,row2) + ext v27.8b, v6.8b , v7.8b , #3 + umlal v14.8h, v28.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row3) + ext v31.8b, v2.8b , v3.8b , #1 + umlal v16.8h, v27.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column2,row3) + ext v30.8b, v3.8b , v4.8b , #1 + umlsl v8.8h, v31.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row2) + ext v28.8b, v5.8b , v6.8b , #1 + umlsl v10.8h, v30.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column2,row2) + ext v27.8b, v6.8b , v7.8b , #1 + umlsl v14.8h, v28.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row3) + ext v31.8b, v2.8b , v3.8b , #4 + umlsl v16.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column2,row3) + ext v30.8b, v3.8b , v4.8b , #4 + umlsl v8.8h, v31.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row2) + ext v28.8b, v5.8b , v6.8b , #4 + umlsl v10.8h, v30.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2,row2) + ext v27.8b, v6.8b , v7.8b , #4 + umlsl v14.8h, v28.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row3) + ld1 {v2.8b, v3.8b, v4.8b}, [x0], x2 //// Load row4 + umlsl v16.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2,row3) + + ld1 {v12.2s, v13.2s}, [x7], x2 //Load value for interpolation (column1,row2) + sqrshrun v20.8b, v8.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row2) + ld1 {v5.8b, v6.8b, v7.8b}, [x0], x2 //// Load row5 + sqrshrun v21.8b, v10.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column2,row2) + ext v31.8b, v2.8b , v3.8b , #5 + urhadd v20.16b, v12.16b , v20.16b //Interpolation step for qpel calculation + urhadd v21.16b, v13.16b , v21.16b //Interpolation step for qpel calculation + + sqrshrun v18.8b, v14.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row3) + ext v30.8b, v3.8b , v4.8b , #5 + st1 {v20.8b, v21.8b}, [x1], x3 ////Store dest row2 + sqrshrun v19.8b, v16.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column2,row3) + ld1 {v12.2s, v13.2s}, [x7], x2 //Load value for interpolation (column1,row3) + +//// Processing row4 and row5 + ext v28.8b, v5.8b , v6.8b , #5 + urhadd v18.16b, v12.16b , v18.16b //Interpolation step for qpel calculation + urhadd v19.16b, v13.16b , v19.16b //Interpolation step for qpel calculation + + uaddl v8.8h, v31.8b, v2.8b //// a0 + a5 (column1,row4) + st1 {v18.8b, v19.8b}, [x1], x3 ////Store dest row3 + uaddl v10.8h, v30.8b, v3.8b //// a0 + a5 (column2,row4) + ext v27.8b, v6.8b , v7.8b , #5 + uaddl v14.8h, v28.8b, v5.8b //// a0 + a5 (column1,row5) + ext v31.8b, v2.8b , v3.8b , #2 + uaddl v16.8h, v27.8b, v6.8b //// a0 + a5 (column2,row5) + ext v30.8b, v3.8b , v4.8b , #2 + umlal v8.8h, v31.8b, v1.8b //// a0 + a5 + 20a2 (column1,row4) + ext v27.8b, v6.8b , v7.8b , #2 + umlal v10.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 (column2,row4) + ext v28.8b, v5.8b , v6.8b , #2 + umlal v14.8h, v28.8b, v1.8b //// a0 + a5 + 20a2 (column1,row5) + ext v31.8b, v2.8b , v3.8b , #3 + umlal v16.8h, v27.8b, v1.8b //// a0 + a5 + 20a2 (column2,row5) + ext v30.8b, v3.8b , v4.8b , #3 + umlal v8.8h, v31.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row4) + ext v28.8b, v5.8b , v6.8b , #3 + umlal v10.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column2,row4) + ext v27.8b, v6.8b , v7.8b , #3 + umlal v14.8h, v28.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row5) + ext v31.8b, v2.8b , v3.8b , #1 + umlal v16.8h, v27.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column2,row5) + ext v30.8b, v3.8b , v4.8b , #1 + umlsl v8.8h, v31.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row4) + ext v28.8b, v5.8b , v6.8b , #1 + umlsl v10.8h, v30.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column2,row4) + ext v27.8b, v6.8b , v7.8b , #1 + umlsl v14.8h, v28.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row4) + ext v31.8b, v2.8b , v3.8b , #4 + umlsl v16.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column2,row5) + ext v30.8b, v3.8b , v4.8b , #4 + umlsl v8.8h, v31.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row4) + ext v28.8b, v5.8b , v6.8b , #4 + umlsl v10.8h, v30.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2,row4) + ext v27.8b, v6.8b , v7.8b , #4 + umlsl v14.8h, v28.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row5) + ld1 {v2.8b, v3.8b, v4.8b}, [x0], x2 //// Load row6 + umlsl v16.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2,row5) + ld1 {v12.2s, v13.2s}, [x7], x2 //Load value for interpolation (column1,row4) + sqrshrun v20.8b, v8.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row4) + ld1 {v5.8b, v6.8b, v7.8b}, [x0], x2 //// Load row7 + sqrshrun v21.8b, v10.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column2,row4) + ext v31.8b, v2.8b , v3.8b , #5 + urhadd v20.16b, v12.16b , v20.16b //Interpolation step for qpel calculation + urhadd v21.16b, v13.16b , v21.16b //Interpolation step for qpel calculation + + sqrshrun v18.8b, v14.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row5) + st1 {v20.8b, v21.8b}, [x1], x3 ////Store dest row4 + ext v30.8b, v3.8b , v4.8b , #5 + sqrshrun v19.8b, v16.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column2,row5) + ld1 {v12.2s, v13.2s}, [x7], x2 //Load value for interpolation (column1,row5) + + + //// Processing row6 and row7 + + ext v28.8b, v5.8b , v6.8b , #5 + urhadd v18.16b, v12.16b , v18.16b //Interpolation step for qpel calculation + urhadd v19.16b, v13.16b , v19.16b //Interpolation step for qpel calculation + + uaddl v8.8h, v31.8b, v2.8b //// a0 + a5 (column1,row6) + st1 {v18.8b, v19.8b}, [x1], x3 ////Store dest row5 + uaddl v10.8h, v30.8b, v3.8b //// a0 + a5 (column2,row6) + ext v27.8b, v6.8b , v7.8b , #5 + uaddl v14.8h, v28.8b, v5.8b //// a0 + a5 (column1,row7) + ext v31.8b, v2.8b , v3.8b , #2 + uaddl v16.8h, v27.8b, v6.8b //// a0 + a5 (column2,row7) + ext v30.8b, v3.8b , v4.8b , #2 + umlal v8.8h, v31.8b, v1.8b //// a0 + a5 + 20a2 (column1,row6) + ext v27.8b, v6.8b , v7.8b , #2 + umlal v10.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 (column2,row6) + ext v28.8b, v5.8b , v6.8b , #2 + umlal v14.8h, v28.8b, v1.8b //// a0 + a5 + 20a2 (column1,row7) + ext v31.8b, v2.8b , v3.8b , #3 + umlal v16.8h, v27.8b, v1.8b //// a0 + a5 + 20a2 (column2,row7) + ext v30.8b, v3.8b , v4.8b , #3 + umlal v8.8h, v31.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row6) + ext v28.8b, v5.8b , v6.8b , #3 + umlal v10.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column2,row6) + ext v27.8b, v6.8b , v7.8b , #3 + umlal v14.8h, v28.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row7) + ext v31.8b, v2.8b , v3.8b , #1 + umlal v16.8h, v27.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column2,row7) + ext v30.8b, v3.8b , v4.8b , #1 + umlsl v8.8h, v31.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row6) + ext v28.8b, v5.8b , v6.8b , #1 + umlsl v10.8h, v30.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column2,row6) + ext v27.8b, v6.8b , v7.8b , #1 + umlsl v14.8h, v28.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row6) + ext v31.8b, v2.8b , v3.8b , #4 + umlsl v16.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column2,row7) + ext v30.8b, v3.8b , v4.8b , #4 + umlsl v8.8h, v31.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row6) + ext v28.8b, v5.8b , v6.8b , #4 + umlsl v10.8h, v30.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2,row6) + ext v27.8b, v6.8b , v7.8b , #4 + ld1 {v12.2s, v13.2s}, [x7], x2 //Load value for interpolation (column1,row6) + sqrshrun v20.8b, v8.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row6) + umlsl v14.8h, v28.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row7) + sqrshrun v21.8b, v10.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column2,row6) + umlsl v16.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column2,row7) + urhadd v20.16b, v12.16b , v20.16b //Interpolation step for qpel calculation + urhadd v21.16b, v13.16b , v21.16b //Interpolation step for qpel calculation + + ld1 {v12.2s, v13.2s}, [x7], x2 //Load value for interpolation (column1,row7) + sqrshrun v18.8b, v14.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row7) + st1 {v20.8b, v21.8b}, [x1], x3 ////Store dest row6 + sqrshrun v19.8b, v16.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column2,row7) + urhadd v18.16b, v12.16b , v18.16b //Interpolation step for qpel calculation + urhadd v19.16b, v13.16b , v19.16b //Interpolation step for qpel calculation + + subs x12, x14, #1 // if height==16 - looping + st1 {v18.8b, v19.8b}, [x1], x3 ////Store dest row7 + + + + beq loop_16 + b end_func + +loop_8: +//// Processing row0 and row1 + + ld1 {v5.8b, v6.8b}, [x0], x2 //// Load row1 + add x14, x14, #1 //for checking loop + ext v28.8b, v5.8b , v6.8b , #5 + ld1 {v2.8b, v3.8b}, [x0], x2 //// Load row0 + ext v25.8b, v5.8b , v6.8b , #2 + ext v31.8b, v2.8b , v3.8b , #5 + ext v24.8b, v5.8b , v6.8b , #3 + ext v23.8b, v5.8b , v6.8b , #1 + ext v22.8b, v5.8b , v6.8b , #4 + uaddl v14.8h, v28.8b, v5.8b //// a0 + a5 (column1,row1) + ext v29.8b, v2.8b , v3.8b , #3 + umlal v14.8h, v25.8b, v1.8b //// a0 + a5 + 20a2 (column1,row1) + umlal v14.8h, v24.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row1) + umlsl v14.8h, v23.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row1) + umlsl v14.8h, v22.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row1) + ext v30.8b, v2.8b , v3.8b , #2 + uaddl v8.8h, v31.8b, v2.8b //// a0 + a5 (column1,row0) + ext v27.8b, v2.8b , v3.8b , #1 + ext v26.8b, v2.8b , v3.8b , #4 + ld1 {v2.8b, v3.8b}, [x0], x2 //// Load row2 + umlal v8.8h, v29.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row0) + umlal v8.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 (column1,row0) + umlsl v8.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row0) + umlsl v8.8h, v26.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row0) + ld1 {v5.8b, v6.8b}, [x0], x2 //// Load row3 + sqrshrun v18.8b, v14.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row0) + + //// Processing row2 and row3 + ext v28.8b, v5.8b , v6.8b , #5 + ext v25.8b, v5.8b , v6.8b , #2 + ext v31.8b, v2.8b , v3.8b , #5 + uaddl v14.8h, v28.8b, v5.8b //// a0 + a5 (column1,row3) + ld1 {v12.2s}, [x7], x2 //Load value for interpolation (column1,row0) + ld1 {v13.2s}, [x7], x2 //Load value for interpolation (column1,row1) + ext v24.8b, v5.8b , v6.8b , #3 + ext v23.8b, v5.8b , v6.8b , #1 + sqrshrun v19.8b, v8.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row1) + ext v22.8b, v5.8b , v6.8b , #4 + ext v29.8b, v2.8b , v3.8b , #3 + umlal v14.8h, v25.8b, v1.8b //// a0 + a5 + 20a2 (column1,row3) + umlal v14.8h, v24.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row3) + umlsl v14.8h, v23.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row3) + umlsl v14.8h, v22.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row3) + urhadd v18.16b, v12.16b , v18.16b //Interpolation step for qpel calculation + urhadd v19.16b, v13.16b , v19.16b //Interpolation step for qpel calculation + + st1 {v18.8b}, [x1], x3 ////Store dest row0 + st1 {v19.8b}, [x1], x3 ////Store dest row1 + uaddl v8.8h, v31.8b, v2.8b //// a0 + a5 (column1,row2) + ext v30.8b, v2.8b , v3.8b , #2 + ext v27.8b, v2.8b , v3.8b , #1 + ext v26.8b, v2.8b , v3.8b , #4 + ld1 {v2.8b, v3.8b}, [x0], x2 //// Load row4 + umlal v8.8h, v29.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row2) + umlal v8.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 (column1,row2) + umlsl v8.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row2) + umlsl v8.8h, v26.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row2) + ld1 {v5.8b, v6.8b}, [x0], x2 //// Load row5 + subs x9, x4, #4 + sqrshrun v19.8b, v14.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row3) + ld1 {v12.2s}, [x7], x2 //Load value for interpolation (column1,row2) + ld1 {v13.2s}, [x7], x2 //Load value for interpolation (column1,row3) + ext v28.8b, v5.8b , v6.8b , #5 + ext v25.8b, v5.8b , v6.8b , #2 + ext v31.8b, v2.8b , v3.8b , #5 + uaddl v14.8h, v28.8b, v5.8b //// a0 + a5 (column1,row5) + ext v24.8b, v5.8b , v6.8b , #3 + sqrshrun v18.8b, v8.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row2) + ext v22.8b, v5.8b , v6.8b , #4 + ext v29.8b, v2.8b , v3.8b , #3 + urhadd v18.16b, v12.16b , v18.16b //Interpolation step for qpel calculation + urhadd v19.16b, v13.16b , v19.16b //Interpolation step for qpel calculation + + st1 {v18.8b}, [x1], x3 ////Store dest row2 + ext v30.8b, v2.8b , v3.8b , #2 + uaddl v8.8h, v31.8b, v2.8b //// a0 + a5 (column1,row4) + st1 {v19.8b}, [x1], x3 ////Store dest row3 + beq end_func // Branch if height==4 + +//// Processing row4 and row5 + ext v23.8b, v5.8b , v6.8b , #1 + umlal v14.8h, v25.8b, v1.8b //// a0 + a5 + 20a2 (column1,row5) + umlal v14.8h, v24.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row5) + umlsl v14.8h, v23.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row5) + umlsl v14.8h, v22.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row5) + ext v27.8b, v2.8b , v3.8b , #1 + ext v26.8b, v2.8b , v3.8b , #4 + ld1 {v2.8b, v3.8b}, [x0], x2 //// Load row6 + umlal v8.8h, v29.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row4) + umlal v8.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 (column1,row4) + umlsl v8.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row4) + umlsl v8.8h, v26.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row4) + sqrshrun v19.8b, v14.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row5) + ld1 {v5.8b, v6.8b}, [x0], x2 //// Load row7 + ext v31.8b, v2.8b , v3.8b , #5 + ext v28.8b, v5.8b , v6.8b , #5 + ld1 {v12.2s}, [x7], x2 //Load value for interpolation (column1,row4) + ld1 {v13.2s}, [x7], x2 //Load value for interpolation (column1,row5) + ext v25.8b, v5.8b , v6.8b , #2 + uaddl v14.8h, v28.8b, v5.8b //// a0 + a5 (column1,row7) + ext v24.8b, v5.8b , v6.8b , #3 + ext v22.8b, v5.8b , v6.8b , #4 + sqrshrun v18.8b, v8.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row4) + ext v29.8b, v2.8b , v3.8b , #3 + ext v30.8b, v2.8b , v3.8b , #2 + urhadd v18.16b, v12.16b , v18.16b //Interpolation step for qpel calculation + urhadd v19.16b, v13.16b , v19.16b //Interpolation step for qpel calculation + + st1 {v18.8b}, [x1], x3 ////Store dest row4 + ext v27.8b, v2.8b , v3.8b , #1 + uaddl v8.8h, v31.8b, v2.8b //// a0 + a5 (column1,row6) + ext v26.8b, v2.8b , v3.8b , #4 + umlal v8.8h, v29.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row6) + umlal v8.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 (column1,row6) + umlsl v8.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row6) + umlsl v8.8h, v26.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row6) + //// Processing row6 and row7 + st1 {v19.8b}, [x1], x3 ////Store dest row5 + ext v23.8b, v5.8b , v6.8b , #1 + umlal v14.8h, v25.8b, v1.8b //// a0 + a5 + 20a2 (column1,row7) + umlal v14.8h, v24.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row7) + umlsl v14.8h, v23.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row7) + umlsl v14.8h, v22.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row7) + ld1 {v12.2s}, [x7], x2 //Load value for interpolation (column1,row6) + ld1 {v13.2s}, [x7], x2 //Load value for interpolation (column1,row7) + sqrshrun v18.8b, v8.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row6) + subs x12, x14, #1 + sqrshrun v19.8b, v14.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row7) + urhadd v18.16b, v12.16b , v18.16b //Interpolation step for qpel calculation + urhadd v19.16b, v13.16b , v19.16b //Interpolation step for qpel calculation + + st1 {v18.8b}, [x1], x3 ////Store dest row6 + st1 {v19.8b}, [x1], x3 ////Store dest row7 + + beq loop_8 //looping if height ==16 + + b end_func + +loop_4: + ld1 {v5.8b, v6.8b}, [x0], x2 //// Load row1 + ext v28.8b, v5.8b , v6.8b , #5 + ld1 {v2.8b, v3.8b}, [x0], x2 //// Load row0 + ext v25.8b, v5.8b , v6.8b , #2 + ext v31.8b, v2.8b , v3.8b , #5 + uaddl v14.8h, v28.8b, v5.8b //// a0 + a5 (column1,row1) + ext v24.8b, v5.8b , v6.8b , #3 + ext v23.8b, v5.8b , v6.8b , #1 + ext v22.8b, v5.8b , v6.8b , #4 + ext v29.8b, v2.8b , v3.8b , #3 + umlal v14.8h, v25.8b, v1.8b //// a0 + a5 + 20a2 (column1,row1) + umlal v14.8h, v24.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row1) + umlsl v14.8h, v23.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row1) + umlsl v14.8h, v22.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row1) + uaddl v8.8h, v31.8b, v2.8b //// a0 + a5 (column1,row0) + ext v30.8b, v2.8b , v3.8b , #2 + ld1 {v12.2s}, [x7], x2 //Load value for interpolation (column1,row0) + ld1 {v13.2s}, [x7], x2 //Load value for interpolation (column1,row1) + ext v27.8b, v2.8b , v3.8b , #1 + ext v26.8b, v2.8b , v3.8b , #4 + ld1 {v2.8b, v3.8b}, [x0], x2 //// Load row2 + umlal v8.8h, v29.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row0) + umlal v8.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 (column1,row0) + umlsl v8.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row0) + umlsl v8.8h, v26.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row0) + ld1 {v5.8b, v6.8b}, [x0], x2 //// Load row3 + ext v28.8b, v5.8b , v6.8b , #5 + ext v25.8b, v5.8b , v6.8b , #2 + sqrshrun v18.8b, v14.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row0) + ext v31.8b, v2.8b , v3.8b , #5 + ext v24.8b, v5.8b , v6.8b , #3 + + ext v23.8b, v5.8b , v6.8b , #1 + ext v22.8b, v5.8b , v6.8b , #4 + ext v29.8b, v2.8b , v3.8b , #3 + sqrshrun v19.8b, v8.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row1) + ext v30.8b, v2.8b , v3.8b , #2 + ext v27.8b, v2.8b , v3.8b , #1 + + //// Processing row2 and row3 + urhadd v18.16b, v12.16b , v18.16b //Interpolation step for qpel calculation + urhadd v19.16b, v13.16b , v19.16b //Interpolation step for qpel calculation + + st1 {v18.s}[0], [x1], x3 ////Store dest row0 + st1 {v19.s}[0], [x1], x3 ////Store dest row1 + uaddl v14.8h, v28.8b, v5.8b //// a0 + a5 (column1,row3) + ext v26.8b, v2.8b , v3.8b , #4 + ld1 {v12.2s}, [x7], x2 //Load value for interpolation (column1,row2) + ld1 {v13.2s}, [x7], x2 //Load value for interpolation (column1,row3) + + umlal v14.8h, v25.8b, v1.8b //// a0 + a5 + 20a2 (column1,row3) + umlal v14.8h, v24.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row3) + umlsl v14.8h, v23.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row3) + umlsl v14.8h, v22.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row3) + uaddl v8.8h, v31.8b, v2.8b //// a0 + a5 (column1,row2) + umlal v8.8h, v29.8b, v1.8b //// a0 + a5 + 20a2 + 20a3 (column1,row2) + umlal v8.8h, v30.8b, v1.8b //// a0 + a5 + 20a2 (column1,row2) + umlsl v8.8h, v27.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 (column1,row2) + umlsl v8.8h, v26.8b, v0.8b //// a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 (column1,row2) + sqrshrun v19.8b, v14.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row3) + sqrshrun v18.8b, v8.8h, #5 //// (a0 + a5 + 20a2 + 20a3 - 5a1 - 5a4 + 16) >> 5 (column1,row2) + urhadd v18.16b, v12.16b , v18.16b //Interpolation step for qpel calculation + urhadd v19.16b, v13.16b , v19.16b //Interpolation step for qpel calculation + + st1 {v18.s}[0], [x1], x3 ////Store dest row2 + subs x4, x4, #8 // Loop if height =8 + st1 {v19.s}[0], [x1], x3 ////Store dest row3 + + beq loop_4 + +end_func: + + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + + diff --git a/dependencies/ih264d/common/armv8/ih264_inter_pred_luma_horz_qpel_vert_hpel_av8.s b/dependencies/ih264d/common/armv8/ih264_inter_pred_luma_horz_qpel_vert_hpel_av8.s new file mode 100644 index 00000000..6ccf11f0 --- /dev/null +++ b/dependencies/ih264d/common/armv8/ih264_inter_pred_luma_horz_qpel_vert_hpel_av8.s @@ -0,0 +1,914 @@ +//****************************************************************************** +//* +//* Copyright (C) 2015 The Android Open Source Project +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at: +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. +//* +//***************************************************************************** +//* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +//*/ +///** +//****************************************************************************** +//* @file +//* ih264_inter_pred_luma_horz_qpel_vert_hpel_av8.s +//* +//* @brief +//* Contains function definitions for inter prediction interpolation. +//* +//* @author +//* Mohit +//* +//* @par List of Functions: +//* +//* - ih264_inter_pred_luma_horz_qpel_vert_hpel_av8() +//* +//* @remarks +//* None +//* +//******************************************************************************* +//*/ + +///* All the functions here are replicated from ih264_inter_pred_filters.c +// + +///** +///** +///** +//******************************************************************************* +//* +//* @brief +//* This function implements a two stage cascaded six tap filter. It +//* applies the six tap filter in the vertical direction on the +//* predictor values, followed by applying the same filter in the +//* horizontal direction on the output of the first stage. It then averages +//* the output of the 1st stage and the final stage to obtain the quarter +//* pel values.The six tap filtering operation is described in sec 8.4.2.2.1 +//* titled "Luma sample interpolation process". +//* +//* @par Description: +//* This function is called to obtain pixels lying at the following +//* location (1/4,1/2) or (3/4,1/2). The function interpolates +//* the predictors first in the verical direction and then in the +//* horizontal direction to output the (1/2,1/2). It then averages +//* the output of the 2nd stage and (1/2,1/2) value to obtain (1/4,1/2) +//* or (3/4,1/2) depending on the offset. +//* +//* @param[in] pu1_src +//* UWORD8 pointer to the source +//* +//* @param[out] pu1_dst +//* UWORD8 pointer to the destination +//* +//* @param[in] src_strd +//* integer source stride +//* +//* @param[in] dst_strd +//* integer destination stride +//* +//* @param[in] ht +//* integer height of the array +//* +//* @param[in] wd +//* integer width of the array +//* +//* @param[in] pu1_tmp: temporary buffer +//* +//* @param[in] dydx: x and y reference offset for qpel calculations +//* +//* @returns +//* +//* @remarks +//* None +//* +//******************************************************************************* +//*/; + +//void ih264_inter_pred_luma_horz_qpel_vert_hpel(UWORD8 *pu1_src, +// UWORD8 *pu1_dst, +// WORD32 src_strd,, +// WORD32 dst_strd, +// WORD32 ht, +// WORD32 wd, +// UWORD8* pu1_tmp, +// UWORD32 dydx) + +//**************Variables Vs Registers***************************************** +// x0 => *pu1_src +// x1 => *pu1_dst +// w2 => src_strd +// w3 => dst_strd +// w4 => ht +// w5 => wd +// x6 => *pu1_tmp +// w7 => dydx + +.text +.p2align 2 +.include "ih264_neon_macros.s" + + + + .global ih264_inter_pred_luma_horz_qpel_vert_hpel_av8 + +ih264_inter_pred_luma_horz_qpel_vert_hpel_av8: + + // STMFD sp!, {x4-x12, x14} //store register values to stack + push_v_regs + stp x19, x20, [sp, #-16]! + sxtw x2, w2 + sxtw x3, w3 + sxtw x4, w4 + sxtw x5, w5 + + sub x0, x0, x2, lsl #1 //pu1_src-2*src_strd + sub x0, x0, #2 //pu1_src-2 + mov x9, x6 + mov w6, w7 + + and x6, x6, #2 // dydx & 0x3 followed by dydx>>1 and dydx<<1 + + add x7, x9, #4 + add x6, x7, x6 // pi16_pred1_temp += (x_offset>>1) + + movi v26.8h, #0x14 // Filter coeff 20 into Q13 + movi v24.8h, #0x5 // Filter coeff 5 into Q12 + movi v27.8h, #0x14 // Filter coeff 20 into Q13 + movi v25.8h, #0x5 // Filter coeff 5 into Q12 + mov x7, #0x20 + mov x8, #0x30 + subs x12, x5, #4 //if wd=4 branch to loop_4 + beq loop_4_start + + subs x12, x5, #8 //if wd=8 branch to loop_8 + beq loop_8_start + + //when wd=16 + movi v28.8h, #0x14 // Filter coeff 20 into Q13 + movi v30.8h, #0x5 // Filter coeff 5 into Q12 + sub x2, x2, #16 + ld1 {v0.2s, v1.2s}, [x0], #16 // Vector load from src[0_0] + ld1 {v12.2s}, [x0], x2 // Vector load from src[0_0] + ld1 {v2.2s, v3.2s}, [x0], #16 // Vector load from src[1_0] + ld1 {v13.2s}, [x0], x2 // Vector load from src[1_0] + ld1 {v4.2s, v5.2s}, [x0], #16 // Vector load from src[2_0] + ld1 {v14.2s}, [x0], x2 // Vector load from src[2_0] + ld1 {v6.2s, v7.2s}, [x0], #16 // Vector load from src[3_0] + ld1 {v15.2s}, [x0], x2 // Vector load from src[3_0] + ld1 {v8.2s, v9.2s}, [x0], #16 // Vector load from src[4_0] + ld1 {v16.2s}, [x0], x2 // Vector load from src[4_0] + +loop_16: + + ld1 {v10.2s, v11.2s}, [x0], #16 // Vector load from src[5_0] + ld1 {v17.2s}, [x0], x2 // Vector load from src[5_0] + + + uaddl v20.8h, v4.8b, v6.8b + uaddl v18.8h, v0.8b, v10.8b + uaddl v22.8h, v2.8b, v8.8b + mla v18.8h, v20.8h , v28.8h + uaddl v24.8h, v5.8b, v7.8b + uaddl v20.8h, v1.8b, v11.8b + uaddl v26.8h, v3.8b, v9.8b + mla v20.8h, v24.8h , v28.8h + uaddl v24.8h, v14.8b, v15.8b + mls v18.8h, v22.8h , v30.8h + uaddl v22.8h, v12.8b, v17.8b + mls v20.8h, v26.8h , v30.8h + uaddl v26.8h, v13.8b, v16.8b + mla v22.8h, v24.8h , v28.8h + mls v22.8h, v26.8h , v30.8h + st1 {v18.4s }, [x9], #16 + st1 {v20.4s}, [x9], #16 + ext v24.16b, v18.16b , v20.16b , #4 + ext v26.16b, v18.16b , v20.16b , #6 + st1 {v22.4s}, [x9] + ext v22.16b, v18.16b , v20.16b , #10 + add v0.8h, v24.8h , v26.8h + ext v24.16b, v18.16b , v20.16b , #2 + ext v26.16b, v18.16b , v20.16b , #8 + add v24.8h, v24.8h , v26.8h + + saddl v26.4s, v18.4h, v22.4h + smlal v26.4s, v0.4h, v28.4h + smlsl v26.4s, v24.4h, v30.4h + + saddl2 v22.4s, v18.8h, v22.8h + smlal2 v22.4s, v0.8h, v28.8h + smlsl2 v22.4s, v24.8h, v30.8h + + sqrshrun v18.4h, v26.4s, #10 + sqrshrun v19.4h, v22.4s, #10 + ld1 {v22.4s}, [x9], #16 + + uqxtn v18.8b, v18.8h + uqxtn v19.8b, v19.8h + mov v18.s[1], v19.s[0] + + ext v24.16b, v20.16b , v22.16b , #4 + ext v26.16b, v20.16b , v22.16b , #6 + ext v0.16b, v20.16b , v22.16b , #10 + st1 {v18.2s}, [x1] + add v18.8h, v24.8h , v26.8h + ext v24.16b, v20.16b , v22.16b , #2 + ext v26.16b, v20.16b , v22.16b , #8 + add v24.8h, v24.8h , v26.8h + + saddl v26.4s, v0.4h, v20.4h + smlal v26.4s, v18.4h, v28.4h + smlsl v26.4s, v24.4h, v30.4h + + saddl2 v22.4s, v0.8h, v20.8h + smlal2 v22.4s, v18.8h, v28.8h + smlsl2 v22.4s, v24.8h, v30.8h + + sqrshrun v19.4h, v26.4s, #10 + sqrshrun v18.4h, v22.4s, #10 + + uaddl v24.8h, v7.8b, v9.8b + ld1 {v20.4s}, [x6], #16 + ld1 {v22.4s}, [x6], x7 + + + uqxtn v19.8b, v19.8h + uqxtn v18.8b, v18.8h + mov v19.s[1], v18.s[0] + + ld1 {v18.2s}, [x1] + sqrshrun v20.8b, v20.8h, #5 + sqrshrun v21.8b, v22.8h, #5 + uaddl v22.8h, v4.8b, v10.8b + ld1 {v0.2s, v1.2s}, [x0], #16 // Vector load from src[6_0] + urhadd v18.16b, v18.16b , v20.16b + urhadd v19.16b, v19.16b , v21.16b + + ld1 {v12.2s}, [x0], x2 // Vector load from src[6_0] + uaddl v20.8h, v6.8b, v8.8b + uaddl v26.8h, v5.8b, v11.8b + st1 {v18.2s, v19.2s}, [x1], x3 // store row 0 + + +//ROW_2 + + + uaddl v18.8h, v2.8b, v0.8b + + mla v18.8h, v20.8h , v28.8h + + uaddl v20.8h, v3.8b, v1.8b + + mla v20.8h, v24.8h , v28.8h + uaddl v24.8h, v15.8b, v16.8b + mls v18.8h, v22.8h , v30.8h + uaddl v22.8h, v13.8b, v12.8b + mls v20.8h, v26.8h , v30.8h + uaddl v26.8h, v14.8b, v17.8b + mla v22.8h, v24.8h , v28.8h + mls v22.8h, v26.8h , v30.8h + st1 {v18.4s}, [x9], #16 + st1 {v20.4s}, [x9], #16 + ext v24.16b, v18.16b , v20.16b , #4 + ext v26.16b, v18.16b , v20.16b , #6 + st1 {v22.4s}, [x9] + ext v22.16b, v18.16b , v20.16b , #10 + add v2.8h, v24.8h , v26.8h + ext v24.16b, v18.16b , v20.16b , #2 + ext v26.16b, v18.16b , v20.16b , #8 + add v24.8h, v24.8h , v26.8h + + saddl v26.4s, v18.4h, v22.4h + smlal v26.4s, v2.4h, v28.4h + smlsl v26.4s, v24.4h, v30.4h + + saddl2 v22.4s, v18.8h, v22.8h + smlal2 v22.4s, v2.8h, v28.8h + smlsl2 v22.4s, v24.8h, v30.8h + + sqrshrun v18.4h, v26.4s, #10 + sqrshrun v19.4h, v22.4s, #10 + + ld1 {v22.4s}, [x9], #16 + + uqxtn v18.8b, v18.8h + uqxtn v19.8b, v19.8h + mov v18.s[1], v19.s[0] + + ext v24.16b, v20.16b , v22.16b , #4 + ext v26.16b, v20.16b , v22.16b , #6 + ext v2.16b, v20.16b , v22.16b , #10 + st1 {v18.2s}, [x1] + add v18.8h, v24.8h , v26.8h + ext v24.16b, v20.16b , v22.16b , #2 + ext v26.16b, v20.16b , v22.16b , #8 + add v24.8h, v24.8h , v26.8h + + saddl v26.4s, v2.4h, v20.4h + smlal v26.4s, v18.4h, v28.4h + smlsl v26.4s, v24.4h, v30.4h + + saddl2 v22.4s, v2.8h, v20.8h + smlal2 v22.4s, v18.8h, v28.8h + smlsl2 v22.4s, v24.8h, v30.8h + + sqrshrun v19.4h, v26.4s, #10 + sqrshrun v18.4h, v22.4s, #10 + uaddl v24.8h, v9.8b, v11.8b + ld1 {v20.4s}, [x6], #16 + ld1 {v22.4s}, [x6], x7 + uqxtn v19.8b, v19.8h + uqxtn v18.8b, v18.8h + mov v19.s[1], v18.s[0] + ld1 {v18.4s}, [x1] + sqrshrun v20.8b, v20.8h, #5 + sqrshrun v21.8b, v22.8h, #5 + + uaddl v22.8h, v6.8b, v0.8b + ld1 {v2.2s, v3.2s}, [x0], #16 // Vector load from src[7_0] + + urhadd v18.16b, v18.16b , v20.16b + urhadd v19.16b, v19.16b , v21.16b + ld1 {v13.2s}, [x0], x2 // Vector load from src[7_0] + uaddl v20.8h, v8.8b, v10.8b + uaddl v26.8h, v7.8b, v1.8b + st1 {v18.2s, v19.2s}, [x1], x3 // store row 1 + +//ROW_3 + + + uaddl v18.8h, v4.8b, v2.8b + + mla v18.8h, v20.8h , v28.8h + + uaddl v20.8h, v5.8b, v3.8b + + mla v20.8h, v24.8h , v28.8h + uaddl v24.8h, v16.8b, v17.8b + mls v18.8h, v22.8h , v30.8h + uaddl v22.8h, v14.8b, v13.8b + mls v20.8h, v26.8h , v30.8h + uaddl v26.8h, v15.8b, v12.8b + mla v22.8h, v24.8h , v28.8h + mls v22.8h, v26.8h , v30.8h + st1 {v18.4s}, [x9], #16 + st1 {v20.4s}, [x9], #16 + ext v24.16b, v18.16b , v20.16b , #4 + ext v26.16b, v18.16b , v20.16b , #6 + st1 {v22.4s}, [x9] + ext v22.16b, v18.16b , v20.16b , #10 + add v4.8h, v24.8h , v26.8h + ext v24.16b, v18.16b , v20.16b , #2 + ext v26.16b, v18.16b , v20.16b , #8 + add v24.8h, v24.8h , v26.8h + + saddl v26.4s, v18.4h, v22.4h + smlal v26.4s, v4.4h, v28.4h + smlsl v26.4s, v24.4h, v30.4h + + saddl2 v22.4s, v18.8h, v22.8h + smlal2 v22.4s, v4.8h, v28.8h + smlsl2 v22.4s, v24.8h, v30.8h + + sqrshrun v18.4h, v26.4s, #10 + sqrshrun v19.4h, v22.4s, #10 + ld1 {v22.4s}, [x9], #16 + + uqxtn v18.8b, v18.8h + uqxtn v19.8b, v19.8h + mov v18.s[1], v19.s[0] + + + ext v24.16b, v20.16b , v22.16b , #4 + ext v26.16b, v20.16b , v22.16b , #6 + ext v4.16b, v20.16b , v22.16b , #10 + st1 {v18.2s}, [x1] + add v18.8h, v24.8h , v26.8h + ext v24.16b, v20.16b , v22.16b , #2 + ext v26.16b, v20.16b , v22.16b , #8 + add v24.8h, v24.8h , v26.8h + + saddl v26.4s, v4.4h, v20.4h + smlal v26.4s, v18.4h, v28.4h + smlsl v26.4s, v24.4h, v30.4h + + saddl2 v22.4s, v4.8h, v20.8h + smlal2 v22.4s, v18.8h, v28.8h + smlsl2 v22.4s, v24.8h, v30.8h + + sqrshrun v19.4h, v26.4s, #10 + sqrshrun v18.4h, v22.4s, #10 + + uaddl v24.8h, v11.8b, v1.8b + ld1 {v20.4s}, [x6], #16 + ld1 {v22.4s}, [x6], x7 + + uqxtn v19.8b, v19.8h + uqxtn v18.8b, v18.8h + mov v19.s[1], v18.s[0] + + ld1 {v18.2s}, [x1] + sqrshrun v20.8b, v20.8h, #5 + sqrshrun v21.8b, v22.8h, #5 + + uaddl v22.8h, v8.8b, v2.8b + ld1 {v4.2s, v5.2s}, [x0], #16 // Vector load from src[8_0] + + urhadd v18.16b, v18.16b , v20.16b + urhadd v19.16b, v19.16b , v21.16b + ld1 {v14.2s}, [x0], x2 // Vector load from src[8_0] + uaddl v20.8h, v10.8b, v0.8b + uaddl v26.8h, v9.8b, v3.8b + st1 {v18.2s, v19.2s}, [x1], x3 // store row 2 + + +//ROW_4 + + uaddl v18.8h, v6.8b, v4.8b + + mla v18.8h, v20.8h , v28.8h + + uaddl v20.8h, v7.8b, v5.8b + + mla v20.8h, v24.8h , v28.8h + uaddl v24.8h, v17.8b, v12.8b + mls v18.8h, v22.8h , v30.8h + uaddl v22.8h, v15.8b, v14.8b + mls v20.8h, v26.8h , v30.8h + uaddl v26.8h, v16.8b, v13.8b + mla v22.8h, v24.8h , v28.8h + mls v22.8h, v26.8h , v30.8h + st1 {v18.4s}, [x9], #16 + st1 {v20.4s}, [x9], #16 + ext v24.16b, v18.16b , v20.16b , #4 + ext v26.16b, v18.16b , v20.16b , #6 + st1 {v22.4s}, [x9] + ext v22.16b, v18.16b , v20.16b , #10 + add v6.8h, v24.8h , v26.8h + ext v24.16b, v18.16b , v20.16b , #2 + ext v26.16b, v18.16b , v20.16b , #8 + add v24.8h, v24.8h , v26.8h + + saddl v26.4s, v18.4h, v22.4h + smlal v26.4s, v6.4h, v28.4h + smlsl v26.4s, v24.4h, v30.4h + + saddl2 v22.4s, v18.8h, v22.8h + smlal2 v22.4s, v6.8h, v28.8h + smlsl2 v22.4s, v24.8h, v30.8h + + sqrshrun v18.4h, v26.4s, #10 + sqrshrun v19.4h, v22.4s, #10 + ld1 {v22.4s}, [x9], #16 + uqxtn v18.8b, v18.8h + uqxtn v19.8b, v19.8h + mov v18.s[1], v19.s[0] + + + ext v24.16b, v20.16b , v22.16b , #4 + ext v26.16b, v20.16b , v22.16b , #6 + ext v6.16b, v20.16b , v22.16b , #10 + st1 {v18.2s}, [x1] + add v18.8h, v24.8h , v26.8h + ext v24.16b, v20.16b , v22.16b , #2 + ext v26.16b, v20.16b , v22.16b , #8 + add v24.8h, v24.8h , v26.8h + + saddl v26.4s, v6.4h, v20.4h + smlal v26.4s, v18.4h, v28.4h + smlsl v26.4s, v24.4h, v30.4h + + saddl2 v22.4s, v6.8h, v20.8h + smlal2 v22.4s, v18.8h, v28.8h + smlsl2 v22.4s, v24.8h, v30.8h + + mov v6.16b, v2.16b + mov v7.16b, v3.16b + + mov v2.16b, v10.16b + mov v3.16b, v11.16b + + subs x4, x4, #4 + sqrshrun v19.4h, v26.4s, #10 + sqrshrun v18.4h, v22.4s, #10 + mov v10.16b, v0.16b + mov v11.16b, v1.16b + + mov v24.8b, v14.8b + + mov v14.16b, v12.16b + mov v15.16b, v13.16b + + + uqxtn v19.8b, v19.8h + uqxtn v18.8b, v18.8h + mov v19.s[1], v18.s[0] + + ld1 {v20.4s}, [x6], #16 + ld1 {v22.4s}, [x6], x7 + ld1 {v18.2s}, [x1] + sqrshrun v20.8b, v20.8h, #5 + sqrshrun v21.8b, v22.8h, #5 + + mov v0.16b, v8.16b + mov v1.16b, v9.16b + + mov v8.16b, v4.16b + mov v9.16b, v5.16b + + mov v12.16b, v16.16b + mov v13.16b, v17.16b + urhadd v18.16b, v18.16b , v20.16b + urhadd v19.16b, v19.16b , v21.16b + + mov v4.16b, v10.16b + mov v5.16b, v11.16b + + mov v16.8b, v24.8b + st1 {v18.2s, v19.2s}, [x1], x3 // store row 3 + + bgt loop_16 // looping if height =16 + b end_func + +loop_8_start: + ld1 {v0.2s, v1.2s}, [x0], x2 // Vector load from src[0_0] + ld1 {v2.2s, v3.2s}, [x0], x2 // Vector load from src[1_0] + ld1 {v4.2s, v5.2s}, [x0], x2 // Vector load from src[2_0] + ld1 {v6.2s, v7.2s}, [x0], x2 // Vector load from src[3_0] + ld1 {v8.2s, v9.2s}, [x0], x2 // Vector load from src[4_0] + +loop_8: + + ld1 {v10.2s, v11.2s}, [x0], x2 // Vector load from src[5_0] + uaddl v14.8h, v4.8b, v6.8b + uaddl v12.8h, v0.8b, v10.8b + uaddl v16.8h, v2.8b, v8.8b + mla v12.8h, v14.8h , v26.8h + uaddl v18.8h, v5.8b, v7.8b + uaddl v14.8h, v1.8b, v11.8b + uaddl v22.8h, v3.8b, v9.8b + mla v14.8h, v18.8h , v26.8h + mls v12.8h, v16.8h , v24.8h + ld1 {v0.2s, v1.2s}, [x0], x2 // Vector load from src[6_0] + uaddl v16.8h, v6.8b, v8.8b + mls v14.8h, v22.8h , v24.8h + uaddl v28.8h, v2.8b, v0.8b + st1 {v12.4s}, [x9], #16 // store row 0 to temp buffer: col 0 + ext v22.16b, v12.16b , v14.16b , #10 + uaddl v18.8h, v4.8b, v10.8b + mla v28.8h, v16.8h , v26.8h + saddl v30.4s, v12.4h, v22.4h + st1 {v14.4s}, [x9], x7 // store row 0 to temp buffer: col 1 + saddl2 v22.4s, v12.8h, v22.8h + ext v16.16b, v12.16b , v14.16b , #4 + mls v28.8h, v18.8h , v24.8h + ext v18.16b, v12.16b , v14.16b , #6 + ext v20.16b, v12.16b , v14.16b , #8 + ext v14.16b, v12.16b , v14.16b , #2 + add v16.8h, v16.8h , v18.8h + add v18.8h, v14.8h , v20.8h + uaddl v20.8h, v7.8b, v9.8b + smlal v30.4s, v16.4h, v26.4h + smlsl v30.4s, v18.4h, v24.4h + smlal2 v22.4s, v16.8h, v26.8h + smlsl2 v22.4s, v18.8h, v24.8h + uaddl v14.8h, v3.8b, v1.8b + st1 {v28.4s}, [x9], #16 // store row 1 to temp buffer: col 0 + mla v14.8h, v20.8h , v26.8h + sqrshrun v12.4h, v30.4s, #10 + uaddl v16.8h, v5.8b, v11.8b + sqrshrun v13.4h, v22.4s, #10 + mls v14.8h, v16.8h , v24.8h + ld1 {v2.2s, v3.2s}, [x0], x2 // Vector load from src[7_0] + uqxtn v25.8b, v12.8h + uqxtn v13.8b, v13.8h + mov v25.s[1], v13.s[0] + uaddl v16.8h, v8.8b, v10.8b + + + ext v22.16b, v28.16b , v14.16b , #10 + uaddl v20.8h, v4.8b, v2.8b + saddl v30.4s, v28.4h, v22.4h + mla v20.8h, v16.8h , v26.8h + st1 {v14.4s}, [x9], x7 // store row 1 to temp buffer: col 1 + saddl2 v22.4s, v28.8h, v22.8h + ext v16.16b, v28.16b , v14.16b , #4 + ext v18.16b, v28.16b , v14.16b , #6 + ext v12.16b, v28.16b , v14.16b , #8 + ext v14.16b, v28.16b , v14.16b , #2 + add v16.8h, v16.8h , v18.8h + add v18.8h, v12.8h , v14.8h + ld1 {v14.4s, v15.4s}, [x6], x8 // load row 0 from temp buffer + smlal v30.4s, v16.4h, v26.4h + smlsl v30.4s, v18.4h, v24.4h + smlal2 v22.4s, v16.8h, v26.8h + smlsl2 v22.4s, v18.8h, v24.8h + sqrshrun v14.8b, v14.8h, #0x5 + ld1 {v28.4s, v29.4s}, [x6], x8 // load row 1 from temp buffer + uaddl v18.8h, v6.8b, v0.8b + sqrshrun v16.4h, v30.4s, #10 + sqrshrun v15.8b, v28.8h, #0x5 + sqrshrun v17.4h, v22.4s, #10 + + mov v12.8b, v25.8b + mov v25.8b, v24.8b + + uaddl v28.8h, v9.8b, v11.8b + uqxtn v13.8b, v16.8h + uqxtn v17.8b, v17.8h + mov v13.s[1], v17.s[0] + + urhadd v12.16b, v12.16b , v14.16b + urhadd v13.16b, v13.16b , v15.16b + uaddl v14.8h, v5.8b, v3.8b + uaddl v22.8h, v7.8b, v1.8b + mls v20.8h, v18.8h , v24.8h + st1 {v12.2s}, [x1], x3 // store row 0 + mla v14.8h, v28.8h , v26.8h + ld1 {v4.2s, v5.2s}, [x0], x2 // Vector load from src[8_0] + uaddl v30.8h, v10.8b, v0.8b + uaddl v28.8h, v6.8b, v4.8b + mls v14.8h, v22.8h , v24.8h + st1 {v13.2s}, [x1], x3 // store row 1 + mla v28.8h, v30.8h , v26.8h + st1 {v20.4s}, [x9], #16 // store row 2 to temp buffer: col 0 + ext v22.16b, v20.16b , v14.16b , #10 + saddl v30.4s, v20.4h, v22.4h + st1 {v14.2s, v15.2s}, [x9], x7 // store row 2 to temp buffer: col 0 + saddl2 v22.4s, v20.8h, v22.8h + ext v16.16b, v20.16b , v14.16b , #4 + ext v18.16b, v20.16b , v14.16b , #6 + ext v12.16b, v20.16b , v14.16b , #8 + ext v14.16b, v20.16b , v14.16b , #2 + add v16.8h, v16.8h , v18.8h + add v18.8h, v14.8h , v12.8h + uaddl v20.8h, v8.8b, v2.8b + smlal v30.4s, v16.4h, v26.4h + smlsl v30.4s, v18.4h, v24.4h + smlal2 v22.4s, v16.8h, v26.8h + smlsl2 v22.4s, v18.8h, v24.8h + uaddl v18.8h, v11.8b, v1.8b + uaddl v16.8h, v7.8b, v5.8b + sqrshrun v12.4h, v30.4s, #10 + uaddl v30.8h, v9.8b, v3.8b + mla v16.8h, v18.8h , v26.8h + sqrshrun v13.4h, v22.4s, #10 + mls v28.8h, v20.8h , v24.8h + ld1 {v14.4s, v15.4s}, [x6], x8 // load row 2 from temp buffer + mls v16.8h, v30.8h , v24.8h + uqxtn v27.8b, v12.8h + uqxtn v13.8b, v13.8h + mov v27.s[1], v13.s[0] + + sqrshrun v14.8b, v14.8h, #5 + ext v22.16b, v28.16b , v16.16b , #10 + st1 {v28.4s}, [x9], #16 // store row 3 to temp buffer: col 0 + saddl v30.4s, v28.4h, v22.4h + st1 {v16.2s, v17.2s}, [x9], x7 // store row 3 to temp buffer: col 1 + saddl2 v22.4s, v28.8h, v22.8h + ext v12.16b, v28.16b , v16.16b , #4 + ext v18.16b, v28.16b , v16.16b , #6 + ext v20.16b, v28.16b , v16.16b , #8 + ext v28.16b, v28.16b , v16.16b , #2 + add v12.8h, v12.8h , v18.8h + add v18.8h, v28.8h , v20.8h + ld1 {v16.4s, v17.4s}, [x6], x8 // load row 3 from temp buffer + smlal v30.4s, v12.4h, v26.4h + smlsl v30.4s, v18.4h, v24.4h + smlal2 v22.4s, v12.8h, v26.8h + smlsl2 v22.4s, v18.8h, v24.8h + sqrshrun v15.8b, v16.8h, #0x5 + + mov v12.8b, v27.8b + mov v27.8b, v26.8b + + sqrshrun v16.4h, v30.4s, #10 + + mov v6.16b, v2.16b + mov v7.16b, v3.16b + + sqrshrun v17.4h, v22.4s, #10 + + mov v2.16b, v10.16b + mov v3.16b, v11.16b + + mov v10.16b, v0.16b + mov v11.16b, v1.16b + + subs x4, x4, #4 + uqxtn v13.8b, v16.8h + uqxtn v17.8b, v17.8h + mov v13.s[1], v17.s[0] + urhadd v12.16b, v12.16b , v14.16b + urhadd v13.16b, v13.16b , v15.16b + + mov v0.16b, v8.16b + mov v1.16b, v9.16b + + mov v8.16b, v4.16b + mov v9.16b, v5.16b + + mov v4.16b, v10.16b + mov v5.16b, v11.16b + + st1 {v12.2s}, [x1], x3 // store row 2 + st1 {v13.2s}, [x1], x3 // store row 3 + + bgt loop_8 //if height =8 loop + b end_func + +loop_4_start: + ld1 {v0.2s, v1.2s}, [x0], x2 // Vector load from src[0_0] + ld1 {v2.2s, v3.2s}, [x0], x2 // Vector load from src[1_0] + ld1 {v4.2s, v5.2s}, [x0], x2 // Vector load from src[2_0] + ld1 {v6.2s, v7.2s}, [x0], x2 // Vector load from src[3_0] + ld1 {v8.2s, v9.2s}, [x0], x2 // Vector load from src[4_0] + +loop_4: + ld1 {v10.2s, v11.2s}, [x0], x2 // Vector load from src[5_0] + uaddl v14.8h, v4.8b, v6.8b // temp1 = src[2_0] + src[3_0] + uaddl v12.8h, v0.8b, v10.8b // temp = src[0_0] + src[5_0] + uaddl v16.8h, v2.8b, v8.8b // temp2 = src[1_0] + src[4_0] + mla v12.8h, v14.8h , v26.8h // temp += temp1 * 20 + uaddl v18.8h, v5.8b, v7.8b // temp1 = src[2_0] + src[3_0] + uaddl v14.8h, v1.8b, v11.8b // temp = src[0_0] + src[5_0] + uaddl v22.8h, v3.8b, v9.8b // temp2 = src[1_0] + src[4_0] + mla v14.8h, v18.8h , v26.8h // temp += temp1 * 20 + mls v12.8h, v16.8h , v24.8h // temp -= temp2 * 5 + ld1 {v0.2s, v1.2s}, [x0], x2 // Vector load from src[6_0] + uaddl v16.8h, v6.8b, v8.8b + mls v14.8h, v22.8h , v24.8h // temp -= temp2 * 5 + //Q6 and Q7 have filtered values + uaddl v28.8h, v2.8b, v0.8b + st1 {v12.4s}, [x9], #16 // store row 0 to temp buffer: col 0 + ext v22.16b, v12.16b , v14.16b , #10 + uaddl v18.8h, v4.8b, v10.8b + mla v28.8h, v16.8h , v26.8h + saddl v30.4s, v12.4h, v22.4h + st1 {v14.4s}, [x9], x7 // store row 0 to temp buffer: col 1 + saddl v22.4s, v13.4h, v23.4h + ext v16.16b, v12.16b , v14.16b , #4 + mls v28.8h, v18.8h , v24.8h + ext v18.16b, v12.16b , v14.16b , #6 + ext v20.16b, v12.16b , v14.16b , #8 + ext v14.16b, v12.16b , v14.16b , #2 + add v16.8h, v16.8h , v18.8h + add v18.8h, v14.8h , v20.8h + uaddl v20.8h, v7.8b, v9.8b + smlal v30.4s, v16.4h, v26.4h + smlsl v30.4s, v18.4h, v24.4h + smlal v22.4s, v17.4h, v26.4h + smlsl v22.4s, v19.4h, v24.4h + uaddl v14.8h, v3.8b, v1.8b + st1 {v28.4s}, [x9], #16 // store row 1 to temp buffer: col 0 + mla v14.8h, v20.8h , v26.8h + sqrshrun v12.4h, v30.4s, #10 + uaddl v16.8h, v5.8b, v11.8b + sqrshrun v13.4h, v22.4s, #10 + mls v14.8h, v16.8h , v24.8h + ld1 {v2.2s, v3.2s}, [x0], x2 // Vector load from src[7_0] + uqxtn v25.8b, v12.8h + uaddl v16.8h, v8.8b, v10.8b + + ext v22.16b, v28.16b , v14.16b , #10 + uaddl v20.8h, v4.8b, v2.8b + saddl v30.4s, v28.4h, v22.4h + mla v20.8h, v16.8h , v26.8h + st1 {v14.4s}, [x9], x7 // store row 1 to temp buffer: col 1 + saddl v22.4s, v29.4h, v23.4h + ext v16.16b, v28.16b , v14.16b , #4 + ext v18.16b, v28.16b , v14.16b , #6 + ext v12.16b, v28.16b , v14.16b , #8 + ext v14.16b, v28.16b , v14.16b , #2 + add v16.8h, v16.8h , v18.8h + add v18.8h, v12.8h , v14.8h + ld1 {v14.2s}, [x6], x8 //load row 0 from temp buffer + smlal v30.4s, v16.4h, v26.4h + smlsl v30.4s, v18.4h, v24.4h + smlal v22.4s, v17.4h, v26.4h + smlsl v22.4s, v19.4h, v24.4h + sqrshrun v14.8b, v14.8h, #0x5 + ld1 {v28.2s}, [x6], x8 //load row 1 from temp buffer + uaddl v18.8h, v6.8b, v0.8b + sqrshrun v16.4h, v30.4s, #10 + sqrshrun v15.8b, v28.8h, #0x5 + sqrshrun v17.4h, v22.4s, #10 + + mov v12.8b, v25.8b + mov v25.8b, v24.8b + + uaddl v28.8h, v9.8b, v11.8b + uqxtn v13.8b, v16.8h + + urhadd v12.16b, v12.16b , v14.16b + urhadd v13.16b, v13.16b , v15.16b + + uaddl v14.8h, v5.8b, v3.8b + uaddl v22.8h, v7.8b, v1.8b + mls v20.8h, v18.8h , v24.8h + st1 {v12.s}[0], [x1], x3 // store row 0 + mla v14.8h, v28.8h , v26.8h + ld1 {v4.2s, v5.2s}, [x0], x2 // Vector load from src[8_0] + uaddl v30.8h, v10.8b, v0.8b + uaddl v28.8h, v6.8b, v4.8b + mls v14.8h, v22.8h , v24.8h + st1 {v13.s}[0], [x1], x3 //store row 1 + mla v28.8h, v30.8h , v26.8h + st1 {v20.4s}, [x9], #16 // store row 2 to temp buffer: col 0 + ext v22.16b, v20.16b , v14.16b , #10 + saddl v30.4s, v20.4h, v22.4h + st1 {v14.4s}, [x9], x7 // store row 2 to temp buffer: col 1 + saddl v22.4s, v21.4h, v23.4h + ext v16.16b, v20.16b , v14.16b , #4 + ext v18.16b, v20.16b , v14.16b , #6 + ext v12.16b, v20.16b , v14.16b , #8 + ext v14.16b, v20.16b , v14.16b , #2 + add v16.8h, v16.8h , v18.8h + add v18.8h, v14.8h , v12.8h + uaddl v20.8h, v8.8b, v2.8b + smlal v30.4s, v16.4h, v26.4h + smlsl v30.4s, v18.4h, v24.4h + smlal v22.4s, v17.4h, v26.4h + smlsl v22.4s, v19.4h, v24.4h + uaddl v18.8h, v11.8b, v1.8b + uaddl v16.8h, v7.8b, v5.8b + sqrshrun v12.4h, v30.4s, #10 + uaddl v30.8h, v9.8b, v3.8b + mla v16.8h, v18.8h , v26.8h + sqrshrun v13.4h, v22.4s, #10 + mls v28.8h, v20.8h , v24.8h + ld1 {v14.2s}, [x6], x8 //load row 3 from temp buffer + mls v16.8h, v30.8h , v24.8h + uqxtn v27.8b, v12.8h + sqrshrun v14.8b, v14.8h, #5 + ext v22.16b, v28.16b , v16.16b , #10 + st1 {v28.4s}, [x9], #16 // store row 3 to temp buffer: col 0 + saddl v30.4s, v28.4h, v22.4h + st1 {v16.4s}, [x9], x7 // store row 3 to temp buffer: col 1 + saddl v22.4s, v29.4h, v23.4h + ext v12.16b, v28.16b , v16.16b , #4 + ext v18.16b, v28.16b , v16.16b , #6 + ext v20.16b, v28.16b , v16.16b , #8 + ext v28.16b, v28.16b , v16.16b , #2 + add v12.8h, v12.8h , v18.8h + add v18.8h, v28.8h , v20.8h + ld1 {v16.2s}, [x6], x8 //load row 4 from temp buffer + smlal v30.4s, v12.4h, v26.4h + smlsl v30.4s, v18.4h, v24.4h + smlal v22.4s, v13.4h, v26.4h + smlsl v22.4s, v19.4h, v24.4h + sqrshrun v15.8b, v16.8h, #0x5 + + mov v12.8b, v27.8b + mov v27.8b, v26.8b + + sqrshrun v16.4h, v30.4s, #10 + + mov v6.16b, v2.16b + mov v7.16b, v3.16b + + sqrshrun v17.4h, v22.4s, #10 + + mov v2.16b, v10.16b + mov v3.16b, v11.16b + + mov v10.16b, v0.16b + mov v11.16b, v1.16b + + subs x4, x4, #4 + uqxtn v13.8b, v16.8h + urhadd v12.16b, v12.16b , v14.16b + urhadd v13.16b, v13.16b , v15.16b + + mov v0.16b, v8.16b + mov v1.16b, v9.16b + + mov v8.16b, v4.16b + mov v9.16b, v5.16b + + + mov v4.16b, v10.16b + mov v5.16b, v11.16b + + + st1 {v12.s}[0], [x1], x3 // store row 2 + st1 {v13.s}[0], [x1], x3 // store row 3 + + bgt loop_4 + +end_func: + // LDMFD sp!,{x4-x12,PC} //Restoring registers from stack + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + + diff --git a/dependencies/ih264d/common/armv8/ih264_inter_pred_luma_horz_qpel_vert_qpel_av8.s b/dependencies/ih264d/common/armv8/ih264_inter_pred_luma_horz_qpel_vert_qpel_av8.s new file mode 100644 index 00000000..a9dfbd19 --- /dev/null +++ b/dependencies/ih264d/common/armv8/ih264_inter_pred_luma_horz_qpel_vert_qpel_av8.s @@ -0,0 +1,962 @@ +//****************************************************************************** +//* +//* Copyright (C) 2015 The Android Open Source Project +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at: +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. +//* +//***************************************************************************** +//* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +//*/ +///** +//****************************************************************************** +//* @file +//* ih264_inter_pred_luma_horz_qpel_vert_qpel_av8.s +//* +//* @brief +//* Contains function definitions for inter prediction interpolation. +//* +//* @author +//* Mohit +//* +//* @par List of Functions: +//* +//* - ih264_inter_pred_luma_horz_qpel_vert_qpel_av8() +//* +//* @remarks +//* None +//* +//******************************************************************************* +//*/ + +///* All the functions here are replicated from ih264_inter_pred_filters.c +// + +///** +///** +///** +//******************************************************************************* +//* +//* @brief +//* This function implements two six tap filters. It +//* applies the six tap filter in the horizontal direction on the +//* predictor values, then applies the same filter in the +//* vertical direction on the predictor values. It then averages these +//* two outputs to obtain quarter pel values in horizontal and vertical direction. +//* The six tap filtering operation is described in sec 8.4.2.2.1 titled +//* "Luma sample interpolation process" +//* +//* @par Description: +//* This function is called to obtain pixels lying at the following +//* location (1/4,1/4) or (3/4,1/4) or (1/4,3/4) or (3/4,3/4). +//* The function interpolates the predictors first in the horizontal direction +//* and then in the vertical direction, and then averages these two +//* values. +//* +//* @param[in] pu1_src +//* UWORD8 pointer to the source +//* +//* @param[out] pu1_dst +//* UWORD8 pointer to the destination +//* +//* @param[in] src_strd +//* integer source stride +//* +//* @param[in] dst_strd +//* integer destination stride +//* +//* @param[in] ht +//* integer height of the array +//* +//* @param[in] wd +//* integer width of the array +//* +//* @param[in] pu1_tmp: temporary buffer +//* +//* @param[in] dydx: x and y reference offset for qpel calculations +//* +//* @returns +//* +//* @remarks +//* None +//* +//******************************************************************************* +//*/; + +//void ih264_inter_pred_luma_horz_qpel_vert_qpel(UWORD8 *pu1_src, +// UWORD8 *pu1_dst, +// WORD32 src_strd,, +// WORD32 dst_strd, +// WORD32 ht, +// WORD32 wd, +// UWORD8* pu1_tmp, +// UWORD32 dydx) + +//**************Variables Vs Registers***************************************** +// x0 => *pu1_src +// x1 => *pu1_dst +// w2 => src_strd +// w3 => dst_strd +// w4 => ht +// w5 => wd +// w7 => dydx + +.text +.p2align 2 +.include "ih264_neon_macros.s" + + + + .global ih264_inter_pred_luma_horz_qpel_vert_qpel_av8 + +ih264_inter_pred_luma_horz_qpel_vert_qpel_av8: + + push_v_regs + stp x19, x20, [sp, #-16]! + sxtw x2, w2 + sxtw x3, w3 + sxtw x4, w4 + sxtw x5, w5 + mov w6, w7 + and x7, x6, #3 + add x7, x0, x7, lsr #1 //pu1_pred_vert = pu1_src + (x_offset>>1) + + and x6, x6, #12 //Finds y-offset + lsr x6, x6, #3 //dydx>>3 + mul x6, x2, x6 + add x6, x0, x6 //pu1_pred_horz = pu1_src + (y_offset>>1)*src_strd + sub x7, x7, x2, lsl #1 //pu1_pred_vert-2*src_strd + sub x6, x6, #2 //pu1_pred_horz-2 + movi v30.8b, #20 // Filter coeff 20 + movi v31.8b, #5 // Filter coeff 5 + + subs x12, x5, #4 //if wd=4 branch to loop_4 + beq loop_4_start + subs x12, x5, #8 //if wd=8 branch to loop_8 + beq loop_8_start + + ld1 {v0.2s, v1.2s}, [x7], x2 // Vector load from src[0_0] + ld1 {v2.2s, v3.2s}, [x7], x2 // Vector load from src[1_0] + + ld1 {v4.2s, v5.2s}, [x7], x2 // Vector load from src[2_0] + ld1 {v6.2s, v7.2s}, [x7], x2 // Vector load from src[3_0] + ld1 {v8.2s, v9.2s}, [x7], x2 // Vector load from src[4_0] + add x11, x6, #8 +loop_16: + ld1 {v10.2s, v11.2s}, [x7], x2 // Vector load from src[5_0] + ld1 {v18.2s, v19.2s}, [x6], x2 // horz row0, col 0 + uaddl v24.8h, v0.8b, v10.8b + umlal v24.8h, v4.8b, v30.8b + umlal v24.8h, v6.8b, v30.8b + umlsl v24.8h, v2.8b, v31.8b + umlsl v24.8h, v8.8b, v31.8b + ext v23.8b, v18.8b , v19.8b , #5 + ext v20.8b, v18.8b , v19.8b , #2 + ext v21.8b, v18.8b , v19.8b , #3 + ext v22.8b, v18.8b , v19.8b , #4 + ext v19.8b, v18.8b , v19.8b , #1 + sqrshrun v26.8b, v24.8h, #5 + uaddl v28.8h, v18.8b, v23.8b + umlal v28.8h, v20.8b, v30.8b + umlal v28.8h, v21.8b, v30.8b + umlsl v28.8h, v19.8b, v31.8b + umlsl v28.8h, v22.8b, v31.8b + ld1 {v18.2s, v19.2s}, [x11], x2 // horz row 0, col 1 + uaddl v24.8h, v1.8b, v11.8b + umlal v24.8h, v5.8b, v30.8b + umlal v24.8h, v7.8b, v30.8b + umlsl v24.8h, v3.8b, v31.8b + umlsl v24.8h, v9.8b, v31.8b + sqrshrun v28.8b, v28.8h, #5 + ext v23.8b, v18.8b , v19.8b , #5 + ext v20.8b, v18.8b , v19.8b , #2 + ext v21.8b, v18.8b , v19.8b , #3 + ext v22.8b, v18.8b , v19.8b , #4 + ext v19.8b, v18.8b , v19.8b , #1 + + sqrshrun v27.8b, v24.8h, #5 + ld1 {v12.2s, v13.2s}, [x7], x2 // src[6_0] + + uaddl v24.8h, v18.8b, v23.8b + umlal v24.8h, v20.8b, v30.8b + umlal v24.8h, v21.8b, v30.8b + umlsl v24.8h, v19.8b, v31.8b + umlsl v24.8h, v22.8b, v31.8b + + uaddl v16.8h, v2.8b, v12.8b + umlal v16.8h, v6.8b, v30.8b + umlal v16.8h, v8.8b, v30.8b + umlsl v16.8h, v4.8b, v31.8b + umlsl v16.8h, v10.8b, v31.8b + + sqrshrun v29.8b, v24.8h, #5 + ld1 {v18.2s, v19.2s}, [x6], x2 // horz row 1, col 0 + + uaddl v24.8h, v3.8b, v13.8b + umlal v24.8h, v7.8b, v30.8b + umlal v24.8h, v9.8b, v30.8b + umlsl v24.8h, v5.8b, v31.8b + umlsl v24.8h, v11.8b, v31.8b + urhadd v28.16b, v28.16b , v26.16b + urhadd v29.16b, v29.16b , v27.16b + sqrshrun v26.8b, v16.8h, #5 + ext v23.8b, v18.8b , v19.8b , #5 + ext v20.8b, v18.8b , v19.8b , #2 + ext v21.8b, v18.8b , v19.8b , #3 + ext v22.8b, v18.8b , v19.8b , #4 + st1 {v28.2s, v29.2s}, [x1], x3 // store row 0 + ext v19.8b, v18.8b , v19.8b , #1 + + sqrshrun v27.8b, v24.8h, #5 + + uaddl v28.8h, v18.8b, v23.8b + umlal v28.8h, v20.8b, v30.8b + umlal v28.8h, v21.8b, v30.8b + umlsl v28.8h, v19.8b, v31.8b + umlsl v28.8h, v22.8b, v31.8b + + ld1 {v18.2s, v19.2s}, [x11], x2 // horz row 1, col 1 + ld1 {v14.2s, v15.2s}, [x7], x2 // src[7_0] + + ext v23.8b, v18.8b , v19.8b , #5 + ext v20.8b, v18.8b , v19.8b , #2 + ext v21.8b, v18.8b , v19.8b , #3 + ext v22.8b, v18.8b , v19.8b , #4 + ext v19.8b, v18.8b , v19.8b , #1 + + sqrshrun v28.8b, v28.8h, #5 + uaddl v24.8h, v18.8b, v23.8b + umlal v24.8h, v20.8b, v30.8b + umlal v24.8h, v21.8b, v30.8b + umlsl v24.8h, v19.8b, v31.8b + umlsl v24.8h, v22.8b, v31.8b + + ld1 {v18.2s, v19.2s}, [x6], x2 // horz row 2, col 0 + uaddl v16.8h, v4.8b, v14.8b + umlal v16.8h, v8.8b, v30.8b + umlal v16.8h, v10.8b, v30.8b + umlsl v16.8h, v6.8b, v31.8b + umlsl v16.8h, v12.8b, v31.8b + + sqrshrun v29.8b, v24.8h, #5 + ext v23.8b, v18.8b , v19.8b , #5 + ext v20.8b, v18.8b , v19.8b , #2 + ext v21.8b, v18.8b , v19.8b , #3 + ext v22.8b, v18.8b , v19.8b , #4 + ext v19.8b, v18.8b , v19.8b , #1 + urhadd v28.16b, v28.16b , v26.16b + urhadd v29.16b, v29.16b , v27.16b + sqrshrun v26.8b, v16.8h, #5 + + uaddl v24.8h, v5.8b, v15.8b + umlal v24.8h, v9.8b, v30.8b + umlal v24.8h, v11.8b, v30.8b + umlsl v24.8h, v7.8b, v31.8b + umlsl v24.8h, v13.8b, v31.8b + + st1 {v28.2s, v29.2s}, [x1], x3 // store row 1 + + uaddl v28.8h, v18.8b, v23.8b + umlal v28.8h, v20.8b, v30.8b + umlal v28.8h, v21.8b, v30.8b + umlsl v28.8h, v19.8b, v31.8b + umlsl v28.8h, v22.8b, v31.8b + + ld1 {v18.2s, v19.2s}, [x11], x2 // horz row 2, col 1 + sqrshrun v27.8b, v24.8h, #5 + ext v23.8b, v18.8b , v19.8b , #5 + ext v20.8b, v18.8b , v19.8b , #2 + ext v21.8b, v18.8b , v19.8b , #3 + ext v22.8b, v18.8b , v19.8b , #4 + ext v19.8b, v18.8b , v19.8b , #1 + + sqrshrun v28.8b, v28.8h, #5 + ld1 {v16.2s, v17.2s}, [x7], x2 // src[8_0] + uaddl v24.8h, v18.8b, v23.8b + umlal v24.8h, v20.8b, v30.8b + umlal v24.8h, v21.8b, v30.8b + umlsl v24.8h, v19.8b, v31.8b + umlsl v24.8h, v22.8b, v31.8b + + ld1 {v18.2s, v19.2s}, [x6], x2 // horz row 3, col 0 + uaddl v0.8h, v6.8b, v16.8b + umlal v0.8h, v10.8b, v30.8b + umlal v0.8h, v12.8b, v30.8b + umlsl v0.8h, v8.8b, v31.8b + umlsl v0.8h, v14.8b, v31.8b + + sqrshrun v29.8b, v24.8h, #5 + + ext v23.8b, v18.8b , v19.8b , #5 + ext v20.8b, v18.8b , v19.8b , #2 + ext v21.8b, v18.8b , v19.8b , #3 + urhadd v28.16b, v28.16b , v26.16b + urhadd v29.16b, v29.16b , v27.16b + ext v22.8b, v18.8b , v19.8b , #4 + ext v19.8b, v18.8b , v19.8b , #1 + sqrshrun v26.8b, v0.8h, #5 + st1 {v28.2s, v29.2s}, [x1], x3 // store row 2 + + uaddl v24.8h, v18.8b, v23.8b + umlal v24.8h, v20.8b, v30.8b + umlal v24.8h, v21.8b, v30.8b + umlsl v24.8h, v19.8b, v31.8b + umlsl v24.8h, v22.8b, v31.8b + + ld1 {v18.2s, v19.2s}, [x11], x2 // horz row 3, col 1 + + uaddl v0.8h, v7.8b, v17.8b + umlal v0.8h, v11.8b, v30.8b + umlal v0.8h, v13.8b, v30.8b + umlsl v0.8h, v9.8b, v31.8b + umlsl v0.8h, v15.8b, v31.8b + + sqrshrun v28.8b, v24.8h, #5 + + ext v23.8b, v18.8b , v19.8b , #5 + ext v20.8b, v18.8b , v19.8b , #2 + ext v21.8b, v18.8b , v19.8b , #3 + ext v22.8b, v18.8b , v19.8b , #4 + ext v19.8b, v18.8b , v19.8b , #1 + + sqrshrun v27.8b, v0.8h, #5 + + uaddl v24.8h, v18.8b, v23.8b + umlal v24.8h, v20.8b, v30.8b + umlal v24.8h, v21.8b, v30.8b + umlsl v24.8h, v19.8b, v31.8b + umlsl v24.8h, v22.8b, v31.8b + + mov v0.16b, v8.16b + mov v1.16b, v9.16b + + mov v2.16b, v10.16b + mov v3.16b, v11.16b + + mov v4.16b, v12.16b + mov v5.16b, v13.16b + + mov v6.16b, v14.16b + mov v7.16b, v15.16b + + mov v8.16b, v16.16b + mov v9.16b, v17.16b + + sqrshrun v29.8b, v24.8h, #5 + urhadd v28.16b, v28.16b , v26.16b + urhadd v29.16b, v29.16b , v27.16b + st1 {v28.2s, v29.2s}, [x1], x3 // store row 3 + + ld1 {v10.2s, v11.2s}, [x7], x2 // Vector load from src[9_0] + ld1 {v18.2s, v19.2s}, [x6], x2 // horz row4, col 0 + uaddl v24.8h, v0.8b, v10.8b + umlal v24.8h, v4.8b, v30.8b + umlal v24.8h, v6.8b, v30.8b + umlsl v24.8h, v2.8b, v31.8b + umlsl v24.8h, v8.8b, v31.8b + ext v23.8b, v18.8b , v19.8b , #5 + ext v20.8b, v18.8b , v19.8b , #2 + ext v21.8b, v18.8b , v19.8b , #3 + ext v22.8b, v18.8b , v19.8b , #4 + ext v19.8b, v18.8b , v19.8b , #1 + sqrshrun v26.8b, v24.8h, #5 + uaddl v28.8h, v18.8b, v23.8b + umlal v28.8h, v20.8b, v30.8b + umlal v28.8h, v21.8b, v30.8b + umlsl v28.8h, v19.8b, v31.8b + umlsl v28.8h, v22.8b, v31.8b + ld1 {v18.2s, v19.2s}, [x11], x2 // horz row 4, col 1 + uaddl v24.8h, v1.8b, v11.8b + umlal v24.8h, v5.8b, v30.8b + umlal v24.8h, v7.8b, v30.8b + umlsl v24.8h, v3.8b, v31.8b + umlsl v24.8h, v9.8b, v31.8b + sqrshrun v28.8b, v28.8h, #5 + ext v23.8b, v18.8b , v19.8b , #5 + ext v20.8b, v18.8b , v19.8b , #2 + ext v21.8b, v18.8b , v19.8b , #3 + ext v22.8b, v18.8b , v19.8b , #4 + ext v19.8b, v18.8b , v19.8b , #1 + + sqrshrun v27.8b, v24.8h, #5 + ld1 {v12.2s, v13.2s}, [x7], x2 // src[10_0] + uaddl v24.8h, v18.8b, v23.8b + umlal v24.8h, v20.8b, v30.8b + umlal v24.8h, v21.8b, v30.8b + umlsl v24.8h, v19.8b, v31.8b + umlsl v24.8h, v22.8b, v31.8b + uaddl v16.8h, v2.8b, v12.8b + umlal v16.8h, v6.8b, v30.8b + umlal v16.8h, v8.8b, v30.8b + umlsl v16.8h, v4.8b, v31.8b + umlsl v16.8h, v10.8b, v31.8b + sqrshrun v29.8b, v24.8h, #5 + ld1 {v18.2s, v19.2s}, [x6], x2 // horz row 5, col 0 + uaddl v24.8h, v3.8b, v13.8b + umlal v24.8h, v7.8b, v30.8b + umlal v24.8h, v9.8b, v30.8b + umlsl v24.8h, v5.8b, v31.8b + umlsl v24.8h, v11.8b, v31.8b + urhadd v28.16b, v28.16b , v26.16b + urhadd v29.16b, v29.16b , v27.16b + sqrshrun v26.8b, v16.8h, #5 + ext v23.8b, v18.8b , v19.8b , #5 + ext v20.8b, v18.8b , v19.8b , #2 + ext v21.8b, v18.8b , v19.8b , #3 + ext v22.8b, v18.8b , v19.8b , #4 + st1 {v28.2s, v29.2s}, [x1], x3 // store row 4 + ext v19.8b, v18.8b , v19.8b , #1 + + sqrshrun v27.8b, v24.8h, #5 + + uaddl v28.8h, v18.8b, v23.8b + umlal v28.8h, v20.8b, v30.8b + umlal v28.8h, v21.8b, v30.8b + umlsl v28.8h, v19.8b, v31.8b + umlsl v28.8h, v22.8b, v31.8b + + ld1 {v18.2s, v19.2s}, [x11], x2 // horz row 5, col 1 + ld1 {v14.2s, v15.2s}, [x7], x2 // src[11_0] + + ext v23.8b, v18.8b , v19.8b , #5 + ext v20.8b, v18.8b , v19.8b , #2 + ext v21.8b, v18.8b , v19.8b , #3 + ext v22.8b, v18.8b , v19.8b , #4 + ext v19.8b, v18.8b , v19.8b , #1 + + sqrshrun v28.8b, v28.8h, #5 + uaddl v24.8h, v18.8b, v23.8b + umlal v24.8h, v20.8b, v30.8b + umlal v24.8h, v21.8b, v30.8b + umlsl v24.8h, v19.8b, v31.8b + umlsl v24.8h, v22.8b, v31.8b + + ld1 {v18.2s, v19.2s}, [x6], x2 // horz row 6, col 0 + uaddl v16.8h, v4.8b, v14.8b + umlal v16.8h, v8.8b, v30.8b + umlal v16.8h, v10.8b, v30.8b + umlsl v16.8h, v6.8b, v31.8b + umlsl v16.8h, v12.8b, v31.8b + + sqrshrun v29.8b, v24.8h, #5 + ext v23.8b, v18.8b , v19.8b , #5 + ext v20.8b, v18.8b , v19.8b , #2 + ext v21.8b, v18.8b , v19.8b , #3 + ext v22.8b, v18.8b , v19.8b , #4 + ext v19.8b, v18.8b , v19.8b , #1 + urhadd v28.16b, v28.16b , v26.16b + urhadd v29.16b, v29.16b , v27.16b + sqrshrun v26.8b, v16.8h, #5 + + uaddl v24.8h, v5.8b, v15.8b + umlal v24.8h, v9.8b, v30.8b + umlal v24.8h, v11.8b, v30.8b + umlsl v24.8h, v7.8b, v31.8b + umlsl v24.8h, v13.8b, v31.8b + + st1 {v28.2s, v29.2s}, [x1], x3 // store row 5 + + uaddl v28.8h, v18.8b, v23.8b + umlal v28.8h, v20.8b, v30.8b + umlal v28.8h, v21.8b, v30.8b + umlsl v28.8h, v19.8b, v31.8b + umlsl v28.8h, v22.8b, v31.8b + + ld1 {v18.2s, v19.2s}, [x11], x2 // horz row 6, col 1 + sqrshrun v27.8b, v24.8h, #5 + ext v23.8b, v18.8b , v19.8b , #5 + ext v20.8b, v18.8b , v19.8b , #2 + ext v21.8b, v18.8b , v19.8b , #3 + ext v22.8b, v18.8b , v19.8b , #4 + ext v19.8b, v18.8b , v19.8b , #1 + + sqrshrun v28.8b, v28.8h, #5 + ld1 {v16.2s, v17.2s}, [x7], x2 // src[12_0] + uaddl v24.8h, v18.8b, v23.8b + umlal v24.8h, v20.8b, v30.8b + umlal v24.8h, v21.8b, v30.8b + umlsl v24.8h, v19.8b, v31.8b + umlsl v24.8h, v22.8b, v31.8b + + ld1 {v18.2s, v19.2s}, [x6], x2 // horz row 7, col 0 + uaddl v0.8h, v6.8b, v16.8b + umlal v0.8h, v10.8b, v30.8b + umlal v0.8h, v12.8b, v30.8b + umlsl v0.8h, v8.8b, v31.8b + umlsl v0.8h, v14.8b, v31.8b + + sqrshrun v29.8b, v24.8h, #5 + + ext v23.8b, v18.8b , v19.8b , #5 + ext v20.8b, v18.8b , v19.8b , #2 + ext v21.8b, v18.8b , v19.8b , #3 + urhadd v28.16b, v28.16b , v26.16b + urhadd v29.16b, v29.16b , v27.16b + ext v22.8b, v18.8b , v19.8b , #4 + ext v19.8b, v18.8b , v19.8b , #1 + sqrshrun v26.8b, v0.8h, #5 + st1 {v28.2s, v29.2s}, [x1], x3 // store row 6 + + uaddl v24.8h, v18.8b, v23.8b + umlal v24.8h, v20.8b, v30.8b + umlal v24.8h, v21.8b, v30.8b + umlsl v24.8h, v19.8b, v31.8b + umlsl v24.8h, v22.8b, v31.8b + + ld1 {v18.2s, v19.2s}, [x11], x2 // horz row 7, col 1 + + uaddl v0.8h, v7.8b, v17.8b + umlal v0.8h, v11.8b, v30.8b + umlal v0.8h, v13.8b, v30.8b + umlsl v0.8h, v9.8b, v31.8b + umlsl v0.8h, v15.8b, v31.8b + + sqrshrun v28.8b, v24.8h, #5 + + ext v23.8b, v18.8b , v19.8b , #5 + ext v20.8b, v18.8b , v19.8b , #2 + ext v21.8b, v18.8b , v19.8b , #3 + ext v22.8b, v18.8b , v19.8b , #4 + ext v19.8b, v18.8b , v19.8b , #1 + + sqrshrun v27.8b, v0.8h, #5 + + uaddl v24.8h, v18.8b, v23.8b + umlal v24.8h, v20.8b, v30.8b + umlal v24.8h, v21.8b, v30.8b + umlsl v24.8h, v19.8b, v31.8b + umlsl v24.8h, v22.8b, v31.8b + + mov v0.16b, v8.16b + mov v1.16b, v9.16b + + mov v2.16b, v10.16b + mov v3.16b, v11.16b + + mov v4.16b, v12.16b + mov v5.16b, v13.16b + + mov v6.16b, v14.16b + mov v7.16b, v15.16b + + mov v8.16b, v16.16b + mov v9.16b, v17.16b + + sqrshrun v29.8b, v24.8h, #5 + subs x4, x4, #8 + urhadd v28.16b, v28.16b , v26.16b + urhadd v29.16b, v29.16b , v27.16b + st1 {v28.2s, v29.2s}, [x1], x3 // store row 7 + + beq end_func // stop looping if ht == 8 + b loop_16 + + +loop_8_start: + ld1 {v0.2s}, [x7], x2 // Vector load from src[0_0] + ld1 {v1.2s}, [x7], x2 // Vector load from src[1_0] + ld1 {v2.2s}, [x7], x2 // Vector load from src[2_0] + ld1 {v3.2s}, [x7], x2 // Vector load from src[3_0] + ld1 {v4.2s}, [x7], x2 // Vector load from src[4_0] + +loop_8: + ld1 {v5.2s}, [x7], x2 // Vector load from src[5_0] + uaddl v10.8h, v0.8b, v5.8b + umlal v10.8h, v2.8b, v30.8b + umlal v10.8h, v3.8b, v30.8b + umlsl v10.8h, v1.8b, v31.8b + umlsl v10.8h, v4.8b, v31.8b + ld1 {v12.2s, v13.2s}, [x6], x2 //horz row 0 + ext v17.8b, v12.8b , v13.8b , #5 + ext v14.8b, v12.8b , v13.8b , #2 + ext v15.8b, v12.8b , v13.8b , #3 + ext v16.8b, v12.8b , v13.8b , #4 + ext v13.8b, v12.8b , v13.8b , #1 + sqrshrun v26.8b, v10.8h, #5 + ld1 {v6.2s}, [x7], x2 // src[6_0] + uaddl v10.8h, v12.8b, v17.8b + umlal v10.8h, v14.8b, v30.8b + umlal v10.8h, v15.8b, v30.8b + umlsl v10.8h, v13.8b, v31.8b + umlsl v10.8h, v16.8b, v31.8b + ld1 {v12.2s, v13.2s}, [x6], x2 // horz row 1 + uaddl v18.8h, v1.8b, v6.8b + umlal v18.8h, v3.8b, v30.8b + umlal v18.8h, v4.8b, v30.8b + umlsl v18.8h, v2.8b, v31.8b + umlsl v18.8h, v5.8b, v31.8b + sqrshrun v28.8b, v10.8h, #5 + ext v17.8b, v12.8b , v13.8b , #5 + ext v14.8b, v12.8b , v13.8b , #2 + ext v15.8b, v12.8b , v13.8b , #3 + ext v16.8b, v12.8b , v13.8b , #4 + ext v13.8b, v12.8b , v13.8b , #1 + sqrshrun v27.8b, v18.8h, #5 + ld1 {v7.2s}, [x7], x2 // src[7_0] + uaddl v10.8h, v12.8b, v17.8b + umlal v10.8h, v14.8b, v30.8b + umlal v10.8h, v15.8b, v30.8b + umlsl v10.8h, v13.8b, v31.8b + umlsl v10.8h, v16.8b, v31.8b + ld1 {v12.2s, v13.2s}, [x6], x2 // horz row 2 + uaddl v18.8h, v2.8b, v7.8b + umlal v18.8h, v4.8b, v30.8b + umlal v18.8h, v5.8b, v30.8b + umlsl v18.8h, v3.8b, v31.8b + umlsl v18.8h, v6.8b, v31.8b + sqrshrun v29.8b, v10.8h, #5 + ext v17.8b, v12.8b , v13.8b , #5 + ext v14.8b, v12.8b , v13.8b , #2 + ext v15.8b, v12.8b , v13.8b , #3 + ext v16.8b, v12.8b , v13.8b , #4 + ext v13.8b, v12.8b , v13.8b , #1 + urhadd v26.16b, v26.16b , v28.16b + urhadd v27.16b, v27.16b , v29.16b + sqrshrun v28.8b, v18.8h, #5 + ld1 {v8.2s}, [x7], x2 // src[8_0] + uaddl v10.8h, v12.8b, v17.8b + umlal v10.8h, v14.8b, v30.8b + umlal v10.8h, v15.8b, v30.8b + umlsl v10.8h, v13.8b, v31.8b + umlsl v10.8h, v16.8b, v31.8b + ld1 {v12.2s, v13.2s}, [x6], x2 // horz row 3 + uaddl v18.8h, v3.8b, v8.8b + umlal v18.8h, v5.8b, v30.8b + umlal v18.8h, v6.8b, v30.8b + umlsl v18.8h, v4.8b, v31.8b + umlsl v18.8h, v7.8b, v31.8b + sqrshrun v24.8b, v10.8h, #5 + ext v17.8b, v12.8b , v13.8b , #5 + ext v14.8b, v12.8b , v13.8b , #2 + ext v15.8b, v12.8b , v13.8b , #3 + ext v16.8b, v12.8b , v13.8b , #4 + ext v13.8b, v12.8b , v13.8b , #1 + sqrshrun v29.8b, v18.8h, #5 + uaddl v10.8h, v12.8b, v17.8b + umlal v10.8h, v14.8b, v30.8b + umlal v10.8h, v15.8b, v30.8b + umlsl v10.8h, v13.8b, v31.8b + umlsl v10.8h, v16.8b, v31.8b + st1 {v26.2s}, [x1], x3 + + mov v0.16b, v4.16b + mov v1.16b, v5.16b + + st1 {v27.2s}, [x1], x3 + + mov v2.16b, v6.16b + mov v3.16b, v7.16b + + mov v4.8b, v8.8b + + sqrshrun v25.8b, v10.8h, #5 + subs x9, x4, #4 + urhadd v24.16b, v24.16b , v28.16b + urhadd v25.16b, v25.16b , v29.16b + st1 {v24.2s}, [x1], x3 + st1 {v25.2s}, [x1], x3 + beq end_func // Branch if height==4 + + ld1 {v5.2s}, [x7], x2 // Vector load from src[9_0] + uaddl v10.8h, v0.8b, v5.8b + umlal v10.8h, v2.8b, v30.8b + umlal v10.8h, v3.8b, v30.8b + umlsl v10.8h, v1.8b, v31.8b + umlsl v10.8h, v4.8b, v31.8b + ld1 {v12.2s, v13.2s}, [x6], x2 //horz row 4 + ext v17.8b, v12.8b , v13.8b , #5 + ext v14.8b, v12.8b , v13.8b , #2 + ext v15.8b, v12.8b , v13.8b , #3 + ext v16.8b, v12.8b , v13.8b , #4 + ext v13.8b, v12.8b , v13.8b , #1 + sqrshrun v26.8b, v10.8h, #5 + ld1 {v6.2s}, [x7], x2 // src[10_0] + uaddl v10.8h, v12.8b, v17.8b + umlal v10.8h, v14.8b, v30.8b + umlal v10.8h, v15.8b, v30.8b + umlsl v10.8h, v13.8b, v31.8b + umlsl v10.8h, v16.8b, v31.8b + ld1 {v12.2s, v13.2s}, [x6], x2 // horz row 5 + uaddl v18.8h, v1.8b, v6.8b + umlal v18.8h, v3.8b, v30.8b + umlal v18.8h, v4.8b, v30.8b + umlsl v18.8h, v2.8b, v31.8b + umlsl v18.8h, v5.8b, v31.8b + sqrshrun v28.8b, v10.8h, #5 + ext v17.8b, v12.8b , v13.8b , #5 + ext v14.8b, v12.8b , v13.8b , #2 + ext v15.8b, v12.8b , v13.8b , #3 + ext v16.8b, v12.8b , v13.8b , #4 + ext v13.8b, v12.8b , v13.8b , #1 + sqrshrun v27.8b, v18.8h, #5 + ld1 {v7.2s}, [x7], x2 // src[11_0] + uaddl v10.8h, v12.8b, v17.8b + umlal v10.8h, v14.8b, v30.8b + umlal v10.8h, v15.8b, v30.8b + umlsl v10.8h, v13.8b, v31.8b + umlsl v10.8h, v16.8b, v31.8b + ld1 {v12.2s, v13.2s}, [x6], x2 // horz row 6 + uaddl v18.8h, v2.8b, v7.8b + umlal v18.8h, v4.8b, v30.8b + umlal v18.8h, v5.8b, v30.8b + umlsl v18.8h, v3.8b, v31.8b + umlsl v18.8h, v6.8b, v31.8b + sqrshrun v29.8b, v10.8h, #5 + ext v17.8b, v12.8b , v13.8b , #5 + ext v14.8b, v12.8b , v13.8b , #2 + ext v15.8b, v12.8b , v13.8b , #3 + ext v16.8b, v12.8b , v13.8b , #4 + ext v13.8b, v12.8b , v13.8b , #1 + urhadd v26.16b, v26.16b , v28.16b + urhadd v27.16b, v27.16b , v29.16b + sqrshrun v28.8b, v18.8h, #5 + ld1 {v8.2s}, [x7], x2 // src[12_0] + uaddl v10.8h, v12.8b, v17.8b + umlal v10.8h, v14.8b, v30.8b + umlal v10.8h, v15.8b, v30.8b + umlsl v10.8h, v13.8b, v31.8b + umlsl v10.8h, v16.8b, v31.8b + ld1 {v12.2s, v13.2s}, [x6], x2 // horz row 7 + uaddl v18.8h, v3.8b, v8.8b + umlal v18.8h, v5.8b, v30.8b + umlal v18.8h, v6.8b, v30.8b + umlsl v18.8h, v4.8b, v31.8b + umlsl v18.8h, v7.8b, v31.8b + sqrshrun v24.8b, v10.8h, #5 + ext v17.8b, v12.8b , v13.8b , #5 + ext v14.8b, v12.8b , v13.8b , #2 + ext v15.8b, v12.8b , v13.8b , #3 + ext v16.8b, v12.8b , v13.8b , #4 + ext v13.8b, v12.8b , v13.8b , #1 + sqrshrun v29.8b, v18.8h, #5 + uaddl v10.8h, v12.8b, v17.8b + umlal v10.8h, v14.8b, v30.8b + umlal v10.8h, v15.8b, v30.8b + umlsl v10.8h, v13.8b, v31.8b + umlsl v10.8h, v16.8b, v31.8b + st1 {v26.2s}, [x1], x3 + + mov v0.16b, v4.16b + mov v1.16b, v5.16b + st1 {v27.2s}, [x1], x3 + + mov v2.16b, v6.16b + mov v3.16b, v7.16b + + mov v4.8b, v8.8b + mov v5.8b, v9.8b + + sqrshrun v25.8b, v10.8h, #5 + subs x4, x4, #8 + urhadd v24.16b, v24.16b , v28.16b + urhadd v25.16b, v25.16b , v29.16b + st1 {v24.2s}, [x1], x3 + st1 {v25.2s}, [x1], x3 + bgt loop_8 //if height =8 loop + b end_func + +loop_4_start: + ld1 {v0.s}[0], [x7], x2 // Vector load from src[0_0] + ld1 {v1.s}[0], [x7], x2 // Vector load from src[1_0] + + ld1 {v2.s}[0], [x7], x2 // Vector load from src[2_0] + ld1 {v3.s}[0], [x7], x2 // Vector load from src[3_0] + ld1 {v4.s}[0], [x7], x2 // Vector load from src[4_0] + + ld1 {v5.s}[0], [x7], x2 // Vector load from src[5_0] + uaddl v10.8h, v0.8b, v5.8b + umlal v10.8h, v2.8b, v30.8b + umlal v10.8h, v3.8b, v30.8b + umlsl v10.8h, v1.8b, v31.8b + umlsl v10.8h, v4.8b, v31.8b + ld1 {v12.2s, v13.2s}, [x6], x2 //load for horz filter row 0 + ext v17.8b, v12.8b , v13.8b , #5 + ext v14.8b, v12.8b , v13.8b , #2 + ext v15.8b, v12.8b , v13.8b , #3 + ext v16.8b, v12.8b , v13.8b , #4 + ext v13.8b, v12.8b , v13.8b , #1 + sqrshrun v26.8b, v10.8h, #5 + ld1 {v6.s}[0], [x7], x2 // Vector load from src[6_0] + uaddl v10.8h, v12.8b, v17.8b + umlal v10.8h, v14.8b, v30.8b + umlal v10.8h, v15.8b, v30.8b + umlsl v10.8h, v13.8b, v31.8b + umlsl v10.8h, v16.8b, v31.8b + ld1 {v12.2s, v13.2s}, [x6], x2 //horz row 1 + uaddl v18.8h, v1.8b, v6.8b + umlal v18.8h, v3.8b, v30.8b + umlal v18.8h, v4.8b, v30.8b + umlsl v18.8h, v2.8b, v31.8b + umlsl v18.8h, v5.8b, v31.8b + sqrshrun v28.8b, v10.8h, #5 + ext v17.8b, v12.8b , v13.8b , #5 + ext v14.8b, v12.8b , v13.8b , #2 + ext v15.8b, v12.8b , v13.8b , #3 + ext v16.8b, v12.8b , v13.8b , #4 + ext v13.8b, v12.8b , v13.8b , #1 + sqrshrun v27.8b, v18.8h, #5 + ld1 {v7.s}[0], [x7], x2 // Vector load from src[7_0] + uaddl v10.8h, v12.8b, v17.8b + umlal v10.8h, v14.8b, v30.8b + umlal v10.8h, v15.8b, v30.8b + umlsl v10.8h, v13.8b, v31.8b + umlsl v10.8h, v16.8b, v31.8b + ld1 {v12.2s, v13.2s}, [x6], x2 //horz row 2 + uaddl v18.8h, v2.8b, v7.8b + umlal v18.8h, v4.8b, v30.8b + umlal v18.8h, v5.8b, v30.8b + umlsl v18.8h, v3.8b, v31.8b + umlsl v18.8h, v6.8b, v31.8b + sqrshrun v29.8b, v10.8h, #5 + ext v17.8b, v12.8b , v13.8b , #5 + ext v14.8b, v12.8b , v13.8b , #2 + ext v15.8b, v12.8b , v13.8b , #3 + ext v16.8b, v12.8b , v13.8b , #4 + ext v13.8b, v12.8b , v13.8b , #1 + urhadd v26.16b, v26.16b , v28.16b + urhadd v27.16b, v27.16b , v29.16b + sqrshrun v28.8b, v18.8h, #5 + ld1 {v8.s}[0], [x7], x2 // Vector load from src[8_0] + uaddl v10.8h, v12.8b, v17.8b + umlal v10.8h, v14.8b, v30.8b + umlal v10.8h, v15.8b, v30.8b + umlsl v10.8h, v13.8b, v31.8b + umlsl v10.8h, v16.8b, v31.8b + ld1 {v12.2s, v13.2s}, [x6], x2 //horz row 3 + uaddl v18.8h, v3.8b, v8.8b + umlal v18.8h, v5.8b, v30.8b + umlal v18.8h, v6.8b, v30.8b + umlsl v18.8h, v4.8b, v31.8b + umlsl v18.8h, v7.8b, v31.8b + sqrshrun v24.8b, v10.8h, #5 + ext v17.8b, v12.8b , v13.8b , #5 + ext v14.8b, v12.8b , v13.8b , #2 + ext v15.8b, v12.8b , v13.8b , #3 + ext v16.8b, v12.8b , v13.8b , #4 + ext v13.8b, v12.8b , v13.8b , #1 + sqrshrun v29.8b, v18.8h, #5 + uaddl v10.8h, v12.8b, v17.8b + umlal v10.8h, v14.8b, v30.8b + umlal v10.8h, v15.8b, v30.8b + umlsl v10.8h, v13.8b, v31.8b + umlsl v10.8h, v16.8b, v31.8b + st1 {v26.s}[0], [x1], x3 + + mov v0.16b, v4.16b + mov v1.16b, v5.16b + + st1 {v27.s}[0], [x1], x3 + + mov v2.16b, v6.16b + mov v3.16b, v7.16b + mov v4.8b, v8.8b + + sqrshrun v25.8b, v10.8h, #5 + subs x4, x4, #4 + urhadd v24.16b, v24.16b , v28.16b + urhadd v25.16b, v25.16b , v29.16b + st1 {v24.s}[0], [x1], x3 + st1 {v25.s}[0], [x1], x3 + beq end_func // Branch if height==4 + + ld1 {v5.s}[0], [x7], x2 // Vector load from src[5_0] + uaddl v10.8h, v0.8b, v5.8b + umlal v10.8h, v2.8b, v30.8b + umlal v10.8h, v3.8b, v30.8b + umlsl v10.8h, v1.8b, v31.8b + umlsl v10.8h, v4.8b, v31.8b + ld1 {v12.2s, v13.2s}, [x6], x2 //load for horz filter row 4 + ext v17.8b, v12.8b , v13.8b , #5 + ext v14.8b, v12.8b , v13.8b , #2 + ext v15.8b, v12.8b , v13.8b , #3 + ext v16.8b, v12.8b , v13.8b , #4 + ext v13.8b, v12.8b , v13.8b , #1 + sqrshrun v26.8b, v10.8h, #5 + ld1 {v6.s}[0], [x7], x2 + uaddl v10.8h, v12.8b, v17.8b + umlal v10.8h, v14.8b, v30.8b + umlal v10.8h, v15.8b, v30.8b + umlsl v10.8h, v13.8b, v31.8b + umlsl v10.8h, v16.8b, v31.8b + ld1 {v12.2s, v13.2s}, [x6], x2 //horz row 5 + uaddl v18.8h, v1.8b, v6.8b + umlal v18.8h, v3.8b, v30.8b + umlal v18.8h, v4.8b, v30.8b + umlsl v18.8h, v2.8b, v31.8b + umlsl v18.8h, v5.8b, v31.8b + sqrshrun v28.8b, v10.8h, #5 + ext v17.8b, v12.8b , v13.8b , #5 + ext v14.8b, v12.8b , v13.8b , #2 + ext v15.8b, v12.8b , v13.8b , #3 + ext v16.8b, v12.8b , v13.8b , #4 + ext v13.8b, v12.8b , v13.8b , #1 + sqrshrun v27.8b, v18.8h, #5 + ld1 {v7.s}[0], [x7], x2 + uaddl v10.8h, v12.8b, v17.8b + umlal v10.8h, v14.8b, v30.8b + umlal v10.8h, v15.8b, v30.8b + umlsl v10.8h, v13.8b, v31.8b + umlsl v10.8h, v16.8b, v31.8b + ld1 {v12.2s, v13.2s}, [x6], x2 //horz row 6 + uaddl v18.8h, v2.8b, v7.8b + umlal v18.8h, v4.8b, v30.8b + umlal v18.8h, v5.8b, v30.8b + umlsl v18.8h, v3.8b, v31.8b + umlsl v18.8h, v6.8b, v31.8b + sqrshrun v29.8b, v10.8h, #5 + ext v17.8b, v12.8b , v13.8b , #5 + ext v14.8b, v12.8b , v13.8b , #2 + ext v15.8b, v12.8b , v13.8b , #3 + ext v16.8b, v12.8b , v13.8b , #4 + ext v13.8b, v12.8b , v13.8b , #1 + urhadd v26.16b, v26.16b , v28.16b + urhadd v27.16b, v27.16b , v29.16b + sqrshrun v28.8b, v18.8h, #5 + ld1 {v8.s}[0], [x7], x2 + uaddl v10.8h, v12.8b, v17.8b + umlal v10.8h, v14.8b, v30.8b + umlal v10.8h, v15.8b, v30.8b + umlsl v10.8h, v13.8b, v31.8b + umlsl v10.8h, v16.8b, v31.8b + ld1 {v12.2s, v13.2s}, [x6], x2 //horz row 7 + uaddl v18.8h, v3.8b, v8.8b + umlal v18.8h, v5.8b, v30.8b + umlal v18.8h, v6.8b, v30.8b + umlsl v18.8h, v4.8b, v31.8b + umlsl v18.8h, v7.8b, v31.8b + sqrshrun v24.8b, v10.8h, #5 + ext v17.8b, v12.8b , v13.8b , #5 + ext v14.8b, v12.8b , v13.8b , #2 + ext v15.8b, v12.8b , v13.8b , #3 + ext v16.8b, v12.8b , v13.8b , #4 + ext v13.8b, v12.8b , v13.8b , #1 + sqrshrun v29.8b, v18.8h, #5 + uaddl v10.8h, v12.8b, v17.8b + umlal v10.8h, v14.8b, v30.8b + umlal v10.8h, v15.8b, v30.8b + umlsl v10.8h, v13.8b, v31.8b + umlsl v10.8h, v16.8b, v31.8b + st1 {v26.s}[0], [x1], x3 + st1 {v27.s}[0], [x1], x3 + sqrshrun v25.8b, v10.8h, #5 + urhadd v24.16b, v24.16b , v28.16b + urhadd v25.16b, v25.16b , v29.16b + st1 {v24.s}[0], [x1], x3 + st1 {v25.s}[0], [x1], x3 + +end_func: + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + + diff --git a/dependencies/ih264d/common/armv8/ih264_inter_pred_luma_vert_qpel_av8.s b/dependencies/ih264d/common/armv8/ih264_inter_pred_luma_vert_qpel_av8.s new file mode 100644 index 00000000..014facaf --- /dev/null +++ b/dependencies/ih264d/common/armv8/ih264_inter_pred_luma_vert_qpel_av8.s @@ -0,0 +1,515 @@ +//****************************************************************************** +//* +//* Copyright (C) 2015 The Android Open Source Project +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at: +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. +//* +//***************************************************************************** +//* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +//*/ +///** +//****************************************************************************** +//* @file +//* ih264_inter_pred_luma_vert_qpel_av8.s +//* +//* @brief +//* Contains function definitions for inter prediction vertical quarter pel interpolation. +//* +//* @author +//* Mohit +//* +//* @par List of Functions: +//* +//* - ih264_inter_pred_luma_vert_qpel_av8() +//* +//* @remarks +//* None +//* +//******************************************************************************* +//*/ + +///* All the functions here are replicated from ih264_inter_pred_filters.c +// + +///** +///** +//******************************************************************************* +//* +//* @brief +//* Quarter pel interprediction luma filter for vertical input +//* +//* @par Description: +//* Applies a 6 tap horizontal filter .The output is clipped to 8 bits +//* sec 8.4.2.2.1 titled "Luma sample interpolation process" +//* +//* @param[in] pu1_src +//* UWORD8 pointer to the source +//* +//* @param[out] pu1_dst +//* UWORD8 pointer to the destination +//* +//* @param[in] src_strd +//* integer source stride +//* +//* @param[in] dst_strd +//* integer destination stride +//* +//* @param[in] ht +//* integer height of the array +//* +//* @param[in] wd +//* integer width of the array +//* +//* @param[in] pu1_tmp: temporary buffer: UNUSED in this function +//* +//* @param[in] dydx: x and y reference offset for qpel calculations. +//* @returns +//* +// @remarks +//* None +//* +//******************************************************************************* +//*/ + +//void ih264_inter_pred_luma_vert ( +// UWORD8 *pu1_src, +// UWORD8 *pu1_dst, +// WORD32 src_strd, +// WORD32 dst_strd, +// WORD32 ht, +// WORD32 wd, +// UWORD8* pu1_tmp, +// UWORD32 dydx) + +//**************Variables Vs Registers***************************************** +// x0 => *pu1_src +// x1 => *pu1_dst +// w2 => src_strd +// w3 => dst_strd +// w4 => ht +// w5 => wd +// w7 => dydx + +.text +.p2align 2 +.include "ih264_neon_macros.s" + + + + .global ih264_inter_pred_luma_vert_qpel_av8 + +ih264_inter_pred_luma_vert_qpel_av8: + + push_v_regs + stp x19, x20, [sp, #-16]! + sxtw x2, w2 + sxtw x3, w3 + sxtw x4, w4 + sxtw x5, w5 + + + and x7, x7, #12 //Finds y-offset + lsr x7, x7, #3 //dydx>>3 + mul x7, x2, x7 + add x7, x0, x7 //pu1_src + (y_offset>>1)*src_strd + sub x14, x4, #16 + movi v22.8h, #20 // Filter coeff 0x14 into Q11 + sub x0, x0, x2, lsl #1 //pu1_src-2*src_strd + subs x12, x5, #8 //if wd=8 branch to loop_8 + movi v24.8h, #5 // Filter coeff 0x4 into Q12 + beq loop_8_start + + subs x12, x5, #4 //if wd=4 branch to loop_4 + beq loop_4_start + + + ld1 {v0.2s, v1.2s}, [x0], x2 // Vector load from src[0_0] + ld1 {v2.2s, v3.2s}, [x0], x2 // Vector load from src[1_0] + ld1 {v4.2s, v5.2s}, [x0], x2 // Vector load from src[2_0] + ld1 {v6.2s, v7.2s}, [x0], x2 // Vector load from src[3_0] + add x14, x14, #1 //for checking loop + ld1 {v8.2s, v9.2s}, [x0], x2 // Vector load from src[4_0] + uaddl v12.8h, v4.8b, v6.8b // temp1 = src[2_0] + src[3_0] + ld1 {v10.2s, v11.2s}, [x0], x2 // Vector load from src[5_0] + +loop_16: //when wd=16 + + uaddl v14.8h, v0.8b, v10.8b // temp = src[0_0] + src[5_0] + uaddl v16.8h, v2.8b, v8.8b // temp2 = src[1_0] + src[4_0] + mla v14.8h, v12.8h , v22.8h // temp += temp1 * 20 + uaddl v20.8h, v1.8b, v11.8b // temp4 = src[0_8] + src[5_8] + uaddl v18.8h, v5.8b, v7.8b // temp3 = src[2_8] + src[3_8] + mla v20.8h, v18.8h , v22.8h // temp4 += temp3 * 20 + ld1 {v0.2s, v1.2s}, [x0], x2 + uaddl v26.8h, v3.8b, v9.8b // temp5 = src[1_8] + src[4_8] + uaddl v12.8h, v6.8b, v8.8b + mls v14.8h, v16.8h , v24.8h // temp -= temp2 * 5 + uaddl v16.8h, v2.8b, v0.8b + uaddl v18.8h, v4.8b, v10.8b + mla v16.8h, v12.8h , v22.8h + mls v20.8h, v26.8h , v24.8h // temp4 -= temp5 * 5 + uaddl v26.8h, v5.8b, v11.8b + uaddl v12.8h, v7.8b, v9.8b + sqrshrun v30.8b, v14.8h, #5 // dst[0_0] = CLIP_U8((temp +16) >> 5) + uaddl v14.8h, v3.8b, v1.8b + ld1 {v2.2s, v3.2s}, [x0], x2 + mla v14.8h, v12.8h , v22.8h + mls v16.8h, v18.8h , v24.8h + sqrshrun v31.8b, v20.8h, #5 // dst[0_8] = CLIP_U8((temp4 +16) >> 5) + ld1 {v20.2s, v21.2s}, [x7], x2 // Load for interpolation row 0 + urhadd v30.16b, v20.16b , v30.16b // Interpolation to obtain qpel value + urhadd v31.16b, v21.16b , v31.16b // Interpolation to obtain qpel value + uaddl v18.8h, v4.8b, v2.8b + uaddl v12.8h, v8.8b, v10.8b + st1 {v30.2s, v31.2s}, [x1], x3 // Vector store to dst[0_0] + mla v18.8h, v12.8h , v22.8h + uaddl v20.8h, v6.8b, v0.8b + mls v14.8h, v26.8h , v24.8h + sqrshrun v30.8b, v16.8h, #5 + uaddl v12.8h, v9.8b, v11.8b + uaddl v16.8h, v5.8b, v3.8b + uaddl v26.8h, v7.8b, v1.8b + mla v16.8h, v12.8h , v22.8h + mls v18.8h, v20.8h , v24.8h + ld1 {v4.2s, v5.2s}, [x0], x2 + sqrshrun v31.8b, v14.8h, #5 + ld1 {v14.2s, v15.2s}, [x7], x2 // Load for interpolation row 1 + uaddl v12.8h, v10.8b, v0.8b + urhadd v30.16b, v14.16b , v30.16b // Interpolation to obtain qpel value + urhadd v31.16b, v15.16b , v31.16b // Interpolation to obtain qpel value + uaddl v14.8h, v6.8b, v4.8b + uaddl v20.8h, v8.8b, v2.8b + mla v14.8h, v12.8h , v22.8h + mls v16.8h, v26.8h , v24.8h + st1 {v30.2s, v31.2s}, [x1], x3 //store row 1 + sqrshrun v30.8b, v18.8h, #5 + uaddl v18.8h, v7.8b, v5.8b + uaddl v12.8h, v11.8b, v1.8b + mla v18.8h, v12.8h , v22.8h + uaddl v26.8h, v9.8b, v3.8b + mls v14.8h, v20.8h , v24.8h + ld1 {v6.2s, v7.2s}, [x0], x2 + sqrshrun v31.8b, v16.8h, #5 + ld1 {v16.2s, v17.2s}, [x7], x2 // Load for interpolation row 2 + mls v18.8h, v26.8h , v24.8h + urhadd v30.16b, v16.16b , v30.16b // Interpolation to obtain qpel value + urhadd v31.16b, v17.16b , v31.16b // Interpolation to obtain qpel value + uaddl v12.8h, v0.8b, v2.8b // temp1 = src[2_0] + src[3_0] + st1 {v30.2s, v31.2s}, [x1], x3 //store row 2 + uaddl v16.8h, v10.8b, v4.8b // temp2 = src[1_0] + src[4_0] + uaddl v20.8h, v9.8b, v7.8b // temp4 = src[0_8] + src[5_8] + sqrshrun v30.8b, v14.8h, #5 + uaddl v26.8h, v5.8b, v11.8b // temp5 = src[1_8] + src[4_8] + uaddl v14.8h, v8.8b, v6.8b // temp = src[0_0] + src[5_0] + sqrshrun v31.8b, v18.8h, #5 + ld1 {v18.2s, v19.2s}, [x7], x2 // Load for interpolation row 3 + mla v14.8h, v12.8h , v22.8h // temp += temp1 * 20 + urhadd v30.16b, v18.16b , v30.16b // Interpolation to obtain qpel value + urhadd v31.16b, v19.16b , v31.16b // Interpolation to obtain qpel value + uaddl v18.8h, v1.8b, v3.8b // temp3 = src[2_8] + src[3_8] + st1 {v30.2s, v31.2s}, [x1], x3 //store row 3 + // 4 rows processed + mla v20.8h, v18.8h , v22.8h // temp4 += temp3 * 20 + ld1 {v8.2s, v9.2s}, [x0], x2 + uaddl v12.8h, v2.8b, v4.8b + uaddl v18.8h, v3.8b, v5.8b + mls v14.8h, v16.8h , v24.8h // temp -= temp2 * 5 + uaddl v28.8h, v9.8b, v11.8b + uaddl v16.8h, v6.8b, v0.8b + mla v28.8h, v18.8h , v22.8h // temp4 += temp3 * 20 + mls v20.8h, v26.8h , v24.8h // temp4 -= temp5 * 5 + uaddl v26.8h, v1.8b, v7.8b + uaddl v18.8h, v5.8b, v7.8b + sqrshrun v30.8b, v14.8h, #5 // dst[0_0] = CLIP_U8((temp +16) >> 5) + uaddl v14.8h, v8.8b, v10.8b + sqrshrun v31.8b, v20.8h, #5 // dst[0_8] = CLIP_U8((temp4 +16) >> 5) + ld1 {v20.2s, v21.2s}, [x7], x2 // Load for interpolation row 4 + ld1 {v10.2s, v11.2s}, [x0], x2 + urhadd v30.16b, v20.16b , v30.16b // Interpolation to obtain qpel value + urhadd v31.16b, v21.16b , v31.16b // Interpolation to obtain qpel value + mls v28.8h, v26.8h , v24.8h // temp4 -= temp5 * 5 + st1 {v30.2s, v31.2s}, [x1], x3 // store row 4 + mla v14.8h, v12.8h , v22.8h // temp += temp1 * 20 + uaddl v20.8h, v11.8b, v1.8b + uaddl v26.8h, v3.8b, v9.8b + mla v20.8h, v18.8h , v22.8h // temp4 += temp3 * 20 + uaddl v12.8h, v6.8b, v4.8b + uaddl v18.8h, v7.8b, v9.8b + sqrshrun v31.8b, v28.8h, #5 // dst[0_8] = CLIP_U8((temp4 +16) >> 5) + mls v14.8h, v16.8h , v24.8h // temp -= temp2 * 5 + uaddl v16.8h, v8.8b, v2.8b + sqrshrun v30.8b, v14.8h, #5 // dst[0_0] = CLIP_U8((temp +16) >> 5) + ld1 {v14.2s, v15.2s}, [x7], x2 // Load for interpolation row 5 + mls v20.8h, v26.8h , v24.8h // temp4 -= temp5 * 5 + urhadd v30.16b, v14.16b , v30.16b // Interpolation to obtain qpel value + urhadd v31.16b, v15.16b , v31.16b // Interpolation to obtain qpel value + uaddl v14.8h, v10.8b, v0.8b + st1 {v30.2s, v31.2s}, [x1], x3 // store row 5 + mla v14.8h, v12.8h , v22.8h // temp += temp1 * 20 + ld1 {v0.2s, v1.2s}, [x0], x2 + uaddl v26.8h, v5.8b, v11.8b + uaddl v12.8h, v8.8b, v6.8b + uaddl v28.8h, v0.8b, v2.8b + sqrshrun v31.8b, v20.8h, #5 // dst[0_8] = CLIP_U8((temp4 +16) >> 5) + mla v28.8h, v12.8h , v22.8h // temp += temp1 * 20 + uaddl v20.8h, v1.8b, v3.8b + mls v14.8h, v16.8h , v24.8h // temp -= temp2 * 5 + mla v20.8h, v18.8h , v22.8h // temp4 += temp3 * 20 + uaddl v16.8h, v10.8b, v4.8b + sqrshrun v30.8b, v14.8h, #5 // dst[0_0] = CLIP_U8((temp +16) >> 5) + ld1 {v14.2s, v15.2s}, [x7], x2 // Load for interpolation row 6 + mov v2.8b, v6.8b + mov v3.8b, v7.8b + urhadd v30.16b, v14.16b , v30.16b // Interpolation to obtain qpel value + urhadd v31.16b, v15.16b , v31.16b // Interpolation to obtain qpel value + + mls v28.8h, v16.8h , v24.8h // temp -= temp2 * 5 + st1 {v30.2s, v31.2s}, [x1], x3 // store row 6 + sqrshrun v30.8b, v28.8h, #5 // dst[0_0] = CLIP_U8((temp +16) >> 5) + swp v0.8b, v4.8b // swapping registers to put it in order + swp v1.8b, v5.8b // swapping registers to put it in order + + mls v20.8h, v26.8h , v24.8h // temp4 -= temp5 * 5 + mov v6.8b, v10.8b + mov v7.8b, v11.8b + subs x12, x14, #1 // if height==16 - looping + swp v4.8b, v8.8b + swp v5.8b, v9.8b + sqrshrun v31.8b, v20.8h, #5 // dst[0_8] = CLIP_U8((temp4 +16) >> 5) + ld1 {v20.2s, v21.2s}, [x7], x2 // Load for interpolation row 7 + urhadd v30.16b, v20.16b , v30.16b // Interpolation to obtain qpel value + urhadd v31.16b, v21.16b , v31.16b // Interpolation to obtain qpel value + st1 {v30.2s, v31.2s}, [x1], x3 // store row 7 + bne end_func //if height =8 end function + add x14, x14, #1 //for checking loop + ld1 {v10.2s, v11.2s}, [x0], x2 + uaddl v12.8h, v4.8b, v6.8b // temp1 = src[2_0] + src[3_0] + + b loop_16 // looping if height =16 + +loop_8_start: +//// Processing row0 and row1 + + ld1 {v0.2s}, [x0], x2 // Vector load from src[0_0] + ld1 {v1.2s}, [x0], x2 // Vector load from src[1_0] + ld1 {v2.2s}, [x0], x2 // Vector load from src[2_0] + ld1 {v3.2s}, [x0], x2 // Vector load from src[3_0] + add x14, x14, #1 //for checking loop + ld1 {v4.2s}, [x0], x2 // Vector load from src[4_0] + ld1 {v5.2s}, [x0], x2 // Vector load from src[5_0] + +loop_8: + //for checking loop + uaddl v6.8h, v2.8b, v3.8b // temp1 = src[2_0] + src[3_0] + uaddl v8.8h, v0.8b, v5.8b // temp = src[0_0] + src[5_0] + uaddl v10.8h, v1.8b, v4.8b // temp2 = src[1_0] + src[4_0] + mla v8.8h, v6.8h , v22.8h // temp += temp1 * 20 + ld1 {v6.2s}, [x0], x2 + uaddl v14.8h, v3.8b, v4.8b + uaddl v16.8h, v1.8b, v6.8b + uaddl v18.8h, v2.8b, v5.8b + mls v8.8h, v10.8h , v24.8h // temp -= temp2 * 5 + mla v16.8h, v14.8h , v22.8h + ld1 {v7.2s}, [x0], x2 + uaddl v20.8h, v4.8b, v5.8b + uaddl v12.8h, v2.8b, v7.8b + uaddl v10.8h, v3.8b, v6.8b + mls v16.8h, v18.8h , v24.8h + sqrshrun v26.8b, v8.8h, #5 // dst[0_0] = CLIP_U8( (temp + 16) >> 5) + mla v12.8h, v20.8h , v22.8h + ld1 {v8.2s}, [x7], x2 //Load value for interpolation (row0) + ld1 {v9.2s}, [x7], x2 //Load value for interpolation (row1) + ld1 {v0.2s}, [x0], x2 + uaddl v14.8h, v5.8b, v6.8b + sqrshrun v27.8b, v16.8h, #5 + urhadd v26.16b, v8.16b , v26.16b // Interpolation step for qpel calculation + urhadd v27.16b, v9.16b , v27.16b // Interpolation step for qpel calculation + + uaddl v20.8h, v3.8b, v0.8b + mls v12.8h, v10.8h , v24.8h + st1 {v26.2s}, [x1], x3 // Vector store to dst[0_0] + uaddl v18.8h, v4.8b, v7.8b + mla v20.8h, v14.8h , v22.8h + st1 {v27.2s}, [x1], x3 // Vector store to dst[1_0] + sqrshrun v28.8b, v12.8h, #5 + mls v20.8h, v18.8h , v24.8h + ld1 {v12.2s}, [x7], x2 //Load value for interpolation (row2) + ld1 {v13.2s}, [x7], x2 //Load value for interpolation (row3) + ld1 {v1.2s}, [x0], x2 + sqrshrun v29.8b, v20.8h, #5 + subs x9, x4, #4 + urhadd v28.16b, v12.16b , v28.16b + urhadd v29.16b, v13.16b , v29.16b + st1 {v28.2s}, [x1], x3 //store row 2 + st1 {v29.2s}, [x1], x3 //store row 3 + beq end_func // Branch if height==4 + uaddl v14.8h, v6.8b, v7.8b // temp1 = src[2_0] + src[3_0] + uaddl v16.8h, v0.8b, v5.8b // temp = src[0_0] + src[5_0] + uaddl v18.8h, v1.8b, v4.8b // temp2 = src[1_0] + src[4_0] + mla v18.8h, v14.8h , v22.8h // temp += temp1 * 20 + ld1 {v2.2s}, [x0], x2 + mls v18.8h, v16.8h , v24.8h // temp -= temp2 * 5 + uaddl v8.8h, v0.8b, v7.8b + uaddl v10.8h, v1.8b, v6.8b + uaddl v12.8h, v2.8b, v5.8b + sqrshrun v26.8b, v18.8h, #5 + mla v12.8h, v8.8h , v22.8h + ld1 {v18.2s}, [x7], x2 //Load value for interpolation (row4) + ld1 {v19.2s}, [x7], x2 //Load value for interpolation (row5) + ld1 {v3.2s}, [x0], x2 + mls v12.8h, v10.8h , v24.8h + sqrshrun v27.8b, v12.8h, #5 + urhadd v26.16b, v18.16b , v26.16b // Interpolation step for qpel calculation + urhadd v27.16b, v19.16b , v27.16b // Interpolation step for qpel calculation + + st1 {v26.2s}, [x1], x3 // store row 4 + st1 {v27.2s}, [x1], x3 // store row 5 + uaddl v14.8h, v0.8b, v1.8b // temp1 = src[2_0] + src[3_0] + uaddl v16.8h, v2.8b, v7.8b // temp = src[0_0] + src[5_0] + uaddl v18.8h, v3.8b, v6.8b // temp2 = src[1_0] + src[4_0] + mla v18.8h, v14.8h , v22.8h // temp += temp1 * 20 + ld1 {v4.2s}, [x0], x2 + mls v18.8h, v16.8h , v24.8h // temp -= temp2 * 5 + uaddl v8.8h, v2.8b, v1.8b + uaddl v10.8h, v3.8b, v0.8b + uaddl v12.8h, v4.8b, v7.8b + sqrshrun v26.8b, v18.8h, #5 + mla v12.8h, v8.8h , v22.8h + ld1 {v18.2s}, [x7], x2 //Load value for interpolation (row6) + ld1 {v19.2s}, [x7], x2 //Load value for interpolation (row7) + ld1 {v5.2s}, [x0], x2 + mls v12.8h, v10.8h , v24.8h + sqrshrun v27.8b, v12.8h, #5 + urhadd v26.16b, v18.16b , v26.16b // Interpolation step for qpel calculation + urhadd v27.16b, v19.16b , v27.16b // Interpolation step for qpel calculation + + subs x12, x14, #1 + st1 {v26.2s}, [x1], x3 // store row 6 + st1 {v27.2s}, [x1], x3 // store row 7 + add x14, x14, #1 + beq loop_8 //looping if height ==16 + + b end_func + + +loop_4_start: +//// Processing row0 and row1 + + + ld1 {v0.s}[0], [x0], x2 // Vector load from src[0_0] + ld1 {v1.s}[0], [x0], x2 // Vector load from src[1_0] + ld1 {v2.s}[0], [x0], x2 // Vector load from src[2_0] + ld1 {v3.s}[0], [x0], x2 // Vector load from src[3_0] + ld1 {v4.s}[0], [x0], x2 // Vector load from src[4_0] + ld1 {v5.s}[0], [x0], x2 // Vector load from src[5_0] + + uaddl v6.8h, v2.8b, v3.8b // temp1 = src[2_0] + src[3_0] + uaddl v8.8h, v0.8b, v5.8b // temp = src[0_0] + src[5_0] + uaddl v10.8h, v1.8b, v4.8b // temp2 = src[1_0] + src[4_0] + mla v8.8h, v6.8h , v22.8h // temp += temp1 * 20 + ld1 {v6.2s}, [x0], x2 + uaddl v14.8h, v3.8b, v4.8b + uaddl v16.8h, v1.8b, v6.8b + uaddl v18.8h, v2.8b, v5.8b + mls v8.8h, v10.8h , v24.8h // temp -= temp2 * 5 + ld1 {v7.s}[0], [x0], x2 + mla v16.8h, v14.8h , v22.8h + uaddl v20.8h, v4.8b, v5.8b + uaddl v12.8h, v2.8b, v7.8b + uaddl v10.8h, v3.8b, v6.8b + mls v16.8h, v18.8h , v24.8h + sqrshrun v26.8b, v8.8h, #5 // dst[0_0] = CLIP_U8( (temp + 16) >> 5) + ld1 {v8.s}[0], [x7], x2 //Load value for interpolation - row 0 + ld1 {v9.s}[0], [x7], x2 //Load value for interpolation - row 1 + mla v12.8h, v20.8h , v22.8h + ld1 {v0.s}[0], [x0], x2 + uaddl v14.8h, v5.8b, v6.8b + sqrshrun v27.8b, v16.8h, #5 + uaddl v20.8h, v3.8b, v0.8b + urhadd v26.16b, v26.16b , v8.16b //Interpolation step for qpel calculation + urhadd v27.16b, v27.16b , v9.16b //Interpolation step for qpel calculation + + mls v12.8h, v10.8h , v24.8h + st1 {v26.s}[0], [x1], x3 // Vector store to dst[0_0] + uaddl v18.8h, v4.8b, v7.8b + mla v20.8h, v14.8h , v22.8h + st1 {v27.s}[0], [x1], x3 // store row 1 + sqrshrun v28.8b, v12.8h, #5 + ld1 {v12.s}[0], [x7], x2 //Load value for interpolation - row 2 + ld1 {v13.s}[0], [x7], x2 //Load value for interpolation - row 3 + + mls v20.8h, v18.8h , v24.8h + ld1 {v1.s}[0], [x0], x2 + sqrshrun v29.8b, v20.8h, #5 + urhadd v28.16b, v12.16b , v28.16b //Interpolation step for qpel calculation + urhadd v29.16b, v13.16b , v29.16b //Interpolation step for qpel calculation + + st1 {v28.s}[0], [x1], x3 //store row 2 + st1 {v29.s}[0], [x1], x3 //store row 3 + + subs x9, x4, #4 + beq end_func // Branch if height==4 + + + uaddl v14.8h, v6.8b, v7.8b // temp1 = src[2_0] + src[3_0] + uaddl v16.8h, v0.8b, v5.8b // temp = src[0_0] + src[5_0] + uaddl v18.8h, v1.8b, v4.8b // temp2 = src[1_0] + src[4_0] + mla v18.8h, v14.8h , v22.8h // temp += temp1 * 20 + ld1 {v2.s}[0], [x0], x2 + mls v18.8h, v16.8h , v24.8h // temp -= temp2 * 5 + uaddl v8.8h, v0.8b, v7.8b + uaddl v10.8h, v1.8b, v6.8b + uaddl v12.8h, v2.8b, v5.8b + sqrshrun v26.8b, v18.8h, #5 + ld1 {v18.s}[0], [x7], x2 //Load value for interpolation - row 4 + ld1 {v19.s}[0], [x7], x2 //Load value for interpolation - row 5 + mla v12.8h, v8.8h , v22.8h + ld1 {v3.s}[0], [x0], x2 + mls v12.8h, v10.8h , v24.8h + sqrshrun v27.8b, v12.8h, #5 + urhadd v26.16b, v18.16b , v26.16b //Interpolation step for qpel calculation + urhadd v27.16b, v27.16b , v19.16b //Interpolation step for qpel calculation + + st1 {v26.s}[0], [x1], x3 //store row 4 + st1 {v27.s}[0], [x1], x3 // store row 5 + uaddl v14.8h, v0.8b, v1.8b // temp1 = src[2_0] + src[3_0] + uaddl v16.8h, v2.8b, v7.8b // temp = src[0_0] + src[5_0] + uaddl v18.8h, v3.8b, v6.8b // temp2 = src[1_0] + src[4_0] + mla v18.8h, v14.8h , v22.8h // temp += temp1 * 20 + ld1 {v4.s}[0], [x0], x2 + mls v18.8h, v16.8h , v24.8h // temp -= temp2 * 5 + uaddl v8.8h, v2.8b, v1.8b + uaddl v10.8h, v3.8b, v0.8b + uaddl v12.8h, v4.8b, v7.8b + sqrshrun v26.8b, v18.8h, #5 + ld1 {v18.s}[0], [x7], x2 //Load value for interpolation - row 6 + ld1 {v19.s}[0], [x7], x2 //Load value for interpolation - row 7 + mla v12.8h, v8.8h , v22.8h + ld1 {v5.s}[0], [x0], x2 + mls v12.8h, v10.8h , v24.8h + sqrshrun v27.8b, v12.8h, #5 + urhadd v26.16b, v18.16b , v26.16b //Interpolation step for qpel calculation + urhadd v27.16b, v19.16b , v27.16b //Interpolation step for qpel calculation + + st1 {v26.s}[0], [x1], x3 // store row 6 + st1 {v27.s}[0], [x1], x3 // store row 7 + + +end_func: + // LDMFD sp!,{x4-x12,PC} //Restoring registers from stack + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + + diff --git a/dependencies/ih264d/common/armv8/ih264_intra_pred_chroma_av8.s b/dependencies/ih264d/common/armv8/ih264_intra_pred_chroma_av8.s new file mode 100644 index 00000000..39c02560 --- /dev/null +++ b/dependencies/ih264d/common/armv8/ih264_intra_pred_chroma_av8.s @@ -0,0 +1,574 @@ +//****************************************************************************** +//* +//* Copyright (C) 2015 The Android Open Source Project +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at: +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. +//* +//***************************************************************************** +//* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +//*/ +///** +//****************************************************************************** +//* @file +//* ih264_intra_pred_chroma.s +//* +//* @brief +//* Contains function definitions for intra chroma prediction . +//* +//* @author +//* Ittiam +//* +//* @par List of Functions: +//* +//* - ih264_intra_pred_luma_chroma_mode_vert_av8() +//* - ih264_intra_pred_luma_chroma_mode_horz_av8() +//* - ih264_intra_pred_luma_chroma_mode_dc_av8() +//* - ih264_intra_pred_luma_chroma_mode_plane_av8() +//* +//* @remarks +//* None +//* +//******************************************************************************* +//*/ + +///* All the functions here are replicated from ih264_chroma_intra_pred_filters.c +// + +///** +///** +///** +// + + +.text +.p2align 2 +.include "ih264_neon_macros.s" + +.extern ih264_gai1_intrapred_chroma_plane_coeffs1 +.extern ih264_gai1_intrapred_chroma_plane_coeffs2 + + + +///** +//******************************************************************************* +//* +//*ih264_intra_pred_chroma_8x8_mode_dc +//* +//* @brief +//* Perform Intra prediction for chroma_8x8 mode:DC +//* +//* @par Description: +//* Perform Intra prediction for chroma_8x8 mode:DC ,described in sec 8.3.4.1 +//* +//* @param[in] pu1_src +//* UWORD8 pointer to the source containing alternate U and V samples +//* +//* @param[out] pu1_dst +//* UWORD8 pointer to the destination with alternate U and V samples +//* +//* @param[in] src_strd +//* integer source stride +//* +//* @param[in] dst_strd +//* integer destination stride +//* +//** @param[in] ui_neighboravailability +//* availability of neighbouring pixels +//* +//* @returns +//* +//* @remarks +//* None +//* +//*******************************************************************************/ +//void ih264_intra_pred_chroma_8x8_mode_dc(UWORD8 *pu1_src, +// UWORD8 *pu1_dst, +// WORD32 src_strd, +// WORD32 dst_strd, +// WORD32 ui_neighboravailability) + +//**************Variables Vs Registers***************************************** +// x0 => *pu1_src +// x1 => *pu1_dst +// w2 => src_strd +// w3 => dst_strd +// w4 => ui_neighboravailability + + + + .global ih264_intra_pred_chroma_8x8_mode_dc_av8 + +ih264_intra_pred_chroma_8x8_mode_dc_av8: + + + push_v_regs + stp x19, x20, [sp, #-16]! + sxtw x3, w3 + + mov w19, #5 + ands w6, w4, w19 + beq none_available + cmp w6, #1 + beq left_only_available + cmp w6, #4 + beq top_only_available + +all_available: + ld1 {v0.8b, v1.8b}, [x0] + add x6, x0, #18 + ld1 {v2.8b, v3.8b}, [x6] + uxtl v0.8h, v0.8b + uxtl v1.8h, v1.8b + addp v0.4s, v0.4s , v0.4s + addp v1.4s, v1.4s , v1.4s + addp v0.4s, v0.4s , v0.4s + addp v1.4s, v1.4s , v1.4s + uxtl v2.8h, v2.8b + uxtl v3.8h, v3.8b + addp v2.4s, v2.4s , v2.4s + addp v3.4s, v3.4s , v3.4s + addp v2.4s, v2.4s , v2.4s + addp v3.4s, v3.4s , v3.4s + rshrn v5.8b, v0.8h, #2 + dup v21.8h, v5.h[0] + rshrn v6.8b, v3.8h, #2 + dup v20.8h, v6.h[0] + add v1.8h, v1.8h, v2.8h + rshrn v1.8b, v1.8h, #3 + dup v23.8h, v1.h[0] + mov v20.d[0], v23.d[0] + add v0.8h, v0.8h, v3.8h + rshrn v0.8b, v0.8h, #3 + dup v23.8h, v0.h[0] + mov v21.d[1], v23.d[0] + b store +left_only_available: + ld1 {v0.8b, v1.8b}, [x0] + uxtl v0.8h, v0.8b + uxtl v1.8h, v1.8b + addp v0.4s, v0.4s , v0.4s + addp v1.4s, v1.4s , v1.4s + addp v0.4s, v0.4s , v0.4s + addp v1.4s, v1.4s , v1.4s + rshrn v0.8b, v0.8h, #2 + rshrn v1.8b, v1.8h, #2 + dup v20.8h , v1.h[0] + dup v21.8h, v0.h[0] + b store + +top_only_available: + add x6, x0, #18 + ld1 {v0.8b, v1.8b}, [x6] + uxtl v0.8h, v0.8b + uxtl v1.8h, v1.8b + addp v0.4s, v0.4s , v0.4s + addp v1.4s, v1.4s , v1.4s + addp v0.4s, v0.4s , v0.4s + addp v1.4s, v1.4s , v1.4s + rshrn v0.8b, v0.8h, #2 + rshrn v1.8b, v1.8h, #2 + dup v20.8h , v0.h[0] + dup v21.8h, v1.h[0] + mov v20.d[1], v21.d[1] + mov v21.d[0], v20.d[0] + b store +none_available: + mov w15, #128 + dup v20.16b, w15 + dup v21.16b, w15 + + +store: + + st1 { v20.16b}, [x1], x3 + st1 { v20.16b}, [x1], x3 + st1 { v20.16b}, [x1], x3 + st1 { v20.16b}, [x1], x3 + st1 { v21.16b}, [x1], x3 + st1 { v21.16b}, [x1], x3 + st1 { v21.16b}, [x1], x3 + st1 { v21.16b}, [x1], x3 +end_func: + + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + + + + +///****************************************************************************** + + +///** +//******************************************************************************* +//* +//*ih264_intra_pred_chroma_8x8_mode_horz +//* +//* @brief +//* Perform Intra prediction for chroma_8x8 mode:Horizontal +//* +//* @par Description: +//* Perform Intra prediction for chroma_8x8 mode:Horizontal ,described in sec 8.3.4.2 +//* +//* @param[in] pu1_src +//* UWORD8 pointer to the source containing alternate U and V samples +//* +//* @param[out] pu1_dst +//* UWORD8 pointer to the destination with alternate U and V samples +//* +//* @param[in] src_strd +//* integer source stride +//* +//* @param[in] dst_strd +//* integer destination stride +//* +//* @param[in] ui_neighboravailability +//* availability of neighbouring pixels(Not used in this function) +//* +//* @returns +//* +//* @remarks +//* None +//* +//******************************************************************************* +//*/ +//void ih264_intra_pred_chroma_8x8_mode_horz(UWORD8 *pu1_src, +// UWORD8 *pu1_dst, +// WORD32 src_strd, +// WORD32 dst_strd, +// WORD32 ui_neighboravailability) +//**************Variables Vs Registers***************************************** +// x0 => *pu1_src +// x1 => *pu1_dst +// w2 => src_strd +// w3 => dst_strd +// w4 => ui_neighboravailability + + + .global ih264_intra_pred_chroma_8x8_mode_horz_av8 + +ih264_intra_pred_chroma_8x8_mode_horz_av8: + + + + push_v_regs + sxtw x3, w3 + ld1 {v0.8h}, [x0] + + dup v10.8h, v0.h[7] + dup v11.8h, v0.h[6] + dup v12.8h, v0.h[5] + dup v13.8h, v0.h[4] + st1 {v10.8h}, [x1], x3 + dup v14.8h, v0.h[3] + st1 {v11.8h}, [x1], x3 + dup v15.8h, v0.h[2] + st1 {v12.8h}, [x1], x3 + dup v16.8h, v0.h[1] + st1 {v13.8h}, [x1], x3 + dup v17.8h, v0.h[0] + st1 {v14.8h}, [x1], x3 + st1 {v15.8h}, [x1], x3 + st1 {v16.8h}, [x1], x3 + st1 {v17.8h}, [x1], x3 + + + pop_v_regs + ret + + + + + + +///** +//******************************************************************************* +//* +//*ih264_intra_pred_chroma_8x8_mode_vert +//* +//* @brief +//* Perform Intra prediction for chroma_8x8 mode:vertical +//* +//* @par Description: +//*Perform Intra prediction for chroma_8x8 mode:vertical ,described in sec 8.3.4.3 +//* +//* @param[in] pu1_src +//* UWORD8 pointer to the source containing alternate U and V samples +//* +//* @param[out] pu1_dst +//* UWORD8 pointer to the destination with alternate U and V samples +//* +//* @param[in] src_strd +//* integer source stride +//* +//* @param[in] dst_strd +//* integer destination stride +//* +//* @param[in] ui_neighboravailability +//* availability of neighbouring pixels(Not used in this function) +//* +//* @returns +//* +//* @remarks +//* None +//* +//******************************************************************************* +//void ih264_intra_pred_chroma_8x8_mode_vert(UWORD8 *pu1_src, +// UWORD8 *pu1_dst, +// WORD32 src_strd, +// WORD32 dst_strd, +// WORD32 ui_neighboravailability) + +//**************Variables Vs Registers***************************************** +// x0 => *pu1_src +// x1 => *pu1_dst +// w2 => src_strd +// w3 => dst_strd +// w4 => ui_neighboravailability + + + .global ih264_intra_pred_chroma_8x8_mode_vert_av8 + +ih264_intra_pred_chroma_8x8_mode_vert_av8: + + push_v_regs + sxtw x3, w3 + + add x0, x0, #18 + ld1 {v0.8b, v1.8b}, [x0] + + st1 {v0.8b, v1.8b}, [x1], x3 + st1 {v0.8b, v1.8b}, [x1], x3 + st1 {v0.8b, v1.8b}, [x1], x3 + st1 {v0.8b, v1.8b}, [x1], x3 + st1 {v0.8b, v1.8b}, [x1], x3 + st1 {v0.8b, v1.8b}, [x1], x3 + st1 {v0.8b, v1.8b}, [x1], x3 + st1 {v0.8b, v1.8b}, [x1], x3 + + pop_v_regs + ret + + + + +///****************************************************************************** + + +///** +//******************************************************************************* +//* +//*ih264_intra_pred_chroma_8x8_mode_plane +//* +//* @brief +//* Perform Intra prediction for chroma_8x8 mode:PLANE +//* +//* @par Description: +//* Perform Intra prediction for chroma_8x8 mode:PLANE ,described in sec 8.3.4.4 +//* +//* @param[in] pu1_src +//* UWORD8 pointer to the source containing alternate U and V samples +//* +//* @param[out] pu1_dst +//* UWORD8 pointer to the destination with alternate U and V samples +//* +//* @param[in] src_strd +//* integer source stride +//* +//* @param[in] dst_strd +//* integer destination stride +//* +//* @param[in] ui_neighboravailability +//* availability of neighbouring pixels +//* +//* @returns +//* +//* @remarks +//* None +//* +//*******************************************************************************/ +//void ih264_intra_pred_chroma_8x8_mode_plane(UWORD8 *pu1_src, +// UWORD8 *pu1_dst, +// WORD32 src_strd, +// WORD32 dst_strd, +// WORD32 ui_neighboravailability) + +//**************Variables Vs Registers***************************************** +// x0 => *pu1_src +// x1 => *pu1_dst +// w2 => src_strd +// w3 => dst_strd +// w4 => ui_neighboravailability + + .global ih264_intra_pred_chroma_8x8_mode_plane_av8 +ih264_intra_pred_chroma_8x8_mode_plane_av8: + + push_v_regs + stp x19, x20, [sp, #-16]! + sxtw x3, w3 + + ld1 {v0.2s}, [x0] + add x10, x0, #10 + ld1 {v1.2s}, [x10] + add x10, x10, #6 + rev64 v5.4h, v0.4h + ld1 {v2.2s}, [x10], #8 + add x10, x10, #2 + rev64 v7.4h, v2.4h + ld1 {v3.2s}, [x10] + sub x5, x3, #8 + adrp x12, :got:ih264_gai1_intrapred_chroma_plane_coeffs1 + ldr x12, [x12, #:got_lo12:ih264_gai1_intrapred_chroma_plane_coeffs1] + usubl v10.8h, v5.8b, v1.8b + ld1 {v8.8b, v9.8b}, [x12] // Load multiplication factors 1 to 8 into D3 + mov v8.d[1], v9.d[0] + usubl v12.8h, v3.8b, v7.8b + mul v14.8h, v10.8h , v8.8h + mul v16.8h, v12.8h , v8.8h + uzp1 v15.8h, v14.8h, v16.8h + uzp2 v16.8h, v14.8h, v16.8h + mov v14.16b, v15.16b + mov v15.d[0], v14.d[1] + mov v17.d[0], v16.d[1] + addp v14.4h, v14.4h, v14.4h + addp v15.4h, v15.4h, v15.4h + addp v16.4h, v16.4h, v16.4h + addp v17.4h, v17.4h, v17.4h + addp v14.4h, v14.4h, v14.4h + addp v15.4h, v15.4h, v15.4h + addp v16.4h, v16.4h, v16.4h + addp v17.4h, v17.4h, v17.4h + mov x6, #34 + dup v18.8h, w6 + smull v22.4s, v14.4h, v18.4h + smull v24.4s, v15.4h, v18.4h + smull v26.4s, v16.4h, v18.4h + smull v28.4s, v17.4h, v18.4h + rshrn v10.4h, v22.4s, #6 + rshrn v12.4h, v24.4s, #6 + rshrn v13.4h, v26.4s, #6 + rshrn v14.4h, v28.4s, #6 + ldrb w6, [x0], #1 + add x10, x0, #31 + ldrb w8, [x0], #1 + ldrb w7, [x10], #1 + ldrb w9, [x10], #1 + add w6, w6, w7 + add w8, w8, w9 + lsl w6, w6, #4 + lsl w8, w8, #4 + dup v0.8h, w6 + dup v2.8h, w8 + dup v4.8h, v12.h[0] + dup v6.8h, v10.h[0] + dup v24.8h, v14.h[0] + dup v26.8h, v13.h[0] + zip1 v5.8h, v4.8h, v24.8h + zip2 v24.8h, v4.8h, v24.8h + mov v4.16b, v5.16b + zip1 v7.8h, v6.8h, v26.8h + zip2 v26.8h, v6.8h, v26.8h + mov v6.16b, v7.16b + zip1 v1.8h, v0.8h, v2.8h + zip2 v2.8h, v0.8h, v2.8h + mov v0.16b, v1.16b + + adrp x12, :got:ih264_gai1_intrapred_chroma_plane_coeffs2 + ldr x12, [x12, #:got_lo12:ih264_gai1_intrapred_chroma_plane_coeffs2] + + ld1 {v8.2s, v9.2s}, [x12] + mov v8.d[1], v9.d[0] + mov v10.16b, v8.16b + mov v22.16b, v8.16b + zip1 v9.8h, v8.8h, v10.8h + zip2 v10.8h, v8.8h, v10.8h + mov v8.16b, v9.16b + mul v12.8h, v4.8h , v8.8h + mul v16.8h, v4.8h , v10.8h + add v12.8h, v0.8h , v12.8h + add v16.8h, v0.8h , v16.8h + dup v20.8h, v22.h[0] + mul v4.8h, v6.8h , v20.8h + dup v30.8h, v22.h[1] + mul v18.8h, v6.8h , v20.8h + mul v14.8h, v6.8h , v30.8h + mul v8.8h, v6.8h , v30.8h + add v24.8h, v12.8h , v4.8h + add v0.8h, v16.8h , v18.8h + add v2.8h, v12.8h , v14.8h + sqrshrun v28.8b, v24.8h, #5 + add v26.8h, v16.8h , v8.8h + sqrshrun v29.8b, v0.8h, #5 + dup v20.8h, v22.h[2] + st1 {v28.8b, v29.8b}, [x1], x3 + sqrshrun v28.8b, v2.8h, #5 + sqrshrun v29.8b, v26.8h, #5 + mul v4.8h, v6.8h , v20.8h + mul v18.8h, v6.8h , v20.8h + st1 {v28.8b, v29.8b}, [x1], x3 + add v24.8h, v12.8h , v4.8h + add v0.8h, v16.8h , v18.8h + dup v30.8h, v22.h[3] + sqrshrun v28.8b, v24.8h, #5 + sqrshrun v29.8b, v0.8h, #5 + mul v14.8h, v6.8h , v30.8h + mul v8.8h, v6.8h , v30.8h + st1 {v28.8b, v29.8b}, [x1], x3 + add v2.8h, v12.8h , v14.8h + add v26.8h, v16.8h , v8.8h + dup v20.8h, v22.h[4] + sqrshrun v28.8b, v2.8h, #5 + sqrshrun v29.8b, v26.8h, #5 + mul v4.8h, v6.8h , v20.8h + mul v18.8h, v6.8h , v20.8h + st1 {v28.8b, v29.8b}, [x1], x3 + add v24.8h, v12.8h , v4.8h + add v0.8h, v16.8h , v18.8h + dup v30.8h, v22.h[5] + sqrshrun v28.8b, v24.8h, #5 + sqrshrun v29.8b, v0.8h, #5 + mul v14.8h, v6.8h , v30.8h + mul v8.8h, v6.8h , v30.8h + st1 {v28.8b, v29.8b}, [x1], x3 + add v2.8h, v12.8h , v14.8h + add v26.8h, v16.8h , v8.8h + dup v20.8h, v22.h[6] + sqrshrun v28.8b, v2.8h, #5 + sqrshrun v29.8b, v26.8h, #5 + mul v4.8h, v6.8h , v20.8h + mul v18.8h, v6.8h , v20.8h + st1 {v28.8b, v29.8b}, [x1], x3 + add v24.8h, v12.8h , v4.8h + add v0.8h, v16.8h , v18.8h + dup v30.8h, v22.h[7] + sqrshrun v28.8b, v24.8h, #5 + sqrshrun v29.8b, v0.8h, #5 + mul v14.8h, v6.8h , v30.8h + mul v8.8h, v6.8h , v30.8h + st1 {v28.8b, v29.8b}, [x1], x3 + add v2.8h, v12.8h , v14.8h + add v26.8h, v16.8h , v8.8h + sqrshrun v28.8b, v2.8h, #5 + sqrshrun v29.8b, v26.8h, #5 + st1 {v28.8b, v29.8b}, [x1], x3 + +end_func_plane: + + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + + diff --git a/dependencies/ih264d/common/armv8/ih264_intra_pred_luma_16x16_av8.s b/dependencies/ih264d/common/armv8/ih264_intra_pred_luma_16x16_av8.s new file mode 100644 index 00000000..fa19c121 --- /dev/null +++ b/dependencies/ih264d/common/armv8/ih264_intra_pred_luma_16x16_av8.s @@ -0,0 +1,592 @@ +//****************************************************************************** +//* +//* Copyright (C) 2015 The Android Open Source Project +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at: +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. +//* +//***************************************************************************** +//* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +//*/ +///** +//****************************************************************************** +//* @file +//* ih264_intra_pred_luma_16x16_av8.s +//* +//* @brief +//* Contains function definitions for intra 16x16 Luma prediction . +//* +//* @author +//* Ittiam +//* +//* @par List of Functions: +//* +//* - ih264_intra_pred_luma_16x16_mode_vert_av8() +//* - ih264_intra_pred_luma_16x16_mode_horz_av8() +//* - ih264_intra_pred_luma_16x16_mode_dc_av8() +//* - ih264_intra_pred_luma_16x16_mode_plane_av8() +//* +//* @remarks +//* None +//* +//******************************************************************************* +//*/ + +///* All the functions here are replicated from ih264_intra_pred_filters.c +// + +///** +///** +///** +// + + +.text +.p2align 2 +.include "ih264_neon_macros.s" +.extern ih264_gai1_intrapred_luma_plane_coeffs + + + +///** +//******************************************************************************* +//* +//*ih264_intra_pred_luma_16x16_mode_vert +//* +//* @brief +//* Perform Intra prediction for luma_16x16 mode:vertical +//* +//* @par Description: +//* Perform Intra prediction for luma_16x16 mode:Vertical ,described in sec 8.3.3.1 +//* +//* @param[in] pu1_src +//* UWORD8 pointer to the source +//* +//* @param[out] pu1_dst +//* UWORD8 pointer to the destination +//* +//* @param[in] src_strd +//* integer source stride +//* +//* @param[in] dst_strd +//* integer destination stride +//* +//* @param[in] ui_neighboravailability +//* availability of neighbouring pixels(Not used in this function) +//* +//* @returns +//* +//* @remarks +//* None +//* +//******************************************************************************* +//void ih264_intra_pred_luma_16x16_mode_vert(UWORD8 *pu1_src, +// UWORD8 *pu1_dst, +// WORD32 src_strd, +// WORD32 dst_strd, +// WORD32 ui_neighboravailability) + +//**************Variables Vs Registers***************************************** +// x0 => *pu1_src +// x1 => *pu1_dst +// w2 => src_strd +// w3 => dst_strd +// w4 => ui_neighboravailability + + + .global ih264_intra_pred_luma_16x16_mode_vert_av8 + +ih264_intra_pred_luma_16x16_mode_vert_av8: + + push_v_regs + sxtw x3, w3 + + + add x0, x0, #17 + ld1 {v0.8b, v1.8b}, [x0] + + st1 {v0.8b, v1.8b}, [x1], x3 + st1 {v0.8b, v1.8b}, [x1], x3 + st1 {v0.8b, v1.8b}, [x1], x3 + st1 {v0.8b, v1.8b}, [x1], x3 + st1 {v0.8b, v1.8b}, [x1], x3 + st1 {v0.8b, v1.8b}, [x1], x3 + st1 {v0.8b, v1.8b}, [x1], x3 + st1 {v0.8b, v1.8b}, [x1], x3 + st1 {v0.8b, v1.8b}, [x1], x3 + st1 {v0.8b, v1.8b}, [x1], x3 + st1 {v0.8b, v1.8b}, [x1], x3 + st1 {v0.8b, v1.8b}, [x1], x3 + st1 {v0.8b, v1.8b}, [x1], x3 + st1 {v0.8b, v1.8b}, [x1], x3 + st1 {v0.8b, v1.8b}, [x1], x3 + st1 {v0.8b, v1.8b}, [x1], x3 + + pop_v_regs + ret + + + + + +///****************************************************************************** + + +///** +//******************************************************************************* +//* +//*ih264_intra_pred_luma_16x16_mode_horz +//* +//* @brief +//* Perform Intra prediction for luma_16x16 mode:horizontal +//* +//* @par Description: +//* Perform Intra prediction for luma_16x16 mode:horizontal ,described in sec 8.3.3.2 +//* +//* @param[in] pu1_src +//* UWORD8 pointer to the source +//* +//* @param[out] pu1_dst +//* UWORD8 pointer to the destination +//* +//* @param[in] src_strd +//* integer source stride +//* +//* @param[in] dst_strd +//* integer destination stride +//* +//* @param[in] ui_neighboravailability +//* availability of neighbouring pixels(Not used in this function) +//* +//* @returns +//* +//* @remarks +//* None +//* +//******************************************************************************* +//*/ +//void ih264_intra_pred_luma_16x16_mode_horz(UWORD8 *pu1_src, +// UWORD8 *pu1_dst, +// WORD32 src_strd, +// WORD32 dst_strd, +// WORD32 ui_neighboravailability) +//**************Variables Vs Registers***************************************** +// x0 => *pu1_src +// x1 => *pu1_dst +// w2 => src_strd +// w3 => dst_strd +// w4 => ui_neighboravailability + + .global ih264_intra_pred_luma_16x16_mode_horz_av8 + +ih264_intra_pred_luma_16x16_mode_horz_av8: + + + + push_v_regs + sxtw x3, w3 + + ld1 {v0.16b}, [x0] + + + + dup v10.16b, v0.b[15] + dup v11.16b, v0.b[14] + dup v12.16b, v0.b[13] + dup v13.16b, v0.b[12] + st1 {v10.16b}, [x1], x3 + dup v14.16b, v0.b[11] + st1 {v11.16b}, [x1], x3 + dup v15.16b, v0.b[10] + st1 {v12.16b}, [x1], x3 + dup v16.16b, v0.b[9] + st1 {v13.16b}, [x1], x3 + dup v17.16b, v0.b[8] + st1 {v14.16b}, [x1], x3 + dup v18.16b, v0.b[7] + st1 {v15.16b}, [x1], x3 + dup v19.16b, v0.b[6] + st1 {v16.16b}, [x1], x3 + dup v20.16b, v0.b[5] + st1 {v17.16b}, [x1], x3 + dup v21.16b, v0.b[4] + st1 {v18.16b}, [x1], x3 + dup v22.16b, v0.b[3] + st1 {v19.16b}, [x1], x3 + dup v23.16b, v0.b[2] + st1 {v20.16b}, [x1], x3 + dup v24.16b, v0.b[1] + st1 {v21.16b}, [x1], x3 + dup v25.16b, v0.b[0] + st1 {v22.16b}, [x1], x3 + st1 {v23.16b}, [x1], x3 + st1 {v24.16b}, [x1], x3 + st1 {v25.16b}, [x1], x3 + + pop_v_regs + ret + + + + + + + +///****************************************************************************** + + +///** +//******************************************************************************* +//* +//*ih264_intra_pred_luma_16x16_mode_dc +//* +//* @brief +//* Perform Intra prediction for luma_16x16 mode:DC +//* +//* @par Description: +//* Perform Intra prediction for luma_16x16 mode:DC ,described in sec 8.3.3.3 +//* +//* @param[in] pu1_src +//* UWORD8 pointer to the source +//* +//* @param[out] pu1_dst +//* UWORD8 pointer to the destination +//* +//* @param[in] src_strd +//* integer source stride +//* +//* @param[in] dst_strd +//* integer destination stride +//* +//* @param[in] ui_neighboravailability +//* availability of neighbouring pixels +//* +//* @returns +//* +//* @remarks +//* None +//* +//*******************************************************************************/ +//void ih264_intra_pred_luma_16x16_mode_dc(UWORD8 *pu1_src, +// UWORD8 *pu1_dst, +// WORD32 src_strd, +// WORD32 dst_strd, +// WORD32 ui_neighboravailability) + +//**************Variables Vs Registers***************************************** +// x0 => *pu1_src +// x1 => *pu1_dst +// w2 => src_strd +// w3 => dst_strd +// w4 => ui_neighboravailability + + .global ih264_intra_pred_luma_16x16_mode_dc_av8 + +ih264_intra_pred_luma_16x16_mode_dc_av8: + + + + push_v_regs + stp x19, x20, [sp, #-16]! + sxtw x3, w3 + + sub v0.16b, v0.16b, v0.16b + sub v1.16b, v1.16b, v1.16b + mov w10, #0 + mov w11 , #3 + ands w6, w4, #0x01 + beq top_available //LEFT NOT AVAILABLE + ld1 {v0.16b}, [x0] + add w10, w10, #8 + add w11, w11, #1 +top_available: + ands w6, w4, #0x04 + beq none_available + add x6, x0, #17 + ld1 {v1.16b}, [x6] + add w10, w10, #8 + add w11, w11, #1 + b summation +none_available: + cmp w4, #0 + bne summation + mov w15, #128 + dup v20.16b, w15 + b store +summation: + uaddl v2.8h, v0.8b, v1.8b + uaddl2 v3.8h, v0.16b, v1.16b + dup v10.8h, w10 + neg w11, w11 + dup v20.8h, w11 + add v0.8h, v2.8h, v3.8h + mov v1.d[0], v0.d[1] + add v0.4h, v0.4h, v1.4h + addp v0.4h, v0.4h , v0.4h + addp v0.4h, v0.4h , v0.4h + add v0.4h, v0.4h, v10.4h + uqshl v0.8h, v0.8h, v20.8h + sqxtun v0.8b, v0.8h + dup v20.16b, v0.b[0] + +store: + + st1 { v20.16b}, [x1], x3 + st1 { v20.16b}, [x1], x3 + st1 { v20.16b}, [x1], x3 + st1 { v20.16b}, [x1], x3 + st1 { v20.16b}, [x1], x3 + st1 { v20.16b}, [x1], x3 + st1 { v20.16b}, [x1], x3 + st1 { v20.16b}, [x1], x3 + st1 { v20.16b}, [x1], x3 + st1 { v20.16b}, [x1], x3 + st1 { v20.16b}, [x1], x3 + st1 { v20.16b}, [x1], x3 + st1 { v20.16b}, [x1], x3 + st1 { v20.16b}, [x1], x3 + st1 { v20.16b}, [x1], x3 + st1 { v20.16b}, [x1], x3 + + + +end_func: + + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + + + + +///****************************************************************************** + + +///** +//******************************************************************************* +//* +//*ih264_intra_pred_luma_16x16_mode_plane +//* +//* @brief +//* Perform Intra prediction for luma_16x16 mode:PLANE +//* +//* @par Description: +//* Perform Intra prediction for luma_16x16 mode:PLANE ,described in sec 8.3.3.4 +//* +//* @param[in] pu1_src +//* UWORD8 pointer to the source +//* +//* @param[out] pu1_dst +//* UWORD8 pointer to the destination +//* +//* @param[in] src_strd +//* integer source stride +//* +//* @param[in] dst_strd +//* integer destination stride +//* +//* @param[in] ui_neighboravailability +//* availability of neighbouring pixels +//* +//* @returns +//* +//* @remarks +//* None +//* +//*******************************************************************************/ +//void ih264_intra_pred_luma_16x16_mode_plane(UWORD8 *pu1_src, +// UWORD8 *pu1_dst, +// WORD32 src_strd, +// WORD32 dst_strd, +// WORD32 ui_neighboravailability) + +//**************Variables Vs Registers***************************************** +// x0 => *pu1_src +// x1 => *pu1_dst +// w2 => src_strd +// w3 => dst_strd +// w4 => ui_neighboravailability + + .global ih264_intra_pred_luma_16x16_mode_plane_av8 +ih264_intra_pred_luma_16x16_mode_plane_av8: + + push_v_regs + stp x19, x20, [sp, #-16]! + sxtw x3, w3 + mov x2, x1 + add x1, x0, #17 + add x0, x0, #15 + mov x8, #9 + sub x1, x1, #1 + mov x10, x1 //top_left + mov x4, #-1 + ld1 {v2.2s}, [x1], x8 + + adrp x7, :got:ih264_gai1_intrapred_luma_plane_coeffs + ldr x7, [x7, #:got_lo12:ih264_gai1_intrapred_luma_plane_coeffs] + + ld1 {v0.2s}, [x1] + rev64 v2.8b, v2.8b + ld1 {v6.2s, v7.2s}, [x7] + usubl v0.8h, v0.8b, v2.8b + uxtl v16.8h, v6.8b + mul v0.8h, v0.8h , v16.8h + uxtl v18.8h, v7.8b + add x7, x0, x4, lsl #3 + sub x0, x7, x4, lsl #1 + neg x14, x4 + addp v0.8h, v0.8h, v1.8h + ldrb w8, [x7], #-1 + ldrb w9, [x0], #1 + saddlp v0.2s, v0.4h + sub w12, w8, w9 + ldrb w8, [x7], #-1 + saddlp v0.1d, v0.2s + ldrb w9, [x0], #1 + sub w8, w8, w9 + shl v2.2s, v0.2s, #2 + add w12, w12, w8, lsl #1 + add v0.2s, v0.2s , v2.2s + ldrb w8, [x7], #-1 + ldrb w9, [x0], #1 + srshr v0.2s, v0.2s, #6 // i_b = D0[0] + sub w8, w8, w9 + ldrb w5, [x7], #-1 + add w8, w8, w8, lsl #1 + dup v4.8h, v0.h[0] + add w12, w12, w8 + ldrb w9, [x0], #1 + mul v0.8h, v4.8h , v16.8h + sub w5, w5, w9 + mul v2.8h, v4.8h , v18.8h + add w12, w12, w5, lsl #2 + ldrb w8, [x7], #-1 + ldrb w9, [x0], #1 + sub w8, w8, w9 + ldrb w5, [x7], #-1 + add w8, w8, w8, lsl #2 + ldrb w6, [x0], #1 + add w12, w12, w8 + ldrb w8, [x7], #-1 + ldrb w9, [x0], #1 + sub w5, w5, w6 + sub w8, w8, w9 + add w5, w5, w5, lsl #1 + sub w20, w8, w8, lsl #3 + neg w8, w20 + add w12, w12, w5, lsl #1 + ldrb w5, [x7], #-1 + ldrb w6, [x10] //top_left + add w12, w12, w8 + sub w9, w5, w6 + ldrb w6, [x1, #7] + add w12, w12, w9, lsl #3 // i_c = w12 + add w8, w5, w6 + add w12, w12, w12, lsl #2 + lsl w8, w8, #4 // i_a = w8 + add w12, w12, #0x20 + lsr w12, w12, #6 + shl v28.8h, v4.8h, #3 + dup v6.8h, w12 + dup v30.8h, w8 + shl v26.8h, v6.8h, #3 + sub v30.8h, v30.8h , v28.8h + sub v30.8h, v30.8h , v26.8h + add v28.8h, v30.8h , v6.8h + add v26.8h, v28.8h , v0.8h + add v28.8h, v28.8h , v2.8h + sqrshrun v20.8b, v26.8h, #5 + sqrshrun v21.8b, v28.8h, #5 + add v26.8h, v26.8h , v6.8h + add v28.8h, v28.8h , v6.8h + sqrshrun v22.8b, v26.8h, #5 + st1 {v20.2s, v21.2s}, [x2], x3 + sqrshrun v23.8b, v28.8h, #5 + add v26.8h, v26.8h , v6.8h + add v28.8h, v28.8h , v6.8h + sqrshrun v20.8b, v26.8h, #5 + st1 {v22.2s, v23.2s}, [x2], x3 + sqrshrun v21.8b, v28.8h, #5 + add v26.8h, v26.8h , v6.8h + add v28.8h, v28.8h , v6.8h + sqrshrun v22.8b, v26.8h, #5 + st1 {v20.2s, v21.2s}, [x2], x3 + sqrshrun v23.8b, v28.8h, #5 + add v26.8h, v26.8h , v6.8h + add v28.8h, v28.8h , v6.8h + sqrshrun v20.8b, v26.8h, #5 + st1 {v22.2s, v23.2s}, [x2], x3 + sqrshrun v21.8b, v28.8h, #5 + add v26.8h, v26.8h , v6.8h + add v28.8h, v28.8h , v6.8h + sqrshrun v22.8b, v26.8h, #5 + st1 {v20.2s, v21.2s}, [x2], x3 + sqrshrun v23.8b, v28.8h, #5 + add v26.8h, v26.8h , v6.8h + add v28.8h, v28.8h , v6.8h + sqrshrun v20.8b, v26.8h, #5 + st1 {v22.2s, v23.2s}, [x2], x3 + sqrshrun v21.8b, v28.8h, #5 + add v26.8h, v26.8h , v6.8h + add v28.8h, v28.8h , v6.8h + sqrshrun v22.8b, v26.8h, #5 + st1 {v20.2s, v21.2s}, [x2], x3 + sqrshrun v23.8b, v28.8h, #5 + add v26.8h, v26.8h , v6.8h + add v28.8h, v28.8h , v6.8h + sqrshrun v20.8b, v26.8h, #5 + st1 {v22.2s, v23.2s}, [x2], x3 + sqrshrun v21.8b, v28.8h, #5 + add v26.8h, v26.8h , v6.8h + add v28.8h, v28.8h , v6.8h + sqrshrun v22.8b, v26.8h, #5 + st1 {v20.2s, v21.2s}, [x2], x3 + sqrshrun v23.8b, v28.8h, #5 + add v26.8h, v26.8h , v6.8h + add v28.8h, v28.8h , v6.8h + sqrshrun v20.8b, v26.8h, #5 + st1 {v22.2s, v23.2s}, [x2], x3 + sqrshrun v21.8b, v28.8h, #5 + add v26.8h, v26.8h , v6.8h + add v28.8h, v28.8h , v6.8h + sqrshrun v22.8b, v26.8h, #5 + st1 {v20.2s, v21.2s}, [x2], x3 + sqrshrun v23.8b, v28.8h, #5 + add v26.8h, v26.8h , v6.8h + add v28.8h, v28.8h , v6.8h + sqrshrun v20.8b, v26.8h, #5 + st1 {v22.2s, v23.2s}, [x2], x3 + sqrshrun v21.8b, v28.8h, #5 + add v26.8h, v26.8h , v6.8h + add v28.8h, v28.8h , v6.8h + sqrshrun v22.8b, v26.8h, #5 + st1 {v20.2s, v21.2s}, [x2], x3 + sqrshrun v23.8b, v28.8h, #5 + add v26.8h, v26.8h , v6.8h + add v28.8h, v28.8h , v6.8h + sqrshrun v20.8b, v26.8h, #5 + st1 {v22.2s, v23.2s}, [x2], x3 + sqrshrun v21.8b, v28.8h, #5 + add v26.8h, v26.8h , v6.8h + add v28.8h, v28.8h , v6.8h + sqrshrun v22.8b, v26.8h, #5 + st1 {v20.2s, v21.2s}, [x2], x3 + sqrshrun v23.8b, v28.8h, #5 + st1 {v22.2s, v23.2s}, [x2], x3 + +end_func_plane: + + ldp x19, x20, [sp], #16 + pop_v_regs + ret + diff --git a/dependencies/ih264d/common/armv8/ih264_intra_pred_luma_4x4_av8.s b/dependencies/ih264d/common/armv8/ih264_intra_pred_luma_4x4_av8.s new file mode 100644 index 00000000..1f951317 --- /dev/null +++ b/dependencies/ih264d/common/armv8/ih264_intra_pred_luma_4x4_av8.s @@ -0,0 +1,872 @@ +//****************************************************************************** +//* +//* Copyright (C) 2015 The Android Open Source Project +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at: +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. +//* +//***************************************************************************** +//* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +//*/ +///** +//****************************************************************************** +//* @file +//* ih264_intra_pred_luma_4x4_av8.s +//* +//* @brief +//* Contains function definitions for intra 4x4 Luma prediction . +//* +//* @author +//* Ittiam +//* +//* @par List of Functions: +//* +//* -ih264_intra_pred_luma_4x4_mode_vert_av8 +//* -ih264_intra_pred_luma_4x4_mode_horz_av8 +//* -ih264_intra_pred_luma_4x4_mode_dc_av8 +//* -ih264_intra_pred_luma_4x4_mode_diag_dl_av8 +//* -ih264_intra_pred_luma_4x4_mode_diag_dr_av8 +//* -ih264_intra_pred_luma_4x4_mode_vert_r_av8 +//* -ih264_intra_pred_luma_4x4_mode_horz_d_av8 +//* -ih264_intra_pred_luma_4x4_mode_vert_l_av8 +//* -ih264_intra_pred_luma_4x4_mode_horz_u_av8 +//* +//* @remarks +//* None +//* +//******************************************************************************* +//*/ + +///* All the functions here are replicated from ih264_intra_pred_filters.c +// + +///** +///** +///** +// + +.text +.p2align 2 +.include "ih264_neon_macros.s" + + + + +///** +//******************************************************************************* +//* +//*ih264_intra_pred_luma_4x4_mode_vert +//* +//* @brief +//* Perform Intra prediction for luma_4x4 mode:vertical +//* +//* @par Description: +//* Perform Intra prediction for luma_4x4 mode:vertical ,described in sec 8.3.1.2.1 +//* +//* @param[in] pu1_src +//* UWORD8 pointer to the source +//* +//* @param[out] pu1_dst +//* UWORD8 pointer to the destination +//* +//* @param[in] src_strd +//* integer source stride +//* +//* @param[in] dst_strd +//* integer destination stride +//* +//* @param[in] ui_neighboravailability +//* availability of neighbouring pixels(Not used in this function) +//* +//* @returns +//* +//* @remarks +//* None +//* +//******************************************************************************* +//void ih264_intra_pred_luma_4x4_mode_vert(UWORD8 *pu1_src, +// UWORD8 *pu1_dst, +// WORD32 src_strd, +// WORD32 dst_strd, +// WORD32 ui_neighboravailability) + +//**************Variables Vs Registers***************************************** +// x0 => *pu1_src +// x1 => *pu1_dst +// w2 => src_strd +// w3 => dst_strd +// w4 => ui_neighboravailability + + .global ih264_intra_pred_luma_4x4_mode_vert_av8 + +ih264_intra_pred_luma_4x4_mode_vert_av8: + + push_v_regs + sxtw x3, w3 + + add x0, x0, #5 + + ld1 {v0.s}[0], [x0] + st1 {v0.s}[0], [x1], x3 + st1 {v0.s}[0], [x1], x3 + st1 {v0.s}[0], [x1], x3 + st1 {v0.s}[0], [x1], x3 + + pop_v_regs + ret + + + + + +///****************************************************************************** + + +///** +//******************************************************************************* +//* +//*ih264_intra_pred_luma_4x4_mode_horz +//* +//* @brief +//* Perform Intra prediction for luma_4x4 mode:horizontal +//* +//* @par Description: +//* Perform Intra prediction for luma_4x4 mode:horizontal ,described in sec 8.3.1.2.2 +//* +//* @param[in] pu1_src +//* UWORD8 pointer to the source +//* +//* @param[out] pu1_dst +//* UWORD8 pointer to the destination +//* +//* @param[in] src_strd +//* integer source stride +//* +//* @param[in] dst_strd +//* integer destination stride +//* +//* @param[in] ui_neighboravailability +//* availability of neighbouring pixels(Not used in this function) +//* +//* @returns +//* +//* @remarks +//* None +//* +//******************************************************************************* +//*/ +//void ih264_intra_pred_luma_4x4_mode_horz(UWORD8 *pu1_src, +// UWORD8 *pu1_dst, +// WORD32 src_strd, +// WORD32 dst_strd, +// WORD32 ui_neighboravailability) +//**************Variables Vs Registers***************************************** +// x0 => *pu1_src +// x1 => *pu1_dst +// w2 => src_strd +// w3 => dst_strd +// w4 => ui_neighboravailability + + + + .global ih264_intra_pred_luma_4x4_mode_horz_av8 + +ih264_intra_pred_luma_4x4_mode_horz_av8: + + push_v_regs + sxtw x3, w3 + + ld1 {v1.s}[0], [x0] + dup v0.8b, v1.b[3] + dup v2.8b, v1.b[2] + st1 {v0.s}[0], [x1], x3 + dup v3.8b, v1.b[1] + st1 {v2.s}[0], [x1], x3 + dup v4.8b, v1.b[0] + st1 {v3.s}[0], [x1], x3 + st1 {v4.s}[0], [x1], x3 + + pop_v_regs + ret + + + + + + + +///****************************************************************************** + + +///** +//******************************************************************************* +//* +//*ih264_intra_pred_luma_4x4_mode_dc +//* +//* @brief +//* Perform Intra prediction for luma_4x4 mode:DC +//* +//* @par Description: +//* Perform Intra prediction for luma_4x4 mode:DC ,described in sec 8.3.1.2.3 +//* +//* @param[in] pu1_src +//* UWORD8 pointer to the source +//* +//* @param[out] pu1_dst +//* UWORD8 pointer to the destination +//* +//* @param[in] src_strd +//* integer source stride +//* +//* @param[in] dst_strd +//* integer destination stride +//* +//* @param[in] ui_neighboravailability +//* availability of neighbouring pixels +//* +//* @returns +//* +//* @remarks +//* None +//* +//*******************************************************************************/ +//void ih264_intra_pred_luma_4x4_mode_dc(UWORD8 *pu1_src, +// UWORD8 *pu1_dst, +// WORD32 src_strd, +// WORD32 dst_strd, +// WORD32 ui_neighboravailability) + +//**************Variables Vs Registers***************************************** +// x0 => *pu1_src +// x1 => *pu1_dst +// w2 => src_strd +// w3 => dst_strd +// w4 => ui_neighboravailability + + + + .global ih264_intra_pred_luma_4x4_mode_dc_av8 + +ih264_intra_pred_luma_4x4_mode_dc_av8: + + + + + push_v_regs + stp x19, x20, [sp, #-16]! + sxtw x3, w3 + + ands w5, w4, #0x01 + beq top_available //LEFT NOT AVAILABLE + + add x10, x0, #3 + mov x2, #-1 + ldrb w5, [x10], #-1 + ldrb w6, [x10], #-1 + ldrb w7, [x10], #-1 + add w5, w5, w6 + ldrb w8, [x10], #-1 + add w5, w5, w7 + ands w11, w4, #0x04 // CHECKING IF TOP_AVAILABLE ELSE BRANCHING TO ONLY LEFT AVAILABLE + add w5, w5, w8 + beq left_available + add x10, x0, #5 + // BOTH LEFT AND TOP AVAILABLE + ldrb w6, [x10], #1 + ldrb w7, [x10], #1 + add w5, w5, w6 + ldrb w8, [x10], #1 + add w5, w5, w7 + ldrb w9, [x10], #1 + add w5, w5, w8 + add w5, w5, w9 + add w5, w5, #4 + lsr w5, w5, #3 + dup v0.8b, w5 + st1 {v0.s}[0], [x1], x3 + st1 {v0.s}[0], [x1], x3 + st1 {v0.s}[0], [x1], x3 + st1 {v0.s}[0], [x1], x3 + b end_func + +top_available: // ONLT TOP AVAILABLE + ands w11, w4, #0x04 // CHECKING TOP AVAILABILTY OR ELSE BRANCH TO NONE AVAILABLE + beq none_available + + add x10, x0, #5 + ldrb w6, [x10], #1 + ldrb w7, [x10], #1 + ldrb w8, [x10], #1 + add w5, w6, w7 + ldrb w9, [x10], #1 + add w5, w5, w8 + add w5, w5, w9 + add w5, w5, #2 + lsr w5, w5, #2 + dup v0.8b, w5 + st1 {v0.s}[0], [x1], x3 + st1 {v0.s}[0], [x1], x3 + st1 {v0.s}[0], [x1], x3 + st1 {v0.s}[0], [x1], x3 + b end_func + +left_available: //ONLY LEFT AVAILABLE + add x5, x5, #2 + lsr x5, x5, #2 + dup v0.8b, w5 + st1 {v0.s}[0], [x1], x3 + st1 {v0.s}[0], [x1], x3 + st1 {v0.s}[0], [x1], x3 + st1 {v0.s}[0], [x1], x3 + b end_func + +none_available: //NONE AVAILABLE + mov x5, #128 + dup v0.8b, w5 + st1 {v0.s}[0], [x1], x3 + st1 {v0.s}[0], [x1], x3 + st1 {v0.s}[0], [x1], x3 + st1 {v0.s}[0], [x1], x3 + b end_func + + +end_func: + + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + + + + + + +///** +//******************************************************************************* +//* +//*ih264_intra_pred_luma_4x4_mode_diag_dl +//* +//* @brief +//* Perform Intra prediction for luma_4x4 mode:Diagonal_Down_Left +//* +//* @par Description: +//* Perform Intra prediction for luma_4x4 mode:Diagonal_Down_Left ,described in sec 8.3.1.2.4 +//* +//* @param[in] pu1_src +//* UWORD8 pointer to the source +//* +//* @param[out] pu1_dst +//* UWORD8 pointer to the destination +//* +//* @param[in] src_strd +//* integer source stride +//* +//* @param[in] dst_strd +//* integer destination stride +//* +//* @param[in] ui_neighboravailability +//* availability of neighbouring pixels +//* +//* @returns +//* +//* @remarks +//* None +//* +//*******************************************************************************/ +//void ih264_intra_pred_luma_4x4_mode_diag_dl(UWORD8 *pu1_src, +// UWORD8 *pu1_dst, +// WORD32 src_strd, +// WORD32 dst_strd, +// WORD32 ui_neighboravailability) + +//**************Variables Vs Registers***************************************** +// x0 => *pu1_src +// x1 => *pu1_dst +// w2 => src_strd +// w3 => dst_strd +// w4 => ui_neighboravailability + + + .global ih264_intra_pred_luma_4x4_mode_diag_dl_av8 + +ih264_intra_pred_luma_4x4_mode_diag_dl_av8: + + + push_v_regs + stp x19, x20, [sp, #-16]! + sxtw x3, w3 + + add x0, x0, #5 + sub x5, x3, #2 + add x6, x0, #7 + ld1 {v0.8b}, [x0] + ext v1.8b, v0.8b , v0.8b , #1 + ext v2.8b, v0.8b , v0.8b , #2 + ld1 {v2.b}[6], [x6] + uaddl v20.8h, v0.8b, v1.8b + uaddl v22.8h, v1.8b, v2.8b + add v24.8h, v20.8h , v22.8h + sqrshrun v3.8b, v24.8h, #2 + st1 {v3.s}[0], [x1], x3 + ext v4.8b, v3.8b , v3.8b , #1 + st1 {v4.s}[0], [x1], x3 + st1 {v3.h}[1], [x1], #2 + st1 {v3.h}[2], [x1], x5 + st1 {v4.h}[1], [x1], #2 + st1 {v4.h}[2], [x1] + +end_func_diag_dl: + + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + + + + + + + + +///** +//******************************************************************************* +//* +//*ih264_intra_pred_luma_4x4_mode_diag_dr +//* +//* @brief +//* Perform Intra prediction for luma_4x4 mode:Diagonal_Down_Right +//* +//* @par Description: +//* Perform Intra prediction for luma_4x4 mode:Diagonal_Down_Right ,described in sec 8.3.1.2.5 +//* +//* @param[in] pu1_src +//* UWORD8 pointer to the source +//* +//* @param[out] pu1_dst +//* UWORD8 pointer to the destination +//* +//* @param[in] src_strd +//* integer source stride +//* +//* @param[in] dst_strd +//* integer destination stride +//* +//* @param[in] ui_neighboravailability +//* availability of neighbouring pixels +//* +//* @returns +//* +//* @remarks +//* None +//* +//*******************************************************************************/ +//void ih264_intra_pred_luma_4x4_mode_diag_dr(UWORD8 *pu1_src, +// UWORD8 *pu1_dst, +// WORD32 src_strd, +// WORD32 dst_strd, +// WORD32 ui_neighboravailability) + +//**************Variables Vs Registers***************************************** +// x0 => *pu1_src +// x1 => *pu1_dst +// w2 => src_strd +// w3 => dst_strd +// w4 => ui_neighboravailability + + + .global ih264_intra_pred_luma_4x4_mode_diag_dr_av8 + +ih264_intra_pred_luma_4x4_mode_diag_dr_av8: + + push_v_regs + stp x19, x20, [sp, #-16]! + sxtw x3, w3 + + + ld1 {v0.8b}, [x0] + add x0, x0, #1 + ld1 {v1.8b}, [x0] + ext v2.8b, v1.8b , v1.8b , #1 + uaddl v20.8h, v0.8b, v1.8b + uaddl v22.8h, v1.8b, v2.8b + add v24.8h, v20.8h , v22.8h + sqrshrun v3.8b, v24.8h, #2 + + ext v4.8b, v3.8b , v3.8b , #1 + sub x5, x3, #2 + st1 {v4.h}[1], [x1], #2 + st1 {v4.h}[2], [x1], x5 + st1 {v3.h}[1], [x1], #2 + st1 {v3.h}[2], [x1], x5 + st1 {v4.s}[0], [x1], x3 + st1 {v3.s}[0], [x1], x3 + +end_func_diag_dr: + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + + + + + + +///** +//******************************************************************************* +//* +//*ih264_intra_pred_luma_4x4_mode_vert_r +//* +//* @brief +//* Perform Intra prediction for luma_4x4 mode:Vertical_Right +//* +//* @par Description: +//* Perform Intra prediction for luma_4x4 mode:Vertical_Right ,described in sec 8.3.1.2.6 +//* +//* @param[in] pu1_src +//* UWORD8 pointer to the source +//* +//* @param[out] pu1_dst +//* UWORD8 pointer to the destination +//* +//* @param[in] src_strd +//* integer source stride +//* +//* @param[in] dst_strd +//* integer destination stride +//* +//* @param[in] ui_neighboravailability +//* availability of neighbouring pixels +//* +//* @returns +//* +//* @remarks +//* None +//* +//*******************************************************************************/ +//void ih264_intra_pred_luma_4x4_mode_vert_r(UWORD8 *pu1_src, +// UWORD8 *pu1_dst, +// WORD32 src_strd, +// WORD32 dst_strd, +// WORD32 ui_neighboravailability) + +//**************Variables Vs Registers***************************************** +// x0 => *pu1_src +// x1 => *pu1_dst +// w2 => src_strd +// w3 => dst_strd +// w4 => ui_neighboravailability + + + .global ih264_intra_pred_luma_4x4_mode_vert_r_av8 + +ih264_intra_pred_luma_4x4_mode_vert_r_av8: + + push_v_regs + stp x19, x20, [sp, #-16]! + sxtw x3, w3 + + + ld1 {v0.8b}, [x0] + add x0, x0, #1 + ld1 {v1.8b}, [x0] + ext v2.8b, v1.8b , v1.8b , #1 + uaddl v20.8h, v0.8b, v1.8b + uaddl v22.8h, v1.8b, v2.8b + add v24.8h, v20.8h , v22.8h + sqrshrun v4.8b, v20.8h, #1 + sqrshrun v3.8b, v24.8h, #2 + sub x5, x3, #2 + ext v5.8b, v3.8b , v3.8b , #3 + st1 {v4.s}[1], [x1], x3 + st1 {v5.s}[0], [x1], x3 + sub x8, x3, #3 + st1 {v3.b}[2], [x1], #1 + st1 {v4.h}[2], [x1], #2 + st1 {v4.b}[6], [x1], x8 + st1 {v3.b}[1], [x1], #1 + st1 {v5.h}[0], [x1], #2 + st1 {v5.b}[2], [x1] + + +end_func_vert_r: + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + + + + +///** +//******************************************************************************* +//* +//*ih264_intra_pred_luma_4x4_mode_horz_d +//* +//* @brief +//* Perform Intra prediction for luma_4x4 mode:Horizontal_Down +//* +//* @par Description: +//* Perform Intra prediction for luma_4x4 mode:Horizontal_Down ,described in sec 8.3.1.2.7 +//* +//* @param[in] pu1_src +//* UWORD8 pointer to the source +//* +//* @param[out] pu1_dst +//* UWORD8 pointer to the destination +//* +//* @param[in] src_strd +//* integer source stride +//* +//* @param[in] dst_strd +//* integer destination stride +//* +//* @param[in] ui_neighboravailability +//* availability of neighbouring pixels +//* +//* @returns +//* +//* @remarks +//* None +//* +//*******************************************************************************/ +//void ih264_intra_pred_luma_4x4_mode_horz_d(UWORD8 *pu1_src, +// UWORD8 *pu1_dst, +// WORD32 src_strd, +// WORD32 dst_strd, +// WORD32 ui_neighboravailability) + +//**************Variables Vs Registers***************************************** +// x0 => *pu1_src +// x1 => *pu1_dst +// w2 => src_strd +// w3 => dst_strd +// w4 => ui_neighboravailability + + + .global ih264_intra_pred_luma_4x4_mode_horz_d_av8 + +ih264_intra_pred_luma_4x4_mode_horz_d_av8: + + push_v_regs + stp x19, x20, [sp, #-16]! + sxtw x3, w3 + + ld1 {v0.8b}, [x0] + add x0, x0, #1 + ld1 {v1.8b}, [x0] + ext v2.8b, v1.8b , v0.8b , #1 + uaddl v20.8h, v0.8b, v1.8b + uaddl v22.8h, v1.8b, v2.8b + add v24.8h, v20.8h , v22.8h + sqrshrun v4.8b, v20.8h, #1 + sqrshrun v5.8b, v24.8h, #2 + sub x5, x3, #2 + mov v6.8b, v5.8b + trn1 v10.8b, v4.8b, v5.8b + trn2 v5.8b, v4.8b, v5.8b // + mov v4.8b, v10.8b + st1 {v5.h}[1], [x1], #2 + st1 {v6.h}[2], [x1], x5 + st1 {v4.h}[1], [x1], #2 + st1 {v5.h}[1], [x1], x5 + st1 {v5.h}[0], [x1], #2 + st1 {v4.h}[1], [x1], x5 + st1 {v4.h}[0], [x1], #2 + st1 {v5.h}[0], [x1], x5 + +end_func_horz_d: + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + + + + + + +///** +//******************************************************************************* +//* +//*ih264_intra_pred_luma_4x4_mode_vert_l +//* +//* @brief +//* Perform Intra prediction for luma_4x4 mode:Vertical_Left +//* +//* @par Description: +//* Perform Intra prediction for luma_4x4 mode:Vertical_Left ,described in sec 8.3.1.2.8 +//* +//* @param[in] pu1_src +//* UWORD8 pointer to the source +//* +//* @param[out] pu1_dst +//* UWORD8 pointer to the destination +//* +//* @param[in] src_strd +//* integer source stride +//* +//* @param[in] dst_strd +//* integer destination stride +//* +//* @param[in] ui_neighboravailability +//* availability of neighbouring pixels +//* +//* @returns +//* +//* @remarks +//* None +//* +//*******************************************************************************/ +//void ih264_intra_pred_luma_4x4_mode_vert_l(UWORD8 *pu1_src, +// UWORD8 *pu1_dst, +// WORD32 src_strd, +// WORD32 dst_strd, +// WORD32 ui_neighboravailability) + +//**************Variables Vs Registers***************************************** +// x0 => *pu1_src +// x1 => *pu1_dst +// w2 => src_strd +// w3 => dst_strd +// w4 => ui_neighboravailability + + + .global ih264_intra_pred_luma_4x4_mode_vert_l_av8 + +ih264_intra_pred_luma_4x4_mode_vert_l_av8: + + push_v_regs + stp x19, x20, [sp, #-16]! + sxtw x3, w3 + add x0, x0, #4 + ld1 {v0.8b}, [x0] + add x0, x0, #1 + ld1 {v1.8b}, [x0] + ext v2.8b, v1.8b , v0.8b , #1 + uaddl v20.8h, v0.8b, v1.8b + uaddl v22.8h, v1.8b, v2.8b + add v24.8h, v20.8h , v22.8h + sqrshrun v4.8b, v20.8h, #1 + sqrshrun v5.8b, v24.8h, #2 + ext v6.8b, v4.8b , v4.8b , #1 + ext v7.8b, v5.8b , v5.8b , #1 + st1 {v6.s}[0], [x1], x3 + ext v8.8b, v4.8b , v4.8b , #2 + ext v9.8b, v5.8b , v5.8b , #2 + st1 {v7.s}[0], [x1], x3 + st1 {v8.s}[0], [x1], x3 + st1 {v9.s}[0], [x1], x3 + +end_func_vert_l: + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + + + + + + +///** +//******************************************************************************* +//* +//*ih264_intra_pred_luma_4x4_mode_horz_u +//* +//* @brief +//* Perform Intra prediction for luma_4x4 mode:Horizontal_Up +//* +//* @par Description: +//* Perform Intra prediction for luma_4x4 mode:Horizontal_Up ,described in sec 8.3.1.2.9 +//* +//* @param[in] pu1_src +//* UWORD8 pointer to the source +//* +//* @param[out] pu1_dst +//* UWORD8 pointer to the destination +//* +//* @param[in] src_strd +//* integer source stride +//* +//* @param[in] dst_strd +//* integer destination stride +//* +//* @param[in] ui_neighboravailability +//* availability of neighbouring pixels +//* +//* @returns +//* +//* @remarks +//* None +//* +//*******************************************************************************/ +//void ih264_intra_pred_luma_4x4_mode_horz_u(UWORD8 *pu1_src, +// UWORD8 *pu1_dst, +// WORD32 src_strd, +// WORD32 dst_strd, +// WORD32 ui_neighboravailability) + +//**************Variables Vs Registers***************************************** +// x0 => *pu1_src +// x1 => *pu1_dst +// w2 => src_strd +// w3 => dst_strd +// w4 => ui_neighboravailability + + + .global ih264_intra_pred_luma_4x4_mode_horz_u_av8 + +ih264_intra_pred_luma_4x4_mode_horz_u_av8: + + push_v_regs + sxtw x3, w3 + stp x19, x20, [sp, #-16]! + mov x10, x0 + ld1 {v0.8b}, [x0] + ldrb w9, [x0], #1 + ext v1.8b, v0.8b , v0.8b , #1 + ld1 {v0.b}[7], [x10] + ext v2.8b, v1.8b , v1.8b , #1 + uaddl v20.8h, v0.8b, v1.8b + uaddl v22.8h, v1.8b, v2.8b + add v24.8h, v20.8h , v22.8h + sqrshrun v4.8b, v20.8h, #1 + sqrshrun v5.8b, v24.8h, #2 + mov v6.8b, v4.8b + ext v6.8b, v5.8b , v4.8b , #1 + st1 {v4.b}[2], [x1], #1 + st1 {v6.b}[0], [x1], #1 + trn1 v10.8b, v6.8b, v5.8b + trn2 v5.8b, v6.8b, v5.8b // + mov v6.8b , v10.8b + sub x5, x3, #2 + trn1 v10.8b, v4.8b, v6.8b + trn2 v6.8b, v4.8b, v6.8b // + mov v4.8b , v10.8b + dup v7.8b, w9 + st1 {v6.h}[0], [x1], x5 + st1 {v6.h}[0], [x1], #2 + st1 {v5.h}[3], [x1], x5 + st1 {v5.h}[3], [x1], #2 + st1 {v7.h}[3], [x1], x5 + st1 {v7.s}[0], [x1], x3 + +end_func_horz_u: + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + + diff --git a/dependencies/ih264d/common/armv8/ih264_intra_pred_luma_8x8_av8.s b/dependencies/ih264d/common/armv8/ih264_intra_pred_luma_8x8_av8.s new file mode 100644 index 00000000..273aa81b --- /dev/null +++ b/dependencies/ih264d/common/armv8/ih264_intra_pred_luma_8x8_av8.s @@ -0,0 +1,1076 @@ +//****************************************************************************** +//* +//* Copyright (C) 2015 The Android Open Source Project +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at: +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. +//* +//***************************************************************************** +//* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +//*/ +///** +//****************************************************************************** +//* @file +//* ih264_intra_pred_luma_8x8_av8.s +//* +//* @brief +//* Contains function definitions for intra 8x8 Luma prediction . +//* +//* @author +//* Ittiam +//* +//* @par List of Functions: +//* +//* -ih264_intra_pred_luma_8x8_mode_vert_av8 +//* -ih264_intra_pred_luma_8x8_mode_horz_av8 +//* -ih264_intra_pred_luma_8x8_mode_dc_av8 +//* -ih264_intra_pred_luma_8x8_mode_diag_dl_av8 +//* -ih264_intra_pred_luma_8x8_mode_diag_dr_av8 +//* -ih264_intra_pred_luma_8x8_mode_vert_r_av8 +//* -ih264_intra_pred_luma_8x8_mode_horz_d_av8 +//* -ih264_intra_pred_luma_8x8_mode_vert_l_av8 +//* -ih264_intra_pred_luma_8x8_mode_horz_u_av8 +//* +//* @remarks +//* None +//* +//******************************************************************************* +//*/ + +///* All the functions here are replicated from ih264_intra_pred_filters.c +// + +///** +///** +///** + +.text +.p2align 2 +.include "ih264_neon_macros.s" + +.extern ih264_gai1_intrapred_luma_8x8_horz_u + + + +///** +//******************************************************************************* +//* +//*ih264_intra_pred_luma_8x8_mode_vert +//* +//* @brief +//* Perform Intra prediction for luma_8x8 mode:vertical +//* +//* @par Description: +//* Perform Intra prediction for luma_8x8 mode:vertical ,described in sec 8.3.2.2.2 +//* +//* @param[in] pu1_src +//* UWORD8 pointer to the source +//* +//* @param[out] pu1_dst +//* UWORD8 pointer to the destination +//* +//* @param[in] src_strd +//* integer source stride +//* +//* @param[in] dst_strd +//* integer destination stride +//* +//* @param[in] ui_neighboravailability +//* availability of neighbouring pixels(Not used in this function) +//* +//* @returns +//* +//* @remarks +//* None +//* +//******************************************************************************* +//void ih264_intra_pred_luma_8x8_mode_vert(UWORD8 *pu1_src, +// UWORD8 *pu1_dst, +// WORD32 src_strd, +// WORD32 dst_strd, +// WORD32 ui_neighboravailability) + +//**************Variables Vs Registers***************************************** +// x0 => *pu1_src +// x1 => *pu1_dst +// w2 => src_strd +// w3 => dst_strd +// w4 => ui_neighboravailability + + + .global ih264_intra_pred_luma_8x8_mode_vert_av8 + +ih264_intra_pred_luma_8x8_mode_vert_av8: + + // STMFD sp!, {x4-x12, x14} //store register values to stack + push_v_regs + //stp x19, x20,[sp,#-16]! + sxtw x3, w3 + + add x0, x0, #9 + ld1 {v0.8b}, [x0] + + st1 {v0.8b}, [x1], x3 + st1 {v0.8b}, [x1], x3 + st1 {v0.8b}, [x1], x3 + st1 {v0.8b}, [x1], x3 + st1 {v0.8b}, [x1], x3 + st1 {v0.8b}, [x1], x3 + st1 {v0.8b}, [x1], x3 + st1 {v0.8b}, [x1], x3 + + // LDMFD sp!,{x4-x12,PC} //Restoring registers from stack + //ldp x19, x20,[sp],#16 + pop_v_regs + ret + + + + + +///****************************************************************************** + + +///** +//******************************************************************************* +//* +//*ih264_intra_pred_luma_8x8_mode_horz +//* +//* @brief +//* Perform Intra prediction for luma_8x8 mode:horizontal +//* +//* @par Description: +//* Perform Intra prediction for luma_8x8 mode:horizontal ,described in sec 8.3.2.2.2 +//* +//* @param[in] pu1_src +//* UWORD8 pointer to the source +//* +//* @param[out] pu1_dst +//* UWORD8 pointer to the destination +//* +//* @param[in] src_strd +//* integer source stride +//* +//* @param[in] dst_strd +//* integer destination stride +//* +//* @param[in] ui_neighboravailability +//* availability of neighbouring pixels(Not used in this function) +//* +//* @returns +//* +//* @remarks +//* None +//* +//******************************************************************************* +//*/ +//void ih264_intra_pred_luma_8x8_mode_horz(UWORD8 *pu1_src, +// UWORD8 *pu1_dst, +// WORD32 src_strd, +// WORD32 dst_strd, +// WORD32 ui_neighboravailability) +//**************Variables Vs Registers***************************************** +// x0 => *pu1_src +// x1 => *pu1_dst +// w2 => src_strd +// w3 => dst_strd +// w4 => ui_neighboravailability + + + .global ih264_intra_pred_luma_8x8_mode_horz_av8 + +ih264_intra_pred_luma_8x8_mode_horz_av8: + + + + // STMFD sp!, {x4-x12, x14} //store register values to stack + push_v_regs + stp x19, x20, [sp, #-16]! + sxtw x3, w3 + add x0, x0, #7 + + ldrb w5, [x0], #-1 + ldrb w6, [x0], #-1 + dup v0.8b, w5 + st1 {v0.8b}, [x1], x3 + ldrb w7, [x0], #-1 + dup v1.8b, w6 + st1 {v1.8b}, [x1], x3 + dup v2.8b, w7 + ldrb w8, [x0], #-1 + dup v3.8b, w8 + st1 {v2.8b}, [x1], x3 + ldrb w5, [x0], #-1 + st1 {v3.8b}, [x1], x3 + dup v0.8b, w5 + ldrb w6, [x0], #-1 + st1 {v0.8b}, [x1], x3 + ldrb w7, [x0], #-1 + dup v1.8b, w6 + dup v2.8b, w7 + st1 {v1.8b}, [x1], x3 + ldrb w8, [x0], #-1 + dup v3.8b, w8 + st1 {v2.8b}, [x1], x3 + st1 {v3.8b}, [x1], x3 + + // LDMFD sp!,{x4-x12,PC} //Restoring registers from stack + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + + + + + + +///****************************************************************************** + + +///** +//******************************************************************************* +//* +//*ih264_intra_pred_luma_8x8_mode_dc +//* +//* @brief +//* Perform Intra prediction for luma_8x8 mode:DC +//* +//* @par Description: +//* Perform Intra prediction for luma_8x8 mode:DC ,described in sec 8.3.2.2.3 +//* +//* @param[in] pu1_src +//* UWORD8 pointer to the source +//* +//* @param[out] pu1_dst +//* UWORD8 pointer to the destination +//* +//* @param[in] src_strd +//* integer source stride +//* +//* @param[in] dst_strd +//* integer destination stride +//* +//* @param[in] ui_neighboravailability +//* availability of neighbouring pixels +//* +//* @returns +//* +//* @remarks +//* None +//* +//*******************************************************************************/ +//void ih264_intra_pred_luma_8x8_mode_dc(UWORD8 *pu1_src, +// UWORD8 *pu1_dst, +// WORD32 src_strd, +// WORD32 dst_strd, +// WORD32 ui_neighboravailability) + +//**************Variables Vs Registers***************************************** +// x0 => *pu1_src +// x1 => *pu1_dst +// w2 => src_strd +// w3 => dst_strd +// w4 => ui_neighboravailability + + + .global ih264_intra_pred_luma_8x8_mode_dc_av8 + +ih264_intra_pred_luma_8x8_mode_dc_av8: + + + + // STMFD sp!, {x4-x12, x14} //store register values to stack + push_v_regs + sxtw x3, w3 + stp x19, x20, [sp, #-16]! + + ands w6, w4, #0x01 + beq top_available //LEFT NOT AVAILABLE + + add x10, x0, #7 + mov x2, #-1 + ldrb w5, [x10], -1 + ldrb w6, [x10], -1 + ldrb w7, [x10], -1 + add w5, w5, w6 + ldrb w8, [x10], -1 + add w5, w5, w7 + ldrb w6, [x10], -1 + add w5, w5, w8 + ldrb w7, [x10], -1 + add w5, w5, w6 + ldrb w8, [x10], -1 + add w5, w5, w7 + ands w11, w4, #0x04 // CHECKING IF TOP_AVAILABLE ELSE BRANCHING TO ONLY LEFT AVAILABLE + add w5, w5, w8 + ldrb w6, [x10], -1 + add w5, w5, w6 + beq left_available + add x10, x0, #9 + // BOTH LEFT AND TOP AVAILABLE + ld1 {v0.8b}, [x10] + uaddlp v1.4h, v0.8b + uaddlp v3.2s, v1.4h + uaddlp v2.1d, v3.2s + dup v10.8h, w5 + dup v8.8h, v2.h[0] + add v12.8h, v8.8h , v10.8h + sqrshrun v31.8b, v12.8h, #4 + st1 {v31.8b}, [x1], x3 + st1 {v31.8b}, [x1], x3 + st1 {v31.8b}, [x1], x3 + st1 {v31.8b}, [x1], x3 + st1 {v31.8b}, [x1], x3 + st1 {v31.8b}, [x1], x3 + st1 {v31.8b}, [x1], x3 + st1 {v31.8b}, [x1], x3 + b end_func + +top_available: // ONLT TOP AVAILABLE + ands w11, w4, #0x04 // CHECKING TOP AVAILABILTY OR ELSE BRANCH TO NONE AVAILABLE + beq none_available + + add x10, x0, #9 + ld1 {v10.8b}, [x10] + uaddlp v14.4h, v10.8b + uaddlp v13.2s, v14.4h + uaddlp v12.1d, v13.2s + rshrn v4.8b, v12.8h, #3 + dup v31.8b, v4.b[0] + st1 {v31.8b}, [x1], x3 + st1 {v31.8b}, [x1], x3 + st1 {v31.8b}, [x1], x3 + st1 {v31.8b}, [x1], x3 + st1 {v31.8b}, [x1], x3 + st1 {v31.8b}, [x1], x3 + st1 {v31.8b}, [x1], x3 + st1 {v31.8b}, [x1], x3 + b end_func + + +left_available: //ONLY LEFT AVAILABLE + add x5, x5, #4 + lsr x5, x5, #3 + dup v0.8b, w5 + st1 {v0.8b}, [x1], x3 + st1 {v0.8b}, [x1], x3 + st1 {v0.8b}, [x1], x3 + st1 {v0.8b}, [x1], x3 + st1 {v0.8b}, [x1], x3 + st1 {v0.8b}, [x1], x3 + st1 {v0.8b}, [x1], x3 + st1 {v0.8b}, [x1], x3 + b end_func + +none_available: //NONE AVAILABLE + mov x9, #128 + dup v0.8b, w9 + st1 {v0.8b}, [x1], x3 + st1 {v0.8b}, [x1], x3 + st1 {v0.8b}, [x1], x3 + st1 {v0.8b}, [x1], x3 + st1 {v0.8b}, [x1], x3 + st1 {v0.8b}, [x1], x3 + st1 {v0.8b}, [x1], x3 + st1 {v0.8b}, [x1], x3 + + +end_func: + + // LDMFD sp!,{x4-x12,PC} //Restoring registers from stack + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + + + + + +///** +//******************************************************************************* +//* +//*ih264_intra_pred_luma_8x8_mode_diag_dl +//* +//* @brief +//* Perform Intra prediction for luma_8x8 mode:Diagonal_Down_Left +//* +//* @par Description: +//* Perform Intra prediction for luma_8x8 mode:Diagonal_Down_Left ,described in sec 8.3.2.2.4 +//* +//* @param[in] pu1_src +//* UWORD8 pointer to the source +//* +//* @param[out] pu1_dst +//* UWORD8 pointer to the destination +//* +//* @param[in] src_strd +//* integer source stride +//* +//* @param[in] dst_strd +//* integer destination stride +//* +//* @param[in] ui_neighboravailability +//* availability of neighbouring pixels +//* +//* @returns +//* +//* @remarks +//* None +//* +//*******************************************************************************/ +//void ih264_intra_pred_luma_8x8_mode_diag_dl(UWORD8 *pu1_src, +// UWORD8 *pu1_dst, +// WORD32 src_strd, +// WORD32 dst_strd, +// WORD32 ui_neighboravailability) + +//**************Variables Vs Registers***************************************** +// x0 => *pu1_src +// x1 => *pu1_dst +// w2 => src_strd +// w3 => dst_strd +// w4 => ui_neighboravailability + + .global ih264_intra_pred_luma_8x8_mode_diag_dl_av8 + +ih264_intra_pred_luma_8x8_mode_diag_dl_av8: + + // STMFD sp!, {x4-x12, x14} //store register values to stack + push_v_regs + stp x19, x20, [sp, #-16]! + sxtw x3, w3 + + add x0, x0, #9 + sub x5, x3, #4 + add x6, x0, #15 + ld1 { v0.16b}, [x0] + mov v1.d[0], v0.d[1] + ext v4.16b, v0.16b , v0.16b , #2 + mov v5.d[0], v4.d[1] + ext v2.16b, v0.16b , v0.16b , #1 + mov v3.d[0], v2.d[1] + ld1 {v5.b}[6], [x6] + // q1 = q0 shifted to left once + // q2 = q1 shifted to left once + uaddl v20.8h, v0.8b, v2.8b //Adding for FILT121 + uaddl v22.8h, v1.8b, v3.8b + uaddl v24.8h, v2.8b, v4.8b + uaddl v26.8h, v3.8b, v5.8b + add v24.8h, v20.8h , v24.8h + add v26.8h, v22.8h , v26.8h + + sqrshrun v4.8b, v24.8h, #2 + sqrshrun v5.8b, v26.8h, #2 + mov v4.d[1], v5.d[0] + //Q2 has all FILT121 values + st1 {v4.8b}, [x1], x3 + ext v18.16b, v4.16b , v4.16b , #1 + ext v16.16b, v18.16b , v18.16b , #1 + st1 {v18.8b}, [x1], x3 + ext v14.16b, v16.16b , v16.16b , #1 + st1 {v16.8b}, [x1], x3 + st1 {v14.8b}, [x1], x3 + st1 {v4.s}[1], [x1], #4 + st1 {v5.s}[0], [x1], x5 + st1 {v18.s}[1], [x1], #4 + st1 {v18.s}[2], [x1], x5 + st1 {v16.s}[1], [x1], #4 + st1 {v16.s}[2], [x1], x5 + st1 {v14.s}[1], [x1], #4 + st1 {v14.s}[2], [x1], x5 + + +end_func_diag_dl: + // LDMFD sp!,{x4-x12,PC} //Restoring registers from stack + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + + + +///** +//******************************************************************************* +//* +//*ih264_intra_pred_luma_8x8_mode_diag_dr +//* +//* @brief +//* Perform Intra prediction for luma_8x8 mode:Diagonal_Down_Right +//* +//* @par Description: +//* Perform Intra prediction for luma_8x8 mode:Diagonal_Down_Right ,described in sec 8.3.2.2.5 +//* +//* @param[in] pu1_src +//* UWORD8 pointer to the source +//* +//* @param[out] pu1_dst +//* UWORD8 pointer to the destination +//* +//* @param[in] src_strd +//* integer source stride +//* +//* @param[in] dst_strd +//* integer destination stride +//* +//* @param[in] ui_neighboravailability +//* availability of neighbouring pixels +//* +//* @returns +//* +//* @remarks +//* None +//* +//*******************************************************************************/ +//void ih264_intra_pred_luma_8x8_mode_diag_dr(UWORD8 *pu1_src, +// UWORD8 *pu1_dst, +// WORD32 src_strd, +// WORD32 dst_strd, +// WORD32 ui_neighboravailability) + +//**************Variables Vs Registers***************************************** +// x0 => *pu1_src +// x1 => *pu1_dst +// w2 => src_strd +// w3 => dst_strd +// w4 => ui_neighboravailability + + + .global ih264_intra_pred_luma_8x8_mode_diag_dr_av8 + +ih264_intra_pred_luma_8x8_mode_diag_dr_av8: + + // STMFD sp!, {x4-x12, x14} //store register values to stack + push_v_regs + stp x19, x20, [sp, #-16]! + sxtw x3, w3 + + + ld1 { v0.16b}, [x0] + mov v1.d[0], v0.d[1] + add x0, x0, #1 + ld1 { v2.16b}, [x0] + mov v3.d[0], v2.d[1] + ext v4.16b, v2.16b , v2.16b , #1 + mov v5.d[0], v4.d[1] + // q1 = q0 shifted to left once + // q2 = q1 shifted to left once + uaddl v20.8h, v0.8b, v2.8b //Adding for FILT121 + uaddl v22.8h, v1.8b, v3.8b + uaddl v24.8h, v2.8b, v4.8b + uaddl v26.8h, v3.8b, v5.8b + add v24.8h, v20.8h , v24.8h + add v26.8h, v22.8h , v26.8h + sqrshrun v4.8b, v24.8h, #2 + sqrshrun v5.8b, v26.8h, #2 + mov v4.d[1], v5.d[0] + //Q2 has all FILT121 values + sub x5, x3, #4 + ext v18.16b, v4.16b , v4.16b , #15 + st1 {v18.d}[1], [x1], x3 + ext v16.16b, v18.16b , v18.16b , #15 + st1 {v16.d}[1], [x1], x3 + ext v14.16b, v16.16b , v16.16b , #15 + st1 {v14.d}[1], [x1], x3 + st1 {v4.s}[1], [x1], #4 + st1 {v5.s}[0], [x1], x5 + st1 {v18.s}[1], [x1], #4 + st1 {v18.s}[2], [x1], x5 + st1 {v16.s}[1], [x1], #4 + st1 {v16.s}[2], [x1], x5 + st1 {v14.s}[1], [x1], #4 + st1 {v14.s}[2], [x1], x5 + st1 {v4.8b}, [x1], x3 + +end_func_diag_dr: + // LDMFD sp!,{x4-x12,PC} //Restoring registers from stack + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + + + +///** +//******************************************************************************* +//* +//*ih264_intra_pred_luma_8x8_mode_vert_r +//* +//* @brief +//* Perform Intra prediction for luma_8x8 mode:Vertical_Right +//* +//* @par Description: +//* Perform Intra prediction for luma_8x8 mode:Vertical_Right ,described in sec 8.3.2.2.6 +//* +//* @param[in] pu1_src +//* UWORD8 pointer to the source +//* +//* @param[out] pu1_dst +//* UWORD8 pointer to the destination +//* +//* @param[in] src_strd +//* integer source stride +//* +//* @param[in] dst_strd +//* integer destination stride +//* +//* @param[in] ui_neighboravailability +//* availability of neighbouring pixels +//* +//* @returns +//* +//* @remarks +//* None +//* +//*******************************************************************************/ +//void ih264_intra_pred_luma_8x8_mode_vert_r(UWORD8 *pu1_src, +// UWORD8 *pu1_dst, +// WORD32 src_strd, +// WORD32 dst_strd, +// WORD32 ui_neighboravailability) + +//**************Variables Vs Registers***************************************** +// x0 => *pu1_src +// x1 => *pu1_dst +// w2 => src_strd +// w3 => dst_strd +// w4 => ui_neighboravailability + + + .global ih264_intra_pred_luma_8x8_mode_vert_r_av8 + +ih264_intra_pred_luma_8x8_mode_vert_r_av8: + + // STMFD sp!, {x4-x12, x14} //store register values to stack + push_v_regs + stp x19, x20, [sp, #-16]! + sxtw x3, w3 + + ld1 { v0.16b}, [x0] + mov v1.d[0], v0.d[1] + add x0, x0, #1 + ld1 { v2.16b}, [x0] + mov v3.d[0], v2.d[1] + ext v4.16b, v2.16b , v2.16b , #1 + mov v5.d[0], v4.d[1] + // q1 = q0 shifted to left once + // q2 = q1 shifted to left once + uaddl v20.8h, v0.8b, v2.8b + uaddl v22.8h, v1.8b, v3.8b + uaddl v24.8h, v2.8b, v4.8b + uaddl v26.8h, v3.8b, v5.8b + add v24.8h, v20.8h , v24.8h + add v26.8h, v22.8h , v26.8h + + sqrshrun v4.8b, v20.8h, #1 + sqrshrun v5.8b, v22.8h, #1 + mov v4.d[1], v5.d[0] + sqrshrun v6.8b, v24.8h, #2 + sqrshrun v7.8b, v26.8h, #2 + mov v6.d[1], v7.d[0] + //Q2 has all FILT11 values + //Q3 has all FILT121 values + sub x5, x3, #6 + sub x6, x3, #4 + st1 {v5.8b}, [x1], x3 // row 0 + ext v18.16b, v6.16b , v6.16b , #15 + mov v22.16b , v18.16b + ext v16.16b, v4.16b , v4.16b , #1 + st1 {v18.d}[1], [x1], x3 //row 1 + mov v14.16b , v16.16b + ext v20.16b, v4.16b , v4.16b , #15 + uzp1 v17.16b, v16.16b, v18.16b + uzp2 v18.16b, v16.16b, v18.16b + mov v16.16b , v17.16b + //row 2 + ext v12.16b, v16.16b , v16.16b , #1 + st1 {v20.d}[1], [x1] + st1 {v6.b}[6], [x1], x3 + //row 3 + + st1 {v12.h}[5], [x1], #2 + st1 {v6.s}[2], [x1], #4 + st1 {v6.h}[6], [x1], x5 + //row 4 + st1 {v18.h}[5], [x1], #2 + st1 {v4.s}[2], [x1], #4 + st1 {v4.h}[6], [x1], x5 + //row 5 + ext v26.16b, v18.16b , v18.16b , #1 + st1 {v16.h}[5], [x1], #2 + st1 {v22.s}[2], [x1], #4 + st1 {v22.h}[6], [x1], x5 + //row 6 + st1 {v26.h}[4], [x1], #2 + st1 {v26.b}[10], [x1], #1 + st1 {v4.b}[8], [x1], #1 + st1 {v14.s}[2], [x1], x6 + //row 7 + st1 {v12.s}[2], [x1], #4 + st1 {v6.s}[2], [x1], #4 + +end_func_vert_r: + // LDMFD sp!,{x4-x12,PC} //Restoring registers from stack + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + + + +///** +//******************************************************************************* +//* +//*ih264_intra_pred_luma_8x8_mode_horz_d +//* +//* @brief +//* Perform Intra prediction for luma_8x8 mode:Horizontal_Down +//* +//* @par Description: +//* Perform Intra prediction for luma_8x8 mode:Horizontal_Down ,described in sec 8.3.2.2.7 +//* +//* @param[in] pu1_src +//* UWORD8 pointer to the source +//* +//* @param[out] pu1_dst +//* UWORD8 pointer to the destination +//* +//* @param[in] src_strd +//* integer source stride +//* +//* @param[in] dst_strd +//* integer destination stride +//* +//* @param[in] ui_neighboravailability +//* availability of neighbouring pixels +//* +//* @returns +//* +//* @remarks +//* None +//* +//*******************************************************************************/ +//void ih264_intra_pred_luma_8x8_mode_horz_d(UWORD8 *pu1_src, +// UWORD8 *pu1_dst, +// WORD32 src_strd, +// WORD32 dst_strd, +// WORD32 ui_neighboravailability) + +//**************Variables Vs Registers***************************************** +// x0 => *pu1_src +// x1 => *pu1_dst +// w2 => src_strd +// w3 => dst_strd +// w4 => ui_neighboravailability + + .global ih264_intra_pred_luma_8x8_mode_horz_d_av8 + +ih264_intra_pred_luma_8x8_mode_horz_d_av8: + + // STMFD sp!, {x4-x12, x14} //store register values to stack + push_v_regs + stp x19, x20, [sp, #-16]! + sxtw x3, w3 + + ld1 { v0.16b}, [x0] + mov v1.d[0], v0.d[1] + add x0, x0, #1 + ld1 { v2.16b}, [x0] + mov v3.d[0], v2.d[1] + ext v4.16b, v2.16b , v2.16b , #1 + mov v5.d[0], v4.d[1] + // q1 = q0 shifted to left once + // q2 = q1 shifted to left once + uaddl v20.8h, v0.8b, v2.8b + uaddl v22.8h, v1.8b, v3.8b + uaddl v24.8h, v2.8b, v4.8b + uaddl v26.8h, v3.8b, v5.8b + add v24.8h, v20.8h , v24.8h + add v26.8h, v22.8h , v26.8h + + sqrshrun v4.8b, v20.8h, #1 + sqrshrun v5.8b, v22.8h, #1 + mov v4.d[1], v5.d[0] + sqrshrun v6.8b, v24.8h, #2 + sqrshrun v7.8b, v26.8h, #2 + mov v6.d[1], v7.d[0] + //Q2 has all FILT11 values + //Q3 has all FILT121 values + mov v8.16b, v4.16b + mov v10.16b, v6.16b + sub x6, x3, #6 + trn1 v9.16b, v8.16b, v10.16b + trn2 v10.16b, v8.16b, v10.16b // + mov v8.16b, v9.16b + mov v12.16b, v8.16b + mov v14.16b, v10.16b + sub x5, x3, #4 + trn1 v13.8h, v12.8h, v14.8h + trn2 v14.8h, v12.8h, v14.8h + mov v12.16b, v13.16b + ext v16.16b, v6.16b , v6.16b , #14 + //ROW 0 + st1 {v16.d}[1], [x1] + st1 {v10.h}[3], [x1], x3 + + //ROW 1 + st1 {v14.s}[1], [x1], #4 + st1 {v6.s}[2], [x1], x5 + //ROW 2 + st1 {v10.h}[2], [x1], #2 + st1 {v14.s}[1], [x1], #4 + st1 {v7.h}[0], [x1], x6 + //ROW 3 + st1 {v12.s}[1], [x1], #4 + st1 {v14.s}[1], [x1], x5 + //ROW 4 + st1 {v14.h}[1], [x1], #2 + st1 {v12.s}[1], [x1], #4 + st1 {v14.h}[2], [x1], x6 + //ROW 5 + st1 {v14.s}[0], [x1], #4 + st1 {v12.s}[1], [x1], x5 + //ROW 6 + st1 {v10.h}[0], [x1], #2 + st1 {v8.h}[1], [x1], #2 + st1 {v14.h}[1], [x1], #2 + st1 {v12.h}[2], [x1], x6 + //ROW 7 + st1 {v12.s}[0], [x1], #4 + st1 {v14.s}[0], [x1], x5 + +end_func_horz_d: + // LDMFD sp!,{x4-x12,PC} //Restoring registers from stack + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + + + + +///** +//******************************************************************************* +//* +//*ih264_intra_pred_luma_8x8_mode_vert_l +//* +//* @brief +//* Perform Intra prediction for luma_8x8 mode:Vertical_Left +//* +//* @par Description: +//* Perform Intra prediction for luma_8x8 mode:Vertical_Left ,described in sec 8.3.2.2.8 +//* +//* @param[in] pu1_src +//* UWORD8 pointer to the source +//* +//* @param[out] pu1_dst +//* UWORD8 pointer to the destination +//* +//* @param[in] src_strd +//* integer source stride +//* +//* @param[in] dst_strd +//* integer destination stride +//* +//* @param[in] ui_neighboravailability +//* availability of neighbouring pixels +//* +//* @returns +//* +//* @remarks +//* None +//* +//*******************************************************************************/ +//void ih264_intra_pred_luma_8x8_mode_vert_l(UWORD8 *pu1_src, +// UWORD8 *pu1_dst, +// WORD32 src_strd, +// WORD32 dst_strd, +// WORD32 ui_neighboravailability) + +//**************Variables Vs Registers***************************************** +// x0 => *pu1_src +// x1 => *pu1_dst +// w2 => src_strd +// w3 => dst_strd +// w4 => ui_neighboravailability + + + .global ih264_intra_pred_luma_8x8_mode_vert_l_av8 + +ih264_intra_pred_luma_8x8_mode_vert_l_av8: + + // STMFD sp!, {x4-x12, x14} //Restoring registers from stack + push_v_regs + stp x19, x20, [sp, #-16]! + sxtw x3, w3 + add x0, x0, #9 + ld1 { v0.16b}, [x0] + mov v1.d[0], v0.d[1] + add x0, x0, #1 + ld1 { v2.16b}, [x0] + mov v3.d[0], v2.d[1] + ext v4.16b, v2.16b , v2.16b , #1 + mov v5.d[0], v4.d[1] + uaddl v20.8h, v0.8b, v2.8b + uaddl v22.8h, v1.8b, v3.8b + uaddl v24.8h, v2.8b, v4.8b + uaddl v26.8h, v3.8b, v5.8b + add v24.8h, v20.8h , v24.8h + add v26.8h, v22.8h , v26.8h + + sqrshrun v4.8b, v20.8h, #1 + sqrshrun v5.8b, v22.8h, #1 + mov v4.d[1], v5.d[0] + sqrshrun v6.8b, v24.8h, #2 + ext v8.16b, v4.16b , v4.16b , #1 + sqrshrun v7.8b, v26.8h, #2 + mov v6.d[1], v7.d[0] + //Q2 has all FILT11 values + //Q3 has all FILT121 values + + ext v10.16b, v6.16b , v6.16b , #1 + //ROW 0,1 + st1 {v4.8b}, [x1], x3 + st1 {v6.8b}, [x1], x3 + + ext v12.16b, v8.16b , v8.16b , #1 + ext v14.16b, v10.16b , v10.16b , #1 + //ROW 2,3 + st1 {v8.8b}, [x1], x3 + st1 {v10.8b}, [x1], x3 + + ext v16.16b, v12.16b , v12.16b , #1 + ext v18.16b, v14.16b , v14.16b , #1 + //ROW 4,5 + st1 {v12.8b}, [x1], x3 + st1 {v14.8b}, [x1], x3 + //ROW 6,7 + st1 {v16.8b}, [x1], x3 + st1 {v18.8b}, [x1], x3 + +end_func_vert_l: + // LDMFD sp!,{x4-x12,PC} //Restoring registers from stack + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + + + + +///** +//******************************************************************************* +//* +//*ih264_intra_pred_luma_8x8_mode_horz_u +//* +//* @brief +//* Perform Intra prediction for luma_8x8 mode:Horizontal_Up +//* +//* @par Description: +//* Perform Intra prediction for luma_8x8 mode:Horizontal_Up ,described in sec 8.3.2.2.9 +//* +//* @param[in] pu1_src +//* UWORD8 pointer to the source +//* +//* @param[out] pu1_dst +//* UWORD8 pointer to the destination +//* +//* @param[in] src_strd +//* integer source stride +//* +//* @param[in] dst_strd +//* integer destination stride +//* +//* @param[in] ui_neighboravailability +//* availability of neighbouring pixels +//* +//* @returns +//* +//* @remarks +//* None +//* +//*******************************************************************************/ +//void ih264_intra_pred_luma_8x8_mode_horz_u(UWORD8 *pu1_src, +// UWORD8 *pu1_dst, +// WORD32 src_strd, +// WORD32 dst_strd, +// WORD32 ui_neighboravailability) + +//**************Variables Vs Registers***************************************** +// x0 => *pu1_src +// x1 => *pu1_dst +// w2 => src_strd +// w3 => dst_strd +// w4 => ui_neighboravailability + + .global ih264_intra_pred_luma_8x8_mode_horz_u_av8 + +ih264_intra_pred_luma_8x8_mode_horz_u_av8: + + // STMFD sp!, {x4-x12, x14} //store register values to stack + push_v_regs + stp x19, x20, [sp, #-16]! + sxtw x3, w3 + + ld1 {v0.8b}, [x0] + ld1 {v1.b}[7], [x0] + mov v0.d[1], v1.d[0] + ext v2.16b, v0.16b , v0.16b , #1 + mov v3.d[0], v2.d[1] + ext v4.16b, v2.16b , v2.16b , #1 + mov v5.d[0], v4.d[1] + + adrp x12, :got:ih264_gai1_intrapred_luma_8x8_horz_u + ldr x12, [x12, #:got_lo12:ih264_gai1_intrapred_luma_8x8_horz_u] + uaddl v20.8h, v0.8b, v2.8b + uaddl v22.8h, v1.8b, v3.8b + uaddl v24.8h, v2.8b, v4.8b + uaddl v26.8h, v3.8b, v5.8b + add v24.8h, v20.8h , v24.8h + add v26.8h, v22.8h , v26.8h + ld1 { v10.16b}, [x12] + mov v11.d[0], v10.d[1] + sqrshrun v4.8b, v20.8h, #1 + sqrshrun v5.8b, v22.8h, #1 + mov v4.d[1], v5.d[0] + sqrshrun v6.8b, v24.8h, #2 + sqrshrun v7.8b, v26.8h, #2 + mov v6.d[1], v7.d[0] + //Q2 has all FILT11 values + //Q3 has all FILT121 values + mov v30.16b, v4.16b + mov v31.16b, v6.16b + tbl v12.8b, {v30.16b, v31.16b}, v10.8b + dup v14.16b, v5.b[7] // + tbl v13.8b, {v30.16b, v31.16b}, v11.8b + mov v12.d[1], v13.d[0] + ext v16.16b, v12.16b , v14.16b , #2 + ext v18.16b, v16.16b , v14.16b , #2 + st1 {v12.8b}, [x1], x3 //0 + ext v20.16b, v18.16b , v14.16b , #2 + st1 {v16.8b}, [x1], x3 //1 + st1 {v18.8b}, [x1], x3 //2 + st1 {v20.8b}, [x1], x3 //3 + st1 {v13.8b}, [x1], x3 //4 + st1 {v16.d}[1], [x1], x3 //5 + st1 {v18.d}[1], [x1], x3 //6 + st1 {v20.d}[1], [x1], x3 //7 + + +end_func_horz_u: + // LDMFD sp!,{x4-x12,PC} //Restoring registers from stack + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + diff --git a/dependencies/ih264d/common/armv8/ih264_iquant_itrans_recon_av8.s b/dependencies/ih264d/common/armv8/ih264_iquant_itrans_recon_av8.s new file mode 100644 index 00000000..003ee74a --- /dev/null +++ b/dependencies/ih264d/common/armv8/ih264_iquant_itrans_recon_av8.s @@ -0,0 +1,784 @@ +//****************************************************************************** +//* +//* Copyright (C) 2015 The Android Open Source Project +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at: +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. +//* +//***************************************************************************** +//* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +//*/ +///** +///******************************************************************************* +// * //file +// * ih264_iquant_itrans_recon_a9.s +// * +// * //brief +// * Contains function definitions for single stage inverse transform +// * +// * //author +// * Parthiban V +// * Mohit +// * Harinarayanaan +// * +// * //par List of Functions: +// * - ih264_iquant_itrans_recon_4x4_av8() +// * - ih264_iquant_itrans_recon_8x8_av8() +// * - ih264_iquant_itrans_recon_chroma_4x4_av8() +// * +// * //remarks +// * None +// * +// ******************************************************************************* + +.text +.p2align 2 +.include "ih264_neon_macros.s" + +///* +// ******************************************************************************* +// * +// * //brief +// * This function performs inverse quant and Inverse transform type Ci4 for 4*4 block +// * +// * //par Description: +// * Performs inverse transform Ci4 and adds the residue to get the +// * reconstructed block +// * +// * //param[in] pi2_src +// * Input 4x4 coefficients +// * +// * //param[in] pu1_pred +// * Prediction 4x4 block +// * +// * //param[out] pu1_out +// * Output 4x4 block +// * +// * //param[in] u4_qp_div_6 +// * QP +// * +// * //param[in] pu2_weigh_mat +// * Pointer to weight matrix +// * +// * //param[in] pred_strd, +// * Prediction stride +// * +// * //param[in] out_strd +// * Output Stride +// * +// *//param[in] pi2_tmp +// * temporary buffer of size 1*16 +// * +// * //param[in] pu2_iscal_mat +// * Pointer to the inverse quantization matrix +// * +// * //returns Void +// * +// * //remarks +// * None +// * +// ******************************************************************************* +// */ +//void ih264_iquant_itrans_recon_4x4(WORD16 *pi2_src, +// UWORD8 *pu1_pred, +// UWORD8 *pu1_out, +// WORD32 pred_strd, +// WORD32 out_strd, +// const UWORD16 *pu2_iscal_mat, +// const UWORD16 *pu2_weigh_mat, +// UWORD32 u4_qp_div_6, +// WORD32 *pi4_tmp, +// WORD32 iq_start_idx +// WORD16 *pi2_dc_ld_addr) +//**************Variables Vs Registers***************************************** +//x0 => *pi2_src +//x1 => *pu1_pred +//x2 => *pu1_out +//w3 => pred_strd +//w4 => out_strd +//x5 => *pu2_iscal_mat +//x6 => *pu2_weigh_mat +//w7 => u4_qp_div_6 +// => pi4_tmp +// => iq_start_idx +// => pi2_dc_ld_addr +//Only one shift is done in horizontal inverse because, +//if u4_qp_div_6 is lesser than 4 then shift value will be neagative and do negative left shift, in this case rnd_factor has value +//if u4_qp_div_6 is greater than 4 then shift value will be positive and do left shift, here rnd_factor is 0 + + .global ih264_iquant_itrans_recon_4x4_av8 +ih264_iquant_itrans_recon_4x4_av8: + + push_v_regs + sxtw x3, w3 + sxtw x4, w4 + + dup v30.4s, w7 //Populate the u4_qp_div_6 in Q15 + + ldr w8, [sp, #72] //Loads iq_start_idx + sxtw x8, w8 + + ldr x10, [sp, #80] //Load alternate dc address + + subs x8, x8, #1 // if x8 == 1 => intra case , so result of subtraction is zero and z flag is set + + +//=======================DEQUANT FROM HERE=================================== + + ld4 {v20.4h - v23.4h}, [x5] // load pu2_iscal_mat[i], i =0..15 + ld4 {v26.4h - v29.4h}, [x6] // pu2_weigh_mat[i], i =0..15 + ld4 {v16.4h - v19.4h}, [x0] // pi2_src_tmp[i], i =0..15 + + + mul v20.4h, v20.4h, v26.4h // x[i]=(scale[i] * dequant[i]) where i = 0..3 + mul v21.4h, v21.4h, v27.4h // x[i]=(scale[i] * dequant[i]) where i = 4..7 + mul v22.4h, v22.4h, v28.4h // x[i]=(scale[i] * dequant[i]) where i = 8..11 + mul v23.4h, v23.4h, v29.4h // x[i]=(scale[i] * dequant[i]) where i = 12..14 + + smull v0.4s, v16.4h, v20.4h // q0 = p[i] = (x[i] * trns_coeff[i]) where i = 0..3 + smull v2.4s, v17.4h, v21.4h // q1 = p[i] = (x[i] * trns_coeff[i]) where i = 4..7 + smull v4.4s, v18.4h, v22.4h // q2 = p[i] = (x[i] * trns_coeff[i]) where i = 8..11 + smull v6.4s, v19.4h, v23.4h // q3 = p[i] = (x[i] * trns_coeff[i]) where i = 12..15 + + sshl v0.4s, v0.4s, v30.4s // q0 = q[i] = (p[i] << (qp/6)) where i = 0..3 + sshl v2.4s, v2.4s, v30.4s // q1 = q[i] = (p[i] << (qp/6)) where i = 4..7 + sshl v4.4s, v4.4s, v30.4s // q2 = q[i] = (p[i] << (qp/6)) where i = 8..11 + sshl v6.4s, v6.4s, v30.4s // q3 = q[i] = (p[i] << (qp/6)) where i = 12..15 + + sqrshrn v0.4h, v0.4s, #0x4 // d0 = c[i] = ((q[i] + 32) >> 4) where i = 0..3 + sqrshrn v1.4h, v2.4s, #0x4 // d1 = c[i] = ((q[i] + 32) >> 4) where i = 4..7 + sqrshrn v2.4h, v4.4s, #0x4 // d2 = c[i] = ((q[i] + 32) >> 4) where i = 8..11 + sqrshrn v3.4h, v6.4s, #0x4 // d3 = c[i] = ((q[i] + 32) >> 4) where i = 12..15 + + bne skip_loading_luma_dc_src + ld1 {v0.h}[0], [x10] // loads signed halfword pi2_dc_ld_addr[0], if x8==1 +skip_loading_luma_dc_src: + + //========= PROCESS IDCT FROM HERE ======= + //Steps for Stage 1: + //------------------ + ld1 {v30.s}[0], [x1], x3 // i row load pu1_pred buffer + + sshr v8.4h, v1.4h, #1 // d1>>1 + sshr v9.4h, v3.4h, #1 // d3>>1 + + add v4.4h, v0.4h, v2.4h // x0 = d0 + d2// + sub v5.4h, v0.4h, v2.4h // x1 = d0 - d2// + sub v6.4h, v8.4h, v3.4h // x2 = (d1 >> 1) - d3// + add v7.4h, v1.4h, v9.4h // x3 = d1 + (d3 >> 1)// + + ld1 {v30.s}[1], [x1], x3 // ii row load pu1_pred buffer + + add v10.4h, v4.4h , v7.4h // x0+x3 + add v11.4h, v5.4h , v6.4h // x1+x2 + sub v12.4h, v5.4h , v6.4h // x1-x2 + sub v13.4h, v4.4h , v7.4h + + ld1 {v31.s}[0], [x1], x3 // iii row load pu1_pred buf + + + //Steps for Stage 2: + //transopose + trn1 v4.4h, v10.4h, v11.4h + trn2 v5.4h, v10.4h, v11.4h + trn1 v6.4h, v12.4h, v13.4h + trn2 v7.4h, v12.4h, v13.4h + + trn1 v10.2s, v4.2s, v6.2s // 0 + trn1 v11.2s, v5.2s, v7.2s // 8 + trn2 v12.2s, v4.2s, v6.2s // 4 + trn2 v13.2s, v5.2s, v7.2s + //end transpose + + sshr v18.4h, v11.4h, #1 // q0>>1 + sshr v19.4h, v13.4h, #1 // q1>>1 + + add v14.4h, v10.4h, v12.4h // x0 = q0 + q2// + sub v15.4h, v10.4h, v12.4h // x1 = q0 - q2// + sub v16.4h, v18.4h, v13.4h // x2 = (q1 >> 1) - q3// + add v17.4h, v11.4h, v19.4h // x3 = q1+ (q3 >> 3)// + + + ld1 {v31.s}[1], [x1], x3 // iv row load pu1_pred buffer + + add v20.4h, v14.4h, v17.4h // x0 + x3 + add v21.4h, v15.4h, v16.4h // x1 + x2 + sub v22.4h, v15.4h, v16.4h // x1 - x2 + sub v23.4h, v14.4h, v17.4h // x0 - x3 + + mov v20.d[1], v21.d[0] + mov v22.d[1], v23.d[0] + + srshr v20.8h, v20.8h, #6 + srshr v22.8h, v22.8h, #6 + + uaddw v20.8h, v20.8h , v30.8b + uaddw v22.8h, v22.8h , v31.8b + + sqxtun v0.8b, v20.8h + sqxtun v1.8b, v22.8h + + st1 {v0.s}[0], [x2], x4 //i row store the value + st1 {v0.s}[1], [x2], x4 //ii row store the value + st1 {v1.s}[0], [x2], x4 //iii row store the value + st1 {v1.s}[1], [x2] //iv row store the value + + pop_v_regs + ret + + +///** +// ******************************************************************************* +// * +// * @brief +// * This function performs inverse quant and Inverse transform type Ci4 for 4*4 block +// * +// * @par Description: +// * Performs inverse transform Ci4 and adds the residue to get the +// * reconstructed block +// * +// * @param[in] pi2_src +// * Input 4x4 coefficients +// * +// * @param[in] pu1_pred +// * Prediction 4x4 block +// * +// * @param[out] pu1_out +// * Output 4x4 block +// * +// * @param[in] u4_qp_div_6 +// * QP +// * +// * @param[in] pu2_weigh_mat +// * Pointer to weight matrix +// * +// * @param[in] pred_strd, +// * Prediction stride +// * +// * @param[in] out_strd +// * Output Stride +// * +// *@param[in] pi2_tmp +// * temporary buffer of size 1*16 +// * +// * @param[in] pu2_iscal_mat +// * Pointer to the inverse quantization matrix +// * +// * @returns Void +// * +// * @remarks +// * None +// * +// ******************************************************************************* +// */ +//void ih264_iquant_itrans_recon_chroma_4x4(WORD16 *pi2_src, +// UWORD8 *pu1_pred, +// UWORD8 *pu1_out, +// WORD32 pred_strd, +// WORD32 out_strd, +// const UWORD16 *pu2_iscal_mat, +// const UWORD16 *pu2_weigh_mat, +// UWORD32 u4_qp_div_6, +// WORD32 *pi4_tmp +// WORD16 *pi2_dc_src) +//**************Variables Vs Registers***************************************** +//x0 => *pi2_src +//x1 => *pu1_pred +//x2 => *pu1_out +//w3 => pred_strd +//w4 => out_strd +//x5 => *pu2_iscal_mat +//x6 => *pu2_weigh_mat +//w7 => u4_qp_div_6 +//sp => pi4_tmp +//sp#8 => *pi2_dc_src + + .global ih264_iquant_itrans_recon_chroma_4x4_av8 +ih264_iquant_itrans_recon_chroma_4x4_av8: + +//VLD4.S16 is used because the pointer is incremented by SUB_BLK_WIDTH_4x4 +//If the macro value changes need to change the instruction according to it. +//Only one shift is done in horizontal inverse because, +//if u4_qp_div_6 is lesser than 4 then shift value will be neagative and do negative left shift, in this case rnd_factor has value +//if u4_qp_div_6 is greater than 4 then shift value will be positive and do left shift, here rnd_factor is 0 + +//at the end of the fucntion, we could have moved 64 bits into heigher 64 bits of register and done further processing +//but it seem to give only reduce the number of instruction by 1. [Since a15 we saw add and sub to be very high throughput +//all instructions were taken as equal + + //reduce sp by 64 + push_v_regs + sxtw x3, w3 + sxtw x4, w4 + + dup v30.4s, w7 //Populate the u4_qp_div_6 in Q15 + + //was at sp + 8, hence now at sp+64+8 = sp+72 + ldr x10, [sp, #72] //Load alternate dc address + +//=======================DEQUANT FROM HERE=================================== + + ld4 {v20.4h - v23.4h}, [x5] // load pu2_iscal_mat[i], i =0..15 + ld4 {v26.4h - v29.4h}, [x6] // pu2_weigh_mat[i], i =0..15 + ld4 {v16.4h - v19.4h}, [x0] // pi2_src_tmp[i], i =0..15 + + + mul v20.4h, v20.4h, v26.4h // x[i]=(scale[i] * dequant[i]) where i = 0..3 + mul v21.4h, v21.4h, v27.4h // x[i]=(scale[i] * dequant[i]) where i = 4..7 + mul v22.4h, v22.4h, v28.4h // x[i]=(scale[i] * dequant[i]) where i = 8..11 + mul v23.4h, v23.4h, v29.4h // x[i]=(scale[i] * dequant[i]) where i = 12..14 + + smull v0.4s, v16.4h, v20.4h // q0 = p[i] = (x[i] * trns_coeff[i]) where i = 0..3 + smull v2.4s, v17.4h, v21.4h // q1 = p[i] = (x[i] * trns_coeff[i]) where i = 4..7 + smull v4.4s, v18.4h, v22.4h // q2 = p[i] = (x[i] * trns_coeff[i]) where i = 8..11 + smull v6.4s, v19.4h, v23.4h // q3 = p[i] = (x[i] * trns_coeff[i]) where i = 12..15 + + sshl v0.4s, v0.4s, v30.4s // q0 = q[i] = (p[i] << (qp/6)) where i = 0..3 + sshl v2.4s, v2.4s, v30.4s // q1 = q[i] = (p[i] << (qp/6)) where i = 4..7 + sshl v4.4s, v4.4s, v30.4s // q2 = q[i] = (p[i] << (qp/6)) where i = 8..11 + sshl v6.4s, v6.4s, v30.4s // q3 = q[i] = (p[i] << (qp/6)) where i = 12..15 + + sqrshrn v0.4h, v0.4s, #0x4 // d0 = c[i] = ((q[i] + 32) >> 4) where i = 0..3 + sqrshrn v1.4h, v2.4s, #0x4 // d1 = c[i] = ((q[i] + 32) >> 4) where i = 4..7 + sqrshrn v2.4h, v4.4s, #0x4 // d2 = c[i] = ((q[i] + 32) >> 4) where i = 8..11 + sqrshrn v3.4h, v6.4s, #0x4 // d3 = c[i] = ((q[i] + 32) >> 4) where i = 12..15 + + ld1 {v0.h}[0], [x10] // loads signed halfword pi2_dc_src[0] + + //========= PROCESS IDCT FROM HERE ======= + //Steps for Stage 1: + //------------------ + + sshr v8.4h, v1.4h, #1 // d1>>1 + sshr v9.4h, v3.4h, #1 // d3>>1 + + add v4.4h, v0.4h, v2.4h // x0 = d0 + d2// + sub v5.4h, v0.4h, v2.4h // x1 = d0 - d2// + sub v6.4h, v8.4h, v3.4h // x2 = (d1 >> 1) - d3// + add v7.4h, v1.4h, v9.4h // x3 = d1 + (d3 >> 1)// + + + add v10.4h, v4.4h , v7.4h // x0+x3 + add v11.4h, v5.4h , v6.4h // x1+x2 + sub v12.4h, v5.4h , v6.4h // x1-x2 + sub v13.4h, v4.4h , v7.4h + + ld1 {v26.8b}, [x1], x3 // i row load pu1_pred buffer + ld1 {v27.8b}, [x1], x3 // ii row load pu1_pred buffer + ld1 {v28.8b}, [x1], x3 // iii row load pu1_pred buf + ld1 {v29.8b}, [x1], x3 // iv row load pu1_pred buffer + + //Steps for Stage 2: + //transopose + trn1 v4.4h, v10.4h, v11.4h + trn2 v5.4h, v10.4h, v11.4h + trn1 v6.4h, v12.4h, v13.4h + trn2 v7.4h, v12.4h, v13.4h + + trn1 v10.2s, v4.2s, v6.2s // 0 + trn1 v11.2s, v5.2s, v7.2s // 8 + trn2 v12.2s, v4.2s, v6.2s // 4 + trn2 v13.2s, v5.2s, v7.2s + //end transpose + + sshr v18.4h, v11.4h, #1 // q0>>1 + sshr v19.4h, v13.4h, #1 // q1>>1 + + add v14.4h, v10.4h, v12.4h // x0 = q0 + q2// + sub v15.4h, v10.4h, v12.4h // x1 = q0 - q2// + sub v16.4h, v18.4h, v13.4h // x2 = (q1 >> 1) - q3// + add v17.4h, v11.4h, v19.4h // x3 = q1+ (q3 >> 3)// + + //Backup the output addr + mov x0, x2 + + //load outpt buufer for interleaving + ld1 {v10.8b}, [x2], x4 + ld1 {v11.8b}, [x2], x4 + ld1 {v12.8b}, [x2], x4 + ld1 {v13.8b}, [x2] + + add v20.4h, v14.4h, v17.4h // x0 + x3 + add v21.4h, v15.4h, v16.4h // x1 + x2 + sub v22.4h, v15.4h, v16.4h // x1 - x2 + sub v23.4h, v14.4h, v17.4h // x0 - x3 + + srshr v20.4h, v20.4h, #6 + srshr v21.4h, v21.4h, #6 + srshr v22.4h, v22.4h, #6 + srshr v23.4h, v23.4h, #6 + + //nop v30.8b //dummy for deinterleaving + movi v31.4h, #0x00ff //mask for interleaving [copy lower 8 bits] + + //Extract u/v plane from interleaved data + uzp1 v26.8b, v26.8b, v30.8b + uzp1 v27.8b, v27.8b, v30.8b + uzp1 v28.8b, v28.8b, v30.8b + uzp1 v29.8b, v29.8b, v30.8b + + uaddw v20.8h, v20.8h, v26.8b + uaddw v21.8h, v21.8h, v27.8b + uaddw v22.8h, v22.8h, v28.8b + uaddw v23.8h, v23.8h, v29.8b + + sqxtun v0.8b, v20.8h + sqxtun v1.8b, v21.8h + sqxtun v2.8b, v22.8h + sqxtun v3.8b, v23.8h + + //long the output so that we have 0 at msb and value at lsb + uxtl v6.8h, v0.8b + uxtl v7.8h, v1.8b + uxtl v8.8h, v2.8b + uxtl v9.8h, v3.8b + + //select lsbs from proceesd data and msbs from pu1_out loaded data + bit v10.8b, v6.8b, v31.8b + bit v11.8b, v7.8b, v31.8b + bit v12.8b, v8.8b, v31.8b + bit v13.8b, v9.8b, v31.8b + + //store the interleaved result + st1 {v10.8b}, [x0], x4 + st1 {v11.8b}, [x0], x4 + st1 {v12.8b}, [x0], x4 + st1 {v13.8b}, [x0] + + pop_v_regs + ret + +///* +// ******************************************************************************* +// * +// * //brief +// * This function performs inverse quant and Inverse transform type Ci4 for 8*8 block +// * +// * //par Description: +// * Performs inverse transform Ci8 and adds the residue to get the +// * reconstructed block +// * +// * //param[in] pi2_src +// * Input 4x4 coefficients +// * +// * //param[in] pu1_pred +// * Prediction 4x4 block +// * +// * //param[out] pu1_out +// * Output 4x4 block +// * +// * //param[in] u4_qp_div_6 +// * QP +// * +// * //param[in] pu2_weigh_mat +// * Pointer to weight matrix +// * +// * //param[in] pred_strd, +// * Prediction stride +// * +// * //param[in] out_strd +// * Output Stride +// * +// *//param[in] pi2_tmp +// * temporary buffer of size 1*64 +// * +// * //param[in] pu2_iscal_mat +// * Pointer to the inverse quantization matrix +// * +// * //returns Void +// * +// * //remarks +// * None +// * +// ******************************************************************************* +// */ +//void ih264_iquant_itrans_recon_8x8(WORD16 *pi2_src, +// UWORD8 *pu1_pred, +// UWORD8 *pu1_out, +// WORD32 pred_strd, +// WORD32 out_strd, +// const UWORD16 *pu2_iscal_mat, +// const UWORD16 *pu2_weigh_mat, +// UWORD32 u4_qp_div_6, +// WORD32 *pi4_tmp, +// WORD32 iq_start_idx +// WORD16 *pi2_dc_ld_addr) +//**************Variables Vs Registers***************************************** +//x0 => *pi2_src +//x1 => *pu1_pred +//x2 => *pu1_out +//w3 => pred_strd +//w4 => out_strd +//x5 => *pu2_iscal_mat +//x6 => *pu2_weigh_mat +//w7 => u4_qp_div_6 +//NOT USED => pi4_tmp +//NOT USED => iq_start_idx +//NOT USED => pi2_dc_ld_addr + + .global ih264_iquant_itrans_recon_8x8_av8 +ih264_iquant_itrans_recon_8x8_av8: + + push_v_regs + sxtw x3, w3 + sxtw x4, w4 + + ld1 {v8.8h -v11.8h}, [x5], #64 + ld1 {v12.8h-v15.8h}, [x5] + + ld1 {v16.8h -v19.8h}, [x6], #64 + ld1 {v20.8h -v23.8h}, [x6] + + mov x8, #16 + ld1 {v0.8h}, [x0], x8 + ld1 {v1.8h}, [x0], x8 + ld1 {v2.8h}, [x0], x8 + ld1 {v3.8h}, [x0], x8 + ld1 {v4.8h}, [x0], x8 + ld1 {v5.8h}, [x0], x8 + ld1 {v6.8h}, [x0], x8 + ld1 {v7.8h}, [x0] + + mul v8.8h, v8.8h, v16.8h + mul v9.8h, v9.8h, v17.8h + mul v10.8h, v10.8h, v18.8h + mul v11.8h, v11.8h, v19.8h + mul v12.8h, v12.8h, v20.8h + mul v13.8h, v13.8h, v21.8h + mul v14.8h, v14.8h, v22.8h + mul v15.8h, v15.8h, v23.8h + + smull v16.4s, v0.4h, v8.4h + smull2 v17.4s, v0.8h, v8.8h + smull v18.4s, v1.4h, v9.4h + smull2 v19.4s, v1.8h, v9.8h + smull v20.4s, v2.4h, v10.4h + smull2 v21.4s, v2.8h, v10.8h + smull v22.4s, v3.4h, v11.4h + smull2 v23.4s, v3.8h, v11.8h + smull v24.4s, v4.4h, v12.4h + smull2 v25.4s, v4.8h, v12.8h + smull v26.4s, v5.4h, v13.4h + smull2 v27.4s, v5.8h, v13.8h + smull v28.4s, v6.4h, v14.4h + smull2 v29.4s, v6.8h, v14.8h + smull v30.4s, v7.4h, v15.4h + smull2 v31.4s, v7.8h, v15.8h + + dup v0.4s, w7 + + sshl v16.4s, v16.4s, v0.4s + sshl v17.4s, v17.4s, v0.4s + sshl v18.4s, v18.4s, v0.4s + sshl v19.4s, v19.4s, v0.4s + sshl v20.4s, v20.4s, v0.4s + sshl v21.4s, v21.4s, v0.4s + sshl v22.4s, v22.4s, v0.4s + sshl v23.4s, v23.4s, v0.4s + sshl v24.4s, v24.4s, v0.4s + sshl v25.4s, v25.4s, v0.4s + sshl v26.4s, v26.4s, v0.4s + sshl v27.4s, v27.4s, v0.4s + sshl v28.4s, v28.4s, v0.4s + sshl v29.4s, v29.4s, v0.4s + sshl v30.4s, v30.4s, v0.4s + sshl v31.4s, v31.4s, v0.4s + + sqrshrn v0.4h, v16.4s, #6 + sqrshrn2 v0.8h, v17.4s, #6 + sqrshrn v1.4h, v18.4s, #6 + sqrshrn2 v1.8h, v19.4s, #6 + sqrshrn v2.4h, v20.4s, #6 + sqrshrn2 v2.8h, v21.4s, #6 + sqrshrn v3.4h, v22.4s, #6 + sqrshrn2 v3.8h, v23.4s, #6 + sqrshrn v4.4h, v24.4s, #6 + sqrshrn2 v4.8h, v25.4s, #6 + sqrshrn v5.4h, v26.4s, #6 + sqrshrn2 v5.8h, v27.4s, #6 + sqrshrn v6.4h, v28.4s, #6 + sqrshrn2 v6.8h, v29.4s, #6 + sqrshrn v7.4h, v30.4s, #6 + sqrshrn2 v7.8h, v31.4s, #6 + + //loop counter + mov x8, #2 +//1x8 transofORM +trans_1x8_1d: + + //transpose 8x8 + trn1 v8.8h, v0.8h, v1.8h + trn2 v9.8h, v0.8h, v1.8h + trn1 v10.8h, v2.8h, v3.8h + trn2 v11.8h, v2.8h, v3.8h + trn1 v12.8h, v4.8h, v5.8h + trn2 v13.8h, v4.8h, v5.8h + trn1 v14.8h, v6.8h, v7.8h + trn2 v15.8h, v6.8h, v7.8h + + trn1 v0.4s, v8.4s, v10.4s + trn2 v2.4s, v8.4s, v10.4s + trn1 v1.4s, v9.4s, v11.4s + trn2 v3.4s, v9.4s, v11.4s + trn1 v4.4s, v12.4s, v14.4s + trn2 v6.4s, v12.4s, v14.4s + trn1 v5.4s, v13.4s, v15.4s + trn2 v7.4s, v13.4s, v15.4s + + trn1 v8.2d, v0.2d, v4.2d //0 + trn2 v12.2d, v0.2d, v4.2d //1 + trn1 v9.2d, v1.2d, v5.2d //2 + trn2 v13.2d, v1.2d, v5.2d //3 + trn1 v10.2d, v2.2d, v6.2d //4 + trn2 v14.2d, v2.2d, v6.2d //5 + trn1 v11.2d, v3.2d, v7.2d //6 + trn2 v15.2d, v3.2d, v7.2d //7 + + // 1 3 5 6 7 + sshr v16.8h, v9.8h, #1 //(pi2_tmp_ptr[1] >> 1) + sshr v17.8h, v10.8h, #1 //(pi2_tmp_ptr[2] >> 1) + sshr v18.8h, v11.8h, #1 //(pi2_tmp_ptr[3] >> 1) + sshr v19.8h, v13.8h, #1 //(pi2_tmp_ptr[5] >> 1) + sshr v20.8h, v14.8h, #1 //(pi2_tmp_ptr[6] >> 1) + sshr v21.8h, v15.8h, #1 //(pi2_tmp_ptr[7] >> 1) + + add v0.8h, v8.8h, v12.8h // i_y0 = (pi2_tmp_ptr[0] + pi2_tmp_ptr[4] ); + sub v2.8h, v8.8h, v12.8h // i_y2 = (pi2_tmp_ptr[0] - pi2_tmp_ptr[4] ); + + sub v4.8h, v17.8h, v14.8h //i_y4 = ((pi2_tmp_ptr[2] >> 1) - pi2_tmp_ptr[6] ); + add v6.8h, v10.8h, v20.8h //i_y6 = (pi2_tmp_ptr[2] + (pi2_tmp_ptr[6] >> 1)); + + //-w3 + w5 + ssubl v22.4s, v13.4h, v11.4h + ssubl2 v23.4s, v13.8h, v11.8h + //w3 + w5 + saddl v24.4s, v13.4h, v11.4h + saddl2 v25.4s, v13.8h, v11.8h + //-w1 + w7 + ssubl v26.4s, v15.4h, v9.4h + ssubl2 v27.4s, v15.8h, v9.8h + //w1 + w7 + saddl v28.4s, v15.4h, v9.4h + saddl2 v29.4s, v15.8h, v9.8h + + //-w3 + w5 - w7 + ssubw v22.4s, v22.4s, v15.4h + ssubw2 v23.4s, v23.4s, v15.8h + //w3 + w5 + w1 + saddw v24.4s, v24.4s, v9.4h + saddw2 v25.4s, v25.4s, v9.8h + //-w1 + w7 + w5 + saddw v26.4s, v26.4s, v13.4h + saddw2 v27.4s, v27.4s, v13.8h + //w1 + w7 - w3 + ssubw v28.4s, v28.4s, v11.4h + ssubw2 v29.4s, v29.4s, v11.8h + + //-w3 + w5 - w7 - (w7 >> 1) + ssubw v22.4s, v22.4s, v21.4h + ssubw2 v23.4s, v23.4s, v21.8h + //w3 + w5 + w1 + (w1 >> 1) + saddw v24.4s, v24.4s, v16.4h + saddw2 v25.4s, v25.4s, v16.8h + //-w1 + w7 + w5 + (w5 >> 1) + saddw v26.4s, v26.4s, v19.4h + saddw2 v27.4s, v27.4s, v19.8h + //w1 + w7 - w3 - (w3 >> 1) + ssubw v28.4s, v28.4s, v18.4h + ssubw2 v29.4s, v29.4s, v18.8h + + xtn v1.4h, v22.4s + xtn2 v1.8h, v23.4s + xtn v3.4h, v28.4s + xtn2 v3.8h, v29.4s + xtn v5.4h, v26.4s + xtn2 v5.8h, v27.4s + xtn v7.4h, v24.4s + xtn2 v7.8h, v25.4s + + sshr v16.8h, v1.8h, #2 //(y1 >> 2) + sshr v17.8h, v3.8h, #2 //(y3 >> 2) + sshr v18.8h, v5.8h, #2 //(y5 >> 2) + sshr v19.8h, v7.8h, #2 //(y7 >> 2) + + add v8.8h, v0.8h, v6.8h + add v9.8h, v1.8h, v19.8h + add v10.8h, v2.8h, v4.8h + add v11.8h, v3.8h, v18.8h + sub v12.8h, v2.8h, v4.8h + sub v13.8h, v17.8h, v5.8h + sub v14.8h, v0.8h, v6.8h + sub v15.8h, v7.8h, v16.8h + + add v0.8h, v8.8h, v15.8h + add v1.8h, v10.8h, v13.8h + add v2.8h, v12.8h, v11.8h + add v3.8h, v14.8h, v9.8h + sub v4.8h, v14.8h, v9.8h + sub v5.8h, v12.8h, v11.8h + sub v6.8h, v10.8h, v13.8h + sub v7.8h, v8.8h, v15.8h + + subs x8, x8, #1 + bne trans_1x8_1d + + ld1 {v22.8b}, [x1], x3 + ld1 {v23.8b}, [x1], x3 + ld1 {v24.8b}, [x1], x3 + ld1 {v25.8b}, [x1], x3 + ld1 {v26.8b}, [x1], x3 + ld1 {v27.8b}, [x1], x3 + ld1 {v28.8b}, [x1], x3 + ld1 {v29.8b}, [x1] + + srshr v0.8h, v0.8h, #6 + srshr v1.8h, v1.8h, #6 + srshr v2.8h, v2.8h, #6 + srshr v3.8h, v3.8h, #6 + srshr v4.8h, v4.8h, #6 + srshr v5.8h, v5.8h, #6 + srshr v6.8h, v6.8h, #6 + srshr v7.8h, v7.8h, #6 + + uaddw v0.8h, v0.8h, v22.8b + uaddw v1.8h, v1.8h, v23.8b + uaddw v2.8h, v2.8h, v24.8b + uaddw v3.8h, v3.8h, v25.8b + uaddw v4.8h, v4.8h, v26.8b + uaddw v5.8h, v5.8h, v27.8b + uaddw v6.8h, v6.8h, v28.8b + uaddw v7.8h, v7.8h, v29.8b + + sqxtun v0.8b, v0.8h + sqxtun v1.8b, v1.8h + sqxtun v2.8b, v2.8h + sqxtun v3.8b, v3.8h + sqxtun v4.8b, v4.8h + sqxtun v5.8b, v5.8h + sqxtun v6.8b, v6.8h + sqxtun v7.8b, v7.8h + + st1 {v0.8b}, [x2], x4 + st1 {v1.8b}, [x2], x4 + st1 {v2.8b}, [x2], x4 + st1 {v3.8b}, [x2], x4 + st1 {v4.8b}, [x2], x4 + st1 {v5.8b}, [x2], x4 + st1 {v6.8b}, [x2], x4 + st1 {v7.8b}, [x2] + + pop_v_regs + ret + + + + diff --git a/dependencies/ih264d/common/armv8/ih264_iquant_itrans_recon_dc_av8.s b/dependencies/ih264d/common/armv8/ih264_iquant_itrans_recon_dc_av8.s new file mode 100644 index 00000000..13061ec4 --- /dev/null +++ b/dependencies/ih264d/common/armv8/ih264_iquant_itrans_recon_dc_av8.s @@ -0,0 +1,403 @@ +//****************************************************************************** +//* +//* Copyright (C) 2015 The Android Open Source Project +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at: +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. +//* +//***************************************************************************** +//* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +//*/ +///** +// ******************************************************************************* +// * @file +// * ih264_iquant_itrans_recon_dc_av8.s +// * +// * @brief +// * Contains function definitions for single stage inverse transform +// * +// * @author +// * Mohit +// * +// * @par List of Functions: +// * - ih264_iquant_itrans_recon_4x4_dc_av8() +// * - ih264_iquant_itrans_recon_8x8_dc_av8() +// * - ih264_iquant_itrans_recon_chroma_4x4_dc_av8() +// * +// * @remarks +// * None +// * +// ******************************************************************************* +//*/ + + +.include "ih264_neon_macros.s" + + +///** +// ******************************************************************************* +// * +// * @brief +// * This function performs inverse quant and Inverse transform type Ci4 for 4*4 block +// * for dc input pattern only, i.e. only the (0,0) element of the input 4x4 block is +// * non-zero. For complete function, refer ih264_iquant_itrans_recon_a9.s +// * +// * @par Description: +// * Performs inverse transform Ci4 and adds the residue to get the +// * reconstructed block +// * +// * @param[in] pi2_src +// * Input 4x4 coefficients +// * +// * @param[in] pu1_pred +// * Prediction 4x4 block +// * +// * @param[out] pu1_out +// * Output 4x4 block +// * +// * @param[in] u4_qp_div_6 +// * QP +// * +// * @param[in] pu2_weigh_mat +// * Pointer to weight matrix +// * +// * @param[in] pred_strd, +// * Prediction stride +// * +// * @param[in] out_strd +// * Output Stride +// * +// *@param[in] pi2_tmp +// * temporary buffer of size 1*16 +// * +// * @param[in] pu2_iscal_mat +// * Pointer to the inverse quantization matrix +// * +// * @returns Void +// * +// * @remarks +// * None +// * +// ******************************************************************************* +// */ +//void ih264_iquant_itrans_recon_4x4_dc(WORD16 *pi2_src, +// UWORD8 *pu1_pred, +// UWORD8 *pu1_out, +// WORD32 pred_strd, +// WORD32 out_strd, +// const UWORD16 *pu2_iscal_mat, +// const UWORD16 *pu2_weigh_mat, +// UWORD32 u4_qp_div_6, +// WORD32 *pi4_tmp, +// WORD32 iq_start_idx +// WORD16 *pi2_dc_ld_addr) +//**************Variables Vs Registers***************************************** +//x0 => *pi2_src +//x1 => *pu1_pred +//x2 => *pu1_out +//w3 => pred_strd +//w4 => out_strd +//x5 => *pu2_iscal_mat +//x6 => *pu2_weigh_mat +//w7 => u4_qp_div_6 +// => pi4_tmp +// => iq_start_idx +// => pi2_dc_ld_addr + +.text +.p2align 2 + + .global ih264_iquant_itrans_recon_4x4_dc_av8 +ih264_iquant_itrans_recon_4x4_dc_av8: + + sxtw x3, w3 + sxtw x4, w4 + ldr w8, [sp, #8] //Loads iq_start_idx + subs w8, w8, #1 // if x8 == 1 => intra case , so result of subtraction is zero and z flag is set + + ldr x10, [sp, #16] //Load alternate dc address + push_v_regs + dup v30.4s, w7 //Populate the u4_qp_div_6 in Q15 + + + bne donot_use_pi2_dc_ld_addr_luma_dc + ld1 {v0.h}[0], [x10] +donot_use_pi2_dc_ld_addr_luma_dc: + + beq donot_use_pi2_src_luma_dc + ld1 {v0.h}[0], [x5] + ld1 {v1.h}[0], [x6] + ld1 {v2.h}[0], [x0] + mul v0.4h, v1.4h, v0.4h + smull v0.4s, v0.4h, v2.4h + sshl v0.4s, v0.4s, v30.4s + sqrshrn v0.4h, v0.4s, #4 +donot_use_pi2_src_luma_dc: + + + dup v0.8h, v0.h[0] + srshr v0.8h, v0.8h, #6 + + ld1 {v1.s}[0], [x1], x3 + ld1 {v1.s}[1], [x1], x3 + ld1 {v2.s}[0], [x1], x3 + ld1 {v2.s}[1], [x1] + + uxtl v1.8h, v1.8b + uxtl v2.8h, v2.8b + + add v1.8h, v0.8h, v1.8h + add v2.8h, v0.8h, v2.8h + + sqxtun v1.8b, v1.8h + sqxtun v2.8b, v2.8h + + st1 {v1.s}[0], [x2], x4 + st1 {v1.s}[1], [x2], x4 + st1 {v2.s}[0], [x2], x4 + st1 {v2.s}[1], [x2] + pop_v_regs + ret + +// /* +// ******************************************************************************** +// * +// * @brief This function reconstructs a 4x4 sub block from quantized resiude and +// * prediction buffer if only dc value is present for residue +// * +// * @par Description: +// * The quantized residue is first inverse quantized, +// * This inverse quantized content is added to the prediction buffer to recon- +// * struct the end output +// * +// * @param[in] pi2_src +// * quantized dc coeffiient +// * +// * @param[in] pu1_pred +// * prediction 4x4 block in interleaved format +// * +// * @param[in] pred_strd, +// * Prediction buffer stride in interleaved format +// * +// * @param[in] out_strd +// * recon buffer Stride +// * +// * @returns none +// * +// * @remarks none +// * +// ******************************************************************************* +// */ +// void ih264_iquant_itrans_recon_chroma_4x4_dc(WORD16 *pi2_src, +// UWORD8 *pu1_pred, +// UWORD8 *pu1_out, +// WORD32 pred_strd, +// WORD32 out_strd, +// const UWORD16 *pu2_iscal_mat, +// const UWORD16 *pu2_weigh_mat, +// UWORD32 u4_qp_div_6, +// WORD16 *pi2_tmp, +// WORD16 *pi2_dc_src) +// Register Usage +// x0 : pi2_src +// x1 : pu1_pred +// x2 : pu1_out +// w3 : pred_strd +// w4 : out_strd +// x5 : pu2_iscal_mat +// x6 : pu2_weigh_mat +// w7 : u4_qp_div_6 +// : pi2_tmp +// : pi2_dc_src +// Neon registers d0-d7, d16-d30 are used +// No need for pushing arm and neon registers + + + .global ih264_iquant_itrans_recon_chroma_4x4_dc_av8 +ih264_iquant_itrans_recon_chroma_4x4_dc_av8: + + sxtw x3, w3 + sxtw x4, w4 + ldr x0, [sp, #8] + push_v_regs + ld1 {v0.h}[0], [x0] + dup v0.8h, v0.h[0] + srshr v0.8h, v0.8h, #6 + + + //backup pu1_out + mov x0, x2 + + //nop v3.16b //dummy for deinterleaving + movi v31.8h, #0x00ff //mask for interleaving [copy lower 8 bits] + + ld1 {v1.d}[0], [x1], x3 + ld1 {v1.d}[1], [x1], x3 + ld1 {v2.d}[0], [x1], x3 + ld1 {v2.d}[1], [x1], x3 + + ld1 {v11.d}[0], [x2], x4 //load pu1_out for interleaving + ld1 {v11.d}[1], [x2], x4 + ld1 {v12.d}[0], [x2], x4 + ld1 {v12.d}[1], [x2] + + uzp1 v1.16b, v1.16b, v3.16b + uzp1 v2.16b, v2.16b, v3.16b + + uaddw v1.8h, v0.8h, v1.8b + uaddw v2.8h, v0.8h, v2.8b + + sqxtun v1.8b, v1.8h + sqxtun v2.8b, v2.8h + + uxtl v1.8h, v1.8b + uxtl v2.8h, v2.8b + + bit v11.16b, v1.16b, v31.16b + bit v12.16b, v2.16b, v31.16b + + st1 {v11.d}[0], [x0], x4 + st1 {v11.d}[1], [x0], x4 + st1 {v12.d}[0], [x0], x4 + st1 {v12.d}[1], [x0] + pop_v_regs + ret + +///* +// ******************************************************************************* +// * +// * //brief +// * This function performs inverse quant and Inverse transform type Ci4 for 8*8 block +// * [Only for Dc coeff] +// * //par Description: +// * Performs inverse transform Ci8 and adds the residue to get the +// * reconstructed block +// * +// * //param[in] pi2_src +// * Input 4x4 coefficients +// * +// * //param[in] pu1_pred +// * Prediction 4x4 block +// * +// * //param[out] pu1_out +// * Output 4x4 block +// * +// * //param[in] u4_qp_div_6 +// * QP +// * +// * //param[in] pu2_weigh_mat +// * Pointer to weight matrix +// * +// * //param[in] pred_strd, +// * Prediction stride +// * +// * //param[in] out_strd +// * Output Stride +// * +// *//param[in] pi2_tmp +// * temporary buffer of size 1*64 +// * +// * //param[in] pu2_iscal_mat +// * Pointer to the inverse quantization matrix +// * +// * //returns Void +// * +// * //remarks +// * None +// * +// ******************************************************************************* +// */ +//void ih264_iquant_itrans_recon_dc_8x8(WORD16 *pi2_src, +// UWORD8 *pu1_pred, +// UWORD8 *pu1_out, +// WORD32 pred_strd, +// WORD32 out_strd, +// const UWORD16 *pu2_iscal_mat, +// const UWORD16 *pu2_weigh_mat, +// UWORD32 u4_qp_div_6, +// WORD32 *pi4_tmp, +// WORD32 iq_start_idx +// WORD16 *pi2_dc_ld_addr) +//**************Variables Vs Registers***************************************** +//x0 => *pi2_src +//x1 => *pu1_pred +//x2 => *pu1_out +//w3 => pred_strd +//w4 => out_strd +//x5 => *pu2_iscal_mat +//x6 => *pu2_weigh_mat +//w7 => u4_qp_div_6 +//NOT USED => pi4_tmp +//NOT USED => iq_start_idx +//NOT USED => pi2_dc_ld_addr + + .global ih264_iquant_itrans_recon_8x8_dc_av8 +ih264_iquant_itrans_recon_8x8_dc_av8: + + push_v_regs + sxtw x3, w3 + sxtw x4, w4 + + ld1 {v1.h}[0], [x5] + ld1 {v2.h}[0], [x6] + ld1 {v0.h}[0], [x0] + dup v3.4s, w7 + + + mul v1.8h, v1.8h, v2.8h + smull v0.4s, v0.4h, v1.4h + sshl v0.4s, v0.4s, v3.4s + + sqrshrn v0.4h, v0.4s, #6 + srshr v0.8h, v0.8h, #6 + dup v0.8h, v0.h[0] + + ld1 {v22.8b}, [x1], x3 + ld1 {v23.8b}, [x1], x3 + ld1 {v24.8b}, [x1], x3 + ld1 {v25.8b}, [x1], x3 + ld1 {v26.8b}, [x1], x3 + ld1 {v27.8b}, [x1], x3 + ld1 {v28.8b}, [x1], x3 + ld1 {v29.8b}, [x1] + + uaddw v1.8h, v0.8h, v22.8b + uaddw v2.8h, v0.8h, v23.8b + uaddw v3.8h, v0.8h, v24.8b + uaddw v8.8h, v0.8h, v25.8b + uaddw v9.8h, v0.8h, v26.8b + uaddw v10.8h, v0.8h, v27.8b + uaddw v11.8h, v0.8h, v28.8b + uaddw v12.8h, v0.8h, v29.8b + + sqxtun v1.8b, v1.8h + sqxtun v2.8b, v2.8h + sqxtun v3.8b, v3.8h + sqxtun v8.8b, v8.8h + sqxtun v9.8b, v9.8h + sqxtun v10.8b, v10.8h + sqxtun v11.8b, v11.8h + sqxtun v12.8b, v12.8h + + st1 {v1.8b}, [x2], x4 + st1 {v2.8b}, [x2], x4 + st1 {v3.8b}, [x2], x4 + st1 {v8.8b}, [x2], x4 + st1 {v9.8b}, [x2], x4 + st1 {v10.8b}, [x2], x4 + st1 {v11.8b}, [x2], x4 + st1 {v12.8b}, [x2] + + pop_v_regs + ret + + diff --git a/dependencies/ih264d/common/armv8/ih264_mem_fns_neon_av8.s b/dependencies/ih264d/common/armv8/ih264_mem_fns_neon_av8.s new file mode 100644 index 00000000..802550df --- /dev/null +++ b/dependencies/ih264d/common/armv8/ih264_mem_fns_neon_av8.s @@ -0,0 +1,270 @@ +//****************************************************************************** +//* +//* Copyright (C) 2015 The Android Open Source Project +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at: +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. +//* +//***************************************************************************** +//* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +//*/ +///** +// ******************************************************************************* +// * @file +// * ih264_mem_fns_neon.s +// * +// * @brief +// * Contains function definitions for memory manipulation +// * +// * @author +// * Naveen SR +// * +// * @par List of Functions: +// * - ih264_memcpy_av8() +// * - ih264_memcpy_mul_8_av8() +// * - ih264_memset_mul_8_av8() +// * - ih264_memset_16bit_mul_8_av8() +// * - ih264_memset_16bit_av8() +// * +// * @remarks +// * None +// * +// ******************************************************************************* +//*/ + +.text +.p2align 2 +.include "ih264_neon_macros.s" +///** +//******************************************************************************* +//* +//* @brief +//* memcpy of a 1d array +//* +//* @par Description: +//* Does memcpy of 8bit data from source to destination for 8,16 or 32 number of bytes +//* +//* @param[in] pu1_dst +//* UWORD8 pointer to the destination +//* +//* @param[in] pu1_src +//* UWORD8 pointer to the source +//* +//* @param[in] num_bytes +//* number of bytes to copy +//* @returns +//* +//* @remarks +//* None +//* +//******************************************************************************* +//*/ +//void ih264_memcpy_mul_8(UWORD8 *pu1_dst, +// UWORD8 *pu1_src, +// UWORD32 num_bytes) +//**************Variables Vs Registers************************* +// x0 => *pu1_dst +// x1 => *pu1_src +// w2 => num_bytes + + + + + + .global ih264_memcpy_mul_8_av8 + +ih264_memcpy_mul_8_av8: + +loop_neon_memcpy_mul_8: + // Memcpy 8 bytes + ld1 {v0.8b}, [x1], #8 + st1 {v0.8b}, [x0], #8 + + subs w2, w2, #8 + bne loop_neon_memcpy_mul_8 + ret + + + +//******************************************************************************* +//*/ +//void ih264_memcpy(UWORD8 *pu1_dst, +// UWORD8 *pu1_src, +// UWORD32 num_bytes) +//**************Variables Vs Registers************************* +// x0 => *pu1_dst +// x1 => *pu1_src +// w2 => num_bytes + + + + .global ih264_memcpy_av8 + +ih264_memcpy_av8: + subs w2, w2, #8 + blt arm_memcpy +loop_neon_memcpy: + // Memcpy 8 bytes + ld1 {v0.8b}, [x1], #8 + st1 {v0.8b}, [x0], #8 + + subs w2, w2, #8 + bge loop_neon_memcpy + cmn w2, #8 + beq end_func1 + +arm_memcpy: + add w2, w2, #8 + +loop_arm_memcpy: + ldrb w3, [x1], #1 + strb w3, [x0], #1 + subs w2, w2, #1 + bne loop_arm_memcpy + ret +end_func1: + ret + + +//void ih264_memset_mul_8(UWORD8 *pu1_dst, +// UWORD8 value, +// UWORD32 num_bytes) +//**************Variables Vs Registers************************* +// x0 => *pu1_dst +// x1 => value +// x2 => num_bytes + + + .global ih264_memset_mul_8_av8 + +ih264_memset_mul_8_av8: + +// Assumptions: numbytes is either 8, 16 or 32 + dup v0.8b, w1 +loop_memset_mul_8: + // Memset 8 bytes + st1 {v0.8b}, [x0], #8 + + subs w2, w2, #8 + bne loop_memset_mul_8 + + ret + + +//void ih264_memset(UWORD8 *pu1_dst, +// UWORD8 value, +// UWORD32 num_bytes) +//**************Variables Vs Registers************************* +// x0 => *pu1_dst +// w1 => value +// w2 => num_bytes + + + + .global ih264_memset_av8 + +ih264_memset_av8: + subs w2, w2, #8 + blt arm_memset + dup v0.8b, w1 +loop_neon_memset: + // Memcpy 8 bytes + st1 {v0.8b}, [x0], #8 + + subs w2, w2, #8 + bge loop_neon_memset + cmn w2, #8 + beq end_func2 + +arm_memset: + add w2, w2, #8 + +loop_arm_memset: + strb w1, [x0], #1 + subs w2, w2, #1 + bne loop_arm_memset + ret +end_func2: + ret + + + + + +//void ih264_memset_16bit_mul_8(UWORD16 *pu2_dst, +// UWORD16 value, +// UWORD32 num_words) +//**************Variables Vs Registers************************* +// x0 => *pu2_dst +// w1 => value +// w2 => num_words + + + .global ih264_memset_16bit_mul_8_av8 + +ih264_memset_16bit_mul_8_av8: + +// Assumptions: num_words is either 8, 16 or 32 + + // Memset 8 words + dup v0.4h, w1 +loop_memset_16bit_mul_8: + st1 {v0.4h}, [x0], #8 + st1 {v0.4h}, [x0], #8 + + subs w2, w2, #8 + bne loop_memset_16bit_mul_8 + + ret + + + +//void ih264_memset_16bit(UWORD16 *pu2_dst, +// UWORD16 value, +// UWORD32 num_words) +//**************Variables Vs Registers************************* +// x0 => *pu2_dst +// w1 => value +// w2 => num_words + + + + .global ih264_memset_16bit_av8 + +ih264_memset_16bit_av8: + subs w2, w2, #8 + blt arm_memset_16bit + dup v0.4h, w1 +loop_neon_memset_16bit: + // Memset 8 words + st1 {v0.4h}, [x0], #8 + st1 {v0.4h}, [x0], #8 + + subs w2, w2, #8 + bge loop_neon_memset_16bit + cmn w2, #8 + beq end_func3 + +arm_memset_16bit: + add w2, w2, #8 + +loop_arm_memset_16bit: + strh w1, [x0], #2 + subs w2, w2, #1 + bne loop_arm_memset_16bit + ret + +end_func3: + ret + + + diff --git a/dependencies/ih264d/common/armv8/ih264_neon_macros.s b/dependencies/ih264d/common/armv8/ih264_neon_macros.s new file mode 100644 index 00000000..6ff5b916 --- /dev/null +++ b/dependencies/ih264d/common/armv8/ih264_neon_macros.s @@ -0,0 +1,41 @@ +//****************************************************************************** +//* +//* Copyright (C) 2015 The Android Open Source Project +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at: +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. +//* +//***************************************************************************** +//* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +//*/ +//******************************************************************************* + + +.macro push_v_regs + stp d8, d9, [sp, #-16]! + stp d10, d11, [sp, #-16]! + stp d12, d13, [sp, #-16]! + stp d14, d15, [sp, #-16]! +.endm +.macro pop_v_regs + ldp d14, d15, [sp], #16 + ldp d12, d13, [sp], #16 + ldp d10, d11, [sp], #16 + ldp d8, d9, [sp], #16 +.endm + +.macro swp reg1, reg2 + eor \reg1, \reg1, \reg2 + eor \reg2, \reg1, \reg2 + eor \reg1, \reg1, \reg2 +.endm + diff --git a/dependencies/ih264d/common/armv8/ih264_padding_neon_av8.s b/dependencies/ih264d/common/armv8/ih264_padding_neon_av8.s new file mode 100644 index 00000000..e03fe2fb --- /dev/null +++ b/dependencies/ih264d/common/armv8/ih264_padding_neon_av8.s @@ -0,0 +1,736 @@ +//****************************************************************************** +//* +//* Copyright (C) 2015 The Android Open Source Project +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at: +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. +//* +//***************************************************************************** +//* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +//*/ +///** +// ******************************************************************************* +// * @file +// * ih264_padding_neon.s +// * +// * @brief +// * Contains function definitions padding +// * +// * @author +// * Ittiam +// * +// * @par List of Functions: +// * - ih264_pad_top_av8() +// * - ih264_pad_left_luma_av8() +// * - ih264_pad_left_chroma_av8() +// * - ih264_pad_right_luma_av8() +// * - ih264_pad_right_chroma_av8() +// * +// * @remarks +// * None +// * +// ******************************************************************************* +//*/ + +.text +.p2align 2 +.include "ih264_neon_macros.s" +///** +//******************************************************************************* +//* +//* @brief pad at the top of a 2d array +//* +//* @par Description: +//* The top row of a 2d array is replicated for pad_size times at the top +//* +//* @param[in] pu1_src +//* UWORD8 pointer to the source +//* +//* @param[in] src_strd +//* integer source stride +//* +//* @param[in] wd +//* integer width of the array +//* +//* @param[in] pad_size +//* integer -padding size of the array +//* +//* @returns none +//* +//* @remarks none +//* +//******************************************************************************* +//*/ +//void ih264_pad_top(UWORD8 *pu1_src, +// WORD32 src_strd, +// WORD32 wd, +// WORD32 pad_size) +//**************Variables Vs Registers************************* +// x0 => *pu1_src +// w1 => src_strd +// w2 => wd +// w3 => pad_size + + .global ih264_pad_top_av8 + +ih264_pad_top_av8: + + // STMFD sp!, {x4-x11,x14} //stack stores the values of the arguments + push_v_regs + sxtw x1, w1 + stp x19, x20, [sp, #-16]! + + sub x5, x0, x1 + neg x6, x1 + +loop_neon_memcpy_mul_16: + // Load 16 bytes + ld1 {v0.8b, v1.8b}, [x0], #16 + mov x4, x5 + mov w7, w3 + add x5, x5, #16 + +loop_neon_pad_top: + st1 {v0.8b, v1.8b}, [x4], x6 + subs w7, w7, #1 + bne loop_neon_pad_top + + subs w2, w2, #16 + bne loop_neon_memcpy_mul_16 + + // LDMFD sp!,{x4-x11,pc} //Reload the registers from SP + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + + + +///** +//******************************************************************************* +//* +//* @brief +//* Padding (luma block) at the left of a 2d array +//* +//* @par Description: +//* The left column of a 2d array is replicated for pad_size times at the left +//* +//* +//* @param[in] pu1_src +//* UWORD8 pointer to the source +//* +//* @param[in] src_strd +//* integer source stride +//* +//* @param[in] ht +//* integer height of the array +//* +//* @param[in] wd +//* integer width of the array +//* +//* @param[in] pad_size +//* integer -padding size of the array +//* +//* @param[in] ht +//* integer height of the array +//* +//* @param[in] wd +//* integer width of the array +//* +//* @returns +//* +//* @remarks +//* None +//* +//******************************************************************************* +//*/ +//#if PAD_LEFT_LUMA == C +//void ih264_pad_left_luma(UWORD8 *pu1_src, +// WORD32 src_strd, +// WORD32 ht, +// WORD32 pad_size) +//**************Variables Vs Registers************************* +// x0 => *pu1_src +// w1 => src_strd +// w2 => ht +// w3 => pad_size + + + + .global ih264_pad_left_luma_av8 + +ih264_pad_left_luma_av8: + + // STMFD sp!, {x4-x11,x14} //stack stores the values of the arguments + push_v_regs + sxtw x1, w1 + sxtw x3, w3 + stp x19, x20, [sp, #-16]! + + + sub x4, x0, x3 + sub x6, x1, #16 + subs x5, x3, #16 + bne loop_32 +loop_16: // /*hard coded for width=16 ,height =8,16*/ + ldrb w8, [x0] + add x0, x0, x1 + ldrb w9, [x0] + add x0, x0, x1 + dup v0.16b, w8 + ldrb w10, [x0] + add x0, x0, x1 + st1 {v0.16b}, [x4], x1 // 16 bytes store + dup v2.16b, w9 + st1 {v2.16b}, [x4], x1 // 16 bytes store + ldrb w11, [x0] + add x0, x0, x1 + dup v4.16b, w10 + dup v6.16b, w11 + st1 {v4.16b}, [x4], x1 // 16 bytes store + ldrb w8, [x0] + add x0, x0, x1 + st1 {v6.16b}, [x4], x1 // 16 bytes store + ldrb w9, [x0] + add x0, x0, x1 + dup v0.16b, w8 + ldrb w10, [x0] + add x0, x0, x1 + st1 {v0.16b}, [x4], x1 // 16 bytes store + dup v2.16b, w9 + ldrb w11, [x0] + add x0, x0, x1 + st1 {v2.16b}, [x4], x1 // 16 bytes store + dup v4.16b, w10 + dup v6.16b, w11 + subs w2, w2, #8 + st1 {v4.16b}, [x4], x1 // 16 bytes store + st1 {v6.16b}, [x4], x1 // 16 bytes store + bne loop_16 + b end_func + +loop_32: // /*hard coded for width=32 ,height =8,16*/ + ldrb w8, [x0] + add x0, x0, x1 + ldrb w9, [x0] + add x0, x0, x1 + dup v0.16b, w8 + ldrb w10, [x0] + add x0, x0, x1 + st1 {v0.16b}, [x4], #16 // 16 bytes store + dup v2.16b, w9 + st1 {v0.16b}, [x4], x6 + st1 {v2.16b}, [x4], #16 // 16 bytes store + dup v4.16b, w10 + st1 {v2.16b}, [x4], x6 // 16 bytes store + ldrb w11, [x0] + add x0, x0, x1 + st1 {v4.16b}, [x4], #16 // 16 bytes store + dup v6.16b, w11 + st1 {v4.16b}, [x4], x6 // 16 bytes store + ldrb w8, [x0] + add x0, x0, x1 + st1 {v6.16b}, [x4], #16 // 16 bytes store + dup v0.16b, w8 + ldrb w9, [x0] + add x0, x0, x1 + st1 {v6.16b}, [x4], x6 // 16 bytes store + ldrb w10, [x0] + add x0, x0, x1 + st1 {v0.16b}, [x4], #16 // 16 bytes store + dup v2.16b, w9 + st1 {v0.16b}, [x4], x6 // 16 bytes store + ldrb w11, [x0] + add x0, x0, x1 + st1 {v2.16b}, [x4], #16 // 16 bytes store + dup v4.16b, w10 + st1 {v2.16b}, [x4], x6 // 16 bytes store + st1 {v4.16b}, [x4], #16 // 16 bytes store + dup v6.16b, w11 + st1 {v4.16b}, [x4], x6 // 16 bytes store + subs w2, w2, #8 + st1 {v6.16b}, [x4], #16 // 16 bytes store + st1 {v6.16b}, [x4], x6 // 16 bytes store + bne loop_32 + + + +end_func: + // LDMFD sp!,{x4-x11,pc} //Reload the registers from SP + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + + + + +///** +//******************************************************************************* +//* +//* @brief +//* Padding (chroma block) at the left of a 2d array +//* +//* @par Description: +//* The left column of a 2d array is replicated for pad_size times at the left +//* +//* +//* @param[in] pu1_src +//* UWORD8 pointer to the source +//* +//* @param[in] src_strd +//* integer source stride +//* +//* @param[in] ht +//* integer height of the array +//* +//* @param[in] wd +//* integer width of the array (each colour component) +//* +//* @param[in] pad_size +//* integer -padding size of the array +//* +//* @param[in] ht +//* integer height of the array +//* +//* @param[in] wd +//* integer width of the array +//* +//* @returns +//* +//* @remarks +//* None +//* +//******************************************************************************* +//*/ +//#if PAD_LEFT_CHROMA == C +//void ih264_pad_left_chroma(UWORD8 *pu1_src, +// WORD32 src_strd, +// WORD32 ht, +// WORD32 pad_size) +//{ +// x0 => *pu1_src +// w1 => src_strd +// w2 => ht +// w3 => pad_size + + + + .global ih264_pad_left_chroma_av8 + +ih264_pad_left_chroma_av8: + + // STMFD sp!, {x4-x11, x14} //stack stores the values of the arguments + push_v_regs + sxtw x1, w1 + sxtw x3, w3 + stp x19, x20, [sp, #-16]! + + sub x4, x0, x3 + sub x6, x1, #16 + + +loop_32_l_c: // /*hard coded for width=32 ,height =4,8,12*/ + ldrh w8, [x0] + add x0, x0, x1 + ldrh w9, [x0] + add x0, x0, x1 + dup v0.8h, w8 + ldrh w10, [x0] + add x0, x0, x1 + st1 {v0.16b}, [x4], #16 // 16 bytes store + dup v2.8h, w9 + st1 {v0.16b}, [x4], x6 // 16 bytes store + ldrh w11, [x0] + add x0, x0, x1 + st1 {v2.16b}, [x4], #16 // 16 bytes store + dup v4.8h, w10 + st1 {v2.16b}, [x4], x6 // 16 bytes store + dup v6.8h, w11 + st1 {v4.16b}, [x4], #16 // 16 bytes store + st1 {v4.16b}, [x4], x6 // 16 bytes store + subs w2, w2, #4 + st1 {v6.16b}, [x4], #16 // 16 bytes store + st1 {v6.16b}, [x4], x6 // 16 bytes store + + + beq end_func_l_c ///* Branching when ht=4*/ + + ldrh w8, [x0] + add x0, x0, x1 + ldrh w9, [x0] + add x0, x0, x1 + dup v0.8h, w8 + ldrh w10, [x0] + add x0, x0, x1 + st1 {v0.16b}, [x4], #16 // 16 bytes store + dup v2.8h, w9 + st1 {v0.16b}, [x4], x6 + ldrh w11, [x0] + add x0, x0, x1 + st1 {v2.16b}, [x4], #16 // 16 bytes store + dup v4.8h, w10 + st1 {v2.16b}, [x4], x6 // 16 bytes store + dup v6.8h, w11 + st1 {v4.16b}, [x4], #16 // 16 bytes store + st1 {v4.16b}, [x4], x6 // 16 bytes store + subs w2, w2, #4 + st1 {v6.16b}, [x4], #16 // 16 bytes store + st1 {v6.16b}, [x4], x6 // 16 bytes store + + beq end_func_l_c ///* Branching when ht=8*/ + bne loop_32_l_c + + ldrh w8, [x0] + add x0, x0, x1 + ldrh w9, [x0] + add x0, x0, x1 + dup v0.8h, w8 + ldrh w10, [x0] + add x0, x0, x1 + st1 {v0.16b}, [x4], #16 // 16 bytes store + dup v2.8h, w9 + st1 {v0.16b}, [x4], x6 + ldrh w11, [x0] + add x0, x0, x1 + st1 {v2.16b}, [x4], #16 // 16 bytes store + dup v4.8h, w10 + st1 {v2.16b}, [x4], x6 // 16 bytes store + dup v6.8h, w11 + st1 {v4.16b}, [x4], #16 // 16 bytes store + st1 {v4.16b}, [x4], x6 // 16 bytes store + st1 {v6.16b}, [x4], #16 // 16 bytes store + st1 {v6.16b}, [x4], x6 // 16 bytes store + +end_func_l_c: + // LDMFD sp!,{x4-x11,pc} //Reload the registers from SP + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + + + + +///** +//******************************************************************************* +//* +//* @brief +//* Padding (luma block) at the right of a 2d array +//* +//* @par Description: +//* The right column of a 2d array is replicated for pad_size times at the right +//* +//* +//* @param[in] pu1_src +//* UWORD8 pointer to the source +//* +//* @param[in] src_strd +//* integer source stride +//* +//* @param[in] ht +//* integer height of the array +//* +//* @param[in] wd +//* integer width of the array +//* +//* @param[in] pad_size +//* integer -padding size of the array +//* +//* @param[in] ht +//* integer height of the array +//* +//* @param[in] wd +//* integer width of the array +//* +//* @returns +//* +//* @remarks +//* None +//* +//******************************************************************************* +//*/ +//#if PAD_RIGHT_LUMA == C +//void ih264_pad_right_luma(UWORD8 *pu1_src, +// WORD32 src_strd, +// WORD32 ht, +// WORD32 pad_size) +//{ +// WORD32 row; +// +// for(row = 0; row < ht; row++) +// { +// memset(pu1_src, *(pu1_src -1), pad_size); +// +// pu1_src += src_strd; +// } +//} +// +// x0 => *pu1_src +// w1 => src_strd +// w2 => ht +// w3 => pad_size + + + + .global ih264_pad_right_luma_av8 + +ih264_pad_right_luma_av8: + + // STMFD sp!, {x4-x11, x14} //stack stores the values of the arguments + push_v_regs + sxtw x1, w1 + sxtw x3, w3 + stp x19, x20, [sp, #-16]! + + mov x4, x0 + sub x6, x1, #16 + sub x0, x0, #1 + subs x5, x3, #16 + bne loop_32 +loop_16_r: // /*hard coded for width=16 ,height =8,16*/ + ldrb w8, [x0] + add x0, x0, x1 + ldrb w9, [x0] + add x0, x0, x1 + dup v0.16b, w8 + ldrb w10, [x0] + add x0, x0, x1 + st1 {v0.16b}, [x4], x1 // 16 bytes store + dup v2.16b, w9 + st1 {v2.16b}, [x4], x1 // 16 bytes store + ldrb w11, [x0] + add x0, x0, x1 + dup v4.16b, w10 + dup v6.16b, w11 + st1 {v4.16b}, [x4], x1 // 16 bytes store + ldrb w8, [x0] + add x0, x0, x1 + st1 {v6.16b}, [x4], x1 // 16 bytes store + ldrb w9, [x0] + add x0, x0, x1 + dup v0.16b, w8 + ldrb w10, [x0] + add x0, x0, x1 + st1 {v0.16b}, [x4], x1 // 16 bytes store + dup v2.16b, w9 + ldrb w11, [x0] + add x0, x0, x1 + st1 {v2.16b}, [x4], x1 // 16 bytes store + dup v4.16b, w10 + dup v6.16b, w11 + subs w2, w2, #8 + st1 {v4.16b}, [x4], x1 // 16 bytes store + st1 {v6.16b}, [x4], x1 // 16 bytes store + bne loop_16_r + b end_func_r + +loop_32_r: // /*hard coded for width=32 ,height =8,16*/ + ldrb w8, [x0] + add x0, x0, x1 + ldrb w9, [x0] + add x0, x0, x1 + dup v0.16b, w8 + ldrb w10, [x0] + add x0, x0, x1 + st1 {v0.16b}, [x4], #16 // 16 bytes store + dup v2.16b, w9 + st1 {v0.16b}, [x4], x6 + st1 {v2.16b}, [x4], #16 // 16 bytes store + dup v4.16b, w10 + st1 {v2.16b}, [x4], x6 // 16 bytes store + ldrb w11, [x0] + add x0, x0, x1 + st1 {v4.16b}, [x4], #16 // 16 bytes store + dup v6.16b, w11 + st1 {v4.16b}, [x4], x6 // 16 bytes store + ldrb w8, [x0] + add x0, x0, x1 + st1 {v6.16b}, [x4], #16 // 16 bytes store + ldrb w9, [x0] + add x0, x0, x1 + dup v0.16b, w8 + st1 {v6.16b}, [x4], x6 // 16 bytes store + ldrb w10, [x0] + add x0, x0, x1 + st1 {v0.16b}, [x4], #16 // 16 bytes store + dup v2.16b, w9 + st1 {v0.16b}, [x4], x6 // 16 bytes store + ldrb w11, [x0] + add x0, x0, x1 + st1 {v2.16b}, [x4], #16 // 16 bytes store + dup v4.16b, w10 + st1 {v2.16b}, [x4], x6 // 16 bytes store + st1 {v4.16b}, [x4], #16 // 16 bytes store + dup v6.16b, w11 + st1 {v4.16b}, [x4], x6 // 16 bytes store + subs w2, w2, #8 + st1 {v6.16b}, [x4], #16 // 16 bytes store + st1 {v6.16b}, [x4], x6 // 16 bytes store + bne loop_32_r + + + +end_func_r: + // LDMFD sp!,{x4-x11,pc} //Reload the registers from SP + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + + + + +///** +//******************************************************************************* +//* +//* @brief +//;* Padding (chroma block) at the right of a 2d array +//* +//* @par Description: +//* The right column of a 2d array is replicated for pad_size times at the right +//* +//* +//* @param[in] pu1_src +//;* UWORD8 pointer to the source +//* +//* @param[in] src_strd +//* integer source stride +//* +//* @param[in] ht +//;* integer height of the array +//* +//* @param[in] wd +//* integer width of the array (each colour component) +//* +//* @param[in] pad_size +//* integer -padding size of the array +//* +//* @param[in] ht +//;* integer height of the array +//* +//* @param[in] wd +//* integer width of the array +//* +//* @returns +//* +//* @remarks +//* None +//* +//******************************************************************************* +//*/ +//#if PAD_RIGHT_CHROMA == C +//void ih264_pad_right_chroma(UWORD8 *pu1_src, +// WORD32 src_strd, +// WORD32 ht, +// WORD32 pad_size) +// x0 => *pu1_src +// w1 => src_strd +// w2 => ht +// w3 => pad_size + + + + .global ih264_pad_right_chroma_av8 + +ih264_pad_right_chroma_av8: + + // STMFD sp!, {x4-x11, x14} //stack stores the values of the arguments + push_v_regs + sxtw x1, w1 + sxtw x3, w3 + stp x19, x20, [sp, #-16]! + + mov x4, x0 + sub x6, x1, #16 + sub x0, x0, #2 +loop_32_r_c: // /*hard coded for width=32 ,height =8,4*/ + ldrh w8, [x0] + add x0, x0, x1 + ldrh w9, [x0] + add x0, x0, x1 + dup v0.8h, w8 + ldrh w10, [x0] + add x0, x0, x1 + st1 {v0.16b}, [x4], #16 // 16 bytes store + dup v2.8h, w9 + st1 {v0.16b}, [x4], x6 + st1 {v2.16b}, [x4], #16 // 16 bytes store + dup v4.8h, w10 + st1 {v2.16b}, [x4], x6 // 16 bytes store + subs w2, w2, #4 + ldrh w11, [x0] + add x0, x0, x1 + st1 {v4.16b}, [x4], #16 // 16 bytes store + dup v6.8h, w11 + st1 {v4.16b}, [x4], x6 // 16 bytes store + st1 {v6.16b}, [x4], #16 // 16 bytes store + st1 {v6.16b}, [x4], x6 // 16 bytes store + + beq end_func_r_c ///* Branching when ht=4*/ + + ldrh w8, [x0] + add x0, x0, x1 + dup v0.8h, w8 + ldrh w9, [x0] + add x0, x0, x1 + ldrh w10, [x0] + add x0, x0, x1 + st1 {v0.16b}, [x4], #16 // 16 bytes store + dup v2.8h, w9 + st1 {v0.16b}, [x4], x6 // 16 bytes store + ldrh w11, [x0] + add x0, x0, x1 + st1 {v2.16b}, [x4], #16 // 16 bytes store + dup v4.8h, w10 + st1 {v2.16b}, [x4], x6 // 16 bytes store + st1 {v4.16b}, [x4], #16 // 16 bytes store + dup v6.8h, w11 + st1 {v4.16b}, [x4], x6 // 16 bytes store + subs w2, w2, #4 + st1 {v6.16b}, [x4], #16 // 16 bytes store + st1 {v6.16b}, [x4], x6 // 16 bytes store + + beq end_func_r_c ///* Branching when ht=8*/ + bne loop_32_r_c + ldrh w8, [x0] + add x0, x0, x1 + dup v0.8h, w8 + ldrh w9, [x0] + add x0, x0, x1 + ldrh w10, [x0] + add x0, x0, x1 + st1 {v0.16b}, [x4], #16 // 16 bytes store + dup v2.8h, w9 + st1 {v0.16b}, [x4], x6 // 16 bytes store + ldrh w11, [x0] + add x0, x0, x1 + st1 {v2.16b}, [x4], #16 // 16 bytes store + dup v4.8h, w10 + st1 {v2.16b}, [x4], x6 // 16 bytes store + st1 {v4.16b}, [x4], #16 // 16 bytes store + dup v6.8h, w11 + st1 {v4.16b}, [x4], x6 // 16 bytes store + st1 {v6.16b}, [x4], #16 // 16 bytes store + st1 {v6.16b}, [x4], x6 // 16 bytes store + +end_func_r_c: + // LDMFD sp!,{x4-x11,pc} //Reload the registers from SP + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + + + + + diff --git a/dependencies/ih264d/common/armv8/ih264_platform_macros.h b/dependencies/ih264d/common/armv8/ih264_platform_macros.h new file mode 100644 index 00000000..a4d7f3a7 --- /dev/null +++ b/dependencies/ih264d/common/armv8/ih264_platform_macros.h @@ -0,0 +1,181 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** +******************************************************************************* +* @file +* ih264_platform_macros.h +* +* @brief +* Platform specific Macro definitions used in the codec +* +* @author +* Ittiam +* +* @remarks +* None +* +******************************************************************************* +*/ +#ifndef _IH264_PLATFORM_MACROS_H_ +#define _IH264_PLATFORM_MACROS_H_ + +#include + +#ifndef ARMV8 + +static __inline WORD32 CLIP_U8(WORD32 x) +{ + asm("usat %0, #8, %1" : "=r"(x) : "r"(x)); + return x; +} + +static __inline WORD32 CLIP_S8(WORD32 x) +{ + asm("ssat %0, #8, %1" : "=r"(x) : "r"(x)); + return x; +} + +static __inline WORD32 CLIP_U10(WORD32 x) +{ + asm("usat %0, #10, %1" : "=r"(x) : "r"(x)); + return x; +} + +static __inline WORD32 CLIP_S10(WORD32 x) +{ + asm("ssat %0, #10, %1" : "=r"(x) : "r"(x)); + return x; +} + +static __inline WORD32 CLIP_U11(WORD32 x) +{ + asm("usat %0, #11, %1" : "=r"(x) : "r"(x)); + return x; +} + +static __inline WORD32 CLIP_S11(WORD32 x) +{ + asm("ssat %0, #11, %1" : "=r"(x) : "r"(x)); + return x; +} + +static __inline WORD32 CLIP_U12(WORD32 x) +{ + asm("usat %0, #12, %1" : "=r"(x) : "r"(x)); + return x; +} + +static __inline WORD32 CLIP_S12(WORD32 x) +{ + asm("ssat %0, #12, %1" : "=r"(x) : "r"(x)); + return x; +} + +static __inline WORD32 CLIP_U16(WORD32 x) +{ + asm("usat %0, #16, %1" : "=r"(x) : "r"(x)); + return x; +} +static __inline WORD32 CLIP_S16(WORD32 x) +{ + asm("ssat %0, #16, %1" : "=r"(x) : "r"(x)); + return x; +} + + +static __inline UWORD32 ITT_BIG_ENDIAN(UWORD32 x) +{ + asm("rev %0, %1" : "=r"(x) : "r"(x)); + return x; +} +#define NOP(nop_cnt) {UWORD32 nop_i; for (nop_i = 0; nop_i < nop_cnt; nop_i++) asm("nop");} + +#else + +#define CLIP_U8(x) CLIP3(0, UINT8_MAX, (x)) +#define CLIP_S8(x) CLIP3(INT8_MIN, INT8_MAX, (x)) + +#define CLIP_U10(x) CLIP3(0, 1023, (x)) +#define CLIP_S10(x) CLIP3(-512, 511, (x)) + +#define CLIP_U11(x) CLIP3(0, 2047, (x)) +#define CLIP_S11(x) CLIP3(-1024, 1023, (x)) + +#define CLIP_U12(x) CLIP3(0, 4095, (x)) +#define CLIP_S12(x) CLIP3(-2048, 2047, (x)) + +#define CLIP_U16(x) CLIP3(0, UINT16_MAX, (x)) +#define CLIP_S16(x) CLIP3(INT16_MIN, INT16_MAX, (x)) + +#define ITT_BIG_ENDIAN(x) __asm__("rev %0, %1" : "=r"(x) : "r"(x)); + +#define NOP(nop_cnt) \ +{ \ + UWORD32 nop_i; \ + for (nop_i = 0; nop_i < nop_cnt; nop_i++) \ + __asm__ __volatile__("mov x0, x0"); \ +} + +#endif + +/*saturating instructions are not available for WORD64 in ARMv7, hence we cannot + * use inline assembly like other clips*/ +#define CLIP_U32(x) CLIP3(0, UINT32_MAX, (x)) +#define CLIP_S32(x) CLIP3(INT32_MIN, INT32_MAX, (x)) + +#define DATA_SYNC() __sync_synchronize() + +#define SHL(x,y) (((y) < 32) ? ((x) << (y)) : 0) +#define SHR(x,y) (((y) < 32) ? ((x) >> (y)) : 0) + +#define SHR_NEG(val,shift) ((shift>0)?(val>>shift):(val<<(-shift))) +#define SHL_NEG(val,shift) ((shift<0)?(val>>(-shift)):(val<> 1; + shrn2 v0.8h, v23.4s, #1 //i4_value = (x3 + x2) >> 1; + shrn v1.4h, v24.4s, #1 //i4_value = (x0 - x1) >> 1; + shrn2 v1.8h, v25.4s, #1 //i4_value = (x3 - x2) >> 1; + + abs v2.8h, v0.8h + abs v3.8h, v1.8h + + cmgt v4.8h, v0.8h, #0 //get the sign row 1,2 + cmgt v5.8h, v1.8h, #0 + + neg w4, w4 //-u4_qbits + dup v22.4s, w4 //load -u4_qbits + + umlal v14.4s, v2.4h, v30.4h + umlal2 v15.4s, v2.8h, v30.8h + umlal v16.4s, v3.4h, v30.4h + umlal2 v17.4s, v3.8h, v30.8h + + ushl v14.4s, v14.4s, v22.4s + ushl v15.4s, v15.4s, v22.4s + ushl v16.4s, v16.4s, v22.4s + ushl v17.4s, v17.4s, v22.4s + + uqxtn v14.4h, v14.4s + uqxtn2 v14.8h, v15.4s + uqxtn v16.4h, v16.4s + uqxtn2 v16.8h, v17.4s + + neg v15.8h, v14.8h + neg v17.8h, v16.8h + + bsl v4.16b, v14.16b, v15.16b + bsl v5.16b, v16.16b, v17.16b + + cmeq v0.8h, v14.8h, #0 + cmeq v1.8h, v16.8h, #0 + + st1 {v4.8h-v5.8h}, [x1] + + movi v20.8b, #16 + + xtn v2.8b, v0.8h + xtn v3.8b, v1.8h + + ushr v2.8b, v2.8b, #7 + ushr v3.8b, v3.8b, #7 + + add v2.8b, v2.8b, v3.8b + addp v2.8b, v2.8b, v2.8b + addp v2.8b, v2.8b, v2.8b + addp v2.8b, v2.8b, v2.8b + sub v20.8b, v20.8b, v2.8b + st1 {v20.b}[0], [x6] + + pop_v_regs + ret + + +//***************************************************************************** +//* +//* function name : ih264_hadamard_quant_2x2_uv +//* description : this function does forward hadamard transform and +//* quantization for dc block of chroma for both planes +//* +//* arguments : x0 :pointer to src buffer +// x1 :pointer to dst buffer +// x2 :pu2_scale_matrix +// x3 :pu2_threshold_matrix +// w4 :u4_qbits +// w5 :u4_round_factor +// x6 :pu1_nnz +// values returned : none +// +// register usage : +// stack usage : 0 bytes +// cycles : around +// interruptiaility : interruptable +// +// known limitations +// \assumptions : +// +// revision history : +// dd mm yyyy author(s) changes +// 20 2 2015 100633 first version +// +//***************************************************************************** +// ih264_hadamard_quant_2x2_uv_av8(word16 *pi2_src, word16 *pi2_dst, +// const uword16 *pu2_scale_matrix, +// const uword16 *pu2_threshold_matrix, uword32 u4_qbits, +// uword32 u4_round_factor,uword8 *pu1_nnz +// ) + + .global ih264_hadamard_quant_2x2_uv_av8 +ih264_hadamard_quant_2x2_uv_av8: + + push_v_regs + + ld2 {v0.4h-v1.4h}, [x0] //load src + + ld1 {v30.h}[0], [x2] //load pu2_scale_matrix[0] + dup v30.4h, v30.h[0] //pu2_scale_matrix + uxtl v30.4s, v30.4h //pu2_scale_matrix + + neg w4, w4 + dup v24.4s, w4 //u4_qbits + + dup v25.4s, w5 //round fact + dup v26.4s, v25.s[0] + + saddl v2.4s, v0.4h, v1.4h //x0 = x4 + x5;, x2 = x6 + x7; + ssubl v3.4s, v0.4h, v1.4h //x1 = x4 - x5; x3 = x6 - x7; + + trn1 v4.4s, v2.4s, v3.4s + trn2 v5.4s, v2.4s, v3.4s //q1 -> x0 x1, q2 -> x2 x3 + + add v0.4s, v4.4s , v5.4s // (x0 + x2) (x1 + x3) (y0 + y2); (y1 + y3); + sub v1.4s, v4.4s , v5.4s // (x0 - x2) (x1 - x3) (y0 - y2); (y1 - y3); + + abs v2.4s, v0.4s + abs v3.4s, v1.4s + + cmgt v4.4s, v0.4s, #0 //get the sign row 1,2 + cmgt v5.4s, v1.4s, #0 + + uqxtn v4.4h, v4.4s + sqxtn2 v4.8h, v5.4s + + mla v25.4s, v2.4s, v30.4s + mla v26.4s, v3.4s, v30.4s + + ushl v2.4s, v25.4s, v24.4s //>>qbit + ushl v3.4s, v26.4s, v24.4s //>>qbit + + uqxtn v2.4h, v2.4s + uqxtn2 v2.8h, v3.4s + + neg v5.8h, v2.8h + + bsl v4.16b, v2.16b, v5.16b //*sign + + //rearrange such that we get each plane coeffs as continous + mov v5.s[0], v4.s[1] + mov v4.s[1], v4.s[2] + mov v4.s[2], v5.s[0] + + cmeq v5.8h, v4.8h, #0 //compute nnz + xtn v5.8b, v5.8h //reduce nnz comparison to 1 bit + ushr v5.8b, v5.8b, #7 //reduce nnz comparison to 1 bit + movi v20.8b, #4 //since we add zeros, we need to subtract from 4 to get nnz + addp v5.8b, v5.8b, v5.8b //sum up nnz + addp v5.8b, v5.8b, v5.8b //sum up nnz + + st1 {v4.8h}, [x1] //store the block + + st1 {v4.8h}, [x1] //store the block + sub v20.8b, v20.8b, v5.8b //4- numzeros + + st1 {v20.h}[0], [x6] //store nnz + + pop_v_regs + ret + + + diff --git a/dependencies/ih264d/common/armv8/ih264_weighted_bi_pred_av8.s b/dependencies/ih264d/common/armv8/ih264_weighted_bi_pred_av8.s new file mode 100644 index 00000000..475f690e --- /dev/null +++ b/dependencies/ih264d/common/armv8/ih264_weighted_bi_pred_av8.s @@ -0,0 +1,573 @@ +//****************************************************************************** +//* +//* Copyright (C) 2015 The Android Open Source Project +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at: +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. +//* +//***************************************************************************** +//* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +//*/ +///** +//****************************************************************************** +//* @file +//* ih264_weighted_bi_pred_av8.s +//* +//* @brief +//* Contains function definitions for weighted biprediction. +//* +//* @author +//* Kaushik Senthoor R +//* +//* @par List of Functions: +//* +//* - ih264_weighted_bi_pred_luma_av8() +//* - ih264_weighted_bi_pred_chroma_av8() +//* +//* @remarks +//* None +//* +//******************************************************************************* +//*/ +//******************************************************************************* +//* @function +//* ih264_weighted_bi_pred_luma_av8() +//* +//* @brief +//* This routine performs the default weighted prediction as described in sec +//* 8.4.2.3.2 titled "Weighted sample prediction process" for luma. +//* +//* @par Description: +//* This function gets two ht x wd blocks, calculates the weighted samples, +//* rounds off, adds offset and stores it in the destination block. +//* +//* @param[in] puc_src1 +//* UWORD8 Pointer to the buffer containing the input block 1. +//* +//* @param[in] puc_src2 +//* UWORD8 Pointer to the buffer containing the input block 2. +//* +//* @param[out] puc_dst +//* UWORD8 pointer to the destination where the output block is stored. +//* +//* @param[in] src_strd1 +//* Stride of the input buffer 1 +//* +//* @param[in] src_strd2 +//* Stride of the input buffer 2 +//* +//* @param[in] dst_strd +//* Stride of the destination buffer +//* +//* @param[in] log_WD +//* number of bits to be rounded off +//* +//* @param[in] wt1 +//* weight for the weighted prediction +//* +//* @param[in] wt2 +//* weight for the weighted prediction +//* +//* @param[in] ofst1 +//* offset 1 used after rounding off +//* +//* @param[in] ofst2 +//* offset 2 used after rounding off +//* +//* @param[in] ht +//* integer height of the array +//* +//* @param[in] wd +//* integer width of the array +//* +//* @returns +//* None +//* +//* @remarks +//* (ht,wd) can be (4,4), (4,8), (8,4), (8,8), (8,16), (16,8) or (16,16). +//* +//******************************************************************************* +//*/ +//void ih264_weighted_bi_pred_luma_av8(UWORD8 *puc_src1, +// UWORD8 *puc_src2, +// UWORD8 *puc_dst, +// WORD32 src_strd1, +// WORD32 src_strd2, +// WORD32 dst_strd, +// WORD32 log_WD, +// WORD32 wt1, +// WORD32 wt2, +// WORD16 ofst1, +// WORD16 ofst2, +// WORD32 ht, +// WORD32 wd) +// +//**************Variables Vs Registers***************************************** +// x0 => puc_src1 +// x1 => puc_src2 +// x2 => puc_dst +// w3 => src_strd1 +// w4 => src_strd2 +// w5 => dst_strd +// w6 => log_WD +// w7 => wt1 +// [sp] => wt2 (w8) +// [sp+8] => ofst1 (w9) +// [sp+16] => ofst2 (w10) +// [sp+24] => ht (w11) +// [sp+32] => wd (w12) +// +.text +.p2align 2 +.include "ih264_neon_macros.s" + + + + .global ih264_weighted_bi_pred_luma_av8 + +ih264_weighted_bi_pred_luma_av8: + + // STMFD sp!, {x4-x12,x14} //stack stores the values of the arguments + push_v_regs + sxtw x3, w3 + sxtw x4, w4 + sxtw x5, w5 + stp x19, x20, [sp, #-16]! + ldr w8, [sp, #80] //Load wt2 in w8 + ldr w9, [sp, #88] //Load ofst1 in w9 + add w6, w6, #1 //w6 = log_WD + 1 + neg w10, w6 //w10 = -(log_WD + 1) + dup v0.8h, w10 //Q0 = -(log_WD + 1) (32-bit) + ldr w10, [sp, #96] //Load ofst2 in w10 + ldr w11, [sp, #104] //Load ht in w11 + ldr w12, [sp, #112] //Load wd in w12 + add w9, w9, #1 //w9 = ofst1 + 1 + add w9, w9, w10 //w9 = ofst1 + ofst2 + 1 + mov v2.s[0], w7 + mov v2.s[1], w8 //D2 = {wt1(32-bit), wt2(32-bit)} + asr w9, w9, #1 //w9 = ofst = (ofst1 + ofst2 + 1) >> 1 + dup v3.8b, w9 //D3 = ofst (8-bit) + cmp w12, #16 + beq loop_16 //branch if wd is 16 + cmp w12, #8 //check if wd is 8 + beq loop_8 //branch if wd is 8 + +loop_4: //each iteration processes four rows + + ld1 {v4.s}[0], [x0], x3 //load row 1 in source 1 + ld1 {v4.s}[1], [x0], x3 //load row 2 in source 1 + ld1 {v6.s}[0], [x1], x4 //load row 1 in source 2 + ld1 {v6.s}[1], [x1], x4 //load row 2 in source 2 + uxtl v4.8h, v4.8b //converting rows 1,2 in source 1 to 16-bit + ld1 {v8.s}[0], [x0], x3 //load row 3 in source 1 + ld1 {v8.s}[1], [x0], x3 //load row 4 in source 1 + uxtl v6.8h, v6.8b //converting rows 1,2 in source 2 to 16-bit + ld1 {v10.s}[0], [x1], x4 //load row 3 in source 2 + ld1 {v10.s}[1], [x1], x4 //load row 4 in source 2 + uxtl v8.8h, v8.8b //converting rows 3,4 in source 1 to 16-bit + uxtl v10.8h, v10.8b //converting rows 3,4 in source 2 to 16-bit + mul v4.8h, v4.8h , v2.h[0] //weight 1 mult. for rows 1,2 + mla v4.8h, v6.8h , v2.h[2] //weight 2 mult. for rows 1,2 + mul v8.8h, v8.8h , v2.h[0] //weight 1 mult. for rows 3,4 + mla v8.8h, v10.8h , v2.h[2] //weight 2 mult. for rows 3,4 + subs w11, w11, #4 //decrement ht by 4 + srshl v4.8h, v4.8h , v0.8h //rounds off the weighted samples from rows 1,2 + srshl v8.8h, v8.8h , v0.8h //rounds off the weighted samples from rows 3,4 + saddw v4.8h, v4.8h , v3.8b //adding offset for rows 1,2 + saddw v8.8h, v8.8h , v3.8b //adding offset for rows 3,4 + sqxtun v4.8b, v4.8h //saturating rows 1,2 to unsigned 8-bit + sqxtun v8.8b, v8.8h //saturating rows 3,4 to unsigned 8-bit + st1 {v4.s}[0], [x2], x5 //store row 1 in destination + st1 {v4.s}[1], [x2], x5 //store row 2 in destination + st1 {v8.s}[0], [x2], x5 //store row 3 in destination + st1 {v8.s}[1], [x2], x5 //store row 4 in destination + bgt loop_4 //if greater than 0 repeat the loop again + b end_loops + +loop_8: //each iteration processes four rows + + ld1 {v4.8b}, [x0], x3 //load row 1 in source 1 + ld1 {v6.8b}, [x1], x4 //load row 1 in source 2 + ld1 {v8.8b}, [x0], x3 //load row 2 in source 1 + ld1 {v10.8b}, [x1], x4 //load row 2 in source 2 + uxtl v4.8h, v4.8b //converting row 1 in source 1 to 16-bit + ld1 {v12.8b}, [x0], x3 //load row 3 in source 1 + ld1 {v14.8b}, [x1], x4 //load row 3 in source 2 + uxtl v6.8h, v6.8b //converting row 1 in source 2 to 16-bit + ld1 {v16.8b}, [x0], x3 //load row 4 in source 1 + ld1 {v18.8b}, [x1], x4 //load row 4 in source 2 + uxtl v8.8h, v8.8b //converting row 2 in source 1 to 16-bit + uxtl v10.8h, v10.8b //converting row 2 in source 2 to 16-bit + mul v4.8h, v4.8h , v2.h[0] //weight 1 mult. for row 1 + mla v4.8h, v6.8h , v2.h[2] //weight 2 mult. for row 1 + uxtl v12.8h, v12.8b //converting row 3 in source 1 to 16-bit + uxtl v14.8h, v14.8b //converting row 3 in source 2 to 16-bit + mul v8.8h, v8.8h , v2.h[0] //weight 1 mult. for row 2 + mla v8.8h, v10.8h , v2.h[2] //weight 2 mult. for row 2 + uxtl v16.8h, v16.8b //converting row 4 in source 1 to 16-bit + uxtl v18.8h, v18.8b //converting row 4 in source 2 to 16-bit + mul v12.8h, v12.8h , v2.h[0] //weight 1 mult. for row 3 + mla v12.8h, v14.8h , v2.h[2] //weight 2 mult. for row 3 + mul v16.8h, v16.8h , v2.h[0] //weight 1 mult. for row 4 + mla v16.8h, v18.8h , v2.h[2] //weight 2 mult. for row 4 + srshl v4.8h, v4.8h , v0.8h //rounds off the weighted samples from row 1 + srshl v8.8h, v8.8h , v0.8h //rounds off the weighted samples from row 2 + srshl v12.8h, v12.8h , v0.8h //rounds off the weighted samples from row 3 + saddw v4.8h, v4.8h , v3.8b //adding offset for row 1 + srshl v16.8h, v16.8h , v0.8h //rounds off the weighted samples from row 4 + saddw v8.8h, v8.8h , v3.8b //adding offset for row 2 + saddw v12.8h, v12.8h , v3.8b //adding offset for row 3 + sqxtun v4.8b, v4.8h //saturating row 1 to unsigned 8-bit + saddw v16.8h, v16.8h , v3.8b //adding offset for row 4 + sqxtun v8.8b, v8.8h //saturating row 2 to unsigned 8-bit + sqxtun v12.8b, v12.8h //saturating row 3 to unsigned 8-bit + sqxtun v16.8b, v16.8h //saturating row 4 to unsigned 8-bit + st1 {v4.8b}, [x2], x5 //store row 1 in destination + st1 {v8.8b}, [x2], x5 //store row 2 in destination + subs w11, w11, #4 //decrement ht by 4 + st1 {v12.8b}, [x2], x5 //store row 3 in destination + st1 {v16.8b}, [x2], x5 //store row 4 in destination + bgt loop_8 //if greater than 0 repeat the loop again + b end_loops + +loop_16: //each iteration processes two rows + + ld1 {v4.8b, v5.8b}, [x0], x3 //load row 1 in source 1 + ld1 {v6.8b, v7.8b}, [x1], x4 //load row 1 in source 2 + ld1 {v8.8b, v9.8b}, [x0], x3 //load row 2 in source 1 + ld1 {v10.8b, v11.8b}, [x1], x4 //load row 2 in source 2 + uxtl v20.8h, v4.8b //converting row 1L in source 1 to 16-bit + ld1 {v12.8b, v13.8b}, [x0], x3 //load row 3 in source 1 + ld1 {v14.8b, v15.8b}, [x1], x4 //load row 3 in source 2 + uxtl v22.8h, v6.8b //converting row 1L in source 2 to 16-bit + ld1 {v16.8b, v17.8b}, [x0], x3 //load row 4 in source 1 + ld1 {v18.8b, v19.8b}, [x1], x4 //load row 4 in source 2 + uxtl v4.8h, v5.8b //converting row 1H in source 1 to 16-bit + uxtl v6.8h, v7.8b //converting row 1H in source 2 to 16-bit + mul v20.8h, v20.8h , v2.h[0] //weight 1 mult. for row 1L + mla v20.8h, v22.8h , v2.h[2] //weight 2 mult. for row 1L + uxtl v24.8h, v8.8b //converting row 2L in source 1 to 16-bit + uxtl v26.8h, v10.8b //converting row 2L in source 2 to 16-bit + mul v4.8h, v4.8h , v2.h[0] //weight 1 mult. for row 1H + mla v4.8h, v6.8h , v2.h[2] //weight 2 mult. for row 1H + uxtl v8.8h, v9.8b //converting row 2H in source 1 to 16-bit + uxtl v10.8h, v11.8b //converting row 2H in source 2 to 16-bit + mul v24.8h, v24.8h , v2.h[0] //weight 1 mult. for row 2L + mla v24.8h, v26.8h , v2.h[2] //weight 2 mult. for row 2L + uxtl v28.8h, v12.8b //converting row 3L in source 1 to 16-bit + uxtl v30.8h, v14.8b //converting row 3L in source 2 to 16-bit + mul v8.8h, v8.8h , v2.h[0] //weight 1 mult. for row 2H + mla v8.8h, v10.8h , v2.h[2] //weight 2 mult. for row 2H + uxtl v12.8h, v13.8b //converting row 3H in source 1 to 16-bit + uxtl v14.8h, v15.8b //converting row 3H in source 2 to 16-bit + mul v28.8h, v28.8h , v2.h[0] //weight 1 mult. for row 3L + mla v28.8h, v30.8h , v2.h[2] //weight 2 mult. for row 3L + uxtl v22.8h, v16.8b //converting row 4L in source 1 to 16-bit + uxtl v6.8h, v18.8b //converting row 4L in source 2 to 16-bit + mul v12.8h, v12.8h , v2.h[0] //weight 1 mult. for row 3H + mla v12.8h, v14.8h , v2.h[2] //weight 2 mult. for row 3H + uxtl v16.8h, v17.8b //converting row 4H in source 1 to 16-bit + uxtl v18.8h, v19.8b //converting row 4H in source 2 to 16-bit + mul v22.8h, v22.8h , v2.h[0] //weight 1 mult. for row 4L + mla v22.8h, v6.8h , v2.h[2] //weight 2 mult. for row 4L + srshl v20.8h, v20.8h , v0.8h //rounds off the weighted samples from row 1L + mul v16.8h, v16.8h , v2.h[0] //weight 1 mult. for row 4H + mla v16.8h, v18.8h , v2.h[2] //weight 2 mult. for row 4H + srshl v4.8h, v4.8h , v0.8h //rounds off the weighted samples from row 1H + srshl v24.8h, v24.8h , v0.8h //rounds off the weighted samples from row 2L + saddw v20.8h, v20.8h , v3.8b //adding offset for row 1L + srshl v8.8h, v8.8h , v0.8h //rounds off the weighted samples from row 2H + saddw v4.8h, v4.8h , v3.8b //adding offset for row 1H + srshl v28.8h, v28.8h , v0.8h //rounds off the weighted samples from row 3L + saddw v24.8h, v24.8h , v3.8b //adding offset for row 2L + srshl v12.8h, v12.8h , v0.8h //rounds off the weighted samples from row 3H + saddw v8.8h, v8.8h , v3.8b //adding offset for row 2H + srshl v22.8h, v22.8h , v0.8h //rounds off the weighted samples from row 4L + saddw v28.8h, v28.8h , v3.8b //adding offset for row 3L + srshl v16.8h, v16.8h , v0.8h //rounds off the weighted samples from row 4H + saddw v12.8h, v12.8h , v3.8b //adding offset for row 3H + sqxtun v26.8b, v20.8h //saturating row 1L to unsigned 8-bit + saddw v22.8h, v22.8h , v3.8b //adding offset for row 4L + sqxtun v27.8b, v4.8h //saturating row 1H to unsigned 8-bit + saddw v16.8h, v16.8h , v3.8b //adding offset for row 4H + sqxtun v10.8b, v24.8h //saturating row 2L to unsigned 8-bit + sqxtun v11.8b, v8.8h //saturating row 2H to unsigned 8-bit + sqxtun v30.8b, v28.8h //saturating row 3L to unsigned 8-bit + sqxtun v31.8b, v12.8h //saturating row 3H to unsigned 8-bit + st1 {v26.8b, v27.8b}, [x2], x5 //store row 1 in destination + sqxtun v14.8b, v22.8h //saturating row 4L to unsigned 8-bit + sqxtun v15.8b, v16.8h //saturating row 4H to unsigned 8-bit + st1 {v10.8b, v11.8b}, [x2], x5 //store row 2 in destination + subs w11, w11, #4 //decrement ht by 4 + st1 {v30.8b, v31.8b}, [x2], x5 //store row 3 in destination + st1 {v14.8b, v15.8b}, [x2], x5 //store row 4 in destination + bgt loop_16 //if greater than 0 repeat the loop again + +end_loops: + + // LDMFD sp!,{x4-x12,x15} //Reload the registers from sp + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + +//******************************************************************************* +//* @function +//* ih264_weighted_bi_pred_chroma_av8() +//* +//* @brief +//* This routine performs the default weighted prediction as described in sec +//* 8.4.2.3.2 titled "Weighted sample prediction process" for chroma. +//* +//* @par Description: +//* This function gets two ht x wd blocks, calculates the weighted samples, +//* rounds off, adds offset and stores it in the destination block for U and V. +//* +//* @param[in] puc_src1 +//* UWORD8 Pointer to the buffer containing the input block 1. +//* +//* @param[in] puc_src2 +//* UWORD8 Pointer to the buffer containing the input block 2. +//* +//* @param[out] puc_dst +//* UWORD8 pointer to the destination where the output block is stored. +//* +//* @param[in] src_strd1 +//* Stride of the input buffer 1 +//* +//* @param[in] src_strd2 +//* Stride of the input buffer 2 +//* +//* @param[in] dst_strd +//* Stride of the destination buffer +//* +//* @param[in] log_WD +//* number of bits to be rounded off +//* +//* @param[in] wt1 +//* weights for the weighted prediction in U and V +//* +//* @param[in] wt2 +//* weights for the weighted prediction in U and V +//* +//* @param[in] ofst1 +//* offset 1 used after rounding off for U an dV +//* +//* @param[in] ofst2 +//* offset 2 used after rounding off for U and V +//* +//* @param[in] ht +//* integer height of the array +//* +//* @param[in] wd +//* integer width of the array +//* +//* @returns +//* None +//* +//* @remarks +//* (ht,wd) can be (2,2), (2,4), (4,2), (4,4), (4,8), (8,4) or (8,8). +//* +//******************************************************************************* +//*/ +//void ih264_weighted_bi_pred_chroma_av8(UWORD8 *puc_src1, +// UWORD8 *puc_src2, +// UWORD8 *puc_dst, +// WORD32 src_strd1, +// WORD32 src_strd2, +// WORD32 dst_strd, +// WORD32 log_WD, +// WORD32 wt1, +// WORD32 wt2, +// WORD32 ofst1, +// WORD32 ofst2, +// WORD32 ht, +// WORD32 wd) +// +//**************Variables Vs Registers***************************************** +// x0 => puc_src1 +// x1 => puc_src2 +// x2 => puc_dst +// w3 => src_strd1 +// w4 => src_strd2 +// w5 => dst_strd +// w6 => log_WD +// w7 => wt1 +// [sp] => wt2 (w8) +// [sp+8] => ofst1 (w9) +// [sp+16] => ofst2 (w10) +// [sp+24] => ht (w11) +// [sp+32] => wd (w12) +// + + + + + + .global ih264_weighted_bi_pred_chroma_av8 + +ih264_weighted_bi_pred_chroma_av8: + + // STMFD sp!, {x4-x12,x14} //stack stores the values of the arguments + push_v_regs + sxtw x3, w3 + sxtw x4, w4 + sxtw x5, w5 + stp x19, x20, [sp, #-16]! + + + ldr w8, [sp, #80] //Load wt2 in w8 + dup v4.4s, w8 //Q2 = (wt2_u, wt2_v) (32-bit) + dup v2.4s, w7 //Q1 = (wt1_u, wt1_v) (32-bit) + add w6, w6, #1 //w6 = log_WD + 1 + ldr w9, [sp, #88] //Load ofst1 in w9 + ldr w10, [sp, #96] //Load ofst2 in w10 + neg w20, w6 //w20 = -(log_WD + 1) + dup v0.8h, w20 //Q0 = -(log_WD + 1) (16-bit) + ldr w11, [sp, #104] //Load ht in x11 + ldr w12, [sp, #112] //Load wd in x12 + dup v20.8h, w9 //0ffset1 + dup v21.8h, w10 //0ffset2 + srhadd v6.8b, v20.8b, v21.8b + sxtl v6.8h, v6.8b + cmp w12, #8 //check if wd is 8 + beq loop_8_uv //branch if wd is 8 + cmp w12, #4 //check if wd is 4 + beq loop_4_uv //branch if wd is 4 + +loop_2_uv: //each iteration processes two rows + + ld1 {v8.s}[0], [x0], x3 //load row 1 in source 1 + ld1 {v8.s}[1], [x0], x3 //load row 2 in source 1 + ld1 {v10.s}[0], [x1], x4 //load row 1 in source 2 + ld1 {v10.s}[1], [x1], x4 //load row 2 in source 2 + uxtl v8.8h, v8.8b //converting rows 1,2 in source 1 to 16-bit + uxtl v10.8h, v10.8b //converting rows 1,2 in source 2 to 16-bit + mul v8.8h, v8.8h , v2.8h //weight 1 mult. for rows 1,2 + mla v8.8h, v10.8h , v4.8h //weight 2 mult. for rows 1,2 + srshl v8.8h, v8.8h , v0.8h //rounds off the weighted samples from rows 1,2 + add v8.8h, v8.8h , v6.8h //adding offset for rows 1,2 + sqxtun v8.8b, v8.8h //saturating rows 1,2 to unsigned 8-bit/ + st1 {v8.s}[0], [x2], x5 //store row 1 in destination + st1 {v8.s}[1], [x2], x5 //store row 2 in destination + subs w11, w11, #2 //decrement ht by 2 + bgt loop_2_uv //if greater than 0 repeat the loop again + b end_loops_uv + +loop_4_uv: //each iteration processes two rows + + ld1 {v8.8b}, [x0], x3 //load row 1 in source 1 + ld1 {v10.8b}, [x1], x4 //load row 1 in source 2 + uxtl v8.8h, v8.8b //converting row 1 in source 1 to 16-bit + ld1 {v12.8b}, [x0], x3 //load row 2 in source 1 + uxtl v10.8h, v10.8b //converting row 1 in source 2 to 16-bit + ld1 {v14.8b}, [x1], x4 //load row 2 in source 2 + uxtl v12.8h, v12.8b //converting row 2 in source 1 to 16-bit + mul v8.8h, v8.8h , v2.8h //weight 1 mult. for row 1 + mla v8.8h, v10.8h , v4.8h //weight 2 mult. for row 1 + uxtl v14.8h, v14.8b //converting row 2 in source 2 to 16-bit + mul v12.8h, v12.8h , v2.8h //weight 1 mult. for row 2 + mla v12.8h, v14.8h , v4.8h //weight 2 mult. for row 2 + subs w11, w11, #2 //decrement ht by 2 + srshl v8.8h, v8.8h , v0.8h //rounds off the weighted samples from row 1 + srshl v12.8h, v12.8h , v0.8h //rounds off the weighted samples from row 2 + add v8.8h, v8.8h , v6.8h //adding offset for row 1 + add v12.8h, v12.8h , v6.8h //adding offset for row 2 + sqxtun v8.8b, v8.8h //saturating row 1 to unsigned 8-bit + sqxtun v12.8b, v12.8h //saturating row 2 to unsigned 8-bit + st1 {v8.8b}, [x2], x5 //store row 1 in destination + st1 {v12.8b}, [x2], x5 //store row 2 in destination + bgt loop_4_uv //if greater than 0 repeat the loop again + b end_loops_uv + +loop_8_uv: //each iteration processes two rows + + ld1 {v8.8b, v9.8b}, [x0], x3 //load row 1 in source 1 + ld1 {v10.8b, v11.8b}, [x1], x4 //load row 1 in source 2 + ld1 {v12.8b, v13.8b}, [x0], x3 //load row 2 in source 1 + ld1 {v14.8b, v15.8b}, [x1], x4 //load row 2 in source 2 + uxtl v24.8h, v8.8b //converting row 1L in source 1 to 16-bit + ld1 {v16.8b, v17.8b}, [x0], x3 //load row 3 in source 1 + ld1 {v18.8b, v19.8b}, [x1], x4 //load row 3 in source 2 + uxtl v26.8h, v10.8b //converting row 1L in source 2 to 16-bit + ld1 {v20.8b, v21.8b}, [x0], x3 //load row 4 in source 1 + ld1 {v22.8b, v23.8b}, [x1], x4 //load row 4 in source 2 + uxtl v8.8h, v9.8b //converting row 1H in source 1 to 16-bit + uxtl v10.8h, v11.8b //converting row 1H in source 2 to 16-bit + mul v24.8h, v24.8h , v2.8h //weight 1 mult. for row 1L + mla v24.8h, v26.8h , v4.8h //weight 2 mult. for row 1L + uxtl v28.8h, v12.8b //converting row 2L in source 1 to 16-bit + uxtl v30.8h, v14.8b //converting row 2L in source 2 to 16-bit + mul v8.8h, v8.8h , v2.8h //weight 1 mult. for row 1H + mla v8.8h, v10.8h , v4.8h //weight 2 mult. for row 1H + uxtl v12.8h, v13.8b //converting row 2H in source 1 to 16-bit + uxtl v14.8h, v15.8b //converting row 2H in source 2 to 16-bit + mul v28.8h, v28.8h , v2.8h //weight 1 mult. for row 2L + mla v28.8h, v30.8h , v4.8h //weight 2 mult. for row 2L + uxtl v26.8h, v16.8b //converting row 3L in source 1 to 16-bit + uxtl v10.8h, v18.8b //converting row 3L in source 2 to 16-bit + mul v12.8h, v12.8h , v2.8h //weight 1 mult. for row 2H + mla v12.8h, v14.8h , v4.8h //weight 2 mult. for row 2H + uxtl v16.8h, v17.8b //converting row 3H in source 1 to 16-bit + uxtl v18.8h, v19.8b //converting row 3H in source 2 to 16-bit + mul v26.8h, v26.8h , v2.8h //weight 1 mult. for row 3L + mla v26.8h, v10.8h , v4.8h //weight 2 mult. for row 3L + uxtl v30.8h, v20.8b //converting row 4L in source 1 to 16-bit + uxtl v14.8h, v22.8b //converting row 4L in source 2 to 16-bit + mul v16.8h, v16.8h , v2.8h //weight 1 mult. for row 3H + mla v16.8h, v18.8h , v4.8h //weight 2 mult. for row 3H + uxtl v20.8h, v21.8b //converting row 4H in source 1 to 16-bit + uxtl v22.8h, v23.8b //converting row 4H in source 2 to 16-bit + mul v30.8h, v30.8h , v2.8h //weight 1 mult. for row 4L + mla v30.8h, v14.8h , v4.8h //weight 2 mult. for row 4L + srshl v24.8h, v24.8h , v0.8h //rounds off the weighted samples from row 1L + mul v20.8h, v20.8h , v2.8h //weight 1 mult. for row 4H + mla v20.8h, v22.8h , v4.8h //weight 2 mult. for row 4H + srshl v8.8h, v8.8h , v0.8h //rounds off the weighted samples from row 1H + srshl v28.8h, v28.8h , v0.8h //rounds off the weighted samples from row 2L + add v24.8h, v24.8h , v6.8h //adding offset for row 1L + srshl v12.8h, v12.8h , v0.8h //rounds off the weighted samples from row 2H + add v8.8h, v8.8h , v6.8h //adding offset for row 1H + srshl v26.8h, v26.8h , v0.8h //rounds off the weighted samples from row 3L + add v28.8h, v28.8h , v6.8h //adding offset for row 2L + srshl v16.8h, v16.8h , v0.8h //rounds off the weighted samples from row 3H + add v12.8h, v12.8h , v6.8h //adding offset for row 2H + srshl v30.8h, v30.8h , v0.8h //rounds off the weighted samples from row 4L + add v26.8h, v26.8h , v6.8h //adding offset for row 3L + srshl v20.8h, v20.8h , v0.8h //rounds off the weighted samples from row 4H + add v16.8h, v16.8h , v6.8h //adding offset for row 3H + sqxtun v10.8b, v24.8h //saturating row 1L to unsigned 8-bit + add v30.8h, v30.8h , v6.8h //adding offset for row 4L + sqxtun v11.8b, v8.8h //saturating row 1H to unsigned 8-bit + add v20.8h, v20.8h , v6.8h //adding offset for row 4H + sqxtun v18.8b, v28.8h //saturating row 2L to unsigned 8-bit + sqxtun v19.8b, v12.8h //saturating row 2H to unsigned 8-bit + sqxtun v14.8b, v26.8h //saturating row 3L to unsigned 8-bit + sqxtun v15.8b, v16.8h //saturating row 3H to unsigned 8-bit + st1 {v10.8b, v11.8b}, [x2], x5 //store row 1 in destination + sqxtun v22.8b, v30.8h //saturating row 4L to unsigned 8-bit + sqxtun v23.8b, v20.8h //saturating row 4H to unsigned 8-bit + st1 {v18.8b, v19.8b}, [x2], x5 //store row 2 in destination + subs w11, w11, #4 //decrement ht by 4 + st1 {v14.8b, v15.8b}, [x2], x5 //store row 3 in destination + st1 {v22.8b, v23.8b}, [x2], x5 //store row 4 in destination + bgt loop_8_uv //if greater than 0 repeat the loop again + +end_loops_uv: + + // LDMFD sp!,{x4-x12,x15} //Reload the registers from sp + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + + diff --git a/dependencies/ih264d/common/armv8/ih264_weighted_pred_av8.s b/dependencies/ih264d/common/armv8/ih264_weighted_pred_av8.s new file mode 100644 index 00000000..f1452170 --- /dev/null +++ b/dependencies/ih264d/common/armv8/ih264_weighted_pred_av8.s @@ -0,0 +1,472 @@ +//****************************************************************************** +//* +//* Copyright (C) 2015 The Android Open Source Project +//* +//* Licensed under the Apache License, Version 2.0 (the "License"); +//* you may not use this file except in compliance with the License. +//* You may obtain a copy of the License at: +//* +//* http://www.apache.org/licenses/LICENSE-2.0 +//* +//* Unless required by applicable law or agreed to in writing, software +//* distributed under the License is distributed on an "AS IS" BASIS, +//* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//* See the License for the specific language governing permissions and +//* limitations under the License. +//* +//***************************************************************************** +//* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +//*/ +///** +//****************************************************************************** +//* @file +//* ih264_weighted_pred_av8.s +//* +//* @brief +//* Contains function definitions for weighted prediction. +//* +//* @author +//* Kaushik Senthoor R +//* +//* @par List of Functions: +//* +//* - ih264_weighted_pred_luma_av8() +//* - ih264_weighted_pred_chroma_av8() +//* +//* @remarks +//* None +//* +//******************************************************************************* +//*/ +//******************************************************************************* +//* @function +//* ih264_weighted_pred_luma_av8() +//* +//* @brief +//* This routine performs the default weighted prediction as described in sec +//* 8.4.2.3.2 titled "Weighted sample prediction process" for luma. +//* +//* @par Description: +//* This function gets a ht x wd block, calculates the weighted sample, rounds +//* off, adds offset and stores it in the destination block. +//* +//* @param[in] puc_src: +//* UWORD8 Pointer to the buffer containing the input block. +//* +//* @param[out] puc_dst +//* UWORD8 pointer to the destination where the output block is stored. +//* +//* @param[in] src_strd +//* Stride of the input buffer +//* +//* @param[in] dst_strd +//* Stride of the destination buffer +//* +//* @param[in] log_WD +//* number of bits to be rounded off +//* +//* @param[in] wt +//* weight for the weighted prediction +//* +//* @param[in] ofst +//* offset used after rounding off +//* +//* @param[in] ht +//* integer height of the array +//* +//* @param[in] wd +//* integer width of the array +//* +//* @returns +//* None +//* +//* @remarks +//* (ht,wd) can be (4,4), (4,8), (8,4), (8,8), (8,16), (16,8) or (16,16). +//* +//******************************************************************************* +//*/ +//void ih264_weighted_pred_luma_av8(UWORD8 *puc_src, +// UWORD8 *puc_dst, +// WORD32 src_strd, +// WORD32 dst_strd, +// WORD32 log_WD, +// WORD32 wt, +// WORD32 ofst, +// WORD32 ht, +// WORD32 wd) +// +//**************Variables Vs Registers***************************************** +// x0 => puc_src +// x1 => puc_dst +// w2 => src_strd +// w3 => dst_strd +// w4 => log_WD +// w5 => wt +// w6 => ofst +// w7 => ht +// [sp] => wd (w8) +// +.text +.p2align 2 +.include "ih264_neon_macros.s" + + + + .global ih264_weighted_pred_luma_av8 + +ih264_weighted_pred_luma_av8: + + // STMFD sp!, {x4-x9,x14} //stack stores the values of the arguments + push_v_regs + sxtw x2, w2 + sxtw x3, w3 + stp x19, x20, [sp, #-16]! + ldr w8, [sp, #80] //Load wd + sxtw x8, w8 + + dup v2.4h, w5 //D2 = wt (16-bit) + neg w9, w4 //w9 = -log_WD + dup v3.8b, w6 //D3 = ofst (8-bit) + cmp w8, #16 //check if wd is 16 + dup v0.8h, w9 //Q0 = -log_WD (16-bit) + beq loop_16 //branch if wd is 16 + + cmp w8, #8 //check if wd is 8 + beq loop_8 //branch if wd is 8 + +loop_4: //each iteration processes four rows + + ld1 {v4.s}[0], [x0], x2 //load row 1 in source + ld1 {v4.s}[1], [x0], x2 //load row 2 in source + ld1 {v6.s}[0], [x0], x2 //load row 3 in source + ld1 {v6.s}[1], [x0], x2 //load row 4 in source + + uxtl v4.8h, v4.8b //converting rows 1,2 to 16-bit + uxtl v6.8h, v6.8b //converting rows 3,4 to 16-bit + + mul v4.8h, v4.8h , v2.h[0] //weight mult. for rows 1,2 + mul v6.8h, v6.8h , v2.h[0] //weight mult. for rows 3,4 + + subs w7, w7, #4 //decrement ht by 4 + srshl v4.8h, v4.8h , v0.8h //rounds off the weighted samples from rows 1,2 + srshl v6.8h, v6.8h , v0.8h //rounds off the weighted samples from rows 3,4 + + saddw v4.8h, v4.8h , v3.8b //adding offset for rows 1,2 + saddw v6.8h, v6.8h , v3.8b //adding offset for rows 3,4 + + sqxtun v4.8b, v4.8h //saturating rows 1,2 to unsigned 8-bit + sqxtun v6.8b, v6.8h //saturating rows 3,4 to unsigned 8-bit + + st1 {v4.s}[0], [x1], x3 //store row 1 in destination + st1 {v4.s}[1], [x1], x3 //store row 2 in destination + st1 {v6.s}[0], [x1], x3 //store row 3 in destination + st1 {v6.s}[1], [x1], x3 //store row 4 in destination + + bgt loop_4 //if greater than 0 repeat the loop again + + b end_loops + +loop_8: //each iteration processes four rows + + ld1 {v4.8b}, [x0], x2 //load row 1 in source + ld1 {v6.8b}, [x0], x2 //load row 2 in source + ld1 {v8.8b}, [x0], x2 //load row 3 in source + uxtl v4.8h, v4.8b //converting row 1 to 16-bit + ld1 {v10.8b}, [x0], x2 //load row 4 in source + uxtl v6.8h, v6.8b //converting row 2 to 16-bit + + uxtl v8.8h, v8.8b //converting row 3 to 16-bit + mul v4.8h, v4.8h , v2.h[0] //weight mult. for row 1 + uxtl v10.8h, v10.8b //converting row 4 to 16-bit + mul v6.8h, v6.8h , v2.h[0] //weight mult. for row 2 + mul v8.8h, v8.8h , v2.h[0] //weight mult. for row 3 + mul v10.8h, v10.8h , v2.h[0] //weight mult. for row 4 + + srshl v4.8h, v4.8h , v0.8h //rounds off the weighted samples from row 1 + srshl v6.8h, v6.8h , v0.8h //rounds off the weighted samples from row 2 + srshl v8.8h, v8.8h , v0.8h //rounds off the weighted samples from row 3 + saddw v4.8h, v4.8h , v3.8b //adding offset for row 1 + srshl v10.8h, v10.8h , v0.8h //rounds off the weighted samples from row 4 + saddw v6.8h, v6.8h , v3.8b //adding offset for row 2 + + saddw v8.8h, v8.8h , v3.8b //adding offset for row 3 + sqxtun v4.8b, v4.8h //saturating row 1 to unsigned 8-bit + saddw v10.8h, v10.8h , v3.8b //adding offset for row 4 + sqxtun v6.8b, v6.8h //saturating row 2 to unsigned 8-bit + sqxtun v8.8b, v8.8h //saturating row 3 to unsigned 8-bit + sqxtun v10.8b, v10.8h //saturating row 4 to unsigned 8-bit + + st1 {v4.8b}, [x1], x3 //store row 1 in destination + st1 {v6.8b}, [x1], x3 //store row 2 in destination + subs w7, w7, #4 //decrement ht by 4 + st1 {v8.8b}, [x1], x3 //store row 3 in destination + st1 {v10.8b}, [x1], x3 //store row 4 in destination + + bgt loop_8 //if greater than 0 repeat the loop again + + b end_loops + +loop_16: //each iteration processes two rows + + ld1 {v4.8b, v5.8b}, [x0], x2 //load row 1 in source + ld1 {v6.8b, v7.8b}, [x0], x2 //load row 2 in source + uxtl v12.8h, v4.8b //converting row 1L to 16-bit + ld1 {v8.8b, v9.8b}, [x0], x2 //load row 3 in source + uxtl v14.8h, v5.8b //converting row 1H to 16-bit + ld1 {v10.8b, v11.8b}, [x0], x2 //load row 4 in source + uxtl v16.8h, v6.8b //converting row 2L to 16-bit + mul v12.8h, v12.8h , v2.h[0] //weight mult. for row 1L + uxtl v18.8h, v7.8b //converting row 2H to 16-bit + mul v14.8h, v14.8h , v2.h[0] //weight mult. for row 1H + uxtl v20.8h, v8.8b //converting row 3L to 16-bit + mul v16.8h, v16.8h , v2.h[0] //weight mult. for row 2L + uxtl v22.8h, v9.8b //converting row 3H to 16-bit + mul v18.8h, v18.8h , v2.h[0] //weight mult. for row 2H + uxtl v24.8h, v10.8b //converting row 4L to 16-bit + mul v20.8h, v20.8h , v2.h[0] //weight mult. for row 3L + uxtl v26.8h, v11.8b //converting row 4H to 16-bit + mul v22.8h, v22.8h , v2.h[0] //weight mult. for row 3H + mul v24.8h, v24.8h , v2.h[0] //weight mult. for row 4L + srshl v12.8h, v12.8h , v0.8h //rounds off the weighted samples from row 1L + mul v26.8h, v26.8h , v2.h[0] //weight mult. for row 4H + srshl v14.8h, v14.8h , v0.8h //rounds off the weighted samples from row 1H + srshl v16.8h, v16.8h , v0.8h //rounds off the weighted samples from row 2L + saddw v12.8h, v12.8h , v3.8b //adding offset for row 1L + srshl v18.8h, v18.8h , v0.8h //rounds off the weighted samples from row 2H + saddw v14.8h, v14.8h , v3.8b //adding offset for row 1H + sqxtun v4.8b, v12.8h //saturating row 1L to unsigned 8-bit + srshl v20.8h, v20.8h , v0.8h //rounds off the weighted samples from row 3L + saddw v16.8h, v16.8h , v3.8b //adding offset for row 2L + sqxtun v5.8b, v14.8h //saturating row 1H to unsigned 8-bit + srshl v22.8h, v22.8h , v0.8h //rounds off the weighted samples from row 3H + saddw v18.8h, v18.8h , v3.8b //adding offset for row 2H + sqxtun v6.8b, v16.8h //saturating row 2L to unsigned 8-bit + srshl v24.8h, v24.8h , v0.8h //rounds off the weighted samples from row 4L + saddw v20.8h, v20.8h , v3.8b //adding offset for row 3L + sqxtun v7.8b, v18.8h //saturating row 2H to unsigned 8-bit + srshl v26.8h, v26.8h , v0.8h //rounds off the weighted samples from row 4H + saddw v22.8h, v22.8h , v3.8b //adding offset for row 3H + sqxtun v8.8b, v20.8h //saturating row 3L to unsigned 8-bit + saddw v24.8h, v24.8h , v3.8b //adding offset for row 4L + sqxtun v9.8b, v22.8h //saturating row 3H to unsigned 8-bit + saddw v26.8h, v26.8h , v3.8b //adding offset for row 4H + sqxtun v10.8b, v24.8h //saturating row 4L to unsigned 8-bit + st1 {v4.8b, v5.8b}, [x1], x3 //store row 1 in destination + sqxtun v11.8b, v26.8h //saturating row 4H to unsigned 8-bit + st1 {v6.8b, v7.8b}, [x1], x3 //store row 2 in destination + subs w7, w7, #4 //decrement ht by 4 + st1 {v8.8b, v9.8b}, [x1], x3 //store row 3 in destination + st1 {v10.8b, v11.8b}, [x1], x3 //store row 4 in destination + + bgt loop_16 //if greater than 0 repeat the loop again + +end_loops: + + // LDMFD sp!,{x4-x9,x15} //Reload the registers from sp + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + +//******************************************************************************* +//* @function +//* ih264_weighted_pred_chroma_av8() +//* +//* @brief +//* This routine performs the default weighted prediction as described in sec +//* 8.4.2.3.2 titled "Weighted sample prediction process" for chroma. +//* +//* @par Description: +//* This function gets a ht x wd block, calculates the weighted sample, rounds +//* off, adds offset and stores it in the destination block for U and V. +//* +//* @param[in] puc_src: +//* UWORD8 Pointer to the buffer containing the input block. +//* +//* @param[out] puc_dst +//* UWORD8 pointer to the destination where the output block is stored. +//* +//* @param[in] src_strd +//* Stride of the input buffer +//* +//* @param[in] dst_strd +//* Stride of the destination buffer +//* +//* @param[in] log_WD +//* number of bits to be rounded off +//* +//* @param[in] wt +//* weights for the weighted prediction for U and V +//* +//* @param[in] ofst +//* offsets used after rounding off for U and V +//* +//* @param[in] ht +//* integer height of the array +//* +//* @param[in] wd +//* integer width of the array +//* +//* @returns +//* None +//* +//* @remarks +//* (ht,wd) can be (2,2), (2,4), (4,2), (4,4), (4,8), (8,4) or (8,8). +//* +//******************************************************************************* +//*/ +//void ih264_weighted_pred_chroma_av8(UWORD8 *puc_src, +// UWORD8 *puc_dst, +// WORD32 src_strd, +// WORD32 dst_strd, +// WORD32 log_WD, +// WORD32 wt, +// WORD32 ofst, +// WORD32 ht, +// WORD32 wd) +// +//**************Variables Vs Registers***************************************** +// x0 => puc_src +// x1 => puc_dst +// w2 => src_strd +// w3 => dst_strd +// w4 => log_WD +// w5 => wt +// w6 => ofst +// w7 => ht +// [sp] => wd (w8) +// + + + + + .global ih264_weighted_pred_chroma_av8 + +ih264_weighted_pred_chroma_av8: + + // STMFD sp!, {x4-x9,x14} //stack stores the values of the arguments + push_v_regs + sxtw x2, w2 + sxtw x3, w3 + stp x19, x20, [sp, #-16]! + + ldr w8, [sp, #80] //Load wd + sxtw x8, w8 + + neg w9, w4 //w9 = -log_WD + dup v2.4s, w5 //Q1 = {wt_u (16-bit), wt_v (16-bit)} + + + dup v4.4h, w6 //D4 = {ofst_u (8-bit), ofst_v (8-bit)} + cmp w8, #8 //check if wd is 8 + dup v0.8h, w9 //Q0 = -log_WD (16-bit) + beq loop_8_uv //branch if wd is 8 + + cmp w8, #4 //check if ws is 4 + beq loop_4_uv //branch if wd is 4 + +loop_2_uv: //each iteration processes two rows + + ld1 {v6.s}[0], [x0], x2 //load row 1 in source + ld1 {v6.s}[1], [x0], x2 //load row 2 in source + uxtl v6.8h, v6.8b //converting rows 1,2 to 16-bit + mul v6.8h, v6.8h , v2.8h //weight mult. for rows 1,2 + srshl v6.8h, v6.8h , v0.8h //rounds off the weighted samples from rows 1,2 + saddw v6.8h, v6.8h , v4.8b //adding offset for rows 1,2 + sqxtun v6.8b, v6.8h //saturating rows 1,2 to unsigned 8-bit + subs w7, w7, #2 //decrement ht by 2 + st1 {v6.s}[0], [x1], x3 //store row 1 in destination + st1 {v6.s}[1], [x1], x3 //store row 2 in destination + bgt loop_2_uv //if greater than 0 repeat the loop again + b end_loops_uv + +loop_4_uv: //each iteration processes two rows + + ld1 {v6.8b}, [x0], x2 //load row 1 in source + ld1 {v8.8b}, [x0], x2 //load row 2 in source + uxtl v6.8h, v6.8b //converting row 1 to 16-bit + uxtl v8.8h, v8.8b //converting row 2 to 16-bit + mul v6.8h, v6.8h , v2.8h //weight mult. for row 1 + mul v8.8h, v8.8h , v2.8h //weight mult. for row 2 + subs w7, w7, #2 //decrement ht by 2 + srshl v6.8h, v6.8h , v0.8h //rounds off the weighted samples from row 1 + srshl v8.8h, v8.8h , v0.8h //rounds off the weighted samples from row 2 + saddw v6.8h, v6.8h , v4.8b //adding offset for row 1 + saddw v8.8h, v8.8h , v4.8b //adding offset for row 2 + sqxtun v6.8b, v6.8h //saturating row 1 to unsigned 8-bit + sqxtun v8.8b, v8.8h //saturating row 2 to unsigned 8-bit + st1 {v6.8b}, [x1], x3 //store row 1 in destination + st1 {v8.8b}, [x1], x3 //store row 2 in destination + + bgt loop_4_uv //if greater than 0 repeat the loop again + + b end_loops_uv + +loop_8_uv: //each iteration processes two rows + + ld1 {v6.8b, v7.8b}, [x0], x2 //load row 1 in source + ld1 {v8.8b, v9.8b}, [x0], x2 //load row 2 in source + uxtl v14.8h, v6.8b //converting row 1L to 16-bit + ld1 {v10.8b, v11.8b}, [x0], x2 //load row 3 in source + uxtl v16.8h, v7.8b //converting row 1H to 16-bit + ld1 {v12.8b, v13.8b}, [x0], x2 //load row 4 in source + + mul v14.8h, v14.8h , v2.8h //weight mult. for row 1L + uxtl v18.8h, v8.8b //converting row 2L to 16-bit + mul v16.8h, v16.8h , v2.8h //weight mult. for row 1H + uxtl v20.8h, v9.8b //converting row 2H to 16-bit + mul v18.8h, v18.8h , v2.8h //weight mult. for row 2L + uxtl v22.8h, v10.8b //converting row 3L to 16-bit + mul v20.8h, v20.8h , v2.8h //weight mult. for row 2H + uxtl v24.8h, v11.8b //converting row 3H to 16-bit + mul v22.8h, v22.8h , v2.8h //weight mult. for row 3L + uxtl v26.8h, v12.8b //converting row 4L to 16-bit + mul v24.8h, v24.8h , v2.8h //weight mult. for row 3H + uxtl v28.8h, v13.8b //converting row 4H to 16-bit + + mul v26.8h, v26.8h , v2.8h //weight mult. for row 4L + srshl v14.8h, v14.8h , v0.8h //rounds off the weighted samples from row 1L + mul v28.8h, v28.8h , v2.8h //weight mult. for row 4H + + srshl v16.8h, v16.8h , v0.8h //rounds off the weighted samples from row 1H + srshl v18.8h, v18.8h , v0.8h //rounds off the weighted samples from row 2L + saddw v14.8h, v14.8h , v4.8b //adding offset for row 1L + srshl v20.8h, v20.8h , v0.8h //rounds off the weighted samples from row 2H + saddw v16.8h, v16.8h , v4.8b //adding offset for row 1H + sqxtun v6.8b, v14.8h //saturating row 1L to unsigned 8-bit + srshl v22.8h, v22.8h , v0.8h //rounds off the weighted samples from row 3L + saddw v18.8h, v18.8h , v4.8b //adding offset for row 2L + sqxtun v7.8b, v16.8h //saturating row 1H to unsigned 8-bit + srshl v24.8h, v24.8h , v0.8h //rounds off the weighted samples from row 3H + saddw v20.8h, v20.8h , v4.8b //adding offset for row 2H + sqxtun v8.8b, v18.8h //saturating row 2L to unsigned 8-bit + srshl v26.8h, v26.8h , v0.8h //rounds off the weighted samples from row 4L + saddw v22.8h, v22.8h , v4.8b //adding offset for row 3L + sqxtun v9.8b, v20.8h //saturating row 2H to unsigned 8-bit + srshl v28.8h, v28.8h , v0.8h //rounds off the weighted samples from row 4H + saddw v24.8h, v24.8h , v4.8b //adding offset for row 3H + + sqxtun v10.8b, v22.8h //saturating row 3L to unsigned 8-bit + saddw v26.8h, v26.8h , v4.8b //adding offset for row 4L + sqxtun v11.8b, v24.8h //saturating row 3H to unsigned 8-bit + saddw v28.8h, v28.8h , v4.8b //adding offset for row 4H + + sqxtun v12.8b, v26.8h //saturating row 4L to unsigned 8-bit + st1 {v6.8b, v7.8b}, [x1], x3 //store row 1 in destination + sqxtun v13.8b, v28.8h //saturating row 4H to unsigned 8-bit + st1 {v8.8b, v9.8b}, [x1], x3 //store row 2 in destination + subs w7, w7, #4 //decrement ht by 4 + st1 {v10.8b, v11.8b}, [x1], x3 //store row 3 in destination + st1 {v12.8b, v13.8b}, [x1], x3 //store row 4 in destination + + bgt loop_8_uv //if greater than 0 repeat the loop again + +end_loops_uv: + + // LDMFD sp!,{x4-x9,x15} //Reload the registers from sp + ldp x19, x20, [sp], #16 + pop_v_regs + ret + + + diff --git a/dependencies/ih264d/common/ih264_buf_mgr.c b/dependencies/ih264d/common/ih264_buf_mgr.c new file mode 100644 index 00000000..ea4333ee --- /dev/null +++ b/dependencies/ih264d/common/ih264_buf_mgr.c @@ -0,0 +1,696 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** +******************************************************************************* +* @file +* ih264_buf_mgr.c +* +* @brief +* Contains function definitions for buffer management +* +* @author +* Srinivas T +* +* @par List of Functions: +* - ih264_buf_mgr_size() +* - ih264_buf_mgr_lock() +* - ih264_buf_mgr_unlock() +* - ih264_buf_mgr_yield() +* - ih264_buf_mgr_free() +* - ih264_buf_mgr_init() +* - ih264_buf_mgr_add() +* - ih264_buf_mgr_get_next_free() +* - ih264_buf_mgr_check_free() +* - ih264_buf_mgr_set_status() +* - ih264_buf_mgr_get_status() +* - ih264_buf_mgr_get_buf() +* - ih264_buf_mgr_get_bufid() +* - ih264_buf_mgr_get_num_active_buf() +* +* @remarks +* None +* +******************************************************************************* +*/ +#include +#include +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_defs.h" +#include "ih264_error.h" +#include "ih264_buf_mgr.h" + +#include "ithread.h" + +/** +******************************************************************************* +* +* @brief Returns size for buf queue context. Does not include buf queue buffer +* requirements +* +* @par Description +* Returns size for buf queue context. Does not include buf queue buffer +* requirements. Buffer size required to store the bufs should be allocated in +* addition to the value returned here. +* +* @returns Size of the buf queue context +* +* @remarks +* +******************************************************************************* +*/ +WORD32 ih264_buf_mgr_size(void) +{ + WORD32 size; + + size = sizeof(buf_mgr_t); + size += ithread_get_mutex_lock_size(); + + return size; +} + +/** +******************************************************************************* +* +* @brief +* Locks the buf_mgr context +* +* @par Description +* Locks the buf_mgr context by calling ithread_mutex_lock() +* +* @param[in] ps_buf_mgr +* Job Queue context +* +* @returns IH264_FAIL if mutex lock fails else IH264_SUCCESS +* +* @remarks +* +******************************************************************************* +*/ +IH264_ERROR_T ih264_buf_mgr_lock(buf_mgr_t *ps_buf_mgr) +{ + WORD32 retval; + retval = ithread_mutex_lock(ps_buf_mgr->pv_mutex); + if(retval) + { + return IH264_FAIL; + } + return IH264_SUCCESS; +} + +/** +******************************************************************************* +* +* @brief +* Unlocks the buf_mgr context +* +* @par Description +* Unlocks the buf_mgr context by calling ithread_mutex_unlock() +* +* @param[in] ps_buf_mgr +* Job Queue context +* +* @returns IH264_FAIL if mutex unlock fails else IH264_SUCCESS +* +* @remarks +* +******************************************************************************* +*/ + +IH264_ERROR_T ih264_buf_mgr_unlock(buf_mgr_t *ps_buf_mgr) +{ + WORD32 retval; + retval = ithread_mutex_unlock(ps_buf_mgr->pv_mutex); + if(retval) + { + return IH264_FAIL; + } + return IH264_SUCCESS; + +} +/** +******************************************************************************* +* +* @brief +* Yeilds the thread +* +* @par Description +* Unlocks the buf_mgr context by calling +* ih264_buf_mgr_unlock(), ithread_yield() and then ih264_buf_mgr_lock() +* buf_mgr is unlocked before to ensure the buf_mgr can be accessed by other threads +* If unlock is not done before calling yield then no other thread can access +* the buf_mgr functions and update buf_mgr. +* +* @param[in] ps_buf_mgr +* Job Queue context +* +* @returns IH264_FAIL if mutex lock unlock or yield fails else IH264_SUCCESS +* +* @remarks +* +******************************************************************************* +*/ +IH264_ERROR_T ih264_buf_mgr_yield(buf_mgr_t *ps_buf_mgr) +{ + + IH264_ERROR_T ret = IH264_SUCCESS; + + IH264_ERROR_T rettmp; + rettmp = ih264_buf_mgr_unlock(ps_buf_mgr); + RETURN_IF((rettmp != IH264_SUCCESS), rettmp); + + //ithread_usleep(10); + ithread_yield(); + + rettmp = ih264_buf_mgr_lock(ps_buf_mgr); + RETURN_IF((rettmp != IH264_SUCCESS), rettmp); + return ret; +} + + +/** +******************************************************************************* +* +* @brief free the buf queue pointers +* +* @par Description +* Frees the buf_mgr context +* +* @param[in] pv_buf +* Memoy for buf queue buffer and buf queue context +* +* @returns Pointer to buf queue context +* +* @remarks +* Since it will be called only once by master thread this is not thread safe. +* +******************************************************************************* +*/ +IH264_ERROR_T ih264_buf_mgr_free(buf_mgr_t *ps_buf_mgr) +{ + WORD32 ret; + ret = ithread_mutex_destroy(ps_buf_mgr->pv_mutex); + + if(0 == ret) + return IH264_SUCCESS; + else + return IH264_FAIL; +} +/** +******************************************************************************* +* +* @brief +* Buffer manager initialization function. +* +* @par Description: +* Initializes the buffer manager structure +* +* @param[in] ps_buf_mgr +* Pointer to the buffer manager +* +* @returns +* +* @remarks +* None +* +******************************************************************************* +*/ + + +void *ih264_buf_mgr_init(void *pv_buf) +{ + WORD32 id; + UWORD8 *pu1_buf; + buf_mgr_t *ps_buf_mgr; + pu1_buf = (UWORD8 *)pv_buf; + + ps_buf_mgr = (buf_mgr_t *)pu1_buf; + pu1_buf += sizeof(buf_mgr_t); + + ps_buf_mgr->pv_mutex = pu1_buf; + pu1_buf += ithread_get_mutex_lock_size(); + + ithread_mutex_init(ps_buf_mgr->pv_mutex); + + ps_buf_mgr->i4_max_buf_cnt = BUF_MGR_MAX_CNT; + ps_buf_mgr->i4_active_buf_cnt = 0; + + for(id = 0; id < BUF_MGR_MAX_CNT; id++) + { + ps_buf_mgr->au4_status[id] = 0; + ps_buf_mgr->apv_ptr[id] = NULL; + } + + return ps_buf_mgr; +} + + +/** +******************************************************************************* +* +* @brief +* Adds and increments the buffer and buffer count. +* +* @par Description: +* Adds a buffer to the buffer manager if it is not already present and +* increments the active buffer count +* +* @param[in] ps_buf_mgr +* Pointer to the buffer manager +* +* @param[in] pv_ptr +* Pointer to the buffer to be added +* +* @returns Returns 0 on success, -1 otherwise +* +* @remarks +* None +* +******************************************************************************* +*/ +IH264_ERROR_T ih264_buf_mgr_add(buf_mgr_t *ps_buf_mgr, + void *pv_ptr, + WORD32 buf_id) +{ + + IH264_ERROR_T ret = IH264_SUCCESS; + ret = ih264_buf_mgr_lock(ps_buf_mgr); + RETURN_IF((ret != IH264_SUCCESS), ret); + + /* Check if buffer ID is within allowed range */ + if(buf_id >= ps_buf_mgr->i4_max_buf_cnt) + { + ret = ih264_buf_mgr_unlock(ps_buf_mgr); + RETURN_IF((ret != IH264_SUCCESS), ret); + + return IH264_FAIL; + } + + /* Check if the current ID is being used to hold some other buffer */ + if((ps_buf_mgr->apv_ptr[buf_id] != NULL) && + (ps_buf_mgr->apv_ptr[buf_id] !=pv_ptr)) + { + ret = ih264_buf_mgr_unlock(ps_buf_mgr); + RETURN_IF((ret != IH264_SUCCESS), ret); + + return IH264_FAIL; + } + ps_buf_mgr->apv_ptr[buf_id] = pv_ptr; + ps_buf_mgr->i4_active_buf_cnt++; + + ret = ih264_buf_mgr_unlock(ps_buf_mgr); + RETURN_IF((ret != IH264_SUCCESS), ret); + + return ret; +} + +/** +******************************************************************************* +* +* @brief +* Gets the next free buffer. +* +* @par Description: +* Returns the next free buffer available and sets the corresponding status +* to DEC +* +* @param[in] ps_buf_mgr +* Pointer to the buffer manager +* +* @param[in] pi4_buf_id +* Pointer to the id of the free buffer +* +* @returns Pointer to the free buffer +* +* @remarks +* None +* +******************************************************************************* +*/ +void* ih264_buf_mgr_get_next_free(buf_mgr_t *ps_buf_mgr, WORD32 *pi4_buf_id) +{ + WORD32 id; + void *pv_ret_ptr; + IH264_ERROR_T ret = IH264_SUCCESS; + ret = ih264_buf_mgr_lock(ps_buf_mgr); + RETURN_IF((ret != IH264_SUCCESS), NULL); + + pv_ret_ptr = NULL; + for(id = 0; id < ps_buf_mgr->i4_active_buf_cnt; id++) + { + /* Check if the buffer is non-null and status is zero */ + if((ps_buf_mgr->au4_status[id] == 0) && (ps_buf_mgr->apv_ptr[id])) + { + *pi4_buf_id = id; + /* DEC is set to 1 */ + ps_buf_mgr->au4_status[id] = 1; + pv_ret_ptr = ps_buf_mgr->apv_ptr[id]; + break; + } + } + ret = ih264_buf_mgr_unlock(ps_buf_mgr); + RETURN_IF((ret != IH264_SUCCESS), NULL); + + return pv_ret_ptr; +} + + +/** +******************************************************************************* +* +* @brief +* Checks the buffer manager for free buffers available. +* +* @par Description: +* Checks if there are any free buffers available +* +* @param[in] ps_buf_mgr +* Pointer to the buffer manager +* +* @returns Returns 0 if available, -1 otherwise +* +* @remarks +* None +* +******************************************************************************* +*/ +IH264_ERROR_T ih264_buf_mgr_check_free(buf_mgr_t *ps_buf_mgr) +{ + WORD32 id; + IH264_ERROR_T ret = IH264_SUCCESS; + IH264_ERROR_T rettmp = IH264_SUCCESS; + rettmp = ih264_buf_mgr_lock(ps_buf_mgr); + RETURN_IF((rettmp != IH264_SUCCESS), ret); + + ret = IH264_FAIL; + for(id = 0; id < ps_buf_mgr->i4_active_buf_cnt; id++) + { + if((ps_buf_mgr->au4_status[id] == 0) && + (ps_buf_mgr->apv_ptr[id])) + { + ret = IH264_SUCCESS; + break; + } + } + rettmp = ih264_buf_mgr_unlock(ps_buf_mgr); + RETURN_IF((rettmp != IH264_SUCCESS), ret); + + return ret; + +} + + +/** +******************************************************************************* +* +* @brief +* Resets the status bits. +* +* @par Description: +* resets the status bits that the mask contains (status corresponding to +* the id) +* +* @param[in] ps_buf_mgr +* Pointer to the buffer manager +* +* @param[in] buf_id +* ID of the buffer status to be released +* +* @param[in] mask +* Contains the bits that are to be reset +* +* @returns 0 if success, -1 otherwise +* +* @remarks +* None +* +******************************************************************************* +*/ +IH264_ERROR_T ih264_buf_mgr_release(buf_mgr_t *ps_buf_mgr, + WORD32 buf_id, + UWORD32 mask) +{ + IH264_ERROR_T ret = IH264_SUCCESS; + ret = ih264_buf_mgr_lock(ps_buf_mgr); + RETURN_IF((ret != IH264_SUCCESS), ret); + + + /* If the given id is pointing to an id which is not yet added */ + if(buf_id >= ps_buf_mgr->i4_active_buf_cnt) + { + ret = ih264_buf_mgr_unlock(ps_buf_mgr); + RETURN_IF((ret != IH264_SUCCESS), ret); + return IH264_FAIL; + } + + ps_buf_mgr->au4_status[buf_id] &= ~mask; + + +/* If both the REF and DISP are zero, DEC is set to zero */ + if(ps_buf_mgr->au4_status[buf_id] == 1) + { + ps_buf_mgr->au4_status[buf_id] = 0; + } + + + ret = ih264_buf_mgr_unlock(ps_buf_mgr); + RETURN_IF((ret != IH264_SUCCESS), ret); + + return ret; +} + + +/** +******************************************************************************* +* +* @brief +* Sets the status bit. +* +* @par Description: +* sets the status bits that the mask contains (status corresponding to the +* id) +* +* +* @param[in] ps_buf_mgr +* Pointer to the buffer manager +* +* @param[in] buf_id +* ID of the buffer whose status needs to be modified +* +* +* @param[in] mask +* Contains the bits that are to be set +* +* @returns 0 if success, -1 otherwise +* +* @remarks +* None +* +******************************************************************************* +*/ +IH264_ERROR_T ih264_buf_mgr_set_status(buf_mgr_t *ps_buf_mgr, + WORD32 buf_id, + UWORD32 mask) +{ + IH264_ERROR_T ret = IH264_SUCCESS; + ret = ih264_buf_mgr_lock(ps_buf_mgr); + RETURN_IF((ret != IH264_SUCCESS), ret); + + if(buf_id >= ps_buf_mgr->i4_active_buf_cnt) + { + ret = ih264_buf_mgr_unlock(ps_buf_mgr); + RETURN_IF((ret != IH264_SUCCESS), ret); + return IH264_FAIL; + } + + + if((ps_buf_mgr->au4_status[buf_id] & mask) != 0) + { + ret = ih264_buf_mgr_unlock(ps_buf_mgr); + RETURN_IF((ret != IH264_SUCCESS), ret); + return IH264_FAIL; + } + + ps_buf_mgr->au4_status[buf_id] |= mask; + ret = ih264_buf_mgr_unlock(ps_buf_mgr); + RETURN_IF((ret != IH264_SUCCESS), ret); + + return ret; +} + + +/** +******************************************************************************* +* +* @brief +* Returns the status of the buffer. +* +* @par Description: +* Returns the status of the buffer corresponding to the id +* +* @param[in] ps_buf_mgr +* Pointer to the buffer manager +* +* @param[in] buf_id +* ID of the buffer status required +* +* @returns Status of the buffer corresponding to the id +* +* @remarks +* None +* +******************************************************************************* +*/ +WORD32 ih264_buf_mgr_get_status( buf_mgr_t *ps_buf_mgr, WORD32 buf_id ) +{ + IH264_ERROR_T ret = IH264_SUCCESS; + UWORD32 status; + + ret = ih264_buf_mgr_lock(ps_buf_mgr); + RETURN_IF((ret != IH264_SUCCESS), ret); + + status = ps_buf_mgr->au4_status[buf_id]; + + ret = ih264_buf_mgr_unlock(ps_buf_mgr); + RETURN_IF((ret != IH264_SUCCESS), ret); + + return status; +} + + +/** +******************************************************************************* +* +* @brief +* Gets the buffer from the buffer manager +* +* @par Description: +* Returns the pointer to the buffer corresponding to the id +* +* @param[in] ps_buf_mgr +* Pointer to the buffer manager +* +* @param[in] buf_id +* ID of the buffer required +* +* @returns Pointer to the buffer required +* +* @remarks +* None +* +******************************************************************************* +*/ +void* ih264_buf_mgr_get_buf(buf_mgr_t *ps_buf_mgr, WORD32 buf_id) +{ + IH264_ERROR_T ret = IH264_SUCCESS; + void *pv_buf; + ret = ih264_buf_mgr_lock(ps_buf_mgr); + RETURN_IF((ret != IH264_SUCCESS), NULL); + + pv_buf = ps_buf_mgr->apv_ptr[buf_id]; + + ret = ih264_buf_mgr_unlock(ps_buf_mgr); + RETURN_IF((ret != IH264_SUCCESS), NULL); + + return pv_buf; +} + + +/** +******************************************************************************* +* +* @brief +* Gets the buffer id from the buffer manager if the buffer is added to the +* buffer manager +* +* @par Description: +* Returns the buffer id corresponding to the given buffer if it exists +* +* @param[in] ps_buf_mgr +* Pointer to the buffer manager +* +* @param[in] pv_buf +* Pointer to the buffer +* +* @returns Buffer id if exists, else -1 +* +* @remarks +* None +* +******************************************************************************* +*/ +WORD32 ih264_buf_mgr_get_bufid(buf_mgr_t *ps_buf_mgr, void *pv_buf) +{ + WORD32 id; + WORD32 buf_id = -1; + IH264_ERROR_T ret = IH264_SUCCESS; + ret = ih264_buf_mgr_lock(ps_buf_mgr); + RETURN_IF((ret != IH264_SUCCESS), ret); + + for(id = 0; id < ps_buf_mgr->i4_active_buf_cnt; id++) + { + if(ps_buf_mgr->apv_ptr[id] == pv_buf) + { + buf_id = id; + break; + } + } + ret = ih264_buf_mgr_unlock(ps_buf_mgr); + RETURN_IF((ret != IH264_SUCCESS), ret); + + return buf_id; +} + + +/** +******************************************************************************* +* +* @brief +* Gets the no.of active buffer +* +* @par Description: +* Return the number of active buffers in the buffer manager +* +* @param[in] ps_buf_mgr +* Pointer to the buffer manager +* +* @returns number of active buffers +* +* @remarks +* None +* +******************************************************************************* +*/ +UWORD32 ih264_buf_mgr_get_num_active_buf(buf_mgr_t *ps_buf_mgr) +{ + UWORD32 u4_buf_cnt; + IH264_ERROR_T ret = IH264_SUCCESS; + + u4_buf_cnt = 0; + + ret = ih264_buf_mgr_lock(ps_buf_mgr); + RETURN_IF((ret != IH264_SUCCESS), ret); + u4_buf_cnt = ps_buf_mgr->i4_active_buf_cnt; + + ret = ih264_buf_mgr_unlock(ps_buf_mgr); + RETURN_IF((ret != IH264_SUCCESS), ret); + + return u4_buf_cnt; +} diff --git a/dependencies/ih264d/common/ih264_buf_mgr.h b/dependencies/ih264d/common/ih264_buf_mgr.h new file mode 100644 index 00000000..52efa70b --- /dev/null +++ b/dependencies/ih264d/common/ih264_buf_mgr.h @@ -0,0 +1,122 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** +******************************************************************************* +* @file +* ih264_buf_mgr.h +* +* @brief +* Function declarations used for buffer management +* +* @remarks +* None +* +******************************************************************************* +*/ +#ifndef _IH264_BUF_MGR_H_ +#define _IH264_BUF_MGR_H_ + +#define BUF_MGR_MAX_CNT 64 + +/** Flag for current encoding decoder */ +#define BUF_MGR_CODEC (1 << 1) + +/** Flag for reference status */ +#define BUF_MGR_REF (1 << 2) + +/** Flag for I/O - Display/output in case of decoder, capture/input in case of encoder */ +#define BUF_MGR_IO (1 << 3) + +typedef struct +{ + /** + * Mutex used to keep the functions thread-safe + */ + void *pv_mutex; + + /** + * max_buf_cnt + */ + WORD32 i4_max_buf_cnt; + + /** + * active_buf_cnt + */ + WORD32 i4_active_buf_cnt; + + /** + * au4_status[BUF_MGR_MAX_CNT] + */ + UWORD32 au4_status[BUF_MGR_MAX_CNT]; + + /* The last three bit of status are: */ + + /* Bit 0 - IN USE */ + /* Bit 1 - CODEC */ + /* Bit 2 - REF */ + /* Bit 3 - DISP/IO/RECON */ + void *apv_ptr[BUF_MGR_MAX_CNT]; + +}buf_mgr_t; + +// Returns size of the buffer manager context +WORD32 ih264_buf_mgr_size(void); + +//Free buffer manager +IH264_ERROR_T ih264_buf_mgr_free(buf_mgr_t *ps_buf_mgr); + +// Initializes the buffer API structure +void *ih264_buf_mgr_init(void *pv_buf); + +// Add buffer to buffer manager. 0: success, -1: fail (u4_active_buf_cnt has reached u4_max_buf_cnt) +IH264_ERROR_T ih264_buf_mgr_add(buf_mgr_t *ps_buf_mgr, + void *pv_ptr, + WORD32 buf_id); + +// this function will set the buffer status to DEC +void* ih264_buf_mgr_get_next_free(buf_mgr_t *ps_buf_mgr, WORD32 *pi4_id); + +// this function will check if there are any free buffers +IH264_ERROR_T ih264_buf_mgr_check_free(buf_mgr_t *ps_buf_mgr); + +// mask will have who released it: DISP:REF:DEC +IH264_ERROR_T ih264_buf_mgr_release(buf_mgr_t *ps_buf_mgr, + WORD32 id, + UWORD32 mask); + +// sets the status to one or all of DISP:REF:DEC +IH264_ERROR_T ih264_buf_mgr_set_status(buf_mgr_t *ps_buf_mgr, + WORD32 id, + UWORD32 mask); + +// Gets status of the buffer +WORD32 ih264_buf_mgr_get_status(buf_mgr_t *ps_buf_mgr, WORD32 id); + +// pass the ID - buffer will be returned +void* ih264_buf_mgr_get_buf(buf_mgr_t *ps_buf_mgr, WORD32 id); +//Pass buffer to get ID +WORD32 ih264_buf_mgr_get_bufid(buf_mgr_t *ps_buf_mgr, void *pv_buf); + +// will return number of active buffers +UWORD32 ih264_buf_mgr_get_num_active_buf(buf_mgr_t *ps_buf_mgr); + + + +#endif /* _IH264_BUF_MGR_H_ */ diff --git a/dependencies/ih264d/common/ih264_cabac_tables.c b/dependencies/ih264d/common/ih264_cabac_tables.c new file mode 100644 index 00000000..835ea334 --- /dev/null +++ b/dependencies/ih264d/common/ih264_cabac_tables.c @@ -0,0 +1,9280 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ + + +/** +****************************************************************************** +* @file +* ih264_cabac_tables.c +* +* @brief +* This file contains H264 cabac tables for init contexts, rlps and +* cabac state transitions +* +* @author +* Ittiam +* +* @par List of Tables +* - gau4_ih264_cabac_table[][] +* - gau1_ih264_cabac_ctxt_init_table[][][] +* +****************************************************************************** +*/ + + +/*****************************************************************************/ +/* File Includes */ +/*****************************************************************************/ + +/* User include files */ +#include "ih264_typedefs.h" +#include "ih264_cabac_tables.h" + + +/*****************************************************************************/ +/* Extern global definitions */ +/*****************************************************************************/ +/*****************************************************************************/ +/* CABAC TABLES */ +/*****************************************************************************/ +/*combined table :guc_RTAB,NextStateLPS,NextStateMPS + input(combined_state): + bits 0-5: state + bits 6:mps + output + bits 0-7:rangeTabLPS + bits 8-14 :combined_next_state_if_mps + bits 15 -21:combined_next_state_if_lps + + */ + +const UWORD32 gau4_ih264_cabac_table[128][4] = + { + { 2097536, 2097584, 2097616, 2097648 }, + + { 640, 679, 709, 739 }, + + { 33664, 33694, 33723, 33752 }, + + { 66683, 66710, 66738, 66765 }, + + { 66932, 66958, 66985, 67011 }, + + { 132719, 132743, 132768, 132793 }, + + { 132969, 132992, 133016, 133039 }, + + { 165988, 166010, 166032, 166054 }, + + { 199007, 199028, 199049, 199070 }, + + { 232026, 232046, 232066, 232086 }, + + { 265045, 265064, 265083, 265102 }, + + { 298065, 298083, 298101, 298119 }, + + { 298317, 298334, 298351, 298368 }, + + { 364105, 364121, 364137, 364154 }, + + { 364357, 364373, 364388, 364404 }, + + { 397378, 397392, 397407, 397422 }, + + { 430398, 430412, 430426, 430440 }, + + { 430651, 430664, 430678, 430691 }, + + { 496440, 496453, 496465, 496478 }, + + { 496693, 496705, 496717, 496729 }, + + { 529715, 529726, 529737, 529749 }, + + { 529968, 529979, 529989, 530000 }, + + { 595758, 595768, 595778, 595788 }, + + { 596011, 596021, 596031, 596040 }, + + { 629033, 629042, 629051, 629061 }, + + { 629287, 629296, 629304, 629313 }, + + { 695077, 695085, 695094, 695102 }, + + { 695331, 695339, 695347, 695355 }, + + { 728353, 728361, 728368, 728376 }, + + { 728608, 728615, 728622, 728629 }, + + { 761630, 761637, 761643, 761650 }, + + { 794653, 794659, 794665, 794672 }, + + { 794907, 794913, 794919, 794925 }, + + { 827930, 827935, 827941, 827947 }, + + { 860952, 860958, 860963, 860969 }, + + { 861207, 861212, 861217, 861223 }, + + { 894230, 894235, 894240, 894245 }, + + { 894485, 894490, 894494, 894499 }, + + { 927508, 927512, 927517, 927521 }, + + { 960531, 960535, 960539, 960543 }, + + { 960786, 960790, 960794, 960798 }, + + { 993809, 993813, 993817, 993820 }, + + { 994064, 994068, 994071, 994075 }, + + { 994319, 994323, 994326, 994329 }, + + { 1027342, 1027346, 1027349, 1027352 }, + + { 1060366, 1060369, 1060372, 1060375 }, + + { 1060621, 1060624, 1060627, 1060630 }, + + { 1093644, 1093647, 1093650, 1093653 }, + + { 1093900, 1093902, 1093905, 1093908 }, + + { 1094155, 1094158, 1094160, 1094163 }, + + { 1127179, 1127181, 1127183, 1127186 }, + + { 1127434, 1127436, 1127439, 1127441 }, + + { 1160458, 1160460, 1160462, 1160464 }, + + { 1160713, 1160715, 1160717, 1160719 }, + + { 1160969, 1160971, 1160972, 1160974 }, + + { 1193992, 1193994, 1193996, 1193998 }, + + { 1194248, 1194249, 1194251, 1194253 }, + + { 1194503, 1194505, 1194507, 1194508 }, + + { 1227527, 1227529, 1227530, 1227532 }, + + { 1227783, 1227784, 1227786, 1227787 }, + + { 1228038, 1228040, 1228041, 1228043 }, + + { 1261062, 1261063, 1261065, 1261066 }, + + { 1261062, 1261063, 1261064, 1261065 }, + + { 2080514, 2080514, 2080514, 2080514 }, + + { 16768, 16816, 16848, 16880 }, + + { 2114176, 2114215, 2114245, 2114275 }, + + { 2147200, 2147230, 2147259, 2147288 }, + + { 2180219, 2180246, 2180274, 2180301 }, + + { 2180468, 2180494, 2180521, 2180547 }, + + { 2246255, 2246279, 2246304, 2246329 }, + + { 2246505, 2246528, 2246552, 2246575 }, + + { 2279524, 2279546, 2279568, 2279590 }, + + { 2312543, 2312564, 2312585, 2312606 }, + + { 2345562, 2345582, 2345602, 2345622 }, + + { 2378581, 2378600, 2378619, 2378638 }, + + { 2411601, 2411619, 2411637, 2411655 }, + + { 2411853, 2411870, 2411887, 2411904 }, + + { 2477641, 2477657, 2477673, 2477690 }, + + { 2477893, 2477909, 2477924, 2477940 }, + + { 2510914, 2510928, 2510943, 2510958 }, + + { 2543934, 2543948, 2543962, 2543976 }, + + { 2544187, 2544200, 2544214, 2544227 }, + + { 2609976, 2609989, 2610001, 2610014 }, + + { 2610229, 2610241, 2610253, 2610265 }, + + { 2643251, 2643262, 2643273, 2643285 }, + + { 2643504, 2643515, 2643525, 2643536 }, + + { 2709294, 2709304, 2709314, 2709324 }, + + { 2709547, 2709557, 2709567, 2709576 }, + + { 2742569, 2742578, 2742587, 2742597 }, + + { 2742823, 2742832, 2742840, 2742849 }, + + { 2808613, 2808621, 2808630, 2808638 }, + + { 2808867, 2808875, 2808883, 2808891 }, + + { 2841889, 2841897, 2841904, 2841912 }, + + { 2842144, 2842151, 2842158, 2842165 }, + + { 2875166, 2875173, 2875179, 2875186 }, + + { 2908189, 2908195, 2908201, 2908208 }, + + { 2908443, 2908449, 2908455, 2908461 }, + + { 2941466, 2941471, 2941477, 2941483 }, + + { 2974488, 2974494, 2974499, 2974505 }, + + { 2974743, 2974748, 2974753, 2974759 }, + + { 3007766, 3007771, 3007776, 3007781 }, + + { 3008021, 3008026, 3008030, 3008035 }, + + { 3041044, 3041048, 3041053, 3041057 }, + + { 3074067, 3074071, 3074075, 3074079 }, + + { 3074322, 3074326, 3074330, 3074334 }, + + { 3107345, 3107349, 3107353, 3107356 }, + + { 3107600, 3107604, 3107607, 3107611 }, + + { 3107855, 3107859, 3107862, 3107865 }, + + { 3140878, 3140882, 3140885, 3140888 }, + + { 3173902, 3173905, 3173908, 3173911 }, + + { 3174157, 3174160, 3174163, 3174166 }, + + { 3207180, 3207183, 3207186, 3207189 }, + + { 3207436, 3207438, 3207441, 3207444 }, + + { 3207691, 3207694, 3207696, 3207699 }, + + { 3240715, 3240717, 3240719, 3240722 }, + + { 3240970, 3240972, 3240975, 3240977 }, + + { 3273994, 3273996, 3273998, 3274000 }, + + { 3274249, 3274251, 3274253, 3274255 }, + + { 3274505, 3274507, 3274508, 3274510 }, + + { 3307528, 3307530, 3307532, 3307534 }, + + { 3307784, 3307785, 3307787, 3307789 }, + + { 3308039, 3308041, 3308043, 3308044 }, + + { 3341063, 3341065, 3341066, 3341068 }, + + { 3341319, 3341320, 3341322, 3341323 }, + + { 3341574, 3341576, 3341577, 3341579 }, + + { 3374598, 3374599, 3374601, 3374602 }, + + { 3374598, 3374599, 3374600, 3374601 }, + + { 4194050, 4194050, 4194050, 4194050 }, + + }; + +/*****************************************************************************/ +/* Global Variable Initialization */ +/*****************************************************************************/ +const UWORD8 gau1_ih264_cabac_ctxt_init_table[NUM_CAB_INIT_IDC_PLUS_ONE][QP_RANGE][NUM_CABAC_CTXTS] = + + { + + { + + { + + 62, + 9, 74, 62, 9, 74, 126, 104, 10, 9, 12, 30, 61, 62, + 54, 14, 118, 6, 78, 65, 1, 14, 73, 13, 64, 20, 62, + 67, 90, 104, 126, 104, 67, 78, 65, 1, 86, 95, 2, + 18, 69, 81, 96, 8, 67, 86, 88, 5, 76, 94, 9, 69, + 81, 88, 67, 74, 74, 80, 72, 5, 22, 0, 0, 0, 83, + 86, 97, 72, 22, 1, 18, 78, 96, 126, 98, 101, 67, + 82, 94, 83, 110, 91, 102, 93, 126, 92, 89, 96, + 108, 17, 65, 6, 93, 74, 92, 87, 126, 9, 3, 4, 69, + 15, 68, 69, 88, 85, 78, 75, 77, 9, 13, 68, 13, 21, + 81, 0, 70, 67, 6, 76, 28, 64, 2, 28, 38, 39, 34, + 27, 93, 73, 73, 17, 14, 100, 10, 10, 10, 2, 7, 7, + 0, 3, 1, 6, 69, 6, 24, 12, 68, 64, 2, 0, 13, 24, + 19, 11, 15, 3, 4, 4, 30, 19, 20, 78, 3, 69, 35, + 23, 19, 14, 17, 19, 12, 16, 24, 1, 17, 9, 9, 5, 0, + 12, 6, 10, 11, 8, 18, 27, 10, 82, 8, 78, 17, 32, + 84, 56, 62, 60, 59, 62, 62, 57, 57, 54, 44, 36, + 33, 43, 29, 70, 67, 4, 67, 33, 31, 28, 34, 32, 25, + 20, 22, 0, 4, 64, 94, 89, 108, 76, 19, 18, 11, 64, + 4, 70, 75, 82, 102, 77, 39, 21, 15, 8, 4, 71, 83, + 87, 119, 5, 34, 27, 25, 20, 8, 5, 64, 74, 90, 70, + 34, 32, 21, 4, 5, 72, 81, 97, 5, 58, 49, 45, 36, + 23, 5, 70, 79, 85, 62, 106, 106, 87, 114, 110, 98, + 110, 106, 103, 107, 108, 112, 96, 95, 91, 93, 94, + 86, 67, 80, 85, 70, 3, 5, 2, 13, 13, 14, 9, 22, + 17, 12, 14, 11, 22, 16, 8, 22, 19, 13, 10, 14, 0, + 64, 69, 4, 70, 19, 32, 20, 10, 29, 25, 11, 23, 31, + 19, 25, 13, 6, 20, 52, 49, 52, 52, 54, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 34, 62, 62, 62, 62, 62, + 62, 54, 37, 36, 6, 82, 75, 97, 125, 62, 62, 62, + 57, 55, 53, 41, 44, 31, 32, 22, 19, 16, 65, 71, 3, + 0, 65, 39, 43, 40, 31, 40, 39, 23, 31, 34, 21, 6, + 10, 2, 86, 23, 12, 4, 79, 71, 69, 70, 66, 68, 73, + 69, 70, 67, 1, 70, 66, 65, 0, 62, 62, 62, 62, 62, + 60, 54, 36, 4, 66, 28, 21, 18, 15, 7, 3, 1, 66, + 76, 85, 81, 77, 81, 80, 73, 74, 83, 71, 67, 2, 66, + 66, 4, 4, 62, 62, 62, 62, 61, 57, 46, 29, 1 }, + + { + + 62, + 9, 74, 62, 9, 74, 125, 102, 11, 10, 12, 29, 60, + 62, 54, 14, 115, 6, 77, 64, 1, 14, 72, 12, 65, + 20, 62, 68, 91, 104, 124, 102, 67, 77, 64, 1, + 85, 93, 3, 18, 68, 80, 95, 8, 67, 85, 88, 5, 75, + 93, 9, 69, 80, 88, 66, 73, 73, 79, 71, 5, 22, 0, + 0, 0, 82, 86, 97, 71, 22, 1, 18, 77, 95, 124, + 96, 99, 65, 80, 92, 82, 108, 89, 100, 92, 125, + 91, 88, 95, 107, 18, 64, 7, 92, 73, 91, 86, 124, + 9, 3, 4, 69, 16, 68, 68, 87, 84, 77, 74, 76, 9, + 13, 67, 13, 21, 80, 0, 69, 67, 6, 75, 28, 64, 2, + 28, 37, 39, 34, 27, 92, 72, 72, 17, 14, 99, 10, + 10, 10, 3, 7, 7, 1, 4, 2, 6, 68, 6, 24, 12, 68, + 64, 2, 0, 13, 23, 19, 11, 15, 4, 5, 4, 29, 19, + 20, 77, 3, 69, 35, 23, 19, 14, 17, 19, 12, 16, + 24, 1, 17, 9, 9, 5, 0, 12, 6, 10, 11, 8, 18, 27, + 10, 81, 8, 77, 17, 31, 83, 55, 62, 59, 58, 61, + 62, 56, 56, 52, 43, 35, 32, 41, 28, 71, 67, 4, + 67, 32, 30, 27, 33, 31, 24, 19, 21, 0, 4, 64, + 93, 88, 107, 75, 20, 18, 11, 0, 5, 69, 74, 81, + 100, 76, 39, 21, 15, 8, 5, 70, 82, 86, 117, 5, + 35, 28, 25, 20, 9, 5, 64, 73, 89, 70, 35, 32, + 21, 4, 6, 71, 80, 96, 5, 58, 49, 45, 36, 23, 5, + 69, 78, 84, 62, 105, 105, 86, 112, 108, 97, 108, + 104, 101, 105, 106, 110, 95, 94, 90, 92, 92, 85, + 67, 79, 84, 69, 3, 5, 2, 13, 13, 13, 8, 22, 17, + 13, 14, 11, 22, 16, 8, 22, 19, 13, 10, 14, 0, + 64, 68, 5, 70, 19, 32, 20, 10, 29, 25, 12, 23, + 30, 19, 25, 13, 6, 19, 52, 49, 52, 51, 53, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 33, 62, 62, 62, + 62, 62, 62, 53, 36, 35, 6, 81, 74, 95, 122, 62, + 62, 62, 56, 53, 52, 40, 42, 30, 31, 21, 18, 15, + 66, 71, 3, 0, 66, 38, 42, 39, 30, 39, 38, 22, + 30, 33, 20, 5, 9, 1, 86, 23, 12, 4, 78, 70, 68, + 69, 65, 67, 71, 68, 69, 66, 3, 68, 65, 0, 2, 62, + 62, 62, 62, 62, 58, 51, 34, 2, 65, 29, 22, 19, + 16, 8, 4, 2, 65, 75, 84, 80, 76, 80, 78, 71, 73, + 82, 70, 66, 3, 65, 65, 4, 4, 62, 62, 62, 62, 58, + 54, 43, 26, 64 }, + + { + + 62, + 9, 74, 62, 9, 74, 123, 101, 11, 10, 12, 28, 59, + 61, 54, 14, 113, 6, 76, 0, 1, 13, 72, 11, 66, + 19, 60, 70, 92, 105, 121, 101, 67, 76, 0, 1, 85, + 92, 3, 17, 68, 80, 94, 8, 67, 85, 88, 5, 75, 92, + 9, 69, 80, 88, 66, 73, 73, 79, 71, 5, 22, 0, 0, + 0, 81, 86, 97, 71, 21, 1, 18, 77, 95, 122, 94, + 97, 64, 78, 91, 81, 107, 88, 99, 91, 123, 91, + 88, 95, 106, 18, 64, 7, 91, 73, 90, 86, 123, 9, + 3, 4, 69, 16, 68, 68, 87, 84, 77, 74, 76, 9, 13, + 67, 13, 21, 80, 0, 69, 67, 6, 75, 27, 64, 2, 27, + 36, 38, 33, 26, 91, 72, 72, 16, 13, 99, 9, 10, + 10, 3, 7, 7, 2, 4, 2, 6, 68, 6, 23, 12, 69, 64, + 2, 64, 13, 22, 19, 11, 14, 4, 5, 4, 28, 19, 19, + 77, 3, 70, 34, 23, 19, 14, 17, 19, 12, 16, 24, + 1, 17, 9, 9, 5, 0, 12, 6, 10, 11, 8, 17, 26, 9, + 81, 8, 77, 16, 30, 83, 53, 62, 57, 56, 59, 60, + 54, 54, 50, 41, 33, 30, 39, 26, 72, 67, 4, 68, + 31, 29, 26, 32, 29, 23, 18, 20, 64, 3, 65, 93, + 88, 106, 75, 20, 18, 11, 0, 5, 69, 74, 81, 99, + 75, 39, 21, 15, 8, 5, 70, 81, 85, 115, 5, 35, + 28, 25, 20, 9, 5, 64, 73, 88, 70, 35, 32, 21, 4, + 6, 71, 80, 95, 5, 57, 48, 44, 35, 23, 5, 69, 78, + 84, 62, 104, 104, 85, 111, 107, 96, 107, 103, + 100, 104, 105, 108, 94, 93, 90, 91, 91, 85, 68, + 79, 83, 69, 3, 4, 2, 12, 12, 12, 7, 21, 17, 13, + 14, 10, 21, 16, 8, 21, 18, 13, 10, 13, 0, 64, + 68, 5, 70, 18, 31, 19, 10, 28, 24, 12, 22, 29, + 19, 25, 12, 5, 17, 51, 48, 51, 50, 52, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 32, 62, 62, 62, 62, + 62, 62, 51, 35, 34, 6, 80, 74, 94, 120, 60, 60, + 62, 54, 51, 50, 38, 40, 29, 29, 20, 16, 14, 67, + 72, 2, 0, 67, 37, 41, 37, 28, 37, 36, 21, 28, + 31, 19, 4, 8, 0, 87, 22, 11, 3, 78, 70, 68, 68, + 65, 66, 70, 67, 68, 65, 4, 67, 64, 1, 3, 62, 62, + 62, 62, 60, 55, 48, 31, 0, 65, 29, 22, 19, 16, + 9, 4, 2, 65, 75, 84, 80, 75, 80, 77, 70, 73, 81, + 69, 65, 3, 65, 64, 4, 4, 62, 62, 62, 60, 55, 50, + 39, 23, 67 }, + + { + + 62, + 9, 74, 62, 9, 74, 121, 99, 12, 10, 11, 26, 57, + 60, 54, 14, 111, 6, 75, 1, 1, 12, 72, 10, 67, + 19, 58, 71, 93, 105, 118, 100, 67, 75, 1, 1, 84, + 91, 4, 17, 68, 79, 93, 7, 68, 85, 88, 5, 75, 92, + 9, 69, 80, 88, 65, 73, 73, 79, 70, 5, 22, 0, 0, + 0, 81, 86, 97, 70, 20, 1, 18, 77, 95, 120, 92, + 96, 1, 76, 90, 80, 105, 87, 98, 90, 121, 90, 88, + 94, 105, 18, 64, 7, 91, 73, 90, 85, 121, 9, 2, + 3, 70, 16, 68, 68, 86, 84, 76, 74, 75, 9, 13, + 67, 13, 20, 80, 0, 69, 67, 6, 75, 26, 64, 2, 26, + 35, 37, 32, 25, 91, 71, 72, 15, 13, 98, 9, 10, + 10, 3, 7, 7, 3, 4, 2, 6, 67, 6, 22, 12, 70, 64, + 2, 64, 12, 21, 19, 11, 13, 4, 5, 4, 26, 19, 18, + 77, 3, 70, 33, 23, 19, 14, 17, 19, 12, 16, 24, + 1, 16, 9, 9, 5, 0, 11, 5, 9, 10, 7, 16, 25, 9, + 81, 7, 77, 15, 28, 83, 52, 62, 55, 54, 57, 58, + 52, 52, 48, 39, 32, 29, 37, 24, 73, 67, 4, 68, + 30, 28, 25, 30, 28, 21, 17, 19, 65, 3, 65, 93, + 88, 106, 74, 20, 18, 11, 0, 5, 69, 74, 80, 98, + 75, 39, 21, 15, 8, 6, 69, 80, 84, 113, 5, 35, + 28, 25, 20, 10, 5, 64, 73, 88, 70, 35, 32, 20, + 4, 6, 71, 80, 94, 5, 57, 48, 43, 34, 23, 5, 69, + 77, 83, 62, 103, 103, 85, 110, 106, 95, 105, + 102, 99, 103, 103, 107, 94, 92, 90, 91, 89, 85, + 68, 79, 83, 69, 2, 4, 2, 11, 11, 11, 6, 21, 16, + 13, 13, 10, 21, 15, 8, 20, 18, 12, 10, 12, 0, + 65, 68, 5, 71, 18, 31, 18, 10, 27, 24, 12, 21, + 28, 18, 24, 11, 5, 16, 50, 47, 51, 49, 51, 61, + 62, 62, 62, 62, 62, 62, 62, 62, 31, 62, 62, 62, + 62, 62, 62, 49, 34, 33, 6, 79, 74, 93, 118, 58, + 58, 62, 52, 49, 48, 37, 38, 27, 28, 19, 15, 12, + 68, 73, 2, 64, 68, 36, 39, 36, 26, 35, 34, 19, + 27, 29, 17, 3, 6, 65, 88, 21, 10, 2, 78, 69, 68, + 68, 64, 66, 69, 66, 67, 64, 5, 66, 0, 3, 4, 62, + 62, 62, 62, 58, 52, 45, 28, 65, 64, 30, 23, 20, + 16, 10, 5, 2, 64, 74, 84, 79, 75, 79, 76, 69, + 73, 81, 69, 65, 3, 64, 0, 4, 4, 62, 62, 62, 57, + 52, 46, 35, 19, 69 }, + + { + + 62, + 9, 74, 62, 9, 74, 120, 98, 12, 10, 11, 25, 56, + 58, 54, 14, 108, 5, 74, 1, 1, 11, 72, 9, 68, 18, + 56, 73, 94, 106, 115, 99, 67, 74, 1, 1, 84, 90, + 4, 16, 68, 79, 93, 7, 68, 84, 88, 5, 75, 91, 8, + 70, 80, 88, 65, 72, 73, 78, 70, 5, 22, 0, 0, 0, + 80, 87, 97, 70, 19, 1, 18, 77, 95, 119, 91, 94, + 2, 75, 89, 79, 104, 85, 97, 89, 119, 90, 87, 94, + 104, 18, 64, 7, 90, 73, 89, 85, 120, 8, 2, 3, + 70, 16, 68, 68, 86, 84, 76, 74, 75, 9, 12, 67, + 13, 20, 80, 0, 69, 67, 6, 75, 26, 65, 2, 26, 34, + 36, 31, 24, 90, 71, 72, 14, 12, 98, 8, 10, 9, 3, + 7, 7, 4, 5, 2, 5, 67, 5, 21, 11, 71, 64, 2, 65, + 12, 20, 18, 10, 13, 5, 5, 4, 25, 18, 17, 77, 3, + 71, 33, 23, 19, 14, 17, 19, 12, 16, 23, 1, 16, + 9, 9, 5, 64, 11, 5, 9, 10, 7, 16, 24, 8, 81, 7, + 77, 14, 27, 83, 50, 62, 53, 52, 55, 56, 50, 50, + 46, 37, 30, 27, 34, 22, 74, 67, 3, 69, 29, 27, + 24, 29, 26, 20, 16, 17, 65, 2, 66, 93, 88, 105, + 74, 20, 18, 11, 0, 5, 69, 74, 80, 97, 74, 39, + 21, 15, 8, 6, 69, 80, 84, 111, 5, 35, 28, 25, + 20, 10, 5, 64, 73, 87, 70, 35, 31, 20, 4, 6, 71, + 80, 94, 5, 56, 47, 42, 33, 23, 5, 69, 77, 83, + 62, 102, 102, 84, 108, 105, 94, 104, 100, 98, + 101, 102, 105, 93, 92, 89, 90, 88, 84, 69, 79, + 82, 69, 2, 3, 1, 10, 10, 10, 5, 20, 16, 13, 13, + 9, 20, 15, 8, 19, 17, 12, 9, 11, 64, 65, 68, 5, + 71, 17, 30, 17, 10, 26, 23, 12, 20, 27, 18, 24, + 10, 4, 14, 49, 47, 50, 48, 49, 60, 62, 62, 62, + 62, 62, 62, 62, 62, 29, 62, 62, 62, 62, 62, 62, + 47, 33, 31, 6, 78, 73, 92, 116, 57, 56, 60, 51, + 47, 46, 35, 36, 26, 26, 17, 13, 11, 69, 74, 1, + 64, 69, 34, 38, 34, 25, 33, 32, 18, 25, 27, 16, + 2, 5, 66, 88, 20, 10, 1, 78, 69, 67, 67, 64, 65, + 68, 66, 66, 0, 6, 65, 1, 4, 5, 62, 62, 62, 61, + 55, 49, 42, 25, 68, 64, 30, 23, 20, 17, 10, 5, + 3, 64, 74, 83, 79, 74, 79, 75, 68, 73, 80, 68, + 64, 3, 64, 1, 4, 4, 62, 62, 61, 54, 49, 42, 31, + 16, 72 }, + + { + + 62, + 9, 74, 62, 9, 74, 118, 96, 12, 10, 10, 23, 54, + 57, 54, 14, 106, 5, 73, 2, 1, 11, 71, 8, 69, 18, + 54, 75, 95, 106, 112, 97, 67, 73, 2, 1, 84, 89, + 4, 16, 68, 79, 92, 7, 69, 84, 88, 5, 75, 90, 8, + 70, 80, 88, 64, 72, 72, 78, 69, 5, 22, 0, 0, 0, + 80, 87, 97, 69, 18, 1, 18, 76, 95, 117, 89, 93, + 4, 73, 87, 78, 103, 84, 96, 88, 117, 89, 87, 93, + 103, 18, 64, 7, 90, 73, 89, 84, 118, 8, 2, 3, + 70, 16, 68, 67, 85, 84, 76, 74, 74, 9, 12, 67, + 13, 20, 79, 0, 68, 67, 6, 75, 25, 65, 2, 25, 33, + 36, 30, 23, 89, 70, 72, 13, 12, 97, 8, 10, 9, 3, + 7, 7, 5, 5, 2, 5, 67, 5, 20, 11, 72, 64, 2, 65, + 11, 19, 18, 10, 12, 5, 5, 4, 24, 18, 16, 77, 3, + 71, 32, 23, 19, 14, 17, 19, 12, 16, 23, 1, 16, + 9, 9, 5, 64, 11, 5, 8, 10, 7, 15, 23, 8, 81, 6, + 77, 13, 26, 83, 49, 61, 52, 51, 53, 54, 48, 48, + 44, 35, 28, 25, 32, 21, 75, 67, 3, 69, 28, 26, + 23, 28, 25, 18, 15, 16, 66, 2, 66, 93, 88, 105, + 74, 20, 18, 11, 0, 5, 68, 73, 79, 96, 74, 39, + 21, 15, 8, 6, 68, 79, 83, 109, 5, 35, 28, 25, + 20, 10, 5, 64, 73, 86, 70, 36, 31, 19, 4, 6, 71, + 80, 93, 5, 56, 46, 41, 32, 23, 5, 69, 77, 82, + 62, 101, 101, 83, 107, 104, 93, 103, 99, 97, + 100, 100, 103, 92, 91, 89, 90, 87, 84, 69, 78, + 81, 69, 1, 3, 1, 10, 9, 9, 4, 19, 15, 13, 12, 9, + 20, 15, 8, 18, 16, 12, 9, 10, 64, 65, 68, 5, 71, + 16, 30, 17, 10, 25, 22, 12, 19, 26, 17, 23, 9, + 3, 12, 48, 46, 50, 47, 48, 58, 62, 62, 62, 62, + 62, 62, 62, 62, 28, 62, 62, 62, 62, 62, 61, 45, + 32, 30, 6, 77, 73, 91, 114, 55, 55, 58, 49, 45, + 44, 34, 34, 25, 24, 16, 11, 9, 70, 75, 1, 64, + 70, 33, 36, 32, 23, 32, 31, 16, 24, 26, 14, 1, + 4, 67, 89, 20, 9, 0, 77, 68, 67, 67, 0, 64, 67, + 65, 65, 1, 8, 64, 2, 5, 7, 62, 62, 62, 58, 53, + 46, 39, 22, 70, 64, 31, 24, 21, 17, 11, 5, 3, 0, + 73, 83, 79, 73, 78, 74, 67, 72, 79, 68, 64, 3, + 0, 2, 4, 4, 62, 62, 58, 51, 46, 39, 27, 12, 75 }, + + { + + 62, + 9, 75, 62, 9, 75, 116, 95, 13, 10, 10, 22, 53, + 56, 54, 14, 104, 5, 73, 3, 1, 10, 71, 7, 70, 17, + 53, 76, 96, 107, 109, 96, 67, 73, 3, 1, 83, 88, + 5, 15, 67, 78, 91, 6, 69, 84, 88, 5, 74, 90, 8, + 70, 79, 88, 64, 72, 72, 78, 69, 5, 22, 0, 0, 0, + 79, 87, 97, 69, 18, 0, 18, 76, 94, 115, 87, 91, + 5, 71, 86, 77, 101, 83, 95, 88, 116, 89, 87, 93, + 103, 19, 64, 7, 89, 72, 88, 84, 117, 8, 1, 2, + 71, 16, 68, 67, 85, 84, 75, 74, 74, 9, 12, 66, + 13, 19, 79, 0, 68, 67, 6, 75, 24, 65, 2, 24, 32, + 35, 30, 23, 89, 70, 72, 13, 11, 97, 7, 10, 9, 3, + 7, 7, 5, 5, 2, 5, 66, 5, 19, 11, 72, 65, 2, 66, + 11, 18, 18, 10, 11, 5, 5, 4, 22, 18, 15, 77, 3, + 72, 31, 23, 18, 14, 17, 19, 12, 16, 23, 1, 15, + 9, 8, 5, 64, 10, 4, 8, 9, 6, 14, 22, 7, 81, 6, + 76, 12, 24, 83, 47, 59, 50, 49, 51, 52, 46, 46, + 42, 33, 27, 24, 30, 19, 76, 67, 3, 70, 27, 25, + 22, 26, 23, 17, 14, 15, 67, 1, 67, 93, 88, 104, + 73, 20, 18, 11, 1, 5, 68, 73, 79, 95, 73, 38, + 21, 15, 8, 7, 68, 78, 82, 107, 5, 36, 28, 25, + 20, 11, 5, 64, 72, 86, 70, 36, 31, 19, 4, 6, 70, + 79, 92, 5, 55, 46, 40, 32, 23, 5, 68, 76, 82, + 62, 101, 100, 83, 106, 103, 92, 101, 98, 96, 99, + 99, 102, 92, 90, 89, 89, 85, 84, 70, 78, 81, 69, + 1, 2, 1, 9, 8, 8, 3, 19, 15, 13, 12, 8, 19, 14, + 8, 18, 16, 11, 9, 10, 64, 66, 68, 5, 72, 16, 29, + 16, 9, 24, 22, 13, 19, 25, 17, 23, 9, 3, 11, 47, + 45, 49, 46, 47, 57, 62, 62, 62, 62, 62, 62, 62, + 61, 27, 62, 62, 62, 62, 62, 59, 43, 31, 29, 6, + 76, 73, 89, 111, 53, 53, 56, 47, 43, 42, 32, 32, + 23, 23, 15, 10, 8, 71, 76, 0, 65, 71, 32, 35, + 31, 21, 30, 29, 15, 22, 24, 13, 64, 2, 69, 90, + 19, 8, 64, 77, 68, 67, 66, 0, 64, 65, 64, 64, 2, + 9, 1, 3, 7, 8, 62, 62, 60, 56, 50, 44, 36, 20, + 72, 0, 31, 24, 21, 17, 12, 6, 3, 0, 73, 83, 78, + 73, 78, 73, 66, 72, 79, 67, 0, 3, 0, 3, 4, 4, + 62, 62, 56, 48, 42, 35, 24, 9, 77 }, + + { + + 62, + 9, 75, 62, 9, 75, 114, 93, 13, 10, 9, 20, 51, + 54, 54, 14, 101, 4, 72, 3, 1, 9, 71, 6, 71, 17, + 51, 78, 97, 107, 106, 95, 67, 72, 3, 1, 83, 87, + 5, 15, 67, 78, 91, 6, 70, 83, 88, 5, 74, 89, 7, + 70, 79, 88, 0, 71, 72, 77, 68, 5, 22, 0, 0, 0, + 79, 87, 97, 68, 17, 0, 18, 76, 94, 114, 85, 90, + 7, 69, 85, 76, 100, 81, 94, 87, 114, 88, 86, 92, + 102, 19, 64, 7, 89, 72, 88, 83, 115, 7, 1, 2, + 71, 16, 68, 67, 84, 84, 75, 74, 73, 9, 11, 66, + 13, 19, 79, 0, 68, 67, 6, 75, 24, 65, 2, 24, 31, + 34, 29, 22, 88, 69, 72, 12, 11, 96, 7, 10, 8, 3, + 7, 7, 6, 6, 2, 5, 66, 5, 18, 11, 73, 65, 2, 66, + 10, 17, 17, 10, 11, 6, 5, 4, 21, 17, 14, 77, 3, + 72, 31, 23, 18, 14, 17, 19, 12, 16, 23, 1, 15, + 9, 8, 5, 64, 10, 4, 7, 9, 6, 14, 21, 7, 81, 5, + 76, 11, 23, 83, 46, 57, 48, 47, 49, 50, 44, 44, + 40, 31, 25, 22, 27, 17, 77, 67, 2, 70, 26, 24, + 21, 25, 22, 15, 13, 14, 67, 1, 67, 93, 88, 104, + 73, 20, 18, 11, 1, 5, 68, 73, 78, 94, 73, 38, + 21, 15, 8, 7, 67, 77, 82, 105, 5, 36, 28, 25, + 20, 11, 5, 64, 72, 85, 70, 36, 30, 18, 4, 6, 70, + 79, 92, 5, 55, 45, 39, 31, 23, 5, 68, 76, 81, + 62, 100, 99, 82, 104, 102, 91, 100, 96, 95, 97, + 97, 100, 91, 89, 88, 89, 84, 83, 70, 78, 80, 69, + 0, 2, 0, 8, 7, 7, 2, 18, 14, 13, 11, 8, 19, 14, + 8, 17, 15, 11, 8, 9, 64, 66, 68, 5, 72, 15, 29, + 15, 9, 23, 21, 13, 18, 24, 16, 22, 8, 2, 9, 46, + 45, 49, 45, 45, 55, 62, 62, 62, 62, 62, 62, 62, + 59, 25, 62, 62, 62, 62, 62, 56, 41, 30, 28, 6, + 75, 72, 88, 109, 52, 51, 54, 46, 41, 40, 31, 30, + 22, 21, 13, 8, 6, 72, 77, 0, 65, 72, 30, 33, 29, + 20, 28, 27, 13, 21, 22, 11, 65, 1, 70, 90, 18, + 8, 65, 77, 67, 66, 66, 1, 0, 64, 0, 0, 3, 10, 2, + 4, 8, 9, 62, 61, 58, 53, 48, 41, 33, 17, 74, 0, + 32, 25, 22, 18, 13, 6, 4, 1, 72, 82, 78, 72, 77, + 72, 65, 72, 78, 67, 0, 3, 1, 4, 4, 4, 62, 62, + 53, 45, 39, 31, 20, 5, 80 }, + + { + + 62, + 8, 75, 62, 8, 75, 113, 92, 13, 10, 9, 19, 50, + 53, 54, 14, 99, 4, 71, 4, 1, 8, 71, 5, 73, 16, + 49, 80, 98, 108, 104, 94, 67, 71, 4, 1, 83, 86, + 5, 14, 67, 78, 90, 5, 70, 83, 89, 5, 74, 89, 7, + 71, 79, 88, 0, 71, 72, 77, 68, 5, 22, 0, 0, 0, + 78, 88, 97, 68, 16, 0, 18, 76, 94, 112, 84, 88, + 8, 68, 84, 75, 99, 80, 93, 86, 112, 88, 86, 92, + 101, 19, 64, 7, 88, 72, 87, 83, 114, 7, 0, 1, + 72, 16, 68, 67, 84, 84, 75, 74, 73, 8, 11, 66, + 13, 18, 79, 0, 68, 67, 5, 75, 23, 66, 2, 23, 29, + 33, 28, 21, 88, 69, 72, 11, 10, 96, 6, 9, 8, 3, + 7, 7, 7, 6, 2, 4, 66, 4, 17, 10, 74, 65, 2, 67, + 10, 16, 17, 9, 10, 6, 5, 4, 19, 17, 13, 77, 3, + 73, 30, 22, 18, 14, 17, 18, 11, 16, 22, 0, 14, + 9, 8, 4, 65, 9, 3, 7, 8, 5, 13, 20, 6, 81, 5, + 76, 10, 21, 83, 44, 55, 46, 45, 47, 47, 42, 42, + 38, 29, 23, 20, 25, 15, 78, 67, 2, 71, 25, 22, + 19, 23, 20, 14, 11, 12, 68, 0, 68, 93, 88, 103, + 73, 20, 18, 11, 1, 5, 68, 73, 78, 93, 72, 38, + 21, 15, 8, 7, 67, 77, 81, 104, 5, 36, 28, 25, + 19, 11, 5, 64, 72, 85, 70, 36, 30, 18, 4, 6, 70, + 79, 91, 5, 54, 44, 38, 30, 22, 5, 68, 76, 81, + 62, 99, 98, 82, 103, 101, 91, 99, 95, 94, 96, + 96, 99, 91, 89, 88, 88, 83, 83, 71, 78, 80, 69, + 0, 1, 0, 7, 6, 5, 1, 17, 14, 13, 11, 7, 18, 13, + 7, 16, 14, 10, 8, 8, 65, 67, 68, 5, 73, 14, 28, + 14, 9, 22, 20, 13, 17, 23, 16, 22, 7, 1, 7, 45, + 44, 48, 43, 44, 54, 62, 62, 62, 62, 62, 62, 62, + 56, 24, 62, 62, 62, 62, 61, 54, 39, 28, 26, 6, + 75, 72, 87, 107, 50, 49, 52, 44, 38, 38, 29, 28, + 20, 19, 12, 6, 5, 73, 78, 64, 66, 73, 29, 32, + 27, 18, 26, 25, 12, 19, 20, 10, 66, 64, 72, 91, + 17, 7, 66, 77, 67, 66, 65, 1, 0, 0, 0, 1, 4, 11, + 3, 5, 9, 10, 61, 59, 56, 51, 45, 38, 30, 14, 77, + 0, 32, 25, 22, 18, 13, 6, 4, 1, 72, 82, 78, 72, + 77, 71, 64, 72, 78, 66, 1, 3, 1, 4, 4, 3, 62, + 61, 51, 42, 36, 27, 16, 2, 83 }, + + { + + 62, + 8, 75, 62, 8, 75, 111, 91, 14, 10, 9, 18, 49, + 52, 54, 14, 97, 4, 70, 5, 1, 8, 70, 4, 74, 15, + 47, 81, 99, 109, 101, 92, 67, 70, 5, 1, 82, 85, + 6, 13, 67, 77, 89, 5, 70, 83, 89, 5, 74, 88, 7, + 71, 79, 88, 0, 71, 71, 77, 68, 5, 22, 0, 0, 0, + 77, 88, 97, 68, 15, 0, 18, 75, 94, 110, 82, 86, + 9, 66, 82, 74, 97, 79, 91, 85, 110, 88, 86, 92, + 100, 19, 64, 7, 87, 72, 86, 82, 113, 7, 0, 1, + 72, 16, 68, 66, 83, 83, 74, 74, 73, 8, 11, 66, + 13, 18, 78, 0, 67, 67, 5, 74, 22, 66, 2, 22, 28, + 33, 27, 20, 87, 69, 71, 10, 9, 96, 5, 9, 8, 4, + 7, 7, 8, 6, 2, 4, 65, 4, 17, 10, 75, 65, 2, 68, + 10, 15, 17, 9, 9, 6, 5, 4, 18, 17, 13, 77, 3, + 74, 29, 22, 18, 14, 17, 18, 11, 16, 22, 0, 14, + 9, 8, 4, 65, 9, 3, 7, 8, 5, 12, 20, 6, 81, 5, + 76, 9, 20, 83, 42, 54, 45, 44, 45, 45, 41, 41, + 36, 27, 22, 19, 23, 14, 79, 67, 2, 72, 24, 21, + 18, 22, 19, 13, 10, 11, 69, 64, 69, 93, 87, 102, + 72, 21, 18, 11, 1, 6, 67, 72, 77, 92, 71, 38, + 21, 15, 8, 8, 67, 76, 80, 102, 5, 36, 28, 25, + 19, 12, 5, 64, 72, 84, 70, 37, 30, 18, 4, 7, 70, + 79, 90, 5, 54, 44, 38, 29, 22, 5, 68, 75, 80, + 62, 98, 97, 81, 102, 99, 90, 97, 94, 92, 95, 95, + 97, 90, 88, 88, 87, 81, 83, 72, 77, 79, 69, 0, + 0, 0, 7, 5, 4, 0, 17, 14, 13, 11, 7, 17, 13, 7, + 15, 14, 10, 8, 7, 65, 67, 67, 6, 73, 14, 27, 14, + 9, 22, 20, 13, 16, 22, 16, 22, 6, 1, 6, 45, 43, + 47, 42, 43, 53, 60, 60, 62, 62, 62, 62, 62, 54, + 23, 62, 62, 62, 62, 58, 52, 38, 27, 25, 6, 74, + 72, 86, 105, 48, 48, 50, 42, 36, 37, 28, 26, 19, + 18, 11, 5, 4, 74, 78, 64, 66, 74, 28, 31, 26, + 16, 25, 24, 11, 18, 19, 9, 67, 65, 73, 92, 17, + 6, 66, 76, 67, 66, 64, 2, 1, 1, 1, 2, 5, 13, 4, + 6, 11, 12, 60, 58, 54, 49, 42, 35, 27, 11, 79, + 1, 32, 25, 23, 18, 14, 7, 4, 2, 71, 82, 77, 71, + 77, 70, 1, 71, 77, 65, 2, 3, 2, 5, 4, 3, 62, 59, + 49, 40, 33, 24, 12, 64, 85 }, + + { + + 62, + 8, 75, 62, 8, 75, 109, 89, 14, 10, 8, 16, 47, + 50, 54, 14, 94, 3, 69, 5, 1, 7, 70, 3, 75, 15, + 45, 83, 100, 109, 98, 91, 67, 69, 5, 1, 82, 84, + 6, 13, 67, 77, 89, 5, 71, 82, 89, 5, 74, 87, 6, + 71, 79, 88, 1, 70, 71, 76, 67, 5, 22, 0, 0, 0, + 77, 88, 97, 67, 14, 0, 18, 75, 94, 109, 80, 85, + 11, 64, 81, 73, 96, 77, 90, 84, 108, 87, 85, 91, + 99, 19, 64, 7, 87, 72, 86, 82, 111, 6, 0, 1, 72, + 16, 68, 66, 83, 83, 74, 74, 72, 8, 10, 66, 13, + 18, 78, 0, 67, 67, 5, 74, 22, 66, 2, 22, 27, 32, + 26, 19, 86, 68, 71, 9, 9, 95, 5, 9, 7, 4, 7, 7, + 9, 7, 2, 4, 65, 4, 16, 10, 76, 65, 2, 68, 9, 14, + 16, 9, 9, 7, 5, 4, 17, 16, 12, 77, 3, 74, 29, + 22, 18, 14, 17, 18, 11, 16, 22, 0, 14, 9, 8, 4, + 65, 9, 3, 6, 8, 5, 12, 19, 5, 81, 4, 76, 8, 19, + 83, 41, 52, 43, 42, 43, 43, 39, 39, 34, 25, 20, + 17, 20, 12, 80, 67, 1, 72, 23, 20, 17, 21, 17, + 11, 9, 10, 69, 64, 69, 93, 87, 102, 72, 21, 18, + 11, 1, 6, 67, 72, 77, 91, 71, 38, 21, 15, 8, 8, + 66, 75, 80, 100, 5, 36, 28, 25, 19, 12, 5, 64, + 72, 83, 70, 37, 29, 17, 4, 7, 70, 79, 90, 5, 53, + 43, 37, 28, 22, 5, 68, 75, 80, 62, 97, 96, 80, + 100, 98, 89, 96, 92, 91, 93, 93, 95, 89, 87, 87, + 87, 80, 82, 72, 77, 78, 69, 64, 0, 64, 6, 4, 3, + 64, 16, 13, 13, 10, 6, 17, 13, 7, 14, 13, 10, 7, + 6, 65, 67, 67, 6, 73, 13, 27, 13, 9, 21, 19, 13, + 15, 21, 15, 21, 5, 0, 4, 44, 43, 47, 41, 41, 51, + 58, 58, 62, 62, 62, 62, 62, 52, 21, 59, 62, 59, + 62, 56, 49, 36, 26, 24, 6, 73, 71, 85, 103, 47, + 46, 48, 41, 34, 35, 26, 24, 18, 16, 9, 3, 2, 75, + 79, 65, 66, 75, 26, 29, 24, 15, 23, 22, 9, 16, + 17, 7, 68, 66, 74, 92, 16, 6, 67, 76, 66, 65, + 64, 2, 2, 2, 2, 3, 6, 14, 5, 7, 12, 13, 60, 56, + 52, 46, 40, 32, 24, 8, 81, 1, 33, 26, 23, 19, + 15, 7, 5, 2, 71, 81, 77, 70, 76, 69, 2, 71, 76, + 65, 2, 3, 2, 6, 4, 3, 62, 57, 46, 37, 30, 20, 8, + 68, 88 }, + + { + + 62, + 8, 76, 62, 8, 76, 107, 88, 15, 10, 8, 15, 46, + 49, 54, 14, 92, 3, 69, 6, 1, 6, 70, 2, 76, 14, + 44, 84, 101, 110, 95, 90, 67, 69, 6, 1, 81, 83, + 7, 12, 66, 76, 88, 4, 71, 82, 89, 5, 73, 87, 6, + 71, 78, 88, 1, 70, 71, 76, 67, 5, 22, 0, 0, 0, + 76, 88, 97, 67, 14, 64, 18, 75, 93, 107, 78, 83, + 12, 1, 80, 72, 94, 76, 89, 84, 107, 87, 85, 91, + 99, 20, 64, 7, 86, 71, 85, 81, 110, 6, 64, 0, + 73, 16, 68, 66, 82, 83, 73, 74, 72, 8, 10, 65, + 13, 17, 78, 0, 67, 67, 5, 74, 21, 66, 2, 21, 26, + 31, 26, 19, 86, 68, 71, 9, 8, 95, 4, 9, 7, 4, 7, + 7, 9, 7, 2, 4, 64, 4, 15, 10, 76, 66, 2, 69, 9, + 13, 16, 9, 8, 7, 5, 4, 15, 16, 11, 77, 3, 75, + 28, 22, 17, 14, 17, 18, 11, 16, 22, 0, 13, 9, 7, + 4, 65, 8, 2, 6, 7, 4, 11, 18, 5, 81, 4, 75, 7, + 17, 83, 39, 50, 41, 40, 41, 41, 37, 37, 32, 23, + 19, 16, 18, 10, 81, 67, 1, 73, 22, 19, 16, 19, + 16, 10, 8, 9, 70, 65, 70, 93, 87, 101, 71, 21, + 18, 11, 2, 6, 67, 72, 76, 90, 70, 37, 21, 15, 8, + 9, 66, 74, 79, 98, 5, 37, 28, 25, 19, 13, 5, 64, + 71, 83, 70, 37, 29, 17, 4, 7, 69, 78, 89, 5, 53, + 43, 36, 28, 22, 5, 67, 74, 79, 62, 97, 95, 80, + 99, 97, 88, 94, 91, 90, 92, 92, 94, 89, 86, 87, + 86, 78, 82, 73, 77, 78, 69, 64, 64, 64, 5, 3, 2, + 65, 16, 13, 13, 10, 6, 16, 12, 7, 14, 13, 9, 7, + 6, 65, 68, 67, 6, 74, 13, 26, 12, 8, 20, 19, 14, + 15, 20, 15, 21, 5, 0, 3, 43, 42, 46, 40, 40, 50, + 56, 56, 61, 60, 62, 62, 60, 49, 20, 57, 62, 56, + 62, 53, 47, 34, 25, 23, 6, 72, 71, 83, 100, 45, + 44, 46, 39, 32, 33, 25, 22, 16, 15, 8, 2, 1, 76, + 80, 65, 67, 76, 25, 28, 23, 13, 21, 20, 8, 15, + 15, 6, 70, 68, 76, 93, 15, 5, 68, 76, 66, 65, 0, + 3, 2, 4, 3, 4, 7, 15, 7, 8, 14, 14, 59, 55, 50, + 44, 37, 30, 21, 6, 83, 2, 33, 26, 24, 19, 16, 8, + 5, 3, 70, 81, 76, 70, 76, 68, 3, 71, 76, 64, 3, + 3, 3, 7, 4, 3, 62, 55, 44, 34, 26, 16, 5, 71, 90 }, + + { + + 62, + 8, 76, 62, 8, 76, 106, 86, 15, 10, 7, 13, 44, + 48, 54, 14, 90, 3, 68, 7, 1, 5, 70, 1, 77, 14, + 42, 86, 102, 110, 92, 89, 67, 68, 7, 1, 81, 82, + 7, 12, 66, 76, 87, 4, 72, 82, 89, 5, 73, 86, 6, + 72, 78, 88, 2, 70, 71, 76, 66, 5, 22, 0, 0, 0, + 76, 89, 97, 66, 13, 64, 18, 75, 93, 105, 77, 82, + 14, 2, 79, 71, 93, 75, 88, 83, 105, 86, 85, 90, + 98, 20, 64, 7, 86, 71, 85, 81, 108, 6, 64, 0, + 73, 16, 68, 66, 82, 83, 73, 74, 71, 8, 10, 65, + 13, 17, 78, 0, 67, 67, 5, 74, 20, 67, 2, 20, 25, + 30, 25, 18, 85, 67, 71, 8, 8, 94, 4, 9, 7, 4, 7, + 7, 10, 7, 2, 3, 64, 3, 14, 9, 77, 66, 2, 69, 8, + 12, 16, 8, 7, 7, 5, 4, 14, 16, 10, 77, 3, 75, + 27, 22, 17, 14, 17, 18, 11, 16, 21, 0, 13, 9, 7, + 4, 66, 8, 2, 5, 7, 4, 10, 17, 4, 81, 3, 75, 6, + 16, 83, 38, 48, 39, 38, 39, 39, 35, 35, 30, 21, + 17, 14, 16, 8, 82, 67, 1, 73, 21, 18, 15, 18, + 14, 8, 7, 7, 71, 65, 70, 93, 87, 101, 71, 21, + 18, 11, 2, 6, 67, 72, 76, 89, 70, 37, 21, 15, 8, + 9, 65, 74, 78, 96, 5, 37, 28, 25, 19, 13, 5, 64, + 71, 82, 70, 37, 29, 16, 4, 7, 69, 78, 88, 5, 52, + 42, 35, 27, 22, 5, 67, 74, 79, 62, 96, 94, 79, + 98, 96, 87, 93, 90, 89, 91, 90, 92, 88, 86, 87, + 86, 77, 82, 73, 77, 77, 69, 65, 64, 64, 4, 2, 1, + 66, 15, 12, 13, 9, 5, 16, 12, 7, 13, 12, 9, 7, + 5, 66, 68, 67, 6, 74, 12, 26, 11, 8, 19, 18, 14, + 14, 19, 14, 20, 4, 64, 1, 42, 41, 46, 39, 39, + 48, 54, 54, 59, 57, 62, 62, 57, 47, 19, 54, 62, + 53, 58, 50, 44, 32, 24, 21, 6, 71, 71, 82, 98, + 43, 42, 44, 37, 30, 31, 23, 20, 15, 13, 7, 0, + 64, 77, 81, 66, 67, 77, 24, 26, 21, 11, 19, 18, + 6, 13, 13, 4, 71, 69, 77, 94, 14, 4, 69, 76, 65, + 65, 0, 3, 3, 5, 3, 5, 8, 16, 8, 9, 15, 15, 59, + 53, 48, 41, 35, 27, 18, 3, 86, 2, 34, 27, 24, + 19, 16, 8, 5, 3, 70, 81, 76, 69, 75, 67, 4, 71, + 75, 64, 3, 3, 3, 8, 4, 3, 61, 53, 41, 31, 23, + 12, 1, 75, 93 }, + + { + + 62, + 8, 76, 62, 8, 76, 104, 85, 15, 10, 7, 12, 43, + 46, 54, 14, 87, 2, 67, 7, 1, 5, 69, 0, 78, 13, + 40, 88, 103, 111, 89, 87, 67, 67, 7, 1, 81, 81, + 7, 11, 66, 76, 87, 4, 72, 81, 89, 5, 73, 85, 5, + 72, 78, 88, 2, 69, 70, 75, 66, 5, 22, 0, 0, 0, + 75, 89, 97, 66, 12, 64, 18, 74, 93, 104, 75, 80, + 15, 4, 77, 70, 92, 73, 87, 82, 103, 86, 84, 90, + 97, 20, 64, 7, 85, 71, 84, 80, 107, 5, 64, 0, + 73, 16, 68, 65, 81, 83, 73, 74, 71, 8, 9, 65, + 13, 17, 77, 0, 66, 67, 5, 74, 20, 67, 2, 20, 24, + 30, 24, 17, 84, 67, 71, 7, 7, 94, 3, 9, 6, 4, 7, + 7, 11, 8, 2, 3, 64, 3, 13, 9, 78, 66, 2, 70, 8, + 11, 15, 8, 7, 8, 5, 4, 13, 15, 9, 77, 3, 76, 27, + 22, 17, 14, 17, 18, 11, 16, 21, 0, 13, 9, 7, 4, + 66, 8, 2, 5, 7, 4, 10, 16, 4, 81, 3, 75, 5, 15, + 83, 36, 46, 38, 37, 37, 37, 33, 33, 28, 19, 15, + 12, 13, 7, 83, 67, 0, 74, 20, 17, 14, 17, 13, 7, + 6, 6, 71, 66, 71, 93, 87, 100, 71, 21, 18, 11, + 2, 6, 66, 71, 75, 88, 69, 37, 21, 15, 8, 9, 65, + 73, 78, 94, 5, 37, 28, 25, 19, 13, 5, 64, 71, + 81, 70, 38, 28, 16, 4, 7, 69, 78, 88, 5, 52, 41, + 34, 26, 22, 5, 67, 74, 78, 62, 95, 93, 78, 96, + 95, 86, 92, 88, 88, 89, 89, 90, 87, 85, 86, 85, + 76, 81, 74, 76, 76, 69, 65, 65, 65, 4, 1, 0, 67, + 14, 12, 13, 9, 5, 15, 12, 7, 12, 11, 9, 6, 4, + 66, 68, 67, 6, 74, 11, 25, 11, 8, 18, 17, 14, + 13, 18, 14, 20, 3, 65, 64, 41, 41, 45, 38, 37, + 47, 52, 52, 57, 55, 62, 61, 54, 45, 17, 51, 62, + 50, 54, 48, 42, 30, 23, 20, 6, 70, 70, 81, 96, + 42, 41, 42, 36, 28, 29, 22, 18, 14, 11, 5, 65, + 65, 78, 82, 66, 67, 78, 22, 25, 19, 10, 18, 17, + 5, 12, 12, 3, 72, 70, 78, 94, 14, 4, 70, 75, 65, + 64, 1, 4, 4, 6, 4, 6, 9, 18, 9, 10, 16, 17, 58, + 51, 46, 39, 32, 24, 15, 0, 88, 2, 34, 27, 25, + 20, 17, 8, 6, 4, 69, 80, 76, 68, 75, 66, 5, 70, + 74, 0, 4, 3, 4, 9, 4, 3, 59, 51, 39, 28, 20, 9, + 66, 78, 96 }, + + { + + 61, + 8, 76, 61, 8, 76, 102, 83, 16, 10, 6, 10, 41, + 45, 54, 14, 85, 2, 66, 8, 1, 4, 69, 64, 79, 13, + 38, 89, 104, 111, 86, 86, 67, 66, 8, 1, 80, 80, + 8, 11, 66, 75, 86, 3, 73, 81, 89, 5, 73, 85, 5, + 72, 78, 88, 3, 69, 70, 75, 65, 5, 22, 0, 0, 0, + 75, 89, 97, 65, 11, 64, 18, 74, 93, 102, 73, 79, + 17, 6, 76, 69, 90, 72, 86, 81, 101, 85, 84, 89, + 96, 20, 64, 7, 85, 71, 84, 80, 105, 5, 65, 64, + 74, 16, 68, 65, 81, 83, 72, 74, 70, 8, 9, 65, + 13, 16, 77, 0, 66, 67, 5, 74, 19, 67, 2, 19, 23, + 29, 23, 16, 84, 66, 71, 6, 7, 93, 3, 9, 6, 4, 7, + 7, 12, 8, 2, 3, 0, 3, 12, 9, 79, 66, 2, 70, 7, + 10, 15, 8, 6, 8, 5, 4, 11, 15, 8, 77, 3, 76, 26, + 22, 17, 14, 17, 18, 11, 16, 21, 0, 12, 9, 7, 4, + 66, 7, 1, 4, 6, 3, 9, 15, 3, 81, 2, 75, 4, 13, + 83, 35, 44, 36, 35, 35, 35, 31, 31, 26, 17, 14, + 11, 11, 5, 84, 67, 0, 74, 19, 16, 13, 15, 11, 5, + 5, 5, 72, 66, 71, 93, 87, 100, 70, 21, 18, 11, + 2, 6, 66, 71, 75, 87, 69, 37, 21, 15, 8, 10, 64, + 72, 77, 92, 5, 37, 28, 25, 19, 14, 5, 64, 71, + 81, 70, 38, 28, 15, 4, 7, 69, 78, 87, 5, 51, 41, + 33, 25, 22, 5, 67, 73, 78, 62, 94, 92, 78, 95, + 94, 85, 90, 87, 87, 88, 87, 89, 87, 84, 86, 85, + 74, 81, 74, 76, 76, 69, 66, 65, 65, 3, 0, 64, + 68, 14, 11, 13, 8, 4, 15, 11, 7, 11, 11, 8, 6, + 3, 66, 69, 67, 6, 75, 11, 25, 10, 8, 17, 17, 14, + 12, 17, 13, 19, 2, 65, 65, 40, 40, 45, 37, 36, + 45, 50, 50, 55, 52, 60, 59, 51, 42, 16, 48, 62, + 47, 50, 45, 39, 28, 22, 19, 6, 69, 70, 80, 94, + 40, 39, 40, 34, 26, 27, 20, 16, 12, 10, 4, 66, + 67, 79, 83, 67, 68, 79, 21, 23, 18, 8, 16, 15, + 3, 10, 10, 1, 73, 72, 80, 95, 13, 3, 71, 75, 64, + 64, 1, 4, 4, 7, 5, 7, 10, 19, 10, 11, 18, 18, + 58, 50, 44, 36, 30, 21, 12, 66, 90, 3, 35, 28, + 25, 20, 18, 9, 6, 4, 69, 80, 75, 68, 74, 65, 6, + 70, 74, 0, 4, 3, 4, 10, 4, 3, 58, 49, 36, 25, + 17, 5, 70, 82, 98 }, + + { + + 60, + 8, 76, 60, 8, 76, 100, 82, 16, 10, 6, 9, 40, 44, + 54, 14, 83, 2, 65, 9, 1, 3, 69, 65, 80, 12, 36, + 91, 105, 112, 83, 85, 67, 65, 9, 1, 80, 79, 8, + 10, 66, 75, 85, 3, 73, 81, 89, 5, 73, 84, 5, 72, + 78, 88, 3, 69, 70, 75, 65, 5, 22, 0, 0, 0, 74, + 89, 97, 65, 10, 64, 18, 74, 93, 100, 71, 77, 18, + 8, 75, 68, 89, 71, 85, 80, 99, 85, 84, 89, 95, + 20, 64, 7, 84, 71, 83, 79, 104, 5, 65, 64, 74, + 16, 68, 65, 80, 83, 72, 74, 70, 8, 9, 65, 13, + 16, 77, 0, 66, 67, 5, 74, 18, 67, 2, 18, 22, 28, + 22, 15, 83, 66, 71, 5, 6, 93, 2, 9, 6, 4, 7, 7, + 13, 8, 2, 3, 0, 3, 11, 9, 80, 66, 2, 71, 7, 9, + 15, 8, 5, 8, 5, 4, 10, 15, 7, 77, 3, 77, 25, 22, + 17, 14, 17, 18, 11, 16, 21, 0, 12, 9, 7, 4, 66, + 7, 1, 4, 6, 3, 8, 14, 3, 81, 2, 75, 3, 12, 83, + 33, 42, 34, 33, 33, 33, 29, 29, 24, 15, 12, 9, + 9, 3, 85, 67, 0, 75, 18, 15, 12, 14, 10, 4, 4, + 4, 73, 67, 72, 93, 87, 99, 70, 21, 18, 11, 2, 6, + 66, 71, 74, 86, 68, 37, 21, 15, 8, 10, 64, 71, + 76, 90, 5, 37, 28, 25, 19, 14, 5, 64, 71, 80, + 70, 38, 28, 15, 4, 7, 69, 78, 86, 5, 51, 40, 32, + 24, 22, 5, 67, 73, 77, 62, 93, 91, 77, 94, 93, + 84, 89, 86, 86, 87, 86, 87, 86, 83, 86, 84, 73, + 81, 75, 76, 75, 69, 66, 66, 65, 2, 64, 65, 69, + 13, 11, 13, 8, 4, 14, 11, 7, 10, 10, 8, 6, 2, + 66, 69, 67, 6, 75, 10, 24, 9, 8, 16, 16, 14, 11, + 16, 13, 19, 1, 66, 67, 39, 39, 44, 36, 35, 44, + 48, 48, 53, 50, 57, 56, 48, 40, 15, 45, 59, 44, + 46, 42, 37, 26, 21, 18, 6, 68, 70, 79, 92, 38, + 37, 38, 32, 24, 25, 19, 14, 11, 8, 3, 68, 68, + 80, 84, 67, 68, 80, 20, 22, 16, 6, 14, 13, 2, 9, + 8, 0, 74, 73, 81, 96, 12, 2, 72, 75, 64, 64, 2, + 5, 5, 8, 6, 8, 11, 20, 11, 12, 19, 19, 57, 48, + 42, 34, 27, 18, 9, 69, 92, 3, 35, 28, 26, 20, + 19, 9, 6, 5, 68, 80, 75, 67, 74, 64, 7, 70, 73, + 1, 5, 3, 5, 11, 4, 3, 57, 47, 34, 22, 14, 1, 74, + 85, 101 }, + + { + + 58, + 7, 77, 58, 7, 77, 99, 81, 16, 10, 5, 7, 38, 42, + 53, 14, 81, 1, 65, 9, 0, 2, 69, 67, 82, 11, 34, + 93, 106, 113, 81, 84, 68, 65, 9, 0, 80, 78, 8, + 9, 66, 75, 85, 2, 74, 81, 90, 5, 73, 84, 4, 73, + 78, 88, 3, 69, 70, 75, 65, 4, 22, 0, 0, 0, 74, + 90, 97, 65, 9, 65, 18, 74, 93, 99, 70, 76, 19, + 9, 74, 67, 88, 70, 84, 80, 98, 85, 84, 89, 95, + 20, 64, 7, 84, 71, 83, 79, 103, 4, 66, 65, 75, + 16, 68, 65, 80, 83, 72, 74, 70, 7, 8, 65, 12, + 15, 77, 64, 66, 67, 4, 74, 17, 68, 1, 17, 20, + 27, 21, 14, 83, 66, 71, 4, 5, 93, 1, 8, 5, 4, 7, + 7, 13, 8, 2, 2, 0, 2, 10, 8, 81, 67, 1, 72, 6, + 8, 14, 7, 4, 8, 5, 4, 8, 14, 6, 77, 3, 78, 24, + 21, 16, 14, 17, 17, 10, 16, 20, 64, 11, 9, 6, 3, + 67, 6, 0, 3, 5, 2, 7, 13, 2, 81, 1, 75, 2, 10, + 83, 31, 40, 32, 31, 31, 30, 27, 27, 22, 13, 10, + 7, 6, 1, 87, 68, 64, 76, 17, 13, 10, 12, 8, 2, + 2, 2, 74, 68, 73, 93, 87, 99, 70, 21, 18, 11, 2, + 6, 66, 71, 74, 85, 68, 36, 21, 15, 8, 10, 64, + 71, 76, 89, 4, 37, 28, 24, 18, 14, 5, 64, 71, + 80, 70, 38, 27, 14, 3, 7, 69, 78, 86, 5, 50, 39, + 31, 23, 21, 5, 67, 73, 77, 62, 93, 90, 77, 93, + 92, 84, 88, 85, 85, 86, 85, 86, 86, 83, 86, 84, + 72, 81, 76, 76, 75, 69, 67, 67, 66, 1, 65, 67, + 71, 12, 10, 13, 7, 3, 13, 10, 6, 9, 9, 7, 5, 1, + 67, 70, 67, 6, 76, 9, 23, 8, 7, 15, 15, 14, 10, + 14, 12, 18, 0, 67, 69, 38, 38, 43, 34, 33, 42, + 46, 46, 50, 47, 54, 53, 45, 37, 13, 42, 55, 41, + 41, 39, 34, 24, 19, 16, 6, 68, 70, 78, 90, 36, + 35, 36, 30, 21, 23, 17, 11, 9, 6, 1, 70, 70, 81, + 85, 68, 69, 82, 18, 20, 14, 4, 12, 11, 0, 7, 6, + 65, 76, 75, 83, 97, 11, 1, 73, 75, 64, 64, 2, 5, + 5, 9, 6, 9, 11, 21, 12, 13, 20, 20, 56, 46, 39, + 31, 24, 15, 5, 72, 95, 3, 35, 28, 26, 20, 19, 9, + 6, 5, 68, 80, 75, 67, 74, 0, 8, 70, 73, 1, 5, 3, + 5, 11, 4, 2, 55, 44, 31, 19, 10, 66, 78, 89, 104 }, + + { + + 57, + 7, 77, 57, 7, 77, 97, 79, 17, 11, 5, 6, 37, 41, + 53, 14, 78, 1, 64, 10, 0, 2, 68, 68, 83, 11, 33, + 94, 107, 113, 78, 82, 68, 64, 10, 0, 79, 76, 9, + 9, 65, 74, 84, 2, 74, 80, 90, 5, 72, 83, 4, 73, + 77, 88, 4, 68, 69, 74, 64, 4, 22, 0, 0, 0, 73, + 90, 97, 64, 9, 65, 18, 73, 92, 97, 68, 74, 21, + 11, 72, 66, 86, 68, 82, 79, 96, 84, 83, 88, 94, + 21, 0, 8, 83, 70, 82, 78, 101, 4, 66, 65, 75, + 17, 68, 64, 79, 82, 71, 73, 69, 7, 8, 64, 12, + 15, 76, 64, 65, 67, 4, 73, 17, 68, 1, 17, 19, + 27, 21, 14, 82, 65, 70, 4, 5, 92, 1, 8, 5, 5, 7, + 7, 14, 9, 3, 2, 1, 2, 10, 8, 81, 67, 1, 72, 6, + 7, 14, 7, 4, 9, 6, 4, 7, 14, 6, 76, 3, 78, 24, + 21, 16, 14, 17, 17, 10, 16, 20, 64, 11, 9, 6, 3, + 67, 6, 0, 3, 5, 2, 7, 13, 2, 80, 1, 74, 2, 9, + 82, 30, 39, 31, 30, 29, 28, 26, 26, 20, 12, 9, + 6, 4, 0, 88, 68, 64, 76, 16, 12, 9, 11, 7, 1, 1, + 1, 74, 68, 73, 92, 86, 98, 69, 22, 18, 11, 3, 7, + 65, 70, 73, 83, 67, 36, 21, 15, 8, 11, 0, 70, + 75, 87, 4, 38, 29, 24, 18, 15, 5, 64, 70, 79, + 70, 39, 27, 14, 3, 8, 68, 77, 85, 5, 50, 39, 31, + 23, 21, 5, 66, 72, 76, 62, 92, 89, 76, 91, 90, + 83, 86, 83, 83, 84, 83, 84, 85, 82, 85, 83, 70, + 80, 76, 75, 74, 68, 67, 67, 66, 1, 65, 68, 72, + 12, 10, 14, 7, 3, 13, 10, 6, 9, 9, 7, 5, 1, 67, + 70, 66, 7, 76, 9, 23, 8, 7, 15, 15, 15, 10, 13, + 12, 18, 0, 67, 70, 38, 38, 43, 33, 32, 41, 44, + 44, 48, 45, 52, 51, 43, 35, 12, 40, 52, 38, 37, + 37, 32, 23, 18, 15, 6, 67, 69, 76, 87, 35, 34, + 35, 29, 19, 22, 16, 9, 8, 5, 0, 71, 71, 82, 85, + 68, 69, 83, 17, 19, 13, 3, 11, 10, 64, 6, 5, 66, + 77, 76, 84, 97, 11, 1, 73, 74, 0, 0, 3, 6, 6, + 11, 7, 10, 12, 23, 14, 14, 22, 22, 56, 45, 37, + 29, 22, 13, 2, 74, 97, 4, 36, 29, 27, 21, 20, + 10, 7, 6, 67, 79, 74, 66, 73, 2, 10, 69, 72, 2, + 6, 4, 6, 12, 4, 2, 54, 42, 29, 17, 7, 69, 81, + 92, 106 }, + + { + + 56, + 7, 77, 56, 7, 77, 95, 78, 17, 11, 5, 5, 36, 40, + 53, 14, 76, 1, 0, 11, 0, 1, 68, 69, 84, 10, 31, + 96, 108, 114, 75, 81, 68, 0, 11, 0, 79, 75, 9, + 8, 65, 74, 83, 2, 74, 80, 90, 5, 72, 82, 4, 73, + 77, 88, 4, 68, 69, 74, 64, 4, 22, 0, 0, 0, 72, + 90, 97, 64, 8, 65, 18, 73, 92, 95, 66, 72, 22, + 13, 71, 65, 85, 67, 81, 78, 94, 84, 83, 88, 93, + 21, 0, 8, 82, 70, 81, 78, 100, 4, 66, 65, 75, + 17, 68, 64, 79, 82, 71, 73, 69, 7, 8, 64, 12, + 15, 76, 64, 65, 67, 4, 73, 16, 68, 1, 16, 18, + 26, 20, 13, 81, 65, 70, 3, 4, 92, 0, 8, 5, 5, 7, + 7, 15, 9, 3, 2, 1, 2, 9, 8, 82, 67, 1, 73, 6, 6, + 14, 7, 3, 9, 6, 4, 6, 14, 5, 76, 3, 79, 23, 21, + 16, 14, 17, 17, 10, 16, 20, 64, 11, 9, 6, 3, 67, + 6, 0, 3, 5, 2, 6, 12, 1, 80, 1, 74, 1, 8, 82, + 28, 37, 29, 28, 27, 26, 24, 24, 18, 10, 7, 4, 2, + 65, 89, 68, 64, 77, 15, 11, 8, 10, 5, 0, 0, 0, + 75, 69, 74, 92, 86, 97, 69, 22, 18, 11, 3, 7, + 65, 70, 73, 82, 66, 36, 21, 15, 8, 11, 0, 69, + 74, 85, 4, 38, 29, 24, 18, 15, 5, 64, 70, 78, + 70, 39, 27, 14, 3, 8, 68, 77, 84, 5, 49, 38, 30, + 22, 21, 5, 66, 72, 76, 62, 91, 88, 75, 90, 89, + 82, 85, 82, 82, 83, 82, 82, 84, 81, 85, 82, 69, + 80, 77, 75, 73, 68, 67, 68, 66, 0, 66, 69, 73, + 11, 10, 14, 7, 2, 12, 10, 6, 8, 8, 7, 5, 0, 67, + 70, 66, 7, 76, 8, 22, 7, 7, 14, 14, 15, 9, 12, + 12, 18, 64, 68, 72, 37, 37, 42, 32, 31, 40, 42, + 42, 46, 43, 49, 48, 40, 33, 11, 37, 49, 35, 33, + 34, 30, 21, 17, 14, 6, 66, 69, 75, 85, 33, 32, + 33, 27, 17, 20, 14, 7, 7, 3, 64, 73, 72, 83, 86, + 69, 69, 84, 16, 18, 11, 1, 9, 8, 65, 4, 3, 67, + 78, 77, 85, 98, 10, 0, 74, 74, 0, 0, 4, 6, 7, + 12, 8, 11, 13, 24, 15, 15, 23, 23, 55, 43, 35, + 27, 19, 10, 64, 77, 99, 4, 36, 29, 27, 21, 21, + 10, 7, 6, 67, 79, 74, 65, 73, 3, 11, 69, 71, 3, + 7, 4, 6, 13, 4, 2, 53, 40, 27, 14, 4, 73, 85, + 95, 109 }, + + { + + 55, + 7, 77, 55, 7, 77, 93, 76, 18, 11, 4, 3, 34, 39, + 53, 14, 74, 1, 1, 12, 0, 0, 68, 70, 85, 10, 29, + 97, 109, 114, 72, 80, 68, 1, 12, 0, 78, 74, 10, + 8, 65, 73, 82, 1, 75, 80, 90, 5, 72, 82, 4, 73, + 77, 88, 5, 68, 69, 74, 0, 4, 22, 0, 0, 0, 72, + 90, 97, 0, 7, 65, 18, 73, 92, 93, 64, 71, 24, + 15, 70, 64, 83, 66, 80, 77, 92, 83, 83, 87, 92, + 21, 0, 8, 82, 70, 81, 77, 98, 4, 67, 66, 76, 17, + 68, 64, 78, 82, 70, 73, 68, 7, 8, 64, 12, 14, + 76, 64, 65, 67, 4, 73, 15, 68, 1, 15, 17, 25, + 19, 12, 81, 64, 70, 2, 4, 91, 0, 8, 5, 5, 7, 7, + 16, 9, 3, 2, 2, 2, 8, 8, 83, 67, 1, 73, 5, 5, + 14, 7, 2, 9, 6, 4, 4, 14, 4, 76, 3, 79, 22, 21, + 16, 14, 17, 17, 10, 16, 20, 64, 10, 9, 6, 3, 67, + 5, 64, 2, 4, 1, 5, 11, 1, 80, 0, 74, 0, 6, 82, + 27, 35, 27, 26, 25, 24, 22, 22, 16, 8, 6, 3, 0, + 67, 90, 68, 64, 77, 14, 10, 7, 8, 4, 65, 64, 64, + 76, 69, 74, 92, 86, 97, 68, 22, 18, 11, 3, 7, + 65, 70, 72, 81, 66, 36, 21, 15, 8, 12, 1, 68, + 73, 83, 4, 38, 29, 24, 18, 16, 5, 64, 70, 78, + 70, 39, 27, 13, 3, 8, 68, 77, 83, 5, 49, 38, 29, + 21, 21, 5, 66, 71, 75, 62, 90, 87, 75, 89, 88, + 81, 83, 81, 81, 82, 80, 81, 84, 80, 85, 82, 67, + 80, 77, 75, 73, 68, 68, 68, 66, 64, 67, 70, 74, + 11, 9, 14, 6, 2, 12, 9, 6, 7, 8, 6, 5, 64, 67, + 71, 66, 7, 77, 8, 22, 6, 7, 13, 14, 15, 8, 11, + 11, 17, 65, 68, 73, 36, 36, 42, 31, 30, 38, 40, + 40, 44, 40, 47, 46, 37, 30, 10, 34, 46, 32, 29, + 31, 27, 19, 16, 13, 6, 65, 69, 74, 83, 31, 30, + 31, 25, 15, 18, 13, 5, 5, 2, 65, 74, 74, 84, 87, + 69, 70, 85, 15, 16, 10, 64, 7, 6, 67, 3, 1, 69, + 79, 79, 87, 99, 9, 64, 75, 74, 1, 0, 4, 7, 7, + 13, 9, 12, 14, 25, 16, 16, 25, 24, 55, 42, 33, + 24, 17, 7, 67, 80, 101, 5, 37, 30, 28, 21, 22, + 11, 7, 7, 66, 79, 73, 65, 72, 4, 12, 69, 71, 3, + 7, 4, 7, 14, 4, 2, 52, 38, 24, 11, 1, 77, 89, + 99, 111 }, + + { + + 53, + 7, 77, 53, 7, 77, 92, 75, 18, 11, 4, 2, 33, 37, + 53, 14, 71, 0, 2, 12, 0, 64, 68, 71, 86, 9, 27, + 99, 110, 115, 69, 79, 68, 2, 12, 0, 78, 73, 10, + 7, 65, 73, 82, 1, 75, 79, 90, 5, 72, 81, 3, 74, + 77, 88, 5, 67, 69, 73, 0, 4, 22, 0, 0, 0, 71, + 91, 97, 0, 6, 65, 18, 73, 92, 92, 0, 69, 25, 16, + 69, 0, 82, 64, 79, 76, 90, 83, 82, 87, 91, 21, + 0, 8, 81, 70, 80, 77, 97, 3, 67, 66, 76, 17, 68, + 64, 78, 82, 70, 73, 68, 7, 7, 64, 12, 14, 76, + 64, 65, 67, 4, 73, 15, 69, 1, 15, 16, 24, 18, + 11, 80, 64, 70, 1, 3, 91, 64, 8, 4, 5, 7, 7, 17, + 10, 3, 1, 2, 1, 7, 7, 84, 67, 1, 74, 5, 4, 13, + 6, 2, 10, 6, 4, 3, 13, 3, 76, 3, 80, 22, 21, 16, + 14, 17, 17, 10, 16, 19, 64, 10, 9, 6, 3, 68, 5, + 64, 2, 4, 1, 5, 10, 0, 80, 0, 74, 64, 5, 82, 25, + 33, 25, 24, 23, 22, 20, 20, 14, 6, 4, 1, 66, 69, + 91, 68, 65, 78, 13, 9, 6, 7, 2, 66, 65, 66, 76, + 70, 75, 92, 86, 96, 68, 22, 18, 11, 3, 7, 65, + 70, 72, 80, 65, 36, 21, 15, 8, 12, 1, 68, 73, + 81, 4, 38, 29, 24, 18, 16, 5, 64, 70, 77, 70, + 39, 26, 13, 3, 8, 68, 77, 83, 5, 48, 37, 28, 20, + 21, 5, 66, 71, 75, 62, 89, 86, 74, 87, 87, 80, + 82, 79, 80, 80, 79, 79, 83, 80, 84, 81, 66, 79, + 78, 75, 72, 68, 68, 69, 67, 65, 68, 71, 75, 10, + 9, 14, 6, 1, 11, 9, 6, 6, 7, 6, 4, 65, 68, 71, + 66, 7, 77, 7, 21, 5, 7, 12, 13, 15, 7, 10, 11, + 17, 66, 69, 75, 35, 36, 41, 30, 28, 37, 38, 38, + 42, 38, 44, 43, 34, 28, 8, 31, 42, 29, 25, 29, + 25, 17, 15, 11, 6, 64, 68, 73, 81, 30, 28, 29, + 24, 13, 16, 11, 3, 4, 0, 67, 76, 75, 85, 88, 70, + 70, 86, 13, 15, 8, 65, 5, 4, 68, 1, 64, 70, 80, + 80, 88, 99, 8, 64, 76, 74, 1, 1, 5, 7, 8, 14, 9, + 13, 15, 26, 17, 17, 26, 25, 54, 40, 31, 22, 14, + 4, 70, 83, 104, 5, 37, 30, 28, 22, 22, 11, 8, 7, + 66, 78, 73, 64, 72, 5, 13, 69, 70, 4, 8, 4, 7, + 15, 4, 2, 50, 36, 22, 8, 65, 81, 93, 102, 114 }, + + { + + 52, + 7, 77, 52, 7, 77, 90, 73, 18, 11, 3, 0, 31, 36, + 53, 14, 69, 0, 3, 13, 0, 64, 67, 72, 87, 9, 25, + 101, 111, 115, 66, 77, 68, 3, 13, 0, 78, 72, 10, + 7, 65, 73, 81, 1, 76, 79, 90, 5, 72, 80, 3, 74, + 77, 88, 6, 67, 68, 73, 1, 4, 22, 0, 0, 0, 71, + 91, 97, 1, 5, 65, 18, 72, 92, 90, 2, 68, 27, 18, + 67, 1, 81, 0, 78, 75, 88, 82, 82, 86, 90, 21, 0, + 8, 81, 70, 80, 76, 95, 3, 67, 66, 76, 17, 68, 0, + 77, 82, 70, 73, 67, 7, 7, 64, 12, 14, 75, 64, + 64, 67, 4, 73, 14, 69, 1, 14, 15, 24, 17, 10, + 79, 0, 70, 0, 3, 90, 64, 8, 4, 5, 7, 7, 18, 10, + 3, 1, 2, 1, 6, 7, 85, 67, 1, 74, 4, 3, 13, 6, 1, + 10, 6, 4, 2, 13, 2, 76, 3, 80, 21, 21, 16, 14, + 17, 17, 10, 16, 19, 64, 10, 9, 6, 3, 68, 5, 64, + 1, 4, 1, 4, 9, 0, 80, 64, 74, 65, 4, 82, 24, 31, + 24, 23, 21, 20, 18, 18, 12, 4, 2, 64, 68, 70, + 92, 68, 65, 78, 12, 8, 5, 6, 1, 68, 66, 67, 77, + 70, 75, 92, 86, 96, 68, 22, 18, 11, 3, 7, 64, + 69, 71, 79, 65, 36, 21, 15, 8, 12, 2, 67, 72, + 79, 4, 38, 29, 24, 18, 16, 5, 64, 70, 76, 70, + 40, 26, 12, 3, 8, 68, 77, 82, 5, 48, 36, 27, 19, + 21, 5, 66, 71, 74, 62, 88, 85, 73, 86, 86, 79, + 81, 78, 79, 79, 77, 77, 82, 79, 84, 81, 65, 79, + 78, 74, 71, 68, 69, 69, 67, 65, 69, 72, 76, 9, + 8, 14, 5, 1, 11, 9, 6, 5, 6, 6, 4, 66, 68, 71, + 66, 7, 77, 6, 21, 5, 7, 11, 12, 15, 6, 9, 10, + 16, 67, 70, 77, 34, 35, 41, 29, 27, 35, 36, 36, + 40, 35, 41, 41, 31, 26, 7, 28, 39, 26, 21, 26, + 22, 15, 14, 10, 6, 0, 68, 72, 79, 28, 27, 27, + 22, 11, 14, 10, 1, 3, 65, 68, 78, 77, 86, 89, + 70, 70, 87, 12, 13, 6, 67, 4, 3, 70, 0, 65, 72, + 81, 81, 89, 100, 8, 65, 77, 73, 2, 1, 5, 8, 9, + 15, 10, 14, 16, 28, 18, 18, 27, 27, 54, 38, 29, + 19, 12, 1, 73, 86, 106, 5, 38, 31, 29, 22, 23, + 11, 8, 8, 65, 78, 73, 0, 71, 6, 14, 68, 69, 4, + 8, 4, 8, 16, 4, 2, 49, 34, 19, 5, 68, 84, 97, + 106, 117 }, + + { + + 51, + 7, 78, 51, 7, 78, 88, 72, 19, 11, 3, 64, 30, 35, + 53, 14, 67, 0, 3, 14, 0, 65, 67, 73, 88, 8, 24, + 102, 112, 116, 0, 76, 68, 3, 14, 0, 77, 71, 11, + 6, 64, 72, 80, 0, 76, 79, 90, 5, 71, 80, 3, 74, + 76, 88, 6, 67, 68, 73, 1, 4, 22, 0, 0, 0, 70, + 91, 97, 1, 5, 66, 18, 72, 91, 88, 4, 66, 28, 20, + 66, 2, 79, 1, 77, 75, 87, 82, 82, 86, 90, 22, 0, + 8, 80, 69, 79, 76, 94, 3, 68, 67, 77, 17, 68, 0, + 77, 82, 69, 73, 67, 7, 7, 0, 12, 13, 75, 64, 64, + 67, 4, 73, 13, 69, 1, 13, 14, 23, 17, 10, 79, 0, + 70, 0, 2, 90, 65, 8, 4, 5, 7, 7, 18, 10, 3, 1, + 3, 1, 5, 7, 85, 68, 1, 75, 4, 2, 13, 6, 0, 10, + 6, 4, 0, 13, 1, 76, 3, 81, 20, 21, 15, 14, 17, + 17, 10, 16, 19, 64, 9, 9, 5, 3, 68, 4, 65, 1, 3, + 0, 3, 8, 64, 80, 64, 73, 66, 2, 82, 22, 29, 22, + 21, 19, 18, 16, 16, 10, 2, 1, 65, 70, 72, 93, + 68, 65, 79, 11, 7, 4, 4, 64, 69, 67, 68, 78, 71, + 76, 92, 86, 95, 67, 22, 18, 11, 4, 7, 64, 69, + 71, 78, 64, 35, 21, 15, 8, 13, 2, 66, 71, 77, 4, + 39, 29, 24, 18, 17, 5, 64, 69, 76, 70, 40, 26, + 12, 3, 8, 67, 76, 81, 5, 47, 36, 26, 19, 21, 5, + 65, 70, 74, 62, 88, 84, 73, 85, 85, 78, 79, 77, + 78, 78, 76, 76, 82, 78, 84, 80, 0, 79, 79, 74, + 71, 68, 69, 70, 67, 66, 70, 73, 77, 9, 8, 14, 5, + 0, 10, 8, 6, 5, 6, 5, 4, 66, 68, 72, 66, 7, 78, + 6, 20, 4, 6, 10, 12, 16, 6, 8, 10, 16, 67, 70, + 78, 33, 34, 40, 28, 26, 34, 34, 34, 38, 33, 39, + 38, 28, 23, 6, 26, 36, 23, 17, 23, 20, 13, 13, + 9, 6, 1, 68, 70, 76, 26, 25, 25, 20, 9, 12, 8, + 64, 1, 66, 69, 79, 78, 87, 90, 71, 71, 88, 11, + 12, 5, 69, 2, 1, 71, 65, 67, 73, 83, 83, 91, + 101, 7, 66, 78, 73, 2, 1, 6, 8, 9, 17, 11, 15, + 17, 29, 20, 19, 29, 28, 53, 37, 27, 17, 9, 64, + 76, 88, 108, 6, 38, 31, 29, 22, 24, 12, 8, 8, + 65, 78, 72, 0, 71, 7, 15, 68, 69, 5, 9, 4, 8, + 17, 4, 2, 48, 32, 17, 2, 72, 88, 100, 109, 119 }, + + { + + 50, + 7, 78, 50, 7, 78, 86, 70, 19, 11, 2, 66, 28, 33, + 53, 14, 64, 64, 4, 14, 0, 66, 67, 74, 89, 8, 22, + 104, 113, 116, 3, 75, 68, 4, 14, 0, 77, 70, 11, + 6, 64, 72, 80, 0, 77, 78, 90, 5, 71, 79, 2, 74, + 76, 88, 7, 66, 68, 72, 2, 4, 22, 0, 0, 0, 70, + 91, 97, 2, 4, 66, 18, 72, 91, 87, 6, 65, 30, 22, + 65, 3, 78, 3, 76, 74, 85, 81, 81, 85, 89, 22, 0, + 8, 80, 69, 79, 75, 92, 2, 68, 67, 77, 17, 68, 0, + 76, 82, 69, 73, 66, 7, 6, 0, 12, 13, 75, 64, 64, + 67, 4, 73, 13, 69, 1, 13, 13, 22, 16, 9, 78, 1, + 70, 64, 2, 89, 65, 8, 3, 5, 7, 7, 19, 11, 3, 1, + 3, 1, 4, 7, 86, 68, 1, 75, 3, 1, 12, 6, 0, 11, + 6, 4, 64, 12, 0, 76, 3, 81, 20, 21, 15, 14, 17, + 17, 10, 16, 19, 64, 9, 9, 5, 3, 68, 4, 65, 0, 3, + 0, 3, 7, 64, 80, 65, 73, 67, 1, 82, 21, 27, 20, + 19, 17, 16, 14, 14, 8, 0, 64, 67, 73, 74, 94, + 68, 66, 79, 10, 6, 3, 3, 65, 71, 68, 69, 78, 71, + 76, 92, 86, 95, 67, 22, 18, 11, 4, 7, 64, 69, + 70, 77, 64, 35, 21, 15, 8, 13, 3, 65, 71, 75, 4, + 39, 29, 24, 18, 17, 5, 64, 69, 75, 70, 40, 25, + 11, 3, 8, 67, 76, 81, 5, 47, 35, 25, 18, 21, 5, + 65, 70, 73, 62, 87, 83, 72, 83, 84, 77, 78, 75, + 77, 76, 74, 74, 81, 77, 83, 80, 1, 78, 79, 74, + 70, 68, 70, 70, 68, 67, 71, 74, 78, 8, 7, 14, 4, + 0, 10, 8, 6, 4, 5, 5, 3, 67, 68, 72, 66, 7, 78, + 5, 20, 3, 6, 9, 11, 16, 5, 7, 9, 15, 68, 71, 80, + 32, 34, 40, 27, 24, 32, 32, 32, 36, 30, 36, 36, + 25, 21, 4, 23, 32, 20, 13, 21, 17, 11, 12, 8, 6, + 2, 67, 69, 74, 25, 23, 23, 19, 7, 10, 7, 66, 0, + 68, 71, 81, 80, 88, 91, 71, 71, 89, 9, 10, 3, + 70, 0, 64, 73, 66, 69, 75, 84, 84, 92, 101, 6, + 66, 79, 73, 3, 2, 6, 9, 10, 18, 12, 16, 18, 30, + 21, 20, 30, 29, 53, 35, 25, 14, 7, 67, 79, 91, + 110, 6, 39, 32, 30, 23, 25, 12, 9, 9, 64, 77, + 72, 1, 70, 8, 16, 68, 68, 5, 9, 4, 9, 18, 4, 2, + 46, 30, 14, 64, 75, 92, 104, 113, 122 }, + + { + + 48, + 6, 78, 48, 6, 78, 85, 69, 19, 11, 2, 67, 27, 32, + 53, 14, 1, 64, 5, 15, 0, 67, 67, 75, 91, 7, 20, + 106, 114, 117, 5, 74, 68, 5, 15, 0, 77, 69, 11, + 5, 64, 72, 79, 64, 77, 78, 91, 5, 71, 79, 2, 75, + 76, 88, 7, 66, 68, 72, 2, 4, 22, 0, 0, 0, 69, + 92, 97, 2, 3, 66, 18, 72, 91, 85, 7, 0, 31, 23, + 64, 4, 77, 4, 75, 73, 83, 81, 81, 85, 88, 22, 0, + 8, 79, 69, 78, 75, 91, 2, 69, 68, 78, 17, 68, 0, + 76, 82, 69, 73, 66, 6, 6, 0, 12, 12, 75, 64, 64, + 67, 3, 73, 12, 70, 1, 12, 11, 21, 15, 8, 78, 1, + 70, 65, 1, 89, 66, 7, 3, 5, 7, 7, 20, 11, 3, 0, + 3, 0, 3, 6, 87, 68, 1, 76, 3, 0, 12, 5, 64, 11, + 6, 4, 66, 12, 64, 76, 3, 82, 19, 20, 15, 14, 17, + 16, 9, 16, 18, 65, 8, 9, 5, 2, 69, 3, 66, 0, 2, + 64, 2, 6, 65, 80, 65, 73, 68, 64, 82, 19, 25, + 18, 17, 15, 13, 12, 12, 6, 65, 66, 69, 75, 76, + 95, 68, 66, 80, 9, 4, 1, 1, 67, 72, 70, 71, 79, + 72, 77, 92, 86, 94, 67, 22, 18, 11, 4, 7, 64, + 69, 70, 76, 0, 35, 21, 15, 8, 13, 3, 65, 70, 74, + 4, 39, 29, 24, 17, 17, 5, 64, 69, 75, 70, 40, + 25, 11, 3, 8, 67, 76, 80, 5, 46, 34, 24, 17, 20, + 5, 65, 70, 73, 62, 86, 82, 72, 82, 83, 77, 77, + 74, 76, 75, 73, 73, 81, 77, 83, 79, 2, 78, 80, + 74, 70, 68, 70, 71, 68, 68, 72, 76, 79, 7, 7, + 14, 4, 64, 9, 7, 5, 3, 4, 4, 3, 68, 69, 73, 66, + 7, 79, 4, 19, 2, 6, 8, 10, 16, 4, 6, 9, 15, 69, + 72, 82, 31, 33, 39, 25, 23, 31, 30, 30, 33, 28, + 33, 33, 22, 18, 3, 20, 29, 17, 9, 18, 15, 9, 10, + 6, 6, 2, 67, 68, 72, 23, 21, 21, 17, 4, 8, 5, + 68, 65, 70, 72, 83, 81, 89, 92, 72, 72, 90, 8, + 9, 1, 72, 65, 66, 74, 68, 71, 76, 85, 86, 94, + 102, 5, 67, 80, 73, 3, 2, 7, 9, 10, 19, 12, 17, + 19, 31, 22, 21, 31, 30, 52, 33, 23, 12, 4, 70, + 82, 94, 113, 6, 39, 32, 30, 23, 25, 12, 9, 9, + 64, 77, 72, 1, 70, 9, 17, 68, 68, 6, 10, 4, 9, + 18, 4, 1, 45, 28, 12, 67, 78, 96, 108, 116, 125 }, + + { + + 47, + 6, 78, 47, 6, 78, 83, 68, 20, 11, 2, 68, 26, 31, + 53, 14, 3, 64, 6, 16, 0, 67, 66, 76, 92, 6, 18, + 107, 115, 118, 8, 72, 68, 6, 16, 0, 76, 68, 12, + 4, 64, 71, 78, 64, 77, 78, 91, 5, 71, 78, 2, 75, + 76, 88, 7, 66, 67, 72, 2, 4, 22, 0, 0, 0, 68, + 92, 97, 2, 2, 66, 18, 71, 91, 83, 9, 2, 32, 25, + 1, 5, 75, 5, 73, 72, 81, 81, 81, 85, 87, 22, 0, + 8, 78, 69, 77, 74, 90, 2, 69, 68, 78, 17, 68, 1, + 75, 81, 68, 73, 66, 6, 6, 0, 12, 12, 74, 64, 0, + 67, 3, 72, 11, 70, 1, 11, 10, 21, 14, 7, 77, 1, + 69, 66, 0, 89, 67, 7, 3, 6, 7, 7, 21, 11, 3, 0, + 4, 0, 3, 6, 88, 68, 1, 77, 3, 64, 12, 5, 65, 11, + 6, 4, 67, 12, 64, 76, 3, 83, 18, 20, 15, 14, 17, + 16, 9, 16, 18, 65, 8, 9, 5, 2, 69, 3, 66, 0, 2, + 64, 1, 6, 65, 80, 65, 73, 69, 65, 82, 17, 24, + 17, 16, 13, 11, 11, 11, 4, 67, 67, 70, 77, 77, + 96, 68, 66, 81, 8, 3, 0, 0, 68, 73, 71, 72, 80, + 73, 78, 92, 85, 93, 66, 23, 18, 11, 4, 8, 0, 68, + 69, 75, 1, 35, 21, 15, 8, 14, 3, 64, 69, 72, 4, + 39, 29, 24, 17, 18, 5, 64, 69, 74, 70, 41, 25, + 11, 3, 9, 67, 76, 79, 5, 46, 34, 24, 16, 20, 5, + 65, 69, 72, 62, 85, 81, 71, 81, 81, 76, 75, 73, + 74, 74, 72, 71, 80, 76, 83, 78, 4, 78, 81, 73, + 69, 68, 70, 72, 68, 68, 73, 77, 80, 7, 7, 14, 4, + 64, 8, 7, 5, 2, 4, 4, 3, 69, 69, 73, 65, 8, 79, + 4, 18, 2, 6, 8, 10, 16, 3, 5, 9, 15, 70, 72, 83, + 31, 32, 38, 24, 22, 30, 28, 28, 31, 26, 31, 30, + 20, 16, 2, 17, 26, 14, 5, 15, 13, 8, 9, 5, 6, 3, + 67, 67, 70, 21, 20, 19, 15, 2, 7, 4, 70, 66, 71, + 73, 84, 82, 90, 92, 72, 72, 91, 7, 8, 0, 74, 66, + 67, 75, 69, 72, 77, 86, 87, 95, 103, 5, 68, 80, + 72, 3, 2, 8, 10, 11, 20, 13, 18, 20, 33, 23, 22, + 33, 32, 51, 32, 21, 10, 1, 73, 85, 97, 115, 7, + 39, 32, 31, 23, 26, 13, 9, 10, 0, 77, 71, 2, 70, + 10, 19, 67, 67, 7, 11, 4, 10, 19, 4, 1, 44, 26, + 10, 69, 81, 99, 112, 119, 126 }, + + { + + 46, + 6, 78, 46, 6, 78, 81, 66, 20, 11, 1, 70, 24, 29, + 53, 14, 6, 65, 7, 16, 0, 68, 66, 77, 93, 6, 16, + 109, 116, 118, 11, 71, 68, 7, 16, 0, 76, 67, 12, + 4, 64, 71, 78, 64, 78, 77, 91, 5, 71, 77, 1, 75, + 76, 88, 8, 65, 67, 71, 3, 4, 22, 0, 0, 0, 68, + 92, 97, 3, 1, 66, 18, 71, 91, 82, 11, 3, 34, 27, + 2, 6, 74, 7, 72, 71, 79, 80, 80, 84, 86, 22, 0, + 8, 78, 69, 77, 74, 88, 1, 69, 68, 78, 17, 68, 1, + 75, 81, 68, 73, 65, 6, 5, 0, 12, 12, 74, 64, 0, + 67, 3, 72, 11, 70, 1, 11, 9, 20, 13, 6, 76, 2, + 69, 67, 0, 88, 67, 7, 2, 6, 7, 7, 22, 12, 3, 0, + 4, 0, 2, 6, 89, 68, 1, 77, 2, 65, 11, 5, 65, 12, + 6, 4, 68, 11, 65, 76, 3, 83, 18, 20, 15, 14, 17, + 16, 9, 16, 18, 65, 8, 9, 5, 2, 69, 3, 66, 64, 2, + 64, 1, 5, 66, 80, 66, 73, 70, 66, 82, 16, 22, + 15, 14, 11, 9, 9, 9, 2, 69, 69, 72, 80, 79, 97, + 68, 67, 81, 7, 2, 64, 64, 70, 75, 72, 73, 80, + 73, 78, 92, 85, 93, 66, 23, 18, 11, 4, 8, 0, 68, + 69, 74, 1, 35, 21, 15, 8, 14, 4, 0, 69, 70, 4, + 39, 29, 24, 17, 18, 5, 64, 69, 73, 70, 41, 24, + 10, 3, 9, 67, 76, 79, 5, 45, 33, 23, 15, 20, 5, + 65, 69, 72, 62, 84, 80, 70, 79, 80, 75, 74, 71, + 73, 72, 70, 69, 79, 75, 82, 78, 5, 77, 81, 73, + 68, 68, 71, 72, 69, 69, 74, 78, 81, 6, 6, 14, 3, + 65, 8, 7, 5, 1, 3, 4, 2, 70, 69, 73, 65, 8, 79, + 3, 18, 1, 6, 7, 9, 16, 2, 4, 8, 14, 71, 73, 85, + 30, 32, 38, 23, 20, 28, 26, 26, 29, 23, 28, 28, + 17, 14, 0, 14, 22, 11, 1, 13, 10, 6, 8, 4, 6, 4, + 66, 66, 68, 20, 18, 17, 14, 0, 5, 2, 72, 67, 73, + 75, 86, 84, 91, 93, 73, 72, 92, 5, 6, 65, 75, + 68, 69, 77, 71, 74, 79, 87, 88, 96, 103, 4, 68, + 81, 72, 4, 3, 8, 10, 12, 21, 14, 19, 21, 34, 24, + 23, 34, 33, 51, 30, 19, 7, 64, 76, 88, 100, 117, + 7, 40, 33, 31, 24, 27, 13, 10, 10, 0, 76, 71, 3, + 69, 11, 20, 67, 66, 7, 11, 4, 10, 20, 4, 1, 42, + 24, 7, 72, 84, 103, 116, 123, 126 }, + + { + + 45, + 6, 79, 45, 6, 79, 79, 65, 21, 11, 1, 71, 23, 28, + 53, 14, 8, 65, 7, 17, 0, 69, 66, 78, 94, 5, 15, + 110, 117, 119, 14, 70, 68, 7, 17, 0, 75, 66, 13, + 3, 0, 70, 77, 65, 78, 77, 91, 5, 70, 77, 1, 75, + 75, 88, 8, 65, 67, 71, 3, 4, 22, 0, 0, 0, 67, + 92, 97, 3, 1, 67, 18, 71, 90, 80, 13, 5, 35, 29, + 3, 7, 72, 8, 71, 71, 78, 80, 80, 84, 86, 23, 0, + 8, 77, 68, 76, 73, 87, 1, 70, 69, 79, 17, 68, 1, + 74, 81, 67, 73, 65, 6, 5, 1, 12, 11, 74, 64, 0, + 67, 3, 72, 10, 70, 1, 10, 8, 19, 13, 6, 76, 2, + 69, 67, 64, 88, 68, 7, 2, 6, 7, 7, 22, 12, 3, 0, + 5, 0, 1, 6, 89, 69, 1, 78, 2, 66, 11, 5, 66, 12, + 6, 4, 70, 11, 66, 76, 3, 84, 17, 20, 14, 14, 17, + 16, 9, 16, 18, 65, 7, 9, 4, 2, 69, 2, 67, 64, 1, + 65, 0, 4, 66, 80, 66, 72, 71, 68, 82, 14, 20, + 13, 12, 9, 7, 7, 7, 0, 71, 70, 73, 82, 81, 98, + 68, 67, 82, 6, 1, 65, 66, 71, 76, 73, 74, 81, + 74, 79, 92, 85, 92, 65, 23, 18, 11, 5, 8, 0, 68, + 68, 73, 2, 34, 21, 15, 8, 15, 4, 1, 68, 68, 4, + 40, 29, 24, 17, 19, 5, 64, 68, 73, 70, 41, 24, + 10, 3, 9, 66, 75, 78, 5, 45, 33, 22, 15, 20, 5, + 64, 68, 71, 62, 84, 79, 70, 78, 79, 74, 72, 70, + 72, 71, 69, 68, 79, 74, 82, 77, 7, 77, 82, 73, + 68, 68, 71, 73, 69, 70, 75, 79, 82, 6, 6, 14, 3, + 65, 7, 6, 5, 1, 3, 3, 2, 70, 69, 74, 65, 8, 80, + 3, 17, 0, 5, 6, 9, 17, 2, 3, 8, 14, 71, 73, 86, + 29, 31, 37, 22, 19, 27, 24, 24, 27, 21, 26, 25, + 14, 11, 64, 12, 19, 8, 66, 10, 8, 4, 7, 3, 6, 5, + 66, 64, 65, 18, 16, 15, 12, 65, 3, 1, 74, 69, + 74, 76, 87, 85, 92, 94, 73, 73, 93, 4, 5, 66, + 77, 70, 71, 78, 72, 76, 80, 89, 90, 98, 104, 3, + 69, 82, 72, 4, 3, 9, 11, 12, 23, 15, 20, 22, 35, + 26, 24, 36, 34, 50, 29, 17, 5, 67, 78, 91, 102, + 119, 8, 40, 33, 32, 24, 28, 14, 10, 11, 1, 76, + 70, 3, 69, 12, 21, 67, 66, 8, 12, 4, 11, 21, 4, + 1, 41, 22, 5, 75, 88, 107, 119, 126, 126 }, + + { + + 43, + 6, 79, 43, 6, 79, 78, 0, 21, 11, 0, 73, 21, 27, + 53, 14, 10, 65, 8, 18, 0, 70, 66, 79, 95, 5, 13, + 112, 118, 119, 17, 69, 68, 8, 18, 0, 75, 65, 13, + 3, 0, 70, 76, 65, 79, 77, 91, 5, 70, 76, 1, 76, + 75, 88, 9, 65, 67, 71, 4, 4, 22, 0, 0, 0, 67, + 93, 97, 4, 0, 67, 18, 71, 90, 78, 14, 6, 37, 30, + 4, 8, 71, 9, 70, 70, 76, 79, 80, 83, 85, 23, 0, + 8, 77, 68, 76, 73, 85, 1, 70, 69, 79, 17, 68, 1, + 74, 81, 67, 73, 64, 6, 5, 1, 12, 11, 74, 64, 0, + 67, 3, 72, 9, 71, 1, 9, 7, 18, 12, 5, 75, 3, 69, + 68, 64, 87, 68, 7, 2, 6, 7, 7, 23, 12, 3, 64, 5, + 64, 0, 5, 90, 69, 1, 78, 1, 67, 11, 4, 67, 12, + 6, 4, 71, 11, 67, 76, 3, 84, 16, 20, 14, 14, 17, + 16, 9, 16, 17, 65, 7, 9, 4, 2, 70, 2, 67, 65, 1, + 65, 64, 3, 67, 80, 67, 72, 72, 69, 82, 13, 18, + 11, 10, 7, 5, 5, 5, 65, 73, 72, 75, 84, 83, 99, + 68, 67, 82, 5, 0, 66, 67, 73, 78, 74, 76, 82, + 74, 79, 92, 85, 92, 65, 23, 18, 11, 5, 8, 0, 68, + 68, 72, 2, 34, 21, 15, 8, 15, 5, 1, 67, 66, 4, + 40, 29, 24, 17, 19, 5, 64, 68, 72, 70, 41, 24, + 9, 3, 9, 66, 75, 77, 5, 44, 32, 21, 14, 20, 5, + 64, 68, 71, 62, 83, 78, 69, 77, 78, 73, 71, 69, + 71, 70, 67, 66, 78, 74, 82, 77, 8, 77, 82, 73, + 67, 68, 72, 73, 69, 71, 76, 80, 83, 5, 5, 14, 2, + 66, 7, 6, 5, 0, 2, 3, 2, 71, 70, 74, 65, 8, 80, + 2, 17, 64, 5, 5, 8, 17, 1, 2, 7, 13, 72, 74, 88, + 28, 30, 37, 21, 18, 25, 22, 22, 25, 18, 23, 23, + 11, 9, 65, 9, 16, 5, 70, 7, 5, 2, 6, 1, 6, 6, + 66, 0, 0, 16, 14, 13, 10, 67, 1, 64, 76, 70, 76, + 77, 89, 87, 93, 95, 74, 73, 94, 3, 3, 68, 79, + 72, 73, 80, 74, 78, 82, 90, 91, 99, 105, 2, 70, + 83, 72, 5, 3, 9, 11, 13, 24, 15, 21, 23, 36, 27, + 25, 37, 35, 50, 27, 15, 2, 69, 81, 94, 105, 122, + 8, 41, 34, 32, 24, 28, 14, 10, 11, 1, 76, 70, 4, + 68, 13, 22, 67, 65, 8, 12, 4, 11, 22, 4, 1, 40, + 20, 2, 78, 91, 111, 123, 126, 126 }, + + { + + 42, + 6, 79, 42, 6, 79, 76, 1, 21, 11, 0, 74, 20, 25, + 53, 14, 13, 66, 9, 18, 0, 70, 65, 80, 96, 4, 11, + 114, 119, 120, 20, 67, 68, 9, 18, 0, 75, 64, 13, + 2, 0, 70, 76, 65, 79, 76, 91, 5, 70, 75, 0, 76, + 75, 88, 9, 64, 66, 70, 4, 4, 22, 0, 0, 0, 66, + 93, 97, 4, 64, 67, 18, 70, 90, 77, 16, 8, 38, + 32, 6, 9, 70, 11, 69, 69, 74, 79, 79, 83, 84, + 23, 0, 8, 76, 68, 75, 72, 84, 0, 70, 69, 79, 17, + 68, 2, 73, 81, 67, 73, 64, 6, 4, 1, 12, 11, 73, + 64, 1, 67, 3, 72, 9, 71, 1, 9, 6, 18, 11, 4, 74, + 3, 69, 69, 65, 87, 69, 7, 1, 6, 7, 7, 24, 13, 3, + 64, 5, 64, 64, 5, 91, 69, 1, 79, 1, 68, 10, 4, + 67, 13, 6, 4, 72, 10, 68, 76, 3, 85, 16, 20, 14, + 14, 17, 16, 9, 16, 17, 65, 7, 9, 4, 2, 70, 2, + 67, 65, 1, 65, 64, 2, 67, 80, 67, 72, 73, 70, + 82, 11, 16, 10, 9, 5, 3, 3, 3, 67, 75, 74, 77, + 87, 84, 100, 68, 68, 83, 4, 64, 67, 68, 74, 79, + 75, 77, 82, 75, 80, 92, 85, 91, 65, 23, 18, 11, + 5, 8, 1, 67, 67, 71, 3, 34, 21, 15, 8, 15, 5, 2, + 67, 64, 4, 40, 29, 24, 17, 19, 5, 64, 68, 71, + 70, 42, 23, 9, 3, 9, 66, 75, 77, 5, 44, 31, 20, + 13, 20, 5, 64, 68, 70, 62, 82, 77, 68, 75, 77, + 72, 70, 67, 70, 68, 66, 64, 77, 73, 81, 76, 9, + 76, 83, 72, 66, 68, 72, 74, 70, 71, 77, 81, 84, + 4, 5, 14, 2, 66, 6, 6, 5, 64, 1, 3, 1, 72, 70, + 74, 65, 8, 80, 1, 16, 64, 5, 4, 7, 17, 0, 1, 7, + 13, 73, 75, 90, 27, 30, 36, 20, 16, 24, 20, 20, + 23, 16, 20, 20, 8, 7, 67, 6, 12, 2, 74, 5, 3, 0, + 5, 0, 6, 7, 65, 1, 2, 15, 13, 11, 9, 69, 64, 65, + 78, 71, 78, 79, 91, 88, 94, 96, 74, 73, 95, 1, + 2, 70, 80, 73, 74, 81, 75, 79, 83, 91, 92, 100, + 105, 2, 70, 84, 71, 5, 4, 10, 12, 14, 25, 16, + 22, 24, 38, 28, 26, 38, 37, 49, 25, 13, 0, 72, + 84, 97, 108, 124, 8, 41, 34, 33, 25, 29, 14, 11, + 12, 2, 75, 70, 5, 68, 14, 23, 66, 64, 9, 13, 4, + 12, 23, 4, 1, 38, 18, 0, 81, 94, 114, 126, 126, + 126 }, + + { + + 41, + 6, 79, 41, 6, 79, 74, 3, 22, 11, 64, 76, 18, 24, + 53, 14, 15, 66, 10, 19, 0, 71, 65, 81, 97, 4, 9, + 115, 120, 120, 23, 66, 68, 10, 19, 0, 74, 0, 14, + 2, 0, 69, 75, 66, 80, 76, 91, 5, 70, 75, 0, 76, + 75, 88, 10, 64, 66, 70, 5, 4, 22, 0, 0, 0, 66, + 93, 97, 5, 65, 67, 18, 70, 90, 75, 18, 9, 40, + 34, 7, 10, 68, 12, 68, 68, 72, 78, 79, 82, 83, + 23, 0, 8, 76, 68, 75, 72, 82, 0, 71, 70, 80, 17, + 68, 2, 73, 81, 66, 73, 0, 6, 4, 1, 12, 10, 73, + 64, 1, 67, 3, 72, 8, 71, 1, 8, 5, 17, 10, 3, 74, + 4, 69, 70, 65, 86, 69, 7, 1, 6, 7, 7, 25, 13, 3, + 64, 6, 64, 65, 5, 92, 69, 1, 79, 0, 69, 10, 4, + 68, 13, 6, 4, 74, 10, 69, 76, 3, 85, 15, 20, 14, + 14, 17, 16, 9, 16, 17, 65, 6, 9, 4, 2, 70, 1, + 68, 66, 0, 66, 65, 1, 68, 80, 68, 72, 74, 72, + 82, 10, 14, 8, 7, 3, 1, 1, 1, 69, 77, 75, 78, + 89, 86, 101, 68, 68, 83, 3, 65, 68, 70, 76, 81, + 76, 78, 83, 75, 80, 92, 85, 91, 64, 23, 18, 11, + 5, 8, 1, 67, 67, 70, 3, 34, 21, 15, 8, 16, 6, 3, + 66, 1, 4, 40, 29, 24, 17, 20, 5, 64, 68, 71, 70, + 42, 23, 8, 3, 9, 66, 75, 76, 5, 43, 31, 19, 12, + 20, 5, 64, 67, 70, 62, 81, 76, 68, 74, 76, 71, + 68, 66, 69, 67, 64, 0, 77, 72, 81, 76, 11, 76, + 83, 72, 66, 68, 73, 74, 70, 72, 78, 82, 85, 4, + 4, 14, 1, 67, 6, 5, 5, 65, 1, 2, 1, 73, 70, 75, + 65, 8, 81, 1, 16, 65, 5, 3, 7, 17, 64, 0, 6, 12, + 74, 75, 91, 26, 29, 36, 19, 15, 22, 18, 18, 21, + 13, 18, 18, 5, 4, 68, 3, 9, 64, 78, 2, 0, 65, 4, + 64, 6, 8, 65, 2, 4, 13, 11, 9, 7, 71, 66, 67, + 80, 73, 79, 80, 92, 90, 95, 97, 75, 74, 96, 0, + 0, 71, 82, 75, 76, 83, 77, 81, 85, 92, 94, 102, + 106, 1, 71, 85, 71, 6, 4, 10, 12, 14, 26, 17, + 23, 25, 39, 29, 27, 40, 38, 49, 24, 11, 66, 74, + 87, 100, 111, 126, 9, 42, 35, 33, 25, 30, 15, + 11, 12, 2, 75, 69, 5, 67, 15, 24, 66, 64, 9, 13, + 4, 12, 24, 4, 1, 37, 16, 66, 84, 97, 118, 126, + 126, 126 }, + + { + + 40, + 6, 79, 40, 6, 79, 72, 4, 22, 11, 64, 77, 17, 23, + 53, 14, 17, 66, 11, 20, 0, 72, 65, 82, 98, 3, 7, + 117, 121, 121, 26, 65, 68, 11, 20, 0, 74, 1, 14, + 1, 0, 69, 74, 66, 80, 76, 91, 5, 70, 74, 0, 76, + 75, 88, 10, 64, 66, 70, 5, 4, 22, 0, 0, 0, 65, + 93, 97, 5, 66, 67, 18, 70, 90, 73, 20, 11, 41, + 36, 8, 11, 67, 13, 67, 67, 70, 78, 79, 82, 82, + 23, 0, 8, 75, 68, 74, 71, 81, 0, 71, 70, 80, 17, + 68, 2, 72, 81, 66, 73, 0, 6, 4, 1, 12, 10, 73, + 64, 1, 67, 3, 72, 7, 71, 1, 7, 4, 16, 9, 2, 73, + 4, 69, 71, 66, 86, 70, 7, 1, 6, 7, 7, 26, 13, 3, + 64, 6, 64, 66, 5, 93, 69, 1, 80, 0, 70, 10, 4, + 69, 13, 6, 4, 75, 10, 70, 76, 3, 86, 14, 20, 14, + 14, 17, 16, 9, 16, 17, 65, 6, 9, 4, 2, 70, 1, + 68, 66, 0, 66, 66, 0, 68, 80, 68, 72, 75, 73, + 82, 8, 12, 6, 5, 1, 64, 64, 64, 71, 79, 77, 80, + 91, 88, 102, 68, 68, 84, 2, 66, 69, 71, 77, 82, + 77, 79, 84, 76, 81, 92, 85, 90, 64, 23, 18, 11, + 5, 8, 1, 67, 66, 69, 4, 34, 21, 15, 8, 16, 6, 4, + 65, 3, 4, 40, 29, 24, 17, 20, 5, 64, 68, 70, 70, + 42, 23, 8, 3, 9, 66, 75, 75, 5, 43, 30, 18, 11, + 20, 5, 64, 67, 69, 62, 80, 75, 67, 73, 75, 70, + 67, 65, 68, 66, 0, 2, 76, 71, 81, 75, 12, 76, + 84, 72, 65, 68, 73, 75, 70, 73, 79, 83, 86, 3, + 4, 14, 1, 67, 5, 5, 5, 66, 0, 2, 1, 74, 70, 75, + 65, 8, 81, 0, 15, 66, 5, 2, 6, 17, 65, 64, 6, + 12, 75, 76, 93, 25, 28, 35, 18, 14, 21, 16, 16, + 19, 11, 15, 15, 2, 2, 69, 0, 6, 67, 82, 64, 65, + 67, 3, 65, 6, 9, 65, 3, 6, 11, 9, 7, 5, 73, 68, + 68, 82, 74, 81, 81, 94, 91, 96, 98, 75, 74, 97, + 64, 64, 73, 84, 77, 78, 84, 78, 83, 86, 93, 95, + 103, 107, 0, 72, 86, 71, 6, 4, 11, 13, 15, 27, + 18, 24, 26, 40, 30, 28, 41, 39, 48, 22, 9, 68, + 77, 90, 103, 114, 126, 9, 42, 35, 34, 25, 31, + 15, 11, 13, 3, 75, 69, 6, 67, 16, 25, 66, 0, 10, + 14, 4, 13, 25, 4, 1, 36, 14, 68, 87, 100, 122, + 126, 126, 126 }, + + { + + 38, + 5, 80, 38, 5, 80, 71, 5, 22, 11, 65, 79, 15, 21, + 52, 14, 19, 67, 11, 20, 64, 73, 65, 84, 100, 2, + 5, 119, 122, 122, 28, 64, 69, 11, 20, 64, 74, 2, + 14, 0, 0, 69, 74, 67, 81, 76, 92, 5, 70, 74, 64, + 77, 75, 88, 10, 64, 66, 70, 5, 3, 22, 0, 0, 0, + 65, 94, 97, 5, 67, 68, 18, 70, 90, 72, 21, 12, + 42, 37, 9, 12, 66, 14, 66, 67, 69, 78, 79, 82, + 82, 23, 0, 8, 75, 68, 74, 71, 80, 64, 72, 71, + 81, 17, 68, 2, 72, 81, 66, 73, 0, 5, 3, 1, 11, + 9, 73, 65, 1, 67, 2, 72, 6, 72, 0, 6, 2, 15, 8, + 1, 73, 4, 69, 72, 67, 86, 71, 6, 0, 6, 7, 7, 26, + 13, 3, 65, 6, 65, 67, 4, 94, 70, 0, 81, 64, 71, + 9, 3, 70, 13, 6, 4, 77, 9, 71, 76, 3, 87, 13, + 19, 13, 14, 17, 15, 8, 16, 16, 66, 5, 9, 3, 1, + 71, 0, 69, 67, 64, 67, 67, 64, 69, 80, 69, 72, + 76, 75, 82, 6, 10, 4, 3, 64, 67, 66, 66, 73, 81, + 79, 82, 94, 90, 104, 69, 69, 85, 1, 68, 71, 73, + 79, 84, 79, 81, 85, 77, 82, 92, 85, 90, 64, 23, + 18, 11, 5, 8, 1, 67, 66, 68, 4, 33, 21, 15, 8, + 16, 6, 4, 65, 4, 3, 40, 29, 23, 16, 20, 5, 64, + 68, 70, 70, 42, 22, 7, 2, 9, 66, 75, 75, 5, 42, + 29, 17, 10, 19, 5, 64, 67, 69, 62, 80, 74, 67, + 72, 74, 70, 66, 64, 67, 65, 1, 3, 76, 71, 81, + 75, 13, 76, 85, 72, 65, 68, 74, 76, 71, 74, 80, + 85, 88, 2, 3, 14, 0, 68, 4, 4, 4, 67, 64, 1, 0, + 75, 71, 76, 65, 8, 82, 64, 14, 67, 4, 1, 5, 17, + 66, 66, 5, 11, 76, 77, 95, 24, 27, 34, 16, 12, + 19, 14, 14, 16, 8, 12, 12, 64, 64, 71, 66, 2, + 70, 87, 67, 68, 69, 1, 67, 6, 9, 65, 4, 8, 9, 7, + 5, 3, 76, 70, 70, 85, 76, 83, 83, 96, 93, 97, + 99, 76, 75, 99, 66, 66, 75, 86, 79, 80, 86, 80, + 85, 88, 95, 97, 105, 108, 64, 73, 87, 71, 6, 4, + 11, 13, 15, 28, 18, 25, 26, 41, 31, 29, 42, 40, + 47, 20, 6, 71, 80, 93, 107, 117, 126, 9, 42, 35, + 34, 25, 31, 15, 11, 13, 3, 75, 69, 6, 67, 17, + 26, 66, 0, 10, 14, 4, 13, 25, 4, 0, 34, 11, 71, + 90, 104, 126, 126, 126, 126 }, + + { + + 37, + 5, 80, 37, 5, 80, 69, 7, 23, 12, 65, 80, 14, 20, + 52, 14, 22, 67, 12, 21, 64, 73, 64, 85, 101, 2, + 4, 120, 123, 122, 31, 1, 69, 12, 21, 64, 73, 4, + 15, 0, 1, 68, 73, 67, 81, 75, 92, 5, 69, 73, 64, + 77, 74, 88, 11, 0, 65, 69, 6, 3, 22, 0, 0, 0, + 64, 94, 97, 6, 67, 68, 18, 69, 89, 70, 23, 14, + 44, 39, 11, 13, 64, 16, 64, 66, 67, 77, 78, 81, + 81, 24, 1, 9, 74, 67, 73, 70, 78, 64, 72, 71, + 81, 18, 68, 3, 71, 80, 65, 72, 1, 5, 3, 2, 11, + 9, 72, 65, 2, 67, 2, 71, 6, 72, 0, 6, 1, 15, 8, + 1, 72, 5, 68, 72, 67, 85, 71, 6, 0, 7, 7, 7, 27, + 14, 4, 65, 7, 65, 67, 4, 94, 70, 0, 81, 64, 72, + 9, 3, 70, 14, 7, 4, 78, 9, 71, 75, 3, 87, 13, + 19, 13, 14, 17, 15, 8, 16, 16, 66, 5, 9, 3, 1, + 71, 0, 69, 67, 64, 67, 67, 64, 69, 79, 69, 71, + 76, 76, 81, 5, 9, 3, 2, 66, 69, 67, 67, 75, 82, + 80, 83, 96, 91, 105, 69, 69, 85, 0, 69, 72, 74, + 80, 85, 80, 82, 85, 77, 82, 91, 84, 89, 0, 24, + 18, 11, 6, 9, 2, 66, 65, 66, 5, 33, 21, 15, 8, + 17, 7, 5, 64, 6, 3, 41, 30, 23, 16, 21, 5, 64, + 67, 69, 70, 43, 22, 7, 2, 10, 65, 74, 74, 5, 42, + 29, 17, 10, 19, 5, 0, 66, 68, 62, 79, 73, 66, + 70, 72, 69, 64, 1, 65, 0, 3, 5, 75, 70, 80, 74, + 15, 75, 85, 71, 64, 67, 74, 76, 71, 74, 80, 86, + 89, 2, 3, 15, 0, 68, 4, 4, 4, 67, 64, 1, 0, 75, + 71, 76, 64, 9, 82, 64, 14, 67, 4, 1, 5, 18, 66, + 67, 5, 11, 76, 77, 96, 24, 27, 34, 15, 11, 18, + 12, 12, 14, 6, 10, 10, 66, 66, 72, 68, 64, 73, + 91, 69, 70, 70, 0, 68, 6, 10, 64, 6, 11, 8, 6, + 4, 2, 78, 71, 71, 87, 77, 84, 84, 97, 94, 98, + 99, 76, 75, 100, 67, 67, 76, 87, 80, 81, 87, 81, + 86, 89, 96, 98, 106, 108, 64, 73, 87, 70, 7, 5, + 12, 14, 16, 30, 19, 26, 27, 43, 33, 30, 44, 42, + 47, 19, 4, 73, 82, 95, 110, 119, 126, 10, 43, + 36, 35, 26, 32, 16, 12, 14, 4, 74, 68, 7, 66, + 19, 28, 65, 1, 11, 15, 5, 14, 26, 4, 0, 33, 9, + 73, 92, 107, 126, 126, 126, 126 }, + + { + + 36, + 5, 80, 36, 5, 80, 67, 8, 23, 12, 65, 81, 13, 19, + 52, 14, 24, 67, 13, 22, 64, 74, 64, 86, 102, 1, + 2, 122, 124, 123, 34, 2, 69, 13, 22, 64, 73, 5, + 15, 64, 1, 68, 72, 67, 81, 75, 92, 5, 69, 72, + 64, 77, 74, 88, 11, 0, 65, 69, 6, 3, 22, 0, 0, + 0, 0, 94, 97, 6, 68, 68, 18, 69, 89, 68, 25, 16, + 45, 41, 12, 14, 0, 17, 0, 65, 65, 77, 78, 81, + 80, 24, 1, 9, 73, 67, 72, 70, 77, 64, 72, 71, + 81, 18, 68, 3, 71, 80, 65, 72, 1, 5, 3, 2, 11, + 9, 72, 65, 2, 67, 2, 71, 5, 72, 0, 5, 0, 14, 7, + 0, 71, 5, 68, 73, 68, 85, 72, 6, 0, 7, 7, 7, 28, + 14, 4, 65, 7, 65, 68, 4, 95, 70, 0, 82, 64, 73, + 9, 3, 71, 14, 7, 4, 79, 9, 72, 75, 3, 88, 12, + 19, 13, 14, 17, 15, 8, 16, 16, 66, 5, 9, 3, 1, + 71, 0, 69, 67, 64, 67, 68, 65, 70, 79, 69, 71, + 77, 77, 81, 3, 7, 1, 0, 68, 71, 69, 69, 77, 84, + 82, 85, 98, 93, 106, 69, 69, 86, 64, 70, 73, 75, + 82, 86, 81, 83, 86, 78, 83, 91, 84, 88, 0, 24, + 18, 11, 6, 9, 2, 66, 65, 65, 6, 33, 21, 15, 8, + 17, 7, 6, 0, 8, 3, 41, 30, 23, 16, 21, 5, 64, + 67, 68, 70, 43, 22, 7, 2, 10, 65, 74, 73, 5, 41, + 28, 16, 9, 19, 5, 0, 66, 68, 62, 78, 72, 65, 69, + 71, 68, 0, 2, 64, 1, 4, 7, 74, 69, 80, 73, 16, + 75, 86, 71, 0, 67, 74, 77, 71, 75, 81, 87, 90, + 1, 3, 15, 0, 69, 3, 4, 4, 68, 65, 1, 0, 76, 71, + 76, 64, 9, 82, 65, 13, 68, 4, 0, 4, 18, 67, 68, + 5, 11, 77, 78, 98, 23, 26, 33, 14, 10, 17, 10, + 10, 12, 4, 7, 7, 69, 68, 73, 71, 67, 76, 95, 72, + 72, 72, 64, 69, 6, 11, 64, 7, 13, 6, 4, 2, 0, + 80, 73, 73, 89, 78, 86, 85, 99, 95, 99, 100, 77, + 75, 101, 68, 68, 78, 89, 82, 83, 88, 83, 88, 90, + 97, 99, 107, 109, 65, 74, 88, 70, 7, 5, 13, 14, + 17, 31, 20, 27, 28, 44, 34, 31, 45, 43, 46, 17, + 2, 75, 85, 98, 113, 122, 126, 10, 43, 36, 35, + 26, 33, 16, 12, 14, 4, 74, 68, 8, 66, 20, 29, + 65, 2, 12, 16, 5, 14, 27, 4, 0, 32, 7, 75, 95, + 110, 126, 126, 126, 126 }, + + { + + 35, + 5, 80, 35, 5, 80, 65, 10, 24, 12, 66, 83, 11, + 18, 52, 14, 26, 67, 14, 23, 64, 75, 64, 87, 103, + 1, 0, 123, 125, 123, 37, 3, 69, 14, 23, 64, 72, + 6, 16, 64, 1, 67, 71, 68, 82, 75, 92, 5, 69, 72, + 64, 77, 74, 88, 12, 0, 65, 69, 7, 3, 22, 0, 0, + 0, 0, 94, 97, 7, 69, 68, 18, 69, 89, 66, 27, 17, + 47, 43, 13, 15, 2, 18, 1, 64, 0, 76, 78, 80, 79, + 24, 1, 9, 73, 67, 72, 69, 75, 64, 73, 72, 82, + 18, 68, 3, 70, 80, 64, 72, 2, 5, 3, 2, 11, 8, + 72, 65, 2, 67, 2, 71, 4, 72, 0, 4, 64, 13, 6, + 64, 71, 6, 68, 74, 68, 84, 72, 6, 0, 7, 7, 7, + 29, 14, 4, 65, 8, 65, 69, 4, 96, 70, 0, 82, 65, + 74, 9, 3, 72, 14, 7, 4, 81, 9, 73, 75, 3, 88, + 11, 19, 13, 14, 17, 15, 8, 16, 16, 66, 4, 9, 3, + 1, 71, 64, 70, 68, 65, 68, 69, 66, 70, 79, 70, + 71, 78, 79, 81, 2, 5, 64, 65, 70, 73, 71, 71, + 79, 86, 83, 86, 100, 95, 107, 69, 69, 86, 65, + 71, 74, 77, 83, 88, 82, 84, 87, 78, 83, 91, 84, + 88, 1, 24, 18, 11, 6, 9, 2, 66, 64, 64, 6, 33, + 21, 15, 8, 18, 8, 7, 1, 10, 3, 41, 30, 23, 16, + 22, 5, 64, 67, 68, 70, 43, 22, 6, 2, 10, 65, 74, + 72, 5, 41, 28, 15, 8, 19, 5, 0, 65, 67, 62, 77, + 71, 65, 68, 70, 67, 2, 3, 0, 2, 6, 8, 74, 68, + 80, 73, 18, 75, 86, 71, 0, 67, 75, 77, 71, 76, + 82, 88, 91, 1, 2, 15, 64, 69, 3, 3, 4, 69, 65, + 0, 0, 77, 71, 77, 64, 9, 83, 65, 13, 69, 4, 64, + 4, 18, 68, 69, 4, 10, 78, 78, 99, 22, 25, 33, + 13, 9, 15, 8, 8, 10, 1, 5, 5, 72, 71, 74, 74, + 70, 79, 99, 75, 75, 74, 65, 70, 6, 12, 64, 8, + 15, 4, 2, 0, 65, 82, 75, 74, 91, 80, 87, 86, + 100, 97, 100, 101, 77, 76, 102, 69, 70, 79, 91, + 84, 85, 90, 84, 90, 92, 98, 101, 109, 110, 66, + 75, 89, 70, 8, 5, 13, 15, 17, 32, 21, 28, 29, + 45, 35, 32, 47, 44, 46, 16, 0, 78, 87, 101, 116, + 125, 126, 11, 44, 37, 36, 26, 34, 17, 12, 15, 5, + 74, 67, 8, 65, 21, 30, 65, 2, 12, 16, 5, 15, 28, + 4, 0, 31, 5, 78, 98, 113, 126, 126, 126, 126 }, + + { + + 33, + 5, 80, 33, 5, 80, 64, 11, 24, 12, 66, 84, 10, + 16, 52, 14, 29, 68, 15, 23, 64, 76, 64, 88, 104, + 0, 65, 125, 126, 124, 40, 4, 69, 15, 23, 64, 72, + 7, 16, 65, 1, 67, 71, 68, 82, 74, 92, 5, 69, 71, + 65, 78, 74, 88, 12, 1, 65, 68, 7, 3, 22, 0, 0, + 0, 1, 95, 97, 7, 70, 68, 18, 69, 89, 65, 28, 19, + 48, 44, 14, 16, 3, 20, 2, 0, 2, 76, 77, 80, 78, + 24, 1, 9, 72, 67, 71, 69, 74, 65, 73, 72, 82, + 18, 68, 3, 70, 80, 64, 72, 2, 5, 2, 2, 11, 8, + 72, 65, 2, 67, 2, 71, 4, 73, 0, 4, 65, 12, 5, + 65, 70, 6, 68, 75, 69, 84, 73, 6, 64, 7, 7, 7, + 30, 15, 4, 66, 8, 66, 70, 3, 97, 70, 0, 83, 65, + 75, 8, 2, 72, 15, 7, 4, 82, 8, 74, 75, 3, 89, + 11, 19, 13, 14, 17, 15, 8, 16, 15, 66, 4, 9, 3, + 1, 72, 64, 70, 68, 65, 68, 69, 67, 71, 79, 70, + 71, 79, 80, 81, 0, 3, 66, 67, 72, 75, 73, 73, + 81, 88, 85, 88, 103, 97, 108, 69, 70, 87, 66, + 72, 75, 78, 85, 89, 83, 86, 87, 79, 84, 91, 84, + 87, 1, 24, 18, 11, 6, 9, 2, 66, 64, 0, 7, 33, + 21, 15, 8, 18, 8, 7, 1, 12, 3, 41, 30, 23, 16, + 22, 5, 64, 67, 67, 70, 43, 21, 6, 2, 10, 65, 74, + 72, 5, 40, 27, 14, 7, 19, 5, 0, 65, 67, 62, 76, + 70, 64, 66, 69, 66, 3, 5, 1, 4, 7, 10, 73, 68, + 79, 72, 19, 74, 87, 71, 1, 67, 75, 78, 72, 77, + 83, 89, 92, 0, 2, 15, 64, 70, 2, 3, 4, 70, 66, + 0, 64, 78, 72, 77, 64, 9, 83, 66, 12, 70, 4, 65, + 3, 18, 69, 70, 4, 10, 79, 79, 101, 21, 25, 32, + 12, 7, 14, 6, 6, 8, 64, 2, 2, 75, 73, 76, 77, + 74, 82, 103, 77, 77, 76, 66, 72, 6, 13, 0, 9, + 17, 3, 0, 65, 66, 84, 77, 76, 93, 81, 89, 88, + 102, 98, 101, 102, 78, 76, 103, 71, 71, 81, 92, + 86, 87, 91, 86, 92, 93, 99, 102, 110, 110, 67, + 75, 90, 70, 8, 6, 14, 15, 18, 33, 21, 29, 30, + 46, 36, 33, 48, 45, 45, 14, 65, 80, 90, 104, + 119, 126, 126, 11, 44, 37, 36, 27, 34, 17, 13, + 15, 5, 73, 67, 9, 65, 22, 31, 65, 3, 13, 17, 5, + 15, 29, 4, 0, 29, 3, 80, 101, 116, 126, 126, + 126, 126 }, + + { + + 32, + 5, 80, 32, 5, 80, 1, 13, 24, 12, 67, 86, 8, 15, + 52, 14, 31, 68, 16, 24, 64, 76, 0, 89, 105, 0, + 67, 126, 126, 124, 43, 6, 69, 16, 24, 64, 72, 8, + 16, 65, 1, 67, 70, 68, 83, 74, 92, 5, 69, 70, + 65, 78, 74, 88, 13, 1, 64, 68, 8, 3, 22, 0, 0, + 0, 1, 95, 97, 8, 71, 68, 18, 68, 89, 0, 30, 20, + 50, 46, 16, 17, 4, 21, 3, 1, 4, 75, 77, 79, 77, + 24, 1, 9, 72, 67, 71, 68, 72, 65, 73, 72, 82, + 18, 68, 4, 69, 80, 64, 72, 3, 5, 2, 2, 11, 8, + 71, 65, 3, 67, 2, 71, 3, 73, 0, 3, 66, 12, 4, + 66, 69, 7, 68, 76, 69, 83, 73, 6, 64, 7, 7, 7, + 31, 15, 4, 66, 8, 66, 71, 3, 98, 70, 0, 83, 66, + 76, 8, 2, 73, 15, 7, 4, 83, 8, 75, 75, 3, 89, + 10, 19, 13, 14, 17, 15, 8, 16, 15, 66, 4, 9, 3, + 1, 72, 64, 70, 69, 65, 68, 70, 68, 71, 79, 71, + 71, 80, 81, 81, 64, 1, 67, 68, 74, 77, 75, 75, + 83, 90, 87, 90, 105, 98, 109, 69, 70, 87, 67, + 73, 76, 79, 86, 91, 84, 87, 88, 79, 84, 91, 84, + 87, 1, 24, 18, 11, 6, 9, 3, 65, 0, 1, 7, 33, 21, + 15, 8, 18, 9, 8, 2, 14, 3, 41, 30, 23, 16, 22, + 5, 64, 67, 66, 70, 44, 21, 5, 2, 10, 65, 74, 71, + 5, 40, 26, 13, 6, 19, 5, 0, 65, 66, 62, 75, 69, + 0, 65, 68, 65, 4, 6, 2, 5, 9, 12, 72, 67, 79, + 72, 20, 74, 87, 70, 2, 67, 76, 78, 72, 77, 84, + 90, 93, 64, 1, 15, 65, 70, 2, 3, 4, 71, 67, 0, + 64, 79, 72, 77, 64, 9, 83, 67, 12, 70, 4, 66, 2, + 18, 70, 71, 3, 9, 80, 80, 103, 20, 24, 32, 11, + 6, 12, 4, 4, 6, 67, 64, 0, 78, 75, 77, 80, 77, + 85, 107, 80, 80, 78, 67, 73, 6, 14, 0, 10, 19, + 1, 64, 67, 68, 86, 79, 77, 95, 82, 91, 89, 104, + 100, 102, 103, 78, 76, 104, 72, 73, 83, 94, 87, + 88, 93, 87, 93, 95, 100, 103, 111, 111, 67, 76, + 91, 69, 9, 6, 14, 16, 19, 34, 22, 30, 31, 48, + 37, 34, 49, 47, 45, 12, 67, 83, 92, 107, 122, + 126, 126, 11, 45, 38, 37, 27, 35, 17, 13, 16, 6, + 73, 67, 10, 64, 23, 32, 64, 4, 13, 17, 5, 16, + 30, 4, 0, 28, 1, 83, 104, 119, 126, 126, 126, + 126 }, + + { + + 31, + 5, 81, 31, 5, 81, 3, 14, 25, 12, 67, 87, 7, 14, + 52, 14, 33, 68, 16, 25, 64, 77, 0, 90, 106, 64, + 68, 126, 126, 125, 46, 7, 69, 16, 25, 64, 71, 9, + 17, 66, 2, 66, 69, 69, 83, 74, 92, 5, 68, 70, + 65, 78, 73, 88, 13, 1, 64, 68, 8, 3, 22, 0, 0, + 0, 2, 95, 97, 8, 71, 69, 18, 68, 88, 2, 32, 22, + 51, 48, 17, 18, 6, 22, 4, 1, 5, 75, 77, 79, 77, + 25, 1, 9, 71, 66, 70, 68, 71, 65, 74, 73, 83, + 18, 68, 4, 69, 80, 0, 72, 3, 5, 2, 3, 11, 7, 71, + 65, 3, 67, 2, 71, 2, 73, 0, 2, 67, 11, 4, 66, + 69, 7, 68, 76, 70, 83, 74, 6, 64, 7, 7, 7, 31, + 15, 4, 66, 9, 66, 72, 3, 98, 71, 0, 84, 66, 77, + 8, 2, 74, 15, 7, 4, 85, 8, 76, 75, 3, 90, 9, 19, + 12, 14, 17, 15, 8, 16, 15, 66, 3, 9, 2, 1, 72, + 65, 71, 69, 66, 69, 71, 69, 72, 79, 71, 70, 81, + 83, 81, 66, 64, 69, 70, 76, 79, 77, 77, 85, 92, + 88, 91, 107, 100, 110, 69, 70, 88, 68, 74, 77, + 81, 88, 92, 85, 88, 89, 80, 85, 91, 84, 86, 2, + 24, 18, 11, 7, 9, 3, 65, 0, 2, 8, 32, 21, 15, 8, + 19, 9, 9, 3, 16, 3, 42, 30, 23, 16, 23, 5, 64, + 66, 66, 70, 44, 21, 5, 2, 10, 64, 73, 70, 5, 39, + 26, 12, 6, 19, 5, 1, 64, 66, 62, 75, 68, 0, 64, + 67, 64, 6, 7, 3, 6, 10, 13, 72, 66, 79, 71, 22, + 74, 88, 70, 2, 67, 76, 79, 72, 78, 85, 91, 94, + 64, 1, 15, 65, 71, 1, 2, 4, 71, 67, 64, 64, 79, + 72, 78, 64, 9, 84, 67, 11, 71, 3, 67, 2, 19, 70, + 72, 3, 9, 80, 80, 104, 19, 23, 31, 10, 5, 11, 2, + 2, 4, 69, 66, 66, 81, 78, 78, 82, 80, 88, 111, + 83, 82, 80, 68, 74, 6, 15, 0, 12, 22, 64, 66, + 69, 70, 88, 81, 79, 97, 84, 92, 90, 105, 101, + 103, 104, 79, 77, 105, 73, 74, 84, 96, 89, 90, + 94, 89, 95, 96, 102, 105, 113, 112, 68, 77, 92, + 69, 9, 6, 15, 16, 19, 36, 23, 31, 32, 49, 39, + 35, 51, 48, 44, 11, 69, 85, 95, 109, 125, 126, + 126, 12, 45, 38, 37, 27, 36, 18, 13, 16, 6, 73, + 66, 10, 64, 24, 33, 64, 4, 14, 18, 5, 16, 31, 4, + 0, 27, 64, 85, 107, 123, 126, 126, 126, 126 }, + + { + + 30, + 5, 81, 30, 5, 81, 5, 16, 25, 12, 68, 89, 5, 12, + 52, 14, 36, 69, 17, 25, 64, 78, 0, 91, 107, 64, + 70, 126, 126, 125, 49, 8, 69, 17, 25, 64, 71, + 10, 17, 66, 2, 66, 69, 69, 84, 73, 92, 5, 68, + 69, 66, 78, 73, 88, 14, 2, 64, 67, 9, 3, 22, 0, + 0, 0, 2, 95, 97, 9, 72, 69, 18, 68, 88, 3, 34, + 23, 53, 50, 18, 19, 7, 24, 5, 2, 7, 74, 76, 78, + 76, 25, 1, 9, 71, 66, 70, 67, 69, 66, 74, 73, + 83, 18, 68, 4, 68, 80, 0, 72, 4, 5, 1, 3, 11, 7, + 71, 65, 3, 67, 2, 71, 2, 73, 0, 2, 68, 10, 3, + 67, 68, 8, 68, 77, 70, 82, 74, 6, 65, 7, 7, 7, + 32, 16, 4, 66, 9, 66, 73, 3, 99, 71, 0, 84, 67, + 78, 7, 2, 74, 16, 7, 4, 86, 7, 77, 75, 3, 90, 9, + 19, 12, 14, 17, 15, 8, 16, 15, 66, 3, 9, 2, 1, + 72, 65, 71, 70, 66, 69, 71, 70, 72, 79, 72, 70, + 82, 84, 81, 67, 66, 71, 72, 78, 81, 79, 79, 87, + 94, 90, 93, 110, 102, 111, 69, 71, 88, 69, 75, + 78, 82, 89, 94, 86, 89, 89, 80, 85, 91, 84, 86, + 2, 24, 18, 11, 7, 9, 3, 65, 1, 3, 8, 32, 21, 15, + 8, 19, 10, 10, 3, 18, 3, 42, 30, 23, 16, 23, 5, + 64, 66, 65, 70, 44, 20, 4, 2, 10, 64, 73, 70, 5, + 39, 25, 11, 5, 19, 5, 1, 64, 65, 62, 74, 67, 1, + 1, 66, 0, 7, 9, 4, 8, 12, 15, 71, 65, 78, 71, + 23, 73, 88, 70, 3, 67, 77, 79, 73, 79, 86, 92, + 95, 65, 0, 15, 66, 71, 1, 2, 4, 72, 68, 64, 65, + 80, 72, 78, 64, 9, 84, 68, 11, 72, 3, 68, 1, 19, + 71, 73, 2, 8, 81, 81, 106, 18, 23, 31, 9, 3, 9, + 0, 0, 2, 72, 69, 68, 84, 80, 80, 85, 84, 91, + 115, 85, 85, 82, 69, 75, 6, 16, 1, 13, 24, 65, + 68, 71, 71, 90, 83, 80, 99, 85, 94, 92, 107, + 103, 104, 105, 79, 77, 106, 75, 76, 86, 97, 91, + 92, 96, 90, 97, 98, 103, 106, 114, 112, 69, 77, + 93, 69, 10, 7, 15, 17, 20, 37, 24, 32, 33, 50, + 40, 36, 52, 49, 44, 9, 71, 88, 97, 112, 126, + 126, 126, 12, 46, 39, 38, 28, 37, 18, 14, 17, 7, + 72, 66, 11, 0, 25, 34, 64, 5, 14, 18, 5, 17, 32, + 4, 0, 25, 66, 88, 110, 126, 126, 126, 126, 126 }, + + { + + 28, + 4, 81, 28, 4, 81, 6, 17, 25, 12, 68, 90, 4, 11, + 52, 14, 38, 69, 18, 26, 64, 79, 0, 92, 109, 65, + 72, 126, 126, 126, 51, 9, 69, 18, 26, 64, 71, + 11, 17, 67, 2, 66, 68, 70, 84, 73, 93, 5, 68, + 69, 66, 79, 73, 88, 14, 2, 64, 67, 9, 3, 22, 0, + 0, 0, 3, 96, 97, 9, 73, 69, 18, 68, 88, 5, 35, + 25, 54, 51, 19, 20, 8, 25, 6, 3, 9, 74, 76, 78, + 75, 25, 1, 9, 70, 66, 69, 67, 68, 66, 75, 74, + 84, 18, 68, 4, 68, 80, 0, 72, 4, 4, 1, 3, 11, 6, + 71, 65, 3, 67, 1, 71, 1, 74, 0, 1, 70, 9, 2, 68, + 68, 8, 68, 78, 71, 82, 75, 5, 65, 7, 7, 7, 33, + 16, 4, 67, 9, 67, 74, 2, 100, 71, 0, 85, 67, 79, + 7, 1, 75, 16, 7, 4, 88, 7, 78, 75, 3, 91, 8, 18, + 12, 14, 17, 14, 7, 16, 14, 67, 2, 9, 2, 0, 73, + 66, 72, 70, 67, 70, 72, 71, 73, 79, 72, 70, 83, + 86, 81, 69, 68, 73, 74, 80, 84, 81, 81, 89, 96, + 92, 95, 112, 104, 112, 69, 71, 89, 70, 77, 80, + 84, 91, 95, 88, 91, 90, 81, 86, 91, 84, 85, 2, + 24, 18, 11, 7, 9, 3, 65, 1, 4, 9, 32, 21, 15, 8, + 19, 10, 10, 4, 19, 3, 42, 30, 23, 15, 23, 5, 64, + 66, 65, 70, 44, 20, 4, 2, 10, 64, 73, 69, 5, 38, + 24, 10, 4, 18, 5, 1, 64, 65, 62, 73, 66, 1, 2, + 65, 0, 8, 10, 5, 9, 13, 16, 71, 65, 78, 70, 24, + 73, 89, 70, 3, 67, 77, 80, 73, 80, 87, 94, 96, + 66, 0, 15, 66, 72, 0, 1, 3, 73, 69, 65, 65, 81, + 73, 79, 64, 9, 85, 69, 10, 73, 3, 69, 0, 19, 72, + 74, 2, 8, 82, 82, 108, 17, 22, 30, 7, 2, 8, 65, + 65, 64, 74, 72, 71, 87, 83, 81, 88, 87, 94, 119, + 88, 87, 84, 71, 77, 6, 16, 1, 14, 26, 67, 70, + 73, 73, 93, 85, 82, 101, 87, 96, 93, 109, 104, + 105, 106, 80, 78, 107, 76, 77, 88, 99, 93, 94, + 97, 92, 99, 99, 104, 108, 116, 113, 70, 78, 94, + 69, 10, 7, 16, 17, 20, 38, 24, 33, 34, 51, 41, + 37, 53, 50, 43, 7, 73, 90, 100, 115, 126, 126, + 126, 12, 46, 39, 38, 28, 37, 18, 14, 17, 7, 72, + 66, 11, 0, 26, 35, 64, 5, 15, 19, 5, 17, 32, 4, + 64, 24, 68, 90, 113, 126, 126, 126, 126, 126 }, + + { + + 27, + 4, 81, 27, 4, 81, 8, 18, 26, 12, 68, 91, 3, 10, + 52, 14, 40, 69, 19, 27, 64, 79, 1, 93, 110, 66, + 74, 126, 126, 126, 54, 11, 69, 19, 27, 64, 70, + 12, 18, 68, 2, 65, 67, 70, 84, 73, 93, 5, 68, + 68, 66, 79, 73, 88, 14, 2, 0, 67, 9, 3, 22, 0, + 0, 0, 4, 96, 97, 9, 74, 69, 18, 67, 88, 7, 37, + 27, 55, 53, 21, 21, 10, 26, 8, 4, 11, 74, 76, + 78, 74, 25, 1, 9, 69, 66, 68, 66, 67, 66, 75, + 74, 84, 18, 68, 5, 67, 79, 1, 72, 4, 4, 1, 3, + 11, 6, 70, 65, 4, 67, 1, 70, 0, 74, 0, 0, 71, 9, + 1, 69, 67, 8, 67, 79, 72, 82, 76, 5, 65, 8, 7, + 7, 34, 16, 4, 67, 10, 67, 74, 2, 101, 71, 0, 86, + 67, 80, 7, 1, 76, 16, 7, 4, 89, 7, 78, 75, 3, + 92, 7, 18, 12, 14, 17, 14, 7, 16, 14, 67, 2, 9, + 2, 0, 73, 66, 72, 70, 67, 70, 73, 71, 73, 79, + 72, 70, 84, 87, 81, 71, 69, 74, 75, 82, 86, 82, + 82, 91, 98, 93, 96, 114, 105, 113, 69, 71, 90, + 71, 78, 81, 85, 92, 96, 89, 92, 91, 82, 87, 91, + 83, 84, 3, 25, 18, 11, 7, 10, 4, 64, 2, 5, 10, + 32, 21, 15, 8, 20, 10, 11, 5, 21, 3, 42, 30, 23, + 15, 24, 5, 64, 66, 64, 70, 45, 20, 4, 2, 11, 64, + 73, 68, 5, 38, 24, 10, 3, 18, 5, 1, 0, 64, 62, + 72, 65, 2, 3, 0, 1, 10, 11, 7, 10, 14, 18, 70, + 64, 78, 69, 26, 73, 90, 69, 4, 67, 77, 81, 73, + 80, 88, 95, 97, 66, 0, 15, 66, 72, 64, 1, 3, 74, + 69, 65, 65, 82, 73, 79, 0, 10, 85, 69, 9, 73, 3, + 69, 0, 19, 73, 75, 2, 8, 83, 82, 109, 17, 21, + 29, 6, 1, 7, 67, 67, 66, 76, 74, 74, 89, 85, 82, + 91, 90, 97, 123, 91, 89, 85, 72, 78, 6, 17, 1, + 15, 28, 69, 71, 75, 75, 95, 86, 83, 103, 88, 97, + 94, 110, 105, 106, 106, 80, 78, 108, 77, 78, 89, + 101, 94, 95, 98, 93, 100, 100, 105, 109, 117, + 114, 70, 79, 94, 68, 10, 7, 17, 18, 21, 39, 25, + 34, 35, 53, 42, 38, 55, 52, 42, 6, 75, 92, 103, + 118, 126, 126, 126, 13, 46, 39, 39, 28, 38, 19, + 14, 18, 8, 72, 65, 12, 0, 27, 37, 0, 6, 16, 20, + 5, 18, 33, 4, 64, 23, 70, 92, 115, 126, 126, + 126, 126, 126 }, + + { + + 26, + 4, 81, 26, 4, 81, 10, 20, 26, 12, 69, 93, 1, 8, + 52, 14, 43, 70, 20, 27, 64, 80, 1, 94, 111, 66, + 76, 126, 126, 126, 57, 12, 69, 20, 27, 64, 70, + 13, 18, 68, 2, 65, 67, 70, 85, 72, 93, 5, 68, + 67, 67, 79, 73, 88, 15, 3, 0, 66, 10, 3, 22, 0, + 0, 0, 4, 96, 97, 10, 75, 69, 18, 67, 88, 8, 39, + 28, 57, 55, 22, 22, 11, 28, 9, 5, 13, 73, 75, + 77, 73, 25, 1, 9, 69, 66, 68, 66, 65, 67, 75, + 74, 84, 18, 68, 5, 67, 79, 1, 72, 5, 4, 0, 3, + 11, 6, 70, 65, 4, 67, 1, 70, 0, 74, 0, 0, 72, 8, + 0, 70, 66, 9, 67, 80, 72, 81, 76, 5, 66, 8, 7, + 7, 35, 17, 4, 67, 10, 67, 75, 2, 102, 71, 0, 86, + 68, 81, 6, 1, 76, 17, 7, 4, 90, 6, 79, 75, 3, + 92, 7, 18, 12, 14, 17, 14, 7, 16, 14, 67, 2, 9, + 2, 0, 73, 66, 72, 71, 67, 70, 73, 72, 74, 79, + 73, 70, 85, 88, 81, 72, 71, 76, 77, 84, 88, 84, + 84, 93, 100, 95, 98, 117, 107, 114, 69, 72, 90, + 72, 79, 82, 86, 94, 98, 90, 93, 91, 82, 87, 91, + 83, 84, 3, 25, 18, 11, 7, 10, 4, 64, 2, 6, 10, + 32, 21, 15, 8, 20, 11, 12, 5, 23, 3, 42, 30, 23, + 15, 24, 5, 64, 66, 0, 70, 45, 19, 3, 2, 11, 64, + 73, 68, 5, 37, 23, 9, 2, 18, 5, 1, 0, 64, 62, + 71, 64, 3, 5, 1, 2, 11, 13, 8, 12, 16, 20, 69, + 0, 77, 69, 27, 72, 90, 69, 5, 67, 78, 81, 74, + 81, 89, 96, 98, 67, 64, 15, 67, 73, 64, 1, 3, + 75, 70, 65, 66, 83, 73, 79, 0, 10, 85, 70, 9, + 74, 3, 70, 64, 19, 74, 76, 1, 7, 84, 83, 111, + 16, 21, 29, 5, 64, 5, 69, 69, 68, 79, 77, 76, + 92, 87, 84, 94, 94, 100, 126, 93, 92, 87, 73, + 79, 6, 18, 2, 16, 30, 70, 73, 77, 76, 97, 88, + 85, 105, 89, 99, 96, 112, 107, 107, 107, 81, 78, + 109, 79, 80, 91, 102, 96, 97, 100, 95, 102, 102, + 106, 110, 118, 114, 71, 79, 95, 68, 11, 8, 17, + 18, 22, 40, 26, 35, 36, 54, 43, 39, 56, 53, 42, + 4, 77, 95, 105, 121, 126, 126, 126, 13, 47, 40, + 39, 29, 39, 19, 15, 18, 8, 71, 65, 13, 1, 28, + 38, 0, 7, 16, 20, 5, 18, 34, 4, 64, 21, 72, 95, + 118, 126, 126, 126, 126, 126 }, + + { + + 25, + 4, 82, 25, 4, 82, 12, 21, 27, 12, 69, 94, 0, 7, + 52, 14, 45, 70, 20, 28, 64, 81, 1, 95, 112, 67, + 77, 126, 126, 126, 60, 13, 69, 20, 28, 64, 69, + 14, 19, 69, 3, 64, 66, 71, 85, 72, 93, 5, 67, + 67, 67, 79, 72, 88, 15, 3, 0, 66, 10, 3, 22, 0, + 0, 0, 5, 96, 97, 10, 75, 70, 18, 67, 87, 10, 41, + 30, 58, 57, 23, 23, 13, 29, 10, 5, 14, 73, 75, + 77, 73, 26, 1, 9, 68, 65, 67, 65, 64, 67, 76, + 75, 85, 18, 68, 5, 66, 79, 2, 72, 5, 4, 0, 4, + 11, 5, 70, 65, 4, 67, 1, 70, 64, 74, 0, 64, 73, + 7, 0, 70, 66, 9, 67, 80, 73, 81, 77, 5, 66, 8, + 7, 7, 35, 17, 4, 67, 11, 67, 76, 2, 102, 72, 0, + 87, 68, 82, 6, 1, 77, 17, 7, 4, 92, 6, 80, 75, + 3, 93, 6, 18, 11, 14, 17, 14, 7, 16, 14, 67, 1, + 9, 1, 0, 73, 67, 73, 71, 68, 71, 74, 73, 74, 79, + 73, 69, 86, 90, 81, 74, 73, 78, 79, 86, 90, 86, + 86, 95, 102, 96, 99, 119, 109, 115, 69, 72, 91, + 73, 80, 83, 88, 95, 99, 91, 94, 92, 83, 88, 91, + 83, 83, 4, 25, 18, 11, 8, 10, 4, 64, 3, 7, 11, + 31, 21, 15, 8, 21, 11, 13, 6, 25, 3, 43, 30, 23, + 15, 25, 5, 64, 65, 0, 70, 45, 19, 3, 2, 11, 0, + 72, 67, 5, 37, 23, 8, 2, 18, 5, 2, 1, 0, 62, 71, + 0, 3, 6, 2, 3, 13, 14, 9, 13, 17, 21, 69, 1, 77, + 68, 29, 72, 91, 69, 5, 67, 78, 82, 74, 82, 90, + 97, 99, 67, 64, 15, 67, 73, 65, 0, 3, 75, 70, + 66, 66, 83, 73, 80, 0, 10, 86, 70, 8, 75, 2, 71, + 64, 20, 74, 77, 1, 7, 84, 83, 112, 15, 20, 28, + 4, 65, 4, 71, 71, 70, 81, 79, 79, 95, 90, 85, + 96, 97, 103, 126, 96, 94, 89, 74, 80, 6, 19, 2, + 18, 33, 72, 75, 79, 78, 99, 90, 86, 107, 91, + 100, 97, 113, 108, 108, 108, 81, 79, 110, 80, + 81, 92, 104, 98, 99, 101, 96, 104, 103, 108, + 112, 120, 115, 72, 80, 96, 68, 11, 8, 18, 19, + 22, 42, 27, 36, 37, 55, 45, 40, 58, 54, 41, 3, + 79, 97, 108, 123, 126, 126, 126, 14, 47, 40, 40, + 29, 40, 20, 15, 19, 9, 71, 64, 13, 1, 29, 39, 0, + 7, 17, 21, 5, 19, 35, 4, 64, 20, 74, 97, 121, + 126, 126, 126, 126, 126 }, + + { + + 23, + 4, 82, 23, 4, 82, 13, 23, 27, 12, 70, 96, 65, 6, + 52, 14, 47, 70, 21, 29, 64, 82, 1, 96, 113, 67, + 79, 126, 126, 126, 62, 14, 69, 21, 29, 64, 69, + 15, 19, 69, 3, 64, 65, 71, 86, 72, 93, 5, 67, + 66, 67, 80, 72, 88, 16, 3, 0, 66, 11, 3, 22, 0, + 0, 0, 5, 97, 97, 11, 76, 70, 18, 67, 87, 12, 42, + 31, 60, 58, 24, 24, 14, 30, 11, 6, 16, 72, 75, + 76, 72, 26, 1, 9, 68, 65, 67, 65, 1, 67, 76, 75, + 85, 18, 68, 5, 66, 79, 2, 72, 6, 4, 0, 4, 11, 5, + 70, 65, 4, 67, 1, 70, 65, 75, 0, 65, 74, 6, 64, + 71, 65, 10, 67, 81, 73, 80, 77, 5, 66, 8, 7, 7, + 36, 17, 4, 68, 11, 68, 77, 1, 103, 72, 0, 87, + 69, 83, 6, 0, 78, 17, 7, 4, 93, 6, 81, 75, 3, + 93, 5, 18, 11, 14, 17, 14, 7, 16, 13, 67, 1, 9, + 1, 0, 74, 67, 73, 72, 68, 71, 75, 74, 75, 79, + 74, 69, 87, 91, 81, 75, 75, 80, 81, 88, 92, 88, + 88, 97, 104, 98, 101, 121, 111, 116, 69, 72, 91, + 74, 81, 84, 89, 97, 101, 92, 96, 93, 83, 88, 91, + 83, 83, 4, 25, 18, 11, 8, 10, 4, 64, 3, 8, 11, + 31, 21, 15, 8, 21, 12, 13, 7, 27, 3, 43, 30, 23, + 15, 25, 5, 64, 65, 1, 70, 45, 19, 2, 2, 11, 0, + 72, 66, 5, 36, 22, 7, 1, 18, 5, 2, 1, 0, 62, 70, + 1, 4, 7, 3, 4, 14, 15, 10, 14, 19, 23, 68, 1, + 77, 68, 30, 72, 91, 69, 6, 67, 79, 82, 74, 83, + 91, 98, 100, 68, 65, 15, 68, 74, 65, 0, 3, 76, + 71, 66, 66, 84, 74, 80, 0, 10, 86, 71, 8, 76, 2, + 72, 65, 20, 75, 78, 0, 6, 85, 84, 114, 14, 19, + 28, 3, 66, 2, 73, 73, 72, 84, 82, 81, 98, 92, + 86, 99, 100, 106, 126, 99, 97, 91, 75, 82, 6, + 20, 2, 19, 35, 74, 77, 81, 80, 101, 92, 88, 109, + 92, 102, 98, 115, 110, 109, 109, 82, 79, 111, + 81, 83, 94, 106, 100, 101, 103, 98, 106, 105, + 109, 113, 121, 116, 73, 81, 97, 68, 12, 8, 18, + 19, 23, 43, 27, 37, 38, 56, 46, 41, 59, 55, 41, + 1, 81, 100, 110, 126, 126, 126, 126, 14, 48, 41, + 40, 29, 40, 20, 15, 19, 9, 71, 64, 14, 2, 30, + 40, 0, 8, 17, 21, 5, 19, 36, 4, 64, 19, 76, 100, + 124, 126, 126, 126, 126, 126 }, + + { + + 22, + 4, 82, 22, 4, 82, 15, 24, 27, 12, 70, 97, 66, 4, + 52, 14, 50, 71, 22, 29, 64, 82, 2, 97, 114, 68, + 81, 126, 126, 126, 62, 16, 69, 22, 29, 64, 69, + 16, 19, 70, 3, 64, 65, 71, 86, 71, 93, 5, 67, + 65, 68, 80, 72, 88, 16, 4, 1, 65, 11, 3, 22, 0, + 0, 0, 6, 97, 97, 11, 77, 70, 18, 66, 87, 13, 44, + 33, 61, 60, 26, 25, 15, 32, 12, 7, 18, 72, 74, + 76, 71, 26, 1, 9, 67, 65, 66, 64, 2, 68, 76, 75, + 85, 18, 68, 6, 65, 79, 2, 72, 6, 4, 64, 4, 11, + 5, 69, 65, 5, 67, 1, 70, 65, 75, 0, 65, 75, 6, + 65, 72, 64, 10, 67, 82, 74, 80, 78, 5, 67, 8, 7, + 7, 37, 18, 4, 68, 11, 68, 78, 1, 104, 72, 0, 88, + 69, 84, 5, 0, 78, 18, 7, 4, 94, 5, 82, 75, 3, + 94, 5, 18, 11, 14, 17, 14, 7, 16, 13, 67, 1, 9, + 1, 0, 74, 67, 73, 72, 68, 71, 75, 75, 75, 79, + 74, 69, 88, 92, 81, 77, 77, 81, 82, 90, 94, 90, + 90, 99, 106, 100, 103, 124, 112, 117, 69, 73, + 92, 75, 82, 85, 90, 98, 102, 93, 97, 93, 84, 89, + 91, 83, 82, 4, 25, 18, 11, 8, 10, 5, 0, 4, 9, + 12, 31, 21, 15, 8, 21, 12, 14, 7, 29, 3, 43, 30, + 23, 15, 25, 5, 64, 65, 2, 70, 46, 18, 2, 2, 11, + 0, 72, 66, 5, 36, 21, 6, 0, 18, 5, 2, 1, 1, 62, + 69, 2, 5, 9, 4, 5, 15, 17, 11, 16, 20, 25, 67, + 2, 76, 67, 31, 71, 92, 68, 7, 67, 79, 83, 75, + 83, 92, 99, 101, 69, 65, 15, 68, 74, 66, 0, 3, + 77, 72, 66, 67, 85, 74, 80, 0, 10, 86, 72, 7, + 76, 2, 73, 66, 20, 76, 79, 0, 6, 86, 85, 116, + 13, 19, 27, 2, 68, 1, 75, 75, 74, 86, 85, 84, + 101, 94, 88, 102, 104, 109, 126, 101, 99, 93, + 76, 83, 6, 21, 3, 20, 37, 75, 78, 83, 81, 103, + 94, 89, 111, 93, 104, 100, 117, 111, 110, 110, + 82, 79, 112, 83, 84, 96, 107, 101, 102, 104, 99, + 107, 106, 110, 114, 122, 116, 73, 81, 98, 67, + 12, 9, 19, 20, 24, 44, 28, 38, 39, 58, 47, 42, + 60, 57, 40, 64, 83, 102, 113, 126, 126, 126, + 126, 14, 48, 41, 41, 30, 41, 20, 16, 20, 10, 70, + 64, 15, 2, 31, 41, 1, 9, 18, 22, 5, 20, 37, 4, + 64, 17, 78, 102, 126, 126, 126, 126, 126, 126 }, + + { + + 21, + 4, 82, 21, 4, 82, 17, 26, 28, 12, 71, 99, 68, 3, + 52, 14, 52, 71, 23, 30, 64, 83, 2, 98, 115, 68, + 83, 126, 126, 126, 62, 17, 69, 23, 30, 64, 68, + 17, 20, 70, 3, 0, 64, 72, 87, 71, 93, 5, 67, 65, + 68, 80, 72, 88, 17, 4, 1, 65, 12, 3, 22, 0, 0, + 0, 6, 97, 97, 12, 78, 70, 18, 66, 87, 15, 46, + 34, 62, 62, 27, 26, 17, 33, 13, 8, 20, 71, 74, + 75, 70, 26, 1, 9, 67, 65, 66, 64, 4, 68, 77, 76, + 86, 18, 68, 6, 65, 79, 3, 72, 7, 4, 64, 4, 11, + 4, 69, 65, 5, 67, 1, 70, 66, 75, 0, 66, 76, 5, + 66, 73, 64, 11, 67, 83, 74, 79, 78, 5, 67, 8, 7, + 7, 38, 18, 4, 68, 12, 68, 79, 1, 105, 72, 0, 88, + 70, 85, 5, 0, 79, 18, 7, 4, 96, 5, 83, 75, 3, + 94, 4, 18, 11, 14, 17, 14, 7, 16, 13, 67, 0, 9, + 1, 0, 74, 68, 74, 73, 69, 72, 76, 76, 76, 79, + 75, 69, 89, 94, 81, 78, 79, 83, 84, 92, 96, 92, + 92, 101, 108, 101, 104, 126, 114, 118, 69, 73, + 92, 76, 83, 86, 92, 100, 104, 94, 98, 94, 84, + 89, 91, 83, 82, 5, 25, 18, 11, 8, 10, 5, 0, 4, + 10, 12, 31, 21, 15, 8, 22, 13, 15, 8, 31, 3, 43, + 30, 23, 15, 26, 5, 64, 65, 2, 70, 46, 18, 1, 2, + 11, 0, 72, 65, 5, 35, 21, 5, 64, 18, 5, 2, 2, 1, + 62, 68, 3, 5, 10, 5, 6, 17, 18, 12, 17, 22, 26, + 67, 3, 76, 67, 33, 71, 92, 68, 7, 67, 80, 83, + 75, 84, 93, 100, 102, 69, 66, 15, 69, 75, 66, + 64, 3, 78, 72, 67, 67, 86, 74, 81, 0, 10, 87, + 72, 7, 77, 2, 74, 66, 20, 77, 80, 64, 5, 87, 85, + 117, 12, 18, 27, 1, 69, 64, 77, 77, 76, 89, 87, + 86, 104, 97, 89, 105, 107, 112, 126, 104, 102, + 95, 77, 84, 6, 22, 3, 21, 39, 77, 80, 85, 83, + 105, 96, 91, 113, 95, 105, 101, 118, 113, 111, + 111, 83, 80, 113, 84, 86, 97, 109, 103, 104, + 106, 101, 109, 108, 111, 116, 124, 117, 74, 82, + 99, 67, 13, 9, 19, 20, 24, 45, 29, 39, 40, 59, + 48, 43, 62, 58, 40, 65, 85, 105, 115, 126, 126, + 126, 126, 15, 49, 42, 41, 30, 42, 21, 16, 20, + 10, 70, 0, 15, 3, 32, 42, 1, 9, 18, 22, 5, 20, + 38, 4, 64, 16, 80, 105, 126, 126, 126, 126, 126, + 126 }, + + { + + 20, + 4, 82, 20, 4, 82, 19, 27, 28, 12, 71, 100, 69, + 2, 52, 14, 54, 71, 24, 31, 64, 84, 2, 99, 116, + 69, 85, 126, 126, 126, 62, 18, 69, 24, 31, 64, + 68, 18, 20, 71, 3, 0, 0, 72, 87, 71, 93, 5, 67, + 64, 68, 80, 72, 88, 17, 4, 1, 65, 12, 3, 22, 0, + 0, 0, 7, 97, 97, 12, 79, 70, 18, 66, 87, 17, 48, + 36, 62, 62, 28, 27, 18, 34, 14, 9, 22, 71, 74, + 75, 69, 26, 1, 9, 66, 65, 65, 0, 5, 68, 77, 76, + 86, 18, 68, 6, 64, 79, 3, 72, 7, 4, 64, 4, 11, + 4, 69, 65, 5, 67, 1, 70, 67, 75, 0, 67, 77, 4, + 67, 74, 0, 11, 67, 84, 75, 79, 79, 5, 67, 8, 7, + 7, 39, 18, 4, 68, 12, 68, 80, 1, 106, 72, 0, 89, + 70, 86, 5, 0, 80, 18, 7, 4, 97, 5, 84, 75, 3, + 95, 3, 18, 11, 14, 17, 14, 7, 16, 13, 67, 0, 9, + 1, 0, 74, 68, 74, 73, 69, 72, 77, 77, 76, 79, + 75, 69, 90, 95, 81, 80, 81, 85, 86, 94, 98, 94, + 94, 103, 110, 103, 106, 126, 116, 119, 69, 73, + 93, 77, 84, 87, 93, 101, 105, 95, 99, 95, 85, + 90, 91, 83, 81, 5, 25, 18, 11, 8, 10, 5, 0, 5, + 11, 13, 31, 21, 15, 8, 22, 13, 16, 9, 33, 3, 43, + 30, 23, 15, 26, 5, 64, 65, 3, 70, 46, 18, 1, 2, + 11, 0, 72, 64, 5, 35, 20, 4, 65, 18, 5, 2, 2, 2, + 62, 67, 4, 6, 11, 6, 7, 18, 19, 13, 18, 23, 28, + 66, 4, 76, 66, 34, 71, 93, 68, 8, 67, 80, 84, + 75, 85, 94, 101, 103, 70, 66, 15, 69, 75, 67, + 64, 3, 79, 73, 67, 67, 87, 74, 81, 0, 10, 87, + 73, 6, 78, 2, 75, 67, 20, 78, 81, 64, 5, 88, 86, + 119, 11, 17, 26, 0, 70, 65, 79, 79, 78, 91, 90, + 89, 107, 99, 90, 108, 110, 115, 126, 107, 104, + 97, 78, 85, 6, 23, 3, 22, 41, 79, 82, 87, 85, + 107, 98, 92, 115, 96, 107, 102, 120, 114, 112, + 112, 83, 80, 114, 85, 87, 99, 111, 105, 106, + 107, 102, 111, 109, 112, 117, 125, 118, 75, 83, + 100, 67, 13, 9, 20, 21, 25, 46, 30, 40, 41, 60, + 49, 44, 62, 59, 39, 67, 87, 107, 118, 126, 126, + 126, 126, 15, 49, 42, 42, 30, 43, 21, 16, 21, + 11, 70, 0, 16, 3, 33, 43, 1, 10, 19, 23, 5, 21, + 39, 4, 64, 15, 82, 107, 126, 126, 126, 126, 126, + 126 }, + + { + + 18, + 3, 83, 18, 3, 83, 20, 28, 28, 12, 72, 102, 71, + 0, 51, 14, 56, 72, 24, 31, 65, 85, 2, 101, 118, + 70, 87, 126, 126, 126, 62, 19, 70, 24, 31, 65, + 68, 19, 20, 72, 3, 0, 0, 73, 88, 71, 94, 5, 67, + 64, 69, 81, 72, 88, 17, 4, 1, 65, 12, 2, 22, 0, + 0, 0, 7, 98, 97, 12, 80, 71, 18, 66, 87, 18, 49, + 37, 62, 62, 29, 28, 19, 35, 15, 9, 23, 71, 74, + 75, 69, 26, 1, 9, 66, 65, 65, 0, 6, 69, 78, 77, + 87, 18, 68, 6, 64, 79, 3, 72, 7, 3, 65, 4, 10, + 3, 69, 66, 5, 67, 0, 70, 68, 76, 64, 68, 79, 3, + 68, 75, 0, 11, 67, 85, 76, 79, 80, 4, 68, 8, 7, + 7, 39, 18, 4, 69, 12, 69, 81, 0, 107, 73, 64, + 90, 71, 87, 4, 64, 81, 18, 7, 4, 99, 4, 85, 75, + 3, 96, 2, 17, 10, 14, 17, 13, 6, 16, 12, 68, 64, + 9, 0, 64, 75, 69, 75, 74, 70, 73, 78, 78, 77, + 79, 76, 69, 91, 97, 81, 82, 83, 87, 88, 96, 101, + 96, 96, 105, 112, 105, 108, 126, 118, 121, 70, + 74, 94, 78, 86, 89, 95, 103, 107, 97, 101, 96, + 86, 91, 91, 83, 81, 5, 25, 18, 11, 8, 10, 5, 0, + 5, 12, 13, 30, 21, 15, 8, 22, 13, 16, 9, 34, 2, + 43, 30, 22, 14, 26, 5, 64, 65, 3, 70, 46, 17, 0, + 1, 11, 0, 72, 64, 5, 34, 19, 3, 66, 17, 5, 2, 2, + 2, 62, 67, 5, 6, 12, 7, 7, 19, 20, 14, 19, 24, + 29, 66, 4, 76, 66, 35, 71, 94, 68, 8, 67, 81, + 85, 76, 86, 95, 103, 105, 71, 67, 15, 70, 76, + 68, 65, 2, 80, 74, 68, 68, 88, 75, 82, 0, 10, + 88, 74, 5, 79, 1, 76, 68, 20, 79, 83, 65, 4, 89, + 87, 121, 10, 16, 25, 65, 72, 67, 81, 81, 81, 94, + 93, 92, 110, 102, 92, 111, 114, 118, 126, 110, + 107, 99, 80, 87, 6, 23, 3, 23, 43, 81, 84, 89, + 87, 110, 100, 94, 118, 98, 109, 104, 122, 116, + 113, 113, 84, 81, 116, 87, 89, 101, 113, 107, + 108, 109, 104, 113, 111, 114, 119, 126, 119, 76, + 84, 101, 67, 13, 9, 20, 21, 25, 47, 30, 41, 41, + 61, 50, 45, 62, 60, 38, 69, 90, 110, 121, 126, + 126, 126, 126, 15, 49, 42, 42, 30, 43, 21, 16, + 21, 11, 70, 0, 16, 3, 34, 44, 1, 10, 19, 23, 5, + 21, 39, 4, 65, 13, 85, 110, 126, 126, 126, 126, + 126, 126 }, + + { + + 17, + 3, 83, 17, 3, 83, 22, 30, 29, 13, 72, 103, 72, + 64, 51, 14, 59, 72, 25, 32, 65, 85, 3, 102, 119, + 70, 88, 126, 126, 126, 62, 21, 70, 25, 32, 65, + 67, 21, 21, 72, 4, 1, 1, 73, 88, 70, 94, 5, 66, + 0, 69, 81, 71, 88, 18, 5, 2, 64, 13, 2, 22, 0, + 0, 0, 8, 98, 97, 13, 80, 71, 18, 65, 86, 20, 51, + 39, 62, 62, 31, 29, 21, 37, 17, 10, 25, 70, 73, + 74, 68, 27, 2, 10, 65, 64, 64, 1, 8, 69, 78, 77, + 87, 19, 68, 7, 0, 78, 4, 71, 8, 3, 65, 5, 10, 3, + 68, 66, 6, 67, 0, 69, 68, 76, 64, 68, 80, 3, 68, + 75, 1, 12, 66, 85, 76, 78, 80, 4, 68, 9, 7, 7, + 40, 19, 5, 69, 13, 69, 81, 0, 107, 73, 64, 90, + 71, 88, 4, 64, 81, 19, 8, 4, 100, 4, 85, 74, 3, + 96, 2, 17, 10, 14, 17, 13, 6, 16, 12, 68, 64, 9, + 0, 64, 75, 69, 75, 74, 70, 73, 78, 78, 77, 78, + 76, 68, 91, 98, 80, 83, 84, 88, 89, 98, 103, 97, + 97, 107, 113, 106, 109, 126, 119, 122, 70, 74, + 94, 79, 87, 90, 96, 104, 108, 98, 102, 96, 86, + 91, 90, 82, 80, 6, 26, 18, 11, 9, 11, 6, 1, 6, + 14, 14, 30, 21, 15, 8, 23, 14, 17, 10, 36, 2, + 44, 31, 22, 14, 27, 5, 64, 64, 4, 70, 47, 17, 0, + 1, 12, 1, 71, 0, 5, 34, 19, 3, 66, 17, 5, 3, 3, + 3, 62, 66, 6, 7, 14, 9, 8, 21, 22, 16, 21, 26, + 31, 65, 5, 75, 65, 37, 70, 94, 67, 9, 66, 81, + 85, 76, 86, 95, 104, 106, 71, 67, 16, 70, 76, + 68, 65, 2, 80, 74, 68, 68, 88, 75, 82, 1, 11, + 88, 74, 5, 79, 1, 76, 68, 21, 79, 84, 65, 4, 89, + 87, 122, 10, 16, 25, 66, 73, 68, 83, 83, 83, 96, + 95, 94, 112, 104, 93, 113, 117, 121, 126, 112, + 109, 100, 81, 88, 6, 24, 4, 25, 46, 82, 85, 90, + 88, 112, 101, 95, 120, 99, 110, 105, 123, 117, + 114, 113, 84, 81, 117, 88, 90, 102, 114, 108, + 109, 110, 105, 114, 112, 115, 120, 126, 119, 76, + 84, 101, 66, 14, 10, 21, 22, 26, 49, 31, 42, 42, + 62, 52, 46, 62, 62, 38, 70, 92, 112, 123, 126, + 126, 126, 126, 16, 50, 43, 43, 31, 44, 22, 17, + 22, 12, 69, 1, 17, 4, 36, 46, 2, 11, 20, 24, 6, + 22, 40, 4, 65, 12, 87, 112, 126, 126, 126, 126, + 126, 126 }, + + { + + 16, + 3, 83, 16, 3, 83, 24, 31, 29, 13, 72, 104, 73, + 65, 51, 14, 61, 72, 26, 33, 65, 86, 3, 103, 120, + 71, 90, 126, 126, 126, 62, 22, 70, 26, 33, 65, + 67, 22, 21, 73, 4, 1, 2, 73, 88, 70, 94, 5, 66, + 1, 69, 81, 71, 88, 18, 5, 2, 64, 13, 2, 22, 0, + 0, 0, 9, 98, 97, 13, 81, 71, 18, 65, 86, 22, 53, + 41, 62, 62, 32, 30, 22, 38, 18, 11, 27, 70, 73, + 74, 67, 27, 2, 10, 64, 64, 0, 1, 9, 69, 78, 77, + 87, 19, 68, 7, 0, 78, 4, 71, 8, 3, 65, 5, 10, 3, + 68, 66, 6, 67, 0, 69, 69, 76, 64, 69, 81, 2, 69, + 76, 2, 12, 66, 86, 77, 78, 81, 4, 68, 9, 7, 7, + 41, 19, 5, 69, 13, 69, 82, 0, 108, 73, 64, 91, + 71, 89, 4, 64, 82, 19, 8, 4, 101, 4, 86, 74, 3, + 97, 1, 17, 10, 14, 17, 13, 6, 16, 12, 68, 64, 9, + 0, 64, 75, 69, 75, 74, 70, 73, 79, 79, 78, 78, + 76, 68, 92, 99, 80, 85, 86, 90, 91, 100, 105, + 99, 99, 109, 115, 108, 111, 126, 121, 123, 70, + 74, 95, 80, 88, 91, 97, 106, 109, 99, 103, 97, + 87, 92, 90, 82, 79, 6, 26, 18, 11, 9, 11, 6, 1, + 6, 15, 15, 30, 21, 15, 8, 23, 14, 18, 11, 38, 2, + 44, 31, 22, 14, 27, 5, 64, 64, 5, 70, 47, 17, 0, + 1, 12, 1, 71, 1, 5, 33, 18, 2, 67, 17, 5, 3, 3, + 3, 62, 65, 7, 8, 15, 10, 9, 22, 23, 17, 22, 27, + 33, 64, 6, 75, 64, 38, 70, 95, 67, 10, 66, 81, + 86, 76, 87, 96, 105, 107, 72, 67, 16, 70, 77, + 69, 65, 2, 81, 75, 68, 68, 89, 75, 82, 1, 11, + 88, 75, 4, 80, 1, 77, 69, 21, 80, 85, 65, 4, 90, + 88, 124, 9, 15, 24, 67, 74, 69, 85, 85, 85, 98, + 98, 97, 115, 106, 94, 116, 120, 124, 126, 115, + 111, 102, 82, 89, 6, 25, 4, 26, 48, 84, 87, 92, + 90, 114, 103, 97, 122, 100, 112, 106, 125, 118, + 115, 114, 85, 81, 118, 89, 91, 104, 116, 110, + 111, 111, 107, 116, 113, 116, 121, 126, 120, 77, + 85, 102, 66, 14, 10, 22, 22, 27, 50, 32, 43, 43, + 62, 53, 47, 62, 62, 37, 72, 94, 114, 126, 126, + 126, 126, 126, 16, 50, 43, 43, 31, 45, 22, 17, + 22, 12, 69, 1, 18, 4, 37, 47, 2, 12, 21, 25, 6, + 22, 41, 4, 65, 11, 89, 114, 126, 126, 126, 126, + 126, 126 }, + + { + + 15, + 3, 83, 15, 3, 83, 26, 33, 30, 13, 73, 106, 75, + 66, 51, 14, 62, 72, 27, 34, 65, 87, 3, 104, 121, + 71, 92, 126, 126, 126, 62, 23, 70, 27, 34, 65, + 66, 23, 22, 73, 4, 2, 3, 74, 89, 70, 94, 5, 66, + 1, 69, 81, 71, 88, 19, 5, 2, 64, 14, 2, 22, 0, + 0, 0, 9, 98, 97, 14, 82, 71, 18, 65, 86, 24, 55, + 42, 62, 62, 33, 31, 24, 39, 19, 12, 29, 69, 73, + 73, 66, 27, 2, 10, 64, 64, 0, 2, 11, 69, 79, 78, + 88, 19, 68, 7, 1, 78, 5, 71, 9, 3, 65, 5, 10, 2, + 68, 66, 6, 67, 0, 69, 70, 76, 64, 70, 82, 1, 70, + 77, 2, 13, 66, 87, 77, 77, 81, 4, 68, 9, 7, 7, + 42, 19, 5, 69, 14, 69, 83, 0, 109, 73, 64, 91, + 72, 90, 4, 64, 83, 19, 8, 4, 103, 4, 87, 74, 3, + 97, 0, 17, 10, 14, 17, 13, 6, 16, 12, 68, 65, 9, + 0, 64, 75, 70, 76, 75, 71, 74, 80, 80, 78, 78, + 77, 68, 93, 101, 80, 86, 88, 92, 93, 102, 107, + 101, 101, 111, 117, 109, 112, 126, 123, 124, 70, + 74, 95, 81, 89, 92, 99, 107, 111, 100, 104, 98, + 87, 92, 90, 82, 79, 7, 26, 18, 11, 9, 11, 6, 1, + 7, 16, 15, 30, 21, 15, 8, 24, 15, 19, 12, 40, 2, + 44, 31, 22, 14, 28, 5, 64, 64, 5, 70, 47, 17, + 64, 1, 12, 1, 71, 2, 5, 33, 18, 1, 68, 17, 5, 3, + 4, 4, 62, 64, 8, 8, 16, 11, 10, 24, 24, 18, 23, + 29, 34, 64, 7, 75, 64, 40, 70, 95, 67, 10, 66, + 82, 86, 76, 88, 97, 106, 108, 72, 68, 16, 71, + 77, 69, 66, 2, 82, 75, 69, 68, 90, 75, 83, 1, + 11, 89, 75, 4, 81, 1, 78, 69, 21, 81, 86, 66, 3, + 91, 88, 125, 8, 14, 24, 68, 75, 71, 87, 87, 87, + 101, 100, 99, 118, 109, 95, 119, 123, 126, 126, + 118, 114, 104, 83, 90, 6, 26, 4, 27, 50, 86, 89, + 94, 92, 116, 105, 98, 124, 102, 113, 107, 126, + 120, 116, 115, 85, 82, 119, 90, 93, 105, 118, + 112, 113, 113, 108, 118, 115, 117, 123, 126, + 121, 78, 86, 103, 66, 15, 10, 22, 23, 27, 51, + 33, 44, 44, 62, 54, 48, 62, 62, 37, 73, 96, 117, + 126, 126, 126, 126, 126, 17, 51, 44, 44, 31, 46, + 23, 17, 23, 13, 69, 2, 18, 5, 38, 48, 2, 12, 21, + 25, 6, 23, 42, 4, 65, 10, 91, 117, 126, 126, + 126, 126, 126, 126 }, + + }, + + { + + { + + 62, + 9, 74, 62, 9, 74, 126, 104, 10, 9, 12, 38, 62, + 62, 54, 22, 118, 65, 71, 79, 11, 13, 70, 9, 29, + 41, 62, 61, 27, 69, 126, 101, 76, 71, 79, 11, + 69, 90, 11, 20, 69, 82, 96, 4, 75, 87, 100, 7, + 74, 85, 4, 81, 86, 95, 66, 77, 70, 86, 72, 2, + 22, 0, 0, 0, 83, 86, 97, 72, 22, 1, 48, 12, 80, + 126, 91, 96, 81, 98, 102, 97, 119, 99, 110, 102, + 126, 80, 89, 94, 92, 24, 65, 84, 126, 73, 104, + 91, 126, 8, 7, 8, 2, 10, 68, 74, 88, 103, 91, + 89, 92, 76, 87, 110, 105, 78, 112, 99, 126, 126, + 126, 126, 66, 78, 71, 72, 4, 8, 70, 75, 89, 119, + 75, 43, 41, 126, 9, 2, 5, 3, 2, 67, 84, 74, 65, + 11, 6, 2, 69, 70, 8, 71, 5, 2, 22, 38, 31, 20, + 16, 19, 12, 17, 25, 66, 25, 21, 29, 89, 18, 35, + 32, 62, 62, 48, 62, 62, 62, 62, 62, 62, 62, 62, + 62, 62, 53, 62, 62, 62, 62, 62, 62, 62, 56, 62, + 62, 62, 27, 62, 62, 62, 62, 62, 62, 62, 62, 62, + 62, 62, 53, 45, 38, 22, 75, 72, 77, 28, 32, 28, + 33, 18, 21, 18, 37, 9, 66, 7, 73, 67, 116, 112, + 71, 2, 10, 66, 77, 80, 84, 87, 126, 101, 24, 10, + 2, 75, 77, 91, 107, 111, 122, 76, 19, 11, 6, 5, + 72, 69, 69, 74, 86, 66, 29, 31, 32, 11, 8, 67, + 73, 89, 11, 59, 55, 55, 44, 26, 2, 73, 70, 78, + 62, 126, 124, 110, 126, 124, 105, 121, 117, 102, + 117, 116, 122, 95, 100, 95, 111, 114, 89, 80, + 82, 85, 81, 72, 64, 67, 7, 69, 69, 69, 69, 67, + 77, 64, 2, 67, 64, 6, 65, 66, 1, 12, 66, 71, 75, + 70, 72, 3, 26, 16, 28, 26, 22, 22, 15, 22, 22, + 4, 13, 23, 66, 13, 62, 62, 62, 62, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 54, 62, 62, 62, 62, + 62, 62, 62, 62, 62, 49, 37, 26, 8, 65, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 43, 33, + 19, 15, 14, 18, 41, 41, 42, 43, 35, 39, 29, 21, + 24, 13, 70, 9, 71, 83, 31, 14, 9, 85, 81, 77, + 81, 80, 73, 74, 83, 71, 67, 2, 66, 66, 4, 4, 62, + 62, 62, 62, 62, 60, 53, 36, 6, 71, 39, 27, 21, + 11, 6, 0, 65, 67, 82, 81, 76, 72, 78, 72, 68, + 70, 76, 66, 1, 6, 2, 3, 9, 5, 62, 62, 62, 62, + 62, 60, 53, 36, 6 }, + + { + + 62, + 9, 74, 62, 9, 74, 125, 102, 11, 10, 12, 37, + 61, 62, 55, 22, 116, 65, 70, 78, 11, 13, 69, + 9, 28, 40, 61, 58, 25, 70, 124, 100, 75, 70, + 78, 11, 69, 89, 11, 20, 68, 81, 95, 4, 75, 86, + 99, 7, 73, 84, 4, 80, 85, 94, 65, 76, 70, 85, + 71, 2, 22, 0, 0, 0, 82, 86, 97, 71, 22, 1, 48, + 12, 80, 124, 89, 94, 79, 95, 100, 95, 117, 97, + 108, 100, 124, 80, 88, 93, 91, 24, 65, 83, + 124, 72, 103, 90, 125, 8, 7, 8, 2, 11, 68, 73, + 87, 102, 90, 88, 91, 75, 86, 108, 103, 77, + 110, 97, 122, 122, 123, 124, 65, 77, 70, 71, + 4, 9, 69, 74, 88, 116, 74, 41, 40, 124, 9, 3, + 5, 4, 3, 66, 82, 73, 64, 11, 6, 2, 68, 69, 7, + 70, 5, 2, 22, 37, 31, 20, 16, 19, 12, 17, 24, + 65, 25, 21, 29, 89, 18, 35, 32, 62, 62, 47, + 62, 62, 62, 61, 62, 62, 62, 62, 62, 62, 52, + 62, 62, 62, 62, 62, 62, 62, 54, 62, 60, 62, + 26, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, + 61, 52, 44, 37, 21, 75, 72, 77, 28, 31, 27, + 32, 17, 20, 17, 36, 8, 66, 6, 73, 67, 115, + 110, 70, 3, 10, 65, 76, 79, 83, 86, 124, 99, + 25, 11, 3, 74, 76, 89, 105, 109, 120, 75, 20, + 12, 7, 6, 71, 68, 68, 73, 85, 66, 30, 31, 32, + 11, 9, 66, 73, 88, 11, 59, 55, 54, 43, 26, 3, + 72, 69, 77, 62, 124, 122, 108, 124, 122, 103, + 119, 115, 100, 115, 114, 119, 94, 99, 94, 109, + 112, 88, 79, 81, 84, 80, 71, 64, 67, 7, 69, + 69, 69, 68, 66, 76, 0, 2, 66, 0, 6, 64, 65, 1, + 12, 65, 70, 74, 69, 71, 3, 25, 16, 27, 26, 22, + 22, 15, 22, 22, 4, 13, 22, 66, 12, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, + 52, 62, 62, 62, 62, 62, 62, 62, 61, 62, 48, + 36, 25, 8, 65, 62, 62, 62, 62, 62, 62, 62, 62, + 62, 62, 62, 62, 42, 32, 18, 15, 14, 17, 40, + 40, 41, 41, 34, 38, 28, 20, 23, 12, 70, 8, 71, + 83, 30, 13, 8, 84, 80, 76, 80, 78, 71, 73, 82, + 70, 66, 3, 65, 65, 4, 4, 62, 62, 62, 62, 60, + 56, 49, 32, 4, 70, 39, 28, 22, 12, 7, 1, 64, + 66, 81, 80, 75, 71, 77, 71, 67, 69, 75, 65, 2, + 6, 3, 4, 9, 5, 62, 62, 62, 62, 60, 56, 49, 32, + 4 }, + + { + + 62, + 9, 74, 62, 9, 74, 123, 101, 11, 10, 12, 36, + 59, 61, 55, 22, 114, 65, 70, 77, 11, 12, 69, + 8, 26, 39, 58, 54, 22, 72, 121, 99, 75, 70, + 77, 11, 69, 88, 11, 19, 68, 81, 94, 4, 75, 86, + 99, 7, 73, 84, 4, 80, 85, 94, 65, 76, 70, 85, + 71, 2, 22, 0, 0, 0, 81, 86, 97, 71, 21, 1, 47, + 12, 80, 122, 88, 93, 77, 93, 99, 94, 115, 96, + 107, 99, 122, 80, 88, 93, 91, 24, 65, 82, 122, + 72, 102, 89, 123, 8, 7, 8, 1, 11, 68, 73, 86, + 101, 89, 87, 90, 75, 85, 107, 102, 76, 109, + 96, 117, 118, 120, 121, 65, 77, 70, 71, 4, 9, + 69, 74, 88, 114, 74, 39, 38, 121, 9, 3, 5, 4, + 3, 66, 80, 72, 64, 11, 6, 2, 67, 68, 6, 70, 5, + 2, 21, 36, 30, 20, 15, 19, 12, 17, 23, 65, 24, + 20, 28, 89, 18, 34, 31, 62, 62, 46, 60, 62, + 62, 59, 62, 62, 62, 62, 62, 62, 50, 62, 62, + 62, 62, 62, 62, 62, 52, 62, 58, 62, 24, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 62, 59, 50, + 42, 35, 19, 75, 72, 78, 27, 30, 26, 31, 16, + 19, 16, 34, 7, 66, 5, 74, 68, 114, 109, 69, 3, + 10, 65, 75, 78, 82, 85, 122, 98, 25, 11, 3, + 73, 75, 88, 103, 107, 118, 74, 21, 13, 8, 7, + 70, 68, 68, 73, 84, 66, 31, 31, 31, 11, 9, 66, + 73, 88, 11, 59, 54, 53, 42, 26, 3, 72, 69, 77, + 62, 123, 121, 107, 122, 120, 102, 117, 113, + 99, 113, 112, 117, 93, 98, 94, 108, 110, 88, + 79, 81, 83, 80, 71, 64, 67, 6, 69, 69, 69, 68, + 66, 75, 0, 2, 66, 0, 6, 64, 65, 1, 11, 65, 70, + 74, 69, 70, 2, 24, 16, 26, 25, 21, 21, 15, 21, + 21, 4, 13, 21, 66, 11, 62, 62, 62, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 50, 62, 62, + 62, 62, 62, 62, 62, 59, 59, 46, 34, 24, 7, 66, + 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, + 62, 40, 30, 16, 14, 13, 15, 39, 39, 39, 39, + 32, 36, 26, 19, 21, 11, 71, 7, 72, 84, 28, 12, + 7, 84, 80, 75, 80, 77, 70, 73, 81, 69, 65, 3, + 65, 64, 4, 4, 62, 62, 62, 62, 57, 52, 45, 28, + 1, 70, 39, 28, 22, 12, 8, 1, 64, 66, 81, 80, + 75, 71, 77, 70, 66, 69, 75, 65, 2, 6, 3, 5, 9, + 5, 62, 62, 62, 62, 57, 52, 45, 28, 1 }, + + { + + 62, + 9, 74, 62, 9, 74, 121, 99, 12, 10, 11, 34, 57, + 60, 55, 22, 112, 65, 69, 76, 11, 12, 69, 8, + 25, 38, 56, 51, 20, 73, 118, 98, 75, 69, 76, + 11, 70, 87, 11, 19, 68, 81, 94, 4, 75, 86, 99, + 7, 73, 83, 4, 80, 84, 94, 65, 76, 70, 85, 71, + 2, 22, 0, 0, 0, 81, 86, 97, 70, 20, 1, 46, 11, + 80, 119, 87, 92, 76, 91, 97, 92, 113, 94, 106, + 98, 120, 80, 88, 92, 91, 24, 65, 81, 120, 72, + 101, 89, 121, 8, 6, 7, 1, 11, 68, 72, 86, 100, + 88, 87, 89, 74, 84, 105, 100, 76, 108, 95, + 112, 113, 117, 118, 65, 77, 70, 70, 4, 9, 68, + 73, 87, 112, 74, 37, 36, 118, 9, 3, 5, 4, 3, + 65, 79, 71, 64, 11, 6, 2, 67, 67, 5, 70, 5, 1, + 21, 35, 30, 20, 15, 19, 12, 17, 22, 65, 23, + 19, 28, 89, 18, 34, 31, 62, 62, 45, 58, 62, + 62, 57, 62, 62, 62, 62, 62, 61, 48, 62, 62, + 62, 62, 62, 62, 60, 50, 62, 56, 62, 22, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 62, 57, 48, + 40, 34, 17, 75, 72, 78, 26, 29, 25, 30, 15, + 18, 15, 32, 6, 67, 4, 75, 68, 114, 107, 68, 4, + 10, 65, 74, 78, 82, 85, 120, 97, 25, 11, 4, + 72, 74, 87, 102, 106, 116, 73, 21, 13, 8, 7, + 69, 67, 68, 73, 84, 66, 31, 31, 30, 11, 9, 66, + 73, 87, 11, 58, 54, 52, 41, 26, 3, 72, 69, 77, + 62, 122, 119, 106, 121, 119, 101, 115, 111, + 98, 112, 110, 115, 93, 97, 93, 107, 108, 87, + 79, 81, 83, 79, 71, 64, 67, 6, 69, 69, 70, 67, + 65, 74, 0, 2, 65, 0, 6, 64, 65, 1, 11, 65, 70, + 74, 69, 70, 1, 23, 16, 25, 24, 20, 21, 15, 20, + 20, 4, 13, 20, 66, 10, 62, 62, 61, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 48, 62, 62, + 62, 62, 62, 62, 62, 57, 57, 44, 32, 22, 6, 67, + 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 59, + 60, 38, 28, 15, 13, 12, 14, 37, 37, 37, 37, + 31, 34, 24, 18, 20, 10, 72, 6, 73, 85, 27, 11, + 6, 84, 79, 75, 79, 76, 69, 73, 81, 69, 65, 3, + 64, 0, 4, 4, 62, 62, 62, 59, 54, 48, 41, 24, + 65, 70, 39, 28, 22, 12, 8, 2, 64, 66, 80, 80, + 75, 70, 76, 69, 65, 69, 74, 65, 2, 6, 3, 5, 9, + 5, 62, 62, 62, 59, 54, 48, 41, 24, 65 }, + + { + + 62, + 9, 74, 62, 9, 74, 120, 98, 12, 10, 11, 33, 55, + 59, 55, 21, 110, 65, 69, 75, 10, 11, 69, 7, + 23, 37, 53, 47, 17, 75, 115, 97, 75, 69, 75, + 10, 70, 86, 11, 18, 68, 80, 93, 4, 75, 86, 99, + 7, 73, 83, 4, 80, 84, 93, 65, 76, 70, 85, 70, + 2, 22, 0, 0, 0, 80, 87, 97, 70, 19, 1, 45, 11, + 80, 117, 86, 91, 74, 89, 96, 91, 112, 93, 104, + 97, 118, 80, 87, 92, 91, 24, 65, 80, 118, 72, + 101, 88, 119, 8, 6, 7, 0, 11, 68, 72, 85, 99, + 87, 86, 88, 74, 84, 104, 99, 75, 107, 94, 107, + 109, 114, 115, 65, 76, 70, 70, 4, 9, 68, 73, + 87, 110, 74, 35, 34, 116, 9, 4, 5, 4, 3, 65, + 77, 70, 0, 10, 6, 2, 66, 67, 4, 70, 5, 1, 20, + 34, 29, 19, 14, 19, 12, 17, 21, 65, 22, 18, + 27, 89, 17, 33, 30, 62, 62, 44, 56, 62, 62, + 55, 62, 62, 62, 62, 62, 59, 46, 59, 62, 62, + 62, 62, 62, 57, 48, 62, 54, 62, 21, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 60, 55, 46, 38, + 32, 15, 75, 72, 79, 25, 28, 24, 28, 14, 16, + 14, 31, 5, 67, 3, 75, 69, 113, 106, 67, 4, 10, + 64, 74, 77, 81, 84, 118, 95, 25, 12, 4, 72, + 73, 86, 100, 104, 115, 73, 22, 14, 9, 8, 68, + 67, 68, 72, 83, 66, 32, 31, 30, 10, 9, 66, 73, + 87, 11, 58, 53, 51, 40, 26, 3, 71, 69, 77, 62, + 120, 118, 105, 119, 117, 100, 114, 110, 97, + 110, 109, 113, 92, 96, 93, 106, 107, 87, 79, + 81, 82, 79, 71, 65, 67, 5, 69, 69, 70, 67, 65, + 73, 0, 2, 65, 0, 6, 64, 65, 1, 10, 65, 70, 74, + 69, 69, 0, 22, 16, 24, 24, 19, 20, 15, 19, 19, + 4, 13, 19, 66, 9, 62, 62, 60, 62, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 46, 62, 62, 62, + 62, 62, 62, 62, 54, 54, 42, 30, 21, 5, 67, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 62, 57, 57, + 36, 26, 13, 12, 12, 12, 36, 36, 36, 35, 29, + 32, 23, 17, 18, 9, 73, 4, 74, 85, 25, 9, 4, + 83, 79, 74, 79, 75, 68, 73, 80, 68, 64, 3, 64, + 1, 4, 4, 62, 62, 62, 56, 50, 44, 36, 20, 68, + 69, 39, 28, 22, 12, 9, 2, 64, 66, 80, 80, 75, + 70, 76, 69, 64, 69, 74, 64, 3, 6, 3, 6, 9, 5, + 62, 62, 62, 56, 50, 44, 36, 20, 68 }, + + { + + 62, + 9, 74, 62, 9, 74, 118, 96, 12, 10, 10, 32, 53, + 58, 55, 21, 108, 65, 69, 74, 10, 11, 69, 6, + 21, 36, 51, 44, 15, 77, 112, 96, 74, 69, 74, + 10, 70, 85, 11, 18, 68, 80, 92, 4, 75, 86, 99, + 7, 73, 83, 4, 80, 83, 93, 65, 76, 70, 85, 70, + 2, 22, 0, 0, 0, 80, 87, 97, 69, 18, 1, 44, 10, + 80, 114, 85, 90, 72, 87, 94, 89, 110, 91, 103, + 96, 115, 80, 87, 91, 90, 24, 65, 79, 116, 72, + 100, 88, 117, 8, 5, 6, 0, 11, 68, 71, 85, 98, + 86, 86, 87, 73, 83, 102, 97, 74, 105, 93, 102, + 105, 111, 112, 64, 76, 69, 69, 4, 9, 67, 73, + 86, 108, 74, 33, 32, 113, 9, 4, 5, 4, 3, 64, + 76, 69, 0, 10, 6, 2, 66, 66, 3, 69, 5, 0, 20, + 33, 29, 19, 14, 19, 12, 17, 20, 64, 21, 18, + 27, 89, 17, 32, 29, 62, 62, 43, 55, 62, 62, + 53, 62, 62, 62, 62, 61, 57, 44, 57, 62, 60, + 62, 62, 62, 55, 46, 62, 52, 62, 19, 62, 62, + 62, 62, 62, 62, 62, 62, 61, 58, 53, 44, 37, + 30, 13, 75, 72, 79, 24, 27, 23, 27, 13, 15, + 13, 29, 4, 68, 2, 76, 70, 112, 104, 66, 5, 10, + 64, 73, 77, 81, 83, 116, 94, 25, 12, 5, 71, + 72, 85, 99, 103, 113, 72, 23, 15, 10, 8, 67, + 66, 67, 72, 83, 66, 32, 31, 29, 10, 9, 66, 73, + 86, 11, 57, 52, 50, 39, 26, 3, 71, 69, 76, 62, + 119, 116, 103, 117, 116, 99, 112, 108, 96, + 108, 107, 111, 91, 95, 92, 105, 105, 87, 79, + 80, 82, 78, 71, 65, 67, 5, 69, 69, 71, 66, 65, + 72, 0, 2, 65, 0, 6, 64, 65, 1, 10, 65, 70, 74, + 69, 69, 64, 21, 16, 23, 23, 19, 19, 15, 19, + 18, 4, 13, 18, 66, 8, 62, 62, 59, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 44, 62, 62, + 62, 62, 62, 62, 61, 52, 52, 40, 29, 19, 5, 68, + 62, 62, 62, 62, 62, 62, 62, 62, 62, 61, 55, + 54, 34, 24, 12, 12, 11, 10, 35, 34, 34, 33, + 27, 30, 21, 16, 17, 8, 73, 3, 75, 86, 24, 8, + 3, 83, 79, 73, 78, 74, 67, 72, 79, 68, 64, 3, + 0, 2, 4, 4, 62, 62, 59, 53, 47, 40, 32, 16, + 71, 69, 39, 28, 22, 12, 9, 2, 0, 65, 79, 80, + 75, 69, 76, 68, 0, 69, 74, 64, 3, 6, 4, 6, 9, + 5, 62, 62, 59, 53, 47, 40, 32, 16, 71 }, + + { + + 62, + 9, 75, 62, 9, 75, 116, 95, 13, 10, 10, 30, 51, + 57, 55, 21, 107, 65, 68, 74, 10, 10, 68, 6, + 20, 34, 48, 40, 12, 78, 110, 95, 74, 68, 74, + 10, 71, 85, 11, 17, 68, 80, 92, 4, 75, 85, 98, + 7, 72, 82, 4, 79, 83, 93, 65, 76, 70, 85, 70, + 2, 22, 0, 0, 0, 79, 87, 97, 69, 18, 0, 44, 10, + 80, 112, 84, 89, 71, 84, 93, 88, 108, 90, 102, + 95, 113, 80, 87, 91, 90, 24, 65, 78, 113, 72, + 99, 87, 115, 7, 5, 6, 64, 12, 68, 71, 84, 98, + 86, 85, 86, 73, 82, 101, 96, 74, 104, 92, 97, + 100, 108, 109, 64, 76, 69, 69, 4, 9, 67, 72, + 86, 106, 73, 31, 30, 110, 9, 4, 5, 4, 4, 64, + 74, 68, 0, 10, 6, 2, 65, 65, 2, 69, 5, 0, 19, + 32, 28, 19, 13, 19, 12, 17, 18, 64, 20, 17, + 26, 89, 17, 32, 29, 62, 62, 42, 53, 62, 62, + 51, 62, 62, 62, 62, 57, 55, 43, 55, 62, 58, + 62, 62, 62, 52, 44, 62, 50, 62, 17, 62, 62, + 62, 62, 62, 62, 62, 62, 59, 56, 50, 42, 35, + 29, 12, 75, 72, 80, 23, 26, 22, 26, 12, 14, + 12, 27, 3, 68, 1, 77, 70, 112, 103, 65, 5, 10, + 64, 72, 76, 80, 83, 114, 93, 26, 12, 5, 70, + 71, 84, 97, 101, 111, 71, 23, 15, 10, 9, 66, + 66, 67, 72, 82, 66, 33, 31, 28, 10, 9, 66, 73, + 86, 10, 57, 52, 49, 38, 25, 3, 71, 69, 76, 62, + 118, 115, 102, 116, 114, 98, 110, 106, 95, + 107, 105, 109, 91, 94, 92, 104, 103, 86, 79, + 80, 81, 78, 71, 65, 67, 4, 69, 69, 71, 66, 64, + 71, 0, 2, 64, 1, 6, 0, 64, 1, 9, 65, 70, 74, + 69, 68, 65, 20, 16, 22, 22, 18, 19, 15, 18, + 18, 4, 12, 16, 67, 7, 62, 62, 58, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 42, 62, 62, + 62, 62, 62, 62, 58, 50, 49, 38, 27, 18, 4, 69, + 62, 62, 62, 62, 62, 62, 62, 62, 61, 58, 52, + 51, 32, 23, 10, 11, 10, 9, 33, 33, 32, 31, 26, + 28, 19, 15, 15, 7, 74, 2, 76, 87, 22, 7, 2, + 83, 78, 73, 78, 73, 66, 72, 79, 67, 0, 3, 0, + 3, 4, 4, 62, 62, 57, 50, 44, 36, 28, 12, 74, + 69, 39, 28, 22, 12, 10, 3, 0, 65, 79, 79, 74, + 69, 75, 67, 1, 68, 73, 64, 3, 6, 4, 7, 9, 5, + 62, 62, 57, 50, 44, 36, 28, 12, 74 }, + + { + + 62, + 9, 75, 62, 9, 75, 114, 93, 13, 10, 9, 29, 49, + 56, 55, 21, 105, 65, 68, 73, 9, 10, 68, 5, 18, + 33, 46, 37, 10, 80, 107, 94, 74, 68, 73, 9, + 71, 84, 11, 17, 68, 79, 91, 4, 75, 85, 98, 7, + 72, 82, 4, 79, 82, 92, 65, 76, 70, 85, 69, 2, + 22, 0, 0, 0, 79, 87, 97, 68, 17, 0, 43, 9, 80, + 109, 83, 88, 69, 82, 91, 86, 107, 88, 100, 94, + 111, 80, 86, 90, 90, 24, 65, 77, 111, 72, 98, + 87, 113, 7, 4, 5, 64, 12, 68, 70, 84, 97, 85, + 85, 85, 72, 81, 99, 94, 73, 103, 91, 92, 96, + 105, 106, 64, 75, 69, 68, 4, 9, 66, 72, 85, + 104, 73, 29, 28, 107, 9, 5, 5, 4, 4, 0, 73, + 67, 1, 9, 6, 2, 65, 65, 1, 69, 5, 64, 19, 31, + 28, 18, 13, 19, 12, 17, 17, 64, 19, 16, 26, + 89, 17, 31, 28, 60, 62, 41, 51, 62, 62, 49, + 62, 61, 62, 62, 54, 53, 41, 52, 62, 55, 62, + 62, 62, 49, 42, 62, 48, 62, 16, 62, 62, 62, + 62, 62, 62, 62, 62, 57, 53, 48, 40, 33, 27, + 10, 75, 72, 80, 22, 25, 21, 24, 11, 13, 11, + 26, 2, 69, 0, 77, 71, 111, 101, 64, 6, 10, 0, + 72, 76, 80, 82, 112, 91, 26, 13, 6, 70, 70, + 83, 96, 100, 109, 71, 24, 16, 11, 9, 65, 65, + 67, 71, 82, 66, 33, 31, 28, 9, 9, 66, 73, 85, + 10, 56, 51, 48, 37, 25, 3, 70, 69, 76, 62, + 116, 113, 101, 114, 113, 97, 109, 105, 94, + 105, 104, 107, 90, 93, 91, 103, 101, 86, 79, + 80, 81, 77, 71, 66, 67, 4, 69, 69, 72, 65, 64, + 70, 0, 2, 64, 1, 6, 0, 64, 1, 9, 65, 70, 74, + 69, 68, 66, 19, 16, 21, 22, 17, 18, 15, 17, + 17, 4, 12, 15, 67, 6, 61, 62, 57, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 40, 62, 62, + 62, 62, 62, 62, 56, 48, 47, 36, 25, 16, 3, 69, + 62, 62, 62, 62, 62, 62, 62, 62, 59, 56, 50, + 48, 30, 21, 9, 10, 10, 7, 32, 31, 31, 29, 24, + 26, 18, 14, 14, 6, 75, 0, 77, 87, 21, 5, 0, + 82, 78, 72, 77, 72, 65, 72, 78, 67, 0, 3, 1, + 4, 4, 4, 62, 62, 54, 47, 40, 32, 24, 8, 77, + 68, 39, 28, 22, 12, 10, 3, 0, 65, 78, 79, 74, + 68, 75, 66, 2, 68, 73, 0, 4, 6, 4, 7, 9, 5, + 62, 62, 54, 47, 40, 32, 24, 8, 77 }, + + { + + 62, + 8, 75, 62, 8, 75, 113, 92, 13, 10, 9, 27, 46, + 55, 55, 20, 103, 66, 68, 72, 9, 9, 68, 4, 16, + 32, 43, 33, 7, 82, 104, 93, 74, 68, 72, 9, 72, + 83, 11, 16, 68, 79, 91, 3, 76, 85, 98, 7, 72, + 82, 4, 79, 82, 92, 65, 76, 70, 85, 69, 2, 22, + 0, 0, 0, 78, 88, 97, 68, 16, 0, 42, 9, 81, + 107, 82, 87, 68, 80, 90, 85, 105, 87, 99, 93, + 109, 80, 86, 90, 90, 24, 65, 76, 109, 72, 98, + 86, 111, 7, 4, 5, 65, 12, 68, 70, 83, 96, 84, + 84, 85, 72, 81, 98, 93, 73, 102, 90, 88, 92, + 102, 104, 64, 75, 69, 68, 3, 9, 66, 72, 85, + 102, 73, 27, 26, 105, 9, 5, 5, 4, 4, 0, 71, + 67, 1, 9, 5, 2, 64, 64, 64, 69, 5, 64, 18, 29, + 27, 18, 12, 19, 12, 16, 16, 64, 18, 15, 25, + 89, 16, 30, 27, 58, 62, 39, 49, 62, 62, 46, + 62, 59, 62, 62, 50, 51, 39, 50, 62, 53, 62, + 62, 62, 46, 40, 62, 46, 62, 14, 62, 62, 62, + 62, 62, 62, 62, 60, 55, 51, 46, 38, 31, 25, 8, + 75, 73, 81, 21, 23, 20, 23, 10, 11, 9, 24, 1, + 69, 64, 78, 72, 111, 100, 0, 6, 10, 0, 71, 75, + 79, 82, 110, 90, 26, 13, 6, 69, 69, 82, 94, + 98, 108, 70, 24, 16, 11, 10, 64, 65, 67, 71, + 81, 67, 34, 31, 27, 9, 9, 66, 73, 85, 10, 56, + 50, 47, 36, 25, 3, 70, 69, 76, 62, 115, 112, + 100, 113, 111, 96, 107, 103, 93, 104, 102, + 105, 90, 93, 91, 102, 100, 86, 79, 80, 80, 77, + 71, 66, 67, 3, 69, 69, 72, 65, 64, 69, 0, 1, + 64, 1, 5, 0, 64, 1, 8, 65, 70, 74, 69, 67, 67, + 18, 16, 19, 21, 16, 17, 14, 16, 16, 4, 12, 14, + 67, 4, 60, 60, 56, 62, 62, 62, 62, 62, 62, 62, + 62, 62, 62, 60, 38, 62, 62, 62, 62, 62, 62, + 53, 45, 44, 34, 23, 15, 2, 70, 62, 62, 62, 62, + 62, 62, 62, 62, 56, 53, 47, 45, 28, 19, 7, 9, + 9, 5, 30, 30, 29, 27, 22, 24, 16, 12, 12, 4, + 76, 64, 78, 88, 19, 4, 64, 82, 78, 72, 77, 71, + 64, 72, 78, 66, 1, 3, 1, 4, 4, 3, 62, 60, 51, + 44, 37, 28, 19, 3, 80, 68, 39, 28, 22, 12, 11, + 3, 0, 65, 78, 79, 74, 68, 75, 66, 2, 68, 73, + 0, 4, 6, 4, 8, 9, 4, 62, 60, 51, 44, 37, 28, + 19, 3, 80 }, + + { + + 62, + 8, 75, 62, 8, 75, 111, 91, 14, 10, 9, 26, 44, + 54, 56, 20, 101, 66, 67, 71, 9, 8, 68, 4, 15, + 31, 41, 29, 4, 83, 101, 92, 73, 67, 71, 9, 72, + 82, 11, 16, 67, 79, 90, 3, 76, 85, 98, 7, 72, + 81, 4, 79, 82, 92, 65, 76, 70, 84, 69, 2, 22, + 0, 0, 0, 77, 88, 97, 68, 15, 0, 41, 9, 81, + 105, 80, 86, 66, 78, 88, 84, 103, 85, 98, 91, + 106, 80, 86, 90, 89, 24, 65, 75, 107, 71, 97, + 85, 109, 7, 4, 5, 65, 12, 68, 70, 82, 95, 83, + 83, 84, 71, 80, 97, 91, 72, 100, 89, 83, 87, + 98, 101, 0, 75, 68, 67, 3, 9, 66, 71, 84, 99, + 73, 25, 25, 102, 9, 5, 5, 4, 4, 1, 69, 66, 1, + 9, 5, 2, 0, 0, 65, 68, 5, 64, 17, 28, 26, 18, + 11, 19, 12, 16, 15, 0, 17, 15, 24, 89, 16, 30, + 27, 56, 62, 38, 48, 62, 62, 44, 60, 57, 62, + 62, 47, 49, 37, 48, 62, 51, 62, 62, 62, 44, + 38, 62, 44, 62, 12, 62, 62, 62, 62, 62, 62, + 60, 58, 53, 49, 44, 37, 30, 24, 6, 75, 73, 81, + 21, 22, 19, 22, 9, 10, 8, 22, 0, 69, 65, 79, + 72, 110, 99, 1, 6, 10, 0, 70, 74, 78, 81, 107, + 89, 26, 13, 6, 68, 68, 81, 92, 96, 106, 69, + 25, 17, 12, 11, 0, 65, 66, 71, 80, 67, 35, 31, + 26, 9, 10, 65, 73, 84, 10, 56, 50, 46, 35, 25, + 3, 70, 69, 75, 62, 114, 111, 98, 111, 109, 95, + 105, 101, 92, 102, 100, 103, 89, 92, 90, 101, + 98, 85, 78, 79, 79, 76, 71, 66, 67, 2, 69, 69, + 72, 65, 0, 68, 1, 1, 0, 1, 5, 0, 64, 1, 7, 65, + 69, 73, 69, 66, 67, 17, 16, 18, 20, 16, 17, + 14, 16, 15, 4, 12, 13, 67, 3, 59, 59, 56, 61, + 62, 62, 62, 62, 62, 62, 62, 62, 62, 57, 36, + 62, 62, 62, 62, 62, 62, 50, 43, 42, 33, 22, + 14, 2, 71, 62, 62, 62, 62, 62, 62, 62, 62, 54, + 51, 45, 43, 26, 17, 5, 9, 8, 4, 29, 29, 27, + 25, 21, 23, 14, 11, 10, 3, 76, 65, 78, 89, 17, + 3, 65, 82, 77, 71, 77, 70, 1, 71, 77, 65, 2, + 3, 2, 5, 4, 3, 62, 58, 49, 41, 34, 24, 15, 64, + 83, 68, 39, 28, 23, 13, 12, 4, 1, 64, 78, 79, + 74, 68, 74, 65, 3, 68, 72, 0, 4, 6, 5, 9, 9, + 4, 62, 58, 49, 41, 34, 24, 15, 64, 83 }, + + { + + 62, + 8, 75, 62, 8, 75, 109, 89, 14, 10, 8, 25, 42, + 53, 56, 20, 99, 66, 67, 70, 8, 8, 68, 3, 13, + 30, 38, 26, 2, 85, 98, 91, 73, 67, 70, 8, 72, + 81, 11, 15, 67, 78, 89, 3, 76, 85, 98, 7, 72, + 81, 4, 79, 81, 91, 65, 76, 70, 84, 68, 2, 22, + 0, 0, 0, 77, 88, 97, 67, 14, 0, 40, 8, 81, + 102, 79, 85, 64, 76, 87, 82, 102, 84, 96, 90, + 104, 80, 85, 89, 89, 24, 65, 74, 105, 71, 96, + 85, 107, 7, 3, 4, 66, 12, 68, 69, 82, 94, 82, + 83, 83, 71, 79, 95, 90, 71, 99, 88, 78, 83, + 95, 98, 0, 74, 68, 67, 3, 9, 65, 71, 84, 97, + 73, 23, 23, 99, 9, 6, 5, 4, 4, 1, 68, 65, 2, + 8, 5, 2, 0, 0, 66, 68, 5, 65, 17, 27, 26, 17, + 11, 19, 12, 16, 14, 0, 16, 14, 24, 89, 16, 29, + 26, 54, 62, 37, 46, 62, 62, 42, 57, 55, 62, + 62, 43, 47, 35, 45, 61, 48, 62, 62, 62, 41, + 36, 58, 42, 62, 11, 62, 62, 62, 62, 62, 60, + 58, 56, 51, 46, 42, 35, 28, 22, 4, 75, 73, 82, + 20, 21, 18, 20, 8, 9, 7, 21, 64, 70, 66, 79, + 73, 109, 97, 2, 7, 10, 1, 70, 74, 78, 80, 105, + 87, 26, 14, 7, 68, 67, 80, 91, 95, 104, 69, + 26, 18, 13, 11, 1, 64, 66, 70, 80, 67, 35, 31, + 26, 8, 10, 65, 73, 84, 10, 55, 49, 45, 34, 25, + 3, 69, 69, 75, 62, 112, 109, 97, 109, 108, 94, + 104, 100, 91, 100, 99, 101, 88, 91, 90, 100, + 96, 85, 78, 79, 79, 76, 71, 67, 67, 2, 69, 69, + 73, 64, 0, 67, 1, 1, 0, 1, 5, 0, 64, 1, 7, 65, + 69, 73, 69, 66, 68, 16, 16, 17, 20, 15, 16, + 14, 15, 14, 4, 12, 12, 67, 2, 58, 58, 55, 59, + 60, 62, 62, 62, 62, 62, 62, 62, 62, 55, 34, + 62, 62, 62, 62, 62, 62, 48, 41, 39, 31, 20, + 12, 1, 71, 62, 62, 62, 62, 62, 62, 62, 62, 52, + 48, 43, 40, 24, 15, 4, 8, 8, 2, 28, 27, 26, + 23, 19, 21, 13, 10, 9, 2, 77, 67, 79, 89, 16, + 1, 67, 81, 77, 70, 76, 69, 2, 71, 76, 65, 2, + 3, 2, 6, 4, 3, 62, 56, 46, 38, 30, 20, 11, 68, + 86, 67, 39, 28, 23, 13, 12, 4, 1, 64, 77, 79, + 74, 67, 74, 64, 4, 68, 72, 1, 5, 6, 5, 9, 9, + 4, 62, 56, 46, 38, 30, 20, 11, 68, 86 }, + + { + + 62, + 8, 76, 62, 8, 76, 107, 88, 15, 10, 8, 23, 40, + 52, 56, 20, 98, 66, 66, 70, 8, 7, 67, 3, 12, + 28, 36, 22, 64, 86, 96, 90, 73, 66, 70, 8, 73, + 81, 11, 15, 67, 78, 89, 3, 76, 84, 97, 7, 71, + 80, 4, 78, 81, 91, 65, 76, 70, 84, 68, 2, 22, + 0, 0, 0, 76, 88, 97, 67, 14, 64, 40, 8, 81, + 100, 78, 84, 0, 73, 85, 81, 100, 82, 95, 89, + 102, 80, 85, 89, 89, 24, 65, 73, 102, 71, 95, + 84, 105, 6, 3, 4, 66, 13, 68, 69, 81, 94, 82, + 82, 82, 70, 78, 94, 88, 71, 98, 87, 73, 78, + 92, 95, 0, 74, 68, 66, 3, 9, 65, 70, 83, 95, + 72, 21, 21, 96, 9, 6, 5, 4, 5, 2, 66, 64, 2, + 8, 5, 2, 1, 1, 67, 68, 5, 65, 16, 26, 25, 17, + 10, 19, 12, 16, 12, 0, 15, 13, 23, 89, 16, 29, + 26, 52, 62, 36, 44, 61, 62, 40, 55, 53, 62, + 62, 40, 45, 34, 43, 57, 46, 62, 62, 62, 38, + 34, 55, 40, 62, 9, 62, 62, 62, 62, 62, 58, 55, + 54, 49, 44, 39, 33, 26, 21, 3, 75, 73, 82, 19, + 20, 17, 19, 7, 8, 6, 19, 65, 70, 67, 80, 73, + 109, 96, 3, 7, 10, 1, 69, 73, 77, 80, 103, 86, + 27, 14, 7, 67, 66, 79, 89, 93, 102, 68, 26, + 18, 13, 12, 2, 64, 66, 70, 79, 67, 36, 31, 25, + 8, 10, 65, 73, 83, 9, 55, 49, 44, 33, 24, 3, + 69, 69, 75, 62, 111, 108, 96, 108, 106, 93, + 102, 98, 90, 99, 97, 99, 88, 90, 89, 99, 94, + 84, 78, 79, 78, 75, 71, 67, 67, 1, 69, 69, 73, + 64, 1, 66, 1, 1, 1, 2, 5, 1, 0, 1, 6, 65, 69, + 73, 69, 65, 69, 15, 16, 16, 19, 14, 16, 14, + 14, 14, 4, 11, 10, 68, 1, 56, 57, 54, 58, 58, + 62, 62, 62, 62, 62, 62, 62, 62, 52, 32, 62, + 62, 62, 62, 62, 62, 45, 39, 37, 29, 18, 11, 0, + 72, 62, 62, 62, 62, 62, 62, 60, 59, 49, 46, + 40, 37, 22, 14, 2, 7, 7, 1, 26, 26, 24, 21, + 18, 19, 11, 9, 7, 1, 78, 68, 80, 90, 14, 0, + 68, 81, 76, 70, 76, 68, 3, 71, 76, 64, 3, 3, + 3, 7, 4, 3, 62, 54, 44, 35, 27, 16, 7, 72, 89, + 67, 39, 28, 23, 13, 13, 5, 1, 64, 77, 78, 73, + 67, 73, 0, 5, 67, 71, 1, 5, 6, 5, 10, 9, 4, + 62, 54, 44, 35, 27, 16, 7, 72, 89 }, + + { + + 62, + 8, 76, 62, 8, 76, 106, 86, 15, 10, 7, 22, 38, + 51, 56, 19, 96, 66, 66, 69, 8, 7, 67, 2, 10, + 27, 33, 19, 66, 88, 93, 89, 73, 66, 69, 8, 73, + 80, 11, 14, 67, 78, 88, 3, 76, 84, 97, 7, 71, + 80, 4, 78, 80, 91, 65, 76, 70, 84, 68, 2, 22, + 0, 0, 0, 76, 89, 97, 66, 13, 64, 39, 7, 81, + 97, 77, 83, 2, 71, 84, 79, 98, 81, 94, 88, + 100, 80, 85, 88, 89, 24, 65, 72, 100, 71, 95, + 84, 103, 6, 2, 3, 67, 13, 68, 68, 81, 93, 81, + 82, 81, 70, 78, 92, 87, 70, 97, 86, 68, 74, + 89, 92, 0, 74, 68, 66, 3, 9, 64, 70, 83, 93, + 72, 19, 19, 94, 9, 6, 5, 4, 5, 2, 65, 0, 2, 8, + 5, 2, 1, 2, 68, 68, 5, 66, 16, 25, 25, 17, 10, + 19, 12, 16, 11, 0, 14, 12, 23, 89, 15, 28, 25, + 50, 62, 35, 42, 59, 60, 38, 52, 51, 62, 62, + 36, 43, 32, 41, 54, 43, 58, 62, 62, 35, 32, + 51, 38, 62, 7, 62, 62, 62, 62, 62, 56, 53, 52, + 47, 42, 37, 31, 24, 19, 1, 75, 73, 83, 18, 19, + 16, 18, 6, 6, 5, 17, 66, 71, 68, 81, 74, 108, + 94, 4, 8, 10, 1, 68, 73, 77, 79, 101, 85, 27, + 14, 8, 66, 65, 78, 88, 92, 101, 67, 27, 19, + 14, 12, 3, 0, 66, 70, 79, 67, 36, 31, 24, 8, + 10, 65, 73, 83, 9, 54, 48, 43, 32, 24, 3, 69, + 69, 75, 62, 110, 106, 95, 106, 105, 92, 100, + 96, 89, 97, 95, 97, 87, 89, 89, 98, 93, 84, + 78, 79, 78, 75, 71, 67, 67, 1, 69, 69, 74, 0, + 1, 65, 1, 1, 1, 2, 5, 1, 0, 1, 6, 65, 69, 73, + 69, 65, 70, 14, 16, 15, 18, 13, 15, 14, 13, + 13, 4, 11, 9, 68, 0, 55, 56, 53, 56, 56, 62, + 61, 62, 62, 62, 62, 62, 61, 50, 30, 62, 62, + 62, 62, 62, 59, 43, 36, 34, 27, 16, 9, 64, 73, + 62, 62, 62, 62, 62, 62, 57, 56, 47, 43, 38, + 34, 20, 12, 1, 6, 6, 64, 25, 24, 22, 19, 16, + 17, 9, 8, 6, 0, 79, 69, 81, 91, 13, 64, 69, + 81, 76, 69, 75, 67, 4, 71, 75, 64, 3, 3, 3, 8, + 4, 3, 61, 52, 41, 32, 24, 12, 2, 76, 92, 67, + 39, 28, 23, 13, 13, 5, 1, 64, 76, 78, 73, 66, + 73, 0, 6, 67, 71, 1, 5, 6, 5, 10, 9, 4, 61, + 52, 41, 32, 24, 12, 2, 76, 92 }, + + { + + 62, + 8, 76, 62, 8, 76, 104, 85, 15, 10, 7, 21, 36, + 50, 56, 19, 94, 66, 66, 68, 7, 6, 67, 1, 8, + 26, 31, 15, 69, 90, 90, 88, 72, 66, 68, 7, 73, + 79, 11, 14, 67, 77, 87, 3, 76, 84, 97, 7, 71, + 80, 4, 78, 80, 90, 65, 76, 70, 84, 67, 2, 22, + 0, 0, 0, 75, 89, 97, 66, 12, 64, 38, 7, 81, + 95, 76, 82, 4, 69, 82, 78, 97, 79, 92, 87, 97, + 80, 84, 88, 88, 24, 65, 71, 98, 71, 94, 83, + 101, 6, 2, 3, 67, 13, 68, 68, 80, 92, 80, 81, + 80, 69, 77, 91, 85, 69, 95, 85, 0, 70, 86, 89, + 1, 73, 67, 65, 3, 9, 64, 70, 82, 91, 72, 17, + 17, 91, 9, 7, 5, 4, 5, 3, 0, 1, 3, 7, 5, 2, 2, + 2, 69, 67, 5, 66, 15, 24, 24, 16, 9, 19, 12, + 16, 10, 1, 13, 12, 22, 89, 15, 27, 24, 48, 62, + 34, 41, 57, 58, 36, 50, 49, 62, 62, 33, 41, + 30, 38, 51, 41, 55, 62, 62, 33, 30, 48, 36, + 62, 6, 62, 62, 62, 61, 60, 54, 51, 50, 45, 39, + 35, 29, 23, 17, 64, 75, 73, 83, 17, 18, 15, + 16, 5, 5, 4, 16, 67, 71, 69, 81, 75, 107, 93, + 5, 8, 10, 2, 68, 72, 76, 78, 99, 83, 27, 15, + 8, 66, 64, 77, 86, 90, 99, 67, 28, 20, 15, 13, + 4, 0, 65, 69, 78, 67, 37, 31, 24, 7, 10, 65, + 73, 82, 9, 54, 47, 42, 31, 24, 3, 68, 69, 74, + 62, 108, 105, 93, 104, 103, 91, 99, 95, 88, + 95, 94, 95, 86, 88, 88, 97, 91, 84, 78, 78, + 77, 74, 71, 68, 67, 0, 69, 69, 74, 0, 1, 64, + 1, 1, 1, 2, 5, 1, 0, 1, 5, 65, 69, 73, 69, 64, + 71, 13, 16, 14, 18, 13, 14, 14, 13, 12, 4, 11, + 8, 68, 64, 54, 55, 52, 54, 54, 62, 59, 61, 62, + 59, 62, 62, 58, 47, 28, 62, 62, 62, 62, 59, + 56, 40, 34, 32, 25, 15, 8, 64, 73, 62, 62, 62, + 62, 59, 59, 55, 53, 45, 41, 36, 31, 18, 10, + 64, 6, 6, 66, 24, 23, 21, 17, 14, 15, 8, 7, 4, + 64, 79, 71, 82, 91, 11, 66, 71, 80, 76, 68, + 75, 66, 5, 70, 74, 0, 4, 3, 4, 9, 4, 3, 60, + 50, 38, 29, 20, 8, 65, 80, 95, 66, 39, 28, 23, + 13, 14, 5, 2, 0, 76, 78, 73, 66, 73, 1, 7, 67, + 71, 2, 6, 6, 6, 11, 9, 4, 60, 50, 38, 29, 20, + 8, 65, 80, 95 }, + + { + + 61, + 8, 76, 61, 8, 76, 102, 83, 16, 10, 6, 19, 34, + 49, 56, 19, 92, 66, 65, 67, 7, 6, 67, 1, 7, + 25, 28, 12, 71, 91, 87, 87, 72, 65, 67, 7, 74, + 78, 11, 13, 67, 77, 87, 3, 76, 84, 97, 7, 71, + 79, 4, 78, 79, 90, 65, 76, 70, 84, 67, 2, 22, + 0, 0, 0, 75, 89, 97, 65, 11, 64, 37, 6, 81, + 92, 75, 81, 5, 67, 81, 76, 95, 78, 91, 86, 95, + 80, 84, 87, 88, 24, 65, 70, 96, 71, 93, 83, + 99, 6, 1, 2, 68, 13, 68, 67, 80, 91, 79, 81, + 79, 69, 76, 89, 84, 69, 94, 84, 5, 65, 83, 86, + 1, 73, 67, 65, 3, 9, 0, 69, 82, 89, 72, 15, + 15, 88, 9, 7, 5, 4, 5, 3, 1, 2, 3, 7, 5, 2, 2, + 3, 70, 67, 5, 67, 15, 23, 24, 16, 9, 19, 12, + 16, 9, 1, 12, 11, 22, 89, 15, 27, 24, 46, 61, + 33, 39, 55, 55, 34, 47, 47, 62, 62, 29, 39, + 28, 36, 48, 38, 52, 61, 62, 30, 28, 44, 34, + 62, 4, 60, 62, 60, 58, 57, 52, 49, 48, 43, 37, + 33, 27, 21, 16, 66, 75, 73, 84, 16, 17, 14, + 15, 4, 4, 3, 14, 68, 72, 70, 82, 75, 107, 91, + 6, 9, 10, 2, 67, 72, 76, 78, 97, 82, 27, 15, + 9, 65, 0, 76, 85, 89, 97, 66, 28, 20, 15, 13, + 5, 1, 65, 69, 78, 67, 37, 31, 23, 7, 10, 65, + 73, 82, 9, 53, 47, 41, 30, 24, 3, 68, 69, 74, + 62, 107, 103, 92, 103, 102, 90, 97, 93, 87, + 94, 92, 93, 86, 87, 88, 96, 89, 83, 78, 78, + 77, 74, 71, 68, 67, 0, 69, 69, 75, 1, 2, 0, 1, + 1, 2, 2, 5, 1, 0, 1, 5, 65, 69, 73, 69, 64, + 72, 12, 16, 13, 17, 12, 14, 14, 12, 11, 4, 11, + 7, 68, 65, 53, 54, 51, 53, 52, 60, 57, 59, 59, + 57, 62, 60, 55, 45, 26, 62, 62, 62, 62, 55, + 53, 38, 32, 29, 23, 13, 6, 65, 74, 62, 62, 62, + 60, 56, 57, 52, 50, 42, 38, 33, 28, 16, 8, 65, + 5, 5, 67, 22, 21, 19, 15, 13, 13, 6, 6, 3, 65, + 80, 72, 83, 92, 10, 67, 72, 80, 75, 68, 74, + 65, 6, 70, 74, 0, 4, 3, 4, 10, 4, 3, 59, 48, + 36, 26, 17, 4, 69, 84, 98, 66, 39, 28, 23, 13, + 14, 6, 2, 0, 75, 78, 73, 65, 72, 2, 8, 67, 70, + 2, 6, 6, 6, 11, 9, 4, 59, 48, 36, 26, 17, 4, + 69, 84, 98 }, + + { + + 60, + 8, 76, 60, 8, 76, 100, 82, 16, 10, 6, 18, 32, + 48, 56, 19, 90, 66, 65, 66, 7, 5, 67, 0, 5, + 24, 26, 8, 74, 93, 84, 86, 72, 65, 66, 7, 74, + 77, 11, 13, 67, 77, 86, 3, 76, 84, 97, 7, 71, + 79, 4, 78, 79, 90, 65, 76, 70, 84, 67, 2, 22, + 0, 0, 0, 74, 89, 97, 65, 10, 64, 36, 6, 81, + 90, 74, 80, 7, 65, 79, 75, 93, 76, 90, 85, 93, + 80, 84, 87, 88, 24, 65, 69, 94, 71, 92, 82, + 97, 6, 1, 2, 68, 13, 68, 67, 79, 90, 78, 80, + 78, 68, 75, 88, 82, 68, 93, 83, 10, 2, 80, 83, + 1, 73, 67, 64, 3, 9, 0, 69, 81, 87, 72, 13, + 13, 85, 9, 7, 5, 4, 5, 4, 3, 3, 3, 7, 5, 2, 3, + 4, 71, 67, 5, 67, 14, 22, 23, 16, 8, 19, 12, + 16, 8, 1, 11, 10, 21, 89, 15, 26, 23, 44, 58, + 32, 37, 53, 53, 32, 45, 45, 62, 62, 26, 37, + 26, 34, 45, 36, 49, 57, 62, 27, 26, 41, 32, + 62, 2, 58, 62, 58, 56, 55, 50, 47, 46, 41, 35, + 31, 25, 19, 14, 68, 75, 73, 84, 15, 16, 13, + 14, 3, 3, 2, 12, 69, 72, 71, 83, 76, 106, 90, + 7, 9, 10, 2, 66, 71, 75, 77, 95, 81, 27, 15, + 9, 64, 1, 75, 83, 87, 95, 65, 29, 21, 16, 14, + 6, 1, 65, 69, 77, 67, 38, 31, 22, 7, 10, 65, + 73, 81, 9, 53, 46, 40, 29, 24, 3, 68, 69, 74, + 62, 106, 102, 91, 101, 100, 89, 95, 91, 86, + 92, 90, 91, 85, 86, 87, 95, 87, 83, 78, 78, + 76, 73, 71, 68, 67, 64, 69, 69, 75, 1, 2, 1, + 1, 1, 2, 2, 5, 1, 0, 1, 4, 65, 69, 73, 69, 0, + 73, 11, 16, 12, 16, 11, 13, 14, 11, 10, 4, 11, + 6, 68, 66, 52, 53, 50, 51, 50, 58, 55, 57, 57, + 54, 61, 57, 52, 42, 24, 62, 62, 62, 62, 52, + 50, 35, 30, 27, 21, 11, 5, 66, 75, 62, 62, 62, + 58, 53, 54, 50, 47, 40, 36, 31, 25, 14, 6, 67, + 4, 4, 69, 21, 20, 17, 13, 11, 11, 4, 5, 1, 66, + 81, 73, 84, 93, 8, 68, 73, 80, 75, 67, 74, 64, + 7, 70, 73, 1, 5, 3, 5, 11, 4, 3, 58, 46, 33, + 23, 14, 0, 73, 88, 101, 66, 39, 28, 23, 13, + 15, 6, 2, 0, 75, 78, 73, 65, 72, 3, 9, 67, 70, + 2, 6, 6, 6, 12, 9, 4, 58, 46, 33, 23, 14, 0, + 73, 88, 101 }, + + { + + 58, + 7, 77, 58, 7, 77, 99, 81, 16, 10, 5, 16, 29, + 47, 56, 18, 89, 67, 65, 66, 6, 4, 67, 64, 3, + 22, 23, 4, 77, 95, 82, 86, 72, 65, 66, 6, 75, + 77, 11, 12, 67, 77, 86, 2, 77, 84, 97, 6, 71, + 79, 4, 78, 79, 90, 65, 76, 71, 84, 67, 2, 22, + 0, 0, 0, 74, 90, 97, 65, 9, 65, 35, 5, 82, 88, + 73, 79, 8, 0, 78, 74, 92, 75, 89, 84, 91, 80, + 84, 87, 88, 24, 65, 69, 92, 71, 92, 82, 96, 5, + 0, 1, 69, 13, 68, 67, 79, 90, 78, 80, 78, 68, + 75, 87, 81, 68, 92, 82, 14, 6, 77, 81, 1, 73, + 67, 64, 2, 9, 0, 69, 81, 85, 72, 11, 11, 83, + 9, 7, 5, 4, 5, 4, 4, 3, 3, 6, 4, 2, 3, 4, 73, + 67, 5, 68, 13, 20, 22, 15, 7, 19, 12, 15, 6, + 1, 10, 9, 20, 89, 14, 25, 22, 41, 54, 30, 35, + 50, 50, 29, 42, 43, 55, 62, 22, 34, 24, 31, + 41, 33, 45, 52, 59, 24, 24, 37, 30, 62, 0, 55, + 59, 55, 53, 52, 47, 44, 43, 39, 32, 28, 23, + 17, 12, 70, 75, 74, 85, 14, 14, 11, 12, 1, 1, + 0, 10, 70, 73, 72, 84, 77, 106, 89, 7, 9, 10, + 2, 66, 71, 75, 77, 93, 80, 27, 15, 9, 64, 1, + 74, 82, 86, 94, 65, 29, 21, 16, 14, 7, 1, 65, + 69, 77, 68, 38, 30, 21, 6, 10, 65, 73, 81, 8, + 52, 45, 38, 28, 23, 3, 68, 69, 74, 62, 105, + 101, 90, 100, 99, 88, 94, 90, 85, 91, 89, 89, + 85, 86, 87, 94, 86, 83, 78, 78, 76, 73, 71, + 69, 68, 65, 69, 70, 76, 1, 2, 2, 1, 0, 2, 2, + 4, 1, 0, 1, 3, 65, 69, 73, 69, 0, 74, 10, 16, + 10, 15, 10, 12, 13, 10, 9, 4, 10, 4, 69, 68, + 50, 51, 49, 49, 48, 55, 52, 54, 54, 51, 58, + 54, 48, 39, 22, 62, 62, 61, 60, 48, 46, 32, + 27, 24, 19, 9, 3, 67, 76, 59, 60, 60, 55, 50, + 51, 47, 43, 37, 33, 28, 22, 12, 4, 69, 3, 3, + 71, 19, 18, 15, 10, 9, 9, 2, 3, 64, 68, 82, + 75, 85, 94, 6, 70, 75, 80, 75, 67, 74, 0, 8, + 70, 73, 1, 5, 3, 5, 11, 4, 2, 56, 44, 30, 19, + 10, 67, 78, 93, 104, 66, 39, 28, 23, 13, 15, + 6, 2, 0, 75, 78, 73, 65, 72, 3, 9, 67, 70, 2, + 6, 6, 6, 12, 8, 3, 56, 44, 30, 19, 10, 67, 78, + 93, 104 }, + + { + + 57, + 7, 77, 57, 7, 77, 97, 79, 17, 11, 5, 15, 27, + 46, 57, 18, 87, 67, 64, 65, 6, 4, 66, 64, 2, + 21, 21, 1, 79, 96, 79, 85, 71, 64, 65, 6, 75, + 76, 11, 12, 66, 76, 85, 2, 77, 83, 96, 6, 70, + 78, 4, 77, 78, 89, 64, 75, 71, 83, 66, 2, 22, + 0, 0, 0, 73, 90, 97, 64, 9, 65, 35, 5, 82, 85, + 71, 77, 10, 3, 76, 72, 90, 73, 87, 82, 88, 80, + 83, 86, 87, 24, 65, 68, 89, 70, 91, 81, 94, 5, + 0, 1, 69, 14, 68, 66, 78, 89, 77, 79, 77, 67, + 74, 85, 79, 67, 90, 80, 19, 11, 73, 78, 2, 72, + 66, 0, 2, 10, 1, 68, 80, 82, 71, 9, 10, 80, 9, + 8, 5, 5, 6, 5, 6, 4, 4, 6, 4, 2, 4, 5, 74, 66, + 5, 68, 13, 19, 22, 15, 7, 19, 12, 15, 5, 2, + 10, 9, 20, 89, 14, 25, 22, 39, 51, 29, 34, 48, + 48, 27, 40, 41, 49, 62, 19, 32, 23, 29, 38, + 31, 42, 48, 55, 22, 22, 34, 28, 62, 64, 53, + 57, 53, 51, 50, 45, 42, 41, 37, 30, 26, 22, + 16, 11, 71, 75, 74, 85, 14, 13, 10, 11, 0, 0, + 64, 9, 71, 73, 73, 84, 77, 105, 87, 8, 10, 10, + 3, 65, 70, 74, 76, 90, 78, 28, 16, 10, 0, 2, + 72, 80, 84, 92, 64, 30, 22, 17, 15, 8, 2, 64, + 68, 76, 68, 39, 30, 21, 6, 11, 64, 73, 80, 8, + 52, 45, 37, 27, 23, 4, 67, 68, 73, 62, 103, + 99, 88, 98, 97, 86, 92, 88, 83, 89, 87, 86, + 84, 85, 86, 92, 84, 82, 77, 77, 75, 72, 70, + 69, 68, 65, 69, 70, 76, 2, 3, 3, 2, 0, 3, 3, + 4, 2, 1, 1, 3, 64, 68, 72, 68, 1, 74, 9, 16, + 9, 15, 10, 12, 13, 10, 9, 4, 10, 3, 69, 69, + 49, 50, 49, 48, 47, 53, 50, 52, 52, 49, 56, + 52, 45, 37, 20, 61, 60, 57, 56, 45, 43, 30, + 25, 22, 18, 8, 2, 67, 76, 57, 58, 58, 53, 48, + 49, 45, 40, 35, 31, 26, 20, 11, 3, 70, 3, 3, + 72, 18, 17, 14, 8, 8, 8, 1, 2, 65, 69, 82, 76, + 85, 94, 5, 71, 76, 79, 74, 66, 73, 2, 10, 69, + 72, 2, 6, 4, 6, 12, 4, 2, 55, 42, 28, 16, 7, + 71, 82, 97, 106, 65, 39, 29, 24, 14, 16, 7, 3, + 1, 74, 77, 72, 64, 71, 4, 10, 66, 69, 3, 7, 6, + 7, 13, 8, 3, 55, 42, 28, 16, 7, 71, 82, 97, + 106 }, + + { + + 56, + 7, 77, 56, 7, 77, 95, 78, 17, 11, 5, 14, 25, + 45, 57, 18, 85, 67, 64, 64, 6, 3, 66, 65, 0, + 20, 18, 66, 82, 98, 76, 84, 71, 64, 64, 6, 75, + 75, 11, 11, 66, 76, 84, 2, 77, 83, 96, 6, 70, + 78, 4, 77, 78, 89, 64, 75, 71, 83, 66, 2, 22, + 0, 0, 0, 72, 90, 97, 64, 8, 65, 34, 5, 82, 83, + 70, 76, 12, 5, 75, 71, 88, 72, 86, 81, 86, 80, + 83, 86, 87, 24, 65, 67, 87, 70, 90, 80, 92, 5, + 0, 1, 70, 14, 68, 66, 77, 88, 76, 78, 76, 67, + 73, 84, 78, 66, 89, 79, 24, 15, 70, 75, 2, 72, + 66, 0, 2, 10, 1, 68, 80, 80, 71, 7, 8, 77, 9, + 8, 5, 5, 6, 5, 8, 5, 4, 6, 4, 2, 5, 6, 75, 66, + 5, 68, 12, 18, 21, 15, 6, 19, 12, 15, 4, 2, 9, + 8, 19, 89, 14, 24, 21, 37, 48, 28, 32, 46, 46, + 25, 38, 39, 43, 62, 15, 30, 21, 27, 35, 29, + 39, 44, 51, 19, 20, 31, 26, 62, 66, 51, 55, + 51, 49, 48, 43, 40, 39, 35, 28, 24, 20, 14, 9, + 73, 75, 74, 86, 13, 12, 9, 10, 64, 64, 65, 7, + 72, 73, 74, 85, 78, 104, 86, 9, 10, 10, 3, 64, + 69, 73, 75, 88, 77, 28, 16, 10, 1, 3, 71, 78, + 82, 90, 0, 31, 23, 18, 16, 9, 2, 64, 68, 75, + 68, 40, 30, 20, 6, 11, 64, 73, 80, 8, 52, 44, + 36, 26, 23, 4, 67, 68, 73, 62, 102, 98, 87, + 96, 95, 85, 90, 86, 82, 87, 85, 84, 83, 84, + 86, 91, 82, 82, 77, 77, 74, 72, 70, 69, 68, + 66, 69, 70, 76, 2, 3, 4, 2, 0, 3, 3, 4, 2, 1, + 1, 2, 64, 68, 72, 68, 2, 75, 8, 16, 8, 14, 9, + 11, 13, 9, 8, 4, 10, 2, 69, 70, 48, 49, 48, + 46, 45, 51, 48, 50, 50, 46, 53, 49, 42, 34, + 18, 57, 56, 53, 51, 42, 40, 27, 23, 19, 16, 6, + 1, 68, 77, 55, 56, 55, 51, 45, 46, 42, 37, 33, + 28, 24, 17, 9, 1, 72, 2, 2, 74, 17, 16, 12, 6, + 6, 6, 64, 1, 67, 70, 83, 77, 86, 95, 3, 72, + 77, 79, 74, 65, 73, 3, 11, 69, 71, 3, 7, 4, 6, + 13, 4, 2, 54, 40, 25, 13, 4, 75, 86, 101, 109, + 65, 39, 29, 24, 14, 17, 7, 3, 1, 74, 77, 72, + 64, 71, 5, 11, 66, 69, 3, 7, 6, 7, 14, 8, 3, + 54, 40, 25, 13, 4, 75, 86, 101, 109 }, + + { + + 55, + 7, 77, 55, 7, 77, 93, 76, 18, 11, 4, 12, 23, + 44, 57, 18, 83, 67, 0, 0, 6, 3, 66, 65, 64, + 19, 16, 69, 84, 99, 73, 83, 71, 0, 0, 6, 76, + 74, 11, 11, 66, 76, 84, 2, 77, 83, 96, 6, 70, + 77, 4, 77, 77, 89, 64, 75, 71, 83, 66, 2, 22, + 0, 0, 0, 72, 90, 97, 0, 7, 65, 33, 4, 82, 80, + 69, 75, 13, 7, 73, 69, 86, 70, 85, 80, 84, 80, + 83, 85, 87, 24, 65, 66, 85, 70, 89, 80, 90, 5, + 64, 0, 70, 14, 68, 65, 77, 87, 75, 78, 75, 66, + 72, 82, 76, 66, 88, 78, 29, 20, 67, 72, 2, 72, + 66, 1, 2, 10, 2, 67, 79, 78, 71, 5, 6, 74, 9, + 8, 5, 5, 6, 6, 9, 6, 4, 6, 4, 2, 5, 7, 76, 66, + 5, 69, 12, 17, 21, 15, 6, 19, 12, 15, 3, 2, 8, + 7, 19, 89, 14, 24, 21, 35, 45, 27, 30, 44, 43, + 23, 35, 37, 36, 62, 12, 28, 19, 25, 32, 26, + 36, 40, 47, 16, 18, 27, 24, 62, 68, 49, 53, + 49, 46, 45, 41, 38, 37, 33, 26, 22, 18, 12, 8, + 75, 75, 74, 86, 12, 11, 8, 9, 65, 65, 66, 5, + 73, 74, 75, 86, 78, 104, 84, 10, 11, 10, 3, 0, + 69, 73, 75, 86, 76, 28, 16, 11, 2, 4, 70, 77, + 81, 88, 1, 31, 23, 18, 16, 10, 3, 64, 68, 75, + 68, 40, 30, 19, 6, 11, 64, 73, 79, 8, 51, 44, + 35, 25, 23, 4, 67, 68, 73, 62, 101, 96, 86, + 95, 94, 84, 88, 84, 81, 86, 83, 82, 83, 83, + 85, 90, 80, 81, 77, 77, 74, 71, 70, 69, 68, + 66, 69, 70, 77, 3, 4, 5, 2, 0, 4, 3, 4, 2, 1, + 1, 2, 64, 68, 72, 68, 2, 76, 7, 16, 7, 13, 8, + 11, 13, 8, 7, 4, 10, 1, 69, 71, 47, 48, 47, + 45, 43, 49, 46, 48, 47, 44, 50, 46, 39, 32, + 16, 53, 52, 49, 46, 38, 37, 25, 21, 17, 14, 4, + 64, 69, 78, 53, 53, 53, 48, 42, 44, 40, 34, + 30, 26, 21, 14, 7, 64, 73, 1, 1, 75, 15, 14, + 10, 4, 5, 4, 66, 0, 68, 71, 84, 78, 87, 96, 2, + 73, 78, 79, 73, 65, 72, 4, 12, 69, 71, 3, 7, + 4, 7, 14, 4, 2, 53, 38, 23, 10, 1, 79, 90, + 105, 112, 65, 39, 29, 24, 14, 17, 8, 3, 1, 73, + 77, 72, 0, 70, 6, 12, 66, 68, 3, 7, 6, 7, 14, + 8, 3, 53, 38, 23, 10, 1, 79, 90, 105, 112 }, + + { + + 53, + 7, 77, 53, 7, 77, 92, 75, 18, 11, 4, 11, 21, + 43, 57, 17, 81, 67, 0, 1, 5, 2, 66, 66, 66, + 18, 13, 73, 87, 101, 70, 82, 71, 0, 1, 5, 76, + 73, 11, 10, 66, 75, 83, 2, 77, 83, 96, 6, 70, + 77, 4, 77, 77, 88, 64, 75, 71, 83, 65, 2, 22, + 0, 0, 0, 71, 91, 97, 0, 6, 65, 32, 4, 82, 78, + 68, 74, 15, 9, 72, 68, 85, 69, 83, 79, 82, 80, + 82, 85, 87, 24, 65, 65, 83, 70, 89, 79, 88, 5, + 64, 0, 71, 14, 68, 65, 76, 86, 74, 77, 74, 66, + 72, 81, 75, 65, 87, 77, 34, 24, 64, 69, 2, 71, + 66, 1, 2, 10, 2, 67, 79, 76, 71, 3, 4, 72, 9, + 9, 5, 5, 6, 6, 11, 7, 5, 5, 4, 2, 6, 7, 77, + 66, 5, 69, 11, 16, 20, 14, 5, 19, 12, 15, 2, + 2, 7, 6, 18, 89, 13, 23, 20, 33, 41, 26, 28, + 42, 41, 21, 33, 35, 30, 62, 8, 26, 17, 22, 29, + 24, 32, 35, 43, 13, 16, 24, 22, 62, 69, 47, + 51, 46, 44, 43, 39, 36, 35, 31, 23, 20, 16, + 10, 6, 77, 75, 74, 87, 11, 10, 7, 7, 66, 67, + 67, 4, 74, 74, 76, 86, 79, 103, 83, 11, 11, + 10, 4, 0, 68, 72, 74, 84, 74, 28, 17, 11, 2, + 5, 69, 75, 79, 87, 1, 32, 24, 19, 17, 11, 3, + 64, 67, 74, 68, 41, 30, 19, 5, 11, 64, 73, 79, + 8, 51, 43, 34, 24, 23, 4, 66, 68, 73, 62, 99, + 95, 85, 93, 92, 83, 87, 83, 80, 84, 82, 80, + 82, 82, 85, 89, 79, 81, 77, 77, 73, 71, 70, + 70, 68, 67, 69, 70, 77, 3, 4, 6, 2, 0, 4, 3, + 4, 2, 1, 1, 1, 64, 68, 72, 68, 3, 77, 6, 16, + 6, 13, 7, 10, 13, 7, 6, 4, 10, 0, 69, 72, 46, + 47, 46, 43, 41, 47, 44, 45, 45, 41, 47, 43, + 36, 29, 14, 48, 48, 45, 41, 35, 33, 22, 18, + 14, 12, 2, 65, 70, 78, 50, 51, 50, 46, 39, 41, + 37, 31, 28, 23, 19, 11, 5, 66, 75, 0, 1, 77, + 14, 13, 9, 2, 3, 2, 67, 64, 70, 72, 85, 80, + 88, 96, 0, 75, 80, 78, 73, 64, 72, 5, 13, 69, + 70, 4, 8, 4, 7, 15, 4, 2, 52, 36, 20, 7, 66, + 83, 95, 109, 115, 64, 39, 29, 24, 14, 18, 8, + 3, 1, 73, 77, 72, 0, 70, 6, 13, 66, 68, 4, 8, + 6, 7, 15, 8, 3, 52, 36, 20, 7, 66, 83, 95, + 109, 115 }, + + { + + 52, + 7, 77, 52, 7, 77, 90, 73, 18, 11, 3, 10, 19, + 42, 57, 17, 79, 67, 0, 2, 5, 2, 66, 67, 68, + 17, 11, 76, 89, 103, 67, 81, 70, 0, 2, 5, 76, + 72, 11, 10, 66, 75, 82, 2, 77, 83, 96, 6, 70, + 77, 4, 77, 76, 88, 64, 75, 71, 83, 65, 2, 22, + 0, 0, 0, 71, 91, 97, 1, 5, 65, 31, 3, 82, 75, + 67, 73, 17, 11, 70, 66, 83, 67, 82, 78, 79, + 80, 82, 84, 86, 24, 65, 64, 81, 70, 88, 79, + 86, 5, 65, 64, 71, 14, 68, 64, 76, 85, 73, 77, + 73, 65, 71, 79, 73, 64, 85, 76, 39, 28, 2, 66, + 3, 71, 65, 2, 2, 10, 3, 67, 78, 74, 71, 1, 2, + 69, 9, 9, 5, 5, 6, 7, 12, 8, 5, 5, 4, 2, 6, 8, + 78, 65, 5, 70, 11, 15, 20, 14, 5, 19, 12, 15, + 1, 3, 6, 6, 18, 89, 13, 22, 19, 31, 38, 25, + 27, 40, 39, 19, 30, 33, 24, 62, 5, 24, 15, 20, + 26, 21, 29, 31, 39, 11, 14, 20, 20, 62, 71, + 45, 49, 44, 42, 41, 37, 34, 33, 29, 21, 18, + 14, 9, 4, 79, 75, 74, 87, 10, 9, 6, 6, 67, 68, + 68, 2, 75, 75, 77, 87, 80, 102, 81, 12, 12, + 10, 4, 1, 68, 72, 73, 82, 73, 28, 17, 12, 3, + 6, 68, 74, 78, 85, 2, 33, 25, 20, 17, 12, 4, + 0, 67, 74, 68, 41, 30, 18, 5, 11, 64, 73, 78, + 8, 50, 42, 33, 23, 23, 4, 66, 68, 72, 62, 98, + 93, 83, 91, 91, 82, 85, 81, 79, 82, 80, 78, + 81, 81, 84, 88, 77, 81, 77, 76, 73, 70, 70, + 70, 68, 67, 69, 70, 78, 4, 4, 7, 2, 0, 4, 3, + 4, 2, 1, 1, 1, 64, 68, 72, 68, 3, 78, 5, 16, + 5, 12, 7, 9, 13, 7, 5, 4, 10, 64, 69, 73, 45, + 46, 45, 41, 39, 45, 42, 43, 42, 38, 44, 40, + 33, 27, 12, 44, 44, 41, 36, 32, 30, 20, 16, + 12, 10, 1, 67, 70, 79, 48, 48, 48, 44, 36, 38, + 35, 28, 26, 21, 17, 8, 3, 68, 76, 0, 0, 79, + 13, 11, 7, 0, 1, 0, 69, 65, 71, 73, 85, 81, + 89, 97, 64, 76, 81, 78, 73, 0, 71, 6, 14, 68, + 69, 4, 8, 4, 8, 16, 4, 2, 51, 34, 17, 4, 69, + 87, 99, 113, 118, 64, 39, 29, 24, 14, 18, 8, + 4, 2, 72, 77, 72, 1, 70, 7, 14, 66, 68, 4, 8, + 6, 8, 15, 8, 3, 51, 34, 17, 4, 69, 87, 99, + 113, 118 }, + + { + + 51, + 7, 78, 51, 7, 78, 88, 72, 19, 11, 3, 8, 17, + 41, 57, 17, 78, 67, 1, 2, 5, 1, 65, 67, 69, + 15, 8, 80, 92, 104, 65, 80, 70, 1, 2, 5, 77, + 72, 11, 9, 66, 75, 82, 2, 77, 82, 95, 6, 69, + 76, 4, 76, 76, 88, 64, 75, 71, 83, 65, 2, 22, + 0, 0, 0, 70, 91, 97, 1, 5, 66, 31, 3, 82, 73, + 66, 72, 18, 14, 69, 65, 81, 66, 81, 77, 77, + 80, 82, 84, 86, 24, 65, 0, 78, 70, 87, 78, 84, + 4, 65, 64, 72, 15, 68, 64, 75, 85, 73, 76, 72, + 65, 70, 78, 72, 64, 84, 75, 44, 33, 5, 0, 3, + 71, 65, 2, 2, 10, 3, 66, 78, 72, 70, 64, 0, + 66, 9, 9, 5, 5, 7, 7, 14, 9, 5, 5, 4, 2, 7, 9, + 79, 65, 5, 70, 10, 14, 19, 14, 4, 19, 12, 15, + 64, 3, 5, 5, 17, 89, 13, 22, 19, 29, 35, 24, + 25, 37, 36, 17, 28, 31, 17, 62, 1, 22, 14, 18, + 22, 19, 26, 27, 34, 8, 12, 17, 18, 62, 73, 43, + 47, 42, 39, 38, 35, 31, 31, 27, 19, 15, 12, 7, + 3, 80, 75, 74, 88, 9, 8, 5, 5, 68, 69, 69, 0, + 76, 75, 78, 88, 80, 102, 80, 13, 12, 10, 4, 2, + 67, 71, 73, 80, 72, 29, 17, 12, 4, 7, 67, 72, + 76, 83, 3, 33, 25, 20, 18, 13, 4, 0, 67, 73, + 68, 42, 30, 17, 5, 11, 64, 73, 78, 7, 50, 42, + 32, 22, 22, 4, 66, 68, 72, 62, 97, 92, 82, 90, + 89, 81, 83, 79, 78, 81, 78, 76, 81, 80, 84, + 87, 75, 80, 77, 76, 72, 70, 70, 70, 68, 68, + 69, 70, 78, 4, 5, 8, 2, 0, 5, 4, 4, 3, 2, 1, + 0, 64, 68, 72, 68, 4, 79, 4, 16, 4, 11, 6, 9, + 13, 6, 5, 4, 9, 66, 70, 74, 43, 45, 44, 40, + 37, 43, 40, 41, 40, 36, 41, 38, 30, 24, 10, + 40, 40, 37, 32, 28, 27, 17, 14, 9, 8, 64, 68, + 71, 80, 46, 46, 45, 41, 33, 36, 32, 25, 23, + 18, 14, 5, 1, 69, 78, 64, 64, 80, 11, 10, 5, + 65, 0, 65, 71, 66, 73, 74, 86, 82, 90, 98, 66, + 77, 82, 78, 72, 0, 71, 7, 15, 68, 69, 5, 9, 4, + 8, 17, 4, 2, 50, 32, 15, 1, 72, 91, 103, 117, + 121, 64, 39, 29, 24, 14, 19, 9, 4, 2, 72, 76, + 71, 1, 69, 8, 15, 65, 67, 4, 8, 6, 8, 16, 8, + 3, 50, 32, 15, 1, 72, 91, 103, 117, 121 }, + + { + + 50, + 7, 78, 50, 7, 78, 86, 70, 19, 11, 2, 7, 15, + 40, 57, 17, 76, 67, 1, 3, 4, 1, 65, 68, 71, + 14, 6, 83, 94, 106, 1, 79, 70, 1, 3, 4, 77, + 71, 11, 9, 66, 74, 81, 2, 77, 82, 95, 6, 69, + 76, 4, 76, 75, 87, 64, 75, 71, 83, 64, 2, 22, + 0, 0, 0, 70, 91, 97, 2, 4, 66, 30, 2, 82, 70, + 65, 71, 20, 16, 67, 0, 80, 64, 79, 76, 75, 80, + 81, 83, 86, 24, 65, 1, 76, 70, 86, 78, 82, 4, + 66, 65, 72, 15, 68, 0, 75, 84, 72, 76, 71, 64, + 69, 76, 70, 0, 83, 74, 49, 37, 8, 3, 3, 70, + 65, 3, 2, 10, 4, 66, 77, 70, 70, 66, 65, 0, 9, + 10, 5, 5, 7, 8, 15, 10, 6, 4, 4, 2, 7, 9, 80, + 65, 5, 71, 10, 13, 19, 13, 4, 19, 12, 15, 65, + 3, 4, 4, 17, 89, 13, 21, 18, 27, 32, 23, 23, + 35, 34, 15, 25, 29, 11, 62, 65, 20, 12, 15, + 19, 16, 23, 22, 30, 5, 10, 13, 16, 62, 74, 41, + 45, 40, 37, 36, 33, 29, 29, 25, 16, 13, 10, 5, + 1, 82, 75, 74, 88, 8, 7, 4, 3, 69, 70, 70, 64, + 77, 76, 79, 88, 81, 101, 78, 14, 13, 10, 5, 2, + 67, 71, 72, 78, 70, 29, 18, 13, 4, 8, 66, 71, + 75, 81, 3, 34, 26, 21, 18, 14, 5, 0, 66, 73, + 68, 42, 30, 17, 4, 11, 64, 73, 77, 7, 49, 41, + 31, 21, 22, 4, 65, 68, 72, 62, 95, 90, 81, 88, + 88, 80, 82, 78, 77, 79, 77, 74, 80, 79, 83, + 86, 73, 80, 77, 76, 72, 69, 70, 71, 68, 68, + 69, 70, 79, 5, 5, 9, 2, 0, 5, 4, 4, 3, 2, 1, + 0, 64, 68, 72, 68, 4, 80, 3, 16, 3, 11, 5, 8, + 13, 5, 4, 4, 9, 67, 70, 75, 42, 44, 43, 38, + 35, 41, 38, 38, 37, 33, 38, 35, 27, 22, 8, 35, + 36, 33, 27, 25, 24, 15, 12, 7, 6, 66, 70, 72, + 80, 43, 43, 43, 39, 30, 33, 30, 22, 21, 16, + 12, 2, 64, 71, 79, 65, 64, 82, 10, 8, 4, 67, + 65, 67, 72, 67, 74, 75, 87, 84, 91, 98, 67, + 79, 84, 77, 72, 1, 70, 8, 16, 68, 68, 5, 9, 4, + 9, 18, 4, 2, 49, 30, 12, 65, 76, 95, 107, 121, + 124, 0, 39, 29, 24, 14, 19, 9, 4, 2, 71, 76, + 71, 2, 69, 9, 16, 65, 67, 5, 9, 6, 8, 16, 8, + 3, 49, 30, 12, 65, 76, 95, 107, 121, 124 }, + + { + + 48, + 6, 78, 48, 6, 78, 85, 69, 19, 11, 2, 5, 12, + 39, 57, 16, 74, 68, 1, 4, 4, 0, 65, 69, 73, + 13, 3, 87, 97, 108, 4, 78, 70, 1, 4, 4, 78, + 70, 11, 8, 66, 74, 81, 1, 78, 82, 95, 6, 69, + 76, 4, 76, 75, 87, 64, 75, 71, 83, 64, 2, 22, + 0, 0, 0, 69, 92, 97, 2, 3, 66, 29, 2, 83, 68, + 64, 70, 21, 18, 66, 1, 78, 0, 78, 75, 73, 80, + 81, 83, 86, 24, 65, 2, 74, 70, 86, 77, 80, 4, + 66, 65, 73, 15, 68, 0, 74, 83, 71, 75, 71, 64, + 69, 75, 69, 0, 82, 73, 53, 41, 11, 5, 3, 70, + 65, 3, 1, 10, 4, 66, 77, 68, 70, 68, 67, 2, 9, + 10, 5, 5, 7, 8, 17, 10, 6, 4, 3, 2, 8, 10, 82, + 65, 5, 71, 9, 11, 18, 13, 3, 19, 12, 14, 66, + 3, 3, 3, 16, 89, 12, 20, 17, 25, 28, 21, 21, + 33, 31, 12, 23, 27, 4, 62, 69, 18, 10, 13, 16, + 14, 19, 18, 26, 2, 8, 10, 14, 62, 76, 39, 42, + 37, 34, 33, 30, 27, 26, 23, 14, 11, 8, 3, 64, + 84, 75, 75, 89, 7, 5, 3, 2, 70, 72, 72, 66, + 78, 76, 80, 89, 82, 101, 77, 15, 13, 10, 5, 3, + 66, 70, 72, 76, 69, 29, 18, 13, 5, 9, 65, 69, + 73, 80, 4, 34, 26, 21, 19, 15, 5, 0, 66, 72, + 69, 43, 30, 16, 4, 11, 64, 73, 77, 7, 49, 40, + 30, 20, 22, 4, 65, 68, 72, 62, 94, 89, 80, 87, + 86, 79, 80, 76, 76, 78, 75, 72, 80, 79, 83, + 85, 72, 80, 77, 76, 71, 69, 70, 71, 68, 69, + 69, 70, 79, 5, 5, 10, 2, 64, 5, 4, 3, 3, 2, 1, + 64, 64, 68, 72, 68, 5, 81, 2, 16, 1, 10, 4, 7, + 12, 4, 3, 4, 9, 68, 70, 77, 41, 42, 42, 36, + 33, 39, 36, 36, 35, 30, 35, 32, 24, 19, 6, 31, + 32, 28, 22, 21, 20, 12, 9, 4, 4, 68, 71, 73, + 81, 41, 41, 40, 36, 27, 30, 27, 19, 18, 13, 9, + 64, 66, 73, 81, 66, 65, 84, 8, 7, 2, 69, 67, + 69, 74, 69, 76, 77, 88, 85, 92, 99, 69, 80, + 85, 77, 72, 1, 70, 9, 17, 68, 68, 6, 10, 4, 9, + 18, 4, 1, 48, 28, 9, 68, 79, 99, 112, 126, + 126, 0, 39, 29, 24, 14, 20, 9, 4, 2, 71, 76, + 71, 2, 69, 9, 16, 65, 67, 5, 9, 6, 8, 17, 8, + 2, 48, 28, 9, 68, 79, 99, 112, 126, 126 }, + + { + + 47, + 6, 78, 47, 6, 78, 83, 68, 20, 11, 2, 4, 10, + 38, 58, 16, 72, 68, 2, 5, 4, 64, 65, 69, 74, + 12, 1, 91, 100, 109, 7, 77, 69, 2, 5, 4, 78, + 69, 11, 8, 65, 74, 80, 1, 78, 82, 95, 6, 69, + 75, 4, 76, 75, 87, 64, 75, 71, 82, 64, 2, 22, + 0, 0, 0, 68, 92, 97, 2, 2, 66, 28, 2, 83, 66, + 1, 69, 23, 20, 64, 2, 76, 2, 77, 73, 70, 80, + 81, 83, 85, 24, 65, 3, 72, 69, 85, 76, 78, 4, + 66, 65, 73, 15, 68, 0, 73, 82, 70, 74, 70, 0, + 68, 74, 67, 1, 80, 72, 58, 46, 15, 8, 4, 70, + 64, 4, 1, 10, 4, 65, 76, 65, 70, 70, 68, 5, 9, + 10, 5, 5, 7, 9, 19, 11, 6, 4, 3, 2, 9, 11, 83, + 64, 5, 71, 8, 10, 17, 13, 2, 19, 12, 14, 67, + 4, 2, 3, 15, 89, 12, 20, 17, 23, 25, 20, 20, + 31, 29, 10, 21, 25, 65, 62, 72, 16, 8, 11, 13, + 12, 16, 14, 22, 0, 6, 7, 12, 62, 78, 37, 40, + 35, 32, 31, 28, 25, 24, 21, 12, 9, 7, 2, 65, + 86, 75, 75, 89, 7, 4, 2, 1, 71, 73, 73, 68, + 79, 76, 81, 90, 82, 100, 76, 16, 13, 10, 5, 4, + 65, 69, 71, 73, 68, 29, 18, 13, 6, 10, 64, 67, + 71, 78, 5, 35, 27, 22, 20, 16, 5, 1, 66, 71, + 69, 44, 30, 15, 4, 12, 0, 73, 76, 7, 49, 40, + 29, 19, 22, 4, 65, 68, 71, 62, 93, 88, 78, 85, + 84, 78, 78, 74, 75, 76, 73, 70, 79, 78, 82, + 84, 70, 79, 76, 75, 70, 68, 70, 71, 68, 70, + 69, 70, 79, 5, 6, 11, 3, 64, 6, 4, 3, 3, 2, 1, + 65, 64, 67, 71, 68, 6, 81, 1, 16, 0, 9, 4, 7, + 12, 4, 2, 4, 9, 69, 70, 78, 40, 41, 42, 35, + 31, 37, 34, 34, 33, 28, 32, 29, 21, 16, 4, 27, + 28, 24, 17, 18, 17, 9, 7, 2, 3, 69, 72, 73, + 82, 39, 39, 38, 34, 25, 28, 25, 16, 16, 11, 7, + 66, 68, 75, 83, 66, 66, 85, 7, 6, 0, 71, 68, + 70, 76, 70, 78, 78, 88, 86, 92, 100, 71, 81, + 86, 77, 71, 2, 70, 10, 19, 67, 67, 7, 11, 4, + 10, 19, 4, 1, 47, 26, 7, 71, 82, 103, 116, + 126, 126, 0, 39, 29, 25, 15, 21, 10, 5, 3, 71, + 76, 71, 2, 68, 10, 17, 65, 66, 5, 9, 6, 9, 18, + 8, 2, 47, 26, 7, 71, 82, 103, 116, 126, 126 }, + + { + + 46, + 6, 78, 46, 6, 78, 81, 66, 20, 11, 1, 3, 8, 37, + 58, 16, 70, 68, 2, 6, 3, 64, 65, 70, 76, 11, + 65, 94, 102, 111, 10, 76, 69, 2, 6, 3, 78, 68, + 11, 7, 65, 73, 79, 1, 78, 82, 95, 6, 69, 75, + 4, 76, 74, 86, 64, 75, 71, 82, 0, 2, 22, 0, 0, + 0, 68, 92, 97, 3, 1, 66, 27, 1, 83, 0, 2, 68, + 25, 22, 0, 4, 75, 3, 75, 72, 68, 80, 80, 82, + 85, 24, 65, 4, 70, 69, 84, 76, 76, 4, 67, 66, + 74, 15, 68, 1, 73, 81, 69, 74, 69, 0, 67, 72, + 66, 2, 79, 71, 62, 50, 18, 11, 4, 69, 64, 4, + 1, 10, 5, 65, 76, 0, 70, 72, 70, 8, 9, 11, 5, + 5, 7, 9, 20, 12, 7, 3, 3, 2, 9, 11, 84, 64, 5, + 72, 8, 9, 17, 12, 2, 19, 12, 14, 68, 4, 1, 2, + 15, 89, 12, 19, 16, 21, 22, 19, 18, 29, 27, 8, + 18, 23, 71, 62, 76, 14, 6, 8, 10, 9, 13, 9, + 18, 66, 4, 3, 10, 62, 79, 35, 38, 33, 30, 29, + 26, 23, 22, 19, 9, 7, 5, 0, 67, 88, 75, 75, + 90, 6, 3, 1, 64, 72, 74, 74, 69, 80, 77, 82, + 90, 83, 99, 74, 17, 14, 10, 6, 4, 65, 69, 70, + 71, 66, 29, 19, 14, 6, 11, 0, 66, 70, 76, 5, + 36, 28, 23, 20, 17, 6, 1, 65, 71, 69, 44, 30, + 15, 3, 12, 0, 73, 76, 7, 48, 39, 28, 18, 22, + 4, 64, 68, 71, 62, 91, 86, 77, 83, 83, 77, 77, + 73, 74, 74, 72, 68, 78, 77, 82, 83, 68, 79, + 76, 75, 70, 68, 70, 72, 68, 70, 69, 70, 80, 6, + 6, 12, 3, 64, 6, 4, 3, 3, 2, 1, 65, 64, 67, + 71, 68, 6, 82, 0, 16, 64, 9, 3, 6, 12, 3, 1, + 4, 9, 70, 70, 79, 39, 40, 41, 33, 29, 35, 32, + 31, 30, 25, 29, 26, 18, 14, 2, 22, 24, 20, 12, + 15, 14, 7, 5, 64, 1, 71, 74, 74, 82, 36, 36, + 35, 32, 22, 25, 22, 13, 14, 8, 5, 69, 70, 77, + 84, 67, 66, 87, 6, 4, 64, 73, 70, 72, 77, 71, + 79, 79, 89, 88, 93, 100, 72, 83, 88, 76, 71, + 3, 69, 11, 20, 67, 66, 7, 11, 4, 10, 20, 4, 1, + 46, 24, 4, 74, 86, 107, 120, 126, 126, 1, 39, + 29, 25, 15, 21, 10, 5, 3, 70, 76, 71, 3, 68, + 11, 18, 65, 66, 6, 10, 6, 9, 18, 8, 2, 46, 24, + 4, 74, 86, 107, 120, 126, 126 }, + + { + + 45, + 6, 79, 45, 6, 79, 79, 65, 21, 11, 1, 1, 6, 36, + 58, 16, 69, 68, 3, 6, 3, 65, 64, 70, 77, 9, + 67, 98, 105, 112, 12, 75, 69, 3, 6, 3, 79, 68, + 11, 7, 65, 73, 79, 1, 78, 81, 94, 6, 68, 74, + 4, 75, 74, 86, 64, 75, 71, 82, 0, 2, 22, 0, 0, + 0, 67, 92, 97, 3, 1, 67, 27, 1, 83, 2, 3, 67, + 26, 25, 2, 5, 73, 5, 74, 71, 66, 80, 80, 82, + 85, 24, 65, 5, 67, 69, 83, 75, 74, 3, 67, 66, + 74, 16, 68, 1, 72, 81, 69, 73, 68, 1, 66, 71, + 64, 2, 78, 70, 62, 55, 21, 14, 4, 69, 64, 5, + 1, 10, 5, 64, 75, 2, 69, 74, 72, 11, 9, 11, 5, + 5, 8, 10, 22, 13, 7, 3, 3, 2, 10, 12, 85, 64, + 5, 72, 7, 8, 16, 12, 1, 19, 12, 14, 70, 4, 0, + 1, 14, 89, 12, 19, 16, 19, 19, 18, 16, 26, 24, + 6, 16, 21, 78, 62, 79, 12, 5, 6, 6, 7, 10, 5, + 13, 69, 2, 0, 8, 62, 81, 33, 36, 31, 27, 26, + 24, 20, 20, 17, 7, 4, 3, 65, 68, 89, 75, 75, + 90, 5, 2, 0, 65, 73, 75, 75, 71, 81, 77, 83, + 91, 83, 99, 73, 18, 14, 10, 6, 5, 64, 68, 70, + 69, 65, 30, 19, 14, 7, 12, 1, 64, 68, 74, 6, + 36, 28, 23, 21, 18, 6, 1, 65, 70, 69, 45, 30, + 14, 3, 12, 0, 73, 75, 6, 48, 39, 27, 17, 21, + 4, 64, 68, 71, 62, 90, 85, 76, 82, 81, 76, 75, + 71, 73, 73, 70, 66, 78, 76, 81, 82, 66, 78, + 76, 75, 69, 67, 70, 72, 68, 71, 69, 70, 80, 6, + 7, 13, 3, 64, 7, 5, 3, 4, 3, 1, 66, 64, 67, + 71, 68, 7, 83, 64, 16, 65, 8, 2, 6, 12, 2, 1, + 4, 8, 72, 71, 80, 37, 39, 40, 32, 27, 33, 30, + 29, 28, 23, 26, 24, 15, 11, 0, 18, 20, 16, 8, + 11, 11, 4, 3, 66, 64, 73, 75, 75, 83, 34, 34, + 33, 29, 19, 23, 20, 10, 11, 6, 2, 72, 72, 78, + 86, 68, 67, 88, 4, 3, 66, 75, 71, 74, 79, 72, + 81, 80, 90, 89, 94, 101, 74, 84, 89, 76, 70, + 3, 69, 12, 21, 67, 66, 8, 12, 4, 11, 21, 4, 1, + 45, 22, 2, 77, 89, 111, 124, 126, 126, 1, 39, + 29, 25, 15, 22, 11, 5, 3, 70, 75, 70, 3, 67, + 12, 19, 64, 65, 6, 10, 6, 9, 19, 8, 2, 45, 22, + 2, 77, 89, 111, 124, 126, 126 }, + + { + + 43, + 6, 79, 43, 6, 79, 78, 0, 21, 11, 0, 0, 4, 35, + 58, 15, 67, 68, 3, 7, 3, 65, 64, 71, 79, 8, + 70, 101, 107, 114, 15, 74, 69, 3, 7, 3, 79, + 67, 11, 6, 65, 73, 78, 1, 78, 81, 94, 6, 68, + 74, 4, 75, 73, 86, 64, 75, 71, 82, 0, 2, 22, + 0, 0, 0, 67, 93, 97, 4, 0, 67, 26, 0, 83, 5, + 4, 66, 28, 27, 3, 7, 71, 6, 73, 70, 64, 80, + 80, 81, 85, 24, 65, 6, 65, 69, 83, 75, 72, 3, + 68, 67, 75, 16, 68, 2, 72, 80, 68, 73, 67, 1, + 66, 69, 0, 3, 77, 69, 62, 59, 24, 17, 4, 69, + 64, 5, 1, 10, 6, 64, 75, 4, 69, 76, 74, 13, 9, + 11, 5, 5, 8, 10, 23, 14, 7, 3, 3, 2, 10, 13, + 86, 64, 5, 73, 7, 7, 16, 12, 1, 19, 12, 14, + 71, 4, 64, 0, 14, 89, 11, 18, 15, 17, 15, 17, + 14, 24, 22, 4, 13, 19, 84, 62, 83, 10, 3, 4, + 3, 4, 6, 1, 9, 72, 0, 67, 6, 62, 83, 31, 34, + 28, 25, 24, 22, 18, 18, 15, 5, 2, 1, 67, 70, + 91, 75, 75, 91, 4, 1, 64, 66, 74, 77, 76, 73, + 82, 78, 84, 92, 84, 98, 71, 19, 15, 10, 6, 6, + 64, 68, 69, 67, 64, 30, 19, 15, 8, 13, 2, 0, + 67, 73, 7, 37, 29, 24, 21, 19, 7, 1, 65, 70, + 69, 45, 30, 13, 3, 12, 0, 73, 75, 6, 47, 38, + 26, 16, 21, 4, 64, 68, 71, 62, 89, 83, 75, 80, + 80, 75, 73, 69, 72, 71, 68, 64, 77, 75, 81, + 81, 65, 78, 76, 75, 69, 67, 70, 72, 68, 71, + 69, 70, 81, 7, 7, 14, 3, 64, 7, 5, 3, 4, 3, 1, + 66, 64, 67, 71, 68, 7, 84, 65, 16, 66, 7, 1, + 5, 12, 1, 0, 4, 8, 73, 71, 81, 36, 38, 39, 30, + 25, 31, 28, 27, 25, 20, 23, 21, 12, 9, 65, 14, + 16, 12, 3, 8, 7, 2, 0, 69, 66, 75, 77, 76, 84, + 32, 31, 30, 27, 16, 20, 17, 7, 9, 3, 0, 75, + 74, 80, 87, 69, 68, 90, 3, 1, 68, 77, 73, 76, + 81, 73, 82, 81, 91, 90, 95, 102, 75, 85, 90, + 76, 70, 4, 68, 13, 22, 67, 65, 8, 12, 4, 11, + 22, 4, 1, 44, 20, 64, 80, 92, 115, 126, 126, + 126, 1, 39, 29, 25, 15, 22, 11, 5, 3, 69, 75, + 70, 4, 67, 12, 20, 64, 65, 6, 10, 6, 9, 19, 8, + 2, 44, 20, 64, 80, 92, 115, 126, 126, 126 }, + + { + + 42, + 6, 79, 42, 6, 79, 76, 1, 21, 11, 0, 64, 2, 34, + 58, 15, 65, 68, 3, 8, 2, 66, 64, 72, 81, 7, + 72, 105, 110, 116, 18, 73, 68, 3, 8, 2, 79, + 66, 11, 6, 65, 72, 77, 1, 78, 81, 94, 6, 68, + 74, 4, 75, 73, 85, 64, 75, 71, 82, 1, 2, 22, + 0, 0, 0, 66, 93, 97, 4, 64, 67, 25, 0, 83, 7, + 5, 65, 30, 29, 5, 8, 70, 8, 71, 69, 2, 80, 79, + 81, 84, 24, 65, 7, 0, 69, 82, 74, 70, 3, 68, + 67, 75, 16, 68, 2, 71, 79, 67, 72, 66, 2, 65, + 68, 2, 4, 75, 68, 62, 62, 27, 20, 5, 68, 0, 6, + 1, 10, 6, 64, 74, 6, 69, 78, 76, 16, 9, 12, 5, + 5, 8, 11, 25, 15, 8, 2, 3, 2, 11, 13, 87, 0, + 5, 73, 6, 6, 15, 11, 0, 19, 12, 14, 72, 5, 65, + 0, 13, 89, 11, 17, 14, 15, 12, 16, 13, 22, 20, + 2, 11, 17, 90, 62, 86, 8, 1, 1, 0, 2, 3, 67, + 5, 74, 65, 70, 4, 62, 84, 29, 32, 26, 23, 22, + 20, 16, 16, 13, 2, 0, 64, 68, 72, 93, 75, 75, + 91, 3, 0, 65, 68, 75, 78, 77, 74, 83, 78, 85, + 92, 85, 97, 70, 20, 15, 10, 7, 6, 0, 67, 68, + 65, 1, 30, 20, 15, 8, 14, 3, 2, 65, 71, 7, 38, + 30, 25, 22, 20, 7, 2, 64, 69, 69, 46, 30, 13, + 2, 12, 0, 73, 74, 6, 47, 37, 25, 15, 21, 4, 0, + 68, 70, 62, 87, 82, 73, 78, 78, 74, 72, 68, + 71, 69, 67, 1, 76, 74, 80, 80, 0, 78, 76, 74, + 68, 66, 70, 73, 68, 72, 69, 70, 81, 7, 7, 15, + 3, 64, 7, 5, 3, 4, 3, 1, 67, 64, 67, 71, 68, + 8, 85, 66, 16, 67, 7, 1, 4, 12, 1, 64, 4, 8, + 74, 71, 82, 35, 37, 38, 28, 23, 29, 26, 24, + 23, 17, 20, 18, 9, 6, 67, 9, 12, 8, 65, 5, 4, + 64, 65, 71, 68, 76, 78, 76, 84, 29, 29, 28, + 25, 13, 17, 15, 4, 7, 1, 65, 78, 76, 82, 89, + 69, 68, 92, 2, 0, 69, 79, 75, 78, 82, 74, 84, + 82, 91, 92, 96, 102, 77, 87, 92, 75, 70, 5, + 68, 14, 23, 66, 64, 9, 13, 4, 12, 23, 4, 1, + 43, 18, 67, 83, 96, 119, 126, 126, 126, 2, 39, + 29, 25, 15, 23, 11, 6, 4, 69, 75, 70, 4, 67, + 13, 21, 64, 65, 7, 11, 6, 10, 20, 8, 2, 43, + 18, 67, 83, 96, 119, 126, 126, 126 }, + + { + + 41, + 6, 79, 41, 6, 79, 74, 3, 22, 11, 64, 66, 0, + 33, 58, 15, 0, 68, 4, 9, 2, 66, 64, 72, 82, 6, + 75, 108, 112, 117, 21, 72, 68, 4, 9, 2, 80, + 65, 11, 5, 65, 72, 77, 1, 78, 81, 94, 6, 68, + 73, 4, 75, 72, 85, 64, 75, 71, 82, 1, 2, 22, + 0, 0, 0, 66, 93, 97, 5, 65, 67, 24, 64, 83, + 10, 6, 64, 31, 31, 6, 10, 68, 9, 70, 68, 4, + 80, 79, 80, 84, 24, 65, 8, 2, 69, 81, 74, 68, + 3, 69, 68, 76, 16, 68, 3, 71, 78, 66, 72, 65, + 2, 64, 66, 3, 4, 74, 67, 62, 62, 30, 23, 5, + 68, 0, 6, 1, 10, 7, 0, 74, 8, 69, 80, 78, 19, + 9, 12, 5, 5, 8, 11, 26, 16, 8, 2, 3, 2, 11, + 14, 88, 0, 5, 74, 6, 5, 15, 11, 0, 19, 12, 14, + 73, 5, 66, 64, 13, 89, 11, 17, 14, 13, 9, 15, + 11, 20, 17, 0, 8, 15, 97, 62, 90, 6, 64, 64, + 66, 64, 0, 71, 1, 77, 67, 74, 2, 62, 86, 27, + 30, 24, 20, 19, 18, 14, 14, 11, 0, 65, 66, 70, + 73, 95, 75, 75, 92, 2, 64, 66, 69, 76, 79, 78, + 76, 84, 79, 86, 93, 85, 97, 68, 21, 16, 10, 7, + 7, 0, 67, 68, 0, 2, 30, 20, 16, 9, 15, 4, 3, + 64, 69, 8, 38, 30, 25, 22, 21, 8, 2, 64, 69, + 69, 46, 30, 12, 2, 12, 0, 73, 74, 6, 46, 37, + 24, 14, 21, 4, 0, 68, 70, 62, 86, 80, 72, 77, + 77, 73, 70, 66, 70, 68, 65, 3, 76, 73, 80, 79, + 2, 77, 76, 74, 68, 66, 70, 73, 68, 72, 69, 70, + 82, 8, 8, 16, 3, 64, 8, 5, 3, 4, 3, 1, 67, 64, + 67, 71, 68, 8, 86, 67, 16, 68, 6, 0, 4, 12, 0, + 65, 4, 8, 75, 71, 83, 34, 36, 37, 27, 21, 27, + 24, 22, 20, 15, 17, 15, 6, 4, 69, 5, 8, 4, 70, + 1, 1, 66, 67, 74, 70, 78, 80, 77, 85, 27, 26, + 25, 22, 10, 15, 12, 1, 4, 65, 68, 81, 78, 84, + 90, 70, 69, 93, 0, 65, 71, 81, 76, 80, 84, 75, + 85, 83, 92, 93, 97, 103, 78, 88, 93, 75, 69, + 5, 67, 15, 24, 66, 64, 9, 13, 4, 12, 24, 4, 1, + 42, 16, 69, 86, 99, 123, 126, 126, 126, 2, 39, + 29, 25, 15, 23, 12, 6, 4, 68, 75, 70, 5, 66, + 14, 22, 64, 64, 7, 11, 6, 10, 20, 8, 2, 42, + 16, 69, 86, 99, 123, 126, 126, 126 }, + + { + + 40, + 6, 79, 40, 6, 79, 72, 4, 22, 11, 64, 67, 65, + 32, 58, 15, 2, 68, 4, 10, 2, 67, 64, 73, 84, + 5, 77, 112, 115, 119, 24, 71, 68, 4, 10, 2, + 80, 64, 11, 5, 65, 72, 76, 1, 78, 81, 94, 6, + 68, 73, 4, 75, 72, 85, 64, 75, 71, 82, 1, 2, + 22, 0, 0, 0, 65, 93, 97, 5, 66, 67, 23, 64, + 83, 12, 7, 0, 33, 33, 8, 11, 66, 11, 69, 67, + 6, 80, 79, 80, 84, 24, 65, 9, 4, 69, 80, 73, + 66, 3, 69, 68, 76, 16, 68, 3, 70, 77, 65, 71, + 64, 3, 0, 65, 5, 5, 73, 66, 62, 62, 33, 26, 5, + 68, 0, 7, 1, 10, 7, 0, 73, 10, 69, 82, 80, 22, + 9, 12, 5, 5, 8, 12, 28, 17, 8, 2, 3, 2, 12, + 15, 89, 0, 5, 74, 5, 4, 14, 11, 64, 19, 12, + 14, 74, 5, 67, 65, 12, 89, 11, 16, 13, 11, 6, + 14, 9, 18, 15, 65, 6, 13, 103, 62, 93, 4, 66, + 66, 69, 66, 66, 75, 66, 80, 69, 77, 0, 62, 88, + 25, 28, 22, 18, 17, 16, 12, 12, 9, 65, 67, 68, + 72, 75, 97, 75, 75, 92, 1, 65, 67, 70, 77, 80, + 79, 78, 85, 79, 87, 94, 86, 96, 67, 22, 16, + 10, 7, 8, 1, 66, 67, 2, 3, 30, 20, 16, 10, 16, + 5, 5, 1, 67, 9, 39, 31, 26, 23, 22, 8, 2, 64, + 68, 69, 47, 30, 11, 2, 12, 0, 73, 73, 6, 46, + 36, 23, 13, 21, 4, 0, 68, 70, 62, 85, 79, 71, + 75, 75, 72, 68, 64, 69, 66, 0, 5, 75, 72, 79, + 78, 4, 77, 76, 74, 67, 65, 70, 73, 68, 73, 69, + 70, 82, 8, 8, 17, 3, 64, 8, 5, 3, 4, 3, 1, 68, + 64, 67, 71, 68, 9, 87, 68, 16, 69, 5, 64, 3, + 12, 64, 66, 4, 8, 76, 71, 84, 33, 35, 36, 25, + 19, 25, 22, 20, 18, 12, 14, 12, 3, 1, 71, 1, + 4, 0, 75, 65, 65, 69, 69, 76, 72, 80, 81, 78, + 86, 25, 24, 23, 20, 7, 12, 10, 65, 2, 67, 70, + 84, 80, 86, 92, 71, 70, 95, 64, 66, 73, 83, + 78, 82, 86, 76, 87, 84, 93, 94, 98, 104, 80, + 89, 94, 75, 69, 6, 67, 16, 25, 66, 0, 10, 14, + 4, 13, 25, 4, 1, 41, 14, 72, 89, 102, 126, + 126, 126, 126, 2, 39, 29, 25, 15, 24, 12, 6, + 4, 68, 75, 70, 5, 66, 15, 23, 64, 64, 7, 11, + 6, 10, 21, 8, 2, 41, 14, 72, 89, 102, 126, + 126, 126, 126 }, + + { + + 38, + 5, 80, 38, 5, 80, 71, 5, 22, 11, 65, 69, 68, + 31, 58, 14, 3, 69, 4, 10, 1, 68, 64, 74, 86, + 3, 80, 116, 118, 121, 26, 71, 68, 4, 10, 1, + 81, 64, 11, 4, 65, 72, 76, 0, 79, 81, 94, 5, + 68, 73, 4, 75, 72, 85, 64, 75, 72, 82, 1, 2, + 22, 0, 0, 0, 65, 94, 97, 5, 67, 68, 22, 65, + 84, 14, 8, 1, 34, 35, 9, 12, 65, 12, 68, 66, + 8, 80, 79, 80, 84, 24, 65, 9, 6, 69, 80, 73, + 65, 2, 70, 69, 77, 16, 68, 3, 70, 77, 65, 71, + 64, 3, 0, 64, 6, 5, 72, 65, 62, 62, 36, 28, 5, + 68, 0, 7, 0, 10, 7, 0, 73, 12, 69, 84, 82, 24, + 9, 12, 5, 5, 8, 12, 29, 17, 8, 1, 2, 2, 12, + 15, 91, 0, 5, 75, 4, 2, 13, 10, 65, 19, 12, + 13, 76, 5, 68, 66, 11, 89, 10, 15, 12, 8, 2, + 12, 7, 15, 12, 68, 3, 11, 110, 62, 97, 1, 68, + 69, 73, 69, 70, 80, 71, 83, 71, 81, 65, 62, + 90, 22, 25, 19, 15, 14, 13, 9, 9, 7, 68, 70, + 70, 74, 77, 99, 75, 76, 93, 0, 67, 69, 72, 79, + 82, 81, 80, 86, 80, 88, 95, 87, 96, 66, 22, + 16, 10, 7, 8, 1, 66, 67, 4, 4, 30, 20, 16, 10, + 16, 6, 6, 2, 66, 9, 39, 31, 26, 23, 23, 8, 2, + 64, 68, 70, 47, 29, 10, 1, 12, 0, 73, 73, 5, + 45, 35, 21, 12, 20, 4, 0, 68, 70, 62, 84, 78, + 70, 74, 74, 71, 67, 0, 68, 65, 1, 7, 75, 72, + 79, 77, 5, 77, 76, 74, 67, 65, 70, 74, 69, 74, + 69, 71, 83, 8, 8, 18, 3, 65, 8, 5, 2, 4, 3, 1, + 69, 64, 67, 71, 68, 9, 88, 69, 16, 71, 4, 65, + 2, 11, 65, 67, 4, 7, 78, 72, 86, 31, 33, 35, + 23, 17, 22, 19, 17, 15, 9, 11, 9, 64, 65, 73, + 67, 0, 68, 80, 69, 69, 72, 72, 79, 74, 82, 83, + 79, 87, 22, 21, 20, 17, 4, 9, 7, 69, 64, 70, + 73, 87, 82, 88, 94, 72, 71, 97, 66, 68, 75, + 86, 80, 84, 88, 78, 89, 86, 94, 96, 99, 105, + 82, 91, 96, 75, 69, 6, 67, 17, 26, 66, 0, 10, + 14, 4, 13, 25, 4, 0, 39, 12, 75, 93, 106, 126, + 126, 126, 126, 2, 39, 29, 25, 15, 24, 12, 6, + 4, 68, 75, 70, 5, 66, 15, 23, 64, 64, 7, 11, + 6, 10, 21, 7, 1, 39, 12, 75, 93, 106, 126, + 126, 126, 126 }, + + { + + 37, + 5, 80, 37, 5, 80, 69, 7, 23, 12, 65, 70, 70, + 30, 59, 14, 5, 69, 5, 11, 1, 68, 0, 74, 87, 2, + 82, 119, 120, 122, 29, 70, 67, 5, 11, 1, 81, + 0, 11, 4, 64, 71, 75, 0, 79, 80, 93, 5, 67, + 72, 4, 74, 71, 84, 0, 74, 72, 81, 2, 2, 22, 0, + 0, 0, 64, 94, 97, 6, 67, 68, 22, 65, 84, 17, + 10, 3, 36, 38, 11, 14, 0, 14, 66, 64, 11, 80, + 78, 79, 83, 24, 65, 10, 9, 68, 79, 72, 0, 2, + 70, 69, 77, 17, 68, 4, 69, 76, 64, 70, 0, 4, + 1, 1, 8, 6, 70, 0, 62, 62, 40, 31, 6, 67, 1, + 8, 0, 11, 8, 1, 72, 15, 68, 86, 83, 27, 9, 13, + 5, 6, 9, 13, 31, 18, 9, 1, 2, 2, 13, 16, 92, + 1, 5, 75, 4, 1, 13, 10, 65, 19, 12, 13, 77, 6, + 68, 66, 11, 89, 10, 15, 12, 6, 64, 11, 6, 13, + 10, 70, 1, 9, 116, 62, 100, 64, 69, 71, 76, + 71, 73, 84, 75, 85, 73, 84, 67, 62, 91, 20, + 23, 17, 13, 12, 11, 7, 7, 5, 70, 72, 71, 75, + 78, 100, 75, 76, 93, 0, 68, 70, 73, 80, 83, + 82, 81, 87, 80, 89, 95, 87, 95, 64, 23, 17, + 10, 8, 9, 2, 65, 66, 7, 6, 31, 21, 17, 11, 17, + 8, 8, 4, 64, 10, 40, 32, 27, 24, 24, 9, 3, 0, + 67, 70, 48, 29, 10, 1, 13, 1, 73, 72, 5, 45, + 35, 20, 11, 20, 5, 1, 67, 69, 62, 82, 76, 68, + 72, 72, 69, 65, 2, 66, 0, 3, 10, 74, 71, 78, + 75, 7, 76, 75, 73, 66, 64, 69, 74, 69, 74, 69, + 71, 83, 9, 9, 19, 4, 65, 9, 6, 2, 5, 4, 1, 69, + 0, 66, 70, 67, 10, 88, 70, 16, 72, 4, 65, 2, + 11, 65, 67, 4, 7, 79, 72, 87, 30, 32, 35, 22, + 16, 20, 17, 15, 13, 7, 9, 7, 67, 67, 75, 71, + 66, 72, 84, 72, 72, 74, 74, 81, 75, 83, 84, + 79, 87, 20, 19, 18, 15, 2, 7, 5, 72, 66, 72, + 75, 89, 83, 89, 95, 72, 71, 98, 67, 69, 76, + 88, 81, 85, 89, 79, 90, 87, 94, 97, 99, 105, + 83, 92, 97, 74, 68, 7, 66, 19, 28, 65, 1, 11, + 15, 5, 14, 26, 4, 0, 38, 10, 77, 96, 109, 126, + 126, 126, 126, 3, 39, 30, 26, 16, 25, 13, 7, + 5, 67, 74, 69, 6, 65, 16, 24, 0, 0, 8, 12, 6, + 11, 22, 7, 1, 38, 10, 77, 96, 109, 126, 126, + 126, 126 }, + + { + + 36, + 5, 80, 36, 5, 80, 67, 8, 23, 12, 65, 71, 72, + 29, 59, 14, 7, 69, 5, 12, 1, 69, 0, 75, 89, 1, + 85, 123, 123, 124, 32, 69, 67, 5, 12, 1, 81, + 1, 11, 3, 64, 71, 74, 0, 79, 80, 93, 5, 67, + 72, 4, 74, 71, 84, 0, 74, 72, 81, 2, 2, 22, 0, + 0, 0, 0, 94, 97, 6, 68, 68, 21, 65, 84, 19, + 11, 4, 38, 40, 12, 15, 2, 15, 65, 0, 13, 80, + 78, 79, 83, 24, 65, 11, 11, 68, 78, 71, 2, 2, + 70, 69, 78, 17, 68, 4, 68, 75, 0, 69, 1, 4, 2, + 2, 9, 7, 69, 1, 62, 62, 43, 34, 6, 67, 1, 8, + 0, 11, 8, 1, 72, 17, 68, 88, 85, 30, 9, 13, 5, + 6, 9, 13, 33, 19, 9, 1, 2, 2, 14, 17, 93, 1, + 5, 75, 3, 0, 12, 10, 66, 19, 12, 13, 78, 6, + 69, 67, 10, 89, 10, 14, 11, 4, 67, 10, 4, 11, + 8, 72, 64, 7, 122, 62, 104, 66, 71, 73, 79, + 73, 76, 88, 79, 88, 75, 87, 69, 62, 93, 18, + 21, 15, 11, 10, 9, 5, 5, 3, 72, 74, 73, 77, + 80, 102, 75, 76, 94, 64, 69, 71, 74, 81, 84, + 83, 83, 88, 80, 90, 96, 88, 94, 0, 24, 17, 10, + 8, 10, 3, 64, 65, 9, 7, 31, 21, 17, 12, 18, 9, + 10, 6, 1, 11, 41, 33, 28, 25, 25, 9, 3, 0, 66, + 70, 49, 29, 9, 1, 13, 1, 73, 72, 5, 45, 34, + 19, 10, 20, 5, 1, 67, 69, 62, 81, 75, 67, 70, + 70, 68, 0, 4, 65, 2, 5, 12, 73, 70, 78, 74, 9, + 76, 75, 73, 65, 64, 69, 74, 69, 75, 69, 71, + 83, 9, 9, 20, 4, 65, 9, 6, 2, 5, 4, 1, 70, 0, + 66, 70, 67, 11, 89, 71, 16, 73, 3, 66, 1, 11, + 66, 68, 4, 7, 80, 72, 88, 29, 31, 34, 20, 14, + 18, 15, 13, 11, 4, 6, 4, 70, 70, 77, 75, 70, + 76, 89, 75, 75, 77, 76, 84, 77, 85, 85, 80, + 88, 18, 17, 15, 13, 64, 4, 2, 75, 68, 75, 77, + 92, 85, 91, 97, 73, 72, 100, 68, 70, 78, 90, + 83, 87, 91, 80, 92, 88, 95, 98, 100, 106, 85, + 93, 98, 74, 68, 8, 66, 20, 29, 65, 2, 12, 16, + 5, 14, 27, 4, 0, 37, 8, 80, 99, 112, 126, 126, + 126, 126, 3, 39, 30, 26, 16, 26, 13, 7, 5, 67, + 74, 69, 6, 65, 17, 25, 0, 0, 8, 12, 6, 11, 23, + 7, 1, 37, 8, 80, 99, 112, 126, 126, 126, 126 }, + + { + + 35, + 5, 80, 35, 5, 80, 65, 10, 24, 12, 66, 73, 74, + 28, 59, 14, 9, 69, 6, 13, 1, 69, 0, 75, 90, 0, + 87, 126, 125, 125, 35, 68, 67, 6, 13, 1, 82, + 2, 11, 3, 64, 71, 74, 0, 79, 80, 93, 5, 67, + 71, 4, 74, 70, 84, 0, 74, 72, 81, 2, 2, 22, 0, + 0, 0, 0, 94, 97, 7, 69, 68, 20, 66, 84, 22, + 12, 5, 39, 42, 14, 17, 4, 17, 64, 1, 15, 80, + 78, 78, 83, 24, 65, 12, 13, 68, 77, 71, 4, 2, + 71, 70, 78, 17, 68, 5, 68, 74, 1, 69, 2, 5, 3, + 4, 11, 7, 68, 2, 62, 62, 46, 37, 6, 67, 1, 9, + 0, 11, 9, 2, 71, 19, 68, 90, 87, 33, 9, 13, 5, + 6, 9, 14, 34, 20, 9, 1, 2, 2, 14, 18, 94, 1, + 5, 76, 3, 64, 12, 10, 66, 19, 12, 13, 79, 6, + 70, 68, 10, 89, 10, 14, 11, 2, 70, 9, 2, 9, 5, + 74, 67, 5, 126, 62, 107, 68, 73, 75, 82, 76, + 79, 92, 83, 91, 77, 91, 71, 62, 95, 16, 19, + 13, 8, 7, 7, 3, 3, 1, 74, 76, 75, 79, 81, 104, + 75, 76, 94, 65, 70, 72, 75, 82, 85, 84, 85, + 89, 81, 91, 97, 88, 94, 2, 25, 18, 10, 8, 11, + 3, 64, 65, 11, 8, 31, 21, 18, 13, 19, 10, 11, + 7, 3, 12, 41, 33, 28, 25, 26, 10, 3, 0, 66, + 70, 49, 29, 8, 1, 13, 1, 73, 71, 5, 44, 34, + 18, 9, 20, 5, 1, 67, 69, 62, 80, 73, 66, 69, + 69, 67, 2, 6, 64, 3, 7, 14, 73, 69, 77, 73, + 11, 75, 75, 73, 65, 0, 69, 74, 69, 75, 69, 71, + 84, 10, 10, 21, 4, 65, 10, 6, 2, 5, 4, 1, 70, + 0, 66, 70, 67, 11, 90, 72, 16, 74, 2, 67, 1, + 11, 67, 69, 4, 7, 81, 72, 89, 28, 30, 33, 19, + 12, 16, 13, 11, 8, 2, 3, 1, 73, 72, 79, 79, + 74, 80, 94, 79, 78, 79, 78, 86, 79, 87, 87, + 81, 89, 16, 14, 13, 10, 67, 2, 0, 78, 71, 77, + 80, 95, 87, 93, 98, 74, 73, 101, 70, 72, 80, + 92, 84, 89, 93, 81, 93, 89, 96, 99, 101, 107, + 86, 94, 99, 74, 67, 8, 65, 21, 30, 65, 2, 12, + 16, 5, 15, 28, 4, 0, 36, 6, 82, 102, 115, 126, + 126, 126, 126, 3, 39, 30, 26, 16, 26, 14, 7, + 5, 66, 74, 69, 7, 64, 18, 26, 0, 1, 8, 12, 6, + 11, 23, 7, 1, 36, 6, 82, 102, 115, 126, 126, + 126, 126 }, + + { + + 33, + 5, 80, 33, 5, 80, 64, 11, 24, 12, 66, 74, 76, + 27, 59, 13, 11, 69, 6, 14, 0, 70, 0, 76, 92, + 64, 90, 126, 126, 126, 38, 67, 67, 6, 14, 0, + 82, 3, 11, 2, 64, 70, 73, 0, 79, 80, 93, 5, + 67, 71, 4, 74, 70, 83, 0, 74, 72, 81, 3, 2, + 22, 0, 0, 0, 1, 95, 97, 7, 70, 68, 19, 66, 84, + 24, 13, 6, 41, 44, 15, 18, 5, 18, 1, 2, 17, + 80, 77, 78, 83, 24, 65, 13, 15, 68, 77, 70, 6, + 2, 71, 70, 79, 17, 68, 5, 67, 73, 2, 68, 3, 5, + 3, 5, 12, 8, 67, 3, 62, 62, 49, 40, 6, 66, 1, + 9, 0, 11, 9, 2, 71, 21, 68, 92, 89, 35, 9, 14, + 5, 6, 9, 14, 36, 21, 10, 0, 2, 2, 15, 18, 95, + 1, 5, 76, 2, 65, 11, 9, 67, 19, 12, 13, 80, 6, + 71, 69, 9, 89, 9, 13, 10, 0, 74, 8, 0, 7, 3, + 76, 69, 3, 126, 62, 111, 70, 75, 78, 85, 78, + 83, 97, 87, 94, 79, 94, 73, 62, 96, 14, 17, + 10, 6, 5, 5, 1, 1, 64, 77, 78, 77, 81, 83, + 106, 75, 76, 95, 66, 71, 73, 77, 83, 87, 85, + 86, 90, 81, 92, 97, 89, 93, 3, 26, 18, 10, 9, + 11, 4, 0, 64, 13, 10, 31, 22, 18, 13, 20, 11, + 13, 9, 4, 12, 42, 34, 29, 26, 27, 10, 3, 1, + 65, 70, 50, 29, 8, 0, 13, 1, 73, 71, 5, 44, + 33, 17, 8, 20, 5, 2, 67, 69, 62, 78, 72, 65, + 67, 67, 66, 3, 7, 0, 5, 8, 16, 72, 68, 77, 72, + 12, 75, 75, 73, 64, 0, 69, 75, 69, 76, 69, 71, + 84, 10, 10, 22, 4, 65, 10, 6, 2, 5, 4, 1, 71, + 0, 66, 70, 67, 12, 91, 73, 16, 75, 2, 68, 0, + 11, 68, 70, 4, 7, 82, 72, 90, 27, 29, 32, 17, + 10, 14, 11, 8, 6, 64, 0, 65, 76, 75, 81, 84, + 78, 84, 99, 82, 82, 82, 81, 89, 81, 89, 88, + 82, 89, 13, 12, 10, 8, 70, 64, 66, 81, 73, 80, + 82, 98, 89, 95, 100, 75, 73, 103, 71, 73, 81, + 94, 86, 91, 94, 82, 95, 90, 97, 101, 102, 107, + 88, 96, 101, 73, 67, 9, 65, 22, 31, 65, 3, 13, + 17, 5, 15, 29, 4, 0, 35, 4, 85, 105, 119, 126, + 126, 126, 126, 4, 39, 30, 26, 16, 27, 14, 7, + 5, 66, 74, 69, 7, 64, 18, 27, 0, 1, 9, 13, 6, + 11, 24, 7, 1, 35, 4, 85, 105, 119, 126, 126, + 126, 126 }, + + { + + 32, + 5, 80, 32, 5, 80, 1, 13, 24, 12, 67, 75, 78, + 26, 59, 13, 13, 69, 6, 15, 0, 70, 0, 77, 94, + 65, 92, 126, 126, 126, 41, 66, 66, 6, 15, 0, + 82, 4, 11, 2, 64, 70, 72, 0, 79, 80, 93, 5, + 67, 71, 4, 74, 69, 83, 0, 74, 72, 81, 3, 2, + 22, 0, 0, 0, 1, 95, 97, 8, 71, 68, 18, 67, 84, + 27, 14, 7, 43, 46, 17, 20, 7, 20, 2, 3, 20, + 80, 77, 77, 82, 24, 65, 14, 17, 68, 76, 70, 8, + 2, 72, 71, 79, 17, 68, 6, 67, 72, 3, 68, 4, 6, + 4, 7, 14, 9, 65, 4, 62, 62, 52, 43, 7, 66, 2, + 10, 0, 11, 10, 2, 70, 23, 68, 94, 91, 38, 9, + 14, 5, 6, 9, 15, 37, 22, 10, 0, 2, 2, 15, 19, + 96, 2, 5, 77, 2, 66, 11, 9, 67, 19, 12, 13, + 81, 7, 72, 69, 9, 89, 9, 12, 9, 65, 77, 7, 64, + 5, 1, 78, 72, 1, 126, 62, 114, 72, 77, 80, 88, + 81, 86, 101, 91, 96, 81, 98, 75, 62, 98, 12, + 15, 8, 4, 3, 3, 64, 64, 66, 79, 80, 79, 82, + 85, 108, 75, 76, 95, 67, 72, 74, 78, 84, 88, + 86, 88, 91, 82, 93, 98, 90, 92, 5, 27, 19, 10, + 9, 12, 4, 0, 0, 15, 11, 31, 22, 19, 14, 21, + 12, 14, 10, 6, 13, 43, 35, 30, 26, 28, 11, 4, + 1, 65, 70, 50, 29, 7, 0, 13, 1, 73, 70, 5, 43, + 32, 16, 7, 20, 5, 2, 67, 68, 62, 77, 70, 0, + 65, 66, 65, 5, 9, 1, 7, 10, 18, 71, 67, 76, + 71, 14, 75, 75, 72, 64, 1, 69, 75, 69, 76, 69, + 71, 85, 11, 10, 23, 4, 65, 10, 6, 2, 5, 4, 1, + 71, 0, 66, 70, 67, 12, 92, 74, 16, 76, 1, 68, + 64, 11, 68, 71, 4, 7, 83, 72, 91, 26, 28, 31, + 15, 8, 12, 9, 6, 3, 67, 66, 68, 79, 77, 83, + 88, 82, 88, 104, 85, 85, 84, 83, 91, 83, 90, + 90, 82, 90, 11, 9, 8, 6, 73, 67, 68, 84, 75, + 82, 84, 101, 91, 97, 101, 75, 74, 105, 72, 75, + 83, 96, 88, 93, 96, 83, 96, 91, 97, 102, 103, + 108, 89, 97, 102, 73, 67, 10, 64, 23, 32, 64, + 4, 13, 17, 5, 16, 30, 4, 0, 34, 2, 88, 108, + 122, 126, 126, 126, 126, 4, 39, 30, 26, 16, + 27, 14, 8, 6, 65, 74, 69, 8, 64, 19, 28, 0, 1, + 9, 13, 6, 12, 24, 7, 1, 34, 2, 88, 108, 122, + 126, 126, 126, 126 }, + + { + + 31, + 5, 81, 31, 5, 81, 3, 14, 25, 12, 67, 77, 80, + 25, 59, 13, 14, 69, 7, 15, 0, 71, 1, 77, 95, + 67, 95, 126, 126, 126, 43, 65, 66, 7, 15, 0, + 83, 4, 11, 1, 64, 70, 72, 0, 79, 79, 92, 5, + 66, 70, 4, 73, 69, 83, 0, 74, 72, 81, 3, 2, + 22, 0, 0, 0, 2, 95, 97, 8, 71, 69, 18, 67, 84, + 29, 15, 8, 44, 49, 18, 21, 9, 21, 3, 4, 22, + 80, 77, 77, 82, 24, 65, 15, 20, 68, 75, 69, + 10, 1, 72, 71, 80, 18, 68, 6, 66, 72, 3, 67, + 5, 6, 5, 8, 15, 9, 64, 5, 62, 62, 55, 46, 7, + 66, 2, 10, 0, 11, 10, 3, 70, 25, 67, 96, 93, + 41, 9, 14, 5, 6, 10, 15, 39, 23, 10, 0, 2, 2, + 16, 20, 97, 2, 5, 77, 1, 67, 10, 9, 68, 19, + 12, 13, 83, 7, 73, 70, 8, 89, 9, 12, 9, 67, + 80, 6, 66, 2, 65, 80, 74, 64, 126, 62, 118, + 74, 78, 82, 92, 83, 89, 105, 96, 99, 83, 101, + 77, 62, 100, 10, 13, 6, 1, 0, 1, 67, 66, 68, + 81, 83, 81, 84, 86, 109, 75, 76, 96, 68, 73, + 75, 79, 85, 89, 87, 90, 92, 82, 94, 99, 90, + 92, 6, 28, 19, 10, 9, 13, 5, 1, 0, 17, 12, 32, + 22, 19, 15, 22, 13, 16, 12, 8, 14, 43, 35, 30, + 27, 29, 11, 4, 1, 64, 70, 51, 29, 6, 0, 13, 1, + 73, 70, 4, 43, 32, 15, 6, 19, 5, 2, 67, 68, + 62, 76, 69, 1, 64, 64, 64, 7, 11, 2, 8, 12, + 20, 71, 66, 76, 70, 16, 74, 75, 72, 0, 1, 69, + 75, 69, 77, 69, 71, 85, 11, 11, 24, 4, 65, 11, + 7, 2, 6, 5, 1, 72, 0, 66, 70, 67, 13, 93, 75, + 16, 77, 0, 69, 64, 11, 69, 71, 4, 6, 85, 73, + 92, 24, 27, 30, 14, 6, 10, 7, 4, 1, 69, 69, + 70, 82, 80, 85, 92, 86, 92, 108, 89, 88, 87, + 85, 94, 85, 92, 91, 83, 91, 9, 7, 5, 3, 76, + 69, 71, 87, 78, 85, 87, 104, 93, 98, 103, 76, + 75, 106, 74, 76, 85, 98, 89, 95, 98, 84, 98, + 92, 98, 103, 104, 109, 91, 98, 103, 73, 66, + 10, 64, 24, 33, 64, 4, 14, 18, 5, 16, 31, 4, + 0, 33, 0, 90, 111, 125, 126, 126, 126, 126, 4, + 39, 30, 26, 16, 28, 15, 8, 6, 65, 73, 68, 8, + 0, 20, 29, 1, 2, 9, 13, 6, 12, 25, 7, 1, 33, + 0, 90, 111, 125, 126, 126, 126, 126 }, + + { + + 30, + 5, 81, 30, 5, 81, 5, 16, 25, 12, 68, 78, 82, + 24, 59, 13, 16, 69, 7, 16, 64, 71, 1, 78, 97, + 68, 97, 126, 126, 126, 46, 64, 66, 7, 16, 64, + 83, 5, 11, 1, 64, 69, 71, 0, 79, 79, 92, 5, + 66, 70, 4, 73, 68, 82, 0, 74, 72, 81, 4, 2, + 22, 0, 0, 0, 2, 95, 97, 9, 72, 69, 17, 68, 84, + 32, 16, 9, 46, 51, 20, 23, 10, 23, 5, 5, 24, + 80, 76, 76, 82, 24, 65, 16, 22, 68, 74, 69, + 12, 1, 73, 72, 80, 18, 68, 7, 66, 71, 4, 67, + 6, 7, 6, 10, 17, 10, 0, 6, 62, 62, 58, 49, 7, + 65, 2, 11, 0, 11, 11, 3, 69, 27, 67, 98, 95, + 44, 9, 15, 5, 6, 10, 16, 40, 24, 11, 64, 2, 2, + 16, 20, 98, 2, 5, 78, 1, 68, 10, 8, 68, 19, + 12, 13, 84, 7, 74, 71, 8, 89, 9, 11, 8, 69, + 83, 5, 68, 0, 67, 82, 77, 66, 126, 62, 121, + 76, 80, 85, 95, 86, 92, 110, 100, 102, 85, + 105, 79, 62, 101, 8, 11, 4, 64, 65, 64, 69, + 68, 70, 84, 85, 83, 86, 88, 111, 75, 76, 96, + 69, 74, 76, 81, 86, 90, 88, 91, 93, 83, 95, + 99, 91, 91, 8, 29, 20, 10, 10, 13, 5, 1, 1, + 19, 14, 32, 23, 20, 15, 23, 14, 17, 13, 10, + 14, 44, 36, 31, 27, 30, 12, 4, 2, 64, 70, 51, + 29, 6, 64, 13, 1, 73, 69, 4, 42, 31, 14, 5, + 19, 5, 3, 67, 68, 62, 74, 67, 2, 1, 0, 0, 8, + 12, 3, 10, 13, 22, 70, 65, 75, 69, 18, 74, 75, + 72, 0, 2, 69, 76, 69, 77, 69, 71, 86, 12, 11, + 25, 4, 65, 11, 7, 2, 6, 5, 1, 72, 0, 66, 70, + 67, 13, 94, 76, 16, 78, 0, 70, 65, 11, 70, 72, + 4, 6, 86, 73, 93, 23, 26, 29, 12, 4, 8, 5, 1, + 65, 72, 72, 73, 85, 82, 87, 97, 90, 96, 113, + 92, 91, 89, 87, 96, 87, 94, 93, 84, 91, 6, 4, + 3, 1, 79, 72, 73, 90, 80, 87, 89, 107, 95, + 100, 104, 77, 75, 108, 75, 78, 86, 100, 91, + 97, 99, 85, 99, 93, 99, 105, 105, 109, 92, + 100, 105, 72, 66, 11, 0, 25, 34, 64, 5, 14, + 18, 5, 17, 32, 4, 0, 32, 65, 93, 114, 126, + 126, 126, 126, 126, 5, 39, 30, 26, 16, 28, 15, + 8, 6, 64, 73, 68, 9, 0, 21, 30, 1, 2, 10, 14, + 6, 12, 25, 7, 1, 32, 65, 93, 114, 126, 126, + 126, 126, 126 }, + + { + + 28, + 4, 81, 28, 4, 81, 6, 17, 25, 12, 68, 80, 85, + 23, 59, 12, 18, 70, 7, 17, 64, 72, 1, 79, 99, + 69, 100, 126, 126, 126, 49, 0, 66, 7, 17, 64, + 84, 6, 11, 0, 64, 69, 71, 64, 80, 79, 92, 5, + 66, 70, 4, 73, 68, 82, 0, 74, 72, 81, 4, 2, + 22, 0, 0, 0, 3, 96, 97, 9, 73, 69, 16, 68, 85, + 34, 17, 10, 47, 53, 21, 24, 12, 24, 6, 6, 26, + 80, 76, 76, 82, 24, 65, 17, 24, 68, 74, 68, + 14, 1, 73, 72, 81, 18, 68, 7, 65, 70, 5, 66, + 6, 7, 6, 11, 18, 10, 1, 7, 62, 62, 61, 51, 7, + 65, 2, 11, 64, 11, 11, 3, 69, 29, 67, 100, 97, + 46, 9, 15, 5, 6, 10, 16, 42, 24, 11, 64, 1, 2, + 17, 21, 100, 2, 5, 78, 0, 70, 9, 8, 69, 19, + 12, 12, 85, 7, 75, 72, 7, 89, 8, 10, 7, 71, + 87, 3, 70, 65, 70, 85, 79, 68, 126, 62, 125, + 78, 82, 87, 98, 88, 96, 114, 104, 105, 87, + 108, 81, 62, 103, 6, 8, 1, 67, 68, 67, 71, 71, + 72, 86, 87, 85, 88, 90, 113, 75, 77, 97, 70, + 76, 77, 82, 87, 92, 90, 93, 94, 83, 96, 100, + 92, 91, 9, 30, 20, 10, 10, 14, 6, 2, 1, 21, + 15, 32, 23, 20, 16, 24, 15, 19, 15, 11, 15, + 44, 36, 31, 28, 31, 12, 4, 2, 0, 71, 52, 29, + 5, 64, 13, 1, 73, 69, 4, 42, 30, 13, 4, 19, 5, + 3, 67, 68, 62, 73, 66, 3, 2, 2, 1, 10, 14, 4, + 11, 15, 24, 70, 65, 75, 68, 19, 74, 75, 72, 1, + 2, 69, 76, 69, 78, 69, 71, 86, 12, 11, 26, 4, + 66, 11, 7, 1, 6, 5, 1, 73, 0, 66, 70, 67, 14, + 95, 77, 16, 80, 64, 71, 66, 10, 71, 73, 4, 6, + 87, 73, 95, 22, 24, 28, 10, 2, 6, 3, 64, 67, + 75, 75, 76, 88, 85, 89, 101, 94, 101, 118, 96, + 95, 92, 90, 99, 89, 96, 94, 85, 92, 4, 2, 0, + 65, 82, 75, 76, 93, 83, 90, 92, 110, 97, 102, + 106, 78, 76, 110, 77, 79, 88, 102, 93, 99, + 101, 87, 101, 95, 100, 106, 106, 110, 94, 101, + 106, 72, 66, 11, 0, 26, 35, 64, 5, 15, 19, 5, + 17, 32, 4, 64, 31, 67, 96, 117, 126, 126, 126, + 126, 126, 5, 39, 30, 26, 16, 29, 15, 8, 6, 64, + 73, 68, 9, 0, 21, 30, 1, 2, 10, 14, 6, 12, 26, + 7, 0, 31, 67, 96, 117, 126, 126, 126, 126, 126 }, + + { + + 27, + 4, 81, 27, 4, 81, 8, 18, 26, 12, 68, 81, 87, + 22, 60, 12, 20, 70, 8, 18, 64, 73, 1, 79, 100, + 70, 102, 126, 126, 126, 52, 1, 65, 8, 18, 64, + 84, 7, 11, 0, 0, 69, 70, 64, 80, 79, 92, 5, + 66, 69, 4, 73, 68, 82, 0, 74, 72, 80, 4, 2, + 22, 0, 0, 0, 4, 96, 97, 9, 74, 69, 15, 68, 85, + 36, 19, 11, 49, 55, 23, 25, 14, 26, 7, 8, 29, + 80, 76, 76, 81, 24, 65, 18, 26, 67, 73, 67, + 16, 1, 73, 72, 81, 18, 68, 7, 64, 69, 6, 65, + 7, 8, 7, 12, 20, 11, 3, 8, 62, 62, 62, 54, 8, + 65, 3, 12, 64, 11, 11, 4, 68, 32, 67, 102, 98, + 49, 9, 15, 5, 6, 10, 17, 44, 25, 11, 64, 1, 2, + 18, 22, 101, 3, 5, 78, 64, 71, 8, 8, 70, 19, + 12, 12, 86, 8, 76, 72, 6, 89, 8, 10, 7, 73, + 90, 2, 71, 67, 72, 87, 81, 70, 126, 62, 126, + 80, 84, 89, 101, 90, 99, 118, 108, 107, 89, + 111, 83, 62, 105, 4, 6, 64, 69, 70, 69, 73, + 73, 74, 88, 89, 86, 89, 91, 115, 75, 77, 97, + 70, 77, 78, 83, 88, 93, 91, 95, 95, 83, 97, + 101, 92, 90, 10, 31, 20, 10, 10, 15, 7, 3, 2, + 24, 16, 32, 23, 20, 17, 25, 16, 21, 17, 13, + 16, 45, 37, 32, 29, 32, 12, 5, 2, 1, 71, 53, + 29, 4, 64, 14, 2, 73, 68, 4, 42, 30, 12, 3, + 19, 5, 3, 67, 67, 62, 72, 65, 5, 4, 4, 2, 12, + 16, 5, 13, 17, 26, 69, 64, 74, 67, 21, 73, 74, + 71, 2, 3, 69, 76, 69, 79, 69, 71, 86, 12, 12, + 27, 5, 66, 12, 7, 1, 6, 5, 1, 74, 0, 65, 69, + 67, 15, 95, 78, 16, 81, 65, 71, 66, 10, 71, + 74, 4, 6, 88, 73, 96, 21, 23, 28, 9, 0, 4, 1, + 66, 69, 77, 78, 79, 91, 88, 91, 105, 98, 105, + 123, 99, 98, 95, 92, 101, 90, 97, 95, 85, 93, + 2, 0, 65, 67, 84, 77, 78, 96, 85, 92, 94, 112, + 99, 104, 108, 78, 77, 111, 78, 80, 90, 104, + 94, 100, 103, 88, 103, 96, 100, 107, 106, 111, + 96, 102, 107, 72, 65, 12, 0, 27, 37, 0, 6, 16, + 20, 5, 18, 33, 4, 64, 30, 69, 98, 120, 126, + 126, 126, 126, 126, 5, 39, 30, 27, 17, 30, 16, + 9, 7, 64, 73, 68, 9, 1, 22, 31, 1, 3, 10, 14, + 6, 13, 27, 7, 0, 30, 69, 98, 120, 126, 126, + 126, 126, 126 }, + + { + + 26, + 4, 81, 26, 4, 81, 10, 20, 26, 12, 69, 82, 89, + 21, 60, 12, 22, 70, 8, 19, 65, 73, 1, 80, 102, + 71, 105, 126, 126, 126, 55, 2, 65, 8, 19, 65, + 84, 8, 11, 64, 0, 68, 69, 64, 80, 79, 92, 5, + 66, 69, 4, 73, 67, 81, 0, 74, 72, 80, 5, 2, + 22, 0, 0, 0, 4, 96, 97, 10, 75, 69, 14, 69, + 85, 39, 20, 12, 51, 57, 24, 27, 15, 27, 9, 9, + 31, 80, 75, 75, 81, 24, 65, 19, 28, 67, 72, + 67, 18, 1, 74, 73, 82, 18, 68, 8, 64, 68, 7, + 65, 8, 8, 8, 14, 21, 12, 4, 9, 62, 62, 62, 57, + 8, 64, 3, 12, 64, 11, 12, 4, 68, 34, 67, 104, + 100, 52, 9, 16, 5, 6, 10, 17, 45, 26, 12, 65, + 1, 2, 18, 22, 102, 3, 5, 79, 64, 72, 8, 7, 70, + 19, 12, 12, 87, 8, 77, 73, 6, 89, 8, 9, 6, 75, + 93, 1, 73, 69, 74, 89, 84, 72, 126, 62, 126, + 82, 86, 92, 104, 93, 102, 123, 112, 110, 91, + 115, 85, 62, 106, 2, 4, 66, 71, 72, 71, 75, + 75, 76, 91, 91, 88, 91, 93, 117, 75, 77, 98, + 71, 78, 79, 85, 89, 94, 92, 96, 96, 84, 98, + 101, 93, 89, 12, 32, 21, 10, 11, 15, 7, 3, 3, + 26, 18, 32, 24, 21, 17, 26, 17, 22, 18, 15, + 16, 46, 38, 33, 29, 33, 13, 5, 3, 1, 71, 53, + 29, 4, 65, 14, 2, 73, 68, 4, 41, 29, 11, 2, + 19, 5, 4, 67, 67, 62, 70, 0, 6, 6, 5, 3, 13, + 17, 6, 15, 18, 28, 68, 0, 74, 66, 23, 73, 74, + 71, 2, 3, 69, 77, 69, 79, 69, 71, 87, 13, 12, + 28, 5, 66, 12, 7, 1, 6, 5, 1, 74, 0, 65, 69, + 67, 15, 96, 79, 16, 82, 65, 72, 67, 10, 72, + 75, 4, 6, 89, 73, 97, 20, 22, 27, 7, 65, 2, + 64, 69, 72, 80, 81, 82, 94, 90, 93, 110, 102, + 109, 126, 102, 101, 97, 94, 104, 92, 99, 97, + 86, 93, 64, 66, 68, 69, 87, 80, 81, 99, 87, + 95, 96, 115, 101, 106, 109, 79, 77, 113, 79, + 82, 91, 106, 96, 102, 104, 89, 104, 97, 101, + 109, 107, 111, 97, 104, 109, 71, 65, 13, 1, + 28, 38, 0, 7, 16, 20, 5, 18, 34, 4, 64, 29, + 71, 101, 123, 126, 126, 126, 126, 126, 6, 39, + 30, 27, 17, 30, 16, 9, 7, 0, 73, 68, 10, 1, + 23, 32, 1, 3, 11, 15, 6, 13, 27, 7, 0, 29, 71, + 101, 123, 126, 126, 126, 126, 126 }, + + { + + 25, + 4, 82, 25, 4, 82, 12, 21, 27, 12, 69, 84, 91, + 20, 60, 12, 23, 70, 9, 19, 65, 74, 2, 80, 103, + 73, 107, 126, 126, 126, 57, 3, 65, 9, 19, 65, + 85, 8, 11, 64, 0, 68, 69, 64, 80, 78, 91, 5, + 65, 68, 4, 72, 67, 81, 0, 74, 72, 80, 5, 2, + 22, 0, 0, 0, 5, 96, 97, 10, 75, 70, 14, 69, + 85, 41, 21, 13, 52, 60, 26, 28, 17, 29, 10, + 10, 33, 80, 75, 75, 81, 24, 65, 20, 31, 67, + 71, 66, 20, 0, 74, 73, 82, 19, 68, 8, 0, 68, + 7, 64, 9, 9, 9, 15, 23, 12, 5, 10, 62, 62, 62, + 60, 8, 64, 3, 13, 64, 11, 12, 5, 67, 36, 66, + 106, 102, 55, 9, 16, 5, 6, 11, 18, 47, 27, 12, + 65, 1, 2, 19, 23, 103, 3, 5, 79, 65, 73, 7, 7, + 71, 19, 12, 12, 89, 8, 78, 74, 5, 89, 8, 9, 6, + 77, 96, 0, 75, 72, 77, 91, 86, 74, 126, 62, + 126, 84, 87, 94, 108, 95, 105, 126, 117, 113, + 93, 118, 87, 62, 108, 0, 2, 68, 74, 75, 73, + 78, 77, 78, 93, 94, 90, 93, 94, 118, 75, 77, + 98, 72, 79, 80, 86, 90, 95, 93, 98, 97, 84, + 99, 102, 93, 89, 13, 33, 21, 10, 11, 16, 8, 4, + 3, 28, 19, 33, 24, 21, 18, 27, 18, 24, 20, 17, + 17, 46, 38, 33, 30, 34, 13, 5, 3, 2, 71, 54, + 29, 3, 65, 14, 2, 73, 67, 3, 41, 29, 10, 1, + 18, 5, 4, 67, 67, 62, 69, 1, 7, 7, 7, 4, 15, + 19, 7, 16, 20, 30, 68, 1, 73, 65, 25, 72, 74, + 71, 3, 4, 69, 77, 69, 80, 69, 71, 87, 13, 13, + 29, 5, 66, 13, 8, 1, 7, 6, 1, 75, 0, 65, 69, + 67, 16, 97, 80, 16, 83, 66, 73, 67, 10, 73, + 75, 4, 5, 91, 74, 98, 18, 21, 26, 6, 67, 0, + 66, 71, 74, 82, 84, 84, 97, 93, 95, 114, 106, + 113, 126, 106, 104, 100, 96, 106, 94, 101, 98, + 87, 94, 66, 68, 70, 72, 90, 82, 83, 102, 90, + 97, 99, 118, 103, 107, 111, 80, 78, 114, 81, + 83, 93, 108, 97, 104, 106, 90, 106, 98, 102, + 110, 108, 112, 99, 105, 110, 71, 64, 13, 1, + 29, 39, 0, 7, 17, 21, 5, 19, 35, 4, 64, 28, + 73, 103, 126, 126, 126, 126, 126, 126, 6, 39, + 30, 27, 17, 31, 17, 9, 7, 0, 72, 67, 10, 2, + 24, 33, 2, 4, 11, 15, 6, 13, 28, 7, 0, 28, 73, + 103, 126, 126, 126, 126, 126, 126 }, + + { + + 23, + 4, 82, 23, 4, 82, 13, 23, 27, 12, 70, 85, 93, + 19, 60, 11, 25, 70, 9, 20, 65, 74, 2, 81, 105, + 74, 110, 126, 126, 126, 60, 4, 65, 9, 20, 65, + 85, 9, 11, 65, 0, 68, 68, 64, 80, 78, 91, 5, + 65, 68, 4, 72, 66, 81, 0, 74, 72, 80, 5, 2, + 22, 0, 0, 0, 5, 97, 97, 11, 76, 70, 13, 70, + 85, 44, 22, 14, 54, 62, 27, 30, 19, 30, 11, + 11, 35, 80, 75, 74, 81, 24, 65, 21, 33, 67, + 71, 66, 22, 0, 75, 74, 83, 19, 68, 9, 0, 67, + 8, 64, 10, 9, 9, 17, 24, 13, 6, 11, 62, 62, + 62, 62, 8, 64, 3, 13, 64, 11, 13, 5, 67, 38, + 66, 108, 104, 57, 9, 16, 5, 6, 11, 18, 48, 28, + 12, 65, 1, 2, 19, 24, 104, 3, 5, 80, 65, 74, + 7, 7, 71, 19, 12, 12, 90, 8, 79, 75, 5, 89, 7, + 8, 5, 79, 100, 64, 77, 74, 79, 93, 89, 76, + 126, 62, 126, 86, 89, 96, 111, 98, 109, 126, + 121, 116, 95, 122, 89, 62, 110, 65, 0, 71, 76, + 77, 75, 80, 79, 80, 95, 96, 92, 95, 96, 120, + 75, 77, 99, 73, 80, 81, 87, 91, 97, 94, 100, + 98, 85, 100, 103, 94, 88, 15, 34, 22, 10, 11, + 17, 8, 4, 4, 30, 20, 33, 24, 22, 19, 28, 19, + 25, 21, 18, 18, 47, 39, 34, 30, 35, 14, 5, 3, + 2, 71, 54, 29, 2, 65, 14, 2, 73, 67, 3, 40, + 28, 9, 0, 18, 5, 4, 67, 67, 62, 68, 3, 8, 9, + 8, 5, 17, 21, 8, 18, 22, 32, 67, 2, 73, 64, + 26, 72, 74, 71, 3, 4, 69, 77, 69, 80, 69, 71, + 88, 14, 13, 30, 5, 66, 13, 8, 1, 7, 6, 1, 75, + 0, 65, 69, 67, 16, 98, 81, 16, 84, 67, 74, 68, + 10, 74, 76, 4, 5, 92, 74, 99, 17, 20, 25, 4, + 69, 65, 68, 73, 77, 85, 87, 87, 100, 95, 97, + 118, 110, 117, 126, 109, 108, 102, 99, 109, + 96, 103, 100, 88, 95, 68, 71, 73, 74, 93, 85, + 86, 105, 92, 100, 101, 121, 105, 109, 112, 81, + 79, 116, 82, 85, 95, 110, 99, 106, 108, 91, + 107, 99, 103, 111, 109, 113, 100, 106, 111, + 71, 64, 14, 2, 30, 40, 0, 8, 17, 21, 5, 19, + 36, 4, 64, 27, 75, 106, 126, 126, 126, 126, + 126, 126, 6, 39, 30, 27, 17, 31, 17, 9, 7, 1, + 72, 67, 11, 2, 24, 34, 2, 4, 11, 15, 6, 13, + 28, 7, 0, 27, 75, 106, 126, 126, 126, 126, + 126, 126 }, + + { + + 22, + 4, 82, 22, 4, 82, 15, 24, 27, 12, 70, 86, 95, + 18, 60, 11, 27, 70, 9, 21, 66, 75, 2, 82, 107, + 75, 112, 126, 126, 126, 62, 5, 64, 9, 21, 66, + 85, 10, 11, 65, 0, 67, 67, 64, 80, 78, 91, 5, + 65, 68, 4, 72, 66, 80, 0, 74, 72, 80, 6, 2, + 22, 0, 0, 0, 6, 97, 97, 11, 77, 70, 12, 70, + 85, 46, 23, 15, 56, 62, 29, 31, 20, 32, 13, + 12, 38, 80, 74, 74, 80, 24, 65, 22, 35, 67, + 70, 65, 24, 0, 75, 74, 83, 19, 68, 9, 1, 66, + 9, 0, 11, 10, 10, 18, 26, 14, 8, 12, 62, 62, + 62, 62, 9, 0, 4, 14, 64, 11, 13, 5, 66, 40, + 66, 110, 106, 60, 9, 17, 5, 6, 11, 19, 50, 29, + 13, 66, 1, 2, 20, 24, 105, 4, 5, 80, 66, 75, + 6, 6, 72, 19, 12, 12, 91, 9, 80, 75, 4, 89, 7, + 7, 4, 81, 103, 65, 78, 76, 81, 95, 91, 78, + 126, 62, 126, 88, 91, 99, 114, 100, 112, 126, + 125, 118, 97, 125, 91, 62, 111, 67, 65, 73, + 78, 79, 77, 82, 81, 82, 98, 98, 94, 96, 98, + 122, 75, 77, 99, 74, 81, 82, 89, 92, 98, 95, + 101, 99, 85, 101, 103, 95, 87, 16, 35, 22, 10, + 12, 17, 9, 5, 5, 32, 22, 33, 25, 22, 19, 29, + 20, 27, 23, 20, 18, 48, 40, 35, 31, 36, 14, 6, + 4, 3, 71, 55, 29, 2, 66, 14, 2, 73, 66, 3, 40, + 27, 8, 64, 18, 5, 5, 67, 66, 62, 66, 4, 10, + 11, 10, 6, 18, 22, 9, 20, 23, 34, 66, 3, 72, + 0, 28, 72, 74, 70, 4, 5, 69, 78, 69, 81, 69, + 71, 88, 14, 13, 31, 5, 66, 13, 8, 1, 7, 6, 1, + 76, 0, 65, 69, 67, 17, 99, 82, 16, 85, 67, 74, + 69, 10, 74, 77, 4, 5, 93, 74, 100, 16, 19, 24, + 2, 71, 67, 70, 76, 79, 88, 90, 90, 103, 98, + 99, 123, 114, 121, 126, 112, 111, 105, 101, + 111, 98, 104, 101, 88, 95, 71, 73, 75, 76, 96, + 88, 88, 108, 94, 102, 103, 124, 107, 111, 114, + 81, 79, 118, 83, 86, 96, 112, 101, 108, 109, + 92, 109, 100, 103, 113, 110, 113, 102, 108, + 113, 70, 64, 15, 2, 31, 41, 1, 9, 18, 22, 5, + 20, 37, 4, 64, 26, 77, 109, 126, 126, 126, + 126, 126, 126, 7, 39, 30, 27, 17, 32, 17, 10, + 8, 1, 72, 67, 11, 2, 25, 35, 2, 4, 12, 16, 6, + 14, 29, 7, 0, 26, 77, 109, 126, 126, 126, 126, + 126, 126 }, + + { + + 21, + 4, 82, 21, 4, 82, 17, 26, 28, 12, 71, 88, 97, + 17, 60, 11, 29, 70, 10, 22, 66, 75, 2, 82, + 108, 76, 115, 126, 126, 126, 62, 6, 64, 10, + 22, 66, 86, 11, 11, 66, 0, 67, 67, 64, 80, 78, + 91, 5, 65, 67, 4, 72, 65, 80, 0, 74, 72, 80, + 6, 2, 22, 0, 0, 0, 6, 97, 97, 12, 78, 70, 11, + 71, 85, 49, 24, 16, 57, 62, 30, 33, 22, 33, + 14, 13, 40, 80, 74, 73, 80, 24, 65, 23, 37, + 67, 69, 65, 26, 0, 76, 75, 84, 19, 68, 10, 1, + 65, 10, 0, 12, 10, 11, 20, 27, 14, 9, 13, 62, + 62, 62, 62, 9, 0, 4, 14, 64, 11, 14, 6, 66, + 42, 66, 112, 108, 62, 9, 17, 5, 6, 11, 19, 51, + 30, 13, 66, 1, 2, 20, 25, 106, 4, 5, 81, 66, + 76, 6, 6, 72, 19, 12, 12, 92, 9, 81, 76, 4, + 89, 7, 7, 4, 83, 106, 66, 80, 78, 84, 97, 94, + 80, 126, 62, 126, 90, 93, 101, 117, 103, 115, + 126, 126, 121, 99, 126, 93, 62, 113, 69, 67, + 75, 81, 82, 79, 84, 83, 84, 100, 100, 96, 98, + 99, 124, 75, 77, 100, 75, 82, 83, 90, 93, 99, + 96, 103, 100, 86, 102, 104, 95, 87, 18, 36, + 23, 10, 12, 18, 9, 5, 5, 34, 23, 33, 25, 23, + 20, 30, 21, 28, 24, 22, 19, 48, 40, 35, 31, + 37, 15, 6, 4, 3, 71, 55, 29, 1, 66, 14, 2, 73, + 66, 3, 39, 27, 7, 65, 18, 5, 5, 67, 66, 62, + 65, 6, 11, 12, 11, 7, 20, 24, 10, 21, 25, 36, + 66, 4, 72, 1, 30, 71, 74, 70, 4, 5, 69, 78, + 69, 81, 69, 71, 89, 15, 14, 32, 5, 66, 14, 8, + 1, 7, 6, 1, 76, 0, 65, 69, 67, 17, 100, 83, + 16, 86, 68, 75, 69, 10, 75, 78, 4, 5, 94, 74, + 101, 15, 18, 23, 1, 73, 69, 72, 78, 82, 90, + 93, 93, 106, 100, 101, 126, 118, 125, 126, + 116, 114, 107, 103, 114, 100, 106, 103, 89, + 96, 73, 76, 78, 79, 99, 90, 91, 111, 97, 105, + 106, 126, 109, 113, 115, 82, 80, 119, 85, 88, + 98, 114, 102, 110, 111, 93, 110, 101, 104, + 114, 111, 114, 103, 109, 114, 70, 0, 15, 3, + 32, 42, 1, 9, 18, 22, 5, 20, 38, 4, 64, 25, + 79, 111, 126, 126, 126, 126, 126, 126, 7, 39, + 30, 27, 17, 32, 18, 10, 8, 2, 72, 67, 12, 3, + 26, 36, 2, 5, 12, 16, 6, 14, 29, 7, 0, 25, 79, + 111, 126, 126, 126, 126, 126, 126 }, + + { + + 20, + 4, 82, 20, 4, 82, 19, 27, 28, 12, 71, 89, 99, + 16, 60, 11, 31, 70, 10, 23, 66, 76, 2, 83, + 110, 77, 117, 126, 126, 126, 62, 7, 64, 10, + 23, 66, 86, 12, 11, 66, 0, 67, 66, 64, 80, 78, + 91, 5, 65, 67, 4, 72, 65, 80, 0, 74, 72, 80, + 6, 2, 22, 0, 0, 0, 7, 97, 97, 12, 79, 70, 10, + 71, 85, 51, 25, 17, 59, 62, 32, 34, 24, 35, + 15, 14, 42, 80, 74, 73, 80, 24, 65, 24, 39, + 67, 68, 64, 28, 0, 76, 75, 84, 19, 68, 10, 2, + 64, 11, 1, 13, 11, 12, 21, 29, 15, 10, 14, 62, + 62, 62, 62, 9, 0, 4, 15, 64, 11, 14, 6, 65, + 44, 66, 114, 110, 62, 9, 17, 5, 6, 11, 20, 53, + 31, 13, 66, 1, 2, 21, 26, 107, 4, 5, 81, 67, + 77, 5, 6, 73, 19, 12, 12, 93, 9, 82, 77, 3, + 89, 7, 6, 3, 85, 109, 67, 82, 80, 86, 99, 96, + 82, 126, 62, 126, 92, 95, 103, 120, 105, 118, + 126, 126, 124, 101, 126, 95, 62, 115, 71, 69, + 77, 83, 84, 81, 86, 85, 86, 102, 102, 98, 100, + 101, 126, 75, 77, 100, 76, 83, 84, 91, 94, + 100, 97, 105, 101, 86, 103, 105, 96, 86, 19, + 37, 23, 10, 12, 19, 10, 6, 6, 36, 24, 33, 25, + 23, 21, 31, 22, 30, 26, 24, 20, 49, 41, 36, + 32, 38, 15, 6, 4, 4, 71, 56, 29, 0, 66, 14, 2, + 73, 65, 3, 39, 26, 6, 66, 18, 5, 5, 67, 66, + 62, 64, 7, 12, 14, 13, 8, 22, 26, 11, 23, 27, + 38, 65, 5, 71, 2, 32, 71, 74, 70, 5, 6, 69, + 78, 69, 82, 69, 71, 89, 15, 14, 33, 5, 66, 14, + 8, 1, 7, 6, 1, 77, 0, 65, 69, 67, 18, 101, 84, + 16, 87, 69, 76, 70, 10, 76, 79, 4, 5, 95, 74, + 102, 14, 17, 22, 64, 75, 71, 74, 80, 84, 93, + 96, 96, 109, 103, 103, 126, 122, 126, 126, + 119, 117, 110, 105, 116, 102, 108, 104, 90, + 97, 75, 78, 80, 81, 102, 93, 93, 114, 99, 107, + 108, 126, 111, 115, 117, 83, 81, 121, 86, 89, + 100, 116, 104, 112, 113, 94, 112, 102, 105, + 115, 112, 115, 105, 110, 115, 70, 0, 16, 3, + 33, 43, 1, 10, 19, 23, 5, 21, 39, 4, 64, 24, + 81, 114, 126, 126, 126, 126, 126, 126, 7, 39, + 30, 27, 17, 33, 18, 10, 8, 2, 72, 67, 12, 3, + 27, 37, 2, 5, 12, 16, 6, 14, 30, 7, 0, 24, 81, + 114, 126, 126, 126, 126, 126, 126 }, + + { + + 18, + 3, 83, 18, 3, 83, 20, 28, 28, 12, 72, 91, 102, + 15, 60, 10, 32, 71, 10, 23, 67, 77, 2, 84, + 112, 79, 120, 126, 126, 126, 62, 7, 64, 10, + 23, 67, 87, 12, 11, 67, 0, 67, 66, 65, 81, 78, + 91, 4, 65, 67, 4, 72, 65, 80, 0, 74, 73, 80, + 6, 2, 22, 0, 0, 0, 7, 98, 97, 12, 80, 71, 9, + 72, 86, 53, 26, 18, 60, 62, 33, 35, 25, 36, + 16, 15, 44, 80, 74, 73, 80, 24, 65, 24, 41, + 67, 68, 64, 29, 64, 77, 76, 85, 19, 68, 10, 2, + 64, 11, 1, 13, 11, 12, 22, 30, 15, 11, 15, 62, + 62, 62, 62, 9, 0, 4, 15, 65, 11, 14, 6, 65, + 46, 66, 116, 112, 62, 9, 17, 5, 6, 11, 20, 54, + 31, 13, 67, 0, 2, 21, 26, 109, 4, 5, 82, 68, + 79, 4, 5, 74, 19, 12, 11, 95, 9, 83, 78, 2, + 89, 6, 5, 2, 88, 113, 69, 84, 83, 89, 102, 99, + 84, 126, 62, 126, 95, 97, 106, 124, 108, 122, + 126, 126, 126, 103, 126, 97, 62, 117, 74, 72, + 80, 86, 87, 84, 89, 88, 88, 105, 105, 100, + 102, 103, 126, 75, 78, 101, 77, 85, 86, 93, + 96, 102, 99, 107, 102, 87, 104, 106, 97, 86, + 20, 37, 23, 10, 12, 19, 10, 6, 6, 38, 25, 33, + 25, 23, 21, 31, 23, 31, 27, 25, 20, 49, 41, + 36, 32, 39, 15, 6, 4, 4, 72, 56, 28, 64, 67, + 14, 2, 73, 65, 2, 38, 25, 4, 67, 17, 5, 5, 67, + 66, 62, 0, 8, 13, 15, 14, 9, 23, 27, 12, 24, + 28, 40, 65, 5, 71, 3, 33, 71, 74, 70, 5, 6, + 69, 79, 70, 83, 69, 72, 90, 15, 14, 34, 5, 67, + 14, 8, 0, 7, 6, 1, 78, 0, 65, 69, 67, 18, 102, + 85, 16, 89, 70, 77, 71, 9, 77, 80, 4, 4, 97, + 75, 104, 12, 15, 21, 66, 77, 74, 77, 83, 87, + 96, 99, 99, 113, 106, 105, 126, 126, 126, 126, + 123, 121, 113, 108, 119, 104, 110, 106, 91, + 98, 78, 81, 83, 84, 105, 96, 96, 118, 102, + 110, 111, 126, 113, 117, 119, 84, 82, 123, 88, + 91, 102, 119, 106, 114, 115, 96, 114, 104, + 106, 117, 113, 116, 107, 112, 117, 70, 0, 16, + 3, 34, 44, 1, 10, 19, 23, 5, 21, 39, 4, 65, + 22, 83, 117, 126, 126, 126, 126, 126, 126, 7, + 39, 30, 27, 17, 33, 18, 10, 8, 2, 72, 67, 12, + 3, 27, 37, 2, 5, 12, 16, 6, 14, 30, 6, 64, 22, + 83, 117, 126, 126, 126, 126, 126, 126 }, + + { + + 17, + 3, 83, 17, 3, 83, 22, 30, 29, 13, 72, 92, 104, + 14, 61, 10, 34, 71, 11, 24, 67, 77, 3, 84, + 113, 80, 122, 126, 126, 126, 62, 8, 0, 11, 24, + 67, 87, 13, 11, 67, 1, 66, 65, 65, 81, 77, 90, + 4, 64, 66, 4, 71, 64, 79, 1, 73, 73, 79, 7, 2, + 22, 0, 0, 0, 8, 98, 97, 13, 80, 71, 9, 72, 86, + 56, 28, 20, 62, 62, 35, 37, 27, 38, 18, 17, + 47, 80, 73, 72, 79, 24, 65, 25, 44, 66, 67, 0, + 31, 64, 77, 76, 85, 20, 68, 11, 3, 0, 12, 2, + 14, 12, 13, 24, 32, 16, 13, 17, 62, 62, 62, + 62, 10, 1, 5, 16, 65, 12, 15, 7, 64, 49, 65, + 118, 113, 62, 9, 18, 5, 7, 12, 21, 56, 32, 14, + 67, 0, 2, 22, 27, 110, 5, 5, 82, 68, 80, 4, 5, + 74, 19, 12, 11, 96, 10, 83, 78, 2, 89, 6, 5, + 2, 90, 116, 70, 85, 85, 91, 104, 101, 86, 126, + 62, 126, 97, 98, 108, 126, 110, 125, 126, 126, + 126, 105, 126, 99, 62, 118, 76, 74, 82, 88, + 89, 86, 91, 90, 90, 107, 107, 101, 103, 104, + 126, 75, 78, 101, 77, 86, 87, 94, 97, 103, + 100, 108, 103, 87, 105, 106, 97, 85, 22, 38, + 24, 10, 13, 20, 11, 7, 7, 41, 27, 34, 26, 24, + 22, 32, 25, 33, 29, 27, 21, 50, 42, 37, 33, + 40, 16, 7, 5, 5, 72, 57, 28, 64, 67, 15, 3, + 73, 64, 2, 38, 25, 3, 68, 17, 6, 6, 66, 65, + 62, 2, 10, 15, 17, 16, 11, 25, 29, 14, 26, 30, + 43, 64, 6, 70, 5, 35, 70, 73, 69, 6, 7, 68, + 79, 70, 83, 69, 72, 90, 16, 15, 35, 6, 67, 15, + 9, 0, 8, 7, 1, 78, 1, 64, 68, 66, 19, 102, 86, + 16, 90, 70, 77, 71, 9, 77, 80, 4, 4, 98, 75, + 105, 11, 14, 21, 67, 78, 76, 79, 85, 89, 98, + 101, 101, 116, 108, 107, 126, 126, 126, 126, + 126, 124, 115, 110, 121, 105, 111, 107, 91, + 98, 80, 83, 85, 86, 107, 98, 98, 121, 104, + 112, 113, 126, 114, 118, 120, 84, 82, 124, 89, + 92, 103, 121, 107, 115, 116, 97, 115, 105, + 106, 118, 113, 116, 108, 113, 118, 69, 1, 17, + 4, 36, 46, 2, 11, 20, 24, 6, 22, 40, 4, 65, + 21, 85, 119, 126, 126, 126, 126, 126, 126, 8, + 39, 31, 28, 18, 34, 19, 11, 9, 3, 71, 66, 13, + 4, 28, 38, 3, 6, 13, 17, 6, 15, 31, 6, 64, 21, + 85, 119, 126, 126, 126, 126, 126, 126 }, + + { + + 16, + 3, 83, 16, 3, 83, 24, 31, 29, 13, 72, 93, 106, + 13, 61, 10, 36, 71, 11, 25, 67, 78, 3, 85, + 115, 81, 125, 126, 126, 126, 62, 9, 0, 11, 25, + 67, 87, 14, 11, 68, 1, 66, 64, 65, 81, 77, 90, + 4, 64, 66, 4, 71, 64, 79, 1, 73, 73, 79, 7, 2, + 22, 0, 0, 0, 9, 98, 97, 13, 81, 71, 8, 72, 86, + 58, 29, 21, 62, 62, 36, 38, 29, 39, 19, 18, + 49, 80, 73, 72, 79, 24, 65, 26, 46, 66, 66, 1, + 33, 64, 77, 76, 86, 20, 68, 11, 4, 1, 13, 3, + 15, 12, 14, 25, 33, 17, 14, 18, 62, 62, 62, + 62, 10, 1, 5, 16, 65, 12, 15, 7, 64, 51, 65, + 120, 115, 62, 9, 18, 5, 7, 12, 21, 58, 33, 14, + 67, 0, 2, 23, 28, 111, 5, 5, 82, 69, 81, 3, 5, + 75, 19, 12, 11, 97, 10, 84, 79, 1, 89, 6, 4, + 1, 92, 119, 71, 87, 87, 93, 106, 103, 88, 126, + 62, 126, 99, 100, 110, 126, 112, 126, 126, + 126, 126, 107, 126, 101, 62, 120, 78, 76, 84, + 90, 91, 88, 93, 92, 92, 109, 109, 103, 105, + 106, 126, 75, 78, 102, 78, 87, 88, 95, 98, + 104, 101, 110, 104, 87, 106, 107, 98, 84, 23, + 39, 24, 10, 13, 21, 12, 8, 8, 43, 28, 34, 26, + 24, 23, 33, 26, 35, 31, 29, 22, 51, 43, 38, + 34, 41, 16, 7, 5, 6, 72, 58, 28, 65, 67, 15, + 3, 73, 64, 2, 38, 24, 2, 69, 17, 6, 6, 66, 65, + 62, 3, 11, 16, 19, 18, 12, 27, 31, 15, 28, 32, + 45, 0, 7, 70, 6, 37, 70, 73, 69, 7, 7, 68, 79, + 70, 84, 69, 72, 90, 16, 15, 36, 6, 67, 15, 9, + 0, 8, 7, 1, 79, 1, 64, 68, 66, 20, 103, 87, + 16, 91, 71, 78, 72, 9, 78, 81, 4, 4, 99, 75, + 106, 10, 13, 20, 69, 80, 78, 81, 87, 91, 101, + 104, 104, 119, 111, 109, 126, 126, 126, 126, + 126, 126, 118, 112, 124, 107, 113, 108, 92, + 99, 82, 85, 88, 88, 110, 101, 101, 124, 106, + 115, 115, 126, 116, 120, 122, 85, 83, 126, 90, + 93, 105, 123, 109, 117, 118, 98, 117, 106, + 107, 119, 114, 117, 110, 114, 119, 69, 1, 18, + 4, 37, 47, 2, 12, 21, 25, 6, 22, 41, 4, 65, + 20, 87, 122, 126, 126, 126, 126, 126, 126, 8, + 39, 31, 28, 18, 35, 19, 11, 9, 3, 71, 66, 13, + 4, 29, 39, 3, 6, 13, 17, 6, 15, 32, 6, 64, 20, + 87, 122, 126, 126, 126, 126, 126, 126 }, + + { + + 15, + 3, 83, 15, 3, 83, 26, 33, 30, 13, 73, 95, 108, + 12, 61, 10, 38, 71, 12, 26, 67, 78, 3, 85, + 116, 82, 126, 126, 126, 126, 62, 10, 0, 12, + 26, 67, 88, 15, 11, 68, 1, 66, 64, 65, 81, 77, + 90, 4, 64, 65, 4, 71, 0, 79, 1, 73, 73, 79, 7, + 2, 22, 0, 0, 0, 9, 98, 97, 14, 82, 71, 7, 73, + 86, 61, 30, 22, 62, 62, 38, 40, 31, 41, 20, + 19, 51, 80, 73, 71, 79, 24, 65, 27, 48, 66, + 65, 1, 35, 64, 78, 77, 86, 20, 68, 12, 4, 2, + 14, 3, 16, 13, 15, 27, 35, 17, 15, 19, 62, 62, + 62, 62, 10, 1, 5, 17, 65, 12, 16, 8, 0, 53, + 65, 122, 117, 62, 9, 18, 5, 7, 12, 22, 59, 34, + 14, 67, 0, 2, 23, 29, 112, 5, 5, 83, 69, 82, + 3, 5, 75, 19, 12, 11, 98, 10, 85, 80, 1, 89, + 6, 4, 1, 94, 122, 72, 89, 89, 96, 108, 106, + 90, 126, 62, 126, 101, 102, 112, 126, 115, + 126, 126, 126, 126, 109, 126, 103, 62, 122, + 80, 78, 86, 93, 94, 90, 95, 94, 94, 111, 111, + 105, 107, 107, 126, 75, 78, 102, 79, 88, 89, + 96, 99, 105, 102, 112, 105, 88, 107, 108, 98, + 84, 25, 40, 25, 10, 13, 22, 12, 8, 8, 45, 29, + 34, 26, 25, 24, 34, 27, 36, 32, 31, 23, 51, + 43, 38, 34, 42, 17, 7, 5, 6, 72, 58, 28, 66, + 67, 15, 3, 73, 0, 2, 37, 24, 1, 70, 17, 6, 6, + 66, 65, 62, 4, 13, 17, 20, 19, 13, 29, 33, 16, + 29, 34, 47, 0, 8, 69, 7, 39, 69, 73, 69, 7, 8, + 68, 79, 70, 84, 69, 72, 91, 17, 16, 37, 6, 67, + 16, 9, 0, 8, 7, 1, 79, 1, 64, 68, 66, 20, 104, + 88, 16, 92, 72, 79, 72, 9, 79, 82, 4, 4, 100, + 75, 107, 9, 12, 19, 70, 82, 80, 83, 89, 94, + 103, 107, 107, 122, 113, 111, 126, 126, 126, + 126, 126, 126, 120, 114, 126, 109, 115, 110, + 93, 100, 84, 88, 90, 91, 113, 103, 103, 126, + 109, 117, 118, 126, 118, 122, 123, 86, 84, + 126, 92, 95, 107, 125, 110, 119, 120, 99, 118, + 107, 108, 120, 115, 118, 111, 115, 120, 69, 2, + 18, 5, 38, 48, 2, 12, 21, 25, 6, 23, 42, 4, + 65, 19, 89, 124, 126, 126, 126, 126, 126, 126, + 8, 39, 31, 28, 18, 35, 20, 11, 9, 4, 71, 66, + 14, 5, 30, 40, 3, 7, 13, 17, 6, 15, 32, 6, 64, + 19, 89, 124, 126, 126, 126, 126, 126, 126 }, + + }, + + { + + { + + 62, + 9, 74, 62, 9, 74, 126, 104, 10, 9, 12, 47, 62, + 62, 12, 1, 99, 47, 85, 102, 6, 6, 73, 6, 23, 53, + 62, 62, 21, 97, 126, 117, 74, 85, 102, 6, 93, + 88, 19, 8, 89, 103, 116, 6, 5, 84, 96, 0, 85, + 106, 0, 75, 90, 101, 8, 79, 75, 97, 13, 3, 22, + 0, 0, 0, 83, 86, 97, 72, 22, 1, 29, 88, 126, + 126, 91, 95, 84, 86, 89, 91, 126, 76, 103, 90, + 126, 80, 76, 84, 78, 8, 2, 83, 126, 79, 104, 91, + 126, 65, 79, 72, 92, 7, 68, 71, 98, 86, 88, 82, + 72, 67, 72, 89, 69, 4, 66, 6, 71, 71, 5, 74, 19, + 69, 1, 12, 16, 21, 22, 10, 76, 78, 83, 11, 67, + 90, 67, 72, 75, 80, 83, 64, 32, 64, 94, 75, 0, + 74, 28, 36, 91, 65, 69, 77, 66, 1, 68, 81, 33, + 56, 40, 74, 66, 124, 26, 62, 62, 126, 24, 21, + 29, 34, 32, 26, 21, 23, 30, 20, 27, 16, 8, 5, 3, + 19, 19, 21, 15, 7, 11, 26, 14, 5, 15, 18, 69, + 30, 0, 62, 62, 62, 53, 62, 62, 62, 62, 46, 38, + 34, 30, 48, 43, 73, 29, 32, 19, 47, 27, 27, 35, + 42, 43, 51, 47, 21, 93, 7, 6, 25, 126, 115, 82, + 1, 10, 4, 85, 89, 94, 92, 126, 100, 6, 67, 71, + 77, 85, 88, 104, 98, 126, 82, 15, 2, 66, 70, 75, + 79, 83, 92, 108, 79, 69, 75, 5, 5, 78, 83, 81, + 99, 81, 25, 1, 5, 4, 73, 76, 86, 83, 87, 62, + 126, 126, 120, 126, 114, 117, 118, 117, 113, + 118, 120, 124, 94, 102, 99, 106, 126, 92, 6, 86, + 94, 91, 77, 71, 73, 64, 81, 64, 6, 67, 68, 67, + 68, 77, 64, 68, 78, 8, 4, 65, 9, 19, 3, 70, 76, + 86, 70, 64, 70, 8, 7, 69, 65, 74, 9, 9, 76, 82, + 77, 77, 21, 62, 62, 62, 62, 62, 62, 62, 62, 62, + 62, 62, 62, 62, 62, 52, 62, 62, 62, 62, 62, 62, + 48, 62, 62, 46, 25, 18, 9, 79, 62, 62, 62, 62, + 48, 48, 38, 41, 47, 45, 35, 22, 35, 16, 1, 32, + 37, 39, 40, 47, 33, 34, 22, 21, 3, 11, 3, 78, + 123, 10, 7, 2, 30, 13, 2, 78, 74, 72, 72, 75, + 71, 0, 70, 75, 72, 67, 10, 4, 11, 68, 62, 62, + 62, 62, 56, 51, 40, 25, 64, 71, 26, 19, 14, 7, + 4, 0, 67, 68, 79, 78, 74, 72, 72, 75, 71, 0, 70, + 75, 72, 67, 10, 4, 11, 68, 62, 62, 62, 62, 56, + 51, 40, 25, 64 }, + + { + + 62, + 9, 74, 62, 9, 74, 125, 102, 11, 10, 12, 46, + 62, 62, 13, 2, 97, 46, 84, 100, 6, 6, 71, 6, + 22, 52, 62, 60, 19, 97, 125, 115, 73, 84, 100, + 6, 92, 87, 20, 8, 88, 102, 114, 5, 4, 84, 96, + 0, 84, 105, 0, 75, 89, 100, 8, 78, 74, 96, 14, + 3, 22, 0, 0, 0, 82, 86, 97, 71, 22, 1, 29, 87, + 125, 124, 89, 94, 82, 84, 88, 89, 125, 75, + 101, 89, 124, 80, 76, 84, 78, 9, 2, 82, 124, + 78, 103, 90, 125, 65, 78, 72, 91, 8, 68, 70, + 97, 85, 87, 81, 71, 66, 71, 88, 68, 5, 66, 6, + 70, 70, 5, 73, 20, 68, 1, 13, 17, 22, 23, 11, + 76, 77, 82, 11, 67, 89, 67, 71, 74, 79, 81, 1, + 33, 1, 92, 75, 64, 73, 29, 37, 91, 65, 68, 77, + 65, 1, 67, 79, 33, 56, 41, 72, 67, 122, 25, + 62, 62, 125, 24, 21, 29, 34, 32, 26, 21, 23, + 30, 20, 27, 16, 8, 5, 3, 19, 19, 21, 15, 7, + 11, 26, 14, 4, 15, 18, 69, 29, 0, 62, 62, 62, + 52, 62, 62, 62, 62, 45, 37, 32, 29, 46, 42, + 74, 28, 31, 18, 46, 27, 27, 34, 41, 42, 50, + 46, 20, 93, 7, 6, 24, 125, 113, 80, 2, 10, 4, + 84, 88, 93, 91, 125, 98, 7, 66, 70, 76, 83, + 87, 102, 97, 124, 81, 16, 3, 65, 69, 74, 78, + 82, 91, 106, 78, 67, 74, 6, 5, 77, 82, 80, 98, + 80, 26, 2, 6, 5, 72, 75, 85, 82, 86, 62, 125, + 125, 118, 125, 112, 115, 116, 115, 111, 116, + 118, 121, 93, 101, 98, 105, 123, 91, 5, 85, + 93, 90, 76, 71, 72, 64, 80, 64, 6, 67, 68, 66, + 68, 77, 64, 68, 77, 8, 4, 65, 9, 19, 3, 70, + 75, 84, 70, 64, 69, 8, 7, 69, 65, 73, 9, 9, + 75, 81, 76, 76, 20, 62, 62, 62, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 50, 62, 62, + 62, 62, 62, 62, 47, 60, 60, 45, 24, 17, 9, 79, + 62, 62, 62, 60, 46, 47, 37, 39, 46, 43, 34, + 20, 33, 15, 0, 31, 36, 37, 39, 46, 32, 33, 21, + 20, 2, 11, 3, 78, 122, 9, 6, 1, 29, 12, 1, 77, + 73, 71, 71, 73, 70, 1, 69, 73, 71, 66, 11, 5, + 12, 67, 62, 62, 62, 62, 54, 50, 38, 24, 65, + 70, 27, 20, 15, 8, 5, 1, 66, 67, 78, 77, 73, + 71, 71, 73, 70, 1, 69, 73, 71, 66, 11, 5, 12, + 67, 62, 62, 62, 62, 54, 50, 38, 24, 65 }, + + { + + 62, + 9, 74, 62, 9, 74, 123, 101, 11, 10, 12, 44, + 60, 62, 14, 2, 95, 44, 84, 99, 6, 6, 70, 5, + 21, 51, 60, 57, 17, 98, 123, 114, 73, 84, 99, + 6, 92, 86, 20, 8, 87, 101, 113, 4, 3, 84, 96, + 0, 84, 104, 0, 75, 89, 100, 8, 78, 74, 95, 14, + 3, 22, 0, 0, 0, 81, 86, 97, 71, 21, 1, 29, 86, + 124, 122, 88, 93, 80, 82, 87, 88, 123, 74, + 100, 88, 122, 81, 76, 84, 78, 9, 2, 81, 122, + 78, 102, 89, 123, 65, 78, 72, 91, 8, 68, 70, + 96, 85, 86, 81, 71, 66, 71, 87, 67, 5, 66, 6, + 70, 70, 5, 73, 20, 68, 1, 13, 17, 22, 23, 11, + 77, 76, 81, 10, 67, 89, 67, 70, 74, 79, 80, 2, + 34, 3, 90, 76, 65, 73, 29, 37, 92, 65, 68, 78, + 64, 1, 67, 78, 33, 56, 41, 71, 68, 121, 24, + 62, 62, 124, 24, 21, 29, 33, 31, 26, 21, 23, + 29, 19, 26, 16, 8, 5, 3, 18, 18, 20, 15, 7, + 11, 25, 13, 3, 14, 17, 69, 28, 64, 62, 62, 62, + 50, 60, 62, 62, 62, 44, 35, 30, 27, 44, 40, + 75, 27, 30, 16, 45, 26, 26, 33, 39, 40, 48, + 44, 18, 93, 6, 5, 22, 124, 112, 79, 3, 10, 4, + 83, 87, 92, 90, 123, 97, 8, 65, 69, 75, 82, + 86, 101, 96, 122, 80, 16, 3, 65, 69, 73, 77, + 81, 90, 105, 78, 66, 73, 6, 5, 76, 81, 80, 97, + 79, 26, 3, 6, 5, 71, 74, 84, 81, 85, 62, 124, + 123, 116, 123, 111, 114, 114, 113, 110, 114, + 116, 119, 92, 100, 97, 104, 120, 91, 4, 85, + 92, 89, 76, 71, 72, 64, 80, 64, 5, 67, 68, 65, + 68, 77, 64, 68, 77, 8, 4, 65, 8, 18, 3, 70, + 75, 83, 71, 64, 68, 7, 7, 69, 65, 73, 9, 9, + 75, 80, 76, 76, 18, 62, 62, 62, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 48, 62, 62, + 62, 62, 62, 61, 45, 58, 58, 43, 23, 16, 8, 79, + 62, 62, 62, 58, 44, 45, 35, 37, 44, 41, 32, + 18, 31, 13, 64, 30, 35, 35, 37, 44, 30, 31, + 20, 19, 1, 10, 2, 78, 121, 8, 5, 64, 28, 11, + 0, 77, 73, 70, 70, 72, 69, 2, 69, 72, 70, 65, + 11, 6, 13, 66, 62, 62, 62, 60, 52, 48, 36, 22, + 66, 69, 27, 20, 16, 9, 6, 1, 65, 67, 77, 77, + 73, 70, 70, 72, 69, 2, 69, 72, 70, 65, 11, 6, + 13, 66, 62, 62, 62, 60, 52, 48, 36, 22, 66 }, + + { + + 62, + 9, 74, 62, 9, 74, 121, 99, 12, 10, 11, 42, 59, + 61, 14, 2, 93, 43, 84, 97, 6, 5, 69, 4, 20, + 50, 58, 53, 15, 99, 121, 112, 73, 84, 97, 6, + 91, 85, 21, 8, 86, 100, 112, 3, 2, 84, 97, 0, + 84, 103, 0, 76, 89, 100, 8, 78, 74, 94, 15, 3, + 22, 0, 0, 0, 81, 86, 97, 70, 20, 1, 28, 86, + 123, 120, 87, 92, 79, 81, 86, 87, 121, 73, 99, + 87, 120, 82, 76, 84, 78, 10, 2, 80, 120, 78, + 101, 88, 121, 65, 78, 72, 91, 9, 68, 69, 95, + 85, 85, 81, 71, 66, 70, 86, 67, 5, 66, 6, 70, + 70, 5, 73, 20, 68, 1, 14, 17, 23, 23, 12, 77, + 76, 80, 10, 67, 89, 67, 69, 74, 78, 79, 3, 35, + 4, 88, 76, 66, 72, 29, 37, 93, 65, 67, 78, 64, + 1, 67, 77, 33, 56, 41, 70, 69, 119, 23, 62, + 62, 122, 24, 21, 28, 32, 31, 25, 20, 23, 29, + 18, 25, 16, 8, 5, 2, 18, 17, 19, 14, 7, 11, + 24, 13, 2, 14, 16, 69, 27, 64, 62, 62, 61, 49, + 58, 62, 62, 62, 43, 33, 28, 26, 42, 38, 77, + 26, 29, 14, 44, 25, 25, 32, 38, 38, 46, 42, + 17, 93, 5, 4, 21, 122, 110, 77, 3, 10, 4, 82, + 86, 91, 89, 121, 96, 9, 64, 68, 75, 81, 85, + 99, 95, 120, 80, 17, 4, 64, 68, 72, 77, 81, + 89, 104, 78, 64, 72, 6, 5, 75, 81, 80, 96, 78, + 27, 4, 7, 5, 70, 74, 83, 81, 85, 62, 122, 122, + 115, 121, 110, 112, 113, 112, 108, 112, 114, + 117, 92, 99, 97, 103, 117, 91, 3, 85, 91, 88, + 76, 71, 72, 64, 79, 64, 4, 67, 68, 65, 68, 77, + 64, 68, 77, 7, 4, 65, 7, 17, 3, 70, 75, 82, + 72, 64, 67, 6, 7, 69, 65, 72, 9, 8, 74, 79, + 76, 76, 17, 62, 62, 62, 62, 62, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 46, 62, 62, 62, 62, + 62, 59, 43, 56, 55, 41, 22, 15, 7, 79, 62, 62, + 62, 56, 42, 43, 34, 35, 42, 39, 30, 16, 29, + 11, 65, 29, 34, 33, 36, 42, 29, 29, 18, 17, 0, + 9, 1, 78, 120, 7, 3, 65, 27, 10, 64, 77, 72, + 70, 70, 71, 68, 3, 69, 71, 69, 64, 12, 7, 13, + 65, 62, 62, 62, 58, 50, 46, 34, 20, 67, 69, + 28, 21, 17, 9, 7, 2, 65, 66, 77, 77, 72, 70, + 70, 71, 68, 3, 69, 71, 69, 64, 12, 7, 13, 65, + 62, 62, 62, 58, 50, 46, 34, 20, 67 }, + + { + + 62, + 9, 74, 62, 9, 74, 120, 98, 12, 10, 11, 40, 57, + 60, 15, 2, 92, 41, 84, 96, 5, 5, 68, 3, 18, + 48, 56, 50, 12, 100, 119, 111, 73, 84, 96, 5, + 91, 84, 21, 7, 86, 99, 110, 2, 0, 85, 97, 0, + 83, 102, 64, 76, 89, 100, 8, 78, 74, 94, 15, + 3, 22, 0, 0, 0, 80, 87, 97, 70, 19, 1, 28, 85, + 122, 118, 86, 91, 77, 79, 86, 86, 119, 72, 98, + 86, 117, 82, 77, 84, 79, 10, 1, 79, 117, 77, + 101, 88, 119, 65, 78, 72, 91, 9, 68, 69, 94, + 85, 85, 80, 71, 66, 70, 85, 66, 5, 67, 5, 70, + 70, 5, 73, 20, 68, 1, 14, 17, 23, 23, 12, 78, + 75, 80, 9, 67, 88, 67, 68, 73, 78, 77, 5, 36, + 6, 86, 77, 67, 72, 30, 37, 94, 65, 67, 79, 0, + 1, 67, 76, 33, 56, 41, 68, 70, 118, 22, 62, + 62, 121, 23, 21, 28, 32, 30, 25, 20, 23, 28, + 17, 24, 15, 8, 5, 2, 17, 17, 18, 14, 6, 10, + 23, 12, 1, 13, 15, 69, 25, 65, 62, 62, 59, 47, + 57, 62, 62, 62, 42, 31, 25, 24, 40, 36, 78, + 24, 28, 13, 43, 24, 24, 30, 36, 36, 44, 41, + 15, 93, 4, 3, 19, 121, 109, 76, 4, 10, 4, 81, + 85, 90, 89, 119, 94, 10, 64, 68, 74, 79, 84, + 98, 94, 117, 79, 17, 4, 64, 68, 71, 76, 80, + 89, 103, 78, 0, 71, 6, 5, 74, 80, 80, 95, 77, + 27, 5, 7, 5, 69, 73, 82, 80, 84, 62, 121, 120, + 113, 120, 109, 111, 111, 110, 107, 111, 112, + 114, 91, 98, 96, 102, 114, 90, 2, 84, 90, 88, + 76, 71, 72, 65, 79, 65, 3, 67, 68, 64, 68, 77, + 64, 68, 76, 7, 3, 65, 6, 16, 2, 70, 75, 81, + 73, 65, 67, 6, 6, 69, 65, 72, 8, 8, 74, 79, + 76, 76, 15, 62, 62, 62, 62, 62, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 44, 62, 62, 62, 62, + 62, 57, 41, 54, 53, 39, 20, 14, 6, 79, 62, 62, + 62, 54, 40, 41, 32, 33, 40, 37, 28, 14, 26, + 10, 67, 28, 33, 30, 34, 41, 27, 27, 17, 16, + 64, 8, 0, 78, 119, 5, 2, 67, 25, 9, 65, 77, + 72, 69, 69, 70, 68, 3, 68, 70, 68, 0, 12, 8, + 14, 65, 62, 62, 60, 56, 48, 44, 31, 18, 69, + 68, 28, 21, 17, 10, 7, 2, 64, 66, 76, 77, 72, + 69, 69, 70, 68, 3, 68, 70, 68, 0, 12, 8, 14, + 65, 62, 62, 60, 56, 48, 44, 31, 18, 69 }, + + { + + 62, + 9, 74, 62, 9, 74, 118, 96, 12, 10, 10, 38, 56, + 59, 16, 2, 90, 39, 83, 94, 5, 5, 67, 2, 17, + 47, 54, 47, 10, 100, 117, 110, 73, 83, 94, 5, + 91, 83, 21, 7, 85, 98, 109, 1, 64, 85, 97, 0, + 83, 101, 64, 76, 89, 100, 8, 77, 74, 93, 16, + 3, 22, 0, 0, 0, 80, 87, 97, 69, 18, 1, 27, 85, + 120, 115, 85, 90, 76, 78, 85, 85, 117, 71, 97, + 85, 115, 83, 77, 84, 79, 10, 1, 78, 115, 77, + 100, 87, 117, 65, 78, 72, 90, 9, 68, 68, 93, + 84, 84, 80, 71, 65, 69, 84, 66, 5, 67, 5, 69, + 70, 5, 73, 21, 68, 1, 15, 18, 23, 23, 12, 78, + 75, 79, 9, 67, 88, 67, 67, 73, 77, 76, 6, 37, + 7, 84, 77, 68, 71, 30, 37, 95, 65, 66, 79, 1, + 1, 67, 74, 33, 56, 41, 67, 71, 116, 21, 62, + 62, 120, 23, 21, 27, 31, 30, 25, 19, 23, 28, + 16, 23, 15, 8, 5, 2, 17, 16, 17, 13, 6, 10, + 22, 12, 0, 12, 15, 69, 24, 65, 62, 62, 58, 46, + 55, 62, 62, 62, 41, 29, 23, 23, 38, 34, 79, + 23, 27, 11, 42, 23, 23, 29, 35, 34, 42, 39, + 14, 93, 3, 2, 17, 119, 107, 75, 4, 10, 4, 80, + 84, 89, 88, 117, 93, 11, 0, 67, 73, 78, 83, + 96, 93, 115, 78, 18, 5, 0, 67, 70, 75, 80, 88, + 102, 77, 1, 70, 6, 5, 73, 80, 79, 94, 76, 27, + 6, 7, 5, 68, 72, 81, 80, 83, 62, 120, 119, + 112, 118, 108, 109, 110, 108, 105, 109, 110, + 112, 90, 97, 95, 101, 111, 90, 1, 84, 89, 87, + 76, 71, 72, 65, 78, 65, 2, 67, 68, 0, 68, 77, + 64, 68, 76, 6, 3, 65, 5, 15, 2, 70, 75, 80, + 73, 65, 66, 5, 6, 69, 65, 72, 8, 7, 74, 78, + 76, 76, 14, 62, 62, 62, 62, 62, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 42, 62, 62, 62, 62, + 62, 55, 40, 52, 50, 37, 19, 13, 5, 79, 62, 62, + 62, 52, 38, 39, 31, 31, 38, 35, 26, 12, 24, 8, + 68, 27, 32, 28, 33, 39, 26, 25, 16, 15, 65, 7, + 64, 78, 118, 4, 1, 68, 24, 8, 66, 77, 71, 69, + 68, 69, 67, 4, 68, 69, 67, 1, 13, 9, 14, 64, + 62, 62, 58, 54, 46, 42, 29, 16, 70, 68, 29, + 22, 18, 11, 8, 3, 64, 66, 75, 77, 71, 69, 68, + 69, 67, 4, 68, 69, 67, 1, 13, 9, 14, 64, 62, + 62, 58, 54, 46, 42, 29, 16, 70 }, + + { + + 62, + 9, 75, 62, 9, 75, 116, 95, 13, 10, 10, 37, 54, + 58, 16, 3, 88, 38, 83, 93, 5, 4, 66, 1, 16, + 46, 53, 43, 8, 101, 115, 108, 73, 83, 93, 5, + 90, 82, 22, 7, 84, 97, 108, 64, 65, 85, 98, 0, + 83, 101, 64, 77, 88, 100, 7, 77, 74, 92, 16, + 3, 22, 0, 0, 0, 79, 87, 97, 69, 18, 0, 27, 84, + 119, 113, 84, 89, 74, 76, 84, 84, 115, 70, 96, + 85, 113, 84, 77, 84, 79, 11, 1, 77, 113, 77, + 99, 86, 115, 65, 78, 72, 90, 10, 69, 68, 93, + 84, 83, 80, 70, 65, 69, 83, 65, 5, 67, 5, 69, + 70, 5, 73, 21, 68, 1, 15, 18, 24, 24, 13, 79, + 74, 78, 8, 67, 88, 67, 66, 73, 77, 75, 7, 37, + 9, 83, 78, 69, 71, 30, 37, 95, 66, 66, 80, 1, + 0, 66, 73, 33, 56, 42, 66, 72, 115, 20, 62, + 62, 118, 23, 21, 27, 30, 29, 24, 19, 22, 27, + 16, 23, 15, 7, 5, 1, 16, 15, 16, 13, 6, 10, + 22, 11, 65, 12, 14, 69, 23, 66, 62, 62, 56, + 44, 53, 62, 62, 62, 39, 27, 21, 21, 36, 32, + 81, 22, 25, 9, 40, 22, 22, 28, 33, 32, 40, 37, + 12, 93, 2, 1, 16, 118, 106, 73, 5, 10, 4, 79, + 84, 89, 87, 116, 92, 12, 1, 66, 73, 77, 82, + 95, 92, 113, 78, 18, 5, 0, 67, 69, 75, 79, 87, + 101, 77, 3, 69, 6, 5, 73, 79, 79, 94, 76, 28, + 6, 8, 5, 67, 72, 81, 79, 83, 62, 118, 117, + 110, 116, 106, 108, 108, 107, 104, 107, 108, + 110, 90, 96, 95, 101, 108, 90, 0, 84, 89, 86, + 76, 71, 72, 65, 78, 65, 1, 67, 68, 0, 68, 77, + 64, 68, 76, 6, 3, 65, 4, 14, 2, 70, 75, 79, + 74, 65, 65, 4, 6, 69, 65, 71, 8, 7, 73, 77, + 76, 76, 12, 62, 62, 62, 62, 62, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 40, 62, 62, 62, 62, + 62, 52, 38, 50, 48, 35, 18, 12, 4, 79, 62, 62, + 62, 50, 36, 38, 29, 29, 36, 32, 24, 10, 22, 6, + 69, 26, 30, 26, 31, 37, 24, 23, 14, 13, 66, 6, + 65, 79, 117, 3, 64, 70, 23, 6, 67, 76, 71, 68, + 68, 68, 66, 5, 68, 68, 66, 2, 13, 10, 15, 0, + 62, 62, 56, 52, 44, 40, 27, 14, 71, 67, 29, + 22, 19, 11, 9, 3, 0, 65, 75, 76, 71, 68, 68, + 68, 66, 5, 68, 68, 66, 2, 13, 10, 15, 0, 62, + 62, 56, 52, 44, 40, 27, 14, 71 }, + + { + + 62, + 9, 75, 62, 9, 75, 114, 93, 13, 10, 9, 35, 53, + 57, 17, 3, 87, 36, 83, 91, 4, 4, 65, 0, 15, + 45, 51, 40, 5, 102, 113, 107, 73, 83, 91, 4, + 90, 81, 22, 7, 84, 96, 106, 65, 66, 85, 98, 0, + 82, 100, 65, 77, 88, 100, 7, 77, 74, 91, 17, + 3, 22, 0, 0, 0, 79, 87, 97, 68, 17, 0, 26, 84, + 118, 111, 83, 88, 73, 75, 83, 83, 113, 69, 95, + 84, 110, 84, 78, 84, 80, 11, 1, 76, 110, 76, + 99, 86, 113, 65, 78, 72, 90, 10, 69, 67, 92, + 84, 82, 79, 70, 65, 68, 82, 65, 5, 68, 5, 69, + 70, 5, 73, 21, 68, 1, 16, 18, 24, 24, 13, 79, + 74, 78, 8, 67, 87, 67, 65, 72, 76, 73, 9, 38, + 10, 81, 78, 70, 70, 31, 37, 96, 66, 65, 80, 2, + 0, 66, 72, 33, 56, 42, 64, 73, 113, 19, 62, + 62, 117, 23, 21, 26, 30, 29, 24, 18, 22, 27, + 15, 22, 15, 7, 5, 1, 16, 15, 15, 12, 6, 10, + 21, 11, 66, 11, 13, 69, 22, 66, 62, 62, 54, + 43, 52, 62, 62, 62, 38, 25, 19, 20, 34, 30, + 82, 21, 24, 8, 39, 21, 21, 26, 32, 30, 38, 36, + 11, 93, 1, 0, 14, 116, 104, 72, 5, 10, 4, 78, + 83, 88, 87, 114, 90, 13, 2, 66, 72, 75, 81, + 93, 91, 110, 77, 19, 6, 1, 66, 68, 74, 79, 86, + 100, 77, 4, 68, 6, 5, 72, 79, 79, 93, 75, 28, + 7, 8, 5, 66, 71, 80, 79, 82, 62, 117, 116, + 109, 115, 105, 106, 107, 105, 102, 105, 106, + 107, 89, 95, 94, 100, 105, 89, 64, 83, 88, 85, + 76, 71, 72, 65, 77, 66, 0, 67, 68, 1, 68, 77, + 64, 68, 75, 5, 2, 65, 3, 13, 1, 70, 75, 78, + 75, 66, 64, 4, 5, 69, 65, 71, 7, 6, 73, 77, + 76, 76, 11, 62, 62, 62, 62, 62, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 38, 62, 62, 62, 62, + 62, 50, 36, 48, 45, 33, 17, 11, 3, 79, 62, 61, + 62, 48, 34, 36, 28, 27, 34, 30, 22, 8, 20, 5, + 71, 25, 29, 24, 30, 36, 23, 21, 13, 12, 67, 5, + 66, 79, 116, 1, 65, 71, 21, 5, 68, 76, 70, 68, + 67, 67, 65, 5, 67, 67, 65, 3, 14, 11, 15, 0, + 62, 60, 54, 50, 42, 38, 24, 12, 72, 67, 30, + 23, 19, 12, 10, 4, 0, 65, 74, 76, 70, 68, 67, + 67, 65, 5, 67, 67, 65, 3, 14, 11, 15, 0, 62, + 60, 54, 50, 42, 38, 24, 12, 72 }, + + { + + 62, + 8, 75, 62, 8, 75, 113, 92, 13, 10, 9, 33, 51, + 56, 17, 3, 85, 34, 83, 90, 4, 3, 64, 64, 13, + 43, 49, 36, 3, 103, 111, 106, 73, 83, 90, 4, + 90, 81, 22, 6, 83, 95, 105, 66, 68, 86, 99, 0, + 82, 99, 65, 78, 88, 100, 7, 77, 74, 91, 17, 3, + 22, 0, 0, 0, 78, 88, 97, 68, 16, 0, 26, 83, + 117, 109, 82, 88, 71, 73, 83, 82, 111, 69, 94, + 83, 108, 85, 78, 85, 80, 11, 0, 76, 108, 76, + 98, 85, 112, 65, 78, 72, 90, 10, 69, 67, 91, + 84, 82, 79, 70, 65, 68, 81, 64, 5, 68, 4, 69, + 70, 4, 73, 21, 68, 1, 16, 18, 24, 24, 13, 80, + 73, 77, 7, 67, 87, 67, 64, 72, 76, 72, 10, 39, + 12, 79, 79, 71, 70, 31, 37, 97, 66, 65, 81, 2, + 0, 66, 71, 33, 56, 42, 0, 74, 112, 18, 59, 62, + 116, 22, 21, 26, 29, 28, 23, 18, 22, 26, 14, + 21, 14, 7, 4, 0, 15, 14, 14, 12, 5, 9, 20, 10, + 67, 10, 12, 69, 20, 67, 62, 62, 52, 41, 50, + 60, 62, 62, 37, 23, 16, 18, 31, 28, 84, 19, + 23, 6, 38, 20, 20, 25, 30, 28, 36, 34, 9, 93, + 0, 64, 12, 115, 103, 71, 6, 10, 4, 78, 82, 87, + 86, 112, 89, 13, 2, 65, 72, 74, 80, 92, 90, + 108, 77, 19, 6, 1, 66, 68, 74, 78, 86, 99, 77, + 5, 67, 6, 5, 71, 78, 79, 92, 74, 28, 8, 8, 5, + 65, 71, 79, 78, 82, 62, 116, 114, 107, 113, + 104, 105, 105, 104, 101, 104, 104, 105, 89, + 94, 94, 99, 102, 89, 65, 83, 87, 85, 76, 71, + 72, 66, 77, 66, 64, 67, 68, 1, 68, 77, 65, 68, + 75, 5, 2, 66, 2, 12, 1, 71, 75, 77, 76, 66, + 64, 3, 5, 69, 66, 71, 7, 6, 73, 76, 76, 76, 9, + 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, + 62, 62, 61, 36, 62, 62, 62, 62, 61, 48, 34, + 45, 43, 31, 15, 9, 2, 79, 61, 59, 62, 46, 31, + 34, 26, 24, 32, 28, 20, 6, 17, 3, 72, 23, 28, + 21, 28, 34, 21, 19, 11, 10, 68, 4, 67, 79, + 115, 0, 67, 73, 20, 4, 69, 76, 70, 67, 67, 66, + 65, 6, 67, 66, 65, 4, 14, 11, 16, 1, 61, 58, + 52, 48, 40, 36, 22, 10, 74, 66, 30, 23, 20, + 12, 10, 4, 1, 65, 74, 76, 70, 67, 67, 66, 65, + 6, 67, 66, 65, 4, 14, 11, 16, 1, 61, 58, 52, + 48, 40, 36, 22, 10, 74 }, + + { + + 62, + 8, 75, 62, 8, 75, 111, 91, 14, 10, 9, 31, 49, + 56, 18, 3, 83, 33, 82, 88, 4, 3, 0, 64, 12, + 42, 47, 33, 1, 103, 109, 104, 72, 82, 88, 4, + 89, 80, 23, 6, 82, 94, 104, 67, 69, 86, 99, 0, + 82, 98, 65, 78, 88, 100, 7, 76, 73, 90, 17, 3, + 22, 0, 0, 0, 77, 88, 97, 68, 15, 0, 26, 82, + 115, 106, 81, 87, 69, 71, 82, 81, 109, 68, 92, + 82, 106, 86, 78, 85, 80, 12, 0, 75, 106, 76, + 97, 84, 110, 65, 77, 72, 89, 11, 69, 66, 90, + 83, 81, 79, 70, 64, 67, 80, 0, 5, 68, 4, 68, + 69, 4, 73, 22, 68, 1, 16, 19, 25, 24, 14, 80, + 72, 76, 6, 67, 87, 67, 0, 72, 75, 71, 11, 40, + 14, 77, 80, 72, 69, 31, 38, 98, 66, 65, 81, 3, + 0, 66, 69, 33, 56, 42, 1, 75, 111, 17, 57, 62, + 114, 22, 21, 26, 28, 28, 23, 18, 22, 26, 13, + 20, 14, 7, 4, 0, 15, 13, 14, 12, 5, 9, 19, 9, + 68, 10, 12, 69, 19, 67, 62, 62, 51, 40, 48, + 58, 62, 62, 36, 21, 14, 17, 29, 27, 85, 18, + 22, 4, 37, 19, 19, 24, 28, 27, 34, 32, 8, 93, + 0, 65, 11, 113, 101, 69, 7, 10, 4, 77, 81, 86, + 85, 110, 88, 14, 3, 64, 71, 73, 79, 91, 89, + 106, 76, 20, 7, 2, 66, 67, 73, 77, 85, 97, 76, + 7, 66, 7, 5, 70, 77, 78, 91, 73, 29, 9, 9, 6, + 64, 70, 78, 77, 81, 62, 114, 112, 105, 111, + 103, 104, 103, 102, 99, 102, 102, 103, 88, 93, + 93, 98, 98, 89, 66, 83, 86, 84, 75, 71, 72, + 66, 77, 66, 65, 67, 68, 2, 68, 77, 65, 68, 75, + 5, 2, 66, 2, 11, 1, 71, 74, 75, 76, 66, 0, 2, + 5, 69, 66, 70, 7, 6, 72, 75, 75, 75, 7, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, + 62, 58, 34, 62, 62, 62, 62, 58, 46, 33, 43, + 41, 30, 14, 8, 1, 79, 59, 57, 60, 44, 29, 32, + 25, 22, 30, 26, 18, 4, 15, 1, 73, 22, 27, 19, + 27, 32, 20, 17, 10, 9, 69, 3, 67, 79, 114, 64, + 68, 75, 19, 3, 70, 76, 69, 66, 66, 64, 64, 7, + 67, 65, 64, 5, 15, 12, 17, 2, 60, 57, 50, 46, + 38, 34, 20, 8, 75, 65, 30, 24, 21, 13, 11, 5, + 2, 64, 73, 76, 69, 66, 66, 64, 64, 7, 67, 65, + 64, 5, 15, 12, 17, 2, 60, 57, 50, 46, 38, 34, + 20, 8, 75 }, + + { + + 62, + 8, 75, 62, 8, 75, 109, 89, 14, 10, 8, 29, 48, + 55, 19, 3, 82, 31, 82, 87, 3, 3, 1, 65, 11, + 41, 45, 30, 65, 104, 107, 103, 72, 82, 87, 3, + 89, 79, 23, 6, 82, 93, 102, 68, 70, 86, 99, 0, + 81, 97, 66, 78, 88, 100, 7, 76, 73, 89, 18, 3, + 22, 0, 0, 0, 77, 88, 97, 67, 14, 0, 25, 82, + 114, 104, 80, 86, 68, 70, 81, 80, 107, 67, 91, + 81, 103, 86, 79, 85, 81, 12, 0, 74, 103, 75, + 97, 84, 108, 65, 77, 72, 89, 11, 69, 66, 89, + 83, 80, 78, 70, 64, 67, 79, 0, 5, 69, 4, 68, + 69, 4, 73, 22, 68, 1, 17, 19, 25, 24, 14, 81, + 72, 76, 6, 67, 86, 67, 1, 71, 75, 69, 13, 41, + 15, 75, 80, 73, 69, 32, 38, 99, 66, 64, 82, 4, + 0, 66, 68, 33, 56, 42, 3, 76, 109, 16, 54, 62, + 113, 22, 21, 25, 28, 27, 23, 17, 22, 25, 12, + 19, 14, 7, 4, 0, 14, 13, 13, 11, 5, 9, 18, 9, + 69, 9, 11, 69, 18, 68, 60, 62, 49, 38, 47, 56, + 62, 62, 35, 19, 12, 15, 27, 25, 86, 17, 21, 3, + 36, 18, 18, 22, 27, 25, 32, 31, 6, 93, 64, 66, + 9, 112, 100, 68, 7, 10, 4, 76, 80, 85, 85, + 108, 86, 15, 4, 64, 70, 71, 78, 89, 88, 103, + 75, 20, 7, 2, 65, 66, 72, 77, 84, 96, 76, 8, + 65, 7, 5, 69, 77, 78, 90, 72, 29, 10, 9, 6, 0, + 69, 77, 77, 80, 62, 113, 111, 104, 110, 102, + 102, 102, 100, 98, 100, 100, 100, 87, 92, 92, + 97, 95, 88, 67, 82, 85, 83, 75, 71, 72, 66, + 76, 67, 66, 67, 68, 3, 68, 77, 65, 68, 74, 4, + 1, 66, 1, 10, 0, 71, 74, 74, 77, 67, 1, 2, 4, + 69, 66, 70, 6, 5, 72, 75, 75, 75, 6, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, + 56, 32, 62, 62, 62, 62, 55, 44, 31, 41, 38, + 28, 13, 7, 0, 79, 57, 54, 57, 42, 27, 30, 23, + 20, 28, 24, 16, 2, 13, 0, 75, 21, 26, 17, 25, + 31, 18, 15, 9, 8, 70, 2, 68, 79, 113, 66, 69, + 76, 17, 2, 71, 76, 69, 66, 65, 0, 0, 7, 66, + 64, 0, 6, 15, 13, 17, 2, 60, 55, 48, 44, 36, + 32, 17, 6, 76, 65, 31, 24, 21, 14, 12, 5, 2, + 64, 72, 76, 69, 66, 65, 0, 0, 7, 66, 64, 0, 6, + 15, 13, 17, 2, 60, 55, 48, 44, 36, 32, 17, 6, + 76 }, + + { + + 62, + 8, 76, 62, 8, 76, 107, 88, 15, 10, 8, 28, 46, + 54, 19, 4, 80, 30, 82, 85, 3, 2, 2, 66, 10, + 40, 44, 26, 67, 105, 105, 101, 72, 82, 85, 3, + 88, 78, 24, 6, 81, 92, 101, 70, 71, 86, 100, + 0, 81, 97, 66, 79, 87, 100, 6, 76, 73, 88, 18, + 3, 22, 0, 0, 0, 76, 88, 97, 67, 14, 64, 25, + 81, 113, 102, 79, 85, 66, 68, 80, 79, 105, 66, + 90, 81, 101, 87, 79, 85, 81, 13, 0, 73, 101, + 75, 96, 83, 106, 65, 77, 72, 89, 12, 70, 65, + 89, 83, 79, 78, 69, 64, 66, 78, 1, 5, 69, 4, + 68, 69, 4, 73, 22, 68, 1, 17, 19, 26, 25, 15, + 81, 71, 75, 5, 67, 86, 67, 2, 71, 74, 68, 14, + 41, 17, 74, 81, 74, 68, 32, 38, 99, 67, 64, + 82, 4, 64, 65, 67, 33, 56, 43, 4, 77, 108, 15, + 51, 62, 111, 22, 21, 25, 27, 27, 22, 17, 21, + 25, 12, 19, 14, 6, 4, 64, 14, 12, 12, 11, 5, + 9, 18, 8, 71, 9, 10, 69, 17, 68, 57, 62, 47, + 37, 45, 54, 62, 61, 33, 17, 10, 14, 25, 23, + 88, 16, 19, 1, 34, 17, 17, 21, 25, 23, 30, 29, + 5, 93, 65, 67, 8, 110, 98, 66, 8, 10, 4, 75, + 80, 85, 84, 107, 85, 16, 5, 0, 70, 70, 77, 88, + 87, 101, 75, 21, 8, 3, 65, 65, 72, 76, 83, 95, + 76, 10, 64, 7, 5, 69, 76, 78, 90, 72, 30, 10, + 10, 6, 1, 69, 77, 76, 80, 62, 111, 109, 102, + 108, 100, 101, 100, 99, 96, 98, 98, 98, 87, + 91, 92, 97, 92, 88, 68, 82, 85, 82, 75, 71, + 72, 66, 76, 67, 67, 67, 68, 3, 68, 77, 65, 68, + 74, 4, 1, 66, 0, 9, 0, 71, 74, 73, 78, 67, 2, + 1, 4, 69, 66, 69, 6, 5, 71, 74, 75, 75, 4, 62, + 61, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, + 62, 53, 30, 62, 62, 62, 62, 53, 41, 29, 39, + 36, 26, 12, 6, 64, 79, 55, 52, 55, 40, 25, 29, + 22, 18, 26, 21, 14, 0, 11, 65, 76, 20, 24, 15, + 24, 29, 17, 13, 7, 6, 71, 1, 69, 80, 112, 67, + 71, 78, 16, 0, 72, 75, 68, 65, 65, 1, 1, 8, + 66, 0, 1, 7, 16, 14, 18, 3, 59, 53, 46, 42, + 34, 30, 15, 4, 77, 64, 31, 25, 22, 14, 13, 6, + 3, 0, 72, 75, 68, 65, 65, 1, 1, 8, 66, 0, 1, + 7, 16, 14, 18, 3, 59, 53, 46, 42, 34, 30, 15, + 4, 77 }, + + { + + 62, + 8, 76, 62, 8, 76, 106, 86, 15, 10, 7, 26, 45, + 53, 20, 4, 78, 28, 82, 84, 3, 2, 3, 67, 8, 38, + 42, 23, 69, 106, 103, 100, 72, 82, 84, 3, 88, + 77, 24, 5, 80, 91, 100, 71, 73, 87, 100, 0, + 81, 96, 66, 79, 87, 100, 6, 76, 73, 88, 19, 3, + 22, 0, 0, 0, 76, 89, 97, 66, 13, 64, 24, 81, + 112, 100, 78, 84, 65, 67, 80, 78, 103, 65, 89, + 80, 99, 88, 79, 85, 81, 13, 64, 72, 99, 75, + 95, 82, 104, 65, 77, 72, 89, 12, 70, 65, 88, + 83, 79, 78, 69, 64, 66, 77, 1, 5, 69, 3, 68, + 69, 4, 73, 22, 68, 1, 18, 19, 26, 25, 15, 82, + 71, 74, 5, 67, 86, 67, 3, 71, 74, 67, 15, 42, + 18, 72, 81, 75, 68, 32, 38, 100, 67, 0, 83, 5, + 64, 65, 66, 33, 56, 43, 5, 78, 106, 14, 48, + 60, 110, 21, 21, 24, 26, 26, 22, 16, 21, 24, + 11, 18, 13, 6, 4, 64, 13, 11, 11, 10, 4, 8, + 17, 8, 72, 8, 9, 69, 15, 69, 55, 62, 45, 35, + 43, 52, 62, 58, 32, 15, 7, 12, 23, 21, 89, 14, + 18, 64, 33, 16, 16, 20, 24, 21, 28, 27, 3, 93, + 66, 68, 6, 109, 97, 65, 8, 10, 4, 74, 79, 84, + 83, 105, 84, 17, 5, 1, 69, 69, 76, 86, 86, 99, + 74, 21, 8, 3, 64, 64, 71, 76, 83, 94, 76, 11, + 0, 7, 5, 68, 76, 78, 89, 71, 30, 11, 10, 6, 2, + 68, 76, 76, 79, 62, 110, 108, 101, 106, 99, + 99, 99, 97, 95, 97, 96, 96, 86, 90, 91, 96, + 89, 88, 69, 82, 84, 82, 75, 71, 72, 67, 75, + 67, 68, 67, 68, 4, 68, 77, 65, 68, 74, 3, 1, + 66, 64, 8, 0, 71, 74, 72, 79, 67, 2, 0, 4, 69, + 66, 69, 6, 4, 71, 73, 75, 75, 3, 62, 60, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 50, + 28, 62, 62, 62, 62, 50, 39, 27, 37, 33, 24, + 10, 5, 65, 79, 52, 50, 53, 38, 23, 27, 20, 16, + 24, 19, 12, 65, 8, 67, 77, 19, 23, 12, 22, 27, + 15, 11, 6, 5, 72, 0, 70, 80, 111, 68, 72, 79, + 15, 64, 73, 75, 68, 65, 64, 2, 1, 9, 66, 1, 2, + 8, 16, 15, 18, 4, 59, 51, 44, 40, 32, 28, 13, + 2, 79, 64, 32, 25, 23, 15, 13, 6, 3, 0, 71, + 75, 68, 65, 64, 2, 1, 9, 66, 1, 2, 8, 16, 15, + 18, 4, 59, 51, 44, 40, 32, 28, 13, 2, 79 }, + + { + + 62, + 8, 76, 62, 8, 76, 104, 85, 15, 10, 7, 24, 43, + 52, 21, 4, 77, 26, 81, 82, 2, 2, 4, 68, 7, 37, + 40, 20, 72, 106, 101, 99, 72, 81, 82, 2, 88, + 76, 24, 5, 80, 90, 98, 72, 74, 87, 100, 0, 80, + 95, 67, 79, 87, 100, 6, 75, 73, 87, 19, 3, 22, + 0, 0, 0, 75, 89, 97, 66, 12, 64, 24, 80, 110, + 97, 77, 83, 0, 65, 79, 77, 101, 64, 88, 79, + 96, 88, 80, 85, 82, 13, 64, 71, 96, 74, 95, + 82, 102, 65, 77, 72, 88, 12, 70, 64, 87, 82, + 78, 77, 69, 0, 65, 76, 2, 5, 70, 3, 67, 69, 4, + 73, 23, 68, 1, 18, 20, 26, 25, 15, 82, 70, 74, + 4, 67, 85, 67, 4, 70, 73, 65, 17, 43, 20, 70, + 82, 76, 67, 33, 38, 101, 67, 0, 83, 6, 64, 65, + 64, 33, 56, 43, 7, 79, 105, 13, 46, 57, 109, + 21, 21, 24, 26, 26, 22, 16, 21, 24, 10, 17, + 13, 6, 4, 64, 13, 11, 10, 10, 4, 8, 16, 7, 73, + 7, 9, 69, 14, 69, 53, 62, 44, 34, 42, 50, 62, + 56, 31, 13, 5, 11, 21, 19, 90, 13, 17, 65, 32, + 15, 15, 18, 22, 19, 26, 26, 2, 93, 67, 69, 4, + 107, 95, 64, 9, 10, 4, 73, 78, 83, 83, 103, + 82, 18, 6, 1, 68, 67, 75, 85, 85, 96, 73, 22, + 9, 4, 64, 0, 70, 75, 82, 93, 75, 12, 1, 7, 5, + 67, 75, 77, 88, 70, 30, 12, 10, 6, 3, 67, 75, + 75, 78, 62, 109, 106, 99, 105, 98, 98, 97, 95, + 93, 95, 94, 93, 85, 89, 90, 95, 86, 87, 70, + 81, 83, 81, 75, 71, 72, 67, 75, 68, 69, 67, + 68, 5, 68, 77, 65, 68, 73, 3, 0, 66, 65, 7, + 64, 71, 74, 71, 79, 68, 3, 0, 3, 69, 66, 69, + 5, 4, 71, 73, 75, 75, 1, 62, 59, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 60, 48, 26, 62, + 62, 62, 62, 47, 37, 26, 35, 31, 22, 9, 4, 66, + 79, 50, 47, 50, 36, 21, 25, 19, 14, 22, 17, + 10, 67, 6, 68, 79, 18, 22, 10, 21, 26, 14, 9, + 5, 4, 73, 64, 71, 80, 110, 70, 73, 81, 13, 65, + 74, 75, 67, 64, 0, 3, 2, 9, 65, 2, 3, 9, 17, + 16, 19, 4, 58, 49, 42, 38, 30, 26, 10, 0, 80, + 0, 32, 26, 23, 16, 14, 7, 4, 0, 70, 75, 67, + 64, 0, 3, 2, 9, 65, 2, 3, 9, 17, 16, 19, 4, + 58, 49, 42, 38, 30, 26, 10, 0, 80 }, + + { + + 61, + 8, 76, 61, 8, 76, 102, 83, 16, 10, 6, 22, 42, + 51, 21, 4, 75, 25, 81, 81, 2, 1, 5, 69, 6, 36, + 38, 16, 74, 107, 99, 97, 72, 81, 81, 2, 87, + 75, 25, 5, 79, 89, 97, 73, 75, 87, 101, 0, 80, + 94, 67, 80, 87, 100, 6, 75, 73, 86, 20, 3, 22, + 0, 0, 0, 75, 89, 97, 65, 11, 64, 23, 80, 109, + 95, 76, 82, 1, 64, 78, 76, 99, 0, 87, 78, 94, + 89, 80, 85, 82, 14, 64, 70, 94, 74, 94, 81, + 100, 65, 77, 72, 88, 13, 70, 64, 86, 82, 77, + 77, 69, 0, 65, 75, 2, 5, 70, 3, 67, 69, 4, 73, + 23, 68, 1, 19, 20, 27, 25, 16, 83, 70, 73, 4, + 67, 85, 67, 5, 70, 73, 64, 18, 44, 21, 68, 82, + 77, 67, 33, 38, 102, 67, 1, 84, 6, 64, 65, 0, + 33, 56, 43, 8, 80, 103, 12, 43, 54, 107, 21, + 21, 23, 25, 25, 21, 15, 21, 23, 9, 16, 13, 6, + 4, 65, 12, 10, 9, 9, 4, 8, 15, 7, 74, 7, 8, + 69, 13, 70, 51, 60, 42, 32, 40, 48, 62, 53, + 30, 11, 3, 9, 19, 17, 92, 12, 16, 67, 31, 14, + 14, 17, 21, 17, 24, 24, 0, 93, 68, 70, 3, 106, + 94, 1, 9, 10, 4, 72, 77, 82, 82, 101, 81, 19, + 7, 2, 68, 66, 74, 83, 84, 94, 73, 22, 9, 4, 0, + 1, 70, 75, 81, 92, 75, 14, 2, 7, 5, 66, 75, + 77, 87, 69, 31, 13, 11, 6, 4, 67, 74, 75, 78, + 62, 107, 105, 98, 103, 97, 96, 96, 94, 92, 93, + 92, 91, 85, 88, 90, 94, 83, 87, 71, 81, 82, + 80, 75, 71, 72, 67, 74, 68, 70, 67, 68, 5, 68, + 77, 65, 68, 73, 2, 0, 66, 66, 6, 64, 71, 74, + 70, 80, 68, 4, 64, 3, 69, 66, 68, 5, 3, 70, + 72, 75, 75, 0, 62, 58, 61, 61, 61, 62, 62, 62, + 61, 62, 62, 62, 57, 45, 24, 62, 60, 59, 60, + 44, 35, 24, 33, 28, 20, 8, 3, 67, 79, 48, 45, + 48, 34, 19, 23, 17, 12, 20, 15, 8, 69, 4, 70, + 80, 17, 21, 8, 19, 24, 12, 7, 3, 2, 74, 65, + 72, 80, 109, 71, 75, 82, 12, 66, 75, 75, 67, + 64, 0, 4, 3, 10, 65, 3, 4, 10, 17, 17, 19, 5, + 58, 47, 40, 36, 28, 24, 8, 65, 81, 0, 33, 26, + 24, 16, 15, 7, 4, 1, 70, 75, 67, 64, 0, 4, 3, + 10, 65, 3, 4, 10, 17, 17, 19, 5, 58, 47, 40, + 36, 28, 24, 8, 65, 81 }, + + { + + 60, + 8, 76, 60, 8, 76, 100, 82, 16, 10, 6, 20, 40, + 50, 22, 4, 73, 23, 81, 79, 2, 1, 6, 70, 5, 35, + 36, 13, 76, 108, 97, 96, 72, 81, 79, 2, 87, + 74, 25, 5, 78, 88, 96, 74, 76, 87, 101, 0, 80, + 93, 67, 80, 87, 100, 6, 75, 73, 85, 20, 3, 22, + 0, 0, 0, 74, 89, 97, 65, 10, 64, 23, 79, 108, + 93, 75, 81, 3, 1, 77, 75, 97, 1, 86, 77, 92, + 90, 80, 85, 82, 14, 64, 69, 92, 74, 93, 80, + 98, 65, 77, 72, 88, 13, 70, 0, 85, 82, 76, 77, + 69, 0, 64, 74, 3, 5, 70, 3, 67, 69, 4, 73, 23, + 68, 1, 19, 20, 27, 25, 16, 83, 69, 72, 3, 67, + 85, 67, 6, 70, 72, 0, 19, 45, 23, 66, 83, 78, + 66, 33, 38, 103, 67, 1, 84, 7, 64, 65, 1, 33, + 56, 43, 9, 81, 102, 11, 40, 51, 106, 21, 21, + 23, 24, 25, 21, 15, 21, 23, 8, 15, 13, 6, 4, + 65, 12, 9, 8, 9, 4, 8, 14, 6, 75, 6, 7, 69, + 12, 70, 49, 58, 40, 31, 38, 46, 59, 51, 29, 9, + 1, 8, 17, 15, 93, 11, 15, 69, 30, 13, 13, 16, + 19, 15, 22, 22, 64, 93, 69, 71, 1, 104, 92, 2, + 10, 10, 4, 71, 76, 81, 81, 99, 80, 20, 8, 3, + 67, 65, 73, 82, 83, 92, 72, 23, 10, 5, 0, 2, + 69, 74, 80, 91, 75, 15, 3, 7, 5, 65, 74, 77, + 86, 68, 31, 14, 11, 6, 5, 66, 73, 74, 77, 62, + 106, 103, 96, 101, 96, 95, 94, 92, 90, 91, 90, + 89, 84, 87, 89, 93, 80, 87, 72, 81, 81, 79, + 75, 71, 72, 67, 74, 68, 71, 67, 68, 6, 68, 77, + 65, 68, 73, 2, 0, 66, 67, 5, 64, 71, 74, 69, + 81, 68, 5, 65, 3, 69, 66, 68, 5, 3, 70, 71, + 75, 75, 65, 61, 57, 60, 59, 59, 62, 62, 62, + 59, 60, 62, 61, 54, 42, 22, 61, 57, 55, 55, + 41, 33, 22, 31, 26, 18, 7, 2, 68, 79, 46, 43, + 46, 32, 17, 21, 16, 10, 18, 13, 6, 71, 2, 72, + 81, 16, 20, 6, 18, 22, 11, 5, 2, 1, 75, 66, + 73, 80, 108, 72, 76, 84, 11, 67, 76, 75, 66, + 0, 1, 5, 4, 11, 65, 4, 5, 11, 18, 18, 20, 6, + 57, 45, 38, 34, 26, 22, 6, 67, 82, 1, 33, 27, + 25, 17, 16, 8, 5, 1, 69, 75, 66, 0, 1, 5, 4, + 11, 65, 4, 5, 11, 18, 18, 20, 6, 57, 45, 38, + 34, 26, 22, 6, 67, 82 }, + + { + + 58, + 7, 77, 58, 7, 77, 99, 81, 16, 10, 5, 18, 38, + 49, 22, 4, 72, 21, 81, 78, 1, 0, 7, 71, 3, 33, + 34, 9, 79, 109, 95, 95, 72, 81, 78, 1, 87, 74, + 25, 4, 78, 88, 95, 76, 78, 88, 102, 64, 80, + 93, 68, 81, 87, 100, 5, 75, 73, 85, 20, 2, 22, + 0, 0, 0, 74, 90, 97, 65, 9, 65, 22, 79, 107, + 91, 74, 81, 4, 2, 77, 74, 96, 1, 85, 77, 90, + 91, 81, 86, 83, 14, 65, 69, 90, 74, 93, 80, + 97, 65, 77, 72, 88, 13, 71, 0, 85, 82, 76, 77, + 69, 0, 64, 73, 3, 5, 71, 2, 67, 69, 3, 73, 23, + 68, 1, 19, 20, 27, 25, 16, 84, 69, 72, 2, 67, + 85, 68, 6, 70, 72, 1, 20, 45, 24, 65, 84, 80, + 66, 33, 38, 104, 68, 1, 85, 7, 65, 65, 2, 33, + 55, 43, 10, 82, 101, 9, 37, 47, 105, 20, 21, + 22, 23, 24, 20, 14, 20, 22, 7, 14, 12, 5, 3, + 66, 11, 8, 7, 8, 3, 7, 13, 5, 77, 5, 6, 69, + 10, 71, 46, 55, 38, 29, 36, 43, 55, 48, 27, 7, + 65, 6, 14, 13, 95, 9, 13, 71, 28, 12, 12, 14, + 17, 13, 20, 20, 66, 93, 70, 72, 64, 103, 91, + 3, 10, 10, 4, 71, 76, 81, 81, 98, 79, 20, 8, + 3, 67, 64, 72, 81, 83, 90, 72, 23, 10, 5, 0, + 2, 69, 74, 80, 90, 75, 16, 4, 7, 4, 65, 74, + 77, 86, 68, 31, 14, 11, 6, 6, 66, 73, 74, 77, + 62, 105, 102, 95, 100, 95, 94, 93, 91, 89, 90, + 89, 87, 84, 87, 89, 93, 77, 87, 74, 81, 81, + 79, 75, 71, 72, 68, 74, 69, 72, 68, 68, 6, 69, + 77, 66, 68, 73, 1, 64, 67, 68, 4, 65, 72, 74, + 68, 82, 69, 5, 66, 2, 69, 67, 68, 4, 2, 70, + 71, 75, 75, 67, 59, 56, 58, 57, 56, 62, 62, + 62, 56, 57, 62, 58, 50, 39, 20, 57, 53, 51, + 49, 38, 30, 20, 28, 23, 16, 5, 0, 69, 79, 43, + 40, 43, 30, 14, 19, 14, 7, 16, 10, 4, 74, 64, + 74, 83, 14, 18, 3, 16, 20, 9, 3, 0, 64, 76, + 67, 74, 81, 107, 74, 78, 86, 9, 69, 78, 75, + 66, 0, 1, 6, 4, 11, 65, 5, 5, 12, 18, 18, 20, + 6, 56, 43, 36, 31, 23, 20, 3, 69, 84, 1, 33, + 27, 25, 17, 16, 8, 5, 1, 69, 75, 66, 0, 1, 6, + 4, 11, 65, 5, 5, 12, 18, 18, 20, 6, 56, 43, + 36, 31, 23, 20, 3, 69, 84 }, + + { + + 57, + 7, 77, 57, 7, 77, 97, 79, 17, 11, 5, 17, 37, + 49, 23, 5, 70, 20, 80, 76, 1, 0, 9, 71, 2, 32, + 33, 6, 81, 109, 93, 93, 71, 80, 76, 1, 86, 73, + 26, 4, 77, 87, 93, 77, 79, 88, 102, 64, 79, + 92, 68, 81, 86, 99, 5, 74, 72, 84, 21, 2, 22, + 0, 0, 0, 73, 90, 97, 64, 9, 65, 22, 78, 105, + 88, 72, 80, 6, 4, 76, 72, 94, 2, 83, 76, 87, + 91, 81, 86, 83, 15, 65, 68, 87, 73, 92, 79, + 95, 65, 76, 72, 87, 14, 71, 1, 84, 81, 75, 76, + 68, 1, 0, 72, 4, 6, 71, 2, 66, 68, 3, 72, 24, + 67, 1, 20, 21, 28, 26, 17, 84, 68, 71, 2, 67, + 84, 68, 7, 69, 71, 3, 22, 46, 26, 0, 84, 81, + 65, 34, 39, 104, 68, 2, 85, 8, 65, 64, 4, 33, + 55, 44, 12, 83, 99, 8, 35, 44, 103, 20, 21, + 22, 23, 24, 20, 14, 20, 22, 7, 14, 12, 5, 3, + 66, 11, 8, 7, 8, 3, 7, 13, 5, 78, 5, 6, 69, 9, + 71, 44, 53, 37, 28, 35, 41, 52, 46, 26, 6, 67, + 5, 12, 12, 96, 8, 12, 72, 27, 12, 12, 13, 16, + 12, 19, 19, 67, 93, 70, 72, 65, 101, 89, 5, + 11, 10, 4, 70, 75, 80, 80, 96, 77, 21, 9, 4, + 66, 1, 71, 79, 82, 87, 71, 24, 11, 6, 1, 3, + 68, 73, 79, 88, 74, 18, 5, 8, 4, 64, 73, 76, + 85, 67, 32, 15, 12, 7, 7, 65, 72, 73, 76, 62, + 103, 100, 93, 98, 93, 92, 91, 89, 87, 88, 87, + 84, 83, 86, 88, 92, 73, 86, 75, 80, 80, 78, + 74, 71, 71, 68, 73, 69, 72, 68, 68, 7, 69, 77, + 66, 68, 72, 1, 64, 67, 68, 4, 65, 72, 73, 66, + 82, 69, 6, 66, 2, 69, 67, 67, 4, 2, 69, 70, + 74, 74, 68, 58, 55, 57, 56, 54, 60, 60, 59, + 54, 55, 59, 56, 47, 37, 18, 54, 50, 48, 44, + 36, 28, 19, 26, 21, 15, 4, 64, 69, 79, 41, 38, + 41, 28, 12, 18, 13, 5, 15, 8, 3, 76, 66, 75, + 84, 13, 17, 1, 15, 19, 8, 2, 64, 65, 77, 67, + 74, 81, 106, 75, 79, 87, 8, 70, 79, 74, 65, 1, + 2, 8, 5, 12, 64, 7, 6, 13, 19, 19, 21, 7, 56, + 42, 35, 29, 21, 19, 1, 70, 85, 2, 34, 28, 26, + 18, 17, 9, 6, 2, 68, 74, 65, 1, 2, 8, 5, 12, + 64, 7, 6, 13, 19, 19, 21, 7, 56, 42, 35, 29, + 21, 19, 1, 70, 85 }, + + { + + 56, + 7, 77, 56, 7, 77, 95, 78, 17, 11, 5, 15, 35, + 48, 24, 5, 68, 18, 80, 75, 1, 0, 10, 72, 1, + 31, 31, 3, 83, 110, 91, 92, 71, 80, 75, 1, 86, + 72, 26, 4, 76, 86, 92, 78, 80, 88, 102, 64, + 79, 91, 68, 81, 86, 99, 5, 74, 72, 83, 21, 2, + 22, 0, 0, 0, 72, 90, 97, 64, 8, 65, 22, 77, + 104, 86, 71, 79, 8, 6, 75, 71, 92, 3, 82, 75, + 85, 92, 81, 86, 83, 15, 65, 67, 85, 73, 91, + 78, 93, 65, 76, 72, 87, 14, 71, 1, 83, 81, 74, + 76, 68, 1, 0, 71, 5, 6, 71, 2, 66, 68, 3, 72, + 24, 67, 1, 20, 21, 28, 26, 17, 85, 67, 70, 1, + 67, 84, 68, 8, 69, 71, 4, 23, 47, 28, 2, 85, + 82, 65, 34, 39, 105, 68, 2, 86, 9, 65, 64, 5, + 33, 55, 44, 13, 84, 98, 7, 32, 41, 102, 20, + 21, 22, 22, 23, 20, 14, 20, 21, 6, 13, 12, 5, + 3, 66, 10, 7, 6, 8, 3, 7, 12, 4, 79, 4, 5, 69, + 8, 72, 42, 51, 35, 26, 33, 39, 49, 44, 25, 4, + 69, 3, 10, 10, 97, 7, 11, 74, 26, 11, 11, 12, + 14, 10, 17, 17, 69, 93, 71, 73, 67, 100, 88, + 6, 12, 10, 4, 69, 74, 79, 79, 94, 76, 22, 10, + 5, 65, 2, 70, 78, 81, 85, 70, 24, 11, 6, 1, 4, + 67, 72, 78, 87, 74, 19, 6, 8, 4, 0, 72, 76, + 84, 66, 32, 16, 12, 7, 8, 64, 71, 72, 75, 62, + 102, 98, 91, 96, 92, 91, 89, 87, 86, 86, 85, + 82, 82, 85, 87, 91, 70, 86, 76, 80, 79, 77, + 74, 71, 71, 68, 73, 69, 73, 68, 68, 8, 69, 77, + 66, 68, 72, 1, 64, 67, 69, 3, 65, 72, 73, 65, + 83, 69, 7, 67, 2, 69, 67, 67, 4, 2, 69, 69, + 74, 74, 70, 57, 54, 56, 54, 52, 57, 57, 56, + 52, 52, 56, 53, 44, 34, 16, 50, 46, 44, 39, + 33, 26, 17, 24, 19, 13, 3, 65, 70, 79, 39, 36, + 39, 26, 10, 16, 11, 3, 13, 6, 1, 78, 68, 77, + 85, 12, 16, 64, 13, 17, 6, 0, 65, 66, 78, 68, + 75, 81, 105, 76, 80, 89, 7, 71, 80, 74, 65, 2, + 3, 9, 6, 13, 64, 8, 7, 14, 19, 20, 22, 8, 55, + 40, 33, 27, 19, 17, 64, 72, 86, 3, 34, 28, 27, + 19, 18, 9, 7, 2, 67, 74, 65, 2, 3, 9, 6, 13, + 64, 8, 7, 14, 19, 20, 22, 8, 55, 40, 33, 27, + 19, 17, 64, 72, 86 }, + + { + + 55, + 7, 77, 55, 7, 77, 93, 76, 18, 11, 4, 13, 34, + 47, 24, 5, 66, 17, 80, 73, 1, 64, 11, 73, 0, + 30, 29, 64, 85, 111, 89, 90, 71, 80, 73, 1, + 85, 71, 27, 4, 75, 85, 91, 79, 81, 88, 103, + 64, 79, 90, 68, 82, 86, 99, 5, 74, 72, 82, 22, + 2, 22, 0, 0, 0, 72, 90, 97, 0, 7, 65, 21, 77, + 103, 84, 70, 78, 9, 7, 74, 70, 90, 4, 81, 74, + 83, 93, 81, 86, 83, 16, 65, 66, 83, 73, 90, + 77, 91, 65, 76, 72, 87, 15, 71, 2, 82, 81, 73, + 76, 68, 1, 1, 70, 5, 6, 71, 2, 66, 68, 3, 72, + 24, 67, 1, 21, 21, 29, 26, 18, 85, 67, 69, 1, + 67, 84, 68, 9, 69, 70, 5, 24, 48, 29, 4, 85, + 83, 64, 34, 39, 106, 68, 3, 86, 9, 65, 64, 6, + 33, 55, 44, 14, 85, 96, 6, 29, 38, 100, 20, + 21, 21, 21, 23, 19, 13, 20, 21, 5, 12, 12, 5, + 3, 67, 10, 6, 5, 7, 3, 7, 11, 4, 80, 4, 4, 69, + 7, 72, 40, 49, 33, 25, 31, 37, 46, 41, 24, 2, + 71, 2, 8, 8, 99, 6, 10, 76, 25, 10, 10, 11, + 13, 8, 15, 15, 70, 93, 72, 74, 68, 98, 86, 8, + 12, 10, 4, 68, 73, 78, 78, 92, 75, 23, 11, 6, + 65, 3, 69, 76, 80, 83, 70, 25, 12, 7, 2, 5, + 67, 72, 77, 86, 74, 21, 7, 8, 4, 1, 72, 76, + 83, 65, 33, 17, 13, 7, 9, 64, 70, 72, 75, 62, + 100, 97, 90, 94, 91, 89, 88, 86, 84, 84, 83, + 80, 82, 84, 87, 90, 67, 86, 77, 80, 78, 76, + 74, 71, 71, 68, 72, 69, 74, 68, 68, 8, 69, 77, + 66, 68, 72, 0, 64, 67, 70, 2, 65, 72, 73, 64, + 84, 69, 8, 68, 2, 69, 67, 66, 4, 1, 68, 68, + 74, 74, 71, 56, 53, 55, 52, 50, 55, 55, 53, + 49, 49, 53, 50, 41, 31, 14, 46, 43, 40, 34, + 30, 24, 15, 22, 16, 11, 2, 66, 71, 79, 37, 34, + 37, 24, 8, 14, 10, 1, 11, 4, 64, 80, 70, 79, + 86, 11, 15, 66, 12, 15, 5, 65, 67, 68, 79, 69, + 76, 81, 104, 77, 82, 90, 6, 72, 81, 74, 64, 2, + 3, 10, 7, 14, 64, 9, 8, 15, 20, 21, 22, 9, 55, + 38, 31, 25, 17, 15, 66, 74, 87, 3, 35, 29, 28, + 19, 19, 10, 7, 3, 67, 74, 64, 2, 3, 10, 7, 14, + 64, 9, 8, 15, 20, 21, 22, 9, 55, 38, 31, 25, + 17, 15, 66, 74, 87 }, + + { + + 53, + 7, 77, 53, 7, 77, 92, 75, 18, 11, 4, 11, 32, + 46, 25, 5, 65, 15, 80, 72, 0, 64, 12, 74, 65, + 28, 27, 67, 88, 112, 87, 89, 71, 80, 72, 0, + 85, 70, 27, 3, 75, 84, 89, 80, 83, 89, 103, + 64, 78, 89, 69, 82, 86, 99, 5, 74, 72, 82, 22, + 2, 22, 0, 0, 0, 71, 91, 97, 0, 6, 65, 21, 76, + 102, 82, 69, 77, 11, 9, 74, 69, 88, 5, 80, 73, + 80, 93, 82, 86, 84, 16, 66, 65, 80, 72, 90, + 77, 89, 65, 76, 72, 87, 15, 71, 2, 81, 81, 73, + 75, 68, 1, 1, 69, 6, 6, 72, 1, 66, 68, 3, 72, + 24, 67, 1, 21, 21, 29, 26, 18, 86, 66, 69, 0, + 67, 83, 68, 10, 68, 70, 7, 26, 49, 31, 6, 86, + 84, 64, 35, 39, 107, 68, 3, 87, 10, 65, 64, 7, + 33, 55, 44, 16, 86, 95, 5, 26, 35, 99, 19, 21, + 21, 21, 22, 19, 13, 20, 20, 4, 11, 11, 5, 3, + 67, 9, 6, 4, 7, 2, 6, 10, 3, 81, 3, 3, 69, 5, + 73, 38, 47, 31, 23, 30, 35, 42, 39, 23, 0, 74, + 0, 6, 6, 100, 4, 9, 77, 24, 9, 9, 9, 11, 6, + 13, 14, 72, 93, 73, 75, 70, 97, 85, 9, 13, 10, + 4, 67, 72, 77, 78, 90, 73, 24, 11, 6, 64, 5, + 68, 75, 79, 80, 69, 25, 12, 7, 2, 6, 66, 71, + 77, 85, 74, 22, 8, 8, 4, 2, 71, 76, 82, 64, + 33, 18, 13, 7, 10, 0, 69, 71, 74, 62, 99, 95, + 88, 93, 90, 88, 86, 84, 83, 83, 81, 77, 81, + 83, 86, 89, 64, 85, 78, 79, 77, 76, 74, 71, + 71, 69, 72, 70, 75, 68, 68, 9, 69, 77, 66, 68, + 71, 0, 65, 67, 71, 1, 66, 72, 73, 0, 85, 70, + 8, 68, 1, 69, 67, 66, 3, 1, 68, 68, 74, 74, + 73, 55, 52, 54, 51, 47, 52, 52, 50, 47, 46, + 49, 47, 37, 29, 12, 42, 39, 36, 29, 27, 22, + 13, 20, 14, 9, 0, 67, 72, 79, 34, 31, 34, 22, + 6, 12, 8, 64, 9, 2, 66, 82, 73, 80, 88, 10, + 14, 69, 10, 14, 3, 67, 68, 69, 80, 70, 77, 81, + 103, 79, 83, 92, 4, 73, 82, 74, 64, 3, 4, 11, + 7, 14, 0, 10, 9, 16, 20, 22, 23, 9, 54, 36, + 29, 23, 15, 13, 69, 76, 89, 4, 35, 29, 28, 20, + 19, 10, 8, 3, 66, 74, 64, 3, 4, 11, 7, 14, 0, + 10, 9, 16, 20, 22, 23, 9, 54, 36, 29, 23, 15, + 13, 69, 76, 89 }, + + { + + 52, + 7, 77, 52, 7, 77, 90, 73, 18, 11, 3, 9, 31, + 45, 26, 5, 0, 13, 79, 70, 0, 64, 13, 75, 66, + 27, 25, 70, 90, 112, 85, 88, 71, 79, 70, 0, + 85, 69, 27, 3, 74, 83, 88, 81, 84, 89, 103, + 64, 78, 88, 69, 82, 86, 99, 5, 73, 72, 81, 23, + 2, 22, 0, 0, 0, 71, 91, 97, 1, 5, 65, 20, 76, + 100, 79, 68, 76, 12, 10, 73, 68, 86, 6, 79, + 72, 78, 94, 82, 86, 84, 16, 66, 64, 78, 72, + 89, 76, 87, 65, 76, 72, 86, 15, 71, 3, 80, 80, + 72, 75, 68, 2, 2, 68, 6, 6, 72, 1, 65, 68, 3, + 72, 25, 67, 1, 22, 22, 29, 26, 18, 86, 66, 68, + 0, 67, 83, 68, 11, 68, 69, 8, 27, 50, 32, 8, + 86, 85, 0, 35, 39, 108, 68, 4, 87, 11, 65, 64, + 9, 33, 55, 44, 17, 87, 93, 4, 24, 32, 98, 19, + 21, 20, 20, 22, 19, 12, 20, 20, 3, 10, 11, 5, + 3, 67, 9, 5, 3, 6, 2, 6, 9, 3, 82, 2, 3, 69, + 4, 73, 36, 45, 30, 22, 28, 33, 39, 36, 22, 65, + 76, 64, 4, 4, 101, 3, 8, 79, 23, 8, 8, 8, 10, + 4, 11, 12, 73, 93, 74, 76, 72, 95, 83, 10, 13, + 10, 4, 66, 71, 76, 77, 88, 72, 25, 12, 7, 0, + 6, 67, 73, 78, 78, 68, 26, 13, 8, 3, 7, 65, + 71, 76, 84, 73, 23, 9, 8, 4, 3, 71, 75, 81, 0, + 33, 19, 13, 7, 11, 1, 68, 71, 73, 62, 98, 94, + 87, 91, 89, 86, 85, 82, 81, 81, 79, 75, 80, + 82, 85, 88, 2, 85, 79, 79, 76, 75, 74, 71, 71, + 69, 71, 70, 76, 68, 68, 10, 69, 77, 66, 68, + 71, 64, 65, 67, 72, 0, 66, 72, 73, 1, 85, 70, + 9, 69, 1, 69, 67, 66, 3, 0, 68, 67, 74, 74, + 74, 54, 51, 53, 49, 45, 50, 49, 47, 44, 43, + 46, 44, 34, 26, 10, 38, 36, 32, 24, 24, 20, + 12, 18, 11, 7, 64, 68, 73, 79, 32, 29, 32, 20, + 4, 10, 7, 66, 7, 0, 68, 84, 75, 82, 89, 9, 13, + 71, 9, 12, 2, 69, 69, 70, 81, 71, 78, 81, 102, + 80, 84, 93, 3, 74, 83, 74, 0, 3, 5, 12, 8, 15, + 0, 11, 10, 17, 21, 23, 23, 10, 54, 34, 27, 21, + 13, 11, 71, 78, 90, 4, 36, 30, 29, 21, 20, 11, + 8, 3, 65, 74, 0, 3, 5, 12, 8, 15, 0, 11, 10, + 17, 21, 23, 23, 10, 54, 34, 27, 21, 13, 11, + 71, 78, 90 }, + + { + + 51, + 7, 78, 51, 7, 78, 88, 72, 19, 11, 3, 8, 29, + 44, 26, 6, 2, 12, 79, 69, 0, 65, 14, 76, 67, + 26, 24, 74, 92, 113, 83, 86, 71, 79, 69, 0, + 84, 68, 28, 3, 73, 82, 87, 83, 85, 89, 104, + 64, 78, 88, 69, 83, 85, 99, 4, 73, 72, 80, 23, + 2, 22, 0, 0, 0, 70, 91, 97, 1, 5, 66, 20, 75, + 99, 77, 67, 75, 14, 12, 72, 67, 84, 7, 78, 72, + 76, 95, 82, 86, 84, 17, 66, 0, 76, 72, 88, 75, + 85, 65, 76, 72, 86, 16, 72, 3, 80, 80, 71, 75, + 67, 2, 2, 67, 7, 6, 72, 1, 65, 68, 3, 72, 25, + 67, 1, 22, 22, 30, 27, 19, 87, 65, 67, 64, 67, + 83, 68, 12, 68, 69, 9, 28, 50, 34, 9, 87, 86, + 0, 35, 39, 108, 69, 4, 88, 11, 66, 0, 10, 33, + 55, 45, 18, 88, 92, 3, 21, 29, 96, 19, 21, 20, + 19, 21, 18, 12, 19, 19, 3, 10, 11, 4, 3, 68, + 8, 4, 2, 6, 2, 6, 9, 2, 84, 2, 2, 69, 3, 74, + 33, 43, 28, 20, 26, 31, 36, 34, 20, 67, 78, + 66, 2, 2, 103, 2, 6, 81, 21, 7, 7, 7, 8, 2, 9, + 10, 75, 93, 75, 77, 73, 94, 82, 12, 14, 10, 4, + 65, 71, 76, 76, 87, 71, 26, 13, 8, 0, 7, 66, + 72, 77, 76, 68, 26, 13, 8, 3, 8, 65, 70, 75, + 83, 73, 25, 10, 8, 4, 3, 70, 75, 81, 0, 34, + 19, 14, 7, 12, 1, 68, 70, 73, 62, 96, 92, 85, + 89, 87, 85, 83, 81, 80, 79, 77, 73, 80, 81, + 85, 88, 5, 85, 80, 79, 76, 74, 74, 71, 71, 69, + 71, 70, 77, 68, 68, 10, 69, 77, 66, 68, 71, + 64, 65, 67, 73, 64, 66, 72, 73, 2, 86, 70, 10, + 70, 1, 69, 67, 65, 3, 0, 67, 66, 74, 74, 76, + 53, 50, 52, 47, 43, 47, 47, 44, 42, 40, 43, + 41, 31, 23, 8, 35, 32, 28, 19, 22, 17, 10, 16, + 9, 5, 65, 69, 74, 79, 30, 27, 30, 18, 2, 9, 5, + 68, 5, 66, 70, 86, 77, 84, 90, 8, 11, 73, 7, + 10, 0, 71, 71, 72, 82, 72, 79, 82, 101, 81, + 86, 95, 2, 76, 84, 73, 0, 4, 5, 13, 9, 16, 0, + 12, 11, 18, 21, 24, 24, 11, 53, 32, 25, 19, + 11, 9, 73, 80, 91, 5, 36, 30, 30, 21, 21, 11, + 9, 4, 65, 73, 0, 4, 5, 13, 9, 16, 0, 12, 11, + 18, 21, 24, 24, 11, 53, 32, 25, 19, 11, 9, 73, + 80, 91 }, + + { + + 50, + 7, 78, 50, 7, 78, 86, 70, 19, 11, 2, 6, 28, + 43, 27, 6, 3, 10, 79, 67, 64, 65, 15, 77, 68, + 25, 22, 77, 95, 114, 81, 85, 71, 79, 67, 64, + 84, 67, 28, 3, 73, 81, 85, 84, 86, 89, 104, + 64, 77, 87, 70, 83, 85, 99, 4, 73, 72, 79, 24, + 2, 22, 0, 0, 0, 70, 91, 97, 2, 4, 66, 19, 75, + 98, 75, 66, 74, 15, 13, 71, 66, 82, 8, 77, 71, + 73, 95, 83, 86, 85, 17, 66, 1, 73, 71, 88, 75, + 83, 65, 76, 72, 86, 16, 72, 4, 79, 80, 70, 74, + 67, 2, 3, 66, 7, 6, 73, 1, 65, 68, 3, 72, 25, + 67, 1, 23, 22, 30, 27, 19, 87, 65, 67, 64, 67, + 82, 68, 13, 67, 68, 11, 30, 51, 35, 11, 87, + 87, 1, 36, 39, 109, 69, 5, 88, 12, 66, 0, 11, + 33, 55, 45, 20, 89, 90, 2, 18, 26, 95, 19, 21, + 19, 19, 21, 18, 11, 19, 19, 2, 9, 11, 4, 3, + 68, 8, 4, 1, 5, 2, 6, 8, 2, 85, 1, 1, 69, 2, + 74, 31, 41, 26, 19, 25, 29, 33, 31, 19, 69, + 80, 67, 0, 0, 104, 1, 5, 82, 20, 6, 6, 5, 7, + 0, 7, 9, 76, 93, 76, 78, 75, 92, 80, 13, 14, + 10, 4, 64, 70, 75, 76, 85, 69, 27, 14, 8, 1, + 9, 65, 70, 76, 73, 67, 27, 14, 9, 4, 9, 64, + 70, 74, 82, 73, 26, 11, 8, 4, 4, 70, 75, 80, + 1, 34, 20, 14, 7, 13, 2, 67, 70, 72, 62, 95, + 91, 84, 88, 86, 83, 82, 79, 78, 77, 75, 70, + 79, 80, 84, 87, 8, 84, 81, 78, 75, 73, 74, 71, + 71, 69, 70, 71, 78, 68, 68, 11, 69, 77, 66, + 68, 70, 65, 66, 67, 74, 65, 67, 72, 73, 3, 87, + 71, 11, 70, 0, 69, 67, 65, 2, 64, 67, 66, 74, + 74, 77, 52, 49, 51, 46, 40, 45, 44, 41, 39, + 37, 40, 38, 28, 21, 6, 31, 29, 24, 14, 19, 15, + 8, 14, 6, 3, 66, 70, 75, 79, 28, 24, 27, 16, + 0, 7, 4, 70, 3, 68, 72, 88, 79, 85, 92, 7, 10, + 75, 6, 9, 64, 73, 72, 73, 83, 73, 80, 82, 100, + 83, 87, 96, 0, 77, 85, 73, 1, 4, 6, 14, 10, + 16, 1, 13, 12, 19, 22, 25, 24, 11, 53, 30, 23, + 17, 9, 7, 76, 82, 92, 5, 37, 31, 30, 22, 22, + 12, 9, 4, 64, 73, 1, 4, 6, 14, 10, 16, 1, 13, + 12, 19, 22, 25, 24, 11, 53, 30, 23, 17, 9, 7, + 76, 82, 92 }, + + { + + 48, + 6, 78, 48, 6, 78, 85, 69, 19, 11, 2, 4, 26, + 42, 27, 6, 5, 8, 79, 66, 64, 66, 16, 78, 70, + 23, 20, 81, 97, 115, 79, 84, 71, 79, 66, 64, + 84, 67, 28, 2, 72, 80, 84, 85, 88, 90, 105, + 64, 77, 86, 70, 84, 85, 99, 4, 73, 72, 79, 24, + 2, 22, 0, 0, 0, 69, 92, 97, 2, 3, 66, 19, 74, + 97, 73, 65, 74, 17, 15, 71, 65, 80, 8, 76, 70, + 71, 96, 83, 87, 85, 17, 67, 1, 71, 71, 87, 74, + 82, 65, 76, 72, 86, 16, 72, 4, 78, 80, 70, 74, + 67, 2, 3, 65, 8, 6, 73, 0, 65, 68, 2, 72, 25, + 67, 1, 23, 22, 30, 27, 19, 88, 64, 66, 65, 67, + 82, 68, 14, 67, 68, 12, 31, 52, 37, 13, 88, + 88, 1, 36, 39, 110, 69, 5, 89, 12, 66, 0, 12, + 33, 55, 45, 21, 90, 89, 1, 15, 22, 94, 18, 21, + 19, 18, 20, 17, 11, 19, 18, 1, 8, 10, 4, 2, + 69, 7, 3, 0, 5, 1, 5, 7, 1, 86, 0, 0, 69, 0, + 75, 29, 39, 24, 17, 23, 26, 29, 29, 18, 71, + 83, 69, 66, 65, 106, 64, 4, 84, 19, 5, 5, 4, + 5, 65, 5, 7, 78, 93, 77, 79, 77, 91, 79, 14, + 15, 10, 4, 64, 69, 74, 75, 83, 68, 27, 14, 9, + 1, 10, 64, 69, 75, 71, 67, 27, 14, 9, 4, 9, + 64, 69, 74, 81, 73, 27, 12, 8, 4, 5, 69, 75, + 79, 2, 34, 21, 14, 7, 14, 2, 66, 69, 72, 62, + 94, 89, 82, 86, 85, 82, 80, 78, 77, 76, 73, + 68, 79, 79, 84, 86, 11, 84, 82, 78, 74, 73, + 74, 71, 71, 70, 70, 71, 79, 68, 68, 11, 69, + 77, 67, 68, 70, 65, 66, 68, 75, 66, 67, 73, + 73, 4, 88, 71, 11, 71, 0, 69, 68, 65, 2, 64, + 67, 65, 74, 74, 79, 51, 48, 50, 44, 38, 42, + 41, 38, 37, 34, 36, 35, 24, 18, 4, 27, 25, 20, + 9, 16, 13, 6, 11, 4, 1, 68, 72, 76, 79, 25, + 22, 25, 14, 66, 5, 2, 73, 1, 70, 74, 90, 82, + 87, 93, 5, 9, 78, 4, 7, 66, 75, 74, 75, 84, + 74, 81, 82, 99, 84, 89, 98, 64, 78, 86, 73, 1, + 5, 6, 15, 10, 17, 1, 14, 12, 20, 22, 25, 25, + 12, 52, 28, 21, 15, 7, 5, 78, 84, 94, 6, 37, + 31, 31, 22, 22, 12, 10, 4, 64, 73, 1, 5, 6, + 15, 10, 17, 1, 14, 12, 20, 22, 25, 25, 12, 52, + 28, 21, 15, 7, 5, 78, 84, 94 }, + + { + + 47, + 6, 78, 47, 6, 78, 83, 68, 20, 11, 2, 2, 24, + 42, 28, 6, 7, 7, 78, 64, 64, 66, 17, 78, 71, + 22, 18, 84, 99, 115, 77, 82, 70, 78, 64, 64, + 83, 66, 29, 2, 71, 79, 83, 86, 89, 90, 105, + 64, 77, 85, 70, 84, 85, 99, 4, 72, 71, 78, 24, + 2, 22, 0, 0, 0, 68, 92, 97, 2, 2, 66, 19, 73, + 95, 70, 64, 73, 19, 17, 70, 64, 78, 9, 74, 69, + 69, 97, 83, 87, 85, 18, 67, 2, 69, 71, 86, 73, + 80, 65, 75, 72, 85, 17, 72, 5, 77, 79, 69, 74, + 67, 3, 4, 64, 9, 6, 73, 0, 64, 67, 2, 72, 26, + 67, 1, 23, 23, 31, 27, 20, 88, 0, 65, 66, 67, + 82, 68, 15, 67, 67, 13, 32, 53, 39, 15, 89, + 89, 2, 36, 40, 111, 69, 5, 89, 13, 66, 0, 14, + 33, 55, 45, 22, 91, 88, 0, 13, 19, 92, 18, 21, + 19, 17, 20, 17, 11, 19, 18, 0, 7, 10, 4, 2, + 69, 7, 2, 0, 5, 1, 5, 6, 0, 87, 0, 0, 69, 64, + 75, 27, 37, 23, 16, 21, 24, 26, 27, 17, 73, + 85, 70, 68, 66, 107, 65, 3, 86, 18, 4, 4, 3, + 3, 66, 3, 5, 79, 93, 77, 80, 78, 89, 77, 16, + 16, 10, 4, 0, 68, 73, 74, 81, 67, 28, 15, 10, + 2, 11, 0, 68, 74, 69, 66, 28, 15, 10, 4, 10, + 0, 68, 73, 79, 72, 29, 13, 9, 4, 6, 68, 74, + 78, 3, 35, 22, 15, 8, 15, 3, 65, 68, 71, 62, + 92, 87, 80, 84, 84, 81, 78, 76, 75, 74, 71, + 66, 78, 78, 83, 85, 15, 84, 83, 78, 73, 72, + 73, 71, 71, 70, 70, 71, 80, 68, 68, 12, 69, + 77, 67, 68, 70, 65, 66, 68, 75, 67, 67, 73, + 72, 6, 88, 71, 12, 72, 0, 69, 68, 64, 2, 64, + 66, 64, 73, 73, 81, 50, 47, 49, 42, 36, 39, + 39, 35, 35, 32, 33, 33, 21, 15, 2, 23, 22, 17, + 4, 13, 11, 5, 9, 2, 0, 69, 73, 77, 79, 23, 20, + 23, 12, 68, 3, 1, 75, 64, 72, 76, 92, 84, 89, + 94, 4, 8, 80, 3, 5, 67, 77, 75, 76, 85, 75, + 81, 82, 98, 85, 90, 100, 65, 79, 87, 73, 2, 6, + 7, 17, 11, 18, 1, 15, 13, 21, 23, 26, 26, 13, + 51, 27, 19, 13, 5, 3, 80, 86, 95, 7, 37, 32, + 32, 23, 23, 13, 11, 5, 0, 73, 2, 6, 7, 17, 11, + 18, 1, 15, 13, 21, 23, 26, 26, 13, 51, 27, 19, + 13, 5, 3, 80, 86, 95 }, + + { + + 46, + 6, 78, 46, 6, 78, 81, 66, 20, 11, 1, 0, 23, + 41, 29, 6, 8, 5, 78, 0, 65, 66, 18, 79, 72, + 21, 16, 87, 102, 116, 75, 81, 70, 78, 0, 65, + 83, 65, 29, 2, 71, 78, 81, 87, 90, 90, 105, + 64, 76, 84, 71, 84, 85, 99, 4, 72, 71, 77, 25, + 2, 22, 0, 0, 0, 68, 92, 97, 3, 1, 66, 18, 73, + 94, 68, 0, 72, 20, 18, 69, 0, 76, 10, 73, 68, + 66, 97, 84, 87, 86, 18, 67, 3, 66, 70, 86, 73, + 78, 65, 75, 72, 85, 17, 72, 5, 76, 79, 68, 73, + 67, 3, 4, 0, 9, 6, 74, 0, 64, 67, 2, 72, 26, + 67, 1, 24, 23, 31, 27, 20, 89, 0, 65, 66, 67, + 81, 68, 16, 66, 67, 15, 34, 54, 40, 17, 89, + 90, 2, 37, 40, 112, 69, 6, 90, 14, 66, 0, 15, + 33, 55, 45, 24, 92, 86, 64, 10, 16, 91, 18, + 21, 18, 17, 19, 17, 10, 19, 17, 64, 6, 10, 4, + 2, 69, 6, 2, 64, 4, 1, 5, 5, 0, 88, 64, 64, + 69, 65, 76, 25, 35, 21, 14, 20, 22, 23, 24, + 16, 75, 87, 72, 70, 68, 108, 66, 2, 87, 17, 3, + 3, 1, 2, 68, 1, 4, 81, 93, 78, 81, 80, 88, 76, + 17, 16, 10, 4, 1, 67, 72, 74, 79, 65, 29, 16, + 10, 3, 13, 1, 66, 73, 66, 65, 28, 15, 10, 5, + 11, 1, 68, 72, 78, 72, 30, 14, 9, 4, 7, 68, + 74, 77, 4, 35, 23, 15, 8, 16, 4, 64, 68, 70, + 62, 91, 86, 79, 83, 83, 79, 77, 74, 74, 72, + 69, 0, 77, 77, 82, 84, 18, 83, 84, 77, 72, 71, + 73, 71, 71, 70, 69, 72, 81, 68, 68, 13, 69, + 77, 67, 68, 69, 66, 67, 68, 76, 68, 68, 73, + 72, 7, 89, 72, 13, 72, 64, 69, 68, 64, 1, 65, + 66, 64, 73, 73, 82, 49, 46, 48, 41, 33, 37, + 36, 32, 32, 29, 30, 30, 18, 13, 0, 19, 18, 13, + 64, 10, 9, 3, 7, 64, 65, 70, 74, 78, 79, 21, + 17, 20, 10, 70, 1, 64, 77, 66, 74, 78, 94, 86, + 90, 96, 3, 7, 82, 1, 4, 69, 79, 76, 77, 86, + 76, 82, 82, 97, 87, 91, 101, 67, 80, 88, 73, + 2, 6, 8, 18, 12, 18, 2, 16, 14, 22, 23, 27, + 26, 13, 51, 25, 17, 11, 3, 1, 83, 88, 96, 7, + 38, 32, 32, 24, 24, 13, 11, 5, 1, 73, 2, 6, 8, + 18, 12, 18, 2, 16, 14, 22, 23, 27, 26, 13, 51, + 25, 17, 11, 3, 1, 83, 88, 96 }, + + { + + 45, + 6, 79, 45, 6, 79, 79, 65, 21, 11, 1, 64, 21, + 40, 29, 7, 10, 4, 78, 2, 65, 67, 19, 80, 73, + 20, 15, 91, 104, 117, 73, 79, 70, 78, 2, 65, + 82, 64, 30, 2, 70, 77, 80, 89, 91, 90, 106, + 64, 76, 84, 71, 85, 84, 99, 3, 72, 71, 76, 25, + 2, 22, 0, 0, 0, 67, 92, 97, 3, 1, 67, 18, 72, + 93, 66, 1, 71, 22, 20, 68, 1, 74, 11, 72, 68, + 64, 98, 84, 87, 86, 19, 67, 4, 64, 70, 85, 72, + 76, 65, 75, 72, 85, 18, 73, 6, 76, 79, 67, 73, + 66, 3, 5, 1, 10, 6, 74, 0, 64, 67, 2, 72, 26, + 67, 1, 24, 23, 32, 28, 21, 89, 1, 64, 67, 67, + 81, 68, 17, 66, 66, 16, 35, 54, 42, 18, 90, + 91, 3, 37, 40, 112, 70, 6, 90, 14, 67, 1, 16, + 33, 55, 46, 25, 93, 85, 65, 7, 13, 89, 18, 21, + 18, 16, 19, 16, 10, 18, 17, 64, 6, 10, 3, 2, + 70, 6, 1, 65, 4, 1, 5, 5, 64, 90, 64, 65, 69, + 66, 76, 22, 33, 19, 13, 18, 20, 20, 22, 14, + 77, 89, 73, 72, 70, 110, 67, 0, 89, 15, 2, 2, + 0, 0, 70, 64, 2, 82, 93, 79, 82, 81, 86, 74, + 19, 17, 10, 4, 2, 67, 72, 73, 78, 64, 30, 17, + 11, 3, 14, 2, 65, 72, 64, 65, 29, 16, 11, 5, + 12, 1, 67, 71, 77, 72, 32, 15, 9, 4, 7, 67, + 74, 77, 4, 36, 23, 16, 8, 17, 4, 64, 67, 70, + 62, 89, 84, 77, 81, 81, 78, 75, 73, 72, 70, + 67, 2, 77, 76, 82, 84, 21, 83, 85, 77, 72, 70, + 73, 71, 71, 70, 69, 72, 82, 68, 68, 13, 69, + 77, 67, 68, 69, 66, 67, 68, 77, 69, 68, 73, + 72, 8, 90, 72, 14, 73, 64, 69, 68, 0, 1, 65, + 65, 0, 73, 73, 84, 48, 45, 47, 39, 31, 34, 34, + 29, 30, 26, 27, 27, 15, 10, 65, 16, 15, 9, 69, + 8, 6, 1, 5, 66, 67, 71, 75, 79, 79, 19, 15, + 18, 8, 72, 0, 65, 79, 68, 77, 80, 96, 88, 92, + 97, 2, 5, 84, 0, 2, 70, 81, 78, 79, 87, 77, + 83, 83, 96, 88, 93, 103, 68, 82, 89, 72, 3, 7, + 8, 19, 13, 19, 2, 17, 15, 23, 24, 28, 27, 14, + 50, 23, 15, 9, 1, 64, 85, 90, 97, 8, 38, 33, + 33, 24, 25, 14, 12, 6, 1, 72, 3, 7, 8, 19, 13, + 19, 2, 17, 15, 23, 24, 28, 27, 14, 50, 23, 15, + 9, 1, 64, 85, 90, 97 }, + + { + + 43, + 6, 79, 43, 6, 79, 78, 0, 21, 11, 0, 66, 20, + 39, 30, 7, 12, 2, 78, 3, 65, 67, 20, 81, 75, + 18, 13, 94, 106, 118, 71, 78, 70, 78, 3, 65, + 82, 0, 30, 1, 69, 76, 79, 90, 93, 91, 106, 64, + 76, 83, 71, 85, 84, 99, 3, 72, 71, 76, 26, 2, + 22, 0, 0, 0, 67, 93, 97, 4, 0, 67, 17, 72, 92, + 64, 2, 70, 23, 21, 68, 2, 72, 12, 71, 67, 1, + 99, 84, 87, 86, 19, 68, 5, 1, 70, 84, 71, 74, + 65, 75, 72, 85, 18, 73, 6, 75, 79, 67, 73, 66, + 3, 5, 2, 10, 6, 74, 64, 64, 67, 2, 72, 26, 67, + 1, 25, 23, 32, 28, 21, 90, 1, 0, 67, 67, 81, + 68, 18, 66, 66, 17, 36, 55, 43, 20, 90, 92, 3, + 37, 40, 113, 70, 7, 91, 15, 67, 1, 17, 33, 55, + 46, 26, 94, 83, 66, 4, 10, 88, 17, 21, 17, 15, + 18, 16, 9, 18, 16, 65, 5, 9, 3, 2, 70, 5, 0, + 66, 3, 0, 4, 4, 64, 91, 65, 66, 69, 68, 77, + 20, 31, 17, 11, 16, 18, 16, 19, 13, 79, 92, + 75, 74, 72, 111, 69, 64, 91, 14, 1, 1, 64, 64, + 72, 66, 0, 84, 93, 80, 83, 83, 85, 73, 20, 17, + 10, 4, 3, 66, 71, 72, 76, 0, 31, 17, 12, 4, + 15, 3, 0, 71, 1, 64, 29, 16, 11, 6, 13, 2, 67, + 71, 76, 72, 33, 16, 9, 4, 8, 67, 74, 76, 5, + 36, 24, 16, 8, 18, 5, 0, 67, 69, 62, 88, 83, + 76, 79, 80, 76, 74, 71, 71, 69, 65, 4, 76, 75, + 81, 83, 24, 83, 86, 77, 71, 70, 73, 71, 71, + 71, 68, 72, 83, 68, 68, 14, 69, 77, 67, 68, + 69, 67, 67, 68, 78, 70, 68, 73, 72, 9, 91, 72, + 14, 74, 64, 69, 68, 0, 1, 66, 65, 1, 73, 73, + 85, 47, 44, 46, 37, 29, 32, 31, 26, 27, 23, + 23, 24, 11, 7, 67, 12, 11, 5, 74, 5, 4, 64, 3, + 69, 69, 73, 76, 80, 79, 16, 13, 16, 6, 74, 65, + 67, 81, 70, 79, 82, 98, 91, 94, 98, 1, 4, 87, + 65, 0, 72, 83, 79, 80, 88, 78, 84, 83, 95, 89, + 94, 104, 69, 83, 90, 72, 3, 7, 9, 20, 13, 20, + 2, 18, 16, 24, 24, 29, 27, 15, 50, 21, 13, 7, + 64, 66, 87, 92, 99, 8, 39, 33, 34, 25, 25, 14, + 12, 6, 2, 72, 3, 7, 9, 20, 13, 20, 2, 18, 16, + 24, 24, 29, 27, 15, 50, 21, 13, 7, 64, 66, 87, + 92, 99 }, + + { + + 42, + 6, 79, 42, 6, 79, 76, 1, 21, 11, 0, 68, 18, + 38, 31, 7, 13, 0, 77, 5, 66, 67, 21, 82, 76, + 17, 11, 97, 109, 118, 69, 77, 70, 77, 5, 66, + 82, 1, 30, 1, 69, 75, 77, 91, 94, 91, 106, 64, + 75, 82, 72, 85, 84, 99, 3, 71, 71, 75, 26, 2, + 22, 0, 0, 0, 66, 93, 97, 4, 64, 67, 17, 71, + 90, 2, 3, 69, 25, 23, 67, 3, 70, 13, 70, 66, + 4, 99, 85, 87, 87, 19, 68, 6, 4, 69, 84, 71, + 72, 65, 75, 72, 84, 18, 73, 7, 74, 78, 66, 72, + 66, 4, 6, 3, 11, 6, 75, 64, 0, 67, 2, 72, 27, + 67, 1, 25, 24, 32, 28, 21, 90, 2, 0, 68, 67, + 80, 68, 19, 65, 65, 19, 38, 56, 45, 22, 91, + 93, 4, 38, 40, 114, 70, 7, 91, 16, 67, 1, 19, + 33, 55, 46, 28, 95, 82, 67, 2, 7, 87, 17, 21, + 17, 15, 18, 16, 9, 18, 16, 66, 4, 9, 3, 2, 70, + 5, 0, 67, 3, 0, 4, 3, 65, 92, 66, 66, 69, 69, + 77, 18, 29, 16, 10, 15, 16, 13, 17, 12, 81, + 94, 76, 76, 74, 112, 70, 65, 92, 13, 0, 0, 66, + 66, 74, 68, 64, 85, 93, 81, 84, 85, 83, 71, + 21, 18, 10, 4, 4, 65, 70, 72, 74, 2, 32, 18, + 12, 5, 17, 4, 1, 70, 4, 0, 30, 17, 12, 6, 14, + 3, 66, 70, 75, 71, 34, 17, 9, 4, 9, 66, 73, + 75, 6, 36, 25, 16, 8, 19, 6, 1, 66, 68, 62, + 87, 81, 74, 78, 79, 75, 72, 69, 69, 67, 0, 7, + 75, 74, 80, 82, 27, 82, 87, 76, 70, 69, 73, + 71, 71, 71, 68, 73, 84, 68, 68, 15, 69, 77, + 67, 68, 68, 67, 68, 68, 79, 71, 69, 73, 72, + 10, 91, 73, 15, 74, 65, 69, 68, 0, 0, 66, 65, + 1, 73, 73, 87, 46, 43, 45, 36, 26, 29, 28, 23, + 25, 20, 20, 21, 8, 5, 69, 8, 8, 1, 79, 2, 2, + 65, 1, 71, 71, 74, 77, 81, 79, 14, 10, 13, 4, + 76, 67, 68, 83, 72, 81, 84, 100, 93, 95, 100, + 0, 3, 89, 66, 64, 73, 85, 80, 81, 89, 79, 85, + 83, 94, 91, 95, 106, 71, 84, 91, 72, 4, 8, 10, + 21, 14, 20, 3, 19, 17, 25, 25, 30, 28, 15, 49, + 19, 11, 5, 66, 68, 90, 94, 100, 9, 39, 34, 34, + 26, 26, 15, 13, 6, 3, 72, 4, 8, 10, 21, 14, + 20, 3, 19, 17, 25, 25, 30, 28, 15, 49, 19, 11, + 5, 66, 68, 90, 94, 100 }, + + { + + 41, + 6, 79, 41, 6, 79, 74, 3, 22, 11, 64, 70, 17, + 37, 31, 7, 15, 64, 77, 6, 66, 68, 22, 83, 77, + 16, 9, 101, 111, 119, 67, 75, 70, 77, 6, 66, + 81, 2, 31, 1, 68, 74, 76, 92, 95, 91, 107, 64, + 75, 81, 72, 86, 84, 99, 3, 71, 71, 74, 27, 2, + 22, 0, 0, 0, 66, 93, 97, 5, 65, 67, 16, 71, + 89, 4, 4, 68, 26, 24, 66, 4, 68, 14, 69, 65, + 6, 100, 85, 87, 87, 20, 68, 7, 6, 69, 83, 70, + 70, 65, 75, 72, 84, 19, 73, 7, 73, 78, 65, 72, + 66, 4, 6, 4, 11, 6, 75, 64, 0, 67, 2, 72, 27, + 67, 1, 26, 24, 33, 28, 22, 91, 2, 1, 68, 67, + 80, 68, 20, 65, 65, 20, 39, 57, 46, 24, 91, + 94, 4, 38, 40, 115, 70, 8, 92, 16, 67, 1, 20, + 33, 55, 46, 29, 96, 80, 68, 64, 4, 85, 17, 21, + 16, 14, 17, 15, 8, 18, 15, 67, 3, 9, 3, 2, 71, + 4, 64, 68, 2, 0, 4, 2, 65, 93, 66, 67, 69, 70, + 78, 16, 27, 14, 8, 13, 14, 10, 14, 11, 83, 96, + 78, 78, 76, 114, 71, 66, 94, 12, 64, 64, 67, + 67, 76, 70, 66, 87, 93, 82, 85, 86, 82, 70, + 23, 18, 10, 4, 5, 64, 69, 71, 72, 3, 33, 19, + 13, 5, 18, 5, 3, 69, 6, 0, 30, 17, 12, 7, 15, + 3, 66, 69, 74, 71, 36, 18, 9, 4, 10, 66, 73, + 74, 7, 37, 26, 17, 8, 20, 6, 2, 66, 68, 62, + 85, 80, 73, 76, 78, 73, 71, 68, 68, 65, 2, 9, + 75, 73, 80, 81, 30, 82, 88, 76, 69, 68, 73, + 71, 71, 71, 67, 73, 85, 68, 68, 15, 69, 77, + 67, 68, 68, 68, 68, 68, 80, 72, 69, 73, 72, + 11, 92, 73, 16, 75, 65, 69, 68, 1, 0, 67, 64, + 2, 73, 73, 88, 45, 42, 44, 34, 24, 27, 26, 20, + 22, 17, 17, 18, 5, 2, 71, 4, 4, 66, 84, 64, 0, + 67, 64, 74, 73, 75, 78, 82, 79, 12, 8, 11, 2, + 78, 69, 70, 85, 74, 83, 86, 102, 95, 97, 101, + 64, 2, 91, 68, 66, 75, 87, 82, 83, 90, 80, 86, + 83, 93, 92, 97, 107, 72, 85, 92, 72, 4, 8, 10, + 22, 15, 21, 3, 20, 18, 26, 25, 31, 28, 16, 49, + 17, 9, 3, 68, 70, 92, 96, 101, 9, 40, 34, 35, + 26, 27, 15, 13, 7, 3, 72, 4, 8, 10, 22, 15, + 21, 3, 20, 18, 26, 25, 31, 28, 16, 49, 17, 9, + 3, 68, 70, 92, 96, 101 }, + + { + + 40, + 6, 79, 40, 6, 79, 72, 4, 22, 11, 64, 72, 15, + 36, 32, 7, 17, 66, 77, 8, 66, 68, 23, 84, 78, + 15, 7, 104, 113, 120, 65, 74, 70, 77, 8, 66, + 81, 3, 31, 1, 67, 73, 75, 93, 96, 91, 107, 64, + 75, 80, 72, 86, 84, 99, 3, 71, 71, 73, 27, 2, + 22, 0, 0, 0, 65, 93, 97, 5, 66, 67, 16, 70, + 88, 6, 5, 67, 28, 26, 65, 5, 66, 15, 68, 64, + 8, 101, 85, 87, 87, 20, 68, 8, 8, 69, 82, 69, + 68, 65, 75, 72, 84, 19, 73, 8, 72, 78, 64, 72, + 66, 4, 7, 5, 12, 6, 75, 64, 0, 67, 2, 72, 27, + 67, 1, 26, 24, 33, 28, 22, 91, 3, 2, 69, 67, + 80, 68, 21, 65, 64, 21, 40, 58, 48, 26, 92, + 95, 5, 38, 40, 116, 70, 8, 92, 17, 67, 1, 21, + 33, 55, 46, 30, 97, 79, 69, 67, 1, 84, 17, 21, + 16, 13, 17, 15, 8, 18, 15, 68, 2, 9, 3, 2, 71, + 4, 65, 69, 2, 0, 4, 1, 66, 94, 67, 68, 69, 71, + 78, 14, 25, 12, 7, 11, 12, 7, 12, 10, 85, 98, + 79, 80, 78, 115, 72, 67, 96, 11, 65, 65, 68, + 69, 78, 72, 68, 88, 93, 83, 86, 88, 80, 68, + 24, 19, 10, 4, 6, 0, 68, 70, 70, 4, 34, 20, + 14, 6, 19, 6, 4, 68, 8, 1, 31, 18, 13, 7, 16, + 4, 65, 68, 73, 71, 37, 19, 9, 4, 11, 65, 73, + 73, 8, 37, 27, 17, 8, 21, 7, 3, 65, 67, 62, + 84, 78, 71, 74, 77, 72, 69, 66, 66, 0, 4, 11, + 74, 72, 79, 80, 33, 82, 89, 76, 68, 67, 73, + 71, 71, 71, 67, 73, 86, 68, 68, 16, 69, 77, + 67, 68, 68, 68, 68, 68, 81, 73, 69, 73, 72, + 12, 93, 73, 17, 76, 65, 69, 68, 1, 0, 67, 64, + 3, 73, 73, 90, 44, 41, 43, 32, 22, 24, 23, 17, + 20, 14, 14, 15, 2, 64, 73, 0, 1, 70, 89, 67, + 65, 69, 66, 76, 75, 76, 79, 83, 79, 10, 6, 9, + 0, 80, 71, 71, 87, 76, 85, 88, 104, 97, 99, + 102, 65, 1, 93, 69, 68, 76, 89, 83, 84, 91, + 81, 87, 83, 92, 93, 98, 109, 73, 86, 93, 72, + 5, 9, 11, 23, 16, 22, 3, 21, 19, 27, 26, 32, + 29, 17, 48, 15, 7, 1, 70, 72, 94, 98, 102, 10, + 40, 35, 36, 27, 28, 16, 14, 7, 4, 72, 5, 9, + 11, 23, 16, 22, 3, 21, 19, 27, 26, 32, 29, 17, + 48, 15, 7, 1, 70, 72, 94, 98, 102 }, + + { + + 38, + 5, 80, 38, 5, 80, 71, 5, 22, 11, 65, 74, 13, + 35, 32, 7, 18, 68, 77, 9, 67, 69, 24, 85, 80, + 13, 5, 108, 116, 121, 0, 73, 70, 77, 9, 67, + 81, 3, 31, 0, 67, 73, 74, 95, 98, 92, 108, 65, + 75, 80, 73, 87, 84, 99, 2, 71, 71, 73, 27, 1, + 22, 0, 0, 0, 65, 94, 97, 5, 67, 68, 15, 70, + 87, 8, 6, 67, 29, 27, 65, 6, 65, 15, 67, 64, + 10, 102, 86, 88, 88, 20, 69, 8, 10, 69, 82, + 69, 67, 65, 75, 72, 84, 19, 74, 8, 72, 78, 64, + 72, 66, 4, 7, 6, 12, 6, 76, 65, 0, 67, 1, 72, + 27, 67, 1, 26, 24, 33, 28, 22, 92, 3, 2, 70, + 67, 80, 69, 21, 65, 64, 22, 41, 58, 49, 27, + 93, 97, 5, 38, 40, 117, 71, 8, 93, 17, 68, 1, + 22, 33, 54, 46, 31, 98, 78, 71, 70, 66, 83, + 16, 21, 15, 12, 16, 14, 7, 17, 14, 69, 1, 8, + 2, 1, 72, 3, 66, 70, 1, 64, 3, 0, 67, 96, 68, + 69, 69, 73, 79, 11, 22, 10, 5, 9, 9, 3, 9, 8, + 87, 101, 81, 83, 80, 117, 74, 69, 98, 9, 66, + 66, 70, 71, 80, 74, 70, 90, 93, 84, 87, 90, + 79, 67, 25, 19, 10, 4, 6, 0, 68, 70, 69, 5, + 34, 20, 14, 6, 20, 7, 5, 68, 10, 1, 31, 18, + 13, 7, 16, 4, 65, 68, 72, 71, 38, 20, 9, 3, + 11, 65, 73, 73, 8, 37, 27, 17, 8, 22, 7, 3, + 65, 67, 62, 83, 77, 70, 73, 76, 71, 68, 65, + 65, 1, 5, 13, 74, 72, 79, 80, 36, 82, 91, 76, + 68, 67, 73, 71, 71, 72, 67, 74, 87, 69, 68, + 16, 70, 77, 68, 68, 68, 69, 69, 69, 82, 74, + 70, 74, 72, 13, 94, 74, 17, 77, 66, 69, 69, 1, + 64, 68, 64, 3, 73, 73, 92, 42, 40, 41, 30, 19, + 21, 20, 14, 17, 11, 10, 12, 65, 67, 75, 67, + 66, 74, 95, 70, 68, 71, 69, 79, 77, 78, 81, + 84, 79, 7, 3, 6, 65, 83, 73, 73, 90, 78, 88, + 90, 107, 100, 101, 104, 67, 64, 96, 71, 70, + 78, 91, 85, 86, 92, 82, 88, 84, 91, 95, 100, + 111, 75, 88, 95, 72, 5, 9, 11, 24, 16, 22, 3, + 22, 19, 28, 26, 32, 29, 17, 47, 13, 5, 65, 73, + 74, 97, 100, 104, 10, 40, 35, 36, 27, 28, 16, + 14, 7, 4, 72, 5, 9, 11, 24, 16, 22, 3, 22, 19, + 28, 26, 32, 29, 17, 47, 13, 5, 65, 73, 74, 97, + 100, 104 }, + + { + + 37, + 5, 80, 37, 5, 80, 69, 7, 23, 12, 65, 75, 12, + 35, 33, 8, 20, 69, 76, 11, 67, 69, 26, 85, 81, + 12, 4, 111, 118, 121, 2, 71, 69, 76, 11, 67, + 80, 4, 32, 0, 66, 72, 72, 96, 99, 92, 108, 65, + 74, 79, 73, 87, 83, 98, 2, 70, 70, 72, 28, 1, + 22, 0, 0, 0, 64, 94, 97, 6, 67, 68, 15, 69, + 85, 11, 8, 66, 31, 29, 64, 8, 0, 16, 65, 0, + 13, 102, 86, 88, 88, 21, 69, 9, 13, 68, 81, + 68, 65, 65, 74, 72, 83, 20, 74, 9, 71, 77, 0, + 71, 65, 5, 8, 7, 13, 7, 76, 65, 1, 66, 1, 71, + 28, 66, 1, 27, 25, 34, 29, 23, 92, 4, 3, 70, + 67, 79, 69, 22, 64, 0, 24, 43, 59, 51, 29, 93, + 98, 6, 39, 41, 117, 71, 9, 93, 18, 68, 2, 24, + 33, 54, 47, 33, 99, 76, 72, 72, 69, 81, 16, + 21, 15, 12, 16, 14, 7, 17, 14, 69, 1, 8, 2, 1, + 72, 3, 66, 70, 1, 64, 3, 0, 67, 97, 68, 69, + 69, 74, 79, 9, 20, 9, 4, 8, 7, 0, 7, 7, 88, + 103, 82, 85, 81, 118, 75, 70, 99, 8, 66, 66, + 71, 72, 81, 75, 71, 91, 93, 84, 87, 91, 77, + 65, 27, 20, 10, 4, 7, 1, 67, 69, 67, 7, 35, + 21, 15, 7, 22, 8, 7, 67, 13, 2, 32, 19, 14, 8, + 17, 5, 64, 67, 70, 70, 40, 21, 10, 3, 12, 64, + 72, 72, 9, 38, 28, 18, 9, 23, 8, 4, 64, 66, + 62, 81, 75, 68, 71, 74, 69, 66, 0, 0, 3, 7, + 16, 73, 71, 78, 79, 40, 81, 92, 75, 67, 66, + 72, 71, 70, 72, 66, 74, 87, 69, 68, 17, 70, + 77, 68, 68, 67, 69, 69, 69, 82, 74, 70, 74, + 71, 15, 94, 74, 18, 77, 66, 69, 69, 2, 64, 68, + 0, 4, 72, 72, 93, 41, 39, 40, 29, 17, 19, 18, + 11, 15, 9, 7, 10, 68, 69, 77, 70, 69, 77, 100, + 72, 70, 72, 71, 81, 78, 79, 82, 84, 79, 5, 1, + 4, 67, 85, 74, 74, 92, 79, 90, 91, 109, 102, + 102, 105, 68, 65, 98, 72, 71, 79, 92, 86, 87, + 93, 82, 88, 84, 90, 96, 101, 112, 76, 89, 96, + 71, 6, 10, 12, 26, 17, 23, 4, 24, 20, 29, 27, + 33, 30, 18, 47, 12, 4, 67, 75, 75, 99, 101, + 105, 11, 41, 36, 37, 28, 29, 17, 15, 8, 5, 71, + 6, 10, 12, 26, 17, 23, 4, 24, 20, 29, 27, 33, + 30, 18, 47, 12, 4, 67, 75, 75, 99, 101, 105 }, + + { + + 36, + 5, 80, 36, 5, 80, 67, 8, 23, 12, 65, 77, 10, + 34, 34, 8, 22, 71, 76, 12, 67, 69, 27, 86, 82, + 11, 2, 114, 120, 122, 4, 70, 69, 76, 12, 67, + 80, 5, 32, 0, 65, 71, 71, 97, 100, 92, 108, + 65, 74, 78, 73, 87, 83, 98, 2, 70, 70, 71, 28, + 1, 22, 0, 0, 0, 0, 94, 97, 6, 68, 68, 15, 68, + 84, 13, 9, 65, 33, 31, 0, 9, 2, 17, 64, 1, 15, + 103, 86, 88, 88, 21, 69, 10, 15, 68, 80, 67, + 0, 65, 74, 72, 83, 20, 74, 9, 70, 77, 1, 71, + 65, 5, 8, 8, 14, 7, 76, 65, 1, 66, 1, 71, 28, + 66, 1, 27, 25, 34, 29, 23, 93, 5, 4, 71, 67, + 79, 69, 23, 64, 0, 25, 44, 60, 53, 31, 94, 99, + 6, 39, 41, 118, 71, 9, 94, 19, 68, 2, 25, 33, + 54, 47, 34, 100, 75, 73, 75, 72, 80, 16, 21, + 15, 11, 15, 14, 7, 17, 13, 70, 0, 8, 2, 1, 72, + 2, 67, 71, 1, 64, 3, 64, 68, 98, 69, 70, 69, + 75, 80, 7, 18, 7, 2, 6, 5, 66, 5, 6, 90, 105, + 84, 87, 83, 119, 76, 71, 101, 7, 67, 67, 72, + 74, 83, 77, 73, 93, 93, 85, 88, 93, 76, 64, + 28, 21, 10, 4, 8, 2, 66, 68, 65, 8, 36, 22, + 16, 8, 23, 9, 8, 66, 15, 3, 32, 19, 14, 8, 18, + 6, 0, 66, 69, 70, 41, 22, 10, 3, 13, 0, 72, + 71, 10, 38, 29, 18, 9, 24, 9, 5, 0, 65, 62, + 80, 73, 66, 69, 73, 68, 64, 2, 1, 5, 9, 18, + 72, 70, 77, 78, 43, 81, 93, 75, 66, 65, 72, + 71, 70, 72, 66, 74, 88, 69, 68, 18, 70, 77, + 68, 68, 67, 69, 69, 69, 83, 75, 70, 74, 71, + 16, 95, 74, 19, 78, 66, 69, 69, 2, 64, 68, 0, + 5, 72, 72, 95, 40, 38, 39, 27, 15, 16, 15, 8, + 13, 6, 4, 7, 71, 72, 79, 74, 73, 81, 105, 75, + 72, 74, 73, 83, 80, 80, 83, 85, 79, 3, 64, 2, + 69, 87, 76, 76, 94, 81, 92, 93, 111, 104, 104, + 106, 69, 66, 100, 74, 73, 81, 94, 87, 88, 94, + 83, 89, 84, 89, 97, 102, 114, 77, 90, 97, 71, + 6, 11, 13, 27, 18, 24, 4, 25, 21, 30, 27, 34, + 31, 19, 46, 10, 2, 69, 77, 77, 101, 103, 106, + 12, 41, 36, 38, 29, 30, 17, 16, 8, 6, 71, 6, + 11, 13, 27, 18, 24, 4, 25, 21, 30, 27, 34, 31, + 19, 46, 10, 2, 69, 77, 77, 101, 103, 106 }, + + { + + 35, + 5, 80, 35, 5, 80, 65, 10, 24, 12, 66, 79, 9, + 33, 34, 8, 24, 72, 76, 14, 67, 70, 28, 87, 83, + 10, 0, 118, 122, 123, 6, 68, 69, 76, 14, 67, + 79, 6, 33, 0, 64, 70, 70, 98, 101, 92, 109, + 65, 74, 77, 73, 88, 83, 98, 2, 70, 70, 70, 29, + 1, 22, 0, 0, 0, 0, 94, 97, 7, 69, 68, 14, 68, + 83, 15, 10, 64, 34, 32, 1, 10, 4, 18, 0, 2, + 17, 104, 86, 88, 88, 22, 69, 11, 17, 68, 79, + 66, 2, 65, 74, 72, 83, 21, 74, 10, 69, 77, 2, + 71, 65, 5, 9, 9, 14, 7, 76, 65, 1, 66, 1, 71, + 28, 66, 1, 28, 25, 35, 29, 24, 93, 5, 5, 71, + 67, 79, 69, 24, 64, 1, 26, 45, 61, 54, 33, 94, + 100, 7, 39, 41, 119, 71, 10, 94, 19, 68, 2, + 26, 33, 54, 47, 35, 101, 73, 74, 78, 75, 78, + 16, 21, 14, 10, 15, 13, 6, 17, 13, 71, 64, 8, + 2, 1, 73, 2, 68, 72, 0, 64, 3, 65, 68, 99, 69, + 71, 69, 76, 80, 5, 16, 5, 1, 4, 3, 69, 2, 5, + 92, 107, 85, 89, 85, 121, 77, 72, 103, 6, 68, + 68, 73, 75, 85, 79, 75, 94, 93, 86, 89, 94, + 74, 1, 30, 21, 10, 4, 9, 3, 65, 67, 0, 9, 37, + 23, 17, 8, 24, 10, 10, 65, 17, 3, 33, 20, 15, + 9, 19, 6, 0, 65, 68, 70, 43, 23, 10, 3, 14, 0, + 72, 70, 11, 39, 30, 19, 9, 25, 9, 6, 0, 65, + 62, 78, 72, 65, 67, 72, 66, 0, 3, 3, 7, 11, + 20, 72, 69, 77, 77, 46, 81, 94, 75, 65, 64, + 72, 71, 70, 72, 65, 74, 89, 69, 68, 18, 70, + 77, 68, 68, 67, 70, 69, 69, 84, 76, 70, 74, + 71, 17, 96, 74, 20, 79, 66, 69, 69, 3, 64, 69, + 1, 6, 72, 72, 96, 39, 37, 38, 25, 13, 14, 13, + 5, 10, 3, 1, 4, 74, 75, 81, 78, 76, 85, 110, + 78, 74, 76, 75, 86, 82, 81, 84, 86, 79, 1, 66, + 0, 71, 89, 78, 77, 96, 83, 94, 95, 113, 106, + 106, 107, 70, 67, 102, 75, 75, 82, 96, 89, 90, + 95, 84, 90, 84, 88, 98, 104, 115, 78, 91, 98, + 71, 7, 11, 13, 28, 19, 25, 4, 26, 22, 31, 28, + 35, 31, 20, 46, 8, 0, 71, 79, 79, 103, 105, + 107, 12, 42, 37, 39, 29, 31, 18, 16, 9, 6, 71, + 7, 11, 13, 28, 19, 25, 4, 26, 22, 31, 28, 35, + 31, 20, 46, 8, 0, 71, 79, 79, 103, 105, 107 }, + + { + + 33, + 5, 80, 33, 5, 80, 64, 11, 24, 12, 66, 81, 7, + 32, 35, 8, 25, 74, 76, 15, 68, 70, 29, 88, 85, + 8, 65, 121, 125, 124, 8, 67, 69, 76, 15, 68, + 79, 7, 33, 64, 64, 69, 68, 99, 103, 93, 109, + 65, 73, 76, 74, 88, 83, 98, 2, 70, 70, 70, 29, + 1, 22, 0, 0, 0, 1, 95, 97, 7, 70, 68, 14, 67, + 82, 17, 11, 0, 36, 34, 1, 11, 6, 19, 1, 3, 20, + 104, 87, 88, 89, 22, 70, 12, 20, 67, 79, 66, + 4, 65, 74, 72, 83, 21, 74, 10, 68, 77, 2, 70, + 65, 5, 9, 10, 15, 7, 77, 66, 1, 66, 1, 71, 28, + 66, 1, 28, 25, 35, 29, 24, 94, 6, 5, 72, 67, + 78, 69, 25, 0, 1, 28, 47, 62, 56, 35, 95, 101, + 7, 40, 41, 120, 71, 10, 95, 20, 68, 2, 27, 33, + 54, 47, 37, 102, 72, 75, 81, 78, 77, 15, 21, + 14, 10, 14, 13, 6, 17, 12, 72, 65, 7, 2, 1, + 73, 1, 68, 73, 0, 65, 2, 66, 69, 100, 70, 72, + 69, 78, 81, 3, 14, 3, 64, 3, 1, 73, 0, 4, 94, + 110, 87, 91, 87, 122, 79, 73, 104, 5, 69, 69, + 75, 77, 87, 81, 76, 96, 93, 87, 90, 96, 73, 2, + 31, 22, 10, 4, 10, 4, 64, 67, 2, 11, 38, 23, + 17, 9, 26, 11, 11, 64, 20, 4, 33, 20, 15, 9, + 20, 7, 1, 65, 67, 70, 44, 24, 10, 3, 15, 1, + 72, 69, 12, 39, 31, 19, 9, 26, 10, 7, 1, 64, + 62, 77, 70, 0, 66, 71, 65, 2, 5, 4, 8, 13, 23, + 71, 68, 76, 76, 49, 80, 95, 74, 64, 64, 72, + 71, 70, 73, 65, 75, 90, 69, 68, 19, 70, 77, + 68, 68, 66, 70, 70, 69, 85, 77, 71, 74, 71, + 18, 97, 75, 20, 79, 67, 69, 69, 3, 65, 69, 1, + 6, 72, 72, 98, 38, 36, 37, 24, 10, 11, 10, 2, + 8, 0, 66, 1, 78, 77, 83, 82, 80, 89, 115, 81, + 76, 78, 77, 88, 84, 83, 85, 87, 79, 65, 69, + 66, 73, 91, 80, 79, 98, 85, 96, 97, 115, 109, + 107, 109, 71, 68, 105, 77, 76, 84, 98, 90, 91, + 96, 85, 91, 84, 87, 100, 105, 117, 80, 92, 99, + 71, 7, 12, 14, 29, 19, 25, 5, 27, 23, 32, 28, + 36, 32, 20, 45, 6, 65, 73, 81, 81, 106, 107, + 109, 13, 42, 37, 39, 30, 31, 18, 17, 9, 7, 71, + 7, 12, 14, 29, 19, 25, 5, 27, 23, 32, 28, 36, + 32, 20, 45, 6, 65, 73, 81, 81, 106, 107, 109 }, + + { + + 32, + 5, 80, 32, 5, 80, 1, 13, 24, 12, 67, 83, 6, + 31, 36, 8, 27, 76, 75, 17, 68, 70, 30, 89, 86, + 7, 67, 124, 126, 124, 10, 66, 69, 75, 17, 68, + 79, 8, 33, 64, 0, 68, 67, 100, 104, 93, 109, + 65, 73, 75, 74, 88, 83, 98, 2, 69, 70, 69, 30, + 1, 22, 0, 0, 0, 1, 95, 97, 8, 71, 68, 13, 67, + 80, 20, 12, 1, 37, 35, 2, 12, 8, 20, 2, 4, 22, + 105, 87, 88, 89, 22, 70, 13, 22, 67, 78, 65, + 6, 65, 74, 72, 82, 21, 74, 11, 67, 76, 3, 70, + 65, 6, 10, 11, 15, 7, 77, 66, 2, 66, 1, 71, + 29, 66, 1, 29, 26, 35, 29, 24, 94, 6, 6, 72, + 67, 78, 69, 26, 0, 2, 29, 48, 62, 57, 37, 95, + 102, 8, 40, 41, 121, 71, 11, 95, 21, 68, 2, + 29, 33, 54, 47, 38, 103, 70, 76, 83, 81, 76, + 15, 21, 13, 9, 14, 13, 5, 17, 12, 73, 66, 7, + 2, 1, 73, 1, 69, 74, 64, 65, 2, 67, 69, 101, + 71, 72, 69, 79, 81, 1, 12, 2, 65, 1, 64, 76, + 66, 3, 96, 112, 88, 93, 89, 123, 80, 74, 106, + 4, 70, 70, 76, 78, 89, 83, 78, 97, 93, 88, 91, + 98, 71, 4, 32, 22, 10, 4, 11, 5, 0, 66, 4, 12, + 39, 24, 18, 10, 27, 12, 13, 0, 22, 5, 34, 21, + 16, 10, 21, 8, 1, 64, 66, 69, 45, 25, 10, 3, + 16, 1, 71, 68, 13, 39, 32, 19, 9, 27, 11, 8, + 1, 0, 62, 76, 69, 1, 64, 70, 0, 3, 7, 6, 10, + 15, 25, 70, 67, 75, 75, 52, 80, 96, 74, 0, 0, + 72, 71, 70, 73, 64, 75, 91, 69, 68, 20, 70, + 77, 68, 68, 66, 71, 70, 69, 86, 78, 71, 74, + 71, 19, 97, 75, 21, 80, 67, 69, 69, 3, 65, 70, + 1, 7, 72, 72, 99, 37, 35, 36, 22, 8, 9, 7, 64, + 5, 66, 69, 65, 81, 80, 85, 86, 83, 93, 120, + 84, 78, 79, 79, 91, 86, 84, 86, 88, 79, 67, + 71, 68, 75, 93, 82, 80, 100, 87, 98, 99, 117, + 111, 109, 110, 72, 69, 107, 78, 78, 85, 100, + 91, 92, 97, 86, 92, 84, 86, 101, 106, 118, 81, + 93, 100, 71, 8, 12, 15, 30, 20, 26, 5, 28, 24, + 33, 29, 37, 32, 21, 45, 4, 67, 75, 83, 83, + 108, 109, 110, 13, 43, 38, 40, 31, 32, 19, 17, + 9, 8, 71, 8, 12, 15, 30, 20, 26, 5, 28, 24, + 33, 29, 37, 32, 21, 45, 4, 67, 75, 83, 83, + 108, 109, 110 }, + + { + + 31, + 5, 81, 31, 5, 81, 3, 14, 25, 12, 67, 84, 4, + 30, 36, 9, 29, 77, 75, 18, 68, 71, 31, 90, 87, + 6, 68, 126, 126, 125, 12, 64, 69, 75, 18, 68, + 78, 9, 34, 64, 1, 67, 66, 102, 105, 93, 110, + 65, 73, 75, 74, 89, 82, 98, 1, 69, 70, 68, 30, + 1, 22, 0, 0, 0, 2, 95, 97, 8, 71, 69, 13, 66, + 79, 22, 13, 2, 39, 37, 3, 13, 10, 21, 3, 4, + 24, 106, 87, 88, 89, 23, 70, 14, 24, 67, 77, + 64, 8, 65, 74, 72, 82, 22, 75, 11, 67, 76, 4, + 70, 64, 6, 10, 12, 16, 7, 77, 66, 2, 66, 1, + 71, 29, 66, 1, 29, 26, 36, 30, 25, 95, 7, 7, + 73, 67, 78, 69, 27, 0, 2, 30, 49, 62, 59, 38, + 96, 103, 8, 40, 41, 121, 72, 11, 96, 21, 69, + 3, 30, 33, 54, 48, 39, 104, 69, 77, 86, 84, + 74, 15, 21, 13, 8, 13, 12, 5, 16, 11, 73, 66, + 7, 1, 1, 74, 0, 70, 75, 64, 65, 2, 67, 70, + 103, 71, 73, 69, 80, 82, 65, 10, 0, 67, 64, + 66, 79, 68, 1, 98, 114, 90, 95, 91, 125, 81, + 76, 108, 2, 71, 71, 77, 80, 91, 85, 80, 99, + 93, 89, 92, 99, 70, 5, 34, 23, 10, 4, 12, 5, + 0, 65, 5, 13, 40, 25, 19, 10, 28, 13, 14, 1, + 24, 5, 34, 21, 16, 10, 22, 8, 2, 0, 65, 69, + 47, 26, 10, 3, 16, 2, 71, 68, 13, 40, 32, 20, + 9, 28, 11, 8, 2, 0, 62, 74, 67, 3, 1, 68, 1, + 5, 8, 7, 12, 17, 27, 70, 66, 75, 75, 55, 80, + 97, 74, 0, 1, 72, 71, 70, 73, 64, 75, 92, 69, + 68, 20, 70, 77, 68, 68, 66, 71, 70, 69, 87, + 79, 71, 74, 71, 20, 98, 75, 22, 81, 67, 69, + 69, 4, 65, 70, 2, 8, 72, 72, 101, 36, 34, 35, + 20, 6, 6, 5, 67, 3, 69, 72, 68, 84, 83, 87, + 89, 87, 97, 125, 86, 81, 81, 81, 93, 88, 85, + 87, 89, 79, 69, 73, 70, 77, 95, 83, 82, 102, + 89, 101, 101, 119, 113, 111, 111, 73, 71, 109, + 80, 80, 87, 102, 93, 94, 98, 87, 93, 85, 85, + 102, 108, 120, 82, 95, 101, 70, 8, 13, 15, 31, + 21, 27, 5, 29, 25, 34, 29, 38, 33, 22, 44, 2, + 69, 77, 85, 85, 110, 111, 111, 14, 43, 38, 41, + 31, 33, 19, 18, 10, 8, 70, 8, 13, 15, 31, 21, + 27, 5, 29, 25, 34, 29, 38, 33, 22, 44, 2, 69, + 77, 85, 85, 110, 111, 111 }, + + { + + 30, + 5, 81, 30, 5, 81, 5, 16, 25, 12, 68, 86, 3, + 29, 37, 9, 30, 79, 75, 20, 69, 71, 32, 91, 88, + 5, 70, 126, 126, 126, 14, 0, 69, 75, 20, 69, + 78, 10, 34, 64, 1, 66, 64, 103, 106, 93, 110, + 65, 72, 74, 75, 89, 82, 98, 1, 69, 70, 67, 31, + 1, 22, 0, 0, 0, 2, 95, 97, 9, 72, 69, 12, 66, + 78, 24, 14, 3, 40, 38, 4, 14, 12, 22, 4, 5, + 27, 106, 88, 88, 90, 23, 70, 15, 27, 66, 77, + 64, 10, 65, 74, 72, 82, 22, 75, 12, 66, 76, 5, + 69, 64, 6, 11, 13, 16, 7, 78, 66, 2, 66, 1, + 71, 29, 66, 1, 30, 26, 36, 30, 25, 95, 7, 7, + 73, 67, 77, 69, 28, 1, 3, 32, 51, 62, 60, 40, + 96, 104, 9, 41, 41, 122, 72, 12, 96, 22, 69, + 3, 31, 33, 54, 48, 41, 105, 67, 78, 89, 87, + 73, 15, 21, 12, 8, 13, 12, 4, 16, 11, 74, 67, + 7, 1, 1, 74, 0, 70, 76, 65, 65, 2, 68, 70, + 104, 72, 74, 69, 81, 82, 67, 8, 65, 68, 65, + 68, 82, 71, 0, 100, 116, 91, 97, 93, 126, 82, + 77, 109, 1, 72, 72, 79, 81, 93, 87, 81, 100, + 93, 90, 93, 101, 68, 7, 35, 23, 10, 4, 13, 6, + 1, 65, 7, 15, 41, 26, 19, 11, 30, 14, 16, 2, + 27, 6, 35, 22, 17, 11, 23, 9, 2, 1, 64, 69, + 48, 27, 10, 3, 17, 2, 71, 67, 14, 40, 33, 20, + 9, 29, 12, 9, 2, 1, 62, 73, 66, 4, 2, 67, 3, + 6, 10, 9, 14, 19, 30, 69, 65, 74, 74, 58, 79, + 98, 73, 1, 2, 72, 71, 70, 73, 0, 76, 93, 69, + 68, 21, 70, 77, 68, 68, 65, 72, 71, 69, 88, + 80, 72, 74, 71, 21, 99, 76, 23, 81, 68, 69, + 69, 4, 66, 71, 2, 8, 72, 72, 102, 35, 33, 34, + 19, 3, 4, 2, 70, 0, 72, 75, 71, 87, 85, 89, + 93, 90, 101, 126, 89, 83, 83, 83, 96, 90, 86, + 88, 90, 79, 71, 76, 73, 79, 97, 85, 83, 104, + 91, 103, 103, 121, 115, 112, 113, 74, 72, 111, + 81, 81, 88, 104, 94, 95, 99, 88, 94, 85, 84, + 104, 109, 121, 84, 96, 102, 70, 9, 13, 16, 32, + 22, 27, 6, 30, 26, 35, 30, 39, 33, 22, 44, 0, + 71, 79, 87, 87, 113, 113, 112, 14, 44, 39, 41, + 32, 34, 20, 18, 10, 9, 70, 9, 13, 16, 32, 22, + 27, 6, 30, 26, 35, 30, 39, 33, 22, 44, 0, 71, + 79, 87, 87, 113, 113, 112 }, + + { + + 28, + 4, 81, 28, 4, 81, 6, 17, 25, 12, 68, 88, 1, + 28, 37, 9, 32, 81, 75, 21, 69, 72, 33, 92, 90, + 3, 72, 126, 126, 126, 16, 1, 69, 75, 21, 69, + 78, 10, 34, 65, 2, 65, 0, 104, 108, 94, 111, + 65, 72, 73, 75, 90, 82, 98, 1, 69, 70, 67, 31, + 1, 22, 0, 0, 0, 3, 96, 97, 9, 73, 69, 12, 65, + 77, 26, 15, 3, 42, 40, 4, 15, 14, 22, 5, 6, + 29, 107, 88, 89, 90, 23, 71, 15, 29, 66, 76, + 0, 11, 65, 74, 72, 82, 22, 75, 12, 65, 76, 5, + 69, 64, 6, 11, 14, 17, 7, 78, 67, 2, 66, 0, + 71, 29, 66, 1, 30, 26, 36, 30, 25, 96, 8, 8, + 74, 67, 77, 69, 29, 1, 3, 33, 52, 62, 62, 42, + 97, 105, 9, 41, 41, 123, 72, 12, 97, 22, 69, + 3, 32, 33, 54, 48, 42, 106, 66, 79, 92, 91, + 72, 14, 21, 12, 7, 12, 11, 4, 16, 10, 75, 68, + 6, 1, 0, 75, 64, 71, 77, 65, 66, 1, 69, 71, + 105, 73, 75, 69, 83, 83, 69, 6, 67, 70, 67, + 71, 86, 73, 64, 102, 119, 93, 100, 95, 126, + 84, 78, 111, 0, 73, 73, 80, 83, 95, 89, 83, + 102, 93, 91, 94, 103, 67, 8, 36, 24, 10, 4, + 13, 7, 2, 64, 9, 16, 41, 26, 20, 11, 31, 15, + 17, 3, 29, 6, 35, 22, 17, 11, 23, 9, 3, 1, 0, + 69, 49, 28, 10, 3, 18, 3, 71, 66, 15, 40, 34, + 20, 9, 30, 12, 10, 3, 1, 62, 72, 64, 6, 4, 66, + 4, 8, 11, 10, 15, 21, 32, 69, 64, 74, 73, 61, + 79, 99, 73, 2, 2, 72, 71, 70, 74, 0, 76, 94, + 69, 68, 21, 70, 77, 69, 68, 65, 72, 71, 70, + 89, 81, 72, 75, 71, 22, 100, 76, 23, 82, 68, + 69, 70, 4, 66, 71, 2, 9, 72, 72, 104, 34, 32, + 33, 17, 1, 1, 64, 73, 65, 75, 79, 74, 91, 88, + 91, 97, 94, 105, 126, 92, 85, 85, 86, 98, 92, + 88, 90, 91, 79, 74, 78, 75, 81, 100, 87, 85, + 107, 93, 105, 105, 123, 118, 114, 114, 76, 73, + 114, 83, 83, 90, 106, 96, 97, 100, 89, 95, 85, + 83, 105, 111, 123, 85, 97, 103, 70, 9, 14, 16, + 33, 22, 28, 6, 31, 26, 36, 30, 39, 34, 23, 43, + 65, 73, 81, 89, 89, 115, 115, 114, 15, 44, 39, + 42, 32, 34, 20, 19, 10, 9, 70, 9, 14, 16, 33, + 22, 28, 6, 31, 26, 36, 30, 39, 34, 23, 43, 65, + 73, 81, 89, 89, 115, 115, 114 }, + + { + + 27, + 4, 81, 27, 4, 81, 8, 18, 26, 12, 68, 90, 64, + 28, 38, 9, 34, 82, 74, 23, 69, 72, 34, 92, 91, + 2, 74, 126, 126, 126, 18, 3, 68, 74, 23, 69, + 77, 11, 35, 65, 3, 64, 1, 105, 109, 94, 111, + 65, 72, 72, 75, 90, 82, 98, 1, 68, 69, 66, 31, + 1, 22, 0, 0, 0, 4, 96, 97, 9, 74, 69, 12, 64, + 75, 29, 16, 4, 44, 42, 5, 16, 16, 23, 7, 7, + 31, 108, 88, 89, 90, 24, 71, 16, 31, 66, 75, + 1, 13, 65, 73, 72, 81, 23, 75, 13, 64, 75, 6, + 69, 64, 7, 12, 15, 18, 7, 78, 67, 3, 65, 0, + 71, 30, 66, 1, 30, 27, 37, 30, 26, 96, 9, 9, + 75, 67, 77, 69, 30, 1, 4, 34, 53, 62, 62, 44, + 98, 106, 10, 41, 42, 124, 72, 12, 97, 23, 69, + 3, 34, 33, 54, 48, 43, 107, 65, 80, 94, 94, + 70, 14, 21, 12, 6, 12, 11, 4, 16, 10, 76, 69, + 6, 1, 0, 75, 64, 72, 77, 65, 66, 1, 70, 72, + 106, 73, 75, 69, 84, 83, 71, 4, 68, 71, 69, + 73, 89, 75, 65, 104, 121, 94, 102, 96, 126, + 85, 79, 113, 64, 74, 74, 81, 85, 96, 91, 85, + 103, 93, 91, 95, 104, 65, 10, 38, 25, 10, 4, + 14, 8, 3, 0, 11, 17, 42, 27, 21, 12, 32, 16, + 18, 4, 31, 7, 36, 23, 18, 11, 24, 10, 4, 2, 2, + 68, 51, 29, 11, 3, 19, 4, 70, 65, 16, 41, 35, + 21, 10, 31, 13, 11, 4, 2, 62, 70, 1, 8, 6, 65, + 5, 10, 13, 12, 17, 23, 34, 68, 0, 73, 72, 62, + 79, 100, 73, 3, 3, 71, 71, 70, 74, 0, 76, 95, + 69, 68, 22, 70, 77, 69, 68, 65, 72, 71, 70, + 89, 82, 72, 75, 70, 24, 100, 76, 24, 83, 68, + 69, 70, 5, 66, 71, 3, 10, 71, 71, 106, 33, 31, + 32, 15, 64, 65, 66, 76, 67, 77, 82, 76, 94, + 91, 93, 101, 97, 108, 126, 95, 87, 86, 88, + 100, 93, 89, 91, 92, 79, 76, 80, 77, 83, 102, + 89, 86, 109, 95, 107, 107, 125, 120, 116, 115, + 77, 74, 116, 84, 85, 91, 108, 97, 98, 101, 90, + 95, 85, 82, 106, 112, 125, 86, 98, 104, 70, + 10, 15, 17, 35, 23, 29, 6, 32, 27, 37, 31, 40, + 35, 24, 42, 66, 75, 83, 91, 91, 117, 117, 115, + 16, 44, 40, 43, 33, 35, 21, 20, 11, 10, 70, + 10, 15, 17, 35, 23, 29, 6, 32, 27, 37, 31, 40, + 35, 24, 42, 66, 75, 83, 91, 91, 117, 117, 115 }, + + { + + 26, + 4, 81, 26, 4, 81, 10, 20, 26, 12, 69, 92, 65, + 27, 39, 9, 35, 84, 74, 24, 70, 72, 35, 93, 92, + 1, 76, 126, 126, 126, 20, 4, 68, 74, 24, 70, + 77, 12, 35, 65, 3, 0, 3, 106, 110, 94, 111, + 65, 71, 71, 76, 90, 82, 98, 1, 68, 69, 65, 32, + 1, 22, 0, 0, 0, 4, 96, 97, 10, 75, 69, 11, 64, + 74, 31, 17, 5, 45, 43, 6, 17, 18, 24, 8, 8, + 34, 108, 89, 89, 91, 24, 71, 17, 34, 65, 75, + 1, 15, 65, 73, 72, 81, 23, 75, 13, 0, 75, 7, + 68, 64, 7, 12, 16, 18, 7, 79, 67, 3, 65, 0, + 71, 30, 66, 1, 31, 27, 37, 30, 26, 97, 9, 9, + 75, 67, 76, 69, 31, 2, 4, 36, 55, 62, 62, 46, + 98, 107, 10, 42, 42, 125, 72, 13, 98, 24, 69, + 3, 35, 33, 54, 48, 45, 108, 0, 81, 97, 97, 69, + 14, 21, 11, 6, 11, 11, 3, 16, 9, 77, 70, 6, 1, + 0, 75, 65, 72, 78, 66, 66, 1, 71, 72, 107, 74, + 76, 69, 85, 84, 73, 2, 70, 73, 70, 75, 92, 78, + 66, 106, 123, 96, 104, 98, 126, 86, 80, 114, + 65, 75, 75, 83, 86, 98, 93, 86, 105, 93, 92, + 96, 106, 64, 11, 39, 25, 10, 4, 15, 9, 4, 0, + 13, 19, 43, 28, 21, 13, 34, 17, 20, 5, 34, 8, + 36, 23, 18, 12, 25, 11, 4, 3, 3, 68, 52, 30, + 11, 3, 20, 4, 70, 64, 17, 41, 36, 21, 10, 32, + 14, 12, 4, 3, 62, 69, 2, 9, 7, 64, 7, 11, 15, + 13, 19, 25, 37, 67, 1, 72, 71, 62, 78, 101, + 72, 4, 4, 71, 71, 70, 74, 1, 77, 96, 69, 68, + 23, 70, 77, 69, 68, 64, 73, 72, 70, 90, 83, + 73, 75, 70, 25, 101, 77, 25, 83, 69, 69, 70, + 5, 67, 72, 3, 10, 71, 71, 107, 32, 30, 31, 14, + 67, 67, 69, 79, 70, 80, 85, 79, 97, 93, 95, + 105, 101, 112, 126, 98, 89, 88, 90, 103, 95, + 90, 92, 93, 79, 78, 83, 80, 85, 104, 91, 88, + 111, 97, 109, 109, 126, 122, 117, 117, 78, 75, + 118, 86, 86, 93, 110, 98, 99, 102, 91, 96, 85, + 81, 108, 113, 126, 88, 99, 105, 70, 10, 15, + 18, 36, 24, 29, 7, 33, 28, 38, 31, 41, 35, 24, + 42, 68, 77, 85, 93, 93, 120, 119, 116, 16, 45, + 40, 43, 34, 36, 21, 20, 11, 11, 70, 10, 15, + 18, 36, 24, 29, 7, 33, 28, 38, 31, 41, 35, 24, + 42, 68, 77, 85, 93, 93, 120, 119, 116 }, + + { + + 25, + 4, 82, 25, 4, 82, 12, 21, 27, 12, 69, 93, 67, + 26, 39, 10, 37, 85, 74, 26, 70, 73, 36, 94, + 93, 0, 77, 126, 126, 126, 22, 6, 68, 74, 26, + 70, 76, 13, 36, 65, 4, 1, 4, 108, 111, 94, + 112, 65, 71, 71, 76, 91, 81, 98, 0, 68, 69, + 64, 32, 1, 22, 0, 0, 0, 5, 96, 97, 10, 75, 70, + 11, 0, 73, 33, 18, 6, 47, 45, 7, 18, 20, 25, + 9, 8, 36, 109, 89, 89, 91, 25, 71, 18, 36, 65, + 74, 2, 17, 65, 73, 72, 81, 24, 76, 14, 0, 75, + 8, 68, 0, 7, 13, 17, 19, 7, 79, 67, 3, 65, 0, + 71, 30, 66, 1, 31, 27, 38, 31, 27, 97, 10, 10, + 76, 67, 76, 69, 32, 2, 5, 37, 56, 62, 62, 47, + 99, 108, 11, 42, 42, 125, 73, 13, 98, 24, 70, + 4, 36, 33, 54, 49, 46, 109, 1, 82, 100, 100, + 67, 14, 21, 11, 5, 11, 10, 3, 15, 9, 77, 70, + 6, 0, 0, 76, 65, 73, 79, 66, 66, 1, 71, 73, + 109, 74, 77, 69, 86, 84, 76, 0, 72, 74, 72, + 77, 95, 80, 68, 108, 125, 97, 106, 100, 126, + 87, 82, 116, 67, 76, 76, 84, 88, 100, 95, 88, + 106, 93, 93, 97, 107, 1, 13, 41, 26, 10, 4, + 16, 9, 4, 1, 14, 20, 44, 29, 22, 13, 35, 18, + 21, 6, 36, 8, 37, 24, 19, 12, 26, 11, 5, 4, 4, + 68, 54, 31, 11, 3, 20, 5, 70, 64, 17, 42, 36, + 22, 10, 33, 14, 12, 5, 3, 62, 67, 4, 11, 9, 1, + 8, 13, 16, 15, 21, 27, 39, 67, 2, 72, 71, 62, + 78, 102, 72, 4, 5, 71, 71, 70, 74, 1, 77, 97, + 69, 68, 23, 70, 77, 69, 68, 64, 73, 72, 70, + 91, 84, 73, 75, 70, 26, 102, 77, 26, 84, 69, + 69, 70, 6, 67, 72, 4, 11, 71, 71, 109, 31, 29, + 30, 12, 69, 70, 71, 82, 72, 83, 88, 82, 100, + 96, 97, 108, 104, 116, 126, 100, 92, 90, 92, + 105, 97, 91, 93, 94, 79, 80, 85, 82, 87, 106, + 92, 89, 113, 99, 112, 111, 126, 124, 119, 118, + 79, 77, 120, 87, 88, 94, 112, 100, 101, 103, + 92, 97, 86, 80, 109, 115, 126, 89, 101, 106, + 69, 11, 16, 18, 37, 25, 30, 7, 34, 29, 39, 32, + 42, 36, 25, 41, 70, 79, 87, 95, 95, 122, 121, + 117, 17, 45, 41, 44, 34, 37, 22, 21, 12, 11, + 69, 11, 16, 18, 37, 25, 30, 7, 34, 29, 39, 32, + 42, 36, 25, 41, 70, 79, 87, 95, 95, 122, 121, + 117 }, + + { + + 23, + 4, 82, 23, 4, 82, 13, 23, 27, 12, 70, 95, 68, + 25, 40, 10, 39, 87, 74, 27, 70, 73, 37, 95, + 95, 65, 79, 126, 126, 126, 24, 7, 68, 74, 27, + 70, 76, 14, 36, 66, 5, 2, 5, 109, 113, 95, + 112, 65, 71, 70, 76, 91, 81, 98, 0, 68, 69, + 64, 33, 1, 22, 0, 0, 0, 5, 97, 97, 11, 76, 70, + 10, 0, 72, 35, 19, 7, 48, 46, 7, 19, 22, 26, + 10, 9, 38, 110, 89, 89, 91, 25, 72, 19, 38, + 65, 73, 3, 19, 65, 73, 72, 81, 24, 76, 14, 1, + 75, 8, 68, 0, 7, 13, 18, 19, 7, 79, 68, 3, 65, + 0, 71, 30, 66, 1, 32, 27, 38, 31, 27, 98, 10, + 11, 76, 67, 76, 69, 33, 2, 5, 38, 57, 62, 62, + 49, 99, 109, 11, 42, 42, 126, 73, 14, 99, 25, + 70, 4, 37, 33, 54, 49, 47, 110, 3, 83, 103, + 103, 66, 13, 21, 10, 4, 10, 10, 2, 15, 8, 78, + 71, 5, 0, 0, 76, 66, 74, 80, 67, 67, 0, 72, + 73, 110, 75, 78, 69, 88, 85, 78, 65, 74, 76, + 74, 79, 99, 83, 69, 110, 126, 99, 108, 102, + 126, 89, 83, 118, 68, 77, 77, 85, 89, 102, 97, + 90, 108, 93, 94, 98, 109, 2, 14, 42, 26, 10, + 4, 17, 10, 5, 2, 16, 21, 45, 29, 23, 14, 36, + 19, 23, 7, 38, 9, 37, 24, 19, 13, 27, 12, 5, + 4, 5, 68, 55, 32, 11, 3, 21, 5, 70, 0, 18, 42, + 37, 22, 10, 34, 15, 13, 5, 4, 62, 66, 5, 12, + 11, 2, 10, 14, 18, 16, 22, 29, 41, 66, 3, 71, + 70, 62, 78, 103, 72, 5, 5, 71, 71, 70, 75, 2, + 77, 98, 69, 68, 24, 70, 77, 69, 68, 64, 74, + 72, 70, 92, 85, 73, 75, 70, 27, 103, 77, 26, + 85, 69, 69, 70, 6, 67, 73, 4, 12, 71, 71, 110, + 30, 28, 29, 10, 71, 72, 74, 85, 75, 86, 92, + 85, 104, 99, 99, 112, 108, 120, 126, 103, 94, + 92, 94, 108, 99, 93, 94, 95, 79, 83, 87, 84, + 89, 108, 94, 91, 115, 101, 114, 113, 126, 126, + 121, 119, 80, 78, 123, 89, 90, 96, 114, 101, + 102, 104, 93, 98, 86, 79, 110, 116, 126, 90, + 102, 107, 69, 11, 16, 19, 38, 25, 31, 7, 35, + 30, 40, 32, 43, 36, 26, 41, 72, 81, 89, 97, + 97, 124, 123, 119, 17, 46, 41, 45, 35, 37, 22, + 21, 12, 12, 69, 11, 16, 19, 38, 25, 31, 7, 35, + 30, 40, 32, 43, 36, 26, 41, 72, 81, 89, 97, + 97, 124, 123, 119 }, + + { + + 22, + 4, 82, 22, 4, 82, 15, 24, 27, 12, 70, 97, 70, + 24, 41, 10, 40, 89, 73, 29, 71, 73, 38, 96, + 96, 66, 81, 126, 126, 126, 26, 8, 68, 73, 29, + 71, 76, 15, 36, 66, 5, 3, 7, 110, 114, 95, + 112, 65, 70, 69, 77, 91, 81, 98, 0, 67, 69, 0, + 33, 1, 22, 0, 0, 0, 6, 97, 97, 11, 77, 70, 10, + 1, 70, 38, 20, 8, 50, 48, 8, 20, 24, 27, 11, + 10, 41, 110, 90, 89, 92, 25, 72, 20, 41, 64, + 73, 3, 21, 65, 73, 72, 80, 24, 76, 15, 2, 74, + 9, 67, 0, 8, 14, 19, 20, 7, 80, 68, 4, 65, 0, + 71, 31, 66, 1, 32, 28, 38, 31, 27, 98, 11, 11, + 77, 67, 75, 69, 34, 3, 6, 40, 59, 62, 62, 51, + 100, 110, 12, 43, 42, 126, 73, 14, 99, 26, 70, + 4, 39, 33, 54, 49, 49, 111, 4, 84, 105, 106, + 65, 13, 21, 10, 4, 10, 10, 2, 15, 8, 79, 72, + 5, 0, 0, 76, 66, 74, 81, 67, 67, 0, 73, 74, + 111, 76, 78, 69, 89, 85, 80, 67, 75, 77, 75, + 81, 102, 85, 70, 112, 126, 100, 110, 104, 126, + 90, 84, 119, 69, 78, 78, 87, 91, 104, 99, 91, + 109, 93, 95, 99, 111, 4, 16, 43, 27, 10, 4, + 18, 11, 6, 2, 18, 23, 46, 30, 23, 15, 38, 20, + 24, 8, 41, 10, 38, 25, 20, 13, 28, 13, 6, 5, + 6, 67, 56, 33, 11, 3, 22, 6, 69, 1, 19, 42, + 38, 22, 10, 35, 16, 14, 6, 5, 62, 65, 7, 14, + 12, 3, 11, 16, 20, 18, 24, 31, 44, 65, 4, 70, + 69, 62, 77, 104, 71, 6, 6, 71, 71, 70, 75, 2, + 78, 99, 69, 68, 25, 70, 77, 69, 68, 0, 74, 73, + 70, 93, 86, 74, 75, 70, 28, 103, 78, 27, 85, + 70, 69, 70, 6, 68, 73, 4, 12, 71, 71, 112, 29, + 27, 28, 9, 74, 75, 77, 88, 77, 89, 95, 88, + 107, 101, 101, 116, 111, 124, 126, 106, 96, + 93, 96, 110, 101, 94, 95, 96, 79, 85, 90, 87, + 91, 110, 96, 92, 117, 103, 116, 115, 126, 126, + 122, 121, 81, 79, 125, 90, 91, 97, 116, 102, + 103, 105, 94, 99, 86, 78, 112, 117, 126, 92, + 103, 108, 69, 12, 17, 20, 39, 26, 31, 8, 36, + 31, 41, 33, 44, 37, 26, 40, 74, 83, 91, 99, + 99, 126, 125, 120, 18, 46, 42, 45, 36, 38, 23, + 22, 12, 13, 69, 12, 17, 20, 39, 26, 31, 8, 36, + 31, 41, 33, 44, 37, 26, 40, 74, 83, 91, 99, + 99, 126, 125, 120 }, + + { + + 21, + 4, 82, 21, 4, 82, 17, 26, 28, 12, 71, 99, 71, + 23, 41, 10, 42, 90, 73, 30, 71, 74, 39, 97, + 97, 67, 83, 126, 126, 126, 28, 10, 68, 73, 30, + 71, 75, 16, 37, 66, 6, 4, 8, 111, 115, 95, + 113, 65, 70, 68, 77, 92, 81, 98, 0, 67, 69, 1, + 34, 1, 22, 0, 0, 0, 6, 97, 97, 12, 78, 70, 9, + 1, 69, 40, 21, 9, 51, 49, 9, 21, 26, 28, 12, + 11, 43, 111, 90, 89, 92, 26, 72, 21, 43, 64, + 72, 4, 23, 65, 73, 72, 80, 25, 76, 15, 3, 74, + 10, 67, 0, 8, 14, 20, 20, 7, 80, 68, 4, 65, 0, + 71, 31, 66, 1, 33, 28, 39, 31, 28, 99, 11, 12, + 77, 67, 75, 69, 35, 3, 6, 41, 60, 62, 62, 53, + 100, 111, 12, 43, 42, 126, 73, 15, 100, 26, + 70, 4, 40, 33, 54, 49, 50, 112, 6, 85, 108, + 109, 0, 13, 21, 9, 3, 9, 9, 1, 15, 7, 80, 73, + 5, 0, 0, 77, 67, 75, 82, 68, 67, 0, 74, 74, + 112, 76, 79, 69, 90, 86, 82, 69, 77, 79, 77, + 83, 105, 88, 71, 114, 126, 102, 112, 106, 126, + 91, 85, 121, 70, 79, 79, 88, 92, 106, 101, 93, + 111, 93, 96, 100, 112, 5, 17, 45, 27, 10, 4, + 19, 12, 7, 3, 20, 24, 47, 31, 24, 15, 39, 21, + 26, 9, 43, 10, 38, 25, 20, 14, 29, 13, 6, 6, + 7, 67, 58, 34, 11, 3, 23, 6, 69, 2, 20, 43, + 39, 23, 10, 36, 16, 15, 6, 5, 62, 0, 8, 15, + 14, 4, 13, 17, 21, 19, 26, 33, 46, 65, 5, 70, + 68, 62, 77, 105, 71, 7, 7, 71, 71, 70, 75, 3, + 78, 100, 69, 68, 25, 70, 77, 69, 68, 0, 75, + 73, 70, 94, 87, 74, 75, 70, 29, 104, 78, 28, + 86, 70, 69, 70, 7, 68, 74, 5, 13, 71, 71, 113, + 28, 26, 27, 7, 76, 77, 79, 91, 80, 92, 98, 91, + 110, 104, 103, 120, 115, 126, 126, 109, 98, + 95, 98, 113, 103, 95, 96, 97, 79, 87, 92, 89, + 93, 112, 98, 94, 119, 105, 118, 117, 126, 126, + 124, 122, 82, 80, 126, 92, 93, 99, 118, 104, + 105, 106, 95, 100, 86, 77, 113, 119, 126, 93, + 104, 109, 69, 12, 17, 20, 40, 27, 32, 8, 37, + 32, 42, 33, 45, 37, 27, 40, 76, 85, 93, 101, + 101, 126, 126, 121, 18, 47, 42, 46, 36, 39, + 23, 22, 13, 13, 69, 12, 17, 20, 40, 27, 32, 8, + 37, 32, 42, 33, 45, 37, 27, 40, 76, 85, 93, + 101, 101, 126, 126, 121 }, + + { + + 20, + 4, 82, 20, 4, 82, 19, 27, 28, 12, 71, 101, 73, + 22, 42, 10, 44, 92, 73, 32, 71, 74, 40, 98, + 98, 68, 85, 126, 126, 126, 30, 11, 68, 73, 32, + 71, 75, 17, 37, 66, 7, 5, 9, 112, 116, 95, + 113, 65, 70, 67, 77, 92, 81, 98, 0, 67, 69, 2, + 34, 1, 22, 0, 0, 0, 7, 97, 97, 12, 79, 70, 9, + 2, 68, 42, 22, 10, 53, 51, 10, 22, 28, 29, 13, + 12, 45, 112, 90, 89, 92, 26, 72, 22, 45, 64, + 71, 5, 25, 65, 73, 72, 80, 25, 76, 16, 4, 74, + 11, 67, 0, 8, 15, 21, 21, 7, 80, 68, 4, 65, 0, + 71, 31, 66, 1, 33, 28, 39, 31, 28, 99, 12, 13, + 78, 67, 75, 69, 36, 3, 7, 42, 61, 62, 62, 55, + 101, 112, 13, 43, 42, 126, 73, 15, 100, 27, + 70, 4, 41, 33, 54, 49, 51, 113, 7, 86, 111, + 112, 1, 13, 21, 9, 2, 9, 9, 1, 15, 7, 81, 74, + 5, 0, 0, 77, 67, 76, 83, 68, 67, 0, 75, 75, + 113, 77, 80, 69, 91, 86, 84, 71, 79, 80, 79, + 85, 108, 90, 72, 116, 126, 103, 114, 108, 126, + 92, 86, 123, 71, 80, 80, 89, 94, 108, 103, 95, + 112, 93, 97, 101, 114, 7, 19, 46, 28, 10, 4, + 20, 13, 8, 4, 22, 25, 48, 32, 25, 16, 40, 22, + 27, 10, 45, 11, 39, 26, 21, 14, 30, 14, 7, 7, + 8, 67, 59, 35, 11, 3, 24, 7, 69, 3, 21, 43, + 40, 23, 10, 37, 17, 16, 7, 6, 62, 1, 10, 17, + 16, 5, 14, 19, 23, 21, 28, 35, 48, 64, 6, 69, + 67, 62, 77, 106, 71, 8, 8, 71, 71, 70, 75, 3, + 78, 101, 69, 68, 26, 70, 77, 69, 68, 0, 75, + 73, 70, 95, 88, 74, 75, 70, 30, 105, 78, 29, + 87, 70, 69, 70, 7, 68, 74, 5, 14, 71, 71, 115, + 27, 25, 26, 5, 78, 80, 82, 94, 82, 95, 101, + 94, 113, 107, 105, 124, 118, 126, 126, 112, + 100, 97, 100, 115, 105, 96, 97, 98, 79, 89, + 94, 91, 95, 114, 100, 95, 121, 107, 120, 119, + 126, 126, 126, 123, 83, 81, 126, 93, 95, 100, + 120, 105, 106, 107, 96, 101, 86, 76, 114, 120, + 126, 94, 105, 110, 69, 13, 18, 21, 41, 28, 33, + 8, 38, 33, 43, 34, 46, 38, 28, 39, 78, 87, 95, + 103, 103, 126, 126, 122, 19, 47, 43, 47, 37, + 40, 24, 23, 13, 14, 69, 13, 18, 21, 41, 28, + 33, 8, 38, 33, 43, 34, 46, 38, 28, 39, 78, 87, + 95, 103, 103, 126, 126, 122 }, + + { + + 18, + 3, 83, 18, 3, 83, 20, 28, 28, 12, 72, 103, 75, + 21, 42, 10, 45, 94, 73, 33, 72, 75, 41, 99, + 100, 70, 87, 126, 126, 126, 32, 12, 68, 73, + 33, 72, 75, 17, 37, 67, 7, 5, 10, 114, 118, + 96, 114, 66, 70, 67, 78, 93, 81, 98, 64, 67, + 69, 2, 34, 0, 22, 0, 0, 0, 7, 98, 97, 12, 80, + 71, 8, 2, 67, 44, 23, 10, 54, 52, 10, 23, 29, + 29, 14, 12, 47, 113, 91, 90, 93, 26, 73, 22, + 47, 64, 71, 5, 26, 65, 73, 72, 80, 25, 77, 16, + 4, 74, 11, 67, 0, 8, 15, 22, 21, 7, 81, 69, 4, + 65, 64, 71, 31, 66, 1, 33, 28, 39, 31, 28, + 100, 12, 13, 79, 67, 75, 70, 36, 3, 7, 43, 62, + 62, 62, 56, 102, 114, 13, 43, 42, 126, 74, 15, + 101, 27, 71, 4, 42, 33, 53, 49, 52, 114, 8, + 88, 114, 116, 2, 12, 21, 8, 1, 8, 8, 0, 14, 6, + 82, 75, 4, 64, 64, 78, 68, 77, 84, 69, 68, 64, + 76, 76, 115, 78, 81, 69, 93, 87, 87, 74, 81, + 82, 81, 88, 112, 93, 74, 118, 126, 105, 117, + 110, 126, 94, 88, 125, 73, 81, 81, 91, 96, + 110, 105, 97, 114, 93, 98, 102, 116, 8, 20, + 47, 28, 10, 4, 20, 13, 8, 4, 23, 26, 48, 32, + 25, 16, 41, 23, 28, 10, 47, 11, 39, 26, 21, + 14, 30, 14, 7, 7, 9, 67, 60, 36, 11, 2, 24, 7, + 69, 3, 21, 43, 40, 23, 10, 38, 17, 16, 7, 6, + 62, 2, 11, 18, 17, 6, 15, 20, 24, 22, 29, 36, + 50, 64, 6, 69, 67, 62, 77, 108, 71, 8, 8, 71, + 71, 70, 76, 3, 79, 102, 70, 68, 26, 71, 77, + 70, 68, 0, 76, 74, 71, 96, 89, 75, 76, 70, 31, + 106, 79, 29, 88, 71, 69, 71, 7, 69, 75, 5, 14, + 71, 71, 117, 25, 24, 24, 3, 81, 83, 85, 97, + 85, 98, 105, 97, 117, 110, 107, 126, 122, 126, + 126, 115, 103, 99, 103, 118, 107, 98, 99, 99, + 79, 92, 97, 94, 97, 117, 102, 97, 124, 109, + 123, 121, 126, 126, 126, 125, 85, 83, 126, 95, + 97, 102, 122, 107, 108, 108, 97, 102, 87, 75, + 116, 122, 126, 96, 107, 112, 69, 13, 18, 21, + 42, 28, 33, 8, 39, 33, 44, 34, 46, 38, 28, 38, + 80, 89, 98, 106, 105, 126, 126, 124, 19, 47, + 43, 47, 37, 40, 24, 23, 13, 14, 69, 13, 18, + 21, 42, 28, 33, 8, 39, 33, 44, 34, 46, 38, 28, + 38, 80, 89, 98, 106, 105, 126, 126, 124 }, + + { + + 17, + 3, 83, 17, 3, 83, 22, 30, 29, 13, 72, 104, 76, + 21, 43, 11, 47, 95, 72, 35, 72, 75, 43, 99, + 101, 71, 88, 126, 126, 126, 34, 14, 67, 72, + 35, 72, 74, 18, 38, 67, 8, 6, 12, 115, 119, + 96, 114, 66, 69, 66, 78, 93, 80, 97, 64, 66, + 68, 3, 35, 0, 22, 0, 0, 0, 8, 98, 97, 13, 80, + 71, 8, 3, 65, 47, 25, 11, 56, 54, 11, 25, 31, + 30, 16, 13, 50, 113, 91, 90, 93, 27, 73, 23, + 50, 0, 70, 6, 28, 65, 72, 72, 79, 26, 77, 17, + 5, 73, 12, 66, 1, 9, 16, 23, 22, 8, 81, 69, 5, + 64, 64, 70, 32, 65, 1, 34, 29, 40, 32, 29, + 100, 13, 14, 79, 67, 74, 70, 37, 4, 8, 45, 62, + 62, 62, 58, 102, 115, 14, 44, 43, 126, 74, 16, + 101, 28, 71, 5, 44, 33, 53, 50, 54, 115, 10, + 89, 116, 119, 4, 12, 21, 8, 1, 8, 8, 0, 14, 6, + 82, 75, 4, 64, 64, 78, 68, 77, 84, 69, 68, 64, + 76, 76, 116, 78, 81, 69, 94, 87, 89, 76, 82, + 83, 82, 90, 115, 95, 75, 119, 126, 106, 119, + 111, 126, 95, 89, 126, 74, 81, 81, 92, 97, + 111, 106, 98, 115, 93, 98, 102, 117, 10, 22, + 49, 29, 10, 4, 21, 14, 9, 5, 25, 28, 49, 33, + 26, 17, 43, 24, 30, 11, 50, 12, 40, 27, 22, + 15, 31, 15, 8, 8, 11, 66, 62, 37, 12, 2, 25, + 8, 68, 4, 22, 44, 41, 24, 11, 39, 18, 17, 8, + 7, 62, 4, 13, 20, 19, 8, 17, 22, 26, 24, 31, + 38, 53, 0, 7, 68, 66, 62, 76, 109, 70, 9, 9, + 70, 71, 69, 76, 4, 79, 102, 70, 68, 27, 71, + 77, 70, 68, 1, 76, 74, 71, 96, 89, 75, 76, 69, + 33, 106, 79, 30, 88, 71, 69, 71, 8, 69, 75, 6, + 15, 70, 70, 118, 24, 23, 23, 2, 83, 85, 87, + 100, 87, 100, 108, 99, 120, 112, 109, 126, + 125, 126, 126, 117, 105, 100, 105, 120, 108, + 99, 100, 99, 79, 94, 99, 96, 99, 119, 103, 98, + 126, 110, 125, 122, 126, 126, 126, 126, 86, + 84, 126, 96, 98, 103, 123, 108, 109, 109, 97, + 102, 87, 74, 117, 123, 126, 97, 108, 113, 68, + 14, 19, 22, 44, 29, 34, 9, 41, 34, 45, 35, 47, + 39, 29, 38, 81, 90, 100, 108, 106, 126, 126, + 125, 20, 48, 44, 48, 38, 41, 25, 24, 14, 15, + 68, 14, 19, 22, 44, 29, 34, 9, 41, 34, 45, 35, + 47, 39, 29, 38, 81, 90, 100, 108, 106, 126, + 126, 125 }, + + { + + 16, + 3, 83, 16, 3, 83, 24, 31, 29, 13, 72, 106, 78, + 20, 44, 11, 49, 97, 72, 36, 72, 75, 44, 100, + 102, 72, 90, 126, 126, 126, 36, 15, 67, 72, + 36, 72, 74, 19, 38, 67, 9, 7, 13, 116, 120, + 96, 114, 66, 69, 65, 78, 93, 80, 97, 64, 66, + 68, 4, 35, 0, 22, 0, 0, 0, 9, 98, 97, 13, 81, + 71, 8, 4, 64, 49, 26, 12, 58, 56, 12, 26, 33, + 31, 17, 14, 52, 114, 91, 90, 93, 27, 73, 24, + 52, 0, 69, 7, 30, 65, 72, 72, 79, 26, 77, 17, + 6, 73, 13, 66, 1, 9, 16, 24, 23, 8, 81, 69, 5, + 64, 64, 70, 32, 65, 1, 34, 29, 40, 32, 29, + 101, 14, 15, 80, 67, 74, 70, 38, 4, 8, 46, 62, + 62, 62, 60, 103, 116, 14, 44, 43, 126, 74, 16, + 102, 29, 71, 5, 45, 33, 53, 50, 55, 116, 11, + 90, 119, 122, 5, 12, 21, 8, 0, 7, 8, 0, 14, 5, + 83, 76, 4, 64, 64, 78, 69, 78, 85, 69, 68, 64, + 77, 77, 117, 79, 82, 69, 95, 88, 91, 78, 84, + 85, 84, 92, 118, 97, 76, 121, 126, 108, 121, + 113, 126, 96, 90, 126, 75, 82, 82, 93, 99, + 113, 108, 100, 117, 93, 99, 103, 119, 11, 23, + 50, 30, 10, 4, 22, 15, 10, 6, 27, 29, 50, 34, + 27, 18, 44, 25, 31, 12, 52, 13, 40, 27, 22, + 15, 32, 16, 9, 9, 12, 66, 62, 38, 12, 2, 26, + 9, 68, 5, 23, 44, 42, 24, 11, 40, 19, 18, 9, + 8, 62, 5, 15, 22, 21, 9, 18, 24, 28, 25, 33, + 40, 55, 1, 8, 67, 65, 62, 76, 110, 70, 10, 10, + 70, 71, 69, 76, 4, 79, 103, 70, 68, 28, 71, + 77, 70, 68, 1, 76, 74, 71, 97, 90, 75, 76, 69, + 34, 107, 79, 31, 89, 71, 69, 71, 8, 69, 75, 6, + 16, 70, 70, 120, 23, 22, 22, 0, 85, 88, 90, + 103, 89, 103, 111, 102, 123, 115, 111, 126, + 126, 126, 126, 120, 107, 102, 107, 122, 110, + 100, 101, 100, 79, 96, 101, 98, 101, 121, 105, + 100, 126, 112, 126, 124, 126, 126, 126, 126, + 87, 85, 126, 98, 100, 105, 125, 109, 110, 110, + 98, 103, 87, 73, 118, 124, 126, 98, 109, 114, + 68, 14, 20, 23, 45, 30, 35, 9, 42, 35, 46, 35, + 48, 40, 30, 37, 83, 92, 102, 110, 108, 126, + 126, 126, 21, 48, 44, 49, 39, 42, 25, 25, 14, + 16, 68, 14, 20, 23, 45, 30, 35, 9, 42, 35, 46, + 35, 48, 40, 30, 37, 83, 92, 102, 110, 108, + 126, 126, 126 }, + + { + + 15, + 3, 83, 15, 3, 83, 26, 33, 30, 13, 73, 108, 79, + 19, 44, 11, 51, 98, 72, 38, 72, 76, 45, 101, + 103, 73, 92, 126, 126, 126, 38, 17, 67, 72, + 38, 72, 73, 20, 39, 67, 10, 8, 14, 117, 121, + 96, 115, 66, 69, 64, 78, 94, 80, 97, 64, 66, + 68, 5, 36, 0, 22, 0, 0, 0, 9, 98, 97, 14, 82, + 71, 7, 4, 0, 51, 27, 13, 59, 57, 13, 27, 35, + 32, 18, 15, 54, 115, 91, 90, 93, 28, 73, 25, + 54, 0, 68, 8, 32, 65, 72, 72, 79, 27, 77, 18, + 7, 73, 14, 66, 1, 9, 17, 25, 23, 8, 81, 69, 5, + 64, 64, 70, 32, 65, 1, 35, 29, 41, 32, 30, + 101, 14, 16, 80, 67, 74, 70, 39, 4, 9, 47, 62, + 62, 62, 62, 103, 117, 15, 44, 43, 126, 74, 17, + 102, 29, 71, 5, 46, 33, 53, 50, 56, 117, 13, + 91, 122, 125, 7, 12, 21, 7, 64, 7, 7, 64, 14, + 5, 84, 77, 4, 64, 64, 79, 69, 79, 86, 70, 68, + 64, 78, 77, 118, 79, 83, 69, 96, 88, 93, 80, + 86, 86, 86, 94, 121, 100, 77, 123, 126, 109, + 123, 115, 126, 97, 91, 126, 76, 83, 83, 94, + 100, 115, 110, 102, 118, 93, 100, 104, 120, + 13, 25, 52, 30, 10, 4, 23, 16, 11, 7, 29, 30, + 51, 35, 28, 18, 45, 26, 33, 13, 54, 13, 41, + 28, 23, 16, 33, 16, 9, 10, 13, 66, 62, 39, 12, + 2, 27, 9, 68, 6, 24, 45, 43, 25, 11, 41, 19, + 19, 9, 8, 62, 7, 16, 23, 23, 10, 20, 25, 29, + 27, 35, 42, 57, 1, 9, 67, 64, 62, 76, 111, 70, + 11, 11, 70, 71, 69, 76, 5, 79, 104, 70, 68, + 28, 71, 77, 70, 68, 1, 77, 74, 71, 98, 91, 75, + 76, 69, 35, 108, 79, 32, 90, 71, 69, 71, 9, + 69, 76, 7, 17, 70, 70, 121, 22, 21, 21, 65, + 87, 90, 92, 106, 92, 106, 114, 105, 126, 118, + 113, 126, 126, 126, 126, 123, 109, 104, 109, + 125, 112, 101, 102, 101, 79, 98, 103, 100, + 103, 123, 107, 101, 126, 114, 126, 126, 126, + 126, 126, 126, 88, 86, 126, 99, 102, 106, 126, + 111, 112, 111, 99, 104, 87, 72, 119, 126, 126, + 99, 110, 115, 68, 15, 20, 23, 46, 31, 36, 9, + 43, 36, 47, 36, 49, 40, 31, 37, 85, 94, 104, + 112, 110, 126, 126, 126, 21, 49, 45, 50, 39, + 43, 26, 25, 15, 16, 68, 15, 20, 23, 46, 31, + 36, 9, 43, 36, 47, 36, 49, 40, 31, 37, 85, 94, + 104, 112, 110, 126, 126, 126 }, + + }, + + { + + { + + 62, + 9, 74, 62, 9, 74, 126, 104, 10, 9, 12, 30, 61, + 62, 54, 14, 118, 6, 78, 65, 1, 14, 73, 13, 64, + 20, 62, 67, 90, 104, 126, 104, 67, 78, 65, 1, + 86, 95, 2, 18, 69, 81, 96, 8, 67, 86, 88, 5, 76, + 94, 9, 69, 81, 88, 67, 74, 74, 80, 72, 5, 22, 0, + 0, 0, 83, 86, 97, 72, 22, 1, 52, 8, 69, 126, + 102, 82, 74, 107, 126, 126, 126, 95, 126, 114, + 126, 123, 115, 122, 115, 0, 68, 84, 104, 70, 93, + 90, 126, 74, 97, 91, 126, 7, 82, 76, 125, 93, + 87, 77, 71, 0, 68, 84, 1, 65, 2, 7, 66, 64, 2, + 78, 13, 11, 28, 19, 25, 18, 17, 19, 46, 12, 13, + 44, 30, 1, 108, 100, 101, 91, 94, 88, 84, 86, + 83, 87, 94, 70, 72, 74, 4, 102, 100, 95, 75, 72, + 75, 71, 17, 69, 1, 65, 26, 72, 6, 9, 1, 72, 62, + 54, 38, 45, 54, 44, 26, 45, 34, 30, 33, 18, 5, + 1, 2, 25, 18, 24, 21, 19, 18, 22, 14, 29, 21, 8, + 12, 17, 89, 62, 62, 62, 62, 62, 62, 62, 62, 62, + 62, 62, 62, 62, 62, 46, 62, 60, 41, 62, 62, 62, + 62, 60, 58, 62, 47, 41, 15, 26, 3, 68, 97, 71, + 21, 13, 9, 1, 5, 0, 72, 74, 91, 67, 36, 24, 19, + 17, 64, 68, 78, 77, 86, 92, 8, 3, 1, 65, 73, 76, + 80, 88, 110, 97, 84, 79, 73, 74, 86, 96, 97, + 117, 78, 30, 15, 10, 1, 71, 79, 86, 90, 97, 62, + 93, 84, 79, 66, 71, 1, 3, 4, 75, 1, 5, 66, 79, + 71, 68, 19, 1, 27, 23, 36, 34, 19, 27, 31, 21, + 15, 1, 17, 64, 104, 97, 96, 88, 85, 85, 85, 88, + 66, 77, 76, 76, 5, 76, 83, 99, 95, 95, 76, 74, + 70, 75, 68, 65, 73, 1, 1, 68, 75, 8, 64, 70, 57, + 44, 47, 49, 50, 52, 48, 47, 40, 40, 43, 37, 19, + 23, 16, 46, 42, 41, 36, 34, 28, 13, 6, 0, 77, + 82, 94, 69, 109, 62, 62, 62, 62, 62, 62, 62, 62, + 62, 62, 62, 61, 50, 28, 5, 62, 62, 33, 62, 62, + 62, 60, 62, 58, 52, 58, 51, 52, 34, 37, 24, 66, + 42, 32, 13, 120, 112, 114, 85, 92, 89, 71, 81, + 80, 68, 70, 7, 68, 13, 74, 62, 62, 62, 62, 60, + 57, 29, 9, 82, 75, 40, 29, 20, 9, 8, 2, 64, 68, + 92, 106, 97, 90, 90, 88, 73, 79, 86, 73, 70, 69, + 66, 64, 5, 4, 62, 62, 62, 62, 60, 54, 43, 27, 67 }, + + { + + 62, + 9, 74, 62, 9, 74, 125, 102, 11, 10, 12, 29, + 60, 62, 54, 14, 115, 6, 77, 64, 1, 14, 72, 12, + 65, 20, 62, 68, 91, 104, 124, 102, 67, 77, 64, + 1, 85, 93, 3, 18, 68, 80, 95, 8, 67, 85, 88, + 5, 75, 93, 9, 69, 80, 88, 66, 73, 73, 79, 71, + 5, 22, 0, 0, 0, 82, 86, 97, 71, 22, 1, 52, 8, + 69, 125, 101, 82, 73, 105, 125, 125, 125, 93, + 125, 112, 125, 121, 114, 121, 114, 1, 67, 83, + 103, 69, 92, 89, 125, 73, 96, 90, 125, 8, 81, + 75, 123, 92, 86, 76, 70, 1, 67, 83, 2, 64, 2, + 7, 65, 64, 2, 77, 13, 11, 28, 19, 25, 18, 17, + 19, 45, 12, 13, 43, 29, 1, 107, 99, 100, 90, + 93, 87, 83, 85, 82, 86, 92, 70, 72, 73, 3, + 101, 99, 95, 74, 72, 74, 70, 17, 68, 1, 65, + 25, 71, 6, 8, 1, 72, 62, 54, 38, 45, 54, 44, + 26, 45, 34, 29, 33, 18, 5, 1, 2, 25, 18, 24, + 21, 19, 17, 22, 14, 28, 20, 8, 11, 16, 89, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, + 62, 60, 44, 62, 59, 40, 62, 62, 62, 62, 58, + 56, 61, 45, 39, 15, 25, 2, 68, 97, 70, 22, 14, + 10, 2, 5, 0, 71, 73, 90, 66, 37, 25, 20, 17, + 0, 67, 77, 76, 85, 91, 9, 4, 2, 64, 72, 75, + 79, 87, 108, 96, 82, 78, 72, 73, 85, 95, 96, + 115, 77, 31, 16, 11, 2, 70, 78, 85, 89, 96, + 62, 92, 83, 78, 66, 70, 1, 4, 5, 74, 2, 6, 65, + 78, 71, 68, 19, 2, 27, 23, 35, 34, 19, 26, 30, + 21, 15, 1, 16, 64, 103, 96, 95, 87, 84, 84, + 84, 87, 66, 76, 75, 75, 5, 75, 82, 98, 94, 95, + 76, 73, 70, 74, 68, 65, 72, 1, 1, 67, 74, 8, + 64, 70, 57, 44, 47, 49, 49, 52, 48, 47, 40, + 40, 43, 37, 19, 22, 15, 45, 41, 40, 35, 33, + 27, 13, 6, 0, 76, 81, 93, 69, 108, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 61, 59, 48, 27, 5, + 62, 62, 32, 62, 62, 62, 58, 62, 56, 50, 56, + 49, 50, 33, 35, 23, 67, 41, 31, 12, 118, 110, + 112, 84, 91, 88, 69, 80, 79, 68, 69, 9, 66, + 15, 73, 62, 62, 62, 62, 58, 55, 27, 7, 83, 74, + 41, 29, 20, 9, 9, 2, 64, 68, 91, 105, 96, 89, + 89, 86, 72, 78, 85, 72, 69, 68, 65, 0, 6, 4, + 62, 62, 62, 62, 59, 53, 41, 26, 67 }, + + { + + 62, + 9, 74, 62, 9, 74, 123, 101, 11, 10, 12, 28, + 59, 61, 54, 14, 113, 6, 76, 0, 1, 13, 72, 11, + 66, 19, 60, 70, 92, 105, 121, 101, 67, 76, 0, + 1, 85, 92, 3, 17, 68, 80, 94, 8, 67, 85, 88, + 5, 75, 92, 9, 69, 80, 88, 66, 73, 73, 79, 71, + 5, 22, 0, 0, 0, 81, 86, 97, 71, 21, 1, 52, 8, + 69, 124, 100, 82, 73, 104, 123, 123, 124, 92, + 123, 111, 123, 120, 113, 120, 113, 2, 67, 82, + 102, 69, 92, 88, 123, 73, 96, 90, 124, 8, 81, + 75, 122, 92, 85, 76, 70, 1, 67, 82, 2, 64, 1, + 7, 65, 64, 2, 77, 13, 11, 27, 19, 24, 18, 17, + 19, 43, 12, 13, 41, 28, 0, 106, 98, 99, 89, + 92, 86, 82, 84, 82, 85, 91, 70, 72, 73, 2, + 101, 98, 95, 74, 72, 73, 70, 16, 67, 1, 65, + 24, 70, 5, 7, 1, 73, 60, 53, 37, 44, 53, 43, + 25, 44, 34, 28, 32, 18, 5, 1, 2, 24, 17, 23, + 20, 18, 16, 21, 13, 26, 19, 7, 10, 15, 89, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, + 62, 58, 41, 62, 57, 38, 62, 62, 62, 62, 56, + 54, 58, 43, 37, 14, 23, 1, 69, 97, 70, 22, 14, + 10, 2, 5, 0, 71, 73, 89, 66, 37, 25, 20, 17, + 1, 67, 76, 76, 84, 90, 10, 5, 2, 64, 71, 75, + 79, 86, 107, 95, 81, 77, 72, 73, 84, 94, 95, + 114, 77, 31, 16, 11, 2, 69, 77, 84, 88, 95, + 62, 92, 83, 78, 66, 70, 1, 4, 5, 74, 2, 6, 64, + 78, 71, 68, 18, 2, 26, 22, 34, 33, 19, 25, 29, + 21, 15, 0, 15, 65, 102, 95, 94, 87, 84, 84, + 83, 86, 66, 76, 75, 75, 4, 75, 82, 98, 93, 95, + 76, 73, 70, 73, 68, 65, 71, 1, 1, 67, 73, 7, + 64, 71, 56, 44, 47, 48, 48, 51, 47, 46, 39, + 39, 42, 36, 18, 21, 14, 43, 40, 38, 33, 32, + 26, 12, 5, 0, 76, 81, 93, 70, 107, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 59, 57, 46, 26, 4, + 62, 60, 31, 62, 62, 62, 56, 60, 54, 48, 54, + 47, 48, 31, 33, 21, 68, 39, 29, 10, 117, 109, + 111, 83, 90, 87, 67, 79, 78, 68, 68, 10, 65, + 16, 72, 62, 62, 62, 62, 55, 52, 24, 5, 84, 74, + 41, 29, 20, 9, 9, 2, 64, 68, 90, 104, 95, 88, + 88, 85, 71, 77, 84, 71, 68, 67, 65, 1, 6, 4, + 62, 62, 62, 61, 57, 51, 39, 24, 68 }, + + { + + 62, + 9, 74, 62, 9, 74, 121, 99, 12, 10, 11, 26, 57, + 60, 54, 14, 111, 6, 75, 1, 1, 12, 72, 10, 67, + 19, 58, 71, 93, 105, 118, 100, 67, 75, 1, 1, + 84, 91, 4, 17, 68, 79, 93, 7, 68, 85, 88, 5, + 75, 92, 9, 69, 80, 88, 65, 73, 73, 79, 70, 5, + 22, 0, 0, 0, 81, 86, 97, 70, 20, 1, 52, 8, 69, + 123, 99, 82, 72, 103, 121, 121, 122, 91, 121, + 110, 121, 119, 112, 119, 112, 3, 67, 81, 101, + 69, 91, 88, 121, 73, 95, 89, 123, 8, 81, 74, + 120, 91, 84, 76, 70, 1, 67, 81, 3, 0, 1, 7, + 65, 64, 2, 77, 13, 10, 27, 19, 23, 18, 17, 19, + 41, 12, 12, 39, 27, 64, 105, 97, 98, 88, 91, + 86, 81, 84, 81, 84, 90, 70, 72, 73, 1, 100, + 97, 95, 74, 72, 72, 70, 15, 66, 1, 65, 23, 69, + 5, 6, 1, 74, 59, 52, 37, 43, 52, 42, 25, 43, + 33, 27, 31, 18, 5, 1, 1, 23, 16, 22, 19, 17, + 15, 20, 13, 24, 18, 7, 9, 14, 89, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 55, + 39, 62, 55, 37, 62, 61, 62, 59, 54, 51, 56, + 41, 34, 13, 21, 0, 70, 97, 70, 23, 14, 10, 2, + 5, 0, 71, 73, 89, 66, 37, 25, 20, 17, 2, 66, + 76, 75, 84, 89, 11, 5, 3, 64, 70, 74, 78, 86, + 106, 94, 80, 76, 71, 73, 83, 93, 94, 113, 76, + 31, 16, 11, 2, 68, 77, 83, 87, 94, 62, 91, 82, + 77, 66, 70, 1, 4, 5, 74, 2, 6, 64, 78, 71, 68, + 18, 3, 25, 21, 33, 32, 19, 24, 28, 21, 15, 0, + 14, 65, 101, 94, 93, 86, 83, 83, 83, 85, 66, + 76, 75, 74, 4, 75, 82, 97, 92, 95, 76, 73, 70, + 72, 68, 65, 70, 1, 1, 67, 72, 6, 64, 72, 55, + 43, 46, 47, 47, 50, 46, 45, 38, 38, 41, 35, + 17, 20, 13, 42, 39, 37, 31, 30, 25, 11, 5, 64, + 76, 81, 93, 70, 106, 62, 62, 62, 62, 62, 62, + 62, 62, 62, 62, 57, 54, 44, 24, 3, 61, 59, 29, + 62, 62, 60, 54, 58, 52, 46, 52, 45, 45, 29, + 31, 19, 69, 37, 27, 9, 116, 108, 110, 82, 89, + 86, 66, 78, 77, 68, 67, 12, 0, 18, 71, 62, 62, + 62, 62, 52, 49, 21, 3, 85, 74, 41, 29, 20, 9, + 9, 2, 64, 68, 90, 103, 94, 87, 87, 84, 71, 77, + 83, 71, 68, 67, 65, 1, 6, 4, 62, 62, 62, 59, + 55, 49, 37, 22, 69 }, + + { + + 62, + 9, 74, 62, 9, 74, 120, 98, 12, 10, 11, 25, 56, + 58, 54, 14, 108, 5, 74, 1, 1, 11, 72, 9, 68, + 18, 56, 73, 94, 106, 115, 99, 67, 74, 1, 1, + 84, 90, 4, 16, 68, 79, 93, 7, 68, 84, 88, 5, + 75, 91, 8, 70, 80, 88, 65, 72, 73, 78, 70, 5, + 22, 0, 0, 0, 80, 87, 97, 70, 19, 1, 52, 8, 69, + 122, 98, 82, 72, 101, 120, 119, 121, 90, 120, + 108, 119, 118, 112, 118, 112, 3, 67, 80, 100, + 69, 91, 87, 119, 73, 95, 89, 122, 8, 80, 74, + 119, 91, 84, 76, 69, 1, 67, 81, 3, 0, 0, 6, + 65, 64, 2, 77, 13, 10, 26, 19, 23, 18, 17, 18, + 39, 12, 12, 37, 26, 65, 104, 96, 97, 87, 91, + 85, 80, 83, 81, 83, 89, 70, 72, 72, 0, 100, + 96, 95, 74, 72, 72, 70, 14, 65, 1, 65, 21, 68, + 4, 5, 1, 75, 57, 51, 36, 42, 51, 41, 24, 42, + 33, 25, 30, 17, 5, 1, 1, 22, 16, 21, 19, 16, + 14, 19, 12, 22, 17, 6, 8, 13, 89, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 62, 59, 53, + 36, 62, 54, 35, 62, 59, 62, 57, 51, 49, 53, + 39, 32, 12, 20, 65, 71, 97, 70, 23, 15, 10, 2, + 5, 0, 71, 73, 88, 65, 38, 25, 20, 17, 3, 66, + 75, 75, 83, 89, 12, 6, 3, 64, 70, 74, 78, 85, + 105, 94, 79, 76, 71, 73, 82, 92, 94, 112, 76, + 32, 16, 11, 2, 67, 76, 83, 86, 93, 62, 91, 82, + 77, 66, 70, 1, 4, 5, 73, 2, 6, 0, 78, 71, 68, + 17, 3, 24, 20, 32, 31, 19, 22, 27, 20, 15, 64, + 13, 66, 101, 94, 92, 86, 83, 83, 82, 84, 67, + 76, 75, 74, 3, 75, 82, 97, 91, 95, 76, 72, 70, + 72, 68, 65, 69, 1, 0, 67, 71, 6, 65, 73, 54, + 43, 46, 46, 46, 49, 45, 44, 37, 37, 40, 34, + 16, 19, 12, 40, 37, 35, 29, 29, 24, 10, 4, 64, + 76, 81, 93, 71, 106, 62, 62, 62, 62, 62, 62, + 62, 62, 62, 60, 55, 52, 42, 23, 2, 59, 57, 28, + 62, 62, 58, 52, 55, 50, 44, 50, 43, 43, 27, + 29, 17, 70, 35, 25, 7, 115, 107, 109, 82, 88, + 85, 64, 77, 76, 68, 66, 13, 1, 19, 71, 62, 62, + 62, 62, 49, 46, 18, 1, 86, 74, 41, 29, 20, 9, + 9, 2, 64, 68, 89, 102, 93, 86, 87, 83, 70, 76, + 82, 70, 67, 66, 64, 2, 7, 4, 62, 62, 62, 57, + 53, 47, 35, 20, 70 }, + + { + + 62, + 9, 74, 62, 9, 74, 118, 96, 12, 10, 10, 23, 54, + 57, 54, 14, 106, 5, 73, 2, 1, 11, 71, 8, 69, + 18, 54, 75, 95, 106, 112, 97, 67, 73, 2, 1, + 84, 89, 4, 16, 68, 79, 92, 7, 69, 84, 88, 5, + 75, 90, 8, 70, 80, 88, 64, 72, 72, 78, 69, 5, + 22, 0, 0, 0, 80, 87, 97, 69, 18, 1, 52, 8, 69, + 121, 97, 82, 71, 100, 118, 117, 119, 89, 118, + 107, 117, 117, 111, 117, 111, 4, 67, 79, 99, + 69, 90, 86, 117, 73, 95, 88, 120, 9, 80, 73, + 118, 90, 83, 76, 69, 2, 66, 80, 4, 1, 0, 6, + 65, 64, 2, 77, 13, 9, 25, 19, 22, 18, 17, 18, + 37, 12, 11, 36, 25, 66, 103, 95, 96, 86, 90, + 84, 79, 82, 80, 82, 88, 70, 72, 72, 64, 99, + 95, 95, 73, 72, 71, 70, 13, 64, 1, 65, 20, 67, + 4, 4, 1, 75, 56, 50, 36, 41, 50, 40, 23, 42, + 33, 24, 29, 17, 5, 1, 0, 22, 15, 20, 18, 15, + 13, 19, 11, 20, 16, 5, 7, 12, 89, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 62, 57, 51, + 34, 60, 52, 33, 62, 57, 60, 55, 49, 47, 50, + 37, 29, 11, 18, 66, 71, 97, 70, 23, 15, 10, 2, + 5, 0, 71, 73, 88, 65, 38, 25, 20, 17, 4, 65, + 74, 75, 82, 88, 13, 7, 3, 0, 69, 73, 77, 85, + 104, 93, 77, 75, 71, 72, 81, 91, 93, 111, 75, + 32, 17, 11, 2, 66, 75, 82, 85, 92, 62, 91, 82, + 76, 66, 70, 1, 4, 5, 73, 2, 7, 0, 78, 71, 68, + 16, 4, 23, 19, 31, 31, 19, 21, 26, 20, 15, 65, + 12, 66, 100, 93, 91, 85, 82, 82, 82, 83, 67, + 76, 75, 74, 2, 75, 82, 96, 90, 95, 76, 72, 70, + 71, 68, 65, 68, 1, 0, 67, 70, 5, 65, 73, 53, + 43, 45, 46, 45, 48, 44, 43, 37, 36, 39, 33, + 15, 18, 11, 39, 36, 34, 27, 28, 23, 9, 3, 65, + 76, 80, 93, 71, 105, 62, 62, 62, 62, 62, 62, + 62, 62, 60, 58, 53, 50, 40, 21, 1, 57, 55, 27, + 61, 62, 56, 50, 53, 48, 42, 48, 41, 40, 25, + 27, 15, 71, 33, 23, 6, 114, 105, 108, 81, 87, + 84, 1, 76, 75, 68, 65, 15, 3, 21, 70, 62, 62, + 62, 62, 47, 43, 16, 64, 87, 74, 41, 29, 20, 9, + 9, 2, 64, 68, 89, 101, 92, 85, 86, 82, 69, 76, + 81, 69, 66, 65, 64, 2, 7, 4, 62, 62, 62, 56, + 51, 45, 33, 18, 71 }, + + { + + 62, + 9, 75, 62, 9, 75, 116, 95, 13, 10, 10, 22, 53, + 56, 54, 14, 104, 5, 73, 3, 1, 10, 71, 7, 70, + 17, 53, 76, 96, 107, 109, 96, 67, 73, 3, 1, + 83, 88, 5, 15, 67, 78, 91, 6, 69, 84, 88, 5, + 74, 90, 8, 70, 79, 88, 64, 72, 72, 78, 69, 5, + 22, 0, 0, 0, 79, 87, 97, 69, 18, 0, 52, 8, 69, + 120, 97, 82, 71, 99, 116, 115, 118, 88, 116, + 106, 115, 116, 110, 116, 110, 5, 67, 78, 99, + 68, 90, 86, 115, 73, 94, 88, 119, 9, 80, 73, + 116, 90, 82, 75, 69, 2, 66, 79, 4, 1, 64, 6, + 65, 64, 2, 77, 13, 9, 25, 19, 21, 18, 17, 18, + 35, 12, 11, 34, 24, 67, 103, 94, 96, 86, 89, + 84, 78, 82, 80, 82, 86, 70, 72, 72, 65, 99, + 94, 95, 73, 72, 70, 69, 12, 64, 1, 65, 19, 66, + 3, 3, 1, 76, 54, 49, 35, 41, 49, 40, 23, 41, + 32, 23, 28, 17, 5, 1, 0, 21, 14, 19, 17, 15, + 12, 18, 11, 18, 15, 5, 6, 11, 89, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 62, 54, 48, + 31, 58, 50, 32, 62, 54, 57, 52, 47, 44, 48, + 34, 27, 10, 16, 67, 72, 97, 69, 24, 15, 11, 2, + 5, 0, 71, 73, 87, 65, 38, 26, 20, 17, 5, 65, + 74, 74, 82, 87, 14, 7, 4, 0, 68, 73, 77, 84, + 103, 92, 76, 74, 70, 72, 81, 91, 92, 109, 75, + 32, 17, 11, 3, 66, 75, 81, 85, 91, 62, 90, 81, + 76, 66, 70, 1, 4, 5, 73, 3, 7, 1, 78, 71, 69, + 16, 4, 22, 18, 30, 30, 19, 20, 25, 20, 15, 65, + 11, 67, 99, 92, 90, 85, 82, 82, 81, 83, 67, + 75, 74, 73, 2, 75, 82, 96, 89, 95, 76, 72, 70, + 70, 68, 65, 67, 0, 0, 67, 70, 4, 65, 74, 52, + 42, 45, 45, 44, 48, 44, 42, 36, 36, 38, 32, + 14, 17, 10, 37, 35, 32, 25, 26, 21, 8, 3, 65, + 76, 80, 92, 72, 104, 62, 62, 62, 62, 62, 62, + 62, 62, 58, 55, 51, 47, 38, 20, 1, 56, 54, 25, + 59, 62, 54, 48, 51, 46, 40, 45, 39, 38, 23, + 25, 14, 73, 31, 21, 4, 113, 104, 107, 80, 86, + 83, 2, 75, 74, 68, 64, 16, 4, 22, 69, 62, 62, + 62, 59, 44, 41, 13, 66, 89, 73, 41, 29, 20, 9, + 9, 2, 64, 68, 88, 100, 92, 84, 85, 81, 69, 75, + 80, 69, 66, 65, 64, 3, 7, 4, 62, 62, 61, 54, + 50, 44, 30, 17, 72 }, + + { + + 62, + 9, 75, 62, 9, 75, 114, 93, 13, 10, 9, 20, 51, + 54, 54, 14, 101, 4, 72, 3, 1, 9, 71, 6, 71, + 17, 51, 78, 97, 107, 106, 95, 67, 72, 3, 1, + 83, 87, 5, 15, 67, 78, 91, 6, 70, 83, 88, 5, + 74, 89, 7, 70, 79, 88, 0, 71, 72, 77, 68, 5, + 22, 0, 0, 0, 79, 87, 97, 68, 17, 0, 52, 8, 69, + 119, 96, 82, 70, 97, 115, 113, 116, 87, 115, + 104, 113, 115, 109, 115, 110, 6, 67, 77, 98, + 68, 89, 85, 113, 73, 94, 87, 118, 9, 79, 72, + 115, 89, 82, 75, 68, 2, 66, 78, 5, 2, 64, 5, + 65, 64, 2, 77, 13, 8, 24, 19, 21, 18, 17, 17, + 33, 12, 10, 32, 23, 68, 102, 93, 95, 85, 88, + 83, 77, 81, 79, 81, 85, 70, 72, 71, 66, 98, + 93, 95, 73, 72, 70, 69, 11, 0, 1, 65, 17, 65, + 3, 2, 1, 77, 53, 48, 35, 40, 48, 39, 22, 40, + 32, 22, 27, 17, 5, 1, 64, 20, 14, 18, 17, 14, + 11, 17, 10, 16, 14, 4, 5, 10, 89, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 60, 61, 52, 46, + 29, 56, 49, 30, 62, 52, 55, 50, 44, 42, 45, + 32, 24, 9, 15, 69, 73, 97, 69, 24, 16, 11, 2, + 5, 0, 71, 73, 87, 64, 39, 26, 20, 17, 6, 64, + 73, 74, 81, 86, 15, 8, 4, 0, 67, 72, 76, 84, + 102, 92, 75, 74, 70, 72, 80, 90, 92, 108, 74, + 33, 17, 11, 3, 65, 74, 80, 84, 90, 62, 90, 81, + 75, 66, 70, 1, 4, 5, 72, 3, 7, 1, 78, 71, 69, + 15, 5, 21, 17, 29, 29, 19, 19, 24, 19, 15, 66, + 10, 67, 98, 92, 89, 84, 81, 81, 81, 82, 67, + 75, 74, 73, 1, 75, 82, 95, 88, 95, 76, 71, 70, + 70, 68, 65, 66, 0, 0, 67, 69, 4, 66, 75, 51, + 42, 44, 44, 43, 47, 43, 41, 35, 35, 37, 31, + 13, 16, 9, 36, 33, 31, 23, 25, 20, 7, 2, 66, + 76, 80, 92, 72, 103, 62, 62, 62, 62, 62, 62, + 62, 61, 56, 53, 49, 45, 36, 18, 0, 54, 52, 24, + 57, 62, 52, 46, 49, 44, 38, 43, 37, 35, 21, + 23, 12, 74, 29, 19, 3, 112, 103, 106, 80, 85, + 82, 4, 74, 73, 68, 0, 18, 6, 24, 69, 62, 62, + 61, 56, 41, 38, 10, 68, 90, 73, 41, 29, 20, 9, + 9, 2, 64, 68, 88, 99, 91, 83, 84, 80, 68, 75, + 79, 68, 65, 64, 0, 3, 8, 4, 62, 62, 59, 52, + 48, 42, 28, 15, 73 }, + + { + + 62, + 8, 75, 62, 8, 75, 113, 92, 13, 10, 9, 19, 50, + 53, 54, 14, 99, 4, 71, 4, 1, 8, 71, 5, 73, 16, + 49, 80, 98, 108, 104, 94, 67, 71, 4, 1, 83, + 86, 5, 14, 67, 78, 90, 5, 70, 83, 89, 5, 74, + 89, 7, 71, 79, 88, 0, 71, 72, 77, 68, 5, 22, + 0, 0, 0, 78, 88, 97, 68, 16, 0, 52, 8, 69, + 118, 95, 82, 70, 96, 113, 111, 115, 86, 113, + 103, 112, 114, 109, 114, 109, 6, 67, 76, 97, + 68, 89, 85, 112, 73, 94, 87, 117, 9, 79, 72, + 114, 89, 81, 75, 68, 2, 66, 78, 5, 2, 65, 5, + 65, 64, 2, 77, 13, 8, 23, 19, 20, 18, 17, 17, + 31, 12, 10, 30, 22, 69, 101, 92, 94, 84, 88, + 83, 76, 81, 79, 80, 84, 70, 72, 71, 68, 98, + 92, 95, 73, 73, 69, 69, 10, 1, 1, 65, 16, 64, + 2, 1, 1, 78, 51, 47, 34, 39, 47, 38, 21, 39, + 31, 20, 26, 16, 5, 1, 64, 19, 13, 17, 16, 13, + 10, 16, 9, 14, 12, 3, 4, 9, 89, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 61, 58, 58, 49, 43, + 26, 54, 47, 28, 61, 50, 52, 47, 42, 39, 42, + 30, 22, 8, 13, 70, 74, 98, 69, 24, 16, 11, 2, + 5, 0, 71, 73, 86, 64, 39, 26, 20, 17, 7, 64, + 73, 74, 81, 86, 16, 8, 4, 0, 67, 72, 76, 83, + 101, 91, 74, 73, 70, 72, 79, 89, 91, 107, 74, + 33, 17, 11, 3, 64, 74, 80, 83, 90, 62, 90, 81, + 75, 66, 70, 1, 4, 5, 72, 3, 7, 2, 78, 71, 69, + 14, 5, 20, 16, 28, 28, 19, 17, 22, 19, 15, 67, + 9, 68, 98, 91, 88, 84, 81, 81, 80, 81, 68, 75, + 74, 73, 0, 75, 82, 95, 88, 96, 76, 71, 70, 69, + 68, 65, 66, 0, 64, 67, 68, 3, 66, 76, 50, 41, + 44, 43, 41, 46, 42, 40, 34, 34, 36, 30, 12, + 15, 8, 34, 32, 29, 21, 23, 19, 6, 1, 66, 76, + 80, 92, 73, 103, 62, 62, 62, 62, 62, 62, 61, + 58, 54, 51, 47, 42, 34, 17, 64, 52, 50, 22, + 55, 61, 49, 43, 46, 41, 36, 41, 34, 33, 19, + 20, 10, 75, 27, 17, 1, 111, 102, 105, 79, 84, + 82, 5, 73, 73, 68, 0, 19, 7, 25, 68, 62, 62, + 58, 53, 38, 35, 7, 70, 91, 73, 41, 29, 20, 9, + 9, 2, 64, 68, 87, 99, 90, 82, 84, 79, 68, 74, + 79, 68, 65, 64, 0, 4, 8, 3, 62, 62, 57, 50, + 46, 40, 26, 13, 74 }, + + { + + 62, + 8, 75, 62, 8, 75, 111, 91, 14, 10, 9, 18, 49, + 52, 54, 14, 97, 4, 70, 5, 1, 8, 70, 4, 74, 15, + 47, 81, 99, 109, 101, 92, 67, 70, 5, 1, 82, + 85, 6, 13, 67, 77, 89, 5, 70, 83, 89, 5, 74, + 88, 7, 71, 79, 88, 0, 71, 71, 77, 68, 5, 22, + 0, 0, 0, 77, 88, 97, 68, 15, 0, 52, 8, 69, + 117, 94, 82, 70, 95, 111, 109, 113, 84, 111, + 102, 110, 113, 108, 113, 108, 7, 66, 75, 96, + 68, 88, 84, 110, 73, 93, 87, 115, 10, 79, 72, + 112, 89, 80, 75, 68, 3, 65, 77, 5, 2, 65, 5, + 64, 64, 2, 76, 13, 8, 23, 19, 19, 18, 17, 17, + 29, 12, 10, 29, 21, 69, 100, 91, 93, 83, 87, + 82, 75, 80, 79, 79, 83, 70, 72, 71, 69, 97, + 91, 95, 72, 73, 68, 69, 9, 2, 1, 65, 15, 0, 1, + 0, 1, 78, 50, 46, 34, 38, 46, 37, 21, 39, 31, + 19, 25, 16, 5, 1, 64, 19, 12, 16, 15, 12, 9, + 16, 9, 13, 11, 3, 3, 8, 89, 62, 62, 62, 62, + 62, 62, 62, 62, 62, 59, 56, 56, 46, 41, 23, + 53, 45, 27, 59, 48, 50, 45, 40, 37, 40, 28, + 20, 8, 11, 71, 74, 98, 69, 25, 16, 11, 3, 5, + 0, 70, 73, 85, 64, 39, 26, 21, 17, 8, 0, 72, + 73, 80, 85, 17, 9, 5, 1, 66, 71, 76, 82, 100, + 90, 72, 72, 69, 71, 78, 88, 90, 106, 73, 33, + 18, 12, 3, 0, 73, 79, 82, 89, 62, 89, 80, 74, + 66, 70, 1, 5, 6, 72, 3, 8, 3, 78, 71, 69, 14, + 5, 19, 16, 27, 28, 19, 16, 21, 19, 15, 67, 8, + 69, 97, 90, 87, 84, 80, 81, 79, 80, 68, 75, + 74, 72, 0, 75, 82, 95, 87, 96, 76, 71, 70, 68, + 68, 65, 65, 0, 64, 67, 67, 2, 66, 76, 49, 41, + 44, 43, 40, 45, 41, 39, 34, 33, 35, 30, 12, + 14, 7, 33, 31, 27, 19, 22, 18, 6, 1, 66, 75, + 79, 92, 74, 102, 62, 62, 62, 62, 62, 62, 59, + 56, 52, 49, 45, 40, 32, 16, 65, 50, 49, 21, + 53, 59, 47, 41, 44, 39, 34, 39, 32, 31, 18, + 18, 8, 76, 25, 15, 64, 110, 100, 103, 78, 83, + 81, 7, 72, 72, 68, 1, 21, 8, 27, 67, 62, 62, + 56, 50, 36, 32, 5, 72, 92, 73, 41, 29, 20, 9, + 10, 2, 64, 68, 86, 98, 89, 81, 83, 77, 67, 73, + 78, 67, 64, 0, 0, 5, 8, 3, 62, 61, 56, 49, 44, + 38, 24, 11, 74 }, + + { + + 62, + 8, 75, 62, 8, 75, 109, 89, 14, 10, 8, 16, 47, + 50, 54, 14, 94, 3, 69, 5, 1, 7, 70, 3, 75, 15, + 45, 83, 100, 109, 98, 91, 67, 69, 5, 1, 82, + 84, 6, 13, 67, 77, 89, 5, 71, 82, 89, 5, 74, + 87, 6, 71, 79, 88, 1, 70, 71, 76, 67, 5, 22, + 0, 0, 0, 77, 88, 97, 67, 14, 0, 52, 8, 69, + 116, 93, 82, 69, 93, 110, 107, 112, 83, 110, + 100, 108, 112, 107, 112, 108, 8, 66, 74, 95, + 68, 88, 83, 108, 73, 93, 86, 114, 10, 78, 71, + 111, 88, 80, 75, 67, 3, 65, 76, 6, 3, 66, 4, + 64, 64, 2, 76, 13, 7, 22, 19, 19, 18, 17, 16, + 27, 12, 9, 27, 20, 70, 99, 90, 92, 82, 86, 81, + 74, 79, 78, 78, 82, 70, 72, 70, 70, 97, 90, + 95, 72, 73, 68, 69, 8, 3, 1, 65, 13, 1, 1, 64, + 1, 79, 48, 45, 33, 37, 45, 36, 20, 38, 31, 18, + 24, 16, 5, 1, 65, 18, 12, 15, 15, 11, 8, 15, + 8, 11, 10, 2, 2, 7, 89, 62, 62, 62, 62, 62, + 62, 62, 62, 62, 57, 54, 53, 44, 39, 21, 51, + 44, 25, 56, 46, 48, 43, 37, 35, 37, 26, 17, 7, + 10, 73, 75, 98, 69, 25, 17, 11, 3, 5, 0, 70, + 73, 85, 0, 40, 26, 21, 17, 9, 0, 71, 73, 79, + 84, 18, 10, 5, 1, 65, 71, 75, 82, 99, 90, 71, + 72, 69, 71, 77, 87, 90, 105, 73, 34, 18, 12, + 3, 1, 72, 78, 81, 88, 62, 89, 80, 74, 66, 70, + 1, 5, 6, 71, 3, 8, 3, 78, 71, 69, 13, 6, 18, + 15, 26, 27, 19, 15, 20, 18, 15, 68, 7, 69, 96, + 90, 86, 83, 80, 80, 79, 79, 68, 75, 74, 72, + 64, 75, 82, 94, 86, 96, 76, 70, 70, 68, 68, + 65, 64, 0, 64, 67, 66, 2, 67, 77, 48, 41, 43, + 42, 39, 44, 40, 38, 33, 32, 34, 29, 11, 13, 6, + 31, 29, 26, 17, 21, 17, 5, 0, 67, 75, 79, 92, + 74, 101, 62, 62, 62, 62, 62, 60, 57, 53, 50, + 47, 43, 38, 30, 14, 66, 48, 47, 20, 51, 57, + 45, 39, 42, 37, 32, 37, 30, 28, 16, 16, 6, 77, + 23, 13, 65, 109, 99, 102, 78, 82, 80, 9, 71, + 71, 68, 2, 22, 10, 28, 67, 62, 60, 53, 47, 33, + 29, 2, 74, 93, 73, 41, 29, 20, 9, 10, 2, 64, + 68, 86, 97, 88, 80, 82, 76, 66, 73, 77, 66, 0, + 1, 1, 5, 9, 3, 60, 59, 54, 47, 42, 36, 22, 9, + 75 }, + + { + + 62, + 8, 76, 62, 8, 76, 107, 88, 15, 10, 8, 15, 46, + 49, 54, 14, 92, 3, 69, 6, 1, 6, 70, 2, 76, 14, + 44, 84, 101, 110, 95, 90, 67, 69, 6, 1, 81, + 83, 7, 12, 66, 76, 88, 4, 71, 82, 89, 5, 73, + 87, 6, 71, 78, 88, 1, 70, 71, 76, 67, 5, 22, + 0, 0, 0, 76, 88, 97, 67, 14, 64, 52, 8, 69, + 115, 93, 82, 69, 92, 108, 105, 110, 82, 108, + 99, 106, 111, 106, 111, 107, 9, 66, 73, 95, + 67, 87, 83, 106, 73, 92, 86, 113, 10, 78, 71, + 109, 88, 79, 74, 67, 3, 65, 75, 6, 3, 66, 4, + 64, 64, 2, 76, 13, 7, 22, 19, 18, 18, 17, 16, + 25, 12, 9, 25, 19, 71, 99, 89, 92, 82, 85, 81, + 73, 79, 78, 78, 80, 70, 72, 70, 71, 96, 89, + 95, 72, 73, 67, 68, 7, 3, 1, 65, 12, 2, 0, 65, + 1, 80, 47, 44, 33, 37, 44, 36, 20, 37, 30, 17, + 23, 16, 5, 1, 65, 17, 11, 14, 14, 11, 7, 14, + 8, 9, 9, 2, 1, 6, 89, 62, 62, 62, 62, 62, 62, + 62, 62, 62, 54, 52, 51, 41, 36, 18, 49, 42, + 24, 54, 43, 45, 40, 35, 32, 35, 23, 15, 6, 8, + 74, 76, 98, 68, 26, 17, 12, 3, 5, 0, 70, 73, + 84, 0, 40, 27, 21, 17, 10, 1, 71, 72, 79, 83, + 19, 10, 6, 1, 64, 70, 75, 81, 98, 89, 70, 71, + 68, 71, 77, 87, 89, 103, 72, 34, 18, 12, 4, 1, + 72, 77, 81, 87, 62, 88, 79, 73, 66, 70, 1, 5, + 6, 71, 4, 8, 4, 78, 71, 70, 13, 6, 17, 14, 25, + 26, 19, 14, 19, 18, 15, 68, 6, 70, 95, 89, 85, + 83, 79, 80, 78, 79, 68, 74, 73, 71, 64, 75, + 82, 94, 85, 96, 76, 70, 70, 67, 68, 65, 0, 64, + 64, 67, 66, 1, 67, 78, 47, 40, 43, 41, 38, 44, + 40, 37, 32, 32, 33, 28, 10, 12, 5, 30, 28, 24, + 15, 19, 15, 4, 0, 67, 75, 79, 91, 75, 100, 62, + 62, 62, 62, 62, 58, 55, 51, 48, 44, 41, 35, + 28, 13, 66, 47, 46, 18, 49, 54, 43, 37, 40, + 35, 30, 34, 28, 26, 14, 14, 5, 79, 21, 11, 67, + 108, 98, 101, 77, 81, 79, 10, 70, 70, 68, 3, + 24, 11, 30, 66, 61, 59, 51, 44, 30, 27, 64, + 76, 95, 72, 41, 29, 20, 9, 10, 2, 64, 68, 85, + 96, 88, 79, 81, 75, 66, 72, 76, 66, 0, 1, 1, + 6, 9, 3, 59, 58, 52, 45, 41, 35, 19, 8, 76 }, + + { + + 62, + 8, 76, 62, 8, 76, 106, 86, 15, 10, 7, 13, 44, + 48, 54, 14, 90, 3, 68, 7, 1, 5, 70, 1, 77, 14, + 42, 86, 102, 110, 92, 89, 67, 68, 7, 1, 81, + 82, 7, 12, 66, 76, 87, 4, 72, 82, 89, 5, 73, + 86, 6, 72, 78, 88, 2, 70, 71, 76, 66, 5, 22, + 0, 0, 0, 76, 89, 97, 66, 13, 64, 52, 8, 69, + 114, 92, 82, 68, 91, 106, 103, 109, 81, 106, + 98, 104, 110, 106, 110, 106, 9, 66, 72, 94, + 67, 87, 82, 104, 73, 92, 85, 112, 10, 78, 70, + 108, 87, 78, 74, 67, 3, 65, 75, 7, 4, 67, 4, + 64, 64, 2, 76, 13, 6, 21, 19, 17, 18, 17, 16, + 23, 12, 8, 23, 18, 72, 98, 88, 91, 81, 85, 80, + 72, 78, 77, 77, 79, 70, 72, 70, 72, 96, 88, + 95, 72, 73, 66, 68, 6, 4, 1, 65, 11, 3, 0, 66, + 1, 81, 45, 43, 32, 36, 43, 35, 19, 36, 30, 15, + 22, 15, 5, 1, 66, 16, 10, 13, 13, 10, 6, 13, + 7, 7, 8, 1, 0, 5, 89, 62, 62, 61, 62, 62, 62, + 62, 62, 61, 52, 50, 48, 39, 34, 16, 47, 40, + 22, 52, 41, 43, 38, 33, 30, 32, 21, 12, 5, 6, + 75, 77, 98, 68, 26, 17, 12, 3, 5, 0, 70, 73, + 84, 0, 40, 27, 21, 17, 11, 1, 70, 72, 78, 83, + 20, 11, 6, 1, 64, 70, 74, 81, 97, 88, 69, 70, + 68, 71, 76, 86, 88, 102, 72, 34, 18, 12, 4, 2, + 71, 77, 80, 86, 62, 88, 79, 73, 66, 70, 1, 5, + 6, 71, 4, 8, 4, 78, 71, 70, 12, 7, 16, 13, 24, + 25, 19, 12, 18, 18, 15, 69, 5, 70, 95, 88, 84, + 82, 79, 79, 78, 78, 69, 74, 73, 71, 65, 75, + 82, 93, 84, 96, 76, 70, 70, 66, 68, 65, 1, 64, + 65, 67, 65, 0, 67, 79, 46, 40, 42, 40, 37, 43, + 39, 36, 31, 31, 32, 27, 9, 11, 4, 28, 27, 23, + 13, 18, 14, 3, 64, 68, 75, 79, 91, 75, 100, + 62, 62, 62, 62, 62, 56, 53, 48, 46, 42, 39, + 33, 26, 11, 67, 45, 44, 17, 47, 52, 41, 35, + 37, 33, 28, 32, 26, 23, 12, 12, 3, 80, 19, 9, + 68, 107, 97, 100, 76, 80, 78, 12, 69, 69, 68, + 4, 25, 13, 31, 65, 59, 57, 48, 41, 27, 24, 67, + 78, 96, 72, 41, 29, 20, 9, 10, 2, 64, 68, 85, + 95, 87, 78, 81, 74, 65, 72, 75, 65, 1, 2, 1, + 6, 9, 3, 58, 56, 50, 43, 39, 33, 17, 6, 77 }, + + { + + 62, + 8, 76, 62, 8, 76, 104, 85, 15, 10, 7, 12, 43, + 46, 54, 14, 87, 2, 67, 7, 1, 5, 69, 0, 78, 13, + 40, 88, 103, 111, 89, 87, 67, 67, 7, 1, 81, + 81, 7, 11, 66, 76, 87, 4, 72, 81, 89, 5, 73, + 85, 5, 72, 78, 88, 2, 69, 70, 75, 66, 5, 22, + 0, 0, 0, 75, 89, 97, 66, 12, 64, 52, 8, 69, + 113, 91, 82, 68, 89, 105, 101, 107, 80, 105, + 96, 102, 109, 105, 109, 106, 10, 66, 71, 93, + 67, 86, 81, 102, 73, 92, 85, 110, 11, 77, 70, + 107, 87, 78, 74, 66, 4, 64, 74, 7, 4, 67, 3, + 64, 64, 2, 76, 13, 6, 20, 19, 17, 18, 17, 15, + 21, 12, 8, 22, 17, 73, 97, 87, 90, 80, 84, 79, + 71, 77, 77, 76, 78, 70, 72, 69, 73, 95, 87, + 95, 71, 73, 66, 68, 5, 5, 1, 65, 9, 4, 64, 67, + 1, 81, 44, 42, 32, 35, 42, 34, 18, 36, 30, 14, + 21, 15, 5, 1, 66, 16, 10, 12, 13, 9, 5, 13, 6, + 5, 7, 0, 64, 4, 89, 61, 62, 59, 62, 61, 60, + 60, 60, 59, 50, 48, 46, 36, 32, 13, 45, 39, + 20, 49, 39, 41, 36, 30, 28, 29, 19, 10, 4, 5, + 77, 77, 98, 68, 26, 18, 12, 3, 5, 0, 70, 73, + 83, 1, 41, 27, 21, 17, 12, 2, 69, 72, 77, 82, + 21, 12, 6, 2, 0, 69, 74, 80, 96, 88, 67, 70, + 68, 70, 75, 85, 88, 101, 71, 35, 19, 12, 4, 3, + 70, 76, 79, 85, 62, 88, 79, 72, 66, 70, 1, 5, + 6, 70, 4, 9, 5, 78, 71, 70, 11, 7, 15, 12, 23, + 25, 19, 11, 17, 17, 15, 70, 4, 71, 94, 88, 83, + 82, 78, 79, 77, 77, 69, 74, 73, 71, 66, 75, + 82, 93, 83, 96, 76, 69, 70, 66, 68, 65, 2, 64, + 65, 67, 64, 0, 68, 79, 45, 40, 42, 40, 36, 42, + 38, 35, 31, 30, 31, 26, 8, 10, 3, 27, 25, 21, + 11, 17, 13, 2, 65, 68, 75, 78, 91, 76, 99, 62, + 62, 62, 62, 60, 54, 51, 46, 44, 40, 37, 31, + 24, 10, 68, 43, 42, 16, 45, 50, 39, 33, 35, + 31, 26, 30, 24, 21, 10, 10, 1, 81, 17, 7, 70, + 106, 95, 99, 76, 79, 77, 14, 68, 68, 68, 5, + 27, 14, 33, 65, 58, 55, 46, 38, 25, 21, 69, + 80, 97, 72, 41, 29, 20, 9, 10, 2, 64, 68, 84, + 94, 86, 77, 80, 73, 64, 71, 74, 64, 2, 3, 2, + 7, 10, 3, 56, 55, 49, 42, 37, 31, 15, 4, 78 }, + + { + + 61, + 8, 76, 61, 8, 76, 102, 83, 16, 10, 6, 10, 41, + 45, 54, 14, 85, 2, 66, 8, 1, 4, 69, 64, 79, + 13, 38, 89, 104, 111, 86, 86, 67, 66, 8, 1, + 80, 80, 8, 11, 66, 75, 86, 3, 73, 81, 89, 5, + 73, 85, 5, 72, 78, 88, 3, 69, 70, 75, 65, 5, + 22, 0, 0, 0, 75, 89, 97, 65, 11, 64, 52, 8, + 69, 112, 90, 82, 67, 88, 103, 99, 106, 79, + 103, 95, 100, 108, 104, 108, 105, 11, 66, 70, + 92, 67, 86, 81, 100, 73, 91, 84, 109, 11, 77, + 69, 105, 86, 77, 74, 66, 4, 64, 73, 8, 5, 68, + 3, 64, 64, 2, 76, 13, 5, 20, 19, 16, 18, 17, + 15, 19, 12, 7, 20, 16, 74, 96, 86, 89, 79, 83, + 79, 70, 77, 76, 75, 77, 70, 72, 69, 74, 95, + 86, 95, 71, 73, 65, 68, 4, 6, 1, 65, 8, 5, 64, + 68, 1, 82, 42, 41, 31, 34, 41, 33, 18, 35, 29, + 13, 20, 15, 5, 1, 67, 15, 9, 11, 12, 8, 4, 12, + 6, 3, 6, 0, 65, 3, 89, 60, 61, 58, 62, 59, 58, + 58, 58, 56, 47, 46, 43, 34, 29, 11, 43, 37, + 19, 47, 37, 38, 33, 28, 25, 27, 17, 7, 3, 3, + 78, 78, 98, 68, 27, 18, 12, 3, 5, 0, 70, 73, + 83, 1, 41, 27, 21, 17, 13, 2, 69, 71, 77, 81, + 22, 12, 7, 2, 1, 69, 73, 80, 95, 87, 66, 69, + 67, 70, 74, 84, 87, 100, 71, 35, 19, 12, 4, 4, + 70, 75, 78, 84, 62, 87, 78, 72, 66, 70, 1, 5, + 6, 70, 4, 9, 5, 78, 71, 70, 11, 8, 14, 11, 22, + 24, 19, 10, 16, 17, 15, 70, 3, 71, 93, 87, 82, + 81, 78, 78, 77, 76, 69, 74, 73, 70, 66, 75, + 82, 92, 82, 96, 76, 69, 70, 65, 68, 65, 3, 64, + 65, 67, 0, 64, 68, 80, 44, 39, 41, 39, 35, 41, + 37, 34, 30, 29, 30, 25, 7, 9, 2, 25, 24, 20, + 9, 15, 12, 1, 65, 69, 75, 78, 91, 76, 98, 62, + 62, 61, 61, 57, 52, 49, 43, 42, 38, 35, 28, + 22, 8, 69, 41, 41, 14, 43, 48, 37, 31, 33, 29, + 24, 28, 22, 18, 8, 8, 64, 82, 15, 5, 71, 105, + 94, 98, 75, 78, 76, 15, 67, 67, 68, 6, 28, 16, + 34, 64, 56, 54, 43, 35, 22, 18, 72, 82, 98, + 72, 41, 29, 20, 9, 10, 2, 64, 68, 84, 93, 85, + 76, 79, 72, 64, 71, 73, 64, 2, 3, 2, 7, 10, 3, + 55, 53, 47, 40, 35, 29, 13, 2, 79 }, + + { + + 60, + 8, 76, 60, 8, 76, 100, 82, 16, 10, 6, 9, 40, + 44, 54, 14, 83, 2, 65, 9, 1, 3, 69, 65, 80, + 12, 36, 91, 105, 112, 83, 85, 67, 65, 9, 1, + 80, 79, 8, 10, 66, 75, 85, 3, 73, 81, 89, 5, + 73, 84, 5, 72, 78, 88, 3, 69, 70, 75, 65, 5, + 22, 0, 0, 0, 74, 89, 97, 65, 10, 64, 52, 8, + 69, 111, 89, 82, 67, 87, 101, 97, 104, 78, + 101, 94, 98, 107, 103, 107, 104, 12, 66, 69, + 91, 67, 85, 80, 98, 73, 91, 84, 108, 11, 77, + 69, 104, 86, 76, 74, 66, 4, 64, 72, 8, 5, 68, + 3, 64, 64, 2, 76, 13, 5, 19, 19, 15, 18, 17, + 15, 17, 12, 7, 18, 15, 75, 95, 85, 88, 78, 82, + 78, 69, 76, 76, 74, 76, 70, 72, 69, 75, 94, + 85, 95, 71, 73, 64, 68, 3, 7, 1, 65, 7, 6, 65, + 69, 1, 83, 41, 40, 31, 33, 40, 32, 17, 34, 29, + 12, 19, 15, 5, 1, 67, 14, 8, 10, 11, 7, 3, 11, + 5, 1, 5, 64, 66, 2, 89, 58, 60, 56, 60, 57, + 56, 56, 56, 54, 45, 44, 41, 31, 27, 8, 41, 35, + 17, 45, 35, 36, 31, 26, 23, 24, 15, 5, 2, 1, + 79, 79, 98, 68, 27, 18, 12, 3, 5, 0, 70, 73, + 82, 1, 41, 27, 21, 17, 14, 3, 68, 71, 76, 80, + 23, 13, 7, 2, 2, 68, 73, 79, 94, 86, 65, 68, + 67, 70, 73, 83, 86, 99, 70, 35, 19, 12, 4, 5, + 69, 74, 77, 83, 62, 87, 78, 71, 66, 70, 1, 5, + 6, 70, 4, 9, 6, 78, 71, 70, 10, 8, 13, 10, 21, + 23, 19, 9, 15, 17, 15, 71, 2, 72, 92, 86, 81, + 81, 77, 78, 76, 75, 69, 74, 73, 70, 67, 75, + 82, 92, 81, 96, 76, 69, 70, 64, 68, 65, 4, 64, + 65, 67, 1, 65, 68, 81, 43, 39, 41, 38, 34, 40, + 36, 33, 29, 28, 29, 24, 6, 8, 1, 24, 23, 18, + 7, 14, 11, 0, 66, 69, 75, 78, 91, 77, 97, 62, + 62, 59, 59, 54, 50, 47, 41, 40, 36, 33, 26, + 20, 7, 70, 39, 39, 13, 41, 46, 35, 29, 31, 27, + 22, 26, 20, 16, 6, 6, 66, 83, 13, 3, 73, 104, + 93, 97, 74, 77, 75, 17, 66, 66, 68, 7, 30, 17, + 36, 0, 55, 52, 41, 32, 19, 15, 75, 84, 99, 72, + 41, 29, 20, 9, 10, 2, 64, 68, 83, 92, 84, 75, + 78, 71, 0, 70, 72, 0, 3, 4, 2, 8, 10, 3, 54, + 52, 45, 38, 33, 27, 11, 0, 80 }, + + { + + 58, + 7, 77, 58, 7, 77, 99, 81, 16, 10, 5, 7, 38, + 42, 53, 14, 81, 1, 65, 9, 0, 2, 69, 67, 82, + 11, 34, 93, 106, 113, 81, 84, 68, 65, 9, 0, + 80, 78, 8, 9, 66, 75, 85, 2, 74, 81, 90, 5, + 73, 84, 4, 73, 78, 88, 3, 69, 70, 75, 65, 4, + 22, 0, 0, 0, 74, 90, 97, 65, 9, 65, 52, 7, 69, + 110, 89, 82, 67, 86, 100, 96, 103, 77, 100, + 93, 97, 106, 103, 106, 104, 12, 66, 69, 91, + 67, 85, 80, 97, 73, 91, 84, 107, 11, 77, 69, + 103, 86, 76, 74, 66, 4, 64, 72, 8, 5, 69, 2, + 64, 65, 2, 76, 12, 4, 18, 19, 14, 17, 17, 14, + 15, 11, 6, 16, 14, 76, 95, 85, 88, 78, 82, 78, + 68, 76, 76, 74, 75, 71, 72, 69, 77, 94, 85, + 95, 71, 74, 64, 68, 2, 7, 1, 65, 5, 6, 66, 70, + 1, 84, 39, 39, 30, 32, 39, 31, 16, 33, 28, 10, + 18, 14, 4, 1, 68, 13, 7, 9, 10, 6, 2, 10, 4, + 64, 3, 65, 68, 0, 89, 56, 58, 54, 58, 55, 53, + 53, 53, 51, 42, 41, 38, 28, 24, 5, 39, 33, 15, + 42, 32, 33, 28, 23, 20, 21, 12, 2, 1, 64, 81, + 80, 99, 68, 27, 18, 12, 3, 5, 64, 70, 73, 82, + 1, 41, 27, 21, 17, 15, 3, 68, 71, 76, 80, 23, + 13, 7, 2, 2, 68, 73, 79, 93, 86, 64, 68, 67, + 70, 73, 83, 86, 98, 70, 35, 19, 12, 4, 5, 69, + 74, 77, 83, 62, 87, 78, 71, 66, 70, 1, 5, 6, + 70, 4, 9, 6, 78, 71, 71, 9, 8, 12, 9, 20, 22, + 18, 7, 13, 16, 14, 72, 0, 73, 92, 86, 80, 81, + 77, 78, 76, 75, 70, 74, 73, 70, 68, 75, 82, + 92, 81, 97, 76, 69, 70, 64, 69, 65, 4, 65, 66, + 67, 1, 66, 69, 82, 42, 38, 40, 37, 32, 39, 35, + 32, 28, 27, 28, 23, 5, 6, 64, 22, 21, 16, 5, + 12, 9, 64, 67, 70, 75, 78, 91, 78, 97, 62, 61, + 57, 56, 51, 47, 44, 38, 37, 33, 30, 23, 17, 5, + 71, 37, 37, 11, 39, 43, 32, 26, 28, 24, 20, + 23, 17, 13, 4, 3, 68, 85, 11, 1, 75, 103, 92, + 96, 74, 77, 75, 18, 66, 66, 68, 7, 31, 18, 37, + 0, 53, 50, 38, 28, 16, 12, 78, 87, 101, 72, + 41, 28, 19, 9, 10, 2, 65, 68, 83, 92, 84, 75, + 78, 70, 0, 70, 72, 0, 3, 4, 2, 8, 10, 2, 52, + 50, 43, 36, 31, 25, 8, 65, 81 }, + + { + + 57, + 7, 77, 57, 7, 77, 97, 79, 17, 11, 5, 6, 37, + 41, 53, 14, 78, 1, 64, 10, 0, 2, 68, 68, 83, + 11, 33, 94, 107, 113, 78, 82, 68, 64, 10, 0, + 79, 76, 9, 9, 65, 74, 84, 2, 74, 80, 90, 5, + 72, 83, 4, 73, 77, 88, 4, 68, 69, 74, 64, 4, + 22, 0, 0, 0, 73, 90, 97, 64, 9, 65, 52, 7, 69, + 108, 88, 82, 66, 84, 98, 94, 101, 75, 98, 91, + 95, 104, 102, 105, 103, 13, 65, 68, 90, 66, + 84, 79, 95, 72, 90, 83, 105, 12, 76, 68, 101, + 85, 75, 73, 65, 5, 0, 71, 9, 6, 69, 2, 0, 65, + 2, 75, 12, 4, 18, 19, 14, 17, 17, 14, 14, 11, + 6, 15, 13, 76, 94, 84, 87, 77, 81, 77, 67, 75, + 75, 73, 73, 71, 72, 68, 78, 93, 84, 95, 70, + 74, 0, 67, 2, 8, 1, 65, 4, 7, 66, 71, 1, 84, + 38, 39, 30, 32, 39, 31, 16, 33, 28, 9, 18, 14, + 4, 1, 68, 13, 7, 9, 10, 6, 1, 10, 4, 65, 2, + 65, 69, 64, 89, 55, 57, 53, 57, 54, 51, 51, + 51, 49, 40, 39, 36, 26, 22, 3, 38, 32, 14, 40, + 30, 31, 26, 21, 18, 19, 10, 0, 1, 65, 82, 80, + 99, 67, 28, 19, 13, 4, 5, 64, 69, 72, 81, 2, + 42, 28, 22, 17, 16, 4, 67, 70, 75, 79, 24, 14, + 8, 3, 3, 67, 72, 78, 91, 85, 1, 67, 66, 69, + 72, 82, 85, 96, 69, 36, 20, 13, 5, 6, 68, 73, + 76, 82, 62, 86, 77, 70, 66, 69, 1, 6, 7, 69, + 5, 10, 7, 77, 71, 71, 9, 9, 12, 9, 19, 22, 18, + 6, 12, 16, 14, 72, 64, 73, 91, 85, 79, 80, 76, + 77, 75, 74, 70, 73, 72, 69, 68, 74, 81, 91, + 80, 97, 76, 68, 70, 0, 69, 65, 5, 65, 66, 66, + 2, 66, 69, 82, 42, 38, 40, 37, 31, 39, 35, 32, + 28, 27, 28, 23, 5, 5, 65, 21, 20, 15, 4, 11, + 8, 64, 67, 70, 74, 77, 90, 78, 96, 60, 59, 55, + 54, 49, 45, 42, 36, 35, 31, 28, 21, 15, 4, 71, + 36, 36, 10, 38, 41, 30, 24, 26, 22, 18, 21, + 15, 11, 3, 1, 69, 86, 10, 0, 76, 101, 90, 94, + 73, 76, 74, 20, 65, 65, 68, 8, 33, 20, 39, 1, + 52, 49, 36, 25, 14, 10, 80, 89, 102, 71, 42, + 28, 19, 9, 11, 2, 65, 68, 82, 91, 83, 74, 77, + 68, 1, 69, 71, 1, 4, 5, 3, 9, 11, 2, 51, 49, + 42, 35, 30, 24, 6, 66, 81 }, + + { + + 56, + 7, 77, 56, 7, 77, 95, 78, 17, 11, 5, 5, 36, + 40, 53, 14, 76, 1, 0, 11, 0, 1, 68, 69, 84, + 10, 31, 96, 108, 114, 75, 81, 68, 0, 11, 0, + 79, 75, 9, 8, 65, 74, 83, 2, 74, 80, 90, 5, + 72, 82, 4, 73, 77, 88, 4, 68, 69, 74, 64, 4, + 22, 0, 0, 0, 72, 90, 97, 64, 8, 65, 52, 7, 69, + 107, 87, 82, 66, 83, 96, 92, 100, 74, 96, 90, + 93, 103, 101, 104, 102, 14, 65, 67, 89, 66, + 84, 78, 93, 72, 90, 83, 104, 12, 76, 68, 100, + 85, 74, 73, 65, 5, 0, 70, 9, 6, 70, 2, 0, 65, + 2, 75, 12, 4, 17, 19, 13, 17, 17, 14, 12, 11, + 6, 13, 12, 77, 93, 83, 86, 76, 80, 76, 66, 74, + 75, 72, 72, 71, 72, 68, 79, 93, 83, 95, 70, + 74, 1, 67, 1, 9, 1, 65, 3, 8, 67, 72, 1, 85, + 36, 38, 29, 31, 38, 30, 15, 32, 28, 8, 17, 14, + 4, 1, 68, 12, 6, 8, 9, 5, 0, 9, 3, 67, 1, 66, + 70, 65, 89, 53, 56, 51, 55, 52, 49, 49, 49, + 46, 38, 37, 33, 23, 20, 0, 36, 30, 12, 38, 28, + 29, 24, 19, 16, 16, 8, 65, 0, 67, 83, 81, 99, + 67, 28, 19, 13, 4, 5, 64, 69, 72, 80, 2, 42, + 28, 22, 17, 17, 4, 66, 70, 74, 78, 25, 15, 8, + 3, 4, 67, 72, 77, 90, 84, 2, 66, 66, 69, 71, + 81, 84, 95, 69, 36, 20, 13, 5, 7, 67, 72, 75, + 81, 62, 86, 77, 70, 66, 69, 1, 6, 7, 69, 5, + 10, 8, 77, 71, 71, 8, 9, 11, 8, 18, 21, 18, 5, + 11, 16, 14, 73, 65, 74, 90, 84, 78, 80, 76, + 77, 74, 73, 70, 73, 72, 69, 69, 74, 81, 91, + 79, 97, 76, 68, 70, 1, 69, 65, 6, 65, 66, 66, + 3, 67, 69, 83, 41, 38, 40, 36, 30, 38, 34, 31, + 27, 26, 27, 22, 4, 4, 66, 19, 19, 13, 2, 10, + 7, 65, 68, 70, 74, 77, 90, 79, 95, 58, 57, 53, + 52, 46, 43, 40, 33, 33, 29, 26, 19, 13, 3, 72, + 34, 34, 9, 36, 39, 28, 22, 24, 20, 16, 19, 13, + 9, 1, 64, 71, 87, 8, 65, 78, 100, 89, 93, 72, + 75, 73, 22, 64, 64, 68, 9, 34, 21, 40, 2, 51, + 47, 33, 22, 11, 7, 83, 91, 103, 71, 42, 28, + 19, 9, 11, 2, 65, 68, 81, 90, 82, 73, 76, 67, + 2, 68, 70, 2, 5, 6, 3, 10, 11, 2, 50, 47, 40, + 33, 28, 22, 4, 68, 82 }, + + { + + 55, + 7, 77, 55, 7, 77, 93, 76, 18, 11, 4, 3, 34, + 39, 53, 14, 74, 1, 1, 12, 0, 0, 68, 70, 85, + 10, 29, 97, 109, 114, 72, 80, 68, 1, 12, 0, + 78, 74, 10, 8, 65, 73, 82, 1, 75, 80, 90, 5, + 72, 82, 4, 73, 77, 88, 5, 68, 69, 74, 0, 4, + 22, 0, 0, 0, 72, 90, 97, 0, 7, 65, 52, 7, 69, + 106, 86, 82, 65, 82, 94, 90, 98, 73, 94, 89, + 91, 102, 100, 103, 101, 15, 65, 66, 88, 66, + 83, 78, 91, 72, 89, 82, 103, 12, 76, 67, 98, + 84, 73, 73, 65, 5, 0, 69, 10, 7, 70, 2, 0, 65, + 2, 75, 12, 3, 17, 19, 12, 17, 17, 14, 10, 11, + 5, 11, 11, 78, 92, 82, 85, 75, 79, 76, 65, 74, + 74, 71, 71, 71, 72, 68, 80, 92, 82, 95, 70, + 74, 2, 67, 0, 10, 1, 65, 2, 9, 67, 73, 1, 86, + 35, 37, 29, 30, 37, 29, 15, 31, 27, 7, 16, 14, + 4, 1, 69, 11, 5, 7, 8, 4, 64, 8, 3, 69, 0, 66, + 71, 66, 89, 52, 54, 50, 53, 50, 47, 47, 47, + 44, 35, 35, 31, 21, 17, 65, 34, 28, 11, 36, + 26, 26, 21, 17, 13, 14, 6, 68, 64, 69, 84, 82, + 99, 67, 29, 19, 13, 4, 5, 64, 69, 72, 80, 2, + 42, 28, 22, 17, 18, 5, 66, 69, 74, 77, 26, 15, + 9, 3, 5, 66, 71, 77, 89, 83, 3, 65, 65, 69, + 70, 80, 83, 94, 68, 36, 20, 13, 5, 8, 67, 71, + 74, 80, 62, 85, 76, 69, 66, 69, 1, 6, 7, 69, + 5, 10, 8, 77, 71, 71, 8, 10, 10, 7, 17, 20, + 18, 4, 10, 16, 14, 73, 66, 74, 89, 83, 77, 79, + 75, 76, 74, 72, 70, 73, 72, 68, 69, 74, 81, + 90, 78, 97, 76, 68, 70, 2, 69, 65, 7, 65, 66, + 66, 4, 68, 69, 84, 40, 37, 39, 35, 29, 37, 33, + 30, 26, 25, 26, 21, 3, 3, 67, 18, 18, 12, 0, + 8, 6, 66, 68, 71, 74, 77, 90, 79, 94, 56, 55, + 51, 50, 43, 41, 38, 31, 31, 27, 24, 16, 11, 1, + 73, 32, 33, 7, 34, 37, 26, 20, 22, 18, 14, 17, + 11, 6, 64, 66, 73, 88, 6, 67, 79, 99, 88, 92, + 71, 74, 72, 23, 0, 0, 68, 10, 36, 23, 42, 3, + 49, 46, 31, 19, 8, 4, 86, 93, 104, 71, 42, 28, + 19, 9, 11, 2, 65, 68, 81, 89, 81, 72, 75, 66, + 2, 68, 69, 2, 5, 6, 3, 10, 11, 2, 49, 46, 38, + 31, 26, 20, 2, 70, 83 }, + + { + + 53, + 7, 77, 53, 7, 77, 92, 75, 18, 11, 4, 2, 33, + 37, 53, 14, 71, 0, 2, 12, 0, 64, 68, 71, 86, + 9, 27, 99, 110, 115, 69, 79, 68, 2, 12, 0, 78, + 73, 10, 7, 65, 73, 82, 1, 75, 79, 90, 5, 72, + 81, 3, 74, 77, 88, 5, 67, 69, 73, 0, 4, 22, 0, + 0, 0, 71, 91, 97, 0, 6, 65, 52, 7, 69, 105, + 85, 82, 65, 80, 93, 88, 97, 72, 93, 87, 89, + 101, 100, 102, 101, 15, 65, 65, 87, 66, 83, + 77, 89, 72, 89, 82, 102, 12, 75, 67, 97, 84, + 73, 73, 64, 5, 0, 69, 10, 7, 71, 1, 0, 65, 2, + 75, 12, 3, 16, 19, 12, 17, 17, 13, 8, 11, 5, + 9, 10, 79, 91, 81, 84, 74, 79, 75, 64, 73, 74, + 70, 70, 71, 72, 67, 81, 92, 81, 95, 70, 74, 2, + 67, 64, 11, 1, 65, 0, 10, 68, 74, 1, 87, 33, + 36, 28, 29, 36, 28, 14, 30, 27, 5, 15, 13, 4, + 1, 69, 10, 5, 6, 8, 3, 65, 7, 2, 71, 64, 67, + 72, 67, 89, 50, 53, 48, 51, 48, 45, 44, 45, + 41, 33, 33, 28, 18, 15, 68, 32, 27, 9, 33, 24, + 24, 19, 14, 11, 11, 4, 70, 65, 70, 86, 83, 99, + 67, 29, 20, 13, 4, 5, 64, 69, 72, 79, 3, 43, + 28, 22, 17, 19, 5, 65, 69, 73, 77, 27, 16, 9, + 3, 5, 66, 71, 76, 88, 83, 4, 65, 65, 69, 69, + 79, 83, 93, 68, 37, 20, 13, 5, 9, 66, 71, 73, + 79, 62, 85, 76, 69, 66, 69, 1, 6, 7, 68, 5, + 10, 9, 77, 71, 71, 7, 10, 9, 6, 16, 19, 18, 2, + 9, 15, 14, 74, 67, 75, 89, 83, 76, 79, 75, 76, + 73, 71, 71, 73, 72, 68, 70, 74, 81, 90, 77, + 97, 76, 67, 70, 2, 69, 65, 8, 65, 67, 66, 5, + 68, 70, 85, 39, 37, 39, 34, 28, 36, 32, 29, + 25, 24, 25, 20, 2, 2, 68, 16, 16, 10, 65, 7, + 5, 67, 69, 71, 74, 77, 90, 80, 94, 53, 52, 49, + 47, 40, 39, 36, 28, 29, 25, 22, 14, 9, 0, 74, + 30, 31, 6, 32, 35, 24, 18, 19, 16, 12, 15, 9, + 4, 66, 68, 75, 89, 4, 69, 81, 98, 87, 91, 71, + 73, 71, 25, 1, 1, 68, 11, 37, 24, 43, 3, 48, + 44, 28, 16, 5, 1, 89, 95, 105, 71, 42, 28, 19, + 9, 11, 2, 65, 68, 80, 88, 80, 71, 75, 65, 3, + 67, 68, 3, 6, 7, 4, 11, 12, 2, 47, 44, 36, 29, + 24, 18, 0, 72, 84 }, + + { + + 52, + 7, 77, 52, 7, 77, 90, 73, 18, 11, 3, 0, 31, + 36, 53, 14, 69, 0, 3, 13, 0, 64, 67, 72, 87, + 9, 25, 101, 111, 115, 66, 77, 68, 3, 13, 0, + 78, 72, 10, 7, 65, 73, 81, 1, 76, 79, 90, 5, + 72, 80, 3, 74, 77, 88, 6, 67, 68, 73, 1, 4, + 22, 0, 0, 0, 71, 91, 97, 1, 5, 65, 52, 7, 69, + 104, 84, 82, 64, 79, 91, 86, 95, 71, 91, 86, + 87, 100, 99, 101, 100, 16, 65, 64, 86, 66, 82, + 76, 87, 72, 89, 81, 100, 13, 75, 66, 96, 83, + 72, 73, 64, 6, 1, 68, 11, 8, 71, 1, 0, 65, 2, + 75, 12, 2, 15, 19, 11, 17, 17, 13, 6, 11, 4, + 8, 9, 80, 90, 80, 83, 73, 78, 74, 0, 72, 73, + 69, 69, 71, 72, 67, 82, 91, 80, 95, 69, 74, 3, + 67, 65, 12, 1, 65, 64, 11, 68, 75, 1, 87, 32, + 35, 28, 28, 35, 27, 13, 30, 27, 4, 14, 13, 4, + 1, 70, 10, 4, 5, 7, 2, 66, 7, 1, 73, 65, 68, + 73, 68, 89, 48, 52, 46, 49, 47, 43, 42, 43, + 39, 31, 31, 26, 16, 13, 70, 30, 25, 7, 31, 22, + 22, 17, 12, 9, 8, 2, 73, 66, 72, 87, 83, 99, + 67, 29, 20, 13, 4, 5, 64, 69, 72, 79, 3, 43, + 28, 22, 17, 20, 6, 64, 69, 72, 76, 28, 17, 9, + 4, 6, 65, 70, 76, 87, 82, 6, 64, 65, 68, 68, + 78, 82, 92, 67, 37, 21, 13, 5, 10, 65, 70, 72, + 78, 62, 85, 76, 68, 66, 69, 1, 6, 7, 68, 5, + 11, 9, 77, 71, 71, 6, 11, 8, 5, 15, 19, 18, 1, + 8, 15, 14, 75, 68, 75, 88, 82, 75, 78, 74, 75, + 73, 70, 71, 73, 72, 68, 71, 74, 81, 89, 76, + 97, 76, 67, 70, 3, 69, 65, 9, 65, 67, 66, 6, + 69, 70, 85, 38, 37, 38, 34, 27, 35, 31, 28, + 25, 23, 24, 19, 1, 1, 69, 15, 15, 9, 67, 6, 4, + 68, 70, 72, 74, 76, 90, 80, 93, 51, 50, 47, + 45, 38, 37, 34, 26, 27, 23, 20, 12, 7, 65, 75, + 28, 29, 5, 30, 33, 22, 16, 17, 14, 10, 13, 7, + 1, 68, 70, 77, 90, 2, 71, 82, 97, 85, 90, 70, + 72, 70, 27, 2, 2, 68, 12, 39, 26, 45, 4, 46, + 42, 26, 13, 3, 65, 91, 97, 106, 71, 42, 28, + 19, 9, 11, 2, 65, 68, 80, 87, 79, 70, 74, 64, + 4, 67, 67, 4, 7, 8, 4, 11, 12, 2, 46, 43, 35, + 28, 22, 16, 65, 74, 85 }, + + { + + 51, + 7, 78, 51, 7, 78, 88, 72, 19, 11, 3, 64, 30, + 35, 53, 14, 67, 0, 3, 14, 0, 65, 67, 73, 88, + 8, 24, 102, 112, 116, 0, 76, 68, 3, 14, 0, 77, + 71, 11, 6, 64, 72, 80, 0, 76, 79, 90, 5, 71, + 80, 3, 74, 76, 88, 6, 67, 68, 73, 1, 4, 22, 0, + 0, 0, 70, 91, 97, 1, 5, 66, 52, 7, 69, 103, + 84, 82, 64, 78, 89, 84, 94, 70, 89, 85, 85, + 99, 98, 100, 99, 17, 65, 0, 86, 65, 82, 76, + 85, 72, 88, 81, 99, 13, 75, 66, 94, 83, 71, + 72, 64, 6, 1, 67, 11, 8, 72, 1, 0, 65, 2, 75, + 12, 2, 15, 19, 10, 17, 17, 13, 4, 11, 4, 6, 8, + 81, 90, 79, 83, 73, 77, 74, 1, 72, 73, 69, 67, + 71, 72, 67, 83, 91, 79, 95, 69, 74, 4, 66, 66, + 12, 1, 65, 65, 12, 69, 76, 1, 88, 30, 34, 27, + 28, 34, 27, 13, 29, 26, 3, 13, 13, 4, 1, 70, + 9, 3, 4, 6, 2, 67, 6, 1, 75, 66, 68, 74, 69, + 89, 47, 50, 45, 47, 45, 41, 40, 41, 36, 28, + 29, 23, 13, 10, 73, 28, 23, 6, 29, 19, 19, 14, + 10, 6, 6, 64, 75, 67, 74, 88, 84, 99, 66, 30, + 20, 14, 4, 5, 64, 69, 72, 78, 3, 43, 29, 22, + 17, 21, 6, 64, 68, 72, 75, 29, 17, 10, 4, 7, + 65, 70, 75, 86, 81, 7, 0, 64, 68, 68, 78, 81, + 90, 67, 37, 21, 13, 6, 10, 65, 69, 72, 77, 62, + 84, 75, 68, 66, 69, 1, 6, 7, 68, 6, 11, 10, + 77, 71, 72, 6, 11, 7, 4, 14, 18, 18, 0, 7, 15, + 14, 75, 69, 76, 87, 81, 74, 78, 74, 75, 72, + 70, 71, 72, 71, 67, 71, 74, 81, 89, 75, 97, + 76, 67, 70, 4, 69, 65, 10, 66, 67, 66, 6, 70, + 70, 86, 37, 36, 38, 33, 26, 35, 31, 27, 24, + 23, 23, 18, 0, 0, 70, 13, 14, 7, 69, 4, 2, 69, + 70, 72, 74, 76, 89, 81, 92, 49, 48, 45, 43, + 35, 35, 32, 23, 25, 20, 18, 9, 5, 66, 75, 27, + 28, 3, 28, 30, 20, 14, 15, 12, 8, 10, 5, 64, + 70, 72, 78, 92, 0, 73, 84, 96, 84, 89, 69, 71, + 69, 28, 3, 3, 68, 13, 40, 27, 46, 5, 45, 41, + 23, 10, 0, 67, 94, 99, 108, 70, 42, 28, 19, 9, + 11, 2, 65, 68, 79, 86, 79, 69, 73, 0, 4, 66, + 66, 4, 7, 8, 4, 12, 12, 2, 45, 41, 33, 26, 21, + 15, 68, 75, 86 }, + + { + + 50, + 7, 78, 50, 7, 78, 86, 70, 19, 11, 2, 66, 28, + 33, 53, 14, 64, 64, 4, 14, 0, 66, 67, 74, 89, + 8, 22, 104, 113, 116, 3, 75, 68, 4, 14, 0, 77, + 70, 11, 6, 64, 72, 80, 0, 77, 78, 90, 5, 71, + 79, 2, 74, 76, 88, 7, 66, 68, 72, 2, 4, 22, 0, + 0, 0, 70, 91, 97, 2, 4, 66, 52, 7, 69, 102, + 83, 82, 0, 76, 88, 82, 92, 69, 88, 83, 83, 98, + 97, 99, 99, 18, 65, 1, 85, 65, 81, 75, 83, 72, + 88, 80, 98, 13, 74, 65, 93, 82, 71, 72, 0, 6, + 1, 66, 12, 9, 72, 0, 0, 65, 2, 75, 12, 1, 14, + 19, 10, 17, 17, 12, 2, 11, 3, 4, 7, 82, 89, + 78, 82, 72, 76, 73, 2, 71, 72, 68, 66, 71, 72, + 66, 84, 90, 78, 95, 69, 74, 4, 66, 67, 13, 1, + 65, 67, 13, 69, 77, 1, 89, 29, 33, 27, 27, 33, + 26, 12, 28, 26, 2, 12, 13, 4, 1, 71, 8, 3, 3, + 6, 1, 68, 5, 0, 77, 67, 69, 75, 70, 89, 45, + 49, 43, 45, 43, 39, 37, 39, 34, 26, 27, 21, + 11, 8, 75, 26, 22, 4, 26, 17, 17, 12, 7, 4, 3, + 66, 78, 68, 75, 90, 85, 99, 66, 30, 21, 14, 4, + 5, 64, 69, 72, 78, 4, 44, 29, 22, 17, 22, 7, + 0, 68, 71, 74, 30, 18, 10, 4, 8, 64, 69, 75, + 85, 81, 8, 0, 64, 68, 67, 77, 81, 89, 66, 38, + 21, 13, 6, 11, 64, 68, 71, 76, 62, 84, 75, 67, + 66, 69, 1, 6, 7, 67, 6, 11, 10, 77, 71, 72, 5, + 12, 6, 3, 13, 17, 18, 64, 6, 14, 14, 76, 70, + 76, 86, 81, 73, 77, 73, 74, 72, 69, 71, 72, + 71, 67, 72, 74, 81, 88, 74, 97, 76, 66, 70, 4, + 69, 65, 11, 66, 67, 66, 7, 70, 71, 87, 36, 36, + 37, 32, 25, 34, 30, 26, 23, 22, 22, 17, 64, + 64, 71, 12, 12, 6, 71, 3, 1, 70, 71, 73, 74, + 76, 89, 81, 91, 47, 46, 43, 40, 32, 33, 30, + 21, 23, 18, 16, 7, 3, 68, 76, 25, 26, 2, 26, + 28, 18, 12, 13, 10, 6, 8, 3, 67, 72, 74, 80, + 93, 65, 75, 85, 95, 83, 88, 69, 70, 68, 30, 4, + 4, 68, 14, 42, 29, 48, 5, 43, 39, 21, 7, 66, + 70, 97, 101, 109, 70, 42, 28, 19, 9, 11, 2, + 65, 68, 79, 85, 78, 68, 72, 1, 5, 66, 65, 5, + 8, 9, 5, 12, 13, 2, 43, 40, 31, 24, 19, 13, + 70, 77, 87 }, + + { + + 48, + 6, 78, 48, 6, 78, 85, 69, 19, 11, 2, 67, 27, + 32, 53, 14, 1, 64, 5, 15, 0, 67, 67, 75, 91, + 7, 20, 106, 114, 117, 5, 74, 68, 5, 15, 0, 77, + 69, 11, 5, 64, 72, 79, 64, 77, 78, 91, 5, 71, + 79, 2, 75, 76, 88, 7, 66, 68, 72, 2, 4, 22, 0, + 0, 0, 69, 92, 97, 2, 3, 66, 52, 7, 69, 101, + 82, 82, 0, 75, 86, 80, 91, 68, 86, 82, 82, 97, + 97, 98, 98, 18, 65, 2, 84, 65, 81, 75, 82, 72, + 88, 80, 97, 13, 74, 65, 92, 82, 70, 72, 0, 6, + 1, 66, 12, 9, 73, 0, 0, 65, 2, 75, 12, 1, 13, + 19, 9, 17, 17, 12, 0, 11, 3, 2, 6, 83, 88, 77, + 81, 71, 76, 73, 3, 71, 72, 67, 65, 71, 72, 66, + 86, 90, 77, 95, 69, 75, 5, 66, 68, 14, 1, 65, + 68, 14, 70, 78, 1, 90, 27, 32, 26, 26, 32, 25, + 11, 27, 25, 0, 11, 12, 4, 1, 71, 7, 2, 2, 5, + 0, 69, 4, 64, 79, 69, 70, 76, 71, 89, 43, 47, + 41, 43, 41, 37, 35, 37, 31, 23, 25, 18, 8, 5, + 78, 24, 20, 2, 24, 15, 14, 9, 5, 1, 0, 68, 80, + 69, 77, 91, 86, 100, 66, 30, 21, 14, 4, 5, 64, + 69, 72, 77, 4, 44, 29, 22, 17, 23, 7, 0, 68, + 71, 74, 31, 18, 10, 4, 8, 64, 69, 74, 84, 80, + 9, 1, 64, 68, 66, 76, 80, 88, 66, 38, 21, 13, + 6, 12, 64, 68, 70, 76, 62, 84, 75, 67, 66, 69, + 1, 6, 7, 67, 6, 11, 11, 77, 71, 72, 4, 12, 5, + 2, 12, 16, 18, 66, 4, 14, 14, 77, 71, 77, 86, + 80, 72, 77, 73, 74, 71, 68, 72, 72, 71, 67, + 73, 74, 81, 88, 74, 98, 76, 66, 70, 5, 69, 65, + 11, 66, 68, 66, 8, 71, 71, 88, 35, 35, 37, 31, + 23, 33, 29, 25, 22, 21, 21, 16, 65, 65, 72, + 10, 11, 4, 73, 1, 0, 71, 72, 73, 74, 76, 89, + 82, 91, 44, 43, 41, 38, 29, 30, 27, 18, 21, + 16, 14, 4, 1, 69, 77, 23, 24, 0, 24, 26, 15, + 9, 10, 7, 4, 6, 0, 69, 74, 77, 82, 94, 67, 77, + 87, 94, 82, 87, 68, 69, 68, 31, 5, 4, 68, 14, + 43, 30, 49, 6, 42, 37, 18, 4, 69, 73, 100, + 103, 110, 70, 42, 28, 19, 9, 11, 2, 65, 68, + 78, 85, 77, 67, 72, 2, 5, 65, 65, 5, 8, 9, 5, + 13, 13, 1, 42, 38, 29, 22, 17, 11, 72, 79, 88 }, + + { + + 47, + 6, 78, 47, 6, 78, 83, 68, 20, 11, 2, 68, 26, + 31, 53, 14, 3, 64, 6, 16, 0, 67, 66, 76, 92, + 6, 18, 107, 115, 118, 8, 72, 68, 6, 16, 0, 76, + 68, 12, 4, 64, 71, 78, 64, 77, 78, 91, 5, 71, + 78, 2, 75, 76, 88, 7, 66, 67, 72, 2, 4, 22, 0, + 0, 0, 68, 92, 97, 2, 2, 66, 52, 7, 69, 100, + 81, 82, 0, 74, 84, 78, 89, 66, 84, 81, 80, 96, + 96, 97, 97, 19, 64, 3, 83, 65, 80, 74, 80, 72, + 87, 80, 95, 14, 74, 65, 90, 82, 69, 72, 0, 7, + 2, 65, 12, 9, 73, 0, 1, 65, 2, 74, 12, 1, 13, + 19, 8, 17, 17, 12, 65, 11, 3, 1, 5, 83, 87, + 76, 80, 70, 75, 72, 4, 70, 72, 66, 64, 71, 72, + 66, 87, 89, 76, 95, 68, 75, 6, 66, 69, 15, 1, + 65, 69, 15, 71, 79, 1, 90, 26, 31, 26, 25, 31, + 24, 11, 27, 25, 64, 10, 12, 4, 1, 71, 7, 1, 1, + 4, 64, 70, 4, 64, 80, 70, 70, 77, 72, 89, 42, + 46, 40, 42, 40, 35, 33, 35, 29, 21, 23, 16, 5, + 3, 81, 23, 18, 1, 22, 13, 12, 7, 3, 64, 65, + 70, 82, 69, 79, 92, 86, 100, 66, 31, 21, 14, + 5, 5, 64, 68, 72, 76, 4, 44, 29, 23, 17, 24, + 8, 1, 67, 70, 73, 32, 19, 11, 5, 9, 0, 69, 73, + 83, 79, 11, 2, 0, 67, 65, 75, 79, 87, 65, 38, + 22, 14, 6, 13, 0, 67, 69, 75, 62, 83, 74, 66, + 66, 69, 1, 7, 8, 67, 6, 12, 12, 77, 71, 72, 4, + 12, 4, 2, 11, 16, 18, 67, 3, 14, 14, 77, 72, + 78, 85, 79, 71, 77, 72, 74, 70, 67, 72, 72, + 71, 66, 73, 74, 81, 88, 73, 98, 76, 66, 70, 6, + 69, 65, 12, 66, 68, 66, 9, 72, 71, 88, 34, 35, + 37, 31, 22, 32, 28, 24, 22, 20, 20, 16, 65, + 66, 73, 9, 10, 2, 75, 0, 64, 71, 72, 73, 73, + 75, 89, 83, 90, 42, 41, 39, 36, 27, 28, 25, + 16, 19, 14, 12, 2, 64, 70, 78, 21, 23, 64, 22, + 24, 13, 7, 8, 5, 2, 4, 65, 71, 75, 79, 84, 95, + 69, 79, 89, 93, 80, 85, 67, 68, 67, 33, 6, 5, + 68, 15, 45, 31, 51, 7, 41, 36, 16, 1, 71, 76, + 102, 105, 111, 70, 42, 28, 19, 9, 12, 2, 65, + 68, 77, 84, 76, 66, 71, 4, 6, 64, 64, 6, 9, + 10, 5, 14, 13, 1, 41, 37, 28, 21, 15, 9, 74, + 81, 88 }, + + { + + 46, + 6, 78, 46, 6, 78, 81, 66, 20, 11, 1, 70, 24, + 29, 53, 14, 6, 65, 7, 16, 0, 68, 66, 77, 93, + 6, 16, 109, 116, 118, 11, 71, 68, 7, 16, 0, + 76, 67, 12, 4, 64, 71, 78, 64, 78, 77, 91, 5, + 71, 77, 1, 75, 76, 88, 8, 65, 67, 71, 3, 4, + 22, 0, 0, 0, 68, 92, 97, 3, 1, 66, 52, 7, 69, + 99, 80, 82, 1, 72, 83, 76, 88, 65, 83, 79, 78, + 95, 95, 96, 97, 20, 64, 4, 82, 65, 80, 73, 78, + 72, 87, 79, 94, 14, 73, 64, 89, 81, 69, 72, 1, + 7, 2, 64, 13, 10, 74, 64, 1, 65, 2, 74, 12, 0, + 12, 19, 8, 17, 17, 11, 67, 11, 2, 64, 4, 84, + 86, 75, 79, 69, 74, 71, 5, 69, 71, 65, 0, 71, + 72, 65, 88, 89, 75, 95, 68, 75, 6, 66, 70, 16, + 1, 65, 71, 16, 71, 80, 1, 91, 24, 30, 25, 24, + 30, 23, 10, 26, 25, 65, 9, 12, 4, 1, 72, 6, 1, + 0, 4, 65, 71, 3, 65, 82, 71, 71, 78, 73, 89, + 40, 45, 38, 40, 38, 33, 30, 33, 26, 19, 21, + 13, 3, 1, 83, 21, 17, 64, 19, 11, 10, 5, 0, + 66, 68, 72, 85, 70, 80, 94, 87, 100, 66, 31, + 22, 14, 5, 5, 64, 68, 72, 76, 5, 45, 29, 23, + 17, 25, 8, 2, 67, 69, 72, 33, 20, 11, 5, 10, + 0, 68, 73, 82, 79, 12, 2, 0, 67, 64, 74, 79, + 86, 65, 39, 22, 14, 6, 14, 1, 66, 68, 74, 62, + 83, 74, 66, 66, 69, 1, 7, 8, 66, 6, 12, 12, + 77, 71, 72, 3, 13, 3, 1, 10, 15, 18, 68, 2, + 13, 14, 78, 73, 78, 84, 79, 70, 76, 72, 73, + 70, 66, 72, 72, 71, 66, 74, 74, 81, 87, 72, + 98, 76, 65, 70, 6, 69, 65, 13, 66, 68, 66, 10, + 72, 72, 89, 33, 35, 36, 30, 21, 31, 27, 23, + 21, 19, 19, 15, 66, 67, 74, 7, 8, 1, 77, 64, + 65, 72, 73, 74, 73, 75, 89, 83, 89, 40, 39, + 37, 33, 24, 26, 23, 13, 17, 12, 10, 0, 66, 72, + 79, 19, 21, 65, 20, 22, 11, 5, 6, 3, 0, 2, 67, + 74, 77, 81, 86, 96, 71, 81, 90, 92, 79, 84, + 67, 67, 66, 35, 7, 6, 68, 16, 46, 33, 52, 7, + 39, 34, 13, 65, 74, 79, 105, 107, 112, 70, 42, + 28, 19, 9, 12, 2, 65, 68, 77, 83, 75, 65, 70, + 5, 7, 64, 0, 7, 10, 11, 6, 14, 14, 1, 39, 35, + 26, 19, 13, 7, 76, 83, 89 }, + + { + + 45, + 6, 79, 45, 6, 79, 79, 65, 21, 11, 1, 71, 23, + 28, 53, 14, 8, 65, 7, 17, 0, 69, 66, 78, 94, + 5, 15, 110, 117, 119, 14, 70, 68, 7, 17, 0, + 75, 66, 13, 3, 0, 70, 77, 65, 78, 77, 91, 5, + 70, 77, 1, 75, 75, 88, 8, 65, 67, 71, 3, 4, + 22, 0, 0, 0, 67, 92, 97, 3, 1, 67, 52, 7, 69, + 98, 80, 82, 1, 71, 81, 74, 86, 64, 81, 78, 76, + 94, 94, 95, 96, 21, 64, 5, 82, 64, 79, 73, 76, + 72, 86, 79, 93, 14, 73, 64, 87, 81, 68, 71, 1, + 7, 2, 0, 13, 10, 74, 64, 1, 65, 2, 74, 12, 0, + 12, 19, 7, 17, 17, 11, 69, 11, 2, 66, 3, 85, + 86, 74, 79, 69, 73, 71, 6, 69, 71, 65, 2, 71, + 72, 65, 89, 88, 74, 95, 68, 75, 7, 65, 71, 16, + 1, 65, 72, 17, 72, 81, 1, 92, 23, 29, 25, 24, + 29, 23, 10, 25, 24, 66, 8, 12, 4, 1, 72, 5, 0, + 64, 3, 65, 72, 2, 65, 84, 72, 71, 79, 74, 89, + 39, 43, 37, 38, 36, 31, 28, 31, 24, 16, 19, + 11, 0, 65, 86, 19, 15, 65, 17, 8, 7, 2, 65, + 69, 70, 75, 87, 71, 82, 95, 88, 100, 65, 32, + 22, 15, 5, 5, 64, 68, 72, 75, 5, 45, 30, 23, + 17, 26, 9, 2, 66, 69, 71, 34, 20, 12, 5, 11, + 1, 68, 72, 81, 78, 13, 3, 1, 67, 64, 74, 78, + 84, 64, 39, 22, 14, 7, 14, 1, 65, 68, 73, 62, + 82, 73, 65, 66, 69, 1, 7, 8, 66, 7, 12, 13, + 77, 71, 73, 3, 13, 2, 0, 9, 14, 18, 69, 1, 13, + 14, 78, 74, 79, 83, 78, 69, 76, 71, 73, 69, + 66, 72, 71, 70, 65, 74, 74, 81, 87, 71, 98, + 76, 65, 70, 7, 69, 65, 14, 67, 68, 66, 10, 73, + 72, 90, 32, 34, 36, 29, 20, 31, 27, 22, 20, + 19, 18, 14, 67, 68, 75, 6, 7, 64, 79, 66, 67, + 73, 73, 74, 73, 75, 88, 84, 88, 38, 37, 35, + 31, 21, 24, 21, 11, 15, 9, 8, 66, 68, 73, 79, + 18, 20, 67, 18, 19, 9, 3, 4, 1, 65, 64, 69, + 76, 79, 83, 87, 98, 73, 83, 92, 91, 78, 83, + 66, 66, 65, 36, 8, 7, 68, 17, 48, 34, 54, 8, + 38, 33, 11, 68, 77, 81, 108, 109, 114, 69, 42, + 28, 19, 9, 12, 2, 65, 68, 76, 82, 75, 64, 69, + 6, 7, 0, 1, 7, 10, 11, 6, 15, 14, 1, 38, 34, + 24, 17, 12, 6, 79, 84, 90 }, + + { + + 43, + 6, 79, 43, 6, 79, 78, 0, 21, 11, 0, 73, 21, + 27, 53, 14, 10, 65, 8, 18, 0, 70, 66, 79, 95, + 5, 13, 112, 118, 119, 17, 69, 68, 8, 18, 0, + 75, 65, 13, 3, 0, 70, 76, 65, 79, 77, 91, 5, + 70, 76, 1, 76, 75, 88, 9, 65, 67, 71, 4, 4, + 22, 0, 0, 0, 67, 93, 97, 4, 0, 67, 52, 7, 69, + 97, 79, 82, 2, 70, 79, 72, 85, 0, 79, 77, 74, + 93, 94, 94, 95, 21, 64, 6, 81, 64, 79, 72, 74, + 72, 86, 78, 92, 14, 73, 0, 86, 80, 67, 71, 1, + 7, 2, 0, 14, 11, 75, 64, 1, 65, 2, 74, 12, 64, + 11, 19, 6, 17, 17, 11, 71, 11, 1, 68, 2, 86, + 85, 73, 78, 68, 73, 70, 7, 68, 70, 64, 3, 71, + 72, 65, 90, 88, 73, 95, 68, 75, 8, 65, 72, 17, + 1, 65, 73, 18, 72, 82, 1, 93, 21, 28, 24, 23, + 28, 22, 9, 24, 24, 68, 7, 11, 4, 1, 73, 4, 64, + 65, 2, 66, 73, 1, 66, 86, 73, 72, 80, 75, 89, + 37, 42, 35, 36, 34, 29, 26, 29, 21, 14, 17, 8, + 65, 67, 88, 17, 13, 67, 15, 6, 5, 0, 67, 71, + 73, 77, 90, 72, 84, 96, 89, 100, 65, 32, 22, + 15, 5, 5, 64, 68, 72, 75, 5, 45, 30, 23, 17, + 27, 9, 3, 66, 68, 71, 35, 21, 12, 5, 11, 1, + 67, 72, 80, 77, 14, 4, 1, 67, 0, 73, 77, 83, + 64, 39, 22, 14, 7, 15, 2, 65, 67, 72, 62, 82, + 73, 65, 66, 69, 1, 7, 8, 66, 7, 12, 13, 77, + 71, 73, 2, 14, 1, 64, 8, 13, 18, 71, 0, 13, + 14, 79, 75, 79, 83, 77, 68, 75, 71, 72, 69, + 65, 73, 71, 70, 65, 75, 74, 81, 86, 70, 98, + 76, 65, 70, 8, 69, 65, 15, 67, 69, 66, 11, 74, + 72, 91, 31, 34, 35, 28, 19, 30, 26, 21, 19, + 18, 17, 13, 68, 69, 76, 4, 6, 65, 81, 67, 68, + 74, 74, 75, 73, 75, 88, 84, 88, 35, 34, 33, + 29, 18, 22, 19, 8, 13, 7, 6, 68, 70, 75, 80, + 16, 18, 68, 16, 17, 7, 1, 1, 64, 67, 66, 71, + 79, 81, 85, 89, 99, 75, 85, 93, 90, 77, 82, + 65, 65, 64, 38, 9, 8, 68, 18, 49, 36, 55, 9, + 36, 31, 8, 71, 80, 84, 111, 111, 115, 69, 42, + 28, 19, 9, 12, 2, 65, 68, 76, 81, 74, 0, 69, + 7, 8, 0, 2, 8, 11, 12, 6, 15, 14, 1, 37, 32, + 22, 15, 10, 4, 81, 86, 91 }, + + { + + 42, + 6, 79, 42, 6, 79, 76, 1, 21, 11, 0, 74, 20, + 25, 53, 14, 13, 66, 9, 18, 0, 70, 65, 80, 96, + 4, 11, 114, 119, 120, 20, 67, 68, 9, 18, 0, + 75, 64, 13, 2, 0, 70, 76, 65, 79, 76, 91, 5, + 70, 75, 0, 76, 75, 88, 9, 64, 66, 70, 4, 4, + 22, 0, 0, 0, 66, 93, 97, 4, 64, 67, 52, 7, 69, + 96, 78, 82, 2, 68, 78, 70, 83, 1, 78, 75, 72, + 92, 93, 93, 95, 22, 64, 7, 80, 64, 78, 71, 72, + 72, 86, 78, 90, 15, 72, 0, 85, 80, 67, 71, 2, + 8, 3, 1, 14, 11, 75, 65, 1, 65, 2, 74, 12, 64, + 10, 19, 6, 17, 17, 10, 73, 11, 1, 69, 1, 87, + 84, 72, 77, 67, 72, 69, 8, 67, 70, 0, 4, 71, + 72, 64, 91, 87, 72, 95, 67, 75, 8, 65, 73, 18, + 1, 65, 75, 19, 73, 83, 1, 93, 20, 27, 24, 22, + 27, 21, 8, 24, 24, 69, 6, 11, 4, 1, 73, 4, 64, + 66, 2, 67, 74, 1, 67, 88, 74, 73, 81, 76, 89, + 35, 41, 33, 34, 33, 27, 23, 27, 19, 12, 15, 6, + 68, 69, 91, 15, 12, 69, 12, 4, 3, 65, 70, 73, + 76, 79, 92, 73, 85, 98, 89, 100, 65, 32, 23, + 15, 5, 5, 64, 68, 72, 74, 6, 46, 30, 23, 17, + 28, 10, 4, 66, 67, 70, 36, 22, 12, 6, 12, 2, + 67, 71, 79, 77, 16, 4, 1, 66, 1, 72, 77, 82, + 0, 40, 23, 14, 7, 16, 3, 64, 66, 71, 62, 82, + 73, 64, 66, 69, 1, 7, 8, 65, 7, 13, 14, 77, + 71, 73, 1, 14, 0, 65, 7, 13, 18, 72, 64, 12, + 14, 80, 76, 80, 82, 77, 67, 75, 70, 72, 68, + 64, 73, 71, 70, 65, 76, 74, 81, 86, 69, 98, + 76, 64, 70, 8, 69, 65, 16, 67, 69, 66, 12, 74, + 73, 91, 30, 34, 35, 28, 18, 29, 25, 20, 19, + 17, 16, 12, 69, 70, 77, 3, 4, 67, 83, 68, 69, + 75, 75, 75, 73, 74, 88, 85, 87, 33, 32, 31, + 26, 16, 20, 17, 6, 11, 5, 4, 70, 72, 76, 81, + 14, 16, 69, 14, 15, 5, 64, 64, 66, 69, 68, 73, + 81, 83, 87, 91, 100, 77, 87, 95, 89, 75, 81, + 65, 64, 0, 40, 10, 9, 68, 19, 51, 37, 57, 9, + 35, 29, 6, 74, 82, 87, 113, 113, 116, 69, 42, + 28, 19, 9, 12, 2, 65, 68, 75, 80, 73, 1, 68, + 8, 9, 1, 3, 9, 12, 13, 7, 16, 15, 1, 35, 31, + 21, 14, 8, 2, 83, 88, 92 }, + + { + + 41, + 6, 79, 41, 6, 79, 74, 3, 22, 11, 64, 76, 18, + 24, 53, 14, 15, 66, 10, 19, 0, 71, 65, 81, 97, + 4, 9, 115, 120, 120, 23, 66, 68, 10, 19, 0, + 74, 0, 14, 2, 0, 69, 75, 66, 80, 76, 91, 5, + 70, 75, 0, 76, 75, 88, 10, 64, 66, 70, 5, 4, + 22, 0, 0, 0, 66, 93, 97, 5, 65, 67, 52, 7, 69, + 95, 77, 82, 3, 67, 76, 68, 82, 2, 76, 74, 70, + 91, 92, 92, 94, 23, 64, 8, 79, 64, 78, 71, 70, + 72, 85, 77, 89, 15, 72, 1, 83, 79, 66, 71, 2, + 8, 3, 2, 15, 12, 76, 65, 1, 65, 2, 74, 12, 65, + 10, 19, 5, 17, 17, 10, 75, 11, 0, 71, 0, 88, + 83, 71, 76, 66, 71, 69, 9, 67, 69, 1, 5, 71, + 72, 64, 92, 87, 71, 95, 67, 75, 9, 65, 74, 19, + 1, 65, 76, 20, 73, 84, 1, 94, 18, 26, 23, 21, + 26, 20, 8, 23, 23, 70, 5, 11, 4, 1, 74, 3, 65, + 67, 1, 68, 75, 0, 67, 90, 75, 73, 82, 77, 89, + 34, 39, 32, 32, 31, 25, 21, 25, 16, 9, 13, 3, + 70, 72, 93, 13, 10, 70, 10, 2, 0, 68, 72, 76, + 78, 81, 95, 74, 87, 99, 90, 100, 65, 33, 23, + 15, 5, 5, 64, 68, 72, 74, 6, 46, 30, 23, 17, + 29, 10, 4, 65, 67, 69, 37, 22, 13, 6, 13, 2, + 66, 71, 78, 76, 17, 5, 2, 66, 2, 71, 76, 81, + 0, 40, 23, 14, 7, 17, 3, 0, 65, 70, 62, 81, + 72, 64, 66, 69, 1, 7, 8, 65, 7, 13, 14, 77, + 71, 73, 1, 15, 64, 66, 6, 12, 18, 73, 65, 12, + 14, 80, 77, 80, 81, 76, 66, 74, 70, 71, 68, 0, + 73, 71, 70, 64, 76, 74, 81, 85, 68, 98, 76, + 64, 70, 9, 69, 65, 17, 67, 69, 66, 13, 75, 73, + 92, 29, 33, 34, 27, 17, 28, 24, 19, 18, 16, + 15, 11, 70, 71, 78, 1, 3, 68, 85, 70, 70, 76, + 75, 76, 73, 74, 88, 85, 86, 31, 30, 29, 24, + 13, 18, 15, 3, 9, 3, 2, 73, 74, 78, 82, 12, + 15, 71, 12, 13, 3, 66, 66, 68, 71, 70, 75, 84, + 85, 89, 93, 101, 79, 89, 96, 88, 74, 80, 64, + 0, 1, 41, 11, 10, 68, 20, 52, 39, 58, 10, 33, + 28, 3, 77, 85, 90, 116, 115, 117, 69, 42, 28, + 19, 9, 12, 2, 65, 68, 75, 79, 72, 2, 67, 9, 9, + 1, 4, 9, 12, 13, 7, 16, 15, 1, 34, 29, 19, 12, + 6, 0, 85, 90, 93 }, + + { + + 40, + 6, 79, 40, 6, 79, 72, 4, 22, 11, 64, 77, 17, + 23, 53, 14, 17, 66, 11, 20, 0, 72, 65, 82, 98, + 3, 7, 117, 121, 121, 26, 65, 68, 11, 20, 0, + 74, 1, 14, 1, 0, 69, 74, 66, 80, 76, 91, 5, + 70, 74, 0, 76, 75, 88, 10, 64, 66, 70, 5, 4, + 22, 0, 0, 0, 65, 93, 97, 5, 66, 67, 52, 7, 69, + 94, 76, 82, 3, 66, 74, 66, 80, 3, 74, 73, 68, + 90, 91, 91, 93, 24, 64, 9, 78, 64, 77, 70, 68, + 72, 85, 77, 88, 15, 72, 1, 82, 79, 65, 71, 2, + 8, 3, 3, 15, 12, 76, 65, 1, 65, 2, 74, 12, 65, + 9, 19, 4, 17, 17, 10, 77, 11, 0, 73, 64, 89, + 82, 70, 75, 65, 70, 68, 10, 66, 69, 2, 6, 71, + 72, 64, 93, 86, 70, 95, 67, 75, 10, 65, 75, + 20, 1, 65, 77, 21, 74, 85, 1, 95, 17, 25, 23, + 20, 25, 19, 7, 22, 23, 71, 4, 11, 4, 1, 74, 2, + 66, 68, 0, 69, 76, 64, 68, 92, 76, 74, 83, 78, + 89, 32, 38, 30, 30, 29, 23, 19, 23, 14, 7, 11, + 1, 73, 74, 96, 11, 8, 72, 8, 0, 65, 70, 74, + 78, 81, 83, 97, 75, 89, 100, 91, 100, 65, 33, + 23, 15, 5, 5, 64, 68, 72, 73, 6, 46, 30, 23, + 17, 30, 11, 5, 65, 66, 68, 38, 23, 13, 6, 14, + 3, 66, 70, 77, 75, 18, 6, 2, 66, 3, 70, 75, + 80, 1, 40, 23, 14, 7, 18, 4, 1, 64, 69, 62, + 81, 72, 0, 66, 69, 1, 7, 8, 65, 7, 13, 15, 77, + 71, 73, 0, 15, 65, 67, 5, 11, 18, 74, 66, 12, + 14, 81, 78, 81, 80, 75, 65, 74, 69, 71, 67, 1, + 73, 71, 70, 64, 77, 74, 81, 85, 67, 98, 76, + 64, 70, 10, 69, 65, 18, 67, 69, 66, 14, 76, + 73, 93, 28, 33, 34, 26, 16, 27, 23, 18, 17, + 15, 14, 10, 71, 72, 79, 0, 2, 70, 87, 71, 71, + 77, 76, 76, 73, 74, 88, 86, 85, 29, 28, 27, + 22, 10, 16, 13, 1, 7, 1, 0, 75, 76, 79, 83, + 10, 13, 72, 10, 11, 1, 68, 68, 70, 73, 72, 77, + 86, 87, 91, 95, 102, 81, 91, 98, 87, 73, 79, + 0, 1, 2, 43, 12, 11, 68, 21, 54, 40, 60, 11, + 32, 26, 1, 80, 88, 93, 119, 117, 118, 69, 42, + 28, 19, 9, 12, 2, 65, 68, 74, 78, 71, 3, 66, + 10, 10, 2, 5, 10, 13, 14, 7, 17, 15, 1, 33, + 28, 17, 10, 4, 65, 87, 92, 94 }, + + { + + 38, + 5, 80, 38, 5, 80, 71, 5, 22, 11, 65, 79, 15, + 21, 52, 14, 19, 67, 11, 20, 64, 73, 65, 84, + 100, 2, 5, 119, 122, 122, 28, 64, 69, 11, 20, + 64, 74, 2, 14, 0, 0, 69, 74, 67, 81, 76, 92, + 5, 70, 74, 64, 77, 75, 88, 10, 64, 66, 70, 5, + 3, 22, 0, 0, 0, 65, 94, 97, 5, 67, 68, 52, 6, + 69, 93, 76, 82, 3, 65, 73, 65, 79, 4, 73, 72, + 67, 89, 91, 90, 93, 24, 64, 9, 78, 64, 77, 70, + 67, 72, 85, 77, 87, 15, 72, 1, 81, 79, 65, 71, + 2, 8, 3, 3, 15, 12, 77, 66, 1, 66, 2, 74, 11, + 66, 8, 19, 3, 16, 17, 9, 79, 10, 64, 75, 65, + 90, 82, 70, 75, 65, 70, 68, 11, 66, 69, 2, 7, + 72, 72, 64, 95, 86, 70, 95, 67, 76, 10, 65, + 76, 20, 1, 65, 79, 21, 75, 86, 1, 96, 15, 24, + 22, 19, 24, 18, 6, 21, 22, 73, 3, 10, 3, 1, + 75, 1, 67, 69, 64, 70, 77, 65, 69, 94, 78, 75, + 85, 80, 89, 30, 36, 28, 28, 27, 20, 16, 20, + 11, 4, 8, 65, 76, 77, 99, 9, 6, 74, 5, 66, 68, + 73, 77, 81, 84, 86, 100, 76, 91, 102, 92, 101, + 65, 33, 23, 15, 5, 5, 65, 68, 72, 73, 6, 46, + 30, 23, 17, 31, 11, 5, 65, 66, 68, 38, 23, 13, + 6, 14, 3, 66, 70, 76, 75, 19, 6, 2, 66, 3, 70, + 75, 79, 1, 40, 23, 14, 7, 18, 4, 1, 64, 69, + 62, 81, 72, 0, 66, 69, 1, 7, 8, 65, 7, 13, 15, + 77, 71, 74, 64, 15, 66, 68, 4, 10, 17, 76, 68, + 11, 13, 82, 80, 82, 80, 75, 64, 74, 69, 71, + 67, 1, 74, 71, 70, 64, 78, 74, 81, 85, 67, 99, + 76, 64, 70, 10, 70, 65, 18, 68, 70, 66, 14, + 77, 74, 94, 27, 32, 33, 25, 14, 26, 22, 17, + 16, 14, 13, 9, 72, 74, 81, 65, 0, 72, 89, 73, + 73, 78, 77, 77, 73, 74, 88, 87, 85, 26, 25, + 25, 19, 7, 13, 10, 65, 4, 65, 66, 78, 79, 81, + 84, 8, 11, 74, 8, 8, 65, 71, 71, 73, 75, 75, + 80, 89, 89, 94, 97, 104, 83, 93, 100, 86, 72, + 78, 0, 1, 2, 44, 12, 11, 68, 21, 55, 41, 61, + 11, 30, 24, 65, 84, 91, 96, 122, 120, 120, 69, + 42, 27, 18, 9, 12, 2, 66, 68, 74, 78, 71, 3, + 66, 11, 10, 2, 5, 10, 13, 14, 7, 17, 15, 0, + 31, 26, 15, 8, 2, 67, 90, 94, 95 }, + + { + + 37, + 5, 80, 37, 5, 80, 69, 7, 23, 12, 65, 80, 14, + 20, 52, 14, 22, 67, 12, 21, 64, 73, 64, 85, + 101, 2, 4, 120, 123, 122, 31, 1, 69, 12, 21, + 64, 73, 4, 15, 0, 1, 68, 73, 67, 81, 75, 92, + 5, 69, 73, 64, 77, 74, 88, 11, 0, 65, 69, 6, + 3, 22, 0, 0, 0, 64, 94, 97, 6, 67, 68, 52, 6, + 69, 91, 75, 82, 4, 0, 71, 0, 77, 6, 71, 70, + 65, 87, 90, 89, 92, 25, 0, 10, 77, 0, 76, 69, + 65, 71, 84, 76, 85, 16, 71, 2, 79, 78, 64, 70, + 3, 9, 4, 4, 16, 13, 77, 66, 2, 66, 2, 73, 11, + 66, 8, 19, 3, 16, 17, 9, 80, 10, 64, 76, 66, + 90, 81, 69, 74, 64, 69, 67, 12, 65, 68, 3, 9, + 72, 72, 0, 96, 85, 69, 95, 66, 76, 11, 64, 76, + 21, 1, 65, 80, 22, 75, 87, 1, 96, 14, 24, 22, + 19, 24, 18, 6, 21, 22, 74, 3, 10, 3, 1, 75, 1, + 67, 69, 64, 70, 78, 65, 69, 95, 79, 75, 86, + 81, 89, 29, 35, 27, 27, 26, 18, 14, 18, 9, 2, + 6, 67, 78, 79, 101, 8, 5, 75, 3, 68, 70, 75, + 79, 83, 86, 88, 102, 76, 92, 103, 92, 101, 64, + 34, 24, 16, 6, 5, 65, 67, 71, 72, 7, 47, 31, + 24, 17, 32, 12, 6, 64, 65, 67, 39, 24, 14, 7, + 15, 4, 65, 69, 74, 74, 21, 7, 3, 65, 4, 69, + 74, 77, 2, 41, 24, 15, 8, 19, 5, 2, 0, 68, 62, + 80, 71, 1, 66, 68, 1, 8, 9, 64, 8, 14, 16, 76, + 71, 74, 64, 16, 66, 68, 3, 10, 17, 77, 69, 11, + 13, 82, 81, 82, 79, 74, 0, 73, 68, 70, 66, 2, + 74, 70, 69, 0, 78, 73, 80, 84, 66, 99, 76, 0, + 70, 11, 70, 65, 19, 68, 70, 65, 15, 77, 74, + 94, 27, 32, 33, 25, 13, 26, 22, 17, 16, 14, + 13, 9, 72, 75, 82, 66, 64, 73, 90, 74, 74, 78, + 77, 77, 72, 73, 87, 87, 84, 24, 23, 23, 17, 5, + 11, 8, 67, 2, 67, 68, 80, 81, 82, 84, 7, 10, + 75, 7, 6, 67, 73, 73, 75, 77, 77, 82, 91, 90, + 96, 98, 105, 84, 94, 101, 84, 70, 76, 1, 2, 3, + 46, 13, 12, 68, 22, 57, 43, 62, 12, 29, 23, + 67, 87, 93, 98, 124, 122, 121, 68, 43, 27, 18, + 9, 13, 2, 66, 68, 73, 77, 70, 4, 65, 13, 11, + 3, 6, 11, 14, 15, 8, 18, 16, 0, 30, 25, 14, 7, + 1, 68, 92, 95, 95 }, + + { + + 36, + 5, 80, 36, 5, 80, 67, 8, 23, 12, 65, 81, 13, + 19, 52, 14, 24, 67, 13, 22, 64, 74, 64, 86, + 102, 1, 2, 122, 124, 123, 34, 2, 69, 13, 22, + 64, 73, 5, 15, 64, 1, 68, 72, 67, 81, 75, 92, + 5, 69, 72, 64, 77, 74, 88, 11, 0, 65, 69, 6, + 3, 22, 0, 0, 0, 0, 94, 97, 6, 68, 68, 52, 6, + 69, 90, 74, 82, 4, 1, 69, 2, 76, 7, 69, 69, 0, + 86, 89, 88, 91, 26, 0, 11, 76, 0, 76, 68, 0, + 71, 84, 76, 84, 16, 71, 2, 78, 78, 0, 70, 3, + 9, 4, 5, 16, 13, 78, 66, 2, 66, 2, 73, 11, 66, + 7, 19, 2, 16, 17, 9, 82, 10, 64, 78, 67, 91, + 80, 68, 73, 0, 68, 66, 13, 64, 68, 4, 10, 72, + 72, 0, 97, 85, 68, 95, 66, 76, 12, 64, 77, 22, + 1, 65, 81, 23, 76, 88, 1, 97, 12, 23, 21, 18, + 23, 17, 5, 20, 22, 75, 2, 10, 3, 1, 75, 0, 68, + 70, 65, 71, 79, 66, 70, 97, 80, 76, 87, 82, + 89, 27, 34, 25, 25, 24, 16, 12, 16, 6, 0, 4, + 70, 81, 81, 104, 6, 3, 77, 1, 70, 72, 77, 81, + 85, 89, 90, 104, 77, 94, 104, 93, 101, 64, 34, + 24, 16, 6, 5, 65, 67, 71, 71, 7, 47, 31, 24, + 17, 33, 12, 7, 64, 64, 66, 40, 25, 14, 7, 16, + 4, 65, 68, 73, 73, 22, 8, 3, 65, 5, 68, 73, + 76, 2, 41, 24, 15, 8, 20, 6, 3, 1, 67, 62, 80, + 71, 1, 66, 68, 1, 8, 9, 64, 8, 14, 17, 76, 71, + 74, 65, 16, 67, 69, 2, 9, 17, 78, 70, 11, 13, + 83, 82, 83, 78, 73, 1, 73, 68, 70, 65, 3, 74, + 70, 69, 0, 79, 73, 80, 84, 65, 99, 76, 0, 70, + 12, 70, 65, 20, 68, 70, 65, 16, 78, 74, 95, + 26, 32, 33, 24, 12, 25, 21, 16, 15, 13, 12, 8, + 73, 76, 83, 68, 65, 75, 92, 75, 75, 79, 78, + 77, 72, 73, 87, 88, 83, 22, 21, 21, 15, 2, 9, + 6, 70, 0, 69, 70, 82, 83, 83, 85, 5, 8, 76, 5, + 4, 69, 75, 75, 77, 79, 79, 84, 93, 92, 98, + 100, 106, 86, 96, 103, 83, 69, 75, 2, 3, 4, + 48, 14, 13, 68, 23, 58, 44, 62, 13, 28, 21, + 70, 90, 96, 101, 126, 124, 122, 68, 43, 27, + 18, 9, 13, 2, 66, 68, 72, 76, 69, 5, 64, 14, + 12, 4, 7, 12, 15, 16, 8, 19, 16, 0, 29, 23, + 12, 5, 64, 70, 94, 97, 96 }, + + { + + 35, + 5, 80, 35, 5, 80, 65, 10, 24, 12, 66, 83, 11, + 18, 52, 14, 26, 67, 14, 23, 64, 75, 64, 87, + 103, 1, 0, 123, 125, 123, 37, 3, 69, 14, 23, + 64, 72, 6, 16, 64, 1, 67, 71, 68, 82, 75, 92, + 5, 69, 72, 64, 77, 74, 88, 12, 0, 65, 69, 7, + 3, 22, 0, 0, 0, 0, 94, 97, 7, 69, 68, 52, 6, + 69, 89, 73, 82, 5, 2, 67, 4, 74, 8, 67, 68, 2, + 85, 88, 87, 90, 27, 0, 12, 75, 0, 75, 68, 2, + 71, 83, 75, 83, 16, 71, 3, 76, 77, 1, 70, 3, + 9, 4, 6, 17, 14, 78, 66, 2, 66, 2, 73, 11, 67, + 7, 19, 1, 16, 17, 9, 84, 10, 65, 80, 68, 92, + 79, 67, 72, 1, 67, 66, 14, 64, 67, 5, 11, 72, + 72, 0, 98, 84, 67, 95, 66, 76, 13, 64, 78, 23, + 1, 65, 82, 24, 76, 89, 1, 98, 11, 22, 21, 17, + 22, 16, 5, 19, 21, 76, 1, 10, 3, 1, 76, 64, + 69, 71, 66, 72, 80, 67, 70, 99, 81, 76, 88, + 83, 89, 26, 32, 24, 23, 22, 14, 10, 14, 4, 66, + 2, 72, 83, 84, 106, 4, 1, 78, 64, 72, 75, 80, + 83, 88, 91, 92, 107, 78, 96, 105, 94, 101, 64, + 35, 24, 16, 6, 5, 65, 67, 71, 71, 7, 47, 31, + 24, 17, 34, 13, 7, 0, 64, 65, 41, 25, 15, 7, + 17, 5, 64, 68, 72, 72, 23, 9, 4, 65, 6, 67, + 72, 75, 3, 41, 24, 15, 8, 21, 6, 4, 2, 66, 62, + 79, 70, 2, 66, 68, 1, 8, 9, 64, 8, 14, 17, 76, + 71, 74, 65, 17, 68, 70, 1, 8, 17, 79, 71, 11, + 13, 83, 83, 83, 77, 72, 2, 72, 67, 69, 65, 4, + 74, 70, 69, 1, 79, 73, 80, 83, 64, 99, 76, 0, + 70, 13, 70, 65, 21, 68, 70, 65, 17, 79, 74, + 96, 25, 31, 32, 23, 11, 24, 20, 15, 14, 12, + 11, 7, 74, 77, 84, 69, 66, 76, 94, 77, 76, 80, + 78, 78, 72, 73, 87, 88, 82, 20, 19, 19, 13, + 64, 7, 4, 72, 65, 71, 72, 85, 85, 85, 86, 3, + 7, 78, 3, 2, 71, 77, 77, 79, 81, 81, 86, 96, + 94, 100, 102, 107, 88, 98, 104, 82, 68, 74, 3, + 4, 5, 49, 15, 14, 68, 24, 60, 46, 62, 14, 26, + 20, 72, 93, 99, 104, 126, 126, 123, 68, 43, + 27, 18, 9, 13, 2, 66, 68, 72, 75, 68, 6, 0, + 15, 12, 4, 8, 12, 15, 16, 8, 19, 16, 0, 28, + 22, 10, 3, 66, 72, 96, 99, 97 }, + + { + + 33, + 5, 80, 33, 5, 80, 64, 11, 24, 12, 66, 84, 10, + 16, 52, 14, 29, 68, 15, 23, 64, 76, 64, 88, + 104, 0, 65, 125, 126, 124, 40, 4, 69, 15, 23, + 64, 72, 7, 16, 65, 1, 67, 71, 68, 82, 74, 92, + 5, 69, 71, 65, 78, 74, 88, 12, 1, 65, 68, 7, + 3, 22, 0, 0, 0, 1, 95, 97, 7, 70, 68, 52, 6, + 69, 88, 72, 82, 5, 4, 66, 6, 73, 9, 66, 66, 4, + 84, 88, 86, 90, 27, 0, 13, 74, 0, 75, 67, 4, + 71, 83, 75, 82, 16, 70, 3, 75, 77, 1, 70, 4, + 9, 4, 6, 17, 14, 79, 67, 2, 66, 2, 73, 11, 67, + 6, 19, 1, 16, 17, 8, 86, 10, 65, 82, 69, 93, + 78, 66, 71, 2, 67, 65, 15, 0, 67, 6, 12, 72, + 72, 1, 99, 84, 66, 95, 66, 76, 13, 64, 79, 24, + 1, 65, 84, 25, 77, 90, 1, 99, 9, 21, 20, 16, + 21, 15, 4, 18, 21, 78, 0, 9, 3, 1, 76, 65, 69, + 72, 66, 73, 81, 68, 71, 101, 82, 77, 89, 84, + 89, 24, 31, 22, 21, 20, 12, 7, 12, 1, 68, 0, + 75, 86, 86, 109, 2, 0, 80, 67, 74, 77, 82, 86, + 90, 94, 94, 109, 79, 97, 107, 95, 101, 64, 35, + 25, 16, 6, 5, 65, 67, 71, 70, 8, 48, 31, 24, + 17, 35, 13, 8, 0, 0, 65, 42, 26, 15, 7, 17, 5, + 64, 67, 71, 72, 24, 9, 4, 65, 7, 66, 72, 74, + 3, 42, 24, 15, 8, 22, 7, 4, 3, 65, 62, 79, 70, + 2, 66, 68, 1, 8, 9, 0, 8, 14, 18, 76, 71, 74, + 66, 17, 69, 71, 0, 7, 17, 81, 72, 10, 13, 84, + 84, 84, 77, 72, 3, 72, 67, 69, 64, 5, 75, 70, + 69, 1, 80, 73, 80, 83, 0, 99, 76, 1, 70, 13, + 70, 65, 22, 68, 71, 65, 18, 79, 75, 97, 24, + 31, 32, 22, 10, 23, 19, 14, 13, 11, 10, 6, 75, + 78, 85, 71, 68, 78, 96, 78, 77, 81, 79, 78, + 72, 73, 87, 89, 82, 17, 16, 17, 10, 67, 5, 2, + 75, 67, 73, 74, 87, 87, 86, 87, 1, 5, 79, 1, + 0, 73, 79, 80, 81, 83, 83, 88, 98, 96, 102, + 104, 108, 90, 100, 106, 81, 67, 73, 3, 5, 6, + 51, 16, 15, 68, 25, 61, 47, 62, 14, 25, 18, + 75, 96, 102, 107, 126, 126, 124, 68, 43, 27, + 18, 9, 13, 2, 66, 68, 71, 74, 67, 7, 0, 16, + 13, 5, 9, 13, 16, 17, 9, 20, 17, 0, 26, 20, 8, + 1, 68, 74, 98, 101, 98 }, + + { + + 32, + 5, 80, 32, 5, 80, 1, 13, 24, 12, 67, 86, 8, + 15, 52, 14, 31, 68, 16, 24, 64, 76, 0, 89, + 105, 0, 67, 126, 126, 124, 43, 6, 69, 16, 24, + 64, 72, 8, 16, 65, 1, 67, 70, 68, 83, 74, 92, + 5, 69, 70, 65, 78, 74, 88, 13, 1, 64, 68, 8, + 3, 22, 0, 0, 0, 1, 95, 97, 8, 71, 68, 52, 6, + 69, 87, 71, 82, 6, 5, 64, 8, 71, 10, 64, 65, + 6, 83, 87, 85, 89, 28, 0, 14, 73, 0, 74, 66, + 6, 71, 83, 74, 80, 17, 70, 4, 74, 76, 2, 70, + 4, 10, 5, 7, 18, 15, 79, 67, 2, 66, 2, 73, 11, + 68, 5, 19, 0, 16, 17, 8, 88, 10, 66, 83, 70, + 94, 77, 65, 70, 3, 66, 64, 16, 1, 66, 7, 13, + 72, 72, 1, 100, 83, 65, 95, 65, 76, 14, 64, + 80, 25, 1, 65, 85, 26, 77, 91, 1, 99, 8, 20, + 20, 15, 20, 14, 3, 18, 21, 79, 64, 9, 3, 1, + 77, 65, 70, 73, 67, 74, 82, 68, 72, 103, 83, + 78, 90, 85, 89, 22, 30, 20, 19, 19, 10, 5, 10, + 64, 70, 65, 77, 88, 88, 111, 0, 65, 82, 69, + 76, 79, 84, 88, 92, 97, 96, 112, 80, 99, 108, + 95, 101, 64, 35, 25, 16, 6, 5, 65, 67, 71, 70, + 8, 48, 31, 24, 17, 36, 14, 9, 0, 1, 64, 43, + 27, 15, 8, 18, 6, 0, 67, 70, 71, 26, 10, 4, + 64, 8, 65, 71, 73, 4, 42, 25, 15, 8, 23, 8, 5, + 4, 64, 62, 79, 70, 3, 66, 68, 1, 8, 9, 0, 8, + 15, 18, 76, 71, 74, 67, 18, 70, 72, 64, 7, 17, + 82, 73, 10, 13, 85, 85, 84, 76, 71, 4, 71, 66, + 68, 64, 6, 75, 70, 69, 1, 81, 73, 80, 82, 1, + 99, 76, 1, 70, 14, 70, 65, 23, 68, 71, 65, 19, + 80, 75, 97, 23, 31, 31, 22, 9, 22, 18, 13, 13, + 10, 9, 5, 76, 79, 86, 72, 69, 79, 98, 79, 78, + 82, 80, 79, 72, 72, 87, 89, 81, 15, 14, 15, 8, + 69, 3, 0, 77, 69, 75, 76, 89, 89, 88, 88, 64, + 3, 80, 64, 65, 75, 81, 82, 83, 85, 85, 90, + 101, 98, 104, 106, 109, 92, 102, 107, 80, 65, + 72, 4, 6, 7, 53, 17, 16, 68, 26, 62, 49, 62, + 15, 23, 16, 77, 99, 104, 110, 126, 126, 125, + 68, 43, 27, 18, 9, 13, 2, 66, 68, 71, 73, 66, + 8, 1, 17, 14, 5, 10, 14, 17, 18, 9, 20, 17, 0, + 25, 19, 7, 0, 70, 76, 100, 103, 99 }, + + { + + 31, + 5, 81, 31, 5, 81, 3, 14, 25, 12, 67, 87, 7, + 14, 52, 14, 33, 68, 16, 25, 64, 77, 0, 90, + 106, 64, 68, 126, 126, 125, 46, 7, 69, 16, 25, + 64, 71, 9, 17, 66, 2, 66, 69, 69, 83, 74, 92, + 5, 68, 70, 65, 78, 73, 88, 13, 1, 64, 68, 8, + 3, 22, 0, 0, 0, 2, 95, 97, 8, 71, 69, 52, 6, + 69, 86, 71, 82, 6, 6, 1, 10, 70, 11, 1, 64, 8, + 82, 86, 84, 88, 29, 0, 15, 73, 1, 74, 66, 8, + 71, 82, 74, 79, 17, 70, 4, 72, 76, 3, 69, 4, + 10, 5, 8, 18, 15, 80, 67, 2, 66, 2, 73, 11, + 68, 5, 19, 64, 16, 17, 8, 90, 10, 66, 85, 71, + 95, 77, 64, 70, 3, 65, 64, 17, 1, 66, 7, 15, + 72, 72, 1, 101, 83, 64, 95, 65, 76, 15, 0, 81, + 25, 1, 65, 86, 27, 78, 92, 1, 100, 6, 19, 19, + 15, 19, 14, 3, 17, 20, 80, 65, 9, 3, 1, 77, + 66, 71, 74, 68, 74, 83, 69, 72, 105, 84, 78, + 91, 86, 89, 21, 28, 19, 17, 17, 8, 3, 8, 67, + 73, 67, 80, 91, 91, 114, 65, 67, 83, 71, 79, + 82, 87, 90, 95, 99, 99, 114, 81, 101, 109, 96, + 101, 0, 36, 25, 17, 6, 5, 65, 67, 71, 69, 8, + 48, 32, 24, 17, 37, 14, 9, 1, 1, 0, 44, 27, + 16, 8, 19, 6, 0, 66, 69, 70, 27, 11, 5, 64, 8, + 65, 70, 71, 4, 42, 25, 15, 9, 23, 8, 6, 4, 0, + 62, 78, 69, 3, 66, 68, 1, 8, 9, 0, 9, 15, 19, + 76, 71, 75, 67, 18, 71, 73, 65, 6, 17, 83, 74, + 10, 13, 85, 86, 85, 75, 70, 5, 71, 66, 68, 0, + 6, 75, 69, 68, 2, 81, 73, 80, 82, 2, 99, 76, + 1, 70, 15, 70, 65, 24, 69, 71, 65, 19, 81, 75, + 98, 22, 30, 31, 21, 8, 22, 18, 12, 12, 10, 8, + 4, 77, 80, 87, 74, 70, 81, 100, 81, 80, 83, + 80, 79, 72, 72, 86, 90, 80, 13, 12, 13, 6, 72, + 1, 65, 80, 71, 78, 78, 92, 91, 89, 88, 65, 2, + 82, 66, 68, 77, 83, 84, 85, 87, 88, 92, 103, + 100, 106, 107, 111, 94, 104, 109, 79, 64, 71, + 5, 7, 8, 54, 18, 17, 68, 27, 62, 50, 62, 16, + 22, 15, 80, 102, 107, 112, 126, 126, 126, 67, + 43, 27, 18, 9, 13, 2, 66, 68, 70, 72, 66, 9, + 2, 18, 14, 6, 11, 14, 17, 18, 9, 21, 17, 0, + 24, 17, 5, 65, 71, 77, 103, 104, 100 }, + + { + + 30, + 5, 81, 30, 5, 81, 5, 16, 25, 12, 68, 89, 5, + 12, 52, 14, 36, 69, 17, 25, 64, 78, 0, 91, + 107, 64, 70, 126, 126, 125, 49, 8, 69, 17, 25, + 64, 71, 10, 17, 66, 2, 66, 69, 69, 84, 73, 92, + 5, 68, 69, 66, 78, 73, 88, 14, 2, 64, 67, 9, + 3, 22, 0, 0, 0, 2, 95, 97, 9, 72, 69, 52, 6, + 69, 85, 70, 82, 7, 8, 2, 12, 68, 12, 2, 1, 10, + 81, 85, 83, 88, 30, 0, 16, 72, 1, 73, 65, 10, + 71, 82, 73, 78, 17, 69, 5, 71, 75, 3, 69, 5, + 10, 5, 9, 19, 16, 80, 68, 2, 66, 2, 73, 11, + 69, 4, 19, 64, 16, 17, 7, 92, 10, 67, 87, 72, + 96, 76, 0, 69, 4, 64, 0, 18, 2, 65, 8, 16, 72, + 72, 2, 102, 82, 0, 95, 65, 76, 15, 0, 82, 26, + 1, 65, 88, 28, 78, 93, 1, 101, 5, 18, 19, 14, + 18, 13, 2, 16, 20, 81, 66, 9, 3, 1, 78, 67, + 71, 75, 68, 75, 84, 70, 73, 107, 85, 79, 92, + 87, 89, 19, 27, 17, 15, 15, 6, 0, 6, 69, 75, + 69, 82, 93, 93, 116, 67, 68, 85, 74, 81, 84, + 89, 93, 97, 102, 101, 117, 82, 102, 111, 97, + 101, 0, 36, 26, 17, 6, 5, 65, 67, 71, 69, 9, + 49, 32, 24, 17, 38, 15, 10, 1, 2, 1, 45, 28, + 16, 8, 20, 7, 1, 66, 68, 70, 28, 11, 5, 64, 9, + 64, 70, 70, 5, 43, 25, 15, 9, 24, 9, 7, 5, 1, + 62, 78, 69, 4, 66, 68, 1, 8, 9, 1, 9, 15, 19, + 76, 71, 75, 68, 19, 72, 74, 66, 5, 17, 84, 75, + 9, 13, 86, 87, 85, 74, 70, 6, 70, 65, 67, 0, + 7, 75, 69, 68, 2, 82, 73, 80, 81, 3, 99, 76, + 2, 70, 15, 70, 65, 25, 69, 71, 65, 20, 81, 76, + 99, 21, 30, 30, 20, 7, 21, 17, 11, 11, 9, 7, + 3, 78, 81, 88, 75, 72, 82, 102, 82, 81, 84, + 81, 80, 72, 72, 86, 90, 79, 11, 10, 11, 3, 75, + 64, 67, 82, 73, 80, 80, 94, 93, 91, 89, 67, 0, + 83, 68, 70, 79, 85, 86, 87, 89, 90, 94, 106, + 102, 108, 109, 112, 96, 106, 110, 78, 0, 70, + 5, 8, 9, 56, 19, 18, 68, 28, 62, 52, 62, 16, + 20, 13, 82, 105, 110, 115, 126, 126, 126, 67, + 43, 27, 18, 9, 13, 2, 66, 68, 70, 71, 65, 10, + 3, 19, 15, 6, 12, 15, 18, 19, 10, 21, 18, 0, + 22, 16, 3, 67, 73, 79, 105, 106, 101 }, + + { + + 28, + 4, 81, 28, 4, 81, 6, 17, 25, 12, 68, 90, 4, + 11, 52, 14, 38, 69, 18, 26, 64, 79, 0, 92, + 109, 65, 72, 126, 126, 126, 51, 9, 69, 18, 26, + 64, 71, 11, 17, 67, 2, 66, 68, 70, 84, 73, 93, + 5, 68, 69, 66, 79, 73, 88, 14, 2, 64, 67, 9, + 3, 22, 0, 0, 0, 3, 96, 97, 9, 73, 69, 52, 6, + 69, 84, 69, 82, 7, 9, 4, 14, 67, 13, 4, 2, 11, + 80, 85, 82, 87, 30, 0, 17, 71, 1, 73, 65, 11, + 71, 82, 73, 77, 17, 69, 5, 70, 75, 4, 69, 5, + 10, 5, 9, 19, 16, 81, 68, 2, 66, 2, 73, 11, + 69, 3, 19, 65, 16, 17, 7, 94, 10, 67, 89, 73, + 97, 75, 1, 68, 5, 64, 0, 19, 2, 65, 9, 17, 72, + 72, 2, 104, 82, 1, 95, 65, 77, 16, 0, 83, 27, + 1, 65, 89, 29, 79, 94, 1, 102, 3, 17, 18, 13, + 17, 12, 1, 15, 19, 83, 67, 8, 3, 1, 78, 68, + 72, 76, 69, 76, 85, 71, 74, 109, 87, 80, 93, + 88, 89, 17, 25, 15, 13, 13, 4, 65, 4, 72, 78, + 71, 85, 96, 96, 119, 69, 70, 87, 76, 83, 87, + 92, 95, 100, 105, 103, 119, 83, 104, 112, 98, + 102, 0, 36, 26, 17, 6, 5, 65, 67, 71, 68, 9, + 49, 32, 24, 17, 39, 15, 10, 1, 2, 1, 46, 28, + 16, 8, 20, 7, 1, 65, 67, 69, 29, 12, 5, 64, + 10, 0, 69, 69, 5, 43, 25, 15, 9, 25, 9, 7, 6, + 1, 62, 78, 69, 4, 66, 68, 1, 8, 9, 1, 9, 15, + 20, 76, 71, 75, 69, 19, 73, 75, 67, 4, 17, 86, + 77, 9, 13, 87, 88, 86, 74, 69, 7, 70, 65, 67, + 1, 8, 76, 69, 68, 2, 83, 73, 80, 81, 3, 100, + 76, 2, 70, 16, 70, 65, 25, 69, 72, 65, 21, 82, + 76, 100, 20, 29, 30, 19, 5, 20, 16, 10, 10, 8, + 6, 2, 79, 82, 89, 77, 73, 84, 104, 84, 82, 85, + 82, 80, 72, 72, 86, 91, 79, 8, 7, 9, 1, 78, + 67, 70, 85, 75, 82, 82, 97, 95, 92, 90, 69, + 65, 85, 70, 72, 82, 88, 89, 90, 91, 92, 97, + 108, 104, 111, 111, 113, 98, 108, 112, 77, 1, + 69, 6, 9, 9, 57, 20, 18, 68, 28, 62, 53, 62, + 17, 19, 11, 85, 108, 113, 118, 126, 126, 126, + 67, 43, 27, 18, 9, 13, 2, 66, 68, 69, 71, 64, + 11, 3, 20, 15, 7, 12, 15, 18, 19, 10, 22, 18, + 64, 21, 14, 1, 69, 75, 81, 107, 108, 102 }, + + { + + 27, + 4, 81, 27, 4, 81, 8, 18, 26, 12, 68, 91, 3, + 10, 52, 14, 40, 69, 19, 27, 64, 79, 1, 93, + 110, 66, 74, 126, 126, 126, 54, 11, 69, 19, + 27, 64, 70, 12, 18, 68, 2, 65, 67, 70, 84, 73, + 93, 5, 68, 68, 66, 79, 73, 88, 14, 2, 0, 67, + 9, 3, 22, 0, 0, 0, 4, 96, 97, 9, 74, 69, 52, + 6, 69, 83, 68, 82, 7, 10, 6, 16, 65, 15, 6, 3, + 13, 79, 84, 81, 86, 31, 1, 18, 70, 1, 72, 64, + 13, 71, 81, 73, 75, 18, 69, 5, 68, 75, 5, 69, + 5, 11, 6, 10, 19, 16, 81, 68, 3, 66, 2, 72, + 11, 69, 3, 19, 66, 16, 17, 7, 96, 10, 67, 90, + 74, 97, 74, 2, 67, 6, 0, 1, 20, 3, 65, 10, 18, + 72, 72, 2, 105, 81, 2, 95, 64, 77, 17, 0, 84, + 28, 1, 65, 90, 30, 80, 95, 1, 102, 2, 16, 18, + 12, 16, 11, 1, 15, 19, 84, 68, 8, 3, 1, 78, + 68, 73, 77, 70, 77, 86, 71, 74, 110, 88, 80, + 94, 89, 89, 16, 24, 14, 12, 12, 2, 67, 2, 74, + 80, 73, 87, 99, 98, 122, 70, 72, 88, 78, 85, + 89, 94, 97, 102, 107, 105, 121, 83, 106, 113, + 98, 102, 0, 37, 26, 17, 7, 5, 65, 66, 71, 67, + 9, 49, 32, 25, 17, 40, 16, 11, 2, 3, 2, 47, + 29, 17, 9, 21, 8, 1, 64, 66, 68, 31, 13, 6, 0, + 11, 1, 68, 68, 6, 43, 26, 16, 9, 26, 10, 8, 7, + 2, 62, 77, 68, 5, 66, 68, 1, 9, 10, 1, 9, 16, + 21, 76, 71, 75, 69, 19, 74, 75, 68, 4, 17, 87, + 78, 9, 13, 87, 89, 87, 73, 68, 8, 70, 64, 67, + 2, 9, 76, 69, 68, 3, 83, 73, 80, 81, 4, 100, + 76, 2, 70, 17, 70, 65, 26, 69, 72, 65, 22, 83, + 76, 100, 19, 29, 30, 19, 4, 19, 15, 9, 10, 7, + 5, 2, 79, 83, 90, 78, 74, 86, 106, 85, 83, 85, + 82, 80, 71, 71, 86, 92, 78, 6, 5, 7, 64, 80, + 69, 72, 87, 77, 84, 84, 99, 97, 93, 91, 71, + 66, 86, 72, 74, 84, 90, 91, 92, 93, 94, 99, + 110, 105, 113, 113, 114, 100, 110, 114, 76, 3, + 67, 7, 10, 10, 59, 21, 19, 68, 29, 62, 54, 62, + 18, 18, 10, 87, 111, 115, 121, 126, 126, 126, + 67, 43, 27, 18, 9, 14, 2, 66, 68, 68, 70, 0, + 12, 4, 22, 16, 8, 13, 16, 19, 20, 10, 23, 18, + 64, 20, 13, 0, 70, 77, 83, 109, 110, 102 }, + + { + + 26, + 4, 81, 26, 4, 81, 10, 20, 26, 12, 69, 93, 1, + 8, 52, 14, 43, 70, 20, 27, 64, 80, 1, 94, 111, + 66, 76, 126, 126, 126, 57, 12, 69, 20, 27, 64, + 70, 13, 18, 68, 2, 65, 67, 70, 85, 72, 93, 5, + 68, 67, 67, 79, 73, 88, 15, 3, 0, 66, 10, 3, + 22, 0, 0, 0, 4, 96, 97, 10, 75, 69, 52, 6, 69, + 82, 67, 82, 8, 12, 7, 18, 64, 16, 7, 5, 15, + 78, 83, 80, 86, 32, 1, 19, 69, 1, 72, 0, 15, + 71, 81, 72, 74, 18, 68, 6, 67, 74, 5, 69, 6, + 11, 6, 11, 20, 17, 82, 69, 3, 66, 2, 72, 11, + 70, 2, 19, 66, 16, 17, 6, 98, 10, 68, 92, 75, + 98, 73, 3, 66, 7, 1, 2, 21, 4, 64, 11, 19, 72, + 72, 3, 106, 81, 3, 95, 64, 77, 17, 0, 85, 29, + 1, 65, 92, 31, 80, 96, 1, 103, 0, 15, 17, 11, + 15, 10, 0, 14, 19, 85, 69, 8, 3, 1, 79, 69, + 73, 78, 70, 78, 87, 72, 75, 112, 89, 81, 95, + 90, 89, 14, 23, 12, 10, 10, 0, 70, 0, 77, 82, + 75, 90, 101, 100, 124, 72, 73, 90, 81, 87, 91, + 96, 100, 104, 110, 107, 124, 84, 107, 115, 99, + 102, 0, 37, 27, 17, 7, 5, 65, 66, 71, 67, 10, + 50, 32, 25, 17, 41, 16, 12, 2, 4, 3, 48, 30, + 17, 9, 22, 8, 2, 64, 65, 68, 32, 13, 6, 0, 12, + 2, 68, 67, 6, 44, 26, 16, 9, 27, 11, 9, 8, 3, + 62, 77, 68, 5, 66, 68, 1, 9, 10, 2, 9, 16, 21, + 76, 71, 75, 70, 20, 75, 76, 69, 3, 17, 88, 79, + 8, 13, 88, 90, 87, 72, 68, 9, 69, 64, 66, 2, + 10, 76, 69, 68, 3, 84, 73, 80, 80, 5, 100, 76, + 3, 70, 17, 70, 65, 27, 69, 72, 65, 23, 83, 77, + 101, 18, 29, 29, 18, 3, 18, 14, 8, 9, 6, 4, 1, + 80, 84, 91, 80, 76, 87, 108, 86, 84, 86, 83, + 81, 71, 71, 86, 92, 77, 4, 3, 5, 67, 83, 71, + 74, 90, 79, 86, 86, 101, 99, 95, 92, 73, 68, + 87, 74, 76, 86, 92, 93, 94, 95, 96, 101, 113, + 107, 115, 115, 115, 102, 112, 115, 75, 4, 66, + 7, 11, 11, 61, 22, 20, 68, 30, 62, 56, 62, 18, + 16, 8, 90, 114, 118, 124, 126, 126, 126, 67, + 43, 27, 18, 9, 14, 2, 66, 68, 68, 69, 1, 13, + 5, 23, 17, 8, 14, 17, 20, 21, 11, 23, 19, 64, + 18, 11, 65, 72, 79, 85, 111, 112, 103 }, + + { + + 25, + 4, 82, 25, 4, 82, 12, 21, 27, 12, 69, 94, 0, + 7, 52, 14, 45, 70, 20, 28, 64, 81, 1, 95, 112, + 67, 77, 126, 126, 126, 60, 13, 69, 20, 28, 64, + 69, 14, 19, 69, 3, 64, 66, 71, 85, 72, 93, 5, + 67, 67, 67, 79, 72, 88, 15, 3, 0, 66, 10, 3, + 22, 0, 0, 0, 5, 96, 97, 10, 75, 70, 52, 6, 69, + 81, 67, 82, 8, 13, 9, 20, 1, 17, 9, 6, 17, 77, + 82, 79, 85, 33, 1, 20, 69, 2, 71, 0, 17, 71, + 80, 72, 73, 18, 68, 6, 65, 74, 6, 68, 6, 11, + 6, 12, 20, 17, 82, 69, 3, 66, 2, 72, 11, 70, + 2, 19, 67, 16, 17, 6, 100, 10, 68, 94, 76, 99, + 73, 4, 66, 7, 2, 2, 22, 4, 64, 11, 21, 72, 72, + 3, 107, 80, 4, 95, 64, 77, 18, 1, 86, 29, 1, + 65, 93, 32, 81, 97, 1, 104, 64, 14, 17, 11, + 14, 10, 0, 13, 18, 86, 70, 8, 3, 1, 79, 70, + 74, 79, 71, 78, 88, 73, 75, 114, 90, 81, 96, + 91, 89, 13, 21, 11, 8, 8, 65, 72, 65, 79, 85, + 77, 92, 104, 103, 126, 74, 75, 91, 83, 90, 94, + 99, 102, 107, 112, 110, 126, 85, 109, 116, + 100, 102, 1, 38, 27, 18, 7, 5, 65, 66, 71, 66, + 10, 50, 33, 25, 17, 42, 17, 12, 3, 4, 4, 49, + 30, 18, 9, 23, 9, 2, 0, 64, 67, 33, 14, 7, 0, + 12, 2, 67, 65, 7, 44, 26, 16, 10, 27, 11, 10, + 8, 4, 62, 76, 67, 6, 66, 68, 1, 9, 10, 2, 10, + 16, 22, 76, 71, 76, 70, 20, 76, 77, 70, 2, 17, + 89, 80, 8, 13, 88, 91, 88, 71, 67, 10, 69, 0, + 66, 3, 10, 76, 68, 67, 4, 84, 73, 80, 80, 6, + 100, 76, 3, 70, 18, 70, 65, 28, 70, 72, 65, + 23, 84, 77, 102, 17, 28, 29, 17, 2, 18, 14, 7, + 8, 6, 3, 0, 81, 85, 92, 81, 77, 89, 110, 88, + 86, 87, 83, 81, 71, 71, 85, 93, 76, 2, 1, 3, + 69, 86, 73, 76, 92, 81, 89, 88, 104, 101, 96, + 92, 74, 69, 89, 76, 79, 88, 94, 95, 96, 97, + 99, 103, 115, 109, 117, 116, 117, 104, 114, + 117, 74, 5, 65, 8, 12, 12, 62, 23, 21, 68, 31, + 62, 57, 62, 19, 15, 7, 92, 117, 121, 126, 126, + 126, 126, 66, 43, 27, 18, 9, 14, 2, 66, 68, + 67, 68, 1, 14, 6, 24, 17, 9, 15, 17, 20, 21, + 11, 24, 19, 64, 17, 10, 67, 74, 80, 86, 114, + 113, 104 }, + + { + + 23, + 4, 82, 23, 4, 82, 13, 23, 27, 12, 70, 96, 65, + 6, 52, 14, 47, 70, 21, 29, 64, 82, 1, 96, 113, + 67, 79, 126, 126, 126, 62, 14, 69, 21, 29, 64, + 69, 15, 19, 69, 3, 64, 65, 71, 86, 72, 93, 5, + 67, 66, 67, 80, 72, 88, 16, 3, 0, 66, 11, 3, + 22, 0, 0, 0, 5, 97, 97, 11, 76, 70, 52, 6, 69, + 80, 66, 82, 9, 14, 11, 22, 2, 18, 11, 7, 19, + 76, 82, 78, 84, 33, 1, 21, 68, 2, 71, 1, 19, + 71, 80, 71, 72, 18, 68, 7, 64, 73, 7, 68, 6, + 11, 6, 12, 21, 18, 83, 69, 3, 66, 2, 72, 11, + 71, 1, 19, 68, 16, 17, 6, 102, 10, 69, 96, 77, + 100, 72, 5, 65, 8, 2, 3, 23, 5, 0, 12, 22, 72, + 72, 3, 108, 80, 5, 95, 64, 77, 19, 1, 87, 30, + 1, 65, 94, 33, 81, 98, 1, 105, 66, 13, 16, 10, + 13, 9, 64, 12, 18, 88, 71, 7, 3, 1, 80, 71, + 75, 80, 72, 79, 89, 74, 76, 116, 91, 82, 97, + 92, 89, 11, 20, 9, 6, 6, 67, 74, 67, 82, 87, + 79, 95, 106, 105, 126, 76, 77, 93, 85, 92, 96, + 101, 104, 109, 115, 112, 126, 86, 111, 117, + 101, 102, 1, 38, 27, 18, 7, 5, 65, 66, 71, 66, + 10, 50, 33, 25, 17, 43, 17, 13, 3, 5, 4, 50, + 31, 18, 9, 23, 9, 3, 0, 0, 66, 34, 15, 7, 0, + 13, 3, 66, 64, 7, 44, 26, 16, 10, 28, 12, 10, + 9, 5, 62, 76, 67, 6, 66, 68, 1, 9, 10, 2, 10, + 16, 22, 76, 71, 76, 71, 21, 77, 78, 71, 1, 17, + 91, 81, 8, 13, 89, 92, 88, 71, 66, 11, 68, 0, + 65, 3, 11, 77, 68, 67, 4, 85, 73, 80, 79, 7, + 100, 76, 3, 70, 19, 70, 65, 29, 70, 73, 65, + 24, 85, 77, 103, 16, 28, 28, 16, 1, 17, 13, 6, + 7, 5, 2, 64, 82, 86, 93, 83, 78, 90, 112, 89, + 87, 88, 84, 82, 71, 71, 85, 93, 76, 64, 65, 1, + 71, 89, 75, 78, 95, 83, 91, 90, 106, 103, 98, + 93, 76, 71, 90, 78, 81, 90, 96, 98, 98, 99, + 101, 105, 118, 111, 119, 118, 118, 106, 116, + 118, 73, 6, 64, 9, 13, 13, 62, 24, 22, 68, 32, + 62, 59, 62, 20, 13, 5, 95, 120, 124, 126, 126, + 126, 126, 66, 43, 27, 18, 9, 14, 2, 66, 68, + 67, 67, 2, 15, 6, 25, 18, 9, 16, 18, 21, 22, + 11, 24, 19, 64, 16, 8, 69, 76, 82, 88, 116, + 115, 105 }, + + { + + 22, + 4, 82, 22, 4, 82, 15, 24, 27, 12, 70, 97, 66, + 4, 52, 14, 50, 71, 22, 29, 64, 82, 2, 97, 114, + 68, 81, 126, 126, 126, 62, 16, 69, 22, 29, 64, + 69, 16, 19, 70, 3, 64, 65, 71, 86, 71, 93, 5, + 67, 65, 68, 80, 72, 88, 16, 4, 1, 65, 11, 3, + 22, 0, 0, 0, 6, 97, 97, 11, 77, 70, 52, 6, 69, + 79, 65, 82, 9, 16, 12, 24, 4, 19, 12, 9, 21, + 75, 81, 77, 84, 34, 1, 22, 67, 2, 70, 2, 21, + 71, 80, 71, 70, 19, 67, 7, 0, 73, 7, 68, 7, + 12, 7, 13, 21, 18, 83, 70, 3, 66, 2, 72, 11, + 71, 0, 19, 68, 16, 17, 5, 104, 10, 69, 97, 78, + 101, 71, 6, 64, 9, 3, 4, 24, 6, 0, 13, 23, 72, + 72, 4, 109, 79, 6, 95, 0, 77, 19, 1, 88, 31, + 1, 65, 96, 34, 82, 99, 1, 105, 67, 12, 16, 9, + 12, 8, 65, 12, 18, 89, 72, 7, 3, 1, 80, 71, + 75, 81, 72, 80, 90, 74, 77, 118, 92, 83, 98, + 93, 89, 9, 19, 7, 4, 5, 69, 77, 69, 84, 89, + 81, 97, 109, 107, 126, 78, 78, 95, 88, 94, 98, + 103, 107, 111, 118, 114, 126, 87, 112, 119, + 101, 102, 1, 38, 28, 18, 7, 5, 65, 66, 71, 65, + 11, 51, 33, 25, 17, 44, 18, 14, 3, 6, 5, 51, + 32, 18, 10, 24, 10, 3, 1, 1, 66, 36, 15, 7, 1, + 14, 4, 66, 0, 8, 45, 27, 16, 10, 29, 13, 11, + 10, 6, 62, 76, 67, 7, 66, 68, 1, 9, 10, 3, 10, + 17, 23, 76, 71, 76, 72, 21, 78, 79, 72, 1, 17, + 92, 82, 7, 13, 90, 93, 89, 70, 66, 12, 68, 1, + 65, 4, 12, 77, 68, 67, 4, 86, 73, 80, 79, 8, + 100, 76, 4, 70, 19, 70, 65, 30, 70, 73, 65, + 25, 85, 78, 103, 15, 28, 28, 16, 0, 16, 12, 5, + 7, 4, 1, 65, 83, 87, 94, 84, 80, 92, 114, 90, + 88, 89, 85, 82, 71, 70, 85, 94, 75, 66, 67, + 64, 74, 91, 77, 80, 97, 85, 93, 92, 108, 105, + 99, 94, 78, 73, 91, 80, 83, 92, 98, 100, 100, + 101, 103, 107, 120, 113, 121, 120, 119, 108, + 118, 120, 72, 8, 0, 9, 14, 14, 62, 25, 23, 68, + 33, 62, 60, 62, 20, 12, 3, 97, 123, 126, 126, + 126, 126, 126, 66, 43, 27, 18, 9, 14, 2, 66, + 68, 66, 66, 3, 16, 7, 26, 19, 10, 17, 19, 22, + 23, 12, 25, 20, 64, 14, 7, 70, 77, 84, 90, + 118, 117, 106 }, + + { + + 21, + 4, 82, 21, 4, 82, 17, 26, 28, 12, 71, 99, 68, + 3, 52, 14, 52, 71, 23, 30, 64, 83, 2, 98, 115, + 68, 83, 126, 126, 126, 62, 17, 69, 23, 30, 64, + 68, 17, 20, 70, 3, 0, 64, 72, 87, 71, 93, 5, + 67, 65, 68, 80, 72, 88, 17, 4, 1, 65, 12, 3, + 22, 0, 0, 0, 6, 97, 97, 12, 78, 70, 52, 6, 69, + 78, 64, 82, 10, 17, 14, 26, 5, 20, 14, 10, 23, + 74, 80, 76, 83, 35, 1, 23, 66, 2, 70, 2, 23, + 71, 79, 70, 69, 19, 67, 8, 2, 72, 8, 68, 7, + 12, 7, 14, 22, 19, 84, 70, 3, 66, 2, 72, 11, + 72, 0, 19, 69, 16, 17, 5, 106, 10, 70, 99, 79, + 102, 70, 7, 0, 10, 4, 4, 25, 6, 1, 14, 24, 72, + 72, 4, 110, 79, 7, 95, 0, 77, 20, 1, 89, 32, + 1, 65, 97, 35, 82, 100, 1, 106, 69, 11, 15, 8, + 11, 7, 65, 11, 17, 90, 73, 7, 3, 1, 81, 72, + 76, 82, 73, 81, 91, 75, 77, 120, 93, 83, 99, + 94, 89, 8, 17, 6, 2, 3, 71, 79, 71, 87, 92, + 83, 100, 111, 110, 126, 80, 80, 96, 90, 96, + 101, 106, 109, 114, 120, 116, 126, 88, 114, + 120, 102, 102, 1, 39, 28, 18, 7, 5, 65, 66, + 71, 65, 11, 51, 33, 25, 17, 45, 18, 14, 4, 6, + 6, 52, 32, 19, 10, 25, 10, 4, 1, 2, 65, 37, + 16, 8, 1, 15, 5, 65, 1, 8, 45, 27, 16, 10, 30, + 13, 12, 11, 7, 62, 75, 66, 7, 66, 68, 1, 9, + 10, 3, 10, 17, 23, 76, 71, 76, 72, 22, 79, 80, + 73, 0, 17, 93, 83, 7, 13, 90, 94, 89, 69, 65, + 13, 67, 1, 64, 4, 13, 77, 68, 67, 5, 86, 73, + 80, 78, 9, 100, 76, 4, 70, 20, 70, 65, 31, 70, + 73, 65, 26, 86, 78, 104, 14, 27, 27, 15, 64, + 15, 11, 4, 6, 3, 0, 66, 84, 88, 95, 86, 81, + 93, 116, 92, 89, 90, 85, 83, 71, 70, 85, 94, + 74, 68, 69, 66, 76, 94, 79, 82, 100, 87, 95, + 94, 111, 107, 101, 95, 80, 74, 93, 82, 85, 94, + 100, 102, 102, 103, 105, 109, 123, 115, 123, + 122, 120, 110, 120, 121, 71, 9, 1, 10, 15, 15, + 62, 26, 24, 68, 34, 62, 62, 62, 21, 10, 2, + 100, 126, 126, 126, 126, 126, 126, 66, 43, 27, + 18, 9, 14, 2, 66, 68, 66, 65, 4, 17, 8, 27, + 19, 10, 18, 19, 22, 23, 12, 25, 20, 64, 13, 5, + 72, 79, 86, 92, 120, 119, 107 }, + + { + + 20, + 4, 82, 20, 4, 82, 19, 27, 28, 12, 71, 100, 69, + 2, 52, 14, 54, 71, 24, 31, 64, 84, 2, 99, 116, + 69, 85, 126, 126, 126, 62, 18, 69, 24, 31, 64, + 68, 18, 20, 71, 3, 0, 0, 72, 87, 71, 93, 5, + 67, 64, 68, 80, 72, 88, 17, 4, 1, 65, 12, 3, + 22, 0, 0, 0, 7, 97, 97, 12, 79, 70, 52, 6, 69, + 77, 0, 82, 10, 18, 16, 28, 7, 21, 16, 11, 25, + 73, 79, 75, 82, 36, 1, 24, 65, 2, 69, 3, 25, + 71, 79, 70, 68, 19, 67, 8, 3, 72, 9, 68, 7, + 12, 7, 15, 22, 19, 84, 70, 3, 66, 2, 72, 11, + 72, 64, 19, 70, 16, 17, 5, 108, 10, 70, 101, + 80, 103, 69, 8, 1, 11, 5, 5, 26, 7, 1, 15, 25, + 72, 72, 4, 111, 78, 8, 95, 0, 77, 21, 1, 90, + 33, 1, 65, 98, 36, 83, 101, 1, 107, 70, 10, + 15, 7, 10, 6, 66, 10, 17, 91, 74, 7, 3, 1, 81, + 73, 77, 83, 74, 82, 92, 76, 78, 122, 94, 84, + 100, 95, 89, 6, 16, 4, 0, 1, 73, 81, 73, 89, + 94, 85, 102, 114, 112, 126, 82, 82, 98, 92, + 98, 103, 108, 111, 116, 123, 118, 126, 89, + 116, 121, 103, 102, 1, 39, 28, 18, 7, 5, 65, + 66, 71, 64, 11, 51, 33, 25, 17, 46, 19, 15, 4, + 7, 7, 53, 33, 19, 10, 26, 11, 4, 2, 3, 64, 38, + 17, 8, 1, 16, 6, 64, 2, 9, 45, 27, 16, 10, 31, + 14, 13, 12, 8, 62, 75, 66, 8, 66, 68, 1, 9, + 10, 3, 10, 17, 24, 76, 71, 76, 73, 22, 80, 81, + 74, 64, 17, 94, 84, 7, 13, 91, 95, 90, 68, 64, + 14, 67, 2, 64, 5, 14, 77, 68, 67, 5, 87, 73, + 80, 78, 10, 100, 76, 4, 70, 21, 70, 65, 32, + 70, 73, 65, 27, 87, 78, 105, 13, 27, 27, 14, + 65, 14, 10, 3, 5, 2, 64, 67, 85, 89, 96, 87, + 82, 95, 118, 93, 90, 91, 86, 83, 71, 70, 85, + 95, 73, 70, 71, 68, 78, 97, 81, 84, 102, 89, + 97, 96, 113, 109, 102, 96, 82, 76, 94, 84, 87, + 96, 102, 104, 104, 105, 107, 111, 125, 117, + 125, 124, 121, 112, 122, 123, 70, 10, 2, 11, + 16, 16, 62, 27, 25, 68, 35, 62, 62, 62, 22, 9, + 0, 102, 126, 126, 126, 126, 126, 126, 66, 43, + 27, 18, 9, 14, 2, 66, 68, 65, 64, 5, 18, 9, + 28, 20, 11, 19, 20, 23, 24, 12, 26, 20, 64, + 12, 4, 74, 81, 88, 94, 122, 121, 108 }, + + { + + 18, + 3, 83, 18, 3, 83, 20, 28, 28, 12, 72, 102, 71, + 0, 51, 14, 56, 72, 24, 31, 65, 85, 2, 101, + 118, 70, 87, 126, 126, 126, 62, 19, 70, 24, + 31, 65, 68, 19, 20, 72, 3, 0, 0, 73, 88, 71, + 94, 5, 67, 64, 69, 81, 72, 88, 17, 4, 1, 65, + 12, 2, 22, 0, 0, 0, 7, 98, 97, 12, 80, 71, 52, + 5, 69, 76, 0, 82, 10, 19, 17, 29, 8, 22, 17, + 12, 26, 72, 79, 74, 82, 36, 1, 24, 65, 2, 69, + 3, 26, 71, 79, 70, 67, 19, 67, 8, 4, 72, 9, + 68, 7, 12, 7, 15, 22, 19, 85, 71, 3, 67, 2, + 72, 10, 73, 65, 19, 71, 15, 17, 4, 110, 9, 71, + 103, 81, 104, 69, 8, 1, 11, 5, 5, 27, 7, 1, + 15, 26, 73, 72, 4, 113, 78, 8, 95, 0, 78, 21, + 1, 91, 33, 1, 65, 100, 36, 84, 102, 1, 108, + 72, 9, 14, 6, 9, 5, 67, 9, 16, 93, 75, 6, 2, + 1, 82, 74, 78, 84, 75, 83, 93, 77, 79, 124, + 96, 85, 102, 97, 89, 4, 14, 2, 65, 64, 76, 84, + 76, 92, 97, 88, 105, 117, 115, 126, 84, 84, + 100, 95, 101, 106, 111, 114, 119, 126, 121, + 126, 90, 118, 123, 104, 103, 1, 39, 28, 18, 7, + 5, 66, 66, 71, 64, 11, 51, 33, 25, 17, 47, 19, + 15, 4, 7, 7, 53, 33, 19, 10, 26, 11, 4, 2, 4, + 64, 39, 17, 8, 1, 16, 6, 64, 3, 9, 45, 27, 16, + 10, 31, 14, 13, 12, 8, 62, 75, 66, 8, 66, 68, + 1, 9, 10, 3, 10, 17, 24, 76, 71, 77, 74, 22, + 81, 82, 75, 65, 16, 96, 86, 6, 12, 92, 97, 91, + 68, 64, 15, 67, 2, 64, 5, 14, 78, 68, 67, 5, + 88, 73, 80, 78, 10, 101, 76, 4, 70, 21, 71, + 65, 32, 71, 74, 65, 27, 88, 79, 106, 12, 26, + 26, 13, 67, 13, 9, 2, 4, 1, 65, 68, 86, 91, + 98, 89, 84, 97, 120, 95, 92, 92, 87, 84, 71, + 70, 85, 96, 73, 73, 74, 70, 81, 100, 84, 87, + 105, 92, 100, 99, 116, 112, 104, 97, 84, 78, + 96, 86, 90, 99, 105, 107, 107, 107, 110, 114, + 126, 119, 126, 126, 123, 114, 124, 125, 69, + 11, 3, 11, 16, 16, 62, 27, 25, 68, 35, 62, 62, + 62, 22, 7, 65, 105, 126, 126, 126, 126, 126, + 126, 66, 43, 26, 17, 9, 14, 2, 67, 68, 65, 64, + 5, 18, 9, 29, 20, 11, 19, 20, 23, 24, 12, 26, + 20, 65, 10, 2, 76, 83, 90, 96, 125, 123, 109 }, + + { + + 17, + 3, 83, 17, 3, 83, 22, 30, 29, 13, 72, 103, 72, + 64, 51, 14, 59, 72, 25, 32, 65, 85, 3, 102, + 119, 70, 88, 126, 126, 126, 62, 21, 70, 25, + 32, 65, 67, 21, 21, 72, 4, 1, 1, 73, 88, 70, + 94, 5, 66, 0, 69, 81, 71, 88, 18, 5, 2, 64, + 13, 2, 22, 0, 0, 0, 8, 98, 97, 13, 80, 71, 52, + 5, 69, 74, 1, 82, 11, 21, 19, 31, 10, 24, 19, + 14, 28, 70, 78, 73, 81, 37, 2, 25, 64, 3, 68, + 4, 28, 70, 78, 69, 65, 20, 66, 9, 6, 71, 10, + 67, 8, 13, 8, 16, 23, 20, 85, 71, 4, 67, 2, + 71, 10, 73, 65, 19, 71, 15, 17, 4, 111, 9, 71, + 104, 82, 104, 68, 9, 2, 12, 6, 6, 28, 8, 2, + 16, 28, 73, 72, 5, 114, 77, 9, 95, 1, 78, 22, + 2, 91, 34, 1, 65, 101, 37, 84, 103, 1, 108, + 73, 9, 14, 6, 9, 5, 67, 9, 16, 94, 75, 6, 2, + 1, 82, 74, 78, 84, 75, 83, 94, 77, 79, 125, + 97, 85, 103, 98, 89, 3, 13, 1, 66, 65, 78, 86, + 78, 94, 99, 90, 107, 119, 117, 126, 85, 85, + 101, 97, 103, 108, 113, 116, 121, 126, 123, + 126, 90, 119, 124, 104, 103, 2, 40, 29, 19, 8, + 5, 66, 65, 70, 0, 12, 52, 34, 26, 17, 48, 20, + 16, 5, 8, 8, 54, 34, 20, 11, 27, 12, 5, 3, 6, + 0, 41, 18, 9, 2, 17, 7, 0, 5, 10, 46, 28, 17, + 11, 32, 15, 14, 13, 9, 62, 74, 65, 9, 66, 67, + 1, 10, 11, 4, 11, 18, 25, 75, 71, 77, 74, 23, + 81, 82, 76, 65, 16, 97, 87, 6, 12, 92, 98, 91, + 67, 0, 16, 66, 3, 0, 6, 15, 78, 67, 66, 6, 88, + 72, 79, 77, 11, 101, 76, 5, 70, 22, 71, 65, + 33, 71, 74, 64, 28, 88, 79, 106, 12, 26, 26, + 13, 68, 13, 9, 2, 4, 1, 65, 68, 86, 92, 99, + 90, 85, 98, 121, 96, 93, 92, 87, 84, 70, 69, + 84, 96, 72, 75, 76, 72, 83, 102, 86, 89, 107, + 94, 102, 101, 118, 114, 105, 97, 85, 79, 97, + 87, 92, 101, 107, 109, 109, 109, 112, 116, + 126, 120, 126, 126, 124, 115, 125, 126, 67, + 13, 5, 12, 17, 17, 62, 28, 26, 68, 36, 62, 62, + 62, 23, 6, 66, 107, 126, 126, 126, 126, 126, + 126, 65, 44, 26, 17, 9, 15, 2, 67, 68, 64, 0, + 6, 19, 10, 31, 21, 12, 20, 21, 24, 25, 13, 27, + 21, 65, 9, 1, 77, 84, 91, 97, 126, 124, 109 }, + + { + + 16, + 3, 83, 16, 3, 83, 24, 31, 29, 13, 72, 104, 73, + 65, 51, 14, 61, 72, 26, 33, 65, 86, 3, 103, + 120, 71, 90, 126, 126, 126, 62, 22, 70, 26, + 33, 65, 67, 22, 21, 73, 4, 1, 2, 73, 88, 70, + 94, 5, 66, 1, 69, 81, 71, 88, 18, 5, 2, 64, + 13, 2, 22, 0, 0, 0, 9, 98, 97, 13, 81, 71, 52, + 5, 69, 73, 2, 82, 11, 22, 21, 33, 11, 25, 21, + 15, 30, 69, 77, 72, 80, 38, 2, 26, 0, 3, 68, + 5, 30, 70, 78, 69, 64, 20, 66, 9, 7, 71, 11, + 67, 8, 13, 8, 17, 23, 20, 86, 71, 4, 67, 2, + 71, 10, 73, 66, 19, 72, 15, 17, 4, 113, 9, 71, + 106, 83, 105, 67, 10, 3, 13, 7, 7, 29, 9, 2, + 17, 29, 73, 72, 5, 115, 77, 10, 95, 1, 78, 23, + 2, 92, 35, 1, 65, 102, 38, 85, 104, 1, 109, + 75, 8, 13, 5, 8, 4, 68, 8, 16, 95, 76, 6, 2, + 1, 82, 75, 79, 85, 76, 84, 95, 78, 80, 126, + 98, 86, 104, 99, 89, 1, 12, 64, 68, 67, 80, + 88, 80, 97, 101, 92, 110, 122, 119, 126, 87, + 87, 103, 99, 105, 110, 115, 118, 123, 126, + 125, 126, 91, 121, 125, 105, 103, 2, 40, 29, + 19, 8, 5, 66, 65, 70, 1, 12, 52, 34, 26, 17, + 49, 20, 17, 5, 9, 9, 55, 35, 20, 11, 28, 12, + 5, 4, 7, 1, 42, 19, 9, 2, 18, 8, 1, 6, 10, 46, + 28, 17, 11, 33, 16, 15, 14, 10, 62, 74, 65, 9, + 66, 67, 1, 10, 11, 4, 11, 18, 26, 75, 71, 77, + 75, 23, 82, 83, 77, 66, 16, 98, 88, 6, 12, 93, + 99, 92, 66, 1, 17, 66, 3, 0, 7, 16, 78, 67, + 66, 6, 89, 72, 79, 77, 12, 101, 76, 5, 70, 23, + 71, 65, 34, 71, 74, 64, 29, 89, 79, 107, 11, + 26, 26, 12, 69, 12, 8, 1, 3, 0, 66, 69, 87, + 93, 100, 92, 86, 100, 123, 97, 94, 93, 88, 84, + 70, 69, 84, 97, 71, 77, 78, 74, 85, 105, 88, + 91, 110, 96, 104, 103, 120, 116, 106, 98, 87, + 81, 98, 89, 94, 103, 109, 111, 111, 111, 114, + 118, 126, 122, 126, 126, 125, 117, 126, 126, + 66, 14, 6, 13, 18, 18, 62, 29, 27, 68, 37, 62, + 62, 62, 24, 5, 68, 110, 126, 126, 126, 126, + 126, 126, 65, 44, 26, 17, 9, 15, 2, 67, 68, 0, + 1, 7, 20, 11, 32, 22, 13, 21, 22, 25, 26, 13, + 28, 21, 65, 8, 64, 79, 86, 93, 99, 126, 126, + 110 }, + + { + + 15, + 3, 83, 15, 3, 83, 26, 33, 30, 13, 73, 106, 75, + 66, 51, 14, 62, 72, 27, 34, 65, 87, 3, 104, + 121, 71, 92, 126, 126, 126, 62, 23, 70, 27, + 34, 65, 66, 23, 22, 73, 4, 2, 3, 74, 89, 70, + 94, 5, 66, 1, 69, 81, 71, 88, 19, 5, 2, 64, + 14, 2, 22, 0, 0, 0, 9, 98, 97, 14, 82, 71, 52, + 5, 69, 72, 3, 82, 12, 23, 23, 35, 13, 26, 23, + 16, 32, 68, 76, 71, 79, 39, 2, 27, 1, 3, 67, + 5, 32, 70, 77, 68, 0, 20, 66, 10, 9, 70, 12, + 67, 8, 13, 8, 18, 24, 21, 86, 71, 4, 67, 2, + 71, 10, 74, 66, 19, 73, 15, 17, 4, 115, 9, 72, + 108, 84, 106, 66, 11, 4, 14, 8, 7, 30, 9, 3, + 18, 30, 73, 72, 5, 116, 76, 11, 95, 1, 78, 24, + 2, 93, 36, 1, 65, 103, 39, 85, 105, 1, 110, + 76, 7, 13, 4, 7, 3, 68, 7, 15, 96, 77, 6, 2, + 1, 83, 76, 80, 86, 77, 85, 96, 79, 80, 126, + 99, 86, 105, 100, 89, 0, 10, 65, 70, 69, 82, + 90, 82, 99, 104, 94, 112, 124, 122, 126, 89, + 89, 104, 101, 107, 113, 118, 120, 126, 126, + 126, 126, 92, 123, 126, 106, 103, 2, 41, 29, + 19, 8, 5, 66, 65, 70, 1, 12, 52, 34, 26, 17, + 50, 21, 17, 6, 9, 10, 56, 35, 21, 11, 29, 13, + 6, 4, 8, 2, 43, 20, 10, 2, 19, 9, 2, 7, 11, + 46, 28, 17, 11, 34, 16, 16, 15, 11, 62, 73, + 64, 10, 66, 67, 1, 10, 11, 4, 11, 18, 26, 75, + 71, 77, 75, 24, 83, 84, 78, 67, 16, 99, 89, 6, + 12, 93, 100, 92, 65, 2, 18, 65, 4, 1, 7, 17, + 78, 67, 66, 7, 89, 72, 79, 76, 13, 101, 76, 5, + 70, 24, 71, 65, 35, 71, 74, 64, 30, 90, 79, + 108, 10, 25, 25, 11, 70, 11, 7, 0, 2, 64, 67, + 70, 88, 94, 101, 93, 87, 101, 125, 99, 95, 94, + 88, 85, 70, 69, 84, 97, 70, 79, 80, 76, 87, + 108, 90, 93, 112, 98, 106, 105, 123, 118, 108, + 99, 89, 82, 100, 91, 96, 105, 111, 113, 113, + 113, 116, 120, 126, 124, 126, 126, 126, 119, + 126, 126, 65, 15, 7, 14, 19, 19, 62, 30, 28, + 68, 38, 62, 62, 62, 25, 3, 69, 112, 126, 126, + 126, 126, 126, 126, 65, 44, 26, 17, 9, 15, 2, + 67, 68, 0, 2, 8, 21, 12, 33, 22, 13, 22, 22, + 25, 26, 13, 28, 21, 65, 7, 65, 81, 88, 95, + 101, 126, 126, 111 }, + + }, + + }; + + diff --git a/dependencies/ih264d/common/ih264_cabac_tables.h b/dependencies/ih264d/common/ih264_cabac_tables.h new file mode 100644 index 00000000..dd2fd359 --- /dev/null +++ b/dependencies/ih264d/common/ih264_cabac_tables.h @@ -0,0 +1,190 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ + +/** +****************************************************************************** +* @file ih264_cabac_tables.h +* +* @brief +* This file contains enumerations, macros and extern declarations of H264 +* cabac tables +* +* @author +* Ittiam +* +* @remarks +* none +****************************************************************************** +*/ + +#ifndef IH264_CABAC_TABLES_H_ +#define IH264_CABAC_TABLES_H_ + +/*****************************************************************************/ +/* Constant Macros */ +/*****************************************************************************/ + +/** +****************************************************************************** + * @brief maximum range of cabac_init_idc (0-2) + 1 for ISLICE +****************************************************************************** + */ +#define NUM_CAB_INIT_IDC_PLUS_ONE 4 + +/** +****************************************************************************** + * @brief max range of qps in H264 (0-51) +****************************************************************************** + */ +#define QP_RANGE 52 + +/** +****************************************************************************** + * @brief max range of cabac contexts in H264 (0-459) +****************************************************************************** + */ +#define NUM_CABAC_CTXTS 460 + + +/** Macros for Cabac checks */ +/** MbType */ +/** |x|x|I_PCM|SKIP| + |S|Inter/Intra|P/B|NON-BD16x16/BD16x16,I16x16/I4x4| */ +#define CAB_INTRA 0x00 /* 0000 00xx */ +#define CAB_INTER 0x04 /* 0000 01xx */ +#define CAB_I4x4 0x00 /* 0000 00x0 */ +#define CAB_I16x16 0x01 /* 0000 00x1 */ +#define CAB_BD16x16 0x04 /* 0000 0100 */ +#define CAB_NON_BD16x16 0x05 /* 0000 0101 */ +#define CAB_P 0x07 /* 0000 0111 */ +#define CAB_SI4x4 0x08 /* 0000 10x0 */ +#define CAB_SI16x16 0x09 /* 0000 10x1 */ +#define CAB_SKIP_MASK 0x10 /* 0001 0000 */ +#define CAB_SKIP 0x10 /* 0001 0000 */ +#define CAB_P_SKIP 0x16 /* 0001 x11x */ +#define CAB_B_SKIP 0x14 /* 0001 x100 */ +#define CAB_BD16x16_MASK 0x07 /* 0000 0111 */ +#define CAB_INTRA_MASK 0x04 /* 0000 0100 */ +#define CAB_I_PCM 0x20 /* 001x xxxx */ + +/** +****************************************************************************** + * @enum ctxBlockCat + +****************************************************************************** +*/ +typedef enum +{ + LUMA_DC_CTXCAT = 0, + LUMA_AC_CTXCAT = 1, + LUMA_4X4_CTXCAT = 2, + CHROMA_DC_CTXCAT = 3, + CHROMA_AC_CTXCAT = 4, + LUMA_8X8_CTXCAT = 5, + NUM_CTX_CAT = 6 +} CTX_BLOCK_CAT; + + +/** +****************************************************************************** + * @enum ctxIdxOffset + +****************************************************************************** +*/ +typedef enum +{ + MB_TYPE_SI_SLICE = 0, + MB_TYPE_I_SLICE = 3, + MB_SKIP_FLAG_P_SLICE = 11, + MB_TYPE_P_SLICE = 14, + SUB_MB_TYPE_P_SLICE = 21, + MB_SKIP_FLAG_B_SLICE = 24, + MB_TYPE_B_SLICE = 27, + SUB_MB_TYPE_B_SLICE = 36, + MVD_X = 40, + MVD_Y = 47, + REF_IDX = 54, + MB_QP_DELTA = 60, + INTRA_CHROMA_PRED_MODE = 64, + PREV_INTRA4X4_PRED_MODE_FLAG = 68, + REM_INTRA4X4_PRED_MODE = 69, + MB_FIELD_DECODING_FLAG = 70, + CBP_LUMA = 73, + CBP_CHROMA = 77, + CBF = 85, + SIGNIFICANT_COEFF_FLAG_FRAME = 105, + SIGNIFICANT_COEFF_FLAG_FLD = 277, + LAST_SIGNIFICANT_COEFF_FLAG_FRAME = 166, + LAST_SIGNIFICANT_COEFF_FLAG_FLD = 338, + COEFF_ABS_LEVEL_MINUS1 = 227, + + /* High profile related Syntax element CABAC offsets */ + TRANSFORM_SIZE_8X8_FLAG = 399, + SIGNIFICANT_COEFF_FLAG_8X8_FRAME = 402, + LAST_SIGNIFICANT_COEFF_FLAG_8X8_FRAME = 417, + COEFF_ABS_LEVEL_MINUS1_8X8 = 426, + SIGNIFICANT_COEFF_FLAG_8X8_FIELD = 436, + LAST_SIGNIFICANT_COEFF_FLAG_8X8_FIELD = 451 + +} cabac_table_num_t; + + +/** +****************************************************************************** + * @enum ctxIdxOffset + +****************************************************************************** +*/ +typedef enum +{ + SIG_COEFF_CTXT_CAT_0_OFFSET = 0, + SIG_COEFF_CTXT_CAT_1_OFFSET = 15, + SIG_COEFF_CTXT_CAT_2_OFFSET = 29, + SIG_COEFF_CTXT_CAT_3_OFFSET = 44, + SIG_COEFF_CTXT_CAT_4_OFFSET = 47, + SIG_COEFF_CTXT_CAT_5_OFFSET = 0, + COEFF_ABS_LEVEL_CAT_0_OFFSET = 0, + COEFF_ABS_LEVEL_CAT_1_OFFSET = 10, + COEFF_ABS_LEVEL_CAT_2_OFFSET = 20, + COEFF_ABS_LEVEL_CAT_3_OFFSET = 30, + COEFF_ABS_LEVEL_CAT_4_OFFSET = 39, + COEFF_ABS_LEVEL_CAT_5_OFFSET = 0 +} cabac_blk_cat_offset_t; + + + + +/*****************************************************************************/ +/* Extern global declarations */ +/*****************************************************************************/ + + +/* CABAC Table declaration*/ +extern const UWORD32 gau4_ih264_cabac_table[128][4]; + + +/*****************************************************************************/ +/* Cabac tables for context initialization depending upon type of Slice, */ +/* cabac init Idc value and Qp. */ +/*****************************************************************************/ +extern const UWORD8 gau1_ih264_cabac_ctxt_init_table[NUM_CAB_INIT_IDC_PLUS_ONE][QP_RANGE][NUM_CABAC_CTXTS]; + + +#endif /* IH264_CABAC_TABLES_H_ */ diff --git a/dependencies/ih264d/common/ih264_cavlc_tables.c b/dependencies/ih264d/common/ih264_cavlc_tables.c new file mode 100644 index 00000000..f122ab9b --- /dev/null +++ b/dependencies/ih264d/common/ih264_cavlc_tables.c @@ -0,0 +1,282 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ + + +/** +****************************************************************************** +* @file +* ih264_cavlc_tables.c +* +* @brief +* This file contains H264 cavlc tables for encoding coeff_tokens, levels, total +* zeros and runs before zeros +* +* @author +* Ittiam +* +* @par List of Tables +* - gu1_code_coeff_token_table +* - gu1_size_coeff_token_table +* - gu1_code_coeff_token_table_chroma +* - gu1_size_coeff_token_table_chroma +* - gu1_threshold_vlc_level +* - gu1_size_zero_table +* - gu1_code_zero_table +* - gu1_size_zero_table_chroma +* - gu1_code_zero_table_chroma +* - gu1_index_zero_table +* - gu1_size_run_table +* - gu1_code_run_table +* - gu4_codeword_level_tables +* - gu1_codesize_level_tables +* +* @remarks +* none +* +****************************************************************************** +*/ + +/*****************************************************************************/ +/* File Includes */ +/*****************************************************************************/ + +/* User include files */ +#include "ih264_typedefs.h" +#include "ih264_cavlc_tables.h" + + +/*****************************************************************************/ +/* Extern global definitions */ +/*****************************************************************************/ + +/** + ****************************************************************************** + * @brief Assignment of cbp to a codenum for intra and inter prediction modes + * chroma format idc != 0 + * input : cbp, intra - 0/inter - 1 + * output : codenum + * @remarks Table 9-4 – Assignment of codeNum to values of coded_block_pattern + * for macroblock prediction modes in H264 spec + ****************************************************************************** + */ +const UWORD8 gu1_cbp_map_tables[48][2]= +{ + { 3, 0}, {29, 2}, {30, 3}, {17, 7}, {31, 4}, {18, 8}, {37, 17}, { 8, 13}, + {32, 5}, {38, 18}, {19, 9}, { 9, 14}, {20, 10}, {10, 15}, {11, 16}, { 2, 11}, + {16, 1}, {33, 32}, {34, 33}, {21, 36}, {35, 34}, {22, 37}, {39, 44}, { 4, 40}, + {36, 35}, {40, 45}, {23, 38}, { 5, 41}, {24, 39}, { 6, 42}, { 7, 43}, { 1, 19}, + {41, 6}, {42, 24}, {43, 25}, {25, 20}, {44, 26}, {26, 21}, {46, 46}, {12, 28}, + {45, 27}, {47, 47}, {27, 22}, {13, 29}, {28, 23}, {14, 30}, {15, 31}, { 0, 12}, +}; + + +/** + ****************************************************************************** + * @brief total non-zero coefficients and numbers of trailing ones of a residual + * block are mapped to coeff_token using the tables given below. + * input : VLC-Num | Trailing ones | Total coeffs + * output : coeff_token (code word, size of the code word) + * @remarks Table-9-5 coeff_token mapping to TotalCoeff( coeff_token ) + * and TrailingOnes( coeff_token ) in H264 spec + ****************************************************************************** + */ +const UWORD8 gu1_code_coeff_token_table[3][4][16] = +{ + { + { 5, 7, 7, 7, 7, 15, 11, 8, 15, 11, 15, 11, 15, 11, 7, 4, }, + { 1, 4, 6, 6, 6, 6, 14, 10, 14, 10, 14, 10, 1, 14, 10, 6, }, + { 0, 1, 5, 5, 5, 5, 5, 13, 9, 13, 9, 13, 9, 13, 9, 5, }, + { 0, 0, 3, 3, 4, 4, 4, 4, 4, 12, 12, 8, 12, 8, 12, 8, }, + }, + { + {11, 7, 7, 7, 4, 7, 15, 11, 15, 11, 8, 15, 11, 7, 9, 7, }, + { 2, 7, 10, 6, 6, 6, 6, 14, 10, 14, 10, 14, 10, 11, 8, 6, }, + { 0, 3, 9, 5, 5, 5, 5, 13, 9, 13, 9, 13, 9, 6, 10, 5, }, + { 0, 0, 5, 4, 6, 8, 4, 4, 4, 12, 8, 12, 12, 8, 1, 4, }, + }, + { + {15, 11, 8, 15, 11, 9, 8, 15, 11, 15, 11, 8, 13, 9, 5, 1, }, + {14, 15, 12, 10, 8, 14, 10, 14, 14, 10, 14, 10, 7, 12, 8, 4, }, + { 0, 13, 14, 11, 9, 13, 9, 13, 10, 13, 9, 13, 9, 11, 7, 3, }, + { 0, 0, 12, 11, 10, 9, 8, 13, 12, 12, 12, 8, 12, 10, 6, 2, }, + }, +}; + +const UWORD8 gu1_size_coeff_token_table[3][4][16] = +{ + { + { 6, 8, 9, 10, 11, 13, 13, 13, 14, 14, 15, 15, 16, 16, 16, 16, }, + { 2, 6, 8, 9, 10, 11, 13, 13, 14, 14, 15, 15, 15, 16, 16, 16, }, + { 0, 3, 7, 8, 9, 10, 11, 13, 13, 14, 14, 15, 15, 16, 16, 16, }, + { 0, 0, 5, 6, 7, 8, 9, 10, 11, 13, 14, 14, 15, 15, 16, 16, }, + }, + { + { 6, 6, 7, 8, 8, 9, 11, 11, 12, 12, 12, 13, 13, 13, 14, 14, }, + { 2, 5, 6, 6, 7, 8, 9, 11, 11, 12, 12, 13, 13, 14, 14, 14, }, + { 0, 3, 6, 6, 7, 8, 9, 11, 11, 12, 12, 13, 13, 13, 14, 14, }, + { 0, 0, 4, 4, 5, 6, 6, 7, 9, 11, 11, 12, 13, 13, 13, 14, }, + }, + { + { 6, 6, 6, 7, 7, 7, 7, 8, 8, 9, 9, 9, 10, 10, 10, 10, }, + { 4, 5, 5, 5, 5, 6, 6, 7, 8, 8, 9, 9, 9, 10, 10, 10, }, + { 0, 4, 5, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 10, }, + { 0, 0, 4, 4, 4, 4, 4, 5, 6, 7, 8, 8, 9, 10, 10, 10, }, + }, +}; +const UWORD8 gu1_code_coeff_token_table_chroma[4][4] = +{ + { 7, 4, 3, 2, }, + { 1, 6, 3, 3, }, + { 0, 1, 2, 2, }, + { 0, 0, 5, 0, }, +}; + +const UWORD8 gu1_size_coeff_token_table_chroma[4][4] = +{ + { 6, 6, 6, 6, }, + { 1, 6, 7, 8, }, + { 0, 3, 7, 8, }, + { 0, 0, 6, 7, }, +}; + +/** + ****************************************************************************** + * @brief After encoding the current Level, to encode the next level, the choice + * of VLC table needs to be updated. The update is carried basing on a set of thresholds. + * These thresholds are listed in the table below for lookup. + * input : suffix_length + * output : threshold + ****************************************************************************** + */ +const UWORD8 gu1_threshold_vlc_level[6] = +{ + 0, 3, 6, 12, 24, 48 +}; + + +/** + ****************************************************************************** + * @brief table for encoding total number of zeros + * input : coeff_token, total zeros + * output : code word, size of the code word + * @remarks Table-9-7, 9-8 total_zeros tables for 4x4 blocks with + * TotalCoeff( coeff_token ) in H264 spec + ****************************************************************************** + */ +const UWORD8 gu1_size_zero_table[135] = +{ + 1, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 9, + 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 6, 6, 6, 6, + 4, 3, 3, 3, 4, 4, 3, 3, 4, 5, 5, 6, 5, 6, + 5, 3, 4, 4, 3, 3, 3, 4, 3, 4, 5, 5, 5, + 4, 4, 4, 3, 3, 3, 3, 3, 4, 5, 4, 5, + 6, 5, 3, 3, 3, 3, 3, 3, 4, 3, 6, + 6, 5, 3, 3, 3, 2, 3, 4, 3, 6, + 6, 4, 5, 3, 2, 2, 3, 3, 6, + 6, 6, 4, 2, 2, 3, 2, 5, + 5, 5, 3, 2, 2, 2, 4, + 4, 4, 3, 3, 1, 3, + 4, 4, 2, 1, 3, + 3, 3, 1, 2, + 2, 2, 1, + 1, 1, +}; +const UWORD8 gu1_code_zero_table[135] = +{ + 1, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 1, + 7, 6, 5, 4, 3, 5, 4, 3, 2, 3, 2, 3, 2, 1, 0, + 5, 7, 6, 5, 4, 3, 4, 3, 2, 3, 2, 1, 1, 0, + 3, 7, 5, 4, 6, 5, 4, 3, 3, 2, 2, 1, 0, + 5, 4, 3, 7, 6, 5, 4, 3, 2, 1, 1, 0, + 1, 1, 7, 6, 5, 4, 3, 2, 1, 1, 0, + 1, 1, 5, 4, 3, 3, 2, 1, 1, 0, + 1, 1, 1, 3, 3, 2, 2, 1, 0, + 1, 0, 1, 3, 2, 1, 1, 1, + 1, 0, 1, 3, 2, 1, 1, + 0, 1, 1, 2, 1, 3, + 0, 1, 1, 1, 1, + 0, 1, 1, 1, + 0, 1, 1, + 0, 1, +}; +const UWORD8 gu1_size_zero_table_chroma[9] = +{ + 1, 2, 3, 3, + 1, 2, 2, + 1, 1, +}; +const UWORD8 gu1_code_zero_table_chroma[9] = +{ + 1, 1, 1, 0, + 1, 1, 0, + 1, 0, +}; + +/** + ****************************************************************************** + * @brief index to access zero table (look up) + * input : TotalCoeff( coeff_token ) + * output : index to access zero table + ****************************************************************************** + */ +const UWORD8 gu1_index_zero_table[15] = +{ + 0, 16, 31, 45, 58, 70, 81, 91, 100, 108, 115, 121, 126, 130, 133, +}; + +/** + ****************************************************************************** + * @brief table for encoding runs of zeros before + * input : zeros left, runs of zeros before + * output : code word, size of the code word + * @remarks Table-9-10 table for run_before in H264 spec + ****************************************************************************** + */ +const UWORD8 gu1_size_run_table[42] = +{ + 1, 1, + 1, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 3, 3, + 2, 2, 3, 3, 3, 3, + 2, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 4, 5, 6, 7, 8, 9, 10, 11, +}; +const UWORD8 gu1_code_run_table[42] = +{ + 1, 0, + 1, 1, 0, + 3, 2, 1, 0, + 3, 2, 1, 1, 0, + 3, 2, 3, 2, 1, 0, + 3, 0, 1, 3, 2, 5, 4, + 7, 6, 5, 4, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, +}; +/** + ****************************************************************************** + * @brief index to access zero table (look up) + * input : TotalCoeff( coeff_token ) + * output : index to access zero table + ****************************************************************************** + */ +const UWORD8 gu1_index_run_table[7] = +{ + 0, 2, 5, 9, 14, 20, 27, +}; diff --git a/dependencies/ih264d/common/ih264_cavlc_tables.h b/dependencies/ih264d/common/ih264_cavlc_tables.h new file mode 100644 index 00000000..78057b52 --- /dev/null +++ b/dependencies/ih264d/common/ih264_cavlc_tables.h @@ -0,0 +1,133 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ + +/** +****************************************************************************** +* @file ih264_cavlc_tables.h +* +* @brief +* This file contains enumerations, macros and extern declarations of H264 +* cavlc tables +* +* @author +* Ittiam +* +* @remarks +* none +****************************************************************************** +*/ + +#ifndef IH264_CAVLC_TABLES_H_ +#define IH264_CAVLC_TABLES_H_ + +/*****************************************************************************/ +/* Constant Macros */ +/*****************************************************************************/ +/** +****************************************************************************** + * @brief maximum zeros left +****************************************************************************** + */ +#define MAX_ZERO_LEFT 6 + +/*****************************************************************************/ +/* Extern global declarations */ +/*****************************************************************************/ + +/** + ****************************************************************************** + * @brief Assignment of cbp to a codenum for intra and inter prediction modes + * chroma format idc != 0 + * input : cbp, intra - 0/inter - 1 + * output : codenum + * @remarks Table 9-4 – Assignment of codeNum to values of coded_block_pattern + * for macroblock prediction modes in H264 spec + ****************************************************************************** + */ +extern const UWORD8 gu1_cbp_map_tables[48][2]; + +/** + ****************************************************************************** + * @brief total non-zero coefficients and numbers of trailing ones of a residual + * block are mapped to coefftoken using the tables given below. + * input : VLC-Num | Trailing ones | Total coeffs + * output : coeff_token (code word, size of the code word) + * @remarks Table-9-5 coeff_token mapping to TotalCoeff( coeff_token ) + * and TrailingOnes( coeff_token ) in H264 spec + ****************************************************************************** + */ +extern const UWORD8 gu1_code_coeff_token_table[3][4][16]; +extern const UWORD8 gu1_size_coeff_token_table[3][4][16]; +extern const UWORD8 gu1_code_coeff_token_table_chroma[4][4]; +extern const UWORD8 gu1_size_coeff_token_table_chroma[4][4]; + +/** + ****************************************************************************** + * @brief Thresholds for determining whether to increment Level table number. + * input : suffix_length + * output : threshold + ****************************************************************************** + */ +extern const UWORD8 gu1_threshold_vlc_level[6]; + +/** + ****************************************************************************** + * @brief table for encoding total number of zeros + * input : coeff_token, total zeros + * output : code word, size of the code word + * @remarks Table-9-7, 9-8 total_zeros tables for 4x4 blocks with + * TotalCoeff( coeff_token ) in H264 spec + ****************************************************************************** + */ +extern const UWORD8 gu1_size_zero_table[135]; +extern const UWORD8 gu1_code_zero_table[135]; +extern const UWORD8 gu1_size_zero_table_chroma[9]; +extern const UWORD8 gu1_code_zero_table_chroma[9]; + +/** + ****************************************************************************** + * @brief index to access zero table (for speed) + * input : TotalCoeff( coeff_token ) + * output : index to access zero table + ****************************************************************************** + */ +extern const UWORD8 gu1_index_zero_table[15]; + +/** + ****************************************************************************** + * @brief table for encoding runs of zeros before + * input : zeros left, runs of zeros before + * output : code word, size of the code word + * @remarks Table-9-10 table for run_before in H264 spec + ****************************************************************************** + */ +extern const UWORD8 gu1_size_run_table[42]; +extern const UWORD8 gu1_code_run_table[42]; + +/** + ****************************************************************************** + * @brief index to access run table (look up) + * input : zeros left + * output : index to access run table + ****************************************************************************** + */ +extern const UWORD8 gu1_index_run_table[7]; + +#endif /* IH264_CAVLC_TABLES_H_ */ diff --git a/dependencies/ih264d/common/ih264_chroma_intra_pred_filters.c b/dependencies/ih264d/common/ih264_chroma_intra_pred_filters.c new file mode 100644 index 00000000..1894bfc4 --- /dev/null +++ b/dependencies/ih264d/common/ih264_chroma_intra_pred_filters.c @@ -0,0 +1,478 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** +******************************************************************************* +* @file +* ih264_chroma_intra_pred_filters.c +* +* @brief +* Contains function definitions for chroma intra prediction filters +* +* @author +* Ittiam +* +* @par List of Functions: +* -ih264_intra_pred_chroma_8x8_mode_dc +* -ih264_intra_pred_chroma_8x8_mode_horz +* -ih264_intra_pred_chroma_8x8_mode_vert +* -ih264_intra_pred_chroma_8x8_mode_plane +* +* @remarks +* None +* +******************************************************************************* +*/ + +/*****************************************************************************/ +/* File Includes */ +/*****************************************************************************/ + +/* System include files */ +#include +#include +#include + +/* User include files */ +#include "ih264_defs.h" +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264_intra_pred_filters.h" + +/* Global variables used only in assembly files*/ +const WORD8 ih264_gai1_intrapred_chroma_plane_coeffs1[] = +{ 0x01,0x00,0x01,0x00, + 0x02,0x00,0x02,0x00, + 0x03,0x00,0x03,0x00, + 0x04,0x00,0x04,0x00 +}; + const WORD8 ih264_gai1_intrapred_chroma_plane_coeffs2[] = + { 0xfd,0xff,0xfe,0xff, + 0xff,0xff,0x00,0x00, + 0x01,0x00,0x02,0x00, + 0x03,0x00,0x04,0x00, + }; + +/*****************************************************************************/ +/* Chroma Intra prediction 8x8 filters */ +/*****************************************************************************/ + +/** +******************************************************************************* +* +* ih264_intra_pred_chroma_8x8_mode_dc +* +* @brief +* Perform Intra prediction for chroma_8x8 mode:DC +* +* @par Description: +* Perform Intra prediction for chroma_8x8 mode:DC ,described in sec 8.3.4.1 +* +* @param[in] pu1_src +* UWORD8 pointer to the source containing alternate U and V samples +* +* @param[out] pu1_dst +* UWORD8 pointer to the destination with alternate U and V samples +* +* @param[in] src_strd +* integer source stride +* +* @param[in] dst_strd +* integer destination stride +* +** @param[in] ngbr_avail +* availability of neighbouring pixels +* +* @returns +* +* @remarks +* None +* +****************************************************************************** +*/ +void ih264_intra_pred_chroma_8x8_mode_dc(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + WORD32 left_avail, left_avail1, left_avail2; /* availability of left predictors (only for DC) */ + WORD32 top_avail; /* availability of top predictors (only for DC) */ + UWORD8 *pu1_left = NULL; /* Pointer to start of left predictors */ + UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ + + /* temporary variables to store accumulated first left half,second left half, + * first top half,second top half of U and V values*/ + WORD32 val_u_l1 = 0, val_u_l2 = 0, val_u_t1 = 0, val_u_t2 = 0; + WORD32 val_v_l1 = 0, val_v_l2 = 0, val_v_t1 = 0, val_v_t2 = 0; + + WORD32 val_u1 = 0, val_u2 = 0, val_v1 = 0, val_v2 = 0; + + WORD32 col, row; /*loop variables*/ + UNUSED(src_strd); + + left_avail = ngbr_avail & 0x11; + left_avail1 = ngbr_avail & 1; + left_avail2 = (ngbr_avail >> 4) & 1; + top_avail = (ngbr_avail >> 2) & 1; + + pu1_top = pu1_src + 2 * BLK8x8SIZE + 2; + pu1_left = pu1_src + 2 * BLK8x8SIZE - 2; + + if(left_avail1) + { /* First 4x4 block*/ + val_u_l1 += *pu1_left; + val_v_l1 += *(pu1_left + 1); + pu1_left -= 2; + val_u_l1 += *pu1_left; + val_v_l1 += *(pu1_left + 1); + pu1_left -= 2; + val_u_l1 += *pu1_left; + val_v_l1 += *(pu1_left + 1); + pu1_left -= 2; + val_u_l1 += *pu1_left + 2; + val_v_l1 += *(pu1_left + 1) + 2; + pu1_left -= 2; + } + else + pu1_left -= 2 * 4; + + if(left_avail2) + { + /* Second 4x4 block*/ + val_u_l2 += *pu1_left; + val_v_l2 += *(pu1_left + 1); + pu1_left -= 2; + val_u_l2 += *pu1_left; + val_v_l2 += *(pu1_left + 1); + pu1_left -= 2; + val_u_l2 += *pu1_left; + val_v_l2 += *(pu1_left + 1); + pu1_left -= 2; + val_u_l2 += *pu1_left + 2; + val_v_l2 += *(pu1_left + 1) + 2; + pu1_left -= 2; + } + else + pu1_left -= 2 * 4; + + if(top_avail) + { + val_u_t1 += *pu1_top + *(pu1_top + 2) + *(pu1_top + 4) + + *(pu1_top + 6) + 2; + val_u_t2 += *(pu1_top + 8) + *(pu1_top + 10) + *(pu1_top + 12) + + *(pu1_top + 14) + 2; + val_v_t1 += *(pu1_top + 1) + *(pu1_top + 3) + *(pu1_top + 5) + + *(pu1_top + 7) + 2; + val_v_t2 += *(pu1_top + 9) + *(pu1_top + 11) + *(pu1_top + 13) + + *(pu1_top + 15) + 2; + } + + if(left_avail + top_avail) + { + val_u1 = (left_avail1 + top_avail) ? + ((val_u_l1 + val_u_t1) + >> (1 + left_avail1 + top_avail)) :128; + val_v1 = (left_avail1 + top_avail) ? + ((val_v_l1 + val_v_t1) + >> (1 + left_avail1 + top_avail)) :128; + if(top_avail) + { + val_u2 = val_u_t2 >> 2; + val_v2 = val_v_t2 >> 2; + } + else if(left_avail1) + { + val_u2 = val_u_l1 >> 2; + val_v2 = val_v_l1 >> 2; + } + else + { + val_u2 = val_v2 = 128; + } + + for(row = 0; row < 4; row++) + { + /*top left 4x4 block*/ + for(col = 0; col < 8; col += 2) + { + *(pu1_dst + row * dst_strd + col) = val_u1; + *(pu1_dst + row * dst_strd + col + 1) = val_v1; + } + /*top right 4x4 block*/ + for(col = 8; col < 16; col += 2) + { + *(pu1_dst + row * dst_strd + col) = val_u2; + *(pu1_dst + row * dst_strd + col + 1) = val_v2; + } + } + + if(left_avail2) + { + val_u1 = val_u_l2 >> 2; + val_v1 = val_v_l2 >> 2; + } + else if(top_avail) + { + val_u1 = val_u_t1 >> 2; + val_v1 = val_v_t1 >> 2; + } + else + { + val_u1 = val_v1 = 128; + } + val_u2 = (left_avail2 + top_avail) ? + ((val_u_l2 + val_u_t2) + >> (1 + left_avail2 + top_avail)) : 128; + val_v2 = (left_avail2 + top_avail) ? + ((val_v_l2 + val_v_t2) + >> (1 + left_avail2 + top_avail)) : 128; + + for(row = 4; row < 8; row++) + { /*bottom left 4x4 block*/ + for(col = 0; col < 8; col += 2) + { + *(pu1_dst + row * dst_strd + col) = val_u1; + *(pu1_dst + row * dst_strd + col + 1) = val_v1; + } + /*bottom right 4x4 block*/ + for(col = 8; col < 16; col += 2) + { + *(pu1_dst + row * dst_strd + col) = val_u2; + *(pu1_dst + row * dst_strd + col + 1) = val_v2; + } + } + } + else + { + /* Both left and top are unavailable, set the block to 128 */ + for(row = 0; row < 8; row++) + { + memset(pu1_dst + row * dst_strd, 128, 8 * sizeof(UWORD16)); + } + } +} + +/** +******************************************************************************* +* +*ih264_intra_pred_chroma_8x8_mode_horz +* +* @brief +* Perform Intra prediction for chroma_8x8 mode:Horizontal +* +* @par Description: +* Perform Intra prediction for chroma_8x8 mode:Horizontal ,described in sec 8.3.4.2 +* +* @param[in] pu1_src +* UWORD8 pointer to the source containing alternate U and V samples +* +* @param[out] pu1_dst +* UWORD8 pointer to the destination with alternate U and V samples +* +* @param[in] src_strd +* integer source stride +* +* @param[in] dst_strd +* integer destination stride +* +* @param[in] ngbr_avail +* availability of neighbouring pixels(Not used in this function) +* +* @returns +* +* @remarks +* None +* +****************************************************************************** +*/ +void ih264_intra_pred_chroma_8x8_mode_horz(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + + UWORD8 *pu1_left = NULL; /* Pointer to start of top predictors */ + WORD32 rows, cols; /* loop variables*/ + UNUSED(src_strd); + UNUSED(ngbr_avail); + pu1_left = pu1_src + 2 * BLK8x8SIZE - 2; + for(rows = 0; rows < 8; rows++) + { + for(cols = 0; cols < 16; cols += 2) + { + *(pu1_dst + rows * dst_strd + cols) = *pu1_left; + + *(pu1_dst + rows * dst_strd + cols + 1) = *(pu1_left + 1); + } + pu1_left -= 2; + } + +} + +/** +******************************************************************************* +* +*ih264_intra_pred_chroma_8x8_mode_vert +* +* @brief +* Perform Intra prediction for chroma_8x8 mode:vertical +* +* @par Description: +* Perform Intra prediction for chroma_8x8 mode:vertical ,described in sec 8.3.4.3 +* +* @param[in] pu1_src +* UWORD8 pointer to the source containing alternate U and V samples +* +* @param[out] pu1_dst +* UWORD8 pointer to the destination with alternate U and V samples +* +* @param[in] src_strd +* integer source stride +* +* @param[in] dst_strd +* integer destination stride +* +* @param[in] ngbr_avail +* availability of neighbouring pixels(Not used in this function) +* +* @returns +* +* @remarks +* None +* +******************************************************************************* +*/ +void ih264_intra_pred_chroma_8x8_mode_vert(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + + UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ + WORD32 row;/*loop variable*/ + UNUSED(src_strd); + UNUSED(ngbr_avail); + pu1_top = pu1_src + 2 * BLK8x8SIZE + 2; + + /* 8 bytes are copied from src to dst */ + for(row = 0; row < 2; row++) + { + memcpy(pu1_dst, pu1_top, 16); + + pu1_dst += dst_strd; + memcpy(pu1_dst, pu1_top, 16); + + pu1_dst += dst_strd; + memcpy(pu1_dst, pu1_top, 16); + + pu1_dst += dst_strd; + memcpy(pu1_dst, pu1_top, 16); + + pu1_dst += dst_strd; + } +} + +/** +******************************************************************************* +* +* ih264_intra_pred_chroma_8x8_mode_plane +* +* @brief +* Perform Intra prediction for chroma_8x8 mode:PLANE +* +* @par Description: +* Perform Intra prediction for chroma_8x8 mode:PLANE ,described in sec 8.3.4.4 +* +* @param[in] pu1_src +* UWORD8 pointer to the source containing alternate U and V samples +* +* @param[out] pu1_dst +* UWORD8 pointer to the destination with alternate U and V samples +* +* @param[in] src_strd +* integer source stride +* +* @param[in] dst_strd +* integer destination stride +* +* @param[in] ngbr_avail +* availability of neighbouring pixels(Not used in this function) +* +* @returns +* +* @remarks +* None +* +****************************************************************************** +*/ +void ih264_intra_pred_chroma_8x8_mode_plane(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + + UWORD8 *pu1_left = NULL; /* Pointer to start of left predictors */ + UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ + WORD32 val = 0; + WORD32 rows, cols; /* loop variables*/ + WORD32 a_u, b_u, c_u, h_u, v_u; /* Implementing section 8.3.4.4 . The variables represent the corresponding variables in the section*/ + WORD32 a_v, b_v, c_v, h_v, v_v; + UNUSED(src_strd); + UNUSED(ngbr_avail); + a_u = b_u = c_u = h_u = v_u = 0; + a_v = b_v = c_v = h_v = v_v = 0; + /* As chroma format 4:2:0 is used,xCF = 4 * ( chroma_format_idc = = 3 ) = 0 and + yCF = 4 * ( chroma_format_idc != 1 ) = 0 */ + pu1_top = pu1_src + 2 * BLK8x8SIZE + 2; + pu1_left = pu1_src + 2 * BLK8x8SIZE - 2; + /* Implementing section 8.3.4.4 */ + for(cols = 0; cols < 4; cols++) + { + h_u += (cols + 1) * (pu1_top[8 + 2 * cols] - pu1_top[4 - 2 * cols]);/*section 8.3.4.4 equation (8-144)*/ + h_v += (cols + 1) * (pu1_top[8 + 2 * cols + 1] - pu1_top[4 - 2 * cols+ 1]); + + v_u += (cols + 1) * (pu1_left[(4 + cols) * (-2)] - pu1_left[(2 - cols) * (-2)]); + v_v += (cols + 1) * (pu1_left[(4 + cols) * (-2) + 1] - pu1_left[(2 - cols) * (-2) + 1]);/*section 8.3.4.4 equation (8-145)*/ + } + a_u = 16 * (pu1_left[7 * (-2)] + pu1_top[14]); + a_v = 16 * (pu1_left[7 * (-2) + 1] + pu1_top[15]);/*section 8.3.3.4 equation (8-141)*/ + b_u = (34 * h_u + 32) >> 6;/*section 8.3.3.4 equation (8-142)*/ + b_v = (34 * h_v + 32) >> 6;/*section 8.3.3.4 equation (8-142)*/ + c_u = (34 * v_u + 32) >> 6;/*section 8.3.3.4 equation (8-143)*/ + c_v = (34 * v_v + 32) >> 6;/*section 8.3.3.4 equation (8-143)*/ + + for(rows = 0; rows < 8; rows++) + { + for(cols = 0; cols < 8; cols++) + { + val = (a_u + b_u * (cols - 3) + c_u * (rows - 3) );/*section 8.3.4.4 equation (8-140)*/ + val = (val + 16) >> 5; + *(pu1_dst + rows * dst_strd + 2 * cols) = CLIP_U8(val); + val = (a_v + b_v * (cols - 3) + c_v * (rows - 3) );/*section 8.3.4.4 equation (8-140)*/ + val = (val + 16) >> 5; + *(pu1_dst + rows * dst_strd + 2 * cols + 1) = CLIP_U8(val); + } + } +} + diff --git a/dependencies/ih264d/common/ih264_common_tables.c b/dependencies/ih264d/common/ih264_common_tables.c new file mode 100644 index 00000000..df09f5ac --- /dev/null +++ b/dependencies/ih264d/common/ih264_common_tables.c @@ -0,0 +1,725 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** +******************************************************************************* +* @file +* ih264_common_tables.c +* +* @brief +* Contains common global tables +* +* @author +* Harish M +* +* @par List of Functions: +* +* @remarks +* None +* +******************************************************************************* +*/ + +/*****************************************************************************/ +/* File Includes */ +/*****************************************************************************/ + +/* User include files */ +#include "ih264_typedefs.h" +#include "ih264_defs.h" +#include "ih264_macros.h" +#include "ih264_structs.h" +#include "ih264_common_tables.h" + + +/*****************************************************************************/ +/* Extern global definitions */ +/*****************************************************************************/ + +/** + ****************************************************************************** + * @brief while encoding, basing on the input configuration parameters, the + * the level of the bitstream is computed basing on the table below. + * input : table_idx + * output : level_idc or cpb size + * @remarks Table A-1 – level table limits + ****************************************************************************** + */ +const level_tables_t gas_ih264_lvl_tbl[16] = +{ + { IH264_LEVEL_10, 1485, 99, 297, 64, 175, 64 }, + { IH264_LEVEL_1B, 1485, 99, 297, 128, 350, 64 }, + { IH264_LEVEL_11, 3000, 396, 675, 192, 500, 128 }, + { IH264_LEVEL_12, 6000, 396, 1782, 384, 1000, 128 }, + { IH264_LEVEL_13, 11880, 396, 1782, 768, 2000, 128 }, + { IH264_LEVEL_20, 11880, 396, 1782, 2000, 2000, 128 }, + { IH264_LEVEL_21, 19800, 792, 3564, 4000, 4000, 256 }, + { IH264_LEVEL_22, 20250, 1620, 6075, 4000, 4000, 256 }, + { IH264_LEVEL_30, 40500, 1620, 6075, 10000, 10000, 256 }, + { IH264_LEVEL_31, 108000, 3600, 13500, 14000, 14000, 512 }, + { IH264_LEVEL_32, 216000, 5120, 15360, 20000, 20000, 512 }, + { IH264_LEVEL_40, 245760, 8192, 24576, 20000, 25000, 512 }, + { IH264_LEVEL_41, 245760, 8192, 24576, 50000, 62500, 512 }, + { IH264_LEVEL_42, 522240, 8704, 26112, 50000, 62500, 512 }, + { IH264_LEVEL_50, 589824, 22080, 82800, 135000, 135000, 512 }, + { IH264_LEVEL_51, 983040, 36864, 138240, 240000, 240000, 512 }, +}; + + +/** + * Array containing supported levels + */ +const WORD32 gai4_ih264_levels[] = +{ + IH264_LEVEL_10, + IH264_LEVEL_11, + IH264_LEVEL_12, + IH264_LEVEL_13, + IH264_LEVEL_20, + IH264_LEVEL_21, + IH264_LEVEL_22, + IH264_LEVEL_30, + IH264_LEVEL_31, + IH264_LEVEL_32, + IH264_LEVEL_40, + IH264_LEVEL_41, + IH264_LEVEL_42, + IH264_LEVEL_50, + IH264_LEVEL_51, +}; + + +/** + * Array giving size of max luma samples in a picture for a given level + */ +const WORD32 gai4_ih264_max_luma_pic_size[] = +{ + /* Level 1 */ + 25344, + /* Level 1.1 */ + 101376, + /* Level 1.2 */ + 101376, + /* Level 1.3 */ + 101376, + /* Level 2 */ + 101376, + /* Level 2.1 */ + 202752, + /* Level 2.2 */ + 414720, + /* Level 3 */ + 414720, + /* Level 3.1 */ + 921600, + /* Level 3.2 */ + 1310720, + /* Level 4 */ + 2097152, + /* Level 4.1 */ + 2097152, + /* Level 4.2 */ + 2228224, + /* Level 5 */ + 5652480, + /* Level 5.1 */ + 9437184 +}; + + +/** Max width and height allowed for a given level */ +/** This is derived as SQRT(8 * gai4_ih264_max_luma_pic_size[]) */ +const WORD32 gai4_ih264_max_wd_ht[] = +{ + /* Level 1 */ + 451, + /* Level 1.1 */ + 901, + /* Level 1.2 */ + 901, + /* Level 1.3 */ + 901, + /* Level 2 */ + 901, + /* Level 2.1 */ + 1274, + /* Level 2.2 */ + 1822, + /* Level 3 */ + 1822, + /* Level 3.1 */ + 2716, + /* Level 3.2 */ + 3239, + /* Level 4 */ + 4096, + /* Level 4.1 */ + 4096, + /* Level 4.2 */ + 4223, + /* Level 5 */ + 6725, + /* Level 5.1 */ + 8689 +}; + +/** Min width and height allowed for a given level */ +/** This is derived as gai4_ih264_max_luma_pic_size[]/gai4_ih264_max_wd_ht[] */ +const WORD32 gai4_ih264_min_wd_ht[] = +{ + /* Level 1 */ + 57, + /* Level 1.1 */ + 113, + /* Level 1.2 */ + 113, + /* Level 1.3 */ + 113, + /* Level 2 */ + 113, + /* Level 2.1 */ + 160, + /* Level 2.2 */ + 228, + /* Level 3 */ + 228, + /* Level 3.1 */ + 340, + /* Level 3.2 */ + 405, + /* Level 4 */ + 512, + /* Level 4.1 */ + 512, + /* Level 4.2 */ + 528, + /* Level 5 */ + 841, + /* Level 5.1 */ + 1087 + +}; + + +/** Table 7-11 Macroblock types for I slices */ +intra_mbtype_info_t gas_ih264_i_mbtype_info[] = +{ + /* For first entry, if transform_size_8x8_flag is 1, mode will be MBPART_I8x8 */ + /* This has to be taken care while accessing the table */ + {0, MBPART_I4x4, VERT_I16x16, 0, 0}, + {0, MBPART_I16x16, VERT_I16x16, 0, 0}, + {0, MBPART_I16x16, HORZ_I16x16, 0, 0}, + {0, MBPART_I16x16, DC_I16x16, 0, 0}, + {0, MBPART_I16x16, PLANE_I16x16, 0, 0}, + {0, MBPART_I16x16, VERT_I16x16, 1, 0}, + {0, MBPART_I16x16, HORZ_I16x16, 1, 0}, + {0, MBPART_I16x16, DC_I16x16, 1, 0}, + {0, MBPART_I16x16, PLANE_I16x16, 1, 0}, + {0, MBPART_I16x16, VERT_I16x16, 2, 0}, + {0, MBPART_I16x16, HORZ_I16x16, 2, 0}, + {0, MBPART_I16x16, DC_I16x16, 2, 0}, + {0, MBPART_I16x16, PLANE_I16x16, 2, 0}, + {0, MBPART_I16x16, VERT_I16x16, 0, 15}, + {0, MBPART_I16x16, HORZ_I16x16, 0, 15}, + {0, MBPART_I16x16, DC_I16x16, 0, 15}, + {0, MBPART_I16x16, PLANE_I16x16, 0, 15}, + {0, MBPART_I16x16, VERT_I16x16, 1, 15}, + {0, MBPART_I16x16, HORZ_I16x16, 1, 15}, + {0, MBPART_I16x16, DC_I16x16, 1, 15}, + {0, MBPART_I16x16, PLANE_I16x16, 1, 15}, + {0, MBPART_I16x16, VERT_I16x16, 2, 15}, + {0, MBPART_I16x16, HORZ_I16x16, 2, 15}, + {0, MBPART_I16x16, DC_I16x16, 2, 15}, + {0, MBPART_I16x16, PLANE_I16x16, 2, 15}, + {0, MBPART_IPCM, VERT_I16x16, 0, 0} +}; + +/** Table 7-13 Macroblock types for P slices */ +inter_mbtype_info_t gas_ih264_p_mbtype_info[] = +{ + {1, MBPART_L0, MBPART_NA, 16, 16}, + {2, MBPART_L0, MBPART_L0, 16, 8}, + {2, MBPART_L0, MBPART_L0, 8, 16}, + {4, MBPART_NA, MBPART_NA, 8, 8}, + {4, MBPART_NA, MBPART_NA, 8, 8}, +}; + +/** Table 7-14 Macroblock types for B slices */ +inter_mbtype_info_t gas_ih264_b_mbtype_info[] = +{ + {0, MBPART_DIRECT, MBPART_NA, 8, 8, }, + {1, MBPART_L0, MBPART_NA, 16, 16, }, + {1, MBPART_L1, MBPART_NA, 16, 16, }, + {1, MBPART_BI, MBPART_NA, 16, 16, }, + {2, MBPART_L0, MBPART_L0, 16, 8, }, + {2, MBPART_L0, MBPART_L0, 8, 16, }, + {2, MBPART_L1, MBPART_L1, 16, 8, }, + {2, MBPART_L1, MBPART_L1, 8, 16, }, + {2, MBPART_L0, MBPART_L1, 16, 8, }, + {2, MBPART_L0, MBPART_L1, 8, 16, }, + {2, MBPART_L1, MBPART_L0, 16, 8, }, + {2, MBPART_L1, MBPART_L0, 8, 16, }, + {2, MBPART_L0, MBPART_BI, 16, 8, }, + {2, MBPART_L0, MBPART_BI, 8, 16, }, + {2, MBPART_L1, MBPART_BI, 16, 8, }, + {2, MBPART_L1, MBPART_BI, 8, 16, }, + {2, MBPART_BI, MBPART_L0, 16, 8, }, + {2, MBPART_BI, MBPART_L0, 8, 16, }, + {2, MBPART_BI, MBPART_L1, 16, 8, }, + {2, MBPART_BI, MBPART_L1, 8, 16, }, + {2, MBPART_BI, MBPART_BI, 16, 8, }, + {2, MBPART_BI, MBPART_BI, 8, 16, }, + {4, MBPART_NA, MBPART_NA, 8, 8, }, +}; + +/** Table 7-17 – Sub-macroblock types in P macroblocks */ +submbtype_info_t gas_ih264_p_submbtype_info[] = +{ + {1, MBPART_L0, 8, 8}, + {2, MBPART_L0, 8, 4}, + {2, MBPART_L0, 4, 8}, + {4, MBPART_L0, 4, 4}, +}; + +/** Table 7-18 – Sub-macroblock types in B macroblocks */ +submbtype_info_t gas_ih264_b_submbtype_info[] = +{ + {4, MBPART_DIRECT, 4, 4}, + {1, MBPART_L0, 8, 8}, + {1, MBPART_L1, 8, 8}, + {1, MBPART_BI, 8, 8}, + {2, MBPART_L0, 8, 4}, + {2, MBPART_L0, 4, 8}, + {2, MBPART_L1, 8, 4}, + {2, MBPART_L1, 4, 8}, + {2, MBPART_BI, 8, 4}, + {2, MBPART_BI, 4, 8}, + {4, MBPART_L0, 4, 4}, + {4, MBPART_L1, 4, 4}, + {4, MBPART_BI, 4, 4}, +}; + + + + +const UWORD8 gau1_ih264_inv_scan_prog4x4[] = +{ + 0, 1, 4, 8, + 5, 2, 3, 6, + 9, 12, 13, 10, + 7, 11, 14, 15 +}; + +const UWORD8 gau1_ih264_inv_scan_int4x4[] = +{ + 0, 4, 1, 8, + 12, 5, 9, 13, + 2, 6, 10, 14, + 3, 7, 11, 15 +}; + +/** Inverse scan tables for individual 4x4 blocks of 8x8 transform coeffs of CAVLC */ +/* progressive */ +const UWORD8 gau1_ih264_inv_scan_prog8x8_cavlc[64] = +{ + 0, 9, 17, 18, 12, 40, 27, 7, + 35, 57, 29, 30, 58, 38, 53, 47, + 1, 2, 24, 11, 19, 48, 20, 14, + 42, 50, 22, 37, 59, 31, 60, 55, + 8, 3, 32, 4, 26, 41, 13, 21, + 49, 43, 15, 44, 52, 39, 61, 62, + 16, 10, 25, 5, 33, 34, 6, 28, + 56, 36, 23, 51, 45, 46, 54, 63 +}; + +/* interlace */ +const UWORD8 gau1_ih264_inv_scan_int8x8_cavlc[64] = +{ + 0, 9, 2, 56, 18, 26, 34, 27, + 35, 28, 36, 29, 45, 7, 54, 39, + 8, 24, 25, 33, 41, 11, 42, 12, + 43, 13, 44, 14, 53, 15, 62, 47, + 16, 32, 40, 10, 49, 4, 50, 5, + 51, 6, 52, 22, 61, 38, 23, 55, + 1, 17, 48, 3, 57, 19, 58, 20, + 59, 21, 60, 37, 30, 46, 31, 63 +}; + + + +/*Inverse scan tables for individual 8x8 blocks of 8x8 transform coeffs of CABAC */ +/* progressive */ + +const UWORD8 gau1_ih264_inv_scan_prog8x8_cabac[64] = +{ + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63 +}; + + +/* interlace */ + +const UWORD8 gau1_ih264_inv_scan_int8x8_cabac[64] = +{ + 0, 8, 16, 1, 9, 24, 32, 17, + 2, 25, 40, 48, 56, 33, 10, 3, + 18, 41, 49, 57, 26, 11, 4, 19, + 34, 42, 50, 58, 27, 12, 5, 20, + 35, 43, 51, 59, 28, 13, 6, 21, + 36, 44, 52, 60, 29, 14, 22, 37, + 45, 53, 61, 30, 7, 15, 38, 46, + 54, 62, 23, 31, 39, 47, 55, 63 +}; + + +const UWORD8 *const gpau1_ih264_inv_scan8x8[] = +{ + gau1_ih264_inv_scan_prog8x8_cavlc, + gau1_ih264_inv_scan_int8x8_cavlc, + gau1_ih264_inv_scan_prog8x8_cabac, + gau1_ih264_inv_scan_int8x8_cabac +}; + +const UWORD8 *const gpau1_ih264_inv_scan4x4[] = +{ + gau1_ih264_inv_scan_prog4x4, + gau1_ih264_inv_scan_int4x4, +}; + +const UWORD8 gau1_ih264_8x8_subblk_idx[] = +{ + 0, 1, 4, 5, + 2, 3, 6, 7, + 8, 9, 12, 13, + 10, 11, 14, 15 +}; + + +/* Table 8-15 Chroma QP offset table */ +const UWORD8 gau1_ih264_chroma_qp[] = +{ + 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 29, 30, + 31, 32, 32, 33, 34, 34, 35, 35, + 36, 36, 37, 37, 37, 38, 38, 38, + 39, 39, 39, 39 +}; + + +/** +****************************************************************************** +* @brief look up table to compute neigbour availability of 4x4 blocks +* input : subblk idx, mb neighbor availability +* output : sub blk neighbor availability +* @remarks +****************************************************************************** +*/ +const UWORD8 gau1_ih264_4x4_ngbr_avbl[16][16] = +{ + { 0x0, 0x1, 0xc, 0x7, 0x1, 0x1, 0xf, 0x7, 0xc, 0xf, 0xc, 0x7, 0xf, 0x7, 0xf, 0x7 }, + { 0x1, 0x1, 0xf, 0x7, 0x1, 0x1, 0xf, 0x7, 0xf, 0xf, 0xf, 0x7, 0xf, 0x7, 0xf, 0x7 }, + { 0x2, 0x1, 0xc, 0x7, 0x1, 0x1, 0xf, 0x7, 0xc, 0xf, 0xc, 0x7, 0xf, 0x7, 0xf, 0x7 }, + { 0x3, 0x1, 0xf, 0x7, 0x1, 0x1, 0xf, 0x7, 0xf, 0xf, 0xf, 0x7, 0xf, 0x7, 0xf, 0x7 }, + + { 0xc, 0xf, 0xc, 0x7, 0xf, 0x7, 0xf, 0x7, 0xc, 0xf, 0xc, 0x7, 0xf, 0x7, 0xf, 0x7 }, + { 0xd, 0xf, 0xf, 0x7, 0xf, 0x7, 0xf, 0x7, 0xf, 0xf, 0xf, 0x7, 0xf, 0x7, 0xf, 0x7 }, + { 0xe, 0xf, 0xc, 0x7, 0xf, 0x7, 0xf, 0x7, 0xc, 0xf, 0xc, 0x7, 0xf, 0x7, 0xf, 0x7 }, + { 0xf, 0xf, 0xf, 0x7, 0xf, 0x7, 0xf, 0x7, 0xf, 0xf, 0xf, 0x7, 0xf, 0x7, 0xf, 0x7 }, + + { 0x0, 0x1, 0xc, 0x7, 0x1, 0x9, 0xf, 0x7, 0xc, 0xf, 0xc, 0x7, 0xf, 0x7, 0xf, 0x7 }, + { 0x1, 0x1, 0xf, 0x7, 0x1, 0x9, 0xf, 0x7, 0xf, 0xf, 0xf, 0x7, 0xf, 0x7, 0xf, 0x7 }, + { 0x2, 0x1, 0xc, 0x7, 0x1, 0x9, 0xf, 0x7, 0xc, 0xf, 0xc, 0x7, 0xf, 0x7, 0xf, 0x7 }, + { 0x3, 0x1, 0xf, 0x7, 0x1, 0x9, 0xf, 0x7, 0xf, 0xf, 0xf, 0x7, 0xf, 0x7, 0xf, 0x7 }, + + { 0xc, 0xf, 0xc, 0x7, 0xf, 0xf, 0xf, 0x7, 0xc, 0xf, 0xc, 0x7, 0xf, 0x7, 0xf, 0x7 }, + { 0xd, 0xf, 0xf, 0x7, 0xf, 0xf, 0xf, 0x7, 0xf, 0xf, 0xf, 0x7, 0xf, 0x7, 0xf, 0x7 }, + { 0xe, 0xf, 0xc, 0x7, 0xf, 0xf, 0xf, 0x7, 0xc, 0xf, 0xc, 0x7, 0xf, 0x7, 0xf, 0x7 }, + { 0xf, 0xf, 0xf, 0x7, 0xf, 0xf, 0xf, 0x7, 0xf, 0xf, 0xf, 0x7, 0xf, 0x7, 0xf, 0x7 }, +}; + + +/** +****************************************************************************** +* @brief look up table to compute neigbour availability of 8x8 blocks +* input : subblk idx, mb neighbor availability +* output : sub blk neighbor availability +* @remarks +****************************************************************************** +*/ +const UWORD8 gau1_ih264_8x8_ngbr_avbl[16][4] = +{ + { 0x0, 0x1, 0xc, 0x7 }, + { 0x1, 0x1, 0xf, 0x7 }, + { 0x2, 0x1, 0xc, 0x7 }, + { 0x3, 0x1, 0xf, 0x7 }, + + { 0xc, 0x7, 0xc, 0x7 }, + { 0xd, 0x7, 0xf, 0x7 }, + { 0xe, 0x7, 0xc, 0x7 }, + { 0xf, 0x7, 0xf, 0x7 }, + + { 0x0, 0x9, 0xc, 0x7 }, + { 0x1, 0x9, 0xf, 0x7 }, + { 0x2, 0x9, 0xc, 0x7 }, + { 0x3, 0x9, 0xf, 0x7 }, + + { 0xc, 0xf, 0xc, 0x7 }, + { 0xd, 0xf, 0xf, 0x7 }, + { 0xe, 0xf, 0xc, 0x7 }, + { 0xf, 0xf, 0xf, 0x7 }, +}; + +/** Table 7-3 Default intra 4x4 scaling list */ +const UWORD16 gau2_ih264_default_intra4x4_scaling_list[] = +{ + 6, 13, 13, 20, + 20, 20, 28, 28, + 28, 28, 32, 32, + 32, 37, 37, 42 +}; + +/** Table 7-3 Default inter 4x4 scaling list */ +const UWORD16 gau2_ih264_default_inter4x4_scaling_list[] = +{ + 10, 14, 14, 20, + 20, 20, 24, 24, + 24, 24, 27, 27, + 27, 30, 30, 34 +}; + +/* Inverse scanned output of gau2_ih264_default_intra4x4_scaling_list */ +const UWORD16 gau2_ih264_default_intra4x4_weight_scale[] = +{ + 6, 13, 20, 28, + 13, 20, 28, 32, + 20, 28, 32, 37, + 28, 32, 37, 42 +}; + +/* Inverse scanned output of gau2_ih264_default_inter4x4_scaling_list */ +const UWORD16 gau2_ih264_default_inter4x4_weight_scale[] = +{ + 10, 14, 20, 24, + 14, 20, 24, 27, + 20, 24, 27, 30, + 24, 27, 30, 34 +}; + +/** Table 7-4 Default intra 8x8 scaling list */ +const UWORD16 gau2_ih264_default_intra8x8_scaling_list[] = +{ + 6, 10, 10, 13, 11, 13, 16, 16, + 16, 16, 18, 18, 18, 18, 18, 23, + 23, 23, 23, 23, 23, 25, 25, 25, + 25, 25, 25, 25, 27, 27, 27, 27, + 27, 27, 27, 27, 29, 29, 29, 29, + 29, 29, 29, 31, 31, 31, 31, 31, + 31, 33, 33, 33, 33, 33, 36, 36, + 36, 36, 38, 38, 38, 40, 40, 42 +}; + +/** Table 7-4 Default inter 8x8 scaling list */ +const UWORD16 gau2_ih264_default_inter8x8_scaling_list[] = +{ + 9, 13, 13, 15, 13, 15, 17, 17, + 17, 17, 19, 19, 19, 19, 19, 21, + 21, 21, 21, 21, 21, 22, 22, 22, + 22, 22, 22, 22, 24, 24, 24, 24, + 24, 24, 24, 24, 25, 25, 25, 25, + 25, 25, 25, 27, 27, 27, 27, 27, + 27, 28, 28, 28, 28, 28, 30, 30, + 30, 30, 32, 32, 32, 33, 33, 35 +}; + +/* Inverse scanned output of gau2_ih264_default_intra8x8_scaling_list */ +const UWORD16 gau2_ih264_default_intra8x8_weight_scale[] = +{ + 6, 10, 13, 16, 18, 23, 25, 27, + 10, 11, 16, 18, 23, 25, 27, 29, + 13, 16, 18, 23, 25, 27, 29, 31, + 16, 18, 23, 25, 27, 29, 31, 33, + 18, 23, 25, 27, 29, 31, 33, 36, + 23, 25, 27, 29, 31, 33, 36, 38, + 25, 27, 29, 31, 33, 36, 38, 40, + 27, 29, 31, 33, 36, 38, 40, 42 +}; + +/* Inverse scanned output of gau2_ih264_default_inter8x8_scaling_list */ +const UWORD16 gau2_ih264_default_inter8x8_weight_scale[] = +{ + 9, 13, 15, 17, 19, 21, 22, 24, + 13, 13, 17, 19, 21, 22, 24, 25, + 15, 17, 19, 21, 22, 24, 25, 27, + 17, 19, 21, 22, 24, 25, 27, 28, + 19, 21, 22, 24, 25, 27, 28, 30, + 21, 22, 24, 25, 27, 28, 30, 32, + 22, 24, 25, 27, 28, 30, 32, 33, + 24, 25, 27, 28, 30, 32, 33, 35 +}; +/* Eq 7-8 Flat scaling matrix for 4x4 */ +const UWORD16 gau2_ih264_flat_4x4_weight_scale[] = +{ + 16, 16, 16, 16, + 16, 16, 16, 16, + 16, 16, 16, 16, + 16, 16, 16, 16 +}; + +/* Eq 7-9 Flat scaling matrix for 8x8 */ +const UWORD16 gau2_ih264_flat_8x8_weight_scale[] = +{ + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16 +}; + + +/** + ****************************************************************************** + * @brief Scale Table for inverse quantizing 4x4 subblock. To inverse quantize + * a given 4x4 quantized block, the coefficient at index location (i,j) is scaled + * by one of the constants in this table and right shift the result by abs (4 - + * floor(qp/6)), here qp is the quantization parameter used to quantize the mb. + * + * input : 16 * qp%6, index location (i,j) + * output : scale constant. + * + * @remarks 16 constants for each index position of the subblock and 6 for each + * qp%6 in the range 0-5 inclusive. + ****************************************************************************** + */ + +const UWORD16 gau2_ih264_iquant_scale_matrix_4x4[96] = +{ + 10, 13, 10, 13, + 13, 16, 13, 16, + 10, 13, 10, 13, + 13, 16, 13, 16, + + 11, 14, 11, 14, + 14, 18, 14, 18, + 11, 14, 11, 14, + 14, 18, 14, 18, + + 13, 16, 13, 16, + 16, 20, 16, 20, + 13, 16, 13, 16, + 16, 20, 16, 20, + + 14, 18, 14, 18, + 18, 23, 18, 23, + 14, 18, 14, 18, + 18, 23, 18, 23, + + 16, 20, 16, 20, + 20, 25, 20, 25, + 16, 20, 16, 20, + 20, 25, 20, 25, + + 18, 23, 18, 23, + 23, 29, 23, 29, + 18, 23, 18, 23, + 23, 29, 23, 29, + +}; + +/** + ****************************************************************************** + * @brief Scale Table for inverse quantizing 8x8 subblock. To inverse quantize + * a given 8x8 quantized block, the coefficient at index location (i,j) is scaled + * by one of the constants in this table and right shift the result by abs (4 - + * floor(qp/6)), here qp is the quantization parameter used to quantize the mb. + * + * input : qp%6, index location (i,j) + * output : scale constant. + * + * @remarks 64 constants for each index position of the subblock and 6 for each + * qp%6 in the range 0-5 inclusive. + ****************************************************************************** + */ +const UWORD16 gau2_ih264_iquant_scale_matrix_8x8 [384] = +{ + 20, 19, 25, 19, 20, 19, 25, 19, + 19, 18, 24, 18, 19, 18, 24, 18, + 25, 24, 32, 24, 25, 24, 32, 24, + 19, 18, 24, 18, 19, 18, 24, 18, + 20, 19, 25, 19, 20, 19, 25, 19, + 19, 18, 24, 18, 19, 18, 24, 18, + 25, 24, 32, 24, 25, 24, 32, 24, + 19, 18, 24, 18, 19, 18, 24, 18, + + 22, 21, 28, 21, 22, 21, 28, 21, + 21, 19, 26, 19, 21, 19, 26, 19, + 28, 26, 35, 26, 28, 26, 35, 26, + 21, 19, 26, 19, 21, 19, 26, 19, + 22, 21, 28, 21, 22, 21, 28, 21, + 21, 19, 26, 19, 21, 19, 26, 19, + 28, 26, 35, 26, 28, 26, 35, 26, + 21, 19, 26, 19, 21, 19, 26, 19, + + 26, 24, 33, 24, 26, 24, 33, 24, + 24, 23, 31, 23, 24, 23, 31, 23, + 33, 31, 42, 31, 33, 31, 42, 31, + 24, 23, 31, 23, 24, 23, 31, 23, + 26, 24, 33, 24, 26, 24, 33, 24, + 24, 23, 31, 23, 24, 23, 31, 23, + 33, 31, 42, 31, 33, 31, 42, 31, + 24, 23, 31, 23, 24, 23, 31, 23, + + 28, 26, 35, 26, 28, 26, 35, 26, + 26, 25, 33, 25, 26, 25, 33, 25, + 35, 33, 45, 33, 35, 33, 45, 33, + 26, 25, 33, 25, 26, 25, 33, 25, + 28, 26, 35, 26, 28, 26, 35, 26, + 26, 25, 33, 25, 26, 25, 33, 25, + 35, 33, 45, 33, 35, 33, 45, 33, + 26, 25, 33, 25, 26, 25, 33, 25, + + 32, 30, 40, 30, 32, 30, 40, 30, + 30, 28, 38, 28, 30, 28, 38, 28, + 40, 38, 51, 38, 40, 38, 51, 38, + 30, 28, 38, 28, 30, 28, 38, 28, + 32, 30, 40, 30, 32, 30, 40, 30, + 30, 28, 38, 28, 30, 28, 38, 28, + 40, 38, 51, 38, 40, 38, 51, 38, + 30, 28, 38, 28, 30, 28, 38, 28, + + 36, 34, 46, 34, 36, 34, 46, 34, + 34, 32, 43, 32, 34, 32, 43, 32, + 46, 43, 58, 43, 46, 43, 58, 43, + 34, 32, 43, 32, 34, 32, 43, 32, + 36, 34, 46, 34, 36, 34, 46, 34, + 34, 32, 43, 32, 34, 32, 43, 32, + 46, 43, 58, 43, 46, 43, 58, 43, + 34, 32, 43, 32, 34, 32, 43, 32, + +}; diff --git a/dependencies/ih264d/common/ih264_common_tables.h b/dependencies/ih264d/common/ih264_common_tables.h new file mode 100644 index 00000000..d4ec1470 --- /dev/null +++ b/dependencies/ih264d/common/ih264_common_tables.h @@ -0,0 +1,136 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** +******************************************************************************* +* @file +* ih264_common_tables.h +* +* @brief +* Common tables +* +* @author +* Harish +* +* @par List of Functions: +* +* @remarks +* None +* +******************************************************************************* +*/ + +#ifndef _IH264_COMMON_TABLES_H_ +#define _IH264_COMMON_TABLES_H_ + + +/*****************************************************************************/ +/* Structures */ +/*****************************************************************************/ + +/** +****************************************************************************** + * @brief level tables +****************************************************************************** + */ +typedef struct +{ + /* level */ + IH264_LEVEL_T u4_level_idc; + + /* max macroblock processing rate */ + UWORD32 u4_max_mbps; + + /* max frame size in mbs */ + UWORD32 u4_max_fs; + + /* max dpb size / 768 */ + UWORD32 u4_max_dpb_size; + + /* max bit rate */ + UWORD32 u4_max_br; + + /* max cpb size */ + UWORD32 u4_max_cpb_size; + + /* max vertical MV component range */ + UWORD32 u4_max_mv_y; + +}level_tables_t; + +/*****************************************************************************/ +/* Extern global declarations */ +/*****************************************************************************/ + +/** + ****************************************************************************** + * @brief while encoding, basing on the input configuration parameters, the + * the level of the bitstream is computed basing on the table below. + * input : table_idx + * output : level_idc or cpb size + * @remarks Table A-1 – level table limits + ****************************************************************************** + */ +extern const level_tables_t gas_ih264_lvl_tbl[16]; + +extern const WORD32 gai4_ih264_levels[]; +extern const WORD32 gai4_ih264_max_luma_pic_size[]; +extern const WORD32 gai4_ih264_max_wd_ht[]; +extern const WORD32 gai4_ih264_min_wd_ht[]; + +extern intra_mbtype_info_t gas_ih264_i_mbtype_info[]; +extern inter_mbtype_info_t gas_ih264_p_mbtype_info[]; +extern inter_mbtype_info_t gas_ih264_b_mbtype_info[]; +extern submbtype_info_t gas_ih264_p_submbtype_info[]; +extern submbtype_info_t gas_ih264_b_submbtype_info[]; + + +extern const UWORD8 gau1_ih264_inv_scan_prog4x4[]; +extern const UWORD8 gau1_ih264_inv_scan_int4x4[]; +extern const UWORD8 gau1_ih264_inv_scan_prog8x8_cavlc[64]; +extern const UWORD8 gau1_ih264_inv_scan_int8x8_cavlc[64]; +extern const UWORD8 gau1_ih264_inv_scan_prog8x8_cabac[64]; +extern const UWORD8 gau1_ih264_inv_scan_int8x8_cabac[64]; + +extern const UWORD8 *const gpau1_ih264_inv_scan8x8[]; +extern const UWORD8 *const gpau1_ih264_inv_scan4x4[]; + +extern const UWORD8 gau1_ih264_8x8_subblk_idx[]; + +extern const UWORD8 gau1_ih264_chroma_qp[]; + +extern const UWORD8 gau1_ih264_4x4_ngbr_avbl[16][16]; +extern const UWORD8 gau1_ih264_8x8_ngbr_avbl[16][4]; + + +extern const UWORD16 gau2_ih264_default_inter4x4_weight_scale[]; +extern const UWORD16 gau2_ih264_default_intra4x4_weight_scale[]; +extern const UWORD16 gau2_ih264_default_intra4x4_scaling_list[]; +extern const UWORD16 gau2_ih264_default_inter4x4_scaling_list[]; +extern const UWORD16 gau2_ih264_default_intra8x8_scaling_list[]; +extern const UWORD16 gau2_ih264_default_inter8x8_scaling_list[]; +extern const UWORD16 gau2_ih264_default_intra8x8_weight_scale[]; +extern const UWORD16 gau2_ih264_default_inter8x8_weight_scale[]; +extern const UWORD16 gau2_ih264_flat_4x4_weight_scale[]; +extern const UWORD16 gau2_ih264_flat_8x8_weight_scale[]; + +extern const UWORD16 gau2_ih264_iquant_scale_matrix_4x4 [96]; +extern const UWORD16 gau2_ih264_iquant_scale_matrix_8x8 [384]; + +#endif /*_IH264_COMMON_TABLES_H_*/ diff --git a/dependencies/ih264d/common/ih264_deblk_edge_filters.c b/dependencies/ih264d/common/ih264_deblk_edge_filters.c new file mode 100644 index 00000000..d2ffefdc --- /dev/null +++ b/dependencies/ih264d/common/ih264_deblk_edge_filters.c @@ -0,0 +1,2087 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/**************************************************************************** */ +/* */ +/* File Name : ih264_deblk_edge_filters.c */ +/* */ +/* Description : Contains function definitions for deblocking */ +/* */ +/* List of Functions : ih264_deblk_luma_vert_bs4() */ +/* ih264_deblk_luma_horz_bs4() */ +/* ih264_deblk_luma_vert_bslt4() */ +/* ih264_deblk_luma_horz_bslt4() */ +/* ih264_deblk_luma_vert_bs4_mbaff() */ +/* ih264_deblk_luma_vert_bslt4_mbaff() */ +/* ih264_deblk_chroma_vert_bs4_bp() */ +/* ih264_deblk_chroma_horz_bs4_bp() */ +/* ih264_deblk_chroma_vert_bslt4_bp() */ +/* ih264_deblk_chroma_horz_bslt4_bp() */ +/* ih264_deblk_chroma_vert_bs4_mbaff_bp() */ +/* ih264_deblk_chroma_vert_bslt4_mbaff_bp() */ +/* ih264_deblk_chroma_vert_bs4() */ +/* ih264_deblk_chroma_horz_bs4() */ +/* ih264_deblk_chroma_vert_bslt4() */ +/* ih264_deblk_chroma_horz_bslt4() */ +/* ih264_deblk_chroma_vert_bs4_mbaff() */ +/* ih264_deblk_chroma_vert_bslt4_mbaff() */ +/* */ +/* Issues / Problems : None */ +/* */ +/* Revision History : */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 28 11 2013 Ittiam Draft */ +/* 29 12 2014 Kaushik Added double-call vertical */ +/* Senthoor deblocking and high profile */ +/* deblocking functions */ +/* */ +/******************************************************************************/ + +/*****************************************************************************/ +/* File Includes */ +/*****************************************************************************/ + +/* System include files */ +#include + +/* User include files */ +#include "ih264_typedefs.h" +#include "ih264_platform_macros.h" +#include "ih264_deblk_edge_filters.h" +#include "ih264_macros.h" + +/*****************************************************************************/ +/* Function Definitions */ +/*****************************************************************************/ + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_deblk_luma_vert_bs4() */ +/* */ +/* Description : This function performs filtering of a luma block */ +/* vertical edge when the boundary strength is set to 4. */ +/* */ +/* Inputs : pu1_src - pointer to the src sample q0 */ +/* src_strd - source stride */ +/* alpha - alpha value for the boundary */ +/* beta - beta value for the boundary */ +/* */ +/* Globals : None */ +/* */ +/* Processing : This operation is described in Sec. 8.7.2.4 under the */ +/* title "Filtering process for edges for bS equal to 4" in */ +/* ITU T Rec H.264. */ +/* */ +/* Outputs : None */ +/* */ +/* Returns : None */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 28 11 2013 Ittiam Draft */ +/* */ +/*****************************************************************************/ +void ih264_deblk_luma_vert_bs4(UWORD8 *pu1_src, + WORD32 src_strd, + WORD32 alpha, + WORD32 beta) +{ + UWORD8 p3, p2, p1, p0, q0, q1, q2, q3; + WORD32 pos_p3, pos_p2, pos_p1, pos_p0; + WORD32 pos_q0, pos_q1, pos_q2,pos_q3; + UWORD8 a_p, a_q; /* threshold variables */ + WORD32 blk_strd = src_strd << 2; /* block_increment = src_strd * 4 */ + UWORD8 *pu1_src_temp; + WORD8 i = 0, edge; + + pos_q0 = 0; + pos_q1 = 1; + pos_q2 = 2; + pos_q3 = 3; + pos_p0 = -1; + pos_p1 = -2; + pos_p2 = -3; + pos_p3 = -4; + + for(edge = 0; edge < 4; edge++, pu1_src += blk_strd) + { + pu1_src_temp = pu1_src; + for(i = 0; i < 4; ++i, pu1_src_temp += src_strd) + { + q0 = pu1_src_temp[pos_q0]; + q1 = pu1_src_temp[pos_q1]; + p0 = pu1_src_temp[pos_p0]; + p1 = pu1_src_temp[pos_p1]; + + /* Filter Decision */ + if((ABS(p0 - q0) >= alpha) || + (ABS(q1 - q0) >= beta) || + (ABS(p1 - p0) >= beta)) + continue; + + p2 = pu1_src_temp[pos_p2]; + p3 = pu1_src_temp[pos_p3]; + q2 = pu1_src_temp[pos_q2]; + q3 = pu1_src_temp[pos_q3]; + + if(ABS(p0 - q0) < ((alpha >> 2) + 2)) + { + /* Threshold Variables */ + a_p = (UWORD8)ABS(p2 - p0); + a_q = (UWORD8)ABS(q2 - q0); + + if(a_p < beta) + { + /* p0', p1', p2' */ + pu1_src_temp[pos_p0] = ((p2 + X2(p1) + X2(p0) + X2(q0) + q1 + + 4) >> 3); + pu1_src_temp[pos_p1] = ((p2 + p1 + p0 + q0 + 2) >> 2); + pu1_src_temp[pos_p2] = + ((X2(p3) + X3(p2) + p1 + p0 + q0 + + 4) >> 3); + } + else + { + /* p0'*/ + pu1_src_temp[pos_p0] = ((X2(p1) + p0 + q1 + 2) >> 2); + } + + if(a_q < beta) + { + /* q0', q1', q2' */ + pu1_src_temp[pos_q0] = (p1 + X2(p0) + X2(q0) + X2(q1) + q2 + + 4) >> 3; + pu1_src_temp[pos_q1] = (p0 + q0 + q1 + q2 + 2) >> 2; + pu1_src_temp[pos_q2] = (X2(q3) + X3(q2) + q1 + q0 + p0 + 4) + >> 3; + } + else + { + /* q0'*/ + pu1_src_temp[pos_q0] = (X2(q1) + q0 + p1 + 2) >> 2; + } + } + else + { + /* p0', q0'*/ + pu1_src_temp[pos_p0] = ((X2(p1) + p0 + q1 + 2) >> 2); + pu1_src_temp[pos_q0] = (X2(q1) + q0 + p1 + 2) >> 2; + } + } + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_deblk_luma_horz_bs4() */ +/* */ +/* Description : This function performs filtering of a luma block */ +/* horizontal edge when the boundary strength is set to 4. */ +/* */ +/* Inputs : pu1_src - pointer to the src sample q0 */ +/* src_strd - source stride */ +/* alpha - alpha value for the boundary */ +/* beta - beta value for the boundary */ +/* */ +/* Globals : None */ +/* */ +/* Processing : This operation is described in Sec. 8.7.2.4 under the */ +/* title "Filtering process for edges for bS equal to 4" in */ +/* ITU T Rec H.264. */ +/* */ +/* Outputs : None */ +/* */ +/* Returns : None */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 28 11 2013 Ittiam Draft */ +/* */ +/*****************************************************************************/ +void ih264_deblk_luma_horz_bs4(UWORD8 *pu1_src, + WORD32 src_strd, + WORD32 alpha, + WORD32 beta) +{ + UWORD8 p3, p2, p1, p0, q0, q1, q2, q3; + WORD32 pos_p3, pos_p2, pos_p1, pos_p0, pos_q0, pos_q1, + pos_q2, pos_q3; + UWORD8 a_p, a_q; /* threshold variables */ + UWORD8 *pu1_p3; /* pointer to the src sample p3 */ + UWORD8 *pu1_p3_temp; + UWORD8 *pu1_src_temp; + WORD8 i = 0, edge; + + pu1_p3 = pu1_src - (src_strd << 2); + pos_q0 = 0; + pos_q1 = src_strd; + pos_q2 = X2(src_strd); + pos_q3 = X3(src_strd); + pos_p0 = X3(src_strd); + pos_p1 = X2(src_strd); + pos_p2 = src_strd; + pos_p3 = 0; + + for(edge = 0; edge < 4; edge++, pu1_src += 4, pu1_p3 += 4) + { + pu1_src_temp = pu1_src; + pu1_p3_temp = pu1_p3; + for(i = 0; i < 4; ++i, pu1_src_temp++, pu1_p3_temp++) + { + q0 = pu1_src_temp[pos_q0]; + q1 = pu1_src_temp[pos_q1]; + p0 = pu1_p3_temp[pos_p0]; + p1 = pu1_p3_temp[pos_p1]; + + /* Filter Decision */ + if((ABS(p0 - q0) >= alpha) || + (ABS(q1 - q0) >= beta) || + (ABS(p1 - p0) >= beta)) + continue; + + p2 = pu1_p3_temp[pos_p2]; + p3 = pu1_p3_temp[pos_p3]; + q2 = pu1_src_temp[pos_q2]; + q3 = pu1_src_temp[pos_q3]; + + if(ABS(p0 - q0) < ((alpha >> 2) + 2)) + { + /* Threshold Variables */ + a_p = ABS(p2 - p0); + a_q = ABS(q2 - q0); + + if((a_p < beta)) + { + /* p0', p1', p2' */ + pu1_p3_temp[pos_p0] = (p2 + X2(p1) + X2(p0) + X2(q0) + q1 + + 4) >> 3; + pu1_p3_temp[pos_p1] = (p2 + p1 + p0 + q0 + 2) >> 2; + pu1_p3_temp[pos_p2] = + (X2(p3) + X3(p2) + p1 + p0 + q0 + + 4) >> 3; + } + else + { + /* p0'*/ + pu1_p3_temp[pos_p0] = (X2(p1) + p0 + q1 + 2) >> 2; + } + + if(a_q < beta) + { + /* q0', q1', q2' */ + pu1_src_temp[pos_q0] = (p1 + X2(p0) + X2(q0) + X2(q1) + + q2 + 4) >> 3; + pu1_src_temp[pos_q1] = (p0 + q0 + q1 + q2 + 2) >> 2; + pu1_src_temp[pos_q2] = (X2(q3) + X3(q2) + q1 + q0 + p0 + + 4) >> 3; + } + else + { + /* q0'*/ + pu1_src_temp[pos_q0] = (X2(q1) + q0 + p1 + 2) >> 2; + } + } + else + { + /* p0', q0'*/ + pu1_p3_temp[pos_p0] = (X2(p1) + p0 + q1 + 2) >> 2; + pu1_src_temp[pos_q0] = (X2(q1) + q0 + p1 + 2) >> 2; + } + } + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_deblk_chroma_vert_bs4_bp() */ +/* */ +/* Description : This function performs filtering of a chroma block */ +/* vertical edge when the boundary strength is set to 4. */ +/* */ +/* Inputs : pu1_src - pointer to the src sample q0 of U */ +/* src_strd - source stride */ +/* alpha - alpha value for the boundary */ +/* beta - beta value for the boundary */ +/* */ +/* Globals : None */ +/* */ +/* Processing : This operation is described in Sec. 8.7.2.4 under the */ +/* title "Filtering process for edges for bS equal to 4" in */ +/* ITU T Rec H.264. */ +/* */ +/* Outputs : None */ +/* */ +/* Returns : None */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 28 11 2013 Ittiam Draft */ +/* */ +/*****************************************************************************/ +void ih264_deblk_chroma_vert_bs4_bp(UWORD8 *pu1_src, + WORD32 src_strd, + WORD32 alpha, + WORD32 beta) +{ + UWORD8 *pu1_src_u = pu1_src; /* pointer to the src sample q0 of U */ + UWORD8 *pu1_src_v = pu1_src + 1; /* pointer to the src sample q0 of V */ + UWORD8 p1_u, p0_u, q0_u, q1_u, p1_v, p0_v, q0_v, q1_v; + WORD32 blk_strd = src_strd << 1; /* block_increment = src_strd * 2 */ + WORD32 pos_p1, pos_p0, pos_q0, pos_q1; + UWORD8 *pu1_src_temp_u, *pu1_src_temp_v; + WORD8 i = 0, edge; + + pos_q0 = 0; + pos_q1 = 2; + pos_p0 = -2; + pos_p1 = -4; + + for(edge = 0; edge < 4; + edge++, pu1_src_u += blk_strd, pu1_src_v += blk_strd) + { + pu1_src_temp_u = pu1_src_u; + pu1_src_temp_v = pu1_src_v; + for(i = 0; i < 2; ++i, pu1_src_temp_u += src_strd, pu1_src_temp_v += + src_strd) + { + q0_u = pu1_src_temp_u[pos_q0]; + q1_u = pu1_src_temp_u[pos_q1]; + p0_u = pu1_src_temp_u[pos_p0]; + p1_u = pu1_src_temp_u[pos_p1]; + q0_v = pu1_src_temp_v[pos_q0]; + q1_v = pu1_src_temp_v[pos_q1]; + p0_v = pu1_src_temp_v[pos_p0]; + p1_v = pu1_src_temp_v[pos_p1]; + + /* Filter Decision */ + if((ABS(p0_u - q0_u) < alpha) && + (ABS(q1_u - q0_u) < beta) && + (ABS(p1_u - p0_u) < beta)) + { + /* p0' */ + pu1_src_temp_u[pos_p0] = ((X2(p1_u) + p0_u + q1_u + 2) >> 2); + /* q0' */ + pu1_src_temp_u[pos_q0] = (X2(q1_u) + q0_u + p1_u + 2) >> 2; + } + + /* Filter Decision */ + if((ABS(p0_v - q0_v) < alpha) && + (ABS(q1_v - q0_v) < beta) && + (ABS(p1_v - p0_v) < beta)) + { + /* p0' */ + pu1_src_temp_v[pos_p0] = ((X2(p1_v) + p0_v + q1_v + 2) >> 2); + /* q0' */ + pu1_src_temp_v[pos_q0] = (X2(q1_v) + q0_v + p1_v + 2) >> 2; + } + } + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_deblk_chroma_horz_bs4_bp() */ +/* */ +/* Description : This function performs filtering of a chroma block */ +/* horizontal edge when the boundary strength is set to 4. */ +/* */ +/* Inputs : pu1_src - pointer to the src sample q0 of U */ +/* src_strd - source stride */ +/* alpha - alpha value for the boundary */ +/* beta - beta value for the boundary */ +/* */ +/* Globals : None */ +/* */ +/* Processing : This operation is described in Sec. 8.7.2.4 under the */ +/* title "Filtering process for edges for bS equal to 4" in */ +/* ITU T Rec H.264. */ +/* */ +/* Outputs : None */ +/* */ +/* Returns : None */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 28 11 2013 Ittiam Draft */ +/* */ +/*****************************************************************************/ +void ih264_deblk_chroma_horz_bs4_bp(UWORD8 *pu1_src, + WORD32 src_strd, + WORD32 alpha, + WORD32 beta) +{ + UWORD8 *pu1_src_u = pu1_src; /* pointer to the src sample q0 of U */ + UWORD8 *pu1_src_v = pu1_src + 1; /* pointer to the src sample q0 of V */ + UWORD8 p1_u, p0_u, q0_u, q1_u, p1_v, p0_v, q0_v, q1_v; + WORD32 pos_p1, pos_p0, pos_q0, pos_q1; + UWORD8 *pu1_src_temp_u, *pu1_src_temp_v; + UWORD8 *pu1_p1_u; /* pointer to the src sample p1 of U */ + UWORD8 *pu1_p1_v; /* pointer to the src sample p1 of U */ + UWORD8 *pu1_p1_temp_u, *pu1_p1_temp_v; + WORD8 i = 0, edge; + + pu1_p1_u = pu1_src_u - (src_strd << 1); + pu1_p1_v = pu1_src_v - (src_strd << 1); + pos_q0 = 0; + pos_q1 = src_strd; + pos_p0 = src_strd; + pos_p1 = 0; + + for(edge = 0; edge < 4; edge++, pu1_src_u += 4, pu1_p1_u += 4, + pu1_src_v += 4, pu1_p1_v += 4) + { + pu1_src_temp_u = pu1_src_u; + pu1_p1_temp_u = pu1_p1_u; + pu1_src_temp_v = pu1_src_v; + pu1_p1_temp_v = pu1_p1_v; + for(i = 0; i < 2; ++i, pu1_src_temp_u += 2, pu1_p1_temp_u += 2, + pu1_src_temp_v += 2, pu1_p1_temp_v += 2) + { + q0_u = pu1_src_temp_u[pos_q0]; + q1_u = pu1_src_temp_u[pos_q1]; + p0_u = pu1_p1_temp_u[pos_p0]; + p1_u = pu1_p1_temp_u[pos_p1]; + + q0_v = pu1_src_temp_v[pos_q0]; + q1_v = pu1_src_temp_v[pos_q1]; + p0_v = pu1_p1_temp_v[pos_p0]; + p1_v = pu1_p1_temp_v[pos_p1]; + + /* Filter Decision */ + if((ABS(p0_u - q0_u) < alpha) && + (ABS(q1_u - q0_u) < beta) && + (ABS(p1_u - p0_u) < beta)) + { + /* p0' */ + pu1_p1_temp_u[pos_p0] = (X2(p1_u) + p0_u + q1_u + 2) >> 2; + /* q0' */ + pu1_src_temp_u[pos_q0] = (X2(q1_u) + q0_u + p1_u + 2) >> 2; + } + + /* Filter Decision */ + if((ABS(p0_v - q0_v) < alpha) && + (ABS(q1_v - q0_v) < beta) && + (ABS(p1_v - p0_v) < beta)) + { + /* p0' */ + pu1_p1_temp_v[pos_p0] = (X2(p1_v) + p0_v + q1_v + 2) >> 2; + /* q0' */ + pu1_src_temp_v[pos_q0] = (X2(q1_v) + q0_v + p1_v + 2) >> 2; + } + } + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_deblk_luma_vert_bslt4() */ +/* */ +/* Description : This function performs filtering of a luma block */ +/* vertical edge when the boundary strength is less than 4. */ +/* */ +/* Inputs : pu1_src - pointer to the src sample q0 */ +/* src_strd - source stride */ +/* alpha - alpha value for the boundary */ +/* beta - beta value for the boundary */ +/* u4_bs - packed Boundary strength array */ +/* pu1_cliptab - tc0_table */ +/* */ +/* Globals : None */ +/* */ +/* Processing : This operation is described in Sec. 8.7.2.3 under the */ +/* title "Filtering process for edges for bS less than 4" */ +/* in ITU T Rec H.264. */ +/* */ +/* Outputs : None */ +/* */ +/* Returns : None */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 28 11 2013 Ittiam Draft */ +/* */ +/*****************************************************************************/ +void ih264_deblk_luma_vert_bslt4(UWORD8 *pu1_src, + WORD32 src_strd, + WORD32 alpha, + WORD32 beta, + UWORD32 u4_bs, + const UWORD8 *pu1_cliptab) +{ + WORD8 i = 0, edge; + UWORD8 p2, p1, p0, q0, q1, q2; + WORD32 pos_p2, pos_p1, pos_p0, pos_q0, pos_q1, pos_q2; + UWORD8 a_p, a_q; /* threshold variables */ + WORD32 blk_strd = src_strd << 2; /* block_increment = src_strd * 4 */ + UWORD8 *pu1_src_temp; + WORD8 delta; + WORD8 tc; + WORD16 val; + UWORD8 tc0, u1_bs; + + pos_q0 = 0; + pos_q1 = 1; + pos_q2 = 2; + pos_p0 = -1; + pos_p1 = -2; + pos_p2 = -3; + + for(edge = 0; edge < 4; edge++, pu1_src += blk_strd) + { + pu1_src_temp = pu1_src; + /* Filter Decision */ + u1_bs = (UWORD8)((u4_bs >> ((3 - edge) << 3)) & 0x0ff); + if(!u1_bs) + continue; + /* tc0 */ + tc0 = pu1_cliptab[u1_bs]; + for(i = 0; i < 4; ++i, pu1_src_temp += src_strd) + { + q0 = pu1_src_temp[pos_q0]; + q1 = pu1_src_temp[pos_q1]; + p0 = pu1_src_temp[pos_p0]; + p1 = pu1_src_temp[pos_p1]; + + /* Filter Decision */ + if((ABS(p0 - q0) >= alpha) || + (ABS(q1 - q0) >= beta) || + (ABS(p1 - p0) >= beta)) + continue; + + q2 = pu1_src_temp[pos_q2]; + p2 = pu1_src_temp[pos_p2]; + + a_p = ABS(p2 - p0); + a_q = ABS(q2 - q0); + + /* tc */ + tc = tc0 + (a_p < beta) + (a_q < beta); + + val = ((((q0 - p0) << 2) + (p1 - q1) + 4) >> 3); + delta = CLIP3(-tc, tc, val); + + /* p0' */ + val = p0 + delta; + pu1_src_temp[pos_p0] = CLIP_U8(val); + /* q0' */ + val = q0 - delta; + pu1_src_temp[pos_q0] = CLIP_U8(val); + + /* Luma only */ + if(a_p < beta) + { + /* p1' */ + val = ((p2 + ((p0 + q0 + 1) >> 1) - (p1 << 1)) >> 1); + pu1_src_temp[pos_p1] += CLIP3(-tc0, tc0, val); + } + + if(a_q < beta) + { + /* q1' */ + val = ((q2 + ((p0 + q0 + 1) >> 1) - (q1 << 1)) >> 1); + pu1_src_temp[pos_q1] += CLIP3(-tc0, tc0, val); + } + } + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_deblk_chroma_vert_bslt4_bp() */ +/* */ +/* Description : This function performs filtering of a chroma block */ +/* vertical edge when the boundary strength is less than 4. */ +/* */ +/* Inputs : pu1_src - pointer to the src sample q0 of U */ +/* src_strd - source stride */ +/* alpha - alpha value for the boundary */ +/* beta - beta value for the boundary */ +/* u4_bs - packed Boundary strength array */ +/* pu1_cliptab - tc0_table */ +/* */ +/* Globals : None */ +/* */ +/* Processing : This operation is described in Sec. 8.7.2.3 under the */ +/* title "Filtering process for edges for bS less than 4" */ +/* in ITU T Rec H.264. */ +/* */ +/* Outputs : None */ +/* */ +/* Returns : None */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 28 11 2013 Ittiam Draft */ +/* */ +/*****************************************************************************/ +void ih264_deblk_chroma_vert_bslt4_bp(UWORD8 *pu1_src, + WORD32 src_strd, + WORD32 alpha, + WORD32 beta, + UWORD32 u4_bs, + const UWORD8 *pu1_cliptab) +{ + UWORD8 *pu1_src_u = pu1_src; /* Pointer to the src sample q0 of plane U*/ + UWORD8 *pu1_src_v = pu1_src + 1; /* Pointer to the src sample q0 of plane V*/ + UWORD8 p1_u, p0_u, q0_u, q1_u, p1_v, p0_v, q0_v, q1_v; + WORD32 blk_strd = src_strd << 1; /* block_increment = src_strd * (4 >> 1)*/ + WORD32 pos_p1, pos_p0, pos_q0, pos_q1; + UWORD8 *pu1_src_temp_u, *pu1_src_temp_v; + WORD8 i = 0, edge; + WORD8 delta; + WORD8 tc; + WORD16 val; + UWORD8 tc0, u1_bs; + + pos_q0 = 0; + pos_q1 = 2; + pos_p0 = -2; + pos_p1 = -4; + + for(edge = 0; edge < 4; + edge++, pu1_src_u += blk_strd, pu1_src_v += blk_strd) + { + pu1_src_temp_u = pu1_src_u; + pu1_src_temp_v = pu1_src_v; + /* Filter Decision */ + u1_bs = (UWORD8)((u4_bs >> ((3 - edge) << 3)) & 0x0ff); + if(!u1_bs) + continue; + /* tc0 */ + tc0 = pu1_cliptab[u1_bs]; + tc = tc0 + 1; + for(i = 0; i < 2; ++i, pu1_src_temp_u += src_strd, pu1_src_temp_v += + src_strd) + { + q0_u = pu1_src_temp_u[pos_q0]; + q1_u = pu1_src_temp_u[pos_q1]; + p0_u = pu1_src_temp_u[pos_p0]; + p1_u = pu1_src_temp_u[pos_p1]; + + q0_v = pu1_src_temp_v[pos_q0]; + q1_v = pu1_src_temp_v[pos_q1]; + p0_v = pu1_src_temp_v[pos_p0]; + p1_v = pu1_src_temp_v[pos_p1]; + + /* Filter Decision */ + if((ABS(p0_u - q0_u) < alpha) && + (ABS(q1_u - q0_u) < beta) && + (ABS(p1_u - p0_u) < beta)) + { + val = ((((q0_u - p0_u) << 2) + (p1_u - q1_u) + 4) >> 3); + delta = CLIP3(-tc, tc, val); + /* p0' */ + val = p0_u + delta; + pu1_src_temp_u[pos_p0] = CLIP_U8(val); + /* q0' */ + val = q0_u - delta; + pu1_src_temp_u[pos_q0] = CLIP_U8(val); + } + + /* Filter Decision */ + if((ABS(p0_v - q0_v) < alpha) && + (ABS(q1_v - q0_v) < beta) && + (ABS(p1_v - p0_v) < beta)) + { + val = ((((q0_v - p0_v) << 2) + (p1_v - q1_v) + 4) >> 3); + delta = CLIP3(-tc, tc, val); + /* p0' */ + val = p0_v + delta; + pu1_src_temp_v[pos_p0] = CLIP_U8(val); + /* q0' */ + val = q0_v - delta; + pu1_src_temp_v[pos_q0] = CLIP_U8(val); + } + } + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_deblk_luma_horz_bslt4() */ +/* */ +/* Description : This function performs filtering of a luma block */ +/* horizontal edge when boundary strength is less than 4. */ +/* */ +/* Inputs : pu1_src - pointer to the src sample q0 */ +/* src_strd - source stride */ +/* alpha - alpha value for the boundary */ +/* beta - beta value for the boundary */ +/* u4_bs - packed Boundary strength array */ +/* pu1_cliptab - tc0_table */ +/* */ +/* Globals : None */ +/* */ +/* Processing : This operation is described in Sec. 8.7.2.3 under the */ +/* title "Filtering process for edges for bS less than 4" */ +/* in ITU T Rec H.264. */ +/* */ +/* Outputs : None */ +/* */ +/* Returns : None */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 28 11 2013 Ittiam Draft */ +/* */ +/*****************************************************************************/ +void ih264_deblk_luma_horz_bslt4(UWORD8 *pu1_src, + WORD32 src_strd, + WORD32 alpha, + WORD32 beta, + UWORD32 u4_bs, + const UWORD8 *pu1_cliptab) +{ + UWORD8 p2, p1, p0, q0, q1, q2; + WORD32 pos_p2, pos_p1, pos_p0, pos_q0, pos_q1, pos_q2; + UWORD8 a_p, a_q; /* Threshold variables */ + UWORD8 *pu1_p2; /* Pointer to the src sample p2 */ + UWORD8 *pu1_p2_temp; + UWORD8 *pu1_src_temp; + WORD8 i = 0, edge; + WORD8 delta; + WORD8 tc; + WORD16 val; + UWORD8 tc0, u1_bs; + + pu1_p2 = pu1_src - (src_strd << 2); + pos_q0 = 0; + pos_q1 = src_strd; + pos_q2 = X2(src_strd); + pos_p0 = X3(src_strd); + pos_p1 = X2(src_strd); + pos_p2 = src_strd; + + for(edge = 0; edge < 4; edge++, pu1_src += 4, pu1_p2 += 4) + { + pu1_src_temp = pu1_src; + pu1_p2_temp = pu1_p2; + + /* Filter Decision */ + u1_bs = (UWORD8)((u4_bs >> ((3 - edge) << 3)) & 0x0ff); + if(!u1_bs) + continue; + /* tc0 */ + tc0 = pu1_cliptab[u1_bs]; + + for(i = 0; i < 4; ++i, pu1_src_temp++, pu1_p2_temp++) + { + q0 = pu1_src_temp[pos_q0]; + q1 = pu1_src_temp[pos_q1]; + p0 = pu1_p2_temp[pos_p0]; + p1 = pu1_p2_temp[pos_p1]; + + /* Filter Decision */ + if((ABS(p0 - q0) >= alpha) || + (ABS(q1 - q0) >= beta) || + (ABS(p1 - p0) >= beta)) + continue; + + q2 = pu1_src_temp[pos_q2]; + p2 = pu1_p2_temp[pos_p2]; + + a_p = ABS(p2 - p0); + a_q = ABS(q2 - q0); + + /* tc */ + tc = tc0 + (a_p < beta) + (a_q < beta); + val = ((((q0 - p0) << 2) + (p1 - q1) + 4) >> 3); + delta = CLIP3(-tc, tc, val); + /* p0' */ + val = p0 + delta; + pu1_p2_temp[pos_p0] = CLIP_U8(val); + /* q0' */ + val = q0 - delta; + pu1_src_temp[pos_q0] = CLIP_U8(val); + + /* Luma */ + if(a_p < beta) + { + /* p1' */ + val = ((p2 + ((p0 + q0 + 1) >> 1) - (p1 << 1)) >> 1); + pu1_p2_temp[pos_p1] += CLIP3(-tc0, tc0, val); + } + + if(a_q < beta) + { + /* q1' */ + val = ((q2 + ((p0 + q0 + 1) >> 1) - (q1 << 1)) >> 1); + pu1_src_temp[pos_q1] += CLIP3(-tc0, tc0, val); + } + } + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_deblk_chroma_horz_bslt4_bp() */ +/* */ +/* Description : This function performs filtering of a chroma block */ +/* horizontal edge when boundary strength is less than 4. */ +/* */ +/* Inputs : pu1_src - pointer to the src sample q0 of U */ +/* src_strd - source stride */ +/* alpha - alpha value for the boundary */ +/* beta - beta value for the boundary */ +/* u4_bs - packed Boundary strength array */ +/* pu1_cliptab - tc0_table */ +/* */ +/* Globals : None */ +/* */ +/* Processing : This operation is described in Sec. 8.7.2.3 under the */ +/* title "Filtering process for edges for bS less than 4" */ +/* in ITU T Rec H.264. */ +/* */ +/* Outputs : None */ +/* */ +/* Returns : None */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 28 11 2013 Ittiam Draft */ +/* */ +/*****************************************************************************/ +void ih264_deblk_chroma_horz_bslt4_bp(UWORD8 *pu1_src, + WORD32 src_strd, + WORD32 alpha, + WORD32 beta, + UWORD32 u4_bs, + const UWORD8 *pu1_cliptab) +{ + UWORD8 *pu1_src_u = pu1_src; /* Pointer to the src sample q0 of plane U*/ + UWORD8 *pu1_src_v = pu1_src + 1; /* Pointer to the src sample q0 of plane V*/ + UWORD8 p1_u, p0_u, q0_u, q1_u, p1_v, p0_v, q0_v, q1_v; + WORD32 pos_p1, pos_p0, pos_q0, pos_q1; + UWORD8 *pu1_src_temp_u, *pu1_src_temp_v; + UWORD8 *pu1_p1_u; /* Pointer to the src sample p1 of plane U*/ + UWORD8 *pu1_p1_v; /* Pointer to the src sample p1 of plane V*/ + UWORD8 *pu1_p1_temp_u, *pu1_p1_temp_v; + WORD8 i = 0, edge; + WORD8 delta; + WORD8 tc; + WORD16 val; + UWORD8 u1_bs; + UWORD8 tc0; + + pu1_p1_u = pu1_src_u - (src_strd << 1); + pu1_p1_v = pu1_src_v - (src_strd << 1); + pos_q0 = 0; + pos_q1 = src_strd; + pos_p0 = src_strd; + pos_p1 = 0; + + for(edge = 0; edge < 4; edge++, pu1_src_u += 4, pu1_p1_u += 4, + pu1_src_v += 4, pu1_p1_v += 4) + { + pu1_src_temp_u = pu1_src_u; + pu1_p1_temp_u = pu1_p1_u; + pu1_src_temp_v = pu1_src_v; + pu1_p1_temp_v = pu1_p1_v; + + /* Filter Decision */ + u1_bs = (UWORD8)((u4_bs >> ((3 - edge) << 3)) & 0x0ff); + if(!u1_bs) + continue; + /* tc0 */ + tc0 = pu1_cliptab[u1_bs]; + + for(i = 0; i < 2; ++i, pu1_src_temp_u += 2, pu1_p1_temp_u += 2, + pu1_src_temp_v += 2, pu1_p1_temp_v += 2) + { + q0_u = pu1_src_temp_u[pos_q0]; + q1_u = pu1_src_temp_u[pos_q1]; + p0_u = pu1_p1_temp_u[pos_p0]; + p1_u = pu1_p1_temp_u[pos_p1]; + + q0_v = pu1_src_temp_v[pos_q0]; + q1_v = pu1_src_temp_v[pos_q1]; + p0_v = pu1_p1_temp_v[pos_p0]; + p1_v = pu1_p1_temp_v[pos_p1]; + + /* tc */ + tc = tc0 + 1; + /* Filter Decision */ + if(ABS(p0_u - q0_u) < alpha && ABS(q1_u - q0_u) < beta + && ABS(p1_u - p0_u) < beta) + { + val = ((((q0_u - p0_u) << 2) + (p1_u - q1_u) + 4) >> 3); + delta = CLIP3(-tc, tc, val); + /* p0' */ + val = p0_u + delta; + pu1_p1_temp_u[pos_p0] = CLIP_U8(val); + /* q0' */ + val = q0_u - delta; + pu1_src_temp_u[pos_q0] = CLIP_U8(val); + } + /* Filter Decision */ + if(ABS(p0_v - q0_v) < alpha && ABS(q1_v - q0_v) < beta + && ABS(p1_v - p0_v) < beta) + { + val = ((((q0_v - p0_v) << 2) + (p1_v - q1_v) + 4) >> 3); + delta = CLIP3(-tc, tc, val); + /* p0' */ + val = p0_v + delta; + pu1_p1_temp_v[pos_p0] = CLIP_U8(val); + /* q0' */ + val = q0_v - delta; + pu1_src_temp_v[pos_q0] = CLIP_U8(val); + } + } + } +} + +/*****************************************************************************/ +/* Function Definitions for vertical edge deblocking for double-call */ +/*****************************************************************************/ + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_deblk_luma_vert_bs4_mbaff() */ +/* */ +/* Description : This function performs filtering of a luma block */ +/* vertical edge when boundary strength is set to 4. */ +/* */ +/* Inputs : pu1_src - pointer to the src sample q0 */ +/* src_strd - source stride */ +/* alpha - alpha value for the boundary */ +/* beta - beta value for the boundary */ +/* */ +/* Globals : None */ +/* */ +/* Processing : When the function is called twice, this operation is as */ +/* described in Sec. 8.7.2.3 under the title "Filtering */ +/* process for edges for bS equal to 4" in ITU T Rec H.264. */ +/* */ +/* Outputs : None */ +/* */ +/* Returns : None */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 29 12 2014 Kaushik Draft */ +/* Senthoor */ +/* */ +/*****************************************************************************/ +void ih264_deblk_luma_vert_bs4_mbaff(UWORD8 *pu1_src, + WORD32 src_strd, + WORD32 alpha, + WORD32 beta) +{ + UWORD8 p3, p2, p1, p0, q0, q1, q2, q3; + WORD32 pos_p3, pos_p2, pos_p1, pos_p0; + WORD32 pos_q0, pos_q1, pos_q2, pos_q3; + UWORD8 a_p, a_q; /* threshold variables */ + WORD32 blk_strd = src_strd << 1; /* block_increment = src_strd * 2 */ + UWORD8 *pu1_src_temp; + WORD8 i = 0, edge; + + pos_q0 = 0; + pos_q1 = 1; + pos_q2 = 2; + pos_q3 = 3; + pos_p0 = -1; + pos_p1 = -2; + pos_p2 = -3; + pos_p3 = -4; + + for(edge = 0; edge < 4; edge++, pu1_src += blk_strd) + { + pu1_src_temp = pu1_src; + for(i = 0; i < 2; ++i, pu1_src_temp += src_strd) + { + q0 = pu1_src_temp[pos_q0]; + q1 = pu1_src_temp[pos_q1]; + p0 = pu1_src_temp[pos_p0]; + p1 = pu1_src_temp[pos_p1]; + + /* Filter Decision */ + if((ABS(p0 - q0) >= alpha) || + (ABS(q1 - q0) >= beta) || + (ABS(p1 - p0) >= beta)) + continue; + + p2 = pu1_src_temp[pos_p2]; + p3 = pu1_src_temp[pos_p3]; + q2 = pu1_src_temp[pos_q2]; + q3 = pu1_src_temp[pos_q3]; + + if(ABS(p0 - q0) < ((alpha >> 2) + 2)) + { + /* Threshold Variables */ + a_p = (UWORD8)ABS(p2 - p0); + a_q = (UWORD8)ABS(q2 - q0); + + if(a_p < beta) + { + /* p0', p1', p2' */ + pu1_src_temp[pos_p0] = ((p2 + X2(p1) + X2(p0) + X2(q0) + q1 + + 4) >> 3); + pu1_src_temp[pos_p1] = ((p2 + p1 + p0 + q0 + 2) >> 2); + pu1_src_temp[pos_p2] = + ((X2(p3) + X3(p2) + p1 + p0 + q0 + + 4) >> 3); + } + else + { + /* p0'*/ + pu1_src_temp[pos_p0] = ((X2(p1) + p0 + q1 + 2) >> 2); + } + + if(a_q < beta) + { + /* q0', q1', q2' */ + pu1_src_temp[pos_q0] = (p1 + X2(p0) + X2(q0) + X2(q1) + q2 + + 4) >> 3; + pu1_src_temp[pos_q1] = (p0 + q0 + q1 + q2 + 2) >> 2; + pu1_src_temp[pos_q2] = (X2(q3) + X3(q2) + q1 + q0 + p0 + 4) + >> 3; + } + else + { + /* q0'*/ + pu1_src_temp[pos_q0] = (X2(q1) + q0 + p1 + 2) >> 2; + } + } + else + { + /* p0', q0'*/ + pu1_src_temp[pos_p0] = ((X2(p1) + p0 + q1 + 2) >> 2); + pu1_src_temp[pos_q0] = (X2(q1) + q0 + p1 + 2) >> 2; + } + } + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_deblk_chroma_vert_bs4_mbaff_bp() */ +/* */ +/* Description : This function performs filtering of a chroma block */ +/* vertical edge when boundary strength is set to 4. */ +/* */ +/* Inputs : pu1_src - pointer to the src sample q0 of U */ +/* src_strd - source stride */ +/* alpha - alpha value for the boundary */ +/* beta - beta value for the boundary */ +/* */ +/* Globals : None */ +/* */ +/* Processing : When the function is called twice, this operation is as */ +/* described in Sec. 8.7.2.3 under the title "Filtering */ +/* process for edges for bS equal to 4" in ITU T Rec H.264. */ +/* */ +/* Outputs : None */ +/* */ +/* Returns : None */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 29 12 2014 Kaushik Draft */ +/* Senthoor */ +/* */ +/*****************************************************************************/ +void ih264_deblk_chroma_vert_bs4_mbaff_bp(UWORD8 *pu1_src, + WORD32 src_strd, + WORD32 alpha, + WORD32 beta) +{ + UWORD8 *pu1_src_u = pu1_src; /* Pointer to the src sample q0 of U */ + UWORD8 *pu1_src_v = pu1_src + 1; /* Pointer to the src sample q0 of V */ + UWORD8 p1_u, p0_u, q0_u, q1_u, p1_v, p0_v, q0_v, q1_v; + WORD32 blk_strd = src_strd; + WORD32 pos_p1, pos_p0, pos_q0, pos_q1; + UWORD8 *pu1_src_temp_u, *pu1_src_temp_v; + WORD8 edge; + + pos_q0 = 0; + pos_q1 = 2; + pos_p0 = -2; + pos_p1 = -4; + + for(edge = 0; edge < 4; + edge++, pu1_src_u += blk_strd, pu1_src_v += blk_strd) + { + pu1_src_temp_u = pu1_src_u; + pu1_src_temp_v = pu1_src_v; + + q0_u = pu1_src_temp_u[pos_q0]; + q1_u = pu1_src_temp_u[pos_q1]; + p0_u = pu1_src_temp_u[pos_p0]; + p1_u = pu1_src_temp_u[pos_p1]; + q0_v = pu1_src_temp_v[pos_q0]; + q1_v = pu1_src_temp_v[pos_q1]; + p0_v = pu1_src_temp_v[pos_p0]; + p1_v = pu1_src_temp_v[pos_p1]; + + /* Filter Decision */ + if((ABS(p0_u - q0_u) < alpha) && + (ABS(q1_u - q0_u) < beta) && + (ABS(p1_u - p0_u) < beta)) + { + /* p0' */ + pu1_src_temp_u[pos_p0] = ((X2(p1_u) + p0_u + q1_u + 2) >> 2); + /* q0' */ + pu1_src_temp_u[pos_q0] = (X2(q1_u) + q0_u + p1_u + 2) >> 2; + } + + /* Filter Decision */ + if(ABS(p0_v - q0_v) < alpha && ABS(q1_v - q0_v) < beta + && ABS(p1_v - p0_v) < beta) + { + /* p0' */ + pu1_src_temp_v[pos_p0] = ((X2(p1_v) + p0_v + q1_v + 2) >> 2); + /* q0' */ + pu1_src_temp_v[pos_q0] = (X2(q1_v) + q0_v + p1_v + 2) >> 2; + } + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_deblk_luma_vert_bslt4_mbaff() */ +/* */ +/* Description : This function performs filtering of a luma block */ +/* vertical edge when boundary strength is less than 4. */ +/* */ +/* Inputs : pu1_src - pointer to the src sample q0 */ +/* src_strd - source stride */ +/* alpha - alpha value for the boundary */ +/* beta - beta value for the boundary */ +/* u4_bs - packed Boundary strength array */ +/* pu1_cliptab - tc0_table */ +/* */ +/* Globals : None */ +/* */ +/* Processing : When the function is called twice, this operation is as */ +/* described in Sec. 8.7.2.3 under the title "Filtering */ +/* process for edges for bS less than 4" in ITU T Rec H.264.*/ +/* */ +/* Outputs : None */ +/* */ +/* Returns : None */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 29 12 2014 Kaushik Draft */ +/* Senthoor */ +/* */ +/*****************************************************************************/ +void ih264_deblk_luma_vert_bslt4_mbaff(UWORD8 *pu1_src, + WORD32 src_strd, + WORD32 alpha, + WORD32 beta, + UWORD32 u4_bs, + const UWORD8 *pu1_cliptab) +{ + WORD8 i = 0, edge; + UWORD8 p2, p1, p0, q0, q1, q2; + WORD32 pos_p2, pos_p1, pos_p0, pos_q0, pos_q1, pos_q2; + UWORD8 a_p, a_q; /* Threshold variables */ + WORD32 blk_strd = src_strd << 1; /* block_increment = src_strd * 2 */ + UWORD8 *pu1_src_temp; + WORD8 delta; + WORD8 tc; + WORD16 val; + UWORD8 tc0, u1_bs; + + pos_q0 = 0; + pos_q1 = 1; + pos_q2 = 2; + pos_p0 = -1; + pos_p1 = -2; + pos_p2 = -3; + + for(edge = 0; edge < 4; edge++, pu1_src += blk_strd) + { + pu1_src_temp = pu1_src; + /* Filter Decision */ + u1_bs = (UWORD8)((u4_bs >> ((3 - edge) << 3)) & 0x0ff); + if(!u1_bs) + continue; + /* tc0 */ + tc0 = pu1_cliptab[u1_bs]; + for(i = 0; i < 2; ++i, pu1_src_temp += src_strd) + { + q0 = pu1_src_temp[pos_q0]; + q1 = pu1_src_temp[pos_q1]; + p0 = pu1_src_temp[pos_p0]; + p1 = pu1_src_temp[pos_p1]; + + /* Filter Decision */ + if((ABS(p0 - q0) >= alpha) || + (ABS(q1 - q0) >= beta) || + (ABS(p1 - p0) >= beta)) + continue; + + q2 = pu1_src_temp[pos_q2]; + p2 = pu1_src_temp[pos_p2]; + + a_p = ABS(p2 - p0); + a_q = ABS(q2 - q0); + + /* tc */ + tc = tc0 + (a_p < beta) + (a_q < beta); + + val = ((((q0 - p0) << 2) + (p1 - q1) + 4) >> 3); + delta = CLIP3(-tc, tc, val); + /* p0' */ + val = p0 + delta; + pu1_src_temp[pos_p0] = CLIP_U8(val); + /* q0' */ + val = q0 - delta; + pu1_src_temp[pos_q0] = CLIP_U8(val); + + /* Luma only */ + if(a_p < beta) + { + /* p1' */ + val = ((p2 + ((p0 + q0 + 1) >> 1) - (p1 << 1)) >> 1); + pu1_src_temp[pos_p1] += CLIP3(-tc0, tc0, val); + } + + if(a_q < beta) + { + /* q1' */ + val = ((q2 + ((p0 + q0 + 1) >> 1) - (q1 << 1)) >> 1); + pu1_src_temp[pos_q1] += CLIP3(-tc0, tc0, val); + } + } + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_deblk_chroma_vert_bslt4_mbaff_bp() */ +/* */ +/* Description : This function performs filtering of a chroma block */ +/* vertical edge when boundary strength is less than 4. */ +/* */ +/* Inputs : pu1_src - pointer to the src sample q0 of U */ +/* src_strd - source stride */ +/* alpha - alpha value for the boundary */ +/* beta - beta value for the boundary */ +/* u4_bs - packed Boundary strength array */ +/* pu1_cliptab - tc0_table */ +/* */ +/* Globals : None */ +/* */ +/* Processing : When the function is called twice, this operation is as */ +/* described in Sec. 8.7.2.3 under the title "Filtering */ +/* process for edges for bS less than 4" in ITU T Rec H.264.*/ +/* */ +/* Outputs : None */ +/* */ +/* Returns : None */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 29 12 2014 Kaushik Draft */ +/* Senthoor */ +/* */ +/*****************************************************************************/ +void ih264_deblk_chroma_vert_bslt4_mbaff_bp(UWORD8 *pu1_src, + WORD32 src_strd, + WORD32 alpha, + WORD32 beta, + UWORD32 u4_bs, + const UWORD8 *pu1_cliptab) +{ + UWORD8 *pu1_src_u = pu1_src; /* Pointer to the src sample q0 of plane U*/ + UWORD8 *pu1_src_v = pu1_src + 1; /* Pointer to the src sample q0 of plane V*/ + UWORD8 p1_u, p0_u, q0_u, q1_u, p1_v, p0_v, q0_v, q1_v; + WORD32 blk_strd = src_strd; + WORD32 pos_p1, pos_p0, pos_q0, pos_q1; + UWORD8 *pu1_src_temp_u, *pu1_src_temp_v; + WORD8 edge; + WORD8 delta; + WORD8 tc; + WORD16 val; + UWORD8 tc0, u1_bs; + + pos_q0 = 0; + pos_q1 = 2; + pos_p0 = -2; + pos_p1 = -4; + + for(edge = 0; edge < 4; + edge++, pu1_src_u += blk_strd, pu1_src_v += blk_strd) + { + pu1_src_temp_u = pu1_src_u; + pu1_src_temp_v = pu1_src_v; + /* Filter Decision */ + u1_bs = (UWORD8)((u4_bs >> ((3 - edge) << 3)) & 0x0ff); + if(!u1_bs) + continue; + /* tc0 */ + tc0 = pu1_cliptab[u1_bs]; + tc = tc0 + 1; + + q0_u = pu1_src_temp_u[pos_q0]; + q1_u = pu1_src_temp_u[pos_q1]; + p0_u = pu1_src_temp_u[pos_p0]; + p1_u = pu1_src_temp_u[pos_p1]; + + q0_v = pu1_src_temp_v[pos_q0]; + q1_v = pu1_src_temp_v[pos_q1]; + p0_v = pu1_src_temp_v[pos_p0]; + p1_v = pu1_src_temp_v[pos_p1]; + + /* Filter Decision */ + if((ABS(p0_u - q0_u) < alpha) && + (ABS(q1_u - q0_u) < beta) && + (ABS(p1_u - p0_u) < beta)) + { + val = ((((q0_u - p0_u) << 2) + (p1_u - q1_u) + 4) >> 3); + delta = CLIP3(-tc, tc, val); + /* p0' */ + val = p0_u + delta; + pu1_src_temp_u[pos_p0] = CLIP_U8(val); + /* q0' */ + val = q0_u - delta; + pu1_src_temp_u[pos_q0] = CLIP_U8(val); + } + + /* Filter Decision */ + if((ABS(p0_v - q0_v) < alpha) && + (ABS(q1_v - q0_v) < beta) && + (ABS(p1_v - p0_v) < beta)) + { + val = ((((q0_v - p0_v) << 2) + (p1_v - q1_v) + 4) >> 3); + delta = CLIP3(-tc, tc, val); + /* p0' */ + val = p0_v + delta; + pu1_src_temp_v[pos_p0] = CLIP_U8(val); + /* q0' */ + val = q0_v - delta; + pu1_src_temp_v[pos_q0] = CLIP_U8(val); + } + } +} + +/*****************************************************************************/ +/* Function Definitions for chroma deblocking in high profile */ +/*****************************************************************************/ + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_deblk_chroma_vert_bs4() */ +/* */ +/* Description : This function performs filtering of a chroma block */ +/* vertical edge when the boundary strength is set to 4 in */ +/* high profile. */ +/* */ +/* Inputs : pu1_src - pointer to the src sample q0 of U */ +/* src_strd - source stride */ +/* alpha_cb - alpha value for the boundary in U */ +/* beta_cb - beta value for the boundary in U */ +/* alpha_cr - alpha value for the boundary in V */ +/* beta_cr - beta value for the boundary in V */ +/* */ +/* Globals : None */ +/* */ +/* Processing : This operation is described in Sec. 8.7.2.4 under the */ +/* title "Filtering process for edges for bS equal to 4" in */ +/* ITU T Rec H.264 with alpha and beta values different in */ +/* U and V. */ +/* */ +/* Outputs : None */ +/* */ +/* Returns : None */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 29 12 2014 Kaushik Draft */ +/* Senthoor */ +/* */ +/*****************************************************************************/ +void ih264_deblk_chroma_vert_bs4(UWORD8 *pu1_src, + WORD32 src_strd, + WORD32 alpha_cb, + WORD32 beta_cb, + WORD32 alpha_cr, + WORD32 beta_cr) +{ + UWORD8 *pu1_src_u = pu1_src; /* Pointer to the src sample q0 of U */ + UWORD8 *pu1_src_v = pu1_src + 1; /* Pointer to the src sample q0 of V */ + UWORD8 p1_u, p0_u, q0_u, q1_u, p1_v, p0_v, q0_v, q1_v; + WORD32 blk_strd = src_strd << 1; /* block_increment = src_strd * 2*/ + WORD32 pos_p1, pos_p0, pos_q0, pos_q1; + UWORD8 *pu1_src_temp_u, *pu1_src_temp_v; + WORD8 i = 0, edge; + + pos_q0 = 0; + pos_q1 = 2; + pos_p0 = -2; + pos_p1 = -4; + + for(edge = 0; edge < 4; + edge++, pu1_src_u += blk_strd, pu1_src_v += blk_strd) + { + pu1_src_temp_u = pu1_src_u; + pu1_src_temp_v = pu1_src_v; + for(i = 0; i < 2; ++i, pu1_src_temp_u += src_strd, pu1_src_temp_v += + src_strd) + { + q0_u = pu1_src_temp_u[pos_q0]; + q1_u = pu1_src_temp_u[pos_q1]; + p0_u = pu1_src_temp_u[pos_p0]; + p1_u = pu1_src_temp_u[pos_p1]; + q0_v = pu1_src_temp_v[pos_q0]; + q1_v = pu1_src_temp_v[pos_q1]; + p0_v = pu1_src_temp_v[pos_p0]; + p1_v = pu1_src_temp_v[pos_p1]; + + /* Filter Decision */ + if((ABS(p0_u - q0_u) < alpha_cb) && + (ABS(q1_u - q0_u) < beta_cb) && + (ABS(p1_u - p0_u) < beta_cb)) + { + /* p0' */ + pu1_src_temp_u[pos_p0] = ((X2(p1_u) + p0_u + q1_u + 2) >> 2); + /* q0' */ + pu1_src_temp_u[pos_q0] = (X2(q1_u) + q0_u + p1_u + 2) >> 2; + } + + /* Filter Decision */ + if((ABS(p0_v - q0_v) < alpha_cr) && + (ABS(q1_v - q0_v) < beta_cr) && + (ABS(p1_v - p0_v) < beta_cr)) + { + /* p0' */ + pu1_src_temp_v[pos_p0] = ((X2(p1_v) + p0_v + q1_v + 2) >> 2); + /* q0' */ + pu1_src_temp_v[pos_q0] = (X2(q1_v) + q0_v + p1_v + 2) >> 2; + } + } + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_deblk_chroma_horz_bs4() */ +/* */ +/* Description : This function performs filtering of a chroma block */ +/* horizontal edge when the boundary strength is set to 4 */ +/* in high profile. */ +/* */ +/* Inputs : pu1_src - pointer to the src sample q0 of U */ +/* src_strd - source stride */ +/* alpha_cb - alpha value for the boundary in U */ +/* beta_cb - beta value for the boundary in U */ +/* alpha_cr - alpha value for the boundary in V */ +/* beta_cr - beta value for the boundary in V */ +/* */ +/* Globals : None */ +/* */ +/* Processing : This operation is described in Sec. 8.7.2.4 under the */ +/* title "Filtering process for edges for bS equal to 4" in */ +/* ITU T Rec H.264 with alpha and beta values different in */ +/* U and V. */ +/* */ +/* Outputs : None */ +/* */ +/* Returns : None */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 29 12 2014 Kaushik Draft */ +/* Senthoor */ +/* */ +/*****************************************************************************/ +void ih264_deblk_chroma_horz_bs4(UWORD8 *pu1_src, + WORD32 src_strd, + WORD32 alpha_cb, + WORD32 beta_cb, + WORD32 alpha_cr, + WORD32 beta_cr) +{ + UWORD8 *pu1_src_u = pu1_src; /* Pointer to the src sample q0 of U */ + UWORD8 *pu1_src_v = pu1_src + 1; /* Pointer to the src sample q0 of V */ + UWORD8 p1_u, p0_u, q0_u, q1_u, p1_v, p0_v, q0_v, q1_v; + WORD32 pos_p1, pos_p0, pos_q0, pos_q1; + UWORD8 *pu1_src_temp_u, *pu1_src_temp_v; + UWORD8 *pu1_p1_u; /* Pointer to the src sample p1 of U */ + UWORD8 *pu1_p1_v; /* Pointer to the src sample p1 of U */ + UWORD8 *pu1_p1_temp_u, *pu1_p1_temp_v; + WORD8 i = 0, edge; + + pu1_p1_u = pu1_src_u - (src_strd << 1); + pu1_p1_v = pu1_src_v - (src_strd << 1); + pos_q0 = 0; + pos_q1 = src_strd; + pos_p0 = src_strd; + pos_p1 = 0; + + for(edge = 0; edge < 4; edge++, pu1_src_u += 4, pu1_p1_u += 4, pu1_src_v += + 4, pu1_p1_v += 4) + { + pu1_src_temp_u = pu1_src_u; + pu1_p1_temp_u = pu1_p1_u; + pu1_src_temp_v = pu1_src_v; + pu1_p1_temp_v = pu1_p1_v; + for(i = 0; i < 2; ++i, pu1_src_temp_u += 2, pu1_p1_temp_u += 2, + pu1_src_temp_v += 2, pu1_p1_temp_v += 2) + { + q0_u = pu1_src_temp_u[pos_q0]; + q1_u = pu1_src_temp_u[pos_q1]; + p0_u = pu1_p1_temp_u[pos_p0]; + p1_u = pu1_p1_temp_u[pos_p1]; + + q0_v = pu1_src_temp_v[pos_q0]; + q1_v = pu1_src_temp_v[pos_q1]; + p0_v = pu1_p1_temp_v[pos_p0]; + p1_v = pu1_p1_temp_v[pos_p1]; + + /* Filter Decision */ + if(ABS(p0_u - q0_u) < alpha_cb && ABS(q1_u - q0_u) < beta_cb + && ABS(p1_u - p0_u) < beta_cb) + { + /* p0' */ + pu1_p1_temp_u[pos_p0] = (X2(p1_u) + p0_u + q1_u + 2) >> 2; + /* q0' */ + pu1_src_temp_u[pos_q0] = (X2(q1_u) + q0_u + p1_u + 2) >> 2; + } + + /* Filter Decision */ + if(ABS(p0_v - q0_v) < alpha_cr && ABS(q1_v - q0_v) < beta_cr + && ABS(p1_v - p0_v) < beta_cr) + { + /* p0' */ + pu1_p1_temp_v[pos_p0] = (X2(p1_v) + p0_v + q1_v + 2) >> 2; + /* q0' */ + pu1_src_temp_v[pos_q0] = (X2(q1_v) + q0_v + p1_v + 2) >> 2; + } + } + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_deblk_chroma_vert_bslt4() */ +/* */ +/* Description : This function performs filtering of a chroma block */ +/* vertical edge when the boundary strength is less than 4 */ +/* in high profile. */ +/* */ +/* Inputs : pu1_src - pointer to the src sample q0 of U */ +/* src_strd - source stride */ +/* alpha_cb - alpha value for the boundary in U */ +/* beta_cb - beta value for the boundary in U */ +/* alpha_cr - alpha value for the boundary in V */ +/* beta_cr - beta value for the boundary in V */ +/* u4_bs - packed Boundary strength array */ +/* pu1_cliptab_cb - tc0_table for U */ +/* pu1_cliptab_cr - tc0_table for V */ +/* */ +/* Globals : None */ +/* */ +/* Processing : This operation is described in Sec. 8.7.2.3 under the */ +/* title "Filtering process for edges for bS less than 4" */ +/* in ITU T Rec H.264 with alpha and beta values different */ +/* in U and V. */ +/* */ +/* Outputs : None */ +/* */ +/* Returns : None */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 29 12 2014 Kaushik Draft */ +/* Senthoor */ +/* */ +/*****************************************************************************/ +void ih264_deblk_chroma_vert_bslt4(UWORD8 *pu1_src, + WORD32 src_strd, + WORD32 alpha_cb, + WORD32 beta_cb, + WORD32 alpha_cr, + WORD32 beta_cr, + UWORD32 u4_bs, + const UWORD8 *pu1_cliptab_cb, + const UWORD8 *pu1_cliptab_cr) +{ + UWORD8 *pu1_src_u = pu1_src; /* Pointer to the src sample q0 of plane U*/ + UWORD8 *pu1_src_v = pu1_src + 1; /* Pointer to the src sample q0 of plane V*/ + UWORD8 p1_u, p0_u, q0_u, q1_u, p1_v, p0_v, q0_v, q1_v; + WORD32 blk_strd = src_strd << 1; /* block_increment = src_strd * 2 */ + WORD32 pos_p1, pos_p0, pos_q0, pos_q1; + UWORD8 *pu1_src_temp_u, *pu1_src_temp_v; + WORD8 i = 0, edge; + WORD8 delta; + WORD8 tcb, tcr; + WORD16 val; + UWORD8 tcb0, tcr0, u1_bs; + + pos_q0 = 0; + pos_q1 = 2; + pos_p0 = -2; + pos_p1 = -4; + + for(edge = 0; edge < 4; + edge++, pu1_src_u += blk_strd, pu1_src_v += blk_strd) + { + pu1_src_temp_u = pu1_src_u; + pu1_src_temp_v = pu1_src_v; + /* Filter Decision */ + u1_bs = (UWORD8)((u4_bs >> ((3 - edge) << 3)) & 0x0ff); + if(!u1_bs) + continue; + /* tc0 */ + tcb0 = pu1_cliptab_cb[u1_bs]; + tcr0 = pu1_cliptab_cr[u1_bs]; + tcb = tcb0 + 1; + tcr = tcr0 + 1; + for(i = 0; i < 2; ++i, pu1_src_temp_u += src_strd, pu1_src_temp_v += + src_strd) + { + q0_u = pu1_src_temp_u[pos_q0]; + q1_u = pu1_src_temp_u[pos_q1]; + p0_u = pu1_src_temp_u[pos_p0]; + p1_u = pu1_src_temp_u[pos_p1]; + + q0_v = pu1_src_temp_v[pos_q0]; + q1_v = pu1_src_temp_v[pos_q1]; + p0_v = pu1_src_temp_v[pos_p0]; + p1_v = pu1_src_temp_v[pos_p1]; + + /* Filter Decision */ + if(ABS(p0_u - q0_u) < alpha_cb && ABS(q1_u - q0_u) < beta_cb + && ABS(p1_u - p0_u) < beta_cb) + { + val = ((((q0_u - p0_u) << 2) + (p1_u - q1_u) + 4) >> 3); + delta = CLIP3(-tcb, tcb, val); + /* p0' */ + val = p0_u + delta; + pu1_src_temp_u[pos_p0] = CLIP_U8(val); + /* q0' */ + val = q0_u - delta; + pu1_src_temp_u[pos_q0] = CLIP_U8(val); + } + + /* Filter Decision */ + if(ABS(p0_v - q0_v) < alpha_cr && ABS(q1_v - q0_v) < beta_cr + && ABS(p1_v - p0_v) < beta_cr) + { + val = ((((q0_v - p0_v) << 2) + (p1_v - q1_v) + 4) >> 3); + delta = CLIP3(-tcr, tcr, val); + /* p0' */ + val = p0_v + delta; + pu1_src_temp_v[pos_p0] = CLIP_U8(val); + /* q0' */ + val = q0_v - delta; + pu1_src_temp_v[pos_q0] = CLIP_U8(val); + } + } + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_deblk_chroma_horz_bslt4() */ +/* */ +/* Description : This function performs filtering of a chroma block */ +/* horizontal edge when the boundary strength is less than */ +/* 4 in high profile. */ +/* */ +/* Inputs : pu1_src - pointer to the src sample q0 of U */ +/* src_strd - source stride */ +/* alpha_cb - alpha value for the boundary in U */ +/* beta_cb - beta value for the boundary in U */ +/* alpha_cr - alpha value for the boundary in V */ +/* beta_cr - beta value for the boundary in V */ +/* u4_bs - packed Boundary strength array */ +/* pu1_cliptab_cb - tc0_table for U */ +/* pu1_cliptab_cr - tc0_table for V */ +/* */ +/* Globals : None */ +/* */ +/* Processing : This operation is described in Sec. 8.7.2.3 under the */ +/* title "Filtering process for edges for bS less than 4" */ +/* in ITU T Rec H.264 with alpha and beta values different */ +/* in U and V. */ +/* */ +/* Outputs : None */ +/* */ +/* Returns : None */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 29 12 2014 Kaushik Draft */ +/* Senthoor */ +/* */ +/*****************************************************************************/ +void ih264_deblk_chroma_horz_bslt4(UWORD8 *pu1_src, + WORD32 src_strd, + WORD32 alpha_cb, + WORD32 beta_cb, + WORD32 alpha_cr, + WORD32 beta_cr, + UWORD32 u4_bs, + const UWORD8 *pu1_cliptab_cb, + const UWORD8 *pu1_cliptab_cr) +{ + UWORD8 *pu1_src_u = pu1_src; /* Pointer to the src sample q0 of plane U*/ + UWORD8 *pu1_src_v = pu1_src + 1; /* Pointer to the src sample q0 of plane V*/ + UWORD8 p1_u, p0_u, q0_u, q1_u, p1_v, p0_v, q0_v, q1_v; + WORD32 pos_p1, pos_p0, pos_q0, pos_q1; + UWORD8 *pu1_src_temp_u, *pu1_src_temp_v; + UWORD8 *pu1_p1_u; /* Pointer to the src sample p1 of plane U*/ + UWORD8 *pu1_p1_v; /* Pointer to the src sample p1 of plane V*/ + UWORD8 *pu1_p1_temp_u, *pu1_p1_temp_v; + WORD8 i = 0, edge; + WORD8 delta; + WORD8 tcb, tcr; + WORD16 val; + UWORD8 u1_bs; + UWORD8 tcb0, tcr0; + + pu1_p1_u = pu1_src_u - (src_strd << 1); + pu1_p1_v = pu1_src_v - (src_strd << 1); + pos_q0 = 0; + pos_q1 = src_strd; + pos_p0 = src_strd; + pos_p1 = 0; + + for(edge = 0; edge < 4; edge++, pu1_src_u += 4, pu1_p1_u += 4, + pu1_src_v += 4, pu1_p1_v += 4) + { + pu1_src_temp_u = pu1_src_u; + pu1_p1_temp_u = pu1_p1_u; + pu1_src_temp_v = pu1_src_v; + pu1_p1_temp_v = pu1_p1_v; + + /* Filter Decision */ + u1_bs = (UWORD8)((u4_bs >> ((3 - edge) << 3)) & 0x0ff); + if(!u1_bs) + continue; + /* tc0 */ + tcb0 = pu1_cliptab_cb[u1_bs]; + tcr0 = pu1_cliptab_cr[u1_bs]; + + for(i = 0; i < 2; ++i, pu1_src_temp_u += 2, pu1_p1_temp_u += 2, + pu1_src_temp_v += 2, pu1_p1_temp_v += 2) + { + q0_u = pu1_src_temp_u[pos_q0]; + q1_u = pu1_src_temp_u[pos_q1]; + p0_u = pu1_p1_temp_u[pos_p0]; + p1_u = pu1_p1_temp_u[pos_p1]; + + q0_v = pu1_src_temp_v[pos_q0]; + q1_v = pu1_src_temp_v[pos_q1]; + p0_v = pu1_p1_temp_v[pos_p0]; + p1_v = pu1_p1_temp_v[pos_p1]; + + /* tc */ + tcb = tcb0 + 1; + tcr = tcr0 + 1; + /* Filter Decision */ + if(ABS(p0_u - q0_u) < alpha_cb && ABS(q1_u - q0_u) < beta_cb + && ABS(p1_u - p0_u) < beta_cb) + { + val = ((((q0_u - p0_u) << 2) + (p1_u - q1_u) + 4) >> 3); + delta = CLIP3(-tcb, tcb, val); + /* p0' */ + val = p0_u + delta; + pu1_p1_temp_u[pos_p0] = CLIP_U8(val); + /* q0' */ + val = q0_u - delta; + pu1_src_temp_u[pos_q0] = CLIP_U8(val); + } + /* Filter Decision */ + if(ABS(p0_v - q0_v) < alpha_cr && ABS(q1_v - q0_v) < beta_cr + && ABS(p1_v - p0_v) < beta_cr) + { + val = ((((q0_v - p0_v) << 2) + (p1_v - q1_v) + 4) >> 3); + delta = CLIP3(-tcr, tcr, val); + /* p0' */ + val = p0_v + delta; + pu1_p1_temp_v[pos_p0] = CLIP_U8(val); + /* q0' */ + val = q0_v - delta; + pu1_src_temp_v[pos_q0] = CLIP_U8(val); + } + } + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_deblk_chroma_vert_bs4_mbaff() */ +/* */ +/* Description : This function performs filtering of a chroma block */ +/* vertical edge when boundary strength is set to 4 in high */ +/* profile. */ +/* */ +/* Inputs : pu1_src - pointer to the src sample q0 of U */ +/* src_strd - source stride */ +/* alpha_cb - alpha value for the boundary in U */ +/* beta_cb - beta value for the boundary in U */ +/* alpha_cr - alpha value for the boundary in V */ +/* beta_cr - beta value for the boundary in V */ +/* u4_bs - packed Boundary strength array */ +/* pu1_cliptab_cb - tc0_table for U */ +/* pu1_cliptab_cr - tc0_table for V */ +/* */ +/* Globals : None */ +/* */ +/* Processing : When the function is called twice, this operation is as */ +/* described in Sec. 8.7.2.4 under the title "Filtering */ +/* process for edges for bS equal to 4" in ITU T Rec H.264 */ +/* with alpha and beta values different in U and V. */ +/* */ +/* Outputs : None */ +/* */ +/* Returns : None */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 29 12 2014 Kaushik Draft */ +/* Senthoor */ +/* */ +/*****************************************************************************/ +void ih264_deblk_chroma_vert_bs4_mbaff(UWORD8 *pu1_src, + WORD32 src_strd, + WORD32 alpha_cb, + WORD32 beta_cb, + WORD32 alpha_cr, + WORD32 beta_cr) +{ + UWORD8 *pu1_src_u = pu1_src; /* Pointer to the src sample q0 of U */ + UWORD8 *pu1_src_v = pu1_src + 1; /* Pointer to the src sample q0 of V */ + UWORD8 p1_u, p0_u, q0_u, q1_u, p1_v, p0_v, q0_v, q1_v; + WORD32 blk_strd = src_strd; + WORD32 pos_p1, pos_p0, pos_q0, pos_q1; + UWORD8 *pu1_src_temp_u, *pu1_src_temp_v; + WORD8 edge; + + pos_q0 = 0; + pos_q1 = 2; + pos_p0 = -2; + pos_p1 = -4; + + for(edge = 0; edge < 4; + edge++, pu1_src_u += blk_strd, pu1_src_v += blk_strd) + { + pu1_src_temp_u = pu1_src_u; + pu1_src_temp_v = pu1_src_v; + q0_u = pu1_src_temp_u[pos_q0]; + q1_u = pu1_src_temp_u[pos_q1]; + p0_u = pu1_src_temp_u[pos_p0]; + p1_u = pu1_src_temp_u[pos_p1]; + q0_v = pu1_src_temp_v[pos_q0]; + q1_v = pu1_src_temp_v[pos_q1]; + p0_v = pu1_src_temp_v[pos_p0]; + p1_v = pu1_src_temp_v[pos_p1]; + + /* Filter Decision */ + if((ABS(p0_u - q0_u) < alpha_cb) && + (ABS(q1_u - q0_u) < beta_cb) && + (ABS(p1_u - p0_u) < beta_cb)) + { + /* p0' */ + pu1_src_temp_u[pos_p0] = ((X2(p1_u) + p0_u + q1_u + 2) >> 2); + /* q0' */ + pu1_src_temp_u[pos_q0] = (X2(q1_u) + q0_u + p1_u + 2) >> 2; + } + + /* Filter Decision */ + if((ABS(p0_v - q0_v) < alpha_cr) && + (ABS(q1_v - q0_v) < beta_cr) && + (ABS(p1_v - p0_v) < beta_cr)) + { + /* p0' */ + pu1_src_temp_v[pos_p0] = ((X2(p1_v) + p0_v + q1_v + 2) >> 2); + /* q0' */ + pu1_src_temp_v[pos_q0] = (X2(q1_v) + q0_v + p1_v + 2) >> 2; + } + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_deblk_chroma_vert_bslt4_mbaff() */ +/* */ +/* Description : This function performs filtering of a chroma block */ +/* vertical edge when boundary strength is less than 4 in */ +/* high profile. */ +/* */ +/* Inputs : pu1_src - pointer to the src sample q0 of U */ +/* src_strd - source stride */ +/* alpha_cb - alpha value for the boundary in U */ +/* beta_cb - beta value for the boundary in U */ +/* alpha_cr - alpha value for the boundary in V */ +/* beta_cr - beta value for the boundary in V */ +/* u4_bs - packed Boundary strength array */ +/* pu1_cliptab_cb - tc0_table for U */ +/* pu1_cliptab_cr - tc0_table for V */ +/* */ +/* Globals : None */ +/* */ +/* Processing : When the function is called twice, this operation is as */ +/* described in Sec. 8.7.2.4 under the title "Filtering */ +/* process for edges for bS less than 4" in ITU T Rec H.264 */ +/* with alpha and beta values different in U and V. */ +/* */ +/* Outputs : None */ +/* */ +/* Returns : None */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 29 12 2014 Kaushik Draft */ +/* Senthoor */ +/* */ +/*****************************************************************************/ +void ih264_deblk_chroma_vert_bslt4_mbaff(UWORD8 *pu1_src, + WORD32 src_strd, + WORD32 alpha_cb, + WORD32 beta_cb, + WORD32 alpha_cr, + WORD32 beta_cr, + UWORD32 u4_bs, + const UWORD8 *pu1_cliptab_cb, + const UWORD8 *pu1_cliptab_cr) +{ + UWORD8 *pu1_src_u = pu1_src; /* Pointer to the src sample q0 of plane U*/ + UWORD8 *pu1_src_v = pu1_src + 1; /* Pointer to the src sample q0 of plane V*/ + UWORD8 p1_u, p0_u, q0_u, q1_u, p1_v, p0_v, q0_v, q1_v; + WORD32 blk_strd = src_strd; + WORD32 pos_p1, pos_p0, pos_q0, pos_q1; + UWORD8 *pu1_src_temp_u, *pu1_src_temp_v; + WORD8 edge; + WORD8 delta; + WORD8 tcb, tcr; + WORD16 val; + UWORD8 tcb0, tcr0, u1_bs; + + pos_q0 = 0; + pos_q1 = 2; + pos_p0 = -2; + pos_p1 = -4; + + for(edge = 0; edge < 4; + edge++, pu1_src_u += blk_strd, pu1_src_v += blk_strd) + { + pu1_src_temp_u = pu1_src_u; + pu1_src_temp_v = pu1_src_v; + /* Filter Decision */ + u1_bs = (UWORD8)((u4_bs >> ((3 - edge) << 3)) & 0x0ff); + if(!u1_bs) + continue; + /* tc0 */ + tcb0 = pu1_cliptab_cb[u1_bs]; + tcr0 = pu1_cliptab_cr[u1_bs]; + tcb = tcb0 + 1; + tcr = tcr0 + 1; + q0_u = pu1_src_temp_u[pos_q0]; + q1_u = pu1_src_temp_u[pos_q1]; + p0_u = pu1_src_temp_u[pos_p0]; + p1_u = pu1_src_temp_u[pos_p1]; + + q0_v = pu1_src_temp_v[pos_q0]; + q1_v = pu1_src_temp_v[pos_q1]; + p0_v = pu1_src_temp_v[pos_p0]; + p1_v = pu1_src_temp_v[pos_p1]; + + /* Filter Decision */ + if((ABS(p0_u - q0_u) < alpha_cb) && + (ABS(q1_u - q0_u) < beta_cb) && + (ABS(p1_u - p0_u) < beta_cb)) + { + val = ((((q0_u - p0_u) << 2) + (p1_u - q1_u) + 4) >> 3); + delta = CLIP3(-tcb, tcb, val); + /* p0' */ + val = p0_u + delta; + pu1_src_temp_u[pos_p0] = CLIP_U8(val); + /* q0' */ + val = q0_u - delta; + pu1_src_temp_u[pos_q0] = CLIP_U8(val); + } + + /* Filter Decision */ + if((ABS(p0_v - q0_v) < alpha_cr) && + (ABS(q1_v - q0_v) < beta_cr) && + (ABS(p1_v - p0_v) < beta_cr)) + { + val = ((((q0_v - p0_v) << 2) + (p1_v - q1_v) + 4) >> 3); + delta = CLIP3(-tcr, tcr, val); + /* p0' */ + val = p0_v + delta; + pu1_src_temp_v[pos_p0] = CLIP_U8(val); + /* q0' */ + val = q0_v - delta; + pu1_src_temp_v[pos_q0] = CLIP_U8(val); + } + } +} diff --git a/dependencies/ih264d/common/ih264_deblk_edge_filters.h b/dependencies/ih264d/common/ih264_deblk_edge_filters.h new file mode 100644 index 00000000..4079dd2c --- /dev/null +++ b/dependencies/ih264d/common/ih264_deblk_edge_filters.h @@ -0,0 +1,195 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** + ******************************************************************************* + * @file + * ih264_deblk_edge_filters.h + * + * @brief + * This file contains declarations of functions used for deblocking + * + * @author + * Ittiam + * + * @remarks + * None + * + ******************************************************************************* + */ + +#ifndef IH264_DEBLK_H_ +#define IH264_DEBLK_H_ + +/*****************************************************************************/ +/* Extern Function Declarations */ +/*****************************************************************************/ + +typedef void ih264_deblk_edge_bslt4_ft(UWORD8 *pu1_src, + WORD32 src_strd, + WORD32 alpha, + WORD32 beta, + UWORD32 u4_bs, + const UWORD8 *pu1_cliptab ); + +typedef void ih264_deblk_edge_bs4_ft(UWORD8 *pu1_src, + WORD32 src_strd, + WORD32 alpha, + WORD32 beta ); + +typedef void ih264_deblk_chroma_edge_bslt4_ft(UWORD8 *pu1_src, + WORD32 src_strd, + WORD32 alpha_cb, + WORD32 beta_cb, + WORD32 alpha_cr, + WORD32 beta_cr, + UWORD32 u4_bs, + const UWORD8 *pu1_cliptab_cb, + const UWORD8 *pu1_cliptab_cr); + +typedef void ih264_deblk_chroma_edge_bs4_ft(UWORD8 *pu1_src, + WORD32 src_strd, + WORD32 alpha_cb, + WORD32 beta_cb, + WORD32 alpha_cr, + WORD32 beta_cr); + + + +ih264_deblk_edge_bs4_ft ih264_deblk_luma_horz_bs4; +ih264_deblk_edge_bs4_ft ih264_deblk_luma_vert_bs4; +ih264_deblk_edge_bs4_ft ih264_deblk_luma_vert_bs4_mbaff; + + +ih264_deblk_edge_bs4_ft ih264_deblk_chroma_horz_bs4_bp; +ih264_deblk_edge_bs4_ft ih264_deblk_chroma_vert_bs4_bp; +ih264_deblk_edge_bs4_ft ih264_deblk_chroma_vert_bs4_mbaff_bp; + + +ih264_deblk_edge_bslt4_ft ih264_deblk_luma_horz_bslt4; +ih264_deblk_edge_bslt4_ft ih264_deblk_luma_vert_bslt4; +ih264_deblk_edge_bslt4_ft ih264_deblk_luma_vert_bslt4_mbaff; + + +ih264_deblk_edge_bslt4_ft ih264_deblk_chroma_horz_bslt4_bp; +ih264_deblk_edge_bslt4_ft ih264_deblk_chroma_vert_bslt4_bp; +ih264_deblk_edge_bslt4_ft ih264_deblk_chroma_vert_bslt4_mbaff_bp; + +ih264_deblk_chroma_edge_bs4_ft ih264_deblk_chroma_vert_bs4; +ih264_deblk_chroma_edge_bs4_ft ih264_deblk_chroma_horz_bs4; +ih264_deblk_chroma_edge_bs4_ft ih264_deblk_chroma_vert_bs4_mbaff; +ih264_deblk_chroma_edge_bs4_ft ih264_deblk_chroma_horz_bs4_mbaff; + +ih264_deblk_chroma_edge_bslt4_ft ih264_deblk_chroma_vert_bslt4; +ih264_deblk_chroma_edge_bslt4_ft ih264_deblk_chroma_horz_bslt4; +ih264_deblk_chroma_edge_bslt4_ft ih264_deblk_chroma_vert_bslt4_mbaff; +ih264_deblk_chroma_edge_bslt4_ft ih264_deblk_chroma_horz_bslt4_mbaff; + + +/*A9*/ +ih264_deblk_edge_bs4_ft ih264_deblk_luma_horz_bs4_a9; +ih264_deblk_edge_bs4_ft ih264_deblk_luma_vert_bs4_a9; +ih264_deblk_edge_bs4_ft ih264_deblk_luma_vert_bs4_mbaff_a9; + + +ih264_deblk_edge_bs4_ft ih264_deblk_chroma_horz_bs4_bp_a9; +ih264_deblk_edge_bs4_ft ih264_deblk_chroma_vert_bs4_bp_a9; +ih264_deblk_edge_bs4_ft ih264_deblk_chroma_vert_bs4_mbaff_bp_a9; + + +ih264_deblk_edge_bslt4_ft ih264_deblk_luma_horz_bslt4_a9; +ih264_deblk_edge_bslt4_ft ih264_deblk_luma_vert_bslt4_a9; +ih264_deblk_edge_bslt4_ft ih264_deblk_luma_vert_bslt4_mbaff_a9; + + +ih264_deblk_edge_bslt4_ft ih264_deblk_chroma_horz_bslt4_bp_a9; +ih264_deblk_edge_bslt4_ft ih264_deblk_chroma_vert_bslt4_bp_a9; +ih264_deblk_edge_bslt4_ft ih264_deblk_chroma_vert_bslt4_mbaff_bp_a9; + +ih264_deblk_chroma_edge_bs4_ft ih264_deblk_chroma_vert_bs4_a9; +ih264_deblk_chroma_edge_bs4_ft ih264_deblk_chroma_horz_bs4_a9; +ih264_deblk_chroma_edge_bs4_ft ih264_deblk_chroma_vert_bs4_mbaff_a9; +ih264_deblk_chroma_edge_bs4_ft ih264_deblk_chroma_horz_bs4_mbaff_a9; + +ih264_deblk_chroma_edge_bslt4_ft ih264_deblk_chroma_vert_bslt4_a9; +ih264_deblk_chroma_edge_bslt4_ft ih264_deblk_chroma_horz_bslt4_a9; +ih264_deblk_chroma_edge_bslt4_ft ih264_deblk_chroma_vert_bslt4_mbaff_a9; +ih264_deblk_chroma_edge_bslt4_ft ih264_deblk_chroma_horz_bslt4_mbaff_a9; + +/*AV8*/ +ih264_deblk_edge_bs4_ft ih264_deblk_luma_horz_bs4_av8; +ih264_deblk_edge_bs4_ft ih264_deblk_luma_vert_bs4_av8; +ih264_deblk_edge_bs4_ft ih264_deblk_luma_vert_bs4_mbaff_av8; + + +ih264_deblk_edge_bs4_ft ih264_deblk_chroma_horz_bs4_bp_av8; +ih264_deblk_edge_bs4_ft ih264_deblk_chroma_vert_bs4_bp_av8; +ih264_deblk_edge_bs4_ft ih264_deblk_chroma_vert_bs4_mbaff_bp_av8; + + +ih264_deblk_edge_bslt4_ft ih264_deblk_luma_horz_bslt4_av8; +ih264_deblk_edge_bslt4_ft ih264_deblk_luma_vert_bslt4_av8; +ih264_deblk_edge_bslt4_ft ih264_deblk_luma_vert_bslt4_mbaff_av8; + + +ih264_deblk_edge_bslt4_ft ih264_deblk_chroma_horz_bslt4_bp_av8; +ih264_deblk_edge_bslt4_ft ih264_deblk_chroma_vert_bslt4_bp_av8; +ih264_deblk_edge_bslt4_ft ih264_deblk_chroma_vert_bslt4_mbaff_bp_av8; + +ih264_deblk_chroma_edge_bs4_ft ih264_deblk_chroma_vert_bs4_av8; +ih264_deblk_chroma_edge_bs4_ft ih264_deblk_chroma_horz_bs4_av8; +ih264_deblk_chroma_edge_bs4_ft ih264_deblk_chroma_vert_bs4_mbaff_av8; +ih264_deblk_chroma_edge_bs4_ft ih264_deblk_chroma_horz_bs4_mbaff_av8; + +ih264_deblk_chroma_edge_bslt4_ft ih264_deblk_chroma_vert_bslt4_av8; +ih264_deblk_chroma_edge_bslt4_ft ih264_deblk_chroma_horz_bslt4_av8; +ih264_deblk_chroma_edge_bslt4_ft ih264_deblk_chroma_vert_bslt4_mbaff_av8; +ih264_deblk_chroma_edge_bslt4_ft ih264_deblk_chroma_horz_bslt4_mbaff_av8; + +/*SSE3*/ +ih264_deblk_edge_bs4_ft ih264_deblk_luma_horz_bs4_ssse3; +ih264_deblk_edge_bs4_ft ih264_deblk_luma_vert_bs4_ssse3; +ih264_deblk_edge_bs4_ft ih264_deblk_luma_vert_bs4_mbaff_ssse3; + + +ih264_deblk_edge_bs4_ft ih264_deblk_chroma_horz_bs4_bp_ssse3; +ih264_deblk_edge_bs4_ft ih264_deblk_chroma_vert_bs4_bp_ssse3; +ih264_deblk_edge_bs4_ft ih264_deblk_chroma_vert_bs4_mbaff_bp_ssse3; + + +ih264_deblk_edge_bslt4_ft ih264_deblk_luma_horz_bslt4_ssse3; +ih264_deblk_edge_bslt4_ft ih264_deblk_luma_vert_bslt4_ssse3; +ih264_deblk_edge_bslt4_ft ih264_deblk_luma_vert_bslt4_mbaff_ssse3; + + +ih264_deblk_edge_bslt4_ft ih264_deblk_chroma_horz_bslt4_bp_ssse3; +ih264_deblk_edge_bslt4_ft ih264_deblk_chroma_vert_bslt4_bp_ssse3; +ih264_deblk_edge_bslt4_ft ih264_deblk_chroma_vert_bslt4_mbaff_bp_ssse3; + +ih264_deblk_chroma_edge_bs4_ft ih264_deblk_chroma_vert_bs4_ssse3; +ih264_deblk_chroma_edge_bs4_ft ih264_deblk_chroma_horz_bs4_ssse3; +ih264_deblk_chroma_edge_bs4_ft ih264_deblk_chroma_vert_bs4_mbaff_ssse3; +ih264_deblk_chroma_edge_bs4_ft ih264_deblk_chroma_horz_bs4_mbaff_ssse3; + +ih264_deblk_chroma_edge_bslt4_ft ih264_deblk_chroma_vert_bslt4_ssse3; +ih264_deblk_chroma_edge_bslt4_ft ih264_deblk_chroma_horz_bslt4_ssse3; +ih264_deblk_chroma_edge_bslt4_ft ih264_deblk_chroma_vert_bslt4_mbaff_ssse3; +ih264_deblk_chroma_edge_bslt4_ft ih264_deblk_chroma_horz_bslt4_mbaff_ssse3; + +#endif /* IH264_DEBLK_H_ */ diff --git a/dependencies/ih264d/common/ih264_deblk_tables.c b/dependencies/ih264d/common/ih264_deblk_tables.c new file mode 100644 index 00000000..91e28e0a --- /dev/null +++ b/dependencies/ih264d/common/ih264_deblk_tables.c @@ -0,0 +1,119 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** +******************************************************************************* +* @file +* ih264_deblk_tables.c +* +* @brief +* Contains tables used for deblocking +* +* @author +* Ittiam +* +* @par List of Tables: +* - guc_ih264_qp_scale_cr[] +* - guc_ih264_alpha_table[] +* - guc_ih264_beta_table[] +* - guc_ih264_clip_table[][] +* +* @remarks +* None +* +******************************************************************************* +*/ + +/*****************************************************************************/ +/* File Includes */ +/*****************************************************************************/ + +/* System include files */ +#include + +/* User include files */ +#include "ih264_typedefs.h" +#include "ih264_deblk_tables.h" + +/*****************************************************************************/ +/* Extern global definitions */ +/*****************************************************************************/ + +/** + ****************************************************************************** + * @brief alpha & beta tables for deblocking + * input : indexA [0-51] & indexB [0-51] + * output : alpha & beta + * + * @remarks Table 8-16 – in H264 Specification, + * Derivation of offset dependent threshold variables + * alpha and beta from indexA and indexB + ****************************************************************************** + */ +const UWORD8 gu1_ih264_alpha_table[52] = +{ + /* indexA :: 0-51 inclusive */ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 4, 4, 5, 6, 7, 8, 9, 10, + 12, 13, 15, 17, 20, 22, 25, 28, + 32, 36, 40, 45, 50, 56, 63, 71, + 80, 90, 101, 113, 127, 144, 162, 182, + 203, 226, 255, 255, +}; + +const UWORD8 gu1_ih264_beta_table[52] = +{ + /* indexB :: 0-51 inclusive */ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 2, 2, 2, 3, 3, 3, 3, 4, + 4, 4, 6, 6, 7, 7, 8, 8, + 9, 9, 10, 10, 11, 11, 12, 12, + 13, 13, 14, 14, 15, 15, 16, 16, + 17, 17, 18, 18, +}; + +/** + ****************************************************************************** + * @brief t'C0 table for deblocking + * input : indexA [0-51] and bS [1,3] + * output : t'C0 + * + * @remarks Table 8-17 – in H264 Specification, + * Value of variable t'C0 as a function of indexA and bS + ****************************************************************************** + */ +const UWORD8 gu1_ih264_clip_table[52][4] = +{ + /* indexA :: 0-51 inclusive */ + { 0, 0, 0, 0}, { 0, 0, 0, 0}, { 0, 0, 0, 0}, { 0, 0, 0, 0}, + { 0, 0, 0, 0}, { 0, 0, 0, 0}, { 0, 0, 0, 0}, { 0, 0, 0, 0}, + { 0, 0, 0, 0}, { 0, 0, 0, 0}, { 0, 0, 0, 0}, { 0, 0, 0, 0}, + { 0, 0, 0, 0}, { 0, 0, 0, 0}, { 0, 0, 0, 0}, { 0, 0, 0, 0}, + { 0, 0, 0, 0}, { 0, 0, 0, 1}, { 0, 0, 0, 1}, { 0, 0, 0, 1}, + { 0, 0, 0, 1}, { 0, 0, 1, 1}, { 0, 0, 1, 1}, { 0, 1, 1, 1}, + { 0, 1, 1, 1}, { 0, 1, 1, 1}, { 0, 1, 1, 1}, { 0, 1, 1, 2}, + { 0, 1, 1, 2}, { 0, 1, 1, 2}, { 0, 1, 1, 2}, { 0, 1, 2, 3}, + { 0, 1, 2, 3}, { 0, 2, 2, 3}, { 0, 2, 2, 4}, { 0, 2, 3, 4}, + { 0, 2, 3, 4}, { 0, 3, 3, 5}, { 0, 3, 4, 6}, { 0, 3, 4, 6}, + { 0, 4, 5, 7}, { 0, 4, 5, 8}, { 0, 4, 6, 9}, { 0, 5, 7,10}, + { 0, 6, 8,11}, { 0, 6, 8,13}, { 0, 7,10,14}, { 0, 8,11,16}, + { 0, 9,12,18}, { 0,10,13,20}, { 0,11,15,23}, { 0,13,17,25}, +}; diff --git a/dependencies/ih264d/common/ih264_deblk_tables.h b/dependencies/ih264d/common/ih264_deblk_tables.h new file mode 100644 index 00000000..3935dcbc --- /dev/null +++ b/dependencies/ih264d/common/ih264_deblk_tables.h @@ -0,0 +1,73 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** + ******************************************************************************* + * @file + * ih264_deblk_tables.h + * + * @brief + * This file contains declarations of tables used for deblocking + * + * @author + * Ittiam + * + * @par List of Functions: + * + * @remarks + * None + * + ******************************************************************************* + */ + +#ifndef IH264_DEBLK_TABLES_H_ +#define IH264_DEBLK_TABLES_H_ + +/*****************************************************************************/ +/* Extern global declarations */ +/*****************************************************************************/ + +/** + ****************************************************************************** + * @brief alpha & beta tables for deblocking + * input : indexA [0-51] & indexB [0-51] + * output : alpha & beta + * + * @remarks Table 8-16 – in H264 Specification, + * Derivation of offset dependent threshold variables + * alpha and beta from indexA and indexB + ****************************************************************************** + */ +extern const UWORD8 gu1_ih264_alpha_table[52]; + +extern const UWORD8 gu1_ih264_beta_table[52]; + +/** + ****************************************************************************** + * @brief t'C0 table for deblocking + * input : indexA [0-51] and bS [1,3] + * output : t'C0 + * + * @remarks Table 8-17 – in H264 Specification, + * Value of variable t'C0 as a function of indexA and bS + ****************************************************************************** + */ +extern const UWORD8 gu1_ih264_clip_table[52][4]; + +#endif /* IH264_DEBLK_TABLES_H_ */ diff --git a/dependencies/ih264d/common/ih264_debug.h b/dependencies/ih264d/common/ih264_debug.h new file mode 100644 index 00000000..96ff2a7f --- /dev/null +++ b/dependencies/ih264d/common/ih264_debug.h @@ -0,0 +1,61 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** +******************************************************************************* +* @file +* ih264_debug.h +* +* @brief +* Definitions for codec debugging +* +* @author +* Ittiam +* +* @par List of Functions: +* +* @remarks +* None +* +******************************************************************************* +*/ +#ifndef _IH264_DEBUG_H_ +#define _IH264_DEBUG_H_ + + +#if DEBUG_PRINT + +#define DEBUG(...) \ +{ \ + printf("\n[H264 DBG] %s/%d:: ", __FUNCTION__, __LINE__); \ + printf(__VA_ARGS__); \ +} + +#else + +#define DEBUG(...) {} + +#endif + + +#define ASSERT(x) assert((x)) + + +#endif /* _IH264_DEBUG_H_ */ + diff --git a/dependencies/ih264d/common/ih264_defs.h b/dependencies/ih264d/common/ih264_defs.h new file mode 100644 index 00000000..9c84be9a --- /dev/null +++ b/dependencies/ih264d/common/ih264_defs.h @@ -0,0 +1,749 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** +******************************************************************************* +* @file +* ih264_defs.h +* +* @brief +* Definitions used in the codec +* +* @author +* Ittiam +* +* +* @remarks +* None +* +******************************************************************************* +*/ + +#ifndef IH264_DEFS_H_ +#define IH264_DEFS_H_ + +/*****************************************************************************/ +/* Enums */ +/*****************************************************************************/ + + +/*****************************************************************************/ +/* Profile and Levels */ +/*****************************************************************************/ + +/** +****************************************************************************** + * @enum PROFILE_IDC + * @brief Defines the set of possible profiles +****************************************************************************** +*/ +enum +{ + IH264_PROFILE_BASELINE = 66, + IH264_PROFILE_MAIN = 77, + IH264_PROFILE_EXTENDED = 88, + IH264_PROFILE_HIGH = 100, + IH264_PROFILE_HIGH10 = 110, + IH264_PROFILE_HIGH422 = 122, + IH264_PROFILE_HIGH444 = 144, +}; + +/** +****************************************************************************** + * @enum LEVEL_IDC + * @brief Defines the set of possible levels +****************************************************************************** +*/ +typedef enum +{ + IH264_LEVEL_10 = 10, + IH264_LEVEL_1B = 9, + IH264_LEVEL_11 = 11, + IH264_LEVEL_12 = 12, + IH264_LEVEL_13 = 13, + IH264_LEVEL_20 = 20, + IH264_LEVEL_21 = 21, + IH264_LEVEL_22 = 22, + IH264_LEVEL_30 = 30, + IH264_LEVEL_31 = 31, + IH264_LEVEL_32 = 32, + IH264_LEVEL_40 = 40, + IH264_LEVEL_41 = 41, + IH264_LEVEL_42 = 42, + IH264_LEVEL_50 = 50, + IH264_LEVEL_51 = 51, +}IH264_LEVEL_T; + + +/** +****************************************************************************** + * @enum PIC TYPES + * @brief Defines the set of possible picture type - not signaled in bitstream +****************************************************************************** +*/ +typedef enum +{ + PIC_NA = 0x7FFFFFFF, + PIC_IDR = 0, + PIC_I = 1, + PIC_P = 2, + PIC_B = 3, + PIC_P_NONREF = 4, + PIC_B_NONREF = 5, + PIC_MAX, +}PIC_TYPE_T; + +/** +****************************************************************************** + * @enum FRAME-FIELD types + * @brief Defines the set of possible field types. +****************************************************************************** +*/ +enum +{ + TOP_FIELD, + BOTTOM_FIELD, + FRAME, +}; + +/** +****************************************************************************** + * @enum SLICE TYPES + * @brief Defines the set of possible SLICE TYPES +****************************************************************************** +*/ +enum +{ + PSLICE = 0, + BSLICE = 1, + ISLICE = 2, + SPSLICE = 3, + SISLICE = 4, + MAXSLICE_TYPE, +}; + +/** +****************************************************************************** + * @enum NAL_UNIT_TYPE + * @brief Defines the set of possible nal unit types +****************************************************************************** +*/ +enum +{ + NAL_UNSPEC_0 = 0, + NAL_SLICE_NON_IDR = 1, + NAL_SLICE_DPA = 2, + NAL_SLICE_DPB = 3, + NAL_SLICE_DPC = 4, + NAL_SLICE_IDR = 5, + NAL_SEI = 6, + NAL_SPS = 7, + NAL_PPS = 8, + NAL_AUD = 9, + NAL_EOSEQ = 10, + NAL_EOSTR = 11, + NAL_FILLER = 12, + NAL_SPSE = 13, + NAL_RES_18 = 14, + NAL_AUX_PIC = 19, + NAL_RES_23 = 20, + NAL_UNSPEC_31 = 24, +}; + +/** +****************************************************************************** + * @enum CHROMA_FORMAT_IDC + * @brief Defines the set of possible chroma formats + * Note Chorma format Do not change enum values +****************************************************************************** +*/ +enum +{ + CHROMA_FMT_IDC_MONOCHROME = 0, + CHROMA_FMT_IDC_YUV420 = 1, + CHROMA_FMT_IDC_YUV422 = 2, + CHROMA_FMT_IDC_YUV444 = 3, + CHROMA_FMT_IDC_YUV444_PLANES = 4, +}; + + +/** +****************************************************************************** + * @enum MBMODES_I16x16 + * @brief Defines the set of possible intra 16x16 mb modes +****************************************************************************** +*/ +typedef enum +{ + VERT_I16x16 = 0, + HORZ_I16x16 = 1, + DC_I16x16 = 2, + PLANE_I16x16 = 3, + MAX_I16x16 = 4, +}MBMODES_I16x16; + +/** +****************************************************************************** + * @enum MBMODES_I4x4 + * @brief Defines the set of possible intra 4x4 mb modes +****************************************************************************** +*/ +typedef enum +{ + VERT_I4x4 = 0, + HORZ_I4x4 = 1, + DC_I4x4 = 2, + DIAG_DL_I4x4 = 3, + DIAG_DR_I4x4 = 4, + VERT_R_I4x4 = 5, + HORZ_D_I4x4 = 6, + VERT_L_I4x4 = 7, + HORZ_U_I4x4 = 8, + MAX_I4x4 = 9, +}MBMODES_I4x4; + +/** +****************************************************************************** + * @enum MBMODES_I8x8 + * @brief Defines the set of possible intra 8x8 mb modes +****************************************************************************** +*/ +typedef enum +{ + VERT_I8x8 = 0, + HORZ_I8x8 = 1, + DC_I8x8 = 2, + DIAG_DL_I8x8 = 3, + DIAG_DR_I8x8 = 4, + VERT_R_I8x8 = 5, + HORZ_D_I8x8 = 6, + VERT_L_I8x8 = 7, + HORZ_U_I8x8 = 8, + MAX_I8x8 = 9, +}MBMODES_I8x8; + +/** +****************************************************************************** + * @enum MBMODES_CHROMA_I8x8 (Chroma) + * @brief Defines the set of possible intra 8x8 mb modes for chroma +****************************************************************************** +*/ +typedef enum +{ + DC_CH_I8x8 = 0, + HORZ_CH_I8x8 = 1, + VERT_CH_I8x8 = 2, + PLANE_CH_I8x8 = 3, + MAX_CH_I8x8 = 4, +}MBMODES_CHROMA_I8x8; + +/** +****************************************************************************** + * @enum MBTYPES + * @brief Defines the set of possible macro block types +****************************************************************************** +*/ +typedef enum +{ + I16x16 = 0, + I4x4 = 1, + I8x8 = 2, + P16x16 = 3, + P16x8 = 4, + P8x16 = 5, + P8x8 = 6, + PSKIP = 7, + IPCM = 8, + B16x16 = 9, + BSKIP = 10, + BDIRECT = 11, + MAX_MBTYPES, +}MBTYPES_T; + +/* Prediction list */ +/* Do not change enum values */ +enum +{ + PRED_L0 = 0, + PRED_L1 = 1, + PRED_BI = 2 +}; + + +/** +****************************************************************************** + * @enum ENTROPY_BLK_TYPE + * @brief Defines the nature of blocks employed in entropy coding +****************************************************************************** +*/ +typedef enum +{ + ENTROPY_BLK_INVALID = -1, + CAVLC_LUMA_4x4_DC = 0, + CAVLC_LUMA_4x4_AC = 1, + CAVLC_LUMA_4x4 = 2, + CAVLC_CHROMA_4x4_DC = 3, + CAVLC_CHROMA_4x4_AC = 4, +} ENTROPY_BLK_TYPE; + +/** +****************************************************************************** + * @enum ENTROPY_MODE + * @brief Entropy coding modes +****************************************************************************** +*/ +typedef enum +{ + CAVLC = 0, + CABAC = 1, +} ENTROPY_MODE; + +/** +****************************************************************************** + * @enum COMPONENT_TYPE + * @brief components Y, U & V +****************************************************************************** +*/ +typedef enum +{ + Y, + U, + V, +} COMPONENT_TYPE; + + +/** +****************************************************************************** + * @enum MBPART_PREDMODE_T + * @brief MbPartps_pred_mode_ctxt Table 7-11 to 7-14 +****************************************************************************** +*/ +typedef enum +{ + MBPART_NA, + MBPART_I4x4, + MBPART_I8x8, + MBPART_I16x16, + MBPART_L0, + MBPART_L1, + MBPART_BI, + MBPART_DIRECT, + MBPART_IPCM, +}MBPART_PREDMODE_T; + + +typedef enum +{ + I_NxN, + I_16x16_0_0_0, + I_16x16_1_0_0, + I_16x16_2_0_0, + I_16x16_3_0_0, + I_16x16_0_1_0, + I_16x16_1_1_0, + I_16x16_2_1_0, + I_16x16_3_1_0, + I_16x16_0_2_0, + I_16x16_1_2_0, + I_16x16_2_2_0, + I_16x16_3_2_0, + I_16x16_0_0_1, + I_16x16_1_0_1, + I_16x16_2_0_1, + I_16x16_3_0_1, + I_16x16_0_1_1, + I_16x16_1_1_1, + I_16x16_2_1_1, + I_16x16_3_1_1, + I_16x16_0_2_1, + I_16x16_1_2_1, + I_16x16_2_2_1, + I_16x16_3_2_1, + I_PCM, +}MBTYPE_ISLICE_T; + +typedef enum +{ + P_L0_16x16, + P_L0_L0_16x8, + P_L0_L0_8x16, + P_8x8, + P_8x8REF0, + P_SKIP +}MBTYPE_PSLICE_T; + +typedef enum +{ + B_DIRECT_16x16, + B_L0_16x16, + B_L1_16x16, + B_BI_16x16, + B_L0_L0_16x8, + B_L0_L0_8x16, + B_L1_L1_16x8, + B_L1_L1_8x16, + B_L0_L1_16x8, + B_L0_L1_8x16, + B_L1_L0_16x8, + B_L1_L0_8x16, + B_L0_BI_16x8, + B_L0_BI_8x16, + B_L1_BI_16x8, + B_L1_BI_8x16, + B_BI_L0_16x8, + B_BI_L0_8x16, + B_BI_L1_16x8, + B_BI_L1_8x16, + B_BI_BI_16x8, + B_BI_BI_8x16, + B_8x8, + B_SKIP, +}MBTYPE_BSLICE_T; + + +typedef enum +{ + P_L0_8x8, + P_L0_8x4, + P_L0_4x8, + P_L0_4x4, +}SUBMBTYPE_PSLICE_T; + +typedef enum +{ + B_DIRECT_8x8, + B_L0_8x8, + B_L1_8x8, + B_BI_8x8, + B_L0_8x4, + B_L0_4x8, + B_L1_8x4, + B_L1_4x8, + B_BI_8x4, + B_BI_4x8, + B_L0_4x4, + B_L1_4x4, + B_BI_4x4, +}SUBMBTYPE_BSLICE_T; + +/** + * DC Mode pattern for 4 4x4 sub blocks in an MB row + */ +#define DC_I16X16_MB_ROW (DC_I16x16 << 24) | (DC_I16x16 << 16) | \ + (DC_I16x16 << 8) | DC_I16x16 + + + +/*****************************************************************************/ +/* Constant Macros */ +/*****************************************************************************/ + +/*****************************************************************************/ +/* Reference frame defs */ +/*****************************************************************************/ +/* Maximum DPB size */ +#define MAX_DPB_SIZE 16 + +/* Maximum mmco commands in slice header */ +#define MAX_MMCO_COMMANDS 32 + +/* Maximum reference reorder idc */ +#define MAX_MODICATION_IDC 32 + +/*****************************************************************************/ +/* SPS restrictions */ +/*****************************************************************************/ + +/* Number of SPS allowed */ +/* An extra buffer is allocated to write the parsed data + * It is copied to the appropriate location later */ +#define MAX_SPS_CNT (32 + 1) + +/* Maximum long term reference pics */ +#define MAX_LTREF_PICS_SPS 16 + +/* Maximum short term reference pics */ +#define MAX_STREF_PICS_SPS 64 + + +/*****************************************************************************/ +/* PPS restrictions */ +/*****************************************************************************/ + +/* Number of PPS allowed */ +/* An extra buffer is allocated to write the parsed data + * It is copied to the appropriate location later */ +#define MAX_PPS_CNT (256 + 1) + +/*****************************************************************************/ +/* Macro definitions for sizes of MB, PU, TU, CU */ +/*****************************************************************************/ +#define MB_SIZE 16 +#define BLK8x8SIZE 8 +#define BLK_SIZE 4 + + +/* TU Size Range */ +#define MAX_TU_SIZE 8 +#define MIN_TU_SIZE 4 + +/* Max Transform Size */ +#define MAX_TRANS_SIZE (MAX_TU_SIZE*MAX_TU_SIZE) + +/* PU Size Range */ +#define MAX_PU_SIZE 16 +#define MIN_PU_SIZE 4 + +/* Number of max TU in a MB row */ +#define MAX_TU_IN_MB_ROW ((MB_SIZE / MIN_TU_SIZE)) + +/* Number of max PU in a CTb row */ +#define MAX_PU_IN_MB_ROW ((MB_SIZE / MIN_PU_SIZE)) + + +/* Number of max PU in a MB */ +/*****************************************************************************/ +/* Note though for 64 x 64 MB, Max PU in MB is 128, in order to store */ +/* intra pred info, 256 entries are needed */ +/*****************************************************************************/ +#define MAX_PU_IN_MB ((MB_SIZE / MIN_PU_SIZE) * \ + (MB_SIZE / MIN_PU_SIZE)) + +/* Number of max TU in a MB */ +#define MAX_TU_IN_MB ((MB_SIZE / MIN_TU_SIZE) * \ + (MB_SIZE / MIN_TU_SIZE)) + + + +/** + * Maximum transform depths + */ +#define MAX_TRAFO_DEPTH 5 + +#define MAX_DC_4x4_SUBBLK_LUMA 1 +#define MAX_AC_4x4_SUBBLK_LUMA 16 +#define MAX_DC_4x4_SUBBLK_CHROMA 2 +#define MAX_AC_4x4_SUBBLK_CHROMA 8 + +#define MAX_4x4_SUBBLKS (MAX_DC_4x4_SUBBLK_LUMA + MAX_DC_4x4_SUBBLK_CHROMA +\ + MAX_AC_4x4_SUBBLK_LUMA + MAX_AC_4x4_SUBBLK_CHROMA) + +/* Max number of deblocking edges */ +#define MAX_VERT_DEBLK_EDGES ((MB_SIZE/8) * (MB_SIZE/4)) +#define MAX_HORZ_DEBLK_EDGES ((MB_SIZE/4) * (MB_SIZE/8)) + +/* Qp can not change below 8x8 level */ +#define MAX_DEBLK_QP_CNT ((MB_SIZE/8) * (MB_SIZE/8)) + +/*****************************************************************************/ +/* Parsing related macros */ +/*****************************************************************************/ +#define SUBBLK_COEFF_CNT 16 + +/* Quant and Trans defs */ + +/*****************************************************************************/ +/* Sizes for Transform functions */ +/*****************************************************************************/ +#define TRANS_SIZE_4 4 +#define TRANS_SIZE_8 8 +#define TRANS_SIZE_16 16 +#define TRANS_SIZE_32 32 + + +#define IT_SHIFT_STAGE_1 7 +#define IT_SHIFT_STAGE_2 12 + +/** + * @breif Maximum transform dynamic range (excluding sign bit) + */ +#define MAX_TR_DYNAMIC_RANGE 15 + +/** + * @brief Q(QP%6) * IQ(QP%6) = 2^20 + */ +#define QUANT_IQUANT_SHIFT 20 + +/** + * @breif Q factor for Qp%6 multiplication + */ +#define QUANT_SHIFT 14 + +/** + * @breif Q shift factor for flat rescale matrix weights + */ +#define FLAT_RESCALE_MAT_Q_SHIFT 11 + +/** + * @breif Scaling matrix is represented in Q15 format + */ +#define SCALING_Q_SHIFT 15 + +/** + * @brief rounding factor for quantization represented in Q9 format + */ +#define QUANT_ROUND_FACTOR_Q 9 + +/** + * @brief Minimum qp supported in H264 spec + */ +#define MIN_H264_QP 0 + +/** + * @brief Maximum qp supported in H264 spec + */ +#define MAX_H264_QP 51 + +/** + * @brief Minimum delta scale supported in H264 spec + */ +#define MIN_H264_DELTA_SCALE (-128) + +/** + * @brief Maximum delta scale supported in H264 spec + */ +#define MAX_H264_DELTA_SCALE 127 + +/** + * @breif Total number of transform sizes + * used for sizeID while getting scale matrix + */ +#define NUM_UNIQUE_TRANS_SIZE 4 + +/** + * @breif Maximum number of bits in frameNumber signaling + */ +#define MAX_BITS_IN_FRAME_NUM 16 + +/** + * @breif Maximum number of bits in POC LSB signaling + */ +#define MAX_BITS_IN_POC_LSB 16 + + +/** + * @breif Maximum PIC Order Count type + */ +#define MAX_PIC_ORDER_COUNT_TYPE 2 + + +/** + * @breif Maximum Weighted bipred idc + */ +#define MAX_WEIGHT_BIPRED_IDC 2 + +/*****************************************************************************/ +/* Number of scaling matrices for each transform size */ +/*****************************************************************************/ +#define SCALE_MAT_CNT_TRANS_SIZE_4 6 +#define SCALE_MAT_CNT_TRANS_SIZE_8 6 +#define SCALE_MAT_CNT_TRANS_SIZE_16 6 +#define SCALE_MAT_CNT_TRANS_SIZE_32 2 + +/* Maximum number of scale matrices for a given transform size */ +#define SCALE_MAT_CNT_MAX_PER_TRANS_SIZE 6 + +/* Total number of scale matrices */ +#define TOTAL_SCALE_MAT_COUNT (SCALE_MAT_CNT_TRANS_SIZE_4 + \ + SCALE_MAT_CNT_TRANS_SIZE_8 + \ + SCALE_MAT_CNT_TRANS_SIZE_16 + \ + SCALE_MAT_CNT_TRANS_SIZE_32) + + +/*****************************************************************************/ +/* Intra pred Macros */ +/*****************************************************************************/ +/** Planar Intra prediction mode */ +#define INTRA_PLANAR 0 + +/** DC Intra prediction mode */ +#define INTRA_DC 1 + +/** Gives angular mode for intra prediction */ +#define INTRA_ANGULAR(x) (x) + +/** Following is used to signal no intra prediction in case of pcm blocks + */ +#define INTRA_PRED_NONE 63 + + +/** Following is used to signal no intra prediction is needed for first three + * 4x4 luma blocks in case of 4x4 TU sizes + * Also used in pcm cases + */ +#define INTRA_PRED_CHROMA_IDX_NONE 7 + + +/** +****************************************************************************** + * @brief neighbor availability masks +****************************************************************************** + */ +#define LEFT_MB_AVAILABLE_MASK 0x01 +#define TOP_LEFT_MB_AVAILABLE_MASK 0x02 +#define TOP_MB_AVAILABLE_MASK 0x04 +#define TOP_RIGHT_MB_AVAILABLE_MASK 0x08 + +/** +****************************************************************************** + * @brief SEI macros +****************************************************************************** + */ +/* + * @brief specifies the number of colour primary components of the mastering display + */ +#define NUM_SEI_MDCV_PRIMARIES 3 + +/* + * @brief specifies the number of colour primary components of the nominal content colour volume + */ +#define NUM_SEI_CCV_PRIMARIES 3 + +#define DISPLAY_PRIMARIES_X_UPPER_LIMIT 37000 +#define DISPLAY_PRIMARIES_X_LOWER_LIMIT 5 +#define DISPLAY_PRIMARIES_X_DIVISION_FACTOR 5 + +#define DISPLAY_PRIMARIES_Y_UPPER_LIMIT 42000 +#define DISPLAY_PRIMARIES_Y_LOWER_LIMIT 5 +#define DISPLAY_PRIMARIES_Y_DIVISION_FACTOR 5 + +#define WHITE_POINT_X_UPPER_LIMIT 37000 +#define WHITE_POINT_X_LOWER_LIMIT 5 +#define WHITE_POINT_X_DIVISION_FACTOR 5 + +#define WHITE_POINT_Y_UPPER_LIMIT 42000 +#define WHITE_POINT_Y_LOWER_LIMIT 5 +#define WHITE_POINT_Y_DIVISION_FACTOR 5 + +#define MAX_DISPLAY_MASTERING_LUMINANCE_UPPER_LIMIT 100000000 +#define MAX_DISPLAY_MASTERING_LUMINANCE_LOWER_LIMIT 50000 +#define MAX_DISPLAY_MASTERING_LUMINANCE_DIVISION_FACTOR 10000 + +#define MIN_DISPLAY_MASTERING_LUMINANCE_UPPER_LIMIT 50000 +#define MIN_DISPLAY_MASTERING_LUMINANCE_LOWER_LIMIT 1 + +#define AMBIENT_LIGHT_X_UPPER_LIMIT 50000 +#define AMBIENT_LIGHT_Y_UPPER_LIMIT 50000 + +#define CCV_PRIMARIES_X_UPPER_LIMIT 5000000 +#define CCV_PRIMARIES_X_LOWER_LIMIT -5000000 +#define CCV_PRIMARIES_Y_UPPER_LIMIT 5000000 +#define CCV_PRIMARIES_Y_LOWER_LIMIT -5000000 + +#endif /* IH264_DEFS_H_ */ diff --git a/dependencies/ih264d/common/ih264_disp_mgr.c b/dependencies/ih264d/common/ih264_disp_mgr.c new file mode 100644 index 00000000..2bdb5244 --- /dev/null +++ b/dependencies/ih264d/common/ih264_disp_mgr.c @@ -0,0 +1,186 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** +******************************************************************************* +* @file +* ih264_disp_mgr.c +* +* @brief +* Contains function definitions for display management +* +* @author +* Srinivas T +* +* @par List of Functions: +* - ih264_disp_mgr_init() +* - ih264_disp_mgr_add() +* - ih264_disp_mgr_get() +* +* @remarks +* None +* +******************************************************************************* +*/ +#include +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_disp_mgr.h" + + +/** +******************************************************************************* +* +* @brief +* Initialization function for display buffer manager +* +* @par Description: +* Initializes the display buffer management structure +* +* @param[in] ps_disp_mgr +* Pointer to the display buffer management structure +* +* @returns none +* +* @remarks +* None +* +******************************************************************************* +*/ +void ih264_disp_mgr_init(disp_mgr_t *ps_disp_mgr) +{ + WORD32 id; + + ps_disp_mgr->u4_last_abs_poc = DEFAULT_POC; + + for(id = 0; id < DISP_MGR_MAX_CNT; id++) + { + ps_disp_mgr->ai4_abs_poc[id] = DEFAULT_POC; + ps_disp_mgr->apv_ptr[id] = NULL; + } +} + + +/** +******************************************************************************* +* +* @brief +* Adds a buffer to the display manager +* +* @par Description: +* Adds a buffer to the display buffer manager +* +* @param[in] ps_disp_mgr +* Pointer to the display buffer management structure +* +* @param[in] buf_id +* ID of the display buffer +* +* @param[in] abs_poc +* Absolute POC of the display buffer +* +* @param[in] pv_ptr +* Pointer to the display buffer +* +* @returns 0 if success, -1 otherwise +* +* @remarks +* None +* +******************************************************************************* +*/ +WORD32 ih264_disp_mgr_add(disp_mgr_t *ps_disp_mgr, + WORD32 buf_id, + WORD32 abs_poc, + void *pv_ptr) +{ + if(buf_id >= DISP_MGR_MAX_CNT) + { + return (-1); + } + + if(ps_disp_mgr->apv_ptr[buf_id] != NULL) + { + return (-1); + } + + ps_disp_mgr->apv_ptr[buf_id] = pv_ptr; + ps_disp_mgr->ai4_abs_poc[buf_id] = abs_poc; + return 0; +} + + +/** +******************************************************************************* +* +* @brief +* Gets the next buffer +* +* @par Description: +* Gets the next display buffer +* +* @param[in] ps_disp_mgr +* Pointer to the display buffer structure +* +* @param[out] pi4_buf_id +* Pointer to hold buffer id of the display buffer being returned +* +* @returns Pointer to the next display buffer +* +* @remarks +* None +* +******************************************************************************* +*/ +void* ih264_disp_mgr_get(disp_mgr_t *ps_disp_mgr, WORD32 *pi4_buf_id) +{ + WORD32 id; + void *pv_ret_ptr; + WORD32 i4_min_poc; + WORD32 min_poc_id; + + + pv_ret_ptr = NULL; + i4_min_poc = 0x7FFFFFFF; + min_poc_id = -1; + + /* Find minimum POC */ + for(id = 0; id < DISP_MGR_MAX_CNT; id++) + { + if((DEFAULT_POC != ps_disp_mgr->ai4_abs_poc[id]) && + (ps_disp_mgr->ai4_abs_poc[id] <= i4_min_poc)) + { + i4_min_poc = ps_disp_mgr->ai4_abs_poc[id]; + min_poc_id = id; + } + } + *pi4_buf_id = min_poc_id; + /* If all pocs are still default_poc then return NULL */ + if(-1 == min_poc_id) + { + return NULL; + } + + pv_ret_ptr = ps_disp_mgr->apv_ptr[min_poc_id]; + + /* Set abs poc to default and apv_ptr to null so that the buffer is not returned again */ + ps_disp_mgr->apv_ptr[min_poc_id] = NULL; + ps_disp_mgr->ai4_abs_poc[min_poc_id] = DEFAULT_POC; + return pv_ret_ptr; +} diff --git a/dependencies/ih264d/common/ih264_disp_mgr.h b/dependencies/ih264d/common/ih264_disp_mgr.h new file mode 100644 index 00000000..6f56493f --- /dev/null +++ b/dependencies/ih264d/common/ih264_disp_mgr.h @@ -0,0 +1,70 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** +******************************************************************************* +* @file +* ih264_disp_mgr.h +* +* @brief +* Function declarations used for display management +* +* @author +* Srinivas T +* +* +* @remarks +* None +* +******************************************************************************* +*/ +#ifndef _DISP_MGR_H_ +#define _DISP_MGR_H_ + +#define DISP_MGR_MAX_CNT 64 +#define DEFAULT_POC 0x7FFFFFFF + +typedef struct +{ + /** + * last_abs_poc + */ + UWORD32 u4_last_abs_poc; + + /** + * au4_abs_poc[DISP_MGR_MAX_CNT] + */ + WORD32 ai4_abs_poc[DISP_MGR_MAX_CNT]; + + /** + * apv_ptr[DISP_MGR_MAX_CNT] + */ + void *apv_ptr[DISP_MGR_MAX_CNT]; +}disp_mgr_t; + +void ih264_disp_mgr_init(disp_mgr_t *ps_disp_mgr); + +WORD32 ih264_disp_mgr_add(disp_mgr_t *ps_disp_mgr, + WORD32 id, + WORD32 abs_poc, + void *pv_ptr); + +void* ih264_disp_mgr_get(disp_mgr_t *ps_disp_mgr, WORD32 *pi4_buf_id); + +#endif //_DISP_MGR_H_ diff --git a/dependencies/ih264d/common/ih264_dpb_mgr.c b/dependencies/ih264d/common/ih264_dpb_mgr.c new file mode 100644 index 00000000..9380b7e7 --- /dev/null +++ b/dependencies/ih264d/common/ih264_dpb_mgr.c @@ -0,0 +1,1176 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** + ******************************************************************************* + * @file + * ih264_dpb_mgr.c + * + * @brief + * Function definitions used for decoded picture buffer management + * + * @author + * Srinivas T + * + * @par List of Functions: + * - ih264_dpb_mgr_init() + * - ih264_dpb_mgr_sort_short_term_fields_by_frame_num() + * - ih264_dpb_mgr_sort_short_term_fields_by_poc_l0() + * - ih264_dpb_mgr_sort_short_term_fields_by_poc_l1() + * - ih264_dpb_mgr_sort_long_term_fields_by_frame_idx() + * - ih264_dpb_mgr_alternate_ref_fields() + * - ih264_dpb_mgr_insert_ref_field() + * - ih264_dpb_mgr_insert_ref_frame() + * - ih264_dpb_mgr_count_ref_frames() + * - ih264_dpb_mgr_delete_ref_frame() + * - ih264_dpb_mgr_delete_long_ref_fields_max_frame_idx() + * - ih264_dpb_mgr_delete_short_ref_frame() + * - ih264_dpb_mgr_delete_all_ref_frames() + * - ih264_dpb_mgr_reset() + * - ih264_dpb_mgr_release_pics() + * + * @remarks + * None + * + ******************************************************************************* + */ + +#include +#include +#include + +#include "ih264_typedefs.h" +#include "ih264_defs.h" +#include "ih264_macros.h" +#include "ih264_error.h" +#include "ih264_structs.h" +#include "ih264_buf_mgr.h" +#include "ih264_dpb_mgr.h" +#include "ih264_debug.h" + +/** + ******************************************************************************* + * + * @brief + * DPB manager initializer + * + * @par Description: + * Initialises the DPB manager structure + * + * @param[in] ps_dpb_mgr + * Pointer to the DPB manager structure + * + * @returns + * + * @remarks + * + * + ******************************************************************************* + */ + +void ih264_dpb_mgr_init(dpb_mgr_t *ps_dpb_mgr) +{ + UWORD32 i; + dpb_info_t *ps_dpb_info = ps_dpb_mgr->as_dpb_info; + for(i = 0; i < MAX_DPB_BUFS; i++) + { + ps_dpb_info[i].ps_prev_dpb = NULL; + ps_dpb_info[i].ps_pic_buf = NULL; + ps_dpb_mgr->as_top_field_pics[i].i4_used_as_ref = INVALID; + ps_dpb_mgr->as_bottom_field_pics[i].i4_used_as_ref = INVALID; + ps_dpb_mgr->as_top_field_pics[i].i1_field_type = INVALID; + ps_dpb_mgr->as_bottom_field_pics[i].i1_field_type = INVALID; + ps_dpb_mgr->as_top_field_pics[i].i4_long_term_frame_idx = -1; + ps_dpb_mgr->as_bottom_field_pics[i].i4_long_term_frame_idx = -1; + } + + ps_dpb_mgr->u1_num_short_term_ref_bufs = 0; + ps_dpb_mgr->u1_num_long_term_ref_bufs = 0; + ps_dpb_mgr->ps_dpb_short_term_head = NULL; + ps_dpb_mgr->ps_dpb_long_term_head = NULL; +} + +/** + ******************************************************************************* + * + * @brief + * Function to sort sort term pics by frame_num. + * + * @par Description: + * Sorts short term fields by frame_num. For 2 fields having same frame_num, + * orders them based on requested first field type. + * + * @param[in] ps_dpb_mgr + * Pointer to the DPB manager structure + * + * @param[in] curr_frame_num + * frame_num of the current pic + * + * @param[in] first_field_type + * For complementary fields, required first field + * + * @param[in] max_frame_num + * Maximum frame_num allowed + * + * @returns + * + * @remarks + * + * + ******************************************************************************* + */ +WORD32 ih264_dpb_mgr_sort_short_term_fields_by_frame_num(dpb_mgr_t *ps_dpb_mgr, + WORD32 curr_frame_num, + WORD32 first_field_type, + WORD32 max_frame_num) +{ + dpb_info_t *ps_dpb_node1 = ps_dpb_mgr->ps_dpb_short_term_head; + dpb_info_t *ps_dpb_node2; + WORD32 frame_num_node1; + WORD32 frame_num_node2; + pic_buf_t *ps_pic_buf; + + if(ps_dpb_node1 == NULL) + return -1; + + for (; ps_dpb_node1 != NULL; ps_dpb_node1 = ps_dpb_node1->ps_prev_dpb) + { + for (ps_dpb_node2 = ps_dpb_node1->ps_prev_dpb; ps_dpb_node2 != NULL; ps_dpb_node2 = ps_dpb_node2->ps_prev_dpb) + { + frame_num_node1 = ps_dpb_node1->ps_pic_buf->i4_frame_num; + frame_num_node2 = ps_dpb_node2->ps_pic_buf->i4_frame_num; + + if(frame_num_node1 > curr_frame_num) + frame_num_node1 = frame_num_node1 - max_frame_num; + if(frame_num_node2 > curr_frame_num) + frame_num_node2 = frame_num_node2 - max_frame_num; + + if(frame_num_node1 < frame_num_node2) + { + ps_pic_buf = ps_dpb_node1->ps_pic_buf; + ps_dpb_node1->ps_pic_buf = ps_dpb_node2->ps_pic_buf; + ps_dpb_node2->ps_pic_buf = ps_pic_buf; + } + } + } + + /** + * For frames and complementary field pairs, + * ensure first_field_type appears first in the list + */ + ps_dpb_node1 = ps_dpb_mgr->ps_dpb_short_term_head; + ps_dpb_node2 = ps_dpb_node1->ps_prev_dpb; + while(ps_dpb_node2 != NULL) + { + pic_buf_t *ps_pic_node1 = ps_dpb_node1->ps_pic_buf; + pic_buf_t *ps_pic_node2 = ps_dpb_node2->ps_pic_buf; + frame_num_node1 = ps_pic_node1->i4_frame_num; + frame_num_node2 = ps_pic_node2->i4_frame_num; + if(frame_num_node1 == frame_num_node2) + { + ASSERT(ps_pic_node1->i1_field_type != ps_pic_node2->i1_field_type); + if(ps_pic_node1->i1_field_type != first_field_type) + { + ps_dpb_node1->ps_pic_buf = ps_pic_node2; + ps_dpb_node2->ps_pic_buf = ps_pic_node1; + } + } + ps_dpb_node1 = ps_dpb_node2; + ps_dpb_node2 = ps_dpb_node2->ps_prev_dpb; + } + return 0; + +} + +/** + ******************************************************************************* + * + * @brief + * Function to sort sort term pics by poc for list 0. + * + * @par Description: + * Orders all the pocs less than current poc in the descending order. + * Then orders all the pocs greater than current poc in the ascending order. + * + * @param[in] ps_dpb_mgr + * Pointer to the DPB manager structure + * + * @param[in] curr_poc + * Poc of the current pic + * + * @param[in] first_field_type + * For complementary fields, required first field + * + * @returns + * + * @remarks + * + * + ******************************************************************************* + */ +WORD32 ih264_dpb_mgr_sort_short_term_fields_by_poc_l0(dpb_mgr_t *ps_dpb_mgr, + WORD32 curr_poc, + WORD32 first_field_type) +{ + dpb_info_t *ps_dpb_node1 = ps_dpb_mgr->ps_dpb_short_term_head; + dpb_info_t *ps_dpb_node2; + WORD32 poc_node1; + WORD32 poc_node2; + WORD32 frame_num_node1; + WORD32 frame_num_node2; + pic_buf_t *ps_pic_buf; + + if(ps_dpb_node1 == NULL) + return -1; + + /** + * Sort the fields by poc. + * All POCs less than current poc are first placed in the descending order. + * Then all POCs greater than current poc are placed in the ascending order. + */ + for (; ps_dpb_node1 != NULL; ps_dpb_node1 = ps_dpb_node1->ps_prev_dpb) + { + for (ps_dpb_node2 = ps_dpb_node1->ps_prev_dpb; ps_dpb_node2 != NULL; ps_dpb_node2 = ps_dpb_node2->ps_prev_dpb) + { + poc_node1 = ps_dpb_node1->ps_pic_buf->i4_abs_poc; + poc_node2 = ps_dpb_node2->ps_pic_buf->i4_abs_poc; + ASSERT(poc_node1 != curr_poc); + ASSERT(poc_node2 != curr_poc); + if(((poc_node1 < curr_poc) && (poc_node2 > curr_poc)) || + ((poc_node1 < curr_poc) && (poc_node2 < curr_poc) && (poc_node1 > poc_node2)) || + ((poc_node1 > curr_poc) && (poc_node2 > curr_poc) && (poc_node1 < poc_node2))) + continue; + + ps_pic_buf = ps_dpb_node1->ps_pic_buf; + ps_dpb_node1->ps_pic_buf = ps_dpb_node2->ps_pic_buf; + ps_dpb_node2->ps_pic_buf = ps_pic_buf; + } + } + + ps_dpb_node1 = ps_dpb_mgr->ps_dpb_short_term_head; + ps_dpb_node2 = ps_dpb_node1->ps_prev_dpb; + while(ps_dpb_node2 != NULL) + { + pic_buf_t *ps_pic_node1 = ps_dpb_node1->ps_pic_buf; + pic_buf_t *ps_pic_node2 = ps_dpb_node2->ps_pic_buf; + frame_num_node1 = ps_pic_node1->i4_frame_num; + frame_num_node2 = ps_pic_node2->i4_frame_num; + if(frame_num_node1 == frame_num_node2) + { + ASSERT(ps_pic_node1->i1_field_type != ps_pic_node2->i1_field_type); + if(ps_pic_node1->i1_field_type != first_field_type) + { + ps_dpb_node1->ps_pic_buf = ps_pic_node2; + ps_dpb_node2->ps_pic_buf = ps_pic_node1; + } + } + ps_dpb_node1 = ps_dpb_node2; + ps_dpb_node2 = ps_dpb_node2->ps_prev_dpb; + } + return 0; + +} + +/** + ******************************************************************************* + * + * @brief + * Function to sort sort term pics by poc for list 1. + * + * @par Description: + * Orders all the pocs greater than current poc in the ascending order. + * Then rrders all the pocs less than current poc in the descending order. + * + * @param[in] ps_dpb_mgr + * Pointer to the DPB manager structure + * + * @param[in] curr_poc + * Poc of the current pic + * + * @param[in] first_field_type + * For complementary fields, required first field + * + * @returns + * + * @remarks + * + * + ******************************************************************************* + */ +WORD32 ih264_dpb_mgr_sort_short_term_fields_by_poc_l1(dpb_mgr_t *ps_dpb_mgr, + WORD32 curr_poc, + WORD32 first_field_type) +{ + dpb_info_t *ps_dpb_node1 = ps_dpb_mgr->ps_dpb_short_term_head; + dpb_info_t *ps_dpb_node2; + WORD32 poc_node1; + WORD32 poc_node2; + WORD32 frame_num_node1; + WORD32 frame_num_node2; + pic_buf_t *ps_pic_buf; + + if(ps_dpb_node1 == NULL) + return -1; + + /** + * Sort the fields by poc. + * All POCs greater than current poc are first placed in the ascending order. + * Then all POCs less than current poc are placed in the decending order. + */ + for (; ps_dpb_node1 != NULL; ps_dpb_node1 = ps_dpb_node1->ps_prev_dpb) + { + for (ps_dpb_node2 = ps_dpb_node1->ps_prev_dpb; ps_dpb_node2 != NULL; ps_dpb_node2 = ps_dpb_node2->ps_prev_dpb) + { + poc_node1 = ps_dpb_node1->ps_pic_buf->i4_abs_poc; + poc_node2 = ps_dpb_node2->ps_pic_buf->i4_abs_poc; + ASSERT(poc_node1 != curr_poc); + ASSERT(poc_node2 != curr_poc); + if(((poc_node1 > curr_poc) && (poc_node2 < curr_poc)) || + ((poc_node1 < curr_poc) && (poc_node2 < curr_poc) && (poc_node1 > poc_node2)) || + ((poc_node1 > curr_poc) && (poc_node2 > curr_poc) && (poc_node1 < poc_node2))) + continue; + + ps_pic_buf = ps_dpb_node1->ps_pic_buf; + ps_dpb_node1->ps_pic_buf = ps_dpb_node2->ps_pic_buf; + ps_dpb_node2->ps_pic_buf = ps_pic_buf; + } + } + + ps_dpb_node1 = ps_dpb_mgr->ps_dpb_short_term_head; + ps_dpb_node2 = ps_dpb_node1->ps_prev_dpb; + while(ps_dpb_node2 != NULL) + { + pic_buf_t *ps_pic_node1 = ps_dpb_node1->ps_pic_buf; + pic_buf_t *ps_pic_node2 = ps_dpb_node2->ps_pic_buf; + frame_num_node1 = ps_pic_node1->i4_frame_num; + frame_num_node2 = ps_pic_node2->i4_frame_num; + if(frame_num_node1 == frame_num_node2) + { + ASSERT(ps_pic_node1->i1_field_type != ps_pic_node2->i1_field_type); + if(ps_pic_node1->i1_field_type != first_field_type) + { + ps_dpb_node1->ps_pic_buf = ps_pic_node2; + ps_dpb_node2->ps_pic_buf = ps_pic_node1; + } + } + ps_dpb_node1 = ps_dpb_node2; + ps_dpb_node2 = ps_dpb_node2->ps_prev_dpb; + } + return 0; +} +/** + ******************************************************************************* + * + * @brief + * Function to sort long term pics by long term frame idx. + * + * @par Description: + * Sorts long term fields by long term frame idx. For 2 fields + * having same frame_num, orders them based on requested first field type. + * + * @param[in] ps_dpb_mgr + * Pointer to the DPB manager structure + * + * @param[in] first_field_type + * For complementary fields, required first field + * + * @returns + * + * @remarks + * + * + ******************************************************************************* + */ +WORD32 ih264_dpb_mgr_sort_long_term_fields_by_frame_idx(dpb_mgr_t *ps_dpb_mgr, + WORD32 first_field_type) +{ + dpb_info_t *ps_dpb_node1 = ps_dpb_mgr->ps_dpb_long_term_head; + dpb_info_t *ps_dpb_node2; + WORD32 frame_idx_node1; + WORD32 frame_idx_node2; + pic_buf_t *ps_pic_buf; + + if(ps_dpb_node1 == NULL) + return -1; + + /* Sort the fields by frame idx */ + for (; ps_dpb_node1 != NULL; ps_dpb_node1 = ps_dpb_node1->ps_prev_dpb) + { + for (ps_dpb_node2 = ps_dpb_node1->ps_prev_dpb; ps_dpb_node2 != NULL; ps_dpb_node2 = ps_dpb_node2->ps_prev_dpb) + { + frame_idx_node1 = ps_dpb_node1->ps_pic_buf->i4_long_term_frame_idx; + frame_idx_node2 = ps_dpb_node2->ps_pic_buf->i4_long_term_frame_idx; + + if(frame_idx_node1 > frame_idx_node2) + { + ps_pic_buf = ps_dpb_node1->ps_pic_buf; + ps_dpb_node1->ps_pic_buf = ps_dpb_node2->ps_pic_buf; + ps_dpb_node2->ps_pic_buf = ps_pic_buf; + } + } + } + + /** + * For frames and complementary field pairs, + * ensure first_field_type appears first in the list + */ + ps_dpb_node1 = ps_dpb_mgr->ps_dpb_long_term_head; + ps_dpb_node2 = ps_dpb_node1->ps_prev_dpb; + while(ps_dpb_node2 != NULL) + { + pic_buf_t *ps_pic_node1 = ps_dpb_node1->ps_pic_buf; + pic_buf_t *ps_pic_node2 = ps_dpb_node2->ps_pic_buf; + frame_idx_node1 = ps_pic_node1->i4_long_term_frame_idx; + frame_idx_node2 = ps_pic_node2->i4_long_term_frame_idx; + if(frame_idx_node1 == frame_idx_node2) + { + ASSERT(ps_pic_node1->i1_field_type != ps_pic_node2->i1_field_type); + if(ps_pic_node1->i1_field_type != first_field_type) + { + ps_dpb_node1->ps_pic_buf = ps_pic_node2; + ps_dpb_node2->ps_pic_buf = ps_pic_node1; + } + } + ps_dpb_node1 = ps_dpb_node2; + ps_dpb_node2 = ps_dpb_node2->ps_prev_dpb; + } + return 0; +} + +/** + ******************************************************************************* + * + * @brief + * Function to alternate fields. + * + * @par Description: + * In the ordered list of fields, alternate fields starting with + * first_field_type + * + * @param[in] ps_dpb_mgr + * Pointer to the DPB manager structure + * + * @param[in] reference_type + * This is used to select between short-term and long-term linked list. + * + * @param[in] first_field_type + * For complementary fields, required first field + * + * @returns + * + * @remarks + * + * + ******************************************************************************* + */ +WORD32 ih264_dpb_mgr_alternate_ref_fields(dpb_mgr_t *ps_dpb_mgr, + WORD32 reference_type, + WORD32 first_field_type) +{ + dpb_info_t s_dpb_head; + dpb_info_t *ps_dpb_head; + dpb_info_t *ps_dpb_node1; + dpb_info_t *ps_dpb_node2; + dpb_info_t *ps_dpb_node3; + dpb_info_t *ps_dpb_node4; + WORD32 expected_field; + + expected_field = first_field_type; + + ps_dpb_head = &s_dpb_head; + + ps_dpb_head->ps_prev_dpb = (reference_type == SHORT_TERM_REF) ? + ps_dpb_mgr->ps_dpb_short_term_head: + ps_dpb_mgr->ps_dpb_long_term_head; + + ps_dpb_node1 = ps_dpb_head; + ps_dpb_node2 = ps_dpb_node1->ps_prev_dpb; + while(ps_dpb_node2 != NULL) + { + pic_buf_t *ps_pic_node2 = ps_dpb_node2->ps_pic_buf; + if(ps_pic_node2->i1_field_type != expected_field) + { + /* + * If it is not expected field, loop over the node till + * the expected field. + */ + ps_dpb_node3 = ps_dpb_node2; + ps_dpb_node4 = ps_dpb_node2->ps_prev_dpb; + while((ps_dpb_node4 != NULL) && + (ps_dpb_node4->ps_pic_buf->i1_field_type != expected_field)) + { + ps_dpb_node3 = ps_dpb_node4; + ps_dpb_node4 = ps_dpb_node4->ps_prev_dpb; + } + if(ps_dpb_node4 != NULL) + { + ps_dpb_node1->ps_prev_dpb = ps_dpb_node4; + ps_dpb_node3->ps_prev_dpb = ps_dpb_node4->ps_prev_dpb; + ps_dpb_node4->ps_prev_dpb = ps_dpb_node2; + } + else + { + /* node4 null means we have reached the end */ + break; + } + } + ps_dpb_node1 = ps_dpb_node1->ps_prev_dpb; + ps_dpb_node2 = ps_dpb_node1->ps_prev_dpb; + expected_field = (ps_dpb_node1->ps_pic_buf->i1_field_type == TOP_FIELD)? + BOTTOM_FIELD:TOP_FIELD; + } + + if(reference_type == SHORT_TERM_REF) + { + ps_dpb_mgr->ps_dpb_short_term_head = ps_dpb_head->ps_prev_dpb; + } + else + { + ps_dpb_mgr->ps_dpb_long_term_head = ps_dpb_head->ps_prev_dpb; + } + + return 0; +} + +/** + ******************************************************************************* + * + * @brief + * Add a ref field to short-term or long-term linked list. + * + * @par Description: + * This function adds a ref field to either short-term or long-term linked + * list. It picks up memory for the link from the array of dpb_info in + * dpb_mgr. The field is added to the beginning of the linked list and the + * head is set the the field. + * + * @param[in] ps_dpb_mgr + * Pointer to the DPB manager structure + * + * @param[in] ps_pic_buf + * Pic buf structure for the field being added. + * + * @param[in] reference_type + * This is used to select between short-term and long-term linked list. + * + * @param[in] frame_num + * frame_num for the field. + * + * @param[in] long_term_frame_idx + * If the ref being added is long-term, long_term_frame_idx of the field. + * Otherwise invalid. + * + * @returns + * + * @remarks + * + * + ******************************************************************************* + */ +WORD32 ih264_dpb_mgr_insert_ref_field(dpb_mgr_t *ps_dpb_mgr, + pic_buf_t *ps_pic_buf, + WORD32 reference_type, + UWORD32 frame_num, + WORD32 long_term_frame_idx) +{ + WORD32 i; + dpb_info_t *ps_dpb_info; + dpb_info_t *ps_dpb_head; + + ps_dpb_info = ps_dpb_mgr->as_dpb_info; + + /* Return error if buffer is already present in the DPB */ + for(i = 0; i < MAX_DPB_BUFS; i++) + { + if( (ps_dpb_info[i].ps_pic_buf == ps_pic_buf) + && (ps_dpb_info[i].ps_pic_buf->i4_used_as_ref == reference_type) ) + { + return (-1); + } + } + + /* Find an unused DPB location */ + for(i = 0; i < MAX_DPB_BUFS; i++) + { + if(NULL == ps_dpb_info[i].ps_pic_buf) + { + break; + } + } + if(i == MAX_DPB_BUFS) + { + return (-1); + } + + ps_dpb_head = (reference_type == SHORT_TERM_REF) + ?ps_dpb_mgr->ps_dpb_short_term_head + :ps_dpb_mgr->ps_dpb_long_term_head; + + if(reference_type == SHORT_TERM_REF) + long_term_frame_idx = -1; + + /* Create DPB info */ + ps_dpb_info[i].ps_pic_buf = ps_pic_buf; + ps_dpb_info[i].ps_prev_dpb = ps_dpb_head; + ps_dpb_info[i].ps_pic_buf->i4_used_as_ref = reference_type; + ps_dpb_info[i].ps_pic_buf->i4_frame_num = frame_num; + ps_dpb_info[i].ps_pic_buf->i4_long_term_frame_idx = long_term_frame_idx; + + /* update the head node of linked list to point to the current picture */ + if(reference_type == SHORT_TERM_REF) + { + ps_dpb_mgr->ps_dpb_short_term_head = ps_dpb_info + i; + + /* Increment Short term buffer count */ + ps_dpb_mgr->u1_num_short_term_ref_bufs++; + + } + else + { + ps_dpb_mgr->ps_dpb_long_term_head = ps_dpb_info + i; + + /* Increment Long term buffer count */ + ps_dpb_mgr->u1_num_long_term_ref_bufs++; + } + + return 0; +} + +/** + ******************************************************************************* + * + * @brief + * Add a ref frame to short-term or long-term linked list. + * + * @par Description: + * This function adds a ref frame to either short-term or long-term linked + * list. Internally it calls add ref field twice to add top and bottom field. + * + * @param[in] ps_dpb_mgr + * Pointer to the DPB manager structure + * + * @param[in] ps_pic_buf + * Pic buf structure for the field being added. + * + * @param[in] reference_type + * This is used to select between short-term and long-term linked list. + * + * @param[in] frame_num + * frame_num for the field. + * + * @param[in] long_term_frame_idx + * If the ref being added is long-term, long_term_frame_idx of the field. + * Otherwise invalid. + * + * @returns + * + * @remarks + * + * + ******************************************************************************* + */ +WORD32 ih264_dpb_mgr_insert_ref_frame(dpb_mgr_t *ps_dpb_mgr, + pic_buf_t *ps_pic_buf, + WORD32 reference_type, + UWORD32 frame_num, + WORD32 long_term_frame_idx) +{ + WORD32 buf_id; + pic_buf_t *ps_pic_top; + pic_buf_t *ps_pic_bottom; + WORD32 ret; + + /* + * For a frame, since the ps_pic_buf passed to this function is that of top field + * obtain bottom field using buf_id. + */ + ps_pic_top = ps_pic_buf; + buf_id = ps_pic_top->i4_buf_id; + ps_pic_bottom = &ps_dpb_mgr->as_bottom_field_pics[buf_id]; + + /* Insert top field */ + ret = ih264_dpb_mgr_insert_ref_field(ps_dpb_mgr, + ps_pic_top, + reference_type, + frame_num, + long_term_frame_idx); + + if(ret != 0) + return ret; + + /* Insert bottom field */ + ret = ih264_dpb_mgr_insert_ref_field(ps_dpb_mgr, + ps_pic_bottom, + reference_type, + frame_num, + long_term_frame_idx); + + if(ret != 0) + return ret; + + return ret; +} + +/** + ******************************************************************************* + * + * @brief + * Returns the number of ref frames in both the linked list. + * + * @par Description: + * Returns the count of number of frames, number of complementary field pairs + * and number of unpaired fields. + * + * @param[in] ps_dpb_mgr + * Pointer to the DPB manager structure + * + * @param[in] curr_frame_num + * frame_num for the field. + * + * @param[in] max_frame_num + * Maximum frame_num allowed + * + * @returns + * + * @remarks + * + * + ******************************************************************************* + */ +WORD32 ih264_dpb_mgr_count_ref_frames(dpb_mgr_t *ps_dpb_mgr, + WORD32 curr_frame_num, + WORD32 max_frame_num) +{ + WORD32 numShortTerm = 0; + WORD32 numLongTerm = 0; + dpb_info_t *ps_dpb_node; + WORD32 frame_num; + WORD32 prev_frame_num; + + /* + * Compute the number of short-term frames/complementary field pairs/ + * unpaired fields + */ + if(ps_dpb_mgr->ps_dpb_short_term_head != NULL) + { + /* Sort the short-term list by frame_num */ + ih264_dpb_mgr_sort_short_term_fields_by_frame_num(ps_dpb_mgr, + curr_frame_num, + TOP_FIELD, + max_frame_num); + + ps_dpb_node = ps_dpb_mgr->ps_dpb_short_term_head; + if(ps_dpb_node != NULL) + { + numShortTerm++; + prev_frame_num = ps_dpb_node->ps_pic_buf->i4_frame_num; + ps_dpb_node = ps_dpb_node->ps_prev_dpb; + } + + while(ps_dpb_node != NULL) + { + frame_num = ps_dpb_node->ps_pic_buf->i4_frame_num; + if(frame_num != prev_frame_num) + numShortTerm++; + prev_frame_num = ps_dpb_node->ps_pic_buf->i4_frame_num; + ps_dpb_node = ps_dpb_node->ps_prev_dpb; + } + } + + /* + * Compute the number of long-term frames/complementary field pairs/ + * unpaired fields + */ + if(ps_dpb_mgr->ps_dpb_long_term_head != NULL) + { + ih264_dpb_mgr_sort_long_term_fields_by_frame_idx(ps_dpb_mgr, + TOP_FIELD); + + ps_dpb_node = ps_dpb_mgr->ps_dpb_long_term_head; + if(ps_dpb_node != NULL) + { + numLongTerm++; + prev_frame_num = ps_dpb_node->ps_pic_buf->i4_frame_num; + ps_dpb_node = ps_dpb_node->ps_prev_dpb; + } + + while(ps_dpb_node != NULL) + { + frame_num = ps_dpb_node->ps_pic_buf->i4_frame_num; + if(frame_num != prev_frame_num) + numLongTerm++; + prev_frame_num = ps_dpb_node->ps_pic_buf->i4_frame_num; + ps_dpb_node = ps_dpb_node->ps_prev_dpb; + } + } + return (numShortTerm + numLongTerm); +} + +/** + ******************************************************************************* + * + * @brief + * Deletes the ref frame at the end of the linked list. + * + * @par Description: + * Deletes the ref frame at the end of the linked list. For unpaired fields, + * it deletes just the last node. For frame or complementary field pair, it + * deletes the last two nodes. + * + * @param[in] ps_dpb_mgr + * Pointer to the DPB manager structure + * + * @param[in] reference_type + * This is used to select between short-term and long-term linked list. + * + * @returns + * + * @remarks + * + * + ******************************************************************************* + */ +WORD32 ih264_dpb_mgr_delete_ref_frame(dpb_mgr_t *ps_dpb_mgr, + WORD32 reference_type) +{ + dpb_info_t *ps_dpb_node1; + dpb_info_t *ps_dpb_node2; + dpb_info_t *ps_dpb_node3; + + /* + * Assumption: The nodes sorted for frame num. + */ + + + /* Select bw short-term and long-term list. */ + ps_dpb_node1 = (reference_type == SHORT_TERM_REF) + ?ps_dpb_mgr->ps_dpb_short_term_head + :ps_dpb_mgr->ps_dpb_long_term_head; + /* If null, no entries in the list. Hence return. */ + if(ps_dpb_node1 == NULL) + return 0; + + /* If only one node in the list, set as unsed for refer and return. */ + if(ps_dpb_node1->ps_prev_dpb == NULL) + { + /* Set the picture as unused for reference */ + ps_dpb_node1->ps_pic_buf->i4_used_as_ref = UNUSED_FOR_REF; + ps_dpb_node1->ps_pic_buf = NULL; + + if(reference_type == SHORT_TERM_REF) + { + ps_dpb_mgr->ps_dpb_short_term_head = NULL; + + /* Increment Short term buffer count */ + ps_dpb_mgr->u1_num_short_term_ref_bufs = 0; + + } + else + { + ps_dpb_mgr->ps_dpb_long_term_head = NULL; + + /* Increment Long term buffer count */ + ps_dpb_mgr->u1_num_long_term_ref_bufs = 0; + + } + return 0; + } + + /** + * If there are only 2 nodes in the list, set second node as unused for reference. + * If the frame_num of second node and first node is same, set first node also as + * unused for reference and set the corresponding head to NULL. + */ + ps_dpb_node2 = ps_dpb_node1->ps_prev_dpb; + if(ps_dpb_node2->ps_prev_dpb == NULL) + { + /* Set the picture as unused for reference */ + if(ps_dpb_node2->ps_pic_buf->i4_frame_num == ps_dpb_node1->ps_pic_buf->i4_frame_num) + { + /* Set the picture as unused for reference */ + ps_dpb_node1->ps_pic_buf->i4_used_as_ref = UNUSED_FOR_REF; + ps_dpb_node1->ps_pic_buf = NULL; + if(reference_type == SHORT_TERM_REF) + { + ps_dpb_mgr->ps_dpb_short_term_head = NULL; + + /* Increment Short term buffer count */ + ps_dpb_mgr->u1_num_short_term_ref_bufs = 0; + + } + else + { + ps_dpb_mgr->ps_dpb_long_term_head = NULL; + + /* Increment Long term buffer count */ + ps_dpb_mgr->u1_num_long_term_ref_bufs = 0; + + } + + } + ps_dpb_node2->ps_pic_buf->i4_used_as_ref = UNUSED_FOR_REF; + ps_dpb_node2->ps_pic_buf = NULL; + ps_dpb_node1->ps_prev_dpb = NULL; + return 0; + } + /* + * If there are more than 2 nodes, run a loop to get the last 3 nodes. + */ + ps_dpb_node3 = ps_dpb_node2->ps_prev_dpb; + while(ps_dpb_node3->ps_prev_dpb != NULL) + { + ps_dpb_node1 = ps_dpb_node2; + ps_dpb_node2 = ps_dpb_node3; + ps_dpb_node3 = ps_dpb_node3->ps_prev_dpb; + } + /* + * If node 2 and node 3 frame_nums are same, set node 2 also as unsed for + * reference and del reference from node1. + */ + if(ps_dpb_node2->ps_pic_buf->i4_frame_num == ps_dpb_node3->ps_pic_buf->i4_frame_num) + { + ps_dpb_node2->ps_pic_buf->i4_used_as_ref = UNUSED_FOR_REF; + ps_dpb_node2->ps_pic_buf = NULL; + ps_dpb_node1->ps_prev_dpb = NULL; + + } + /* Set the third node as unused for reference */ + ps_dpb_node3->ps_pic_buf->i4_used_as_ref = UNUSED_FOR_REF; + ps_dpb_node3->ps_pic_buf = NULL; + ps_dpb_node2->ps_prev_dpb = NULL; + + return 0; +} +/** + ******************************************************************************* + * + * @brief + * Delete long-term ref fields above max frame idx. + * + * @par Description: + * Deletes all the long-term ref fields having idx greater than max_frame_idx + * + * @param[in] ps_dpb_mgr + * Pointer to the DPB manager structure + * + * @param[in] max_frame_idx + * Max long-term frame idx allowed. + * + * @returns + * + * @remarks + * + * + ******************************************************************************* + */ +WORD32 ih264_dpb_mgr_delete_long_ref_fields_max_frame_idx(dpb_mgr_t *ps_dpb_mgr, + WORD32 max_frame_idx) +{ + dpb_info_t *ps_dpb_node1; + dpb_info_t *ps_dpb_node2; + /* + * Loop until there is node which isn't to be deleted is encountered. + */ + while(ps_dpb_mgr->ps_dpb_long_term_head != NULL) + { + if(ps_dpb_mgr->ps_dpb_long_term_head->ps_pic_buf->i4_long_term_frame_idx + <= max_frame_idx) + { + break; + } + ps_dpb_mgr->ps_dpb_long_term_head->ps_pic_buf->i4_used_as_ref = UNUSED_FOR_REF; + ps_dpb_mgr->ps_dpb_long_term_head->ps_pic_buf = NULL; + ps_dpb_mgr->ps_dpb_long_term_head = ps_dpb_mgr->ps_dpb_long_term_head->ps_prev_dpb; + } + + ps_dpb_node1 = ps_dpb_mgr->ps_dpb_long_term_head; + if(ps_dpb_node1 == NULL) + return 0; + /* + * With the node that isn't to be deleted as head, loop until the end. + */ + ps_dpb_node2 = ps_dpb_node1->ps_prev_dpb; + while(ps_dpb_node2 != NULL) + { + if(ps_dpb_node2->ps_pic_buf->i4_long_term_frame_idx > max_frame_idx) + { + ps_dpb_node2->ps_pic_buf->i4_used_as_ref = UNUSED_FOR_REF; + ps_dpb_node2->ps_pic_buf = NULL; + ps_dpb_node1->ps_prev_dpb = ps_dpb_node2->ps_prev_dpb; + } + ps_dpb_node1 = ps_dpb_node1->ps_prev_dpb; + if(ps_dpb_node1 == NULL) + break; + ps_dpb_node2 = ps_dpb_node1->ps_prev_dpb; + } + return 0; +} + +/** + ******************************************************************************* + * + * @brief + * Deletes the short-term with least frame_num + * + * @par Description: + * Deletes the short-term with least frame_num. It sorts the function the + * short-term linked list by frame-num and the function that deletes the last + * frame in the linked list. + * + * @param[in] ps_dpb_mgr + * Pointer to the DPB manager structure + * + * @param[in] curr_frame_num + * frame_num of the current pic + * + * @param[in] max_frame_num + * Maximum frame_num allowed + * + * @returns + * + * @remarks + * + * + ******************************************************************************* + */ +WORD32 ih264_dpb_mgr_delete_short_ref_frame(dpb_mgr_t *ps_dpb_mgr, + WORD32 curr_frame_num, + WORD32 max_frame_num) +{ + WORD32 ret; + /* Sort the short-term list by frame_num */ + ret = ih264_dpb_mgr_sort_short_term_fields_by_frame_num(ps_dpb_mgr, + curr_frame_num, + TOP_FIELD, + max_frame_num); + + /* Delete the last reference frame or field */ + ret = ih264_dpb_mgr_delete_ref_frame(ps_dpb_mgr,SHORT_TERM_REF); + + if(ret != 0) + { + ASSERT(0); + } + + return ret; +} +/** + ******************************************************************************* + * + * @brief + * Deletes all the ref frames. + * + * @par Description: + * Deletes all of the ref frames/fields in the short-term and long-term linked + * list. + * + * @param[in] ps_dpb_mgr + * Pointer to the DPB manager structure + * + * @returns + * + * @remarks + * + * + ******************************************************************************* + */ +WORD32 ih264_dpb_mgr_delete_all_ref_frames(dpb_mgr_t *ps_dpb_mgr) +{ + /* Loop over short-term linked list. */ + while(ps_dpb_mgr->ps_dpb_short_term_head != NULL) + { + ih264_dpb_mgr_delete_ref_frame(ps_dpb_mgr,SHORT_TERM_REF); + } + + /* Loop over long-term linked list. */ + while(ps_dpb_mgr->ps_dpb_long_term_head != NULL) + { + ih264_dpb_mgr_delete_ref_frame(ps_dpb_mgr,LONG_TERM_REF); + } + return 0; +} + + +void ih264_dpb_mgr_reset(dpb_mgr_t *ps_dpb_mgr, buf_mgr_t *ps_buf_mgr) +{ + WORD32 i; + dpb_info_t *ps_dpb_info; + ASSERT(0); + + + ps_dpb_info = ps_dpb_mgr->as_dpb_info; + + for(i = 0; i < MAX_DPB_BUFS; i++) + { + if(ps_dpb_info[i].ps_pic_buf->i4_used_as_ref) + { + ps_dpb_info[i].ps_pic_buf->i4_used_as_ref = UNUSED_FOR_REF; + ps_dpb_info[i].ps_prev_dpb = NULL; + //Release physical buffer + ih264_buf_mgr_release(ps_buf_mgr, ps_dpb_info[i].ps_pic_buf->i4_buf_id, + BUF_MGR_REF); + + ps_dpb_info[i].ps_pic_buf = NULL; + } + } + ps_dpb_mgr->u1_num_short_term_ref_bufs = 0; + ps_dpb_mgr->u1_num_long_term_ref_bufs = 0; + ps_dpb_mgr->ps_dpb_short_term_head = NULL; + ps_dpb_mgr->ps_dpb_long_term_head = NULL; + +} + +/** + ******************************************************************************* + * + * @brief + * deletes all pictures from DPB + * + * @par Description: + * Deletes all pictures present in the DPB manager + * + * @param[in] ps_buf_mgr + * Pointer to buffer manager structure + * + * @param[in] u1_disp_bufs + * Number of buffers to be deleted + * + * @returns + * + * @remarks + * + * + ******************************************************************************* + */ + +void ih264_dpb_mgr_release_pics(buf_mgr_t *ps_buf_mgr, UWORD8 u1_disp_bufs) +{ + WORD8 i; + UWORD32 buf_status; + ASSERT(0); + + for(i = 0; i < u1_disp_bufs; i++) + { + buf_status = ih264_buf_mgr_get_status(ps_buf_mgr, i); + if(0 != buf_status) + { + ih264_buf_mgr_release((buf_mgr_t *)ps_buf_mgr, i, BUF_MGR_REF); + } + } +} diff --git a/dependencies/ih264d/common/ih264_dpb_mgr.h b/dependencies/ih264d/common/ih264_dpb_mgr.h new file mode 100644 index 00000000..b0cf0fd2 --- /dev/null +++ b/dependencies/ih264d/common/ih264_dpb_mgr.h @@ -0,0 +1,186 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ + +/** + ******************************************************************************* + * @file + * ih264_dpb_mgr.h + * + * @brief + * Function declarations used for decoded picture buffer management + * + * @author + * Srinivas T + * + * + * @remarks + * None + * + ******************************************************************************* + */ +#ifndef _IH264_DPB_MGR_H_ +#define _IH264_DPB_MGR_H_ + +/* Temporary definitions. Have to be defined later */ + +#define MAX_DPB_BUFS (MAX_DPB_SIZE * 4) + +#define MARK_ST_PICNUM_AS_NONREF 1 +#define MARK_LT_INDEX_AS_NONREF 2 +#define MARK_ST_PICNUM_AS_LT_INDEX 3 +#define RESET_REF_PICTURES 5 + +typedef struct dpb_info_t dpb_info_t; + +enum +{ + INVALID = -1, + UNUSED_FOR_REF = 0 , + LONG_TERM_REF , + SHORT_TERM_REF , +}; +struct dpb_info_t +{ + /** + * Pointer to picture buffer structure + */ + pic_buf_t *ps_pic_buf; + + /** + * Link to the DPB buffer with previous link + */ + dpb_info_t *ps_prev_dpb; + +}; + +typedef struct +{ + /** + * Pointer to the most recent pic Num + */ + dpb_info_t *ps_dpb_short_term_head; + + /** + * Pointer to the most recent pic Num + */ + dpb_info_t *ps_dpb_long_term_head; + + /** + * Physical storage for dpbInfo for ref bufs + */ + dpb_info_t as_dpb_info[MAX_DPB_BUFS]; + + /** + * Array of structures for bottom field. + */ + pic_buf_t as_top_field_pics[MAX_DPB_BUFS]; + + /** + * Array of structures for bottom field. + */ + pic_buf_t as_bottom_field_pics[MAX_DPB_BUFS]; + + /** + * Number of short-term reference buffers + */ + UWORD8 u1_num_short_term_ref_bufs; + + /** + * Number of long-term reference buffers + */ + UWORD8 u1_num_long_term_ref_bufs; + + /** + * buffer ID current frame + */ + WORD32 i4_cur_frame_buf_id; + +} dpb_mgr_t; + +void ih264_dpb_mgr_init(dpb_mgr_t *ps_dpb_mgr); + +WORD32 ih264_dpb_mgr_insert_ref_frame(dpb_mgr_t *ps_dpb_mgr, + pic_buf_t *ps_pic_buf, + WORD32 reference_type, + UWORD32 frame_num, + WORD32 long_term_frame_idx); + +WORD32 ih264_dpb_mgr_delete_ref_frame(dpb_mgr_t *ps_dpb_mgr, + WORD32 reference_type); + +WORD32 ih264_dpb_mgr_delete_all_ref_frames(dpb_mgr_t *ps_dpb_mgr); + +WORD32 ih264_dpb_mgr_count_ref_frames(dpb_mgr_t *ps_dpb_mgr, + WORD32 curr_frame_num, + WORD32 max_frame_num); + +WORD32 ih264_dpb_mgr_delete_short_ref_frame(dpb_mgr_t *ps_dpb_mgr, + WORD32 curr_frame_num, + WORD32 max_frame_num); + +WORD32 ih264_dpb_mgr_insert_ref_field(dpb_mgr_t *ps_dpb_mgr, + pic_buf_t *ps_pic_buf, + WORD32 reference_type, + UWORD32 frame_num, + WORD32 long_term_frame_idx); + +WORD32 ih264_dpb_mgr_delete_ref_field(dpb_mgr_t *ps_dpb_mgr, + WORD32 reference_type); + +WORD32 ih264_dpb_mgr_alternate_ref_fields(dpb_mgr_t *ps_dpb_mgr, + WORD32 reference_type, + WORD32 first_field_type); + +WORD32 ih264_dpb_mgr_sort_short_term_fields_by_frame_num(dpb_mgr_t *ps_dpb_mgr, + WORD32 curr_frame_num, + WORD32 first_field_type, + WORD32 max_frame_num); + +WORD32 ih264_dpb_mgr_sort_short_term_fields_by_poc_l0(dpb_mgr_t *ps_dpb_mgr, + WORD32 curr_poc, + WORD32 first_field_type); + +WORD32 ih264_dpb_mgr_sort_short_term_fields_by_poc_l1(dpb_mgr_t *ps_dpb_mgr, + WORD32 curr_poc, + WORD32 first_field_type); + +WORD32 ih264_dpb_mgr_sort_long_term_fields_by_frame_idx(dpb_mgr_t *ps_dpb_mgr, + WORD32 first_field_type); + +WORD32 ih264_dpb_mgr_delete_long_ref_fields_max_frame_idx(dpb_mgr_t *ps_dpb_mgr, + WORD32 max_frame_idx); + +void ih264_dpb_mgr_del_ref(dpb_mgr_t *ps_dpb_mgr, + buf_mgr_t *ps_buf_mgr, + WORD32 u4_abs_poc); + +pic_buf_t *ih264_dpb_mgr_get_ref_by_nearest_poc(dpb_mgr_t *ps_dpb_mgr, + WORD32 cur_abs_poc); + +pic_buf_t *ih264_dpb_mgr_get_ref_by_poc(dpb_mgr_t *ps_dpb_mgr, WORD32 abs_poc); + +pic_buf_t *ih264_dpb_mgr_get_ref_by_poc_lsb(dpb_mgr_t *ps_dpb_mgr, + WORD32 poc_lsb); + +void ih264_dpb_mgr_reset(dpb_mgr_t *ps_dpb_mgr, buf_mgr_t *ps_buf_mgr); + +void ih264_dpb_mgr_release_pics(buf_mgr_t *ps_buf_mgr, UWORD8 u1_disp_bufs); + +#endif /* _IH264_DPB_MGR_H_ */ diff --git a/dependencies/ih264d/common/ih264_error.h b/dependencies/ih264d/common/ih264_error.h new file mode 100644 index 00000000..ff1662d6 --- /dev/null +++ b/dependencies/ih264d/common/ih264_error.h @@ -0,0 +1,68 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** +******************************************************************************* +* @file +* ih264_error.h +* +* @brief +* Definitions related to error handling for common modules +* +* @author +* Harish +* +* @par List of Functions: +* +* @remarks +* None +* +******************************************************************************* +*/ + +#ifndef _IH264_ERROR_H_ +#define _IH264_ERROR_H_ + +/** + * Enumerations for error codes used in the codec. + * Not all these are expected to be returned to the application. + * Only select few will be exported + */ +typedef enum +{ + /** + * No error + */ + IH264_SUCCESS = 0, + /** + * Start error code for decoder + */ + IH264_DEC_ERROR_START = 0x100, + + /** + * Start error code for encoder + */ + IH264_ENC_ERROR_START = 0x200, + /** + * Generic failure + */ + IH264_FAIL = 0x7FFFFFFF +}IH264_ERROR_T; + +#endif /* _IH264_ERROR_H_ */ diff --git a/dependencies/ih264d/common/ih264_ihadamard_scaling.c b/dependencies/ih264d/common/ih264_ihadamard_scaling.c new file mode 100644 index 00000000..e4729c83 --- /dev/null +++ b/dependencies/ih264d/common/ih264_ihadamard_scaling.c @@ -0,0 +1,216 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** + ******************************************************************************* + * @file + * ih264_ihadamard_scaling.c + * + * @brief + * Contains definition of functions for h264 inverse hadamard 4x4 transform and scaling + * + * @author + * Mohit + * + * @par List of Functions: + * - ih264_ihadamard_scaling_4x4() + * + * @remarks + * + ******************************************************************************* + */ + +/*****************************************************************************/ +/* File Includes */ +/*****************************************************************************/ + +/* User include files */ +#include "ih264_typedefs.h" +#include "ih264_defs.h" +#include "ih264_trans_macros.h" +#include "ih264_macros.h" +#include "ih264_trans_data.h" +#include "ih264_size_defs.h" +#include "ih264_structs.h" +#include "ih264_trans_quant_itrans_iquant.h" + +/* + ******************************************************************************** + * + * @brief This function performs a 4x4 inverse hadamard transform on the 4x4 DC coefficients + * of a 16x16 intra prediction macroblock, and then performs scaling. + * prediction buffer + * + * @par Description: + * The DC coefficients pass through a 2-stage inverse hadamard transform. + * This inverse transformed content is scaled to based on Qp value. + * + * @param[in] pi2_src + * input 4x4 block of DC coefficients + * + * @param[out] pi2_out + * output 4x4 block + * + * @param[in] pu2_iscal_mat + * pointer to scaling list + * + * @param[in] pu2_weigh_mat + * pointer to weight matrix + * + * @param[in] u4_qp_div_6 + * Floor (qp/6) + * + * @param[in] pi4_tmp + * temporary buffer of size 1*16 + * + * @returns none + * + * @remarks none + * + ******************************************************************************* + */ +void ih264_ihadamard_scaling_4x4(WORD16* pi2_src, + WORD16* pi2_out, + const UWORD16 *pu2_iscal_mat, + const UWORD16 *pu2_weigh_mat, + UWORD32 u4_qp_div_6, + WORD32* pi4_tmp) +{ + WORD32 i; + WORD32 x0, x1, x2, x3, x4, x5, x6, x7; + WORD16* pi2_src_ptr, *pi2_out_ptr; + WORD32* pi4_tmp_ptr; + WORD32 rnd_fact = (u4_qp_div_6 < 6) ? (1 << (5 - u4_qp_div_6)) : 0; + pi4_tmp_ptr = pi4_tmp; + pi2_src_ptr = pi2_src; + pi2_out_ptr = pi2_out; + // Horizontal transform + for(i = 0; i < SUB_BLK_WIDTH_4x4; i++) + { + x4 = pi2_src_ptr[0]; + x5 = pi2_src_ptr[1]; + x6 = pi2_src_ptr[2]; + x7 = pi2_src_ptr[3]; + + x0 = x4 + x7; + x1 = x5 + x6; + x2 = x5 - x6; + x3 = x4 - x7; + + pi4_tmp_ptr[0] = x0 + x1; + pi4_tmp_ptr[1] = x2 + x3; + pi4_tmp_ptr[2] = x0 - x1; + pi4_tmp_ptr[3] = x3 - x2; + + pi4_tmp_ptr += SUB_BLK_WIDTH_4x4; + pi2_src_ptr += SUB_BLK_WIDTH_4x4; + } + pi4_tmp_ptr = pi4_tmp; + // Vertical Transform + for(i = 0; i < SUB_BLK_WIDTH_4x4; i++) + { + x4 = pi4_tmp_ptr[0]; + x5 = pi4_tmp_ptr[4]; + x6 = pi4_tmp_ptr[8]; + x7 = pi4_tmp_ptr[12]; + + x0 = x4 + x7; + x1 = x5 + x6; + x2 = x5 - x6; + x3 = x4 - x7; + + pi4_tmp_ptr[0] = x0 + x1; + pi4_tmp_ptr[4] = x2 + x3; + pi4_tmp_ptr[8] = x0 - x1; + pi4_tmp_ptr[12] = x3 - x2; + + pi4_tmp_ptr++; + } + pi4_tmp_ptr = pi4_tmp; + //Scaling + for(i = 0; i < (SUB_BLK_WIDTH_4x4 * SUB_BLK_WIDTH_4x4); i++) + { + INV_QUANT(pi4_tmp_ptr[i], pu2_iscal_mat[0], pu2_weigh_mat[0], u4_qp_div_6, + rnd_fact, 6); + pi2_out_ptr[i] = pi4_tmp_ptr[i]; + } +} + +void ih264_ihadamard_scaling_2x2_uv(WORD16* pi2_src, + WORD16* pi2_out, + const UWORD16 *pu2_iscal_mat, + const UWORD16 *pu2_weigh_mat, + UWORD32 u4_qp_div_6, + WORD32* pi4_tmp) +{ + WORD32 i4_x0,i4_x1,i4_x2,i4_x3,i4_x4,i4_x5,i4_x6,i4_x7; + WORD32 i4_y0,i4_y1,i4_y2,i4_y3,i4_y4,i4_y5,i4_y6,i4_y7; + + UNUSED(pi4_tmp); + + i4_x4 = pi2_src[0]; + i4_x5 = pi2_src[1]; + i4_x6 = pi2_src[2]; + i4_x7 = pi2_src[3]; + + i4_x0 = i4_x4 + i4_x5; + i4_x1 = i4_x4 - i4_x5; + i4_x2 = i4_x6 + i4_x7; + i4_x3 = i4_x6 - i4_x7; + + i4_x4 = i4_x0+i4_x2; + i4_x5 = i4_x1+i4_x3; + i4_x6 = i4_x0-i4_x2; + i4_x7 = i4_x1-i4_x3; + + INV_QUANT(i4_x4,pu2_iscal_mat[0],pu2_weigh_mat[0],u4_qp_div_6,0,5); + INV_QUANT(i4_x5,pu2_iscal_mat[0],pu2_weigh_mat[0],u4_qp_div_6,0,5); + INV_QUANT(i4_x6,pu2_iscal_mat[0],pu2_weigh_mat[0],u4_qp_div_6,0,5); + INV_QUANT(i4_x7,pu2_iscal_mat[0],pu2_weigh_mat[0],u4_qp_div_6,0,5); + + pi2_out[0] = i4_x4; + pi2_out[1] = i4_x5; + pi2_out[2] = i4_x6; + pi2_out[3] = i4_x7; + + i4_y4 = pi2_src[4]; + i4_y5 = pi2_src[5]; + i4_y6 = pi2_src[6]; + i4_y7 = pi2_src[7]; + + i4_y0 = i4_y4 + i4_y5; + i4_y1 = i4_y4 - i4_y5; + i4_y2 = i4_y6 + i4_y7; + i4_y3 = i4_y6 - i4_y7; + + i4_y4 = i4_y0+i4_y2; + i4_y5 = i4_y1+i4_y3; + i4_y6 = i4_y0-i4_y2; + i4_y7 = i4_y1-i4_y3; + + INV_QUANT(i4_y4,pu2_iscal_mat[0],pu2_weigh_mat[0],u4_qp_div_6,0,5); + INV_QUANT(i4_y5,pu2_iscal_mat[0],pu2_weigh_mat[0],u4_qp_div_6,0,5); + INV_QUANT(i4_y6,pu2_iscal_mat[0],pu2_weigh_mat[0],u4_qp_div_6,0,5); + INV_QUANT(i4_y7,pu2_iscal_mat[0],pu2_weigh_mat[0],u4_qp_div_6,0,5); + + pi2_out[4] = i4_y4; + pi2_out[5] = i4_y5; + pi2_out[6] = i4_y6; + pi2_out[7] = i4_y7; +} diff --git a/dependencies/ih264d/common/ih264_inter_pred_filters.c b/dependencies/ih264d/common/ih264_inter_pred_filters.c new file mode 100644 index 00000000..7d1e4079 --- /dev/null +++ b/dependencies/ih264d/common/ih264_inter_pred_filters.c @@ -0,0 +1,1042 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** + ******************************************************************************* + * @file + * ih264_inter_pred_filters.c + * + * @brief + * Contains function definitions for inter prediction interpolation filters + * + * @author + * Ittiam + * + * @par List of Functions: + * - ih264_inter_pred_luma_copy + * - ih264_interleave_copy + * - ih264_inter_pred_luma_horz + * - ih264_inter_pred_luma_vert + * - ih264_inter_pred_luma_horz_hpel_vert_hpel + * - ih264_inter_pred_luma_horz_qpel + * - ih264_inter_pred_luma_vert_qpel + * - ih264_inter_pred_luma_horz_qpel_vert_qpel + * - ih264_inter_pred_luma_horz_hpel_vert_qpel + * - ih264_inter_pred_luma_horz_qpel_vert_hpel + * - ih264_inter_pred_luma_bilinear + * - ih264_inter_pred_chroma + * + * @remarks + * None + * + ******************************************************************************* + */ + +/*****************************************************************************/ +/* File Includes */ +/*****************************************************************************/ + +/* User include files */ +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264_inter_pred_filters.h" + + +/*****************************************************************************/ +/* Constant Data variables */ +/*****************************************************************************/ + +/* coefficients for 6 tap filtering*/ +const WORD32 ih264_g_six_tap[3] ={1,-5,20}; + + +/*****************************************************************************/ +/* Function definitions . */ +/*****************************************************************************/ +/** + ******************************************************************************* + * + * @brief + * Interprediction luma function for copy + * + * @par Description: + * Copies the array of width 'wd' and height 'ht' from the location pointed + * by 'src' to the location pointed by 'dst' + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * + * @param[in] ht + * integer height of the array + * + * @param[in] wd + * integer width of the array + * + * @returns + * + * @remarks + * None + * + ******************************************************************************* + */ + +void ih264_inter_pred_luma_copy(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ht, + WORD32 wd, + UWORD8* pu1_tmp, + WORD32 dydx) +{ + WORD32 row, col; + UNUSED(pu1_tmp); + UNUSED(dydx); + for(row = 0; row < ht; row++) + { + for(col = 0; col < wd; col++) + { + pu1_dst[col] = pu1_src[col]; + } + + pu1_src += src_strd; + pu1_dst += dst_strd; + } +} + +/** + ******************************************************************************* + * + * @brief + * Fucntion for copying to an interleaved destination + * + * @par Description: + * Copies the array of width 'wd' and height 'ht' from the location pointed + * by 'src' to the location pointed by 'dst' + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ht + * integer height of the array + * + * @param[in] wd + * integer width of the array + * + * @returns + * + * @remarks + * The alternate elements of src will be copied to alternate locations in dsr + * Other locations are not touched + * + ******************************************************************************* + */ +void ih264_interleave_copy(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ht, + WORD32 wd) +{ + WORD32 row, col; + wd *= 2; + + for(row = 0; row < ht; row++) + { + for(col = 0; col < wd; col+=2) + { + pu1_dst[col] = pu1_src[col]; + } + + pu1_src += src_strd; + pu1_dst += dst_strd; + } +} + +/** + ******************************************************************************* + * + * @brief + * Interprediction luma filter for horizontal input + * + * @par Description: + * Applies a 6 tap horizontal filter .The output is clipped to 8 bits + * sec 8.4.2.2.1 titled "Luma sample interpolation process" + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ht + * integer height of the array + * + * @param[in] wd + * integer width of the array + * + * @returns + * + * @remarks + * None + * + ******************************************************************************* + */ +void ih264_inter_pred_luma_horz(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ht, + WORD32 wd, + UWORD8* pu1_tmp, + WORD32 dydx) +{ + WORD32 row, col; + WORD16 i2_tmp; + UNUSED(pu1_tmp); + UNUSED(dydx); + + for(row = 0; row < ht; row++) + { + for(col = 0; col < wd; col++) + { + i2_tmp = 0;/*ih264_g_six_tap[] is the array containing the filter coeffs*/ + i2_tmp = ih264_g_six_tap[0] * + (pu1_src[col - 2] + pu1_src[col + 3]) + + ih264_g_six_tap[1] * + (pu1_src[col - 1] + pu1_src[col + 2]) + + ih264_g_six_tap[2] * + (pu1_src[col] + pu1_src[col + 1]); + i2_tmp = (i2_tmp + 16) >> 5; + pu1_dst[col] = CLIP_U8(i2_tmp); + } + + pu1_src += src_strd; + pu1_dst += dst_strd; + } + +} + +/** + ******************************************************************************* + * + * @brief + * Interprediction luma filter for vertical input + * + * @par Description: + * Applies a 6 tap vertical filter.The output is clipped to 8 bits + * sec 8.4.2.2.1 titled "Luma sample interpolation process" + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ht + * integer height of the array + * + * @param[in] wd + * integer width of the array + * + * @returns + * + * @remarks + * None + * + ******************************************************************************* + */ +void ih264_inter_pred_luma_vert(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ht, + WORD32 wd, + UWORD8* pu1_tmp, + WORD32 dydx) +{ + WORD32 row, col; + WORD16 i2_tmp; + UNUSED(pu1_tmp); + UNUSED(dydx); + + for(row = 0; row < ht; row++) + { + for(col = 0; col < wd; col++) + { + i2_tmp = 0; /*ih264_g_six_tap[] is the array containing the filter coeffs*/ + i2_tmp = ih264_g_six_tap[0] * + (pu1_src[col - 2 * src_strd] + pu1_src[col + 3 * src_strd]) + + ih264_g_six_tap[1] * + (pu1_src[col - 1 * src_strd] + pu1_src[col + 2 * src_strd]) + + ih264_g_six_tap[2] * + (pu1_src[col] + pu1_src[col + 1 * src_strd]); + i2_tmp = (i2_tmp + 16) >> 5; + pu1_dst[col] = CLIP_U8(i2_tmp); + } + pu1_src += src_strd; + pu1_dst += dst_strd; + } +} + +/*! + ************************************************************************** + * \if Function name : ih264_inter_pred_luma_horz_hpel_vert_hpel \endif + * + * \brief + * This function implements a two stage cascaded six tap filter. It + * applies the six tap filter in the horizontal direction on the + * predictor values, followed by applying the same filter in the + * vertical direction on the output of the first stage. The six tap + * filtering operation is described in sec 8.4.2.2.1 titled "Luma sample + * interpolation process" + * + * \param pu1_src: Pointer to the buffer containing the predictor values. + * pu1_src could point to the frame buffer or the predictor buffer. + * \param pu1_dst: Pointer to the destination buffer where the output of + * the six tap filter is stored. + * \param ht: Height of the rectangular pixel grid to be interpolated + * \param wd: Width of the rectangular pixel grid to be interpolated + * \param src_strd: Width of the buffer pointed to by pu1_src. + * \param dst_strd: Width of the destination buffer + * \param pu1_tmp: temporary buffer. + * \param dydx: x and y reference offset for qpel calculations: UNUSED in this function. + * + * \return + * None. + * + * \note + * This function takes the 8 bit predictor values, applies the six tap + * filter in the horizontal direction and outputs the result clipped to + * 8 bit precision. The input is stored in the buffer pointed to by + * pu1_src while the output is stored in the buffer pointed by pu1_dst. + * Both pu1_src and pu1_dst could point to the same buffer i.e. the + * six tap filter could be done in place. + * + ************************************************************************** + */ +void ih264_inter_pred_luma_horz_hpel_vert_hpel(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ht, + WORD32 wd, + UWORD8* pu1_tmp, + WORD32 dydx) +{ + WORD32 row, col; + WORD32 tmp; + WORD16* pi2_pred1_temp; + WORD16* pi2_pred1; + UNUSED(dydx); + pi2_pred1_temp = (WORD16*)pu1_tmp; + pi2_pred1_temp += 2; + pi2_pred1 = pi2_pred1_temp; + for(row = 0; row < ht; row++) + { + for(col = -2; col < wd + 3; col++) + { + tmp = 0;/*ih264_g_six_tap[] is the array containing the filter coeffs*/ + tmp = ih264_g_six_tap[0] * + (pu1_src[col - 2 * src_strd] + pu1_src[col + 3 * src_strd]) + + ih264_g_six_tap[1] * + (pu1_src[col - 1 * src_strd] + pu1_src[col + 2 * src_strd]) + + ih264_g_six_tap[2] * + (pu1_src[col] + pu1_src[col + 1 * src_strd]); + pi2_pred1_temp[col] = tmp; + } + pu1_src += src_strd; + pi2_pred1_temp = pi2_pred1_temp + wd + 5; + } + + for(row = 0; row < ht; row++) + { + for(col = 0; col < wd; col++) + { + tmp = 0;/*ih264_g_six_tap[] is the array containing the filter coeffs*/ + tmp = ih264_g_six_tap[0] * + (pi2_pred1[col - 2] + pi2_pred1[col + 3]) + + ih264_g_six_tap[1] * + (pi2_pred1[col - 1] + pi2_pred1[col + 2]) + + ih264_g_six_tap[2] * (pi2_pred1[col] + pi2_pred1[col + 1]); + tmp = (tmp + 512) >> 10; + pu1_dst[col] = CLIP_U8(tmp); + } + pi2_pred1 += (wd + 5); + pu1_dst += dst_strd; + } +} + +/*! + ************************************************************************** + * \if Function name : ih264_inter_pred_luma_horz_qpel \endif + * + * \brief + * This routine applies the six tap filter to the predictors in the + * horizontal direction. The six tap filtering operation is described in + * sec 8.4.2.2.1 titled "Luma sample interpolation process" + * + * \param pu1_src: Pointer to the buffer containing the predictor values. + * pu1_src could point to the frame buffer or the predictor buffer. + * \param pu1_dst: Pointer to the destination buffer where the output of + * the six tap filter is stored. + * \param ht: Height of the rectangular pixel grid to be interpolated + * \param wd: Width of the rectangular pixel grid to be interpolated + * \param src_strd: Width of the buffer pointed to by pu1_src. + * \param dst_strd: Width of the destination buffer + * \param pu1_tmp: temporary buffer: UNUSED in this function + * \param dydx: x and y reference offset for qpel calculations. + * + * \return + * None. + * + * \note + * This function takes the 8 bit predictor values, applies the six tap + * filter in the horizontal direction and outputs the result clipped to + * 8 bit precision. The input is stored in the buffer pointed to by + * pu1_src while the output is stored in the buffer pointed by pu1_dst. + * Both pu1_src and pu1_dst could point to the same buffer i.e. the + * six tap filter could be done in place. + * + ************************************************************************** + */ +void ih264_inter_pred_luma_horz_qpel(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ht, + WORD32 wd, + UWORD8* pu1_tmp, + WORD32 dydx) +{ + WORD32 row, col; + UWORD8 *pu1_pred1; + WORD32 x_offset = dydx & 0x3; + UNUSED(pu1_tmp); + pu1_pred1 = pu1_src + (x_offset >> 1); + + for(row = 0; row < ht; row++) + { + for(col = 0; col < wd; col++, pu1_src++, pu1_dst++) + { + WORD16 i2_temp; + /* The logic below implements the following equation + i2_temp = puc_pred[-2] - 5 * (puc_pred[-1] + puc_pred[2]) + + 20 * (puc_pred[0] + puc_pred[1]) + puc_pred[3]; */ + i2_temp = pu1_src[-2] + pu1_src[3] + - (pu1_src[-1] + pu1_src[2]) + + ((pu1_src[0] + pu1_src[1] - pu1_src[-1] - pu1_src[2]) << 2) + + ((pu1_src[0] + pu1_src[1]) << 4); + i2_temp = (i2_temp + 16) >> 5; + i2_temp = CLIP_U8(i2_temp); + *pu1_dst = (i2_temp + *pu1_pred1 + 1) >> 1; + + pu1_pred1++; + } + pu1_dst += dst_strd - wd; + pu1_src += src_strd - wd; + pu1_pred1 += src_strd - wd; + } +} + +/*! + ************************************************************************** + * \if Function name : ih264_inter_pred_luma_vert_qpel \endif + * + * \brief + * This routine applies the six tap filter to the predictors in the + * vertical direction and interpolates them to obtain pixels at quarter vertical + * positions (0, 1/4) and (0, 3/4). The six tap filtering operation is + * described in sec 8.4.2.2.1 titled "Luma sample interpolation process" + * + * \param pu1_src: Pointer to the buffer containing the predictor values. + * pu1_src could point to the frame buffer or the predictor buffer. + * \param pu1_dst: Pointer to the destination buffer where the output of + * the six tap filter is stored. + * \param ht: Height of the rectangular pixel grid to be interpolated + * \param wd: Width of the rectangular pixel grid to be interpolated + * \param src_strd: Width of the buffer pointed to by puc_pred. + * \param dst_strd: Width of the destination buffer + * \param pu1_tmp: temporary buffer: UNUSED in this function + * \param dydx: x and y reference offset for qpel calculations. + * + * \return + * void + * + * \note + * This function takes the 8 bit predictor values, applies the six tap + * filter in the vertical direction and outputs the result clipped to + * 8 bit precision. The input is stored in the buffer pointed to by + * puc_pred while the output is stored in the buffer pointed by puc_dest. + * Both puc_pred and puc_dest could point to the same buffer i.e. the + * six tap filter could be done in place. + * + * \para + * <paragraph> + * ... + ************************************************************************** + */ +void ih264_inter_pred_luma_vert_qpel(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ht, + WORD32 wd, + UWORD8* pu1_tmp, + WORD32 dydx) +{ + WORD32 row, col; + WORD32 y_offset = dydx >> 2; + WORD32 off1, off2, off3; + UWORD8 *pu1_pred1; + UNUSED(pu1_tmp); + y_offset = y_offset & 0x3; + + off1 = src_strd; + off2 = src_strd << 1; + off3 = off1 + off2; + + pu1_pred1 = pu1_src + (y_offset >> 1) * src_strd; + + for(row = 0; row < ht; row++) + { + for(col = 0; col < wd; col++, pu1_dst++, pu1_src++, pu1_pred1++) + { + WORD16 i2_temp; + /* The logic below implements the following equation + i16_temp = puc_pred[-2*src_strd] + puc_pred[3*src_strd] - + 5 * (puc_pred[-1*src_strd] + puc_pred[2*src_strd]) + + 20 * (puc_pred[0] + puc_pred[src_strd]); */ + i2_temp = pu1_src[-off2] + pu1_src[off3] + - (pu1_src[-off1] + pu1_src[off2]) + + ((pu1_src[0] + pu1_src[off1] - pu1_src[-off1] - pu1_src[off2]) << 2) + + ((pu1_src[0] + pu1_src[off1]) << 4); + i2_temp = (i2_temp + 16) >> 5; + i2_temp = CLIP_U8(i2_temp); + + *pu1_dst = (i2_temp + *pu1_pred1 + 1) >> 1; + } + pu1_src += src_strd - wd; + pu1_pred1 += src_strd - wd; + pu1_dst += dst_strd - wd; + } +} + +/*! + ************************************************************************** + * \if Function name : ih264_inter_pred_luma_horz_qpel_vert_qpel \endif + * + * \brief + * This routine applies the six tap filter to the predictors in the + * vertical and horizontal direction and averages them to get pixels at locations + * (1/4,1/4), (1/4, 3/4), (3/4, 1/4) & (3/4, 3/4). The six tap filtering operation + * is described in sec 8.4.2.2.1 titled "Luma sample interpolation process" + * + * \param pu1_src: Pointer to the buffer containing the predictor values. + * pu1_src could point to the frame buffer or the predictor buffer. + * \param pu1_dst: Pointer to the destination buffer where the output of + * the six tap filter is stored. + * \param wd: Width of the rectangular pixel grid to be interpolated + * \param ht: Height of the rectangular pixel grid to be interpolated + * \param src_strd: Width of the buffer pointed to by puc_pred. + * \param dst_strd: Width of the destination buffer + * \param pu1_tmp: temporary buffer, UNUSED in this function + * \param dydx: x and y reference offset for qpel calculations. + * + * \return + * void + * + * \note + * This function takes the 8 bit predictor values, applies the six tap + * filter in the vertical direction and outputs the result clipped to + * 8 bit precision. The input is stored in the buffer pointed to by + * puc_pred while the output is stored in the buffer pointed by puc_dest. + * Both puc_pred and puc_dest could point to the same buffer i.e. the + * six tap filter could be done in place. + * + * \para <title> + * <paragraph> + * ... + ************************************************************************** + */ +void ih264_inter_pred_luma_horz_qpel_vert_qpel(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ht, + WORD32 wd, + UWORD8* pu1_tmp, + WORD32 dydx) +{ + WORD32 row, col; + WORD32 x_offset = dydx & 0x3; + WORD32 y_offset = dydx >> 2; + + WORD32 off1, off2, off3; + UWORD8* pu1_pred_vert, *pu1_pred_horz; + UNUSED(pu1_tmp); + y_offset = y_offset & 0x3; + + off1 = src_strd; + off2 = src_strd << 1; + off3 = off1 + off2; + + pu1_pred_horz = pu1_src + (y_offset >> 1) * src_strd; + pu1_pred_vert = pu1_src + (x_offset >> 1); + + for(row = 0; row < ht; row++) + { + for(col = 0; col < wd; + col++, pu1_dst++, pu1_pred_vert++, pu1_pred_horz++) + { + WORD16 i2_temp_vert, i2_temp_horz; + /* The logic below implements the following equation + i2_temp = puc_pred[-2*src_strd] + puc_pred[3*src_strd] - + 5 * (puc_pred[-1*src_strd] + puc_pred[2*src_strd]) + + 20 * (puc_pred[0] + puc_pred[src_strd]); */ + i2_temp_vert = pu1_pred_vert[-off2] + pu1_pred_vert[off3] + - (pu1_pred_vert[-off1] + pu1_pred_vert[off2]) + + ((pu1_pred_vert[0] + pu1_pred_vert[off1] + - pu1_pred_vert[-off1] + - pu1_pred_vert[off2]) << 2) + + ((pu1_pred_vert[0] + pu1_pred_vert[off1]) << 4); + i2_temp_vert = (i2_temp_vert + 16) >> 5; + i2_temp_vert = CLIP_U8(i2_temp_vert); + + /* The logic below implements the following equation + i16_temp = puc_pred[-2] - 5 * (puc_pred[-1] + puc_pred[2]) + + 20 * (puc_pred[0] + puc_pred[1]) + puc_pred[3]; */ + i2_temp_horz = pu1_pred_horz[-2] + pu1_pred_horz[3] + - (pu1_pred_horz[-1] + pu1_pred_horz[2]) + + ((pu1_pred_horz[0] + pu1_pred_horz[1] + - pu1_pred_horz[-1] + - pu1_pred_horz[2]) << 2) + + ((pu1_pred_horz[0] + pu1_pred_horz[1]) << 4); + i2_temp_horz = (i2_temp_horz + 16) >> 5; + i2_temp_horz = CLIP_U8(i2_temp_horz); + *pu1_dst = (i2_temp_vert + i2_temp_horz + 1) >> 1; + } + pu1_pred_vert += (src_strd - wd); + pu1_pred_horz += (src_strd - wd); + pu1_dst += (dst_strd - wd); + } +} + +/*! + ************************************************************************** + * \if Function name : ih264_inter_pred_luma_horz_qpel_vert_hpel \endif + * + * \brief + * This routine applies the six tap filter to the predictors in the vertical + * and horizontal direction to obtain the pixel at (1/2,1/2). It then interpolates + * pixel at (0,1/2) and (1/2,1/2) to obtain pixel at (1/4,1/2). Similarly for (3/4,1/2). + * The six tap filtering operation is described in sec 8.4.2.2.1 titled + * "Luma sample interpolation process" + * + * \param pu1_src: Pointer to the buffer containing the predictor values. + * pu1_src could point to the frame buffer or the predictor buffer. + * \param pu1_dst: Pointer to the destination buffer where the output of + * the six tap filter followed by interpolation is stored. + * \param wd: Width of the rectangular pixel grid to be interpolated + * \param ht: Height of the rectangular pixel grid to be interpolated + * \param src_strd: Width of the buffer pointed to by puc_pred. + * \param dst_strd: Width of the destination buffer + * \param pu1_tmp: buffer to store temporary output after 1st 6-tap filter. + * \param dydx: x and y reference offset for qpel calculations. + * + * \return + * void + * + * \note + * This function takes the 8 bit predictor values, applies the six tap + * filter in the vertical direction and outputs the result clipped to + * 8 bit precision. The input is stored in the buffer pointed to by + * puc_pred while the output is stored in the buffer pointed by puc_dest. + * Both puc_pred and puc_dest could point to the same buffer i.e. the + * six tap filter could be done in place. + * + * \para <title> + * <paragraph> + * ... + ************************************************************************** + */ +void ih264_inter_pred_luma_horz_qpel_vert_hpel(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ht, + WORD32 wd, + UWORD8* pu1_tmp, + WORD32 dydx) +{ + WORD32 row, col; + WORD32 tmp; + WORD16* pi2_pred1_temp, *pi2_pred1; + UWORD8* pu1_dst_tmp; + WORD32 x_offset = dydx & 0x3; + WORD16 i2_macro; + + pi2_pred1_temp = (WORD16*)pu1_tmp; + pi2_pred1_temp += 2; + pi2_pred1 = pi2_pred1_temp; + pu1_dst_tmp = pu1_dst; + + for(row = 0; row < ht; row++) + { + for(col = -2; col < wd + 3; col++) + { + tmp = 0;/*ih264_g_six_tap[] is the array containing the filter coeffs*/ + tmp = ih264_g_six_tap[0] * + (pu1_src[col - 2 * src_strd] + pu1_src[col + 3 * src_strd]) + + ih264_g_six_tap[1] * + (pu1_src[col - 1 * src_strd] + pu1_src[col + 2 * src_strd]) + + ih264_g_six_tap[2] * + (pu1_src[col] + pu1_src[col + 1 * src_strd]); + pi2_pred1_temp[col] = tmp; + } + + pu1_src += src_strd; + pi2_pred1_temp = pi2_pred1_temp + wd + 5; + } + + pi2_pred1_temp = pi2_pred1; + for(row = 0; row < ht; row++) + { + for(col = 0; col < wd; col++) + { + tmp = 0;/*ih264_g_six_tap[] is the array containing the filter coeffs*/ + tmp = ih264_g_six_tap[0] * + (pi2_pred1[col - 2] + pi2_pred1[col + 3]) + + ih264_g_six_tap[1] * + (pi2_pred1[col - 1] + pi2_pred1[col + 2]) + + ih264_g_six_tap[2] * + (pi2_pred1[col] + pi2_pred1[col + 1]); + tmp = (tmp + 512) >> 10; + pu1_dst[col] = CLIP_U8(tmp); + } + pi2_pred1 += (wd + 5); + pu1_dst += dst_strd; + } + + pu1_dst = pu1_dst_tmp; + pi2_pred1_temp += (x_offset >> 1); + for(row = ht; row != 0; row--) + { + for(col = wd; col != 0; col--, pu1_dst++, pi2_pred1_temp++) + { + UWORD8 uc_temp; + /* Clipping the output of the six tap filter obtained from the + first stage of the 2d filter stage */ + *pi2_pred1_temp = (*pi2_pred1_temp + 16) >> 5; + i2_macro = (*pi2_pred1_temp); + uc_temp = CLIP_U8(i2_macro); + *pu1_dst = (*pu1_dst + uc_temp + 1) >> 1; + } + pi2_pred1_temp += 5; + pu1_dst += dst_strd - wd; + } +} + +/*! + ************************************************************************** + * \if Function name : ih264_inter_pred_luma_horz_hpel_vert_qpel \endif + * + * \brief + * This routine applies the six tap filter to the predictors in the horizontal + * and vertical direction to obtain the pixel at (1/2,1/2). It then interpolates + * pixel at (1/2,0) and (1/2,1/2) to obtain pixel at (1/2,1/4). Similarly for (1/2,3/4). + * The six tap filtering operation is described in sec 8.4.2.2.1 titled + * "Luma sample interpolation process" + * + * \param pu1_src: Pointer to the buffer containing the predictor values. + * pu1_src could point to the frame buffer or the predictor buffer. + * \param pu1_dst: Pointer to the destination buffer where the output of + * the six tap filter followed by interpolation is stored. + * \param wd: Width of the rectangular pixel grid to be interpolated + * \param ht: Height of the rectangular pixel grid to be interpolated + * \param src_strd: Width of the buffer pointed to by puc_pred. + * \param dst_strd: Width of the destination buffer + * \param pu1_tmp: buffer to store temporary output after 1st 6-tap filter. + * \param dydx: x and y reference offset for qpel calculations. + * + * \return + * void + * + * \note + * This function takes the 8 bit predictor values, applies the six tap + * filter in the vertical direction and outputs the result clipped to + * 8 bit precision. The input is stored in the buffer pointed to by + * puc_pred while the output is stored in the buffer pointed by puc_dest. + * Both puc_pred and puc_dest could point to the same buffer i.e. the + * six tap filter could be done in place. + * + * \para <title> + * <paragraph> + * ... + ************************************************************************** + */ +void ih264_inter_pred_luma_horz_hpel_vert_qpel(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ht, + WORD32 wd, + UWORD8* pu1_tmp, + WORD32 dydx) +{ + + WORD32 row, col; + WORD32 tmp; + WORD32 y_offset = dydx >> 2; + WORD16* pi2_pred1_temp, *pi2_pred1; + UWORD8* pu1_dst_tmp; + //WORD32 x_offset = dydx & 0x3; + WORD16 i2_macro; + + y_offset = y_offset & 0x3; + + pi2_pred1_temp = (WORD16*)pu1_tmp; + pi2_pred1_temp += 2 * wd; + pi2_pred1 = pi2_pred1_temp; + pu1_dst_tmp = pu1_dst; + pu1_src -= 2 * src_strd; + for(row = -2; row < ht + 3; row++) + { + for(col = 0; col < wd; col++) + { + tmp = 0;/*ih264_g_six_tap[] is the array containing the filter coeffs*/ + tmp = ih264_g_six_tap[0] * (pu1_src[col - 2] + pu1_src[col + 3]) + + ih264_g_six_tap[1] * (pu1_src[col - 1] + pu1_src[col + 2]) + + ih264_g_six_tap[2] * (pu1_src[col] + pu1_src[col + 1]); + pi2_pred1_temp[col - 2 * wd] = tmp; + } + + pu1_src += src_strd; + pi2_pred1_temp += wd; + } + pi2_pred1_temp = pi2_pred1; + for(row = 0; row < ht; row++) + { + for(col = 0; col < wd; col++) + { + tmp = 0;/*ih264_g_six_tap[] is the array containing the filter coeffs*/ + tmp = ih264_g_six_tap[0] * (pi2_pred1[col - 2 * wd] + pi2_pred1[col + 3 * wd]) + + ih264_g_six_tap[1] * (pi2_pred1[col - 1 * wd] + pi2_pred1[col + 2 * wd]) + + ih264_g_six_tap[2] * (pi2_pred1[col] + pi2_pred1[col + 1 * wd]); + tmp = (tmp + 512) >> 10; + pu1_dst[col] = CLIP_U8(tmp); + } + pi2_pred1 += wd; + pu1_dst += dst_strd; + } + pu1_dst = pu1_dst_tmp; + pi2_pred1_temp += (y_offset >> 1) * wd; + for(row = ht; row != 0; row--) + + { + for(col = wd; col != 0; col--, pu1_dst++, pi2_pred1_temp++) + { + UWORD8 u1_temp; + /* Clipping the output of the six tap filter obtained from the + first stage of the 2d filter stage */ + *pi2_pred1_temp = (*pi2_pred1_temp + 16) >> 5; + i2_macro = (*pi2_pred1_temp); + u1_temp = CLIP_U8(i2_macro); + *pu1_dst = (*pu1_dst + u1_temp + 1) >> 1; + } + //pi16_pred1_temp += wd; + pu1_dst += dst_strd - wd; + } +} + +/** + ******************************************************************************* + * function:ih264_inter_pred_luma_bilinear + * + * @brief + * This routine applies the bilinear filter to the predictors . + * The filtering operation is described in + * sec 8.4.2.2.1 titled "Luma sample interpolation process" + * + * @par Description: +\note + * This function is called to obtain pixels lying at the following + * locations (1/4,1), (3/4,1),(1,1/4), (1,3/4) ,(1/4,1/2), (3/4,1/2),(1/2,1/4), (1/2,3/4),(3/4,1/4),(1/4,3/4),(3/4,3/4)&& (1/4,1/4) . + * The function averages the two adjacent values from the two input arrays in horizontal direction. + * + * + * @param[in] pu1_src1: + * UWORD8 Pointer to the buffer containing the first input array. + * + * @param[in] pu1_src2: + * UWORD8 Pointer to the buffer containing the second input array. + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination where the output of bilinear filter is stored. + * + * @param[in] src_strd1 + * Stride of the first input buffer + * + * @param[in] src_strd2 + * Stride of the second input buffer + * + * @param[in] dst_strd + * integer destination stride of pu1_dst + * + * @param[in] ht + * integer height of the array + * + * @param[in] wd + * integer width of the array + * + * @returns + * + * @remarks + * None + * + ******************************************************************************* + */ +void ih264_inter_pred_luma_bilinear(UWORD8 *pu1_src1, + UWORD8 *pu1_src2, + UWORD8 *pu1_dst, + WORD32 src_strd1, + WORD32 src_strd2, + WORD32 dst_strd, + WORD32 ht, + WORD32 wd) +{ + WORD32 row, col; + WORD16 i2_tmp; + + for(row = 0; row < ht; row++) + { + for(col = 0; col < wd; col++) + { + i2_tmp = pu1_src1[col] + pu1_src2[col]; + i2_tmp = (i2_tmp + 1) >> 1; + pu1_dst[col] = CLIP_U8(i2_tmp); + } + pu1_src1 += src_strd1; + pu1_src2 += src_strd2; + pu1_dst += dst_strd; + } + +} + +/** + ******************************************************************************* + * + * @brief + * Interprediction chroma filter + * + * @par Description: + * Applies filtering to chroma samples as mentioned in + * sec 8.4.2.2.2 titled "chroma sample interpolation process" + * + * @param[in] pu1_src + * UWORD8 pointer to the source containing alternate U and V samples + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] u1_dx + * dx value where the sample is to be produced(refer sec 8.4.2.2.2 ) + * + * @param[in] u1_dy + * dy value where the sample is to be produced(refer sec 8.4.2.2.2 ) + * + * @param[in] ht + * integer height of the array + * + * @param[in] wd + * integer width of the array + * + * @returns + * + * @remarks + * None + * + ******************************************************************************* + */ +void ih264_inter_pred_chroma(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 dx, + WORD32 dy, + WORD32 ht, + WORD32 wd) +{ + WORD32 row, col; + WORD16 i2_tmp; + + for(row = 0; row < ht; row++) + { + for(col = 0; col < 2 * wd; col++) + { + i2_tmp = 0; /* applies equation (8-266) in section 8.4.2.2.2 */ + i2_tmp = (8 - dx) * (8 - dy) * pu1_src[col] + + (dx) * (8 - dy) * pu1_src[col + 2] + + (8 - dx) * (dy) * (pu1_src + src_strd)[col] + + (dx) * (dy) * (pu1_src + src_strd)[col + 2]; + i2_tmp = (i2_tmp + 32) >> 6; + pu1_dst[col] = CLIP_U8(i2_tmp); + } + pu1_src += src_strd; + pu1_dst += dst_strd; + } +} diff --git a/dependencies/ih264d/common/ih264_inter_pred_filters.h b/dependencies/ih264d/common/ih264_inter_pred_filters.h new file mode 100644 index 00000000..c439ab84 --- /dev/null +++ b/dependencies/ih264d/common/ih264_inter_pred_filters.h @@ -0,0 +1,241 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ + +/** + ******************************************************************************* + * @file + * ih264_inter_pred_filters.h + * + * @brief + * Declarations of functions used for inter prediction + * + * @author + * Ittiam + * + * @par List of Functions: + * -ih264_inter_pred_luma_copy + * -ih264_interleave_copy + * -ih264_inter_pred_luma_horz + * -ih264_inter_pred_luma_vert + * -ih264_inter_pred_luma_horz_hpel_vert_hpel + * -ih264_inter_pred_luma_vert_qpel + * -ih264_inter_pred_luma_horz_qpel + * -ih264_inter_pred_luma_horz_qpel_vert_qpel + * -ih264_inter_pred_luma_horz_qpel_vert_hpel + * -ih264_inter_pred_luma_horz_hpel_vert_qpel + * -ih264_inter_pred_luma_bilinear + * -ih264_inter_pred_chroma + * -ih264_inter_pred_luma_copy_a9q + * -ih264_interleave_copy_a9 + * -ih264_inter_pred_luma_horz_a9q + * -ih264_inter_pred_luma_vert_a9q + * -ih264_inter_pred_luma_bilinear_a9q + * -ih264_inter_pred_luma_horz_hpel_vert_hpel_a9q + * -ih264_inter_pred_luma_horz_qpel_a9q + * -ih264_inter_pred_luma_vert_qpel_a9q + * -ih264_inter_pred_luma_horz_qpel_vert_qpel_a9q + * -ih264_inter_pred_luma_horz_qpel_vert_hpel_a9q + * -ih264_inter_pred_luma_horz_hpel_vert_qpel_a9q + * -ih264_inter_pred_chroma_a9q + * -ih264_inter_pred_luma_copy_av8 + * -ih264_interleave_copy_av8 + * -ih264_inter_pred_luma_horz_av8 + * -ih264_inter_pred_luma_vert_av8 + * -ih264_inter_pred_luma_bilinear_av8 + * -ih264_inter_pred_luma_horz_hpel_vert_hpel_av8 + * -ih264_inter_pred_luma_horz_qpel_av8 + * -ih264_inter_pred_luma_vert_qpel_av8 + * -ih264_inter_pred_luma_horz_qpel_vert_qpel_av8 + * -ih264_inter_pred_luma_horz_qpel_vert_hpel_av8 + * -ih264_inter_pred_luma_horz_hpel_vert_qpel_av8 + * -ih264_inter_pred_chroma_av8 + * -ih264_inter_pred_chroma_dx_zero_av8 + * -ih264_inter_pred_chroma_dy_zero_av8 + * -ih264_inter_pred_luma_copy_ssse3 + * -ih264_inter_pred_luma_copy_ssse3 + * -ih264_inter_pred_luma_horz_ssse3 + * -ih264_inter_pred_luma_vert_ssse3 + * -ih264_inter_pred_luma_bilinear_ssse3 + * -ih264_inter_pred_luma_horz_hpel_vert_hpel_ssse3 + * -ih264_inter_pred_luma_horz_qpel_ssse3 + * -ih264_inter_pred_luma_vert_qpel_ssse3 + * -ih264_inter_pred_luma_horz_qpel_vert_qpel_ssse3 + * -ih264_inter_pred_luma_horz_qpel_vert_hpel_ssse3 + * -ih264_inter_pred_luma_horz_hpel_vert_qpel_ssse3 + * -ih264_inter_pred_chroma_ssse3 + * + * @remarks + * None + * + ******************************************************************************* + */ + +#ifndef _IH264_INTER_PRED_H_ +#define _IH264_INTER_PRED_H_ + +/*****************************************************************************/ +/* Constant Data variables */ +/*****************************************************************************/ + +extern const WORD32 ih264_g_six_tap[3];/* coefficients for 6 tap filtering*/ + +/*****************************************************************************/ +/* Extern Function Declarations */ +/*****************************************************************************/ + +typedef void ih264_inter_pred_luma_ft(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ht, + WORD32 wd, + UWORD8* pu1_tmp, + WORD32 dydx); + +typedef void ih264_interleave_copy_ft(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ht, + WORD32 wd); + +typedef void ih264_inter_pred_luma_bilinear_ft(UWORD8 *pu1_src1, + UWORD8 *pu1_src2, + UWORD8 *pu1_dst, + WORD32 src_strd1, + WORD32 src_strd2, + WORD32 dst_strd, + WORD32 height, + WORD32 width); + +typedef void ih264_inter_pred_chroma_ft(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 dx, + WORD32 dy, + WORD32 ht, + WORD32 wd); + +/* No NEON Declarations */ + +ih264_inter_pred_luma_ft ih264_inter_pred_luma_copy; + +ih264_interleave_copy_ft ih264_interleave_copy; + +ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz; + +ih264_inter_pred_luma_ft ih264_inter_pred_luma_vert; + +ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_hpel_vert_hpel; + +ih264_inter_pred_luma_ft ih264_inter_pred_luma_vert_qpel; + +ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_qpel; + +ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_qpel_vert_qpel; + +ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_qpel_vert_hpel; + +ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_hpel_vert_qpel; + +ih264_inter_pred_luma_bilinear_ft ih264_inter_pred_luma_bilinear; + +ih264_inter_pred_chroma_ft ih264_inter_pred_chroma; + +/* A9 NEON Declarations */ +ih264_inter_pred_luma_ft ih264_inter_pred_luma_copy_a9q; + +ih264_interleave_copy_ft ih264_interleave_copy_a9; + +ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_a9q; + +ih264_inter_pred_luma_ft ih264_inter_pred_luma_vert_a9q; + +ih264_inter_pred_luma_bilinear_ft ih264_inter_pred_luma_bilinear_a9q; + +ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_hpel_vert_hpel_a9q; + +ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_qpel_a9q; + +ih264_inter_pred_luma_ft ih264_inter_pred_luma_vert_qpel_a9q; + +ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_qpel_vert_qpel_a9q; + +ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_qpel_vert_hpel_a9q; + +ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_hpel_vert_qpel_a9q; + +ih264_inter_pred_chroma_ft ih264_inter_pred_chroma_a9q; + +/* AV8 NEON Declarations */ +ih264_inter_pred_luma_ft ih264_inter_pred_luma_copy_av8; + +ih264_interleave_copy_ft ih264_interleave_copy_av8; + +ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_av8; + +ih264_inter_pred_luma_ft ih264_inter_pred_luma_vert_av8; + +ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_hpel_vert_hpel_av8; + +ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_qpel_av8; + +ih264_inter_pred_luma_ft ih264_inter_pred_luma_vert_qpel_av8; + +ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_qpel_vert_qpel_av8; + +ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_qpel_vert_hpel_av8; + +ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_hpel_vert_qpel_av8; + +ih264_inter_pred_chroma_ft ih264_inter_pred_chroma_av8; + +ih264_inter_pred_chroma_ft ih264_inter_pred_chroma_dx_zero_av8; + +ih264_inter_pred_chroma_ft ih264_inter_pred_chroma_dy_zero_av8; + + +/* SSSE3 Intrinsic Declarations */ +ih264_inter_pred_luma_ft ih264_inter_pred_luma_copy_ssse3; + +ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_ssse3; + +ih264_inter_pred_luma_ft ih264_inter_pred_luma_vert_ssse3; + +ih264_inter_pred_luma_bilinear_ft ih264_inter_pred_luma_bilinear_ssse3; + +ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_hpel_vert_hpel_ssse3; + +ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_qpel_ssse3; + +ih264_inter_pred_luma_ft ih264_inter_pred_luma_vert_qpel_ssse3; + +ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_qpel_vert_qpel_ssse3; + +ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_qpel_vert_hpel_ssse3; + +ih264_inter_pred_luma_ft ih264_inter_pred_luma_horz_hpel_vert_qpel_ssse3; + +ih264_inter_pred_chroma_ft ih264_inter_pred_chroma_ssse3; + +#endif + +/** Nothing past this point */ diff --git a/dependencies/ih264d/common/ih264_intra_pred_filters.h b/dependencies/ih264d/common/ih264_intra_pred_filters.h new file mode 100644 index 00000000..caf6b335 --- /dev/null +++ b/dependencies/ih264d/common/ih264_intra_pred_filters.h @@ -0,0 +1,331 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** + ******************************************************************************* + * @file + * ih264_intra_pred_filters.h + * + * @brief + * Declarations of functions used for intra prediction + * + * @author + * Ittiam + * + * @remarks + * None + * + ******************************************************************************* + */ + +#ifndef IH264_INTRA_PRED_FILTERS_H_ + +#define IH264_INTRA_PRED_FILTERS_H_ + +/*****************************************************************************/ +/* Macro Expansion */ +/*****************************************************************************/ + +/*! Filter (1,2,1) i.e (a + 2b + c) / 4 */ +#define FILT121(a,b,c) ((a + (b<<1) + c + 2)>>2) +/*! Filter (1,1) i.e (a + b) / 2 */ +#define FILT11(a,b) ((a + b + 1)>>1) +/*****************************************************************************/ +/* Global Variables */ +/*****************************************************************************/ + +/* Global variables used only in assembly files*/ +extern const WORD8 ih264_gai1_intrapred_luma_plane_coeffs[]; +extern const WORD8 ih264_gai1_intrapred_chroma_plane_coeffs1[]; +extern const WORD8 ih264_gai1_intrapred_chroma_plane_coeffs2[]; +extern const WORD8 ih264_gai1_intrapred_luma_8x8_horz_u[]; + +/*****************************************************************************/ +/* Extern Function Declarations */ +/*****************************************************************************/ + + +typedef void ih264_intra_pred_ref_filtering_ft(UWORD8 *pu1_left, + UWORD8 *pu1_topleft, + UWORD8 *pu1_top, + UWORD8 *pu1_dst, + WORD32 left_strd, + WORD32 ngbr_avail); + +typedef void ih264_intra_pred_luma_ft(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail); + +/* No Neon Definitions */ + +/* Luma 4x4 Intra pred filters */ + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_vert; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_horz; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_dc; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_diag_dl; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_diag_dr; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_vert_r; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_horz_d; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_vert_l; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_horz_u; + +/* Luma 8x8 Intra pred filters */ + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_vert; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_horz; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_dc; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_diag_dl; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_diag_dr; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_vert_r; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_horz_d; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_vert_l; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_horz_u; + +/* Luma 16x16 Intra pred filters */ + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_16x16_mode_vert; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_16x16_mode_horz; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_16x16_mode_dc; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_16x16_mode_plane; + +/* Chroma 8x8 Intra pred filters */ + +typedef ih264_intra_pred_luma_ft ih264_intra_pred_chroma_ft; + +ih264_intra_pred_chroma_ft ih264_intra_pred_chroma_8x8_mode_dc; + +ih264_intra_pred_chroma_ft ih264_intra_pred_chroma_8x8_mode_horz; + +ih264_intra_pred_chroma_ft ih264_intra_pred_chroma_8x8_mode_vert; + +ih264_intra_pred_chroma_ft ih264_intra_pred_chroma_8x8_mode_plane; + + +ih264_intra_pred_ref_filtering_ft ih264_intra_pred_luma_8x8_mode_ref_filtering; + +/* A9 Definition */ + +/* Luma 4x4 Intra pred filters */ + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_vert_a9q; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_horz_a9q; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_dc_a9q; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_diag_dl_a9q; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_diag_dr_a9q; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_vert_r_a9q; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_horz_d_a9q; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_vert_l_a9q; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_horz_u_a9q; + +/* Luma 8x8 Intra pred filters */ + +ih264_intra_pred_ref_filtering_ft ih264_intra_pred_luma_8x8_mode_ref_filtering_a9q; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_vert_a9q; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_horz_a9q; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_dc_a9q; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_diag_dl_a9q; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_diag_dr_a9q; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_vert_r_a9q; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_horz_d_a9q; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_vert_l_a9q; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_horz_u_a9q; + +/* Luma 16x16 Intra pred filters */ + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_16x16_mode_vert_a9q; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_16x16_mode_horz_a9q; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_16x16_mode_dc_a9q; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_16x16_mode_plane_a9q; + +/* Chroma 8x8 Intra pred filters */ + +ih264_intra_pred_chroma_ft ih264_intra_pred_chroma_8x8_mode_dc_a9q; + +ih264_intra_pred_chroma_ft ih264_intra_pred_chroma_8x8_mode_horz_a9q; + +ih264_intra_pred_chroma_ft ih264_intra_pred_chroma_8x8_mode_vert_a9q; + +ih264_intra_pred_chroma_ft ih264_intra_pred_chroma_8x8_mode_plane_a9q; + +/* X86 Intrinsic Definitions */ + +/* Luma 4x4 Intra pred filters */ + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_vert_ssse3; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_horz_ssse3; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_dc_ssse3; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_diag_dl_ssse3; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_diag_dr_ssse3; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_vert_r_ssse3; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_horz_d_ssse3; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_vert_l_ssse3; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_horz_u_ssse3; + +/* Luma 8x8 Intra pred filters */ + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_vert_ssse3; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_horz_ssse3; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_dc_ssse3; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_diag_dl_ssse3; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_diag_dr_ssse3; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_vert_r_ssse3; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_horz_d_ssse3; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_vert_l_ssse3; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_horz_u_ssse3; + +/* Luma 16x16 Intra pred filters */ + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_16x16_mode_vert_ssse3; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_16x16_mode_horz_ssse3; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_16x16_mode_dc_ssse3; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_16x16_mode_plane_ssse3; + +/* Chroma 8x8 Intra pred filters */ + +ih264_intra_pred_chroma_ft ih264_intra_pred_chroma_8x8_mode_dc_ssse3; + +ih264_intra_pred_chroma_ft ih264_intra_pred_chroma_8x8_mode_horz_ssse3; + +ih264_intra_pred_chroma_ft ih264_intra_pred_chroma_8x8_mode_vert_ssse3; + +ih264_intra_pred_chroma_ft ih264_intra_pred_chroma_8x8_mode_plane_ssse3; + +/* AV8 Definition */ + +/* Luma 4x4 Intra pred filters */ +ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_vert_av8; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_horz_av8; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_dc_av8; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_diag_dl_av8; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_diag_dr_av8; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_vert_r_av8; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_horz_d_av8; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_vert_l_av8; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_4x4_mode_horz_u_av8; + +/* Luma 8x8 Intra pred filters */ + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_vert_av8; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_horz_av8; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_dc_av8; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_diag_dl_av8; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_diag_dr_av8; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_vert_r_av8; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_horz_d_av8; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_vert_l_av8; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_8x8_mode_horz_u_av8; + +/* Luma 16x16 Intra pred filters */ + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_16x16_mode_vert_av8; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_16x16_mode_horz_av8; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_16x16_mode_dc_av8; + +ih264_intra_pred_luma_ft ih264_intra_pred_luma_16x16_mode_plane_av8; + +/* Chroma 8x8 Intra pred filters */ + +ih264_intra_pred_chroma_ft ih264_intra_pred_chroma_8x8_mode_dc_av8; + +ih264_intra_pred_chroma_ft ih264_intra_pred_chroma_8x8_mode_horz_av8; + +ih264_intra_pred_chroma_ft ih264_intra_pred_chroma_8x8_mode_vert_av8; + +ih264_intra_pred_chroma_ft ih264_intra_pred_chroma_8x8_mode_plane_av8; + +#endif /* IH264_INTRA_PRED_FILTERS_H_ */ diff --git a/dependencies/ih264d/common/ih264_iquant_itrans_recon.c b/dependencies/ih264d/common/ih264_iquant_itrans_recon.c new file mode 100644 index 00000000..3c14046f --- /dev/null +++ b/dependencies/ih264d/common/ih264_iquant_itrans_recon.c @@ -0,0 +1,873 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** + ******************************************************************************* + * @file + * ih264_iquant_itrans_recon.c + * + * @brief + * Contains definition of functions for h264 inverse quantization inverse transformation and recon + * + * @author + * Ittiam + * + * @par List of Functions: + * - ih264_iquant_itrans_recon_4x4() + * - ih264_iquant_itrans_recon_8x8() + * - ih264_iquant_itrans_recon_4x4_dc() + * - ih264_iquant_itrans_recon_8x8_dc() + * - ih264_iquant_itrans_recon_chroma_4x4() + * -ih264_iquant_itrans_recon_chroma_4x4_dc() + * + * @remarks + * + ******************************************************************************* + */ + +/*****************************************************************************/ +/* File Includes */ +/*****************************************************************************/ + +/* User include files */ +#include "ih264_typedefs.h" +#include "ih264_defs.h" +#include "ih264_trans_macros.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264_trans_data.h" +#include "ih264_size_defs.h" +#include "ih264_structs.h" +#include "ih264_trans_quant_itrans_iquant.h" + +/* + ******************************************************************************** + * + * @brief This function reconstructs a 4x4 sub block from quantized resiude and + * prediction buffer + * + * @par Description: + * The quantized residue is first inverse quantized, then inverse transformed. + * This inverse transformed content is added to the prediction buffer to recon- + * struct the end output + * + * @param[in] pi2_src + * quantized 4x4 block + * + * @param[in] pu1_pred + * prediction 4x4 block + * + * @param[out] pu1_out + * reconstructed 4x4 block + * + * @param[in] src_strd + * quantization buffer stride + * + * @param[in] pred_strd, + * Prediction buffer stride + * + * @param[in] out_strd + * recon buffer Stride + * + * @param[in] pu2_scaling_list + * pointer to scaling list + * + * @param[in] pu2_norm_adjust + * pointer to inverse scale matrix + * + * @param[in] u4_qp_div_6 + * Floor (qp/6) + * + * @param[in] pi4_tmp + * temporary buffer of size 1*16 + * + * @returns none + * + * @remarks none + * + ******************************************************************************* + */ +void ih264_iquant_itrans_recon_4x4(WORD16 *pi2_src, + UWORD8 *pu1_pred, + UWORD8 *pu1_out, + WORD32 pred_strd, + WORD32 out_strd, + const UWORD16 *pu2_iscal_mat, + const UWORD16 *pu2_weigh_mat, + UWORD32 u4_qp_div_6, + WORD16 *pi2_tmp, + WORD32 iq_start_idx, + WORD16 *pi2_dc_ld_addr +) +{ + WORD16 *pi2_src_ptr = pi2_src; + WORD16 *pi2_tmp_ptr = pi2_tmp; + UWORD8 *pu1_pred_ptr = pu1_pred; + UWORD8 *pu1_out_ptr = pu1_out; + WORD16 x0, x1, x2, x3, i; + WORD32 q0, q1, q2, q3; + WORD16 i_macro; + WORD16 rnd_fact = (u4_qp_div_6 < 4) ? 1 << (3 - u4_qp_div_6) : 0; + + /* inverse quant */ + /*horizontal inverse transform */ + for(i = 0; i < SUB_BLK_WIDTH_4x4; i++) + { + q0 = pi2_src_ptr[0]; + INV_QUANT(q0, pu2_iscal_mat[0], pu2_weigh_mat[0], u4_qp_div_6, rnd_fact, + 4); + if (i==0 && iq_start_idx == 1) + q0 = pi2_dc_ld_addr[0]; // Restoring dc value for intra case + + q2 = pi2_src_ptr[2]; + INV_QUANT(q2, pu2_iscal_mat[2], pu2_weigh_mat[2], u4_qp_div_6, rnd_fact, + 4); + + x0 = q0 + q2; + x1 = q0 - q2; + + q1 = pi2_src_ptr[1]; + INV_QUANT(q1, pu2_iscal_mat[1], pu2_weigh_mat[1], u4_qp_div_6, rnd_fact, + 4); + + q3 = pi2_src_ptr[3]; + INV_QUANT(q3, pu2_iscal_mat[3], pu2_weigh_mat[3], u4_qp_div_6, rnd_fact, + 4); + + x2 = (q1 >> 1) - q3; + x3 = q1 + (q3 >> 1); + + pi2_tmp_ptr[0] = x0 + x3; + pi2_tmp_ptr[1] = x1 + x2; + pi2_tmp_ptr[2] = x1 - x2; + pi2_tmp_ptr[3] = x0 - x3; + + pi2_src_ptr += SUB_BLK_WIDTH_4x4; + pi2_tmp_ptr += SUB_BLK_WIDTH_4x4; + pu2_iscal_mat += SUB_BLK_WIDTH_4x4; + pu2_weigh_mat += SUB_BLK_WIDTH_4x4; + } + + /* vertical inverse transform */ + pi2_tmp_ptr = pi2_tmp; + for(i = 0; i < SUB_BLK_WIDTH_4x4; i++) + { + pu1_pred_ptr = pu1_pred; + pu1_out = pu1_out_ptr; + + x0 = (pi2_tmp_ptr[0] + pi2_tmp_ptr[8]); + x1 = (pi2_tmp_ptr[0] - pi2_tmp_ptr[8]); + x2 = (pi2_tmp_ptr[4] >> 1) - pi2_tmp_ptr[12]; + x3 = pi2_tmp_ptr[4] + (pi2_tmp_ptr[12] >> 1); + + /* inverse prediction */ + i_macro = x0 + x3; + i_macro = ((i_macro + 32) >> 6); + i_macro += *pu1_pred_ptr; + *pu1_out = CLIP_U8(i_macro); + pu1_pred_ptr += pred_strd; + pu1_out += out_strd; + + i_macro = x1 + x2; + i_macro = ((i_macro + 32) >> 6); + i_macro += *pu1_pred_ptr; + *pu1_out = CLIP_U8(i_macro); + pu1_pred_ptr += pred_strd; + pu1_out += out_strd; + + i_macro = x1 - x2; + i_macro = ((i_macro + 32) >> 6); + i_macro += *pu1_pred_ptr; + *pu1_out = CLIP_U8(i_macro); + pu1_pred_ptr += pred_strd; + pu1_out += out_strd; + + i_macro = x0 - x3; + i_macro = ((i_macro + 32) >> 6); + i_macro += *pu1_pred_ptr; + *pu1_out = CLIP_U8(i_macro); + + pi2_tmp_ptr++; + pu1_out_ptr++; + pu1_pred++; + } + +} + +void ih264_iquant_itrans_recon_4x4_dc(WORD16 *pi2_src, + UWORD8 *pu1_pred, + UWORD8 *pu1_out, + WORD32 pred_strd, + WORD32 out_strd, + const UWORD16 *pu2_iscal_mat, + const UWORD16 *pu2_weigh_mat, + UWORD32 u4_qp_div_6, + WORD16 *pi2_tmp, + WORD32 iq_start_idx, + WORD16 *pi2_dc_ld_addr) +{ + UWORD8 *pu1_pred_ptr = pu1_pred; + UWORD8 *pu1_out_ptr = pu1_out; + WORD32 q0; + WORD16 x, i_macro, i; + WORD16 rnd_fact = (u4_qp_div_6 < 4) ? 1 << (3 - u4_qp_div_6) : 0; + UNUSED(pi2_tmp); + + if (iq_start_idx == 0) + { + q0 = pi2_src[0]; + INV_QUANT(q0, pu2_iscal_mat[0], pu2_weigh_mat[0], u4_qp_div_6, rnd_fact, 4); + } + else + { + q0 = pi2_dc_ld_addr[0]; // Restoring dc value for intra case3 + } + i_macro = ((q0 + 32) >> 6); + for(i = 0; i < SUB_BLK_WIDTH_4x4; i++) + { + pu1_pred_ptr = pu1_pred; + pu1_out = pu1_out_ptr; + + /* inverse prediction */ + + x = i_macro + *pu1_pred_ptr; + *pu1_out = CLIP_U8(x); + pu1_pred_ptr += pred_strd; + pu1_out += out_strd; + + x = i_macro + *pu1_pred_ptr; + *pu1_out = CLIP_U8(x); + pu1_pred_ptr += pred_strd; + pu1_out += out_strd; + + x = i_macro + *pu1_pred_ptr; + *pu1_out = CLIP_U8(x); + pu1_pred_ptr += pred_strd; + pu1_out += out_strd; + + x = i_macro + *pu1_pred_ptr; + *pu1_out = CLIP_U8(x); + + pu1_out_ptr++; + pu1_pred++; + } +} + +/** + ******************************************************************************* + * + * @brief + * This function performs inverse quant and Inverse transform type Ci4 for 8x8 block + * + * @par Description: + * Performs inverse transform Ci8 and adds the residue to get the + * reconstructed block + * + * @param[in] pi2_src + * Input 8x8coefficients + * + * @param[in] pu1_pred + * Prediction 8x8 block + * + * @param[out] pu1_recon + * Output 8x8 block + * + * @param[in] q_div + * QP/6 + * + * @param[in] q_rem + * QP%6 + * + * @param[in] q_lev + * Quantizer level + * + * @param[in] src_strd + * Input stride + * + * @param[in] pred_strd, + * Prediction stride + * + * @param[in] out_strd + * Output Stride + * + * @param[in] pi4_tmp + * temporary buffer of size 1*16 we dont need a bigger blcok since we reuse + * the tmp for each block + * + * @param[in] pu4_iquant_mat + * Pointer to the inverse quantization matrix + * + * @returns Void + * + * @remarks + * None + * + ******************************************************************************* + */ +void ih264_iquant_itrans_recon_8x8(WORD16 *pi2_src, + UWORD8 *pu1_pred, + UWORD8 *pu1_out, + WORD32 pred_strd, + WORD32 out_strd, + const UWORD16 *pu2_iscale_mat, + const UWORD16 *pu2_weigh_mat, + UWORD32 qp_div, + WORD16 *pi2_tmp, + WORD32 iq_start_idx, + WORD16 *pi2_dc_ld_addr +) +{ + WORD32 i; + WORD16 *pi2_tmp_ptr = pi2_tmp; + UWORD8 *pu1_pred_ptr = pu1_pred; + UWORD8 *pu1_out_ptr = pu1_out; + WORD16 i_z0, i_z1, i_z2, i_z3, i_z4, i_z5, i_z6, i_z7; + WORD16 i_y0, i_y1, i_y2, i_y3, i_y4, i_y5, i_y6, i_y7; + WORD16 i_macro; + WORD32 q; + WORD32 rnd_fact = (qp_div < 6) ? (1 << (5 - qp_div)) : 0; + UNUSED(iq_start_idx); + UNUSED(pi2_dc_ld_addr); + /*************************************************************/ + /* De quantization of coefficients. Will be replaced by SIMD */ + /* operations on platform. Note : DC coeff is not scaled */ + /*************************************************************/ + for(i = 0; i < (SUB_BLK_WIDTH_8x8 * SUB_BLK_WIDTH_8x8); i++) + { + q = pi2_src[i]; + INV_QUANT(q, pu2_iscale_mat[i], pu2_weigh_mat[i], qp_div, rnd_fact, 6); + pi2_tmp_ptr[i] = q; + } + /* Perform Inverse transform */ + /*--------------------------------------------------------------------*/ + /* IDCT [ Horizontal transformation ] */ + /*--------------------------------------------------------------------*/ + for(i = 0; i < SUB_BLK_WIDTH_8x8; i++) + { + /*------------------------------------------------------------------*/ + /* y0 = w0 + w4 */ + /* y1 = -w3 + w5 - w7 - (w7 >> 1) */ + /* y2 = w0 - w4 */ + /* y3 = w1 + w7 - w3 - (w3 >> 1) */ + /* y4 = (w2 >> 1) - w6 */ + /* y5 = -w1 + w7 + w5 + (w5 >> 1) */ + /* y6 = w2 + (w6 >> 1) */ + /* y7 = w3 + w5 + w1 + (w1 >> 1) */ + /*------------------------------------------------------------------*/ + i_y0 = (pi2_tmp_ptr[0] + pi2_tmp_ptr[4] ); + + i_y1 = ((WORD32)(-pi2_tmp_ptr[3]) + pi2_tmp_ptr[5] - pi2_tmp_ptr[7] + - (pi2_tmp_ptr[7] >> 1)); + + i_y2 = (pi2_tmp_ptr[0] - pi2_tmp_ptr[4] ); + + i_y3 = ((WORD32)pi2_tmp_ptr[1] + pi2_tmp_ptr[7] - pi2_tmp_ptr[3] + - (pi2_tmp_ptr[3] >> 1)); + + i_y4 = ((pi2_tmp_ptr[2] >> 1) - pi2_tmp_ptr[6] ); + + i_y5 = ((WORD32)(-pi2_tmp_ptr[1]) + pi2_tmp_ptr[7] + pi2_tmp_ptr[5] + + (pi2_tmp_ptr[5] >> 1)); + + i_y6 = (pi2_tmp_ptr[2] + (pi2_tmp_ptr[6] >> 1)); + + i_y7 = ((WORD32)pi2_tmp_ptr[3] + pi2_tmp_ptr[5] + pi2_tmp_ptr[1] + + (pi2_tmp_ptr[1] >> 1)); + + /*------------------------------------------------------------------*/ + /* z0 = y0 + y6 */ + /* z1 = y1 + (y7 >> 2) */ + /* z2 = y2 + y4 */ + /* z3 = y3 + (y5 >> 2) */ + /* z4 = y2 - y4 */ + /* z5 = (y3 >> 2) - y5 */ + /* z6 = y0 - y6 */ + /* z7 = y7 - (y1 >> 2) */ + /*------------------------------------------------------------------*/ + i_z0 = i_y0 + i_y6; + i_z1 = i_y1 + (i_y7 >> 2); + i_z2 = i_y2 + i_y4; + i_z3 = i_y3 + (i_y5 >> 2); + i_z4 = i_y2 - i_y4; + i_z5 = (i_y3 >> 2) - i_y5; + i_z6 = i_y0 - i_y6; + i_z7 = i_y7 - (i_y1 >> 2); + + /*------------------------------------------------------------------*/ + /* x0 = z0 + z7 */ + /* x1 = z2 + z5 */ + /* x2 = z4 + z3 */ + /* x3 = z6 + z1 */ + /* x4 = z6 - z1 */ + /* x5 = z4 - z3 */ + /* x6 = z2 - z5 */ + /* x7 = z0 - z7 */ + /*------------------------------------------------------------------*/ + pi2_tmp_ptr[0] = i_z0 + i_z7; + pi2_tmp_ptr[1] = i_z2 + i_z5; + pi2_tmp_ptr[2] = i_z4 + i_z3; + pi2_tmp_ptr[3] = i_z6 + i_z1; + pi2_tmp_ptr[4] = i_z6 - i_z1; + pi2_tmp_ptr[5] = i_z4 - i_z3; + pi2_tmp_ptr[6] = i_z2 - i_z5; + pi2_tmp_ptr[7] = i_z0 - i_z7; + + /* move to the next row */ + //pi2_src_ptr += SUB_BLK_WIDTH_8x8; + pi2_tmp_ptr += SUB_BLK_WIDTH_8x8; + } + /*--------------------------------------------------------------------*/ + /* IDCT [ Vertical transformation] and Xij = (xij + 32)>>6 */ + /* */ + /* Add the prediction and store it back to reconstructed frame buffer */ + /* [Prediction buffer itself in this case] */ + /*--------------------------------------------------------------------*/ + + pi2_tmp_ptr = pi2_tmp; + for(i = 0; i < SUB_BLK_WIDTH_8x8; i++) + { + pu1_pred_ptr = pu1_pred; + pu1_out = pu1_out_ptr; + /*------------------------------------------------------------------*/ + /* y0j = w0j + w4j */ + /* y1j = -w3j + w5j -w7j -(w7j >> 1) */ + /* y2j = w0j -w4j */ + /* y3j = w1j + w7j -w3j -(w3j >> 1) */ + /* y4j = ( w2j >> 1 ) -w6j */ + /* y5j = -w1j + w7j + w5j + (w5j >> 1) */ + /* y6j = w2j + ( w6j >> 1 ) */ + /* y7j = w3j + w5j + w1j + (w1j >> 1) */ + /*------------------------------------------------------------------*/ + i_y0 = pi2_tmp_ptr[0] + pi2_tmp_ptr[32]; + + i_y1 = (WORD32)(-pi2_tmp_ptr[24]) + pi2_tmp_ptr[40] - pi2_tmp_ptr[56] + - (pi2_tmp_ptr[56] >> 1); + + i_y2 = pi2_tmp_ptr[0] - pi2_tmp_ptr[32]; + + i_y3 = (WORD32)pi2_tmp_ptr[8] + pi2_tmp_ptr[56] - pi2_tmp_ptr[24] + - (pi2_tmp_ptr[24] >> 1); + + i_y4 = (pi2_tmp_ptr[16] >> 1) - pi2_tmp_ptr[48]; + + i_y5 = (WORD32)(-pi2_tmp_ptr[8]) + pi2_tmp_ptr[56] + pi2_tmp_ptr[40] + + (pi2_tmp_ptr[40] >> 1); + + i_y6 = pi2_tmp_ptr[16] + (pi2_tmp_ptr[48] >> 1); + + i_y7 = (WORD32)pi2_tmp_ptr[24] + pi2_tmp_ptr[40] + pi2_tmp_ptr[8] + + (pi2_tmp_ptr[8] >> 1); + + /*------------------------------------------------------------------*/ + /* z0j = y0j + y6j */ + /* z1j = y1j + (y7j >> 2) */ + /* z2j = y2j + y4j */ + /* z3j = y3j + (y5j >> 2) */ + /* z4j = y2j -y4j */ + /* z5j = (y3j >> 2) -y5j */ + /* z6j = y0j -y6j */ + /* z7j = y7j -(y1j >> 2) */ + /*------------------------------------------------------------------*/ + i_z0 = i_y0 + i_y6; + i_z1 = i_y1 + (i_y7 >> 2); + i_z2 = i_y2 + i_y4; + i_z3 = i_y3 + (i_y5 >> 2); + i_z4 = i_y2 - i_y4; + i_z5 = (i_y3 >> 2) - i_y5; + i_z6 = i_y0 - i_y6; + i_z7 = i_y7 - (i_y1 >> 2); + + /*------------------------------------------------------------------*/ + /* x0j = z0j + z7j */ + /* x1j = z2j + z5j */ + /* x2j = z4j + z3j */ + /* x3j = z6j + z1j */ + /* x4j = z6j -z1j */ + /* x5j = z4j -z3j */ + /* x6j = z2j -z5j */ + /* x7j = z0j -z7j */ + /*------------------------------------------------------------------*/ + i_macro = ((i_z0 + i_z7 + 32) >> 6) + *pu1_pred_ptr; + *pu1_out = CLIP_U8(i_macro); + /* Change uc_recBuffer to Point to next element in the same column*/ + pu1_pred_ptr += pred_strd; + pu1_out += out_strd; + + i_macro = ((i_z2 + i_z5 + 32) >> 6) + *pu1_pred_ptr; + *pu1_out = CLIP_U8(i_macro); + pu1_pred_ptr += pred_strd; + pu1_out += out_strd; + + i_macro = ((i_z4 + i_z3 + 32) >> 6) + *pu1_pred_ptr; + *pu1_out = CLIP_U8(i_macro); + pu1_pred_ptr += pred_strd; + pu1_out += out_strd; + + i_macro = ((i_z6 + i_z1 + 32) >> 6) + *pu1_pred_ptr; + *pu1_out = CLIP_U8(i_macro); + pu1_pred_ptr += pred_strd; + pu1_out += out_strd; + + i_macro = ((i_z6 - i_z1 + 32) >> 6) + *pu1_pred_ptr; + *pu1_out = CLIP_U8(i_macro); + pu1_pred_ptr += pred_strd; + pu1_out += out_strd; + + i_macro = ((i_z4 - i_z3 + 32) >> 6) + *pu1_pred_ptr; + *pu1_out = CLIP_U8(i_macro); + pu1_pred_ptr += pred_strd; + pu1_out += out_strd; + + i_macro = ((i_z2 - i_z5 + 32) >> 6) + *pu1_pred_ptr; + *pu1_out = CLIP_U8(i_macro); + pu1_pred_ptr += pred_strd; + pu1_out += out_strd; + + i_macro = ((i_z0 - i_z7 + 32) >> 6) + *pu1_pred_ptr; + *pu1_out = CLIP_U8(i_macro); + + pi2_tmp_ptr++; + pu1_out_ptr++; + pu1_pred++; + } +} + +void ih264_iquant_itrans_recon_8x8_dc(WORD16 *pi2_src, + UWORD8 *pu1_pred, + UWORD8 *pu1_out, + WORD32 pred_strd, + WORD32 out_strd, + const UWORD16 *pu2_iscale_mat, + const UWORD16 *pu2_weigh_mat, + UWORD32 qp_div, + WORD16 *pi2_tmp, + WORD32 iq_start_idx, + WORD16 *pi2_dc_ld_addr) +{ + UWORD8 *pu1_pred_ptr = pu1_pred; + UWORD8 *pu1_out_ptr = pu1_out; + WORD16 x, i, i_macro; + WORD32 q; + WORD32 rnd_fact = (qp_div < 6) ? (1 << (5 - qp_div)) : 0; + UNUSED(pi2_tmp); + UNUSED(iq_start_idx); + UNUSED(pi2_dc_ld_addr); + /*************************************************************/ + /* Dequantization of coefficients. Will be replaced by SIMD */ + /* operations on platform. Note : DC coeff is not scaled */ + /*************************************************************/ + q = pi2_src[0]; + INV_QUANT(q, pu2_iscale_mat[0], pu2_weigh_mat[0], qp_div, rnd_fact, 6); + i_macro = (q + 32) >> 6; + /* Perform Inverse transform */ + /*--------------------------------------------------------------------*/ + /* IDCT [ Horizontal transformation ] */ + /*--------------------------------------------------------------------*/ + /*--------------------------------------------------------------------*/ + /* IDCT [ Vertical transformation] and Xij = (xij + 32)>>6 */ + /* */ + /* Add the prediction and store it back to reconstructed frame buffer */ + /* [Prediction buffer itself in this case] */ + /*--------------------------------------------------------------------*/ + for(i = 0; i < SUB_BLK_WIDTH_8x8; i++) + { + pu1_pred_ptr = pu1_pred; + pu1_out = pu1_out_ptr; + + x = i_macro + *pu1_pred_ptr; + *pu1_out = CLIP_U8(x); + /* Change uc_recBuffer to Point to next element in the same column*/ + pu1_pred_ptr += pred_strd; + pu1_out += out_strd; + + x = i_macro + *pu1_pred_ptr; + *pu1_out = CLIP_U8(x); + pu1_pred_ptr += pred_strd; + pu1_out += out_strd; + + x = i_macro + *pu1_pred_ptr; + *pu1_out = CLIP_U8(x); + pu1_pred_ptr += pred_strd; + pu1_out += out_strd; + + x = i_macro + *pu1_pred_ptr; + *pu1_out = CLIP_U8(x); + pu1_pred_ptr += pred_strd; + pu1_out += out_strd; + + x = i_macro + *pu1_pred_ptr; + *pu1_out = CLIP_U8(x); + pu1_pred_ptr += pred_strd; + pu1_out += out_strd; + + x = i_macro + *pu1_pred_ptr; + *pu1_out = CLIP_U8(x); + pu1_pred_ptr += pred_strd; + pu1_out += out_strd; + + x = i_macro + *pu1_pred_ptr; + *pu1_out = CLIP_U8(x); + pu1_pred_ptr += pred_strd; + pu1_out += out_strd; + + x = i_macro + *pu1_pred_ptr; + *pu1_out = CLIP_U8(x); + + pu1_out_ptr++; + pu1_pred++; + } +} + +/* + ******************************************************************************** + * + * @brief This function reconstructs a 4x4 sub block from quantized resiude and + * prediction buffer + * + * @par Description: + * The quantized residue is first inverse quantized, then inverse transformed. + * This inverse transformed content is added to the prediction buffer to recon- + * struct the end output + * + * @param[in] pi2_src + * quantized 4x4 block + * + * @param[in] pu1_pred + * prediction 4x4 block + * + * @param[out] pu1_out + * reconstructed 4x4 block + * + * @param[in] src_strd + * quantization buffer stride + * + * @param[in] pred_strd, + * Prediction buffer stride + * + * @param[in] out_strd + * recon buffer Stride + * + * @param[in] pu2_scaling_list + * pointer to scaling list + * + * @param[in] pu2_norm_adjust + * pointer to inverse scale matrix + * + * @param[in] u4_qp_div_6 + * Floor (qp/6) + * + * @param[in] pi4_tmp + * temporary buffer of size 1*16 + * + * @returns none + * + * @remarks none + * + ******************************************************************************* + */ +void ih264_iquant_itrans_recon_chroma_4x4(WORD16 *pi2_src, + UWORD8 *pu1_pred, + UWORD8 *pu1_out, + WORD32 pred_strd, + WORD32 out_strd, + const UWORD16 *pu2_iscal_mat, + const UWORD16 *pu2_weigh_mat, + UWORD32 u4_qp_div_6, + WORD16 *pi2_tmp, + WORD16 *pi2_dc_src) +{ + WORD16 *pi2_src_ptr = pi2_src; + WORD16 *pi2_tmp_ptr = pi2_tmp; + UWORD8 *pu1_pred_ptr = pu1_pred; + UWORD8 *pu1_out_ptr = pu1_out; + WORD16 x0, x1, x2, x3, i; + WORD32 q0, q1, q2, q3; + WORD16 i_macro; + WORD16 rnd_fact = (u4_qp_div_6 < 4) ? 1 << (3 - u4_qp_div_6) : 0; + + /* inverse quant */ + /*horizontal inverse transform */ + for(i = 0; i < SUB_BLK_WIDTH_4x4; i++) + { + if(i==0) + { + q0 = pi2_dc_src[0]; + } + else + { + q0 = pi2_src_ptr[0]; + INV_QUANT(q0, pu2_iscal_mat[0], pu2_weigh_mat[0], u4_qp_div_6, rnd_fact, 4); + } + + q2 = pi2_src_ptr[2]; + INV_QUANT(q2, pu2_iscal_mat[2], pu2_weigh_mat[2], u4_qp_div_6, rnd_fact, + 4); + + x0 = q0 + q2; + x1 = q0 - q2; + + q1 = pi2_src_ptr[1]; + INV_QUANT(q1, pu2_iscal_mat[1], pu2_weigh_mat[1], u4_qp_div_6, rnd_fact, + 4); + + q3 = pi2_src_ptr[3]; + INV_QUANT(q3, pu2_iscal_mat[3], pu2_weigh_mat[3], u4_qp_div_6, rnd_fact, + 4); + + x2 = (q1 >> 1) - q3; + x3 = q1 + (q3 >> 1); + + pi2_tmp_ptr[0] = x0 + x3; + pi2_tmp_ptr[1] = x1 + x2; + pi2_tmp_ptr[2] = x1 - x2; + pi2_tmp_ptr[3] = x0 - x3; + + pi2_src_ptr += SUB_BLK_WIDTH_4x4; + pi2_tmp_ptr += SUB_BLK_WIDTH_4x4; + pu2_iscal_mat += SUB_BLK_WIDTH_4x4; + pu2_weigh_mat += SUB_BLK_WIDTH_4x4; + } + + /* vertical inverse transform */ + pi2_tmp_ptr = pi2_tmp; + for(i = 0; i < SUB_BLK_WIDTH_4x4; i++) + { + pu1_pred_ptr = pu1_pred; + pu1_out = pu1_out_ptr; + + x0 = (pi2_tmp_ptr[0] + pi2_tmp_ptr[8]); + x1 = (pi2_tmp_ptr[0] - pi2_tmp_ptr[8]); + x2 = (pi2_tmp_ptr[4] >> 1) - pi2_tmp_ptr[12]; + x3 = pi2_tmp_ptr[4] + (pi2_tmp_ptr[12] >> 1); + + /* inverse prediction */ + i_macro = x0 + x3; + i_macro = ((i_macro + 32) >> 6); + i_macro += *pu1_pred_ptr; + *pu1_out = CLIP_U8(i_macro); + pu1_pred_ptr += pred_strd; + pu1_out += out_strd; + + i_macro = x1 + x2; + i_macro = ((i_macro + 32) >> 6); + i_macro += *pu1_pred_ptr; + *pu1_out = CLIP_U8(i_macro); + pu1_pred_ptr += pred_strd; + pu1_out += out_strd; + + i_macro = x1 - x2; + i_macro = ((i_macro + 32) >> 6); + i_macro += *pu1_pred_ptr; + *pu1_out = CLIP_U8(i_macro); + pu1_pred_ptr += pred_strd; + pu1_out += out_strd; + + i_macro = x0 - x3; + i_macro = ((i_macro + 32) >> 6); + i_macro += *pu1_pred_ptr; + *pu1_out = CLIP_U8(i_macro); + + pi2_tmp_ptr++; + pu1_out_ptr+= 2; //Interleaved store for output + pu1_pred+= 2; //Interleaved load for pred buffer + } +} + +/* + ******************************************************************************** + * + * @brief This function reconstructs a 4x4 sub block from quantized resiude and + * prediction buffer if only dc value is present for residue + * + * @par Description: + * The quantized residue is first inverse quantized, + * This inverse quantized content is added to the prediction buffer to recon- + * struct the end output + * + * @param[in] pi2_src + * quantized dc coefficient + * + * @param[in] pu1_pred + * prediction 4x4 block in interleaved format + * + * @param[in] pred_strd, + * Prediction buffer stride in interleaved format + * + * @param[in] out_strd + * recon buffer Stride + * + * @returns none + * + * @remarks none + * + ******************************************************************************* + */ + +void ih264_iquant_itrans_recon_chroma_4x4_dc(WORD16 *pi2_src, + UWORD8 *pu1_pred, + UWORD8 *pu1_out, + WORD32 pred_strd, + WORD32 out_strd, + const UWORD16 *pu2_iscal_mat, + const UWORD16 *pu2_weigh_mat, + UWORD32 u4_qp_div_6, + WORD16 *pi2_tmp, + WORD16 *pi2_dc_src) +{ + UWORD8 *pu1_pred_ptr = pu1_pred; + UWORD8 *pu1_out_ptr = pu1_out; + WORD32 q0; + WORD16 x, i_macro, i; + UNUSED(pi2_src); + UNUSED(pu2_iscal_mat); + UNUSED(pu2_weigh_mat); + UNUSED(u4_qp_div_6); + UNUSED(pi2_tmp); + + q0 = pi2_dc_src[0]; // Restoring dc value for intra case3 + i_macro = ((q0 + 32) >> 6); + + for(i = 0; i < SUB_BLK_WIDTH_4x4; i++) + { + pu1_pred_ptr = pu1_pred; + pu1_out = pu1_out_ptr; + + /* inverse prediction */ + x = i_macro + *pu1_pred_ptr; + *pu1_out = CLIP_U8(x); + pu1_pred_ptr += pred_strd; + pu1_out += out_strd; + + x = i_macro + *pu1_pred_ptr; + *pu1_out = CLIP_U8(x); + pu1_pred_ptr += pred_strd; + pu1_out += out_strd; + + x = i_macro + *pu1_pred_ptr; + *pu1_out = CLIP_U8(x); + pu1_pred_ptr += pred_strd; + pu1_out += out_strd; + + x = i_macro + *pu1_pred_ptr; + *pu1_out = CLIP_U8(x); + + pu1_out_ptr+=2; + pu1_pred+=2; + } +} diff --git a/dependencies/ih264d/common/ih264_list.c b/dependencies/ih264d/common/ih264_list.c new file mode 100644 index 00000000..736b41c4 --- /dev/null +++ b/dependencies/ih264d/common/ih264_list.c @@ -0,0 +1,574 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** +******************************************************************************* +* @file +* ih264_list.c +* +* @brief +* Contains functions for buf queue +* +* @author +* Harish +* +* @par List of Functions: +* ih264_list_size() +* ih264_list_lock() +* ih264_list_unlock() +* ih264_list_yield() +* ih264_list_free() +* ih264_list_init() +* ih264_list_reset() +* ih264_list_deinit() +* ih264_list_terminate() +* ih264_list_queue() +* ih264_list_dequeue() +* +* @remarks +* None +* +******************************************************************************* +*/ +/*****************************************************************************/ +/* File Includes */ +/*****************************************************************************/ +#include <stdio.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +#include "ih264_typedefs.h" +#include "ithread.h" +#include "ih264_platform_macros.h" +#include "ih264_macros.h" +#include "ih264_debug.h" +#include "ih264_error.h" +#include "ih264_list.h" + +/** +******************************************************************************* +* +* @brief Returns size for buf queue context. Does not include buf queue buffer +* requirements +* +* @par Description +* Returns size for buf queue context. Does not include buf queue buffer +* requirements. Buffer size required to store the bufs should be allocated in +* addition to the value returned here. +* +* @returns Size of the buf queue context +* +* @remarks +* +******************************************************************************* +*/ +WORD32 ih264_list_size(WORD32 num_entries, WORD32 entry_size) +{ + WORD32 size; + WORD32 clz; + size = sizeof(list_t); + size += ithread_get_mutex_lock_size(); + + /* Use next power of two number of entries*/ + clz = CLZ(num_entries); + num_entries = 1 << (32 - clz); + + size += num_entries * entry_size; + return size; +} + +/** +******************************************************************************* +* +* @brief +* Locks the list context +* +* @par Description +* Locks the list context by calling ithread_mutex_lock() +* +* @param[in] ps_list +* Job Queue context +* +* @returns IH264_FAIL if mutex lock fails else IH264_SUCCESS +* +* @remarks +* +******************************************************************************* +*/ +IH264_ERROR_T ih264_list_lock(list_t *ps_list) +{ + WORD32 retval; + retval = ithread_mutex_lock(ps_list->pv_mutex); + if(retval) + { + return IH264_FAIL; + } + return IH264_SUCCESS; +} + +/** +******************************************************************************* +* +* @brief +* Unlocks the list context +* +* @par Description +* Unlocks the list context by calling ithread_mutex_unlock() +* +* @param[in] ps_list +* Job Queue context +* +* @returns IH264_FAIL if mutex unlock fails else IH264_SUCCESS +* +* @remarks +* +******************************************************************************* +*/ + +IH264_ERROR_T ih264_list_unlock(list_t *ps_list) +{ + WORD32 retval; + retval = ithread_mutex_unlock(ps_list->pv_mutex); + if(retval) + { + return IH264_FAIL; + } + return IH264_SUCCESS; + +} +/** +******************************************************************************* +* +* @brief +* Yields the thread +* +* @par Description +* Unlocks the list context by calling +* ih264_list_unlock(), ithread_yield() and then ih264_list_lock() +* list is unlocked before to ensure the list can be accessed by other threads +* If unlock is not done before calling yield then no other thread can access +* the list functions and update list. +* +* @param[in] ps_list +* Job Queue context +* +* @returns IH264_FAIL if mutex lock unlock or yield fails else IH264_SUCCESS +* +* @remarks +* +******************************************************************************* +*/ +IH264_ERROR_T ih264_list_yield(list_t *ps_list) +{ + + IH264_ERROR_T ret = IH264_SUCCESS; + + IH264_ERROR_T rettmp; + rettmp = ih264_list_unlock(ps_list); + RETURN_IF((rettmp != IH264_SUCCESS), rettmp); + + ithread_yield(); + + if(ps_list->i4_yeild_interval_us > 0) + ithread_usleep(ps_list->i4_yeild_interval_us); + + rettmp = ih264_list_lock(ps_list); + RETURN_IF((rettmp != IH264_SUCCESS), rettmp); + return ret; +} + + +/** +******************************************************************************* +* +* @brief free the buf queue pointers +* +* @par Description +* Frees the list context +* +* @param[in] pv_buf +* Memory for buf queue buffer and buf queue context +* +* @returns Pointer to buf queue context +* +* @remarks +* Since it will be called only once by master thread this is not thread safe. +* +******************************************************************************* +*/ +IH264_ERROR_T ih264_list_free(list_t *ps_list) +{ + WORD32 ret; + ret = ithread_mutex_destroy(ps_list->pv_mutex); + + if(0 == ret) + return IH264_SUCCESS; + else + return IH264_FAIL; +} + +/** +******************************************************************************* +* +* @brief Initialize the buf queue +* +* @par Description +* Initializes the list context and sets write and read pointers to start of +* buf queue buffer +* +* @param[in] pv_buf +* Memoy for buf queue buffer and buf queue context +* +* @param[in] buf_size +* Size of the total memory allocated +* +* @returns Pointer to buf queue context +* +* @remarks +* Since it will be called only once by master thread this is not thread safe. +* +******************************************************************************* +*/ +void* ih264_list_init(void *pv_buf, + WORD32 buf_size, + WORD32 num_entries, + WORD32 entry_size, + WORD32 yeild_interval_us) +{ + list_t *ps_list; + UWORD8 *pu1_buf; + + pu1_buf = (UWORD8 *)pv_buf; + + ps_list = (list_t *)pu1_buf; + pu1_buf += sizeof(list_t); + buf_size -= sizeof(list_t); + + ps_list->pv_mutex = pu1_buf; + pu1_buf += ithread_get_mutex_lock_size(); + buf_size -= ithread_get_mutex_lock_size(); + + if (buf_size <= 0) + return NULL; + + ithread_mutex_init(ps_list->pv_mutex); + + /* Ensure num_entries is power of two */ + ASSERT(0 == (num_entries & (num_entries - 1))); + + /* Ensure remaining buffer is large enough to hold given number of entries */ + ASSERT((num_entries * entry_size) <= buf_size); + + ps_list->pv_buf_base = pu1_buf; + ps_list->i4_terminate = 0; + ps_list->i4_entry_size = entry_size; + ps_list->i4_buf_rd_idx = 0; + ps_list->i4_buf_wr_idx = 0; + ps_list->i4_log2_buf_max_idx = 32 - CLZ(num_entries); + ps_list->i4_buf_max_idx = num_entries; + ps_list->i4_yeild_interval_us = yeild_interval_us; + + return ps_list; +} +/** +******************************************************************************* +* +* @brief +* Resets the list context +* +* @par Description +* Resets the list context by initializing buf queue context elements +* +* @param[in] ps_list +* Job Queue context +* +* @returns IH264_FAIL if lock unlock fails else IH264_SUCCESS +* +* @remarks +* +******************************************************************************* +*/ +IH264_ERROR_T ih264_list_reset(list_t *ps_list) +{ + IH264_ERROR_T ret = IH264_SUCCESS; + ret = ih264_list_lock(ps_list); + RETURN_IF((ret != IH264_SUCCESS), ret); + + ps_list->i4_terminate = 0; + ps_list->i4_buf_rd_idx = 0; + ps_list->i4_buf_wr_idx = 0; + + ret = ih264_list_unlock(ps_list); + RETURN_IF((ret != IH264_SUCCESS), ret); + + return ret; +} + +/** +******************************************************************************* +* +* @brief +* Deinitializes the list context +* +* @par Description +* Deinitializes the list context by calling ih264_list_reset() +* and then destrying the mutex created +* +* @param[in] ps_list +* Job Queue context +* +* @returns IH264_FAIL if lock unlock fails else IH264_SUCCESS +* +* @remarks +* +******************************************************************************* +*/ +IH264_ERROR_T ih264_list_deinit(list_t *ps_list) +{ + WORD32 retval; + IH264_ERROR_T ret = IH264_SUCCESS; + + ret = ih264_list_reset(ps_list); + RETURN_IF((ret != IH264_SUCCESS), ret); + + retval = ithread_mutex_destroy(ps_list->pv_mutex); + if(retval) + { + return IH264_FAIL; + } + + return IH264_SUCCESS; +} + + +/** +******************************************************************************* +* +* @brief +* Terminates the list +* +* @par Description +* Terminates the list by setting a flag in context. +* +* @param[in] ps_list +* Job Queue context +* +* @returns IH264_FAIL if lock unlock fails else IH264_SUCCESS +* +* @remarks +* +******************************************************************************* +*/ + +IH264_ERROR_T ih264_list_terminate(list_t *ps_list) +{ + IH264_ERROR_T ret = IH264_SUCCESS; + ret = ih264_list_lock(ps_list); + RETURN_IF((ret != IH264_SUCCESS), ret); + + ps_list->i4_terminate = 1; + + ret = ih264_list_unlock(ps_list); + RETURN_IF((ret != IH264_SUCCESS), ret); + return ret; +} + + +/** +******************************************************************************* +* +* @brief Adds a buf to the queue +* +* @par Description +* Adds a buf to the queue and updates wr address to next location. +* Format/content of the buf structure is abstracted and hence size of the buf +* buffer is being passed. +* +* @param[in] ps_list +* Job Queue context +* +* @param[in] pv_buf +* Pointer to the location that contains details of the buf to be added +* +* @param[in] buf_size +* Size of the buf buffer +* +* @param[in] blocking +* To signal if the write is blocking or non-blocking. +* +* @returns +* +* @remarks +* Job Queue buffer is assumed to be allocated to handle worst case number of bufs +* Wrap around is not supported +* +******************************************************************************* +*/ +IH264_ERROR_T ih264_list_queue(list_t *ps_list, void *pv_buf, WORD32 blocking) +{ + IH264_ERROR_T ret = IH264_SUCCESS; + IH264_ERROR_T rettmp; + + WORD32 diff; + void *pv_buf_wr; + + volatile WORD32 *pi4_wr_idx, *pi4_rd_idx; + WORD32 buf_size = ps_list->i4_entry_size; + + + rettmp = ih264_list_lock(ps_list); + RETURN_IF((rettmp != IH264_SUCCESS), rettmp); + + + + while(1) + { + /* Ensure wr idx does not go beyond rd idx by more than number of entries + */ + pi4_wr_idx = &ps_list->i4_buf_wr_idx; + pi4_rd_idx = &ps_list->i4_buf_rd_idx; + diff = *pi4_wr_idx - *pi4_rd_idx; + + if(diff < ps_list->i4_buf_max_idx) + { + WORD32 wr_idx; + wr_idx = ps_list->i4_buf_wr_idx & (ps_list->i4_buf_max_idx - 1); + pv_buf_wr = (UWORD8 *)ps_list->pv_buf_base + wr_idx * buf_size; + + memcpy(pv_buf_wr, pv_buf, buf_size); + ps_list->i4_buf_wr_idx++; + break; + } + else + { + /* wr is ahead, so wait for rd to consume */ + if(blocking) + { + ih264_list_yield(ps_list); + } + else + { + ret = IH264_FAIL; + break; + } + } + + } + ps_list->i4_terminate = 0; + + rettmp = ih264_list_unlock(ps_list); + RETURN_IF((rettmp != IH264_SUCCESS), rettmp); + + return ret; +} +/** +******************************************************************************* +* +* @brief Gets next from the Job queue +* +* @par Description +* Gets next buf from the buf queue and updates rd address to next location. +* Format/content of the buf structure is abstracted and hence size of the buf +* buffer is being passed. If it is a blocking call and if there is no new buf +* then this functions unlocks the mutex and calls yield and then locks it back. +* and continues till a buf is available or terminate is set +* +* @param[in] ps_list +* Job Queue context +* +* @param[out] pv_buf +* Pointer to the location that contains details of the buf to be written +* +* @param[in] buf_size +* Size of the buf buffer +* +* @param[in] blocking +* To signal if the read is blocking or non-blocking. +* +* @returns +* +* @remarks +* Job Queue buffer is assumed to be allocated to handle worst case number of bufs +* Wrap around is not supported +* +******************************************************************************* +*/ +IH264_ERROR_T ih264_list_dequeue(list_t *ps_list, void *pv_buf, WORD32 blocking) +{ + IH264_ERROR_T ret = IH264_SUCCESS; + IH264_ERROR_T rettmp; + WORD32 buf_size = ps_list->i4_entry_size; + WORD32 diff; + + void *pv_buf_rd; + volatile WORD32 *pi4_wr_idx, *pi4_rd_idx; + + rettmp = ih264_list_lock(ps_list); + RETURN_IF((rettmp != IH264_SUCCESS), rettmp); + + while(1) + { + /* Ensure wr idx is ahead of rd idx and + * wr idx does not go beyond rd idx by more than number of entries + */ + pi4_wr_idx = &ps_list->i4_buf_wr_idx; + pi4_rd_idx = &ps_list->i4_buf_rd_idx; + diff = *pi4_wr_idx - *pi4_rd_idx; + + + if(diff > 0) + { + WORD32 rd_idx; + rd_idx = ps_list->i4_buf_rd_idx & (ps_list->i4_buf_max_idx - 1); + pv_buf_rd = (UWORD8 *)ps_list->pv_buf_base + rd_idx * buf_size; + + memcpy(pv_buf, pv_buf_rd, buf_size); + ps_list->i4_buf_rd_idx++; + break; + } + else + { + /* If terminate is signaled then break */ + if(ps_list->i4_terminate) + { + ret = IH264_FAIL; + break; + } + /* wr is ahead, so wait for rd to consume */ + if(blocking) + { + ih264_list_yield(ps_list); + } + else + { + ret = IH264_FAIL; + break; + } + } + + } + + + rettmp = ih264_list_unlock(ps_list); + RETURN_IF((rettmp != IH264_SUCCESS), rettmp); + + return ret; +} diff --git a/dependencies/ih264d/common/ih264_list.h b/dependencies/ih264d/common/ih264_list.h new file mode 100644 index 00000000..fc59d954 --- /dev/null +++ b/dependencies/ih264d/common/ih264_list.h @@ -0,0 +1,93 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** +******************************************************************************* +* @file +* ih264_list.h +* +* @brief +* Contains functions for buf queue +* +* @author +* Harish +* +* @par List of Functions: +* +* @remarks +* None +* +******************************************************************************* +*/ + +#ifndef _IH264_LIST_H_ +#define _IH264_LIST_H_ + +typedef struct +{ + /** Pointer to buffer base which contains the bufs */ + void *pv_buf_base; + + /** Mutex used to keep the functions thread-safe */ + void *pv_mutex; + + /** Current write index */ + volatile WORD32 i4_buf_wr_idx; + + /** Current read index */ + volatile WORD32 i4_buf_rd_idx; + + /** Maximum index */ + WORD32 i4_buf_max_idx; + + /** Log2(buf_max_idx) - + * To ensure number of entries is power of two + * This makes it easier to wrap around by using AND with buf_max_idx - 1 + * */ + WORD32 i4_log2_buf_max_idx; + + /** Flag to indicate list has to be terminated */ + WORD32 i4_terminate; + + /** Size of each entry */ + WORD32 i4_entry_size; + + /** If the list is to be used frequently send this as zero, else send a large value + * to ensure cores are not loaded unnecessarily. + * For eg: For picture level queues this can be a large value like 100us + * but for jobq this will be zero. + */ + WORD32 i4_yeild_interval_us; + +}list_t; + +WORD32 ih264_list_size(WORD32 num_entries, WORD32 entry_size); +void* ih264_list_init(void *pv_buf, + WORD32 buf_size, + WORD32 num_entries, + WORD32 entry_size, + WORD32 yeild_interval_us); +IH264_ERROR_T ih264_list_free(list_t *ps_list); +IH264_ERROR_T ih264_list_reset(list_t *ps_list); +IH264_ERROR_T ih264_list_deinit(list_t *ps_list); +IH264_ERROR_T ih264_list_terminate(list_t *ps_list); +IH264_ERROR_T ih264_list_queue(list_t *ps_list, void *pv_buf, WORD32 blocking); +IH264_ERROR_T ih264_list_dequeue(list_t *ps_list, void *pv_buf, WORD32 blocking); + +#endif /* _IH264_PROCESS_SLICE_H_ */ diff --git a/dependencies/ih264d/common/ih264_luma_intra_pred_filters.c b/dependencies/ih264d/common/ih264_luma_intra_pred_filters.c new file mode 100644 index 00000000..4a5b143f --- /dev/null +++ b/dependencies/ih264d/common/ih264_luma_intra_pred_filters.c @@ -0,0 +1,1933 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** + ******************************************************************************* + * @file + * ih264_luma_intra_pred_filters.c + * + * @brief + * Contains function definitions for intra prediction filters + * + * @author + * Ittiam + * + * @par List of Functions: + * - ih264_intra_pred_luma_4x4_mode_vert + * - ih264_intra_pred_luma_4x4_mode_horz + * - ih264_intra_pred_luma_4x4_mode_dc + * - ih264_intra_pred_luma_4x4_mode_diag_dl + * - ih264_intra_pred_luma_4x4_mode_diag_dr + * - ih264_intra_pred_luma_4x4_mode_vert_r + * - ih264_intra_pred_luma_4x4_mode_horz_d + * - ih264_intra_pred_luma_4x4_mode_vert_l + * - ih264_intra_pred_luma_4x4_mode_horz_u + * - ih264_intra_pred_luma_8x8_mode_ref_filtering + * - ih264_intra_pred_luma_8x8_mode_vert + * - ih264_intra_pred_luma_8x8_mode_horz + * - ih264_intra_pred_luma_8x8_mode_dc + * - ih264_intra_pred_luma_8x8_mode_diag_dl + * - ih264_intra_pred_luma_8x8_mode_diag_dr + * - ih264_intra_pred_luma_8x8_mode_vert_r + * - ih264_intra_pred_luma_8x8_mode_horz_d + * - ih264_intra_pred_luma_8x8_mode_vert_l + * - ih264_intra_pred_luma_8x8_mode_horz_u + * - ih264_intra_pred_luma_16x16_mode_vert + * - ih264_intra_pred_luma_16x16_mode_horz + * - ih264_intra_pred_luma_16x16_mode_dc + * - ih264_intra_pred_luma_16x16_mode_plane + * + * + * @remarks + * None + * + ****************************************************************************** + */ + +/*****************************************************************************/ +/* File Includes */ +/*****************************************************************************/ +/* System include files */ +#include <stdio.h> +#include <stddef.h> +#include <string.h> + +/* User include files */ +#include "ih264_defs.h" +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264_intra_pred_filters.h" + +/* Global variables used only in assembly files*/ +const WORD8 ih264_gai1_intrapred_luma_plane_coeffs[] = +{ 0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0A, 0x0B, 0x0C, + 0x0D, 0x0E, 0x0F, 0x10, }; + +const WORD8 ih264_gai1_intrapred_luma_8x8_horz_u[] = +{ 0x06,0x15,0x05,0x14, + 0x04,0x13,0x03,0x12, + 0x02,0x11,0x01,0x10, + 0x00,0x1F,0x0F,0x0F +}; + +/******************* LUMA INTRAPREDICTION *******************/ + +/******************* 4x4 Modes *******************/ + +/** + ******************************************************************************* + * + *ih264_intra_pred_luma_4x4_mode_vert + * + * @brief + * Perform Intra prediction for luma_4x4 mode:vertical + * + * @par Description: + * Perform Intra prediction for luma_4x4 mode:vertical ,described in sec 8.3.1.2.1 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ngbr_avail + * availability of neighbouring pixels(Not used in this function) + * + * @returns + * + * @remarks + * None + * + ******************************************************************************* + */ +void ih264_intra_pred_luma_4x4_mode_vert(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ + UNUSED(src_strd); + UNUSED(ngbr_avail); + pu1_top = pu1_src + BLK_SIZE + 1; + + memcpy(pu1_dst, pu1_top, 4); + memcpy(pu1_dst + dst_strd, pu1_top, 4); + memcpy(pu1_dst + 2 * dst_strd, pu1_top, 4); + memcpy(pu1_dst + 3 * dst_strd, pu1_top, 4); +} + +/** + ******************************************************************************* + * + *ih264_intra_pred_luma_4x4_mode_horz + * + * @brief + * Perform Intra prediction for luma_4x4 mode:horizontal + * + * @par Description: + * Perform Intra prediction for luma_4x4 mode:horizontal ,described in sec 8.3.1.2.2 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ngbr_avail + * availability of neighbouring pixels(Not used in this function) + * + * @returns + * + * @remarks + * None + * + ******************************************************************************* + */ +void ih264_intra_pred_luma_4x4_mode_horz(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + UWORD8 *pu1_left = NULL; /* Pointer to start of left predictors */ + + UNUSED(src_strd); + UNUSED(ngbr_avail); + pu1_left = pu1_src + BLK_SIZE - 1; + + memset(pu1_dst, *pu1_left, 4); + memset(pu1_dst + dst_strd, *(pu1_left - 1), 4); + memset(pu1_dst + 2 * dst_strd, *(pu1_left - 2), 4); + memset(pu1_dst + 3 * dst_strd, *(pu1_left - 3), 4); +} + +/** + ******************************************************************************* + * + *ih264_intra_pred_luma_4x4_mode_dc + * + * @brief + * Perform Intra prediction for luma_4x4 mode:DC + * + * @par Description: + * Perform Intra prediction for luma_4x4 mode:DC ,described in sec 8.3.1.2.3 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ngbr_avail + * availability of neighbouring pixels + * + * @returns + * + * @remarks + * None + * + *******************************************************************************/ +void ih264_intra_pred_luma_4x4_mode_dc(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + UWORD8 u1_useleft; /* availability of left predictors (only for DC) */ + UWORD8 u1_usetop; /* availability of top predictors (only for DC) */ + UWORD8 *pu1_left = NULL; /* Pointer to start of left predictors */ + UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ + WORD32 val = 0; + UNUSED(src_strd); + UNUSED(ngbr_avail); + u1_useleft = BOOLEAN(ngbr_avail & LEFT_MB_AVAILABLE_MASK); + u1_usetop = BOOLEAN(ngbr_avail & TOP_MB_AVAILABLE_MASK); + pu1_top = pu1_src + BLK_SIZE + 1; + pu1_left = pu1_src + BLK_SIZE - 1; + + if(u1_useleft) + { + val += *pu1_left--; + val += *pu1_left--; + val += *pu1_left--; + val += *pu1_left + 2; + } + if(u1_usetop) + { + val += *pu1_top + *(pu1_top + 1) + *(pu1_top + 2) + *(pu1_top + 3) + + 2; + } + /* Since 2 is added if either left/top pred is there, + val still being zero implies both preds are not there */ + val = (val) ? (val >> (1 + u1_useleft + u1_usetop)) : 128; + + /* 4 bytes are copied from src to dst */ + memset(pu1_dst, val, 4); + memset(pu1_dst + dst_strd, val, 4); + memset(pu1_dst + 2 * dst_strd, val, 4); + memset(pu1_dst + 3 * dst_strd, val, 4); +} + +/** + ******************************************************************************* + * + *ih264_intra_pred_luma_4x4_mode_diag_dl + * + * @brief + * Perform Intra prediction for luma_4x4 mode:Diagonal_Down_Left + * + * @par Description: + * Perform Intra prediction for luma_4x4 mode:Diagonal_Down_Left ,described in sec 8.3.1.2.4 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ngbr_avail + * availability of neighbouring pixels(Not used in this function) + * + * @returns + * + * @remarks + * None + * + *******************************************************************************/ +void ih264_intra_pred_luma_4x4_mode_diag_dl(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ + UWORD32 ui4_a, ui4_b, ui4_c, ui4_d, ui4_e, ui4_f, ui4_g, ui4_h; + UWORD8 predicted_pixels[7]; + UNUSED(src_strd); + UNUSED(ngbr_avail); + pu1_top = pu1_src +BLK_SIZE + 1; + + ui4_a = *pu1_top++; + ui4_b = *pu1_top++; + ui4_c = *pu1_top++; + ui4_d = *pu1_top++; + ui4_e = *pu1_top++; + ui4_f = *pu1_top++; + ui4_g = *pu1_top++; + ui4_h = *pu1_top; + + predicted_pixels[0] = FILT121(ui4_a, ui4_b, ui4_c); + predicted_pixels[1] = FILT121(ui4_b, ui4_c, ui4_d); + predicted_pixels[2] = FILT121(ui4_c, ui4_d, ui4_e); + predicted_pixels[3] = FILT121(ui4_d, ui4_e, ui4_f); + predicted_pixels[4] = FILT121(ui4_e, ui4_f, ui4_g); + predicted_pixels[5] = FILT121(ui4_f, ui4_g, ui4_h); + predicted_pixels[6] = FILT121(ui4_g, ui4_h, ui4_h); + + memcpy(pu1_dst, predicted_pixels, 4); + memcpy(pu1_dst + dst_strd, predicted_pixels + 1, 4); + memcpy(pu1_dst + 2 * dst_strd, predicted_pixels + 2, 4); + memcpy(pu1_dst + 3 * dst_strd, predicted_pixels + 3, 4); +} + +/** + ******************************************************************************* + * + *ih264_intra_pred_luma_4x4_mode_diag_dr + * + * @brief + * Perform Intra prediction for luma_4x4 mode:Diagonal_Down_Right + * + * @par Description: + * Perform Intra prediction for luma_4x4 mode:Diagonal_Down_Right ,described in sec 8.3.1.2.5 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ngbr_avail + * availability of neighbouring pixels(Not used in this function) + * + * @returns + * + * @remarks + * None + * + *******************************************************************************/ +void ih264_intra_pred_luma_4x4_mode_diag_dr(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ + UWORD8 *pu1_left = NULL; /* Pointer to start of left predictors */ + UWORD8 *pu1_topleft = NULL;/* Pointer to top left predictor */ + UWORD32 ui4_a, ui4_b, ui4_c, ui4_d, ui4_i, ui4_j, ui4_k, ui4_l, ui4_m; + UWORD8 predicted_pixels[7]; + UNUSED(src_strd); + UNUSED(ngbr_avail); + pu1_top = pu1_src + BLK_SIZE + 1; + pu1_left = pu1_src + BLK_SIZE - 1; + pu1_topleft = pu1_src +BLK_SIZE; + + ui4_a = *pu1_top++; + ui4_b = *pu1_top++; + ui4_c = *pu1_top++; + ui4_d = *pu1_top++; + ui4_i = *pu1_left--; + ui4_j = *pu1_left--; + ui4_k = *pu1_left--; + ui4_l = *pu1_left; + ui4_m = *pu1_topleft; + + predicted_pixels[2] = FILT121(ui4_j, ui4_i, ui4_m); + predicted_pixels[1] = FILT121(ui4_k, ui4_j, ui4_i); + predicted_pixels[0] = FILT121(ui4_l, ui4_k, ui4_j); + predicted_pixels[3] = FILT121(ui4_i, ui4_m, ui4_a); + predicted_pixels[4] = FILT121(ui4_m, ui4_a, ui4_b); + predicted_pixels[5] = FILT121(ui4_a, ui4_b, ui4_c); + predicted_pixels[6] = FILT121(ui4_b, ui4_c, ui4_d); + + memcpy(pu1_dst, predicted_pixels + 3, 4); + memcpy(pu1_dst + dst_strd, predicted_pixels + 2, 4); + memcpy(pu1_dst + 2 * dst_strd, predicted_pixels + 1, 4); + memcpy(pu1_dst + 3 * dst_strd, predicted_pixels, 4); +} + +/** + ******************************************************************************* + * + *ih264_intra_pred_luma_4x4_mode_vert_r + * + * @brief + * Perform Intra prediction for luma_4x4 mode:Vertical_Right + * + * @par Description: + * Perform Intra prediction for luma_4x4 mode:Vertical_Right ,described in sec 8.3.1.2.6 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ngbr_avail + * availability of neighbouring pixels(Not used in this function) + * + * @returns + * + * @remarks + * None + * + *******************************************************************************/ +void ih264_intra_pred_luma_4x4_mode_vert_r(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + + UWORD32 ui4_a, ui4_b, ui4_c, ui4_d, ui4_i, ui4_j, ui4_k, ui4_m; + UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ + UWORD8 *pu1_left = NULL; /* Pointer to start of left predictors */ + UWORD8 *pu1_topleft = NULL;/* Pointer to top left predictor */ + UWORD8 predicted_pixels[10]; + UNUSED(src_strd); + UNUSED(ngbr_avail); + pu1_top = pu1_src +BLK_SIZE + 1; + pu1_left = pu1_src + BLK_SIZE - 1; + pu1_topleft = pu1_src + BLK_SIZE; + + ui4_a = *pu1_top++; + ui4_b = *pu1_top++; + ui4_c = *pu1_top++; + ui4_d = *pu1_top++; + ui4_i = *pu1_left--; + ui4_j = *pu1_left--; + ui4_k = *pu1_left; + ui4_m = *pu1_topleft; + + predicted_pixels[6] = FILT11(ui4_m, ui4_a); + predicted_pixels[7] = FILT11(ui4_a, ui4_b); + predicted_pixels[8] = FILT11(ui4_b, ui4_c); + predicted_pixels[9] = FILT11(ui4_c, ui4_d); + predicted_pixels[1] = FILT121(ui4_i, ui4_m, ui4_a); + predicted_pixels[2] = FILT121(ui4_m, ui4_a, ui4_b); + predicted_pixels[3] = FILT121(ui4_a, ui4_b, ui4_c); + predicted_pixels[4] = FILT121(ui4_b, ui4_c, ui4_d); + predicted_pixels[5] = FILT121(ui4_j, ui4_i, ui4_m); + predicted_pixels[0] = FILT121(ui4_k, ui4_j, ui4_i); + + memcpy(pu1_dst, predicted_pixels + 6, 4); + memcpy(pu1_dst + dst_strd, predicted_pixels + 1, 4); + memcpy(pu1_dst + 2 * dst_strd, predicted_pixels + 5, 4); + memcpy(pu1_dst + 3 * dst_strd, predicted_pixels, 4); +} + +/* + ******************************************************************************* + * + *ih264_intra_pred_luma_4x4_mode_horz_d + * + * @brief + * Perform Intra prediction for luma_4x4 mode:Horizontal_Down + * + * @par Description: + * Perform Intra prediction for luma_4x4 mode:Horizontal_Down ,described in sec 8.3.1.2.7 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ngbr_avail + * availability of neighbouring pixels(Not used in this function) + * + * @returns + * + * @remarks + * None + * + *******************************************************************************/ +void ih264_intra_pred_luma_4x4_mode_horz_d(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ + UWORD8 *pu1_left = NULL; /* Pointer to start of left predictors */ + UWORD8 *pu1_topleft = NULL;/* Pointer to top left predictor */ + UWORD32 ui4_a, ui4_b, ui4_c, ui4_i, ui4_j, ui4_k, ui4_l, ui4_m; + UWORD8 predicted_pixels[10]; + UNUSED(src_strd); + UNUSED(ngbr_avail); + pu1_top = pu1_src + BLK_SIZE + 1; + pu1_left = pu1_src + BLK_SIZE - 1; + pu1_topleft = pu1_src + BLK_SIZE; + + ui4_a = *pu1_top++; + ui4_b = *pu1_top++; + ui4_c = *pu1_top++; + ui4_i = *pu1_left--; + ui4_j = *pu1_left--; + ui4_k = *pu1_left--; + ui4_l = *pu1_left--; + ui4_m = *pu1_topleft; + + predicted_pixels[6] = FILT11(ui4_i, ui4_m); + predicted_pixels[7] = FILT121(ui4_i, ui4_m, ui4_a); + predicted_pixels[8] = FILT121(ui4_m, ui4_a, ui4_b); + predicted_pixels[9] = FILT121(ui4_a, ui4_b, ui4_c); + predicted_pixels[1] = FILT121(ui4_l, ui4_k, ui4_j); + predicted_pixels[2] = FILT11(ui4_k, ui4_j); + predicted_pixels[3] = FILT121(ui4_k, ui4_j, ui4_i); + predicted_pixels[4] = FILT11(ui4_j, ui4_i); + predicted_pixels[5] = FILT121(ui4_j, ui4_i, ui4_m); + predicted_pixels[0] = FILT11(ui4_l, ui4_k); + + memcpy(pu1_dst, predicted_pixels + 6, 4); + memcpy(pu1_dst + dst_strd, predicted_pixels + 4, 4); + memcpy(pu1_dst + 2 * dst_strd, predicted_pixels + 2, 4); + memcpy(pu1_dst + 3 * dst_strd, predicted_pixels, 4); +} + +/** + ******************************************************************************* + * + *ih264_intra_pred_luma_4x4_mode_vert_l + * + * @brief + * Perform Intra prediction for luma_4x4 mode:Vertical_Left + * + * @par Description: + * Perform Intra prediction for luma_4x4 mode:Vertical_Left ,described in sec 8.3.1.2.8 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ngbr_avail + * availability of neighbouring pixels(Not used in this function) + * + * @returns + * + * @remarks + * None + * + *******************************************************************************/ +void ih264_intra_pred_luma_4x4_mode_vert_l(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ + UWORD32 ui4_a, ui4_b, ui4_c, ui4_d, ui4_e, ui4_f, ui4_g; + UWORD8 predicted_pixels[10]; + UNUSED(src_strd); + UNUSED(ngbr_avail); + pu1_top = pu1_src + BLK_SIZE + 1; + + ui4_a = *pu1_top++; + ui4_b = *pu1_top++; + ui4_c = *pu1_top++; + ui4_d = *pu1_top++; + ui4_e = *pu1_top++; + ui4_f = *pu1_top++; + ui4_g = *pu1_top; + + predicted_pixels[5] = FILT11(ui4_a, ui4_b); + predicted_pixels[6] = FILT11(ui4_b, ui4_c); + predicted_pixels[7] = FILT11(ui4_c, ui4_d); + predicted_pixels[8] = FILT11(ui4_d, ui4_e); + predicted_pixels[0] = FILT121(ui4_a, ui4_b, ui4_c); + predicted_pixels[1] = FILT121(ui4_b, ui4_c, ui4_d); + predicted_pixels[2] = FILT121(ui4_c, ui4_d, ui4_e); + predicted_pixels[3] = FILT121(ui4_d, ui4_e, ui4_f); + predicted_pixels[9] = FILT11(ui4_e, ui4_f); + predicted_pixels[4] = FILT121(ui4_e, ui4_f, ui4_g); + + memcpy(pu1_dst, predicted_pixels + 5, 4); + memcpy(pu1_dst + dst_strd, predicted_pixels, 4); + memcpy(pu1_dst + 2 * dst_strd, predicted_pixels + 6, 4); + memcpy(pu1_dst + 3 * dst_strd, predicted_pixels + 1, 4); +} + +/** + ******************************************************************************* + * + *ih264_intra_pred_luma_4x4_mode_horz_u + * + * @brief + * Perform Intra prediction for luma_4x4 mode:Horizontal_Up + * + * @par Description: + * Perform Intra prediction for luma_4x4 mode:Horizontal_Up ,described in sec 8.3.1.2.9 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ngbr_avail + * availability of neighbouring pixels(Not used in this function) + * + * @returns + * + * @remarks + * None + * + *******************************************************************************/ +void ih264_intra_pred_luma_4x4_mode_horz_u(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + UWORD8 *pu1_left = NULL; /* Pointer to start of left predictors */ + UWORD32 ui4_i, ui4_j, ui4_k, ui4_l; + UWORD8 predicted_pixels[10]; + UNUSED(src_strd); + UNUSED(ngbr_avail); + pu1_left = pu1_src + BLK_SIZE - 1; + + ui4_i = *pu1_left--; + ui4_j = *pu1_left--; + ui4_k = *pu1_left--; + ui4_l = *pu1_left--; + + predicted_pixels[0] = FILT11(ui4_j, ui4_i); + predicted_pixels[1] = FILT121(ui4_k, ui4_j, ui4_i); + predicted_pixels[2] = FILT11(ui4_k, ui4_j); + predicted_pixels[3] = FILT121(ui4_l, ui4_k, ui4_j); + predicted_pixels[4] = FILT11(ui4_l, ui4_k); + predicted_pixels[5] = FILT121(ui4_l, ui4_l, ui4_k); + predicted_pixels[6] = ui4_l; + predicted_pixels[7] = ui4_l; + predicted_pixels[8] = ui4_l; + predicted_pixels[9] = ui4_l; + + memcpy(pu1_dst, predicted_pixels, 4); + memcpy(pu1_dst + dst_strd, predicted_pixels + 2, 4); + memcpy(pu1_dst + 2 * dst_strd, predicted_pixels + 4, 4); + memcpy(pu1_dst + 3 * dst_strd, predicted_pixels + 6, 4); +} + +/******************* 8x8 Modes *******************/ + +/** + ******************************************************************************* + * + *ih264_intra_pred_luma_8x8_mode_ref_filtering + * + * @brief + * Reference sample filtering process for Intra_8x8 sample prediction + * + * @par Description: + * Perform Reference sample filtering process for Intra_8x8 sample prediction ,described in sec 8.3.2.2.1 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride[Not Used] + * + * @param[in] dst_strd + * integer destination stride[Not Used] + * + * @param[in] ngbr_avail + * availability of neighbouring pixels(Not used in this function) + * + * @returns + * + * @remarks + * None + * + * + *******************************************************************************/ +void ih264_intra_pred_luma_8x8_mode_ref_filtering(UWORD8 *pu1_left, + UWORD8 *pu1_topleft, + UWORD8 *pu1_top, + UWORD8 *pu1_dst, + WORD32 left_strd, + WORD32 ngbr_avail) +{ + WORD32 top_avail, left_avail, top_left_avail, top_right_avail; + + left_avail = BOOLEAN(ngbr_avail & LEFT_MB_AVAILABLE_MASK); + top_avail = BOOLEAN(ngbr_avail & TOP_MB_AVAILABLE_MASK); + top_left_avail = BOOLEAN(ngbr_avail & TOP_LEFT_MB_AVAILABLE_MASK); + top_right_avail = BOOLEAN(ngbr_avail & TOP_RIGHT_MB_AVAILABLE_MASK); + + if(top_avail) + { + WORD32 i; + UWORD32 u4_xm1; + + if(!top_right_avail) + { + memset(pu1_dst + 8 + 1 + 8, pu1_top[7], 8); + top_right_avail = 1; + } + else + { + memcpy(pu1_dst + 8 + 1 + 8, pu1_top + 8, 8); + } + + if(top_left_avail) + { + pu1_dst[8 + 1 + 0] = FILT121((*pu1_topleft), pu1_top[0], + pu1_top[1]); + + } + else + { + pu1_dst[8 + 1] = ((3 * pu1_top[0]) + pu1_top[1] + 2) >> 2; + } + + for(i = 1; i <= 6; i++) + { + pu1_dst[8 + 1 + i] = FILT121(pu1_top[i - 1], pu1_top[i], + pu1_top[i + 1]); + + } + /* First byte of Top Right input is in pu1_dst[8 + 1 + 8]*/ + pu1_dst[8 + 1 + 7] = FILT121(pu1_top[6], pu1_top[7], + pu1_dst[8 + 1 + 8]); + + /* filtered output and source in same buf, to prevent output(x - 1) + being over written in process */ + u4_xm1 = pu1_top[7]; + + for(i = 8; i <= 14; i++) + { + UWORD32 u4_x; + u4_x = (u4_xm1 + (pu1_dst[8 + 1 + i] << 1) + pu1_dst[8 + 1 + i + 1] + + 2) >> 2; + /* assigning u4_xm1 from the un-filtered values for the next iteration */ + u4_xm1 = pu1_dst[8 + 1 + i]; + pu1_dst[8 + 1 + i] = u4_x; + } + + pu1_dst[8 + 1 + 15] = (u4_xm1 + (3 * pu1_dst[8 + 1 + 15]) + 2) >> 2; + + } + + /* pu1_topleft is overloaded. It is both: */ + /* a. A pointer for the top left pixel */ + /* b. An indicator of availability of top left. */ + /* If it is null then top left not available */ + if(top_left_avail) + { + if((!top_avail) || (!left_avail)) + { + if(top_avail) + pu1_dst[8] = (3 * pu1_topleft[0] + pu1_top[0] + 2) >> 2; + else if(left_avail) + pu1_dst[8] = (3 * pu1_topleft[0] + pu1_left[0] + 2) >> 2; + } + else + { + pu1_dst[8] = FILT121(pu1_top[0], (*pu1_topleft), pu1_left[0]); + } + } + + if(left_avail) + { + UWORD32 idx; + if(0 != pu1_topleft) + { + pu1_dst[7] = FILT121((*pu1_topleft), pu1_left[0], + pu1_left[left_strd]); + } + else + { + pu1_dst[7] = ((3 * pu1_left[0]) + pu1_left[left_strd] + 2) >> 2; + } + + for(idx = 1; idx <= 6; idx++) + { + pu1_dst[7 - idx] = FILT121(pu1_left[(idx - 1) * left_strd], + pu1_left[idx * left_strd], + pu1_left[(idx + 1) * left_strd]); + + } + pu1_dst[0] = (pu1_left[6 * left_strd] + 3 * pu1_left[7 * left_strd] + 2) + >> 2; + + } +} + +/** + ******************************************************************************* + * + *ih264_intra_pred_luma_8x8_mode_vert + * + * @brief + * Perform Intra prediction for luma_8x8 mode:vertical + * + * @par Description: + * Perform Intra prediction for luma_8x8 mode:vertical ,described in sec 8.3.2.2.2 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ngbr_avail + * availability of neighbouring pixels(Not used in this function) + * + * @returns + * + * @remarks + * None + * + ******************************************************************************* + */ +void ih264_intra_pred_luma_8x8_mode_vert(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + UWORD8 *pu1_top = NULL; + UNUSED(src_strd); + UNUSED(ngbr_avail); + pu1_top = pu1_src + BLK8x8SIZE + 1; + + memcpy(pu1_dst, pu1_top, 8); + memcpy(pu1_dst + dst_strd, pu1_top, 8); + memcpy(pu1_dst + 2 * dst_strd, pu1_top, 8); + memcpy(pu1_dst + 3 * dst_strd, pu1_top, 8); + memcpy(pu1_dst + 4 * dst_strd, pu1_top, 8); + memcpy(pu1_dst + 5 * dst_strd, pu1_top, 8); + memcpy(pu1_dst + 6 * dst_strd, pu1_top, 8); + memcpy(pu1_dst + 7 * dst_strd, pu1_top, 8); +} + +/** + ******************************************************************************* + * + *ih264_intra_pred_luma_8x8_mode_horz + * + * @brief + * Perform Intra prediction for luma_8x8 mode:horizontal + * + * @par Description: + * Perform Intra prediction for luma_8x8 mode:horizontal ,described in sec 8.3.2.2.2 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ngbr_avail + * availability of neighbouring pixels(Not used in this function) + * + * @returns + * + * @remarks + * None + * + ******************************************************************************* + */ + +void ih264_intra_pred_luma_8x8_mode_horz(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + UWORD8 *pu1_left = pu1_src + BLK8x8SIZE - 1; + UNUSED(src_strd); + UNUSED(ngbr_avail); + memset(pu1_dst, *pu1_left, 8); + memset(pu1_dst + dst_strd, *(pu1_left - 1), 8); + memset(pu1_dst + 2 * dst_strd, *(pu1_left - 2), 8); + memset(pu1_dst + 3 * dst_strd, *(pu1_left - 3), 8); + memset(pu1_dst + 4 * dst_strd, *(pu1_left - 4), 8); + memset(pu1_dst + 5 * dst_strd, *(pu1_left - 5), 8); + memset(pu1_dst + 6 * dst_strd, *(pu1_left - 6), 8); + memset(pu1_dst + 7 * dst_strd, *(pu1_left - 7), 8); +} + +/** + ******************************************************************************* + * + *ih264_intra_pred_luma_8x8_mode_dc + * + * @brief + * Perform Intra prediction for luma_8x8 mode:DC + * + * @par Description: + * Perform Intra prediction for luma_8x8 mode:DC ,described in sec 8.3.2.2.4 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ngbr_avail + * availability of neighbouring pixels + * + * @returns + * + * @remarks + * None + * + *******************************************************************************/ +void ih264_intra_pred_luma_8x8_mode_dc(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + UWORD8 u1_useleft; /* availability of left predictors (only for DC) */ + UWORD8 u1_usetop; /* availability of top predictors (only for DC) */ + UWORD8 *pu1_left = NULL; /* Pointer to start of left predictors */ + UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ + WORD32 row; + WORD32 val = 0; + UNUSED(src_strd); + + u1_useleft = BOOLEAN(ngbr_avail & LEFT_MB_AVAILABLE_MASK); + u1_usetop = BOOLEAN(ngbr_avail & TOP_MB_AVAILABLE_MASK); + pu1_top = pu1_src + BLK8x8SIZE + 1; + pu1_left = pu1_src + BLK8x8SIZE - 1; + + if(u1_useleft) + { + for(row = 0; row < BLK8x8SIZE; row++) + val += *(pu1_left - row); + val += 4; + } + if(u1_usetop) + { + for(row = 0; row < BLK8x8SIZE; row++) + val += *(pu1_top + row); + val += 4; + } + + /* Since 4 is added if either left/top pred is there, + val still being zero implies both preds are not there */ + val = (val) ? (val >> (2 + u1_useleft + u1_usetop)) : 128; + + memset(pu1_dst, val, 8); + memset(pu1_dst + dst_strd, val, 8); + memset(pu1_dst + 2 * dst_strd, val, 8); + memset(pu1_dst + 3 * dst_strd, val, 8); + memset(pu1_dst + 4 * dst_strd, val, 8); + memset(pu1_dst + 5 * dst_strd, val, 8); + memset(pu1_dst + 6 * dst_strd, val, 8); + memset(pu1_dst + 7 * dst_strd, val, 8); +} + +/** + ******************************************************************************* + * + *ih264_intra_pred_luma_8x8_mode_diag_dl + * + * @brief + * Perform Intra prediction for luma_8x8 mode:Diagonal_Down_Left + * + * @par Description: + * Perform Intra prediction for luma_8x8 mode:Diagonal_Down_Left ,described in sec 8.3.2.2.5 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ngbr_avail + * availability of neighbouring pixels(Not used in this function) + * + * @returns + * + * @remarks + * None + * + *******************************************************************************/ +void ih264_intra_pred_luma_8x8_mode_diag_dl(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ + UWORD32 ui4_a, ui4_b, ui4_c, ui4_d, ui4_e, ui4_f, ui4_g, ui4_h; + UWORD32 ui4_i, ui4_j, ui4_k, ui4_l, ui4_m, ui4_n, ui4_o, ui4_p; + UWORD8 predicted_pixels[15]; + UNUSED(src_strd); + UNUSED(ngbr_avail); + pu1_top = pu1_src + BLK8x8SIZE + 1; + + ui4_a = *pu1_top++; + ui4_b = *pu1_top++; + ui4_c = *pu1_top++; + ui4_d = *pu1_top++; + ui4_e = *pu1_top++; + ui4_f = *pu1_top++; + ui4_g = *pu1_top++; + ui4_h = *pu1_top++; + ui4_i = *pu1_top++; + ui4_j = *pu1_top++; + ui4_k = *pu1_top++; + ui4_l = *pu1_top++; + ui4_m = *pu1_top++; + ui4_n = *pu1_top++; + ui4_o = *pu1_top++; + ui4_p = *pu1_top; + + predicted_pixels[0] = FILT121(ui4_a, ui4_b, ui4_c); + predicted_pixels[1] = FILT121(ui4_b, ui4_c, ui4_d); + predicted_pixels[2] = FILT121(ui4_c, ui4_d, ui4_e); + predicted_pixels[3] = FILT121(ui4_d, ui4_e, ui4_f); + predicted_pixels[4] = FILT121(ui4_e, ui4_f, ui4_g); + predicted_pixels[5] = FILT121(ui4_f, ui4_g, ui4_h); + predicted_pixels[6] = FILT121(ui4_g, ui4_h, ui4_i); + predicted_pixels[7] = FILT121(ui4_h, ui4_i, ui4_j); + predicted_pixels[8] = FILT121(ui4_i, ui4_j, ui4_k); + predicted_pixels[9] = FILT121(ui4_j, ui4_k, ui4_l); + predicted_pixels[10] = FILT121(ui4_k, ui4_l, ui4_m); + predicted_pixels[11] = FILT121(ui4_l, ui4_m, ui4_n); + predicted_pixels[12] = FILT121(ui4_m, ui4_n, ui4_o); + predicted_pixels[13] = FILT121(ui4_n, ui4_o, ui4_p); + predicted_pixels[14] = FILT121(ui4_o, ui4_p, ui4_p); + + memcpy(pu1_dst, predicted_pixels, 8); + memcpy(pu1_dst + dst_strd, predicted_pixels + 1, 8); + memcpy(pu1_dst + 2 * dst_strd, predicted_pixels + 2, 8); + memcpy(pu1_dst + 3 * dst_strd, predicted_pixels + 3, 8); + memcpy(pu1_dst + 4 * dst_strd, predicted_pixels + 4, 8); + memcpy(pu1_dst + 5 * dst_strd, predicted_pixels + 5, 8); + memcpy(pu1_dst + 6 * dst_strd, predicted_pixels + 6, 8); + memcpy(pu1_dst + 7 * dst_strd, predicted_pixels + 7, 8); +} + +/** + ******************************************************************************* + * + *ih264_intra_pred_luma_8x8_mode_diag_dr + * + * @brief + * Perform Intra prediction for luma_8x8 mode:Diagonal_Down_Right + * + * @par Description: + * Perform Intra prediction for luma_8x8 mode:Diagonal_Down_Right ,described in sec 8.3.2.2.6 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ngbr_avail + * availability of neighbouring pixels(Not used in this function) + * + * @returns + * + * @remarks + * None + * + *******************************************************************************/ +void ih264_intra_pred_luma_8x8_mode_diag_dr(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + UWORD8 *pu1_left = NULL; /* Pointer to start of left predictors */ + UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ + UWORD8 *pu1_topleft = NULL; /* Pointer to start of top left predictors */ + UWORD32 ui4_a; + UWORD32 ui4_b, ui4_c, ui4_d, ui4_e, ui4_f, ui4_g, ui4_h, ui4_i; + UWORD32 ui4_j, ui4_k, ui4_l, ui4_m, ui4_n, ui4_o, ui4_p, ui4_q; + UWORD8 predicted_pixels[15]; + UNUSED(src_strd); + UNUSED(ngbr_avail); + pu1_top = pu1_src + BLK8x8SIZE + 1; + pu1_left = pu1_src + BLK8x8SIZE - 1; + pu1_topleft = pu1_src + BLK8x8SIZE; + + ui4_a = *pu1_topleft; + ui4_b = *pu1_top++; + ui4_c = *pu1_top++; + ui4_d = *pu1_top++; + ui4_e = *pu1_top++; + ui4_f = *pu1_top++; + ui4_g = *pu1_top++; + ui4_h = *pu1_top++; + ui4_i = *pu1_top; + ui4_j = *pu1_left--; + ui4_k = *pu1_left--; + ui4_l = *pu1_left--; + ui4_m = *pu1_left--; + ui4_n = *pu1_left--; + ui4_o = *pu1_left--; + ui4_p = *pu1_left--; + ui4_q = *pu1_left; + + predicted_pixels[6] = FILT121(ui4_a, ui4_j, ui4_k); + predicted_pixels[5] = FILT121(ui4_j, ui4_k, ui4_l); + predicted_pixels[4] = FILT121(ui4_k, ui4_l, ui4_m); + predicted_pixels[3] = FILT121(ui4_l, ui4_m, ui4_n); + predicted_pixels[2] = FILT121(ui4_m, ui4_n, ui4_o); + predicted_pixels[1] = FILT121(ui4_n, ui4_o, ui4_p); + predicted_pixels[0] = FILT121(ui4_o, ui4_p, ui4_q); + predicted_pixels[7] = FILT121(ui4_b, ui4_a, ui4_j); + predicted_pixels[8] = FILT121(ui4_a, ui4_b, ui4_c); + predicted_pixels[9] = FILT121(ui4_b, ui4_c, ui4_d); + predicted_pixels[10] = FILT121(ui4_c, ui4_d, ui4_e); + predicted_pixels[11] = FILT121(ui4_d, ui4_e, ui4_f); + predicted_pixels[12] = FILT121(ui4_e, ui4_f, ui4_g); + predicted_pixels[13] = FILT121(ui4_f, ui4_g, ui4_h); + predicted_pixels[14] = FILT121(ui4_g, ui4_h, ui4_i); + + memcpy(pu1_dst, predicted_pixels + 7, 8); + memcpy(pu1_dst + dst_strd, predicted_pixels + 6, 8); + memcpy(pu1_dst + 2 * dst_strd, predicted_pixels + 5, 8); + memcpy(pu1_dst + 3 * dst_strd, predicted_pixels + 4, 8); + memcpy(pu1_dst + 4 * dst_strd, predicted_pixels + 3, 8); + memcpy(pu1_dst + 5 * dst_strd, predicted_pixels + 2, 8); + memcpy(pu1_dst + 6 * dst_strd, predicted_pixels + 1, 8); + memcpy(pu1_dst + 7 * dst_strd, predicted_pixels, 8); +} + +/** + ******************************************************************************* + * + *ih264_intra_pred_luma_8x8_mode_vert_r + * + * @brief + * Perform Intra prediction for luma_8x8 mode:Vertical_Right + * + * @par Description: + * Perform Intra prediction for luma_8x8 mode:Vertical_Right ,described in sec 8.3.2.2.7 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ngbr_avail + * availability of neighbouring pixels(Not used in this function) + * + * @returns + * + * @remarks + * None + * + *******************************************************************************/ +void ih264_intra_pred_luma_8x8_mode_vert_r(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + UWORD8 *pu1_left = NULL; /* Pointer to start of left predictors */ + UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ + UWORD8 *pu1_topleft = NULL; /* Pointer to start of top left predictors */ + UWORD32 ui4_a; + UWORD32 ui4_b, ui4_c, ui4_d, ui4_e, ui4_f, ui4_g, ui4_h, ui4_i; + UWORD32 ui4_j, ui4_k, ui4_l, ui4_m, ui4_n, ui4_o, ui4_p; + UWORD8 predicted_pixels[22]; + + UNUSED(src_strd); + UNUSED(ngbr_avail); + pu1_top = pu1_src + BLK8x8SIZE + 1; + pu1_left = pu1_src + BLK8x8SIZE - 1; + pu1_topleft = pu1_src + BLK8x8SIZE; + + ui4_a = *pu1_topleft; + + ui4_b = *pu1_top++; + ui4_c = *pu1_top++; + ui4_d = *pu1_top++; + ui4_e = *pu1_top++; + ui4_f = *pu1_top++; + ui4_g = *pu1_top++; + ui4_h = *pu1_top++; + ui4_i = *pu1_top; + ui4_j = *pu1_left--; + ui4_k = *pu1_left--; + ui4_l = *pu1_left--; + ui4_m = *pu1_left--; + ui4_n = *pu1_left--; + ui4_o = *pu1_left--; + ui4_p = *pu1_left--; + + predicted_pixels[0] = FILT121(ui4_o, ui4_n, ui4_m); + predicted_pixels[1] = FILT121(ui4_m, ui4_l, ui4_k); + predicted_pixels[2] = FILT121(ui4_k, ui4_j, ui4_a); + predicted_pixels[3] = FILT11(ui4_a, ui4_b); + predicted_pixels[4] = FILT11(ui4_b, ui4_c); + predicted_pixels[5] = FILT11(ui4_c, ui4_d); + predicted_pixels[6] = FILT11(ui4_d, ui4_e); + predicted_pixels[7] = FILT11(ui4_e, ui4_f); + predicted_pixels[8] = FILT11(ui4_f, ui4_g); + predicted_pixels[9] = FILT11(ui4_g, ui4_h); + predicted_pixels[10] = FILT11(ui4_h, ui4_i); + predicted_pixels[11] = FILT121(ui4_p, ui4_o, ui4_n); + predicted_pixels[12] = FILT121(ui4_n, ui4_m, ui4_l); + predicted_pixels[13] = FILT121(ui4_l, ui4_k, ui4_j); + predicted_pixels[14] = FILT121(ui4_b, ui4_a, ui4_j); + predicted_pixels[15] = FILT121(ui4_a, ui4_b, ui4_c); + predicted_pixels[16] = FILT121(ui4_b, ui4_c, ui4_d); + predicted_pixels[17] = FILT121(ui4_c, ui4_d, ui4_e); + predicted_pixels[18] = FILT121(ui4_d, ui4_e, ui4_f); + predicted_pixels[19] = FILT121(ui4_e, ui4_f, ui4_g); + predicted_pixels[20] = FILT121(ui4_f, ui4_g, ui4_h); + predicted_pixels[21] = FILT121(ui4_g, ui4_h, ui4_i); + + memcpy(pu1_dst, predicted_pixels + 3, 8); + memcpy(pu1_dst + 1 * dst_strd, predicted_pixels + 14, 8); + memcpy(pu1_dst + 2 * dst_strd, predicted_pixels + 2, 8); + memcpy(pu1_dst + 3 * dst_strd, predicted_pixels + 13, 8); + memcpy(pu1_dst + 4 * dst_strd, predicted_pixels + 1, 8); + memcpy(pu1_dst + 5 * dst_strd, predicted_pixels + 12, 8); + memcpy(pu1_dst + 6 * dst_strd, predicted_pixels, 8); + memcpy(pu1_dst + 7 * dst_strd, predicted_pixels + 11, 8); + +} + +/* + ******************************************************************************* + * + *ih264_intra_pred_luma_8x8_mode_horz_d + * + * @brief + * Perform Intra prediction for luma_8x8 mode:Horizontal_Down + * + * @par Description: + * Perform Intra prediction for luma_8x8 mode:Horizontal_Down ,described in sec 8.3.2.2.8 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ngbr_avail + * availability of neighbouring pixels(Not used in this function) + * + * @returns + * + * @remarks + * None + * + *******************************************************************************/ + +void ih264_intra_pred_luma_8x8_mode_horz_d(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + UWORD8 *pu1_left = NULL; /* Pointer to start of left predictors */ + UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ + UWORD8 *pu1_topleft = NULL; /* Pointer to start of top left predictors */ + UWORD32 ui4_a; + UWORD32 ui4_b, ui4_c, ui4_d, ui4_e, ui4_f, ui4_g, ui4_h, ui4_i; + UWORD32 ui4_j, ui4_k, ui4_l, ui4_m, ui4_n, ui4_o, ui4_p; + UWORD8 predicted_pixels[22]; + UNUSED(src_strd); + UNUSED(ngbr_avail); + pu1_top = pu1_src + BLK8x8SIZE + 1; + pu1_left = pu1_src + BLK8x8SIZE - 1; + pu1_topleft = pu1_src + BLK8x8SIZE; + + ui4_a = *pu1_topleft; + ui4_j = *pu1_top++; + ui4_k = *pu1_top++; + ui4_l = *pu1_top++; + ui4_m = *pu1_top++; + ui4_n = *pu1_top++; + ui4_o = *pu1_top++; + ui4_p = *pu1_top++; + ui4_b = *pu1_left--; + ui4_c = *pu1_left--; + ui4_d = *pu1_left--; + ui4_e = *pu1_left--; + ui4_f = *pu1_left--; + ui4_g = *pu1_left--; + ui4_h = *pu1_left--; + ui4_i = *pu1_left; + + predicted_pixels[0] = FILT11(ui4_h, ui4_i); + predicted_pixels[1] = FILT121(ui4_g, ui4_h, ui4_i); + predicted_pixels[2] = FILT11(ui4_g, ui4_h); + predicted_pixels[3] = FILT121(ui4_f, ui4_g, ui4_h); + predicted_pixels[4] = FILT11(ui4_f, ui4_g); + predicted_pixels[5] = FILT121(ui4_e, ui4_f, ui4_g); + predicted_pixels[6] = FILT11(ui4_e, ui4_f); + predicted_pixels[7] = FILT121(ui4_d, ui4_e, ui4_f); + predicted_pixels[8] = FILT11(ui4_d, ui4_e); + predicted_pixels[9] = FILT121(ui4_c, ui4_d, ui4_e); + predicted_pixels[10] = FILT11(ui4_c, ui4_d); + predicted_pixels[11] = FILT121(ui4_b, ui4_c, ui4_d); + predicted_pixels[12] = FILT11(ui4_b, ui4_c); + predicted_pixels[13] = FILT121(ui4_a, ui4_b, ui4_c); + predicted_pixels[14] = FILT11(ui4_a, ui4_b); + predicted_pixels[15] = FILT121(ui4_j, ui4_a, ui4_b); + predicted_pixels[16] = FILT121(ui4_k, ui4_j, ui4_a); + predicted_pixels[17] = FILT121(ui4_l, ui4_k, ui4_j); + predicted_pixels[18] = FILT121(ui4_m, ui4_l, ui4_k); + predicted_pixels[19] = FILT121(ui4_n, ui4_m, ui4_l); + predicted_pixels[20] = FILT121(ui4_o, ui4_n, ui4_m); + predicted_pixels[21] = FILT121(ui4_p, ui4_o, ui4_n); + + memcpy(pu1_dst, predicted_pixels + 14, 8); + memcpy(pu1_dst + dst_strd, predicted_pixels + 12, 8); + memcpy(pu1_dst + 2 * dst_strd, predicted_pixels + 10, 8); + memcpy(pu1_dst + 3 * dst_strd, predicted_pixels + 8, 8); + memcpy(pu1_dst + 4 * dst_strd, predicted_pixels + 6, 8); + memcpy(pu1_dst + 5 * dst_strd, predicted_pixels + 4, 8); + memcpy(pu1_dst + 6 * dst_strd, predicted_pixels + 2, 8); + memcpy(pu1_dst + 7 * dst_strd, predicted_pixels, 8); +} + +/** + ******************************************************************************* + * + *ih264_intra_pred_luma_8x8_mode_vert_l + * + * @brief + * Perform Intra prediction for luma_8x8 mode:Vertical_Left + * + * @par Description: + * Perform Intra prediction for luma_8x8 mode:Vertical_Left ,described in sec 8.3.2.2.9 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ngbr_avail + * availability of neighbouring pixels(Not used in this function) + * + * @returns + * + * @remarks + * None + * + *******************************************************************************/ + +void ih264_intra_pred_luma_8x8_mode_vert_l(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ + UWORD32 ui4_a, ui4_b, ui4_c, ui4_d, ui4_e, ui4_f, ui4_g, ui4_h; + UWORD32 ui4_i, ui4_j, ui4_k, ui4_l, ui4_m; + UWORD8 predicted_pixels[22]; + UNUSED(src_strd); + UNUSED(ngbr_avail); + pu1_top = pu1_src + BLK8x8SIZE + 1; + + ui4_a = *pu1_top++; + ui4_b = *pu1_top++; + ui4_c = *pu1_top++; + ui4_d = *pu1_top++; + ui4_e = *pu1_top++; + ui4_f = *pu1_top++; + ui4_g = *pu1_top++; + ui4_h = *pu1_top++; + ui4_i = *pu1_top++; + ui4_j = *pu1_top++; + ui4_k = *pu1_top++; + ui4_l = *pu1_top++; + ui4_m = *pu1_top++; + + predicted_pixels[0] = FILT11(ui4_a, ui4_b); + predicted_pixels[1] = FILT11(ui4_b, ui4_c); + predicted_pixels[2] = FILT11(ui4_c, ui4_d); + predicted_pixels[3] = FILT11(ui4_d, ui4_e); + predicted_pixels[4] = FILT11(ui4_e, ui4_f); + predicted_pixels[5] = FILT11(ui4_f, ui4_g); + predicted_pixels[6] = FILT11(ui4_g, ui4_h); + predicted_pixels[7] = FILT11(ui4_h, ui4_i); + predicted_pixels[8] = FILT11(ui4_i, ui4_j); + predicted_pixels[9] = FILT11(ui4_j, ui4_k); + predicted_pixels[10] = FILT11(ui4_k, ui4_l); + predicted_pixels[11] = FILT121(ui4_a, ui4_b, ui4_c); + predicted_pixels[12] = FILT121(ui4_b, ui4_c, ui4_d); + predicted_pixels[13] = FILT121(ui4_c, ui4_d, ui4_e); + predicted_pixels[14] = FILT121(ui4_d, ui4_e, ui4_f); + predicted_pixels[15] = FILT121(ui4_e, ui4_f, ui4_g); + predicted_pixels[16] = FILT121(ui4_f, ui4_g, ui4_h); + predicted_pixels[17] = FILT121(ui4_g, ui4_h, ui4_i); + predicted_pixels[18] = FILT121(ui4_h, ui4_i, ui4_j); + predicted_pixels[19] = FILT121(ui4_i, ui4_j, ui4_k); + predicted_pixels[20] = FILT121(ui4_j, ui4_k, ui4_l); + predicted_pixels[21] = FILT121(ui4_k, ui4_l, ui4_m); + + memcpy(pu1_dst, predicted_pixels, 8); + memcpy(pu1_dst + 2 * dst_strd, predicted_pixels + 1, 8); + memcpy(pu1_dst + 4 * dst_strd, predicted_pixels + 2, 8); + memcpy(pu1_dst + 6 * dst_strd, predicted_pixels + 3, 8); + memcpy(pu1_dst + 1 * dst_strd, predicted_pixels + 11, 8); + memcpy(pu1_dst + 3 * dst_strd, predicted_pixels + 12, 8); + memcpy(pu1_dst + 5 * dst_strd, predicted_pixels + 13, 8); + memcpy(pu1_dst + 7 * dst_strd, predicted_pixels + 14, 8); +} + +/** + ******************************************************************************* + * + *ih264_intra_pred_luma_8x8_mode_horz_u + * + * @brief + * Perform Intra prediction for luma_8x8 mode:Horizontal_Up + * + * @par Description: + * Perform Intra prediction for luma_8x8 mode:Horizontal_Up ,described in sec 8.3.2.2.10 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ngbr_avail + * availability of neighbouring pixels(Not used in this function) + * + * @returns + * + * @remarks + * None + * + *******************************************************************************/ + +void ih264_intra_pred_luma_8x8_mode_horz_u(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) + +{ + UWORD8 *pu1_left = NULL; /* Pointer to start of left predictors */ + UWORD32 ui4_j, ui4_k, ui4_l, ui4_m, ui4_n, ui4_o, ui4_p, ui4_q; + UWORD8 predicted_pixels[22]; + UNUSED(src_strd); + UNUSED(ngbr_avail); + pu1_left = pu1_src + BLK8x8SIZE - 1; + + ui4_j = *pu1_left--; + ui4_k = *pu1_left--; + ui4_l = *pu1_left--; + ui4_m = *pu1_left--; + ui4_n = *pu1_left--; + ui4_o = *pu1_left--; + ui4_p = *pu1_left--; + ui4_q = *pu1_left; + + pu1_left = pu1_src + BLK8x8SIZE - 1; + + predicted_pixels[0] = FILT11(ui4_j, ui4_k); + predicted_pixels[1] = FILT121(ui4_j, ui4_k, ui4_l); + predicted_pixels[2] = FILT11(ui4_k, ui4_l); + predicted_pixels[3] = FILT121(ui4_k, ui4_l, ui4_m); + predicted_pixels[4] = FILT11(ui4_l, ui4_m); + predicted_pixels[5] = FILT121(ui4_l, ui4_m, ui4_n); + predicted_pixels[6] = FILT11(ui4_m, ui4_n); + predicted_pixels[7] = FILT121(ui4_m, ui4_n, ui4_o); + predicted_pixels[8] = FILT11(ui4_n, ui4_o); + predicted_pixels[9] = FILT121(ui4_n, ui4_o, ui4_p); + predicted_pixels[10] = FILT11(ui4_o, ui4_p); + predicted_pixels[11] = FILT121(ui4_o, ui4_p, ui4_q); + predicted_pixels[12] = FILT11(ui4_p, ui4_q); + predicted_pixels[13] = FILT121(ui4_p, ui4_q, ui4_q); + memset(predicted_pixels+14,ui4_q,8); + + memcpy(pu1_dst, predicted_pixels, 8); + memcpy(pu1_dst + 1 * dst_strd, predicted_pixels + 2, 8); + memcpy(pu1_dst + 2 * dst_strd, predicted_pixels + 4, 8); + memcpy(pu1_dst + 3 * dst_strd, predicted_pixels + 6, 8); + memcpy(pu1_dst + 4 * dst_strd, predicted_pixels + 8, 8); + memcpy(pu1_dst + 5 * dst_strd, predicted_pixels + 10, 8); + memcpy(pu1_dst + 6 * dst_strd, predicted_pixels + 12, 8); + memcpy(pu1_dst + 7 * dst_strd, predicted_pixels + 14, 8); +} + + +/******************* 16x16 Modes *******************/ + +/** + ******************************************************************************* + * + *ih264_intra_pred_luma_16x16_mode_vert + * + * @brief + * Perform Intra prediction for luma_16x16 mode:Vertical + * + * @par Description: + * Perform Intra prediction for luma_16x16 mode:Vertical, described in sec 8.3.3.1 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ngbr_avail + * availability of neighbouring pixels (Not used in this function) + * + * @returns + * + * @remarks + * None + * + *******************************************************************************/ + +void ih264_intra_pred_luma_16x16_mode_vert(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ + WORD32 rows; /* loop variables*/ + UNUSED(src_strd); + UNUSED(ngbr_avail); + pu1_top = pu1_src + MB_SIZE + 1; + + for(rows = 0; rows < 16; rows += 4, pu1_dst += dst_strd) + { + memcpy(pu1_dst, pu1_top, 16); + pu1_dst += dst_strd; + memcpy(pu1_dst, pu1_top, 16); + pu1_dst += dst_strd; + memcpy(pu1_dst, pu1_top, 16); + pu1_dst += dst_strd; + memcpy(pu1_dst, pu1_top, 16); + } +} + +/** + ******************************************************************************* + * + *ih264_intra_pred_luma_16x16_mode_horz + * + * @brief + * Perform Intra prediction for luma_16x16 mode:Horizontal + * + * @par Description: + * Perform Intra prediction for luma_16x16 mode:Horizontal, described in sec 8.3.3.2 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ngbr_avail + * availability of neighbouring pixels(Not used in this function) + * + * @returns + * + * @remarks + * None + * + *******************************************************************************/ + +void ih264_intra_pred_luma_16x16_mode_horz(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + UWORD8 *pu1_left = NULL; /* Pointer to start of top predictors */ + WORD32 rows; + UNUSED(src_strd); + UNUSED(ngbr_avail); + pu1_left = pu1_src + MB_SIZE - 1; + + for(rows = 0; rows < 16; rows += 4, pu1_dst += dst_strd, pu1_left --) + { + memset(pu1_dst, *pu1_left, 16); /* copy the left value to the entire row*/ + pu1_left --; + pu1_dst += dst_strd; + memset(pu1_dst, *pu1_left, 16); + pu1_left --; + pu1_dst += dst_strd; + memset(pu1_dst, *pu1_left, 16); + pu1_left --; + pu1_dst += dst_strd; + memset(pu1_dst, *pu1_left, 16); + } +} + +/** + ******************************************************************************* + * + *ih264_intra_pred_luma_16x16_mode_dc + * + * @brief + * Perform Intra prediction for luma_16x16 mode:DC + * + * @par Description: + * Perform Intra prediction for luma_16x16 mode:DC, described in sec 8.3.3.3 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + ** @param[in] ngbr_avail + * availability of neighbouring pixels + * + * @returns + * + * @remarks + * None + * + *******************************************************************************/ + +void ih264_intra_pred_luma_16x16_mode_dc(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + WORD8 u1_useleft; /* availability of left predictors (only for DC) */ + UWORD8 u1_usetop; /* availability of top predictors (only for DC) */ + UWORD8 *pu1_left = NULL; /* Pointer to start of left predictors */ + UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ + WORD32 rows; /* loop variables*/ + WORD32 val = 0; + UNUSED(src_strd); + + u1_useleft = BOOLEAN(ngbr_avail & LEFT_MB_AVAILABLE_MASK); + u1_usetop = BOOLEAN(ngbr_avail & TOP_MB_AVAILABLE_MASK); + pu1_top = pu1_src + MB_SIZE + 1; + pu1_left = pu1_src + MB_SIZE - 1; + if(u1_useleft) + { + for(rows = 0; rows < 16; rows++) + val += *(pu1_left - rows); + val += 8; + } + if(u1_usetop) + { + for(rows = 0; rows < 16; rows++) + val += *(pu1_top + rows); + val += 8; + } + /* Since 8 is added if either left/top pred is there, + val still being zero implies both preds are not there */ + val = (val) ? (val >> (3 + u1_useleft + u1_usetop)) : 128; + + for(rows = 0; rows < 16; rows += 4, pu1_dst += dst_strd) + { + memset(pu1_dst, val, 16); + pu1_dst += dst_strd; + memset(pu1_dst, val, 16); + pu1_dst += dst_strd; + memset(pu1_dst, val, 16); + pu1_dst += dst_strd; + memset(pu1_dst, val, 16); + } +} + +/** + ******************************************************************************* + * + *ih264_intra_pred_luma_16x16_mode_plane + * + * @brief + * Perform Intra prediction for luma_16x16 mode:PLANE + * + * @par Description: + * Perform Intra prediction for luma_16x16 mode:PLANE, described in sec 8.3.3.4 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ngbr_avail + * availability of neighbouring pixels(Not used in this function) + * + * @returns + * + * @remarks + * None + * + *******************************************************************************/ + +void ih264_intra_pred_luma_16x16_mode_plane(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + /*! Written with no multiplications */ + UWORD8 *pu1_left = NULL; /* Pointer to start of left predictors */ + UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ + UWORD8 *pu1_topleft = NULL; + WORD32 a, b, c, tmp; + UWORD8 *pu1_tmp1, *pu1_tmp2; + WORD32 shift; + UNUSED(src_strd); + UNUSED(ngbr_avail); + pu1_top = pu1_src + MB_SIZE + 1; + pu1_left = pu1_src + MB_SIZE - 1; + pu1_topleft = pu1_src + MB_SIZE; + + { + a = (*(pu1_top + 15) + *(pu1_left - 15)) << 4; + + /*! Implement Sum(x*(P((x+7),-1) - P((x-7),-1))) x=1...8 */ + pu1_tmp1 = pu1_top + 8; + pu1_tmp2 = pu1_tmp1 - 2; + + /* Pixel diffs are only 9 bits; + so sign extension allows shifts to be used even for signed */ + b = ((*pu1_tmp1++) - (*pu1_tmp2--)); /* x=1 */ + b += ((*pu1_tmp1++) - (*pu1_tmp2--)) << 1; /* x=2 */ + tmp = ((*pu1_tmp1++) - (*pu1_tmp2--)); + b += (tmp << 1) + tmp; /* x=3 */ + b += ((*pu1_tmp1++) - (*pu1_tmp2--)) << 2; /* x=4 */ + + tmp = ((*pu1_tmp1++) - (*pu1_tmp2--)); + b += (tmp << 2) + tmp; /* x=5 */ + tmp = ((*pu1_tmp1++) - (*pu1_tmp2--)); + b += (tmp << 2) + (tmp << 1); /* x=6 */ + tmp = ((*pu1_tmp1++) - (*pu1_tmp2--)); + b += (tmp << 3) - tmp; /* x=7 */ + b += ((*pu1_tmp1) - (*pu1_topleft)) << 3; /* x=8 */ + + b = ((b << 2) + b + 32) >> 6; /*! (5*H + 32)>>6 */ + + /*! Implement Sum(y*(P(-1,(y+7)) - P(-1,(y-7)))) y=1...8 */ + pu1_tmp1 = pu1_left - 8; + pu1_tmp2 = pu1_tmp1 + 2; + + c = ((*pu1_tmp1) - (*pu1_tmp2)); /* y=1 */ + pu1_tmp1--; + pu1_tmp2++; + c += ((*pu1_tmp1) - (*pu1_tmp2)) << 1; /* y=2 */ + pu1_tmp1--; + pu1_tmp2++; + tmp = ((*pu1_tmp1) - (*pu1_tmp2)); + c += (tmp << 1) + tmp; /* y=3 */ + pu1_tmp1--; + pu1_tmp2++; + c += ((*pu1_tmp1) - (*pu1_tmp2)) << 2; /* y=4 */ + pu1_tmp1--; + pu1_tmp2++; + + tmp = ((*pu1_tmp1) - (*pu1_tmp2)); + c += (tmp << 2) + tmp; /* y=5 */ + pu1_tmp1--; + pu1_tmp2++; + tmp = ((*pu1_tmp1) - (*pu1_tmp2)); + c += (tmp << 2) + (tmp << 1); /* y=6 */ + pu1_tmp1--; + pu1_tmp2++; + tmp = ((*pu1_tmp1) - (*pu1_tmp2)); + c += (tmp << 3) - tmp; /* y=7 */ + pu1_tmp1--; //pu1_tmp2 ++; + /* Modified to get (-1,-1) location as *(pu1_top - 1) instead of (pu1_left - ui4_stride) */ + //c += ((*pu1_tmp1) - (*(pu1_top - 1)))<<3; /* y=8 */ + c += ((*pu1_tmp1) - (*pu1_topleft)) << 3; /* y=8 */ + + c = ((c << 2) + c + 32) >> 6; /*! (5*V + 32)>>32 */ + shift = 3; + } + + /*! Now from the plane parameters a, b, and c, + compute the fitted plane values over the block */ + { + WORD32 tmp1, tmpx, tmpx_init, j, i; + + tmpx_init = -(b << shift); /* -8b */ + tmp = a - (c << shift) + 16; /* a-((4or8)*c)+16 */ + for(i = 0; i < 16; i++) + { + tmp += c; /*increment every time by c to get c*(y-7or3)*/ + tmpx = tmpx_init; /* Init to -8b */ + for(j = 0; j < 16; j++) + { + tmpx += b; /* increment every time by b to get b*(x-7or3) */ + tmp1 = (tmp + tmpx) >> 5; + *pu1_dst++ = CLIP_U8(tmp1); + } + pu1_dst += (dst_strd - 16); + } + } +} diff --git a/dependencies/ih264d/common/ih264_macros.h b/dependencies/ih264d/common/ih264_macros.h new file mode 100644 index 00000000..969012fb --- /dev/null +++ b/dependencies/ih264d/common/ih264_macros.h @@ -0,0 +1,110 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ + +/********************************************************************************* +* @file +* ih264_macros.h +* +* @brief +* Macro definitions used in the codec +* +* @author +* Ittiam +* +* @remarks +* None +* +******************************************************************************* +*/ +#ifndef _IH264_MACROS_H_ +#define _IH264_MACROS_H_ + +/*****************************************************************************/ +/* Function Macros */ +/*****************************************************************************/ +#define RETURN_IF(cond, retval) if(cond) {return (retval);} +#define UNUSED(x) ((void)(x)) + +#define ALIGN128(x) ((((x) + 127) >> 7) << 7) +#define ALIGN64(x) ((((x) + 63) >> 6) << 6) +#define ALIGN32(x) ((((x) + 31) >> 5) << 5) +#define ALIGN16(x) ((((x) + 15) >> 4) << 4) +#define ALIGN8(x) ((((x) + 7) >> 3) << 3) +#define ALIGN4(x) ((((x) + 3) >> 2) << 2) +#define ALIGN2(x) ((((x) + 1) >> 1) << 1) + +/** +****************************************************************************** + * @brief Min, Max +****************************************************************************** + */ +#define MAX(a,b) ((a > b)?(a):(b)) +#define MIN(a,b) ((a < b)?(a):(b)) +#define MIN3(a,b,c) ((a) < (b)) ? (((a) < (c)) ? (a) : (c)) : (((b) < (c)) ? (b) : (c)) +#define MAX3(a,b,c) ((a) > (b)) ? (((a) > (c)) ? (a) : (c)) : (((b) > (c)) ? (b) : (c)) +/** +****************************************************************************** + * @brief Div, Mod +****************************************************************************** + */ +#define MOD(x,y) ((x)%(y)) +#define DIV(x,y) ((x)/(y)) + +/** +****************************************************************************** + * @brief Clip +****************************************************************************** + */ +#define CLIP3(miny, maxy, y) (((y) < (miny))?(miny):(((y) > (maxy))?(maxy):(y))) + +/** +****************************************************************************** + * @brief True, False +****************************************************************************** + */ +#define BOOLEAN(x) (!!(x)) + +/** +****************************************************************************** + * @brief Frequently used multiplications x2. x3, and x4 +****************************************************************************** + */ +#define X2(a) ((a) << 1) +#define X3(a) (((a) << 1) + (a)) +#define X4(a) ((a) << 2) + +/** +****************************************************************************** + * @brief Misc +****************************************************************************** + */ +#define ABS(x) ((x) < 0 ? (-(x)) : (x)) +#define SIGNXY(x,y) (((y) < 0) ? (-1 * (x)) : (x)) + +#define SIGN(x) (((x) >= 0) ? (((x) > 0) ? 1 : 0) : -1) + +#define RESET_BIT(x, pos) (x) = (x) & ~(1 << pos); +#define SET_BIT(x, pos) (x) = (x) | (1 << pos); +#define GET_BIT(x, pos) ((x) >> (pos)) & 0x1 + +#define INSERT_BIT(x, pos, bit) { RESET_BIT(x, pos); (x) = (x) | (bit << pos); } +#endif /*_IH264_MACROS_H_*/ + + diff --git a/dependencies/ih264d/common/ih264_mem_fns.c b/dependencies/ih264d/common/ih264_mem_fns.c new file mode 100644 index 00000000..1c1f3285 --- /dev/null +++ b/dependencies/ih264d/common/ih264_mem_fns.c @@ -0,0 +1,176 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** + ******************************************************************************* + * @file + * ih264_mem_fns.c + * + * @brief + * Functions used for memory operations + * + * @author + * Ittiam + * + * @par List of Functions: + * ih264_memcpy() + * ih264_memcpy_mul_8() + * ih264_memset() + * ih264_memset_mul_8() + * ih264_memset_16bit() + * ih264_memset_16bit_mul_8() + * + * @remarks + * None + * + ****************************************************************************** + */ + +/*****************************************************************************/ +/* File Includes */ +/*****************************************************************************/ +/* System include files */ +#include <stdio.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> + + +/* User include files */ +#include "ih264_typedefs.h" +#include "ih264_mem_fns.h" + +/** + ******************************************************************************* + * + * @brief + * memcpy of a 8,16 or 32 bytes + * + * @par Description: + * Does memcpy of 8bit data from source to destination for 8,16 or 32 number of bytes + * + * @param[in] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[in] num_bytes + * number of bytes to copy + * @returns + * + * @remarks + * None + * + ******************************************************************************* + */ + +void ih264_memcpy(UWORD8 *pu1_dst, UWORD8 *pu1_src, UWORD32 num_bytes) +{ + memcpy(pu1_dst, pu1_src, num_bytes); +} + + +void ih264_memcpy_mul_8(UWORD8 *pu1_dst, UWORD8 *pu1_src, UWORD32 num_bytes) +{ + memcpy(pu1_dst, pu1_src, num_bytes); +} + +/** + ******************************************************************************* + * + * @brief + * memset of a 8,16 or 32 bytes + * + * @par Description: + * Does memset of 8bit data for 8,16 or 32 number of bytes + * + * @param[in] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] value + * UWORD8 value used for memset + * + * @param[in] num_bytes + * number of bytes to set + * @returns + * + * @remarks + * None + * + ******************************************************************************* + */ + +void ih264_memset(UWORD8 *pu1_dst, UWORD8 value, UWORD32 num_bytes) +{ + memset(pu1_dst, value, num_bytes); +} + + +void ih264_memset_mul_8(UWORD8 *pu1_dst, UWORD8 value, UWORD32 num_bytes) +{ + memset(pu1_dst, value, num_bytes); +} + +/** + ******************************************************************************* + * + * @brief + * memset of 16bit data of a 8,16 or 32 bytes + * + * @par Description: + * Does memset of 16bit data for 8,16 or 32 number of bytes + * + * @param[in] pu2_dst + * UWORD8 pointer to the destination + * + * @param[in] value + * UWORD16 value used for memset + * + * @param[in] num_words + * number of words to set + * @returns + * + * @remarks + * None + * + ******************************************************************************* + */ + +void ih264_memset_16bit(UWORD16 *pu2_dst, UWORD16 value, UWORD32 num_words) +{ + UWORD32 i; + for(i = 0; i < num_words; i++) + { + *pu2_dst++ = value; + } +} + +void ih264_memset_16bit_mul_8(UWORD16 *pu2_dst, + UWORD16 value, + UWORD32 num_words) +{ + UWORD32 i; + for(i = 0; i < num_words; i++) + { + *pu2_dst++ = value; + } +} + diff --git a/dependencies/ih264d/common/ih264_mem_fns.h b/dependencies/ih264d/common/ih264_mem_fns.h new file mode 100644 index 00000000..e0167f40 --- /dev/null +++ b/dependencies/ih264d/common/ih264_mem_fns.h @@ -0,0 +1,126 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** +******************************************************************************* +* @file +* ih264_mem_fns.h +* +* @brief +* Function declarations used for memory functions +* +* @author +* Ittiam +* +* @remarks +* None +* +******************************************************************************* +*/ +#ifndef _IH264_MEM_FNS_H_ +#define _IH264_MEM_FNS_H_ + +typedef void ih264_memcpy_ft(UWORD8 *pu1_dst, UWORD8 *pu1_src, UWORD32 num_bytes); + +typedef void ih264_memcpy_mul_8_ft(UWORD8 *pu1_dst, UWORD8 *pu1_src, UWORD32 num_bytes); +/** + ******************************************************************************* + * + * @brief + * memset of a 8,16 or 32 bytes + * + * @par Description: + * Does memset of 8bit data for 8,16 or 32 number of bytes + * + * @param[in] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] value + * UWORD8 value used for memset + * + * @param[in] num_bytes + * number of bytes to set + * @returns + * + * @remarks + * None + * + ******************************************************************************* + */ +typedef void ih264_memset_ft(UWORD8 *pu1_dst, UWORD8 value, UWORD32 num_bytes); + +typedef void ih264_memset_mul_8_ft(UWORD8 *pu1_dst, UWORD8 value, UWORD32 num_bytes); + +/** + ******************************************************************************* + * + * @brief + * memset of 16bit data of a 8,16 or 32 bytes + * + * @par Description: + * Does memset of 16bit data for 8,16 or 32 number of bytes + * + * @param[in] pu2_dst + * UWORD8 pointer to the destination + * + * @param[in] value + * UWORD16 value used for memset + * + * @param[in] num_words + * number of words to set + * @returns + * + * @remarks + * None + * + ******************************************************************************* + */ +typedef void ih264_memset_16bit_ft(UWORD16 *pu2_dst, UWORD16 value, UWORD32 num_words); + +typedef void ih264_memset_16bit_mul_8_ft(UWORD16 *pu2_dst, UWORD16 value, UWORD32 num_words); + +/* C function declarations */ +ih264_memcpy_ft ih264_memcpy; +ih264_memcpy_mul_8_ft ih264_memcpy_mul_8; +ih264_memset_ft ih264_memset; +ih264_memset_mul_8_ft ih264_memset_mul_8; +ih264_memset_16bit_ft ih264_memset_16bit; +ih264_memset_16bit_mul_8_ft ih264_memset_16bit_mul_8; + +/* A9 Q function declarations */ +ih264_memcpy_ft ih264_memcpy_a9q; +ih264_memcpy_mul_8_ft ih264_memcpy_mul_8_a9q; +ih264_memset_ft ih264_memset_a9q; +ih264_memset_mul_8_ft ih264_memset_mul_8_a9q; +ih264_memset_16bit_ft ih264_memset_16bit_a9q; +ih264_memset_16bit_mul_8_ft ih264_memset_16bit_mul_8_a9q; + +/* AV8 function declarations */ +ih264_memcpy_ft ih264_memcpy_av8; +ih264_memcpy_mul_8_ft ih264_memcpy_mul_8_av8; +ih264_memset_ft ih264_memset_av8; +ih264_memset_mul_8_ft ih264_memset_mul_8_av8; +ih264_memset_16bit_ft ih264_memset_16bit_av8; +ih264_memset_16bit_mul_8_ft ih264_memset_16bit_mul_8_av8; + + +ih264_memcpy_mul_8_ft ih264_memcpy_mul_8_ssse3; +ih264_memset_mul_8_ft ih264_memset_mul_8_ssse3; +ih264_memset_16bit_mul_8_ft ih264_memset_16bit_mul_8_ssse3; +#endif //_MEM_FNS_H_ diff --git a/dependencies/ih264d/common/ih264_padding.c b/dependencies/ih264d/common/ih264_padding.c new file mode 100644 index 00000000..8e8f3e25 --- /dev/null +++ b/dependencies/ih264d/common/ih264_padding.c @@ -0,0 +1,331 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ + +/** +******************************************************************************* +* @file +* ih264_padding.c +* +* @brief +* Contains function definitions for Padding +* +* @author +* Ittiam +* +* @par List of Functions: +* - ih264_pad_top() +* - ih264_pad_bottom() +* - ih264_pad_left_luma() +* - ih264_pad_left_chroma() +* - ih264_pad_right_luma() +* - ih264_pad_right_chroma() +* +* @remarks +* None +* +******************************************************************************* +*/ + +/*****************************************************************************/ +/* File Includes */ +/*****************************************************************************/ + +/* System include files */ +#include <stddef.h> +#include <string.h> + +/* User include files */ +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_padding.h" + + +/*****************************************************************************/ +/* Function Definitions */ +/*****************************************************************************/ + +/** +******************************************************************************* +* +* @brief pad at the top of a 2d array +* +* @par Description: +* The top row of a 2d array is replicated for pad_size times at the top +* +* @param[in] pu1_src +* UWORD8 pointer to the source +* +* @param[in] src_strd +* integer source stride +* +* @param[in] wd +* integer width of the array +* +* @param[in] pad_size +* integer -padding size of the array +* +* @returns none +* +* @remarks none +* +******************************************************************************* +*/ +void ih264_pad_top(UWORD8 *pu1_src, + WORD32 src_strd, + WORD32 wd, + WORD32 pad_size) +{ + WORD32 row; + + for(row = 1; row <= pad_size; row++) + { + memcpy(pu1_src - row * src_strd, pu1_src, wd); + } +} + + + +/** +******************************************************************************* +* +* @brief pad at the bottom of a 2d array +* +* @par Description: +* The bottom row of a 2d array is replicated for pad_size times at the bottom +* +* @param[in] pu1_src +* UWORD8 pointer to the source +* +* @param[in] src_strd +* integer source stride +* +* @param[in] wd +* integer width of the array +* +* @param[in] pad_size +* integer -padding size of the array +* +* @returns none +* +* @remarks none +* +******************************************************************************* +*/ +void ih264_pad_bottom(UWORD8 *pu1_src, + WORD32 src_strd, + WORD32 wd, + WORD32 pad_size) +{ + WORD32 row; + + for(row = 1; row <= pad_size; row++) + { + memcpy(pu1_src + (row - 1) * src_strd, pu1_src - 1 * src_strd, wd); + } +} + +/** +******************************************************************************* +* +* @brief pad (luma block) at the left of a 2d array +* +* @par Description: +* The left column of a 2d array is replicated for pad_size times to the left +* +* @param[in] pu1_src +* UWORD8 pointer to the source +* +* @param[in] src_strd +* integer source stride +* +* @param[in] ht +* integer height of the array +* +* @param[in] pad_size +* integer -padding size of the array +* +* @returns none +* +* @remarks none +* +******************************************************************************* + */ +void ih264_pad_left_luma(UWORD8 *pu1_src, + WORD32 src_strd, + WORD32 ht, + WORD32 pad_size) +{ + WORD32 row; + + for(row = 0; row < ht; row++) + { + + memset(pu1_src - pad_size, *pu1_src, pad_size); + + pu1_src += src_strd; + } +} + +/** +******************************************************************************* +* +* @brief pad (chroma block) at the left of a 2d array +* +* @par Description: +* The left column of a 2d array is replicated for pad_size times to the left +* +* @param[in] pu1_src +* UWORD8 pointer to the source +* +* @param[in] src_strd +* integer source stride +* +* @param[in] ht +* integer height of the array +* +* @param[in] pad_size +* integer -padding size of the array +* +* @returns none +* +* @remarks none +* +******************************************************************************* +*/ +void ih264_pad_left_chroma(UWORD8 *pu1_src, + WORD32 src_strd, + WORD32 ht, + WORD32 pad_size) +{ + /* temp var */ + WORD32 row, col; + UWORD16 u2_uv_val; + + /* pointer to src */ + UWORD16 *pu2_src = (UWORD16 *)pu1_src; + + src_strd >>= 1; + pad_size >>= 1; + + for(row = 0; row < ht; row++) + { + u2_uv_val = pu2_src[0]; + + for (col = -pad_size; col < 0; col++) + { + pu2_src[col] = u2_uv_val; + } + + pu2_src += src_strd; + } +} + +/** +******************************************************************************* +* +* @brief pad (luma block) at the right of a 2d array +* +* @par Description: +* The right column of a 2d array is replicated for pad_size times at the right +* +* @param[in] pu1_src +* UWORD8 pointer to the source +* +* @param[in] src_strd +* integer source stride +* +* @param[in] ht +* integer height of the array +* +* @param[in] pad_size +* integer -padding size of the array +* +* @returns none +* +* @remarks none +* +******************************************************************************* +*/ +void ih264_pad_right_luma(UWORD8 *pu1_src, + WORD32 src_strd, + WORD32 ht, + WORD32 pad_size) +{ + WORD32 row; + + for(row = 0; row < ht; row++) + { + memset(pu1_src, *(pu1_src -1), pad_size); + + pu1_src += src_strd; + } +} + +/** +******************************************************************************* +* +* @brief pad (chroma block) at the right of a 2d array +* +* @par Description: +* The right column of a 2d array is replicated for pad_size times at the right +* +* @param[in] pu1_src +* UWORD8 pointer to the source +* +* @param[in] src_strd +* integer source stride +* +* @param[in] ht +* integer height of the array +* +* @param[in] pad_size +* integer -padding size of the array +* +* @returns none +* +* @remarks none +* +******************************************************************************* +*/ +void ih264_pad_right_chroma(UWORD8 *pu1_src, + WORD32 src_strd, + WORD32 ht, + WORD32 pad_size) +{ + WORD32 row, col; + UWORD16 u2_uv_val; + UWORD16 *pu2_src = (UWORD16 *)pu1_src; + + src_strd >>= 1; + pad_size >>= 1; + + for(row = 0; row < ht; row++) + { + u2_uv_val = pu2_src[-1]; + + for (col = 0; col < pad_size; col++) + { + pu2_src[col] = u2_uv_val; + } + + pu2_src += src_strd; + } +} + diff --git a/dependencies/ih264d/common/ih264_padding.h b/dependencies/ih264d/common/ih264_padding.h new file mode 100644 index 00000000..e4e18fbe --- /dev/null +++ b/dependencies/ih264d/common/ih264_padding.h @@ -0,0 +1,74 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ + +/** +******************************************************************************* +* @file +* ih264_padding.h +* +* @brief +* Declarations for padding functions +* +* @author +* Ittiam +* +* @remarks +* None +* +******************************************************************************* +*/ +#ifndef _IH264_PADDING_H_ +#define _IH264_PADDING_H_ + +/*****************************************************************************/ +/* Function Declarations */ +/*****************************************************************************/ + +typedef void ih264_pad(UWORD8 *, WORD32, WORD32, WORD32); + +/* C function declarations */ +ih264_pad ih264_pad_top; +ih264_pad ih264_pad_bottom; +ih264_pad ih264_pad_left_luma; +ih264_pad ih264_pad_left_chroma; +ih264_pad ih264_pad_right_luma; +ih264_pad ih264_pad_right_chroma; + +/* A9 Q function declarations */ +ih264_pad ih264_pad_top_a9q; +ih264_pad ih264_pad_left_luma_a9q; +ih264_pad ih264_pad_left_chroma_a9q; +ih264_pad ih264_pad_right_luma_a9q; +ih264_pad ih264_pad_right_chroma_a9q; + +/* AV8 function declarations */ +ih264_pad ih264_pad_top_av8; +ih264_pad ih264_pad_left_luma_av8; +ih264_pad ih264_pad_left_chroma_av8; +ih264_pad ih264_pad_right_luma_av8; +ih264_pad ih264_pad_right_chroma_av8; + + +ih264_pad ih264_pad_left_luma_ssse3; +ih264_pad ih264_pad_left_chroma_ssse3; +ih264_pad ih264_pad_right_luma_ssse3; +ih264_pad ih264_pad_right_chroma_ssse3; + +#endif /*_IH264_PADDING_H_*/ diff --git a/dependencies/ih264d/common/ih264_resi_trans.h b/dependencies/ih264d/common/ih264_resi_trans.h new file mode 100644 index 00000000..ee0add39 --- /dev/null +++ b/dependencies/ih264d/common/ih264_resi_trans.h @@ -0,0 +1,70 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** +******************************************************************************* +* @file +* ih264_resi_trans.h +* +* @brief +* Functions declarations for residue and forward transform +* +* @par List of Functions: +* - ih264_resi_trans_ft +* - ih264_resi_trans_4x4 +* - ih264_resi_trans_4x4 +* - ih264_resi_trans_4x4_a9 +* - ih264_resi_trans_4x4_a9 +* +* @author +* Ittiam +* +* @remarks +* None +* +******************************************************************************* +*/ + +#ifndef IH264_RESI_TRANS_H_ +#define IH264_RESI_TRANS_H_ + +/*****************************************************************************/ +/* Extern Function Declarations */ +/*****************************************************************************/ + +typedef void ih264_resi_trans_ft(UWORD8 *pu1_src, + UWORD8 *pu1_pred, + WORD32 *pi4_out, + WORD32 src_strd, + WORD32 pred_strd, + WORD32 out_strd); + +/*C functions*/ + +ih264_resi_trans_ft ih264_resi_trans_4x4; + +ih264_resi_trans_ft ih264_resi_trans_8x8; + +/*A9 functions*/ + +ih264_resi_trans_ft ih264_resi_trans_4x4_a9; + +ih264_resi_trans_ft ih264_resi_trans_8x8_a9; + +#endif /* IH264_RESI_TRANS_H_ */ diff --git a/dependencies/ih264d/common/ih264_resi_trans_quant.c b/dependencies/ih264d/common/ih264_resi_trans_quant.c new file mode 100644 index 00000000..cf1d43c9 --- /dev/null +++ b/dependencies/ih264d/common/ih264_resi_trans_quant.c @@ -0,0 +1,814 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** + ******************************************************************************* + * @file + * ih264_resi_trans_quant.c + * + * @brief + * Contains function definitions single stage forward transform for H.264 + * It will calculate the residue, do the cf and then do quantization + * + * @author + * Ittiam + * + * @par List of Functions: + * - ih264_resi_trans_quant_4x4() + * - ih264_resi_trans_quant_chroma_4x4 + * - ih264_hadamard_quant_4x4 + * - ih264_hadamard_quant_2x2_uv + * - ih264_resi_trans_quant_8x8 + * + * @remarks + ******************************************************************************* + */ + +/*****************************************************************************/ +/* File Includes */ +/*****************************************************************************/ + +/* System include files */ +#include <stddef.h> + +/* User include files */ +#include "ih264_typedefs.h" +#include "ih264_defs.h" +#include "ih264_size_defs.h" +#include "ih264_macros.h" +#include "ih264_trans_macros.h" +#include "ih264_trans_data.h" +#include "ih264_structs.h" +#include "ih264_trans_quant_itrans_iquant.h" + +/** + ******************************************************************************* + * + * @brief + * This function performs forward transform and quantization on a 4*4 block + * + * @par Description: + * The function accepts source buffer and estimation buffer. From these, it + * computes the residue. This is residue is then transformed and quantized. + * The transform and quantization are in placed computed. They use the residue + * buffer for this. + * + * @param[in] pu1_src + * Pointer to source sub-block + * + * @param[in] pu1_pred + * Pointer to prediction sub-block + * + * @param[in] pi2_out + * Pointer to residual sub-block + * + * @param[in] src_strd + * Source stride + * + * @param[in] pred_strd + * Prediction stride + * + * @param[in] dst_strd + * Destination stride + * + * @param[in] u4_qbits + * QP_BITS_h264_4x4 + floor(QP/6) + * + * @param[in] pu2_threshold_matrix + * Pointer to Forward Quant Threshold Matrix + * + * @param[in] pu2_scale_matrix + * Pointer to Forward Quant Scale Matrix + * + * @param[in] u4_round_factor + * Quantization Round factor + * + * @param[out] pu1_nnz + * Total non-zero coefficients in the current sub-block + * + * @returns + * + * @remarks + * None + * + ******************************************************************************* + */ +void ih264_resi_trans_quant_4x4(UWORD8 *pu1_src, + UWORD8 *pu1_pred, + WORD16 *pi2_out, + WORD32 src_strd, + WORD32 pred_strd, + const UWORD16 *pu2_scale_matrix, + const UWORD16 *pu2_threshold_matrix, + UWORD32 u4_qbits, + UWORD32 u4_round_factor, + UWORD8 *pu1_nnz, + WORD16 *pi2_alt_dc_addr) +{ + UWORD32 i; + WORD32 x0, x1, x2, x3, x4, x5, x6, x7; + WORD32 i4_value, i4_sign; + UWORD32 u4_abs_value; + WORD16 *pi2_out_tmp = pi2_out; + UWORD32 u4_nonzero_coeff = 0; + + for (i = 0; i < SUB_BLK_WIDTH_4x4; i++) + { + /* computing prediction error (residue) */ + x4 = pu1_src[0] - pu1_pred[0]; + x5 = pu1_src[1] - pu1_pred[1]; + x6 = pu1_src[2] - pu1_pred[2]; + x7 = pu1_src[3] - pu1_pred[3]; + + /* Horizontal transform */ + x0 = x4 + x7; + x1 = x5 + x6; + x2 = x5 - x6; + x3 = x4 - x7; + + pi2_out_tmp[0] = x0 + x1; + pi2_out_tmp[1] = (x3 <<1) + x2; + pi2_out_tmp[2] = x0 - x1; + pi2_out_tmp[3] = x3 - (x2<<1); + + /* pointing to next row; */ + pu1_src += src_strd; + pu1_pred += pred_strd; + pi2_out_tmp += 4; + + } + pi2_out_tmp = pi2_out; + for (i = 0; i < SUB_BLK_WIDTH_4x4; i++) + { + + /* Vertical transform and quantization */ + x4 = pi2_out_tmp[0]; + x5 = pi2_out_tmp[4]; + x6 = pi2_out_tmp[8]; + x7 = pi2_out_tmp[12]; + + + x0 = x4 + x7; + x1 = x5 + x6; + x2 = x5 - x6; + x3 = x4 - x7; + + /* quantization is done in place */ + + i4_value = x0 + x1; + + if(i==0) + { + (*pi2_alt_dc_addr) = i4_value; + } + + FWD_QUANT(i4_value, u4_abs_value, i4_sign, pu2_threshold_matrix[0], pu2_scale_matrix[0], u4_round_factor, u4_qbits, u4_nonzero_coeff); + pi2_out_tmp[0] = i4_value; + + + i4_value = (x3 << 1) + x2; + FWD_QUANT(i4_value, u4_abs_value, i4_sign, pu2_threshold_matrix[4], pu2_scale_matrix[4], u4_round_factor, u4_qbits, u4_nonzero_coeff); + pi2_out_tmp[4] = i4_value; + + + i4_value = x0 - x1; + FWD_QUANT(i4_value, u4_abs_value, i4_sign, pu2_threshold_matrix[8], pu2_scale_matrix[8], u4_round_factor, u4_qbits, u4_nonzero_coeff); + pi2_out_tmp[8] = i4_value; + + + i4_value = x3 - (x2 << 1); + FWD_QUANT(i4_value, u4_abs_value, i4_sign, pu2_threshold_matrix[12], pu2_scale_matrix[12], u4_round_factor, u4_qbits, u4_nonzero_coeff); + pi2_out_tmp[12] = i4_value; + + pi2_out_tmp ++; + pu2_scale_matrix++; + pu2_threshold_matrix++; + } + + /* Return total nonzero coefficients in the current sub block */ + *pu1_nnz = u4_nonzero_coeff; +} +/** + ******************************************************************************* + * + * @brief + * This function performs forward transform and quantization on a 4*4 chroma block + * with interleaved values + * + * @par Description: + * The function accepts source buffer and estimation buffer. From these, it + * computes the residue. This is residue is then transformed and quantized. + * The transform and quantization are in placed computed. They use the residue + * buffer for this. + * + * @param[in] pu1_src + * Pointer to source sub-block + * + * @param[in] pu1_pred + * Pointer to prediction sub-block + * + * @param[in] pi2_out + * Pointer to residual sub-block + * + * @param[in] src_strd + * Source stride + * + * @param[in] pred_strd + * Prediction stride + * + * @param[in] dst_strd + * Destination stride + * + * @param[in] u4_qbits + * QP_BITS_h264_4x4 + floor(QP/6) + * + * @param[in] pu2_threshold_matrix + * Pointer to Forward Quant Threshold Matrix + * + * @param[in] pu2_scale_matrix + * Pointer to Forward Quant Scale Matrix + * + * @param[in] u4_round_factor + * Quantization Round factor + * + * @param[out] pu1_nnz + * Total non-zero coefficients in the current sub-block + * + * @returns + * + * @remarks + * None + * + ******************************************************************************* + */ +void ih264_resi_trans_quant_chroma_4x4(UWORD8 *pu1_src, + UWORD8 *pu1_pred, + WORD16 *pi2_out, + WORD32 src_strd, + WORD32 pred_strd, + const UWORD16 *pu2_scale_matrix, + const UWORD16 *pu2_threshold_matrix, + UWORD32 u4_qbits, + UWORD32 u4_round_factor, + UWORD8 *pu1_nnz, + WORD16 *pu1_dc_alt_addr) +{ + UWORD32 i; + WORD32 x0, x1, x2, x3, x4, x5, x6, x7; + WORD32 i4_value, i4_sign; + UWORD32 u4_abs_value; + WORD16 *pi2_out_tmp = pi2_out; + UWORD32 u4_nonzero_coeff = 0; + + for (i = 0; i < SUB_BLK_WIDTH_4x4; i++) + { + /* computing prediction error (residue) */ + x4 = pu1_src[0] - pu1_pred[0]; + x5 = pu1_src[2] - pu1_pred[2]; + x6 = pu1_src[4] - pu1_pred[4]; + x7 = pu1_src[6] - pu1_pred[6]; + + /* Horizontal transform */ + x0 = x4 + x7; + x1 = x5 + x6; + x2 = x5 - x6; + x3 = x4 - x7; + + pi2_out_tmp[0] = x0 + x1; + pi2_out_tmp[1] = (x3 <<1) + x2; + pi2_out_tmp[2] = x0 - x1; + pi2_out_tmp[3] = x3 - (x2<<1); + + /* pointing to next row; */ + pu1_src += src_strd; + pu1_pred += pred_strd; + pi2_out_tmp += 4; + + } + pi2_out_tmp = pi2_out; + for (i = 0; i < SUB_BLK_WIDTH_4x4; i++) + { + + /* Vertical transform and quantization */ + x4 = pi2_out_tmp[0]; + x5 = pi2_out_tmp[4]; + x6 = pi2_out_tmp[8]; + x7 = pi2_out_tmp[12]; + + + x0 = x4 + x7; + x1 = x5 + x6; + x2 = x5 - x6; + x3 = x4 - x7; + + /* quantization is done in place */ + + i4_value = x0 + x1; + + if(i==0) + { + *pu1_dc_alt_addr = i4_value; + } + + FWD_QUANT(i4_value, u4_abs_value, i4_sign, pu2_threshold_matrix[0], + pu2_scale_matrix[0], u4_round_factor, u4_qbits, + u4_nonzero_coeff); + pi2_out_tmp[0] = i4_value; + + i4_value = (x3 << 1) + x2; + FWD_QUANT(i4_value, u4_abs_value, i4_sign, pu2_threshold_matrix[4], + pu2_scale_matrix[4], u4_round_factor, u4_qbits, + u4_nonzero_coeff); + pi2_out_tmp[4] = i4_value; + + i4_value = x0 - x1; + FWD_QUANT(i4_value, u4_abs_value, i4_sign, pu2_threshold_matrix[8], + pu2_scale_matrix[8], u4_round_factor, u4_qbits, + u4_nonzero_coeff); + pi2_out_tmp[8] = i4_value; + + i4_value = x3 - (x2 << 1); + FWD_QUANT(i4_value, u4_abs_value, i4_sign, pu2_threshold_matrix[12], + pu2_scale_matrix[12], u4_round_factor, u4_qbits, + u4_nonzero_coeff); + pi2_out_tmp[12] = i4_value; + + pi2_out_tmp ++; + pu2_scale_matrix++; + pu2_threshold_matrix++; + } + + /* Return total nonzero coefficients in the current sub block */ + *pu1_nnz = u4_nonzero_coeff; +} + +/** + ******************************************************************************* + * + * @brief + * This function performs forward hadamard transform and quantization on a 4*4 block + * + * @par Description: + * The function accepts source buffer and estimation buffer. From these, it + * computes the residue. This is residue is then transformed and quantized. + * The transform and quantization are in placed computed. They use the residue + * buffer for this. + * + * @param[in] pu1_src + * Pointer to source sub-block + * + * @param[in] pu1_pred + * Pointer to prediction sub-block + * + * @param[in] pi2_out + * Pointer to residual sub-block + * + * @param[in] src_strd + * Source stride + * + * @param[in] pred_strd + * Prediction stride + * + * @param[in] dst_strd + * Destination stride + * + * @param[in] u4_qbits + * QP_BITS_h264_4x4 + floor(QP/6) + * + * @param[in] pu2_threshold_matrix + * Pointer to Forward Quant Threshold Matrix + * + * @param[in] pu2_scale_matrix + * Pointer to Forward Quant Scale Matrix + * + * @param[in] u4_round_factor + * Quantization Round factor + * + * @param[out] pu1_nnz + * Total non-zero coefficients in the current sub-block + * + * @returns + * + * @remarks + * None + * + */ + +void ih264_hadamard_quant_4x4(WORD16 *pi2_src, + WORD16 *pi2_dst, + const UWORD16 *pu2_scale_matrix, + const UWORD16 *pu2_threshold_matrix, + UWORD32 u4_qbits, + UWORD32 u4_round_factor, + UWORD8 *pu1_nnz) +{ + WORD32 i; + WORD32 x0,x1,x2,x3,x4,x5,x6,x7,i4_value; + UWORD32 u4_abs_value; + WORD32 i4_sign; + + *pu1_nnz = 0; + + for (i = 0; i < SUB_BLK_WIDTH_4x4; i++) + { + x4 = pi2_src[0]; + x5 = pi2_src[1]; + x6 = pi2_src[2]; + x7 = pi2_src[3]; + + x0 = x4 + x7; + x1 = x5 + x6; + x2 = x5 - x6; + x3 = x4 - x7; + + pi2_dst[0] = x0 + x1; + pi2_dst[1] = x3 + x2; + pi2_dst[2] = x0 - x1; + pi2_dst[3] = x3 - x2; + + pi2_src += 4; + pi2_dst += 4; + } + + /* Vertical transform and quantization */ + pi2_dst -= SUB_BLK_WIDTH_4x4<<2; + + for (i = 0; i < SUB_BLK_WIDTH_4x4; i++) + { + x4 = pi2_dst[0]; + x5 = pi2_dst[4]; + x6 = pi2_dst[8]; + x7 = pi2_dst[12] ; + + x0 = x4 + x7; + x1 = x5 + x6; + x2 = x5 - x6; + x3 = x4 - x7; + + + i4_value = (x0 + x1) >> 1; + FWD_QUANT(i4_value, u4_abs_value, i4_sign, pu2_threshold_matrix[0], + pu2_scale_matrix[0], u4_round_factor, u4_qbits, pu1_nnz[0]); + pi2_dst[0] = i4_value; + + i4_value = (x3 + x2) >> 1; + FWD_QUANT(i4_value, u4_abs_value, i4_sign, pu2_threshold_matrix[0], + pu2_scale_matrix[0], u4_round_factor, u4_qbits, pu1_nnz[0]); + pi2_dst[4] = i4_value; + + i4_value = (x0 - x1) >> 1; + FWD_QUANT(i4_value, u4_abs_value, i4_sign, pu2_threshold_matrix[0], + pu2_scale_matrix[0], u4_round_factor, u4_qbits, pu1_nnz[0]); + pi2_dst[8] = i4_value; + + i4_value = (x3 - x2) >> 1; + FWD_QUANT(i4_value, u4_abs_value, i4_sign, pu2_threshold_matrix[0], + pu2_scale_matrix[0], u4_round_factor, u4_qbits, pu1_nnz[0]); + pi2_dst[12] = i4_value; + + pi2_dst ++; + } +} + +/** + ******************************************************************************* + * + * @brief + * This function performs forward hadamard transform and quantization on a 2*2 block + * for both U and V planes + * + * @par Description: + * The function accepts source buffer and estimation buffer. From these, it + * computes the residue. This is residue is then transformed and quantized. + * The transform and quantization are in placed computed. They use the residue + * buffer for this. + * + * @param[in] pu1_src + * Pointer to source sub-block + * + * @param[in] pu1_pred + * Pointer to prediction sub-block + * + * @param[in] pi2_out + * Pointer to residual sub-block + * + * @param[in] src_strd + * Source stride + * + * @param[in] pred_strd + * Prediction stride + * + * @param[in] dst_strd + * Destination stride + * + * @param[in] u4_qbits + * QP_BITS_h264_4x4 + floor(QP/6) + * + * @param[in] pu2_threshold_matrix + * Pointer to Forward Quant Threshold Matrix + * + * @param[in] pu2_scale_matrix + * Pointer to Forward Quant Scale Matrix + * + * @param[in] u4_round_factor + * Quantization Round factor + * + * @param[out] pu1_nnz + * Total non-zero coefficients in the current sub-block + * + * @returns + * + * @remarks + * NNZ for dc is populated at 0 and 5th position of pu1_nnz + * + */ + +void ih264_hadamard_quant_2x2_uv(WORD16 *pi2_src, + WORD16 *pi2_dst, + const UWORD16 *pu2_scale_matrix, + const UWORD16 *pu2_threshold_matrix, + UWORD32 u4_qbits, + UWORD32 u4_round_factor, + UWORD8 *pu1_nnz) +{ + WORD32 x0, x1, x2, x3, x4, x5, x6, x7; + WORD32 i4_value, i4_sign, plane; + UWORD32 u4_abs_value; + + for(plane = 0; plane < 2; plane++) + { + pu1_nnz[plane] = 0; + + /* Horizontal transform */ + x4 = pi2_src[0]; + x5 = pi2_src[1]; + x6 = pi2_src[2]; + x7 = pi2_src[3]; + + x0 = x4 + x5; + x1 = x4 - x5; + x2 = x6 + x7; + x3 = x6 - x7; + + /* Vertical transform and quantization */ + i4_value = (x0 + x2); + FWD_QUANT(i4_value, u4_abs_value, i4_sign, pu2_threshold_matrix[0], + pu2_scale_matrix[0], u4_round_factor, u4_qbits, + pu1_nnz[plane]); + pi2_dst[0] = i4_value; + + i4_value = (x0 - x2); + FWD_QUANT(i4_value, u4_abs_value, i4_sign, pu2_threshold_matrix[0], + pu2_scale_matrix[0], u4_round_factor, u4_qbits, + pu1_nnz[plane]); + pi2_dst[2] = i4_value; + + i4_value = (x1 - x3); + FWD_QUANT(i4_value, u4_abs_value, i4_sign, pu2_threshold_matrix[0], + pu2_scale_matrix[0], u4_round_factor, u4_qbits, + pu1_nnz[plane]); + pi2_dst[3] = i4_value; + + i4_value = (x1 + x3); + FWD_QUANT(i4_value, u4_abs_value, i4_sign, pu2_threshold_matrix[0], + pu2_scale_matrix[0], u4_round_factor, u4_qbits, + pu1_nnz[plane]); + pi2_dst[1] = i4_value; + + pi2_dst += 4; + pi2_src += 4; + + } +} + +/* + ******************************************************************************* + * + * @brief + * This function performs Single stage forward transform CF8 and quantization on 8*8 blocks + * for h.264 + * + * @par Description: + * Performs single stage 8x8 forward transform CF8 after calculating the residue + * The result is then quantized + * + * @param[in] pu1_src + * Input 8x8 pixels + * + * @param[in] pu1_pred + * Input 8x8 pixels + * + * @param[in] pi1_out + * Output 8x8 pixels + * + * @param[in] u4_thresh + * Threshold under which the coeffs are not quantized + * + * @param[in] u4_qp_div + * QP/6 + * + * @param[in] u4_qp_rem + * QP%6 + * + * @param[in] u2_src_stride + * Source stride + * + * @param[in] pred_strd + * stride for prediciton buffer + * + * @param[in] dst_strd + * stride for destination buffer + * + * @param[in] pu4_quant_mat + * Pointer to the 4x4 quantization matrix + * + * @returns Void + * + * + ******************************************************************************* + */ +void ih264_resi_trans_quant_8x8(UWORD8 *pu1_src, + UWORD8 *pu1_pred, + WORD16 *pi2_out, + WORD32 src_strd, + WORD32 pred_strd, + const UWORD16 *pu2_scale_matrix, + const UWORD16 *pu2_threshold_matrix, + UWORD32 u4_qbits, + UWORD32 u4_round_factor, + UWORD8 *pu1_nnz, + WORD16 *pu1_dc_alt_addr) + +{ + WORD16 *pi2_out_tmp = pi2_out; + UWORD32 i; + WORD32 a0, a1, a2, a3, a4, a5, a6, a7; + WORD32 r0, r1, r2, r3, r4, r5, r6, r7; + WORD32 i4_sign; + UWORD32 u4_abs_value; + UWORD32 u4_nonzero_coeff = 0; + + UNUSED(pu1_dc_alt_addr); + + /*Horizontal transform */ + /* we are going to use the a's and r's in a twisted way since */ + /*i dont want to declare more variables */ + for(i = 0; i < SUB_BLK_WIDTH_8x8; ++i) + { + r0 = pu1_src[0]; + r0 -= pu1_pred[0]; + r1 = pu1_src[1]; + r1 -= pu1_pred[1]; + r2 = pu1_src[2];r2 -= pu1_pred[2]; + r3 = pu1_src[3];r3 -= pu1_pred[3]; + r4 = pu1_src[4];r4 -= pu1_pred[4]; + r5 = pu1_src[5];r5 -= pu1_pred[5]; + r6 = pu1_src[6];r6 -= pu1_pred[6]; + r7 = pu1_src[7];r7 -= pu1_pred[7]; + + + a0 = r0 + r7; + a1 = r1 + r6; + a2 = r2 + r5; + a3 = r3 + r4; + + a4 = a0 + a3; + a5 = a1 + a2; + a6 = a0 - a3; + a7 = a1 - a2; + + pi2_out_tmp[0] = a4 + a5; + + pi2_out_tmp[2] = a6 + (a7>>1); + pi2_out_tmp[4] = a4 - a5; + pi2_out_tmp[6] = (a6>>1) - a7; + + a0 = r0 - r7; + a1 = r1 - r6; + a2 = r2 - r5; + a3 = r3 - r4; + + a4 = a1 + a2 + ((a0>>1) + a0); + a5 = a0 - a3 - ((a2>>1) + a2); + a6 = a0 + a3 - ((a1>>1) + a1); + a7 = a1 - a2 + ((a3>>1) + a3); + + pi2_out_tmp[1] = a4 + (a7>>2); + pi2_out_tmp[3] = a5 + (a6>>2); + pi2_out_tmp[5] = a6 - (a5>>2); + pi2_out_tmp[7] = (a4>>2) - a7; + + pu1_src += src_strd; + pu1_pred += pred_strd; + pi2_out_tmp += 8; + } + + /*vertical transform and quant */ + + pi2_out_tmp = pi2_out; + + for (i = 0; i < SUB_BLK_WIDTH_8x8; ++i) + { + + r0 = pi2_out_tmp[0]; + r1 = pi2_out_tmp[8]; + r2 = pi2_out_tmp[16]; + r3 = pi2_out_tmp[24]; + r4 = pi2_out_tmp[32]; + r5 = pi2_out_tmp[40]; + r6 = pi2_out_tmp[48]; + r7 = pi2_out_tmp[56]; + + a0 = r0 + r7; + a1 = r1 + r6; + a2 = r2 + r5; + a3 = r3 + r4; + + a4 = a0 + a3; + a5 = a1 + a2; + a6 = a0 - a3; + a7 = a1 - a2; + + a0 = r0 - r7; + a1 = r1 - r6; + a2 = r2 - r5; + a3 = r3 - r4; + + r0 = a4 + a5; + r2 = a6 + (a7>>1); + r4 = a4 - a5; + r6 = (a6>>1) - a7; + + a4 = a1 + a2 + ((a0>>1) + a0); + a5 = a0 - a3 - ((a2>>1) + a2); + a6 = a0 + a3 - ((a1>>1) + a1); + a7 = a1 - a2 + ((a3>>1) + a3); + + r1 = a4 + (a7>>2); + r3 = a5 + (a6>>2); + r5 = a6 - (a5>>2); + r7 = (a4>>2) - a7; + + FWD_QUANT(r0, u4_abs_value, i4_sign, pu2_threshold_matrix[0], + pu2_scale_matrix[0], u4_round_factor, u4_qbits, + u4_nonzero_coeff); + pi2_out_tmp[0] = r0; + + FWD_QUANT(r1, u4_abs_value, i4_sign, pu2_threshold_matrix[8], + pu2_scale_matrix[8], u4_round_factor, u4_qbits, + u4_nonzero_coeff); + pi2_out_tmp[8] = r1; + + FWD_QUANT(r2, u4_abs_value, i4_sign, pu2_threshold_matrix[16], + pu2_scale_matrix[16], u4_round_factor, u4_qbits, + u4_nonzero_coeff); + pi2_out_tmp[16] = r2; + + FWD_QUANT(r3, u4_abs_value, i4_sign, pu2_threshold_matrix[24], + pu2_scale_matrix[24], u4_round_factor, u4_qbits, + u4_nonzero_coeff); + pi2_out_tmp[24] = r3; + + FWD_QUANT(r4, u4_abs_value, i4_sign, pu2_threshold_matrix[32], + pu2_scale_matrix[32], u4_round_factor, u4_qbits, + u4_nonzero_coeff); + pi2_out_tmp[32] = r4; + + FWD_QUANT(r5, u4_abs_value, i4_sign, pu2_threshold_matrix[40], + pu2_scale_matrix[40], u4_round_factor, u4_qbits, + u4_nonzero_coeff); + pi2_out_tmp[40] = r5; + + FWD_QUANT(r6, u4_abs_value, i4_sign, pu2_threshold_matrix[48], + pu2_scale_matrix[48], u4_round_factor, u4_qbits, + u4_nonzero_coeff); + pi2_out_tmp[48] = r6; + + FWD_QUANT(r7, u4_abs_value, i4_sign, pu2_threshold_matrix[56], + pu2_scale_matrix[56], u4_round_factor, u4_qbits, + u4_nonzero_coeff); + pi2_out_tmp[56] = r7; + + pi2_out_tmp++; + pu2_scale_matrix++; + pu2_threshold_matrix++; + } + /* Return total nonzero coefficients in the current sub block */ + *pu1_nnz = u4_nonzero_coeff; +} diff --git a/dependencies/ih264d/common/ih264_size_defs.h b/dependencies/ih264d/common/ih264_size_defs.h new file mode 100644 index 00000000..e2a8b76c --- /dev/null +++ b/dependencies/ih264d/common/ih264_size_defs.h @@ -0,0 +1,85 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** + ******************************************************************************* + * @file + * ih264_size_defs.h + * + * @brief + * Contains declaration of global variables for H264 transform , quant and inverse quant + * + * @author + * Ittiam + * + * @remarks + * + ********************************************************************************/ + +#ifndef IH264_SIZE_DEFS_H_ +#define IH264_SIZE_DEFS_H_ + +/*****************************************************************************/ +/* Constant Macros */ +/*****************************************************************************/ + +/*-----------------------Primary defs--------------------------*/ + +/*Width of a 4x4 block*/ +#define SUB_BLK_WIDTH_4x4 4 + +/*Width of an 8x8 block*/ +#define SUB_BLK_WIDTH_8x8 8 + +/*Number of chroma blocks in a row of coffs*/ +#define SUB_BLK_COUNT_CHROMA_4x4_420 2 + +/*Number of luma blocks in a row of coffs*/ +#define SUB_BLK_COUNT_LUMA_4x4 4 + +/*Numbr of chroma planes*/ +#define NUM_CHROMA_PLANES 2 + +/*Constant bit shifts*/ +#define QP_BITS_h264_4x4 15 +#define QP_BITS_h264_8x8 16 + + +/*---------------------------Derived defs------------------------*/ + +/*Number of coefficients ina 4x4 block*/ +#define COFF_CNT_SUB_BLK_4x4 SUB_BLK_WIDTH_4x4*SUB_BLK_WIDTH_4x4; + +/*Number of luma blocks in a row of coffs*/ +#define SUB_BLK_LUMA_4X4_CNT_MB SUB_BLK_COUNT_LUMA_4x4 * SUB_BLK_COUNT_LUMA_4x4 + +/*Number of chroma coffs in an MB*/ +#define SUB_BLK_CHROMA_4X4_CNT_MB SUB_BLK_COUNT_CHROMA_4x4_420 * SUB_BLK_COUNT_CHROMA_4x4_420 +#define SUB_BLK_CHROMA_4X4_CNT_MB_BIPLANE SUB_BLK_CHROMA_4X4_CNT_MB*NUM_CHROMA_PLANES + +/*Size of trans buff = 4x4 for DC block + 4x4 * coffs for 4x4 ac blocks*/ +#define SIZE_TRANS_BUFF (SUB_BLK_WIDTH_4x4*SUB_BLK_WIDTH_4x4*+ \ + SUB_BLK_WIDTH_4x4*SUB_BLK_WIDTH_4x4* \ + SUB_BLK_COUNT_LUMA_4x4*SUB_BLK_COUNT_LUMA_4x4) + +/*memory size = memory size of 4x4 block of resi coff + 4x4 for DC coff block */ +#define SIZE_TMP_BUFF_ITRANS ((SUB_BLK_WIDTH_4x4*SUB_BLK_WIDTH_4x4) +\ + (SUB_BLK_WIDTH_4x4*SUB_BLK_WIDTH_4x4)) + +#endif /* IH264_DEFS_H_ */ diff --git a/dependencies/ih264d/common/ih264_structs.h b/dependencies/ih264d/common/ih264_structs.h new file mode 100644 index 00000000..d1eaeac3 --- /dev/null +++ b/dependencies/ih264d/common/ih264_structs.h @@ -0,0 +1,1918 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ + +/** + ******************************************************************************* + * @file + * ih264_structs.h + * + * @brief + * Structure definitions used in the code + * + * @author + * Ittiam + * + * @par List of Functions: + * + * @remarks + * None + * + ******************************************************************************* + */ + +#ifndef _IH264_STRUCTS_H_ +#define _IH264_STRUCTS_H_ + +/** MB Type info for Intra MBs */ +typedef struct +{ + UWORD32 u4_num_mbpart; + MBPART_PREDMODE_T e_mbpart_predmode; + MBMODES_I16x16 e_intra_predmode; + UWORD32 u4_cpb_chroma; + UWORD32 u4_cpb_luma; +}intra_mbtype_info_t; + +/** MB Type info for Inter MBs */ +typedef struct +{ + UWORD32 u4_num_mbpart; + MBPART_PREDMODE_T e_mbpart_predmode_0; + MBPART_PREDMODE_T e_mbpart_predmode_1; + UWORD32 u4_mbpart_wd; + UWORD32 u4_mbpart_ht; +}inter_mbtype_info_t; + + +/** Sub MB Type info for Inter MBs */ +typedef struct +{ + UWORD32 u4_num_mbpart; + MBPART_PREDMODE_T e_mbpart_predmode; + UWORD32 u4_mbpart_wd; + UWORD32 u4_mbpart_ht; +}submbtype_info_t; + +/** + * Picture buffer + */ +typedef struct +{ + UWORD8* pu1_luma; + UWORD8* pu1_chroma; + + WORD32 i4_abs_poc; + WORD32 i4_poc_lsb; + + + /** Lower 32 bit of time stamp */ + UWORD32 u4_timestamp_low; + + /** Upper 32 bit of time stamp */ + UWORD32 u4_timestamp_high; + + WORD32 i4_used_as_ref; + + /** + * frame_num in the slice header + */ + WORD32 i4_frame_num; + + /** + * Long-term frame idx + * TODO: store in frame_num + */ + WORD32 i4_long_term_frame_idx; + + /* + * 0: Top Field + * 1: Bottom Field + */ + WORD8 i1_field_type; + + /** + * buffer ID from frame buffer manager + */ + WORD32 i4_buf_id; + +} pic_buf_t; + + +/** + * Reference List + */ +typedef struct +{ + void *pv_pic_buf; + + void *pv_mv_buf; + +} ref_list_t; + + +/** + * Motion vector + */ +typedef struct +{ + /** + * Horizontal Motion Vector + */ + WORD16 i2_mvx; + + /** + * Vertical Motion Vector + */ + WORD16 i2_mvy; +} mv_t; + +/*****************************************************************************/ +/* Following results in packed 48 bit structure. If mv_t included */ +/* ref_pic_buf_id, then 8 bits will be wasted for each mv for aligning. */ +/* Also using mv_t as elements directly instead of a pointer to l0 and l1 */ +/* mvs. Since pointer takes 4 bytes and MV itself is 4 bytes. It does not */ +/* really help using pointers. */ +/*****************************************************************************/ + +/** + * PU Motion Vector info + */ +typedef struct +{ + /** + * L0 Motion Vector + */ + mv_t s_l0_mv; + + /** + * L1 Motion Vector + */ + mv_t s_l1_mv; + + /** + * L0 Ref index + */ + WORD8 i1_l0_ref_idx; + + /** + * L1 Ref index + */ + WORD8 i1_l1_ref_idx; + + /** + * L0 Ref Pic Buf ID + */ + WORD8 i1_l0_ref_pic_buf_id; + + /** + * L1 Ref Pic Buf ID + */ + WORD8 i1_l1_ref_pic_buf_id; + +} pu_mv_t; + +/** + * PU information + */ +typedef struct +{ + + /** + * Motion Vectors + */ + pu_mv_t s_mv; + + /** + * PU X position in terms of min PU (4x4) units + */ + UWORD32 b2_pos_x : 2; + + /** + * PU Y position in terms of min PU (4x4) units + */ + UWORD32 b2_pos_y : 2; + + /** + * PU width in pixels = (b2_wd + 1) << 2 + */ + UWORD32 b2_wd : 2; + + /** + * PU height in pixels = (b2_ht + 1) << 2 + */ + UWORD32 b2_ht : 2; + + /** + * Intra or Inter flag for each partition - 0 or 1 + */ + UWORD32 b1_intra_flag : 1; + + /** + * PRED_L0, PRED_L1, PRED_BI + */ + UWORD32 b2_pred_mode : 2; + +} pu_t; + + +/** + * MB information to be stored for entire frame + */ +typedef struct +{ + /** + * Transform sizes 0: 4x4, 1: 8x8, + */ + UWORD32 b1_trans_size : 1; + + /** + * CBP - 4 bits for Y, 1 for U and 1 for V + */ + UWORD32 b6_cbp: 6; + + /** + * Intra pred sizes 0: 4x4, 1: 8x8, 2: 16x16 + */ + UWORD32 b2_intra_pred_size : 2; + + /** + * Flag to signal if the current MB is IPCM + */ + UWORD32 b1_ipcm : 1; + +}mb_t; + +/*****************************************************************************/ +/* Info from last TU row of MB is stored in a row level neighbour buffer */ +/* , which will be used for Boundary Strength computation */ +/*****************************************************************************/ +/** + * MB neighbor info + */ +typedef struct +{ + /** + * Slice index of the mb + */ + UWORD16 u2_slice_idx; + + /*************************************************************************/ + /* CBF of bottom TU row (replicated in 4 pixel boundary) */ + /* MSB contains CBF of first TU in the last row and LSB contains CBF */ + /* of last TU in the last row */ + /*************************************************************************/ + /** + * CBF of bottom TU row + */ + UWORD16 u2_packed_cbf; + + /*************************************************************************/ + /* QP of bottom TU row (replicated at 8 pixel boundary (Since QP can */ + /* not change at less than min CU granularity) */ + /*************************************************************************/ + /** + * QP of bottom TU row + */ + UWORD8 u1_qp; + +} mb_top_ny_info_t; + +/** + * MB level context + */ +typedef struct _mb_ctxt_t +{ + /*************************************************************************/ + /* Tile boundary can be detected by looking at tile start x and tile */ + /* start y. And based on the tile, slice and frame boundary the */ + /* following will be initialized. */ + /*************************************************************************/ + /** + * Pointer to left MB + */ + /* If not available, this will be set to NULL */ + struct _mb_ctxt_t *ps_mb_left; + + /** + * Pointer to top-left MB + */ + /* If not available, this will be set to NULL */ + mb_top_ny_info_t *ps_mb_ny_topleft; + + /** + * Pointer to top MB + */ + /* If not available, this will be set to NULL */ + mb_top_ny_info_t *ps_mb_ny_top; + + /** + * Pointer to top-right MB + */ + /* If not available, this will be set to NULL */ + mb_top_ny_info_t *ps_mb_ny_topright; + + /*************************************************************************/ + /* Pointer to PU data. */ + /* This points to a MV Bank stored at frame level. Though this */ + /* pointer can be derived by reading offset at frame level, it is */ + /* stored here for faster access. Can be removed if storage of MB */ + /* structure is critical */ + /*************************************************************************/ + /** + * Pointer to PU data + */ + pu_t *ps_pu; + + /*************************************************************************/ + /* Pointer to a PU map stored at frame level, */ + /* Though this pointer can be derived by multiplying MB address with */ + /* number of minTUs in a MB, it is stored here for faster access. */ + /* Can be removed if storage of MB structure is critical */ + /*************************************************************************/ + /** + * Pointer to a PU map stored at frame level + */ + UWORD8 *pu1_pu_map; + + /** + * Number of TUs filled in as_tu + */ + /*************************************************************************/ + /* Having the first entry as 32 bit data, helps in keeping each of */ + /* the structures aligned to 32 bits at MB level */ + /*************************************************************************/ + WORD32 i4_tu_cnt; + + /** + * Pointer to transform coeff data + */ + /*************************************************************************/ + /* Following format is repeated for every coded TU */ + /* Luma Block */ + /* num_coeffs : 16 bits */ + /* zero_cols : 8 bits ( 1 bit per 4 columns) */ + /* sig_coeff_map : ((TU Size * TU Size) + 31) >> 5 number of WORD32s */ + /* coeff_data : Non zero coefficients */ + /* Cb Block (only for last TU in 4x4 case else for every luma TU) */ + /* num_coeffs : 16 bits */ + /* zero_cols : 8 bits ( 1 bit per 4 columns) */ + /* sig_coeff_map : ((TU Size * TU Size) + 31) >> 5 number of WORD32s */ + /* coeff_data : Non zero coefficients */ + /* Cr Block (only for last TU in 4x4 case else for every luma TU) */ + /* num_coeffs : 16 bits */ + /* zero_cols : 8 bits ( 1 bit per 4 columns) */ + /* sig_coeff_map : ((TU Size * TU Size) + 31) >> 5 number of WORD32s */ + /* coeff_data : Non zero coefficients */ + /*************************************************************************/ + void *pv_coeff_data; + + /** + * Slice to which the MB belongs to + */ + WORD32 i4_slice_idx; + + /** + * MB column position + */ + WORD32 i4_pos_x; + + /** + * MB row position + */ + WORD32 i4_pos_y; + + /** + * Number of PUs filled in ps_pu + */ + WORD32 i4_pu_cnt; + + /** + * Index of current PU being processed in ps_pu + */ + /* Scratch variable set to 0 at the start of any PU processing function */ + WORD32 i4_pu_idx; + + /** + * Vertical Boundary strength + */ + /* Two bits per edge. + Stored in format. BS[15] | BS[14] | .. |BS[0]*/ + UWORD32 *pu4_vert_bs; + + /** + * Horizontal Boundary strength + */ + + /* Two bits per edge. + Stored in format. BS[15] | BS[14] | .. |BS[0]*/ + UWORD32 *pu4_horz_bs; + + /** + * Qp array stored for each 8x8 pixels + */ + UWORD8 *pu1_qp; + + /** + * Pointer to current frame's pu_t array + */ + pu_t *ps_frm_pu; + + /** + * Pointer to current frame's pu_t index array, which stores starting index + * of pu_t for every MB + */ + UWORD32 *pu4_frm_pu_idx; + + /** + * Pointer to current frame's pu map array + */ + UWORD8 *pu1_frm_pu_map; + + /*************************************************************************/ + /* Need to add encoder specific elements for identifying the order of */ + /* coding for CU, TU and PU if any */ + /*************************************************************************/ +} mb_ctxt_t; + +/*************************************************************************/ +/* The following describes how each of the CU cases are handled */ +/*************************************************************************/ + +/*************************************************************************/ +/* For SKIP MB */ +/* One Inter PU with appropriate MV */ +/* One TU which says CBP is zero and size is 16x16 */ +/*************************************************************************/ + +/*************************************************************************/ +/* For Inter MB */ +/* M Inter PU with appropriate MVs (M between 1 to 4) */ +/* Number of TUs derived based on transform size */ +/*************************************************************************/ + +/*************************************************************************/ +/* For Intra MB */ +/* Number of TUs derived based on transform size */ +/* N Intra Modes are signaled along with coeff data at the start */ +/*************************************************************************/ + +/*************************************************************************/ +/* For Intra PCM MB */ +/* One TU which says ipcm is 1 */ +/*************************************************************************/ + + + +/** + * Structure to hold quantization parameters of an mb + */ +typedef struct +{ + + /* + * mb qp + */ + UWORD8 u1_mb_qp; + + /* + * mb qp / 6 + */ + UWORD8 u1_qp_div; + + /* + * mb qp mod 6 + */ + UWORD8 u1_qp_rem; + + /* + * QP bits + */ + UWORD8 u1_qbits; + + /* + * forward scale matrix + */ + const UWORD16 *pu2_scale_mat; + + /* + * threshold matrix for quantization + */ + UWORD16 *pu2_thres_mat; + + /* + * Threshold to compare the sad with + */ + UWORD16 *pu2_sad_thrsh; + + /* + * qp dependent rounding constant + */ + UWORD32 u4_dead_zone; + + /* + * inverse scale matrix + */ + const UWORD16 *pu2_iscale_mat; + + /* + * Weight matrix in iquant + */ + UWORD16 *pu2_weigh_mat; + +}quant_params_t; + +/** + * Structure to hold Profile tier level info for a given layer + */ + +typedef struct +{ + /** + * NAL unit type + */ + WORD8 i1_nal_unit_type; + + /** + * NAL ref idc + */ + WORD8 i1_nal_ref_idc; + + +} nal_header_t; + +/** + * HRD parameters Info + */ +typedef struct +{ + /** + * Specifies the number of alternative CPB specifications in the + * bitstream + */ + UWORD8 u1_cpb_cnt_minus1; + + /** + * (together with bit_rate_value_minus1) specifies the + * maximum input bit rate of the i-th CPB + */ + UWORD32 u4_bit_rate_scale; + + /** + * (together with cpb_size_du_value_minus1) specifies + * CPB size of the i-th CPB when the CPB operates + * at the access unit level + */ + UWORD32 u4_cpb_size_scale; + + /** + * (together with bit_rate_scale) specifies the + * maximum input bit rate for the i-th CPB + */ + UWORD32 au4_bit_rate_value_minus1[32]; + /** + * together with cpb_size_scale to specify the + * CPB size when the CPB operates at the access unit level. + */ + UWORD32 au4_cpb_size_value_minus1[32]; + + /** + * if 1, specifies that the HSS operates in a constant bit rate (CBR) mode + * if 0, specifies that the HSS operates in a intermittent bit rate (CBR) mode + */ + UWORD8 au1_cbr_flag[32]; + + + /** + * specifies the length, in bits for initial cpb delay (nal/vcl)syntax in bp sei + */ + UWORD8 u1_initial_cpb_removal_delay_length_minus1; + + /** + * specifies the length, in bits for the cpb delay syntax in pt_sei + */ + UWORD8 u1_cpb_removal_delay_length_minus1; + + /** + * specifies the length, in bits, of the pic_dpb_output_delay syntax element in the pt SEI message + */ + UWORD8 u1_dpb_output_delay_length_minus1; + + /** + * Specifies length of the time offset parameter + */ + UWORD8 u1_time_offset_length; + +}hrd_params_t; + + +/** + * Structure to hold VUI parameters Info + */ +typedef struct +{ + /** + * indicates the presence of aspect_ratio + */ + UWORD8 u1_aspect_ratio_info_present_flag; + + /** + * specifies the aspect ratio of the luma samples + */ + UWORD8 u1_aspect_ratio_idc; + + /** + * width of the luma samples. user dependent + */ + UWORD16 u2_sar_width; + + /** + * Height of the luma samples. user dependent + */ + UWORD16 u2_sar_height; + + /** + * if 1, specifies that the overscan_appropriate_flag is present + * if 0, the preferred display method for the video signal is unspecified + */ + UWORD8 u1_overscan_info_present_flag; + + /** + * if 1,indicates that the cropped decoded pictures output + * are suitable for display using overscan + */ + UWORD8 u1_overscan_appropriate_flag; + + /** + * if 1 specifies that video_format, video_full_range_flag and + * colour_description_present_flag are present + */ + UWORD8 u1_video_signal_type_present_flag; + + /** + * pal, secam, ntsc, ... + */ + UWORD8 u1_video_format; + + /** + * indicates the black level and range of the luma and chroma signals + */ + UWORD8 u1_video_full_range_flag; + + /** + * if 1,to 1 specifies that colour_primaries, transfer_characteristics + * and matrix_coefficients are present + */ + UWORD8 u1_colour_description_present_flag; + + /** + * indicates the chromaticity coordinates of the source primaries + */ + UWORD8 u1_colour_primaries; + + /** + * indicates the opto-electronic transfer characteristic of the source picture + */ + UWORD8 u1_transfer_characteristics; + + /** + * the matrix coefficients used in deriving luma and chroma signals + * from the green, blue, and red primaries + */ + UWORD8 u1_matrix_coefficients; + + /** + * if 1, specifies that chroma_sample_loc_type_top_field and + * chroma_sample_loc_type_bottom_field are present + */ + UWORD8 u1_chroma_loc_info_present_flag; + + /** + * location of chroma samples + */ + UWORD8 u1_chroma_sample_loc_type_top_field; + + UWORD8 u1_chroma_sample_loc_type_bottom_field; + + /** + * Indicates the presence of the + * num_units_in_ticks, time_scale flag + */ + UWORD8 u1_vui_timing_info_present_flag; + + /** + * Number of units that + * correspond to one increment of the + * clock. Indicates the resolution + */ + UWORD32 u4_vui_num_units_in_tick; + + /** + * The number of time units that pass in one second + */ + UWORD32 u4_vui_time_scale; + + /** + * Flag indicating that time difference between two frames is a constant + */ + UWORD8 u1_fixed_frame_rate_flag; + + /** + * Indicates the presence of NAL HRD parameters + */ + UWORD8 u1_nal_hrd_parameters_present_flag; + + /** + * NAL level HRD parameters + */ + hrd_params_t s_nal_hrd_parameters; + + /** + * Indicates the presence of VCL HRD parameters + */ + UWORD8 u1_vcl_hrd_parameters_present_flag; + + /** + * VCL level HRD parameters + */ + hrd_params_t s_vcl_hrd_parameters; + + /** + * Specifies the HRD operational mode + */ + UWORD8 u1_low_delay_hrd_flag; + + /** + * Indicates presence of SEI messages which include pic_struct syntax element + */ + UWORD8 u1_pic_struct_present_flag; + + /** + * 1, specifies that the following cvs bitstream restriction parameters are present + */ + UWORD8 u1_bitstream_restriction_flag; + + /** + * if 0, indicates that no pel outside the pic boundaries and + * no sub-pels derived using pels outside the pic boundaries is used for inter prediction + */ + UWORD8 u1_motion_vectors_over_pic_boundaries_flag; + + /** + * Indicates a number of bytes not exceeded by the sum of the sizes of the VCL NAL units + * associated with any coded picture + */ + UWORD8 u1_max_bytes_per_pic_denom; + + /** + * Indicates an upper bound for the number of bits of coding_unit() data + */ + UWORD8 u1_max_bits_per_mb_denom; + + /** + * Indicate the maximum absolute value of a decoded horizontal MV component + * in quarter-pel luma units + */ + UWORD8 u1_log2_max_mv_length_horizontal; + + /** + * Indicate the maximum absolute value of a decoded vertical MV component + * in quarter-pel luma units + */ + UWORD8 u1_log2_max_mv_length_vertical; + + /** + * Max number of frames that are not synchronized in display and decode order + */ + UWORD8 u1_num_reorder_frames; + + /** + * specifies required size of the HRD DPB in units of frame buffers. + */ + UWORD8 u1_max_dec_frame_buffering; + +} vui_t; + + +/** + * Structure to hold SPS info + */ +typedef struct +{ + /** + * profile_idc + */ + UWORD8 u1_profile_idc; + + /** constraint_set0_flag */ + UWORD8 u1_constraint_set0_flag; + + /** constraint_set1_flag */ + UWORD8 u1_constraint_set1_flag; + + /** constraint_set2_flag */ + UWORD8 u1_constraint_set2_flag; + + /** constraint_set3_flag */ + UWORD8 u1_constraint_set3_flag; + + /** + * level_idc + */ + UWORD8 u1_level_idc; + + /** + * seq_parameter_set_id + */ + UWORD8 u1_sps_id; + + + /** + * chroma_format_idc + */ + UWORD8 u1_chroma_format_idc; + + /** + * residual_colour_transform_flag + */ + WORD8 i1_residual_colour_transform_flag; + + /** + * bit_depth_luma_minus8 + */ + WORD8 i1_bit_depth_luma; + + /** + * bit_depth_chroma_minus8 + */ + WORD8 i1_bit_depth_chroma; + + /** + * qpprime_y_zero_transform_bypass_flag + */ + WORD8 i1_qpprime_y_zero_transform_bypass_flag; + + /** + * seq_scaling_matrix_present_flag + */ + WORD8 i1_seq_scaling_matrix_present_flag; + + /** + * seq_scaling_list_present_flag + */ + WORD8 ai1_seq_scaling_list_present_flag[8]; + + /** + * log2_max_frame_num_minus4 + */ + WORD8 i1_log2_max_frame_num; + + /** + * MaxFrameNum in the standard + * 1 << i1_log2_max_frame_num + */ + WORD32 i4_max_frame_num; + + /** + * pic_order_cnt_type + */ + WORD8 i1_pic_order_cnt_type; + + /** + * log2_max_pic_order_cnt_lsb_minus4 + */ + WORD8 i1_log2_max_pic_order_cnt_lsb; + + /** + * MaxPicOrderCntLsb in the standard. + * 1 << log2_max_pic_order_cnt_lsb_minus4 + */ + WORD32 i4_max_pic_order_cnt_lsb; + + /** + * delta_pic_order_always_zero_flag + */ + WORD8 i1_delta_pic_order_always_zero_flag; + + /** + * offset_for_non_ref_pic + */ + WORD32 i4_offset_for_non_ref_pic; + + /** + * offset_for_top_to_bottom_field + */ + WORD32 i4_offset_for_top_to_bottom_field; + + /** + * num_ref_frames_in_pic_order_cnt_cycle + */ + UWORD8 u1_num_ref_frames_in_pic_order_cnt_cycle; + + /** + * Offset_for_ref_frame + */ + WORD32 ai4_offset_for_ref_frame[256]; + + /** + * max_num_ref_frames + */ + UWORD8 u1_max_num_ref_frames; + + /** + * gaps_in_frame_num_value_allowed_flag + */ + WORD8 i1_gaps_in_frame_num_value_allowed_flag; + + /** + * pic_width_in_mbs_minus1 + */ + WORD16 i2_pic_width_in_mbs_minus1; + + /** + * pic_height_in_map_units_minus1 + */ + WORD16 i2_pic_height_in_map_units_minus1; + + /** + * frame_mbs_only_flag + */ + WORD8 i1_frame_mbs_only_flag; + + /** + * mb_adaptive_frame_field_flag + */ + WORD8 i1_mb_adaptive_frame_field_flag; + + /** + * direct_8x8_inference_flag + */ + WORD8 i1_direct_8x8_inference_flag; + + /** + * frame_cropping_flag + */ + WORD8 i1_frame_cropping_flag; + + /** + * frame_crop_left_offset + */ + WORD16 i2_frame_crop_left_offset; + + /** + * frame_crop_right_offset + */ + WORD16 i2_frame_crop_right_offset; + + /** + * frame_crop_top_offset + */ + WORD16 i2_frame_crop_top_offset; + + /** + * frame_crop_bottom_offset + */ + WORD16 i2_frame_crop_bottom_offset; + + /** + * vui_parameters_present_flag + */ + WORD8 i1_vui_parameters_present_flag; + + /** + * vui_parameters_Structure_info + */ + vui_t s_vui_parameters; + + /** + * Flag to give status of SPS structure + */ + WORD8 i1_sps_valid; + + /** + * Coded Picture width + */ + WORD32 i2_pic_wd; + + /** + * Coded Picture height + */ + WORD32 i2_pic_ht; + + /** + * Picture width in MB units + */ + + WORD16 i2_pic_wd_in_mb; + + /** + * Picture height in MB units + */ + + WORD16 i2_pic_ht_in_mb; + + /** + * useDefaultScalingMatrixFlag + */ + WORD8 ai1_use_default_scaling_matrix_flag[8]; + + /** + * 4x4 Scaling lists after inverse zig zag scan + */ + UWORD16 au2_4x4_weight_scale[6][16]; + + /** + * 4x4 Scaling lists after inverse zig zag scan + */ + UWORD16 au2_8x8_weight_scale[2][64]; + +} sps_t; + + +/** + * Structure to hold PPS info + */ +typedef struct +{ + /** + * pic_parameter_set_id + */ + UWORD8 u1_pps_id; + + /** + * seq_parameter_set_id + */ + UWORD8 u1_sps_id; + + /** + * Entropy coding : 0-VLC; 1 - CABAC + */ + UWORD8 u1_entropy_coding_mode_flag; + + /* + * Pic order present flag + */ + UWORD8 u1_pic_order_present_flag; + + /* + * Number of slice groups + */ + UWORD8 u1_num_slice_groups; + + /* + * Slice group map type + */ + UWORD8 u1_slice_group_map_type; + + /* + * Maximum reference picture index in the reference list 0 : range [0 - 31] + */ + WORD8 i1_num_ref_idx_l0_default_active; + + /* + * Maximum reference picture index in the reference list 1 : range [0 - 31] + */ + WORD8 i1_num_ref_idx_l1_default_active; + + /** + * weighted_pred_flag + */ + WORD8 i1_weighted_pred_flag; + + /** + * weighted_bipred_flag + */ + WORD8 i1_weighted_bipred_idc; + + /** + * pic_init_qp_minus26 + */ + WORD8 i1_pic_init_qp; + + /** + * pic_init_qs_minus26 + */ + WORD8 i1_pic_init_qs; + + /* + * Chroma QP offset w.r.t QPY {-12,12} + */ + WORD8 i1_chroma_qp_index_offset; + + /** + * deblocking_filter_control_present_flag + */ + WORD8 i1_deblocking_filter_control_present_flag; + + /** + * constrained_intra_pred_flag + */ + WORD8 i1_constrained_intra_pred_flag; + + /** + * redundant_pic_cnt_present_flag + */ + WORD8 i1_redundant_pic_cnt_present_flag; + + /** + * transform_8x8_mode_flag + */ + WORD8 i1_transform_8x8_mode_flag; + + /** + * pic_scaling_matrix_present_flag + */ + WORD8 i1_pic_scaling_matrix_present_flag; + + /* + * Second chroma QP offset + */ + WORD8 i1_second_chroma_qp_index_offset; + + + /** + * useDefaultScalingMatrixFlag + */ + WORD8 ai1_use_default_scaling_matrix_flag[8]; + + /** + * 4x4 Scaling lists after inverse zig zag scan + */ + UWORD16 au2_4x4_weight_scale[6][16]; + + /** + * 4x4 Scaling lists after inverse zig zag scan + */ + UWORD16 au2_8x8_weight_scale[2][64]; + + + /** + * pic_scaling_list_present_flag + */ + WORD8 ai1_pic_scaling_list_present_flag[8]; + + /** + * Flag to give status of PPS structure + */ + WORD8 i1_pps_valid; + + +} pps_t; + +/** + * MMCO commands and params. + */ +typedef struct +{ + /* memory management control operation command */ + UWORD8 u1_memory_management_control_operation; + + /* + * Contains difference of pic nums of short-term pic/frame + * 1. To signal it as "unused for reference" if mmco = 1 + * 2. To signal it as "used for long-term reference" if mmco = 3 + */ + UWORD32 u4_difference_of_pic_nums_minus1; + + /* Long-term pic num to be set as "unused for reference" */ + UWORD8 u1_long_term_pic_num; + + /* + * Assign a long-term idx to a picture as follows + * 1. Assign to a short-term pic if mmco = 3 + * 2. Assign to the current pic if mmco = 6 + */ + UWORD8 u1_long_term_frame_idx; + + /* + * The max long-term idx. The long-term pics having idx above + * are set as "unused for reference + */ + UWORD8 u1_max_long_term_frame_idx_plus1; + +}mmco_prms_t; + +/** + * Structure to hold Reference picture list modification info + */ +typedef struct +{ + /* ref_pic_list_modification_flag_l0 */ + WORD8 i1_ref_pic_list_modification_flag_l0; + + /* Modification required in list0 */ + WORD8 i1_modification_of_pic_nums_idc_l0[MAX_MODICATION_IDC]; + + /* + * The absolute difference between the picture number of + * the picture being moved to the current index in + * list0 and the picture number prediction value + */ + UWORD32 u4_abs_diff_pic_num_minus1_l0[MAX_MODICATION_IDC]; + + /* + * The long-term picture number of the picture being moved + * to the current index in list0 + */ + UWORD8 u1_long_term_pic_num_l0[MAX_MODICATION_IDC]; + + /* ref_pic_list_modification_flag_l1 */ + WORD8 i1_ref_pic_list_modification_flag_l1; + + /* Modification required in list1 */ + WORD8 i1_modification_of_pic_nums_idc_l1[MAX_MODICATION_IDC]; + + /* + * The absolute difference between the picture number of + * the picture being moved to the current index in + * list1 and the picture number prediction value + */ + UWORD32 u4_abs_diff_pic_num_minus1_l1[MAX_MODICATION_IDC]; + + /* + * The long-term picture number of the picture being moved + * to the current index in list1 + */ + UWORD8 u1_long_term_pic_num_l1[MAX_MODICATION_IDC]; +}rplm_t; + +/** + * Structure to hold Slice Header info + */ +typedef struct +{ + + /* + * nal_unit_type + */ + WORD8 i1_nal_unit_type; + + /* + * nal_unit_idc + */ + WORD8 i1_nal_unit_idc; + + /* + * first_mb_in_slice + */ + UWORD16 u2_first_mb_in_slice; + + /* + * slice_type + */ + UWORD8 u1_slice_type; + + /* + * pic_parameter_set_id + */ + UWORD8 u1_pps_id; + + /* + * frame_num + */ + WORD32 i4_frame_num; + + /* + * field_pic_flag + */ + WORD8 i1_field_pic_flag; + + /* + * bottom_field_flag + */ + WORD8 i1_bottom_field_flag; + + /* + * second_field + */ + WORD8 i1_second_field_flag; + + /* + * idr_pic_id + */ + UWORD16 u2_idr_pic_id ; + + /* + * pic_order_cnt_lsb + */ + UWORD16 i4_pic_order_cnt_lsb; + + /* + * delta_pic_order_cnt_bottom + */ + WORD32 i4_delta_pic_order_cnt_bottom; + + /* + * delta_pic_order_cnt + */ + WORD32 ai4_delta_pic_order_cnt[2]; + + /* + * redundant_pic_cnt + */ + UWORD8 u1_redundant_pic_cnt; + + /* + * direct_spatial_mv_pred_flag + */ + UWORD8 u1_direct_spatial_mv_pred_flag; + + /* + * num_ref_idx_active_override_flag + */ + UWORD8 u1_num_ref_idx_active_override_flag; + + /* + * num_ref_idx_l0_active + */ + WORD8 i1_num_ref_idx_l0_active; + + /* + * num_ref_idx_l1_active_minus1 + */ + WORD8 i1_num_ref_idx_l1_active; + + /* + * ref_pic_list_reordering_flag_l0 + */ + UWORD8 u1_ref_idx_reordering_flag_l0; + + /* + * ref_pic_list_reordering_flag_l1 + */ + UWORD8 u1_ref_idx_reordering_flag_l1; + + /** + * Reference prediction list modification + */ + rplm_t s_rplm; + + /** + * L0 Reference pic lists + */ + ref_list_t as_ref_pic_list0[MAX_DPB_SIZE]; + + /** + * L1 Reference pic lists + */ + ref_list_t as_ref_pic_list1[MAX_DPB_SIZE]; + + /* + * no_output_of_prior_pics_flag + */ + UWORD8 u1_no_output_of_prior_pics_flag; + + /* + * long_term_reference_flag + */ + UWORD8 u1_long_term_reference_flag; + + /* + * adaptive_ref_pic_marking_mode_flag + */ + UWORD8 u1_adaptive_ref_pic_marking_mode_flag; + + /* + * Array to structures to store mmco commands + * and parameters. + */ + mmco_prms_t as_mmco_prms[MAX_MMCO_COMMANDS]; + + /* + * entropy_coding_mode_flag + */ + WORD8 u1_entropy_coding_mode_flag; + + /* + * cabac_init_idc + */ + WORD8 i1_cabac_init_idc; + + /* + * i1_slice_qp + */ + WORD8 i1_slice_qp; + + /* + * sp_for_switch_flag + */ + UWORD8 u1_sp_for_switch_flag; + + /* + * slice_qs_delta + */ + UWORD8 u1_slice_qs; + + /* + * disable_deblocking_filter_idc + */ + WORD8 u1_disable_deblocking_filter_idc; + + /* + * slice_alpha_c0_offset_div2 + */ + WORD8 i1_slice_alpha_c0_offset_div2; + + /* + * slice_beta_offset_div2 + */ + WORD8 i1_slice_beta_offset_div2; + + /* + * num_slice_groups_minus1 + */ + WORD8 u1_num_slice_groups_minus1; + + /* + * slice_group_change_cycle + */ + WORD8 u1_slice_group_change_cycle; + + /** + * Start MB X + */ + UWORD16 i2_mb_x; + + /** + * Start MB Y + */ + UWORD16 i2_mb_y; + + /** + * Absolute POC. Contains minimum of top and bottom POC. + */ + WORD32 i4_abs_pic_order_cnt; + + /** + * Absolute top POC. Contains top poc for frame or top + * field. Invalid for bottom field. + */ + WORD32 i4_abs_top_pic_order_cnt; + + /** + * Absolute top POC. Contains bottom poc for frame or bottom + * field. Invalid for top field. + */ + WORD32 i4_abs_bottom_pic_order_cnt; + + /** Flag signaling if the current slice is ref slice */ + UWORD8 i1_nal_ref_idc; + + /** Flag to indicate if the current slice is MBAFF Frame */ + UWORD8 u1_mbaff_frame_flag; + + /** luma_log2_weight_denom */ + UWORD8 u1_luma_log2_weight_denom; + + /** chroma_log2_weight_denom */ + UWORD8 u1_chroma_log2_weight_denom; + + /** luma_weight_l0_flag */ + UWORD8 au1_luma_weight_l0_flag[MAX_DPB_SIZE]; + + /** luma_weight_l0 : (-128, 127 )is the range of weights + * when weighted pred is enabled, 128 is default value */ + WORD16 ai2_luma_weight_l0[MAX_DPB_SIZE]; + + /** luma_offset_l0 : (-128, 127 )is the range of offset + * when weighted pred is enabled, 0 is default value */ + WORD8 ai1_luma_offset_l0[MAX_DPB_SIZE]; + + /** chroma_weight_l0_flag */ + UWORD8 au1_chroma_weight_l0_flag[MAX_DPB_SIZE]; + + /** chroma_weight_l0 : (-128, 127 )is the range of weights + * when weighted pred is enabled, 128 is default value*/ + WORD16 ai2_chroma_weight_l0[MAX_DPB_SIZE][2]; + + /** chroma_offset_l0 : (-128, 127 )is the range of offset + * when weighted pred is enabled, 0 is default value*/ + WORD8 ai1_chroma_offset_l0[MAX_DPB_SIZE][2]; + + /** luma_weight_l0_flag */ + UWORD8 au1_luma_weight_l1_flag[MAX_DPB_SIZE]; + + /** luma_weight_l1 : (-128, 127 )is the range of weights + * when weighted pred is enabled, 128 is default value */ + WORD16 ai2_luma_weight_l1[MAX_DPB_SIZE]; + + /** luma_offset_l1 : (-128, 127 )is the range of offset + * when weighted pred is enabled, 0 is default value */ + WORD8 ai1_luma_offset_l1[MAX_DPB_SIZE]; + + /** chroma_weight_l1_flag */ + UWORD8 au1_chroma_weight_l1_flag[MAX_DPB_SIZE]; + + /** chroma_weight_l1 : (-128, 127 )is the range of weights + * when weighted pred is enabled, 128 is default value */ + WORD16 ai2_chroma_weight_l1[MAX_DPB_SIZE][2]; + + /** chroma_offset_l1 :(-128, 127 )is the range of offset + * when weighted pred is enabled, 0 is default value */ + WORD8 ai1_chroma_offset_l1[MAX_DPB_SIZE][2]; +}slice_header_t; + + +/*****************************************************************************/ +/* The following can be used to type cast coefficient data that is stored */ +/* per subblock. Note that though i2_level is shown as an array that */ +/* holds 16 coefficients, only the first few entries will be valid. Next */ +/* subblocks data starts after the valid number of coefficients. Number */ +/* of non-zero coefficients will be derived using number of non-zero bits */ +/* in sig coeff map */ +/*****************************************************************************/ + +/** + * Structure to hold coefficient info for a 2x2 chroma DC transform + */ +typedef struct +{ + /** + * significant coefficient map + */ + UWORD8 u1_sig_coeff_map; + + /** + * sub block position + */ + UWORD8 u1_subblk_pos; + + /** + * holds coefficients + */ + WORD16 ai2_level[2 * 2]; +}tu_sblk2x2_coeff_data_t; + +/** + * Structure to hold coefficient info for a 4x4 transform + */ +typedef struct +{ + /** + * significant coefficient map + */ + UWORD16 u2_sig_coeff_map; + + /** + * sub block position + */ + UWORD16 u2_subblk_pos; + + /** + * holds coefficients + */ + WORD16 ai2_level[SUBBLK_COEFF_CNT]; +}tu_sblk4x4_coeff_data_t; + +/** + * Structure to hold coefficient info for a 8x8 transform + */ +typedef struct +{ + + /** + * significant coefficient map + */ + UWORD32 au4_sig_coeff_map[2]; + + /** + * sub block position + */ + UWORD16 u2_subblk_pos; + + /** + * holds coefficients + */ + WORD16 ai2_level[TRANS_SIZE_8 * TRANS_SIZE_8]; +}tu_blk8x8_coeff_data_t; + + +/** + * Structure to hold coefficient info for a 16x16 IPCM MB + */ +typedef struct +{ + /** + * holds coefficients + */ + UWORD8 au1_level[MB_SIZE * MB_SIZE * 3 / 2]; +}tu_ipcm_coeff_data_t; + + +typedef struct +{ + /** + * Transform sizes 0: 4x4, 1: 8x8, + */ + UWORD32 b1_trans_size : 1; + + /** + * Flag to signal if the current MB is IPCM + */ + UWORD32 b1_ipcm : 1; + + /** + * Intra pred sizes 0: 4x4, 1: 8x8, 2: 16x16 + */ + UWORD32 b2_intra_pred_size : 2; + + /** + * Chroma intra mode + */ + UWORD32 b2_intra_chroma_pred_mode: 2; + + /** + * Number of coded subblocks in the current MB, for which + * tu data is sent. Maximum of 27 subblocks in the following + * order. + * 1 4x4 luma DC(for intra16x16), + * 16 4x4 luma, + * 2 2x2 chroma DC, + * 8 4x4 chroma, + */ + WORD32 b5_num_coded_sblks: 5; + + /** + * Flag to signal if 4x4 subblock for DC values (in INTRA 16x16 MB) + * is coded + */ + UWORD32 b1_luma_dc_coded: 1; + + /** + * Flag to signal if 4x4 subblock for DC values (in INTRA 16x16 MB) + * is coded + */ + UWORD32 b1_chroma_dc_coded: 1; + + /** + * CSBP - 16 bits, 1 bit for each 4x4 + * for intra16x16 mb_type only ac coefficients are + */ + UWORD32 b16_luma_csbp: 16; + + /** + * CSBP - 16 bits, 1 bit for each 4x4 + * for intra16x16 mb_type only ac coefficients are + */ + UWORD32 b8_chroma_csbp: 8; + + /** + * Luma Intra pred modes, + * Based on intra pred size either 16, 4 or 1 entry will be + * populated below. + */ + UWORD8 au1_luma_intra_modes[16]; + +}intra_mb_t; + + +typedef struct +{ + /** + * Transform sizes 0: 4x4, 1: 8x8, + */ + UWORD8 b1_trans_size : 1; + + + /** + * Skip flag + */ + UWORD8 b1_skip : 1; + + + /** + * Number of coded subblocks in the current MB, for which + * tu data is sent. Maximum of 26 subblocks in the following + * order. + * 16 4x4 luma, + * 2 2x2 chroma DC, + * 8 4x4 chroma, + */ + WORD32 b5_num_coded_sblks: 5; + + /** + * CSBP - 16 bits, 1 bit for each 4x4 + * for intra16x16 mb_type only ac coefficients are + */ + UWORD32 b16_luma_csbp: 16; + + /** + * CSBP - 16 bits, 1 bit for each 4x4 + * for intra16x16 mb_type only ac coefficients are + */ + UWORD32 b16_chroma_csbp: 8; +}inter_mb_t; + +/** + * Structure to hold Mastering Display Color Volume SEI + */ +typedef struct +{ + /** + * Array to store the display_primaries_x values + */ + UWORD16 au2_display_primaries_x[NUM_SEI_MDCV_PRIMARIES]; + + /** + * Array to store the display_primaries_y values + */ + UWORD16 au2_display_primaries_y[NUM_SEI_MDCV_PRIMARIES]; + + /** + * Variable to store the white point x value + */ + UWORD16 u2_white_point_x; + + /** + * Variable to store the white point y value + */ + UWORD16 u2_white_point_y; + + /** + * Variable to store the max display mastering luminance value + */ + UWORD32 u4_max_display_mastering_luminance; + + /** + * Variable to store the min display mastering luminance value + */ + UWORD32 u4_min_display_mastering_luminance; +}sei_mdcv_params_t; + + +/** + * Structure for Content Light Level Info + * + */ +typedef struct +{ + /** + * The maximum pixel intensity of all samples + */ + UWORD16 u2_max_content_light_level; + + /** + * The average pixel intensity of all samples + */ + UWORD16 u2_max_pic_average_light_level; +}sei_cll_params_t; + + +/** + * Structure to hold Ambient viewing environment SEI + */ +typedef struct +{ + /** + * specifies the environmental illluminance of the ambient viewing environment + */ + UWORD32 u4_ambient_illuminance; + + /* + * specify the normalized x chromaticity coordinates of the + * environmental ambient light in the nominal viewing environment + */ + UWORD16 u2_ambient_light_x; + + /* + * specify the normalized y chromaticity coordinates of the + * environmental ambient light in the nominal viewing environment + */ + UWORD16 u2_ambient_light_y; +}sei_ave_params_t; + + +/** + * Structure to hold Content color volume SEI + */ +typedef struct +{ + /* + * Flag used to control persistence of CCV SEI messages + */ + UWORD8 u1_ccv_cancel_flag; + + /* + * specifies the persistence of the CCV SEI message for the current layer + */ + UWORD8 u1_ccv_persistence_flag; + + /* + * specifies the presence of syntax elements ccv_primaries_x and ccv_primaries_y + */ + UWORD8 u1_ccv_primaries_present_flag; + + /* + * specifies that the syntax element ccv_min_luminance_value is present + */ + UWORD8 u1_ccv_min_luminance_value_present_flag; + + /* + * specifies that the syntax element ccv_max_luminance_value is present + */ + UWORD8 u1_ccv_max_luminance_value_present_flag; + + /* + * specifies that the syntax element ccv_avg_luminance_value is present + */ + UWORD8 u1_ccv_avg_luminance_value_present_flag; + + /* + * shall be equal to 0 in bitstreams conforming to this version. Other values + * for reserved_zero_2bits are reserved for future use + */ + UWORD8 u1_ccv_reserved_zero_2bits; + + /* + * specify the normalized x chromaticity coordinates of the colour + * primary component c of the nominal content colour volume + */ + WORD32 ai4_ccv_primaries_x[NUM_SEI_CCV_PRIMARIES]; + + /* + * specify the normalized y chromaticity coordinates of the colour + * primary component c of the nominal content colour volume + */ + WORD32 ai4_ccv_primaries_y[NUM_SEI_CCV_PRIMARIES]; + + /* + * specifies the normalized minimum luminance value + */ + UWORD32 u4_ccv_min_luminance_value; + + /* + * specifies the normalized maximum luminance value + */ + UWORD32 u4_ccv_max_luminance_value; + + /* + * specifies the normalized average luminance value + */ + UWORD32 u4_ccv_avg_luminance_value; +}sei_ccv_params_t; + + +/** + * Structure to hold SEI parameters Info + */ +typedef struct +{ + /** + * mastering display color volume info present flag + */ + UWORD8 u1_sei_mdcv_params_present_flag; + + /* + * MDCV parameters + */ + sei_mdcv_params_t s_sei_mdcv_params; + + /** + * content light level info present flag + */ + UWORD8 u1_sei_cll_params_present_flag; + + /* + * CLL parameters + */ + sei_cll_params_t s_sei_cll_params; + + /** + * ambient viewing environment info present flag + */ + UWORD8 u1_sei_ave_params_present_flag; + + /* + * AVE parameters + */ + sei_ave_params_t s_sei_ave_params; + + /** + * content color volume info present flag + */ + UWORD8 u1_sei_ccv_params_present_flag; + + /* + * CCV parameters + */ + sei_ccv_params_t s_sei_ccv_params; +} sei_params_t; + + +#endif /* _IH264_STRUCTS_H_ */ diff --git a/dependencies/ih264d/common/ih264_trans_data.c b/dependencies/ih264d/common/ih264_trans_data.c new file mode 100644 index 00000000..a1231e6f --- /dev/null +++ b/dependencies/ih264d/common/ih264_trans_data.c @@ -0,0 +1,312 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** + ******************************************************************************* + * @file + * ih264_trans_data.c + * + * @brief + * Contains definition of global variables for H264 encoder + * + * @author + * Ittiam + * + * @remarks + * + ******************************************************************************* + */ + +#include "ih264_typedefs.h" +#include "ih264_trans_data.h" + +/*****************************************************************************/ +/* Extern global definitions */ +/*****************************************************************************/ + +/* + * Since we don't have a division operation in neon + * we will multiply by LCM of 16,6,10 and scale accordingly + * so care that to get the actual transform you need to divide by LCM + * LCM = 240 + */ + +const UWORD16 g_scal_coff_h264_4x4[16] ={ + 15,40,40,40, + 40,24,40,24, + 15,40,40,15, + 40,24,40,24}; + + + +const UWORD16 g_scal_coff_h264_8x8[16]= + { + 16, 15, 20, 15, + 15, 14, 19, 14, + 20, 19, 25, 19, + 15, 14, 19, 14 + }; +/* + * The scaling is by an 8x8 matrix, but due its 4x4 symmetry we can use + * a 4x4 matrix for scaling + * now since divide is to be avoided, we will compute 1/ values and scale it up + * to preserve information since our data is max 10 bit +1 sign bit we can shift a maximum of 21 bits up + * hence multiply the matrix as such +{16.000 15.059 20.227 15.059 +15.059 14.173 19.051 14.173 +20.227 19.051 25.600 19.051 +15.059 14.173 19.051 14.173}; +{512, 544, 405, 544, +544, 578, 430, 578, +405, 430, 320, 430, +544, 578, 430, 578};*/ + + +/** + ****************************************************************************** + * @brief Scale Table for quantizing 4x4 subblock. To quantize a given 4x4 DCT + * transformed block, the coefficient at index location (i,j) is scaled by one of + * the constants in this table and right shift the result by (QP_BITS_h264_4x4 + + * floor(qp/6)), here qp is the quantization parameter used to quantize the mb. + * + * input : qp%6, index location (i,j) + * output : scale constant. + * + * @remarks 16 constants for each index position of the subblock and 6 for each + * qp%6 in the range 0-5 inclusive. + ****************************************************************************** + */ +const UWORD16 gu2_quant_scale_matrix_4x4[96] = +{ + 13107, 8066, 13107, 8066, + 8066, 5243, 8066, 5243, + 13107, 8066, 13107, 8066, + 8066, 5243, 8066, 5243, + + 11916, 7490, 11916, 7490, + 7490, 4660, 7490, 4660, + 11916, 7490, 11916, 7490, + 7490, 4660, 7490, 4660, + + 10082, 6554, 10082, 6554, + 6554, 4194, 6554, 4194, + 10082, 6554, 10082, 6554, + 6554, 4194, 6554, 4194, + + 9362, 5825, 9362, 5825, + 5825, 3647, 5825, 3647, + 9362, 5825, 9362, 5825, + 5825, 3647, 5825, 3647, + + 8192, 5243, 8192, 5243, + 5243, 3355, 5243, 3355, + 8192, 5243, 8192, 5243, + 5243, 3355, 5243, 3355, + + 7282, 4559, 7282, 4559, + 4559, 2893, 4559, 2893, + 7282, 4559, 7282, 4559, + 4559, 2893, 4559, 2893, + +}; + +/** + ****************************************************************************** + * @brief Round Factor for quantizing subblock. While quantizing a given 4x4 DCT + * transformed block, the coefficient at index location (i,j) is scaled by one of + * the constants in the table gu2_forward_quant_scalar_4x4 and then right shift + * the result by (QP_BITS_h264_4x4 + floor(qp/6)). + * Before right shifting a round factor is added. + * The round factor can be any value [a * (1 << (QP_BITS_h264_4x4 + floor(qp/6)))] + * for 'a' lies in the range 0-0.5. + * Here qp is the quantization parameter used to quantize the mb. + * + * input : qp/6 + * output : round factor. + * + * @remarks The round factor is constructed by setting a = 1/3 + * + * round factor constructed by setting a = 1/3 + { + 10922, 21845, 43690, 87381, + 174762, 349525, 699050, 1398101, + 2796202, + } + * + * round factor constructed by setting a = 0.49 + *{ + 16056, 32112, 64225, + 128450, 256901, 513802, + 1027604, 2055208, 4110417, + }; + + * round factor constructed by setting a = 0.5 + 16384, 32768, 65536, + 131072, 262144, 524288, + 1048576, 2097152, 4194304, + + ****************************************************************************** + */ +const UWORD32 gu4_forward_quant_round_factor_4x4[9] = +{ + 10922, 21845, 43690, 87381, + 174762, 349525, 699050, 1398101, + 2796202, +}; + + + +/** + ****************************************************************************** + * @brief Threshold Table. Quantizing the given DCT coefficient is done only if + * it exceeds the threshold value presented in this table. + * + * input : qp/6, qp%6, index location (i,j) + * output : Threshold constant. + * + * @remarks 16 constants for each index position of the subblock and 6 for each + * qp%6 in the range 0-5 inclusive and 9 for each qp/6 in the range 0-51. + ****************************************************************************** + */ +const UWORD16 gu2_forward_quant_threshold_4x4[96] = +{ + 426, 693, 426, 693, + 693, 1066, 693, 1066, + 426, 693, 426, 693, + 693, 1066, 693, 1066, + + 469, 746, 469, 746, + 746, 1200, 746, 1200, + 469, 746, 469, 746, + 746, 1200, 746, 1200, + + 554, 853, 554, 853, + 853, 1333, 853, 1333, + 554, 853, 554, 853, + 853, 1333, 853, 1333, + + 597, 960, 597, 960, + 960, 1533, 960, 1533, + 597, 960, 597, 960, + 960, 1533, 960, 1533, + + 682, 1066, 682, 1066, + 1066, 1666, 1066, 1666, + 682, 1066, 682, 1066, + 1066, 1666, 1066, 1666, + + 767, 1226, 767, 1226, + 1226, 1933, 1226, 1933, + 767, 1226, 767, 1226, + 1226, 1933, 1226, 1933, +}; + +/** + ****************************************************************************** + * @brief Scale Table for quantizing 8x8 subblock. To quantize a given 8x8 DCT + * transformed block, the coefficient at index location (i,j) is scaled by one of + * the constants in this table and right shift the result by (QP_BITS_h264_8x8 + + * floor(qp/6)), here qp is the quantization parameter used to quantize the mb. + * + * input : qp%6, index location (i,j) + * output : scale constant. + * + * @remarks 64 constants for each index position of the subblock and 6 for each + * qp%6 in the range 0-5 inclusive. + ****************************************************************************** + */ +const UWORD16 gu2_quant_scale_matrix_8x8 [384] = +{ + 13107, 12222, 16777, 12222, 13107, 12222, 16777, 12222, + 12222, 11428, 15481, 11428, 12222, 11428, 15481, 11428, + 16777, 15481, 20972, 15481, 16777, 15481, 20972, 15481, + 12222, 11428, 15481, 11428, 12222, 11428, 15481, 11428, + 13107, 12222, 16777, 12222, 13107, 12222, 16777, 12222, + 12222, 11428, 15481, 11428, 12222, 11428, 15481, 11428, + 16777, 15481, 20972, 15481, 16777, 15481, 20972, 15481, + 12222, 11428, 15481, 11428, 12222, 11428, 15481, 11428, + + 11916, 11058, 14980, 11058, 11916, 11058, 14980, 11058, + 11058, 10826, 14290, 10826, 11058, 10826, 14290, 10826, + 14980, 14290, 19174, 14290, 14980, 14290, 19174, 14290, + 11058, 10826, 14290, 10826, 11058, 10826, 14290, 10826, + 11916, 11058, 14980, 11058, 11916, 11058, 14980, 11058, + 11058, 10826, 14290, 10826, 11058, 10826, 14290, 10826, + 14980, 14290, 19174, 14290, 14980, 14290, 19174, 14290, + 11058, 10826, 14290, 10826, 11058, 10826, 14290, 10826, + + 10082, 9675, 12710, 9675, 10082, 9675, 12710, 9675, + 9675, 8943, 11985, 8943, 9675, 8943, 11985, 8943, + 12710, 11985, 15978, 11985, 12710, 11985, 15978, 11985, + 9675, 8943, 11985, 8943, 9675, 8943, 11985, 8943, + 10082, 9675, 12710, 9675, 10082, 9675, 12710, 9675, + 9675, 8943, 11985, 8943, 9675, 8943, 11985, 8943, + 12710, 11985, 15978, 11985, 12710, 11985, 15978, 11985, + 9675, 8943, 11985, 8943, 9675, 8943, 11985, 8943, + + 9362, 8931, 11984, 8931, 9362, 8931, 11984, 8931, + 8931, 8228, 11259, 8228, 8931, 8228, 11259, 8228, + 11984, 11259, 14913, 11259, 11984, 11259, 14913, 11259, + 8931, 8228, 11259, 8228, 8931, 8228, 11259, 8228, + 9362, 8931, 11984, 8931, 9362, 8931, 11984, 8931, + 8931, 8228, 11259, 8228, 8931, 8228, 11259, 8228, + 11984, 11259, 14913, 11259, 11984, 11259, 14913, 11259, + 8931, 8228, 11259, 8228, 8931, 8228, 11259, 8228, + + 8192, 7740, 10486, 7740, 8192, 7740, 10486, 7740, + 7740, 7346, 9777, 7346, 7740, 7346, 9777, 7346, + 10486, 9777, 13159, 9777, 10486, 9777, 13159, 9777, + 7740, 7346, 9777, 7346, 7740, 7346, 9777, 7346, + 8192, 7740, 10486, 7740, 8192, 7740, 10486, 7740, + 7740, 7346, 9777, 7346, 7740, 7346, 9777, 7346, + 10486, 9777, 13159, 9777, 10486, 9777, 13159, 9777, + 7740, 7346, 9777, 7346, 7740, 7346, 9777, 7346, + + 7282, 6830, 9118, 6830, 7282, 6830, 9118, 6830, + 6830, 6428, 8640, 6428, 6830, 6428, 8640, 6428, + 9118, 8640, 11570, 8640, 9118, 8640, 11570, 8640, + 6830, 6428, 8640, 6428, 6830, 6428, 8640, 6428, + 7282, 6830, 9118, 6830, 7282, 6830, 9118, 6830, + 6830, 6428, 8640, 6428, 6830, 6428, 8640, 6428, + 9118, 8640, 11570, 8640, 9118, 8640, 11570, 8640, + 6830, 6428, 8640, 6428, 6830, 6428, 8640, 6428, + +}; + + +/** + ****************************************************************************** + * @brief Specification of QPc as a function of qPi + * + * input : qp luma + * output : qp chroma. + * + * @remarks Refer Table 8-15 of h264 specification. + ****************************************************************************** + */ +const UWORD8 gu1_qpc_fqpi[52] = +{ + 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 29, 30, + 31, 32, 32, 33, 34, 34, 35, 35, + 36, 36, 37, 37, 37, 38, 38, 38, + 39, 39, 39, 39, +}; diff --git a/dependencies/ih264d/common/ih264_trans_data.h b/dependencies/ih264d/common/ih264_trans_data.h new file mode 100644 index 00000000..dc77ae74 --- /dev/null +++ b/dependencies/ih264d/common/ih264_trans_data.h @@ -0,0 +1,125 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** + ******************************************************************************* + * @file + * ih264_trans_data.h + * + * @brief + * Contains declaration of global variables for H264 transform , qnat and inverse quant + * + * @author + * Ittiam + * + * @remarks + * + ******************************************************************************* + */ +#ifndef IH264_GLOBAL_DATA_H_ +#define IH264_GLOBAL_DATA_H_ + +/*****************************************************************************/ +/* Extern global declarations */ +/*****************************************************************************/ + +/* Scaling matrices for h264 quantization */ +extern const UWORD16 g_scal_coff_h264_4x4[16]; +extern const UWORD16 g_scal_coff_h264_8x8[16]; + + +/** + ****************************************************************************** + * @brief Scale Table for quantizing 4x4 subblock. To quantize a given 4x4 DCT + * transformed block, the coefficient at index location (i,j) is scaled by one of + * the constants in this table and right shift the result by (QP_BITS_h264_4x4 + + * floor(qp/6)), here qp is the quantization parameter used to quantize the mb. + * + * input : qp%6, index location (i,j) + * output : scale constant. + * + * @remarks 16 constants for each index position of the subblock and 6 for each + * qp%6 in the range 0-5 inclusive. + ****************************************************************************** + */ +extern const UWORD16 gu2_quant_scale_matrix_4x4[96]; + +/** + ****************************************************************************** + * @brief Round Factor for quantizing subblock. While quantizing a given 4x4 DCT + * transformed block, the coefficient at index location (i,j) is scaled by one of + * the constants in the table gu2_forward_quant_scalar_4x4 and then right shift + * the result by (QP_BITS_h264_4x4 + floor(qp/6)). + * Before right shifting a round factor is added. + * The round factor can be any value [a * (1 << (QP_BITS_h264_4x4 + floor(qp/6)))] + * for 'a' lies in the range 0-0.5. + * Here qp is the quantization parameter used to quantize the mb. + * + * input : qp/6 + * output : round factor. + * + * @remarks The round factor is constructed by setting a = 1/3 + ****************************************************************************** + */ +extern const UWORD32 gu4_forward_quant_round_factor_4x4[9]; + +/** + ****************************************************************************** + * @brief Threshold Table. Quantizing the given DCT coefficient is done only if + * it exceeds the threshold value presented in this table. + * + * input : qp/6, qp%6, index location (i,j) + * output : Threshold constant. + * + * @remarks 16 constants for each index position of the subblock and 6 for each + * qp%6 in the range 0-5 inclusive and 9 for each qp/6 in the range 0-51. + ****************************************************************************** + */ +extern const UWORD16 gu2_forward_quant_threshold_4x4[96]; + +/** + ****************************************************************************** + * @brief Scale Table for quantizing 8x8 subblock. To quantize a given 8x8 DCT + * transformed block, the coefficient at index location (i,j) is scaled by one of + * the constants in this table and right shift the result by (QP_BITS_h264_8x8 + + * floor(qp/6)), here qp is the quantization parameter used to quantize the mb. + * + * input : qp%6, index location (i,j) + * output : scale constant. + * + * @remarks 64 constants for each index position of the subblock and 6 for each + * qp%6 in the range 0-5 inclusive. + ****************************************************************************** + */ +extern const UWORD16 gu2_quant_scale_matrix_8x8 [384]; + +/** + ****************************************************************************** + * @brief Specification of QPc as a function of qPi + * + * input : qp luma + * output : qp chroma. + * + * @remarks Refer Table 8-15 of h264 specification. + ****************************************************************************** + */ +extern const UWORD8 gu1_qpc_fqpi[52]; + + +#endif /* IH264_GLOBAL_DATA_H_ */ diff --git a/dependencies/ih264d/common/ih264_trans_macros.h b/dependencies/ih264d/common/ih264_trans_macros.h new file mode 100644 index 00000000..f114d0eb --- /dev/null +++ b/dependencies/ih264d/common/ih264_trans_macros.h @@ -0,0 +1,124 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** +******************************************************************************* +* @file +* ih264_trans_macros.h +* +* @brief +* The file contains definitions of macros that perform forward and inverse +* quantization +* +* @author +* Ittiam +* +* @remark +* None +* +******************************************************************************* +*/ + +#ifndef IH264_TRANS_MACROS_H_ +#define IH264_TRANS_MACROS_H_ + +/*****************************************************************************/ +/* Function Macros */ +/*****************************************************************************/ + +/** +****************************************************************************** + * @brief Macro to perform forward quantization. + * @description The value to be quantized is first compared with a threshold. + * If the value is less than the threshold, the quantization value is returned + * as zero else the value is quantized traditionally as per the rules of + * h264 specification +****************************************************************************** + */ +#define FWD_QUANT(i4_value, u4_abs_value, i4_sign, threshold, scale, rndfactor, qbits, u4_nnz) \ + {\ + if (i4_value < 0)\ + {\ + u4_abs_value = -i4_value;\ + i4_sign = -1;\ + }\ + else\ + {\ + u4_abs_value = i4_value;\ + i4_sign = 1;\ + }\ + if (u4_abs_value < threshold)\ + {\ + i4_value = 0;\ + }\ + else\ + {\ + u4_abs_value *= scale;\ + u4_abs_value += rndfactor;\ + u4_abs_value >>= qbits;\ + i4_value = u4_abs_value * i4_sign;\ + if (i4_value)\ + {\ + u4_nnz++;\ + }\ + }\ + } + +/** +****************************************************************************** + * @brief Macro to perform inverse quantization. + * @remarks The value can also be de-quantized as + * if (u4_qp_div_6 < 4) + * { + * i4_value = (quant_scale * weight_scale * i4_value + (1 << (3-u4_qp_div_6))) + * i4_value >>= (4 - u4_qp_div_6) + * } + * else + * { + * i4_value = (quant_scale * weight_scale * i4_value) << (u4_qp_div_6 -4) + * } +****************************************************************************** + */ +#define INV_QUANT(i4_value, quant_scale, weight_scale, u4_qp_div_6, rndfactor, qbits)\ + {\ + i4_value *= quant_scale;\ + i4_value *= weight_scale;\ + i4_value += rndfactor;\ + i4_value <<= u4_qp_div_6;\ + i4_value >>= qbits;\ + } + +#define QUANT_H264(x,y,w,z,shft) (shft = ABS(x),\ + shft *= y,\ + shft += z,\ + shft = shft>>w,\ + shft = SIGNXY(shft,x)) + +#define IQUANT_H264(x,y,wscal,w,shft) (shft = x, \ + shft *=y, \ + shft *=wscal, \ + shft = shft<<w) + +#define IQUANT_lev_H264(x,y,wscal,add_f,w,shft) (shft = x, \ + shft *=y, \ + shft *=wscal, \ + shft+= add_f, \ + shft = shft>>w) + +#endif /* IH264_TRANS_MACROS_H_ */ diff --git a/dependencies/ih264d/common/ih264_trans_quant_itrans_iquant.h b/dependencies/ih264d/common/ih264_trans_quant_itrans_iquant.h new file mode 100644 index 00000000..83551aad --- /dev/null +++ b/dependencies/ih264d/common/ih264_trans_quant_itrans_iquant.h @@ -0,0 +1,232 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** + ******************************************************************************* + * @file + * ih264_trans_quant.h + * + * @brief + * Contains declarations for forward and inverse transform paths for H264 + * + * @author + * Ittiam + * + * @remarks + * + ******************************************************************************* + */ + +#ifndef IH264_TRANS_QUANT_H_ +#define IH264_TRANS_QUANT_H_ + +/*****************************************************************************/ +/* Extern Function Declarations */ +/*****************************************************************************/ + + +typedef void ih264_resi_trans_dctrans_quant_ft(UWORD8*pu1_src, + UWORD8 *pu1_pred, + WORD16 *pi2_out, + WORD32 src_strd, + WORD32 pred_strd, + WORD32 dst_strd, + const UWORD16 *pu2_scale_mat, + const UWORD16 *pu2_thresh_mat, + UWORD32 u4_qbit, + UWORD32 u4_round_fact, + UWORD8 *pu1_nnz); + +typedef void ih264_idctrans_iquant_itrans_recon_ft(WORD16 *pi2_src, + UWORD8 *pu1_pred, + UWORD8 *pu1_out, + WORD32 src_strd, + WORD32 pred_strd, + WORD32 out_strd, + const UWORD16 *pu2_iscale_mat, + const UWORD16 *pu2_weigh_mat, + UWORD32 qp_div, + UWORD32 pi4_cntrl, + WORD32 *pi4_tmp); + + +/*Function prototype declarations*/ +typedef void ih264_resi_trans_quant_ft(UWORD8*pu1_src, + UWORD8 *pu1_pred, + WORD16 *pi2_out, + WORD32 src_strd, + WORD32 pred_strd, + const UWORD16 *pu2_scale_mat, + const UWORD16 *pu2_thresh_mat, + UWORD32 u4_qbit, + UWORD32 u4_round_fact, + UWORD8 *pu1_nnz, + WORD16 *pi2_alt_dc_addr); + +typedef void ih264_luma_16x16_resi_trans_dctrans_quant_ft(UWORD8 *pu1_src, + UWORD8 *pu1_pred, + WORD16 *pi2_out, + WORD32 src_strd, + WORD32 pred_strd, + WORD32 dst_strd, + const UWORD16 *pu2_scale_matrix, + const UWORD16 *pu2_threshold_matrix, + UWORD32 u4_qbits, + UWORD32 u4_round_factor, + UWORD8 *pu1_nnz, + UWORD32 u4_dc_flag); + +typedef void ih264_chroma_8x8_resi_trans_dctrans_quant_ft(UWORD8 *pu1_src, + UWORD8 *pu1_pred, + WORD16 *pi2_out, + WORD32 src_strd, + WORD32 pred_strd, + WORD32 dst_strd, + const UWORD16 *pu2_scale_matrix, + const UWORD16 *pu2_threshold_matrix, + UWORD32 u4_qbits, + UWORD32 u4_round_factor, + UWORD8 *pu1_nnz); + +typedef void ih264_iquant_itrans_recon_ft(WORD16 *pi2_src, + UWORD8 *pu1_pred, + UWORD8 *pu1_out, + WORD32 pred_strd, + WORD32 out_strd, + const UWORD16 *pu2_iscale_mat, + const UWORD16 *pu2_weigh_mat, + UWORD32 qp_div, + WORD16 *pi2_tmp, + WORD32 iq_start_idx, + WORD16 *pi2_dc_ld_addr); + + +typedef void ih264_iquant_itrans_recon_chroma_ft(WORD16 *pi2_src, + UWORD8 *pu1_pred, + UWORD8 *pu1_out, + WORD32 pred_strd, + WORD32 out_strd, + const UWORD16 *pu2_iscal_mat, + const UWORD16 *pu2_weigh_mat, + UWORD32 u4_qp_div_6, + WORD16 *pi2_tmp, + WORD16 *pi2_dc_src); + + +typedef void ih264_luma_16x16_idctrans_iquant_itrans_recon_ft(WORD16 *pi2_src, + UWORD8 *pu1_pred, + UWORD8 *pu1_out, + WORD32 src_strd, + WORD32 pred_strd, + WORD32 out_strd, + const UWORD16 *pu2_iscale_mat, + const UWORD16 *pu2_weigh_mat, + UWORD32 qp_div, + UWORD32 pi4_cntrl, + UWORD32 u4_dc_trans_flag, + WORD32 *pi4_tmp); + +typedef void ih264_chroma_8x8_idctrans_iquant_itrans_recon_ft(WORD16 *pi2_src, + UWORD8 *pu1_pred, + UWORD8 *pu1_out, + WORD32 src_strd, + WORD32 pred_strd, + WORD32 out_strd, + const UWORD16 *pu2_iscale_mat, + const UWORD16 *pu2_weigh_mat, + UWORD32 qp_div, + UWORD32 pi4_cntrl, + WORD32 *pi4_tmp); + +typedef void ih264_ihadamard_scaling_ft(WORD16* pi2_src, + WORD16* pi2_out, + const UWORD16 *pu2_iscal_mat, + const UWORD16 *pu2_weigh_mat, + UWORD32 u4_qp_div_6, + WORD32* pi4_tmp); + +typedef void ih264_hadamard_quant_ft(WORD16 *pi2_src, WORD16 *pi2_dst, + const UWORD16 *pu2_scale_matrix, + const UWORD16 *pu2_threshold_matrix, UWORD32 u4_qbits, + UWORD32 u4_round_factor,UWORD8 *pu1_nnz); + +ih264_resi_trans_quant_ft ih264_resi_trans_quant_4x4; +ih264_resi_trans_quant_ft ih264_resi_trans_quant_chroma_4x4; +ih264_resi_trans_quant_ft ih264_resi_trans_quant_8x8; +ih264_iquant_itrans_recon_ft ih264_iquant_itrans_recon_4x4; +ih264_iquant_itrans_recon_ft ih264_iquant_itrans_recon_8x8; +ih264_iquant_itrans_recon_ft ih264_iquant_itrans_recon_4x4_dc; +ih264_iquant_itrans_recon_ft ih264_iquant_itrans_recon_8x8_dc; +ih264_iquant_itrans_recon_chroma_ft ih264_iquant_itrans_recon_chroma_4x4; +ih264_iquant_itrans_recon_chroma_ft ih264_iquant_itrans_recon_chroma_4x4_dc; +ih264_ihadamard_scaling_ft ih264_ihadamard_scaling_4x4; +ih264_ihadamard_scaling_ft ih264_ihadamard_scaling_2x2_uv; +ih264_hadamard_quant_ft ih264_hadamard_quant_4x4; +ih264_hadamard_quant_ft ih264_hadamard_quant_2x2_uv; + +/*A9 Declarations*/ +ih264_resi_trans_quant_ft ih264_resi_trans_quant_4x4_a9; +ih264_resi_trans_quant_ft ih264_resi_trans_quant_chroma_4x4_a9; +ih264_iquant_itrans_recon_ft ih264_iquant_itrans_recon_4x4_a9; +ih264_iquant_itrans_recon_ft ih264_iquant_itrans_recon_8x8_a9; +ih264_iquant_itrans_recon_ft ih264_iquant_itrans_recon_4x4_dc_a9; +ih264_iquant_itrans_recon_ft ih264_iquant_itrans_recon_8x8_dc_a9; +ih264_iquant_itrans_recon_chroma_ft ih264_iquant_itrans_recon_chroma_4x4_a9; +ih264_iquant_itrans_recon_chroma_ft ih264_iquant_itrans_recon_chroma_4x4_dc_a9; +ih264_luma_16x16_resi_trans_dctrans_quant_ft ih264_luma_16x16_resi_trans_dctrans_quant_a9; +ih264_chroma_8x8_resi_trans_dctrans_quant_ft ih264_chroma_8x8_resi_trans_dctrans_quant_a9; +ih264_luma_16x16_idctrans_iquant_itrans_recon_ft ih264_luma_16x16_idctrans_iquant_itrans_recon_a9; +ih264_chroma_8x8_idctrans_iquant_itrans_recon_ft ih264_chroma_8x8_idctrans_iquant_itrans_recon_a9; +ih264_ihadamard_scaling_ft ih264_ihadamard_scaling_4x4_a9; +ih264_ihadamard_scaling_ft ih264_ihadamard_scaling_2x2_uv_a9; +ih264_hadamard_quant_ft ih264_hadamard_quant_4x4_a9; +ih264_hadamard_quant_ft ih264_hadamard_quant_2x2_uv_a9; + +/*Av8 Declarations*/ +ih264_resi_trans_quant_ft ih264_resi_trans_quant_4x4_av8; +ih264_resi_trans_quant_ft ih264_resi_trans_quant_chroma_4x4_av8; +ih264_iquant_itrans_recon_ft ih264_iquant_itrans_recon_4x4_av8; +ih264_iquant_itrans_recon_ft ih264_iquant_itrans_recon_8x8_av8; +ih264_iquant_itrans_recon_ft ih264_iquant_itrans_recon_4x4_dc_av8; +ih264_iquant_itrans_recon_ft ih264_iquant_itrans_recon_8x8_dc_av8; +ih264_iquant_itrans_recon_chroma_ft ih264_iquant_itrans_recon_chroma_4x4_av8; +ih264_iquant_itrans_recon_chroma_ft ih264_iquant_itrans_recon_chroma_4x4_dc_av8; +ih264_ihadamard_scaling_ft ih264_ihadamard_scaling_4x4_av8; +ih264_ihadamard_scaling_ft ih264_ihadamard_scaling_2x2_uv_av8; +ih264_hadamard_quant_ft ih264_hadamard_quant_4x4_av8; +ih264_hadamard_quant_ft ih264_hadamard_quant_2x2_uv_av8; + +/*SSSE3 Declarations*/ +ih264_iquant_itrans_recon_ft ih264_iquant_itrans_recon_4x4_ssse3; +ih264_iquant_itrans_recon_ft ih264_iquant_itrans_recon_8x8_ssse3; +ih264_iquant_itrans_recon_ft ih264_iquant_itrans_recon_4x4_dc_ssse3; +ih264_iquant_itrans_recon_ft ih264_iquant_itrans_recon_8x8_dc_ssse3; +ih264_iquant_itrans_recon_chroma_ft ih264_iquant_itrans_recon_chroma_4x4_dc_ssse3; +ih264_ihadamard_scaling_ft ih264_ihadamard_scaling_4x4_ssse3; +ih264_ihadamard_scaling_ft ih264_ihadamard_scaling_2x2_uv_ssse3; +/*SSSE42 Declarations*/ +ih264_resi_trans_quant_ft ih264_resi_trans_quant_4x4_sse42; +ih264_resi_trans_quant_ft ih264_resi_trans_quant_chroma_4x4_sse42; +ih264_iquant_itrans_recon_ft ih264_iquant_itrans_recon_4x4_sse42; +ih264_iquant_itrans_recon_chroma_ft ih264_iquant_itrans_recon_chroma_4x4_sse42; +ih264_ihadamard_scaling_ft ih264_ihadamard_scaling_4x4_sse42; +ih264_hadamard_quant_ft ih264_hadamard_quant_4x4_sse42; +ih264_hadamard_quant_ft ih264_hadamard_quant_2x2_uv_sse42; + +#endif /* IH264_TRANS_QUANT_H_ */ diff --git a/dependencies/ih264d/common/ih264_typedefs.h b/dependencies/ih264d/common/ih264_typedefs.h new file mode 100644 index 00000000..29138c6b --- /dev/null +++ b/dependencies/ih264d/common/ih264_typedefs.h @@ -0,0 +1,64 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** +******************************************************************************* +* @file +* ih264_typedefs.h +* +* @brief +* Type definitions used in the code +* +* @author +* Ittiam +* +* @remarks +* None +* +******************************************************************************* +*/ + +#ifndef _IH264_TYPEDEFS_H_ +#define _IH264_TYPEDEFS_H_ + +#include <stdint.h> +/*****************************************************************************/ +/* Unsigned data types */ +/*****************************************************************************/ +typedef uint8_t UWORD8; +typedef uint16_t UWORD16; +typedef uint32_t UWORD32; +typedef uint64_t UWORD64; + + +/*****************************************************************************/ +/* Signed data types */ +/*****************************************************************************/ +typedef int8_t WORD8; +typedef int16_t WORD16; +typedef int32_t WORD32; +typedef int64_t WORD64; + +/*****************************************************************************/ +/* Miscellaneous data types */ +/*****************************************************************************/ +typedef char CHAR; +typedef double DOUBLE; + +#endif /* _IH264_TYPEDEFS_H_ */ diff --git a/dependencies/ih264d/common/ih264_weighted_pred.c b/dependencies/ih264d/common/ih264_weighted_pred.c new file mode 100644 index 00000000..d5d73f2f --- /dev/null +++ b/dependencies/ih264d/common/ih264_weighted_pred.c @@ -0,0 +1,495 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/*****************************************************************************/ +/* */ +/* File Name : ih264_weighted_pred.c */ +/* */ +/* Description : Contains function definitions for weighted */ +/* prediction functions */ +/* */ +/* List of Functions : ih264_default_weighted_pred_luma() */ +/* ih264_default_weighted_pred_chroma() */ +/* ih264_weighted_pred_luma() */ +/* ih264_weighted_pred_chroma() */ +/* ih264_weighted_bipred_luma() */ +/* ih264_weighted_bipred_chroma() */ +/* */ +/* Issues / Problems : None */ +/* */ +/* Revision History : */ +/* */ +/* DD MM YYYY Author(s) Changes */ +/* 07 01 2015 Kaushik Initial version */ +/* Senthoor */ +/* */ +/*****************************************************************************/ +/*****************************************************************************/ +/* File Includes */ +/*****************************************************************************/ + +/* User include files */ +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264_weighted_pred.h" + +/*****************************************************************************/ +/* Function definitions . */ +/*****************************************************************************/ +/*****************************************************************************/ +/* */ +/* Function Name : ih264_default_weighted_pred_luma */ +/* */ +/* Description : This function performs the default weighted prediction */ +/* as described in sec 8.4.2.3.1 titled "Default weighted */ +/* sample prediction process" for luma. The function gets */ +/* two ht x wd blocks, calculates their rounded-average and */ +/* stores it in the destination block. (ht,wd) can be */ +/* (4,4), (8,4), (4,8), (8,8), (16,8), (8,16) or (16,16). */ +/* */ +/* Inputs : puc_src1 - Pointer to source 1 */ +/* puc_src2 - Pointer to source 2 */ +/* puc_dst - Pointer to destination */ +/* src_strd1 - stride for source 1 */ +/* src_strd1 - stride for source 2 */ +/* dst_strd - stride for destination */ +/* ht - height of the block */ +/* wd - width of the block */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes */ +/* 07 01 2015 Kaushik Initial Version */ +/* Senthoor */ +/* */ +/*****************************************************************************/ +void ih264_default_weighted_pred_luma(UWORD8 *pu1_src1, + UWORD8 *pu1_src2, + UWORD8 *pu1_dst, + WORD32 src_strd1, + WORD32 src_strd2, + WORD32 dst_strd, + WORD32 ht, + WORD32 wd) +{ + WORD32 i, j; + + src_strd1 -= wd; + src_strd2 -= wd; + dst_strd -= wd; + + for(i = 0; i < ht; i++) + { + for(j = 0; j < wd; j++, pu1_src1++, pu1_src2++, pu1_dst++) + *pu1_dst = (*pu1_src1 + *pu1_src2 + 1) >> 1; + + pu1_src1 += src_strd1; + pu1_src2 += src_strd2; + pu1_dst += dst_strd; + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_default_weighted_pred_chroma */ +/* */ +/* Description : This function performs the default weighted prediction */ +/* as described in sec 8.4.2.3.1 titled "Default weighted */ +/* sample prediction process" for chroma. The function gets */ +/* two ht x wd blocks, calculates their rounded-average and */ +/* stores it in the destination block. (ht,wd) can be */ +/* (2,2), (4,2) , (2,4), (4,4), (8,4), (4,8) or (8,8). */ +/* */ +/* Inputs : puc_src1 - Pointer to source 1 */ +/* puc_src2 - Pointer to source 2 */ +/* puc_dst - Pointer to destination */ +/* src_strd1 - stride for source 1 */ +/* src_strd1 - stride for source 2 */ +/* dst_strd - stride for destination */ +/* ht - height of the block */ +/* wd - width of the block */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes */ +/* 07 01 2015 Kaushik Initial Version */ +/* Senthoor */ +/* */ +/*****************************************************************************/ +void ih264_default_weighted_pred_chroma(UWORD8 *pu1_src1, + UWORD8 *pu1_src2, + UWORD8 *pu1_dst, + WORD32 src_strd1, + WORD32 src_strd2, + WORD32 dst_strd, + WORD32 ht, + WORD32 wd) +{ + WORD32 i, j; + + wd = wd << 1; + + src_strd1 -= wd; + src_strd2 -= wd; + dst_strd -= wd; + + for(i = 0; i < ht; i++) + { + for(j = 0; j < wd; j++, pu1_src1++, pu1_src2++, pu1_dst++) + *pu1_dst = (*pu1_src1 + *pu1_src2 + 1) >> 1; + + pu1_src1 += src_strd1; + pu1_src2 += src_strd2; + pu1_dst += dst_strd; + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_weighted_pred_luma */ +/* */ +/* Description : This function performs the weighted prediction as */ +/* described in sec 8.4.2.3.2 titled "Weighted sample */ +/* prediction process" for luma. The function gets one */ +/* ht x wd block, weights it, rounds it off, offsets it, */ +/* saturates it to unsigned 8-bit and stores it in the */ +/* destination block. (ht,wd) can be (4,4), (8,4), (4,8), */ +/* (8,8), (16,8), (8,16) or (16,16). */ +/* */ +/* Inputs : puc_src - Pointer to source */ +/* puc_dst - Pointer to destination */ +/* src_strd - stride for source */ +/* dst_strd - stride for destination */ +/* log_wd - number of bits to be rounded off */ +/* wt - weight value */ +/* ofst - offset value */ +/* ht - height of the block */ +/* wd - width of the block */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes */ +/* 07 01 2015 Kaushik Initial Version */ +/* Senthoor */ +/* */ +/*****************************************************************************/ +void ih264_weighted_pred_luma(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 log_wd, + WORD32 wt, + WORD32 ofst, + WORD32 ht, + WORD32 wd) +{ + WORD32 i, j; + + wt = (WORD16)(wt & 0xffff); + ofst = (WORD8)(ofst & 0xff); + + src_strd -= wd; + dst_strd -= wd; + + if(log_wd >= 1) + { + WORD32 i_ofst = (1 << (log_wd - 1)) + (ofst << log_wd); + for(i = 0; i < ht; i++) + { + for(j = 0; j < wd; j++, pu1_src++, pu1_dst++) + *pu1_dst = CLIP_U8((wt * (*pu1_src) + i_ofst) >> log_wd); + + pu1_src += src_strd; + pu1_dst += dst_strd; + } + } + else + { + for(i = 0; i < ht; i++) + { + for(j = 0; j < wd; j++, pu1_src++, pu1_dst++) + *pu1_dst = CLIP_U8(wt * (*pu1_src) + ofst); + + pu1_src += src_strd; + pu1_dst += dst_strd; + } + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_weighted_pred_chroma */ +/* */ +/* Description : This function performs the weighted prediction as */ +/* described in sec 8.4.2.3.2 titled "Weighted sample */ +/* prediction process" for chroma. The function gets one */ +/* ht x wd block, weights it, rounds it off, offsets it, */ +/* saturates it to unsigned 8-bit and stores it in the */ +/* destination block. (ht,wd) can be (2,2), (4,2), (2,4), */ +/* (4,4), (8,4), (4,8) or (8,8). */ +/* */ +/* Inputs : puc_src - Pointer to source */ +/* puc_dst - Pointer to destination */ +/* src_strd - stride for source */ +/* dst_strd - stride for destination */ +/* log_wd - number of bits to be rounded off */ +/* wt - weight values for u and v */ +/* ofst - offset values for u and v */ +/* ht - height of the block */ +/* wd - width of the block */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes */ +/* 07 01 2015 Kaushik Initial Version */ +/* Senthoor */ +/* */ +/*****************************************************************************/ +void ih264_weighted_pred_chroma(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 log_wd, + WORD32 wt, + WORD32 ofst, + WORD32 ht, + WORD32 wd) +{ + WORD32 i, j; + WORD32 wt_u, wt_v; + WORD32 ofst_u, ofst_v; + + wt_u = (WORD16)(wt & 0xffff); + wt_v = (WORD16)(wt >> 16); + + ofst_u = (WORD8)(ofst & 0xff); + ofst_v = (WORD8)(ofst >> 8); + + src_strd -= wd << 1; + dst_strd -= wd << 1; + + if(log_wd >= 1) + { + ofst_u = (1 << (log_wd - 1)) + (ofst_u << log_wd); + ofst_v = (1 << (log_wd - 1)) + (ofst_v << log_wd); + + for(i = 0; i < ht; i++) + { + for(j = 0; j < wd; j++, pu1_src++, pu1_dst++) + { + *pu1_dst = CLIP_U8((wt_u * (*pu1_src) + ofst_u) >> log_wd); + pu1_src++; + pu1_dst++; + *pu1_dst = CLIP_U8((wt_v * (*pu1_src) + ofst_v) >> log_wd); + } + pu1_src += src_strd; + pu1_dst += dst_strd; + } + } + else + { + for(i = 0; i < ht; i++) + { + for(j = 0; j < wd; j++, pu1_src++, pu1_dst++) + { + *pu1_dst = CLIP_U8(wt_u * (*pu1_src) + ofst_u); + pu1_src++; + pu1_dst++; + *pu1_dst = CLIP_U8(wt_v * (*pu1_src) + ofst_v); + } + pu1_src += src_strd; + pu1_dst += dst_strd; + } + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_weighted_bi_pred_luma */ +/* */ +/* Description : This function performs the weighted biprediction as */ +/* described in sec 8.4.2.3.2 titled "Weighted sample */ +/* prediction process" for luma. The function gets two */ +/* ht x wd blocks, weights them, adds them, rounds off the */ +/* sum, offsets it, saturates it to unsigned 8-bit and */ +/* stores it in the destination block. (ht,wd) can be */ +/* (4,4), (8,4), (4,8), (8,8), (16,8), (8,16) or (16,16). */ +/* */ +/* Inputs : puc_src1 - Pointer to source 1 */ +/* puc_src2 - Pointer to source 2 */ +/* puc_dst - Pointer to destination */ +/* src_strd1 - stride for source 1 */ +/* src_strd2 - stride for source 2 */ +/* dst_strd2 - stride for destination */ +/* log_wd - number of bits to be rounded off */ +/* wt1 - weight value for source 1 */ +/* wt2 - weight value for source 2 */ +/* ofst1 - offset value for source 1 */ +/* ofst2 - offset value for source 2 */ +/* ht - height of the block */ +/* wd - width of the block */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes */ +/* 07 01 2015 Kaushik Initial Version */ +/* Senthoor */ +/* */ +/*****************************************************************************/ +void ih264_weighted_bi_pred_luma(UWORD8 *pu1_src1, + UWORD8 *pu1_src2, + UWORD8 *pu1_dst, + WORD32 src_strd1, + WORD32 src_strd2, + WORD32 dst_strd, + WORD32 log_wd, + WORD32 wt1, + WORD32 wt2, + WORD32 ofst1, + WORD32 ofst2, + WORD32 ht, + WORD32 wd) +{ + WORD32 i, j; + WORD32 shft, ofst; + + ofst1 = (WORD8)(ofst1 & 0xff); + ofst2 = (WORD8)(ofst2 & 0xff); + wt1 = (WORD16)(wt1 & 0xffff); + wt2 = (WORD16)(wt2 & 0xffff); + ofst = (ofst1 + ofst2 + 1) >> 1; + + shft = log_wd + 1; + ofst = (1 << log_wd) + (ofst << shft); + + src_strd1 -= wd; + src_strd2 -= wd; + dst_strd -= wd; + + for(i = 0; i < ht; i++) + { + for(j = 0; j < wd; j++, pu1_src1++, pu1_src2++, pu1_dst++) + *pu1_dst = CLIP_U8((wt1 * (*pu1_src1) + wt2 * (*pu1_src2) + ofst) >> shft); + + pu1_src1 += src_strd1; + pu1_src2 += src_strd2; + pu1_dst += dst_strd; + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_weighted_bi_pred_chroma */ +/* */ +/* Description : This function performs the weighted biprediction as */ +/* described in sec 8.4.2.3.2 titled "Weighted sample */ +/* prediction process" for chroma. The function gets two */ +/* ht x wd blocks, weights them, adds them, rounds off the */ +/* sum, offsets it, saturates it to unsigned 8-bit and */ +/* stores it in the destination block. (ht,wd) can be */ +/* (2,2), (4,2), (2,4), (4,4), (8,4), (4,8) or (8,8). */ +/* */ +/* Inputs : puc_src1 - Pointer to source 1 */ +/* puc_src2 - Pointer to source 2 */ +/* puc_dst - Pointer to destination */ +/* src_strd1 - stride for source 1 */ +/* src_strd2 - stride for source 2 */ +/* dst_strd2 - stride for destination */ +/* log_wd - number of bits to be rounded off */ +/* wt1 - weight values for u and v in source 1 */ +/* wt2 - weight values for u and v in source 2 */ +/* ofst1 - offset value for u and v in source 1 */ +/* ofst2 - offset value for u and v in source 2 */ +/* ht - height of the block */ +/* wd - width of the block */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes */ +/* 07 01 2015 Kaushik Initial Version */ +/* Senthoor */ +/* */ +/*****************************************************************************/ +void ih264_weighted_bi_pred_chroma(UWORD8 *pu1_src1, + UWORD8 *pu1_src2, + UWORD8 *pu1_dst, + WORD32 src_strd1, + WORD32 src_strd2, + WORD32 dst_strd, + WORD32 log_wd, + WORD32 wt1, + WORD32 wt2, + WORD32 ofst1, + WORD32 ofst2, + WORD32 ht, + WORD32 wd) +{ + WORD32 i, j; + WORD32 wt1_u, wt1_v, wt2_u, wt2_v; + WORD32 ofst1_u, ofst1_v, ofst2_u, ofst2_v; + WORD32 ofst_u, ofst_v; + WORD32 shft; + + ofst1_u = (WORD8)(ofst1 & 0xff); + ofst1_v = (WORD8)(ofst1 >> 8); + ofst2_u = (WORD8)(ofst2 & 0xff); + ofst2_v = (WORD8)(ofst2 >> 8); + wt1_u = (WORD16)(wt1 & 0xffff); + wt1_v = (WORD16)(wt1 >> 16); + wt2_u = (WORD16)(wt2 & 0xffff); + wt2_v = (WORD16)(wt2 >> 16); + ofst_u = (ofst1_u + ofst2_u + 1) >> 1; + ofst_v = (ofst1_v + ofst2_v + 1) >> 1; + + src_strd1 -= wd << 1; + src_strd2 -= wd << 1; + dst_strd -= wd << 1; + + shft = log_wd + 1; + ofst_u = (1 << log_wd) + (ofst_u << shft); + ofst_v = (1 << log_wd) + (ofst_v << shft); + + for(i = 0; i < ht; i++) + { + for(j = 0; j < wd; j++, pu1_src1++, pu1_src2++, pu1_dst++) + { + *pu1_dst = CLIP_U8((wt1_u * (*pu1_src1) + wt2_u * (*pu1_src2) + ofst_u) >> shft); + pu1_src1++; + pu1_src2++; + pu1_dst++; + *pu1_dst = CLIP_U8((wt1_v * (*pu1_src1) + wt2_v * (*pu1_src2) + ofst_v) >> shft); + } + pu1_src1 += src_strd1; + pu1_src2 += src_strd2; + pu1_dst += dst_strd; + } +} diff --git a/dependencies/ih264d/common/ih264_weighted_pred.h b/dependencies/ih264d/common/ih264_weighted_pred.h new file mode 100644 index 00000000..f9b93b0f --- /dev/null +++ b/dependencies/ih264d/common/ih264_weighted_pred.h @@ -0,0 +1,164 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ + +/** +******************************************************************************* +* @file +* ih264_weighted_pred.h +* +* @brief +* Declarations of functions used for weighted prediction +* +* @author +* Ittiam +* +* @par List of Functions: +* -ih264_default_weighted_pred_luma +* -ih264_default_weighted_pred_chroma +* -ih264_weighted_pred_luma +* -ih264_weighted_pred_chroma +* -ih264_weighted_bi_pred_luma +* -ih264_weighted_bi_pred_chroma +* -ih264_default_weighted_pred_luma_a9q +* -ih264_default_weighted_pred_chroma_a9q +* -ih264_weighted_pred_luma_a9q +* -ih264_weighted_pred_luma_a9q +* -ih264_weighted_bi_pred_luma_a9q +* -ih264_weighted_bi_pred_chroma_a9q +* -ih264_default_weighted_pred_luma_av8 +* -ih264_default_weighted_pred_chroma_av8 +* -ih264_weighted_pred_luma_av8 +* -ih264_weighted_pred_chroma_av8 +* -ih264_weighted_bi_pred_luma_av8 +* -ih264_weighted_bi_pred_chroma_av8 +* -ih264_default_weighted_pred_luma_sse42 +* -ih264_default_weighted_pred_chroma_sse42 +* -ih264_weighted_pred_luma_sse42 +* -ih264_weighted_pred_chroma_sse42 +* -ih264_weighted_bi_pred_luma_sse42 +* -ih264_weighted_bi_pred_chroma_sse42 +* +* +* @remarks +* None +* +******************************************************************************* +*/ + +#ifndef IH264_WEIGHTED_PRED_H_ +#define IH264_WEIGHTED_PRED_H_ + +/*****************************************************************************/ +/* Extern Function Declarations */ +/*****************************************************************************/ +typedef void ih264_default_weighted_pred_ft(UWORD8 *puc_src1, + UWORD8 *puc_src2, + UWORD8 *puc_dst, + WORD32 src_strd1, + WORD32 src_strd2, + WORD32 dst_strd, + WORD32 ht, + WORD32 wd); + +typedef void ih264_weighted_pred_ft(UWORD8 *puc_src, + UWORD8 *puc_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 log_wd, + WORD32 wt, + WORD32 ofst, + WORD32 ht, + WORD32 wd); + +typedef void ih264_weighted_bi_pred_ft(UWORD8 *puc_src1, + UWORD8 *puc_src2, + UWORD8 *puc_dst, + WORD32 src_strd1, + WORD32 src_strd2, + WORD32 dst_strd, + WORD32 log_wd, + WORD32 wt1, + WORD32 wt2, + WORD32 ofst1, + WORD32 ofst2, + WORD32 ht, + WORD32 wd); + +/* No NEON Declarations */ + +ih264_default_weighted_pred_ft ih264_default_weighted_pred_luma; + +ih264_default_weighted_pred_ft ih264_default_weighted_pred_chroma; + +ih264_weighted_pred_ft ih264_weighted_pred_luma; + +ih264_weighted_pred_ft ih264_weighted_pred_chroma; + +ih264_weighted_bi_pred_ft ih264_weighted_bi_pred_luma; + +ih264_weighted_bi_pred_ft ih264_weighted_bi_pred_chroma; + +/* A9 NEON Declarations */ + +ih264_default_weighted_pred_ft ih264_default_weighted_pred_luma_a9q; + +ih264_default_weighted_pred_ft ih264_default_weighted_pred_chroma_a9q; + +ih264_weighted_pred_ft ih264_weighted_pred_luma_a9q; + +ih264_weighted_pred_ft ih264_weighted_pred_chroma_a9q; + +ih264_weighted_bi_pred_ft ih264_weighted_bi_pred_luma_a9q; + +ih264_weighted_bi_pred_ft ih264_weighted_bi_pred_chroma_a9q; + + +/* AV8 NEON Declarations */ + +ih264_default_weighted_pred_ft ih264_default_weighted_pred_luma_av8; + +ih264_default_weighted_pred_ft ih264_default_weighted_pred_chroma_av8; + +ih264_weighted_pred_ft ih264_weighted_pred_luma_av8; + +ih264_weighted_pred_ft ih264_weighted_pred_chroma_av8; + +ih264_weighted_bi_pred_ft ih264_weighted_bi_pred_luma_av8; + +ih264_weighted_bi_pred_ft ih264_weighted_bi_pred_chroma_av8; + + +/* SSE42 Intrinsic Declarations */ + +ih264_default_weighted_pred_ft ih264_default_weighted_pred_luma_sse42; + +ih264_default_weighted_pred_ft ih264_default_weighted_pred_chroma_sse42; + +ih264_weighted_pred_ft ih264_weighted_pred_luma_sse42; + +ih264_weighted_pred_ft ih264_weighted_pred_chroma_sse42; + +ih264_weighted_bi_pred_ft ih264_weighted_bi_pred_luma_sse42; + +ih264_weighted_bi_pred_ft ih264_weighted_bi_pred_chroma_sse42; + +#endif /* IH264_WEIGHTED_PRED_H_ */ + +/** Nothing past this point */ diff --git a/dependencies/ih264d/common/ithread.c b/dependencies/ih264d/common/ithread.c new file mode 100644 index 00000000..d710e323 --- /dev/null +++ b/dependencies/ih264d/common/ithread.c @@ -0,0 +1,359 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore + * Modified for use with Cemu emulator project +*/ +/*****************************************************************************/ +/* */ +/* File Name : ithread.c */ +/* */ +/* Description : Contains abstraction for threads, mutex and semaphores*/ +/* */ +/* List of Functions : */ +/* */ +/* Issues / Problems : None */ +/* */ +/* Revision History : */ +/* */ +/* DD MM YYYY Author(s) Changes */ +/* 07 09 2012 Harish Initial Version */ +/*****************************************************************************/ +/*****************************************************************************/ +/* File Includes */ +/*****************************************************************************/ +#include <string.h> +#include "ih264_typedefs.h" + + + +#include "ithread.h" +#include <sys/types.h> + + +#define UNUSED(x) ((void)(x)) + +//#define PTHREAD_AFFINITY +//#define SYSCALL_AFFINITY + +#ifdef PTHREAD_AFFINITY +#define _GNU_SOURCE +#define __USE_GNU +#endif + +#ifdef _WIN32 + +#include <Windows.h> + +#else + +#include <pthread.h> +#include <sched.h> +#include <semaphore.h> +#include <unistd.h> + +#endif + +#ifdef PTHREAD_AFFINITY +#include <sys/prctl.h> +#endif + + +#ifdef _WIN32 + +UWORD32 ithread_get_handle_size(void) +{ + return sizeof(HANDLE); +} + +UWORD32 ithread_get_mutex_lock_size(void) +{ + return sizeof(CRITICAL_SECTION); +} + +WORD32 ithread_create(void* thread_handle, void* attribute, void* strt, void* argument) +{ + //UNUSED(attribute); + //return pthread_create((pthread_t*)thread_handle, NULL, (void* (*)(void*)) strt, argument); + __debugbreak(); + return 0; +} + +WORD32 ithread_join(void* thread_handle, void** val_ptr) +{ + //UNUSED(val_ptr); + //pthread_t* pthread_handle = (pthread_t*)thread_handle; + //return pthread_join(*pthread_handle, NULL); + + __debugbreak(); + return 0; +} + +WORD32 ithread_get_mutex_struct_size(void) +{ + return sizeof(CRITICAL_SECTION); +} +WORD32 ithread_mutex_init(void* mutex) +{ + InitializeCriticalSection((LPCRITICAL_SECTION)mutex); + return 0; +} + +WORD32 ithread_mutex_destroy(void* mutex) +{ + return 0; +} + +WORD32 ithread_mutex_lock(void* mutex) +{ + EnterCriticalSection((LPCRITICAL_SECTION)mutex); + return 0; +} + +WORD32 ithread_mutex_unlock(void* mutex) +{ + LeaveCriticalSection((LPCRITICAL_SECTION)mutex); + return 0; +} + +void ithread_yield(void) +{ + Sleep(0); +} + +void ithread_msleep(UWORD32 u4_time_ms) +{ + Sleep(u4_time_ms); +} + +void ithread_usleep(UWORD32 u4_time_us) +{ + __debugbreak(); + //usleep(u4_time_us); +} + +UWORD32 ithread_get_sem_struct_size(void) +{ + __debugbreak(); + return 0; + //return(sizeof(sem_t)); +} + + +WORD32 ithread_sem_init(void* sem, WORD32 pshared, UWORD32 value) +{ + __debugbreak(); + return 0; + //return sem_init((sem_t*)sem, pshared, value); +} + +WORD32 ithread_sem_post(void* sem) +{ + __debugbreak(); + return 0; + //return sem_post((sem_t*)sem); +} + + +WORD32 ithread_sem_wait(void* sem) +{ + __debugbreak(); + return 0; + //return sem_wait((sem_t*)sem); +} + + +WORD32 ithread_sem_destroy(void* sem) +{ + __debugbreak(); + return 0; + //return sem_destroy((sem_t*)sem); +} + +void ithread_set_name(CHAR* pc_thread_name) +{ + +} + +WORD32 ithread_set_affinity(WORD32 core_id) +{ +#ifdef PTHREAD_AFFINITY + cpu_set_t cpuset; + int num_cores = sysconf(_SC_NPROCESSORS_ONLN); + pthread_t cur_thread = pthread_self(); + + if (core_id >= num_cores) + return -1; + + CPU_ZERO(&cpuset); + CPU_SET(core_id, &cpuset); + + return pthread_setaffinity_np(cur_thread, sizeof(cpu_set_t), &cpuset); + +#elif SYSCALL_AFFINITY + WORD32 i4_sys_res; + UNUSED(core_id); + + pid_t pid = gettid(); + + + i4_sys_res = syscall(__NR_sched_setaffinity, pid, sizeof(i4_mask), &i4_mask); + if (i4_sys_res) + { + //WORD32 err; + //err = errno; + //perror("Error in setaffinity syscall PERROR : "); + //LOG_ERROR("Error in the syscall setaffinity: mask=0x%x err=0x%x", i4_mask, i4_sys_res); + return -1; + } +#else + UNUSED(core_id); +#endif + return 1; +} + +#else + +UWORD32 ithread_get_handle_size(void) +{ + return sizeof(pthread_t); +} + +UWORD32 ithread_get_mutex_lock_size(void) +{ + return sizeof(pthread_mutex_t); +} + +WORD32 ithread_create(void* thread_handle, void* attribute, void* strt, void* argument) +{ + UNUSED(attribute); + return pthread_create((pthread_t*)thread_handle, NULL, (void* (*)(void*)) strt, argument); +} + +WORD32 ithread_join(void* thread_handle, void** val_ptr) +{ + UNUSED(val_ptr); + pthread_t* pthread_handle = (pthread_t*)thread_handle; + return pthread_join(*pthread_handle, NULL); +} + +WORD32 ithread_get_mutex_struct_size(void) +{ + return(sizeof(pthread_mutex_t)); +} +WORD32 ithread_mutex_init(void* mutex) +{ + return pthread_mutex_init((pthread_mutex_t*)mutex, NULL); +} + +WORD32 ithread_mutex_destroy(void* mutex) +{ + return pthread_mutex_destroy((pthread_mutex_t*)mutex); +} + +WORD32 ithread_mutex_lock(void* mutex) +{ + return pthread_mutex_lock((pthread_mutex_t*)mutex); +} + +WORD32 ithread_mutex_unlock(void* mutex) +{ + return pthread_mutex_unlock((pthread_mutex_t*)mutex); +} + +void ithread_yield(void) +{ + sched_yield(); +} + +void ithread_msleep(UWORD32 u4_time_ms) +{ + usleep(u4_time_ms * 1000); +} + +UWORD32 ithread_get_sem_struct_size(void) +{ + return(sizeof(sem_t)); +} + + +WORD32 ithread_sem_init(void* sem, WORD32 pshared, UWORD32 value) +{ + return sem_init((sem_t*)sem, pshared, value); +} + +WORD32 ithread_sem_post(void* sem) +{ + return sem_post((sem_t*)sem); +} + + +WORD32 ithread_sem_wait(void* sem) +{ + return sem_wait((sem_t*)sem); +} + + +WORD32 ithread_sem_destroy(void* sem) +{ + return sem_destroy((sem_t*)sem); +} + +void ithread_set_name(CHAR* pc_thread_name) +{ + UNUSED(pc_thread_name); +} + +WORD32 ithread_set_affinity(WORD32 core_id) +{ +#ifdef PTHREAD_AFFINITY + cpu_set_t cpuset; + int num_cores = sysconf(_SC_NPROCESSORS_ONLN); + pthread_t cur_thread = pthread_self(); + + if (core_id >= num_cores) + return -1; + + CPU_ZERO(&cpuset); + CPU_SET(core_id, &cpuset); + + return pthread_setaffinity_np(cur_thread, sizeof(cpu_set_t), &cpuset); + +#elif SYSCALL_AFFINITY + WORD32 i4_sys_res; + UNUSED(core_id); + + pid_t pid = gettid(); + + + i4_sys_res = syscall(__NR_sched_setaffinity, pid, sizeof(i4_mask), &i4_mask); + if (i4_sys_res) + { + //WORD32 err; + //err = errno; + //perror("Error in setaffinity syscall PERROR : "); + //LOG_ERROR("Error in the syscall setaffinity: mask=0x%x err=0x%x", i4_mask, i4_sys_res); + return -1; + } +#else + UNUSED(core_id); +#endif + return 1; + +} + +#endif \ No newline at end of file diff --git a/dependencies/ih264d/common/ithread.h b/dependencies/ih264d/common/ithread.h new file mode 100644 index 00000000..e5610154 --- /dev/null +++ b/dependencies/ih264d/common/ithread.h @@ -0,0 +1,99 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/*****************************************************************************/ +/* */ +/* File Name : ithread.h */ +/* */ +/* Description : This file contains all the necessary structure and */ +/* enumeration definitions needed for the Application */ +/* Program Interface(API) of the */ +/* Thread Abstraction Layer */ +/* */ +/* List of Functions : ithread_get_handle_size */ +/* ithread_get_mutex_lock_size */ +/* ithread_create */ +/* ithread_join */ +/* ithread_get_mutex_struct_size */ +/* ithread_mutex_init */ +/* ithread_mutex_destroy */ +/* ithread_mutex_lock */ +/* ithread_mutex_unlock */ +/* ithread_yield */ +/* ithread_sleep */ +/* ithread_msleep */ +/* ithread_usleep */ +/* ithread_get_sem_struct_size */ +/* ithread_sem_init */ +/* ithread_sem_post */ +/* ithread_sem_wait */ +/* ithread_sem_destroy */ +/* ithread_set_affinity */ +/* */ +/* Issues / Problems : None */ +/* */ +/* Revision History : */ +/* */ +/* DD MM YYYY Author(s) Changes */ +/* 06 09 2012 Harish Initial Version */ +/* */ +/*****************************************************************************/ + +#ifndef _ITHREAD_H_ +#define _ITHREAD_H_ + +UWORD32 ithread_get_handle_size(void); + +UWORD32 ithread_get_mutex_lock_size(void); + +WORD32 ithread_create(void *thread_handle, void *attribute, void *strt, void *argument); + +WORD32 ithread_join(void *thread_id, void ** val_ptr); + +WORD32 ithread_get_mutex_struct_size(void); + +WORD32 ithread_mutex_init(void *mutex); + +WORD32 ithread_mutex_destroy(void *mutex); + +WORD32 ithread_mutex_lock(void *mutex); + +WORD32 ithread_mutex_unlock(void *mutex); + +void ithread_yield(void); + +void ithread_msleep(UWORD32 u4_time_ms); + +void ithread_usleep(UWORD32 u4_time_us); + +UWORD32 ithread_get_sem_struct_size(void); + +WORD32 ithread_sem_init(void *sem,WORD32 pshared,UWORD32 value); + +WORD32 ithread_sem_post(void *sem); + +WORD32 ithread_sem_wait(void *sem); + +WORD32 ithread_sem_destroy(void *sem); + +WORD32 ithread_set_affinity(WORD32 core_id); + +void ithread_set_name(CHAR *pc_thread_name); + +#endif /* _ITHREAD_H_ */ diff --git a/dependencies/ih264d/common/mips/ih264_platform_macros.h b/dependencies/ih264d/common/mips/ih264_platform_macros.h new file mode 100644 index 00000000..fa0ba61d --- /dev/null +++ b/dependencies/ih264d/common/mips/ih264_platform_macros.h @@ -0,0 +1,115 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** +******************************************************************************* +* @file +* ih264_platform_macros.h +* +* @brief +* Platform specific Macro definitions used in the codec +* +* @author +* Ittiam +* +* @remarks +* None +* +******************************************************************************* +*/ + + +#ifndef _IH264_PLATFORM_MACROS_H_ +#define _IH264_PLATFORM_MACROS_H_ + +#include <stdint.h> + +#define CLIP_U8(x) CLIP3(0, UINT8_MAX, (x)) +#define CLIP_S8(x) CLIP3(INT8_MIN, INT8_MAX, (x)) + +#define CLIP_U10(x) CLIP3(0, 1023, (x)) +#define CLIP_S10(x) CLIP3(-512, 511, (x)) + +#define CLIP_U11(x) CLIP3(0, 2047, (x)) +#define CLIP_S11(x) CLIP3(-1024, 1023, (x)) + +#define CLIP_U12(x) CLIP3(0, 4095, (x)) +#define CLIP_S12(x) CLIP3(-2048, 2047, (x)) + +#define CLIP_U16(x) CLIP3(0, UINT16_MAX, (x)) +#define CLIP_S16(x) CLIP3(INT16_MIN, INT16_MAX, (x)) + +#define CLIP_U32(x) CLIP3(0, UINT32_MAX, (x)) +#define CLIP_S32(x) CLIP3(INT32_MIN, INT32_MAX, (x)) + +#define MEM_ALIGN16 __attribute__ ((aligned (16))) + +#define SHL(x,y) (((y) < 32) ? ((x) << (y)) : 0) +#define SHR(x,y) (((y) < 32) ? ((x) >> (y)) : 0) + +#define SHR_NEG(val,shift) ((shift>0)?(val>>shift):(val<<(-shift))) +#define SHL_NEG(val,shift) ((shift<0)?(val>>(-shift)):(val<<shift)) + + +#define ITT_BIG_ENDIAN(x) ((x << 24)) | \ + ((x & 0x0000ff00) << 8) | \ + ((x & 0x00ff0000) >> 8) | \ + ((UWORD32)x >> 24); + + +#define NOP(nop_cnt) {UWORD32 nop_i; for (nop_i = 0; nop_i < nop_cnt; nop_i++);} + +#define PLD(a) + +/* In normal cases, 0 will not be passed as an argument to CLZ and CTZ. +As CLZ and CTZ outputs are used as a shift value in few places, these return +31 for u4_word == 0 case, just to handle error cases gracefully without any +undefined behaviour */ + +static __inline UWORD32 CLZ(UWORD32 u4_word) +{ + if(u4_word) + return(__builtin_clz(u4_word)); + else + return 31; +} + +static __inline UWORD32 CTZ(UWORD32 u4_word) +{ + if(0 == u4_word) + return 31; + else + { + unsigned int index; + index = __builtin_ctz(u4_word); + return (UWORD32)index; + } +} + +#define DATA_SYNC() + +#define INLINE + +#define PREFETCH(ptr, type) + +#define MEM_ALIGN8 __attribute__ ((aligned (8))) +#define MEM_ALIGN16 __attribute__ ((aligned (16))) +#define MEM_ALIGN32 __attribute__ ((aligned (32))) + +#endif /* _IH264_PLATFORM_MACROS_H_ */ diff --git a/dependencies/ih264d/common/x86/ih264_chroma_intra_pred_filters_ssse3.c b/dependencies/ih264d/common/x86/ih264_chroma_intra_pred_filters_ssse3.c new file mode 100644 index 00000000..d43ce207 --- /dev/null +++ b/dependencies/ih264d/common/x86/ih264_chroma_intra_pred_filters_ssse3.c @@ -0,0 +1,417 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** +******************************************************************************* +* @file +* ih264_chroma_intra_pred_filters_ssse3.c +* +* @brief +* Contains function definitions for chroma intra prediction filters in x86 +* intrinsics +* +* @author +* Ittiam +* +* @par List of Functions: +* -ih264_intra_pred_chroma_8x8_mode_horz_ssse3 +* -ih264_intra_pred_chroma_8x8_mode_vert_ssse3 +* -ih264_intra_pred_chroma_8x8_mode_plane_ssse3 +* +* @remarks +* None +* +******************************************************************************* +*/ + +/*****************************************************************************/ +/* File Includes */ +/*****************************************************************************/ + +/* System include files */ +#include <stdio.h> +#include <stddef.h> +#include <string.h> + +/* User include files */ +#include "ih264_defs.h" +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264_intra_pred_filters.h" + + +/*****************************************************************************/ +/* Chroma Intra prediction 8x8 filters */ +/*****************************************************************************/ +/** +******************************************************************************* +* +* ih264_intra_pred_chroma_8x8_mode_horz_ssse3 +* +* @brief +* Perform Intra prediction for chroma_8x8 mode:Horizontal +* +* @par Description: +* Perform Intra prediction for chroma_8x8 mode:Horizontal ,described in sec 8.3.4.2 +* +* @param[in] pu1_src +* UWORD8 pointer to the source containing alternate U and V samples +* +* @param[out] pu1_dst +* UWORD8 pointer to the destination with alternate U and V samples +* +* @param[in] src_strd +* integer source stride +* +* @param[in] dst_strd +* integer destination stride +* +* @param[in] ngbr_avail +* availability of neighbouring pixels(Not used in this function) +* +* @returns +* +* @remarks +* None +* +****************************************************************************** +*/ +void ih264_intra_pred_chroma_8x8_mode_horz_ssse3(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + + UWORD8 *pu1_left; /* Pointer to start of top predictors */ + WORD32 dst_strd2; + + __m128i row1_16x8b, row2_16x8b; + + UNUSED(src_strd); + UNUSED(ngbr_avail); + + pu1_left = pu1_src + 2 * BLK8x8SIZE - 2; + + + dst_strd2 = dst_strd << 1; + row1_16x8b = _mm_set1_epi16(*((WORD16 *)(pu1_left))); + row2_16x8b = _mm_set1_epi16(*((WORD16 *)(pu1_left - 2))); + _mm_storeu_si128((__m128i *)pu1_dst, row1_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), row2_16x8b); + + pu1_dst += dst_strd2; + row1_16x8b = _mm_set1_epi16(*((WORD16 *)(pu1_left - 4))); + row2_16x8b = _mm_set1_epi16(*((WORD16 *)(pu1_left - 6))); + _mm_storeu_si128((__m128i *)pu1_dst, row1_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), row2_16x8b); + + pu1_dst += dst_strd2; + row1_16x8b = _mm_set1_epi16(*((WORD16 *)(pu1_left - 8))); + row2_16x8b = _mm_set1_epi16(*((WORD16 *)(pu1_left - 10))); + _mm_storeu_si128((__m128i *)pu1_dst, row1_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), row2_16x8b); + + pu1_dst += dst_strd2; + row1_16x8b = _mm_set1_epi16(*((WORD16 *)(pu1_left - 12))); + row2_16x8b = _mm_set1_epi16(*((WORD16 *)(pu1_left - 14))); + _mm_storeu_si128((__m128i *)pu1_dst, row1_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), row2_16x8b); +} + +/** +******************************************************************************* +* +* ih264_intra_pred_chroma_8x8_mode_vert_ssse3 +* +* @brief +* Perform Intra prediction for chroma_8x8 mode:vertical +* +* @par Description: +* Perform Intra prediction for chroma_8x8 mode:vertical ,described in sec 8.3.4.3 +* +* @param[in] pu1_src +* UWORD8 pointer to the source containing alternate U and V samples +* +* @param[out] pu1_dst +* UWORD8 pointer to the destination with alternate U and V samples +* +* @param[in] src_strd +* integer source stride +* +* @param[in] dst_strd +* integer destination stride +* +* @param[in] ngbr_avail +* availability of neighbouring pixels(Not used in this function) +* +* @returns +* +* @remarks +* None +* +******************************************************************************* +*/ +void ih264_intra_pred_chroma_8x8_mode_vert_ssse3(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + UWORD8 *pu1_top; /* Pointer to start of top predictors */ + WORD32 dst_strd2; + + __m128i top_16x8b; + + UNUSED(src_strd); + UNUSED(ngbr_avail); + + pu1_top = pu1_src + 2 * BLK8x8SIZE + 2; + + top_16x8b = _mm_loadu_si128((__m128i *)pu1_top); + + dst_strd2 = dst_strd << 1; + _mm_storeu_si128((__m128i *)pu1_dst, top_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), top_16x8b); + + pu1_dst += dst_strd2; + _mm_storeu_si128((__m128i *)pu1_dst, top_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), top_16x8b); + + pu1_dst += dst_strd2; + _mm_storeu_si128((__m128i *)pu1_dst, top_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), top_16x8b); + + pu1_dst += dst_strd2; + _mm_storeu_si128((__m128i *)pu1_dst, top_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), top_16x8b); +} + +/** +******************************************************************************* +* +* ih264_intra_pred_chroma_8x8_mode_plane_ssse3 +* +* @brief +* Perform Intra prediction for chroma_8x8 mode:PLANE +* +* @par Description: +* Perform Intra prediction for chroma_8x8 mode:PLANE ,described in sec 8.3.4.4 +* +* @param[in] pu1_src +* UWORD8 pointer to the source containing alternate U and V samples +* +* @param[out] pu1_dst +* UWORD8 pointer to the destination with alternate U and V samples +* +* @param[in] src_strd +* integer source stride +* +* @param[in] dst_strd +* integer destination stride +* +* @param[in] ngbr_avail +* availability of neighbouring pixels(Not used in this function) +* +* @returns +* +* @remarks +* None +* +****************************************************************************** +*/ +void ih264_intra_pred_chroma_8x8_mode_plane_ssse3(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + UWORD8 *pu1_left, *pu1_top; + WORD32 a_u, a_v, b_u, b_v, c_u, c_v; + + __m128i mul_8x16b, shuffle_8x16b; + + UNUSED(src_strd); + UNUSED(ngbr_avail); + + pu1_top = pu1_src + MB_SIZE + 2; + pu1_left = pu1_src + MB_SIZE - 2; + + mul_8x16b = _mm_setr_epi16(1, 2, 3, 4, 1, 2, 3, 4); + shuffle_8x16b = _mm_setr_epi16(0xff00, 0xff02, 0xff04, 0xff06, + 0xff01, 0xff03, 0xff05, 0xff07); + + //calculating a, b and c + { + WORD32 h_u, h_v, v_u, v_v; + + __m128i h_val1_16x8b, h_val2_16x8b; + __m128i h_val1_8x16b, h_val2_8x16b, h_val_4x32b; + __m128i v_val1_16x8b, v_val2_16x8b; + __m128i v_val1_8x16b, v_val2_8x16b, v_val_4x32b; + __m128i hv_val_4x32b; + + h_val1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_top + 8)); + h_val2_16x8b = _mm_loadl_epi64((__m128i *)(pu1_top - 2)); + v_val1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_left - 14)); + v_val2_16x8b = _mm_loadl_epi64((__m128i *)(pu1_left - 4)); + + // reversing the order + h_val2_16x8b = _mm_shufflelo_epi16(h_val2_16x8b, 0x1b); + v_val1_16x8b = _mm_shufflelo_epi16(v_val1_16x8b, 0x1b); + + // separating u and v and 8-bit to 16-bit conversion + h_val1_8x16b = _mm_shuffle_epi8(h_val1_16x8b, shuffle_8x16b); + h_val2_8x16b = _mm_shuffle_epi8(h_val2_16x8b, shuffle_8x16b); + v_val1_8x16b = _mm_shuffle_epi8(v_val1_16x8b, shuffle_8x16b); + v_val2_8x16b = _mm_shuffle_epi8(v_val2_16x8b, shuffle_8x16b); + + h_val1_8x16b = _mm_sub_epi16(h_val1_8x16b, h_val2_8x16b); + v_val1_8x16b = _mm_sub_epi16(v_val1_8x16b, v_val2_8x16b); + + h_val_4x32b = _mm_madd_epi16(mul_8x16b, h_val1_8x16b); + v_val_4x32b = _mm_madd_epi16(mul_8x16b, v_val1_8x16b); + + hv_val_4x32b = _mm_hadd_epi32(h_val_4x32b, v_val_4x32b); + + a_u = (pu1_left[7 * (-2)] + pu1_top[14]) << 4; + a_v = (pu1_left[7 * (-2) + 1] + pu1_top[15]) << 4; + + h_u = _mm_extract_epi16(hv_val_4x32b, 0); + h_v = _mm_extract_epi16(hv_val_4x32b, 2); + v_u = _mm_extract_epi16(hv_val_4x32b, 4); + v_v = _mm_extract_epi16(hv_val_4x32b, 6); + + h_u = (h_u << 16) >> 15; // sign-extension and multiplication by 2 + h_v = (h_v << 16) >> 15; + v_u = (v_u << 16) >> 15; + v_v = (v_v << 16) >> 15; + + b_u = ((h_u << 4) + h_u + 32) >> 6; + b_v = ((h_v << 4) + h_v + 32) >> 6; + c_u = ((v_u << 4) + v_u + 32) >> 6; + c_v = ((v_v << 4) + v_v + 32) >> 6; + } + //using a, b and c to compute the fitted plane values + { + __m128i const_8x16b, c2_8x16b; + __m128i res1_l_8x16b, res1_h_8x16b; + __m128i res2_l_8x16b, res2_h_8x16b; + __m128i res1_sh_l_8x16b, res1_sh_h_8x16b, res1_16x8b; + __m128i res2_sh_l_8x16b, res2_sh_h_8x16b, res2_16x8b; + + WORD32 b_u2, b_v2, b_u3, b_v3; + WORD32 const_u, const_v; + WORD32 dst_strd2; + + const_u = a_u - (c_u << 1) - c_u + 16; + const_v = a_v - (c_v << 1) - c_v + 16; + + b_u2 = b_u << 1; + b_v2 = b_v << 1; + b_u3 = b_u + b_u2; + b_v3 = b_v + b_v2; + + const_8x16b = _mm_setr_epi16(const_u, const_v, const_u, const_v, const_u, const_v, const_u, const_v); + res1_l_8x16b = _mm_setr_epi16(-b_u3, -b_v3, -b_u2, -b_v2, -b_u, -b_v, 0, 0); + //contains {-b*3, -b*2, -b*1, b*0} + res1_h_8x16b = _mm_setr_epi16(b_u, b_v, b_u2, b_v2, b_u3, b_v3, b_u << 2, b_v << 2); + //contains {b*1, b*2, b*3, b*4} + c2_8x16b = _mm_setr_epi16(c_u, c_v, c_u, c_v, c_u, c_v, c_u, c_v); + + // rows 1, 2 + res1_l_8x16b = _mm_add_epi16(res1_l_8x16b, const_8x16b); + res1_h_8x16b = _mm_add_epi16(res1_h_8x16b, const_8x16b); + res2_l_8x16b = _mm_add_epi16(res1_l_8x16b, c2_8x16b); + res2_h_8x16b = _mm_add_epi16(res1_h_8x16b, c2_8x16b); + + res1_sh_l_8x16b = _mm_srai_epi16(res1_l_8x16b, 5); + res1_sh_h_8x16b = _mm_srai_epi16(res1_h_8x16b, 5); + res2_sh_l_8x16b = _mm_srai_epi16(res2_l_8x16b, 5); + res2_sh_h_8x16b = _mm_srai_epi16(res2_h_8x16b, 5); + + dst_strd2 = dst_strd << 1; + c2_8x16b = _mm_slli_epi16(c2_8x16b, 1); + + res1_16x8b = _mm_packus_epi16(res1_sh_l_8x16b, res1_sh_h_8x16b); + res2_16x8b = _mm_packus_epi16(res2_sh_l_8x16b, res2_sh_h_8x16b); + + _mm_storeu_si128((__m128i *)pu1_dst, res1_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), res2_16x8b); + + // rows 3, 4 + res1_l_8x16b = _mm_add_epi16(res1_l_8x16b, c2_8x16b); + res1_h_8x16b = _mm_add_epi16(res1_h_8x16b, c2_8x16b); + res2_l_8x16b = _mm_add_epi16(res2_l_8x16b, c2_8x16b); + res2_h_8x16b = _mm_add_epi16(res2_h_8x16b, c2_8x16b); + + res1_sh_l_8x16b = _mm_srai_epi16(res1_l_8x16b, 5); + res1_sh_h_8x16b = _mm_srai_epi16(res1_h_8x16b, 5); + res2_sh_l_8x16b = _mm_srai_epi16(res2_l_8x16b, 5); + res2_sh_h_8x16b = _mm_srai_epi16(res2_h_8x16b, 5); + + pu1_dst += dst_strd2; + + res1_16x8b = _mm_packus_epi16(res1_sh_l_8x16b, res1_sh_h_8x16b); + res2_16x8b = _mm_packus_epi16(res2_sh_l_8x16b, res2_sh_h_8x16b); + + _mm_storeu_si128((__m128i *)pu1_dst, res1_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), res2_16x8b); + + // rows 5, 6 + res1_l_8x16b = _mm_add_epi16(res1_l_8x16b, c2_8x16b); + res1_h_8x16b = _mm_add_epi16(res1_h_8x16b, c2_8x16b); + res2_l_8x16b = _mm_add_epi16(res2_l_8x16b, c2_8x16b); + res2_h_8x16b = _mm_add_epi16(res2_h_8x16b, c2_8x16b); + + res1_sh_l_8x16b = _mm_srai_epi16(res1_l_8x16b, 5); + res1_sh_h_8x16b = _mm_srai_epi16(res1_h_8x16b, 5); + res2_sh_l_8x16b = _mm_srai_epi16(res2_l_8x16b, 5); + res2_sh_h_8x16b = _mm_srai_epi16(res2_h_8x16b, 5); + + pu1_dst += dst_strd2; + + res1_16x8b = _mm_packus_epi16(res1_sh_l_8x16b, res1_sh_h_8x16b); + res2_16x8b = _mm_packus_epi16(res2_sh_l_8x16b, res2_sh_h_8x16b); + + _mm_storeu_si128((__m128i *)pu1_dst, res1_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), res2_16x8b); + + // rows 7, 8 + res1_l_8x16b = _mm_add_epi16(res1_l_8x16b, c2_8x16b); + res1_h_8x16b = _mm_add_epi16(res1_h_8x16b, c2_8x16b); + res2_l_8x16b = _mm_add_epi16(res2_l_8x16b, c2_8x16b); + res2_h_8x16b = _mm_add_epi16(res2_h_8x16b, c2_8x16b); + + res1_sh_l_8x16b = _mm_srai_epi16(res1_l_8x16b, 5); + res1_sh_h_8x16b = _mm_srai_epi16(res1_h_8x16b, 5); + res2_sh_l_8x16b = _mm_srai_epi16(res2_l_8x16b, 5); + res2_sh_h_8x16b = _mm_srai_epi16(res2_h_8x16b, 5); + + pu1_dst += dst_strd2; + + res1_16x8b = _mm_packus_epi16(res1_sh_l_8x16b, res1_sh_h_8x16b); + res2_16x8b = _mm_packus_epi16(res2_sh_l_8x16b, res2_sh_h_8x16b); + + _mm_storeu_si128((__m128i *)pu1_dst, res1_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), res2_16x8b); + + } +} diff --git a/dependencies/ih264d/common/x86/ih264_deblk_chroma_ssse3.c b/dependencies/ih264d/common/x86/ih264_deblk_chroma_ssse3.c new file mode 100644 index 00000000..a36447a2 --- /dev/null +++ b/dependencies/ih264d/common/x86/ih264_deblk_chroma_ssse3.c @@ -0,0 +1,1087 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/*****************************************************************************/ +/* */ +/* File Name : ih264_deblk_chroma_ssse3.c */ +/* */ +/* Description : Contains function definitions for deblocking */ +/* */ +/* List of Functions : ih264_deblk_chroma_vert_bs4_ssse3() */ +/* ih264_deblk_chroma_horz_bs4_ssse3() */ +/* ih264_deblk_chroma_vert_bslt4_ssse3() */ +/* ih264_deblk_chroma_horz_bslt4_ssse3() */ +/* ih264_deblk_chroma_vert_bs4_mbaff_ssse3() */ +/* ih264_deblk_chroma_vert_bslt4_mbaff_ssse3() */ +/* */ +/* Issues / Problems : None */ +/* */ +/* Revision History : */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 12 02 2015 Naveen Kumar P Added chrom deblocking ssse3 */ +/* intrinsics */ +/* */ +/*****************************************************************************/ + +/*****************************************************************************/ +/* File Includes */ +/*****************************************************************************/ + +/* System include files */ +#include <stdio.h> + +/* User include files */ +#include "ih264_typedefs.h" +#include "ih264_platform_macros.h" +#include "ih264_deblk_edge_filters.h" +#include "ih264_macros.h" + +/*****************************************************************************/ +/* Function Definitions */ +/*****************************************************************************/ + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_deblk_chroma_vert_bs4_ssse3() */ +/* */ +/* Description : This function performs filtering of a chroma block */ +/* vertical edge when the boundary strength is set to 4 in */ +/* high profile. */ +/* */ +/* Inputs : pu1_src - pointer to the src sample q0 of U */ +/* src_strd - source stride */ +/* alpha_cb - alpha value for the boundary in U */ +/* beta_cb - beta value for the boundary in U */ +/* alpha_cr - alpha value for the boundary in V */ +/* beta_cr - beta value for the boundary in V */ +/* */ +/* Globals : None */ +/* */ +/* Processing : This operation is described in Sec. 8.7.2.4 under the */ +/* title "Filtering process for edges for bS equal to 4" in */ +/* ITU T Rec H.264 with alpha and beta values different in */ +/* U and V. */ +/* */ +/* Outputs : None */ +/* */ +/* Returns : None */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 12 02 2015 Naveen Kumar P Initial version */ +/* */ +/*****************************************************************************/ +void ih264_deblk_chroma_vert_bs4_ssse3(UWORD8 *pu1_src, + WORD32 src_strd, + WORD32 alpha_cb, + WORD32 beta_cb, + WORD32 alpha_cr, + WORD32 beta_cr) +{ + UWORD8 *pu1_src_uv = pu1_src; /* Pointer to the src sample q0 of plane U*/ + WORD32 alpha_cbcr = (alpha_cr << 16) + alpha_cb; + WORD32 beta_cbcr = (beta_cr << 16) + beta_cb; + __m128i linea, lineb, linec, lined, linee, linef, lineg, lineh; + __m128i temp1, temp2, temp3, temp4; + + __m128i q0_uv_16x8, p0_uv_16x8, q1_uv_16x8, p1_uv_16x8; + __m128i q0_uv_8x16, p0_uv_8x16, q1_uv_8x16, p1_uv_8x16; + __m128i flag1, flag2; + __m128i diff, alpha_cbcr_16x8, beta_cbcr_16x8; + __m128i zero = _mm_setzero_si128(); + __m128i p0_uv_8x16_1, p0_uv_8x16_2, q0_uv_8x16_1, q0_uv_8x16_2; + + /* Load and transpose the pixel values */ + linea = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4)); + lineb = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4 + src_strd)); + linec = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4 + 2 * src_strd)); + lined = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4 + 3 * src_strd)); + linee = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4 + 4 * src_strd)); + linef = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4 + 5 * src_strd)); + lineg = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4 + 6 * src_strd)); + lineh = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4 + 7 * src_strd)); + + temp1 = _mm_unpacklo_epi16(linea, lineb); + temp2 = _mm_unpacklo_epi16(linec, lined); + temp3 = _mm_unpacklo_epi16(linee, linef); + temp4 = _mm_unpacklo_epi16(lineg, lineh); + + p1_uv_8x16 = _mm_unpacklo_epi32(temp1, temp2); + p0_uv_8x16 = _mm_unpacklo_epi32(temp3, temp4); + q0_uv_8x16 = _mm_unpackhi_epi32(temp1, temp2); + q1_uv_8x16 = _mm_unpackhi_epi32(temp3, temp4); + + p1_uv_16x8 = _mm_unpacklo_epi64(p1_uv_8x16, p0_uv_8x16); + p0_uv_16x8 = _mm_unpackhi_epi64(p1_uv_8x16, p0_uv_8x16); + q0_uv_16x8 = _mm_unpacklo_epi64(q0_uv_8x16, q1_uv_8x16); + q1_uv_16x8 = _mm_unpackhi_epi64(q0_uv_8x16, q1_uv_8x16); + /* End of transpose */ + + q0_uv_8x16 = _mm_unpacklo_epi8(q0_uv_16x8, zero); + q1_uv_8x16 = _mm_unpacklo_epi8(q1_uv_16x8, zero); + p1_uv_8x16 = _mm_unpacklo_epi8(p1_uv_16x8, zero); + p0_uv_8x16 = _mm_unpacklo_epi8(p0_uv_16x8, zero); + + diff = _mm_subs_epi16(p0_uv_8x16, q0_uv_8x16); //Condn 1 + diff = _mm_abs_epi16(diff); + alpha_cbcr_16x8 = _mm_set1_epi32(alpha_cbcr); + flag1 = _mm_cmpgt_epi16(alpha_cbcr_16x8, diff); + + diff = _mm_subs_epi16(q1_uv_8x16, q0_uv_8x16); //Condtn 2 + diff = _mm_abs_epi16(diff); + beta_cbcr_16x8 = _mm_set1_epi32(beta_cbcr); + flag1 = _mm_and_si128(flag1, _mm_cmpgt_epi16(beta_cbcr_16x8, diff)); + + diff = _mm_subs_epi16(p1_uv_8x16, p0_uv_8x16); //Condtn 3 + diff = _mm_abs_epi16(diff); + flag1 = _mm_and_si128(flag1, _mm_cmpgt_epi16(beta_cbcr_16x8, diff)); + + temp1 = _mm_slli_epi16(p1_uv_8x16, 1); + temp2 = _mm_add_epi16(p0_uv_8x16, q1_uv_8x16); + temp1 = _mm_add_epi16(temp1, _mm_set1_epi16(2)); + temp1 = _mm_add_epi16(temp1, temp2); + p0_uv_8x16_1 = _mm_srai_epi16(temp1, 2); + + temp1 = _mm_slli_epi16(q1_uv_8x16, 1); + temp2 = _mm_add_epi16(p1_uv_8x16, q0_uv_8x16); + temp1 = _mm_add_epi16(temp1, _mm_set1_epi16(2)); + temp1 = _mm_add_epi16(temp1, temp2); + q0_uv_8x16_1 = _mm_srai_epi16(temp1, 2); + + q0_uv_8x16 = _mm_unpackhi_epi8(q0_uv_16x8, zero); + q1_uv_8x16 = _mm_unpackhi_epi8(q1_uv_16x8, zero); + p1_uv_8x16 = _mm_unpackhi_epi8(p1_uv_16x8, zero); + p0_uv_8x16 = _mm_unpackhi_epi8(p0_uv_16x8, zero); + + diff = _mm_subs_epi16(p0_uv_8x16, q0_uv_8x16); //Condn 1 + diff = _mm_abs_epi16(diff); + alpha_cbcr_16x8 = _mm_set1_epi32(alpha_cbcr); + flag2 = _mm_cmpgt_epi16(alpha_cbcr_16x8, diff); + + diff = _mm_subs_epi16(q1_uv_8x16, q0_uv_8x16); //Condtn 2 + diff = _mm_abs_epi16(diff); + beta_cbcr_16x8 = _mm_set1_epi32(beta_cbcr); + flag2 = _mm_and_si128(flag2, _mm_cmpgt_epi16(beta_cbcr_16x8, diff)); + + diff = _mm_subs_epi16(p1_uv_8x16, p0_uv_8x16); //Condtn 3 + diff = _mm_abs_epi16(diff); + flag2 = _mm_and_si128(flag2, _mm_cmpgt_epi16(beta_cbcr_16x8, diff)); + + temp1 = _mm_slli_epi16(p1_uv_8x16, 1); + temp2 = _mm_add_epi16(p0_uv_8x16, q1_uv_8x16); + temp1 = _mm_add_epi16(temp1, _mm_set1_epi16(2)); + temp1 = _mm_add_epi16(temp1, temp2); + p0_uv_8x16_2 = _mm_srai_epi16(temp1, 2); + + temp1 = _mm_slli_epi16(q1_uv_8x16, 1); + temp2 = _mm_add_epi16(p1_uv_8x16, q0_uv_8x16); + temp1 = _mm_add_epi16(temp1, _mm_set1_epi16(2)); + temp1 = _mm_add_epi16(temp1, temp2); + q0_uv_8x16_2 = _mm_srai_epi16(temp1, 2); + + p0_uv_8x16_2 = _mm_packus_epi16(p0_uv_8x16_1, p0_uv_8x16_2); + q0_uv_8x16_2 = _mm_packus_epi16(q0_uv_8x16_1, q0_uv_8x16_2); + + flag1 = _mm_packs_epi16(flag1, flag2); + + p0_uv_8x16_1 = _mm_and_si128(p0_uv_16x8, + _mm_xor_si128(flag1, _mm_set1_epi8(0xFF))); + p0_uv_8x16_2 = _mm_and_si128(p0_uv_8x16_2, flag1); + p0_uv_16x8 = _mm_add_epi8(p0_uv_8x16_1, p0_uv_8x16_2); + + q0_uv_8x16_1 = _mm_and_si128(q0_uv_16x8, + _mm_xor_si128(flag1, _mm_set1_epi8(0xFF))); + q0_uv_8x16_2 = _mm_and_si128(q0_uv_8x16_2, flag1); + q0_uv_16x8 = _mm_add_epi8(q0_uv_8x16_1, q0_uv_8x16_2); + + /* Inverse-transpose and store back */ + temp1 = _mm_unpacklo_epi16(p1_uv_16x8, p0_uv_16x8); + temp2 = _mm_unpackhi_epi16(p1_uv_16x8, p0_uv_16x8); + temp3 = _mm_unpacklo_epi16(q0_uv_16x8, q1_uv_16x8); + temp4 = _mm_unpackhi_epi16(q0_uv_16x8, q1_uv_16x8); + + linea = _mm_unpacklo_epi32(temp1, temp3); + lineb = _mm_srli_si128(linea, 8); + linec = _mm_unpackhi_epi32(temp1, temp3); + lined = _mm_srli_si128(linec, 8); + linee = _mm_unpacklo_epi32(temp2, temp4); + linef = _mm_srli_si128(linee, 8); + lineg = _mm_unpackhi_epi32(temp2, temp4); + lineh = _mm_srli_si128(lineg, 8); + + _mm_storel_epi64((__m128i *)(pu1_src_uv - 4), linea); + _mm_storel_epi64((__m128i *)(pu1_src_uv - 4 + src_strd), lineb); + _mm_storel_epi64((__m128i *)(pu1_src_uv - 4 + 2 * src_strd), linec); + _mm_storel_epi64((__m128i *)(pu1_src_uv - 4 + 3 * src_strd), lined); + _mm_storel_epi64((__m128i *)(pu1_src_uv - 4 + 4 * src_strd), linee); + _mm_storel_epi64((__m128i *)(pu1_src_uv - 4 + 5 * src_strd), linef); + _mm_storel_epi64((__m128i *)(pu1_src_uv - 4 + 6 * src_strd), lineg); + _mm_storel_epi64((__m128i *)(pu1_src_uv - 4 + 7 * src_strd), lineh); + +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_deblk_chroma_horz_bs4_ssse3() */ +/* */ +/* Description : This function performs filtering of a chroma block */ +/* horizontal edge when the boundary strength is set to 4 */ +/* in high profile. */ +/* */ +/* Inputs : pu1_src - pointer to the src sample q0 of U */ +/* src_strd - source stride */ +/* alpha_cb - alpha value for the boundary in U */ +/* beta_cb - beta value for the boundary in U */ +/* alpha_cr - alpha value for the boundary in V */ +/* beta_cr - beta value for the boundary in V */ +/* */ +/* Globals : None */ +/* */ +/* Processing : This operation is described in Sec. 8.7.2.4 under the */ +/* title "Filtering process for edges for bS equal to 4" in */ +/* ITU T Rec H.264 with alpha and beta values different in */ +/* U and V. */ +/* */ +/* Outputs : None */ +/* */ +/* Returns : None */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 12 02 2015 Naveen Kumar P Initial version */ +/* */ +/*****************************************************************************/ +void ih264_deblk_chroma_horz_bs4_ssse3(UWORD8 *pu1_src, + WORD32 src_strd, + WORD32 alpha_cb, + WORD32 beta_cb, + WORD32 alpha_cr, + WORD32 beta_cr) +{ + UWORD8 *pu1_src_uv = pu1_src; /* Pointer to the src sample q0 of plane U*/ + WORD16 i16_posP1, i16_posP0, i16_posQ1; + + UWORD8 *pu1_HorzPixelUV; /*! < Pointer to the first pixel of the boundary */ + WORD32 alpha_cbcr = (alpha_cr << 16) + alpha_cb; + WORD32 beta_cbcr = (beta_cr << 16) + beta_cb; + __m128i q0_uv_16x8, p0_uv_16x8, q1_uv_16x8, p1_uv_16x8; + __m128i q0_uv_8x16, p0_uv_8x16, q1_uv_8x16, p1_uv_8x16; + __m128i flag1, flag2; + __m128i diff, alpha_cbcr_16x8, beta_cbcr_16x8; + __m128i zero = _mm_setzero_si128(); + __m128i p0_uv_8x16_1, p0_uv_8x16_2, q0_uv_8x16_1, q0_uv_8x16_2; + __m128i temp1, temp2; + + pu1_HorzPixelUV = pu1_src_uv - (src_strd << 1); + + i16_posQ1 = src_strd; + i16_posP0 = src_strd; + i16_posP1 = 0; + + q0_uv_16x8 = _mm_loadu_si128((__m128i *)(pu1_src_uv)); + q1_uv_16x8 = _mm_loadu_si128((__m128i *)(pu1_src_uv + i16_posQ1)); + p1_uv_16x8 = _mm_loadu_si128((__m128i *)(pu1_HorzPixelUV + i16_posP1)); + p0_uv_16x8 = _mm_loadu_si128((__m128i *)(pu1_HorzPixelUV + i16_posP0)); + + q0_uv_8x16 = _mm_unpacklo_epi8(q0_uv_16x8, zero); + q1_uv_8x16 = _mm_unpacklo_epi8(q1_uv_16x8, zero); + p1_uv_8x16 = _mm_unpacklo_epi8(p1_uv_16x8, zero); + p0_uv_8x16 = _mm_unpacklo_epi8(p0_uv_16x8, zero); + + diff = _mm_subs_epi16(p0_uv_8x16, q0_uv_8x16); //Condn 1 + diff = _mm_abs_epi16(diff); + alpha_cbcr_16x8 = _mm_set1_epi32(alpha_cbcr); + flag1 = _mm_cmpgt_epi16(alpha_cbcr_16x8, diff); + + diff = _mm_subs_epi16(q1_uv_8x16, q0_uv_8x16); //Condtn 2 + diff = _mm_abs_epi16(diff); + beta_cbcr_16x8 = _mm_set1_epi32(beta_cbcr); + flag1 = _mm_and_si128(flag1, _mm_cmpgt_epi16(beta_cbcr_16x8, diff)); + + diff = _mm_subs_epi16(p1_uv_8x16, p0_uv_8x16); //Condtn 3 + diff = _mm_abs_epi16(diff); + flag1 = _mm_and_si128(flag1, _mm_cmpgt_epi16(beta_cbcr_16x8, diff)); + + temp1 = _mm_slli_epi16(p1_uv_8x16, 1); + temp2 = _mm_add_epi16(p0_uv_8x16, q1_uv_8x16); + temp1 = _mm_add_epi16(temp1, _mm_set1_epi16(2)); + temp1 = _mm_add_epi16(temp1, temp2); + p0_uv_8x16_1 = _mm_srai_epi16(temp1, 2); + + temp1 = _mm_slli_epi16(q1_uv_8x16, 1); + temp2 = _mm_add_epi16(p1_uv_8x16, q0_uv_8x16); + temp1 = _mm_add_epi16(temp1, _mm_set1_epi16(2)); + temp1 = _mm_add_epi16(temp1, temp2); + q0_uv_8x16_1 = _mm_srai_epi16(temp1, 2); + + q0_uv_8x16 = _mm_unpackhi_epi8(q0_uv_16x8, zero); + q1_uv_8x16 = _mm_unpackhi_epi8(q1_uv_16x8, zero); + p1_uv_8x16 = _mm_unpackhi_epi8(p1_uv_16x8, zero); + p0_uv_8x16 = _mm_unpackhi_epi8(p0_uv_16x8, zero); + + diff = _mm_subs_epi16(p0_uv_8x16, q0_uv_8x16); //Condn 1 + diff = _mm_abs_epi16(diff); + alpha_cbcr_16x8 = _mm_set1_epi32(alpha_cbcr); + flag2 = _mm_cmpgt_epi16(alpha_cbcr_16x8, diff); + + diff = _mm_subs_epi16(q1_uv_8x16, q0_uv_8x16); //Condtn 2 + diff = _mm_abs_epi16(diff); + beta_cbcr_16x8 = _mm_set1_epi32(beta_cbcr); + flag2 = _mm_and_si128(flag2, _mm_cmpgt_epi16(beta_cbcr_16x8, diff)); + + diff = _mm_subs_epi16(p1_uv_8x16, p0_uv_8x16); //Condtn 3 + diff = _mm_abs_epi16(diff); + flag2 = _mm_and_si128(flag2, _mm_cmpgt_epi16(beta_cbcr_16x8, diff)); + + temp1 = _mm_slli_epi16(p1_uv_8x16, 1); + temp2 = _mm_add_epi16(p0_uv_8x16, q1_uv_8x16); + temp1 = _mm_add_epi16(temp1, _mm_set1_epi16(2)); + temp1 = _mm_add_epi16(temp1, temp2); + p0_uv_8x16_2 = _mm_srai_epi16(temp1, 2); + + temp1 = _mm_slli_epi16(q1_uv_8x16, 1); + temp2 = _mm_add_epi16(p1_uv_8x16, q0_uv_8x16); + temp1 = _mm_add_epi16(temp1, _mm_set1_epi16(2)); + temp1 = _mm_add_epi16(temp1, temp2); + q0_uv_8x16_2 = _mm_srai_epi16(temp1, 2); + + p0_uv_8x16_2 = _mm_packus_epi16(p0_uv_8x16_1, p0_uv_8x16_2); + q0_uv_8x16_2 = _mm_packus_epi16(q0_uv_8x16_1, q0_uv_8x16_2); + + flag1 = _mm_packs_epi16(flag1, flag2); + + p0_uv_8x16_1 = _mm_and_si128(p0_uv_16x8, + _mm_xor_si128(flag1, _mm_set1_epi8(0xFF))); + p0_uv_8x16_2 = _mm_and_si128(p0_uv_8x16_2, flag1); + p0_uv_8x16_1 = _mm_add_epi8(p0_uv_8x16_1, p0_uv_8x16_2); + _mm_storeu_si128((__m128i *)(pu1_HorzPixelUV + i16_posP0), p0_uv_8x16_1); + + q0_uv_8x16_1 = _mm_and_si128(q0_uv_16x8, + _mm_xor_si128(flag1, _mm_set1_epi8(0xFF))); + q0_uv_8x16_2 = _mm_and_si128(q0_uv_8x16_2, flag1); + q0_uv_8x16_1 = _mm_add_epi8(q0_uv_8x16_1, q0_uv_8x16_2); + _mm_storeu_si128((__m128i *)(pu1_src_uv), q0_uv_8x16_1); + +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_deblk_chroma_vert_bslt4_ssse3() */ +/* */ +/* Description : This function performs filtering of a chroma block */ +/* vertical edge when the boundary strength is less than 4 */ +/* in high profile. */ +/* */ +/* Inputs : pu1_src - pointer to the src sample q0 of U */ +/* src_strd - source stride */ +/* alpha_cb - alpha value for the boundary in U */ +/* beta_cb - beta value for the boundary in U */ +/* alpha_cr - alpha value for the boundary in V */ +/* beta_cr - beta value for the boundary in V */ +/* u4_bs - packed Boundary strength array */ +/* pu1_cliptab_cb - tc0_table for U */ +/* pu1_cliptab_cr - tc0_table for V */ +/* */ +/* Globals : None */ +/* */ +/* Processing : This operation is described in Sec. 8.7.2.3 under the */ +/* title "Filtering process for edges for bS less than 4" */ +/* in ITU T Rec H.264 with alpha and beta values different */ +/* in U and V. */ +/* */ +/* Outputs : None */ +/* */ +/* Returns : None */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 12 02 2015 Naveen Kumar P Initial version */ +/* */ +/*****************************************************************************/ +void ih264_deblk_chroma_vert_bslt4_ssse3(UWORD8 *pu1_src, + WORD32 src_strd, + WORD32 alpha_cb, + WORD32 beta_cb, + WORD32 alpha_cr, + WORD32 beta_cr, + UWORD32 u4_bs, + const UWORD8 *pu1_cliptab_cb, + const UWORD8 *pu1_cliptab_cr) +{ + UWORD8 *pu1_src_uv = pu1_src; /* Pointer to the src sample q0 of plane U*/ + UWORD8 u1_Bs0, u1_Bs1, u1_Bs2, u1_Bs3; + WORD32 alpha_cbcr = (alpha_cr << 16) + alpha_cb; + WORD32 beta_cbcr = (beta_cr << 16) + beta_cb; + __m128i linea, lineb, linec, lined, linee, linef, lineg, lineh; + __m128i temp1, temp2, temp3, temp4; + + __m128i q0_uv_16x8, p0_uv_16x8, q1_uv_16x8, p1_uv_16x8; + __m128i q0_uv_8x16, p0_uv_8x16, q1_uv_8x16, p1_uv_8x16; + __m128i flag_bs, flag1, flag2; + __m128i diff, diff1, alpha_cbcr_16x8, beta_cbcr_16x8, in_macro; + __m128i zero = _mm_setzero_si128(); + __m128i C0_uv_8x16; + __m128i p0_uv_8x16_1, p0_uv_8x16_2, q0_uv_8x16_1, q0_uv_8x16_2; + + u1_Bs0 = (u4_bs >> 24) & 0xff; + u1_Bs1 = (u4_bs >> 16) & 0xff; + u1_Bs2 = (u4_bs >> 8) & 0xff; + u1_Bs3 = (u4_bs >> 0) & 0xff; + + flag_bs = _mm_set_epi8(u1_Bs3, u1_Bs3, u1_Bs3, u1_Bs3, u1_Bs2, u1_Bs2, + u1_Bs2, u1_Bs2, u1_Bs1, u1_Bs1, u1_Bs1, u1_Bs1, + u1_Bs0, u1_Bs0, u1_Bs0, u1_Bs0); + flag_bs = _mm_cmpeq_epi8(flag_bs, zero); //Set flag to 1s and 0s + flag_bs = _mm_xor_si128(flag_bs, _mm_set1_epi8(0xFF)); //Invert for required mask + + /* Load and transpose the pixel values */ + linea = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4)); + lineb = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4 + src_strd)); + linec = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4 + 2 * src_strd)); + lined = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4 + 3 * src_strd)); + linee = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4 + 4 * src_strd)); + linef = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4 + 5 * src_strd)); + lineg = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4 + 6 * src_strd)); + lineh = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4 + 7 * src_strd)); + + temp1 = _mm_unpacklo_epi16(linea, lineb); + temp2 = _mm_unpacklo_epi16(linec, lined); + temp3 = _mm_unpacklo_epi16(linee, linef); + temp4 = _mm_unpacklo_epi16(lineg, lineh); + + p1_uv_8x16 = _mm_unpacklo_epi32(temp1, temp2); + p0_uv_8x16 = _mm_unpacklo_epi32(temp3, temp4); + q0_uv_8x16 = _mm_unpackhi_epi32(temp1, temp2); + q1_uv_8x16 = _mm_unpackhi_epi32(temp3, temp4); + + p1_uv_16x8 = _mm_unpacklo_epi64(p1_uv_8x16, p0_uv_8x16); + p0_uv_16x8 = _mm_unpackhi_epi64(p1_uv_8x16, p0_uv_8x16); + q0_uv_16x8 = _mm_unpacklo_epi64(q0_uv_8x16, q1_uv_8x16); + q1_uv_16x8 = _mm_unpackhi_epi64(q0_uv_8x16, q1_uv_8x16); + /* End of transpose */ + + q0_uv_8x16 = _mm_unpacklo_epi8(q0_uv_16x8, zero); + q1_uv_8x16 = _mm_unpacklo_epi8(q1_uv_16x8, zero); + p1_uv_8x16 = _mm_unpacklo_epi8(p1_uv_16x8, zero); + p0_uv_8x16 = _mm_unpacklo_epi8(p0_uv_16x8, zero); + + diff = _mm_subs_epi16(p0_uv_8x16, q0_uv_8x16); //Condn 1 + diff = _mm_abs_epi16(diff); + alpha_cbcr_16x8 = _mm_set1_epi32(alpha_cbcr); + flag1 = _mm_cmpgt_epi16(alpha_cbcr_16x8, diff); + + diff = _mm_subs_epi16(q1_uv_8x16, q0_uv_8x16); //Condtn 2 + diff = _mm_abs_epi16(diff); + beta_cbcr_16x8 = _mm_set1_epi32(beta_cbcr); + flag1 = _mm_and_si128(flag1, _mm_cmpgt_epi16(beta_cbcr_16x8, diff)); + + diff = _mm_subs_epi16(p1_uv_8x16, p0_uv_8x16); //Condtn 3 + diff = _mm_abs_epi16(diff); + flag1 = _mm_and_si128(flag1, _mm_cmpgt_epi16(beta_cbcr_16x8, diff)); + + diff = _mm_subs_epi16(q0_uv_8x16, p0_uv_8x16); + diff = _mm_slli_epi16(diff, 2); + diff1 = _mm_subs_epi16(p1_uv_8x16, q1_uv_8x16); + diff = _mm_add_epi16(diff, diff1); + diff = _mm_add_epi16(diff, _mm_set1_epi16(4)); + in_macro = _mm_srai_epi16(diff, 3); + + C0_uv_8x16 = _mm_set_epi16(pu1_cliptab_cr[u1_Bs1], pu1_cliptab_cb[u1_Bs1], + pu1_cliptab_cr[u1_Bs1], pu1_cliptab_cb[u1_Bs1], + pu1_cliptab_cr[u1_Bs0], pu1_cliptab_cb[u1_Bs0], + pu1_cliptab_cr[u1_Bs0], pu1_cliptab_cb[u1_Bs0]); + + C0_uv_8x16 = _mm_add_epi16(C0_uv_8x16, _mm_set1_epi16(1)); + + in_macro = _mm_min_epi16(C0_uv_8x16, in_macro); //CLIP3 + C0_uv_8x16 = _mm_subs_epi16(zero, C0_uv_8x16); + in_macro = _mm_max_epi16(C0_uv_8x16, in_macro); + + p0_uv_8x16_1 = _mm_add_epi16(p0_uv_8x16, in_macro); + q0_uv_8x16_1 = _mm_sub_epi16(q0_uv_8x16, in_macro); + + q0_uv_8x16 = _mm_unpackhi_epi8(q0_uv_16x8, zero); + q1_uv_8x16 = _mm_unpackhi_epi8(q1_uv_16x8, zero); + p1_uv_8x16 = _mm_unpackhi_epi8(p1_uv_16x8, zero); + p0_uv_8x16 = _mm_unpackhi_epi8(p0_uv_16x8, zero); + + diff = _mm_subs_epi16(p0_uv_8x16, q0_uv_8x16); //Condn 1 + diff = _mm_abs_epi16(diff); + alpha_cbcr_16x8 = _mm_set1_epi32(alpha_cbcr); + flag2 = _mm_cmpgt_epi16(alpha_cbcr_16x8, diff); + + diff = _mm_subs_epi16(q1_uv_8x16, q0_uv_8x16); //Condtn 2 + diff = _mm_abs_epi16(diff); + beta_cbcr_16x8 = _mm_set1_epi32(beta_cbcr); + flag2 = _mm_and_si128(flag2, _mm_cmpgt_epi16(beta_cbcr_16x8, diff)); + + diff = _mm_subs_epi16(p1_uv_8x16, p0_uv_8x16); //Condtn 3 + diff = _mm_abs_epi16(diff); + flag2 = _mm_and_si128(flag2, _mm_cmpgt_epi16(beta_cbcr_16x8, diff)); + + diff = _mm_subs_epi16(q0_uv_8x16, p0_uv_8x16); + diff = _mm_slli_epi16(diff, 2); + diff1 = _mm_subs_epi16(p1_uv_8x16, q1_uv_8x16); + diff = _mm_add_epi16(diff, diff1); + diff = _mm_add_epi16(diff, _mm_set1_epi16(4)); + in_macro = _mm_srai_epi16(diff, 3); + + C0_uv_8x16 = _mm_set_epi16(pu1_cliptab_cr[u1_Bs3], pu1_cliptab_cb[u1_Bs3], + pu1_cliptab_cr[u1_Bs3], pu1_cliptab_cb[u1_Bs3], + pu1_cliptab_cr[u1_Bs2], pu1_cliptab_cb[u1_Bs2], + pu1_cliptab_cr[u1_Bs2], pu1_cliptab_cb[u1_Bs2]); + + C0_uv_8x16 = _mm_add_epi16(C0_uv_8x16, _mm_set1_epi16(1)); + + in_macro = _mm_min_epi16(C0_uv_8x16, in_macro); //CLIP3 + C0_uv_8x16 = _mm_subs_epi16(zero, C0_uv_8x16); + in_macro = _mm_max_epi16(C0_uv_8x16, in_macro); + + p0_uv_8x16_2 = _mm_add_epi16(p0_uv_8x16, in_macro); + q0_uv_8x16_2 = _mm_sub_epi16(q0_uv_8x16, in_macro); + + p0_uv_8x16_2 = _mm_packus_epi16(p0_uv_8x16_1, p0_uv_8x16_2); + q0_uv_8x16_2 = _mm_packus_epi16(q0_uv_8x16_1, q0_uv_8x16_2); + + flag1 = _mm_packs_epi16(flag1, flag2); + flag1 = _mm_and_si128(flag1, flag_bs); //Final flag (BS condition + other 3 conditions) + + p0_uv_8x16_1 = _mm_and_si128(p0_uv_16x8, + _mm_xor_si128(flag1, _mm_set1_epi8(0xFF))); + p0_uv_8x16_2 = _mm_and_si128(p0_uv_8x16_2, flag1); + p0_uv_16x8 = _mm_add_epi8(p0_uv_8x16_1, p0_uv_8x16_2); + + q0_uv_8x16_1 = _mm_and_si128(q0_uv_16x8, + _mm_xor_si128(flag1, _mm_set1_epi8(0xFF))); + q0_uv_8x16_2 = _mm_and_si128(q0_uv_8x16_2, flag1); + q0_uv_16x8 = _mm_add_epi8(q0_uv_8x16_1, q0_uv_8x16_2); + + /* Inverse-transpose and store back */ + temp1 = _mm_unpacklo_epi16(p1_uv_16x8, p0_uv_16x8); + temp2 = _mm_unpackhi_epi16(p1_uv_16x8, p0_uv_16x8); + temp3 = _mm_unpacklo_epi16(q0_uv_16x8, q1_uv_16x8); + temp4 = _mm_unpackhi_epi16(q0_uv_16x8, q1_uv_16x8); + + linea = _mm_unpacklo_epi32(temp1, temp3); + lineb = _mm_srli_si128(linea, 8); + linec = _mm_unpackhi_epi32(temp1, temp3); + lined = _mm_srli_si128(linec, 8); + linee = _mm_unpacklo_epi32(temp2, temp4); + linef = _mm_srli_si128(linee, 8); + lineg = _mm_unpackhi_epi32(temp2, temp4); + lineh = _mm_srli_si128(lineg, 8); + + _mm_storel_epi64((__m128i *)(pu1_src_uv - 4), linea); + _mm_storel_epi64((__m128i *)(pu1_src_uv - 4 + src_strd), lineb); + _mm_storel_epi64((__m128i *)(pu1_src_uv - 4 + 2 * src_strd), linec); + _mm_storel_epi64((__m128i *)(pu1_src_uv - 4 + 3 * src_strd), lined); + _mm_storel_epi64((__m128i *)(pu1_src_uv - 4 + 4 * src_strd), linee); + _mm_storel_epi64((__m128i *)(pu1_src_uv - 4 + 5 * src_strd), linef); + _mm_storel_epi64((__m128i *)(pu1_src_uv - 4 + 6 * src_strd), lineg); + _mm_storel_epi64((__m128i *)(pu1_src_uv - 4 + 7 * src_strd), lineh); + +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_deblk_chroma_horz_bslt4_ssse3() */ +/* */ +/* Description : This function performs filtering of a chroma block */ +/* horizontal edge when the boundary strength is less than */ +/* 4 in high profile. */ +/* */ +/* Inputs : pu1_src - pointer to the src sample q0 of U */ +/* src_strd - source stride */ +/* alpha_cb - alpha value for the boundary in U */ +/* beta_cb - beta value for the boundary in U */ +/* alpha_cr - alpha value for the boundary in V */ +/* beta_cr - beta value for the boundary in V */ +/* u4_bs - packed Boundary strength array */ +/* pu1_cliptab_cb - tc0_table for U */ +/* pu1_cliptab_cr - tc0_table for V */ +/* */ +/* Globals : None */ +/* */ +/* Processing : This operation is described in Sec. 8.7.2.3 under the */ +/* title "Filtering process for edges for bS less than 4" */ +/* in ITU T Rec H.264 with alpha and beta values different */ +/* in U and V. */ +/* */ +/* Outputs : None */ +/* */ +/* Returns : None */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 12 02 2015 Naveen Kumar P Initial version */ +/* */ +/*****************************************************************************/ +void ih264_deblk_chroma_horz_bslt4_ssse3(UWORD8 *pu1_src, + WORD32 src_strd, + WORD32 alpha_cb, + WORD32 beta_cb, + WORD32 alpha_cr, + WORD32 beta_cr, + UWORD32 u4_bs, + const UWORD8 *pu1_cliptab_cb, + const UWORD8 *pu1_cliptab_cr) +{ + UWORD8 *pu1_src_uv = pu1_src; /* Pointer to the src sample q0 of plane U*/ + WORD16 i16_posP1, i16_posP0, i16_posQ1; + UWORD8 u1_Bs0, u1_Bs1, u1_Bs2, u1_Bs3; + + UWORD8 *pu1_HorzPixelUV; /*! < Pointer to the first pixel of the boundary */ + WORD32 alpha_cbcr = (alpha_cr << 16) + alpha_cb; + WORD32 beta_cbcr = (beta_cr << 16) + beta_cb; + __m128i q0_uv_16x8, p0_uv_16x8, q1_uv_16x8, p1_uv_16x8; + __m128i q0_uv_8x16, p0_uv_8x16, q1_uv_8x16, p1_uv_8x16; + __m128i flag_bs, flag1, flag2; + __m128i diff, diff1, alpha_cbcr_16x8, beta_cbcr_16x8, in_macro; + __m128i zero = _mm_setzero_si128(); + __m128i C0_uv_8x16; + __m128i p0_uv_8x16_1, p0_uv_8x16_2, q0_uv_8x16_1, q0_uv_8x16_2; + + pu1_HorzPixelUV = pu1_src_uv - (src_strd << 1); + + i16_posQ1 = src_strd; + i16_posP0 = src_strd; + i16_posP1 = 0; + + u1_Bs0 = (u4_bs >> 24) & 0xff; + u1_Bs1 = (u4_bs >> 16) & 0xff; + u1_Bs2 = (u4_bs >> 8) & 0xff; + u1_Bs3 = (u4_bs >> 0) & 0xff; + + flag_bs = _mm_set_epi8(u1_Bs3, u1_Bs3, u1_Bs3, u1_Bs3, u1_Bs2, u1_Bs2, + u1_Bs2, u1_Bs2, u1_Bs1, u1_Bs1, u1_Bs1, u1_Bs1, + u1_Bs0, u1_Bs0, u1_Bs0, u1_Bs0); + flag_bs = _mm_cmpeq_epi8(flag_bs, zero); //Set flag to 1s and 0s + flag_bs = _mm_xor_si128(flag_bs, _mm_set1_epi8(0xFF)); //Invert for required mask + + q0_uv_16x8 = _mm_loadu_si128((__m128i *)(pu1_src_uv)); + q1_uv_16x8 = _mm_loadu_si128((__m128i *)(pu1_src_uv + i16_posQ1)); + p1_uv_16x8 = _mm_loadu_si128((__m128i *)(pu1_HorzPixelUV + i16_posP1)); + p0_uv_16x8 = _mm_loadu_si128((__m128i *)(pu1_HorzPixelUV + i16_posP0)); + + q0_uv_8x16 = _mm_unpacklo_epi8(q0_uv_16x8, zero); + q1_uv_8x16 = _mm_unpacklo_epi8(q1_uv_16x8, zero); + p1_uv_8x16 = _mm_unpacklo_epi8(p1_uv_16x8, zero); + p0_uv_8x16 = _mm_unpacklo_epi8(p0_uv_16x8, zero); + + diff = _mm_subs_epi16(p0_uv_8x16, q0_uv_8x16); //Condn 1 + diff = _mm_abs_epi16(diff); + alpha_cbcr_16x8 = _mm_set1_epi32(alpha_cbcr); + flag1 = _mm_cmpgt_epi16(alpha_cbcr_16x8, diff); + + diff = _mm_subs_epi16(q1_uv_8x16, q0_uv_8x16); //Condtn 2 + diff = _mm_abs_epi16(diff); + beta_cbcr_16x8 = _mm_set1_epi32(beta_cbcr); + flag1 = _mm_and_si128(flag1, _mm_cmpgt_epi16(beta_cbcr_16x8, diff)); + + diff = _mm_subs_epi16(p1_uv_8x16, p0_uv_8x16); //Condtn 3 + diff = _mm_abs_epi16(diff); + flag1 = _mm_and_si128(flag1, _mm_cmpgt_epi16(beta_cbcr_16x8, diff)); + + diff = _mm_subs_epi16(q0_uv_8x16, p0_uv_8x16); + diff = _mm_slli_epi16(diff, 2); + diff1 = _mm_subs_epi16(p1_uv_8x16, q1_uv_8x16); + diff = _mm_add_epi16(diff, diff1); + diff = _mm_add_epi16(diff, _mm_set1_epi16(4)); + in_macro = _mm_srai_epi16(diff, 3); + + C0_uv_8x16 = _mm_set_epi16(pu1_cliptab_cr[u1_Bs1], pu1_cliptab_cb[u1_Bs1], + pu1_cliptab_cr[u1_Bs1], pu1_cliptab_cb[u1_Bs1], + pu1_cliptab_cr[u1_Bs0], pu1_cliptab_cb[u1_Bs0], + pu1_cliptab_cr[u1_Bs0], pu1_cliptab_cb[u1_Bs0]); + + C0_uv_8x16 = _mm_add_epi16(C0_uv_8x16, _mm_set1_epi16(1)); + + in_macro = _mm_min_epi16(C0_uv_8x16, in_macro); //CLIP3 + C0_uv_8x16 = _mm_subs_epi16(zero, C0_uv_8x16); + in_macro = _mm_max_epi16(C0_uv_8x16, in_macro); + + p0_uv_8x16_1 = _mm_add_epi16(p0_uv_8x16, in_macro); + q0_uv_8x16_1 = _mm_sub_epi16(q0_uv_8x16, in_macro); + + q0_uv_8x16 = _mm_unpackhi_epi8(q0_uv_16x8, zero); + q1_uv_8x16 = _mm_unpackhi_epi8(q1_uv_16x8, zero); + p1_uv_8x16 = _mm_unpackhi_epi8(p1_uv_16x8, zero); + p0_uv_8x16 = _mm_unpackhi_epi8(p0_uv_16x8, zero); + + diff = _mm_subs_epi16(p0_uv_8x16, q0_uv_8x16); //Condn 1 + diff = _mm_abs_epi16(diff); + alpha_cbcr_16x8 = _mm_set1_epi32(alpha_cbcr); + flag2 = _mm_cmpgt_epi16(alpha_cbcr_16x8, diff); + + diff = _mm_subs_epi16(q1_uv_8x16, q0_uv_8x16); //Condtn 2 + diff = _mm_abs_epi16(diff); + beta_cbcr_16x8 = _mm_set1_epi32(beta_cbcr); + flag2 = _mm_and_si128(flag2, _mm_cmpgt_epi16(beta_cbcr_16x8, diff)); + + diff = _mm_subs_epi16(p1_uv_8x16, p0_uv_8x16); //Condtn 3 + diff = _mm_abs_epi16(diff); + flag2 = _mm_and_si128(flag2, _mm_cmpgt_epi16(beta_cbcr_16x8, diff)); + + diff = _mm_subs_epi16(q0_uv_8x16, p0_uv_8x16); + diff = _mm_slli_epi16(diff, 2); + diff1 = _mm_subs_epi16(p1_uv_8x16, q1_uv_8x16); + diff = _mm_add_epi16(diff, diff1); + diff = _mm_add_epi16(diff, _mm_set1_epi16(4)); + in_macro = _mm_srai_epi16(diff, 3); + + C0_uv_8x16 = _mm_set_epi16(pu1_cliptab_cr[u1_Bs3], pu1_cliptab_cb[u1_Bs3], + pu1_cliptab_cr[u1_Bs3], pu1_cliptab_cb[u1_Bs3], + pu1_cliptab_cr[u1_Bs2], pu1_cliptab_cb[u1_Bs2], + pu1_cliptab_cr[u1_Bs2], pu1_cliptab_cb[u1_Bs2]); + + C0_uv_8x16 = _mm_add_epi16(C0_uv_8x16, _mm_set1_epi16(1)); + + in_macro = _mm_min_epi16(C0_uv_8x16, in_macro); //CLIP3 + C0_uv_8x16 = _mm_subs_epi16(zero, C0_uv_8x16); + in_macro = _mm_max_epi16(C0_uv_8x16, in_macro); + + p0_uv_8x16_2 = _mm_add_epi16(p0_uv_8x16, in_macro); + q0_uv_8x16_2 = _mm_sub_epi16(q0_uv_8x16, in_macro); + + p0_uv_8x16_2 = _mm_packus_epi16(p0_uv_8x16_1, p0_uv_8x16_2); + q0_uv_8x16_2 = _mm_packus_epi16(q0_uv_8x16_1, q0_uv_8x16_2); + + flag1 = _mm_packs_epi16(flag1, flag2); + flag1 = _mm_and_si128(flag1, flag_bs); //Final flag (BS condition + other 3 conditions) + + p0_uv_8x16_1 = _mm_and_si128(p0_uv_16x8, + _mm_xor_si128(flag1, _mm_set1_epi8(0xFF))); + p0_uv_8x16_2 = _mm_and_si128(p0_uv_8x16_2, flag1); + p0_uv_8x16_1 = _mm_add_epi8(p0_uv_8x16_1, p0_uv_8x16_2); + _mm_storeu_si128((__m128i *)(pu1_HorzPixelUV + i16_posP0), p0_uv_8x16_1); + + q0_uv_8x16_1 = _mm_and_si128(q0_uv_16x8, + _mm_xor_si128(flag1, _mm_set1_epi8(0xFF))); + q0_uv_8x16_2 = _mm_and_si128(q0_uv_8x16_2, flag1); + q0_uv_8x16_1 = _mm_add_epi8(q0_uv_8x16_1, q0_uv_8x16_2); + _mm_storeu_si128((__m128i *)(pu1_src_uv), q0_uv_8x16_1); + +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_deblk_chroma_vert_bs4_mbaff_ssse3() */ +/* */ +/* Description : This function performs filtering of a chroma block */ +/* vertical edge when boundary strength is set to 4 in high */ +/* profile. */ +/* */ +/* Inputs : pu1_src - pointer to the src sample q0 of U */ +/* src_strd - source stride */ +/* alpha_cb - alpha value for the boundary in U */ +/* beta_cb - beta value for the boundary in U */ +/* alpha_cr - alpha value for the boundary in V */ +/* beta_cr - beta value for the boundary in V */ +/* u4_bs - packed Boundary strength array */ +/* pu1_cliptab_cb - tc0_table for U */ +/* pu1_cliptab_cr - tc0_table for V */ +/* */ +/* Globals : None */ +/* */ +/* Processing : When the function is called twice, this operation is as */ +/* described in Sec. 8.7.2.4 under the title "Filtering */ +/* process for edges for bS equal to 4" in ITU T Rec H.264 */ +/* with alpha and beta values different in U and V. */ +/* */ +/* Outputs : None */ +/* */ +/* Returns : None */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 12 02 2015 Naveen Kumar P Initial version */ +/* */ +/*****************************************************************************/ +void ih264_deblk_chroma_vert_bs4_mbaff_ssse3(UWORD8 *pu1_src, + WORD32 src_strd, + WORD32 alpha_cb, + WORD32 beta_cb, + WORD32 alpha_cr, + WORD32 beta_cr) +{ + UWORD8 *pu1_src_uv = pu1_src; /* Pointer to the src sample q0 of plane U*/ + WORD32 alpha_cbcr = (alpha_cr << 16) + alpha_cb; + WORD32 beta_cbcr = (beta_cr << 16) + beta_cb; + __m128i linea, lineb, linec, lined; + __m128i temp1, temp2; + + __m128i q0_uv_16x8, p0_uv_16x8, q1_uv_16x8, p1_uv_16x8; + __m128i q0_uv_8x16, p0_uv_8x16, q1_uv_8x16, p1_uv_8x16; + __m128i flag1; + __m128i diff, alpha_cbcr_16x8, beta_cbcr_16x8; + __m128i zero = _mm_setzero_si128(); + __m128i p0_uv_8x16_1, p0_uv_8x16_2, q0_uv_8x16_1, q0_uv_8x16_2; + + /* Load and transpose the pixel values */ + linea = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4)); + lineb = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4 + src_strd)); + linec = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4 + 2 * src_strd)); + lined = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4 + 3 * src_strd)); + + temp1 = _mm_unpacklo_epi16(linea, lineb); + temp2 = _mm_unpacklo_epi16(linec, lined); + + p1_uv_16x8 = _mm_unpacklo_epi32(temp1, temp2); + p0_uv_16x8 = _mm_srli_si128(p1_uv_16x8, 8); + q0_uv_16x8 = _mm_unpackhi_epi32(temp1, temp2); + q1_uv_16x8 = _mm_srli_si128(q0_uv_16x8, 8); + /* End of transpose */ + + q0_uv_8x16 = _mm_unpacklo_epi8(q0_uv_16x8, zero); + q1_uv_8x16 = _mm_unpacklo_epi8(q1_uv_16x8, zero); + p1_uv_8x16 = _mm_unpacklo_epi8(p1_uv_16x8, zero); + p0_uv_8x16 = _mm_unpacklo_epi8(p0_uv_16x8, zero); + + diff = _mm_subs_epi16(p0_uv_8x16, q0_uv_8x16); //Condn 1 + diff = _mm_abs_epi16(diff); + alpha_cbcr_16x8 = _mm_set1_epi32(alpha_cbcr); + flag1 = _mm_cmpgt_epi16(alpha_cbcr_16x8, diff); + + diff = _mm_subs_epi16(q1_uv_8x16, q0_uv_8x16); //Condtn 2 + diff = _mm_abs_epi16(diff); + beta_cbcr_16x8 = _mm_set1_epi32(beta_cbcr); + flag1 = _mm_and_si128(flag1, _mm_cmpgt_epi16(beta_cbcr_16x8, diff)); + + diff = _mm_subs_epi16(p1_uv_8x16, p0_uv_8x16); //Condtn 3 + diff = _mm_abs_epi16(diff); + flag1 = _mm_and_si128(flag1, _mm_cmpgt_epi16(beta_cbcr_16x8, diff)); + + temp1 = _mm_slli_epi16(p1_uv_8x16, 1); + temp2 = _mm_add_epi16(p0_uv_8x16, q1_uv_8x16); + temp1 = _mm_add_epi16(temp1, _mm_set1_epi16(2)); + temp1 = _mm_add_epi16(temp1, temp2); + p0_uv_8x16_1 = _mm_srai_epi16(temp1, 2); + + temp1 = _mm_slli_epi16(q1_uv_8x16, 1); + temp2 = _mm_add_epi16(p1_uv_8x16, q0_uv_8x16); + temp1 = _mm_add_epi16(temp1, _mm_set1_epi16(2)); + temp1 = _mm_add_epi16(temp1, temp2); + q0_uv_8x16_1 = _mm_srai_epi16(temp1, 2); + + p0_uv_8x16_2 = _mm_packus_epi16(p0_uv_8x16_1, p0_uv_8x16_1); + q0_uv_8x16_2 = _mm_packus_epi16(q0_uv_8x16_1, q0_uv_8x16_1); + + flag1 = _mm_packs_epi16(flag1, flag1); + + p0_uv_8x16_1 = _mm_and_si128(p0_uv_16x8, + _mm_xor_si128(flag1, _mm_set1_epi8(0xFF))); + p0_uv_8x16_2 = _mm_and_si128(p0_uv_8x16_2, flag1); + p0_uv_16x8 = _mm_add_epi8(p0_uv_8x16_1, p0_uv_8x16_2); + + q0_uv_8x16_1 = _mm_and_si128(q0_uv_16x8, + _mm_xor_si128(flag1, _mm_set1_epi8(0xFF))); + q0_uv_8x16_2 = _mm_and_si128(q0_uv_8x16_2, flag1); + q0_uv_16x8 = _mm_add_epi8(q0_uv_8x16_1, q0_uv_8x16_2); + + /* Inverse-transpose and store back */ + temp1 = _mm_unpacklo_epi16(p1_uv_16x8, p0_uv_16x8); + temp2 = _mm_unpacklo_epi16(q0_uv_16x8, q1_uv_16x8); + + linea = _mm_unpacklo_epi32(temp1, temp2); + lineb = _mm_srli_si128(linea, 8); + linec = _mm_unpackhi_epi32(temp1, temp2); + lined = _mm_srli_si128(linec, 8); + + _mm_storel_epi64((__m128i *)(pu1_src_uv - 4), linea); + _mm_storel_epi64((__m128i *)(pu1_src_uv - 4 + src_strd), lineb); + _mm_storel_epi64((__m128i *)(pu1_src_uv - 4 + 2 * src_strd), linec); + _mm_storel_epi64((__m128i *)(pu1_src_uv - 4 + 3 * src_strd), lined); + +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_deblk_chroma_vert_bslt4_mbaff_ssse3() */ +/* */ +/* Description : This function performs filtering of a chroma block */ +/* vertical edge when boundary strength is less than 4 in */ +/* high profile. */ +/* */ +/* Inputs : pu1_src - pointer to the src sample q0 of U */ +/* src_strd - source stride */ +/* alpha_cb - alpha value for the boundary in U */ +/* beta_cb - beta value for the boundary in U */ +/* alpha_cr - alpha value for the boundary in V */ +/* beta_cr - beta value for the boundary in V */ +/* u4_bs - packed Boundary strength array */ +/* pu1_cliptab_cb - tc0_table for U */ +/* pu1_cliptab_cr - tc0_table for V */ +/* */ +/* Globals : None */ +/* */ +/* Processing : When the function is called twice, this operation is as */ +/* described in Sec. 8.7.2.4 under the title "Filtering */ +/* process for edges for bS less than 4" in ITU T Rec H.264 */ +/* with alpha and beta values different in U and V. */ +/* */ +/* Outputs : None */ +/* */ +/* Returns : None */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 12 02 2015 Naveen Kumar P Initial version */ +/* */ +/*****************************************************************************/ +void ih264_deblk_chroma_vert_bslt4_mbaff_ssse3(UWORD8 *pu1_src, + WORD32 src_strd, + WORD32 alpha_cb, + WORD32 beta_cb, + WORD32 alpha_cr, + WORD32 beta_cr, + UWORD32 u4_bs, + const UWORD8 *pu1_cliptab_cb, + const UWORD8 *pu1_cliptab_cr) +{ + UWORD8 *pu1_src_uv = pu1_src; /* Pointer to the src sample q0 of plane U*/ + UWORD8 u1_Bs0, u1_Bs1, u1_Bs2, u1_Bs3; + WORD32 alpha_cbcr = (alpha_cr << 16) + alpha_cb; + WORD32 beta_cbcr = (beta_cr << 16) + beta_cb; + __m128i linea, lineb, linec, lined; + __m128i temp1, temp2; + + __m128i q0_uv_16x8, p0_uv_16x8, q1_uv_16x8, p1_uv_16x8; + __m128i q0_uv_8x16, p0_uv_8x16, q1_uv_8x16, p1_uv_8x16; + __m128i flag_bs, flag1; + __m128i diff, diff1, alpha_cbcr_16x8, beta_cbcr_16x8, in_macro; + __m128i zero = _mm_setzero_si128(); + __m128i C0_uv_8x16; + __m128i p0_uv_8x16_1, p0_uv_8x16_2, q0_uv_8x16_1, q0_uv_8x16_2; + + u1_Bs0 = (u4_bs >> 24) & 0xff; + u1_Bs1 = (u4_bs >> 16) & 0xff; + u1_Bs2 = (u4_bs >> 8) & 0xff; + u1_Bs3 = (u4_bs >> 0) & 0xff; + + flag_bs = _mm_set_epi8(0, 0, 0, 0, 0, 0, 0, 0, u1_Bs3, u1_Bs3, u1_Bs2, + u1_Bs2, u1_Bs1, u1_Bs1, u1_Bs0, u1_Bs0); + flag_bs = _mm_cmpeq_epi8(flag_bs, zero); //Set flag to 1s and 0s + flag_bs = _mm_xor_si128(flag_bs, _mm_set1_epi8(0xFF)); //Invert for required mask + + /* Load and transpose the pixel values */ + linea = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4)); + lineb = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4 + src_strd)); + linec = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4 + 2 * src_strd)); + lined = _mm_loadl_epi64((__m128i *)(pu1_src_uv - 4 + 3 * src_strd)); + + temp1 = _mm_unpacklo_epi16(linea, lineb); + temp2 = _mm_unpacklo_epi16(linec, lined); + + p1_uv_16x8 = _mm_unpacklo_epi32(temp1, temp2); + p0_uv_16x8 = _mm_srli_si128(p1_uv_16x8, 8); + q0_uv_16x8 = _mm_unpackhi_epi32(temp1, temp2); + q1_uv_16x8 = _mm_srli_si128(q0_uv_16x8, 8); + /* End of transpose */ + + q0_uv_8x16 = _mm_unpacklo_epi8(q0_uv_16x8, zero); + q1_uv_8x16 = _mm_unpacklo_epi8(q1_uv_16x8, zero); + p1_uv_8x16 = _mm_unpacklo_epi8(p1_uv_16x8, zero); + p0_uv_8x16 = _mm_unpacklo_epi8(p0_uv_16x8, zero); + + diff = _mm_subs_epi16(p0_uv_8x16, q0_uv_8x16); //Condn 1 + diff = _mm_abs_epi16(diff); + alpha_cbcr_16x8 = _mm_set1_epi32(alpha_cbcr); + flag1 = _mm_cmpgt_epi16(alpha_cbcr_16x8, diff); + + diff = _mm_subs_epi16(q1_uv_8x16, q0_uv_8x16); //Condtn 2 + diff = _mm_abs_epi16(diff); + beta_cbcr_16x8 = _mm_set1_epi32(beta_cbcr); + flag1 = _mm_and_si128(flag1, _mm_cmpgt_epi16(beta_cbcr_16x8, diff)); + + diff = _mm_subs_epi16(p1_uv_8x16, p0_uv_8x16); //Condtn 3 + diff = _mm_abs_epi16(diff); + flag1 = _mm_and_si128(flag1, _mm_cmpgt_epi16(beta_cbcr_16x8, diff)); + + diff = _mm_subs_epi16(q0_uv_8x16, p0_uv_8x16); + diff = _mm_slli_epi16(diff, 2); + diff1 = _mm_subs_epi16(p1_uv_8x16, q1_uv_8x16); + diff = _mm_add_epi16(diff, diff1); + diff = _mm_add_epi16(diff, _mm_set1_epi16(4)); + in_macro = _mm_srai_epi16(diff, 3); + + C0_uv_8x16 = _mm_set_epi16(pu1_cliptab_cr[u1_Bs3], pu1_cliptab_cb[u1_Bs3], + pu1_cliptab_cr[u1_Bs2], pu1_cliptab_cb[u1_Bs2], + pu1_cliptab_cr[u1_Bs1], pu1_cliptab_cb[u1_Bs1], + pu1_cliptab_cr[u1_Bs0], pu1_cliptab_cb[u1_Bs0]); + + C0_uv_8x16 = _mm_add_epi16(C0_uv_8x16, _mm_set1_epi16(1)); + + in_macro = _mm_min_epi16(C0_uv_8x16, in_macro); //CLIP3 + C0_uv_8x16 = _mm_subs_epi16(zero, C0_uv_8x16); + in_macro = _mm_max_epi16(C0_uv_8x16, in_macro); + + p0_uv_8x16_1 = _mm_add_epi16(p0_uv_8x16, in_macro); + q0_uv_8x16_1 = _mm_sub_epi16(q0_uv_8x16, in_macro); + + p0_uv_8x16_2 = _mm_packus_epi16(p0_uv_8x16_1, p0_uv_8x16_1); + q0_uv_8x16_2 = _mm_packus_epi16(q0_uv_8x16_1, q0_uv_8x16_1); + + flag1 = _mm_packs_epi16(flag1, flag1); + flag1 = _mm_and_si128(flag1, flag_bs); //Final flag (BS condition + other 3 conditions) + + p0_uv_8x16_1 = _mm_and_si128(p0_uv_16x8, + _mm_xor_si128(flag1, _mm_set1_epi8(0xFF))); + p0_uv_8x16_2 = _mm_and_si128(p0_uv_8x16_2, flag1); + p0_uv_16x8 = _mm_add_epi8(p0_uv_8x16_1, p0_uv_8x16_2); + + q0_uv_8x16_1 = _mm_and_si128(q0_uv_16x8, + _mm_xor_si128(flag1, _mm_set1_epi8(0xFF))); + q0_uv_8x16_2 = _mm_and_si128(q0_uv_8x16_2, flag1); + q0_uv_16x8 = _mm_add_epi8(q0_uv_8x16_1, q0_uv_8x16_2); + + /* Inverse-transpose and store back */ + temp1 = _mm_unpacklo_epi16(p1_uv_16x8, p0_uv_16x8); + temp2 = _mm_unpacklo_epi16(q0_uv_16x8, q1_uv_16x8); + + linea = _mm_unpacklo_epi32(temp1, temp2); + lineb = _mm_srli_si128(linea, 8); + linec = _mm_unpackhi_epi32(temp1, temp2); + lined = _mm_srli_si128(linec, 8); + + _mm_storel_epi64((__m128i *)(pu1_src_uv - 4), linea); + _mm_storel_epi64((__m128i *)(pu1_src_uv - 4 + src_strd), lineb); + _mm_storel_epi64((__m128i *)(pu1_src_uv - 4 + 2 * src_strd), linec); + _mm_storel_epi64((__m128i *)(pu1_src_uv - 4 + 3 * src_strd), lined); + +} + diff --git a/dependencies/ih264d/common/x86/ih264_deblk_luma_ssse3.c b/dependencies/ih264d/common/x86/ih264_deblk_luma_ssse3.c new file mode 100644 index 00000000..e29bebbe --- /dev/null +++ b/dependencies/ih264d/common/x86/ih264_deblk_luma_ssse3.c @@ -0,0 +1,2012 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/*****************************************************************************/ +/* */ +/* File Name : ih264_deblk_luma_ssse3.c */ +/* */ +/* Description : Contains function definitions for deblocking */ +/* */ +/* List of Functions : ih264_deblk_luma_vert_bs4_ssse3() */ +/* ih264_deblk_luma_horz_bs4_ssse3() */ +/* ih264_deblk_luma_vert_bslt4_ssse3() */ +/* ih264_deblk_luma_horz_bslt4_ssse3() */ +/* ih264_deblk_luma_vert_bs4_mbaff_ssse3() */ +/* ih264_deblk_luma_vert_bslt4_mbaff_ssse3() */ +/* */ +/* Issues / Problems : None */ +/* */ +/* Revision History : */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 12 02 2015 Naveen Kumar P Added luma deblocking ssse3 */ +/* intrinsics */ +/* */ +/*****************************************************************************/ + +/*****************************************************************************/ +/* File Includes */ +/*****************************************************************************/ + +/* System include files */ +#include <stdio.h> + +/* User include files */ +#include "ih264_typedefs.h" +#include "ih264_platform_macros.h" +#include "ih264_deblk_edge_filters.h" +#include "ih264_macros.h" + +/*****************************************************************************/ +/* Function Definitions */ +/*****************************************************************************/ + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_deblk_luma_vert_bs4_ssse3() */ +/* */ +/* Description : This function performs filtering of a luma block */ +/* vertical edge when the boundary strength is set to 4. */ +/* */ +/* Inputs : pu1_src - pointer to the src sample q0 */ +/* src_strd - source stride */ +/* alpha - alpha value for the boundary */ +/* beta - beta value for the boundary */ +/* */ +/* Globals : None */ +/* */ +/* Processing : This operation is described in Sec. 8.7.2.4 under the */ +/* title "Filtering process for edges for bS equal to 4" in */ +/* ITU T Rec H.264. */ +/* */ +/* Outputs : None */ +/* */ +/* Returns : None */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 12 02 2015 Naveen Kumar P Initial version */ +/* */ +/*****************************************************************************/ +void ih264_deblk_luma_vert_bs4_ssse3(UWORD8 *pu1_src, + WORD32 src_strd, + WORD32 alpha, + WORD32 beta) +{ + __m128i zero = _mm_setzero_si128(); + __m128i q0_16x8, q1_16x8, q2_16x8, q3_16x8; + __m128i p0_16x8, p1_16x8, p2_16x8, p3_16x8; + __m128i q0_8x16, q1_8x16, q2_8x16, q3_8x16; + __m128i p0_8x16, p1_8x16, p2_8x16, p3_8x16; + __m128i q0_16x8_1; + __m128i p0_16x8_1; + __m128i q0_16x8_2, q1_16x8_2, q2_16x8_2; + __m128i p0_16x8_2, p1_16x8_2, p2_16x8_2; + __m128i temp1, temp2, temp3, temp4, temp5, temp6; + __m128i Alpha_8x16, Beta_8x16; + __m128i flag1_16x8, flag2_16x8, flag3_16x8, flag4_16x8; + __m128i const_val2_16x8 = _mm_set1_epi16(2); + __m128i line1, line2, line3, line4, line5, line6, line7, line8; + + Alpha_8x16 = _mm_set1_epi16(alpha); + Beta_8x16 = _mm_set1_epi16(beta); + + line1 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 0 * src_strd)); + line2 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 1 * src_strd)); + line3 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 2 * src_strd)); + line4 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 3 * src_strd)); + line5 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 4 * src_strd)); + line6 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 5 * src_strd)); + line7 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 6 * src_strd)); + line8 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 7 * src_strd)); + + temp1 = _mm_unpacklo_epi8(line1, line2); + temp2 = _mm_unpacklo_epi8(line3, line4); + temp3 = _mm_unpacklo_epi8(line5, line6); + temp4 = _mm_unpacklo_epi8(line7, line8); + + line1 = _mm_unpacklo_epi16(temp1, temp2); + line2 = _mm_unpackhi_epi16(temp1, temp2); + line3 = _mm_unpacklo_epi16(temp3, temp4); + line4 = _mm_unpackhi_epi16(temp3, temp4); + + p1_8x16 = _mm_unpacklo_epi32(line1, line3); + p0_8x16 = _mm_unpackhi_epi32(line1, line3); + q0_8x16 = _mm_unpacklo_epi32(line2, line4); + q1_8x16 = _mm_unpackhi_epi32(line2, line4); + + line1 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 8 * src_strd)); + line2 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 9 * src_strd)); + line3 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 10 * src_strd)); + line4 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 11 * src_strd)); + line5 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 12 * src_strd)); + line6 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 13 * src_strd)); + line7 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 14 * src_strd)); + line8 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 15 * src_strd)); + + temp1 = _mm_unpacklo_epi8(line1, line2); + temp2 = _mm_unpacklo_epi8(line3, line4); + temp3 = _mm_unpacklo_epi8(line5, line6); + temp4 = _mm_unpacklo_epi8(line7, line8); + + line1 = _mm_unpacklo_epi16(temp1, temp2); + line2 = _mm_unpackhi_epi16(temp1, temp2); + line3 = _mm_unpacklo_epi16(temp3, temp4); + line4 = _mm_unpackhi_epi16(temp3, temp4); + + temp1 = _mm_unpacklo_epi32(line1, line3); + temp2 = _mm_unpackhi_epi32(line1, line3); + temp3 = _mm_unpacklo_epi32(line2, line4); + temp4 = _mm_unpackhi_epi32(line2, line4); + + p3_16x8 = _mm_unpacklo_epi64(p1_8x16, temp1); + p2_16x8 = _mm_unpackhi_epi64(p1_8x16, temp1); + q2_16x8 = _mm_unpacklo_epi64(q1_8x16, temp4); + q3_16x8 = _mm_unpackhi_epi64(q1_8x16, temp4); + p1_16x8 = _mm_unpacklo_epi64(p0_8x16, temp2); + p0_16x8 = _mm_unpackhi_epi64(p0_8x16, temp2); + q0_16x8 = _mm_unpacklo_epi64(q0_8x16, temp3); + q1_16x8 = _mm_unpackhi_epi64(q0_8x16, temp3); + + //Cond1 (ABS(p0 - q0) < alpha) + temp1 = _mm_subs_epu8(q0_16x8, p0_16x8); + temp2 = _mm_subs_epu8(p0_16x8, q0_16x8); + temp1 = _mm_add_epi8(temp1, temp2); + + temp2 = _mm_unpacklo_epi8(temp1, zero); + temp1 = _mm_unpackhi_epi8(temp1, zero); + + temp2 = _mm_cmpgt_epi16(Alpha_8x16, temp2); + temp1 = _mm_cmpgt_epi16(Alpha_8x16, temp1); + + flag1_16x8 = _mm_packs_epi16(temp2, temp1); + + //Cond2 (ABS(q1 - q0) < beta) + temp1 = _mm_subs_epu8(q0_16x8, q1_16x8); + temp2 = _mm_subs_epu8(q1_16x8, q0_16x8); + temp1 = _mm_add_epi8(temp1, temp2); + + temp2 = _mm_unpacklo_epi8(temp1, zero); + temp1 = _mm_unpackhi_epi8(temp1, zero); + + temp2 = _mm_cmpgt_epi16(Beta_8x16, temp2); + temp1 = _mm_cmpgt_epi16(Beta_8x16, temp1); + + flag2_16x8 = _mm_packs_epi16(temp2, temp1); + + flag1_16x8 = _mm_and_si128(flag1_16x8, flag2_16x8); + + //Cond3 (ABS(p1 - p0) < beta) + temp1 = _mm_subs_epu8(p0_16x8, p1_16x8); + temp2 = _mm_subs_epu8(p1_16x8, p0_16x8); + temp1 = _mm_add_epi8(temp1, temp2); + + temp2 = _mm_unpacklo_epi8(temp1, zero); + temp1 = _mm_unpackhi_epi8(temp1, zero); + + temp2 = _mm_cmpgt_epi16(Beta_8x16, temp2); + temp1 = _mm_cmpgt_epi16(Beta_8x16, temp1); + + flag2_16x8 = _mm_packs_epi16(temp2, temp1); + + // !((ABS(p0 - q0) < alpha) || (ABS(q1 - q0) < beta) || (ABS(p1 - p0) < beta)) + flag1_16x8 = _mm_and_si128(flag1_16x8, flag2_16x8); + + // (ABS(p0 - q0) < ((alpha >> 2) + 2)) + temp1 = _mm_subs_epu8(p0_16x8, q0_16x8); + temp2 = _mm_subs_epu8(q0_16x8, p0_16x8); + temp1 = _mm_add_epi8(temp1, temp2); + Alpha_8x16 = _mm_srai_epi16(Alpha_8x16, 2); + Alpha_8x16 = _mm_add_epi16(Alpha_8x16, const_val2_16x8); + + temp2 = _mm_unpacklo_epi8(temp1, zero); + temp1 = _mm_unpackhi_epi8(temp1, zero); + temp2 = _mm_cmpgt_epi16(Alpha_8x16, temp2); + temp1 = _mm_cmpgt_epi16(Alpha_8x16, temp1); + + flag2_16x8 = _mm_packs_epi16(temp2, temp1); + flag2_16x8 = _mm_and_si128(flag1_16x8, flag2_16x8); + + // (ABS(p2 - p0) < beta) + temp1 = _mm_subs_epu8(p0_16x8, p2_16x8); + temp2 = _mm_subs_epu8(p2_16x8, p0_16x8); + temp1 = _mm_add_epi8(temp1, temp2); + + temp2 = _mm_unpacklo_epi8(temp1, zero); + temp1 = _mm_unpackhi_epi8(temp1, zero); + temp2 = _mm_cmpgt_epi16(Beta_8x16, temp2); + temp1 = _mm_cmpgt_epi16(Beta_8x16, temp1); + + flag3_16x8 = _mm_packs_epi16(temp2, temp1); + flag3_16x8 = _mm_and_si128(flag3_16x8, flag2_16x8); + + // (ABS(q2 - q0) < beta) + temp1 = _mm_subs_epu8(q0_16x8, q2_16x8); + temp2 = _mm_subs_epu8(q2_16x8, q0_16x8); + temp1 = _mm_add_epi8(temp1, temp2); + + temp2 = _mm_unpacklo_epi8(temp1, zero); + temp1 = _mm_unpackhi_epi8(temp1, zero); + temp2 = _mm_cmpgt_epi16(Beta_8x16, temp2); + temp1 = _mm_cmpgt_epi16(Beta_8x16, temp1); + + flag4_16x8 = _mm_packs_epi16(temp2, temp1); + flag4_16x8 = _mm_and_si128(flag4_16x8, flag2_16x8); + + // First 8 pixels + p3_8x16 = _mm_unpacklo_epi8(p3_16x8, zero); + p2_8x16 = _mm_unpacklo_epi8(p2_16x8, zero); + p1_8x16 = _mm_unpacklo_epi8(p1_16x8, zero); + p0_8x16 = _mm_unpacklo_epi8(p0_16x8, zero); + q0_8x16 = _mm_unpacklo_epi8(q0_16x8, zero); + q1_8x16 = _mm_unpacklo_epi8(q1_16x8, zero); + q2_8x16 = _mm_unpacklo_epi8(q2_16x8, zero); + q3_8x16 = _mm_unpacklo_epi8(q3_16x8, zero); + + // p0_1 and q0_1 + temp1 = _mm_add_epi16(p0_8x16, q1_8x16); + temp2 = _mm_add_epi16(p1_8x16, q0_8x16); + temp5 = _mm_add_epi16(temp1, const_val2_16x8); + temp6 = _mm_add_epi16(temp2, const_val2_16x8); + temp3 = _mm_slli_epi16(p1_8x16, 1); + temp4 = _mm_slli_epi16(q1_8x16, 1); + temp1 = _mm_add_epi16(temp5, temp3); + temp2 = _mm_add_epi16(temp6, temp4); + p0_16x8_1 = _mm_srai_epi16(temp1, 2); + q0_16x8_1 = _mm_srai_epi16(temp2, 2); + + // p1_2 and q1_2 + temp6 = _mm_add_epi16(temp6, p0_8x16); + temp5 = _mm_add_epi16(temp5, q0_8x16); + temp1 = _mm_add_epi16(temp6, p2_8x16); + temp2 = _mm_add_epi16(temp5, q2_8x16); + p1_16x8_2 = _mm_srai_epi16(temp1, 2); + q1_16x8_2 = _mm_srai_epi16(temp2, 2); + + // p0_2 and q0_2 + temp1 = _mm_add_epi16(temp3, p2_8x16); + temp2 = _mm_add_epi16(temp4, q2_8x16); + temp1 = _mm_add_epi16(temp1, q1_8x16); + temp2 = _mm_add_epi16(temp2, p1_8x16); + temp3 = _mm_add_epi16(p0_8x16, q0_8x16); + temp3 = _mm_slli_epi16(temp3, 1); + temp1 = _mm_add_epi16(temp1, temp3); + temp2 = _mm_add_epi16(temp2, temp3); + temp1 = _mm_add_epi16(temp1, _mm_set1_epi16(4)); + temp2 = _mm_add_epi16(temp2, _mm_set1_epi16(4)); + p0_16x8_2 = _mm_srai_epi16(temp1, 3); + q0_16x8_2 = _mm_srai_epi16(temp2, 3); + + // p2_2 and q2_2 + temp1 = _mm_add_epi16(temp6, const_val2_16x8); + temp2 = _mm_add_epi16(temp5, const_val2_16x8); + temp3 = _mm_slli_epi16(p2_8x16, 1); + temp4 = _mm_slli_epi16(q2_8x16, 1); + temp3 = _mm_add_epi16(p2_8x16, temp3); + temp4 = _mm_add_epi16(q2_8x16, temp4); + temp5 = _mm_slli_epi16(p3_8x16, 1); + temp6 = _mm_slli_epi16(q3_8x16, 1); + temp1 = _mm_add_epi16(temp1, temp3); + temp2 = _mm_add_epi16(temp2, temp4); + temp1 = _mm_add_epi16(temp1, temp5); + temp2 = _mm_add_epi16(temp2, temp6); + p2_16x8_2 = _mm_srai_epi16(temp1, 3); + q2_16x8_2 = _mm_srai_epi16(temp2, 3); + + // Second 8 pixels and packing with first 8 pixels + p3_8x16 = _mm_unpackhi_epi8(p3_16x8, zero); + p2_8x16 = _mm_unpackhi_epi8(p2_16x8, zero); + p1_8x16 = _mm_unpackhi_epi8(p1_16x8, zero); + p0_8x16 = _mm_unpackhi_epi8(p0_16x8, zero); + q0_8x16 = _mm_unpackhi_epi8(q0_16x8, zero); + q1_8x16 = _mm_unpackhi_epi8(q1_16x8, zero); + q2_8x16 = _mm_unpackhi_epi8(q2_16x8, zero); + q3_8x16 = _mm_unpackhi_epi8(q3_16x8, zero); + + // p0_1 and q0_1 + temp1 = _mm_add_epi16(p0_8x16, q1_8x16); + temp2 = _mm_add_epi16(p1_8x16, q0_8x16); + temp5 = _mm_add_epi16(temp1, const_val2_16x8); + temp6 = _mm_add_epi16(temp2, const_val2_16x8); + temp3 = _mm_slli_epi16(p1_8x16, 1); + temp4 = _mm_slli_epi16(q1_8x16, 1); + temp1 = _mm_add_epi16(temp5, temp3); + temp2 = _mm_add_epi16(temp6, temp4); + temp1 = _mm_srai_epi16(temp1, 2); + temp2 = _mm_srai_epi16(temp2, 2); + p0_16x8_1 = _mm_packus_epi16(p0_16x8_1, temp1); + q0_16x8_1 = _mm_packus_epi16(q0_16x8_1, temp2); + + // p1_2 and q1_2 + temp6 = _mm_add_epi16(temp6, p0_8x16); + temp5 = _mm_add_epi16(temp5, q0_8x16); + temp1 = _mm_add_epi16(temp6, p2_8x16); + temp2 = _mm_add_epi16(temp5, q2_8x16); + temp1 = _mm_srai_epi16(temp1, 2); + temp2 = _mm_srai_epi16(temp2, 2); + p1_16x8_2 = _mm_packus_epi16(p1_16x8_2, temp1); + q1_16x8_2 = _mm_packus_epi16(q1_16x8_2, temp2); + + // p0_2 and q0_2 + temp1 = _mm_add_epi16(temp3, p2_8x16); + temp2 = _mm_add_epi16(temp4, q2_8x16); + temp1 = _mm_add_epi16(temp1, q1_8x16); + temp2 = _mm_add_epi16(temp2, p1_8x16); + temp3 = _mm_add_epi16(p0_8x16, q0_8x16); + temp3 = _mm_slli_epi16(temp3, 1); + temp1 = _mm_add_epi16(temp1, temp3); + temp2 = _mm_add_epi16(temp2, temp3); + temp1 = _mm_add_epi16(temp1, _mm_set1_epi16(4)); + temp2 = _mm_add_epi16(temp2, _mm_set1_epi16(4)); + temp1 = _mm_srai_epi16(temp1, 3); + temp2 = _mm_srai_epi16(temp2, 3); + p0_16x8_2 = _mm_packus_epi16(p0_16x8_2, temp1); + q0_16x8_2 = _mm_packus_epi16(q0_16x8_2, temp2); + + // p2_2 and q2_2 + temp1 = _mm_add_epi16(temp6, const_val2_16x8); + temp2 = _mm_add_epi16(temp5, const_val2_16x8); + temp3 = _mm_slli_epi16(p2_8x16, 1); + temp4 = _mm_slli_epi16(q2_8x16, 1); + temp3 = _mm_add_epi16(p2_8x16, temp3); + temp4 = _mm_add_epi16(q2_8x16, temp4); + temp5 = _mm_slli_epi16(p3_8x16, 1); + temp6 = _mm_slli_epi16(q3_8x16, 1); + temp1 = _mm_add_epi16(temp1, temp3); + temp2 = _mm_add_epi16(temp2, temp4); + temp1 = _mm_add_epi16(temp1, temp5); + temp2 = _mm_add_epi16(temp2, temp6); + temp1 = _mm_srai_epi16(temp1, 3); + temp2 = _mm_srai_epi16(temp2, 3); + p2_16x8_2 = _mm_packus_epi16(p2_16x8_2, temp1); + q2_16x8_2 = _mm_packus_epi16(q2_16x8_2, temp2); + + // p0 and q0 + p0_16x8 = _mm_and_si128(p0_16x8, + _mm_xor_si128(flag1_16x8, _mm_set1_epi8(0xFF))); + p0_16x8_1 = _mm_and_si128(p0_16x8_1, flag1_16x8); + p0_16x8 = _mm_add_epi8(p0_16x8, p0_16x8_1); + q0_16x8 = _mm_and_si128(q0_16x8, + _mm_xor_si128(flag1_16x8, _mm_set1_epi8(0xFF))); + q0_16x8_1 = _mm_and_si128(q0_16x8_1, flag1_16x8); + q0_16x8 = _mm_add_epi8(q0_16x8, q0_16x8_1); + + // p0 and q0 + p0_16x8 = _mm_and_si128(p0_16x8, + _mm_xor_si128(flag3_16x8, _mm_set1_epi8(0xFF))); + p0_16x8_2 = _mm_and_si128(p0_16x8_2, flag3_16x8); + p0_16x8 = _mm_add_epi8(p0_16x8, p0_16x8_2); + q0_16x8 = _mm_and_si128(q0_16x8, + _mm_xor_si128(flag4_16x8, _mm_set1_epi8(0xFF))); + q0_16x8_2 = _mm_and_si128(q0_16x8_2, flag4_16x8); + q0_16x8 = _mm_add_epi8(q0_16x8, q0_16x8_2); + + // p1 and q1 + p1_16x8 = _mm_and_si128(p1_16x8, + _mm_xor_si128(flag3_16x8, _mm_set1_epi8(0xFF))); + p1_16x8_2 = _mm_and_si128(p1_16x8_2, flag3_16x8); + p1_16x8 = _mm_add_epi8(p1_16x8, p1_16x8_2); + q1_16x8 = _mm_and_si128(q1_16x8, + _mm_xor_si128(flag4_16x8, _mm_set1_epi8(0xFF))); + q1_16x8_2 = _mm_and_si128(q1_16x8_2, flag4_16x8); + q1_16x8 = _mm_add_epi8(q1_16x8, q1_16x8_2); + + // p2 and q2 + p2_16x8 = _mm_and_si128(p2_16x8, + _mm_xor_si128(flag3_16x8, _mm_set1_epi8(0xFF))); + p2_16x8_2 = _mm_and_si128(p2_16x8_2, flag3_16x8); + p2_16x8 = _mm_add_epi8(p2_16x8, p2_16x8_2); + q2_16x8 = _mm_and_si128(q2_16x8, + _mm_xor_si128(flag4_16x8, _mm_set1_epi8(0xFF))); + q2_16x8_2 = _mm_and_si128(q2_16x8_2, flag4_16x8); + q2_16x8 = _mm_add_epi8(q2_16x8, q2_16x8_2); + + temp1 = _mm_unpacklo_epi8(p3_16x8, p2_16x8); + temp2 = _mm_unpacklo_epi8(p1_16x8, p0_16x8); + temp3 = _mm_unpacklo_epi8(q0_16x8, q1_16x8); + temp4 = _mm_unpacklo_epi8(q2_16x8, q3_16x8); + + p3_8x16 = _mm_unpacklo_epi16(temp1, temp2); + p2_8x16 = _mm_unpackhi_epi16(temp1, temp2); + q2_8x16 = _mm_unpacklo_epi16(temp3, temp4); + q3_8x16 = _mm_unpackhi_epi16(temp3, temp4); + + line1 = _mm_unpacklo_epi32(p3_8x16, q2_8x16); + line2 = _mm_srli_si128(line1, 8); + line3 = _mm_unpackhi_epi32(p3_8x16, q2_8x16); + line4 = _mm_srli_si128(line3, 8); + line5 = _mm_unpacklo_epi32(p2_8x16, q3_8x16); + line6 = _mm_srli_si128(line5, 8); + line7 = _mm_unpackhi_epi32(p2_8x16, q3_8x16); + line8 = _mm_srli_si128(line7, 8); + + _mm_storel_epi64((__m128i *)(pu1_src - 4 + 0 * src_strd), line1); + _mm_storel_epi64((__m128i *)(pu1_src - 4 + 1 * src_strd), line2); + _mm_storel_epi64((__m128i *)(pu1_src - 4 + 2 * src_strd), line3); + _mm_storel_epi64((__m128i *)(pu1_src - 4 + 3 * src_strd), line4); + _mm_storel_epi64((__m128i *)(pu1_src - 4 + 4 * src_strd), line5); + _mm_storel_epi64((__m128i *)(pu1_src - 4 + 5 * src_strd), line6); + _mm_storel_epi64((__m128i *)(pu1_src - 4 + 6 * src_strd), line7); + _mm_storel_epi64((__m128i *)(pu1_src - 4 + 7 * src_strd), line8); + + temp1 = _mm_unpackhi_epi8(p3_16x8, p2_16x8); + temp2 = _mm_unpackhi_epi8(p1_16x8, p0_16x8); + temp3 = _mm_unpackhi_epi8(q0_16x8, q1_16x8); + temp4 = _mm_unpackhi_epi8(q2_16x8, q3_16x8); + + p3_8x16 = _mm_unpacklo_epi16(temp1, temp2); + p2_8x16 = _mm_unpackhi_epi16(temp1, temp2); + q2_8x16 = _mm_unpacklo_epi16(temp3, temp4); + q3_8x16 = _mm_unpackhi_epi16(temp3, temp4); + + line1 = _mm_unpacklo_epi32(p3_8x16, q2_8x16); + line2 = _mm_srli_si128(line1, 8); + line3 = _mm_unpackhi_epi32(p3_8x16, q2_8x16); + line4 = _mm_srli_si128(line3, 8); + line5 = _mm_unpacklo_epi32(p2_8x16, q3_8x16); + line6 = _mm_srli_si128(line5, 8); + line7 = _mm_unpackhi_epi32(p2_8x16, q3_8x16); + line8 = _mm_srli_si128(line7, 8); + + _mm_storel_epi64((__m128i *)(pu1_src - 4 + 8 * src_strd), line1); + _mm_storel_epi64((__m128i *)(pu1_src - 4 + 9 * src_strd), line2); + _mm_storel_epi64((__m128i *)(pu1_src - 4 + 10 * src_strd), line3); + _mm_storel_epi64((__m128i *)(pu1_src - 4 + 11 * src_strd), line4); + _mm_storel_epi64((__m128i *)(pu1_src - 4 + 12 * src_strd), line5); + _mm_storel_epi64((__m128i *)(pu1_src - 4 + 13 * src_strd), line6); + _mm_storel_epi64((__m128i *)(pu1_src - 4 + 14 * src_strd), line7); + _mm_storel_epi64((__m128i *)(pu1_src - 4 + 15 * src_strd), line8); + +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_deblk_luma_horz_bs4_ssse3() */ +/* */ +/* Description : This function performs filtering of a luma block */ +/* horizontal edge when the boundary strength is set to 4. */ +/* */ +/* Inputs : pu1_src - pointer to the src sample q0 */ +/* src_strd - source stride */ +/* alpha - alpha value for the boundary */ +/* beta - beta value for the boundary */ +/* */ +/* Globals : None */ +/* */ +/* Processing : This operation is described in Sec. 8.7.2.4 under the */ +/* title "Filtering process for edges for bS equal to 4" in */ +/* ITU T Rec H.264. */ +/* */ +/* Outputs : None */ +/* */ +/* Returns : None */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 12 02 2015 Naveen Kumar P Initial version */ +/* */ +/*****************************************************************************/ +void ih264_deblk_luma_horz_bs4_ssse3(UWORD8 *pu1_src, + WORD32 src_strd, + WORD32 alpha, + WORD32 beta) +{ + WORD16 i16_posP3, i16_posP2, i16_posP1, i16_posP0; + WORD16 i16_posQ1, i16_posQ2, i16_posQ3; + UWORD8 *pu1_HorzPixel; + __m128i zero = _mm_setzero_si128(); + __m128i q0_16x8, q1_16x8, q2_16x8, q3_16x8; + __m128i p0_16x8, p1_16x8, p2_16x8, p3_16x8; + __m128i q0_8x16, q1_8x16, q2_8x16, q3_8x16; + __m128i p0_8x16, p1_8x16, p2_8x16, p3_8x16; + __m128i q0_16x8_1; + __m128i p0_16x8_1; + __m128i q0_16x8_2, q1_16x8_2, q2_16x8_2; + __m128i p0_16x8_2, p1_16x8_2, p2_16x8_2; + __m128i temp1, temp2, temp3, temp4, temp5, temp6; + __m128i Alpha_8x16, Beta_8x16; + __m128i flag1_16x8, flag2_16x8, flag3_16x8, flag4_16x8; + __m128i const_val2_16x8 = _mm_set1_epi16(2); + + pu1_HorzPixel = pu1_src - (src_strd << 2); + + i16_posQ1 = src_strd; + i16_posQ2 = X2(src_strd); + i16_posQ3 = X3(src_strd); + i16_posP0 = X3(src_strd); + i16_posP1 = X2(src_strd); + i16_posP2 = src_strd; + i16_posP3 = 0; + + Alpha_8x16 = _mm_set1_epi16(alpha); + Beta_8x16 = _mm_set1_epi16(beta); + + p3_16x8 = _mm_loadu_si128((__m128i *)(pu1_HorzPixel + i16_posP3)); + p2_16x8 = _mm_loadu_si128((__m128i *)(pu1_HorzPixel + i16_posP2)); + p1_16x8 = _mm_loadu_si128((__m128i *)(pu1_HorzPixel + i16_posP1)); + p0_16x8 = _mm_loadu_si128((__m128i *)(pu1_HorzPixel + i16_posP0)); + q0_16x8 = _mm_loadu_si128((__m128i *)(pu1_src)); + q1_16x8 = _mm_loadu_si128((__m128i *)(pu1_src + i16_posQ1)); + q2_16x8 = _mm_loadu_si128((__m128i *)(pu1_src + i16_posQ2)); + q3_16x8 = _mm_loadu_si128((__m128i *)(pu1_src + i16_posQ3)); + + //Cond1 (ABS(p0 - q0) < alpha) + temp1 = _mm_subs_epu8(q0_16x8, p0_16x8); + temp2 = _mm_subs_epu8(p0_16x8, q0_16x8); + temp1 = _mm_add_epi8(temp1, temp2); + + temp2 = _mm_unpacklo_epi8(temp1, zero); + temp1 = _mm_unpackhi_epi8(temp1, zero); + + temp2 = _mm_cmpgt_epi16(Alpha_8x16, temp2); + temp1 = _mm_cmpgt_epi16(Alpha_8x16, temp1); + + flag1_16x8 = _mm_packs_epi16(temp2, temp1); + + //Cond2 (ABS(q1 - q0) < beta) + temp1 = _mm_subs_epu8(q0_16x8, q1_16x8); + temp2 = _mm_subs_epu8(q1_16x8, q0_16x8); + temp1 = _mm_add_epi8(temp1, temp2); + + temp2 = _mm_unpacklo_epi8(temp1, zero); + temp1 = _mm_unpackhi_epi8(temp1, zero); + + temp2 = _mm_cmpgt_epi16(Beta_8x16, temp2); + temp1 = _mm_cmpgt_epi16(Beta_8x16, temp1); + + flag2_16x8 = _mm_packs_epi16(temp2, temp1); + + flag1_16x8 = _mm_and_si128(flag1_16x8, flag2_16x8); + + //Cond3 (ABS(p1 - p0) < beta) + temp1 = _mm_subs_epu8(p0_16x8, p1_16x8); + temp2 = _mm_subs_epu8(p1_16x8, p0_16x8); + temp1 = _mm_add_epi8(temp1, temp2); + + temp2 = _mm_unpacklo_epi8(temp1, zero); + temp1 = _mm_unpackhi_epi8(temp1, zero); + + temp2 = _mm_cmpgt_epi16(Beta_8x16, temp2); + temp1 = _mm_cmpgt_epi16(Beta_8x16, temp1); + + flag2_16x8 = _mm_packs_epi16(temp2, temp1); + + // !((ABS(p0 - q0) < alpha) || (ABS(q1 - q0) < beta) || (ABS(p1 - p0) < beta)) + flag1_16x8 = _mm_and_si128(flag1_16x8, flag2_16x8); + + // (ABS(p0 - q0) < ((alpha >> 2) + 2)) + temp1 = _mm_subs_epu8(p0_16x8, q0_16x8); + temp2 = _mm_subs_epu8(q0_16x8, p0_16x8); + temp1 = _mm_add_epi8(temp1, temp2); + Alpha_8x16 = _mm_srai_epi16(Alpha_8x16, 2); + Alpha_8x16 = _mm_add_epi16(Alpha_8x16, const_val2_16x8); + + temp2 = _mm_unpacklo_epi8(temp1, zero); + temp1 = _mm_unpackhi_epi8(temp1, zero); + temp2 = _mm_cmpgt_epi16(Alpha_8x16, temp2); + temp1 = _mm_cmpgt_epi16(Alpha_8x16, temp1); + + flag2_16x8 = _mm_packs_epi16(temp2, temp1); + flag2_16x8 = _mm_and_si128(flag1_16x8, flag2_16x8); + + // (ABS(p2 - p0) < beta) + temp1 = _mm_subs_epu8(p0_16x8, p2_16x8); + temp2 = _mm_subs_epu8(p2_16x8, p0_16x8); + temp1 = _mm_add_epi8(temp1, temp2); + + temp2 = _mm_unpacklo_epi8(temp1, zero); + temp1 = _mm_unpackhi_epi8(temp1, zero); + temp2 = _mm_cmpgt_epi16(Beta_8x16, temp2); + temp1 = _mm_cmpgt_epi16(Beta_8x16, temp1); + + flag3_16x8 = _mm_packs_epi16(temp2, temp1); + flag3_16x8 = _mm_and_si128(flag3_16x8, flag2_16x8); + + // (ABS(q2 - q0) < beta) + temp1 = _mm_subs_epu8(q0_16x8, q2_16x8); + temp2 = _mm_subs_epu8(q2_16x8, q0_16x8); + temp1 = _mm_add_epi8(temp1, temp2); + + temp2 = _mm_unpacklo_epi8(temp1, zero); + temp1 = _mm_unpackhi_epi8(temp1, zero); + temp2 = _mm_cmpgt_epi16(Beta_8x16, temp2); + temp1 = _mm_cmpgt_epi16(Beta_8x16, temp1); + + flag4_16x8 = _mm_packs_epi16(temp2, temp1); + flag4_16x8 = _mm_and_si128(flag4_16x8, flag2_16x8); + + // First 8 pixels + p3_8x16 = _mm_unpacklo_epi8(p3_16x8, zero); + p2_8x16 = _mm_unpacklo_epi8(p2_16x8, zero); + p1_8x16 = _mm_unpacklo_epi8(p1_16x8, zero); + p0_8x16 = _mm_unpacklo_epi8(p0_16x8, zero); + q0_8x16 = _mm_unpacklo_epi8(q0_16x8, zero); + q1_8x16 = _mm_unpacklo_epi8(q1_16x8, zero); + q2_8x16 = _mm_unpacklo_epi8(q2_16x8, zero); + q3_8x16 = _mm_unpacklo_epi8(q3_16x8, zero); + + // p0_1 and q0_1 + temp1 = _mm_add_epi16(p0_8x16, q1_8x16); + temp2 = _mm_add_epi16(p1_8x16, q0_8x16); + temp5 = _mm_add_epi16(temp1, const_val2_16x8); + temp6 = _mm_add_epi16(temp2, const_val2_16x8); + temp3 = _mm_slli_epi16(p1_8x16, 1); + temp4 = _mm_slli_epi16(q1_8x16, 1); + temp1 = _mm_add_epi16(temp5, temp3); + temp2 = _mm_add_epi16(temp6, temp4); + p0_16x8_1 = _mm_srai_epi16(temp1, 2); + q0_16x8_1 = _mm_srai_epi16(temp2, 2); + + // p1_2 and q1_2 + temp6 = _mm_add_epi16(temp6, p0_8x16); + temp5 = _mm_add_epi16(temp5, q0_8x16); + temp1 = _mm_add_epi16(temp6, p2_8x16); + temp2 = _mm_add_epi16(temp5, q2_8x16); + p1_16x8_2 = _mm_srai_epi16(temp1, 2); + q1_16x8_2 = _mm_srai_epi16(temp2, 2); + + // p0_2 and q0_2 + temp1 = _mm_add_epi16(temp3, p2_8x16); + temp2 = _mm_add_epi16(temp4, q2_8x16); + temp1 = _mm_add_epi16(temp1, q1_8x16); + temp2 = _mm_add_epi16(temp2, p1_8x16); + temp3 = _mm_add_epi16(p0_8x16, q0_8x16); + temp3 = _mm_slli_epi16(temp3, 1); + temp1 = _mm_add_epi16(temp1, temp3); + temp2 = _mm_add_epi16(temp2, temp3); + temp1 = _mm_add_epi16(temp1, _mm_set1_epi16(4)); + temp2 = _mm_add_epi16(temp2, _mm_set1_epi16(4)); + p0_16x8_2 = _mm_srai_epi16(temp1, 3); + q0_16x8_2 = _mm_srai_epi16(temp2, 3); + + // p2_2 and q2_2 + temp1 = _mm_add_epi16(temp6, const_val2_16x8); + temp2 = _mm_add_epi16(temp5, const_val2_16x8); + temp3 = _mm_slli_epi16(p2_8x16, 1); + temp4 = _mm_slli_epi16(q2_8x16, 1); + temp3 = _mm_add_epi16(p2_8x16, temp3); + temp4 = _mm_add_epi16(q2_8x16, temp4); + temp5 = _mm_slli_epi16(p3_8x16, 1); + temp6 = _mm_slli_epi16(q3_8x16, 1); + temp1 = _mm_add_epi16(temp1, temp3); + temp2 = _mm_add_epi16(temp2, temp4); + temp1 = _mm_add_epi16(temp1, temp5); + temp2 = _mm_add_epi16(temp2, temp6); + p2_16x8_2 = _mm_srai_epi16(temp1, 3); + q2_16x8_2 = _mm_srai_epi16(temp2, 3); + + // Second 8 pixels and packing with first 8 pixels + p3_8x16 = _mm_unpackhi_epi8(p3_16x8, zero); + p2_8x16 = _mm_unpackhi_epi8(p2_16x8, zero); + p1_8x16 = _mm_unpackhi_epi8(p1_16x8, zero); + p0_8x16 = _mm_unpackhi_epi8(p0_16x8, zero); + q0_8x16 = _mm_unpackhi_epi8(q0_16x8, zero); + q1_8x16 = _mm_unpackhi_epi8(q1_16x8, zero); + q2_8x16 = _mm_unpackhi_epi8(q2_16x8, zero); + q3_8x16 = _mm_unpackhi_epi8(q3_16x8, zero); + + // p0_1 and q0_1 + temp1 = _mm_add_epi16(p0_8x16, q1_8x16); + temp2 = _mm_add_epi16(p1_8x16, q0_8x16); + temp5 = _mm_add_epi16(temp1, const_val2_16x8); + temp6 = _mm_add_epi16(temp2, const_val2_16x8); + temp3 = _mm_slli_epi16(p1_8x16, 1); + temp4 = _mm_slli_epi16(q1_8x16, 1); + temp1 = _mm_add_epi16(temp5, temp3); + temp2 = _mm_add_epi16(temp6, temp4); + temp1 = _mm_srai_epi16(temp1, 2); + temp2 = _mm_srai_epi16(temp2, 2); + p0_16x8_1 = _mm_packus_epi16(p0_16x8_1, temp1); + q0_16x8_1 = _mm_packus_epi16(q0_16x8_1, temp2); + + // p1_2 and q1_2 + temp6 = _mm_add_epi16(temp6, p0_8x16); + temp5 = _mm_add_epi16(temp5, q0_8x16); + temp1 = _mm_add_epi16(temp6, p2_8x16); + temp2 = _mm_add_epi16(temp5, q2_8x16); + temp1 = _mm_srai_epi16(temp1, 2); + temp2 = _mm_srai_epi16(temp2, 2); + p1_16x8_2 = _mm_packus_epi16(p1_16x8_2, temp1); + q1_16x8_2 = _mm_packus_epi16(q1_16x8_2, temp2); + + // p0_2 and q0_2 + temp1 = _mm_add_epi16(temp3, p2_8x16); + temp2 = _mm_add_epi16(temp4, q2_8x16); + temp1 = _mm_add_epi16(temp1, q1_8x16); + temp2 = _mm_add_epi16(temp2, p1_8x16); + temp3 = _mm_add_epi16(p0_8x16, q0_8x16); + temp3 = _mm_slli_epi16(temp3, 1); + temp1 = _mm_add_epi16(temp1, temp3); + temp2 = _mm_add_epi16(temp2, temp3); + temp1 = _mm_add_epi16(temp1, _mm_set1_epi16(4)); + temp2 = _mm_add_epi16(temp2, _mm_set1_epi16(4)); + temp1 = _mm_srai_epi16(temp1, 3); + temp2 = _mm_srai_epi16(temp2, 3); + p0_16x8_2 = _mm_packus_epi16(p0_16x8_2, temp1); + q0_16x8_2 = _mm_packus_epi16(q0_16x8_2, temp2); + + // p2_2 and q2_2 + temp1 = _mm_add_epi16(temp6, const_val2_16x8); + temp2 = _mm_add_epi16(temp5, const_val2_16x8); + temp3 = _mm_slli_epi16(p2_8x16, 1); + temp4 = _mm_slli_epi16(q2_8x16, 1); + temp3 = _mm_add_epi16(p2_8x16, temp3); + temp4 = _mm_add_epi16(q2_8x16, temp4); + temp5 = _mm_slli_epi16(p3_8x16, 1); + temp6 = _mm_slli_epi16(q3_8x16, 1); + temp1 = _mm_add_epi16(temp1, temp3); + temp2 = _mm_add_epi16(temp2, temp4); + temp1 = _mm_add_epi16(temp1, temp5); + temp2 = _mm_add_epi16(temp2, temp6); + temp1 = _mm_srai_epi16(temp1, 3); + temp2 = _mm_srai_epi16(temp2, 3); + p2_16x8_2 = _mm_packus_epi16(p2_16x8_2, temp1); + q2_16x8_2 = _mm_packus_epi16(q2_16x8_2, temp2); + + // p0 and q0 + p0_16x8 = _mm_and_si128(p0_16x8, + _mm_xor_si128(flag1_16x8, _mm_set1_epi8(0xFF))); + p0_16x8_1 = _mm_and_si128(p0_16x8_1, flag1_16x8); + p0_16x8 = _mm_add_epi8(p0_16x8, p0_16x8_1); + q0_16x8 = _mm_and_si128(q0_16x8, + _mm_xor_si128(flag1_16x8, _mm_set1_epi8(0xFF))); + q0_16x8_1 = _mm_and_si128(q0_16x8_1, flag1_16x8); + q0_16x8 = _mm_add_epi8(q0_16x8, q0_16x8_1); + + // p0 and q0 + p0_16x8 = _mm_and_si128(p0_16x8, + _mm_xor_si128(flag3_16x8, _mm_set1_epi8(0xFF))); + p0_16x8_2 = _mm_and_si128(p0_16x8_2, flag3_16x8); + p0_16x8 = _mm_add_epi8(p0_16x8, p0_16x8_2); + q0_16x8 = _mm_and_si128(q0_16x8, + _mm_xor_si128(flag4_16x8, _mm_set1_epi8(0xFF))); + q0_16x8_2 = _mm_and_si128(q0_16x8_2, flag4_16x8); + q0_16x8 = _mm_add_epi8(q0_16x8, q0_16x8_2); + + // p1 and q1 + p1_16x8 = _mm_and_si128(p1_16x8, + _mm_xor_si128(flag3_16x8, _mm_set1_epi8(0xFF))); + p1_16x8_2 = _mm_and_si128(p1_16x8_2, flag3_16x8); + p1_16x8 = _mm_add_epi8(p1_16x8, p1_16x8_2); + q1_16x8 = _mm_and_si128(q1_16x8, + _mm_xor_si128(flag4_16x8, _mm_set1_epi8(0xFF))); + q1_16x8_2 = _mm_and_si128(q1_16x8_2, flag4_16x8); + q1_16x8 = _mm_add_epi8(q1_16x8, q1_16x8_2); + + // p2 and q2 + p2_16x8 = _mm_and_si128(p2_16x8, + _mm_xor_si128(flag3_16x8, _mm_set1_epi8(0xFF))); + p2_16x8_2 = _mm_and_si128(p2_16x8_2, flag3_16x8); + p2_16x8 = _mm_add_epi8(p2_16x8, p2_16x8_2); + q2_16x8 = _mm_and_si128(q2_16x8, + _mm_xor_si128(flag4_16x8, _mm_set1_epi8(0xFF))); + q2_16x8_2 = _mm_and_si128(q2_16x8_2, flag4_16x8); + q2_16x8 = _mm_add_epi8(q2_16x8, q2_16x8_2); + + _mm_storeu_si128((__m128i *)(pu1_HorzPixel + i16_posP2), p2_16x8); + _mm_storeu_si128((__m128i *)(pu1_HorzPixel + i16_posP1), p1_16x8); + _mm_storeu_si128((__m128i *)(pu1_HorzPixel + i16_posP0), p0_16x8); + + _mm_storeu_si128((__m128i *)(pu1_src), q0_16x8); + _mm_storeu_si128((__m128i *)(pu1_src + i16_posQ1), q1_16x8); + _mm_storeu_si128((__m128i *)(pu1_src + i16_posQ2), q2_16x8); + +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_deblk_luma_vert_bslt4_ssse3() */ +/* */ +/* Description : This function performs filtering of a luma block */ +/* vertical edge when the boundary strength is less than 4. */ +/* */ +/* Inputs : pu1_src - pointer to the src sample q0 */ +/* src_strd - source stride */ +/* alpha - alpha value for the boundary */ +/* beta - beta value for the boundary */ +/* u4_bs - packed Boundary strength array */ +/* pu1_cliptab - tc0_table */ +/* */ +/* Globals : None */ +/* */ +/* Processing : This operation is described in Sec. 8.7.2.3 under the */ +/* title "Filtering process for edges for bS less than 4" */ +/* in ITU T Rec H.264. */ +/* */ +/* Outputs : None */ +/* */ +/* Returns : None */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 12 02 2015 Naveen Kumar P Initial version */ +/* */ +/*****************************************************************************/ +void ih264_deblk_luma_vert_bslt4_ssse3(UWORD8 *pu1_src, + WORD32 src_strd, + WORD32 alpha, + WORD32 beta, + UWORD32 u4_bs, + const UWORD8 *pu1_cliptab) +{ + UWORD8 u1_Bs, u1_Bs1; + + WORD32 j = 0; + + __m128i linea, lineb, linec, lined, linee, linef, lineg, lineh; + __m128i int1, int2, int3, int4, high1, high2; + __m128i flag, flag1, i_C, i_C0; + __m128i i_Ap, i_Aq, diff, const1, const2, in_macro, in_macrotemp, temp, + temp1; + __m128i zero = _mm_setzero_si128(); + + for(j = 0; j <= 8 * src_strd; j += 8 * src_strd) + { + //Transpose + linea = _mm_loadl_epi64((__m128i *)(pu1_src - 3 + j)); + lineb = _mm_loadl_epi64((__m128i *)(pu1_src - 3 + src_strd + j)); + linec = _mm_loadl_epi64((__m128i *)(pu1_src - 3 + 2 * src_strd + j)); + lined = _mm_loadl_epi64((__m128i *)(pu1_src - 3 + 3 * src_strd + j)); + + linea = _mm_unpacklo_epi8(linea, zero); + lineb = _mm_unpacklo_epi8(lineb, zero); + linec = _mm_unpacklo_epi8(linec, zero); + lined = _mm_unpacklo_epi8(lined, zero); + + int1 = _mm_unpacklo_epi16(linea, lineb); + lineb = _mm_unpackhi_epi16(linea, lineb); + + int2 = _mm_unpacklo_epi16(linec, lined); + lined = _mm_unpackhi_epi16(linec, lined); + + linea = _mm_unpacklo_epi16(int1, int2); + int1 = _mm_unpackhi_epi16(int1, int2); + + linec = _mm_unpacklo_epi16(lineb, lined); + high1 = _mm_unpackhi_epi16(lineb, lined); + + linee = _mm_loadl_epi64((__m128i *)(pu1_src - 3 + 4 * src_strd + j)); + linef = _mm_loadl_epi64((__m128i *)(pu1_src - 3 + 5 * src_strd + j)); + lineg = _mm_loadl_epi64((__m128i *)(pu1_src - 3 + 6 * src_strd + j)); + lineh = _mm_loadl_epi64((__m128i *)(pu1_src - 3 + 7 * src_strd + j)); + + linee = _mm_unpacklo_epi8(linee, zero); + linef = _mm_unpacklo_epi8(linef, zero); + lineg = _mm_unpacklo_epi8(lineg, zero); + lineh = _mm_unpacklo_epi8(lineh, zero); + + int2 = _mm_unpacklo_epi16(linee, linef); + linef = _mm_unpackhi_epi16(linee, linef); + + int3 = _mm_unpacklo_epi16(lineg, lineh); + lineh = _mm_unpackhi_epi16(lineg, lineh); + + linee = _mm_unpacklo_epi16(int2, int3); + int2 = _mm_unpackhi_epi16(int2, int3); + + lineg = _mm_unpacklo_epi16(linef, lineh); + high2 = _mm_unpackhi_epi16(linef, lineh); + + int4 = _mm_unpacklo_epi16(linea, linee); + lineb = _mm_unpackhi_epi16(linea, linee); + + int3 = _mm_unpacklo_epi16(int1, int2); + lined = _mm_unpackhi_epi16(int1, int2); + + int2 = _mm_unpacklo_epi16(linec, lineg); + linef = _mm_unpackhi_epi16(linec, lineg); + + linea = int4; + linec = int3; + linee = int2; + + lineg = _mm_unpacklo_epi16(high1, high2); + lineh = _mm_unpackhi_epi16(high1, high2); + + //end of transpose + + u1_Bs = (u4_bs >> 24) & 0xff; + u1_Bs1 = (u4_bs >> 16) & 0xff; + u4_bs <<= 16; + + flag1 = _mm_set_epi16(u1_Bs1, u1_Bs, u1_Bs1, u1_Bs, u1_Bs1, u1_Bs, + u1_Bs1, u1_Bs); + flag1 = _mm_cmpeq_epi16(flag1, zero); //Set flag to 1s and 0s + flag1 = _mm_xor_si128(flag1, _mm_set1_epi16(0xFFFF)); //Invert for required mask + + i_C0 = _mm_set_epi16(pu1_cliptab[u1_Bs1], pu1_cliptab[u1_Bs], + pu1_cliptab[u1_Bs1], pu1_cliptab[u1_Bs], + pu1_cliptab[u1_Bs1], pu1_cliptab[u1_Bs], + pu1_cliptab[u1_Bs1], pu1_cliptab[u1_Bs]); + + diff = _mm_subs_epi16(linec, lined); //Condn 1 + diff = _mm_abs_epi16(diff); + const1 = _mm_set1_epi16(alpha); + flag = _mm_cmpgt_epi16(const1, diff); + + diff = _mm_subs_epi16(linee, lined); //Condtn 2 + diff = _mm_abs_epi16(diff); + const1 = _mm_set1_epi16(beta); + flag = _mm_and_si128(flag, _mm_cmpgt_epi16(const1, diff)); + + diff = _mm_subs_epi16(lineb, linec); //Condtn 3 + diff = _mm_abs_epi16(diff); + flag = _mm_and_si128(flag, _mm_cmpgt_epi16(const1, diff)); //Const 1= Beta from now on + + flag = _mm_and_si128(flag, flag1); //Final flag (ui_B condition + other 3 conditions) + + //Adding Ap<Beta and Aq<Beta + i_Ap = _mm_subs_epi16(linea, linec); + i_Ap = _mm_abs_epi16(i_Ap); + const2 = _mm_cmpgt_epi16(const1, i_Ap); + const2 = _mm_subs_epi16(zero, const2); //Make FFFF=1 and 0000=0 + i_C = _mm_add_epi16(i_C0, const2); + + i_Aq = _mm_subs_epi16(linef, lined); + i_Aq = _mm_abs_epi16(i_Aq); + const2 = _mm_cmpgt_epi16(const1, i_Aq); + const2 = _mm_subs_epi16(zero, const2); + i_C = _mm_add_epi16(i_C, const2); + + //Calculate in_macro + diff = _mm_subs_epi16(lined, linec); + diff = _mm_slli_epi16(diff, 2); + const2 = _mm_subs_epi16(lineb, linee); + diff = _mm_add_epi16(diff, const2); + const2 = _mm_set1_epi16(4); + diff = _mm_add_epi16(diff, const2); + in_macro = _mm_srai_epi16(diff, 3); + + in_macro = _mm_min_epi16(i_C, in_macro); //CLIP3 + i_C = _mm_subs_epi16(zero, i_C); + in_macro = _mm_max_epi16(i_C, in_macro); + + //Compute and store + in_macrotemp = _mm_add_epi16(linec, in_macro); + in_macrotemp = _mm_and_si128(in_macrotemp, flag); + temp = _mm_and_si128(linec, + _mm_xor_si128(flag, _mm_set1_epi16(0xFFFF))); + temp = _mm_add_epi16(temp, in_macrotemp); + //temp= _mm_packus_epi16 (temp, zero); + //_mm_storel_epi64(uc_HorzPixel+i16_posP0+i, in_macrotemp); + + in_macrotemp = _mm_subs_epi16(lined, in_macro); + in_macrotemp = _mm_and_si128(in_macrotemp, flag); + temp1 = _mm_and_si128(lined, + _mm_xor_si128(flag, _mm_set1_epi16(0xFFFF))); + temp1 = _mm_add_epi16(temp1, in_macrotemp); + //temp1= _mm_packus_epi16 (temp1, zero); + //_mm_storel_epi64(pu1_src+i, in_macrotemp); + + //If Ap<Beta + flag1 = _mm_cmpgt_epi16(const1, i_Ap); + flag1 = _mm_and_si128(flag, flag1); + in_macrotemp = _mm_add_epi16(linec, lined); + in_macrotemp = _mm_add_epi16(in_macrotemp, _mm_set1_epi16(1)); + in_macrotemp = _mm_srai_epi16(in_macrotemp, 1); + in_macro = _mm_add_epi16(in_macrotemp, linea); + in_macro = _mm_subs_epi16(in_macro, _mm_slli_epi16(lineb, 1)); + in_macro = _mm_srai_epi16(in_macro, 1); + + in_macro = _mm_min_epi16(i_C0, in_macro); //CLIP3 + i_C0 = _mm_subs_epi16(zero, i_C0); + in_macro = _mm_max_epi16(i_C0, in_macro); + + in_macro = _mm_and_si128(in_macro, flag1); + lineb = _mm_add_epi16(lineb, in_macro); + //in_macro= _mm_packus_epi16 (i_p1, zero); + //_mm_storel_epi64(uc_HorzPixel+i16_posP1+i, in_macro); + + flag1 = _mm_cmpgt_epi16(const1, i_Aq); + flag1 = _mm_and_si128(flag, flag1); + in_macro = _mm_add_epi16(in_macrotemp, linef); + in_macro = _mm_subs_epi16(in_macro, _mm_slli_epi16(linee, 1)); + in_macro = _mm_srai_epi16(in_macro, 1); + + i_C0 = _mm_abs_epi16(i_C0); + in_macro = _mm_min_epi16(i_C0, in_macro); //CLIP3 + i_C0 = _mm_subs_epi16(zero, i_C0); + in_macro = _mm_max_epi16(i_C0, in_macro); + + in_macro = _mm_and_si128(in_macro, flag1); + linee = _mm_add_epi16(linee, in_macro); + //in_macro= _mm_packus_epi16 (i_q1, zero); + //_mm_storel_epi64(pu1_src+i16_posQ1+i, in_macro); + linec = temp; + lined = temp1; + //End of filtering + + int1 = _mm_unpacklo_epi16(linea, linee); + linee = _mm_unpackhi_epi16(linea, linee); + + int2 = _mm_unpacklo_epi16(linec, lineg); + lineg = _mm_unpackhi_epi16(linec, lineg); + + linea = _mm_unpacklo_epi16(int1, int2); + int3 = _mm_unpackhi_epi16(int1, int2); + + linec = _mm_unpacklo_epi16(linee, lineg); + lineg = _mm_unpackhi_epi16(linee, lineg); + + int1 = _mm_unpacklo_epi16(lineb, linef); + linef = _mm_unpackhi_epi16(lineb, linef); + + int2 = _mm_unpacklo_epi16(lined, lineh); + lineh = _mm_unpackhi_epi16(lined, lineh); + + lineb = _mm_unpacklo_epi16(int1, int2); + int4 = _mm_unpackhi_epi16(int1, int2); + + lined = _mm_unpacklo_epi16(linef, lineh); + lineh = _mm_unpackhi_epi16(linef, lineh); + + int1 = _mm_unpackhi_epi16(linea, lineb); + linea = _mm_unpacklo_epi16(linea, lineb); + + int2 = _mm_unpacklo_epi16(int3, int4); + high1 = _mm_unpackhi_epi16(int3, int4); + + lineb = _mm_unpacklo_epi16(linec, lined); + linef = _mm_unpackhi_epi16(linec, lined); + + lined = _mm_unpacklo_epi16(lineg, lineh); + lineh = _mm_unpackhi_epi16(lineg, lineh); + + linee = int1; + lineg = high1; + linec = int2; + //End of inverse transpose + + //Packs and stores + linea = _mm_packus_epi16(linea, zero); + _mm_storel_epi64((__m128i *)(pu1_src - 3 + j), linea); + + lineb = _mm_packus_epi16(lineb, zero); + _mm_storel_epi64((__m128i *)(pu1_src - 3 + src_strd + j), lineb); + + linec = _mm_packus_epi16(linec, zero); + _mm_storel_epi64((__m128i *)(pu1_src - 3 + 2 * src_strd + j), linec); + + lined = _mm_packus_epi16(lined, zero); + _mm_storel_epi64((__m128i *)(pu1_src - 3 + 3 * src_strd + j), lined); + + linee = _mm_packus_epi16(linee, zero); + _mm_storel_epi64((__m128i *)(pu1_src - 3 + 4 * src_strd + j), linee); + + linef = _mm_packus_epi16(linef, zero); + _mm_storel_epi64((__m128i *)(pu1_src - 3 + 5 * src_strd + j), linef); + + lineg = _mm_packus_epi16(lineg, zero); + _mm_storel_epi64((__m128i *)(pu1_src - 3 + 6 * src_strd + j), lineg); + + lineh = _mm_packus_epi16(lineh, zero); + _mm_storel_epi64((__m128i *)(pu1_src - 3 + 7 * src_strd + j), lineh); + + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_deblk_luma_horz_bslt4_ssse3() */ +/* */ +/* Description : This function performs filtering of a luma block */ +/* horizontal edge when boundary strength is less than 4. */ +/* */ +/* Inputs : pu1_src - pointer to the src sample q0 */ +/* src_strd - source stride */ +/* alpha - alpha value for the boundary */ +/* beta - beta value for the boundary */ +/* u4_bs - packed Boundary strength array */ +/* pu1_cliptab - tc0_table */ +/* */ +/* Globals : None */ +/* */ +/* Processing : This operation is described in Sec. 8.7.2.3 under the */ +/* title "Filtering process for edges for bS less than 4" */ +/* in ITU T Rec H.264. */ +/* */ +/* Outputs : None */ +/* */ +/* Returns : None */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 12 02 2015 Naveen Kumar P Initial version */ +/* */ +/*****************************************************************************/ +void ih264_deblk_luma_horz_bslt4_ssse3(UWORD8 *pu1_src, + WORD32 src_strd, + WORD32 alpha, + WORD32 beta, + UWORD32 u4_bs, + const UWORD8 *pu1_cliptab) +{ + WORD16 i16_posP2, i16_posP1, i16_posP0, i16_posQ1, i16_posQ2; + UWORD8 *pu1_HorzPixel; + __m128i zero = _mm_setzero_si128(); + __m128i bs_flag_16x8b, C0_16x8, C0_8x16, C0_hi_8x16, C_8x16, C_hi_8x16; + __m128i q0_16x8, q1_16x8, q2_16x8, p0_16x8, p1_16x8, p2_16x8; + __m128i temp1, temp2; + __m128i Alpha_8x16, Beta_8x16, flag1_16x8, flag2_16x8, flag3_16x8; + __m128i in_macro_16x8, in_macro_hi_16x8; + __m128i const_val4_8x16; + UWORD8 u1_Bs0, u1_Bs1, u1_Bs2, u1_Bs3; + UWORD8 clip0, clip1, clip2, clip3; + + pu1_HorzPixel = pu1_src - (src_strd << 2); + + i16_posQ1 = src_strd; + i16_posQ2 = X2(src_strd); + i16_posP0 = X3(src_strd); + i16_posP1 = X2(src_strd); + i16_posP2 = src_strd; + + q0_16x8 = _mm_loadu_si128((__m128i *)(pu1_src)); + q1_16x8 = _mm_loadu_si128((__m128i *)(pu1_src + i16_posQ1)); + + u1_Bs0 = (u4_bs >> 24) & 0xff; + u1_Bs1 = (u4_bs >> 16) & 0xff; + u1_Bs2 = (u4_bs >> 8) & 0xff; + u1_Bs3 = (u4_bs >> 0) & 0xff; + clip0 = pu1_cliptab[u1_Bs0]; + clip1 = pu1_cliptab[u1_Bs1]; + clip2 = pu1_cliptab[u1_Bs2]; + clip3 = pu1_cliptab[u1_Bs3]; + + Alpha_8x16 = _mm_set1_epi16(alpha); + Beta_8x16 = _mm_set1_epi16(beta); + + bs_flag_16x8b = _mm_set_epi8(u1_Bs3, u1_Bs3, u1_Bs3, u1_Bs3, u1_Bs2, u1_Bs2, + u1_Bs2, u1_Bs2, u1_Bs1, u1_Bs1, u1_Bs1, u1_Bs1, + u1_Bs0, u1_Bs0, u1_Bs0, u1_Bs0); + + C0_16x8 = _mm_set_epi8(clip3, clip3, clip3, clip3, clip2, clip2, clip2, + clip2, clip1, clip1, clip1, clip1, clip0, clip0, + clip0, clip0); + + bs_flag_16x8b = _mm_cmpeq_epi8(bs_flag_16x8b, zero); + bs_flag_16x8b = _mm_xor_si128(bs_flag_16x8b, _mm_set1_epi8(0xFF)); //Invert for required mask + C0_8x16 = _mm_unpacklo_epi8(C0_16x8, zero); + C0_hi_8x16 = _mm_unpackhi_epi8(C0_16x8, zero); + + p1_16x8 = _mm_loadu_si128((__m128i *)(pu1_HorzPixel + i16_posP1)); + p0_16x8 = _mm_loadu_si128((__m128i *)(pu1_HorzPixel + i16_posP0)); + p2_16x8 = _mm_loadu_si128((__m128i *)(pu1_HorzPixel + i16_posP2)); + q2_16x8 = _mm_loadu_si128((__m128i *)(pu1_src + i16_posQ2)); + + //Cond1 (ABS(p0 - q0) < alpha) + temp1 = _mm_subs_epu8(q0_16x8, p0_16x8); + temp2 = _mm_subs_epu8(p0_16x8, q0_16x8); + temp1 = _mm_add_epi8(temp1, temp2); + + temp2 = _mm_unpacklo_epi8(temp1, zero); + temp1 = _mm_unpackhi_epi8(temp1, zero); + + temp2 = _mm_cmpgt_epi16(Alpha_8x16, temp2); + temp1 = _mm_cmpgt_epi16(Alpha_8x16, temp1); + + flag1_16x8 = _mm_packs_epi16(temp2, temp1); + flag1_16x8 = _mm_and_si128(flag1_16x8, bs_flag_16x8b); + + //Cond2 (ABS(q1 - q0) < beta) + temp1 = _mm_subs_epu8(q0_16x8, q1_16x8); + temp2 = _mm_subs_epu8(q1_16x8, q0_16x8); + temp1 = _mm_add_epi8(temp1, temp2); + + temp2 = _mm_unpacklo_epi8(temp1, zero); + temp1 = _mm_unpackhi_epi8(temp1, zero); + + temp2 = _mm_cmpgt_epi16(Beta_8x16, temp2); + temp1 = _mm_cmpgt_epi16(Beta_8x16, temp1); + + flag2_16x8 = _mm_packs_epi16(temp2, temp1); + + flag1_16x8 = _mm_and_si128(flag1_16x8, flag2_16x8); + + //Cond3 (ABS(p1 - p0) < beta) + temp1 = _mm_subs_epu8(p0_16x8, p1_16x8); + temp2 = _mm_subs_epu8(p1_16x8, p0_16x8); + temp1 = _mm_add_epi8(temp1, temp2); + + temp2 = _mm_unpacklo_epi8(temp1, zero); + temp1 = _mm_unpackhi_epi8(temp1, zero); + + temp2 = _mm_cmpgt_epi16(Beta_8x16, temp2); + temp1 = _mm_cmpgt_epi16(Beta_8x16, temp1); + + flag2_16x8 = _mm_packs_epi16(temp2, temp1); + + // !((ABS(p0 - q0) < alpha) || (ABS(q1 - q0) < beta) || (ABS(p1 - p0) < beta)) + flag1_16x8 = _mm_and_si128(flag1_16x8, flag2_16x8); + + // (ABS(p2 - p0) < beta) + temp1 = _mm_subs_epu8(p0_16x8, p2_16x8); + temp2 = _mm_subs_epu8(p2_16x8, p0_16x8); + temp1 = _mm_add_epi8(temp1, temp2); + + temp2 = _mm_unpacklo_epi8(temp1, zero); + temp1 = _mm_unpackhi_epi8(temp1, zero); + temp2 = _mm_cmpgt_epi16(Beta_8x16, temp2); + temp1 = _mm_cmpgt_epi16(Beta_8x16, temp1); + + flag2_16x8 = _mm_packs_epi16(temp2, temp1); + flag2_16x8 = _mm_and_si128(flag1_16x8, flag2_16x8); + + temp2 = _mm_subs_epi16(zero, temp2); + temp1 = _mm_subs_epi16(zero, temp1); + + C_8x16 = _mm_add_epi16(C0_8x16, temp2); + C_hi_8x16 = _mm_add_epi16(C0_hi_8x16, temp1); + + // (ABS(q2 - q0) < beta) + temp1 = _mm_subs_epu8(q0_16x8, q2_16x8); + temp2 = _mm_subs_epu8(q2_16x8, q0_16x8); + temp1 = _mm_add_epi8(temp1, temp2); + + temp2 = _mm_unpacklo_epi8(temp1, zero); + temp1 = _mm_unpackhi_epi8(temp1, zero); + temp2 = _mm_cmpgt_epi16(Beta_8x16, temp2); + temp1 = _mm_cmpgt_epi16(Beta_8x16, temp1); + + flag3_16x8 = _mm_packs_epi16(temp2, temp1); + flag3_16x8 = _mm_and_si128(flag1_16x8, flag3_16x8); + + temp2 = _mm_subs_epi16(zero, temp2); + temp1 = _mm_subs_epi16(zero, temp1); + + C_8x16 = _mm_add_epi16(C_8x16, temp2); + C_hi_8x16 = _mm_add_epi16(C_hi_8x16, temp1); + + const_val4_8x16 = _mm_set1_epi16(4); + temp1 = _mm_subs_epi16(_mm_unpacklo_epi8(q0_16x8, zero), + _mm_unpacklo_epi8(p0_16x8, zero)); + temp2 = _mm_subs_epi16(_mm_unpacklo_epi8(p1_16x8, zero), + _mm_unpacklo_epi8(q1_16x8, zero)); + temp1 = _mm_slli_epi16(temp1, 2); + temp1 = _mm_add_epi16(temp1, temp2); + temp1 = _mm_add_epi16(temp1, const_val4_8x16); + in_macro_16x8 = _mm_srai_epi16(temp1, 3); + + temp1 = _mm_subs_epi16(_mm_unpackhi_epi8(q0_16x8, zero), + _mm_unpackhi_epi8(p0_16x8, zero)); + temp2 = _mm_subs_epi16(_mm_unpackhi_epi8(p1_16x8, zero), + _mm_unpackhi_epi8(q1_16x8, zero)); + temp1 = _mm_slli_epi16(temp1, 2); + temp1 = _mm_add_epi16(temp1, temp2); + temp1 = _mm_add_epi16(temp1, const_val4_8x16); + in_macro_hi_16x8 = _mm_srai_epi16(temp1, 3); + + in_macro_16x8 = _mm_min_epi16(C_8x16, in_macro_16x8); //CLIP3 + in_macro_hi_16x8 = _mm_min_epi16(C_hi_8x16, in_macro_hi_16x8); //CLIP3 + C_8x16 = _mm_subs_epi16(zero, C_8x16); + C_hi_8x16 = _mm_subs_epi16(zero, C_hi_8x16); + in_macro_16x8 = _mm_max_epi16(C_8x16, in_macro_16x8); //CLIP3 + in_macro_hi_16x8 = _mm_max_epi16(C_hi_8x16, in_macro_hi_16x8); //CLIP3 + + temp1 = _mm_add_epi16(_mm_unpacklo_epi8(p0_16x8, zero), in_macro_16x8); + temp2 = _mm_add_epi16(_mm_unpackhi_epi8(p0_16x8, zero), in_macro_hi_16x8); + + temp1 = _mm_packus_epi16(temp1, temp2); + + temp1 = _mm_and_si128(temp1, flag1_16x8); + temp2 = _mm_and_si128(p0_16x8, + _mm_xor_si128(flag1_16x8, _mm_set1_epi16(0xFFFF))); + + temp1 = _mm_add_epi8(temp1, temp2); + + _mm_storeu_si128((__m128i *)(pu1_HorzPixel + i16_posP0), temp1); + + temp1 = _mm_sub_epi16(_mm_unpacklo_epi8(q0_16x8, zero), in_macro_16x8); + temp2 = _mm_sub_epi16(_mm_unpackhi_epi8(q0_16x8, zero), in_macro_hi_16x8); + + temp1 = _mm_packus_epi16(temp1, temp2); + + temp1 = _mm_and_si128(temp1, flag1_16x8); + temp2 = _mm_and_si128(q0_16x8, + _mm_xor_si128(flag1_16x8, _mm_set1_epi16(0xFFFF))); + + temp1 = _mm_add_epi8(temp1, temp2); + _mm_storeu_si128((__m128i *)(pu1_src), temp1); + + //if(Ap < Beta) + temp1 = _mm_avg_epu16(_mm_unpacklo_epi8(q0_16x8, zero), + _mm_unpacklo_epi8(p0_16x8, zero)); + temp2 = _mm_slli_epi16(_mm_unpacklo_epi8(p1_16x8, zero), 1); + //temp2 = _mm_subs_epi16(zero,temp2); + temp2 = _mm_subs_epi16(_mm_unpacklo_epi8(p2_16x8, zero), temp2); + temp2 = _mm_add_epi16(temp1, temp2); + in_macro_16x8 = _mm_srai_epi16(temp2, 1); + + temp1 = _mm_avg_epu16(_mm_unpackhi_epi8(q0_16x8, zero), + _mm_unpackhi_epi8(p0_16x8, zero)); + temp2 = _mm_slli_epi16(_mm_unpackhi_epi8(p1_16x8, zero), 1); + //temp2 = _mm_subs_epi16(zero,temp2); + temp2 = _mm_subs_epi16(_mm_unpackhi_epi8(p2_16x8, zero), temp2); + temp2 = _mm_add_epi16(temp1, temp2); + in_macro_hi_16x8 = _mm_srai_epi16(temp2, 1); + + in_macro_16x8 = _mm_min_epi16(C0_8x16, in_macro_16x8); //CLIP3 + in_macro_hi_16x8 = _mm_min_epi16(C0_hi_8x16, in_macro_hi_16x8); //CLIP3 + C0_8x16 = _mm_subs_epi16(zero, C0_8x16); + C0_hi_8x16 = _mm_subs_epi16(zero, C0_hi_8x16); + in_macro_16x8 = _mm_max_epi16(C0_8x16, in_macro_16x8); //CLIP3 + in_macro_hi_16x8 = _mm_max_epi16(C0_hi_8x16, in_macro_hi_16x8); //CLIP3 + + temp1 = _mm_add_epi16(_mm_unpacklo_epi8(p1_16x8, zero), in_macro_16x8); + temp2 = _mm_add_epi16(_mm_unpackhi_epi8(p1_16x8, zero), in_macro_hi_16x8); + + temp1 = _mm_packus_epi16(temp1, temp2); + + temp1 = _mm_and_si128(temp1, flag2_16x8); + temp2 = _mm_and_si128(p1_16x8, + _mm_xor_si128(flag2_16x8, _mm_set1_epi16(0xFFFF))); + temp1 = _mm_add_epi8(temp1, temp2); + _mm_storeu_si128((__m128i *)(pu1_HorzPixel + i16_posP1), temp1); + + //if(Aq < Beta) + temp1 = _mm_avg_epu16(_mm_unpacklo_epi8(q0_16x8, zero), + _mm_unpacklo_epi8(p0_16x8, zero)); + temp2 = _mm_slli_epi16(_mm_unpacklo_epi8(q1_16x8, zero), 1); + //temp2 = _mm_slli_epi16 (temp2, 1); + temp2 = _mm_subs_epi16(_mm_unpacklo_epi8(q2_16x8, zero), temp2); + temp2 = _mm_add_epi16(temp1, temp2); + in_macro_16x8 = _mm_srai_epi16(temp2, 1); + + temp1 = _mm_avg_epu16(_mm_unpackhi_epi8(q0_16x8, zero), + _mm_unpackhi_epi8(p0_16x8, zero)); + temp2 = _mm_slli_epi16(_mm_unpackhi_epi8(q1_16x8, zero), 1); + //temp2 = _mm_slli_epi16 (temp2, 1); + temp2 = _mm_subs_epi16(_mm_unpackhi_epi8(q2_16x8, zero), temp2); + temp2 = _mm_add_epi16(temp1, temp2); + in_macro_hi_16x8 = _mm_srai_epi16(temp2, 1); + + in_macro_16x8 = _mm_max_epi16(C0_8x16, in_macro_16x8); //CLIP3 + in_macro_hi_16x8 = _mm_max_epi16(C0_hi_8x16, in_macro_hi_16x8); //CLIP3 + C0_8x16 = _mm_subs_epi16(zero, C0_8x16); + C0_hi_8x16 = _mm_subs_epi16(zero, C0_hi_8x16); + in_macro_16x8 = _mm_min_epi16(C0_8x16, in_macro_16x8); //CLIP3 + in_macro_hi_16x8 = _mm_min_epi16(C0_hi_8x16, in_macro_hi_16x8); //CLIP3 + + temp1 = _mm_add_epi16(_mm_unpacklo_epi8(q1_16x8, zero), in_macro_16x8); + temp2 = _mm_add_epi16(_mm_unpackhi_epi8(q1_16x8, zero), in_macro_hi_16x8); + + temp1 = _mm_packus_epi16(temp1, temp2); + + temp1 = _mm_and_si128(temp1, flag3_16x8); + temp2 = _mm_and_si128(q1_16x8, + _mm_xor_si128(flag3_16x8, _mm_set1_epi16(0xFFFF))); + temp1 = _mm_add_epi8(temp1, temp2); + + _mm_storeu_si128((__m128i *)(pu1_src + i16_posQ1), temp1); + +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_deblk_luma_vert_bs4_mbaff_ssse3() */ +/* */ +/* Description : This function performs filtering of a luma block */ +/* vertical edge when boundary strength is set to 4. */ +/* */ +/* Inputs : pu1_src - pointer to the src sample q0 */ +/* src_strd - source stride */ +/* alpha - alpha value for the boundary */ +/* beta - beta value for the boundary */ +/* */ +/* Globals : None */ +/* */ +/* Processing : When the function is called twice, this operation is as */ +/* described in Sec. 8.7.2.3 under the title "Filtering */ +/* process for edges for bS equal to 4" in ITU T Rec H.264. */ +/* */ +/* Outputs : None */ +/* */ +/* Returns : None */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 12 02 2015 Naveen Kumar P Initial version */ +/* */ +/*****************************************************************************/ +void ih264_deblk_luma_vert_bs4_mbaff_ssse3(UWORD8 *pu1_src, + WORD32 src_strd, + WORD32 alpha, + WORD32 beta) +{ + __m128i zero = _mm_setzero_si128(); + __m128i q0_16x8, q1_16x8, q2_16x8, q3_16x8; + __m128i p0_16x8, p1_16x8, p2_16x8, p3_16x8; + __m128i q0_8x16, q1_8x16, q2_8x16, q3_8x16; + __m128i p0_8x16, p1_8x16, p2_8x16, p3_8x16; + __m128i q0_16x8_1; + __m128i p0_16x8_1; + __m128i q0_16x8_2, q1_16x8_2, q2_16x8_2; + __m128i p0_16x8_2, p1_16x8_2, p2_16x8_2; + __m128i temp1, temp2, temp3, temp4, temp5, temp6; + __m128i Alpha_8x16, Beta_8x16; + __m128i flag1_16x8, flag2_16x8, flag3_16x8, flag4_16x8; + __m128i const_val2_16x8 = _mm_set1_epi16(2); + __m128i line1, line2, line3, line4, line5, line6, line7, line8; + + Alpha_8x16 = _mm_set1_epi16(alpha); + Beta_8x16 = _mm_set1_epi16(beta); + + line1 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 0 * src_strd)); + line2 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 1 * src_strd)); + line3 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 2 * src_strd)); + line4 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 3 * src_strd)); + line5 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 4 * src_strd)); + line6 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 5 * src_strd)); + line7 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 6 * src_strd)); + line8 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 7 * src_strd)); + + temp1 = _mm_unpacklo_epi8(line1, line2); + temp2 = _mm_unpacklo_epi8(line3, line4); + temp3 = _mm_unpacklo_epi8(line5, line6); + temp4 = _mm_unpacklo_epi8(line7, line8); + + line1 = _mm_unpacklo_epi16(temp1, temp2); + line2 = _mm_unpackhi_epi16(temp1, temp2); + line3 = _mm_unpacklo_epi16(temp3, temp4); + line4 = _mm_unpackhi_epi16(temp3, temp4); + + p1_8x16 = _mm_unpacklo_epi32(line1, line3); + p0_8x16 = _mm_unpackhi_epi32(line1, line3); + q0_8x16 = _mm_unpacklo_epi32(line2, line4); + q1_8x16 = _mm_unpackhi_epi32(line2, line4); + + p3_16x8 = _mm_unpacklo_epi64(p1_8x16, zero); + p2_16x8 = _mm_unpackhi_epi64(p1_8x16, zero); + q2_16x8 = _mm_unpacklo_epi64(q1_8x16, zero); + q3_16x8 = _mm_unpackhi_epi64(q1_8x16, zero); + p1_16x8 = _mm_unpacklo_epi64(p0_8x16, zero); + p0_16x8 = _mm_unpackhi_epi64(p0_8x16, zero); + q0_16x8 = _mm_unpacklo_epi64(q0_8x16, zero); + q1_16x8 = _mm_unpackhi_epi64(q0_8x16, zero); + + //Cond1 (ABS(p0 - q0) < alpha) + temp1 = _mm_subs_epu8(q0_16x8, p0_16x8); + temp2 = _mm_subs_epu8(p0_16x8, q0_16x8); + temp1 = _mm_add_epi8(temp1, temp2); + + temp2 = _mm_unpacklo_epi8(temp1, zero); + temp1 = _mm_unpackhi_epi8(temp1, zero); + + temp2 = _mm_cmpgt_epi16(Alpha_8x16, temp2); + temp1 = _mm_cmpgt_epi16(Alpha_8x16, temp1); + + flag1_16x8 = _mm_packs_epi16(temp2, temp1); + + //Cond2 (ABS(q1 - q0) < beta) + temp1 = _mm_subs_epu8(q0_16x8, q1_16x8); + temp2 = _mm_subs_epu8(q1_16x8, q0_16x8); + temp1 = _mm_add_epi8(temp1, temp2); + + temp2 = _mm_unpacklo_epi8(temp1, zero); + temp1 = _mm_unpackhi_epi8(temp1, zero); + + temp2 = _mm_cmpgt_epi16(Beta_8x16, temp2); + temp1 = _mm_cmpgt_epi16(Beta_8x16, temp1); + + flag2_16x8 = _mm_packs_epi16(temp2, temp1); + + flag1_16x8 = _mm_and_si128(flag1_16x8, flag2_16x8); + + //Cond3 (ABS(p1 - p0) < beta) + temp1 = _mm_subs_epu8(p0_16x8, p1_16x8); + temp2 = _mm_subs_epu8(p1_16x8, p0_16x8); + temp1 = _mm_add_epi8(temp1, temp2); + + temp2 = _mm_unpacklo_epi8(temp1, zero); + temp1 = _mm_unpackhi_epi8(temp1, zero); + + temp2 = _mm_cmpgt_epi16(Beta_8x16, temp2); + temp1 = _mm_cmpgt_epi16(Beta_8x16, temp1); + + flag2_16x8 = _mm_packs_epi16(temp2, temp1); + + // !((ABS(p0 - q0) < alpha) || (ABS(q1 - q0) < beta) || (ABS(p1 - p0) < beta)) + flag1_16x8 = _mm_and_si128(flag1_16x8, flag2_16x8); + + // (ABS(p0 - q0) < ((alpha >> 2) + 2)) + temp1 = _mm_subs_epu8(p0_16x8, q0_16x8); + temp2 = _mm_subs_epu8(q0_16x8, p0_16x8); + temp1 = _mm_add_epi8(temp1, temp2); + Alpha_8x16 = _mm_srai_epi16(Alpha_8x16, 2); + Alpha_8x16 = _mm_add_epi16(Alpha_8x16, const_val2_16x8); + + temp2 = _mm_unpacklo_epi8(temp1, zero); + temp1 = _mm_unpackhi_epi8(temp1, zero); + temp2 = _mm_cmpgt_epi16(Alpha_8x16, temp2); + temp1 = _mm_cmpgt_epi16(Alpha_8x16, temp1); + + flag2_16x8 = _mm_packs_epi16(temp2, temp1); + flag2_16x8 = _mm_and_si128(flag1_16x8, flag2_16x8); + + // (ABS(p2 - p0) < beta) + temp1 = _mm_subs_epu8(p0_16x8, p2_16x8); + temp2 = _mm_subs_epu8(p2_16x8, p0_16x8); + temp1 = _mm_add_epi8(temp1, temp2); + + temp2 = _mm_unpacklo_epi8(temp1, zero); + temp1 = _mm_unpackhi_epi8(temp1, zero); + temp2 = _mm_cmpgt_epi16(Beta_8x16, temp2); + temp1 = _mm_cmpgt_epi16(Beta_8x16, temp1); + + flag3_16x8 = _mm_packs_epi16(temp2, temp1); + flag3_16x8 = _mm_and_si128(flag3_16x8, flag2_16x8); + + // (ABS(q2 - q0) < beta) + temp1 = _mm_subs_epu8(q0_16x8, q2_16x8); + temp2 = _mm_subs_epu8(q2_16x8, q0_16x8); + temp1 = _mm_add_epi8(temp1, temp2); + + temp2 = _mm_unpacklo_epi8(temp1, zero); + temp1 = _mm_unpackhi_epi8(temp1, zero); + temp2 = _mm_cmpgt_epi16(Beta_8x16, temp2); + temp1 = _mm_cmpgt_epi16(Beta_8x16, temp1); + + flag4_16x8 = _mm_packs_epi16(temp2, temp1); + flag4_16x8 = _mm_and_si128(flag4_16x8, flag2_16x8); + + // First 8 pixels + p3_8x16 = _mm_unpacklo_epi8(p3_16x8, zero); + p2_8x16 = _mm_unpacklo_epi8(p2_16x8, zero); + p1_8x16 = _mm_unpacklo_epi8(p1_16x8, zero); + p0_8x16 = _mm_unpacklo_epi8(p0_16x8, zero); + q0_8x16 = _mm_unpacklo_epi8(q0_16x8, zero); + q1_8x16 = _mm_unpacklo_epi8(q1_16x8, zero); + q2_8x16 = _mm_unpacklo_epi8(q2_16x8, zero); + q3_8x16 = _mm_unpacklo_epi8(q3_16x8, zero); + + // p0_1 and q0_1 + temp1 = _mm_add_epi16(p0_8x16, q1_8x16); + temp2 = _mm_add_epi16(p1_8x16, q0_8x16); + temp5 = _mm_add_epi16(temp1, const_val2_16x8); + temp6 = _mm_add_epi16(temp2, const_val2_16x8); + temp3 = _mm_slli_epi16(p1_8x16, 1); + temp4 = _mm_slli_epi16(q1_8x16, 1); + temp1 = _mm_add_epi16(temp5, temp3); + temp2 = _mm_add_epi16(temp6, temp4); + p0_16x8_1 = _mm_srai_epi16(temp1, 2); + q0_16x8_1 = _mm_srai_epi16(temp2, 2); + + // p1_2 and q1_2 + temp6 = _mm_add_epi16(temp6, p0_8x16); + temp5 = _mm_add_epi16(temp5, q0_8x16); + temp1 = _mm_add_epi16(temp6, p2_8x16); + temp2 = _mm_add_epi16(temp5, q2_8x16); + p1_16x8_2 = _mm_srai_epi16(temp1, 2); + q1_16x8_2 = _mm_srai_epi16(temp2, 2); + + // p0_2 and q0_2 + temp1 = _mm_add_epi16(temp3, p2_8x16); + temp2 = _mm_add_epi16(temp4, q2_8x16); + temp1 = _mm_add_epi16(temp1, q1_8x16); + temp2 = _mm_add_epi16(temp2, p1_8x16); + temp3 = _mm_add_epi16(p0_8x16, q0_8x16); + temp3 = _mm_slli_epi16(temp3, 1); + temp1 = _mm_add_epi16(temp1, temp3); + temp2 = _mm_add_epi16(temp2, temp3); + temp1 = _mm_add_epi16(temp1, _mm_set1_epi16(4)); + temp2 = _mm_add_epi16(temp2, _mm_set1_epi16(4)); + p0_16x8_2 = _mm_srai_epi16(temp1, 3); + q0_16x8_2 = _mm_srai_epi16(temp2, 3); + + // p2_2 and q2_2 + temp1 = _mm_add_epi16(temp6, const_val2_16x8); + temp2 = _mm_add_epi16(temp5, const_val2_16x8); + temp3 = _mm_slli_epi16(p2_8x16, 1); + temp4 = _mm_slli_epi16(q2_8x16, 1); + temp3 = _mm_add_epi16(p2_8x16, temp3); + temp4 = _mm_add_epi16(q2_8x16, temp4); + temp5 = _mm_slli_epi16(p3_8x16, 1); + temp6 = _mm_slli_epi16(q3_8x16, 1); + temp1 = _mm_add_epi16(temp1, temp3); + temp2 = _mm_add_epi16(temp2, temp4); + temp1 = _mm_add_epi16(temp1, temp5); + temp2 = _mm_add_epi16(temp2, temp6); + p2_16x8_2 = _mm_srai_epi16(temp1, 3); + q2_16x8_2 = _mm_srai_epi16(temp2, 3); + + // p0_1 and q0_1 + p0_16x8_1 = _mm_packus_epi16(p0_16x8_1, zero); + q0_16x8_1 = _mm_packus_epi16(q0_16x8_1, zero); + + // p1_2 and q1_2 + p1_16x8_2 = _mm_packus_epi16(p1_16x8_2, zero); + q1_16x8_2 = _mm_packus_epi16(q1_16x8_2, zero); + + // p0_2 and q0_2 + p0_16x8_2 = _mm_packus_epi16(p0_16x8_2, zero); + q0_16x8_2 = _mm_packus_epi16(q0_16x8_2, zero); + + // p2_2 and q2_2 + p2_16x8_2 = _mm_packus_epi16(p2_16x8_2, zero); + q2_16x8_2 = _mm_packus_epi16(q2_16x8_2, zero); + + // p0 and q0 + p0_16x8 = _mm_and_si128(p0_16x8, + _mm_xor_si128(flag1_16x8, _mm_set1_epi8(0xFF))); + p0_16x8_1 = _mm_and_si128(p0_16x8_1, flag1_16x8); + p0_16x8 = _mm_add_epi8(p0_16x8, p0_16x8_1); + q0_16x8 = _mm_and_si128(q0_16x8, + _mm_xor_si128(flag1_16x8, _mm_set1_epi8(0xFF))); + q0_16x8_1 = _mm_and_si128(q0_16x8_1, flag1_16x8); + q0_16x8 = _mm_add_epi8(q0_16x8, q0_16x8_1); + + // p0 and q0 + p0_16x8 = _mm_and_si128(p0_16x8, + _mm_xor_si128(flag3_16x8, _mm_set1_epi8(0xFF))); + p0_16x8_2 = _mm_and_si128(p0_16x8_2, flag3_16x8); + p0_16x8 = _mm_add_epi8(p0_16x8, p0_16x8_2); + q0_16x8 = _mm_and_si128(q0_16x8, + _mm_xor_si128(flag4_16x8, _mm_set1_epi8(0xFF))); + q0_16x8_2 = _mm_and_si128(q0_16x8_2, flag4_16x8); + q0_16x8 = _mm_add_epi8(q0_16x8, q0_16x8_2); + + // p1 and q1 + p1_16x8 = _mm_and_si128(p1_16x8, + _mm_xor_si128(flag3_16x8, _mm_set1_epi8(0xFF))); + p1_16x8_2 = _mm_and_si128(p1_16x8_2, flag3_16x8); + p1_16x8 = _mm_add_epi8(p1_16x8, p1_16x8_2); + q1_16x8 = _mm_and_si128(q1_16x8, + _mm_xor_si128(flag4_16x8, _mm_set1_epi8(0xFF))); + q1_16x8_2 = _mm_and_si128(q1_16x8_2, flag4_16x8); + q1_16x8 = _mm_add_epi8(q1_16x8, q1_16x8_2); + + // p2 and q2 + p2_16x8 = _mm_and_si128(p2_16x8, + _mm_xor_si128(flag3_16x8, _mm_set1_epi8(0xFF))); + p2_16x8_2 = _mm_and_si128(p2_16x8_2, flag3_16x8); + p2_16x8 = _mm_add_epi8(p2_16x8, p2_16x8_2); + q2_16x8 = _mm_and_si128(q2_16x8, + _mm_xor_si128(flag4_16x8, _mm_set1_epi8(0xFF))); + q2_16x8_2 = _mm_and_si128(q2_16x8_2, flag4_16x8); + q2_16x8 = _mm_add_epi8(q2_16x8, q2_16x8_2); + + temp1 = _mm_unpacklo_epi8(p3_16x8, p2_16x8); + temp2 = _mm_unpacklo_epi8(p1_16x8, p0_16x8); + temp3 = _mm_unpacklo_epi8(q0_16x8, q1_16x8); + temp4 = _mm_unpacklo_epi8(q2_16x8, q3_16x8); + + p3_8x16 = _mm_unpacklo_epi16(temp1, temp2); + p2_8x16 = _mm_unpackhi_epi16(temp1, temp2); + q2_8x16 = _mm_unpacklo_epi16(temp3, temp4); + q3_8x16 = _mm_unpackhi_epi16(temp3, temp4); + + line1 = _mm_unpacklo_epi32(p3_8x16, q2_8x16); + line2 = _mm_srli_si128(line1, 8); + line3 = _mm_unpackhi_epi32(p3_8x16, q2_8x16); + line4 = _mm_srli_si128(line3, 8); + line5 = _mm_unpacklo_epi32(p2_8x16, q3_8x16); + line6 = _mm_srli_si128(line5, 8); + line7 = _mm_unpackhi_epi32(p2_8x16, q3_8x16); + line8 = _mm_srli_si128(line7, 8); + + _mm_storel_epi64((__m128i *)(pu1_src - 4 + 0 * src_strd), line1); + _mm_storel_epi64((__m128i *)(pu1_src - 4 + 1 * src_strd), line2); + _mm_storel_epi64((__m128i *)(pu1_src - 4 + 2 * src_strd), line3); + _mm_storel_epi64((__m128i *)(pu1_src - 4 + 3 * src_strd), line4); + _mm_storel_epi64((__m128i *)(pu1_src - 4 + 4 * src_strd), line5); + _mm_storel_epi64((__m128i *)(pu1_src - 4 + 5 * src_strd), line6); + _mm_storel_epi64((__m128i *)(pu1_src - 4 + 6 * src_strd), line7); + _mm_storel_epi64((__m128i *)(pu1_src - 4 + 7 * src_strd), line8); + +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_deblk_luma_vert_bslt4_mbaff_ssse3() */ +/* */ +/* Description : This function performs filtering of a luma block */ +/* vertical edge when boundary strength is less than 4. */ +/* */ +/* Inputs : pu1_src - pointer to the src sample q0 */ +/* src_strd - source stride */ +/* alpha - alpha value for the boundary */ +/* beta - beta value for the boundary */ +/* u4_bs - packed Boundary strength array */ +/* pu1_cliptab - tc0_table */ +/* */ +/* Globals : None */ +/* */ +/* Processing : When the function is called twice, this operation is as */ +/* described in Sec. 8.7.2.3 under the title "Filtering */ +/* process for edges for bS less than 4" in ITU T Rec H.264.*/ +/* */ +/* Outputs : None */ +/* */ +/* Returns : None */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 12 02 2015 Naveen Kumar P Initial version */ +/* */ +/*****************************************************************************/ +void ih264_deblk_luma_vert_bslt4_mbaff_ssse3(UWORD8 *pu1_src, + WORD32 src_strd, + WORD32 alpha, + WORD32 beta, + UWORD32 u4_bs, + const UWORD8 *pu1_cliptab) +{ + __m128i zero = _mm_setzero_si128(); + __m128i bs_flag_16x8b, C0_16x8, C0_8x16, C_8x16; + __m128i q0_16x8, q1_16x8, q2_16x8, q3_16x8; + __m128i p0_16x8, p1_16x8, p2_16x8, p3_16x8; + __m128i temp1, temp2, temp3, temp4; + __m128i Alpha_8x16, Beta_8x16, flag1_16x8, flag2_16x8, flag3_16x8; + __m128i in_macro_16x8; + __m128i const_val4_8x16; + UWORD8 u1_Bs0, u1_Bs1, u1_Bs2, u1_Bs3; + UWORD8 clip0, clip1, clip2, clip3; + __m128i line1, line2, line3, line4, line5, line6, line7, line8; + __m128i q0_16x8_1, q1_16x8_1, q0_16x8_2; + __m128i p0_16x8_1, p1_16x8_1, p0_16x8_2; + + line1 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 0 * src_strd)); + line2 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 1 * src_strd)); + line3 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 2 * src_strd)); + line4 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 3 * src_strd)); + line5 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 4 * src_strd)); + line6 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 5 * src_strd)); + line7 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 6 * src_strd)); + line8 = _mm_loadl_epi64((__m128i *)(pu1_src - 4 + 7 * src_strd)); + + temp1 = _mm_unpacklo_epi8(line1, line2); + temp2 = _mm_unpacklo_epi8(line3, line4); + temp3 = _mm_unpacklo_epi8(line5, line6); + temp4 = _mm_unpacklo_epi8(line7, line8); + + line1 = _mm_unpacklo_epi16(temp1, temp2); + line2 = _mm_unpackhi_epi16(temp1, temp2); + line3 = _mm_unpacklo_epi16(temp3, temp4); + line4 = _mm_unpackhi_epi16(temp3, temp4); + + temp1 = _mm_unpacklo_epi32(line1, line3); + temp2 = _mm_unpackhi_epi32(line1, line3); + temp3 = _mm_unpacklo_epi32(line2, line4); + temp4 = _mm_unpackhi_epi32(line2, line4); + + p3_16x8 = _mm_unpacklo_epi64(temp1, zero); + p2_16x8 = _mm_unpackhi_epi64(temp1, zero); + q2_16x8 = _mm_unpacklo_epi64(temp4, zero); + q3_16x8 = _mm_unpackhi_epi64(temp4, zero); + p1_16x8 = _mm_unpacklo_epi64(temp2, zero); + p0_16x8 = _mm_unpackhi_epi64(temp2, zero); + q0_16x8 = _mm_unpacklo_epi64(temp3, zero); + q1_16x8 = _mm_unpackhi_epi64(temp3, zero); + + u1_Bs0 = (u4_bs >> 24) & 0xff; + u1_Bs1 = (u4_bs >> 16) & 0xff; + u1_Bs2 = (u4_bs >> 8) & 0xff; + u1_Bs3 = (u4_bs >> 0) & 0xff; + clip0 = pu1_cliptab[u1_Bs0]; + clip1 = pu1_cliptab[u1_Bs1]; + clip2 = pu1_cliptab[u1_Bs2]; + clip3 = pu1_cliptab[u1_Bs3]; + + Alpha_8x16 = _mm_set1_epi16(alpha); + Beta_8x16 = _mm_set1_epi16(beta); + + bs_flag_16x8b = _mm_set_epi8(0, 0, 0, 0, 0, 0, 0, 0, u1_Bs3, u1_Bs3, u1_Bs2, + u1_Bs2, u1_Bs1, u1_Bs1, u1_Bs0, u1_Bs0); + + C0_16x8 = _mm_set_epi8(0, 0, 0, 0, 0, 0, 0, 0, clip3, clip3, clip2, clip2, + clip1, clip1, clip0, clip0); + + bs_flag_16x8b = _mm_cmpeq_epi8(bs_flag_16x8b, zero); + bs_flag_16x8b = _mm_xor_si128(bs_flag_16x8b, _mm_set1_epi8(0xFF)); //Invert for required mask + C0_8x16 = _mm_unpacklo_epi8(C0_16x8, zero); + + //Cond1 (ABS(p0 - q0) < alpha) + temp1 = _mm_subs_epu8(q0_16x8, p0_16x8); + temp2 = _mm_subs_epu8(p0_16x8, q0_16x8); + temp1 = _mm_add_epi8(temp1, temp2); + + temp2 = _mm_unpacklo_epi8(temp1, zero); + temp2 = _mm_cmpgt_epi16(Alpha_8x16, temp2); + + flag1_16x8 = _mm_packs_epi16(temp2, zero); + flag1_16x8 = _mm_and_si128(flag1_16x8, bs_flag_16x8b); + + //Cond2 (ABS(q1 - q0) < beta) + temp1 = _mm_subs_epu8(q0_16x8, q1_16x8); + temp2 = _mm_subs_epu8(q1_16x8, q0_16x8); + temp1 = _mm_add_epi8(temp1, temp2); + + temp2 = _mm_unpacklo_epi8(temp1, zero); + temp2 = _mm_cmpgt_epi16(Beta_8x16, temp2); + + flag2_16x8 = _mm_packs_epi16(temp2, zero); + flag1_16x8 = _mm_and_si128(flag1_16x8, flag2_16x8); + + //Cond3 (ABS(p1 - p0) < beta) + temp1 = _mm_subs_epu8(p0_16x8, p1_16x8); + temp2 = _mm_subs_epu8(p1_16x8, p0_16x8); + temp1 = _mm_add_epi8(temp1, temp2); + + temp2 = _mm_unpacklo_epi8(temp1, zero); + temp2 = _mm_cmpgt_epi16(Beta_8x16, temp2); + + flag2_16x8 = _mm_packs_epi16(temp2, zero); + + // !((ABS(p0 - q0) < alpha) || (ABS(q1 - q0) < beta) || (ABS(p1 - p0) < beta)) + flag1_16x8 = _mm_and_si128(flag1_16x8, flag2_16x8); + + // (ABS(p2 - p0) < beta) + temp1 = _mm_subs_epu8(p0_16x8, p2_16x8); + temp2 = _mm_subs_epu8(p2_16x8, p0_16x8); + temp1 = _mm_add_epi8(temp1, temp2); + + temp2 = _mm_unpacklo_epi8(temp1, zero); + temp2 = _mm_cmpgt_epi16(Beta_8x16, temp2); + + flag2_16x8 = _mm_packs_epi16(temp2, zero); + flag2_16x8 = _mm_and_si128(flag1_16x8, flag2_16x8); + + temp2 = _mm_subs_epi16(zero, temp2); + + C_8x16 = _mm_add_epi16(C0_8x16, temp2); + + // (ABS(q2 - q0) < beta) + temp1 = _mm_subs_epu8(q0_16x8, q2_16x8); + temp2 = _mm_subs_epu8(q2_16x8, q0_16x8); + temp1 = _mm_add_epi8(temp1, temp2); + + temp2 = _mm_unpacklo_epi8(temp1, zero); + temp2 = _mm_cmpgt_epi16(Beta_8x16, temp2); + + flag3_16x8 = _mm_packs_epi16(temp2, zero); + flag3_16x8 = _mm_and_si128(flag1_16x8, flag3_16x8); + + temp2 = _mm_subs_epi16(zero, temp2); + + C_8x16 = _mm_add_epi16(C_8x16, temp2); + + const_val4_8x16 = _mm_set1_epi16(4); + temp1 = _mm_subs_epi16(_mm_unpacklo_epi8(q0_16x8, zero), + _mm_unpacklo_epi8(p0_16x8, zero)); + temp2 = _mm_subs_epi16(_mm_unpacklo_epi8(p1_16x8, zero), + _mm_unpacklo_epi8(q1_16x8, zero)); + temp1 = _mm_slli_epi16(temp1, 2); + temp1 = _mm_add_epi16(temp1, temp2); + temp1 = _mm_add_epi16(temp1, const_val4_8x16); + in_macro_16x8 = _mm_srai_epi16(temp1, 3); + + in_macro_16x8 = _mm_min_epi16(C_8x16, in_macro_16x8); //CLIP3 + C_8x16 = _mm_subs_epi16(zero, C_8x16); + in_macro_16x8 = _mm_max_epi16(C_8x16, in_macro_16x8); //CLIP3 + + // p0 + temp1 = _mm_add_epi16(_mm_unpacklo_epi8(p0_16x8, zero), in_macro_16x8); + + temp1 = _mm_packus_epi16(temp1, zero); + + p0_16x8_1 = _mm_and_si128(temp1, flag1_16x8); + p0_16x8_2 = _mm_and_si128( + p0_16x8, _mm_xor_si128(flag1_16x8, _mm_set1_epi16(0xFFFF))); + + p0_16x8_1 = _mm_add_epi8(p0_16x8_1, p0_16x8_2); + + // q0 + temp1 = _mm_sub_epi16(_mm_unpacklo_epi8(q0_16x8, zero), in_macro_16x8); + + temp1 = _mm_packus_epi16(temp1, zero); + + q0_16x8_1 = _mm_and_si128(temp1, flag1_16x8); + q0_16x8_2 = _mm_and_si128( + q0_16x8, _mm_xor_si128(flag1_16x8, _mm_set1_epi16(0xFFFF))); + + q0_16x8_1 = _mm_add_epi8(q0_16x8_1, q0_16x8_2); + + //if(Ap < Beta) + temp1 = _mm_avg_epu16(_mm_unpacklo_epi8(q0_16x8, zero), + _mm_unpacklo_epi8(p0_16x8, zero)); + temp2 = _mm_slli_epi16(_mm_unpacklo_epi8(p1_16x8, zero), 1); + //temp2 = _mm_subs_epi16(zero,temp2); + temp2 = _mm_subs_epi16(_mm_unpacklo_epi8(p2_16x8, zero), temp2); + temp2 = _mm_add_epi16(temp1, temp2); + in_macro_16x8 = _mm_srai_epi16(temp2, 1); + + in_macro_16x8 = _mm_min_epi16(C0_8x16, in_macro_16x8); //CLIP3 + C0_8x16 = _mm_subs_epi16(zero, C0_8x16); + in_macro_16x8 = _mm_max_epi16(C0_8x16, in_macro_16x8); //CLIP3 + + // p1 + temp1 = _mm_add_epi16(_mm_unpacklo_epi8(p1_16x8, zero), in_macro_16x8); + + temp1 = _mm_packus_epi16(temp1, zero); + + p1_16x8_1 = _mm_and_si128(temp1, flag2_16x8); + p1_16x8 = _mm_and_si128(p1_16x8, + _mm_xor_si128(flag2_16x8, _mm_set1_epi16(0xFFFF))); + p1_16x8 = _mm_add_epi8(p1_16x8, p1_16x8_1); + + //if(Aq < Beta) + temp1 = _mm_avg_epu16(_mm_unpacklo_epi8(q0_16x8, zero), + _mm_unpacklo_epi8(p0_16x8, zero)); + temp2 = _mm_slli_epi16(_mm_unpacklo_epi8(q1_16x8, zero), 1); + //temp2 = _mm_slli_epi16 (temp2, 1); + temp2 = _mm_subs_epi16(_mm_unpacklo_epi8(q2_16x8, zero), temp2); + temp2 = _mm_add_epi16(temp1, temp2); + in_macro_16x8 = _mm_srai_epi16(temp2, 1); + + in_macro_16x8 = _mm_max_epi16(C0_8x16, in_macro_16x8); //CLIP3 + C0_8x16 = _mm_subs_epi16(zero, C0_8x16); + in_macro_16x8 = _mm_min_epi16(C0_8x16, in_macro_16x8); //CLIP3 + + temp1 = _mm_add_epi16(_mm_unpacklo_epi8(q1_16x8, zero), in_macro_16x8); + + // q1 + temp1 = _mm_packus_epi16(temp1, zero); + + q1_16x8_1 = _mm_and_si128(temp1, flag3_16x8); + q1_16x8 = _mm_and_si128(q1_16x8, + _mm_xor_si128(flag3_16x8, _mm_set1_epi16(0xFFFF))); + q1_16x8 = _mm_add_epi8(q1_16x8, q1_16x8_1); + + temp1 = _mm_unpacklo_epi8(p3_16x8, p2_16x8); + temp2 = _mm_unpacklo_epi8(p1_16x8, p0_16x8_1); + temp3 = _mm_unpacklo_epi8(q0_16x8_1, q1_16x8); + temp4 = _mm_unpacklo_epi8(q2_16x8, q3_16x8); + + line7 = _mm_unpacklo_epi16(temp1, temp2); + temp1 = _mm_unpackhi_epi16(temp1, temp2); + line8 = _mm_unpacklo_epi16(temp3, temp4); + temp2 = _mm_unpackhi_epi16(temp3, temp4); + + line1 = _mm_unpacklo_epi32(line7, line8); + line2 = _mm_srli_si128(line1, 8); + line3 = _mm_unpackhi_epi32(line7, line8); + line4 = _mm_srli_si128(line3, 8); + line5 = _mm_unpacklo_epi32(temp1, temp2); + line6 = _mm_srli_si128(line5, 8); + line7 = _mm_unpackhi_epi32(temp1, temp2); + line8 = _mm_srli_si128(line7, 8); + + _mm_storel_epi64((__m128i *)(pu1_src - 4 + 0 * src_strd), line1); + _mm_storel_epi64((__m128i *)(pu1_src - 4 + 1 * src_strd), line2); + _mm_storel_epi64((__m128i *)(pu1_src - 4 + 2 * src_strd), line3); + _mm_storel_epi64((__m128i *)(pu1_src - 4 + 3 * src_strd), line4); + _mm_storel_epi64((__m128i *)(pu1_src - 4 + 4 * src_strd), line5); + _mm_storel_epi64((__m128i *)(pu1_src - 4 + 5 * src_strd), line6); + _mm_storel_epi64((__m128i *)(pu1_src - 4 + 6 * src_strd), line7); + _mm_storel_epi64((__m128i *)(pu1_src - 4 + 7 * src_strd), line8); +} + diff --git a/dependencies/ih264d/common/x86/ih264_ihadamard_scaling_sse42.c b/dependencies/ih264d/common/x86/ih264_ihadamard_scaling_sse42.c new file mode 100644 index 00000000..3c4bb1c6 --- /dev/null +++ b/dependencies/ih264d/common/x86/ih264_ihadamard_scaling_sse42.c @@ -0,0 +1,248 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** + ******************************************************************************* + * @file + * ih264_ihadamard_scaling_sse42.c + * + * @brief + * Contains definition of functions for h264 inverse hadamard 4x4 transform and scaling + * + * @author + * Mohit + * + * @par List of Functions: + * - ih264_ihadamard_scaling_4x4_sse42() + * - ih264_ihadamard_scaling_2x2_uv_ssse42() + * + * @remarks + * + ******************************************************************************* + */ +/*****************************************************************************/ +/* File Includes */ +/*****************************************************************************/ + +/* User include files */ +#include "ih264_typedefs.h" +#include "ih264_defs.h" +#include "ih264_trans_macros.h" +#include "ih264_macros.h" +#include "ih264_trans_data.h" +#include "ih264_size_defs.h" +#include "ih264_structs.h" +#include "ih264_trans_quant_itrans_iquant.h" +#include <immintrin.h> +#include <smmintrin.h> + +/* + ******************************************************************************** + * + * @brief This function performs a 4x4 inverse hadamard transform on the 4x4 DC coefficients + * of a 16x16 intra prediction macroblock, and then performs scaling. + * prediction buffer + * + * @par Description: + * The DC coefficients pass through a 2-stage inverse hadamard transform. + * This inverse transformed content is scaled to based on Qp value. + * + * @param[in] pi2_src + * input 4x4 block of DC coefficients + * + * @param[out] pi2_out + * output 4x4 block + * + * @param[in] pu2_iscal_mat + * pointer to scaling list + * + * @param[in] pu2_weigh_mat + * pointer to weight matrix + * + * @param[in] u4_qp_div_6 + * Floor (qp/6) + * + * @param[in] pi4_tmp + * temporary buffer of size 1*16 + * + * @returns none + * + * @remarks none + * + ******************************************************************************* + */ +void ih264_ihadamard_scaling_4x4_sse42(WORD16* pi2_src, + WORD16* pi2_out, + const UWORD16 *pu2_iscal_mat, + const UWORD16 *pu2_weigh_mat, + UWORD32 u4_qp_div_6, + WORD32* pi4_tmp) +{ + __m128i src_r0_r1, src_r2_r3; + __m128i src_r0, src_r1, src_r2, src_r3; + __m128i temp0, temp1, temp2, temp3; + __m128i add_rshift = _mm_set1_epi32((u4_qp_div_6 < 6) ? (1 << (5 - u4_qp_div_6)) : 0); + __m128i mult_val = _mm_set1_epi32(pu2_iscal_mat[0] * pu2_weigh_mat[0]); + UNUSED (pi4_tmp); + + src_r0_r1 = _mm_loadu_si128((__m128i *) (pi2_src)); //a00 a01 a02 a03 a10 a11 a12 a13 -- the source matrix 0th,1st row + src_r2_r3 = _mm_loadu_si128((__m128i *) (pi2_src + 8)); //a20 a21 a22 a23 a30 a31 a32 a33 -- the source matrix 2nd,3rd row + //sign_reg = _mm_cmpgt_epi16(zero_8x16b, src_r0_r1); + src_r0 = _mm_cvtepi16_epi32(src_r0_r1); + src_r0_r1 = _mm_srli_si128(src_r0_r1, 8); + src_r1 = _mm_cvtepi16_epi32(src_r0_r1); + + src_r2 = _mm_cvtepi16_epi32(src_r2_r3); + src_r2_r3 = _mm_srli_si128(src_r2_r3, 8); + src_r3 = _mm_cvtepi16_epi32(src_r2_r3); + + /* Perform Inverse transform */ + /*-------------------------------------------------------------*/ + /* IDCT [ Horizontal transformation ] */ + /*-------------------------------------------------------------*/ + // Matrix transpose + /* + * a0 a1 a2 a3 + * b0 b1 b2 b3 + * c0 c1 c2 c3 + * d0 d1 d2 d3 + */ + temp0 = _mm_unpacklo_epi32(src_r0, src_r1); //a0 b0 a1 b1 + temp2 = _mm_unpacklo_epi32(src_r2, src_r3); //c0 d0 c1 d1 + temp1 = _mm_unpackhi_epi32(src_r0, src_r1); //a2 b2 a3 b3 + temp3 = _mm_unpackhi_epi32(src_r2, src_r3); //c2 d2 c3 d3 + src_r0 = _mm_unpacklo_epi64(temp0, temp2); //a0 b0 c0 d0 + src_r1 = _mm_unpackhi_epi64(temp0, temp2); //a1 b1 c1 d1 + src_r2 = _mm_unpacklo_epi64(temp1, temp3); //a2 b2 c2 d2 + src_r3 = _mm_unpackhi_epi64(temp1, temp3); //a3 b3 c3 d3 + + temp0 = _mm_add_epi32(src_r0, src_r3); + temp1 = _mm_add_epi32(src_r1, src_r2); + temp2 = _mm_sub_epi32(src_r1, src_r2); + temp3 = _mm_sub_epi32(src_r0, src_r3); + + src_r0 = _mm_add_epi32(temp0, temp1); + src_r1 = _mm_add_epi32(temp2, temp3); + src_r2 = _mm_sub_epi32(temp0, temp1); + src_r3 = _mm_sub_epi32(temp3, temp2); + + /*-------------------------------------------------------------*/ + /* IDCT [ Vertical transformation ] */ + /*-------------------------------------------------------------*/ + // Matrix transpose + /* + * a0 b0 c0 d0 + * a1 b1 c1 d1 + * a2 b2 c2 d2 + * a3 b3 c3 d3 + */ + temp0 = _mm_unpacklo_epi32(src_r0, src_r1); //a0 a1 b0 b1 + temp2 = _mm_unpacklo_epi32(src_r2, src_r3); //a2 a3 b2 b3 + temp1 = _mm_unpackhi_epi32(src_r0, src_r1); //c0 c1 d0 d1 + temp3 = _mm_unpackhi_epi32(src_r2, src_r3); //c2 c3 d2 d3 + src_r0 = _mm_unpacklo_epi64(temp0, temp2); //a0 a1 a2 a3 + src_r1 = _mm_unpackhi_epi64(temp0, temp2); //b0 b1 b2 b3 + src_r2 = _mm_unpacklo_epi64(temp1, temp3); //c0 c1 c2 c3 + src_r3 = _mm_unpackhi_epi64(temp1, temp3); //d0 d1 d2 d3 + + temp0 = _mm_add_epi32(src_r0, src_r3); + temp1 = _mm_add_epi32(src_r1, src_r2); + temp2 = _mm_sub_epi32(src_r1, src_r2); + temp3 = _mm_sub_epi32(src_r0, src_r3); + + src_r0 = _mm_add_epi32(temp0, temp1); + src_r1 = _mm_add_epi32(temp2, temp3); + src_r2 = _mm_sub_epi32(temp0, temp1); + src_r3 = _mm_sub_epi32(temp3, temp2); + + src_r0 = _mm_mullo_epi32(src_r0, mult_val); + src_r1 = _mm_mullo_epi32(src_r1, mult_val); + src_r2 = _mm_mullo_epi32(src_r2, mult_val); + src_r3 = _mm_mullo_epi32(src_r3, mult_val); + + //Scaling + if(u4_qp_div_6 >= 6) + { + src_r0 = _mm_slli_epi32(src_r0, u4_qp_div_6 - 6); + src_r1 = _mm_slli_epi32(src_r1, u4_qp_div_6 - 6); + src_r2 = _mm_slli_epi32(src_r2, u4_qp_div_6 - 6); + src_r3 = _mm_slli_epi32(src_r3, u4_qp_div_6 - 6); + } + else + { + temp0 = _mm_add_epi32(src_r0, add_rshift); + temp1 = _mm_add_epi32(src_r1, add_rshift); + temp2 = _mm_add_epi32(src_r2, add_rshift); + temp3 = _mm_add_epi32(src_r3, add_rshift); + src_r0 = _mm_srai_epi32(temp0, 6 - u4_qp_div_6); + src_r1 = _mm_srai_epi32(temp1, 6 - u4_qp_div_6); + src_r2 = _mm_srai_epi32(temp2, 6 - u4_qp_div_6); + src_r3 = _mm_srai_epi32(temp3, 6 - u4_qp_div_6); + } + src_r0_r1 = _mm_packs_epi32(src_r0, src_r1); + src_r2_r3 = _mm_packs_epi32(src_r2, src_r3); + + _mm_storeu_si128((__m128i *) (&pi2_out[0]), src_r0_r1); + _mm_storeu_si128((__m128i *) (&pi2_out[8]), src_r2_r3); +} + +void ih264_ihadamard_scaling_2x2_uv_sse42(WORD16* pi2_src, + WORD16* pi2_out, + const UWORD16 *pu2_iscal_mat, + const UWORD16 *pu2_weigh_mat, + UWORD32 u4_qp_div_6, + WORD32* pi4_tmp) +{ + __m128i src, plane_0, plane_1, temp0, temp1, sign_reg; + __m128i zero_8x16b = _mm_setzero_si128(); + __m128i scale_val = _mm_set1_epi32((WORD32)(pu2_iscal_mat[0] * pu2_weigh_mat[0])); + UNUSED(pi4_tmp); + + src = _mm_loadu_si128((__m128i *) pi2_src); //a0 a1 a2 a3 b0 b1 b2 b3 + sign_reg = _mm_cmpgt_epi16(zero_8x16b, src); + plane_0 = _mm_unpacklo_epi16(src, sign_reg); //a0 a1 a2 a3 -- 32 bits + plane_1 = _mm_unpackhi_epi16(src, sign_reg); //b0 b1 b2 b3 -- 32 bits + + temp0 = _mm_hadd_epi32(plane_0, plane_1); //a0+a1 a2+a3 b0+b1 b2+b3 + temp1 = _mm_hsub_epi32(plane_0, plane_1); //a0-a1 a2-a3 b0-b1 b2-b3 + plane_0 = _mm_hadd_epi32(temp0, temp1); //a0+a1+a2+a3 b0+b1+b2+b3 a0-a1+a2-a3 b0-b1+b2-b3 + plane_1 = _mm_hsub_epi32(temp0, temp1); //a0+a1-a2-a3 b0+b1-b2-b3 a0-a1-a2+a3 b0-b1-b2+b3 + temp0 = _mm_unpacklo_epi32(plane_0, plane_1); //a0+a1+a2+a3 a0+a1-a2-a3 b0+b1+b2+b3 b0+b1-b2-b3 + temp1 = _mm_unpackhi_epi32(plane_0, plane_1); //a0-a1+a2-a3 a0-a1-a2+a3 b0-b1+b2-b3 b0-b1-b2+b3 + + plane_0 = _mm_unpacklo_epi64(temp0, temp1); //a0+a1+a2+a3 a0+a1-a2-a3 a0-a1+a2-a3 a0-a1-a2+a3 + plane_1 = _mm_unpackhi_epi64(temp0, temp1); //b0+b1+b2+b3 b0+b1-b2-b3 b0-b1+b2-b3 b0-b1-b2+b3 + + plane_0 = _mm_shuffle_epi32(plane_0, 0xd8); //a0+a1+a2+a3 a0-a1+a2-a3 a0+a1-a2-a3 a0-a1-a2+a3 + plane_1 = _mm_shuffle_epi32(plane_1, 0xd8); //b0+b1+b2+b3 b0-b1+b2-b3 b0+b1-b2-b3 b0-b1-b2+b3 + + temp0 = _mm_mullo_epi32(scale_val, plane_0); //multiply by pu2_iscal_mat[0] * pu2_weigh_mat[0] + temp1 = _mm_mullo_epi32(scale_val, plane_1); //multiply by pu2_iscal_mat[0] * pu2_weigh_mat[0] + + temp0 = _mm_slli_epi32(temp0, u4_qp_div_6); + temp1 = _mm_slli_epi32(temp1, u4_qp_div_6); + + temp0 = _mm_srai_epi32(temp0, 5); + temp1 = _mm_srai_epi32(temp1, 5); + + temp0 = _mm_packs_epi32(temp0, temp1); //Final values are 16-bits only. + + _mm_storeu_si128((__m128i *) (&pi2_out[0]), temp0); + +} diff --git a/dependencies/ih264d/common/x86/ih264_ihadamard_scaling_ssse3.c b/dependencies/ih264d/common/x86/ih264_ihadamard_scaling_ssse3.c new file mode 100644 index 00000000..b4d483f1 --- /dev/null +++ b/dependencies/ih264d/common/x86/ih264_ihadamard_scaling_ssse3.c @@ -0,0 +1,209 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** + ******************************************************************************* + * @file + * ih264_ihadamard_scaling_ssse3.c + * + * @brief + * Contains definition of functions for h264 inverse hadamard 4x4 transform and scaling + * + * @author + * Mohit + * + * @par List of Functions: + * - ih264_ihadamard_scaling_4x4_ssse3() + * + * @remarks + * + ******************************************************************************* + */ +/*****************************************************************************/ +/* File Includes */ +/*****************************************************************************/ + +/* User include files */ +#include "ih264_typedefs.h" +#include "ih264_defs.h" +#include "ih264_trans_macros.h" +#include "ih264_macros.h" +#include "ih264_trans_data.h" +#include "ih264_size_defs.h" +#include "ih264_structs.h" +#include "ih264_trans_quant_itrans_iquant.h" +#include <immintrin.h> + +/* + ******************************************************************************** + * + * @brief This function performs a 4x4 inverse hadamard transform on the 4x4 DC coefficients + * of a 16x16 intra prediction macroblock, and then performs scaling. + * prediction buffer + * + * @par Description: + * The DC coefficients pass through a 2-stage inverse hadamard transform. + * This inverse transformed content is scaled to based on Qp value. + * + * @param[in] pi2_src + * input 4x4 block of DC coefficients + * + * @param[out] pi2_out + * output 4x4 block + * + * @param[in] pu2_iscal_mat + * pointer to scaling list + * + * @param[in] pu2_weigh_mat + * pointer to weight matrix + * + * @param[in] u4_qp_div_6 + * Floor (qp/6) + * + * @param[in] pi4_tmp + * temporary buffer of size 1*16 + * + * @returns none + * + * @remarks none + * + ******************************************************************************* + */ +void ih264_ihadamard_scaling_4x4_ssse3(WORD16* pi2_src, + WORD16* pi2_out, + const UWORD16 *pu2_iscal_mat, + const UWORD16 *pu2_weigh_mat, + UWORD32 u4_qp_div_6, + WORD32* pi4_tmp) +{ + int val = 0xFFFF; + __m128i src_r0_r1, src_r2_r3, sign_reg, zero_8x16b = _mm_setzero_si128(); + __m128i src_r0, src_r1, src_r2, src_r3; + __m128i temp0, temp1, temp2, temp3; + __m128i add_rshift = _mm_set1_epi32((u4_qp_div_6 < 6) ? (1 << (5 - u4_qp_div_6)) : 0); + __m128i mult_val = _mm_set1_epi32(pu2_iscal_mat[0] * pu2_weigh_mat[0]); + + __m128i mask = _mm_set1_epi32(val); + UNUSED (pi4_tmp); + + mult_val = _mm_and_si128(mult_val, mask); + + src_r0_r1 = _mm_loadu_si128((__m128i *) (pi2_src)); //a00 a01 a02 a03 a10 a11 a12 a13 -- the source matrix 0th,1st row + src_r2_r3 = _mm_loadu_si128((__m128i *) (pi2_src + 8)); //a20 a21 a22 a23 a30 a31 a32 a33 -- the source matrix 2nd,3rd row + sign_reg = _mm_cmpgt_epi16(zero_8x16b, src_r0_r1); + src_r0 = _mm_unpacklo_epi16(src_r0_r1, sign_reg); + src_r1 = _mm_unpackhi_epi16(src_r0_r1, sign_reg); + sign_reg = _mm_cmpgt_epi16(zero_8x16b, src_r2_r3); + src_r2 = _mm_unpacklo_epi16(src_r2_r3, sign_reg); + src_r3 = _mm_unpackhi_epi16(src_r2_r3, sign_reg); + + /* Perform Inverse transform */ + /*-------------------------------------------------------------*/ + /* IDCT [ Horizontal transformation ] */ + /*-------------------------------------------------------------*/ + // Matrix transpose + /* + * a0 a1 a2 a3 + * b0 b1 b2 b3 + * c0 c1 c2 c3 + * d0 d1 d2 d3 + */ + temp0 = _mm_unpacklo_epi32(src_r0, src_r1); //a0 b0 a1 b1 + temp2 = _mm_unpacklo_epi32(src_r2, src_r3); //c0 d0 c1 d1 + temp1 = _mm_unpackhi_epi32(src_r0, src_r1); //a2 b2 a3 b3 + temp3 = _mm_unpackhi_epi32(src_r2, src_r3); //c2 d2 c3 d3 + src_r0 = _mm_unpacklo_epi64(temp0, temp2); //a0 b0 c0 d0 + src_r1 = _mm_unpackhi_epi64(temp0, temp2); //a1 b1 c1 d1 + src_r2 = _mm_unpacklo_epi64(temp1, temp3); //a2 b2 c2 d2 + src_r3 = _mm_unpackhi_epi64(temp1, temp3); //a3 b3 c3 d3 + + temp0 = _mm_add_epi32(src_r0, src_r3); + temp1 = _mm_add_epi32(src_r1, src_r2); + temp2 = _mm_sub_epi32(src_r1, src_r2); + temp3 = _mm_sub_epi32(src_r0, src_r3); + + src_r0 = _mm_add_epi32(temp0, temp1); + src_r1 = _mm_add_epi32(temp2, temp3); + src_r2 = _mm_sub_epi32(temp0, temp1); + src_r3 = _mm_sub_epi32(temp3, temp2); + + /*-------------------------------------------------------------*/ + /* IDCT [ Vertical transformation ] */ + /*-------------------------------------------------------------*/ + // Matrix transpose + /* + * a0 b0 c0 d0 + * a1 b1 c1 d1 + * a2 b2 c2 d2 + * a3 b3 c3 d3 + */ + temp0 = _mm_unpacklo_epi32(src_r0, src_r1); //a0 a1 b0 b1 + temp2 = _mm_unpacklo_epi32(src_r2, src_r3); //a2 a3 b2 b3 + temp1 = _mm_unpackhi_epi32(src_r0, src_r1); //c0 c1 d0 d1 + temp3 = _mm_unpackhi_epi32(src_r2, src_r3); //c2 c3 d2 d3 + src_r0 = _mm_unpacklo_epi64(temp0, temp2); //a0 a1 a2 a3 + src_r1 = _mm_unpackhi_epi64(temp0, temp2); //b0 b1 b2 b3 + src_r2 = _mm_unpacklo_epi64(temp1, temp3); //c0 c1 c2 c3 + src_r3 = _mm_unpackhi_epi64(temp1, temp3); //d0 d1 d2 d3 + + temp0 = _mm_add_epi32(src_r0, src_r3); + temp1 = _mm_add_epi32(src_r1, src_r2); + temp2 = _mm_sub_epi32(src_r1, src_r2); + temp3 = _mm_sub_epi32(src_r0, src_r3); + + src_r0 = _mm_add_epi32(temp0, temp1); + src_r1 = _mm_add_epi32(temp2, temp3); + src_r2 = _mm_sub_epi32(temp0, temp1); + src_r3 = _mm_sub_epi32(temp3, temp2); + + src_r0 = _mm_and_si128(src_r0, mask); + src_r1 = _mm_and_si128(src_r1, mask); + src_r2 = _mm_and_si128(src_r2, mask); + src_r3 = _mm_and_si128(src_r3, mask); + + src_r0 = _mm_madd_epi16(src_r0, mult_val); + src_r1 = _mm_madd_epi16(src_r1, mult_val); + src_r2 = _mm_madd_epi16(src_r2, mult_val); + src_r3 = _mm_madd_epi16(src_r3, mult_val); + + //Scaling + if(u4_qp_div_6 >= 6) + { + src_r0 = _mm_slli_epi32(src_r0, u4_qp_div_6 - 6); + src_r1 = _mm_slli_epi32(src_r1, u4_qp_div_6 - 6); + src_r2 = _mm_slli_epi32(src_r2, u4_qp_div_6 - 6); + src_r3 = _mm_slli_epi32(src_r3, u4_qp_div_6 - 6); + } + else + { + temp0 = _mm_add_epi32(src_r0, add_rshift); + temp1 = _mm_add_epi32(src_r1, add_rshift); + temp2 = _mm_add_epi32(src_r2, add_rshift); + temp3 = _mm_add_epi32(src_r3, add_rshift); + src_r0 = _mm_srai_epi32(temp0, 6 - u4_qp_div_6); + src_r1 = _mm_srai_epi32(temp1, 6 - u4_qp_div_6); + src_r2 = _mm_srai_epi32(temp2, 6 - u4_qp_div_6); + src_r3 = _mm_srai_epi32(temp3, 6 - u4_qp_div_6); + } + src_r0_r1 = _mm_packs_epi32(src_r0, src_r1); + src_r2_r3 = _mm_packs_epi32(src_r2, src_r3); + + _mm_storeu_si128((__m128i *) (&pi2_out[0]), src_r0_r1); + _mm_storeu_si128((__m128i *) (&pi2_out[8]), src_r2_r3); +} diff --git a/dependencies/ih264d/common/x86/ih264_inter_pred_filters_ssse3.c b/dependencies/ih264d/common/x86/ih264_inter_pred_filters_ssse3.c new file mode 100644 index 00000000..480a8c7c --- /dev/null +++ b/dependencies/ih264d/common/x86/ih264_inter_pred_filters_ssse3.c @@ -0,0 +1,4266 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/*****************************************************************************/ +/* */ +/* File Name : ih264_inter_pred_filters_intr_ssse3.c */ +/* */ +/* Description : Contains function definitions for weighted */ +/* prediction functions in x86 sse4 intrinsics */ +/* */ +/* List of Functions : ih264_inter_pred_luma_copy_ssse3() */ +/* ih264_inter_pred_luma_horz_ssse3() */ +/* ih264_inter_pred_luma_vert_ssse3() */ +/* ih264_inter_pred_luma_horz_hpel_vert_hpel_ssse3() */ +/* ih264_inter_pred_luma_horz_qpel_ssse3() */ +/* ih264_inter_pred_luma_vert_qpel_ssse3() */ +/* ih264_inter_pred_luma_horz_qpel_vert_qpel_ssse3() */ +/* ih264_inter_pred_luma_horz_hpel_vert_qpel_ssse3() */ +/* ih264_inter_pred_luma_horz_qpel_vert_hpel_ssse3() */ +/* ih264_inter_pred_chroma_ssse3() */ +/* */ +/* Issues / Problems : None */ +/* */ +/* Revision History : */ +/* */ +/* DD MM YYYY Author(s) Changes */ +/* 13 02 2015 Kaushik Initial version */ +/* Senthoor */ +/* */ +/*****************************************************************************/ +/*****************************************************************************/ +/* File Includes */ +/*****************************************************************************/ + +#include <immintrin.h> +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264_inter_pred_filters.h" + +/*****************************************************************************/ +/* Constant Data variables */ +/*****************************************************************************/ + +/* coefficients for 6 tap filtering*/ +//const WORD32 ih264_g_six_tap[3] ={1,-5,20}; +/*****************************************************************************/ +/* Function definitions . */ +/*****************************************************************************/ +/*****************************************************************************/ +/* */ +/* Function Name : ih264_inter_pred_luma_copy_ssse3 */ +/* */ +/* Description : This function copies the contents of ht x wd block from */ +/* source to destination. (ht,wd) can be (4,4), (8,4), */ +/* (4,8), (8,8), (16,8), (8,16) or (16,16). */ +/* */ +/* Inputs : puc_src - pointer to source */ +/* puc_dst - pointer to destination */ +/* src_strd - stride for source */ +/* dst_strd - stride for destination */ +/* ht - height of the block */ +/* wd - width of the block */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes */ +/* 13 02 2015 Kaushik Initial Version */ +/* Senthoor */ +/* */ +/*****************************************************************************/ +void ih264_inter_pred_luma_copy_ssse3(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ht, + WORD32 wd, + UWORD8* pu1_tmp, + WORD32 dydx) +{ + __m128i y_0_16x8b, y_1_16x8b, y_2_16x8b, y_3_16x8b; + + WORD32 src_strd2, src_strd3, src_strd4, dst_strd2, dst_strd3, dst_strd4; + UNUSED(pu1_tmp); + UNUSED(dydx); + + src_strd2 = src_strd << 1; + dst_strd2 = dst_strd << 1; + src_strd4 = src_strd << 2; + dst_strd4 = dst_strd << 2; + src_strd3 = src_strd2 + src_strd; + dst_strd3 = dst_strd2 + dst_strd; + + if(wd == 4) + { + do + { + *((WORD32 *)(pu1_dst)) = *((WORD32 *)(pu1_src)); + *((WORD32 *)(pu1_dst + dst_strd)) = *((WORD32 *)(pu1_src + src_strd)); + *((WORD32 *)(pu1_dst + dst_strd2)) = *((WORD32 *)(pu1_src + src_strd2)); + *((WORD32 *)(pu1_dst + dst_strd3)) = *((WORD32 *)(pu1_src + src_strd3)); + + ht -= 4; + pu1_src += src_strd4; + pu1_dst += dst_strd4; + } + while(ht > 0); + } + else if(wd == 8) + { + do + { + y_0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); + y_1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src + src_strd)); + y_2_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src + src_strd2)); + y_3_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src + src_strd3)); + + _mm_storel_epi64((__m128i *)pu1_dst, y_0_16x8b); + _mm_storel_epi64((__m128i *)(pu1_dst + dst_strd), y_1_16x8b); + _mm_storel_epi64((__m128i *)(pu1_dst + dst_strd2), y_2_16x8b); + _mm_storel_epi64((__m128i *)(pu1_dst + dst_strd3), y_3_16x8b); + + ht -= 4; + pu1_src += src_strd4; + pu1_dst += dst_strd4; + } + while(ht > 0); + } + else // wd == 16 + { + WORD32 src_strd5, src_strd6, src_strd7, src_strd8; + WORD32 dst_strd5, dst_strd6, dst_strd7, dst_strd8; + + __m128i y_4_16x8b, y_5_16x8b, y_6_16x8b, y_7_16x8b; + + src_strd5 = src_strd2 + src_strd3; + dst_strd5 = dst_strd2 + dst_strd3; + src_strd6 = src_strd3 << 1; + dst_strd6 = dst_strd3 << 1; + src_strd7 = src_strd3 + src_strd4; + dst_strd7 = dst_strd3 + dst_strd4; + src_strd8 = src_strd << 3; + dst_strd8 = dst_strd << 3; + + do + { + y_0_16x8b = _mm_loadu_si128((__m128i *)pu1_src); + y_1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd)); + y_2_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd2)); + y_3_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd3)); + y_4_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd4)); + y_5_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd5)); + y_6_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd6)); + y_7_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd7)); + + _mm_storeu_si128((__m128i *)pu1_dst, y_0_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), y_1_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd2), y_2_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd3), y_3_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd4), y_4_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd5), y_5_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd6), y_6_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd7), y_7_16x8b); + + ht -= 8; + pu1_src += src_strd8; + pu1_dst += dst_strd8; + } + while(ht > 0); + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_inter_pred_luma_horz_ssse3 */ +/* */ +/* Description : This function applies a horizontal 6-tap filter on */ +/* ht x wd block as mentioned in sec. 8.4.2.2.1 titled */ +/* "Luma sample interpolation process". (ht,wd) can be */ +/* (4,4), (8,4), (4,8), (8,8), (16,8), (8,16) or (16,16). */ +/* */ +/* Inputs : puc_src - pointer to source */ +/* puc_dst - pointer to destination */ +/* src_strd - stride for source */ +/* dst_strd - stride for destination */ +/* ht - height of the block */ +/* wd - width of the block */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes */ +/* 13 02 2015 Kaushik Initial Version */ +/* Senthoor */ +/* */ +/*****************************************************************************/ +void ih264_inter_pred_luma_horz_ssse3(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ht, + WORD32 wd, + UWORD8* pu1_tmp, + WORD32 dydx) +{ + __m128i coeff0_1_16x8b, coeff2_3_16x8b, coeff4_5_16x8b; + __m128i const_val16_8x16b; + + UNUSED(pu1_tmp); + UNUSED(dydx); + + pu1_src -= 2; // the filter input starts from x[-2] (till x[3]) + + coeff0_1_16x8b = _mm_set1_epi32(0xFB01FB01); //c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 + coeff2_3_16x8b = _mm_set1_epi32(0x14141414); //c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 + coeff4_5_16x8b = _mm_set1_epi32(0x01FB01FB); //c4 c5 c4 c5 c4 c5 c4 c5 c4 c5 c4 c5 c4 c5 c4 c5 + //c0 = c5 = 1, c1 = c4 = -5, c2 = c3 = 20 + const_val16_8x16b = _mm_set1_epi16(16); + + if(wd == 4) + { + __m128i src_r0_16x8b, src_r1_16x8b, src_r0r1_16x8b; + __m128i src_r0_sht_16x8b, src_r1_sht_16x8b; + + __m128i res_r0r1_t1_8x16b, res_r0r1_t2_8x16b, res_r0r1_t3_8x16b; + __m128i res_r0r1_16x8b; + + //Row0 : a0 a1 a2 a3 a4 a5 a6 a7 a8 a9..... + //Row1 : b0 b1 b2 b3 b4 b5 b6 b7 b8 b9..... + + do + { + src_r0_16x8b = _mm_loadu_si128((__m128i *)pu1_src); //a0 a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 + src_r1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd)); //b0 b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 + + src_r0_sht_16x8b = _mm_srli_si128(src_r0_16x8b, 1); //a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 0 + src_r1_sht_16x8b = _mm_srli_si128(src_r1_16x8b, 1); //b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 0 + + src_r0_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a0 a1 a1 a2 a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 + src_r1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b0 b1 b1 b2 b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 + + src_r0r1_16x8b = _mm_unpacklo_epi64(src_r0_16x8b, src_r1_16x8b); //a0 a1 a1 a2 a2 a3 a3 a4 b0 b1 b1 b2 b2 b3 b3 b4 + res_r0r1_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); //a0*c0+a1*c1 a1*c0+a2*c1 a2*c0+a3*c1 a3*c0+a4*c1 + //b0*c0+b1*c1 b1*c0+b2*c1 b2*c0+b3*c1 b3*c0+b4*c1 + + src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 4); //a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 0 0 0 0 + src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 4); //b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 0 0 0 0 + + src_r0r1_16x8b = _mm_unpacklo_epi64(src_r0_16x8b, src_r1_16x8b); //a2 a3 a3 a4 a4 a5 a5 a6 b2 b3 b3 b4 b4 b5 b5 b6 + res_r0r1_t2_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff2_3_16x8b); //a2*c2+a3*c3 a3*c2+a4*c3 a4*c2+a5*c3 a5*c2+a6*c3 + //b2*c2+b3*c3 b3*c2+b4*c3 b4*c2+b5*c3 b5*c2+b6*c3 + + src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 4); //a4 a5 a5 a6 a6 a7 a7 a8 0 0 0 0 0 0 0 0 + src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 4); //b4 b5 b5 b6 b6 b7 b7 b8 0 0 0 0 0 0 0 0 + + src_r0r1_16x8b = _mm_unpacklo_epi64(src_r0_16x8b, src_r1_16x8b); //a4 a5 a5 a6 a6 a7 a7 a8 b4 b5 b5 b6 b6 b7 b7 b8 + res_r0r1_t3_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff4_5_16x8b); //a4*c4+a5*c5 a5*c4+a6*c5 a6*c4+a7*c5 a7*c4+a8*c5 + //b4*c4+b5*c5 b5*c4+b6*c5 b4*c6+b7*c5 b7*c4+b8*c5 + + res_r0r1_t1_8x16b = _mm_add_epi16(res_r0r1_t1_8x16b, res_r0r1_t2_8x16b); + res_r0r1_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_r0r1_t3_8x16b); + res_r0r1_t1_8x16b = _mm_add_epi16(res_r0r1_t1_8x16b, res_r0r1_t3_8x16b); //a0*c0+a1*c1+a2*c2+a3*c3+a4*a4+a5*c5 + 16; + //a1*c0+a2*c1+a2*c2+a3*c3+a5*a4+a6*c5 + 16; + //a2*c0+a3*c1+a4*c2+a5*c3+a6*a4+a7*c5 + 16; + //a3*c0+a4*c1+a5*c2+a6*c3+a6*a4+a8*c5 + 16; + //b0*c0+b1*c1+b2*c2+b3*c3+b4*b4+b5*c5 + 16; + //b1*c0+b2*c1+b2*c2+b3*c3+b5*b4+b6*c5 + 16; + //b2*c0+b3*c1+b4*c2+b5*c3+b6*b4+b7*c5 + 16; + //b3*c0+b4*c1+b5*c2+b6*c3+b6*b4+b8*c5 + 16; + + res_r0r1_t1_8x16b = _mm_srai_epi16(res_r0r1_t1_8x16b, 5); //shifting right by 5 bits. + + res_r0r1_16x8b = _mm_packus_epi16(res_r0r1_t1_8x16b, res_r0r1_t1_8x16b); + + *((WORD32 *)(pu1_dst)) = _mm_cvtsi128_si32(res_r0r1_16x8b); + res_r0r1_16x8b = _mm_srli_si128(res_r0r1_16x8b, 4); + *((WORD32 *)(pu1_dst + dst_strd)) = _mm_cvtsi128_si32(res_r0r1_16x8b); + + ht -= 2; + pu1_src += src_strd << 1; + pu1_dst += dst_strd << 1; + } + while(ht > 0); + } + else if(wd == 8) + { + __m128i src_r0_16x8b, src_r1_16x8b, src_r0_sht_16x8b, src_r1_sht_16x8b; + __m128i src_r0_t1_16x8b, src_r1_t1_16x8b; + + __m128i res_r0_t1_8x16b, res_r0_t2_8x16b, res_r0_t3_8x16b; + __m128i res_r1_t1_8x16b, res_r1_t2_8x16b, res_r1_t3_8x16b; + + //Row0 : a0 a1 a2 a3 a4 a5 a6 a7 a8 a9..... + //Row1 : b0 b1 b2 b3 b4 b5 b6 b7 b8 b9..... + + do + { + src_r0_16x8b = _mm_loadu_si128((__m128i *)pu1_src); //a0 a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 + src_r1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd)); //b0 b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 + + src_r0_sht_16x8b = _mm_srli_si128(src_r0_16x8b, 1); //a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 0 + src_r1_sht_16x8b = _mm_srli_si128(src_r1_16x8b, 1); //b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 0 + + src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a0 a1 a1 a2 a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 + src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b0 b1 b1 b2 b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 + + res_r0_t1_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff0_1_16x8b); //a0*c0+a1*c1 a1*c0+a2*c1 a2*c0+a3*c1 a3*c0+a4*c1 + //a4*c0+a5*c1 a5*c0+a6*c1 a6*c0+a7*c1 a7*c0+a8*c1 + res_r1_t1_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff0_1_16x8b); //b0*c0+b1*c1 b1*c0+b2*c1 b2*c0+b3*c1 b3*c0+b4*c1 + //b4*c0+b5*c1 b5*c0+b6*c1 b6*c0+b7*c1 b7*c0+b8*c1 + + src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 2); //a2 a3 a4 a5 a6 a7 a8 a9....a15 0 0 + src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 2); //b2 b3 b4 b5 b6 b7 b8 b9....b15 0 0 + + src_r0_sht_16x8b = _mm_srli_si128(src_r0_sht_16x8b, 2); //a3 a4 a5 a6 a7 a8 a9....a15 0 0 0 + src_r1_sht_16x8b = _mm_srli_si128(src_r1_sht_16x8b, 2); //b3 b4 b5 b6 b7 b8 b9....b15 0 0 0 + + src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 a8 a9 a9 a10 + src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 a8 a9 a9 a10 + + res_r0_t2_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff2_3_16x8b); //a2*c2+a3*c3 a3*c2+a4*c3 a4*c2+a5*c3 a5*c2+a6*c3 + //a6*c2+a7*c3 a7*c2+a8*c3 a8*c2+a9*c3 a9*c2+a10*c3 + res_r1_t2_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff2_3_16x8b); //b2*c2+b3*c3 b3*c2+b4*c3 b2*c4+b5*c3 b5*c2+b6*c3 + //b6*c2+b7*c3 b7*c2+b8*c3 b8*c2+b9*c3 b9*c2+b10*c3 + + src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 2); //a4 a5 a6 a7 a8 a9....a15 0 0 0 0 + src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 2); //b4 b5 b6 b7 b8 b9....b15 0 0 0 0 + + src_r0_sht_16x8b = _mm_srli_si128(src_r0_sht_16x8b, 2); //a5 a6 a7 a8 a9....a15 0 0 0 0 0 + src_r1_sht_16x8b = _mm_srli_si128(src_r1_sht_16x8b, 2); //b5 b6 b7 b8 b9....b15 0 0 0 0 0 + + src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a4 a5 a5 a6 a6 a7 a7 a8 a8 a9 a9 a10 a10 a11 a11 a12 + src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b4 b5 b5 b6 b6 b7 b7 b8 b8 b9 b9 b10 b10 b11 b11 b12 + + res_r0_t3_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff4_5_16x8b); //a4*c4+a5*c5 a5*c4+a6*c5 a6*c4+a7*c5 a7*c4+a8*c5 + //a8*c4+a9*c5 a9*c4+a10*c5 a10*c4+a11*c5 a11*c4+a12*c5 + res_r1_t3_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff4_5_16x8b); //b4*c4+b5*c5 b5*c4+b6*c5 b6*c4+b7*c5 b7*c4+b8*c5 + //b8*c4+b9*c5 b9*c4+b10*c5 b10*c4+b11*c5 b11*c4+b12*c5 + res_r0_t1_8x16b = _mm_add_epi16(res_r0_t1_8x16b, res_r0_t2_8x16b); + res_r1_t1_8x16b = _mm_add_epi16(res_r1_t1_8x16b, res_r1_t2_8x16b); + res_r0_t3_8x16b = _mm_add_epi16(res_r0_t3_8x16b, const_val16_8x16b); + res_r1_t3_8x16b = _mm_add_epi16(res_r1_t3_8x16b, const_val16_8x16b); + res_r0_t1_8x16b = _mm_add_epi16(res_r0_t1_8x16b, res_r0_t3_8x16b); + res_r1_t1_8x16b = _mm_add_epi16(res_r1_t1_8x16b, res_r1_t3_8x16b); + + res_r0_t1_8x16b = _mm_srai_epi16(res_r0_t1_8x16b, 5); //shifting right by 5 bits. + res_r1_t1_8x16b = _mm_srai_epi16(res_r1_t1_8x16b, 5); + + src_r0_16x8b = _mm_packus_epi16(res_r0_t1_8x16b, res_r0_t1_8x16b); + src_r1_16x8b = _mm_packus_epi16(res_r1_t1_8x16b, res_r1_t1_8x16b); + + _mm_storel_epi64((__m128i *)pu1_dst, src_r0_16x8b); + _mm_storel_epi64((__m128i *)(pu1_dst + dst_strd), src_r1_16x8b); + + ht -= 2; + pu1_src += src_strd << 1; + pu1_dst += dst_strd << 1; + } + while(ht > 0); + } + else // wd == 16 + { + __m128i src_r0_16x8b, src_r1_16x8b, src_r0_sht_16x8b, src_r1_sht_16x8b; + __m128i src_r0_t1_16x8b, src_r1_t1_16x8b; + + __m128i res_r0_t1_8x16b, res_r0_t2_8x16b, res_r0_t3_8x16b; + __m128i res_r1_t1_8x16b, res_r1_t2_8x16b, res_r1_t3_8x16b; + + //Row0 : a0 a1 a2 a3 a4 a5 a6 a7 a8 a9..... + //Row0 : b0 b1 b2 b3 b4 b5 b6 b7 b8 b9..... + //b0 is same a8. Similarly other bn pixels are same as a(n+8) pixels. + + do + { + src_r0_16x8b = _mm_loadu_si128((__m128i *)pu1_src); //a0 a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 + src_r1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + 8)); //b0 b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 + + src_r0_sht_16x8b = _mm_srli_si128(src_r0_16x8b, 1); //a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 0 + src_r1_sht_16x8b = _mm_srli_si128(src_r1_16x8b, 1); //b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 0 + + src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a0 a1 a1 a2 a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 + src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b0 b1 b1 b2 b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 + + res_r0_t1_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff0_1_16x8b); //a0*c0+a1*c1 a1*c0+a2*c1 a2*c0+a3*c1 a3*c0+a4*c1 + //a4*c0+a5*c1 a5*c0+a6*c1 a6*c0+a7*c1 a7*c0+a8*c1 + res_r1_t1_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff0_1_16x8b); //b0*c0+b1*c1 b1*c0+b2*c1 b2*c0+b3*c1 b3*c0+b4*c1 + //b4*c0+b5*c1 b5*c0+b6*c1 b6*c0+b7*c1 b7*c0+b8*c1 + + src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 2); //a2 a3 a4 a5 a6 a7 a8 a9....a15 0 0 + src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 2); //b2 b3 b4 b5 b6 b7 b8 b9....b15 0 0 + + src_r0_sht_16x8b = _mm_srli_si128(src_r0_sht_16x8b, 2); //a3 a4 a5 a6 a7 a8 a9....a15 0 0 0 + src_r1_sht_16x8b = _mm_srli_si128(src_r1_sht_16x8b, 2); //b3 b4 b5 b6 b7 b8 b9....b15 0 0 0 + + src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 a8 a9 a9 a10 + src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 a8 a9 a9 a10 + + res_r0_t2_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff2_3_16x8b); //a2*c2+a3*c3 a3*c2+a4*c3 a4*c2+a5*c3 a5*c2+a6*c3 + //a6*c2+a7*c3 a7*c2+a8*c3 a8*c2+a9*c3 a9*c2+a10*c3 + res_r1_t2_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff2_3_16x8b); //b2*c2+b3*c3 b3*c2+b4*c3 b2*c4+b5*c3 b5*c2+b6*c3 + //b6*c2+b7*c3 b7*c2+b8*c3 b8*c2+b9*c3 b9*c2+b10*c3 + + src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 2); //a4 a5 a6 a7 a8 a9....a15 0 0 0 0 + src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 2); //b4 b5 b6 b7 b8 b9....b15 0 0 0 0 + + src_r0_sht_16x8b = _mm_srli_si128(src_r0_sht_16x8b, 2); //a5 a6 a7 a8 a9....a15 0 0 0 0 0 + src_r1_sht_16x8b = _mm_srli_si128(src_r1_sht_16x8b, 2); //b5 b6 b7 b8 b9....b15 0 0 0 0 0 + + src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a4 a5 a5 a6 a6 a7 a7 a8 a8 a9 a9 a10 a10 a11 a11 a12 + src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b4 b5 b5 b6 b6 b7 b7 b8 b8 b9 b9 b10 b10 b11 b11 b12 + + res_r0_t3_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff4_5_16x8b); //a4*c4+a5*c5 a5*c4+a6*c5 a6*c4+a7*c5 a7*c4+a8*c5 + //a8*c4+a9*c5 a9*c4+a10*c5 a10*c4+a11*c5 a11*c4+a12*c5 + res_r1_t3_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff4_5_16x8b); //b4*c4+b5*c5 b5*c4+b6*c5 b6*c4+b7*c5 b7*c4+b8*c5 + //b8*c4+b9*c5 b9*c4+b10*c5 b10*c4+b11*c5 b11*c4+b12*c5 + res_r0_t1_8x16b = _mm_add_epi16(res_r0_t1_8x16b, res_r0_t2_8x16b); + res_r1_t1_8x16b = _mm_add_epi16(res_r1_t1_8x16b, res_r1_t2_8x16b); + res_r0_t3_8x16b = _mm_add_epi16(res_r0_t3_8x16b, const_val16_8x16b); + res_r1_t3_8x16b = _mm_add_epi16(res_r1_t3_8x16b, const_val16_8x16b); + res_r0_t1_8x16b = _mm_add_epi16(res_r0_t1_8x16b, res_r0_t3_8x16b); + res_r1_t1_8x16b = _mm_add_epi16(res_r1_t1_8x16b, res_r1_t3_8x16b); + + res_r0_t1_8x16b = _mm_srai_epi16(res_r0_t1_8x16b, 5); //shifting right by 5 bits. + res_r1_t1_8x16b = _mm_srai_epi16(res_r1_t1_8x16b, 5); + + src_r0_16x8b = _mm_packus_epi16(res_r0_t1_8x16b, res_r1_t1_8x16b); + _mm_storeu_si128((__m128i *)pu1_dst, src_r0_16x8b); + + ht--; + pu1_src += src_strd; + pu1_dst += dst_strd; + } + while(ht > 0); + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_inter_pred_luma_vert_ssse3 */ +/* */ +/* Description : This function applies a vertical 6-tap filter on */ +/* ht x wd block as mentioned in sec. 8.4.2.2.1 titled */ +/* "Luma sample interpolation process". (ht,wd) can be */ +/* (4,4), (8,4), (4,8), (8,8), (16,8), (8,16) or (16,16). */ +/* */ +/* Inputs : puc_src - pointer to source */ +/* puc_dst - pointer to destination */ +/* src_strd - stride for source */ +/* dst_strd - stride for destination */ +/* ht - height of the block */ +/* wd - width of the block */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes */ +/* 13 02 2015 Kaushik Initial Version */ +/* Senthoor */ +/* */ +/*****************************************************************************/ +void ih264_inter_pred_luma_vert_ssse3(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ht, + WORD32 wd, + UWORD8* pu1_tmp, + WORD32 dydx) +{ + __m128i src_r0_16x8b, src_r1_16x8b, src_r2_16x8b, src_r3_16x8b, src_r4_16x8b; + __m128i src_r5_16x8b, src_r6_16x8b; + __m128i src_r0r1_16x8b, src_r2r3_16x8b, src_r4r5_16x8b; + + __m128i res_16x8b, res_t1_8x16b, res_t2_8x16b, res_t3_8x16b; + + __m128i coeff0_1_16x8b, coeff2_3_16x8b, coeff4_5_16x8b; + __m128i const_val16_8x16b; + + UNUSED(pu1_tmp); + UNUSED(dydx); + + coeff0_1_16x8b = _mm_set1_epi32(0xFB01FB01); //c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 + coeff2_3_16x8b = _mm_set1_epi32(0x14141414); //c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 + coeff4_5_16x8b = _mm_set1_epi32(0x01FB01FB); //c4 c5 c4 c5 c4 c5 c4 c5 c4 c5 c4 c5 c4 c5 c4 c5 + //c0 = c5 = 1, c1 = c4 = -5, c2 = c3 = 20 + const_val16_8x16b = _mm_set1_epi16(16); + + pu1_src -= src_strd << 1; // the filter input starts from x[-2] (till x[3]) + + if(wd == 4) + { + //Epilogue: Load all the pred rows except sixth and seventh row + // for the first and second row processing. + src_r0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); + pu1_src += src_strd; + src_r1_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); + pu1_src += src_strd; + src_r2_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); + pu1_src += src_strd; + src_r3_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); + pu1_src += src_strd; + src_r4_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); + pu1_src += src_strd; + + src_r0_16x8b = _mm_unpacklo_epi32(src_r0_16x8b, src_r1_16x8b); + src_r1_16x8b = _mm_unpacklo_epi32(src_r1_16x8b, src_r2_16x8b); + src_r2_16x8b = _mm_unpacklo_epi32(src_r2_16x8b, src_r3_16x8b); + src_r3_16x8b = _mm_unpacklo_epi32(src_r3_16x8b, src_r4_16x8b); + + do + { + src_r5_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); + src_r6_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src + src_strd)); + + src_r4_16x8b = _mm_unpacklo_epi32(src_r4_16x8b, src_r5_16x8b); + src_r5_16x8b = _mm_unpacklo_epi32(src_r5_16x8b, src_r6_16x8b); + + src_r0r1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r1_16x8b); + src_r2r3_16x8b = _mm_unpacklo_epi8(src_r2_16x8b, src_r3_16x8b); + src_r4r5_16x8b = _mm_unpacklo_epi8(src_r4_16x8b, src_r5_16x8b); + + res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); + res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); + res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); + + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); + res_t3_8x16b = _mm_add_epi16(res_t3_8x16b, const_val16_8x16b); + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t3_8x16b); + + res_t1_8x16b = _mm_srai_epi16(res_t1_8x16b, 5); //shifting right by 5 bits. + res_16x8b = _mm_packus_epi16(res_t1_8x16b, res_t1_8x16b); + + *((WORD32 *)(pu1_dst)) = _mm_cvtsi128_si32(res_16x8b); + res_16x8b = _mm_srli_si128(res_16x8b, 4); + *((WORD32 *)(pu1_dst + dst_strd)) = _mm_cvtsi128_si32(res_16x8b); + + src_r0_16x8b = src_r2_16x8b; + src_r1_16x8b = src_r3_16x8b; + src_r2_16x8b = src_r4_16x8b; + src_r3_16x8b = src_r5_16x8b; + src_r4_16x8b = src_r6_16x8b; + + ht -= 2; + pu1_src += src_strd << 1; + pu1_dst += dst_strd << 1; + } + while(ht > 0); + } + + else if(wd == 8) + { + //Epilogue: Load all the pred rows except sixth and seventh row + // for the first and second row processing. + src_r0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); + pu1_src += src_strd; + src_r1_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); + pu1_src += src_strd; + src_r2_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); + pu1_src += src_strd; + src_r3_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); + pu1_src += src_strd; + src_r4_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); + pu1_src += src_strd; + + src_r0_16x8b = _mm_unpacklo_epi64(src_r0_16x8b, src_r1_16x8b); + src_r1_16x8b = _mm_unpacklo_epi64(src_r1_16x8b, src_r2_16x8b); + src_r2_16x8b = _mm_unpacklo_epi64(src_r2_16x8b, src_r3_16x8b); + src_r3_16x8b = _mm_unpacklo_epi64(src_r3_16x8b, src_r4_16x8b); + + do + { + src_r5_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); + src_r6_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src + src_strd)); + + src_r4_16x8b = _mm_unpacklo_epi64(src_r4_16x8b, src_r5_16x8b); + src_r5_16x8b = _mm_unpacklo_epi64(src_r5_16x8b, src_r6_16x8b); + + src_r0r1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r1_16x8b); + src_r2r3_16x8b = _mm_unpacklo_epi8(src_r2_16x8b, src_r3_16x8b); + src_r4r5_16x8b = _mm_unpacklo_epi8(src_r4_16x8b, src_r5_16x8b); + + res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); + res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); + res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); + + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); + res_t3_8x16b = _mm_add_epi16(res_t3_8x16b, const_val16_8x16b); + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t3_8x16b); + + res_t1_8x16b = _mm_srai_epi16(res_t1_8x16b, 5); //shifting right by 5 bits. + res_16x8b = _mm_packus_epi16(res_t1_8x16b, res_t1_8x16b); + + _mm_storel_epi64((__m128i *)pu1_dst, res_16x8b); + + src_r0r1_16x8b = _mm_unpackhi_epi8(src_r0_16x8b, src_r1_16x8b); + src_r2r3_16x8b = _mm_unpackhi_epi8(src_r2_16x8b, src_r3_16x8b); + src_r4r5_16x8b = _mm_unpackhi_epi8(src_r4_16x8b, src_r5_16x8b); + + res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); + res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); + res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); + + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); + res_t3_8x16b = _mm_add_epi16(res_t3_8x16b, const_val16_8x16b); + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t3_8x16b); + + res_t1_8x16b = _mm_srai_epi16(res_t1_8x16b, 5); //shifting right by 5 bits. + res_16x8b = _mm_packus_epi16(res_t1_8x16b, res_t1_8x16b); + + _mm_storel_epi64((__m128i *)(pu1_dst + dst_strd), res_16x8b); + + src_r0_16x8b = src_r2_16x8b; + src_r1_16x8b = src_r3_16x8b; + src_r2_16x8b = src_r4_16x8b; + src_r3_16x8b = src_r5_16x8b; + src_r4_16x8b = src_r6_16x8b; + + ht -= 2; + pu1_src += src_strd << 1; + pu1_dst += dst_strd << 1; + } + while(ht > 0); + } + else // wd == 16 + { + __m128i res_t0_8x16b; + + //Epilogue: Load all the pred rows except sixth and seventh row + // for the first and second row processing. + src_r0_16x8b = _mm_loadu_si128((__m128i *)pu1_src); + pu1_src += src_strd; + src_r1_16x8b = _mm_loadu_si128((__m128i *)pu1_src); + pu1_src += src_strd; + src_r2_16x8b = _mm_loadu_si128((__m128i *)pu1_src); + pu1_src += src_strd; + src_r3_16x8b = _mm_loadu_si128((__m128i *)pu1_src); + pu1_src += src_strd; + src_r4_16x8b = _mm_loadu_si128((__m128i *)pu1_src); + pu1_src += src_strd; + + do + { + src_r5_16x8b = _mm_loadu_si128((__m128i *)pu1_src); + src_r6_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd)); + + src_r0r1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r1_16x8b); + src_r2r3_16x8b = _mm_unpacklo_epi8(src_r2_16x8b, src_r3_16x8b); + src_r4r5_16x8b = _mm_unpacklo_epi8(src_r4_16x8b, src_r5_16x8b); + + res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); + res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); + res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); + + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); + res_t3_8x16b = _mm_add_epi16(res_t3_8x16b, const_val16_8x16b); + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t3_8x16b); + res_t0_8x16b = _mm_srai_epi16(res_t1_8x16b, 5); //shifting right by 5 bits. + + src_r0r1_16x8b = _mm_unpackhi_epi8(src_r0_16x8b, src_r1_16x8b); + src_r2r3_16x8b = _mm_unpackhi_epi8(src_r2_16x8b, src_r3_16x8b); + src_r4r5_16x8b = _mm_unpackhi_epi8(src_r4_16x8b, src_r5_16x8b); + + res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); + res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); + res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); + + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); + res_t3_8x16b = _mm_add_epi16(res_t3_8x16b, const_val16_8x16b); + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t3_8x16b); + res_t1_8x16b = _mm_srai_epi16(res_t1_8x16b, 5); //shifting right by 5 bits. + + res_16x8b = _mm_packus_epi16(res_t0_8x16b, res_t1_8x16b); + + _mm_storeu_si128((__m128i *)pu1_dst, res_16x8b); + + src_r0r1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r2_16x8b); + src_r2r3_16x8b = _mm_unpacklo_epi8(src_r3_16x8b, src_r4_16x8b); + src_r4r5_16x8b = _mm_unpacklo_epi8(src_r5_16x8b, src_r6_16x8b); + + res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); + res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); + res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); + + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); + res_t3_8x16b = _mm_add_epi16(res_t3_8x16b, const_val16_8x16b); + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t3_8x16b); + res_t0_8x16b = _mm_srai_epi16(res_t1_8x16b, 5); //shifting right by 5 bits. + + src_r0r1_16x8b = _mm_unpackhi_epi8(src_r1_16x8b, src_r2_16x8b); + src_r2r3_16x8b = _mm_unpackhi_epi8(src_r3_16x8b, src_r4_16x8b); + src_r4r5_16x8b = _mm_unpackhi_epi8(src_r5_16x8b, src_r6_16x8b); + + res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); + res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); + res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); + + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); + res_t3_8x16b = _mm_add_epi16(res_t3_8x16b, const_val16_8x16b); + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t3_8x16b); + res_t1_8x16b = _mm_srai_epi16(res_t1_8x16b, 5); //shifting right by 5 bits. + + res_16x8b = _mm_packus_epi16(res_t0_8x16b, res_t1_8x16b); + + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), res_16x8b); + + src_r0_16x8b = src_r2_16x8b; + src_r1_16x8b = src_r3_16x8b; + src_r2_16x8b = src_r4_16x8b; + src_r3_16x8b = src_r5_16x8b; + src_r4_16x8b = src_r6_16x8b; + + ht -= 2; + pu1_src += src_strd << 1; + pu1_dst += dst_strd << 1; + } + while(ht > 0); + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_inter_pred_luma_horz_hpel_vert_hpel_ssse3 */ +/* */ +/* Description : This function implements a two stage cascaded six tap */ +/* filter, horizontally and then vertically on ht x wd */ +/* block as mentioned in sec. 8.4.2.2.1 titled "Luma sample */ +/* interpolation process". (ht,wd) can be (4,4), (8,4), */ +/* (4,8), (8,8), (16,8), (8,16) or (16,16). */ +/* */ +/* Inputs : puc_src - pointer to source */ +/* puc_dst - pointer to destination */ +/* src_strd - stride for source */ +/* dst_strd - stride for destination */ +/* ht - height of the block */ +/* wd - width of the block */ +/* pu1_tmp - pointer to temporary buffer */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes */ +/* 13 02 2015 Kaushik Initial Version */ +/* Senthoor */ +/* */ +/*****************************************************************************/ +void ih264_inter_pred_luma_horz_hpel_vert_hpel_ssse3(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ht, + WORD32 wd, + UWORD8* pu1_tmp, + WORD32 dydx) +{ + UNUSED(dydx); + + if(wd == 4) + { + WORD16 *pi2_temp; + + pu1_tmp += 4; + pu1_src -= src_strd << 1; + pi2_temp = (WORD16 *)pu1_tmp; + pu1_src -= 2; // the filter input starts from x[-2] (till x[3]) + + // Horizontal 6-tap filtering + { + WORD32 ht_tmp = ht + 4; + + __m128i src_r0_16x8b, src_r1_16x8b; + __m128i src_r0_sht_16x8b, src_r1_sht_16x8b; + __m128i src_r0r1_t1_16x8b; + __m128i res_r0r1_t1_8x16b, res_r0r1_t2_8x16b, res_r0r1_t3_8x16b; + __m128i coeff0_1_16x8b, coeff2_3_16x8b, coeff4_5_16x8b; + + coeff0_1_16x8b = _mm_set1_epi32(0xFB01FB01); //c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 + coeff2_3_16x8b = _mm_set1_epi32(0x14141414); //c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 + coeff4_5_16x8b = _mm_set1_epi32(0x01FB01FB); //c4 c5 c5 c5 c4 c5 c5 c5 c4 c5 c5 c5 c4 c5 c5 c5 + //c0 = c5 = 1, c1 = c4 = -5, c2 = c3 = 20 + //Row0 : a0 a1 a2 a3 a4 a5 a6 a7 a8 a9..... + //Row1 : b0 b1 b2 b3 b4 b5 b6 b7 b8 b9..... + + do + { + src_r0_16x8b = _mm_loadu_si128((__m128i *)pu1_src); //a0 a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 + src_r1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd)); //b0 b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 + + src_r0_sht_16x8b = _mm_srli_si128(src_r0_16x8b, 1); //a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 0 + src_r1_sht_16x8b = _mm_srli_si128(src_r1_16x8b, 1); //b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 0 + + src_r0_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a0 a1 a1 a2 a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 + src_r1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b0 b1 b1 b2 b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 + + src_r0r1_t1_16x8b = _mm_unpacklo_epi64(src_r0_16x8b, src_r1_16x8b); //a0 a1 a1 a2 a2 a3 a3 a4 b0 b1 b1 b2 b2 b3 b3 b4 + res_r0r1_t1_8x16b = _mm_maddubs_epi16(src_r0r1_t1_16x8b, coeff0_1_16x8b); //a0*c0+a1*c1 a1*c0+a2*c1 a2*c0+a3*c1 a3*c0+a4*c1 + //b0*c0+b1*c1 b1*c0+b2*c1 b2*c0+b3*c1 b3*c0+b4*c1 + + src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 4); //a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 0 0 0 0 + src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 4); //b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 0 0 0 0 + + src_r0r1_t1_16x8b = _mm_unpacklo_epi64(src_r0_16x8b, src_r1_16x8b); //a2 a3 a3 a4 a4 a5 a5 a6 b2 b3 b3 b4 b4 b5 b5 b6 + res_r0r1_t2_8x16b = _mm_maddubs_epi16(src_r0r1_t1_16x8b, coeff2_3_16x8b); //a2*c2+a3*c3 a3*c2+a4*c3 a4*c2+a5*c3 a5*c2+a6*c3 + //b2*c2+b3*c3 b3*c2+b4*c3 b4*c2+b5*c3 b5*c2+b6*c3 + + src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 4); //a4 a5 a5 a6 a6 a7 a7 a8 0 0 0 0 0 0 0 0 + src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 4); //b4 b5 b5 b6 b6 b7 b7 b8 0 0 0 0 0 0 0 0 + + src_r0r1_t1_16x8b = _mm_unpacklo_epi64(src_r0_16x8b, src_r1_16x8b); //a4 a5 a5 a6 a6 a7 a7 a8 b4 b5 b5 b6 b6 b7 b7 b8 + res_r0r1_t3_8x16b = _mm_maddubs_epi16(src_r0r1_t1_16x8b, coeff4_5_16x8b); //a4*c4+a5*c5 a5*c4+a6*c5 a6*c4+a7*c5 a7*c4+a8*c5 + //b4*c4+b5*c5 b5*c4+b6*c5 b4*c6+b7*c5 b7*c4+b8*c5 + res_r0r1_t1_8x16b = _mm_add_epi16(res_r0r1_t1_8x16b, res_r0r1_t2_8x16b); + res_r0r1_t1_8x16b = _mm_add_epi16(res_r0r1_t3_8x16b, res_r0r1_t1_8x16b); + + _mm_storeu_si128((__m128i *)pi2_temp, res_r0r1_t1_8x16b); + + ht_tmp -= 2; + pu1_src += src_strd << 1; + pi2_temp += 8; + } + while(ht_tmp > 0); + + src_r0_16x8b = _mm_loadu_si128((__m128i *)pu1_src); //a0 a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 + src_r0_sht_16x8b = _mm_srli_si128(src_r0_16x8b, 1); //a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 0 + + src_r0_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a0 a1 a1 a2 a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 + res_r0r1_t1_8x16b = _mm_maddubs_epi16(src_r0_16x8b, coeff0_1_16x8b); //a0*c0+a1*c1 a1*c0+a2*c1 a2*c0+a3*c1 a3*c0+a4*c1 + + src_r0_16x8b = _mm_srli_si128(src_r0_16x8b,4); //a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 0 0 0 0 + res_r0r1_t2_8x16b = _mm_maddubs_epi16(src_r0_16x8b, coeff2_3_16x8b); //a2*c2+a3*c3 a3*c2+a4*c3 a4*c2+a5*c3 a5*c2+a6*c3 + + src_r0_16x8b = _mm_srli_si128(src_r0_16x8b,4); //a4 a5 a5 a6 a6 a7 a7 a8 0 0 0 0 0 0 0 0 + res_r0r1_t3_8x16b = _mm_maddubs_epi16(src_r0_16x8b, coeff4_5_16x8b); //a4*c4+a5*c5 a5*c4+a6*c5 a6*c4+a7*c5 a7*c4+a8*c5 + + res_r0r1_t1_8x16b = _mm_add_epi16(res_r0r1_t1_8x16b, res_r0r1_t2_8x16b); + res_r0r1_t1_8x16b = _mm_add_epi16(res_r0r1_t3_8x16b, res_r0r1_t1_8x16b); + + _mm_storel_epi64((__m128i *)pi2_temp, res_r0r1_t1_8x16b); + } + + pi2_temp = (WORD16 *)pu1_tmp; + + // Vertical 6-tap filtering + { + __m128i src_r0_8x16b, src_r1_8x16b, src_r2_8x16b, src_r3_8x16b, + src_r4_8x16b; + __m128i src_r5_8x16b, src_r6_8x16b; + __m128i src_t1_8x16b, src_t2_8x16b; + + __m128i res_t0_4x32b, res_t1_4x32b, res_t2_4x32b, res_t3_4x32b; + __m128i res_8x16b, res_16x8b; + + __m128i coeff0_1_8x16b, coeff2_3_8x16b, coeff4_5_8x16b; + __m128i const_val512_4x32b; + + coeff0_1_8x16b = _mm_set1_epi32(0xFFFB0001); + coeff2_3_8x16b = _mm_set1_epi32(0x00140014); + coeff4_5_8x16b = _mm_set1_epi32(0x0001FFFB); + + const_val512_4x32b = _mm_set1_epi32(512); + + src_r0_8x16b = _mm_loadl_epi64((__m128i *)(pi2_temp)); + src_r1_8x16b = _mm_loadl_epi64((__m128i *)(pi2_temp + 4)); + src_r2_8x16b = _mm_loadl_epi64((__m128i *)(pi2_temp + 8)); + src_r3_8x16b = _mm_loadl_epi64((__m128i *)(pi2_temp + 12)); + src_r4_8x16b = _mm_loadl_epi64((__m128i *)(pi2_temp + 16)); + pi2_temp += 20; + + do + { + src_r5_8x16b = _mm_loadl_epi64((__m128i *)pi2_temp); + src_r6_8x16b = _mm_loadl_epi64((__m128i *)(pi2_temp + 4)); + + src_r0_8x16b = _mm_unpacklo_epi16(src_r0_8x16b, src_r1_8x16b); + src_t1_8x16b = _mm_unpacklo_epi16(src_r2_8x16b, src_r3_8x16b); + src_t2_8x16b = _mm_unpacklo_epi16(src_r4_8x16b, src_r5_8x16b); + + res_t1_4x32b = _mm_madd_epi16(src_r0_8x16b, coeff0_1_8x16b); + res_t2_4x32b = _mm_madd_epi16(src_t1_8x16b, coeff2_3_8x16b); + res_t3_4x32b = _mm_madd_epi16(src_t2_8x16b, coeff4_5_8x16b); + + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); + res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); + + res_t0_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); + + src_r1_8x16b = _mm_unpacklo_epi16(src_r1_8x16b, src_r2_8x16b); + src_t1_8x16b = _mm_unpacklo_epi16(src_r3_8x16b, src_r4_8x16b); + src_t2_8x16b = _mm_unpacklo_epi16(src_r5_8x16b, src_r6_8x16b); + + res_t1_4x32b = _mm_madd_epi16(src_r1_8x16b, coeff0_1_8x16b); + res_t2_4x32b = _mm_madd_epi16(src_t1_8x16b, coeff2_3_8x16b); + res_t3_4x32b = _mm_madd_epi16(src_t2_8x16b, coeff4_5_8x16b); + + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); + res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); + + res_t1_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); + + res_8x16b = _mm_packs_epi32(res_t0_4x32b, res_t1_4x32b); + res_16x8b = _mm_packus_epi16(res_8x16b, res_8x16b); + + *((WORD32 *)(pu1_dst)) = _mm_cvtsi128_si32(res_16x8b); + res_16x8b = _mm_srli_si128(res_16x8b, 4); + *((WORD32 *)(pu1_dst + dst_strd)) = _mm_cvtsi128_si32(res_16x8b); + + src_r0_8x16b = src_r2_8x16b; + src_r1_8x16b = src_r3_8x16b; + src_r2_8x16b = src_r4_8x16b; + src_r3_8x16b = src_r5_8x16b; + src_r4_8x16b = src_r6_8x16b; + + ht -= 2; + pi2_temp += 8; + pu1_dst += dst_strd << 1; + } + while(ht > 0); + } + } + else if(wd == 8) + { + WORD16 *pi2_temp; + + pu1_tmp += 4; + pu1_src -= src_strd << 1; + pi2_temp = (WORD16 *)pu1_tmp; + pu1_src -= 2; // the filter input starts from x[-2] (till x[3]) + + // Horizontal 6-tap filtering + { + WORD32 ht_tmp = ht + 4; + + __m128i src_r0_16x8b, src_r1_16x8b; + __m128i src_r0_sht_16x8b, src_r1_sht_16x8b; + __m128i src_r0_t1_16x8b, src_r1_t1_16x8b; + __m128i res_r0_t1_8x16b, res_r0_t2_8x16b, res_r0_t3_8x16b; + __m128i res_r1_t1_8x16b, res_r1_t2_8x16b, res_r1_t3_8x16b; + __m128i coeff0_1_16x8b, coeff2_3_16x8b, coeff4_5_16x8b; + + coeff0_1_16x8b = _mm_set1_epi32(0xFB01FB01); //c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 + coeff2_3_16x8b = _mm_set1_epi32(0x14141414); //c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 + coeff4_5_16x8b = _mm_set1_epi32(0x01FB01FB); //c4 c5 c5 c5 c4 c5 c5 c5 c4 c5 c5 c5 c4 c5 c5 c5 + //c0 = c5 = 1, c1 = c4 = -5, c2 = c3 = 20 + //Row0 : a0 a1 a2 a3 a4 a5 a6 a7 a8 a9..... + //Row1 : b0 b1 b2 b3 b4 b5 b6 b7 b8 b9..... + + do + { + src_r0_16x8b = _mm_loadu_si128((__m128i *)pu1_src); //a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15 + src_r1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd)); //b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 b10 b11 b12 b13 b14 b15 + + src_r0_sht_16x8b = _mm_srli_si128(src_r0_16x8b, 1); //a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 0 + src_r1_sht_16x8b = _mm_srli_si128(src_r1_16x8b, 1); //b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 0 + + src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a0 a1 a1 a2 a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 + src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b0 b1 b1 b2 b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 + + res_r0_t1_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff0_1_16x8b); //a0*c0+a1*c1 a1*c0+a2*c1 a2*c0+a3*c1 a3*c0+a4*c1 + //a4*c0+a5*c1 a5*c0+a6*c1 a6*c0+a7*c1 a7*c0+a8*c1 + res_r1_t1_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff0_1_16x8b); //b0*c0+b1*c1 b1*c0+b2*c1 b2*c0+b3*c1 b3*c0+b4*c1 + //b4*c0+b5*c1 b5*c0+b6*c1 b6*c0+b7*c1 b7*c0+b8*c1 + + src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 2); //a2 a3 a4 a5 a6 a7 a8 a9....a15 0 0 + src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 2); //b2 b3 b4 b5 b6 b7 b8 b9....b15 0 0 + + src_r0_sht_16x8b = _mm_srli_si128(src_r0_sht_16x8b, 2); //a3 a4 a5 a6 a7 a8 a9....a15 0 0 0 + src_r1_sht_16x8b = _mm_srli_si128(src_r1_sht_16x8b, 2); //b3 b4 b5 b6 b7 b8 b9....b15 0 0 0 + + src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 a8 a9 a9 a10 + src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 a8 a9 a9 a10 + + res_r0_t2_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff2_3_16x8b); //a2*c2+a3*c3 a3*c2+a4*c3 a4*c2+a5*c3 a5*c2+a6*c3 + //a6*c2+a7*c3 a7*c2+a8*c3 a8*c2+a9*c3 a9*c2+a10*c3 + res_r1_t2_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff2_3_16x8b); //b2*c2+b3*c3 b3*c2+b4*c3 b2*c4+b5*c3 b5*c2+b6*c3 + //b6*c2+b7*c3 b7*c2+b8*c3 b8*c2+b9*c3 b9*c2+b10*c3 + + src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 2); //a4 a5 a6 a7 a8 a9....a15 0 0 0 0 + src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 2); //b4 b5 b6 b7 b8 b9....b15 0 0 0 0 + + src_r0_sht_16x8b = _mm_srli_si128(src_r0_sht_16x8b, 2); //a5 a6 a7 a8 a9....a15 0 0 0 0 0 + src_r1_sht_16x8b = _mm_srli_si128(src_r1_sht_16x8b, 2); //b5 b6 b7 b8 b9....b15 0 0 0 0 0 + + src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a4 a5 a5 a6 a6 a7 a7 a8 a8 a9 a9 a10 a10 a11 a11 a12 + src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b4 b5 b5 b6 b6 b7 b7 b8 b8 b9 b9 b10 b10 b11 b11 b12 + + res_r0_t3_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff4_5_16x8b); //a4*c4+a5*c5 a5*c4+a6*c5 a6*c4+a7*c5 a7*c4+a8*c5 + //a8*c4+a9*c5 a9*c4+a10*c5 a10*c4+a11*c5 a11*c4+a12*c5 + res_r1_t3_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff4_5_16x8b); //b4*c4+b5*c5 b5*c4+b6*c5 b6*c4+b7*c5 b7*c4+b8*c5 + //b8*c4+b9*c5 b9*c4+b10*c5 b10*c4+b11*c5 b11*c4+b12*c5 + res_r0_t1_8x16b = _mm_add_epi16(res_r0_t1_8x16b, res_r0_t2_8x16b); + res_r0_t1_8x16b = _mm_add_epi16(res_r0_t1_8x16b, res_r0_t3_8x16b); + + res_r1_t1_8x16b = _mm_add_epi16(res_r1_t1_8x16b, res_r1_t2_8x16b); + res_r1_t1_8x16b = _mm_add_epi16(res_r1_t1_8x16b, res_r1_t3_8x16b); + + _mm_storeu_si128((__m128i *)pi2_temp, res_r0_t1_8x16b); + _mm_storeu_si128((__m128i *)(pi2_temp + 8), res_r1_t1_8x16b); + + ht_tmp -= 2; + pu1_src += src_strd << 1; + pi2_temp += 16; + } + while(ht_tmp > 0); + + src_r0_16x8b = _mm_loadu_si128((__m128i *)pu1_src); //a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15 + src_r0_sht_16x8b = _mm_srli_si128(src_r0_16x8b, 1); //a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 0 + + src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b,src_r0_sht_16x8b); //a0 a1 a1 a2 a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 + res_r0_t1_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b,coeff0_1_16x8b); //a0*c0+a1*c1 a1*c0+a2*c1 a2*c0+a3*c1 a3*c0+a4*c1 + //a4*c0+a5*c1 a5*c0+a6*c1 a6*c0+a7*c1 a7*c0+a8*c1 + + src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 2); //a2 a3 a4 a5 a6 a7 a8 a9....a15 0 0 + src_r0_sht_16x8b = _mm_srli_si128(src_r0_sht_16x8b, 2); //a3 a4 a5 a6 a7 a8 a9....a15 0 0 0 + + src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 a8 a9 a9 a10 + res_r0_t2_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff2_3_16x8b); //a2*c2+a3*c3 a3*c2+a4*c3 a4*c2+a5*c3 a5*c2+a6*c3 + //a6*c2+a7*c3 a7*c2+a8*c3 a8*c2+a9*c3 a9*c2+a10*c3 + + src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 2); //a4 a5 a6 a7 a8 a9....a15 0 0 0 0 + src_r0_sht_16x8b = _mm_srli_si128(src_r0_sht_16x8b, 2); //a5 a6 a7 a8 a9....a15 0 0 0 0 0 + + src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a4 a5 a5 a6 a6 a7 a7 a8 a8 a9 a9 a10 a10 a11 a11 a12 + res_r0_t3_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff4_5_16x8b); //a4*c4+a5*c5 a5*c4+a6*c5 a6*c4+a7*c5 a7*c4+a8*c5 + //a8*c4+a9*c5 a9*c4+a10*c5 a10*c4+a11*c5 a11*c4+a12*c5 + res_r0_t1_8x16b = _mm_add_epi16(res_r0_t1_8x16b, res_r0_t2_8x16b); + res_r0_t1_8x16b = _mm_add_epi16(res_r0_t1_8x16b, res_r0_t3_8x16b); + + _mm_storeu_si128((__m128i *)pi2_temp, res_r0_t1_8x16b); + } + + pi2_temp = (WORD16 *)pu1_tmp; + + // Vertical 6-tap filtering + { + __m128i src_r0_8x16b, src_r1_8x16b, src_r2_8x16b, src_r3_8x16b, + src_r4_8x16b; + __m128i src_r5_8x16b, src_r6_8x16b; + __m128i src_r0r1_8x16b, src_r2r3_8x16b, src_r4r5_8x16b; + + __m128i res_t1_4x32b, res_t2_4x32b, res_t3_4x32b; + __m128i res_c0_4x32b, res_c1_4x32b; + __m128i res_8x16b, res_16x8b; + + __m128i coeff0_1_8x16b, coeff2_3_8x16b, coeff4_5_8x16b; + __m128i const_val512_4x32b; + + coeff0_1_8x16b = _mm_set1_epi32(0xFFFB0001); + coeff2_3_8x16b = _mm_set1_epi32(0x00140014); + coeff4_5_8x16b = _mm_set1_epi32(0x0001FFFB); + + const_val512_4x32b = _mm_set1_epi32(512); + + src_r0_8x16b = _mm_loadu_si128((__m128i *)pi2_temp); + src_r1_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp + 8)); + src_r2_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp + 16)); + src_r3_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp + 24)); + src_r4_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp + 32)); + pi2_temp += 40; + + do + { + src_r5_8x16b = _mm_loadu_si128((__m128i *)pi2_temp); + src_r6_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp + 8)); + + src_r0r1_8x16b = _mm_unpacklo_epi16(src_r0_8x16b, src_r1_8x16b); + src_r2r3_8x16b = _mm_unpacklo_epi16(src_r2_8x16b, src_r3_8x16b); + src_r4r5_8x16b = _mm_unpacklo_epi16(src_r4_8x16b, src_r5_8x16b); + + res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); + res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); + res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); + + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); + res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); + res_c0_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); + + src_r0r1_8x16b = _mm_unpackhi_epi16(src_r0_8x16b, src_r1_8x16b); + src_r2r3_8x16b = _mm_unpackhi_epi16(src_r2_8x16b, src_r3_8x16b); + src_r4r5_8x16b = _mm_unpackhi_epi16(src_r4_8x16b, src_r5_8x16b); + + res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); + res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); + res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); + + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); + res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); + res_c1_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); + + res_8x16b = _mm_packs_epi32(res_c0_4x32b, res_c1_4x32b); + res_16x8b = _mm_packus_epi16(res_8x16b, res_8x16b); + + _mm_storel_epi64((__m128i *)pu1_dst, res_16x8b); + + src_r0r1_8x16b = _mm_unpacklo_epi16(src_r1_8x16b, src_r2_8x16b); + src_r2r3_8x16b = _mm_unpacklo_epi16(src_r3_8x16b, src_r4_8x16b); + src_r4r5_8x16b = _mm_unpacklo_epi16(src_r5_8x16b, src_r6_8x16b); + + res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); + res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); + res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); + + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); + res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); + res_c0_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); + + src_r0r1_8x16b = _mm_unpackhi_epi16(src_r1_8x16b, src_r2_8x16b); + src_r2r3_8x16b = _mm_unpackhi_epi16(src_r3_8x16b, src_r4_8x16b); + src_r4r5_8x16b = _mm_unpackhi_epi16(src_r5_8x16b, src_r6_8x16b); + + res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); + res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); + res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); + + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); + res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); + res_c1_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); + + res_8x16b = _mm_packs_epi32(res_c0_4x32b, res_c1_4x32b); + res_16x8b = _mm_packus_epi16(res_8x16b, res_8x16b); + + _mm_storel_epi64((__m128i *)(pu1_dst + dst_strd), res_16x8b); + + src_r0_8x16b = src_r2_8x16b; + src_r1_8x16b = src_r3_8x16b; + src_r2_8x16b = src_r4_8x16b; + src_r3_8x16b = src_r5_8x16b; + src_r4_8x16b = src_r6_8x16b; + + ht -= 2; + pi2_temp += 16; + pu1_dst += dst_strd << 1; + } + while(ht > 0); + } + } + else // wd == 16 + { + WORD16 *pi2_temp; + WORD32 ht_tmp; + + pu1_tmp += 4; + pu1_src -= src_strd << 1; + pi2_temp = (WORD16 *)pu1_tmp; + pu1_src -= 2; // the filter input starts from x[-2] (till x[3]) + + // Horizontal 6-tap filtering + { + __m128i src_r0_16x8b, src_r1_16x8b, src_r0_sht_16x8b, src_r1_sht_16x8b; + __m128i src_r0_t1_16x8b, src_r1_t1_16x8b; + + __m128i res_r0_t1_8x16b, res_r0_t2_8x16b, res_r0_t3_8x16b; + __m128i res_r1_t1_8x16b, res_r1_t2_8x16b, res_r1_t3_8x16b; + + __m128i coeff0_1_16x8b, coeff2_3_16x8b, coeff4_5_16x8b; + + ht_tmp = ht + 5; + + coeff0_1_16x8b = _mm_set1_epi32(0xFB01FB01); //c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 + coeff2_3_16x8b = _mm_set1_epi32(0x14141414); //c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 + coeff4_5_16x8b = _mm_set1_epi32(0x01FB01FB); //c4 c5 c5 c5 c4 c5 c5 c5 c4 c5 c5 c5 c4 c5 c5 c5 + //c0 = c5 = 1, c1 = c4 = -5, c2 = c3 = 20 + //Row0 : a0 a1 a2 a3 a4 a5 a6 a7 a8 a9..... + //Row0 : b0 b1 b2 b3 b4 b5 b6 b7 b8 b9..... + //b0 is same a8. Similarly other bn pixels are same as a(n+8) pixels. + + do + { + src_r0_16x8b = _mm_loadu_si128((__m128i *)pu1_src); //a0 a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 + src_r1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + 8)); //b0 b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 + + src_r0_sht_16x8b = _mm_srli_si128(src_r0_16x8b, 1); //a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 0 + src_r1_sht_16x8b = _mm_srli_si128(src_r1_16x8b, 1); //b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 0 + + src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a0 a1 a1 a2 a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 + src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b0 b1 b1 b2 b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 + + res_r0_t1_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff0_1_16x8b); //a0*c0+a1*c1 a1*c0+a2*c1 a2*c0+a3*c1 a3*c0+a4*c1 + //a4*c0+a5*c1 a5*c0+a6*c1 a6*c0+a7*c1 a7*c0+a8*c1 + res_r1_t1_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff0_1_16x8b); //b0*c0+b1*c1 b1*c0+b2*c1 b2*c0+b3*c1 b3*c0+b4*c1 + //b4*c0+b5*c1 b5*c0+b6*c1 b6*c0+b7*c1 b7*c0+b8*c1 + + src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 2); //a2 a3 a4 a5 a6 a7 a8 a9....a15 0 0 + src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 2); //b2 b3 b4 b5 b6 b7 b8 b9....b15 0 0 + + src_r0_sht_16x8b = _mm_srli_si128(src_r0_sht_16x8b, 2); //a3 a4 a5 a6 a7 a8 a9....a15 0 0 0 + src_r1_sht_16x8b = _mm_srli_si128(src_r1_sht_16x8b, 2); //b3 b4 b5 b6 b7 b8 b9....b15 0 0 0 + + src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 a8 a9 a9 a10 + src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 a8 a9 a9 a10 + + res_r0_t2_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff2_3_16x8b); //a2*c2+a3*c3 a3*c2+a4*c3 a4*c2+a5*c3 a5*c2+a6*c3 + //a6*c2+a7*c3 a7*c2+a8*c3 a8*c2+a9*c3 a9*c2+a10*c3 + res_r1_t2_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff2_3_16x8b); //b2*c2+b3*c3 b3*c2+b4*c3 b2*c4+b5*c3 b5*c2+b6*c3 + //b6*c2+b7*c3 b7*c2+b8*c3 b8*c2+b9*c3 b9*c2+b10*c3 + + src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 2); //a4 a5 a6 a7 a8 a9....a15 0 0 0 0 + src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 2); //b4 b5 b6 b7 b8 b9....b15 0 0 0 0 + + src_r0_sht_16x8b = _mm_srli_si128(src_r0_sht_16x8b, 2); //a5 a6 a7 a8 a9....a15 0 0 0 0 0 + src_r1_sht_16x8b = _mm_srli_si128(src_r1_sht_16x8b, 2); //b5 b6 b7 b8 b9....b15 0 0 0 0 0 + + src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a4 a5 a5 a6 a6 a7 a7 a8 a8 a9 a9 a10 a10 a11 a11 a12 + src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b4 b5 b5 b6 b6 b7 b7 b8 b8 b9 b9 b10 b10 b11 b11 b12 + + res_r0_t3_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff4_5_16x8b); //a4*c4+a5*c5 a5*c4+a6*c5 a6*c4+a7*c5 a7*c4+a8*c5 + //a8*c4+a9*c5 a9*c4+a10*c5 a10*c4+a11*c5 a11*c4+a12*c5 + res_r1_t3_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff4_5_16x8b); //b4*c4+b5*c5 b5*c4+b6*c5 b6*c4+b7*c5 b7*c4+b8*c5 + //b8*c4+b9*c5 b9*c4+b10*c5 b10*c4+b11*c5 b11*c4+b12*c5 + res_r0_t1_8x16b = _mm_add_epi16(res_r0_t1_8x16b, res_r0_t2_8x16b); + res_r0_t1_8x16b = _mm_add_epi16(res_r0_t1_8x16b, res_r0_t3_8x16b); + + res_r1_t1_8x16b = _mm_add_epi16(res_r1_t1_8x16b, res_r1_t2_8x16b); + res_r1_t1_8x16b = _mm_add_epi16(res_r1_t1_8x16b, res_r1_t3_8x16b); + + _mm_storeu_si128((__m128i *)pi2_temp, res_r0_t1_8x16b); + _mm_storeu_si128((__m128i *)(pi2_temp + 8), res_r1_t1_8x16b); + + ht_tmp--; + pu1_src += src_strd; + pi2_temp += 16; + } + while(ht_tmp > 0); + } + + pi2_temp = (WORD16 *)pu1_tmp; + + // Vertical 6-tap filtering + { + WORD16 *pi2_temp2; + UWORD8 *pu1_dst2; + WORD32 ht_tmp; + + __m128i src_r0_8x16b, src_r1_8x16b, src_r2_8x16b, src_r3_8x16b, src_r4_8x16b; + __m128i src_r5_8x16b, src_r6_8x16b; + __m128i src_r0r1_8x16b, src_r2r3_8x16b, src_r4r5_8x16b; + + __m128i res_t1_4x32b, res_t2_4x32b, res_t3_4x32b; + __m128i res_c0_4x32b, res_c1_4x32b; + __m128i res_8x16b, res_16x8b; + + __m128i coeff0_1_8x16b, coeff2_3_8x16b, coeff4_5_8x16b; + __m128i const_val512_4x32b; + + coeff0_1_8x16b = _mm_set1_epi32(0xFFFB0001); + coeff2_3_8x16b = _mm_set1_epi32(0x00140014); + coeff4_5_8x16b = _mm_set1_epi32(0x0001FFFB); + + const_val512_4x32b = _mm_set1_epi32(512); + + pi2_temp2 = pi2_temp + 8; + pu1_dst2 = pu1_dst + 8; + ht_tmp = ht; + + /**********************************************************/ + /* Do first height x 8 block */ + /**********************************************************/ + src_r0_8x16b = _mm_loadu_si128((__m128i *)pi2_temp); + src_r1_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp + 16)); + src_r2_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp + 32)); + src_r3_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp + 48)); + src_r4_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp + 64)); + pi2_temp += 80; + + do + { + src_r5_8x16b = _mm_loadu_si128((__m128i *)pi2_temp); + src_r6_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp + 16)); + + src_r0r1_8x16b = _mm_unpacklo_epi16(src_r0_8x16b, src_r1_8x16b); + src_r2r3_8x16b = _mm_unpacklo_epi16(src_r2_8x16b, src_r3_8x16b); + src_r4r5_8x16b = _mm_unpacklo_epi16(src_r4_8x16b, src_r5_8x16b); + + res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); + res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); + res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); + + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); + res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); + res_c0_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); + + src_r0r1_8x16b = _mm_unpackhi_epi16(src_r0_8x16b, src_r1_8x16b); + src_r2r3_8x16b = _mm_unpackhi_epi16(src_r2_8x16b, src_r3_8x16b); + src_r4r5_8x16b = _mm_unpackhi_epi16(src_r4_8x16b, src_r5_8x16b); + + res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); + res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); + res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); + + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); + res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); + res_c1_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); + + res_8x16b = _mm_packs_epi32(res_c0_4x32b, res_c1_4x32b); + res_16x8b = _mm_packus_epi16(res_8x16b, res_8x16b); + + _mm_storel_epi64((__m128i *)pu1_dst, res_16x8b); + + src_r0r1_8x16b = _mm_unpacklo_epi16(src_r1_8x16b, src_r2_8x16b); + src_r2r3_8x16b = _mm_unpacklo_epi16(src_r3_8x16b, src_r4_8x16b); + src_r4r5_8x16b = _mm_unpacklo_epi16(src_r5_8x16b, src_r6_8x16b); + + res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); + res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); + res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); + + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); + res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); + res_c0_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); + + src_r0r1_8x16b = _mm_unpackhi_epi16(src_r1_8x16b, src_r2_8x16b); + src_r2r3_8x16b = _mm_unpackhi_epi16(src_r3_8x16b, src_r4_8x16b); + src_r4r5_8x16b = _mm_unpackhi_epi16(src_r5_8x16b, src_r6_8x16b); + + res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); + res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); + res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); + + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); + res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); + res_c1_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); + + res_8x16b = _mm_packs_epi32(res_c0_4x32b, res_c1_4x32b); + res_16x8b = _mm_packus_epi16(res_8x16b, res_8x16b); + + _mm_storel_epi64((__m128i *)(pu1_dst + dst_strd), res_16x8b); + + src_r0_8x16b = src_r2_8x16b; + src_r1_8x16b = src_r3_8x16b; + src_r2_8x16b = src_r4_8x16b; + src_r3_8x16b = src_r5_8x16b; + src_r4_8x16b = src_r6_8x16b; + + ht_tmp -= 2; + pi2_temp += 32; + pu1_dst += dst_strd << 1; + } + while(ht_tmp > 0); + + /**********************************************************/ + /* Do second ht x 8 block */ + /**********************************************************/ + src_r0_8x16b = _mm_loadu_si128((__m128i *)pi2_temp2); + src_r1_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 16)); + src_r2_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 32)); + src_r3_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 48)); + src_r4_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 64)); + pi2_temp2 += 80; + + do + { + src_r5_8x16b = _mm_loadu_si128((__m128i *)pi2_temp2); + src_r6_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 16)); + + src_r0r1_8x16b = _mm_unpacklo_epi16(src_r0_8x16b, src_r1_8x16b); + src_r2r3_8x16b = _mm_unpacklo_epi16(src_r2_8x16b, src_r3_8x16b); + src_r4r5_8x16b = _mm_unpacklo_epi16(src_r4_8x16b, src_r5_8x16b); + + res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); + res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); + res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); + + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); + res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); + res_c0_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); + + src_r0r1_8x16b = _mm_unpackhi_epi16(src_r0_8x16b, src_r1_8x16b); + src_r2r3_8x16b = _mm_unpackhi_epi16(src_r2_8x16b, src_r3_8x16b); + src_r4r5_8x16b = _mm_unpackhi_epi16(src_r4_8x16b, src_r5_8x16b); + + res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); + res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); + res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); + + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); + res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); + res_c1_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); + + res_8x16b = _mm_packs_epi32(res_c0_4x32b, res_c1_4x32b); + res_16x8b = _mm_packus_epi16(res_8x16b, res_8x16b); + + _mm_storel_epi64((__m128i *)pu1_dst2, res_16x8b); + + src_r0r1_8x16b = _mm_unpacklo_epi16(src_r1_8x16b, src_r2_8x16b); + src_r2r3_8x16b = _mm_unpacklo_epi16(src_r3_8x16b, src_r4_8x16b); + src_r4r5_8x16b = _mm_unpacklo_epi16(src_r5_8x16b, src_r6_8x16b); + + res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); + res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); + res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); + + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); + res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); + res_c0_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); + + src_r0r1_8x16b = _mm_unpackhi_epi16(src_r1_8x16b, src_r2_8x16b); + src_r2r3_8x16b = _mm_unpackhi_epi16(src_r3_8x16b, src_r4_8x16b); + src_r4r5_8x16b = _mm_unpackhi_epi16(src_r5_8x16b, src_r6_8x16b); + + res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); + res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); + res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); + + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); + res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); + res_c1_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); + + res_8x16b = _mm_packs_epi32(res_c0_4x32b, res_c1_4x32b); + res_16x8b = _mm_packus_epi16(res_8x16b, res_8x16b); + + _mm_storel_epi64((__m128i *)(pu1_dst2 + dst_strd), res_16x8b); + + src_r0_8x16b = src_r2_8x16b; + src_r1_8x16b = src_r3_8x16b; + src_r2_8x16b = src_r4_8x16b; + src_r3_8x16b = src_r5_8x16b; + src_r4_8x16b = src_r6_8x16b; + + ht -= 2; + pi2_temp2 += 32; + pu1_dst2 += dst_strd << 1; + } + while(ht > 0); + } + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_inter_pred_luma_horz_qpel_ssse3 */ +/* */ +/* Description : This function implements a six-tap filter horizontally */ +/* on ht x wd block and averages the values with the source */ +/* pixels to calculate horizontal quarter-pel as mentioned */ +/* in sec. 8.4.2.2.1 titled "Luma sample interpolation */ +/* process". (ht,wd) can be (4,4), (8,4), (4,8), (8,8), */ +/* (16,8), (8,16) or (16,16). */ +/* */ +/* Inputs : puc_src - pointer to source */ +/* puc_dst - pointer to destination */ +/* src_strd - stride for source */ +/* dst_strd - stride for destination */ +/* ht - height of the block */ +/* wd - width of the block */ +/* pu1_tmp - pointer to temporary buffer */ +/* dydx - x and y reference offset for q-pel */ +/* calculations */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes */ +/* 13 02 2015 Kaushik Initial Version */ +/* Senthoor */ +/* */ +/*****************************************************************************/ +void ih264_inter_pred_luma_horz_qpel_ssse3(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ht, + WORD32 wd, + UWORD8* pu1_tmp, + WORD32 dydx) +{ + WORD32 x_offset; + UWORD8 *pu1_pred1; + + __m128i src_r0_16x8b, src_r1_16x8b; + __m128i src_r0_sht_16x8b, src_r1_sht_16x8b; + __m128i coeff0_1_16x8b, coeff2_3_16x8b, coeff4_5_16x8b; + __m128i const_val16_8x16b; + + UNUSED(pu1_tmp); + + x_offset = dydx & 3; + + coeff0_1_16x8b = _mm_set1_epi32(0xFB01FB01); //c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 + coeff2_3_16x8b = _mm_set1_epi32(0x14141414); //c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 + coeff4_5_16x8b = _mm_set1_epi32(0x01FB01FB); //c4 c5 c4 c5 c4 c5 c4 c5 c4 c5 c4 c5 c4 c5 c4 c5 + //c0 = c5 = 1, c1 = c4 = -5, c2 = c3 = 20 + pu1_pred1 = pu1_src + (x_offset >> 1); + + const_val16_8x16b = _mm_set1_epi16(16); + + pu1_src -= 2; // the filter input starts from x[-2] (till x[3]) + + if(wd == 4) + { + __m128i src_r0r1_16x8b; + + __m128i res_r0r1_t1_8x16b, res_r0r1_t2_8x16b, res_r0r1_t3_8x16b; + __m128i res_r0r1_16x8b; + + //Row0 : a0 a1 a2 a3 a4 a5 a6 a7 a8 a9..... + //Row1 : b0 b1 b2 b3 b4 b5 b6 b7 b8 b9..... + + do + { + src_r0_16x8b = _mm_loadu_si128((__m128i *)pu1_src); //a0 a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 + src_r1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd)); //b0 b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 + + src_r0_sht_16x8b = _mm_srli_si128(src_r0_16x8b, 1); //a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 0 + src_r1_sht_16x8b = _mm_srli_si128(src_r1_16x8b, 1); //b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 0 + + src_r0_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a0 a1 a1 a2 a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 + src_r1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b0 b1 b1 b2 b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 + + src_r0r1_16x8b = _mm_unpacklo_epi64(src_r0_16x8b, src_r1_16x8b); //a0 a1 a1 a2 a2 a3 a3 a4 b0 b1 b1 b2 b2 b3 b3 b4 + res_r0r1_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); //a0*c0+a1*c1 a1*c0+a2*c1 a2*c0+a3*c1 a3*c0+a4*c1 + //b0*c0+b1*c1 b1*c0+b2*c1 b2*c0+b3*c1 b3*c0+b4*c1 + + src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 4); //a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 0 0 0 0 + src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 4); //b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 0 0 0 0 + + src_r0r1_16x8b = _mm_unpacklo_epi64(src_r0_16x8b, src_r1_16x8b); //a2 a3 a3 a4 a4 a5 a5 a6 b2 b3 b3 b4 b4 b5 b5 b6 + res_r0r1_t2_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff2_3_16x8b); //a2*c2+a3*c3 a3*c2+a4*c3 a4*c2+a5*c3 a5*c2+a6*c3 + //b2*c2+b3*c3 b3*c2+b4*c3 b4*c2+b5*c3 b5*c2+b6*c3 + + src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 4); //a4 a5 a5 a6 a6 a7 a7 a8 0 0 0 0 0 0 0 0 + src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 4); //b4 b5 b5 b6 b6 b7 b7 b8 0 0 0 0 0 0 0 0 + + src_r0r1_16x8b = _mm_unpacklo_epi64(src_r0_16x8b, src_r1_16x8b); //a4 a5 a5 a6 a6 a7 a7 a8 b4 b5 b5 b6 b6 b7 b7 b8 + res_r0r1_t3_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff4_5_16x8b); //a4*c4+a5*c5 a5*c4+a6*c5 a6*c4+a7*c5 a7*c4+a8*c5 + //b4*c4+b5*c5 b5*c4+b6*c5 b4*c6+b7*c5 b7*c4+b8*c5 + src_r0_16x8b = _mm_loadl_epi64((__m128i *)pu1_pred1); + src_r1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_pred1 + src_strd)); + + res_r0r1_t1_8x16b = _mm_add_epi16(res_r0r1_t1_8x16b, res_r0r1_t2_8x16b); + res_r0r1_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_r0r1_t3_8x16b); + res_r0r1_t1_8x16b = _mm_add_epi16(res_r0r1_t1_8x16b, res_r0r1_t3_8x16b); //a0*c0+a1*c1+a2*c2+a3*c3+a4*a4+a5*c5 + 16; + //a1*c0+a2*c1+a2*c2+a3*c3+a5*a4+a6*c5 + 16; + //a2*c0+a3*c1+a4*c2+a5*c3+a6*a4+a7*c5 + 16; + //a3*c0+a4*c1+a5*c2+a6*c3+a6*a4+a8*c5 + 16; + //b0*c0+b1*c1+b2*c2+b3*c3+b4*b4+b5*c5 + 16; + //b1*c0+b2*c1+b2*c2+b3*c3+b5*b4+b6*c5 + 16; + //b2*c0+b3*c1+b4*c2+b5*c3+b6*b4+b7*c5 + 16; + //b3*c0+b4*c1+b5*c2+b6*c3+b6*b4+b8*c5 + 16; + src_r0r1_16x8b = _mm_unpacklo_epi32(src_r0_16x8b,src_r1_16x8b); + + res_r0r1_t1_8x16b = _mm_srai_epi16(res_r0r1_t1_8x16b, 5); //shifting right by 5 bits. + + res_r0r1_16x8b = _mm_packus_epi16(res_r0r1_t1_8x16b, res_r0r1_t1_8x16b); + res_r0r1_16x8b = _mm_avg_epu8(src_r0r1_16x8b, res_r0r1_16x8b); //computing q-pel + + *((WORD32 *)(pu1_dst)) = _mm_cvtsi128_si32(res_r0r1_16x8b); + res_r0r1_16x8b = _mm_srli_si128(res_r0r1_16x8b, 4); + *((WORD32 *)(pu1_dst + dst_strd)) = _mm_cvtsi128_si32(res_r0r1_16x8b); + + ht -= 2; + pu1_src += src_strd << 1; + pu1_pred1 += src_strd << 1; + pu1_dst += dst_strd << 1; + } + while(ht > 0); + } + else if(wd == 8) + { + __m128i src_r0_t1_16x8b, src_r1_t1_16x8b; + + __m128i res_r0_t1_8x16b, res_r0_t2_8x16b, res_r0_t3_8x16b; + __m128i res_r1_t1_8x16b, res_r1_t2_8x16b, res_r1_t3_8x16b; + __m128i res_r0_16x8b, res_r1_16x8b; + + //Row0 : a0 a1 a2 a3 a4 a5 a6 a7 a8 a9..... + //Row1 : b0 b1 b2 b3 b4 b5 b6 b7 b8 b9..... + + do + { + src_r0_16x8b = _mm_loadu_si128((__m128i *)pu1_src); //a0 a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 + src_r1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd)); //b0 b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 + + src_r0_sht_16x8b = _mm_srli_si128(src_r0_16x8b, 1); //a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 0 + src_r1_sht_16x8b = _mm_srli_si128(src_r1_16x8b, 1); //b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 0 + + src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a0 a1 a1 a2 a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 + src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b0 b1 b1 b2 b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 + + res_r0_t1_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff0_1_16x8b); //a0*c0+a1*c1 a1*c0+a2*c1 a2*c0+a3*c1 a3*c0+a4*c1 + //a4*c0+a5*c1 a5*c0+a6*c1 a6*c0+a7*c1 a7*c0+a8*c1 + res_r1_t1_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff0_1_16x8b); //b0*c0+b1*c1 b1*c0+b2*c1 b2*c0+b3*c1 b3*c0+b4*c1 + //b4*c0+b5*c1 b5*c0+b6*c1 b6*c0+b7*c1 b7*c0+b8*c1 + + src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 2); //a2 a3 a4 a5 a6 a7 a8 a9....a15 0 0 + src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 2); //b2 b3 b4 b5 b6 b7 b8 b9....b15 0 0 + + src_r0_sht_16x8b = _mm_srli_si128(src_r0_sht_16x8b, 2); //a3 a4 a5 a6 a7 a8 a9....a15 0 0 0 + src_r1_sht_16x8b = _mm_srli_si128(src_r1_sht_16x8b, 2); //b3 b4 b5 b6 b7 b8 b9....b15 0 0 0 + + src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 a8 a9 a9 a10 + src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 a8 a9 a9 a10 + + res_r0_t2_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff2_3_16x8b); //a2*c2+a3*c3 a3*c2+a4*c3 a4*c2+a5*c3 a5*c2+a6*c3 + //a6*c2+a7*c3 a7*c2+a8*c3 a8*c2+a9*c3 a9*c2+a10*c3 + res_r1_t2_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff2_3_16x8b); //b2*c2+b3*c3 b3*c2+b4*c3 b2*c4+b5*c3 b5*c2+b6*c3 + //b6*c2+b7*c3 b7*c2+b8*c3 b8*c2+b9*c3 b9*c2+b10*c3 + + src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 2); //a4 a5 a6 a7 a8 a9....a15 0 0 0 0 + src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 2); //b4 b5 b6 b7 b8 b9....b15 0 0 0 0 + + src_r0_sht_16x8b = _mm_srli_si128(src_r0_sht_16x8b, 2); //a5 a6 a7 a8 a9....a15 0 0 0 0 0 + src_r1_sht_16x8b = _mm_srli_si128(src_r1_sht_16x8b, 2); //b5 b6 b7 b8 b9....b15 0 0 0 0 0 + + src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a4 a5 a5 a6 a6 a7 a7 a8 a8 a9 a9 a10 a10 a11 a11 a12 + src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b4 b5 b5 b6 b6 b7 b7 b8 b8 b9 b9 b10 b10 b11 b11 b12 + + res_r0_t3_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff4_5_16x8b); //a4*c4+a5*c5 a5*c4+a6*c5 a6*c4+a7*c5 a7*c4+a8*c5 + //a8*c4+a9*c5 a9*c4+a10*c5 a10*c4+a11*c5 a11*c4+a12*c5 + res_r1_t3_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff4_5_16x8b); //b4*c4+b5*c5 b5*c4+b6*c5 b6*c4+b7*c5 b7*c4+b8*c5 + //b8*c4+b9*c5 b9*c4+b10*c5 b10*c4+b11*c5 b11*c4+b12*c5 + src_r0_16x8b = _mm_loadl_epi64((__m128i *)pu1_pred1); + src_r1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_pred1 + src_strd)); + + res_r0_t1_8x16b = _mm_add_epi16(res_r0_t1_8x16b, res_r0_t2_8x16b); + res_r1_t1_8x16b = _mm_add_epi16(res_r1_t1_8x16b, res_r1_t2_8x16b); + res_r0_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_r0_t3_8x16b); + res_r1_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_r1_t3_8x16b); + res_r0_t1_8x16b = _mm_add_epi16(res_r0_t1_8x16b, res_r0_t3_8x16b); + res_r1_t1_8x16b = _mm_add_epi16(res_r1_t1_8x16b, res_r1_t3_8x16b); + + res_r0_t1_8x16b = _mm_srai_epi16(res_r0_t1_8x16b, 5); + res_r1_t1_8x16b = _mm_srai_epi16(res_r1_t1_8x16b, 5); //shifting right by 5 bits. + + res_r0_16x8b = _mm_packus_epi16(res_r0_t1_8x16b, res_r0_t1_8x16b); + res_r1_16x8b = _mm_packus_epi16(res_r1_t1_8x16b, res_r1_t1_8x16b); + + res_r0_16x8b = _mm_avg_epu8(src_r0_16x8b, res_r0_16x8b); + res_r1_16x8b = _mm_avg_epu8(src_r1_16x8b, res_r1_16x8b); //computing q-pel + + _mm_storel_epi64((__m128i *)pu1_dst, res_r0_16x8b); + _mm_storel_epi64((__m128i *)(pu1_dst + dst_strd), res_r1_16x8b); + + ht -= 2; + pu1_src += src_strd << 1; + pu1_pred1 += src_strd << 1; + pu1_dst += dst_strd << 1; + } + while(ht > 0); + } + else // wd == 16 + { + __m128i src_r0_t1_16x8b, src_r1_t1_16x8b; + + __m128i res_r0_t1_8x16b, res_r0_t2_8x16b, res_r0_t3_8x16b; + __m128i res_r1_t1_8x16b, res_r1_t2_8x16b, res_r1_t3_8x16b; + __m128i res_16x8b; + + //Row0 : a0 a1 a2 a3 a4 a5 a6 a7 a8 a9..... + //Row0 : b0 b1 b2 b3 b4 b5 b6 b7 b8 b9..... + //b0 is same a8. Similarly other bn pixels are same as a(n+8) pixels. + + do + { + src_r0_16x8b = _mm_loadu_si128((__m128i *)pu1_src); //a0 a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 + src_r1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + 8)); //b0 b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 + + src_r0_sht_16x8b = _mm_srli_si128(src_r0_16x8b, 1); //a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 0 + src_r1_sht_16x8b = _mm_srli_si128(src_r1_16x8b, 1); //b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 0 + + src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a0 a1 a1 a2 a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 + src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b0 b1 b1 b2 b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 + + res_r0_t1_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff0_1_16x8b); //a0*c0+a1*c1 a1*c0+a2*c1 a2*c0+a3*c1 a3*c0+a4*c1 + //a4*c0+a5*c1 a5*c0+a6*c1 a6*c0+a7*c1 a7*c0+a8*c1 + res_r1_t1_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff0_1_16x8b); //b0*c0+b1*c1 b1*c0+b2*c1 b2*c0+b3*c1 b3*c0+b4*c1 + //b4*c0+b5*c1 b5*c0+b6*c1 b6*c0+b7*c1 b7*c0+b8*c1 + + src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 2); //a2 a3 a4 a5 a6 a7 a8 a9....a15 0 0 + src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 2); //b2 b3 b4 b5 b6 b7 b8 b9....b15 0 0 + + src_r0_sht_16x8b = _mm_srli_si128(src_r0_sht_16x8b, 2); //a3 a4 a5 a6 a7 a8 a9....a15 0 0 0 + src_r1_sht_16x8b = _mm_srli_si128(src_r1_sht_16x8b, 2); //b3 b4 b5 b6 b7 b8 b9....b15 0 0 0 + + src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 a8 a9 a9 a10 + src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 a8 a9 a9 a10 + + res_r0_t2_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff2_3_16x8b); //a2*c2+a3*c3 a3*c2+a4*c3 a4*c2+a5*c3 a5*c2+a6*c3 + //a6*c2+a7*c3 a7*c2+a8*c3 a8*c2+a9*c3 a9*c2+a10*c3 + res_r1_t2_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff2_3_16x8b); //b2*c2+b3*c3 b3*c2+b4*c3 b2*c4+b5*c3 b5*c2+b6*c3 + //b6*c2+b7*c3 b7*c2+b8*c3 b8*c2+b9*c3 b9*c2+b10*c3 + + src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 2); //a4 a5 a6 a7 a8 a9....a15 0 0 0 0 + src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 2); //b4 b5 b6 b7 b8 b9....b15 0 0 0 0 + + src_r0_sht_16x8b = _mm_srli_si128(src_r0_sht_16x8b, 2); //a5 a6 a7 a8 a9....a15 0 0 0 0 0 + src_r1_sht_16x8b = _mm_srli_si128(src_r1_sht_16x8b, 2); //b5 b6 b7 b8 b9....b15 0 0 0 0 0 + + src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a4 a5 a5 a6 a6 a7 a7 a8 a8 a9 a9 a10 a10 a11 a11 a12 + src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b4 b5 b5 b6 b6 b7 b7 b8 b8 b9 b9 b10 b10 b11 b11 b12 + + res_r0_t3_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff4_5_16x8b); //a4*c4+a5*c5 a5*c4+a6*c5 a6*c4+a7*c5 a7*c4+a8*c5 + //a8*c4+a9*c5 a9*c4+a10*c5 a10*c4+a11*c5 a11*c4+a12*c5 + res_r1_t3_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff4_5_16x8b); //b4*c4+b5*c5 b5*c4+b6*c5 b6*c4+b7*c5 b7*c4+b8*c5 + //b8*c4+b9*c5 b9*c4+b10*c5 b10*c4+b11*c5 b11*c4+b12*c5 + src_r0_16x8b = _mm_loadu_si128((__m128i *)pu1_pred1); + + res_r0_t1_8x16b = _mm_add_epi16(res_r0_t1_8x16b, res_r0_t2_8x16b); + res_r1_t1_8x16b = _mm_add_epi16(res_r1_t1_8x16b, res_r1_t2_8x16b); + res_r0_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_r0_t3_8x16b); + res_r1_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_r1_t3_8x16b); + res_r0_t1_8x16b = _mm_add_epi16(res_r0_t1_8x16b, res_r0_t3_8x16b); + res_r1_t1_8x16b = _mm_add_epi16(res_r1_t1_8x16b, res_r1_t3_8x16b); + + res_r0_t1_8x16b = _mm_srai_epi16(res_r0_t1_8x16b, 5); + res_r1_t1_8x16b = _mm_srai_epi16(res_r1_t1_8x16b, 5); //shifting right by 5 bits + + res_16x8b = _mm_packus_epi16(res_r0_t1_8x16b, res_r1_t1_8x16b); + res_16x8b = _mm_avg_epu8(src_r0_16x8b, res_16x8b); //computing q-pel + + _mm_storeu_si128((__m128i *)pu1_dst, res_16x8b); + + ht--; + pu1_src += src_strd; + pu1_pred1 += src_strd; + pu1_dst += dst_strd; + } + while(ht > 0); + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_inter_pred_luma_vert_qpel_ssse3 */ +/* */ +/* Description : This function implements a six-tap filter vertically on */ +/* ht x wd block and averages the values with the source */ +/* pixels to calculate vertical quarter-pel as mentioned in */ +/* sec. 8.4.2.2.1 titled "Luma sample interpolation */ +/* process". (ht,wd) can be (4,4), (8,4), (4,8), (8,8), */ +/* (16,8), (8,16) or (16,16). */ +/* */ +/* Inputs : puc_src - pointer to source */ +/* puc_dst - pointer to destination */ +/* src_strd - stride for source */ +/* dst_strd - stride for destination */ +/* ht - height of the block */ +/* wd - width of the block */ +/* pu1_tmp - pointer to temporary buffer */ +/* dydx - x and y reference offset for q-pel */ +/* calculations */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes */ +/* 13 02 2015 Kaushik Initial Version */ +/* Senthoor */ +/* */ +/*****************************************************************************/ +void ih264_inter_pred_luma_vert_qpel_ssse3(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ht, + WORD32 wd, + UWORD8* pu1_tmp, + WORD32 dydx) +{ + WORD32 y_offset; + UWORD8 *pu1_pred1; + + + __m128i src_r0_16x8b, src_r1_16x8b, src_r2_16x8b, src_r3_16x8b, src_r4_16x8b; + __m128i src_r5_16x8b, src_r6_16x8b; + __m128i src_r0r1_16x8b, src_r2r3_16x8b, src_r4r5_16x8b; + __m128i res_16x8b, res_t1_8x16b, res_t2_8x16b, res_t3_8x16b; + + __m128i coeff0_1_16x8b, coeff2_3_16x8b, coeff4_5_16x8b; + __m128i const_val16_8x16b; + + UNUSED(pu1_tmp); + y_offset = dydx & 0xf; + + coeff0_1_16x8b = _mm_set1_epi32(0xFB01FB01); //c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 + coeff2_3_16x8b = _mm_set1_epi32(0x14141414); //c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 + coeff4_5_16x8b = _mm_set1_epi32(0x01FB01FB); //c4 c5 c4 c5 c4 c5 c4 c5 c4 c5 c4 c5 c4 c5 c4 c5 + //c0 = c5 = 1, c1 = c4 = -5, c2 = c3 = 20 + + pu1_pred1 = pu1_src + (y_offset >> 3) * src_strd; + + const_val16_8x16b = _mm_set1_epi16(16); + + pu1_src -= src_strd << 1; // the filter input starts from x[-2] (till x[3]) + + if(wd == 4) + { + //Epilogue: Load all the pred rows except sixth and seventh row + // for the first and second row processing. + src_r0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); + pu1_src += src_strd; + src_r1_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); + pu1_src += src_strd; + src_r2_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); + pu1_src += src_strd; + src_r3_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); + pu1_src += src_strd; + src_r4_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); + pu1_src += src_strd; + + src_r0_16x8b = _mm_unpacklo_epi32(src_r0_16x8b, src_r1_16x8b); + src_r1_16x8b = _mm_unpacklo_epi32(src_r1_16x8b, src_r2_16x8b); + src_r2_16x8b = _mm_unpacklo_epi32(src_r2_16x8b, src_r3_16x8b); + src_r3_16x8b = _mm_unpacklo_epi32(src_r3_16x8b, src_r4_16x8b); + + do + { + src_r5_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); + src_r6_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src + src_strd)); + + src_r4_16x8b = _mm_unpacklo_epi32(src_r4_16x8b, src_r5_16x8b); + src_r5_16x8b = _mm_unpacklo_epi32(src_r5_16x8b, src_r6_16x8b); + + src_r0r1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r1_16x8b); + src_r2r3_16x8b = _mm_unpacklo_epi8(src_r2_16x8b, src_r3_16x8b); + src_r4r5_16x8b = _mm_unpacklo_epi8(src_r4_16x8b, src_r5_16x8b); + + res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); + res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); + res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); + + src_r0_16x8b = _mm_loadl_epi64((__m128i *)pu1_pred1); + src_r1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_pred1 + src_strd)); + + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); + res_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_t3_8x16b); + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t3_8x16b); + + src_r0r1_16x8b = _mm_unpacklo_epi32(src_r0_16x8b,src_r1_16x8b); + + res_t1_8x16b = _mm_srai_epi16(res_t1_8x16b, 5); //shifting right by 5 bits. + + res_16x8b = _mm_packus_epi16(res_t1_8x16b, res_t1_8x16b); + + res_16x8b = _mm_avg_epu8(src_r0r1_16x8b, res_16x8b); //computing q-pel + + *((WORD32 *)(pu1_dst)) = _mm_cvtsi128_si32(res_16x8b); + res_16x8b = _mm_srli_si128(res_16x8b, 4); + *((WORD32 *)(pu1_dst + dst_strd)) = _mm_cvtsi128_si32(res_16x8b); + + src_r0_16x8b = src_r2_16x8b; + src_r1_16x8b = src_r3_16x8b; + src_r2_16x8b = src_r4_16x8b; + src_r3_16x8b = src_r5_16x8b; + src_r4_16x8b = src_r6_16x8b; + + ht -= 2; + pu1_src += src_strd << 1; + pu1_pred1 += src_strd << 1; + pu1_dst += dst_strd << 1; + } + while(ht > 0); + } + + else if(wd == 8) + { + //Epilogue: Load all the pred rows except sixth and seventh row + // for the first and second row processing. + src_r0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); + pu1_src += src_strd; + src_r1_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); + pu1_src += src_strd; + src_r2_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); + pu1_src += src_strd; + src_r3_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); + pu1_src += src_strd; + src_r4_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); + pu1_src += src_strd; + + src_r0_16x8b = _mm_unpacklo_epi64(src_r0_16x8b, src_r1_16x8b); + src_r1_16x8b = _mm_unpacklo_epi64(src_r1_16x8b, src_r2_16x8b); + src_r2_16x8b = _mm_unpacklo_epi64(src_r2_16x8b, src_r3_16x8b); + src_r3_16x8b = _mm_unpacklo_epi64(src_r3_16x8b, src_r4_16x8b); + + do + { + src_r5_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); + src_r6_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src + src_strd)); + + src_r4_16x8b = _mm_unpacklo_epi64(src_r4_16x8b, src_r5_16x8b); + src_r5_16x8b = _mm_unpacklo_epi64(src_r5_16x8b, src_r6_16x8b); + + src_r0r1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r1_16x8b); + src_r2r3_16x8b = _mm_unpacklo_epi8(src_r2_16x8b, src_r3_16x8b); + src_r4r5_16x8b = _mm_unpacklo_epi8(src_r4_16x8b, src_r5_16x8b); + + res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); + res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); + res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); + + src_r0r1_16x8b = _mm_loadl_epi64((__m128i *)pu1_pred1); + + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); + res_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_t3_8x16b); + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t3_8x16b); + + res_t1_8x16b = _mm_srai_epi16(res_t1_8x16b, 5); //shifting right by 5 bits. + + res_16x8b = _mm_packus_epi16(res_t1_8x16b, res_t1_8x16b); + res_16x8b = _mm_avg_epu8(src_r0r1_16x8b, res_16x8b); //computing q-pel + + _mm_storel_epi64((__m128i *)pu1_dst, res_16x8b); + + src_r0r1_16x8b = _mm_unpackhi_epi8(src_r0_16x8b, src_r1_16x8b); + src_r2r3_16x8b = _mm_unpackhi_epi8(src_r2_16x8b, src_r3_16x8b); + src_r4r5_16x8b = _mm_unpackhi_epi8(src_r4_16x8b, src_r5_16x8b); + + res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); + res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); + res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); + + src_r0r1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_pred1 + src_strd)); + + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); + res_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_t3_8x16b); + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t3_8x16b); + + res_t1_8x16b = _mm_srai_epi16(res_t1_8x16b, 5); //shifting right by 5 bits. + + res_16x8b = _mm_packus_epi16(res_t1_8x16b, res_t1_8x16b); + res_16x8b = _mm_avg_epu8(src_r0r1_16x8b, res_16x8b); //computing q-pel + + _mm_storel_epi64((__m128i *)(pu1_dst + dst_strd), res_16x8b); + + src_r0_16x8b = src_r2_16x8b; + src_r1_16x8b = src_r3_16x8b; + src_r2_16x8b = src_r4_16x8b; + src_r3_16x8b = src_r5_16x8b; + src_r4_16x8b = src_r6_16x8b; + + ht -= 2; + pu1_src += src_strd << 1; + pu1_pred1 += src_strd << 1; + pu1_dst += dst_strd << 1; + } + while(ht > 0); + } + else // wd == 16 + { + __m128i res_t0_8x16b; + + //Epilogue: Load all the pred rows except sixth and seventh row + // for the first and second row processing. + src_r0_16x8b = _mm_loadu_si128((__m128i *)pu1_src); + pu1_src += src_strd; + src_r1_16x8b = _mm_loadu_si128((__m128i *)pu1_src); + pu1_src += src_strd; + src_r2_16x8b = _mm_loadu_si128((__m128i *)pu1_src); + pu1_src += src_strd; + src_r3_16x8b = _mm_loadu_si128((__m128i *)pu1_src); + pu1_src += src_strd; + src_r4_16x8b = _mm_loadu_si128((__m128i *)pu1_src); + pu1_src += src_strd; + + do + { + src_r5_16x8b = _mm_loadu_si128((__m128i *)pu1_src); + src_r6_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd)); + + src_r0r1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r1_16x8b); + src_r2r3_16x8b = _mm_unpacklo_epi8(src_r2_16x8b, src_r3_16x8b); + src_r4r5_16x8b = _mm_unpacklo_epi8(src_r4_16x8b, src_r5_16x8b); + + res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); + res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); + res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); + + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); + res_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_t3_8x16b); + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t3_8x16b); + + res_t0_8x16b = _mm_srai_epi16(res_t1_8x16b, 5); //shifting right by 5 bits. + + src_r0r1_16x8b = _mm_unpackhi_epi8(src_r0_16x8b, src_r1_16x8b); + src_r2r3_16x8b = _mm_unpackhi_epi8(src_r2_16x8b, src_r3_16x8b); + src_r4r5_16x8b = _mm_unpackhi_epi8(src_r4_16x8b, src_r5_16x8b); + + res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); + res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); + res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); + + src_r0r1_16x8b = _mm_loadu_si128((__m128i *)pu1_pred1); + + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); + res_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_t3_8x16b); + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t3_8x16b); + + res_t1_8x16b = _mm_srai_epi16(res_t1_8x16b, 5); //shifting right by 5 bits. + + res_16x8b = _mm_packus_epi16(res_t0_8x16b, res_t1_8x16b); + res_16x8b = _mm_avg_epu8(src_r0r1_16x8b, res_16x8b); //computing q-pel + + _mm_storeu_si128((__m128i *)pu1_dst, res_16x8b); + + src_r0r1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r2_16x8b); + src_r2r3_16x8b = _mm_unpacklo_epi8(src_r3_16x8b, src_r4_16x8b); + src_r4r5_16x8b = _mm_unpacklo_epi8(src_r5_16x8b, src_r6_16x8b); + + res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); + res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); + res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); + + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); + res_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_t3_8x16b); + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t3_8x16b); + + res_t0_8x16b = _mm_srai_epi16(res_t1_8x16b, 5); //shifting right by 5 bits. + + src_r0r1_16x8b = _mm_unpackhi_epi8(src_r1_16x8b, src_r2_16x8b); + src_r2r3_16x8b = _mm_unpackhi_epi8(src_r3_16x8b, src_r4_16x8b); + src_r4r5_16x8b = _mm_unpackhi_epi8(src_r5_16x8b, src_r6_16x8b); + + res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); + res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); + res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); + + src_r0r1_16x8b = _mm_loadu_si128((__m128i *)(pu1_pred1 + src_strd)); + + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); + res_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_t3_8x16b); + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t3_8x16b); + + res_t1_8x16b = _mm_srai_epi16(res_t1_8x16b, 5); //shifting right by 5 bits. + + res_16x8b = _mm_packus_epi16(res_t0_8x16b, res_t1_8x16b); + res_16x8b = _mm_avg_epu8(src_r0r1_16x8b, res_16x8b); //computing q-pel + + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), res_16x8b); + + src_r0_16x8b = src_r2_16x8b; + src_r1_16x8b = src_r3_16x8b; + src_r2_16x8b = src_r4_16x8b; + src_r3_16x8b = src_r5_16x8b; + src_r4_16x8b = src_r6_16x8b; + + ht -= 2; + pu1_src += src_strd << 1; + pu1_pred1 += src_strd << 1; + pu1_dst += dst_strd << 1; + } + while(ht > 0); + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_inter_pred_luma_horz_qpel_vert_qpel_ssse3 */ +/* */ +/* Description : This function implements a six-tap filter vertically and */ +/* horizontally on ht x wd block separately and averages */ +/* the two sets of values to calculate values at (1/4,1/4), */ +/* (1/4, 3/4), (3/4, 1/4) or (3/4, 3/4) as mentioned in */ +/* sec. 8.4.2.2.1 titled "Luma sample interpolation */ +/* process". (ht,wd) can be (4,4), (8,4), (4,8), (8,8), */ +/* (16,8), (8,16) or (16,16). */ +/* */ +/* Inputs : puc_src - pointer to source */ +/* puc_dst - pointer to destination */ +/* src_strd - stride for source */ +/* dst_strd - stride for destination */ +/* ht - height of the block */ +/* wd - width of the block */ +/* pu1_tmp - pointer to temporary buffer */ +/* dydx - x and y reference offset for q-pel */ +/* calculations */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes */ +/* 13 02 2015 Kaushik Initial Version */ +/* Senthoor */ +/* */ +/*****************************************************************************/ +void ih264_inter_pred_luma_horz_qpel_vert_qpel_ssse3(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ht, + WORD32 wd, + UWORD8* pu1_tmp, + WORD32 dydx) +{ + WORD32 ht_temp; + UWORD8 *pu1_pred_vert,*pu1_pred_horiz; + UWORD8 *pu1_tmp1, *pu1_tmp2; + WORD32 x_offset, y_offset; + + __m128i coeff0_1_16x8b, coeff2_3_16x8b, coeff4_5_16x8b; + __m128i const_val16_8x16b; + + pu1_tmp1 = pu1_tmp; + + dydx &= 0xf; + ht_temp = ht; + x_offset = dydx & 0x3; + y_offset = dydx >> 2; + pu1_tmp2 = pu1_tmp1; + + pu1_pred_vert = pu1_src + (x_offset >> 1) - 2*src_strd; + pu1_pred_horiz = pu1_src + (y_offset >> 1) * src_strd - 2; + //the filter input starts from x[-2] (till x[3]) + + coeff0_1_16x8b = _mm_set1_epi32(0xFB01FB01); //c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 + coeff2_3_16x8b = _mm_set1_epi32(0x14141414); //c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 + coeff4_5_16x8b = _mm_set1_epi32(0x01FB01FB); //c4 c5 c5 c5 c4 c5 c5 c5 c4 c5 c5 c5 c4 c5 c5 c5 + //c0 = c5 = 1, c1 = c4 = -5, c2 = c3 = 20 + const_val16_8x16b = _mm_set1_epi16(16); + + if(wd == 4) + { + //vertical q-pel filter + { + __m128i src_r0_16x8b, src_r1_16x8b, src_r2_16x8b, src_r3_16x8b, src_r4_16x8b; + __m128i src_r5_16x8b, src_r6_16x8b; + __m128i src_r0r1_16x8b, src_r2r3_16x8b, src_r4r5_16x8b; + + __m128i res_r0r1_16x8b, res_t1_8x16b, res_t2_8x16b, res_t3_8x16b; + + //epilogue: Load all the pred rows except sixth and seventh row for the + //first and second row processing. + src_r0_16x8b = _mm_loadl_epi64((__m128i *)(pu1_pred_vert)); + pu1_pred_vert = pu1_pred_vert + src_strd; + + src_r1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_pred_vert)); + pu1_pred_vert = pu1_pred_vert + src_strd; + src_r0_16x8b = _mm_unpacklo_epi32(src_r0_16x8b, src_r1_16x8b); + + src_r2_16x8b = _mm_loadl_epi64((__m128i *)(pu1_pred_vert)); + pu1_pred_vert = pu1_pred_vert + src_strd; + src_r1_16x8b = _mm_unpacklo_epi32(src_r1_16x8b, src_r2_16x8b); + + src_r3_16x8b = _mm_loadl_epi64((__m128i *)(pu1_pred_vert)); + pu1_pred_vert = pu1_pred_vert + src_strd; + src_r2_16x8b = _mm_unpacklo_epi32(src_r2_16x8b, src_r3_16x8b); + + src_r4_16x8b = _mm_loadl_epi64((__m128i *)(pu1_pred_vert)); + pu1_pred_vert = pu1_pred_vert + src_strd; + src_r3_16x8b = _mm_unpacklo_epi32(src_r3_16x8b, src_r4_16x8b); + + //Core Loop: Process all the rows. + do + { + src_r5_16x8b = _mm_loadl_epi64((__m128i *)(pu1_pred_vert)); + src_r4_16x8b = _mm_unpacklo_epi32(src_r4_16x8b, src_r5_16x8b); + + src_r6_16x8b = _mm_loadl_epi64((__m128i *)(pu1_pred_vert + src_strd)); + src_r5_16x8b = _mm_unpacklo_epi32(src_r5_16x8b, src_r6_16x8b); + + src_r0r1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r1_16x8b); + src_r2r3_16x8b = _mm_unpacklo_epi8(src_r2_16x8b, src_r3_16x8b); + src_r4r5_16x8b = _mm_unpacklo_epi8(src_r4_16x8b, src_r5_16x8b); + + res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); + res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); + res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); + + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); + res_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_t3_8x16b); + res_t1_8x16b = _mm_add_epi16(res_t3_8x16b, res_t1_8x16b); + + res_t1_8x16b = _mm_srai_epi16(res_t1_8x16b, 5); //shifting right by 5 bits. + res_r0r1_16x8b = _mm_packus_epi16(res_t1_8x16b, res_t1_8x16b); + + _mm_storel_epi64((__m128i *)pu1_tmp1, res_r0r1_16x8b); + + src_r0_16x8b = src_r2_16x8b; + src_r1_16x8b = src_r3_16x8b; + src_r2_16x8b = src_r4_16x8b; + src_r3_16x8b = src_r5_16x8b; + src_r4_16x8b = src_r6_16x8b; + + ht_temp -= 2; + pu1_pred_vert += src_strd << 1; + pu1_tmp1 += 8; + } + while(ht_temp > 0); + } + + //horizontal q-pel filter + { + __m128i src_r0_16x8b, src_r1_16x8b; + __m128i src_r0_sht_16x8b, src_r1_sht_16x8b; + __m128i src_r0r1_vpel_16x8b, src_r0r1_t1_16x8b; + + __m128i res_r0r1_t1_8x16b, res_r0r1_t2_8x16b, res_r0r1_t3_8x16b; + __m128i res_r0r1_16x8b; + + //Row0 : a0 a1 a2 a3 a4 a5 a6 a7 a8 a9..... + //Row1 : b0 b1 b2 b3 b4 b5 b6 b7 b8 b9..... + + do + { + src_r0_16x8b = _mm_loadu_si128((__m128i *)pu1_pred_horiz); //a0 a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 + src_r1_16x8b = _mm_loadu_si128((__m128i *)(pu1_pred_horiz + src_strd)); //b0 b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 + + src_r0r1_vpel_16x8b = _mm_loadl_epi64((__m128i *)pu1_tmp2); + + src_r0_sht_16x8b = _mm_srli_si128(src_r0_16x8b, 1); //a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 0 + src_r1_sht_16x8b = _mm_srli_si128(src_r1_16x8b, 1); //b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 0 + + src_r0_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a0 a1 a1 a2 a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 + src_r1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b0 b1 b1 b2 b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 + + src_r0r1_t1_16x8b = _mm_unpacklo_epi64(src_r0_16x8b, src_r1_16x8b); //a0 a1 a1 a2 a2 a3 a3 a4 b0 b1 b1 b2 b2 b3 b3 b4 + res_r0r1_t1_8x16b = _mm_maddubs_epi16(src_r0r1_t1_16x8b, coeff0_1_16x8b); //a0*c0+a1*c1 a1*c0+a2*c1 a2*c0+a3*c1 a3*c0+a4*c1 + //b0*c0+b1*c1 b1*c0+b2*c1 b2*c0+b3*c1 b3*c0+b4*c1 + + src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 4); //a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 0 0 0 0 + src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 4); //b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 0 0 0 0 + + src_r0r1_t1_16x8b = _mm_unpacklo_epi64(src_r0_16x8b, src_r1_16x8b); //a2 a3 a3 a4 a4 a5 a5 a6 b2 b3 b3 b4 b4 b5 b5 b6 + res_r0r1_t2_8x16b = _mm_maddubs_epi16(src_r0r1_t1_16x8b, coeff2_3_16x8b); //a2*c2+a3*c3 a3*c2+a4*c3 a4*c2+a5*c3 a5*c2+a6*c3 + //b2*c2+b3*c3 b3*c2+b4*c3 b4*c2+b5*c3 b5*c2+b6*c3 + + src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 4); //a4 a5 a5 a6 a6 a7 a7 a8 0 0 0 0 0 0 0 0 + src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 4); //b4 b5 b5 b6 b6 b7 b7 b8 0 0 0 0 0 0 0 0 + + src_r0r1_t1_16x8b = _mm_unpacklo_epi64(src_r0_16x8b, src_r1_16x8b); //a4 a5 a5 a6 a6 a7 a7 a8 b4 b5 b5 b6 b6 b7 b7 b8 + res_r0r1_t3_8x16b = _mm_maddubs_epi16(src_r0r1_t1_16x8b, coeff4_5_16x8b); //a4*c4+a5*c5 a5*c4+a6*c5 a6*c4+a7*c5 a7*c4+a8*c5 + //b4*c4+b5*c5 b5*c4+b6*c5 b4*c6+b7*c5 b7*c4+b8*c5 + + res_r0r1_t1_8x16b = _mm_add_epi16(res_r0r1_t1_8x16b, res_r0r1_t2_8x16b); + res_r0r1_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_r0r1_t3_8x16b); + res_r0r1_t1_8x16b = _mm_add_epi16(res_r0r1_t1_8x16b, res_r0r1_t3_8x16b); //a0*c0+a1*c1+a2*c2+a3*c3+a4*a4+a5*c5 + 15; + //a1*c0+a2*c1+a2*c2+a3*c3+a5*a4+a6*c5 + 15; + //a2*c0+a3*c1+a4*c2+a5*c3+a6*a4+a7*c5 + 15; + //a3*c0+a4*c1+a5*c2+a6*c3+a6*a4+a8*c5 + 15; + //b0*c0+b1*c1+b2*c2+b3*c3+b4*b4+b5*c5 + 15; + //b1*c0+b2*c1+b2*c2+b3*c3+b5*b4+b6*c5 + 15; + //b2*c0+b3*c1+b4*c2+b5*c3+b6*b4+b7*c5 + 15; + //b3*c0+b4*c1+b5*c2+b6*c3+b6*b4+b8*c5 + 15; + + res_r0r1_t1_8x16b = _mm_srai_epi16(res_r0r1_t1_8x16b, 5); //shifting right by 5 bits. + + res_r0r1_16x8b = _mm_packus_epi16(res_r0r1_t1_8x16b,res_r0r1_t1_8x16b); + + res_r0r1_16x8b = _mm_avg_epu8(res_r0r1_16x8b,src_r0r1_vpel_16x8b); + + *((WORD32 *)(pu1_dst)) = _mm_cvtsi128_si32(res_r0r1_16x8b); + res_r0r1_16x8b = _mm_srli_si128(res_r0r1_16x8b, 4); + *((WORD32 *)(pu1_dst + dst_strd)) = _mm_cvtsi128_si32(res_r0r1_16x8b); + + ht -= 2; + pu1_pred_horiz += src_strd << 1; + pu1_tmp2 += 8; + pu1_dst += dst_strd << 1; + } + while(ht > 0); + } + } + else if(wd == 8) + { + //vertical q-pel filter + { + __m128i src_r0_16x8b, src_r1_16x8b, src_r2_16x8b, src_r3_16x8b; + __m128i src_r4_16x8b, src_r5_16x8b, src_r6_16x8b; + __m128i src_r0r1_16x8b, src_r2r3_16x8b, src_r4r5_16x8b; + + __m128i res_16x8b, res_t1_8x16b, res_t2_8x16b, res_t3_8x16b; + + //epilogue: Load all the pred rows except sixth and seventh row for the + //first and second row processing. + src_r0_16x8b = _mm_loadl_epi64((__m128i *)(pu1_pred_vert)); + pu1_pred_vert = pu1_pred_vert + src_strd; + + src_r1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_pred_vert)); + pu1_pred_vert = pu1_pred_vert + src_strd; + src_r0_16x8b = _mm_unpacklo_epi64(src_r0_16x8b, src_r1_16x8b); + + src_r2_16x8b = _mm_loadl_epi64((__m128i *)(pu1_pred_vert)); + pu1_pred_vert = pu1_pred_vert + src_strd; + src_r1_16x8b = _mm_unpacklo_epi64(src_r1_16x8b, src_r2_16x8b); + + src_r3_16x8b = _mm_loadl_epi64((__m128i *)(pu1_pred_vert)); + pu1_pred_vert = pu1_pred_vert + src_strd; + src_r2_16x8b = _mm_unpacklo_epi64(src_r2_16x8b, src_r3_16x8b); + + src_r4_16x8b = _mm_loadl_epi64((__m128i *)(pu1_pred_vert)); + pu1_pred_vert = pu1_pred_vert + src_strd; + src_r3_16x8b = _mm_unpacklo_epi64(src_r3_16x8b, src_r4_16x8b); + + //Core Loop: Process all the rows. + do + { + src_r5_16x8b = _mm_loadl_epi64((__m128i *)(pu1_pred_vert)); + src_r4_16x8b = _mm_unpacklo_epi64(src_r4_16x8b, src_r5_16x8b); + + src_r6_16x8b = _mm_loadl_epi64((__m128i *)(pu1_pred_vert + src_strd)); + src_r5_16x8b = _mm_unpacklo_epi64(src_r5_16x8b, src_r6_16x8b); + + src_r0r1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r1_16x8b); + src_r2r3_16x8b = _mm_unpacklo_epi8(src_r2_16x8b, src_r3_16x8b); + src_r4r5_16x8b = _mm_unpacklo_epi8(src_r4_16x8b, src_r5_16x8b); + + res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); + res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); + res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); + + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); + res_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_t3_8x16b); + res_t1_8x16b = _mm_add_epi16(res_t3_8x16b, res_t1_8x16b); + + res_t1_8x16b = _mm_srai_epi16(res_t1_8x16b, 5); //shifting right by 5 bits. + res_16x8b = _mm_packus_epi16(res_t1_8x16b, res_t1_8x16b); + + _mm_storel_epi64((__m128i *)(pu1_tmp1), res_16x8b); + + src_r0r1_16x8b = _mm_unpackhi_epi8(src_r0_16x8b, src_r1_16x8b); + src_r2r3_16x8b = _mm_unpackhi_epi8(src_r2_16x8b, src_r3_16x8b); + src_r4r5_16x8b = _mm_unpackhi_epi8(src_r4_16x8b, src_r5_16x8b); + + res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); + res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); + res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); + + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); + res_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_t3_8x16b); + res_t1_8x16b = _mm_add_epi16(res_t3_8x16b, res_t1_8x16b); + + res_t1_8x16b = _mm_srai_epi16(res_t1_8x16b, 5); //shifting right by 5 bits. + res_16x8b = _mm_packus_epi16(res_t1_8x16b, res_t1_8x16b); + + _mm_storel_epi64((__m128i *)(pu1_tmp1 + 8), res_16x8b); + + src_r0_16x8b = src_r2_16x8b; + src_r1_16x8b = src_r3_16x8b; + src_r2_16x8b = src_r4_16x8b; + src_r3_16x8b = src_r5_16x8b; + src_r4_16x8b = src_r6_16x8b; + + ht_temp -= 2; + pu1_pred_vert += src_strd << 1; + pu1_tmp1 += 16; + } + while(ht_temp > 0); + } + + //horizontal q-pel filter + { + __m128i src_r0_16x8b, src_r1_16x8b, src_r0_sht_16x8b, src_r1_sht_16x8b; + __m128i src_r0_t1_16x8b, src_r1_t1_16x8b; + __m128i src_r0_vpel_16x8b, src_r1_vpel_16x8b; + + __m128i res_r0_t1_8x16b, res_r0_t2_8x16b, res_r0_t3_8x16b; + __m128i res_r1_t1_8x16b, res_r1_t2_8x16b, res_r1_t3_8x16b, res_16x8b; + + //Row0 : a0 a1 a2 a3 a4 a5 a6 a7 a8 a9..... + //Row1 : b0 b1 b2 b3 b4 b5 b6 b7 b8 b9..... + + do + { + src_r0_16x8b = _mm_loadu_si128((__m128i *)(pu1_pred_horiz)); //a0 a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 + src_r1_16x8b = _mm_loadu_si128((__m128i *)(pu1_pred_horiz + src_strd)); //b0 b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 + + src_r0_vpel_16x8b = _mm_loadl_epi64((__m128i *)(pu1_tmp2)); //a2 a3 a4 a5 a6 a7 a8....a15 0 or + //a3 a4 a5 a6 a7 a8 a9....a15 0 + src_r1_vpel_16x8b = _mm_loadl_epi64((__m128i *)(pu1_tmp2 + 8)); + //b2 b3 b4 b5 b6 b7 b8....b15 0 or + //b3 b4 b5 b6 b7 b8 b9....b15 0 + + src_r0_sht_16x8b = _mm_srli_si128(src_r0_16x8b, 1); //a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 0 + src_r1_sht_16x8b = _mm_srli_si128(src_r1_16x8b, 1); //b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 0 + + src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a0 a1 a1 a2 a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 + src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b0 b1 b1 b2 b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 + + res_r0_t1_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff0_1_16x8b); //a0*c0+a1*c1 a1*c0+a2*c1 a2*c0+a3*c1 a3*c0+a4*c1 + //a4*c0+a5*c1 a5*c0+a6*c1 a6*c0+a7*c1 a7*c0+a8*c1 + res_r1_t1_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff0_1_16x8b); //b0*c0+b1*c1 b1*c0+b2*c1 b2*c0+b3*c1 b3*c0+b4*c1 + //b4*c0+b5*c1 b5*c0+b6*c1 b6*c0+b7*c1 b7*c0+b8*c1 + + src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 2); //a2 a3 a4 a5 a6 a7 a8 a9....a15 0 0 + src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 2); //b2 b3 b4 b5 b6 b7 b8 b9....b15 0 0 + + src_r0_sht_16x8b = _mm_srli_si128(src_r0_sht_16x8b, 2); //a3 a4 a5 a6 a7 a8 a9....a15 0 0 0 + src_r1_sht_16x8b = _mm_srli_si128(src_r1_sht_16x8b, 2); //b3 b4 b5 b6 b7 b8 b9....b15 0 0 0 + + src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 a8 a9 a9 a10 + src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 a8 a9 a9 a10 + + res_r0_t2_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff2_3_16x8b); //a2*c2+a3*c3 a3*c2+a4*c3 a4*c2+a5*c3 a5*c2+a6*c3 + //a6*c2+a7*c3 a7*c2+a8*c3 a8*c2+a9*c3 a9*c2+a10*c3 + res_r1_t2_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff2_3_16x8b); //b2*c2+b3*c3 b3*c2+b4*c3 b2*c4+b5*c3 b5*c2+b6*c3 + //b6*c2+b7*c3 b7*c2+b8*c3 b8*c2+b9*c3 b9*c2+b10*c3 + + src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 2); //a4 a5 a6 a7 a8 a9....a15 0 0 0 0 + src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 2); //b4 b5 b6 b7 b8 b9....b15 0 0 0 0 + + src_r0_sht_16x8b = _mm_srli_si128(src_r0_sht_16x8b, 2); //a5 a6 a7 a8 a9....a15 0 0 0 0 0 + src_r1_sht_16x8b = _mm_srli_si128(src_r1_sht_16x8b, 2); //b5 b6 b7 b8 b9....b15 0 0 0 0 0 + + src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a4 a5 a5 a6 a6 a7 a7 a8 a8 a9 a9 a10 a10 a11 a11 a12 + src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b4 b5 b5 b6 b6 b7 b7 b8 b8 b9 b9 b10 b10 b11 b11 b12 + + res_r0_t3_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff4_5_16x8b); //a4*c4+a5*c5 a5*c4+a6*c5 a6*c4+a7*c5 a7*c4+a8*c5 + //a8*c4+a9*c5 a9*c4+a10*c5 a10*c4+a11*c5 a11*c4+a12*c5 + res_r1_t3_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff4_5_16x8b); //b4*c4+b5*c5 b5*c4+b6*c5 b6*c4+b7*c5 b7*c4+b8*c5 + //b8*c4+b9*c5 b9*c4+b10*c5 b10*c4+b11*c5 b11*c4+b12*c5 + res_r0_t1_8x16b = _mm_add_epi16(res_r0_t1_8x16b, res_r0_t2_8x16b); + res_r0_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_r0_t3_8x16b); + res_r0_t1_8x16b = _mm_add_epi16(res_r0_t1_8x16b, res_r0_t3_8x16b); + res_r0_t1_8x16b = _mm_srai_epi16(res_r0_t1_8x16b, 5); //shifting right by 5 bits. + + res_16x8b = _mm_packus_epi16(res_r0_t1_8x16b, res_r0_t1_8x16b); + res_16x8b = _mm_avg_epu8(res_16x8b, src_r0_vpel_16x8b); + + _mm_storel_epi64((__m128i *)(pu1_dst), res_16x8b); + + res_r1_t1_8x16b = _mm_add_epi16(res_r1_t1_8x16b, res_r1_t2_8x16b); + res_r1_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_r1_t3_8x16b); + res_r1_t1_8x16b = _mm_add_epi16(res_r1_t1_8x16b, res_r1_t3_8x16b); + res_r1_t1_8x16b = _mm_srai_epi16(res_r1_t1_8x16b, 5); //shifting right by 5 bits. + + res_16x8b = _mm_packus_epi16(res_r1_t1_8x16b, res_r1_t1_8x16b); + res_16x8b = _mm_avg_epu8(res_16x8b,src_r1_vpel_16x8b); + + _mm_storel_epi64((__m128i *)(pu1_dst + dst_strd), res_16x8b); + + ht -= 2; + pu1_pred_horiz += src_strd << 1; + pu1_dst += dst_strd << 1; + pu1_tmp2 += 16; + } + while(ht > 0); + } + } + else // wd == 16 + { + //vertical q-pel filter + { + __m128i src_r0_16x8b, src_r1_16x8b, src_r2_16x8b, src_r3_16x8b; + __m128i src_r4_16x8b, src_r5_16x8b, src_r6_16x8b; + __m128i src_r0r1_16x8b, src_r2r3_16x8b, src_r4r5_16x8b; + + __m128i res_t0_8x16b, res_t1_8x16b, res_t2_8x16b, res_t3_8x16b; + __m128i res_16x8b; + + //epilogue: Load all the pred rows except sixth and seventh row for the + //first and second row processing. + src_r0_16x8b = _mm_loadu_si128((__m128i *)(pu1_pred_vert)); + pu1_pred_vert = pu1_pred_vert + src_strd; + src_r1_16x8b = _mm_loadu_si128((__m128i *)(pu1_pred_vert)); + pu1_pred_vert = pu1_pred_vert + src_strd; + src_r2_16x8b = _mm_loadu_si128((__m128i *)(pu1_pred_vert)); + pu1_pred_vert = pu1_pred_vert + src_strd; + src_r3_16x8b = _mm_loadu_si128((__m128i *)(pu1_pred_vert)); + pu1_pred_vert = pu1_pred_vert + src_strd; + src_r4_16x8b = _mm_loadu_si128((__m128i *)(pu1_pred_vert)); + pu1_pred_vert = pu1_pred_vert + src_strd; + + //Core Loop: Process all the rows. + do + { + src_r5_16x8b = _mm_loadu_si128((__m128i *)(pu1_pred_vert)); + src_r6_16x8b = _mm_loadu_si128((__m128i *)(pu1_pred_vert + src_strd)); + + src_r0r1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r1_16x8b); + src_r2r3_16x8b = _mm_unpacklo_epi8(src_r2_16x8b, src_r3_16x8b); + src_r4r5_16x8b = _mm_unpacklo_epi8(src_r4_16x8b, src_r5_16x8b); + + res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); + res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); + res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); + + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); + res_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_t3_8x16b); + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t3_8x16b); + res_t0_8x16b = _mm_srai_epi16(res_t1_8x16b, 5); //shifting right by 5 bits. + + src_r0r1_16x8b = _mm_unpackhi_epi8(src_r0_16x8b, src_r1_16x8b); + src_r2r3_16x8b = _mm_unpackhi_epi8(src_r2_16x8b, src_r3_16x8b); + src_r4r5_16x8b = _mm_unpackhi_epi8(src_r4_16x8b, src_r5_16x8b); + + res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); + res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); + res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); + + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); + res_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_t3_8x16b); + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t3_8x16b); + res_t1_8x16b = _mm_srai_epi16(res_t1_8x16b, 5); //shifting right by 5 bits. + + res_16x8b = _mm_packus_epi16(res_t0_8x16b, res_t1_8x16b); + + _mm_storeu_si128((__m128i *)(pu1_tmp1), res_16x8b); + + src_r0r1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r2_16x8b); + src_r2r3_16x8b = _mm_unpacklo_epi8(src_r3_16x8b, src_r4_16x8b); + src_r4r5_16x8b = _mm_unpacklo_epi8(src_r5_16x8b, src_r6_16x8b); + + res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); + res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); + res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); + + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); + res_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_t3_8x16b); + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t3_8x16b); + res_t0_8x16b = _mm_srai_epi16(res_t1_8x16b, 5); //shifting right by 5 bits. + + src_r0r1_16x8b = _mm_unpackhi_epi8(src_r1_16x8b, src_r2_16x8b); + src_r2r3_16x8b = _mm_unpackhi_epi8(src_r3_16x8b, src_r4_16x8b); + src_r4r5_16x8b = _mm_unpackhi_epi8(src_r5_16x8b, src_r6_16x8b); + + res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); + res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); + res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); + + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); + res_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_t3_8x16b); + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t3_8x16b); + res_t1_8x16b = _mm_srai_epi16(res_t1_8x16b, 5); //shifting right by 5 bits. + + res_16x8b = _mm_packus_epi16(res_t0_8x16b, res_t1_8x16b); + + _mm_storeu_si128((__m128i *)(pu1_tmp1 + 16), res_16x8b); + + src_r0_16x8b = src_r2_16x8b; + src_r1_16x8b = src_r3_16x8b; + src_r2_16x8b = src_r4_16x8b; + src_r3_16x8b = src_r5_16x8b; + src_r4_16x8b = src_r6_16x8b; + + ht_temp -= 2; + pu1_pred_vert += src_strd << 1; + pu1_tmp1 += 32; + } + while(ht_temp > 0); + } + //horizontal q-pel filter + { + __m128i src_r0_16x8b, src_r1_16x8b, src_r0_sht_16x8b, src_r1_sht_16x8b; + __m128i src_r0_t1_16x8b, src_r1_t1_16x8b; + __m128i src_vpel_16x8b; + + __m128i res_r0_t1_8x16b, res_r0_t2_8x16b, res_r0_t3_8x16b; + __m128i res_r1_t1_8x16b, res_r1_t2_8x16b, res_r1_t3_8x16b; + __m128i res_16x8b; + + //Row0 : a0 a1 a2 a3 a4 a5 a6 a7 a8 a9..... + //Row0 : b0 b1 b2 b3 b4 b5 b6 b7 b8 b9..... + //b0 is same a8. Similarly other bn pixels are same as a(n+8) pixels. + + do + { + src_r0_16x8b = _mm_loadu_si128((__m128i *)(pu1_pred_horiz)); //a0 a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 + src_r1_16x8b = _mm_loadu_si128((__m128i *)(pu1_pred_horiz + 8)); //b0 b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 + src_vpel_16x8b = _mm_loadu_si128((__m128i *)(pu1_tmp2)); + + src_r0_sht_16x8b = _mm_srli_si128(src_r0_16x8b, 1); //a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 0 + src_r1_sht_16x8b = _mm_srli_si128(src_r1_16x8b, 1); //b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 0 + + src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a0 a1 a1 a2 a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 + src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b0 b1 b1 b2 b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 + + res_r0_t1_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff0_1_16x8b); //a0*c0+a1*c1 a1*c0+a2*c1 a2*c0+a3*c1 a3*c0+a4*c1 + //a4*c0+a5*c1 a5*c0+a6*c1 a6*c0+a7*c1 a7*c0+a8*c1 + res_r1_t1_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff0_1_16x8b); //b0*c0+b1*c1 b1*c0+b2*c1 b2*c0+b3*c1 b3*c0+b4*c1 + //b4*c0+b5*c1 b5*c0+b6*c1 b6*c0+b7*c1 b7*c0+b8*c1 + + src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 2); //a2 a3 a4 a5 a6 a7 a8 a9....a15 0 0 + src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 2); //b2 b3 b4 b5 b6 b7 b8 b9....b15 0 0 + + src_r0_sht_16x8b = _mm_srli_si128(src_r0_sht_16x8b, 2); //a3 a4 a5 a6 a7 a8 a9....a15 0 0 0 + src_r1_sht_16x8b = _mm_srli_si128(src_r1_sht_16x8b, 2); //b3 b4 b5 b6 b7 b8 b9....b15 0 0 0 + + src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 a8 a9 a9 a10 + src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 a8 a9 a9 a10 + + res_r0_t2_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff2_3_16x8b); //a2*c2+a3*c3 a3*c2+a4*c3 a4*c2+a5*c3 a5*c2+a6*c3 + //a6*c2+a7*c3 a7*c2+a8*c3 a8*c2+a9*c3 a9*c2+a10*c3 + res_r1_t2_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff2_3_16x8b); //b2*c2+b3*c3 b3*c2+b4*c3 b2*c4+b5*c3 b5*c2+b6*c3 + //b6*c2+b7*c3 b7*c2+b8*c3 b8*c2+b9*c3 b9*c2+b10*c3 + + src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 2); //a4 a5 a6 a7 a8 a9....a15 0 0 0 0 + src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 2); //b4 b5 b6 b7 b8 b9....b15 0 0 0 0 + + src_r0_sht_16x8b = _mm_srli_si128(src_r0_sht_16x8b, 2); //a5 a6 a7 a8 a9....a15 0 0 0 0 0 + src_r1_sht_16x8b = _mm_srli_si128(src_r1_sht_16x8b, 2); //b5 b6 b7 b8 b9....b15 0 0 0 0 0 + + src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a4 a5 a5 a6 a6 a7 a7 a8 a8 a9 a9 a10 a10 a11 a11 a12 + src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b4 b5 b5 b6 b6 b7 b7 b8 b8 b9 b9 b10 b10 b11 b11 b12 + + res_r0_t3_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff4_5_16x8b); //a4*c4+a5*c5 a5*c4+a6*c5 a6*c4+a7*c5 a7*c4+a8*c5 + //a8*c4+a9*c5 a9*c4+a10*c5 a10*c4+a11*c5 a11*c4+a12*c5 + res_r1_t3_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff4_5_16x8b); //b4*c4+b5*c5 b5*c4+b6*c5 b6*c4+b7*c5 b7*c4+b8*c5 + //b8*c4+b9*c5 b9*c4+b10*c5 b10*c4+b11*c5 b11*c4+b12*c5 + res_r0_t1_8x16b = _mm_add_epi16(res_r0_t1_8x16b, res_r0_t2_8x16b); + res_r0_t3_8x16b = _mm_add_epi16(const_val16_8x16b, res_r0_t3_8x16b); + res_r0_t1_8x16b = _mm_add_epi16(res_r0_t1_8x16b, res_r0_t3_8x16b); + res_r0_t1_8x16b = _mm_srai_epi16(res_r0_t1_8x16b, 5); //shifting right by 5 bits. + + res_r1_t1_8x16b = _mm_add_epi16(res_r1_t1_8x16b, res_r1_t2_8x16b); + res_r1_t1_8x16b = _mm_add_epi16(res_r1_t1_8x16b, res_r1_t3_8x16b); + res_r1_t1_8x16b = _mm_add_epi16(res_r1_t1_8x16b, const_val16_8x16b); + res_r1_t1_8x16b = _mm_srai_epi16(res_r1_t1_8x16b, 5); //shifting right by 5 bits. + + res_16x8b = _mm_packus_epi16(res_r0_t1_8x16b, res_r1_t1_8x16b); + + res_16x8b = _mm_avg_epu8(res_16x8b, src_vpel_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst), res_16x8b); + + ht --; + pu1_pred_horiz += src_strd; + pu1_dst += dst_strd; + pu1_tmp2 += 16; + } + while(ht > 0); + } + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_inter_pred_luma_horz_qpel_vert_hpel_ssse3 */ +/* */ +/* Description : This function implements a six-tap filter vertically and */ +/* horizontally on ht x wd block separately and averages */ +/* the two sets of values to calculate values at (1/4,1/2), */ +/* or (3/4, 1/2) as mentioned in sec. 8.4.2.2.1 titled */ +/* "Luma sample interpolation process". (ht,wd) can be */ +/* (4,4), (8,4), (4,8), (8,8), (16,8), (8,16) or (16,16). */ +/* */ +/* Inputs : puc_src - pointer to source */ +/* puc_dst - pointer to destination */ +/* src_strd - stride for source */ +/* dst_strd - stride for destination */ +/* ht - height of the block */ +/* wd - width of the block */ +/* pu1_tmp - pointer to temporary buffer */ +/* dydx - x and y reference offset for q-pel */ +/* calculations */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes */ +/* 13 02 2015 Kaushik Initial Version */ +/* Senthoor */ +/* */ +/*****************************************************************************/ +void ih264_inter_pred_luma_horz_qpel_vert_hpel_ssse3(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ht, + WORD32 wd, + UWORD8* pu1_tmp, + WORD32 dydx) +{ + WORD32 ht_temp; + WORD32 x_offset; + WORD32 off0,off1, off2, off3, off4, off5; + WORD16 *pi2_temp1,*pi2_temp2,*pi2_temp3; + + ht_temp = ht; + x_offset = dydx & 0x3; + pi2_temp1 = (WORD16 *)pu1_tmp; + pi2_temp2 = pi2_temp1; + pi2_temp3 = pi2_temp1 + (x_offset >> 1); + + pu1_src -= 2 * src_strd; + pu1_src -= 2; + pi2_temp3 += 2; + //the filter input starts from x[-2] (till x[3]) + + if(wd == 4) + { + //vertical half-pel + { + __m128i src_r0_16x8b, src_r1_16x8b, src_r2_16x8b, src_r3_16x8b, src_r4_16x8b; + __m128i src_r5_16x8b, src_r6_16x8b; + __m128i src_r0r1_16x8b, src_r2r3_16x8b, src_r4r5_16x8b; + + __m128i res_t1_8x16b, res_t2_8x16b, res_t3_8x16b; + + __m128i coeff0_1_16x8b, coeff2_3_16x8b, coeff4_5_16x8b; + + coeff0_1_16x8b = _mm_set1_epi32(0xFB01FB01); //c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 + coeff2_3_16x8b = _mm_set1_epi32(0x14141414); //c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 + coeff4_5_16x8b = _mm_set1_epi32(0x01FB01FB); //c4 c5 c5 c5 c4 c5 c5 c5 c4 c5 c5 c5 c4 c5 c5 c5 + //c0 = c5 = 1, c1 = c4 = -5, c2 = c3 = 20 + off0 = -((src_strd << 2) + src_strd) + 8; + off1 = -(src_strd << 2) + 8; + off2 = -((src_strd << 1) + src_strd) + 8; + off3 = -(src_strd << 1) + 8; + off4 = -src_strd + 8; + off5 = 8; + + //epilogue: Load all the pred rows except sixth and seventh row for the + //first and second row processing. + src_r0_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src)); + pu1_src = pu1_src + src_strd; + + src_r1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src)); + pu1_src = pu1_src + src_strd; + + src_r2_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src)); + pu1_src = pu1_src + src_strd; + + src_r3_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src)); + pu1_src = pu1_src + src_strd; + + src_r4_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src)); + pu1_src = pu1_src + src_strd; + + //Core Loop: Process all the rows. + do + { + src_r5_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src)); + + src_r0r1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r1_16x8b); + src_r2r3_16x8b = _mm_unpacklo_epi8(src_r2_16x8b, src_r3_16x8b); + src_r4r5_16x8b = _mm_unpacklo_epi8(src_r4_16x8b, src_r5_16x8b); + + res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); + res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); + res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); + + res_t1_8x16b = _mm_add_epi16(res_t2_8x16b, res_t1_8x16b); + res_t1_8x16b = _mm_add_epi16(res_t3_8x16b, res_t1_8x16b); + + _mm_storeu_si128((__m128i *)(pi2_temp1), res_t1_8x16b); + + pi2_temp1[8] = pu1_src[off0] + pu1_src[off5] + - (pu1_src[off1] + pu1_src[off4]) + + ((pu1_src[off2] + pu1_src[off3] - pu1_src[off1] - pu1_src[off4]) << 2) + + ((pu1_src[off2] + pu1_src[off3]) << 4); + + pu1_src = pu1_src + src_strd; + pi2_temp1 = pi2_temp1 + 9; + + src_r6_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src)); + + src_r0r1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r2_16x8b); + src_r2r3_16x8b = _mm_unpacklo_epi8(src_r3_16x8b, src_r4_16x8b); + src_r4r5_16x8b = _mm_unpacklo_epi8(src_r5_16x8b, src_r6_16x8b); + + res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); + res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); + res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); + + res_t1_8x16b = _mm_add_epi16(res_t2_8x16b, res_t1_8x16b); + res_t1_8x16b = _mm_add_epi16(res_t3_8x16b, res_t1_8x16b); + + _mm_storeu_si128((__m128i *)(pi2_temp1), res_t1_8x16b); + + pi2_temp1[8] = pu1_src[off0] + pu1_src[off5] + - (pu1_src[off1] + pu1_src[off4]) + + ((pu1_src[off2] + pu1_src[off3] - pu1_src[off1] - pu1_src[off4]) << 2) + + ((pu1_src[off2] + pu1_src[off3]) << 4); + + ht_temp -= 2; + pu1_src = pu1_src + src_strd; + pi2_temp1 = pi2_temp1 + 9; + + src_r0_16x8b = src_r2_16x8b; + src_r1_16x8b = src_r3_16x8b; + src_r2_16x8b = src_r4_16x8b; + src_r3_16x8b = src_r5_16x8b; + src_r4_16x8b = src_r6_16x8b; + } + while(ht_temp > 0); + } + + //horizontal q-pel + { + __m128i src_r0_8x16b, src_r1_8x16b, src_r2_8x16b; + __m128i src_r3_8x16b, src_r4_8x16b, src_r5_8x16b; + __m128i src_r0r1_c0_8x16b, src_r2r3_c0_8x16b, src_r4r5_c0_8x16b; + __m128i src_hpel_16x8b, src_hpel_8x16b; + + __m128i res_t1_4x32b, res_t2_4x32b, res_t3_4x32b; + __m128i res_8x16b, res_16x8b; + + __m128i coeff0_1_8x16b, coeff2_3_8x16b, coeff4_5_8x16b; + __m128i const_val512_4x32b, const_val16_8x16b; + + coeff0_1_8x16b = _mm_set1_epi32(0xFFFB0001); + coeff2_3_8x16b = _mm_set1_epi32(0x00140014); + coeff4_5_8x16b = _mm_set1_epi32(0x0001FFFB); + + const_val512_4x32b = _mm_set1_epi32(512); + const_val16_8x16b = _mm_set1_epi16(16); + + do + { + src_r0_8x16b = _mm_loadl_epi64((__m128i *)(pi2_temp2)); + src_r1_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 1)); + src_r2_8x16b = _mm_srli_si128(src_r1_8x16b, 2); + src_r3_8x16b = _mm_srli_si128(src_r1_8x16b, 4); + src_r4_8x16b = _mm_srli_si128(src_r1_8x16b, 6); + src_r5_8x16b = _mm_srli_si128(src_r1_8x16b, 8); + + src_r0r1_c0_8x16b = _mm_unpacklo_epi16(src_r0_8x16b, src_r1_8x16b); + src_r2r3_c0_8x16b = _mm_unpacklo_epi16(src_r2_8x16b, src_r3_8x16b); + src_r4r5_c0_8x16b = _mm_unpacklo_epi16(src_r4_8x16b, src_r5_8x16b); + + res_t1_4x32b = _mm_madd_epi16(src_r0r1_c0_8x16b, coeff0_1_8x16b); + res_t2_4x32b = _mm_madd_epi16(src_r2r3_c0_8x16b, coeff2_3_8x16b); + res_t3_4x32b = _mm_madd_epi16(src_r4r5_c0_8x16b, coeff4_5_8x16b); + + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); + res_t3_4x32b = _mm_add_epi32(const_val512_4x32b, res_t3_4x32b); + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); + res_t1_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); + + res_8x16b = _mm_packs_epi32(res_t1_4x32b, res_t1_4x32b); + res_16x8b = _mm_packus_epi16(res_8x16b, res_8x16b); + + src_hpel_8x16b = _mm_loadl_epi64((__m128i *)(pi2_temp3)); + src_hpel_8x16b = _mm_add_epi16(src_hpel_8x16b, const_val16_8x16b); + src_hpel_8x16b = _mm_srai_epi16(src_hpel_8x16b, 5); //shifting right by 5 bits. + src_hpel_16x8b = _mm_packus_epi16(src_hpel_8x16b, src_hpel_8x16b); + + res_16x8b = _mm_avg_epu8(res_16x8b, src_hpel_16x8b); + + *((WORD32 *)(pu1_dst)) = _mm_cvtsi128_si32(res_16x8b); + + ht--; + pi2_temp2 = pi2_temp2 + 4 + 5; + pi2_temp3 = pi2_temp3 + 4 + 5; + pu1_dst = pu1_dst + dst_strd; + } + while(ht > 0); + } + } + else if(wd == 8) + { + // vertical half-pel + { + __m128i src_r0_16x8b, src_r1_16x8b, src_r2_16x8b, src_r3_16x8b, src_r4_16x8b; + __m128i src_r5_16x8b, src_r6_16x8b; + __m128i src_r0r1_16x8b, src_r2r3_16x8b, src_r4r5_16x8b; + + __m128i res_t1_8x16b, res_t2_8x16b, res_t3_8x16b; + + __m128i coeff0_1_16x8b, coeff2_3_16x8b, coeff4_5_16x8b; + + coeff0_1_16x8b = _mm_set1_epi32(0xFB01FB01); //c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 + coeff2_3_16x8b = _mm_set1_epi32(0x14141414); //c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 + coeff4_5_16x8b = _mm_set1_epi32(0x01FB01FB); //c4 c5 c5 c5 c4 c5 c5 c5 c4 c5 c5 c5 c4 c5 c5 c5 + + //epilogue: Load all the pred rows except sixth and seventh row for the + //first and second row processing. + src_r0_16x8b = _mm_loadu_si128((__m128i *)(pu1_src)); + pu1_src = pu1_src + src_strd; + + src_r1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src)); + pu1_src = pu1_src + src_strd; + + src_r2_16x8b = _mm_loadu_si128((__m128i *)(pu1_src)); + pu1_src = pu1_src + src_strd; + + src_r3_16x8b = _mm_loadu_si128((__m128i *)(pu1_src)); + pu1_src = pu1_src + src_strd; + + src_r4_16x8b = _mm_loadu_si128((__m128i *)(pu1_src)); + pu1_src = pu1_src + src_strd; + + //Core Loop: Process all the rows. + do + { + src_r5_16x8b = _mm_loadu_si128((__m128i *)(pu1_src)); + src_r6_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd)); + + src_r0r1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r1_16x8b); + src_r2r3_16x8b = _mm_unpacklo_epi8(src_r2_16x8b, src_r3_16x8b); + src_r4r5_16x8b = _mm_unpacklo_epi8(src_r4_16x8b, src_r5_16x8b); + + res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); + res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); + res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); + + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); + res_t1_8x16b = _mm_add_epi16(res_t3_8x16b, res_t1_8x16b); + + _mm_storeu_si128((__m128i *)(pi2_temp1), res_t1_8x16b); + + src_r0r1_16x8b = _mm_unpackhi_epi8(src_r0_16x8b, src_r1_16x8b); + src_r2r3_16x8b = _mm_unpackhi_epi8(src_r2_16x8b, src_r3_16x8b); + src_r4r5_16x8b = _mm_unpackhi_epi8(src_r4_16x8b, src_r5_16x8b); + + res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); + res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); + res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); + + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); + res_t1_8x16b = _mm_add_epi16(res_t3_8x16b, res_t1_8x16b); + + _mm_storeu_si128((__m128i *)(pi2_temp1 + 8), res_t1_8x16b); + + src_r0r1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r2_16x8b); + src_r2r3_16x8b = _mm_unpacklo_epi8(src_r3_16x8b, src_r4_16x8b); + src_r4r5_16x8b = _mm_unpacklo_epi8(src_r5_16x8b, src_r6_16x8b); + + res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); + res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); + res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); + + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); + res_t1_8x16b = _mm_add_epi16(res_t3_8x16b, res_t1_8x16b); + + _mm_storeu_si128((__m128i *)(pi2_temp1 + 8 + 5), res_t1_8x16b); + + src_r0r1_16x8b = _mm_unpackhi_epi8(src_r1_16x8b, src_r2_16x8b); + src_r2r3_16x8b = _mm_unpackhi_epi8(src_r3_16x8b, src_r4_16x8b); + src_r4r5_16x8b = _mm_unpackhi_epi8(src_r5_16x8b, src_r6_16x8b); + + res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); + res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); + res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); + + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); + res_t1_8x16b = _mm_add_epi16(res_t3_8x16b, res_t1_8x16b); + + _mm_storeu_si128((__m128i *)(pi2_temp1 + 8 + 5 + 8), res_t1_8x16b); + + src_r0_16x8b = src_r2_16x8b; + src_r1_16x8b = src_r3_16x8b; + src_r2_16x8b = src_r4_16x8b; + src_r3_16x8b = src_r5_16x8b; + src_r4_16x8b = src_r6_16x8b; + + ht_temp -= 2; + pu1_src = pu1_src + (src_strd << 1); + pi2_temp1 = pi2_temp1 + (13 << 1); + } + while(ht_temp > 0); + } + // horizontal q-pel + { + __m128i src_r0_8x16b, src_r1_8x16b, src_r2_8x16b, src_r3_8x16b; + __m128i src_r4_8x16b, src_r5_8x16b; + __m128i src_r0r1_c0_8x16b, src_r2r3_c0_8x16b, src_r4r5_c0_8x16b; + __m128i src_r0r1_c1_8x16b, src_r2r3_c1_8x16b, src_r4r5_c1_8x16b; + __m128i src_hpel_8x16b, src_hpel_16x8b; + + __m128i res_t0_4x32b, res_t1_4x32b, res_t2_4x32b, res_t3_4x32b; + __m128i res_8x16b, res_16x8b; + + __m128i coeff0_1_8x16b, coeff2_3_8x16b, coeff4_5_8x16b; + __m128i const_val512_4x32b, const_val16_8x16b; + + coeff0_1_8x16b = _mm_set1_epi32(0xFFFB0001); + coeff2_3_8x16b = _mm_set1_epi32(0x00140014); + coeff4_5_8x16b = _mm_set1_epi32(0x0001FFFB); + + const_val512_4x32b = _mm_set1_epi32(512); + const_val16_8x16b = _mm_set1_epi16(16); + + do + { + src_r0_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2)); + src_r1_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 1)); + src_r2_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 2)); + src_r3_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 3)); + src_r4_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 4)); + src_r5_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 5)); + + src_r0r1_c0_8x16b = _mm_unpacklo_epi16(src_r0_8x16b, src_r1_8x16b); + src_r2r3_c0_8x16b = _mm_unpacklo_epi16(src_r2_8x16b, src_r3_8x16b); + src_r4r5_c0_8x16b = _mm_unpacklo_epi16(src_r4_8x16b, src_r5_8x16b); + + src_r0r1_c1_8x16b = _mm_unpackhi_epi16(src_r0_8x16b, src_r1_8x16b); + src_r2r3_c1_8x16b = _mm_unpackhi_epi16(src_r2_8x16b, src_r3_8x16b); + src_r4r5_c1_8x16b = _mm_unpackhi_epi16(src_r4_8x16b, src_r5_8x16b); + + res_t1_4x32b = _mm_madd_epi16(src_r0r1_c0_8x16b, coeff0_1_8x16b); + res_t2_4x32b = _mm_madd_epi16(src_r2r3_c0_8x16b, coeff2_3_8x16b); + res_t3_4x32b = _mm_madd_epi16(src_r4r5_c0_8x16b, coeff4_5_8x16b); + + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); + res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); + + res_t0_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); + + res_t1_4x32b = _mm_madd_epi16(src_r0r1_c1_8x16b, coeff0_1_8x16b); + res_t2_4x32b = _mm_madd_epi16(src_r2r3_c1_8x16b, coeff2_3_8x16b); + res_t3_4x32b = _mm_madd_epi16(src_r4r5_c1_8x16b, coeff4_5_8x16b); + + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); + res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); + + res_t1_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); + + res_8x16b = _mm_packs_epi32(res_t0_4x32b, res_t1_4x32b); + res_16x8b = _mm_packus_epi16(res_8x16b, res_8x16b); + + src_hpel_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp3)); + src_hpel_8x16b = _mm_add_epi16(src_hpel_8x16b, const_val16_8x16b); + src_hpel_8x16b = _mm_srai_epi16(src_hpel_8x16b, 5); //shifting right by 5 bits. + src_hpel_16x8b = _mm_packus_epi16(src_hpel_8x16b, src_hpel_8x16b); + + res_16x8b = _mm_avg_epu8(res_16x8b, src_hpel_16x8b); + + _mm_storel_epi64((__m128i *)(pu1_dst), res_16x8b); + + ht--; + pi2_temp2 = pi2_temp2 + 8 + 5; + pi2_temp3 = pi2_temp3 + 8 + 5; + pu1_dst = pu1_dst + dst_strd; + } + while(ht > 0); + } + } + else // wd == 16 + { + // vertical half-pel + { + __m128i src_r0_16x8b, src_r1_16x8b, src_r2_16x8b, src_r3_16x8b; + __m128i src_r4_16x8b, src_r5_16x8b; + __m128i src_r0_c2_16x8b, src_r1_c2_16x8b, src_r2_c2_16x8b, src_r3_c2_16x8b; + __m128i src_r4_c2_16x8b, src_r5_c2_16x8b; + __m128i src_r0r1_16x8b, src_r2r3_16x8b, src_r4r5_16x8b; + + __m128i res_t1_8x16b, res_t2_8x16b, res_t3_8x16b; + + __m128i coeff0_1_16x8b,coeff2_3_16x8b,coeff4_5_16x8b; + + coeff0_1_16x8b = _mm_set1_epi32(0xFB01FB01); //c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 + coeff2_3_16x8b = _mm_set1_epi32(0x14141414); //c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 + coeff4_5_16x8b = _mm_set1_epi32(0x01FB01FB); //c4 c5 c5 c5 c4 c5 c5 c5 c4 c5 c5 c5 c4 c5 c5 c5 + + src_r0_16x8b = _mm_loadu_si128((__m128i *)(pu1_src)); + src_r0_c2_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + 16)); + pu1_src = pu1_src + src_strd; + src_r1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src)); + src_r1_c2_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + 16)); + pu1_src = pu1_src + src_strd; + src_r2_16x8b = _mm_loadu_si128((__m128i *)(pu1_src)); + src_r2_c2_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + 16)); + pu1_src = pu1_src + src_strd; + src_r3_16x8b = _mm_loadu_si128((__m128i *)(pu1_src)); + src_r3_c2_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + 16)); + pu1_src = pu1_src + src_strd; + src_r4_16x8b = _mm_loadu_si128((__m128i *)(pu1_src)); + src_r4_c2_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + 16)); + pu1_src = pu1_src + src_strd; + + //Core Loop: Process all the rows. + do + { + src_r5_16x8b = _mm_loadu_si128((__m128i *)(pu1_src)); + src_r5_c2_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + 16)); + + src_r0r1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r1_16x8b); + src_r2r3_16x8b = _mm_unpacklo_epi8(src_r2_16x8b, src_r3_16x8b); + src_r4r5_16x8b = _mm_unpacklo_epi8(src_r4_16x8b, src_r5_16x8b); + + res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); + res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); + res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); + + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); + res_t1_8x16b = _mm_add_epi16(res_t3_8x16b, res_t1_8x16b); + + _mm_storeu_si128((__m128i *)(pi2_temp1), res_t1_8x16b); + + src_r0r1_16x8b = _mm_unpackhi_epi8(src_r0_16x8b, src_r1_16x8b); + src_r2r3_16x8b = _mm_unpackhi_epi8(src_r2_16x8b, src_r3_16x8b); + src_r4r5_16x8b = _mm_unpackhi_epi8(src_r4_16x8b, src_r5_16x8b); + + res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); + res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); + res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); + + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); + res_t1_8x16b = _mm_add_epi16(res_t3_8x16b, res_t1_8x16b); + + _mm_storeu_si128((__m128i *)(pi2_temp1 + 8), res_t1_8x16b); + + src_r0r1_16x8b = _mm_unpacklo_epi8(src_r0_c2_16x8b, src_r1_c2_16x8b); + src_r2r3_16x8b = _mm_unpacklo_epi8(src_r2_c2_16x8b, src_r3_c2_16x8b); + src_r4r5_16x8b = _mm_unpacklo_epi8(src_r4_c2_16x8b, src_r5_c2_16x8b); + + res_t1_8x16b = _mm_maddubs_epi16(src_r0r1_16x8b, coeff0_1_16x8b); + res_t2_8x16b = _mm_maddubs_epi16(src_r2r3_16x8b, coeff2_3_16x8b); + res_t3_8x16b = _mm_maddubs_epi16(src_r4r5_16x8b, coeff4_5_16x8b); + + res_t1_8x16b = _mm_add_epi16(res_t1_8x16b, res_t2_8x16b); + res_t1_8x16b = _mm_add_epi16(res_t3_8x16b, res_t1_8x16b); + + _mm_storeu_si128((__m128i *)(pi2_temp1 + 16), res_t1_8x16b); + + src_r0_16x8b = src_r1_16x8b; + src_r1_16x8b = src_r2_16x8b; + src_r2_16x8b = src_r3_16x8b; + src_r3_16x8b = src_r4_16x8b; + src_r4_16x8b = src_r5_16x8b; + + src_r0_c2_16x8b = src_r1_c2_16x8b; + src_r1_c2_16x8b = src_r2_c2_16x8b; + src_r2_c2_16x8b = src_r3_c2_16x8b; + src_r3_c2_16x8b = src_r4_c2_16x8b; + src_r4_c2_16x8b = src_r5_c2_16x8b; + + ht_temp--; + pu1_src = pu1_src + src_strd; + pi2_temp1 = pi2_temp1 + 16 + 5; + } + while(ht_temp > 0); + } + // horizontal q-pel + { + __m128i src_r0_8x16b, src_r1_8x16b, src_r2_8x16b, src_r3_8x16b; + __m128i src_r4_8x16b, src_r5_8x16b; + __m128i src_r0r1_8x16b, src_r2r3_8x16b, src_r4r5_8x16b; + __m128i src_hpel1_8x16b, src_hpel2_8x16b, src_hpel_16x8b; + + __m128i res_t0_4x32b, res_t1_4x32b, res_t2_4x32b, res_t3_4x32b; + __m128i res_c0_8x16b, res_c1_8x16b, res_16x8b; + + __m128i coeff0_1_8x16b, coeff2_3_8x16b, coeff4_5_8x16b; + __m128i const_val512_4x32b, const_val16_8x16b; + + coeff0_1_8x16b = _mm_set1_epi32(0xFFFB0001); + coeff2_3_8x16b = _mm_set1_epi32(0x00140014); + coeff4_5_8x16b = _mm_set1_epi32(0x0001FFFB); + + const_val512_4x32b = _mm_set1_epi32(512); + const_val16_8x16b = _mm_set1_epi16(16); + + do + { + src_r0_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2)); + src_r1_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 1)); + src_r2_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 2)); + src_r3_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 3)); + src_r4_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 4)); + src_r5_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 5)); + + src_r0r1_8x16b = _mm_unpacklo_epi16(src_r0_8x16b, src_r1_8x16b); + src_r2r3_8x16b = _mm_unpacklo_epi16(src_r2_8x16b, src_r3_8x16b); + src_r4r5_8x16b = _mm_unpacklo_epi16(src_r4_8x16b, src_r5_8x16b); + + res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); + res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); + res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); + + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); + res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); + res_t0_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); + + src_r0r1_8x16b = _mm_unpackhi_epi16(src_r0_8x16b, src_r1_8x16b); + src_r2r3_8x16b = _mm_unpackhi_epi16(src_r2_8x16b, src_r3_8x16b); + src_r4r5_8x16b = _mm_unpackhi_epi16(src_r4_8x16b, src_r5_8x16b); + + res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); + res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); + res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); + + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); + res_t3_4x32b = _mm_add_epi32(const_val512_4x32b, res_t3_4x32b); + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); + res_t1_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); + + res_c0_8x16b = _mm_packs_epi32(res_t0_4x32b, res_t1_4x32b); + + src_r0_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 8)); + src_r1_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 8 + 1)); + src_r2_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 8 + 2)); + src_r3_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 8 + 3)); + src_r4_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 8 + 4)); + src_r5_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 8 + 5)); + + src_r0r1_8x16b = _mm_unpacklo_epi16(src_r0_8x16b, src_r1_8x16b); + src_r2r3_8x16b = _mm_unpacklo_epi16(src_r2_8x16b, src_r3_8x16b); + src_r4r5_8x16b = _mm_unpacklo_epi16(src_r4_8x16b, src_r5_8x16b); + + res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); + res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); + res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); + + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); + res_t3_4x32b = _mm_add_epi32(const_val512_4x32b, res_t3_4x32b); + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); + res_t0_4x32b = _mm_srai_epi32(res_t1_4x32b ,10); + + src_r0r1_8x16b = _mm_unpackhi_epi16(src_r0_8x16b, src_r1_8x16b); + src_r2r3_8x16b = _mm_unpackhi_epi16(src_r2_8x16b, src_r3_8x16b); + src_r4r5_8x16b = _mm_unpackhi_epi16(src_r4_8x16b, src_r5_8x16b); + + res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); + res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); + res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); + + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); + res_t3_4x32b = _mm_add_epi32(const_val512_4x32b, res_t3_4x32b); + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); + res_t1_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); + + res_c1_8x16b = _mm_packs_epi32(res_t0_4x32b, res_t1_4x32b); + res_16x8b = _mm_packus_epi16(res_c0_8x16b, res_c1_8x16b); + + src_hpel1_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp3)); + src_hpel1_8x16b = _mm_add_epi16(src_hpel1_8x16b, const_val16_8x16b); + src_hpel1_8x16b = _mm_srai_epi16(src_hpel1_8x16b, 5); //shifting right by 5 bits. + + src_hpel2_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp3 + 8)); + src_hpel2_8x16b = _mm_add_epi16(src_hpel2_8x16b, const_val16_8x16b); + src_hpel2_8x16b = _mm_srai_epi16(src_hpel2_8x16b, 5); //shifting right by 5 bits. + + src_hpel_16x8b = _mm_packus_epi16(src_hpel1_8x16b, src_hpel2_8x16b); + res_16x8b = _mm_avg_epu8(res_16x8b, src_hpel_16x8b); + + _mm_storeu_si128((__m128i *)(pu1_dst), res_16x8b); + + ht--; + pi2_temp2 = pi2_temp2 + 16 + 5; + pi2_temp3 = pi2_temp3 + 16 + 5; + pu1_dst = pu1_dst + dst_strd; + } + while(ht > 0); + } + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_inter_pred_luma_horz_hpel_vert_qpel_ssse3 */ +/* */ +/* Description : This function implements a six-tap filter vertically and */ +/* horizontally on ht x wd block separately and averages */ +/* the two sets of values to calculate values at (1/2,1/4), */ +/* or (1/2, 3/4) as mentioned in sec. 8.4.2.2.1 titled */ +/* "Luma sample interpolation process". (ht,wd) can be */ +/* (4,4), (8,4), (4,8), (8,8), (16,8), (8,16) or (16,16). */ +/* */ +/* Inputs : puc_src - pointer to source */ +/* puc_dst - pointer to destination */ +/* src_strd - stride for source */ +/* dst_strd - stride for destination */ +/* ht - height of the block */ +/* wd - width of the block */ +/* pu1_tmp - pointer to temporary buffer */ +/* dydx - x and y reference offset for q-pel */ +/* calculations */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes */ +/* 13 02 2015 Kaushik Initial Version */ +/* Senthoor */ +/* */ +/*****************************************************************************/ +void ih264_inter_pred_luma_horz_hpel_vert_qpel_ssse3(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ht, + WORD32 wd, + UWORD8* pu1_tmp, + WORD32 dydx) +{ + WORD32 ht_temp; + WORD32 y_offset; + WORD16 *pi2_temp1,*pi2_temp2,*pi2_temp3; + + y_offset = (dydx & 0xf) >> 2; + pi2_temp1 = (WORD16 *)pu1_tmp; + pi2_temp2 = pi2_temp1; + pi2_temp3 = pi2_temp1 + (y_offset >> 1) * wd; + + ht_temp = ht + 5; + pu1_src -= src_strd << 1; + pu1_src -= 2; + pi2_temp3 += wd << 1; + //the filter input starts from x[-2] (till x[3]) + + if(wd == 4) + { + // horizontal half-pel + { + __m128i src_r0_16x8b, src_r1_16x8b, src_r0r1_t1_16x8b; + __m128i src_r0_sht_16x8b, src_r1_sht_16x8b; + __m128i res_r0r1_t1_8x16b, res_r0r1_t2_8x16b, res_r0r1_t3_8x16b; + __m128i coeff0_1_16x8b, coeff2_3_16x8b, coeff4_5_16x8b; + + coeff0_1_16x8b = _mm_set1_epi32(0xFB01FB01); //c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 + coeff2_3_16x8b = _mm_set1_epi32(0x14141414); //c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 + coeff4_5_16x8b = _mm_set1_epi32(0x01FB01FB); //c4 c5 c5 c5 c4 c5 c5 c5 c4 c5 c5 c5 c4 c5 c5 c5 + + //Row0 : a0 a1 a2 a3 a4 a5 a6 a7 a8 a9..... + //Row1 : b0 b1 b2 b3 b4 b5 b6 b7 b8 b9..... + + do + { + src_r0_16x8b = _mm_loadu_si128((__m128i *)pu1_src); //a0 a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 + src_r1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd)); //b0 b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 + + src_r0_sht_16x8b = _mm_srli_si128(src_r0_16x8b, 1); //a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 0 + src_r1_sht_16x8b = _mm_srli_si128(src_r1_16x8b, 1); //b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 0 + + src_r0_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a0 a1 a1 a2 a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 + src_r1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b0 b1 b1 b2 b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 + + src_r0r1_t1_16x8b = _mm_unpacklo_epi64(src_r0_16x8b, src_r1_16x8b); //a0 a1 a1 a2 a2 a3 a3 a4 b0 b1 b1 b2 b2 b3 b3 b4 + res_r0r1_t1_8x16b = _mm_maddubs_epi16(src_r0r1_t1_16x8b, coeff0_1_16x8b); //a0*c0+a1*c1 a1*c0+a2*c1 a2*c0+a3*c1 a3*c0+a4*c1 + //b0*c0+b1*c1 b1*c0+b2*c1 b2*c0+b3*c1 b3*c0+b4*c1 + + src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 4); //a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 0 0 0 0 + src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 4); //b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 0 0 0 0 + + src_r0r1_t1_16x8b = _mm_unpacklo_epi64(src_r0_16x8b, src_r1_16x8b); //a2 a3 a3 a4 a4 a5 a5 a6 b2 b3 b3 b4 b4 b5 b5 b6 + res_r0r1_t2_8x16b = _mm_maddubs_epi16(src_r0r1_t1_16x8b, coeff2_3_16x8b); //a2*c2+a3*c3 a3*c2+a4*c3 a4*c2+a5*c3 a5*c2+a6*c3 + //b2*c2+b3*c3 b3*c2+b4*c3 b4*c2+b5*c3 b5*c2+b6*c3 + + src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 4); //a4 a5 a5 a6 a6 a7 a7 a8 0 0 0 0 0 0 0 0 + src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 4); //b4 b5 b5 b6 b6 b7 b7 b8 0 0 0 0 0 0 0 0 + + src_r0r1_t1_16x8b = _mm_unpacklo_epi64(src_r0_16x8b, src_r1_16x8b); //a4 a5 a5 a6 a6 a7 a7 a8 b4 b5 b5 b6 b6 b7 b7 b8 + res_r0r1_t3_8x16b = _mm_maddubs_epi16(src_r0r1_t1_16x8b, coeff4_5_16x8b); //a4*c4+a5*c5 a5*c4+a6*c5 a6*c4+a7*c5 a7*c4+a8*c5 + //b4*c4+b5*c5 b5*c4+b6*c5 b4*c6+b7*c5 b7*c4+b8*c5 + + res_r0r1_t1_8x16b = _mm_add_epi16(res_r0r1_t1_8x16b, res_r0r1_t2_8x16b); + res_r0r1_t1_8x16b = _mm_add_epi16(res_r0r1_t1_8x16b, res_r0r1_t3_8x16b); + + + _mm_storeu_si128((__m128i *)(pi2_temp1), res_r0r1_t1_8x16b); + + ht_temp -= 2; + pu1_src = pu1_src + (src_strd << 1); + pi2_temp1 = pi2_temp1 + (4 << 1); + } + while(ht_temp > 0); + } + // vertical q-pel + { + __m128i src_r0_8x16b, src_r1_8x16b, src_r2_8x16b, src_r3_8x16b; + __m128i src_r4_8x16b, src_r5_8x16b, src_r6_8x16b; + __m128i src_r0r1_c0_8x16b, src_r2r3_c0_8x16b, src_r4r5_c0_8x16b; + __m128i src_hpel_16x8b, src_hpel_8x16b; + + __m128i res_t0_4x32b, res_t1_4x32b, res_t2_4x32b, res_t3_4x32b; + __m128i res_8x16b, res_16x8b; + + __m128i coeff0_1_8x16b, coeff2_3_8x16b, coeff4_5_8x16b; + __m128i const_val512_4x32b, const_val16_8x16b; + + const_val512_4x32b = _mm_set1_epi32(512); + const_val16_8x16b = _mm_set1_epi16(16); + + coeff0_1_8x16b = _mm_set1_epi32(0xFFFB0001); + coeff2_3_8x16b = _mm_set1_epi32(0x00140014); + coeff4_5_8x16b = _mm_set1_epi32(0x0001FFFB); + + src_r0_8x16b = _mm_loadl_epi64((__m128i *)(pi2_temp2)); + src_r1_8x16b = _mm_loadl_epi64((__m128i *)(pi2_temp2 + 4)); + src_r2_8x16b = _mm_loadl_epi64((__m128i *)(pi2_temp2 + 8)); + src_r3_8x16b = _mm_loadl_epi64((__m128i *)(pi2_temp2 + 12)); + src_r4_8x16b = _mm_loadl_epi64((__m128i *)(pi2_temp2 + 16)); + pi2_temp2 += 20; + + do + { + src_r5_8x16b = _mm_loadl_epi64((__m128i *)(pi2_temp2)); + src_r6_8x16b = _mm_loadl_epi64((__m128i *)(pi2_temp2 + 4)); + + src_r0r1_c0_8x16b = _mm_unpacklo_epi16(src_r0_8x16b, src_r1_8x16b); + src_r2r3_c0_8x16b = _mm_unpacklo_epi16(src_r2_8x16b, src_r3_8x16b); + src_r4r5_c0_8x16b = _mm_unpacklo_epi16(src_r4_8x16b, src_r5_8x16b); + + res_t1_4x32b = _mm_madd_epi16(src_r0r1_c0_8x16b, coeff0_1_8x16b); + res_t2_4x32b = _mm_madd_epi16(src_r2r3_c0_8x16b, coeff2_3_8x16b); + res_t3_4x32b = _mm_madd_epi16(src_r4r5_c0_8x16b, coeff4_5_8x16b); + + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); + res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); + res_t0_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); + + src_r0r1_c0_8x16b = _mm_unpacklo_epi16(src_r1_8x16b, src_r2_8x16b); + src_r2r3_c0_8x16b = _mm_unpacklo_epi16(src_r3_8x16b, src_r4_8x16b); + src_r4r5_c0_8x16b = _mm_unpacklo_epi16(src_r5_8x16b, src_r6_8x16b); + + res_t1_4x32b = _mm_madd_epi16(src_r0r1_c0_8x16b, coeff0_1_8x16b); + res_t2_4x32b = _mm_madd_epi16(src_r2r3_c0_8x16b, coeff2_3_8x16b); + res_t3_4x32b = _mm_madd_epi16(src_r4r5_c0_8x16b, coeff4_5_8x16b); + + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); + res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); + res_t1_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); + + res_8x16b = _mm_packs_epi32(res_t0_4x32b, res_t1_4x32b); + res_16x8b = _mm_packus_epi16(res_8x16b, res_8x16b); + + src_hpel_8x16b = _mm_loadu_si128((__m128i *)pi2_temp3); + src_hpel_8x16b = _mm_add_epi16(src_hpel_8x16b, const_val16_8x16b); + src_hpel_8x16b = _mm_srai_epi16(src_hpel_8x16b, 5); //shifting right by 5 bits. + src_hpel_16x8b = _mm_packus_epi16(src_hpel_8x16b, src_hpel_8x16b); + + res_16x8b = _mm_avg_epu8(res_16x8b, src_hpel_16x8b); + + *((WORD32 *)(pu1_dst)) = _mm_cvtsi128_si32(res_16x8b); + res_16x8b = _mm_srli_si128(res_16x8b, 4); + *((WORD32 *)(pu1_dst + dst_strd)) = _mm_cvtsi128_si32(res_16x8b); + + src_r0_8x16b = src_r2_8x16b; + src_r1_8x16b = src_r3_8x16b; + src_r2_8x16b = src_r4_8x16b; + src_r3_8x16b = src_r5_8x16b; + src_r4_8x16b = src_r6_8x16b; + + ht -= 2; + pi2_temp2 = pi2_temp2 + (4 << 1); + pi2_temp3 = pi2_temp3 + (4 << 1); + pu1_dst = pu1_dst + (dst_strd << 1); + } + while(ht > 0); + } + } + else if(wd == 8) + { + // horizontal half-pel + { + __m128i src_r0_16x8b, src_r1_16x8b, src_r0_sht_16x8b, src_r1_sht_16x8b; + __m128i src_r0_t1_16x8b, src_r1_t1_16x8b; + + __m128i res_r0_t1_8x16b, res_r0_t2_8x16b, res_r0_t3_8x16b; + __m128i res_r1_t1_8x16b, res_r1_t2_8x16b, res_r1_t3_8x16b; + + __m128i coeff0_1_16x8b, coeff2_3_16x8b, coeff4_5_16x8b; + + coeff0_1_16x8b = _mm_set1_epi32(0xFB01FB01); //c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 + coeff2_3_16x8b = _mm_set1_epi32(0x14141414); //c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 + coeff4_5_16x8b = _mm_set1_epi32(0x01FB01FB); //c4 c5 c5 c5 c4 c5 c5 c5 c4 c5 c5 c5 c4 c5 c5 c5 + + //Row0 : a0 a1 a2 a3 a4 a5 a6 a7 a8 a9..... + //Row1 : b0 b1 b2 b3 b4 b5 b6 b7 b8 b9..... + + do + { + src_r0_16x8b = _mm_loadu_si128((__m128i *)(pu1_src)); //a0 a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 + src_r1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd)); //b0 b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 + + src_r0_sht_16x8b = _mm_srli_si128(src_r0_16x8b, 1); //a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 0 + src_r1_sht_16x8b = _mm_srli_si128(src_r1_16x8b, 1); //b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 0 + + src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a0 a1 a1 a2 a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 + src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b0 b1 b1 b2 b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 + + res_r0_t1_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff0_1_16x8b); //a0*c0+a1*c1 a1*c0+a2*c1 a2*c0+a3*c1 a3*c0+a4*c1 + //a4*c0+a5*c1 a5*c0+a6*c1 a6*c0+a7*c1 a7*c0+a8*c1 + res_r1_t1_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff0_1_16x8b); //b0*c0+b1*c1 b1*c0+b2*c1 b2*c0+b3*c1 b3*c0+b4*c1 + //b4*c0+b5*c1 b5*c0+b6*c1 b6*c0+b7*c1 b7*c0+b8*c1 + + src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 2); //a2 a3 a4 a5 a6 a7 a8 a9....a15 0 0 + src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 2); //b2 b3 b4 b5 b6 b7 b8 b9....b15 0 0 + + src_r0_sht_16x8b = _mm_srli_si128(src_r0_sht_16x8b, 2); //a3 a4 a5 a6 a7 a8 a9....a15 0 0 0 + src_r1_sht_16x8b = _mm_srli_si128(src_r1_sht_16x8b, 2); //b3 b4 b5 b6 b7 b8 b9....b15 0 0 0 + + src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 a8 a9 a9 a10 + src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 a8 a9 a9 a10 + + res_r0_t2_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff2_3_16x8b); //a2*c2+a3*c3 a3*c2+a4*c3 a4*c2+a5*c3 a5*c2+a6*c3 + //a6*c2+a7*c3 a7*c2+a8*c3 a8*c2+a9*c3 a9*c2+a10*c3 + res_r1_t2_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff2_3_16x8b); //b2*c2+b3*c3 b3*c2+b4*c3 b2*c4+b5*c3 b5*c2+b6*c3 + //b6*c2+b7*c3 b7*c2+b8*c3 b8*c2+b9*c3 b9*c2+b10*c3 + + src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 2); //a4 a5 a6 a7 a8 a9....a15 0 0 0 0 + src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 2); //b4 b5 b6 b7 b8 b9....b15 0 0 0 0 + + src_r0_sht_16x8b = _mm_srli_si128(src_r0_sht_16x8b, 2); //a5 a6 a7 a8 a9....a15 0 0 0 0 0 + src_r1_sht_16x8b = _mm_srli_si128(src_r1_sht_16x8b, 2); //b5 b6 b7 b8 b9....b15 0 0 0 0 0 + + src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a4 a5 a5 a6 a6 a7 a7 a8 a8 a9 a9 a10 a10 a11 a11 a12 + src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b4 b5 b5 b6 b6 b7 b7 b8 b8 b9 b9 b10 b10 b11 b11 b12 + + res_r0_t3_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff4_5_16x8b); //a4*c4+a5*c5 a5*c4+a6*c5 a6*c4+a7*c5 a7*c4+a8*c5 + //a8*c4+a9*c5 a9*c4+a10*c5 a10*c4+a11*c5 a11*c4+a12*c5 + res_r1_t3_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff4_5_16x8b); //b4*c4+b5*c5 b5*c4+b6*c5 b6*c4+b7*c5 b7*c4+b8*c5 + //b8*c4+b9*c5 b9*c4+b10*c5 b10*c4+b11*c5 b11*c4+b12*c5 + res_r0_t1_8x16b = _mm_add_epi16(res_r0_t1_8x16b, res_r0_t2_8x16b); + res_r0_t1_8x16b = _mm_add_epi16(res_r0_t1_8x16b, res_r0_t3_8x16b); + + res_r1_t1_8x16b = _mm_add_epi16(res_r1_t1_8x16b, res_r1_t2_8x16b); + res_r1_t1_8x16b = _mm_add_epi16(res_r1_t1_8x16b, res_r1_t3_8x16b); + + _mm_storeu_si128((__m128i *)(pi2_temp1), res_r0_t1_8x16b); + _mm_storeu_si128((__m128i *)(pi2_temp1 + 8), res_r1_t1_8x16b); + + ht_temp -= 2; + pu1_src = pu1_src + (src_strd << 1); + pi2_temp1 = pi2_temp1 + (8 << 1); + } + while(ht_temp > 0); + } + // vertical q-pel + { + __m128i src_r0_8x16b, src_r1_8x16b, src_r2_8x16b, src_r3_8x16b; + __m128i src_r4_8x16b, src_r5_8x16b, src_r6_8x16b; + __m128i src_r0r1_8x16b, src_r2r3_8x16b, src_r4r5_8x16b; + __m128i src_hpel_8x16b, src_hpel_16x8b; + + __m128i res_t0_4x32b, res_t1_4x32b, res_t2_4x32b, res_t3_4x32b; + __m128i res_8x16b, res_16x8b; + + __m128i coeff0_1_8x16b, coeff2_3_8x16b, coeff4_5_8x16b; + __m128i const_val512_4x32b, const_val16_8x16b; + + coeff0_1_8x16b = _mm_set1_epi32(0xFFFB0001); + coeff2_3_8x16b = _mm_set1_epi32(0x00140014); + coeff4_5_8x16b = _mm_set1_epi32(0x0001FFFB); + + const_val512_4x32b = _mm_set1_epi32(512); + const_val16_8x16b = _mm_set1_epi16(16); + + src_r0_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2)); + src_r1_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 8)); + src_r2_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 16)); + src_r3_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 24)); + src_r4_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 32)); + pi2_temp2 += 40; + + do + { + src_r5_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2)); + src_r6_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 8)); + + src_r0r1_8x16b = _mm_unpacklo_epi16(src_r0_8x16b, src_r1_8x16b); + src_r2r3_8x16b = _mm_unpacklo_epi16(src_r2_8x16b, src_r3_8x16b); + src_r4r5_8x16b = _mm_unpacklo_epi16(src_r4_8x16b, src_r5_8x16b); + + res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); + res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); + res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); + + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); + res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); + res_t0_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); + + src_r0r1_8x16b = _mm_unpackhi_epi16(src_r0_8x16b, src_r1_8x16b); + src_r2r3_8x16b = _mm_unpackhi_epi16(src_r2_8x16b, src_r3_8x16b); + src_r4r5_8x16b = _mm_unpackhi_epi16(src_r4_8x16b, src_r5_8x16b); + + res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); + res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); + res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); + + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); + res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); + res_t1_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); + + res_8x16b = _mm_packs_epi32(res_t0_4x32b, res_t1_4x32b); + res_16x8b = _mm_packus_epi16(res_8x16b, res_8x16b); + + src_hpel_8x16b = _mm_loadu_si128((__m128i *)pi2_temp3); + src_hpel_8x16b = _mm_add_epi16(const_val16_8x16b, src_hpel_8x16b); + src_hpel_8x16b = _mm_srai_epi16(src_hpel_8x16b, 5); //shifting right by 5 bits. + src_hpel_16x8b = _mm_packus_epi16(src_hpel_8x16b, src_hpel_8x16b); + + res_16x8b = _mm_avg_epu8(res_16x8b, src_hpel_16x8b); + + _mm_storel_epi64((__m128i *)(pu1_dst), res_16x8b); + + src_r0r1_8x16b = _mm_unpacklo_epi16(src_r1_8x16b, src_r2_8x16b); + src_r2r3_8x16b = _mm_unpacklo_epi16(src_r3_8x16b, src_r4_8x16b); + src_r4r5_8x16b = _mm_unpacklo_epi16(src_r5_8x16b, src_r6_8x16b); + + res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); + res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); + res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); + + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); + res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); + res_t0_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); + + src_r0r1_8x16b = _mm_unpackhi_epi16(src_r1_8x16b, src_r2_8x16b); + src_r2r3_8x16b = _mm_unpackhi_epi16(src_r3_8x16b, src_r4_8x16b); + src_r4r5_8x16b = _mm_unpackhi_epi16(src_r5_8x16b, src_r6_8x16b); + + res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); + res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); + res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); + + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); + res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); + res_t1_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); + + res_8x16b = _mm_packs_epi32(res_t0_4x32b, res_t1_4x32b); + res_16x8b = _mm_packus_epi16(res_8x16b, res_8x16b); + + src_hpel_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp3 + 8)); + src_hpel_8x16b = _mm_add_epi16(const_val16_8x16b, src_hpel_8x16b); + src_hpel_8x16b = _mm_srai_epi16(src_hpel_8x16b, 5); //shifting right by 5 bits. + src_hpel_16x8b = _mm_packus_epi16(src_hpel_8x16b, src_hpel_8x16b); + + res_16x8b = _mm_avg_epu8(res_16x8b, src_hpel_16x8b); + + _mm_storel_epi64((__m128i *)(pu1_dst + dst_strd), res_16x8b); + + src_r0_8x16b = src_r2_8x16b; + src_r1_8x16b = src_r3_8x16b; + src_r2_8x16b = src_r4_8x16b; + src_r3_8x16b = src_r5_8x16b; + src_r4_8x16b = src_r6_8x16b; + + ht -= 2; + pi2_temp2 = pi2_temp2 + (8 << 1); + pi2_temp3 = pi2_temp3 + (8 << 1); + pu1_dst = pu1_dst + (dst_strd << 1); + } + while(ht > 0); + } + } + else // wd == 16 + { + UWORD8 *pu1_dst1; + WORD16 *pi2_temp4,*pi2_temp5; + + pu1_dst1 = pu1_dst + 8; + pi2_temp4 = pi2_temp2 + 8; + pi2_temp5 = pi2_temp3 + 8; + + // horizontal half-pel + { + __m128i src_r0_16x8b, src_r1_16x8b, src_r0_sht_16x8b, src_r1_sht_16x8b; + __m128i src_r0_t1_16x8b, src_r1_t1_16x8b; + + __m128i res_r0_t1_8x16b, res_r0_t2_8x16b, res_r0_t3_8x16b; + __m128i res_r1_t1_8x16b, res_r1_t2_8x16b, res_r1_t3_8x16b; + + __m128i coeff0_1_16x8b, coeff2_3_16x8b, coeff4_5_16x8b; + + coeff0_1_16x8b = _mm_set1_epi32(0xFB01FB01); //c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 c0 c1 + coeff2_3_16x8b = _mm_set1_epi32(0x14141414); //c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 c2 c3 + coeff4_5_16x8b = _mm_set1_epi32(0x01FB01FB); //c4 c5 c5 c5 c4 c5 c5 c5 c4 c5 c5 c5 c4 c5 c5 c5 + + //Row0 : a0 a1 a2 a3 a4 a5 a6 a7 a8 a9..... + //Row0 : b0 b1 b2 b3 b4 b5 b6 b7 b8 b9..... + //b0 is same a8. Similarly other bn pixels are same as a(n+8) pixels. + + do + { + src_r0_16x8b = _mm_loadu_si128((__m128i *)(pu1_src)); //a0 a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 + src_r1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + 8)); //b0 b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 + + src_r0_sht_16x8b = _mm_srli_si128(src_r0_16x8b, 1); //a1 a2 a3 a4 a5 a6 a7 a8 a9....a15 0 + src_r1_sht_16x8b = _mm_srli_si128(src_r1_16x8b, 1); //b1 b2 b3 b4 b5 b6 b7 b8 b9....b15 0 + + src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a0 a1 a1 a2 a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 + src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b0 b1 b1 b2 b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 + + res_r0_t1_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff0_1_16x8b); //a0*c0+a1*c1 a1*c0+a2*c1 a2*c0+a3*c1 a3*c0+a4*c1 + //a4*c0+a5*c1 a5*c0+a6*c1 a6*c0+a7*c1 a7*c0+a8*c1 + res_r1_t1_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff0_1_16x8b); //b0*c0+b1*c1 b1*c0+b2*c1 b2*c0+b3*c1 b3*c0+b4*c1 + //b4*c0+b5*c1 b5*c0+b6*c1 b6*c0+b7*c1 b7*c0+b8*c1 + + src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 2); //a2 a3 a4 a5 a6 a7 a8 a9....a15 0 0 + src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 2); //b2 b3 b4 b5 b6 b7 b8 b9....b15 0 0 + + src_r0_sht_16x8b = _mm_srli_si128(src_r0_sht_16x8b, 2); //a3 a4 a5 a6 a7 a8 a9....a15 0 0 0 + src_r1_sht_16x8b = _mm_srli_si128(src_r1_sht_16x8b, 2); //b3 b4 b5 b6 b7 b8 b9....b15 0 0 0 + + src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a2 a3 a3 a4 a4 a5 a5 a6 a6 a7 a7 a8 a8 a9 a9 a10 + src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b2 b3 b3 b4 b4 b5 b5 b6 b6 b7 b7 b8 a8 a9 a9 a10 + + res_r0_t2_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff2_3_16x8b); //a2*c2+a3*c3 a3*c2+a4*c3 a4*c2+a5*c3 a5*c2+a6*c3 + //a6*c2+a7*c3 a7*c2+a8*c3 a8*c2+a9*c3 a9*c2+a10*c3 + res_r1_t2_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff2_3_16x8b); //b2*c2+b3*c3 b3*c2+b4*c3 b2*c4+b5*c3 b5*c2+b6*c3 + //b6*c2+b7*c3 b7*c2+b8*c3 b8*c2+b9*c3 b9*c2+b10*c3 + + src_r0_16x8b = _mm_srli_si128(src_r0_16x8b, 2); //a4 a5 a6 a7 a8 a9....a15 0 0 0 0 + src_r1_16x8b = _mm_srli_si128(src_r1_16x8b, 2); //b4 b5 b6 b7 b8 b9....b15 0 0 0 0 + + src_r0_sht_16x8b = _mm_srli_si128(src_r0_sht_16x8b, 2); //a5 a6 a7 a8 a9....a15 0 0 0 0 0 + src_r1_sht_16x8b = _mm_srli_si128(src_r1_sht_16x8b, 2); //b5 b6 b7 b8 b9....b15 0 0 0 0 0 + + src_r0_t1_16x8b = _mm_unpacklo_epi8(src_r0_16x8b, src_r0_sht_16x8b); //a4 a5 a5 a6 a6 a7 a7 a8 a8 a9 a9 a10 a10 a11 a11 a12 + src_r1_t1_16x8b = _mm_unpacklo_epi8(src_r1_16x8b, src_r1_sht_16x8b); //b4 b5 b5 b6 b6 b7 b7 b8 b8 b9 b9 b10 b10 b11 b11 b12 + + res_r0_t3_8x16b = _mm_maddubs_epi16(src_r0_t1_16x8b, coeff4_5_16x8b); //a4*c4+a5*c5 a5*c4+a6*c5 a6*c4+a7*c5 a7*c4+a8*c5 + //a8*c4+a9*c5 a9*c4+a10*c5 a10*c4+a11*c5 a11*c4+a12*c5 + res_r1_t3_8x16b = _mm_maddubs_epi16(src_r1_t1_16x8b, coeff4_5_16x8b); //b4*c4+b5*c5 b5*c4+b6*c5 b6*c4+b7*c5 b7*c4+b8*c5 + //b8*c4+b9*c5 b9*c4+b10*c5 b10*c4+b11*c5 b11*c4+b12*c5 + res_r0_t1_8x16b = _mm_add_epi16(res_r0_t1_8x16b, res_r0_t2_8x16b); + res_r0_t1_8x16b = _mm_add_epi16(res_r0_t1_8x16b, res_r0_t3_8x16b); + + res_r1_t1_8x16b = _mm_add_epi16(res_r1_t1_8x16b, res_r1_t2_8x16b); + res_r1_t1_8x16b = _mm_add_epi16(res_r1_t1_8x16b, res_r1_t3_8x16b); + + _mm_storeu_si128((__m128i *)(pi2_temp1), res_r0_t1_8x16b); + _mm_storeu_si128((__m128i *)(pi2_temp1 + 8), res_r1_t1_8x16b); + + ht_temp--; + pu1_src = pu1_src + src_strd; + pi2_temp1 = pi2_temp1 + 16; + } + while(ht_temp > 0); + } + // vertical q-pel + { + __m128i src_r0_8x16b, src_r1_8x16b, src_r2_8x16b, src_r3_8x16b, src_r4_8x16b; + __m128i src_r5_8x16b, src_r6_8x16b; + __m128i src_r0r1_8x16b, src_r2r3_8x16b, src_r4r5_8x16b; + __m128i src_hpel_8x16b, src_hpel_16x8b; + + __m128i res_t0_4x32b, res_t1_4x32b, res_t2_4x32b, res_t3_4x32b; + __m128i res_8x16b, res_16x8b; + + __m128i coeff0_1_8x16b, coeff2_3_8x16b, coeff4_5_8x16b; + __m128i const_val512_4x32b, const_val16_8x16b; + + coeff0_1_8x16b = _mm_set1_epi32(0xFFFB0001); + coeff2_3_8x16b = _mm_set1_epi32(0x00140014); + coeff4_5_8x16b = _mm_set1_epi32(0x0001FFFB); + + const_val512_4x32b = _mm_set1_epi32(512); + const_val16_8x16b = _mm_set1_epi16(16); + + /**********************************************************/ + /* Do first height x 8 block */ + /**********************************************************/ + src_r0_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2)); + src_r1_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 16)); + src_r2_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 32)); + src_r3_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 48)); + src_r4_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 64)); + pi2_temp2 += 80; + + ht_temp = ht; + do + { + src_r5_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2)); + src_r6_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp2 + 16)); + + src_r0r1_8x16b = _mm_unpacklo_epi16(src_r0_8x16b, src_r1_8x16b); + src_r2r3_8x16b = _mm_unpacklo_epi16(src_r2_8x16b, src_r3_8x16b); + src_r4r5_8x16b = _mm_unpacklo_epi16(src_r4_8x16b, src_r5_8x16b); + + res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); + res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); + res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); + + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); + res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); + res_t0_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); + + src_r0r1_8x16b = _mm_unpackhi_epi16(src_r0_8x16b, src_r1_8x16b); + src_r2r3_8x16b = _mm_unpackhi_epi16(src_r2_8x16b, src_r3_8x16b); + src_r4r5_8x16b = _mm_unpackhi_epi16(src_r4_8x16b, src_r5_8x16b); + + res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); + res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); + res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); + + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); + res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); + res_t1_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); + + res_8x16b = _mm_packs_epi32(res_t0_4x32b, res_t1_4x32b); + res_16x8b = _mm_packus_epi16(res_8x16b, res_8x16b); + + src_hpel_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp3)); + src_hpel_8x16b = _mm_add_epi16(src_hpel_8x16b, const_val16_8x16b); + src_hpel_8x16b = _mm_srai_epi16(src_hpel_8x16b, 5); //shifting right by 5 bits. + src_hpel_16x8b = _mm_packus_epi16(src_hpel_8x16b, src_hpel_8x16b); + + res_16x8b = _mm_avg_epu8(res_16x8b, src_hpel_16x8b); + _mm_storel_epi64((__m128i *)(pu1_dst), res_16x8b); + + src_r0r1_8x16b = _mm_unpacklo_epi16(src_r1_8x16b, src_r2_8x16b); + src_r2r3_8x16b = _mm_unpacklo_epi16(src_r3_8x16b, src_r4_8x16b); + src_r4r5_8x16b = _mm_unpacklo_epi16(src_r5_8x16b, src_r6_8x16b); + + res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); + res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); + res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); + + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); + res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); + res_t0_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); + + src_r0r1_8x16b = _mm_unpackhi_epi16(src_r1_8x16b, src_r2_8x16b); + src_r2r3_8x16b = _mm_unpackhi_epi16(src_r3_8x16b, src_r4_8x16b); + src_r4r5_8x16b = _mm_unpackhi_epi16(src_r5_8x16b, src_r6_8x16b); + + res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); + res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); + res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); + + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); + res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); + res_t1_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); + + res_8x16b = _mm_packs_epi32(res_t0_4x32b, res_t1_4x32b); + res_16x8b = _mm_packus_epi16(res_8x16b, res_8x16b); + + src_hpel_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp3 + 16)); + src_hpel_8x16b = _mm_add_epi16(src_hpel_8x16b, const_val16_8x16b); + src_hpel_8x16b = _mm_srai_epi16(src_hpel_8x16b, 5); //shifting right by 5 bits. + src_hpel_16x8b = _mm_packus_epi16(src_hpel_8x16b, src_hpel_8x16b); + + res_16x8b = _mm_avg_epu8(res_16x8b, src_hpel_16x8b); + _mm_storel_epi64((__m128i *)(pu1_dst + dst_strd), res_16x8b); + + src_r0_8x16b = src_r2_8x16b; + src_r1_8x16b = src_r3_8x16b; + src_r2_8x16b = src_r4_8x16b; + src_r3_8x16b = src_r5_8x16b; + src_r4_8x16b = src_r6_8x16b; + + ht_temp -= 2; + pi2_temp3 = pi2_temp3 + (16 << 1); + pi2_temp2 = pi2_temp2 + (16 << 1); + pu1_dst = pu1_dst + (dst_strd << 1); + } + while(ht_temp > 0); + + /**********************************************************/ + /* Do second height * 8 block */ + /**********************************************************/ + src_r0_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp4)); + src_r1_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp4 + 16)); + src_r2_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp4 + 32)); + src_r3_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp4 + 48)); + src_r4_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp4 + 64)); + pi2_temp4 += 80; + + do + { + src_r5_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp4)); + src_r6_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp4 + 16)); + + src_r0r1_8x16b = _mm_unpacklo_epi16(src_r0_8x16b, src_r1_8x16b); + src_r2r3_8x16b = _mm_unpacklo_epi16(src_r2_8x16b, src_r3_8x16b); + src_r4r5_8x16b = _mm_unpacklo_epi16(src_r4_8x16b, src_r5_8x16b); + + res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); + res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); + res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); + + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); + res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); + res_t0_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); + + src_r0r1_8x16b = _mm_unpackhi_epi16(src_r0_8x16b, src_r1_8x16b); + src_r2r3_8x16b = _mm_unpackhi_epi16(src_r2_8x16b, src_r3_8x16b); + src_r4r5_8x16b = _mm_unpackhi_epi16(src_r4_8x16b, src_r5_8x16b); + + res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); + res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); + res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); + + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); + res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); + res_t1_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); + + res_8x16b = _mm_packs_epi32(res_t0_4x32b, res_t1_4x32b); + res_16x8b = _mm_packus_epi16(res_8x16b, res_8x16b); + + src_hpel_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp5)); + src_hpel_8x16b = _mm_add_epi16(src_hpel_8x16b, const_val16_8x16b); + src_hpel_8x16b = _mm_srai_epi16(src_hpel_8x16b, 5); //shifting right by 5 bits. + src_hpel_16x8b = _mm_packus_epi16(src_hpel_8x16b, src_hpel_8x16b); + + res_16x8b = _mm_avg_epu8(res_16x8b, src_hpel_16x8b); + _mm_storel_epi64((__m128i *)(pu1_dst1), res_16x8b); + + src_r0r1_8x16b = _mm_unpacklo_epi16(src_r1_8x16b, src_r2_8x16b); + src_r2r3_8x16b = _mm_unpacklo_epi16(src_r3_8x16b, src_r4_8x16b); + src_r4r5_8x16b = _mm_unpacklo_epi16(src_r5_8x16b, src_r6_8x16b); + + res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); + res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); + res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); + + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); + res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); + res_t0_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); + + src_r0r1_8x16b = _mm_unpackhi_epi16(src_r1_8x16b, src_r2_8x16b); + src_r2r3_8x16b = _mm_unpackhi_epi16(src_r3_8x16b, src_r4_8x16b); + src_r4r5_8x16b = _mm_unpackhi_epi16(src_r5_8x16b, src_r6_8x16b); + + res_t1_4x32b = _mm_madd_epi16(src_r0r1_8x16b, coeff0_1_8x16b); + res_t2_4x32b = _mm_madd_epi16(src_r2r3_8x16b, coeff2_3_8x16b); + res_t3_4x32b = _mm_madd_epi16(src_r4r5_8x16b, coeff4_5_8x16b); + + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t2_4x32b); + res_t3_4x32b = _mm_add_epi32(res_t3_4x32b, const_val512_4x32b); + res_t1_4x32b = _mm_add_epi32(res_t1_4x32b, res_t3_4x32b); + res_t1_4x32b = _mm_srai_epi32(res_t1_4x32b, 10); + + res_8x16b = _mm_packs_epi32(res_t0_4x32b, res_t1_4x32b); + res_16x8b = _mm_packus_epi16(res_8x16b, res_8x16b); + + src_hpel_8x16b = _mm_loadu_si128((__m128i *)(pi2_temp5 + 16)); + src_hpel_8x16b = _mm_add_epi16(src_hpel_8x16b, const_val16_8x16b); + src_hpel_8x16b = _mm_srai_epi16(src_hpel_8x16b, 5); //shifting right by 5 bits. + src_hpel_16x8b = _mm_packus_epi16(src_hpel_8x16b, src_hpel_8x16b); + + res_16x8b = _mm_avg_epu8(res_16x8b, src_hpel_16x8b); + _mm_storel_epi64((__m128i *)(pu1_dst1 + dst_strd), res_16x8b); + + src_r0_8x16b = src_r2_8x16b; + src_r1_8x16b = src_r3_8x16b; + src_r2_8x16b = src_r4_8x16b; + src_r3_8x16b = src_r5_8x16b; + src_r4_8x16b = src_r6_8x16b; + + ht -= 2; + pi2_temp5 = pi2_temp5 + (16 << 1); + pi2_temp4 = pi2_temp4 + (16 << 1); + pu1_dst1 = pu1_dst1 + (dst_strd << 1); + } + while(ht > 0); + } + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_inter_pred_chroma_ssse3 */ +/* */ +/* Description : This function implements a four-tap 2D filter as */ +/* mentioned in sec. 8.4.2.2.2 titled "Chroma sample */ +/* "interpolation process". (ht,wd) can be (2,2), (4,2), */ +/* (2,4), (4,4), (8,4), (4,8) or (8,8). */ +/* */ +/* Inputs : puc_src - pointer to source */ +/* puc_dst - pointer to destination */ +/* src_strd - stride for source */ +/* dst_strd - stride for destination */ +/* dx - x position of destination value */ +/* dy - y position of destination value */ +/* ht - height of the block */ +/* wd - width of the block */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes */ +/* 13 02 2015 Kaushik Initial Version */ +/* Senthoor */ +/* */ +/*****************************************************************************/ +void ih264_inter_pred_chroma_ssse3(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 dx, + WORD32 dy, + WORD32 ht, + WORD32 wd) +{ + WORD32 i, j, A, B, C, D; + + i = 8 - dx; + j = 8 - dy; + + A = i * j; + B = dx * j; + C = i * dy; + D = dx * dy; + + if(wd == 2) + { + WORD32 tmp1, tmp2, tmp3, tmp4; + + do + { + //U + tmp1 = A * pu1_src[0] + B * pu1_src[2] + C * pu1_src[src_strd] + D * pu1_src[src_strd + 2]; + tmp2 = A * pu1_src[2] + B * pu1_src[4] + C * pu1_src[src_strd + 2] + D * pu1_src[src_strd + 4]; + //V + tmp3 = A * pu1_src[1] + B * pu1_src[3] + C * pu1_src[src_strd + 1] + D * pu1_src[src_strd + 3]; + tmp4 = A * pu1_src[3] + B * pu1_src[5] + C * pu1_src[src_strd + 3] + D * pu1_src[src_strd + 5]; + + tmp1 = (tmp1 + 32) >> 6; + tmp2 = (tmp2 + 32) >> 6; + tmp3 = (tmp3 + 32) >> 6; + tmp4 = (tmp4 + 32) >> 6; + + pu1_dst[0] = CLIP_U8(tmp1); + pu1_dst[2] = CLIP_U8(tmp2); + pu1_dst[1] = CLIP_U8(tmp3); + pu1_dst[3] = CLIP_U8(tmp4); + + pu1_src += src_strd; + pu1_dst += dst_strd; + + tmp1 = A * pu1_src[0] + B * pu1_src[2] + C * pu1_src[src_strd] + D * pu1_src[src_strd + 2]; + tmp2 = A * pu1_src[2] + B * pu1_src[4] + C * pu1_src[src_strd + 2] + D * pu1_src[src_strd + 4]; + tmp3 = A * pu1_src[1] + B * pu1_src[3] + C * pu1_src[src_strd + 1] + D * pu1_src[src_strd + 3]; + tmp4 = A * pu1_src[3] + B * pu1_src[5] + C * pu1_src[src_strd + 3] + D * pu1_src[src_strd + 5]; + + tmp1 = (tmp1 + 32) >> 6; + tmp2 = (tmp2 + 32) >> 6; + tmp3 = (tmp3 + 32) >> 6; + tmp4 = (tmp4 + 32) >> 6; + + pu1_dst[0] = CLIP_U8(tmp1); + pu1_dst[2] = CLIP_U8(tmp2); + pu1_dst[1] = CLIP_U8(tmp3); + pu1_dst[3] = CLIP_U8(tmp4); + + ht -= 2; + pu1_src += src_strd; + pu1_dst += dst_strd; + } + while(ht > 0); + + } + else if(wd == 4) + { + WORD32 AB, CD; + + __m128i src_r1_16x8b, src_r2_16x8b, src_r3_16x8b; + __m128i res1_AB_8x16b, res1_CD_8x16b, res1_8x16b, res1_16x8b; + __m128i res2_AB_8x16b, res2_CD_8x16b, res2_8x16b, res2_16x8b; + + __m128i coeffAB_16x8b, coeffCD_16x8b, round_add32_8x16b; + __m128i const_shuff_16x8b; + + AB = (B << 8) + A; + CD = (D << 8) + C; + + coeffAB_16x8b = _mm_set1_epi16(AB); + coeffCD_16x8b = _mm_set1_epi16(CD); + + round_add32_8x16b = _mm_set1_epi16(32); + + const_shuff_16x8b = _mm_setr_epi32(0x03010200, 0x05030402, 0x07050604, 0x09070806); + + src_r1_16x8b = _mm_loadu_si128((__m128i *)pu1_src); + src_r1_16x8b = _mm_shuffle_epi8(src_r1_16x8b, const_shuff_16x8b); + pu1_src += src_strd; + + do + { + src_r2_16x8b = _mm_loadu_si128((__m128i *)pu1_src); + src_r3_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd)); + + src_r2_16x8b = _mm_shuffle_epi8(src_r2_16x8b, const_shuff_16x8b); + src_r3_16x8b = _mm_shuffle_epi8(src_r3_16x8b, const_shuff_16x8b); + + res1_AB_8x16b = _mm_maddubs_epi16(src_r1_16x8b, coeffAB_16x8b); + res1_CD_8x16b = _mm_maddubs_epi16(src_r2_16x8b, coeffCD_16x8b); + res2_AB_8x16b = _mm_maddubs_epi16(src_r2_16x8b, coeffAB_16x8b); + res2_CD_8x16b = _mm_maddubs_epi16(src_r3_16x8b, coeffCD_16x8b); + + res1_8x16b = _mm_add_epi16(res1_AB_8x16b, res1_CD_8x16b); + res2_8x16b = _mm_add_epi16(res2_AB_8x16b, res2_CD_8x16b); + res1_8x16b = _mm_add_epi16(res1_8x16b, round_add32_8x16b); + res2_8x16b = _mm_add_epi16(res2_8x16b, round_add32_8x16b); + + res1_8x16b = _mm_srai_epi16(res1_8x16b, 6); + res2_8x16b = _mm_srai_epi16(res2_8x16b, 6); + + res1_16x8b = _mm_packus_epi16(res1_8x16b, res1_8x16b); + res2_16x8b = _mm_packus_epi16(res2_8x16b, res2_8x16b); + + _mm_storel_epi64((__m128i *)pu1_dst, res1_16x8b); + _mm_storel_epi64((__m128i *)(pu1_dst + dst_strd), res2_16x8b); + + src_r1_16x8b = src_r3_16x8b; + + ht -= 2; + pu1_src += src_strd << 1; + pu1_dst += dst_strd << 1; + } + while(ht > 0); + } + else // wd == 8 + { + WORD32 AB, CD; + + __m128i src_r1l_16x8b, src_r2l_16x8b; + __m128i src_r1h_16x8b, src_r2h_16x8b; + + __m128i res_l_AB_8x16b, res_l_CD_8x16b; + __m128i res_h_AB_8x16b, res_h_CD_8x16b; + __m128i res_l_8x16b, res_h_8x16b, res_16x8b; + + __m128i coeffAB_16x8b, coeffCD_16x8b, round_add32_8x16b; + __m128i const_shuff_16x8b; + + AB = (B << 8) + A; + CD = (D << 8) + C; + + coeffAB_16x8b = _mm_set1_epi16(AB); + coeffCD_16x8b = _mm_set1_epi16(CD); + + round_add32_8x16b = _mm_set1_epi16(32); + + const_shuff_16x8b = _mm_setr_epi32(0x03010200, 0x05030402, 0x07050604, 0x09070806); + + src_r1l_16x8b = _mm_loadu_si128((__m128i *)pu1_src); + src_r1h_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + 8)); + + src_r1l_16x8b = _mm_shuffle_epi8(src_r1l_16x8b, const_shuff_16x8b); + src_r1h_16x8b = _mm_shuffle_epi8(src_r1h_16x8b, const_shuff_16x8b); + + pu1_src += src_strd; + + do + { + //row 1 + src_r2l_16x8b = _mm_loadu_si128((__m128i *)pu1_src); + src_r2h_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + 8)); + + src_r2l_16x8b = _mm_shuffle_epi8(src_r2l_16x8b, const_shuff_16x8b); + src_r2h_16x8b = _mm_shuffle_epi8(src_r2h_16x8b, const_shuff_16x8b); + + res_l_AB_8x16b = _mm_maddubs_epi16(src_r1l_16x8b, coeffAB_16x8b); + res_h_AB_8x16b = _mm_maddubs_epi16(src_r1h_16x8b, coeffAB_16x8b); + res_l_CD_8x16b = _mm_maddubs_epi16(src_r2l_16x8b, coeffCD_16x8b); + res_h_CD_8x16b = _mm_maddubs_epi16(src_r2h_16x8b, coeffCD_16x8b); + + res_l_8x16b = _mm_add_epi16(res_l_AB_8x16b, round_add32_8x16b); + res_h_8x16b = _mm_add_epi16(res_h_AB_8x16b, round_add32_8x16b); + res_l_8x16b = _mm_add_epi16(res_l_8x16b, res_l_CD_8x16b); + res_h_8x16b = _mm_add_epi16(res_h_8x16b, res_h_CD_8x16b); + + res_l_8x16b = _mm_srai_epi16(res_l_8x16b, 6); + res_h_8x16b = _mm_srai_epi16(res_h_8x16b, 6); + + res_16x8b = _mm_packus_epi16(res_l_8x16b, res_h_8x16b); + + _mm_storeu_si128((__m128i *)pu1_dst, res_16x8b); + + pu1_src += src_strd; + pu1_dst += dst_strd; + + //row 2 + src_r1l_16x8b = _mm_loadu_si128((__m128i *)pu1_src); + src_r1h_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + 8)); + + src_r1l_16x8b = _mm_shuffle_epi8(src_r1l_16x8b, const_shuff_16x8b); + src_r1h_16x8b = _mm_shuffle_epi8(src_r1h_16x8b, const_shuff_16x8b); + + res_l_AB_8x16b = _mm_maddubs_epi16(src_r2l_16x8b, coeffAB_16x8b); + res_h_AB_8x16b = _mm_maddubs_epi16(src_r2h_16x8b, coeffAB_16x8b); + res_l_CD_8x16b = _mm_maddubs_epi16(src_r1l_16x8b, coeffCD_16x8b); + res_h_CD_8x16b = _mm_maddubs_epi16(src_r1h_16x8b, coeffCD_16x8b); + + res_l_8x16b = _mm_add_epi16(res_l_AB_8x16b, round_add32_8x16b); + res_h_8x16b = _mm_add_epi16(res_h_AB_8x16b, round_add32_8x16b); + res_l_8x16b = _mm_add_epi16(res_l_8x16b, res_l_CD_8x16b); + res_h_8x16b = _mm_add_epi16(res_h_8x16b, res_h_CD_8x16b); + + res_l_8x16b = _mm_srai_epi16(res_l_8x16b, 6); + res_h_8x16b = _mm_srai_epi16(res_h_8x16b, 6); + + res_16x8b = _mm_packus_epi16(res_l_8x16b, res_h_8x16b); + + _mm_storeu_si128((__m128i *)pu1_dst, res_16x8b); + + pu1_src += src_strd; + pu1_dst += dst_strd; + + //row 3 + src_r2l_16x8b = _mm_loadu_si128((__m128i *)pu1_src); + src_r2h_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + 8)); + + src_r2l_16x8b = _mm_shuffle_epi8(src_r2l_16x8b, const_shuff_16x8b); + src_r2h_16x8b = _mm_shuffle_epi8(src_r2h_16x8b, const_shuff_16x8b); + + res_l_AB_8x16b = _mm_maddubs_epi16(src_r1l_16x8b, coeffAB_16x8b); + res_h_AB_8x16b = _mm_maddubs_epi16(src_r1h_16x8b, coeffAB_16x8b); + res_l_CD_8x16b = _mm_maddubs_epi16(src_r2l_16x8b, coeffCD_16x8b); + res_h_CD_8x16b = _mm_maddubs_epi16(src_r2h_16x8b, coeffCD_16x8b); + + res_l_8x16b = _mm_add_epi16(res_l_AB_8x16b, round_add32_8x16b); + res_h_8x16b = _mm_add_epi16(res_h_AB_8x16b, round_add32_8x16b); + res_l_8x16b = _mm_add_epi16(res_l_8x16b, res_l_CD_8x16b); + res_h_8x16b = _mm_add_epi16(res_h_8x16b, res_h_CD_8x16b); + + res_l_8x16b = _mm_srai_epi16(res_l_8x16b, 6); + res_h_8x16b = _mm_srai_epi16(res_h_8x16b, 6); + + res_16x8b = _mm_packus_epi16(res_l_8x16b, res_h_8x16b); + + _mm_storeu_si128((__m128i *)pu1_dst, res_16x8b); + + pu1_src += src_strd; + pu1_dst += dst_strd; + + //row 1 + src_r1l_16x8b = _mm_loadu_si128((__m128i *)pu1_src); + src_r1h_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + 8)); + + src_r1l_16x8b = _mm_shuffle_epi8(src_r1l_16x8b, const_shuff_16x8b); + src_r1h_16x8b = _mm_shuffle_epi8(src_r1h_16x8b, const_shuff_16x8b); + + res_l_AB_8x16b = _mm_maddubs_epi16(src_r2l_16x8b, coeffAB_16x8b); + res_h_AB_8x16b = _mm_maddubs_epi16(src_r2h_16x8b, coeffAB_16x8b); + res_l_CD_8x16b = _mm_maddubs_epi16(src_r1l_16x8b, coeffCD_16x8b); + res_h_CD_8x16b = _mm_maddubs_epi16(src_r1h_16x8b, coeffCD_16x8b); + + res_l_8x16b = _mm_add_epi16(res_l_AB_8x16b, round_add32_8x16b); + res_h_8x16b = _mm_add_epi16(res_h_AB_8x16b, round_add32_8x16b); + res_l_8x16b = _mm_add_epi16(res_l_8x16b, res_l_CD_8x16b); + res_h_8x16b = _mm_add_epi16(res_h_8x16b, res_h_CD_8x16b); + + res_l_8x16b = _mm_srai_epi16(res_l_8x16b, 6); + res_h_8x16b = _mm_srai_epi16(res_h_8x16b, 6); + + res_16x8b = _mm_packus_epi16(res_l_8x16b, res_h_8x16b); + + _mm_storeu_si128((__m128i *)pu1_dst, res_16x8b); + + ht -= 4; + pu1_src += src_strd; + pu1_dst += dst_strd; + } + while(ht > 0); + } +} diff --git a/dependencies/ih264d/common/x86/ih264_iquant_itrans_recon_dc_ssse3.c b/dependencies/ih264d/common/x86/ih264_iquant_itrans_recon_dc_ssse3.c new file mode 100644 index 00000000..bcfe503f --- /dev/null +++ b/dependencies/ih264d/common/x86/ih264_iquant_itrans_recon_dc_ssse3.c @@ -0,0 +1,470 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** + ******************************************************************************* + * @file + * ih264_iquant_itrans_recon_dc_ssse3.c + * + * @brief + * Contains function definitions for inverse quantization, inverse + * transform and reconstruction + * + * @author + * Mohit [100664] + * + * @par List of Functions: + * - ih264_iquant_itrans_recon_4x4_dc_ssse3() + * - ih264_iquant_itrans_recon_8x8_dc_ssse3() + * + * @remarks + * None + * + ******************************************************************************* + */ +/* User include files */ +#include "ih264_typedefs.h" +#include "ih264_defs.h" +#include "ih264_trans_macros.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264_trans_data.h" +#include "ih264_size_defs.h" +#include "ih264_structs.h" +#include "ih264_trans_quant_itrans_iquant.h" +#include <immintrin.h> + +/* + ******************************************************************************** + * + * @brief This function reconstructs a 4x4 sub block from quantized resiude and + * prediction buffer for dc input pattern only, i.e. only the (0,0) element of the input + * 4x4 block is non-zero. For complete function, refer ih264_iquant_itrans_recon_ssse3.c + * + * @par Description: + * The quantized residue is first inverse quantized, then inverse transformed. + * This inverse transformed content is added to the prediction buffer to recon- + * struct the end output + * + * @param[in] pi2_src + * quantized 4x4 block + * + * @param[in] pu1_pred + * prediction 4x4 block + * + * @param[out] pu1_out + * reconstructed 4x4 block + * + * @param[in] src_strd + * quantization buffer stride + * + * @param[in] pred_strd, + * Prediction buffer stride + * + * @param[in] out_strd + * recon buffer Stride + * + * @param[in] pu2_scaling_list + * pointer to scaling list + * + * @param[in] pu2_norm_adjust + * pointer to inverse scale matrix + * + * @param[in] u4_qp_div_6 + * Floor (qp/6) + * + * @param[in] pi4_tmp + * temporary buffer of size 1*16 + * + * @returns none + * + * @remarks none + * + ******************************************************************************* + */ +void ih264_iquant_itrans_recon_4x4_dc_ssse3(WORD16 *pi2_src, + UWORD8 *pu1_pred, + UWORD8 *pu1_out, + WORD32 pred_strd, + WORD32 out_strd, + const UWORD16 *pu2_iscal_mat, + const UWORD16 *pu2_weigh_mat, + UWORD32 u4_qp_div_6, + WORD16 *pi2_tmp, + WORD32 iq_start_idx, + WORD16 *pi2_dc_ld_addr) +{ + UWORD32 *pu4_out = (UWORD32 *)pu1_out; + WORD32 q0 = pi2_src[0]; + WORD16 i_macro, rnd_fact = (u4_qp_div_6 < 4) ? 1 << (3 - u4_qp_div_6) : 0; + + __m128i predload_r,pred_r0, pred_r1, pred_r2, pred_r3; + __m128i sign_reg; + __m128i zero_8x16b = _mm_setzero_si128(); // all bits reset to zero + __m128i temp4, temp5, temp6, temp7; + __m128i value_add; + + UNUSED (pi2_tmp); + + INV_QUANT(q0, pu2_iscal_mat[0], pu2_weigh_mat[0], u4_qp_div_6, rnd_fact, 4); + + if (iq_start_idx != 0 ) + q0 = pi2_dc_ld_addr[0]; // Restoring dc value for intra case + + i_macro = ((q0 + 32) >> 6); + + value_add = _mm_set1_epi16(i_macro); + + zero_8x16b = _mm_setzero_si128(); // all bits reset to zero + //Load pred buffer + predload_r = _mm_loadl_epi64((__m128i *) (&pu1_pred[0])); //p00 p01 p02 p03 0 0 0 0 0 0 0 0 -- all 8 bits + pred_r0 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p00 p01 p02 p03 0 0 0 0 -- all 16 bits + predload_r = _mm_loadl_epi64((__m128i *) (&pu1_pred[pred_strd])); //p10 p11 p12 p13 0 0 0 0 0 0 0 0 -- all 8 bits + pred_r1 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p10 p11 p12 p13 0 0 0 0 -- all 16 bits + predload_r = _mm_loadl_epi64((__m128i *) (&pu1_pred[2*pred_strd])); //p20 p21 p22 p23 0 0 0 0 0 0 0 0 -- all 8 bits + pred_r2 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p20 p21 p22 p23 0 0 0 0 -- all 16 bits + predload_r = _mm_loadl_epi64((__m128i *) (&pu1_pred[3*pred_strd])); //p30 p31 p32 p33 0 0 0 0 0 0 0 0 -- all 8 bits + pred_r3 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p30 p31 p32 p33 0 0 0 0 -- all 16 bits + + pred_r0 = _mm_unpacklo_epi64(pred_r0, pred_r1); //p00 p01 p02 p03 p10 p11 p12 p13 + pred_r2 = _mm_unpacklo_epi64(pred_r2, pred_r3); //p20 p21 p22p p23 p30 p31 p32 p33 + + temp4 = _mm_add_epi16(value_add, pred_r0); + temp5 = _mm_add_epi16(value_add, pred_r2); + /*------------------------------------------------------------------*/ + //Clipping the results to 8 bits + sign_reg = _mm_cmpgt_epi16(temp4, zero_8x16b); // sign check + temp4 = _mm_and_si128(temp4, sign_reg); + sign_reg = _mm_cmpgt_epi16(temp5, zero_8x16b); // sign check + temp5 = _mm_and_si128(temp5, sign_reg); + + temp4 = _mm_packus_epi16(temp4,temp5); + temp5 = _mm_srli_si128(temp4,4); + temp6 = _mm_srli_si128(temp5,4); + temp7 = _mm_srli_si128(temp6,4); + + *pu4_out = _mm_cvtsi128_si32(temp4); + pu1_out += out_strd; + pu4_out = (UWORD32 *)(pu1_out); + *(pu4_out) = _mm_cvtsi128_si32(temp5); + pu1_out += out_strd; + pu4_out = (UWORD32 *)(pu1_out); + *(pu4_out) = _mm_cvtsi128_si32(temp6); + pu1_out += out_strd; + pu4_out = (UWORD32 *)(pu1_out); + *(pu4_out) = _mm_cvtsi128_si32(temp7); +} +/** + ******************************************************************************* + * + * @brief + * This function performs inverse quant and Inverse transform type Ci4 for 8x8 block + * for dc input pattern only, i.e. only the (0,0) element of the input 8x8 block is + * non-zero. For complete function, refer ih264_iquant_itrans_recon_ssse3.c + * + * @par Description: + * Performs inverse transform Ci8 and adds the residue to get the + * reconstructed block + * + * @param[in] pi2_src + * Input 8x8coefficients + * + * @param[in] pu1_pred + * Prediction 8x8 block + * + * @param[out] pu1_recon + * Output 8x8 block + * + * @param[in] q_div + * QP/6 + * + * @param[in] q_rem + * QP%6 + * + * @param[in] q_lev + * Quantizer level + * + * @param[in] u4_src_stride + * Input stride + * + * @param[in] u4_pred_stride, + * Prediction stride + * + * @param[in] u4_out_stride + * Output Stride + * + * @param[in] pi4_tmp + * temporary buffer of size 1*64 + * the tmp for each block + * + * @param[in] pu4_iquant_mat + * Pointer to the inverse quantization matrix + * + * @returns Void + * + * @remarks + * None + * + ******************************************************************************* + */ + +void ih264_iquant_itrans_recon_8x8_dc_ssse3 (WORD16 *pi2_src, + UWORD8 *pu1_pred, + UWORD8 *pu1_out, + WORD32 pred_strd, + WORD32 out_strd, + const UWORD16 *pu2_iscale_mat, + const UWORD16 *pu2_weigh_mat, + UWORD32 qp_div, + WORD16 *pi2_tmp, + WORD32 iq_start_idx, + WORD16 *pi2_dc_ld_addr) +{ + WORD32 q0 = pi2_src[0]; + WORD16 i_macro, rnd_fact = (qp_div < 6) ? 1 << (5 - qp_div) : 0; + + __m128i predload_r,pred_r0, pred_r1, pred_r2, pred_r3,pred_r4,pred_r5,pred_r6,pred_r7; + __m128i sign_reg; + __m128i zero_8x16b = _mm_setzero_si128(); // all bits reset to zero + __m128i temp1,temp2,temp3,temp4, temp5, temp6, temp7,temp8; + __m128i value_add; + + UNUSED (pi2_tmp); + UNUSED (iq_start_idx); + UNUSED (pi2_dc_ld_addr); + + INV_QUANT(q0, pu2_iscale_mat[0], pu2_weigh_mat[0], qp_div, rnd_fact, 6); + i_macro = ((q0 + 32) >> 6); + + value_add = _mm_set1_epi16(i_macro); + + //Load pred buffer row 0 + predload_r = _mm_loadl_epi64((__m128i *)(&pu1_pred[0])); //p0 p1 p2 p3 p4 p5 p6 p7 0 0 0 0 0 0 0 0 -- all 8 bits + pred_r0 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p0 p1 p2 p3 p4 p5 p6 p7 -- all 16 bits + //Load pred buffer row 1 + predload_r = _mm_loadl_epi64((__m128i *)(&pu1_pred[pred_strd])); //p0 p1 p2 p3 p4 p5 p6 p7 0 0 0 0 0 0 0 0 -- all 8 bits + pred_r1 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p0 p1 p2 p3 p4 p5 p6 p7 -- all 16 bits + //Load pred buffer row 2 + predload_r = _mm_loadl_epi64( + (__m128i *)(&pu1_pred[2 * pred_strd])); //p0 p1 p2 p3 p4 p5 p6 p7 0 0 0 0 0 0 0 0 -- all 8 bits + pred_r2 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p0 p1 p2 p3 p4 p5 p6 p7 -- all 16 bits + //Load pred buffer row 3 + predload_r = _mm_loadl_epi64( + (__m128i *)(&pu1_pred[3 * pred_strd])); //p0 p1 p2 p3 p4 p5 p6 p7 0 0 0 0 0 0 0 0 -- all 8 bits + pred_r3 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p0 p1 p2 p3 p4 p5 p6 p7 -- all 16 bits + //Load pred buffer row 4 + predload_r = _mm_loadl_epi64( + (__m128i *)(&pu1_pred[4 * pred_strd])); //p0 p1 p2 p3 p4 p5 p6 p7 0 0 0 0 0 0 0 0 -- all 8 bits + pred_r4 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p0 p1 p2 p3 p4 p5 p6 p7 -- all 16 bits + //Load pred buffer row 5 + predload_r = _mm_loadl_epi64( + (__m128i *)(&pu1_pred[5 * pred_strd])); //p0 p1 p2 p3 p4 p5 p6 p7 0 0 0 0 0 0 0 0 -- all 8 bit + pred_r5 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p0 p1 p2 p3 p4 p5 p6 p7 -- all 16 bits + //Load pred buffer row 6 + predload_r = _mm_loadl_epi64( + (__m128i *)(&pu1_pred[6 * pred_strd])); //p0 p1 p2 p3 p4 p5 p6 p7 0 0 0 0 0 0 0 0 -- all 8 bits + pred_r6 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p0 p1 p2 p3 p4 p5 p6 p7 -- all 16 bits + //Load pred buffer row 7 + predload_r = _mm_loadl_epi64( + (__m128i *)(&pu1_pred[7 * pred_strd])); //p0 p1 p2 p3 p4 p5 p6 p7 0 0 0 0 0 0 0 0 -- all 8 bits + pred_r7 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p0 p1 p2 p3 p4 p5 p6 p7 -- all 16 bits + + temp1 = _mm_add_epi16(value_add, pred_r0); + + temp2 = _mm_add_epi16(value_add, pred_r1); + + temp3 = _mm_add_epi16(value_add, pred_r2); + + temp4 = _mm_add_epi16(value_add, pred_r3); + + temp5 = _mm_add_epi16(value_add, pred_r4); + + temp6 = _mm_add_epi16(value_add, pred_r5); + + temp7 = _mm_add_epi16(value_add, pred_r6); + + temp8 = _mm_add_epi16(value_add, pred_r7); + /*------------------------------------------------------------------*/ + //Clipping the results to 8 bits + sign_reg = _mm_cmpgt_epi16(temp1, zero_8x16b); // sign check + temp1 = _mm_and_si128(temp1, sign_reg); + sign_reg = _mm_cmpgt_epi16(temp2, zero_8x16b); // sign check + temp2 = _mm_and_si128(temp2, sign_reg); + sign_reg = _mm_cmpgt_epi16(temp3, zero_8x16b); // sign check + temp3 = _mm_and_si128(temp3, sign_reg); + sign_reg = _mm_cmpgt_epi16(temp4, zero_8x16b); // sign check + temp4 = _mm_and_si128(temp4, sign_reg); + sign_reg = _mm_cmpgt_epi16(temp5, zero_8x16b); // sign check + temp5 = _mm_and_si128(temp5, sign_reg); + sign_reg = _mm_cmpgt_epi16(temp6, zero_8x16b); // sign check + temp6 = _mm_and_si128(temp6, sign_reg); + sign_reg = _mm_cmpgt_epi16(temp7, zero_8x16b); // sign check + temp7 = _mm_and_si128(temp7, sign_reg); + sign_reg = _mm_cmpgt_epi16(temp8, zero_8x16b); // sign check + temp8 = _mm_and_si128(temp8, sign_reg); + + temp1 = _mm_packus_epi16(temp1, zero_8x16b); + temp2 = _mm_packus_epi16(temp2, zero_8x16b); + temp3 = _mm_packus_epi16(temp3, zero_8x16b); + temp4 = _mm_packus_epi16(temp4, zero_8x16b); + temp5 = _mm_packus_epi16(temp5, zero_8x16b); + temp6 = _mm_packus_epi16(temp6, zero_8x16b); + temp7 = _mm_packus_epi16(temp7, zero_8x16b); + temp8 = _mm_packus_epi16(temp8, zero_8x16b); + + _mm_storel_epi64((__m128i *)(&pu1_out[0]), temp1); + _mm_storel_epi64((__m128i *)(&pu1_out[out_strd]), temp2); + _mm_storel_epi64((__m128i *)(&pu1_out[2 * out_strd]), temp3); + _mm_storel_epi64((__m128i *)(&pu1_out[3 * out_strd]), temp4); + _mm_storel_epi64((__m128i *)(&pu1_out[4 * out_strd]), temp5); + _mm_storel_epi64((__m128i *)(&pu1_out[5 * out_strd]), temp6); + _mm_storel_epi64((__m128i *)(&pu1_out[6 * out_strd]), temp7); + _mm_storel_epi64((__m128i *)(&pu1_out[7 * out_strd]), temp8); +} + +/* + ******************************************************************************** + * + * @brief This function reconstructs a 4x4 sub block from quantized chroma resiude and + * prediction buffer + * + * @par Description: + * The quantized residue is first inverse quantized, then inverse transformed. + * This inverse transformed content is added to the prediction buffer to recon- + * struct the end output + * + * @param[in] pi2_src + * quantized 4x4 block + * + * @param[in] pu1_pred + * prediction 4x4 block + * + * @param[out] pu1_out + * reconstructed 4x4 block + * + * @param[in] src_strd + * quantization buffer stride + * + * @param[in] pred_strd, + * Prediction buffer stride + * + * @param[in] out_strd + * recon buffer Stride + * + * @param[in] pu2_scaling_list + * pointer to scaling list + * + * @param[in] pu2_norm_adjust + * pointer to inverse scale matrix + * + * @param[in] u4_qp_div_6 + * Floor (qp/6) + * + * @param[in] pi4_tmp + * temporary buffer of size 1*16 + * + * @returns none + * + * @remarks none + * + ******************************************************************************* + */ +void ih264_iquant_itrans_recon_chroma_4x4_dc_ssse3(WORD16 *pi2_src, + UWORD8 *pu1_pred, + UWORD8 *pu1_out, + WORD32 pred_strd, + WORD32 out_strd, + const UWORD16 *pu2_iscal_mat, + const UWORD16 *pu2_weigh_mat, + UWORD32 u4_qp_div_6, + WORD16 *pi2_tmp, + WORD16 *pi2_dc_src) + { + WORD16 q0 = pi2_dc_src[0]; // DC value won't be dequantized for chroma inverse transform + WORD16 i_macro = ((q0 + 32) >> 6); + + __m128i pred_r0, pred_r1, pred_r2, pred_r3, sign_reg; + __m128i zero_8x16b = _mm_setzero_si128(); // all bits reset to zero + __m128i chroma_mask = _mm_set1_epi16 (0xFF); + __m128i value_add = _mm_set1_epi16(i_macro); + __m128i out_r0, out_r1, out_r2, out_r3; + + UNUSED (pi2_src); + UNUSED (pu2_iscal_mat); + UNUSED (pu2_weigh_mat); + UNUSED (u4_qp_div_6); + UNUSED (pi2_tmp); + + //Load pred buffer + pred_r0 = _mm_loadl_epi64((__m128i *) (&pu1_pred[0])); //p00 p01 p02 p03 0 0 0 0 0 0 0 0 -- all 8 bits + pred_r1 = _mm_loadl_epi64((__m128i *) (&pu1_pred[pred_strd])); //p10 p11 p12 p13 0 0 0 0 0 0 0 0 -- all 8 bits + pred_r2 = _mm_loadl_epi64((__m128i *) (&pu1_pred[2 * pred_strd])); //p20 p21 p22 p23 0 0 0 0 0 0 0 0 -- all 8 bits + pred_r3 = _mm_loadl_epi64((__m128i *) (&pu1_pred[3 * pred_strd])); //p30 p31 p32 p33 0 0 0 0 0 0 0 0 -- all 8 bits + + pred_r0 = _mm_and_si128(pred_r0, chroma_mask); + pred_r1 = _mm_and_si128(pred_r1, chroma_mask); + pred_r2 = _mm_and_si128(pred_r2, chroma_mask); + pred_r3 = _mm_and_si128(pred_r3, chroma_mask); + + pred_r0 = _mm_unpacklo_epi64(pred_r0, pred_r1); //p00 p01 p02 p03 p10 p11 p12 p13 + pred_r2 = _mm_unpacklo_epi64(pred_r2, pred_r3); //p20 p21 p22p p23 p30 p31 p32 p33 + + pred_r0 = _mm_add_epi16(value_add, pred_r0); + pred_r2 = _mm_add_epi16(value_add, pred_r2); + + /*------------------------------------------------------------------*/ + //Clipping the results to 8 bits + sign_reg = _mm_cmpgt_epi16(pred_r0, zero_8x16b); // sign check + pred_r0 = _mm_and_si128(pred_r0, sign_reg); + sign_reg = _mm_cmpgt_epi16(pred_r2, zero_8x16b); + pred_r2 = _mm_and_si128(pred_r2, sign_reg); + + pred_r0 = _mm_packus_epi16(pred_r0, pred_r2); + pred_r1 = _mm_srli_si128(pred_r0, 4); + pred_r2 = _mm_srli_si128(pred_r1, 4); + pred_r3 = _mm_srli_si128(pred_r2, 4); + + pred_r0 = _mm_unpacklo_epi8(pred_r0, zero_8x16b); //p00 p01 p02 p03 -- all 16 bits + pred_r1 = _mm_unpacklo_epi8(pred_r1, zero_8x16b); //p10 p11 p12 p13 -- all 16 bits + pred_r2 = _mm_unpacklo_epi8(pred_r2, zero_8x16b); //p20 p21 p22 p23 -- all 16 bits + pred_r3 = _mm_unpacklo_epi8(pred_r3, zero_8x16b); //p30 p31 p32 p33 -- all 16 bits + + chroma_mask = _mm_set1_epi16 (0xFF00); + out_r0 = _mm_loadl_epi64((__m128i *) (&pu1_out[0])); + out_r1 = _mm_loadl_epi64((__m128i *) (&pu1_out[out_strd])); + out_r2 = _mm_loadl_epi64((__m128i *) (&pu1_out[2 * out_strd])); + out_r3 = _mm_loadl_epi64((__m128i *) (&pu1_out[3 * out_strd])); + + out_r0 = _mm_and_si128(out_r0, chroma_mask); + out_r1 = _mm_and_si128(out_r1, chroma_mask); + out_r2 = _mm_and_si128(out_r2, chroma_mask); + out_r3 = _mm_and_si128(out_r3, chroma_mask); + + out_r0 = _mm_add_epi8(out_r0, pred_r0); + out_r1 = _mm_add_epi8(out_r1, pred_r1); + out_r2 = _mm_add_epi8(out_r2, pred_r2); + out_r3 = _mm_add_epi8(out_r3, pred_r3); + + _mm_storel_epi64((__m128i *)(&pu1_out[0]), out_r0); + _mm_storel_epi64((__m128i *)(&pu1_out[out_strd]), out_r1); + _mm_storel_epi64((__m128i *)(&pu1_out[2 * out_strd]), out_r2); + _mm_storel_epi64((__m128i *)(&pu1_out[3 * out_strd]), out_r3); +} + + diff --git a/dependencies/ih264d/common/x86/ih264_iquant_itrans_recon_sse42.c b/dependencies/ih264d/common/x86/ih264_iquant_itrans_recon_sse42.c new file mode 100644 index 00000000..a7b9e824 --- /dev/null +++ b/dependencies/ih264d/common/x86/ih264_iquant_itrans_recon_sse42.c @@ -0,0 +1,572 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** + ******************************************************************************* + * @file + * ih264_iquant_itrans_recon_sse42.c + * + * @brief + * Contains function definitions for inverse quantization, inverse + * transform and reconstruction + * + * @author + * Mohit [100664] + * + * @par List of Functions: + * - ih264_iquant_itrans_recon_4x4_sse42() + * - ih264_iquant_itrans_recon_chroma_4x4_sse42() + * + * @remarks + * None + * + ******************************************************************************* + */ +/* User include files */ +#include "ih264_typedefs.h" +#include "ih264_defs.h" +#include "ih264_trans_macros.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264_trans_data.h" +#include "ih264_size_defs.h" +#include "ih264_structs.h" +#include "ih264_trans_quant_itrans_iquant.h" +#include <immintrin.h> + +/* + ******************************************************************************** + * + * @brief This function reconstructs a 4x4 sub block from quantized resiude and + * prediction buffer + * + * @par Description: + * The quantized residue is first inverse quantized, then inverse transformed. + * This inverse transformed content is added to the prediction buffer to recon- + * struct the end output + * + * @param[in] pi2_src + * quantized 4x4 block + * + * @param[in] pu1_pred + * prediction 4x4 block + * + * @param[out] pu1_out + * reconstructed 4x4 block + * + * @param[in] src_strd + * quantization buffer stride + * + * @param[in] pred_strd, + * Prediction buffer stride + * + * @param[in] out_strd + * recon buffer Stride + * + * @param[in] pu2_scaling_list + * pointer to scaling list + * + * @param[in] pu2_norm_adjust + * pointer to inverse scale matrix + * + * @param[in] u4_qp_div_6 + * Floor (qp/6) + * + * @param[in] pi4_tmp + * temporary buffer of size 1*16 + * + * @returns none + * + * @remarks none + * + ******************************************************************************* + */ +void ih264_iquant_itrans_recon_4x4_sse42(WORD16 *pi2_src, + UWORD8 *pu1_pred, + UWORD8 *pu1_out, + WORD32 pred_strd, + WORD32 out_strd, + const UWORD16 *pu2_iscal_mat, + const UWORD16 *pu2_weigh_mat, + UWORD32 u4_qp_div_6, + WORD16 *pi2_tmp, + WORD32 iq_start_idx, + WORD16 *pi2_dc_ld_addr) + { + UWORD32 *pu4_out = (UWORD32 *) pu1_out; + __m128i src_r0_r1, src_r2_r3; + __m128i src_r0, src_r1, src_r2, src_r3; + __m128i scalemat_r0_r1, scalemat_r2_r3; + __m128i pred_r0, pred_r1, pred_r2, pred_r3; + __m128i sign_reg, dequant_r0_r1, dequant_r2_r3; + __m128i zero_8x16b = _mm_setzero_si128(); // all bits reset to zero + __m128i temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7; + __m128i resq_r0, resq_r1, resq_r2, resq_r3; + __m128i add_rshift = _mm_set1_epi32((u4_qp_div_6 < 4) ? (1 << (3 - u4_qp_div_6)) : 0); + __m128i value_32 = _mm_set1_epi32(32); + UNUSED (pi2_tmp); + + /*************************************************************/ + /* Dequantization of coefficients. Will be replaced by SIMD */ + /* operations on platform */ + /*************************************************************/ + src_r0_r1 = _mm_loadu_si128((__m128i *) (pi2_src)); //a00 a01 a02 a03 a10 a11 a12 a13 -- the source matrix 0th,1st row + src_r2_r3 = _mm_loadu_si128((__m128i *) (pi2_src + 8)); //a20 a21 a22 a23 a30 a31 a32 a33 -- the source matrix 2nd,3rd row + scalemat_r0_r1 = _mm_loadu_si128((__m128i *) (pu2_iscal_mat)); //b00 b01 b02 b03 b10 b11 b12 b13 -- the scaling matrix 0th,1st row + scalemat_r2_r3 = _mm_loadu_si128((__m128i *) (pu2_iscal_mat + 8)); //b20 b21 b22 b23 b30 b31 b32 b33 -- the scaling matrix 2nd,3rd row + dequant_r0_r1 = _mm_loadu_si128((__m128i *) (pu2_weigh_mat)); //q00 q01 q02 q03 q10 q11 q12 q13 -- all 16 bits + dequant_r2_r3 = _mm_loadu_si128((__m128i *) (pu2_weigh_mat + 8)); //q20 q21 q22 q23 q30 q31 q32 q33 -- all 16 bits + + temp0 = _mm_mullo_epi16(scalemat_r0_r1, dequant_r0_r1); //b00*q00 b01*q01 b02*q02 b03*q03 b10*q10 b11*q11 b12*q12 b13*q13 -- 16 bit result + temp1 = _mm_mullo_epi16(scalemat_r2_r3, dequant_r2_r3); //b00*q00 b01*q01 b02*q02 b03*q03 b10*q10 b11*q11 b12*q12 b13*q13 -- 16 bit result + + temp4 = _mm_unpacklo_epi16(temp0, zero_8x16b); // b00*q00 0 b01*q01 0 b02*q02 0 b03*q03 0 -- 16 bit long + temp5 = _mm_unpackhi_epi16(temp0, zero_8x16b); // b10*q10 0 b11*q11 0 b12*q12 0 b13*q13 0 -- 16 bit long + temp6 = _mm_unpacklo_epi16(temp1, zero_8x16b); // b00*q00 0 b01*q01 0 b02*q02 0 b03*q03 0 -- 16 bit long + temp7 = _mm_unpackhi_epi16(temp1, zero_8x16b); // b10*q10 0 b11*q11 0 b12*q12 0 b13*q13 0 -- 16 bit long + + src_r0 = _mm_unpacklo_epi16(src_r0_r1, zero_8x16b); // a00 0 a01 0 a02 0 a03 0 -- 16 bit long + src_r1 = _mm_unpackhi_epi16(src_r0_r1, zero_8x16b); // a10 0 a11 0 a12 0 a13 0 -- 16 bit long + src_r2 = _mm_unpacklo_epi16(src_r2_r3, zero_8x16b); // a20 0 a21 0 a22 0 a23 0 -- 16 bit long + src_r3 = _mm_unpackhi_epi16(src_r2_r3, zero_8x16b); // a30 0 a31 0 a32 0 a33 0 -- 16 bit long + + temp4 = _mm_madd_epi16(src_r0, temp4); //a00*b00*q00 a10*b10*q10 a20*b20*q20 a30*b30 q30 -- 32 bits long + temp5 = _mm_madd_epi16(src_r1, temp5); + temp6 = _mm_madd_epi16(src_r2, temp6); + temp7 = _mm_madd_epi16(src_r3, temp7); + + if (u4_qp_div_6 >= 4) { + resq_r0 = _mm_slli_epi32(temp4, u4_qp_div_6 - 4); + resq_r1 = _mm_slli_epi32(temp5, u4_qp_div_6 - 4); + resq_r2 = _mm_slli_epi32(temp6, u4_qp_div_6 - 4); + resq_r3 = _mm_slli_epi32(temp7, u4_qp_div_6 - 4); + } else { + temp4 = _mm_add_epi32(temp4, add_rshift); + temp5 = _mm_add_epi32(temp5, add_rshift); + temp6 = _mm_add_epi32(temp6, add_rshift); + temp7 = _mm_add_epi32(temp7, add_rshift); + resq_r0 = _mm_srai_epi32(temp4, 4 - u4_qp_div_6); + resq_r1 = _mm_srai_epi32(temp5, 4 - u4_qp_div_6); + resq_r2 = _mm_srai_epi32(temp6, 4 - u4_qp_div_6); + resq_r3 = _mm_srai_epi32(temp7, 4 - u4_qp_div_6); + } + + if (iq_start_idx == 1) + resq_r0 = _mm_insert_epi32(resq_r0,(WORD32)pi2_dc_ld_addr[0],0); + /* Perform Inverse transform */ + /*-------------------------------------------------------------*/ + /* IDCT [ Horizontal transformation ] */ + /*-------------------------------------------------------------*/ + // Matrix transpose + /* + * a0 a1 a2 a3 + * b0 b1 b2 b3 + * c0 c1 c2 c3 + * d0 d1 d2 d3 + */ + temp1 = _mm_unpacklo_epi32(resq_r0, resq_r1); //a0 b0 a1 b1 + temp3 = _mm_unpacklo_epi32(resq_r2, resq_r3); //c0 d0 c1 d1 + temp2 = _mm_unpackhi_epi32(resq_r0, resq_r1); //a2 b2 a3 b3 + temp4 = _mm_unpackhi_epi32(resq_r2, resq_r3); //c2 d2 c3 d3 + resq_r0 = _mm_unpacklo_epi64(temp1, temp3); //a0 b0 c0 d0 + resq_r1 = _mm_unpackhi_epi64(temp1, temp3); //a1 b1 c1 d1 + resq_r2 = _mm_unpacklo_epi64(temp2, temp4); //a2 b2 c2 d2 + resq_r3 = _mm_unpackhi_epi64(temp2, temp4); //a3 b3 c3 d3 + //Transform starts -- horizontal transform + /*------------------------------------------------------------------*/ + /* z0 = w0 + w2 */ + temp0 = _mm_add_epi32(resq_r0, resq_r2); + /* z1 = w0 - w2 */ + temp1 = _mm_sub_epi32(resq_r0, resq_r2); + /* z2 = (w1 >> 1) - w3 */ + temp2 = _mm_srai_epi32(resq_r1, 1); //(w1>>1) + temp2 = _mm_sub_epi32(temp2, resq_r3); //(w1>>1) - w3 + /* z3 = w1 + (w3 >> 1) */ + temp3 = _mm_srai_epi32(resq_r3, 1); //(w3>>1) + w1 + temp3 = _mm_add_epi32(temp3, resq_r1); + /*----------------------------------------------------------*/ + /* x0 = z0 + z3 */ + resq_r0 = _mm_add_epi32(temp0, temp3); + /* x1 = z1 + z2 */ + resq_r1 = _mm_add_epi32(temp1, temp2); + /* x2 = z1 - z2 */ + resq_r2 = _mm_sub_epi32(temp1, temp2); + /* x3 = z0 - z3 */ + resq_r3 = _mm_sub_epi32(temp0, temp3); + // Matrix transpose + /* + * a0 b0 c0 d0 + * a1 b1 c1 d1 + * a2 b2 c2 d2 + * a3 b3 c3 d3 + */ + temp1 = _mm_unpacklo_epi32(resq_r0, resq_r1); //a0 a1 b0 b1 + temp3 = _mm_unpacklo_epi32(resq_r2, resq_r3); //a2 a3 b2 b3 + temp2 = _mm_unpackhi_epi32(resq_r0, resq_r1); //c0 c1 d0 d1 + temp4 = _mm_unpackhi_epi32(resq_r2, resq_r3); //c2 c3 d2 d3 + resq_r0 = _mm_unpacklo_epi64(temp1, temp3); //a0 a1 a2 a3 + resq_r1 = _mm_unpackhi_epi64(temp1, temp3); //b0 b1 b2 b3 + resq_r2 = _mm_unpacklo_epi64(temp2, temp4); //c0 c1 c2 c3 + resq_r3 = _mm_unpackhi_epi64(temp2, temp4); //d0 d1 d2 d3 + //Transform ends -- horizontal transform + + //Load pred buffer + pred_r0 = _mm_loadl_epi64((__m128i *) (&pu1_pred[0])); //p00 p01 p02 p03 0 0 0 0 0 0 0 0 -- all 8 bits + pred_r1 = _mm_loadl_epi64((__m128i *) (&pu1_pred[pred_strd])); //p10 p11 p12 p13 0 0 0 0 0 0 0 0 -- all 8 bits + pred_r2 = _mm_loadl_epi64((__m128i *) (&pu1_pred[2 * pred_strd])); //p20 p21 p22 p23 0 0 0 0 0 0 0 0 -- all 8 bits + pred_r3 = _mm_loadl_epi64((__m128i *) (&pu1_pred[3 * pred_strd])); //p30 p31 p32 p33 0 0 0 0 0 0 0 0 -- all 8 bits + + pred_r0 = _mm_cvtepu8_epi32(pred_r0); //p00 p01 p02 p03 -- all 32 bits + pred_r1 = _mm_cvtepu8_epi32(pred_r1); //p10 p11 p12 p13 -- all 32 bits + pred_r2 = _mm_cvtepu8_epi32(pred_r2); //p20 p21 p22 p23 -- all 32 bits + pred_r3 = _mm_cvtepu8_epi32(pred_r3); //p30 p31 p32 p33 -- all 32 bits + + /*--------------------------------------------------------------*/ + /* IDCT [ Vertical transformation] and Xij = (xij + 32)>>6 */ + /* */ + /* Add the prediction and store it back to same buffer */ + /*--------------------------------------------------------------*/ + /* z0j = y0j + y2j */ + temp0 = _mm_add_epi32(resq_r0, resq_r2); + /* z1j = y0j - y2j */ + temp1 = _mm_sub_epi32(resq_r0, resq_r2); + /* z2j = (y1j>>1) - y3j */ + temp2 = _mm_srai_epi32(resq_r1, 1); //(y1j>>1) + temp2 = _mm_sub_epi32(temp2, resq_r3); + /* z3j = y1j + (y3j>>1) */ + temp3 = _mm_srai_epi32(resq_r3, 1); //(y3j>>1) + temp3 = _mm_add_epi32(temp3, resq_r1); + + /* x0j = z0j + z3j */ + temp4 = _mm_add_epi32(temp0, temp3); + temp4 = _mm_add_epi32(temp4, value_32); + temp4 = _mm_srai_epi32(temp4, 6); + temp4 = _mm_add_epi32(temp4, pred_r0); + /* x1j = z1j + z2j */ + temp5 = _mm_add_epi32(temp1, temp2); + temp5 = _mm_add_epi32(temp5, value_32); + temp5 = _mm_srai_epi32(temp5, 6); + temp5 = _mm_add_epi32(temp5, pred_r1); + /* x2j = z1j - z2j */ + temp6 = _mm_sub_epi32(temp1, temp2); + temp6 = _mm_add_epi32(temp6, value_32); + temp6 = _mm_srai_epi32(temp6, 6); + temp6 = _mm_add_epi32(temp6, pred_r2); + /* x3j = z0j - z3j */ + temp7 = _mm_sub_epi32(temp0, temp3); + temp7 = _mm_add_epi32(temp7, value_32); + temp7 = _mm_srai_epi32(temp7, 6); + temp7 = _mm_add_epi32(temp7, pred_r3); + + // 32-bit to 16-bit conversion + temp0 = _mm_packs_epi32(temp4, temp5); + temp1 = _mm_packs_epi32(temp6, temp7); + /*------------------------------------------------------------------*/ + //Clipping the results to 8 bits + sign_reg = _mm_cmpgt_epi16(temp0, zero_8x16b); // sign check + temp0 = _mm_and_si128(temp0, sign_reg); + sign_reg = _mm_cmpgt_epi16(temp1, zero_8x16b); + temp1 = _mm_and_si128(temp1, sign_reg); + + resq_r0 = _mm_packus_epi16(temp0, temp1); + resq_r1 = _mm_srli_si128(resq_r0, 4); + resq_r2 = _mm_srli_si128(resq_r1, 4); + resq_r3 = _mm_srli_si128(resq_r2, 4); + + *pu4_out = _mm_cvtsi128_si32(resq_r0); + pu1_out += out_strd; + pu4_out = (UWORD32 *) (pu1_out); + *(pu4_out) = _mm_cvtsi128_si32(resq_r1); + pu1_out += out_strd; + pu4_out = (UWORD32 *) (pu1_out); + *(pu4_out) = _mm_cvtsi128_si32(resq_r2); + pu1_out += out_strd; + pu4_out = (UWORD32 *) (pu1_out); + *(pu4_out) = _mm_cvtsi128_si32(resq_r3); +} + +/* + ******************************************************************************** + * + * @brief This function reconstructs a 4x4 sub block from quantized chroma resiude and + * prediction buffer + * + * @par Description: + * The quantized residue is first inverse quantized, then inverse transformed. + * This inverse transformed content is added to the prediction buffer to recon- + * struct the end output + * + * @param[in] pi2_src + * quantized 4x4 block + * + * @param[in] pu1_pred + * prediction 4x4 block + * + * @param[out] pu1_out + * reconstructed 4x4 block + * + * @param[in] src_strd + * quantization buffer stride + * + * @param[in] pred_strd, + * Prediction buffer stride + * + * @param[in] out_strd + * recon buffer Stride + * + * @param[in] pu2_scaling_list + * pointer to scaling list + * + * @param[in] pu2_norm_adjust + * pointer to inverse scale matrix + * + * @param[in] u4_qp_div_6 + * Floor (qp/6) + * + * @param[in] pi4_tmp + * temporary buffer of size 1*16 + * + * @returns none + * + * @remarks none + * + ******************************************************************************* + */ +void ih264_iquant_itrans_recon_chroma_4x4_sse42(WORD16 *pi2_src, + UWORD8 *pu1_pred, + UWORD8 *pu1_out, + WORD32 pred_strd, + WORD32 out_strd, + const UWORD16 *pu2_iscal_mat, + const UWORD16 *pu2_weigh_mat, + UWORD32 u4_qp_div_6, + WORD16 *pi2_tmp, + WORD16 *pi2_dc_ld_addr) + { + __m128i src_r0_r1, src_r2_r3; + __m128i src_r0, src_r1, src_r2, src_r3; + __m128i scalemat_r0_r1, scalemat_r2_r3; + __m128i pred_r0, pred_r1, pred_r2, pred_r3; + __m128i sign_reg, dequant_r0_r1, dequant_r2_r3; + __m128i zero_8x16b = _mm_setzero_si128(); // all bits reset to zero + __m128i temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7; + __m128i resq_r0, resq_r1, resq_r2, resq_r3; + __m128i add_rshift = _mm_set1_epi32((u4_qp_div_6 < 4) ? (1 << (3 - u4_qp_div_6)) : 0); + __m128i value_32 = _mm_set1_epi32(32); + __m128i chroma_mask = _mm_set1_epi16 (0xFF); + __m128i out_r0, out_r1, out_r2, out_r3; + UNUSED (pi2_tmp); + + /*************************************************************/ + /* Dequantization of coefficients. Will be replaced by SIMD */ + /* operations on platform */ + /*************************************************************/ + src_r0_r1 = _mm_loadu_si128((__m128i *) (pi2_src)); //a00 a01 a02 a03 a10 a11 a12 a13 -- the source matrix 0th,1st row + src_r2_r3 = _mm_loadu_si128((__m128i *) (pi2_src + 8)); //a20 a21 a22 a23 a30 a31 a32 a33 -- the source matrix 2nd,3rd row + scalemat_r0_r1 = _mm_loadu_si128((__m128i *) (pu2_iscal_mat)); //b00 b01 b02 b03 b10 b11 b12 b13 -- the scaling matrix 0th,1st row + scalemat_r2_r3 = _mm_loadu_si128((__m128i *) (pu2_iscal_mat + 8)); //b20 b21 b22 b23 b30 b31 b32 b33 -- the scaling matrix 2nd,3rd row + dequant_r0_r1 = _mm_loadu_si128((__m128i *) (pu2_weigh_mat)); //q00 q01 q02 q03 q10 q11 q12 q13 -- all 16 bits + dequant_r2_r3 = _mm_loadu_si128((__m128i *) (pu2_weigh_mat + 8)); //q20 q21 q22 q23 q30 q31 q32 q33 -- all 16 bits + + temp0 = _mm_mullo_epi16(scalemat_r0_r1, dequant_r0_r1); //b00*q00 b01*q01 b02*q02 b03*q03 b10*q10 b11*q11 b12*q12 b13*q13 -- 16 bit result + temp1 = _mm_mullo_epi16(scalemat_r2_r3, dequant_r2_r3); //b00*q00 b01*q01 b02*q02 b03*q03 b10*q10 b11*q11 b12*q12 b13*q13 -- 16 bit result + + temp4 = _mm_unpacklo_epi16(temp0, zero_8x16b); // b00*q00 0 b01*q01 0 b02*q02 0 b03*q03 0 -- 16 bit long + temp5 = _mm_unpackhi_epi16(temp0, zero_8x16b); // b10*q10 0 b11*q11 0 b12*q12 0 b13*q13 0 -- 16 bit long + temp6 = _mm_unpacklo_epi16(temp1, zero_8x16b); // b00*q00 0 b01*q01 0 b02*q02 0 b03*q03 0 -- 16 bit long + temp7 = _mm_unpackhi_epi16(temp1, zero_8x16b); // b10*q10 0 b11*q11 0 b12*q12 0 b13*q13 0 -- 16 bit long + + src_r0 = _mm_unpacklo_epi16(src_r0_r1, zero_8x16b); // a00 0 a01 0 a02 0 a03 0 -- 16 bit long + src_r1 = _mm_unpackhi_epi16(src_r0_r1, zero_8x16b); // a10 0 a11 0 a12 0 a13 0 -- 16 bit long + src_r2 = _mm_unpacklo_epi16(src_r2_r3, zero_8x16b); // a20 0 a21 0 a22 0 a23 0 -- 16 bit long + src_r3 = _mm_unpackhi_epi16(src_r2_r3, zero_8x16b); // a30 0 a31 0 a32 0 a33 0 -- 16 bit long + + temp4 = _mm_madd_epi16(src_r0, temp4); //a00*b00*q00 a10*b10*q10 a20*b20*q20 a30*b30 q30 -- 32 bits long + temp5 = _mm_madd_epi16(src_r1, temp5); + temp6 = _mm_madd_epi16(src_r2, temp6); + temp7 = _mm_madd_epi16(src_r3, temp7); + + if (u4_qp_div_6 >= 4) { + resq_r0 = _mm_slli_epi32(temp4, u4_qp_div_6 - 4); + resq_r1 = _mm_slli_epi32(temp5, u4_qp_div_6 - 4); + resq_r2 = _mm_slli_epi32(temp6, u4_qp_div_6 - 4); + resq_r3 = _mm_slli_epi32(temp7, u4_qp_div_6 - 4); + } else { + temp4 = _mm_add_epi32(temp4, add_rshift); + temp5 = _mm_add_epi32(temp5, add_rshift); + temp6 = _mm_add_epi32(temp6, add_rshift); + temp7 = _mm_add_epi32(temp7, add_rshift); + resq_r0 = _mm_srai_epi32(temp4, 4 - u4_qp_div_6); + resq_r1 = _mm_srai_epi32(temp5, 4 - u4_qp_div_6); + resq_r2 = _mm_srai_epi32(temp6, 4 - u4_qp_div_6); + resq_r3 = _mm_srai_epi32(temp7, 4 - u4_qp_div_6); + } + + resq_r0 = _mm_insert_epi32(resq_r0,(WORD32)pi2_dc_ld_addr[0],0); + /* Perform Inverse transform */ + /*-------------------------------------------------------------*/ + /* IDCT [ Horizontal transformation ] */ + /*-------------------------------------------------------------*/ + // Matrix transpose + /* + * a0 a1 a2 a3 + * b0 b1 b2 b3 + * c0 c1 c2 c3 + * d0 d1 d2 d3 + */ + temp1 = _mm_unpacklo_epi32(resq_r0, resq_r1); //a0 b0 a1 b1 + temp3 = _mm_unpacklo_epi32(resq_r2, resq_r3); //c0 d0 c1 d1 + temp2 = _mm_unpackhi_epi32(resq_r0, resq_r1); //a2 b2 a3 b3 + temp4 = _mm_unpackhi_epi32(resq_r2, resq_r3); //c2 d2 c3 d3 + resq_r0 = _mm_unpacklo_epi64(temp1, temp3); //a0 b0 c0 d0 + resq_r1 = _mm_unpackhi_epi64(temp1, temp3); //a1 b1 c1 d1 + resq_r2 = _mm_unpacklo_epi64(temp2, temp4); //a2 b2 c2 d2 + resq_r3 = _mm_unpackhi_epi64(temp2, temp4); //a3 b3 c3 d3 + //Transform starts -- horizontal transform + /*------------------------------------------------------------------*/ + /* z0 = w0 + w2 */ + temp0 = _mm_add_epi32(resq_r0, resq_r2); + /* z1 = w0 - w2 */ + temp1 = _mm_sub_epi32(resq_r0, resq_r2); + /* z2 = (w1 >> 1) - w3 */ + temp2 = _mm_srai_epi32(resq_r1, 1); //(w1>>1) + temp2 = _mm_sub_epi32(temp2, resq_r3); //(w1>>1) - w3 + /* z3 = w1 + (w3 >> 1) */ + temp3 = _mm_srai_epi32(resq_r3, 1); //(w3>>1) + w1 + temp3 = _mm_add_epi32(temp3, resq_r1); + /*----------------------------------------------------------*/ + /* x0 = z0 + z3 */ + resq_r0 = _mm_add_epi32(temp0, temp3); + /* x1 = z1 + z2 */ + resq_r1 = _mm_add_epi32(temp1, temp2); + /* x2 = z1 - z2 */ + resq_r2 = _mm_sub_epi32(temp1, temp2); + /* x3 = z0 - z3 */ + resq_r3 = _mm_sub_epi32(temp0, temp3); + // Matrix transpose + /* + * a0 b0 c0 d0 + * a1 b1 c1 d1 + * a2 b2 c2 d2 + * a3 b3 c3 d3 + */ + temp1 = _mm_unpacklo_epi32(resq_r0, resq_r1); //a0 a1 b0 b1 + temp3 = _mm_unpacklo_epi32(resq_r2, resq_r3); //a2 a3 b2 b3 + temp2 = _mm_unpackhi_epi32(resq_r0, resq_r1); //c0 c1 d0 d1 + temp4 = _mm_unpackhi_epi32(resq_r2, resq_r3); //c2 c3 d2 d3 + resq_r0 = _mm_unpacklo_epi64(temp1, temp3); //a0 a1 a2 a3 + resq_r1 = _mm_unpackhi_epi64(temp1, temp3); //b0 b1 b2 b3 + resq_r2 = _mm_unpacklo_epi64(temp2, temp4); //c0 c1 c2 c3 + resq_r3 = _mm_unpackhi_epi64(temp2, temp4); //d0 d1 d2 d3 + //Transform ends -- horizontal transform + + //Load pred buffer + pred_r0 = _mm_loadl_epi64((__m128i *) (&pu1_pred[0])); //p00 p01 p02 p03 0 0 0 0 0 0 0 0 -- all 8 bits + pred_r1 = _mm_loadl_epi64((__m128i *) (&pu1_pred[pred_strd])); //p10 p11 p12 p13 0 0 0 0 0 0 0 0 -- all 8 bits + pred_r2 = _mm_loadl_epi64((__m128i *) (&pu1_pred[2 * pred_strd])); //p20 p21 p22 p23 0 0 0 0 0 0 0 0 -- all 8 bits + pred_r3 = _mm_loadl_epi64((__m128i *) (&pu1_pred[3 * pred_strd])); //p30 p31 p32 p33 0 0 0 0 0 0 0 0 -- all 8 bits + + pred_r0 = _mm_and_si128(pred_r0, chroma_mask); + pred_r1 = _mm_and_si128(pred_r1, chroma_mask); + pred_r2 = _mm_and_si128(pred_r2, chroma_mask); + pred_r3 = _mm_and_si128(pred_r3, chroma_mask); + + pred_r0 = _mm_cvtepu16_epi32(pred_r0); //p00 p01 p02 p03 -- all 32 bits + pred_r1 = _mm_cvtepu16_epi32(pred_r1); //p10 p11 p12 p13 -- all 32 bits + pred_r2 = _mm_cvtepu16_epi32(pred_r2); //p20 p21 p22 p23 -- all 32 bits + pred_r3 = _mm_cvtepu16_epi32(pred_r3); //p30 p31 p32 p33 -- all 32 bits + + /*--------------------------------------------------------------*/ + /* IDCT [ Vertical transformation] and Xij = (xij + 32)>>6 */ + /* */ + /* Add the prediction and store it back to same buffer */ + /*--------------------------------------------------------------*/ + /* z0j = y0j + y2j */ + temp0 = _mm_add_epi32(resq_r0, resq_r2); + /* z1j = y0j - y2j */ + temp1 = _mm_sub_epi32(resq_r0, resq_r2); + /* z2j = (y1j>>1) - y3j */ + temp2 = _mm_srai_epi32(resq_r1, 1); //(y1j>>1) + temp2 = _mm_sub_epi32(temp2, resq_r3); + /* z3j = y1j + (y3j>>1) */ + temp3 = _mm_srai_epi32(resq_r3, 1); //(y3j>>1) + temp3 = _mm_add_epi32(temp3, resq_r1); + + /* x0j = z0j + z3j */ + temp4 = _mm_add_epi32(temp0, temp3); + temp4 = _mm_add_epi32(temp4, value_32); + temp4 = _mm_srai_epi32(temp4, 6); + temp4 = _mm_add_epi32(temp4, pred_r0); + /* x1j = z1j + z2j */ + temp5 = _mm_add_epi32(temp1, temp2); + temp5 = _mm_add_epi32(temp5, value_32); + temp5 = _mm_srai_epi32(temp5, 6); + temp5 = _mm_add_epi32(temp5, pred_r1); + /* x2j = z1j - z2j */ + temp6 = _mm_sub_epi32(temp1, temp2); + temp6 = _mm_add_epi32(temp6, value_32); + temp6 = _mm_srai_epi32(temp6, 6); + temp6 = _mm_add_epi32(temp6, pred_r2); + /* x3j = z0j - z3j */ + temp7 = _mm_sub_epi32(temp0, temp3); + temp7 = _mm_add_epi32(temp7, value_32); + temp7 = _mm_srai_epi32(temp7, 6); + temp7 = _mm_add_epi32(temp7, pred_r3); + + // 32-bit to 16-bit conversion + temp0 = _mm_packs_epi32(temp4, temp5); + temp1 = _mm_packs_epi32(temp6, temp7); + /*------------------------------------------------------------------*/ + //Clipping the results to 8 bits + sign_reg = _mm_cmpgt_epi16(temp0, zero_8x16b); // sign check + temp0 = _mm_and_si128(temp0, sign_reg); + sign_reg = _mm_cmpgt_epi16(temp1, zero_8x16b); + temp1 = _mm_and_si128(temp1, sign_reg); + + resq_r0 = _mm_packus_epi16(temp0, temp1); + resq_r1 = _mm_srli_si128(resq_r0, 4); + resq_r2 = _mm_srli_si128(resq_r1, 4); + resq_r3 = _mm_srli_si128(resq_r2, 4); + + resq_r0 = _mm_cvtepu8_epi16(resq_r0); //p00 p01 p02 p03 -- all 16 bits + resq_r1 = _mm_cvtepu8_epi16(resq_r1); //p10 p11 p12 p13 -- all 16 bits + resq_r2 = _mm_cvtepu8_epi16(resq_r2); //p20 p21 p22 p23 -- all 16 bits + resq_r3 = _mm_cvtepu8_epi16(resq_r3); //p30 p31 p32 p33 -- all 16 bits + + chroma_mask = _mm_set1_epi16 (0xFF00); + out_r0 = _mm_loadl_epi64((__m128i *) (&pu1_out[0])); + out_r1 = _mm_loadl_epi64((__m128i *) (&pu1_out[out_strd])); + out_r2 = _mm_loadl_epi64((__m128i *) (&pu1_out[2 * out_strd])); + out_r3 = _mm_loadl_epi64((__m128i *) (&pu1_out[3 * out_strd])); + + out_r0 = _mm_and_si128(out_r0, chroma_mask); + out_r1 = _mm_and_si128(out_r1, chroma_mask); + out_r2 = _mm_and_si128(out_r2, chroma_mask); + out_r3 = _mm_and_si128(out_r3, chroma_mask); + + out_r0 = _mm_add_epi8(out_r0, resq_r0); + out_r1 = _mm_add_epi8(out_r1, resq_r1); + out_r2 = _mm_add_epi8(out_r2, resq_r2); + out_r3 = _mm_add_epi8(out_r3, resq_r3); + + _mm_storel_epi64((__m128i *)(&pu1_out[0]), out_r0); + _mm_storel_epi64((__m128i *)(&pu1_out[out_strd]), out_r1); + _mm_storel_epi64((__m128i *)(&pu1_out[2 * out_strd]), out_r2); + _mm_storel_epi64((__m128i *)(&pu1_out[3 * out_strd]), out_r3); +} diff --git a/dependencies/ih264d/common/x86/ih264_iquant_itrans_recon_ssse3.c b/dependencies/ih264d/common/x86/ih264_iquant_itrans_recon_ssse3.c new file mode 100644 index 00000000..506be495 --- /dev/null +++ b/dependencies/ih264d/common/x86/ih264_iquant_itrans_recon_ssse3.c @@ -0,0 +1,1040 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** + ******************************************************************************* + * @file + * ih264_iquant_itrans_recon_ssse3.c + * + * @brief + * Contains function definitions for inverse quantization, inverse + * transform and reconstruction + * + * @author + * Mohit [100664] + * + * @par List of Functions: + * - ih264_iquant_itrans_recon_4x4_ssse3() + * - ih264_iquant_itrans_recon_8x8_ssse3() + * + * @remarks + * None + * + ******************************************************************************* + */ +/* User include files */ +#include "ih264_typedefs.h" +#include "ih264_defs.h" +#include "ih264_trans_macros.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264_trans_data.h" +#include "ih264_size_defs.h" +#include "ih264_structs.h" +#include "ih264_trans_quant_itrans_iquant.h" +#include <immintrin.h> + +/* + ******************************************************************************** + * + * @brief This function reconstructs a 4x4 sub block from quantized resiude and + * prediction buffer + * + * @par Description: + * The quantized residue is first inverse quantized, then inverse transformed. + * This inverse transformed content is added to the prediction buffer to recon- + * struct the end output + * + * @param[in] pi2_src + * quantized 4x4 block + * + * @param[in] pu1_pred + * prediction 4x4 block + * + * @param[out] pu1_out + * reconstructed 4x4 block + * + * @param[in] src_strd + * quantization buffer stride + * + * @param[in] pred_strd, + * Prediction buffer stride + * + * @param[in] out_strd + * recon buffer Stride + * + * @param[in] pu2_scaling_list + * pointer to scaling list + * + * @param[in] pu2_norm_adjust + * pointer to inverse scale matrix + * + * @param[in] u4_qp_div_6 + * Floor (qp/6) + * + * @param[in] pi4_tmp + * temporary buffer of size 1*16 + * + * @returns none + * + * @remarks none + * + ******************************************************************************* + */ +void ih264_iquant_itrans_recon_4x4_ssse3(WORD16 *pi2_src, + UWORD8 *pu1_pred, + UWORD8 *pu1_out, + WORD32 pred_strd, + WORD32 out_strd, + const UWORD16 *pu2_iscal_mat, + const UWORD16 *pu2_weigh_mat, + UWORD32 u4_qp_div_6, + WORD16 *pi2_tmp, + WORD32 iq_start_idx, + WORD16 *pi2_dc_ld_addr) +{ + UWORD32 *pu4_out = (UWORD32 *) pu1_out; + __m128i src_r0_r1, src_r2_r3; + __m128i src_r0, src_r1, src_r2, src_r3; + __m128i scalemat_r0_r1, scalemat_r2_r3, predload_r; + __m128i pred_r0, pred_r1, pred_r2, pred_r3; + __m128i sign_reg, dequant_r0_r1, dequant_r2_r3; + __m128i zero_8x16b = _mm_setzero_si128(); // all bits reset to zero + __m128i temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7; + __m128i resq_r0, resq_r1, resq_r2, resq_r3; + __m128i add_rshift = _mm_set1_epi32((u4_qp_div_6 < 4) ? (1 << (3 - u4_qp_div_6)) : 0); + __m128i value_32 = _mm_set1_epi32(32); + UNUSED (pi2_tmp); + UNUSED (pi2_dc_ld_addr); + + /*************************************************************/ + /* Dequantization of coefficients. Will be replaced by SIMD */ + /* operations on platform */ + /*************************************************************/ + src_r0_r1 = _mm_loadu_si128((__m128i *) (pi2_src)); //a00 a01 a02 a03 a10 a11 a12 a13 -- the source matrix 0th,1st row + src_r2_r3 = _mm_loadu_si128((__m128i *) (pi2_src + 8)); //a20 a21 a22 a23 a30 a31 a32 a33 -- the source matrix 2nd,3rd row + scalemat_r0_r1 = _mm_loadu_si128((__m128i *) (pu2_iscal_mat)); //b00 b01 b02 b03 b10 b11 b12 b13 -- the scaling matrix 0th,1st row + scalemat_r2_r3 = _mm_loadu_si128((__m128i *) (pu2_iscal_mat + 8)); //b20 b21 b22 b23 b30 b31 b32 b33 -- the scaling matrix 2nd,3rd row + dequant_r0_r1 = _mm_loadu_si128((__m128i *) (pu2_weigh_mat)); //q00 q01 q02 q03 q10 q11 q12 q13 -- all 16 bits + dequant_r2_r3 = _mm_loadu_si128((__m128i *) (pu2_weigh_mat + 8)); //q20 q21 q22 q23 q30 q31 q32 q33 -- all 16 bits + + temp0 = _mm_mullo_epi16(scalemat_r0_r1, dequant_r0_r1); //b00*q00 b01*q01 b02*q02 b03*q03 b10*q10 b11*q11 b12*q12 b13*q13 -- 16 bit result + temp1 = _mm_mullo_epi16(scalemat_r2_r3, dequant_r2_r3); //b00*q00 b01*q01 b02*q02 b03*q03 b10*q10 b11*q11 b12*q12 b13*q13 -- 16 bit result + + temp4 = _mm_unpacklo_epi16(temp0, zero_8x16b); // b00*q00 0 b01*q01 0 b02*q02 0 b03*q03 0 -- 16 bit long + temp5 = _mm_unpackhi_epi16(temp0, zero_8x16b); // b10*q10 0 b11*q11 0 b12*q12 0 b13*q13 0 -- 16 bit long + temp6 = _mm_unpacklo_epi16(temp1, zero_8x16b); // b00*q00 0 b01*q01 0 b02*q02 0 b03*q03 0 -- 16 bit long + temp7 = _mm_unpackhi_epi16(temp1, zero_8x16b); // b10*q10 0 b11*q11 0 b12*q12 0 b13*q13 0 -- 16 bit long + + src_r0 = _mm_unpacklo_epi16(src_r0_r1, zero_8x16b); // a00 0 a01 0 a02 0 a03 0 -- 16 bit long + src_r1 = _mm_unpackhi_epi16(src_r0_r1, zero_8x16b); // a10 0 a11 0 a12 0 a13 0 -- 16 bit long + src_r2 = _mm_unpacklo_epi16(src_r2_r3, zero_8x16b); // a20 0 a21 0 a22 0 a23 0 -- 16 bit long + src_r3 = _mm_unpackhi_epi16(src_r2_r3, zero_8x16b); // a30 0 a31 0 a32 0 a33 0 -- 16 bit long + + temp4 = _mm_madd_epi16(src_r0, temp4); //a00*b00*q00 a10*b10*q10 a20*b20*q20 a30*b30 q30 -- 32 bits long + temp5 = _mm_madd_epi16(src_r1, temp5); + temp6 = _mm_madd_epi16(src_r2, temp6); + temp7 = _mm_madd_epi16(src_r3, temp7); + + if (u4_qp_div_6 >= 4) { + resq_r0 = _mm_slli_epi32(temp4, u4_qp_div_6 - 4); + resq_r1 = _mm_slli_epi32(temp5, u4_qp_div_6 - 4); + resq_r2 = _mm_slli_epi32(temp6, u4_qp_div_6 - 4); + resq_r3 = _mm_slli_epi32(temp7, u4_qp_div_6 - 4); + } else { + temp4 = _mm_add_epi32(temp4, add_rshift); + temp5 = _mm_add_epi32(temp5, add_rshift); + temp6 = _mm_add_epi32(temp6, add_rshift); + temp7 = _mm_add_epi32(temp7, add_rshift); + resq_r0 = _mm_srai_epi32(temp4, 4 - u4_qp_div_6); + resq_r1 = _mm_srai_epi32(temp5, 4 - u4_qp_div_6); + resq_r2 = _mm_srai_epi32(temp6, 4 - u4_qp_div_6); + resq_r3 = _mm_srai_epi32(temp7, 4 - u4_qp_div_6); + } + + if (iq_start_idx == 1) + { + resq_r0 = _mm_insert_epi16(resq_r0,(WORD32)pi2_src[0],0); + if (pi2_src[0] >= 0) + resq_r0 = _mm_insert_epi16(resq_r0,0,1); + else + resq_r0 = _mm_insert_epi16(resq_r0,-1,1); + } + /* Perform Inverse transform */ + /*-------------------------------------------------------------*/ + /* IDCT [ Horizontal transformation ] */ + /*-------------------------------------------------------------*/ + // Matrix transpose + /* + * a0 a1 a2 a3 + * b0 b1 b2 b3 + * c0 c1 c2 c3 + * d0 d1 d2 d3 + */ + temp1 = _mm_unpacklo_epi32(resq_r0, resq_r1); //a0 b0 a1 b1 + temp3 = _mm_unpacklo_epi32(resq_r2, resq_r3); //c0 d0 c1 d1 + temp2 = _mm_unpackhi_epi32(resq_r0, resq_r1); //a2 b2 a3 b3 + temp4 = _mm_unpackhi_epi32(resq_r2, resq_r3); //c2 d2 c3 d3 + resq_r0 = _mm_unpacklo_epi64(temp1, temp3); //a0 b0 c0 d0 + resq_r1 = _mm_unpackhi_epi64(temp1, temp3); //a1 b1 c1 d1 + resq_r2 = _mm_unpacklo_epi64(temp2, temp4); //a2 b2 c2 d2 + resq_r3 = _mm_unpackhi_epi64(temp2, temp4); //a3 b3 c3 d3 + //Transform starts -- horizontal transform + /*------------------------------------------------------------------*/ + /* z0 = w0 + w2 */ + temp0 = _mm_add_epi32(resq_r0, resq_r2); + /* z1 = w0 - w2 */ + temp1 = _mm_sub_epi32(resq_r0, resq_r2); + /* z2 = (w1 >> 1) - w3 */ + temp2 = _mm_srai_epi32(resq_r1, 1); //(w1>>1) + temp2 = _mm_sub_epi32(temp2, resq_r3); //(w1>>1) - w3 + /* z3 = w1 + (w3 >> 1) */ + temp3 = _mm_srai_epi32(resq_r3, 1); //(w3>>1) + w1 + temp3 = _mm_add_epi32(temp3, resq_r1); + /*----------------------------------------------------------*/ + /* x0 = z0 + z3 */ + resq_r0 = _mm_add_epi32(temp0, temp3); + /* x1 = z1 + z2 */ + resq_r1 = _mm_add_epi32(temp1, temp2); + /* x2 = z1 - z2 */ + resq_r2 = _mm_sub_epi32(temp1, temp2); + /* x3 = z0 - z3 */ + resq_r3 = _mm_sub_epi32(temp0, temp3); + // Matrix transpose + /* + * a0 b0 c0 d0 + * a1 b1 c1 d1 + * a2 b2 c2 d2 + * a3 b3 c3 d3 + */ + temp1 = _mm_unpacklo_epi32(resq_r0, resq_r1); //a0 a1 b0 b1 + temp3 = _mm_unpacklo_epi32(resq_r2, resq_r3); //a2 a3 b2 b3 + temp2 = _mm_unpackhi_epi32(resq_r0, resq_r1); //c0 c1 d0 d1 + temp4 = _mm_unpackhi_epi32(resq_r2, resq_r3); //c2 c3 d2 d3 + resq_r0 = _mm_unpacklo_epi64(temp1, temp3); //a0 a1 a2 a3 + resq_r1 = _mm_unpackhi_epi64(temp1, temp3); //b0 b1 b2 b3 + resq_r2 = _mm_unpacklo_epi64(temp2, temp4); //c0 c1 c2 c3 + resq_r3 = _mm_unpackhi_epi64(temp2, temp4); //d0 d1 d2 d3 + //Transform ends -- horizontal transform + + zero_8x16b = _mm_setzero_si128(); // all bits reset to zero + //Load pred buffer + predload_r = _mm_loadl_epi64((__m128i *) (&pu1_pred[0])); //p00 p01 p02 p03 0 0 0 0 0 0 0 0 -- all 8 bits + pred_r0 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p00 p01 p02 p03 0 0 0 0 -- all 16 bits + + predload_r = _mm_loadl_epi64((__m128i *) (&pu1_pred[pred_strd])); //p10 p11 p12 p13 0 0 0 0 0 0 0 0 -- all 8 bits + pred_r1 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p10 p11 p12 p13 0 0 0 0 -- all 16 bits + + predload_r = _mm_loadl_epi64((__m128i *) (&pu1_pred[2 * pred_strd])); //p20 p21 p22 p23 0 0 0 0 0 0 0 0 -- all 8 bits + pred_r2 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p20 p21 p22 p23 0 0 0 0 -- all 16 bits + + predload_r = _mm_loadl_epi64((__m128i *) (&pu1_pred[3 * pred_strd])); //p30 p31 p32 p33 0 0 0 0 0 0 0 0 -- all 8 bits + pred_r3 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p30 p31 p32 p33 0 0 0 0 -- all 16 bits + pred_r0 = _mm_unpacklo_epi16(pred_r0, zero_8x16b); //p00 p01 p02 p03 -- 32 bits sign extended + pred_r1 = _mm_unpacklo_epi16(pred_r1, zero_8x16b); //p10 p11 p12 p13 -- 32 bits sign extended + pred_r2 = _mm_unpacklo_epi16(pred_r2, zero_8x16b); //p20 p21 p22 p23 -- 32 bits sign extended + pred_r3 = _mm_unpacklo_epi16(pred_r3, zero_8x16b); //p30 p31 p32 p33 -- 32 bits sign extended + + /*--------------------------------------------------------------*/ + /* IDCT [ Vertical transformation] and Xij = (xij + 32)>>6 */ + /* */ + /* Add the prediction and store it back to same buffer */ + /*--------------------------------------------------------------*/ + /* z0j = y0j + y2j */ + temp0 = _mm_add_epi32(resq_r0, resq_r2); + /* z1j = y0j - y2j */ + temp1 = _mm_sub_epi32(resq_r0, resq_r2); + /* z2j = (y1j>>1) - y3j */ + temp2 = _mm_srai_epi32(resq_r1, 1); //(y1j>>1) + temp2 = _mm_sub_epi32(temp2, resq_r3); + /* z3j = y1j + (y3j>>1) */ + temp3 = _mm_srai_epi32(resq_r3, 1); //(y3j>>1) + temp3 = _mm_add_epi32(temp3, resq_r1); + + /* x0j = z0j + z3j */ + temp4 = _mm_add_epi32(temp0, temp3); + temp4 = _mm_add_epi32(temp4, value_32); + temp4 = _mm_srai_epi32(temp4, 6); + temp4 = _mm_add_epi32(temp4, pred_r0); + /* x1j = z1j + z2j */ + temp5 = _mm_add_epi32(temp1, temp2); + temp5 = _mm_add_epi32(temp5, value_32); + temp5 = _mm_srai_epi32(temp5, 6); + temp5 = _mm_add_epi32(temp5, pred_r1); + /* x2j = z1j - z2j */ + temp6 = _mm_sub_epi32(temp1, temp2); + temp6 = _mm_add_epi32(temp6, value_32); + temp6 = _mm_srai_epi32(temp6, 6); + temp6 = _mm_add_epi32(temp6, pred_r2); + /* x3j = z0j - z3j */ + temp7 = _mm_sub_epi32(temp0, temp3); + temp7 = _mm_add_epi32(temp7, value_32); + temp7 = _mm_srai_epi32(temp7, 6); + temp7 = _mm_add_epi32(temp7, pred_r3); + + // 32-bit to 16-bit conversion + temp0 = _mm_packs_epi32(temp4, temp5); + temp1 = _mm_packs_epi32(temp6, temp7); + /*------------------------------------------------------------------*/ + //Clipping the results to 8 bits + sign_reg = _mm_cmpgt_epi16(temp0, zero_8x16b); // sign check + temp0 = _mm_and_si128(temp0, sign_reg); + sign_reg = _mm_cmpgt_epi16(temp1, zero_8x16b); + temp1 = _mm_and_si128(temp1, sign_reg); + + resq_r0 = _mm_packus_epi16(temp0, temp1); + resq_r1 = _mm_srli_si128(resq_r0, 4); + resq_r2 = _mm_srli_si128(resq_r1, 4); + resq_r3 = _mm_srli_si128(resq_r2, 4); + + *pu4_out = _mm_cvtsi128_si32(resq_r0); + pu1_out += out_strd; + pu4_out = (UWORD32 *) (pu1_out); + *(pu4_out) = _mm_cvtsi128_si32(resq_r1); + pu1_out += out_strd; + pu4_out = (UWORD32 *) (pu1_out); + *(pu4_out) = _mm_cvtsi128_si32(resq_r2); + pu1_out += out_strd; + pu4_out = (UWORD32 *) (pu1_out); + *(pu4_out) = _mm_cvtsi128_si32(resq_r3); +} +/** + ******************************************************************************* + * + * @brief + * This function performs inverse quant and Inverse transform type Ci4 for 8x8 block + * + * @par Description: + * Performs inverse transform Ci8 and adds the residue to get the + * reconstructed block + * + * @param[in] pi2_src + * Input 8x8coefficients + * + * @param[in] pu1_pred + * Prediction 8x8 block + * + * @param[out] pu1_recon + * Output 8x8 block + * + * @param[in] q_div + * QP/6 + * + * @param[in] q_rem + * QP%6 + * + * @param[in] q_lev + * Quantizer level + * + * @param[in] u4_src_stride + * Input stride + * + * @param[in] u4_pred_stride, + * Prediction stride + * + * @param[in] u4_out_stride + * Output Stride + * + * @param[in] pi4_tmp + * temporary buffer of size 1*64 + * the tmp for each block + * + * @param[in] pu4_iquant_mat + * Pointer to the inverse quantization matrix + * + * @returns Void + * + * @remarks + * None + * + ******************************************************************************* + */ + +void ih264_iquant_itrans_recon_8x8_ssse3(WORD16 *pi2_src, + UWORD8 *pu1_pred, + UWORD8 *pu1_out, + WORD32 pred_strd, + WORD32 out_strd, + const UWORD16 *pu2_iscale_mat, + const UWORD16 *pu2_weigh_mat, + UWORD32 qp_div, + WORD16 *pi2_tmp, + WORD32 iq_start_idx, + WORD16 *pi2_dc_ld_addr) +{ + __m128i src_r0; + __m128i scalemat_r0; + __m128i zero_8x16b = _mm_setzero_si128(); // all bits reset to zero + // __m128i one_8x16b = _mm_set1_epi8(255); // all bits set to 1 + // __m128i one_zero_mask = _mm_unpacklo_epi16(one_8x16b, zero_8x16b); // 1 0 1 0 1 0 1 0 --- 16 bits size + __m128i value_32 = _mm_set1_epi32(32); + __m128i add_rshift = _mm_set1_epi32((qp_div < 6) ? (1 << (5 - qp_div)) : 0); + __m128i dequant_r0; + __m128i predload_r; + __m128i pred_r0_1, pred_r1_1, pred_r2_1, pred_r3_1, pred_r4_1, pred_r5_1, + pred_r6_1, pred_r7_1; + __m128i sign_reg; + __m128i src_r0_1, src_r0_2; + __m128i scalemat_r0_1, scalemat_r0_2; + __m128i temp1, temp2, temp3, temp4, temp5, temp6, temp7, temp8; + __m128i temp10, temp11, temp12, temp13, temp14, temp15, temp16, temp17, + temp18, temp19, temp20; + // To store dequantization results + __m128i resq_r0_1, resq_r0_2, resq_r1_1, resq_r1_2, resq_r2_1, resq_r2_2, + resq_r3_1, resq_r3_2, resq_r4_1, resq_r4_2, resq_r5_1, resq_r5_2, + resq_r6_1, resq_r6_2, resq_r7_1, resq_r7_2; + UNUSED (pi2_tmp); + UNUSED (iq_start_idx); + UNUSED (pi2_dc_ld_addr); + + /*************************************************************/ + /* Dequantization of coefficients. Will be replaced by SIMD */ + /* operations on platform. Note : DC coeff is not scaled */ + /*************************************************************/ + + // Row 0 processing + src_r0 = _mm_loadu_si128((__m128i *) (pi2_src)); //a00 a01 a02 a03 a04 a05 a06 a07 -- the source matrix 0th row + scalemat_r0 = _mm_loadu_si128((__m128i *) (pu2_iscale_mat)); //b00 b01 b02 b03 b04 b05 b06 b07 -- the scaling matrix 0th row + dequant_r0 = _mm_loadu_si128((__m128i *) (&pu2_weigh_mat[0])); //q0 q1 q2 q3 q4 q5 q6 q7 -- all 16 bits + src_r0_1 = _mm_unpacklo_epi16(src_r0, zero_8x16b); //a00 0 a01 0 a02 0 a03 0 -- 16 bit long + src_r0_2 = _mm_unpackhi_epi16(src_r0, zero_8x16b); // a04 0 a05 0 a06 0 a07 0 -- 16 bit long + temp10 = _mm_mullo_epi16(scalemat_r0, dequant_r0); //b00*q0 b01*q1 b02*q2 b03*q3 b04*q4 b05*q5 b06*q6 b07*q7 -- 16 bit result + scalemat_r0_1 = _mm_unpacklo_epi16(temp10, zero_8x16b); // b00*q0 0 b01*q1 0 b02*q2 0 b03*q3 0 -- 16 bit long + scalemat_r0_2 = _mm_unpackhi_epi16(temp10, zero_8x16b); // b04*q4 0 b05*q5 0 b06*q6 0 b07*q7 0 -- 16 bit long + + temp5 = _mm_madd_epi16(src_r0_1, scalemat_r0_1); // a00*b00*q0 a01*b01*q1 a02*b02*q2 a03*b03*q3 -- 32 bits long + temp7 = _mm_madd_epi16(src_r0_2, scalemat_r0_2); // a04*b04*q4 a05*b05*q5 a06*b06*q6 a07*b07*q7 -- 32 bits long + + if (qp_div >= 6) { + resq_r0_1 = _mm_slli_epi32(temp5, qp_div - 6); + resq_r0_2 = _mm_slli_epi32(temp7, qp_div - 6); + } else { + temp5 = _mm_add_epi32(temp5, add_rshift); + temp7 = _mm_add_epi32(temp7, add_rshift); + resq_r0_1 = _mm_srai_epi32(temp5, 6 - qp_div); + resq_r0_2 = _mm_srai_epi32(temp7, 6 - qp_div); + } + resq_r0_1 = _mm_packs_epi32(resq_r0_1, resq_r0_2); //a00*b00*q0 a01*b01*q1 a02*b02*q2 a03*b03*q3 a04*b04*q4 a05*b05*q5 a06*b06*q6 a07*b07*q7 -- 16 bit long + // Row 1 processing + src_r0 = _mm_loadu_si128((__m128i *) (pi2_src + 8)); //a00 a01 a02 a03 a04 a05 a06 a07 a08 -- the source matrix 1st row + scalemat_r0 = _mm_loadu_si128((__m128i *) (pu2_iscale_mat + 8)); //b00 b01 b02 b03 b04 b05 b06 b07 b08 -- the scaling matrix 1st row + dequant_r0 = _mm_loadu_si128((__m128i *) (&pu2_weigh_mat[8])); //q0 q1 q2 q3 q4 q5 q6 q7 -- all 16 bits + src_r0_1 = _mm_unpacklo_epi16(src_r0, zero_8x16b); //a00 0 a01 0 a02 0 a03 0 -- 16 bit long + src_r0_2 = _mm_unpackhi_epi16(src_r0, zero_8x16b); // a04 0 a05 0 a06 0 a07 0 -- 16 bit long + temp10 = _mm_mullo_epi16(scalemat_r0, dequant_r0); //b00*q0 b01*q1 b02*q2 b03*q3 b04*q4 b05*q5 b06*q6 b07*q7 -- 16 bit result + scalemat_r0_1 = _mm_unpacklo_epi16(temp10, zero_8x16b); // b00*q0 0 b01*q1 0 b02*q2 0 b03*q3 0 -- 16 bit long + scalemat_r0_2 = _mm_unpackhi_epi16(temp10, zero_8x16b); // b04*q4 0 b05*q5 0 b06*q6 0 b07*q7 0 -- 16 bit long + temp5 = _mm_madd_epi16(src_r0_1, scalemat_r0_1); // a00*b00*q0 a01*b01*q1 a02*b02*q2 a03*b03*q3 -- 32 bits long + temp7 = _mm_madd_epi16(src_r0_2, scalemat_r0_2); // a04*b04*q4 a05*b05*q5 a06*b06*q6 a07*b07*q7 -- 32 bits long + if (qp_div >= 6) { + resq_r1_1 = _mm_slli_epi32(temp5, qp_div - 6); + resq_r1_2 = _mm_slli_epi32(temp7, qp_div - 6); + } else { + temp5 = _mm_add_epi32(temp5, add_rshift); + temp7 = _mm_add_epi32(temp7, add_rshift); + resq_r1_1 = _mm_srai_epi32(temp5, 6 - qp_div); + resq_r1_2 = _mm_srai_epi32(temp7, 6 - qp_div); + } + resq_r1_1 = _mm_packs_epi32(resq_r1_1, resq_r1_2); //a00*b00*q0 a01*b01*q1 a02*b02*q2 a03*b03*q3 a04*b04*q4 a05*b05*q5 a06*b06*q6 a07*b07*q7 -- 16 bit long + // Row 2 processing + src_r0 = _mm_loadu_si128((__m128i *) (pi2_src + 16)); //a00 a01 a02 a03 a04 a05 a06 a07 a08 -- the source matrix 2nd row + scalemat_r0 = _mm_loadu_si128((__m128i *) (pu2_iscale_mat + 16)); //b00 b01 b02 b03 b04 b05 b06 b07 b08 -- the scaling matrix 2nd row + dequant_r0 = _mm_loadu_si128((__m128i *) (&pu2_weigh_mat[16])); //q0 q1 q2 q3 q4 q5 q6 q7 -- all 16 bits + src_r0_1 = _mm_unpacklo_epi16(src_r0, zero_8x16b); //a00 0 a01 0 a02 0 a03 0 -- 16 bit long + src_r0_2 = _mm_unpackhi_epi16(src_r0, zero_8x16b); // a04 0 a05 0 a06 0 a07 0 -- 16 bit long + temp10 = _mm_mullo_epi16(scalemat_r0, dequant_r0); //b00*q0 b01*q1 b02*q2 b03*q3 b04*q4 b05*q5 b06*q6 b07*q7 -- 16 bit result + scalemat_r0_1 = _mm_unpacklo_epi16(temp10, zero_8x16b); // b00*q0 0 b01*q1 0 b02*q2 0 b03*q3 0 -- 16 bit long + scalemat_r0_2 = _mm_unpackhi_epi16(temp10, zero_8x16b); // b04*q4 0 b05*q5 0 b06*q6 0 b07*q7 0 -- 16 bit long + temp5 = _mm_madd_epi16(src_r0_1, scalemat_r0_1); // a00*b00*q0 a01*b01*q1 a02*b02*q2 a03*b03*q3 -- 32 bits long + temp7 = _mm_madd_epi16(src_r0_2, scalemat_r0_2); // a04*b04*q4 a05*b05*q5 a06*b06*q6 a07*b07*q7 -- 32 bits long + if (qp_div >= 6) { + resq_r2_1 = _mm_slli_epi32(temp5, qp_div - 6); + resq_r2_2 = _mm_slli_epi32(temp7, qp_div - 6); + } else { + temp5 = _mm_add_epi32(temp5, add_rshift); + temp7 = _mm_add_epi32(temp7, add_rshift); + resq_r2_1 = _mm_srai_epi32(temp5, 6 - qp_div); + resq_r2_2 = _mm_srai_epi32(temp7, 6 - qp_div); + } + resq_r2_1 = _mm_packs_epi32(resq_r2_1, resq_r2_2); //a00*b00*q0 a01*b01*q1 a02*b02*q2 a03*b03*q3 a04*b04*q4 a05*b05*q5 a06*b06*q6 a07*b07*q7 -- 16 bit long + // Row 3 processing + src_r0 = _mm_loadu_si128((__m128i *) (pi2_src + 24)); //a00 a01 a02 a03 a04 a05 a06 a07 a08 -- the source matrix 3rd row + scalemat_r0 = _mm_loadu_si128((__m128i *) (pu2_iscale_mat + 24)); //b00 b01 b02 b03 b04 b05 b06 b07 b08 -- the scaling matrix 3rd row + dequant_r0 = _mm_loadu_si128((__m128i *) (&pu2_weigh_mat[24])); //q0 q1 q2 q3 q4 q5 q6 q7 -- all 16 bits + src_r0_1 = _mm_unpacklo_epi16(src_r0, zero_8x16b); //a00 0 a01 0 a02 0 a03 0 -- 16 bit long + src_r0_2 = _mm_unpackhi_epi16(src_r0, zero_8x16b); // a04 0 a05 0 a06 0 a07 0 -- 16 bit long + temp10 = _mm_mullo_epi16(scalemat_r0, dequant_r0); //b00*q0 b01*q1 b02*q2 b03*q3 b04*q4 b05*q5 b06*q6 b07*q7 -- 16 bit result + scalemat_r0_1 = _mm_unpacklo_epi16(temp10, zero_8x16b); // b00*q0 0 b01*q1 0 b02*q2 0 b03*q3 0 -- 16 bit long + scalemat_r0_2 = _mm_unpackhi_epi16(temp10, zero_8x16b); // b04*q4 0 b05*q5 0 b06*q6 0 b07*q7 0 -- 16 bit long + temp5 = _mm_madd_epi16(src_r0_1, scalemat_r0_1); // a00*b00*q0 a01*b01*q1 a02*b02*q2 a03*b03*q3 - 32 bits long + temp7 = _mm_madd_epi16(src_r0_2, scalemat_r0_2); // a04*b04*q4 a05*b05*q5 a06*b06*q6 a07*b07*q7 -- 32 bits long + if (qp_div >= 6) { + resq_r3_1 = _mm_slli_epi32(temp5, qp_div - 6); + resq_r3_2 = _mm_slli_epi32(temp7, qp_div - 6); + } else { + temp5 = _mm_add_epi32(temp5, add_rshift); + temp7 = _mm_add_epi32(temp7, add_rshift); + resq_r3_1 = _mm_srai_epi32(temp5, 6 - qp_div); + resq_r3_2 = _mm_srai_epi32(temp7, 6 - qp_div); + } + resq_r3_1 = _mm_packs_epi32(resq_r3_1, resq_r3_2); //a00*b00*q0 a01*b01*q1 a02*b02*q2 a03*b03*q3 a04*b04*q4 a05*b05*q5 a06*b06*q6 a07*b07*q7 -- 16 bit long + // Row 4 processing + src_r0 = _mm_loadu_si128((__m128i *) (pi2_src + 32)); //a00 a01 a02 a03 a04 a05 a06 a07 a08 -- the source matrix 4th row + scalemat_r0 = _mm_loadu_si128((__m128i *) (pu2_iscale_mat + 32)); //b00 b01 b02 b03 b04 b05 b06 b07 b08 -- the scaling matrix 4th row + dequant_r0 = _mm_loadu_si128((__m128i *) (&pu2_weigh_mat[32])); //q0 q1 q2 q3 q4 q5 q6 q7 -- all 16 bits + src_r0_1 = _mm_unpacklo_epi16(src_r0, zero_8x16b); //a00 0 a01 0 a02 0 a03 0 -- 16 bit long + src_r0_2 = _mm_unpackhi_epi16(src_r0, zero_8x16b); // a04 0 a05 0 a06 0 a07 0 -- 16 bit long + temp10 = _mm_mullo_epi16(scalemat_r0, dequant_r0); //b00*q0 b01*q1 b02*q2 b03*q3 b04*q4 b05*q5 b06*q6 b07*q7 -- 16 bit result + scalemat_r0_1 = _mm_unpacklo_epi16(temp10, zero_8x16b); // b00*q0 0 b01*q1 0 b02*q2 0 b03*q3 0 -- 16 bit long + scalemat_r0_2 = _mm_unpackhi_epi16(temp10, zero_8x16b); // b04*q4 0 b05*q5 0 b06*q6 0 b07*q7 0 -- 16 bit long + temp5 = _mm_madd_epi16(src_r0_1, scalemat_r0_1); // a00*b00*q0 a01*b01*q1 a02*b02*q2 a03*b03*q3 -- 32 bits long + temp7 = _mm_madd_epi16(src_r0_2, scalemat_r0_2); // a04*b04*q4 a05*b05*q5 a06*b06*q6 a07*b07*q7 -- 32 bits long + if (qp_div >= 6) { + resq_r4_1 = _mm_slli_epi32(temp5, qp_div - 6); + resq_r4_2 = _mm_slli_epi32(temp7, qp_div - 6); + + } else { + temp5 = _mm_add_epi32(temp5, add_rshift); + temp7 = _mm_add_epi32(temp7, add_rshift); + resq_r4_1 = _mm_srai_epi32(temp5, 6 - qp_div); + resq_r4_2 = _mm_srai_epi32(temp7, 6 - qp_div); + } + resq_r4_1 = _mm_packs_epi32(resq_r4_1, resq_r4_2); //a00*b00*q0 a01*b01*q1 a02*b02*q2 a03*b03*q3 a04*b04*q4 a05*b05*q5 a06*b06*q6 a07*b07*q7 -- 16 bit long + // Row 5 processing + src_r0 = _mm_loadu_si128((__m128i *) (pi2_src + 40)); //a00 a01 a02 a03 a04 a05 a06 a07 a08 -- the source matrix 5th row + scalemat_r0 = _mm_loadu_si128((__m128i *) (pu2_iscale_mat + 40)); //b00 b01 b02 b03 b04 b05 b06 b07 b08 -- the scaling matrix 5th row + dequant_r0 = _mm_loadu_si128((__m128i *) (&pu2_weigh_mat[40])); //q0 q1 q2 q3 q4 q5 q6 q7 -- all 16 bits + src_r0_1 = _mm_unpacklo_epi16(src_r0, zero_8x16b); //a00 0 a01 0 a02 0 a03 0 -- 16 bit long + src_r0_2 = _mm_unpackhi_epi16(src_r0, zero_8x16b); // a04 0 a05 0 a06 0 a07 0 -- 16 bit long + temp10 = _mm_mullo_epi16(scalemat_r0, dequant_r0); //b00*q0 b01*q1 b02*q2 b03*q3 b04*q4 b05*q5 b06*q6 b07*q7 -- 16 bit result + scalemat_r0_1 = _mm_unpacklo_epi16(temp10, zero_8x16b); // b00*q0 0 b01*q1 0 b02*q2 0 b03*q3 0 -- 16 bit long + scalemat_r0_2 = _mm_unpackhi_epi16(temp10, zero_8x16b); // b04*q4 0 b05*q5 0 b06*q6 0 b07*q7 0 -- 16 bit long + temp5 = _mm_madd_epi16(src_r0_1, scalemat_r0_1); // a00*b00*q0 a01*b01*q1 a02*b02*q2 a03*b03*q3 -- 32 bits long + temp7 = _mm_madd_epi16(src_r0_2, scalemat_r0_2); // a04*b04*q4 a05*b05*q5 a06*b06*q6 a07*b07*q7 -- 32 bits long + if (qp_div >= 6) { + resq_r5_1 = _mm_slli_epi32(temp5, qp_div - 6); + resq_r5_2 = _mm_slli_epi32(temp7, qp_div - 6); + //resq_r5_1 = _mm_and_si128(resq_r5_1,one_zero_mask); + //resq_r5_2 = _mm_and_si128(resq_r5_2,one_zero_mask); + } else { + temp5 = _mm_add_epi32(temp5, add_rshift); + temp7 = _mm_add_epi32(temp7, add_rshift); + resq_r5_1 = _mm_srai_epi32(temp5, 6 - qp_div); + resq_r5_2 = _mm_srai_epi32(temp7, 6 - qp_div); + } + resq_r5_1 = _mm_packs_epi32(resq_r5_1, resq_r5_2); //a00*b00*q0 a01*b01*q1 a02*b02*q2 a03*b03*q3 a04*b04*q4 a05*b05*q5 a06*b06*q6 a07*b07*q7 -- 16 bit long + // Row 6 processing + src_r0 = _mm_loadu_si128((__m128i *) (pi2_src + 48)); //a00 a01 a02 a03 a04 a05 a06 a07 a08 -- the source matrix 6th row + scalemat_r0 = _mm_loadu_si128((__m128i *) (pu2_iscale_mat + 48)); //b00 b01 b02 b03 b04 b05 b06 b07 b08 -- the scaling matrix 6th row + dequant_r0 = _mm_loadu_si128((__m128i *) (&pu2_weigh_mat[48])); //q0 q1 q2 q3 q4 q5 q6 q7 -- all 16 bits + src_r0_1 = _mm_unpacklo_epi16(src_r0, zero_8x16b); //a00 0 a01 0 a02 0 a03 0 -- 16 bit long + src_r0_2 = _mm_unpackhi_epi16(src_r0, zero_8x16b); // a04 0 a05 0 a06 0 a07 0 -- 16 bit long + temp10 = _mm_mullo_epi16(scalemat_r0, dequant_r0); //b00*q0 b01*q1 b02*q2 b03*q3 b04*q4 b05*q5 b06*q6 b07*q7 -- 16 bit result + scalemat_r0_1 = _mm_unpacklo_epi16(temp10, zero_8x16b); // b00*q0 0 b01*q1 0 b02*q2 0 b03*q3 0 -- 16 bit long + scalemat_r0_2 = _mm_unpackhi_epi16(temp10, zero_8x16b); // b04*q4 0 b05*q5 0 b06*q6 0 b07*q7 0 -- 16 bit long + temp5 = _mm_madd_epi16(src_r0_1, scalemat_r0_1); // a00*b00*q0 a01*b01*q1 a02*b02*q2 a03*b03*q3 -- 32 bits long + temp7 = _mm_madd_epi16(src_r0_2, scalemat_r0_2); // a04*b04*q4 a05*b05*q5 a06*b06*q6 a07*b07*q7 -- 32 bits long + if (qp_div >= 6) { + resq_r6_1 = _mm_slli_epi32(temp5, qp_div - 6); + resq_r6_2 = _mm_slli_epi32(temp7, qp_div - 6); + //resq_r6_1 = _mm_and_si128(resq_r6_1,one_zero_mask); + //resq_r6_2 = _mm_and_si128(resq_r6_2,one_zero_mask); + } else { + temp5 = _mm_add_epi32(temp5, add_rshift); + temp7 = _mm_add_epi32(temp7, add_rshift); + resq_r6_1 = _mm_srai_epi32(temp5, 6 - qp_div); + resq_r6_2 = _mm_srai_epi32(temp7, 6 - qp_div); + //resq_r6_1 = _mm_and_si128(resq_r6_1,one_zero_mask); + //resq_r6_2 = _mm_and_si128(resq_r6_2,one_zero_mask); + } + resq_r6_1 = _mm_packs_epi32(resq_r6_1, resq_r6_2); //a00*b00*q0 a01*b01*q1 a02*b02*q2 a03*b03*q3 a04*b04*q4 a05*b05*q5 a06*b06*q6 a07*b07*q7 -- 16 bit long + // Row 7 processing + src_r0 = _mm_loadu_si128((__m128i *) (pi2_src + 56)); //a00 a01 a02 a03 a04 a05 a06 a07 a08 -- the source matrix 7th row + scalemat_r0 = _mm_loadu_si128((__m128i *) (pu2_iscale_mat + 56)); //b00 b01 b02 b03 b04 b05 b06 b07 b08 -- the scaling matrix 7th row + dequant_r0 = _mm_loadu_si128((__m128i *) (&pu2_weigh_mat[56])); //q0 q1 q2 q3 q4 q5 q6 q7 -- all 16 bits + src_r0_1 = _mm_unpacklo_epi16(src_r0, zero_8x16b); //a00 0 a01 0 a02 0 a03 0 -- 16 bit long + src_r0_2 = _mm_unpackhi_epi16(src_r0, zero_8x16b); // a04 0 a05 0 a06 0 a07 0 -- 16 bit long + temp10 = _mm_mullo_epi16(scalemat_r0, dequant_r0); //b00*q0 b01*q1 b02*q2 b03*q3 b04*q4 b05*q5 b06*q6 b07*q7 -- 16 bit result + scalemat_r0_1 = _mm_unpacklo_epi16(temp10, zero_8x16b); // b00*q0 0 b01*q1 0 b02*q2 0 b03*q3 0 -- 16 bit long + scalemat_r0_2 = _mm_unpackhi_epi16(temp10, zero_8x16b); // b04*q4 0 b05*q5 0 b06*q6 0 b07*q7 0 -- 16 bit long + temp5 = _mm_madd_epi16(src_r0_1, scalemat_r0_1); // a00*b00*q0 a01*b01*q1 a02*b02*q2 a03*b03*q3 -- 32 bits long + temp7 = _mm_madd_epi16(src_r0_2, scalemat_r0_2); // a04*b04*q4 a05*b05*q5 a06*b06*q6 a07*b07*q7 -- 32 bits long + if (qp_div >= 6) { + resq_r7_1 = _mm_slli_epi32(temp5, qp_div - 6); + resq_r7_2 = _mm_slli_epi32(temp7, qp_div - 6); + } else { + temp5 = _mm_add_epi32(temp5, add_rshift); + temp7 = _mm_add_epi32(temp7, add_rshift); + resq_r7_1 = _mm_srai_epi32(temp5, 6 - qp_div); + resq_r7_2 = _mm_srai_epi32(temp7, 6 - qp_div); + } + resq_r7_1 = _mm_packs_epi32(resq_r7_1, resq_r7_2); //a00*b00*q0 a01*b01*q1 a02*b02*q2 a03*b03*q3 a04*b04*q4 a05*b05*q5 a06*b06*q6 a07*b07*q7 -- 16 bit long + /* Perform Inverse transform */ + /*--------------------------------------------------------------------*/ + /* IDCT [ Horizontal transformation ] */ + /*--------------------------------------------------------------------*/ + // Matrix transpose + /* + * a0 a1 a2 a3 a4 a5 a6 a7 + * b0 b1 b2 b3 b4 b5 b6 b7 + * c0 c1 c2 c3 c4 c5 c6 c7 + * d0 d1 d2 d3 d4 d5 d6 d7 + */ + temp1 = _mm_unpacklo_epi16(resq_r0_1, resq_r1_1); //a0 b0 a1 b1 a2 b2 a3 b3 + temp3 = _mm_unpacklo_epi16(resq_r2_1, resq_r3_1); //c0 d0 c1 d1 c2 d2 c3 d3 + temp2 = _mm_unpackhi_epi16(resq_r0_1, resq_r1_1); //a4 b4 a5 b5 a6 b6 a7 b7 + temp4 = _mm_unpackhi_epi16(resq_r2_1, resq_r3_1); //c4 d4 c5 d5 c6 d6 c7 d7 + resq_r0_1 = _mm_unpacklo_epi32(temp1, temp3); //a0 b0 c0 d0 a1 b1 c1 d1 + resq_r1_1 = _mm_unpackhi_epi32(temp1, temp3); //a2 b2 c2 d2 a3 b3 c3 d3 + resq_r2_1 = _mm_unpacklo_epi32(temp2, temp4); //a4 b4 c4 d4 a5 b5 c5 d5 + resq_r3_1 = _mm_unpackhi_epi32(temp2, temp4); //a6 b6 c6 d6 a7 b7 c7 d7 + /* + * e0 e1 e2 e3 e4 e5 e6 e7 + * f0 f1 f2 f3 f4 f5 f6 f7 + * g0 g1 g2 g3 g4 g5 g6 g7 + * h0 h1 h2 h3 h4 h5 h6 h7 + */ + temp1 = _mm_unpacklo_epi16(resq_r4_1, resq_r5_1); //e0 f0 e1 f1 e2 f2 e2 f3 + temp3 = _mm_unpacklo_epi16(resq_r6_1, resq_r7_1); //g0 h0 g1 h1 g2 h2 g3 h3 + temp2 = _mm_unpackhi_epi16(resq_r4_1, resq_r5_1); //e4 f4 e5 f5 e6 f6 e7 f7 + temp4 = _mm_unpackhi_epi16(resq_r6_1, resq_r7_1); //g4 h4 g5 h5 g6 h6 g7 h7 + resq_r4_1 = _mm_unpacklo_epi32(temp1, temp3); //e0 f0 g0 h0 e1 f1 g1 h1 + resq_r5_1 = _mm_unpackhi_epi32(temp1, temp3); //e2 f2 g2 h2 e3 f3 g3 h3 + resq_r6_1 = _mm_unpacklo_epi32(temp2, temp4); //e4 f4 g4 h4 e5 f5 g5 h5 + resq_r7_1 = _mm_unpackhi_epi32(temp2, temp4); //e6 f6 g6 h6 e7 f7 g7 h7 + /* + * a0 b0 c0 d0 a1 b1 c1 d1 + * a2 b2 c2 d2 a3 b3 c3 d3 + * a4 b4 c4 d4 a5 b5 c5 d5 + * a6 b6 c6 d6 a7 b7 c7 d7 + * e0 f0 g0 h0 e1 f1 g1 h1 + * e2 f2 g2 h2 e3 f3 g3 h3 + * e4 f4 g4 h4 e5 f5 g5 h5 + * e6 f6 g6 h6 e7 f7 g7 h7 + */ + resq_r0_2 = _mm_unpacklo_epi64(resq_r0_1, resq_r4_1); //a0 b0 c0 d0 e0 f0 g0 h0 + resq_r1_2 = _mm_unpackhi_epi64(resq_r0_1, resq_r4_1); //a1 b1 c1 d1 e1 f1 g1 h1 + resq_r2_2 = _mm_unpacklo_epi64(resq_r1_1, resq_r5_1); //a2 b2 c2 d2 e2 f2 g2 h2 + resq_r3_2 = _mm_unpackhi_epi64(resq_r1_1, resq_r5_1); //a3 b3 c3 d3 e3 f3 g3 h3 + resq_r4_2 = _mm_unpacklo_epi64(resq_r2_1, resq_r6_1); //a4 b4 c4 d4 e4 f4 g4 h4 + resq_r5_2 = _mm_unpackhi_epi64(resq_r2_1, resq_r6_1); //a5 b5 c5 d5 e5 f5 g5 h5 + resq_r6_2 = _mm_unpacklo_epi64(resq_r3_1, resq_r7_1); //a6 b6 c6 d6 e6 f6 g6 h6 + resq_r7_2 = _mm_unpackhi_epi64(resq_r3_1, resq_r7_1); //a7 b7 c7 d7 e7 f7 g7 h7 + + sign_reg = _mm_cmpgt_epi16(zero_8x16b, resq_r1_2); + resq_r1_1 = _mm_unpacklo_epi16(resq_r1_2, sign_reg); //a1 b1 c1 d1 -- 32 bit + resq_r1_2 = _mm_unpackhi_epi16(resq_r1_2, sign_reg); //e1 f1 g1 h1 -- 32 bit + sign_reg = _mm_cmpgt_epi16(zero_8x16b, resq_r3_2); + resq_r3_1 = _mm_unpacklo_epi16(resq_r3_2, sign_reg); //a3 b3 c3 d3 -- 32 bit + resq_r3_2 = _mm_unpackhi_epi16(resq_r3_2, sign_reg); //e3 f3 g3 h3 -- 32 bit + sign_reg = _mm_cmpgt_epi16(zero_8x16b, resq_r5_2); + resq_r5_1 = _mm_unpacklo_epi16(resq_r5_2, sign_reg); //a5 b5 c5 d5 -- 32 bit + resq_r5_2 = _mm_unpackhi_epi16(resq_r5_2, sign_reg); //e5 f5 g5 h5 -- 32 bit + sign_reg = _mm_cmpgt_epi16(zero_8x16b, resq_r7_2); + resq_r7_1 = _mm_unpacklo_epi16(resq_r7_2, sign_reg); //a7 b7 c7 d7 -- 32 bit + resq_r7_2 = _mm_unpackhi_epi16(resq_r7_2, sign_reg); //e7 f7 g7 h7 -- 32 bit + //Transform starts -- horizontal transform + /*------------------------------------------------------------------*/ + /* y0 = w0 + w4 */ + temp1 = _mm_add_epi16(resq_r0_2, resq_r4_2); + /* y2 = w0 - w4 */ + temp3 = _mm_sub_epi16(resq_r0_2, resq_r4_2); + /* y1 = -w3 + w5 - w7 - (w7 >> 1) */ + temp2 = _mm_sub_epi32(resq_r5_1, resq_r3_1); //-w3+w5 + temp10 = _mm_sub_epi32(resq_r5_2, resq_r3_2); + temp4 = _mm_sub_epi32(temp2, resq_r7_1); //-w3+w5-w7 + temp12 = _mm_sub_epi32(temp10, resq_r7_2); + temp5 = _mm_srai_epi32(resq_r7_1, 1); //w7>>1 + temp13 = _mm_srai_epi32(resq_r7_2, 1); + temp2 = _mm_sub_epi32(temp4, temp5); //-w3+w5-w7 -(w7>>1) + temp10 = _mm_sub_epi32(temp12, temp13); + temp2 = _mm_packs_epi32(temp2, temp10); + /* y3 = w1 + w7 - w3 - (w3 >> 1) */ + temp4 = _mm_add_epi32(resq_r1_1, resq_r7_1); //w1+w7 + temp12 = _mm_add_epi32(resq_r1_2, resq_r7_2); + temp4 = _mm_sub_epi32(temp4, resq_r3_1); //w1+w7-w3 + temp12 = _mm_sub_epi32(temp12, resq_r3_2); + temp5 = _mm_srai_epi32(resq_r3_1, 1); //w3>>1 + temp13 = _mm_srai_epi32(resq_r3_2, 1); + temp4 = _mm_sub_epi32(temp4, temp5); //w1+w7-w3-(w3>>1) + temp12 = _mm_sub_epi32(temp12, temp13); + temp4 = _mm_packs_epi32(temp4, temp12); + /* y4 = (w2 >> 1) - w6 */ + temp5 = _mm_srai_epi16(resq_r2_2, 1); //w2>>1 + temp5 = _mm_sub_epi16(temp5, resq_r6_2); //(w2>>1)-w6 + /* y5 = -w1 + w7 + w5 + (w5 >> 1) */ + temp6 = _mm_sub_epi32(resq_r7_1, resq_r1_1); //w7-w1 + temp14 = _mm_sub_epi32(resq_r7_2, resq_r1_2); + temp6 = _mm_add_epi32(temp6, resq_r5_1); //w7-w1+w5 + temp14 = _mm_add_epi32(temp14, resq_r5_2); + temp7 = _mm_srai_epi32(resq_r5_1, 1); //w5>>1 + temp15 = _mm_srai_epi32(resq_r5_2, 1); + temp6 = _mm_add_epi32(temp6, temp7); //w7-w1_w5+(w5>>1) + temp14 = _mm_add_epi32(temp14, temp15); + temp6 = _mm_packs_epi32(temp6, temp14); + /* y6 = w2 + (w6 >> 1) */ + temp7 = _mm_srai_epi16(resq_r6_2, 1); //w6>>1 + temp7 = _mm_add_epi16(temp7, resq_r2_2); //(w6>>1)+w2 + /* y7 = w3 + w5 + w1 + (w1 >> 1) */ + temp8 = _mm_add_epi32(resq_r3_1, resq_r5_1); //w3+w5 + temp16 = _mm_add_epi32(resq_r3_2, resq_r5_2); + temp8 = _mm_add_epi32(temp8, resq_r1_1); //w3+w5+w1 + temp16 = _mm_add_epi32(temp16, resq_r1_2); + temp17 = _mm_srai_epi32(resq_r1_1, 1); //w1>>1 + temp18 = _mm_srai_epi32(resq_r1_2, 1); + temp8 = _mm_add_epi32(temp8, temp17); //w3+w5+w1+(w1>>1) + temp16 = _mm_add_epi32(temp16, temp18); + temp8 = _mm_packs_epi32(temp8, temp16); + /*------------------------------------------------------------------*/ + /*------------------------------------------------------------------*/ + /* z0 = y0 + y6 */ + resq_r0_1 = _mm_add_epi16(temp1, temp7); + /* z1 = y1 + (y7 >> 2) */ + resq_r1_1 = _mm_srai_epi16(temp8, 2); + resq_r1_1 = _mm_add_epi16(resq_r1_1, temp2); + /* z2 = y2 + y4 */ + resq_r2_1 = _mm_add_epi16(temp3, temp5); + /* z3 = y3 + (y5 >> 2) */ + resq_r3_1 = _mm_srai_epi16(temp6, 2); + resq_r3_1 = _mm_add_epi16(resq_r3_1, temp4); + /* z4 = y2 - y4 */ + resq_r4_1 = _mm_sub_epi16(temp3, temp5); + /* z5 = (y3 >> 2) - y5 */ + resq_r5_1 = _mm_srai_epi16(temp4, 2); + resq_r5_1 = _mm_sub_epi16(resq_r5_1, temp6); + /* z6 = y0 - y6 */ + resq_r6_1 = _mm_sub_epi16(temp1, temp7); + /* z7 = y7 - (y1 >> 2) */ + resq_r7_1 = _mm_srai_epi16(temp2, 2); + resq_r7_1 = _mm_sub_epi16(temp8, resq_r7_1); + /*------------------------------------------------------------------*/ + /*------------------------------------------------------------------*/ + /* x0 = z0 + z7 */ + temp1 = _mm_add_epi16(resq_r0_1, resq_r7_1); + /* x1 = z2 + z5 */ + temp2 = _mm_add_epi16(resq_r2_1, resq_r5_1); + /* x2 = z4 + z3 */ + temp3 = _mm_add_epi16(resq_r4_1, resq_r3_1); + /* x3 = z6 + z1 */ + temp4 = _mm_add_epi16(resq_r6_1, resq_r1_1); + /* x4 = z6 - z1 */ + temp5 = _mm_sub_epi16(resq_r6_1, resq_r1_1); + /* x5 = z4 - z3 */ + temp6 = _mm_sub_epi16(resq_r4_1, resq_r3_1); + /* x6 = z2 - z5 */ + temp7 = _mm_sub_epi16(resq_r2_1, resq_r5_1); + /* x7 = z0 - z7 */ + temp8 = _mm_sub_epi16(resq_r0_1, resq_r7_1); + /*------------------------------------------------------------------*/ + // Matrix transpose + /* + * a0 b0 c0 d0 e0 f0 g0 h0 + * a1 b1 c1 d1 e1 f1 g1 h1 + * a2 b2 c2 d2 e2 f2 g2 h2 + * a3 b3 c3 d3 e3 f3 g3 h3 + */ + temp17 = _mm_unpacklo_epi16(temp1, temp2); //a0 a1 b0 b1 c0 c1 d0 d1 + temp19 = _mm_unpacklo_epi16(temp3, temp4); //a2 a3 b2 b3 c2 c3 d2 d3 + temp18 = _mm_unpackhi_epi16(temp1, temp2); //e0 e1 f0 f1 g0 g1 h0 h1 + temp20 = _mm_unpackhi_epi16(temp3, temp4); //e2 e3 f2 f3 g2 g3 h2 h3 + + resq_r0_1 = _mm_unpacklo_epi32(temp17, temp19); //a0 a1 a2 a3 b0 b1 b2 b3 + resq_r1_1 = _mm_unpackhi_epi32(temp17, temp19); //c0 c1 c2 c3 d0 d1 d2 d3 + resq_r2_1 = _mm_unpacklo_epi32(temp18, temp20); //e0 e1 e2 e3 f0 f1 f2 f3 + resq_r3_1 = _mm_unpackhi_epi32(temp18, temp20); //g0 g2 g2 g3 h0 h1 h2 h3 + /* + * a4 b4 c4 d4 e4 f4 g4 h4 + * a5 b5 c5 d5 e5 f5 g5 h5 + * a6 b6 c6 d6 e6 f6 g6 h6 + * a7 b7 c7 d7 e7 f7 g7 h7 + */ + temp17 = _mm_unpacklo_epi16(temp5, temp6); //a4 a5 b4 b5 c4 c5 d4 d5 + temp19 = _mm_unpacklo_epi16(temp7, temp8); //a6 a7 b6 b7 c6 c7 d6 d7 + temp18 = _mm_unpackhi_epi16(temp5, temp6); //e4 e5 f4 f5 g4 g5 h4 h5 + temp20 = _mm_unpackhi_epi16(temp7, temp8); //e6 e7 f6 f7 g6 g7 h6 h7 + + resq_r4_1 = _mm_unpacklo_epi32(temp17, temp19); //a4 a5 a6 a7 b4 b5 b6 b7 + resq_r5_1 = _mm_unpackhi_epi32(temp17, temp19); //c4 c5 c6 c7 d4 d5 d6 d7 + resq_r6_1 = _mm_unpacklo_epi32(temp18, temp20); //e4 e5 e6 e7 f4 f5 f6 f7 + resq_r7_1 = _mm_unpackhi_epi32(temp18, temp20); //g4 g5 g6 g7 h4 h5 h6 h7 + /* a0 a1 a2 a3 b0 b1 b2 b3 + * c0 c1 c2 c3 d0 d1 d2 d3 + * e0 e1 e2 e3 f0 f1 f2 f3 + * g0 g2 g2 g3 h0 h1 h2 h3 + * a4 a5 a6 a7 b4 b5 b6 b7 + * c4 c5 c6 c7 d4 d5 d6 d7 + * e4 e5 e6 e7 f4 f5 f6 f7 + * g4 g5 g6 g7 h4 h5 h6 h7 + */ + resq_r0_2 = _mm_unpacklo_epi64(resq_r0_1, resq_r4_1); //a0 a1 a2 a3 a4 a5 a6 a7 + resq_r1_2 = _mm_unpackhi_epi64(resq_r0_1, resq_r4_1); //b0 b1 b2 b3 b4 b5 b6 b7 + resq_r2_2 = _mm_unpacklo_epi64(resq_r1_1, resq_r5_1); //c0 c1 c2 c3 c4 c5 c6 c7 + resq_r3_2 = _mm_unpackhi_epi64(resq_r1_1, resq_r5_1); //d0 d1 d2 d3 d4 d5 d6 d7 + resq_r4_2 = _mm_unpacklo_epi64(resq_r2_1, resq_r6_1); //e0 e1 e2 e3 e4 e5 e6 e7 + resq_r5_2 = _mm_unpackhi_epi64(resq_r2_1, resq_r6_1); //f0 f1 f2 f3 f4 f5 f6 f7 + resq_r6_2 = _mm_unpacklo_epi64(resq_r3_1, resq_r7_1); //g0 g1 g2 g3 g4 g5 g6 g7 + resq_r7_2 = _mm_unpackhi_epi64(resq_r3_1, resq_r7_1); //h0 h1 h2 h3 h4 h5 h6 h7 + + sign_reg = _mm_cmpgt_epi16(zero_8x16b, resq_r1_2); + resq_r1_1 = _mm_unpacklo_epi16(resq_r1_2, sign_reg); //a1 b1 c1 d1 -- 32 bit + resq_r1_2 = _mm_unpackhi_epi16(resq_r1_2, sign_reg); //e1 f1 g1 h1 -- 32 bit + sign_reg = _mm_cmpgt_epi16(zero_8x16b, resq_r3_2); + resq_r3_1 = _mm_unpacklo_epi16(resq_r3_2, sign_reg); //a3 b3 c3 d3 -- 32 bit + resq_r3_2 = _mm_unpackhi_epi16(resq_r3_2, sign_reg); //e3 f3 g3 h3 -- 32 bit + sign_reg = _mm_cmpgt_epi16(zero_8x16b, resq_r5_2); + resq_r5_1 = _mm_unpacklo_epi16(resq_r5_2, sign_reg); //a5 b5 c5 d5 -- 32 bit + resq_r5_2 = _mm_unpackhi_epi16(resq_r5_2, sign_reg); //e5 f5 g5 h5 -- 32 bit + sign_reg = _mm_cmpgt_epi16(zero_8x16b, resq_r7_2); + resq_r7_1 = _mm_unpacklo_epi16(resq_r7_2, sign_reg); //a7 b7 c7 d7 -- 32 bit + resq_r7_2 = _mm_unpackhi_epi16(resq_r7_2, sign_reg); //e7 f7 g7 h7 -- 32 bit + + zero_8x16b = _mm_setzero_si128(); // all bits reset to zero + //Load pred buffer row 0 + predload_r = _mm_loadl_epi64((__m128i *) (&pu1_pred[0])); //p0 p1 p2 p3 p4 p5 p6 p7 0 0 0 0 0 0 0 0 -- all 8 bits + pred_r0_1 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p0 p1 p2 p3 p4 p5 p6 p7 -- all 16 bits + //Load pred buffer row 1 + predload_r = _mm_loadl_epi64((__m128i *) (&pu1_pred[pred_strd])); //p0 p1 p2 p3 p4 p5 p6 p7 0 0 0 0 0 0 0 0 -- all 8 bits + pred_r1_1 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p0 p1 p2 p3 p4 p5 p6 p7 -- all 16 bits + //Load pred buffer row 2 + predload_r = _mm_loadl_epi64((__m128i *) (&pu1_pred[2 * pred_strd])); //p0 p1 p2 p3 p4 p5 p6 p7 0 0 0 0 0 0 0 0 -- all 8 bits + pred_r2_1 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p0 p1 p2 p3 p4 p5 p6 p7 -- all 16 bits + //Load pred buffer row 3 + predload_r = _mm_loadl_epi64((__m128i *) (&pu1_pred[3 * pred_strd])); //p0 p1 p2 p3 p4 p5 p6 p7 0 0 0 0 0 0 0 0 -- all 8 bits + pred_r3_1 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p0 p1 p2 p3 p4 p5 p6 p7 -- all 16 bits + //Load pred buffer row 4 + predload_r = _mm_loadl_epi64((__m128i *) (&pu1_pred[4 * pred_strd])); //p0 p1 p2 p3 p4 p5 p6 p7 0 0 0 0 0 0 0 0 -- all 8 bits + pred_r4_1 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p0 p1 p2 p3 p4 p5 p6 p7 -- all 16 bits + //Load pred buffer row 5 + predload_r = _mm_loadl_epi64((__m128i *) (&pu1_pred[5 * pred_strd])); //p0 p1 p2 p3 p4 p5 p6 p7 0 0 0 0 0 0 0 0 -- all 8 bit + pred_r5_1 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p0 p1 p2 p3 p4 p5 p6 p7 -- all 16 bits + //Load pred buffer row 6 + predload_r = _mm_loadl_epi64((__m128i *) (&pu1_pred[6 * pred_strd])); //p0 p1 p2 p3 p4 p5 p6 p7 0 0 0 0 0 0 0 0 -- all 8 bits + pred_r6_1 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p0 p1 p2 p3 p4 p5 p6 p7 -- all 16 bits + //Load pred buffer row 7 + predload_r = _mm_loadl_epi64((__m128i *) (&pu1_pred[7 * pred_strd])); //p0 p1 p2 p3 p4 p5 p6 p7 0 0 0 0 0 0 0 0 -- all 8 bits + pred_r7_1 = _mm_unpacklo_epi8(predload_r, zero_8x16b); //p0 p1 p2 p3 p4 p5 p6 p7 -- all 16 bits + + /*--------------------------------------------------------------------*/ + /* IDCT [ Vertical transformation] and Xij = (xij + 32)>>6 */ + /* */ + /* Add the prediction and store it back to reconstructed frame buffer */ + /* [Prediction buffer itself in this case] */ + /*--------------------------------------------------------------------*/ + + /* y0j = w0j + w4j */ + temp1 = _mm_add_epi16(resq_r0_2, resq_r4_2); + /* y2j = w0j - w4j */ + temp3 = _mm_sub_epi16(resq_r0_2, resq_r4_2); + /* y1j = -w3j + w5j - w7j - (w7j >> 1) */ + temp2 = _mm_sub_epi32(resq_r5_1, resq_r3_1); //-w3+w5 + temp10 = _mm_sub_epi32(resq_r5_2, resq_r3_2); + temp4 = _mm_sub_epi32(temp2, resq_r7_1); //-w3+w5-w7 + temp12 = _mm_sub_epi32(temp10, resq_r7_2); + temp5 = _mm_srai_epi32(resq_r7_1, 1); //w7>>1 + temp13 = _mm_srai_epi32(resq_r7_2, 1); + temp2 = _mm_sub_epi32(temp4, temp5); //-w3+w5-w7 -(w7>>1) + temp10 = _mm_sub_epi32(temp12, temp13); + temp2 = _mm_packs_epi32(temp2, temp10); + /* y3j = w1j + w7j - w3j - (w3j >> 1) */ + temp4 = _mm_add_epi32(resq_r1_1, resq_r7_1); //w1+w7 + temp12 = _mm_add_epi32(resq_r1_2, resq_r7_2); + temp4 = _mm_sub_epi32(temp4, resq_r3_1); //w1+w7-w3 + temp12 = _mm_sub_epi32(temp12, resq_r3_2); + temp5 = _mm_srai_epi32(resq_r3_1, 1); //w3>>1 + temp13 = _mm_srai_epi32(resq_r3_2, 1); + temp4 = _mm_sub_epi32(temp4, temp5); //w1+w7-w3-(w3>>1) + temp12 = _mm_sub_epi32(temp12, temp13); + temp4 = _mm_packs_epi32(temp4, temp12); + /* y4j = (w2j >> 1) - w6j */ + temp5 = _mm_srai_epi16(resq_r2_2, 1); //w2>>1 + temp5 = _mm_sub_epi16(temp5, resq_r6_2); //(w2>>1)-w6 + /* y5j = -w1j + w7j + w5j + (w5j >> 1) */ + temp6 = _mm_sub_epi32(resq_r7_1, resq_r1_1); //w7-w1 + temp14 = _mm_sub_epi32(resq_r7_2, resq_r1_2); + temp6 = _mm_add_epi32(temp6, resq_r5_1); //w7-w1+w5 + temp14 = _mm_add_epi32(temp14, resq_r5_2); + temp7 = _mm_srai_epi32(resq_r5_1, 1); //w5>>1 + temp15 = _mm_srai_epi32(resq_r5_2, 1); + temp6 = _mm_add_epi32(temp6, temp7); //w7-w1_w5+(w5>>1) + temp14 = _mm_add_epi32(temp14, temp15); + temp6 = _mm_packs_epi32(temp6, temp14); + /* y6j = w2j + (w6j >> 1) */ + temp7 = _mm_srai_epi16(resq_r6_2, 1); //w6>>1 + temp7 = _mm_add_epi16(temp7, resq_r2_2); //(w6>>1)+w2 + /* y7j = w3j + w5j + w1j + (w1j >> 1) */ + temp8 = _mm_add_epi32(resq_r3_1, resq_r5_1); //w3+w5 + temp16 = _mm_add_epi32(resq_r3_2, resq_r5_2); + temp8 = _mm_add_epi32(temp8, resq_r1_1); //w3+w5+w1 + temp16 = _mm_add_epi32(temp16, resq_r1_2); + temp17 = _mm_srai_epi32(resq_r1_1, 1); //w1>>1 + temp18 = _mm_srai_epi32(resq_r1_2, 1); + temp8 = _mm_add_epi32(temp8, temp17); //w3+w5+w1+(w1>>1) + temp16 = _mm_add_epi32(temp16, temp18); + temp8 = _mm_packs_epi32(temp8, temp16); + /*------------------------------------------------------------------*/ + /*------------------------------------------------------------------*/ + /* z0j = y0j + y6j */ + resq_r0_1 = _mm_add_epi16(temp1, temp7); + /* z1j = y1j + (y7j >> 2) */ + resq_r1_1 = _mm_srai_epi16(temp8, 2); + resq_r1_1 = _mm_add_epi16(resq_r1_1, temp2); + /* z2j = y2j + y4j */ + resq_r2_1 = _mm_add_epi16(temp3, temp5); + /* z3j = y3j + (y5j >> 2) */ + resq_r3_1 = _mm_srai_epi16(temp6, 2); + resq_r3_1 = _mm_add_epi16(resq_r3_1, temp4); + /* z4j = y2j - y4j */ + resq_r4_1 = _mm_sub_epi16(temp3, temp5); + /* z5j = (y3j >> 2) - y5j */ + resq_r5_1 = _mm_srai_epi16(temp4, 2); + resq_r5_1 = _mm_sub_epi16(resq_r5_1, temp6); + /* z6j = y0j - y6j */ + resq_r6_1 = _mm_sub_epi16(temp1, temp7); + /* z7j = y7j - (y1j >> 2) */ + resq_r7_1 = _mm_srai_epi16(temp2, 2); + resq_r7_1 = _mm_sub_epi16(temp8, resq_r7_1); + /*------------------------------------------------------------------*/ + + /*------------------------------------------------------------------*/ + /* x0j = z0j + z7j */ + temp1 = _mm_add_epi16(resq_r0_1, resq_r7_1); + sign_reg = _mm_cmpgt_epi16(zero_8x16b, temp1); + temp10 = _mm_unpacklo_epi16(temp1, sign_reg); + temp11 = _mm_unpackhi_epi16(temp1, sign_reg); + temp10 = _mm_add_epi32(temp10, value_32); + temp11 = _mm_add_epi32(temp11, value_32); + temp10 = _mm_srai_epi32(temp10, 6); + temp11 = _mm_srai_epi32(temp11, 6); + temp10 = _mm_packs_epi32(temp10, temp11); + temp1 = _mm_add_epi16(temp10, pred_r0_1); + /* x1j = z2j + z5j */ + temp2 = _mm_add_epi16(resq_r2_1, resq_r5_1); + sign_reg = _mm_cmpgt_epi16(zero_8x16b, temp2); + temp10 = _mm_unpacklo_epi16(temp2, sign_reg); + temp11 = _mm_unpackhi_epi16(temp2, sign_reg); + temp10 = _mm_add_epi32(temp10, value_32); + temp11 = _mm_add_epi32(temp11, value_32); + temp10 = _mm_srai_epi32(temp10, 6); + temp11 = _mm_srai_epi32(temp11, 6); + temp10 = _mm_packs_epi32(temp10, temp11); + temp2 = _mm_add_epi16(temp10, pred_r1_1); + /* x2j = z4j + z3j */ + temp3 = _mm_add_epi16(resq_r4_1, resq_r3_1); + sign_reg = _mm_cmpgt_epi16(zero_8x16b, temp3); + temp10 = _mm_unpacklo_epi16(temp3, sign_reg); + temp11 = _mm_unpackhi_epi16(temp3, sign_reg); + temp10 = _mm_add_epi32(temp10, value_32); + temp11 = _mm_add_epi32(temp11, value_32); + temp10 = _mm_srai_epi32(temp10, 6); + temp11 = _mm_srai_epi32(temp11, 6); + temp10 = _mm_packs_epi32(temp10, temp11); + temp3 = _mm_add_epi16(temp10, pred_r2_1); + /* x3j = z6j + z1j */ + temp4 = _mm_add_epi16(resq_r6_1, resq_r1_1); + sign_reg = _mm_cmpgt_epi16(zero_8x16b, temp4); + temp10 = _mm_unpacklo_epi16(temp4, sign_reg); + temp11 = _mm_unpackhi_epi16(temp4, sign_reg); + temp10 = _mm_add_epi32(temp10, value_32); + temp11 = _mm_add_epi32(temp11, value_32); + temp10 = _mm_srai_epi32(temp10, 6); + temp11 = _mm_srai_epi32(temp11, 6); + temp10 = _mm_packs_epi32(temp10, temp11); + temp4 = _mm_add_epi16(temp10, pred_r3_1); + /* x4j = z6j - z1j */ + temp5 = _mm_sub_epi16(resq_r6_1, resq_r1_1); + sign_reg = _mm_cmpgt_epi16(zero_8x16b, temp5); + temp10 = _mm_unpacklo_epi16(temp5, sign_reg); + temp11 = _mm_unpackhi_epi16(temp5, sign_reg); + temp10 = _mm_add_epi32(temp10, value_32); + temp11 = _mm_add_epi32(temp11, value_32); + temp10 = _mm_srai_epi32(temp10, 6); + temp11 = _mm_srai_epi32(temp11, 6); + temp10 = _mm_packs_epi32(temp10, temp11); + temp5 = _mm_add_epi16(temp10, pred_r4_1); + /* x5j = z4j - z3j */ + temp6 = _mm_sub_epi16(resq_r4_1, resq_r3_1); + sign_reg = _mm_cmpgt_epi16(zero_8x16b, temp6); + temp10 = _mm_unpacklo_epi16(temp6, sign_reg); + temp11 = _mm_unpackhi_epi16(temp6, sign_reg); + temp10 = _mm_add_epi32(temp10, value_32); + temp11 = _mm_add_epi32(temp11, value_32); + temp10 = _mm_srai_epi32(temp10, 6); + temp11 = _mm_srai_epi32(temp11, 6); + temp10 = _mm_packs_epi32(temp10, temp11); + temp6 = _mm_add_epi16(temp10, pred_r5_1); + /* x6j = z2j - z5j */ + temp7 = _mm_sub_epi16(resq_r2_1, resq_r5_1); + sign_reg = _mm_cmpgt_epi16(zero_8x16b, temp7); + temp10 = _mm_unpacklo_epi16(temp7, sign_reg); + temp11 = _mm_unpackhi_epi16(temp7, sign_reg); + temp10 = _mm_add_epi32(temp10, value_32); + temp11 = _mm_add_epi32(temp11, value_32); + temp10 = _mm_srai_epi32(temp10, 6); + temp11 = _mm_srai_epi32(temp11, 6); + temp10 = _mm_packs_epi32(temp10, temp11); + temp7 = _mm_add_epi16(temp10, pred_r6_1); + /* x7j = z0j - z7j */ + temp8 = _mm_sub_epi16(resq_r0_1, resq_r7_1); + sign_reg = _mm_cmpgt_epi16(zero_8x16b, temp8); + temp10 = _mm_unpacklo_epi16(temp8, sign_reg); + temp11 = _mm_unpackhi_epi16(temp8, sign_reg); + temp10 = _mm_add_epi32(temp10, value_32); + temp11 = _mm_add_epi32(temp11, value_32); + temp10 = _mm_srai_epi32(temp10, 6); + temp11 = _mm_srai_epi32(temp11, 6); + temp10 = _mm_packs_epi32(temp10, temp11); + temp8 = _mm_add_epi16(temp10, pred_r7_1); + /*------------------------------------------------------------------*/ + //Clipping the results to 8 bits + sign_reg = _mm_cmpgt_epi16(temp1, zero_8x16b); // sign check + temp1 = _mm_and_si128(temp1, sign_reg); + sign_reg = _mm_cmpgt_epi16(temp2, zero_8x16b); // sign check + temp2 = _mm_and_si128(temp2, sign_reg); + sign_reg = _mm_cmpgt_epi16(temp3, zero_8x16b); // sign check + temp3 = _mm_and_si128(temp3, sign_reg); + sign_reg = _mm_cmpgt_epi16(temp4, zero_8x16b); // sign check + temp4 = _mm_and_si128(temp4, sign_reg); + sign_reg = _mm_cmpgt_epi16(temp5, zero_8x16b); // sign check + temp5 = _mm_and_si128(temp5, sign_reg); + sign_reg = _mm_cmpgt_epi16(temp6, zero_8x16b); // sign check + temp6 = _mm_and_si128(temp6, sign_reg); + sign_reg = _mm_cmpgt_epi16(temp7, zero_8x16b); // sign check + temp7 = _mm_and_si128(temp7, sign_reg); + sign_reg = _mm_cmpgt_epi16(temp8, zero_8x16b); // sign check + temp8 = _mm_and_si128(temp8, sign_reg); + + resq_r0_2 = _mm_packus_epi16(temp1, zero_8x16b); + resq_r1_2 = _mm_packus_epi16(temp2, zero_8x16b); + resq_r2_2 = _mm_packus_epi16(temp3, zero_8x16b); + resq_r3_2 = _mm_packus_epi16(temp4, zero_8x16b); + resq_r4_2 = _mm_packus_epi16(temp5, zero_8x16b); + resq_r5_2 = _mm_packus_epi16(temp6, zero_8x16b); + resq_r6_2 = _mm_packus_epi16(temp7, zero_8x16b); + resq_r7_2 = _mm_packus_epi16(temp8, zero_8x16b); + + _mm_storel_epi64((__m128i *) (&pu1_out[0]), resq_r0_2); + _mm_storel_epi64((__m128i *) (&pu1_out[out_strd]), resq_r1_2); + _mm_storel_epi64((__m128i *) (&pu1_out[2 * out_strd]), resq_r2_2); + _mm_storel_epi64((__m128i *) (&pu1_out[3 * out_strd]), resq_r3_2); + _mm_storel_epi64((__m128i *) (&pu1_out[4 * out_strd]), resq_r4_2); + _mm_storel_epi64((__m128i *) (&pu1_out[5 * out_strd]), resq_r5_2); + _mm_storel_epi64((__m128i *) (&pu1_out[6 * out_strd]), resq_r6_2); + _mm_storel_epi64((__m128i *) (&pu1_out[7 * out_strd]), resq_r7_2); +} + diff --git a/dependencies/ih264d/common/x86/ih264_luma_intra_pred_filters_ssse3.c b/dependencies/ih264d/common/x86/ih264_luma_intra_pred_filters_ssse3.c new file mode 100644 index 00000000..a1721d52 --- /dev/null +++ b/dependencies/ih264d/common/x86/ih264_luma_intra_pred_filters_ssse3.c @@ -0,0 +1,2238 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** + ******************************************************************************* + * @file + * ih264_luma_intra_pred_filters_ssse3.c + * + * @brief + * Contains function definitions for luma intra prediction filters in x86 + * intrinsics + * + * @author + * Ittiam + * + * @par List of Functions: + * - ih264_intra_pred_luma_4x4_mode_vert_ssse3 + * - ih264_intra_pred_luma_4x4_mode_horz_ssse3 + * - ih264_intra_pred_luma_4x4_mode_dc_ssse3 + * - ih264_intra_pred_luma_4x4_mode_diag_dl_ssse3 + * - ih264_intra_pred_luma_4x4_mode_diag_dr_ssse3 + * - ih264_intra_pred_luma_4x4_mode_vert_r_ssse3 + * - ih264_intra_pred_luma_4x4_mode_horz_d_ssse3 + * - ih264_intra_pred_luma_4x4_mode_vert_l_ssse3 + * - ih264_intra_pred_luma_4x4_mode_horz_u_ssse3 + * - ih264_intra_pred_luma_8x8_mode_vert_ssse3 + * - ih264_intra_pred_luma_8x8_mode_horz_ssse3 + * - ih264_intra_pred_luma_8x8_mode_dc_ssse3 + * - ih264_intra_pred_luma_8x8_mode_diag_dl_ssse3 + * - ih264_intra_pred_luma_8x8_mode_diag_dr_ssse3 + * - ih264_intra_pred_luma_8x8_mode_vert_r_ssse3 + * - ih264_intra_pred_luma_8x8_mode_horz_d_ssse3 + * - ih264_intra_pred_luma_8x8_mode_vert_l_ssse3 + * - ih264_intra_pred_luma_8x8_mode_horz_u_ssse3 + * - ih264_intra_pred_luma_16x16_mode_vert_ssse3 + * - ih264_intra_pred_luma_16x16_mode_horz_ssse3 + * - ih264_intra_pred_luma_16x16_mode_dc_ssse3 + * - ih264_intra_pred_luma_16x16_mode_plane_ssse3 + * + * @remarks + * None + * + ****************************************************************************** + */ + +/*****************************************************************************/ +/* File Includes */ +/*****************************************************************************/ +/* System include files */ +#include <stdio.h> +#include <stddef.h> +#include <string.h> +#include <immintrin.h> + +/* User include files */ +#include "ih264_defs.h" +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264_intra_pred_filters.h" + + + +/******************* LUMA INTRAPREDICTION *******************/ + +/******************* 4x4 Modes *******************/ + +/** + ******************************************************************************* + * + * ih264_intra_pred_luma_4x4_mode_vert_ssse3 + * + * @brief + * Perform Intra prediction for luma_4x4 mode:vertical + * + * @par Description: + * Perform Intra prediction for luma_4x4 mode:vertical ,described in sec 8.3.1.2.1 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ngbr_avail + * availability of neighbouring pixels(Not used in this function) + * + * @returns + * + * @remarks + * None + * + ******************************************************************************* + */ +void ih264_intra_pred_luma_4x4_mode_vert_ssse3(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + UWORD8 *pu1_top; + WORD32 dst_strd2, dst_strd3; + WORD32 i4_top; + + UNUSED(src_strd); + UNUSED(ngbr_avail); + + pu1_top = pu1_src + BLK_SIZE + 1; + + i4_top = *((WORD32 *)pu1_top); + + dst_strd2 = dst_strd << 1; + dst_strd3 = dst_strd + dst_strd2; + + *((WORD32 *)(pu1_dst)) = i4_top; + *((WORD32 *)(pu1_dst + dst_strd)) = i4_top; + *((WORD32 *)(pu1_dst + dst_strd2)) = i4_top; + *((WORD32 *)(pu1_dst + dst_strd3)) = i4_top; +} + +/** + ******************************************************************************* + * + *ih264_intra_pred_luma_4x4_mode_horz_ssse3 + * + * @brief + * Perform Intra prediction for luma_4x4 mode:horizontal + * + * @par Description: + * Perform Intra prediction for luma_4x4 mode:horizontal ,described in sec 8.3.1.2.2 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ngbr_avail + * availability of neighbouring pixels(Not used in this function) + * + * @returns + * + * @remarks + * None + * + ******************************************************************************* + */ +void ih264_intra_pred_luma_4x4_mode_horz_ssse3(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + UWORD8 *pu1_left = NULL; /* Pointer to start of left predictors */ + WORD32 row1,row2,row3,row4; + UWORD8 val; + WORD32 dst_strd2, dst_strd3; + + UNUSED(src_strd); + UNUSED(ngbr_avail); + pu1_left = pu1_src + BLK_SIZE - 1; + + val = *pu1_left; + row1 = val + (val << 8) + (val << 16) + (val << 24); + val = *(pu1_left - 1); + row2 = val + (val << 8) + (val << 16) + (val << 24); + val = *(pu1_left - 2); + row3 = val + (val << 8) + (val << 16) + (val << 24); + val = *(pu1_left - 3); + row4 = val + (val << 8) + (val << 16) + (val << 24); + + dst_strd2 = dst_strd << 1; + dst_strd3 = dst_strd + dst_strd2; + + *((WORD32 *)(pu1_dst)) = row1; + *((WORD32 *)(pu1_dst + dst_strd)) = row2; + *((WORD32 *)(pu1_dst + dst_strd2)) = row3; + *((WORD32 *)(pu1_dst + dst_strd3)) = row4; +} + +/** + ******************************************************************************* + * + * ih264_intra_pred_luma_4x4_mode_dc_ssse3 + * + * @brief + * Perform Intra prediction for luma_4x4 mode:DC + * + * @par Description: + * Perform Intra prediction for luma_4x4 mode:DC ,described in sec 8.3.1.2.3 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ngbr_avail + * availability of neighbouring pixels + * + * @returns + * + * @remarks + * None + * + *******************************************************************************/ +void ih264_intra_pred_luma_4x4_mode_dc_ssse3(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + UWORD8 u1_useleft; /* availability of left predictors (only for DC) */ + UWORD8 u1_usetop; /* availability of top predictors (only for DC) */ + UWORD8 *pu1_left = NULL; /* Pointer to start of left predictors */ + UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ + WORD32 dst_strd2, dst_strd3; + WORD32 val = 0; + UNUSED(src_strd); + UNUSED(ngbr_avail); + u1_useleft = BOOLEAN(ngbr_avail & LEFT_MB_AVAILABLE_MASK); + u1_usetop = BOOLEAN(ngbr_avail & TOP_MB_AVAILABLE_MASK); + pu1_top = pu1_src + BLK_SIZE + 1; + pu1_left = pu1_src + BLK_SIZE - 1; + + if(u1_useleft) + { + val += *pu1_left--; + val += *pu1_left--; + val += *pu1_left--; + val += *pu1_left + 2; + } + if(u1_usetop) + { + val += *pu1_top + *(pu1_top + 1) + *(pu1_top + 2) + *(pu1_top + 3) + + 2; + } + /* Since 2 is added if either left/top pred is there, + val still being zero implies both preds are not there */ + val = (val) ? (val >> (1 + u1_useleft + u1_usetop)) : 128; + + val = val + (val << 8) + (val << 16) + (val << 24); + + dst_strd2 = dst_strd << 1; + dst_strd3 = dst_strd + dst_strd2; + + *((WORD32 *)(pu1_dst)) = val; + *((WORD32 *)(pu1_dst + dst_strd)) = val; + *((WORD32 *)(pu1_dst + dst_strd2)) = val; + *((WORD32 *)(pu1_dst + dst_strd3)) = val; +} + +/** + ******************************************************************************* + * + * ih264_intra_pred_luma_4x4_mode_diag_dl_ssse3 + * + * @brief + * Perform Intra prediction for luma_4x4 mode:Diagonal_Down_Left + * + * @par Description: + * Perform Intra prediction for luma_4x4 mode:Diagonal_Down_Left ,described in sec 8.3.1.2.4 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ngbr_avail + * availability of neighbouring pixels(Not used in this function) + * + * @returns + * + * @remarks + * None + * + *******************************************************************************/ +void ih264_intra_pred_luma_4x4_mode_diag_dl_ssse3(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + UWORD8 *pu1_top; + WORD32 dst_strd2, dst_strd3; + + __m128i top_16x8b, top_8x16b, top_sh_8x16b; + __m128i res1_8x16b, res2_8x16b, res_16x8b; + __m128i zero_vector, const_2_8x16b; + WORD32 row1,row2,row3,row4; + + UNUSED(src_strd); + UNUSED(ngbr_avail); + + pu1_top = pu1_src + BLK_SIZE + 1; + + top_16x8b = _mm_loadl_epi64((__m128i *)pu1_top); + zero_vector = _mm_setzero_si128(); + top_8x16b = _mm_unpacklo_epi8(top_16x8b, zero_vector); //t0 t1 t2 t3 t4 t5 t6 t7 + + top_sh_8x16b = _mm_srli_si128(top_8x16b, 2); //t1 t2 t3 t4 t5 t6 t7 0 + const_2_8x16b = _mm_set1_epi16(2); + + top_sh_8x16b = _mm_shufflehi_epi16(top_sh_8x16b, 0xa4); //t1 t2 t3 t4 t5 t6 t7 t7 + res1_8x16b = _mm_add_epi16(top_8x16b, top_sh_8x16b); + res2_8x16b = _mm_srli_si128(res1_8x16b, 2); + + res1_8x16b = _mm_add_epi16(res1_8x16b, const_2_8x16b); + res1_8x16b = _mm_add_epi16(res2_8x16b, res1_8x16b); + res1_8x16b = _mm_srai_epi16(res1_8x16b, 2); + + dst_strd2 = dst_strd << 1; + dst_strd3 = dst_strd + dst_strd2; + + res_16x8b = _mm_packus_epi16(res1_8x16b, res1_8x16b); + row1 = _mm_cvtsi128_si32(res_16x8b); + res_16x8b = _mm_srli_si128(res_16x8b, 1); + row2 = _mm_cvtsi128_si32(res_16x8b); + res_16x8b = _mm_srli_si128(res_16x8b, 1); + row3 = _mm_cvtsi128_si32(res_16x8b); + res_16x8b = _mm_srli_si128(res_16x8b, 1); + row4 = _mm_cvtsi128_si32(res_16x8b); + + *((WORD32 *)(pu1_dst)) = row1; + *((WORD32 *)(pu1_dst + dst_strd)) = row2; + *((WORD32 *)(pu1_dst + dst_strd2)) = row3; + *((WORD32 *)(pu1_dst + dst_strd3)) = row4; +} + +/** + ******************************************************************************* + * + * ih264_intra_pred_luma_4x4_mode_diag_dr_ssse3 + * + * @brief + * Perform Intra prediction for luma_4x4 mode:Diagonal_Down_Right + * + * @par Description: + * Perform Intra prediction for luma_4x4 mode:Diagonal_Down_Right ,described in sec 8.3.1.2.5 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ngbr_avail + * availability of neighbouring pixels(Not used in this function) + * + * @returns + * + * @remarks + * None + * + *******************************************************************************/ +void ih264_intra_pred_luma_4x4_mode_diag_dr_ssse3(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + UWORD8 *pu1_left; + WORD32 dst_strd2, dst_strd3; + + __m128i top_left_16x8b, top_left_8x16b; + __m128i top_left_sh_16x8b, top_left_sh_8x16b; + __m128i res1_8x16b, res2_8x16b; + __m128i res1_16x8b, res2_16x8b; + __m128i zero_vector, const_2_8x16b; + WORD32 row1,row2,row3,row4; + + UNUSED(src_strd); + UNUSED(ngbr_avail); + + pu1_left = pu1_src + BLK_SIZE - 1; + + top_left_16x8b = _mm_loadu_si128((__m128i *)(pu1_left - 3)); //l3 l2 l1 l0 tl t0 t1 t2... + zero_vector = _mm_setzero_si128(); + top_left_sh_16x8b = _mm_srli_si128(top_left_16x8b, 1); //l2 l1 l0 tl t0 t1 t2 t3... + + top_left_8x16b = _mm_unpacklo_epi8(top_left_16x8b, zero_vector); + top_left_sh_8x16b = _mm_unpacklo_epi8(top_left_sh_16x8b, zero_vector); + + res1_8x16b = _mm_add_epi16(top_left_8x16b, top_left_sh_8x16b); //l3+l2 l2+l1 l1+l0 l0+tl tl+t0 t0+t1 t1+t2 t2+t3... + const_2_8x16b = _mm_set1_epi16(2); + res2_8x16b = _mm_srli_si128(res1_8x16b, 2); //l2+l1 l1+l0 l0+tl tl+t0 t0+t1 t1+t2 t2+t3... + + res1_8x16b = _mm_add_epi16(res1_8x16b, const_2_8x16b); + res1_8x16b = _mm_add_epi16(res2_8x16b, res1_8x16b); //l3+2*l2+l1+2 l2+2*l1+l0+2... + res1_8x16b = _mm_srai_epi16(res1_8x16b, 2); + res1_16x8b = _mm_packus_epi16(res1_8x16b, res1_8x16b); + + dst_strd2 = dst_strd << 1; + dst_strd3 = dst_strd + dst_strd2; + + res2_16x8b = _mm_srli_si128(res1_16x8b, 3); + + row1 = _mm_cvtsi128_si32(res2_16x8b); + res2_16x8b = _mm_srli_si128(res1_16x8b, 2); + row2 = _mm_cvtsi128_si32(res2_16x8b); + res2_16x8b = _mm_srli_si128(res1_16x8b, 1); + row3 = _mm_cvtsi128_si32(res2_16x8b); + row4 = _mm_cvtsi128_si32(res1_16x8b); + + *((WORD32 *)(pu1_dst)) = row1; + *((WORD32 *)(pu1_dst + dst_strd)) = row2; + *((WORD32 *)(pu1_dst + dst_strd2)) = row3; + *((WORD32 *)(pu1_dst + dst_strd3)) = row4; +} + +/** + ******************************************************************************* + * + * ih264_intra_pred_luma_4x4_mode_vert_r_ssse3 + * + * @brief + * Perform Intra prediction for luma_4x4 mode:Vertical_Right + * + * @par Description: + * Perform Intra prediction for luma_4x4 mode:Vertical_Right ,described in sec 8.3.1.2.6 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ngbr_avail + * availability of neighbouring pixels(Not used in this function) + * + * @returns + * + * @remarks + * None + * + *******************************************************************************/ +void ih264_intra_pred_luma_4x4_mode_vert_r_ssse3(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + UWORD8 *pu1_left; + WORD32 dst_strd2, dst_strd3; + + __m128i val_16x8b, temp_16x8b; + __m128i w11_a1_16x8b, w11_a2_16x8b; + __m128i w121_a1_8x16b, w121_a2_8x16b, w121_sh_8x16b; + __m128i row1_16x8b, row2_16x8b, row3_16x8b, row4_16x8b; + __m128i zero_vector, const_2_8x16b; + WORD32 row1,row2,row3,row4; + + UNUSED(src_strd); + UNUSED(ngbr_avail); + + pu1_left = pu1_src + BLK_SIZE - 1; + + val_16x8b = _mm_loadl_epi64((__m128i *)(pu1_left - 2)); + zero_vector = _mm_setzero_si128(); + + w121_a1_8x16b = _mm_unpacklo_epi8(val_16x8b, zero_vector); //l2 l1 l0 tl t0 t1 t2 t3 + w11_a1_16x8b = _mm_srli_si128(val_16x8b, 3); + w121_a2_8x16b = _mm_srli_si128(w121_a1_8x16b, 2); //l1 l0 tl t0 t1 t2 t3 0 + w11_a2_16x8b = _mm_srli_si128(val_16x8b, 4); + + w121_a1_8x16b = _mm_add_epi16(w121_a1_8x16b, w121_a2_8x16b); //l2+l1 l1+l0 l0+tl tl+t0 t0+t1 t1+t2 t2+t3 t3 + row1_16x8b = _mm_avg_epu8(w11_a1_16x8b, w11_a2_16x8b); + w121_a2_8x16b = _mm_srli_si128(w121_a1_8x16b, 2); //l1+l0 l0+tl tl+t0 t0+t1 t1+t2 t2+t3 t3 0 + + const_2_8x16b = _mm_set1_epi16(2); + w121_a1_8x16b = _mm_add_epi16(w121_a1_8x16b, w121_a2_8x16b); //l2+2*l1+l0 l1+2*l0+tl ... + w121_a1_8x16b = _mm_add_epi16(w121_a1_8x16b, const_2_8x16b); + w121_a1_8x16b = _mm_srai_epi16(w121_a1_8x16b, 2); + + w121_sh_8x16b = _mm_shufflelo_epi16(w121_a1_8x16b, 0xe1); + w121_sh_8x16b = _mm_srli_si128(w121_sh_8x16b, 2); + + row4_16x8b = _mm_packus_epi16(w121_sh_8x16b, w121_sh_8x16b); + temp_16x8b = _mm_slli_si128(w121_a1_8x16b, 13); + row2_16x8b = _mm_srli_si128(row4_16x8b, 1); + row3_16x8b = _mm_alignr_epi8(row1_16x8b, temp_16x8b, 15); + + dst_strd2 = dst_strd << 1; + dst_strd3 = dst_strd + dst_strd2; + + row1 = _mm_cvtsi128_si32(row1_16x8b); + row2 = _mm_cvtsi128_si32(row2_16x8b); + row3 = _mm_cvtsi128_si32(row3_16x8b); + row4 = _mm_cvtsi128_si32(row4_16x8b); + + *((WORD32 *)(pu1_dst)) = row1; + *((WORD32 *)(pu1_dst + dst_strd)) = row2; + *((WORD32 *)(pu1_dst + dst_strd2)) = row3; + *((WORD32 *)(pu1_dst + dst_strd3)) = row4; +} + +/* + ******************************************************************************* + * + * ih264_intra_pred_luma_4x4_mode_horz_d_ssse3 + * + * @brief + * Perform Intra prediction for luma_4x4 mode:Horizontal_Down + * + * @par Description: + * Perform Intra prediction for luma_4x4 mode:Horizontal_Down ,described in sec 8.3.1.2.7 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ngbr_avail + * availability of neighbouring pixels(Not used in this function) + * + * @returns + * + * @remarks + * None + * + *******************************************************************************/ +void ih264_intra_pred_luma_4x4_mode_horz_d_ssse3(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + UWORD8 *pu1_left; + WORD32 dst_strd2, dst_strd3; + WORD32 val_121_t0t1; + + __m128i val_16x8b, val_sh_16x8b; + __m128i w11_16x8b; + __m128i w121_a1_8x16b, w121_a2_8x16b, w121_16x8b; + __m128i row1_16x8b, row2_16x8b, row3_16x8b, row4_16x8b; + + __m128i zero_vector, const_2_8x16b; + WORD32 row1,row2,row3,row4; + + UNUSED(src_strd); + UNUSED(ngbr_avail); + + pu1_left = pu1_src + BLK_SIZE - 1; + + val_16x8b = _mm_loadl_epi64((__m128i *)(pu1_left - 3)); + zero_vector = _mm_setzero_si128(); + val_sh_16x8b = _mm_srli_si128(val_16x8b, 1); + w11_16x8b = _mm_avg_epu8(val_16x8b, val_sh_16x8b); + + w121_a1_8x16b = _mm_unpacklo_epi8(val_16x8b, zero_vector); //l3 l2 l1 l0 tl t0 t1 t2 + w121_a2_8x16b = _mm_srli_si128(w121_a1_8x16b, 2); //l2 l1 l0 tl t0 t1 t2 0 + w121_a1_8x16b = _mm_add_epi16(w121_a1_8x16b, w121_a2_8x16b); //l3+l2 l2+l1 l1+l0 l0+tl tl+t0 t0+t1 t1+t2 t2 + w121_a2_8x16b = _mm_srli_si128(w121_a1_8x16b, 2); //l2+l1 l1+l0 l0+tl tl+t0 t0+t1 t1+t2 t2 0 + + zero_vector = _mm_setzero_si128(); + const_2_8x16b = _mm_set1_epi16(2); + + w121_a1_8x16b = _mm_add_epi16(w121_a1_8x16b, w121_a2_8x16b); //l3+2*l2+l1 l2+2*l1+l0 l1+2*l0+tl ... + w121_a1_8x16b = _mm_add_epi16(w121_a1_8x16b, const_2_8x16b); + w121_a1_8x16b = _mm_srai_epi16(w121_a1_8x16b, 2); + + w121_16x8b = _mm_packus_epi16(w121_a1_8x16b, w121_a1_8x16b); + + row4_16x8b = _mm_unpacklo_epi8(w11_16x8b, w121_16x8b); + val_121_t0t1 = _mm_extract_epi16(w121_16x8b, 2); + row4_16x8b = _mm_insert_epi16(row4_16x8b, val_121_t0t1, 4); + + dst_strd2 = dst_strd << 1; + dst_strd3 = dst_strd + dst_strd2; + + row1_16x8b = _mm_srli_si128(row4_16x8b, 6); + row2_16x8b = _mm_srli_si128(row4_16x8b, 4); + row3_16x8b = _mm_srli_si128(row4_16x8b, 2); + + row1 = _mm_cvtsi128_si32(row1_16x8b); + row2 = _mm_cvtsi128_si32(row2_16x8b); + row3 = _mm_cvtsi128_si32(row3_16x8b); + row4 = _mm_cvtsi128_si32(row4_16x8b); + + *((WORD32 *)(pu1_dst)) = row1; + *((WORD32 *)(pu1_dst + dst_strd)) = row2; + *((WORD32 *)(pu1_dst + dst_strd2)) = row3; + *((WORD32 *)(pu1_dst + dst_strd3)) = row4; +} + +/** + ******************************************************************************* + * + * ih264_intra_pred_luma_4x4_mode_vert_l_ssse3 + * + * @brief + * Perform Intra prediction for luma_4x4 mode:Vertical_Left + * + * @par Description: + * Perform Intra prediction for luma_4x4 mode:Vertical_Left ,described in sec 8.3.1.2.8 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ngbr_avail + * availability of neighbouring pixels(Not used in this function) + * + * @returns + * + * @remarks + * None + * + *******************************************************************************/ +void ih264_intra_pred_luma_4x4_mode_vert_l_ssse3(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + UWORD8 *pu1_top; + WORD32 dst_strd2, dst_strd3; + + __m128i val_16x8b, val_sh_16x8b; + __m128i w121_a1_8x16b, w121_a2_8x16b; + __m128i row1_16x8b, row2_16x8b, row3_16x8b, row4_16x8b; + + __m128i zero_vector, const_2_8x16b; + WORD32 row1,row2,row3,row4; + + UNUSED(src_strd); + UNUSED(ngbr_avail); + + pu1_top = pu1_src +BLK_SIZE + 1; + + val_16x8b = _mm_loadl_epi64((__m128i *)pu1_top); + zero_vector = _mm_setzero_si128(); + val_sh_16x8b = _mm_srli_si128(val_16x8b, 1); + row1_16x8b = _mm_avg_epu8(val_16x8b, val_sh_16x8b); + + w121_a1_8x16b = _mm_unpacklo_epi8(val_16x8b, zero_vector); //t0 t1 t2 t3 t4 t5... + w121_a2_8x16b = _mm_srli_si128(w121_a1_8x16b, 2); //t1 t2 t3 t4 t5 t6... + w121_a1_8x16b = _mm_add_epi16(w121_a1_8x16b, w121_a2_8x16b); //t0+t1 t1+t2 t2+t3 t3+t4 t4+t5... + w121_a2_8x16b = _mm_srli_si128(w121_a1_8x16b, 2); //t1+t2 t2+t3 t3+t4 t4+t5 t5+t6... + + zero_vector = _mm_setzero_si128(); + const_2_8x16b = _mm_set1_epi16(2); + + w121_a1_8x16b = _mm_add_epi16(w121_a1_8x16b, w121_a2_8x16b); //t0+2*t1+t2 t1+2*t2+t3 t2+2*t3+t4... + w121_a1_8x16b = _mm_add_epi16(w121_a1_8x16b, const_2_8x16b); + w121_a1_8x16b = _mm_srai_epi16(w121_a1_8x16b, 2); + + row2_16x8b = _mm_packus_epi16(w121_a1_8x16b, w121_a1_8x16b); + + dst_strd2 = dst_strd << 1; + dst_strd3 = dst_strd + dst_strd2; + + row3_16x8b = _mm_srli_si128(row1_16x8b, 1); + row4_16x8b = _mm_srli_si128(row2_16x8b, 1); + + row1 = _mm_cvtsi128_si32(row1_16x8b); + row2 = _mm_cvtsi128_si32(row2_16x8b); + row3 = _mm_cvtsi128_si32(row3_16x8b); + row4 = _mm_cvtsi128_si32(row4_16x8b); + + *((WORD32 *)(pu1_dst)) = row1; + *((WORD32 *)(pu1_dst + dst_strd)) = row2; + *((WORD32 *)(pu1_dst + dst_strd2)) = row3; + *((WORD32 *)(pu1_dst + dst_strd3)) = row4; +} + +/** + ******************************************************************************* + * + * ih264_intra_pred_luma_4x4_mode_horz_u_ssse3 + * + * @brief + * Perform Intra prediction for luma_4x4 mode:Horizontal_Up + * + * @par Description: + * Perform Intra prediction for luma_4x4 mode:Horizontal_Up ,described in sec 8.3.1.2.9 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ngbr_avail + * availability of neighbouring pixels(Not used in this function) + * + * @returns + * + * @remarks + * None + * + *******************************************************************************/ +void ih264_intra_pred_luma_4x4_mode_horz_u_ssse3(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + UWORD8 *pu1_left; + WORD32 dst_strd2, dst_strd3; + + __m128i val_16x8b, val_sh_16x8b; + __m128i w11_16x8b; + __m128i w121_a1_8x16b, w121_a2_8x16b, w121_16x8b; + __m128i row1_16x8b, row2_16x8b, row3_16x8b, row4_16x8b; + + __m128i zero_vector, const_2_8x16b, rev_16x8b; + WORD32 row1,row2,row3,row4; + + UNUSED(src_strd); + UNUSED(ngbr_avail); + + pu1_left = pu1_src + BLK_SIZE - 1; + + zero_vector = _mm_setzero_si128(); + rev_16x8b = _mm_setr_epi8(3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + + val_16x8b = _mm_loadl_epi64((__m128i *)(pu1_left - 3)); //l3 l2 l1 l0 0 0 0... + val_16x8b = _mm_shuffle_epi8(val_16x8b, rev_16x8b); //l0 l1 l2 l3 l3 l3 l3... + + val_sh_16x8b = _mm_srli_si128(val_16x8b, 1); + w11_16x8b = _mm_avg_epu8(val_16x8b, val_sh_16x8b); + + w121_a1_8x16b = _mm_unpacklo_epi8(val_16x8b, zero_vector); //l0 l1 l2 l3 l3 l3... + w121_a2_8x16b = _mm_srli_si128(w121_a1_8x16b, 2); //l1 l2 l3 l3 l3 l3... + + w121_a1_8x16b = _mm_add_epi16(w121_a1_8x16b, w121_a2_8x16b); //l0+t1 l1+l2 l2+l3 2*l3 2*l3... + w121_a2_8x16b = _mm_srli_si128(w121_a1_8x16b, 2); //l1+t2 l2+l3 2*l3 2*l3 2*l3... + + zero_vector = _mm_setzero_si128(); + const_2_8x16b = _mm_set1_epi16(2); + + w121_a1_8x16b = _mm_add_epi16(w121_a1_8x16b, w121_a2_8x16b); //l0+2*l1+l2 l1+2*l2+l3 l2+3*l3 4*l3 4*l3... + w121_a1_8x16b = _mm_add_epi16(w121_a1_8x16b, const_2_8x16b); + w121_a1_8x16b = _mm_srai_epi16(w121_a1_8x16b, 2); + + w121_16x8b = _mm_packus_epi16(w121_a1_8x16b, w121_a1_8x16b); + + dst_strd2 = dst_strd << 1; + dst_strd3 = dst_strd + dst_strd2; + + row1_16x8b = _mm_unpacklo_epi8(w11_16x8b, w121_16x8b); + row2_16x8b = _mm_srli_si128(row1_16x8b, 2); + row3_16x8b = _mm_srli_si128(row1_16x8b, 4); + row4_16x8b = _mm_srli_si128(row1_16x8b, 6); + + row1 = _mm_cvtsi128_si32(row1_16x8b); + row2 = _mm_cvtsi128_si32(row2_16x8b); + row3 = _mm_cvtsi128_si32(row3_16x8b); + row4 = _mm_cvtsi128_si32(row4_16x8b); + + *((WORD32 *)(pu1_dst)) = row1; + *((WORD32 *)(pu1_dst + dst_strd)) = row2; + *((WORD32 *)(pu1_dst + dst_strd2)) = row3; + *((WORD32 *)(pu1_dst + dst_strd3)) = row4; +} + +/******************* 8x8 Modes *******************/ + +/** + ******************************************************************************* + * + * ih264_intra_pred_luma_8x8_mode_vert_ssse3 + * + * @brief + * Perform Intra prediction for luma_8x8 mode:vertical + * + * @par Description: + * Perform Intra prediction for luma_8x8 mode:vertical ,described in sec 8.3.2.2.2 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ngbr_avail + * availability of neighbouring pixels(Not used in this function) + * + * @returns + * + * @remarks + * None + * + ******************************************************************************* + */ +void ih264_intra_pred_luma_8x8_mode_vert_ssse3(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + UWORD8 *pu1_top = NULL; + __m128i top_8x8b; + UNUSED(src_strd); + UNUSED(ngbr_avail); + pu1_top = pu1_src + BLK8x8SIZE + 1; + + top_8x8b = _mm_loadl_epi64((__m128i *)pu1_top); + + _mm_storel_epi64((__m128i *)(pu1_dst + 0 * dst_strd), top_8x8b); + _mm_storel_epi64((__m128i *)(pu1_dst + 1 * dst_strd), top_8x8b); + _mm_storel_epi64((__m128i *)(pu1_dst + 2 * dst_strd), top_8x8b); + _mm_storel_epi64((__m128i *)(pu1_dst + 3 * dst_strd), top_8x8b); + _mm_storel_epi64((__m128i *)(pu1_dst + 4 * dst_strd), top_8x8b); + _mm_storel_epi64((__m128i *)(pu1_dst + 5 * dst_strd), top_8x8b); + _mm_storel_epi64((__m128i *)(pu1_dst + 6 * dst_strd), top_8x8b); + _mm_storel_epi64((__m128i *)(pu1_dst + 7 * dst_strd), top_8x8b); +} + +/** + ******************************************************************************* + * + * ih264_intra_pred_luma_8x8_mode_horz_ssse3 + * + * @brief + * Perform Intra prediction for luma_8x8 mode:horizontal + * + * @par Description: + * Perform Intra prediction for uma_8x8 mode:horizontal ,described in sec 8.3.2.2.2 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ngbr_avail + * availability of neighbouring pixels(Not used in this function) + * + * @returns + * + * @remarks + * None + * + ******************************************************************************* + */ +void ih264_intra_pred_luma_8x8_mode_horz_ssse3(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + UWORD8 *pu1_left = pu1_src + BLK8x8SIZE - 1; + __m128i row1_8x8b, row2_8x8b, row3_8x8b, row4_8x8b; + __m128i row5_8x8b, row6_8x8b, row7_8x8b, row8_8x8b; + + UNUSED(src_strd); + UNUSED(ngbr_avail); + + row1_8x8b = _mm_set1_epi8(pu1_left[0]); + row2_8x8b = _mm_set1_epi8(pu1_left[-1]); + row3_8x8b = _mm_set1_epi8(pu1_left[-2]); + row4_8x8b = _mm_set1_epi8(pu1_left[-3]); + row5_8x8b = _mm_set1_epi8(pu1_left[-4]); + row6_8x8b = _mm_set1_epi8(pu1_left[-5]); + row7_8x8b = _mm_set1_epi8(pu1_left[-6]); + row8_8x8b = _mm_set1_epi8(pu1_left[-7]); + + _mm_storel_epi64((__m128i *)(pu1_dst + 0 * dst_strd), row1_8x8b); + _mm_storel_epi64((__m128i *)(pu1_dst + 1 * dst_strd), row2_8x8b); + _mm_storel_epi64((__m128i *)(pu1_dst + 2 * dst_strd), row3_8x8b); + _mm_storel_epi64((__m128i *)(pu1_dst + 3 * dst_strd), row4_8x8b); + _mm_storel_epi64((__m128i *)(pu1_dst + 4 * dst_strd), row5_8x8b); + _mm_storel_epi64((__m128i *)(pu1_dst + 5 * dst_strd), row6_8x8b); + _mm_storel_epi64((__m128i *)(pu1_dst + 6 * dst_strd), row7_8x8b); + _mm_storel_epi64((__m128i *)(pu1_dst + 7 * dst_strd), row8_8x8b); +} + +/** + ******************************************************************************* + * + * ih264_intra_pred_luma_8x8_mode_dc_ssse3 + * + * @brief + * Perform Intra prediction for luma_8x8 mode:DC + * + * @par Description: + * Perform Intra prediction for luma_8x8 mode:DC ,described in sec 8.3.2.2.4 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ngbr_avail + * availability of neighbouring pixels + * + * @returns + * + * @remarks + * None + * + *******************************************************************************/ +void ih264_intra_pred_luma_8x8_mode_dc_ssse3(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + UWORD8 u1_useleft; /* availability of left predictors (only for DC) */ + UWORD8 u1_usetop; /* availability of top predictors (only for DC) */ + UWORD8 *pu1_left = NULL; /* Pointer to start of left predictors */ + UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ + __m128i dc_val_8x8b; + WORD32 dc_val = 0; + UNUSED(src_strd); + + u1_useleft = BOOLEAN(ngbr_avail & LEFT_MB_AVAILABLE_MASK); + u1_usetop = BOOLEAN(ngbr_avail & TOP_MB_AVAILABLE_MASK); + pu1_top = pu1_src + BLK8x8SIZE + 1; + pu1_left = pu1_src + BLK8x8SIZE - 1; + + if(u1_useleft || u1_usetop) + { + WORD32 shft = 2; + __m128i val_8x8b, zero_8x8b, sum_8x16b; + + zero_8x8b = _mm_setzero_si128(); + + if(u1_useleft) + { + val_8x8b = _mm_loadl_epi64((__m128i *)(pu1_left - 7)); + sum_8x16b = _mm_sad_epu8(zero_8x8b, val_8x8b); + + shft++; + dc_val += 4; + dc_val += _mm_extract_epi16(sum_8x16b, 0); + } + if(u1_usetop) + { + val_8x8b = _mm_loadl_epi64((__m128i *)pu1_top); + sum_8x16b = _mm_sad_epu8(zero_8x8b, val_8x8b); + + shft++; + dc_val += 4; + dc_val += _mm_extract_epi16(sum_8x16b, 0); + } + dc_val = dc_val >> shft; + } + else + dc_val = 128; + + dc_val_8x8b = _mm_set1_epi8(dc_val); + + _mm_storel_epi64((__m128i *)(pu1_dst + 0 * dst_strd), dc_val_8x8b); + _mm_storel_epi64((__m128i *)(pu1_dst + 1 * dst_strd), dc_val_8x8b); + _mm_storel_epi64((__m128i *)(pu1_dst + 2 * dst_strd), dc_val_8x8b); + _mm_storel_epi64((__m128i *)(pu1_dst + 3 * dst_strd), dc_val_8x8b); + _mm_storel_epi64((__m128i *)(pu1_dst + 4 * dst_strd), dc_val_8x8b); + _mm_storel_epi64((__m128i *)(pu1_dst + 5 * dst_strd), dc_val_8x8b); + _mm_storel_epi64((__m128i *)(pu1_dst + 6 * dst_strd), dc_val_8x8b); + _mm_storel_epi64((__m128i *)(pu1_dst + 7 * dst_strd), dc_val_8x8b); +} + +/** + ******************************************************************************* + * + * ih264_intra_pred_luma_8x8_mode_diag_dl_ssse3 + * + * @brief + * Perform Intra prediction for luma_8x8 mode:Diagonal_Down_Left + * + * @par Description: + * Perform Intra prediction for luma_8x8 mode:Diagonal_Down_Left ,described in sec 8.3.2.2.5 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ngbr_avail + * availability of neighbouring pixels(Not used in this function) + * + * @returns + * + * @remarks + * None + * + *******************************************************************************/ +void ih264_intra_pred_luma_8x8_mode_diag_dl_ssse3(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ + __m128i top_16x8; + __m128i out_15x16; + __m128i a0_8x16, a1_8x16, a2_8x16; + __m128i temp1, temp2; + __m128i res1_8x16, res2_8x16; + __m128i zero = _mm_setzero_si128(); + __m128i const_val2_8x16 = _mm_set1_epi16(2); + + UNUSED(src_strd); + UNUSED(ngbr_avail); + + pu1_top = pu1_src + BLK8x8SIZE + 1; + + top_16x8 = _mm_loadu_si128((__m128i *)(pu1_top)); + + temp1 = _mm_srli_si128(top_16x8, 1); + temp2 = _mm_srli_si128(top_16x8, 2); + a0_8x16 = _mm_unpacklo_epi8(top_16x8, zero); + a1_8x16 = _mm_unpacklo_epi8(temp1, zero); + a2_8x16 = _mm_unpacklo_epi8(temp2, zero); + + a0_8x16 = _mm_add_epi16(a0_8x16, a2_8x16); + a1_8x16 = _mm_add_epi16(a1_8x16, a1_8x16); + a0_8x16 = _mm_add_epi16(a0_8x16, const_val2_8x16); + a0_8x16 = _mm_add_epi16(a0_8x16, a1_8x16); + res1_8x16 = _mm_srai_epi16(a0_8x16, 2); + + temp2 = _mm_srli_si128(top_16x8, 2); + temp1 = _mm_srli_si128(top_16x8, 1); + a2_8x16 = _mm_unpackhi_epi8(temp2, zero); + a0_8x16 = _mm_unpackhi_epi8(top_16x8, zero); + a2_8x16 = _mm_shufflehi_epi16(a2_8x16, 0x14); + a1_8x16 = _mm_unpackhi_epi8(temp1, zero); + + a0_8x16 = _mm_add_epi16(a0_8x16, a2_8x16); + a1_8x16 = _mm_add_epi16(a1_8x16, a1_8x16); + a0_8x16 = _mm_add_epi16(a0_8x16, const_val2_8x16); + a0_8x16 = _mm_add_epi16(a0_8x16, a1_8x16); + res2_8x16 = _mm_srai_epi16(a0_8x16, 2); + + out_15x16 = _mm_packus_epi16(res1_8x16, res2_8x16); + + _mm_storel_epi64((__m128i *)(pu1_dst + 0 * dst_strd), out_15x16); + out_15x16 = _mm_srli_si128(out_15x16, 1); + _mm_storel_epi64((__m128i *)(pu1_dst + 1 * dst_strd), out_15x16); + out_15x16 = _mm_srli_si128(out_15x16, 1); + _mm_storel_epi64((__m128i *)(pu1_dst + 2 * dst_strd), out_15x16); + out_15x16 = _mm_srli_si128(out_15x16, 1); + _mm_storel_epi64((__m128i *)(pu1_dst + 3 * dst_strd), out_15x16); + out_15x16 = _mm_srli_si128(out_15x16, 1); + _mm_storel_epi64((__m128i *)(pu1_dst + 4 * dst_strd), out_15x16); + out_15x16 = _mm_srli_si128(out_15x16, 1); + _mm_storel_epi64((__m128i *)(pu1_dst + 5 * dst_strd), out_15x16); + out_15x16 = _mm_srli_si128(out_15x16, 1); + _mm_storel_epi64((__m128i *)(pu1_dst + 6 * dst_strd), out_15x16); + out_15x16 = _mm_srli_si128(out_15x16, 1); + _mm_storel_epi64((__m128i *)(pu1_dst + 7 * dst_strd), out_15x16); +} + +/** + ******************************************************************************* + * + * ih264_intra_pred_luma_8x8_mode_diag_dr_ssse3 + * + * @brief + * Perform Intra prediction for luma_8x8 mode:Diagonal_Down_Right + * + * @par Description: + * Perform Intra prediction for luma_8x8 mode:Diagonal_Down_Right ,described in sec 8.3.2.2.6 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ngbr_avail + * availability of neighbouring pixels(Not used in this function) + * + * @returns + * + * @remarks + * None + * + *******************************************************************************/ +void ih264_intra_pred_luma_8x8_mode_diag_dr_ssse3(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + UWORD8 *pu1_left = NULL; /* Pointer to start of left predictors */ + UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ + __m128i top_8x8, left_16x8; + __m128i out_15x16; + __m128i a0_8x16, a1_8x16, a2_8x16; + __m128i temp1, temp2; + __m128i res1_8x16, res2_8x16; + __m128i zero = _mm_setzero_si128(); + __m128i const_val2_8x16 = _mm_set1_epi16(2); + __m128i str_8x8; + + UNUSED(src_strd); + UNUSED(ngbr_avail); + + pu1_left = pu1_src + BLK8x8SIZE - 1; + pu1_top = pu1_src + BLK8x8SIZE + 1; + + left_16x8 = _mm_loadu_si128((__m128i *)(pu1_left - 7)); + + temp1 = _mm_srli_si128(left_16x8, 1); + temp2 = _mm_srli_si128(left_16x8, 2); + a0_8x16 = _mm_unpacklo_epi8(left_16x8, zero); + a1_8x16 = _mm_unpacklo_epi8(temp1, zero); + a2_8x16 = _mm_unpacklo_epi8(temp2, zero); + + a0_8x16 = _mm_add_epi16(a0_8x16, a2_8x16); + a1_8x16 = _mm_add_epi16(a1_8x16, a1_8x16); + a0_8x16 = _mm_add_epi16(a0_8x16, const_val2_8x16); + a0_8x16 = _mm_add_epi16(a0_8x16, a1_8x16); + res1_8x16 = _mm_srai_epi16(a0_8x16, 2); + + top_8x8 = _mm_loadu_si128((__m128i *)(pu1_top - 1)); + + temp1 = _mm_srli_si128(top_8x8, 1); + temp2 = _mm_srli_si128(top_8x8, 2); + a0_8x16 = _mm_unpacklo_epi8(top_8x8, zero); + a1_8x16 = _mm_unpacklo_epi8(temp1, zero); + a2_8x16 = _mm_unpacklo_epi8(temp2, zero); + + a0_8x16 = _mm_add_epi16(a0_8x16, a2_8x16); + a1_8x16 = _mm_add_epi16(a1_8x16, a1_8x16); + a0_8x16 = _mm_add_epi16(a0_8x16, const_val2_8x16); + a0_8x16 = _mm_add_epi16(a0_8x16, a1_8x16); + res2_8x16 = _mm_srai_epi16(a0_8x16, 2); + + out_15x16 = _mm_packus_epi16(res1_8x16, res2_8x16); + + str_8x8 = _mm_srli_si128(out_15x16, 7); + _mm_storel_epi64((__m128i *)(pu1_dst + 0 * dst_strd), str_8x8); + str_8x8 = _mm_srli_si128(out_15x16, 6); + _mm_storel_epi64((__m128i *)(pu1_dst + 1 * dst_strd), str_8x8); + str_8x8 = _mm_srli_si128(out_15x16, 5); + _mm_storel_epi64((__m128i *)(pu1_dst + 2 * dst_strd), str_8x8); + str_8x8 = _mm_srli_si128(out_15x16, 4); + _mm_storel_epi64((__m128i *)(pu1_dst + 3 * dst_strd), str_8x8); + str_8x8 = _mm_srli_si128(out_15x16, 3); + _mm_storel_epi64((__m128i *)(pu1_dst + 4 * dst_strd), str_8x8); + str_8x8 = _mm_srli_si128(out_15x16, 2); + _mm_storel_epi64((__m128i *)(pu1_dst + 5 * dst_strd), str_8x8); + str_8x8 = _mm_srli_si128(out_15x16, 1); + _mm_storel_epi64((__m128i *)(pu1_dst + 6 * dst_strd), str_8x8); + _mm_storel_epi64((__m128i *)(pu1_dst + 7 * dst_strd), out_15x16); +} + +/** + ******************************************************************************* + * + * ih264_intra_pred_luma_8x8_mode_vert_r_ssse3 + * + * @brief + * Perform Intra prediction for luma_8x8 mode:Vertical_Right + * + * @par Description: + * Perform Intra prediction for luma_8x8 mode:Vertical_Right ,described in sec 8.3.2.2.7 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ngbr_avail + * availability of neighbouring pixels(Not used in this function) + * + * @returns + * + * @remarks + * None + * + *******************************************************************************/ +void ih264_intra_pred_luma_8x8_mode_vert_r_ssse3(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + UWORD8 *pu1_left = NULL; /* Pointer to start of left predictors */ + UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ + __m128i top_8x8, left_16x8; + __m128i out1_16x16, out2_16x16; + __m128i a0_8x16, a1_8x16, a2_8x16; + __m128i temp1, temp2; + __m128i res1_8x16, res2_8x16, res3_8x16; + __m128i zero = _mm_setzero_si128(); + __m128i const_val2_8x16 = _mm_set1_epi16(2); + __m128i str_8x8; + __m128i mask = _mm_set1_epi32(0xFFFF); + + UNUSED(src_strd); + UNUSED(ngbr_avail); + + pu1_left = pu1_src + BLK8x8SIZE - 1; + pu1_top = pu1_src + BLK8x8SIZE + 1; + + left_16x8 = _mm_loadu_si128((__m128i *)(pu1_left - 6)); + + temp1 = _mm_srli_si128(left_16x8, 1); + temp2 = _mm_srli_si128(left_16x8, 2); + a0_8x16 = _mm_unpacklo_epi8(left_16x8, zero); + a1_8x16 = _mm_unpacklo_epi8(temp1, zero); + a2_8x16 = _mm_unpacklo_epi8(temp2, zero); + + a0_8x16 = _mm_add_epi16(a0_8x16, a2_8x16); + a1_8x16 = _mm_add_epi16(a1_8x16, a1_8x16); + a0_8x16 = _mm_add_epi16(a0_8x16, const_val2_8x16); + a0_8x16 = _mm_add_epi16(a0_8x16, a1_8x16); + res1_8x16 = _mm_srai_epi16(a0_8x16, 2); + + top_8x8 = _mm_loadu_si128((__m128i *)(pu1_top - 1)); + + temp1 = _mm_srli_si128(top_8x8, 1); + temp2 = _mm_srli_si128(top_8x8, 2); + a0_8x16 = _mm_unpacklo_epi8(top_8x8, zero); + a1_8x16 = _mm_unpacklo_epi8(temp1, zero); + a2_8x16 = _mm_unpacklo_epi8(temp2, zero); + + res3_8x16 = _mm_avg_epu16(a0_8x16, a1_8x16); + + a0_8x16 = _mm_add_epi16(a0_8x16, a2_8x16); + a1_8x16 = _mm_add_epi16(a1_8x16, a1_8x16); + a0_8x16 = _mm_add_epi16(a0_8x16, const_val2_8x16); + a0_8x16 = _mm_add_epi16(a0_8x16, a1_8x16); + res2_8x16 = _mm_srai_epi16(a0_8x16, 2); + + str_8x8 = _mm_packus_epi16(res3_8x16, zero); + _mm_storel_epi64((__m128i *)(pu1_dst + 0 * dst_strd), str_8x8); + + temp1 = _mm_and_si128(res1_8x16, mask); + temp1 = _mm_packs_epi32(temp1, temp1); + out1_16x16 = _mm_packus_epi16(temp1, res2_8x16); + + res1_8x16 = _mm_slli_si128(res1_8x16, 2); + temp1 = _mm_and_si128(res1_8x16, mask); + temp1 = _mm_packs_epi32(temp1, temp1); + out2_16x16 = _mm_packus_epi16(temp1, res3_8x16); + + str_8x8 = _mm_srli_si128(out1_16x16, 7); + _mm_storel_epi64((__m128i *)(pu1_dst + 1 * dst_strd), str_8x8); + + str_8x8 = _mm_srli_si128(out2_16x16, 7); + _mm_storel_epi64((__m128i *)(pu1_dst + 2 * dst_strd), str_8x8); + + str_8x8 = _mm_srli_si128(out1_16x16, 6); + _mm_storel_epi64((__m128i *)(pu1_dst + 3 * dst_strd), str_8x8); + + str_8x8 = _mm_srli_si128(out2_16x16, 6); + _mm_storel_epi64((__m128i *)(pu1_dst + 4 * dst_strd), str_8x8); + + str_8x8 = _mm_srli_si128(out1_16x16, 5); + _mm_storel_epi64((__m128i *)(pu1_dst + 5 * dst_strd), str_8x8); + + str_8x8 = _mm_srli_si128(out2_16x16, 5); + _mm_storel_epi64((__m128i *)(pu1_dst + 6 * dst_strd), str_8x8); + + str_8x8 = _mm_srli_si128(out1_16x16, 4); + _mm_storel_epi64((__m128i *)(pu1_dst + 7 * dst_strd), str_8x8); +} + +/* + ******************************************************************************* + * + * ih264_intra_pred_luma_8x8_mode_horz_d_ssse3 + * + * @brief + * Perform Intra prediction for luma_8x8 mode:Horizontal_Down + * + * @par Description: + * Perform Intra prediction for luma_8x8 mode:Horizontal_Down ,described in sec 8.3.2.2.8 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ngbr_avail + * availability of neighbouring pixels(Not used in this function) + * + * @returns + * + * @remarks + * None + * + *******************************************************************************/ +void ih264_intra_pred_luma_8x8_mode_horz_d_ssse3(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + UWORD8 *pu1_left = NULL; /* Pointer to start of left predictors */ + __m128i pels_16x16; + __m128i temp1, temp2, temp3, temp4; + __m128i a0_8x16, a1_8x16, a2_8x16; + __m128i zero = _mm_setzero_si128(); + __m128i const_val2_8x16 = _mm_set1_epi16(2); + __m128i res1_8x16, res2_8x16; + __m128i out1_16x16, out2_16x16; + __m128i str_8x8; + UNUSED(src_strd); + UNUSED(ngbr_avail); + + pu1_left = pu1_src + BLK8x8SIZE - 1; + + pels_16x16 = _mm_loadu_si128((__m128i *)(pu1_left - 7)); + + temp1 = _mm_srli_si128(pels_16x16, 1); + temp2 = _mm_srli_si128(pels_16x16, 2); + a0_8x16 = _mm_unpacklo_epi8(pels_16x16, zero); + a1_8x16 = _mm_unpacklo_epi8(temp1, zero); + a2_8x16 = _mm_unpacklo_epi8(temp2, zero); + + res1_8x16 = _mm_avg_epu16(a0_8x16, a1_8x16); + + a0_8x16 = _mm_add_epi16(a0_8x16, a2_8x16); + a1_8x16 = _mm_add_epi16(a1_8x16, a1_8x16); + a0_8x16 = _mm_add_epi16(a0_8x16, const_val2_8x16); + a0_8x16 = _mm_add_epi16(a0_8x16, a1_8x16); + res2_8x16 = _mm_srai_epi16(a0_8x16, 2); + + temp3 = _mm_unpacklo_epi16(res1_8x16, res2_8x16); + temp4 = _mm_unpackhi_epi16(res1_8x16, res2_8x16); + out2_16x16 = _mm_packus_epi16(temp3, temp4); + + a0_8x16 = _mm_unpackhi_epi8(pels_16x16, zero); + a1_8x16 = _mm_unpackhi_epi8(temp1, zero); + a2_8x16 = _mm_unpackhi_epi8(temp2, zero); + + a0_8x16 = _mm_add_epi16(a0_8x16, a2_8x16); + a1_8x16 = _mm_add_epi16(a1_8x16, a1_8x16); + a0_8x16 = _mm_add_epi16(a0_8x16, const_val2_8x16); + a0_8x16 = _mm_add_epi16(a0_8x16, a1_8x16); + res2_8x16 = _mm_srai_epi16(a0_8x16, 2); + + out1_16x16 = _mm_packus_epi16(res2_8x16, zero); + temp1 = _mm_srli_si128(out2_16x16, 8); + out1_16x16 = _mm_unpacklo_epi64(temp1, out1_16x16); + + str_8x8 = _mm_srli_si128(out1_16x16, 6); + _mm_storel_epi64((__m128i *)(pu1_dst + 0 * dst_strd), str_8x8); + str_8x8 = _mm_srli_si128(out1_16x16, 4); + _mm_storel_epi64((__m128i *)(pu1_dst + 1 * dst_strd), str_8x8); + str_8x8 = _mm_srli_si128(out1_16x16, 2); + _mm_storel_epi64((__m128i *)(pu1_dst + 2 * dst_strd), str_8x8); + _mm_storel_epi64((__m128i *)(pu1_dst + 3 * dst_strd), out1_16x16); + + str_8x8 = _mm_srli_si128(out2_16x16, 6); + _mm_storel_epi64((__m128i *)(pu1_dst + 4 * dst_strd), str_8x8); + str_8x8 = _mm_srli_si128(out2_16x16, 4); + _mm_storel_epi64((__m128i *)(pu1_dst + 5 * dst_strd), str_8x8); + str_8x8 = _mm_srli_si128(out2_16x16, 2); + _mm_storel_epi64((__m128i *)(pu1_dst + 6 * dst_strd), str_8x8); + _mm_storel_epi64((__m128i *)(pu1_dst + 7 * dst_strd), out2_16x16); +} + +/** + ******************************************************************************* + * + * ih264_intra_pred_luma_8x8_mode_vert_l_ssse3 + * + * @brief + * Perform Intra prediction for luma_8x8 mode:Vertical_Left + * + * @par Description: + * Perform Intra prediction for luma_8x8 mode:Vertical_Left ,described in sec 8.3.2.2.9 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ngbr_avail + * availability of neighbouring pixels(Not used in this function) + * + * @returns + * + * @remarks + * None + * + *******************************************************************************/ + +void ih264_intra_pred_luma_8x8_mode_vert_l_ssse3(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + UWORD8 *pu1_top = NULL; /* Pointer to start of top predictors */ + __m128i top_16x16; + __m128i temp1, temp2; + __m128i a0_8x16, a1_8x16, a2_8x16; + __m128i zero = _mm_setzero_si128(); + __m128i const_val2_8x16 = _mm_set1_epi16(2); + __m128i res1_8x16, res2_8x16, res3_8x16, res4_8x16; + __m128i out1_16x16, out2_16x16; + UNUSED(src_strd); + UNUSED(ngbr_avail); + pu1_top = pu1_src + BLK8x8SIZE + 1; + + top_16x16 = _mm_loadu_si128((__m128i *)(pu1_top)); + temp1 = _mm_srli_si128(top_16x16, 1); + temp2 = _mm_srli_si128(top_16x16, 2); + a0_8x16 = _mm_unpacklo_epi8(top_16x16, zero); + a1_8x16 = _mm_unpacklo_epi8(temp1, zero); + a2_8x16 = _mm_unpacklo_epi8(temp2, zero); + + res1_8x16 = _mm_avg_epu16(a0_8x16, a1_8x16); + + a0_8x16 = _mm_add_epi16(a0_8x16, a2_8x16); + a1_8x16 = _mm_add_epi16(a1_8x16, a1_8x16); + a0_8x16 = _mm_add_epi16(a0_8x16, const_val2_8x16); + a0_8x16 = _mm_add_epi16(a0_8x16, a1_8x16); + res2_8x16 = _mm_srai_epi16(a0_8x16, 2); + + a0_8x16 = _mm_unpackhi_epi8(top_16x16, zero); + a1_8x16 = _mm_unpackhi_epi8(temp1, zero); + a2_8x16 = _mm_unpackhi_epi8(temp2, zero); + + res3_8x16 = _mm_avg_epu16(a0_8x16, a1_8x16); + + a0_8x16 = _mm_add_epi16(a0_8x16, a2_8x16); + a1_8x16 = _mm_add_epi16(a1_8x16, a1_8x16); + a0_8x16 = _mm_add_epi16(a0_8x16, const_val2_8x16); + a0_8x16 = _mm_add_epi16(a0_8x16, a1_8x16); + res4_8x16 = _mm_srai_epi16(a0_8x16, 2); + + out1_16x16 = _mm_packus_epi16(res1_8x16, res3_8x16); + out2_16x16 = _mm_packus_epi16(res2_8x16, res4_8x16); + + _mm_storel_epi64((__m128i *)(pu1_dst + 0 * dst_strd), out1_16x16); + _mm_storel_epi64((__m128i *)(pu1_dst + 1 * dst_strd), out2_16x16); + out1_16x16 = _mm_srli_si128(out1_16x16, 1); + out2_16x16 = _mm_srli_si128(out2_16x16, 1); + _mm_storel_epi64((__m128i *)(pu1_dst + 2 * dst_strd), out1_16x16); + _mm_storel_epi64((__m128i *)(pu1_dst + 3 * dst_strd), out2_16x16); + out1_16x16 = _mm_srli_si128(out1_16x16, 1); + out2_16x16 = _mm_srli_si128(out2_16x16, 1); + _mm_storel_epi64((__m128i *)(pu1_dst + 4 * dst_strd), out1_16x16); + _mm_storel_epi64((__m128i *)(pu1_dst + 5 * dst_strd), out2_16x16); + out1_16x16 = _mm_srli_si128(out1_16x16, 1); + out2_16x16 = _mm_srli_si128(out2_16x16, 1); + _mm_storel_epi64((__m128i *)(pu1_dst + 6 * dst_strd), out1_16x16); + _mm_storel_epi64((__m128i *)(pu1_dst + 7 * dst_strd), out2_16x16); +} + +/** + ******************************************************************************* + * + * ih264_intra_pred_luma_8x8_mode_horz_u_ssse3 + * + * @brief + * Perform Intra prediction for luma_8x8 mode:Horizontal_Up + * + * @par Description: + * Perform Intra prediction for luma_8x8 mode:Horizontal_Up ,described in sec 8.3.2.2.10 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ngbr_avail + * availability of neighbouring pixels(Not used in this function) + * + * @returns + * + * @remarks + * None + * + *******************************************************************************/ +void ih264_intra_pred_luma_8x8_mode_horz_u_ssse3(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + UWORD8 *pu1_left = NULL; /* Pointer to start of left predictors */ + __m128i left_16x16; + __m128i temp1, temp2; + __m128i a0_8x16, a1_8x16, a2_8x16; + __m128i zero = _mm_setzero_si128(); + __m128i const_val2_8x16 = _mm_set1_epi16(2); + __m128i res1_8x16, res2_8x16; + __m128i out1_16x16; + __m128i str_8x8; + __m128i shuffle_16x16; + UNUSED(src_strd); + UNUSED(ngbr_avail); + + pu1_left = pu1_src + BLK8x8SIZE - 1; + shuffle_16x16 = _mm_set_epi8(0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, + 0x0F); + + left_16x16 = _mm_loadu_si128((__m128i *)(pu1_left - 7)); + temp1 = _mm_srli_si128(left_16x16, 1); + a0_8x16 = _mm_unpacklo_epi8(left_16x16, zero); + a0_8x16 = _mm_slli_si128(a0_8x16, 2); + a1_8x16 = _mm_unpacklo_epi8(left_16x16, zero); + a0_8x16 = _mm_shufflelo_epi16(a0_8x16, 0xE5); + a2_8x16 = _mm_unpacklo_epi8(temp1, zero); + + res1_8x16 = _mm_avg_epu16(a0_8x16, a1_8x16); + + a0_8x16 = _mm_add_epi16(a0_8x16, a2_8x16); + a1_8x16 = _mm_add_epi16(a1_8x16, a1_8x16); + a0_8x16 = _mm_add_epi16(a0_8x16, const_val2_8x16); + a0_8x16 = _mm_add_epi16(a0_8x16, a1_8x16); + res2_8x16 = _mm_srai_epi16(a0_8x16, 2); + + temp1 = _mm_unpacklo_epi16(res1_8x16, res2_8x16); + temp2 = _mm_unpackhi_epi16(res1_8x16, res2_8x16); + out1_16x16 = _mm_packus_epi16(temp1, temp2); + out1_16x16 = _mm_shuffle_epi8(out1_16x16, shuffle_16x16); + + str_8x8 = _mm_srli_si128(out1_16x16, 1); + _mm_storel_epi64((__m128i *)(pu1_dst + 0 * dst_strd), str_8x8); + str_8x8 = _mm_srli_si128(out1_16x16, 3); + _mm_storel_epi64((__m128i *)(pu1_dst + 1 * dst_strd), str_8x8); + str_8x8 = _mm_srli_si128(out1_16x16, 5); + _mm_storel_epi64((__m128i *)(pu1_dst + 2 * dst_strd), str_8x8); + str_8x8 = _mm_srli_si128(out1_16x16, 7); + _mm_storel_epi64((__m128i *)(pu1_dst + 3 * dst_strd), str_8x8); + temp1 = _mm_set1_epi8(pu1_left[-7]); + str_8x8 = _mm_unpacklo_epi64(str_8x8, temp1); + str_8x8 = _mm_srli_si128(str_8x8, 2); + _mm_storel_epi64((__m128i *)(pu1_dst + 4 * dst_strd), str_8x8); + str_8x8 = _mm_srli_si128(str_8x8, 2); + _mm_storel_epi64((__m128i *)(pu1_dst + 5 * dst_strd), str_8x8); + str_8x8 = _mm_srli_si128(str_8x8, 2); + _mm_storel_epi64((__m128i *)(pu1_dst + 6 * dst_strd), str_8x8); + str_8x8 = _mm_srli_si128(str_8x8, 2); + _mm_storel_epi64((__m128i *)(pu1_dst + 7 * dst_strd), str_8x8); + +} + + +/******************* 16x16 Modes *******************/ + +/** + ******************************************************************************* + * + *ih264_intra_pred_luma_16x16_mode_vert_ssse3 + * + * @brief + * Perform Intra prediction for luma_16x16 mode:Vertical + * + * @par Description: + * Perform Intra prediction for luma_16x16 mode:Vertical, described in sec 8.3.3.1 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ngbr_avail + * availability of neighbouring pixels (Not used in this function) + * + * @returns + * + * @remarks + * None + * + *******************************************************************************/ +void ih264_intra_pred_luma_16x16_mode_vert_ssse3(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + UWORD8 *pu1_top; + WORD32 dst_strd2, dst_strd3, dst_strd4; + + __m128i top_16x8b; + + UNUSED(src_strd); + UNUSED(ngbr_avail); + + pu1_top = pu1_src + MB_SIZE + 1; + + dst_strd2 = dst_strd << 1; + dst_strd4 = dst_strd << 2; + + top_16x8b = _mm_loadu_si128((__m128i *)pu1_top); + + dst_strd3 = dst_strd + dst_strd2; + + _mm_storeu_si128((__m128i *)pu1_dst, top_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), top_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd2), top_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd3), top_16x8b); + pu1_dst += dst_strd4; + + _mm_storeu_si128((__m128i *)pu1_dst, top_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), top_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd2), top_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd3), top_16x8b); + pu1_dst += dst_strd4; + + _mm_storeu_si128((__m128i *)pu1_dst, top_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), top_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd2), top_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd3), top_16x8b); + pu1_dst += dst_strd4; + + _mm_storeu_si128((__m128i *)pu1_dst, top_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), top_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd2), top_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd3), top_16x8b); +} + +/** + ******************************************************************************* + * + *ih264_intra_pred_luma_16x16_mode_horz_ssse3 + * + * @brief + * Perform Intra prediction for luma_16x16 mode:Horizontal + * + * @par Description: + * Perform Intra prediction for luma_16x16 mode:Horizontal, described in sec 8.3.3.2 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ngbr_avail + * availability of neighbouring pixels(Not used in this function) + * + * @returns + * + * @remarks + * None + * + *******************************************************************************/ +void ih264_intra_pred_luma_16x16_mode_horz_ssse3(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + UWORD8 *pu1_left; + WORD32 dst_strd2, dst_strd3, dst_strd4; + + __m128i row1_16x8b, row2_16x8b, row3_16x8b, row4_16x8b; + + UNUSED(src_strd); + UNUSED(ngbr_avail); + + pu1_left = pu1_src + MB_SIZE - 1; + + dst_strd4 = dst_strd << 2; + + dst_strd2 = dst_strd << 1; + dst_strd3 = dst_strd4 - dst_strd; + + row1_16x8b = _mm_set1_epi8(*(pu1_left)); + row2_16x8b = _mm_set1_epi8(*(pu1_left - 1)); + row3_16x8b = _mm_set1_epi8(*(pu1_left - 2)); + row4_16x8b = _mm_set1_epi8(*(pu1_left - 3)); + + _mm_storeu_si128((__m128i *)pu1_dst, row1_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), row2_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd2), row3_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd3), row4_16x8b); + + pu1_dst += dst_strd4; + row1_16x8b = _mm_set1_epi8(*(pu1_left - 4)); + row2_16x8b = _mm_set1_epi8(*(pu1_left - 5)); + row3_16x8b = _mm_set1_epi8(*(pu1_left - 6)); + row4_16x8b = _mm_set1_epi8(*(pu1_left - 7)); + + _mm_storeu_si128((__m128i *)pu1_dst, row1_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), row2_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd2), row3_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd3), row4_16x8b); + + pu1_dst += dst_strd4; + row1_16x8b = _mm_set1_epi8(*(pu1_left - 8)); + row2_16x8b = _mm_set1_epi8(*(pu1_left - 9)); + row3_16x8b = _mm_set1_epi8(*(pu1_left - 10)); + row4_16x8b = _mm_set1_epi8(*(pu1_left - 11)); + + _mm_storeu_si128((__m128i *)pu1_dst, row1_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), row2_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd2), row3_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd3), row4_16x8b); + + pu1_dst += dst_strd4; + row1_16x8b = _mm_set1_epi8(*(pu1_left - 12)); + row2_16x8b = _mm_set1_epi8(*(pu1_left - 13)); + row3_16x8b = _mm_set1_epi8(*(pu1_left - 14)); + row4_16x8b = _mm_set1_epi8(*(pu1_left - 15)); + + _mm_storeu_si128((__m128i *)pu1_dst, row1_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), row2_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd2), row3_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd3), row4_16x8b); +} + +/** + ******************************************************************************* + * + *ih264_intra_pred_luma_16x16_mode_dc_ssse3 + * + * @brief + * Perform Intra prediction for luma_16x16 mode:DC + * + * @par Description: + * Perform Intra prediction for luma_16x16 mode:DC, described in sec 8.3.3.3 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + ** @param[in] ngbr_avail + * availability of neighbouring pixels + * + * @returns + * + * @remarks + * None + * + *******************************************************************************/ +void ih264_intra_pred_luma_16x16_mode_dc_ssse3(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + WORD8 u1_useleft, u1_usetop; + WORD32 dc_val; + + WORD32 dst_strd2, dst_strd3, dst_strd4; + + __m128i dc_val_16x8b; + + UNUSED(src_strd); + + u1_useleft = BOOLEAN(ngbr_avail & LEFT_MB_AVAILABLE_MASK); + u1_usetop = BOOLEAN(ngbr_avail & TOP_MB_AVAILABLE_MASK); + + if(u1_useleft || u1_usetop) + { + WORD32 shft; + __m128i val_16x8b, zero_16x8b, sum_8x16b; + + dc_val = 0; + shft = 3; + + zero_16x8b = _mm_setzero_si128(); + + if(u1_useleft) + { + UWORD8 *pu1_left; + + pu1_left = pu1_src + MB_SIZE - 1; + + val_16x8b = _mm_loadu_si128((__m128i *)(pu1_left - 15)); + sum_8x16b = _mm_sad_epu8(zero_16x8b, val_16x8b); + + shft++; + dc_val += 8; + dc_val += _mm_extract_epi16(sum_8x16b, 0); + dc_val += _mm_extract_epi16(sum_8x16b, 4); + } + if(u1_usetop) + { + UWORD8 *pu1_top; + + pu1_top = pu1_src + MB_SIZE + 1; + + val_16x8b = _mm_loadu_si128((__m128i *)pu1_top); + sum_8x16b = _mm_sad_epu8(zero_16x8b, val_16x8b); + + shft++; + dc_val += 8; + dc_val += _mm_extract_epi16(sum_8x16b, 0); + dc_val += _mm_extract_epi16(sum_8x16b, 4); + } + dc_val = dc_val >> shft; + } + else + dc_val = 128; + + dc_val_16x8b = _mm_set1_epi8(dc_val); + + dst_strd2 = dst_strd << 1; + dst_strd4 = dst_strd << 2; + dst_strd3 = dst_strd + dst_strd2; + + _mm_storeu_si128((__m128i *)pu1_dst, dc_val_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), dc_val_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd2), dc_val_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd3), dc_val_16x8b); + pu1_dst += dst_strd4; + + _mm_storeu_si128((__m128i *)pu1_dst, dc_val_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), dc_val_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd2), dc_val_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd3), dc_val_16x8b); + pu1_dst += dst_strd4; + + _mm_storeu_si128((__m128i *)pu1_dst, dc_val_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), dc_val_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd2), dc_val_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd3), dc_val_16x8b); + pu1_dst += dst_strd4; + + _mm_storeu_si128((__m128i *)pu1_dst, dc_val_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), dc_val_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd2), dc_val_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd3), dc_val_16x8b); +} + +/** + ******************************************************************************* + * + *ih264_intra_pred_luma_16x16_mode_plane_ssse3 + * + * @brief + * Perform Intra prediction for luma_16x16 mode:PLANE + * + * @par Description: + * Perform Intra prediction for luma_16x16 mode:PLANE, described in sec 8.3.3.4 + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[out] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] src_strd + * integer source stride + * + * @param[in] dst_strd + * integer destination stride + * + * @param[in] ngbr_avail + * availability of neighbouring pixels(Not used in this function) + * + * @returns + * + * @remarks + * None + * + *******************************************************************************/ +void ih264_intra_pred_luma_16x16_mode_plane_ssse3(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 ngbr_avail) +{ + UWORD8 *pu1_left, *pu1_top; + WORD32 a, b, c; + + __m128i rev_8x16b, mul_8x16b, zero_16x8b; + + UNUSED(src_strd); + UNUSED(ngbr_avail); + + pu1_top = pu1_src + MB_SIZE + 1; + pu1_left = pu1_src + MB_SIZE - 1; + + rev_8x16b = _mm_setr_epi16(0x0f0e, 0x0d0c, 0x0b0a, 0x0908, 0x0706, 0x0504, 0x0302, 0x0100); + //used to reverse the order of 16-bit values in a vector + + mul_8x16b = _mm_setr_epi16(1, 2, 3, 4, 5, 6, 7, 8); + zero_16x8b = _mm_setzero_si128(); + + //calculating a, b and c + { + WORD32 h, v; + + __m128i h_val1_16x8b, h_val2_16x8b; + __m128i h_val1_8x16b, h_val2_8x16b, h_val_4x32b; + __m128i v_val1_16x8b, v_val2_16x8b; + __m128i v_val1_8x16b, v_val2_8x16b, v_val_4x32b; + __m128i hv_val_4x32b; + + a = (pu1_top[15] + pu1_left[-15]) << 4; + + h_val1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_top + 8)); + h_val2_16x8b = _mm_loadl_epi64((__m128i *)(pu1_top - 1)); + v_val1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_left - 15)); + v_val2_16x8b = _mm_loadl_epi64((__m128i *)(pu1_left - 6)); + + h_val1_8x16b = _mm_unpacklo_epi8(h_val1_16x8b, zero_16x8b); + h_val2_8x16b = _mm_unpacklo_epi8(h_val2_16x8b, zero_16x8b); + v_val1_8x16b = _mm_unpacklo_epi8(v_val1_16x8b, zero_16x8b); + v_val2_8x16b = _mm_unpacklo_epi8(v_val2_16x8b, zero_16x8b); + + h_val2_8x16b = _mm_shuffle_epi8(h_val2_8x16b, rev_8x16b); + v_val1_8x16b = _mm_shuffle_epi8(v_val1_8x16b, rev_8x16b); + + h_val1_8x16b = _mm_sub_epi16(h_val1_8x16b, h_val2_8x16b); + v_val1_8x16b = _mm_sub_epi16(v_val1_8x16b, v_val2_8x16b); + + h_val_4x32b = _mm_madd_epi16(mul_8x16b, h_val1_8x16b); + v_val_4x32b = _mm_madd_epi16(mul_8x16b, v_val1_8x16b); + + hv_val_4x32b = _mm_hadd_epi32(h_val_4x32b, v_val_4x32b); + hv_val_4x32b = _mm_hadd_epi32(hv_val_4x32b, hv_val_4x32b); + + h = _mm_extract_epi16(hv_val_4x32b, 0); + v = _mm_extract_epi16(hv_val_4x32b, 2); + h = (h << 16) >> 16; + v = (v << 16) >> 16; + + b = ((h << 2) + h + 32) >> 6; + c = ((v << 2) + v + 32) >> 6; + } + + //using a, b and c to compute the fitted plane values + { + __m128i const_8x16b, b_8x16b, c_8x16b, c2_8x16b; + __m128i res1_l_8x16b, res1_h_8x16b; + __m128i res2_l_8x16b, res2_h_8x16b; + __m128i res1_sh_l_8x16b, res1_sh_h_8x16b, res1_16x8b; + __m128i res2_sh_l_8x16b, res2_sh_h_8x16b, res2_16x8b; + + b_8x16b = _mm_set1_epi16(b); + c_8x16b = _mm_set1_epi16(c); + c2_8x16b = _mm_set1_epi16(c << 1); + const_8x16b = _mm_set1_epi16(a - c*7 + 16); + + res1_h_8x16b = _mm_mullo_epi16(mul_8x16b, b_8x16b); + //contains {b*1, b*2, b*3,... b*8} + + res1_l_8x16b = _mm_shuffle_epi8(res1_h_8x16b, rev_8x16b); + res1_l_8x16b = _mm_srli_si128(res1_l_8x16b, 2); + res1_l_8x16b = _mm_sub_epi16(zero_16x8b, res1_l_8x16b); + //contains {-b*7, -b*6,... -b*1, b*0} + + // rows 1, 2 + res1_h_8x16b = _mm_add_epi16(res1_h_8x16b, const_8x16b); + res1_l_8x16b = _mm_add_epi16(res1_l_8x16b, const_8x16b); + res2_h_8x16b = _mm_add_epi16(res1_h_8x16b, c_8x16b); + res2_l_8x16b = _mm_add_epi16(res1_l_8x16b, c_8x16b); + + res1_sh_h_8x16b = _mm_srai_epi16(res1_h_8x16b, 5); + res1_sh_l_8x16b = _mm_srai_epi16(res1_l_8x16b, 5); + res2_sh_h_8x16b = _mm_srai_epi16(res2_h_8x16b, 5); + res2_sh_l_8x16b = _mm_srai_epi16(res2_l_8x16b, 5); + + res1_16x8b = _mm_packus_epi16(res1_sh_l_8x16b, res1_sh_h_8x16b); + res2_16x8b = _mm_packus_epi16(res2_sh_l_8x16b, res2_sh_h_8x16b); + + _mm_storeu_si128((__m128i *)pu1_dst, res1_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), res2_16x8b); + + // rows 3, 4 + res1_h_8x16b = _mm_add_epi16(res1_h_8x16b, c2_8x16b); + res1_l_8x16b = _mm_add_epi16(res1_l_8x16b, c2_8x16b); + res2_h_8x16b = _mm_add_epi16(res2_h_8x16b, c2_8x16b); + res2_l_8x16b = _mm_add_epi16(res2_l_8x16b, c2_8x16b); + + res1_sh_h_8x16b = _mm_srai_epi16(res1_h_8x16b, 5); + res1_sh_l_8x16b = _mm_srai_epi16(res1_l_8x16b, 5); + res2_sh_h_8x16b = _mm_srai_epi16(res2_h_8x16b, 5); + res2_sh_l_8x16b = _mm_srai_epi16(res2_l_8x16b, 5); + + pu1_dst += dst_strd << 1; + + res1_16x8b = _mm_packus_epi16(res1_sh_l_8x16b, res1_sh_h_8x16b); + res2_16x8b = _mm_packus_epi16(res2_sh_l_8x16b, res2_sh_h_8x16b); + + _mm_storeu_si128((__m128i *)pu1_dst, res1_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), res2_16x8b); + + // rows 5, 6 + res1_h_8x16b = _mm_add_epi16(res1_h_8x16b, c2_8x16b); + res1_l_8x16b = _mm_add_epi16(res1_l_8x16b, c2_8x16b); + res2_h_8x16b = _mm_add_epi16(res2_h_8x16b, c2_8x16b); + res2_l_8x16b = _mm_add_epi16(res2_l_8x16b, c2_8x16b); + + res1_sh_h_8x16b = _mm_srai_epi16(res1_h_8x16b, 5); + res1_sh_l_8x16b = _mm_srai_epi16(res1_l_8x16b, 5); + res2_sh_h_8x16b = _mm_srai_epi16(res2_h_8x16b, 5); + res2_sh_l_8x16b = _mm_srai_epi16(res2_l_8x16b, 5); + + pu1_dst += dst_strd << 1; + + res1_16x8b = _mm_packus_epi16(res1_sh_l_8x16b, res1_sh_h_8x16b); + res2_16x8b = _mm_packus_epi16(res2_sh_l_8x16b, res2_sh_h_8x16b); + + _mm_storeu_si128((__m128i *)pu1_dst, res1_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), res2_16x8b); + + // rows 7, 8 + res1_h_8x16b = _mm_add_epi16(res1_h_8x16b, c2_8x16b); + res1_l_8x16b = _mm_add_epi16(res1_l_8x16b, c2_8x16b); + res2_h_8x16b = _mm_add_epi16(res2_h_8x16b, c2_8x16b); + res2_l_8x16b = _mm_add_epi16(res2_l_8x16b, c2_8x16b); + + res1_sh_h_8x16b = _mm_srai_epi16(res1_h_8x16b, 5); + res1_sh_l_8x16b = _mm_srai_epi16(res1_l_8x16b, 5); + res2_sh_h_8x16b = _mm_srai_epi16(res2_h_8x16b, 5); + res2_sh_l_8x16b = _mm_srai_epi16(res2_l_8x16b, 5); + + pu1_dst += dst_strd << 1; + + res1_16x8b = _mm_packus_epi16(res1_sh_l_8x16b, res1_sh_h_8x16b); + res2_16x8b = _mm_packus_epi16(res2_sh_l_8x16b, res2_sh_h_8x16b); + + _mm_storeu_si128((__m128i *)pu1_dst, res1_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), res2_16x8b); + + // rows 9, 10 + res1_h_8x16b = _mm_add_epi16(res1_h_8x16b, c2_8x16b); + res1_l_8x16b = _mm_add_epi16(res1_l_8x16b, c2_8x16b); + res2_h_8x16b = _mm_add_epi16(res2_h_8x16b, c2_8x16b); + res2_l_8x16b = _mm_add_epi16(res2_l_8x16b, c2_8x16b); + + res1_sh_h_8x16b = _mm_srai_epi16(res1_h_8x16b, 5); + res1_sh_l_8x16b = _mm_srai_epi16(res1_l_8x16b, 5); + res2_sh_h_8x16b = _mm_srai_epi16(res2_h_8x16b, 5); + res2_sh_l_8x16b = _mm_srai_epi16(res2_l_8x16b, 5); + + pu1_dst += dst_strd << 1; + + res1_16x8b = _mm_packus_epi16(res1_sh_l_8x16b, res1_sh_h_8x16b); + res2_16x8b = _mm_packus_epi16(res2_sh_l_8x16b, res2_sh_h_8x16b); + + _mm_storeu_si128((__m128i *)pu1_dst, res1_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), res2_16x8b); + + // rows 11, 12 + res1_h_8x16b = _mm_add_epi16(res1_h_8x16b, c2_8x16b); + res1_l_8x16b = _mm_add_epi16(res1_l_8x16b, c2_8x16b); + res2_h_8x16b = _mm_add_epi16(res2_h_8x16b, c2_8x16b); + res2_l_8x16b = _mm_add_epi16(res2_l_8x16b, c2_8x16b); + + res1_sh_h_8x16b = _mm_srai_epi16(res1_h_8x16b, 5); + res1_sh_l_8x16b = _mm_srai_epi16(res1_l_8x16b, 5); + res2_sh_h_8x16b = _mm_srai_epi16(res2_h_8x16b, 5); + res2_sh_l_8x16b = _mm_srai_epi16(res2_l_8x16b, 5); + + pu1_dst += dst_strd << 1; + + res1_16x8b = _mm_packus_epi16(res1_sh_l_8x16b, res1_sh_h_8x16b); + res2_16x8b = _mm_packus_epi16(res2_sh_l_8x16b, res2_sh_h_8x16b); + + _mm_storeu_si128((__m128i *)pu1_dst, res1_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), res2_16x8b); + + // rows 13, 14 + res1_h_8x16b = _mm_add_epi16(res1_h_8x16b, c2_8x16b); + res1_l_8x16b = _mm_add_epi16(res1_l_8x16b, c2_8x16b); + res2_h_8x16b = _mm_add_epi16(res2_h_8x16b, c2_8x16b); + res2_l_8x16b = _mm_add_epi16(res2_l_8x16b, c2_8x16b); + + res1_sh_h_8x16b = _mm_srai_epi16(res1_h_8x16b, 5); + res1_sh_l_8x16b = _mm_srai_epi16(res1_l_8x16b, 5); + res2_sh_h_8x16b = _mm_srai_epi16(res2_h_8x16b, 5); + res2_sh_l_8x16b = _mm_srai_epi16(res2_l_8x16b, 5); + + pu1_dst += dst_strd << 1; + + res1_16x8b = _mm_packus_epi16(res1_sh_l_8x16b, res1_sh_h_8x16b); + res2_16x8b = _mm_packus_epi16(res2_sh_l_8x16b, res2_sh_h_8x16b); + + _mm_storeu_si128((__m128i *)pu1_dst, res1_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), res2_16x8b); + + // rows 15, 16 + res1_h_8x16b = _mm_add_epi16(res1_h_8x16b, c2_8x16b); + res1_l_8x16b = _mm_add_epi16(res1_l_8x16b, c2_8x16b); + res2_h_8x16b = _mm_add_epi16(res2_h_8x16b, c2_8x16b); + res2_l_8x16b = _mm_add_epi16(res2_l_8x16b, c2_8x16b); + + res1_sh_h_8x16b = _mm_srai_epi16(res1_h_8x16b, 5); + res1_sh_l_8x16b = _mm_srai_epi16(res1_l_8x16b, 5); + res2_sh_h_8x16b = _mm_srai_epi16(res2_h_8x16b, 5); + res2_sh_l_8x16b = _mm_srai_epi16(res2_l_8x16b, 5); + + pu1_dst += dst_strd << 1; + + res1_16x8b = _mm_packus_epi16(res1_sh_l_8x16b, res1_sh_h_8x16b); + res2_16x8b = _mm_packus_epi16(res2_sh_l_8x16b, res2_sh_h_8x16b); + + _mm_storeu_si128((__m128i *)pu1_dst, res1_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), res2_16x8b); + } +} diff --git a/dependencies/ih264d/common/x86/ih264_mem_fns_ssse3.c b/dependencies/ih264d/common/x86/ih264_mem_fns_ssse3.c new file mode 100644 index 00000000..8ca1f3e5 --- /dev/null +++ b/dependencies/ih264d/common/x86/ih264_mem_fns_ssse3.c @@ -0,0 +1,169 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** + ******************************************************************************* + * @file + * ih264_mem_fns_atom_intr.c + * + * @brief + * Functions used for memory operations + * + * @author + * Ittiam + * + * @par List of Functions: + * + * @remarks + * None + * + ******************************************************************************* + */ + +/*****************************************************************************/ +/* File Includes */ +/*****************************************************************************/ +#include <stdio.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +#include "ih264_typedefs.h" +#include "ih264_mem_fns.h" + +#include <immintrin.h> + +/** + ******************************************************************************* + * + * @brief + * memcpy of a 8,16 or 32 bytes + * + * @par Description: + * Does memcpy of 8bit data from source to destination for 8,16 or 32 number of bytes + * + * @param[in] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] pu1_src + * UWORD8 pointer to the source + * + * @param[in] num_bytes + * number of bytes to copy + * @returns + * + * @remarks + * None + * + ******************************************************************************* + */ + + + + +void ih264_memcpy_mul_8_ssse3(UWORD8 *pu1_dst, UWORD8 *pu1_src, UWORD32 num_bytes) +{ + int col; + for(col = num_bytes; col >= 8; col -= 8) + { + __m128i src_temp16x8b; + src_temp16x8b = _mm_loadl_epi64((__m128i *)(pu1_src)); + pu1_src += 8; + _mm_storel_epi64((__m128i *)(pu1_dst), src_temp16x8b); + pu1_dst += 8; + } +} + +/** + ******************************************************************************* + * + * @brief + * memset of a 8,16 or 32 bytes + * + * @par Description: + * Does memset of 8bit data for 8,16 or 32 number of bytes + * + * @param[in] pu1_dst + * UWORD8 pointer to the destination + * + * @param[in] value + * UWORD8 value used for memset + * + * @param[in] num_bytes + * number of bytes to set + * @returns + * + * @remarks + * None + * + ******************************************************************************* + */ + + +void ih264_memset_mul_8_ssse3(UWORD8 *pu1_dst, UWORD8 value, UWORD32 num_bytes) +{ + int col; + __m128i src_temp16x8b; + src_temp16x8b = _mm_set1_epi8(value); + for(col = num_bytes; col >= 8; col -= 8) + { + _mm_storel_epi64((__m128i *)(pu1_dst), src_temp16x8b); + pu1_dst += 8; + } +} + +/** + ******************************************************************************* + * + * @brief + * memset of 16bit data of a 8,16 or 32 bytes + * + * @par Description: + * Does memset of 16bit data for 8,16 or 32 number of bytes + * + * @param[in] pu2_dst + * UWORD8 pointer to the destination + * + * @param[in] value + * UWORD16 value used for memset + * + * @param[in] num_words + * number of words to set + * @returns + * + * @remarks + * None + * + ******************************************************************************* + */ + + +void ih264_memset_16bit_mul_8_ssse3(UWORD16 *pu2_dst, UWORD16 value, UWORD32 num_words) +{ + int col; + __m128i src_temp16x8b; + src_temp16x8b = _mm_set1_epi16(value); + for(col = num_words; col >= 8; col -= 8) + { + _mm_storeu_si128((__m128i *)(pu2_dst), src_temp16x8b); + pu2_dst += 8; + } +} + diff --git a/dependencies/ih264d/common/x86/ih264_padding_ssse3.c b/dependencies/ih264d/common/x86/ih264_padding_ssse3.c new file mode 100644 index 00000000..43ded8e7 --- /dev/null +++ b/dependencies/ih264d/common/x86/ih264_padding_ssse3.c @@ -0,0 +1,317 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** +******************************************************************************* +* @file +* ih264_padding_atom_intr.c +* +* @brief +* Contains function definitions for Padding +* +* @author +* Srinivas T +* +* @par List of Functions: +* - ih264_pad_left_luma_ssse3() +* - ih264_pad_left_chroma_ssse3() +* - ih264_pad_right_luma_ssse3() +* - ih264_pad_right_chroma_ssse3() +* +* @remarks +* None +* +******************************************************************************* +*/ + +#include <string.h> +#include <assert.h> +#include "ih264_typedefs.h" +#include "ih264_platform_macros.h" +#include "ih264_mem_fns.h" +#include "ih264_debug.h" + +#include <immintrin.h> + + +/** +******************************************************************************* +* +* @brief +* Padding (luma block) at the left of a 2d array +* +* @par Description: +* The left column of a 2d array is replicated for pad_size times at the left +* +* +* @param[in] pu1_src +* UWORD8 pointer to the source +* +* @param[in] src_strd +* integer source stride +* +* @param[in] ht +* integer height of the array +* +* @param[in] wd +* integer width of the array +* +* @param[in] pad_size +* integer -padding size of the array +* +* @param[in] ht +* integer height of the array +* +* @param[in] wd +* integer width of the array +* +* @returns +* +* @remarks +* None +* +******************************************************************************* +*/ + +void ih264_pad_left_luma_ssse3(UWORD8 *pu1_src, + WORD32 src_strd, + WORD32 ht, + WORD32 pad_size) +{ + WORD32 row; + WORD32 i; + UWORD8 *pu1_dst; + + ASSERT(pad_size % 8 == 0); + + for(row = 0; row < ht; row++) + { + __m128i src_temp0_16x8b; + + pu1_dst = pu1_src - pad_size; + src_temp0_16x8b = _mm_set1_epi8(*pu1_src); + for(i = 0; i < pad_size; i += 8) + { + _mm_storel_epi64((__m128i *)(pu1_dst + i), src_temp0_16x8b); + } + pu1_src += src_strd; + } + +} + + + +/** +******************************************************************************* +* +* @brief +* Padding (chroma block) at the left of a 2d array +* +* @par Description: +* The left column of a 2d array is replicated for pad_size times at the left +* +* +* @param[in] pu1_src +* UWORD8 pointer to the source +* +* @param[in] src_strd +* integer source stride +* +* @param[in] ht +* integer height of the array +* +* @param[in] wd +* integer width of the array (each colour component) +* +* @param[in] pad_size +* integer -padding size of the array +* +* @param[in] ht +* integer height of the array +* +* @param[in] wd +* integer width of the array +* +* @returns +* +* @remarks +* None +* +******************************************************************************* +*/ + +void ih264_pad_left_chroma_ssse3(UWORD8 *pu1_src, + WORD32 src_strd, + WORD32 ht, + WORD32 pad_size) +{ + WORD32 row; + WORD32 col; + UWORD8 *pu1_dst; + + ASSERT(pad_size % 8 == 0); + for(row = 0; row < ht; row++) + { + __m128i src_temp0_16x8b; + + pu1_dst = pu1_src - pad_size; + src_temp0_16x8b = _mm_set1_epi16(*((UWORD16 *)pu1_src)); + for(col = 0; col < pad_size; col += 8) + { + _mm_storel_epi64((__m128i *)(pu1_dst + col), src_temp0_16x8b); + } + pu1_src += src_strd; + } + +} + + + +/** +******************************************************************************* +* +* @brief +* Padding (luma block) at the right of a 2d array +* +* @par Description: +* The right column of a 2d array is replicated for pad_size times at the right +* +* +* @param[in] pu1_src +* UWORD8 pointer to the source +* +* @param[in] src_strd +* integer source stride +* +* @param[in] ht +* integer height of the array +* +* @param[in] wd +* integer width of the array +* +* @param[in] pad_size +* integer -padding size of the array +* +* @param[in] ht +* integer height of the array +* +* @param[in] wd +* integer width of the array +* +* @returns +* +* @remarks +* None +* +******************************************************************************* +*/ + +void ih264_pad_right_luma_ssse3(UWORD8 *pu1_src, + WORD32 src_strd, + WORD32 ht, + WORD32 pad_size) +{ + WORD32 row; + WORD32 col; + UWORD8 *pu1_dst; + + ASSERT(pad_size % 8 == 0); + + for(row = 0; row < ht; row++) + { + __m128i src_temp0_16x8b; + + pu1_dst = pu1_src; + src_temp0_16x8b = _mm_set1_epi8(*(pu1_src - 1)); + for(col = 0; col < pad_size; col += 8) + { + _mm_storel_epi64((__m128i *)(pu1_dst + col), src_temp0_16x8b); + } + pu1_src += src_strd; + } + +} + + + +/** +******************************************************************************* +* +* @brief +* Padding (chroma block) at the right of a 2d array +* +* @par Description: +* The right column of a 2d array is replicated for pad_size times at the right +* +* +* @param[in] pu1_src +* UWORD8 pointer to the source +* +* @param[in] src_strd +* integer source stride +* +* @param[in] ht +* integer height of the array +* +* @param[in] wd +* integer width of the array (each colour component) +* +* @param[in] pad_size +* integer -padding size of the array +* +* @param[in] ht +* integer height of the array +* +* @param[in] wd +* integer width of the array +* +* @returns +* +* @remarks +* None +* +******************************************************************************* +*/ + +void ih264_pad_right_chroma_ssse3(UWORD8 *pu1_src, + WORD32 src_strd, + WORD32 ht, + WORD32 pad_size) +{ + WORD32 row; + WORD32 col; + UWORD8 *pu1_dst; + + ASSERT(pad_size % 8 == 0); + + for(row = 0; row < ht; row++) + { + __m128i src_temp0_16x8b; + + pu1_dst = pu1_src; + src_temp0_16x8b = _mm_set1_epi16(*((UWORD16 *)(pu1_src - 2))); + for(col = 0; col < pad_size; col += 8) + { + _mm_storel_epi64((__m128i *)(pu1_dst + col), src_temp0_16x8b); + } + + pu1_src += src_strd; + } +} + diff --git a/dependencies/ih264d/common/x86/ih264_platform_macros.h b/dependencies/ih264d/common/x86/ih264_platform_macros.h new file mode 100644 index 00000000..0b15cf5a --- /dev/null +++ b/dependencies/ih264d/common/x86/ih264_platform_macros.h @@ -0,0 +1,145 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** +******************************************************************************* +* @file +* ih264_platform_macros.h +* +* @brief +* Platform specific Macro definitions used in the codec +* +* @author +* Ittiam +* +* @remarks +* None +* +******************************************************************************* +*/ + + +#ifndef _IH264_PLATFORM_MACROS_H_ +#define _IH264_PLATFORM_MACROS_H_ + +#include <stdint.h> +#include <immintrin.h> + +#define CLIP_U8(x) CLIP3(0, UINT8_MAX, (x)) +#define CLIP_S8(x) CLIP3(INT8_MIN, INT8_MAX, (x)) + +#define CLIP_U10(x) CLIP3(0, 1023, (x)) +#define CLIP_S10(x) CLIP3(-512, 511, (x)) + +#define CLIP_U11(x) CLIP3(0, 2047, (x)) +#define CLIP_S11(x) CLIP3(-1024, 1023, (x)) + +#define CLIP_U12(x) CLIP3(0, 4095, (x)) +#define CLIP_S12(x) CLIP3(-2048, 2047, (x)) + +#define CLIP_U16(x) CLIP3(0, UINT16_MAX, (x)) +#define CLIP_S16(x) CLIP3(INT16_MIN, INT16_MAX, (x)) + +#define CLIP_U32(x) CLIP3(0, UINT32_MAX, (x)) +#define CLIP_S32(x) CLIP3(INT32_MIN, INT32_MAX, (x)) + +#define MEM_ALIGN16 __attribute__ ((aligned (16))) + +#define SHL(x,y) (((y) < 32) ? ((x) << (y)) : 0) +#define SHR(x,y) (((y) < 32) ? ((x) >> (y)) : 0) + +#define SHR_NEG(val,shift) ((shift>0)?(val>>shift):(val<<(-shift))) +#define SHL_NEG(val,shift) ((shift<0)?(val>>(-shift)):(val<<shift)) + +#define PLD(a) + +/* For MSVC x64 */ + +#ifdef _MSC_VER + +static inline int __builtin_clz(unsigned x) +{ + unsigned long n; + if (x == 0) + return 32; + _BitScanReverse(&n, x); + return 31 - n; +} + +static inline int __builtin_ctz(unsigned x) { + unsigned long ret; + _BitScanForward(&ret, x); + return (int)ret; +} + +static inline void __sync_synchronize() { + __faststorefence(); +} + +#define NOP(nop_cnt) {UWORD32 nop_i; for (nop_i = 0; nop_i < nop_cnt; nop_i++) __nop();} + +#else + +#define NOP(nop_cnt) {UWORD32 nop_i; for (nop_i = 0; nop_i < nop_cnt; nop_i++) asm("nop");} + +#endif + +/* In normal cases, 0 will not be passed as an argument to CLZ and CTZ. +As CLZ and CTZ outputs are used as a shift value in few places, these return +31 for u4_word == 0 case, just to handle error cases gracefully without any +undefined behaviour */ + +static __inline UWORD32 CLZ(UWORD32 u4_word) +{ + if(u4_word || 1) + return(__builtin_clz(u4_word)); + else + return 31; +} + +static __inline UWORD32 CTZ(UWORD32 u4_word) +{ + if(0 == u4_word && 0) + return 31; + else + { + unsigned int index; + index = __builtin_ctz(u4_word); + return (UWORD32)index; + } +} + +#define DATA_SYNC() __sync_synchronize() + +//#define INLINE __inline +#define INLINE inline + +#define PREFETCH_ENABLE 1 + +#if PREFETCH_ENABLE +#define PREFETCH(ptr, type) _mm_prefetch(ptr, type); +#else +#define PREFETCH(ptr, type) +#endif + +#define MEM_ALIGN8 __attribute__ ((aligned (8))) +#define MEM_ALIGN16 __attribute__ ((aligned (16))) +#define MEM_ALIGN32 __attribute__ ((aligned (32))) + +#endif /* _IH264_PLATFORM_MACROS_H_ */ diff --git a/dependencies/ih264d/common/x86/ih264_resi_trans_quant_sse42.c b/dependencies/ih264d/common/x86/ih264_resi_trans_quant_sse42.c new file mode 100644 index 00000000..f4f5cbfa --- /dev/null +++ b/dependencies/ih264d/common/x86/ih264_resi_trans_quant_sse42.c @@ -0,0 +1,992 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** + ******************************************************************************* + * @file + * ih264_resi_trans_quant_sse42.c + * + * @brief + * Contains function definitions single stage forward transform for H.264 + * It will calculate the residue, do the cf and then do quantization + * + * @author + * Mohit [100664] + * + * @par List of Functions: + * - ih264_resi_trans_quant_4x4_sse42() + * - ih264_resi_trans_quant_chroma_4x4_sse42() + * + * @remarks + * None + * + ******************************************************************************* + */ +/* System include files */ +#include <stddef.h> + +/* User include files */ +#include "ih264_typedefs.h" +#include "ih264_defs.h" +#include "ih264_size_defs.h" +#include "ih264_macros.h" +#include "ih264_trans_macros.h" +#include "ih264_trans_data.h" +#include "ih264_structs.h" +#include "ih264_trans_quant_itrans_iquant.h" +#include <immintrin.h> +/** + ******************************************************************************* + * + * @brief + * This function performs forward transform and quantization on a 4*4 block + * + * @par Description: + * The function accepts source buffer and estimation buffer. From these, it + * computes the residue. This is residue is then transformed and quantized. + * The transform and quantization are in placed computed. They use the residue + * buffer for this. + * + * @param[in] pu1_src + * Pointer to source sub-block + * + * @param[in] pu1_pred + * Pointer to prediction sub-block + * + * @param[in] pi2_out + * Pointer to residual sub-block + * + * @param[in] src_strd + * Source stride + * + * @param[in] pred_strd + * Prediction stride + * + * @param[in] dst_strd + * Destination stride + * + * @param[in] u4_qbits + * QP_BITS_h264_4x4 + floor(QP/6) + * + * @param[in] pu2_threshold_matrix + * Pointer to Forward Quant Threshold Matrix + * + * @param[in] pu2_scale_matrix + * Pointer to Forward Quant Scale Matrix + * + * @param[in] u4_round_factor + * Quantization Round factor + * + * @param[out] pu1_nnz + * Total non-zero coefficients in the current sub-block + * + * @returns + * + * @remarks + * None + * + ******************************************************************************* + */ +void ih264_resi_trans_quant_4x4_sse42(UWORD8 *pu1_src, UWORD8 *pu1_pred, + WORD16 *pi2_out, WORD32 src_strd, WORD32 pred_strd, + const UWORD16 *pu2_scale_matrix, const UWORD16 *pu2_threshold_matrix, + UWORD32 u4_qbits, UWORD32 u4_round_factor, UWORD8 *pu1_nnz, + WORD16 *pi2_alt_dc_addr) +{ + WORD32 tmp_dc, u4_zero_coeff, u4_nonzero_coeff = 0; + WORD32 mask0, mask1; + __m128i sum0, sum1, sum2, cmp0, cmp1; + __m128i rnd_fact = _mm_set1_epi32(u4_round_factor); + __m128i temp_2 = _mm_set1_epi16(2); + __m128i temp_1 = _mm_set1_epi16(1); + __m128i src_r0, src_r1, src_r2, src_r3; + __m128i pred_r0, pred_r1, pred_r2, pred_r3; + __m128i temp0, temp1, temp2, temp3; + __m128i zero_8x16b = _mm_setzero_si128(); // all bits reset to zero + __m128i sign_reg0, sign_reg2; + __m128i scalemat_r0_r1, scalemat_r2_r3; + + UNUSED (pu2_threshold_matrix); + + scalemat_r0_r1 = _mm_loadu_si128((__m128i *) (pu2_scale_matrix)); //b00 b01 b02 b03 b10 b11 b12 b13 -- the scaling matrix 0th,1st row + scalemat_r2_r3 = _mm_loadu_si128((__m128i *) (pu2_scale_matrix + 8)); //b20 b21 b22 b23 b30 b31 b32 b33 -- the scaling matrix 2nd,3rd row + src_r0 = _mm_loadl_epi64((__m128i *) (&pu1_src[0])); //a00 a01 a02 a03 0 0 0 0 0 0 0 0 -- all 8 bits + src_r1 = _mm_loadl_epi64((__m128i *) (&pu1_src[src_strd])); //a10 a11 a12 a13 0 0 0 0 0 0 0 0 -- all 8 bits + src_r2 = _mm_loadl_epi64((__m128i *) (&pu1_src[2 * src_strd])); //a20 a21 a22 a23 0 0 0 0 0 0 0 0 -- all 8 bits + src_r3 = _mm_loadl_epi64((__m128i *) (&pu1_src[3 * src_strd])); //a30 a31 a32 a33 0 0 0 0 0 0 0 0 -- all 8 bits + + src_r0 = _mm_cvtepu8_epi16(src_r0); + src_r1 = _mm_cvtepu8_epi16(src_r1); + src_r2 = _mm_cvtepu8_epi16(src_r2); + src_r3 = _mm_cvtepu8_epi16(src_r3); + + pred_r0 = _mm_loadl_epi64((__m128i *) (&pu1_pred[0])); //p00 p01 p02 p03 0 0 0 0 0 0 0 0 -- all 8 bits + pred_r1 = _mm_loadl_epi64((__m128i *) (&pu1_pred[pred_strd])); //p10 p11 p12 p13 0 0 0 0 0 0 0 0 -- all 8 bits + pred_r2 = _mm_loadl_epi64((__m128i *) (&pu1_pred[2 * pred_strd])); //p20 p21 p22 p23 0 0 0 0 0 0 0 0 -- all 8 bits + pred_r3 = _mm_loadl_epi64((__m128i *) (&pu1_pred[3 * pred_strd])); //p30 p31 p32 p33 0 0 0 0 0 0 0 0 -- all 8 bits + + pred_r0 = _mm_cvtepu8_epi16(pred_r0); //p00 p01 p02 p03 -- all 16 bits + pred_r1 = _mm_cvtepu8_epi16(pred_r1); //p10 p11 p12 p13 -- all 16 bits + pred_r2 = _mm_cvtepu8_epi16(pred_r2); //p20 p21 p22 p23 -- all 16 bits + pred_r3 = _mm_cvtepu8_epi16(pred_r3); //p30 p31 p32 p33 -- all 16 bits + + src_r0 = _mm_sub_epi16(src_r0, pred_r0); + src_r1 = _mm_sub_epi16(src_r1, pred_r1); + src_r2 = _mm_sub_epi16(src_r2, pred_r2); + src_r3 = _mm_sub_epi16(src_r3, pred_r3); + + /* Perform Forward transform */ + /*-------------------------------------------------------------*/ + /* DCT [ Horizontal transformation ] */ + /*-------------------------------------------------------------*/ + // Matrix transpose + /* + * a0 a1 a2 a3 + * b0 b1 b2 b3 + * c0 c1 c2 c3 + * d0 d1 d2 d3 + */ + temp0 = _mm_unpacklo_epi16(src_r0, src_r1); //a0 b0 a1 b1 a2 b2 a3 b3 + temp2 = _mm_unpacklo_epi16(src_r2, src_r3); //c0 d0 c1 d1 c2 d2 c3 d3 + temp1 = _mm_unpacklo_epi32(temp0, temp2); //a0 b0 c0 d0 a1 b1 c1 d1 + temp3 = _mm_unpackhi_epi32(temp0, temp2); //a2 b2 c2 d2 a3 b3 c3 d3 + + src_r0 = _mm_unpacklo_epi64(temp1, zero_8x16b); //a0 b0 c0 d0 + src_r1 = _mm_unpackhi_epi64(temp1, zero_8x16b); //a1 b1 c1 d1 + src_r2 = _mm_unpacklo_epi64(temp3, zero_8x16b); //a2 b2 c2 d2 + src_r3 = _mm_unpackhi_epi64(temp3, zero_8x16b); //a3 b3 c3 d3 + + /*----------------------------------------------------------*/ + /* x0 = z0 + z3 */ + temp0 = _mm_add_epi16(src_r0, src_r3); + /* x1 = z1 + z2 */ + temp1 = _mm_add_epi16(src_r1, src_r2); + /* x2 = z1 - z2 */ + temp2 = _mm_sub_epi16(src_r1, src_r2); + /* x3 = z0 - z3 */ + temp3 = _mm_sub_epi16(src_r0, src_r3); + + /* z0 = x0 + x1 */ + src_r0 = _mm_add_epi16(temp0, temp1); + /* z1 = (x3 << 1) + x2 */ + src_r1 = _mm_slli_epi16(temp3, 1); //(x3<<1) + src_r1 = _mm_add_epi16(src_r1, temp2); + /* z2 = x0 - x1 */ + src_r2 = _mm_sub_epi16(temp0, temp1); + /* z3 = x3 - (x2 << 1) */ + src_r3 = _mm_slli_epi16(temp2, 1); //(x2<<1) + src_r3 = _mm_sub_epi16(temp3, src_r3); + + // Matrix transpose + /* + * a0 b0 c0 d0 + * a1 b1 c1 d1 + * a2 b2 c2 d2 + * a3 b3 c3 d3 + */ + temp0 = _mm_unpacklo_epi16(src_r0, src_r1); //a0 a1 b0 b1 c0 c1 d0 d1 + temp2 = _mm_unpacklo_epi16(src_r2, src_r3); //a2 a3 b2 b3 c2 c3 d2 d3 + temp1 = _mm_unpacklo_epi32(temp0, temp2); //a0 a1 a2 a3 b0 b1 b2 b3 + temp3 = _mm_unpackhi_epi32(temp0, temp2); //c0 c1 c2 c3 d0 d1 d2 d3 + + src_r0 = _mm_unpacklo_epi64(temp1, zero_8x16b); //a0 a1 a2 a3 + src_r1 = _mm_unpackhi_epi64(temp1, zero_8x16b); //b0 b1 b2 b3 + src_r2 = _mm_unpacklo_epi64(temp3, zero_8x16b); //c0 c1 c2 c3 + src_r3 = _mm_unpackhi_epi64(temp3, zero_8x16b); //d0 d1 d2 d3 + + /*----------------------------------------------------------*/ + /* x0 = z0 + z3 */ + temp0 = _mm_add_epi16(src_r0, src_r3); + /* x1 = z1 + z2 */ + temp1 = _mm_add_epi16(src_r1, src_r2); + /* x2 = z1 - z2 */ + temp2 = _mm_sub_epi16(src_r1, src_r2); + /* x3 = z0 - z3 */ + temp3 = _mm_sub_epi16(src_r0, src_r3); + + /* z0 = x0 + x1 */ + src_r0 = _mm_add_epi16(temp0, temp1); + /* z1 = (x3 << 1) + x2 */ + src_r1 = _mm_slli_epi16(temp3, 1); //(x3<<1) + src_r1 = _mm_add_epi16(src_r1, temp2); + /* z2 = x0 - x1 */ + src_r2 = _mm_sub_epi16(temp0, temp1); + /* z3 = x3 - (x2 << 1) */ + src_r3 = _mm_slli_epi16(temp2, 1); //(x2<<1) + src_r3 = _mm_sub_epi16(temp3, src_r3); + + tmp_dc = _mm_extract_epi16(src_r0,0); //a0 + *pi2_alt_dc_addr = tmp_dc; + + src_r0 = _mm_unpacklo_epi64(src_r0, src_r1); //a0 a1 a2 a3 b0 b1 b2 b3 + src_r2 = _mm_unpacklo_epi64(src_r2, src_r3); //c0 c1 c2 c3 d0 d1 d2 d3 + sign_reg0 = _mm_cmpgt_epi16(zero_8x16b,src_r0); + sign_reg2 = _mm_cmpgt_epi16(zero_8x16b,src_r2); + + sign_reg0 = _mm_mullo_epi16(temp_2,sign_reg0); + sign_reg2 = _mm_mullo_epi16(temp_2,sign_reg2); + + sign_reg0 = _mm_add_epi16(temp_1,sign_reg0); + sign_reg2 = _mm_add_epi16(temp_1,sign_reg2); + + src_r0 = _mm_abs_epi16(src_r0); + src_r2 = _mm_abs_epi16(src_r2); + + src_r1 = _mm_srli_si128(src_r0, 8); + src_r0 = _mm_cvtepu16_epi32(src_r0); + src_r1 = _mm_cvtepu16_epi32(src_r1); + src_r3 = _mm_srli_si128(src_r2, 8); + src_r2 = _mm_cvtepu16_epi32(src_r2); + src_r3 = _mm_cvtepu16_epi32(src_r3); + + temp0 = _mm_cvtepu16_epi32(scalemat_r0_r1); + scalemat_r0_r1 = _mm_srli_si128(scalemat_r0_r1, 8); + temp2 = _mm_cvtepu16_epi32(scalemat_r2_r3); + scalemat_r2_r3 = _mm_srli_si128(scalemat_r2_r3, 8); + temp1 = _mm_cvtepu16_epi32(scalemat_r0_r1); + temp3 = _mm_cvtepu16_epi32(scalemat_r2_r3); + + temp0 = _mm_mullo_epi32(temp0, src_r0); + temp1 = _mm_mullo_epi32(temp1, src_r1); + temp2 = _mm_mullo_epi32(temp2, src_r2); + temp3 = _mm_mullo_epi32(temp3, src_r3); + + temp0 = _mm_add_epi32(temp0,rnd_fact); + temp1 = _mm_add_epi32(temp1,rnd_fact); + temp2 = _mm_add_epi32(temp2,rnd_fact); + temp3 = _mm_add_epi32(temp3,rnd_fact); + + temp0 = _mm_srli_epi32(temp0,u4_qbits); + temp1 = _mm_srli_epi32(temp1,u4_qbits); + temp2 = _mm_srli_epi32(temp2,u4_qbits); + temp3 = _mm_srli_epi32(temp3,u4_qbits); + + temp0 = _mm_packs_epi32 (temp0,temp1); + temp2 = _mm_packs_epi32 (temp2,temp3); + + temp0 = _mm_sign_epi16(temp0, sign_reg0); + temp2 = _mm_sign_epi16(temp2, sign_reg2); + + _mm_storeu_si128((__m128i *) (&pi2_out[0]), temp0); + _mm_storeu_si128((__m128i *) (&pi2_out[8]), temp2); + + cmp0 = _mm_cmpeq_epi16(temp0, zero_8x16b); + cmp1 = _mm_cmpeq_epi16(temp2, zero_8x16b); + + mask0 = _mm_movemask_epi8(cmp0); + mask1 = _mm_movemask_epi8(cmp1); + u4_zero_coeff = 0; + if(mask0) + { + if(mask0 == 0xffff) + u4_zero_coeff+=8; + else + { + cmp0 = _mm_and_si128(temp_1, cmp0); + sum0 = _mm_hadd_epi16(cmp0, zero_8x16b); + sum1 = _mm_hadd_epi16(sum0, zero_8x16b); + sum2 = _mm_hadd_epi16(sum1, zero_8x16b); + u4_zero_coeff += _mm_cvtsi128_si32(sum2); + } + } + if(mask1) + { + if(mask1 == 0xffff) + u4_zero_coeff+=8; + else + { + cmp1 = _mm_and_si128(temp_1, cmp1); + sum0 = _mm_hadd_epi16(cmp1, zero_8x16b); + sum1 = _mm_hadd_epi16(sum0, zero_8x16b); + sum2 = _mm_hadd_epi16(sum1, zero_8x16b); + u4_zero_coeff += _mm_cvtsi128_si32(sum2); + } + } + + /* Return total nonzero coefficients in the current sub block */ + u4_nonzero_coeff = 16 - u4_zero_coeff; + *pu1_nnz = u4_nonzero_coeff; +} + +/** + ******************************************************************************* + * + * @brief + * This function performs forward transform and quantization on a 4*4 chroma block + * + * @par Description: + * The function accepts source buffer and estimation buffer. From these, it + * computes the residue. This is residue is then transformed and quantized. + * The transform and quantization are in placed computed. They use the residue + * buffer for this. + * + * @param[in] pu1_src + * Pointer to source sub-block + * + * @param[in] pu1_pred + * Pointer to prediction sub-block + * + * @param[in] pi2_out + * Pointer to residual sub-block + * + * @param[in] src_strd + * Source stride + * + * @param[in] pred_strd + * Prediction stride + * + * @param[in] dst_strd + * Destination stride + * + * @param[in] u4_qbits + * QP_BITS_h264_4x4 + floor(QP/6) + * + * @param[in] pu2_threshold_matrix + * Pointer to Forward Quant Threshold Matrix + * + * @param[in] pu2_scale_matrix + * Pointer to Forward Quant Scale Matrix + * + * @param[in] u4_round_factor + * Quantization Round factor + * + * @param[out] pu1_nnz + * Total non-zero coefficients in the current sub-block + * + * @returns + * + * @remarks + * None + * + ******************************************************************************* + */ +void ih264_resi_trans_quant_chroma_4x4_sse42(UWORD8 *pu1_src,UWORD8 *pu1_pred,WORD16 *pi2_out, + WORD32 src_strd,WORD32 pred_strd, + const UWORD16 *pu2_scale_matrix, + const UWORD16 *pu2_threshold_matrix, + UWORD32 u4_qbits,UWORD32 u4_round_factor, + UWORD8 *pu1_nnz, WORD16 *pi2_alt_dc_addr) +{ + WORD32 tmp_dc, u4_zero_coeff, u4_nonzero_coeff = 0; + WORD32 mask0, mask1; + __m128i cmp0, cmp1, sum0, sum1, sum2; + __m128i rnd_fact = _mm_set1_epi32(u4_round_factor); + __m128i temp_2 = _mm_set1_epi16(2); + __m128i temp_1 = _mm_set1_epi16(1); + __m128i src_r0, src_r1, src_r2, src_r3; + __m128i pred_r0, pred_r1, pred_r2, pred_r3; + __m128i temp0, temp1, temp2, temp3; + __m128i zero_8x16b = _mm_setzero_si128(); // all bits reset to zero + __m128i sign_reg0, sign_reg2; + __m128i scalemat_r0_r1, scalemat_r2_r3; + __m128i chroma_mask = _mm_set1_epi16 (0xFF); + + UNUSED (pu2_threshold_matrix); + + scalemat_r0_r1 = _mm_loadu_si128((__m128i *) (pu2_scale_matrix)); //b00 b01 b02 b03 b10 b11 b12 b13 -- the scaling matrix 0th,1st row + scalemat_r2_r3 = _mm_loadu_si128((__m128i *) (pu2_scale_matrix + 8)); //b20 b21 b22 b23 b30 b31 b32 b33 -- the scaling matrix 2nd,3rd row + src_r0 = _mm_loadl_epi64((__m128i *) (&pu1_src[0])); //a00 a01 a02 a03 0 0 0 0 0 0 0 0 -- all 8 bits + src_r1 = _mm_loadl_epi64((__m128i *) (&pu1_src[src_strd])); //a10 a11 a12 a13 0 0 0 0 0 0 0 0 -- all 8 bits + src_r2 = _mm_loadl_epi64((__m128i *) (&pu1_src[2 * src_strd])); //a20 a21 a22 a23 0 0 0 0 0 0 0 0 -- all 8 bits + src_r3 = _mm_loadl_epi64((__m128i *) (&pu1_src[3 * src_strd])); //a30 a31 a32 a33 0 0 0 0 0 0 0 0 -- all 8 bits + + src_r0 = _mm_and_si128(src_r0, chroma_mask); + src_r1 = _mm_and_si128(src_r1, chroma_mask); + src_r2 = _mm_and_si128(src_r2, chroma_mask); + src_r3 = _mm_and_si128(src_r3, chroma_mask); +// src_r0 = _mm_cvtepu8_epi16(src_r0); +// src_r1 = _mm_cvtepu8_epi16(src_r1); +// src_r2 = _mm_cvtepu8_epi16(src_r2); +// src_r3 = _mm_cvtepu8_epi16(src_r3); + + pred_r0 = _mm_loadl_epi64((__m128i *) (&pu1_pred[0])); //p00 p01 p02 p03 0 0 0 0 0 0 0 0 -- all 8 bits + pred_r1 = _mm_loadl_epi64((__m128i *) (&pu1_pred[pred_strd])); //p10 p11 p12 p13 0 0 0 0 0 0 0 0 -- all 8 bits + pred_r2 = _mm_loadl_epi64((__m128i *) (&pu1_pred[2 * pred_strd])); //p20 p21 p22 p23 0 0 0 0 0 0 0 0 -- all 8 bits + pred_r3 = _mm_loadl_epi64((__m128i *) (&pu1_pred[3 * pred_strd])); //p30 p31 p32 p33 0 0 0 0 0 0 0 0 -- all 8 bits + + pred_r0 = _mm_and_si128(pred_r0, chroma_mask); + pred_r1 = _mm_and_si128(pred_r1, chroma_mask); + pred_r2 = _mm_and_si128(pred_r2, chroma_mask); + pred_r3 = _mm_and_si128(pred_r3, chroma_mask); +// pred_r0 = _mm_cvtepu8_epi16(pred_r0); //p00 p01 p02 p03 -- all 16 bits +// pred_r1 = _mm_cvtepu8_epi16(pred_r1); //p10 p11 p12 p13 -- all 16 bits +// pred_r2 = _mm_cvtepu8_epi16(pred_r2); //p20 p21 p22 p23 -- all 16 bits +// pred_r3 = _mm_cvtepu8_epi16(pred_r3); //p30 p31 p32 p33 -- all 16 bits + + src_r0 = _mm_sub_epi16(src_r0, pred_r0); + src_r1 = _mm_sub_epi16(src_r1, pred_r1); + src_r2 = _mm_sub_epi16(src_r2, pred_r2); + src_r3 = _mm_sub_epi16(src_r3, pred_r3); + + /* Perform Forward transform */ + /*-------------------------------------------------------------*/ + /* DCT [ Horizontal transformation ] */ + /*-------------------------------------------------------------*/ + // Matrix transpose + /* + * a0 a1 a2 a3 + * b0 b1 b2 b3 + * c0 c1 c2 c3 + * d0 d1 d2 d3 + */ + temp0 = _mm_unpacklo_epi16(src_r0, src_r1); //a0 b0 a1 b1 a2 b2 a3 b3 + temp2 = _mm_unpacklo_epi16(src_r2, src_r3); //c0 d0 c1 d1 c2 d2 c3 d3 + temp1 = _mm_unpacklo_epi32(temp0, temp2); //a0 b0 c0 d0 a1 b1 c1 d1 + temp3 = _mm_unpackhi_epi32(temp0, temp2); //a2 b2 c2 d2 a3 b3 c3 d3 + + src_r0 = _mm_unpacklo_epi64(temp1, zero_8x16b); //a0 b0 c0 d0 + src_r1 = _mm_unpackhi_epi64(temp1, zero_8x16b); //a1 b1 c1 d1 + src_r2 = _mm_unpacklo_epi64(temp3, zero_8x16b); //a2 b2 c2 d2 + src_r3 = _mm_unpackhi_epi64(temp3, zero_8x16b); //a3 b3 c3 d3 + + /*----------------------------------------------------------*/ + /* x0 = z0 + z3 */ + temp0 = _mm_add_epi16(src_r0, src_r3); + /* x1 = z1 + z2 */ + temp1 = _mm_add_epi16(src_r1, src_r2); + /* x2 = z1 - z2 */ + temp2 = _mm_sub_epi16(src_r1, src_r2); + /* x3 = z0 - z3 */ + temp3 = _mm_sub_epi16(src_r0, src_r3); + + /* z0 = x0 + x1 */ + src_r0 = _mm_add_epi16(temp0, temp1); + /* z1 = (x3 << 1) + x2 */ + src_r1 = _mm_slli_epi16(temp3, 1); //(x3<<1) + src_r1 = _mm_add_epi16(src_r1, temp2); + /* z2 = x0 - x1 */ + src_r2 = _mm_sub_epi16(temp0, temp1); + /* z3 = x3 - (x2 << 1) */ + src_r3 = _mm_slli_epi16(temp2, 1); //(x2<<1) + src_r3 = _mm_sub_epi16(temp3, src_r3); + + // Matrix transpose + /* + * a0 b0 c0 d0 + * a1 b1 c1 d1 + * a2 b2 c2 d2 + * a3 b3 c3 d3 + */ + temp0 = _mm_unpacklo_epi16(src_r0, src_r1); //a0 a1 b0 b1 c0 c1 d0 d1 + temp2 = _mm_unpacklo_epi16(src_r2, src_r3); //a2 a3 b2 b3 c2 c3 d2 d3 + temp1 = _mm_unpacklo_epi32(temp0, temp2); //a0 a1 a2 a3 b0 b1 b2 b3 + temp3 = _mm_unpackhi_epi32(temp0, temp2); //c0 c1 c2 c3 d0 d1 d2 d3 + + src_r0 = _mm_unpacklo_epi64(temp1, zero_8x16b); //a0 a1 a2 a3 + src_r1 = _mm_unpackhi_epi64(temp1, zero_8x16b); //b0 b1 b2 b3 + src_r2 = _mm_unpacklo_epi64(temp3, zero_8x16b); //c0 c1 c2 c3 + src_r3 = _mm_unpackhi_epi64(temp3, zero_8x16b); //d0 d1 d2 d3 + + /*----------------------------------------------------------*/ + /* x0 = z0 + z3 */ + temp0 = _mm_add_epi16(src_r0, src_r3); + /* x1 = z1 + z2 */ + temp1 = _mm_add_epi16(src_r1, src_r2); + /* x2 = z1 - z2 */ + temp2 = _mm_sub_epi16(src_r1, src_r2); + /* x3 = z0 - z3 */ + temp3 = _mm_sub_epi16(src_r0, src_r3); + + /* z0 = x0 + x1 */ + src_r0 = _mm_add_epi16(temp0, temp1); + /* z1 = (x3 << 1) + x2 */ + src_r1 = _mm_slli_epi16(temp3, 1); //(x3<<1) + src_r1 = _mm_add_epi16(src_r1, temp2); + /* z2 = x0 - x1 */ + src_r2 = _mm_sub_epi16(temp0, temp1); + /* z3 = x3 - (x2 << 1) */ + src_r3 = _mm_slli_epi16(temp2, 1); //(x2<<1) + src_r3 = _mm_sub_epi16(temp3, src_r3); + + tmp_dc = _mm_extract_epi16(src_r0,0); //a0 + *pi2_alt_dc_addr = tmp_dc; + + src_r0 = _mm_unpacklo_epi64(src_r0, src_r1); //a0 a1 a2 a3 b0 b1 b2 b3 + src_r2 = _mm_unpacklo_epi64(src_r2, src_r3); //c0 c1 c2 c3 d0 d1 d2 d3 + sign_reg0 = _mm_cmpgt_epi16(zero_8x16b,src_r0); + sign_reg2 = _mm_cmpgt_epi16(zero_8x16b,src_r2); + + sign_reg0 = _mm_mullo_epi16(temp_2,sign_reg0); + sign_reg2 = _mm_mullo_epi16(temp_2,sign_reg2); + + sign_reg0 = _mm_add_epi16(temp_1,sign_reg0); + sign_reg2 = _mm_add_epi16(temp_1,sign_reg2); + + src_r0 = _mm_abs_epi16(src_r0); + src_r2 = _mm_abs_epi16(src_r2); + + src_r1 = _mm_srli_si128(src_r0, 8); + src_r0 = _mm_cvtepu16_epi32(src_r0); + src_r1 = _mm_cvtepu16_epi32(src_r1); + src_r3 = _mm_srli_si128(src_r2, 8); + src_r2 = _mm_cvtepu16_epi32(src_r2); + src_r3 = _mm_cvtepu16_epi32(src_r3); + + temp0 = _mm_cvtepu16_epi32(scalemat_r0_r1); + scalemat_r0_r1 = _mm_srli_si128(scalemat_r0_r1, 8); + temp2 = _mm_cvtepu16_epi32(scalemat_r2_r3); + scalemat_r2_r3 = _mm_srli_si128(scalemat_r2_r3, 8); + temp1 = _mm_cvtepu16_epi32(scalemat_r0_r1); + temp3 = _mm_cvtepu16_epi32(scalemat_r2_r3); + + temp0 = _mm_mullo_epi32(temp0, src_r0); + temp1 = _mm_mullo_epi32(temp1, src_r1); + temp2 = _mm_mullo_epi32(temp2, src_r2); + temp3 = _mm_mullo_epi32(temp3, src_r3); + + temp0 = _mm_add_epi32(temp0,rnd_fact); + temp1 = _mm_add_epi32(temp1,rnd_fact); + temp2 = _mm_add_epi32(temp2,rnd_fact); + temp3 = _mm_add_epi32(temp3,rnd_fact); + + temp0 = _mm_srli_epi32(temp0,u4_qbits); + temp1 = _mm_srli_epi32(temp1,u4_qbits); + temp2 = _mm_srli_epi32(temp2,u4_qbits); + temp3 = _mm_srli_epi32(temp3,u4_qbits); + + temp0 = _mm_packs_epi32 (temp0,temp1); + temp2 = _mm_packs_epi32 (temp2,temp3); + + temp0 = _mm_sign_epi16(temp0, sign_reg0); + temp2 = _mm_sign_epi16(temp2, sign_reg2); + + //temp0 = _mm_insert_epi16(temp0, tmp_dc, 0); + + _mm_storeu_si128((__m128i *) (&pi2_out[0]), temp0); + _mm_storeu_si128((__m128i *) (&pi2_out[8]), temp2); + + cmp0 = _mm_cmpeq_epi16(temp0, zero_8x16b); + cmp1 = _mm_cmpeq_epi16(temp2, zero_8x16b); + + mask0 = _mm_movemask_epi8(cmp0); + mask1 = _mm_movemask_epi8(cmp1); + u4_zero_coeff = 0; + if(mask0) + { + if(mask0 == 0xffff) + u4_zero_coeff+=8; + else + { + cmp0 = _mm_and_si128(temp_1, cmp0); + sum0 = _mm_hadd_epi16(cmp0, zero_8x16b); + sum1 = _mm_hadd_epi16(sum0, zero_8x16b); + sum2 = _mm_hadd_epi16(sum1, zero_8x16b); + u4_zero_coeff += _mm_cvtsi128_si32(sum2); + } + } + if(mask1) + { + if(mask1 == 0xffff) + u4_zero_coeff+=8; + else + { + cmp1 = _mm_and_si128(temp_1, cmp1); + sum0 = _mm_hadd_epi16(cmp1, zero_8x16b); + sum1 = _mm_hadd_epi16(sum0, zero_8x16b); + sum2 = _mm_hadd_epi16(sum1, zero_8x16b); + u4_zero_coeff += _mm_cvtsi128_si32(sum2); + } + } + + /* Return total nonzero coefficients in the current sub block */ + u4_nonzero_coeff = 16 - u4_zero_coeff; + *pu1_nnz = u4_nonzero_coeff; + +} + + +/** + ******************************************************************************* + * + * @brief + * This function performs forward hadamard transform and quantization on a 4*4 block + * + * @par Description: + * The function accepts source buffer and estimation buffer. From these, it + * computes the residue. This is residue is then transformed and quantized. + * The transform and quantization are in placed computed. They use the residue + * buffer for this. + * + * @param[in] pu1_src + * Pointer to source sub-block + * + * @param[in] pu1_pred + * Pointer to prediction sub-block + * + * @param[in] pi2_out + * Pointer to residual sub-block + * + * @param[in] src_strd + * Source stride + * + * @param[in] pred_strd + * Prediction stride + * + * @param[in] dst_strd + * Destination stride + * + * @param[in] u4_qbits + * QP_BITS_h264_4x4 + floor(QP/6) + * + * @param[in] pu2_threshold_matrix + * Pointer to Forward Quant Threshold Matrix + * + * @param[in] pu2_scale_matrix + * Pointer to Forward Quant Scale Matrix + * + * @param[in] u4_round_factor + * Quantization Round factor + * + * @param[out] pu1_nnz + * Total non-zero coefficients in the current sub-block + * + * @returns + * + * @remarks + * None + * + */ + +void ih264_hadamard_quant_4x4_sse42(WORD16 *pi2_src, WORD16 *pi2_dst, + const UWORD16 *pu2_scale_matrix, + const UWORD16 *pu2_threshold_matrix, UWORD32 u4_qbits, + UWORD32 u4_round_factor,UWORD8 *pu1_nnz + ) +{ + WORD32 u4_zero_coeff,u4_nonzero_coeff=0; + __m128i cmp0, cmp1, sum0, sum1, sum2; + WORD32 mask0, mask1; + __m128i src_r0_r1, src_r2_r3, sign_reg; + __m128i src_r0, src_r1, src_r2, src_r3; + __m128i zero_8x16b = _mm_setzero_si128(); + __m128i temp0, temp1, temp2, temp3; + __m128i sign_reg0, sign_reg1, sign_reg2, sign_reg3; + __m128i temp_1 = _mm_set1_epi16(1); + __m128i rnd_fact = _mm_set1_epi32(u4_round_factor); + __m128i scale_val = _mm_set1_epi32(pu2_scale_matrix[0]); + + UNUSED (pu2_threshold_matrix); + + src_r0_r1 = _mm_loadu_si128((__m128i *) (pi2_src)); //a00 a01 a02 a03 a10 a11 a12 a13 -- the source matrix 0th,1st row + src_r2_r3 = _mm_loadu_si128((__m128i *) (pi2_src + 8)); //a20 a21 a22 a23 a30 a31 a32 a33 -- the source matrix 2nd,3rd row + sign_reg = _mm_cmpgt_epi16(zero_8x16b, src_r0_r1); + src_r0 = _mm_unpacklo_epi16(src_r0_r1, sign_reg); //a0 a1 a2 a3 + src_r1 = _mm_unpackhi_epi16(src_r0_r1, sign_reg); //b0 b1 b2 b3 + sign_reg = _mm_cmpgt_epi16(zero_8x16b, src_r2_r3); + src_r2 = _mm_unpacklo_epi16(src_r2_r3, sign_reg); //c0 c1 c2 c3 + src_r3 = _mm_unpackhi_epi16(src_r2_r3, sign_reg); //d0 d1 d2 d3 + + /* Perform Inverse transform */ + /*-------------------------------------------------------------*/ + /* Forward DC transform [ Horizontal transformation ] */ + /*-------------------------------------------------------------*/ + // Matrix transpose + /* + * a0 a1 a2 a3 + * b0 b1 b2 b3 + * c0 c1 c2 c3 + * d0 d1 d2 d3 + */ + temp0 = _mm_unpacklo_epi32(src_r0, src_r1); //a0 b0 a1 b1 + temp2 = _mm_unpacklo_epi32(src_r2, src_r3); //c0 d0 c1 d1 + temp1 = _mm_unpackhi_epi32(src_r0, src_r1); //a2 b2 a3 b3 + temp3 = _mm_unpackhi_epi32(src_r2, src_r3); //c2 d2 c3 d3 + src_r0 = _mm_unpacklo_epi64(temp0, temp2); //a0 b0 c0 d0 + src_r1 = _mm_unpackhi_epi64(temp0, temp2); //a1 b1 c1 d1 + src_r2 = _mm_unpacklo_epi64(temp1, temp3); //a2 b2 c2 d2 + src_r3 = _mm_unpackhi_epi64(temp1, temp3); //a3 b3 c3 d3 + + temp0 = _mm_add_epi32(src_r0, src_r3); + temp1 = _mm_add_epi32(src_r1, src_r2); + temp2 = _mm_sub_epi32(src_r1, src_r2); + temp3 = _mm_sub_epi32(src_r0, src_r3); + + src_r0 = _mm_add_epi32(temp0, temp1); + src_r1 = _mm_add_epi32(temp2, temp3); + src_r2 = _mm_sub_epi32(temp0, temp1); + src_r3 = _mm_sub_epi32(temp3, temp2); + + /*-------------------------------------------------------------*/ + /* Forward DC transform [ Vertical transformation ] */ + /*-------------------------------------------------------------*/ + // Matrix transpose + /* + * a0 b0 c0 d0 + * a1 b1 c1 d1 + * a2 b2 c2 d2 + * a3 b3 c3 d3 + */ + temp0 = _mm_unpacklo_epi32(src_r0, src_r1); //a0 a1 b0 b1 + temp2 = _mm_unpacklo_epi32(src_r2, src_r3); //a2 a3 b2 b3 + temp1 = _mm_unpackhi_epi32(src_r0, src_r1); //c0 c1 d0 d1 + temp3 = _mm_unpackhi_epi32(src_r2, src_r3); //c2 c3 d2 d3 + src_r0 = _mm_unpacklo_epi64(temp0, temp2); //a0 a1 a2 a3 + src_r1 = _mm_unpackhi_epi64(temp0, temp2); //b0 b1 b2 b3 + src_r2 = _mm_unpacklo_epi64(temp1, temp3); //c0 c1 c2 c3 + src_r3 = _mm_unpackhi_epi64(temp1, temp3); //d0 d1 d2 d3 + + temp0 = _mm_add_epi32(src_r0, src_r3); + temp1 = _mm_add_epi32(src_r1, src_r2); + temp2 = _mm_sub_epi32(src_r1, src_r2); + temp3 = _mm_sub_epi32(src_r0, src_r3); + + src_r0 = _mm_add_epi32(temp0, temp1); + src_r1 = _mm_add_epi32(temp2, temp3); + src_r2 = _mm_sub_epi32(temp0, temp1); + src_r3 = _mm_sub_epi32(temp3, temp2); + + src_r0 = _mm_srai_epi32(src_r0, 1); + src_r1 = _mm_srai_epi32(src_r1, 1); + src_r2 = _mm_srai_epi32(src_r2, 1); + src_r3 = _mm_srai_epi32(src_r3, 1); + + // Quantization + sign_reg0 = _mm_cmpgt_epi32(zero_8x16b, src_r0); //Find sign of each value for later restoration + sign_reg1 = _mm_cmpgt_epi32(zero_8x16b, src_r1); + sign_reg2 = _mm_cmpgt_epi32(zero_8x16b, src_r2); + sign_reg3 = _mm_cmpgt_epi32(zero_8x16b, src_r3); + + sign_reg0 = _mm_packs_epi32(sign_reg0, sign_reg1); //Sign = -1 or 0 depending on <0 or >0 respectively + sign_reg2 = _mm_packs_epi32(sign_reg2, sign_reg3); + + sign_reg0 = _mm_slli_epi16(sign_reg0, 1); //Sign = -2 or 0 depending on <0 or >0 respectively + sign_reg2 = _mm_slli_epi16(sign_reg2, 1); + + sign_reg0 = _mm_add_epi16(temp_1,sign_reg0); //Sign = -1 or 1 depending on <0 or >0 respectively + sign_reg2 = _mm_add_epi16(temp_1,sign_reg2); + + src_r0 = _mm_abs_epi32(src_r0); //Absolute values + src_r1 = _mm_abs_epi32(src_r1); + src_r2 = _mm_abs_epi32(src_r2); + src_r3 = _mm_abs_epi32(src_r3); + + temp0 = _mm_mullo_epi32(scale_val, src_r0); //multiply by pu2_scale_matrix[0] + temp1 = _mm_mullo_epi32(scale_val, src_r1); + temp2 = _mm_mullo_epi32(scale_val, src_r2); + temp3 = _mm_mullo_epi32(scale_val, src_r3); + + temp0 = _mm_add_epi32(temp0,rnd_fact); //Add round factor + temp1 = _mm_add_epi32(temp1,rnd_fact); + temp2 = _mm_add_epi32(temp2,rnd_fact); + temp3 = _mm_add_epi32(temp3,rnd_fact); + + temp0 = _mm_srli_epi32(temp0,u4_qbits); //RIght shift by qbits, unsigned variable, so shift right immediate works + temp1 = _mm_srli_epi32(temp1,u4_qbits); + temp2 = _mm_srli_epi32(temp2,u4_qbits); + temp3 = _mm_srli_epi32(temp3,u4_qbits); + + temp0 = _mm_packs_epi32 (temp0,temp1); //Final values are 16-bits only. + temp2 = _mm_packs_epi32 (temp2,temp3); + + temp0 = _mm_sign_epi16(temp0, sign_reg0); //Sign restoration + temp2 = _mm_sign_epi16(temp2, sign_reg2); + + _mm_storeu_si128((__m128i *) (&pi2_dst[0]), temp0); + _mm_storeu_si128((__m128i *) (&pi2_dst[8]), temp2); + + cmp0 = _mm_cmpeq_epi16(temp0, zero_8x16b); + cmp1 = _mm_cmpeq_epi16(temp2, zero_8x16b); + + mask0 = _mm_movemask_epi8(cmp0); + mask1 = _mm_movemask_epi8(cmp1); + u4_zero_coeff = 0; + if(mask0) + { + if(mask0 == 0xffff) + u4_zero_coeff+=8; + else + { + cmp0 = _mm_and_si128(temp_1, cmp0); + sum0 = _mm_hadd_epi16(cmp0, zero_8x16b); + sum1 = _mm_hadd_epi16(sum0, zero_8x16b); + sum2 = _mm_hadd_epi16(sum1, zero_8x16b); + u4_zero_coeff += _mm_cvtsi128_si32(sum2); + } + } + if(mask1) + { + if(mask1 == 0xffff) + u4_zero_coeff+=8; + else + { + cmp1 = _mm_and_si128(temp_1, cmp1); + sum0 = _mm_hadd_epi16(cmp1, zero_8x16b); + sum1 = _mm_hadd_epi16(sum0, zero_8x16b); + sum2 = _mm_hadd_epi16(sum1, zero_8x16b); + u4_zero_coeff += _mm_cvtsi128_si32(sum2); + } + } + + /* Return total nonzero coefficients in the current sub block */ + u4_nonzero_coeff = 16 - u4_zero_coeff; + pu1_nnz[0] = u4_nonzero_coeff; +} + + +/** + ******************************************************************************* + * + * @brief + * This function performs forward hadamard transform and quantization on a 2*2 block + * for both U and V planes + * + * @par Description: + * The function accepts source buffer and estimation buffer. From these, it + * computes the residue. This is residue is then transformed and quantized. + * The transform and quantization are in placed computed. They use the residue + * buffer for this. + * + * @param[in] pu1_src + * Pointer to source sub-block + * + * @param[in] pu1_pred + * Pointer to prediction sub-block + * + * @param[in] pi2_out + * Pointer to residual sub-block + * + * @param[in] src_strd + * Source stride + * + * @param[in] pred_strd + * Prediction stride + * + * @param[in] dst_strd + * Destination stride + * + * @param[in] u4_qbits + * QP_BITS_h264_4x4 + floor(QP/6) + * + * @param[in] pu2_threshold_matrix + * Pointer to Forward Quant Threshold Matrix + * + * @param[in] pu2_scale_matrix + * Pointer to Forward Quant Scale Matrix + * + * @param[in] u4_round_factor + * Quantization Round factor + * + * @param[out] pu1_nnz + * Total non-zero coefficients in the current sub-block + * + * @returns + * + * @remarks + * NNZ for dc is populated at 0 and 5th position of pu1_nnz + * + */ + +void ih264_hadamard_quant_2x2_uv_sse42(WORD16 *pi2_src, WORD16 *pi2_dst, + const UWORD16 *pu2_scale_matrix, + const UWORD16 *pu2_threshold_matrix, UWORD32 u4_qbits, + UWORD32 u4_round_factor,UWORD8 *pu1_nnz) +{ + WORD32 val, nonzero_coeff_0=0, nonzero_coeff_1=0; + __m128i cmp, cmp0, cmp1; + __m128i sum0, sum1; + WORD32 mask, mask0, mask1; + __m128i src, plane_0, plane_1, temp0, temp1, sign_reg; + __m128i zero_8x16b = _mm_setzero_si128(); + __m128i scale_val = _mm_set1_epi32(pu2_scale_matrix[0]); + __m128i sign_reg0, sign_reg1; + __m128i temp_1 = _mm_set1_epi16(1); + __m128i rnd_fact = _mm_set1_epi32(u4_round_factor); + + UNUSED (pu2_threshold_matrix); + + src = _mm_loadu_si128((__m128i *)pi2_src); //a0 a1 a2 a3 b0 b1 b2 b3 + sign_reg = _mm_cmpgt_epi16(zero_8x16b, src); + plane_0 = _mm_unpacklo_epi16(src, sign_reg); //a0 a1 a2 a3 -- 32 bits + plane_1 = _mm_unpackhi_epi16(src, sign_reg); //b0 b1 b2 b3 -- 32 bits + + temp0 = _mm_hadd_epi32(plane_0, plane_1); //a0+a1 a2+a3 b0+b1 b2+b3 + temp1 = _mm_hsub_epi32(plane_0, plane_1); //a0-a1 a2-a3 b0-b1 b2-b3 + + plane_0 = _mm_hadd_epi32(temp0, temp1); //a0+a1+a2+a3 b0+b1+b2+b3 a0-a1+a2-a3 b0-b1+b2-b3 + plane_1 = _mm_hsub_epi32(temp0, temp1); //a0+a1-a2-a3 b0+b1-b2-b3 a0-a1-a2+a3 b0-b1-b2+b3 + + temp0 = _mm_unpacklo_epi32(plane_0, plane_1); //a0+a1+a2+a3 a0+a1-a2-a3 b0+b1+b2+b3 b0+b1-b2-b3 + temp1 = _mm_unpackhi_epi32(plane_0, plane_1); //a0-a1+a2-a3 a0-a1-a2+a3 b0-b1+b2-b3 b0-b1-b2+b3 + + plane_0 = _mm_unpacklo_epi64(temp0, temp1); //a0+a1+a2+a3 a0+a1-a2-a3 a0-a1+a2-a3 a0-a1-a2+a3 + plane_1 = _mm_unpackhi_epi64(temp0, temp1); //b0+b1+b2+b3 b0+b1-b2-b3 b0-b1+b2-b3 b0-b1-b2+b3 + + plane_0 = _mm_shuffle_epi32(plane_0, 0xd8); //a0+a1+a2+a3 a0-a1+a2-a3 a0+a1-a2-a3 a0-a1-a2+a3 + plane_1 = _mm_shuffle_epi32(plane_1, 0xd8); //b0+b1+b2+b3 b0-b1+b2-b3 b0+b1-b2-b3 b0-b1-b2+b3 + // Quantization + sign_reg0 = _mm_cmpgt_epi32(zero_8x16b, plane_0); //Find sign of each value for later restoration + sign_reg1 = _mm_cmpgt_epi32(zero_8x16b, plane_1); + + sign_reg0 = _mm_packs_epi32(sign_reg0, sign_reg1); //Sign = -1 or 0 depending on <0 or >0 respectively + sign_reg0 = _mm_slli_epi16(sign_reg0, 1); //Sign = -2 or 0 depending on <0 or >0 respectively + sign_reg0 = _mm_add_epi16(temp_1,sign_reg0); //Sign = -1 or 1 depending on <0 or >0 respectively + + plane_0 = _mm_abs_epi32(plane_0); //Absolute values + plane_1 = _mm_abs_epi32(plane_1); + + temp0 = _mm_mullo_epi32(scale_val, plane_0); //multiply by pu2_scale_matrix[0] + temp1 = _mm_mullo_epi32(scale_val, plane_1); //multiply by pu2_scale_matrix[0] + + temp0 = _mm_add_epi32(temp0,rnd_fact); //Add round factor + temp1 = _mm_add_epi32(temp1,rnd_fact); + + temp0 = _mm_srli_epi32(temp0,u4_qbits); //RIght shift by qbits, unsigned variable, so shift right immediate works + temp1 = _mm_srli_epi32(temp1,u4_qbits); + + temp0 = _mm_packs_epi32 (temp0,temp1); //Final values are 16-bits only. + temp0 = _mm_sign_epi16(temp0, sign_reg0); //Sign restoration + + _mm_storeu_si128((__m128i *) (&pi2_dst[0]), temp0); + + cmp = _mm_cmpeq_epi16(temp0, zero_8x16b); + mask = _mm_movemask_epi8(cmp); + mask0 = mask & 0xff; + mask1 = mask>>8; + if(mask0) + { + if(mask0 == 0xff) + nonzero_coeff_0 += 4; + else + { + cmp0 = _mm_and_si128(temp_1, cmp); + sum0 = _mm_hadd_epi16(cmp0, zero_8x16b); + sum1 = _mm_hadd_epi16(sum0, zero_8x16b); + val = _mm_cvtsi128_si32(sum1); + val = val & 0xffff; + nonzero_coeff_0 += val; + } + } + if(mask1) + { + if(mask1 == 0xff) + nonzero_coeff_1 += 4; + else + { + cmp1 = _mm_srli_si128(cmp, 8); + cmp1 = _mm_and_si128(temp_1, cmp1); + sum0 = _mm_hadd_epi16(cmp1, zero_8x16b); + sum1 = _mm_hadd_epi16(sum0, zero_8x16b); + nonzero_coeff_1 += _mm_cvtsi128_si32(sum1); + } + } + + pu1_nnz[0] = 4 - nonzero_coeff_0; + pu1_nnz[1] = 4 - nonzero_coeff_1; + +} diff --git a/dependencies/ih264d/common/x86/ih264_weighted_pred_sse42.c b/dependencies/ih264d/common/x86/ih264_weighted_pred_sse42.c new file mode 100644 index 00000000..48f1f542 --- /dev/null +++ b/dependencies/ih264d/common/x86/ih264_weighted_pred_sse42.c @@ -0,0 +1,1303 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/*****************************************************************************/ +/* */ +/* File Name : ih264_weighted_pred_intr_sse42.c */ +/* */ +/* Description : Contains function definitions for weighted */ +/* prediction functions in x86 sse4 intrinsics */ +/* */ +/* List of Functions : ih264_default_weighted_pred_luma_sse42() */ +/* ih264_default_weighted_pred_chroma_sse42() */ +/* ih264_weighted_pred_luma_sse42() */ +/* ih264_weighted_pred_chroma_sse42() */ +/* ih264_weighted_bipred_luma_sse42() */ +/* ih264_weighted_bipred_chroma_sse42() */ +/* */ +/* Issues / Problems : None */ +/* */ +/* Revision History : */ +/* */ +/* DD MM YYYY Author(s) Changes */ +/* 30 01 2015 Kaushik Initial version */ +/* Senthoor */ +/* */ +/*****************************************************************************/ +/*****************************************************************************/ +/* File Includes */ +/*****************************************************************************/ + +#include <immintrin.h> +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264_weighted_pred.h" + +/*****************************************************************************/ +/* Function definitions . */ +/*****************************************************************************/ +/*****************************************************************************/ +/* */ +/* Function Name : ih264_default_weighted_pred_luma_sse42 */ +/* */ +/* Description : This function performs the default weighted prediction */ +/* as described in sec 8.4.2.3.1 titled "Default weighted */ +/* sample prediction process" for luma. The function gets */ +/* two ht x wd blocks, calculates their rounded-average and */ +/* stores it in the destination block. (ht,wd) can be */ +/* (4,4), (8,4), (4,8), (8,8), (16,8), (8,16) or (16,16). */ +/* */ +/* Inputs : pu1_src1 - Pointer to source 1 */ +/* pu1_src2 - Pointer to source 2 */ +/* pu1_dst - Pointer to destination */ +/* src_strd1 - stride for source 1 */ +/* src_strd1 - stride for source 2 */ +/* dst_strd - stride for destination */ +/* ht - height of the block */ +/* wd - width of the block */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes */ +/* 04 02 2015 Kaushik Initial Version */ +/* Senthoor */ +/* */ +/*****************************************************************************/ +void ih264_default_weighted_pred_luma_sse42(UWORD8 *pu1_src1, + UWORD8 *pu1_src2, + UWORD8 *pu1_dst, + WORD32 src_strd1, + WORD32 src_strd2, + WORD32 dst_strd, + WORD32 ht, + WORD32 wd) +{ + __m128i y0_0_16x8b, y0_1_16x8b, y0_2_16x8b, y0_3_16x8b; + __m128i y1_0_16x8b, y1_1_16x8b, y1_2_16x8b, y1_3_16x8b; + + if(wd == 4) + { + do + { + y0_0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src1); + y0_1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src1 + src_strd1)); + y0_2_16x8b = _mm_loadl_epi64( + (__m128i *)(pu1_src1 + (src_strd1 << 1))); + y0_3_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src1 + src_strd1 * 3)); + + y1_0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src2); + y1_1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src2 + src_strd2)); + y1_2_16x8b = _mm_loadl_epi64( + (__m128i *)(pu1_src2 + (src_strd2 << 1))); + y1_3_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src2 + src_strd2 * 3)); + + y0_0_16x8b = _mm_avg_epu8(y0_0_16x8b, y1_0_16x8b); + y0_1_16x8b = _mm_avg_epu8(y0_1_16x8b, y1_1_16x8b); + y0_2_16x8b = _mm_avg_epu8(y0_2_16x8b, y1_2_16x8b); + y0_3_16x8b = _mm_avg_epu8(y0_3_16x8b, y1_3_16x8b); + + *((WORD32 *)(pu1_dst)) = _mm_cvtsi128_si32(y0_0_16x8b); + *((WORD32 *)(pu1_dst + dst_strd)) = _mm_cvtsi128_si32(y0_1_16x8b); + *((WORD32 *)(pu1_dst + (dst_strd << 1))) = _mm_cvtsi128_si32(y0_2_16x8b); + *((WORD32 *)(pu1_dst + dst_strd * 3)) = _mm_cvtsi128_si32(y0_3_16x8b); + + ht -= 4; + pu1_src1 += src_strd1 << 2; + pu1_src2 += src_strd2 << 2; + pu1_dst += dst_strd << 2; + } + while(ht > 0); + } + else if(wd == 8) + { + do + { + y0_0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src1); + y0_1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src1 + src_strd1)); + y0_2_16x8b = _mm_loadl_epi64( + (__m128i *)(pu1_src1 + (src_strd1 << 1))); + y0_3_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src1 + src_strd1 * 3)); + + y1_0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src2); + y1_1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src2 + src_strd2)); + y1_2_16x8b = _mm_loadl_epi64( + (__m128i *)(pu1_src2 + (src_strd2 << 1))); + y1_3_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src2 + src_strd2 * 3)); + + y0_0_16x8b = _mm_avg_epu8(y0_0_16x8b, y1_0_16x8b); + y0_1_16x8b = _mm_avg_epu8(y0_1_16x8b, y1_1_16x8b); + y0_2_16x8b = _mm_avg_epu8(y0_2_16x8b, y1_2_16x8b); + y0_3_16x8b = _mm_avg_epu8(y0_3_16x8b, y1_3_16x8b); + + _mm_storel_epi64((__m128i *)pu1_dst, y0_0_16x8b); + _mm_storel_epi64((__m128i *)(pu1_dst + dst_strd), y0_1_16x8b); + _mm_storel_epi64((__m128i *)(pu1_dst + (dst_strd << 1)), y0_2_16x8b); + _mm_storel_epi64((__m128i *)(pu1_dst + dst_strd * 3), y0_3_16x8b); + + ht -= 4; + pu1_src1 += src_strd1 << 2; + pu1_src2 += src_strd2 << 2; + pu1_dst += dst_strd << 2; + } + while(ht > 0); + } + else // wd == 16 + { + __m128i y0_4_16x8b, y0_5_16x8b, y0_6_16x8b, y0_7_16x8b; + __m128i y1_4_16x8b, y1_5_16x8b, y1_6_16x8b, y1_7_16x8b; + + do + { + y0_0_16x8b = _mm_loadu_si128((__m128i *)pu1_src1); + y0_1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src1 + src_strd1)); + y0_2_16x8b = _mm_loadu_si128( + (__m128i *)(pu1_src1 + (src_strd1 << 1))); + y0_3_16x8b = _mm_loadu_si128((__m128i *)(pu1_src1 + src_strd1 * 3)); + y0_4_16x8b = _mm_loadu_si128( + (__m128i *)(pu1_src1 + (src_strd1 << 2))); + y0_5_16x8b = _mm_loadu_si128((__m128i *)(pu1_src1 + src_strd1 * 5)); + y0_6_16x8b = _mm_loadu_si128((__m128i *)(pu1_src1 + src_strd1 * 6)); + y0_7_16x8b = _mm_loadu_si128((__m128i *)(pu1_src1 + src_strd1 * 7)); + + y1_0_16x8b = _mm_loadu_si128((__m128i *)pu1_src2); + y1_1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src2 + src_strd2)); + y1_2_16x8b = _mm_loadu_si128( + (__m128i *)(pu1_src2 + (src_strd2 << 1))); + y1_3_16x8b = _mm_loadu_si128((__m128i *)(pu1_src2 + src_strd2 * 3)); + y1_4_16x8b = _mm_loadu_si128( + (__m128i *)(pu1_src2 + (src_strd2 << 2))); + y1_5_16x8b = _mm_loadu_si128((__m128i *)(pu1_src2 + src_strd2 * 5)); + y1_6_16x8b = _mm_loadu_si128((__m128i *)(pu1_src2 + src_strd2 * 6)); + y1_7_16x8b = _mm_loadu_si128((__m128i *)(pu1_src2 + src_strd2 * 7)); + + y0_0_16x8b = _mm_avg_epu8(y0_0_16x8b, y1_0_16x8b); + y0_1_16x8b = _mm_avg_epu8(y0_1_16x8b, y1_1_16x8b); + y0_2_16x8b = _mm_avg_epu8(y0_2_16x8b, y1_2_16x8b); + y0_3_16x8b = _mm_avg_epu8(y0_3_16x8b, y1_3_16x8b); + y0_4_16x8b = _mm_avg_epu8(y0_4_16x8b, y1_4_16x8b); + y0_5_16x8b = _mm_avg_epu8(y0_5_16x8b, y1_5_16x8b); + y0_6_16x8b = _mm_avg_epu8(y0_6_16x8b, y1_6_16x8b); + y0_7_16x8b = _mm_avg_epu8(y0_7_16x8b, y1_7_16x8b); + + _mm_storeu_si128((__m128i *)pu1_dst, y0_0_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), y0_1_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + (dst_strd << 1)), y0_2_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd * 3), y0_3_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + (dst_strd << 2)), y0_4_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd * 5), y0_5_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd * 6), y0_6_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd * 7), y0_7_16x8b); + + ht -= 8; + pu1_src1 += src_strd1 << 3; + pu1_src2 += src_strd2 << 3; + pu1_dst += dst_strd << 3; + } + while(ht > 0); + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_default_weighted_pred_chroma_sse42 */ +/* */ +/* Description : This function performs the default weighted prediction */ +/* as described in sec 8.4.2.3.1 titled "Default weighted */ +/* sample prediction process" for chroma. The function gets */ +/* two ht x wd blocks, calculates their rounded-average and */ +/* stores it in the destination block. (ht,wd) can be */ +/* (2,2), (4,2) , (2,4), (4,4), (8,4), (4,8) or (8,8). */ +/* */ +/* Inputs : pu1_src1 - Pointer to source 1 */ +/* pu1_src2 - Pointer to source 2 */ +/* pu1_dst - Pointer to destination */ +/* src_strd1 - stride for source 1 */ +/* src_strd1 - stride for source 2 */ +/* dst_strd - stride for destination */ +/* ht - height of the block */ +/* wd - width of the block */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes */ +/* 04 02 2015 Kaushik Initial Version */ +/* Senthoor */ +/* */ +/*****************************************************************************/ +void ih264_default_weighted_pred_chroma_sse42(UWORD8 *pu1_src1, + UWORD8 *pu1_src2, + UWORD8 *pu1_dst, + WORD32 src_strd1, + WORD32 src_strd2, + WORD32 dst_strd, + WORD32 ht, + WORD32 wd) +{ + __m128i uv0_0_16x8b, uv0_1_16x8b; + __m128i uv1_0_16x8b, uv1_1_16x8b; + + if(wd == 2) + { + do + { + uv0_0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src1); + uv0_1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src1 + src_strd1)); + + uv1_0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src2); + uv1_1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src2 + src_strd2)); + + uv0_0_16x8b = _mm_avg_epu8(uv0_0_16x8b, uv1_0_16x8b); + uv0_1_16x8b = _mm_avg_epu8(uv0_1_16x8b, uv1_1_16x8b); + + *((WORD32 *)(pu1_dst)) = _mm_cvtsi128_si32(uv0_0_16x8b); + *((WORD32 *)(pu1_dst + dst_strd)) = _mm_cvtsi128_si32(uv0_1_16x8b); + + ht -= 2; + pu1_src1 += src_strd1 << 1; + pu1_src2 += src_strd2 << 1; + pu1_dst += dst_strd << 1; + } + while(ht > 0); + } + else if(wd == 4) + { + do + { + uv0_0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src1); + uv0_1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src1 + src_strd1)); + + uv1_0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src2); + uv1_1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src2 + src_strd2)); + + uv0_0_16x8b = _mm_avg_epu8(uv0_0_16x8b, uv1_0_16x8b); + uv0_1_16x8b = _mm_avg_epu8(uv0_1_16x8b, uv1_1_16x8b); + + _mm_storel_epi64((__m128i *)pu1_dst, uv0_0_16x8b); + _mm_storel_epi64((__m128i *)(pu1_dst + dst_strd), uv0_1_16x8b); + + ht -= 2; + pu1_src1 += src_strd1 << 1; + pu1_src2 += src_strd2 << 1; + pu1_dst += dst_strd << 1; + } + while(ht > 0); + } + else // wd == 8 + { + __m128i uv0_2_16x8b, uv0_3_16x8b; + __m128i uv1_2_16x8b, uv1_3_16x8b; + + do + { + uv0_0_16x8b = _mm_loadu_si128((__m128i *)pu1_src1); + uv0_1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src1 + src_strd1)); + uv0_2_16x8b = _mm_loadu_si128( + (__m128i *)(pu1_src1 + (src_strd1 << 1))); + uv0_3_16x8b = _mm_loadu_si128( + (__m128i *)(pu1_src1 + src_strd1 * 3)); + + uv1_0_16x8b = _mm_loadu_si128((__m128i *)pu1_src2); + uv1_1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src2 + src_strd2)); + uv1_2_16x8b = _mm_loadu_si128( + (__m128i *)(pu1_src2 + (src_strd2 << 1))); + uv1_3_16x8b = _mm_loadu_si128( + (__m128i *)(pu1_src2 + src_strd2 * 3)); + + uv0_0_16x8b = _mm_avg_epu8(uv0_0_16x8b, uv1_0_16x8b); + uv0_1_16x8b = _mm_avg_epu8(uv0_1_16x8b, uv1_1_16x8b); + uv0_2_16x8b = _mm_avg_epu8(uv0_2_16x8b, uv1_2_16x8b); + uv0_3_16x8b = _mm_avg_epu8(uv0_3_16x8b, uv1_3_16x8b); + + _mm_storeu_si128((__m128i *)pu1_dst, uv0_0_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), uv0_1_16x8b); + _mm_storeu_si128( + (__m128i *)(pu1_dst + (dst_strd << 1)), uv0_2_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd * 3), uv0_3_16x8b); + + ht -= 4; + pu1_src1 += src_strd1 << 2; + pu1_src2 += src_strd2 << 2; + pu1_dst += dst_strd << 2; + } + while(ht > 0); + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_weighted_pred_luma_sse42 */ +/* */ +/* Description : This function performs the weighted prediction as */ +/* described in sec 8.4.2.3.2 titled "Weighted sample */ +/* prediction process" for luma. The function gets one */ +/* ht x wd block, weights it, rounds it off, offsets it, */ +/* saturates it to unsigned 8-bit and stores it in the */ +/* destination block. (ht,wd) can be (4,4), (8,4), (4,8), */ +/* (8,8), (16,8), (8,16) or (16,16). */ +/* */ +/* Inputs : pu1_src - Pointer to source */ +/* pu1_dst - Pointer to destination */ +/* src_strd - stride for source */ +/* dst_strd - stride for destination */ +/* log_wd - number of bits to be rounded off */ +/* wt - weight value */ +/* ofst - offset value */ +/* ht - height of the block */ +/* wd - width of the block */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes */ +/* 04 02 2015 Kaushik Initial Version */ +/* Senthoor */ +/* */ +/*****************************************************************************/ +void ih264_weighted_pred_luma_sse42(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 log_wd, + WORD32 wt, + WORD32 ofst, + WORD32 ht, + WORD32 wd) +{ + __m128i y_0_16x8b, y_1_16x8b, y_2_16x8b, y_3_16x8b; + + __m128i wt_8x16b, round_8x16b, ofst_8x16b; + + WORD32 round_val; + + wt = (WORD16)(wt & 0xffff); + round_val = 1 << (log_wd - 1); + ofst = (WORD8)(ofst & 0xff); + + wt_8x16b = _mm_set1_epi16(wt); + round_8x16b = _mm_set1_epi16(round_val); + ofst_8x16b = _mm_set1_epi16(ofst); + + if(wd == 4) + { + __m128i y_0_8x16b, y_2_8x16b; + + do + { + y_0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); + y_1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src + src_strd)); + y_2_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src + (src_strd << 1))); + y_3_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src + src_strd * 3)); + + y_0_16x8b = _mm_unpacklo_epi32(y_0_16x8b, y_1_16x8b); + y_2_16x8b = _mm_unpacklo_epi32(y_2_16x8b, y_3_16x8b); + + y_0_8x16b = _mm_cvtepu8_epi16(y_0_16x8b); + y_2_8x16b = _mm_cvtepu8_epi16(y_2_16x8b); + + y_0_8x16b = _mm_mullo_epi16(y_0_8x16b, wt_8x16b); + y_2_8x16b = _mm_mullo_epi16(y_2_8x16b, wt_8x16b); + + y_0_8x16b = _mm_adds_epi16(round_8x16b, y_0_8x16b); + y_2_8x16b = _mm_adds_epi16(round_8x16b, y_2_8x16b); + + y_0_8x16b = _mm_srai_epi16(y_0_8x16b, log_wd); + y_2_8x16b = _mm_srai_epi16(y_2_8x16b, log_wd); + + y_0_8x16b = _mm_adds_epi16(ofst_8x16b, y_0_8x16b); + y_2_8x16b = _mm_adds_epi16(ofst_8x16b, y_2_8x16b); + + y_0_16x8b = _mm_packus_epi16(y_0_8x16b, y_2_8x16b); + y_1_16x8b = _mm_srli_si128(y_0_16x8b, 4); + y_2_16x8b = _mm_srli_si128(y_0_16x8b, 8); + y_3_16x8b = _mm_srli_si128(y_0_16x8b, 12); + + *((WORD32 *)(pu1_dst)) = _mm_cvtsi128_si32(y_0_16x8b); + *((WORD32 *)(pu1_dst + dst_strd)) = _mm_cvtsi128_si32(y_1_16x8b); + *((WORD32 *)(pu1_dst + (dst_strd << 1))) = _mm_cvtsi128_si32(y_2_16x8b); + *((WORD32 *)(pu1_dst + dst_strd * 3)) = _mm_cvtsi128_si32(y_3_16x8b); + + ht -= 4; + pu1_src += src_strd << 2; + pu1_dst += dst_strd << 2; + } + while(ht > 0); + } + else if(wd == 8) + { + __m128i y_0_8x16b, y_1_8x16b, y_2_8x16b, y_3_8x16b; + + do + { + y_0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); + y_1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src + src_strd)); + y_2_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src + (src_strd << 1))); + y_3_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src + src_strd * 3)); + + y_0_8x16b = _mm_cvtepu8_epi16(y_0_16x8b); + y_1_8x16b = _mm_cvtepu8_epi16(y_1_16x8b); + y_2_8x16b = _mm_cvtepu8_epi16(y_2_16x8b); + y_3_8x16b = _mm_cvtepu8_epi16(y_3_16x8b); + + y_0_8x16b = _mm_mullo_epi16(y_0_8x16b, wt_8x16b); + y_1_8x16b = _mm_mullo_epi16(y_1_8x16b, wt_8x16b); + y_2_8x16b = _mm_mullo_epi16(y_2_8x16b, wt_8x16b); + y_3_8x16b = _mm_mullo_epi16(y_3_8x16b, wt_8x16b); + + y_0_8x16b = _mm_adds_epi16(round_8x16b, y_0_8x16b); + y_1_8x16b = _mm_adds_epi16(round_8x16b, y_1_8x16b); + y_2_8x16b = _mm_adds_epi16(round_8x16b, y_2_8x16b); + y_3_8x16b = _mm_adds_epi16(round_8x16b, y_3_8x16b); + + y_0_8x16b = _mm_srai_epi16(y_0_8x16b, log_wd); + y_1_8x16b = _mm_srai_epi16(y_1_8x16b, log_wd); + y_2_8x16b = _mm_srai_epi16(y_2_8x16b, log_wd); + y_3_8x16b = _mm_srai_epi16(y_3_8x16b, log_wd); + + y_0_8x16b = _mm_adds_epi16(ofst_8x16b, y_0_8x16b); + y_1_8x16b = _mm_adds_epi16(ofst_8x16b, y_1_8x16b); + y_2_8x16b = _mm_adds_epi16(ofst_8x16b, y_2_8x16b); + y_3_8x16b = _mm_adds_epi16(ofst_8x16b, y_3_8x16b); + + y_0_16x8b = _mm_packus_epi16(y_0_8x16b, y_1_8x16b); + y_2_16x8b = _mm_packus_epi16(y_2_8x16b, y_3_8x16b); + y_1_16x8b = _mm_srli_si128(y_0_16x8b, 8); + y_3_16x8b = _mm_srli_si128(y_2_16x8b, 8); + + _mm_storel_epi64((__m128i *)pu1_dst, y_0_16x8b); + _mm_storel_epi64((__m128i *)(pu1_dst + dst_strd), y_1_16x8b); + _mm_storel_epi64((__m128i *)(pu1_dst + (dst_strd << 1)), y_2_16x8b); + _mm_storel_epi64((__m128i *)(pu1_dst + dst_strd * 3), y_3_16x8b); + + ht -= 4; + pu1_src += src_strd << 2; + pu1_dst += dst_strd << 2; + } + while(ht > 0); + } + else // wd == 16 + { + __m128i y_0L_8x16b, y_1L_8x16b, y_2L_8x16b, y_3L_8x16b; + __m128i y_0H_8x16b, y_1H_8x16b, y_2H_8x16b, y_3H_8x16b; + + __m128i zero_16x8b; + zero_16x8b = _mm_set1_epi8(0); + + do + { + y_0_16x8b = _mm_loadu_si128((__m128i *)pu1_src); + y_1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd)); + y_2_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + (src_strd << 1))); + y_3_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd * 3)); + + y_0L_8x16b = _mm_cvtepu8_epi16(y_0_16x8b); + y_0H_8x16b = _mm_unpackhi_epi8(y_0_16x8b, zero_16x8b); + y_1L_8x16b = _mm_cvtepu8_epi16(y_1_16x8b); + y_1H_8x16b = _mm_unpackhi_epi8(y_1_16x8b, zero_16x8b); + y_2L_8x16b = _mm_cvtepu8_epi16(y_2_16x8b); + y_2H_8x16b = _mm_unpackhi_epi8(y_2_16x8b, zero_16x8b); + y_3L_8x16b = _mm_cvtepu8_epi16(y_3_16x8b); + y_3H_8x16b = _mm_unpackhi_epi8(y_3_16x8b, zero_16x8b); + + y_0L_8x16b = _mm_mullo_epi16(y_0L_8x16b, wt_8x16b); + y_0H_8x16b = _mm_mullo_epi16(y_0H_8x16b, wt_8x16b); + y_1L_8x16b = _mm_mullo_epi16(y_1L_8x16b, wt_8x16b); + y_1H_8x16b = _mm_mullo_epi16(y_1H_8x16b, wt_8x16b); + y_2L_8x16b = _mm_mullo_epi16(y_2L_8x16b, wt_8x16b); + y_2H_8x16b = _mm_mullo_epi16(y_2H_8x16b, wt_8x16b); + y_3L_8x16b = _mm_mullo_epi16(y_3L_8x16b, wt_8x16b); + y_3H_8x16b = _mm_mullo_epi16(y_3H_8x16b, wt_8x16b); + + y_0L_8x16b = _mm_adds_epi16(round_8x16b, y_0L_8x16b); + y_0H_8x16b = _mm_adds_epi16(round_8x16b, y_0H_8x16b); + y_1L_8x16b = _mm_adds_epi16(round_8x16b, y_1L_8x16b); + y_1H_8x16b = _mm_adds_epi16(round_8x16b, y_1H_8x16b); + y_2L_8x16b = _mm_adds_epi16(round_8x16b, y_2L_8x16b); + y_2H_8x16b = _mm_adds_epi16(round_8x16b, y_2H_8x16b); + y_3L_8x16b = _mm_adds_epi16(round_8x16b, y_3L_8x16b); + y_3H_8x16b = _mm_adds_epi16(round_8x16b, y_3H_8x16b); + + y_0L_8x16b = _mm_srai_epi16(y_0L_8x16b, log_wd); + y_0H_8x16b = _mm_srai_epi16(y_0H_8x16b, log_wd); + y_1L_8x16b = _mm_srai_epi16(y_1L_8x16b, log_wd); + y_1H_8x16b = _mm_srai_epi16(y_1H_8x16b, log_wd); + y_2L_8x16b = _mm_srai_epi16(y_2L_8x16b, log_wd); + y_2H_8x16b = _mm_srai_epi16(y_2H_8x16b, log_wd); + y_3L_8x16b = _mm_srai_epi16(y_3L_8x16b, log_wd); + y_3H_8x16b = _mm_srai_epi16(y_3H_8x16b, log_wd); + + y_0L_8x16b = _mm_adds_epi16(ofst_8x16b, y_0L_8x16b); + y_0H_8x16b = _mm_adds_epi16(ofst_8x16b, y_0H_8x16b); + y_1L_8x16b = _mm_adds_epi16(ofst_8x16b, y_1L_8x16b); + y_1H_8x16b = _mm_adds_epi16(ofst_8x16b, y_1H_8x16b); + y_2L_8x16b = _mm_adds_epi16(ofst_8x16b, y_2L_8x16b); + y_2H_8x16b = _mm_adds_epi16(ofst_8x16b, y_2H_8x16b); + y_3L_8x16b = _mm_adds_epi16(ofst_8x16b, y_3L_8x16b); + y_3H_8x16b = _mm_adds_epi16(ofst_8x16b, y_3H_8x16b); + + y_0_16x8b = _mm_packus_epi16(y_0L_8x16b, y_0H_8x16b); + y_1_16x8b = _mm_packus_epi16(y_1L_8x16b, y_1H_8x16b); + y_2_16x8b = _mm_packus_epi16(y_2L_8x16b, y_2H_8x16b); + y_3_16x8b = _mm_packus_epi16(y_3L_8x16b, y_3H_8x16b); + + _mm_storeu_si128((__m128i *)pu1_dst, y_0_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), y_1_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + (dst_strd << 1)), y_2_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd * 3), y_3_16x8b); + + ht -= 4; + pu1_src += src_strd << 2; + pu1_dst += dst_strd << 2; + } + while(ht > 0); + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_weighted_pred_chroma_sse42 */ +/* */ +/* Description : This function performs the weighted prediction as */ +/* described in sec 8.4.2.3.2 titled "Weighted sample */ +/* prediction process" for chroma. The function gets one */ +/* ht x wd block, weights it, rounds it off, offsets it, */ +/* saturates it to unsigned 8-bit and stores it in the */ +/* destination block. (ht,wd) can be (2,2), (4,2), (2,4), */ +/* (4,4), (8,4), (4,8) or (8,8). */ +/* */ +/* Inputs : pu1_src - Pointer to source */ +/* pu1_dst - Pointer to destination */ +/* src_strd - stride for source */ +/* dst_strd - stride for destination */ +/* log_wd - number of bits to be rounded off */ +/* wt - weight values for u and v */ +/* ofst - offset values for u and v */ +/* ht - height of the block */ +/* wd - width of the block */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes */ +/* 04 02 2015 Kaushik Initial Version */ +/* Senthoor */ +/* */ +/*****************************************************************************/ +void ih264_weighted_pred_chroma_sse42(UWORD8 *pu1_src, + UWORD8 *pu1_dst, + WORD32 src_strd, + WORD32 dst_strd, + WORD32 log_wd, + WORD32 wt, + WORD32 ofst, + WORD32 ht, + WORD32 wd) +{ + __m128i y_0_16x8b, y_1_16x8b; + + __m128i wt_8x16b, round_8x16b, ofst_8x16b; + + WORD32 ofst_u, ofst_v; + WORD32 round_val; + + ofst_u = (WORD8)(ofst & 0xff); + ofst_v = (WORD8)(ofst >> 8); + round_val = 1 << (log_wd - 1); + ofst = (ofst_u & 0xffff) | (ofst_v << 16); + + wt_8x16b = _mm_set1_epi32(wt); + round_8x16b = _mm_set1_epi16(round_val); + ofst_8x16b = _mm_set1_epi32(ofst); + + if(wd == 2) + { + __m128i y_0_8x16b; + + do + { + y_0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); + y_1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src + src_strd)); + + y_0_16x8b = _mm_unpacklo_epi32(y_0_16x8b, y_1_16x8b); + + y_0_8x16b = _mm_cvtepu8_epi16(y_0_16x8b); + + y_0_8x16b = _mm_mullo_epi16(y_0_8x16b, wt_8x16b); + + y_0_8x16b = _mm_adds_epi16(round_8x16b, y_0_8x16b); + + y_0_8x16b = _mm_srai_epi16(y_0_8x16b, log_wd); + + y_0_8x16b = _mm_adds_epi16(ofst_8x16b, y_0_8x16b); + + y_0_16x8b = _mm_packus_epi16(y_0_8x16b, y_0_8x16b); + y_1_16x8b = _mm_srli_si128(y_0_16x8b, 4); + + *((WORD32 *)(pu1_dst)) = _mm_cvtsi128_si32(y_0_16x8b); + *((WORD32 *)(pu1_dst + dst_strd)) = _mm_cvtsi128_si32(y_1_16x8b); + + ht -= 2; + pu1_src += src_strd << 1; + pu1_dst += dst_strd << 1; + } + while(ht > 0); + } + else if(wd == 4) + { + __m128i y_0_8x16b, y_1_8x16b; + + do + { + y_0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src); + y_1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src + src_strd)); + + y_0_8x16b = _mm_cvtepu8_epi16(y_0_16x8b); + y_1_8x16b = _mm_cvtepu8_epi16(y_1_16x8b); + + y_0_8x16b = _mm_mullo_epi16(y_0_8x16b, wt_8x16b); + y_1_8x16b = _mm_mullo_epi16(y_1_8x16b, wt_8x16b); + + y_0_8x16b = _mm_adds_epi16(round_8x16b, y_0_8x16b); + y_1_8x16b = _mm_adds_epi16(round_8x16b, y_1_8x16b); + + y_0_8x16b = _mm_srai_epi16(y_0_8x16b, log_wd); + y_1_8x16b = _mm_srai_epi16(y_1_8x16b, log_wd); + + y_0_8x16b = _mm_adds_epi16(ofst_8x16b, y_0_8x16b); + y_1_8x16b = _mm_adds_epi16(ofst_8x16b, y_1_8x16b); + + y_0_16x8b = _mm_packus_epi16(y_0_8x16b, y_1_8x16b); + y_1_16x8b = _mm_srli_si128(y_0_16x8b, 8); + + _mm_storel_epi64((__m128i *)pu1_dst, y_0_16x8b); + _mm_storel_epi64((__m128i *)(pu1_dst + dst_strd), y_1_16x8b); + + ht -= 2; + pu1_src += src_strd << 1; + pu1_dst += dst_strd << 1; + } + while(ht > 0); + } + else // wd == 16 + { + __m128i y_2_16x8b, y_3_16x8b; + __m128i y_0L_8x16b, y_1L_8x16b, y_2L_8x16b, y_3L_8x16b; + __m128i y_0H_8x16b, y_1H_8x16b, y_2H_8x16b, y_3H_8x16b; + + __m128i zero_16x8b; + zero_16x8b = _mm_set1_epi8(0); + + do + { + y_0_16x8b = _mm_loadu_si128((__m128i *)pu1_src); + y_1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd)); + y_2_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + (src_strd << 1))); + y_3_16x8b = _mm_loadu_si128((__m128i *)(pu1_src + src_strd * 3)); + + y_0L_8x16b = _mm_cvtepu8_epi16(y_0_16x8b); + y_0H_8x16b = _mm_unpackhi_epi8(y_0_16x8b, zero_16x8b); + y_1L_8x16b = _mm_cvtepu8_epi16(y_1_16x8b); + y_1H_8x16b = _mm_unpackhi_epi8(y_1_16x8b, zero_16x8b); + y_2L_8x16b = _mm_cvtepu8_epi16(y_2_16x8b); + y_2H_8x16b = _mm_unpackhi_epi8(y_2_16x8b, zero_16x8b); + y_3L_8x16b = _mm_cvtepu8_epi16(y_3_16x8b); + y_3H_8x16b = _mm_unpackhi_epi8(y_3_16x8b, zero_16x8b); + + y_0L_8x16b = _mm_mullo_epi16(y_0L_8x16b, wt_8x16b); + y_0H_8x16b = _mm_mullo_epi16(y_0H_8x16b, wt_8x16b); + y_1L_8x16b = _mm_mullo_epi16(y_1L_8x16b, wt_8x16b); + y_1H_8x16b = _mm_mullo_epi16(y_1H_8x16b, wt_8x16b); + y_2L_8x16b = _mm_mullo_epi16(y_2L_8x16b, wt_8x16b); + y_2H_8x16b = _mm_mullo_epi16(y_2H_8x16b, wt_8x16b); + y_3L_8x16b = _mm_mullo_epi16(y_3L_8x16b, wt_8x16b); + y_3H_8x16b = _mm_mullo_epi16(y_3H_8x16b, wt_8x16b); + + y_0L_8x16b = _mm_adds_epi16(round_8x16b, y_0L_8x16b); + y_0H_8x16b = _mm_adds_epi16(round_8x16b, y_0H_8x16b); + y_1L_8x16b = _mm_adds_epi16(round_8x16b, y_1L_8x16b); + y_1H_8x16b = _mm_adds_epi16(round_8x16b, y_1H_8x16b); + y_2L_8x16b = _mm_adds_epi16(round_8x16b, y_2L_8x16b); + y_2H_8x16b = _mm_adds_epi16(round_8x16b, y_2H_8x16b); + y_3L_8x16b = _mm_adds_epi16(round_8x16b, y_3L_8x16b); + y_3H_8x16b = _mm_adds_epi16(round_8x16b, y_3H_8x16b); + + y_0L_8x16b = _mm_srai_epi16(y_0L_8x16b, log_wd); + y_0H_8x16b = _mm_srai_epi16(y_0H_8x16b, log_wd); + y_1L_8x16b = _mm_srai_epi16(y_1L_8x16b, log_wd); + y_1H_8x16b = _mm_srai_epi16(y_1H_8x16b, log_wd); + y_2L_8x16b = _mm_srai_epi16(y_2L_8x16b, log_wd); + y_2H_8x16b = _mm_srai_epi16(y_2H_8x16b, log_wd); + y_3L_8x16b = _mm_srai_epi16(y_3L_8x16b, log_wd); + y_3H_8x16b = _mm_srai_epi16(y_3H_8x16b, log_wd); + + y_0L_8x16b = _mm_adds_epi16(ofst_8x16b, y_0L_8x16b); + y_0H_8x16b = _mm_adds_epi16(ofst_8x16b, y_0H_8x16b); + y_1L_8x16b = _mm_adds_epi16(ofst_8x16b, y_1L_8x16b); + y_1H_8x16b = _mm_adds_epi16(ofst_8x16b, y_1H_8x16b); + y_2L_8x16b = _mm_adds_epi16(ofst_8x16b, y_2L_8x16b); + y_2H_8x16b = _mm_adds_epi16(ofst_8x16b, y_2H_8x16b); + y_3L_8x16b = _mm_adds_epi16(ofst_8x16b, y_3L_8x16b); + y_3H_8x16b = _mm_adds_epi16(ofst_8x16b, y_3H_8x16b); + + y_0_16x8b = _mm_packus_epi16(y_0L_8x16b, y_0H_8x16b); + y_1_16x8b = _mm_packus_epi16(y_1L_8x16b, y_1H_8x16b); + y_2_16x8b = _mm_packus_epi16(y_2L_8x16b, y_2H_8x16b); + y_3_16x8b = _mm_packus_epi16(y_3L_8x16b, y_3H_8x16b); + + _mm_storeu_si128((__m128i *)pu1_dst, y_0_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), y_1_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + (dst_strd << 1)), y_2_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd * 3), y_3_16x8b); + + ht -= 4; + pu1_src += src_strd << 2; + pu1_dst += dst_strd << 2; + } + while(ht > 0); + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_weighted_bi_pred_luma_sse42 */ +/* */ +/* Description : This function performs the weighted biprediction as */ +/* described in sec 8.4.2.3.2 titled "Weighted sample */ +/* prediction process" for luma. The function gets two */ +/* ht x wd blocks, weights them, adds them, rounds off the */ +/* sum, offsets it, saturates it to unsigned 8-bit and */ +/* stores it in the destination block. (ht,wd) can be */ +/* (4,4), (8,4), (4,8), (8,8), (16,8), (8,16) or (16,16). */ +/* */ +/* Inputs : pu1_src1 - Pointer to source 1 */ +/* pu1_src2 - Pointer to source 2 */ +/* pu1_dst - Pointer to destination */ +/* src_strd1 - stride for source 1 */ +/* src_strd2 - stride for source 2 */ +/* dst_strd2 - stride for destination */ +/* log_wd - number of bits to be rounded off */ +/* wt1 - weight value for source 1 */ +/* wt2 - weight value for source 2 */ +/* ofst1 - offset value for source 1 */ +/* ofst2 - offset value for source 2 */ +/* ht - height of the block */ +/* wd - width of the block */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes */ +/* 04 02 2015 Kaushik Initial Version */ +/* Senthoor */ +/* */ +/*****************************************************************************/ +void ih264_weighted_bi_pred_luma_sse42(UWORD8 *pu1_src1, + UWORD8 *pu1_src2, + UWORD8 *pu1_dst, + WORD32 src_strd1, + WORD32 src_strd2, + WORD32 dst_strd, + WORD32 log_wd, + WORD32 wt1, + WORD32 wt2, + WORD32 ofst1, + WORD32 ofst2, + WORD32 ht, + WORD32 wd) +{ + __m128i y1_0_16x8b, y1_1_16x8b; + __m128i y2_0_16x8b, y2_1_16x8b; + + __m128i wt1_8x16b, wt2_8x16b; + __m128i ofst_8x16b, round_8x16b; + + WORD32 ofst; + WORD32 round_val, shft; + + wt1 = (WORD16)(wt1 & 0xffff); + wt2 = (WORD16)(wt2 & 0xffff); + round_val = 1 << log_wd; + shft = log_wd + 1; + ofst1 = (WORD8)(ofst1 & 0xff); + ofst2 = (WORD8)(ofst2 & 0xff); + ofst = (ofst1 + ofst2 + 1) >> 1; + + wt1_8x16b = _mm_set1_epi16(wt1); + wt2_8x16b = _mm_set1_epi16(wt2); + round_8x16b = _mm_set1_epi16(round_val); + ofst_8x16b = _mm_set1_epi16(ofst); + + if(wd == 4) + { + __m128i y1_2_16x8b, y1_3_16x8b; + __m128i y2_2_16x8b, y2_3_16x8b; + + __m128i y1_0_8x16b, y1_2_8x16b; + __m128i y2_0_8x16b, y2_2_8x16b; + + do + { + y1_0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src1); + y1_1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src1 + src_strd1)); + y1_2_16x8b = _mm_loadl_epi64( + (__m128i *)(pu1_src1 + (src_strd1 << 1))); + y1_3_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src1 + src_strd1 * 3)); + + y2_0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src2); + y2_1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src2 + src_strd2)); + y2_2_16x8b = _mm_loadl_epi64( + (__m128i *)(pu1_src2 + (src_strd2 << 1))); + y2_3_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src2 + src_strd2 * 3)); + + y1_0_16x8b = _mm_unpacklo_epi32(y1_0_16x8b, y1_1_16x8b); + y1_2_16x8b = _mm_unpacklo_epi32(y1_2_16x8b, y1_3_16x8b); + y2_0_16x8b = _mm_unpacklo_epi32(y2_0_16x8b, y2_1_16x8b); + y2_2_16x8b = _mm_unpacklo_epi32(y2_2_16x8b, y2_3_16x8b); + + y1_0_8x16b = _mm_cvtepu8_epi16(y1_0_16x8b); + y1_2_8x16b = _mm_cvtepu8_epi16(y1_2_16x8b); + y2_0_8x16b = _mm_cvtepu8_epi16(y2_0_16x8b); + y2_2_8x16b = _mm_cvtepu8_epi16(y2_2_16x8b); + + y1_0_8x16b = _mm_mullo_epi16(y1_0_8x16b, wt1_8x16b); + y2_0_8x16b = _mm_mullo_epi16(y2_0_8x16b, wt2_8x16b); + y1_2_8x16b = _mm_mullo_epi16(y1_2_8x16b, wt1_8x16b); + y2_2_8x16b = _mm_mullo_epi16(y2_2_8x16b, wt2_8x16b); + + y1_0_8x16b = _mm_adds_epi16(y1_0_8x16b, y2_0_8x16b); + y1_2_8x16b = _mm_adds_epi16(y1_2_8x16b, y2_2_8x16b); + + y1_0_8x16b = _mm_adds_epi16(round_8x16b, y1_0_8x16b); + y1_2_8x16b = _mm_adds_epi16(round_8x16b, y1_2_8x16b); + + y1_0_8x16b = _mm_srai_epi16(y1_0_8x16b, shft); + y1_2_8x16b = _mm_srai_epi16(y1_2_8x16b, shft); + + y1_0_8x16b = _mm_adds_epi16(ofst_8x16b, y1_0_8x16b); + y1_2_8x16b = _mm_adds_epi16(ofst_8x16b, y1_2_8x16b); + + y1_0_16x8b = _mm_packus_epi16(y1_0_8x16b, y1_2_8x16b); + y1_1_16x8b = _mm_srli_si128(y1_0_16x8b, 4); + y1_2_16x8b = _mm_srli_si128(y1_0_16x8b, 8); + y1_3_16x8b = _mm_srli_si128(y1_0_16x8b, 12); + + *((WORD32 *)(pu1_dst)) = _mm_cvtsi128_si32(y1_0_16x8b); + *((WORD32 *)(pu1_dst + dst_strd)) = _mm_cvtsi128_si32(y1_1_16x8b); + *((WORD32 *)(pu1_dst + (dst_strd << 1))) = _mm_cvtsi128_si32(y1_2_16x8b); + *((WORD32 *)(pu1_dst + dst_strd * 3)) = _mm_cvtsi128_si32(y1_3_16x8b); + + + ht -= 4; + pu1_src1 += src_strd1 << 2; + pu1_src2 += src_strd2 << 2; + pu1_dst += dst_strd << 2; + } + while(ht > 0); + } + else if(wd == 8) + { + __m128i y1_2_16x8b, y1_3_16x8b; + __m128i y2_2_16x8b, y2_3_16x8b; + + __m128i y1_0_8x16b, y1_1_8x16b, y1_2_8x16b, y1_3_8x16b; + __m128i y2_0_8x16b, y2_1_8x16b, y2_2_8x16b, y2_3_8x16b; + + do + { + y1_0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src1); + y1_1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src1 + src_strd1)); + y1_2_16x8b = _mm_loadl_epi64( + (__m128i *)(pu1_src1 + (src_strd1 << 1))); + y1_3_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src1 + src_strd1 * 3)); + + y2_0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src2); + y2_1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src2 + src_strd2)); + y2_2_16x8b = _mm_loadl_epi64( + (__m128i *)(pu1_src2 + (src_strd2 << 1))); + y2_3_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src2 + src_strd2 * 3)); + + y1_0_8x16b = _mm_cvtepu8_epi16(y1_0_16x8b); + y1_1_8x16b = _mm_cvtepu8_epi16(y1_1_16x8b); + y1_2_8x16b = _mm_cvtepu8_epi16(y1_2_16x8b); + y1_3_8x16b = _mm_cvtepu8_epi16(y1_3_16x8b); + + y2_0_8x16b = _mm_cvtepu8_epi16(y2_0_16x8b); + y2_1_8x16b = _mm_cvtepu8_epi16(y2_1_16x8b); + y2_2_8x16b = _mm_cvtepu8_epi16(y2_2_16x8b); + y2_3_8x16b = _mm_cvtepu8_epi16(y2_3_16x8b); + + y1_0_8x16b = _mm_mullo_epi16(y1_0_8x16b, wt1_8x16b); + y2_0_8x16b = _mm_mullo_epi16(y2_0_8x16b, wt2_8x16b); + y1_1_8x16b = _mm_mullo_epi16(y1_1_8x16b, wt1_8x16b); + y2_1_8x16b = _mm_mullo_epi16(y2_1_8x16b, wt2_8x16b); + + y1_2_8x16b = _mm_mullo_epi16(y1_2_8x16b, wt1_8x16b); + y2_2_8x16b = _mm_mullo_epi16(y2_2_8x16b, wt2_8x16b); + y1_3_8x16b = _mm_mullo_epi16(y1_3_8x16b, wt1_8x16b); + y2_3_8x16b = _mm_mullo_epi16(y2_3_8x16b, wt2_8x16b); + + y1_0_8x16b = _mm_adds_epi16(y1_0_8x16b, y2_0_8x16b); + y1_1_8x16b = _mm_adds_epi16(y1_1_8x16b, y2_1_8x16b); + y1_2_8x16b = _mm_adds_epi16(y1_2_8x16b, y2_2_8x16b); + y1_3_8x16b = _mm_adds_epi16(y1_3_8x16b, y2_3_8x16b); + + y1_0_8x16b = _mm_adds_epi16(round_8x16b, y1_0_8x16b); + y1_1_8x16b = _mm_adds_epi16(round_8x16b, y1_1_8x16b); + y1_2_8x16b = _mm_adds_epi16(round_8x16b, y1_2_8x16b); + y1_3_8x16b = _mm_adds_epi16(round_8x16b, y1_3_8x16b); + + y1_0_8x16b = _mm_srai_epi16(y1_0_8x16b, shft); + y1_1_8x16b = _mm_srai_epi16(y1_1_8x16b, shft); + y1_2_8x16b = _mm_srai_epi16(y1_2_8x16b, shft); + y1_3_8x16b = _mm_srai_epi16(y1_3_8x16b, shft); + + y1_0_8x16b = _mm_adds_epi16(ofst_8x16b, y1_0_8x16b); + y1_1_8x16b = _mm_adds_epi16(ofst_8x16b, y1_1_8x16b); + y1_2_8x16b = _mm_adds_epi16(ofst_8x16b, y1_2_8x16b); + y1_3_8x16b = _mm_adds_epi16(ofst_8x16b, y1_3_8x16b); + + y1_0_16x8b = _mm_packus_epi16(y1_0_8x16b, y1_1_8x16b); + y1_2_16x8b = _mm_packus_epi16(y1_2_8x16b, y1_3_8x16b); + y1_1_16x8b = _mm_srli_si128(y1_0_16x8b, 8); + y1_3_16x8b = _mm_srli_si128(y1_2_16x8b, 8); + + _mm_storel_epi64((__m128i *)pu1_dst, y1_0_16x8b); + _mm_storel_epi64((__m128i *)(pu1_dst + dst_strd), y1_1_16x8b); + _mm_storel_epi64((__m128i *)(pu1_dst + (dst_strd << 1)), y1_2_16x8b); + _mm_storel_epi64((__m128i *)(pu1_dst + dst_strd * 3), y1_3_16x8b); + + ht -= 4; + pu1_src1 += src_strd1 << 2; + pu1_src2 += src_strd2 << 2; + pu1_dst += dst_strd << 2; + } + while(ht > 0); + } + else // wd == 16 + { + __m128i y1_0L_8x16b, y1_0H_8x16b, y1_1L_8x16b, y1_1H_8x16b; + __m128i y2_0L_8x16b, y2_0H_8x16b, y2_1L_8x16b, y2_1H_8x16b; + + __m128i zero_16x8b; + zero_16x8b = _mm_set1_epi8(0); + + do + { + y1_0_16x8b = _mm_loadu_si128((__m128i *)pu1_src1); + y1_1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src1 + src_strd1)); + y2_0_16x8b = _mm_loadu_si128((__m128i *)pu1_src2); + y2_1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src2 + src_strd2)); + + y1_0L_8x16b = _mm_cvtepu8_epi16(y1_0_16x8b); + y1_0H_8x16b = _mm_unpackhi_epi8(y1_0_16x8b, zero_16x8b); + y1_1L_8x16b = _mm_cvtepu8_epi16(y1_1_16x8b); + y1_1H_8x16b = _mm_unpackhi_epi8(y1_1_16x8b, zero_16x8b); + + y2_0L_8x16b = _mm_cvtepu8_epi16(y2_0_16x8b); + y2_0H_8x16b = _mm_unpackhi_epi8(y2_0_16x8b, zero_16x8b); + y2_1L_8x16b = _mm_cvtepu8_epi16(y2_1_16x8b); + y2_1H_8x16b = _mm_unpackhi_epi8(y2_1_16x8b, zero_16x8b); + + y1_0L_8x16b = _mm_mullo_epi16(y1_0L_8x16b, wt1_8x16b); + y1_0H_8x16b = _mm_mullo_epi16(y1_0H_8x16b, wt1_8x16b); + y1_1L_8x16b = _mm_mullo_epi16(y1_1L_8x16b, wt1_8x16b); + y1_1H_8x16b = _mm_mullo_epi16(y1_1H_8x16b, wt1_8x16b); + + y2_0L_8x16b = _mm_mullo_epi16(y2_0L_8x16b, wt2_8x16b); + y2_0H_8x16b = _mm_mullo_epi16(y2_0H_8x16b, wt2_8x16b); + y2_1L_8x16b = _mm_mullo_epi16(y2_1L_8x16b, wt2_8x16b); + y2_1H_8x16b = _mm_mullo_epi16(y2_1H_8x16b, wt2_8x16b); + + y1_0L_8x16b = _mm_adds_epi16(y1_0L_8x16b, y2_0L_8x16b); + y1_0H_8x16b = _mm_adds_epi16(y1_0H_8x16b, y2_0H_8x16b); + y1_1L_8x16b = _mm_adds_epi16(y1_1L_8x16b, y2_1L_8x16b); + y1_1H_8x16b = _mm_adds_epi16(y1_1H_8x16b, y2_1H_8x16b); + + y1_0L_8x16b = _mm_adds_epi16(round_8x16b, y1_0L_8x16b); + y1_0H_8x16b = _mm_adds_epi16(round_8x16b, y1_0H_8x16b); + y1_1L_8x16b = _mm_adds_epi16(round_8x16b, y1_1L_8x16b); + y1_1H_8x16b = _mm_adds_epi16(round_8x16b, y1_1H_8x16b); + + y1_0L_8x16b = _mm_srai_epi16(y1_0L_8x16b, shft); + y1_0H_8x16b = _mm_srai_epi16(y1_0H_8x16b, shft); + y1_1L_8x16b = _mm_srai_epi16(y1_1L_8x16b, shft); + y1_1H_8x16b = _mm_srai_epi16(y1_1H_8x16b, shft); + + y1_0L_8x16b = _mm_adds_epi16(ofst_8x16b, y1_0L_8x16b); + y1_0H_8x16b = _mm_adds_epi16(ofst_8x16b, y1_0H_8x16b); + y1_1L_8x16b = _mm_adds_epi16(ofst_8x16b, y1_1L_8x16b); + y1_1H_8x16b = _mm_adds_epi16(ofst_8x16b, y1_1H_8x16b); + + y1_0_16x8b = _mm_packus_epi16(y1_0L_8x16b, y1_0H_8x16b); + y1_1_16x8b = _mm_packus_epi16(y1_1L_8x16b, y1_1H_8x16b); + + _mm_storeu_si128((__m128i *)pu1_dst, y1_0_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), y1_1_16x8b); + + ht -= 2; + pu1_src1 += src_strd1 << 1; + pu1_src2 += src_strd2 << 1; + pu1_dst += dst_strd << 1; + } + while(ht > 0); + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264_weighted_bi_pred_chroma_sse42 */ +/* */ +/* Description : This function performs the weighted biprediction as */ +/* described in sec 8.4.2.3.2 titled "Weighted sample */ +/* prediction process" for chroma. The function gets two */ +/* ht x wd blocks, weights them, adds them, rounds off the */ +/* sum, offsets it, saturates it to unsigned 8-bit and */ +/* stores it in the destination block. (ht,wd) can be */ +/* (2,2), (4,2), (2,4), (4,4), (8,4), (4,8) or (8,8). */ +/* */ +/* Inputs : pu1_src1 - Pointer to source 1 */ +/* pu1_src2 - Pointer to source 2 */ +/* pu1_dst - Pointer to destination */ +/* src_strd1 - stride for source 1 */ +/* src_strd2 - stride for source 2 */ +/* dst_strd2 - stride for destination */ +/* log_wd - number of bits to be rounded off */ +/* wt1 - weight values for u and v in source 1 */ +/* wt2 - weight values for u and v in source 2 */ +/* ofst1 - offset value for u and v in source 1 */ +/* ofst2 - offset value for u and v in source 2 */ +/* ht - height of the block */ +/* wd - width of the block */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes */ +/* 04 02 2015 Kaushik Initial Version */ +/* Senthoor */ +/* */ +/*****************************************************************************/ +void ih264_weighted_bi_pred_chroma_sse42(UWORD8 *pu1_src1, + UWORD8 *pu1_src2, + UWORD8 *pu1_dst, + WORD32 src_strd1, + WORD32 src_strd2, + WORD32 dst_strd, + WORD32 log_wd, + WORD32 wt1, + WORD32 wt2, + WORD32 ofst1, + WORD32 ofst2, + WORD32 ht, + WORD32 wd) +{ + __m128i y1_0_16x8b, y1_1_16x8b; + __m128i y2_0_16x8b, y2_1_16x8b; + + __m128i wt1_8x16b, wt2_8x16b; + __m128i ofst_8x16b, round_8x16b; + + WORD32 ofst1_u, ofst2_u, ofst_u; + WORD32 ofst1_v, ofst2_v, ofst_v; + WORD32 round_val, shft, ofst_val; + + round_val = 1 << log_wd; + shft = log_wd + 1; + + ofst1_u = (WORD8)(ofst1 & 0xff); + ofst1_v = (WORD8)(ofst1 >> 8); + ofst2_u = (WORD8)(ofst2 & 0xff); + ofst2_v = (WORD8)(ofst2 >> 8); + + wt1_8x16b = _mm_set1_epi32(wt1); + wt2_8x16b = _mm_set1_epi32(wt2); + + ofst_u = (ofst1_u + ofst2_u + 1) >> 1; + ofst_v = (ofst1_v + ofst2_v + 1) >> 1; + ofst_val = (ofst_u & 0xffff) | (ofst_v << 16); + + round_8x16b = _mm_set1_epi16(round_val); + ofst_8x16b = _mm_set1_epi32(ofst_val); + + if(wd == 2) + { + __m128i y1_0_8x16b, y2_0_8x16b; + + do + { + y1_0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src1); + y1_1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src1 + src_strd1)); + + y2_0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src2); + y2_1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src2 + src_strd2)); + + y1_0_16x8b = _mm_unpacklo_epi32(y1_0_16x8b, y1_1_16x8b); + y2_0_16x8b = _mm_unpacklo_epi32(y2_0_16x8b, y2_1_16x8b); + + y1_0_8x16b = _mm_cvtepu8_epi16(y1_0_16x8b); + y2_0_8x16b = _mm_cvtepu8_epi16(y2_0_16x8b); + + y1_0_8x16b = _mm_mullo_epi16(y1_0_8x16b, wt1_8x16b); + y2_0_8x16b = _mm_mullo_epi16(y2_0_8x16b, wt2_8x16b); + + y1_0_8x16b = _mm_adds_epi16(y1_0_8x16b, y2_0_8x16b); + y1_0_8x16b = _mm_adds_epi16(round_8x16b, y1_0_8x16b); + + y1_0_8x16b = _mm_srai_epi16(y1_0_8x16b, shft); + y1_0_8x16b = _mm_adds_epi16(ofst_8x16b, y1_0_8x16b); + + y1_0_16x8b = _mm_packus_epi16(y1_0_8x16b, y1_0_8x16b); + y1_1_16x8b = _mm_srli_si128(y1_0_16x8b, 4); + + *((WORD32 *)(pu1_dst)) = _mm_cvtsi128_si32(y1_0_16x8b); + *((WORD32 *)(pu1_dst + dst_strd)) = _mm_cvtsi128_si32(y1_1_16x8b); + + ht -= 2; + pu1_src1 += src_strd1 << 1; + pu1_src2 += src_strd2 << 1; + pu1_dst += dst_strd << 1; + } + while(ht > 0); + } + else if(wd == 4) + { + __m128i y1_0_8x16b, y1_1_8x16b; + __m128i y2_0_8x16b, y2_1_8x16b; + + do + { + y1_0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src1); + y1_1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src1 + src_strd1)); + + y2_0_16x8b = _mm_loadl_epi64((__m128i *)pu1_src2); + y2_1_16x8b = _mm_loadl_epi64((__m128i *)(pu1_src2 + src_strd2)); + + y1_0_8x16b = _mm_cvtepu8_epi16(y1_0_16x8b); + y1_1_8x16b = _mm_cvtepu8_epi16(y1_1_16x8b); + + y2_0_8x16b = _mm_cvtepu8_epi16(y2_0_16x8b); + y2_1_8x16b = _mm_cvtepu8_epi16(y2_1_16x8b); + + y1_0_8x16b = _mm_mullo_epi16(y1_0_8x16b, wt1_8x16b); + y2_0_8x16b = _mm_mullo_epi16(y2_0_8x16b, wt2_8x16b); + y1_1_8x16b = _mm_mullo_epi16(y1_1_8x16b, wt1_8x16b); + y2_1_8x16b = _mm_mullo_epi16(y2_1_8x16b, wt2_8x16b); + + y1_0_8x16b = _mm_adds_epi16(y1_0_8x16b, y2_0_8x16b); + y1_1_8x16b = _mm_adds_epi16(y1_1_8x16b, y2_1_8x16b); + + y1_0_8x16b = _mm_adds_epi16(round_8x16b, y1_0_8x16b); + y1_1_8x16b = _mm_adds_epi16(round_8x16b, y1_1_8x16b); + + y1_0_8x16b = _mm_srai_epi16(y1_0_8x16b, shft); + y1_1_8x16b = _mm_srai_epi16(y1_1_8x16b, shft); + + y1_0_8x16b = _mm_adds_epi16(ofst_8x16b, y1_0_8x16b); + y1_1_8x16b = _mm_adds_epi16(ofst_8x16b, y1_1_8x16b); + + y1_0_16x8b = _mm_packus_epi16(y1_0_8x16b, y1_1_8x16b); + y1_1_16x8b = _mm_srli_si128(y1_0_16x8b, 8); + + _mm_storel_epi64((__m128i *)pu1_dst, y1_0_16x8b); + _mm_storel_epi64((__m128i *)(pu1_dst + dst_strd), y1_1_16x8b); + + ht -= 2; + pu1_src1 += src_strd1 << 1; + pu1_src2 += src_strd2 << 1; + pu1_dst += dst_strd << 1; + } + while(ht > 0); + } + else // wd == 8 + { + __m128i y1_0L_8x16b, y1_0H_8x16b, y1_1L_8x16b, y1_1H_8x16b; + __m128i y2_0L_8x16b, y2_0H_8x16b, y2_1L_8x16b, y2_1H_8x16b; + + __m128i zero_16x8b; + zero_16x8b = _mm_set1_epi8(0); + + do + { + y1_0_16x8b = _mm_loadu_si128((__m128i *)pu1_src1); + y1_1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src1 + src_strd1)); + y2_0_16x8b = _mm_loadu_si128((__m128i *)pu1_src2); + y2_1_16x8b = _mm_loadu_si128((__m128i *)(pu1_src2 + src_strd2)); + + y1_0L_8x16b = _mm_cvtepu8_epi16(y1_0_16x8b); + y1_0H_8x16b = _mm_unpackhi_epi8(y1_0_16x8b, zero_16x8b); + y1_1L_8x16b = _mm_cvtepu8_epi16(y1_1_16x8b); + y1_1H_8x16b = _mm_unpackhi_epi8(y1_1_16x8b, zero_16x8b); + + y2_0L_8x16b = _mm_cvtepu8_epi16(y2_0_16x8b); + y2_0H_8x16b = _mm_unpackhi_epi8(y2_0_16x8b, zero_16x8b); + y2_1L_8x16b = _mm_cvtepu8_epi16(y2_1_16x8b); + y2_1H_8x16b = _mm_unpackhi_epi8(y2_1_16x8b, zero_16x8b); + + y1_0L_8x16b = _mm_mullo_epi16(y1_0L_8x16b, wt1_8x16b); + y1_0H_8x16b = _mm_mullo_epi16(y1_0H_8x16b, wt1_8x16b); + y1_1L_8x16b = _mm_mullo_epi16(y1_1L_8x16b, wt1_8x16b); + y1_1H_8x16b = _mm_mullo_epi16(y1_1H_8x16b, wt1_8x16b); + + y2_0L_8x16b = _mm_mullo_epi16(y2_0L_8x16b, wt2_8x16b); + y2_0H_8x16b = _mm_mullo_epi16(y2_0H_8x16b, wt2_8x16b); + y2_1L_8x16b = _mm_mullo_epi16(y2_1L_8x16b, wt2_8x16b); + y2_1H_8x16b = _mm_mullo_epi16(y2_1H_8x16b, wt2_8x16b); + + y1_0L_8x16b = _mm_adds_epi16(y1_0L_8x16b, y2_0L_8x16b); + y1_0H_8x16b = _mm_adds_epi16(y1_0H_8x16b, y2_0H_8x16b); + y1_1L_8x16b = _mm_adds_epi16(y1_1L_8x16b, y2_1L_8x16b); + y1_1H_8x16b = _mm_adds_epi16(y1_1H_8x16b, y2_1H_8x16b); + + y1_0L_8x16b = _mm_adds_epi16(round_8x16b, y1_0L_8x16b); + y1_0H_8x16b = _mm_adds_epi16(round_8x16b, y1_0H_8x16b); + y1_1L_8x16b = _mm_adds_epi16(round_8x16b, y1_1L_8x16b); + y1_1H_8x16b = _mm_adds_epi16(round_8x16b, y1_1H_8x16b); + + y1_0L_8x16b = _mm_srai_epi16(y1_0L_8x16b, shft); + y1_0H_8x16b = _mm_srai_epi16(y1_0H_8x16b, shft); + y1_1L_8x16b = _mm_srai_epi16(y1_1L_8x16b, shft); + y1_1H_8x16b = _mm_srai_epi16(y1_1H_8x16b, shft); + + y1_0L_8x16b = _mm_adds_epi16(ofst_8x16b, y1_0L_8x16b); + y1_0H_8x16b = _mm_adds_epi16(ofst_8x16b, y1_0H_8x16b); + y1_1L_8x16b = _mm_adds_epi16(ofst_8x16b, y1_1L_8x16b); + y1_1H_8x16b = _mm_adds_epi16(ofst_8x16b, y1_1H_8x16b); + + y1_0_16x8b = _mm_packus_epi16(y1_0L_8x16b, y1_0H_8x16b); + y1_1_16x8b = _mm_packus_epi16(y1_1L_8x16b, y1_1H_8x16b); + + _mm_storeu_si128((__m128i *)pu1_dst, y1_0_16x8b); + _mm_storeu_si128((__m128i *)(pu1_dst + dst_strd), y1_1_16x8b); + + ht -= 2; + pu1_src1 += src_strd1 << 1; + pu1_src2 += src_strd2 << 1; + pu1_dst += dst_strd << 1; + } + while(ht > 0); + } +} diff --git a/dependencies/ih264d/decoder/arm/ih264d_function_selector.c b/dependencies/ih264d/decoder/arm/ih264d_function_selector.c new file mode 100644 index 00000000..925043bc --- /dev/null +++ b/dependencies/ih264d/decoder/arm/ih264d_function_selector.c @@ -0,0 +1,102 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** +******************************************************************************* +* @file +* ihevcd_function_selector.c +* +* @brief +* Contains functions to initialize function pointers used in hevc +* +* @author +* Naveen +* +* @par List of Functions: +* @remarks +* None +* +******************************************************************************* +*/ +/*****************************************************************************/ +/* File Includes */ +/*****************************************************************************/ +#include <stdio.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> + +#include "ih264_typedefs.h" +#include "iv.h" +#include "ivd.h" +#include "ih264_defs.h" +#include "ih264_size_defs.h" +#include "ih264_error.h" +#include "ih264_trans_quant_itrans_iquant.h" +#include "ih264_inter_pred_filters.h" + +#include "ih264d_structs.h" +#include "ih264d_function_selector.h" + +void ih264d_init_function_ptr(dec_struct_t *ps_codec) +{ + + IVD_ARCH_T e_proc_arch = ps_codec->e_processor_arch; + ih264d_init_function_ptr_generic(ps_codec); + switch(e_proc_arch) + { +#if defined(ARMV8) + case ARCH_ARMV8_GENERIC: + default: + ih264d_init_function_ptr_av8(ps_codec); + break; +#elif !defined(DISABLE_NEON) + case ARCH_ARM_A5: + case ARCH_ARM_A7: + case ARCH_ARM_A9: + case ARCH_ARM_A15: + case ARCH_ARM_A9Q: + default: + ih264d_init_function_ptr_a9q(ps_codec); + break; +#else + default: +#endif + case ARCH_ARM_NONEON: + break; + } +} + +void ih264d_init_arch(dec_struct_t *ps_codec) +{ +#ifdef DEFAULT_ARCH +#if DEFAULT_ARCH == D_ARCH_ARM_NONEON + ps_codec->e_processor_arch = ARCH_ARM_NONEON; +#elif DEFAULT_ARCH == D_ARCH_ARMV8_GENERIC + ps_codec->e_processor_arch = ARCH_ARMV8_GENERIC; +#elif DEFAULT_ARCH == D_ARCH_ARM_NEONINTR + ps_codec->e_processor_arch = ARCH_ARM_NEONINTR; +#else + ps_codec->e_processor_arch = ARCH_ARM_A9Q; +#endif +#else + ps_codec->e_processor_arch = ARCH_ARM_A9Q; +#endif + +} diff --git a/dependencies/ih264d/decoder/arm/ih264d_function_selector_a9q.c b/dependencies/ih264d/decoder/arm/ih264d_function_selector_a9q.c new file mode 100644 index 00000000..0cf85816 --- /dev/null +++ b/dependencies/ih264d/decoder/arm/ih264d_function_selector_a9q.c @@ -0,0 +1,200 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** +******************************************************************************* +* @file +* ih264e_function_selector_a9q.c +* +* @brief +* Contains functions to initialize function pointers of codec context +* +* @author +* Ittiam +* +* @par List of Functions: +* - ih264e_init_function_ptr_a9q +* +* @remarks +* None +* +******************************************************************************* +*/ + + +/*****************************************************************************/ +/* File Includes */ +/*****************************************************************************/ + +/* System Include files */ +#include <stdio.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> + +/* User Include files */ +#include "ih264_typedefs.h" +#include "iv.h" +#include "ivd.h" +#include "ih264_defs.h" +#include "ih264_size_defs.h" +#include "ih264_error.h" +#include "ih264_trans_quant_itrans_iquant.h" +#include "ih264_inter_pred_filters.h" + +#include "ih264d_structs.h" + + +/** +******************************************************************************* +* +* @brief Initialize the intra/inter/transform/deblk function pointers of +* codec context +* +* @par Description: the current routine initializes the function pointers of +* codec context basing on the architecture in use +* +* @param[in] ps_codec +* Codec context pointer +* +* @returns none +* +* @remarks none +* +******************************************************************************* +*/ +void ih264d_init_function_ptr_a9q(dec_struct_t *ps_codec) +{ + + /* Init function pointers for intra pred leaf level functions luma + * Intra 16x16 */ + ps_codec->apf_intra_pred_luma_16x16[0] = ih264_intra_pred_luma_16x16_mode_vert_a9q; + ps_codec->apf_intra_pred_luma_16x16[1] = ih264_intra_pred_luma_16x16_mode_horz_a9q; + ps_codec->apf_intra_pred_luma_16x16[2] = ih264_intra_pred_luma_16x16_mode_dc_a9q; + ps_codec->apf_intra_pred_luma_16x16[3] = ih264_intra_pred_luma_16x16_mode_plane_a9q; + + /* Init function pointers for intra pred leaf level functions luma + * Intra 4x4 */ + ps_codec->apf_intra_pred_luma_4x4[0] = ih264_intra_pred_luma_4x4_mode_vert_a9q; + ps_codec->apf_intra_pred_luma_4x4[1] = ih264_intra_pred_luma_4x4_mode_horz_a9q; + ps_codec->apf_intra_pred_luma_4x4[2] = ih264_intra_pred_luma_4x4_mode_dc_a9q; + ps_codec->apf_intra_pred_luma_4x4[3] = ih264_intra_pred_luma_4x4_mode_diag_dl_a9q; + ps_codec->apf_intra_pred_luma_4x4[4] = ih264_intra_pred_luma_4x4_mode_diag_dr_a9q; + ps_codec->apf_intra_pred_luma_4x4[5] = ih264_intra_pred_luma_4x4_mode_vert_r_a9q; + ps_codec->apf_intra_pred_luma_4x4[6] = ih264_intra_pred_luma_4x4_mode_horz_d_a9q; + ps_codec->apf_intra_pred_luma_4x4[7] = ih264_intra_pred_luma_4x4_mode_vert_l_a9q; + ps_codec->apf_intra_pred_luma_4x4[8] = ih264_intra_pred_luma_4x4_mode_horz_u_a9q; + + /* Init function pointers for intra pred leaf level functions luma + * Intra 8x8 */ + ps_codec->apf_intra_pred_luma_8x8[0] = ih264_intra_pred_luma_8x8_mode_vert_a9q; + ps_codec->apf_intra_pred_luma_8x8[1] = ih264_intra_pred_luma_8x8_mode_horz_a9q; + ps_codec->apf_intra_pred_luma_8x8[2] = ih264_intra_pred_luma_8x8_mode_dc_a9q; + ps_codec->apf_intra_pred_luma_8x8[3] = ih264_intra_pred_luma_8x8_mode_diag_dl_a9q; + ps_codec->apf_intra_pred_luma_8x8[4] = ih264_intra_pred_luma_8x8_mode_diag_dr_a9q; + ps_codec->apf_intra_pred_luma_8x8[5] = ih264_intra_pred_luma_8x8_mode_vert_r_a9q; + ps_codec->apf_intra_pred_luma_8x8[6] = ih264_intra_pred_luma_8x8_mode_horz_d_a9q; + ps_codec->apf_intra_pred_luma_8x8[7] = ih264_intra_pred_luma_8x8_mode_vert_l_a9q; + ps_codec->apf_intra_pred_luma_8x8[8] = ih264_intra_pred_luma_8x8_mode_horz_u_a9q; + + /* ih264_intra_pred_luma_8x8_mode_ref_filtering_a9q does not handle all availibilities */ + ps_codec->pf_intra_pred_ref_filtering = ih264_intra_pred_luma_8x8_mode_ref_filtering; + + /* Init function pointers for intra pred leaf level functions chroma + * Intra 8x8 */ + ps_codec->apf_intra_pred_chroma[0] = ih264_intra_pred_chroma_8x8_mode_vert_a9q; + ps_codec->apf_intra_pred_chroma[1] = ih264_intra_pred_chroma_8x8_mode_horz_a9q; + /* ih264_intra_pred_chroma_8x8_mode_dc_a9q does not support interlaced clips, hence using C */ + ps_codec->apf_intra_pred_chroma[2] = ih264_intra_pred_chroma_8x8_mode_dc; + ps_codec->apf_intra_pred_chroma[3] = ih264_intra_pred_chroma_8x8_mode_plane_a9q; + + + ps_codec->pf_default_weighted_pred_luma = ih264_default_weighted_pred_luma_a9q; + ps_codec->pf_default_weighted_pred_chroma = ih264_default_weighted_pred_chroma_a9q; + ps_codec->pf_weighted_pred_luma = ih264_weighted_pred_luma_a9q; + ps_codec->pf_weighted_pred_chroma = ih264_weighted_pred_chroma_a9q; + ps_codec->pf_weighted_bi_pred_luma = ih264_weighted_bi_pred_luma_a9q; + ps_codec->pf_weighted_bi_pred_chroma = ih264_weighted_bi_pred_chroma_a9q; + + /* Padding Functions */ + ps_codec->pf_pad_top = ih264_pad_top_a9q; + ps_codec->pf_pad_bottom = ih264_pad_bottom; + + ps_codec->pf_pad_left_luma = ih264_pad_left_luma_a9q; + ps_codec->pf_pad_right_luma = ih264_pad_right_luma_a9q; + ps_codec->pf_pad_left_chroma = ih264_pad_left_chroma_a9q; + ps_codec->pf_pad_right_chroma = ih264_pad_right_chroma_a9q; + + ps_codec->pf_iquant_itrans_recon_luma_4x4 = ih264_iquant_itrans_recon_4x4_a9; + ps_codec->pf_iquant_itrans_recon_luma_4x4_dc = ih264_iquant_itrans_recon_4x4_dc_a9; + ps_codec->pf_iquant_itrans_recon_luma_8x8 = ih264_iquant_itrans_recon_8x8_a9; + ps_codec->pf_iquant_itrans_recon_luma_8x8_dc = ih264_iquant_itrans_recon_8x8_dc_a9; + ps_codec->pf_ihadamard_scaling_4x4 = ih264_ihadamard_scaling_4x4_a9; + + + ps_codec->pf_iquant_itrans_recon_chroma_4x4 = ih264_iquant_itrans_recon_chroma_4x4_a9; + ps_codec->pf_iquant_itrans_recon_chroma_4x4_dc = ih264_iquant_itrans_recon_chroma_4x4_dc_a9; + + /* Init fn ptr luma deblocking */ + ps_codec->pf_deblk_luma_vert_bs4 = ih264_deblk_luma_vert_bs4_a9; + ps_codec->pf_deblk_luma_vert_bslt4 = ih264_deblk_luma_vert_bslt4_a9; + ps_codec->pf_deblk_luma_vert_bs4_mbaff = ih264_deblk_luma_vert_bs4_mbaff_a9; + ps_codec->pf_deblk_luma_vert_bslt4_mbaff = ih264_deblk_luma_vert_bslt4_mbaff_a9; + + ps_codec->pf_deblk_luma_horz_bs4 = ih264_deblk_luma_horz_bs4_a9; + ps_codec->pf_deblk_luma_horz_bslt4 = ih264_deblk_luma_horz_bslt4_a9; + + /* Init fn ptr chroma deblocking */ + ps_codec->pf_deblk_chroma_vert_bs4 = ih264_deblk_chroma_vert_bs4_a9; + ps_codec->pf_deblk_chroma_vert_bslt4 = ih264_deblk_chroma_vert_bslt4_a9; + ps_codec->pf_deblk_chroma_vert_bs4_mbaff = ih264_deblk_chroma_vert_bs4_mbaff_a9; + ps_codec->pf_deblk_chroma_vert_bslt4_mbaff = ih264_deblk_chroma_vert_bslt4_mbaff_a9; + + ps_codec->pf_deblk_chroma_horz_bs4 = ih264_deblk_chroma_horz_bs4_a9; + ps_codec->pf_deblk_chroma_horz_bslt4 = ih264_deblk_chroma_horz_bslt4_a9; + + + /* Inter pred leaf level functions */ + ps_codec->apf_inter_pred_luma[0] = ih264_inter_pred_luma_copy_a9q; + + ps_codec->apf_inter_pred_luma[1] = ih264_inter_pred_luma_horz_qpel_a9q; + ps_codec->apf_inter_pred_luma[2] = ih264_inter_pred_luma_horz_a9q; + ps_codec->apf_inter_pred_luma[3] = ih264_inter_pred_luma_horz_qpel_a9q; + ps_codec->apf_inter_pred_luma[4] = ih264_inter_pred_luma_vert_qpel_a9q; + + ps_codec->apf_inter_pred_luma[5] = ih264_inter_pred_luma_horz_qpel_vert_qpel_a9q; + + ps_codec->apf_inter_pred_luma[6] = ih264_inter_pred_luma_horz_hpel_vert_qpel_a9q; + + ps_codec->apf_inter_pred_luma[7] = ih264_inter_pred_luma_horz_qpel_vert_qpel_a9q; + + ps_codec->apf_inter_pred_luma[8] = ih264_inter_pred_luma_vert_a9q; + ps_codec->apf_inter_pred_luma[9] = ih264_inter_pred_luma_horz_qpel_vert_hpel_a9q; + ps_codec->apf_inter_pred_luma[10] = ih264_inter_pred_luma_horz_hpel_vert_hpel_a9q; + ps_codec->apf_inter_pred_luma[11] = ih264_inter_pred_luma_horz_qpel_vert_hpel_a9q; + ps_codec->apf_inter_pred_luma[12] = ih264_inter_pred_luma_vert_qpel_a9q; + ps_codec->apf_inter_pred_luma[13] = ih264_inter_pred_luma_horz_qpel_vert_qpel_a9q; + ps_codec->apf_inter_pred_luma[14] = ih264_inter_pred_luma_horz_hpel_vert_qpel_a9q; + ps_codec->apf_inter_pred_luma[15] = ih264_inter_pred_luma_horz_qpel_vert_qpel_a9q; + + ps_codec->pf_inter_pred_chroma = ih264_inter_pred_chroma_a9q; + + + return; +} diff --git a/dependencies/ih264d/decoder/arm/ih264d_function_selector_av8.c b/dependencies/ih264d/decoder/arm/ih264d_function_selector_av8.c new file mode 100644 index 00000000..5715ee04 --- /dev/null +++ b/dependencies/ih264d/decoder/arm/ih264d_function_selector_av8.c @@ -0,0 +1,191 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** +******************************************************************************* +* @file +* ih264e_function_selector_av8.c +* +* @brief +* Contains functions to initialize function pointers of codec context +* +* @author +* Ittiam +* +* @par List of Functions: +* - ih264e_init_function_ptr_av8 +* +* @remarks +* None +* +******************************************************************************* +*/ + + +/*****************************************************************************/ +/* File Includes */ +/*****************************************************************************/ + +/* System Include files */ +#include <stdio.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> + +/* User Include files */ +#include "ih264_typedefs.h" +#include "iv.h" +#include "ivd.h" +#include "ih264_defs.h" +#include "ih264_size_defs.h" +#include "ih264_error.h" +#include "ih264_trans_quant_itrans_iquant.h" +#include "ih264_inter_pred_filters.h" + +#include "ih264d_structs.h" +#include "ih264d_function_selector.h" + + +/** +******************************************************************************* +* +* @brief Initialize the intra/inter/transform/deblk function pointers of +* codec context +* +* @par Description: the current routine initializes the function pointers of +* codec context basing on the architecture in use +* +* @param[in] ps_codec +* Codec context pointer +* +* @returns none +* +* @remarks none +* +******************************************************************************* +*/ +void ih264d_init_function_ptr_av8(dec_struct_t *ps_codec) +{ + /* Init function pointers for intra pred leaf level functions luma + * Intra 16x16 */ + ps_codec->apf_intra_pred_luma_16x16[0] = ih264_intra_pred_luma_16x16_mode_vert_av8; + ps_codec->apf_intra_pred_luma_16x16[1] = ih264_intra_pred_luma_16x16_mode_horz_av8; + ps_codec->apf_intra_pred_luma_16x16[2] = ih264_intra_pred_luma_16x16_mode_dc_av8; + ps_codec->apf_intra_pred_luma_16x16[3] = ih264_intra_pred_luma_16x16_mode_plane_av8; + + /* Init function pointers for intra pred leaf level functions luma + * Intra 4x4 */ + ps_codec->apf_intra_pred_luma_4x4[0] = ih264_intra_pred_luma_4x4_mode_vert_av8; + ps_codec->apf_intra_pred_luma_4x4[1] = ih264_intra_pred_luma_4x4_mode_horz_av8; + ps_codec->apf_intra_pred_luma_4x4[2] = ih264_intra_pred_luma_4x4_mode_dc_av8; + ps_codec->apf_intra_pred_luma_4x4[3] = ih264_intra_pred_luma_4x4_mode_diag_dl_av8; + ps_codec->apf_intra_pred_luma_4x4[4] = ih264_intra_pred_luma_4x4_mode_diag_dr_av8; + ps_codec->apf_intra_pred_luma_4x4[5] = ih264_intra_pred_luma_4x4_mode_vert_r_av8; + ps_codec->apf_intra_pred_luma_4x4[6] = ih264_intra_pred_luma_4x4_mode_horz_d_av8; + ps_codec->apf_intra_pred_luma_4x4[7] = ih264_intra_pred_luma_4x4_mode_vert_l_av8; + ps_codec->apf_intra_pred_luma_4x4[8] = ih264_intra_pred_luma_4x4_mode_horz_u_av8; + + /* Init function pointers for intra pred leaf level functions luma + * Intra 8x8 */ + ps_codec->apf_intra_pred_luma_8x8[0] = ih264_intra_pred_luma_8x8_mode_vert_av8; + ps_codec->apf_intra_pred_luma_8x8[1] = ih264_intra_pred_luma_8x8_mode_horz_av8; + ps_codec->apf_intra_pred_luma_8x8[2] = ih264_intra_pred_luma_8x8_mode_dc_av8; + ps_codec->apf_intra_pred_luma_8x8[3] = ih264_intra_pred_luma_8x8_mode_diag_dl_av8; + ps_codec->apf_intra_pred_luma_8x8[4] = ih264_intra_pred_luma_8x8_mode_diag_dr_av8; + ps_codec->apf_intra_pred_luma_8x8[5] = ih264_intra_pred_luma_8x8_mode_vert_r_av8; + ps_codec->apf_intra_pred_luma_8x8[6] = ih264_intra_pred_luma_8x8_mode_horz_d_av8; + ps_codec->apf_intra_pred_luma_8x8[7] = ih264_intra_pred_luma_8x8_mode_vert_l_av8; + ps_codec->apf_intra_pred_luma_8x8[8] = ih264_intra_pred_luma_8x8_mode_horz_u_av8; + + ps_codec->pf_intra_pred_ref_filtering = ih264_intra_pred_luma_8x8_mode_ref_filtering; + + /* Init function pointers for intra pred leaf level functions chroma + * Intra 8x8 */ + ps_codec->apf_intra_pred_chroma[0] = ih264_intra_pred_chroma_8x8_mode_vert_av8; + ps_codec->apf_intra_pred_chroma[1] = ih264_intra_pred_chroma_8x8_mode_horz_av8; + /* ih264_intra_pred_chroma_8x8_mode_dc_av8 does not support interlaced clips, hence using C */ + ps_codec->apf_intra_pred_chroma[2] = ih264_intra_pred_chroma_8x8_mode_dc; + ps_codec->apf_intra_pred_chroma[3] = ih264_intra_pred_chroma_8x8_mode_plane_av8; + + ps_codec->pf_default_weighted_pred_luma = ih264_default_weighted_pred_luma_av8; + ps_codec->pf_default_weighted_pred_chroma = ih264_default_weighted_pred_chroma_av8; + ps_codec->pf_weighted_pred_luma = ih264_weighted_pred_luma_av8; + ps_codec->pf_weighted_pred_chroma = ih264_weighted_pred_chroma_av8; + ps_codec->pf_weighted_bi_pred_luma = ih264_weighted_bi_pred_luma_av8; + ps_codec->pf_weighted_bi_pred_chroma = ih264_weighted_bi_pred_chroma_av8; + + /* Padding Functions */ + ps_codec->pf_pad_top = ih264_pad_top_av8; + ps_codec->pf_pad_bottom = ih264_pad_bottom; + ps_codec->pf_pad_left_luma = ih264_pad_left_luma_av8; + ps_codec->pf_pad_left_chroma = ih264_pad_left_chroma_av8; + ps_codec->pf_pad_right_luma = ih264_pad_right_luma_av8; + ps_codec->pf_pad_right_chroma = ih264_pad_right_chroma_av8; + + + ps_codec->pf_iquant_itrans_recon_luma_4x4 = ih264_iquant_itrans_recon_4x4_av8; + ps_codec->pf_iquant_itrans_recon_luma_4x4_dc = ih264_iquant_itrans_recon_4x4_dc_av8; + ps_codec->pf_iquant_itrans_recon_luma_8x8 = ih264_iquant_itrans_recon_8x8_av8; + ps_codec->pf_iquant_itrans_recon_luma_8x8_dc = ih264_iquant_itrans_recon_8x8_dc_av8; + ps_codec->pf_iquant_itrans_recon_chroma_4x4 = ih264_iquant_itrans_recon_chroma_4x4_av8; + ps_codec->pf_iquant_itrans_recon_chroma_4x4_dc = ih264_iquant_itrans_recon_chroma_4x4_dc_av8; + ps_codec->pf_ihadamard_scaling_4x4 = ih264_ihadamard_scaling_4x4_av8; + + + /* Init fn ptr luma deblocking */ + ps_codec->pf_deblk_luma_vert_bs4 = ih264_deblk_luma_vert_bs4_av8; + ps_codec->pf_deblk_luma_vert_bslt4 = ih264_deblk_luma_vert_bslt4_av8; + ps_codec->pf_deblk_luma_vert_bs4_mbaff = ih264_deblk_luma_vert_bs4_mbaff; + ps_codec->pf_deblk_luma_vert_bslt4_mbaff = ih264_deblk_luma_vert_bslt4_mbaff; + + ps_codec->pf_deblk_luma_horz_bs4 = ih264_deblk_luma_horz_bs4_av8; + ps_codec->pf_deblk_luma_horz_bslt4 = ih264_deblk_luma_horz_bslt4_av8; + + /* Init fn ptr chroma deblocking */ + ps_codec->pf_deblk_chroma_vert_bs4 = ih264_deblk_chroma_vert_bs4_av8; + ps_codec->pf_deblk_chroma_vert_bslt4 = ih264_deblk_chroma_vert_bslt4_av8; + ps_codec->pf_deblk_chroma_vert_bs4_mbaff = ih264_deblk_chroma_vert_bs4_mbaff; + ps_codec->pf_deblk_chroma_vert_bslt4_mbaff = ih264_deblk_chroma_vert_bslt4_mbaff; + + ps_codec->pf_deblk_chroma_horz_bs4 = ih264_deblk_chroma_horz_bs4_av8; + ps_codec->pf_deblk_chroma_horz_bslt4 = ih264_deblk_chroma_horz_bslt4_av8; + + /* Inter pred leaf level functions */ + ps_codec->apf_inter_pred_luma[0] = ih264_inter_pred_luma_copy_av8; + ps_codec->apf_inter_pred_luma[1] = ih264_inter_pred_luma_horz_qpel_av8; + ps_codec->apf_inter_pred_luma[2] = ih264_inter_pred_luma_horz_av8; + ps_codec->apf_inter_pred_luma[3] = ih264_inter_pred_luma_horz_qpel_av8; + ps_codec->apf_inter_pred_luma[4] = ih264_inter_pred_luma_vert_qpel_av8; + ps_codec->apf_inter_pred_luma[5] = ih264_inter_pred_luma_horz_qpel_vert_qpel_av8; + ps_codec->apf_inter_pred_luma[6] = ih264_inter_pred_luma_horz_hpel_vert_qpel_av8; + ps_codec->apf_inter_pred_luma[7] = ih264_inter_pred_luma_horz_qpel_vert_qpel_av8; + ps_codec->apf_inter_pred_luma[8] = ih264_inter_pred_luma_vert_av8; + ps_codec->apf_inter_pred_luma[9] = ih264_inter_pred_luma_horz_qpel_vert_hpel_av8; + ps_codec->apf_inter_pred_luma[10] = ih264_inter_pred_luma_horz_hpel_vert_hpel_av8; + ps_codec->apf_inter_pred_luma[11] = ih264_inter_pred_luma_horz_qpel_vert_hpel_av8; + ps_codec->apf_inter_pred_luma[12] = ih264_inter_pred_luma_vert_qpel_av8; + ps_codec->apf_inter_pred_luma[13] = ih264_inter_pred_luma_horz_qpel_vert_qpel_av8; + ps_codec->apf_inter_pred_luma[14] = ih264_inter_pred_luma_horz_hpel_vert_qpel_av8; + ps_codec->apf_inter_pred_luma[15] = ih264_inter_pred_luma_horz_qpel_vert_qpel_av8; + + ps_codec->pf_inter_pred_chroma = ih264_inter_pred_chroma_av8; + + + return; +} diff --git a/dependencies/ih264d/decoder/ih264d.h b/dependencies/ih264d/decoder/ih264d.h new file mode 100644 index 00000000..fa9d7f1d --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d.h @@ -0,0 +1,734 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/*****************************************************************************/ +/* */ +/* File Name : ih264d.h */ +/* */ +/* Description : This file contains all the necessary structure and */ +/* enumeration definitions needed for the Application */ +/* Program Interface(API) of the Ittiam H264 ASP */ +/* Decoder on Cortex A8 - Neon platform */ +/* */ +/* List of Functions : ih264d_api_function */ +/* */ +/* Issues / Problems : None */ +/* */ +/* Revision History : */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 26 08 2010 100239(RCY) Draft */ +/* */ +/*****************************************************************************/ + +#ifndef _IH264D_H_ +#define _IH264D_H_ +#ifdef __cplusplus +extern "C" { +#endif + +#include "iv.h" +#include "ivd.h" + + +/*****************************************************************************/ +/* Constant Macros */ +/*****************************************************************************/ +#define IVD_ERROR_MASK 0xFF + +/*****************************************************************************/ +/* Function Macros */ +/*****************************************************************************/ +#define IS_IVD_CONCEALMENT_APPLIED(x) (x & (1 << IVD_APPLIEDCONCEALMENT)) +#define IS_IVD_INSUFFICIENTDATA_ERROR(x) (x & (1 << IVD_INSUFFICIENTDATA)) +#define IS_IVD_CORRUPTEDDATA_ERROR(x) (x & (1 << IVD_CORRUPTEDDATA)) +#define IS_IVD_CORRUPTEDHEADER_ERROR(x) (x & (1 << IVD_CORRUPTEDHEADER)) +#define IS_IVD_UNSUPPORTEDINPUT_ERROR(x) (x & (1 << IVD_UNSUPPORTEDINPUT)) +#define IS_IVD_UNSUPPORTEDPARAM_ERROR(x) (x & (1 << IVD_UNSUPPORTEDPARAM)) +#define IS_IVD_FATAL_ERROR(x) (x & (1 << IVD_FATALERROR)) +#define IS_IVD_INVALID_BITSTREAM_ERROR(x) (x & (1 << IVD_INVALID_BITSTREAM)) +#define IS_IVD_INCOMPLETE_BITSTREAM_ERROR(x) (x & (1 << IVD_INCOMPLETE_BITSTREAM)) + + +/*****************************************************************************/ +/* API Function Prototype */ +/*****************************************************************************/ +IV_API_CALL_STATUS_T ih264d_api_function(iv_obj_t *ps_handle, void *pv_api_ip,void *pv_api_op); + +/*****************************************************************************/ +/* Enums */ +/*****************************************************************************/ +/* Codec Error codes for H264 ASP Decoder */ + +typedef enum { + + IH264D_VID_HDR_DEC_NUM_FRM_BUF_NOT_SUFFICIENT = IVD_DUMMY_ELEMENT_FOR_CODEC_EXTENSIONS + 1, + +}IH264D_ERROR_CODES_T; + +/*****************************************************************************/ +/* Extended Structures */ +/*****************************************************************************/ + + +/*****************************************************************************/ +/* Delete Codec */ +/*****************************************************************************/ + + +typedef struct { + ivd_delete_ip_t s_ivd_delete_ip_t; +}ih264d_delete_ip_t; + + +typedef struct{ + ivd_delete_op_t s_ivd_delete_op_t; +}ih264d_delete_op_t; + + +/*****************************************************************************/ +/* Initialize decoder */ +/*****************************************************************************/ + + +typedef struct { + ivd_create_ip_t s_ivd_create_ip_t; +}ih264d_create_ip_t; + + +typedef struct{ + ivd_create_op_t s_ivd_create_op_t; +}ih264d_create_op_t; + + +/*****************************************************************************/ +/* Video Decode */ +/*****************************************************************************/ + + +typedef struct { + ivd_video_decode_ip_t s_ivd_video_decode_ip_t; +}ih264d_video_decode_ip_t; + + +typedef struct{ + ivd_video_decode_op_t s_ivd_video_decode_op_t; +}ih264d_video_decode_op_t; + + +/*****************************************************************************/ +/* Get Display Frame */ +/*****************************************************************************/ + + +typedef struct +{ + ivd_get_display_frame_ip_t s_ivd_get_display_frame_ip_t; +}ih264d_get_display_frame_ip_t; + + +typedef struct +{ + ivd_get_display_frame_op_t s_ivd_get_display_frame_op_t; +}ih264d_get_display_frame_op_t; + +/*****************************************************************************/ +/* Set Display Frame */ +/*****************************************************************************/ + + +typedef struct +{ + ivd_set_display_frame_ip_t s_ivd_set_display_frame_ip_t; +}ih264d_set_display_frame_ip_t; + + +typedef struct +{ + ivd_set_display_frame_op_t s_ivd_set_display_frame_op_t; +}ih264d_set_display_frame_op_t; + +/*****************************************************************************/ +/* Release Display Buffers */ +/*****************************************************************************/ + + +typedef struct +{ + ivd_rel_display_frame_ip_t s_ivd_rel_display_frame_ip_t; +}ih264d_rel_display_frame_ip_t; + + +typedef struct +{ + ivd_rel_display_frame_op_t s_ivd_rel_display_frame_op_t; +}ih264d_rel_display_frame_op_t; + + +typedef enum { + /** Set number of cores/threads to be used */ + IH264D_CMD_CTL_SET_NUM_CORES = IVD_CMD_CTL_CODEC_SUBCMD_START, + + /** Set processor details */ + IH264D_CMD_CTL_SET_PROCESSOR = IVD_CMD_CTL_CODEC_SUBCMD_START + 0x001, + + /** Get display buffer dimensions */ + IH264D_CMD_CTL_GET_BUFFER_DIMENSIONS = IVD_CMD_CTL_CODEC_SUBCMD_START + 0x100, + + /** Get VUI parameters */ + IH264D_CMD_CTL_GET_VUI_PARAMS = IVD_CMD_CTL_CODEC_SUBCMD_START + 0x101, + + /** Enable/disable GPU, supported on select platforms */ + IH264D_CMD_CTL_GPU_ENABLE_DISABLE = IVD_CMD_CTL_CODEC_SUBCMD_START + 0x200, + + /** Set degrade level */ + IH264D_CMD_CTL_DEGRADE = IVD_CMD_CTL_CODEC_SUBCMD_START + 0x300, + + /** Get SEI MDCV parameters */ + IH264D_CMD_CTL_GET_SEI_MDCV_PARAMS = IVD_CMD_CTL_CODEC_SUBCMD_START + 0x301, + + /** Get SEI CLL parameters */ + IH264D_CMD_CTL_GET_SEI_CLL_PARAMS = IVD_CMD_CTL_CODEC_SUBCMD_START + 0x302, + + /** Get SEI AVE parameters */ + IH264D_CMD_CTL_GET_SEI_AVE_PARAMS = IVD_CMD_CTL_CODEC_SUBCMD_START + 0x303, + + /** Get SEI CCV parameters */ + IH264D_CMD_CTL_GET_SEI_CCV_PARAMS = IVD_CMD_CTL_CODEC_SUBCMD_START + 0x304 + +}IH264D_CMD_CTL_SUB_CMDS; +/*****************************************************************************/ +/* Video control Flush */ +/*****************************************************************************/ + + +typedef struct{ + ivd_ctl_flush_ip_t s_ivd_ctl_flush_ip_t; +}ih264d_ctl_flush_ip_t; + + +typedef struct{ + ivd_ctl_flush_op_t s_ivd_ctl_flush_op_t; +}ih264d_ctl_flush_op_t; + +/*****************************************************************************/ +/* Video control reset */ +/*****************************************************************************/ + + +typedef struct{ + ivd_ctl_reset_ip_t s_ivd_ctl_reset_ip_t; +}ih264d_ctl_reset_ip_t; + + +typedef struct{ + ivd_ctl_reset_op_t s_ivd_ctl_reset_op_t; +}ih264d_ctl_reset_op_t; + + +/*****************************************************************************/ +/* Video control Set Params */ +/*****************************************************************************/ + + +typedef struct { + ivd_ctl_set_config_ip_t s_ivd_ctl_set_config_ip_t; +}ih264d_ctl_set_config_ip_t; + + +typedef struct{ + ivd_ctl_set_config_op_t s_ivd_ctl_set_config_op_t; +}ih264d_ctl_set_config_op_t; + +/*****************************************************************************/ +/* Video control:Get Buf Info */ +/*****************************************************************************/ + + +typedef struct{ + ivd_ctl_getbufinfo_ip_t s_ivd_ctl_getbufinfo_ip_t; +}ih264d_ctl_getbufinfo_ip_t; + + + +typedef struct{ + ivd_ctl_getbufinfo_op_t s_ivd_ctl_getbufinfo_op_t; +}ih264d_ctl_getbufinfo_op_t; + + +/*****************************************************************************/ +/* Video control:Getstatus Call */ +/*****************************************************************************/ + + +typedef struct{ + ivd_ctl_getstatus_ip_t s_ivd_ctl_getstatus_ip_t; +}ih264d_ctl_getstatus_ip_t; + + + +typedef struct{ + ivd_ctl_getstatus_op_t s_ivd_ctl_getstatus_op_t; +}ih264d_ctl_getstatus_op_t; + + +/*****************************************************************************/ +/* Video control:Get Version Info */ +/*****************************************************************************/ + + +typedef struct{ + ivd_ctl_getversioninfo_ip_t s_ivd_ctl_getversioninfo_ip_t; +}ih264d_ctl_getversioninfo_ip_t; + + + +typedef struct{ + ivd_ctl_getversioninfo_op_t s_ivd_ctl_getversioninfo_op_t; +}ih264d_ctl_getversioninfo_op_t; + +typedef struct{ + + /** + * u4_size + */ + UWORD32 u4_size; + + /** + * cmd + */ + IVD_API_COMMAND_TYPE_T e_cmd; + + /** + * sub_cmd + */ + IVD_CONTROL_API_COMMAND_TYPE_T e_sub_cmd; + + /** + * Pictures that are are degraded + * 0 : No degrade + * 1 : Only on non-reference frames + * 2 : Use interval specified by u4_nondegrade_interval + * 3 : All non-key frames + * 4 : All frames + */ + WORD32 i4_degrade_pics; + + /** + * Interval for pictures which are completely decoded without any degradation + */ + WORD32 i4_nondegrade_interval; + + /** + * bit position (lsb is zero): Type of degradation + * 1 : Disable deblocking + * 2 : Faster inter prediction filters + * 3 : Fastest inter prediction filters + */ + WORD32 i4_degrade_type; + +}ih264d_ctl_degrade_ip_t; + +typedef struct +{ + /** + * u4_size + */ + UWORD32 u4_size; + + /** + * error_code + */ + UWORD32 u4_error_code; +}ih264d_ctl_degrade_op_t; + +typedef struct{ + UWORD32 u4_size; + IVD_API_COMMAND_TYPE_T e_cmd; + IVD_CONTROL_API_COMMAND_TYPE_T e_sub_cmd; + UWORD32 u4_disable_deblk_level; +}ih264d_ctl_disable_deblock_ip_t; + +typedef struct{ + UWORD32 u4_size; + UWORD32 u4_error_code; +}ih264d_ctl_disable_deblock_op_t; + + +typedef struct{ + UWORD32 u4_size; + IVD_API_COMMAND_TYPE_T e_cmd; + IVD_CONTROL_API_COMMAND_TYPE_T e_sub_cmd; + UWORD32 u4_num_cores; +}ih264d_ctl_set_num_cores_ip_t; + +typedef struct{ + UWORD32 u4_size; + UWORD32 u4_error_code; +}ih264d_ctl_set_num_cores_op_t; + +typedef struct +{ + /** + * i4_size + */ + UWORD32 u4_size; + /** + * cmd + */ + IVD_API_COMMAND_TYPE_T e_cmd; + /** + * sub cmd + */ + IVD_CONTROL_API_COMMAND_TYPE_T e_sub_cmd; + /** + * Processor type + */ + UWORD32 u4_arch; + /** + * SOC type + */ + UWORD32 u4_soc; + + /** + * num_cores + */ + UWORD32 u4_num_cores; + +}ih264d_ctl_set_processor_ip_t; + +typedef struct +{ + /** + * i4_size + */ + UWORD32 u4_size; + /** + * error_code + */ + UWORD32 u4_error_code; +}ih264d_ctl_set_processor_op_t; + +typedef struct{ + UWORD32 u4_size; + IVD_API_COMMAND_TYPE_T e_cmd; + IVD_CONTROL_API_COMMAND_TYPE_T e_sub_cmd; +}ih264d_ctl_get_frame_dimensions_ip_t; + + +typedef struct{ + UWORD32 u4_size; + UWORD32 u4_error_code; + UWORD32 u4_x_offset[3]; + UWORD32 u4_y_offset[3]; + UWORD32 u4_disp_wd[3]; + UWORD32 u4_disp_ht[3]; + UWORD32 u4_buffer_wd[3]; + UWORD32 u4_buffer_ht[3]; +}ih264d_ctl_get_frame_dimensions_op_t; + +typedef struct +{ + UWORD32 u4_size; + IVD_API_COMMAND_TYPE_T e_cmd; + IVD_CONTROL_API_COMMAND_TYPE_T e_sub_cmd; +}ih264d_ctl_get_vui_params_ip_t; + +typedef struct +{ + UWORD32 u4_size; + UWORD32 u4_error_code; + UWORD8 u1_aspect_ratio_idc; + UWORD16 u2_sar_width; + UWORD16 u2_sar_height; + UWORD8 u1_overscan_appropriate_flag; + UWORD8 u1_video_format; + UWORD8 u1_video_full_range_flag; + UWORD8 u1_colour_primaries; + UWORD8 u1_tfr_chars; + UWORD8 u1_matrix_coeffs; + UWORD8 u1_cr_top_field; + UWORD8 u1_cr_bottom_field; + UWORD32 u4_num_units_in_tick; + UWORD32 u4_time_scale; + UWORD8 u1_fixed_frame_rate_flag; + UWORD8 u1_nal_hrd_params_present; + UWORD8 u1_vcl_hrd_params_present; + UWORD8 u1_low_delay_hrd_flag; + UWORD8 u1_pic_struct_present_flag; + UWORD8 u1_bitstream_restriction_flag; + UWORD8 u1_mv_over_pic_boundaries_flag; + UWORD32 u4_max_bytes_per_pic_denom; + UWORD32 u4_max_bits_per_mb_denom; + UWORD32 u4_log2_max_mv_length_horz; + UWORD32 u4_log2_max_mv_length_vert; + UWORD32 u4_num_reorder_frames; + UWORD32 u4_max_dec_frame_buffering; +}ih264d_ctl_get_vui_params_op_t; + + +typedef struct +{ + /** + * u4_size + */ + UWORD32 u4_size; + + /** + * cmd + */ + IVD_API_COMMAND_TYPE_T e_cmd; + + /** + * sub_cmd + */ + IVD_CONTROL_API_COMMAND_TYPE_T e_sub_cmd; +}ih264d_ctl_get_sei_mdcv_params_ip_t; + +typedef struct +{ + /** + * u4_size + */ + UWORD32 u4_size; + + /** + * error_code + */ + UWORD32 u4_error_code; + + /** + * Array to store the display_primaries_x values + */ + UWORD16 au2_display_primaries_x[NUM_SEI_MDCV_PRIMARIES]; + + /** + * Array to store the display_primaries_y values + */ + UWORD16 au2_display_primaries_y[NUM_SEI_MDCV_PRIMARIES]; + + /** + * Variable to store the white point x value + */ + UWORD16 u2_white_point_x; + + /** + * Variable to store the white point y value + */ + UWORD16 u2_white_point_y; + + /** + * Variable to store the max display mastering luminance value + */ + UWORD32 u4_max_display_mastering_luminance; + + /** + * Variable to store the min display mastering luminance value + */ + UWORD32 u4_min_display_mastering_luminance; +}ih264d_ctl_get_sei_mdcv_params_op_t; + +typedef struct +{ + /** + * u4_size + */ + UWORD32 u4_size; + + /** + * cmd + */ + IVD_API_COMMAND_TYPE_T e_cmd; + + /** + * sub_cmd + */ + IVD_CONTROL_API_COMMAND_TYPE_T e_sub_cmd; +}ih264d_ctl_get_sei_cll_params_ip_t; + +typedef struct +{ + /** + * u4_size + */ + UWORD32 u4_size; + + /** + * error_code + */ + UWORD32 u4_error_code; + + /** + * The maximum pixel intensity of all samples + */ + UWORD16 u2_max_content_light_level; + + /** + * The average pixel intensity of all samples + */ + UWORD16 u2_max_pic_average_light_level; +} ih264d_ctl_get_sei_cll_params_op_t; + +typedef struct +{ + /** + * u4_size + */ + UWORD32 u4_size; + + /** + * cmd + */ + IVD_API_COMMAND_TYPE_T e_cmd; + + /** + * sub_cmd + */ + IVD_CONTROL_API_COMMAND_TYPE_T e_sub_cmd; +}ih264d_ctl_get_sei_ave_params_ip_t; + +typedef struct +{ + /** + * u4_size + */ + UWORD32 u4_size; + + /** + * error_code + */ + UWORD32 u4_error_code; + + /** + * specifies the environmental illluminance of the ambient viewing environment + */ + UWORD32 u4_ambient_illuminance; + + /* + * specify the normalized x chromaticity coordinates of the + * environmental ambient light in the nominal viewing environment + */ + UWORD16 u2_ambient_light_x; + + /* + * specify the normalized y chromaticity coordinates of the + * environmental ambient light in the nominal viewing environment + */ + UWORD16 u2_ambient_light_y; +} ih264d_ctl_get_sei_ave_params_op_t; + +typedef struct +{ + /** + * u4_size + */ + UWORD32 u4_size; + + /** + * cmd + */ + IVD_API_COMMAND_TYPE_T e_cmd; + + /** + * sub_cmd + */ + IVD_CONTROL_API_COMMAND_TYPE_T e_sub_cmd; +}ih264d_ctl_get_sei_ccv_params_ip_t; + +typedef struct +{ + /** + * u4_size + */ + UWORD32 u4_size; + + /** + * error_code + */ + UWORD32 u4_error_code; + + /* + * Flag used to control persistence of CCV SEI messages + */ + UWORD8 u1_ccv_cancel_flag; + + /* + * specifies the persistence of the CCV SEI message for the current layer + */ + UWORD8 u1_ccv_persistence_flag; + + /* + * specifies the presence of syntax elements ccv_primaries_x and ccv_primaries_y + */ + UWORD8 u1_ccv_primaries_present_flag; + + /* + * specifies that the syntax element ccv_min_luminance_value is present + */ + UWORD8 u1_ccv_min_luminance_value_present_flag; + + /* + * specifies that the syntax element ccv_max_luminance_value is present + */ + UWORD8 u1_ccv_max_luminance_value_present_flag; + + /* + * specifies that the syntax element ccv_avg_luminance_value is present + */ + UWORD8 u1_ccv_avg_luminance_value_present_flag; + + /* + * shall be equal to 0 in bitstreams conforming to this version. Other values + * for reserved_zero_2bits are reserved for future use + */ + UWORD8 u1_ccv_reserved_zero_2bits; + + /* + * specify the normalized x chromaticity coordinates of the colour + * primary component c of the nominal content colour volume + */ + WORD32 ai4_ccv_primaries_x[NUM_SEI_CCV_PRIMARIES]; + + /* + * specify the normalized y chromaticity coordinates of the colour + * primary component c of the nominal content colour volume + */ + WORD32 ai4_ccv_primaries_y[NUM_SEI_CCV_PRIMARIES]; + + /* + * specifies the normalized minimum luminance value + */ + UWORD32 u4_ccv_min_luminance_value; + + /* + * specifies the normalized maximum luminance value + */ + UWORD32 u4_ccv_max_luminance_value; + + /* + * specifies the normalized average luminance value + */ + UWORD32 u4_ccv_avg_luminance_value; +} ih264d_ctl_get_sei_ccv_params_op_t; + + +#ifdef __cplusplus +} /* closing brace for extern "C" */ +#endif +#endif /* _IH264D_H_ */ diff --git a/dependencies/ih264d/decoder/ih264d_api.c b/dependencies/ih264d/decoder/ih264d_api.c new file mode 100644 index 00000000..69dfe6bd --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_api.c @@ -0,0 +1,4225 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore + * Modified for use with Cemu emulator project + */ + +/*****************************************************************************/ +/* */ +/* File Name : ih264d_api.c */ +/* */ +/* Description : Has all API related functions */ +/* */ +/* */ +/* List of Functions : api_check_struct_sanity */ +/* ih264d_set_processor */ +/* ih264d_create */ +/* ih264d_delete */ +/* ih264d_init */ +/* ih264d_map_error */ +/* ih264d_video_decode */ +/* ih264d_get_version */ +/* ih264d_get_display_frame */ +/* ih264d_set_display_frame */ +/* ih264d_set_flush_mode */ +/* ih264d_get_status */ +/* ih264d_get_buf_info */ +/* ih264d_set_params */ +/* ih264d_set_default_params */ +/* ih264d_reset */ +/* ih264d_ctl */ +/* ih264d_rel_display_frame */ +/* ih264d_set_degrade */ +/* ih264d_get_frame_dimensions */ +/* ih264d_set_num_cores */ +/* ih264d_fill_output_struct_from_context */ +/* ih264d_api_function */ +/* */ +/* Issues / Problems : None */ +/* */ +/* Revision History : */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 14 10 2008 100356(SKV) Draft */ +/* */ +/*****************************************************************************/ +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264d_tables.h" +#include "iv.h" +#include "ivd.h" +#include "ih264d.h" +#include "ih264d_defs.h" + +#include <string.h> +#include <limits.h> +#include <stddef.h> + +#include "ih264d_inter_pred.h" + +#include "ih264d_structs.h" +#include "ih264d_nal.h" +#include "ih264d_error_handler.h" + +#include "ih264d_defs.h" + +#include "ithread.h" +#include "ih264d_parse_slice.h" +#include "ih264d_function_selector.h" +#include "ih264_error.h" +#include "ih264_disp_mgr.h" +#include "ih264_buf_mgr.h" +#include "ih264d_deblocking.h" +#include "ih264d_parse_cavlc.h" +#include "ih264d_parse_cabac.h" +#include "ih264d_utils.h" +#include "ih264d_format_conv.h" +#include "ih264d_parse_headers.h" +#include "ih264d_thread_compute_bs.h" +#include <assert.h> + + +/*********************/ +/* Codec Versioning */ +/*********************/ +//Move this to where it is used +#define CODEC_NAME "H264VDEC" +#define CODEC_RELEASE_TYPE "production" +#define CODEC_RELEASE_VER "05.00" +#define CODEC_VENDOR "ITTIAM" +#define MAXVERSION_STRLEN 511 +#ifdef ANDROID +#define VERSION(version_string, codec_name, codec_release_type, codec_release_ver, codec_vendor) \ + snprintf(version_string, MAXVERSION_STRLEN, \ + "@(#)Id:%s_%s Ver:%s Released by %s", \ + codec_name, codec_release_type, codec_release_ver, codec_vendor) +#else +#define VERSION(version_string, codec_name, codec_release_type, codec_release_ver, codec_vendor) \ + snprintf(version_string, MAXVERSION_STRLEN, \ + "@(#)Id:%s_%s Ver:%s Released by %s Build: %s @ %s", \ + codec_name, codec_release_type, codec_release_ver, codec_vendor, __DATE__, __TIME__) +#endif + + +#define MIN_IN_BUFS 1 +#define MIN_OUT_BUFS_420 3 +#define MIN_OUT_BUFS_422ILE 1 +#define MIN_OUT_BUFS_RGB565 1 +#define MIN_OUT_BUFS_420SP 2 + +#define NUM_FRAMES_LIMIT_ENABLED 0 + +#if NUM_FRAMES_LIMIT_ENABLED +#define NUM_FRAMES_LIMIT 10000 +#else +#define NUM_FRAMES_LIMIT 0x7FFFFFFF +#endif + + +UWORD32 ih264d_get_extra_mem_external(UWORD32 width, UWORD32 height); +WORD32 ih264d_get_frame_dimensions(iv_obj_t *dec_hdl, + void *pv_api_ip, + void *pv_api_op); +WORD32 ih264d_get_vui_params(iv_obj_t *dec_hdl, + void *pv_api_ip, + void *pv_api_op); + +WORD32 ih264d_get_sei_mdcv_params(iv_obj_t *dec_hdl, + void *pv_api_ip, + void *pv_api_op); + +WORD32 ih264d_get_sei_cll_params(iv_obj_t *dec_hdl, + void *pv_api_ip, + void *pv_api_op); + +WORD32 ih264d_get_sei_ave_params(iv_obj_t *dec_hdl, + void *pv_api_ip, + void *pv_api_op); + +WORD32 ih264d_get_sei_ccv_params(iv_obj_t *dec_hdl, + void *pv_api_ip, + void *pv_api_op); + +WORD32 ih264d_set_num_cores(iv_obj_t *dec_hdl, void *pv_api_ip, void *pv_api_op); + +WORD32 ih264d_deblock_display(dec_struct_t *ps_dec); + +void ih264d_signal_decode_thread(dec_struct_t *ps_dec); + +void ih264d_signal_bs_deblk_thread(dec_struct_t *ps_dec); +void ih264d_decode_picture_thread(dec_struct_t *ps_dec); + +WORD32 ih264d_set_degrade(iv_obj_t *ps_codec_obj, + void *pv_api_ip, + void *pv_api_op); + +void ih264d_fill_output_struct_from_context(dec_struct_t *ps_dec, + ivd_video_decode_op_t *ps_dec_op); + +/*! + ************************************************************************** + * \if Function name : ih264d_export_sei_params \endif + * + * \brief + * Exports sei params from decoder to application. + * + * \return + * 0 on Success and error code otherwise + ************************************************************************** + */ + +void ih264d_export_sei_params(ivd_sei_decode_op_t *ps_sei_decode_op, dec_struct_t *ps_dec) +{ + WORD32 i4_status = IV_SUCCESS; + sei *ps_sei = (sei *)ps_dec->pv_disp_sei_params; + + i4_status = ih264d_export_sei_mdcv_params(ps_sei_decode_op, ps_sei, &ps_dec->s_sei_export); + i4_status = ih264d_export_sei_cll_params(ps_sei_decode_op, ps_sei, &ps_dec->s_sei_export); + i4_status = ih264d_export_sei_ave_params(ps_sei_decode_op, ps_sei, &ps_dec->s_sei_export); + i4_status = ih264d_export_sei_ccv_params(ps_sei_decode_op, ps_sei, &ps_dec->s_sei_export); + + UNUSED(i4_status); +} + +static IV_API_CALL_STATUS_T api_check_struct_sanity(iv_obj_t *ps_handle, + void *pv_api_ip, + void *pv_api_op) +{ + IVD_API_COMMAND_TYPE_T e_cmd; + UWORD32 *pu4_api_ip; + UWORD32 *pu4_api_op; + UWORD32 i, j; + + if(NULL == pv_api_op) + return (IV_FAIL); + + if(NULL == pv_api_ip) + return (IV_FAIL); + + pu4_api_ip = (UWORD32 *)pv_api_ip; + pu4_api_op = (UWORD32 *)pv_api_op; + e_cmd = *(pu4_api_ip + 1); + + /* error checks on handle */ + switch((WORD32)e_cmd) + { + case IVD_CMD_CREATE: + break; + + case IVD_CMD_REL_DISPLAY_FRAME: + case IVD_CMD_SET_DISPLAY_FRAME: + case IVD_CMD_GET_DISPLAY_FRAME: + case IVD_CMD_VIDEO_DECODE: + case IVD_CMD_DELETE: + case IVD_CMD_VIDEO_CTL: + if(ps_handle == NULL) + { + *(pu4_api_op + 1) |= 1 << IVD_UNSUPPORTEDPARAM; + *(pu4_api_op + 1) |= IVD_HANDLE_NULL; + return IV_FAIL; + } + + if(ps_handle->u4_size != sizeof(iv_obj_t)) + { + *(pu4_api_op + 1) |= 1 << IVD_UNSUPPORTEDPARAM; + *(pu4_api_op + 1) |= IVD_HANDLE_STRUCT_SIZE_INCORRECT; + return IV_FAIL; + } + + if(ps_handle->pv_fxns != ih264d_api_function) + { + *(pu4_api_op + 1) |= 1 << IVD_UNSUPPORTEDPARAM; + *(pu4_api_op + 1) |= IVD_INVALID_HANDLE_NULL; + return IV_FAIL; + } + + if(ps_handle->pv_codec_handle == NULL) + { + *(pu4_api_op + 1) |= 1 << IVD_UNSUPPORTEDPARAM; + *(pu4_api_op + 1) |= IVD_INVALID_HANDLE_NULL; + return IV_FAIL; + } + break; + default: + *(pu4_api_op + 1) |= 1 << IVD_UNSUPPORTEDPARAM; + *(pu4_api_op + 1) |= IVD_INVALID_API_CMD; + return IV_FAIL; + } + + switch((WORD32)e_cmd) + { + case IVD_CMD_CREATE: + { + ih264d_create_ip_t *ps_ip = (ih264d_create_ip_t *)pv_api_ip; + ih264d_create_op_t *ps_op = (ih264d_create_op_t *)pv_api_op; + + + ps_op->s_ivd_create_op_t.u4_error_code = 0; + + if((ps_ip->s_ivd_create_ip_t.u4_size > sizeof(ih264d_create_ip_t)) + || (ps_ip->s_ivd_create_ip_t.u4_size + < sizeof(ivd_create_ip_t))) + { + ps_op->s_ivd_create_op_t.u4_error_code |= 1 + << IVD_UNSUPPORTEDPARAM; + ps_op->s_ivd_create_op_t.u4_error_code |= + IVD_IP_API_STRUCT_SIZE_INCORRECT; + H264_DEC_DEBUG_PRINT("\n"); + return (IV_FAIL); + } + + if((ps_op->s_ivd_create_op_t.u4_size != sizeof(ih264d_create_op_t)) + && (ps_op->s_ivd_create_op_t.u4_size + != sizeof(ivd_create_op_t))) + { + ps_op->s_ivd_create_op_t.u4_error_code |= 1 + << IVD_UNSUPPORTEDPARAM; + ps_op->s_ivd_create_op_t.u4_error_code |= + IVD_OP_API_STRUCT_SIZE_INCORRECT; + H264_DEC_DEBUG_PRINT("\n"); + return (IV_FAIL); + } + + + if((ps_ip->s_ivd_create_ip_t.e_output_format != IV_YUV_420P) + && (ps_ip->s_ivd_create_ip_t.e_output_format + != IV_YUV_422ILE) + && (ps_ip->s_ivd_create_ip_t.e_output_format + != IV_RGB_565) + && (ps_ip->s_ivd_create_ip_t.e_output_format + != IV_YUV_420SP_UV) + && (ps_ip->s_ivd_create_ip_t.e_output_format + != IV_YUV_420SP_VU)) + { + ps_op->s_ivd_create_op_t.u4_error_code |= 1 + << IVD_UNSUPPORTEDPARAM; + ps_op->s_ivd_create_op_t.u4_error_code |= + IVD_INIT_DEC_COL_FMT_NOT_SUPPORTED; + H264_DEC_DEBUG_PRINT("\n"); + return (IV_FAIL); + } + + } + break; + + case IVD_CMD_GET_DISPLAY_FRAME: + { + ih264d_get_display_frame_ip_t *ps_ip = + (ih264d_get_display_frame_ip_t *)pv_api_ip; + ih264d_get_display_frame_op_t *ps_op = + (ih264d_get_display_frame_op_t *)pv_api_op; + + ps_op->s_ivd_get_display_frame_op_t.u4_error_code = 0; + + if((ps_ip->s_ivd_get_display_frame_ip_t.u4_size + != sizeof(ih264d_get_display_frame_ip_t)) + && (ps_ip->s_ivd_get_display_frame_ip_t.u4_size + != sizeof(ivd_get_display_frame_ip_t))) + { + ps_op->s_ivd_get_display_frame_op_t.u4_error_code |= 1 + << IVD_UNSUPPORTEDPARAM; + ps_op->s_ivd_get_display_frame_op_t.u4_error_code |= + IVD_IP_API_STRUCT_SIZE_INCORRECT; + return (IV_FAIL); + } + + if((ps_op->s_ivd_get_display_frame_op_t.u4_size + != sizeof(ih264d_get_display_frame_op_t)) + && (ps_op->s_ivd_get_display_frame_op_t.u4_size + != sizeof(ivd_get_display_frame_op_t))) + { + ps_op->s_ivd_get_display_frame_op_t.u4_error_code |= 1 + << IVD_UNSUPPORTEDPARAM; + ps_op->s_ivd_get_display_frame_op_t.u4_error_code |= + IVD_OP_API_STRUCT_SIZE_INCORRECT; + return (IV_FAIL); + } + } + break; + + case IVD_CMD_REL_DISPLAY_FRAME: + { + ih264d_rel_display_frame_ip_t *ps_ip = + (ih264d_rel_display_frame_ip_t *)pv_api_ip; + ih264d_rel_display_frame_op_t *ps_op = + (ih264d_rel_display_frame_op_t *)pv_api_op; + + ps_op->s_ivd_rel_display_frame_op_t.u4_error_code = 0; + + if((ps_ip->s_ivd_rel_display_frame_ip_t.u4_size + != sizeof(ih264d_rel_display_frame_ip_t)) + && (ps_ip->s_ivd_rel_display_frame_ip_t.u4_size + != sizeof(ivd_rel_display_frame_ip_t))) + { + ps_op->s_ivd_rel_display_frame_op_t.u4_error_code |= 1 + << IVD_UNSUPPORTEDPARAM; + ps_op->s_ivd_rel_display_frame_op_t.u4_error_code |= + IVD_IP_API_STRUCT_SIZE_INCORRECT; + return (IV_FAIL); + } + + if((ps_op->s_ivd_rel_display_frame_op_t.u4_size + != sizeof(ih264d_rel_display_frame_op_t)) + && (ps_op->s_ivd_rel_display_frame_op_t.u4_size + != sizeof(ivd_rel_display_frame_op_t))) + { + ps_op->s_ivd_rel_display_frame_op_t.u4_error_code |= 1 + << IVD_UNSUPPORTEDPARAM; + ps_op->s_ivd_rel_display_frame_op_t.u4_error_code |= + IVD_OP_API_STRUCT_SIZE_INCORRECT; + return (IV_FAIL); + } + + } + break; + + case IVD_CMD_SET_DISPLAY_FRAME: + { + ih264d_set_display_frame_ip_t *ps_ip = + (ih264d_set_display_frame_ip_t *)pv_api_ip; + ih264d_set_display_frame_op_t *ps_op = + (ih264d_set_display_frame_op_t *)pv_api_op; + UWORD32 j; + + ps_op->s_ivd_set_display_frame_op_t.u4_error_code = 0; + + if((ps_ip->s_ivd_set_display_frame_ip_t.u4_size + != sizeof(ih264d_set_display_frame_ip_t)) + && (ps_ip->s_ivd_set_display_frame_ip_t.u4_size + != sizeof(ivd_set_display_frame_ip_t))) + { + ps_op->s_ivd_set_display_frame_op_t.u4_error_code |= 1 + << IVD_UNSUPPORTEDPARAM; + ps_op->s_ivd_set_display_frame_op_t.u4_error_code |= + IVD_IP_API_STRUCT_SIZE_INCORRECT; + return (IV_FAIL); + } + + if((ps_op->s_ivd_set_display_frame_op_t.u4_size + != sizeof(ih264d_set_display_frame_op_t)) + && (ps_op->s_ivd_set_display_frame_op_t.u4_size + != sizeof(ivd_set_display_frame_op_t))) + { + ps_op->s_ivd_set_display_frame_op_t.u4_error_code |= 1 + << IVD_UNSUPPORTEDPARAM; + ps_op->s_ivd_set_display_frame_op_t.u4_error_code |= + IVD_OP_API_STRUCT_SIZE_INCORRECT; + return (IV_FAIL); + } + + if(ps_ip->s_ivd_set_display_frame_ip_t.num_disp_bufs == 0) + { + ps_op->s_ivd_set_display_frame_op_t.u4_error_code |= 1 + << IVD_UNSUPPORTEDPARAM; + ps_op->s_ivd_set_display_frame_op_t.u4_error_code |= + IVD_DISP_FRM_ZERO_OP_BUFS; + return IV_FAIL; + } + + for(j = 0; j < ps_ip->s_ivd_set_display_frame_ip_t.num_disp_bufs; + j++) + { + if(ps_ip->s_ivd_set_display_frame_ip_t.s_disp_buffer[j].u4_num_bufs + == 0) + { + ps_op->s_ivd_set_display_frame_op_t.u4_error_code |= 1 + << IVD_UNSUPPORTEDPARAM; + ps_op->s_ivd_set_display_frame_op_t.u4_error_code |= + IVD_DISP_FRM_ZERO_OP_BUFS; + return IV_FAIL; + } + + for(i = 0; + i + < ps_ip->s_ivd_set_display_frame_ip_t.s_disp_buffer[j].u4_num_bufs; + i++) + { + if(ps_ip->s_ivd_set_display_frame_ip_t.s_disp_buffer[j].pu1_bufs[i] + == NULL) + { + ps_op->s_ivd_set_display_frame_op_t.u4_error_code |= 1 + << IVD_UNSUPPORTEDPARAM; + ps_op->s_ivd_set_display_frame_op_t.u4_error_code |= + IVD_DISP_FRM_OP_BUF_NULL; + return IV_FAIL; + } + + if(ps_ip->s_ivd_set_display_frame_ip_t.s_disp_buffer[j].u4_min_out_buf_size[i] + == 0) + { + ps_op->s_ivd_set_display_frame_op_t.u4_error_code |= 1 + << IVD_UNSUPPORTEDPARAM; + ps_op->s_ivd_set_display_frame_op_t.u4_error_code |= + IVD_DISP_FRM_ZERO_OP_BUF_SIZE; + return IV_FAIL; + } + } + } + } + break; + + case IVD_CMD_VIDEO_DECODE: + { + ih264d_video_decode_ip_t *ps_ip = + (ih264d_video_decode_ip_t *)pv_api_ip; + ih264d_video_decode_op_t *ps_op = + (ih264d_video_decode_op_t *)pv_api_op; + + H264_DEC_DEBUG_PRINT("The input bytes is: %d", + ps_ip->s_ivd_video_decode_ip_t.u4_num_Bytes); + ps_op->s_ivd_video_decode_op_t.u4_error_code = 0; + + if(ps_ip->s_ivd_video_decode_ip_t.u4_size + != sizeof(ih264d_video_decode_ip_t)&& + ps_ip->s_ivd_video_decode_ip_t.u4_size != offsetof(ivd_video_decode_ip_t, s_out_buffer)) + { + ps_op->s_ivd_video_decode_op_t.u4_error_code |= 1 + << IVD_UNSUPPORTEDPARAM; + ps_op->s_ivd_video_decode_op_t.u4_error_code |= + IVD_IP_API_STRUCT_SIZE_INCORRECT; + return (IV_FAIL); + } + + if(ps_op->s_ivd_video_decode_op_t.u4_size + != sizeof(ih264d_video_decode_op_t)&& + ps_op->s_ivd_video_decode_op_t.u4_size != offsetof(ivd_video_decode_op_t, u4_output_present)) + { + ps_op->s_ivd_video_decode_op_t.u4_error_code |= 1 + << IVD_UNSUPPORTEDPARAM; + ps_op->s_ivd_video_decode_op_t.u4_error_code |= + IVD_OP_API_STRUCT_SIZE_INCORRECT; + return (IV_FAIL); + } + + } + break; + + case IVD_CMD_DELETE: + { + ih264d_delete_ip_t *ps_ip = + (ih264d_delete_ip_t *)pv_api_ip; + ih264d_delete_op_t *ps_op = + (ih264d_delete_op_t *)pv_api_op; + + ps_op->s_ivd_delete_op_t.u4_error_code = 0; + + if(ps_ip->s_ivd_delete_ip_t.u4_size + != sizeof(ih264d_delete_ip_t)) + { + ps_op->s_ivd_delete_op_t.u4_error_code |= 1 + << IVD_UNSUPPORTEDPARAM; + ps_op->s_ivd_delete_op_t.u4_error_code |= + IVD_IP_API_STRUCT_SIZE_INCORRECT; + return (IV_FAIL); + } + + if(ps_op->s_ivd_delete_op_t.u4_size + != sizeof(ih264d_delete_op_t)) + { + ps_op->s_ivd_delete_op_t.u4_error_code |= 1 + << IVD_UNSUPPORTEDPARAM; + ps_op->s_ivd_delete_op_t.u4_error_code |= + IVD_OP_API_STRUCT_SIZE_INCORRECT; + return (IV_FAIL); + } + + } + break; + + case IVD_CMD_VIDEO_CTL: + { + UWORD32 *pu4_ptr_cmd; + UWORD32 sub_command; + + pu4_ptr_cmd = (UWORD32 *)pv_api_ip; + pu4_ptr_cmd += 2; + sub_command = *pu4_ptr_cmd; + + switch(sub_command) + { + case IVD_CMD_CTL_SETPARAMS: + { + ih264d_ctl_set_config_ip_t *ps_ip; + ih264d_ctl_set_config_op_t *ps_op; + ps_ip = (ih264d_ctl_set_config_ip_t *)pv_api_ip; + ps_op = (ih264d_ctl_set_config_op_t *)pv_api_op; + + if(ps_ip->s_ivd_ctl_set_config_ip_t.u4_size + != sizeof(ih264d_ctl_set_config_ip_t)) + { + ps_op->s_ivd_ctl_set_config_op_t.u4_error_code |= 1 + << IVD_UNSUPPORTEDPARAM; + ps_op->s_ivd_ctl_set_config_op_t.u4_error_code |= + IVD_IP_API_STRUCT_SIZE_INCORRECT; + return IV_FAIL; + } + } + //no break; is needed here + case IVD_CMD_CTL_SETDEFAULT: + { + ih264d_ctl_set_config_op_t *ps_op; + ps_op = (ih264d_ctl_set_config_op_t *)pv_api_op; + if(ps_op->s_ivd_ctl_set_config_op_t.u4_size + != sizeof(ih264d_ctl_set_config_op_t)) + { + ps_op->s_ivd_ctl_set_config_op_t.u4_error_code |= 1 + << IVD_UNSUPPORTEDPARAM; + ps_op->s_ivd_ctl_set_config_op_t.u4_error_code |= + IVD_OP_API_STRUCT_SIZE_INCORRECT; + return IV_FAIL; + } + } + break; + + case IVD_CMD_CTL_GETPARAMS: + { + ih264d_ctl_getstatus_ip_t *ps_ip; + ih264d_ctl_getstatus_op_t *ps_op; + + ps_ip = (ih264d_ctl_getstatus_ip_t *)pv_api_ip; + ps_op = (ih264d_ctl_getstatus_op_t *)pv_api_op; + if(ps_ip->s_ivd_ctl_getstatus_ip_t.u4_size + != sizeof(ih264d_ctl_getstatus_ip_t)) + { + ps_op->s_ivd_ctl_getstatus_op_t.u4_error_code |= 1 + << IVD_UNSUPPORTEDPARAM; + ps_op->s_ivd_ctl_getstatus_op_t.u4_error_code |= + IVD_IP_API_STRUCT_SIZE_INCORRECT; + return IV_FAIL; + } + if(ps_op->s_ivd_ctl_getstatus_op_t.u4_size + != sizeof(ih264d_ctl_getstatus_op_t)) + { + ps_op->s_ivd_ctl_getstatus_op_t.u4_error_code |= 1 + << IVD_UNSUPPORTEDPARAM; + ps_op->s_ivd_ctl_getstatus_op_t.u4_error_code |= + IVD_OP_API_STRUCT_SIZE_INCORRECT; + return IV_FAIL; + } + } + break; + + case IVD_CMD_CTL_GETBUFINFO: + { + ih264d_ctl_getbufinfo_ip_t *ps_ip; + ih264d_ctl_getbufinfo_op_t *ps_op; + ps_ip = (ih264d_ctl_getbufinfo_ip_t *)pv_api_ip; + ps_op = (ih264d_ctl_getbufinfo_op_t *)pv_api_op; + + if(ps_ip->s_ivd_ctl_getbufinfo_ip_t.u4_size + != sizeof(ih264d_ctl_getbufinfo_ip_t)) + { + ps_op->s_ivd_ctl_getbufinfo_op_t.u4_error_code |= 1 + << IVD_UNSUPPORTEDPARAM; + ps_op->s_ivd_ctl_getbufinfo_op_t.u4_error_code |= + IVD_IP_API_STRUCT_SIZE_INCORRECT; + return IV_FAIL; + } + if(ps_op->s_ivd_ctl_getbufinfo_op_t.u4_size + != sizeof(ih264d_ctl_getbufinfo_op_t)) + { + ps_op->s_ivd_ctl_getbufinfo_op_t.u4_error_code |= 1 + << IVD_UNSUPPORTEDPARAM; + ps_op->s_ivd_ctl_getbufinfo_op_t.u4_error_code |= + IVD_OP_API_STRUCT_SIZE_INCORRECT; + return IV_FAIL; + } + } + break; + + case IVD_CMD_CTL_GETVERSION: + { + ih264d_ctl_getversioninfo_ip_t *ps_ip; + ih264d_ctl_getversioninfo_op_t *ps_op; + ps_ip = (ih264d_ctl_getversioninfo_ip_t *)pv_api_ip; + ps_op = (ih264d_ctl_getversioninfo_op_t *)pv_api_op; + if(ps_ip->s_ivd_ctl_getversioninfo_ip_t.u4_size + != sizeof(ih264d_ctl_getversioninfo_ip_t)) + { + ps_op->s_ivd_ctl_getversioninfo_op_t.u4_error_code |= 1 + << IVD_UNSUPPORTEDPARAM; + ps_op->s_ivd_ctl_getversioninfo_op_t.u4_error_code |= + IVD_IP_API_STRUCT_SIZE_INCORRECT; + return IV_FAIL; + } + if(ps_op->s_ivd_ctl_getversioninfo_op_t.u4_size + != sizeof(ih264d_ctl_getversioninfo_op_t)) + { + ps_op->s_ivd_ctl_getversioninfo_op_t.u4_error_code |= 1 + << IVD_UNSUPPORTEDPARAM; + ps_op->s_ivd_ctl_getversioninfo_op_t.u4_error_code |= + IVD_OP_API_STRUCT_SIZE_INCORRECT; + return IV_FAIL; + } + } + break; + + case IVD_CMD_CTL_FLUSH: + { + ih264d_ctl_flush_ip_t *ps_ip; + ih264d_ctl_flush_op_t *ps_op; + ps_ip = (ih264d_ctl_flush_ip_t *)pv_api_ip; + ps_op = (ih264d_ctl_flush_op_t *)pv_api_op; + if(ps_ip->s_ivd_ctl_flush_ip_t.u4_size + != sizeof(ih264d_ctl_flush_ip_t)) + { + ps_op->s_ivd_ctl_flush_op_t.u4_error_code |= 1 + << IVD_UNSUPPORTEDPARAM; + ps_op->s_ivd_ctl_flush_op_t.u4_error_code |= + IVD_IP_API_STRUCT_SIZE_INCORRECT; + return IV_FAIL; + } + if(ps_op->s_ivd_ctl_flush_op_t.u4_size + != sizeof(ih264d_ctl_flush_op_t)) + { + ps_op->s_ivd_ctl_flush_op_t.u4_error_code |= 1 + << IVD_UNSUPPORTEDPARAM; + ps_op->s_ivd_ctl_flush_op_t.u4_error_code |= + IVD_OP_API_STRUCT_SIZE_INCORRECT; + return IV_FAIL; + } + } + break; + + case IVD_CMD_CTL_RESET: + { + ih264d_ctl_reset_ip_t *ps_ip; + ih264d_ctl_reset_op_t *ps_op; + ps_ip = (ih264d_ctl_reset_ip_t *)pv_api_ip; + ps_op = (ih264d_ctl_reset_op_t *)pv_api_op; + if(ps_ip->s_ivd_ctl_reset_ip_t.u4_size + != sizeof(ih264d_ctl_reset_ip_t)) + { + ps_op->s_ivd_ctl_reset_op_t.u4_error_code |= 1 + << IVD_UNSUPPORTEDPARAM; + ps_op->s_ivd_ctl_reset_op_t.u4_error_code |= + IVD_IP_API_STRUCT_SIZE_INCORRECT; + return IV_FAIL; + } + if(ps_op->s_ivd_ctl_reset_op_t.u4_size + != sizeof(ih264d_ctl_reset_op_t)) + { + ps_op->s_ivd_ctl_reset_op_t.u4_error_code |= 1 + << IVD_UNSUPPORTEDPARAM; + ps_op->s_ivd_ctl_reset_op_t.u4_error_code |= + IVD_OP_API_STRUCT_SIZE_INCORRECT; + return IV_FAIL; + } + } + break; + + case IH264D_CMD_CTL_DEGRADE: + { + ih264d_ctl_degrade_ip_t *ps_ip; + ih264d_ctl_degrade_op_t *ps_op; + + ps_ip = (ih264d_ctl_degrade_ip_t *)pv_api_ip; + ps_op = (ih264d_ctl_degrade_op_t *)pv_api_op; + + if(ps_ip->u4_size != sizeof(ih264d_ctl_degrade_ip_t)) + { + ps_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; + ps_op->u4_error_code |= + IVD_IP_API_STRUCT_SIZE_INCORRECT; + return IV_FAIL; + } + + if(ps_op->u4_size != sizeof(ih264d_ctl_degrade_op_t)) + { + ps_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; + ps_op->u4_error_code |= + IVD_OP_API_STRUCT_SIZE_INCORRECT; + return IV_FAIL; + } + + if((ps_ip->i4_degrade_pics < 0) + || (ps_ip->i4_degrade_pics > 4) + || (ps_ip->i4_nondegrade_interval < 0) + || (ps_ip->i4_degrade_type < 0) + || (ps_ip->i4_degrade_type > 15)) + { + ps_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; + return IV_FAIL; + } + + break; + } + + case IH264D_CMD_CTL_GET_BUFFER_DIMENSIONS: + { + ih264d_ctl_get_frame_dimensions_ip_t *ps_ip; + ih264d_ctl_get_frame_dimensions_op_t *ps_op; + + ps_ip = (ih264d_ctl_get_frame_dimensions_ip_t *)pv_api_ip; + ps_op = (ih264d_ctl_get_frame_dimensions_op_t *)pv_api_op; + + if(ps_ip->u4_size + != sizeof(ih264d_ctl_get_frame_dimensions_ip_t)) + { + ps_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; + ps_op->u4_error_code |= + IVD_IP_API_STRUCT_SIZE_INCORRECT; + return IV_FAIL; + } + + if(ps_op->u4_size + != sizeof(ih264d_ctl_get_frame_dimensions_op_t)) + { + ps_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; + ps_op->u4_error_code |= + IVD_OP_API_STRUCT_SIZE_INCORRECT; + return IV_FAIL; + } + + break; + } + case IH264D_CMD_CTL_GET_VUI_PARAMS: + { + ih264d_ctl_get_vui_params_ip_t *ps_ip; + ih264d_ctl_get_vui_params_op_t *ps_op; + + ps_ip = + (ih264d_ctl_get_vui_params_ip_t *)pv_api_ip; + ps_op = + (ih264d_ctl_get_vui_params_op_t *)pv_api_op; + + if(ps_ip->u4_size + != sizeof(ih264d_ctl_get_vui_params_ip_t)) + { + ps_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; + ps_op->u4_error_code |= + IVD_IP_API_STRUCT_SIZE_INCORRECT; + return IV_FAIL; + } + + if(ps_op->u4_size + != sizeof(ih264d_ctl_get_vui_params_op_t)) + { + ps_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; + ps_op->u4_error_code |= + IVD_OP_API_STRUCT_SIZE_INCORRECT; + return IV_FAIL; + } + + break; + } + case IH264D_CMD_CTL_GET_SEI_MDCV_PARAMS: + { + ih264d_ctl_get_sei_mdcv_params_ip_t *ps_ip; + ih264d_ctl_get_sei_mdcv_params_op_t *ps_op; + + ps_ip = (ih264d_ctl_get_sei_mdcv_params_ip_t *)pv_api_ip; + ps_op = (ih264d_ctl_get_sei_mdcv_params_op_t *)pv_api_op; + + if(ps_ip->u4_size != sizeof(ih264d_ctl_get_sei_mdcv_params_ip_t)) + { + ps_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; + ps_op->u4_error_code |= + IVD_IP_API_STRUCT_SIZE_INCORRECT; + return IV_FAIL; + } + + if(ps_op->u4_size != sizeof(ih264d_ctl_get_sei_mdcv_params_op_t)) + { + ps_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; + ps_op->u4_error_code |= + IVD_OP_API_STRUCT_SIZE_INCORRECT; + return IV_FAIL; + } + + break; + } + + case IH264D_CMD_CTL_GET_SEI_CLL_PARAMS: + { + ih264d_ctl_get_sei_cll_params_ip_t *ps_ip; + ih264d_ctl_get_sei_cll_params_op_t *ps_op; + + ps_ip = (ih264d_ctl_get_sei_cll_params_ip_t *)pv_api_ip; + ps_op = (ih264d_ctl_get_sei_cll_params_op_t *)pv_api_op; + + if(ps_ip->u4_size != sizeof(ih264d_ctl_get_sei_cll_params_ip_t)) + { + ps_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; + ps_op->u4_error_code |= + IVD_IP_API_STRUCT_SIZE_INCORRECT; + return IV_FAIL; + } + + if(ps_op->u4_size != sizeof(ih264d_ctl_get_sei_cll_params_op_t)) + { + ps_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; + ps_op->u4_error_code |= + IVD_OP_API_STRUCT_SIZE_INCORRECT; + return IV_FAIL; + } + + break; + } + + case IH264D_CMD_CTL_GET_SEI_AVE_PARAMS: + { + ih264d_ctl_get_sei_ave_params_ip_t *ps_ip; + ih264d_ctl_get_sei_ave_params_op_t *ps_op; + + ps_ip = (ih264d_ctl_get_sei_ave_params_ip_t *)pv_api_ip; + ps_op = (ih264d_ctl_get_sei_ave_params_op_t *)pv_api_op; + + if(ps_ip->u4_size != sizeof(ih264d_ctl_get_sei_ave_params_ip_t)) + { + ps_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; + ps_op->u4_error_code |= + IVD_IP_API_STRUCT_SIZE_INCORRECT; + return IV_FAIL; + } + + if(ps_op->u4_size != sizeof(ih264d_ctl_get_sei_ave_params_op_t)) + { + ps_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; + ps_op->u4_error_code |= + IVD_OP_API_STRUCT_SIZE_INCORRECT; + return IV_FAIL; + } + + break; + } + + case IH264D_CMD_CTL_GET_SEI_CCV_PARAMS: + { + ih264d_ctl_get_sei_ccv_params_ip_t *ps_ip; + ih264d_ctl_get_sei_ccv_params_op_t *ps_op; + + ps_ip = (ih264d_ctl_get_sei_ccv_params_ip_t *)pv_api_ip; + ps_op = (ih264d_ctl_get_sei_ccv_params_op_t *)pv_api_op; + + if(ps_ip->u4_size != sizeof(ih264d_ctl_get_sei_ccv_params_ip_t)) + { + ps_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; + ps_op->u4_error_code |= + IVD_IP_API_STRUCT_SIZE_INCORRECT; + return IV_FAIL; + } + + if(ps_op->u4_size != sizeof(ih264d_ctl_get_sei_ccv_params_op_t)) + { + ps_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; + ps_op->u4_error_code |= + IVD_OP_API_STRUCT_SIZE_INCORRECT; + return IV_FAIL; + } + + break; + } + + case IH264D_CMD_CTL_SET_NUM_CORES: + { + ih264d_ctl_set_num_cores_ip_t *ps_ip; + ih264d_ctl_set_num_cores_op_t *ps_op; + + ps_ip = (ih264d_ctl_set_num_cores_ip_t *)pv_api_ip; + ps_op = (ih264d_ctl_set_num_cores_op_t *)pv_api_op; + + if(ps_ip->u4_size != sizeof(ih264d_ctl_set_num_cores_ip_t)) + { + ps_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; + ps_op->u4_error_code |= + IVD_IP_API_STRUCT_SIZE_INCORRECT; + return IV_FAIL; + } + + if(ps_op->u4_size != sizeof(ih264d_ctl_set_num_cores_op_t)) + { + ps_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; + ps_op->u4_error_code |= + IVD_OP_API_STRUCT_SIZE_INCORRECT; + return IV_FAIL; + } + + if((ps_ip->u4_num_cores != 1) && (ps_ip->u4_num_cores != 2) + && (ps_ip->u4_num_cores != 3) + && (ps_ip->u4_num_cores != 4)) + { + ps_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; + return IV_FAIL; + } + break; + } + case IH264D_CMD_CTL_SET_PROCESSOR: + { + ih264d_ctl_set_processor_ip_t *ps_ip; + ih264d_ctl_set_processor_op_t *ps_op; + + ps_ip = (ih264d_ctl_set_processor_ip_t *)pv_api_ip; + ps_op = (ih264d_ctl_set_processor_op_t *)pv_api_op; + + if(ps_ip->u4_size != sizeof(ih264d_ctl_set_processor_ip_t)) + { + ps_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; + ps_op->u4_error_code |= + IVD_IP_API_STRUCT_SIZE_INCORRECT; + return IV_FAIL; + } + + if(ps_op->u4_size != sizeof(ih264d_ctl_set_processor_op_t)) + { + ps_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; + ps_op->u4_error_code |= + IVD_OP_API_STRUCT_SIZE_INCORRECT; + return IV_FAIL; + } + + break; + } + default: + *(pu4_api_op + 1) |= 1 << IVD_UNSUPPORTEDPARAM; + *(pu4_api_op + 1) |= IVD_UNSUPPORTED_API_CMD; + return IV_FAIL; + break; + } + } + break; + } + + return IV_SUCCESS; +} + + +/** + ******************************************************************************* + * + * @brief + * Sets Processor type + * + * @par Description: + * Sets Processor type + * + * @param[in] ps_codec_obj + * Pointer to codec object at API level + * + * @param[in] pv_api_ip + * Pointer to input argument structure + * + * @param[out] pv_api_op + * Pointer to output argument structure + * + * @returns Status + * + * @remarks + * + * + ******************************************************************************* + */ + +WORD32 ih264d_set_processor(iv_obj_t *dec_hdl, void *pv_api_ip, void *pv_api_op) +{ + ih264d_ctl_set_processor_ip_t *ps_ip; + ih264d_ctl_set_processor_op_t *ps_op; + dec_struct_t *ps_codec = (dec_struct_t *)dec_hdl->pv_codec_handle; + + ps_ip = (ih264d_ctl_set_processor_ip_t *)pv_api_ip; + ps_op = (ih264d_ctl_set_processor_op_t *)pv_api_op; + + ps_codec->e_processor_arch = (IVD_ARCH_T)ps_ip->u4_arch; + ps_codec->e_processor_soc = (IVD_SOC_T)ps_ip->u4_soc; + + ih264d_init_function_ptr(ps_codec); + + ps_op->u4_error_code = 0; + return IV_SUCCESS; +} + + +/************************************************************************** + * \if Function name : ih264d_init_decoder \endif + * + * + * \brief + * Initializes the decoder + * + * \param apiVersion : Version of the api being used. + * \param errorHandlingMechanism : Mechanism to be used for errror handling. + * \param postFilteringType: Type of post filtering operation to be used. + * \param uc_outputFormat: Format of the decoded picture [default 4:2:0]. + * \param uc_dispBufs: Number of Display Buffers. + * \param p_NALBufAPI: Pointer to NAL Buffer API. + * \param p_DispBufAPI: Pointer to Display Buffer API. + * \param ih264d_dec_mem_manager :Pointer to the function that will be called by decoder + * for memory allocation and freeing. + * + * \return + * 0 on Success and -1 on error + * + ************************************************************************** + */ +void ih264d_init_decoder(void * ps_dec_params) +{ + dec_struct_t * ps_dec = (dec_struct_t *)ps_dec_params; + dec_slice_params_t *ps_cur_slice; + pocstruct_t *ps_prev_poc, *ps_cur_poc; + WORD32 size; + + size = sizeof(pred_info_t) * 2 * 32; + memset(ps_dec->ps_pred, 0 , size); + + size = sizeof(disp_mgr_t); + memset(ps_dec->pv_disp_buf_mgr, 0 , size); + + size = sizeof(buf_mgr_t) + ithread_get_mutex_lock_size(); + memset(ps_dec->pv_pic_buf_mgr, 0, size); + + size = sizeof(dec_err_status_t); + memset(ps_dec->ps_dec_err_status, 0, size); + + size = sizeof(sei); + memset(ps_dec->ps_sei, 0, size); + + size = sizeof(sei); + memset(ps_dec->ps_sei_parse, 0, size); + + size = sizeof(dpb_commands_t); + memset(ps_dec->ps_dpb_cmds, 0, size); + + size = sizeof(dec_bit_stream_t); + memset(ps_dec->ps_bitstrm, 0, size); + + size = sizeof(dec_slice_params_t); + memset(ps_dec->ps_cur_slice, 0, size); + + size = MAX(sizeof(dec_seq_params_t), sizeof(dec_pic_params_t)); + memset(ps_dec->pv_scratch_sps_pps, 0, size); + + size = sizeof(ctxt_inc_mb_info_t); + memset(ps_dec->ps_left_mb_ctxt_info, 0, size); + + size = (sizeof(neighbouradd_t) << 2); + memset(ps_dec->ps_left_mvpred_addr, 0 ,size); + + size = sizeof(buf_mgr_t) + ithread_get_mutex_lock_size(); + memset(ps_dec->pv_mv_buf_mgr, 0, size); + + /* Free any dynamic buffers that are allocated */ + ih264d_free_dynamic_bufs(ps_dec); + + { + UWORD8 i; + struct pic_buffer_t *ps_init_dpb; + ps_init_dpb = ps_dec->ps_dpb_mgr->ps_init_dpb[0][0]; + for(i = 0; i < 2 * MAX_REF_BUFS; i++) + { + ps_init_dpb->pu1_buf1 = NULL; + ps_init_dpb->u1_long_term_frm_idx = MAX_REF_BUFS + 1; + ps_dec->ps_dpb_mgr->ps_init_dpb[0][i] = ps_init_dpb; + ps_dec->ps_dpb_mgr->ps_mod_dpb[0][i] = ps_init_dpb; + ps_init_dpb++; + } + + ps_init_dpb = ps_dec->ps_dpb_mgr->ps_init_dpb[1][0]; + for(i = 0; i < 2 * MAX_REF_BUFS; i++) + { + ps_init_dpb->pu1_buf1 = NULL; + ps_init_dpb->u1_long_term_frm_idx = MAX_REF_BUFS + 1; + ps_dec->ps_dpb_mgr->ps_init_dpb[1][i] = ps_init_dpb; + ps_dec->ps_dpb_mgr->ps_mod_dpb[1][i] = ps_init_dpb; + ps_init_dpb++; + } + } + + ps_cur_slice = ps_dec->ps_cur_slice; + ps_dec->init_done = 0; + + ps_dec->u4_num_cores = 1; + + ps_dec->u2_pic_ht = ps_dec->u2_pic_wd = 0; + + ps_dec->u1_separate_parse = DEFAULT_SEPARATE_PARSE; + ps_dec->u4_app_disable_deblk_frm = 0; + ps_dec->i4_degrade_type = 0; + ps_dec->i4_degrade_pics = 0; + + memset(ps_dec->ps_pps, 0, + ((sizeof(dec_pic_params_t)) * MAX_NUM_PIC_PARAMS)); + memset(ps_dec->ps_sps, 0, + ((sizeof(dec_seq_params_t)) * MAX_NUM_SEQ_PARAMS)); + + /* Initialization of function pointers ih264d_deblock_picture function*/ + + ps_dec->p_DeblockPicture[0] = ih264d_deblock_picture_non_mbaff; + ps_dec->p_DeblockPicture[1] = ih264d_deblock_picture_mbaff; + + ps_dec->s_cab_dec_env.pv_codec_handle = ps_dec; + + ps_dec->u4_num_fld_in_frm = 0; + + ps_dec->ps_dpb_mgr->pv_codec_handle = ps_dec; + + /* Initialize the sei validity u4_flag with zero indiacting sei is not valid*/ + ps_dec->ps_sei->u1_is_valid = 0; + + /* decParams Initializations */ + ps_dec->ps_cur_pps = NULL; + ps_dec->ps_cur_sps = NULL; + ps_dec->u1_init_dec_flag = 0; + ps_dec->u1_first_slice_in_stream = 1; + ps_dec->u1_last_pic_not_decoded = 0; + ps_dec->u4_app_disp_width = 0; + ps_dec->i4_header_decoded = 0; + ps_dec->u4_total_frames_decoded = 0; + + ps_dec->i4_error_code = 0; + ps_dec->i4_content_type = IV_CONTENTTYPE_NA; + ps_dec->ps_cur_slice->u1_mbaff_frame_flag = 0; + + ps_dec->ps_dec_err_status->u1_err_flag = ACCEPT_ALL_PICS; //REJECT_PB_PICS; + ps_dec->ps_dec_err_status->u1_cur_pic_type = PIC_TYPE_UNKNOWN; + ps_dec->ps_dec_err_status->u4_frm_sei_sync = SYNC_FRM_DEFAULT; + ps_dec->ps_dec_err_status->u4_cur_frm = INIT_FRAME; + ps_dec->ps_dec_err_status->u1_pic_aud_i = PIC_TYPE_UNKNOWN; + + ps_dec->u1_pr_sl_type = 0xFF; + ps_dec->u2_mbx = 0xffff; + ps_dec->u2_mby = 0; + ps_dec->u2_total_mbs_coded = 0; + + /* POC initializations */ + ps_prev_poc = &ps_dec->s_prev_pic_poc; + ps_cur_poc = &ps_dec->s_cur_pic_poc; + ps_prev_poc->i4_pic_order_cnt_lsb = ps_cur_poc->i4_pic_order_cnt_lsb = 0; + ps_prev_poc->i4_pic_order_cnt_msb = ps_cur_poc->i4_pic_order_cnt_msb = 0; + ps_prev_poc->i4_delta_pic_order_cnt_bottom = + ps_cur_poc->i4_delta_pic_order_cnt_bottom = 0; + ps_prev_poc->i4_delta_pic_order_cnt[0] = + ps_cur_poc->i4_delta_pic_order_cnt[0] = 0; + ps_prev_poc->i4_delta_pic_order_cnt[1] = + ps_cur_poc->i4_delta_pic_order_cnt[1] = 0; + ps_prev_poc->u1_mmco_equalto5 = ps_cur_poc->u1_mmco_equalto5 = 0; + ps_prev_poc->i4_top_field_order_count = ps_cur_poc->i4_top_field_order_count = + 0; + ps_prev_poc->i4_bottom_field_order_count = + ps_cur_poc->i4_bottom_field_order_count = 0; + ps_prev_poc->u1_bot_field = ps_cur_poc->u1_bot_field = 0; + ps_prev_poc->u1_mmco_equalto5 = ps_cur_poc->u1_mmco_equalto5 = 0; + ps_prev_poc->i4_prev_frame_num_ofst = ps_cur_poc->i4_prev_frame_num_ofst = 0; + ps_cur_slice->u1_mmco_equalto5 = 0; + ps_cur_slice->u2_frame_num = 0; + + ps_dec->i4_max_poc = 0; + ps_dec->i4_prev_max_display_seq = 0; + ps_dec->u1_recon_mb_grp = 4; + ps_dec->i4_reorder_depth = -1; + + /* Field PIC initializations */ + ps_dec->u1_second_field = 0; + ps_dec->s_prev_seq_params.u1_eoseq_pending = 0; + + /* Set the cropping parameters as zero */ + ps_dec->u2_crop_offset_y = 0; + ps_dec->u2_crop_offset_uv = 0; + + ps_dec->u1_frame_cropping_flag = 0; + ps_dec->u1_frame_cropping_rect_left_ofst = 0; + ps_dec->u1_frame_cropping_rect_right_ofst = 0; + ps_dec->u1_frame_cropping_rect_top_ofst = 0; + ps_dec->u1_frame_cropping_rect_bottom_ofst = 0; + + /* The Initial Frame Rate Info is not Present */ + ps_dec->i4_vui_frame_rate = -1; + ps_dec->i4_pic_type = NA_SLICE; + ps_dec->i4_frametype = IV_NA_FRAME; + ps_dec->i4_content_type = IV_CONTENTTYPE_NA; + + ps_dec->u1_res_changed = 0; + + + ps_dec->u1_frame_decoded_flag = 0; + + /* Set the default frame seek mask mode */ + ps_dec->u4_skip_frm_mask = SKIP_NONE; + + /********************************************************/ + /* Initialize CAVLC residual decoding function pointers */ + /********************************************************/ + ps_dec->pf_cavlc_4x4res_block[0] = ih264d_cavlc_4x4res_block_totalcoeff_1; + ps_dec->pf_cavlc_4x4res_block[1] = + ih264d_cavlc_4x4res_block_totalcoeff_2to10; + ps_dec->pf_cavlc_4x4res_block[2] = + ih264d_cavlc_4x4res_block_totalcoeff_11to16; + + ps_dec->pf_cavlc_parse4x4coeff[0] = ih264d_cavlc_parse4x4coeff_n0to7; + ps_dec->pf_cavlc_parse4x4coeff[1] = ih264d_cavlc_parse4x4coeff_n8; + + ps_dec->pf_cavlc_parse_8x8block[0] = + ih264d_cavlc_parse_8x8block_none_available; + ps_dec->pf_cavlc_parse_8x8block[1] = + ih264d_cavlc_parse_8x8block_left_available; + ps_dec->pf_cavlc_parse_8x8block[2] = + ih264d_cavlc_parse_8x8block_top_available; + ps_dec->pf_cavlc_parse_8x8block[3] = + ih264d_cavlc_parse_8x8block_both_available; + + /***************************************************************************/ + /* Initialize Bs calculation function pointers for P and B, 16x16/non16x16 */ + /***************************************************************************/ + ps_dec->pf_fill_bs1[0][0] = ih264d_fill_bs1_16x16mb_pslice; + ps_dec->pf_fill_bs1[0][1] = ih264d_fill_bs1_non16x16mb_pslice; + + ps_dec->pf_fill_bs1[1][0] = ih264d_fill_bs1_16x16mb_bslice; + ps_dec->pf_fill_bs1[1][1] = ih264d_fill_bs1_non16x16mb_bslice; + + ps_dec->pf_fill_bs_xtra_left_edge[0] = + ih264d_fill_bs_xtra_left_edge_cur_frm; + ps_dec->pf_fill_bs_xtra_left_edge[1] = + ih264d_fill_bs_xtra_left_edge_cur_fld; + + /* Initialize Reference Pic Buffers */ + ih264d_init_ref_bufs(ps_dec->ps_dpb_mgr); + + ps_dec->u2_prv_frame_num = 0; + ps_dec->u1_top_bottom_decoded = 0; + ps_dec->u1_dangling_field = 0; + + ps_dec->s_cab_dec_env.cabac_table = gau4_ih264d_cabac_table; + + ps_dec->pu1_left_mv_ctxt_inc = ps_dec->u1_left_mv_ctxt_inc_arr[0]; + ps_dec->pi1_left_ref_idx_ctxt_inc = + &ps_dec->i1_left_ref_idx_ctx_inc_arr[0][0]; + ps_dec->pu1_left_yuv_dc_csbp = &ps_dec->u1_yuv_dc_csbp_topmb; + + /* ! */ + /* Initializing flush frame u4_flag */ + ps_dec->u1_flushfrm = 0; + + { + ps_dec->s_cab_dec_env.pv_codec_handle = (void*)ps_dec; + ps_dec->ps_bitstrm->pv_codec_handle = (void*)ps_dec; + ps_dec->ps_cur_slice->pv_codec_handle = (void*)ps_dec; + ps_dec->ps_dpb_mgr->pv_codec_handle = (void*)ps_dec; + } + + memset(ps_dec->disp_bufs, 0, (MAX_DISP_BUFS_NEW) * sizeof(disp_buf_t)); + memset(ps_dec->u4_disp_buf_mapping, 0, + (MAX_DISP_BUFS_NEW) * sizeof(UWORD32)); + memset(ps_dec->u4_disp_buf_to_be_freed, 0, + (MAX_DISP_BUFS_NEW) * sizeof(UWORD32)); + memset(ps_dec->ps_cur_slice, 0, sizeof(dec_slice_params_t)); + + ih264d_init_arch(ps_dec); + ih264d_init_function_ptr(ps_dec); + ps_dec->e_frm_out_mode = IVD_DISPLAY_FRAME_OUT; + ps_dec->init_done = 1; + +} +WORD32 ih264d_free_static_bufs(iv_obj_t *dec_hdl) +{ + dec_struct_t *ps_dec; + + void (*pf_aligned_free)(void *pv_mem_ctxt, void *pv_buf); + void *pv_mem_ctxt; + + ps_dec = (dec_struct_t *)dec_hdl->pv_codec_handle; + pf_aligned_free = ps_dec->pf_aligned_free; + pv_mem_ctxt = ps_dec->pv_mem_ctxt; + + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_sps); + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_pps); + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pv_dec_thread_handle); + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pv_bs_deblk_thread_handle); + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_dpb_mgr); + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_pred); + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pv_disp_buf_mgr); + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pv_pic_buf_mgr); + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_pic_buf_base); + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_dec_err_status); + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_sei); + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_sei_parse); + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_dpb_cmds); + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_bitstrm); + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_cur_slice); + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pv_scratch_sps_pps); + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pu1_bits_buf_static); + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ppv_map_ref_idx_to_poc_base); + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->p_cabac_ctxt_table_t); + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_left_mb_ctxt_info); + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pu1_ref_buff_base); + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pi2_pred1); + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pu1_temp_mc_buffer); + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pu1_init_dpb_base); + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pu4_mbaff_wt_mat); + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pu4_wts_ofsts_mat); + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_left_mvpred_addr); + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pv_mv_buf_mgr); + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_col_mv_base); + PS_DEC_ALIGNED_FREE(ps_dec, dec_hdl->pv_codec_handle); + + if(dec_hdl) + { + pf_aligned_free(pv_mem_ctxt, dec_hdl); + } + return IV_SUCCESS; +} +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_create */ +/* */ +/* Description : creates decoder */ +/* */ +/* Inputs :iv_obj_t decoder handle */ +/* :pv_api_ip pointer to input structure */ +/* :pv_api_op pointer to output structure */ +/* Outputs : */ +/* Returns : void */ +/* */ +/* Issues : none */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 22 10 2008 100356 Draft */ +/* */ +/*****************************************************************************/ +WORD32 ih264d_allocate_static_bufs(iv_obj_t **dec_hdl, void *pv_api_ip, void *pv_api_op) +{ + ih264d_create_ip_t *ps_create_ip; + ih264d_create_op_t *ps_create_op; + void *pv_buf; + UWORD8 *pu1_buf; + dec_struct_t *ps_dec; + void *(*pf_aligned_alloc)(void *pv_mem_ctxt, WORD32 alignment, WORD32 size); + void (*pf_aligned_free)(void *pv_mem_ctxt, void *pv_buf); + void *pv_mem_ctxt; + WORD32 size; + + ps_create_ip = (ih264d_create_ip_t *)pv_api_ip; + ps_create_op = (ih264d_create_op_t *)pv_api_op; + + ps_create_op->s_ivd_create_op_t.u4_error_code = 0; + + pf_aligned_alloc = ps_create_ip->s_ivd_create_ip_t.pf_aligned_alloc; + pf_aligned_free = ps_create_ip->s_ivd_create_ip_t.pf_aligned_free; + pv_mem_ctxt = ps_create_ip->s_ivd_create_ip_t.pv_mem_ctxt; + + /* Initialize return handle to NULL */ + ps_create_op->s_ivd_create_op_t.pv_handle = NULL; + pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, sizeof(iv_obj_t)); + RETURN_IF((NULL == pv_buf), IV_FAIL); + memset(pv_buf, 0, sizeof(iv_obj_t)); + *dec_hdl = (iv_obj_t *)pv_buf; + ps_create_op->s_ivd_create_op_t.pv_handle = *dec_hdl; + + (*dec_hdl)->pv_codec_handle = NULL; + pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, sizeof(dec_struct_t)); + RETURN_IF((NULL == pv_buf), IV_FAIL); + (*dec_hdl)->pv_codec_handle = (dec_struct_t *)pv_buf; + ps_dec = (dec_struct_t *)pv_buf; + + memset(ps_dec, 0, sizeof(dec_struct_t)); + +#ifndef LOGO_EN + ps_dec->u4_share_disp_buf = ps_create_ip->s_ivd_create_ip_t.u4_share_disp_buf; +#else + ps_dec->u4_share_disp_buf = 0; +#endif + + ps_dec->u1_chroma_format = + (UWORD8)(ps_create_ip->s_ivd_create_ip_t.e_output_format); + + if((ps_dec->u1_chroma_format != IV_YUV_420P) + && (ps_dec->u1_chroma_format + != IV_YUV_420SP_UV) + && (ps_dec->u1_chroma_format + != IV_YUV_420SP_VU)) + { + ps_dec->u4_share_disp_buf = 0; + } + + ps_dec->pf_aligned_alloc = pf_aligned_alloc; + ps_dec->pf_aligned_free = pf_aligned_free; + ps_dec->pv_mem_ctxt = pv_mem_ctxt; + + + size = ((sizeof(dec_seq_params_t)) * MAX_NUM_SEQ_PARAMS); + pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + memset(pv_buf, 0, size); + ps_dec->ps_sps = pv_buf; + + size = (sizeof(dec_pic_params_t)) * MAX_NUM_PIC_PARAMS; + pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + memset(pv_buf, 0, size); + ps_dec->ps_pps = pv_buf; + + size = ithread_get_handle_size(); + pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + memset(pv_buf, 0, size); + ps_dec->pv_dec_thread_handle = pv_buf; + + size = ithread_get_handle_size(); + pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + memset(pv_buf, 0, size); + ps_dec->pv_bs_deblk_thread_handle = pv_buf; + + size = sizeof(dpb_manager_t); + pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + memset(pv_buf, 0, size); + ps_dec->ps_dpb_mgr = pv_buf; + + size = sizeof(pred_info_t) * 2 * 32; + pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + memset(pv_buf, 0, size); + ps_dec->ps_pred = pv_buf; + + size = sizeof(disp_mgr_t); + pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + memset(pv_buf, 0, size); + ps_dec->pv_disp_buf_mgr = pv_buf; + + size = sizeof(buf_mgr_t) + ithread_get_mutex_lock_size(); + pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + memset(pv_buf, 0, size); + ps_dec->pv_pic_buf_mgr = pv_buf; + + size = sizeof(struct pic_buffer_t) * (H264_MAX_REF_PICS * 2); + pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + memset(pv_buf, 0, size); + ps_dec->ps_pic_buf_base = pv_buf; + + size = sizeof(dec_err_status_t); + pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + memset(pv_buf, 0, size); + ps_dec->ps_dec_err_status = (dec_err_status_t *)pv_buf; + + size = sizeof(sei); + pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + memset(pv_buf, 0, size); + ps_dec->ps_sei = (sei *)pv_buf; + + size = sizeof(sei); + pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + memset(pv_buf, 0, size); + ps_dec->ps_sei_parse = (sei *)pv_buf; + + size = sizeof(dpb_commands_t); + pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + memset(pv_buf, 0, size); + ps_dec->ps_dpb_cmds = (dpb_commands_t *)pv_buf; + + size = sizeof(dec_bit_stream_t); + pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + memset(pv_buf, 0, size); + ps_dec->ps_bitstrm = (dec_bit_stream_t *)pv_buf; + + size = sizeof(dec_slice_params_t); + pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + memset(pv_buf, 0, size); + ps_dec->ps_cur_slice = (dec_slice_params_t *)pv_buf; + + size = MAX(sizeof(dec_seq_params_t), sizeof(dec_pic_params_t)); + pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + memset(pv_buf, 0, size); + ps_dec->pv_scratch_sps_pps = pv_buf; + + + ps_dec->u4_static_bits_buf_size = 256000; + pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, ps_dec->u4_static_bits_buf_size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + memset(pv_buf, 0, ps_dec->u4_static_bits_buf_size); + ps_dec->pu1_bits_buf_static = pv_buf; + + + size = ((TOTAL_LIST_ENTRIES + PAD_MAP_IDX_POC) + * sizeof(void *)); + pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + ps_dec->ppv_map_ref_idx_to_poc_base = pv_buf; + memset(ps_dec->ppv_map_ref_idx_to_poc_base, 0, size); + + ps_dec->ppv_map_ref_idx_to_poc = ps_dec->ppv_map_ref_idx_to_poc_base + OFFSET_MAP_IDX_POC; + + + size = (sizeof(bin_ctxt_model_t) * NUM_CABAC_CTXTS); + pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + memset(pv_buf, 0, size); + ps_dec->p_cabac_ctxt_table_t = pv_buf; + + + + size = sizeof(ctxt_inc_mb_info_t); + pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + memset(pv_buf, 0, size); + ps_dec->ps_left_mb_ctxt_info = pv_buf; + + + + size = MAX_REF_BUF_SIZE * 2; + pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + memset(pv_buf, 0, size); + ps_dec->pu1_ref_buff_base = pv_buf; + ps_dec->pu1_ref_buff = ps_dec->pu1_ref_buff_base + MAX_REF_BUF_SIZE; + + + size = ((sizeof(WORD16)) * PRED_BUFFER_WIDTH + * PRED_BUFFER_HEIGHT * 2); + pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + memset(pv_buf, 0, size); + ps_dec->pi2_pred1 = pv_buf; + + + size = sizeof(UWORD8) * (MB_LUM_SIZE); + pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + memset(pv_buf, 0, size); + ps_dec->pu1_temp_mc_buffer = pv_buf; + + + + + size = 8 * MAX_REF_BUFS * sizeof(struct pic_buffer_t); + pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + memset(pv_buf, 0, size); + + ps_dec->pu1_init_dpb_base = pv_buf; + pu1_buf = pv_buf; + ps_dec->ps_dpb_mgr->ps_init_dpb[0][0] = (struct pic_buffer_t *)pu1_buf; + + pu1_buf += size / 2; + ps_dec->ps_dpb_mgr->ps_init_dpb[1][0] = (struct pic_buffer_t *)pu1_buf; + + size = (sizeof(UWORD32) * 2 * 3 + * ((MAX_FRAMES << 1) * (MAX_FRAMES << 1)) * 2); + pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + memset(pv_buf, 0, size); + ps_dec->pu4_mbaff_wt_mat = pv_buf; + + size = sizeof(UWORD32) * 2 * 3 + * ((MAX_FRAMES << 1) * (MAX_FRAMES << 1)); + pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + memset(pv_buf, 0, size); + ps_dec->pu4_wts_ofsts_mat = pv_buf; + + + size = (sizeof(neighbouradd_t) << 2); + pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + memset(pv_buf, 0, size); + ps_dec->ps_left_mvpred_addr = pv_buf; + + + size = sizeof(buf_mgr_t) + ithread_get_mutex_lock_size(); + pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + memset(pv_buf, 0, size); + ps_dec->pv_mv_buf_mgr = pv_buf; + + + size = sizeof(col_mv_buf_t) * (H264_MAX_REF_PICS * 2); + pv_buf = pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + ps_dec->ps_col_mv_base = pv_buf; + memset(ps_dec->ps_col_mv_base, 0, size); + + ih264d_init_decoder(ps_dec); + + return IV_SUCCESS; +} + + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_create */ +/* */ +/* Description : creates decoder */ +/* */ +/* Inputs :iv_obj_t decoder handle */ +/* :pv_api_ip pointer to input structure */ +/* :pv_api_op pointer to output structure */ +/* Outputs : */ +/* Returns : void */ +/* */ +/* Issues : none */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 22 10 2008 100356 Draft */ +/* */ +/*****************************************************************************/ +WORD32 ih264d_create(iv_obj_t *dec_hdl, void *pv_api_ip, void *pv_api_op) +{ + ih264d_create_ip_t *ps_create_ip; + ih264d_create_op_t *ps_create_op; + + WORD32 ret; + + ps_create_ip = (ih264d_create_ip_t *)pv_api_ip; + ps_create_op = (ih264d_create_op_t *)pv_api_op; + + ps_create_op->s_ivd_create_op_t.u4_error_code = 0; + dec_hdl = NULL; + ret = ih264d_allocate_static_bufs(&dec_hdl, pv_api_ip, pv_api_op); + + /* If allocation of some buffer fails, then free buffers allocated till then */ + if(IV_FAIL == ret) + { + if(dec_hdl) + { + if(dec_hdl->pv_codec_handle) + { + ih264d_free_static_bufs(dec_hdl); + } + else + { + void (*pf_aligned_free)(void *pv_mem_ctxt, void *pv_buf); + void *pv_mem_ctxt; + + pf_aligned_free = ps_create_ip->s_ivd_create_ip_t.pf_aligned_free; + pv_mem_ctxt = ps_create_ip->s_ivd_create_ip_t.pv_mem_ctxt; + pf_aligned_free(pv_mem_ctxt, dec_hdl); + } + } + ps_create_op->s_ivd_create_op_t.u4_error_code = IVD_MEM_ALLOC_FAILED; + ps_create_op->s_ivd_create_op_t.u4_error_code |= 1 << IVD_FATALERROR; + + return IV_FAIL; + } + + return IV_SUCCESS; +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_map_error */ +/* */ +/* Description : Maps error codes to IVD error groups */ +/* */ +/* Inputs : */ +/* Globals : <Does it use any global variables?> */ +/* Outputs : */ +/* Returns : void */ +/* */ +/* Issues : none */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 22 10 2008 100356 Draft */ +/* */ +/*****************************************************************************/ +UWORD32 ih264d_map_error(UWORD32 i4_err_status) +{ + UWORD32 temp = 0; + + switch(i4_err_status) + { + case ERROR_MEM_ALLOC_ISRAM_T: + case ERROR_MEM_ALLOC_SDRAM_T: + case ERROR_BUF_MGR: + case ERROR_MB_GROUP_ASSGN_T: + case ERROR_FRAME_LIMIT_OVER: + case ERROR_ACTUAL_RESOLUTION_GREATER_THAN_INIT: + case ERROR_PROFILE_NOT_SUPPORTED: + case ERROR_INIT_NOT_DONE: + case IVD_MEM_ALLOC_FAILED: + case ERROR_FEATURE_UNAVAIL: + case IVD_STREAM_WIDTH_HEIGHT_NOT_SUPPORTED: + temp = 1 << IVD_FATALERROR; + H264_DEC_DEBUG_PRINT("\nFatal Error\n"); + break; + + case ERROR_DBP_MANAGER_T: + case ERROR_GAPS_IN_FRM_NUM: + case ERROR_UNKNOWN_NAL: + case ERROR_INV_MB_SLC_GRP_T: + case ERROR_MULTIPLE_SLC_GRP_T: + case ERROR_UNKNOWN_LEVEL: + case ERROR_UNAVAIL_PICBUF_T: + case ERROR_UNAVAIL_MVBUF_T: + case ERROR_UNAVAIL_DISPBUF_T: + case ERROR_NUM_REF: + case ERROR_REFIDX_ORDER_T: + case ERROR_PIC0_NOT_FOUND_T: + case ERROR_MB_TYPE: + case ERROR_SUB_MB_TYPE: + case ERROR_CBP: + case ERROR_REF_IDX: + case ERROR_NUM_MV: + case ERROR_CHROMA_PRED_MODE: + case ERROR_INTRAPRED: + case ERROR_NEXT_MB_ADDRESS_T: + case ERROR_MB_ADDRESS_T: + case ERROR_PIC1_NOT_FOUND_T: + case ERROR_CAVLC_NUM_COEFF_T: + case ERROR_CAVLC_SCAN_POS_T: + case ERROR_PRED_WEIGHT_TABLE_T: + case ERROR_CORRUPTED_SLICE: + temp = 1 << IVD_CORRUPTEDDATA; + break; + + case ERROR_NOT_SUPP_RESOLUTION: + case ERROR_ACTUAL_LEVEL_GREATER_THAN_INIT: + temp = 1 << IVD_UNSUPPORTEDINPUT; + break; + + case ERROR_INVALID_PIC_PARAM: + case ERROR_INVALID_SEQ_PARAM: + case ERROR_EGC_EXCEED_32_1_T: + case ERROR_EGC_EXCEED_32_2_T: + case ERROR_INV_RANGE_TEV_T: + case ERROR_INV_SLC_TYPE_T: + case ERROR_INV_POC_TYPE_T: + case ERROR_INV_RANGE_QP_T: + case ERROR_INV_SPS_PPS_T: + case ERROR_INV_SLICE_HDR_T: + case ERROR_INV_SEI_MDCV_PARAMS: + case ERROR_INV_SEI_CLL_PARAMS: + case ERROR_INV_SEI_AVE_PARAMS: + case ERROR_INV_SEI_CCV_PARAMS: + temp = 1 << IVD_CORRUPTEDHEADER; + break; + + case ERROR_EOB_FLUSHBITS_T: + case ERROR_EOB_GETBITS_T: + case ERROR_EOB_GETBIT_T: + case ERROR_EOB_BYPASS_T: + case ERROR_EOB_DECISION_T: + case ERROR_EOB_TERMINATE_T: + case ERROR_EOB_READCOEFF4X4CAB_T: + temp = 1 << IVD_INSUFFICIENTDATA; + break; + case ERROR_DYNAMIC_RESOLUTION_NOT_SUPPORTED: + case ERROR_DISP_WIDTH_RESET_TO_PIC_WIDTH: + temp = 1 << IVD_UNSUPPORTEDPARAM | 1 << IVD_FATALERROR; + break; + + case ERROR_DANGLING_FIELD_IN_PIC: + temp = 1 << IVD_APPLIEDCONCEALMENT; + break; + + } + + return temp; + +} + +UWORD32 ih264d_get_outbuf_size(WORD32 pic_wd, + UWORD32 pic_ht, + UWORD8 u1_chroma_format, + UWORD32 *p_buf_size) +{ + UWORD32 u4_min_num_out_bufs = 0; + + if(u1_chroma_format == IV_YUV_420P) + u4_min_num_out_bufs = MIN_OUT_BUFS_420; + else if(u1_chroma_format == IV_YUV_422ILE) + u4_min_num_out_bufs = MIN_OUT_BUFS_422ILE; + else if(u1_chroma_format == IV_RGB_565) + u4_min_num_out_bufs = MIN_OUT_BUFS_RGB565; + else if((u1_chroma_format == IV_YUV_420SP_UV) + || (u1_chroma_format == IV_YUV_420SP_VU)) + u4_min_num_out_bufs = MIN_OUT_BUFS_420SP; + + if(u1_chroma_format == IV_YUV_420P) + { + p_buf_size[0] = (pic_wd * pic_ht); + p_buf_size[1] = (pic_wd * pic_ht) >> 2; + p_buf_size[2] = (pic_wd * pic_ht) >> 2; + } + else if(u1_chroma_format == IV_YUV_422ILE) + { + p_buf_size[0] = (pic_wd * pic_ht) * 2; + p_buf_size[1] = p_buf_size[2] = 0; + } + else if(u1_chroma_format == IV_RGB_565) + { + p_buf_size[0] = (pic_wd * pic_ht) * 2; + p_buf_size[1] = p_buf_size[2] = 0; + } + else if((u1_chroma_format == IV_YUV_420SP_UV) + || (u1_chroma_format == IV_YUV_420SP_VU)) + { + p_buf_size[0] = (pic_wd * pic_ht); + p_buf_size[1] = (pic_wd * pic_ht) >> 1; + p_buf_size[2] = 0; + } + + return u4_min_num_out_bufs; +} + +WORD32 check_app_out_buf_size(dec_struct_t *ps_dec) +{ + UWORD32 au4_min_out_buf_size[IVD_VIDDEC_MAX_IO_BUFFERS]; + UWORD32 u4_min_num_out_bufs, i; + UWORD32 pic_wd, pic_ht; + + if(0 == ps_dec->u4_share_disp_buf) + { + pic_wd = ps_dec->u2_disp_width; + pic_ht = ps_dec->u2_disp_height; + + } + else + { + pic_wd = ps_dec->u2_frm_wd_y; + pic_ht = ps_dec->u2_frm_ht_y; + } + + if(ps_dec->u4_app_disp_width > pic_wd) + pic_wd = ps_dec->u4_app_disp_width; + + u4_min_num_out_bufs = ih264d_get_outbuf_size(pic_wd, pic_ht, + ps_dec->u1_chroma_format, + &au4_min_out_buf_size[0]); + + + if(0 == ps_dec->u4_share_disp_buf) + { + if(ps_dec->ps_out_buffer->u4_num_bufs < u4_min_num_out_bufs) + return IV_FAIL; + + for(i = 0; i < u4_min_num_out_bufs; i++) + { + if(ps_dec->ps_out_buffer->u4_min_out_buf_size[i] + < au4_min_out_buf_size[i]) + return (IV_FAIL); + } + } + else + { + if(ps_dec->disp_bufs[0].u4_num_bufs < u4_min_num_out_bufs) + return IV_FAIL; + + for(i = 0; i < u4_min_num_out_bufs; i++) + { + /* We need to check only with the disp_buffer[0], because we have + * already ensured that all the buffers are of the same size in + * ih264d_set_display_frame. + */ + if(ps_dec->disp_bufs[0].u4_bufsize[i] < au4_min_out_buf_size[i]) + return (IV_FAIL); + } + + } + + return (IV_SUCCESS); +} + + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_video_decode */ +/* */ +/* Description : handle video decode API command */ +/* */ +/* Inputs :iv_obj_t decoder handle */ +/* :pv_api_ip pointer to input structure */ +/* :pv_api_op pointer to output structure */ +/* Outputs : */ +/* Returns : void */ +/* */ +/* Issues : none */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 22 10 2008 100356 Draft */ +/* */ +/*****************************************************************************/ + +WORD32 ih264d_video_decode(iv_obj_t *dec_hdl, void *pv_api_ip, void *pv_api_op) +{ + /* ! */ + + dec_struct_t * ps_dec = (dec_struct_t *)(dec_hdl->pv_codec_handle); + + WORD32 i4_err_status = 0; + UWORD8 *pu1_buf = NULL; + WORD32 buflen; + UWORD32 u4_max_ofst, u4_length_of_start_code = 0; + + UWORD32 bytes_consumed = 0; + UWORD32 cur_slice_is_nonref = 0; + UWORD32 u4_next_is_aud; + UWORD32 u4_first_start_code_found = 0; + WORD32 ret = 0,api_ret_value = IV_SUCCESS; + WORD32 header_data_left = 0,frame_data_left = 0; + UWORD8 *pu1_bitstrm_buf; + ih264d_video_decode_ip_t *ps_h264d_dec_ip; + ih264d_video_decode_op_t *ps_h264d_dec_op; + ivd_video_decode_ip_t *ps_dec_ip; + ivd_video_decode_op_t *ps_dec_op; + + ithread_set_name((void*)"Parse_thread"); + + ps_h264d_dec_ip = (ih264d_video_decode_ip_t *)pv_api_ip; + ps_h264d_dec_op = (ih264d_video_decode_op_t *)pv_api_op; + ps_dec_ip = &ps_h264d_dec_ip->s_ivd_video_decode_ip_t; + ps_dec_op = &ps_h264d_dec_op->s_ivd_video_decode_op_t; + + { + UWORD32 u4_size; + u4_size = ps_dec_op->u4_size; + memset(ps_h264d_dec_op, 0, sizeof(ih264d_video_decode_op_t)); + ps_dec_op->u4_size = u4_size; + } + + ps_dec->pv_dec_out = ps_dec_op; + if(ps_dec->init_done != 1) + { + return IV_FAIL; + } + + /*Data memory barries instruction,so that bitstream write by the application is complete*/ + DATA_SYNC(); + + if(0 == ps_dec->u1_flushfrm) + { + if(ps_dec_ip->pv_stream_buffer == NULL) + { + ps_dec_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; + ps_dec_op->u4_error_code |= IVD_DEC_FRM_BS_BUF_NULL; + return IV_FAIL; + } + if(ps_dec_ip->u4_num_Bytes <= 0) + { + ps_dec_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; + ps_dec_op->u4_error_code |= IVD_DEC_NUMBYTES_INV; + return IV_FAIL; + + } + } + ps_dec->u1_pic_decode_done = 0; + + ps_dec_op->u4_num_bytes_consumed = 0; + ps_dec_op->i4_reorder_depth = -1; + ps_dec_op->i4_display_index = DEFAULT_POC; + ps_dec->ps_out_buffer = NULL; + + if(ps_dec_ip->u4_size + >= offsetof(ivd_video_decode_ip_t, s_out_buffer)) + ps_dec->ps_out_buffer = &ps_dec_ip->s_out_buffer; + + ps_dec->u4_fmt_conv_cur_row = 0; + + ps_dec->u4_output_present = 0; + ps_dec->s_disp_op.u4_error_code = 1; + ps_dec->u4_fmt_conv_num_rows = FMT_CONV_NUM_ROWS; + if(0 == ps_dec->u4_share_disp_buf + && ps_dec->i4_decode_header == 0) + { + UWORD32 i; + if((ps_dec->ps_out_buffer->u4_num_bufs == 0) || + (ps_dec->ps_out_buffer->u4_num_bufs > IVD_VIDDEC_MAX_IO_BUFFERS)) + { + ps_dec_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; + ps_dec_op->u4_error_code |= IVD_DISP_FRM_ZERO_OP_BUFS; + return IV_FAIL; + } + + for(i = 0; i < ps_dec->ps_out_buffer->u4_num_bufs; i++) + { + if(ps_dec->ps_out_buffer->pu1_bufs[i] == NULL) + { + ps_dec_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; + ps_dec_op->u4_error_code |= IVD_DISP_FRM_OP_BUF_NULL; + return IV_FAIL; + } + + if(ps_dec->ps_out_buffer->u4_min_out_buf_size[i] == 0) + { + ps_dec_op->u4_error_code |= 1 << IVD_UNSUPPORTEDPARAM; + ps_dec_op->u4_error_code |= + IVD_DISP_FRM_ZERO_OP_BUF_SIZE; + return IV_FAIL; + } + } + } + + if(ps_dec->u4_total_frames_decoded >= NUM_FRAMES_LIMIT) + { + ps_dec_op->u4_error_code = ERROR_FRAME_LIMIT_OVER; + return IV_FAIL; + } + + /* ! */ + ps_dec->u4_ts = ps_dec_ip->u4_ts; + + ps_dec_op->u4_error_code = 0; + ps_dec_op->e_pic_type = IV_NA_FRAME; + ps_dec_op->u4_output_present = 0; + ps_dec_op->u4_frame_decoded_flag = 0; + + ps_dec->i4_frametype = IV_NA_FRAME; + ps_dec->i4_content_type = IV_CONTENTTYPE_NA; + + ps_dec->u4_slice_start_code_found = 0; + + /* In case the deocder is not in flush mode(in shared mode), + then decoder has to pick up a buffer to write current frame. + Check if a frame is available in such cases */ + + if(ps_dec->u1_init_dec_flag == 1 && ps_dec->u4_share_disp_buf == 1 + && ps_dec->u1_flushfrm == 0) + { + UWORD32 i; + + WORD32 disp_avail = 0, free_id; + + /* Check if at least one buffer is available with the codec */ + /* If not then return to application with error */ + for(i = 0; i < ps_dec->u1_pic_bufs; i++) + { + if(0 == ps_dec->u4_disp_buf_mapping[i] + || 1 == ps_dec->u4_disp_buf_to_be_freed[i]) + { + disp_avail = 1; + break; + } + + } + + if(0 == disp_avail) + { + /* If something is queued for display wait for that buffer to be returned */ + + ps_dec_op->u4_error_code = IVD_DEC_REF_BUF_NULL; + ps_dec_op->u4_error_code |= (1 << IVD_UNSUPPORTEDPARAM); + return (IV_FAIL); + } + + while(1) + { + pic_buffer_t *ps_pic_buf; + ps_pic_buf = (pic_buffer_t *)ih264_buf_mgr_get_next_free( + (buf_mgr_t *)ps_dec->pv_pic_buf_mgr, &free_id); + + if(ps_pic_buf == NULL) + { + UWORD32 i, display_queued = 0; + + /* check if any buffer was given for display which is not returned yet */ + for(i = 0; i < (MAX_DISP_BUFS_NEW); i++) + { + if(0 != ps_dec->u4_disp_buf_mapping[i]) + { + display_queued = 1; + break; + } + } + /* If some buffer is queued for display, then codec has to singal an error and wait + for that buffer to be returned. + If nothing is queued for display then codec has ownership of all display buffers + and it can reuse any of the existing buffers and continue decoding */ + + if(1 == display_queued) + { + /* If something is queued for display wait for that buffer to be returned */ + ps_dec_op->u4_error_code = IVD_DEC_REF_BUF_NULL; + ps_dec_op->u4_error_code |= (1 + << IVD_UNSUPPORTEDPARAM); + return (IV_FAIL); + } + } + else + { + /* If the buffer is with display, then mark it as in use and then look for a buffer again */ + if(1 == ps_dec->u4_disp_buf_mapping[free_id]) + { + ih264_buf_mgr_set_status( + (buf_mgr_t *)ps_dec->pv_pic_buf_mgr, + free_id, + BUF_MGR_IO); + } + else + { + /** + * Found a free buffer for present call. Release it now. + * Will be again obtained later. + */ + ih264_buf_mgr_release((buf_mgr_t *)ps_dec->pv_pic_buf_mgr, + free_id, + BUF_MGR_IO); + break; + } + } + } + + } + + if(ps_dec->u1_flushfrm) + { + if(ps_dec->u1_init_dec_flag == 0) + { + /*Come out of flush mode and return*/ + ps_dec->u1_flushfrm = 0; + return (IV_FAIL); + } + + + + ih264d_get_next_display_field(ps_dec, ps_dec->ps_out_buffer, + &(ps_dec->s_disp_op)); + if(0 == ps_dec->s_disp_op.u4_error_code) + { + /* check output buffer size given by the application */ + if(check_app_out_buf_size(ps_dec) != IV_SUCCESS) + { + ps_dec_op->u4_error_code= IVD_DISP_FRM_ZERO_OP_BUF_SIZE; + return (IV_FAIL); + } + + ps_dec->u4_fmt_conv_cur_row = 0; + ps_dec->u4_fmt_conv_num_rows = ps_dec->s_disp_frame_info.u4_y_ht; + ih264d_format_convert(ps_dec, &(ps_dec->s_disp_op), + ps_dec->u4_fmt_conv_cur_row, + ps_dec->u4_fmt_conv_num_rows); + ps_dec->u4_fmt_conv_cur_row += ps_dec->u4_fmt_conv_num_rows; + ps_dec->u4_output_present = 1; + + } + ih264d_export_sei_params(&ps_dec_op->s_sei_decode_op, ps_dec); + + ih264d_release_display_field(ps_dec, &(ps_dec->s_disp_op)); + + ps_dec_op->u4_pic_wd = (UWORD32)ps_dec->u2_disp_width; + ps_dec_op->u4_pic_ht = (UWORD32)ps_dec->u2_disp_height; + ps_dec_op->i4_reorder_depth = ps_dec->i4_reorder_depth; + ps_dec_op->i4_display_index = ps_dec->i4_display_index; + + ps_dec_op->u4_new_seq = 0; + + ps_dec_op->u4_output_present = ps_dec->u4_output_present; + ps_dec_op->u4_progressive_frame_flag = + ps_dec->s_disp_op.u4_progressive_frame_flag; + ps_dec_op->e_output_format = + ps_dec->s_disp_op.e_output_format; + ps_dec_op->s_disp_frm_buf = ps_dec->s_disp_op.s_disp_frm_buf; + ps_dec_op->e4_fld_type = ps_dec->s_disp_op.e4_fld_type; + ps_dec_op->u4_ts = ps_dec->s_disp_op.u4_ts; + ps_dec_op->u4_disp_buf_id = ps_dec->s_disp_op.u4_disp_buf_id; + + /*In the case of flush ,since no frame is decoded set pic type as invalid*/ + ps_dec_op->u4_is_ref_flag = -1; + ps_dec_op->e_pic_type = IV_NA_FRAME; + ps_dec_op->u4_frame_decoded_flag = 0; + + if(0 == ps_dec->s_disp_op.u4_error_code) + { + return (IV_SUCCESS); + } + else + return (IV_FAIL); + + } + if(ps_dec->u1_res_changed == 1) + { + /*if resolution has changed and all buffers have been flushed, reset decoder*/ + ih264d_init_decoder(ps_dec); + } + + ps_dec->u2_cur_mb_addr = 0; + ps_dec->u2_total_mbs_coded = 0; + ps_dec->u2_cur_slice_num = 0; + ps_dec->cur_dec_mb_num = 0; + ps_dec->cur_recon_mb_num = 0; + ps_dec->u4_first_slice_in_pic = 1; + ps_dec->u1_slice_header_done = 0; + ps_dec->u1_dangling_field = 0; + + ps_dec->u4_dec_thread_created = 0; + ps_dec->u4_bs_deblk_thread_created = 0; + ps_dec->u4_cur_bs_mb_num = 0; + ps_dec->u4_start_recon_deblk = 0; + ps_dec->u4_sps_cnt_in_process = 0; + + DEBUG_THREADS_PRINTF(" Starting process call\n"); + + + ps_dec->u4_pic_buf_got = 0; + + do + { + WORD32 buf_size; + + pu1_buf = (UWORD8*)ps_dec_ip->pv_stream_buffer + + ps_dec_op->u4_num_bytes_consumed; + + u4_max_ofst = ps_dec_ip->u4_num_Bytes + - ps_dec_op->u4_num_bytes_consumed; + + /* If dynamic bitstream buffer is not allocated and + * header decode is done, then allocate dynamic bitstream buffer + */ + if((NULL == ps_dec->pu1_bits_buf_dynamic) && + (ps_dec->i4_header_decoded & 1)) + { + WORD32 size; + + void *pv_buf; + void *pv_mem_ctxt = ps_dec->pv_mem_ctxt; + size = MAX(256000, ps_dec->u2_pic_wd * ps_dec->u2_pic_ht * 3 / 2); + pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, + size + EXTRA_BS_OFFSET); + RETURN_IF((NULL == pv_buf), IV_FAIL); + memset(pv_buf, 0, size + EXTRA_BS_OFFSET); + ps_dec->pu1_bits_buf_dynamic = pv_buf; + ps_dec->u4_dynamic_bits_buf_size = size; + } + + if(ps_dec->pu1_bits_buf_dynamic) + { + pu1_bitstrm_buf = ps_dec->pu1_bits_buf_dynamic; + buf_size = ps_dec->u4_dynamic_bits_buf_size; + } + else + { + pu1_bitstrm_buf = ps_dec->pu1_bits_buf_static; + buf_size = ps_dec->u4_static_bits_buf_size; + } + + u4_next_is_aud = 0; + + buflen = ih264d_find_start_code(pu1_buf, 0, u4_max_ofst, + &u4_length_of_start_code, + &u4_next_is_aud); + + if(buflen == -1) + buflen = 0; + /* Ignore bytes beyond the allocated size of intermediate buffer */ + /* Since 8 bytes are read ahead, ensure 8 bytes are free at the + end of the buffer, which will be memset to 0 after emulation prevention */ + buflen = MIN(buflen, buf_size - 8); + + bytes_consumed = buflen + u4_length_of_start_code; + ps_dec_op->u4_num_bytes_consumed += bytes_consumed; + + if(buflen) + { + memcpy(pu1_bitstrm_buf, pu1_buf + u4_length_of_start_code, + buflen); + /* Decoder may read extra 8 bytes near end of the frame */ + if((buflen + 8) < buf_size) + { + memset(pu1_bitstrm_buf + buflen, 0, 8); + } + u4_first_start_code_found = 1; + + } + else + { + /*start code not found*/ + + if(u4_first_start_code_found == 0) + { + /*no start codes found in current process call*/ + + ps_dec->i4_error_code = ERROR_START_CODE_NOT_FOUND; + ps_dec_op->u4_error_code |= 1 << IVD_INSUFFICIENTDATA; + + if(ps_dec->u4_pic_buf_got == 0) + { + + ih264d_fill_output_struct_from_context(ps_dec, + ps_dec_op); + + ps_dec_op->u4_error_code = ps_dec->i4_error_code; + ps_dec_op->u4_frame_decoded_flag = 0; + + return (IV_FAIL); + } + else + { + ps_dec->u1_pic_decode_done = 1; + continue; + } + } + else + { + /* a start code has already been found earlier in the same process call*/ + frame_data_left = 0; + header_data_left = 0; + continue; + } + + } + + ret = ih264d_parse_nal_unit(dec_hdl, ps_dec_op, + pu1_bitstrm_buf, buflen); + if(ret != OK) + { + UWORD32 error = ih264d_map_error(ret); + ps_dec_op->u4_error_code = error | ret; + api_ret_value = IV_FAIL; + + if((ret == IVD_RES_CHANGED) + || (ret == IVD_MEM_ALLOC_FAILED) + || (ret == ERROR_UNAVAIL_PICBUF_T) + || (ret == ERROR_UNAVAIL_MVBUF_T) + || (ret == ERROR_INV_SPS_PPS_T) + || (ret == ERROR_FEATURE_UNAVAIL) + || (ret == IVD_STREAM_WIDTH_HEIGHT_NOT_SUPPORTED) + || (ret == IVD_DISP_FRM_ZERO_OP_BUF_SIZE)) + { + ps_dec->u4_slice_start_code_found = 0; + break; + } + + if((ret == ERROR_INCOMPLETE_FRAME) || (ret == ERROR_DANGLING_FIELD_IN_PIC)) + { + ps_dec_op->u4_num_bytes_consumed -= bytes_consumed; + api_ret_value = IV_FAIL; + break; + } + + if(ret == ERROR_IN_LAST_SLICE_OF_PIC) + { + api_ret_value = IV_FAIL; + break; + } + + } + + header_data_left = ((ps_dec->i4_decode_header == 1) + && (ps_dec->i4_header_decoded != 3) + && (ps_dec_op->u4_num_bytes_consumed + < ps_dec_ip->u4_num_Bytes)); + frame_data_left = (((ps_dec->i4_decode_header == 0) + && ((ps_dec->u1_pic_decode_done == 0) + || (u4_next_is_aud == 1))) + && (ps_dec_op->u4_num_bytes_consumed + < ps_dec_ip->u4_num_Bytes)); + } + while(( header_data_left == 1)||(frame_data_left == 1)); + + if((ps_dec->u4_pic_buf_got == 1) + && (ret != IVD_MEM_ALLOC_FAILED) + && ps_dec->u2_total_mbs_coded < ps_dec->u2_frm_ht_in_mbs * ps_dec->u2_frm_wd_in_mbs) + { + // last slice - missing/corruption + WORD32 num_mb_skipped; + WORD32 prev_slice_err; + pocstruct_t temp_poc; + WORD32 ret1; + WORD32 ht_in_mbs; + ht_in_mbs = ps_dec->u2_pic_ht >> (4 + ps_dec->ps_cur_slice->u1_field_pic_flag); + num_mb_skipped = (ht_in_mbs * ps_dec->u2_frm_wd_in_mbs) + - ps_dec->u2_total_mbs_coded; + + if(ps_dec->u4_first_slice_in_pic && (ps_dec->u4_pic_buf_got == 0)) + prev_slice_err = 1; + else + prev_slice_err = 2; + + if(ps_dec->u4_first_slice_in_pic && (ps_dec->u2_total_mbs_coded == 0)) + prev_slice_err = 1; + + ret1 = ih264d_mark_err_slice_skip(ps_dec, num_mb_skipped, ps_dec->u1_nal_unit_type == IDR_SLICE_NAL, ps_dec->ps_cur_slice->u2_frame_num, + &temp_poc, prev_slice_err); + + if((ret1 == ERROR_UNAVAIL_PICBUF_T) || (ret1 == ERROR_UNAVAIL_MVBUF_T) || + (ret1 == ERROR_INV_SPS_PPS_T)) + { + ret = ret1; + } + } + + if((ret == IVD_RES_CHANGED) + || (ret == IVD_MEM_ALLOC_FAILED) + || (ret == ERROR_UNAVAIL_PICBUF_T) + || (ret == ERROR_UNAVAIL_MVBUF_T) + || (ret == ERROR_INV_SPS_PPS_T)) + { + + /* signal the decode thread */ + ih264d_signal_decode_thread(ps_dec); + /* close deblock thread if it is not closed yet */ + if(ps_dec->u4_num_cores == 3) + { + ih264d_signal_bs_deblk_thread(ps_dec); + } + /* dont consume bitstream for change in resolution case */ + if(ret == IVD_RES_CHANGED) + { + ps_dec_op->u4_num_bytes_consumed -= bytes_consumed; + } + return IV_FAIL; + } + + /* Mirror some raw state info to output */ + ps_dec_op->u1_frame_cropping_flag = ps_dec->u1_frame_cropping_flag; + ps_dec_op->u1_frame_cropping_rect_left_ofst = ps_dec->u1_frame_cropping_rect_left_ofst; + ps_dec_op->u1_frame_cropping_rect_right_ofst = ps_dec->u1_frame_cropping_rect_right_ofst; + ps_dec_op->u1_frame_cropping_rect_top_ofst = ps_dec->u1_frame_cropping_rect_top_ofst; + ps_dec_op->u1_frame_cropping_rect_bottom_ofst = ps_dec->u1_frame_cropping_rect_bottom_ofst; + + ps_dec_op->u4_raw_wd = ps_dec->u2_pic_wd; + ps_dec_op->u4_raw_ht = ps_dec->u2_pic_ht; + + + if(ps_dec->u1_separate_parse) + { + /* If Format conversion is not complete, + complete it here */ + if(ps_dec->u4_num_cores == 2) + { + + /*do deblocking of all mbs*/ + if((ps_dec->u4_nmb_deblk == 0) &&(ps_dec->u4_start_recon_deblk == 1) && (ps_dec->ps_cur_sps->u1_mb_aff_flag == 0)) + { + UWORD32 u4_num_mbs,u4_max_addr; + tfr_ctxt_t s_tfr_ctxt; + tfr_ctxt_t *ps_tfr_cxt = &s_tfr_ctxt; + pad_mgr_t *ps_pad_mgr = &ps_dec->s_pad_mgr; + + /*BS is done for all mbs while parsing*/ + u4_max_addr = (ps_dec->u2_frm_wd_in_mbs * ps_dec->u2_frm_ht_in_mbs) - 1; + ps_dec->u4_cur_bs_mb_num = u4_max_addr + 1; + + + ih264d_init_deblk_tfr_ctxt(ps_dec, ps_pad_mgr, ps_tfr_cxt, + ps_dec->u2_frm_wd_in_mbs, 0); + + + u4_num_mbs = u4_max_addr + - ps_dec->u4_cur_deblk_mb_num + 1; + + DEBUG_PERF_PRINTF("mbs left for deblocking= %d \n",u4_num_mbs); + + if(u4_num_mbs != 0) + ih264d_check_mb_map_deblk(ps_dec, u4_num_mbs, + ps_tfr_cxt,1); + + ps_dec->u4_start_recon_deblk = 0; + + } + + } + + /*signal the decode thread*/ + ih264d_signal_decode_thread(ps_dec); + /* close deblock thread if it is not closed yet*/ + if(ps_dec->u4_num_cores == 3) + { + ih264d_signal_bs_deblk_thread(ps_dec); + } + } + + + DATA_SYNC(); + + + if((ps_dec_op->u4_error_code & 0xff) + != ERROR_DYNAMIC_RESOLUTION_NOT_SUPPORTED) + { + ps_dec_op->u4_pic_wd = (UWORD32)ps_dec->u2_disp_width; + ps_dec_op->u4_pic_ht = (UWORD32)ps_dec->u2_disp_height; + ps_dec_op->i4_reorder_depth = ps_dec->i4_reorder_depth; + } + +//Report if header (sps and pps) has not been decoded yet + if(ps_dec->i4_decode_header == 1 && ps_dec->i4_header_decoded != 3) + { + ps_dec_op->u4_error_code |= (1 << IVD_INSUFFICIENTDATA); + api_ret_value = IV_FAIL; + } + + if((ps_dec->u4_pic_buf_got == 1) + && (ERROR_DANGLING_FIELD_IN_PIC != i4_err_status)) + { + /* + * For field pictures, set the bottom and top picture decoded u4_flag correctly. + */ + + if(ps_dec->ps_cur_slice->u1_field_pic_flag) + { + if(1 == ps_dec->ps_cur_slice->u1_bottom_field_flag) + { + ps_dec->u1_top_bottom_decoded |= BOT_FIELD_ONLY; + } + else + { + ps_dec->u1_top_bottom_decoded |= TOP_FIELD_ONLY; + } + } + else + { + ps_dec->u1_top_bottom_decoded = TOP_FIELD_ONLY | BOT_FIELD_ONLY; + } + + /* if new frame in not found (if we are still getting slices from previous frame) + * ih264d_deblock_display is not called. Such frames will not be added to reference /display + */ + if ((ps_dec->ps_dec_err_status->u1_err_flag & REJECT_CUR_PIC) == 0) + { + /* Calling Function to deblock Picture and Display */ + ret = ih264d_deblock_display(ps_dec); + } + + + /*set to complete ,as we dont support partial frame decode*/ + if(ps_dec->i4_header_decoded == 3) + { + ps_dec->u2_total_mbs_coded = ps_dec->ps_cur_sps->u2_max_mb_addr + 1; + } + + /*Update the i4_frametype at the end of picture*/ + if(ps_dec->ps_cur_slice->u1_nal_unit_type == IDR_SLICE_NAL) + { + ps_dec->i4_frametype = IV_IDR_FRAME; + } + else if(ps_dec->i4_pic_type == B_SLICE) + { + ps_dec->i4_frametype = IV_B_FRAME; + } + else if(ps_dec->i4_pic_type == P_SLICE) + { + ps_dec->i4_frametype = IV_P_FRAME; + } + else if(ps_dec->i4_pic_type == I_SLICE) + { + ps_dec->i4_frametype = IV_I_FRAME; + } + else + { + H264_DEC_DEBUG_PRINT("Shouldn't come here\n"); + } + + //Update the content type + ps_dec->i4_content_type = ps_dec->ps_cur_slice->u1_field_pic_flag; + + ps_dec->u4_total_frames_decoded = ps_dec->u4_total_frames_decoded + 2; + ps_dec->u4_total_frames_decoded = ps_dec->u4_total_frames_decoded + - ps_dec->ps_cur_slice->u1_field_pic_flag; + + } + + /* close deblock thread if it is not closed yet*/ + if(ps_dec->u4_num_cores == 3) + { + ih264d_signal_bs_deblk_thread(ps_dec); + } + + + { + /* In case the decoder is configured to run in low delay mode, + * then get display buffer and then format convert. + * Note in this mode, format conversion does not run paralelly in a thread and adds to the codec cycles + */ + + if((IVD_DECODE_FRAME_OUT == ps_dec->e_frm_out_mode) + && ps_dec->u1_init_dec_flag) + { + + ih264d_get_next_display_field(ps_dec, ps_dec->ps_out_buffer, + &(ps_dec->s_disp_op)); + if(0 == ps_dec->s_disp_op.u4_error_code) + { + ps_dec->u4_fmt_conv_cur_row = 0; + ps_dec->u4_output_present = 1; + } + } + + ih264d_fill_output_struct_from_context(ps_dec, ps_dec_op); + + /* If Format conversion is not complete, + complete it here */ + if(ps_dec->u4_output_present && + (ps_dec->u4_fmt_conv_cur_row < ps_dec->s_disp_frame_info.u4_y_ht)) + { + ps_dec->u4_fmt_conv_num_rows = ps_dec->s_disp_frame_info.u4_y_ht + - ps_dec->u4_fmt_conv_cur_row; + ih264d_format_convert(ps_dec, &(ps_dec->s_disp_op), + ps_dec->u4_fmt_conv_cur_row, + ps_dec->u4_fmt_conv_num_rows); + ps_dec->u4_fmt_conv_cur_row += ps_dec->u4_fmt_conv_num_rows; + } + + ih264d_release_display_field(ps_dec, &(ps_dec->s_disp_op)); + } + + if(ps_dec->i4_decode_header == 1 && (ps_dec->i4_header_decoded & 1) == 1) + { + ps_dec_op->u4_progressive_frame_flag = 1; + if((NULL != ps_dec->ps_cur_sps) && (1 == (ps_dec->ps_cur_sps->u1_is_valid))) + { + if((0 == ps_dec->ps_sps->u1_frame_mbs_only_flag) + && (0 == ps_dec->ps_sps->u1_mb_aff_flag)) + ps_dec_op->u4_progressive_frame_flag = 0; + + } + } + + if((TOP_FIELD_ONLY | BOT_FIELD_ONLY) == ps_dec->u1_top_bottom_decoded) + { + ps_dec->u1_top_bottom_decoded = 0; + } + /*--------------------------------------------------------------------*/ + /* Do End of Pic processing. */ + /* Should be called only if frame was decoded in previous process call*/ + /*--------------------------------------------------------------------*/ + if(ps_dec->u4_pic_buf_got == 1) + { + if(1 == ps_dec->u1_last_pic_not_decoded) + { + ret = ih264d_end_of_pic_dispbuf_mgr(ps_dec); + + if(ret != OK) + return ret; + + ret = ih264d_end_of_pic(ps_dec); + if(ret != OK) + return ret; + } + else + { + ret = ih264d_end_of_pic(ps_dec); + if(ret != OK) + return ret; + } + + } + + + /*Data memory barrier instruction,so that yuv write by the library is complete*/ + DATA_SYNC(); + + H264_DEC_DEBUG_PRINT("The num bytes consumed: %d\n", + ps_dec_op->u4_num_bytes_consumed); + return api_ret_value; +} + +WORD32 ih264d_get_version(iv_obj_t *dec_hdl, void *pv_api_ip, void *pv_api_op) +{ + char version_string[MAXVERSION_STRLEN + 1]; + UWORD32 version_string_len; + + ivd_ctl_getversioninfo_ip_t *ps_ip; + ivd_ctl_getversioninfo_op_t *ps_op; + + ps_ip = (ivd_ctl_getversioninfo_ip_t *)pv_api_ip; + ps_op = (ivd_ctl_getversioninfo_op_t *)pv_api_op; + UNUSED(dec_hdl); + ps_op->u4_error_code = IV_SUCCESS; + + VERSION(version_string, CODEC_NAME, CODEC_RELEASE_TYPE, CODEC_RELEASE_VER, + CODEC_VENDOR); + + if((WORD32)ps_ip->u4_version_buffer_size <= 0) + { + ps_op->u4_error_code = IH264D_VERS_BUF_INSUFFICIENT; + return (IV_FAIL); + } + + version_string_len = strnlen(version_string, MAXVERSION_STRLEN) + 1; + + if(ps_ip->u4_version_buffer_size >= version_string_len) //(WORD32)sizeof(sizeof(version_string))) + { + memcpy(ps_ip->pv_version_buffer, version_string, version_string_len); + ps_op->u4_error_code = IV_SUCCESS; + } + else + { + ps_op->u4_error_code = IH264D_VERS_BUF_INSUFFICIENT; + return IV_FAIL; + } + return (IV_SUCCESS); +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_get_display_frame */ +/* */ +/* Description : */ +/* Inputs :iv_obj_t decoder handle */ +/* :pv_api_ip pointer to input structure */ +/* :pv_api_op pointer to output structure */ +/* Outputs : */ +/* Returns : void */ +/* */ +/* Issues : none */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 22 10 2008 100356 Draft */ +/* */ +/*****************************************************************************/ +WORD32 ih264d_get_display_frame(iv_obj_t *dec_hdl, + void *pv_api_ip, + void *pv_api_op) +{ + + UNUSED(dec_hdl); + UNUSED(pv_api_ip); + UNUSED(pv_api_op); + // This function is no longer needed, output is returned in the process() + return IV_FAIL; +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_set_display_frame */ +/* */ +/* Description : */ +/* */ +/* Inputs :iv_obj_t decoder handle */ +/* :pv_api_ip pointer to input structure */ +/* :pv_api_op pointer to output structure */ +/* Outputs : */ +/* Returns : void */ +/* */ +/* Issues : none */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 22 10 2008 100356 Draft */ +/* */ +/*****************************************************************************/ +WORD32 ih264d_set_display_frame(iv_obj_t *dec_hdl, + void *pv_api_ip, + void *pv_api_op) +{ + + UWORD32 u4_disp_buf_size[3], u4_num_disp_bufs; + ivd_set_display_frame_ip_t *dec_disp_ip; + ivd_set_display_frame_op_t *dec_disp_op; + + UWORD32 i; + dec_struct_t * ps_dec = (dec_struct_t *)(dec_hdl->pv_codec_handle); + + dec_disp_ip = (ivd_set_display_frame_ip_t *)pv_api_ip; + dec_disp_op = (ivd_set_display_frame_op_t *)pv_api_op; + dec_disp_op->u4_error_code = 0; + + + ps_dec->u4_num_disp_bufs = 0; + if(ps_dec->u4_share_disp_buf) + { + UWORD32 u4_num_bufs = dec_disp_ip->num_disp_bufs; + + u4_num_bufs = MIN(u4_num_bufs, MAX_DISP_BUFS_NEW); + + ps_dec->u4_num_disp_bufs = u4_num_bufs; + + /* Get the number and sizes of the first buffer. Compare this with the + * rest to make sure all the buffers are of the same size. + */ + u4_num_disp_bufs = dec_disp_ip->s_disp_buffer[0].u4_num_bufs; + + u4_disp_buf_size[0] = + dec_disp_ip->s_disp_buffer[0].u4_min_out_buf_size[0]; + u4_disp_buf_size[1] = + dec_disp_ip->s_disp_buffer[0].u4_min_out_buf_size[1]; + u4_disp_buf_size[2] = + dec_disp_ip->s_disp_buffer[0].u4_min_out_buf_size[2]; + + for(i = 0; i < u4_num_bufs; i++) + { + if(dec_disp_ip->s_disp_buffer[i].u4_num_bufs != u4_num_disp_bufs) + { + return IV_FAIL; + } + + if((dec_disp_ip->s_disp_buffer[i].u4_min_out_buf_size[0] + != u4_disp_buf_size[0]) + || (dec_disp_ip->s_disp_buffer[i].u4_min_out_buf_size[1] + != u4_disp_buf_size[1]) + || (dec_disp_ip->s_disp_buffer[i].u4_min_out_buf_size[2] + != u4_disp_buf_size[2])) + { + return IV_FAIL; + } + + ps_dec->disp_bufs[i].u4_num_bufs = + dec_disp_ip->s_disp_buffer[i].u4_num_bufs; + + ps_dec->disp_bufs[i].buf[0] = + dec_disp_ip->s_disp_buffer[i].pu1_bufs[0]; + ps_dec->disp_bufs[i].buf[1] = + dec_disp_ip->s_disp_buffer[i].pu1_bufs[1]; + ps_dec->disp_bufs[i].buf[2] = + dec_disp_ip->s_disp_buffer[i].pu1_bufs[2]; + + ps_dec->disp_bufs[i].u4_bufsize[0] = + dec_disp_ip->s_disp_buffer[i].u4_min_out_buf_size[0]; + ps_dec->disp_bufs[i].u4_bufsize[1] = + dec_disp_ip->s_disp_buffer[i].u4_min_out_buf_size[1]; + ps_dec->disp_bufs[i].u4_bufsize[2] = + dec_disp_ip->s_disp_buffer[i].u4_min_out_buf_size[2]; + + } + } + return IV_SUCCESS; + +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_set_flush_mode */ +/* */ +/* Description : */ +/* */ +/* Inputs :iv_obj_t decoder handle */ +/* :pv_api_ip pointer to input structure */ +/* :pv_api_op pointer to output structure */ +/* Globals : <Does it use any global variables?> */ +/* Outputs : */ +/* Returns : void */ +/* */ +/* Issues : none */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 22 10 2008 100356 Draft */ +/* */ +/*****************************************************************************/ +WORD32 ih264d_set_flush_mode(iv_obj_t *dec_hdl, void *pv_api_ip, void *pv_api_op) +{ + dec_struct_t * ps_dec; + ivd_ctl_flush_op_t *ps_ctl_op = (ivd_ctl_flush_op_t*)pv_api_op; + ps_ctl_op->u4_error_code = 0; + + ps_dec = (dec_struct_t *)(dec_hdl->pv_codec_handle); + UNUSED(pv_api_ip); + /* ! */ + /* Signal flush frame control call */ + ps_dec->u1_flushfrm = 1; + + if(ps_dec->u1_init_dec_flag == 1) + { + ih264d_release_pics_in_dpb((void *)ps_dec, ps_dec->u1_pic_bufs); + ih264d_release_display_bufs(ps_dec); + } + + ps_ctl_op->u4_error_code = 0; + + /* Ignore dangling fields during flush */ + ps_dec->u1_top_bottom_decoded = 0; + + return IV_SUCCESS; +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_get_status */ +/* */ +/* Description : */ +/* */ +/* Inputs :iv_obj_t decoder handle */ +/* :pv_api_ip pointer to input structure */ +/* :pv_api_op pointer to output structure */ +/* Globals : <Does it use any global variables?> */ +/* Outputs : */ +/* Returns : void */ +/* */ +/* Issues : none */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 22 10 2008 100356 Draft */ +/* */ +/*****************************************************************************/ + +WORD32 ih264d_get_status(iv_obj_t *dec_hdl, void *pv_api_ip, void *pv_api_op) +{ + + UWORD32 i; + dec_struct_t * ps_dec; + UWORD32 pic_wd, pic_ht; + ivd_ctl_getstatus_op_t *ps_ctl_op = (ivd_ctl_getstatus_op_t*)pv_api_op; + UNUSED(pv_api_ip); + ps_ctl_op->u4_error_code = 0; + + ps_dec = (dec_struct_t *)(dec_hdl->pv_codec_handle); + + + if((NULL != ps_dec->ps_cur_sps) && (1 == (ps_dec->ps_cur_sps->u1_is_valid))) + { + ps_ctl_op->u4_pic_ht = ps_dec->u2_disp_height; + ps_ctl_op->u4_pic_wd = ps_dec->u2_disp_width; + + if(0 == ps_dec->u4_share_disp_buf) + { + pic_wd = ps_dec->u2_disp_width; + pic_ht = ps_dec->u2_disp_height; + + } + else + { + pic_wd = ps_dec->u2_frm_wd_y; + pic_ht = ps_dec->u2_frm_ht_y; + } + } + else + { + pic_wd = 0; + pic_ht = 0; + + ps_ctl_op->u4_pic_ht = pic_wd; + ps_ctl_op->u4_pic_wd = pic_ht; + + if(1 == ps_dec->u4_share_disp_buf) + { + pic_wd += (PAD_LEN_Y_H << 1); + pic_ht += (PAD_LEN_Y_V << 2); + + } + + } + + if(ps_dec->u4_app_disp_width > pic_wd) + pic_wd = ps_dec->u4_app_disp_width; + if(0 == ps_dec->u4_share_disp_buf) + ps_ctl_op->u4_num_disp_bufs = 1; + else + { + if((NULL != ps_dec->ps_cur_sps) && (1 == (ps_dec->ps_cur_sps->u1_is_valid))) + { + if((ps_dec->ps_cur_sps->u1_vui_parameters_present_flag == 1) && + (1 == ps_dec->ps_cur_sps->s_vui.u1_bitstream_restriction_flag)) + { + ps_ctl_op->u4_num_disp_bufs = + ps_dec->ps_cur_sps->s_vui.u4_num_reorder_frames + 1; + } + else + { + /*if VUI is not present assume maximum possible refrence frames for the level, + * as max reorder frames*/ + ps_ctl_op->u4_num_disp_bufs = ih264d_get_dpb_size(ps_dec->ps_cur_sps); + } + + ps_ctl_op->u4_num_disp_bufs += + ps_dec->ps_cur_sps->u1_num_ref_frames + 1; + } + else + { + ps_ctl_op->u4_num_disp_bufs = 32; + } + ps_ctl_op->u4_num_disp_bufs = MAX( + ps_ctl_op->u4_num_disp_bufs, 6); + ps_ctl_op->u4_num_disp_bufs = MIN( + ps_ctl_op->u4_num_disp_bufs, 32); + } + + ps_ctl_op->u4_error_code = ps_dec->i4_error_code; + + ps_ctl_op->u4_frame_rate = 0; //make it proper + ps_ctl_op->u4_bit_rate = 0; //make it proper + ps_ctl_op->e_content_type = ps_dec->i4_content_type; + ps_ctl_op->e_output_chroma_format = ps_dec->u1_chroma_format; + ps_ctl_op->u4_min_num_in_bufs = MIN_IN_BUFS; + + if(ps_dec->u1_chroma_format == IV_YUV_420P) + { + ps_ctl_op->u4_min_num_out_bufs = MIN_OUT_BUFS_420; + } + else if(ps_dec->u1_chroma_format == IV_YUV_422ILE) + { + ps_ctl_op->u4_min_num_out_bufs = MIN_OUT_BUFS_422ILE; + } + else if(ps_dec->u1_chroma_format == IV_RGB_565) + { + ps_ctl_op->u4_min_num_out_bufs = MIN_OUT_BUFS_RGB565; + } + else if((ps_dec->u1_chroma_format == IV_YUV_420SP_UV) + || (ps_dec->u1_chroma_format == IV_YUV_420SP_VU)) + { + ps_ctl_op->u4_min_num_out_bufs = MIN_OUT_BUFS_420SP; + } + + else + { + //Invalid chroma format; Error code may be updated, verify in testing if needed + ps_ctl_op->u4_error_code = ERROR_FEATURE_UNAVAIL; + return IV_FAIL; + } + + for(i = 0; i < ps_ctl_op->u4_min_num_in_bufs; i++) + { + ps_ctl_op->u4_min_in_buf_size[i] = MAX(256000, pic_wd * pic_ht * 3 / 2); + } + + /*!*/ + if(ps_dec->u1_chroma_format == IV_YUV_420P) + { + ps_ctl_op->u4_min_out_buf_size[0] = (pic_wd * pic_ht); + ps_ctl_op->u4_min_out_buf_size[1] = (pic_wd * pic_ht) + >> 2; + ps_ctl_op->u4_min_out_buf_size[2] = (pic_wd * pic_ht) + >> 2; + } + else if(ps_dec->u1_chroma_format == IV_YUV_422ILE) + { + ps_ctl_op->u4_min_out_buf_size[0] = (pic_wd * pic_ht) + * 2; + ps_ctl_op->u4_min_out_buf_size[1] = + ps_ctl_op->u4_min_out_buf_size[2] = 0; + } + else if(ps_dec->u1_chroma_format == IV_RGB_565) + { + ps_ctl_op->u4_min_out_buf_size[0] = (pic_wd * pic_ht) + * 2; + ps_ctl_op->u4_min_out_buf_size[1] = + ps_ctl_op->u4_min_out_buf_size[2] = 0; + } + else if((ps_dec->u1_chroma_format == IV_YUV_420SP_UV) + || (ps_dec->u1_chroma_format == IV_YUV_420SP_VU)) + { + ps_ctl_op->u4_min_out_buf_size[0] = (pic_wd * pic_ht); + ps_ctl_op->u4_min_out_buf_size[1] = (pic_wd * pic_ht) + >> 1; + ps_ctl_op->u4_min_out_buf_size[2] = 0; + } + + ps_dec->u4_num_disp_bufs_requested = ps_ctl_op->u4_num_disp_bufs; + return IV_SUCCESS; +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_get_buf_info */ +/* */ +/* Description : */ +/* */ +/* Inputs :iv_obj_t decoder handle */ +/* :pv_api_ip pointer to input structure */ +/* :pv_api_op pointer to output structure */ +/* Globals : <Does it use any global variables?> */ +/* Outputs : */ +/* Returns : void */ +/* */ +/* Issues : none */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 22 10 2008 100356 Draft */ +/* */ +/*****************************************************************************/ +WORD32 ih264d_get_buf_info(iv_obj_t *dec_hdl, void *pv_api_ip, void *pv_api_op) +{ + + dec_struct_t * ps_dec; + UWORD8 i = 0; // Default for 420P format + UWORD16 pic_wd, pic_ht; + ivd_ctl_getbufinfo_op_t *ps_ctl_op = + (ivd_ctl_getbufinfo_op_t*)pv_api_op; + UWORD32 au4_min_out_buf_size[IVD_VIDDEC_MAX_IO_BUFFERS]; + UNUSED(pv_api_ip); + + ps_ctl_op->u4_error_code = 0; + + ps_dec = (dec_struct_t *)(dec_hdl->pv_codec_handle); + + ps_ctl_op->u4_min_num_in_bufs = MIN_IN_BUFS; + + + ps_ctl_op->u4_num_disp_bufs = 1; + + + pic_wd = 0; + pic_ht = 0; + + if(ps_dec->i4_header_decoded == 3) + { + + if(0 == ps_dec->u4_share_disp_buf) + { + pic_wd = ps_dec->u2_disp_width; + pic_ht = ps_dec->u2_disp_height; + + } + else + { + pic_wd = ps_dec->u2_frm_wd_y; + pic_ht = ps_dec->u2_frm_ht_y; + } + + } + + for(i = 0; i < ps_ctl_op->u4_min_num_in_bufs; i++) + { + ps_ctl_op->u4_min_in_buf_size[i] = MAX(256000, pic_wd * pic_ht * 3 / 2); + } + if((WORD32)ps_dec->u4_app_disp_width > pic_wd) + pic_wd = ps_dec->u4_app_disp_width; + + if(0 == ps_dec->u4_share_disp_buf) + ps_ctl_op->u4_num_disp_bufs = 1; + else + { + if((NULL != ps_dec->ps_cur_sps) && (1 == (ps_dec->ps_cur_sps->u1_is_valid))) + { + if((ps_dec->ps_cur_sps->u1_vui_parameters_present_flag == 1) && + (1 == ps_dec->ps_cur_sps->s_vui.u1_bitstream_restriction_flag)) + { + ps_ctl_op->u4_num_disp_bufs = + ps_dec->ps_cur_sps->s_vui.u4_num_reorder_frames + 1; + } + else + { + /*if VUI is not present assume maximum possible refrence frames for the level, + * as max reorder frames*/ + ps_ctl_op->u4_num_disp_bufs = ih264d_get_dpb_size(ps_dec->ps_cur_sps); + } + + ps_ctl_op->u4_num_disp_bufs += + ps_dec->ps_cur_sps->u1_num_ref_frames + 1; + + } + else + { + ps_ctl_op->u4_num_disp_bufs = 32; + + } + + ps_ctl_op->u4_num_disp_bufs = MAX( + ps_ctl_op->u4_num_disp_bufs, 6); + ps_ctl_op->u4_num_disp_bufs = MIN( + ps_ctl_op->u4_num_disp_bufs, 32); + } + + ps_ctl_op->u4_min_num_out_bufs = ih264d_get_outbuf_size( + pic_wd, pic_ht, ps_dec->u1_chroma_format, + &au4_min_out_buf_size[0]); + + for(i = 0; i < ps_ctl_op->u4_min_num_out_bufs; i++) + { + ps_ctl_op->u4_min_out_buf_size[i] = au4_min_out_buf_size[i]; + } + + ps_dec->u4_num_disp_bufs_requested = ps_ctl_op->u4_num_disp_bufs; + + return IV_SUCCESS; +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_set_params */ +/* */ +/* Description : */ +/* */ +/* Inputs :iv_obj_t decoder handle */ +/* :pv_api_ip pointer to input structure */ +/* :pv_api_op pointer to output structure */ +/* Outputs : */ +/* Returns : void */ +/* */ +/* Issues : none */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 22 10 2008 100356 Draft */ +/* */ +/*****************************************************************************/ +WORD32 ih264d_set_params(iv_obj_t *dec_hdl, void *pv_api_ip, void *pv_api_op) +{ + + dec_struct_t * ps_dec; + WORD32 ret = IV_SUCCESS; + + ih264d_ctl_set_config_ip_t *ps_h264d_ctl_ip = + (ih264d_ctl_set_config_ip_t *)pv_api_ip; + ih264d_ctl_set_config_op_t *ps_h264d_ctl_op = + (ih264d_ctl_set_config_op_t *)pv_api_op;; + ivd_ctl_set_config_ip_t *ps_ctl_ip = + &ps_h264d_ctl_ip->s_ivd_ctl_set_config_ip_t; + ivd_ctl_set_config_op_t *ps_ctl_op = + &ps_h264d_ctl_op->s_ivd_ctl_set_config_op_t; + + ps_dec = (dec_struct_t *)(dec_hdl->pv_codec_handle); + + ps_dec->u4_skip_frm_mask = 0; + + ps_ctl_op->u4_error_code = 0; + + if(ps_ctl_ip->e_frm_skip_mode != IVD_SKIP_NONE) + { + ps_ctl_op->u4_error_code = (1 << IVD_UNSUPPORTEDPARAM); + ret = IV_FAIL; + } + + if(ps_ctl_ip->u4_disp_wd >= ps_dec->u2_disp_width) + { + ps_dec->u4_app_disp_width = ps_ctl_ip->u4_disp_wd; + } + else if(0 == ps_dec->i4_header_decoded) + { + ps_dec->u4_app_disp_width = ps_ctl_ip->u4_disp_wd; + } + else if(ps_ctl_ip->u4_disp_wd == 0) + { + ps_dec->u4_app_disp_width = 0; + } + else + { + /* + * Set the display width to zero. This will ensure that the wrong value we had stored (0xFFFFFFFF) + * does not propogate. + */ + ps_dec->u4_app_disp_width = 0; + ps_ctl_op->u4_error_code |= (1 << IVD_UNSUPPORTEDPARAM); + ps_ctl_op->u4_error_code |= ERROR_DISP_WIDTH_INVALID; + ret = IV_FAIL; + } + + if(ps_ctl_ip->e_vid_dec_mode == IVD_DECODE_FRAME) + ps_dec->i4_decode_header = 0; + else if(ps_ctl_ip->e_vid_dec_mode == IVD_DECODE_HEADER) + ps_dec->i4_decode_header = 1; + else + { + ps_ctl_op->u4_error_code = (1 << IVD_UNSUPPORTEDPARAM); + ps_dec->i4_decode_header = 1; + ret = IV_FAIL; + } + ps_dec->e_frm_out_mode = IVD_DISPLAY_FRAME_OUT; + + if((ps_ctl_ip->e_frm_out_mode != IVD_DECODE_FRAME_OUT) && + (ps_ctl_ip->e_frm_out_mode != IVD_DISPLAY_FRAME_OUT)) + { + ps_ctl_op->u4_error_code = (1 << IVD_UNSUPPORTEDPARAM); + ret = IV_FAIL; + } + ps_dec->e_frm_out_mode = ps_ctl_ip->e_frm_out_mode; + return ret; + +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_set_default_params */ +/* */ +/* Description : */ +/* */ +/* Inputs :iv_obj_t decoder handle */ +/* :pv_api_ip pointer to input structure */ +/* :pv_api_op pointer to output structure */ +/* Outputs : */ +/* Returns : void */ +/* */ +/* Issues : none */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 08 08 2011 100421 Copied from set_params */ +/* */ +/*****************************************************************************/ +WORD32 ih264d_set_default_params(iv_obj_t *dec_hdl, + void *pv_api_ip, + void *pv_api_op) +{ + + dec_struct_t * ps_dec; + WORD32 ret = IV_SUCCESS; + + ivd_ctl_set_config_op_t *ps_ctl_op = + (ivd_ctl_set_config_op_t *)pv_api_op; + ps_dec = (dec_struct_t *)(dec_hdl->pv_codec_handle); + UNUSED(pv_api_ip); + + + { + ps_dec->u4_app_disp_width = 0; + ps_dec->u4_skip_frm_mask = 0; + ps_dec->i4_decode_header = 1; + + ps_ctl_op->u4_error_code = 0; + } + + + return ret; +} +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_reset */ +/* */ +/* Description : */ +/* */ +/* Inputs :iv_obj_t decoder handle */ +/* :pv_api_ip pointer to input structure */ +/* :pv_api_op pointer to output structure */ +/* Globals : <Does it use any global variables?> */ +/* Outputs : */ +/* Returns : void */ +/* */ +/* Issues : none */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 22 10 2008 100356 Draft */ +/* */ +/*****************************************************************************/ +WORD32 ih264d_delete(iv_obj_t *dec_hdl, void *pv_api_ip, void *pv_api_op) +{ + dec_struct_t *ps_dec; + ih264d_delete_ip_t *ps_ip = (ih264d_delete_ip_t *)pv_api_ip; + ih264d_delete_op_t *ps_op = (ih264d_delete_op_t *)pv_api_op; + + ps_dec = (dec_struct_t *)(dec_hdl->pv_codec_handle); + UNUSED(ps_ip); + ps_op->s_ivd_delete_op_t.u4_error_code = 0; + ih264d_free_dynamic_bufs(ps_dec); + ih264d_free_static_bufs(dec_hdl); + return IV_SUCCESS; +} +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_reset */ +/* */ +/* Description : */ +/* */ +/* Inputs :iv_obj_t decoder handle */ +/* :pv_api_ip pointer to input structure */ +/* :pv_api_op pointer to output structure */ +/* Globals : <Does it use any global variables?> */ +/* Outputs : */ +/* Returns : void */ +/* */ +/* Issues : none */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 22 10 2008 100356 Draft */ +/* */ +/*****************************************************************************/ +WORD32 ih264d_reset(iv_obj_t *dec_hdl, void *pv_api_ip, void *pv_api_op) +{ + dec_struct_t * ps_dec; + ivd_ctl_reset_op_t *ps_ctl_op = (ivd_ctl_reset_op_t *)pv_api_op; + UNUSED(pv_api_ip); + ps_ctl_op->u4_error_code = 0; + + ps_dec = (dec_struct_t *)(dec_hdl->pv_codec_handle); + + if(ps_dec != NULL) + { + ih264d_init_decoder(ps_dec); + } + else + { + H264_DEC_DEBUG_PRINT( + "\nReset called without Initializing the decoder\n"); + ps_ctl_op->u4_error_code = ERROR_INIT_NOT_DONE; + } + + return IV_SUCCESS; +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_ctl */ +/* */ +/* Description : */ +/* */ +/* Inputs :iv_obj_t decoder handle */ +/* :pv_api_ip pointer to input structure */ +/* :pv_api_op pointer to output structure */ +/* Outputs : */ +/* Returns : void */ +/* */ +/* Issues : none */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 22 10 2008 100356 Draft */ +/* */ +/*****************************************************************************/ +WORD32 ih264d_ctl(iv_obj_t *dec_hdl, void *pv_api_ip, void *pv_api_op) +{ + ivd_ctl_set_config_ip_t *ps_ctl_ip; + ivd_ctl_set_config_op_t *ps_ctl_op; + WORD32 ret = IV_SUCCESS; + UWORD32 subcommand; + dec_struct_t *ps_dec = dec_hdl->pv_codec_handle; + + if(ps_dec->init_done != 1) + { + //Return proper Error Code + return IV_FAIL; + } + ps_ctl_ip = (ivd_ctl_set_config_ip_t*)pv_api_ip; + ps_ctl_op = (ivd_ctl_set_config_op_t*)pv_api_op; + ps_ctl_op->u4_error_code = 0; + subcommand = ps_ctl_ip->e_sub_cmd; + + switch(subcommand) + { + case IVD_CMD_CTL_GETPARAMS: + ret = ih264d_get_status(dec_hdl, (void *)pv_api_ip, + (void *)pv_api_op); + break; + case IVD_CMD_CTL_SETPARAMS: + ret = ih264d_set_params(dec_hdl, (void *)pv_api_ip, + (void *)pv_api_op); + break; + case IVD_CMD_CTL_RESET: + ret = ih264d_reset(dec_hdl, (void *)pv_api_ip, (void *)pv_api_op); + break; + case IVD_CMD_CTL_SETDEFAULT: + ret = ih264d_set_default_params(dec_hdl, (void *)pv_api_ip, + (void *)pv_api_op); + break; + case IVD_CMD_CTL_FLUSH: + ret = ih264d_set_flush_mode(dec_hdl, (void *)pv_api_ip, + (void *)pv_api_op); + break; + case IVD_CMD_CTL_GETBUFINFO: + ret = ih264d_get_buf_info(dec_hdl, (void *)pv_api_ip, + (void *)pv_api_op); + break; + case IVD_CMD_CTL_GETVERSION: + ret = ih264d_get_version(dec_hdl, (void *)pv_api_ip, + (void *)pv_api_op); + break; + case IH264D_CMD_CTL_DEGRADE: + ret = ih264d_set_degrade(dec_hdl, (void *)pv_api_ip, + (void *)pv_api_op); + break; + + case IH264D_CMD_CTL_SET_NUM_CORES: + ret = ih264d_set_num_cores(dec_hdl, (void *)pv_api_ip, + (void *)pv_api_op); + break; + case IH264D_CMD_CTL_GET_BUFFER_DIMENSIONS: + ret = ih264d_get_frame_dimensions(dec_hdl, (void *)pv_api_ip, + (void *)pv_api_op); + break; + case IH264D_CMD_CTL_GET_VUI_PARAMS: + ret = ih264d_get_vui_params(dec_hdl, (void *)pv_api_ip, + (void *)pv_api_op); + break; + case IH264D_CMD_CTL_GET_SEI_MDCV_PARAMS: + ret = ih264d_get_sei_mdcv_params(dec_hdl, (void *)pv_api_ip, + (void *)pv_api_op); + break; + case IH264D_CMD_CTL_GET_SEI_CLL_PARAMS: + ret = ih264d_get_sei_cll_params(dec_hdl, (void *)pv_api_ip, + (void *)pv_api_op); + break; + case IH264D_CMD_CTL_GET_SEI_AVE_PARAMS: + ret = ih264d_get_sei_ave_params(dec_hdl, (void *)pv_api_ip, + (void *)pv_api_op); + break; + case IH264D_CMD_CTL_GET_SEI_CCV_PARAMS: + ret = ih264d_get_sei_ccv_params(dec_hdl, (void *)pv_api_ip, + (void *)pv_api_op); + break; + case IH264D_CMD_CTL_SET_PROCESSOR: + ret = ih264d_set_processor(dec_hdl, (void *)pv_api_ip, + (void *)pv_api_op); + break; + default: + H264_DEC_DEBUG_PRINT("\ndo nothing\n") + ; + break; + } + + return ret; +} +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_rel_display_frame */ +/* */ +/* Description : */ +/* */ +/* Inputs :iv_obj_t decoder handle */ +/* :pv_api_ip pointer to input structure */ +/* :pv_api_op pointer to output structure */ +/* Outputs : */ +/* Returns : void */ +/* */ +/* Issues : none */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 22 10 2008 100356 Draft */ +/* */ +/*****************************************************************************/ +WORD32 ih264d_rel_display_frame(iv_obj_t *dec_hdl, + void *pv_api_ip, + void *pv_api_op) +{ + + ivd_rel_display_frame_ip_t *ps_rel_ip; + ivd_rel_display_frame_op_t *ps_rel_op; + UWORD32 buf_released = 0; + + UWORD32 u4_ts = 0; + dec_struct_t *ps_dec = dec_hdl->pv_codec_handle; + + ps_rel_ip = (ivd_rel_display_frame_ip_t *)pv_api_ip; + ps_rel_op = (ivd_rel_display_frame_op_t *)pv_api_op; + ps_rel_op->u4_error_code = 0; + u4_ts = ps_rel_ip->u4_disp_buf_id; + + if(0 == ps_dec->u4_share_disp_buf) + { + ps_dec->u4_disp_buf_mapping[u4_ts] = 0; + ps_dec->u4_disp_buf_to_be_freed[u4_ts] = 0; + return IV_SUCCESS; + } + + if(ps_dec->pv_pic_buf_mgr != NULL) + { + if(1 == ps_dec->u4_disp_buf_mapping[u4_ts]) + { + ih264_buf_mgr_release((buf_mgr_t *)ps_dec->pv_pic_buf_mgr, + ps_rel_ip->u4_disp_buf_id, + BUF_MGR_IO); + ps_dec->u4_disp_buf_mapping[u4_ts] = 0; + buf_released = 1; + } + } + + if((1 == ps_dec->u4_share_disp_buf) && (0 == buf_released)) + ps_dec->u4_disp_buf_to_be_freed[u4_ts] = 1; + + return IV_SUCCESS; +} + +/** + ******************************************************************************* + * + * @brief + * Sets degrade params + * + * @par Description: + * Sets degrade params. + * Refer to ih264d_ctl_degrade_ip_t definition for details + * + * @param[in] ps_codec_obj + * Pointer to codec object at API level + * + * @param[in] pv_api_ip + * Pointer to input argument structure + * + * @param[out] pv_api_op + * Pointer to output argument structure + * + * @returns Status + * + * @remarks + * + * + ******************************************************************************* + */ + +WORD32 ih264d_set_degrade(iv_obj_t *ps_codec_obj, + void *pv_api_ip, + void *pv_api_op) +{ + ih264d_ctl_degrade_ip_t *ps_ip; + ih264d_ctl_degrade_op_t *ps_op; + dec_struct_t *ps_codec = (dec_struct_t *)ps_codec_obj->pv_codec_handle; + + ps_ip = (ih264d_ctl_degrade_ip_t *)pv_api_ip; + ps_op = (ih264d_ctl_degrade_op_t *)pv_api_op; + + ps_codec->i4_degrade_type = ps_ip->i4_degrade_type; + ps_codec->i4_nondegrade_interval = ps_ip->i4_nondegrade_interval; + ps_codec->i4_degrade_pics = ps_ip->i4_degrade_pics; + + ps_op->u4_error_code = 0; + ps_codec->i4_degrade_pic_cnt = 0; + + return IV_SUCCESS; +} + +WORD32 ih264d_get_frame_dimensions(iv_obj_t *dec_hdl, + void *pv_api_ip, + void *pv_api_op) +{ + ih264d_ctl_get_frame_dimensions_ip_t *ps_ip; + ih264d_ctl_get_frame_dimensions_op_t *ps_op; + dec_struct_t *ps_dec = dec_hdl->pv_codec_handle; + UWORD32 disp_wd, disp_ht, buffer_wd, buffer_ht, x_offset, y_offset; + + ps_ip = (ih264d_ctl_get_frame_dimensions_ip_t *)pv_api_ip; + + ps_op = (ih264d_ctl_get_frame_dimensions_op_t *)pv_api_op; + UNUSED(ps_ip); + if((NULL != ps_dec->ps_cur_sps) && (1 == (ps_dec->ps_cur_sps->u1_is_valid))) + { + disp_wd = ps_dec->u2_disp_width; + disp_ht = ps_dec->u2_disp_height; + + if(0 == ps_dec->u4_share_disp_buf) + { + buffer_wd = disp_wd; + buffer_ht = disp_ht; + } + else + { + buffer_wd = ps_dec->u2_frm_wd_y; + buffer_ht = ps_dec->u2_frm_ht_y; + } + } + else + { + disp_wd = 0; + disp_ht = 0; + + if(0 == ps_dec->u4_share_disp_buf) + { + buffer_wd = disp_wd; + buffer_ht = disp_ht; + } + else + { + buffer_wd = ALIGN16(disp_wd) + (PAD_LEN_Y_H << 1); + buffer_ht = ALIGN16(disp_ht) + (PAD_LEN_Y_V << 2); + } + } + if(ps_dec->u4_app_disp_width > buffer_wd) + buffer_wd = ps_dec->u4_app_disp_width; + + if(0 == ps_dec->u4_share_disp_buf) + { + x_offset = 0; + y_offset = 0; + } + else + { + y_offset = (PAD_LEN_Y_V << 1); + x_offset = PAD_LEN_Y_H; + + if((NULL != ps_dec->ps_sps) && (1 == (ps_dec->ps_sps->u1_is_valid)) + && (0 != ps_dec->u2_crop_offset_y)) + { + y_offset += ps_dec->u2_crop_offset_y / ps_dec->u2_frm_wd_y; + x_offset += ps_dec->u2_crop_offset_y % ps_dec->u2_frm_wd_y; + } + } + + ps_op->u4_disp_wd[0] = disp_wd; + ps_op->u4_disp_ht[0] = disp_ht; + ps_op->u4_buffer_wd[0] = buffer_wd; + ps_op->u4_buffer_ht[0] = buffer_ht; + ps_op->u4_x_offset[0] = x_offset; + ps_op->u4_y_offset[0] = y_offset; + + ps_op->u4_disp_wd[1] = ps_op->u4_disp_wd[2] = ((ps_op->u4_disp_wd[0] + 1) + >> 1); + ps_op->u4_disp_ht[1] = ps_op->u4_disp_ht[2] = ((ps_op->u4_disp_ht[0] + 1) + >> 1); + ps_op->u4_buffer_wd[1] = ps_op->u4_buffer_wd[2] = (ps_op->u4_buffer_wd[0] + >> 1); + ps_op->u4_buffer_ht[1] = ps_op->u4_buffer_ht[2] = (ps_op->u4_buffer_ht[0] + >> 1); + ps_op->u4_x_offset[1] = ps_op->u4_x_offset[2] = + (ps_op->u4_x_offset[0] >> 1); + ps_op->u4_y_offset[1] = ps_op->u4_y_offset[2] = + (ps_op->u4_y_offset[0] >> 1); + + if((ps_dec->u1_chroma_format == IV_YUV_420SP_UV) + || (ps_dec->u1_chroma_format == IV_YUV_420SP_VU)) + { + ps_op->u4_disp_wd[2] = 0; + ps_op->u4_disp_ht[2] = 0; + ps_op->u4_buffer_wd[2] = 0; + ps_op->u4_buffer_ht[2] = 0; + ps_op->u4_x_offset[2] = 0; + ps_op->u4_y_offset[2] = 0; + + ps_op->u4_disp_wd[1] <<= 1; + ps_op->u4_buffer_wd[1] <<= 1; + ps_op->u4_x_offset[1] <<= 1; + } + + return IV_SUCCESS; + +} + +WORD32 ih264d_get_vui_params(iv_obj_t *dec_hdl, + void *pv_api_ip, + void *pv_api_op) +{ + ih264d_ctl_get_vui_params_ip_t *ps_ip; + ih264d_ctl_get_vui_params_op_t *ps_op; + dec_struct_t *ps_dec = dec_hdl->pv_codec_handle; + dec_seq_params_t *ps_sps; + vui_t *ps_vui; + WORD32 i; + UWORD32 u4_size; + + ps_ip = (ih264d_ctl_get_vui_params_ip_t *)pv_api_ip; + ps_op = (ih264d_ctl_get_vui_params_op_t *)pv_api_op; + UNUSED(ps_ip); + + u4_size = ps_op->u4_size; + memset(ps_op, 0, sizeof(ih264d_ctl_get_vui_params_op_t)); + ps_op->u4_size = u4_size; + + if(NULL == ps_dec->ps_cur_sps) + { + ps_op->u4_error_code = ERROR_VUI_PARAMS_NOT_FOUND; + return IV_FAIL; + } + + ps_sps = ps_dec->ps_cur_sps; + if((0 == ps_sps->u1_is_valid) + || (0 == ps_sps->u1_vui_parameters_present_flag)) + { + ps_op->u4_error_code = ERROR_VUI_PARAMS_NOT_FOUND; + return IV_FAIL; + } + + ps_vui = &ps_sps->s_vui; + + ps_op->u1_aspect_ratio_idc = ps_vui->u1_aspect_ratio_idc; + ps_op->u2_sar_width = ps_vui->u2_sar_width; + ps_op->u2_sar_height = ps_vui->u2_sar_height; + ps_op->u1_overscan_appropriate_flag = ps_vui->u1_overscan_appropriate_flag; + ps_op->u1_video_format = ps_vui->u1_video_format; + ps_op->u1_video_full_range_flag = ps_vui->u1_video_full_range_flag; + ps_op->u1_colour_primaries = ps_vui->u1_colour_primaries; + ps_op->u1_tfr_chars = ps_vui->u1_tfr_chars; + ps_op->u1_matrix_coeffs = ps_vui->u1_matrix_coeffs; + ps_op->u1_cr_top_field = ps_vui->u1_cr_top_field; + ps_op->u1_cr_bottom_field = ps_vui->u1_cr_bottom_field; + ps_op->u4_num_units_in_tick = ps_vui->u4_num_units_in_tick; + ps_op->u4_time_scale = ps_vui->u4_time_scale; + ps_op->u1_fixed_frame_rate_flag = ps_vui->u1_fixed_frame_rate_flag; + ps_op->u1_nal_hrd_params_present = ps_vui->u1_nal_hrd_params_present; + ps_op->u1_vcl_hrd_params_present = ps_vui->u1_vcl_hrd_params_present; + ps_op->u1_low_delay_hrd_flag = ps_vui->u1_low_delay_hrd_flag; + ps_op->u1_pic_struct_present_flag = ps_vui->u1_pic_struct_present_flag; + ps_op->u1_bitstream_restriction_flag = ps_vui->u1_bitstream_restriction_flag; + ps_op->u1_mv_over_pic_boundaries_flag = ps_vui->u1_mv_over_pic_boundaries_flag; + ps_op->u4_max_bytes_per_pic_denom = ps_vui->u4_max_bytes_per_pic_denom; + ps_op->u4_max_bits_per_mb_denom = ps_vui->u4_max_bits_per_mb_denom; + ps_op->u4_log2_max_mv_length_horz = ps_vui->u4_log2_max_mv_length_horz; + ps_op->u4_log2_max_mv_length_vert = ps_vui->u4_log2_max_mv_length_vert; + ps_op->u4_num_reorder_frames = ps_vui->u4_num_reorder_frames; + ps_op->u4_max_dec_frame_buffering = ps_vui->u4_max_dec_frame_buffering; + + return IV_SUCCESS; +} +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_get_sei_mdcv_params */ +/* */ +/* Description : This function populates SEI mdcv message in */ +/* output structure */ +/* Inputs : iv_obj_t decoder handle */ +/* : pv_api_ip pointer to input structure */ +/* : pv_api_op pointer to output structure */ +/* Outputs : */ +/* Returns : returns 0; 1 with error code when MDCV is not present */ +/* */ +/* Issues : none */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* */ +/* */ +/*****************************************************************************/ +WORD32 ih264d_get_sei_mdcv_params(iv_obj_t *dec_hdl, + void *pv_api_ip, + void *pv_api_op) +{ + ih264d_ctl_get_sei_mdcv_params_ip_t *ps_ip; + ih264d_ctl_get_sei_mdcv_params_op_t *ps_op; + dec_struct_t *ps_dec = dec_hdl->pv_codec_handle; + sei_mdcv_params_t *ps_sei_mdcv; + WORD32 i4_count; + + ps_ip = (ih264d_ctl_get_sei_mdcv_params_ip_t *)pv_api_ip; + ps_op = (ih264d_ctl_get_sei_mdcv_params_op_t *)pv_api_op; + UNUSED(ps_ip); + + if(0 == ps_dec->s_sei_export.u1_sei_mdcv_params_present_flag) + { + ps_op->u4_error_code = ERROR_SEI_MDCV_PARAMS_NOT_FOUND; + return IV_FAIL; + } + + ps_sei_mdcv = &ps_dec->s_sei_export.s_sei_mdcv_params; + + for(i4_count = 0; i4_count < NUM_SEI_MDCV_PRIMARIES; i4_count++) + { + ps_op->au2_display_primaries_x[i4_count] = ps_sei_mdcv->au2_display_primaries_x[i4_count]; + ps_op->au2_display_primaries_y[i4_count] = ps_sei_mdcv->au2_display_primaries_y[i4_count]; + } + + ps_op->u2_white_point_x = ps_sei_mdcv->u2_white_point_x; + ps_op->u2_white_point_y = ps_sei_mdcv->u2_white_point_y; + ps_op->u4_max_display_mastering_luminance = ps_sei_mdcv->u4_max_display_mastering_luminance; + ps_op->u4_min_display_mastering_luminance = ps_sei_mdcv->u4_min_display_mastering_luminance; + + return IV_SUCCESS; +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_get_sei_cll_params */ +/* */ +/* Description : This function populates SEI cll message in */ +/* output structure */ +/* Inputs : iv_obj_t decoder handle */ +/* : pv_api_ip pointer to input structure */ +/* : pv_api_op pointer to output structure */ +/* Outputs : */ +/* Returns : returns 0; 1 with error code when CLL is not present */ +/* */ +/* Issues : none */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* */ +/* */ +/*****************************************************************************/ +WORD32 ih264d_get_sei_cll_params(iv_obj_t *dec_hdl, + void *pv_api_ip, + void *pv_api_op) +{ + ih264d_ctl_get_sei_cll_params_ip_t *ps_ip; + ih264d_ctl_get_sei_cll_params_op_t *ps_op; + dec_struct_t *ps_dec = dec_hdl->pv_codec_handle; + sei_cll_params_t *ps_sei_cll; + + ps_ip = (ih264d_ctl_get_sei_cll_params_ip_t *)pv_api_ip; + ps_op = (ih264d_ctl_get_sei_cll_params_op_t *)pv_api_op; + UNUSED(ps_ip); + + if(0 == ps_dec->s_sei_export.u1_sei_cll_params_present_flag) + { + ps_op->u4_error_code = ERROR_SEI_CLL_PARAMS_NOT_FOUND; + return IV_FAIL; + } + + ps_sei_cll = &ps_dec->s_sei_export.s_sei_cll_params; + + ps_op->u2_max_content_light_level = ps_sei_cll->u2_max_content_light_level; + ps_op->u2_max_pic_average_light_level = ps_sei_cll->u2_max_pic_average_light_level; + + return IV_SUCCESS; +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_get_sei_ave_params */ +/* */ +/* Description : This function populates SEI ave message in */ +/* output structure */ +/* Inputs : iv_obj_t decoder handle */ +/* : pv_api_ip pointer to input structure */ +/* : pv_api_op pointer to output structure */ +/* Outputs : */ +/* Returns : returns 0; 1 with error code when AVE is not present */ +/* */ +/* Issues : none */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* */ +/* */ +/*****************************************************************************/ +WORD32 ih264d_get_sei_ave_params(iv_obj_t *dec_hdl, + void *pv_api_ip, + void *pv_api_op) +{ + ih264d_ctl_get_sei_ave_params_ip_t *ps_ip; + ih264d_ctl_get_sei_ave_params_op_t *ps_op; + dec_struct_t *ps_dec = dec_hdl->pv_codec_handle; + sei_ave_params_t *ps_sei_ave; + + ps_ip = (ih264d_ctl_get_sei_ave_params_ip_t *)pv_api_ip; + ps_op = (ih264d_ctl_get_sei_ave_params_op_t *)pv_api_op; + UNUSED(ps_ip); + + if(0 == ps_dec->s_sei_export.u1_sei_ave_params_present_flag) + { + ps_op->u4_error_code = ERROR_SEI_AVE_PARAMS_NOT_FOUND; + return IV_FAIL; + } + + ps_sei_ave = &ps_dec->s_sei_export.s_sei_ave_params; + + ps_op->u4_ambient_illuminance = ps_sei_ave->u4_ambient_illuminance; + ps_op->u2_ambient_light_x = ps_sei_ave->u2_ambient_light_x; + ps_op->u2_ambient_light_y = ps_sei_ave->u2_ambient_light_y; + + return IV_SUCCESS; +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_get_sei_ccv_params */ +/* */ +/* Description : This function populates SEI mdcv message in */ +/* output structure */ +/* Inputs : iv_obj_t decoder handle */ +/* : pv_api_ip pointer to input structure */ +/* : pv_api_op pointer to output structure */ +/* Outputs : */ +/* Returns : returns 0; 1 with error code when CCV is not present */ +/* */ +/* Issues : none */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* */ +/* */ +/*****************************************************************************/ +WORD32 ih264d_get_sei_ccv_params(iv_obj_t *dec_hdl, + void *pv_api_ip, + void *pv_api_op) +{ + ih264d_ctl_get_sei_ccv_params_ip_t *ps_ip; + ih264d_ctl_get_sei_ccv_params_op_t *ps_op; + dec_struct_t *ps_dec = dec_hdl->pv_codec_handle; + sei_ccv_params_t *ps_sei_ccv; + WORD32 i4_count; + + ps_ip = (ih264d_ctl_get_sei_ccv_params_ip_t *)pv_api_ip; + ps_op = (ih264d_ctl_get_sei_ccv_params_op_t *)pv_api_op; + UNUSED(ps_ip); + + if(0 == ps_dec->s_sei_export.u1_sei_ccv_params_present_flag) + { + ps_op->u4_error_code = ERROR_SEI_CCV_PARAMS_NOT_FOUND; + return IV_FAIL; + } + + ps_sei_ccv = &ps_dec->s_sei_export.s_sei_ccv_params; + + ps_op->u1_ccv_cancel_flag = ps_sei_ccv->u1_ccv_cancel_flag; + + if(0 == ps_op->u1_ccv_cancel_flag) + { + ps_op->u1_ccv_persistence_flag = ps_sei_ccv->u1_ccv_persistence_flag; + ps_op->u1_ccv_primaries_present_flag = ps_sei_ccv->u1_ccv_primaries_present_flag; + ps_op->u1_ccv_min_luminance_value_present_flag = + ps_sei_ccv->u1_ccv_min_luminance_value_present_flag; + ps_op->u1_ccv_max_luminance_value_present_flag = + ps_sei_ccv->u1_ccv_max_luminance_value_present_flag; + ps_op->u1_ccv_avg_luminance_value_present_flag = + ps_sei_ccv->u1_ccv_avg_luminance_value_present_flag; + ps_op->u1_ccv_reserved_zero_2bits = ps_sei_ccv->u1_ccv_reserved_zero_2bits; + + if(1 == ps_sei_ccv->u1_ccv_primaries_present_flag) + { + for(i4_count = 0; i4_count < NUM_SEI_CCV_PRIMARIES; i4_count++) + { + ps_op->ai4_ccv_primaries_x[i4_count] = ps_sei_ccv->ai4_ccv_primaries_x[i4_count]; + ps_op->ai4_ccv_primaries_y[i4_count] = ps_sei_ccv->ai4_ccv_primaries_y[i4_count]; + } + } + + if(1 == ps_sei_ccv->u1_ccv_min_luminance_value_present_flag) + { + ps_op->u4_ccv_min_luminance_value = ps_sei_ccv->u4_ccv_min_luminance_value; + } + if(1 == ps_sei_ccv->u1_ccv_max_luminance_value_present_flag) + { + ps_op->u4_ccv_max_luminance_value = ps_sei_ccv->u4_ccv_max_luminance_value; + } + if(1 == ps_sei_ccv->u1_ccv_avg_luminance_value_present_flag) + { + ps_op->u4_ccv_avg_luminance_value = ps_sei_ccv->u4_ccv_avg_luminance_value; + } + } + + return IV_SUCCESS; +} + +WORD32 ih264d_set_num_cores(iv_obj_t *dec_hdl, void *pv_api_ip, void *pv_api_op) +{ + ih264d_ctl_set_num_cores_ip_t *ps_ip; + ih264d_ctl_set_num_cores_op_t *ps_op; + dec_struct_t *ps_dec = dec_hdl->pv_codec_handle; + + ps_ip = (ih264d_ctl_set_num_cores_ip_t *)pv_api_ip; + ps_op = (ih264d_ctl_set_num_cores_op_t *)pv_api_op; + ps_op->u4_error_code = 0; + ps_dec->u4_num_cores = ps_ip->u4_num_cores; + if(ps_dec->u4_num_cores == 1) + { + ps_dec->u1_separate_parse = 0; + } + else + { + ps_dec->u1_separate_parse = 1; + } + + /*using only upto three threads currently*/ + if(ps_dec->u4_num_cores > 3) + ps_dec->u4_num_cores = 3; + + return IV_SUCCESS; +} + +void ih264d_fill_output_struct_from_context(dec_struct_t *ps_dec, + ivd_video_decode_op_t *ps_dec_op) +{ + if((ps_dec_op->u4_error_code & 0xff) + != ERROR_DYNAMIC_RESOLUTION_NOT_SUPPORTED) + { + ps_dec_op->u4_pic_wd = (UWORD32)ps_dec->u2_disp_width; + ps_dec_op->u4_pic_ht = (UWORD32)ps_dec->u2_disp_height; + } + ps_dec_op->i4_reorder_depth = ps_dec->i4_reorder_depth; + ps_dec_op->i4_display_index = ps_dec->i4_display_index; + ps_dec_op->e_pic_type = ps_dec->i4_frametype; + + ps_dec_op->u4_new_seq = 0; + ps_dec_op->u4_output_present = ps_dec->u4_output_present; + ps_dec_op->u4_progressive_frame_flag = + ps_dec->s_disp_op.u4_progressive_frame_flag; + + ps_dec_op->u4_is_ref_flag = 1; + if(ps_dec_op->u4_frame_decoded_flag) + { + if(ps_dec->ps_cur_slice->u1_nal_ref_idc == 0) + ps_dec_op->u4_is_ref_flag = 0; + } + + ps_dec_op->e_output_format = ps_dec->s_disp_op.e_output_format; + ps_dec_op->s_disp_frm_buf = ps_dec->s_disp_op.s_disp_frm_buf; + ps_dec_op->e4_fld_type = ps_dec->s_disp_op.e4_fld_type; + ps_dec_op->u4_ts = ps_dec->s_disp_op.u4_ts; + ps_dec_op->u4_disp_buf_id = ps_dec->s_disp_op.u4_disp_buf_id; + + ih264d_export_sei_params(&ps_dec_op->s_sei_decode_op, ps_dec); +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_api_function */ +/* */ +/* Description : */ +/* */ +/* Inputs :iv_obj_t decoder handle */ +/* :pv_api_ip pointer to input structure */ +/* :pv_api_op pointer to output structure */ +/* Outputs : */ +/* Returns : void */ +/* */ +/* Issues : none */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 22 10 2008 100356 Draft */ +/* */ +/*****************************************************************************/ +IV_API_CALL_STATUS_T ih264d_api_function(iv_obj_t *dec_hdl, + void *pv_api_ip, + void *pv_api_op) +{ + UWORD32 command; + UWORD32 *pu2_ptr_cmd; + UWORD32 u4_api_ret; + IV_API_CALL_STATUS_T e_status; + e_status = api_check_struct_sanity(dec_hdl, pv_api_ip, pv_api_op); + + if(e_status != IV_SUCCESS) + { + UWORD32 *ptr_err; + + ptr_err = (UWORD32 *)pv_api_op; + UNUSED(ptr_err); + H264_DEC_DEBUG_PRINT("error code = %d\n", *(ptr_err + 1)); + return IV_FAIL; + } + + pu2_ptr_cmd = (UWORD32 *)pv_api_ip; + pu2_ptr_cmd++; + + command = *pu2_ptr_cmd; +// H264_DEC_DEBUG_PRINT("inside lib = %d\n",command); + switch(command) + { + + case IVD_CMD_CREATE: + u4_api_ret = ih264d_create(dec_hdl, (void *)pv_api_ip, + (void *)pv_api_op); + break; + case IVD_CMD_DELETE: + u4_api_ret = ih264d_delete(dec_hdl, (void *)pv_api_ip, + (void *)pv_api_op); + break; + + case IVD_CMD_VIDEO_DECODE: + u4_api_ret = ih264d_video_decode(dec_hdl, (void *)pv_api_ip, + (void *)pv_api_op); + break; + + case IVD_CMD_GET_DISPLAY_FRAME: + u4_api_ret = ih264d_get_display_frame(dec_hdl, (void *)pv_api_ip, + (void *)pv_api_op); + + break; + + case IVD_CMD_SET_DISPLAY_FRAME: + u4_api_ret = ih264d_set_display_frame(dec_hdl, (void *)pv_api_ip, + (void *)pv_api_op); + + break; + + case IVD_CMD_REL_DISPLAY_FRAME: + u4_api_ret = ih264d_rel_display_frame(dec_hdl, (void *)pv_api_ip, + (void *)pv_api_op); + break; + + case IVD_CMD_VIDEO_CTL: + u4_api_ret = ih264d_ctl(dec_hdl, (void *)pv_api_ip, + (void *)pv_api_op); + break; + default: + u4_api_ret = IV_FAIL; + break; + } + + return u4_api_ret; +} diff --git a/dependencies/ih264d/decoder/ih264d_bitstrm.c b/dependencies/ih264d/decoder/ih264d_bitstrm.c new file mode 100644 index 00000000..fd41bc6c --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_bitstrm.c @@ -0,0 +1,181 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ + +/*! + ************************************************************************** + * \file ih264d_bitstrm.c + * + * \brief + * Bitstream parsing routines + * + * \date + * 20/11/2002 + * + * \author AI + ************************************************************************** + */ + +#include <stdlib.h> +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264d_bitstrm.h" +#include "ih264d_error_handler.h" + +#include "ih264d_debug.h" +#include "ih264d_tables.h" +#include "ih264d_structs.h" + +/*! + ************************************************************************** + * \if Function name : ih264d_get_bit_h264 \endif + * + * \brief + * Read one bit from the bitstream. + * + * This is a Bitstream processing function. It reads the + * bit currently pointed by the bit pointer in the + * buffer and advances the pointer by one. It returns + * the bit (0 or 1) in the form of an unsigned integer. + * + * \return + * Returns the next bit (0 or 1) in the bitstream. + * + ************************************************************************** + */ +UWORD8 ih264d_get_bit_h264(dec_bit_stream_t *ps_stream) +{ + UWORD32 u4_code; + + GETBIT(u4_code, ps_stream->u4_ofst, ps_stream->pu4_buffer); + return (u4_code); +} + +/*! + ************************************************************************** + * \if Function name : ih264d_get_bits_h264 \endif + * + * \brief + * Read specified number of bits from the bitstream. + * + * This is a Bitstream processing function. It reads the + * number specified number of bits from the current bit + * position and advances the bit and byte pointers + * appropriately. + * + * \return + * An unsigned 32 bit integer with its least significant bits + * containing the bits in order of their occurence in the bitstream. + * + ************************************************************************** + */ + +UWORD32 ih264d_get_bits_h264(dec_bit_stream_t *ps_bitstrm, UWORD32 u4_num_bits) +{ + UWORD32 u4_code = 0; + if(u4_num_bits) + GETBITS(u4_code, ps_bitstrm->u4_ofst, ps_bitstrm->pu4_buffer, u4_num_bits); + return (u4_code); +} + +/*! + ************************************************************************** + * \if Function name : ih264d_next_bits_h264 \endif + * + * \brief + * Peek specified number of bits from the bitstream. + * + * This is a Bitstream processing function. It gets the + * specified number of bits from the buffer without + * altering the current pointers. It is equivalent to + * next_bits() function in the standard. + * + * \return + * An unsigned 32 bit integer with its least significant bits + * containing the bits in order of their occurence in the bitstream. + ************************************************************************** + */ +UWORD32 ih264d_next_bits_h264(dec_bit_stream_t *ps_bitstrm, UWORD32 u4_num_bits) +{ + UWORD32 u4_word_off = (ps_bitstrm->u4_ofst >> 5); + UWORD32 u4_bit_off = ps_bitstrm->u4_ofst & 0x1F; + UWORD32 *pu4_bitstream = ps_bitstrm->pu4_buffer; + UWORD32 u4_bits = pu4_bitstream[u4_word_off++] << u4_bit_off; + + /*************************************************************************/ + /* Test if number of bits to be read exceeds the number of bits in the */ + /* current word. If yes, read from the next word of the buffer, The bits */ + /* from both the words are concatenated to get next 32 bits in 'u4_bits' */ + /*************************************************************************/ + if(u4_bit_off > (INT_IN_BITS - u4_num_bits)) + u4_bits |= (pu4_bitstream[u4_word_off] >> (INT_IN_BITS - u4_bit_off)); + + return ((u4_bits >> (INT_IN_BITS - u4_num_bits))); +} + +/*! + ************************************************************************** + * \if Function name : ih264d_flush_bits_h264 \endif + * + * \brief + * Flush specified number of bits from the bitstream. + * + * This function flushes the specified number of bits (marks + * as read) from the buffer. + * + * \return + * A 8 bit unsigned integer with value + * '1' on successful flush + * '0' on failure. + * + ************************************************************************** + */ +WORD32 ih264d_flush_bits_h264(dec_bit_stream_t *ps_bitstrm, WORD32 u4_num_bits) +{ + ps_bitstrm->u4_ofst += u4_num_bits; + + if(ps_bitstrm->u4_ofst > ps_bitstrm->u4_max_ofst) + { + return ERROR_EOB_FLUSHBITS_T; + } + return OK; +} + +/*! + ************************************************************************** + * \if Function name : ih264d_check_byte_aligned \endif + * + * \brief + * Checks whether the bit ps_bitstrm u4_ofst is at byte boundary. + * + * \param ps_bitstrm : Pointer to bitstream + * + * \return + * Returns 1 if bit ps_bitstrm u4_ofst is at byte alligned position else zero. + ************************************************************************** + */ + +UWORD8 ih264d_check_byte_aligned(dec_bit_stream_t * ps_bitstrm) +{ + if(ps_bitstrm->u4_ofst & 0x07) + return (0); + else + return (1); +} diff --git a/dependencies/ih264d/decoder/ih264d_bitstrm.h b/dependencies/ih264d/decoder/ih264d_bitstrm.h new file mode 100644 index 00000000..8bb06fb5 --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_bitstrm.h @@ -0,0 +1,198 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ + +#ifndef _IH264D_BITSTRM_H_ +#define _IH264D_BITSTRM_H_ +/*! + ************************************************************************* + * \file ih264d_bitstrm.h + * + * \brief + * Contains all the declarations of bitstream reading routines + * + * \date + * 20/11/2002 + * + * \author AI + ************************************************************************* + */ + +/* Includes */ +#include <stdio.h> +#include <stdlib.h> +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" + +#define INT_IN_BYTES 4 +#define INT_IN_BITS 32 + +/* Based on level 1.2 of baseline profile */ +/* 396[MAX_FS] * 128 * 1.5 [ChromaFormatParameter] / sizeof(UWORD32) + i.e 396 * 128 * 1.5 / 4 = 19008 */ +/* Based on level 3 of main profile */ +/* 1620[MAX_FS] * 128 * 1.5 [ChromaFormatParameter] / sizeof(UWORD32) + i.e 1620 * 128 * 1.5 / 4= 77760 */ +#define SIZE_OF_BUFFER 77760 + +/* Structure for the ps_bitstrm */ +typedef struct +{ + UWORD32 u4_ofst; /* Offset in the buffer for the current bit */ + UWORD32 *pu4_buffer; /* Bitstream Buffer */ + UWORD32 u4_max_ofst; /* points to first bit beyond the buffer */ + void * pv_codec_handle; /* For Error Handling */ +} dec_bit_stream_t; + +/* To read the next bit */ +UWORD8 ih264d_get_bit_h264(dec_bit_stream_t *); + +/* To read the next specified number of bits */ +UWORD32 ih264d_get_bits_h264(dec_bit_stream_t *, UWORD32); + +/* To see the next specified number of bits */ +UWORD32 ih264d_next_bits_h264(dec_bit_stream_t *, UWORD32); + +/* To flush a specified number of bits*/ +WORD32 ih264d_flush_bits_h264(dec_bit_stream_t *, WORD32); + +/*! + ************************************************************************** + * \if Function name : MoreRbspData \endif + * + * \brief + * Determines whether there is more data in RBSP or not. + * + * \param ps_bitstrm : Pointer to bitstream + * + * \return + * Returns 1 if there is more data in RBSP before rbsp_trailing_bits(). + * Otherwise it returns FALSE. + ************************************************************************** + */ + + +#define EXCEED_OFFSET(ps_bitstrm) \ + (ps_bitstrm->u4_ofst > ps_bitstrm->u4_max_ofst) +#define CHECK_BITS_SUFFICIENT(ps_bitstrm, bits_to_read) \ + (ps_bitstrm->u4_ofst + bits_to_read <= ps_bitstrm->u4_max_ofst) +#define MORE_RBSP_DATA(ps_bitstrm) \ + CHECK_BITS_SUFFICIENT(ps_bitstrm, 1) + +void GoToByteBoundary(dec_bit_stream_t * ps_bitstrm); +UWORD8 ih264d_check_byte_aligned(dec_bit_stream_t * ps_bitstrm); + +/*****************************************************************************/ +/* Define a macro for inlining of GETBIT: */ +/*****************************************************************************/ +#define GETBIT(u4_code, u4_offset, pu4_bitstream) \ +{ \ + UWORD32 *pu4_buf = (pu4_bitstream); \ + UWORD32 u4_word_off = ((u4_offset) >> 5); \ + UWORD32 u4_bit_off = (u4_offset) & 0x1F; \ + u4_code = pu4_buf[u4_word_off] << u4_bit_off; \ + (u4_offset)++; \ + u4_code = (u4_code >> 31); \ +} + + + +/*****************************************************************************/ +/* Define a macro for inlining of GETBITS: u4_no_bits shall not exceed 32 */ +/*****************************************************************************/ +#define GETBITS(u4_code, u4_offset, pu4_bitstream, u4_no_bits) \ +{ \ + UWORD32 *pu4_buf = (pu4_bitstream); \ + UWORD32 u4_word_off = ((u4_offset) >> 5); \ + UWORD32 u4_bit_off = (u4_offset) & 0x1F; \ + u4_code = pu4_buf[u4_word_off++] << u4_bit_off; \ + \ + if(u4_bit_off) \ + u4_code |= (pu4_buf[u4_word_off] >> (INT_IN_BITS - u4_bit_off)); \ + u4_code = u4_code >> (INT_IN_BITS - u4_no_bits); \ + (u4_offset) += u4_no_bits; \ +} \ + \ + +/*****************************************************************************/ +/* Define a macro for inlining of NEXTBITS */ +/*****************************************************************************/ +#define NEXTBITS(u4_word, u4_offset, pu4_bitstream, u4_no_bits) \ +{ \ + UWORD32 *pu4_buf = (pu4_bitstream); \ + UWORD32 u4_word_off = ((u4_offset) >> 5); \ + UWORD32 u4_bit_off = (u4_offset) & 0x1F; \ + u4_word = pu4_buf[u4_word_off++] << u4_bit_off; \ + if(u4_bit_off) \ + u4_word |= (pu4_buf[u4_word_off] >> (INT_IN_BITS - u4_bit_off)); \ + u4_word = u4_word >> (INT_IN_BITS - u4_no_bits); \ +} +/*****************************************************************************/ +/* Define a macro for inlining of NEXTBITS_32 */ +/*****************************************************************************/ +#define NEXTBITS_32(u4_word, u4_offset, pu4_bitstream) \ +{ \ + UWORD32 *pu4_buf = (pu4_bitstream); \ + UWORD32 u4_word_off = ((u4_offset) >> 5); \ + UWORD32 u4_bit_off = (u4_offset) & 0x1F; \ + \ + u4_word = pu4_buf[u4_word_off++] << u4_bit_off; \ + if(u4_bit_off) \ + u4_word |= (pu4_buf[u4_word_off] >> (INT_IN_BITS - u4_bit_off)); \ +} + + +/*****************************************************************************/ +/* Define a macro for inlining of FIND_ONE_IN_STREAM_32 */ +/*****************************************************************************/ +#define FIND_ONE_IN_STREAM_32(u4_ldz, u4_offset, pu4_bitstream) \ +{ \ + UWORD32 u4_word; \ + NEXTBITS_32(u4_word, u4_offset, pu4_bitstream); \ + u4_ldz = CLZ(u4_word); \ + (u4_offset) += (u4_ldz + 1); \ +} + +/*****************************************************************************/ +/* Define a macro for inlining of FIND_ONE_IN_STREAM_LEN */ +/*****************************************************************************/ +#define FIND_ONE_IN_STREAM_LEN(u4_ldz, u4_offset, pu4_bitstream, u4_len) \ +{ \ + UWORD32 u4_word; \ + NEXTBITS_32(u4_word, u4_offset, pu4_bitstream); \ + u4_ldz = CLZ(u4_word); \ + if(u4_ldz < u4_len) \ + (u4_offset) += (u4_ldz + 1); \ + else \ + { \ + u4_ldz = u4_len; \ + (u4_offset) += u4_ldz; \ + } \ +} + +/*****************************************************************************/ +/* Define a macro for inlining of FLUSHBITS */ +/*****************************************************************************/ +#define FLUSHBITS(u4_offset, u4_no_bits) \ +{ \ + (u4_offset) += (u4_no_bits); \ +} + +#endif /* _BITSTREAM_H_ */ diff --git a/dependencies/ih264d/decoder/ih264d_cabac.c b/dependencies/ih264d/decoder/ih264d_cabac.c new file mode 100644 index 00000000..ef1fafc0 --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_cabac.c @@ -0,0 +1,779 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/*! + *************************************************************************** + * \file ih264d_cabac.c + * + * \brief + * This file contains Binary decoding routines. + * + * \date + * 04/02/2003 + * + * \author NS + *************************************************************************** + */ +#include <string.h> +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264d_structs.h" +#include "ih264d_cabac.h" +#include "ih264d_bitstrm.h" +#include "ih264d_error_handler.h" +#include "ih264d_defs.h" +#include "ih264d_debug.h" +#include "ih264d_tables.h" +#include "ih264d_parse_cabac.h" +#include "ih264d_tables.h" + + + +/*! + ************************************************************************** + * \if Function name : ih264d_init_cabac_dec_envirnoment \endif + * + * \brief + * This function initializes CABAC decoding envirnoment. This function + * implements 9.3.3.2.3.1 of ISO/IEC14496-10. + * + * \return + * None + * + ************************************************************************** + */ +WORD32 ih264d_init_cabac_dec_envirnoment(decoding_envirnoment_t * ps_cab_env, + dec_bit_stream_t *ps_bitstrm) +{ + UWORD32 u4_code_int_val_ofst; + + ps_cab_env->u4_code_int_range = (HALF - 2) << 23; + NEXTBITS(u4_code_int_val_ofst, ps_bitstrm->u4_ofst, ps_bitstrm->pu4_buffer, + 32); + FLUSHBITS(ps_bitstrm->u4_ofst, 9) + + if(EXCEED_OFFSET(ps_bitstrm)) + return ERROR_EOB_FLUSHBITS_T; + + ps_cab_env->u4_code_int_val_ofst = u4_code_int_val_ofst; + + /*brief description of the design adopted for CABAC*/ + /*according to the standard the u4_code_int_range needs to be initialized 0x 1FE(10 bits) and + 9 bits from the bit stream need to be read and into the u4_code_int_val_ofst.As and when the + u4_code_int_range becomes less than 10 bits we need to renormalize and read from the bitstream* + + In the implemented design + initially + + range_new = range <<23 + valOffset_new = valOffset << 23 + 23 bits(read from the bit stream) + + Thus we have read 23 more bits ahead of time. + + It can be mathematical proved that even with the modified range and u4_ofst the operations + like comparison and subtraction needed for a bin decode are still valid(both in the regular case and the bypass case) + + As bins are decoded..we consume the bits that we have already read into the valOffset.The clz of Range + gives us the number of bits we consumed of the 23 bits that we have read ahead of time. + + when the number bits we have consumed exceeds 23 ,we renormalize..and we read from the bitstream again*/ + +RESET_BIN_COUNTS(ps_cab_env) + + return OK; +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_init_cabac_contexts */ +/* */ +/* Description : This function initializes the cabac contexts */ +/* depending upon slice type and Init_Idc value. */ +/* Inputs : ps_dec, slice type */ +/* Globals : <Does it use any global variables?> */ +/* Outputs : */ +/* Returns : void */ +/* */ +/* Issues : none */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 03 05 2005 100153) Draft */ +/* */ +/*****************************************************************************/ + +void ih264d_init_cabac_contexts(UWORD8 u1_slice_type, dec_struct_t * ps_dec) +{ + + bin_ctxt_model_t *p_cabac_ctxt_table_t = ps_dec->p_cabac_ctxt_table_t; + UWORD8 u1_qp_y = ps_dec->ps_cur_slice->u1_slice_qp; + UWORD8 u1_cabac_init_Idc = 0; + + if(I_SLICE != u1_slice_type) + { + u1_cabac_init_Idc = ps_dec->ps_cur_slice->u1_cabac_init_idc; + } + + { + /* MAKING ps_dec->p_ctxt_inc_mb_map a scratch buffer */ + /* 0th entry of CtxtIncMbMap will be always be containing default values + for CABAC context representing MB not available */ + ctxt_inc_mb_info_t *p_DefCtxt = ps_dec->p_ctxt_inc_mb_map - 1; + UWORD8 *pu1_temp; + WORD8 i; + p_DefCtxt->u1_mb_type = CAB_SKIP; + + p_DefCtxt->u1_cbp = 0x0f; + p_DefCtxt->u1_intra_chroma_pred_mode = 0; + + p_DefCtxt->u1_yuv_dc_csbp = 0x7; + + p_DefCtxt->u1_transform8x8_ctxt = 0; + + pu1_temp = (UWORD8*)p_DefCtxt->i1_ref_idx; + for(i = 0; i < 4; i++, pu1_temp++) + (*pu1_temp) = 0; + pu1_temp = (UWORD8*)p_DefCtxt->u1_mv; + for(i = 0; i < 16; i++, pu1_temp++) + (*pu1_temp) = 0; + ps_dec->ps_def_ctxt_mb_info = p_DefCtxt; + } + + if(u1_slice_type == I_SLICE) + { + u1_cabac_init_Idc = 3; + ps_dec->p_mb_type_t = p_cabac_ctxt_table_t + MB_TYPE_I_SLICE; + } + else if(u1_slice_type == P_SLICE) + { + ps_dec->p_mb_type_t = p_cabac_ctxt_table_t + MB_TYPE_P_SLICE; + ps_dec->p_mb_skip_flag_t = p_cabac_ctxt_table_t + MB_SKIP_FLAG_P_SLICE; + ps_dec->p_sub_mb_type_t = p_cabac_ctxt_table_t + SUB_MB_TYPE_P_SLICE; + } + else if(u1_slice_type == B_SLICE) + { + ps_dec->p_mb_type_t = p_cabac_ctxt_table_t + MB_TYPE_B_SLICE; + ps_dec->p_mb_skip_flag_t = p_cabac_ctxt_table_t + MB_SKIP_FLAG_B_SLICE; + ps_dec->p_sub_mb_type_t = p_cabac_ctxt_table_t + SUB_MB_TYPE_B_SLICE; + } + { + bin_ctxt_model_t *p_cabac_ctxt_table_t_tmp = p_cabac_ctxt_table_t; + if(ps_dec->ps_cur_slice->u1_field_pic_flag) + { + p_cabac_ctxt_table_t_tmp += SIGNIFICANT_COEFF_FLAG_FLD; + + } + else + { + p_cabac_ctxt_table_t_tmp += SIGNIFICANT_COEFF_FLAG_FRAME; + } + { + bin_ctxt_model_t * * p_significant_coeff_flag_t = + ps_dec->p_significant_coeff_flag_t; + p_significant_coeff_flag_t[0] = p_cabac_ctxt_table_t_tmp + + SIG_COEFF_CTXT_CAT_0_OFFSET; + p_significant_coeff_flag_t[1] = p_cabac_ctxt_table_t_tmp + + SIG_COEFF_CTXT_CAT_1_OFFSET; + p_significant_coeff_flag_t[2] = p_cabac_ctxt_table_t_tmp + + SIG_COEFF_CTXT_CAT_2_OFFSET; + p_significant_coeff_flag_t[3] = p_cabac_ctxt_table_t_tmp + + SIG_COEFF_CTXT_CAT_3_OFFSET; + p_significant_coeff_flag_t[4] = p_cabac_ctxt_table_t_tmp + + SIG_COEFF_CTXT_CAT_4_OFFSET; + + p_significant_coeff_flag_t[5] = p_cabac_ctxt_table_t_tmp + + SIG_COEFF_CTXT_CAT_5_OFFSET; + + } + } + + memcpy(p_cabac_ctxt_table_t, + gau1_ih264d_cabac_ctxt_init_table[u1_cabac_init_Idc][u1_qp_y], + NUM_CABAC_CTXTS * sizeof(bin_ctxt_model_t)); +} +/*! + ************************************************************************** + * \if Function name : ih264d_decode_bin \endif + * + * \brief + * This function implements decoding process of a decision as defined + * in 9.3.3.2.2. + * + * \return + * Returns symbol decoded. + * + * \note + * It is specified in 9.3.3.2.3.2 that, one of the input to this function + * is CtxIdx. CtxIdx is used to identify state and MPS of that context + * (Refer Fig 9.11 - Flowchart for encoding a decision). To suffice that + * here we pass a pointer bin_ctxt_model_t which contains these values. + * + ************************************************************************** + */ + +UWORD32 ih264d_decode_bin(UWORD32 u4_ctx_inc, + bin_ctxt_model_t *ps_src_bin_ctxt, + dec_bit_stream_t *ps_bitstrm, + decoding_envirnoment_t *ps_cab_env) + +{ + + UWORD32 u4_qnt_int_range, u4_code_int_range, u4_code_int_val_ofst, + u4_int_range_lps; + + UWORD32 u4_symbol, u4_mps_state; + + bin_ctxt_model_t *ps_bin_ctxt; + + UWORD32 table_lookup; + const UWORD32 *pu4_table = (const UWORD32 *)ps_cab_env->cabac_table; + UWORD32 u4_clz; + + ps_bin_ctxt = ps_src_bin_ctxt + u4_ctx_inc; + + u4_code_int_range = ps_cab_env->u4_code_int_range; + u4_code_int_val_ofst = ps_cab_env->u4_code_int_val_ofst; + + u4_mps_state = (ps_bin_ctxt->u1_mps_state); + u4_clz = CLZ(u4_code_int_range); + + u4_qnt_int_range = u4_code_int_range << u4_clz; + u4_qnt_int_range = (u4_qnt_int_range >> 29) & 0x3; + + table_lookup = pu4_table[(u4_mps_state << 2) + u4_qnt_int_range]; + u4_int_range_lps = table_lookup & 0xff; + + u4_int_range_lps = u4_int_range_lps << (23 - u4_clz); + u4_code_int_range = u4_code_int_range - u4_int_range_lps; + + u4_symbol = ((u4_mps_state >> 6) & 0x1); + + u4_mps_state = (table_lookup >> 8) & 0x7F; + + CHECK_IF_LPS(u4_code_int_range, u4_code_int_val_ofst, u4_symbol, + u4_int_range_lps, u4_mps_state, table_lookup) + + if(u4_code_int_range < ONE_RIGHT_SHIFTED_BY_8) + { + UWORD32 *pu4_buffer, u4_offset; + + pu4_buffer = ps_bitstrm->pu4_buffer; + u4_offset = ps_bitstrm->u4_ofst; + + RENORM_RANGE_OFFSET(u4_code_int_range, u4_code_int_val_ofst, u4_offset, + pu4_buffer) + + ps_bitstrm->u4_ofst = u4_offset; + } + + INC_BIN_COUNT(ps_cab_env) + + ps_cab_env->u4_code_int_val_ofst = u4_code_int_val_ofst; + ps_cab_env->u4_code_int_range = u4_code_int_range; + ps_bin_ctxt->u1_mps_state = u4_mps_state; + + return (u4_symbol); +} + +/*! + ************************************************************************** + * \if Function name : ih264d_decode_terminate \endif + * + * \brief + * This function implements decoding process of a termination as defined + * 9.3.3.2.2.3 of ISO/IEC14496-10. + * + * \return + * Returns symbol decoded. + * + * \note + * This routine is called while decoding "end_of_skice_flag" and of the + * bin indicating PCM mode in MBType. + * + ************************************************************************** + */ +UWORD8 ih264d_decode_terminate(decoding_envirnoment_t * ps_cab_env, + dec_bit_stream_t * ps_stream) +{ + UWORD32 u4_symbol; + UWORD32 u4_code_int_val_ofst, u4_code_int_range; + UWORD32 u4_clz; + + u4_code_int_range = ps_cab_env->u4_code_int_range; + u4_code_int_val_ofst = ps_cab_env->u4_code_int_val_ofst; + + u4_clz = CLZ(u4_code_int_range); + u4_code_int_range -= (2 << (23 - u4_clz)); + + if(u4_code_int_val_ofst >= u4_code_int_range) + { + /* S=1 */ + u4_symbol = 1; + + { + + /*the u4_ofst needs to be updated before termination*/ + ps_stream->u4_ofst += u4_clz; + + } + + } + else + { + /* S=0 */ + u4_symbol = 0; + + if(u4_code_int_range < ONE_RIGHT_SHIFTED_BY_8) + { + UWORD32 *pu4_buffer, u4_offset; + + pu4_buffer = ps_stream->pu4_buffer; + u4_offset = ps_stream->u4_ofst; + + RENORM_RANGE_OFFSET(u4_code_int_range, u4_code_int_val_ofst, u4_offset, + pu4_buffer) + ps_stream->u4_ofst = u4_offset; + } + } + + ps_cab_env->u4_code_int_range = u4_code_int_range; + ps_cab_env->u4_code_int_val_ofst = u4_code_int_val_ofst; + + INC_BIN_COUNT(ps_cab_env) + + return (u4_symbol); +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_decode_bins_tunary */ +/* */ +/* Description : This function decodes bins in the case of TUNARY */ +/* binarization technique.valid_length is assumed equal to 3 */ +/* and u1_max_bins <= 4 in this functon. */ +/* Inputs : <What inputs does the function take?> */ +/* Globals : <Does it use any global variables?> */ +/* Processing : <Describe how the function operates - include algorithm */ +/* description> */ +/* Outputs : <What does the function produce?> */ +/* Returns : <What does the function return?> */ +/* */ +/* Issues : */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 20 11 2008 SH Draft */ +/* */ +/*****************************************************************************/ + +UWORD32 ih264d_decode_bins_tunary(UWORD8 u1_max_bins, + UWORD32 u4_ctx_inc, + bin_ctxt_model_t *ps_src_bin_ctxt, + dec_bit_stream_t *ps_bitstrm, + decoding_envirnoment_t *ps_cab_env) + +{ + UWORD32 u4_value; + UWORD32 u4_symbol; + UWORD8 u4_ctx_Inc; + bin_ctxt_model_t *ps_bin_ctxt; + UWORD32 u4_code_int_range, u4_code_int_val_ofst; + const UWORD32 *pu4_table = (const UWORD32 *)ps_cab_env->cabac_table; + + u4_value = 0; + + /*u1_max_bins has to be less than or equal to 4, u1_max_bins <= 4 for this function*/ + + /*here the valid length is assumed to be equal to 3 ,so the calling function is expected + to duplicate CtxInc if valid lenth is 2 and cmaxbin is greater than2*/ + u4_code_int_range = ps_cab_env->u4_code_int_range; + u4_code_int_val_ofst = ps_cab_env->u4_code_int_val_ofst; + + do + { + u4_ctx_Inc = u4_ctx_inc & 0xF; + u4_ctx_inc = u4_ctx_inc >> 4; + + ps_bin_ctxt = ps_src_bin_ctxt + u4_ctx_Inc; + + DECODE_ONE_BIN_MACRO(ps_bin_ctxt, u4_code_int_range, u4_code_int_val_ofst, + pu4_table, ps_bitstrm, u4_symbol) + + INC_BIN_COUNT(ps_cab_env);INC_DECISION_BINS(ps_cab_env); + + u4_value++; + } + while((u4_value < u1_max_bins) & (u4_symbol)); + + u4_value = u4_value - 1 + u4_symbol; + + ps_cab_env->u4_code_int_range = u4_code_int_range; + ps_cab_env->u4_code_int_val_ofst = u4_code_int_val_ofst; + + return (u4_value); + +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_decode_bins */ +/* */ +/* Description : This function decodes bins in the case of MSB_FIRST_FLC */ +/* binarization technique.valid_length is always equal max_bins */ +/* for MSB_FIRST_FLC. assumes u1_max_bins <= 4 */ +/* Inputs : <What inputs does the function take?> */ +/* Globals : <Does it use any global variables?> */ +/* Processing : <Describe how the function operates - include algorithm */ +/* description> */ +/* Outputs : <What does the function produce?> */ +/* Returns : <What does the function return?> */ +/* */ +/* Issues : <List any issues or problems with this function> */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 20 11 2008 SH Draft */ +/* */ +/*****************************************************************************/ + +UWORD32 ih264d_decode_bins(UWORD8 u1_max_bins, + UWORD32 u4_ctx_inc, + bin_ctxt_model_t *ps_src_bin_ctxt, + dec_bit_stream_t *ps_bitstrm, + decoding_envirnoment_t *ps_cab_env) + +{ + UWORD32 u4_value; + UWORD32 u4_symbol, i; + UWORD32 u4_ctxt_inc; + bin_ctxt_model_t *ps_bin_ctxt; + UWORD32 u4_code_int_range, u4_code_int_val_ofst; + const UWORD32 *pu4_table = (const UWORD32 *)ps_cab_env->cabac_table; + + i = 0; + + u4_value = 0; + + /*u1_max_bins has to be less than or equal to 4, u1_max_bins <= 4 for this fucntion*/ + u4_code_int_range = ps_cab_env->u4_code_int_range; + u4_code_int_val_ofst = ps_cab_env->u4_code_int_val_ofst; + + do + { + u4_ctxt_inc = u4_ctx_inc & 0xf; + u4_ctx_inc = u4_ctx_inc >> 4; + + ps_bin_ctxt = ps_src_bin_ctxt + u4_ctxt_inc; + + DECODE_ONE_BIN_MACRO(ps_bin_ctxt, u4_code_int_range, u4_code_int_val_ofst, + pu4_table, ps_bitstrm, u4_symbol) + + INC_BIN_COUNT(ps_cab_env);INC_DECISION_BINS(ps_cab_env); + + u4_value = (u4_value << 1) | (u4_symbol); + + i++; + } + while(i < u1_max_bins); + + ps_cab_env->u4_code_int_range = u4_code_int_range; + ps_cab_env->u4_code_int_val_ofst = u4_code_int_val_ofst; + + return (u4_value); + +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_decode_bins_unary */ +/* */ +/* Description : This function decodes bins in the case of UNARY */ +/* binarization technique.here the valid length is taken to 5*/ +/* and cmax is always greater than 9 */ +/* Inputs : <What inputs does the function take?> */ +/* Globals : <Does it use any global variables?> */ +/* Processing : <Describe how the function operates - include algorithm */ +/* description> */ +/* Outputs : <What does the function produce?> */ +/* Returns : <What does the function return?> */ +/* */ +/* Issues : <List any issues or problems with this function> */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 20 11 2008 SH Draft */ +/* */ +/*****************************************************************************/ +UWORD32 ih264d_decode_bins_unary(UWORD8 u1_max_bins, + UWORD32 u4_ctx_inc, + bin_ctxt_model_t *ps_src_bin_ctxt, + dec_bit_stream_t *ps_bitstrm, + decoding_envirnoment_t *ps_cab_env) +{ + UWORD32 u4_value; + UWORD32 u4_symbol; + bin_ctxt_model_t *ps_bin_ctxt; + UWORD32 u4_ctx_Inc; + UWORD32 u4_code_int_range, u4_code_int_val_ofst; + const UWORD32 *pu4_table = (const UWORD32 *)ps_cab_env->cabac_table; + + /* in this function the valid length for u4_ctx_inc is always taken to be,so if the + the valid length is lessthan 5 the caller need to duplicate accordingly*/ + + /*u1_max_bins is always greater or equal to 9 we have the check for u1_max_bins only after the 2 loop*/ + u4_value = 0; + u4_code_int_range = ps_cab_env->u4_code_int_range; + u4_code_int_val_ofst = ps_cab_env->u4_code_int_val_ofst; + + do + { + u4_ctx_Inc = u4_ctx_inc & 0xf; + u4_ctx_inc = u4_ctx_inc >> 4; + + ps_bin_ctxt = ps_src_bin_ctxt + u4_ctx_Inc; + + DECODE_ONE_BIN_MACRO(ps_bin_ctxt, u4_code_int_range, u4_code_int_val_ofst, + pu4_table, ps_bitstrm, u4_symbol) + + INC_BIN_COUNT(ps_cab_env);INC_DECISION_BINS(ps_cab_env); + + u4_value++; + + } + while(u4_symbol && u4_value < 4); + + if(u4_symbol && (u4_value < u1_max_bins)) + { + + u4_ctx_Inc = u4_ctx_inc & 0xf; + + ps_bin_ctxt = ps_src_bin_ctxt + u4_ctx_Inc; + + do + { + + DECODE_ONE_BIN_MACRO(ps_bin_ctxt, u4_code_int_range, u4_code_int_val_ofst, + pu4_table, ps_bitstrm, u4_symbol) + + INC_BIN_COUNT(ps_cab_env);INC_DECISION_BINS(ps_cab_env); + + u4_value++; + + } + while(u4_symbol && (u4_value < u1_max_bins)); + + } + + ps_cab_env->u4_code_int_range = u4_code_int_range; + ps_cab_env->u4_code_int_val_ofst = u4_code_int_val_ofst; + + u4_value = u4_value - 1 + u4_symbol; + + return (u4_value); + +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_decode_bypass_bins_unary */ +/* */ +/* Description : This function is used in the case of UNARY coding */ +/* */ +/* */ +/* Inputs : <What inputs does the function take?> */ +/* Globals : <Does it use any global variables?> */ +/* Processing : <Describe how the function operates - include algorithm */ +/* description> */ +/* Outputs : <What does the function produce?> */ +/* Returns : <What does the function return?> */ +/* */ +/* Issues : <List any issues or problems with this function> */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 13 10 2005 Ittiam Draft */ +/* */ +/*****************************************************************************/ + +UWORD32 ih264d_decode_bypass_bins_unary(decoding_envirnoment_t *ps_cab_env, + dec_bit_stream_t *ps_bitstrm) +{ + UWORD32 u4_value; + UWORD32 u4_bin; + UWORD32 u4_code_int_val_ofst, u4_code_int_range; + + UWORD32 u1_max_bins; + + u4_code_int_val_ofst = ps_cab_env->u4_code_int_val_ofst; + u4_code_int_range = ps_cab_env->u4_code_int_range; + + if(u4_code_int_range < ONE_RIGHT_SHIFTED_BY_9) + { + UWORD32 *pu4_buffer, u4_offset; + + pu4_buffer = ps_bitstrm->pu4_buffer; + u4_offset = ps_bitstrm->u4_ofst; + + RENORM_RANGE_OFFSET(u4_code_int_range, u4_code_int_val_ofst, u4_offset, + pu4_buffer) + ps_bitstrm->u4_ofst = u4_offset; + } + + /*as it is called only form mvd*/ + u1_max_bins = 32; + u4_value = 0; + + do + { + u4_value++; + + u4_code_int_range = u4_code_int_range >> 1; + if(u4_code_int_val_ofst >= u4_code_int_range) + { + /* S=1 */ + u4_bin = 1; + u4_code_int_val_ofst -= u4_code_int_range; + } + else + { + /* S=0 */ + u4_bin = 0; + } + + INC_BIN_COUNT(ps_cab_env);INC_BYPASS_BINS(ps_cab_env); + + if(u4_code_int_range < ONE_RIGHT_SHIFTED_BY_9) + { + UWORD32 *pu4_buffer, u4_offset; + + pu4_buffer = ps_bitstrm->pu4_buffer; + u4_offset = ps_bitstrm->u4_ofst; + + RENORM_RANGE_OFFSET(u4_code_int_range, u4_code_int_val_ofst, u4_offset, + pu4_buffer) + + ps_bitstrm->u4_ofst = u4_offset; + } + + } + while(u4_bin && (u4_value < u1_max_bins)); + + ps_cab_env->u4_code_int_val_ofst = u4_code_int_val_ofst; + ps_cab_env->u4_code_int_range = u4_code_int_range; + u4_value = (u4_value - 1 + u4_bin); + +return (u4_value); +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_decode_bypass_bins */ +/* */ +/* Description : This function is used in the case of FLC coding */ +/* */ +/* */ +/* Inputs : <What inputs does the function take?> */ +/* Globals : <Does it use any global variables?> */ +/* Processing : <Describe how the function operates - include algorithm */ +/* description> */ +/* Outputs : <What does the function produce?> */ +/* Returns : <What does the function return?> */ +/* */ +/* Issues : <List any issues or problems with this function> */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 13 10 2005 Ittiam Draft */ +/* */ +/*****************************************************************************/ + +UWORD32 ih264d_decode_bypass_bins(decoding_envirnoment_t *ps_cab_env, + UWORD8 u1_max_bins, + dec_bit_stream_t *ps_bitstrm) +{ + UWORD32 u4_bins; + UWORD32 u4_bin; + UWORD32 u4_code_int_val_ofst, u4_code_int_range; + + u4_bins = 0; + u4_code_int_val_ofst = ps_cab_env->u4_code_int_val_ofst; + u4_code_int_range = ps_cab_env->u4_code_int_range; + + if(u4_code_int_range < ONE_RIGHT_SHIFTED_BY_9) + { + UWORD32 *pu4_buffer, u4_offset; + + pu4_buffer = ps_bitstrm->pu4_buffer; + u4_offset = ps_bitstrm->u4_ofst; + + RENORM_RANGE_OFFSET(u4_code_int_range, u4_code_int_val_ofst, u4_offset, + pu4_buffer) + ps_bitstrm->u4_ofst = u4_offset; + } + + do + { + + u4_code_int_range = u4_code_int_range >> 1; + + if(u4_code_int_val_ofst >= u4_code_int_range) + { + /* S=1 */ + u4_bin = 1; + u4_code_int_val_ofst -= u4_code_int_range; + } + else + { + /* S=0 */ + u4_bin = 0; + } + + INC_BIN_COUNT(ps_cab_env);INC_BYPASS_BINS(ps_cab_env); + + u4_bins = ((u4_bins << 1) | u4_bin); + u1_max_bins--; + + if(u4_code_int_range < ONE_RIGHT_SHIFTED_BY_9) + { + UWORD32 *pu4_buffer, u4_offset; + + pu4_buffer = ps_bitstrm->pu4_buffer; + u4_offset = ps_bitstrm->u4_ofst; + + RENORM_RANGE_OFFSET(u4_code_int_range, u4_code_int_val_ofst, u4_offset, + pu4_buffer) + ps_bitstrm->u4_ofst = u4_offset; + } + + } + while(u1_max_bins); + + ps_cab_env->u4_code_int_val_ofst = u4_code_int_val_ofst; + ps_cab_env->u4_code_int_range = u4_code_int_range; + + return (u4_bins); +} + diff --git a/dependencies/ih264d/decoder/ih264d_cabac.h b/dependencies/ih264d/decoder/ih264d_cabac.h new file mode 100644 index 00000000..6ee3d52d --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_cabac.h @@ -0,0 +1,267 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/*! + *************************************************************************** + * \file ih264d_cabac.h + * + * \brief + * This file contains declarations of Binary decoding routines and tables. + * + * \date + * 04/02/2003 + * + * \author NS + *************************************************************************** + */ + +#ifndef _IH264D_CABAC_H_ +#define _IH264D_CABAC_H_ + +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264d_bitstrm.h" +#include "ih264d_defs.h" + +#define B_BITS 10 + +#define HALF (1 << (B_BITS-1)) +#define QUARTER (1 << (B_BITS-2)) + +#define CTXT_UNUSED {0,64} +#define NUM_MB_SKIP_CTXT 6 +#define NUM_MB_TYPE_CTXT 9 +#define NUM_SUBMB_TYPE_CTXT 7 +#define NUM_REF_IDX_CTXT 6 +#define NUM_MB_QP_DELTA 4 +#define NUM_PRED_MODE 6 +#define NUM_MB_FIELD 3 +#define NUM_CBP 12 +#define NUM_CTX_MVD 14 + +/* Residual block cabac context parameters */ +#define NUM_CTX_CAT 6 +#define NUM_LUMA_CTX_CAT 3 +#define NUM_CTX_CODED_BLOCK 4 +/* Luma CtxSigCoeff + CtxLastCoeff = 15 + 15 = 30 */ +#define NUM_LUMA_CTX_SIG_COEF 30 +/* Chroma DC CtxSigCoeff + CtxLastCoeff = 3 + 3 = 6 */ +#define NUM_CTX_CHROMA_DC_SIG_COEF 6 +/* Chroma AC CtxSigCoeff + CtxLastCoeff = 14 + 14 = 28 */ +#define NUM_CTX_CHROMA_AC_SIG_COEF 28 +#define NUM_CTX_ABS_LEVEL 10 + +#define LUMA_DC_CTXCAT 0 +#define LUMA_AC_CTXCAT 1 +#define LUMA_4X4_CTXCAT 2 +#define CHROMA_DC_CTXCAT 3 +#define CHROMA_AC_CTXCAT 4 +#define LUMA_8X8_CTXCAT 5 + +/*****************************************************************************/ +/* Constant Macros */ +/*****************************************************************************/ +#define NUM_CABAC_CTXTS 460 +#define QP_RANGE 52 +#define NUM_CAB_INIT_IDC_PLUS_ONE 4 +#define LAST_COEFF_CTXT_MINUS_SIG_COEFF_CTXT 61 +#define LAST_COEFF_CTXT_MINUS_SIG_COEFF_CTXT_8X8 15 + +/*bits 0 to 5 :state + bit 6:mps*/ +typedef struct +{ + UWORD8 u1_mps_state; /* state number */ +} bin_ctxt_model_t; + +typedef struct + +{ + /* Neighbour availability Variables needed to get CtxtInc, for CABAC */ + UWORD8 u1_mb_type; /** macroblock type: I/P/B/SI/SP */ + UWORD8 u1_cbp; /** Coded Block Pattern */ + UWORD8 u1_intra_chroma_pred_mode; + + /*************************************************************************/ + /* Arrangnment of DC CSBP */ + /* bits: b7 b6 b5 b4 b3 b2 b1 b0 */ + /* CSBP: x x x x x Vdc Udc Ydc */ + /*************************************************************************/ + UWORD8 u1_yuv_dc_csbp; + WORD8 i1_ref_idx[4]; + UWORD8 u1_mv[4][4]; + UWORD8 u1_transform8x8_ctxt; +} ctxt_inc_mb_info_t; + +#define ONE_RIGHT_SHIFTED_BY_8 1<<8 +#define ONE_RIGHT_SHIFTED_BY_9 1<<9 +#define ONE_RIGHT_SHIFTED_BY_14 1<<14 +typedef struct +{ + UWORD32 u4_code_int_range; + UWORD32 u4_code_int_val_ofst; + const void *cabac_table; + void * pv_codec_handle; /* For Error Handling */ +} decoding_envirnoment_t; + +WORD32 ih264d_init_cabac_dec_envirnoment(decoding_envirnoment_t * ps_cab_env, + dec_bit_stream_t *ps_bitstrm); + +UWORD32 ih264d_decode_bin(UWORD32 u4_ctx_inc, + bin_ctxt_model_t *ps_bin_ctxt, + dec_bit_stream_t *ps_bitstrm, + decoding_envirnoment_t *ps_cab_env); +UWORD8 ih264d_decode_terminate(decoding_envirnoment_t * ps_cab_env, + dec_bit_stream_t * ps_bitstrm); + +UWORD32 ih264d_decode_bins_tunary(UWORD8 u1_max_bins, + UWORD32 u4_ctx_inc, + bin_ctxt_model_t *ps_src_bin_ctxt, + dec_bit_stream_t *ps_bitstrm, + decoding_envirnoment_t *ps_cab_env); + +UWORD32 ih264d_decode_bins(UWORD8 u1_max_bins, + UWORD32 u4_ctx_inc, + bin_ctxt_model_t *ps_src_bin_ctxt, + dec_bit_stream_t *ps_bitstrm, + decoding_envirnoment_t *ps_cab_env); +UWORD32 ih264d_decode_bins_unary(UWORD8 u1_max_bins, + UWORD32 u4_ctx_inc, + bin_ctxt_model_t *ps_src_bin_ctxt, + dec_bit_stream_t *ps_bitstrm, + decoding_envirnoment_t *ps_cab_env); + +UWORD32 ih264d_decode_bypass_bins_unary(decoding_envirnoment_t *ps_cab_env, + dec_bit_stream_t *ps_bitstrm); + +UWORD32 ih264d_decode_bypass_bins(decoding_envirnoment_t *ps_cab_env, + UWORD8 u1_max_bins, + dec_bit_stream_t *ps_bitstrm); + +/*****************************************************************************/ +/* Function Macros */ +/*****************************************************************************/ + +/*****************************************************************************/ +/* Defining a macro for renormalization*/ +/*****************************************************************************/ + +/*we renormalize every time the number bits(which are read ahead of time) we have + consumed in the u4_ofst exceeds 23*/ + +#define RENORM_RANGE_OFFSET(u4_codeIntRange_m,u4_codeIntValOffset_m,u4_offset_m,pu4_buffer_m) \ + { \ + UWORD32 read_bits_m,u4_clz_m ; \ + u4_clz_m = CLZ(u4_codeIntRange_m); \ + NEXTBITS(read_bits_m,(u4_offset_m+23),pu4_buffer_m,u4_clz_m) \ + FLUSHBITS(u4_offset_m,(u4_clz_m)) \ + u4_codeIntRange_m = u4_codeIntRange_m << u4_clz_m; \ + u4_codeIntValOffset_m = (u4_codeIntValOffset_m << u4_clz_m) | read_bits_m; \ + } + +/*****************************************************************************/ +/* Defining a macro for checking if the symbol is MPS*/ +/*****************************************************************************/ + +#define CHECK_IF_LPS(u4_codeIntRange_m,u4_codeIntValOffset_m,u4_symbol_m, \ + u4_codeIntRangeLPS_m,u1_mps_state_m,table_lookup_m) \ +{ \ + if(u4_codeIntValOffset_m >= u4_codeIntRange_m) \ + { \ + u4_symbol_m = 1 - u4_symbol_m; \ + u4_codeIntValOffset_m -= u4_codeIntRange_m; \ + u4_codeIntRange_m = u4_codeIntRangeLPS_m; \ + u1_mps_state_m = (table_lookup_m >> 15) & 0x7F; \ + } \ +} + +/*! + ************************************************************************** + * \if Function name : DECODE_ONE_BIN_MACRO \endif + * + * \brief + * This function implements decoding process of a decision as defined + * in 9.3.3.2.2. + * + * \return + * Returns symbol decoded. + * + * \note + * It is specified in 9.3.3.2.3.2 that, one of the input to this function + * is CtxIdx. CtxIdx is used to identify state and MPS of that context + * (Refer Fig 9.11 - Flowchart for encoding a decision). To suffice that + * here we pass a pointer bin_ctxt_model_t which contains these values. + * + ************************************************************************** + */ + +#define DECODE_ONE_BIN_MACRO(p_binCtxt_arg ,u4_code_int_range,u4_code_int_val_ofst, \ + pu4_table_arg, \ + p_DecBitStream_arg,u4_symbol) \ +{ \ + bin_ctxt_model_t *p_binCtxt_m = (bin_ctxt_model_t *) p_binCtxt_arg; \ + dec_bit_stream_t *p_DecBitStream_m = (dec_bit_stream_t *) p_DecBitStream_arg; \ + const UWORD32 *pu4_table_m = (const UWORD32 *) pu4_table_arg; \ + \ + UWORD32 u4_quantCodeIntRange_m,u4_codeIntRangeLPS_m; \ + UWORD32 u1_mps_state_m; \ + UWORD32 table_lookup_m; \ + UWORD32 u4_clz_m; \ + \ + u1_mps_state_m = (p_binCtxt_m->u1_mps_state); \ + u4_clz_m = CLZ(u4_code_int_range); \ + u4_quantCodeIntRange_m = u4_code_int_range << u4_clz_m; \ + u4_quantCodeIntRange_m = (u4_quantCodeIntRange_m >> 29) & 0x3; \ + table_lookup_m = pu4_table_m[(u1_mps_state_m << 2)+u4_quantCodeIntRange_m]; \ + u4_codeIntRangeLPS_m = table_lookup_m & 0xff; \ + \ + u4_codeIntRangeLPS_m = u4_codeIntRangeLPS_m << (23 - u4_clz_m); \ + u4_code_int_range = u4_code_int_range - u4_codeIntRangeLPS_m; \ + u4_symbol = ((u1_mps_state_m>> 6) & 0x1); \ + /*if mps*/ \ + u1_mps_state_m = (table_lookup_m >> 8) & 0x7F; \ + if(u4_code_int_val_ofst >= u4_code_int_range) \ + { \ + \ + u4_symbol = 1 - u4_symbol; \ + u4_code_int_val_ofst -= u4_code_int_range; \ + u4_code_int_range = u4_codeIntRangeLPS_m; \ + u1_mps_state_m = (table_lookup_m >> 15) & 0x7F; \ + } \ + if(u4_code_int_range < ONE_RIGHT_SHIFTED_BY_8) \ + { \ + UWORD32 *pu4_buffer,u4_offset; \ + UWORD32 read_bits,u4_clz_m ; \ + \ + pu4_buffer = p_DecBitStream_m->pu4_buffer; \ + u4_offset = p_DecBitStream_m->u4_ofst; \ + u4_clz_m = CLZ(u4_code_int_range); \ + NEXTBITS(read_bits,(u4_offset+23),pu4_buffer,u4_clz_m) \ + FLUSHBITS(u4_offset,(u4_clz_m)) \ + u4_code_int_range = u4_code_int_range << u4_clz_m; \ + u4_code_int_val_ofst= (u4_code_int_val_ofst << u4_clz_m) | read_bits; \ + \ + \ + p_DecBitStream_m->u4_ofst = u4_offset; \ + } \ + p_binCtxt_m->u1_mps_state = u1_mps_state_m; \ +} + +#endif /* _IH264D_CABAC_H_ */ diff --git a/dependencies/ih264d/decoder/ih264d_cabac_init_tables.c b/dependencies/ih264d/decoder/ih264d_cabac_init_tables.c new file mode 100644 index 00000000..cd35a2d3 --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_cabac_init_tables.c @@ -0,0 +1,9274 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +#ifndef _CABAC_INIT_TABLES_H_ +#define _CABAC_INIT_TABLES_H_ + +/*****************************************************************************/ +/* */ +/* File Name : ih264d_cabac_init_tables.c */ +/* */ +/* Description : This file contains the initialized cabac context */ +/* structures for all possible values of Qp (0 - 51) */ +/* Cabac_init Idc (0 - 2) and I slice. The contexts */ +/* are initialized and stored as per tables 9-11 to */ +/* 9 -23 */ +/* */ +/* Issues / Problems : None */ +/* */ +/* Revision History : */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 10 01 2005 SH */ +/* */ +/*****************************************************************************/ + +/*****************************************************************************/ +/* File Includes */ +/*****************************************************************************/ +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264d_cabac.h" +#include "ih264d_tables.h" + +/*combined table :guc_RTAB,NextStateLPS,NextStateMPS + input(combined_state): + bits 0-5: state + bits 6:mps + output + bits 0-7:rangeTabLPS + bits 8-14 :combined_next_state_if_mps + bits 15 -21:combined_next_state_if_lps + + */ + +const UWORD32 gau4_ih264d_cabac_table[128][4] = + { + { 2097536, 2097584, 2097616, 2097648 }, + + { 640, 679, 709, 739 }, + + { 33664, 33694, 33723, 33752 }, + + { 66683, 66710, 66738, 66765 }, + + { 66932, 66958, 66985, 67011 }, + + { 132719, 132743, 132768, 132793 }, + + { 132969, 132992, 133016, 133039 }, + + { 165988, 166010, 166032, 166054 }, + + { 199007, 199028, 199049, 199070 }, + + { 232026, 232046, 232066, 232086 }, + + { 265045, 265064, 265083, 265102 }, + + { 298065, 298083, 298101, 298119 }, + + { 298317, 298334, 298351, 298368 }, + + { 364105, 364121, 364137, 364154 }, + + { 364357, 364373, 364388, 364404 }, + + { 397378, 397392, 397407, 397422 }, + + { 430398, 430412, 430426, 430440 }, + + { 430651, 430664, 430678, 430691 }, + + { 496440, 496453, 496465, 496478 }, + + { 496693, 496705, 496717, 496729 }, + + { 529715, 529726, 529737, 529749 }, + + { 529968, 529979, 529989, 530000 }, + + { 595758, 595768, 595778, 595788 }, + + { 596011, 596021, 596031, 596040 }, + + { 629033, 629042, 629051, 629061 }, + + { 629287, 629296, 629304, 629313 }, + + { 695077, 695085, 695094, 695102 }, + + { 695331, 695339, 695347, 695355 }, + + { 728353, 728361, 728368, 728376 }, + + { 728608, 728615, 728622, 728629 }, + + { 761630, 761637, 761643, 761650 }, + + { 794653, 794659, 794665, 794672 }, + + { 794907, 794913, 794919, 794925 }, + + { 827930, 827935, 827941, 827947 }, + + { 860952, 860958, 860963, 860969 }, + + { 861207, 861212, 861217, 861223 }, + + { 894230, 894235, 894240, 894245 }, + + { 894485, 894490, 894494, 894499 }, + + { 927508, 927512, 927517, 927521 }, + + { 960531, 960535, 960539, 960543 }, + + { 960786, 960790, 960794, 960798 }, + + { 993809, 993813, 993817, 993820 }, + + { 994064, 994068, 994071, 994075 }, + + { 994319, 994323, 994326, 994329 }, + + { 1027342, 1027346, 1027349, 1027352 }, + + { 1060366, 1060369, 1060372, 1060375 }, + + { 1060621, 1060624, 1060627, 1060630 }, + + { 1093644, 1093647, 1093650, 1093653 }, + + { 1093900, 1093902, 1093905, 1093908 }, + + { 1094155, 1094158, 1094160, 1094163 }, + + { 1127179, 1127181, 1127183, 1127186 }, + + { 1127434, 1127436, 1127439, 1127441 }, + + { 1160458, 1160460, 1160462, 1160464 }, + + { 1160713, 1160715, 1160717, 1160719 }, + + { 1160969, 1160971, 1160972, 1160974 }, + + { 1193992, 1193994, 1193996, 1193998 }, + + { 1194248, 1194249, 1194251, 1194253 }, + + { 1194503, 1194505, 1194507, 1194508 }, + + { 1227527, 1227529, 1227530, 1227532 }, + + { 1227783, 1227784, 1227786, 1227787 }, + + { 1228038, 1228040, 1228041, 1228043 }, + + { 1261062, 1261063, 1261065, 1261066 }, + + { 1261062, 1261063, 1261064, 1261065 }, + + { 2080514, 2080514, 2080514, 2080514 }, + + { 16768, 16816, 16848, 16880 }, + + { 2114176, 2114215, 2114245, 2114275 }, + + { 2147200, 2147230, 2147259, 2147288 }, + + { 2180219, 2180246, 2180274, 2180301 }, + + { 2180468, 2180494, 2180521, 2180547 }, + + { 2246255, 2246279, 2246304, 2246329 }, + + { 2246505, 2246528, 2246552, 2246575 }, + + { 2279524, 2279546, 2279568, 2279590 }, + + { 2312543, 2312564, 2312585, 2312606 }, + + { 2345562, 2345582, 2345602, 2345622 }, + + { 2378581, 2378600, 2378619, 2378638 }, + + { 2411601, 2411619, 2411637, 2411655 }, + + { 2411853, 2411870, 2411887, 2411904 }, + + { 2477641, 2477657, 2477673, 2477690 }, + + { 2477893, 2477909, 2477924, 2477940 }, + + { 2510914, 2510928, 2510943, 2510958 }, + + { 2543934, 2543948, 2543962, 2543976 }, + + { 2544187, 2544200, 2544214, 2544227 }, + + { 2609976, 2609989, 2610001, 2610014 }, + + { 2610229, 2610241, 2610253, 2610265 }, + + { 2643251, 2643262, 2643273, 2643285 }, + + { 2643504, 2643515, 2643525, 2643536 }, + + { 2709294, 2709304, 2709314, 2709324 }, + + { 2709547, 2709557, 2709567, 2709576 }, + + { 2742569, 2742578, 2742587, 2742597 }, + + { 2742823, 2742832, 2742840, 2742849 }, + + { 2808613, 2808621, 2808630, 2808638 }, + + { 2808867, 2808875, 2808883, 2808891 }, + + { 2841889, 2841897, 2841904, 2841912 }, + + { 2842144, 2842151, 2842158, 2842165 }, + + { 2875166, 2875173, 2875179, 2875186 }, + + { 2908189, 2908195, 2908201, 2908208 }, + + { 2908443, 2908449, 2908455, 2908461 }, + + { 2941466, 2941471, 2941477, 2941483 }, + + { 2974488, 2974494, 2974499, 2974505 }, + + { 2974743, 2974748, 2974753, 2974759 }, + + { 3007766, 3007771, 3007776, 3007781 }, + + { 3008021, 3008026, 3008030, 3008035 }, + + { 3041044, 3041048, 3041053, 3041057 }, + + { 3074067, 3074071, 3074075, 3074079 }, + + { 3074322, 3074326, 3074330, 3074334 }, + + { 3107345, 3107349, 3107353, 3107356 }, + + { 3107600, 3107604, 3107607, 3107611 }, + + { 3107855, 3107859, 3107862, 3107865 }, + + { 3140878, 3140882, 3140885, 3140888 }, + + { 3173902, 3173905, 3173908, 3173911 }, + + { 3174157, 3174160, 3174163, 3174166 }, + + { 3207180, 3207183, 3207186, 3207189 }, + + { 3207436, 3207438, 3207441, 3207444 }, + + { 3207691, 3207694, 3207696, 3207699 }, + + { 3240715, 3240717, 3240719, 3240722 }, + + { 3240970, 3240972, 3240975, 3240977 }, + + { 3273994, 3273996, 3273998, 3274000 }, + + { 3274249, 3274251, 3274253, 3274255 }, + + { 3274505, 3274507, 3274508, 3274510 }, + + { 3307528, 3307530, 3307532, 3307534 }, + + { 3307784, 3307785, 3307787, 3307789 }, + + { 3308039, 3308041, 3308043, 3308044 }, + + { 3341063, 3341065, 3341066, 3341068 }, + + { 3341319, 3341320, 3341322, 3341323 }, + + { 3341574, 3341576, 3341577, 3341579 }, + + { 3374598, 3374599, 3374601, 3374602 }, + + { 3374598, 3374599, 3374600, 3374601 }, + + { 4194050, 4194050, 4194050, 4194050 }, + + }; + +/*****************************************************************************/ +/* Global Variable Initialization */ +/*****************************************************************************/ +const UWORD8 gau1_ih264d_cabac_ctxt_init_table[NUM_CAB_INIT_IDC_PLUS_ONE][QP_RANGE][NUM_CABAC_CTXTS] = + + { + + { + + { + + 62, + 9, 74, 62, 9, 74, 126, 104, 10, 9, 12, 30, 61, 62, + 54, 14, 118, 6, 78, 65, 1, 14, 73, 13, 64, 20, 62, + 67, 90, 104, 126, 104, 67, 78, 65, 1, 86, 95, 2, + 18, 69, 81, 96, 8, 67, 86, 88, 5, 76, 94, 9, 69, + 81, 88, 67, 74, 74, 80, 72, 5, 22, 0, 0, 0, 83, + 86, 97, 72, 22, 1, 18, 78, 96, 126, 98, 101, 67, + 82, 94, 83, 110, 91, 102, 93, 126, 92, 89, 96, + 108, 17, 65, 6, 93, 74, 92, 87, 126, 9, 3, 4, 69, + 15, 68, 69, 88, 85, 78, 75, 77, 9, 13, 68, 13, 21, + 81, 0, 70, 67, 6, 76, 28, 64, 2, 28, 38, 39, 34, + 27, 93, 73, 73, 17, 14, 100, 10, 10, 10, 2, 7, 7, + 0, 3, 1, 6, 69, 6, 24, 12, 68, 64, 2, 0, 13, 24, + 19, 11, 15, 3, 4, 4, 30, 19, 20, 78, 3, 69, 35, + 23, 19, 14, 17, 19, 12, 16, 24, 1, 17, 9, 9, 5, 0, + 12, 6, 10, 11, 8, 18, 27, 10, 82, 8, 78, 17, 32, + 84, 56, 62, 60, 59, 62, 62, 57, 57, 54, 44, 36, + 33, 43, 29, 70, 67, 4, 67, 33, 31, 28, 34, 32, 25, + 20, 22, 0, 4, 64, 94, 89, 108, 76, 19, 18, 11, 64, + 4, 70, 75, 82, 102, 77, 39, 21, 15, 8, 4, 71, 83, + 87, 119, 5, 34, 27, 25, 20, 8, 5, 64, 74, 90, 70, + 34, 32, 21, 4, 5, 72, 81, 97, 5, 58, 49, 45, 36, + 23, 5, 70, 79, 85, 62, 106, 106, 87, 114, 110, 98, + 110, 106, 103, 107, 108, 112, 96, 95, 91, 93, 94, + 86, 67, 80, 85, 70, 3, 5, 2, 13, 13, 14, 9, 22, + 17, 12, 14, 11, 22, 16, 8, 22, 19, 13, 10, 14, 0, + 64, 69, 4, 70, 19, 32, 20, 10, 29, 25, 11, 23, 31, + 19, 25, 13, 6, 20, 52, 49, 52, 52, 54, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 34, 62, 62, 62, 62, 62, + 62, 54, 37, 36, 6, 82, 75, 97, 125, 62, 62, 62, + 57, 55, 53, 41, 44, 31, 32, 22, 19, 16, 65, 71, 3, + 0, 65, 39, 43, 40, 31, 40, 39, 23, 31, 34, 21, 6, + 10, 2, 86, 23, 12, 4, 79, 71, 69, 70, 66, 68, 73, + 69, 70, 67, 1, 70, 66, 65, 0, 62, 62, 62, 62, 62, + 60, 54, 36, 4, 66, 28, 21, 18, 15, 7, 3, 1, 66, + 76, 85, 81, 77, 81, 80, 73, 74, 83, 71, 67, 2, 66, + 66, 4, 4, 62, 62, 62, 62, 61, 57, 46, 29, 1 }, + + { + + 62, + 9, 74, 62, 9, 74, 125, 102, 11, 10, 12, 29, 60, + 62, 54, 14, 115, 6, 77, 64, 1, 14, 72, 12, 65, + 20, 62, 68, 91, 104, 124, 102, 67, 77, 64, 1, + 85, 93, 3, 18, 68, 80, 95, 8, 67, 85, 88, 5, 75, + 93, 9, 69, 80, 88, 66, 73, 73, 79, 71, 5, 22, 0, + 0, 0, 82, 86, 97, 71, 22, 1, 18, 77, 95, 124, + 96, 99, 65, 80, 92, 82, 108, 89, 100, 92, 125, + 91, 88, 95, 107, 18, 64, 7, 92, 73, 91, 86, 124, + 9, 3, 4, 69, 16, 68, 68, 87, 84, 77, 74, 76, 9, + 13, 67, 13, 21, 80, 0, 69, 67, 6, 75, 28, 64, 2, + 28, 37, 39, 34, 27, 92, 72, 72, 17, 14, 99, 10, + 10, 10, 3, 7, 7, 1, 4, 2, 6, 68, 6, 24, 12, 68, + 64, 2, 0, 13, 23, 19, 11, 15, 4, 5, 4, 29, 19, + 20, 77, 3, 69, 35, 23, 19, 14, 17, 19, 12, 16, + 24, 1, 17, 9, 9, 5, 0, 12, 6, 10, 11, 8, 18, 27, + 10, 81, 8, 77, 17, 31, 83, 55, 62, 59, 58, 61, + 62, 56, 56, 52, 43, 35, 32, 41, 28, 71, 67, 4, + 67, 32, 30, 27, 33, 31, 24, 19, 21, 0, 4, 64, + 93, 88, 107, 75, 20, 18, 11, 0, 5, 69, 74, 81, + 100, 76, 39, 21, 15, 8, 5, 70, 82, 86, 117, 5, + 35, 28, 25, 20, 9, 5, 64, 73, 89, 70, 35, 32, + 21, 4, 6, 71, 80, 96, 5, 58, 49, 45, 36, 23, 5, + 69, 78, 84, 62, 105, 105, 86, 112, 108, 97, 108, + 104, 101, 105, 106, 110, 95, 94, 90, 92, 92, 85, + 67, 79, 84, 69, 3, 5, 2, 13, 13, 13, 8, 22, 17, + 13, 14, 11, 22, 16, 8, 22, 19, 13, 10, 14, 0, + 64, 68, 5, 70, 19, 32, 20, 10, 29, 25, 12, 23, + 30, 19, 25, 13, 6, 19, 52, 49, 52, 51, 53, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 33, 62, 62, 62, + 62, 62, 62, 53, 36, 35, 6, 81, 74, 95, 122, 62, + 62, 62, 56, 53, 52, 40, 42, 30, 31, 21, 18, 15, + 66, 71, 3, 0, 66, 38, 42, 39, 30, 39, 38, 22, + 30, 33, 20, 5, 9, 1, 86, 23, 12, 4, 78, 70, 68, + 69, 65, 67, 71, 68, 69, 66, 3, 68, 65, 0, 2, 62, + 62, 62, 62, 62, 58, 51, 34, 2, 65, 29, 22, 19, + 16, 8, 4, 2, 65, 75, 84, 80, 76, 80, 78, 71, 73, + 82, 70, 66, 3, 65, 65, 4, 4, 62, 62, 62, 62, 58, + 54, 43, 26, 64 }, + + { + + 62, + 9, 74, 62, 9, 74, 123, 101, 11, 10, 12, 28, 59, + 61, 54, 14, 113, 6, 76, 0, 1, 13, 72, 11, 66, + 19, 60, 70, 92, 105, 121, 101, 67, 76, 0, 1, 85, + 92, 3, 17, 68, 80, 94, 8, 67, 85, 88, 5, 75, 92, + 9, 69, 80, 88, 66, 73, 73, 79, 71, 5, 22, 0, 0, + 0, 81, 86, 97, 71, 21, 1, 18, 77, 95, 122, 94, + 97, 64, 78, 91, 81, 107, 88, 99, 91, 123, 91, + 88, 95, 106, 18, 64, 7, 91, 73, 90, 86, 123, 9, + 3, 4, 69, 16, 68, 68, 87, 84, 77, 74, 76, 9, 13, + 67, 13, 21, 80, 0, 69, 67, 6, 75, 27, 64, 2, 27, + 36, 38, 33, 26, 91, 72, 72, 16, 13, 99, 9, 10, + 10, 3, 7, 7, 2, 4, 2, 6, 68, 6, 23, 12, 69, 64, + 2, 64, 13, 22, 19, 11, 14, 4, 5, 4, 28, 19, 19, + 77, 3, 70, 34, 23, 19, 14, 17, 19, 12, 16, 24, + 1, 17, 9, 9, 5, 0, 12, 6, 10, 11, 8, 17, 26, 9, + 81, 8, 77, 16, 30, 83, 53, 62, 57, 56, 59, 60, + 54, 54, 50, 41, 33, 30, 39, 26, 72, 67, 4, 68, + 31, 29, 26, 32, 29, 23, 18, 20, 64, 3, 65, 93, + 88, 106, 75, 20, 18, 11, 0, 5, 69, 74, 81, 99, + 75, 39, 21, 15, 8, 5, 70, 81, 85, 115, 5, 35, + 28, 25, 20, 9, 5, 64, 73, 88, 70, 35, 32, 21, 4, + 6, 71, 80, 95, 5, 57, 48, 44, 35, 23, 5, 69, 78, + 84, 62, 104, 104, 85, 111, 107, 96, 107, 103, + 100, 104, 105, 108, 94, 93, 90, 91, 91, 85, 68, + 79, 83, 69, 3, 4, 2, 12, 12, 12, 7, 21, 17, 13, + 14, 10, 21, 16, 8, 21, 18, 13, 10, 13, 0, 64, + 68, 5, 70, 18, 31, 19, 10, 28, 24, 12, 22, 29, + 19, 25, 12, 5, 17, 51, 48, 51, 50, 52, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 32, 62, 62, 62, 62, + 62, 62, 51, 35, 34, 6, 80, 74, 94, 120, 60, 60, + 62, 54, 51, 50, 38, 40, 29, 29, 20, 16, 14, 67, + 72, 2, 0, 67, 37, 41, 37, 28, 37, 36, 21, 28, + 31, 19, 4, 8, 0, 87, 22, 11, 3, 78, 70, 68, 68, + 65, 66, 70, 67, 68, 65, 4, 67, 64, 1, 3, 62, 62, + 62, 62, 60, 55, 48, 31, 0, 65, 29, 22, 19, 16, + 9, 4, 2, 65, 75, 84, 80, 75, 80, 77, 70, 73, 81, + 69, 65, 3, 65, 64, 4, 4, 62, 62, 62, 60, 55, 50, + 39, 23, 67 }, + + { + + 62, + 9, 74, 62, 9, 74, 121, 99, 12, 10, 11, 26, 57, + 60, 54, 14, 111, 6, 75, 1, 1, 12, 72, 10, 67, + 19, 58, 71, 93, 105, 118, 100, 67, 75, 1, 1, 84, + 91, 4, 17, 68, 79, 93, 7, 68, 85, 88, 5, 75, 92, + 9, 69, 80, 88, 65, 73, 73, 79, 70, 5, 22, 0, 0, + 0, 81, 86, 97, 70, 20, 1, 18, 77, 95, 120, 92, + 96, 1, 76, 90, 80, 105, 87, 98, 90, 121, 90, 88, + 94, 105, 18, 64, 7, 91, 73, 90, 85, 121, 9, 2, + 3, 70, 16, 68, 68, 86, 84, 76, 74, 75, 9, 13, + 67, 13, 20, 80, 0, 69, 67, 6, 75, 26, 64, 2, 26, + 35, 37, 32, 25, 91, 71, 72, 15, 13, 98, 9, 10, + 10, 3, 7, 7, 3, 4, 2, 6, 67, 6, 22, 12, 70, 64, + 2, 64, 12, 21, 19, 11, 13, 4, 5, 4, 26, 19, 18, + 77, 3, 70, 33, 23, 19, 14, 17, 19, 12, 16, 24, + 1, 16, 9, 9, 5, 0, 11, 5, 9, 10, 7, 16, 25, 9, + 81, 7, 77, 15, 28, 83, 52, 62, 55, 54, 57, 58, + 52, 52, 48, 39, 32, 29, 37, 24, 73, 67, 4, 68, + 30, 28, 25, 30, 28, 21, 17, 19, 65, 3, 65, 93, + 88, 106, 74, 20, 18, 11, 0, 5, 69, 74, 80, 98, + 75, 39, 21, 15, 8, 6, 69, 80, 84, 113, 5, 35, + 28, 25, 20, 10, 5, 64, 73, 88, 70, 35, 32, 20, + 4, 6, 71, 80, 94, 5, 57, 48, 43, 34, 23, 5, 69, + 77, 83, 62, 103, 103, 85, 110, 106, 95, 105, + 102, 99, 103, 103, 107, 94, 92, 90, 91, 89, 85, + 68, 79, 83, 69, 2, 4, 2, 11, 11, 11, 6, 21, 16, + 13, 13, 10, 21, 15, 8, 20, 18, 12, 10, 12, 0, + 65, 68, 5, 71, 18, 31, 18, 10, 27, 24, 12, 21, + 28, 18, 24, 11, 5, 16, 50, 47, 51, 49, 51, 61, + 62, 62, 62, 62, 62, 62, 62, 62, 31, 62, 62, 62, + 62, 62, 62, 49, 34, 33, 6, 79, 74, 93, 118, 58, + 58, 62, 52, 49, 48, 37, 38, 27, 28, 19, 15, 12, + 68, 73, 2, 64, 68, 36, 39, 36, 26, 35, 34, 19, + 27, 29, 17, 3, 6, 65, 88, 21, 10, 2, 78, 69, 68, + 68, 64, 66, 69, 66, 67, 64, 5, 66, 0, 3, 4, 62, + 62, 62, 62, 58, 52, 45, 28, 65, 64, 30, 23, 20, + 16, 10, 5, 2, 64, 74, 84, 79, 75, 79, 76, 69, + 73, 81, 69, 65, 3, 64, 0, 4, 4, 62, 62, 62, 57, + 52, 46, 35, 19, 69 }, + + { + + 62, + 9, 74, 62, 9, 74, 120, 98, 12, 10, 11, 25, 56, + 58, 54, 14, 108, 5, 74, 1, 1, 11, 72, 9, 68, 18, + 56, 73, 94, 106, 115, 99, 67, 74, 1, 1, 84, 90, + 4, 16, 68, 79, 93, 7, 68, 84, 88, 5, 75, 91, 8, + 70, 80, 88, 65, 72, 73, 78, 70, 5, 22, 0, 0, 0, + 80, 87, 97, 70, 19, 1, 18, 77, 95, 119, 91, 94, + 2, 75, 89, 79, 104, 85, 97, 89, 119, 90, 87, 94, + 104, 18, 64, 7, 90, 73, 89, 85, 120, 8, 2, 3, + 70, 16, 68, 68, 86, 84, 76, 74, 75, 9, 12, 67, + 13, 20, 80, 0, 69, 67, 6, 75, 26, 65, 2, 26, 34, + 36, 31, 24, 90, 71, 72, 14, 12, 98, 8, 10, 9, 3, + 7, 7, 4, 5, 2, 5, 67, 5, 21, 11, 71, 64, 2, 65, + 12, 20, 18, 10, 13, 5, 5, 4, 25, 18, 17, 77, 3, + 71, 33, 23, 19, 14, 17, 19, 12, 16, 23, 1, 16, + 9, 9, 5, 64, 11, 5, 9, 10, 7, 16, 24, 8, 81, 7, + 77, 14, 27, 83, 50, 62, 53, 52, 55, 56, 50, 50, + 46, 37, 30, 27, 34, 22, 74, 67, 3, 69, 29, 27, + 24, 29, 26, 20, 16, 17, 65, 2, 66, 93, 88, 105, + 74, 20, 18, 11, 0, 5, 69, 74, 80, 97, 74, 39, + 21, 15, 8, 6, 69, 80, 84, 111, 5, 35, 28, 25, + 20, 10, 5, 64, 73, 87, 70, 35, 31, 20, 4, 6, 71, + 80, 94, 5, 56, 47, 42, 33, 23, 5, 69, 77, 83, + 62, 102, 102, 84, 108, 105, 94, 104, 100, 98, + 101, 102, 105, 93, 92, 89, 90, 88, 84, 69, 79, + 82, 69, 2, 3, 1, 10, 10, 10, 5, 20, 16, 13, 13, + 9, 20, 15, 8, 19, 17, 12, 9, 11, 64, 65, 68, 5, + 71, 17, 30, 17, 10, 26, 23, 12, 20, 27, 18, 24, + 10, 4, 14, 49, 47, 50, 48, 49, 60, 62, 62, 62, + 62, 62, 62, 62, 62, 29, 62, 62, 62, 62, 62, 62, + 47, 33, 31, 6, 78, 73, 92, 116, 57, 56, 60, 51, + 47, 46, 35, 36, 26, 26, 17, 13, 11, 69, 74, 1, + 64, 69, 34, 38, 34, 25, 33, 32, 18, 25, 27, 16, + 2, 5, 66, 88, 20, 10, 1, 78, 69, 67, 67, 64, 65, + 68, 66, 66, 0, 6, 65, 1, 4, 5, 62, 62, 62, 61, + 55, 49, 42, 25, 68, 64, 30, 23, 20, 17, 10, 5, + 3, 64, 74, 83, 79, 74, 79, 75, 68, 73, 80, 68, + 64, 3, 64, 1, 4, 4, 62, 62, 61, 54, 49, 42, 31, + 16, 72 }, + + { + + 62, + 9, 74, 62, 9, 74, 118, 96, 12, 10, 10, 23, 54, + 57, 54, 14, 106, 5, 73, 2, 1, 11, 71, 8, 69, 18, + 54, 75, 95, 106, 112, 97, 67, 73, 2, 1, 84, 89, + 4, 16, 68, 79, 92, 7, 69, 84, 88, 5, 75, 90, 8, + 70, 80, 88, 64, 72, 72, 78, 69, 5, 22, 0, 0, 0, + 80, 87, 97, 69, 18, 1, 18, 76, 95, 117, 89, 93, + 4, 73, 87, 78, 103, 84, 96, 88, 117, 89, 87, 93, + 103, 18, 64, 7, 90, 73, 89, 84, 118, 8, 2, 3, + 70, 16, 68, 67, 85, 84, 76, 74, 74, 9, 12, 67, + 13, 20, 79, 0, 68, 67, 6, 75, 25, 65, 2, 25, 33, + 36, 30, 23, 89, 70, 72, 13, 12, 97, 8, 10, 9, 3, + 7, 7, 5, 5, 2, 5, 67, 5, 20, 11, 72, 64, 2, 65, + 11, 19, 18, 10, 12, 5, 5, 4, 24, 18, 16, 77, 3, + 71, 32, 23, 19, 14, 17, 19, 12, 16, 23, 1, 16, + 9, 9, 5, 64, 11, 5, 8, 10, 7, 15, 23, 8, 81, 6, + 77, 13, 26, 83, 49, 61, 52, 51, 53, 54, 48, 48, + 44, 35, 28, 25, 32, 21, 75, 67, 3, 69, 28, 26, + 23, 28, 25, 18, 15, 16, 66, 2, 66, 93, 88, 105, + 74, 20, 18, 11, 0, 5, 68, 73, 79, 96, 74, 39, + 21, 15, 8, 6, 68, 79, 83, 109, 5, 35, 28, 25, + 20, 10, 5, 64, 73, 86, 70, 36, 31, 19, 4, 6, 71, + 80, 93, 5, 56, 46, 41, 32, 23, 5, 69, 77, 82, + 62, 101, 101, 83, 107, 104, 93, 103, 99, 97, + 100, 100, 103, 92, 91, 89, 90, 87, 84, 69, 78, + 81, 69, 1, 3, 1, 10, 9, 9, 4, 19, 15, 13, 12, 9, + 20, 15, 8, 18, 16, 12, 9, 10, 64, 65, 68, 5, 71, + 16, 30, 17, 10, 25, 22, 12, 19, 26, 17, 23, 9, + 3, 12, 48, 46, 50, 47, 48, 58, 62, 62, 62, 62, + 62, 62, 62, 62, 28, 62, 62, 62, 62, 62, 61, 45, + 32, 30, 6, 77, 73, 91, 114, 55, 55, 58, 49, 45, + 44, 34, 34, 25, 24, 16, 11, 9, 70, 75, 1, 64, + 70, 33, 36, 32, 23, 32, 31, 16, 24, 26, 14, 1, + 4, 67, 89, 20, 9, 0, 77, 68, 67, 67, 0, 64, 67, + 65, 65, 1, 8, 64, 2, 5, 7, 62, 62, 62, 58, 53, + 46, 39, 22, 70, 64, 31, 24, 21, 17, 11, 5, 3, 0, + 73, 83, 79, 73, 78, 74, 67, 72, 79, 68, 64, 3, + 0, 2, 4, 4, 62, 62, 58, 51, 46, 39, 27, 12, 75 }, + + { + + 62, + 9, 75, 62, 9, 75, 116, 95, 13, 10, 10, 22, 53, + 56, 54, 14, 104, 5, 73, 3, 1, 10, 71, 7, 70, 17, + 53, 76, 96, 107, 109, 96, 67, 73, 3, 1, 83, 88, + 5, 15, 67, 78, 91, 6, 69, 84, 88, 5, 74, 90, 8, + 70, 79, 88, 64, 72, 72, 78, 69, 5, 22, 0, 0, 0, + 79, 87, 97, 69, 18, 0, 18, 76, 94, 115, 87, 91, + 5, 71, 86, 77, 101, 83, 95, 88, 116, 89, 87, 93, + 103, 19, 64, 7, 89, 72, 88, 84, 117, 8, 1, 2, + 71, 16, 68, 67, 85, 84, 75, 74, 74, 9, 12, 66, + 13, 19, 79, 0, 68, 67, 6, 75, 24, 65, 2, 24, 32, + 35, 30, 23, 89, 70, 72, 13, 11, 97, 7, 10, 9, 3, + 7, 7, 5, 5, 2, 5, 66, 5, 19, 11, 72, 65, 2, 66, + 11, 18, 18, 10, 11, 5, 5, 4, 22, 18, 15, 77, 3, + 72, 31, 23, 18, 14, 17, 19, 12, 16, 23, 1, 15, + 9, 8, 5, 64, 10, 4, 8, 9, 6, 14, 22, 7, 81, 6, + 76, 12, 24, 83, 47, 59, 50, 49, 51, 52, 46, 46, + 42, 33, 27, 24, 30, 19, 76, 67, 3, 70, 27, 25, + 22, 26, 23, 17, 14, 15, 67, 1, 67, 93, 88, 104, + 73, 20, 18, 11, 1, 5, 68, 73, 79, 95, 73, 38, + 21, 15, 8, 7, 68, 78, 82, 107, 5, 36, 28, 25, + 20, 11, 5, 64, 72, 86, 70, 36, 31, 19, 4, 6, 70, + 79, 92, 5, 55, 46, 40, 32, 23, 5, 68, 76, 82, + 62, 101, 100, 83, 106, 103, 92, 101, 98, 96, 99, + 99, 102, 92, 90, 89, 89, 85, 84, 70, 78, 81, 69, + 1, 2, 1, 9, 8, 8, 3, 19, 15, 13, 12, 8, 19, 14, + 8, 18, 16, 11, 9, 10, 64, 66, 68, 5, 72, 16, 29, + 16, 9, 24, 22, 13, 19, 25, 17, 23, 9, 3, 11, 47, + 45, 49, 46, 47, 57, 62, 62, 62, 62, 62, 62, 62, + 61, 27, 62, 62, 62, 62, 62, 59, 43, 31, 29, 6, + 76, 73, 89, 111, 53, 53, 56, 47, 43, 42, 32, 32, + 23, 23, 15, 10, 8, 71, 76, 0, 65, 71, 32, 35, + 31, 21, 30, 29, 15, 22, 24, 13, 64, 2, 69, 90, + 19, 8, 64, 77, 68, 67, 66, 0, 64, 65, 64, 64, 2, + 9, 1, 3, 7, 8, 62, 62, 60, 56, 50, 44, 36, 20, + 72, 0, 31, 24, 21, 17, 12, 6, 3, 0, 73, 83, 78, + 73, 78, 73, 66, 72, 79, 67, 0, 3, 0, 3, 4, 4, + 62, 62, 56, 48, 42, 35, 24, 9, 77 }, + + { + + 62, + 9, 75, 62, 9, 75, 114, 93, 13, 10, 9, 20, 51, + 54, 54, 14, 101, 4, 72, 3, 1, 9, 71, 6, 71, 17, + 51, 78, 97, 107, 106, 95, 67, 72, 3, 1, 83, 87, + 5, 15, 67, 78, 91, 6, 70, 83, 88, 5, 74, 89, 7, + 70, 79, 88, 0, 71, 72, 77, 68, 5, 22, 0, 0, 0, + 79, 87, 97, 68, 17, 0, 18, 76, 94, 114, 85, 90, + 7, 69, 85, 76, 100, 81, 94, 87, 114, 88, 86, 92, + 102, 19, 64, 7, 89, 72, 88, 83, 115, 7, 1, 2, + 71, 16, 68, 67, 84, 84, 75, 74, 73, 9, 11, 66, + 13, 19, 79, 0, 68, 67, 6, 75, 24, 65, 2, 24, 31, + 34, 29, 22, 88, 69, 72, 12, 11, 96, 7, 10, 8, 3, + 7, 7, 6, 6, 2, 5, 66, 5, 18, 11, 73, 65, 2, 66, + 10, 17, 17, 10, 11, 6, 5, 4, 21, 17, 14, 77, 3, + 72, 31, 23, 18, 14, 17, 19, 12, 16, 23, 1, 15, + 9, 8, 5, 64, 10, 4, 7, 9, 6, 14, 21, 7, 81, 5, + 76, 11, 23, 83, 46, 57, 48, 47, 49, 50, 44, 44, + 40, 31, 25, 22, 27, 17, 77, 67, 2, 70, 26, 24, + 21, 25, 22, 15, 13, 14, 67, 1, 67, 93, 88, 104, + 73, 20, 18, 11, 1, 5, 68, 73, 78, 94, 73, 38, + 21, 15, 8, 7, 67, 77, 82, 105, 5, 36, 28, 25, + 20, 11, 5, 64, 72, 85, 70, 36, 30, 18, 4, 6, 70, + 79, 92, 5, 55, 45, 39, 31, 23, 5, 68, 76, 81, + 62, 100, 99, 82, 104, 102, 91, 100, 96, 95, 97, + 97, 100, 91, 89, 88, 89, 84, 83, 70, 78, 80, 69, + 0, 2, 0, 8, 7, 7, 2, 18, 14, 13, 11, 8, 19, 14, + 8, 17, 15, 11, 8, 9, 64, 66, 68, 5, 72, 15, 29, + 15, 9, 23, 21, 13, 18, 24, 16, 22, 8, 2, 9, 46, + 45, 49, 45, 45, 55, 62, 62, 62, 62, 62, 62, 62, + 59, 25, 62, 62, 62, 62, 62, 56, 41, 30, 28, 6, + 75, 72, 88, 109, 52, 51, 54, 46, 41, 40, 31, 30, + 22, 21, 13, 8, 6, 72, 77, 0, 65, 72, 30, 33, 29, + 20, 28, 27, 13, 21, 22, 11, 65, 1, 70, 90, 18, + 8, 65, 77, 67, 66, 66, 1, 0, 64, 0, 0, 3, 10, 2, + 4, 8, 9, 62, 61, 58, 53, 48, 41, 33, 17, 74, 0, + 32, 25, 22, 18, 13, 6, 4, 1, 72, 82, 78, 72, 77, + 72, 65, 72, 78, 67, 0, 3, 1, 4, 4, 4, 62, 62, + 53, 45, 39, 31, 20, 5, 80 }, + + { + + 62, + 8, 75, 62, 8, 75, 113, 92, 13, 10, 9, 19, 50, + 53, 54, 14, 99, 4, 71, 4, 1, 8, 71, 5, 73, 16, + 49, 80, 98, 108, 104, 94, 67, 71, 4, 1, 83, 86, + 5, 14, 67, 78, 90, 5, 70, 83, 89, 5, 74, 89, 7, + 71, 79, 88, 0, 71, 72, 77, 68, 5, 22, 0, 0, 0, + 78, 88, 97, 68, 16, 0, 18, 76, 94, 112, 84, 88, + 8, 68, 84, 75, 99, 80, 93, 86, 112, 88, 86, 92, + 101, 19, 64, 7, 88, 72, 87, 83, 114, 7, 0, 1, + 72, 16, 68, 67, 84, 84, 75, 74, 73, 8, 11, 66, + 13, 18, 79, 0, 68, 67, 5, 75, 23, 66, 2, 23, 29, + 33, 28, 21, 88, 69, 72, 11, 10, 96, 6, 9, 8, 3, + 7, 7, 7, 6, 2, 4, 66, 4, 17, 10, 74, 65, 2, 67, + 10, 16, 17, 9, 10, 6, 5, 4, 19, 17, 13, 77, 3, + 73, 30, 22, 18, 14, 17, 18, 11, 16, 22, 0, 14, + 9, 8, 4, 65, 9, 3, 7, 8, 5, 13, 20, 6, 81, 5, + 76, 10, 21, 83, 44, 55, 46, 45, 47, 47, 42, 42, + 38, 29, 23, 20, 25, 15, 78, 67, 2, 71, 25, 22, + 19, 23, 20, 14, 11, 12, 68, 0, 68, 93, 88, 103, + 73, 20, 18, 11, 1, 5, 68, 73, 78, 93, 72, 38, + 21, 15, 8, 7, 67, 77, 81, 104, 5, 36, 28, 25, + 19, 11, 5, 64, 72, 85, 70, 36, 30, 18, 4, 6, 70, + 79, 91, 5, 54, 44, 38, 30, 22, 5, 68, 76, 81, + 62, 99, 98, 82, 103, 101, 91, 99, 95, 94, 96, + 96, 99, 91, 89, 88, 88, 83, 83, 71, 78, 80, 69, + 0, 1, 0, 7, 6, 5, 1, 17, 14, 13, 11, 7, 18, 13, + 7, 16, 14, 10, 8, 8, 65, 67, 68, 5, 73, 14, 28, + 14, 9, 22, 20, 13, 17, 23, 16, 22, 7, 1, 7, 45, + 44, 48, 43, 44, 54, 62, 62, 62, 62, 62, 62, 62, + 56, 24, 62, 62, 62, 62, 61, 54, 39, 28, 26, 6, + 75, 72, 87, 107, 50, 49, 52, 44, 38, 38, 29, 28, + 20, 19, 12, 6, 5, 73, 78, 64, 66, 73, 29, 32, + 27, 18, 26, 25, 12, 19, 20, 10, 66, 64, 72, 91, + 17, 7, 66, 77, 67, 66, 65, 1, 0, 0, 0, 1, 4, 11, + 3, 5, 9, 10, 61, 59, 56, 51, 45, 38, 30, 14, 77, + 0, 32, 25, 22, 18, 13, 6, 4, 1, 72, 82, 78, 72, + 77, 71, 64, 72, 78, 66, 1, 3, 1, 4, 4, 3, 62, + 61, 51, 42, 36, 27, 16, 2, 83 }, + + { + + 62, + 8, 75, 62, 8, 75, 111, 91, 14, 10, 9, 18, 49, + 52, 54, 14, 97, 4, 70, 5, 1, 8, 70, 4, 74, 15, + 47, 81, 99, 109, 101, 92, 67, 70, 5, 1, 82, 85, + 6, 13, 67, 77, 89, 5, 70, 83, 89, 5, 74, 88, 7, + 71, 79, 88, 0, 71, 71, 77, 68, 5, 22, 0, 0, 0, + 77, 88, 97, 68, 15, 0, 18, 75, 94, 110, 82, 86, + 9, 66, 82, 74, 97, 79, 91, 85, 110, 88, 86, 92, + 100, 19, 64, 7, 87, 72, 86, 82, 113, 7, 0, 1, + 72, 16, 68, 66, 83, 83, 74, 74, 73, 8, 11, 66, + 13, 18, 78, 0, 67, 67, 5, 74, 22, 66, 2, 22, 28, + 33, 27, 20, 87, 69, 71, 10, 9, 96, 5, 9, 8, 4, + 7, 7, 8, 6, 2, 4, 65, 4, 17, 10, 75, 65, 2, 68, + 10, 15, 17, 9, 9, 6, 5, 4, 18, 17, 13, 77, 3, + 74, 29, 22, 18, 14, 17, 18, 11, 16, 22, 0, 14, + 9, 8, 4, 65, 9, 3, 7, 8, 5, 12, 20, 6, 81, 5, + 76, 9, 20, 83, 42, 54, 45, 44, 45, 45, 41, 41, + 36, 27, 22, 19, 23, 14, 79, 67, 2, 72, 24, 21, + 18, 22, 19, 13, 10, 11, 69, 64, 69, 93, 87, 102, + 72, 21, 18, 11, 1, 6, 67, 72, 77, 92, 71, 38, + 21, 15, 8, 8, 67, 76, 80, 102, 5, 36, 28, 25, + 19, 12, 5, 64, 72, 84, 70, 37, 30, 18, 4, 7, 70, + 79, 90, 5, 54, 44, 38, 29, 22, 5, 68, 75, 80, + 62, 98, 97, 81, 102, 99, 90, 97, 94, 92, 95, 95, + 97, 90, 88, 88, 87, 81, 83, 72, 77, 79, 69, 0, + 0, 0, 7, 5, 4, 0, 17, 14, 13, 11, 7, 17, 13, 7, + 15, 14, 10, 8, 7, 65, 67, 67, 6, 73, 14, 27, 14, + 9, 22, 20, 13, 16, 22, 16, 22, 6, 1, 6, 45, 43, + 47, 42, 43, 53, 60, 60, 62, 62, 62, 62, 62, 54, + 23, 62, 62, 62, 62, 58, 52, 38, 27, 25, 6, 74, + 72, 86, 105, 48, 48, 50, 42, 36, 37, 28, 26, 19, + 18, 11, 5, 4, 74, 78, 64, 66, 74, 28, 31, 26, + 16, 25, 24, 11, 18, 19, 9, 67, 65, 73, 92, 17, + 6, 66, 76, 67, 66, 64, 2, 1, 1, 1, 2, 5, 13, 4, + 6, 11, 12, 60, 58, 54, 49, 42, 35, 27, 11, 79, + 1, 32, 25, 23, 18, 14, 7, 4, 2, 71, 82, 77, 71, + 77, 70, 1, 71, 77, 65, 2, 3, 2, 5, 4, 3, 62, 59, + 49, 40, 33, 24, 12, 64, 85 }, + + { + + 62, + 8, 75, 62, 8, 75, 109, 89, 14, 10, 8, 16, 47, + 50, 54, 14, 94, 3, 69, 5, 1, 7, 70, 3, 75, 15, + 45, 83, 100, 109, 98, 91, 67, 69, 5, 1, 82, 84, + 6, 13, 67, 77, 89, 5, 71, 82, 89, 5, 74, 87, 6, + 71, 79, 88, 1, 70, 71, 76, 67, 5, 22, 0, 0, 0, + 77, 88, 97, 67, 14, 0, 18, 75, 94, 109, 80, 85, + 11, 64, 81, 73, 96, 77, 90, 84, 108, 87, 85, 91, + 99, 19, 64, 7, 87, 72, 86, 82, 111, 6, 0, 1, 72, + 16, 68, 66, 83, 83, 74, 74, 72, 8, 10, 66, 13, + 18, 78, 0, 67, 67, 5, 74, 22, 66, 2, 22, 27, 32, + 26, 19, 86, 68, 71, 9, 9, 95, 5, 9, 7, 4, 7, 7, + 9, 7, 2, 4, 65, 4, 16, 10, 76, 65, 2, 68, 9, 14, + 16, 9, 9, 7, 5, 4, 17, 16, 12, 77, 3, 74, 29, + 22, 18, 14, 17, 18, 11, 16, 22, 0, 14, 9, 8, 4, + 65, 9, 3, 6, 8, 5, 12, 19, 5, 81, 4, 76, 8, 19, + 83, 41, 52, 43, 42, 43, 43, 39, 39, 34, 25, 20, + 17, 20, 12, 80, 67, 1, 72, 23, 20, 17, 21, 17, + 11, 9, 10, 69, 64, 69, 93, 87, 102, 72, 21, 18, + 11, 1, 6, 67, 72, 77, 91, 71, 38, 21, 15, 8, 8, + 66, 75, 80, 100, 5, 36, 28, 25, 19, 12, 5, 64, + 72, 83, 70, 37, 29, 17, 4, 7, 70, 79, 90, 5, 53, + 43, 37, 28, 22, 5, 68, 75, 80, 62, 97, 96, 80, + 100, 98, 89, 96, 92, 91, 93, 93, 95, 89, 87, 87, + 87, 80, 82, 72, 77, 78, 69, 64, 0, 64, 6, 4, 3, + 64, 16, 13, 13, 10, 6, 17, 13, 7, 14, 13, 10, 7, + 6, 65, 67, 67, 6, 73, 13, 27, 13, 9, 21, 19, 13, + 15, 21, 15, 21, 5, 0, 4, 44, 43, 47, 41, 41, 51, + 58, 58, 62, 62, 62, 62, 62, 52, 21, 59, 62, 59, + 62, 56, 49, 36, 26, 24, 6, 73, 71, 85, 103, 47, + 46, 48, 41, 34, 35, 26, 24, 18, 16, 9, 3, 2, 75, + 79, 65, 66, 75, 26, 29, 24, 15, 23, 22, 9, 16, + 17, 7, 68, 66, 74, 92, 16, 6, 67, 76, 66, 65, + 64, 2, 2, 2, 2, 3, 6, 14, 5, 7, 12, 13, 60, 56, + 52, 46, 40, 32, 24, 8, 81, 1, 33, 26, 23, 19, + 15, 7, 5, 2, 71, 81, 77, 70, 76, 69, 2, 71, 76, + 65, 2, 3, 2, 6, 4, 3, 62, 57, 46, 37, 30, 20, 8, + 68, 88 }, + + { + + 62, + 8, 76, 62, 8, 76, 107, 88, 15, 10, 8, 15, 46, + 49, 54, 14, 92, 3, 69, 6, 1, 6, 70, 2, 76, 14, + 44, 84, 101, 110, 95, 90, 67, 69, 6, 1, 81, 83, + 7, 12, 66, 76, 88, 4, 71, 82, 89, 5, 73, 87, 6, + 71, 78, 88, 1, 70, 71, 76, 67, 5, 22, 0, 0, 0, + 76, 88, 97, 67, 14, 64, 18, 75, 93, 107, 78, 83, + 12, 1, 80, 72, 94, 76, 89, 84, 107, 87, 85, 91, + 99, 20, 64, 7, 86, 71, 85, 81, 110, 6, 64, 0, + 73, 16, 68, 66, 82, 83, 73, 74, 72, 8, 10, 65, + 13, 17, 78, 0, 67, 67, 5, 74, 21, 66, 2, 21, 26, + 31, 26, 19, 86, 68, 71, 9, 8, 95, 4, 9, 7, 4, 7, + 7, 9, 7, 2, 4, 64, 4, 15, 10, 76, 66, 2, 69, 9, + 13, 16, 9, 8, 7, 5, 4, 15, 16, 11, 77, 3, 75, + 28, 22, 17, 14, 17, 18, 11, 16, 22, 0, 13, 9, 7, + 4, 65, 8, 2, 6, 7, 4, 11, 18, 5, 81, 4, 75, 7, + 17, 83, 39, 50, 41, 40, 41, 41, 37, 37, 32, 23, + 19, 16, 18, 10, 81, 67, 1, 73, 22, 19, 16, 19, + 16, 10, 8, 9, 70, 65, 70, 93, 87, 101, 71, 21, + 18, 11, 2, 6, 67, 72, 76, 90, 70, 37, 21, 15, 8, + 9, 66, 74, 79, 98, 5, 37, 28, 25, 19, 13, 5, 64, + 71, 83, 70, 37, 29, 17, 4, 7, 69, 78, 89, 5, 53, + 43, 36, 28, 22, 5, 67, 74, 79, 62, 97, 95, 80, + 99, 97, 88, 94, 91, 90, 92, 92, 94, 89, 86, 87, + 86, 78, 82, 73, 77, 78, 69, 64, 64, 64, 5, 3, 2, + 65, 16, 13, 13, 10, 6, 16, 12, 7, 14, 13, 9, 7, + 6, 65, 68, 67, 6, 74, 13, 26, 12, 8, 20, 19, 14, + 15, 20, 15, 21, 5, 0, 3, 43, 42, 46, 40, 40, 50, + 56, 56, 61, 60, 62, 62, 60, 49, 20, 57, 62, 56, + 62, 53, 47, 34, 25, 23, 6, 72, 71, 83, 100, 45, + 44, 46, 39, 32, 33, 25, 22, 16, 15, 8, 2, 1, 76, + 80, 65, 67, 76, 25, 28, 23, 13, 21, 20, 8, 15, + 15, 6, 70, 68, 76, 93, 15, 5, 68, 76, 66, 65, 0, + 3, 2, 4, 3, 4, 7, 15, 7, 8, 14, 14, 59, 55, 50, + 44, 37, 30, 21, 6, 83, 2, 33, 26, 24, 19, 16, 8, + 5, 3, 70, 81, 76, 70, 76, 68, 3, 71, 76, 64, 3, + 3, 3, 7, 4, 3, 62, 55, 44, 34, 26, 16, 5, 71, 90 }, + + { + + 62, + 8, 76, 62, 8, 76, 106, 86, 15, 10, 7, 13, 44, + 48, 54, 14, 90, 3, 68, 7, 1, 5, 70, 1, 77, 14, + 42, 86, 102, 110, 92, 89, 67, 68, 7, 1, 81, 82, + 7, 12, 66, 76, 87, 4, 72, 82, 89, 5, 73, 86, 6, + 72, 78, 88, 2, 70, 71, 76, 66, 5, 22, 0, 0, 0, + 76, 89, 97, 66, 13, 64, 18, 75, 93, 105, 77, 82, + 14, 2, 79, 71, 93, 75, 88, 83, 105, 86, 85, 90, + 98, 20, 64, 7, 86, 71, 85, 81, 108, 6, 64, 0, + 73, 16, 68, 66, 82, 83, 73, 74, 71, 8, 10, 65, + 13, 17, 78, 0, 67, 67, 5, 74, 20, 67, 2, 20, 25, + 30, 25, 18, 85, 67, 71, 8, 8, 94, 4, 9, 7, 4, 7, + 7, 10, 7, 2, 3, 64, 3, 14, 9, 77, 66, 2, 69, 8, + 12, 16, 8, 7, 7, 5, 4, 14, 16, 10, 77, 3, 75, + 27, 22, 17, 14, 17, 18, 11, 16, 21, 0, 13, 9, 7, + 4, 66, 8, 2, 5, 7, 4, 10, 17, 4, 81, 3, 75, 6, + 16, 83, 38, 48, 39, 38, 39, 39, 35, 35, 30, 21, + 17, 14, 16, 8, 82, 67, 1, 73, 21, 18, 15, 18, + 14, 8, 7, 7, 71, 65, 70, 93, 87, 101, 71, 21, + 18, 11, 2, 6, 67, 72, 76, 89, 70, 37, 21, 15, 8, + 9, 65, 74, 78, 96, 5, 37, 28, 25, 19, 13, 5, 64, + 71, 82, 70, 37, 29, 16, 4, 7, 69, 78, 88, 5, 52, + 42, 35, 27, 22, 5, 67, 74, 79, 62, 96, 94, 79, + 98, 96, 87, 93, 90, 89, 91, 90, 92, 88, 86, 87, + 86, 77, 82, 73, 77, 77, 69, 65, 64, 64, 4, 2, 1, + 66, 15, 12, 13, 9, 5, 16, 12, 7, 13, 12, 9, 7, + 5, 66, 68, 67, 6, 74, 12, 26, 11, 8, 19, 18, 14, + 14, 19, 14, 20, 4, 64, 1, 42, 41, 46, 39, 39, + 48, 54, 54, 59, 57, 62, 62, 57, 47, 19, 54, 62, + 53, 58, 50, 44, 32, 24, 21, 6, 71, 71, 82, 98, + 43, 42, 44, 37, 30, 31, 23, 20, 15, 13, 7, 0, + 64, 77, 81, 66, 67, 77, 24, 26, 21, 11, 19, 18, + 6, 13, 13, 4, 71, 69, 77, 94, 14, 4, 69, 76, 65, + 65, 0, 3, 3, 5, 3, 5, 8, 16, 8, 9, 15, 15, 59, + 53, 48, 41, 35, 27, 18, 3, 86, 2, 34, 27, 24, + 19, 16, 8, 5, 3, 70, 81, 76, 69, 75, 67, 4, 71, + 75, 64, 3, 3, 3, 8, 4, 3, 61, 53, 41, 31, 23, + 12, 1, 75, 93 }, + + { + + 62, + 8, 76, 62, 8, 76, 104, 85, 15, 10, 7, 12, 43, + 46, 54, 14, 87, 2, 67, 7, 1, 5, 69, 0, 78, 13, + 40, 88, 103, 111, 89, 87, 67, 67, 7, 1, 81, 81, + 7, 11, 66, 76, 87, 4, 72, 81, 89, 5, 73, 85, 5, + 72, 78, 88, 2, 69, 70, 75, 66, 5, 22, 0, 0, 0, + 75, 89, 97, 66, 12, 64, 18, 74, 93, 104, 75, 80, + 15, 4, 77, 70, 92, 73, 87, 82, 103, 86, 84, 90, + 97, 20, 64, 7, 85, 71, 84, 80, 107, 5, 64, 0, + 73, 16, 68, 65, 81, 83, 73, 74, 71, 8, 9, 65, + 13, 17, 77, 0, 66, 67, 5, 74, 20, 67, 2, 20, 24, + 30, 24, 17, 84, 67, 71, 7, 7, 94, 3, 9, 6, 4, 7, + 7, 11, 8, 2, 3, 64, 3, 13, 9, 78, 66, 2, 70, 8, + 11, 15, 8, 7, 8, 5, 4, 13, 15, 9, 77, 3, 76, 27, + 22, 17, 14, 17, 18, 11, 16, 21, 0, 13, 9, 7, 4, + 66, 8, 2, 5, 7, 4, 10, 16, 4, 81, 3, 75, 5, 15, + 83, 36, 46, 38, 37, 37, 37, 33, 33, 28, 19, 15, + 12, 13, 7, 83, 67, 0, 74, 20, 17, 14, 17, 13, 7, + 6, 6, 71, 66, 71, 93, 87, 100, 71, 21, 18, 11, + 2, 6, 66, 71, 75, 88, 69, 37, 21, 15, 8, 9, 65, + 73, 78, 94, 5, 37, 28, 25, 19, 13, 5, 64, 71, + 81, 70, 38, 28, 16, 4, 7, 69, 78, 88, 5, 52, 41, + 34, 26, 22, 5, 67, 74, 78, 62, 95, 93, 78, 96, + 95, 86, 92, 88, 88, 89, 89, 90, 87, 85, 86, 85, + 76, 81, 74, 76, 76, 69, 65, 65, 65, 4, 1, 0, 67, + 14, 12, 13, 9, 5, 15, 12, 7, 12, 11, 9, 6, 4, + 66, 68, 67, 6, 74, 11, 25, 11, 8, 18, 17, 14, + 13, 18, 14, 20, 3, 65, 64, 41, 41, 45, 38, 37, + 47, 52, 52, 57, 55, 62, 61, 54, 45, 17, 51, 62, + 50, 54, 48, 42, 30, 23, 20, 6, 70, 70, 81, 96, + 42, 41, 42, 36, 28, 29, 22, 18, 14, 11, 5, 65, + 65, 78, 82, 66, 67, 78, 22, 25, 19, 10, 18, 17, + 5, 12, 12, 3, 72, 70, 78, 94, 14, 4, 70, 75, 65, + 64, 1, 4, 4, 6, 4, 6, 9, 18, 9, 10, 16, 17, 58, + 51, 46, 39, 32, 24, 15, 0, 88, 2, 34, 27, 25, + 20, 17, 8, 6, 4, 69, 80, 76, 68, 75, 66, 5, 70, + 74, 0, 4, 3, 4, 9, 4, 3, 59, 51, 39, 28, 20, 9, + 66, 78, 96 }, + + { + + 61, + 8, 76, 61, 8, 76, 102, 83, 16, 10, 6, 10, 41, + 45, 54, 14, 85, 2, 66, 8, 1, 4, 69, 64, 79, 13, + 38, 89, 104, 111, 86, 86, 67, 66, 8, 1, 80, 80, + 8, 11, 66, 75, 86, 3, 73, 81, 89, 5, 73, 85, 5, + 72, 78, 88, 3, 69, 70, 75, 65, 5, 22, 0, 0, 0, + 75, 89, 97, 65, 11, 64, 18, 74, 93, 102, 73, 79, + 17, 6, 76, 69, 90, 72, 86, 81, 101, 85, 84, 89, + 96, 20, 64, 7, 85, 71, 84, 80, 105, 5, 65, 64, + 74, 16, 68, 65, 81, 83, 72, 74, 70, 8, 9, 65, + 13, 16, 77, 0, 66, 67, 5, 74, 19, 67, 2, 19, 23, + 29, 23, 16, 84, 66, 71, 6, 7, 93, 3, 9, 6, 4, 7, + 7, 12, 8, 2, 3, 0, 3, 12, 9, 79, 66, 2, 70, 7, + 10, 15, 8, 6, 8, 5, 4, 11, 15, 8, 77, 3, 76, 26, + 22, 17, 14, 17, 18, 11, 16, 21, 0, 12, 9, 7, 4, + 66, 7, 1, 4, 6, 3, 9, 15, 3, 81, 2, 75, 4, 13, + 83, 35, 44, 36, 35, 35, 35, 31, 31, 26, 17, 14, + 11, 11, 5, 84, 67, 0, 74, 19, 16, 13, 15, 11, 5, + 5, 5, 72, 66, 71, 93, 87, 100, 70, 21, 18, 11, + 2, 6, 66, 71, 75, 87, 69, 37, 21, 15, 8, 10, 64, + 72, 77, 92, 5, 37, 28, 25, 19, 14, 5, 64, 71, + 81, 70, 38, 28, 15, 4, 7, 69, 78, 87, 5, 51, 41, + 33, 25, 22, 5, 67, 73, 78, 62, 94, 92, 78, 95, + 94, 85, 90, 87, 87, 88, 87, 89, 87, 84, 86, 85, + 74, 81, 74, 76, 76, 69, 66, 65, 65, 3, 0, 64, + 68, 14, 11, 13, 8, 4, 15, 11, 7, 11, 11, 8, 6, + 3, 66, 69, 67, 6, 75, 11, 25, 10, 8, 17, 17, 14, + 12, 17, 13, 19, 2, 65, 65, 40, 40, 45, 37, 36, + 45, 50, 50, 55, 52, 60, 59, 51, 42, 16, 48, 62, + 47, 50, 45, 39, 28, 22, 19, 6, 69, 70, 80, 94, + 40, 39, 40, 34, 26, 27, 20, 16, 12, 10, 4, 66, + 67, 79, 83, 67, 68, 79, 21, 23, 18, 8, 16, 15, + 3, 10, 10, 1, 73, 72, 80, 95, 13, 3, 71, 75, 64, + 64, 1, 4, 4, 7, 5, 7, 10, 19, 10, 11, 18, 18, + 58, 50, 44, 36, 30, 21, 12, 66, 90, 3, 35, 28, + 25, 20, 18, 9, 6, 4, 69, 80, 75, 68, 74, 65, 6, + 70, 74, 0, 4, 3, 4, 10, 4, 3, 58, 49, 36, 25, + 17, 5, 70, 82, 98 }, + + { + + 60, + 8, 76, 60, 8, 76, 100, 82, 16, 10, 6, 9, 40, 44, + 54, 14, 83, 2, 65, 9, 1, 3, 69, 65, 80, 12, 36, + 91, 105, 112, 83, 85, 67, 65, 9, 1, 80, 79, 8, + 10, 66, 75, 85, 3, 73, 81, 89, 5, 73, 84, 5, 72, + 78, 88, 3, 69, 70, 75, 65, 5, 22, 0, 0, 0, 74, + 89, 97, 65, 10, 64, 18, 74, 93, 100, 71, 77, 18, + 8, 75, 68, 89, 71, 85, 80, 99, 85, 84, 89, 95, + 20, 64, 7, 84, 71, 83, 79, 104, 5, 65, 64, 74, + 16, 68, 65, 80, 83, 72, 74, 70, 8, 9, 65, 13, + 16, 77, 0, 66, 67, 5, 74, 18, 67, 2, 18, 22, 28, + 22, 15, 83, 66, 71, 5, 6, 93, 2, 9, 6, 4, 7, 7, + 13, 8, 2, 3, 0, 3, 11, 9, 80, 66, 2, 71, 7, 9, + 15, 8, 5, 8, 5, 4, 10, 15, 7, 77, 3, 77, 25, 22, + 17, 14, 17, 18, 11, 16, 21, 0, 12, 9, 7, 4, 66, + 7, 1, 4, 6, 3, 8, 14, 3, 81, 2, 75, 3, 12, 83, + 33, 42, 34, 33, 33, 33, 29, 29, 24, 15, 12, 9, + 9, 3, 85, 67, 0, 75, 18, 15, 12, 14, 10, 4, 4, + 4, 73, 67, 72, 93, 87, 99, 70, 21, 18, 11, 2, 6, + 66, 71, 74, 86, 68, 37, 21, 15, 8, 10, 64, 71, + 76, 90, 5, 37, 28, 25, 19, 14, 5, 64, 71, 80, + 70, 38, 28, 15, 4, 7, 69, 78, 86, 5, 51, 40, 32, + 24, 22, 5, 67, 73, 77, 62, 93, 91, 77, 94, 93, + 84, 89, 86, 86, 87, 86, 87, 86, 83, 86, 84, 73, + 81, 75, 76, 75, 69, 66, 66, 65, 2, 64, 65, 69, + 13, 11, 13, 8, 4, 14, 11, 7, 10, 10, 8, 6, 2, + 66, 69, 67, 6, 75, 10, 24, 9, 8, 16, 16, 14, 11, + 16, 13, 19, 1, 66, 67, 39, 39, 44, 36, 35, 44, + 48, 48, 53, 50, 57, 56, 48, 40, 15, 45, 59, 44, + 46, 42, 37, 26, 21, 18, 6, 68, 70, 79, 92, 38, + 37, 38, 32, 24, 25, 19, 14, 11, 8, 3, 68, 68, + 80, 84, 67, 68, 80, 20, 22, 16, 6, 14, 13, 2, 9, + 8, 0, 74, 73, 81, 96, 12, 2, 72, 75, 64, 64, 2, + 5, 5, 8, 6, 8, 11, 20, 11, 12, 19, 19, 57, 48, + 42, 34, 27, 18, 9, 69, 92, 3, 35, 28, 26, 20, + 19, 9, 6, 5, 68, 80, 75, 67, 74, 64, 7, 70, 73, + 1, 5, 3, 5, 11, 4, 3, 57, 47, 34, 22, 14, 1, 74, + 85, 101 }, + + { + + 58, + 7, 77, 58, 7, 77, 99, 81, 16, 10, 5, 7, 38, 42, + 53, 14, 81, 1, 65, 9, 0, 2, 69, 67, 82, 11, 34, + 93, 106, 113, 81, 84, 68, 65, 9, 0, 80, 78, 8, + 9, 66, 75, 85, 2, 74, 81, 90, 5, 73, 84, 4, 73, + 78, 88, 3, 69, 70, 75, 65, 4, 22, 0, 0, 0, 74, + 90, 97, 65, 9, 65, 18, 74, 93, 99, 70, 76, 19, + 9, 74, 67, 88, 70, 84, 80, 98, 85, 84, 89, 95, + 20, 64, 7, 84, 71, 83, 79, 103, 4, 66, 65, 75, + 16, 68, 65, 80, 83, 72, 74, 70, 7, 8, 65, 12, + 15, 77, 64, 66, 67, 4, 74, 17, 68, 1, 17, 20, + 27, 21, 14, 83, 66, 71, 4, 5, 93, 1, 8, 5, 4, 7, + 7, 13, 8, 2, 2, 0, 2, 10, 8, 81, 67, 1, 72, 6, + 8, 14, 7, 4, 8, 5, 4, 8, 14, 6, 77, 3, 78, 24, + 21, 16, 14, 17, 17, 10, 16, 20, 64, 11, 9, 6, 3, + 67, 6, 0, 3, 5, 2, 7, 13, 2, 81, 1, 75, 2, 10, + 83, 31, 40, 32, 31, 31, 30, 27, 27, 22, 13, 10, + 7, 6, 1, 87, 68, 64, 76, 17, 13, 10, 12, 8, 2, + 2, 2, 74, 68, 73, 93, 87, 99, 70, 21, 18, 11, 2, + 6, 66, 71, 74, 85, 68, 36, 21, 15, 8, 10, 64, + 71, 76, 89, 4, 37, 28, 24, 18, 14, 5, 64, 71, + 80, 70, 38, 27, 14, 3, 7, 69, 78, 86, 5, 50, 39, + 31, 23, 21, 5, 67, 73, 77, 62, 93, 90, 77, 93, + 92, 84, 88, 85, 85, 86, 85, 86, 86, 83, 86, 84, + 72, 81, 76, 76, 75, 69, 67, 67, 66, 1, 65, 67, + 71, 12, 10, 13, 7, 3, 13, 10, 6, 9, 9, 7, 5, 1, + 67, 70, 67, 6, 76, 9, 23, 8, 7, 15, 15, 14, 10, + 14, 12, 18, 0, 67, 69, 38, 38, 43, 34, 33, 42, + 46, 46, 50, 47, 54, 53, 45, 37, 13, 42, 55, 41, + 41, 39, 34, 24, 19, 16, 6, 68, 70, 78, 90, 36, + 35, 36, 30, 21, 23, 17, 11, 9, 6, 1, 70, 70, 81, + 85, 68, 69, 82, 18, 20, 14, 4, 12, 11, 0, 7, 6, + 65, 76, 75, 83, 97, 11, 1, 73, 75, 64, 64, 2, 5, + 5, 9, 6, 9, 11, 21, 12, 13, 20, 20, 56, 46, 39, + 31, 24, 15, 5, 72, 95, 3, 35, 28, 26, 20, 19, 9, + 6, 5, 68, 80, 75, 67, 74, 0, 8, 70, 73, 1, 5, 3, + 5, 11, 4, 2, 55, 44, 31, 19, 10, 66, 78, 89, 104 }, + + { + + 57, + 7, 77, 57, 7, 77, 97, 79, 17, 11, 5, 6, 37, 41, + 53, 14, 78, 1, 64, 10, 0, 2, 68, 68, 83, 11, 33, + 94, 107, 113, 78, 82, 68, 64, 10, 0, 79, 76, 9, + 9, 65, 74, 84, 2, 74, 80, 90, 5, 72, 83, 4, 73, + 77, 88, 4, 68, 69, 74, 64, 4, 22, 0, 0, 0, 73, + 90, 97, 64, 9, 65, 18, 73, 92, 97, 68, 74, 21, + 11, 72, 66, 86, 68, 82, 79, 96, 84, 83, 88, 94, + 21, 0, 8, 83, 70, 82, 78, 101, 4, 66, 65, 75, + 17, 68, 64, 79, 82, 71, 73, 69, 7, 8, 64, 12, + 15, 76, 64, 65, 67, 4, 73, 17, 68, 1, 17, 19, + 27, 21, 14, 82, 65, 70, 4, 5, 92, 1, 8, 5, 5, 7, + 7, 14, 9, 3, 2, 1, 2, 10, 8, 81, 67, 1, 72, 6, + 7, 14, 7, 4, 9, 6, 4, 7, 14, 6, 76, 3, 78, 24, + 21, 16, 14, 17, 17, 10, 16, 20, 64, 11, 9, 6, 3, + 67, 6, 0, 3, 5, 2, 7, 13, 2, 80, 1, 74, 2, 9, + 82, 30, 39, 31, 30, 29, 28, 26, 26, 20, 12, 9, + 6, 4, 0, 88, 68, 64, 76, 16, 12, 9, 11, 7, 1, 1, + 1, 74, 68, 73, 92, 86, 98, 69, 22, 18, 11, 3, 7, + 65, 70, 73, 83, 67, 36, 21, 15, 8, 11, 0, 70, + 75, 87, 4, 38, 29, 24, 18, 15, 5, 64, 70, 79, + 70, 39, 27, 14, 3, 8, 68, 77, 85, 5, 50, 39, 31, + 23, 21, 5, 66, 72, 76, 62, 92, 89, 76, 91, 90, + 83, 86, 83, 83, 84, 83, 84, 85, 82, 85, 83, 70, + 80, 76, 75, 74, 68, 67, 67, 66, 1, 65, 68, 72, + 12, 10, 14, 7, 3, 13, 10, 6, 9, 9, 7, 5, 1, 67, + 70, 66, 7, 76, 9, 23, 8, 7, 15, 15, 15, 10, 13, + 12, 18, 0, 67, 70, 38, 38, 43, 33, 32, 41, 44, + 44, 48, 45, 52, 51, 43, 35, 12, 40, 52, 38, 37, + 37, 32, 23, 18, 15, 6, 67, 69, 76, 87, 35, 34, + 35, 29, 19, 22, 16, 9, 8, 5, 0, 71, 71, 82, 85, + 68, 69, 83, 17, 19, 13, 3, 11, 10, 64, 6, 5, 66, + 77, 76, 84, 97, 11, 1, 73, 74, 0, 0, 3, 6, 6, + 11, 7, 10, 12, 23, 14, 14, 22, 22, 56, 45, 37, + 29, 22, 13, 2, 74, 97, 4, 36, 29, 27, 21, 20, + 10, 7, 6, 67, 79, 74, 66, 73, 2, 10, 69, 72, 2, + 6, 4, 6, 12, 4, 2, 54, 42, 29, 17, 7, 69, 81, + 92, 106 }, + + { + + 56, + 7, 77, 56, 7, 77, 95, 78, 17, 11, 5, 5, 36, 40, + 53, 14, 76, 1, 0, 11, 0, 1, 68, 69, 84, 10, 31, + 96, 108, 114, 75, 81, 68, 0, 11, 0, 79, 75, 9, + 8, 65, 74, 83, 2, 74, 80, 90, 5, 72, 82, 4, 73, + 77, 88, 4, 68, 69, 74, 64, 4, 22, 0, 0, 0, 72, + 90, 97, 64, 8, 65, 18, 73, 92, 95, 66, 72, 22, + 13, 71, 65, 85, 67, 81, 78, 94, 84, 83, 88, 93, + 21, 0, 8, 82, 70, 81, 78, 100, 4, 66, 65, 75, + 17, 68, 64, 79, 82, 71, 73, 69, 7, 8, 64, 12, + 15, 76, 64, 65, 67, 4, 73, 16, 68, 1, 16, 18, + 26, 20, 13, 81, 65, 70, 3, 4, 92, 0, 8, 5, 5, 7, + 7, 15, 9, 3, 2, 1, 2, 9, 8, 82, 67, 1, 73, 6, 6, + 14, 7, 3, 9, 6, 4, 6, 14, 5, 76, 3, 79, 23, 21, + 16, 14, 17, 17, 10, 16, 20, 64, 11, 9, 6, 3, 67, + 6, 0, 3, 5, 2, 6, 12, 1, 80, 1, 74, 1, 8, 82, + 28, 37, 29, 28, 27, 26, 24, 24, 18, 10, 7, 4, 2, + 65, 89, 68, 64, 77, 15, 11, 8, 10, 5, 0, 0, 0, + 75, 69, 74, 92, 86, 97, 69, 22, 18, 11, 3, 7, + 65, 70, 73, 82, 66, 36, 21, 15, 8, 11, 0, 69, + 74, 85, 4, 38, 29, 24, 18, 15, 5, 64, 70, 78, + 70, 39, 27, 14, 3, 8, 68, 77, 84, 5, 49, 38, 30, + 22, 21, 5, 66, 72, 76, 62, 91, 88, 75, 90, 89, + 82, 85, 82, 82, 83, 82, 82, 84, 81, 85, 82, 69, + 80, 77, 75, 73, 68, 67, 68, 66, 0, 66, 69, 73, + 11, 10, 14, 7, 2, 12, 10, 6, 8, 8, 7, 5, 0, 67, + 70, 66, 7, 76, 8, 22, 7, 7, 14, 14, 15, 9, 12, + 12, 18, 64, 68, 72, 37, 37, 42, 32, 31, 40, 42, + 42, 46, 43, 49, 48, 40, 33, 11, 37, 49, 35, 33, + 34, 30, 21, 17, 14, 6, 66, 69, 75, 85, 33, 32, + 33, 27, 17, 20, 14, 7, 7, 3, 64, 73, 72, 83, 86, + 69, 69, 84, 16, 18, 11, 1, 9, 8, 65, 4, 3, 67, + 78, 77, 85, 98, 10, 0, 74, 74, 0, 0, 4, 6, 7, + 12, 8, 11, 13, 24, 15, 15, 23, 23, 55, 43, 35, + 27, 19, 10, 64, 77, 99, 4, 36, 29, 27, 21, 21, + 10, 7, 6, 67, 79, 74, 65, 73, 3, 11, 69, 71, 3, + 7, 4, 6, 13, 4, 2, 53, 40, 27, 14, 4, 73, 85, + 95, 109 }, + + { + + 55, + 7, 77, 55, 7, 77, 93, 76, 18, 11, 4, 3, 34, 39, + 53, 14, 74, 1, 1, 12, 0, 0, 68, 70, 85, 10, 29, + 97, 109, 114, 72, 80, 68, 1, 12, 0, 78, 74, 10, + 8, 65, 73, 82, 1, 75, 80, 90, 5, 72, 82, 4, 73, + 77, 88, 5, 68, 69, 74, 0, 4, 22, 0, 0, 0, 72, + 90, 97, 0, 7, 65, 18, 73, 92, 93, 64, 71, 24, + 15, 70, 64, 83, 66, 80, 77, 92, 83, 83, 87, 92, + 21, 0, 8, 82, 70, 81, 77, 98, 4, 67, 66, 76, 17, + 68, 64, 78, 82, 70, 73, 68, 7, 8, 64, 12, 14, + 76, 64, 65, 67, 4, 73, 15, 68, 1, 15, 17, 25, + 19, 12, 81, 64, 70, 2, 4, 91, 0, 8, 5, 5, 7, 7, + 16, 9, 3, 2, 2, 2, 8, 8, 83, 67, 1, 73, 5, 5, + 14, 7, 2, 9, 6, 4, 4, 14, 4, 76, 3, 79, 22, 21, + 16, 14, 17, 17, 10, 16, 20, 64, 10, 9, 6, 3, 67, + 5, 64, 2, 4, 1, 5, 11, 1, 80, 0, 74, 0, 6, 82, + 27, 35, 27, 26, 25, 24, 22, 22, 16, 8, 6, 3, 0, + 67, 90, 68, 64, 77, 14, 10, 7, 8, 4, 65, 64, 64, + 76, 69, 74, 92, 86, 97, 68, 22, 18, 11, 3, 7, + 65, 70, 72, 81, 66, 36, 21, 15, 8, 12, 1, 68, + 73, 83, 4, 38, 29, 24, 18, 16, 5, 64, 70, 78, + 70, 39, 27, 13, 3, 8, 68, 77, 83, 5, 49, 38, 29, + 21, 21, 5, 66, 71, 75, 62, 90, 87, 75, 89, 88, + 81, 83, 81, 81, 82, 80, 81, 84, 80, 85, 82, 67, + 80, 77, 75, 73, 68, 68, 68, 66, 64, 67, 70, 74, + 11, 9, 14, 6, 2, 12, 9, 6, 7, 8, 6, 5, 64, 67, + 71, 66, 7, 77, 8, 22, 6, 7, 13, 14, 15, 8, 11, + 11, 17, 65, 68, 73, 36, 36, 42, 31, 30, 38, 40, + 40, 44, 40, 47, 46, 37, 30, 10, 34, 46, 32, 29, + 31, 27, 19, 16, 13, 6, 65, 69, 74, 83, 31, 30, + 31, 25, 15, 18, 13, 5, 5, 2, 65, 74, 74, 84, 87, + 69, 70, 85, 15, 16, 10, 64, 7, 6, 67, 3, 1, 69, + 79, 79, 87, 99, 9, 64, 75, 74, 1, 0, 4, 7, 7, + 13, 9, 12, 14, 25, 16, 16, 25, 24, 55, 42, 33, + 24, 17, 7, 67, 80, 101, 5, 37, 30, 28, 21, 22, + 11, 7, 7, 66, 79, 73, 65, 72, 4, 12, 69, 71, 3, + 7, 4, 7, 14, 4, 2, 52, 38, 24, 11, 1, 77, 89, + 99, 111 }, + + { + + 53, + 7, 77, 53, 7, 77, 92, 75, 18, 11, 4, 2, 33, 37, + 53, 14, 71, 0, 2, 12, 0, 64, 68, 71, 86, 9, 27, + 99, 110, 115, 69, 79, 68, 2, 12, 0, 78, 73, 10, + 7, 65, 73, 82, 1, 75, 79, 90, 5, 72, 81, 3, 74, + 77, 88, 5, 67, 69, 73, 0, 4, 22, 0, 0, 0, 71, + 91, 97, 0, 6, 65, 18, 73, 92, 92, 0, 69, 25, 16, + 69, 0, 82, 64, 79, 76, 90, 83, 82, 87, 91, 21, + 0, 8, 81, 70, 80, 77, 97, 3, 67, 66, 76, 17, 68, + 64, 78, 82, 70, 73, 68, 7, 7, 64, 12, 14, 76, + 64, 65, 67, 4, 73, 15, 69, 1, 15, 16, 24, 18, + 11, 80, 64, 70, 1, 3, 91, 64, 8, 4, 5, 7, 7, 17, + 10, 3, 1, 2, 1, 7, 7, 84, 67, 1, 74, 5, 4, 13, + 6, 2, 10, 6, 4, 3, 13, 3, 76, 3, 80, 22, 21, 16, + 14, 17, 17, 10, 16, 19, 64, 10, 9, 6, 3, 68, 5, + 64, 2, 4, 1, 5, 10, 0, 80, 0, 74, 64, 5, 82, 25, + 33, 25, 24, 23, 22, 20, 20, 14, 6, 4, 1, 66, 69, + 91, 68, 65, 78, 13, 9, 6, 7, 2, 66, 65, 66, 76, + 70, 75, 92, 86, 96, 68, 22, 18, 11, 3, 7, 65, + 70, 72, 80, 65, 36, 21, 15, 8, 12, 1, 68, 73, + 81, 4, 38, 29, 24, 18, 16, 5, 64, 70, 77, 70, + 39, 26, 13, 3, 8, 68, 77, 83, 5, 48, 37, 28, 20, + 21, 5, 66, 71, 75, 62, 89, 86, 74, 87, 87, 80, + 82, 79, 80, 80, 79, 79, 83, 80, 84, 81, 66, 79, + 78, 75, 72, 68, 68, 69, 67, 65, 68, 71, 75, 10, + 9, 14, 6, 1, 11, 9, 6, 6, 7, 6, 4, 65, 68, 71, + 66, 7, 77, 7, 21, 5, 7, 12, 13, 15, 7, 10, 11, + 17, 66, 69, 75, 35, 36, 41, 30, 28, 37, 38, 38, + 42, 38, 44, 43, 34, 28, 8, 31, 42, 29, 25, 29, + 25, 17, 15, 11, 6, 64, 68, 73, 81, 30, 28, 29, + 24, 13, 16, 11, 3, 4, 0, 67, 76, 75, 85, 88, 70, + 70, 86, 13, 15, 8, 65, 5, 4, 68, 1, 64, 70, 80, + 80, 88, 99, 8, 64, 76, 74, 1, 1, 5, 7, 8, 14, 9, + 13, 15, 26, 17, 17, 26, 25, 54, 40, 31, 22, 14, + 4, 70, 83, 104, 5, 37, 30, 28, 22, 22, 11, 8, 7, + 66, 78, 73, 64, 72, 5, 13, 69, 70, 4, 8, 4, 7, + 15, 4, 2, 50, 36, 22, 8, 65, 81, 93, 102, 114 }, + + { + + 52, + 7, 77, 52, 7, 77, 90, 73, 18, 11, 3, 0, 31, 36, + 53, 14, 69, 0, 3, 13, 0, 64, 67, 72, 87, 9, 25, + 101, 111, 115, 66, 77, 68, 3, 13, 0, 78, 72, 10, + 7, 65, 73, 81, 1, 76, 79, 90, 5, 72, 80, 3, 74, + 77, 88, 6, 67, 68, 73, 1, 4, 22, 0, 0, 0, 71, + 91, 97, 1, 5, 65, 18, 72, 92, 90, 2, 68, 27, 18, + 67, 1, 81, 0, 78, 75, 88, 82, 82, 86, 90, 21, 0, + 8, 81, 70, 80, 76, 95, 3, 67, 66, 76, 17, 68, 0, + 77, 82, 70, 73, 67, 7, 7, 64, 12, 14, 75, 64, + 64, 67, 4, 73, 14, 69, 1, 14, 15, 24, 17, 10, + 79, 0, 70, 0, 3, 90, 64, 8, 4, 5, 7, 7, 18, 10, + 3, 1, 2, 1, 6, 7, 85, 67, 1, 74, 4, 3, 13, 6, 1, + 10, 6, 4, 2, 13, 2, 76, 3, 80, 21, 21, 16, 14, + 17, 17, 10, 16, 19, 64, 10, 9, 6, 3, 68, 5, 64, + 1, 4, 1, 4, 9, 0, 80, 64, 74, 65, 4, 82, 24, 31, + 24, 23, 21, 20, 18, 18, 12, 4, 2, 64, 68, 70, + 92, 68, 65, 78, 12, 8, 5, 6, 1, 68, 66, 67, 77, + 70, 75, 92, 86, 96, 68, 22, 18, 11, 3, 7, 64, + 69, 71, 79, 65, 36, 21, 15, 8, 12, 2, 67, 72, + 79, 4, 38, 29, 24, 18, 16, 5, 64, 70, 76, 70, + 40, 26, 12, 3, 8, 68, 77, 82, 5, 48, 36, 27, 19, + 21, 5, 66, 71, 74, 62, 88, 85, 73, 86, 86, 79, + 81, 78, 79, 79, 77, 77, 82, 79, 84, 81, 65, 79, + 78, 74, 71, 68, 69, 69, 67, 65, 69, 72, 76, 9, + 8, 14, 5, 1, 11, 9, 6, 5, 6, 6, 4, 66, 68, 71, + 66, 7, 77, 6, 21, 5, 7, 11, 12, 15, 6, 9, 10, + 16, 67, 70, 77, 34, 35, 41, 29, 27, 35, 36, 36, + 40, 35, 41, 41, 31, 26, 7, 28, 39, 26, 21, 26, + 22, 15, 14, 10, 6, 0, 68, 72, 79, 28, 27, 27, + 22, 11, 14, 10, 1, 3, 65, 68, 78, 77, 86, 89, + 70, 70, 87, 12, 13, 6, 67, 4, 3, 70, 0, 65, 72, + 81, 81, 89, 100, 8, 65, 77, 73, 2, 1, 5, 8, 9, + 15, 10, 14, 16, 28, 18, 18, 27, 27, 54, 38, 29, + 19, 12, 1, 73, 86, 106, 5, 38, 31, 29, 22, 23, + 11, 8, 8, 65, 78, 73, 0, 71, 6, 14, 68, 69, 4, + 8, 4, 8, 16, 4, 2, 49, 34, 19, 5, 68, 84, 97, + 106, 117 }, + + { + + 51, + 7, 78, 51, 7, 78, 88, 72, 19, 11, 3, 64, 30, 35, + 53, 14, 67, 0, 3, 14, 0, 65, 67, 73, 88, 8, 24, + 102, 112, 116, 0, 76, 68, 3, 14, 0, 77, 71, 11, + 6, 64, 72, 80, 0, 76, 79, 90, 5, 71, 80, 3, 74, + 76, 88, 6, 67, 68, 73, 1, 4, 22, 0, 0, 0, 70, + 91, 97, 1, 5, 66, 18, 72, 91, 88, 4, 66, 28, 20, + 66, 2, 79, 1, 77, 75, 87, 82, 82, 86, 90, 22, 0, + 8, 80, 69, 79, 76, 94, 3, 68, 67, 77, 17, 68, 0, + 77, 82, 69, 73, 67, 7, 7, 0, 12, 13, 75, 64, 64, + 67, 4, 73, 13, 69, 1, 13, 14, 23, 17, 10, 79, 0, + 70, 0, 2, 90, 65, 8, 4, 5, 7, 7, 18, 10, 3, 1, + 3, 1, 5, 7, 85, 68, 1, 75, 4, 2, 13, 6, 0, 10, + 6, 4, 0, 13, 1, 76, 3, 81, 20, 21, 15, 14, 17, + 17, 10, 16, 19, 64, 9, 9, 5, 3, 68, 4, 65, 1, 3, + 0, 3, 8, 64, 80, 64, 73, 66, 2, 82, 22, 29, 22, + 21, 19, 18, 16, 16, 10, 2, 1, 65, 70, 72, 93, + 68, 65, 79, 11, 7, 4, 4, 64, 69, 67, 68, 78, 71, + 76, 92, 86, 95, 67, 22, 18, 11, 4, 7, 64, 69, + 71, 78, 64, 35, 21, 15, 8, 13, 2, 66, 71, 77, 4, + 39, 29, 24, 18, 17, 5, 64, 69, 76, 70, 40, 26, + 12, 3, 8, 67, 76, 81, 5, 47, 36, 26, 19, 21, 5, + 65, 70, 74, 62, 88, 84, 73, 85, 85, 78, 79, 77, + 78, 78, 76, 76, 82, 78, 84, 80, 0, 79, 79, 74, + 71, 68, 69, 70, 67, 66, 70, 73, 77, 9, 8, 14, 5, + 0, 10, 8, 6, 5, 6, 5, 4, 66, 68, 72, 66, 7, 78, + 6, 20, 4, 6, 10, 12, 16, 6, 8, 10, 16, 67, 70, + 78, 33, 34, 40, 28, 26, 34, 34, 34, 38, 33, 39, + 38, 28, 23, 6, 26, 36, 23, 17, 23, 20, 13, 13, + 9, 6, 1, 68, 70, 76, 26, 25, 25, 20, 9, 12, 8, + 64, 1, 66, 69, 79, 78, 87, 90, 71, 71, 88, 11, + 12, 5, 69, 2, 1, 71, 65, 67, 73, 83, 83, 91, + 101, 7, 66, 78, 73, 2, 1, 6, 8, 9, 17, 11, 15, + 17, 29, 20, 19, 29, 28, 53, 37, 27, 17, 9, 64, + 76, 88, 108, 6, 38, 31, 29, 22, 24, 12, 8, 8, + 65, 78, 72, 0, 71, 7, 15, 68, 69, 5, 9, 4, 8, + 17, 4, 2, 48, 32, 17, 2, 72, 88, 100, 109, 119 }, + + { + + 50, + 7, 78, 50, 7, 78, 86, 70, 19, 11, 2, 66, 28, 33, + 53, 14, 64, 64, 4, 14, 0, 66, 67, 74, 89, 8, 22, + 104, 113, 116, 3, 75, 68, 4, 14, 0, 77, 70, 11, + 6, 64, 72, 80, 0, 77, 78, 90, 5, 71, 79, 2, 74, + 76, 88, 7, 66, 68, 72, 2, 4, 22, 0, 0, 0, 70, + 91, 97, 2, 4, 66, 18, 72, 91, 87, 6, 65, 30, 22, + 65, 3, 78, 3, 76, 74, 85, 81, 81, 85, 89, 22, 0, + 8, 80, 69, 79, 75, 92, 2, 68, 67, 77, 17, 68, 0, + 76, 82, 69, 73, 66, 7, 6, 0, 12, 13, 75, 64, 64, + 67, 4, 73, 13, 69, 1, 13, 13, 22, 16, 9, 78, 1, + 70, 64, 2, 89, 65, 8, 3, 5, 7, 7, 19, 11, 3, 1, + 3, 1, 4, 7, 86, 68, 1, 75, 3, 1, 12, 6, 0, 11, + 6, 4, 64, 12, 0, 76, 3, 81, 20, 21, 15, 14, 17, + 17, 10, 16, 19, 64, 9, 9, 5, 3, 68, 4, 65, 0, 3, + 0, 3, 7, 64, 80, 65, 73, 67, 1, 82, 21, 27, 20, + 19, 17, 16, 14, 14, 8, 0, 64, 67, 73, 74, 94, + 68, 66, 79, 10, 6, 3, 3, 65, 71, 68, 69, 78, 71, + 76, 92, 86, 95, 67, 22, 18, 11, 4, 7, 64, 69, + 70, 77, 64, 35, 21, 15, 8, 13, 3, 65, 71, 75, 4, + 39, 29, 24, 18, 17, 5, 64, 69, 75, 70, 40, 25, + 11, 3, 8, 67, 76, 81, 5, 47, 35, 25, 18, 21, 5, + 65, 70, 73, 62, 87, 83, 72, 83, 84, 77, 78, 75, + 77, 76, 74, 74, 81, 77, 83, 80, 1, 78, 79, 74, + 70, 68, 70, 70, 68, 67, 71, 74, 78, 8, 7, 14, 4, + 0, 10, 8, 6, 4, 5, 5, 3, 67, 68, 72, 66, 7, 78, + 5, 20, 3, 6, 9, 11, 16, 5, 7, 9, 15, 68, 71, 80, + 32, 34, 40, 27, 24, 32, 32, 32, 36, 30, 36, 36, + 25, 21, 4, 23, 32, 20, 13, 21, 17, 11, 12, 8, 6, + 2, 67, 69, 74, 25, 23, 23, 19, 7, 10, 7, 66, 0, + 68, 71, 81, 80, 88, 91, 71, 71, 89, 9, 10, 3, + 70, 0, 64, 73, 66, 69, 75, 84, 84, 92, 101, 6, + 66, 79, 73, 3, 2, 6, 9, 10, 18, 12, 16, 18, 30, + 21, 20, 30, 29, 53, 35, 25, 14, 7, 67, 79, 91, + 110, 6, 39, 32, 30, 23, 25, 12, 9, 9, 64, 77, + 72, 1, 70, 8, 16, 68, 68, 5, 9, 4, 9, 18, 4, 2, + 46, 30, 14, 64, 75, 92, 104, 113, 122 }, + + { + + 48, + 6, 78, 48, 6, 78, 85, 69, 19, 11, 2, 67, 27, 32, + 53, 14, 1, 64, 5, 15, 0, 67, 67, 75, 91, 7, 20, + 106, 114, 117, 5, 74, 68, 5, 15, 0, 77, 69, 11, + 5, 64, 72, 79, 64, 77, 78, 91, 5, 71, 79, 2, 75, + 76, 88, 7, 66, 68, 72, 2, 4, 22, 0, 0, 0, 69, + 92, 97, 2, 3, 66, 18, 72, 91, 85, 7, 0, 31, 23, + 64, 4, 77, 4, 75, 73, 83, 81, 81, 85, 88, 22, 0, + 8, 79, 69, 78, 75, 91, 2, 69, 68, 78, 17, 68, 0, + 76, 82, 69, 73, 66, 6, 6, 0, 12, 12, 75, 64, 64, + 67, 3, 73, 12, 70, 1, 12, 11, 21, 15, 8, 78, 1, + 70, 65, 1, 89, 66, 7, 3, 5, 7, 7, 20, 11, 3, 0, + 3, 0, 3, 6, 87, 68, 1, 76, 3, 0, 12, 5, 64, 11, + 6, 4, 66, 12, 64, 76, 3, 82, 19, 20, 15, 14, 17, + 16, 9, 16, 18, 65, 8, 9, 5, 2, 69, 3, 66, 0, 2, + 64, 2, 6, 65, 80, 65, 73, 68, 64, 82, 19, 25, + 18, 17, 15, 13, 12, 12, 6, 65, 66, 69, 75, 76, + 95, 68, 66, 80, 9, 4, 1, 1, 67, 72, 70, 71, 79, + 72, 77, 92, 86, 94, 67, 22, 18, 11, 4, 7, 64, + 69, 70, 76, 0, 35, 21, 15, 8, 13, 3, 65, 70, 74, + 4, 39, 29, 24, 17, 17, 5, 64, 69, 75, 70, 40, + 25, 11, 3, 8, 67, 76, 80, 5, 46, 34, 24, 17, 20, + 5, 65, 70, 73, 62, 86, 82, 72, 82, 83, 77, 77, + 74, 76, 75, 73, 73, 81, 77, 83, 79, 2, 78, 80, + 74, 70, 68, 70, 71, 68, 68, 72, 76, 79, 7, 7, + 14, 4, 64, 9, 7, 5, 3, 4, 4, 3, 68, 69, 73, 66, + 7, 79, 4, 19, 2, 6, 8, 10, 16, 4, 6, 9, 15, 69, + 72, 82, 31, 33, 39, 25, 23, 31, 30, 30, 33, 28, + 33, 33, 22, 18, 3, 20, 29, 17, 9, 18, 15, 9, 10, + 6, 6, 2, 67, 68, 72, 23, 21, 21, 17, 4, 8, 5, + 68, 65, 70, 72, 83, 81, 89, 92, 72, 72, 90, 8, + 9, 1, 72, 65, 66, 74, 68, 71, 76, 85, 86, 94, + 102, 5, 67, 80, 73, 3, 2, 7, 9, 10, 19, 12, 17, + 19, 31, 22, 21, 31, 30, 52, 33, 23, 12, 4, 70, + 82, 94, 113, 6, 39, 32, 30, 23, 25, 12, 9, 9, + 64, 77, 72, 1, 70, 9, 17, 68, 68, 6, 10, 4, 9, + 18, 4, 1, 45, 28, 12, 67, 78, 96, 108, 116, 125 }, + + { + + 47, + 6, 78, 47, 6, 78, 83, 68, 20, 11, 2, 68, 26, 31, + 53, 14, 3, 64, 6, 16, 0, 67, 66, 76, 92, 6, 18, + 107, 115, 118, 8, 72, 68, 6, 16, 0, 76, 68, 12, + 4, 64, 71, 78, 64, 77, 78, 91, 5, 71, 78, 2, 75, + 76, 88, 7, 66, 67, 72, 2, 4, 22, 0, 0, 0, 68, + 92, 97, 2, 2, 66, 18, 71, 91, 83, 9, 2, 32, 25, + 1, 5, 75, 5, 73, 72, 81, 81, 81, 85, 87, 22, 0, + 8, 78, 69, 77, 74, 90, 2, 69, 68, 78, 17, 68, 1, + 75, 81, 68, 73, 66, 6, 6, 0, 12, 12, 74, 64, 0, + 67, 3, 72, 11, 70, 1, 11, 10, 21, 14, 7, 77, 1, + 69, 66, 0, 89, 67, 7, 3, 6, 7, 7, 21, 11, 3, 0, + 4, 0, 3, 6, 88, 68, 1, 77, 3, 64, 12, 5, 65, 11, + 6, 4, 67, 12, 64, 76, 3, 83, 18, 20, 15, 14, 17, + 16, 9, 16, 18, 65, 8, 9, 5, 2, 69, 3, 66, 0, 2, + 64, 1, 6, 65, 80, 65, 73, 69, 65, 82, 17, 24, + 17, 16, 13, 11, 11, 11, 4, 67, 67, 70, 77, 77, + 96, 68, 66, 81, 8, 3, 0, 0, 68, 73, 71, 72, 80, + 73, 78, 92, 85, 93, 66, 23, 18, 11, 4, 8, 0, 68, + 69, 75, 1, 35, 21, 15, 8, 14, 3, 64, 69, 72, 4, + 39, 29, 24, 17, 18, 5, 64, 69, 74, 70, 41, 25, + 11, 3, 9, 67, 76, 79, 5, 46, 34, 24, 16, 20, 5, + 65, 69, 72, 62, 85, 81, 71, 81, 81, 76, 75, 73, + 74, 74, 72, 71, 80, 76, 83, 78, 4, 78, 81, 73, + 69, 68, 70, 72, 68, 68, 73, 77, 80, 7, 7, 14, 4, + 64, 8, 7, 5, 2, 4, 4, 3, 69, 69, 73, 65, 8, 79, + 4, 18, 2, 6, 8, 10, 16, 3, 5, 9, 15, 70, 72, 83, + 31, 32, 38, 24, 22, 30, 28, 28, 31, 26, 31, 30, + 20, 16, 2, 17, 26, 14, 5, 15, 13, 8, 9, 5, 6, 3, + 67, 67, 70, 21, 20, 19, 15, 2, 7, 4, 70, 66, 71, + 73, 84, 82, 90, 92, 72, 72, 91, 7, 8, 0, 74, 66, + 67, 75, 69, 72, 77, 86, 87, 95, 103, 5, 68, 80, + 72, 3, 2, 8, 10, 11, 20, 13, 18, 20, 33, 23, 22, + 33, 32, 51, 32, 21, 10, 1, 73, 85, 97, 115, 7, + 39, 32, 31, 23, 26, 13, 9, 10, 0, 77, 71, 2, 70, + 10, 19, 67, 67, 7, 11, 4, 10, 19, 4, 1, 44, 26, + 10, 69, 81, 99, 112, 119, 126 }, + + { + + 46, + 6, 78, 46, 6, 78, 81, 66, 20, 11, 1, 70, 24, 29, + 53, 14, 6, 65, 7, 16, 0, 68, 66, 77, 93, 6, 16, + 109, 116, 118, 11, 71, 68, 7, 16, 0, 76, 67, 12, + 4, 64, 71, 78, 64, 78, 77, 91, 5, 71, 77, 1, 75, + 76, 88, 8, 65, 67, 71, 3, 4, 22, 0, 0, 0, 68, + 92, 97, 3, 1, 66, 18, 71, 91, 82, 11, 3, 34, 27, + 2, 6, 74, 7, 72, 71, 79, 80, 80, 84, 86, 22, 0, + 8, 78, 69, 77, 74, 88, 1, 69, 68, 78, 17, 68, 1, + 75, 81, 68, 73, 65, 6, 5, 0, 12, 12, 74, 64, 0, + 67, 3, 72, 11, 70, 1, 11, 9, 20, 13, 6, 76, 2, + 69, 67, 0, 88, 67, 7, 2, 6, 7, 7, 22, 12, 3, 0, + 4, 0, 2, 6, 89, 68, 1, 77, 2, 65, 11, 5, 65, 12, + 6, 4, 68, 11, 65, 76, 3, 83, 18, 20, 15, 14, 17, + 16, 9, 16, 18, 65, 8, 9, 5, 2, 69, 3, 66, 64, 2, + 64, 1, 5, 66, 80, 66, 73, 70, 66, 82, 16, 22, + 15, 14, 11, 9, 9, 9, 2, 69, 69, 72, 80, 79, 97, + 68, 67, 81, 7, 2, 64, 64, 70, 75, 72, 73, 80, + 73, 78, 92, 85, 93, 66, 23, 18, 11, 4, 8, 0, 68, + 69, 74, 1, 35, 21, 15, 8, 14, 4, 0, 69, 70, 4, + 39, 29, 24, 17, 18, 5, 64, 69, 73, 70, 41, 24, + 10, 3, 9, 67, 76, 79, 5, 45, 33, 23, 15, 20, 5, + 65, 69, 72, 62, 84, 80, 70, 79, 80, 75, 74, 71, + 73, 72, 70, 69, 79, 75, 82, 78, 5, 77, 81, 73, + 68, 68, 71, 72, 69, 69, 74, 78, 81, 6, 6, 14, 3, + 65, 8, 7, 5, 1, 3, 4, 2, 70, 69, 73, 65, 8, 79, + 3, 18, 1, 6, 7, 9, 16, 2, 4, 8, 14, 71, 73, 85, + 30, 32, 38, 23, 20, 28, 26, 26, 29, 23, 28, 28, + 17, 14, 0, 14, 22, 11, 1, 13, 10, 6, 8, 4, 6, 4, + 66, 66, 68, 20, 18, 17, 14, 0, 5, 2, 72, 67, 73, + 75, 86, 84, 91, 93, 73, 72, 92, 5, 6, 65, 75, + 68, 69, 77, 71, 74, 79, 87, 88, 96, 103, 4, 68, + 81, 72, 4, 3, 8, 10, 12, 21, 14, 19, 21, 34, 24, + 23, 34, 33, 51, 30, 19, 7, 64, 76, 88, 100, 117, + 7, 40, 33, 31, 24, 27, 13, 10, 10, 0, 76, 71, 3, + 69, 11, 20, 67, 66, 7, 11, 4, 10, 20, 4, 1, 42, + 24, 7, 72, 84, 103, 116, 123, 126 }, + + { + + 45, + 6, 79, 45, 6, 79, 79, 65, 21, 11, 1, 71, 23, 28, + 53, 14, 8, 65, 7, 17, 0, 69, 66, 78, 94, 5, 15, + 110, 117, 119, 14, 70, 68, 7, 17, 0, 75, 66, 13, + 3, 0, 70, 77, 65, 78, 77, 91, 5, 70, 77, 1, 75, + 75, 88, 8, 65, 67, 71, 3, 4, 22, 0, 0, 0, 67, + 92, 97, 3, 1, 67, 18, 71, 90, 80, 13, 5, 35, 29, + 3, 7, 72, 8, 71, 71, 78, 80, 80, 84, 86, 23, 0, + 8, 77, 68, 76, 73, 87, 1, 70, 69, 79, 17, 68, 1, + 74, 81, 67, 73, 65, 6, 5, 1, 12, 11, 74, 64, 0, + 67, 3, 72, 10, 70, 1, 10, 8, 19, 13, 6, 76, 2, + 69, 67, 64, 88, 68, 7, 2, 6, 7, 7, 22, 12, 3, 0, + 5, 0, 1, 6, 89, 69, 1, 78, 2, 66, 11, 5, 66, 12, + 6, 4, 70, 11, 66, 76, 3, 84, 17, 20, 14, 14, 17, + 16, 9, 16, 18, 65, 7, 9, 4, 2, 69, 2, 67, 64, 1, + 65, 0, 4, 66, 80, 66, 72, 71, 68, 82, 14, 20, + 13, 12, 9, 7, 7, 7, 0, 71, 70, 73, 82, 81, 98, + 68, 67, 82, 6, 1, 65, 66, 71, 76, 73, 74, 81, + 74, 79, 92, 85, 92, 65, 23, 18, 11, 5, 8, 0, 68, + 68, 73, 2, 34, 21, 15, 8, 15, 4, 1, 68, 68, 4, + 40, 29, 24, 17, 19, 5, 64, 68, 73, 70, 41, 24, + 10, 3, 9, 66, 75, 78, 5, 45, 33, 22, 15, 20, 5, + 64, 68, 71, 62, 84, 79, 70, 78, 79, 74, 72, 70, + 72, 71, 69, 68, 79, 74, 82, 77, 7, 77, 82, 73, + 68, 68, 71, 73, 69, 70, 75, 79, 82, 6, 6, 14, 3, + 65, 7, 6, 5, 1, 3, 3, 2, 70, 69, 74, 65, 8, 80, + 3, 17, 0, 5, 6, 9, 17, 2, 3, 8, 14, 71, 73, 86, + 29, 31, 37, 22, 19, 27, 24, 24, 27, 21, 26, 25, + 14, 11, 64, 12, 19, 8, 66, 10, 8, 4, 7, 3, 6, 5, + 66, 64, 65, 18, 16, 15, 12, 65, 3, 1, 74, 69, + 74, 76, 87, 85, 92, 94, 73, 73, 93, 4, 5, 66, + 77, 70, 71, 78, 72, 76, 80, 89, 90, 98, 104, 3, + 69, 82, 72, 4, 3, 9, 11, 12, 23, 15, 20, 22, 35, + 26, 24, 36, 34, 50, 29, 17, 5, 67, 78, 91, 102, + 119, 8, 40, 33, 32, 24, 28, 14, 10, 11, 1, 76, + 70, 3, 69, 12, 21, 67, 66, 8, 12, 4, 11, 21, 4, + 1, 41, 22, 5, 75, 88, 107, 119, 126, 126 }, + + { + + 43, + 6, 79, 43, 6, 79, 78, 0, 21, 11, 0, 73, 21, 27, + 53, 14, 10, 65, 8, 18, 0, 70, 66, 79, 95, 5, 13, + 112, 118, 119, 17, 69, 68, 8, 18, 0, 75, 65, 13, + 3, 0, 70, 76, 65, 79, 77, 91, 5, 70, 76, 1, 76, + 75, 88, 9, 65, 67, 71, 4, 4, 22, 0, 0, 0, 67, + 93, 97, 4, 0, 67, 18, 71, 90, 78, 14, 6, 37, 30, + 4, 8, 71, 9, 70, 70, 76, 79, 80, 83, 85, 23, 0, + 8, 77, 68, 76, 73, 85, 1, 70, 69, 79, 17, 68, 1, + 74, 81, 67, 73, 64, 6, 5, 1, 12, 11, 74, 64, 0, + 67, 3, 72, 9, 71, 1, 9, 7, 18, 12, 5, 75, 3, 69, + 68, 64, 87, 68, 7, 2, 6, 7, 7, 23, 12, 3, 64, 5, + 64, 0, 5, 90, 69, 1, 78, 1, 67, 11, 4, 67, 12, + 6, 4, 71, 11, 67, 76, 3, 84, 16, 20, 14, 14, 17, + 16, 9, 16, 17, 65, 7, 9, 4, 2, 70, 2, 67, 65, 1, + 65, 64, 3, 67, 80, 67, 72, 72, 69, 82, 13, 18, + 11, 10, 7, 5, 5, 5, 65, 73, 72, 75, 84, 83, 99, + 68, 67, 82, 5, 0, 66, 67, 73, 78, 74, 76, 82, + 74, 79, 92, 85, 92, 65, 23, 18, 11, 5, 8, 0, 68, + 68, 72, 2, 34, 21, 15, 8, 15, 5, 1, 67, 66, 4, + 40, 29, 24, 17, 19, 5, 64, 68, 72, 70, 41, 24, + 9, 3, 9, 66, 75, 77, 5, 44, 32, 21, 14, 20, 5, + 64, 68, 71, 62, 83, 78, 69, 77, 78, 73, 71, 69, + 71, 70, 67, 66, 78, 74, 82, 77, 8, 77, 82, 73, + 67, 68, 72, 73, 69, 71, 76, 80, 83, 5, 5, 14, 2, + 66, 7, 6, 5, 0, 2, 3, 2, 71, 70, 74, 65, 8, 80, + 2, 17, 64, 5, 5, 8, 17, 1, 2, 7, 13, 72, 74, 88, + 28, 30, 37, 21, 18, 25, 22, 22, 25, 18, 23, 23, + 11, 9, 65, 9, 16, 5, 70, 7, 5, 2, 6, 1, 6, 6, + 66, 0, 0, 16, 14, 13, 10, 67, 1, 64, 76, 70, 76, + 77, 89, 87, 93, 95, 74, 73, 94, 3, 3, 68, 79, + 72, 73, 80, 74, 78, 82, 90, 91, 99, 105, 2, 70, + 83, 72, 5, 3, 9, 11, 13, 24, 15, 21, 23, 36, 27, + 25, 37, 35, 50, 27, 15, 2, 69, 81, 94, 105, 122, + 8, 41, 34, 32, 24, 28, 14, 10, 11, 1, 76, 70, 4, + 68, 13, 22, 67, 65, 8, 12, 4, 11, 22, 4, 1, 40, + 20, 2, 78, 91, 111, 123, 126, 126 }, + + { + + 42, + 6, 79, 42, 6, 79, 76, 1, 21, 11, 0, 74, 20, 25, + 53, 14, 13, 66, 9, 18, 0, 70, 65, 80, 96, 4, 11, + 114, 119, 120, 20, 67, 68, 9, 18, 0, 75, 64, 13, + 2, 0, 70, 76, 65, 79, 76, 91, 5, 70, 75, 0, 76, + 75, 88, 9, 64, 66, 70, 4, 4, 22, 0, 0, 0, 66, + 93, 97, 4, 64, 67, 18, 70, 90, 77, 16, 8, 38, + 32, 6, 9, 70, 11, 69, 69, 74, 79, 79, 83, 84, + 23, 0, 8, 76, 68, 75, 72, 84, 0, 70, 69, 79, 17, + 68, 2, 73, 81, 67, 73, 64, 6, 4, 1, 12, 11, 73, + 64, 1, 67, 3, 72, 9, 71, 1, 9, 6, 18, 11, 4, 74, + 3, 69, 69, 65, 87, 69, 7, 1, 6, 7, 7, 24, 13, 3, + 64, 5, 64, 64, 5, 91, 69, 1, 79, 1, 68, 10, 4, + 67, 13, 6, 4, 72, 10, 68, 76, 3, 85, 16, 20, 14, + 14, 17, 16, 9, 16, 17, 65, 7, 9, 4, 2, 70, 2, + 67, 65, 1, 65, 64, 2, 67, 80, 67, 72, 73, 70, + 82, 11, 16, 10, 9, 5, 3, 3, 3, 67, 75, 74, 77, + 87, 84, 100, 68, 68, 83, 4, 64, 67, 68, 74, 79, + 75, 77, 82, 75, 80, 92, 85, 91, 65, 23, 18, 11, + 5, 8, 1, 67, 67, 71, 3, 34, 21, 15, 8, 15, 5, 2, + 67, 64, 4, 40, 29, 24, 17, 19, 5, 64, 68, 71, + 70, 42, 23, 9, 3, 9, 66, 75, 77, 5, 44, 31, 20, + 13, 20, 5, 64, 68, 70, 62, 82, 77, 68, 75, 77, + 72, 70, 67, 70, 68, 66, 64, 77, 73, 81, 76, 9, + 76, 83, 72, 66, 68, 72, 74, 70, 71, 77, 81, 84, + 4, 5, 14, 2, 66, 6, 6, 5, 64, 1, 3, 1, 72, 70, + 74, 65, 8, 80, 1, 16, 64, 5, 4, 7, 17, 0, 1, 7, + 13, 73, 75, 90, 27, 30, 36, 20, 16, 24, 20, 20, + 23, 16, 20, 20, 8, 7, 67, 6, 12, 2, 74, 5, 3, 0, + 5, 0, 6, 7, 65, 1, 2, 15, 13, 11, 9, 69, 64, 65, + 78, 71, 78, 79, 91, 88, 94, 96, 74, 73, 95, 1, + 2, 70, 80, 73, 74, 81, 75, 79, 83, 91, 92, 100, + 105, 2, 70, 84, 71, 5, 4, 10, 12, 14, 25, 16, + 22, 24, 38, 28, 26, 38, 37, 49, 25, 13, 0, 72, + 84, 97, 108, 124, 8, 41, 34, 33, 25, 29, 14, 11, + 12, 2, 75, 70, 5, 68, 14, 23, 66, 64, 9, 13, 4, + 12, 23, 4, 1, 38, 18, 0, 81, 94, 114, 126, 126, + 126 }, + + { + + 41, + 6, 79, 41, 6, 79, 74, 3, 22, 11, 64, 76, 18, 24, + 53, 14, 15, 66, 10, 19, 0, 71, 65, 81, 97, 4, 9, + 115, 120, 120, 23, 66, 68, 10, 19, 0, 74, 0, 14, + 2, 0, 69, 75, 66, 80, 76, 91, 5, 70, 75, 0, 76, + 75, 88, 10, 64, 66, 70, 5, 4, 22, 0, 0, 0, 66, + 93, 97, 5, 65, 67, 18, 70, 90, 75, 18, 9, 40, + 34, 7, 10, 68, 12, 68, 68, 72, 78, 79, 82, 83, + 23, 0, 8, 76, 68, 75, 72, 82, 0, 71, 70, 80, 17, + 68, 2, 73, 81, 66, 73, 0, 6, 4, 1, 12, 10, 73, + 64, 1, 67, 3, 72, 8, 71, 1, 8, 5, 17, 10, 3, 74, + 4, 69, 70, 65, 86, 69, 7, 1, 6, 7, 7, 25, 13, 3, + 64, 6, 64, 65, 5, 92, 69, 1, 79, 0, 69, 10, 4, + 68, 13, 6, 4, 74, 10, 69, 76, 3, 85, 15, 20, 14, + 14, 17, 16, 9, 16, 17, 65, 6, 9, 4, 2, 70, 1, + 68, 66, 0, 66, 65, 1, 68, 80, 68, 72, 74, 72, + 82, 10, 14, 8, 7, 3, 1, 1, 1, 69, 77, 75, 78, + 89, 86, 101, 68, 68, 83, 3, 65, 68, 70, 76, 81, + 76, 78, 83, 75, 80, 92, 85, 91, 64, 23, 18, 11, + 5, 8, 1, 67, 67, 70, 3, 34, 21, 15, 8, 16, 6, 3, + 66, 1, 4, 40, 29, 24, 17, 20, 5, 64, 68, 71, 70, + 42, 23, 8, 3, 9, 66, 75, 76, 5, 43, 31, 19, 12, + 20, 5, 64, 67, 70, 62, 81, 76, 68, 74, 76, 71, + 68, 66, 69, 67, 64, 0, 77, 72, 81, 76, 11, 76, + 83, 72, 66, 68, 73, 74, 70, 72, 78, 82, 85, 4, + 4, 14, 1, 67, 6, 5, 5, 65, 1, 2, 1, 73, 70, 75, + 65, 8, 81, 1, 16, 65, 5, 3, 7, 17, 64, 0, 6, 12, + 74, 75, 91, 26, 29, 36, 19, 15, 22, 18, 18, 21, + 13, 18, 18, 5, 4, 68, 3, 9, 64, 78, 2, 0, 65, 4, + 64, 6, 8, 65, 2, 4, 13, 11, 9, 7, 71, 66, 67, + 80, 73, 79, 80, 92, 90, 95, 97, 75, 74, 96, 0, + 0, 71, 82, 75, 76, 83, 77, 81, 85, 92, 94, 102, + 106, 1, 71, 85, 71, 6, 4, 10, 12, 14, 26, 17, + 23, 25, 39, 29, 27, 40, 38, 49, 24, 11, 66, 74, + 87, 100, 111, 126, 9, 42, 35, 33, 25, 30, 15, + 11, 12, 2, 75, 69, 5, 67, 15, 24, 66, 64, 9, 13, + 4, 12, 24, 4, 1, 37, 16, 66, 84, 97, 118, 126, + 126, 126 }, + + { + + 40, + 6, 79, 40, 6, 79, 72, 4, 22, 11, 64, 77, 17, 23, + 53, 14, 17, 66, 11, 20, 0, 72, 65, 82, 98, 3, 7, + 117, 121, 121, 26, 65, 68, 11, 20, 0, 74, 1, 14, + 1, 0, 69, 74, 66, 80, 76, 91, 5, 70, 74, 0, 76, + 75, 88, 10, 64, 66, 70, 5, 4, 22, 0, 0, 0, 65, + 93, 97, 5, 66, 67, 18, 70, 90, 73, 20, 11, 41, + 36, 8, 11, 67, 13, 67, 67, 70, 78, 79, 82, 82, + 23, 0, 8, 75, 68, 74, 71, 81, 0, 71, 70, 80, 17, + 68, 2, 72, 81, 66, 73, 0, 6, 4, 1, 12, 10, 73, + 64, 1, 67, 3, 72, 7, 71, 1, 7, 4, 16, 9, 2, 73, + 4, 69, 71, 66, 86, 70, 7, 1, 6, 7, 7, 26, 13, 3, + 64, 6, 64, 66, 5, 93, 69, 1, 80, 0, 70, 10, 4, + 69, 13, 6, 4, 75, 10, 70, 76, 3, 86, 14, 20, 14, + 14, 17, 16, 9, 16, 17, 65, 6, 9, 4, 2, 70, 1, + 68, 66, 0, 66, 66, 0, 68, 80, 68, 72, 75, 73, + 82, 8, 12, 6, 5, 1, 64, 64, 64, 71, 79, 77, 80, + 91, 88, 102, 68, 68, 84, 2, 66, 69, 71, 77, 82, + 77, 79, 84, 76, 81, 92, 85, 90, 64, 23, 18, 11, + 5, 8, 1, 67, 66, 69, 4, 34, 21, 15, 8, 16, 6, 4, + 65, 3, 4, 40, 29, 24, 17, 20, 5, 64, 68, 70, 70, + 42, 23, 8, 3, 9, 66, 75, 75, 5, 43, 30, 18, 11, + 20, 5, 64, 67, 69, 62, 80, 75, 67, 73, 75, 70, + 67, 65, 68, 66, 0, 2, 76, 71, 81, 75, 12, 76, + 84, 72, 65, 68, 73, 75, 70, 73, 79, 83, 86, 3, + 4, 14, 1, 67, 5, 5, 5, 66, 0, 2, 1, 74, 70, 75, + 65, 8, 81, 0, 15, 66, 5, 2, 6, 17, 65, 64, 6, + 12, 75, 76, 93, 25, 28, 35, 18, 14, 21, 16, 16, + 19, 11, 15, 15, 2, 2, 69, 0, 6, 67, 82, 64, 65, + 67, 3, 65, 6, 9, 65, 3, 6, 11, 9, 7, 5, 73, 68, + 68, 82, 74, 81, 81, 94, 91, 96, 98, 75, 74, 97, + 64, 64, 73, 84, 77, 78, 84, 78, 83, 86, 93, 95, + 103, 107, 0, 72, 86, 71, 6, 4, 11, 13, 15, 27, + 18, 24, 26, 40, 30, 28, 41, 39, 48, 22, 9, 68, + 77, 90, 103, 114, 126, 9, 42, 35, 34, 25, 31, + 15, 11, 13, 3, 75, 69, 6, 67, 16, 25, 66, 0, 10, + 14, 4, 13, 25, 4, 1, 36, 14, 68, 87, 100, 122, + 126, 126, 126 }, + + { + + 38, + 5, 80, 38, 5, 80, 71, 5, 22, 11, 65, 79, 15, 21, + 52, 14, 19, 67, 11, 20, 64, 73, 65, 84, 100, 2, + 5, 119, 122, 122, 28, 64, 69, 11, 20, 64, 74, 2, + 14, 0, 0, 69, 74, 67, 81, 76, 92, 5, 70, 74, 64, + 77, 75, 88, 10, 64, 66, 70, 5, 3, 22, 0, 0, 0, + 65, 94, 97, 5, 67, 68, 18, 70, 90, 72, 21, 12, + 42, 37, 9, 12, 66, 14, 66, 67, 69, 78, 79, 82, + 82, 23, 0, 8, 75, 68, 74, 71, 80, 64, 72, 71, + 81, 17, 68, 2, 72, 81, 66, 73, 0, 5, 3, 1, 11, + 9, 73, 65, 1, 67, 2, 72, 6, 72, 0, 6, 2, 15, 8, + 1, 73, 4, 69, 72, 67, 86, 71, 6, 0, 6, 7, 7, 26, + 13, 3, 65, 6, 65, 67, 4, 94, 70, 0, 81, 64, 71, + 9, 3, 70, 13, 6, 4, 77, 9, 71, 76, 3, 87, 13, + 19, 13, 14, 17, 15, 8, 16, 16, 66, 5, 9, 3, 1, + 71, 0, 69, 67, 64, 67, 67, 64, 69, 80, 69, 72, + 76, 75, 82, 6, 10, 4, 3, 64, 67, 66, 66, 73, 81, + 79, 82, 94, 90, 104, 69, 69, 85, 1, 68, 71, 73, + 79, 84, 79, 81, 85, 77, 82, 92, 85, 90, 64, 23, + 18, 11, 5, 8, 1, 67, 66, 68, 4, 33, 21, 15, 8, + 16, 6, 4, 65, 4, 3, 40, 29, 23, 16, 20, 5, 64, + 68, 70, 70, 42, 22, 7, 2, 9, 66, 75, 75, 5, 42, + 29, 17, 10, 19, 5, 64, 67, 69, 62, 80, 74, 67, + 72, 74, 70, 66, 64, 67, 65, 1, 3, 76, 71, 81, + 75, 13, 76, 85, 72, 65, 68, 74, 76, 71, 74, 80, + 85, 88, 2, 3, 14, 0, 68, 4, 4, 4, 67, 64, 1, 0, + 75, 71, 76, 65, 8, 82, 64, 14, 67, 4, 1, 5, 17, + 66, 66, 5, 11, 76, 77, 95, 24, 27, 34, 16, 12, + 19, 14, 14, 16, 8, 12, 12, 64, 64, 71, 66, 2, + 70, 87, 67, 68, 69, 1, 67, 6, 9, 65, 4, 8, 9, 7, + 5, 3, 76, 70, 70, 85, 76, 83, 83, 96, 93, 97, + 99, 76, 75, 99, 66, 66, 75, 86, 79, 80, 86, 80, + 85, 88, 95, 97, 105, 108, 64, 73, 87, 71, 6, 4, + 11, 13, 15, 28, 18, 25, 26, 41, 31, 29, 42, 40, + 47, 20, 6, 71, 80, 93, 107, 117, 126, 9, 42, 35, + 34, 25, 31, 15, 11, 13, 3, 75, 69, 6, 67, 17, + 26, 66, 0, 10, 14, 4, 13, 25, 4, 0, 34, 11, 71, + 90, 104, 126, 126, 126, 126 }, + + { + + 37, + 5, 80, 37, 5, 80, 69, 7, 23, 12, 65, 80, 14, 20, + 52, 14, 22, 67, 12, 21, 64, 73, 64, 85, 101, 2, + 4, 120, 123, 122, 31, 1, 69, 12, 21, 64, 73, 4, + 15, 0, 1, 68, 73, 67, 81, 75, 92, 5, 69, 73, 64, + 77, 74, 88, 11, 0, 65, 69, 6, 3, 22, 0, 0, 0, + 64, 94, 97, 6, 67, 68, 18, 69, 89, 70, 23, 14, + 44, 39, 11, 13, 64, 16, 64, 66, 67, 77, 78, 81, + 81, 24, 1, 9, 74, 67, 73, 70, 78, 64, 72, 71, + 81, 18, 68, 3, 71, 80, 65, 72, 1, 5, 3, 2, 11, + 9, 72, 65, 2, 67, 2, 71, 6, 72, 0, 6, 1, 15, 8, + 1, 72, 5, 68, 72, 67, 85, 71, 6, 0, 7, 7, 7, 27, + 14, 4, 65, 7, 65, 67, 4, 94, 70, 0, 81, 64, 72, + 9, 3, 70, 14, 7, 4, 78, 9, 71, 75, 3, 87, 13, + 19, 13, 14, 17, 15, 8, 16, 16, 66, 5, 9, 3, 1, + 71, 0, 69, 67, 64, 67, 67, 64, 69, 79, 69, 71, + 76, 76, 81, 5, 9, 3, 2, 66, 69, 67, 67, 75, 82, + 80, 83, 96, 91, 105, 69, 69, 85, 0, 69, 72, 74, + 80, 85, 80, 82, 85, 77, 82, 91, 84, 89, 0, 24, + 18, 11, 6, 9, 2, 66, 65, 66, 5, 33, 21, 15, 8, + 17, 7, 5, 64, 6, 3, 41, 30, 23, 16, 21, 5, 64, + 67, 69, 70, 43, 22, 7, 2, 10, 65, 74, 74, 5, 42, + 29, 17, 10, 19, 5, 0, 66, 68, 62, 79, 73, 66, + 70, 72, 69, 64, 1, 65, 0, 3, 5, 75, 70, 80, 74, + 15, 75, 85, 71, 64, 67, 74, 76, 71, 74, 80, 86, + 89, 2, 3, 15, 0, 68, 4, 4, 4, 67, 64, 1, 0, 75, + 71, 76, 64, 9, 82, 64, 14, 67, 4, 1, 5, 18, 66, + 67, 5, 11, 76, 77, 96, 24, 27, 34, 15, 11, 18, + 12, 12, 14, 6, 10, 10, 66, 66, 72, 68, 64, 73, + 91, 69, 70, 70, 0, 68, 6, 10, 64, 6, 11, 8, 6, + 4, 2, 78, 71, 71, 87, 77, 84, 84, 97, 94, 98, + 99, 76, 75, 100, 67, 67, 76, 87, 80, 81, 87, 81, + 86, 89, 96, 98, 106, 108, 64, 73, 87, 70, 7, 5, + 12, 14, 16, 30, 19, 26, 27, 43, 33, 30, 44, 42, + 47, 19, 4, 73, 82, 95, 110, 119, 126, 10, 43, + 36, 35, 26, 32, 16, 12, 14, 4, 74, 68, 7, 66, + 19, 28, 65, 1, 11, 15, 5, 14, 26, 4, 0, 33, 9, + 73, 92, 107, 126, 126, 126, 126 }, + + { + + 36, + 5, 80, 36, 5, 80, 67, 8, 23, 12, 65, 81, 13, 19, + 52, 14, 24, 67, 13, 22, 64, 74, 64, 86, 102, 1, + 2, 122, 124, 123, 34, 2, 69, 13, 22, 64, 73, 5, + 15, 64, 1, 68, 72, 67, 81, 75, 92, 5, 69, 72, + 64, 77, 74, 88, 11, 0, 65, 69, 6, 3, 22, 0, 0, + 0, 0, 94, 97, 6, 68, 68, 18, 69, 89, 68, 25, 16, + 45, 41, 12, 14, 0, 17, 0, 65, 65, 77, 78, 81, + 80, 24, 1, 9, 73, 67, 72, 70, 77, 64, 72, 71, + 81, 18, 68, 3, 71, 80, 65, 72, 1, 5, 3, 2, 11, + 9, 72, 65, 2, 67, 2, 71, 5, 72, 0, 5, 0, 14, 7, + 0, 71, 5, 68, 73, 68, 85, 72, 6, 0, 7, 7, 7, 28, + 14, 4, 65, 7, 65, 68, 4, 95, 70, 0, 82, 64, 73, + 9, 3, 71, 14, 7, 4, 79, 9, 72, 75, 3, 88, 12, + 19, 13, 14, 17, 15, 8, 16, 16, 66, 5, 9, 3, 1, + 71, 0, 69, 67, 64, 67, 68, 65, 70, 79, 69, 71, + 77, 77, 81, 3, 7, 1, 0, 68, 71, 69, 69, 77, 84, + 82, 85, 98, 93, 106, 69, 69, 86, 64, 70, 73, 75, + 82, 86, 81, 83, 86, 78, 83, 91, 84, 88, 0, 24, + 18, 11, 6, 9, 2, 66, 65, 65, 6, 33, 21, 15, 8, + 17, 7, 6, 0, 8, 3, 41, 30, 23, 16, 21, 5, 64, + 67, 68, 70, 43, 22, 7, 2, 10, 65, 74, 73, 5, 41, + 28, 16, 9, 19, 5, 0, 66, 68, 62, 78, 72, 65, 69, + 71, 68, 0, 2, 64, 1, 4, 7, 74, 69, 80, 73, 16, + 75, 86, 71, 0, 67, 74, 77, 71, 75, 81, 87, 90, + 1, 3, 15, 0, 69, 3, 4, 4, 68, 65, 1, 0, 76, 71, + 76, 64, 9, 82, 65, 13, 68, 4, 0, 4, 18, 67, 68, + 5, 11, 77, 78, 98, 23, 26, 33, 14, 10, 17, 10, + 10, 12, 4, 7, 7, 69, 68, 73, 71, 67, 76, 95, 72, + 72, 72, 64, 69, 6, 11, 64, 7, 13, 6, 4, 2, 0, + 80, 73, 73, 89, 78, 86, 85, 99, 95, 99, 100, 77, + 75, 101, 68, 68, 78, 89, 82, 83, 88, 83, 88, 90, + 97, 99, 107, 109, 65, 74, 88, 70, 7, 5, 13, 14, + 17, 31, 20, 27, 28, 44, 34, 31, 45, 43, 46, 17, + 2, 75, 85, 98, 113, 122, 126, 10, 43, 36, 35, + 26, 33, 16, 12, 14, 4, 74, 68, 8, 66, 20, 29, + 65, 2, 12, 16, 5, 14, 27, 4, 0, 32, 7, 75, 95, + 110, 126, 126, 126, 126 }, + + { + + 35, + 5, 80, 35, 5, 80, 65, 10, 24, 12, 66, 83, 11, + 18, 52, 14, 26, 67, 14, 23, 64, 75, 64, 87, 103, + 1, 0, 123, 125, 123, 37, 3, 69, 14, 23, 64, 72, + 6, 16, 64, 1, 67, 71, 68, 82, 75, 92, 5, 69, 72, + 64, 77, 74, 88, 12, 0, 65, 69, 7, 3, 22, 0, 0, + 0, 0, 94, 97, 7, 69, 68, 18, 69, 89, 66, 27, 17, + 47, 43, 13, 15, 2, 18, 1, 64, 0, 76, 78, 80, 79, + 24, 1, 9, 73, 67, 72, 69, 75, 64, 73, 72, 82, + 18, 68, 3, 70, 80, 64, 72, 2, 5, 3, 2, 11, 8, + 72, 65, 2, 67, 2, 71, 4, 72, 0, 4, 64, 13, 6, + 64, 71, 6, 68, 74, 68, 84, 72, 6, 0, 7, 7, 7, + 29, 14, 4, 65, 8, 65, 69, 4, 96, 70, 0, 82, 65, + 74, 9, 3, 72, 14, 7, 4, 81, 9, 73, 75, 3, 88, + 11, 19, 13, 14, 17, 15, 8, 16, 16, 66, 4, 9, 3, + 1, 71, 64, 70, 68, 65, 68, 69, 66, 70, 79, 70, + 71, 78, 79, 81, 2, 5, 64, 65, 70, 73, 71, 71, + 79, 86, 83, 86, 100, 95, 107, 69, 69, 86, 65, + 71, 74, 77, 83, 88, 82, 84, 87, 78, 83, 91, 84, + 88, 1, 24, 18, 11, 6, 9, 2, 66, 64, 64, 6, 33, + 21, 15, 8, 18, 8, 7, 1, 10, 3, 41, 30, 23, 16, + 22, 5, 64, 67, 68, 70, 43, 22, 6, 2, 10, 65, 74, + 72, 5, 41, 28, 15, 8, 19, 5, 0, 65, 67, 62, 77, + 71, 65, 68, 70, 67, 2, 3, 0, 2, 6, 8, 74, 68, + 80, 73, 18, 75, 86, 71, 0, 67, 75, 77, 71, 76, + 82, 88, 91, 1, 2, 15, 64, 69, 3, 3, 4, 69, 65, + 0, 0, 77, 71, 77, 64, 9, 83, 65, 13, 69, 4, 64, + 4, 18, 68, 69, 4, 10, 78, 78, 99, 22, 25, 33, + 13, 9, 15, 8, 8, 10, 1, 5, 5, 72, 71, 74, 74, + 70, 79, 99, 75, 75, 74, 65, 70, 6, 12, 64, 8, + 15, 4, 2, 0, 65, 82, 75, 74, 91, 80, 87, 86, + 100, 97, 100, 101, 77, 76, 102, 69, 70, 79, 91, + 84, 85, 90, 84, 90, 92, 98, 101, 109, 110, 66, + 75, 89, 70, 8, 5, 13, 15, 17, 32, 21, 28, 29, + 45, 35, 32, 47, 44, 46, 16, 0, 78, 87, 101, 116, + 125, 126, 11, 44, 37, 36, 26, 34, 17, 12, 15, 5, + 74, 67, 8, 65, 21, 30, 65, 2, 12, 16, 5, 15, 28, + 4, 0, 31, 5, 78, 98, 113, 126, 126, 126, 126 }, + + { + + 33, + 5, 80, 33, 5, 80, 64, 11, 24, 12, 66, 84, 10, + 16, 52, 14, 29, 68, 15, 23, 64, 76, 64, 88, 104, + 0, 65, 125, 126, 124, 40, 4, 69, 15, 23, 64, 72, + 7, 16, 65, 1, 67, 71, 68, 82, 74, 92, 5, 69, 71, + 65, 78, 74, 88, 12, 1, 65, 68, 7, 3, 22, 0, 0, + 0, 1, 95, 97, 7, 70, 68, 18, 69, 89, 65, 28, 19, + 48, 44, 14, 16, 3, 20, 2, 0, 2, 76, 77, 80, 78, + 24, 1, 9, 72, 67, 71, 69, 74, 65, 73, 72, 82, + 18, 68, 3, 70, 80, 64, 72, 2, 5, 2, 2, 11, 8, + 72, 65, 2, 67, 2, 71, 4, 73, 0, 4, 65, 12, 5, + 65, 70, 6, 68, 75, 69, 84, 73, 6, 64, 7, 7, 7, + 30, 15, 4, 66, 8, 66, 70, 3, 97, 70, 0, 83, 65, + 75, 8, 2, 72, 15, 7, 4, 82, 8, 74, 75, 3, 89, + 11, 19, 13, 14, 17, 15, 8, 16, 15, 66, 4, 9, 3, + 1, 72, 64, 70, 68, 65, 68, 69, 67, 71, 79, 70, + 71, 79, 80, 81, 0, 3, 66, 67, 72, 75, 73, 73, + 81, 88, 85, 88, 103, 97, 108, 69, 70, 87, 66, + 72, 75, 78, 85, 89, 83, 86, 87, 79, 84, 91, 84, + 87, 1, 24, 18, 11, 6, 9, 2, 66, 64, 0, 7, 33, + 21, 15, 8, 18, 8, 7, 1, 12, 3, 41, 30, 23, 16, + 22, 5, 64, 67, 67, 70, 43, 21, 6, 2, 10, 65, 74, + 72, 5, 40, 27, 14, 7, 19, 5, 0, 65, 67, 62, 76, + 70, 64, 66, 69, 66, 3, 5, 1, 4, 7, 10, 73, 68, + 79, 72, 19, 74, 87, 71, 1, 67, 75, 78, 72, 77, + 83, 89, 92, 0, 2, 15, 64, 70, 2, 3, 4, 70, 66, + 0, 64, 78, 72, 77, 64, 9, 83, 66, 12, 70, 4, 65, + 3, 18, 69, 70, 4, 10, 79, 79, 101, 21, 25, 32, + 12, 7, 14, 6, 6, 8, 64, 2, 2, 75, 73, 76, 77, + 74, 82, 103, 77, 77, 76, 66, 72, 6, 13, 0, 9, + 17, 3, 0, 65, 66, 84, 77, 76, 93, 81, 89, 88, + 102, 98, 101, 102, 78, 76, 103, 71, 71, 81, 92, + 86, 87, 91, 86, 92, 93, 99, 102, 110, 110, 67, + 75, 90, 70, 8, 6, 14, 15, 18, 33, 21, 29, 30, + 46, 36, 33, 48, 45, 45, 14, 65, 80, 90, 104, + 119, 126, 126, 11, 44, 37, 36, 27, 34, 17, 13, + 15, 5, 73, 67, 9, 65, 22, 31, 65, 3, 13, 17, 5, + 15, 29, 4, 0, 29, 3, 80, 101, 116, 126, 126, + 126, 126 }, + + { + + 32, + 5, 80, 32, 5, 80, 1, 13, 24, 12, 67, 86, 8, 15, + 52, 14, 31, 68, 16, 24, 64, 76, 0, 89, 105, 0, + 67, 126, 126, 124, 43, 6, 69, 16, 24, 64, 72, 8, + 16, 65, 1, 67, 70, 68, 83, 74, 92, 5, 69, 70, + 65, 78, 74, 88, 13, 1, 64, 68, 8, 3, 22, 0, 0, + 0, 1, 95, 97, 8, 71, 68, 18, 68, 89, 0, 30, 20, + 50, 46, 16, 17, 4, 21, 3, 1, 4, 75, 77, 79, 77, + 24, 1, 9, 72, 67, 71, 68, 72, 65, 73, 72, 82, + 18, 68, 4, 69, 80, 64, 72, 3, 5, 2, 2, 11, 8, + 71, 65, 3, 67, 2, 71, 3, 73, 0, 3, 66, 12, 4, + 66, 69, 7, 68, 76, 69, 83, 73, 6, 64, 7, 7, 7, + 31, 15, 4, 66, 8, 66, 71, 3, 98, 70, 0, 83, 66, + 76, 8, 2, 73, 15, 7, 4, 83, 8, 75, 75, 3, 89, + 10, 19, 13, 14, 17, 15, 8, 16, 15, 66, 4, 9, 3, + 1, 72, 64, 70, 69, 65, 68, 70, 68, 71, 79, 71, + 71, 80, 81, 81, 64, 1, 67, 68, 74, 77, 75, 75, + 83, 90, 87, 90, 105, 98, 109, 69, 70, 87, 67, + 73, 76, 79, 86, 91, 84, 87, 88, 79, 84, 91, 84, + 87, 1, 24, 18, 11, 6, 9, 3, 65, 0, 1, 7, 33, 21, + 15, 8, 18, 9, 8, 2, 14, 3, 41, 30, 23, 16, 22, + 5, 64, 67, 66, 70, 44, 21, 5, 2, 10, 65, 74, 71, + 5, 40, 26, 13, 6, 19, 5, 0, 65, 66, 62, 75, 69, + 0, 65, 68, 65, 4, 6, 2, 5, 9, 12, 72, 67, 79, + 72, 20, 74, 87, 70, 2, 67, 76, 78, 72, 77, 84, + 90, 93, 64, 1, 15, 65, 70, 2, 3, 4, 71, 67, 0, + 64, 79, 72, 77, 64, 9, 83, 67, 12, 70, 4, 66, 2, + 18, 70, 71, 3, 9, 80, 80, 103, 20, 24, 32, 11, + 6, 12, 4, 4, 6, 67, 64, 0, 78, 75, 77, 80, 77, + 85, 107, 80, 80, 78, 67, 73, 6, 14, 0, 10, 19, + 1, 64, 67, 68, 86, 79, 77, 95, 82, 91, 89, 104, + 100, 102, 103, 78, 76, 104, 72, 73, 83, 94, 87, + 88, 93, 87, 93, 95, 100, 103, 111, 111, 67, 76, + 91, 69, 9, 6, 14, 16, 19, 34, 22, 30, 31, 48, + 37, 34, 49, 47, 45, 12, 67, 83, 92, 107, 122, + 126, 126, 11, 45, 38, 37, 27, 35, 17, 13, 16, 6, + 73, 67, 10, 64, 23, 32, 64, 4, 13, 17, 5, 16, + 30, 4, 0, 28, 1, 83, 104, 119, 126, 126, 126, + 126 }, + + { + + 31, + 5, 81, 31, 5, 81, 3, 14, 25, 12, 67, 87, 7, 14, + 52, 14, 33, 68, 16, 25, 64, 77, 0, 90, 106, 64, + 68, 126, 126, 125, 46, 7, 69, 16, 25, 64, 71, 9, + 17, 66, 2, 66, 69, 69, 83, 74, 92, 5, 68, 70, + 65, 78, 73, 88, 13, 1, 64, 68, 8, 3, 22, 0, 0, + 0, 2, 95, 97, 8, 71, 69, 18, 68, 88, 2, 32, 22, + 51, 48, 17, 18, 6, 22, 4, 1, 5, 75, 77, 79, 77, + 25, 1, 9, 71, 66, 70, 68, 71, 65, 74, 73, 83, + 18, 68, 4, 69, 80, 0, 72, 3, 5, 2, 3, 11, 7, 71, + 65, 3, 67, 2, 71, 2, 73, 0, 2, 67, 11, 4, 66, + 69, 7, 68, 76, 70, 83, 74, 6, 64, 7, 7, 7, 31, + 15, 4, 66, 9, 66, 72, 3, 98, 71, 0, 84, 66, 77, + 8, 2, 74, 15, 7, 4, 85, 8, 76, 75, 3, 90, 9, 19, + 12, 14, 17, 15, 8, 16, 15, 66, 3, 9, 2, 1, 72, + 65, 71, 69, 66, 69, 71, 69, 72, 79, 71, 70, 81, + 83, 81, 66, 64, 69, 70, 76, 79, 77, 77, 85, 92, + 88, 91, 107, 100, 110, 69, 70, 88, 68, 74, 77, + 81, 88, 92, 85, 88, 89, 80, 85, 91, 84, 86, 2, + 24, 18, 11, 7, 9, 3, 65, 0, 2, 8, 32, 21, 15, 8, + 19, 9, 9, 3, 16, 3, 42, 30, 23, 16, 23, 5, 64, + 66, 66, 70, 44, 21, 5, 2, 10, 64, 73, 70, 5, 39, + 26, 12, 6, 19, 5, 1, 64, 66, 62, 75, 68, 0, 64, + 67, 64, 6, 7, 3, 6, 10, 13, 72, 66, 79, 71, 22, + 74, 88, 70, 2, 67, 76, 79, 72, 78, 85, 91, 94, + 64, 1, 15, 65, 71, 1, 2, 4, 71, 67, 64, 64, 79, + 72, 78, 64, 9, 84, 67, 11, 71, 3, 67, 2, 19, 70, + 72, 3, 9, 80, 80, 104, 19, 23, 31, 10, 5, 11, 2, + 2, 4, 69, 66, 66, 81, 78, 78, 82, 80, 88, 111, + 83, 82, 80, 68, 74, 6, 15, 0, 12, 22, 64, 66, + 69, 70, 88, 81, 79, 97, 84, 92, 90, 105, 101, + 103, 104, 79, 77, 105, 73, 74, 84, 96, 89, 90, + 94, 89, 95, 96, 102, 105, 113, 112, 68, 77, 92, + 69, 9, 6, 15, 16, 19, 36, 23, 31, 32, 49, 39, + 35, 51, 48, 44, 11, 69, 85, 95, 109, 125, 126, + 126, 12, 45, 38, 37, 27, 36, 18, 13, 16, 6, 73, + 66, 10, 64, 24, 33, 64, 4, 14, 18, 5, 16, 31, 4, + 0, 27, 64, 85, 107, 123, 126, 126, 126, 126 }, + + { + + 30, + 5, 81, 30, 5, 81, 5, 16, 25, 12, 68, 89, 5, 12, + 52, 14, 36, 69, 17, 25, 64, 78, 0, 91, 107, 64, + 70, 126, 126, 125, 49, 8, 69, 17, 25, 64, 71, + 10, 17, 66, 2, 66, 69, 69, 84, 73, 92, 5, 68, + 69, 66, 78, 73, 88, 14, 2, 64, 67, 9, 3, 22, 0, + 0, 0, 2, 95, 97, 9, 72, 69, 18, 68, 88, 3, 34, + 23, 53, 50, 18, 19, 7, 24, 5, 2, 7, 74, 76, 78, + 76, 25, 1, 9, 71, 66, 70, 67, 69, 66, 74, 73, + 83, 18, 68, 4, 68, 80, 0, 72, 4, 5, 1, 3, 11, 7, + 71, 65, 3, 67, 2, 71, 2, 73, 0, 2, 68, 10, 3, + 67, 68, 8, 68, 77, 70, 82, 74, 6, 65, 7, 7, 7, + 32, 16, 4, 66, 9, 66, 73, 3, 99, 71, 0, 84, 67, + 78, 7, 2, 74, 16, 7, 4, 86, 7, 77, 75, 3, 90, 9, + 19, 12, 14, 17, 15, 8, 16, 15, 66, 3, 9, 2, 1, + 72, 65, 71, 70, 66, 69, 71, 70, 72, 79, 72, 70, + 82, 84, 81, 67, 66, 71, 72, 78, 81, 79, 79, 87, + 94, 90, 93, 110, 102, 111, 69, 71, 88, 69, 75, + 78, 82, 89, 94, 86, 89, 89, 80, 85, 91, 84, 86, + 2, 24, 18, 11, 7, 9, 3, 65, 1, 3, 8, 32, 21, 15, + 8, 19, 10, 10, 3, 18, 3, 42, 30, 23, 16, 23, 5, + 64, 66, 65, 70, 44, 20, 4, 2, 10, 64, 73, 70, 5, + 39, 25, 11, 5, 19, 5, 1, 64, 65, 62, 74, 67, 1, + 1, 66, 0, 7, 9, 4, 8, 12, 15, 71, 65, 78, 71, + 23, 73, 88, 70, 3, 67, 77, 79, 73, 79, 86, 92, + 95, 65, 0, 15, 66, 71, 1, 2, 4, 72, 68, 64, 65, + 80, 72, 78, 64, 9, 84, 68, 11, 72, 3, 68, 1, 19, + 71, 73, 2, 8, 81, 81, 106, 18, 23, 31, 9, 3, 9, + 0, 0, 2, 72, 69, 68, 84, 80, 80, 85, 84, 91, + 115, 85, 85, 82, 69, 75, 6, 16, 1, 13, 24, 65, + 68, 71, 71, 90, 83, 80, 99, 85, 94, 92, 107, + 103, 104, 105, 79, 77, 106, 75, 76, 86, 97, 91, + 92, 96, 90, 97, 98, 103, 106, 114, 112, 69, 77, + 93, 69, 10, 7, 15, 17, 20, 37, 24, 32, 33, 50, + 40, 36, 52, 49, 44, 9, 71, 88, 97, 112, 126, + 126, 126, 12, 46, 39, 38, 28, 37, 18, 14, 17, 7, + 72, 66, 11, 0, 25, 34, 64, 5, 14, 18, 5, 17, 32, + 4, 0, 25, 66, 88, 110, 126, 126, 126, 126, 126 }, + + { + + 28, + 4, 81, 28, 4, 81, 6, 17, 25, 12, 68, 90, 4, 11, + 52, 14, 38, 69, 18, 26, 64, 79, 0, 92, 109, 65, + 72, 126, 126, 126, 51, 9, 69, 18, 26, 64, 71, + 11, 17, 67, 2, 66, 68, 70, 84, 73, 93, 5, 68, + 69, 66, 79, 73, 88, 14, 2, 64, 67, 9, 3, 22, 0, + 0, 0, 3, 96, 97, 9, 73, 69, 18, 68, 88, 5, 35, + 25, 54, 51, 19, 20, 8, 25, 6, 3, 9, 74, 76, 78, + 75, 25, 1, 9, 70, 66, 69, 67, 68, 66, 75, 74, + 84, 18, 68, 4, 68, 80, 0, 72, 4, 4, 1, 3, 11, 6, + 71, 65, 3, 67, 1, 71, 1, 74, 0, 1, 70, 9, 2, 68, + 68, 8, 68, 78, 71, 82, 75, 5, 65, 7, 7, 7, 33, + 16, 4, 67, 9, 67, 74, 2, 100, 71, 0, 85, 67, 79, + 7, 1, 75, 16, 7, 4, 88, 7, 78, 75, 3, 91, 8, 18, + 12, 14, 17, 14, 7, 16, 14, 67, 2, 9, 2, 0, 73, + 66, 72, 70, 67, 70, 72, 71, 73, 79, 72, 70, 83, + 86, 81, 69, 68, 73, 74, 80, 84, 81, 81, 89, 96, + 92, 95, 112, 104, 112, 69, 71, 89, 70, 77, 80, + 84, 91, 95, 88, 91, 90, 81, 86, 91, 84, 85, 2, + 24, 18, 11, 7, 9, 3, 65, 1, 4, 9, 32, 21, 15, 8, + 19, 10, 10, 4, 19, 3, 42, 30, 23, 15, 23, 5, 64, + 66, 65, 70, 44, 20, 4, 2, 10, 64, 73, 69, 5, 38, + 24, 10, 4, 18, 5, 1, 64, 65, 62, 73, 66, 1, 2, + 65, 0, 8, 10, 5, 9, 13, 16, 71, 65, 78, 70, 24, + 73, 89, 70, 3, 67, 77, 80, 73, 80, 87, 94, 96, + 66, 0, 15, 66, 72, 0, 1, 3, 73, 69, 65, 65, 81, + 73, 79, 64, 9, 85, 69, 10, 73, 3, 69, 0, 19, 72, + 74, 2, 8, 82, 82, 108, 17, 22, 30, 7, 2, 8, 65, + 65, 64, 74, 72, 71, 87, 83, 81, 88, 87, 94, 119, + 88, 87, 84, 71, 77, 6, 16, 1, 14, 26, 67, 70, + 73, 73, 93, 85, 82, 101, 87, 96, 93, 109, 104, + 105, 106, 80, 78, 107, 76, 77, 88, 99, 93, 94, + 97, 92, 99, 99, 104, 108, 116, 113, 70, 78, 94, + 69, 10, 7, 16, 17, 20, 38, 24, 33, 34, 51, 41, + 37, 53, 50, 43, 7, 73, 90, 100, 115, 126, 126, + 126, 12, 46, 39, 38, 28, 37, 18, 14, 17, 7, 72, + 66, 11, 0, 26, 35, 64, 5, 15, 19, 5, 17, 32, 4, + 64, 24, 68, 90, 113, 126, 126, 126, 126, 126 }, + + { + + 27, + 4, 81, 27, 4, 81, 8, 18, 26, 12, 68, 91, 3, 10, + 52, 14, 40, 69, 19, 27, 64, 79, 1, 93, 110, 66, + 74, 126, 126, 126, 54, 11, 69, 19, 27, 64, 70, + 12, 18, 68, 2, 65, 67, 70, 84, 73, 93, 5, 68, + 68, 66, 79, 73, 88, 14, 2, 0, 67, 9, 3, 22, 0, + 0, 0, 4, 96, 97, 9, 74, 69, 18, 67, 88, 7, 37, + 27, 55, 53, 21, 21, 10, 26, 8, 4, 11, 74, 76, + 78, 74, 25, 1, 9, 69, 66, 68, 66, 67, 66, 75, + 74, 84, 18, 68, 5, 67, 79, 1, 72, 4, 4, 1, 3, + 11, 6, 70, 65, 4, 67, 1, 70, 0, 74, 0, 0, 71, 9, + 1, 69, 67, 8, 67, 79, 72, 82, 76, 5, 65, 8, 7, + 7, 34, 16, 4, 67, 10, 67, 74, 2, 101, 71, 0, 86, + 67, 80, 7, 1, 76, 16, 7, 4, 89, 7, 78, 75, 3, + 92, 7, 18, 12, 14, 17, 14, 7, 16, 14, 67, 2, 9, + 2, 0, 73, 66, 72, 70, 67, 70, 73, 71, 73, 79, + 72, 70, 84, 87, 81, 71, 69, 74, 75, 82, 86, 82, + 82, 91, 98, 93, 96, 114, 105, 113, 69, 71, 90, + 71, 78, 81, 85, 92, 96, 89, 92, 91, 82, 87, 91, + 83, 84, 3, 25, 18, 11, 7, 10, 4, 64, 2, 5, 10, + 32, 21, 15, 8, 20, 10, 11, 5, 21, 3, 42, 30, 23, + 15, 24, 5, 64, 66, 64, 70, 45, 20, 4, 2, 11, 64, + 73, 68, 5, 38, 24, 10, 3, 18, 5, 1, 0, 64, 62, + 72, 65, 2, 3, 0, 1, 10, 11, 7, 10, 14, 18, 70, + 64, 78, 69, 26, 73, 90, 69, 4, 67, 77, 81, 73, + 80, 88, 95, 97, 66, 0, 15, 66, 72, 64, 1, 3, 74, + 69, 65, 65, 82, 73, 79, 0, 10, 85, 69, 9, 73, 3, + 69, 0, 19, 73, 75, 2, 8, 83, 82, 109, 17, 21, + 29, 6, 1, 7, 67, 67, 66, 76, 74, 74, 89, 85, 82, + 91, 90, 97, 123, 91, 89, 85, 72, 78, 6, 17, 1, + 15, 28, 69, 71, 75, 75, 95, 86, 83, 103, 88, 97, + 94, 110, 105, 106, 106, 80, 78, 108, 77, 78, 89, + 101, 94, 95, 98, 93, 100, 100, 105, 109, 117, + 114, 70, 79, 94, 68, 10, 7, 17, 18, 21, 39, 25, + 34, 35, 53, 42, 38, 55, 52, 42, 6, 75, 92, 103, + 118, 126, 126, 126, 13, 46, 39, 39, 28, 38, 19, + 14, 18, 8, 72, 65, 12, 0, 27, 37, 0, 6, 16, 20, + 5, 18, 33, 4, 64, 23, 70, 92, 115, 126, 126, + 126, 126, 126 }, + + { + + 26, + 4, 81, 26, 4, 81, 10, 20, 26, 12, 69, 93, 1, 8, + 52, 14, 43, 70, 20, 27, 64, 80, 1, 94, 111, 66, + 76, 126, 126, 126, 57, 12, 69, 20, 27, 64, 70, + 13, 18, 68, 2, 65, 67, 70, 85, 72, 93, 5, 68, + 67, 67, 79, 73, 88, 15, 3, 0, 66, 10, 3, 22, 0, + 0, 0, 4, 96, 97, 10, 75, 69, 18, 67, 88, 8, 39, + 28, 57, 55, 22, 22, 11, 28, 9, 5, 13, 73, 75, + 77, 73, 25, 1, 9, 69, 66, 68, 66, 65, 67, 75, + 74, 84, 18, 68, 5, 67, 79, 1, 72, 5, 4, 0, 3, + 11, 6, 70, 65, 4, 67, 1, 70, 0, 74, 0, 0, 72, 8, + 0, 70, 66, 9, 67, 80, 72, 81, 76, 5, 66, 8, 7, + 7, 35, 17, 4, 67, 10, 67, 75, 2, 102, 71, 0, 86, + 68, 81, 6, 1, 76, 17, 7, 4, 90, 6, 79, 75, 3, + 92, 7, 18, 12, 14, 17, 14, 7, 16, 14, 67, 2, 9, + 2, 0, 73, 66, 72, 71, 67, 70, 73, 72, 74, 79, + 73, 70, 85, 88, 81, 72, 71, 76, 77, 84, 88, 84, + 84, 93, 100, 95, 98, 117, 107, 114, 69, 72, 90, + 72, 79, 82, 86, 94, 98, 90, 93, 91, 82, 87, 91, + 83, 84, 3, 25, 18, 11, 7, 10, 4, 64, 2, 6, 10, + 32, 21, 15, 8, 20, 11, 12, 5, 23, 3, 42, 30, 23, + 15, 24, 5, 64, 66, 0, 70, 45, 19, 3, 2, 11, 64, + 73, 68, 5, 37, 23, 9, 2, 18, 5, 1, 0, 64, 62, + 71, 64, 3, 5, 1, 2, 11, 13, 8, 12, 16, 20, 69, + 0, 77, 69, 27, 72, 90, 69, 5, 67, 78, 81, 74, + 81, 89, 96, 98, 67, 64, 15, 67, 73, 64, 1, 3, + 75, 70, 65, 66, 83, 73, 79, 0, 10, 85, 70, 9, + 74, 3, 70, 64, 19, 74, 76, 1, 7, 84, 83, 111, + 16, 21, 29, 5, 64, 5, 69, 69, 68, 79, 77, 76, + 92, 87, 84, 94, 94, 100, 126, 93, 92, 87, 73, + 79, 6, 18, 2, 16, 30, 70, 73, 77, 76, 97, 88, + 85, 105, 89, 99, 96, 112, 107, 107, 107, 81, 78, + 109, 79, 80, 91, 102, 96, 97, 100, 95, 102, 102, + 106, 110, 118, 114, 71, 79, 95, 68, 11, 8, 17, + 18, 22, 40, 26, 35, 36, 54, 43, 39, 56, 53, 42, + 4, 77, 95, 105, 121, 126, 126, 126, 13, 47, 40, + 39, 29, 39, 19, 15, 18, 8, 71, 65, 13, 1, 28, + 38, 0, 7, 16, 20, 5, 18, 34, 4, 64, 21, 72, 95, + 118, 126, 126, 126, 126, 126 }, + + { + + 25, + 4, 82, 25, 4, 82, 12, 21, 27, 12, 69, 94, 0, 7, + 52, 14, 45, 70, 20, 28, 64, 81, 1, 95, 112, 67, + 77, 126, 126, 126, 60, 13, 69, 20, 28, 64, 69, + 14, 19, 69, 3, 64, 66, 71, 85, 72, 93, 5, 67, + 67, 67, 79, 72, 88, 15, 3, 0, 66, 10, 3, 22, 0, + 0, 0, 5, 96, 97, 10, 75, 70, 18, 67, 87, 10, 41, + 30, 58, 57, 23, 23, 13, 29, 10, 5, 14, 73, 75, + 77, 73, 26, 1, 9, 68, 65, 67, 65, 64, 67, 76, + 75, 85, 18, 68, 5, 66, 79, 2, 72, 5, 4, 0, 4, + 11, 5, 70, 65, 4, 67, 1, 70, 64, 74, 0, 64, 73, + 7, 0, 70, 66, 9, 67, 80, 73, 81, 77, 5, 66, 8, + 7, 7, 35, 17, 4, 67, 11, 67, 76, 2, 102, 72, 0, + 87, 68, 82, 6, 1, 77, 17, 7, 4, 92, 6, 80, 75, + 3, 93, 6, 18, 11, 14, 17, 14, 7, 16, 14, 67, 1, + 9, 1, 0, 73, 67, 73, 71, 68, 71, 74, 73, 74, 79, + 73, 69, 86, 90, 81, 74, 73, 78, 79, 86, 90, 86, + 86, 95, 102, 96, 99, 119, 109, 115, 69, 72, 91, + 73, 80, 83, 88, 95, 99, 91, 94, 92, 83, 88, 91, + 83, 83, 4, 25, 18, 11, 8, 10, 4, 64, 3, 7, 11, + 31, 21, 15, 8, 21, 11, 13, 6, 25, 3, 43, 30, 23, + 15, 25, 5, 64, 65, 0, 70, 45, 19, 3, 2, 11, 0, + 72, 67, 5, 37, 23, 8, 2, 18, 5, 2, 1, 0, 62, 71, + 0, 3, 6, 2, 3, 13, 14, 9, 13, 17, 21, 69, 1, 77, + 68, 29, 72, 91, 69, 5, 67, 78, 82, 74, 82, 90, + 97, 99, 67, 64, 15, 67, 73, 65, 0, 3, 75, 70, + 66, 66, 83, 73, 80, 0, 10, 86, 70, 8, 75, 2, 71, + 64, 20, 74, 77, 1, 7, 84, 83, 112, 15, 20, 28, + 4, 65, 4, 71, 71, 70, 81, 79, 79, 95, 90, 85, + 96, 97, 103, 126, 96, 94, 89, 74, 80, 6, 19, 2, + 18, 33, 72, 75, 79, 78, 99, 90, 86, 107, 91, + 100, 97, 113, 108, 108, 108, 81, 79, 110, 80, + 81, 92, 104, 98, 99, 101, 96, 104, 103, 108, + 112, 120, 115, 72, 80, 96, 68, 11, 8, 18, 19, + 22, 42, 27, 36, 37, 55, 45, 40, 58, 54, 41, 3, + 79, 97, 108, 123, 126, 126, 126, 14, 47, 40, 40, + 29, 40, 20, 15, 19, 9, 71, 64, 13, 1, 29, 39, 0, + 7, 17, 21, 5, 19, 35, 4, 64, 20, 74, 97, 121, + 126, 126, 126, 126, 126 }, + + { + + 23, + 4, 82, 23, 4, 82, 13, 23, 27, 12, 70, 96, 65, 6, + 52, 14, 47, 70, 21, 29, 64, 82, 1, 96, 113, 67, + 79, 126, 126, 126, 62, 14, 69, 21, 29, 64, 69, + 15, 19, 69, 3, 64, 65, 71, 86, 72, 93, 5, 67, + 66, 67, 80, 72, 88, 16, 3, 0, 66, 11, 3, 22, 0, + 0, 0, 5, 97, 97, 11, 76, 70, 18, 67, 87, 12, 42, + 31, 60, 58, 24, 24, 14, 30, 11, 6, 16, 72, 75, + 76, 72, 26, 1, 9, 68, 65, 67, 65, 1, 67, 76, 75, + 85, 18, 68, 5, 66, 79, 2, 72, 6, 4, 0, 4, 11, 5, + 70, 65, 4, 67, 1, 70, 65, 75, 0, 65, 74, 6, 64, + 71, 65, 10, 67, 81, 73, 80, 77, 5, 66, 8, 7, 7, + 36, 17, 4, 68, 11, 68, 77, 1, 103, 72, 0, 87, + 69, 83, 6, 0, 78, 17, 7, 4, 93, 6, 81, 75, 3, + 93, 5, 18, 11, 14, 17, 14, 7, 16, 13, 67, 1, 9, + 1, 0, 74, 67, 73, 72, 68, 71, 75, 74, 75, 79, + 74, 69, 87, 91, 81, 75, 75, 80, 81, 88, 92, 88, + 88, 97, 104, 98, 101, 121, 111, 116, 69, 72, 91, + 74, 81, 84, 89, 97, 101, 92, 96, 93, 83, 88, 91, + 83, 83, 4, 25, 18, 11, 8, 10, 4, 64, 3, 8, 11, + 31, 21, 15, 8, 21, 12, 13, 7, 27, 3, 43, 30, 23, + 15, 25, 5, 64, 65, 1, 70, 45, 19, 2, 2, 11, 0, + 72, 66, 5, 36, 22, 7, 1, 18, 5, 2, 1, 0, 62, 70, + 1, 4, 7, 3, 4, 14, 15, 10, 14, 19, 23, 68, 1, + 77, 68, 30, 72, 91, 69, 6, 67, 79, 82, 74, 83, + 91, 98, 100, 68, 65, 15, 68, 74, 65, 0, 3, 76, + 71, 66, 66, 84, 74, 80, 0, 10, 86, 71, 8, 76, 2, + 72, 65, 20, 75, 78, 0, 6, 85, 84, 114, 14, 19, + 28, 3, 66, 2, 73, 73, 72, 84, 82, 81, 98, 92, + 86, 99, 100, 106, 126, 99, 97, 91, 75, 82, 6, + 20, 2, 19, 35, 74, 77, 81, 80, 101, 92, 88, 109, + 92, 102, 98, 115, 110, 109, 109, 82, 79, 111, + 81, 83, 94, 106, 100, 101, 103, 98, 106, 105, + 109, 113, 121, 116, 73, 81, 97, 68, 12, 8, 18, + 19, 23, 43, 27, 37, 38, 56, 46, 41, 59, 55, 41, + 1, 81, 100, 110, 126, 126, 126, 126, 14, 48, 41, + 40, 29, 40, 20, 15, 19, 9, 71, 64, 14, 2, 30, + 40, 0, 8, 17, 21, 5, 19, 36, 4, 64, 19, 76, 100, + 124, 126, 126, 126, 126, 126 }, + + { + + 22, + 4, 82, 22, 4, 82, 15, 24, 27, 12, 70, 97, 66, 4, + 52, 14, 50, 71, 22, 29, 64, 82, 2, 97, 114, 68, + 81, 126, 126, 126, 62, 16, 69, 22, 29, 64, 69, + 16, 19, 70, 3, 64, 65, 71, 86, 71, 93, 5, 67, + 65, 68, 80, 72, 88, 16, 4, 1, 65, 11, 3, 22, 0, + 0, 0, 6, 97, 97, 11, 77, 70, 18, 66, 87, 13, 44, + 33, 61, 60, 26, 25, 15, 32, 12, 7, 18, 72, 74, + 76, 71, 26, 1, 9, 67, 65, 66, 64, 2, 68, 76, 75, + 85, 18, 68, 6, 65, 79, 2, 72, 6, 4, 64, 4, 11, + 5, 69, 65, 5, 67, 1, 70, 65, 75, 0, 65, 75, 6, + 65, 72, 64, 10, 67, 82, 74, 80, 78, 5, 67, 8, 7, + 7, 37, 18, 4, 68, 11, 68, 78, 1, 104, 72, 0, 88, + 69, 84, 5, 0, 78, 18, 7, 4, 94, 5, 82, 75, 3, + 94, 5, 18, 11, 14, 17, 14, 7, 16, 13, 67, 1, 9, + 1, 0, 74, 67, 73, 72, 68, 71, 75, 75, 75, 79, + 74, 69, 88, 92, 81, 77, 77, 81, 82, 90, 94, 90, + 90, 99, 106, 100, 103, 124, 112, 117, 69, 73, + 92, 75, 82, 85, 90, 98, 102, 93, 97, 93, 84, 89, + 91, 83, 82, 4, 25, 18, 11, 8, 10, 5, 0, 4, 9, + 12, 31, 21, 15, 8, 21, 12, 14, 7, 29, 3, 43, 30, + 23, 15, 25, 5, 64, 65, 2, 70, 46, 18, 2, 2, 11, + 0, 72, 66, 5, 36, 21, 6, 0, 18, 5, 2, 1, 1, 62, + 69, 2, 5, 9, 4, 5, 15, 17, 11, 16, 20, 25, 67, + 2, 76, 67, 31, 71, 92, 68, 7, 67, 79, 83, 75, + 83, 92, 99, 101, 69, 65, 15, 68, 74, 66, 0, 3, + 77, 72, 66, 67, 85, 74, 80, 0, 10, 86, 72, 7, + 76, 2, 73, 66, 20, 76, 79, 0, 6, 86, 85, 116, + 13, 19, 27, 2, 68, 1, 75, 75, 74, 86, 85, 84, + 101, 94, 88, 102, 104, 109, 126, 101, 99, 93, + 76, 83, 6, 21, 3, 20, 37, 75, 78, 83, 81, 103, + 94, 89, 111, 93, 104, 100, 117, 111, 110, 110, + 82, 79, 112, 83, 84, 96, 107, 101, 102, 104, 99, + 107, 106, 110, 114, 122, 116, 73, 81, 98, 67, + 12, 9, 19, 20, 24, 44, 28, 38, 39, 58, 47, 42, + 60, 57, 40, 64, 83, 102, 113, 126, 126, 126, + 126, 14, 48, 41, 41, 30, 41, 20, 16, 20, 10, 70, + 64, 15, 2, 31, 41, 1, 9, 18, 22, 5, 20, 37, 4, + 64, 17, 78, 102, 126, 126, 126, 126, 126, 126 }, + + { + + 21, + 4, 82, 21, 4, 82, 17, 26, 28, 12, 71, 99, 68, 3, + 52, 14, 52, 71, 23, 30, 64, 83, 2, 98, 115, 68, + 83, 126, 126, 126, 62, 17, 69, 23, 30, 64, 68, + 17, 20, 70, 3, 0, 64, 72, 87, 71, 93, 5, 67, 65, + 68, 80, 72, 88, 17, 4, 1, 65, 12, 3, 22, 0, 0, + 0, 6, 97, 97, 12, 78, 70, 18, 66, 87, 15, 46, + 34, 62, 62, 27, 26, 17, 33, 13, 8, 20, 71, 74, + 75, 70, 26, 1, 9, 67, 65, 66, 64, 4, 68, 77, 76, + 86, 18, 68, 6, 65, 79, 3, 72, 7, 4, 64, 4, 11, + 4, 69, 65, 5, 67, 1, 70, 66, 75, 0, 66, 76, 5, + 66, 73, 64, 11, 67, 83, 74, 79, 78, 5, 67, 8, 7, + 7, 38, 18, 4, 68, 12, 68, 79, 1, 105, 72, 0, 88, + 70, 85, 5, 0, 79, 18, 7, 4, 96, 5, 83, 75, 3, + 94, 4, 18, 11, 14, 17, 14, 7, 16, 13, 67, 0, 9, + 1, 0, 74, 68, 74, 73, 69, 72, 76, 76, 76, 79, + 75, 69, 89, 94, 81, 78, 79, 83, 84, 92, 96, 92, + 92, 101, 108, 101, 104, 126, 114, 118, 69, 73, + 92, 76, 83, 86, 92, 100, 104, 94, 98, 94, 84, + 89, 91, 83, 82, 5, 25, 18, 11, 8, 10, 5, 0, 4, + 10, 12, 31, 21, 15, 8, 22, 13, 15, 8, 31, 3, 43, + 30, 23, 15, 26, 5, 64, 65, 2, 70, 46, 18, 1, 2, + 11, 0, 72, 65, 5, 35, 21, 5, 64, 18, 5, 2, 2, 1, + 62, 68, 3, 5, 10, 5, 6, 17, 18, 12, 17, 22, 26, + 67, 3, 76, 67, 33, 71, 92, 68, 7, 67, 80, 83, + 75, 84, 93, 100, 102, 69, 66, 15, 69, 75, 66, + 64, 3, 78, 72, 67, 67, 86, 74, 81, 0, 10, 87, + 72, 7, 77, 2, 74, 66, 20, 77, 80, 64, 5, 87, 85, + 117, 12, 18, 27, 1, 69, 64, 77, 77, 76, 89, 87, + 86, 104, 97, 89, 105, 107, 112, 126, 104, 102, + 95, 77, 84, 6, 22, 3, 21, 39, 77, 80, 85, 83, + 105, 96, 91, 113, 95, 105, 101, 118, 113, 111, + 111, 83, 80, 113, 84, 86, 97, 109, 103, 104, + 106, 101, 109, 108, 111, 116, 124, 117, 74, 82, + 99, 67, 13, 9, 19, 20, 24, 45, 29, 39, 40, 59, + 48, 43, 62, 58, 40, 65, 85, 105, 115, 126, 126, + 126, 126, 15, 49, 42, 41, 30, 42, 21, 16, 20, + 10, 70, 0, 15, 3, 32, 42, 1, 9, 18, 22, 5, 20, + 38, 4, 64, 16, 80, 105, 126, 126, 126, 126, 126, + 126 }, + + { + + 20, + 4, 82, 20, 4, 82, 19, 27, 28, 12, 71, 100, 69, + 2, 52, 14, 54, 71, 24, 31, 64, 84, 2, 99, 116, + 69, 85, 126, 126, 126, 62, 18, 69, 24, 31, 64, + 68, 18, 20, 71, 3, 0, 0, 72, 87, 71, 93, 5, 67, + 64, 68, 80, 72, 88, 17, 4, 1, 65, 12, 3, 22, 0, + 0, 0, 7, 97, 97, 12, 79, 70, 18, 66, 87, 17, 48, + 36, 62, 62, 28, 27, 18, 34, 14, 9, 22, 71, 74, + 75, 69, 26, 1, 9, 66, 65, 65, 0, 5, 68, 77, 76, + 86, 18, 68, 6, 64, 79, 3, 72, 7, 4, 64, 4, 11, + 4, 69, 65, 5, 67, 1, 70, 67, 75, 0, 67, 77, 4, + 67, 74, 0, 11, 67, 84, 75, 79, 79, 5, 67, 8, 7, + 7, 39, 18, 4, 68, 12, 68, 80, 1, 106, 72, 0, 89, + 70, 86, 5, 0, 80, 18, 7, 4, 97, 5, 84, 75, 3, + 95, 3, 18, 11, 14, 17, 14, 7, 16, 13, 67, 0, 9, + 1, 0, 74, 68, 74, 73, 69, 72, 77, 77, 76, 79, + 75, 69, 90, 95, 81, 80, 81, 85, 86, 94, 98, 94, + 94, 103, 110, 103, 106, 126, 116, 119, 69, 73, + 93, 77, 84, 87, 93, 101, 105, 95, 99, 95, 85, + 90, 91, 83, 81, 5, 25, 18, 11, 8, 10, 5, 0, 5, + 11, 13, 31, 21, 15, 8, 22, 13, 16, 9, 33, 3, 43, + 30, 23, 15, 26, 5, 64, 65, 3, 70, 46, 18, 1, 2, + 11, 0, 72, 64, 5, 35, 20, 4, 65, 18, 5, 2, 2, 2, + 62, 67, 4, 6, 11, 6, 7, 18, 19, 13, 18, 23, 28, + 66, 4, 76, 66, 34, 71, 93, 68, 8, 67, 80, 84, + 75, 85, 94, 101, 103, 70, 66, 15, 69, 75, 67, + 64, 3, 79, 73, 67, 67, 87, 74, 81, 0, 10, 87, + 73, 6, 78, 2, 75, 67, 20, 78, 81, 64, 5, 88, 86, + 119, 11, 17, 26, 0, 70, 65, 79, 79, 78, 91, 90, + 89, 107, 99, 90, 108, 110, 115, 126, 107, 104, + 97, 78, 85, 6, 23, 3, 22, 41, 79, 82, 87, 85, + 107, 98, 92, 115, 96, 107, 102, 120, 114, 112, + 112, 83, 80, 114, 85, 87, 99, 111, 105, 106, + 107, 102, 111, 109, 112, 117, 125, 118, 75, 83, + 100, 67, 13, 9, 20, 21, 25, 46, 30, 40, 41, 60, + 49, 44, 62, 59, 39, 67, 87, 107, 118, 126, 126, + 126, 126, 15, 49, 42, 42, 30, 43, 21, 16, 21, + 11, 70, 0, 16, 3, 33, 43, 1, 10, 19, 23, 5, 21, + 39, 4, 64, 15, 82, 107, 126, 126, 126, 126, 126, + 126 }, + + { + + 18, + 3, 83, 18, 3, 83, 20, 28, 28, 12, 72, 102, 71, + 0, 51, 14, 56, 72, 24, 31, 65, 85, 2, 101, 118, + 70, 87, 126, 126, 126, 62, 19, 70, 24, 31, 65, + 68, 19, 20, 72, 3, 0, 0, 73, 88, 71, 94, 5, 67, + 64, 69, 81, 72, 88, 17, 4, 1, 65, 12, 2, 22, 0, + 0, 0, 7, 98, 97, 12, 80, 71, 18, 66, 87, 18, 49, + 37, 62, 62, 29, 28, 19, 35, 15, 9, 23, 71, 74, + 75, 69, 26, 1, 9, 66, 65, 65, 0, 6, 69, 78, 77, + 87, 18, 68, 6, 64, 79, 3, 72, 7, 3, 65, 4, 10, + 3, 69, 66, 5, 67, 0, 70, 68, 76, 64, 68, 79, 3, + 68, 75, 0, 11, 67, 85, 76, 79, 80, 4, 68, 8, 7, + 7, 39, 18, 4, 69, 12, 69, 81, 0, 107, 73, 64, + 90, 71, 87, 4, 64, 81, 18, 7, 4, 99, 4, 85, 75, + 3, 96, 2, 17, 10, 14, 17, 13, 6, 16, 12, 68, 64, + 9, 0, 64, 75, 69, 75, 74, 70, 73, 78, 78, 77, + 79, 76, 69, 91, 97, 81, 82, 83, 87, 88, 96, 101, + 96, 96, 105, 112, 105, 108, 126, 118, 121, 70, + 74, 94, 78, 86, 89, 95, 103, 107, 97, 101, 96, + 86, 91, 91, 83, 81, 5, 25, 18, 11, 8, 10, 5, 0, + 5, 12, 13, 30, 21, 15, 8, 22, 13, 16, 9, 34, 2, + 43, 30, 22, 14, 26, 5, 64, 65, 3, 70, 46, 17, 0, + 1, 11, 0, 72, 64, 5, 34, 19, 3, 66, 17, 5, 2, 2, + 2, 62, 67, 5, 6, 12, 7, 7, 19, 20, 14, 19, 24, + 29, 66, 4, 76, 66, 35, 71, 94, 68, 8, 67, 81, + 85, 76, 86, 95, 103, 105, 71, 67, 15, 70, 76, + 68, 65, 2, 80, 74, 68, 68, 88, 75, 82, 0, 10, + 88, 74, 5, 79, 1, 76, 68, 20, 79, 83, 65, 4, 89, + 87, 121, 10, 16, 25, 65, 72, 67, 81, 81, 81, 94, + 93, 92, 110, 102, 92, 111, 114, 118, 126, 110, + 107, 99, 80, 87, 6, 23, 3, 23, 43, 81, 84, 89, + 87, 110, 100, 94, 118, 98, 109, 104, 122, 116, + 113, 113, 84, 81, 116, 87, 89, 101, 113, 107, + 108, 109, 104, 113, 111, 114, 119, 126, 119, 76, + 84, 101, 67, 13, 9, 20, 21, 25, 47, 30, 41, 41, + 61, 50, 45, 62, 60, 38, 69, 90, 110, 121, 126, + 126, 126, 126, 15, 49, 42, 42, 30, 43, 21, 16, + 21, 11, 70, 0, 16, 3, 34, 44, 1, 10, 19, 23, 5, + 21, 39, 4, 65, 13, 85, 110, 126, 126, 126, 126, + 126, 126 }, + + { + + 17, + 3, 83, 17, 3, 83, 22, 30, 29, 13, 72, 103, 72, + 64, 51, 14, 59, 72, 25, 32, 65, 85, 3, 102, 119, + 70, 88, 126, 126, 126, 62, 21, 70, 25, 32, 65, + 67, 21, 21, 72, 4, 1, 1, 73, 88, 70, 94, 5, 66, + 0, 69, 81, 71, 88, 18, 5, 2, 64, 13, 2, 22, 0, + 0, 0, 8, 98, 97, 13, 80, 71, 18, 65, 86, 20, 51, + 39, 62, 62, 31, 29, 21, 37, 17, 10, 25, 70, 73, + 74, 68, 27, 2, 10, 65, 64, 64, 1, 8, 69, 78, 77, + 87, 19, 68, 7, 0, 78, 4, 71, 8, 3, 65, 5, 10, 3, + 68, 66, 6, 67, 0, 69, 68, 76, 64, 68, 80, 3, 68, + 75, 1, 12, 66, 85, 76, 78, 80, 4, 68, 9, 7, 7, + 40, 19, 5, 69, 13, 69, 81, 0, 107, 73, 64, 90, + 71, 88, 4, 64, 81, 19, 8, 4, 100, 4, 85, 74, 3, + 96, 2, 17, 10, 14, 17, 13, 6, 16, 12, 68, 64, 9, + 0, 64, 75, 69, 75, 74, 70, 73, 78, 78, 77, 78, + 76, 68, 91, 98, 80, 83, 84, 88, 89, 98, 103, 97, + 97, 107, 113, 106, 109, 126, 119, 122, 70, 74, + 94, 79, 87, 90, 96, 104, 108, 98, 102, 96, 86, + 91, 90, 82, 80, 6, 26, 18, 11, 9, 11, 6, 1, 6, + 14, 14, 30, 21, 15, 8, 23, 14, 17, 10, 36, 2, + 44, 31, 22, 14, 27, 5, 64, 64, 4, 70, 47, 17, 0, + 1, 12, 1, 71, 0, 5, 34, 19, 3, 66, 17, 5, 3, 3, + 3, 62, 66, 6, 7, 14, 9, 8, 21, 22, 16, 21, 26, + 31, 65, 5, 75, 65, 37, 70, 94, 67, 9, 66, 81, + 85, 76, 86, 95, 104, 106, 71, 67, 16, 70, 76, + 68, 65, 2, 80, 74, 68, 68, 88, 75, 82, 1, 11, + 88, 74, 5, 79, 1, 76, 68, 21, 79, 84, 65, 4, 89, + 87, 122, 10, 16, 25, 66, 73, 68, 83, 83, 83, 96, + 95, 94, 112, 104, 93, 113, 117, 121, 126, 112, + 109, 100, 81, 88, 6, 24, 4, 25, 46, 82, 85, 90, + 88, 112, 101, 95, 120, 99, 110, 105, 123, 117, + 114, 113, 84, 81, 117, 88, 90, 102, 114, 108, + 109, 110, 105, 114, 112, 115, 120, 126, 119, 76, + 84, 101, 66, 14, 10, 21, 22, 26, 49, 31, 42, 42, + 62, 52, 46, 62, 62, 38, 70, 92, 112, 123, 126, + 126, 126, 126, 16, 50, 43, 43, 31, 44, 22, 17, + 22, 12, 69, 1, 17, 4, 36, 46, 2, 11, 20, 24, 6, + 22, 40, 4, 65, 12, 87, 112, 126, 126, 126, 126, + 126, 126 }, + + { + + 16, + 3, 83, 16, 3, 83, 24, 31, 29, 13, 72, 104, 73, + 65, 51, 14, 61, 72, 26, 33, 65, 86, 3, 103, 120, + 71, 90, 126, 126, 126, 62, 22, 70, 26, 33, 65, + 67, 22, 21, 73, 4, 1, 2, 73, 88, 70, 94, 5, 66, + 1, 69, 81, 71, 88, 18, 5, 2, 64, 13, 2, 22, 0, + 0, 0, 9, 98, 97, 13, 81, 71, 18, 65, 86, 22, 53, + 41, 62, 62, 32, 30, 22, 38, 18, 11, 27, 70, 73, + 74, 67, 27, 2, 10, 64, 64, 0, 1, 9, 69, 78, 77, + 87, 19, 68, 7, 0, 78, 4, 71, 8, 3, 65, 5, 10, 3, + 68, 66, 6, 67, 0, 69, 69, 76, 64, 69, 81, 2, 69, + 76, 2, 12, 66, 86, 77, 78, 81, 4, 68, 9, 7, 7, + 41, 19, 5, 69, 13, 69, 82, 0, 108, 73, 64, 91, + 71, 89, 4, 64, 82, 19, 8, 4, 101, 4, 86, 74, 3, + 97, 1, 17, 10, 14, 17, 13, 6, 16, 12, 68, 64, 9, + 0, 64, 75, 69, 75, 74, 70, 73, 79, 79, 78, 78, + 76, 68, 92, 99, 80, 85, 86, 90, 91, 100, 105, + 99, 99, 109, 115, 108, 111, 126, 121, 123, 70, + 74, 95, 80, 88, 91, 97, 106, 109, 99, 103, 97, + 87, 92, 90, 82, 79, 6, 26, 18, 11, 9, 11, 6, 1, + 6, 15, 15, 30, 21, 15, 8, 23, 14, 18, 11, 38, 2, + 44, 31, 22, 14, 27, 5, 64, 64, 5, 70, 47, 17, 0, + 1, 12, 1, 71, 1, 5, 33, 18, 2, 67, 17, 5, 3, 3, + 3, 62, 65, 7, 8, 15, 10, 9, 22, 23, 17, 22, 27, + 33, 64, 6, 75, 64, 38, 70, 95, 67, 10, 66, 81, + 86, 76, 87, 96, 105, 107, 72, 67, 16, 70, 77, + 69, 65, 2, 81, 75, 68, 68, 89, 75, 82, 1, 11, + 88, 75, 4, 80, 1, 77, 69, 21, 80, 85, 65, 4, 90, + 88, 124, 9, 15, 24, 67, 74, 69, 85, 85, 85, 98, + 98, 97, 115, 106, 94, 116, 120, 124, 126, 115, + 111, 102, 82, 89, 6, 25, 4, 26, 48, 84, 87, 92, + 90, 114, 103, 97, 122, 100, 112, 106, 125, 118, + 115, 114, 85, 81, 118, 89, 91, 104, 116, 110, + 111, 111, 107, 116, 113, 116, 121, 126, 120, 77, + 85, 102, 66, 14, 10, 22, 22, 27, 50, 32, 43, 43, + 62, 53, 47, 62, 62, 37, 72, 94, 114, 126, 126, + 126, 126, 126, 16, 50, 43, 43, 31, 45, 22, 17, + 22, 12, 69, 1, 18, 4, 37, 47, 2, 12, 21, 25, 6, + 22, 41, 4, 65, 11, 89, 114, 126, 126, 126, 126, + 126, 126 }, + + { + + 15, + 3, 83, 15, 3, 83, 26, 33, 30, 13, 73, 106, 75, + 66, 51, 14, 62, 72, 27, 34, 65, 87, 3, 104, 121, + 71, 92, 126, 126, 126, 62, 23, 70, 27, 34, 65, + 66, 23, 22, 73, 4, 2, 3, 74, 89, 70, 94, 5, 66, + 1, 69, 81, 71, 88, 19, 5, 2, 64, 14, 2, 22, 0, + 0, 0, 9, 98, 97, 14, 82, 71, 18, 65, 86, 24, 55, + 42, 62, 62, 33, 31, 24, 39, 19, 12, 29, 69, 73, + 73, 66, 27, 2, 10, 64, 64, 0, 2, 11, 69, 79, 78, + 88, 19, 68, 7, 1, 78, 5, 71, 9, 3, 65, 5, 10, 2, + 68, 66, 6, 67, 0, 69, 70, 76, 64, 70, 82, 1, 70, + 77, 2, 13, 66, 87, 77, 77, 81, 4, 68, 9, 7, 7, + 42, 19, 5, 69, 14, 69, 83, 0, 109, 73, 64, 91, + 72, 90, 4, 64, 83, 19, 8, 4, 103, 4, 87, 74, 3, + 97, 0, 17, 10, 14, 17, 13, 6, 16, 12, 68, 65, 9, + 0, 64, 75, 70, 76, 75, 71, 74, 80, 80, 78, 78, + 77, 68, 93, 101, 80, 86, 88, 92, 93, 102, 107, + 101, 101, 111, 117, 109, 112, 126, 123, 124, 70, + 74, 95, 81, 89, 92, 99, 107, 111, 100, 104, 98, + 87, 92, 90, 82, 79, 7, 26, 18, 11, 9, 11, 6, 1, + 7, 16, 15, 30, 21, 15, 8, 24, 15, 19, 12, 40, 2, + 44, 31, 22, 14, 28, 5, 64, 64, 5, 70, 47, 17, + 64, 1, 12, 1, 71, 2, 5, 33, 18, 1, 68, 17, 5, 3, + 4, 4, 62, 64, 8, 8, 16, 11, 10, 24, 24, 18, 23, + 29, 34, 64, 7, 75, 64, 40, 70, 95, 67, 10, 66, + 82, 86, 76, 88, 97, 106, 108, 72, 68, 16, 71, + 77, 69, 66, 2, 82, 75, 69, 68, 90, 75, 83, 1, + 11, 89, 75, 4, 81, 1, 78, 69, 21, 81, 86, 66, 3, + 91, 88, 125, 8, 14, 24, 68, 75, 71, 87, 87, 87, + 101, 100, 99, 118, 109, 95, 119, 123, 126, 126, + 118, 114, 104, 83, 90, 6, 26, 4, 27, 50, 86, 89, + 94, 92, 116, 105, 98, 124, 102, 113, 107, 126, + 120, 116, 115, 85, 82, 119, 90, 93, 105, 118, + 112, 113, 113, 108, 118, 115, 117, 123, 126, + 121, 78, 86, 103, 66, 15, 10, 22, 23, 27, 51, + 33, 44, 44, 62, 54, 48, 62, 62, 37, 73, 96, 117, + 126, 126, 126, 126, 126, 17, 51, 44, 44, 31, 46, + 23, 17, 23, 13, 69, 2, 18, 5, 38, 48, 2, 12, 21, + 25, 6, 23, 42, 4, 65, 10, 91, 117, 126, 126, + 126, 126, 126, 126 }, + + }, + + { + + { + + 62, + 9, 74, 62, 9, 74, 126, 104, 10, 9, 12, 38, 62, + 62, 54, 22, 118, 65, 71, 79, 11, 13, 70, 9, 29, + 41, 62, 61, 27, 69, 126, 101, 76, 71, 79, 11, + 69, 90, 11, 20, 69, 82, 96, 4, 75, 87, 100, 7, + 74, 85, 4, 81, 86, 95, 66, 77, 70, 86, 72, 2, + 22, 0, 0, 0, 83, 86, 97, 72, 22, 1, 48, 12, 80, + 126, 91, 96, 81, 98, 102, 97, 119, 99, 110, 102, + 126, 80, 89, 94, 92, 24, 65, 84, 126, 73, 104, + 91, 126, 8, 7, 8, 2, 10, 68, 74, 88, 103, 91, + 89, 92, 76, 87, 110, 105, 78, 112, 99, 126, 126, + 126, 126, 66, 78, 71, 72, 4, 8, 70, 75, 89, 119, + 75, 43, 41, 126, 9, 2, 5, 3, 2, 67, 84, 74, 65, + 11, 6, 2, 69, 70, 8, 71, 5, 2, 22, 38, 31, 20, + 16, 19, 12, 17, 25, 66, 25, 21, 29, 89, 18, 35, + 32, 62, 62, 48, 62, 62, 62, 62, 62, 62, 62, 62, + 62, 62, 53, 62, 62, 62, 62, 62, 62, 62, 56, 62, + 62, 62, 27, 62, 62, 62, 62, 62, 62, 62, 62, 62, + 62, 62, 53, 45, 38, 22, 75, 72, 77, 28, 32, 28, + 33, 18, 21, 18, 37, 9, 66, 7, 73, 67, 116, 112, + 71, 2, 10, 66, 77, 80, 84, 87, 126, 101, 24, 10, + 2, 75, 77, 91, 107, 111, 122, 76, 19, 11, 6, 5, + 72, 69, 69, 74, 86, 66, 29, 31, 32, 11, 8, 67, + 73, 89, 11, 59, 55, 55, 44, 26, 2, 73, 70, 78, + 62, 126, 124, 110, 126, 124, 105, 121, 117, 102, + 117, 116, 122, 95, 100, 95, 111, 114, 89, 80, + 82, 85, 81, 72, 64, 67, 7, 69, 69, 69, 69, 67, + 77, 64, 2, 67, 64, 6, 65, 66, 1, 12, 66, 71, 75, + 70, 72, 3, 26, 16, 28, 26, 22, 22, 15, 22, 22, + 4, 13, 23, 66, 13, 62, 62, 62, 62, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 54, 62, 62, 62, 62, + 62, 62, 62, 62, 62, 49, 37, 26, 8, 65, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 43, 33, + 19, 15, 14, 18, 41, 41, 42, 43, 35, 39, 29, 21, + 24, 13, 70, 9, 71, 83, 31, 14, 9, 85, 81, 77, + 81, 80, 73, 74, 83, 71, 67, 2, 66, 66, 4, 4, 62, + 62, 62, 62, 62, 60, 53, 36, 6, 71, 39, 27, 21, + 11, 6, 0, 65, 67, 82, 81, 76, 72, 78, 72, 68, + 70, 76, 66, 1, 6, 2, 3, 9, 5, 62, 62, 62, 62, + 62, 60, 53, 36, 6 }, + + { + + 62, + 9, 74, 62, 9, 74, 125, 102, 11, 10, 12, 37, + 61, 62, 55, 22, 116, 65, 70, 78, 11, 13, 69, + 9, 28, 40, 61, 58, 25, 70, 124, 100, 75, 70, + 78, 11, 69, 89, 11, 20, 68, 81, 95, 4, 75, 86, + 99, 7, 73, 84, 4, 80, 85, 94, 65, 76, 70, 85, + 71, 2, 22, 0, 0, 0, 82, 86, 97, 71, 22, 1, 48, + 12, 80, 124, 89, 94, 79, 95, 100, 95, 117, 97, + 108, 100, 124, 80, 88, 93, 91, 24, 65, 83, + 124, 72, 103, 90, 125, 8, 7, 8, 2, 11, 68, 73, + 87, 102, 90, 88, 91, 75, 86, 108, 103, 77, + 110, 97, 122, 122, 123, 124, 65, 77, 70, 71, + 4, 9, 69, 74, 88, 116, 74, 41, 40, 124, 9, 3, + 5, 4, 3, 66, 82, 73, 64, 11, 6, 2, 68, 69, 7, + 70, 5, 2, 22, 37, 31, 20, 16, 19, 12, 17, 24, + 65, 25, 21, 29, 89, 18, 35, 32, 62, 62, 47, + 62, 62, 62, 61, 62, 62, 62, 62, 62, 62, 52, + 62, 62, 62, 62, 62, 62, 62, 54, 62, 60, 62, + 26, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, + 61, 52, 44, 37, 21, 75, 72, 77, 28, 31, 27, + 32, 17, 20, 17, 36, 8, 66, 6, 73, 67, 115, + 110, 70, 3, 10, 65, 76, 79, 83, 86, 124, 99, + 25, 11, 3, 74, 76, 89, 105, 109, 120, 75, 20, + 12, 7, 6, 71, 68, 68, 73, 85, 66, 30, 31, 32, + 11, 9, 66, 73, 88, 11, 59, 55, 54, 43, 26, 3, + 72, 69, 77, 62, 124, 122, 108, 124, 122, 103, + 119, 115, 100, 115, 114, 119, 94, 99, 94, 109, + 112, 88, 79, 81, 84, 80, 71, 64, 67, 7, 69, + 69, 69, 68, 66, 76, 0, 2, 66, 0, 6, 64, 65, 1, + 12, 65, 70, 74, 69, 71, 3, 25, 16, 27, 26, 22, + 22, 15, 22, 22, 4, 13, 22, 66, 12, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, + 52, 62, 62, 62, 62, 62, 62, 62, 61, 62, 48, + 36, 25, 8, 65, 62, 62, 62, 62, 62, 62, 62, 62, + 62, 62, 62, 62, 42, 32, 18, 15, 14, 17, 40, + 40, 41, 41, 34, 38, 28, 20, 23, 12, 70, 8, 71, + 83, 30, 13, 8, 84, 80, 76, 80, 78, 71, 73, 82, + 70, 66, 3, 65, 65, 4, 4, 62, 62, 62, 62, 60, + 56, 49, 32, 4, 70, 39, 28, 22, 12, 7, 1, 64, + 66, 81, 80, 75, 71, 77, 71, 67, 69, 75, 65, 2, + 6, 3, 4, 9, 5, 62, 62, 62, 62, 60, 56, 49, 32, + 4 }, + + { + + 62, + 9, 74, 62, 9, 74, 123, 101, 11, 10, 12, 36, + 59, 61, 55, 22, 114, 65, 70, 77, 11, 12, 69, + 8, 26, 39, 58, 54, 22, 72, 121, 99, 75, 70, + 77, 11, 69, 88, 11, 19, 68, 81, 94, 4, 75, 86, + 99, 7, 73, 84, 4, 80, 85, 94, 65, 76, 70, 85, + 71, 2, 22, 0, 0, 0, 81, 86, 97, 71, 21, 1, 47, + 12, 80, 122, 88, 93, 77, 93, 99, 94, 115, 96, + 107, 99, 122, 80, 88, 93, 91, 24, 65, 82, 122, + 72, 102, 89, 123, 8, 7, 8, 1, 11, 68, 73, 86, + 101, 89, 87, 90, 75, 85, 107, 102, 76, 109, + 96, 117, 118, 120, 121, 65, 77, 70, 71, 4, 9, + 69, 74, 88, 114, 74, 39, 38, 121, 9, 3, 5, 4, + 3, 66, 80, 72, 64, 11, 6, 2, 67, 68, 6, 70, 5, + 2, 21, 36, 30, 20, 15, 19, 12, 17, 23, 65, 24, + 20, 28, 89, 18, 34, 31, 62, 62, 46, 60, 62, + 62, 59, 62, 62, 62, 62, 62, 62, 50, 62, 62, + 62, 62, 62, 62, 62, 52, 62, 58, 62, 24, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 62, 59, 50, + 42, 35, 19, 75, 72, 78, 27, 30, 26, 31, 16, + 19, 16, 34, 7, 66, 5, 74, 68, 114, 109, 69, 3, + 10, 65, 75, 78, 82, 85, 122, 98, 25, 11, 3, + 73, 75, 88, 103, 107, 118, 74, 21, 13, 8, 7, + 70, 68, 68, 73, 84, 66, 31, 31, 31, 11, 9, 66, + 73, 88, 11, 59, 54, 53, 42, 26, 3, 72, 69, 77, + 62, 123, 121, 107, 122, 120, 102, 117, 113, + 99, 113, 112, 117, 93, 98, 94, 108, 110, 88, + 79, 81, 83, 80, 71, 64, 67, 6, 69, 69, 69, 68, + 66, 75, 0, 2, 66, 0, 6, 64, 65, 1, 11, 65, 70, + 74, 69, 70, 2, 24, 16, 26, 25, 21, 21, 15, 21, + 21, 4, 13, 21, 66, 11, 62, 62, 62, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 50, 62, 62, + 62, 62, 62, 62, 62, 59, 59, 46, 34, 24, 7, 66, + 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, + 62, 40, 30, 16, 14, 13, 15, 39, 39, 39, 39, + 32, 36, 26, 19, 21, 11, 71, 7, 72, 84, 28, 12, + 7, 84, 80, 75, 80, 77, 70, 73, 81, 69, 65, 3, + 65, 64, 4, 4, 62, 62, 62, 62, 57, 52, 45, 28, + 1, 70, 39, 28, 22, 12, 8, 1, 64, 66, 81, 80, + 75, 71, 77, 70, 66, 69, 75, 65, 2, 6, 3, 5, 9, + 5, 62, 62, 62, 62, 57, 52, 45, 28, 1 }, + + { + + 62, + 9, 74, 62, 9, 74, 121, 99, 12, 10, 11, 34, 57, + 60, 55, 22, 112, 65, 69, 76, 11, 12, 69, 8, + 25, 38, 56, 51, 20, 73, 118, 98, 75, 69, 76, + 11, 70, 87, 11, 19, 68, 81, 94, 4, 75, 86, 99, + 7, 73, 83, 4, 80, 84, 94, 65, 76, 70, 85, 71, + 2, 22, 0, 0, 0, 81, 86, 97, 70, 20, 1, 46, 11, + 80, 119, 87, 92, 76, 91, 97, 92, 113, 94, 106, + 98, 120, 80, 88, 92, 91, 24, 65, 81, 120, 72, + 101, 89, 121, 8, 6, 7, 1, 11, 68, 72, 86, 100, + 88, 87, 89, 74, 84, 105, 100, 76, 108, 95, + 112, 113, 117, 118, 65, 77, 70, 70, 4, 9, 68, + 73, 87, 112, 74, 37, 36, 118, 9, 3, 5, 4, 3, + 65, 79, 71, 64, 11, 6, 2, 67, 67, 5, 70, 5, 1, + 21, 35, 30, 20, 15, 19, 12, 17, 22, 65, 23, + 19, 28, 89, 18, 34, 31, 62, 62, 45, 58, 62, + 62, 57, 62, 62, 62, 62, 62, 61, 48, 62, 62, + 62, 62, 62, 62, 60, 50, 62, 56, 62, 22, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 62, 57, 48, + 40, 34, 17, 75, 72, 78, 26, 29, 25, 30, 15, + 18, 15, 32, 6, 67, 4, 75, 68, 114, 107, 68, 4, + 10, 65, 74, 78, 82, 85, 120, 97, 25, 11, 4, + 72, 74, 87, 102, 106, 116, 73, 21, 13, 8, 7, + 69, 67, 68, 73, 84, 66, 31, 31, 30, 11, 9, 66, + 73, 87, 11, 58, 54, 52, 41, 26, 3, 72, 69, 77, + 62, 122, 119, 106, 121, 119, 101, 115, 111, + 98, 112, 110, 115, 93, 97, 93, 107, 108, 87, + 79, 81, 83, 79, 71, 64, 67, 6, 69, 69, 70, 67, + 65, 74, 0, 2, 65, 0, 6, 64, 65, 1, 11, 65, 70, + 74, 69, 70, 1, 23, 16, 25, 24, 20, 21, 15, 20, + 20, 4, 13, 20, 66, 10, 62, 62, 61, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 48, 62, 62, + 62, 62, 62, 62, 62, 57, 57, 44, 32, 22, 6, 67, + 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 59, + 60, 38, 28, 15, 13, 12, 14, 37, 37, 37, 37, + 31, 34, 24, 18, 20, 10, 72, 6, 73, 85, 27, 11, + 6, 84, 79, 75, 79, 76, 69, 73, 81, 69, 65, 3, + 64, 0, 4, 4, 62, 62, 62, 59, 54, 48, 41, 24, + 65, 70, 39, 28, 22, 12, 8, 2, 64, 66, 80, 80, + 75, 70, 76, 69, 65, 69, 74, 65, 2, 6, 3, 5, 9, + 5, 62, 62, 62, 59, 54, 48, 41, 24, 65 }, + + { + + 62, + 9, 74, 62, 9, 74, 120, 98, 12, 10, 11, 33, 55, + 59, 55, 21, 110, 65, 69, 75, 10, 11, 69, 7, + 23, 37, 53, 47, 17, 75, 115, 97, 75, 69, 75, + 10, 70, 86, 11, 18, 68, 80, 93, 4, 75, 86, 99, + 7, 73, 83, 4, 80, 84, 93, 65, 76, 70, 85, 70, + 2, 22, 0, 0, 0, 80, 87, 97, 70, 19, 1, 45, 11, + 80, 117, 86, 91, 74, 89, 96, 91, 112, 93, 104, + 97, 118, 80, 87, 92, 91, 24, 65, 80, 118, 72, + 101, 88, 119, 8, 6, 7, 0, 11, 68, 72, 85, 99, + 87, 86, 88, 74, 84, 104, 99, 75, 107, 94, 107, + 109, 114, 115, 65, 76, 70, 70, 4, 9, 68, 73, + 87, 110, 74, 35, 34, 116, 9, 4, 5, 4, 3, 65, + 77, 70, 0, 10, 6, 2, 66, 67, 4, 70, 5, 1, 20, + 34, 29, 19, 14, 19, 12, 17, 21, 65, 22, 18, + 27, 89, 17, 33, 30, 62, 62, 44, 56, 62, 62, + 55, 62, 62, 62, 62, 62, 59, 46, 59, 62, 62, + 62, 62, 62, 57, 48, 62, 54, 62, 21, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 60, 55, 46, 38, + 32, 15, 75, 72, 79, 25, 28, 24, 28, 14, 16, + 14, 31, 5, 67, 3, 75, 69, 113, 106, 67, 4, 10, + 64, 74, 77, 81, 84, 118, 95, 25, 12, 4, 72, + 73, 86, 100, 104, 115, 73, 22, 14, 9, 8, 68, + 67, 68, 72, 83, 66, 32, 31, 30, 10, 9, 66, 73, + 87, 11, 58, 53, 51, 40, 26, 3, 71, 69, 77, 62, + 120, 118, 105, 119, 117, 100, 114, 110, 97, + 110, 109, 113, 92, 96, 93, 106, 107, 87, 79, + 81, 82, 79, 71, 65, 67, 5, 69, 69, 70, 67, 65, + 73, 0, 2, 65, 0, 6, 64, 65, 1, 10, 65, 70, 74, + 69, 69, 0, 22, 16, 24, 24, 19, 20, 15, 19, 19, + 4, 13, 19, 66, 9, 62, 62, 60, 62, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 46, 62, 62, 62, + 62, 62, 62, 62, 54, 54, 42, 30, 21, 5, 67, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 62, 57, 57, + 36, 26, 13, 12, 12, 12, 36, 36, 36, 35, 29, + 32, 23, 17, 18, 9, 73, 4, 74, 85, 25, 9, 4, + 83, 79, 74, 79, 75, 68, 73, 80, 68, 64, 3, 64, + 1, 4, 4, 62, 62, 62, 56, 50, 44, 36, 20, 68, + 69, 39, 28, 22, 12, 9, 2, 64, 66, 80, 80, 75, + 70, 76, 69, 64, 69, 74, 64, 3, 6, 3, 6, 9, 5, + 62, 62, 62, 56, 50, 44, 36, 20, 68 }, + + { + + 62, + 9, 74, 62, 9, 74, 118, 96, 12, 10, 10, 32, 53, + 58, 55, 21, 108, 65, 69, 74, 10, 11, 69, 6, + 21, 36, 51, 44, 15, 77, 112, 96, 74, 69, 74, + 10, 70, 85, 11, 18, 68, 80, 92, 4, 75, 86, 99, + 7, 73, 83, 4, 80, 83, 93, 65, 76, 70, 85, 70, + 2, 22, 0, 0, 0, 80, 87, 97, 69, 18, 1, 44, 10, + 80, 114, 85, 90, 72, 87, 94, 89, 110, 91, 103, + 96, 115, 80, 87, 91, 90, 24, 65, 79, 116, 72, + 100, 88, 117, 8, 5, 6, 0, 11, 68, 71, 85, 98, + 86, 86, 87, 73, 83, 102, 97, 74, 105, 93, 102, + 105, 111, 112, 64, 76, 69, 69, 4, 9, 67, 73, + 86, 108, 74, 33, 32, 113, 9, 4, 5, 4, 3, 64, + 76, 69, 0, 10, 6, 2, 66, 66, 3, 69, 5, 0, 20, + 33, 29, 19, 14, 19, 12, 17, 20, 64, 21, 18, + 27, 89, 17, 32, 29, 62, 62, 43, 55, 62, 62, + 53, 62, 62, 62, 62, 61, 57, 44, 57, 62, 60, + 62, 62, 62, 55, 46, 62, 52, 62, 19, 62, 62, + 62, 62, 62, 62, 62, 62, 61, 58, 53, 44, 37, + 30, 13, 75, 72, 79, 24, 27, 23, 27, 13, 15, + 13, 29, 4, 68, 2, 76, 70, 112, 104, 66, 5, 10, + 64, 73, 77, 81, 83, 116, 94, 25, 12, 5, 71, + 72, 85, 99, 103, 113, 72, 23, 15, 10, 8, 67, + 66, 67, 72, 83, 66, 32, 31, 29, 10, 9, 66, 73, + 86, 11, 57, 52, 50, 39, 26, 3, 71, 69, 76, 62, + 119, 116, 103, 117, 116, 99, 112, 108, 96, + 108, 107, 111, 91, 95, 92, 105, 105, 87, 79, + 80, 82, 78, 71, 65, 67, 5, 69, 69, 71, 66, 65, + 72, 0, 2, 65, 0, 6, 64, 65, 1, 10, 65, 70, 74, + 69, 69, 64, 21, 16, 23, 23, 19, 19, 15, 19, + 18, 4, 13, 18, 66, 8, 62, 62, 59, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 44, 62, 62, + 62, 62, 62, 62, 61, 52, 52, 40, 29, 19, 5, 68, + 62, 62, 62, 62, 62, 62, 62, 62, 62, 61, 55, + 54, 34, 24, 12, 12, 11, 10, 35, 34, 34, 33, + 27, 30, 21, 16, 17, 8, 73, 3, 75, 86, 24, 8, + 3, 83, 79, 73, 78, 74, 67, 72, 79, 68, 64, 3, + 0, 2, 4, 4, 62, 62, 59, 53, 47, 40, 32, 16, + 71, 69, 39, 28, 22, 12, 9, 2, 0, 65, 79, 80, + 75, 69, 76, 68, 0, 69, 74, 64, 3, 6, 4, 6, 9, + 5, 62, 62, 59, 53, 47, 40, 32, 16, 71 }, + + { + + 62, + 9, 75, 62, 9, 75, 116, 95, 13, 10, 10, 30, 51, + 57, 55, 21, 107, 65, 68, 74, 10, 10, 68, 6, + 20, 34, 48, 40, 12, 78, 110, 95, 74, 68, 74, + 10, 71, 85, 11, 17, 68, 80, 92, 4, 75, 85, 98, + 7, 72, 82, 4, 79, 83, 93, 65, 76, 70, 85, 70, + 2, 22, 0, 0, 0, 79, 87, 97, 69, 18, 0, 44, 10, + 80, 112, 84, 89, 71, 84, 93, 88, 108, 90, 102, + 95, 113, 80, 87, 91, 90, 24, 65, 78, 113, 72, + 99, 87, 115, 7, 5, 6, 64, 12, 68, 71, 84, 98, + 86, 85, 86, 73, 82, 101, 96, 74, 104, 92, 97, + 100, 108, 109, 64, 76, 69, 69, 4, 9, 67, 72, + 86, 106, 73, 31, 30, 110, 9, 4, 5, 4, 4, 64, + 74, 68, 0, 10, 6, 2, 65, 65, 2, 69, 5, 0, 19, + 32, 28, 19, 13, 19, 12, 17, 18, 64, 20, 17, + 26, 89, 17, 32, 29, 62, 62, 42, 53, 62, 62, + 51, 62, 62, 62, 62, 57, 55, 43, 55, 62, 58, + 62, 62, 62, 52, 44, 62, 50, 62, 17, 62, 62, + 62, 62, 62, 62, 62, 62, 59, 56, 50, 42, 35, + 29, 12, 75, 72, 80, 23, 26, 22, 26, 12, 14, + 12, 27, 3, 68, 1, 77, 70, 112, 103, 65, 5, 10, + 64, 72, 76, 80, 83, 114, 93, 26, 12, 5, 70, + 71, 84, 97, 101, 111, 71, 23, 15, 10, 9, 66, + 66, 67, 72, 82, 66, 33, 31, 28, 10, 9, 66, 73, + 86, 10, 57, 52, 49, 38, 25, 3, 71, 69, 76, 62, + 118, 115, 102, 116, 114, 98, 110, 106, 95, + 107, 105, 109, 91, 94, 92, 104, 103, 86, 79, + 80, 81, 78, 71, 65, 67, 4, 69, 69, 71, 66, 64, + 71, 0, 2, 64, 1, 6, 0, 64, 1, 9, 65, 70, 74, + 69, 68, 65, 20, 16, 22, 22, 18, 19, 15, 18, + 18, 4, 12, 16, 67, 7, 62, 62, 58, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 42, 62, 62, + 62, 62, 62, 62, 58, 50, 49, 38, 27, 18, 4, 69, + 62, 62, 62, 62, 62, 62, 62, 62, 61, 58, 52, + 51, 32, 23, 10, 11, 10, 9, 33, 33, 32, 31, 26, + 28, 19, 15, 15, 7, 74, 2, 76, 87, 22, 7, 2, + 83, 78, 73, 78, 73, 66, 72, 79, 67, 0, 3, 0, + 3, 4, 4, 62, 62, 57, 50, 44, 36, 28, 12, 74, + 69, 39, 28, 22, 12, 10, 3, 0, 65, 79, 79, 74, + 69, 75, 67, 1, 68, 73, 64, 3, 6, 4, 7, 9, 5, + 62, 62, 57, 50, 44, 36, 28, 12, 74 }, + + { + + 62, + 9, 75, 62, 9, 75, 114, 93, 13, 10, 9, 29, 49, + 56, 55, 21, 105, 65, 68, 73, 9, 10, 68, 5, 18, + 33, 46, 37, 10, 80, 107, 94, 74, 68, 73, 9, + 71, 84, 11, 17, 68, 79, 91, 4, 75, 85, 98, 7, + 72, 82, 4, 79, 82, 92, 65, 76, 70, 85, 69, 2, + 22, 0, 0, 0, 79, 87, 97, 68, 17, 0, 43, 9, 80, + 109, 83, 88, 69, 82, 91, 86, 107, 88, 100, 94, + 111, 80, 86, 90, 90, 24, 65, 77, 111, 72, 98, + 87, 113, 7, 4, 5, 64, 12, 68, 70, 84, 97, 85, + 85, 85, 72, 81, 99, 94, 73, 103, 91, 92, 96, + 105, 106, 64, 75, 69, 68, 4, 9, 66, 72, 85, + 104, 73, 29, 28, 107, 9, 5, 5, 4, 4, 0, 73, + 67, 1, 9, 6, 2, 65, 65, 1, 69, 5, 64, 19, 31, + 28, 18, 13, 19, 12, 17, 17, 64, 19, 16, 26, + 89, 17, 31, 28, 60, 62, 41, 51, 62, 62, 49, + 62, 61, 62, 62, 54, 53, 41, 52, 62, 55, 62, + 62, 62, 49, 42, 62, 48, 62, 16, 62, 62, 62, + 62, 62, 62, 62, 62, 57, 53, 48, 40, 33, 27, + 10, 75, 72, 80, 22, 25, 21, 24, 11, 13, 11, + 26, 2, 69, 0, 77, 71, 111, 101, 64, 6, 10, 0, + 72, 76, 80, 82, 112, 91, 26, 13, 6, 70, 70, + 83, 96, 100, 109, 71, 24, 16, 11, 9, 65, 65, + 67, 71, 82, 66, 33, 31, 28, 9, 9, 66, 73, 85, + 10, 56, 51, 48, 37, 25, 3, 70, 69, 76, 62, + 116, 113, 101, 114, 113, 97, 109, 105, 94, + 105, 104, 107, 90, 93, 91, 103, 101, 86, 79, + 80, 81, 77, 71, 66, 67, 4, 69, 69, 72, 65, 64, + 70, 0, 2, 64, 1, 6, 0, 64, 1, 9, 65, 70, 74, + 69, 68, 66, 19, 16, 21, 22, 17, 18, 15, 17, + 17, 4, 12, 15, 67, 6, 61, 62, 57, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 40, 62, 62, + 62, 62, 62, 62, 56, 48, 47, 36, 25, 16, 3, 69, + 62, 62, 62, 62, 62, 62, 62, 62, 59, 56, 50, + 48, 30, 21, 9, 10, 10, 7, 32, 31, 31, 29, 24, + 26, 18, 14, 14, 6, 75, 0, 77, 87, 21, 5, 0, + 82, 78, 72, 77, 72, 65, 72, 78, 67, 0, 3, 1, + 4, 4, 4, 62, 62, 54, 47, 40, 32, 24, 8, 77, + 68, 39, 28, 22, 12, 10, 3, 0, 65, 78, 79, 74, + 68, 75, 66, 2, 68, 73, 0, 4, 6, 4, 7, 9, 5, + 62, 62, 54, 47, 40, 32, 24, 8, 77 }, + + { + + 62, + 8, 75, 62, 8, 75, 113, 92, 13, 10, 9, 27, 46, + 55, 55, 20, 103, 66, 68, 72, 9, 9, 68, 4, 16, + 32, 43, 33, 7, 82, 104, 93, 74, 68, 72, 9, 72, + 83, 11, 16, 68, 79, 91, 3, 76, 85, 98, 7, 72, + 82, 4, 79, 82, 92, 65, 76, 70, 85, 69, 2, 22, + 0, 0, 0, 78, 88, 97, 68, 16, 0, 42, 9, 81, + 107, 82, 87, 68, 80, 90, 85, 105, 87, 99, 93, + 109, 80, 86, 90, 90, 24, 65, 76, 109, 72, 98, + 86, 111, 7, 4, 5, 65, 12, 68, 70, 83, 96, 84, + 84, 85, 72, 81, 98, 93, 73, 102, 90, 88, 92, + 102, 104, 64, 75, 69, 68, 3, 9, 66, 72, 85, + 102, 73, 27, 26, 105, 9, 5, 5, 4, 4, 0, 71, + 67, 1, 9, 5, 2, 64, 64, 64, 69, 5, 64, 18, 29, + 27, 18, 12, 19, 12, 16, 16, 64, 18, 15, 25, + 89, 16, 30, 27, 58, 62, 39, 49, 62, 62, 46, + 62, 59, 62, 62, 50, 51, 39, 50, 62, 53, 62, + 62, 62, 46, 40, 62, 46, 62, 14, 62, 62, 62, + 62, 62, 62, 62, 60, 55, 51, 46, 38, 31, 25, 8, + 75, 73, 81, 21, 23, 20, 23, 10, 11, 9, 24, 1, + 69, 64, 78, 72, 111, 100, 0, 6, 10, 0, 71, 75, + 79, 82, 110, 90, 26, 13, 6, 69, 69, 82, 94, + 98, 108, 70, 24, 16, 11, 10, 64, 65, 67, 71, + 81, 67, 34, 31, 27, 9, 9, 66, 73, 85, 10, 56, + 50, 47, 36, 25, 3, 70, 69, 76, 62, 115, 112, + 100, 113, 111, 96, 107, 103, 93, 104, 102, + 105, 90, 93, 91, 102, 100, 86, 79, 80, 80, 77, + 71, 66, 67, 3, 69, 69, 72, 65, 64, 69, 0, 1, + 64, 1, 5, 0, 64, 1, 8, 65, 70, 74, 69, 67, 67, + 18, 16, 19, 21, 16, 17, 14, 16, 16, 4, 12, 14, + 67, 4, 60, 60, 56, 62, 62, 62, 62, 62, 62, 62, + 62, 62, 62, 60, 38, 62, 62, 62, 62, 62, 62, + 53, 45, 44, 34, 23, 15, 2, 70, 62, 62, 62, 62, + 62, 62, 62, 62, 56, 53, 47, 45, 28, 19, 7, 9, + 9, 5, 30, 30, 29, 27, 22, 24, 16, 12, 12, 4, + 76, 64, 78, 88, 19, 4, 64, 82, 78, 72, 77, 71, + 64, 72, 78, 66, 1, 3, 1, 4, 4, 3, 62, 60, 51, + 44, 37, 28, 19, 3, 80, 68, 39, 28, 22, 12, 11, + 3, 0, 65, 78, 79, 74, 68, 75, 66, 2, 68, 73, + 0, 4, 6, 4, 8, 9, 4, 62, 60, 51, 44, 37, 28, + 19, 3, 80 }, + + { + + 62, + 8, 75, 62, 8, 75, 111, 91, 14, 10, 9, 26, 44, + 54, 56, 20, 101, 66, 67, 71, 9, 8, 68, 4, 15, + 31, 41, 29, 4, 83, 101, 92, 73, 67, 71, 9, 72, + 82, 11, 16, 67, 79, 90, 3, 76, 85, 98, 7, 72, + 81, 4, 79, 82, 92, 65, 76, 70, 84, 69, 2, 22, + 0, 0, 0, 77, 88, 97, 68, 15, 0, 41, 9, 81, + 105, 80, 86, 66, 78, 88, 84, 103, 85, 98, 91, + 106, 80, 86, 90, 89, 24, 65, 75, 107, 71, 97, + 85, 109, 7, 4, 5, 65, 12, 68, 70, 82, 95, 83, + 83, 84, 71, 80, 97, 91, 72, 100, 89, 83, 87, + 98, 101, 0, 75, 68, 67, 3, 9, 66, 71, 84, 99, + 73, 25, 25, 102, 9, 5, 5, 4, 4, 1, 69, 66, 1, + 9, 5, 2, 0, 0, 65, 68, 5, 64, 17, 28, 26, 18, + 11, 19, 12, 16, 15, 0, 17, 15, 24, 89, 16, 30, + 27, 56, 62, 38, 48, 62, 62, 44, 60, 57, 62, + 62, 47, 49, 37, 48, 62, 51, 62, 62, 62, 44, + 38, 62, 44, 62, 12, 62, 62, 62, 62, 62, 62, + 60, 58, 53, 49, 44, 37, 30, 24, 6, 75, 73, 81, + 21, 22, 19, 22, 9, 10, 8, 22, 0, 69, 65, 79, + 72, 110, 99, 1, 6, 10, 0, 70, 74, 78, 81, 107, + 89, 26, 13, 6, 68, 68, 81, 92, 96, 106, 69, + 25, 17, 12, 11, 0, 65, 66, 71, 80, 67, 35, 31, + 26, 9, 10, 65, 73, 84, 10, 56, 50, 46, 35, 25, + 3, 70, 69, 75, 62, 114, 111, 98, 111, 109, 95, + 105, 101, 92, 102, 100, 103, 89, 92, 90, 101, + 98, 85, 78, 79, 79, 76, 71, 66, 67, 2, 69, 69, + 72, 65, 0, 68, 1, 1, 0, 1, 5, 0, 64, 1, 7, 65, + 69, 73, 69, 66, 67, 17, 16, 18, 20, 16, 17, + 14, 16, 15, 4, 12, 13, 67, 3, 59, 59, 56, 61, + 62, 62, 62, 62, 62, 62, 62, 62, 62, 57, 36, + 62, 62, 62, 62, 62, 62, 50, 43, 42, 33, 22, + 14, 2, 71, 62, 62, 62, 62, 62, 62, 62, 62, 54, + 51, 45, 43, 26, 17, 5, 9, 8, 4, 29, 29, 27, + 25, 21, 23, 14, 11, 10, 3, 76, 65, 78, 89, 17, + 3, 65, 82, 77, 71, 77, 70, 1, 71, 77, 65, 2, + 3, 2, 5, 4, 3, 62, 58, 49, 41, 34, 24, 15, 64, + 83, 68, 39, 28, 23, 13, 12, 4, 1, 64, 78, 79, + 74, 68, 74, 65, 3, 68, 72, 0, 4, 6, 5, 9, 9, + 4, 62, 58, 49, 41, 34, 24, 15, 64, 83 }, + + { + + 62, + 8, 75, 62, 8, 75, 109, 89, 14, 10, 8, 25, 42, + 53, 56, 20, 99, 66, 67, 70, 8, 8, 68, 3, 13, + 30, 38, 26, 2, 85, 98, 91, 73, 67, 70, 8, 72, + 81, 11, 15, 67, 78, 89, 3, 76, 85, 98, 7, 72, + 81, 4, 79, 81, 91, 65, 76, 70, 84, 68, 2, 22, + 0, 0, 0, 77, 88, 97, 67, 14, 0, 40, 8, 81, + 102, 79, 85, 64, 76, 87, 82, 102, 84, 96, 90, + 104, 80, 85, 89, 89, 24, 65, 74, 105, 71, 96, + 85, 107, 7, 3, 4, 66, 12, 68, 69, 82, 94, 82, + 83, 83, 71, 79, 95, 90, 71, 99, 88, 78, 83, + 95, 98, 0, 74, 68, 67, 3, 9, 65, 71, 84, 97, + 73, 23, 23, 99, 9, 6, 5, 4, 4, 1, 68, 65, 2, + 8, 5, 2, 0, 0, 66, 68, 5, 65, 17, 27, 26, 17, + 11, 19, 12, 16, 14, 0, 16, 14, 24, 89, 16, 29, + 26, 54, 62, 37, 46, 62, 62, 42, 57, 55, 62, + 62, 43, 47, 35, 45, 61, 48, 62, 62, 62, 41, + 36, 58, 42, 62, 11, 62, 62, 62, 62, 62, 60, + 58, 56, 51, 46, 42, 35, 28, 22, 4, 75, 73, 82, + 20, 21, 18, 20, 8, 9, 7, 21, 64, 70, 66, 79, + 73, 109, 97, 2, 7, 10, 1, 70, 74, 78, 80, 105, + 87, 26, 14, 7, 68, 67, 80, 91, 95, 104, 69, + 26, 18, 13, 11, 1, 64, 66, 70, 80, 67, 35, 31, + 26, 8, 10, 65, 73, 84, 10, 55, 49, 45, 34, 25, + 3, 69, 69, 75, 62, 112, 109, 97, 109, 108, 94, + 104, 100, 91, 100, 99, 101, 88, 91, 90, 100, + 96, 85, 78, 79, 79, 76, 71, 67, 67, 2, 69, 69, + 73, 64, 0, 67, 1, 1, 0, 1, 5, 0, 64, 1, 7, 65, + 69, 73, 69, 66, 68, 16, 16, 17, 20, 15, 16, + 14, 15, 14, 4, 12, 12, 67, 2, 58, 58, 55, 59, + 60, 62, 62, 62, 62, 62, 62, 62, 62, 55, 34, + 62, 62, 62, 62, 62, 62, 48, 41, 39, 31, 20, + 12, 1, 71, 62, 62, 62, 62, 62, 62, 62, 62, 52, + 48, 43, 40, 24, 15, 4, 8, 8, 2, 28, 27, 26, + 23, 19, 21, 13, 10, 9, 2, 77, 67, 79, 89, 16, + 1, 67, 81, 77, 70, 76, 69, 2, 71, 76, 65, 2, + 3, 2, 6, 4, 3, 62, 56, 46, 38, 30, 20, 11, 68, + 86, 67, 39, 28, 23, 13, 12, 4, 1, 64, 77, 79, + 74, 67, 74, 64, 4, 68, 72, 1, 5, 6, 5, 9, 9, + 4, 62, 56, 46, 38, 30, 20, 11, 68, 86 }, + + { + + 62, + 8, 76, 62, 8, 76, 107, 88, 15, 10, 8, 23, 40, + 52, 56, 20, 98, 66, 66, 70, 8, 7, 67, 3, 12, + 28, 36, 22, 64, 86, 96, 90, 73, 66, 70, 8, 73, + 81, 11, 15, 67, 78, 89, 3, 76, 84, 97, 7, 71, + 80, 4, 78, 81, 91, 65, 76, 70, 84, 68, 2, 22, + 0, 0, 0, 76, 88, 97, 67, 14, 64, 40, 8, 81, + 100, 78, 84, 0, 73, 85, 81, 100, 82, 95, 89, + 102, 80, 85, 89, 89, 24, 65, 73, 102, 71, 95, + 84, 105, 6, 3, 4, 66, 13, 68, 69, 81, 94, 82, + 82, 82, 70, 78, 94, 88, 71, 98, 87, 73, 78, + 92, 95, 0, 74, 68, 66, 3, 9, 65, 70, 83, 95, + 72, 21, 21, 96, 9, 6, 5, 4, 5, 2, 66, 64, 2, + 8, 5, 2, 1, 1, 67, 68, 5, 65, 16, 26, 25, 17, + 10, 19, 12, 16, 12, 0, 15, 13, 23, 89, 16, 29, + 26, 52, 62, 36, 44, 61, 62, 40, 55, 53, 62, + 62, 40, 45, 34, 43, 57, 46, 62, 62, 62, 38, + 34, 55, 40, 62, 9, 62, 62, 62, 62, 62, 58, 55, + 54, 49, 44, 39, 33, 26, 21, 3, 75, 73, 82, 19, + 20, 17, 19, 7, 8, 6, 19, 65, 70, 67, 80, 73, + 109, 96, 3, 7, 10, 1, 69, 73, 77, 80, 103, 86, + 27, 14, 7, 67, 66, 79, 89, 93, 102, 68, 26, + 18, 13, 12, 2, 64, 66, 70, 79, 67, 36, 31, 25, + 8, 10, 65, 73, 83, 9, 55, 49, 44, 33, 24, 3, + 69, 69, 75, 62, 111, 108, 96, 108, 106, 93, + 102, 98, 90, 99, 97, 99, 88, 90, 89, 99, 94, + 84, 78, 79, 78, 75, 71, 67, 67, 1, 69, 69, 73, + 64, 1, 66, 1, 1, 1, 2, 5, 1, 0, 1, 6, 65, 69, + 73, 69, 65, 69, 15, 16, 16, 19, 14, 16, 14, + 14, 14, 4, 11, 10, 68, 1, 56, 57, 54, 58, 58, + 62, 62, 62, 62, 62, 62, 62, 62, 52, 32, 62, + 62, 62, 62, 62, 62, 45, 39, 37, 29, 18, 11, 0, + 72, 62, 62, 62, 62, 62, 62, 60, 59, 49, 46, + 40, 37, 22, 14, 2, 7, 7, 1, 26, 26, 24, 21, + 18, 19, 11, 9, 7, 1, 78, 68, 80, 90, 14, 0, + 68, 81, 76, 70, 76, 68, 3, 71, 76, 64, 3, 3, + 3, 7, 4, 3, 62, 54, 44, 35, 27, 16, 7, 72, 89, + 67, 39, 28, 23, 13, 13, 5, 1, 64, 77, 78, 73, + 67, 73, 0, 5, 67, 71, 1, 5, 6, 5, 10, 9, 4, + 62, 54, 44, 35, 27, 16, 7, 72, 89 }, + + { + + 62, + 8, 76, 62, 8, 76, 106, 86, 15, 10, 7, 22, 38, + 51, 56, 19, 96, 66, 66, 69, 8, 7, 67, 2, 10, + 27, 33, 19, 66, 88, 93, 89, 73, 66, 69, 8, 73, + 80, 11, 14, 67, 78, 88, 3, 76, 84, 97, 7, 71, + 80, 4, 78, 80, 91, 65, 76, 70, 84, 68, 2, 22, + 0, 0, 0, 76, 89, 97, 66, 13, 64, 39, 7, 81, + 97, 77, 83, 2, 71, 84, 79, 98, 81, 94, 88, + 100, 80, 85, 88, 89, 24, 65, 72, 100, 71, 95, + 84, 103, 6, 2, 3, 67, 13, 68, 68, 81, 93, 81, + 82, 81, 70, 78, 92, 87, 70, 97, 86, 68, 74, + 89, 92, 0, 74, 68, 66, 3, 9, 64, 70, 83, 93, + 72, 19, 19, 94, 9, 6, 5, 4, 5, 2, 65, 0, 2, 8, + 5, 2, 1, 2, 68, 68, 5, 66, 16, 25, 25, 17, 10, + 19, 12, 16, 11, 0, 14, 12, 23, 89, 15, 28, 25, + 50, 62, 35, 42, 59, 60, 38, 52, 51, 62, 62, + 36, 43, 32, 41, 54, 43, 58, 62, 62, 35, 32, + 51, 38, 62, 7, 62, 62, 62, 62, 62, 56, 53, 52, + 47, 42, 37, 31, 24, 19, 1, 75, 73, 83, 18, 19, + 16, 18, 6, 6, 5, 17, 66, 71, 68, 81, 74, 108, + 94, 4, 8, 10, 1, 68, 73, 77, 79, 101, 85, 27, + 14, 8, 66, 65, 78, 88, 92, 101, 67, 27, 19, + 14, 12, 3, 0, 66, 70, 79, 67, 36, 31, 24, 8, + 10, 65, 73, 83, 9, 54, 48, 43, 32, 24, 3, 69, + 69, 75, 62, 110, 106, 95, 106, 105, 92, 100, + 96, 89, 97, 95, 97, 87, 89, 89, 98, 93, 84, + 78, 79, 78, 75, 71, 67, 67, 1, 69, 69, 74, 0, + 1, 65, 1, 1, 1, 2, 5, 1, 0, 1, 6, 65, 69, 73, + 69, 65, 70, 14, 16, 15, 18, 13, 15, 14, 13, + 13, 4, 11, 9, 68, 0, 55, 56, 53, 56, 56, 62, + 61, 62, 62, 62, 62, 62, 61, 50, 30, 62, 62, + 62, 62, 62, 59, 43, 36, 34, 27, 16, 9, 64, 73, + 62, 62, 62, 62, 62, 62, 57, 56, 47, 43, 38, + 34, 20, 12, 1, 6, 6, 64, 25, 24, 22, 19, 16, + 17, 9, 8, 6, 0, 79, 69, 81, 91, 13, 64, 69, + 81, 76, 69, 75, 67, 4, 71, 75, 64, 3, 3, 3, 8, + 4, 3, 61, 52, 41, 32, 24, 12, 2, 76, 92, 67, + 39, 28, 23, 13, 13, 5, 1, 64, 76, 78, 73, 66, + 73, 0, 6, 67, 71, 1, 5, 6, 5, 10, 9, 4, 61, + 52, 41, 32, 24, 12, 2, 76, 92 }, + + { + + 62, + 8, 76, 62, 8, 76, 104, 85, 15, 10, 7, 21, 36, + 50, 56, 19, 94, 66, 66, 68, 7, 6, 67, 1, 8, + 26, 31, 15, 69, 90, 90, 88, 72, 66, 68, 7, 73, + 79, 11, 14, 67, 77, 87, 3, 76, 84, 97, 7, 71, + 80, 4, 78, 80, 90, 65, 76, 70, 84, 67, 2, 22, + 0, 0, 0, 75, 89, 97, 66, 12, 64, 38, 7, 81, + 95, 76, 82, 4, 69, 82, 78, 97, 79, 92, 87, 97, + 80, 84, 88, 88, 24, 65, 71, 98, 71, 94, 83, + 101, 6, 2, 3, 67, 13, 68, 68, 80, 92, 80, 81, + 80, 69, 77, 91, 85, 69, 95, 85, 0, 70, 86, 89, + 1, 73, 67, 65, 3, 9, 64, 70, 82, 91, 72, 17, + 17, 91, 9, 7, 5, 4, 5, 3, 0, 1, 3, 7, 5, 2, 2, + 2, 69, 67, 5, 66, 15, 24, 24, 16, 9, 19, 12, + 16, 10, 1, 13, 12, 22, 89, 15, 27, 24, 48, 62, + 34, 41, 57, 58, 36, 50, 49, 62, 62, 33, 41, + 30, 38, 51, 41, 55, 62, 62, 33, 30, 48, 36, + 62, 6, 62, 62, 62, 61, 60, 54, 51, 50, 45, 39, + 35, 29, 23, 17, 64, 75, 73, 83, 17, 18, 15, + 16, 5, 5, 4, 16, 67, 71, 69, 81, 75, 107, 93, + 5, 8, 10, 2, 68, 72, 76, 78, 99, 83, 27, 15, + 8, 66, 64, 77, 86, 90, 99, 67, 28, 20, 15, 13, + 4, 0, 65, 69, 78, 67, 37, 31, 24, 7, 10, 65, + 73, 82, 9, 54, 47, 42, 31, 24, 3, 68, 69, 74, + 62, 108, 105, 93, 104, 103, 91, 99, 95, 88, + 95, 94, 95, 86, 88, 88, 97, 91, 84, 78, 78, + 77, 74, 71, 68, 67, 0, 69, 69, 74, 0, 1, 64, + 1, 1, 1, 2, 5, 1, 0, 1, 5, 65, 69, 73, 69, 64, + 71, 13, 16, 14, 18, 13, 14, 14, 13, 12, 4, 11, + 8, 68, 64, 54, 55, 52, 54, 54, 62, 59, 61, 62, + 59, 62, 62, 58, 47, 28, 62, 62, 62, 62, 59, + 56, 40, 34, 32, 25, 15, 8, 64, 73, 62, 62, 62, + 62, 59, 59, 55, 53, 45, 41, 36, 31, 18, 10, + 64, 6, 6, 66, 24, 23, 21, 17, 14, 15, 8, 7, 4, + 64, 79, 71, 82, 91, 11, 66, 71, 80, 76, 68, + 75, 66, 5, 70, 74, 0, 4, 3, 4, 9, 4, 3, 60, + 50, 38, 29, 20, 8, 65, 80, 95, 66, 39, 28, 23, + 13, 14, 5, 2, 0, 76, 78, 73, 66, 73, 1, 7, 67, + 71, 2, 6, 6, 6, 11, 9, 4, 60, 50, 38, 29, 20, + 8, 65, 80, 95 }, + + { + + 61, + 8, 76, 61, 8, 76, 102, 83, 16, 10, 6, 19, 34, + 49, 56, 19, 92, 66, 65, 67, 7, 6, 67, 1, 7, + 25, 28, 12, 71, 91, 87, 87, 72, 65, 67, 7, 74, + 78, 11, 13, 67, 77, 87, 3, 76, 84, 97, 7, 71, + 79, 4, 78, 79, 90, 65, 76, 70, 84, 67, 2, 22, + 0, 0, 0, 75, 89, 97, 65, 11, 64, 37, 6, 81, + 92, 75, 81, 5, 67, 81, 76, 95, 78, 91, 86, 95, + 80, 84, 87, 88, 24, 65, 70, 96, 71, 93, 83, + 99, 6, 1, 2, 68, 13, 68, 67, 80, 91, 79, 81, + 79, 69, 76, 89, 84, 69, 94, 84, 5, 65, 83, 86, + 1, 73, 67, 65, 3, 9, 0, 69, 82, 89, 72, 15, + 15, 88, 9, 7, 5, 4, 5, 3, 1, 2, 3, 7, 5, 2, 2, + 3, 70, 67, 5, 67, 15, 23, 24, 16, 9, 19, 12, + 16, 9, 1, 12, 11, 22, 89, 15, 27, 24, 46, 61, + 33, 39, 55, 55, 34, 47, 47, 62, 62, 29, 39, + 28, 36, 48, 38, 52, 61, 62, 30, 28, 44, 34, + 62, 4, 60, 62, 60, 58, 57, 52, 49, 48, 43, 37, + 33, 27, 21, 16, 66, 75, 73, 84, 16, 17, 14, + 15, 4, 4, 3, 14, 68, 72, 70, 82, 75, 107, 91, + 6, 9, 10, 2, 67, 72, 76, 78, 97, 82, 27, 15, + 9, 65, 0, 76, 85, 89, 97, 66, 28, 20, 15, 13, + 5, 1, 65, 69, 78, 67, 37, 31, 23, 7, 10, 65, + 73, 82, 9, 53, 47, 41, 30, 24, 3, 68, 69, 74, + 62, 107, 103, 92, 103, 102, 90, 97, 93, 87, + 94, 92, 93, 86, 87, 88, 96, 89, 83, 78, 78, + 77, 74, 71, 68, 67, 0, 69, 69, 75, 1, 2, 0, 1, + 1, 2, 2, 5, 1, 0, 1, 5, 65, 69, 73, 69, 64, + 72, 12, 16, 13, 17, 12, 14, 14, 12, 11, 4, 11, + 7, 68, 65, 53, 54, 51, 53, 52, 60, 57, 59, 59, + 57, 62, 60, 55, 45, 26, 62, 62, 62, 62, 55, + 53, 38, 32, 29, 23, 13, 6, 65, 74, 62, 62, 62, + 60, 56, 57, 52, 50, 42, 38, 33, 28, 16, 8, 65, + 5, 5, 67, 22, 21, 19, 15, 13, 13, 6, 6, 3, 65, + 80, 72, 83, 92, 10, 67, 72, 80, 75, 68, 74, + 65, 6, 70, 74, 0, 4, 3, 4, 10, 4, 3, 59, 48, + 36, 26, 17, 4, 69, 84, 98, 66, 39, 28, 23, 13, + 14, 6, 2, 0, 75, 78, 73, 65, 72, 2, 8, 67, 70, + 2, 6, 6, 6, 11, 9, 4, 59, 48, 36, 26, 17, 4, + 69, 84, 98 }, + + { + + 60, + 8, 76, 60, 8, 76, 100, 82, 16, 10, 6, 18, 32, + 48, 56, 19, 90, 66, 65, 66, 7, 5, 67, 0, 5, + 24, 26, 8, 74, 93, 84, 86, 72, 65, 66, 7, 74, + 77, 11, 13, 67, 77, 86, 3, 76, 84, 97, 7, 71, + 79, 4, 78, 79, 90, 65, 76, 70, 84, 67, 2, 22, + 0, 0, 0, 74, 89, 97, 65, 10, 64, 36, 6, 81, + 90, 74, 80, 7, 65, 79, 75, 93, 76, 90, 85, 93, + 80, 84, 87, 88, 24, 65, 69, 94, 71, 92, 82, + 97, 6, 1, 2, 68, 13, 68, 67, 79, 90, 78, 80, + 78, 68, 75, 88, 82, 68, 93, 83, 10, 2, 80, 83, + 1, 73, 67, 64, 3, 9, 0, 69, 81, 87, 72, 13, + 13, 85, 9, 7, 5, 4, 5, 4, 3, 3, 3, 7, 5, 2, 3, + 4, 71, 67, 5, 67, 14, 22, 23, 16, 8, 19, 12, + 16, 8, 1, 11, 10, 21, 89, 15, 26, 23, 44, 58, + 32, 37, 53, 53, 32, 45, 45, 62, 62, 26, 37, + 26, 34, 45, 36, 49, 57, 62, 27, 26, 41, 32, + 62, 2, 58, 62, 58, 56, 55, 50, 47, 46, 41, 35, + 31, 25, 19, 14, 68, 75, 73, 84, 15, 16, 13, + 14, 3, 3, 2, 12, 69, 72, 71, 83, 76, 106, 90, + 7, 9, 10, 2, 66, 71, 75, 77, 95, 81, 27, 15, + 9, 64, 1, 75, 83, 87, 95, 65, 29, 21, 16, 14, + 6, 1, 65, 69, 77, 67, 38, 31, 22, 7, 10, 65, + 73, 81, 9, 53, 46, 40, 29, 24, 3, 68, 69, 74, + 62, 106, 102, 91, 101, 100, 89, 95, 91, 86, + 92, 90, 91, 85, 86, 87, 95, 87, 83, 78, 78, + 76, 73, 71, 68, 67, 64, 69, 69, 75, 1, 2, 1, + 1, 1, 2, 2, 5, 1, 0, 1, 4, 65, 69, 73, 69, 0, + 73, 11, 16, 12, 16, 11, 13, 14, 11, 10, 4, 11, + 6, 68, 66, 52, 53, 50, 51, 50, 58, 55, 57, 57, + 54, 61, 57, 52, 42, 24, 62, 62, 62, 62, 52, + 50, 35, 30, 27, 21, 11, 5, 66, 75, 62, 62, 62, + 58, 53, 54, 50, 47, 40, 36, 31, 25, 14, 6, 67, + 4, 4, 69, 21, 20, 17, 13, 11, 11, 4, 5, 1, 66, + 81, 73, 84, 93, 8, 68, 73, 80, 75, 67, 74, 64, + 7, 70, 73, 1, 5, 3, 5, 11, 4, 3, 58, 46, 33, + 23, 14, 0, 73, 88, 101, 66, 39, 28, 23, 13, + 15, 6, 2, 0, 75, 78, 73, 65, 72, 3, 9, 67, 70, + 2, 6, 6, 6, 12, 9, 4, 58, 46, 33, 23, 14, 0, + 73, 88, 101 }, + + { + + 58, + 7, 77, 58, 7, 77, 99, 81, 16, 10, 5, 16, 29, + 47, 56, 18, 89, 67, 65, 66, 6, 4, 67, 64, 3, + 22, 23, 4, 77, 95, 82, 86, 72, 65, 66, 6, 75, + 77, 11, 12, 67, 77, 86, 2, 77, 84, 97, 6, 71, + 79, 4, 78, 79, 90, 65, 76, 71, 84, 67, 2, 22, + 0, 0, 0, 74, 90, 97, 65, 9, 65, 35, 5, 82, 88, + 73, 79, 8, 0, 78, 74, 92, 75, 89, 84, 91, 80, + 84, 87, 88, 24, 65, 69, 92, 71, 92, 82, 96, 5, + 0, 1, 69, 13, 68, 67, 79, 90, 78, 80, 78, 68, + 75, 87, 81, 68, 92, 82, 14, 6, 77, 81, 1, 73, + 67, 64, 2, 9, 0, 69, 81, 85, 72, 11, 11, 83, + 9, 7, 5, 4, 5, 4, 4, 3, 3, 6, 4, 2, 3, 4, 73, + 67, 5, 68, 13, 20, 22, 15, 7, 19, 12, 15, 6, + 1, 10, 9, 20, 89, 14, 25, 22, 41, 54, 30, 35, + 50, 50, 29, 42, 43, 55, 62, 22, 34, 24, 31, + 41, 33, 45, 52, 59, 24, 24, 37, 30, 62, 0, 55, + 59, 55, 53, 52, 47, 44, 43, 39, 32, 28, 23, + 17, 12, 70, 75, 74, 85, 14, 14, 11, 12, 1, 1, + 0, 10, 70, 73, 72, 84, 77, 106, 89, 7, 9, 10, + 2, 66, 71, 75, 77, 93, 80, 27, 15, 9, 64, 1, + 74, 82, 86, 94, 65, 29, 21, 16, 14, 7, 1, 65, + 69, 77, 68, 38, 30, 21, 6, 10, 65, 73, 81, 8, + 52, 45, 38, 28, 23, 3, 68, 69, 74, 62, 105, + 101, 90, 100, 99, 88, 94, 90, 85, 91, 89, 89, + 85, 86, 87, 94, 86, 83, 78, 78, 76, 73, 71, + 69, 68, 65, 69, 70, 76, 1, 2, 2, 1, 0, 2, 2, + 4, 1, 0, 1, 3, 65, 69, 73, 69, 0, 74, 10, 16, + 10, 15, 10, 12, 13, 10, 9, 4, 10, 4, 69, 68, + 50, 51, 49, 49, 48, 55, 52, 54, 54, 51, 58, + 54, 48, 39, 22, 62, 62, 61, 60, 48, 46, 32, + 27, 24, 19, 9, 3, 67, 76, 59, 60, 60, 55, 50, + 51, 47, 43, 37, 33, 28, 22, 12, 4, 69, 3, 3, + 71, 19, 18, 15, 10, 9, 9, 2, 3, 64, 68, 82, + 75, 85, 94, 6, 70, 75, 80, 75, 67, 74, 0, 8, + 70, 73, 1, 5, 3, 5, 11, 4, 2, 56, 44, 30, 19, + 10, 67, 78, 93, 104, 66, 39, 28, 23, 13, 15, + 6, 2, 0, 75, 78, 73, 65, 72, 3, 9, 67, 70, 2, + 6, 6, 6, 12, 8, 3, 56, 44, 30, 19, 10, 67, 78, + 93, 104 }, + + { + + 57, + 7, 77, 57, 7, 77, 97, 79, 17, 11, 5, 15, 27, + 46, 57, 18, 87, 67, 64, 65, 6, 4, 66, 64, 2, + 21, 21, 1, 79, 96, 79, 85, 71, 64, 65, 6, 75, + 76, 11, 12, 66, 76, 85, 2, 77, 83, 96, 6, 70, + 78, 4, 77, 78, 89, 64, 75, 71, 83, 66, 2, 22, + 0, 0, 0, 73, 90, 97, 64, 9, 65, 35, 5, 82, 85, + 71, 77, 10, 3, 76, 72, 90, 73, 87, 82, 88, 80, + 83, 86, 87, 24, 65, 68, 89, 70, 91, 81, 94, 5, + 0, 1, 69, 14, 68, 66, 78, 89, 77, 79, 77, 67, + 74, 85, 79, 67, 90, 80, 19, 11, 73, 78, 2, 72, + 66, 0, 2, 10, 1, 68, 80, 82, 71, 9, 10, 80, 9, + 8, 5, 5, 6, 5, 6, 4, 4, 6, 4, 2, 4, 5, 74, 66, + 5, 68, 13, 19, 22, 15, 7, 19, 12, 15, 5, 2, + 10, 9, 20, 89, 14, 25, 22, 39, 51, 29, 34, 48, + 48, 27, 40, 41, 49, 62, 19, 32, 23, 29, 38, + 31, 42, 48, 55, 22, 22, 34, 28, 62, 64, 53, + 57, 53, 51, 50, 45, 42, 41, 37, 30, 26, 22, + 16, 11, 71, 75, 74, 85, 14, 13, 10, 11, 0, 0, + 64, 9, 71, 73, 73, 84, 77, 105, 87, 8, 10, 10, + 3, 65, 70, 74, 76, 90, 78, 28, 16, 10, 0, 2, + 72, 80, 84, 92, 64, 30, 22, 17, 15, 8, 2, 64, + 68, 76, 68, 39, 30, 21, 6, 11, 64, 73, 80, 8, + 52, 45, 37, 27, 23, 4, 67, 68, 73, 62, 103, + 99, 88, 98, 97, 86, 92, 88, 83, 89, 87, 86, + 84, 85, 86, 92, 84, 82, 77, 77, 75, 72, 70, + 69, 68, 65, 69, 70, 76, 2, 3, 3, 2, 0, 3, 3, + 4, 2, 1, 1, 3, 64, 68, 72, 68, 1, 74, 9, 16, + 9, 15, 10, 12, 13, 10, 9, 4, 10, 3, 69, 69, + 49, 50, 49, 48, 47, 53, 50, 52, 52, 49, 56, + 52, 45, 37, 20, 61, 60, 57, 56, 45, 43, 30, + 25, 22, 18, 8, 2, 67, 76, 57, 58, 58, 53, 48, + 49, 45, 40, 35, 31, 26, 20, 11, 3, 70, 3, 3, + 72, 18, 17, 14, 8, 8, 8, 1, 2, 65, 69, 82, 76, + 85, 94, 5, 71, 76, 79, 74, 66, 73, 2, 10, 69, + 72, 2, 6, 4, 6, 12, 4, 2, 55, 42, 28, 16, 7, + 71, 82, 97, 106, 65, 39, 29, 24, 14, 16, 7, 3, + 1, 74, 77, 72, 64, 71, 4, 10, 66, 69, 3, 7, 6, + 7, 13, 8, 3, 55, 42, 28, 16, 7, 71, 82, 97, + 106 }, + + { + + 56, + 7, 77, 56, 7, 77, 95, 78, 17, 11, 5, 14, 25, + 45, 57, 18, 85, 67, 64, 64, 6, 3, 66, 65, 0, + 20, 18, 66, 82, 98, 76, 84, 71, 64, 64, 6, 75, + 75, 11, 11, 66, 76, 84, 2, 77, 83, 96, 6, 70, + 78, 4, 77, 78, 89, 64, 75, 71, 83, 66, 2, 22, + 0, 0, 0, 72, 90, 97, 64, 8, 65, 34, 5, 82, 83, + 70, 76, 12, 5, 75, 71, 88, 72, 86, 81, 86, 80, + 83, 86, 87, 24, 65, 67, 87, 70, 90, 80, 92, 5, + 0, 1, 70, 14, 68, 66, 77, 88, 76, 78, 76, 67, + 73, 84, 78, 66, 89, 79, 24, 15, 70, 75, 2, 72, + 66, 0, 2, 10, 1, 68, 80, 80, 71, 7, 8, 77, 9, + 8, 5, 5, 6, 5, 8, 5, 4, 6, 4, 2, 5, 6, 75, 66, + 5, 68, 12, 18, 21, 15, 6, 19, 12, 15, 4, 2, 9, + 8, 19, 89, 14, 24, 21, 37, 48, 28, 32, 46, 46, + 25, 38, 39, 43, 62, 15, 30, 21, 27, 35, 29, + 39, 44, 51, 19, 20, 31, 26, 62, 66, 51, 55, + 51, 49, 48, 43, 40, 39, 35, 28, 24, 20, 14, 9, + 73, 75, 74, 86, 13, 12, 9, 10, 64, 64, 65, 7, + 72, 73, 74, 85, 78, 104, 86, 9, 10, 10, 3, 64, + 69, 73, 75, 88, 77, 28, 16, 10, 1, 3, 71, 78, + 82, 90, 0, 31, 23, 18, 16, 9, 2, 64, 68, 75, + 68, 40, 30, 20, 6, 11, 64, 73, 80, 8, 52, 44, + 36, 26, 23, 4, 67, 68, 73, 62, 102, 98, 87, + 96, 95, 85, 90, 86, 82, 87, 85, 84, 83, 84, + 86, 91, 82, 82, 77, 77, 74, 72, 70, 69, 68, + 66, 69, 70, 76, 2, 3, 4, 2, 0, 3, 3, 4, 2, 1, + 1, 2, 64, 68, 72, 68, 2, 75, 8, 16, 8, 14, 9, + 11, 13, 9, 8, 4, 10, 2, 69, 70, 48, 49, 48, + 46, 45, 51, 48, 50, 50, 46, 53, 49, 42, 34, + 18, 57, 56, 53, 51, 42, 40, 27, 23, 19, 16, 6, + 1, 68, 77, 55, 56, 55, 51, 45, 46, 42, 37, 33, + 28, 24, 17, 9, 1, 72, 2, 2, 74, 17, 16, 12, 6, + 6, 6, 64, 1, 67, 70, 83, 77, 86, 95, 3, 72, + 77, 79, 74, 65, 73, 3, 11, 69, 71, 3, 7, 4, 6, + 13, 4, 2, 54, 40, 25, 13, 4, 75, 86, 101, 109, + 65, 39, 29, 24, 14, 17, 7, 3, 1, 74, 77, 72, + 64, 71, 5, 11, 66, 69, 3, 7, 6, 7, 14, 8, 3, + 54, 40, 25, 13, 4, 75, 86, 101, 109 }, + + { + + 55, + 7, 77, 55, 7, 77, 93, 76, 18, 11, 4, 12, 23, + 44, 57, 18, 83, 67, 0, 0, 6, 3, 66, 65, 64, + 19, 16, 69, 84, 99, 73, 83, 71, 0, 0, 6, 76, + 74, 11, 11, 66, 76, 84, 2, 77, 83, 96, 6, 70, + 77, 4, 77, 77, 89, 64, 75, 71, 83, 66, 2, 22, + 0, 0, 0, 72, 90, 97, 0, 7, 65, 33, 4, 82, 80, + 69, 75, 13, 7, 73, 69, 86, 70, 85, 80, 84, 80, + 83, 85, 87, 24, 65, 66, 85, 70, 89, 80, 90, 5, + 64, 0, 70, 14, 68, 65, 77, 87, 75, 78, 75, 66, + 72, 82, 76, 66, 88, 78, 29, 20, 67, 72, 2, 72, + 66, 1, 2, 10, 2, 67, 79, 78, 71, 5, 6, 74, 9, + 8, 5, 5, 6, 6, 9, 6, 4, 6, 4, 2, 5, 7, 76, 66, + 5, 69, 12, 17, 21, 15, 6, 19, 12, 15, 3, 2, 8, + 7, 19, 89, 14, 24, 21, 35, 45, 27, 30, 44, 43, + 23, 35, 37, 36, 62, 12, 28, 19, 25, 32, 26, + 36, 40, 47, 16, 18, 27, 24, 62, 68, 49, 53, + 49, 46, 45, 41, 38, 37, 33, 26, 22, 18, 12, 8, + 75, 75, 74, 86, 12, 11, 8, 9, 65, 65, 66, 5, + 73, 74, 75, 86, 78, 104, 84, 10, 11, 10, 3, 0, + 69, 73, 75, 86, 76, 28, 16, 11, 2, 4, 70, 77, + 81, 88, 1, 31, 23, 18, 16, 10, 3, 64, 68, 75, + 68, 40, 30, 19, 6, 11, 64, 73, 79, 8, 51, 44, + 35, 25, 23, 4, 67, 68, 73, 62, 101, 96, 86, + 95, 94, 84, 88, 84, 81, 86, 83, 82, 83, 83, + 85, 90, 80, 81, 77, 77, 74, 71, 70, 69, 68, + 66, 69, 70, 77, 3, 4, 5, 2, 0, 4, 3, 4, 2, 1, + 1, 2, 64, 68, 72, 68, 2, 76, 7, 16, 7, 13, 8, + 11, 13, 8, 7, 4, 10, 1, 69, 71, 47, 48, 47, + 45, 43, 49, 46, 48, 47, 44, 50, 46, 39, 32, + 16, 53, 52, 49, 46, 38, 37, 25, 21, 17, 14, 4, + 64, 69, 78, 53, 53, 53, 48, 42, 44, 40, 34, + 30, 26, 21, 14, 7, 64, 73, 1, 1, 75, 15, 14, + 10, 4, 5, 4, 66, 0, 68, 71, 84, 78, 87, 96, 2, + 73, 78, 79, 73, 65, 72, 4, 12, 69, 71, 3, 7, + 4, 7, 14, 4, 2, 53, 38, 23, 10, 1, 79, 90, + 105, 112, 65, 39, 29, 24, 14, 17, 8, 3, 1, 73, + 77, 72, 0, 70, 6, 12, 66, 68, 3, 7, 6, 7, 14, + 8, 3, 53, 38, 23, 10, 1, 79, 90, 105, 112 }, + + { + + 53, + 7, 77, 53, 7, 77, 92, 75, 18, 11, 4, 11, 21, + 43, 57, 17, 81, 67, 0, 1, 5, 2, 66, 66, 66, + 18, 13, 73, 87, 101, 70, 82, 71, 0, 1, 5, 76, + 73, 11, 10, 66, 75, 83, 2, 77, 83, 96, 6, 70, + 77, 4, 77, 77, 88, 64, 75, 71, 83, 65, 2, 22, + 0, 0, 0, 71, 91, 97, 0, 6, 65, 32, 4, 82, 78, + 68, 74, 15, 9, 72, 68, 85, 69, 83, 79, 82, 80, + 82, 85, 87, 24, 65, 65, 83, 70, 89, 79, 88, 5, + 64, 0, 71, 14, 68, 65, 76, 86, 74, 77, 74, 66, + 72, 81, 75, 65, 87, 77, 34, 24, 64, 69, 2, 71, + 66, 1, 2, 10, 2, 67, 79, 76, 71, 3, 4, 72, 9, + 9, 5, 5, 6, 6, 11, 7, 5, 5, 4, 2, 6, 7, 77, + 66, 5, 69, 11, 16, 20, 14, 5, 19, 12, 15, 2, + 2, 7, 6, 18, 89, 13, 23, 20, 33, 41, 26, 28, + 42, 41, 21, 33, 35, 30, 62, 8, 26, 17, 22, 29, + 24, 32, 35, 43, 13, 16, 24, 22, 62, 69, 47, + 51, 46, 44, 43, 39, 36, 35, 31, 23, 20, 16, + 10, 6, 77, 75, 74, 87, 11, 10, 7, 7, 66, 67, + 67, 4, 74, 74, 76, 86, 79, 103, 83, 11, 11, + 10, 4, 0, 68, 72, 74, 84, 74, 28, 17, 11, 2, + 5, 69, 75, 79, 87, 1, 32, 24, 19, 17, 11, 3, + 64, 67, 74, 68, 41, 30, 19, 5, 11, 64, 73, 79, + 8, 51, 43, 34, 24, 23, 4, 66, 68, 73, 62, 99, + 95, 85, 93, 92, 83, 87, 83, 80, 84, 82, 80, + 82, 82, 85, 89, 79, 81, 77, 77, 73, 71, 70, + 70, 68, 67, 69, 70, 77, 3, 4, 6, 2, 0, 4, 3, + 4, 2, 1, 1, 1, 64, 68, 72, 68, 3, 77, 6, 16, + 6, 13, 7, 10, 13, 7, 6, 4, 10, 0, 69, 72, 46, + 47, 46, 43, 41, 47, 44, 45, 45, 41, 47, 43, + 36, 29, 14, 48, 48, 45, 41, 35, 33, 22, 18, + 14, 12, 2, 65, 70, 78, 50, 51, 50, 46, 39, 41, + 37, 31, 28, 23, 19, 11, 5, 66, 75, 0, 1, 77, + 14, 13, 9, 2, 3, 2, 67, 64, 70, 72, 85, 80, + 88, 96, 0, 75, 80, 78, 73, 64, 72, 5, 13, 69, + 70, 4, 8, 4, 7, 15, 4, 2, 52, 36, 20, 7, 66, + 83, 95, 109, 115, 64, 39, 29, 24, 14, 18, 8, + 3, 1, 73, 77, 72, 0, 70, 6, 13, 66, 68, 4, 8, + 6, 7, 15, 8, 3, 52, 36, 20, 7, 66, 83, 95, + 109, 115 }, + + { + + 52, + 7, 77, 52, 7, 77, 90, 73, 18, 11, 3, 10, 19, + 42, 57, 17, 79, 67, 0, 2, 5, 2, 66, 67, 68, + 17, 11, 76, 89, 103, 67, 81, 70, 0, 2, 5, 76, + 72, 11, 10, 66, 75, 82, 2, 77, 83, 96, 6, 70, + 77, 4, 77, 76, 88, 64, 75, 71, 83, 65, 2, 22, + 0, 0, 0, 71, 91, 97, 1, 5, 65, 31, 3, 82, 75, + 67, 73, 17, 11, 70, 66, 83, 67, 82, 78, 79, + 80, 82, 84, 86, 24, 65, 64, 81, 70, 88, 79, + 86, 5, 65, 64, 71, 14, 68, 64, 76, 85, 73, 77, + 73, 65, 71, 79, 73, 64, 85, 76, 39, 28, 2, 66, + 3, 71, 65, 2, 2, 10, 3, 67, 78, 74, 71, 1, 2, + 69, 9, 9, 5, 5, 6, 7, 12, 8, 5, 5, 4, 2, 6, 8, + 78, 65, 5, 70, 11, 15, 20, 14, 5, 19, 12, 15, + 1, 3, 6, 6, 18, 89, 13, 22, 19, 31, 38, 25, + 27, 40, 39, 19, 30, 33, 24, 62, 5, 24, 15, 20, + 26, 21, 29, 31, 39, 11, 14, 20, 20, 62, 71, + 45, 49, 44, 42, 41, 37, 34, 33, 29, 21, 18, + 14, 9, 4, 79, 75, 74, 87, 10, 9, 6, 6, 67, 68, + 68, 2, 75, 75, 77, 87, 80, 102, 81, 12, 12, + 10, 4, 1, 68, 72, 73, 82, 73, 28, 17, 12, 3, + 6, 68, 74, 78, 85, 2, 33, 25, 20, 17, 12, 4, + 0, 67, 74, 68, 41, 30, 18, 5, 11, 64, 73, 78, + 8, 50, 42, 33, 23, 23, 4, 66, 68, 72, 62, 98, + 93, 83, 91, 91, 82, 85, 81, 79, 82, 80, 78, + 81, 81, 84, 88, 77, 81, 77, 76, 73, 70, 70, + 70, 68, 67, 69, 70, 78, 4, 4, 7, 2, 0, 4, 3, + 4, 2, 1, 1, 1, 64, 68, 72, 68, 3, 78, 5, 16, + 5, 12, 7, 9, 13, 7, 5, 4, 10, 64, 69, 73, 45, + 46, 45, 41, 39, 45, 42, 43, 42, 38, 44, 40, + 33, 27, 12, 44, 44, 41, 36, 32, 30, 20, 16, + 12, 10, 1, 67, 70, 79, 48, 48, 48, 44, 36, 38, + 35, 28, 26, 21, 17, 8, 3, 68, 76, 0, 0, 79, + 13, 11, 7, 0, 1, 0, 69, 65, 71, 73, 85, 81, + 89, 97, 64, 76, 81, 78, 73, 0, 71, 6, 14, 68, + 69, 4, 8, 4, 8, 16, 4, 2, 51, 34, 17, 4, 69, + 87, 99, 113, 118, 64, 39, 29, 24, 14, 18, 8, + 4, 2, 72, 77, 72, 1, 70, 7, 14, 66, 68, 4, 8, + 6, 8, 15, 8, 3, 51, 34, 17, 4, 69, 87, 99, + 113, 118 }, + + { + + 51, + 7, 78, 51, 7, 78, 88, 72, 19, 11, 3, 8, 17, + 41, 57, 17, 78, 67, 1, 2, 5, 1, 65, 67, 69, + 15, 8, 80, 92, 104, 65, 80, 70, 1, 2, 5, 77, + 72, 11, 9, 66, 75, 82, 2, 77, 82, 95, 6, 69, + 76, 4, 76, 76, 88, 64, 75, 71, 83, 65, 2, 22, + 0, 0, 0, 70, 91, 97, 1, 5, 66, 31, 3, 82, 73, + 66, 72, 18, 14, 69, 65, 81, 66, 81, 77, 77, + 80, 82, 84, 86, 24, 65, 0, 78, 70, 87, 78, 84, + 4, 65, 64, 72, 15, 68, 64, 75, 85, 73, 76, 72, + 65, 70, 78, 72, 64, 84, 75, 44, 33, 5, 0, 3, + 71, 65, 2, 2, 10, 3, 66, 78, 72, 70, 64, 0, + 66, 9, 9, 5, 5, 7, 7, 14, 9, 5, 5, 4, 2, 7, 9, + 79, 65, 5, 70, 10, 14, 19, 14, 4, 19, 12, 15, + 64, 3, 5, 5, 17, 89, 13, 22, 19, 29, 35, 24, + 25, 37, 36, 17, 28, 31, 17, 62, 1, 22, 14, 18, + 22, 19, 26, 27, 34, 8, 12, 17, 18, 62, 73, 43, + 47, 42, 39, 38, 35, 31, 31, 27, 19, 15, 12, 7, + 3, 80, 75, 74, 88, 9, 8, 5, 5, 68, 69, 69, 0, + 76, 75, 78, 88, 80, 102, 80, 13, 12, 10, 4, 2, + 67, 71, 73, 80, 72, 29, 17, 12, 4, 7, 67, 72, + 76, 83, 3, 33, 25, 20, 18, 13, 4, 0, 67, 73, + 68, 42, 30, 17, 5, 11, 64, 73, 78, 7, 50, 42, + 32, 22, 22, 4, 66, 68, 72, 62, 97, 92, 82, 90, + 89, 81, 83, 79, 78, 81, 78, 76, 81, 80, 84, + 87, 75, 80, 77, 76, 72, 70, 70, 70, 68, 68, + 69, 70, 78, 4, 5, 8, 2, 0, 5, 4, 4, 3, 2, 1, + 0, 64, 68, 72, 68, 4, 79, 4, 16, 4, 11, 6, 9, + 13, 6, 5, 4, 9, 66, 70, 74, 43, 45, 44, 40, + 37, 43, 40, 41, 40, 36, 41, 38, 30, 24, 10, + 40, 40, 37, 32, 28, 27, 17, 14, 9, 8, 64, 68, + 71, 80, 46, 46, 45, 41, 33, 36, 32, 25, 23, + 18, 14, 5, 1, 69, 78, 64, 64, 80, 11, 10, 5, + 65, 0, 65, 71, 66, 73, 74, 86, 82, 90, 98, 66, + 77, 82, 78, 72, 0, 71, 7, 15, 68, 69, 5, 9, 4, + 8, 17, 4, 2, 50, 32, 15, 1, 72, 91, 103, 117, + 121, 64, 39, 29, 24, 14, 19, 9, 4, 2, 72, 76, + 71, 1, 69, 8, 15, 65, 67, 4, 8, 6, 8, 16, 8, + 3, 50, 32, 15, 1, 72, 91, 103, 117, 121 }, + + { + + 50, + 7, 78, 50, 7, 78, 86, 70, 19, 11, 2, 7, 15, + 40, 57, 17, 76, 67, 1, 3, 4, 1, 65, 68, 71, + 14, 6, 83, 94, 106, 1, 79, 70, 1, 3, 4, 77, + 71, 11, 9, 66, 74, 81, 2, 77, 82, 95, 6, 69, + 76, 4, 76, 75, 87, 64, 75, 71, 83, 64, 2, 22, + 0, 0, 0, 70, 91, 97, 2, 4, 66, 30, 2, 82, 70, + 65, 71, 20, 16, 67, 0, 80, 64, 79, 76, 75, 80, + 81, 83, 86, 24, 65, 1, 76, 70, 86, 78, 82, 4, + 66, 65, 72, 15, 68, 0, 75, 84, 72, 76, 71, 64, + 69, 76, 70, 0, 83, 74, 49, 37, 8, 3, 3, 70, + 65, 3, 2, 10, 4, 66, 77, 70, 70, 66, 65, 0, 9, + 10, 5, 5, 7, 8, 15, 10, 6, 4, 4, 2, 7, 9, 80, + 65, 5, 71, 10, 13, 19, 13, 4, 19, 12, 15, 65, + 3, 4, 4, 17, 89, 13, 21, 18, 27, 32, 23, 23, + 35, 34, 15, 25, 29, 11, 62, 65, 20, 12, 15, + 19, 16, 23, 22, 30, 5, 10, 13, 16, 62, 74, 41, + 45, 40, 37, 36, 33, 29, 29, 25, 16, 13, 10, 5, + 1, 82, 75, 74, 88, 8, 7, 4, 3, 69, 70, 70, 64, + 77, 76, 79, 88, 81, 101, 78, 14, 13, 10, 5, 2, + 67, 71, 72, 78, 70, 29, 18, 13, 4, 8, 66, 71, + 75, 81, 3, 34, 26, 21, 18, 14, 5, 0, 66, 73, + 68, 42, 30, 17, 4, 11, 64, 73, 77, 7, 49, 41, + 31, 21, 22, 4, 65, 68, 72, 62, 95, 90, 81, 88, + 88, 80, 82, 78, 77, 79, 77, 74, 80, 79, 83, + 86, 73, 80, 77, 76, 72, 69, 70, 71, 68, 68, + 69, 70, 79, 5, 5, 9, 2, 0, 5, 4, 4, 3, 2, 1, + 0, 64, 68, 72, 68, 4, 80, 3, 16, 3, 11, 5, 8, + 13, 5, 4, 4, 9, 67, 70, 75, 42, 44, 43, 38, + 35, 41, 38, 38, 37, 33, 38, 35, 27, 22, 8, 35, + 36, 33, 27, 25, 24, 15, 12, 7, 6, 66, 70, 72, + 80, 43, 43, 43, 39, 30, 33, 30, 22, 21, 16, + 12, 2, 64, 71, 79, 65, 64, 82, 10, 8, 4, 67, + 65, 67, 72, 67, 74, 75, 87, 84, 91, 98, 67, + 79, 84, 77, 72, 1, 70, 8, 16, 68, 68, 5, 9, 4, + 9, 18, 4, 2, 49, 30, 12, 65, 76, 95, 107, 121, + 124, 0, 39, 29, 24, 14, 19, 9, 4, 2, 71, 76, + 71, 2, 69, 9, 16, 65, 67, 5, 9, 6, 8, 16, 8, + 3, 49, 30, 12, 65, 76, 95, 107, 121, 124 }, + + { + + 48, + 6, 78, 48, 6, 78, 85, 69, 19, 11, 2, 5, 12, + 39, 57, 16, 74, 68, 1, 4, 4, 0, 65, 69, 73, + 13, 3, 87, 97, 108, 4, 78, 70, 1, 4, 4, 78, + 70, 11, 8, 66, 74, 81, 1, 78, 82, 95, 6, 69, + 76, 4, 76, 75, 87, 64, 75, 71, 83, 64, 2, 22, + 0, 0, 0, 69, 92, 97, 2, 3, 66, 29, 2, 83, 68, + 64, 70, 21, 18, 66, 1, 78, 0, 78, 75, 73, 80, + 81, 83, 86, 24, 65, 2, 74, 70, 86, 77, 80, 4, + 66, 65, 73, 15, 68, 0, 74, 83, 71, 75, 71, 64, + 69, 75, 69, 0, 82, 73, 53, 41, 11, 5, 3, 70, + 65, 3, 1, 10, 4, 66, 77, 68, 70, 68, 67, 2, 9, + 10, 5, 5, 7, 8, 17, 10, 6, 4, 3, 2, 8, 10, 82, + 65, 5, 71, 9, 11, 18, 13, 3, 19, 12, 14, 66, + 3, 3, 3, 16, 89, 12, 20, 17, 25, 28, 21, 21, + 33, 31, 12, 23, 27, 4, 62, 69, 18, 10, 13, 16, + 14, 19, 18, 26, 2, 8, 10, 14, 62, 76, 39, 42, + 37, 34, 33, 30, 27, 26, 23, 14, 11, 8, 3, 64, + 84, 75, 75, 89, 7, 5, 3, 2, 70, 72, 72, 66, + 78, 76, 80, 89, 82, 101, 77, 15, 13, 10, 5, 3, + 66, 70, 72, 76, 69, 29, 18, 13, 5, 9, 65, 69, + 73, 80, 4, 34, 26, 21, 19, 15, 5, 0, 66, 72, + 69, 43, 30, 16, 4, 11, 64, 73, 77, 7, 49, 40, + 30, 20, 22, 4, 65, 68, 72, 62, 94, 89, 80, 87, + 86, 79, 80, 76, 76, 78, 75, 72, 80, 79, 83, + 85, 72, 80, 77, 76, 71, 69, 70, 71, 68, 69, + 69, 70, 79, 5, 5, 10, 2, 64, 5, 4, 3, 3, 2, 1, + 64, 64, 68, 72, 68, 5, 81, 2, 16, 1, 10, 4, 7, + 12, 4, 3, 4, 9, 68, 70, 77, 41, 42, 42, 36, + 33, 39, 36, 36, 35, 30, 35, 32, 24, 19, 6, 31, + 32, 28, 22, 21, 20, 12, 9, 4, 4, 68, 71, 73, + 81, 41, 41, 40, 36, 27, 30, 27, 19, 18, 13, 9, + 64, 66, 73, 81, 66, 65, 84, 8, 7, 2, 69, 67, + 69, 74, 69, 76, 77, 88, 85, 92, 99, 69, 80, + 85, 77, 72, 1, 70, 9, 17, 68, 68, 6, 10, 4, 9, + 18, 4, 1, 48, 28, 9, 68, 79, 99, 112, 126, + 126, 0, 39, 29, 24, 14, 20, 9, 4, 2, 71, 76, + 71, 2, 69, 9, 16, 65, 67, 5, 9, 6, 8, 17, 8, + 2, 48, 28, 9, 68, 79, 99, 112, 126, 126 }, + + { + + 47, + 6, 78, 47, 6, 78, 83, 68, 20, 11, 2, 4, 10, + 38, 58, 16, 72, 68, 2, 5, 4, 64, 65, 69, 74, + 12, 1, 91, 100, 109, 7, 77, 69, 2, 5, 4, 78, + 69, 11, 8, 65, 74, 80, 1, 78, 82, 95, 6, 69, + 75, 4, 76, 75, 87, 64, 75, 71, 82, 64, 2, 22, + 0, 0, 0, 68, 92, 97, 2, 2, 66, 28, 2, 83, 66, + 1, 69, 23, 20, 64, 2, 76, 2, 77, 73, 70, 80, + 81, 83, 85, 24, 65, 3, 72, 69, 85, 76, 78, 4, + 66, 65, 73, 15, 68, 0, 73, 82, 70, 74, 70, 0, + 68, 74, 67, 1, 80, 72, 58, 46, 15, 8, 4, 70, + 64, 4, 1, 10, 4, 65, 76, 65, 70, 70, 68, 5, 9, + 10, 5, 5, 7, 9, 19, 11, 6, 4, 3, 2, 9, 11, 83, + 64, 5, 71, 8, 10, 17, 13, 2, 19, 12, 14, 67, + 4, 2, 3, 15, 89, 12, 20, 17, 23, 25, 20, 20, + 31, 29, 10, 21, 25, 65, 62, 72, 16, 8, 11, 13, + 12, 16, 14, 22, 0, 6, 7, 12, 62, 78, 37, 40, + 35, 32, 31, 28, 25, 24, 21, 12, 9, 7, 2, 65, + 86, 75, 75, 89, 7, 4, 2, 1, 71, 73, 73, 68, + 79, 76, 81, 90, 82, 100, 76, 16, 13, 10, 5, 4, + 65, 69, 71, 73, 68, 29, 18, 13, 6, 10, 64, 67, + 71, 78, 5, 35, 27, 22, 20, 16, 5, 1, 66, 71, + 69, 44, 30, 15, 4, 12, 0, 73, 76, 7, 49, 40, + 29, 19, 22, 4, 65, 68, 71, 62, 93, 88, 78, 85, + 84, 78, 78, 74, 75, 76, 73, 70, 79, 78, 82, + 84, 70, 79, 76, 75, 70, 68, 70, 71, 68, 70, + 69, 70, 79, 5, 6, 11, 3, 64, 6, 4, 3, 3, 2, 1, + 65, 64, 67, 71, 68, 6, 81, 1, 16, 0, 9, 4, 7, + 12, 4, 2, 4, 9, 69, 70, 78, 40, 41, 42, 35, + 31, 37, 34, 34, 33, 28, 32, 29, 21, 16, 4, 27, + 28, 24, 17, 18, 17, 9, 7, 2, 3, 69, 72, 73, + 82, 39, 39, 38, 34, 25, 28, 25, 16, 16, 11, 7, + 66, 68, 75, 83, 66, 66, 85, 7, 6, 0, 71, 68, + 70, 76, 70, 78, 78, 88, 86, 92, 100, 71, 81, + 86, 77, 71, 2, 70, 10, 19, 67, 67, 7, 11, 4, + 10, 19, 4, 1, 47, 26, 7, 71, 82, 103, 116, + 126, 126, 0, 39, 29, 25, 15, 21, 10, 5, 3, 71, + 76, 71, 2, 68, 10, 17, 65, 66, 5, 9, 6, 9, 18, + 8, 2, 47, 26, 7, 71, 82, 103, 116, 126, 126 }, + + { + + 46, + 6, 78, 46, 6, 78, 81, 66, 20, 11, 1, 3, 8, 37, + 58, 16, 70, 68, 2, 6, 3, 64, 65, 70, 76, 11, + 65, 94, 102, 111, 10, 76, 69, 2, 6, 3, 78, 68, + 11, 7, 65, 73, 79, 1, 78, 82, 95, 6, 69, 75, + 4, 76, 74, 86, 64, 75, 71, 82, 0, 2, 22, 0, 0, + 0, 68, 92, 97, 3, 1, 66, 27, 1, 83, 0, 2, 68, + 25, 22, 0, 4, 75, 3, 75, 72, 68, 80, 80, 82, + 85, 24, 65, 4, 70, 69, 84, 76, 76, 4, 67, 66, + 74, 15, 68, 1, 73, 81, 69, 74, 69, 0, 67, 72, + 66, 2, 79, 71, 62, 50, 18, 11, 4, 69, 64, 4, + 1, 10, 5, 65, 76, 0, 70, 72, 70, 8, 9, 11, 5, + 5, 7, 9, 20, 12, 7, 3, 3, 2, 9, 11, 84, 64, 5, + 72, 8, 9, 17, 12, 2, 19, 12, 14, 68, 4, 1, 2, + 15, 89, 12, 19, 16, 21, 22, 19, 18, 29, 27, 8, + 18, 23, 71, 62, 76, 14, 6, 8, 10, 9, 13, 9, + 18, 66, 4, 3, 10, 62, 79, 35, 38, 33, 30, 29, + 26, 23, 22, 19, 9, 7, 5, 0, 67, 88, 75, 75, + 90, 6, 3, 1, 64, 72, 74, 74, 69, 80, 77, 82, + 90, 83, 99, 74, 17, 14, 10, 6, 4, 65, 69, 70, + 71, 66, 29, 19, 14, 6, 11, 0, 66, 70, 76, 5, + 36, 28, 23, 20, 17, 6, 1, 65, 71, 69, 44, 30, + 15, 3, 12, 0, 73, 76, 7, 48, 39, 28, 18, 22, + 4, 64, 68, 71, 62, 91, 86, 77, 83, 83, 77, 77, + 73, 74, 74, 72, 68, 78, 77, 82, 83, 68, 79, + 76, 75, 70, 68, 70, 72, 68, 70, 69, 70, 80, 6, + 6, 12, 3, 64, 6, 4, 3, 3, 2, 1, 65, 64, 67, + 71, 68, 6, 82, 0, 16, 64, 9, 3, 6, 12, 3, 1, + 4, 9, 70, 70, 79, 39, 40, 41, 33, 29, 35, 32, + 31, 30, 25, 29, 26, 18, 14, 2, 22, 24, 20, 12, + 15, 14, 7, 5, 64, 1, 71, 74, 74, 82, 36, 36, + 35, 32, 22, 25, 22, 13, 14, 8, 5, 69, 70, 77, + 84, 67, 66, 87, 6, 4, 64, 73, 70, 72, 77, 71, + 79, 79, 89, 88, 93, 100, 72, 83, 88, 76, 71, + 3, 69, 11, 20, 67, 66, 7, 11, 4, 10, 20, 4, 1, + 46, 24, 4, 74, 86, 107, 120, 126, 126, 1, 39, + 29, 25, 15, 21, 10, 5, 3, 70, 76, 71, 3, 68, + 11, 18, 65, 66, 6, 10, 6, 9, 18, 8, 2, 46, 24, + 4, 74, 86, 107, 120, 126, 126 }, + + { + + 45, + 6, 79, 45, 6, 79, 79, 65, 21, 11, 1, 1, 6, 36, + 58, 16, 69, 68, 3, 6, 3, 65, 64, 70, 77, 9, + 67, 98, 105, 112, 12, 75, 69, 3, 6, 3, 79, 68, + 11, 7, 65, 73, 79, 1, 78, 81, 94, 6, 68, 74, + 4, 75, 74, 86, 64, 75, 71, 82, 0, 2, 22, 0, 0, + 0, 67, 92, 97, 3, 1, 67, 27, 1, 83, 2, 3, 67, + 26, 25, 2, 5, 73, 5, 74, 71, 66, 80, 80, 82, + 85, 24, 65, 5, 67, 69, 83, 75, 74, 3, 67, 66, + 74, 16, 68, 1, 72, 81, 69, 73, 68, 1, 66, 71, + 64, 2, 78, 70, 62, 55, 21, 14, 4, 69, 64, 5, + 1, 10, 5, 64, 75, 2, 69, 74, 72, 11, 9, 11, 5, + 5, 8, 10, 22, 13, 7, 3, 3, 2, 10, 12, 85, 64, + 5, 72, 7, 8, 16, 12, 1, 19, 12, 14, 70, 4, 0, + 1, 14, 89, 12, 19, 16, 19, 19, 18, 16, 26, 24, + 6, 16, 21, 78, 62, 79, 12, 5, 6, 6, 7, 10, 5, + 13, 69, 2, 0, 8, 62, 81, 33, 36, 31, 27, 26, + 24, 20, 20, 17, 7, 4, 3, 65, 68, 89, 75, 75, + 90, 5, 2, 0, 65, 73, 75, 75, 71, 81, 77, 83, + 91, 83, 99, 73, 18, 14, 10, 6, 5, 64, 68, 70, + 69, 65, 30, 19, 14, 7, 12, 1, 64, 68, 74, 6, + 36, 28, 23, 21, 18, 6, 1, 65, 70, 69, 45, 30, + 14, 3, 12, 0, 73, 75, 6, 48, 39, 27, 17, 21, + 4, 64, 68, 71, 62, 90, 85, 76, 82, 81, 76, 75, + 71, 73, 73, 70, 66, 78, 76, 81, 82, 66, 78, + 76, 75, 69, 67, 70, 72, 68, 71, 69, 70, 80, 6, + 7, 13, 3, 64, 7, 5, 3, 4, 3, 1, 66, 64, 67, + 71, 68, 7, 83, 64, 16, 65, 8, 2, 6, 12, 2, 1, + 4, 8, 72, 71, 80, 37, 39, 40, 32, 27, 33, 30, + 29, 28, 23, 26, 24, 15, 11, 0, 18, 20, 16, 8, + 11, 11, 4, 3, 66, 64, 73, 75, 75, 83, 34, 34, + 33, 29, 19, 23, 20, 10, 11, 6, 2, 72, 72, 78, + 86, 68, 67, 88, 4, 3, 66, 75, 71, 74, 79, 72, + 81, 80, 90, 89, 94, 101, 74, 84, 89, 76, 70, + 3, 69, 12, 21, 67, 66, 8, 12, 4, 11, 21, 4, 1, + 45, 22, 2, 77, 89, 111, 124, 126, 126, 1, 39, + 29, 25, 15, 22, 11, 5, 3, 70, 75, 70, 3, 67, + 12, 19, 64, 65, 6, 10, 6, 9, 19, 8, 2, 45, 22, + 2, 77, 89, 111, 124, 126, 126 }, + + { + + 43, + 6, 79, 43, 6, 79, 78, 0, 21, 11, 0, 0, 4, 35, + 58, 15, 67, 68, 3, 7, 3, 65, 64, 71, 79, 8, + 70, 101, 107, 114, 15, 74, 69, 3, 7, 3, 79, + 67, 11, 6, 65, 73, 78, 1, 78, 81, 94, 6, 68, + 74, 4, 75, 73, 86, 64, 75, 71, 82, 0, 2, 22, + 0, 0, 0, 67, 93, 97, 4, 0, 67, 26, 0, 83, 5, + 4, 66, 28, 27, 3, 7, 71, 6, 73, 70, 64, 80, + 80, 81, 85, 24, 65, 6, 65, 69, 83, 75, 72, 3, + 68, 67, 75, 16, 68, 2, 72, 80, 68, 73, 67, 1, + 66, 69, 0, 3, 77, 69, 62, 59, 24, 17, 4, 69, + 64, 5, 1, 10, 6, 64, 75, 4, 69, 76, 74, 13, 9, + 11, 5, 5, 8, 10, 23, 14, 7, 3, 3, 2, 10, 13, + 86, 64, 5, 73, 7, 7, 16, 12, 1, 19, 12, 14, + 71, 4, 64, 0, 14, 89, 11, 18, 15, 17, 15, 17, + 14, 24, 22, 4, 13, 19, 84, 62, 83, 10, 3, 4, + 3, 4, 6, 1, 9, 72, 0, 67, 6, 62, 83, 31, 34, + 28, 25, 24, 22, 18, 18, 15, 5, 2, 1, 67, 70, + 91, 75, 75, 91, 4, 1, 64, 66, 74, 77, 76, 73, + 82, 78, 84, 92, 84, 98, 71, 19, 15, 10, 6, 6, + 64, 68, 69, 67, 64, 30, 19, 15, 8, 13, 2, 0, + 67, 73, 7, 37, 29, 24, 21, 19, 7, 1, 65, 70, + 69, 45, 30, 13, 3, 12, 0, 73, 75, 6, 47, 38, + 26, 16, 21, 4, 64, 68, 71, 62, 89, 83, 75, 80, + 80, 75, 73, 69, 72, 71, 68, 64, 77, 75, 81, + 81, 65, 78, 76, 75, 69, 67, 70, 72, 68, 71, + 69, 70, 81, 7, 7, 14, 3, 64, 7, 5, 3, 4, 3, 1, + 66, 64, 67, 71, 68, 7, 84, 65, 16, 66, 7, 1, + 5, 12, 1, 0, 4, 8, 73, 71, 81, 36, 38, 39, 30, + 25, 31, 28, 27, 25, 20, 23, 21, 12, 9, 65, 14, + 16, 12, 3, 8, 7, 2, 0, 69, 66, 75, 77, 76, 84, + 32, 31, 30, 27, 16, 20, 17, 7, 9, 3, 0, 75, + 74, 80, 87, 69, 68, 90, 3, 1, 68, 77, 73, 76, + 81, 73, 82, 81, 91, 90, 95, 102, 75, 85, 90, + 76, 70, 4, 68, 13, 22, 67, 65, 8, 12, 4, 11, + 22, 4, 1, 44, 20, 64, 80, 92, 115, 126, 126, + 126, 1, 39, 29, 25, 15, 22, 11, 5, 3, 69, 75, + 70, 4, 67, 12, 20, 64, 65, 6, 10, 6, 9, 19, 8, + 2, 44, 20, 64, 80, 92, 115, 126, 126, 126 }, + + { + + 42, + 6, 79, 42, 6, 79, 76, 1, 21, 11, 0, 64, 2, 34, + 58, 15, 65, 68, 3, 8, 2, 66, 64, 72, 81, 7, + 72, 105, 110, 116, 18, 73, 68, 3, 8, 2, 79, + 66, 11, 6, 65, 72, 77, 1, 78, 81, 94, 6, 68, + 74, 4, 75, 73, 85, 64, 75, 71, 82, 1, 2, 22, + 0, 0, 0, 66, 93, 97, 4, 64, 67, 25, 0, 83, 7, + 5, 65, 30, 29, 5, 8, 70, 8, 71, 69, 2, 80, 79, + 81, 84, 24, 65, 7, 0, 69, 82, 74, 70, 3, 68, + 67, 75, 16, 68, 2, 71, 79, 67, 72, 66, 2, 65, + 68, 2, 4, 75, 68, 62, 62, 27, 20, 5, 68, 0, 6, + 1, 10, 6, 64, 74, 6, 69, 78, 76, 16, 9, 12, 5, + 5, 8, 11, 25, 15, 8, 2, 3, 2, 11, 13, 87, 0, + 5, 73, 6, 6, 15, 11, 0, 19, 12, 14, 72, 5, 65, + 0, 13, 89, 11, 17, 14, 15, 12, 16, 13, 22, 20, + 2, 11, 17, 90, 62, 86, 8, 1, 1, 0, 2, 3, 67, + 5, 74, 65, 70, 4, 62, 84, 29, 32, 26, 23, 22, + 20, 16, 16, 13, 2, 0, 64, 68, 72, 93, 75, 75, + 91, 3, 0, 65, 68, 75, 78, 77, 74, 83, 78, 85, + 92, 85, 97, 70, 20, 15, 10, 7, 6, 0, 67, 68, + 65, 1, 30, 20, 15, 8, 14, 3, 2, 65, 71, 7, 38, + 30, 25, 22, 20, 7, 2, 64, 69, 69, 46, 30, 13, + 2, 12, 0, 73, 74, 6, 47, 37, 25, 15, 21, 4, 0, + 68, 70, 62, 87, 82, 73, 78, 78, 74, 72, 68, + 71, 69, 67, 1, 76, 74, 80, 80, 0, 78, 76, 74, + 68, 66, 70, 73, 68, 72, 69, 70, 81, 7, 7, 15, + 3, 64, 7, 5, 3, 4, 3, 1, 67, 64, 67, 71, 68, + 8, 85, 66, 16, 67, 7, 1, 4, 12, 1, 64, 4, 8, + 74, 71, 82, 35, 37, 38, 28, 23, 29, 26, 24, + 23, 17, 20, 18, 9, 6, 67, 9, 12, 8, 65, 5, 4, + 64, 65, 71, 68, 76, 78, 76, 84, 29, 29, 28, + 25, 13, 17, 15, 4, 7, 1, 65, 78, 76, 82, 89, + 69, 68, 92, 2, 0, 69, 79, 75, 78, 82, 74, 84, + 82, 91, 92, 96, 102, 77, 87, 92, 75, 70, 5, + 68, 14, 23, 66, 64, 9, 13, 4, 12, 23, 4, 1, + 43, 18, 67, 83, 96, 119, 126, 126, 126, 2, 39, + 29, 25, 15, 23, 11, 6, 4, 69, 75, 70, 4, 67, + 13, 21, 64, 65, 7, 11, 6, 10, 20, 8, 2, 43, + 18, 67, 83, 96, 119, 126, 126, 126 }, + + { + + 41, + 6, 79, 41, 6, 79, 74, 3, 22, 11, 64, 66, 0, + 33, 58, 15, 0, 68, 4, 9, 2, 66, 64, 72, 82, 6, + 75, 108, 112, 117, 21, 72, 68, 4, 9, 2, 80, + 65, 11, 5, 65, 72, 77, 1, 78, 81, 94, 6, 68, + 73, 4, 75, 72, 85, 64, 75, 71, 82, 1, 2, 22, + 0, 0, 0, 66, 93, 97, 5, 65, 67, 24, 64, 83, + 10, 6, 64, 31, 31, 6, 10, 68, 9, 70, 68, 4, + 80, 79, 80, 84, 24, 65, 8, 2, 69, 81, 74, 68, + 3, 69, 68, 76, 16, 68, 3, 71, 78, 66, 72, 65, + 2, 64, 66, 3, 4, 74, 67, 62, 62, 30, 23, 5, + 68, 0, 6, 1, 10, 7, 0, 74, 8, 69, 80, 78, 19, + 9, 12, 5, 5, 8, 11, 26, 16, 8, 2, 3, 2, 11, + 14, 88, 0, 5, 74, 6, 5, 15, 11, 0, 19, 12, 14, + 73, 5, 66, 64, 13, 89, 11, 17, 14, 13, 9, 15, + 11, 20, 17, 0, 8, 15, 97, 62, 90, 6, 64, 64, + 66, 64, 0, 71, 1, 77, 67, 74, 2, 62, 86, 27, + 30, 24, 20, 19, 18, 14, 14, 11, 0, 65, 66, 70, + 73, 95, 75, 75, 92, 2, 64, 66, 69, 76, 79, 78, + 76, 84, 79, 86, 93, 85, 97, 68, 21, 16, 10, 7, + 7, 0, 67, 68, 0, 2, 30, 20, 16, 9, 15, 4, 3, + 64, 69, 8, 38, 30, 25, 22, 21, 8, 2, 64, 69, + 69, 46, 30, 12, 2, 12, 0, 73, 74, 6, 46, 37, + 24, 14, 21, 4, 0, 68, 70, 62, 86, 80, 72, 77, + 77, 73, 70, 66, 70, 68, 65, 3, 76, 73, 80, 79, + 2, 77, 76, 74, 68, 66, 70, 73, 68, 72, 69, 70, + 82, 8, 8, 16, 3, 64, 8, 5, 3, 4, 3, 1, 67, 64, + 67, 71, 68, 8, 86, 67, 16, 68, 6, 0, 4, 12, 0, + 65, 4, 8, 75, 71, 83, 34, 36, 37, 27, 21, 27, + 24, 22, 20, 15, 17, 15, 6, 4, 69, 5, 8, 4, 70, + 1, 1, 66, 67, 74, 70, 78, 80, 77, 85, 27, 26, + 25, 22, 10, 15, 12, 1, 4, 65, 68, 81, 78, 84, + 90, 70, 69, 93, 0, 65, 71, 81, 76, 80, 84, 75, + 85, 83, 92, 93, 97, 103, 78, 88, 93, 75, 69, + 5, 67, 15, 24, 66, 64, 9, 13, 4, 12, 24, 4, 1, + 42, 16, 69, 86, 99, 123, 126, 126, 126, 2, 39, + 29, 25, 15, 23, 12, 6, 4, 68, 75, 70, 5, 66, + 14, 22, 64, 64, 7, 11, 6, 10, 20, 8, 2, 42, + 16, 69, 86, 99, 123, 126, 126, 126 }, + + { + + 40, + 6, 79, 40, 6, 79, 72, 4, 22, 11, 64, 67, 65, + 32, 58, 15, 2, 68, 4, 10, 2, 67, 64, 73, 84, + 5, 77, 112, 115, 119, 24, 71, 68, 4, 10, 2, + 80, 64, 11, 5, 65, 72, 76, 1, 78, 81, 94, 6, + 68, 73, 4, 75, 72, 85, 64, 75, 71, 82, 1, 2, + 22, 0, 0, 0, 65, 93, 97, 5, 66, 67, 23, 64, + 83, 12, 7, 0, 33, 33, 8, 11, 66, 11, 69, 67, + 6, 80, 79, 80, 84, 24, 65, 9, 4, 69, 80, 73, + 66, 3, 69, 68, 76, 16, 68, 3, 70, 77, 65, 71, + 64, 3, 0, 65, 5, 5, 73, 66, 62, 62, 33, 26, 5, + 68, 0, 7, 1, 10, 7, 0, 73, 10, 69, 82, 80, 22, + 9, 12, 5, 5, 8, 12, 28, 17, 8, 2, 3, 2, 12, + 15, 89, 0, 5, 74, 5, 4, 14, 11, 64, 19, 12, + 14, 74, 5, 67, 65, 12, 89, 11, 16, 13, 11, 6, + 14, 9, 18, 15, 65, 6, 13, 103, 62, 93, 4, 66, + 66, 69, 66, 66, 75, 66, 80, 69, 77, 0, 62, 88, + 25, 28, 22, 18, 17, 16, 12, 12, 9, 65, 67, 68, + 72, 75, 97, 75, 75, 92, 1, 65, 67, 70, 77, 80, + 79, 78, 85, 79, 87, 94, 86, 96, 67, 22, 16, + 10, 7, 8, 1, 66, 67, 2, 3, 30, 20, 16, 10, 16, + 5, 5, 1, 67, 9, 39, 31, 26, 23, 22, 8, 2, 64, + 68, 69, 47, 30, 11, 2, 12, 0, 73, 73, 6, 46, + 36, 23, 13, 21, 4, 0, 68, 70, 62, 85, 79, 71, + 75, 75, 72, 68, 64, 69, 66, 0, 5, 75, 72, 79, + 78, 4, 77, 76, 74, 67, 65, 70, 73, 68, 73, 69, + 70, 82, 8, 8, 17, 3, 64, 8, 5, 3, 4, 3, 1, 68, + 64, 67, 71, 68, 9, 87, 68, 16, 69, 5, 64, 3, + 12, 64, 66, 4, 8, 76, 71, 84, 33, 35, 36, 25, + 19, 25, 22, 20, 18, 12, 14, 12, 3, 1, 71, 1, + 4, 0, 75, 65, 65, 69, 69, 76, 72, 80, 81, 78, + 86, 25, 24, 23, 20, 7, 12, 10, 65, 2, 67, 70, + 84, 80, 86, 92, 71, 70, 95, 64, 66, 73, 83, + 78, 82, 86, 76, 87, 84, 93, 94, 98, 104, 80, + 89, 94, 75, 69, 6, 67, 16, 25, 66, 0, 10, 14, + 4, 13, 25, 4, 1, 41, 14, 72, 89, 102, 126, + 126, 126, 126, 2, 39, 29, 25, 15, 24, 12, 6, + 4, 68, 75, 70, 5, 66, 15, 23, 64, 64, 7, 11, + 6, 10, 21, 8, 2, 41, 14, 72, 89, 102, 126, + 126, 126, 126 }, + + { + + 38, + 5, 80, 38, 5, 80, 71, 5, 22, 11, 65, 69, 68, + 31, 58, 14, 3, 69, 4, 10, 1, 68, 64, 74, 86, + 3, 80, 116, 118, 121, 26, 71, 68, 4, 10, 1, + 81, 64, 11, 4, 65, 72, 76, 0, 79, 81, 94, 5, + 68, 73, 4, 75, 72, 85, 64, 75, 72, 82, 1, 2, + 22, 0, 0, 0, 65, 94, 97, 5, 67, 68, 22, 65, + 84, 14, 8, 1, 34, 35, 9, 12, 65, 12, 68, 66, + 8, 80, 79, 80, 84, 24, 65, 9, 6, 69, 80, 73, + 65, 2, 70, 69, 77, 16, 68, 3, 70, 77, 65, 71, + 64, 3, 0, 64, 6, 5, 72, 65, 62, 62, 36, 28, 5, + 68, 0, 7, 0, 10, 7, 0, 73, 12, 69, 84, 82, 24, + 9, 12, 5, 5, 8, 12, 29, 17, 8, 1, 2, 2, 12, + 15, 91, 0, 5, 75, 4, 2, 13, 10, 65, 19, 12, + 13, 76, 5, 68, 66, 11, 89, 10, 15, 12, 8, 2, + 12, 7, 15, 12, 68, 3, 11, 110, 62, 97, 1, 68, + 69, 73, 69, 70, 80, 71, 83, 71, 81, 65, 62, + 90, 22, 25, 19, 15, 14, 13, 9, 9, 7, 68, 70, + 70, 74, 77, 99, 75, 76, 93, 0, 67, 69, 72, 79, + 82, 81, 80, 86, 80, 88, 95, 87, 96, 66, 22, + 16, 10, 7, 8, 1, 66, 67, 4, 4, 30, 20, 16, 10, + 16, 6, 6, 2, 66, 9, 39, 31, 26, 23, 23, 8, 2, + 64, 68, 70, 47, 29, 10, 1, 12, 0, 73, 73, 5, + 45, 35, 21, 12, 20, 4, 0, 68, 70, 62, 84, 78, + 70, 74, 74, 71, 67, 0, 68, 65, 1, 7, 75, 72, + 79, 77, 5, 77, 76, 74, 67, 65, 70, 74, 69, 74, + 69, 71, 83, 8, 8, 18, 3, 65, 8, 5, 2, 4, 3, 1, + 69, 64, 67, 71, 68, 9, 88, 69, 16, 71, 4, 65, + 2, 11, 65, 67, 4, 7, 78, 72, 86, 31, 33, 35, + 23, 17, 22, 19, 17, 15, 9, 11, 9, 64, 65, 73, + 67, 0, 68, 80, 69, 69, 72, 72, 79, 74, 82, 83, + 79, 87, 22, 21, 20, 17, 4, 9, 7, 69, 64, 70, + 73, 87, 82, 88, 94, 72, 71, 97, 66, 68, 75, + 86, 80, 84, 88, 78, 89, 86, 94, 96, 99, 105, + 82, 91, 96, 75, 69, 6, 67, 17, 26, 66, 0, 10, + 14, 4, 13, 25, 4, 0, 39, 12, 75, 93, 106, 126, + 126, 126, 126, 2, 39, 29, 25, 15, 24, 12, 6, + 4, 68, 75, 70, 5, 66, 15, 23, 64, 64, 7, 11, + 6, 10, 21, 7, 1, 39, 12, 75, 93, 106, 126, + 126, 126, 126 }, + + { + + 37, + 5, 80, 37, 5, 80, 69, 7, 23, 12, 65, 70, 70, + 30, 59, 14, 5, 69, 5, 11, 1, 68, 0, 74, 87, 2, + 82, 119, 120, 122, 29, 70, 67, 5, 11, 1, 81, + 0, 11, 4, 64, 71, 75, 0, 79, 80, 93, 5, 67, + 72, 4, 74, 71, 84, 0, 74, 72, 81, 2, 2, 22, 0, + 0, 0, 64, 94, 97, 6, 67, 68, 22, 65, 84, 17, + 10, 3, 36, 38, 11, 14, 0, 14, 66, 64, 11, 80, + 78, 79, 83, 24, 65, 10, 9, 68, 79, 72, 0, 2, + 70, 69, 77, 17, 68, 4, 69, 76, 64, 70, 0, 4, + 1, 1, 8, 6, 70, 0, 62, 62, 40, 31, 6, 67, 1, + 8, 0, 11, 8, 1, 72, 15, 68, 86, 83, 27, 9, 13, + 5, 6, 9, 13, 31, 18, 9, 1, 2, 2, 13, 16, 92, + 1, 5, 75, 4, 1, 13, 10, 65, 19, 12, 13, 77, 6, + 68, 66, 11, 89, 10, 15, 12, 6, 64, 11, 6, 13, + 10, 70, 1, 9, 116, 62, 100, 64, 69, 71, 76, + 71, 73, 84, 75, 85, 73, 84, 67, 62, 91, 20, + 23, 17, 13, 12, 11, 7, 7, 5, 70, 72, 71, 75, + 78, 100, 75, 76, 93, 0, 68, 70, 73, 80, 83, + 82, 81, 87, 80, 89, 95, 87, 95, 64, 23, 17, + 10, 8, 9, 2, 65, 66, 7, 6, 31, 21, 17, 11, 17, + 8, 8, 4, 64, 10, 40, 32, 27, 24, 24, 9, 3, 0, + 67, 70, 48, 29, 10, 1, 13, 1, 73, 72, 5, 45, + 35, 20, 11, 20, 5, 1, 67, 69, 62, 82, 76, 68, + 72, 72, 69, 65, 2, 66, 0, 3, 10, 74, 71, 78, + 75, 7, 76, 75, 73, 66, 64, 69, 74, 69, 74, 69, + 71, 83, 9, 9, 19, 4, 65, 9, 6, 2, 5, 4, 1, 69, + 0, 66, 70, 67, 10, 88, 70, 16, 72, 4, 65, 2, + 11, 65, 67, 4, 7, 79, 72, 87, 30, 32, 35, 22, + 16, 20, 17, 15, 13, 7, 9, 7, 67, 67, 75, 71, + 66, 72, 84, 72, 72, 74, 74, 81, 75, 83, 84, + 79, 87, 20, 19, 18, 15, 2, 7, 5, 72, 66, 72, + 75, 89, 83, 89, 95, 72, 71, 98, 67, 69, 76, + 88, 81, 85, 89, 79, 90, 87, 94, 97, 99, 105, + 83, 92, 97, 74, 68, 7, 66, 19, 28, 65, 1, 11, + 15, 5, 14, 26, 4, 0, 38, 10, 77, 96, 109, 126, + 126, 126, 126, 3, 39, 30, 26, 16, 25, 13, 7, + 5, 67, 74, 69, 6, 65, 16, 24, 0, 0, 8, 12, 6, + 11, 22, 7, 1, 38, 10, 77, 96, 109, 126, 126, + 126, 126 }, + + { + + 36, + 5, 80, 36, 5, 80, 67, 8, 23, 12, 65, 71, 72, + 29, 59, 14, 7, 69, 5, 12, 1, 69, 0, 75, 89, 1, + 85, 123, 123, 124, 32, 69, 67, 5, 12, 1, 81, + 1, 11, 3, 64, 71, 74, 0, 79, 80, 93, 5, 67, + 72, 4, 74, 71, 84, 0, 74, 72, 81, 2, 2, 22, 0, + 0, 0, 0, 94, 97, 6, 68, 68, 21, 65, 84, 19, + 11, 4, 38, 40, 12, 15, 2, 15, 65, 0, 13, 80, + 78, 79, 83, 24, 65, 11, 11, 68, 78, 71, 2, 2, + 70, 69, 78, 17, 68, 4, 68, 75, 0, 69, 1, 4, 2, + 2, 9, 7, 69, 1, 62, 62, 43, 34, 6, 67, 1, 8, + 0, 11, 8, 1, 72, 17, 68, 88, 85, 30, 9, 13, 5, + 6, 9, 13, 33, 19, 9, 1, 2, 2, 14, 17, 93, 1, + 5, 75, 3, 0, 12, 10, 66, 19, 12, 13, 78, 6, + 69, 67, 10, 89, 10, 14, 11, 4, 67, 10, 4, 11, + 8, 72, 64, 7, 122, 62, 104, 66, 71, 73, 79, + 73, 76, 88, 79, 88, 75, 87, 69, 62, 93, 18, + 21, 15, 11, 10, 9, 5, 5, 3, 72, 74, 73, 77, + 80, 102, 75, 76, 94, 64, 69, 71, 74, 81, 84, + 83, 83, 88, 80, 90, 96, 88, 94, 0, 24, 17, 10, + 8, 10, 3, 64, 65, 9, 7, 31, 21, 17, 12, 18, 9, + 10, 6, 1, 11, 41, 33, 28, 25, 25, 9, 3, 0, 66, + 70, 49, 29, 9, 1, 13, 1, 73, 72, 5, 45, 34, + 19, 10, 20, 5, 1, 67, 69, 62, 81, 75, 67, 70, + 70, 68, 0, 4, 65, 2, 5, 12, 73, 70, 78, 74, 9, + 76, 75, 73, 65, 64, 69, 74, 69, 75, 69, 71, + 83, 9, 9, 20, 4, 65, 9, 6, 2, 5, 4, 1, 70, 0, + 66, 70, 67, 11, 89, 71, 16, 73, 3, 66, 1, 11, + 66, 68, 4, 7, 80, 72, 88, 29, 31, 34, 20, 14, + 18, 15, 13, 11, 4, 6, 4, 70, 70, 77, 75, 70, + 76, 89, 75, 75, 77, 76, 84, 77, 85, 85, 80, + 88, 18, 17, 15, 13, 64, 4, 2, 75, 68, 75, 77, + 92, 85, 91, 97, 73, 72, 100, 68, 70, 78, 90, + 83, 87, 91, 80, 92, 88, 95, 98, 100, 106, 85, + 93, 98, 74, 68, 8, 66, 20, 29, 65, 2, 12, 16, + 5, 14, 27, 4, 0, 37, 8, 80, 99, 112, 126, 126, + 126, 126, 3, 39, 30, 26, 16, 26, 13, 7, 5, 67, + 74, 69, 6, 65, 17, 25, 0, 0, 8, 12, 6, 11, 23, + 7, 1, 37, 8, 80, 99, 112, 126, 126, 126, 126 }, + + { + + 35, + 5, 80, 35, 5, 80, 65, 10, 24, 12, 66, 73, 74, + 28, 59, 14, 9, 69, 6, 13, 1, 69, 0, 75, 90, 0, + 87, 126, 125, 125, 35, 68, 67, 6, 13, 1, 82, + 2, 11, 3, 64, 71, 74, 0, 79, 80, 93, 5, 67, + 71, 4, 74, 70, 84, 0, 74, 72, 81, 2, 2, 22, 0, + 0, 0, 0, 94, 97, 7, 69, 68, 20, 66, 84, 22, + 12, 5, 39, 42, 14, 17, 4, 17, 64, 1, 15, 80, + 78, 78, 83, 24, 65, 12, 13, 68, 77, 71, 4, 2, + 71, 70, 78, 17, 68, 5, 68, 74, 1, 69, 2, 5, 3, + 4, 11, 7, 68, 2, 62, 62, 46, 37, 6, 67, 1, 9, + 0, 11, 9, 2, 71, 19, 68, 90, 87, 33, 9, 13, 5, + 6, 9, 14, 34, 20, 9, 1, 2, 2, 14, 18, 94, 1, + 5, 76, 3, 64, 12, 10, 66, 19, 12, 13, 79, 6, + 70, 68, 10, 89, 10, 14, 11, 2, 70, 9, 2, 9, 5, + 74, 67, 5, 126, 62, 107, 68, 73, 75, 82, 76, + 79, 92, 83, 91, 77, 91, 71, 62, 95, 16, 19, + 13, 8, 7, 7, 3, 3, 1, 74, 76, 75, 79, 81, 104, + 75, 76, 94, 65, 70, 72, 75, 82, 85, 84, 85, + 89, 81, 91, 97, 88, 94, 2, 25, 18, 10, 8, 11, + 3, 64, 65, 11, 8, 31, 21, 18, 13, 19, 10, 11, + 7, 3, 12, 41, 33, 28, 25, 26, 10, 3, 0, 66, + 70, 49, 29, 8, 1, 13, 1, 73, 71, 5, 44, 34, + 18, 9, 20, 5, 1, 67, 69, 62, 80, 73, 66, 69, + 69, 67, 2, 6, 64, 3, 7, 14, 73, 69, 77, 73, + 11, 75, 75, 73, 65, 0, 69, 74, 69, 75, 69, 71, + 84, 10, 10, 21, 4, 65, 10, 6, 2, 5, 4, 1, 70, + 0, 66, 70, 67, 11, 90, 72, 16, 74, 2, 67, 1, + 11, 67, 69, 4, 7, 81, 72, 89, 28, 30, 33, 19, + 12, 16, 13, 11, 8, 2, 3, 1, 73, 72, 79, 79, + 74, 80, 94, 79, 78, 79, 78, 86, 79, 87, 87, + 81, 89, 16, 14, 13, 10, 67, 2, 0, 78, 71, 77, + 80, 95, 87, 93, 98, 74, 73, 101, 70, 72, 80, + 92, 84, 89, 93, 81, 93, 89, 96, 99, 101, 107, + 86, 94, 99, 74, 67, 8, 65, 21, 30, 65, 2, 12, + 16, 5, 15, 28, 4, 0, 36, 6, 82, 102, 115, 126, + 126, 126, 126, 3, 39, 30, 26, 16, 26, 14, 7, + 5, 66, 74, 69, 7, 64, 18, 26, 0, 1, 8, 12, 6, + 11, 23, 7, 1, 36, 6, 82, 102, 115, 126, 126, + 126, 126 }, + + { + + 33, + 5, 80, 33, 5, 80, 64, 11, 24, 12, 66, 74, 76, + 27, 59, 13, 11, 69, 6, 14, 0, 70, 0, 76, 92, + 64, 90, 126, 126, 126, 38, 67, 67, 6, 14, 0, + 82, 3, 11, 2, 64, 70, 73, 0, 79, 80, 93, 5, + 67, 71, 4, 74, 70, 83, 0, 74, 72, 81, 3, 2, + 22, 0, 0, 0, 1, 95, 97, 7, 70, 68, 19, 66, 84, + 24, 13, 6, 41, 44, 15, 18, 5, 18, 1, 2, 17, + 80, 77, 78, 83, 24, 65, 13, 15, 68, 77, 70, 6, + 2, 71, 70, 79, 17, 68, 5, 67, 73, 2, 68, 3, 5, + 3, 5, 12, 8, 67, 3, 62, 62, 49, 40, 6, 66, 1, + 9, 0, 11, 9, 2, 71, 21, 68, 92, 89, 35, 9, 14, + 5, 6, 9, 14, 36, 21, 10, 0, 2, 2, 15, 18, 95, + 1, 5, 76, 2, 65, 11, 9, 67, 19, 12, 13, 80, 6, + 71, 69, 9, 89, 9, 13, 10, 0, 74, 8, 0, 7, 3, + 76, 69, 3, 126, 62, 111, 70, 75, 78, 85, 78, + 83, 97, 87, 94, 79, 94, 73, 62, 96, 14, 17, + 10, 6, 5, 5, 1, 1, 64, 77, 78, 77, 81, 83, + 106, 75, 76, 95, 66, 71, 73, 77, 83, 87, 85, + 86, 90, 81, 92, 97, 89, 93, 3, 26, 18, 10, 9, + 11, 4, 0, 64, 13, 10, 31, 22, 18, 13, 20, 11, + 13, 9, 4, 12, 42, 34, 29, 26, 27, 10, 3, 1, + 65, 70, 50, 29, 8, 0, 13, 1, 73, 71, 5, 44, + 33, 17, 8, 20, 5, 2, 67, 69, 62, 78, 72, 65, + 67, 67, 66, 3, 7, 0, 5, 8, 16, 72, 68, 77, 72, + 12, 75, 75, 73, 64, 0, 69, 75, 69, 76, 69, 71, + 84, 10, 10, 22, 4, 65, 10, 6, 2, 5, 4, 1, 71, + 0, 66, 70, 67, 12, 91, 73, 16, 75, 2, 68, 0, + 11, 68, 70, 4, 7, 82, 72, 90, 27, 29, 32, 17, + 10, 14, 11, 8, 6, 64, 0, 65, 76, 75, 81, 84, + 78, 84, 99, 82, 82, 82, 81, 89, 81, 89, 88, + 82, 89, 13, 12, 10, 8, 70, 64, 66, 81, 73, 80, + 82, 98, 89, 95, 100, 75, 73, 103, 71, 73, 81, + 94, 86, 91, 94, 82, 95, 90, 97, 101, 102, 107, + 88, 96, 101, 73, 67, 9, 65, 22, 31, 65, 3, 13, + 17, 5, 15, 29, 4, 0, 35, 4, 85, 105, 119, 126, + 126, 126, 126, 4, 39, 30, 26, 16, 27, 14, 7, + 5, 66, 74, 69, 7, 64, 18, 27, 0, 1, 9, 13, 6, + 11, 24, 7, 1, 35, 4, 85, 105, 119, 126, 126, + 126, 126 }, + + { + + 32, + 5, 80, 32, 5, 80, 1, 13, 24, 12, 67, 75, 78, + 26, 59, 13, 13, 69, 6, 15, 0, 70, 0, 77, 94, + 65, 92, 126, 126, 126, 41, 66, 66, 6, 15, 0, + 82, 4, 11, 2, 64, 70, 72, 0, 79, 80, 93, 5, + 67, 71, 4, 74, 69, 83, 0, 74, 72, 81, 3, 2, + 22, 0, 0, 0, 1, 95, 97, 8, 71, 68, 18, 67, 84, + 27, 14, 7, 43, 46, 17, 20, 7, 20, 2, 3, 20, + 80, 77, 77, 82, 24, 65, 14, 17, 68, 76, 70, 8, + 2, 72, 71, 79, 17, 68, 6, 67, 72, 3, 68, 4, 6, + 4, 7, 14, 9, 65, 4, 62, 62, 52, 43, 7, 66, 2, + 10, 0, 11, 10, 2, 70, 23, 68, 94, 91, 38, 9, + 14, 5, 6, 9, 15, 37, 22, 10, 0, 2, 2, 15, 19, + 96, 2, 5, 77, 2, 66, 11, 9, 67, 19, 12, 13, + 81, 7, 72, 69, 9, 89, 9, 12, 9, 65, 77, 7, 64, + 5, 1, 78, 72, 1, 126, 62, 114, 72, 77, 80, 88, + 81, 86, 101, 91, 96, 81, 98, 75, 62, 98, 12, + 15, 8, 4, 3, 3, 64, 64, 66, 79, 80, 79, 82, + 85, 108, 75, 76, 95, 67, 72, 74, 78, 84, 88, + 86, 88, 91, 82, 93, 98, 90, 92, 5, 27, 19, 10, + 9, 12, 4, 0, 0, 15, 11, 31, 22, 19, 14, 21, + 12, 14, 10, 6, 13, 43, 35, 30, 26, 28, 11, 4, + 1, 65, 70, 50, 29, 7, 0, 13, 1, 73, 70, 5, 43, + 32, 16, 7, 20, 5, 2, 67, 68, 62, 77, 70, 0, + 65, 66, 65, 5, 9, 1, 7, 10, 18, 71, 67, 76, + 71, 14, 75, 75, 72, 64, 1, 69, 75, 69, 76, 69, + 71, 85, 11, 10, 23, 4, 65, 10, 6, 2, 5, 4, 1, + 71, 0, 66, 70, 67, 12, 92, 74, 16, 76, 1, 68, + 64, 11, 68, 71, 4, 7, 83, 72, 91, 26, 28, 31, + 15, 8, 12, 9, 6, 3, 67, 66, 68, 79, 77, 83, + 88, 82, 88, 104, 85, 85, 84, 83, 91, 83, 90, + 90, 82, 90, 11, 9, 8, 6, 73, 67, 68, 84, 75, + 82, 84, 101, 91, 97, 101, 75, 74, 105, 72, 75, + 83, 96, 88, 93, 96, 83, 96, 91, 97, 102, 103, + 108, 89, 97, 102, 73, 67, 10, 64, 23, 32, 64, + 4, 13, 17, 5, 16, 30, 4, 0, 34, 2, 88, 108, + 122, 126, 126, 126, 126, 4, 39, 30, 26, 16, + 27, 14, 8, 6, 65, 74, 69, 8, 64, 19, 28, 0, 1, + 9, 13, 6, 12, 24, 7, 1, 34, 2, 88, 108, 122, + 126, 126, 126, 126 }, + + { + + 31, + 5, 81, 31, 5, 81, 3, 14, 25, 12, 67, 77, 80, + 25, 59, 13, 14, 69, 7, 15, 0, 71, 1, 77, 95, + 67, 95, 126, 126, 126, 43, 65, 66, 7, 15, 0, + 83, 4, 11, 1, 64, 70, 72, 0, 79, 79, 92, 5, + 66, 70, 4, 73, 69, 83, 0, 74, 72, 81, 3, 2, + 22, 0, 0, 0, 2, 95, 97, 8, 71, 69, 18, 67, 84, + 29, 15, 8, 44, 49, 18, 21, 9, 21, 3, 4, 22, + 80, 77, 77, 82, 24, 65, 15, 20, 68, 75, 69, + 10, 1, 72, 71, 80, 18, 68, 6, 66, 72, 3, 67, + 5, 6, 5, 8, 15, 9, 64, 5, 62, 62, 55, 46, 7, + 66, 2, 10, 0, 11, 10, 3, 70, 25, 67, 96, 93, + 41, 9, 14, 5, 6, 10, 15, 39, 23, 10, 0, 2, 2, + 16, 20, 97, 2, 5, 77, 1, 67, 10, 9, 68, 19, + 12, 13, 83, 7, 73, 70, 8, 89, 9, 12, 9, 67, + 80, 6, 66, 2, 65, 80, 74, 64, 126, 62, 118, + 74, 78, 82, 92, 83, 89, 105, 96, 99, 83, 101, + 77, 62, 100, 10, 13, 6, 1, 0, 1, 67, 66, 68, + 81, 83, 81, 84, 86, 109, 75, 76, 96, 68, 73, + 75, 79, 85, 89, 87, 90, 92, 82, 94, 99, 90, + 92, 6, 28, 19, 10, 9, 13, 5, 1, 0, 17, 12, 32, + 22, 19, 15, 22, 13, 16, 12, 8, 14, 43, 35, 30, + 27, 29, 11, 4, 1, 64, 70, 51, 29, 6, 0, 13, 1, + 73, 70, 4, 43, 32, 15, 6, 19, 5, 2, 67, 68, + 62, 76, 69, 1, 64, 64, 64, 7, 11, 2, 8, 12, + 20, 71, 66, 76, 70, 16, 74, 75, 72, 0, 1, 69, + 75, 69, 77, 69, 71, 85, 11, 11, 24, 4, 65, 11, + 7, 2, 6, 5, 1, 72, 0, 66, 70, 67, 13, 93, 75, + 16, 77, 0, 69, 64, 11, 69, 71, 4, 6, 85, 73, + 92, 24, 27, 30, 14, 6, 10, 7, 4, 1, 69, 69, + 70, 82, 80, 85, 92, 86, 92, 108, 89, 88, 87, + 85, 94, 85, 92, 91, 83, 91, 9, 7, 5, 3, 76, + 69, 71, 87, 78, 85, 87, 104, 93, 98, 103, 76, + 75, 106, 74, 76, 85, 98, 89, 95, 98, 84, 98, + 92, 98, 103, 104, 109, 91, 98, 103, 73, 66, + 10, 64, 24, 33, 64, 4, 14, 18, 5, 16, 31, 4, + 0, 33, 0, 90, 111, 125, 126, 126, 126, 126, 4, + 39, 30, 26, 16, 28, 15, 8, 6, 65, 73, 68, 8, + 0, 20, 29, 1, 2, 9, 13, 6, 12, 25, 7, 1, 33, + 0, 90, 111, 125, 126, 126, 126, 126 }, + + { + + 30, + 5, 81, 30, 5, 81, 5, 16, 25, 12, 68, 78, 82, + 24, 59, 13, 16, 69, 7, 16, 64, 71, 1, 78, 97, + 68, 97, 126, 126, 126, 46, 64, 66, 7, 16, 64, + 83, 5, 11, 1, 64, 69, 71, 0, 79, 79, 92, 5, + 66, 70, 4, 73, 68, 82, 0, 74, 72, 81, 4, 2, + 22, 0, 0, 0, 2, 95, 97, 9, 72, 69, 17, 68, 84, + 32, 16, 9, 46, 51, 20, 23, 10, 23, 5, 5, 24, + 80, 76, 76, 82, 24, 65, 16, 22, 68, 74, 69, + 12, 1, 73, 72, 80, 18, 68, 7, 66, 71, 4, 67, + 6, 7, 6, 10, 17, 10, 0, 6, 62, 62, 58, 49, 7, + 65, 2, 11, 0, 11, 11, 3, 69, 27, 67, 98, 95, + 44, 9, 15, 5, 6, 10, 16, 40, 24, 11, 64, 2, 2, + 16, 20, 98, 2, 5, 78, 1, 68, 10, 8, 68, 19, + 12, 13, 84, 7, 74, 71, 8, 89, 9, 11, 8, 69, + 83, 5, 68, 0, 67, 82, 77, 66, 126, 62, 121, + 76, 80, 85, 95, 86, 92, 110, 100, 102, 85, + 105, 79, 62, 101, 8, 11, 4, 64, 65, 64, 69, + 68, 70, 84, 85, 83, 86, 88, 111, 75, 76, 96, + 69, 74, 76, 81, 86, 90, 88, 91, 93, 83, 95, + 99, 91, 91, 8, 29, 20, 10, 10, 13, 5, 1, 1, + 19, 14, 32, 23, 20, 15, 23, 14, 17, 13, 10, + 14, 44, 36, 31, 27, 30, 12, 4, 2, 64, 70, 51, + 29, 6, 64, 13, 1, 73, 69, 4, 42, 31, 14, 5, + 19, 5, 3, 67, 68, 62, 74, 67, 2, 1, 0, 0, 8, + 12, 3, 10, 13, 22, 70, 65, 75, 69, 18, 74, 75, + 72, 0, 2, 69, 76, 69, 77, 69, 71, 86, 12, 11, + 25, 4, 65, 11, 7, 2, 6, 5, 1, 72, 0, 66, 70, + 67, 13, 94, 76, 16, 78, 0, 70, 65, 11, 70, 72, + 4, 6, 86, 73, 93, 23, 26, 29, 12, 4, 8, 5, 1, + 65, 72, 72, 73, 85, 82, 87, 97, 90, 96, 113, + 92, 91, 89, 87, 96, 87, 94, 93, 84, 91, 6, 4, + 3, 1, 79, 72, 73, 90, 80, 87, 89, 107, 95, + 100, 104, 77, 75, 108, 75, 78, 86, 100, 91, + 97, 99, 85, 99, 93, 99, 105, 105, 109, 92, + 100, 105, 72, 66, 11, 0, 25, 34, 64, 5, 14, + 18, 5, 17, 32, 4, 0, 32, 65, 93, 114, 126, + 126, 126, 126, 126, 5, 39, 30, 26, 16, 28, 15, + 8, 6, 64, 73, 68, 9, 0, 21, 30, 1, 2, 10, 14, + 6, 12, 25, 7, 1, 32, 65, 93, 114, 126, 126, + 126, 126, 126 }, + + { + + 28, + 4, 81, 28, 4, 81, 6, 17, 25, 12, 68, 80, 85, + 23, 59, 12, 18, 70, 7, 17, 64, 72, 1, 79, 99, + 69, 100, 126, 126, 126, 49, 0, 66, 7, 17, 64, + 84, 6, 11, 0, 64, 69, 71, 64, 80, 79, 92, 5, + 66, 70, 4, 73, 68, 82, 0, 74, 72, 81, 4, 2, + 22, 0, 0, 0, 3, 96, 97, 9, 73, 69, 16, 68, 85, + 34, 17, 10, 47, 53, 21, 24, 12, 24, 6, 6, 26, + 80, 76, 76, 82, 24, 65, 17, 24, 68, 74, 68, + 14, 1, 73, 72, 81, 18, 68, 7, 65, 70, 5, 66, + 6, 7, 6, 11, 18, 10, 1, 7, 62, 62, 61, 51, 7, + 65, 2, 11, 64, 11, 11, 3, 69, 29, 67, 100, 97, + 46, 9, 15, 5, 6, 10, 16, 42, 24, 11, 64, 1, 2, + 17, 21, 100, 2, 5, 78, 0, 70, 9, 8, 69, 19, + 12, 12, 85, 7, 75, 72, 7, 89, 8, 10, 7, 71, + 87, 3, 70, 65, 70, 85, 79, 68, 126, 62, 125, + 78, 82, 87, 98, 88, 96, 114, 104, 105, 87, + 108, 81, 62, 103, 6, 8, 1, 67, 68, 67, 71, 71, + 72, 86, 87, 85, 88, 90, 113, 75, 77, 97, 70, + 76, 77, 82, 87, 92, 90, 93, 94, 83, 96, 100, + 92, 91, 9, 30, 20, 10, 10, 14, 6, 2, 1, 21, + 15, 32, 23, 20, 16, 24, 15, 19, 15, 11, 15, + 44, 36, 31, 28, 31, 12, 4, 2, 0, 71, 52, 29, + 5, 64, 13, 1, 73, 69, 4, 42, 30, 13, 4, 19, 5, + 3, 67, 68, 62, 73, 66, 3, 2, 2, 1, 10, 14, 4, + 11, 15, 24, 70, 65, 75, 68, 19, 74, 75, 72, 1, + 2, 69, 76, 69, 78, 69, 71, 86, 12, 11, 26, 4, + 66, 11, 7, 1, 6, 5, 1, 73, 0, 66, 70, 67, 14, + 95, 77, 16, 80, 64, 71, 66, 10, 71, 73, 4, 6, + 87, 73, 95, 22, 24, 28, 10, 2, 6, 3, 64, 67, + 75, 75, 76, 88, 85, 89, 101, 94, 101, 118, 96, + 95, 92, 90, 99, 89, 96, 94, 85, 92, 4, 2, 0, + 65, 82, 75, 76, 93, 83, 90, 92, 110, 97, 102, + 106, 78, 76, 110, 77, 79, 88, 102, 93, 99, + 101, 87, 101, 95, 100, 106, 106, 110, 94, 101, + 106, 72, 66, 11, 0, 26, 35, 64, 5, 15, 19, 5, + 17, 32, 4, 64, 31, 67, 96, 117, 126, 126, 126, + 126, 126, 5, 39, 30, 26, 16, 29, 15, 8, 6, 64, + 73, 68, 9, 0, 21, 30, 1, 2, 10, 14, 6, 12, 26, + 7, 0, 31, 67, 96, 117, 126, 126, 126, 126, 126 }, + + { + + 27, + 4, 81, 27, 4, 81, 8, 18, 26, 12, 68, 81, 87, + 22, 60, 12, 20, 70, 8, 18, 64, 73, 1, 79, 100, + 70, 102, 126, 126, 126, 52, 1, 65, 8, 18, 64, + 84, 7, 11, 0, 0, 69, 70, 64, 80, 79, 92, 5, + 66, 69, 4, 73, 68, 82, 0, 74, 72, 80, 4, 2, + 22, 0, 0, 0, 4, 96, 97, 9, 74, 69, 15, 68, 85, + 36, 19, 11, 49, 55, 23, 25, 14, 26, 7, 8, 29, + 80, 76, 76, 81, 24, 65, 18, 26, 67, 73, 67, + 16, 1, 73, 72, 81, 18, 68, 7, 64, 69, 6, 65, + 7, 8, 7, 12, 20, 11, 3, 8, 62, 62, 62, 54, 8, + 65, 3, 12, 64, 11, 11, 4, 68, 32, 67, 102, 98, + 49, 9, 15, 5, 6, 10, 17, 44, 25, 11, 64, 1, 2, + 18, 22, 101, 3, 5, 78, 64, 71, 8, 8, 70, 19, + 12, 12, 86, 8, 76, 72, 6, 89, 8, 10, 7, 73, + 90, 2, 71, 67, 72, 87, 81, 70, 126, 62, 126, + 80, 84, 89, 101, 90, 99, 118, 108, 107, 89, + 111, 83, 62, 105, 4, 6, 64, 69, 70, 69, 73, + 73, 74, 88, 89, 86, 89, 91, 115, 75, 77, 97, + 70, 77, 78, 83, 88, 93, 91, 95, 95, 83, 97, + 101, 92, 90, 10, 31, 20, 10, 10, 15, 7, 3, 2, + 24, 16, 32, 23, 20, 17, 25, 16, 21, 17, 13, + 16, 45, 37, 32, 29, 32, 12, 5, 2, 1, 71, 53, + 29, 4, 64, 14, 2, 73, 68, 4, 42, 30, 12, 3, + 19, 5, 3, 67, 67, 62, 72, 65, 5, 4, 4, 2, 12, + 16, 5, 13, 17, 26, 69, 64, 74, 67, 21, 73, 74, + 71, 2, 3, 69, 76, 69, 79, 69, 71, 86, 12, 12, + 27, 5, 66, 12, 7, 1, 6, 5, 1, 74, 0, 65, 69, + 67, 15, 95, 78, 16, 81, 65, 71, 66, 10, 71, + 74, 4, 6, 88, 73, 96, 21, 23, 28, 9, 0, 4, 1, + 66, 69, 77, 78, 79, 91, 88, 91, 105, 98, 105, + 123, 99, 98, 95, 92, 101, 90, 97, 95, 85, 93, + 2, 0, 65, 67, 84, 77, 78, 96, 85, 92, 94, 112, + 99, 104, 108, 78, 77, 111, 78, 80, 90, 104, + 94, 100, 103, 88, 103, 96, 100, 107, 106, 111, + 96, 102, 107, 72, 65, 12, 0, 27, 37, 0, 6, 16, + 20, 5, 18, 33, 4, 64, 30, 69, 98, 120, 126, + 126, 126, 126, 126, 5, 39, 30, 27, 17, 30, 16, + 9, 7, 64, 73, 68, 9, 1, 22, 31, 1, 3, 10, 14, + 6, 13, 27, 7, 0, 30, 69, 98, 120, 126, 126, + 126, 126, 126 }, + + { + + 26, + 4, 81, 26, 4, 81, 10, 20, 26, 12, 69, 82, 89, + 21, 60, 12, 22, 70, 8, 19, 65, 73, 1, 80, 102, + 71, 105, 126, 126, 126, 55, 2, 65, 8, 19, 65, + 84, 8, 11, 64, 0, 68, 69, 64, 80, 79, 92, 5, + 66, 69, 4, 73, 67, 81, 0, 74, 72, 80, 5, 2, + 22, 0, 0, 0, 4, 96, 97, 10, 75, 69, 14, 69, + 85, 39, 20, 12, 51, 57, 24, 27, 15, 27, 9, 9, + 31, 80, 75, 75, 81, 24, 65, 19, 28, 67, 72, + 67, 18, 1, 74, 73, 82, 18, 68, 8, 64, 68, 7, + 65, 8, 8, 8, 14, 21, 12, 4, 9, 62, 62, 62, 57, + 8, 64, 3, 12, 64, 11, 12, 4, 68, 34, 67, 104, + 100, 52, 9, 16, 5, 6, 10, 17, 45, 26, 12, 65, + 1, 2, 18, 22, 102, 3, 5, 79, 64, 72, 8, 7, 70, + 19, 12, 12, 87, 8, 77, 73, 6, 89, 8, 9, 6, 75, + 93, 1, 73, 69, 74, 89, 84, 72, 126, 62, 126, + 82, 86, 92, 104, 93, 102, 123, 112, 110, 91, + 115, 85, 62, 106, 2, 4, 66, 71, 72, 71, 75, + 75, 76, 91, 91, 88, 91, 93, 117, 75, 77, 98, + 71, 78, 79, 85, 89, 94, 92, 96, 96, 84, 98, + 101, 93, 89, 12, 32, 21, 10, 11, 15, 7, 3, 3, + 26, 18, 32, 24, 21, 17, 26, 17, 22, 18, 15, + 16, 46, 38, 33, 29, 33, 13, 5, 3, 1, 71, 53, + 29, 4, 65, 14, 2, 73, 68, 4, 41, 29, 11, 2, + 19, 5, 4, 67, 67, 62, 70, 0, 6, 6, 5, 3, 13, + 17, 6, 15, 18, 28, 68, 0, 74, 66, 23, 73, 74, + 71, 2, 3, 69, 77, 69, 79, 69, 71, 87, 13, 12, + 28, 5, 66, 12, 7, 1, 6, 5, 1, 74, 0, 65, 69, + 67, 15, 96, 79, 16, 82, 65, 72, 67, 10, 72, + 75, 4, 6, 89, 73, 97, 20, 22, 27, 7, 65, 2, + 64, 69, 72, 80, 81, 82, 94, 90, 93, 110, 102, + 109, 126, 102, 101, 97, 94, 104, 92, 99, 97, + 86, 93, 64, 66, 68, 69, 87, 80, 81, 99, 87, + 95, 96, 115, 101, 106, 109, 79, 77, 113, 79, + 82, 91, 106, 96, 102, 104, 89, 104, 97, 101, + 109, 107, 111, 97, 104, 109, 71, 65, 13, 1, + 28, 38, 0, 7, 16, 20, 5, 18, 34, 4, 64, 29, + 71, 101, 123, 126, 126, 126, 126, 126, 6, 39, + 30, 27, 17, 30, 16, 9, 7, 0, 73, 68, 10, 1, + 23, 32, 1, 3, 11, 15, 6, 13, 27, 7, 0, 29, 71, + 101, 123, 126, 126, 126, 126, 126 }, + + { + + 25, + 4, 82, 25, 4, 82, 12, 21, 27, 12, 69, 84, 91, + 20, 60, 12, 23, 70, 9, 19, 65, 74, 2, 80, 103, + 73, 107, 126, 126, 126, 57, 3, 65, 9, 19, 65, + 85, 8, 11, 64, 0, 68, 69, 64, 80, 78, 91, 5, + 65, 68, 4, 72, 67, 81, 0, 74, 72, 80, 5, 2, + 22, 0, 0, 0, 5, 96, 97, 10, 75, 70, 14, 69, + 85, 41, 21, 13, 52, 60, 26, 28, 17, 29, 10, + 10, 33, 80, 75, 75, 81, 24, 65, 20, 31, 67, + 71, 66, 20, 0, 74, 73, 82, 19, 68, 8, 0, 68, + 7, 64, 9, 9, 9, 15, 23, 12, 5, 10, 62, 62, 62, + 60, 8, 64, 3, 13, 64, 11, 12, 5, 67, 36, 66, + 106, 102, 55, 9, 16, 5, 6, 11, 18, 47, 27, 12, + 65, 1, 2, 19, 23, 103, 3, 5, 79, 65, 73, 7, 7, + 71, 19, 12, 12, 89, 8, 78, 74, 5, 89, 8, 9, 6, + 77, 96, 0, 75, 72, 77, 91, 86, 74, 126, 62, + 126, 84, 87, 94, 108, 95, 105, 126, 117, 113, + 93, 118, 87, 62, 108, 0, 2, 68, 74, 75, 73, + 78, 77, 78, 93, 94, 90, 93, 94, 118, 75, 77, + 98, 72, 79, 80, 86, 90, 95, 93, 98, 97, 84, + 99, 102, 93, 89, 13, 33, 21, 10, 11, 16, 8, 4, + 3, 28, 19, 33, 24, 21, 18, 27, 18, 24, 20, 17, + 17, 46, 38, 33, 30, 34, 13, 5, 3, 2, 71, 54, + 29, 3, 65, 14, 2, 73, 67, 3, 41, 29, 10, 1, + 18, 5, 4, 67, 67, 62, 69, 1, 7, 7, 7, 4, 15, + 19, 7, 16, 20, 30, 68, 1, 73, 65, 25, 72, 74, + 71, 3, 4, 69, 77, 69, 80, 69, 71, 87, 13, 13, + 29, 5, 66, 13, 8, 1, 7, 6, 1, 75, 0, 65, 69, + 67, 16, 97, 80, 16, 83, 66, 73, 67, 10, 73, + 75, 4, 5, 91, 74, 98, 18, 21, 26, 6, 67, 0, + 66, 71, 74, 82, 84, 84, 97, 93, 95, 114, 106, + 113, 126, 106, 104, 100, 96, 106, 94, 101, 98, + 87, 94, 66, 68, 70, 72, 90, 82, 83, 102, 90, + 97, 99, 118, 103, 107, 111, 80, 78, 114, 81, + 83, 93, 108, 97, 104, 106, 90, 106, 98, 102, + 110, 108, 112, 99, 105, 110, 71, 64, 13, 1, + 29, 39, 0, 7, 17, 21, 5, 19, 35, 4, 64, 28, + 73, 103, 126, 126, 126, 126, 126, 126, 6, 39, + 30, 27, 17, 31, 17, 9, 7, 0, 72, 67, 10, 2, + 24, 33, 2, 4, 11, 15, 6, 13, 28, 7, 0, 28, 73, + 103, 126, 126, 126, 126, 126, 126 }, + + { + + 23, + 4, 82, 23, 4, 82, 13, 23, 27, 12, 70, 85, 93, + 19, 60, 11, 25, 70, 9, 20, 65, 74, 2, 81, 105, + 74, 110, 126, 126, 126, 60, 4, 65, 9, 20, 65, + 85, 9, 11, 65, 0, 68, 68, 64, 80, 78, 91, 5, + 65, 68, 4, 72, 66, 81, 0, 74, 72, 80, 5, 2, + 22, 0, 0, 0, 5, 97, 97, 11, 76, 70, 13, 70, + 85, 44, 22, 14, 54, 62, 27, 30, 19, 30, 11, + 11, 35, 80, 75, 74, 81, 24, 65, 21, 33, 67, + 71, 66, 22, 0, 75, 74, 83, 19, 68, 9, 0, 67, + 8, 64, 10, 9, 9, 17, 24, 13, 6, 11, 62, 62, + 62, 62, 8, 64, 3, 13, 64, 11, 13, 5, 67, 38, + 66, 108, 104, 57, 9, 16, 5, 6, 11, 18, 48, 28, + 12, 65, 1, 2, 19, 24, 104, 3, 5, 80, 65, 74, + 7, 7, 71, 19, 12, 12, 90, 8, 79, 75, 5, 89, 7, + 8, 5, 79, 100, 64, 77, 74, 79, 93, 89, 76, + 126, 62, 126, 86, 89, 96, 111, 98, 109, 126, + 121, 116, 95, 122, 89, 62, 110, 65, 0, 71, 76, + 77, 75, 80, 79, 80, 95, 96, 92, 95, 96, 120, + 75, 77, 99, 73, 80, 81, 87, 91, 97, 94, 100, + 98, 85, 100, 103, 94, 88, 15, 34, 22, 10, 11, + 17, 8, 4, 4, 30, 20, 33, 24, 22, 19, 28, 19, + 25, 21, 18, 18, 47, 39, 34, 30, 35, 14, 5, 3, + 2, 71, 54, 29, 2, 65, 14, 2, 73, 67, 3, 40, + 28, 9, 0, 18, 5, 4, 67, 67, 62, 68, 3, 8, 9, + 8, 5, 17, 21, 8, 18, 22, 32, 67, 2, 73, 64, + 26, 72, 74, 71, 3, 4, 69, 77, 69, 80, 69, 71, + 88, 14, 13, 30, 5, 66, 13, 8, 1, 7, 6, 1, 75, + 0, 65, 69, 67, 16, 98, 81, 16, 84, 67, 74, 68, + 10, 74, 76, 4, 5, 92, 74, 99, 17, 20, 25, 4, + 69, 65, 68, 73, 77, 85, 87, 87, 100, 95, 97, + 118, 110, 117, 126, 109, 108, 102, 99, 109, + 96, 103, 100, 88, 95, 68, 71, 73, 74, 93, 85, + 86, 105, 92, 100, 101, 121, 105, 109, 112, 81, + 79, 116, 82, 85, 95, 110, 99, 106, 108, 91, + 107, 99, 103, 111, 109, 113, 100, 106, 111, + 71, 64, 14, 2, 30, 40, 0, 8, 17, 21, 5, 19, + 36, 4, 64, 27, 75, 106, 126, 126, 126, 126, + 126, 126, 6, 39, 30, 27, 17, 31, 17, 9, 7, 1, + 72, 67, 11, 2, 24, 34, 2, 4, 11, 15, 6, 13, + 28, 7, 0, 27, 75, 106, 126, 126, 126, 126, + 126, 126 }, + + { + + 22, + 4, 82, 22, 4, 82, 15, 24, 27, 12, 70, 86, 95, + 18, 60, 11, 27, 70, 9, 21, 66, 75, 2, 82, 107, + 75, 112, 126, 126, 126, 62, 5, 64, 9, 21, 66, + 85, 10, 11, 65, 0, 67, 67, 64, 80, 78, 91, 5, + 65, 68, 4, 72, 66, 80, 0, 74, 72, 80, 6, 2, + 22, 0, 0, 0, 6, 97, 97, 11, 77, 70, 12, 70, + 85, 46, 23, 15, 56, 62, 29, 31, 20, 32, 13, + 12, 38, 80, 74, 74, 80, 24, 65, 22, 35, 67, + 70, 65, 24, 0, 75, 74, 83, 19, 68, 9, 1, 66, + 9, 0, 11, 10, 10, 18, 26, 14, 8, 12, 62, 62, + 62, 62, 9, 0, 4, 14, 64, 11, 13, 5, 66, 40, + 66, 110, 106, 60, 9, 17, 5, 6, 11, 19, 50, 29, + 13, 66, 1, 2, 20, 24, 105, 4, 5, 80, 66, 75, + 6, 6, 72, 19, 12, 12, 91, 9, 80, 75, 4, 89, 7, + 7, 4, 81, 103, 65, 78, 76, 81, 95, 91, 78, + 126, 62, 126, 88, 91, 99, 114, 100, 112, 126, + 125, 118, 97, 125, 91, 62, 111, 67, 65, 73, + 78, 79, 77, 82, 81, 82, 98, 98, 94, 96, 98, + 122, 75, 77, 99, 74, 81, 82, 89, 92, 98, 95, + 101, 99, 85, 101, 103, 95, 87, 16, 35, 22, 10, + 12, 17, 9, 5, 5, 32, 22, 33, 25, 22, 19, 29, + 20, 27, 23, 20, 18, 48, 40, 35, 31, 36, 14, 6, + 4, 3, 71, 55, 29, 2, 66, 14, 2, 73, 66, 3, 40, + 27, 8, 64, 18, 5, 5, 67, 66, 62, 66, 4, 10, + 11, 10, 6, 18, 22, 9, 20, 23, 34, 66, 3, 72, + 0, 28, 72, 74, 70, 4, 5, 69, 78, 69, 81, 69, + 71, 88, 14, 13, 31, 5, 66, 13, 8, 1, 7, 6, 1, + 76, 0, 65, 69, 67, 17, 99, 82, 16, 85, 67, 74, + 69, 10, 74, 77, 4, 5, 93, 74, 100, 16, 19, 24, + 2, 71, 67, 70, 76, 79, 88, 90, 90, 103, 98, + 99, 123, 114, 121, 126, 112, 111, 105, 101, + 111, 98, 104, 101, 88, 95, 71, 73, 75, 76, 96, + 88, 88, 108, 94, 102, 103, 124, 107, 111, 114, + 81, 79, 118, 83, 86, 96, 112, 101, 108, 109, + 92, 109, 100, 103, 113, 110, 113, 102, 108, + 113, 70, 64, 15, 2, 31, 41, 1, 9, 18, 22, 5, + 20, 37, 4, 64, 26, 77, 109, 126, 126, 126, + 126, 126, 126, 7, 39, 30, 27, 17, 32, 17, 10, + 8, 1, 72, 67, 11, 2, 25, 35, 2, 4, 12, 16, 6, + 14, 29, 7, 0, 26, 77, 109, 126, 126, 126, 126, + 126, 126 }, + + { + + 21, + 4, 82, 21, 4, 82, 17, 26, 28, 12, 71, 88, 97, + 17, 60, 11, 29, 70, 10, 22, 66, 75, 2, 82, + 108, 76, 115, 126, 126, 126, 62, 6, 64, 10, + 22, 66, 86, 11, 11, 66, 0, 67, 67, 64, 80, 78, + 91, 5, 65, 67, 4, 72, 65, 80, 0, 74, 72, 80, + 6, 2, 22, 0, 0, 0, 6, 97, 97, 12, 78, 70, 11, + 71, 85, 49, 24, 16, 57, 62, 30, 33, 22, 33, + 14, 13, 40, 80, 74, 73, 80, 24, 65, 23, 37, + 67, 69, 65, 26, 0, 76, 75, 84, 19, 68, 10, 1, + 65, 10, 0, 12, 10, 11, 20, 27, 14, 9, 13, 62, + 62, 62, 62, 9, 0, 4, 14, 64, 11, 14, 6, 66, + 42, 66, 112, 108, 62, 9, 17, 5, 6, 11, 19, 51, + 30, 13, 66, 1, 2, 20, 25, 106, 4, 5, 81, 66, + 76, 6, 6, 72, 19, 12, 12, 92, 9, 81, 76, 4, + 89, 7, 7, 4, 83, 106, 66, 80, 78, 84, 97, 94, + 80, 126, 62, 126, 90, 93, 101, 117, 103, 115, + 126, 126, 121, 99, 126, 93, 62, 113, 69, 67, + 75, 81, 82, 79, 84, 83, 84, 100, 100, 96, 98, + 99, 124, 75, 77, 100, 75, 82, 83, 90, 93, 99, + 96, 103, 100, 86, 102, 104, 95, 87, 18, 36, + 23, 10, 12, 18, 9, 5, 5, 34, 23, 33, 25, 23, + 20, 30, 21, 28, 24, 22, 19, 48, 40, 35, 31, + 37, 15, 6, 4, 3, 71, 55, 29, 1, 66, 14, 2, 73, + 66, 3, 39, 27, 7, 65, 18, 5, 5, 67, 66, 62, + 65, 6, 11, 12, 11, 7, 20, 24, 10, 21, 25, 36, + 66, 4, 72, 1, 30, 71, 74, 70, 4, 5, 69, 78, + 69, 81, 69, 71, 89, 15, 14, 32, 5, 66, 14, 8, + 1, 7, 6, 1, 76, 0, 65, 69, 67, 17, 100, 83, + 16, 86, 68, 75, 69, 10, 75, 78, 4, 5, 94, 74, + 101, 15, 18, 23, 1, 73, 69, 72, 78, 82, 90, + 93, 93, 106, 100, 101, 126, 118, 125, 126, + 116, 114, 107, 103, 114, 100, 106, 103, 89, + 96, 73, 76, 78, 79, 99, 90, 91, 111, 97, 105, + 106, 126, 109, 113, 115, 82, 80, 119, 85, 88, + 98, 114, 102, 110, 111, 93, 110, 101, 104, + 114, 111, 114, 103, 109, 114, 70, 0, 15, 3, + 32, 42, 1, 9, 18, 22, 5, 20, 38, 4, 64, 25, + 79, 111, 126, 126, 126, 126, 126, 126, 7, 39, + 30, 27, 17, 32, 18, 10, 8, 2, 72, 67, 12, 3, + 26, 36, 2, 5, 12, 16, 6, 14, 29, 7, 0, 25, 79, + 111, 126, 126, 126, 126, 126, 126 }, + + { + + 20, + 4, 82, 20, 4, 82, 19, 27, 28, 12, 71, 89, 99, + 16, 60, 11, 31, 70, 10, 23, 66, 76, 2, 83, + 110, 77, 117, 126, 126, 126, 62, 7, 64, 10, + 23, 66, 86, 12, 11, 66, 0, 67, 66, 64, 80, 78, + 91, 5, 65, 67, 4, 72, 65, 80, 0, 74, 72, 80, + 6, 2, 22, 0, 0, 0, 7, 97, 97, 12, 79, 70, 10, + 71, 85, 51, 25, 17, 59, 62, 32, 34, 24, 35, + 15, 14, 42, 80, 74, 73, 80, 24, 65, 24, 39, + 67, 68, 64, 28, 0, 76, 75, 84, 19, 68, 10, 2, + 64, 11, 1, 13, 11, 12, 21, 29, 15, 10, 14, 62, + 62, 62, 62, 9, 0, 4, 15, 64, 11, 14, 6, 65, + 44, 66, 114, 110, 62, 9, 17, 5, 6, 11, 20, 53, + 31, 13, 66, 1, 2, 21, 26, 107, 4, 5, 81, 67, + 77, 5, 6, 73, 19, 12, 12, 93, 9, 82, 77, 3, + 89, 7, 6, 3, 85, 109, 67, 82, 80, 86, 99, 96, + 82, 126, 62, 126, 92, 95, 103, 120, 105, 118, + 126, 126, 124, 101, 126, 95, 62, 115, 71, 69, + 77, 83, 84, 81, 86, 85, 86, 102, 102, 98, 100, + 101, 126, 75, 77, 100, 76, 83, 84, 91, 94, + 100, 97, 105, 101, 86, 103, 105, 96, 86, 19, + 37, 23, 10, 12, 19, 10, 6, 6, 36, 24, 33, 25, + 23, 21, 31, 22, 30, 26, 24, 20, 49, 41, 36, + 32, 38, 15, 6, 4, 4, 71, 56, 29, 0, 66, 14, 2, + 73, 65, 3, 39, 26, 6, 66, 18, 5, 5, 67, 66, + 62, 64, 7, 12, 14, 13, 8, 22, 26, 11, 23, 27, + 38, 65, 5, 71, 2, 32, 71, 74, 70, 5, 6, 69, + 78, 69, 82, 69, 71, 89, 15, 14, 33, 5, 66, 14, + 8, 1, 7, 6, 1, 77, 0, 65, 69, 67, 18, 101, 84, + 16, 87, 69, 76, 70, 10, 76, 79, 4, 5, 95, 74, + 102, 14, 17, 22, 64, 75, 71, 74, 80, 84, 93, + 96, 96, 109, 103, 103, 126, 122, 126, 126, + 119, 117, 110, 105, 116, 102, 108, 104, 90, + 97, 75, 78, 80, 81, 102, 93, 93, 114, 99, 107, + 108, 126, 111, 115, 117, 83, 81, 121, 86, 89, + 100, 116, 104, 112, 113, 94, 112, 102, 105, + 115, 112, 115, 105, 110, 115, 70, 0, 16, 3, + 33, 43, 1, 10, 19, 23, 5, 21, 39, 4, 64, 24, + 81, 114, 126, 126, 126, 126, 126, 126, 7, 39, + 30, 27, 17, 33, 18, 10, 8, 2, 72, 67, 12, 3, + 27, 37, 2, 5, 12, 16, 6, 14, 30, 7, 0, 24, 81, + 114, 126, 126, 126, 126, 126, 126 }, + + { + + 18, + 3, 83, 18, 3, 83, 20, 28, 28, 12, 72, 91, 102, + 15, 60, 10, 32, 71, 10, 23, 67, 77, 2, 84, + 112, 79, 120, 126, 126, 126, 62, 7, 64, 10, + 23, 67, 87, 12, 11, 67, 0, 67, 66, 65, 81, 78, + 91, 4, 65, 67, 4, 72, 65, 80, 0, 74, 73, 80, + 6, 2, 22, 0, 0, 0, 7, 98, 97, 12, 80, 71, 9, + 72, 86, 53, 26, 18, 60, 62, 33, 35, 25, 36, + 16, 15, 44, 80, 74, 73, 80, 24, 65, 24, 41, + 67, 68, 64, 29, 64, 77, 76, 85, 19, 68, 10, 2, + 64, 11, 1, 13, 11, 12, 22, 30, 15, 11, 15, 62, + 62, 62, 62, 9, 0, 4, 15, 65, 11, 14, 6, 65, + 46, 66, 116, 112, 62, 9, 17, 5, 6, 11, 20, 54, + 31, 13, 67, 0, 2, 21, 26, 109, 4, 5, 82, 68, + 79, 4, 5, 74, 19, 12, 11, 95, 9, 83, 78, 2, + 89, 6, 5, 2, 88, 113, 69, 84, 83, 89, 102, 99, + 84, 126, 62, 126, 95, 97, 106, 124, 108, 122, + 126, 126, 126, 103, 126, 97, 62, 117, 74, 72, + 80, 86, 87, 84, 89, 88, 88, 105, 105, 100, + 102, 103, 126, 75, 78, 101, 77, 85, 86, 93, + 96, 102, 99, 107, 102, 87, 104, 106, 97, 86, + 20, 37, 23, 10, 12, 19, 10, 6, 6, 38, 25, 33, + 25, 23, 21, 31, 23, 31, 27, 25, 20, 49, 41, + 36, 32, 39, 15, 6, 4, 4, 72, 56, 28, 64, 67, + 14, 2, 73, 65, 2, 38, 25, 4, 67, 17, 5, 5, 67, + 66, 62, 0, 8, 13, 15, 14, 9, 23, 27, 12, 24, + 28, 40, 65, 5, 71, 3, 33, 71, 74, 70, 5, 6, + 69, 79, 70, 83, 69, 72, 90, 15, 14, 34, 5, 67, + 14, 8, 0, 7, 6, 1, 78, 0, 65, 69, 67, 18, 102, + 85, 16, 89, 70, 77, 71, 9, 77, 80, 4, 4, 97, + 75, 104, 12, 15, 21, 66, 77, 74, 77, 83, 87, + 96, 99, 99, 113, 106, 105, 126, 126, 126, 126, + 123, 121, 113, 108, 119, 104, 110, 106, 91, + 98, 78, 81, 83, 84, 105, 96, 96, 118, 102, + 110, 111, 126, 113, 117, 119, 84, 82, 123, 88, + 91, 102, 119, 106, 114, 115, 96, 114, 104, + 106, 117, 113, 116, 107, 112, 117, 70, 0, 16, + 3, 34, 44, 1, 10, 19, 23, 5, 21, 39, 4, 65, + 22, 83, 117, 126, 126, 126, 126, 126, 126, 7, + 39, 30, 27, 17, 33, 18, 10, 8, 2, 72, 67, 12, + 3, 27, 37, 2, 5, 12, 16, 6, 14, 30, 6, 64, 22, + 83, 117, 126, 126, 126, 126, 126, 126 }, + + { + + 17, + 3, 83, 17, 3, 83, 22, 30, 29, 13, 72, 92, 104, + 14, 61, 10, 34, 71, 11, 24, 67, 77, 3, 84, + 113, 80, 122, 126, 126, 126, 62, 8, 0, 11, 24, + 67, 87, 13, 11, 67, 1, 66, 65, 65, 81, 77, 90, + 4, 64, 66, 4, 71, 64, 79, 1, 73, 73, 79, 7, 2, + 22, 0, 0, 0, 8, 98, 97, 13, 80, 71, 9, 72, 86, + 56, 28, 20, 62, 62, 35, 37, 27, 38, 18, 17, + 47, 80, 73, 72, 79, 24, 65, 25, 44, 66, 67, 0, + 31, 64, 77, 76, 85, 20, 68, 11, 3, 0, 12, 2, + 14, 12, 13, 24, 32, 16, 13, 17, 62, 62, 62, + 62, 10, 1, 5, 16, 65, 12, 15, 7, 64, 49, 65, + 118, 113, 62, 9, 18, 5, 7, 12, 21, 56, 32, 14, + 67, 0, 2, 22, 27, 110, 5, 5, 82, 68, 80, 4, 5, + 74, 19, 12, 11, 96, 10, 83, 78, 2, 89, 6, 5, + 2, 90, 116, 70, 85, 85, 91, 104, 101, 86, 126, + 62, 126, 97, 98, 108, 126, 110, 125, 126, 126, + 126, 105, 126, 99, 62, 118, 76, 74, 82, 88, + 89, 86, 91, 90, 90, 107, 107, 101, 103, 104, + 126, 75, 78, 101, 77, 86, 87, 94, 97, 103, + 100, 108, 103, 87, 105, 106, 97, 85, 22, 38, + 24, 10, 13, 20, 11, 7, 7, 41, 27, 34, 26, 24, + 22, 32, 25, 33, 29, 27, 21, 50, 42, 37, 33, + 40, 16, 7, 5, 5, 72, 57, 28, 64, 67, 15, 3, + 73, 64, 2, 38, 25, 3, 68, 17, 6, 6, 66, 65, + 62, 2, 10, 15, 17, 16, 11, 25, 29, 14, 26, 30, + 43, 64, 6, 70, 5, 35, 70, 73, 69, 6, 7, 68, + 79, 70, 83, 69, 72, 90, 16, 15, 35, 6, 67, 15, + 9, 0, 8, 7, 1, 78, 1, 64, 68, 66, 19, 102, 86, + 16, 90, 70, 77, 71, 9, 77, 80, 4, 4, 98, 75, + 105, 11, 14, 21, 67, 78, 76, 79, 85, 89, 98, + 101, 101, 116, 108, 107, 126, 126, 126, 126, + 126, 124, 115, 110, 121, 105, 111, 107, 91, + 98, 80, 83, 85, 86, 107, 98, 98, 121, 104, + 112, 113, 126, 114, 118, 120, 84, 82, 124, 89, + 92, 103, 121, 107, 115, 116, 97, 115, 105, + 106, 118, 113, 116, 108, 113, 118, 69, 1, 17, + 4, 36, 46, 2, 11, 20, 24, 6, 22, 40, 4, 65, + 21, 85, 119, 126, 126, 126, 126, 126, 126, 8, + 39, 31, 28, 18, 34, 19, 11, 9, 3, 71, 66, 13, + 4, 28, 38, 3, 6, 13, 17, 6, 15, 31, 6, 64, 21, + 85, 119, 126, 126, 126, 126, 126, 126 }, + + { + + 16, + 3, 83, 16, 3, 83, 24, 31, 29, 13, 72, 93, 106, + 13, 61, 10, 36, 71, 11, 25, 67, 78, 3, 85, + 115, 81, 125, 126, 126, 126, 62, 9, 0, 11, 25, + 67, 87, 14, 11, 68, 1, 66, 64, 65, 81, 77, 90, + 4, 64, 66, 4, 71, 64, 79, 1, 73, 73, 79, 7, 2, + 22, 0, 0, 0, 9, 98, 97, 13, 81, 71, 8, 72, 86, + 58, 29, 21, 62, 62, 36, 38, 29, 39, 19, 18, + 49, 80, 73, 72, 79, 24, 65, 26, 46, 66, 66, 1, + 33, 64, 77, 76, 86, 20, 68, 11, 4, 1, 13, 3, + 15, 12, 14, 25, 33, 17, 14, 18, 62, 62, 62, + 62, 10, 1, 5, 16, 65, 12, 15, 7, 64, 51, 65, + 120, 115, 62, 9, 18, 5, 7, 12, 21, 58, 33, 14, + 67, 0, 2, 23, 28, 111, 5, 5, 82, 69, 81, 3, 5, + 75, 19, 12, 11, 97, 10, 84, 79, 1, 89, 6, 4, + 1, 92, 119, 71, 87, 87, 93, 106, 103, 88, 126, + 62, 126, 99, 100, 110, 126, 112, 126, 126, + 126, 126, 107, 126, 101, 62, 120, 78, 76, 84, + 90, 91, 88, 93, 92, 92, 109, 109, 103, 105, + 106, 126, 75, 78, 102, 78, 87, 88, 95, 98, + 104, 101, 110, 104, 87, 106, 107, 98, 84, 23, + 39, 24, 10, 13, 21, 12, 8, 8, 43, 28, 34, 26, + 24, 23, 33, 26, 35, 31, 29, 22, 51, 43, 38, + 34, 41, 16, 7, 5, 6, 72, 58, 28, 65, 67, 15, + 3, 73, 64, 2, 38, 24, 2, 69, 17, 6, 6, 66, 65, + 62, 3, 11, 16, 19, 18, 12, 27, 31, 15, 28, 32, + 45, 0, 7, 70, 6, 37, 70, 73, 69, 7, 7, 68, 79, + 70, 84, 69, 72, 90, 16, 15, 36, 6, 67, 15, 9, + 0, 8, 7, 1, 79, 1, 64, 68, 66, 20, 103, 87, + 16, 91, 71, 78, 72, 9, 78, 81, 4, 4, 99, 75, + 106, 10, 13, 20, 69, 80, 78, 81, 87, 91, 101, + 104, 104, 119, 111, 109, 126, 126, 126, 126, + 126, 126, 118, 112, 124, 107, 113, 108, 92, + 99, 82, 85, 88, 88, 110, 101, 101, 124, 106, + 115, 115, 126, 116, 120, 122, 85, 83, 126, 90, + 93, 105, 123, 109, 117, 118, 98, 117, 106, + 107, 119, 114, 117, 110, 114, 119, 69, 1, 18, + 4, 37, 47, 2, 12, 21, 25, 6, 22, 41, 4, 65, + 20, 87, 122, 126, 126, 126, 126, 126, 126, 8, + 39, 31, 28, 18, 35, 19, 11, 9, 3, 71, 66, 13, + 4, 29, 39, 3, 6, 13, 17, 6, 15, 32, 6, 64, 20, + 87, 122, 126, 126, 126, 126, 126, 126 }, + + { + + 15, + 3, 83, 15, 3, 83, 26, 33, 30, 13, 73, 95, 108, + 12, 61, 10, 38, 71, 12, 26, 67, 78, 3, 85, + 116, 82, 126, 126, 126, 126, 62, 10, 0, 12, + 26, 67, 88, 15, 11, 68, 1, 66, 64, 65, 81, 77, + 90, 4, 64, 65, 4, 71, 0, 79, 1, 73, 73, 79, 7, + 2, 22, 0, 0, 0, 9, 98, 97, 14, 82, 71, 7, 73, + 86, 61, 30, 22, 62, 62, 38, 40, 31, 41, 20, + 19, 51, 80, 73, 71, 79, 24, 65, 27, 48, 66, + 65, 1, 35, 64, 78, 77, 86, 20, 68, 12, 4, 2, + 14, 3, 16, 13, 15, 27, 35, 17, 15, 19, 62, 62, + 62, 62, 10, 1, 5, 17, 65, 12, 16, 8, 0, 53, + 65, 122, 117, 62, 9, 18, 5, 7, 12, 22, 59, 34, + 14, 67, 0, 2, 23, 29, 112, 5, 5, 83, 69, 82, + 3, 5, 75, 19, 12, 11, 98, 10, 85, 80, 1, 89, + 6, 4, 1, 94, 122, 72, 89, 89, 96, 108, 106, + 90, 126, 62, 126, 101, 102, 112, 126, 115, + 126, 126, 126, 126, 109, 126, 103, 62, 122, + 80, 78, 86, 93, 94, 90, 95, 94, 94, 111, 111, + 105, 107, 107, 126, 75, 78, 102, 79, 88, 89, + 96, 99, 105, 102, 112, 105, 88, 107, 108, 98, + 84, 25, 40, 25, 10, 13, 22, 12, 8, 8, 45, 29, + 34, 26, 25, 24, 34, 27, 36, 32, 31, 23, 51, + 43, 38, 34, 42, 17, 7, 5, 6, 72, 58, 28, 66, + 67, 15, 3, 73, 0, 2, 37, 24, 1, 70, 17, 6, 6, + 66, 65, 62, 4, 13, 17, 20, 19, 13, 29, 33, 16, + 29, 34, 47, 0, 8, 69, 7, 39, 69, 73, 69, 7, 8, + 68, 79, 70, 84, 69, 72, 91, 17, 16, 37, 6, 67, + 16, 9, 0, 8, 7, 1, 79, 1, 64, 68, 66, 20, 104, + 88, 16, 92, 72, 79, 72, 9, 79, 82, 4, 4, 100, + 75, 107, 9, 12, 19, 70, 82, 80, 83, 89, 94, + 103, 107, 107, 122, 113, 111, 126, 126, 126, + 126, 126, 126, 120, 114, 126, 109, 115, 110, + 93, 100, 84, 88, 90, 91, 113, 103, 103, 126, + 109, 117, 118, 126, 118, 122, 123, 86, 84, + 126, 92, 95, 107, 125, 110, 119, 120, 99, 118, + 107, 108, 120, 115, 118, 111, 115, 120, 69, 2, + 18, 5, 38, 48, 2, 12, 21, 25, 6, 23, 42, 4, + 65, 19, 89, 124, 126, 126, 126, 126, 126, 126, + 8, 39, 31, 28, 18, 35, 20, 11, 9, 4, 71, 66, + 14, 5, 30, 40, 3, 7, 13, 17, 6, 15, 32, 6, 64, + 19, 89, 124, 126, 126, 126, 126, 126, 126 }, + + }, + + { + + { + + 62, + 9, 74, 62, 9, 74, 126, 104, 10, 9, 12, 47, 62, + 62, 12, 1, 99, 47, 85, 102, 6, 6, 73, 6, 23, 53, + 62, 62, 21, 97, 126, 117, 74, 85, 102, 6, 93, + 88, 19, 8, 89, 103, 116, 6, 5, 84, 96, 0, 85, + 106, 0, 75, 90, 101, 8, 79, 75, 97, 13, 3, 22, + 0, 0, 0, 83, 86, 97, 72, 22, 1, 29, 88, 126, + 126, 91, 95, 84, 86, 89, 91, 126, 76, 103, 90, + 126, 80, 76, 84, 78, 8, 2, 83, 126, 79, 104, 91, + 126, 65, 79, 72, 92, 7, 68, 71, 98, 86, 88, 82, + 72, 67, 72, 89, 69, 4, 66, 6, 71, 71, 5, 74, 19, + 69, 1, 12, 16, 21, 22, 10, 76, 78, 83, 11, 67, + 90, 67, 72, 75, 80, 83, 64, 32, 64, 94, 75, 0, + 74, 28, 36, 91, 65, 69, 77, 66, 1, 68, 81, 33, + 56, 40, 74, 66, 124, 26, 62, 62, 126, 24, 21, + 29, 34, 32, 26, 21, 23, 30, 20, 27, 16, 8, 5, 3, + 19, 19, 21, 15, 7, 11, 26, 14, 5, 15, 18, 69, + 30, 0, 62, 62, 62, 53, 62, 62, 62, 62, 46, 38, + 34, 30, 48, 43, 73, 29, 32, 19, 47, 27, 27, 35, + 42, 43, 51, 47, 21, 93, 7, 6, 25, 126, 115, 82, + 1, 10, 4, 85, 89, 94, 92, 126, 100, 6, 67, 71, + 77, 85, 88, 104, 98, 126, 82, 15, 2, 66, 70, 75, + 79, 83, 92, 108, 79, 69, 75, 5, 5, 78, 83, 81, + 99, 81, 25, 1, 5, 4, 73, 76, 86, 83, 87, 62, + 126, 126, 120, 126, 114, 117, 118, 117, 113, + 118, 120, 124, 94, 102, 99, 106, 126, 92, 6, 86, + 94, 91, 77, 71, 73, 64, 81, 64, 6, 67, 68, 67, + 68, 77, 64, 68, 78, 8, 4, 65, 9, 19, 3, 70, 76, + 86, 70, 64, 70, 8, 7, 69, 65, 74, 9, 9, 76, 82, + 77, 77, 21, 62, 62, 62, 62, 62, 62, 62, 62, 62, + 62, 62, 62, 62, 62, 52, 62, 62, 62, 62, 62, 62, + 48, 62, 62, 46, 25, 18, 9, 79, 62, 62, 62, 62, + 48, 48, 38, 41, 47, 45, 35, 22, 35, 16, 1, 32, + 37, 39, 40, 47, 33, 34, 22, 21, 3, 11, 3, 78, + 123, 10, 7, 2, 30, 13, 2, 78, 74, 72, 72, 75, + 71, 0, 70, 75, 72, 67, 10, 4, 11, 68, 62, 62, + 62, 62, 56, 51, 40, 25, 64, 71, 26, 19, 14, 7, + 4, 0, 67, 68, 79, 78, 74, 72, 72, 75, 71, 0, 70, + 75, 72, 67, 10, 4, 11, 68, 62, 62, 62, 62, 56, + 51, 40, 25, 64 }, + + { + + 62, + 9, 74, 62, 9, 74, 125, 102, 11, 10, 12, 46, + 62, 62, 13, 2, 97, 46, 84, 100, 6, 6, 71, 6, + 22, 52, 62, 60, 19, 97, 125, 115, 73, 84, 100, + 6, 92, 87, 20, 8, 88, 102, 114, 5, 4, 84, 96, + 0, 84, 105, 0, 75, 89, 100, 8, 78, 74, 96, 14, + 3, 22, 0, 0, 0, 82, 86, 97, 71, 22, 1, 29, 87, + 125, 124, 89, 94, 82, 84, 88, 89, 125, 75, + 101, 89, 124, 80, 76, 84, 78, 9, 2, 82, 124, + 78, 103, 90, 125, 65, 78, 72, 91, 8, 68, 70, + 97, 85, 87, 81, 71, 66, 71, 88, 68, 5, 66, 6, + 70, 70, 5, 73, 20, 68, 1, 13, 17, 22, 23, 11, + 76, 77, 82, 11, 67, 89, 67, 71, 74, 79, 81, 1, + 33, 1, 92, 75, 64, 73, 29, 37, 91, 65, 68, 77, + 65, 1, 67, 79, 33, 56, 41, 72, 67, 122, 25, + 62, 62, 125, 24, 21, 29, 34, 32, 26, 21, 23, + 30, 20, 27, 16, 8, 5, 3, 19, 19, 21, 15, 7, + 11, 26, 14, 4, 15, 18, 69, 29, 0, 62, 62, 62, + 52, 62, 62, 62, 62, 45, 37, 32, 29, 46, 42, + 74, 28, 31, 18, 46, 27, 27, 34, 41, 42, 50, + 46, 20, 93, 7, 6, 24, 125, 113, 80, 2, 10, 4, + 84, 88, 93, 91, 125, 98, 7, 66, 70, 76, 83, + 87, 102, 97, 124, 81, 16, 3, 65, 69, 74, 78, + 82, 91, 106, 78, 67, 74, 6, 5, 77, 82, 80, 98, + 80, 26, 2, 6, 5, 72, 75, 85, 82, 86, 62, 125, + 125, 118, 125, 112, 115, 116, 115, 111, 116, + 118, 121, 93, 101, 98, 105, 123, 91, 5, 85, + 93, 90, 76, 71, 72, 64, 80, 64, 6, 67, 68, 66, + 68, 77, 64, 68, 77, 8, 4, 65, 9, 19, 3, 70, + 75, 84, 70, 64, 69, 8, 7, 69, 65, 73, 9, 9, + 75, 81, 76, 76, 20, 62, 62, 62, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 50, 62, 62, + 62, 62, 62, 62, 47, 60, 60, 45, 24, 17, 9, 79, + 62, 62, 62, 60, 46, 47, 37, 39, 46, 43, 34, + 20, 33, 15, 0, 31, 36, 37, 39, 46, 32, 33, 21, + 20, 2, 11, 3, 78, 122, 9, 6, 1, 29, 12, 1, 77, + 73, 71, 71, 73, 70, 1, 69, 73, 71, 66, 11, 5, + 12, 67, 62, 62, 62, 62, 54, 50, 38, 24, 65, + 70, 27, 20, 15, 8, 5, 1, 66, 67, 78, 77, 73, + 71, 71, 73, 70, 1, 69, 73, 71, 66, 11, 5, 12, + 67, 62, 62, 62, 62, 54, 50, 38, 24, 65 }, + + { + + 62, + 9, 74, 62, 9, 74, 123, 101, 11, 10, 12, 44, + 60, 62, 14, 2, 95, 44, 84, 99, 6, 6, 70, 5, + 21, 51, 60, 57, 17, 98, 123, 114, 73, 84, 99, + 6, 92, 86, 20, 8, 87, 101, 113, 4, 3, 84, 96, + 0, 84, 104, 0, 75, 89, 100, 8, 78, 74, 95, 14, + 3, 22, 0, 0, 0, 81, 86, 97, 71, 21, 1, 29, 86, + 124, 122, 88, 93, 80, 82, 87, 88, 123, 74, + 100, 88, 122, 81, 76, 84, 78, 9, 2, 81, 122, + 78, 102, 89, 123, 65, 78, 72, 91, 8, 68, 70, + 96, 85, 86, 81, 71, 66, 71, 87, 67, 5, 66, 6, + 70, 70, 5, 73, 20, 68, 1, 13, 17, 22, 23, 11, + 77, 76, 81, 10, 67, 89, 67, 70, 74, 79, 80, 2, + 34, 3, 90, 76, 65, 73, 29, 37, 92, 65, 68, 78, + 64, 1, 67, 78, 33, 56, 41, 71, 68, 121, 24, + 62, 62, 124, 24, 21, 29, 33, 31, 26, 21, 23, + 29, 19, 26, 16, 8, 5, 3, 18, 18, 20, 15, 7, + 11, 25, 13, 3, 14, 17, 69, 28, 64, 62, 62, 62, + 50, 60, 62, 62, 62, 44, 35, 30, 27, 44, 40, + 75, 27, 30, 16, 45, 26, 26, 33, 39, 40, 48, + 44, 18, 93, 6, 5, 22, 124, 112, 79, 3, 10, 4, + 83, 87, 92, 90, 123, 97, 8, 65, 69, 75, 82, + 86, 101, 96, 122, 80, 16, 3, 65, 69, 73, 77, + 81, 90, 105, 78, 66, 73, 6, 5, 76, 81, 80, 97, + 79, 26, 3, 6, 5, 71, 74, 84, 81, 85, 62, 124, + 123, 116, 123, 111, 114, 114, 113, 110, 114, + 116, 119, 92, 100, 97, 104, 120, 91, 4, 85, + 92, 89, 76, 71, 72, 64, 80, 64, 5, 67, 68, 65, + 68, 77, 64, 68, 77, 8, 4, 65, 8, 18, 3, 70, + 75, 83, 71, 64, 68, 7, 7, 69, 65, 73, 9, 9, + 75, 80, 76, 76, 18, 62, 62, 62, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 48, 62, 62, + 62, 62, 62, 61, 45, 58, 58, 43, 23, 16, 8, 79, + 62, 62, 62, 58, 44, 45, 35, 37, 44, 41, 32, + 18, 31, 13, 64, 30, 35, 35, 37, 44, 30, 31, + 20, 19, 1, 10, 2, 78, 121, 8, 5, 64, 28, 11, + 0, 77, 73, 70, 70, 72, 69, 2, 69, 72, 70, 65, + 11, 6, 13, 66, 62, 62, 62, 60, 52, 48, 36, 22, + 66, 69, 27, 20, 16, 9, 6, 1, 65, 67, 77, 77, + 73, 70, 70, 72, 69, 2, 69, 72, 70, 65, 11, 6, + 13, 66, 62, 62, 62, 60, 52, 48, 36, 22, 66 }, + + { + + 62, + 9, 74, 62, 9, 74, 121, 99, 12, 10, 11, 42, 59, + 61, 14, 2, 93, 43, 84, 97, 6, 5, 69, 4, 20, + 50, 58, 53, 15, 99, 121, 112, 73, 84, 97, 6, + 91, 85, 21, 8, 86, 100, 112, 3, 2, 84, 97, 0, + 84, 103, 0, 76, 89, 100, 8, 78, 74, 94, 15, 3, + 22, 0, 0, 0, 81, 86, 97, 70, 20, 1, 28, 86, + 123, 120, 87, 92, 79, 81, 86, 87, 121, 73, 99, + 87, 120, 82, 76, 84, 78, 10, 2, 80, 120, 78, + 101, 88, 121, 65, 78, 72, 91, 9, 68, 69, 95, + 85, 85, 81, 71, 66, 70, 86, 67, 5, 66, 6, 70, + 70, 5, 73, 20, 68, 1, 14, 17, 23, 23, 12, 77, + 76, 80, 10, 67, 89, 67, 69, 74, 78, 79, 3, 35, + 4, 88, 76, 66, 72, 29, 37, 93, 65, 67, 78, 64, + 1, 67, 77, 33, 56, 41, 70, 69, 119, 23, 62, + 62, 122, 24, 21, 28, 32, 31, 25, 20, 23, 29, + 18, 25, 16, 8, 5, 2, 18, 17, 19, 14, 7, 11, + 24, 13, 2, 14, 16, 69, 27, 64, 62, 62, 61, 49, + 58, 62, 62, 62, 43, 33, 28, 26, 42, 38, 77, + 26, 29, 14, 44, 25, 25, 32, 38, 38, 46, 42, + 17, 93, 5, 4, 21, 122, 110, 77, 3, 10, 4, 82, + 86, 91, 89, 121, 96, 9, 64, 68, 75, 81, 85, + 99, 95, 120, 80, 17, 4, 64, 68, 72, 77, 81, + 89, 104, 78, 64, 72, 6, 5, 75, 81, 80, 96, 78, + 27, 4, 7, 5, 70, 74, 83, 81, 85, 62, 122, 122, + 115, 121, 110, 112, 113, 112, 108, 112, 114, + 117, 92, 99, 97, 103, 117, 91, 3, 85, 91, 88, + 76, 71, 72, 64, 79, 64, 4, 67, 68, 65, 68, 77, + 64, 68, 77, 7, 4, 65, 7, 17, 3, 70, 75, 82, + 72, 64, 67, 6, 7, 69, 65, 72, 9, 8, 74, 79, + 76, 76, 17, 62, 62, 62, 62, 62, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 46, 62, 62, 62, 62, + 62, 59, 43, 56, 55, 41, 22, 15, 7, 79, 62, 62, + 62, 56, 42, 43, 34, 35, 42, 39, 30, 16, 29, + 11, 65, 29, 34, 33, 36, 42, 29, 29, 18, 17, 0, + 9, 1, 78, 120, 7, 3, 65, 27, 10, 64, 77, 72, + 70, 70, 71, 68, 3, 69, 71, 69, 64, 12, 7, 13, + 65, 62, 62, 62, 58, 50, 46, 34, 20, 67, 69, + 28, 21, 17, 9, 7, 2, 65, 66, 77, 77, 72, 70, + 70, 71, 68, 3, 69, 71, 69, 64, 12, 7, 13, 65, + 62, 62, 62, 58, 50, 46, 34, 20, 67 }, + + { + + 62, + 9, 74, 62, 9, 74, 120, 98, 12, 10, 11, 40, 57, + 60, 15, 2, 92, 41, 84, 96, 5, 5, 68, 3, 18, + 48, 56, 50, 12, 100, 119, 111, 73, 84, 96, 5, + 91, 84, 21, 7, 86, 99, 110, 2, 0, 85, 97, 0, + 83, 102, 64, 76, 89, 100, 8, 78, 74, 94, 15, + 3, 22, 0, 0, 0, 80, 87, 97, 70, 19, 1, 28, 85, + 122, 118, 86, 91, 77, 79, 86, 86, 119, 72, 98, + 86, 117, 82, 77, 84, 79, 10, 1, 79, 117, 77, + 101, 88, 119, 65, 78, 72, 91, 9, 68, 69, 94, + 85, 85, 80, 71, 66, 70, 85, 66, 5, 67, 5, 70, + 70, 5, 73, 20, 68, 1, 14, 17, 23, 23, 12, 78, + 75, 80, 9, 67, 88, 67, 68, 73, 78, 77, 5, 36, + 6, 86, 77, 67, 72, 30, 37, 94, 65, 67, 79, 0, + 1, 67, 76, 33, 56, 41, 68, 70, 118, 22, 62, + 62, 121, 23, 21, 28, 32, 30, 25, 20, 23, 28, + 17, 24, 15, 8, 5, 2, 17, 17, 18, 14, 6, 10, + 23, 12, 1, 13, 15, 69, 25, 65, 62, 62, 59, 47, + 57, 62, 62, 62, 42, 31, 25, 24, 40, 36, 78, + 24, 28, 13, 43, 24, 24, 30, 36, 36, 44, 41, + 15, 93, 4, 3, 19, 121, 109, 76, 4, 10, 4, 81, + 85, 90, 89, 119, 94, 10, 64, 68, 74, 79, 84, + 98, 94, 117, 79, 17, 4, 64, 68, 71, 76, 80, + 89, 103, 78, 0, 71, 6, 5, 74, 80, 80, 95, 77, + 27, 5, 7, 5, 69, 73, 82, 80, 84, 62, 121, 120, + 113, 120, 109, 111, 111, 110, 107, 111, 112, + 114, 91, 98, 96, 102, 114, 90, 2, 84, 90, 88, + 76, 71, 72, 65, 79, 65, 3, 67, 68, 64, 68, 77, + 64, 68, 76, 7, 3, 65, 6, 16, 2, 70, 75, 81, + 73, 65, 67, 6, 6, 69, 65, 72, 8, 8, 74, 79, + 76, 76, 15, 62, 62, 62, 62, 62, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 44, 62, 62, 62, 62, + 62, 57, 41, 54, 53, 39, 20, 14, 6, 79, 62, 62, + 62, 54, 40, 41, 32, 33, 40, 37, 28, 14, 26, + 10, 67, 28, 33, 30, 34, 41, 27, 27, 17, 16, + 64, 8, 0, 78, 119, 5, 2, 67, 25, 9, 65, 77, + 72, 69, 69, 70, 68, 3, 68, 70, 68, 0, 12, 8, + 14, 65, 62, 62, 60, 56, 48, 44, 31, 18, 69, + 68, 28, 21, 17, 10, 7, 2, 64, 66, 76, 77, 72, + 69, 69, 70, 68, 3, 68, 70, 68, 0, 12, 8, 14, + 65, 62, 62, 60, 56, 48, 44, 31, 18, 69 }, + + { + + 62, + 9, 74, 62, 9, 74, 118, 96, 12, 10, 10, 38, 56, + 59, 16, 2, 90, 39, 83, 94, 5, 5, 67, 2, 17, + 47, 54, 47, 10, 100, 117, 110, 73, 83, 94, 5, + 91, 83, 21, 7, 85, 98, 109, 1, 64, 85, 97, 0, + 83, 101, 64, 76, 89, 100, 8, 77, 74, 93, 16, + 3, 22, 0, 0, 0, 80, 87, 97, 69, 18, 1, 27, 85, + 120, 115, 85, 90, 76, 78, 85, 85, 117, 71, 97, + 85, 115, 83, 77, 84, 79, 10, 1, 78, 115, 77, + 100, 87, 117, 65, 78, 72, 90, 9, 68, 68, 93, + 84, 84, 80, 71, 65, 69, 84, 66, 5, 67, 5, 69, + 70, 5, 73, 21, 68, 1, 15, 18, 23, 23, 12, 78, + 75, 79, 9, 67, 88, 67, 67, 73, 77, 76, 6, 37, + 7, 84, 77, 68, 71, 30, 37, 95, 65, 66, 79, 1, + 1, 67, 74, 33, 56, 41, 67, 71, 116, 21, 62, + 62, 120, 23, 21, 27, 31, 30, 25, 19, 23, 28, + 16, 23, 15, 8, 5, 2, 17, 16, 17, 13, 6, 10, + 22, 12, 0, 12, 15, 69, 24, 65, 62, 62, 58, 46, + 55, 62, 62, 62, 41, 29, 23, 23, 38, 34, 79, + 23, 27, 11, 42, 23, 23, 29, 35, 34, 42, 39, + 14, 93, 3, 2, 17, 119, 107, 75, 4, 10, 4, 80, + 84, 89, 88, 117, 93, 11, 0, 67, 73, 78, 83, + 96, 93, 115, 78, 18, 5, 0, 67, 70, 75, 80, 88, + 102, 77, 1, 70, 6, 5, 73, 80, 79, 94, 76, 27, + 6, 7, 5, 68, 72, 81, 80, 83, 62, 120, 119, + 112, 118, 108, 109, 110, 108, 105, 109, 110, + 112, 90, 97, 95, 101, 111, 90, 1, 84, 89, 87, + 76, 71, 72, 65, 78, 65, 2, 67, 68, 0, 68, 77, + 64, 68, 76, 6, 3, 65, 5, 15, 2, 70, 75, 80, + 73, 65, 66, 5, 6, 69, 65, 72, 8, 7, 74, 78, + 76, 76, 14, 62, 62, 62, 62, 62, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 42, 62, 62, 62, 62, + 62, 55, 40, 52, 50, 37, 19, 13, 5, 79, 62, 62, + 62, 52, 38, 39, 31, 31, 38, 35, 26, 12, 24, 8, + 68, 27, 32, 28, 33, 39, 26, 25, 16, 15, 65, 7, + 64, 78, 118, 4, 1, 68, 24, 8, 66, 77, 71, 69, + 68, 69, 67, 4, 68, 69, 67, 1, 13, 9, 14, 64, + 62, 62, 58, 54, 46, 42, 29, 16, 70, 68, 29, + 22, 18, 11, 8, 3, 64, 66, 75, 77, 71, 69, 68, + 69, 67, 4, 68, 69, 67, 1, 13, 9, 14, 64, 62, + 62, 58, 54, 46, 42, 29, 16, 70 }, + + { + + 62, + 9, 75, 62, 9, 75, 116, 95, 13, 10, 10, 37, 54, + 58, 16, 3, 88, 38, 83, 93, 5, 4, 66, 1, 16, + 46, 53, 43, 8, 101, 115, 108, 73, 83, 93, 5, + 90, 82, 22, 7, 84, 97, 108, 64, 65, 85, 98, 0, + 83, 101, 64, 77, 88, 100, 7, 77, 74, 92, 16, + 3, 22, 0, 0, 0, 79, 87, 97, 69, 18, 0, 27, 84, + 119, 113, 84, 89, 74, 76, 84, 84, 115, 70, 96, + 85, 113, 84, 77, 84, 79, 11, 1, 77, 113, 77, + 99, 86, 115, 65, 78, 72, 90, 10, 69, 68, 93, + 84, 83, 80, 70, 65, 69, 83, 65, 5, 67, 5, 69, + 70, 5, 73, 21, 68, 1, 15, 18, 24, 24, 13, 79, + 74, 78, 8, 67, 88, 67, 66, 73, 77, 75, 7, 37, + 9, 83, 78, 69, 71, 30, 37, 95, 66, 66, 80, 1, + 0, 66, 73, 33, 56, 42, 66, 72, 115, 20, 62, + 62, 118, 23, 21, 27, 30, 29, 24, 19, 22, 27, + 16, 23, 15, 7, 5, 1, 16, 15, 16, 13, 6, 10, + 22, 11, 65, 12, 14, 69, 23, 66, 62, 62, 56, + 44, 53, 62, 62, 62, 39, 27, 21, 21, 36, 32, + 81, 22, 25, 9, 40, 22, 22, 28, 33, 32, 40, 37, + 12, 93, 2, 1, 16, 118, 106, 73, 5, 10, 4, 79, + 84, 89, 87, 116, 92, 12, 1, 66, 73, 77, 82, + 95, 92, 113, 78, 18, 5, 0, 67, 69, 75, 79, 87, + 101, 77, 3, 69, 6, 5, 73, 79, 79, 94, 76, 28, + 6, 8, 5, 67, 72, 81, 79, 83, 62, 118, 117, + 110, 116, 106, 108, 108, 107, 104, 107, 108, + 110, 90, 96, 95, 101, 108, 90, 0, 84, 89, 86, + 76, 71, 72, 65, 78, 65, 1, 67, 68, 0, 68, 77, + 64, 68, 76, 6, 3, 65, 4, 14, 2, 70, 75, 79, + 74, 65, 65, 4, 6, 69, 65, 71, 8, 7, 73, 77, + 76, 76, 12, 62, 62, 62, 62, 62, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 40, 62, 62, 62, 62, + 62, 52, 38, 50, 48, 35, 18, 12, 4, 79, 62, 62, + 62, 50, 36, 38, 29, 29, 36, 32, 24, 10, 22, 6, + 69, 26, 30, 26, 31, 37, 24, 23, 14, 13, 66, 6, + 65, 79, 117, 3, 64, 70, 23, 6, 67, 76, 71, 68, + 68, 68, 66, 5, 68, 68, 66, 2, 13, 10, 15, 0, + 62, 62, 56, 52, 44, 40, 27, 14, 71, 67, 29, + 22, 19, 11, 9, 3, 0, 65, 75, 76, 71, 68, 68, + 68, 66, 5, 68, 68, 66, 2, 13, 10, 15, 0, 62, + 62, 56, 52, 44, 40, 27, 14, 71 }, + + { + + 62, + 9, 75, 62, 9, 75, 114, 93, 13, 10, 9, 35, 53, + 57, 17, 3, 87, 36, 83, 91, 4, 4, 65, 0, 15, + 45, 51, 40, 5, 102, 113, 107, 73, 83, 91, 4, + 90, 81, 22, 7, 84, 96, 106, 65, 66, 85, 98, 0, + 82, 100, 65, 77, 88, 100, 7, 77, 74, 91, 17, + 3, 22, 0, 0, 0, 79, 87, 97, 68, 17, 0, 26, 84, + 118, 111, 83, 88, 73, 75, 83, 83, 113, 69, 95, + 84, 110, 84, 78, 84, 80, 11, 1, 76, 110, 76, + 99, 86, 113, 65, 78, 72, 90, 10, 69, 67, 92, + 84, 82, 79, 70, 65, 68, 82, 65, 5, 68, 5, 69, + 70, 5, 73, 21, 68, 1, 16, 18, 24, 24, 13, 79, + 74, 78, 8, 67, 87, 67, 65, 72, 76, 73, 9, 38, + 10, 81, 78, 70, 70, 31, 37, 96, 66, 65, 80, 2, + 0, 66, 72, 33, 56, 42, 64, 73, 113, 19, 62, + 62, 117, 23, 21, 26, 30, 29, 24, 18, 22, 27, + 15, 22, 15, 7, 5, 1, 16, 15, 15, 12, 6, 10, + 21, 11, 66, 11, 13, 69, 22, 66, 62, 62, 54, + 43, 52, 62, 62, 62, 38, 25, 19, 20, 34, 30, + 82, 21, 24, 8, 39, 21, 21, 26, 32, 30, 38, 36, + 11, 93, 1, 0, 14, 116, 104, 72, 5, 10, 4, 78, + 83, 88, 87, 114, 90, 13, 2, 66, 72, 75, 81, + 93, 91, 110, 77, 19, 6, 1, 66, 68, 74, 79, 86, + 100, 77, 4, 68, 6, 5, 72, 79, 79, 93, 75, 28, + 7, 8, 5, 66, 71, 80, 79, 82, 62, 117, 116, + 109, 115, 105, 106, 107, 105, 102, 105, 106, + 107, 89, 95, 94, 100, 105, 89, 64, 83, 88, 85, + 76, 71, 72, 65, 77, 66, 0, 67, 68, 1, 68, 77, + 64, 68, 75, 5, 2, 65, 3, 13, 1, 70, 75, 78, + 75, 66, 64, 4, 5, 69, 65, 71, 7, 6, 73, 77, + 76, 76, 11, 62, 62, 62, 62, 62, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 38, 62, 62, 62, 62, + 62, 50, 36, 48, 45, 33, 17, 11, 3, 79, 62, 61, + 62, 48, 34, 36, 28, 27, 34, 30, 22, 8, 20, 5, + 71, 25, 29, 24, 30, 36, 23, 21, 13, 12, 67, 5, + 66, 79, 116, 1, 65, 71, 21, 5, 68, 76, 70, 68, + 67, 67, 65, 5, 67, 67, 65, 3, 14, 11, 15, 0, + 62, 60, 54, 50, 42, 38, 24, 12, 72, 67, 30, + 23, 19, 12, 10, 4, 0, 65, 74, 76, 70, 68, 67, + 67, 65, 5, 67, 67, 65, 3, 14, 11, 15, 0, 62, + 60, 54, 50, 42, 38, 24, 12, 72 }, + + { + + 62, + 8, 75, 62, 8, 75, 113, 92, 13, 10, 9, 33, 51, + 56, 17, 3, 85, 34, 83, 90, 4, 3, 64, 64, 13, + 43, 49, 36, 3, 103, 111, 106, 73, 83, 90, 4, + 90, 81, 22, 6, 83, 95, 105, 66, 68, 86, 99, 0, + 82, 99, 65, 78, 88, 100, 7, 77, 74, 91, 17, 3, + 22, 0, 0, 0, 78, 88, 97, 68, 16, 0, 26, 83, + 117, 109, 82, 88, 71, 73, 83, 82, 111, 69, 94, + 83, 108, 85, 78, 85, 80, 11, 0, 76, 108, 76, + 98, 85, 112, 65, 78, 72, 90, 10, 69, 67, 91, + 84, 82, 79, 70, 65, 68, 81, 64, 5, 68, 4, 69, + 70, 4, 73, 21, 68, 1, 16, 18, 24, 24, 13, 80, + 73, 77, 7, 67, 87, 67, 64, 72, 76, 72, 10, 39, + 12, 79, 79, 71, 70, 31, 37, 97, 66, 65, 81, 2, + 0, 66, 71, 33, 56, 42, 0, 74, 112, 18, 59, 62, + 116, 22, 21, 26, 29, 28, 23, 18, 22, 26, 14, + 21, 14, 7, 4, 0, 15, 14, 14, 12, 5, 9, 20, 10, + 67, 10, 12, 69, 20, 67, 62, 62, 52, 41, 50, + 60, 62, 62, 37, 23, 16, 18, 31, 28, 84, 19, + 23, 6, 38, 20, 20, 25, 30, 28, 36, 34, 9, 93, + 0, 64, 12, 115, 103, 71, 6, 10, 4, 78, 82, 87, + 86, 112, 89, 13, 2, 65, 72, 74, 80, 92, 90, + 108, 77, 19, 6, 1, 66, 68, 74, 78, 86, 99, 77, + 5, 67, 6, 5, 71, 78, 79, 92, 74, 28, 8, 8, 5, + 65, 71, 79, 78, 82, 62, 116, 114, 107, 113, + 104, 105, 105, 104, 101, 104, 104, 105, 89, + 94, 94, 99, 102, 89, 65, 83, 87, 85, 76, 71, + 72, 66, 77, 66, 64, 67, 68, 1, 68, 77, 65, 68, + 75, 5, 2, 66, 2, 12, 1, 71, 75, 77, 76, 66, + 64, 3, 5, 69, 66, 71, 7, 6, 73, 76, 76, 76, 9, + 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, + 62, 62, 61, 36, 62, 62, 62, 62, 61, 48, 34, + 45, 43, 31, 15, 9, 2, 79, 61, 59, 62, 46, 31, + 34, 26, 24, 32, 28, 20, 6, 17, 3, 72, 23, 28, + 21, 28, 34, 21, 19, 11, 10, 68, 4, 67, 79, + 115, 0, 67, 73, 20, 4, 69, 76, 70, 67, 67, 66, + 65, 6, 67, 66, 65, 4, 14, 11, 16, 1, 61, 58, + 52, 48, 40, 36, 22, 10, 74, 66, 30, 23, 20, + 12, 10, 4, 1, 65, 74, 76, 70, 67, 67, 66, 65, + 6, 67, 66, 65, 4, 14, 11, 16, 1, 61, 58, 52, + 48, 40, 36, 22, 10, 74 }, + + { + + 62, + 8, 75, 62, 8, 75, 111, 91, 14, 10, 9, 31, 49, + 56, 18, 3, 83, 33, 82, 88, 4, 3, 0, 64, 12, + 42, 47, 33, 1, 103, 109, 104, 72, 82, 88, 4, + 89, 80, 23, 6, 82, 94, 104, 67, 69, 86, 99, 0, + 82, 98, 65, 78, 88, 100, 7, 76, 73, 90, 17, 3, + 22, 0, 0, 0, 77, 88, 97, 68, 15, 0, 26, 82, + 115, 106, 81, 87, 69, 71, 82, 81, 109, 68, 92, + 82, 106, 86, 78, 85, 80, 12, 0, 75, 106, 76, + 97, 84, 110, 65, 77, 72, 89, 11, 69, 66, 90, + 83, 81, 79, 70, 64, 67, 80, 0, 5, 68, 4, 68, + 69, 4, 73, 22, 68, 1, 16, 19, 25, 24, 14, 80, + 72, 76, 6, 67, 87, 67, 0, 72, 75, 71, 11, 40, + 14, 77, 80, 72, 69, 31, 38, 98, 66, 65, 81, 3, + 0, 66, 69, 33, 56, 42, 1, 75, 111, 17, 57, 62, + 114, 22, 21, 26, 28, 28, 23, 18, 22, 26, 13, + 20, 14, 7, 4, 0, 15, 13, 14, 12, 5, 9, 19, 9, + 68, 10, 12, 69, 19, 67, 62, 62, 51, 40, 48, + 58, 62, 62, 36, 21, 14, 17, 29, 27, 85, 18, + 22, 4, 37, 19, 19, 24, 28, 27, 34, 32, 8, 93, + 0, 65, 11, 113, 101, 69, 7, 10, 4, 77, 81, 86, + 85, 110, 88, 14, 3, 64, 71, 73, 79, 91, 89, + 106, 76, 20, 7, 2, 66, 67, 73, 77, 85, 97, 76, + 7, 66, 7, 5, 70, 77, 78, 91, 73, 29, 9, 9, 6, + 64, 70, 78, 77, 81, 62, 114, 112, 105, 111, + 103, 104, 103, 102, 99, 102, 102, 103, 88, 93, + 93, 98, 98, 89, 66, 83, 86, 84, 75, 71, 72, + 66, 77, 66, 65, 67, 68, 2, 68, 77, 65, 68, 75, + 5, 2, 66, 2, 11, 1, 71, 74, 75, 76, 66, 0, 2, + 5, 69, 66, 70, 7, 6, 72, 75, 75, 75, 7, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, + 62, 58, 34, 62, 62, 62, 62, 58, 46, 33, 43, + 41, 30, 14, 8, 1, 79, 59, 57, 60, 44, 29, 32, + 25, 22, 30, 26, 18, 4, 15, 1, 73, 22, 27, 19, + 27, 32, 20, 17, 10, 9, 69, 3, 67, 79, 114, 64, + 68, 75, 19, 3, 70, 76, 69, 66, 66, 64, 64, 7, + 67, 65, 64, 5, 15, 12, 17, 2, 60, 57, 50, 46, + 38, 34, 20, 8, 75, 65, 30, 24, 21, 13, 11, 5, + 2, 64, 73, 76, 69, 66, 66, 64, 64, 7, 67, 65, + 64, 5, 15, 12, 17, 2, 60, 57, 50, 46, 38, 34, + 20, 8, 75 }, + + { + + 62, + 8, 75, 62, 8, 75, 109, 89, 14, 10, 8, 29, 48, + 55, 19, 3, 82, 31, 82, 87, 3, 3, 1, 65, 11, + 41, 45, 30, 65, 104, 107, 103, 72, 82, 87, 3, + 89, 79, 23, 6, 82, 93, 102, 68, 70, 86, 99, 0, + 81, 97, 66, 78, 88, 100, 7, 76, 73, 89, 18, 3, + 22, 0, 0, 0, 77, 88, 97, 67, 14, 0, 25, 82, + 114, 104, 80, 86, 68, 70, 81, 80, 107, 67, 91, + 81, 103, 86, 79, 85, 81, 12, 0, 74, 103, 75, + 97, 84, 108, 65, 77, 72, 89, 11, 69, 66, 89, + 83, 80, 78, 70, 64, 67, 79, 0, 5, 69, 4, 68, + 69, 4, 73, 22, 68, 1, 17, 19, 25, 24, 14, 81, + 72, 76, 6, 67, 86, 67, 1, 71, 75, 69, 13, 41, + 15, 75, 80, 73, 69, 32, 38, 99, 66, 64, 82, 4, + 0, 66, 68, 33, 56, 42, 3, 76, 109, 16, 54, 62, + 113, 22, 21, 25, 28, 27, 23, 17, 22, 25, 12, + 19, 14, 7, 4, 0, 14, 13, 13, 11, 5, 9, 18, 9, + 69, 9, 11, 69, 18, 68, 60, 62, 49, 38, 47, 56, + 62, 62, 35, 19, 12, 15, 27, 25, 86, 17, 21, 3, + 36, 18, 18, 22, 27, 25, 32, 31, 6, 93, 64, 66, + 9, 112, 100, 68, 7, 10, 4, 76, 80, 85, 85, + 108, 86, 15, 4, 64, 70, 71, 78, 89, 88, 103, + 75, 20, 7, 2, 65, 66, 72, 77, 84, 96, 76, 8, + 65, 7, 5, 69, 77, 78, 90, 72, 29, 10, 9, 6, 0, + 69, 77, 77, 80, 62, 113, 111, 104, 110, 102, + 102, 102, 100, 98, 100, 100, 100, 87, 92, 92, + 97, 95, 88, 67, 82, 85, 83, 75, 71, 72, 66, + 76, 67, 66, 67, 68, 3, 68, 77, 65, 68, 74, 4, + 1, 66, 1, 10, 0, 71, 74, 74, 77, 67, 1, 2, 4, + 69, 66, 70, 6, 5, 72, 75, 75, 75, 6, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, + 56, 32, 62, 62, 62, 62, 55, 44, 31, 41, 38, + 28, 13, 7, 0, 79, 57, 54, 57, 42, 27, 30, 23, + 20, 28, 24, 16, 2, 13, 0, 75, 21, 26, 17, 25, + 31, 18, 15, 9, 8, 70, 2, 68, 79, 113, 66, 69, + 76, 17, 2, 71, 76, 69, 66, 65, 0, 0, 7, 66, + 64, 0, 6, 15, 13, 17, 2, 60, 55, 48, 44, 36, + 32, 17, 6, 76, 65, 31, 24, 21, 14, 12, 5, 2, + 64, 72, 76, 69, 66, 65, 0, 0, 7, 66, 64, 0, 6, + 15, 13, 17, 2, 60, 55, 48, 44, 36, 32, 17, 6, + 76 }, + + { + + 62, + 8, 76, 62, 8, 76, 107, 88, 15, 10, 8, 28, 46, + 54, 19, 4, 80, 30, 82, 85, 3, 2, 2, 66, 10, + 40, 44, 26, 67, 105, 105, 101, 72, 82, 85, 3, + 88, 78, 24, 6, 81, 92, 101, 70, 71, 86, 100, + 0, 81, 97, 66, 79, 87, 100, 6, 76, 73, 88, 18, + 3, 22, 0, 0, 0, 76, 88, 97, 67, 14, 64, 25, + 81, 113, 102, 79, 85, 66, 68, 80, 79, 105, 66, + 90, 81, 101, 87, 79, 85, 81, 13, 0, 73, 101, + 75, 96, 83, 106, 65, 77, 72, 89, 12, 70, 65, + 89, 83, 79, 78, 69, 64, 66, 78, 1, 5, 69, 4, + 68, 69, 4, 73, 22, 68, 1, 17, 19, 26, 25, 15, + 81, 71, 75, 5, 67, 86, 67, 2, 71, 74, 68, 14, + 41, 17, 74, 81, 74, 68, 32, 38, 99, 67, 64, + 82, 4, 64, 65, 67, 33, 56, 43, 4, 77, 108, 15, + 51, 62, 111, 22, 21, 25, 27, 27, 22, 17, 21, + 25, 12, 19, 14, 6, 4, 64, 14, 12, 12, 11, 5, + 9, 18, 8, 71, 9, 10, 69, 17, 68, 57, 62, 47, + 37, 45, 54, 62, 61, 33, 17, 10, 14, 25, 23, + 88, 16, 19, 1, 34, 17, 17, 21, 25, 23, 30, 29, + 5, 93, 65, 67, 8, 110, 98, 66, 8, 10, 4, 75, + 80, 85, 84, 107, 85, 16, 5, 0, 70, 70, 77, 88, + 87, 101, 75, 21, 8, 3, 65, 65, 72, 76, 83, 95, + 76, 10, 64, 7, 5, 69, 76, 78, 90, 72, 30, 10, + 10, 6, 1, 69, 77, 76, 80, 62, 111, 109, 102, + 108, 100, 101, 100, 99, 96, 98, 98, 98, 87, + 91, 92, 97, 92, 88, 68, 82, 85, 82, 75, 71, + 72, 66, 76, 67, 67, 67, 68, 3, 68, 77, 65, 68, + 74, 4, 1, 66, 0, 9, 0, 71, 74, 73, 78, 67, 2, + 1, 4, 69, 66, 69, 6, 5, 71, 74, 75, 75, 4, 62, + 61, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, + 62, 53, 30, 62, 62, 62, 62, 53, 41, 29, 39, + 36, 26, 12, 6, 64, 79, 55, 52, 55, 40, 25, 29, + 22, 18, 26, 21, 14, 0, 11, 65, 76, 20, 24, 15, + 24, 29, 17, 13, 7, 6, 71, 1, 69, 80, 112, 67, + 71, 78, 16, 0, 72, 75, 68, 65, 65, 1, 1, 8, + 66, 0, 1, 7, 16, 14, 18, 3, 59, 53, 46, 42, + 34, 30, 15, 4, 77, 64, 31, 25, 22, 14, 13, 6, + 3, 0, 72, 75, 68, 65, 65, 1, 1, 8, 66, 0, 1, + 7, 16, 14, 18, 3, 59, 53, 46, 42, 34, 30, 15, + 4, 77 }, + + { + + 62, + 8, 76, 62, 8, 76, 106, 86, 15, 10, 7, 26, 45, + 53, 20, 4, 78, 28, 82, 84, 3, 2, 3, 67, 8, 38, + 42, 23, 69, 106, 103, 100, 72, 82, 84, 3, 88, + 77, 24, 5, 80, 91, 100, 71, 73, 87, 100, 0, + 81, 96, 66, 79, 87, 100, 6, 76, 73, 88, 19, 3, + 22, 0, 0, 0, 76, 89, 97, 66, 13, 64, 24, 81, + 112, 100, 78, 84, 65, 67, 80, 78, 103, 65, 89, + 80, 99, 88, 79, 85, 81, 13, 64, 72, 99, 75, + 95, 82, 104, 65, 77, 72, 89, 12, 70, 65, 88, + 83, 79, 78, 69, 64, 66, 77, 1, 5, 69, 3, 68, + 69, 4, 73, 22, 68, 1, 18, 19, 26, 25, 15, 82, + 71, 74, 5, 67, 86, 67, 3, 71, 74, 67, 15, 42, + 18, 72, 81, 75, 68, 32, 38, 100, 67, 0, 83, 5, + 64, 65, 66, 33, 56, 43, 5, 78, 106, 14, 48, + 60, 110, 21, 21, 24, 26, 26, 22, 16, 21, 24, + 11, 18, 13, 6, 4, 64, 13, 11, 11, 10, 4, 8, + 17, 8, 72, 8, 9, 69, 15, 69, 55, 62, 45, 35, + 43, 52, 62, 58, 32, 15, 7, 12, 23, 21, 89, 14, + 18, 64, 33, 16, 16, 20, 24, 21, 28, 27, 3, 93, + 66, 68, 6, 109, 97, 65, 8, 10, 4, 74, 79, 84, + 83, 105, 84, 17, 5, 1, 69, 69, 76, 86, 86, 99, + 74, 21, 8, 3, 64, 64, 71, 76, 83, 94, 76, 11, + 0, 7, 5, 68, 76, 78, 89, 71, 30, 11, 10, 6, 2, + 68, 76, 76, 79, 62, 110, 108, 101, 106, 99, + 99, 99, 97, 95, 97, 96, 96, 86, 90, 91, 96, + 89, 88, 69, 82, 84, 82, 75, 71, 72, 67, 75, + 67, 68, 67, 68, 4, 68, 77, 65, 68, 74, 3, 1, + 66, 64, 8, 0, 71, 74, 72, 79, 67, 2, 0, 4, 69, + 66, 69, 6, 4, 71, 73, 75, 75, 3, 62, 60, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 50, + 28, 62, 62, 62, 62, 50, 39, 27, 37, 33, 24, + 10, 5, 65, 79, 52, 50, 53, 38, 23, 27, 20, 16, + 24, 19, 12, 65, 8, 67, 77, 19, 23, 12, 22, 27, + 15, 11, 6, 5, 72, 0, 70, 80, 111, 68, 72, 79, + 15, 64, 73, 75, 68, 65, 64, 2, 1, 9, 66, 1, 2, + 8, 16, 15, 18, 4, 59, 51, 44, 40, 32, 28, 13, + 2, 79, 64, 32, 25, 23, 15, 13, 6, 3, 0, 71, + 75, 68, 65, 64, 2, 1, 9, 66, 1, 2, 8, 16, 15, + 18, 4, 59, 51, 44, 40, 32, 28, 13, 2, 79 }, + + { + + 62, + 8, 76, 62, 8, 76, 104, 85, 15, 10, 7, 24, 43, + 52, 21, 4, 77, 26, 81, 82, 2, 2, 4, 68, 7, 37, + 40, 20, 72, 106, 101, 99, 72, 81, 82, 2, 88, + 76, 24, 5, 80, 90, 98, 72, 74, 87, 100, 0, 80, + 95, 67, 79, 87, 100, 6, 75, 73, 87, 19, 3, 22, + 0, 0, 0, 75, 89, 97, 66, 12, 64, 24, 80, 110, + 97, 77, 83, 0, 65, 79, 77, 101, 64, 88, 79, + 96, 88, 80, 85, 82, 13, 64, 71, 96, 74, 95, + 82, 102, 65, 77, 72, 88, 12, 70, 64, 87, 82, + 78, 77, 69, 0, 65, 76, 2, 5, 70, 3, 67, 69, 4, + 73, 23, 68, 1, 18, 20, 26, 25, 15, 82, 70, 74, + 4, 67, 85, 67, 4, 70, 73, 65, 17, 43, 20, 70, + 82, 76, 67, 33, 38, 101, 67, 0, 83, 6, 64, 65, + 64, 33, 56, 43, 7, 79, 105, 13, 46, 57, 109, + 21, 21, 24, 26, 26, 22, 16, 21, 24, 10, 17, + 13, 6, 4, 64, 13, 11, 10, 10, 4, 8, 16, 7, 73, + 7, 9, 69, 14, 69, 53, 62, 44, 34, 42, 50, 62, + 56, 31, 13, 5, 11, 21, 19, 90, 13, 17, 65, 32, + 15, 15, 18, 22, 19, 26, 26, 2, 93, 67, 69, 4, + 107, 95, 64, 9, 10, 4, 73, 78, 83, 83, 103, + 82, 18, 6, 1, 68, 67, 75, 85, 85, 96, 73, 22, + 9, 4, 64, 0, 70, 75, 82, 93, 75, 12, 1, 7, 5, + 67, 75, 77, 88, 70, 30, 12, 10, 6, 3, 67, 75, + 75, 78, 62, 109, 106, 99, 105, 98, 98, 97, 95, + 93, 95, 94, 93, 85, 89, 90, 95, 86, 87, 70, + 81, 83, 81, 75, 71, 72, 67, 75, 68, 69, 67, + 68, 5, 68, 77, 65, 68, 73, 3, 0, 66, 65, 7, + 64, 71, 74, 71, 79, 68, 3, 0, 3, 69, 66, 69, + 5, 4, 71, 73, 75, 75, 1, 62, 59, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 60, 48, 26, 62, + 62, 62, 62, 47, 37, 26, 35, 31, 22, 9, 4, 66, + 79, 50, 47, 50, 36, 21, 25, 19, 14, 22, 17, + 10, 67, 6, 68, 79, 18, 22, 10, 21, 26, 14, 9, + 5, 4, 73, 64, 71, 80, 110, 70, 73, 81, 13, 65, + 74, 75, 67, 64, 0, 3, 2, 9, 65, 2, 3, 9, 17, + 16, 19, 4, 58, 49, 42, 38, 30, 26, 10, 0, 80, + 0, 32, 26, 23, 16, 14, 7, 4, 0, 70, 75, 67, + 64, 0, 3, 2, 9, 65, 2, 3, 9, 17, 16, 19, 4, + 58, 49, 42, 38, 30, 26, 10, 0, 80 }, + + { + + 61, + 8, 76, 61, 8, 76, 102, 83, 16, 10, 6, 22, 42, + 51, 21, 4, 75, 25, 81, 81, 2, 1, 5, 69, 6, 36, + 38, 16, 74, 107, 99, 97, 72, 81, 81, 2, 87, + 75, 25, 5, 79, 89, 97, 73, 75, 87, 101, 0, 80, + 94, 67, 80, 87, 100, 6, 75, 73, 86, 20, 3, 22, + 0, 0, 0, 75, 89, 97, 65, 11, 64, 23, 80, 109, + 95, 76, 82, 1, 64, 78, 76, 99, 0, 87, 78, 94, + 89, 80, 85, 82, 14, 64, 70, 94, 74, 94, 81, + 100, 65, 77, 72, 88, 13, 70, 64, 86, 82, 77, + 77, 69, 0, 65, 75, 2, 5, 70, 3, 67, 69, 4, 73, + 23, 68, 1, 19, 20, 27, 25, 16, 83, 70, 73, 4, + 67, 85, 67, 5, 70, 73, 64, 18, 44, 21, 68, 82, + 77, 67, 33, 38, 102, 67, 1, 84, 6, 64, 65, 0, + 33, 56, 43, 8, 80, 103, 12, 43, 54, 107, 21, + 21, 23, 25, 25, 21, 15, 21, 23, 9, 16, 13, 6, + 4, 65, 12, 10, 9, 9, 4, 8, 15, 7, 74, 7, 8, + 69, 13, 70, 51, 60, 42, 32, 40, 48, 62, 53, + 30, 11, 3, 9, 19, 17, 92, 12, 16, 67, 31, 14, + 14, 17, 21, 17, 24, 24, 0, 93, 68, 70, 3, 106, + 94, 1, 9, 10, 4, 72, 77, 82, 82, 101, 81, 19, + 7, 2, 68, 66, 74, 83, 84, 94, 73, 22, 9, 4, 0, + 1, 70, 75, 81, 92, 75, 14, 2, 7, 5, 66, 75, + 77, 87, 69, 31, 13, 11, 6, 4, 67, 74, 75, 78, + 62, 107, 105, 98, 103, 97, 96, 96, 94, 92, 93, + 92, 91, 85, 88, 90, 94, 83, 87, 71, 81, 82, + 80, 75, 71, 72, 67, 74, 68, 70, 67, 68, 5, 68, + 77, 65, 68, 73, 2, 0, 66, 66, 6, 64, 71, 74, + 70, 80, 68, 4, 64, 3, 69, 66, 68, 5, 3, 70, + 72, 75, 75, 0, 62, 58, 61, 61, 61, 62, 62, 62, + 61, 62, 62, 62, 57, 45, 24, 62, 60, 59, 60, + 44, 35, 24, 33, 28, 20, 8, 3, 67, 79, 48, 45, + 48, 34, 19, 23, 17, 12, 20, 15, 8, 69, 4, 70, + 80, 17, 21, 8, 19, 24, 12, 7, 3, 2, 74, 65, + 72, 80, 109, 71, 75, 82, 12, 66, 75, 75, 67, + 64, 0, 4, 3, 10, 65, 3, 4, 10, 17, 17, 19, 5, + 58, 47, 40, 36, 28, 24, 8, 65, 81, 0, 33, 26, + 24, 16, 15, 7, 4, 1, 70, 75, 67, 64, 0, 4, 3, + 10, 65, 3, 4, 10, 17, 17, 19, 5, 58, 47, 40, + 36, 28, 24, 8, 65, 81 }, + + { + + 60, + 8, 76, 60, 8, 76, 100, 82, 16, 10, 6, 20, 40, + 50, 22, 4, 73, 23, 81, 79, 2, 1, 6, 70, 5, 35, + 36, 13, 76, 108, 97, 96, 72, 81, 79, 2, 87, + 74, 25, 5, 78, 88, 96, 74, 76, 87, 101, 0, 80, + 93, 67, 80, 87, 100, 6, 75, 73, 85, 20, 3, 22, + 0, 0, 0, 74, 89, 97, 65, 10, 64, 23, 79, 108, + 93, 75, 81, 3, 1, 77, 75, 97, 1, 86, 77, 92, + 90, 80, 85, 82, 14, 64, 69, 92, 74, 93, 80, + 98, 65, 77, 72, 88, 13, 70, 0, 85, 82, 76, 77, + 69, 0, 64, 74, 3, 5, 70, 3, 67, 69, 4, 73, 23, + 68, 1, 19, 20, 27, 25, 16, 83, 69, 72, 3, 67, + 85, 67, 6, 70, 72, 0, 19, 45, 23, 66, 83, 78, + 66, 33, 38, 103, 67, 1, 84, 7, 64, 65, 1, 33, + 56, 43, 9, 81, 102, 11, 40, 51, 106, 21, 21, + 23, 24, 25, 21, 15, 21, 23, 8, 15, 13, 6, 4, + 65, 12, 9, 8, 9, 4, 8, 14, 6, 75, 6, 7, 69, + 12, 70, 49, 58, 40, 31, 38, 46, 59, 51, 29, 9, + 1, 8, 17, 15, 93, 11, 15, 69, 30, 13, 13, 16, + 19, 15, 22, 22, 64, 93, 69, 71, 1, 104, 92, 2, + 10, 10, 4, 71, 76, 81, 81, 99, 80, 20, 8, 3, + 67, 65, 73, 82, 83, 92, 72, 23, 10, 5, 0, 2, + 69, 74, 80, 91, 75, 15, 3, 7, 5, 65, 74, 77, + 86, 68, 31, 14, 11, 6, 5, 66, 73, 74, 77, 62, + 106, 103, 96, 101, 96, 95, 94, 92, 90, 91, 90, + 89, 84, 87, 89, 93, 80, 87, 72, 81, 81, 79, + 75, 71, 72, 67, 74, 68, 71, 67, 68, 6, 68, 77, + 65, 68, 73, 2, 0, 66, 67, 5, 64, 71, 74, 69, + 81, 68, 5, 65, 3, 69, 66, 68, 5, 3, 70, 71, + 75, 75, 65, 61, 57, 60, 59, 59, 62, 62, 62, + 59, 60, 62, 61, 54, 42, 22, 61, 57, 55, 55, + 41, 33, 22, 31, 26, 18, 7, 2, 68, 79, 46, 43, + 46, 32, 17, 21, 16, 10, 18, 13, 6, 71, 2, 72, + 81, 16, 20, 6, 18, 22, 11, 5, 2, 1, 75, 66, + 73, 80, 108, 72, 76, 84, 11, 67, 76, 75, 66, + 0, 1, 5, 4, 11, 65, 4, 5, 11, 18, 18, 20, 6, + 57, 45, 38, 34, 26, 22, 6, 67, 82, 1, 33, 27, + 25, 17, 16, 8, 5, 1, 69, 75, 66, 0, 1, 5, 4, + 11, 65, 4, 5, 11, 18, 18, 20, 6, 57, 45, 38, + 34, 26, 22, 6, 67, 82 }, + + { + + 58, + 7, 77, 58, 7, 77, 99, 81, 16, 10, 5, 18, 38, + 49, 22, 4, 72, 21, 81, 78, 1, 0, 7, 71, 3, 33, + 34, 9, 79, 109, 95, 95, 72, 81, 78, 1, 87, 74, + 25, 4, 78, 88, 95, 76, 78, 88, 102, 64, 80, + 93, 68, 81, 87, 100, 5, 75, 73, 85, 20, 2, 22, + 0, 0, 0, 74, 90, 97, 65, 9, 65, 22, 79, 107, + 91, 74, 81, 4, 2, 77, 74, 96, 1, 85, 77, 90, + 91, 81, 86, 83, 14, 65, 69, 90, 74, 93, 80, + 97, 65, 77, 72, 88, 13, 71, 0, 85, 82, 76, 77, + 69, 0, 64, 73, 3, 5, 71, 2, 67, 69, 3, 73, 23, + 68, 1, 19, 20, 27, 25, 16, 84, 69, 72, 2, 67, + 85, 68, 6, 70, 72, 1, 20, 45, 24, 65, 84, 80, + 66, 33, 38, 104, 68, 1, 85, 7, 65, 65, 2, 33, + 55, 43, 10, 82, 101, 9, 37, 47, 105, 20, 21, + 22, 23, 24, 20, 14, 20, 22, 7, 14, 12, 5, 3, + 66, 11, 8, 7, 8, 3, 7, 13, 5, 77, 5, 6, 69, + 10, 71, 46, 55, 38, 29, 36, 43, 55, 48, 27, 7, + 65, 6, 14, 13, 95, 9, 13, 71, 28, 12, 12, 14, + 17, 13, 20, 20, 66, 93, 70, 72, 64, 103, 91, + 3, 10, 10, 4, 71, 76, 81, 81, 98, 79, 20, 8, + 3, 67, 64, 72, 81, 83, 90, 72, 23, 10, 5, 0, + 2, 69, 74, 80, 90, 75, 16, 4, 7, 4, 65, 74, + 77, 86, 68, 31, 14, 11, 6, 6, 66, 73, 74, 77, + 62, 105, 102, 95, 100, 95, 94, 93, 91, 89, 90, + 89, 87, 84, 87, 89, 93, 77, 87, 74, 81, 81, + 79, 75, 71, 72, 68, 74, 69, 72, 68, 68, 6, 69, + 77, 66, 68, 73, 1, 64, 67, 68, 4, 65, 72, 74, + 68, 82, 69, 5, 66, 2, 69, 67, 68, 4, 2, 70, + 71, 75, 75, 67, 59, 56, 58, 57, 56, 62, 62, + 62, 56, 57, 62, 58, 50, 39, 20, 57, 53, 51, + 49, 38, 30, 20, 28, 23, 16, 5, 0, 69, 79, 43, + 40, 43, 30, 14, 19, 14, 7, 16, 10, 4, 74, 64, + 74, 83, 14, 18, 3, 16, 20, 9, 3, 0, 64, 76, + 67, 74, 81, 107, 74, 78, 86, 9, 69, 78, 75, + 66, 0, 1, 6, 4, 11, 65, 5, 5, 12, 18, 18, 20, + 6, 56, 43, 36, 31, 23, 20, 3, 69, 84, 1, 33, + 27, 25, 17, 16, 8, 5, 1, 69, 75, 66, 0, 1, 6, + 4, 11, 65, 5, 5, 12, 18, 18, 20, 6, 56, 43, + 36, 31, 23, 20, 3, 69, 84 }, + + { + + 57, + 7, 77, 57, 7, 77, 97, 79, 17, 11, 5, 17, 37, + 49, 23, 5, 70, 20, 80, 76, 1, 0, 9, 71, 2, 32, + 33, 6, 81, 109, 93, 93, 71, 80, 76, 1, 86, 73, + 26, 4, 77, 87, 93, 77, 79, 88, 102, 64, 79, + 92, 68, 81, 86, 99, 5, 74, 72, 84, 21, 2, 22, + 0, 0, 0, 73, 90, 97, 64, 9, 65, 22, 78, 105, + 88, 72, 80, 6, 4, 76, 72, 94, 2, 83, 76, 87, + 91, 81, 86, 83, 15, 65, 68, 87, 73, 92, 79, + 95, 65, 76, 72, 87, 14, 71, 1, 84, 81, 75, 76, + 68, 1, 0, 72, 4, 6, 71, 2, 66, 68, 3, 72, 24, + 67, 1, 20, 21, 28, 26, 17, 84, 68, 71, 2, 67, + 84, 68, 7, 69, 71, 3, 22, 46, 26, 0, 84, 81, + 65, 34, 39, 104, 68, 2, 85, 8, 65, 64, 4, 33, + 55, 44, 12, 83, 99, 8, 35, 44, 103, 20, 21, + 22, 23, 24, 20, 14, 20, 22, 7, 14, 12, 5, 3, + 66, 11, 8, 7, 8, 3, 7, 13, 5, 78, 5, 6, 69, 9, + 71, 44, 53, 37, 28, 35, 41, 52, 46, 26, 6, 67, + 5, 12, 12, 96, 8, 12, 72, 27, 12, 12, 13, 16, + 12, 19, 19, 67, 93, 70, 72, 65, 101, 89, 5, + 11, 10, 4, 70, 75, 80, 80, 96, 77, 21, 9, 4, + 66, 1, 71, 79, 82, 87, 71, 24, 11, 6, 1, 3, + 68, 73, 79, 88, 74, 18, 5, 8, 4, 64, 73, 76, + 85, 67, 32, 15, 12, 7, 7, 65, 72, 73, 76, 62, + 103, 100, 93, 98, 93, 92, 91, 89, 87, 88, 87, + 84, 83, 86, 88, 92, 73, 86, 75, 80, 80, 78, + 74, 71, 71, 68, 73, 69, 72, 68, 68, 7, 69, 77, + 66, 68, 72, 1, 64, 67, 68, 4, 65, 72, 73, 66, + 82, 69, 6, 66, 2, 69, 67, 67, 4, 2, 69, 70, + 74, 74, 68, 58, 55, 57, 56, 54, 60, 60, 59, + 54, 55, 59, 56, 47, 37, 18, 54, 50, 48, 44, + 36, 28, 19, 26, 21, 15, 4, 64, 69, 79, 41, 38, + 41, 28, 12, 18, 13, 5, 15, 8, 3, 76, 66, 75, + 84, 13, 17, 1, 15, 19, 8, 2, 64, 65, 77, 67, + 74, 81, 106, 75, 79, 87, 8, 70, 79, 74, 65, 1, + 2, 8, 5, 12, 64, 7, 6, 13, 19, 19, 21, 7, 56, + 42, 35, 29, 21, 19, 1, 70, 85, 2, 34, 28, 26, + 18, 17, 9, 6, 2, 68, 74, 65, 1, 2, 8, 5, 12, + 64, 7, 6, 13, 19, 19, 21, 7, 56, 42, 35, 29, + 21, 19, 1, 70, 85 }, + + { + + 56, + 7, 77, 56, 7, 77, 95, 78, 17, 11, 5, 15, 35, + 48, 24, 5, 68, 18, 80, 75, 1, 0, 10, 72, 1, + 31, 31, 3, 83, 110, 91, 92, 71, 80, 75, 1, 86, + 72, 26, 4, 76, 86, 92, 78, 80, 88, 102, 64, + 79, 91, 68, 81, 86, 99, 5, 74, 72, 83, 21, 2, + 22, 0, 0, 0, 72, 90, 97, 64, 8, 65, 22, 77, + 104, 86, 71, 79, 8, 6, 75, 71, 92, 3, 82, 75, + 85, 92, 81, 86, 83, 15, 65, 67, 85, 73, 91, + 78, 93, 65, 76, 72, 87, 14, 71, 1, 83, 81, 74, + 76, 68, 1, 0, 71, 5, 6, 71, 2, 66, 68, 3, 72, + 24, 67, 1, 20, 21, 28, 26, 17, 85, 67, 70, 1, + 67, 84, 68, 8, 69, 71, 4, 23, 47, 28, 2, 85, + 82, 65, 34, 39, 105, 68, 2, 86, 9, 65, 64, 5, + 33, 55, 44, 13, 84, 98, 7, 32, 41, 102, 20, + 21, 22, 22, 23, 20, 14, 20, 21, 6, 13, 12, 5, + 3, 66, 10, 7, 6, 8, 3, 7, 12, 4, 79, 4, 5, 69, + 8, 72, 42, 51, 35, 26, 33, 39, 49, 44, 25, 4, + 69, 3, 10, 10, 97, 7, 11, 74, 26, 11, 11, 12, + 14, 10, 17, 17, 69, 93, 71, 73, 67, 100, 88, + 6, 12, 10, 4, 69, 74, 79, 79, 94, 76, 22, 10, + 5, 65, 2, 70, 78, 81, 85, 70, 24, 11, 6, 1, 4, + 67, 72, 78, 87, 74, 19, 6, 8, 4, 0, 72, 76, + 84, 66, 32, 16, 12, 7, 8, 64, 71, 72, 75, 62, + 102, 98, 91, 96, 92, 91, 89, 87, 86, 86, 85, + 82, 82, 85, 87, 91, 70, 86, 76, 80, 79, 77, + 74, 71, 71, 68, 73, 69, 73, 68, 68, 8, 69, 77, + 66, 68, 72, 1, 64, 67, 69, 3, 65, 72, 73, 65, + 83, 69, 7, 67, 2, 69, 67, 67, 4, 2, 69, 69, + 74, 74, 70, 57, 54, 56, 54, 52, 57, 57, 56, + 52, 52, 56, 53, 44, 34, 16, 50, 46, 44, 39, + 33, 26, 17, 24, 19, 13, 3, 65, 70, 79, 39, 36, + 39, 26, 10, 16, 11, 3, 13, 6, 1, 78, 68, 77, + 85, 12, 16, 64, 13, 17, 6, 0, 65, 66, 78, 68, + 75, 81, 105, 76, 80, 89, 7, 71, 80, 74, 65, 2, + 3, 9, 6, 13, 64, 8, 7, 14, 19, 20, 22, 8, 55, + 40, 33, 27, 19, 17, 64, 72, 86, 3, 34, 28, 27, + 19, 18, 9, 7, 2, 67, 74, 65, 2, 3, 9, 6, 13, + 64, 8, 7, 14, 19, 20, 22, 8, 55, 40, 33, 27, + 19, 17, 64, 72, 86 }, + + { + + 55, + 7, 77, 55, 7, 77, 93, 76, 18, 11, 4, 13, 34, + 47, 24, 5, 66, 17, 80, 73, 1, 64, 11, 73, 0, + 30, 29, 64, 85, 111, 89, 90, 71, 80, 73, 1, + 85, 71, 27, 4, 75, 85, 91, 79, 81, 88, 103, + 64, 79, 90, 68, 82, 86, 99, 5, 74, 72, 82, 22, + 2, 22, 0, 0, 0, 72, 90, 97, 0, 7, 65, 21, 77, + 103, 84, 70, 78, 9, 7, 74, 70, 90, 4, 81, 74, + 83, 93, 81, 86, 83, 16, 65, 66, 83, 73, 90, + 77, 91, 65, 76, 72, 87, 15, 71, 2, 82, 81, 73, + 76, 68, 1, 1, 70, 5, 6, 71, 2, 66, 68, 3, 72, + 24, 67, 1, 21, 21, 29, 26, 18, 85, 67, 69, 1, + 67, 84, 68, 9, 69, 70, 5, 24, 48, 29, 4, 85, + 83, 64, 34, 39, 106, 68, 3, 86, 9, 65, 64, 6, + 33, 55, 44, 14, 85, 96, 6, 29, 38, 100, 20, + 21, 21, 21, 23, 19, 13, 20, 21, 5, 12, 12, 5, + 3, 67, 10, 6, 5, 7, 3, 7, 11, 4, 80, 4, 4, 69, + 7, 72, 40, 49, 33, 25, 31, 37, 46, 41, 24, 2, + 71, 2, 8, 8, 99, 6, 10, 76, 25, 10, 10, 11, + 13, 8, 15, 15, 70, 93, 72, 74, 68, 98, 86, 8, + 12, 10, 4, 68, 73, 78, 78, 92, 75, 23, 11, 6, + 65, 3, 69, 76, 80, 83, 70, 25, 12, 7, 2, 5, + 67, 72, 77, 86, 74, 21, 7, 8, 4, 1, 72, 76, + 83, 65, 33, 17, 13, 7, 9, 64, 70, 72, 75, 62, + 100, 97, 90, 94, 91, 89, 88, 86, 84, 84, 83, + 80, 82, 84, 87, 90, 67, 86, 77, 80, 78, 76, + 74, 71, 71, 68, 72, 69, 74, 68, 68, 8, 69, 77, + 66, 68, 72, 0, 64, 67, 70, 2, 65, 72, 73, 64, + 84, 69, 8, 68, 2, 69, 67, 66, 4, 1, 68, 68, + 74, 74, 71, 56, 53, 55, 52, 50, 55, 55, 53, + 49, 49, 53, 50, 41, 31, 14, 46, 43, 40, 34, + 30, 24, 15, 22, 16, 11, 2, 66, 71, 79, 37, 34, + 37, 24, 8, 14, 10, 1, 11, 4, 64, 80, 70, 79, + 86, 11, 15, 66, 12, 15, 5, 65, 67, 68, 79, 69, + 76, 81, 104, 77, 82, 90, 6, 72, 81, 74, 64, 2, + 3, 10, 7, 14, 64, 9, 8, 15, 20, 21, 22, 9, 55, + 38, 31, 25, 17, 15, 66, 74, 87, 3, 35, 29, 28, + 19, 19, 10, 7, 3, 67, 74, 64, 2, 3, 10, 7, 14, + 64, 9, 8, 15, 20, 21, 22, 9, 55, 38, 31, 25, + 17, 15, 66, 74, 87 }, + + { + + 53, + 7, 77, 53, 7, 77, 92, 75, 18, 11, 4, 11, 32, + 46, 25, 5, 65, 15, 80, 72, 0, 64, 12, 74, 65, + 28, 27, 67, 88, 112, 87, 89, 71, 80, 72, 0, + 85, 70, 27, 3, 75, 84, 89, 80, 83, 89, 103, + 64, 78, 89, 69, 82, 86, 99, 5, 74, 72, 82, 22, + 2, 22, 0, 0, 0, 71, 91, 97, 0, 6, 65, 21, 76, + 102, 82, 69, 77, 11, 9, 74, 69, 88, 5, 80, 73, + 80, 93, 82, 86, 84, 16, 66, 65, 80, 72, 90, + 77, 89, 65, 76, 72, 87, 15, 71, 2, 81, 81, 73, + 75, 68, 1, 1, 69, 6, 6, 72, 1, 66, 68, 3, 72, + 24, 67, 1, 21, 21, 29, 26, 18, 86, 66, 69, 0, + 67, 83, 68, 10, 68, 70, 7, 26, 49, 31, 6, 86, + 84, 64, 35, 39, 107, 68, 3, 87, 10, 65, 64, 7, + 33, 55, 44, 16, 86, 95, 5, 26, 35, 99, 19, 21, + 21, 21, 22, 19, 13, 20, 20, 4, 11, 11, 5, 3, + 67, 9, 6, 4, 7, 2, 6, 10, 3, 81, 3, 3, 69, 5, + 73, 38, 47, 31, 23, 30, 35, 42, 39, 23, 0, 74, + 0, 6, 6, 100, 4, 9, 77, 24, 9, 9, 9, 11, 6, + 13, 14, 72, 93, 73, 75, 70, 97, 85, 9, 13, 10, + 4, 67, 72, 77, 78, 90, 73, 24, 11, 6, 64, 5, + 68, 75, 79, 80, 69, 25, 12, 7, 2, 6, 66, 71, + 77, 85, 74, 22, 8, 8, 4, 2, 71, 76, 82, 64, + 33, 18, 13, 7, 10, 0, 69, 71, 74, 62, 99, 95, + 88, 93, 90, 88, 86, 84, 83, 83, 81, 77, 81, + 83, 86, 89, 64, 85, 78, 79, 77, 76, 74, 71, + 71, 69, 72, 70, 75, 68, 68, 9, 69, 77, 66, 68, + 71, 0, 65, 67, 71, 1, 66, 72, 73, 0, 85, 70, + 8, 68, 1, 69, 67, 66, 3, 1, 68, 68, 74, 74, + 73, 55, 52, 54, 51, 47, 52, 52, 50, 47, 46, + 49, 47, 37, 29, 12, 42, 39, 36, 29, 27, 22, + 13, 20, 14, 9, 0, 67, 72, 79, 34, 31, 34, 22, + 6, 12, 8, 64, 9, 2, 66, 82, 73, 80, 88, 10, + 14, 69, 10, 14, 3, 67, 68, 69, 80, 70, 77, 81, + 103, 79, 83, 92, 4, 73, 82, 74, 64, 3, 4, 11, + 7, 14, 0, 10, 9, 16, 20, 22, 23, 9, 54, 36, + 29, 23, 15, 13, 69, 76, 89, 4, 35, 29, 28, 20, + 19, 10, 8, 3, 66, 74, 64, 3, 4, 11, 7, 14, 0, + 10, 9, 16, 20, 22, 23, 9, 54, 36, 29, 23, 15, + 13, 69, 76, 89 }, + + { + + 52, + 7, 77, 52, 7, 77, 90, 73, 18, 11, 3, 9, 31, + 45, 26, 5, 0, 13, 79, 70, 0, 64, 13, 75, 66, + 27, 25, 70, 90, 112, 85, 88, 71, 79, 70, 0, + 85, 69, 27, 3, 74, 83, 88, 81, 84, 89, 103, + 64, 78, 88, 69, 82, 86, 99, 5, 73, 72, 81, 23, + 2, 22, 0, 0, 0, 71, 91, 97, 1, 5, 65, 20, 76, + 100, 79, 68, 76, 12, 10, 73, 68, 86, 6, 79, + 72, 78, 94, 82, 86, 84, 16, 66, 64, 78, 72, + 89, 76, 87, 65, 76, 72, 86, 15, 71, 3, 80, 80, + 72, 75, 68, 2, 2, 68, 6, 6, 72, 1, 65, 68, 3, + 72, 25, 67, 1, 22, 22, 29, 26, 18, 86, 66, 68, + 0, 67, 83, 68, 11, 68, 69, 8, 27, 50, 32, 8, + 86, 85, 0, 35, 39, 108, 68, 4, 87, 11, 65, 64, + 9, 33, 55, 44, 17, 87, 93, 4, 24, 32, 98, 19, + 21, 20, 20, 22, 19, 12, 20, 20, 3, 10, 11, 5, + 3, 67, 9, 5, 3, 6, 2, 6, 9, 3, 82, 2, 3, 69, + 4, 73, 36, 45, 30, 22, 28, 33, 39, 36, 22, 65, + 76, 64, 4, 4, 101, 3, 8, 79, 23, 8, 8, 8, 10, + 4, 11, 12, 73, 93, 74, 76, 72, 95, 83, 10, 13, + 10, 4, 66, 71, 76, 77, 88, 72, 25, 12, 7, 0, + 6, 67, 73, 78, 78, 68, 26, 13, 8, 3, 7, 65, + 71, 76, 84, 73, 23, 9, 8, 4, 3, 71, 75, 81, 0, + 33, 19, 13, 7, 11, 1, 68, 71, 73, 62, 98, 94, + 87, 91, 89, 86, 85, 82, 81, 81, 79, 75, 80, + 82, 85, 88, 2, 85, 79, 79, 76, 75, 74, 71, 71, + 69, 71, 70, 76, 68, 68, 10, 69, 77, 66, 68, + 71, 64, 65, 67, 72, 0, 66, 72, 73, 1, 85, 70, + 9, 69, 1, 69, 67, 66, 3, 0, 68, 67, 74, 74, + 74, 54, 51, 53, 49, 45, 50, 49, 47, 44, 43, + 46, 44, 34, 26, 10, 38, 36, 32, 24, 24, 20, + 12, 18, 11, 7, 64, 68, 73, 79, 32, 29, 32, 20, + 4, 10, 7, 66, 7, 0, 68, 84, 75, 82, 89, 9, 13, + 71, 9, 12, 2, 69, 69, 70, 81, 71, 78, 81, 102, + 80, 84, 93, 3, 74, 83, 74, 0, 3, 5, 12, 8, 15, + 0, 11, 10, 17, 21, 23, 23, 10, 54, 34, 27, 21, + 13, 11, 71, 78, 90, 4, 36, 30, 29, 21, 20, 11, + 8, 3, 65, 74, 0, 3, 5, 12, 8, 15, 0, 11, 10, + 17, 21, 23, 23, 10, 54, 34, 27, 21, 13, 11, + 71, 78, 90 }, + + { + + 51, + 7, 78, 51, 7, 78, 88, 72, 19, 11, 3, 8, 29, + 44, 26, 6, 2, 12, 79, 69, 0, 65, 14, 76, 67, + 26, 24, 74, 92, 113, 83, 86, 71, 79, 69, 0, + 84, 68, 28, 3, 73, 82, 87, 83, 85, 89, 104, + 64, 78, 88, 69, 83, 85, 99, 4, 73, 72, 80, 23, + 2, 22, 0, 0, 0, 70, 91, 97, 1, 5, 66, 20, 75, + 99, 77, 67, 75, 14, 12, 72, 67, 84, 7, 78, 72, + 76, 95, 82, 86, 84, 17, 66, 0, 76, 72, 88, 75, + 85, 65, 76, 72, 86, 16, 72, 3, 80, 80, 71, 75, + 67, 2, 2, 67, 7, 6, 72, 1, 65, 68, 3, 72, 25, + 67, 1, 22, 22, 30, 27, 19, 87, 65, 67, 64, 67, + 83, 68, 12, 68, 69, 9, 28, 50, 34, 9, 87, 86, + 0, 35, 39, 108, 69, 4, 88, 11, 66, 0, 10, 33, + 55, 45, 18, 88, 92, 3, 21, 29, 96, 19, 21, 20, + 19, 21, 18, 12, 19, 19, 3, 10, 11, 4, 3, 68, + 8, 4, 2, 6, 2, 6, 9, 2, 84, 2, 2, 69, 3, 74, + 33, 43, 28, 20, 26, 31, 36, 34, 20, 67, 78, + 66, 2, 2, 103, 2, 6, 81, 21, 7, 7, 7, 8, 2, 9, + 10, 75, 93, 75, 77, 73, 94, 82, 12, 14, 10, 4, + 65, 71, 76, 76, 87, 71, 26, 13, 8, 0, 7, 66, + 72, 77, 76, 68, 26, 13, 8, 3, 8, 65, 70, 75, + 83, 73, 25, 10, 8, 4, 3, 70, 75, 81, 0, 34, + 19, 14, 7, 12, 1, 68, 70, 73, 62, 96, 92, 85, + 89, 87, 85, 83, 81, 80, 79, 77, 73, 80, 81, + 85, 88, 5, 85, 80, 79, 76, 74, 74, 71, 71, 69, + 71, 70, 77, 68, 68, 10, 69, 77, 66, 68, 71, + 64, 65, 67, 73, 64, 66, 72, 73, 2, 86, 70, 10, + 70, 1, 69, 67, 65, 3, 0, 67, 66, 74, 74, 76, + 53, 50, 52, 47, 43, 47, 47, 44, 42, 40, 43, + 41, 31, 23, 8, 35, 32, 28, 19, 22, 17, 10, 16, + 9, 5, 65, 69, 74, 79, 30, 27, 30, 18, 2, 9, 5, + 68, 5, 66, 70, 86, 77, 84, 90, 8, 11, 73, 7, + 10, 0, 71, 71, 72, 82, 72, 79, 82, 101, 81, + 86, 95, 2, 76, 84, 73, 0, 4, 5, 13, 9, 16, 0, + 12, 11, 18, 21, 24, 24, 11, 53, 32, 25, 19, + 11, 9, 73, 80, 91, 5, 36, 30, 30, 21, 21, 11, + 9, 4, 65, 73, 0, 4, 5, 13, 9, 16, 0, 12, 11, + 18, 21, 24, 24, 11, 53, 32, 25, 19, 11, 9, 73, + 80, 91 }, + + { + + 50, + 7, 78, 50, 7, 78, 86, 70, 19, 11, 2, 6, 28, + 43, 27, 6, 3, 10, 79, 67, 64, 65, 15, 77, 68, + 25, 22, 77, 95, 114, 81, 85, 71, 79, 67, 64, + 84, 67, 28, 3, 73, 81, 85, 84, 86, 89, 104, + 64, 77, 87, 70, 83, 85, 99, 4, 73, 72, 79, 24, + 2, 22, 0, 0, 0, 70, 91, 97, 2, 4, 66, 19, 75, + 98, 75, 66, 74, 15, 13, 71, 66, 82, 8, 77, 71, + 73, 95, 83, 86, 85, 17, 66, 1, 73, 71, 88, 75, + 83, 65, 76, 72, 86, 16, 72, 4, 79, 80, 70, 74, + 67, 2, 3, 66, 7, 6, 73, 1, 65, 68, 3, 72, 25, + 67, 1, 23, 22, 30, 27, 19, 87, 65, 67, 64, 67, + 82, 68, 13, 67, 68, 11, 30, 51, 35, 11, 87, + 87, 1, 36, 39, 109, 69, 5, 88, 12, 66, 0, 11, + 33, 55, 45, 20, 89, 90, 2, 18, 26, 95, 19, 21, + 19, 19, 21, 18, 11, 19, 19, 2, 9, 11, 4, 3, + 68, 8, 4, 1, 5, 2, 6, 8, 2, 85, 1, 1, 69, 2, + 74, 31, 41, 26, 19, 25, 29, 33, 31, 19, 69, + 80, 67, 0, 0, 104, 1, 5, 82, 20, 6, 6, 5, 7, + 0, 7, 9, 76, 93, 76, 78, 75, 92, 80, 13, 14, + 10, 4, 64, 70, 75, 76, 85, 69, 27, 14, 8, 1, + 9, 65, 70, 76, 73, 67, 27, 14, 9, 4, 9, 64, + 70, 74, 82, 73, 26, 11, 8, 4, 4, 70, 75, 80, + 1, 34, 20, 14, 7, 13, 2, 67, 70, 72, 62, 95, + 91, 84, 88, 86, 83, 82, 79, 78, 77, 75, 70, + 79, 80, 84, 87, 8, 84, 81, 78, 75, 73, 74, 71, + 71, 69, 70, 71, 78, 68, 68, 11, 69, 77, 66, + 68, 70, 65, 66, 67, 74, 65, 67, 72, 73, 3, 87, + 71, 11, 70, 0, 69, 67, 65, 2, 64, 67, 66, 74, + 74, 77, 52, 49, 51, 46, 40, 45, 44, 41, 39, + 37, 40, 38, 28, 21, 6, 31, 29, 24, 14, 19, 15, + 8, 14, 6, 3, 66, 70, 75, 79, 28, 24, 27, 16, + 0, 7, 4, 70, 3, 68, 72, 88, 79, 85, 92, 7, 10, + 75, 6, 9, 64, 73, 72, 73, 83, 73, 80, 82, 100, + 83, 87, 96, 0, 77, 85, 73, 1, 4, 6, 14, 10, + 16, 1, 13, 12, 19, 22, 25, 24, 11, 53, 30, 23, + 17, 9, 7, 76, 82, 92, 5, 37, 31, 30, 22, 22, + 12, 9, 4, 64, 73, 1, 4, 6, 14, 10, 16, 1, 13, + 12, 19, 22, 25, 24, 11, 53, 30, 23, 17, 9, 7, + 76, 82, 92 }, + + { + + 48, + 6, 78, 48, 6, 78, 85, 69, 19, 11, 2, 4, 26, + 42, 27, 6, 5, 8, 79, 66, 64, 66, 16, 78, 70, + 23, 20, 81, 97, 115, 79, 84, 71, 79, 66, 64, + 84, 67, 28, 2, 72, 80, 84, 85, 88, 90, 105, + 64, 77, 86, 70, 84, 85, 99, 4, 73, 72, 79, 24, + 2, 22, 0, 0, 0, 69, 92, 97, 2, 3, 66, 19, 74, + 97, 73, 65, 74, 17, 15, 71, 65, 80, 8, 76, 70, + 71, 96, 83, 87, 85, 17, 67, 1, 71, 71, 87, 74, + 82, 65, 76, 72, 86, 16, 72, 4, 78, 80, 70, 74, + 67, 2, 3, 65, 8, 6, 73, 0, 65, 68, 2, 72, 25, + 67, 1, 23, 22, 30, 27, 19, 88, 64, 66, 65, 67, + 82, 68, 14, 67, 68, 12, 31, 52, 37, 13, 88, + 88, 1, 36, 39, 110, 69, 5, 89, 12, 66, 0, 12, + 33, 55, 45, 21, 90, 89, 1, 15, 22, 94, 18, 21, + 19, 18, 20, 17, 11, 19, 18, 1, 8, 10, 4, 2, + 69, 7, 3, 0, 5, 1, 5, 7, 1, 86, 0, 0, 69, 0, + 75, 29, 39, 24, 17, 23, 26, 29, 29, 18, 71, + 83, 69, 66, 65, 106, 64, 4, 84, 19, 5, 5, 4, + 5, 65, 5, 7, 78, 93, 77, 79, 77, 91, 79, 14, + 15, 10, 4, 64, 69, 74, 75, 83, 68, 27, 14, 9, + 1, 10, 64, 69, 75, 71, 67, 27, 14, 9, 4, 9, + 64, 69, 74, 81, 73, 27, 12, 8, 4, 5, 69, 75, + 79, 2, 34, 21, 14, 7, 14, 2, 66, 69, 72, 62, + 94, 89, 82, 86, 85, 82, 80, 78, 77, 76, 73, + 68, 79, 79, 84, 86, 11, 84, 82, 78, 74, 73, + 74, 71, 71, 70, 70, 71, 79, 68, 68, 11, 69, + 77, 67, 68, 70, 65, 66, 68, 75, 66, 67, 73, + 73, 4, 88, 71, 11, 71, 0, 69, 68, 65, 2, 64, + 67, 65, 74, 74, 79, 51, 48, 50, 44, 38, 42, + 41, 38, 37, 34, 36, 35, 24, 18, 4, 27, 25, 20, + 9, 16, 13, 6, 11, 4, 1, 68, 72, 76, 79, 25, + 22, 25, 14, 66, 5, 2, 73, 1, 70, 74, 90, 82, + 87, 93, 5, 9, 78, 4, 7, 66, 75, 74, 75, 84, + 74, 81, 82, 99, 84, 89, 98, 64, 78, 86, 73, 1, + 5, 6, 15, 10, 17, 1, 14, 12, 20, 22, 25, 25, + 12, 52, 28, 21, 15, 7, 5, 78, 84, 94, 6, 37, + 31, 31, 22, 22, 12, 10, 4, 64, 73, 1, 5, 6, + 15, 10, 17, 1, 14, 12, 20, 22, 25, 25, 12, 52, + 28, 21, 15, 7, 5, 78, 84, 94 }, + + { + + 47, + 6, 78, 47, 6, 78, 83, 68, 20, 11, 2, 2, 24, + 42, 28, 6, 7, 7, 78, 64, 64, 66, 17, 78, 71, + 22, 18, 84, 99, 115, 77, 82, 70, 78, 64, 64, + 83, 66, 29, 2, 71, 79, 83, 86, 89, 90, 105, + 64, 77, 85, 70, 84, 85, 99, 4, 72, 71, 78, 24, + 2, 22, 0, 0, 0, 68, 92, 97, 2, 2, 66, 19, 73, + 95, 70, 64, 73, 19, 17, 70, 64, 78, 9, 74, 69, + 69, 97, 83, 87, 85, 18, 67, 2, 69, 71, 86, 73, + 80, 65, 75, 72, 85, 17, 72, 5, 77, 79, 69, 74, + 67, 3, 4, 64, 9, 6, 73, 0, 64, 67, 2, 72, 26, + 67, 1, 23, 23, 31, 27, 20, 88, 0, 65, 66, 67, + 82, 68, 15, 67, 67, 13, 32, 53, 39, 15, 89, + 89, 2, 36, 40, 111, 69, 5, 89, 13, 66, 0, 14, + 33, 55, 45, 22, 91, 88, 0, 13, 19, 92, 18, 21, + 19, 17, 20, 17, 11, 19, 18, 0, 7, 10, 4, 2, + 69, 7, 2, 0, 5, 1, 5, 6, 0, 87, 0, 0, 69, 64, + 75, 27, 37, 23, 16, 21, 24, 26, 27, 17, 73, + 85, 70, 68, 66, 107, 65, 3, 86, 18, 4, 4, 3, + 3, 66, 3, 5, 79, 93, 77, 80, 78, 89, 77, 16, + 16, 10, 4, 0, 68, 73, 74, 81, 67, 28, 15, 10, + 2, 11, 0, 68, 74, 69, 66, 28, 15, 10, 4, 10, + 0, 68, 73, 79, 72, 29, 13, 9, 4, 6, 68, 74, + 78, 3, 35, 22, 15, 8, 15, 3, 65, 68, 71, 62, + 92, 87, 80, 84, 84, 81, 78, 76, 75, 74, 71, + 66, 78, 78, 83, 85, 15, 84, 83, 78, 73, 72, + 73, 71, 71, 70, 70, 71, 80, 68, 68, 12, 69, + 77, 67, 68, 70, 65, 66, 68, 75, 67, 67, 73, + 72, 6, 88, 71, 12, 72, 0, 69, 68, 64, 2, 64, + 66, 64, 73, 73, 81, 50, 47, 49, 42, 36, 39, + 39, 35, 35, 32, 33, 33, 21, 15, 2, 23, 22, 17, + 4, 13, 11, 5, 9, 2, 0, 69, 73, 77, 79, 23, 20, + 23, 12, 68, 3, 1, 75, 64, 72, 76, 92, 84, 89, + 94, 4, 8, 80, 3, 5, 67, 77, 75, 76, 85, 75, + 81, 82, 98, 85, 90, 100, 65, 79, 87, 73, 2, 6, + 7, 17, 11, 18, 1, 15, 13, 21, 23, 26, 26, 13, + 51, 27, 19, 13, 5, 3, 80, 86, 95, 7, 37, 32, + 32, 23, 23, 13, 11, 5, 0, 73, 2, 6, 7, 17, 11, + 18, 1, 15, 13, 21, 23, 26, 26, 13, 51, 27, 19, + 13, 5, 3, 80, 86, 95 }, + + { + + 46, + 6, 78, 46, 6, 78, 81, 66, 20, 11, 1, 0, 23, + 41, 29, 6, 8, 5, 78, 0, 65, 66, 18, 79, 72, + 21, 16, 87, 102, 116, 75, 81, 70, 78, 0, 65, + 83, 65, 29, 2, 71, 78, 81, 87, 90, 90, 105, + 64, 76, 84, 71, 84, 85, 99, 4, 72, 71, 77, 25, + 2, 22, 0, 0, 0, 68, 92, 97, 3, 1, 66, 18, 73, + 94, 68, 0, 72, 20, 18, 69, 0, 76, 10, 73, 68, + 66, 97, 84, 87, 86, 18, 67, 3, 66, 70, 86, 73, + 78, 65, 75, 72, 85, 17, 72, 5, 76, 79, 68, 73, + 67, 3, 4, 0, 9, 6, 74, 0, 64, 67, 2, 72, 26, + 67, 1, 24, 23, 31, 27, 20, 89, 0, 65, 66, 67, + 81, 68, 16, 66, 67, 15, 34, 54, 40, 17, 89, + 90, 2, 37, 40, 112, 69, 6, 90, 14, 66, 0, 15, + 33, 55, 45, 24, 92, 86, 64, 10, 16, 91, 18, + 21, 18, 17, 19, 17, 10, 19, 17, 64, 6, 10, 4, + 2, 69, 6, 2, 64, 4, 1, 5, 5, 0, 88, 64, 64, + 69, 65, 76, 25, 35, 21, 14, 20, 22, 23, 24, + 16, 75, 87, 72, 70, 68, 108, 66, 2, 87, 17, 3, + 3, 1, 2, 68, 1, 4, 81, 93, 78, 81, 80, 88, 76, + 17, 16, 10, 4, 1, 67, 72, 74, 79, 65, 29, 16, + 10, 3, 13, 1, 66, 73, 66, 65, 28, 15, 10, 5, + 11, 1, 68, 72, 78, 72, 30, 14, 9, 4, 7, 68, + 74, 77, 4, 35, 23, 15, 8, 16, 4, 64, 68, 70, + 62, 91, 86, 79, 83, 83, 79, 77, 74, 74, 72, + 69, 0, 77, 77, 82, 84, 18, 83, 84, 77, 72, 71, + 73, 71, 71, 70, 69, 72, 81, 68, 68, 13, 69, + 77, 67, 68, 69, 66, 67, 68, 76, 68, 68, 73, + 72, 7, 89, 72, 13, 72, 64, 69, 68, 64, 1, 65, + 66, 64, 73, 73, 82, 49, 46, 48, 41, 33, 37, + 36, 32, 32, 29, 30, 30, 18, 13, 0, 19, 18, 13, + 64, 10, 9, 3, 7, 64, 65, 70, 74, 78, 79, 21, + 17, 20, 10, 70, 1, 64, 77, 66, 74, 78, 94, 86, + 90, 96, 3, 7, 82, 1, 4, 69, 79, 76, 77, 86, + 76, 82, 82, 97, 87, 91, 101, 67, 80, 88, 73, + 2, 6, 8, 18, 12, 18, 2, 16, 14, 22, 23, 27, + 26, 13, 51, 25, 17, 11, 3, 1, 83, 88, 96, 7, + 38, 32, 32, 24, 24, 13, 11, 5, 1, 73, 2, 6, 8, + 18, 12, 18, 2, 16, 14, 22, 23, 27, 26, 13, 51, + 25, 17, 11, 3, 1, 83, 88, 96 }, + + { + + 45, + 6, 79, 45, 6, 79, 79, 65, 21, 11, 1, 64, 21, + 40, 29, 7, 10, 4, 78, 2, 65, 67, 19, 80, 73, + 20, 15, 91, 104, 117, 73, 79, 70, 78, 2, 65, + 82, 64, 30, 2, 70, 77, 80, 89, 91, 90, 106, + 64, 76, 84, 71, 85, 84, 99, 3, 72, 71, 76, 25, + 2, 22, 0, 0, 0, 67, 92, 97, 3, 1, 67, 18, 72, + 93, 66, 1, 71, 22, 20, 68, 1, 74, 11, 72, 68, + 64, 98, 84, 87, 86, 19, 67, 4, 64, 70, 85, 72, + 76, 65, 75, 72, 85, 18, 73, 6, 76, 79, 67, 73, + 66, 3, 5, 1, 10, 6, 74, 0, 64, 67, 2, 72, 26, + 67, 1, 24, 23, 32, 28, 21, 89, 1, 64, 67, 67, + 81, 68, 17, 66, 66, 16, 35, 54, 42, 18, 90, + 91, 3, 37, 40, 112, 70, 6, 90, 14, 67, 1, 16, + 33, 55, 46, 25, 93, 85, 65, 7, 13, 89, 18, 21, + 18, 16, 19, 16, 10, 18, 17, 64, 6, 10, 3, 2, + 70, 6, 1, 65, 4, 1, 5, 5, 64, 90, 64, 65, 69, + 66, 76, 22, 33, 19, 13, 18, 20, 20, 22, 14, + 77, 89, 73, 72, 70, 110, 67, 0, 89, 15, 2, 2, + 0, 0, 70, 64, 2, 82, 93, 79, 82, 81, 86, 74, + 19, 17, 10, 4, 2, 67, 72, 73, 78, 64, 30, 17, + 11, 3, 14, 2, 65, 72, 64, 65, 29, 16, 11, 5, + 12, 1, 67, 71, 77, 72, 32, 15, 9, 4, 7, 67, + 74, 77, 4, 36, 23, 16, 8, 17, 4, 64, 67, 70, + 62, 89, 84, 77, 81, 81, 78, 75, 73, 72, 70, + 67, 2, 77, 76, 82, 84, 21, 83, 85, 77, 72, 70, + 73, 71, 71, 70, 69, 72, 82, 68, 68, 13, 69, + 77, 67, 68, 69, 66, 67, 68, 77, 69, 68, 73, + 72, 8, 90, 72, 14, 73, 64, 69, 68, 0, 1, 65, + 65, 0, 73, 73, 84, 48, 45, 47, 39, 31, 34, 34, + 29, 30, 26, 27, 27, 15, 10, 65, 16, 15, 9, 69, + 8, 6, 1, 5, 66, 67, 71, 75, 79, 79, 19, 15, + 18, 8, 72, 0, 65, 79, 68, 77, 80, 96, 88, 92, + 97, 2, 5, 84, 0, 2, 70, 81, 78, 79, 87, 77, + 83, 83, 96, 88, 93, 103, 68, 82, 89, 72, 3, 7, + 8, 19, 13, 19, 2, 17, 15, 23, 24, 28, 27, 14, + 50, 23, 15, 9, 1, 64, 85, 90, 97, 8, 38, 33, + 33, 24, 25, 14, 12, 6, 1, 72, 3, 7, 8, 19, 13, + 19, 2, 17, 15, 23, 24, 28, 27, 14, 50, 23, 15, + 9, 1, 64, 85, 90, 97 }, + + { + + 43, + 6, 79, 43, 6, 79, 78, 0, 21, 11, 0, 66, 20, + 39, 30, 7, 12, 2, 78, 3, 65, 67, 20, 81, 75, + 18, 13, 94, 106, 118, 71, 78, 70, 78, 3, 65, + 82, 0, 30, 1, 69, 76, 79, 90, 93, 91, 106, 64, + 76, 83, 71, 85, 84, 99, 3, 72, 71, 76, 26, 2, + 22, 0, 0, 0, 67, 93, 97, 4, 0, 67, 17, 72, 92, + 64, 2, 70, 23, 21, 68, 2, 72, 12, 71, 67, 1, + 99, 84, 87, 86, 19, 68, 5, 1, 70, 84, 71, 74, + 65, 75, 72, 85, 18, 73, 6, 75, 79, 67, 73, 66, + 3, 5, 2, 10, 6, 74, 64, 64, 67, 2, 72, 26, 67, + 1, 25, 23, 32, 28, 21, 90, 1, 0, 67, 67, 81, + 68, 18, 66, 66, 17, 36, 55, 43, 20, 90, 92, 3, + 37, 40, 113, 70, 7, 91, 15, 67, 1, 17, 33, 55, + 46, 26, 94, 83, 66, 4, 10, 88, 17, 21, 17, 15, + 18, 16, 9, 18, 16, 65, 5, 9, 3, 2, 70, 5, 0, + 66, 3, 0, 4, 4, 64, 91, 65, 66, 69, 68, 77, + 20, 31, 17, 11, 16, 18, 16, 19, 13, 79, 92, + 75, 74, 72, 111, 69, 64, 91, 14, 1, 1, 64, 64, + 72, 66, 0, 84, 93, 80, 83, 83, 85, 73, 20, 17, + 10, 4, 3, 66, 71, 72, 76, 0, 31, 17, 12, 4, + 15, 3, 0, 71, 1, 64, 29, 16, 11, 6, 13, 2, 67, + 71, 76, 72, 33, 16, 9, 4, 8, 67, 74, 76, 5, + 36, 24, 16, 8, 18, 5, 0, 67, 69, 62, 88, 83, + 76, 79, 80, 76, 74, 71, 71, 69, 65, 4, 76, 75, + 81, 83, 24, 83, 86, 77, 71, 70, 73, 71, 71, + 71, 68, 72, 83, 68, 68, 14, 69, 77, 67, 68, + 69, 67, 67, 68, 78, 70, 68, 73, 72, 9, 91, 72, + 14, 74, 64, 69, 68, 0, 1, 66, 65, 1, 73, 73, + 85, 47, 44, 46, 37, 29, 32, 31, 26, 27, 23, + 23, 24, 11, 7, 67, 12, 11, 5, 74, 5, 4, 64, 3, + 69, 69, 73, 76, 80, 79, 16, 13, 16, 6, 74, 65, + 67, 81, 70, 79, 82, 98, 91, 94, 98, 1, 4, 87, + 65, 0, 72, 83, 79, 80, 88, 78, 84, 83, 95, 89, + 94, 104, 69, 83, 90, 72, 3, 7, 9, 20, 13, 20, + 2, 18, 16, 24, 24, 29, 27, 15, 50, 21, 13, 7, + 64, 66, 87, 92, 99, 8, 39, 33, 34, 25, 25, 14, + 12, 6, 2, 72, 3, 7, 9, 20, 13, 20, 2, 18, 16, + 24, 24, 29, 27, 15, 50, 21, 13, 7, 64, 66, 87, + 92, 99 }, + + { + + 42, + 6, 79, 42, 6, 79, 76, 1, 21, 11, 0, 68, 18, + 38, 31, 7, 13, 0, 77, 5, 66, 67, 21, 82, 76, + 17, 11, 97, 109, 118, 69, 77, 70, 77, 5, 66, + 82, 1, 30, 1, 69, 75, 77, 91, 94, 91, 106, 64, + 75, 82, 72, 85, 84, 99, 3, 71, 71, 75, 26, 2, + 22, 0, 0, 0, 66, 93, 97, 4, 64, 67, 17, 71, + 90, 2, 3, 69, 25, 23, 67, 3, 70, 13, 70, 66, + 4, 99, 85, 87, 87, 19, 68, 6, 4, 69, 84, 71, + 72, 65, 75, 72, 84, 18, 73, 7, 74, 78, 66, 72, + 66, 4, 6, 3, 11, 6, 75, 64, 0, 67, 2, 72, 27, + 67, 1, 25, 24, 32, 28, 21, 90, 2, 0, 68, 67, + 80, 68, 19, 65, 65, 19, 38, 56, 45, 22, 91, + 93, 4, 38, 40, 114, 70, 7, 91, 16, 67, 1, 19, + 33, 55, 46, 28, 95, 82, 67, 2, 7, 87, 17, 21, + 17, 15, 18, 16, 9, 18, 16, 66, 4, 9, 3, 2, 70, + 5, 0, 67, 3, 0, 4, 3, 65, 92, 66, 66, 69, 69, + 77, 18, 29, 16, 10, 15, 16, 13, 17, 12, 81, + 94, 76, 76, 74, 112, 70, 65, 92, 13, 0, 0, 66, + 66, 74, 68, 64, 85, 93, 81, 84, 85, 83, 71, + 21, 18, 10, 4, 4, 65, 70, 72, 74, 2, 32, 18, + 12, 5, 17, 4, 1, 70, 4, 0, 30, 17, 12, 6, 14, + 3, 66, 70, 75, 71, 34, 17, 9, 4, 9, 66, 73, + 75, 6, 36, 25, 16, 8, 19, 6, 1, 66, 68, 62, + 87, 81, 74, 78, 79, 75, 72, 69, 69, 67, 0, 7, + 75, 74, 80, 82, 27, 82, 87, 76, 70, 69, 73, + 71, 71, 71, 68, 73, 84, 68, 68, 15, 69, 77, + 67, 68, 68, 67, 68, 68, 79, 71, 69, 73, 72, + 10, 91, 73, 15, 74, 65, 69, 68, 0, 0, 66, 65, + 1, 73, 73, 87, 46, 43, 45, 36, 26, 29, 28, 23, + 25, 20, 20, 21, 8, 5, 69, 8, 8, 1, 79, 2, 2, + 65, 1, 71, 71, 74, 77, 81, 79, 14, 10, 13, 4, + 76, 67, 68, 83, 72, 81, 84, 100, 93, 95, 100, + 0, 3, 89, 66, 64, 73, 85, 80, 81, 89, 79, 85, + 83, 94, 91, 95, 106, 71, 84, 91, 72, 4, 8, 10, + 21, 14, 20, 3, 19, 17, 25, 25, 30, 28, 15, 49, + 19, 11, 5, 66, 68, 90, 94, 100, 9, 39, 34, 34, + 26, 26, 15, 13, 6, 3, 72, 4, 8, 10, 21, 14, + 20, 3, 19, 17, 25, 25, 30, 28, 15, 49, 19, 11, + 5, 66, 68, 90, 94, 100 }, + + { + + 41, + 6, 79, 41, 6, 79, 74, 3, 22, 11, 64, 70, 17, + 37, 31, 7, 15, 64, 77, 6, 66, 68, 22, 83, 77, + 16, 9, 101, 111, 119, 67, 75, 70, 77, 6, 66, + 81, 2, 31, 1, 68, 74, 76, 92, 95, 91, 107, 64, + 75, 81, 72, 86, 84, 99, 3, 71, 71, 74, 27, 2, + 22, 0, 0, 0, 66, 93, 97, 5, 65, 67, 16, 71, + 89, 4, 4, 68, 26, 24, 66, 4, 68, 14, 69, 65, + 6, 100, 85, 87, 87, 20, 68, 7, 6, 69, 83, 70, + 70, 65, 75, 72, 84, 19, 73, 7, 73, 78, 65, 72, + 66, 4, 6, 4, 11, 6, 75, 64, 0, 67, 2, 72, 27, + 67, 1, 26, 24, 33, 28, 22, 91, 2, 1, 68, 67, + 80, 68, 20, 65, 65, 20, 39, 57, 46, 24, 91, + 94, 4, 38, 40, 115, 70, 8, 92, 16, 67, 1, 20, + 33, 55, 46, 29, 96, 80, 68, 64, 4, 85, 17, 21, + 16, 14, 17, 15, 8, 18, 15, 67, 3, 9, 3, 2, 71, + 4, 64, 68, 2, 0, 4, 2, 65, 93, 66, 67, 69, 70, + 78, 16, 27, 14, 8, 13, 14, 10, 14, 11, 83, 96, + 78, 78, 76, 114, 71, 66, 94, 12, 64, 64, 67, + 67, 76, 70, 66, 87, 93, 82, 85, 86, 82, 70, + 23, 18, 10, 4, 5, 64, 69, 71, 72, 3, 33, 19, + 13, 5, 18, 5, 3, 69, 6, 0, 30, 17, 12, 7, 15, + 3, 66, 69, 74, 71, 36, 18, 9, 4, 10, 66, 73, + 74, 7, 37, 26, 17, 8, 20, 6, 2, 66, 68, 62, + 85, 80, 73, 76, 78, 73, 71, 68, 68, 65, 2, 9, + 75, 73, 80, 81, 30, 82, 88, 76, 69, 68, 73, + 71, 71, 71, 67, 73, 85, 68, 68, 15, 69, 77, + 67, 68, 68, 68, 68, 68, 80, 72, 69, 73, 72, + 11, 92, 73, 16, 75, 65, 69, 68, 1, 0, 67, 64, + 2, 73, 73, 88, 45, 42, 44, 34, 24, 27, 26, 20, + 22, 17, 17, 18, 5, 2, 71, 4, 4, 66, 84, 64, 0, + 67, 64, 74, 73, 75, 78, 82, 79, 12, 8, 11, 2, + 78, 69, 70, 85, 74, 83, 86, 102, 95, 97, 101, + 64, 2, 91, 68, 66, 75, 87, 82, 83, 90, 80, 86, + 83, 93, 92, 97, 107, 72, 85, 92, 72, 4, 8, 10, + 22, 15, 21, 3, 20, 18, 26, 25, 31, 28, 16, 49, + 17, 9, 3, 68, 70, 92, 96, 101, 9, 40, 34, 35, + 26, 27, 15, 13, 7, 3, 72, 4, 8, 10, 22, 15, + 21, 3, 20, 18, 26, 25, 31, 28, 16, 49, 17, 9, + 3, 68, 70, 92, 96, 101 }, + + { + + 40, + 6, 79, 40, 6, 79, 72, 4, 22, 11, 64, 72, 15, + 36, 32, 7, 17, 66, 77, 8, 66, 68, 23, 84, 78, + 15, 7, 104, 113, 120, 65, 74, 70, 77, 8, 66, + 81, 3, 31, 1, 67, 73, 75, 93, 96, 91, 107, 64, + 75, 80, 72, 86, 84, 99, 3, 71, 71, 73, 27, 2, + 22, 0, 0, 0, 65, 93, 97, 5, 66, 67, 16, 70, + 88, 6, 5, 67, 28, 26, 65, 5, 66, 15, 68, 64, + 8, 101, 85, 87, 87, 20, 68, 8, 8, 69, 82, 69, + 68, 65, 75, 72, 84, 19, 73, 8, 72, 78, 64, 72, + 66, 4, 7, 5, 12, 6, 75, 64, 0, 67, 2, 72, 27, + 67, 1, 26, 24, 33, 28, 22, 91, 3, 2, 69, 67, + 80, 68, 21, 65, 64, 21, 40, 58, 48, 26, 92, + 95, 5, 38, 40, 116, 70, 8, 92, 17, 67, 1, 21, + 33, 55, 46, 30, 97, 79, 69, 67, 1, 84, 17, 21, + 16, 13, 17, 15, 8, 18, 15, 68, 2, 9, 3, 2, 71, + 4, 65, 69, 2, 0, 4, 1, 66, 94, 67, 68, 69, 71, + 78, 14, 25, 12, 7, 11, 12, 7, 12, 10, 85, 98, + 79, 80, 78, 115, 72, 67, 96, 11, 65, 65, 68, + 69, 78, 72, 68, 88, 93, 83, 86, 88, 80, 68, + 24, 19, 10, 4, 6, 0, 68, 70, 70, 4, 34, 20, + 14, 6, 19, 6, 4, 68, 8, 1, 31, 18, 13, 7, 16, + 4, 65, 68, 73, 71, 37, 19, 9, 4, 11, 65, 73, + 73, 8, 37, 27, 17, 8, 21, 7, 3, 65, 67, 62, + 84, 78, 71, 74, 77, 72, 69, 66, 66, 0, 4, 11, + 74, 72, 79, 80, 33, 82, 89, 76, 68, 67, 73, + 71, 71, 71, 67, 73, 86, 68, 68, 16, 69, 77, + 67, 68, 68, 68, 68, 68, 81, 73, 69, 73, 72, + 12, 93, 73, 17, 76, 65, 69, 68, 1, 0, 67, 64, + 3, 73, 73, 90, 44, 41, 43, 32, 22, 24, 23, 17, + 20, 14, 14, 15, 2, 64, 73, 0, 1, 70, 89, 67, + 65, 69, 66, 76, 75, 76, 79, 83, 79, 10, 6, 9, + 0, 80, 71, 71, 87, 76, 85, 88, 104, 97, 99, + 102, 65, 1, 93, 69, 68, 76, 89, 83, 84, 91, + 81, 87, 83, 92, 93, 98, 109, 73, 86, 93, 72, + 5, 9, 11, 23, 16, 22, 3, 21, 19, 27, 26, 32, + 29, 17, 48, 15, 7, 1, 70, 72, 94, 98, 102, 10, + 40, 35, 36, 27, 28, 16, 14, 7, 4, 72, 5, 9, + 11, 23, 16, 22, 3, 21, 19, 27, 26, 32, 29, 17, + 48, 15, 7, 1, 70, 72, 94, 98, 102 }, + + { + + 38, + 5, 80, 38, 5, 80, 71, 5, 22, 11, 65, 74, 13, + 35, 32, 7, 18, 68, 77, 9, 67, 69, 24, 85, 80, + 13, 5, 108, 116, 121, 0, 73, 70, 77, 9, 67, + 81, 3, 31, 0, 67, 73, 74, 95, 98, 92, 108, 65, + 75, 80, 73, 87, 84, 99, 2, 71, 71, 73, 27, 1, + 22, 0, 0, 0, 65, 94, 97, 5, 67, 68, 15, 70, + 87, 8, 6, 67, 29, 27, 65, 6, 65, 15, 67, 64, + 10, 102, 86, 88, 88, 20, 69, 8, 10, 69, 82, + 69, 67, 65, 75, 72, 84, 19, 74, 8, 72, 78, 64, + 72, 66, 4, 7, 6, 12, 6, 76, 65, 0, 67, 1, 72, + 27, 67, 1, 26, 24, 33, 28, 22, 92, 3, 2, 70, + 67, 80, 69, 21, 65, 64, 22, 41, 58, 49, 27, + 93, 97, 5, 38, 40, 117, 71, 8, 93, 17, 68, 1, + 22, 33, 54, 46, 31, 98, 78, 71, 70, 66, 83, + 16, 21, 15, 12, 16, 14, 7, 17, 14, 69, 1, 8, + 2, 1, 72, 3, 66, 70, 1, 64, 3, 0, 67, 96, 68, + 69, 69, 73, 79, 11, 22, 10, 5, 9, 9, 3, 9, 8, + 87, 101, 81, 83, 80, 117, 74, 69, 98, 9, 66, + 66, 70, 71, 80, 74, 70, 90, 93, 84, 87, 90, + 79, 67, 25, 19, 10, 4, 6, 0, 68, 70, 69, 5, + 34, 20, 14, 6, 20, 7, 5, 68, 10, 1, 31, 18, + 13, 7, 16, 4, 65, 68, 72, 71, 38, 20, 9, 3, + 11, 65, 73, 73, 8, 37, 27, 17, 8, 22, 7, 3, + 65, 67, 62, 83, 77, 70, 73, 76, 71, 68, 65, + 65, 1, 5, 13, 74, 72, 79, 80, 36, 82, 91, 76, + 68, 67, 73, 71, 71, 72, 67, 74, 87, 69, 68, + 16, 70, 77, 68, 68, 68, 69, 69, 69, 82, 74, + 70, 74, 72, 13, 94, 74, 17, 77, 66, 69, 69, 1, + 64, 68, 64, 3, 73, 73, 92, 42, 40, 41, 30, 19, + 21, 20, 14, 17, 11, 10, 12, 65, 67, 75, 67, + 66, 74, 95, 70, 68, 71, 69, 79, 77, 78, 81, + 84, 79, 7, 3, 6, 65, 83, 73, 73, 90, 78, 88, + 90, 107, 100, 101, 104, 67, 64, 96, 71, 70, + 78, 91, 85, 86, 92, 82, 88, 84, 91, 95, 100, + 111, 75, 88, 95, 72, 5, 9, 11, 24, 16, 22, 3, + 22, 19, 28, 26, 32, 29, 17, 47, 13, 5, 65, 73, + 74, 97, 100, 104, 10, 40, 35, 36, 27, 28, 16, + 14, 7, 4, 72, 5, 9, 11, 24, 16, 22, 3, 22, 19, + 28, 26, 32, 29, 17, 47, 13, 5, 65, 73, 74, 97, + 100, 104 }, + + { + + 37, + 5, 80, 37, 5, 80, 69, 7, 23, 12, 65, 75, 12, + 35, 33, 8, 20, 69, 76, 11, 67, 69, 26, 85, 81, + 12, 4, 111, 118, 121, 2, 71, 69, 76, 11, 67, + 80, 4, 32, 0, 66, 72, 72, 96, 99, 92, 108, 65, + 74, 79, 73, 87, 83, 98, 2, 70, 70, 72, 28, 1, + 22, 0, 0, 0, 64, 94, 97, 6, 67, 68, 15, 69, + 85, 11, 8, 66, 31, 29, 64, 8, 0, 16, 65, 0, + 13, 102, 86, 88, 88, 21, 69, 9, 13, 68, 81, + 68, 65, 65, 74, 72, 83, 20, 74, 9, 71, 77, 0, + 71, 65, 5, 8, 7, 13, 7, 76, 65, 1, 66, 1, 71, + 28, 66, 1, 27, 25, 34, 29, 23, 92, 4, 3, 70, + 67, 79, 69, 22, 64, 0, 24, 43, 59, 51, 29, 93, + 98, 6, 39, 41, 117, 71, 9, 93, 18, 68, 2, 24, + 33, 54, 47, 33, 99, 76, 72, 72, 69, 81, 16, + 21, 15, 12, 16, 14, 7, 17, 14, 69, 1, 8, 2, 1, + 72, 3, 66, 70, 1, 64, 3, 0, 67, 97, 68, 69, + 69, 74, 79, 9, 20, 9, 4, 8, 7, 0, 7, 7, 88, + 103, 82, 85, 81, 118, 75, 70, 99, 8, 66, 66, + 71, 72, 81, 75, 71, 91, 93, 84, 87, 91, 77, + 65, 27, 20, 10, 4, 7, 1, 67, 69, 67, 7, 35, + 21, 15, 7, 22, 8, 7, 67, 13, 2, 32, 19, 14, 8, + 17, 5, 64, 67, 70, 70, 40, 21, 10, 3, 12, 64, + 72, 72, 9, 38, 28, 18, 9, 23, 8, 4, 64, 66, + 62, 81, 75, 68, 71, 74, 69, 66, 0, 0, 3, 7, + 16, 73, 71, 78, 79, 40, 81, 92, 75, 67, 66, + 72, 71, 70, 72, 66, 74, 87, 69, 68, 17, 70, + 77, 68, 68, 67, 69, 69, 69, 82, 74, 70, 74, + 71, 15, 94, 74, 18, 77, 66, 69, 69, 2, 64, 68, + 0, 4, 72, 72, 93, 41, 39, 40, 29, 17, 19, 18, + 11, 15, 9, 7, 10, 68, 69, 77, 70, 69, 77, 100, + 72, 70, 72, 71, 81, 78, 79, 82, 84, 79, 5, 1, + 4, 67, 85, 74, 74, 92, 79, 90, 91, 109, 102, + 102, 105, 68, 65, 98, 72, 71, 79, 92, 86, 87, + 93, 82, 88, 84, 90, 96, 101, 112, 76, 89, 96, + 71, 6, 10, 12, 26, 17, 23, 4, 24, 20, 29, 27, + 33, 30, 18, 47, 12, 4, 67, 75, 75, 99, 101, + 105, 11, 41, 36, 37, 28, 29, 17, 15, 8, 5, 71, + 6, 10, 12, 26, 17, 23, 4, 24, 20, 29, 27, 33, + 30, 18, 47, 12, 4, 67, 75, 75, 99, 101, 105 }, + + { + + 36, + 5, 80, 36, 5, 80, 67, 8, 23, 12, 65, 77, 10, + 34, 34, 8, 22, 71, 76, 12, 67, 69, 27, 86, 82, + 11, 2, 114, 120, 122, 4, 70, 69, 76, 12, 67, + 80, 5, 32, 0, 65, 71, 71, 97, 100, 92, 108, + 65, 74, 78, 73, 87, 83, 98, 2, 70, 70, 71, 28, + 1, 22, 0, 0, 0, 0, 94, 97, 6, 68, 68, 15, 68, + 84, 13, 9, 65, 33, 31, 0, 9, 2, 17, 64, 1, 15, + 103, 86, 88, 88, 21, 69, 10, 15, 68, 80, 67, + 0, 65, 74, 72, 83, 20, 74, 9, 70, 77, 1, 71, + 65, 5, 8, 8, 14, 7, 76, 65, 1, 66, 1, 71, 28, + 66, 1, 27, 25, 34, 29, 23, 93, 5, 4, 71, 67, + 79, 69, 23, 64, 0, 25, 44, 60, 53, 31, 94, 99, + 6, 39, 41, 118, 71, 9, 94, 19, 68, 2, 25, 33, + 54, 47, 34, 100, 75, 73, 75, 72, 80, 16, 21, + 15, 11, 15, 14, 7, 17, 13, 70, 0, 8, 2, 1, 72, + 2, 67, 71, 1, 64, 3, 64, 68, 98, 69, 70, 69, + 75, 80, 7, 18, 7, 2, 6, 5, 66, 5, 6, 90, 105, + 84, 87, 83, 119, 76, 71, 101, 7, 67, 67, 72, + 74, 83, 77, 73, 93, 93, 85, 88, 93, 76, 64, + 28, 21, 10, 4, 8, 2, 66, 68, 65, 8, 36, 22, + 16, 8, 23, 9, 8, 66, 15, 3, 32, 19, 14, 8, 18, + 6, 0, 66, 69, 70, 41, 22, 10, 3, 13, 0, 72, + 71, 10, 38, 29, 18, 9, 24, 9, 5, 0, 65, 62, + 80, 73, 66, 69, 73, 68, 64, 2, 1, 5, 9, 18, + 72, 70, 77, 78, 43, 81, 93, 75, 66, 65, 72, + 71, 70, 72, 66, 74, 88, 69, 68, 18, 70, 77, + 68, 68, 67, 69, 69, 69, 83, 75, 70, 74, 71, + 16, 95, 74, 19, 78, 66, 69, 69, 2, 64, 68, 0, + 5, 72, 72, 95, 40, 38, 39, 27, 15, 16, 15, 8, + 13, 6, 4, 7, 71, 72, 79, 74, 73, 81, 105, 75, + 72, 74, 73, 83, 80, 80, 83, 85, 79, 3, 64, 2, + 69, 87, 76, 76, 94, 81, 92, 93, 111, 104, 104, + 106, 69, 66, 100, 74, 73, 81, 94, 87, 88, 94, + 83, 89, 84, 89, 97, 102, 114, 77, 90, 97, 71, + 6, 11, 13, 27, 18, 24, 4, 25, 21, 30, 27, 34, + 31, 19, 46, 10, 2, 69, 77, 77, 101, 103, 106, + 12, 41, 36, 38, 29, 30, 17, 16, 8, 6, 71, 6, + 11, 13, 27, 18, 24, 4, 25, 21, 30, 27, 34, 31, + 19, 46, 10, 2, 69, 77, 77, 101, 103, 106 }, + + { + + 35, + 5, 80, 35, 5, 80, 65, 10, 24, 12, 66, 79, 9, + 33, 34, 8, 24, 72, 76, 14, 67, 70, 28, 87, 83, + 10, 0, 118, 122, 123, 6, 68, 69, 76, 14, 67, + 79, 6, 33, 0, 64, 70, 70, 98, 101, 92, 109, + 65, 74, 77, 73, 88, 83, 98, 2, 70, 70, 70, 29, + 1, 22, 0, 0, 0, 0, 94, 97, 7, 69, 68, 14, 68, + 83, 15, 10, 64, 34, 32, 1, 10, 4, 18, 0, 2, + 17, 104, 86, 88, 88, 22, 69, 11, 17, 68, 79, + 66, 2, 65, 74, 72, 83, 21, 74, 10, 69, 77, 2, + 71, 65, 5, 9, 9, 14, 7, 76, 65, 1, 66, 1, 71, + 28, 66, 1, 28, 25, 35, 29, 24, 93, 5, 5, 71, + 67, 79, 69, 24, 64, 1, 26, 45, 61, 54, 33, 94, + 100, 7, 39, 41, 119, 71, 10, 94, 19, 68, 2, + 26, 33, 54, 47, 35, 101, 73, 74, 78, 75, 78, + 16, 21, 14, 10, 15, 13, 6, 17, 13, 71, 64, 8, + 2, 1, 73, 2, 68, 72, 0, 64, 3, 65, 68, 99, 69, + 71, 69, 76, 80, 5, 16, 5, 1, 4, 3, 69, 2, 5, + 92, 107, 85, 89, 85, 121, 77, 72, 103, 6, 68, + 68, 73, 75, 85, 79, 75, 94, 93, 86, 89, 94, + 74, 1, 30, 21, 10, 4, 9, 3, 65, 67, 0, 9, 37, + 23, 17, 8, 24, 10, 10, 65, 17, 3, 33, 20, 15, + 9, 19, 6, 0, 65, 68, 70, 43, 23, 10, 3, 14, 0, + 72, 70, 11, 39, 30, 19, 9, 25, 9, 6, 0, 65, + 62, 78, 72, 65, 67, 72, 66, 0, 3, 3, 7, 11, + 20, 72, 69, 77, 77, 46, 81, 94, 75, 65, 64, + 72, 71, 70, 72, 65, 74, 89, 69, 68, 18, 70, + 77, 68, 68, 67, 70, 69, 69, 84, 76, 70, 74, + 71, 17, 96, 74, 20, 79, 66, 69, 69, 3, 64, 69, + 1, 6, 72, 72, 96, 39, 37, 38, 25, 13, 14, 13, + 5, 10, 3, 1, 4, 74, 75, 81, 78, 76, 85, 110, + 78, 74, 76, 75, 86, 82, 81, 84, 86, 79, 1, 66, + 0, 71, 89, 78, 77, 96, 83, 94, 95, 113, 106, + 106, 107, 70, 67, 102, 75, 75, 82, 96, 89, 90, + 95, 84, 90, 84, 88, 98, 104, 115, 78, 91, 98, + 71, 7, 11, 13, 28, 19, 25, 4, 26, 22, 31, 28, + 35, 31, 20, 46, 8, 0, 71, 79, 79, 103, 105, + 107, 12, 42, 37, 39, 29, 31, 18, 16, 9, 6, 71, + 7, 11, 13, 28, 19, 25, 4, 26, 22, 31, 28, 35, + 31, 20, 46, 8, 0, 71, 79, 79, 103, 105, 107 }, + + { + + 33, + 5, 80, 33, 5, 80, 64, 11, 24, 12, 66, 81, 7, + 32, 35, 8, 25, 74, 76, 15, 68, 70, 29, 88, 85, + 8, 65, 121, 125, 124, 8, 67, 69, 76, 15, 68, + 79, 7, 33, 64, 64, 69, 68, 99, 103, 93, 109, + 65, 73, 76, 74, 88, 83, 98, 2, 70, 70, 70, 29, + 1, 22, 0, 0, 0, 1, 95, 97, 7, 70, 68, 14, 67, + 82, 17, 11, 0, 36, 34, 1, 11, 6, 19, 1, 3, 20, + 104, 87, 88, 89, 22, 70, 12, 20, 67, 79, 66, + 4, 65, 74, 72, 83, 21, 74, 10, 68, 77, 2, 70, + 65, 5, 9, 10, 15, 7, 77, 66, 1, 66, 1, 71, 28, + 66, 1, 28, 25, 35, 29, 24, 94, 6, 5, 72, 67, + 78, 69, 25, 0, 1, 28, 47, 62, 56, 35, 95, 101, + 7, 40, 41, 120, 71, 10, 95, 20, 68, 2, 27, 33, + 54, 47, 37, 102, 72, 75, 81, 78, 77, 15, 21, + 14, 10, 14, 13, 6, 17, 12, 72, 65, 7, 2, 1, + 73, 1, 68, 73, 0, 65, 2, 66, 69, 100, 70, 72, + 69, 78, 81, 3, 14, 3, 64, 3, 1, 73, 0, 4, 94, + 110, 87, 91, 87, 122, 79, 73, 104, 5, 69, 69, + 75, 77, 87, 81, 76, 96, 93, 87, 90, 96, 73, 2, + 31, 22, 10, 4, 10, 4, 64, 67, 2, 11, 38, 23, + 17, 9, 26, 11, 11, 64, 20, 4, 33, 20, 15, 9, + 20, 7, 1, 65, 67, 70, 44, 24, 10, 3, 15, 1, + 72, 69, 12, 39, 31, 19, 9, 26, 10, 7, 1, 64, + 62, 77, 70, 0, 66, 71, 65, 2, 5, 4, 8, 13, 23, + 71, 68, 76, 76, 49, 80, 95, 74, 64, 64, 72, + 71, 70, 73, 65, 75, 90, 69, 68, 19, 70, 77, + 68, 68, 66, 70, 70, 69, 85, 77, 71, 74, 71, + 18, 97, 75, 20, 79, 67, 69, 69, 3, 65, 69, 1, + 6, 72, 72, 98, 38, 36, 37, 24, 10, 11, 10, 2, + 8, 0, 66, 1, 78, 77, 83, 82, 80, 89, 115, 81, + 76, 78, 77, 88, 84, 83, 85, 87, 79, 65, 69, + 66, 73, 91, 80, 79, 98, 85, 96, 97, 115, 109, + 107, 109, 71, 68, 105, 77, 76, 84, 98, 90, 91, + 96, 85, 91, 84, 87, 100, 105, 117, 80, 92, 99, + 71, 7, 12, 14, 29, 19, 25, 5, 27, 23, 32, 28, + 36, 32, 20, 45, 6, 65, 73, 81, 81, 106, 107, + 109, 13, 42, 37, 39, 30, 31, 18, 17, 9, 7, 71, + 7, 12, 14, 29, 19, 25, 5, 27, 23, 32, 28, 36, + 32, 20, 45, 6, 65, 73, 81, 81, 106, 107, 109 }, + + { + + 32, + 5, 80, 32, 5, 80, 1, 13, 24, 12, 67, 83, 6, + 31, 36, 8, 27, 76, 75, 17, 68, 70, 30, 89, 86, + 7, 67, 124, 126, 124, 10, 66, 69, 75, 17, 68, + 79, 8, 33, 64, 0, 68, 67, 100, 104, 93, 109, + 65, 73, 75, 74, 88, 83, 98, 2, 69, 70, 69, 30, + 1, 22, 0, 0, 0, 1, 95, 97, 8, 71, 68, 13, 67, + 80, 20, 12, 1, 37, 35, 2, 12, 8, 20, 2, 4, 22, + 105, 87, 88, 89, 22, 70, 13, 22, 67, 78, 65, + 6, 65, 74, 72, 82, 21, 74, 11, 67, 76, 3, 70, + 65, 6, 10, 11, 15, 7, 77, 66, 2, 66, 1, 71, + 29, 66, 1, 29, 26, 35, 29, 24, 94, 6, 6, 72, + 67, 78, 69, 26, 0, 2, 29, 48, 62, 57, 37, 95, + 102, 8, 40, 41, 121, 71, 11, 95, 21, 68, 2, + 29, 33, 54, 47, 38, 103, 70, 76, 83, 81, 76, + 15, 21, 13, 9, 14, 13, 5, 17, 12, 73, 66, 7, + 2, 1, 73, 1, 69, 74, 64, 65, 2, 67, 69, 101, + 71, 72, 69, 79, 81, 1, 12, 2, 65, 1, 64, 76, + 66, 3, 96, 112, 88, 93, 89, 123, 80, 74, 106, + 4, 70, 70, 76, 78, 89, 83, 78, 97, 93, 88, 91, + 98, 71, 4, 32, 22, 10, 4, 11, 5, 0, 66, 4, 12, + 39, 24, 18, 10, 27, 12, 13, 0, 22, 5, 34, 21, + 16, 10, 21, 8, 1, 64, 66, 69, 45, 25, 10, 3, + 16, 1, 71, 68, 13, 39, 32, 19, 9, 27, 11, 8, + 1, 0, 62, 76, 69, 1, 64, 70, 0, 3, 7, 6, 10, + 15, 25, 70, 67, 75, 75, 52, 80, 96, 74, 0, 0, + 72, 71, 70, 73, 64, 75, 91, 69, 68, 20, 70, + 77, 68, 68, 66, 71, 70, 69, 86, 78, 71, 74, + 71, 19, 97, 75, 21, 80, 67, 69, 69, 3, 65, 70, + 1, 7, 72, 72, 99, 37, 35, 36, 22, 8, 9, 7, 64, + 5, 66, 69, 65, 81, 80, 85, 86, 83, 93, 120, + 84, 78, 79, 79, 91, 86, 84, 86, 88, 79, 67, + 71, 68, 75, 93, 82, 80, 100, 87, 98, 99, 117, + 111, 109, 110, 72, 69, 107, 78, 78, 85, 100, + 91, 92, 97, 86, 92, 84, 86, 101, 106, 118, 81, + 93, 100, 71, 8, 12, 15, 30, 20, 26, 5, 28, 24, + 33, 29, 37, 32, 21, 45, 4, 67, 75, 83, 83, + 108, 109, 110, 13, 43, 38, 40, 31, 32, 19, 17, + 9, 8, 71, 8, 12, 15, 30, 20, 26, 5, 28, 24, + 33, 29, 37, 32, 21, 45, 4, 67, 75, 83, 83, + 108, 109, 110 }, + + { + + 31, + 5, 81, 31, 5, 81, 3, 14, 25, 12, 67, 84, 4, + 30, 36, 9, 29, 77, 75, 18, 68, 71, 31, 90, 87, + 6, 68, 126, 126, 125, 12, 64, 69, 75, 18, 68, + 78, 9, 34, 64, 1, 67, 66, 102, 105, 93, 110, + 65, 73, 75, 74, 89, 82, 98, 1, 69, 70, 68, 30, + 1, 22, 0, 0, 0, 2, 95, 97, 8, 71, 69, 13, 66, + 79, 22, 13, 2, 39, 37, 3, 13, 10, 21, 3, 4, + 24, 106, 87, 88, 89, 23, 70, 14, 24, 67, 77, + 64, 8, 65, 74, 72, 82, 22, 75, 11, 67, 76, 4, + 70, 64, 6, 10, 12, 16, 7, 77, 66, 2, 66, 1, + 71, 29, 66, 1, 29, 26, 36, 30, 25, 95, 7, 7, + 73, 67, 78, 69, 27, 0, 2, 30, 49, 62, 59, 38, + 96, 103, 8, 40, 41, 121, 72, 11, 96, 21, 69, + 3, 30, 33, 54, 48, 39, 104, 69, 77, 86, 84, + 74, 15, 21, 13, 8, 13, 12, 5, 16, 11, 73, 66, + 7, 1, 1, 74, 0, 70, 75, 64, 65, 2, 67, 70, + 103, 71, 73, 69, 80, 82, 65, 10, 0, 67, 64, + 66, 79, 68, 1, 98, 114, 90, 95, 91, 125, 81, + 76, 108, 2, 71, 71, 77, 80, 91, 85, 80, 99, + 93, 89, 92, 99, 70, 5, 34, 23, 10, 4, 12, 5, + 0, 65, 5, 13, 40, 25, 19, 10, 28, 13, 14, 1, + 24, 5, 34, 21, 16, 10, 22, 8, 2, 0, 65, 69, + 47, 26, 10, 3, 16, 2, 71, 68, 13, 40, 32, 20, + 9, 28, 11, 8, 2, 0, 62, 74, 67, 3, 1, 68, 1, + 5, 8, 7, 12, 17, 27, 70, 66, 75, 75, 55, 80, + 97, 74, 0, 1, 72, 71, 70, 73, 64, 75, 92, 69, + 68, 20, 70, 77, 68, 68, 66, 71, 70, 69, 87, + 79, 71, 74, 71, 20, 98, 75, 22, 81, 67, 69, + 69, 4, 65, 70, 2, 8, 72, 72, 101, 36, 34, 35, + 20, 6, 6, 5, 67, 3, 69, 72, 68, 84, 83, 87, + 89, 87, 97, 125, 86, 81, 81, 81, 93, 88, 85, + 87, 89, 79, 69, 73, 70, 77, 95, 83, 82, 102, + 89, 101, 101, 119, 113, 111, 111, 73, 71, 109, + 80, 80, 87, 102, 93, 94, 98, 87, 93, 85, 85, + 102, 108, 120, 82, 95, 101, 70, 8, 13, 15, 31, + 21, 27, 5, 29, 25, 34, 29, 38, 33, 22, 44, 2, + 69, 77, 85, 85, 110, 111, 111, 14, 43, 38, 41, + 31, 33, 19, 18, 10, 8, 70, 8, 13, 15, 31, 21, + 27, 5, 29, 25, 34, 29, 38, 33, 22, 44, 2, 69, + 77, 85, 85, 110, 111, 111 }, + + { + + 30, + 5, 81, 30, 5, 81, 5, 16, 25, 12, 68, 86, 3, + 29, 37, 9, 30, 79, 75, 20, 69, 71, 32, 91, 88, + 5, 70, 126, 126, 126, 14, 0, 69, 75, 20, 69, + 78, 10, 34, 64, 1, 66, 64, 103, 106, 93, 110, + 65, 72, 74, 75, 89, 82, 98, 1, 69, 70, 67, 31, + 1, 22, 0, 0, 0, 2, 95, 97, 9, 72, 69, 12, 66, + 78, 24, 14, 3, 40, 38, 4, 14, 12, 22, 4, 5, + 27, 106, 88, 88, 90, 23, 70, 15, 27, 66, 77, + 64, 10, 65, 74, 72, 82, 22, 75, 12, 66, 76, 5, + 69, 64, 6, 11, 13, 16, 7, 78, 66, 2, 66, 1, + 71, 29, 66, 1, 30, 26, 36, 30, 25, 95, 7, 7, + 73, 67, 77, 69, 28, 1, 3, 32, 51, 62, 60, 40, + 96, 104, 9, 41, 41, 122, 72, 12, 96, 22, 69, + 3, 31, 33, 54, 48, 41, 105, 67, 78, 89, 87, + 73, 15, 21, 12, 8, 13, 12, 4, 16, 11, 74, 67, + 7, 1, 1, 74, 0, 70, 76, 65, 65, 2, 68, 70, + 104, 72, 74, 69, 81, 82, 67, 8, 65, 68, 65, + 68, 82, 71, 0, 100, 116, 91, 97, 93, 126, 82, + 77, 109, 1, 72, 72, 79, 81, 93, 87, 81, 100, + 93, 90, 93, 101, 68, 7, 35, 23, 10, 4, 13, 6, + 1, 65, 7, 15, 41, 26, 19, 11, 30, 14, 16, 2, + 27, 6, 35, 22, 17, 11, 23, 9, 2, 1, 64, 69, + 48, 27, 10, 3, 17, 2, 71, 67, 14, 40, 33, 20, + 9, 29, 12, 9, 2, 1, 62, 73, 66, 4, 2, 67, 3, + 6, 10, 9, 14, 19, 30, 69, 65, 74, 74, 58, 79, + 98, 73, 1, 2, 72, 71, 70, 73, 0, 76, 93, 69, + 68, 21, 70, 77, 68, 68, 65, 72, 71, 69, 88, + 80, 72, 74, 71, 21, 99, 76, 23, 81, 68, 69, + 69, 4, 66, 71, 2, 8, 72, 72, 102, 35, 33, 34, + 19, 3, 4, 2, 70, 0, 72, 75, 71, 87, 85, 89, + 93, 90, 101, 126, 89, 83, 83, 83, 96, 90, 86, + 88, 90, 79, 71, 76, 73, 79, 97, 85, 83, 104, + 91, 103, 103, 121, 115, 112, 113, 74, 72, 111, + 81, 81, 88, 104, 94, 95, 99, 88, 94, 85, 84, + 104, 109, 121, 84, 96, 102, 70, 9, 13, 16, 32, + 22, 27, 6, 30, 26, 35, 30, 39, 33, 22, 44, 0, + 71, 79, 87, 87, 113, 113, 112, 14, 44, 39, 41, + 32, 34, 20, 18, 10, 9, 70, 9, 13, 16, 32, 22, + 27, 6, 30, 26, 35, 30, 39, 33, 22, 44, 0, 71, + 79, 87, 87, 113, 113, 112 }, + + { + + 28, + 4, 81, 28, 4, 81, 6, 17, 25, 12, 68, 88, 1, + 28, 37, 9, 32, 81, 75, 21, 69, 72, 33, 92, 90, + 3, 72, 126, 126, 126, 16, 1, 69, 75, 21, 69, + 78, 10, 34, 65, 2, 65, 0, 104, 108, 94, 111, + 65, 72, 73, 75, 90, 82, 98, 1, 69, 70, 67, 31, + 1, 22, 0, 0, 0, 3, 96, 97, 9, 73, 69, 12, 65, + 77, 26, 15, 3, 42, 40, 4, 15, 14, 22, 5, 6, + 29, 107, 88, 89, 90, 23, 71, 15, 29, 66, 76, + 0, 11, 65, 74, 72, 82, 22, 75, 12, 65, 76, 5, + 69, 64, 6, 11, 14, 17, 7, 78, 67, 2, 66, 0, + 71, 29, 66, 1, 30, 26, 36, 30, 25, 96, 8, 8, + 74, 67, 77, 69, 29, 1, 3, 33, 52, 62, 62, 42, + 97, 105, 9, 41, 41, 123, 72, 12, 97, 22, 69, + 3, 32, 33, 54, 48, 42, 106, 66, 79, 92, 91, + 72, 14, 21, 12, 7, 12, 11, 4, 16, 10, 75, 68, + 6, 1, 0, 75, 64, 71, 77, 65, 66, 1, 69, 71, + 105, 73, 75, 69, 83, 83, 69, 6, 67, 70, 67, + 71, 86, 73, 64, 102, 119, 93, 100, 95, 126, + 84, 78, 111, 0, 73, 73, 80, 83, 95, 89, 83, + 102, 93, 91, 94, 103, 67, 8, 36, 24, 10, 4, + 13, 7, 2, 64, 9, 16, 41, 26, 20, 11, 31, 15, + 17, 3, 29, 6, 35, 22, 17, 11, 23, 9, 3, 1, 0, + 69, 49, 28, 10, 3, 18, 3, 71, 66, 15, 40, 34, + 20, 9, 30, 12, 10, 3, 1, 62, 72, 64, 6, 4, 66, + 4, 8, 11, 10, 15, 21, 32, 69, 64, 74, 73, 61, + 79, 99, 73, 2, 2, 72, 71, 70, 74, 0, 76, 94, + 69, 68, 21, 70, 77, 69, 68, 65, 72, 71, 70, + 89, 81, 72, 75, 71, 22, 100, 76, 23, 82, 68, + 69, 70, 4, 66, 71, 2, 9, 72, 72, 104, 34, 32, + 33, 17, 1, 1, 64, 73, 65, 75, 79, 74, 91, 88, + 91, 97, 94, 105, 126, 92, 85, 85, 86, 98, 92, + 88, 90, 91, 79, 74, 78, 75, 81, 100, 87, 85, + 107, 93, 105, 105, 123, 118, 114, 114, 76, 73, + 114, 83, 83, 90, 106, 96, 97, 100, 89, 95, 85, + 83, 105, 111, 123, 85, 97, 103, 70, 9, 14, 16, + 33, 22, 28, 6, 31, 26, 36, 30, 39, 34, 23, 43, + 65, 73, 81, 89, 89, 115, 115, 114, 15, 44, 39, + 42, 32, 34, 20, 19, 10, 9, 70, 9, 14, 16, 33, + 22, 28, 6, 31, 26, 36, 30, 39, 34, 23, 43, 65, + 73, 81, 89, 89, 115, 115, 114 }, + + { + + 27, + 4, 81, 27, 4, 81, 8, 18, 26, 12, 68, 90, 64, + 28, 38, 9, 34, 82, 74, 23, 69, 72, 34, 92, 91, + 2, 74, 126, 126, 126, 18, 3, 68, 74, 23, 69, + 77, 11, 35, 65, 3, 64, 1, 105, 109, 94, 111, + 65, 72, 72, 75, 90, 82, 98, 1, 68, 69, 66, 31, + 1, 22, 0, 0, 0, 4, 96, 97, 9, 74, 69, 12, 64, + 75, 29, 16, 4, 44, 42, 5, 16, 16, 23, 7, 7, + 31, 108, 88, 89, 90, 24, 71, 16, 31, 66, 75, + 1, 13, 65, 73, 72, 81, 23, 75, 13, 64, 75, 6, + 69, 64, 7, 12, 15, 18, 7, 78, 67, 3, 65, 0, + 71, 30, 66, 1, 30, 27, 37, 30, 26, 96, 9, 9, + 75, 67, 77, 69, 30, 1, 4, 34, 53, 62, 62, 44, + 98, 106, 10, 41, 42, 124, 72, 12, 97, 23, 69, + 3, 34, 33, 54, 48, 43, 107, 65, 80, 94, 94, + 70, 14, 21, 12, 6, 12, 11, 4, 16, 10, 76, 69, + 6, 1, 0, 75, 64, 72, 77, 65, 66, 1, 70, 72, + 106, 73, 75, 69, 84, 83, 71, 4, 68, 71, 69, + 73, 89, 75, 65, 104, 121, 94, 102, 96, 126, + 85, 79, 113, 64, 74, 74, 81, 85, 96, 91, 85, + 103, 93, 91, 95, 104, 65, 10, 38, 25, 10, 4, + 14, 8, 3, 0, 11, 17, 42, 27, 21, 12, 32, 16, + 18, 4, 31, 7, 36, 23, 18, 11, 24, 10, 4, 2, 2, + 68, 51, 29, 11, 3, 19, 4, 70, 65, 16, 41, 35, + 21, 10, 31, 13, 11, 4, 2, 62, 70, 1, 8, 6, 65, + 5, 10, 13, 12, 17, 23, 34, 68, 0, 73, 72, 62, + 79, 100, 73, 3, 3, 71, 71, 70, 74, 0, 76, 95, + 69, 68, 22, 70, 77, 69, 68, 65, 72, 71, 70, + 89, 82, 72, 75, 70, 24, 100, 76, 24, 83, 68, + 69, 70, 5, 66, 71, 3, 10, 71, 71, 106, 33, 31, + 32, 15, 64, 65, 66, 76, 67, 77, 82, 76, 94, + 91, 93, 101, 97, 108, 126, 95, 87, 86, 88, + 100, 93, 89, 91, 92, 79, 76, 80, 77, 83, 102, + 89, 86, 109, 95, 107, 107, 125, 120, 116, 115, + 77, 74, 116, 84, 85, 91, 108, 97, 98, 101, 90, + 95, 85, 82, 106, 112, 125, 86, 98, 104, 70, + 10, 15, 17, 35, 23, 29, 6, 32, 27, 37, 31, 40, + 35, 24, 42, 66, 75, 83, 91, 91, 117, 117, 115, + 16, 44, 40, 43, 33, 35, 21, 20, 11, 10, 70, + 10, 15, 17, 35, 23, 29, 6, 32, 27, 37, 31, 40, + 35, 24, 42, 66, 75, 83, 91, 91, 117, 117, 115 }, + + { + + 26, + 4, 81, 26, 4, 81, 10, 20, 26, 12, 69, 92, 65, + 27, 39, 9, 35, 84, 74, 24, 70, 72, 35, 93, 92, + 1, 76, 126, 126, 126, 20, 4, 68, 74, 24, 70, + 77, 12, 35, 65, 3, 0, 3, 106, 110, 94, 111, + 65, 71, 71, 76, 90, 82, 98, 1, 68, 69, 65, 32, + 1, 22, 0, 0, 0, 4, 96, 97, 10, 75, 69, 11, 64, + 74, 31, 17, 5, 45, 43, 6, 17, 18, 24, 8, 8, + 34, 108, 89, 89, 91, 24, 71, 17, 34, 65, 75, + 1, 15, 65, 73, 72, 81, 23, 75, 13, 0, 75, 7, + 68, 64, 7, 12, 16, 18, 7, 79, 67, 3, 65, 0, + 71, 30, 66, 1, 31, 27, 37, 30, 26, 97, 9, 9, + 75, 67, 76, 69, 31, 2, 4, 36, 55, 62, 62, 46, + 98, 107, 10, 42, 42, 125, 72, 13, 98, 24, 69, + 3, 35, 33, 54, 48, 45, 108, 0, 81, 97, 97, 69, + 14, 21, 11, 6, 11, 11, 3, 16, 9, 77, 70, 6, 1, + 0, 75, 65, 72, 78, 66, 66, 1, 71, 72, 107, 74, + 76, 69, 85, 84, 73, 2, 70, 73, 70, 75, 92, 78, + 66, 106, 123, 96, 104, 98, 126, 86, 80, 114, + 65, 75, 75, 83, 86, 98, 93, 86, 105, 93, 92, + 96, 106, 64, 11, 39, 25, 10, 4, 15, 9, 4, 0, + 13, 19, 43, 28, 21, 13, 34, 17, 20, 5, 34, 8, + 36, 23, 18, 12, 25, 11, 4, 3, 3, 68, 52, 30, + 11, 3, 20, 4, 70, 64, 17, 41, 36, 21, 10, 32, + 14, 12, 4, 3, 62, 69, 2, 9, 7, 64, 7, 11, 15, + 13, 19, 25, 37, 67, 1, 72, 71, 62, 78, 101, + 72, 4, 4, 71, 71, 70, 74, 1, 77, 96, 69, 68, + 23, 70, 77, 69, 68, 64, 73, 72, 70, 90, 83, + 73, 75, 70, 25, 101, 77, 25, 83, 69, 69, 70, + 5, 67, 72, 3, 10, 71, 71, 107, 32, 30, 31, 14, + 67, 67, 69, 79, 70, 80, 85, 79, 97, 93, 95, + 105, 101, 112, 126, 98, 89, 88, 90, 103, 95, + 90, 92, 93, 79, 78, 83, 80, 85, 104, 91, 88, + 111, 97, 109, 109, 126, 122, 117, 117, 78, 75, + 118, 86, 86, 93, 110, 98, 99, 102, 91, 96, 85, + 81, 108, 113, 126, 88, 99, 105, 70, 10, 15, + 18, 36, 24, 29, 7, 33, 28, 38, 31, 41, 35, 24, + 42, 68, 77, 85, 93, 93, 120, 119, 116, 16, 45, + 40, 43, 34, 36, 21, 20, 11, 11, 70, 10, 15, + 18, 36, 24, 29, 7, 33, 28, 38, 31, 41, 35, 24, + 42, 68, 77, 85, 93, 93, 120, 119, 116 }, + + { + + 25, + 4, 82, 25, 4, 82, 12, 21, 27, 12, 69, 93, 67, + 26, 39, 10, 37, 85, 74, 26, 70, 73, 36, 94, + 93, 0, 77, 126, 126, 126, 22, 6, 68, 74, 26, + 70, 76, 13, 36, 65, 4, 1, 4, 108, 111, 94, + 112, 65, 71, 71, 76, 91, 81, 98, 0, 68, 69, + 64, 32, 1, 22, 0, 0, 0, 5, 96, 97, 10, 75, 70, + 11, 0, 73, 33, 18, 6, 47, 45, 7, 18, 20, 25, + 9, 8, 36, 109, 89, 89, 91, 25, 71, 18, 36, 65, + 74, 2, 17, 65, 73, 72, 81, 24, 76, 14, 0, 75, + 8, 68, 0, 7, 13, 17, 19, 7, 79, 67, 3, 65, 0, + 71, 30, 66, 1, 31, 27, 38, 31, 27, 97, 10, 10, + 76, 67, 76, 69, 32, 2, 5, 37, 56, 62, 62, 47, + 99, 108, 11, 42, 42, 125, 73, 13, 98, 24, 70, + 4, 36, 33, 54, 49, 46, 109, 1, 82, 100, 100, + 67, 14, 21, 11, 5, 11, 10, 3, 15, 9, 77, 70, + 6, 0, 0, 76, 65, 73, 79, 66, 66, 1, 71, 73, + 109, 74, 77, 69, 86, 84, 76, 0, 72, 74, 72, + 77, 95, 80, 68, 108, 125, 97, 106, 100, 126, + 87, 82, 116, 67, 76, 76, 84, 88, 100, 95, 88, + 106, 93, 93, 97, 107, 1, 13, 41, 26, 10, 4, + 16, 9, 4, 1, 14, 20, 44, 29, 22, 13, 35, 18, + 21, 6, 36, 8, 37, 24, 19, 12, 26, 11, 5, 4, 4, + 68, 54, 31, 11, 3, 20, 5, 70, 64, 17, 42, 36, + 22, 10, 33, 14, 12, 5, 3, 62, 67, 4, 11, 9, 1, + 8, 13, 16, 15, 21, 27, 39, 67, 2, 72, 71, 62, + 78, 102, 72, 4, 5, 71, 71, 70, 74, 1, 77, 97, + 69, 68, 23, 70, 77, 69, 68, 64, 73, 72, 70, + 91, 84, 73, 75, 70, 26, 102, 77, 26, 84, 69, + 69, 70, 6, 67, 72, 4, 11, 71, 71, 109, 31, 29, + 30, 12, 69, 70, 71, 82, 72, 83, 88, 82, 100, + 96, 97, 108, 104, 116, 126, 100, 92, 90, 92, + 105, 97, 91, 93, 94, 79, 80, 85, 82, 87, 106, + 92, 89, 113, 99, 112, 111, 126, 124, 119, 118, + 79, 77, 120, 87, 88, 94, 112, 100, 101, 103, + 92, 97, 86, 80, 109, 115, 126, 89, 101, 106, + 69, 11, 16, 18, 37, 25, 30, 7, 34, 29, 39, 32, + 42, 36, 25, 41, 70, 79, 87, 95, 95, 122, 121, + 117, 17, 45, 41, 44, 34, 37, 22, 21, 12, 11, + 69, 11, 16, 18, 37, 25, 30, 7, 34, 29, 39, 32, + 42, 36, 25, 41, 70, 79, 87, 95, 95, 122, 121, + 117 }, + + { + + 23, + 4, 82, 23, 4, 82, 13, 23, 27, 12, 70, 95, 68, + 25, 40, 10, 39, 87, 74, 27, 70, 73, 37, 95, + 95, 65, 79, 126, 126, 126, 24, 7, 68, 74, 27, + 70, 76, 14, 36, 66, 5, 2, 5, 109, 113, 95, + 112, 65, 71, 70, 76, 91, 81, 98, 0, 68, 69, + 64, 33, 1, 22, 0, 0, 0, 5, 97, 97, 11, 76, 70, + 10, 0, 72, 35, 19, 7, 48, 46, 7, 19, 22, 26, + 10, 9, 38, 110, 89, 89, 91, 25, 72, 19, 38, + 65, 73, 3, 19, 65, 73, 72, 81, 24, 76, 14, 1, + 75, 8, 68, 0, 7, 13, 18, 19, 7, 79, 68, 3, 65, + 0, 71, 30, 66, 1, 32, 27, 38, 31, 27, 98, 10, + 11, 76, 67, 76, 69, 33, 2, 5, 38, 57, 62, 62, + 49, 99, 109, 11, 42, 42, 126, 73, 14, 99, 25, + 70, 4, 37, 33, 54, 49, 47, 110, 3, 83, 103, + 103, 66, 13, 21, 10, 4, 10, 10, 2, 15, 8, 78, + 71, 5, 0, 0, 76, 66, 74, 80, 67, 67, 0, 72, + 73, 110, 75, 78, 69, 88, 85, 78, 65, 74, 76, + 74, 79, 99, 83, 69, 110, 126, 99, 108, 102, + 126, 89, 83, 118, 68, 77, 77, 85, 89, 102, 97, + 90, 108, 93, 94, 98, 109, 2, 14, 42, 26, 10, + 4, 17, 10, 5, 2, 16, 21, 45, 29, 23, 14, 36, + 19, 23, 7, 38, 9, 37, 24, 19, 13, 27, 12, 5, + 4, 5, 68, 55, 32, 11, 3, 21, 5, 70, 0, 18, 42, + 37, 22, 10, 34, 15, 13, 5, 4, 62, 66, 5, 12, + 11, 2, 10, 14, 18, 16, 22, 29, 41, 66, 3, 71, + 70, 62, 78, 103, 72, 5, 5, 71, 71, 70, 75, 2, + 77, 98, 69, 68, 24, 70, 77, 69, 68, 64, 74, + 72, 70, 92, 85, 73, 75, 70, 27, 103, 77, 26, + 85, 69, 69, 70, 6, 67, 73, 4, 12, 71, 71, 110, + 30, 28, 29, 10, 71, 72, 74, 85, 75, 86, 92, + 85, 104, 99, 99, 112, 108, 120, 126, 103, 94, + 92, 94, 108, 99, 93, 94, 95, 79, 83, 87, 84, + 89, 108, 94, 91, 115, 101, 114, 113, 126, 126, + 121, 119, 80, 78, 123, 89, 90, 96, 114, 101, + 102, 104, 93, 98, 86, 79, 110, 116, 126, 90, + 102, 107, 69, 11, 16, 19, 38, 25, 31, 7, 35, + 30, 40, 32, 43, 36, 26, 41, 72, 81, 89, 97, + 97, 124, 123, 119, 17, 46, 41, 45, 35, 37, 22, + 21, 12, 12, 69, 11, 16, 19, 38, 25, 31, 7, 35, + 30, 40, 32, 43, 36, 26, 41, 72, 81, 89, 97, + 97, 124, 123, 119 }, + + { + + 22, + 4, 82, 22, 4, 82, 15, 24, 27, 12, 70, 97, 70, + 24, 41, 10, 40, 89, 73, 29, 71, 73, 38, 96, + 96, 66, 81, 126, 126, 126, 26, 8, 68, 73, 29, + 71, 76, 15, 36, 66, 5, 3, 7, 110, 114, 95, + 112, 65, 70, 69, 77, 91, 81, 98, 0, 67, 69, 0, + 33, 1, 22, 0, 0, 0, 6, 97, 97, 11, 77, 70, 10, + 1, 70, 38, 20, 8, 50, 48, 8, 20, 24, 27, 11, + 10, 41, 110, 90, 89, 92, 25, 72, 20, 41, 64, + 73, 3, 21, 65, 73, 72, 80, 24, 76, 15, 2, 74, + 9, 67, 0, 8, 14, 19, 20, 7, 80, 68, 4, 65, 0, + 71, 31, 66, 1, 32, 28, 38, 31, 27, 98, 11, 11, + 77, 67, 75, 69, 34, 3, 6, 40, 59, 62, 62, 51, + 100, 110, 12, 43, 42, 126, 73, 14, 99, 26, 70, + 4, 39, 33, 54, 49, 49, 111, 4, 84, 105, 106, + 65, 13, 21, 10, 4, 10, 10, 2, 15, 8, 79, 72, + 5, 0, 0, 76, 66, 74, 81, 67, 67, 0, 73, 74, + 111, 76, 78, 69, 89, 85, 80, 67, 75, 77, 75, + 81, 102, 85, 70, 112, 126, 100, 110, 104, 126, + 90, 84, 119, 69, 78, 78, 87, 91, 104, 99, 91, + 109, 93, 95, 99, 111, 4, 16, 43, 27, 10, 4, + 18, 11, 6, 2, 18, 23, 46, 30, 23, 15, 38, 20, + 24, 8, 41, 10, 38, 25, 20, 13, 28, 13, 6, 5, + 6, 67, 56, 33, 11, 3, 22, 6, 69, 1, 19, 42, + 38, 22, 10, 35, 16, 14, 6, 5, 62, 65, 7, 14, + 12, 3, 11, 16, 20, 18, 24, 31, 44, 65, 4, 70, + 69, 62, 77, 104, 71, 6, 6, 71, 71, 70, 75, 2, + 78, 99, 69, 68, 25, 70, 77, 69, 68, 0, 74, 73, + 70, 93, 86, 74, 75, 70, 28, 103, 78, 27, 85, + 70, 69, 70, 6, 68, 73, 4, 12, 71, 71, 112, 29, + 27, 28, 9, 74, 75, 77, 88, 77, 89, 95, 88, + 107, 101, 101, 116, 111, 124, 126, 106, 96, + 93, 96, 110, 101, 94, 95, 96, 79, 85, 90, 87, + 91, 110, 96, 92, 117, 103, 116, 115, 126, 126, + 122, 121, 81, 79, 125, 90, 91, 97, 116, 102, + 103, 105, 94, 99, 86, 78, 112, 117, 126, 92, + 103, 108, 69, 12, 17, 20, 39, 26, 31, 8, 36, + 31, 41, 33, 44, 37, 26, 40, 74, 83, 91, 99, + 99, 126, 125, 120, 18, 46, 42, 45, 36, 38, 23, + 22, 12, 13, 69, 12, 17, 20, 39, 26, 31, 8, 36, + 31, 41, 33, 44, 37, 26, 40, 74, 83, 91, 99, + 99, 126, 125, 120 }, + + { + + 21, + 4, 82, 21, 4, 82, 17, 26, 28, 12, 71, 99, 71, + 23, 41, 10, 42, 90, 73, 30, 71, 74, 39, 97, + 97, 67, 83, 126, 126, 126, 28, 10, 68, 73, 30, + 71, 75, 16, 37, 66, 6, 4, 8, 111, 115, 95, + 113, 65, 70, 68, 77, 92, 81, 98, 0, 67, 69, 1, + 34, 1, 22, 0, 0, 0, 6, 97, 97, 12, 78, 70, 9, + 1, 69, 40, 21, 9, 51, 49, 9, 21, 26, 28, 12, + 11, 43, 111, 90, 89, 92, 26, 72, 21, 43, 64, + 72, 4, 23, 65, 73, 72, 80, 25, 76, 15, 3, 74, + 10, 67, 0, 8, 14, 20, 20, 7, 80, 68, 4, 65, 0, + 71, 31, 66, 1, 33, 28, 39, 31, 28, 99, 11, 12, + 77, 67, 75, 69, 35, 3, 6, 41, 60, 62, 62, 53, + 100, 111, 12, 43, 42, 126, 73, 15, 100, 26, + 70, 4, 40, 33, 54, 49, 50, 112, 6, 85, 108, + 109, 0, 13, 21, 9, 3, 9, 9, 1, 15, 7, 80, 73, + 5, 0, 0, 77, 67, 75, 82, 68, 67, 0, 74, 74, + 112, 76, 79, 69, 90, 86, 82, 69, 77, 79, 77, + 83, 105, 88, 71, 114, 126, 102, 112, 106, 126, + 91, 85, 121, 70, 79, 79, 88, 92, 106, 101, 93, + 111, 93, 96, 100, 112, 5, 17, 45, 27, 10, 4, + 19, 12, 7, 3, 20, 24, 47, 31, 24, 15, 39, 21, + 26, 9, 43, 10, 38, 25, 20, 14, 29, 13, 6, 6, + 7, 67, 58, 34, 11, 3, 23, 6, 69, 2, 20, 43, + 39, 23, 10, 36, 16, 15, 6, 5, 62, 0, 8, 15, + 14, 4, 13, 17, 21, 19, 26, 33, 46, 65, 5, 70, + 68, 62, 77, 105, 71, 7, 7, 71, 71, 70, 75, 3, + 78, 100, 69, 68, 25, 70, 77, 69, 68, 0, 75, + 73, 70, 94, 87, 74, 75, 70, 29, 104, 78, 28, + 86, 70, 69, 70, 7, 68, 74, 5, 13, 71, 71, 113, + 28, 26, 27, 7, 76, 77, 79, 91, 80, 92, 98, 91, + 110, 104, 103, 120, 115, 126, 126, 109, 98, + 95, 98, 113, 103, 95, 96, 97, 79, 87, 92, 89, + 93, 112, 98, 94, 119, 105, 118, 117, 126, 126, + 124, 122, 82, 80, 126, 92, 93, 99, 118, 104, + 105, 106, 95, 100, 86, 77, 113, 119, 126, 93, + 104, 109, 69, 12, 17, 20, 40, 27, 32, 8, 37, + 32, 42, 33, 45, 37, 27, 40, 76, 85, 93, 101, + 101, 126, 126, 121, 18, 47, 42, 46, 36, 39, + 23, 22, 13, 13, 69, 12, 17, 20, 40, 27, 32, 8, + 37, 32, 42, 33, 45, 37, 27, 40, 76, 85, 93, + 101, 101, 126, 126, 121 }, + + { + + 20, + 4, 82, 20, 4, 82, 19, 27, 28, 12, 71, 101, 73, + 22, 42, 10, 44, 92, 73, 32, 71, 74, 40, 98, + 98, 68, 85, 126, 126, 126, 30, 11, 68, 73, 32, + 71, 75, 17, 37, 66, 7, 5, 9, 112, 116, 95, + 113, 65, 70, 67, 77, 92, 81, 98, 0, 67, 69, 2, + 34, 1, 22, 0, 0, 0, 7, 97, 97, 12, 79, 70, 9, + 2, 68, 42, 22, 10, 53, 51, 10, 22, 28, 29, 13, + 12, 45, 112, 90, 89, 92, 26, 72, 22, 45, 64, + 71, 5, 25, 65, 73, 72, 80, 25, 76, 16, 4, 74, + 11, 67, 0, 8, 15, 21, 21, 7, 80, 68, 4, 65, 0, + 71, 31, 66, 1, 33, 28, 39, 31, 28, 99, 12, 13, + 78, 67, 75, 69, 36, 3, 7, 42, 61, 62, 62, 55, + 101, 112, 13, 43, 42, 126, 73, 15, 100, 27, + 70, 4, 41, 33, 54, 49, 51, 113, 7, 86, 111, + 112, 1, 13, 21, 9, 2, 9, 9, 1, 15, 7, 81, 74, + 5, 0, 0, 77, 67, 76, 83, 68, 67, 0, 75, 75, + 113, 77, 80, 69, 91, 86, 84, 71, 79, 80, 79, + 85, 108, 90, 72, 116, 126, 103, 114, 108, 126, + 92, 86, 123, 71, 80, 80, 89, 94, 108, 103, 95, + 112, 93, 97, 101, 114, 7, 19, 46, 28, 10, 4, + 20, 13, 8, 4, 22, 25, 48, 32, 25, 16, 40, 22, + 27, 10, 45, 11, 39, 26, 21, 14, 30, 14, 7, 7, + 8, 67, 59, 35, 11, 3, 24, 7, 69, 3, 21, 43, + 40, 23, 10, 37, 17, 16, 7, 6, 62, 1, 10, 17, + 16, 5, 14, 19, 23, 21, 28, 35, 48, 64, 6, 69, + 67, 62, 77, 106, 71, 8, 8, 71, 71, 70, 75, 3, + 78, 101, 69, 68, 26, 70, 77, 69, 68, 0, 75, + 73, 70, 95, 88, 74, 75, 70, 30, 105, 78, 29, + 87, 70, 69, 70, 7, 68, 74, 5, 14, 71, 71, 115, + 27, 25, 26, 5, 78, 80, 82, 94, 82, 95, 101, + 94, 113, 107, 105, 124, 118, 126, 126, 112, + 100, 97, 100, 115, 105, 96, 97, 98, 79, 89, + 94, 91, 95, 114, 100, 95, 121, 107, 120, 119, + 126, 126, 126, 123, 83, 81, 126, 93, 95, 100, + 120, 105, 106, 107, 96, 101, 86, 76, 114, 120, + 126, 94, 105, 110, 69, 13, 18, 21, 41, 28, 33, + 8, 38, 33, 43, 34, 46, 38, 28, 39, 78, 87, 95, + 103, 103, 126, 126, 122, 19, 47, 43, 47, 37, + 40, 24, 23, 13, 14, 69, 13, 18, 21, 41, 28, + 33, 8, 38, 33, 43, 34, 46, 38, 28, 39, 78, 87, + 95, 103, 103, 126, 126, 122 }, + + { + + 18, + 3, 83, 18, 3, 83, 20, 28, 28, 12, 72, 103, 75, + 21, 42, 10, 45, 94, 73, 33, 72, 75, 41, 99, + 100, 70, 87, 126, 126, 126, 32, 12, 68, 73, + 33, 72, 75, 17, 37, 67, 7, 5, 10, 114, 118, + 96, 114, 66, 70, 67, 78, 93, 81, 98, 64, 67, + 69, 2, 34, 0, 22, 0, 0, 0, 7, 98, 97, 12, 80, + 71, 8, 2, 67, 44, 23, 10, 54, 52, 10, 23, 29, + 29, 14, 12, 47, 113, 91, 90, 93, 26, 73, 22, + 47, 64, 71, 5, 26, 65, 73, 72, 80, 25, 77, 16, + 4, 74, 11, 67, 0, 8, 15, 22, 21, 7, 81, 69, 4, + 65, 64, 71, 31, 66, 1, 33, 28, 39, 31, 28, + 100, 12, 13, 79, 67, 75, 70, 36, 3, 7, 43, 62, + 62, 62, 56, 102, 114, 13, 43, 42, 126, 74, 15, + 101, 27, 71, 4, 42, 33, 53, 49, 52, 114, 8, + 88, 114, 116, 2, 12, 21, 8, 1, 8, 8, 0, 14, 6, + 82, 75, 4, 64, 64, 78, 68, 77, 84, 69, 68, 64, + 76, 76, 115, 78, 81, 69, 93, 87, 87, 74, 81, + 82, 81, 88, 112, 93, 74, 118, 126, 105, 117, + 110, 126, 94, 88, 125, 73, 81, 81, 91, 96, + 110, 105, 97, 114, 93, 98, 102, 116, 8, 20, + 47, 28, 10, 4, 20, 13, 8, 4, 23, 26, 48, 32, + 25, 16, 41, 23, 28, 10, 47, 11, 39, 26, 21, + 14, 30, 14, 7, 7, 9, 67, 60, 36, 11, 2, 24, 7, + 69, 3, 21, 43, 40, 23, 10, 38, 17, 16, 7, 6, + 62, 2, 11, 18, 17, 6, 15, 20, 24, 22, 29, 36, + 50, 64, 6, 69, 67, 62, 77, 108, 71, 8, 8, 71, + 71, 70, 76, 3, 79, 102, 70, 68, 26, 71, 77, + 70, 68, 0, 76, 74, 71, 96, 89, 75, 76, 70, 31, + 106, 79, 29, 88, 71, 69, 71, 7, 69, 75, 5, 14, + 71, 71, 117, 25, 24, 24, 3, 81, 83, 85, 97, + 85, 98, 105, 97, 117, 110, 107, 126, 122, 126, + 126, 115, 103, 99, 103, 118, 107, 98, 99, 99, + 79, 92, 97, 94, 97, 117, 102, 97, 124, 109, + 123, 121, 126, 126, 126, 125, 85, 83, 126, 95, + 97, 102, 122, 107, 108, 108, 97, 102, 87, 75, + 116, 122, 126, 96, 107, 112, 69, 13, 18, 21, + 42, 28, 33, 8, 39, 33, 44, 34, 46, 38, 28, 38, + 80, 89, 98, 106, 105, 126, 126, 124, 19, 47, + 43, 47, 37, 40, 24, 23, 13, 14, 69, 13, 18, + 21, 42, 28, 33, 8, 39, 33, 44, 34, 46, 38, 28, + 38, 80, 89, 98, 106, 105, 126, 126, 124 }, + + { + + 17, + 3, 83, 17, 3, 83, 22, 30, 29, 13, 72, 104, 76, + 21, 43, 11, 47, 95, 72, 35, 72, 75, 43, 99, + 101, 71, 88, 126, 126, 126, 34, 14, 67, 72, + 35, 72, 74, 18, 38, 67, 8, 6, 12, 115, 119, + 96, 114, 66, 69, 66, 78, 93, 80, 97, 64, 66, + 68, 3, 35, 0, 22, 0, 0, 0, 8, 98, 97, 13, 80, + 71, 8, 3, 65, 47, 25, 11, 56, 54, 11, 25, 31, + 30, 16, 13, 50, 113, 91, 90, 93, 27, 73, 23, + 50, 0, 70, 6, 28, 65, 72, 72, 79, 26, 77, 17, + 5, 73, 12, 66, 1, 9, 16, 23, 22, 8, 81, 69, 5, + 64, 64, 70, 32, 65, 1, 34, 29, 40, 32, 29, + 100, 13, 14, 79, 67, 74, 70, 37, 4, 8, 45, 62, + 62, 62, 58, 102, 115, 14, 44, 43, 126, 74, 16, + 101, 28, 71, 5, 44, 33, 53, 50, 54, 115, 10, + 89, 116, 119, 4, 12, 21, 8, 1, 8, 8, 0, 14, 6, + 82, 75, 4, 64, 64, 78, 68, 77, 84, 69, 68, 64, + 76, 76, 116, 78, 81, 69, 94, 87, 89, 76, 82, + 83, 82, 90, 115, 95, 75, 119, 126, 106, 119, + 111, 126, 95, 89, 126, 74, 81, 81, 92, 97, + 111, 106, 98, 115, 93, 98, 102, 117, 10, 22, + 49, 29, 10, 4, 21, 14, 9, 5, 25, 28, 49, 33, + 26, 17, 43, 24, 30, 11, 50, 12, 40, 27, 22, + 15, 31, 15, 8, 8, 11, 66, 62, 37, 12, 2, 25, + 8, 68, 4, 22, 44, 41, 24, 11, 39, 18, 17, 8, + 7, 62, 4, 13, 20, 19, 8, 17, 22, 26, 24, 31, + 38, 53, 0, 7, 68, 66, 62, 76, 109, 70, 9, 9, + 70, 71, 69, 76, 4, 79, 102, 70, 68, 27, 71, + 77, 70, 68, 1, 76, 74, 71, 96, 89, 75, 76, 69, + 33, 106, 79, 30, 88, 71, 69, 71, 8, 69, 75, 6, + 15, 70, 70, 118, 24, 23, 23, 2, 83, 85, 87, + 100, 87, 100, 108, 99, 120, 112, 109, 126, + 125, 126, 126, 117, 105, 100, 105, 120, 108, + 99, 100, 99, 79, 94, 99, 96, 99, 119, 103, 98, + 126, 110, 125, 122, 126, 126, 126, 126, 86, + 84, 126, 96, 98, 103, 123, 108, 109, 109, 97, + 102, 87, 74, 117, 123, 126, 97, 108, 113, 68, + 14, 19, 22, 44, 29, 34, 9, 41, 34, 45, 35, 47, + 39, 29, 38, 81, 90, 100, 108, 106, 126, 126, + 125, 20, 48, 44, 48, 38, 41, 25, 24, 14, 15, + 68, 14, 19, 22, 44, 29, 34, 9, 41, 34, 45, 35, + 47, 39, 29, 38, 81, 90, 100, 108, 106, 126, + 126, 125 }, + + { + + 16, + 3, 83, 16, 3, 83, 24, 31, 29, 13, 72, 106, 78, + 20, 44, 11, 49, 97, 72, 36, 72, 75, 44, 100, + 102, 72, 90, 126, 126, 126, 36, 15, 67, 72, + 36, 72, 74, 19, 38, 67, 9, 7, 13, 116, 120, + 96, 114, 66, 69, 65, 78, 93, 80, 97, 64, 66, + 68, 4, 35, 0, 22, 0, 0, 0, 9, 98, 97, 13, 81, + 71, 8, 4, 64, 49, 26, 12, 58, 56, 12, 26, 33, + 31, 17, 14, 52, 114, 91, 90, 93, 27, 73, 24, + 52, 0, 69, 7, 30, 65, 72, 72, 79, 26, 77, 17, + 6, 73, 13, 66, 1, 9, 16, 24, 23, 8, 81, 69, 5, + 64, 64, 70, 32, 65, 1, 34, 29, 40, 32, 29, + 101, 14, 15, 80, 67, 74, 70, 38, 4, 8, 46, 62, + 62, 62, 60, 103, 116, 14, 44, 43, 126, 74, 16, + 102, 29, 71, 5, 45, 33, 53, 50, 55, 116, 11, + 90, 119, 122, 5, 12, 21, 8, 0, 7, 8, 0, 14, 5, + 83, 76, 4, 64, 64, 78, 69, 78, 85, 69, 68, 64, + 77, 77, 117, 79, 82, 69, 95, 88, 91, 78, 84, + 85, 84, 92, 118, 97, 76, 121, 126, 108, 121, + 113, 126, 96, 90, 126, 75, 82, 82, 93, 99, + 113, 108, 100, 117, 93, 99, 103, 119, 11, 23, + 50, 30, 10, 4, 22, 15, 10, 6, 27, 29, 50, 34, + 27, 18, 44, 25, 31, 12, 52, 13, 40, 27, 22, + 15, 32, 16, 9, 9, 12, 66, 62, 38, 12, 2, 26, + 9, 68, 5, 23, 44, 42, 24, 11, 40, 19, 18, 9, + 8, 62, 5, 15, 22, 21, 9, 18, 24, 28, 25, 33, + 40, 55, 1, 8, 67, 65, 62, 76, 110, 70, 10, 10, + 70, 71, 69, 76, 4, 79, 103, 70, 68, 28, 71, + 77, 70, 68, 1, 76, 74, 71, 97, 90, 75, 76, 69, + 34, 107, 79, 31, 89, 71, 69, 71, 8, 69, 75, 6, + 16, 70, 70, 120, 23, 22, 22, 0, 85, 88, 90, + 103, 89, 103, 111, 102, 123, 115, 111, 126, + 126, 126, 126, 120, 107, 102, 107, 122, 110, + 100, 101, 100, 79, 96, 101, 98, 101, 121, 105, + 100, 126, 112, 126, 124, 126, 126, 126, 126, + 87, 85, 126, 98, 100, 105, 125, 109, 110, 110, + 98, 103, 87, 73, 118, 124, 126, 98, 109, 114, + 68, 14, 20, 23, 45, 30, 35, 9, 42, 35, 46, 35, + 48, 40, 30, 37, 83, 92, 102, 110, 108, 126, + 126, 126, 21, 48, 44, 49, 39, 42, 25, 25, 14, + 16, 68, 14, 20, 23, 45, 30, 35, 9, 42, 35, 46, + 35, 48, 40, 30, 37, 83, 92, 102, 110, 108, + 126, 126, 126 }, + + { + + 15, + 3, 83, 15, 3, 83, 26, 33, 30, 13, 73, 108, 79, + 19, 44, 11, 51, 98, 72, 38, 72, 76, 45, 101, + 103, 73, 92, 126, 126, 126, 38, 17, 67, 72, + 38, 72, 73, 20, 39, 67, 10, 8, 14, 117, 121, + 96, 115, 66, 69, 64, 78, 94, 80, 97, 64, 66, + 68, 5, 36, 0, 22, 0, 0, 0, 9, 98, 97, 14, 82, + 71, 7, 4, 0, 51, 27, 13, 59, 57, 13, 27, 35, + 32, 18, 15, 54, 115, 91, 90, 93, 28, 73, 25, + 54, 0, 68, 8, 32, 65, 72, 72, 79, 27, 77, 18, + 7, 73, 14, 66, 1, 9, 17, 25, 23, 8, 81, 69, 5, + 64, 64, 70, 32, 65, 1, 35, 29, 41, 32, 30, + 101, 14, 16, 80, 67, 74, 70, 39, 4, 9, 47, 62, + 62, 62, 62, 103, 117, 15, 44, 43, 126, 74, 17, + 102, 29, 71, 5, 46, 33, 53, 50, 56, 117, 13, + 91, 122, 125, 7, 12, 21, 7, 64, 7, 7, 64, 14, + 5, 84, 77, 4, 64, 64, 79, 69, 79, 86, 70, 68, + 64, 78, 77, 118, 79, 83, 69, 96, 88, 93, 80, + 86, 86, 86, 94, 121, 100, 77, 123, 126, 109, + 123, 115, 126, 97, 91, 126, 76, 83, 83, 94, + 100, 115, 110, 102, 118, 93, 100, 104, 120, + 13, 25, 52, 30, 10, 4, 23, 16, 11, 7, 29, 30, + 51, 35, 28, 18, 45, 26, 33, 13, 54, 13, 41, + 28, 23, 16, 33, 16, 9, 10, 13, 66, 62, 39, 12, + 2, 27, 9, 68, 6, 24, 45, 43, 25, 11, 41, 19, + 19, 9, 8, 62, 7, 16, 23, 23, 10, 20, 25, 29, + 27, 35, 42, 57, 1, 9, 67, 64, 62, 76, 111, 70, + 11, 11, 70, 71, 69, 76, 5, 79, 104, 70, 68, + 28, 71, 77, 70, 68, 1, 77, 74, 71, 98, 91, 75, + 76, 69, 35, 108, 79, 32, 90, 71, 69, 71, 9, + 69, 76, 7, 17, 70, 70, 121, 22, 21, 21, 65, + 87, 90, 92, 106, 92, 106, 114, 105, 126, 118, + 113, 126, 126, 126, 126, 123, 109, 104, 109, + 125, 112, 101, 102, 101, 79, 98, 103, 100, + 103, 123, 107, 101, 126, 114, 126, 126, 126, + 126, 126, 126, 88, 86, 126, 99, 102, 106, 126, + 111, 112, 111, 99, 104, 87, 72, 119, 126, 126, + 99, 110, 115, 68, 15, 20, 23, 46, 31, 36, 9, + 43, 36, 47, 36, 49, 40, 31, 37, 85, 94, 104, + 112, 110, 126, 126, 126, 21, 49, 45, 50, 39, + 43, 26, 25, 15, 16, 68, 15, 20, 23, 46, 31, + 36, 9, 43, 36, 47, 36, 49, 40, 31, 37, 85, 94, + 104, 112, 110, 126, 126, 126 }, + + }, + + { + + { + + 62, + 9, 74, 62, 9, 74, 126, 104, 10, 9, 12, 30, 61, + 62, 54, 14, 118, 6, 78, 65, 1, 14, 73, 13, 64, + 20, 62, 67, 90, 104, 126, 104, 67, 78, 65, 1, + 86, 95, 2, 18, 69, 81, 96, 8, 67, 86, 88, 5, 76, + 94, 9, 69, 81, 88, 67, 74, 74, 80, 72, 5, 22, 0, + 0, 0, 83, 86, 97, 72, 22, 1, 52, 8, 69, 126, + 102, 82, 74, 107, 126, 126, 126, 95, 126, 114, + 126, 123, 115, 122, 115, 0, 68, 84, 104, 70, 93, + 90, 126, 74, 97, 91, 126, 7, 82, 76, 125, 93, + 87, 77, 71, 0, 68, 84, 1, 65, 2, 7, 66, 64, 2, + 78, 13, 11, 28, 19, 25, 18, 17, 19, 46, 12, 13, + 44, 30, 1, 108, 100, 101, 91, 94, 88, 84, 86, + 83, 87, 94, 70, 72, 74, 4, 102, 100, 95, 75, 72, + 75, 71, 17, 69, 1, 65, 26, 72, 6, 9, 1, 72, 62, + 54, 38, 45, 54, 44, 26, 45, 34, 30, 33, 18, 5, + 1, 2, 25, 18, 24, 21, 19, 18, 22, 14, 29, 21, 8, + 12, 17, 89, 62, 62, 62, 62, 62, 62, 62, 62, 62, + 62, 62, 62, 62, 62, 46, 62, 60, 41, 62, 62, 62, + 62, 60, 58, 62, 47, 41, 15, 26, 3, 68, 97, 71, + 21, 13, 9, 1, 5, 0, 72, 74, 91, 67, 36, 24, 19, + 17, 64, 68, 78, 77, 86, 92, 8, 3, 1, 65, 73, 76, + 80, 88, 110, 97, 84, 79, 73, 74, 86, 96, 97, + 117, 78, 30, 15, 10, 1, 71, 79, 86, 90, 97, 62, + 93, 84, 79, 66, 71, 1, 3, 4, 75, 1, 5, 66, 79, + 71, 68, 19, 1, 27, 23, 36, 34, 19, 27, 31, 21, + 15, 1, 17, 64, 104, 97, 96, 88, 85, 85, 85, 88, + 66, 77, 76, 76, 5, 76, 83, 99, 95, 95, 76, 74, + 70, 75, 68, 65, 73, 1, 1, 68, 75, 8, 64, 70, 57, + 44, 47, 49, 50, 52, 48, 47, 40, 40, 43, 37, 19, + 23, 16, 46, 42, 41, 36, 34, 28, 13, 6, 0, 77, + 82, 94, 69, 109, 62, 62, 62, 62, 62, 62, 62, 62, + 62, 62, 62, 61, 50, 28, 5, 62, 62, 33, 62, 62, + 62, 60, 62, 58, 52, 58, 51, 52, 34, 37, 24, 66, + 42, 32, 13, 120, 112, 114, 85, 92, 89, 71, 81, + 80, 68, 70, 7, 68, 13, 74, 62, 62, 62, 62, 60, + 57, 29, 9, 82, 75, 40, 29, 20, 9, 8, 2, 64, 68, + 92, 106, 97, 90, 90, 88, 73, 79, 86, 73, 70, 69, + 66, 64, 5, 4, 62, 62, 62, 62, 60, 54, 43, 27, 67 }, + + { + + 62, + 9, 74, 62, 9, 74, 125, 102, 11, 10, 12, 29, + 60, 62, 54, 14, 115, 6, 77, 64, 1, 14, 72, 12, + 65, 20, 62, 68, 91, 104, 124, 102, 67, 77, 64, + 1, 85, 93, 3, 18, 68, 80, 95, 8, 67, 85, 88, + 5, 75, 93, 9, 69, 80, 88, 66, 73, 73, 79, 71, + 5, 22, 0, 0, 0, 82, 86, 97, 71, 22, 1, 52, 8, + 69, 125, 101, 82, 73, 105, 125, 125, 125, 93, + 125, 112, 125, 121, 114, 121, 114, 1, 67, 83, + 103, 69, 92, 89, 125, 73, 96, 90, 125, 8, 81, + 75, 123, 92, 86, 76, 70, 1, 67, 83, 2, 64, 2, + 7, 65, 64, 2, 77, 13, 11, 28, 19, 25, 18, 17, + 19, 45, 12, 13, 43, 29, 1, 107, 99, 100, 90, + 93, 87, 83, 85, 82, 86, 92, 70, 72, 73, 3, + 101, 99, 95, 74, 72, 74, 70, 17, 68, 1, 65, + 25, 71, 6, 8, 1, 72, 62, 54, 38, 45, 54, 44, + 26, 45, 34, 29, 33, 18, 5, 1, 2, 25, 18, 24, + 21, 19, 17, 22, 14, 28, 20, 8, 11, 16, 89, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, + 62, 60, 44, 62, 59, 40, 62, 62, 62, 62, 58, + 56, 61, 45, 39, 15, 25, 2, 68, 97, 70, 22, 14, + 10, 2, 5, 0, 71, 73, 90, 66, 37, 25, 20, 17, + 0, 67, 77, 76, 85, 91, 9, 4, 2, 64, 72, 75, + 79, 87, 108, 96, 82, 78, 72, 73, 85, 95, 96, + 115, 77, 31, 16, 11, 2, 70, 78, 85, 89, 96, + 62, 92, 83, 78, 66, 70, 1, 4, 5, 74, 2, 6, 65, + 78, 71, 68, 19, 2, 27, 23, 35, 34, 19, 26, 30, + 21, 15, 1, 16, 64, 103, 96, 95, 87, 84, 84, + 84, 87, 66, 76, 75, 75, 5, 75, 82, 98, 94, 95, + 76, 73, 70, 74, 68, 65, 72, 1, 1, 67, 74, 8, + 64, 70, 57, 44, 47, 49, 49, 52, 48, 47, 40, + 40, 43, 37, 19, 22, 15, 45, 41, 40, 35, 33, + 27, 13, 6, 0, 76, 81, 93, 69, 108, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 61, 59, 48, 27, 5, + 62, 62, 32, 62, 62, 62, 58, 62, 56, 50, 56, + 49, 50, 33, 35, 23, 67, 41, 31, 12, 118, 110, + 112, 84, 91, 88, 69, 80, 79, 68, 69, 9, 66, + 15, 73, 62, 62, 62, 62, 58, 55, 27, 7, 83, 74, + 41, 29, 20, 9, 9, 2, 64, 68, 91, 105, 96, 89, + 89, 86, 72, 78, 85, 72, 69, 68, 65, 0, 6, 4, + 62, 62, 62, 62, 59, 53, 41, 26, 67 }, + + { + + 62, + 9, 74, 62, 9, 74, 123, 101, 11, 10, 12, 28, + 59, 61, 54, 14, 113, 6, 76, 0, 1, 13, 72, 11, + 66, 19, 60, 70, 92, 105, 121, 101, 67, 76, 0, + 1, 85, 92, 3, 17, 68, 80, 94, 8, 67, 85, 88, + 5, 75, 92, 9, 69, 80, 88, 66, 73, 73, 79, 71, + 5, 22, 0, 0, 0, 81, 86, 97, 71, 21, 1, 52, 8, + 69, 124, 100, 82, 73, 104, 123, 123, 124, 92, + 123, 111, 123, 120, 113, 120, 113, 2, 67, 82, + 102, 69, 92, 88, 123, 73, 96, 90, 124, 8, 81, + 75, 122, 92, 85, 76, 70, 1, 67, 82, 2, 64, 1, + 7, 65, 64, 2, 77, 13, 11, 27, 19, 24, 18, 17, + 19, 43, 12, 13, 41, 28, 0, 106, 98, 99, 89, + 92, 86, 82, 84, 82, 85, 91, 70, 72, 73, 2, + 101, 98, 95, 74, 72, 73, 70, 16, 67, 1, 65, + 24, 70, 5, 7, 1, 73, 60, 53, 37, 44, 53, 43, + 25, 44, 34, 28, 32, 18, 5, 1, 2, 24, 17, 23, + 20, 18, 16, 21, 13, 26, 19, 7, 10, 15, 89, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, + 62, 58, 41, 62, 57, 38, 62, 62, 62, 62, 56, + 54, 58, 43, 37, 14, 23, 1, 69, 97, 70, 22, 14, + 10, 2, 5, 0, 71, 73, 89, 66, 37, 25, 20, 17, + 1, 67, 76, 76, 84, 90, 10, 5, 2, 64, 71, 75, + 79, 86, 107, 95, 81, 77, 72, 73, 84, 94, 95, + 114, 77, 31, 16, 11, 2, 69, 77, 84, 88, 95, + 62, 92, 83, 78, 66, 70, 1, 4, 5, 74, 2, 6, 64, + 78, 71, 68, 18, 2, 26, 22, 34, 33, 19, 25, 29, + 21, 15, 0, 15, 65, 102, 95, 94, 87, 84, 84, + 83, 86, 66, 76, 75, 75, 4, 75, 82, 98, 93, 95, + 76, 73, 70, 73, 68, 65, 71, 1, 1, 67, 73, 7, + 64, 71, 56, 44, 47, 48, 48, 51, 47, 46, 39, + 39, 42, 36, 18, 21, 14, 43, 40, 38, 33, 32, + 26, 12, 5, 0, 76, 81, 93, 70, 107, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 59, 57, 46, 26, 4, + 62, 60, 31, 62, 62, 62, 56, 60, 54, 48, 54, + 47, 48, 31, 33, 21, 68, 39, 29, 10, 117, 109, + 111, 83, 90, 87, 67, 79, 78, 68, 68, 10, 65, + 16, 72, 62, 62, 62, 62, 55, 52, 24, 5, 84, 74, + 41, 29, 20, 9, 9, 2, 64, 68, 90, 104, 95, 88, + 88, 85, 71, 77, 84, 71, 68, 67, 65, 1, 6, 4, + 62, 62, 62, 61, 57, 51, 39, 24, 68 }, + + { + + 62, + 9, 74, 62, 9, 74, 121, 99, 12, 10, 11, 26, 57, + 60, 54, 14, 111, 6, 75, 1, 1, 12, 72, 10, 67, + 19, 58, 71, 93, 105, 118, 100, 67, 75, 1, 1, + 84, 91, 4, 17, 68, 79, 93, 7, 68, 85, 88, 5, + 75, 92, 9, 69, 80, 88, 65, 73, 73, 79, 70, 5, + 22, 0, 0, 0, 81, 86, 97, 70, 20, 1, 52, 8, 69, + 123, 99, 82, 72, 103, 121, 121, 122, 91, 121, + 110, 121, 119, 112, 119, 112, 3, 67, 81, 101, + 69, 91, 88, 121, 73, 95, 89, 123, 8, 81, 74, + 120, 91, 84, 76, 70, 1, 67, 81, 3, 0, 1, 7, + 65, 64, 2, 77, 13, 10, 27, 19, 23, 18, 17, 19, + 41, 12, 12, 39, 27, 64, 105, 97, 98, 88, 91, + 86, 81, 84, 81, 84, 90, 70, 72, 73, 1, 100, + 97, 95, 74, 72, 72, 70, 15, 66, 1, 65, 23, 69, + 5, 6, 1, 74, 59, 52, 37, 43, 52, 42, 25, 43, + 33, 27, 31, 18, 5, 1, 1, 23, 16, 22, 19, 17, + 15, 20, 13, 24, 18, 7, 9, 14, 89, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 55, + 39, 62, 55, 37, 62, 61, 62, 59, 54, 51, 56, + 41, 34, 13, 21, 0, 70, 97, 70, 23, 14, 10, 2, + 5, 0, 71, 73, 89, 66, 37, 25, 20, 17, 2, 66, + 76, 75, 84, 89, 11, 5, 3, 64, 70, 74, 78, 86, + 106, 94, 80, 76, 71, 73, 83, 93, 94, 113, 76, + 31, 16, 11, 2, 68, 77, 83, 87, 94, 62, 91, 82, + 77, 66, 70, 1, 4, 5, 74, 2, 6, 64, 78, 71, 68, + 18, 3, 25, 21, 33, 32, 19, 24, 28, 21, 15, 0, + 14, 65, 101, 94, 93, 86, 83, 83, 83, 85, 66, + 76, 75, 74, 4, 75, 82, 97, 92, 95, 76, 73, 70, + 72, 68, 65, 70, 1, 1, 67, 72, 6, 64, 72, 55, + 43, 46, 47, 47, 50, 46, 45, 38, 38, 41, 35, + 17, 20, 13, 42, 39, 37, 31, 30, 25, 11, 5, 64, + 76, 81, 93, 70, 106, 62, 62, 62, 62, 62, 62, + 62, 62, 62, 62, 57, 54, 44, 24, 3, 61, 59, 29, + 62, 62, 60, 54, 58, 52, 46, 52, 45, 45, 29, + 31, 19, 69, 37, 27, 9, 116, 108, 110, 82, 89, + 86, 66, 78, 77, 68, 67, 12, 0, 18, 71, 62, 62, + 62, 62, 52, 49, 21, 3, 85, 74, 41, 29, 20, 9, + 9, 2, 64, 68, 90, 103, 94, 87, 87, 84, 71, 77, + 83, 71, 68, 67, 65, 1, 6, 4, 62, 62, 62, 59, + 55, 49, 37, 22, 69 }, + + { + + 62, + 9, 74, 62, 9, 74, 120, 98, 12, 10, 11, 25, 56, + 58, 54, 14, 108, 5, 74, 1, 1, 11, 72, 9, 68, + 18, 56, 73, 94, 106, 115, 99, 67, 74, 1, 1, + 84, 90, 4, 16, 68, 79, 93, 7, 68, 84, 88, 5, + 75, 91, 8, 70, 80, 88, 65, 72, 73, 78, 70, 5, + 22, 0, 0, 0, 80, 87, 97, 70, 19, 1, 52, 8, 69, + 122, 98, 82, 72, 101, 120, 119, 121, 90, 120, + 108, 119, 118, 112, 118, 112, 3, 67, 80, 100, + 69, 91, 87, 119, 73, 95, 89, 122, 8, 80, 74, + 119, 91, 84, 76, 69, 1, 67, 81, 3, 0, 0, 6, + 65, 64, 2, 77, 13, 10, 26, 19, 23, 18, 17, 18, + 39, 12, 12, 37, 26, 65, 104, 96, 97, 87, 91, + 85, 80, 83, 81, 83, 89, 70, 72, 72, 0, 100, + 96, 95, 74, 72, 72, 70, 14, 65, 1, 65, 21, 68, + 4, 5, 1, 75, 57, 51, 36, 42, 51, 41, 24, 42, + 33, 25, 30, 17, 5, 1, 1, 22, 16, 21, 19, 16, + 14, 19, 12, 22, 17, 6, 8, 13, 89, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 62, 59, 53, + 36, 62, 54, 35, 62, 59, 62, 57, 51, 49, 53, + 39, 32, 12, 20, 65, 71, 97, 70, 23, 15, 10, 2, + 5, 0, 71, 73, 88, 65, 38, 25, 20, 17, 3, 66, + 75, 75, 83, 89, 12, 6, 3, 64, 70, 74, 78, 85, + 105, 94, 79, 76, 71, 73, 82, 92, 94, 112, 76, + 32, 16, 11, 2, 67, 76, 83, 86, 93, 62, 91, 82, + 77, 66, 70, 1, 4, 5, 73, 2, 6, 0, 78, 71, 68, + 17, 3, 24, 20, 32, 31, 19, 22, 27, 20, 15, 64, + 13, 66, 101, 94, 92, 86, 83, 83, 82, 84, 67, + 76, 75, 74, 3, 75, 82, 97, 91, 95, 76, 72, 70, + 72, 68, 65, 69, 1, 0, 67, 71, 6, 65, 73, 54, + 43, 46, 46, 46, 49, 45, 44, 37, 37, 40, 34, + 16, 19, 12, 40, 37, 35, 29, 29, 24, 10, 4, 64, + 76, 81, 93, 71, 106, 62, 62, 62, 62, 62, 62, + 62, 62, 62, 60, 55, 52, 42, 23, 2, 59, 57, 28, + 62, 62, 58, 52, 55, 50, 44, 50, 43, 43, 27, + 29, 17, 70, 35, 25, 7, 115, 107, 109, 82, 88, + 85, 64, 77, 76, 68, 66, 13, 1, 19, 71, 62, 62, + 62, 62, 49, 46, 18, 1, 86, 74, 41, 29, 20, 9, + 9, 2, 64, 68, 89, 102, 93, 86, 87, 83, 70, 76, + 82, 70, 67, 66, 64, 2, 7, 4, 62, 62, 62, 57, + 53, 47, 35, 20, 70 }, + + { + + 62, + 9, 74, 62, 9, 74, 118, 96, 12, 10, 10, 23, 54, + 57, 54, 14, 106, 5, 73, 2, 1, 11, 71, 8, 69, + 18, 54, 75, 95, 106, 112, 97, 67, 73, 2, 1, + 84, 89, 4, 16, 68, 79, 92, 7, 69, 84, 88, 5, + 75, 90, 8, 70, 80, 88, 64, 72, 72, 78, 69, 5, + 22, 0, 0, 0, 80, 87, 97, 69, 18, 1, 52, 8, 69, + 121, 97, 82, 71, 100, 118, 117, 119, 89, 118, + 107, 117, 117, 111, 117, 111, 4, 67, 79, 99, + 69, 90, 86, 117, 73, 95, 88, 120, 9, 80, 73, + 118, 90, 83, 76, 69, 2, 66, 80, 4, 1, 0, 6, + 65, 64, 2, 77, 13, 9, 25, 19, 22, 18, 17, 18, + 37, 12, 11, 36, 25, 66, 103, 95, 96, 86, 90, + 84, 79, 82, 80, 82, 88, 70, 72, 72, 64, 99, + 95, 95, 73, 72, 71, 70, 13, 64, 1, 65, 20, 67, + 4, 4, 1, 75, 56, 50, 36, 41, 50, 40, 23, 42, + 33, 24, 29, 17, 5, 1, 0, 22, 15, 20, 18, 15, + 13, 19, 11, 20, 16, 5, 7, 12, 89, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 62, 57, 51, + 34, 60, 52, 33, 62, 57, 60, 55, 49, 47, 50, + 37, 29, 11, 18, 66, 71, 97, 70, 23, 15, 10, 2, + 5, 0, 71, 73, 88, 65, 38, 25, 20, 17, 4, 65, + 74, 75, 82, 88, 13, 7, 3, 0, 69, 73, 77, 85, + 104, 93, 77, 75, 71, 72, 81, 91, 93, 111, 75, + 32, 17, 11, 2, 66, 75, 82, 85, 92, 62, 91, 82, + 76, 66, 70, 1, 4, 5, 73, 2, 7, 0, 78, 71, 68, + 16, 4, 23, 19, 31, 31, 19, 21, 26, 20, 15, 65, + 12, 66, 100, 93, 91, 85, 82, 82, 82, 83, 67, + 76, 75, 74, 2, 75, 82, 96, 90, 95, 76, 72, 70, + 71, 68, 65, 68, 1, 0, 67, 70, 5, 65, 73, 53, + 43, 45, 46, 45, 48, 44, 43, 37, 36, 39, 33, + 15, 18, 11, 39, 36, 34, 27, 28, 23, 9, 3, 65, + 76, 80, 93, 71, 105, 62, 62, 62, 62, 62, 62, + 62, 62, 60, 58, 53, 50, 40, 21, 1, 57, 55, 27, + 61, 62, 56, 50, 53, 48, 42, 48, 41, 40, 25, + 27, 15, 71, 33, 23, 6, 114, 105, 108, 81, 87, + 84, 1, 76, 75, 68, 65, 15, 3, 21, 70, 62, 62, + 62, 62, 47, 43, 16, 64, 87, 74, 41, 29, 20, 9, + 9, 2, 64, 68, 89, 101, 92, 85, 86, 82, 69, 76, + 81, 69, 66, 65, 64, 2, 7, 4, 62, 62, 62, 56, + 51, 45, 33, 18, 71 }, + + { + + 62, + 9, 75, 62, 9, 75, 116, 95, 13, 10, 10, 22, 53, + 56, 54, 14, 104, 5, 73, 3, 1, 10, 71, 7, 70, + 17, 53, 76, 96, 107, 109, 96, 67, 73, 3, 1, + 83, 88, 5, 15, 67, 78, 91, 6, 69, 84, 88, 5, + 74, 90, 8, 70, 79, 88, 64, 72, 72, 78, 69, 5, + 22, 0, 0, 0, 79, 87, 97, 69, 18, 0, 52, 8, 69, + 120, 97, 82, 71, 99, 116, 115, 118, 88, 116, + 106, 115, 116, 110, 116, 110, 5, 67, 78, 99, + 68, 90, 86, 115, 73, 94, 88, 119, 9, 80, 73, + 116, 90, 82, 75, 69, 2, 66, 79, 4, 1, 64, 6, + 65, 64, 2, 77, 13, 9, 25, 19, 21, 18, 17, 18, + 35, 12, 11, 34, 24, 67, 103, 94, 96, 86, 89, + 84, 78, 82, 80, 82, 86, 70, 72, 72, 65, 99, + 94, 95, 73, 72, 70, 69, 12, 64, 1, 65, 19, 66, + 3, 3, 1, 76, 54, 49, 35, 41, 49, 40, 23, 41, + 32, 23, 28, 17, 5, 1, 0, 21, 14, 19, 17, 15, + 12, 18, 11, 18, 15, 5, 6, 11, 89, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 62, 54, 48, + 31, 58, 50, 32, 62, 54, 57, 52, 47, 44, 48, + 34, 27, 10, 16, 67, 72, 97, 69, 24, 15, 11, 2, + 5, 0, 71, 73, 87, 65, 38, 26, 20, 17, 5, 65, + 74, 74, 82, 87, 14, 7, 4, 0, 68, 73, 77, 84, + 103, 92, 76, 74, 70, 72, 81, 91, 92, 109, 75, + 32, 17, 11, 3, 66, 75, 81, 85, 91, 62, 90, 81, + 76, 66, 70, 1, 4, 5, 73, 3, 7, 1, 78, 71, 69, + 16, 4, 22, 18, 30, 30, 19, 20, 25, 20, 15, 65, + 11, 67, 99, 92, 90, 85, 82, 82, 81, 83, 67, + 75, 74, 73, 2, 75, 82, 96, 89, 95, 76, 72, 70, + 70, 68, 65, 67, 0, 0, 67, 70, 4, 65, 74, 52, + 42, 45, 45, 44, 48, 44, 42, 36, 36, 38, 32, + 14, 17, 10, 37, 35, 32, 25, 26, 21, 8, 3, 65, + 76, 80, 92, 72, 104, 62, 62, 62, 62, 62, 62, + 62, 62, 58, 55, 51, 47, 38, 20, 1, 56, 54, 25, + 59, 62, 54, 48, 51, 46, 40, 45, 39, 38, 23, + 25, 14, 73, 31, 21, 4, 113, 104, 107, 80, 86, + 83, 2, 75, 74, 68, 64, 16, 4, 22, 69, 62, 62, + 62, 59, 44, 41, 13, 66, 89, 73, 41, 29, 20, 9, + 9, 2, 64, 68, 88, 100, 92, 84, 85, 81, 69, 75, + 80, 69, 66, 65, 64, 3, 7, 4, 62, 62, 61, 54, + 50, 44, 30, 17, 72 }, + + { + + 62, + 9, 75, 62, 9, 75, 114, 93, 13, 10, 9, 20, 51, + 54, 54, 14, 101, 4, 72, 3, 1, 9, 71, 6, 71, + 17, 51, 78, 97, 107, 106, 95, 67, 72, 3, 1, + 83, 87, 5, 15, 67, 78, 91, 6, 70, 83, 88, 5, + 74, 89, 7, 70, 79, 88, 0, 71, 72, 77, 68, 5, + 22, 0, 0, 0, 79, 87, 97, 68, 17, 0, 52, 8, 69, + 119, 96, 82, 70, 97, 115, 113, 116, 87, 115, + 104, 113, 115, 109, 115, 110, 6, 67, 77, 98, + 68, 89, 85, 113, 73, 94, 87, 118, 9, 79, 72, + 115, 89, 82, 75, 68, 2, 66, 78, 5, 2, 64, 5, + 65, 64, 2, 77, 13, 8, 24, 19, 21, 18, 17, 17, + 33, 12, 10, 32, 23, 68, 102, 93, 95, 85, 88, + 83, 77, 81, 79, 81, 85, 70, 72, 71, 66, 98, + 93, 95, 73, 72, 70, 69, 11, 0, 1, 65, 17, 65, + 3, 2, 1, 77, 53, 48, 35, 40, 48, 39, 22, 40, + 32, 22, 27, 17, 5, 1, 64, 20, 14, 18, 17, 14, + 11, 17, 10, 16, 14, 4, 5, 10, 89, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 60, 61, 52, 46, + 29, 56, 49, 30, 62, 52, 55, 50, 44, 42, 45, + 32, 24, 9, 15, 69, 73, 97, 69, 24, 16, 11, 2, + 5, 0, 71, 73, 87, 64, 39, 26, 20, 17, 6, 64, + 73, 74, 81, 86, 15, 8, 4, 0, 67, 72, 76, 84, + 102, 92, 75, 74, 70, 72, 80, 90, 92, 108, 74, + 33, 17, 11, 3, 65, 74, 80, 84, 90, 62, 90, 81, + 75, 66, 70, 1, 4, 5, 72, 3, 7, 1, 78, 71, 69, + 15, 5, 21, 17, 29, 29, 19, 19, 24, 19, 15, 66, + 10, 67, 98, 92, 89, 84, 81, 81, 81, 82, 67, + 75, 74, 73, 1, 75, 82, 95, 88, 95, 76, 71, 70, + 70, 68, 65, 66, 0, 0, 67, 69, 4, 66, 75, 51, + 42, 44, 44, 43, 47, 43, 41, 35, 35, 37, 31, + 13, 16, 9, 36, 33, 31, 23, 25, 20, 7, 2, 66, + 76, 80, 92, 72, 103, 62, 62, 62, 62, 62, 62, + 62, 61, 56, 53, 49, 45, 36, 18, 0, 54, 52, 24, + 57, 62, 52, 46, 49, 44, 38, 43, 37, 35, 21, + 23, 12, 74, 29, 19, 3, 112, 103, 106, 80, 85, + 82, 4, 74, 73, 68, 0, 18, 6, 24, 69, 62, 62, + 61, 56, 41, 38, 10, 68, 90, 73, 41, 29, 20, 9, + 9, 2, 64, 68, 88, 99, 91, 83, 84, 80, 68, 75, + 79, 68, 65, 64, 0, 3, 8, 4, 62, 62, 59, 52, + 48, 42, 28, 15, 73 }, + + { + + 62, + 8, 75, 62, 8, 75, 113, 92, 13, 10, 9, 19, 50, + 53, 54, 14, 99, 4, 71, 4, 1, 8, 71, 5, 73, 16, + 49, 80, 98, 108, 104, 94, 67, 71, 4, 1, 83, + 86, 5, 14, 67, 78, 90, 5, 70, 83, 89, 5, 74, + 89, 7, 71, 79, 88, 0, 71, 72, 77, 68, 5, 22, + 0, 0, 0, 78, 88, 97, 68, 16, 0, 52, 8, 69, + 118, 95, 82, 70, 96, 113, 111, 115, 86, 113, + 103, 112, 114, 109, 114, 109, 6, 67, 76, 97, + 68, 89, 85, 112, 73, 94, 87, 117, 9, 79, 72, + 114, 89, 81, 75, 68, 2, 66, 78, 5, 2, 65, 5, + 65, 64, 2, 77, 13, 8, 23, 19, 20, 18, 17, 17, + 31, 12, 10, 30, 22, 69, 101, 92, 94, 84, 88, + 83, 76, 81, 79, 80, 84, 70, 72, 71, 68, 98, + 92, 95, 73, 73, 69, 69, 10, 1, 1, 65, 16, 64, + 2, 1, 1, 78, 51, 47, 34, 39, 47, 38, 21, 39, + 31, 20, 26, 16, 5, 1, 64, 19, 13, 17, 16, 13, + 10, 16, 9, 14, 12, 3, 4, 9, 89, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 61, 58, 58, 49, 43, + 26, 54, 47, 28, 61, 50, 52, 47, 42, 39, 42, + 30, 22, 8, 13, 70, 74, 98, 69, 24, 16, 11, 2, + 5, 0, 71, 73, 86, 64, 39, 26, 20, 17, 7, 64, + 73, 74, 81, 86, 16, 8, 4, 0, 67, 72, 76, 83, + 101, 91, 74, 73, 70, 72, 79, 89, 91, 107, 74, + 33, 17, 11, 3, 64, 74, 80, 83, 90, 62, 90, 81, + 75, 66, 70, 1, 4, 5, 72, 3, 7, 2, 78, 71, 69, + 14, 5, 20, 16, 28, 28, 19, 17, 22, 19, 15, 67, + 9, 68, 98, 91, 88, 84, 81, 81, 80, 81, 68, 75, + 74, 73, 0, 75, 82, 95, 88, 96, 76, 71, 70, 69, + 68, 65, 66, 0, 64, 67, 68, 3, 66, 76, 50, 41, + 44, 43, 41, 46, 42, 40, 34, 34, 36, 30, 12, + 15, 8, 34, 32, 29, 21, 23, 19, 6, 1, 66, 76, + 80, 92, 73, 103, 62, 62, 62, 62, 62, 62, 61, + 58, 54, 51, 47, 42, 34, 17, 64, 52, 50, 22, + 55, 61, 49, 43, 46, 41, 36, 41, 34, 33, 19, + 20, 10, 75, 27, 17, 1, 111, 102, 105, 79, 84, + 82, 5, 73, 73, 68, 0, 19, 7, 25, 68, 62, 62, + 58, 53, 38, 35, 7, 70, 91, 73, 41, 29, 20, 9, + 9, 2, 64, 68, 87, 99, 90, 82, 84, 79, 68, 74, + 79, 68, 65, 64, 0, 4, 8, 3, 62, 62, 57, 50, + 46, 40, 26, 13, 74 }, + + { + + 62, + 8, 75, 62, 8, 75, 111, 91, 14, 10, 9, 18, 49, + 52, 54, 14, 97, 4, 70, 5, 1, 8, 70, 4, 74, 15, + 47, 81, 99, 109, 101, 92, 67, 70, 5, 1, 82, + 85, 6, 13, 67, 77, 89, 5, 70, 83, 89, 5, 74, + 88, 7, 71, 79, 88, 0, 71, 71, 77, 68, 5, 22, + 0, 0, 0, 77, 88, 97, 68, 15, 0, 52, 8, 69, + 117, 94, 82, 70, 95, 111, 109, 113, 84, 111, + 102, 110, 113, 108, 113, 108, 7, 66, 75, 96, + 68, 88, 84, 110, 73, 93, 87, 115, 10, 79, 72, + 112, 89, 80, 75, 68, 3, 65, 77, 5, 2, 65, 5, + 64, 64, 2, 76, 13, 8, 23, 19, 19, 18, 17, 17, + 29, 12, 10, 29, 21, 69, 100, 91, 93, 83, 87, + 82, 75, 80, 79, 79, 83, 70, 72, 71, 69, 97, + 91, 95, 72, 73, 68, 69, 9, 2, 1, 65, 15, 0, 1, + 0, 1, 78, 50, 46, 34, 38, 46, 37, 21, 39, 31, + 19, 25, 16, 5, 1, 64, 19, 12, 16, 15, 12, 9, + 16, 9, 13, 11, 3, 3, 8, 89, 62, 62, 62, 62, + 62, 62, 62, 62, 62, 59, 56, 56, 46, 41, 23, + 53, 45, 27, 59, 48, 50, 45, 40, 37, 40, 28, + 20, 8, 11, 71, 74, 98, 69, 25, 16, 11, 3, 5, + 0, 70, 73, 85, 64, 39, 26, 21, 17, 8, 0, 72, + 73, 80, 85, 17, 9, 5, 1, 66, 71, 76, 82, 100, + 90, 72, 72, 69, 71, 78, 88, 90, 106, 73, 33, + 18, 12, 3, 0, 73, 79, 82, 89, 62, 89, 80, 74, + 66, 70, 1, 5, 6, 72, 3, 8, 3, 78, 71, 69, 14, + 5, 19, 16, 27, 28, 19, 16, 21, 19, 15, 67, 8, + 69, 97, 90, 87, 84, 80, 81, 79, 80, 68, 75, + 74, 72, 0, 75, 82, 95, 87, 96, 76, 71, 70, 68, + 68, 65, 65, 0, 64, 67, 67, 2, 66, 76, 49, 41, + 44, 43, 40, 45, 41, 39, 34, 33, 35, 30, 12, + 14, 7, 33, 31, 27, 19, 22, 18, 6, 1, 66, 75, + 79, 92, 74, 102, 62, 62, 62, 62, 62, 62, 59, + 56, 52, 49, 45, 40, 32, 16, 65, 50, 49, 21, + 53, 59, 47, 41, 44, 39, 34, 39, 32, 31, 18, + 18, 8, 76, 25, 15, 64, 110, 100, 103, 78, 83, + 81, 7, 72, 72, 68, 1, 21, 8, 27, 67, 62, 62, + 56, 50, 36, 32, 5, 72, 92, 73, 41, 29, 20, 9, + 10, 2, 64, 68, 86, 98, 89, 81, 83, 77, 67, 73, + 78, 67, 64, 0, 0, 5, 8, 3, 62, 61, 56, 49, 44, + 38, 24, 11, 74 }, + + { + + 62, + 8, 75, 62, 8, 75, 109, 89, 14, 10, 8, 16, 47, + 50, 54, 14, 94, 3, 69, 5, 1, 7, 70, 3, 75, 15, + 45, 83, 100, 109, 98, 91, 67, 69, 5, 1, 82, + 84, 6, 13, 67, 77, 89, 5, 71, 82, 89, 5, 74, + 87, 6, 71, 79, 88, 1, 70, 71, 76, 67, 5, 22, + 0, 0, 0, 77, 88, 97, 67, 14, 0, 52, 8, 69, + 116, 93, 82, 69, 93, 110, 107, 112, 83, 110, + 100, 108, 112, 107, 112, 108, 8, 66, 74, 95, + 68, 88, 83, 108, 73, 93, 86, 114, 10, 78, 71, + 111, 88, 80, 75, 67, 3, 65, 76, 6, 3, 66, 4, + 64, 64, 2, 76, 13, 7, 22, 19, 19, 18, 17, 16, + 27, 12, 9, 27, 20, 70, 99, 90, 92, 82, 86, 81, + 74, 79, 78, 78, 82, 70, 72, 70, 70, 97, 90, + 95, 72, 73, 68, 69, 8, 3, 1, 65, 13, 1, 1, 64, + 1, 79, 48, 45, 33, 37, 45, 36, 20, 38, 31, 18, + 24, 16, 5, 1, 65, 18, 12, 15, 15, 11, 8, 15, + 8, 11, 10, 2, 2, 7, 89, 62, 62, 62, 62, 62, + 62, 62, 62, 62, 57, 54, 53, 44, 39, 21, 51, + 44, 25, 56, 46, 48, 43, 37, 35, 37, 26, 17, 7, + 10, 73, 75, 98, 69, 25, 17, 11, 3, 5, 0, 70, + 73, 85, 0, 40, 26, 21, 17, 9, 0, 71, 73, 79, + 84, 18, 10, 5, 1, 65, 71, 75, 82, 99, 90, 71, + 72, 69, 71, 77, 87, 90, 105, 73, 34, 18, 12, + 3, 1, 72, 78, 81, 88, 62, 89, 80, 74, 66, 70, + 1, 5, 6, 71, 3, 8, 3, 78, 71, 69, 13, 6, 18, + 15, 26, 27, 19, 15, 20, 18, 15, 68, 7, 69, 96, + 90, 86, 83, 80, 80, 79, 79, 68, 75, 74, 72, + 64, 75, 82, 94, 86, 96, 76, 70, 70, 68, 68, + 65, 64, 0, 64, 67, 66, 2, 67, 77, 48, 41, 43, + 42, 39, 44, 40, 38, 33, 32, 34, 29, 11, 13, 6, + 31, 29, 26, 17, 21, 17, 5, 0, 67, 75, 79, 92, + 74, 101, 62, 62, 62, 62, 62, 60, 57, 53, 50, + 47, 43, 38, 30, 14, 66, 48, 47, 20, 51, 57, + 45, 39, 42, 37, 32, 37, 30, 28, 16, 16, 6, 77, + 23, 13, 65, 109, 99, 102, 78, 82, 80, 9, 71, + 71, 68, 2, 22, 10, 28, 67, 62, 60, 53, 47, 33, + 29, 2, 74, 93, 73, 41, 29, 20, 9, 10, 2, 64, + 68, 86, 97, 88, 80, 82, 76, 66, 73, 77, 66, 0, + 1, 1, 5, 9, 3, 60, 59, 54, 47, 42, 36, 22, 9, + 75 }, + + { + + 62, + 8, 76, 62, 8, 76, 107, 88, 15, 10, 8, 15, 46, + 49, 54, 14, 92, 3, 69, 6, 1, 6, 70, 2, 76, 14, + 44, 84, 101, 110, 95, 90, 67, 69, 6, 1, 81, + 83, 7, 12, 66, 76, 88, 4, 71, 82, 89, 5, 73, + 87, 6, 71, 78, 88, 1, 70, 71, 76, 67, 5, 22, + 0, 0, 0, 76, 88, 97, 67, 14, 64, 52, 8, 69, + 115, 93, 82, 69, 92, 108, 105, 110, 82, 108, + 99, 106, 111, 106, 111, 107, 9, 66, 73, 95, + 67, 87, 83, 106, 73, 92, 86, 113, 10, 78, 71, + 109, 88, 79, 74, 67, 3, 65, 75, 6, 3, 66, 4, + 64, 64, 2, 76, 13, 7, 22, 19, 18, 18, 17, 16, + 25, 12, 9, 25, 19, 71, 99, 89, 92, 82, 85, 81, + 73, 79, 78, 78, 80, 70, 72, 70, 71, 96, 89, + 95, 72, 73, 67, 68, 7, 3, 1, 65, 12, 2, 0, 65, + 1, 80, 47, 44, 33, 37, 44, 36, 20, 37, 30, 17, + 23, 16, 5, 1, 65, 17, 11, 14, 14, 11, 7, 14, + 8, 9, 9, 2, 1, 6, 89, 62, 62, 62, 62, 62, 62, + 62, 62, 62, 54, 52, 51, 41, 36, 18, 49, 42, + 24, 54, 43, 45, 40, 35, 32, 35, 23, 15, 6, 8, + 74, 76, 98, 68, 26, 17, 12, 3, 5, 0, 70, 73, + 84, 0, 40, 27, 21, 17, 10, 1, 71, 72, 79, 83, + 19, 10, 6, 1, 64, 70, 75, 81, 98, 89, 70, 71, + 68, 71, 77, 87, 89, 103, 72, 34, 18, 12, 4, 1, + 72, 77, 81, 87, 62, 88, 79, 73, 66, 70, 1, 5, + 6, 71, 4, 8, 4, 78, 71, 70, 13, 6, 17, 14, 25, + 26, 19, 14, 19, 18, 15, 68, 6, 70, 95, 89, 85, + 83, 79, 80, 78, 79, 68, 74, 73, 71, 64, 75, + 82, 94, 85, 96, 76, 70, 70, 67, 68, 65, 0, 64, + 64, 67, 66, 1, 67, 78, 47, 40, 43, 41, 38, 44, + 40, 37, 32, 32, 33, 28, 10, 12, 5, 30, 28, 24, + 15, 19, 15, 4, 0, 67, 75, 79, 91, 75, 100, 62, + 62, 62, 62, 62, 58, 55, 51, 48, 44, 41, 35, + 28, 13, 66, 47, 46, 18, 49, 54, 43, 37, 40, + 35, 30, 34, 28, 26, 14, 14, 5, 79, 21, 11, 67, + 108, 98, 101, 77, 81, 79, 10, 70, 70, 68, 3, + 24, 11, 30, 66, 61, 59, 51, 44, 30, 27, 64, + 76, 95, 72, 41, 29, 20, 9, 10, 2, 64, 68, 85, + 96, 88, 79, 81, 75, 66, 72, 76, 66, 0, 1, 1, + 6, 9, 3, 59, 58, 52, 45, 41, 35, 19, 8, 76 }, + + { + + 62, + 8, 76, 62, 8, 76, 106, 86, 15, 10, 7, 13, 44, + 48, 54, 14, 90, 3, 68, 7, 1, 5, 70, 1, 77, 14, + 42, 86, 102, 110, 92, 89, 67, 68, 7, 1, 81, + 82, 7, 12, 66, 76, 87, 4, 72, 82, 89, 5, 73, + 86, 6, 72, 78, 88, 2, 70, 71, 76, 66, 5, 22, + 0, 0, 0, 76, 89, 97, 66, 13, 64, 52, 8, 69, + 114, 92, 82, 68, 91, 106, 103, 109, 81, 106, + 98, 104, 110, 106, 110, 106, 9, 66, 72, 94, + 67, 87, 82, 104, 73, 92, 85, 112, 10, 78, 70, + 108, 87, 78, 74, 67, 3, 65, 75, 7, 4, 67, 4, + 64, 64, 2, 76, 13, 6, 21, 19, 17, 18, 17, 16, + 23, 12, 8, 23, 18, 72, 98, 88, 91, 81, 85, 80, + 72, 78, 77, 77, 79, 70, 72, 70, 72, 96, 88, + 95, 72, 73, 66, 68, 6, 4, 1, 65, 11, 3, 0, 66, + 1, 81, 45, 43, 32, 36, 43, 35, 19, 36, 30, 15, + 22, 15, 5, 1, 66, 16, 10, 13, 13, 10, 6, 13, + 7, 7, 8, 1, 0, 5, 89, 62, 62, 61, 62, 62, 62, + 62, 62, 61, 52, 50, 48, 39, 34, 16, 47, 40, + 22, 52, 41, 43, 38, 33, 30, 32, 21, 12, 5, 6, + 75, 77, 98, 68, 26, 17, 12, 3, 5, 0, 70, 73, + 84, 0, 40, 27, 21, 17, 11, 1, 70, 72, 78, 83, + 20, 11, 6, 1, 64, 70, 74, 81, 97, 88, 69, 70, + 68, 71, 76, 86, 88, 102, 72, 34, 18, 12, 4, 2, + 71, 77, 80, 86, 62, 88, 79, 73, 66, 70, 1, 5, + 6, 71, 4, 8, 4, 78, 71, 70, 12, 7, 16, 13, 24, + 25, 19, 12, 18, 18, 15, 69, 5, 70, 95, 88, 84, + 82, 79, 79, 78, 78, 69, 74, 73, 71, 65, 75, + 82, 93, 84, 96, 76, 70, 70, 66, 68, 65, 1, 64, + 65, 67, 65, 0, 67, 79, 46, 40, 42, 40, 37, 43, + 39, 36, 31, 31, 32, 27, 9, 11, 4, 28, 27, 23, + 13, 18, 14, 3, 64, 68, 75, 79, 91, 75, 100, + 62, 62, 62, 62, 62, 56, 53, 48, 46, 42, 39, + 33, 26, 11, 67, 45, 44, 17, 47, 52, 41, 35, + 37, 33, 28, 32, 26, 23, 12, 12, 3, 80, 19, 9, + 68, 107, 97, 100, 76, 80, 78, 12, 69, 69, 68, + 4, 25, 13, 31, 65, 59, 57, 48, 41, 27, 24, 67, + 78, 96, 72, 41, 29, 20, 9, 10, 2, 64, 68, 85, + 95, 87, 78, 81, 74, 65, 72, 75, 65, 1, 2, 1, + 6, 9, 3, 58, 56, 50, 43, 39, 33, 17, 6, 77 }, + + { + + 62, + 8, 76, 62, 8, 76, 104, 85, 15, 10, 7, 12, 43, + 46, 54, 14, 87, 2, 67, 7, 1, 5, 69, 0, 78, 13, + 40, 88, 103, 111, 89, 87, 67, 67, 7, 1, 81, + 81, 7, 11, 66, 76, 87, 4, 72, 81, 89, 5, 73, + 85, 5, 72, 78, 88, 2, 69, 70, 75, 66, 5, 22, + 0, 0, 0, 75, 89, 97, 66, 12, 64, 52, 8, 69, + 113, 91, 82, 68, 89, 105, 101, 107, 80, 105, + 96, 102, 109, 105, 109, 106, 10, 66, 71, 93, + 67, 86, 81, 102, 73, 92, 85, 110, 11, 77, 70, + 107, 87, 78, 74, 66, 4, 64, 74, 7, 4, 67, 3, + 64, 64, 2, 76, 13, 6, 20, 19, 17, 18, 17, 15, + 21, 12, 8, 22, 17, 73, 97, 87, 90, 80, 84, 79, + 71, 77, 77, 76, 78, 70, 72, 69, 73, 95, 87, + 95, 71, 73, 66, 68, 5, 5, 1, 65, 9, 4, 64, 67, + 1, 81, 44, 42, 32, 35, 42, 34, 18, 36, 30, 14, + 21, 15, 5, 1, 66, 16, 10, 12, 13, 9, 5, 13, 6, + 5, 7, 0, 64, 4, 89, 61, 62, 59, 62, 61, 60, + 60, 60, 59, 50, 48, 46, 36, 32, 13, 45, 39, + 20, 49, 39, 41, 36, 30, 28, 29, 19, 10, 4, 5, + 77, 77, 98, 68, 26, 18, 12, 3, 5, 0, 70, 73, + 83, 1, 41, 27, 21, 17, 12, 2, 69, 72, 77, 82, + 21, 12, 6, 2, 0, 69, 74, 80, 96, 88, 67, 70, + 68, 70, 75, 85, 88, 101, 71, 35, 19, 12, 4, 3, + 70, 76, 79, 85, 62, 88, 79, 72, 66, 70, 1, 5, + 6, 70, 4, 9, 5, 78, 71, 70, 11, 7, 15, 12, 23, + 25, 19, 11, 17, 17, 15, 70, 4, 71, 94, 88, 83, + 82, 78, 79, 77, 77, 69, 74, 73, 71, 66, 75, + 82, 93, 83, 96, 76, 69, 70, 66, 68, 65, 2, 64, + 65, 67, 64, 0, 68, 79, 45, 40, 42, 40, 36, 42, + 38, 35, 31, 30, 31, 26, 8, 10, 3, 27, 25, 21, + 11, 17, 13, 2, 65, 68, 75, 78, 91, 76, 99, 62, + 62, 62, 62, 60, 54, 51, 46, 44, 40, 37, 31, + 24, 10, 68, 43, 42, 16, 45, 50, 39, 33, 35, + 31, 26, 30, 24, 21, 10, 10, 1, 81, 17, 7, 70, + 106, 95, 99, 76, 79, 77, 14, 68, 68, 68, 5, + 27, 14, 33, 65, 58, 55, 46, 38, 25, 21, 69, + 80, 97, 72, 41, 29, 20, 9, 10, 2, 64, 68, 84, + 94, 86, 77, 80, 73, 64, 71, 74, 64, 2, 3, 2, + 7, 10, 3, 56, 55, 49, 42, 37, 31, 15, 4, 78 }, + + { + + 61, + 8, 76, 61, 8, 76, 102, 83, 16, 10, 6, 10, 41, + 45, 54, 14, 85, 2, 66, 8, 1, 4, 69, 64, 79, + 13, 38, 89, 104, 111, 86, 86, 67, 66, 8, 1, + 80, 80, 8, 11, 66, 75, 86, 3, 73, 81, 89, 5, + 73, 85, 5, 72, 78, 88, 3, 69, 70, 75, 65, 5, + 22, 0, 0, 0, 75, 89, 97, 65, 11, 64, 52, 8, + 69, 112, 90, 82, 67, 88, 103, 99, 106, 79, + 103, 95, 100, 108, 104, 108, 105, 11, 66, 70, + 92, 67, 86, 81, 100, 73, 91, 84, 109, 11, 77, + 69, 105, 86, 77, 74, 66, 4, 64, 73, 8, 5, 68, + 3, 64, 64, 2, 76, 13, 5, 20, 19, 16, 18, 17, + 15, 19, 12, 7, 20, 16, 74, 96, 86, 89, 79, 83, + 79, 70, 77, 76, 75, 77, 70, 72, 69, 74, 95, + 86, 95, 71, 73, 65, 68, 4, 6, 1, 65, 8, 5, 64, + 68, 1, 82, 42, 41, 31, 34, 41, 33, 18, 35, 29, + 13, 20, 15, 5, 1, 67, 15, 9, 11, 12, 8, 4, 12, + 6, 3, 6, 0, 65, 3, 89, 60, 61, 58, 62, 59, 58, + 58, 58, 56, 47, 46, 43, 34, 29, 11, 43, 37, + 19, 47, 37, 38, 33, 28, 25, 27, 17, 7, 3, 3, + 78, 78, 98, 68, 27, 18, 12, 3, 5, 0, 70, 73, + 83, 1, 41, 27, 21, 17, 13, 2, 69, 71, 77, 81, + 22, 12, 7, 2, 1, 69, 73, 80, 95, 87, 66, 69, + 67, 70, 74, 84, 87, 100, 71, 35, 19, 12, 4, 4, + 70, 75, 78, 84, 62, 87, 78, 72, 66, 70, 1, 5, + 6, 70, 4, 9, 5, 78, 71, 70, 11, 8, 14, 11, 22, + 24, 19, 10, 16, 17, 15, 70, 3, 71, 93, 87, 82, + 81, 78, 78, 77, 76, 69, 74, 73, 70, 66, 75, + 82, 92, 82, 96, 76, 69, 70, 65, 68, 65, 3, 64, + 65, 67, 0, 64, 68, 80, 44, 39, 41, 39, 35, 41, + 37, 34, 30, 29, 30, 25, 7, 9, 2, 25, 24, 20, + 9, 15, 12, 1, 65, 69, 75, 78, 91, 76, 98, 62, + 62, 61, 61, 57, 52, 49, 43, 42, 38, 35, 28, + 22, 8, 69, 41, 41, 14, 43, 48, 37, 31, 33, 29, + 24, 28, 22, 18, 8, 8, 64, 82, 15, 5, 71, 105, + 94, 98, 75, 78, 76, 15, 67, 67, 68, 6, 28, 16, + 34, 64, 56, 54, 43, 35, 22, 18, 72, 82, 98, + 72, 41, 29, 20, 9, 10, 2, 64, 68, 84, 93, 85, + 76, 79, 72, 64, 71, 73, 64, 2, 3, 2, 7, 10, 3, + 55, 53, 47, 40, 35, 29, 13, 2, 79 }, + + { + + 60, + 8, 76, 60, 8, 76, 100, 82, 16, 10, 6, 9, 40, + 44, 54, 14, 83, 2, 65, 9, 1, 3, 69, 65, 80, + 12, 36, 91, 105, 112, 83, 85, 67, 65, 9, 1, + 80, 79, 8, 10, 66, 75, 85, 3, 73, 81, 89, 5, + 73, 84, 5, 72, 78, 88, 3, 69, 70, 75, 65, 5, + 22, 0, 0, 0, 74, 89, 97, 65, 10, 64, 52, 8, + 69, 111, 89, 82, 67, 87, 101, 97, 104, 78, + 101, 94, 98, 107, 103, 107, 104, 12, 66, 69, + 91, 67, 85, 80, 98, 73, 91, 84, 108, 11, 77, + 69, 104, 86, 76, 74, 66, 4, 64, 72, 8, 5, 68, + 3, 64, 64, 2, 76, 13, 5, 19, 19, 15, 18, 17, + 15, 17, 12, 7, 18, 15, 75, 95, 85, 88, 78, 82, + 78, 69, 76, 76, 74, 76, 70, 72, 69, 75, 94, + 85, 95, 71, 73, 64, 68, 3, 7, 1, 65, 7, 6, 65, + 69, 1, 83, 41, 40, 31, 33, 40, 32, 17, 34, 29, + 12, 19, 15, 5, 1, 67, 14, 8, 10, 11, 7, 3, 11, + 5, 1, 5, 64, 66, 2, 89, 58, 60, 56, 60, 57, + 56, 56, 56, 54, 45, 44, 41, 31, 27, 8, 41, 35, + 17, 45, 35, 36, 31, 26, 23, 24, 15, 5, 2, 1, + 79, 79, 98, 68, 27, 18, 12, 3, 5, 0, 70, 73, + 82, 1, 41, 27, 21, 17, 14, 3, 68, 71, 76, 80, + 23, 13, 7, 2, 2, 68, 73, 79, 94, 86, 65, 68, + 67, 70, 73, 83, 86, 99, 70, 35, 19, 12, 4, 5, + 69, 74, 77, 83, 62, 87, 78, 71, 66, 70, 1, 5, + 6, 70, 4, 9, 6, 78, 71, 70, 10, 8, 13, 10, 21, + 23, 19, 9, 15, 17, 15, 71, 2, 72, 92, 86, 81, + 81, 77, 78, 76, 75, 69, 74, 73, 70, 67, 75, + 82, 92, 81, 96, 76, 69, 70, 64, 68, 65, 4, 64, + 65, 67, 1, 65, 68, 81, 43, 39, 41, 38, 34, 40, + 36, 33, 29, 28, 29, 24, 6, 8, 1, 24, 23, 18, + 7, 14, 11, 0, 66, 69, 75, 78, 91, 77, 97, 62, + 62, 59, 59, 54, 50, 47, 41, 40, 36, 33, 26, + 20, 7, 70, 39, 39, 13, 41, 46, 35, 29, 31, 27, + 22, 26, 20, 16, 6, 6, 66, 83, 13, 3, 73, 104, + 93, 97, 74, 77, 75, 17, 66, 66, 68, 7, 30, 17, + 36, 0, 55, 52, 41, 32, 19, 15, 75, 84, 99, 72, + 41, 29, 20, 9, 10, 2, 64, 68, 83, 92, 84, 75, + 78, 71, 0, 70, 72, 0, 3, 4, 2, 8, 10, 3, 54, + 52, 45, 38, 33, 27, 11, 0, 80 }, + + { + + 58, + 7, 77, 58, 7, 77, 99, 81, 16, 10, 5, 7, 38, + 42, 53, 14, 81, 1, 65, 9, 0, 2, 69, 67, 82, + 11, 34, 93, 106, 113, 81, 84, 68, 65, 9, 0, + 80, 78, 8, 9, 66, 75, 85, 2, 74, 81, 90, 5, + 73, 84, 4, 73, 78, 88, 3, 69, 70, 75, 65, 4, + 22, 0, 0, 0, 74, 90, 97, 65, 9, 65, 52, 7, 69, + 110, 89, 82, 67, 86, 100, 96, 103, 77, 100, + 93, 97, 106, 103, 106, 104, 12, 66, 69, 91, + 67, 85, 80, 97, 73, 91, 84, 107, 11, 77, 69, + 103, 86, 76, 74, 66, 4, 64, 72, 8, 5, 69, 2, + 64, 65, 2, 76, 12, 4, 18, 19, 14, 17, 17, 14, + 15, 11, 6, 16, 14, 76, 95, 85, 88, 78, 82, 78, + 68, 76, 76, 74, 75, 71, 72, 69, 77, 94, 85, + 95, 71, 74, 64, 68, 2, 7, 1, 65, 5, 6, 66, 70, + 1, 84, 39, 39, 30, 32, 39, 31, 16, 33, 28, 10, + 18, 14, 4, 1, 68, 13, 7, 9, 10, 6, 2, 10, 4, + 64, 3, 65, 68, 0, 89, 56, 58, 54, 58, 55, 53, + 53, 53, 51, 42, 41, 38, 28, 24, 5, 39, 33, 15, + 42, 32, 33, 28, 23, 20, 21, 12, 2, 1, 64, 81, + 80, 99, 68, 27, 18, 12, 3, 5, 64, 70, 73, 82, + 1, 41, 27, 21, 17, 15, 3, 68, 71, 76, 80, 23, + 13, 7, 2, 2, 68, 73, 79, 93, 86, 64, 68, 67, + 70, 73, 83, 86, 98, 70, 35, 19, 12, 4, 5, 69, + 74, 77, 83, 62, 87, 78, 71, 66, 70, 1, 5, 6, + 70, 4, 9, 6, 78, 71, 71, 9, 8, 12, 9, 20, 22, + 18, 7, 13, 16, 14, 72, 0, 73, 92, 86, 80, 81, + 77, 78, 76, 75, 70, 74, 73, 70, 68, 75, 82, + 92, 81, 97, 76, 69, 70, 64, 69, 65, 4, 65, 66, + 67, 1, 66, 69, 82, 42, 38, 40, 37, 32, 39, 35, + 32, 28, 27, 28, 23, 5, 6, 64, 22, 21, 16, 5, + 12, 9, 64, 67, 70, 75, 78, 91, 78, 97, 62, 61, + 57, 56, 51, 47, 44, 38, 37, 33, 30, 23, 17, 5, + 71, 37, 37, 11, 39, 43, 32, 26, 28, 24, 20, + 23, 17, 13, 4, 3, 68, 85, 11, 1, 75, 103, 92, + 96, 74, 77, 75, 18, 66, 66, 68, 7, 31, 18, 37, + 0, 53, 50, 38, 28, 16, 12, 78, 87, 101, 72, + 41, 28, 19, 9, 10, 2, 65, 68, 83, 92, 84, 75, + 78, 70, 0, 70, 72, 0, 3, 4, 2, 8, 10, 2, 52, + 50, 43, 36, 31, 25, 8, 65, 81 }, + + { + + 57, + 7, 77, 57, 7, 77, 97, 79, 17, 11, 5, 6, 37, + 41, 53, 14, 78, 1, 64, 10, 0, 2, 68, 68, 83, + 11, 33, 94, 107, 113, 78, 82, 68, 64, 10, 0, + 79, 76, 9, 9, 65, 74, 84, 2, 74, 80, 90, 5, + 72, 83, 4, 73, 77, 88, 4, 68, 69, 74, 64, 4, + 22, 0, 0, 0, 73, 90, 97, 64, 9, 65, 52, 7, 69, + 108, 88, 82, 66, 84, 98, 94, 101, 75, 98, 91, + 95, 104, 102, 105, 103, 13, 65, 68, 90, 66, + 84, 79, 95, 72, 90, 83, 105, 12, 76, 68, 101, + 85, 75, 73, 65, 5, 0, 71, 9, 6, 69, 2, 0, 65, + 2, 75, 12, 4, 18, 19, 14, 17, 17, 14, 14, 11, + 6, 15, 13, 76, 94, 84, 87, 77, 81, 77, 67, 75, + 75, 73, 73, 71, 72, 68, 78, 93, 84, 95, 70, + 74, 0, 67, 2, 8, 1, 65, 4, 7, 66, 71, 1, 84, + 38, 39, 30, 32, 39, 31, 16, 33, 28, 9, 18, 14, + 4, 1, 68, 13, 7, 9, 10, 6, 1, 10, 4, 65, 2, + 65, 69, 64, 89, 55, 57, 53, 57, 54, 51, 51, + 51, 49, 40, 39, 36, 26, 22, 3, 38, 32, 14, 40, + 30, 31, 26, 21, 18, 19, 10, 0, 1, 65, 82, 80, + 99, 67, 28, 19, 13, 4, 5, 64, 69, 72, 81, 2, + 42, 28, 22, 17, 16, 4, 67, 70, 75, 79, 24, 14, + 8, 3, 3, 67, 72, 78, 91, 85, 1, 67, 66, 69, + 72, 82, 85, 96, 69, 36, 20, 13, 5, 6, 68, 73, + 76, 82, 62, 86, 77, 70, 66, 69, 1, 6, 7, 69, + 5, 10, 7, 77, 71, 71, 9, 9, 12, 9, 19, 22, 18, + 6, 12, 16, 14, 72, 64, 73, 91, 85, 79, 80, 76, + 77, 75, 74, 70, 73, 72, 69, 68, 74, 81, 91, + 80, 97, 76, 68, 70, 0, 69, 65, 5, 65, 66, 66, + 2, 66, 69, 82, 42, 38, 40, 37, 31, 39, 35, 32, + 28, 27, 28, 23, 5, 5, 65, 21, 20, 15, 4, 11, + 8, 64, 67, 70, 74, 77, 90, 78, 96, 60, 59, 55, + 54, 49, 45, 42, 36, 35, 31, 28, 21, 15, 4, 71, + 36, 36, 10, 38, 41, 30, 24, 26, 22, 18, 21, + 15, 11, 3, 1, 69, 86, 10, 0, 76, 101, 90, 94, + 73, 76, 74, 20, 65, 65, 68, 8, 33, 20, 39, 1, + 52, 49, 36, 25, 14, 10, 80, 89, 102, 71, 42, + 28, 19, 9, 11, 2, 65, 68, 82, 91, 83, 74, 77, + 68, 1, 69, 71, 1, 4, 5, 3, 9, 11, 2, 51, 49, + 42, 35, 30, 24, 6, 66, 81 }, + + { + + 56, + 7, 77, 56, 7, 77, 95, 78, 17, 11, 5, 5, 36, + 40, 53, 14, 76, 1, 0, 11, 0, 1, 68, 69, 84, + 10, 31, 96, 108, 114, 75, 81, 68, 0, 11, 0, + 79, 75, 9, 8, 65, 74, 83, 2, 74, 80, 90, 5, + 72, 82, 4, 73, 77, 88, 4, 68, 69, 74, 64, 4, + 22, 0, 0, 0, 72, 90, 97, 64, 8, 65, 52, 7, 69, + 107, 87, 82, 66, 83, 96, 92, 100, 74, 96, 90, + 93, 103, 101, 104, 102, 14, 65, 67, 89, 66, + 84, 78, 93, 72, 90, 83, 104, 12, 76, 68, 100, + 85, 74, 73, 65, 5, 0, 70, 9, 6, 70, 2, 0, 65, + 2, 75, 12, 4, 17, 19, 13, 17, 17, 14, 12, 11, + 6, 13, 12, 77, 93, 83, 86, 76, 80, 76, 66, 74, + 75, 72, 72, 71, 72, 68, 79, 93, 83, 95, 70, + 74, 1, 67, 1, 9, 1, 65, 3, 8, 67, 72, 1, 85, + 36, 38, 29, 31, 38, 30, 15, 32, 28, 8, 17, 14, + 4, 1, 68, 12, 6, 8, 9, 5, 0, 9, 3, 67, 1, 66, + 70, 65, 89, 53, 56, 51, 55, 52, 49, 49, 49, + 46, 38, 37, 33, 23, 20, 0, 36, 30, 12, 38, 28, + 29, 24, 19, 16, 16, 8, 65, 0, 67, 83, 81, 99, + 67, 28, 19, 13, 4, 5, 64, 69, 72, 80, 2, 42, + 28, 22, 17, 17, 4, 66, 70, 74, 78, 25, 15, 8, + 3, 4, 67, 72, 77, 90, 84, 2, 66, 66, 69, 71, + 81, 84, 95, 69, 36, 20, 13, 5, 7, 67, 72, 75, + 81, 62, 86, 77, 70, 66, 69, 1, 6, 7, 69, 5, + 10, 8, 77, 71, 71, 8, 9, 11, 8, 18, 21, 18, 5, + 11, 16, 14, 73, 65, 74, 90, 84, 78, 80, 76, + 77, 74, 73, 70, 73, 72, 69, 69, 74, 81, 91, + 79, 97, 76, 68, 70, 1, 69, 65, 6, 65, 66, 66, + 3, 67, 69, 83, 41, 38, 40, 36, 30, 38, 34, 31, + 27, 26, 27, 22, 4, 4, 66, 19, 19, 13, 2, 10, + 7, 65, 68, 70, 74, 77, 90, 79, 95, 58, 57, 53, + 52, 46, 43, 40, 33, 33, 29, 26, 19, 13, 3, 72, + 34, 34, 9, 36, 39, 28, 22, 24, 20, 16, 19, 13, + 9, 1, 64, 71, 87, 8, 65, 78, 100, 89, 93, 72, + 75, 73, 22, 64, 64, 68, 9, 34, 21, 40, 2, 51, + 47, 33, 22, 11, 7, 83, 91, 103, 71, 42, 28, + 19, 9, 11, 2, 65, 68, 81, 90, 82, 73, 76, 67, + 2, 68, 70, 2, 5, 6, 3, 10, 11, 2, 50, 47, 40, + 33, 28, 22, 4, 68, 82 }, + + { + + 55, + 7, 77, 55, 7, 77, 93, 76, 18, 11, 4, 3, 34, + 39, 53, 14, 74, 1, 1, 12, 0, 0, 68, 70, 85, + 10, 29, 97, 109, 114, 72, 80, 68, 1, 12, 0, + 78, 74, 10, 8, 65, 73, 82, 1, 75, 80, 90, 5, + 72, 82, 4, 73, 77, 88, 5, 68, 69, 74, 0, 4, + 22, 0, 0, 0, 72, 90, 97, 0, 7, 65, 52, 7, 69, + 106, 86, 82, 65, 82, 94, 90, 98, 73, 94, 89, + 91, 102, 100, 103, 101, 15, 65, 66, 88, 66, + 83, 78, 91, 72, 89, 82, 103, 12, 76, 67, 98, + 84, 73, 73, 65, 5, 0, 69, 10, 7, 70, 2, 0, 65, + 2, 75, 12, 3, 17, 19, 12, 17, 17, 14, 10, 11, + 5, 11, 11, 78, 92, 82, 85, 75, 79, 76, 65, 74, + 74, 71, 71, 71, 72, 68, 80, 92, 82, 95, 70, + 74, 2, 67, 0, 10, 1, 65, 2, 9, 67, 73, 1, 86, + 35, 37, 29, 30, 37, 29, 15, 31, 27, 7, 16, 14, + 4, 1, 69, 11, 5, 7, 8, 4, 64, 8, 3, 69, 0, 66, + 71, 66, 89, 52, 54, 50, 53, 50, 47, 47, 47, + 44, 35, 35, 31, 21, 17, 65, 34, 28, 11, 36, + 26, 26, 21, 17, 13, 14, 6, 68, 64, 69, 84, 82, + 99, 67, 29, 19, 13, 4, 5, 64, 69, 72, 80, 2, + 42, 28, 22, 17, 18, 5, 66, 69, 74, 77, 26, 15, + 9, 3, 5, 66, 71, 77, 89, 83, 3, 65, 65, 69, + 70, 80, 83, 94, 68, 36, 20, 13, 5, 8, 67, 71, + 74, 80, 62, 85, 76, 69, 66, 69, 1, 6, 7, 69, + 5, 10, 8, 77, 71, 71, 8, 10, 10, 7, 17, 20, + 18, 4, 10, 16, 14, 73, 66, 74, 89, 83, 77, 79, + 75, 76, 74, 72, 70, 73, 72, 68, 69, 74, 81, + 90, 78, 97, 76, 68, 70, 2, 69, 65, 7, 65, 66, + 66, 4, 68, 69, 84, 40, 37, 39, 35, 29, 37, 33, + 30, 26, 25, 26, 21, 3, 3, 67, 18, 18, 12, 0, + 8, 6, 66, 68, 71, 74, 77, 90, 79, 94, 56, 55, + 51, 50, 43, 41, 38, 31, 31, 27, 24, 16, 11, 1, + 73, 32, 33, 7, 34, 37, 26, 20, 22, 18, 14, 17, + 11, 6, 64, 66, 73, 88, 6, 67, 79, 99, 88, 92, + 71, 74, 72, 23, 0, 0, 68, 10, 36, 23, 42, 3, + 49, 46, 31, 19, 8, 4, 86, 93, 104, 71, 42, 28, + 19, 9, 11, 2, 65, 68, 81, 89, 81, 72, 75, 66, + 2, 68, 69, 2, 5, 6, 3, 10, 11, 2, 49, 46, 38, + 31, 26, 20, 2, 70, 83 }, + + { + + 53, + 7, 77, 53, 7, 77, 92, 75, 18, 11, 4, 2, 33, + 37, 53, 14, 71, 0, 2, 12, 0, 64, 68, 71, 86, + 9, 27, 99, 110, 115, 69, 79, 68, 2, 12, 0, 78, + 73, 10, 7, 65, 73, 82, 1, 75, 79, 90, 5, 72, + 81, 3, 74, 77, 88, 5, 67, 69, 73, 0, 4, 22, 0, + 0, 0, 71, 91, 97, 0, 6, 65, 52, 7, 69, 105, + 85, 82, 65, 80, 93, 88, 97, 72, 93, 87, 89, + 101, 100, 102, 101, 15, 65, 65, 87, 66, 83, + 77, 89, 72, 89, 82, 102, 12, 75, 67, 97, 84, + 73, 73, 64, 5, 0, 69, 10, 7, 71, 1, 0, 65, 2, + 75, 12, 3, 16, 19, 12, 17, 17, 13, 8, 11, 5, + 9, 10, 79, 91, 81, 84, 74, 79, 75, 64, 73, 74, + 70, 70, 71, 72, 67, 81, 92, 81, 95, 70, 74, 2, + 67, 64, 11, 1, 65, 0, 10, 68, 74, 1, 87, 33, + 36, 28, 29, 36, 28, 14, 30, 27, 5, 15, 13, 4, + 1, 69, 10, 5, 6, 8, 3, 65, 7, 2, 71, 64, 67, + 72, 67, 89, 50, 53, 48, 51, 48, 45, 44, 45, + 41, 33, 33, 28, 18, 15, 68, 32, 27, 9, 33, 24, + 24, 19, 14, 11, 11, 4, 70, 65, 70, 86, 83, 99, + 67, 29, 20, 13, 4, 5, 64, 69, 72, 79, 3, 43, + 28, 22, 17, 19, 5, 65, 69, 73, 77, 27, 16, 9, + 3, 5, 66, 71, 76, 88, 83, 4, 65, 65, 69, 69, + 79, 83, 93, 68, 37, 20, 13, 5, 9, 66, 71, 73, + 79, 62, 85, 76, 69, 66, 69, 1, 6, 7, 68, 5, + 10, 9, 77, 71, 71, 7, 10, 9, 6, 16, 19, 18, 2, + 9, 15, 14, 74, 67, 75, 89, 83, 76, 79, 75, 76, + 73, 71, 71, 73, 72, 68, 70, 74, 81, 90, 77, + 97, 76, 67, 70, 2, 69, 65, 8, 65, 67, 66, 5, + 68, 70, 85, 39, 37, 39, 34, 28, 36, 32, 29, + 25, 24, 25, 20, 2, 2, 68, 16, 16, 10, 65, 7, + 5, 67, 69, 71, 74, 77, 90, 80, 94, 53, 52, 49, + 47, 40, 39, 36, 28, 29, 25, 22, 14, 9, 0, 74, + 30, 31, 6, 32, 35, 24, 18, 19, 16, 12, 15, 9, + 4, 66, 68, 75, 89, 4, 69, 81, 98, 87, 91, 71, + 73, 71, 25, 1, 1, 68, 11, 37, 24, 43, 3, 48, + 44, 28, 16, 5, 1, 89, 95, 105, 71, 42, 28, 19, + 9, 11, 2, 65, 68, 80, 88, 80, 71, 75, 65, 3, + 67, 68, 3, 6, 7, 4, 11, 12, 2, 47, 44, 36, 29, + 24, 18, 0, 72, 84 }, + + { + + 52, + 7, 77, 52, 7, 77, 90, 73, 18, 11, 3, 0, 31, + 36, 53, 14, 69, 0, 3, 13, 0, 64, 67, 72, 87, + 9, 25, 101, 111, 115, 66, 77, 68, 3, 13, 0, + 78, 72, 10, 7, 65, 73, 81, 1, 76, 79, 90, 5, + 72, 80, 3, 74, 77, 88, 6, 67, 68, 73, 1, 4, + 22, 0, 0, 0, 71, 91, 97, 1, 5, 65, 52, 7, 69, + 104, 84, 82, 64, 79, 91, 86, 95, 71, 91, 86, + 87, 100, 99, 101, 100, 16, 65, 64, 86, 66, 82, + 76, 87, 72, 89, 81, 100, 13, 75, 66, 96, 83, + 72, 73, 64, 6, 1, 68, 11, 8, 71, 1, 0, 65, 2, + 75, 12, 2, 15, 19, 11, 17, 17, 13, 6, 11, 4, + 8, 9, 80, 90, 80, 83, 73, 78, 74, 0, 72, 73, + 69, 69, 71, 72, 67, 82, 91, 80, 95, 69, 74, 3, + 67, 65, 12, 1, 65, 64, 11, 68, 75, 1, 87, 32, + 35, 28, 28, 35, 27, 13, 30, 27, 4, 14, 13, 4, + 1, 70, 10, 4, 5, 7, 2, 66, 7, 1, 73, 65, 68, + 73, 68, 89, 48, 52, 46, 49, 47, 43, 42, 43, + 39, 31, 31, 26, 16, 13, 70, 30, 25, 7, 31, 22, + 22, 17, 12, 9, 8, 2, 73, 66, 72, 87, 83, 99, + 67, 29, 20, 13, 4, 5, 64, 69, 72, 79, 3, 43, + 28, 22, 17, 20, 6, 64, 69, 72, 76, 28, 17, 9, + 4, 6, 65, 70, 76, 87, 82, 6, 64, 65, 68, 68, + 78, 82, 92, 67, 37, 21, 13, 5, 10, 65, 70, 72, + 78, 62, 85, 76, 68, 66, 69, 1, 6, 7, 68, 5, + 11, 9, 77, 71, 71, 6, 11, 8, 5, 15, 19, 18, 1, + 8, 15, 14, 75, 68, 75, 88, 82, 75, 78, 74, 75, + 73, 70, 71, 73, 72, 68, 71, 74, 81, 89, 76, + 97, 76, 67, 70, 3, 69, 65, 9, 65, 67, 66, 6, + 69, 70, 85, 38, 37, 38, 34, 27, 35, 31, 28, + 25, 23, 24, 19, 1, 1, 69, 15, 15, 9, 67, 6, 4, + 68, 70, 72, 74, 76, 90, 80, 93, 51, 50, 47, + 45, 38, 37, 34, 26, 27, 23, 20, 12, 7, 65, 75, + 28, 29, 5, 30, 33, 22, 16, 17, 14, 10, 13, 7, + 1, 68, 70, 77, 90, 2, 71, 82, 97, 85, 90, 70, + 72, 70, 27, 2, 2, 68, 12, 39, 26, 45, 4, 46, + 42, 26, 13, 3, 65, 91, 97, 106, 71, 42, 28, + 19, 9, 11, 2, 65, 68, 80, 87, 79, 70, 74, 64, + 4, 67, 67, 4, 7, 8, 4, 11, 12, 2, 46, 43, 35, + 28, 22, 16, 65, 74, 85 }, + + { + + 51, + 7, 78, 51, 7, 78, 88, 72, 19, 11, 3, 64, 30, + 35, 53, 14, 67, 0, 3, 14, 0, 65, 67, 73, 88, + 8, 24, 102, 112, 116, 0, 76, 68, 3, 14, 0, 77, + 71, 11, 6, 64, 72, 80, 0, 76, 79, 90, 5, 71, + 80, 3, 74, 76, 88, 6, 67, 68, 73, 1, 4, 22, 0, + 0, 0, 70, 91, 97, 1, 5, 66, 52, 7, 69, 103, + 84, 82, 64, 78, 89, 84, 94, 70, 89, 85, 85, + 99, 98, 100, 99, 17, 65, 0, 86, 65, 82, 76, + 85, 72, 88, 81, 99, 13, 75, 66, 94, 83, 71, + 72, 64, 6, 1, 67, 11, 8, 72, 1, 0, 65, 2, 75, + 12, 2, 15, 19, 10, 17, 17, 13, 4, 11, 4, 6, 8, + 81, 90, 79, 83, 73, 77, 74, 1, 72, 73, 69, 67, + 71, 72, 67, 83, 91, 79, 95, 69, 74, 4, 66, 66, + 12, 1, 65, 65, 12, 69, 76, 1, 88, 30, 34, 27, + 28, 34, 27, 13, 29, 26, 3, 13, 13, 4, 1, 70, + 9, 3, 4, 6, 2, 67, 6, 1, 75, 66, 68, 74, 69, + 89, 47, 50, 45, 47, 45, 41, 40, 41, 36, 28, + 29, 23, 13, 10, 73, 28, 23, 6, 29, 19, 19, 14, + 10, 6, 6, 64, 75, 67, 74, 88, 84, 99, 66, 30, + 20, 14, 4, 5, 64, 69, 72, 78, 3, 43, 29, 22, + 17, 21, 6, 64, 68, 72, 75, 29, 17, 10, 4, 7, + 65, 70, 75, 86, 81, 7, 0, 64, 68, 68, 78, 81, + 90, 67, 37, 21, 13, 6, 10, 65, 69, 72, 77, 62, + 84, 75, 68, 66, 69, 1, 6, 7, 68, 6, 11, 10, + 77, 71, 72, 6, 11, 7, 4, 14, 18, 18, 0, 7, 15, + 14, 75, 69, 76, 87, 81, 74, 78, 74, 75, 72, + 70, 71, 72, 71, 67, 71, 74, 81, 89, 75, 97, + 76, 67, 70, 4, 69, 65, 10, 66, 67, 66, 6, 70, + 70, 86, 37, 36, 38, 33, 26, 35, 31, 27, 24, + 23, 23, 18, 0, 0, 70, 13, 14, 7, 69, 4, 2, 69, + 70, 72, 74, 76, 89, 81, 92, 49, 48, 45, 43, + 35, 35, 32, 23, 25, 20, 18, 9, 5, 66, 75, 27, + 28, 3, 28, 30, 20, 14, 15, 12, 8, 10, 5, 64, + 70, 72, 78, 92, 0, 73, 84, 96, 84, 89, 69, 71, + 69, 28, 3, 3, 68, 13, 40, 27, 46, 5, 45, 41, + 23, 10, 0, 67, 94, 99, 108, 70, 42, 28, 19, 9, + 11, 2, 65, 68, 79, 86, 79, 69, 73, 0, 4, 66, + 66, 4, 7, 8, 4, 12, 12, 2, 45, 41, 33, 26, 21, + 15, 68, 75, 86 }, + + { + + 50, + 7, 78, 50, 7, 78, 86, 70, 19, 11, 2, 66, 28, + 33, 53, 14, 64, 64, 4, 14, 0, 66, 67, 74, 89, + 8, 22, 104, 113, 116, 3, 75, 68, 4, 14, 0, 77, + 70, 11, 6, 64, 72, 80, 0, 77, 78, 90, 5, 71, + 79, 2, 74, 76, 88, 7, 66, 68, 72, 2, 4, 22, 0, + 0, 0, 70, 91, 97, 2, 4, 66, 52, 7, 69, 102, + 83, 82, 0, 76, 88, 82, 92, 69, 88, 83, 83, 98, + 97, 99, 99, 18, 65, 1, 85, 65, 81, 75, 83, 72, + 88, 80, 98, 13, 74, 65, 93, 82, 71, 72, 0, 6, + 1, 66, 12, 9, 72, 0, 0, 65, 2, 75, 12, 1, 14, + 19, 10, 17, 17, 12, 2, 11, 3, 4, 7, 82, 89, + 78, 82, 72, 76, 73, 2, 71, 72, 68, 66, 71, 72, + 66, 84, 90, 78, 95, 69, 74, 4, 66, 67, 13, 1, + 65, 67, 13, 69, 77, 1, 89, 29, 33, 27, 27, 33, + 26, 12, 28, 26, 2, 12, 13, 4, 1, 71, 8, 3, 3, + 6, 1, 68, 5, 0, 77, 67, 69, 75, 70, 89, 45, + 49, 43, 45, 43, 39, 37, 39, 34, 26, 27, 21, + 11, 8, 75, 26, 22, 4, 26, 17, 17, 12, 7, 4, 3, + 66, 78, 68, 75, 90, 85, 99, 66, 30, 21, 14, 4, + 5, 64, 69, 72, 78, 4, 44, 29, 22, 17, 22, 7, + 0, 68, 71, 74, 30, 18, 10, 4, 8, 64, 69, 75, + 85, 81, 8, 0, 64, 68, 67, 77, 81, 89, 66, 38, + 21, 13, 6, 11, 64, 68, 71, 76, 62, 84, 75, 67, + 66, 69, 1, 6, 7, 67, 6, 11, 10, 77, 71, 72, 5, + 12, 6, 3, 13, 17, 18, 64, 6, 14, 14, 76, 70, + 76, 86, 81, 73, 77, 73, 74, 72, 69, 71, 72, + 71, 67, 72, 74, 81, 88, 74, 97, 76, 66, 70, 4, + 69, 65, 11, 66, 67, 66, 7, 70, 71, 87, 36, 36, + 37, 32, 25, 34, 30, 26, 23, 22, 22, 17, 64, + 64, 71, 12, 12, 6, 71, 3, 1, 70, 71, 73, 74, + 76, 89, 81, 91, 47, 46, 43, 40, 32, 33, 30, + 21, 23, 18, 16, 7, 3, 68, 76, 25, 26, 2, 26, + 28, 18, 12, 13, 10, 6, 8, 3, 67, 72, 74, 80, + 93, 65, 75, 85, 95, 83, 88, 69, 70, 68, 30, 4, + 4, 68, 14, 42, 29, 48, 5, 43, 39, 21, 7, 66, + 70, 97, 101, 109, 70, 42, 28, 19, 9, 11, 2, + 65, 68, 79, 85, 78, 68, 72, 1, 5, 66, 65, 5, + 8, 9, 5, 12, 13, 2, 43, 40, 31, 24, 19, 13, + 70, 77, 87 }, + + { + + 48, + 6, 78, 48, 6, 78, 85, 69, 19, 11, 2, 67, 27, + 32, 53, 14, 1, 64, 5, 15, 0, 67, 67, 75, 91, + 7, 20, 106, 114, 117, 5, 74, 68, 5, 15, 0, 77, + 69, 11, 5, 64, 72, 79, 64, 77, 78, 91, 5, 71, + 79, 2, 75, 76, 88, 7, 66, 68, 72, 2, 4, 22, 0, + 0, 0, 69, 92, 97, 2, 3, 66, 52, 7, 69, 101, + 82, 82, 0, 75, 86, 80, 91, 68, 86, 82, 82, 97, + 97, 98, 98, 18, 65, 2, 84, 65, 81, 75, 82, 72, + 88, 80, 97, 13, 74, 65, 92, 82, 70, 72, 0, 6, + 1, 66, 12, 9, 73, 0, 0, 65, 2, 75, 12, 1, 13, + 19, 9, 17, 17, 12, 0, 11, 3, 2, 6, 83, 88, 77, + 81, 71, 76, 73, 3, 71, 72, 67, 65, 71, 72, 66, + 86, 90, 77, 95, 69, 75, 5, 66, 68, 14, 1, 65, + 68, 14, 70, 78, 1, 90, 27, 32, 26, 26, 32, 25, + 11, 27, 25, 0, 11, 12, 4, 1, 71, 7, 2, 2, 5, + 0, 69, 4, 64, 79, 69, 70, 76, 71, 89, 43, 47, + 41, 43, 41, 37, 35, 37, 31, 23, 25, 18, 8, 5, + 78, 24, 20, 2, 24, 15, 14, 9, 5, 1, 0, 68, 80, + 69, 77, 91, 86, 100, 66, 30, 21, 14, 4, 5, 64, + 69, 72, 77, 4, 44, 29, 22, 17, 23, 7, 0, 68, + 71, 74, 31, 18, 10, 4, 8, 64, 69, 74, 84, 80, + 9, 1, 64, 68, 66, 76, 80, 88, 66, 38, 21, 13, + 6, 12, 64, 68, 70, 76, 62, 84, 75, 67, 66, 69, + 1, 6, 7, 67, 6, 11, 11, 77, 71, 72, 4, 12, 5, + 2, 12, 16, 18, 66, 4, 14, 14, 77, 71, 77, 86, + 80, 72, 77, 73, 74, 71, 68, 72, 72, 71, 67, + 73, 74, 81, 88, 74, 98, 76, 66, 70, 5, 69, 65, + 11, 66, 68, 66, 8, 71, 71, 88, 35, 35, 37, 31, + 23, 33, 29, 25, 22, 21, 21, 16, 65, 65, 72, + 10, 11, 4, 73, 1, 0, 71, 72, 73, 74, 76, 89, + 82, 91, 44, 43, 41, 38, 29, 30, 27, 18, 21, + 16, 14, 4, 1, 69, 77, 23, 24, 0, 24, 26, 15, + 9, 10, 7, 4, 6, 0, 69, 74, 77, 82, 94, 67, 77, + 87, 94, 82, 87, 68, 69, 68, 31, 5, 4, 68, 14, + 43, 30, 49, 6, 42, 37, 18, 4, 69, 73, 100, + 103, 110, 70, 42, 28, 19, 9, 11, 2, 65, 68, + 78, 85, 77, 67, 72, 2, 5, 65, 65, 5, 8, 9, 5, + 13, 13, 1, 42, 38, 29, 22, 17, 11, 72, 79, 88 }, + + { + + 47, + 6, 78, 47, 6, 78, 83, 68, 20, 11, 2, 68, 26, + 31, 53, 14, 3, 64, 6, 16, 0, 67, 66, 76, 92, + 6, 18, 107, 115, 118, 8, 72, 68, 6, 16, 0, 76, + 68, 12, 4, 64, 71, 78, 64, 77, 78, 91, 5, 71, + 78, 2, 75, 76, 88, 7, 66, 67, 72, 2, 4, 22, 0, + 0, 0, 68, 92, 97, 2, 2, 66, 52, 7, 69, 100, + 81, 82, 0, 74, 84, 78, 89, 66, 84, 81, 80, 96, + 96, 97, 97, 19, 64, 3, 83, 65, 80, 74, 80, 72, + 87, 80, 95, 14, 74, 65, 90, 82, 69, 72, 0, 7, + 2, 65, 12, 9, 73, 0, 1, 65, 2, 74, 12, 1, 13, + 19, 8, 17, 17, 12, 65, 11, 3, 1, 5, 83, 87, + 76, 80, 70, 75, 72, 4, 70, 72, 66, 64, 71, 72, + 66, 87, 89, 76, 95, 68, 75, 6, 66, 69, 15, 1, + 65, 69, 15, 71, 79, 1, 90, 26, 31, 26, 25, 31, + 24, 11, 27, 25, 64, 10, 12, 4, 1, 71, 7, 1, 1, + 4, 64, 70, 4, 64, 80, 70, 70, 77, 72, 89, 42, + 46, 40, 42, 40, 35, 33, 35, 29, 21, 23, 16, 5, + 3, 81, 23, 18, 1, 22, 13, 12, 7, 3, 64, 65, + 70, 82, 69, 79, 92, 86, 100, 66, 31, 21, 14, + 5, 5, 64, 68, 72, 76, 4, 44, 29, 23, 17, 24, + 8, 1, 67, 70, 73, 32, 19, 11, 5, 9, 0, 69, 73, + 83, 79, 11, 2, 0, 67, 65, 75, 79, 87, 65, 38, + 22, 14, 6, 13, 0, 67, 69, 75, 62, 83, 74, 66, + 66, 69, 1, 7, 8, 67, 6, 12, 12, 77, 71, 72, 4, + 12, 4, 2, 11, 16, 18, 67, 3, 14, 14, 77, 72, + 78, 85, 79, 71, 77, 72, 74, 70, 67, 72, 72, + 71, 66, 73, 74, 81, 88, 73, 98, 76, 66, 70, 6, + 69, 65, 12, 66, 68, 66, 9, 72, 71, 88, 34, 35, + 37, 31, 22, 32, 28, 24, 22, 20, 20, 16, 65, + 66, 73, 9, 10, 2, 75, 0, 64, 71, 72, 73, 73, + 75, 89, 83, 90, 42, 41, 39, 36, 27, 28, 25, + 16, 19, 14, 12, 2, 64, 70, 78, 21, 23, 64, 22, + 24, 13, 7, 8, 5, 2, 4, 65, 71, 75, 79, 84, 95, + 69, 79, 89, 93, 80, 85, 67, 68, 67, 33, 6, 5, + 68, 15, 45, 31, 51, 7, 41, 36, 16, 1, 71, 76, + 102, 105, 111, 70, 42, 28, 19, 9, 12, 2, 65, + 68, 77, 84, 76, 66, 71, 4, 6, 64, 64, 6, 9, + 10, 5, 14, 13, 1, 41, 37, 28, 21, 15, 9, 74, + 81, 88 }, + + { + + 46, + 6, 78, 46, 6, 78, 81, 66, 20, 11, 1, 70, 24, + 29, 53, 14, 6, 65, 7, 16, 0, 68, 66, 77, 93, + 6, 16, 109, 116, 118, 11, 71, 68, 7, 16, 0, + 76, 67, 12, 4, 64, 71, 78, 64, 78, 77, 91, 5, + 71, 77, 1, 75, 76, 88, 8, 65, 67, 71, 3, 4, + 22, 0, 0, 0, 68, 92, 97, 3, 1, 66, 52, 7, 69, + 99, 80, 82, 1, 72, 83, 76, 88, 65, 83, 79, 78, + 95, 95, 96, 97, 20, 64, 4, 82, 65, 80, 73, 78, + 72, 87, 79, 94, 14, 73, 64, 89, 81, 69, 72, 1, + 7, 2, 64, 13, 10, 74, 64, 1, 65, 2, 74, 12, 0, + 12, 19, 8, 17, 17, 11, 67, 11, 2, 64, 4, 84, + 86, 75, 79, 69, 74, 71, 5, 69, 71, 65, 0, 71, + 72, 65, 88, 89, 75, 95, 68, 75, 6, 66, 70, 16, + 1, 65, 71, 16, 71, 80, 1, 91, 24, 30, 25, 24, + 30, 23, 10, 26, 25, 65, 9, 12, 4, 1, 72, 6, 1, + 0, 4, 65, 71, 3, 65, 82, 71, 71, 78, 73, 89, + 40, 45, 38, 40, 38, 33, 30, 33, 26, 19, 21, + 13, 3, 1, 83, 21, 17, 64, 19, 11, 10, 5, 0, + 66, 68, 72, 85, 70, 80, 94, 87, 100, 66, 31, + 22, 14, 5, 5, 64, 68, 72, 76, 5, 45, 29, 23, + 17, 25, 8, 2, 67, 69, 72, 33, 20, 11, 5, 10, + 0, 68, 73, 82, 79, 12, 2, 0, 67, 64, 74, 79, + 86, 65, 39, 22, 14, 6, 14, 1, 66, 68, 74, 62, + 83, 74, 66, 66, 69, 1, 7, 8, 66, 6, 12, 12, + 77, 71, 72, 3, 13, 3, 1, 10, 15, 18, 68, 2, + 13, 14, 78, 73, 78, 84, 79, 70, 76, 72, 73, + 70, 66, 72, 72, 71, 66, 74, 74, 81, 87, 72, + 98, 76, 65, 70, 6, 69, 65, 13, 66, 68, 66, 10, + 72, 72, 89, 33, 35, 36, 30, 21, 31, 27, 23, + 21, 19, 19, 15, 66, 67, 74, 7, 8, 1, 77, 64, + 65, 72, 73, 74, 73, 75, 89, 83, 89, 40, 39, + 37, 33, 24, 26, 23, 13, 17, 12, 10, 0, 66, 72, + 79, 19, 21, 65, 20, 22, 11, 5, 6, 3, 0, 2, 67, + 74, 77, 81, 86, 96, 71, 81, 90, 92, 79, 84, + 67, 67, 66, 35, 7, 6, 68, 16, 46, 33, 52, 7, + 39, 34, 13, 65, 74, 79, 105, 107, 112, 70, 42, + 28, 19, 9, 12, 2, 65, 68, 77, 83, 75, 65, 70, + 5, 7, 64, 0, 7, 10, 11, 6, 14, 14, 1, 39, 35, + 26, 19, 13, 7, 76, 83, 89 }, + + { + + 45, + 6, 79, 45, 6, 79, 79, 65, 21, 11, 1, 71, 23, + 28, 53, 14, 8, 65, 7, 17, 0, 69, 66, 78, 94, + 5, 15, 110, 117, 119, 14, 70, 68, 7, 17, 0, + 75, 66, 13, 3, 0, 70, 77, 65, 78, 77, 91, 5, + 70, 77, 1, 75, 75, 88, 8, 65, 67, 71, 3, 4, + 22, 0, 0, 0, 67, 92, 97, 3, 1, 67, 52, 7, 69, + 98, 80, 82, 1, 71, 81, 74, 86, 64, 81, 78, 76, + 94, 94, 95, 96, 21, 64, 5, 82, 64, 79, 73, 76, + 72, 86, 79, 93, 14, 73, 64, 87, 81, 68, 71, 1, + 7, 2, 0, 13, 10, 74, 64, 1, 65, 2, 74, 12, 0, + 12, 19, 7, 17, 17, 11, 69, 11, 2, 66, 3, 85, + 86, 74, 79, 69, 73, 71, 6, 69, 71, 65, 2, 71, + 72, 65, 89, 88, 74, 95, 68, 75, 7, 65, 71, 16, + 1, 65, 72, 17, 72, 81, 1, 92, 23, 29, 25, 24, + 29, 23, 10, 25, 24, 66, 8, 12, 4, 1, 72, 5, 0, + 64, 3, 65, 72, 2, 65, 84, 72, 71, 79, 74, 89, + 39, 43, 37, 38, 36, 31, 28, 31, 24, 16, 19, + 11, 0, 65, 86, 19, 15, 65, 17, 8, 7, 2, 65, + 69, 70, 75, 87, 71, 82, 95, 88, 100, 65, 32, + 22, 15, 5, 5, 64, 68, 72, 75, 5, 45, 30, 23, + 17, 26, 9, 2, 66, 69, 71, 34, 20, 12, 5, 11, + 1, 68, 72, 81, 78, 13, 3, 1, 67, 64, 74, 78, + 84, 64, 39, 22, 14, 7, 14, 1, 65, 68, 73, 62, + 82, 73, 65, 66, 69, 1, 7, 8, 66, 7, 12, 13, + 77, 71, 73, 3, 13, 2, 0, 9, 14, 18, 69, 1, 13, + 14, 78, 74, 79, 83, 78, 69, 76, 71, 73, 69, + 66, 72, 71, 70, 65, 74, 74, 81, 87, 71, 98, + 76, 65, 70, 7, 69, 65, 14, 67, 68, 66, 10, 73, + 72, 90, 32, 34, 36, 29, 20, 31, 27, 22, 20, + 19, 18, 14, 67, 68, 75, 6, 7, 64, 79, 66, 67, + 73, 73, 74, 73, 75, 88, 84, 88, 38, 37, 35, + 31, 21, 24, 21, 11, 15, 9, 8, 66, 68, 73, 79, + 18, 20, 67, 18, 19, 9, 3, 4, 1, 65, 64, 69, + 76, 79, 83, 87, 98, 73, 83, 92, 91, 78, 83, + 66, 66, 65, 36, 8, 7, 68, 17, 48, 34, 54, 8, + 38, 33, 11, 68, 77, 81, 108, 109, 114, 69, 42, + 28, 19, 9, 12, 2, 65, 68, 76, 82, 75, 64, 69, + 6, 7, 0, 1, 7, 10, 11, 6, 15, 14, 1, 38, 34, + 24, 17, 12, 6, 79, 84, 90 }, + + { + + 43, + 6, 79, 43, 6, 79, 78, 0, 21, 11, 0, 73, 21, + 27, 53, 14, 10, 65, 8, 18, 0, 70, 66, 79, 95, + 5, 13, 112, 118, 119, 17, 69, 68, 8, 18, 0, + 75, 65, 13, 3, 0, 70, 76, 65, 79, 77, 91, 5, + 70, 76, 1, 76, 75, 88, 9, 65, 67, 71, 4, 4, + 22, 0, 0, 0, 67, 93, 97, 4, 0, 67, 52, 7, 69, + 97, 79, 82, 2, 70, 79, 72, 85, 0, 79, 77, 74, + 93, 94, 94, 95, 21, 64, 6, 81, 64, 79, 72, 74, + 72, 86, 78, 92, 14, 73, 0, 86, 80, 67, 71, 1, + 7, 2, 0, 14, 11, 75, 64, 1, 65, 2, 74, 12, 64, + 11, 19, 6, 17, 17, 11, 71, 11, 1, 68, 2, 86, + 85, 73, 78, 68, 73, 70, 7, 68, 70, 64, 3, 71, + 72, 65, 90, 88, 73, 95, 68, 75, 8, 65, 72, 17, + 1, 65, 73, 18, 72, 82, 1, 93, 21, 28, 24, 23, + 28, 22, 9, 24, 24, 68, 7, 11, 4, 1, 73, 4, 64, + 65, 2, 66, 73, 1, 66, 86, 73, 72, 80, 75, 89, + 37, 42, 35, 36, 34, 29, 26, 29, 21, 14, 17, 8, + 65, 67, 88, 17, 13, 67, 15, 6, 5, 0, 67, 71, + 73, 77, 90, 72, 84, 96, 89, 100, 65, 32, 22, + 15, 5, 5, 64, 68, 72, 75, 5, 45, 30, 23, 17, + 27, 9, 3, 66, 68, 71, 35, 21, 12, 5, 11, 1, + 67, 72, 80, 77, 14, 4, 1, 67, 0, 73, 77, 83, + 64, 39, 22, 14, 7, 15, 2, 65, 67, 72, 62, 82, + 73, 65, 66, 69, 1, 7, 8, 66, 7, 12, 13, 77, + 71, 73, 2, 14, 1, 64, 8, 13, 18, 71, 0, 13, + 14, 79, 75, 79, 83, 77, 68, 75, 71, 72, 69, + 65, 73, 71, 70, 65, 75, 74, 81, 86, 70, 98, + 76, 65, 70, 8, 69, 65, 15, 67, 69, 66, 11, 74, + 72, 91, 31, 34, 35, 28, 19, 30, 26, 21, 19, + 18, 17, 13, 68, 69, 76, 4, 6, 65, 81, 67, 68, + 74, 74, 75, 73, 75, 88, 84, 88, 35, 34, 33, + 29, 18, 22, 19, 8, 13, 7, 6, 68, 70, 75, 80, + 16, 18, 68, 16, 17, 7, 1, 1, 64, 67, 66, 71, + 79, 81, 85, 89, 99, 75, 85, 93, 90, 77, 82, + 65, 65, 64, 38, 9, 8, 68, 18, 49, 36, 55, 9, + 36, 31, 8, 71, 80, 84, 111, 111, 115, 69, 42, + 28, 19, 9, 12, 2, 65, 68, 76, 81, 74, 0, 69, + 7, 8, 0, 2, 8, 11, 12, 6, 15, 14, 1, 37, 32, + 22, 15, 10, 4, 81, 86, 91 }, + + { + + 42, + 6, 79, 42, 6, 79, 76, 1, 21, 11, 0, 74, 20, + 25, 53, 14, 13, 66, 9, 18, 0, 70, 65, 80, 96, + 4, 11, 114, 119, 120, 20, 67, 68, 9, 18, 0, + 75, 64, 13, 2, 0, 70, 76, 65, 79, 76, 91, 5, + 70, 75, 0, 76, 75, 88, 9, 64, 66, 70, 4, 4, + 22, 0, 0, 0, 66, 93, 97, 4, 64, 67, 52, 7, 69, + 96, 78, 82, 2, 68, 78, 70, 83, 1, 78, 75, 72, + 92, 93, 93, 95, 22, 64, 7, 80, 64, 78, 71, 72, + 72, 86, 78, 90, 15, 72, 0, 85, 80, 67, 71, 2, + 8, 3, 1, 14, 11, 75, 65, 1, 65, 2, 74, 12, 64, + 10, 19, 6, 17, 17, 10, 73, 11, 1, 69, 1, 87, + 84, 72, 77, 67, 72, 69, 8, 67, 70, 0, 4, 71, + 72, 64, 91, 87, 72, 95, 67, 75, 8, 65, 73, 18, + 1, 65, 75, 19, 73, 83, 1, 93, 20, 27, 24, 22, + 27, 21, 8, 24, 24, 69, 6, 11, 4, 1, 73, 4, 64, + 66, 2, 67, 74, 1, 67, 88, 74, 73, 81, 76, 89, + 35, 41, 33, 34, 33, 27, 23, 27, 19, 12, 15, 6, + 68, 69, 91, 15, 12, 69, 12, 4, 3, 65, 70, 73, + 76, 79, 92, 73, 85, 98, 89, 100, 65, 32, 23, + 15, 5, 5, 64, 68, 72, 74, 6, 46, 30, 23, 17, + 28, 10, 4, 66, 67, 70, 36, 22, 12, 6, 12, 2, + 67, 71, 79, 77, 16, 4, 1, 66, 1, 72, 77, 82, + 0, 40, 23, 14, 7, 16, 3, 64, 66, 71, 62, 82, + 73, 64, 66, 69, 1, 7, 8, 65, 7, 13, 14, 77, + 71, 73, 1, 14, 0, 65, 7, 13, 18, 72, 64, 12, + 14, 80, 76, 80, 82, 77, 67, 75, 70, 72, 68, + 64, 73, 71, 70, 65, 76, 74, 81, 86, 69, 98, + 76, 64, 70, 8, 69, 65, 16, 67, 69, 66, 12, 74, + 73, 91, 30, 34, 35, 28, 18, 29, 25, 20, 19, + 17, 16, 12, 69, 70, 77, 3, 4, 67, 83, 68, 69, + 75, 75, 75, 73, 74, 88, 85, 87, 33, 32, 31, + 26, 16, 20, 17, 6, 11, 5, 4, 70, 72, 76, 81, + 14, 16, 69, 14, 15, 5, 64, 64, 66, 69, 68, 73, + 81, 83, 87, 91, 100, 77, 87, 95, 89, 75, 81, + 65, 64, 0, 40, 10, 9, 68, 19, 51, 37, 57, 9, + 35, 29, 6, 74, 82, 87, 113, 113, 116, 69, 42, + 28, 19, 9, 12, 2, 65, 68, 75, 80, 73, 1, 68, + 8, 9, 1, 3, 9, 12, 13, 7, 16, 15, 1, 35, 31, + 21, 14, 8, 2, 83, 88, 92 }, + + { + + 41, + 6, 79, 41, 6, 79, 74, 3, 22, 11, 64, 76, 18, + 24, 53, 14, 15, 66, 10, 19, 0, 71, 65, 81, 97, + 4, 9, 115, 120, 120, 23, 66, 68, 10, 19, 0, + 74, 0, 14, 2, 0, 69, 75, 66, 80, 76, 91, 5, + 70, 75, 0, 76, 75, 88, 10, 64, 66, 70, 5, 4, + 22, 0, 0, 0, 66, 93, 97, 5, 65, 67, 52, 7, 69, + 95, 77, 82, 3, 67, 76, 68, 82, 2, 76, 74, 70, + 91, 92, 92, 94, 23, 64, 8, 79, 64, 78, 71, 70, + 72, 85, 77, 89, 15, 72, 1, 83, 79, 66, 71, 2, + 8, 3, 2, 15, 12, 76, 65, 1, 65, 2, 74, 12, 65, + 10, 19, 5, 17, 17, 10, 75, 11, 0, 71, 0, 88, + 83, 71, 76, 66, 71, 69, 9, 67, 69, 1, 5, 71, + 72, 64, 92, 87, 71, 95, 67, 75, 9, 65, 74, 19, + 1, 65, 76, 20, 73, 84, 1, 94, 18, 26, 23, 21, + 26, 20, 8, 23, 23, 70, 5, 11, 4, 1, 74, 3, 65, + 67, 1, 68, 75, 0, 67, 90, 75, 73, 82, 77, 89, + 34, 39, 32, 32, 31, 25, 21, 25, 16, 9, 13, 3, + 70, 72, 93, 13, 10, 70, 10, 2, 0, 68, 72, 76, + 78, 81, 95, 74, 87, 99, 90, 100, 65, 33, 23, + 15, 5, 5, 64, 68, 72, 74, 6, 46, 30, 23, 17, + 29, 10, 4, 65, 67, 69, 37, 22, 13, 6, 13, 2, + 66, 71, 78, 76, 17, 5, 2, 66, 2, 71, 76, 81, + 0, 40, 23, 14, 7, 17, 3, 0, 65, 70, 62, 81, + 72, 64, 66, 69, 1, 7, 8, 65, 7, 13, 14, 77, + 71, 73, 1, 15, 64, 66, 6, 12, 18, 73, 65, 12, + 14, 80, 77, 80, 81, 76, 66, 74, 70, 71, 68, 0, + 73, 71, 70, 64, 76, 74, 81, 85, 68, 98, 76, + 64, 70, 9, 69, 65, 17, 67, 69, 66, 13, 75, 73, + 92, 29, 33, 34, 27, 17, 28, 24, 19, 18, 16, + 15, 11, 70, 71, 78, 1, 3, 68, 85, 70, 70, 76, + 75, 76, 73, 74, 88, 85, 86, 31, 30, 29, 24, + 13, 18, 15, 3, 9, 3, 2, 73, 74, 78, 82, 12, + 15, 71, 12, 13, 3, 66, 66, 68, 71, 70, 75, 84, + 85, 89, 93, 101, 79, 89, 96, 88, 74, 80, 64, + 0, 1, 41, 11, 10, 68, 20, 52, 39, 58, 10, 33, + 28, 3, 77, 85, 90, 116, 115, 117, 69, 42, 28, + 19, 9, 12, 2, 65, 68, 75, 79, 72, 2, 67, 9, 9, + 1, 4, 9, 12, 13, 7, 16, 15, 1, 34, 29, 19, 12, + 6, 0, 85, 90, 93 }, + + { + + 40, + 6, 79, 40, 6, 79, 72, 4, 22, 11, 64, 77, 17, + 23, 53, 14, 17, 66, 11, 20, 0, 72, 65, 82, 98, + 3, 7, 117, 121, 121, 26, 65, 68, 11, 20, 0, + 74, 1, 14, 1, 0, 69, 74, 66, 80, 76, 91, 5, + 70, 74, 0, 76, 75, 88, 10, 64, 66, 70, 5, 4, + 22, 0, 0, 0, 65, 93, 97, 5, 66, 67, 52, 7, 69, + 94, 76, 82, 3, 66, 74, 66, 80, 3, 74, 73, 68, + 90, 91, 91, 93, 24, 64, 9, 78, 64, 77, 70, 68, + 72, 85, 77, 88, 15, 72, 1, 82, 79, 65, 71, 2, + 8, 3, 3, 15, 12, 76, 65, 1, 65, 2, 74, 12, 65, + 9, 19, 4, 17, 17, 10, 77, 11, 0, 73, 64, 89, + 82, 70, 75, 65, 70, 68, 10, 66, 69, 2, 6, 71, + 72, 64, 93, 86, 70, 95, 67, 75, 10, 65, 75, + 20, 1, 65, 77, 21, 74, 85, 1, 95, 17, 25, 23, + 20, 25, 19, 7, 22, 23, 71, 4, 11, 4, 1, 74, 2, + 66, 68, 0, 69, 76, 64, 68, 92, 76, 74, 83, 78, + 89, 32, 38, 30, 30, 29, 23, 19, 23, 14, 7, 11, + 1, 73, 74, 96, 11, 8, 72, 8, 0, 65, 70, 74, + 78, 81, 83, 97, 75, 89, 100, 91, 100, 65, 33, + 23, 15, 5, 5, 64, 68, 72, 73, 6, 46, 30, 23, + 17, 30, 11, 5, 65, 66, 68, 38, 23, 13, 6, 14, + 3, 66, 70, 77, 75, 18, 6, 2, 66, 3, 70, 75, + 80, 1, 40, 23, 14, 7, 18, 4, 1, 64, 69, 62, + 81, 72, 0, 66, 69, 1, 7, 8, 65, 7, 13, 15, 77, + 71, 73, 0, 15, 65, 67, 5, 11, 18, 74, 66, 12, + 14, 81, 78, 81, 80, 75, 65, 74, 69, 71, 67, 1, + 73, 71, 70, 64, 77, 74, 81, 85, 67, 98, 76, + 64, 70, 10, 69, 65, 18, 67, 69, 66, 14, 76, + 73, 93, 28, 33, 34, 26, 16, 27, 23, 18, 17, + 15, 14, 10, 71, 72, 79, 0, 2, 70, 87, 71, 71, + 77, 76, 76, 73, 74, 88, 86, 85, 29, 28, 27, + 22, 10, 16, 13, 1, 7, 1, 0, 75, 76, 79, 83, + 10, 13, 72, 10, 11, 1, 68, 68, 70, 73, 72, 77, + 86, 87, 91, 95, 102, 81, 91, 98, 87, 73, 79, + 0, 1, 2, 43, 12, 11, 68, 21, 54, 40, 60, 11, + 32, 26, 1, 80, 88, 93, 119, 117, 118, 69, 42, + 28, 19, 9, 12, 2, 65, 68, 74, 78, 71, 3, 66, + 10, 10, 2, 5, 10, 13, 14, 7, 17, 15, 1, 33, + 28, 17, 10, 4, 65, 87, 92, 94 }, + + { + + 38, + 5, 80, 38, 5, 80, 71, 5, 22, 11, 65, 79, 15, + 21, 52, 14, 19, 67, 11, 20, 64, 73, 65, 84, + 100, 2, 5, 119, 122, 122, 28, 64, 69, 11, 20, + 64, 74, 2, 14, 0, 0, 69, 74, 67, 81, 76, 92, + 5, 70, 74, 64, 77, 75, 88, 10, 64, 66, 70, 5, + 3, 22, 0, 0, 0, 65, 94, 97, 5, 67, 68, 52, 6, + 69, 93, 76, 82, 3, 65, 73, 65, 79, 4, 73, 72, + 67, 89, 91, 90, 93, 24, 64, 9, 78, 64, 77, 70, + 67, 72, 85, 77, 87, 15, 72, 1, 81, 79, 65, 71, + 2, 8, 3, 3, 15, 12, 77, 66, 1, 66, 2, 74, 11, + 66, 8, 19, 3, 16, 17, 9, 79, 10, 64, 75, 65, + 90, 82, 70, 75, 65, 70, 68, 11, 66, 69, 2, 7, + 72, 72, 64, 95, 86, 70, 95, 67, 76, 10, 65, + 76, 20, 1, 65, 79, 21, 75, 86, 1, 96, 15, 24, + 22, 19, 24, 18, 6, 21, 22, 73, 3, 10, 3, 1, + 75, 1, 67, 69, 64, 70, 77, 65, 69, 94, 78, 75, + 85, 80, 89, 30, 36, 28, 28, 27, 20, 16, 20, + 11, 4, 8, 65, 76, 77, 99, 9, 6, 74, 5, 66, 68, + 73, 77, 81, 84, 86, 100, 76, 91, 102, 92, 101, + 65, 33, 23, 15, 5, 5, 65, 68, 72, 73, 6, 46, + 30, 23, 17, 31, 11, 5, 65, 66, 68, 38, 23, 13, + 6, 14, 3, 66, 70, 76, 75, 19, 6, 2, 66, 3, 70, + 75, 79, 1, 40, 23, 14, 7, 18, 4, 1, 64, 69, + 62, 81, 72, 0, 66, 69, 1, 7, 8, 65, 7, 13, 15, + 77, 71, 74, 64, 15, 66, 68, 4, 10, 17, 76, 68, + 11, 13, 82, 80, 82, 80, 75, 64, 74, 69, 71, + 67, 1, 74, 71, 70, 64, 78, 74, 81, 85, 67, 99, + 76, 64, 70, 10, 70, 65, 18, 68, 70, 66, 14, + 77, 74, 94, 27, 32, 33, 25, 14, 26, 22, 17, + 16, 14, 13, 9, 72, 74, 81, 65, 0, 72, 89, 73, + 73, 78, 77, 77, 73, 74, 88, 87, 85, 26, 25, + 25, 19, 7, 13, 10, 65, 4, 65, 66, 78, 79, 81, + 84, 8, 11, 74, 8, 8, 65, 71, 71, 73, 75, 75, + 80, 89, 89, 94, 97, 104, 83, 93, 100, 86, 72, + 78, 0, 1, 2, 44, 12, 11, 68, 21, 55, 41, 61, + 11, 30, 24, 65, 84, 91, 96, 122, 120, 120, 69, + 42, 27, 18, 9, 12, 2, 66, 68, 74, 78, 71, 3, + 66, 11, 10, 2, 5, 10, 13, 14, 7, 17, 15, 0, + 31, 26, 15, 8, 2, 67, 90, 94, 95 }, + + { + + 37, + 5, 80, 37, 5, 80, 69, 7, 23, 12, 65, 80, 14, + 20, 52, 14, 22, 67, 12, 21, 64, 73, 64, 85, + 101, 2, 4, 120, 123, 122, 31, 1, 69, 12, 21, + 64, 73, 4, 15, 0, 1, 68, 73, 67, 81, 75, 92, + 5, 69, 73, 64, 77, 74, 88, 11, 0, 65, 69, 6, + 3, 22, 0, 0, 0, 64, 94, 97, 6, 67, 68, 52, 6, + 69, 91, 75, 82, 4, 0, 71, 0, 77, 6, 71, 70, + 65, 87, 90, 89, 92, 25, 0, 10, 77, 0, 76, 69, + 65, 71, 84, 76, 85, 16, 71, 2, 79, 78, 64, 70, + 3, 9, 4, 4, 16, 13, 77, 66, 2, 66, 2, 73, 11, + 66, 8, 19, 3, 16, 17, 9, 80, 10, 64, 76, 66, + 90, 81, 69, 74, 64, 69, 67, 12, 65, 68, 3, 9, + 72, 72, 0, 96, 85, 69, 95, 66, 76, 11, 64, 76, + 21, 1, 65, 80, 22, 75, 87, 1, 96, 14, 24, 22, + 19, 24, 18, 6, 21, 22, 74, 3, 10, 3, 1, 75, 1, + 67, 69, 64, 70, 78, 65, 69, 95, 79, 75, 86, + 81, 89, 29, 35, 27, 27, 26, 18, 14, 18, 9, 2, + 6, 67, 78, 79, 101, 8, 5, 75, 3, 68, 70, 75, + 79, 83, 86, 88, 102, 76, 92, 103, 92, 101, 64, + 34, 24, 16, 6, 5, 65, 67, 71, 72, 7, 47, 31, + 24, 17, 32, 12, 6, 64, 65, 67, 39, 24, 14, 7, + 15, 4, 65, 69, 74, 74, 21, 7, 3, 65, 4, 69, + 74, 77, 2, 41, 24, 15, 8, 19, 5, 2, 0, 68, 62, + 80, 71, 1, 66, 68, 1, 8, 9, 64, 8, 14, 16, 76, + 71, 74, 64, 16, 66, 68, 3, 10, 17, 77, 69, 11, + 13, 82, 81, 82, 79, 74, 0, 73, 68, 70, 66, 2, + 74, 70, 69, 0, 78, 73, 80, 84, 66, 99, 76, 0, + 70, 11, 70, 65, 19, 68, 70, 65, 15, 77, 74, + 94, 27, 32, 33, 25, 13, 26, 22, 17, 16, 14, + 13, 9, 72, 75, 82, 66, 64, 73, 90, 74, 74, 78, + 77, 77, 72, 73, 87, 87, 84, 24, 23, 23, 17, 5, + 11, 8, 67, 2, 67, 68, 80, 81, 82, 84, 7, 10, + 75, 7, 6, 67, 73, 73, 75, 77, 77, 82, 91, 90, + 96, 98, 105, 84, 94, 101, 84, 70, 76, 1, 2, 3, + 46, 13, 12, 68, 22, 57, 43, 62, 12, 29, 23, + 67, 87, 93, 98, 124, 122, 121, 68, 43, 27, 18, + 9, 13, 2, 66, 68, 73, 77, 70, 4, 65, 13, 11, + 3, 6, 11, 14, 15, 8, 18, 16, 0, 30, 25, 14, 7, + 1, 68, 92, 95, 95 }, + + { + + 36, + 5, 80, 36, 5, 80, 67, 8, 23, 12, 65, 81, 13, + 19, 52, 14, 24, 67, 13, 22, 64, 74, 64, 86, + 102, 1, 2, 122, 124, 123, 34, 2, 69, 13, 22, + 64, 73, 5, 15, 64, 1, 68, 72, 67, 81, 75, 92, + 5, 69, 72, 64, 77, 74, 88, 11, 0, 65, 69, 6, + 3, 22, 0, 0, 0, 0, 94, 97, 6, 68, 68, 52, 6, + 69, 90, 74, 82, 4, 1, 69, 2, 76, 7, 69, 69, 0, + 86, 89, 88, 91, 26, 0, 11, 76, 0, 76, 68, 0, + 71, 84, 76, 84, 16, 71, 2, 78, 78, 0, 70, 3, + 9, 4, 5, 16, 13, 78, 66, 2, 66, 2, 73, 11, 66, + 7, 19, 2, 16, 17, 9, 82, 10, 64, 78, 67, 91, + 80, 68, 73, 0, 68, 66, 13, 64, 68, 4, 10, 72, + 72, 0, 97, 85, 68, 95, 66, 76, 12, 64, 77, 22, + 1, 65, 81, 23, 76, 88, 1, 97, 12, 23, 21, 18, + 23, 17, 5, 20, 22, 75, 2, 10, 3, 1, 75, 0, 68, + 70, 65, 71, 79, 66, 70, 97, 80, 76, 87, 82, + 89, 27, 34, 25, 25, 24, 16, 12, 16, 6, 0, 4, + 70, 81, 81, 104, 6, 3, 77, 1, 70, 72, 77, 81, + 85, 89, 90, 104, 77, 94, 104, 93, 101, 64, 34, + 24, 16, 6, 5, 65, 67, 71, 71, 7, 47, 31, 24, + 17, 33, 12, 7, 64, 64, 66, 40, 25, 14, 7, 16, + 4, 65, 68, 73, 73, 22, 8, 3, 65, 5, 68, 73, + 76, 2, 41, 24, 15, 8, 20, 6, 3, 1, 67, 62, 80, + 71, 1, 66, 68, 1, 8, 9, 64, 8, 14, 17, 76, 71, + 74, 65, 16, 67, 69, 2, 9, 17, 78, 70, 11, 13, + 83, 82, 83, 78, 73, 1, 73, 68, 70, 65, 3, 74, + 70, 69, 0, 79, 73, 80, 84, 65, 99, 76, 0, 70, + 12, 70, 65, 20, 68, 70, 65, 16, 78, 74, 95, + 26, 32, 33, 24, 12, 25, 21, 16, 15, 13, 12, 8, + 73, 76, 83, 68, 65, 75, 92, 75, 75, 79, 78, + 77, 72, 73, 87, 88, 83, 22, 21, 21, 15, 2, 9, + 6, 70, 0, 69, 70, 82, 83, 83, 85, 5, 8, 76, 5, + 4, 69, 75, 75, 77, 79, 79, 84, 93, 92, 98, + 100, 106, 86, 96, 103, 83, 69, 75, 2, 3, 4, + 48, 14, 13, 68, 23, 58, 44, 62, 13, 28, 21, + 70, 90, 96, 101, 126, 124, 122, 68, 43, 27, + 18, 9, 13, 2, 66, 68, 72, 76, 69, 5, 64, 14, + 12, 4, 7, 12, 15, 16, 8, 19, 16, 0, 29, 23, + 12, 5, 64, 70, 94, 97, 96 }, + + { + + 35, + 5, 80, 35, 5, 80, 65, 10, 24, 12, 66, 83, 11, + 18, 52, 14, 26, 67, 14, 23, 64, 75, 64, 87, + 103, 1, 0, 123, 125, 123, 37, 3, 69, 14, 23, + 64, 72, 6, 16, 64, 1, 67, 71, 68, 82, 75, 92, + 5, 69, 72, 64, 77, 74, 88, 12, 0, 65, 69, 7, + 3, 22, 0, 0, 0, 0, 94, 97, 7, 69, 68, 52, 6, + 69, 89, 73, 82, 5, 2, 67, 4, 74, 8, 67, 68, 2, + 85, 88, 87, 90, 27, 0, 12, 75, 0, 75, 68, 2, + 71, 83, 75, 83, 16, 71, 3, 76, 77, 1, 70, 3, + 9, 4, 6, 17, 14, 78, 66, 2, 66, 2, 73, 11, 67, + 7, 19, 1, 16, 17, 9, 84, 10, 65, 80, 68, 92, + 79, 67, 72, 1, 67, 66, 14, 64, 67, 5, 11, 72, + 72, 0, 98, 84, 67, 95, 66, 76, 13, 64, 78, 23, + 1, 65, 82, 24, 76, 89, 1, 98, 11, 22, 21, 17, + 22, 16, 5, 19, 21, 76, 1, 10, 3, 1, 76, 64, + 69, 71, 66, 72, 80, 67, 70, 99, 81, 76, 88, + 83, 89, 26, 32, 24, 23, 22, 14, 10, 14, 4, 66, + 2, 72, 83, 84, 106, 4, 1, 78, 64, 72, 75, 80, + 83, 88, 91, 92, 107, 78, 96, 105, 94, 101, 64, + 35, 24, 16, 6, 5, 65, 67, 71, 71, 7, 47, 31, + 24, 17, 34, 13, 7, 0, 64, 65, 41, 25, 15, 7, + 17, 5, 64, 68, 72, 72, 23, 9, 4, 65, 6, 67, + 72, 75, 3, 41, 24, 15, 8, 21, 6, 4, 2, 66, 62, + 79, 70, 2, 66, 68, 1, 8, 9, 64, 8, 14, 17, 76, + 71, 74, 65, 17, 68, 70, 1, 8, 17, 79, 71, 11, + 13, 83, 83, 83, 77, 72, 2, 72, 67, 69, 65, 4, + 74, 70, 69, 1, 79, 73, 80, 83, 64, 99, 76, 0, + 70, 13, 70, 65, 21, 68, 70, 65, 17, 79, 74, + 96, 25, 31, 32, 23, 11, 24, 20, 15, 14, 12, + 11, 7, 74, 77, 84, 69, 66, 76, 94, 77, 76, 80, + 78, 78, 72, 73, 87, 88, 82, 20, 19, 19, 13, + 64, 7, 4, 72, 65, 71, 72, 85, 85, 85, 86, 3, + 7, 78, 3, 2, 71, 77, 77, 79, 81, 81, 86, 96, + 94, 100, 102, 107, 88, 98, 104, 82, 68, 74, 3, + 4, 5, 49, 15, 14, 68, 24, 60, 46, 62, 14, 26, + 20, 72, 93, 99, 104, 126, 126, 123, 68, 43, + 27, 18, 9, 13, 2, 66, 68, 72, 75, 68, 6, 0, + 15, 12, 4, 8, 12, 15, 16, 8, 19, 16, 0, 28, + 22, 10, 3, 66, 72, 96, 99, 97 }, + + { + + 33, + 5, 80, 33, 5, 80, 64, 11, 24, 12, 66, 84, 10, + 16, 52, 14, 29, 68, 15, 23, 64, 76, 64, 88, + 104, 0, 65, 125, 126, 124, 40, 4, 69, 15, 23, + 64, 72, 7, 16, 65, 1, 67, 71, 68, 82, 74, 92, + 5, 69, 71, 65, 78, 74, 88, 12, 1, 65, 68, 7, + 3, 22, 0, 0, 0, 1, 95, 97, 7, 70, 68, 52, 6, + 69, 88, 72, 82, 5, 4, 66, 6, 73, 9, 66, 66, 4, + 84, 88, 86, 90, 27, 0, 13, 74, 0, 75, 67, 4, + 71, 83, 75, 82, 16, 70, 3, 75, 77, 1, 70, 4, + 9, 4, 6, 17, 14, 79, 67, 2, 66, 2, 73, 11, 67, + 6, 19, 1, 16, 17, 8, 86, 10, 65, 82, 69, 93, + 78, 66, 71, 2, 67, 65, 15, 0, 67, 6, 12, 72, + 72, 1, 99, 84, 66, 95, 66, 76, 13, 64, 79, 24, + 1, 65, 84, 25, 77, 90, 1, 99, 9, 21, 20, 16, + 21, 15, 4, 18, 21, 78, 0, 9, 3, 1, 76, 65, 69, + 72, 66, 73, 81, 68, 71, 101, 82, 77, 89, 84, + 89, 24, 31, 22, 21, 20, 12, 7, 12, 1, 68, 0, + 75, 86, 86, 109, 2, 0, 80, 67, 74, 77, 82, 86, + 90, 94, 94, 109, 79, 97, 107, 95, 101, 64, 35, + 25, 16, 6, 5, 65, 67, 71, 70, 8, 48, 31, 24, + 17, 35, 13, 8, 0, 0, 65, 42, 26, 15, 7, 17, 5, + 64, 67, 71, 72, 24, 9, 4, 65, 7, 66, 72, 74, + 3, 42, 24, 15, 8, 22, 7, 4, 3, 65, 62, 79, 70, + 2, 66, 68, 1, 8, 9, 0, 8, 14, 18, 76, 71, 74, + 66, 17, 69, 71, 0, 7, 17, 81, 72, 10, 13, 84, + 84, 84, 77, 72, 3, 72, 67, 69, 64, 5, 75, 70, + 69, 1, 80, 73, 80, 83, 0, 99, 76, 1, 70, 13, + 70, 65, 22, 68, 71, 65, 18, 79, 75, 97, 24, + 31, 32, 22, 10, 23, 19, 14, 13, 11, 10, 6, 75, + 78, 85, 71, 68, 78, 96, 78, 77, 81, 79, 78, + 72, 73, 87, 89, 82, 17, 16, 17, 10, 67, 5, 2, + 75, 67, 73, 74, 87, 87, 86, 87, 1, 5, 79, 1, + 0, 73, 79, 80, 81, 83, 83, 88, 98, 96, 102, + 104, 108, 90, 100, 106, 81, 67, 73, 3, 5, 6, + 51, 16, 15, 68, 25, 61, 47, 62, 14, 25, 18, + 75, 96, 102, 107, 126, 126, 124, 68, 43, 27, + 18, 9, 13, 2, 66, 68, 71, 74, 67, 7, 0, 16, + 13, 5, 9, 13, 16, 17, 9, 20, 17, 0, 26, 20, 8, + 1, 68, 74, 98, 101, 98 }, + + { + + 32, + 5, 80, 32, 5, 80, 1, 13, 24, 12, 67, 86, 8, + 15, 52, 14, 31, 68, 16, 24, 64, 76, 0, 89, + 105, 0, 67, 126, 126, 124, 43, 6, 69, 16, 24, + 64, 72, 8, 16, 65, 1, 67, 70, 68, 83, 74, 92, + 5, 69, 70, 65, 78, 74, 88, 13, 1, 64, 68, 8, + 3, 22, 0, 0, 0, 1, 95, 97, 8, 71, 68, 52, 6, + 69, 87, 71, 82, 6, 5, 64, 8, 71, 10, 64, 65, + 6, 83, 87, 85, 89, 28, 0, 14, 73, 0, 74, 66, + 6, 71, 83, 74, 80, 17, 70, 4, 74, 76, 2, 70, + 4, 10, 5, 7, 18, 15, 79, 67, 2, 66, 2, 73, 11, + 68, 5, 19, 0, 16, 17, 8, 88, 10, 66, 83, 70, + 94, 77, 65, 70, 3, 66, 64, 16, 1, 66, 7, 13, + 72, 72, 1, 100, 83, 65, 95, 65, 76, 14, 64, + 80, 25, 1, 65, 85, 26, 77, 91, 1, 99, 8, 20, + 20, 15, 20, 14, 3, 18, 21, 79, 64, 9, 3, 1, + 77, 65, 70, 73, 67, 74, 82, 68, 72, 103, 83, + 78, 90, 85, 89, 22, 30, 20, 19, 19, 10, 5, 10, + 64, 70, 65, 77, 88, 88, 111, 0, 65, 82, 69, + 76, 79, 84, 88, 92, 97, 96, 112, 80, 99, 108, + 95, 101, 64, 35, 25, 16, 6, 5, 65, 67, 71, 70, + 8, 48, 31, 24, 17, 36, 14, 9, 0, 1, 64, 43, + 27, 15, 8, 18, 6, 0, 67, 70, 71, 26, 10, 4, + 64, 8, 65, 71, 73, 4, 42, 25, 15, 8, 23, 8, 5, + 4, 64, 62, 79, 70, 3, 66, 68, 1, 8, 9, 0, 8, + 15, 18, 76, 71, 74, 67, 18, 70, 72, 64, 7, 17, + 82, 73, 10, 13, 85, 85, 84, 76, 71, 4, 71, 66, + 68, 64, 6, 75, 70, 69, 1, 81, 73, 80, 82, 1, + 99, 76, 1, 70, 14, 70, 65, 23, 68, 71, 65, 19, + 80, 75, 97, 23, 31, 31, 22, 9, 22, 18, 13, 13, + 10, 9, 5, 76, 79, 86, 72, 69, 79, 98, 79, 78, + 82, 80, 79, 72, 72, 87, 89, 81, 15, 14, 15, 8, + 69, 3, 0, 77, 69, 75, 76, 89, 89, 88, 88, 64, + 3, 80, 64, 65, 75, 81, 82, 83, 85, 85, 90, + 101, 98, 104, 106, 109, 92, 102, 107, 80, 65, + 72, 4, 6, 7, 53, 17, 16, 68, 26, 62, 49, 62, + 15, 23, 16, 77, 99, 104, 110, 126, 126, 125, + 68, 43, 27, 18, 9, 13, 2, 66, 68, 71, 73, 66, + 8, 1, 17, 14, 5, 10, 14, 17, 18, 9, 20, 17, 0, + 25, 19, 7, 0, 70, 76, 100, 103, 99 }, + + { + + 31, + 5, 81, 31, 5, 81, 3, 14, 25, 12, 67, 87, 7, + 14, 52, 14, 33, 68, 16, 25, 64, 77, 0, 90, + 106, 64, 68, 126, 126, 125, 46, 7, 69, 16, 25, + 64, 71, 9, 17, 66, 2, 66, 69, 69, 83, 74, 92, + 5, 68, 70, 65, 78, 73, 88, 13, 1, 64, 68, 8, + 3, 22, 0, 0, 0, 2, 95, 97, 8, 71, 69, 52, 6, + 69, 86, 71, 82, 6, 6, 1, 10, 70, 11, 1, 64, 8, + 82, 86, 84, 88, 29, 0, 15, 73, 1, 74, 66, 8, + 71, 82, 74, 79, 17, 70, 4, 72, 76, 3, 69, 4, + 10, 5, 8, 18, 15, 80, 67, 2, 66, 2, 73, 11, + 68, 5, 19, 64, 16, 17, 8, 90, 10, 66, 85, 71, + 95, 77, 64, 70, 3, 65, 64, 17, 1, 66, 7, 15, + 72, 72, 1, 101, 83, 64, 95, 65, 76, 15, 0, 81, + 25, 1, 65, 86, 27, 78, 92, 1, 100, 6, 19, 19, + 15, 19, 14, 3, 17, 20, 80, 65, 9, 3, 1, 77, + 66, 71, 74, 68, 74, 83, 69, 72, 105, 84, 78, + 91, 86, 89, 21, 28, 19, 17, 17, 8, 3, 8, 67, + 73, 67, 80, 91, 91, 114, 65, 67, 83, 71, 79, + 82, 87, 90, 95, 99, 99, 114, 81, 101, 109, 96, + 101, 0, 36, 25, 17, 6, 5, 65, 67, 71, 69, 8, + 48, 32, 24, 17, 37, 14, 9, 1, 1, 0, 44, 27, + 16, 8, 19, 6, 0, 66, 69, 70, 27, 11, 5, 64, 8, + 65, 70, 71, 4, 42, 25, 15, 9, 23, 8, 6, 4, 0, + 62, 78, 69, 3, 66, 68, 1, 8, 9, 0, 9, 15, 19, + 76, 71, 75, 67, 18, 71, 73, 65, 6, 17, 83, 74, + 10, 13, 85, 86, 85, 75, 70, 5, 71, 66, 68, 0, + 6, 75, 69, 68, 2, 81, 73, 80, 82, 2, 99, 76, + 1, 70, 15, 70, 65, 24, 69, 71, 65, 19, 81, 75, + 98, 22, 30, 31, 21, 8, 22, 18, 12, 12, 10, 8, + 4, 77, 80, 87, 74, 70, 81, 100, 81, 80, 83, + 80, 79, 72, 72, 86, 90, 80, 13, 12, 13, 6, 72, + 1, 65, 80, 71, 78, 78, 92, 91, 89, 88, 65, 2, + 82, 66, 68, 77, 83, 84, 85, 87, 88, 92, 103, + 100, 106, 107, 111, 94, 104, 109, 79, 64, 71, + 5, 7, 8, 54, 18, 17, 68, 27, 62, 50, 62, 16, + 22, 15, 80, 102, 107, 112, 126, 126, 126, 67, + 43, 27, 18, 9, 13, 2, 66, 68, 70, 72, 66, 9, + 2, 18, 14, 6, 11, 14, 17, 18, 9, 21, 17, 0, + 24, 17, 5, 65, 71, 77, 103, 104, 100 }, + + { + + 30, + 5, 81, 30, 5, 81, 5, 16, 25, 12, 68, 89, 5, + 12, 52, 14, 36, 69, 17, 25, 64, 78, 0, 91, + 107, 64, 70, 126, 126, 125, 49, 8, 69, 17, 25, + 64, 71, 10, 17, 66, 2, 66, 69, 69, 84, 73, 92, + 5, 68, 69, 66, 78, 73, 88, 14, 2, 64, 67, 9, + 3, 22, 0, 0, 0, 2, 95, 97, 9, 72, 69, 52, 6, + 69, 85, 70, 82, 7, 8, 2, 12, 68, 12, 2, 1, 10, + 81, 85, 83, 88, 30, 0, 16, 72, 1, 73, 65, 10, + 71, 82, 73, 78, 17, 69, 5, 71, 75, 3, 69, 5, + 10, 5, 9, 19, 16, 80, 68, 2, 66, 2, 73, 11, + 69, 4, 19, 64, 16, 17, 7, 92, 10, 67, 87, 72, + 96, 76, 0, 69, 4, 64, 0, 18, 2, 65, 8, 16, 72, + 72, 2, 102, 82, 0, 95, 65, 76, 15, 0, 82, 26, + 1, 65, 88, 28, 78, 93, 1, 101, 5, 18, 19, 14, + 18, 13, 2, 16, 20, 81, 66, 9, 3, 1, 78, 67, + 71, 75, 68, 75, 84, 70, 73, 107, 85, 79, 92, + 87, 89, 19, 27, 17, 15, 15, 6, 0, 6, 69, 75, + 69, 82, 93, 93, 116, 67, 68, 85, 74, 81, 84, + 89, 93, 97, 102, 101, 117, 82, 102, 111, 97, + 101, 0, 36, 26, 17, 6, 5, 65, 67, 71, 69, 9, + 49, 32, 24, 17, 38, 15, 10, 1, 2, 1, 45, 28, + 16, 8, 20, 7, 1, 66, 68, 70, 28, 11, 5, 64, 9, + 64, 70, 70, 5, 43, 25, 15, 9, 24, 9, 7, 5, 1, + 62, 78, 69, 4, 66, 68, 1, 8, 9, 1, 9, 15, 19, + 76, 71, 75, 68, 19, 72, 74, 66, 5, 17, 84, 75, + 9, 13, 86, 87, 85, 74, 70, 6, 70, 65, 67, 0, + 7, 75, 69, 68, 2, 82, 73, 80, 81, 3, 99, 76, + 2, 70, 15, 70, 65, 25, 69, 71, 65, 20, 81, 76, + 99, 21, 30, 30, 20, 7, 21, 17, 11, 11, 9, 7, + 3, 78, 81, 88, 75, 72, 82, 102, 82, 81, 84, + 81, 80, 72, 72, 86, 90, 79, 11, 10, 11, 3, 75, + 64, 67, 82, 73, 80, 80, 94, 93, 91, 89, 67, 0, + 83, 68, 70, 79, 85, 86, 87, 89, 90, 94, 106, + 102, 108, 109, 112, 96, 106, 110, 78, 0, 70, + 5, 8, 9, 56, 19, 18, 68, 28, 62, 52, 62, 16, + 20, 13, 82, 105, 110, 115, 126, 126, 126, 67, + 43, 27, 18, 9, 13, 2, 66, 68, 70, 71, 65, 10, + 3, 19, 15, 6, 12, 15, 18, 19, 10, 21, 18, 0, + 22, 16, 3, 67, 73, 79, 105, 106, 101 }, + + { + + 28, + 4, 81, 28, 4, 81, 6, 17, 25, 12, 68, 90, 4, + 11, 52, 14, 38, 69, 18, 26, 64, 79, 0, 92, + 109, 65, 72, 126, 126, 126, 51, 9, 69, 18, 26, + 64, 71, 11, 17, 67, 2, 66, 68, 70, 84, 73, 93, + 5, 68, 69, 66, 79, 73, 88, 14, 2, 64, 67, 9, + 3, 22, 0, 0, 0, 3, 96, 97, 9, 73, 69, 52, 6, + 69, 84, 69, 82, 7, 9, 4, 14, 67, 13, 4, 2, 11, + 80, 85, 82, 87, 30, 0, 17, 71, 1, 73, 65, 11, + 71, 82, 73, 77, 17, 69, 5, 70, 75, 4, 69, 5, + 10, 5, 9, 19, 16, 81, 68, 2, 66, 2, 73, 11, + 69, 3, 19, 65, 16, 17, 7, 94, 10, 67, 89, 73, + 97, 75, 1, 68, 5, 64, 0, 19, 2, 65, 9, 17, 72, + 72, 2, 104, 82, 1, 95, 65, 77, 16, 0, 83, 27, + 1, 65, 89, 29, 79, 94, 1, 102, 3, 17, 18, 13, + 17, 12, 1, 15, 19, 83, 67, 8, 3, 1, 78, 68, + 72, 76, 69, 76, 85, 71, 74, 109, 87, 80, 93, + 88, 89, 17, 25, 15, 13, 13, 4, 65, 4, 72, 78, + 71, 85, 96, 96, 119, 69, 70, 87, 76, 83, 87, + 92, 95, 100, 105, 103, 119, 83, 104, 112, 98, + 102, 0, 36, 26, 17, 6, 5, 65, 67, 71, 68, 9, + 49, 32, 24, 17, 39, 15, 10, 1, 2, 1, 46, 28, + 16, 8, 20, 7, 1, 65, 67, 69, 29, 12, 5, 64, + 10, 0, 69, 69, 5, 43, 25, 15, 9, 25, 9, 7, 6, + 1, 62, 78, 69, 4, 66, 68, 1, 8, 9, 1, 9, 15, + 20, 76, 71, 75, 69, 19, 73, 75, 67, 4, 17, 86, + 77, 9, 13, 87, 88, 86, 74, 69, 7, 70, 65, 67, + 1, 8, 76, 69, 68, 2, 83, 73, 80, 81, 3, 100, + 76, 2, 70, 16, 70, 65, 25, 69, 72, 65, 21, 82, + 76, 100, 20, 29, 30, 19, 5, 20, 16, 10, 10, 8, + 6, 2, 79, 82, 89, 77, 73, 84, 104, 84, 82, 85, + 82, 80, 72, 72, 86, 91, 79, 8, 7, 9, 1, 78, + 67, 70, 85, 75, 82, 82, 97, 95, 92, 90, 69, + 65, 85, 70, 72, 82, 88, 89, 90, 91, 92, 97, + 108, 104, 111, 111, 113, 98, 108, 112, 77, 1, + 69, 6, 9, 9, 57, 20, 18, 68, 28, 62, 53, 62, + 17, 19, 11, 85, 108, 113, 118, 126, 126, 126, + 67, 43, 27, 18, 9, 13, 2, 66, 68, 69, 71, 64, + 11, 3, 20, 15, 7, 12, 15, 18, 19, 10, 22, 18, + 64, 21, 14, 1, 69, 75, 81, 107, 108, 102 }, + + { + + 27, + 4, 81, 27, 4, 81, 8, 18, 26, 12, 68, 91, 3, + 10, 52, 14, 40, 69, 19, 27, 64, 79, 1, 93, + 110, 66, 74, 126, 126, 126, 54, 11, 69, 19, + 27, 64, 70, 12, 18, 68, 2, 65, 67, 70, 84, 73, + 93, 5, 68, 68, 66, 79, 73, 88, 14, 2, 0, 67, + 9, 3, 22, 0, 0, 0, 4, 96, 97, 9, 74, 69, 52, + 6, 69, 83, 68, 82, 7, 10, 6, 16, 65, 15, 6, 3, + 13, 79, 84, 81, 86, 31, 1, 18, 70, 1, 72, 64, + 13, 71, 81, 73, 75, 18, 69, 5, 68, 75, 5, 69, + 5, 11, 6, 10, 19, 16, 81, 68, 3, 66, 2, 72, + 11, 69, 3, 19, 66, 16, 17, 7, 96, 10, 67, 90, + 74, 97, 74, 2, 67, 6, 0, 1, 20, 3, 65, 10, 18, + 72, 72, 2, 105, 81, 2, 95, 64, 77, 17, 0, 84, + 28, 1, 65, 90, 30, 80, 95, 1, 102, 2, 16, 18, + 12, 16, 11, 1, 15, 19, 84, 68, 8, 3, 1, 78, + 68, 73, 77, 70, 77, 86, 71, 74, 110, 88, 80, + 94, 89, 89, 16, 24, 14, 12, 12, 2, 67, 2, 74, + 80, 73, 87, 99, 98, 122, 70, 72, 88, 78, 85, + 89, 94, 97, 102, 107, 105, 121, 83, 106, 113, + 98, 102, 0, 37, 26, 17, 7, 5, 65, 66, 71, 67, + 9, 49, 32, 25, 17, 40, 16, 11, 2, 3, 2, 47, + 29, 17, 9, 21, 8, 1, 64, 66, 68, 31, 13, 6, 0, + 11, 1, 68, 68, 6, 43, 26, 16, 9, 26, 10, 8, 7, + 2, 62, 77, 68, 5, 66, 68, 1, 9, 10, 1, 9, 16, + 21, 76, 71, 75, 69, 19, 74, 75, 68, 4, 17, 87, + 78, 9, 13, 87, 89, 87, 73, 68, 8, 70, 64, 67, + 2, 9, 76, 69, 68, 3, 83, 73, 80, 81, 4, 100, + 76, 2, 70, 17, 70, 65, 26, 69, 72, 65, 22, 83, + 76, 100, 19, 29, 30, 19, 4, 19, 15, 9, 10, 7, + 5, 2, 79, 83, 90, 78, 74, 86, 106, 85, 83, 85, + 82, 80, 71, 71, 86, 92, 78, 6, 5, 7, 64, 80, + 69, 72, 87, 77, 84, 84, 99, 97, 93, 91, 71, + 66, 86, 72, 74, 84, 90, 91, 92, 93, 94, 99, + 110, 105, 113, 113, 114, 100, 110, 114, 76, 3, + 67, 7, 10, 10, 59, 21, 19, 68, 29, 62, 54, 62, + 18, 18, 10, 87, 111, 115, 121, 126, 126, 126, + 67, 43, 27, 18, 9, 14, 2, 66, 68, 68, 70, 0, + 12, 4, 22, 16, 8, 13, 16, 19, 20, 10, 23, 18, + 64, 20, 13, 0, 70, 77, 83, 109, 110, 102 }, + + { + + 26, + 4, 81, 26, 4, 81, 10, 20, 26, 12, 69, 93, 1, + 8, 52, 14, 43, 70, 20, 27, 64, 80, 1, 94, 111, + 66, 76, 126, 126, 126, 57, 12, 69, 20, 27, 64, + 70, 13, 18, 68, 2, 65, 67, 70, 85, 72, 93, 5, + 68, 67, 67, 79, 73, 88, 15, 3, 0, 66, 10, 3, + 22, 0, 0, 0, 4, 96, 97, 10, 75, 69, 52, 6, 69, + 82, 67, 82, 8, 12, 7, 18, 64, 16, 7, 5, 15, + 78, 83, 80, 86, 32, 1, 19, 69, 1, 72, 0, 15, + 71, 81, 72, 74, 18, 68, 6, 67, 74, 5, 69, 6, + 11, 6, 11, 20, 17, 82, 69, 3, 66, 2, 72, 11, + 70, 2, 19, 66, 16, 17, 6, 98, 10, 68, 92, 75, + 98, 73, 3, 66, 7, 1, 2, 21, 4, 64, 11, 19, 72, + 72, 3, 106, 81, 3, 95, 64, 77, 17, 0, 85, 29, + 1, 65, 92, 31, 80, 96, 1, 103, 0, 15, 17, 11, + 15, 10, 0, 14, 19, 85, 69, 8, 3, 1, 79, 69, + 73, 78, 70, 78, 87, 72, 75, 112, 89, 81, 95, + 90, 89, 14, 23, 12, 10, 10, 0, 70, 0, 77, 82, + 75, 90, 101, 100, 124, 72, 73, 90, 81, 87, 91, + 96, 100, 104, 110, 107, 124, 84, 107, 115, 99, + 102, 0, 37, 27, 17, 7, 5, 65, 66, 71, 67, 10, + 50, 32, 25, 17, 41, 16, 12, 2, 4, 3, 48, 30, + 17, 9, 22, 8, 2, 64, 65, 68, 32, 13, 6, 0, 12, + 2, 68, 67, 6, 44, 26, 16, 9, 27, 11, 9, 8, 3, + 62, 77, 68, 5, 66, 68, 1, 9, 10, 2, 9, 16, 21, + 76, 71, 75, 70, 20, 75, 76, 69, 3, 17, 88, 79, + 8, 13, 88, 90, 87, 72, 68, 9, 69, 64, 66, 2, + 10, 76, 69, 68, 3, 84, 73, 80, 80, 5, 100, 76, + 3, 70, 17, 70, 65, 27, 69, 72, 65, 23, 83, 77, + 101, 18, 29, 29, 18, 3, 18, 14, 8, 9, 6, 4, 1, + 80, 84, 91, 80, 76, 87, 108, 86, 84, 86, 83, + 81, 71, 71, 86, 92, 77, 4, 3, 5, 67, 83, 71, + 74, 90, 79, 86, 86, 101, 99, 95, 92, 73, 68, + 87, 74, 76, 86, 92, 93, 94, 95, 96, 101, 113, + 107, 115, 115, 115, 102, 112, 115, 75, 4, 66, + 7, 11, 11, 61, 22, 20, 68, 30, 62, 56, 62, 18, + 16, 8, 90, 114, 118, 124, 126, 126, 126, 67, + 43, 27, 18, 9, 14, 2, 66, 68, 68, 69, 1, 13, + 5, 23, 17, 8, 14, 17, 20, 21, 11, 23, 19, 64, + 18, 11, 65, 72, 79, 85, 111, 112, 103 }, + + { + + 25, + 4, 82, 25, 4, 82, 12, 21, 27, 12, 69, 94, 0, + 7, 52, 14, 45, 70, 20, 28, 64, 81, 1, 95, 112, + 67, 77, 126, 126, 126, 60, 13, 69, 20, 28, 64, + 69, 14, 19, 69, 3, 64, 66, 71, 85, 72, 93, 5, + 67, 67, 67, 79, 72, 88, 15, 3, 0, 66, 10, 3, + 22, 0, 0, 0, 5, 96, 97, 10, 75, 70, 52, 6, 69, + 81, 67, 82, 8, 13, 9, 20, 1, 17, 9, 6, 17, 77, + 82, 79, 85, 33, 1, 20, 69, 2, 71, 0, 17, 71, + 80, 72, 73, 18, 68, 6, 65, 74, 6, 68, 6, 11, + 6, 12, 20, 17, 82, 69, 3, 66, 2, 72, 11, 70, + 2, 19, 67, 16, 17, 6, 100, 10, 68, 94, 76, 99, + 73, 4, 66, 7, 2, 2, 22, 4, 64, 11, 21, 72, 72, + 3, 107, 80, 4, 95, 64, 77, 18, 1, 86, 29, 1, + 65, 93, 32, 81, 97, 1, 104, 64, 14, 17, 11, + 14, 10, 0, 13, 18, 86, 70, 8, 3, 1, 79, 70, + 74, 79, 71, 78, 88, 73, 75, 114, 90, 81, 96, + 91, 89, 13, 21, 11, 8, 8, 65, 72, 65, 79, 85, + 77, 92, 104, 103, 126, 74, 75, 91, 83, 90, 94, + 99, 102, 107, 112, 110, 126, 85, 109, 116, + 100, 102, 1, 38, 27, 18, 7, 5, 65, 66, 71, 66, + 10, 50, 33, 25, 17, 42, 17, 12, 3, 4, 4, 49, + 30, 18, 9, 23, 9, 2, 0, 64, 67, 33, 14, 7, 0, + 12, 2, 67, 65, 7, 44, 26, 16, 10, 27, 11, 10, + 8, 4, 62, 76, 67, 6, 66, 68, 1, 9, 10, 2, 10, + 16, 22, 76, 71, 76, 70, 20, 76, 77, 70, 2, 17, + 89, 80, 8, 13, 88, 91, 88, 71, 67, 10, 69, 0, + 66, 3, 10, 76, 68, 67, 4, 84, 73, 80, 80, 6, + 100, 76, 3, 70, 18, 70, 65, 28, 70, 72, 65, + 23, 84, 77, 102, 17, 28, 29, 17, 2, 18, 14, 7, + 8, 6, 3, 0, 81, 85, 92, 81, 77, 89, 110, 88, + 86, 87, 83, 81, 71, 71, 85, 93, 76, 2, 1, 3, + 69, 86, 73, 76, 92, 81, 89, 88, 104, 101, 96, + 92, 74, 69, 89, 76, 79, 88, 94, 95, 96, 97, + 99, 103, 115, 109, 117, 116, 117, 104, 114, + 117, 74, 5, 65, 8, 12, 12, 62, 23, 21, 68, 31, + 62, 57, 62, 19, 15, 7, 92, 117, 121, 126, 126, + 126, 126, 66, 43, 27, 18, 9, 14, 2, 66, 68, + 67, 68, 1, 14, 6, 24, 17, 9, 15, 17, 20, 21, + 11, 24, 19, 64, 17, 10, 67, 74, 80, 86, 114, + 113, 104 }, + + { + + 23, + 4, 82, 23, 4, 82, 13, 23, 27, 12, 70, 96, 65, + 6, 52, 14, 47, 70, 21, 29, 64, 82, 1, 96, 113, + 67, 79, 126, 126, 126, 62, 14, 69, 21, 29, 64, + 69, 15, 19, 69, 3, 64, 65, 71, 86, 72, 93, 5, + 67, 66, 67, 80, 72, 88, 16, 3, 0, 66, 11, 3, + 22, 0, 0, 0, 5, 97, 97, 11, 76, 70, 52, 6, 69, + 80, 66, 82, 9, 14, 11, 22, 2, 18, 11, 7, 19, + 76, 82, 78, 84, 33, 1, 21, 68, 2, 71, 1, 19, + 71, 80, 71, 72, 18, 68, 7, 64, 73, 7, 68, 6, + 11, 6, 12, 21, 18, 83, 69, 3, 66, 2, 72, 11, + 71, 1, 19, 68, 16, 17, 6, 102, 10, 69, 96, 77, + 100, 72, 5, 65, 8, 2, 3, 23, 5, 0, 12, 22, 72, + 72, 3, 108, 80, 5, 95, 64, 77, 19, 1, 87, 30, + 1, 65, 94, 33, 81, 98, 1, 105, 66, 13, 16, 10, + 13, 9, 64, 12, 18, 88, 71, 7, 3, 1, 80, 71, + 75, 80, 72, 79, 89, 74, 76, 116, 91, 82, 97, + 92, 89, 11, 20, 9, 6, 6, 67, 74, 67, 82, 87, + 79, 95, 106, 105, 126, 76, 77, 93, 85, 92, 96, + 101, 104, 109, 115, 112, 126, 86, 111, 117, + 101, 102, 1, 38, 27, 18, 7, 5, 65, 66, 71, 66, + 10, 50, 33, 25, 17, 43, 17, 13, 3, 5, 4, 50, + 31, 18, 9, 23, 9, 3, 0, 0, 66, 34, 15, 7, 0, + 13, 3, 66, 64, 7, 44, 26, 16, 10, 28, 12, 10, + 9, 5, 62, 76, 67, 6, 66, 68, 1, 9, 10, 2, 10, + 16, 22, 76, 71, 76, 71, 21, 77, 78, 71, 1, 17, + 91, 81, 8, 13, 89, 92, 88, 71, 66, 11, 68, 0, + 65, 3, 11, 77, 68, 67, 4, 85, 73, 80, 79, 7, + 100, 76, 3, 70, 19, 70, 65, 29, 70, 73, 65, + 24, 85, 77, 103, 16, 28, 28, 16, 1, 17, 13, 6, + 7, 5, 2, 64, 82, 86, 93, 83, 78, 90, 112, 89, + 87, 88, 84, 82, 71, 71, 85, 93, 76, 64, 65, 1, + 71, 89, 75, 78, 95, 83, 91, 90, 106, 103, 98, + 93, 76, 71, 90, 78, 81, 90, 96, 98, 98, 99, + 101, 105, 118, 111, 119, 118, 118, 106, 116, + 118, 73, 6, 64, 9, 13, 13, 62, 24, 22, 68, 32, + 62, 59, 62, 20, 13, 5, 95, 120, 124, 126, 126, + 126, 126, 66, 43, 27, 18, 9, 14, 2, 66, 68, + 67, 67, 2, 15, 6, 25, 18, 9, 16, 18, 21, 22, + 11, 24, 19, 64, 16, 8, 69, 76, 82, 88, 116, + 115, 105 }, + + { + + 22, + 4, 82, 22, 4, 82, 15, 24, 27, 12, 70, 97, 66, + 4, 52, 14, 50, 71, 22, 29, 64, 82, 2, 97, 114, + 68, 81, 126, 126, 126, 62, 16, 69, 22, 29, 64, + 69, 16, 19, 70, 3, 64, 65, 71, 86, 71, 93, 5, + 67, 65, 68, 80, 72, 88, 16, 4, 1, 65, 11, 3, + 22, 0, 0, 0, 6, 97, 97, 11, 77, 70, 52, 6, 69, + 79, 65, 82, 9, 16, 12, 24, 4, 19, 12, 9, 21, + 75, 81, 77, 84, 34, 1, 22, 67, 2, 70, 2, 21, + 71, 80, 71, 70, 19, 67, 7, 0, 73, 7, 68, 7, + 12, 7, 13, 21, 18, 83, 70, 3, 66, 2, 72, 11, + 71, 0, 19, 68, 16, 17, 5, 104, 10, 69, 97, 78, + 101, 71, 6, 64, 9, 3, 4, 24, 6, 0, 13, 23, 72, + 72, 4, 109, 79, 6, 95, 0, 77, 19, 1, 88, 31, + 1, 65, 96, 34, 82, 99, 1, 105, 67, 12, 16, 9, + 12, 8, 65, 12, 18, 89, 72, 7, 3, 1, 80, 71, + 75, 81, 72, 80, 90, 74, 77, 118, 92, 83, 98, + 93, 89, 9, 19, 7, 4, 5, 69, 77, 69, 84, 89, + 81, 97, 109, 107, 126, 78, 78, 95, 88, 94, 98, + 103, 107, 111, 118, 114, 126, 87, 112, 119, + 101, 102, 1, 38, 28, 18, 7, 5, 65, 66, 71, 65, + 11, 51, 33, 25, 17, 44, 18, 14, 3, 6, 5, 51, + 32, 18, 10, 24, 10, 3, 1, 1, 66, 36, 15, 7, 1, + 14, 4, 66, 0, 8, 45, 27, 16, 10, 29, 13, 11, + 10, 6, 62, 76, 67, 7, 66, 68, 1, 9, 10, 3, 10, + 17, 23, 76, 71, 76, 72, 21, 78, 79, 72, 1, 17, + 92, 82, 7, 13, 90, 93, 89, 70, 66, 12, 68, 1, + 65, 4, 12, 77, 68, 67, 4, 86, 73, 80, 79, 8, + 100, 76, 4, 70, 19, 70, 65, 30, 70, 73, 65, + 25, 85, 78, 103, 15, 28, 28, 16, 0, 16, 12, 5, + 7, 4, 1, 65, 83, 87, 94, 84, 80, 92, 114, 90, + 88, 89, 85, 82, 71, 70, 85, 94, 75, 66, 67, + 64, 74, 91, 77, 80, 97, 85, 93, 92, 108, 105, + 99, 94, 78, 73, 91, 80, 83, 92, 98, 100, 100, + 101, 103, 107, 120, 113, 121, 120, 119, 108, + 118, 120, 72, 8, 0, 9, 14, 14, 62, 25, 23, 68, + 33, 62, 60, 62, 20, 12, 3, 97, 123, 126, 126, + 126, 126, 126, 66, 43, 27, 18, 9, 14, 2, 66, + 68, 66, 66, 3, 16, 7, 26, 19, 10, 17, 19, 22, + 23, 12, 25, 20, 64, 14, 7, 70, 77, 84, 90, + 118, 117, 106 }, + + { + + 21, + 4, 82, 21, 4, 82, 17, 26, 28, 12, 71, 99, 68, + 3, 52, 14, 52, 71, 23, 30, 64, 83, 2, 98, 115, + 68, 83, 126, 126, 126, 62, 17, 69, 23, 30, 64, + 68, 17, 20, 70, 3, 0, 64, 72, 87, 71, 93, 5, + 67, 65, 68, 80, 72, 88, 17, 4, 1, 65, 12, 3, + 22, 0, 0, 0, 6, 97, 97, 12, 78, 70, 52, 6, 69, + 78, 64, 82, 10, 17, 14, 26, 5, 20, 14, 10, 23, + 74, 80, 76, 83, 35, 1, 23, 66, 2, 70, 2, 23, + 71, 79, 70, 69, 19, 67, 8, 2, 72, 8, 68, 7, + 12, 7, 14, 22, 19, 84, 70, 3, 66, 2, 72, 11, + 72, 0, 19, 69, 16, 17, 5, 106, 10, 70, 99, 79, + 102, 70, 7, 0, 10, 4, 4, 25, 6, 1, 14, 24, 72, + 72, 4, 110, 79, 7, 95, 0, 77, 20, 1, 89, 32, + 1, 65, 97, 35, 82, 100, 1, 106, 69, 11, 15, 8, + 11, 7, 65, 11, 17, 90, 73, 7, 3, 1, 81, 72, + 76, 82, 73, 81, 91, 75, 77, 120, 93, 83, 99, + 94, 89, 8, 17, 6, 2, 3, 71, 79, 71, 87, 92, + 83, 100, 111, 110, 126, 80, 80, 96, 90, 96, + 101, 106, 109, 114, 120, 116, 126, 88, 114, + 120, 102, 102, 1, 39, 28, 18, 7, 5, 65, 66, + 71, 65, 11, 51, 33, 25, 17, 45, 18, 14, 4, 6, + 6, 52, 32, 19, 10, 25, 10, 4, 1, 2, 65, 37, + 16, 8, 1, 15, 5, 65, 1, 8, 45, 27, 16, 10, 30, + 13, 12, 11, 7, 62, 75, 66, 7, 66, 68, 1, 9, + 10, 3, 10, 17, 23, 76, 71, 76, 72, 22, 79, 80, + 73, 0, 17, 93, 83, 7, 13, 90, 94, 89, 69, 65, + 13, 67, 1, 64, 4, 13, 77, 68, 67, 5, 86, 73, + 80, 78, 9, 100, 76, 4, 70, 20, 70, 65, 31, 70, + 73, 65, 26, 86, 78, 104, 14, 27, 27, 15, 64, + 15, 11, 4, 6, 3, 0, 66, 84, 88, 95, 86, 81, + 93, 116, 92, 89, 90, 85, 83, 71, 70, 85, 94, + 74, 68, 69, 66, 76, 94, 79, 82, 100, 87, 95, + 94, 111, 107, 101, 95, 80, 74, 93, 82, 85, 94, + 100, 102, 102, 103, 105, 109, 123, 115, 123, + 122, 120, 110, 120, 121, 71, 9, 1, 10, 15, 15, + 62, 26, 24, 68, 34, 62, 62, 62, 21, 10, 2, + 100, 126, 126, 126, 126, 126, 126, 66, 43, 27, + 18, 9, 14, 2, 66, 68, 66, 65, 4, 17, 8, 27, + 19, 10, 18, 19, 22, 23, 12, 25, 20, 64, 13, 5, + 72, 79, 86, 92, 120, 119, 107 }, + + { + + 20, + 4, 82, 20, 4, 82, 19, 27, 28, 12, 71, 100, 69, + 2, 52, 14, 54, 71, 24, 31, 64, 84, 2, 99, 116, + 69, 85, 126, 126, 126, 62, 18, 69, 24, 31, 64, + 68, 18, 20, 71, 3, 0, 0, 72, 87, 71, 93, 5, + 67, 64, 68, 80, 72, 88, 17, 4, 1, 65, 12, 3, + 22, 0, 0, 0, 7, 97, 97, 12, 79, 70, 52, 6, 69, + 77, 0, 82, 10, 18, 16, 28, 7, 21, 16, 11, 25, + 73, 79, 75, 82, 36, 1, 24, 65, 2, 69, 3, 25, + 71, 79, 70, 68, 19, 67, 8, 3, 72, 9, 68, 7, + 12, 7, 15, 22, 19, 84, 70, 3, 66, 2, 72, 11, + 72, 64, 19, 70, 16, 17, 5, 108, 10, 70, 101, + 80, 103, 69, 8, 1, 11, 5, 5, 26, 7, 1, 15, 25, + 72, 72, 4, 111, 78, 8, 95, 0, 77, 21, 1, 90, + 33, 1, 65, 98, 36, 83, 101, 1, 107, 70, 10, + 15, 7, 10, 6, 66, 10, 17, 91, 74, 7, 3, 1, 81, + 73, 77, 83, 74, 82, 92, 76, 78, 122, 94, 84, + 100, 95, 89, 6, 16, 4, 0, 1, 73, 81, 73, 89, + 94, 85, 102, 114, 112, 126, 82, 82, 98, 92, + 98, 103, 108, 111, 116, 123, 118, 126, 89, + 116, 121, 103, 102, 1, 39, 28, 18, 7, 5, 65, + 66, 71, 64, 11, 51, 33, 25, 17, 46, 19, 15, 4, + 7, 7, 53, 33, 19, 10, 26, 11, 4, 2, 3, 64, 38, + 17, 8, 1, 16, 6, 64, 2, 9, 45, 27, 16, 10, 31, + 14, 13, 12, 8, 62, 75, 66, 8, 66, 68, 1, 9, + 10, 3, 10, 17, 24, 76, 71, 76, 73, 22, 80, 81, + 74, 64, 17, 94, 84, 7, 13, 91, 95, 90, 68, 64, + 14, 67, 2, 64, 5, 14, 77, 68, 67, 5, 87, 73, + 80, 78, 10, 100, 76, 4, 70, 21, 70, 65, 32, + 70, 73, 65, 27, 87, 78, 105, 13, 27, 27, 14, + 65, 14, 10, 3, 5, 2, 64, 67, 85, 89, 96, 87, + 82, 95, 118, 93, 90, 91, 86, 83, 71, 70, 85, + 95, 73, 70, 71, 68, 78, 97, 81, 84, 102, 89, + 97, 96, 113, 109, 102, 96, 82, 76, 94, 84, 87, + 96, 102, 104, 104, 105, 107, 111, 125, 117, + 125, 124, 121, 112, 122, 123, 70, 10, 2, 11, + 16, 16, 62, 27, 25, 68, 35, 62, 62, 62, 22, 9, + 0, 102, 126, 126, 126, 126, 126, 126, 66, 43, + 27, 18, 9, 14, 2, 66, 68, 65, 64, 5, 18, 9, + 28, 20, 11, 19, 20, 23, 24, 12, 26, 20, 64, + 12, 4, 74, 81, 88, 94, 122, 121, 108 }, + + { + + 18, + 3, 83, 18, 3, 83, 20, 28, 28, 12, 72, 102, 71, + 0, 51, 14, 56, 72, 24, 31, 65, 85, 2, 101, + 118, 70, 87, 126, 126, 126, 62, 19, 70, 24, + 31, 65, 68, 19, 20, 72, 3, 0, 0, 73, 88, 71, + 94, 5, 67, 64, 69, 81, 72, 88, 17, 4, 1, 65, + 12, 2, 22, 0, 0, 0, 7, 98, 97, 12, 80, 71, 52, + 5, 69, 76, 0, 82, 10, 19, 17, 29, 8, 22, 17, + 12, 26, 72, 79, 74, 82, 36, 1, 24, 65, 2, 69, + 3, 26, 71, 79, 70, 67, 19, 67, 8, 4, 72, 9, + 68, 7, 12, 7, 15, 22, 19, 85, 71, 3, 67, 2, + 72, 10, 73, 65, 19, 71, 15, 17, 4, 110, 9, 71, + 103, 81, 104, 69, 8, 1, 11, 5, 5, 27, 7, 1, + 15, 26, 73, 72, 4, 113, 78, 8, 95, 0, 78, 21, + 1, 91, 33, 1, 65, 100, 36, 84, 102, 1, 108, + 72, 9, 14, 6, 9, 5, 67, 9, 16, 93, 75, 6, 2, + 1, 82, 74, 78, 84, 75, 83, 93, 77, 79, 124, + 96, 85, 102, 97, 89, 4, 14, 2, 65, 64, 76, 84, + 76, 92, 97, 88, 105, 117, 115, 126, 84, 84, + 100, 95, 101, 106, 111, 114, 119, 126, 121, + 126, 90, 118, 123, 104, 103, 1, 39, 28, 18, 7, + 5, 66, 66, 71, 64, 11, 51, 33, 25, 17, 47, 19, + 15, 4, 7, 7, 53, 33, 19, 10, 26, 11, 4, 2, 4, + 64, 39, 17, 8, 1, 16, 6, 64, 3, 9, 45, 27, 16, + 10, 31, 14, 13, 12, 8, 62, 75, 66, 8, 66, 68, + 1, 9, 10, 3, 10, 17, 24, 76, 71, 77, 74, 22, + 81, 82, 75, 65, 16, 96, 86, 6, 12, 92, 97, 91, + 68, 64, 15, 67, 2, 64, 5, 14, 78, 68, 67, 5, + 88, 73, 80, 78, 10, 101, 76, 4, 70, 21, 71, + 65, 32, 71, 74, 65, 27, 88, 79, 106, 12, 26, + 26, 13, 67, 13, 9, 2, 4, 1, 65, 68, 86, 91, + 98, 89, 84, 97, 120, 95, 92, 92, 87, 84, 71, + 70, 85, 96, 73, 73, 74, 70, 81, 100, 84, 87, + 105, 92, 100, 99, 116, 112, 104, 97, 84, 78, + 96, 86, 90, 99, 105, 107, 107, 107, 110, 114, + 126, 119, 126, 126, 123, 114, 124, 125, 69, + 11, 3, 11, 16, 16, 62, 27, 25, 68, 35, 62, 62, + 62, 22, 7, 65, 105, 126, 126, 126, 126, 126, + 126, 66, 43, 26, 17, 9, 14, 2, 67, 68, 65, 64, + 5, 18, 9, 29, 20, 11, 19, 20, 23, 24, 12, 26, + 20, 65, 10, 2, 76, 83, 90, 96, 125, 123, 109 }, + + { + + 17, + 3, 83, 17, 3, 83, 22, 30, 29, 13, 72, 103, 72, + 64, 51, 14, 59, 72, 25, 32, 65, 85, 3, 102, + 119, 70, 88, 126, 126, 126, 62, 21, 70, 25, + 32, 65, 67, 21, 21, 72, 4, 1, 1, 73, 88, 70, + 94, 5, 66, 0, 69, 81, 71, 88, 18, 5, 2, 64, + 13, 2, 22, 0, 0, 0, 8, 98, 97, 13, 80, 71, 52, + 5, 69, 74, 1, 82, 11, 21, 19, 31, 10, 24, 19, + 14, 28, 70, 78, 73, 81, 37, 2, 25, 64, 3, 68, + 4, 28, 70, 78, 69, 65, 20, 66, 9, 6, 71, 10, + 67, 8, 13, 8, 16, 23, 20, 85, 71, 4, 67, 2, + 71, 10, 73, 65, 19, 71, 15, 17, 4, 111, 9, 71, + 104, 82, 104, 68, 9, 2, 12, 6, 6, 28, 8, 2, + 16, 28, 73, 72, 5, 114, 77, 9, 95, 1, 78, 22, + 2, 91, 34, 1, 65, 101, 37, 84, 103, 1, 108, + 73, 9, 14, 6, 9, 5, 67, 9, 16, 94, 75, 6, 2, + 1, 82, 74, 78, 84, 75, 83, 94, 77, 79, 125, + 97, 85, 103, 98, 89, 3, 13, 1, 66, 65, 78, 86, + 78, 94, 99, 90, 107, 119, 117, 126, 85, 85, + 101, 97, 103, 108, 113, 116, 121, 126, 123, + 126, 90, 119, 124, 104, 103, 2, 40, 29, 19, 8, + 5, 66, 65, 70, 0, 12, 52, 34, 26, 17, 48, 20, + 16, 5, 8, 8, 54, 34, 20, 11, 27, 12, 5, 3, 6, + 0, 41, 18, 9, 2, 17, 7, 0, 5, 10, 46, 28, 17, + 11, 32, 15, 14, 13, 9, 62, 74, 65, 9, 66, 67, + 1, 10, 11, 4, 11, 18, 25, 75, 71, 77, 74, 23, + 81, 82, 76, 65, 16, 97, 87, 6, 12, 92, 98, 91, + 67, 0, 16, 66, 3, 0, 6, 15, 78, 67, 66, 6, 88, + 72, 79, 77, 11, 101, 76, 5, 70, 22, 71, 65, + 33, 71, 74, 64, 28, 88, 79, 106, 12, 26, 26, + 13, 68, 13, 9, 2, 4, 1, 65, 68, 86, 92, 99, + 90, 85, 98, 121, 96, 93, 92, 87, 84, 70, 69, + 84, 96, 72, 75, 76, 72, 83, 102, 86, 89, 107, + 94, 102, 101, 118, 114, 105, 97, 85, 79, 97, + 87, 92, 101, 107, 109, 109, 109, 112, 116, + 126, 120, 126, 126, 124, 115, 125, 126, 67, + 13, 5, 12, 17, 17, 62, 28, 26, 68, 36, 62, 62, + 62, 23, 6, 66, 107, 126, 126, 126, 126, 126, + 126, 65, 44, 26, 17, 9, 15, 2, 67, 68, 64, 0, + 6, 19, 10, 31, 21, 12, 20, 21, 24, 25, 13, 27, + 21, 65, 9, 1, 77, 84, 91, 97, 126, 124, 109 }, + + { + + 16, + 3, 83, 16, 3, 83, 24, 31, 29, 13, 72, 104, 73, + 65, 51, 14, 61, 72, 26, 33, 65, 86, 3, 103, + 120, 71, 90, 126, 126, 126, 62, 22, 70, 26, + 33, 65, 67, 22, 21, 73, 4, 1, 2, 73, 88, 70, + 94, 5, 66, 1, 69, 81, 71, 88, 18, 5, 2, 64, + 13, 2, 22, 0, 0, 0, 9, 98, 97, 13, 81, 71, 52, + 5, 69, 73, 2, 82, 11, 22, 21, 33, 11, 25, 21, + 15, 30, 69, 77, 72, 80, 38, 2, 26, 0, 3, 68, + 5, 30, 70, 78, 69, 64, 20, 66, 9, 7, 71, 11, + 67, 8, 13, 8, 17, 23, 20, 86, 71, 4, 67, 2, + 71, 10, 73, 66, 19, 72, 15, 17, 4, 113, 9, 71, + 106, 83, 105, 67, 10, 3, 13, 7, 7, 29, 9, 2, + 17, 29, 73, 72, 5, 115, 77, 10, 95, 1, 78, 23, + 2, 92, 35, 1, 65, 102, 38, 85, 104, 1, 109, + 75, 8, 13, 5, 8, 4, 68, 8, 16, 95, 76, 6, 2, + 1, 82, 75, 79, 85, 76, 84, 95, 78, 80, 126, + 98, 86, 104, 99, 89, 1, 12, 64, 68, 67, 80, + 88, 80, 97, 101, 92, 110, 122, 119, 126, 87, + 87, 103, 99, 105, 110, 115, 118, 123, 126, + 125, 126, 91, 121, 125, 105, 103, 2, 40, 29, + 19, 8, 5, 66, 65, 70, 1, 12, 52, 34, 26, 17, + 49, 20, 17, 5, 9, 9, 55, 35, 20, 11, 28, 12, + 5, 4, 7, 1, 42, 19, 9, 2, 18, 8, 1, 6, 10, 46, + 28, 17, 11, 33, 16, 15, 14, 10, 62, 74, 65, 9, + 66, 67, 1, 10, 11, 4, 11, 18, 26, 75, 71, 77, + 75, 23, 82, 83, 77, 66, 16, 98, 88, 6, 12, 93, + 99, 92, 66, 1, 17, 66, 3, 0, 7, 16, 78, 67, + 66, 6, 89, 72, 79, 77, 12, 101, 76, 5, 70, 23, + 71, 65, 34, 71, 74, 64, 29, 89, 79, 107, 11, + 26, 26, 12, 69, 12, 8, 1, 3, 0, 66, 69, 87, + 93, 100, 92, 86, 100, 123, 97, 94, 93, 88, 84, + 70, 69, 84, 97, 71, 77, 78, 74, 85, 105, 88, + 91, 110, 96, 104, 103, 120, 116, 106, 98, 87, + 81, 98, 89, 94, 103, 109, 111, 111, 111, 114, + 118, 126, 122, 126, 126, 125, 117, 126, 126, + 66, 14, 6, 13, 18, 18, 62, 29, 27, 68, 37, 62, + 62, 62, 24, 5, 68, 110, 126, 126, 126, 126, + 126, 126, 65, 44, 26, 17, 9, 15, 2, 67, 68, 0, + 1, 7, 20, 11, 32, 22, 13, 21, 22, 25, 26, 13, + 28, 21, 65, 8, 64, 79, 86, 93, 99, 126, 126, + 110 }, + + { + + 15, + 3, 83, 15, 3, 83, 26, 33, 30, 13, 73, 106, 75, + 66, 51, 14, 62, 72, 27, 34, 65, 87, 3, 104, + 121, 71, 92, 126, 126, 126, 62, 23, 70, 27, + 34, 65, 66, 23, 22, 73, 4, 2, 3, 74, 89, 70, + 94, 5, 66, 1, 69, 81, 71, 88, 19, 5, 2, 64, + 14, 2, 22, 0, 0, 0, 9, 98, 97, 14, 82, 71, 52, + 5, 69, 72, 3, 82, 12, 23, 23, 35, 13, 26, 23, + 16, 32, 68, 76, 71, 79, 39, 2, 27, 1, 3, 67, + 5, 32, 70, 77, 68, 0, 20, 66, 10, 9, 70, 12, + 67, 8, 13, 8, 18, 24, 21, 86, 71, 4, 67, 2, + 71, 10, 74, 66, 19, 73, 15, 17, 4, 115, 9, 72, + 108, 84, 106, 66, 11, 4, 14, 8, 7, 30, 9, 3, + 18, 30, 73, 72, 5, 116, 76, 11, 95, 1, 78, 24, + 2, 93, 36, 1, 65, 103, 39, 85, 105, 1, 110, + 76, 7, 13, 4, 7, 3, 68, 7, 15, 96, 77, 6, 2, + 1, 83, 76, 80, 86, 77, 85, 96, 79, 80, 126, + 99, 86, 105, 100, 89, 0, 10, 65, 70, 69, 82, + 90, 82, 99, 104, 94, 112, 124, 122, 126, 89, + 89, 104, 101, 107, 113, 118, 120, 126, 126, + 126, 126, 92, 123, 126, 106, 103, 2, 41, 29, + 19, 8, 5, 66, 65, 70, 1, 12, 52, 34, 26, 17, + 50, 21, 17, 6, 9, 10, 56, 35, 21, 11, 29, 13, + 6, 4, 8, 2, 43, 20, 10, 2, 19, 9, 2, 7, 11, + 46, 28, 17, 11, 34, 16, 16, 15, 11, 62, 73, + 64, 10, 66, 67, 1, 10, 11, 4, 11, 18, 26, 75, + 71, 77, 75, 24, 83, 84, 78, 67, 16, 99, 89, 6, + 12, 93, 100, 92, 65, 2, 18, 65, 4, 1, 7, 17, + 78, 67, 66, 7, 89, 72, 79, 76, 13, 101, 76, 5, + 70, 24, 71, 65, 35, 71, 74, 64, 30, 90, 79, + 108, 10, 25, 25, 11, 70, 11, 7, 0, 2, 64, 67, + 70, 88, 94, 101, 93, 87, 101, 125, 99, 95, 94, + 88, 85, 70, 69, 84, 97, 70, 79, 80, 76, 87, + 108, 90, 93, 112, 98, 106, 105, 123, 118, 108, + 99, 89, 82, 100, 91, 96, 105, 111, 113, 113, + 113, 116, 120, 126, 124, 126, 126, 126, 119, + 126, 126, 65, 15, 7, 14, 19, 19, 62, 30, 28, + 68, 38, 62, 62, 62, 25, 3, 69, 112, 126, 126, + 126, 126, 126, 126, 65, 44, 26, 17, 9, 15, 2, + 67, 68, 0, 2, 8, 21, 12, 33, 22, 13, 22, 22, + 25, 26, 13, 28, 21, 65, 7, 65, 81, 88, 95, + 101, 126, 126, 111 }, + + }, + + }; + +#endif diff --git a/dependencies/ih264d/decoder/ih264d_compute_bs.c b/dependencies/ih264d/decoder/ih264d_compute_bs.c new file mode 100644 index 00000000..4a6750a4 --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_compute_bs.c @@ -0,0 +1,2394 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ + +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264d_structs.h" +#include "ih264d_defs.h" +#include "ih264d_deblocking.h" +#include "string.h" +#include "ih264d_debug.h" +#include "ih264d_tables.h" + +UWORD16 ih264d_update_csbp_8x8(UWORD16 u2_luma_csbp) +{ + UWORD16 u2_mod_csbp; + + u2_mod_csbp = u2_luma_csbp; + + if(u2_mod_csbp & 0x0033) + { + u2_mod_csbp |= 0x0033; + } + + if(u2_mod_csbp & 0x00CC) + { + u2_mod_csbp |= 0x00CC; + } + + if(u2_mod_csbp & 0x3300) + { + u2_mod_csbp |= 0x3300; + } + + if(u2_mod_csbp & 0xCC00) + { + u2_mod_csbp |= 0xCC00; + } + + return u2_mod_csbp; +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_fill_bs2_horz_vert */ +/* */ +/* Description : This function fills boundray strength (=2) for all horz */ +/* and vert edges of current mb based on coded sub block */ +/* pattern of current, top and left mb */ +/* Inputs : */ +/* pu4_bs : Base pointer of BS table which gets updated */ +/* u4_left_mb_csbp : left mb's coded sub block pattern */ +/* u4_top_mb_csbp : top mb's coded sub block pattern */ +/* u4_cur_mb_csbp : current mb's coded sub block pattern */ +/* */ +/* Globals : <Does it use any global variables?> */ +/* Processing : */ +/* */ +/* csbp for each 4x4 block in a mb is bit packet in reverse */ +/* raster scan order for each mb as shown below: */ +/* 15|14|13|12|11|10|9|8|7|6|5|4|3|2|1|0. */ +/* */ +/* BS=2 for a 4x4 edge if any of adjacent blocks forming edge */ +/* are coded. Keeping this in mind, bs=2 for all horz and vert */ +/* edges can be derived using a lookup table for each edge */ +/* after "ORing" the csbp values as follows: */ +/* (C means current Mb, T means top mb and L means left mb) */ +/* */ +/* All Horz edges: */ +/* 15C|14C|13C|12C|11C|10C|9C|8C|7C|6C|5C|4C|3C |2C |1C |0C */ +/* (or with) 11C|10C| 9C| 8C| 7C|6C |5C|4C|3C|2C|1C|0C|15T|14T|13T|12T */ +/* -----BS[3]-----|----BS[2]----|---BS[1]---|----BS[0]-----| */ +/* */ +/* All Vert edges: */ +/* 15C|14C|13C|12C|11C|10C|9C| 8C|7C|6C|5C|4C|3C |2C |1C |0C */ +/* (or with) 14C|13C|12C|15L|10C| 9C|8C|11L|6C|5C|4C|7L|2C |1C |0C |3L */ +/* Do 4x4 transpose of resulting pattern to get vertBS[4]-BS[7] */ +/* */ +/* Outputs : <What does the function produce?> */ +/* Returns : <What does the function return?> */ +/* */ +/* Issues : <List any issues or problems with this function> */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 16 10 2008 Jay Draft */ +/* */ +/*****************************************************************************/ +#define CSBP_LEFT_BLOCK_MASK 0x1111 +#define CSBP_RIGHT_BLOCK_MASK 0x8888 + +void ih264d_fill_bs2_horz_vert(UWORD32 *pu4_bs, /* Base pointer of BS table */ + WORD32 u4_left_mb_csbp, /* csbp of left mb */ + WORD32 u4_top_mb_csbp, /* csbp of top mb */ + WORD32 u4_cur_mb_csbp, /* csbp of current mb */ + const UWORD32 *pu4_packed_bs2, const UWORD16 *pu2_4x4_v2h_reorder) +{ + /*************************************************************************/ + /*u4_nbr_horz_csbp=11C|10C|9C|8C|7C|6C|5C|4C|3C|2C|1C|0C|15T|14T|13T|12T */ + /*************************************************************************/ + UWORD32 u4_nbr_horz_csbp = (u4_cur_mb_csbp << 4) | (u4_top_mb_csbp >> 12); + UWORD32 u4_horz_bs2_dec = u4_cur_mb_csbp | u4_nbr_horz_csbp; + + /*************************************************************************/ + /*u4_left_mb_masked_csbp = 15L|0|0|0|11L|0|0|0|7L|0|0|0|3L|0|0|0 */ + /*************************************************************************/ + UWORD32 u4_left_mb_masked_csbp = u4_left_mb_csbp & CSBP_RIGHT_BLOCK_MASK; + + /*************************************************************************/ + /*u4_cur_mb_masked_csbp =14C|13C|12C|x|10C|9C|8C|x|6C|5C|4C|x|2C|1C|0C|x */ + /*************************************************************************/ + UWORD32 u4_cur_mb_masked_csbp = (u4_cur_mb_csbp << 1) + & (~CSBP_LEFT_BLOCK_MASK); + + /*************************************************************************/ + /*u4_nbr_vert_csbp=14C|13C|12C|15L|10C|9C|8C|11L|6C|5C|4C|7L|2C|1C|0C|3L */ + /*************************************************************************/ + UWORD32 u4_nbr_vert_csbp = (u4_cur_mb_masked_csbp) + | (u4_left_mb_masked_csbp >> 3); + + UWORD32 u4_vert_bs2_dec = u4_cur_mb_csbp | u4_nbr_vert_csbp; + + UWORD32 u4_reordered_vert_bs2_dec, u4_temp; + + PROFILE_DISABLE_BOUNDARY_STRENGTH() + + /*************************************************************************/ + /* Fill horz edges (0,1,2,3) boundary strengths 2 using look up table */ + /*************************************************************************/ + pu4_bs[0] = pu4_packed_bs2[u4_horz_bs2_dec & 0xF]; + pu4_bs[1] = pu4_packed_bs2[(u4_horz_bs2_dec >> 4) & 0xF]; + pu4_bs[2] = pu4_packed_bs2[(u4_horz_bs2_dec >> 8) & 0xF]; + pu4_bs[3] = pu4_packed_bs2[(u4_horz_bs2_dec >> 12) & 0xF]; + + /*************************************************************************/ + /* Do 4x4 tranpose of u4_vert_bs2_dec by using look up table for reorder */ + /*************************************************************************/ + u4_reordered_vert_bs2_dec = pu2_4x4_v2h_reorder[u4_vert_bs2_dec & 0xF]; + u4_temp = pu2_4x4_v2h_reorder[(u4_vert_bs2_dec >> 4) & 0xF]; + u4_reordered_vert_bs2_dec |= (u4_temp << 1); + u4_temp = pu2_4x4_v2h_reorder[(u4_vert_bs2_dec >> 8) & 0xF]; + u4_reordered_vert_bs2_dec |= (u4_temp << 2); + u4_temp = pu2_4x4_v2h_reorder[(u4_vert_bs2_dec >> 12) & 0xF]; + u4_reordered_vert_bs2_dec |= (u4_temp << 3); + + /*************************************************************************/ + /* Fill vert edges (4,5,6,7) boundary strengths 2 using look up table */ + /*************************************************************************/ + pu4_bs[4] = pu4_packed_bs2[u4_reordered_vert_bs2_dec & 0xF]; + pu4_bs[5] = pu4_packed_bs2[(u4_reordered_vert_bs2_dec >> 4) & 0xF]; + pu4_bs[6] = pu4_packed_bs2[(u4_reordered_vert_bs2_dec >> 8) & 0xF]; + pu4_bs[7] = pu4_packed_bs2[(u4_reordered_vert_bs2_dec >> 12) & 0xF]; +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_fill_bs1_16x16mb_pslice */ +/* */ +/* Description : This function fills boundray strength (=1) for those */ +/* horz and vert mb edges of 16x16mb which are set to 0 by */ +/* ih264d_fill_bs2_horz_vert. This function is used for p slices */ +/* */ +/* Inputs : <What inputs does the function take?> */ +/* Globals : <Does it use any global variables?> */ +/* Processing : If any motion vector component of adjacent 4x4 blocks */ +/* differs by more than 1 integer pel or if reference */ +/* pictures are different, Bs is set to 1. */ +/* */ +/* Outputs : <What does the function produce?> */ +/* Returns : <What does the function return?> */ +/* */ +/* Issues : <List any issues or problems with this function> */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 16 10 2008 Jay Draft */ +/* */ +/*****************************************************************************/ +void ih264d_fill_bs1_16x16mb_pslice(mv_pred_t *ps_cur_mv_pred, + mv_pred_t *ps_top_mv_pred, + void **ppv_map_ref_idx_to_poc, + UWORD32 *pu4_bs_table, /* pointer to the BsTable array */ + mv_pred_t *ps_leftmost_mv_pred, + neighbouradd_t *ps_left_addr, + void **u4_pic_addrress, /* picture address for BS calc */ + WORD32 i4_ver_mvlimit) +{ + WORD16 i2_q_mv0, i2_q_mv1; + WORD16 i2_p_mv0, i2_p_mv1; + void *pv_cur_pic_addr0, *pv_cur_pic_addr1; + void *pv_nbr_pic_addr0, *pv_nbr_pic_addr1; + void **ppv_map_ref_idx_to_poc_l0; //,*ppv_map_ref_idx_to_poc_l1; + UWORD32 i; + UWORD32 u4_bs_horz = pu4_bs_table[0]; + UWORD32 u4_bs_vert = pu4_bs_table[4]; + + PROFILE_DISABLE_BOUNDARY_STRENGTH() + + ppv_map_ref_idx_to_poc_l0 = ppv_map_ref_idx_to_poc; + + i2_q_mv0 = ps_cur_mv_pred->i2_mv[0]; + i2_q_mv1 = ps_cur_mv_pred->i2_mv[1]; + pv_cur_pic_addr0 = ppv_map_ref_idx_to_poc_l0[ps_cur_mv_pred->i1_ref_frame[0]]; + pv_cur_pic_addr1 = 0; + + /*********************************/ + /* Computing Bs for the top edge */ + /*********************************/ + for(i = 0; i < 4; i++, ps_top_mv_pred++) + { + UWORD32 u4_idx = 24 - (i << 3); + + /*********************************/ + /* check if Bs is already set */ + /*********************************/ + if(!((u4_bs_horz >> u4_idx) & 0xf)) + { + /************************************************************/ + /* If Bs is not set, use left edge and current edge mvs and */ + /* reference pictures addresses to evaluate Bs==1 */ + /************************************************************/ + UWORD32 u4_bs_temp1; + UWORD32 u4_bs; + + /*********************************************************/ + /* If any motion vector component differs by more than 1 */ + /* integer pel or if reference pictures are different Bs */ + /* is set to 1. Note that this condition shall be met for*/ + /* both (fwd-fwd,bwd-bwd) and (fwd-bwd,bwd-fwd) direction*/ + /*********************************************************/ + i2_p_mv0 = ps_top_mv_pred->i2_mv[0]; + i2_p_mv1 = ps_top_mv_pred->i2_mv[1]; + pv_nbr_pic_addr0 = u4_pic_addrress[i & 2]; + pv_nbr_pic_addr1 = u4_pic_addrress[1 + (i & 2)]; + + u4_bs_temp1 = ((ABS((i2_p_mv0 - i2_q_mv0)) >= 4) || + (ABS((i2_p_mv1 - i2_q_mv1)) >= i4_ver_mvlimit)); + + u4_bs = ((pv_cur_pic_addr0 != pv_nbr_pic_addr0) + || (pv_cur_pic_addr1 != pv_nbr_pic_addr1) + || u4_bs_temp1); + + u4_bs_horz |= (u4_bs << u4_idx); + } + } + pu4_bs_table[0] = u4_bs_horz; + + /***********************************/ + /* Computing Bs for the left edge */ + /***********************************/ + for(i = 0; i < 4; i++, ps_leftmost_mv_pred += 4) + { + UWORD32 u4_idx = 24 - (i << 3); + + /*********************************/ + /* check if Bs is already set */ + /*********************************/ + if(!((u4_bs_vert >> u4_idx) & 0xf)) + { + /****************************************************/ + /* If Bs is not set, evalaute conditions for Bs=1 */ + /****************************************************/ + UWORD32 u4_bs_temp1; + UWORD32 u4_bs; + /*********************************************************/ + /* If any motion vector component differs by more than 1 */ + /* integer pel or if reference pictures are different Bs */ + /* is set to 1. Note that this condition shall be met for*/ + /* both (fwd-fwd,bwd-bwd) and (fwd-bwd,bwd-fwd) direction*/ + /*********************************************************/ + + i2_p_mv0 = ps_leftmost_mv_pred->i2_mv[0]; + i2_p_mv1 = ps_leftmost_mv_pred->i2_mv[1]; + pv_nbr_pic_addr0 = ps_left_addr->u4_add[i & 2]; + pv_nbr_pic_addr1 = ps_left_addr->u4_add[1 + (i & 2)]; + + u4_bs_temp1 = + ((ABS((i2_p_mv0 - i2_q_mv0)) + >= 4) + | (ABS((i2_p_mv1 - i2_q_mv1)) + >= i4_ver_mvlimit)); + + u4_bs = ((pv_cur_pic_addr0 != pv_nbr_pic_addr0) + || (pv_cur_pic_addr1 != pv_nbr_pic_addr1) + || u4_bs_temp1); + + u4_bs_vert |= (u4_bs << u4_idx); + } + } + pu4_bs_table[4] = u4_bs_vert; + + return; +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_fill_bs1_non16x16mb_pslice */ +/* */ +/* Description : This function fills boundray strength (=1) for those */ +/* horz and vert edges of non16x16mb which are set to 0 by */ +/* ih264d_fill_bs2_horz_vert. This function is used for p slices */ +/* */ +/* Inputs : <What inputs does the function take?> */ +/* Globals : <Does it use any global variables?> */ +/* Processing : If any motion vector component of adjacent 4x4 blocks */ +/* differs by more than 1 integer pel or if reference */ +/* pictures are different, Bs is set to 1. */ +/* */ +/* Outputs : <What does the function produce?> */ +/* Returns : <What does the function return?> */ +/* */ +/* Issues : <List any issues or problems with this function> */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 16 10 2008 Jay Draft */ +/* */ +/*****************************************************************************/ +void ih264d_fill_bs1_non16x16mb_pslice(mv_pred_t *ps_cur_mv_pred, + mv_pred_t *ps_top_mv_pred, + void **ppv_map_ref_idx_to_poc, + UWORD32 *pu4_bs_table, /* pointer to the BsTable array */ + mv_pred_t *ps_leftmost_mv_pred, + neighbouradd_t *ps_left_addr, + void **u4_pic_addrress, + WORD32 i4_ver_mvlimit) +{ + UWORD32 edge; + void **ppv_map_ref_idx_to_poc_l0; //,*ppv_map_ref_idx_to_poc_l1; + + PROFILE_DISABLE_BOUNDARY_STRENGTH() + + ppv_map_ref_idx_to_poc_l0 = ppv_map_ref_idx_to_poc; + + + for(edge = 0; edge < 4; edge++, ps_top_mv_pred = ps_cur_mv_pred - 4) + { + /*********************************************************************/ + /* Each iteration of this loop fills the four BS values of one HORIZ */ + /* edge and one BS value for each of the four VERT edges. */ + /*********************************************************************/ + WORD32 i; + UWORD32 u4_vert_idx = 24 - (edge << 3); + UWORD32 u4_bs_horz = pu4_bs_table[edge]; + mv_pred_t *ps_left_mv_pred = ps_leftmost_mv_pred + (edge << 2); + + for(i = 0; i < 4; i++, ps_top_mv_pred++, ps_cur_mv_pred++) + { + WORD16 i2_cur_mv0, i2_cur_mv1; + WORD8 i1_cur_ref0; + void *pv_cur_pic_addr0, *pv_cur_pic_addr1 = 0; + void *pv_nbr_pic_addr0, *pv_nbr_pic_addr1; + + /******************************************************/ + /* Each iteration of this inner loop computes a HORIZ */ + /* and a VERT BS value for a 4x4 block */ + /******************************************************/ + UWORD32 u4_bs_vert = (pu4_bs_table[i + 4] >> u4_vert_idx) & 0xf; + UWORD32 u4_horz_idx = 24 - (i << 3); + + /*****************************************************/ + /* check if vert Bs for this block is already set */ + /*****************************************************/ + if(!u4_bs_vert) + { + WORD16 i2_left_mv0, i2_left_mv1; + /************************************************************/ + /* If Bs is not set, use left edge and current edge mvs and */ + /* reference pictures addresses to evaluate Bs==1 */ + /************************************************************/ + i2_left_mv0 = ps_left_mv_pred->i2_mv[0]; + i2_left_mv1 = ps_left_mv_pred->i2_mv[1]; + + i2_cur_mv0 = ps_cur_mv_pred->i2_mv[0]; + i2_cur_mv1 = ps_cur_mv_pred->i2_mv[1]; + + i1_cur_ref0 = ps_cur_mv_pred->i1_ref_frame[0]; + + pv_cur_pic_addr0 = ppv_map_ref_idx_to_poc_l0[i1_cur_ref0]; + if(i) + { + WORD8 i1_left_ref0 = ps_left_mv_pred->i1_ref_frame[0]; + pv_nbr_pic_addr0 = ppv_map_ref_idx_to_poc_l0[i1_left_ref0]; + pv_nbr_pic_addr1 = 0; + } + else + { + pv_nbr_pic_addr0 = ps_left_addr->u4_add[edge & 2]; + pv_nbr_pic_addr1 = ps_left_addr->u4_add[1 + (edge & 2)]; + } + + { + UWORD32 u4_bs_temp1; + /*********************************************************/ + /* If any motion vector component differs by more than 1 */ + /* integer pel or if reference pictures are different Bs */ + /* is set to 1. Note that this condition shall be met for*/ + /* both (fwd-fwd,bwd-bwd) and (fwd-bwd,bwd-fwd) direction*/ + /*********************************************************/ + + u4_bs_temp1 = + ((ABS((i2_left_mv0 - i2_cur_mv0)) + >= 4) + | (ABS((i2_left_mv1 + - i2_cur_mv1)) + >= i4_ver_mvlimit)); + + u4_bs_vert = ((pv_nbr_pic_addr0 != pv_cur_pic_addr0) + || (pv_nbr_pic_addr1 != pv_cur_pic_addr1) + || u4_bs_temp1); + + pu4_bs_table[i + 4] |= (u4_bs_vert << u4_vert_idx); + } + } + + /*****************************************************/ + /* check if horz Bs for this block is already set */ + /*****************************************************/ + if(!((u4_bs_horz >> u4_horz_idx) & 0xf)) + { + WORD16 i2_top_mv0, i2_top_mv1; + /************************************************************/ + /* If Bs is not set, use top edge and current edge mvs and */ + /* reference pictures addresses to evaluate Bs==1 */ + /************************************************************/ + i2_cur_mv0 = ps_cur_mv_pred->i2_mv[0]; + i2_cur_mv1 = ps_cur_mv_pred->i2_mv[1]; + + i1_cur_ref0 = ps_cur_mv_pred->i1_ref_frame[0]; + + i2_top_mv0 = ps_top_mv_pred->i2_mv[0]; + i2_top_mv1 = ps_top_mv_pred->i2_mv[1]; + + pv_cur_pic_addr0 = ppv_map_ref_idx_to_poc_l0[i1_cur_ref0]; + if(edge) + { + WORD8 i1_top_ref0 = ps_top_mv_pred->i1_ref_frame[0]; + pv_nbr_pic_addr0 = ppv_map_ref_idx_to_poc_l0[i1_top_ref0]; + pv_nbr_pic_addr1 = 0; + } + else + { + pv_nbr_pic_addr0 = u4_pic_addrress[i & 2]; + pv_nbr_pic_addr1 = u4_pic_addrress[1 + (i & 2)]; + } + + { + UWORD32 u4_bs_temp1; + UWORD32 u4_bs; + /*********************************************************/ + /* If any motion vector component differs by more than 1 */ + /* integer pel or if reference pictures are different Bs */ + /* is set to 1. Note that this condition shall be met for*/ + /* both (fwd-fwd,bwd-bwd) and (fwd-bwd,bwd-fwd) direction*/ + /*********************************************************/ + + u4_bs_temp1 = + ((ABS((i2_top_mv0 - i2_cur_mv0)) + >= 4) + | (ABS((i2_top_mv1 + - i2_cur_mv1)) + >= i4_ver_mvlimit)); + + u4_bs = ((pv_nbr_pic_addr0 != pv_cur_pic_addr0) + || (pv_nbr_pic_addr1 != pv_cur_pic_addr1) + || u4_bs_temp1); + + u4_bs_horz |= (u4_bs << u4_horz_idx); + } + } + + ps_left_mv_pred = ps_cur_mv_pred; + } + + pu4_bs_table[edge] = u4_bs_horz; + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_fill_bs1_16x16mb_bslice */ +/* */ +/* Description : This function fills boundray strength (=1) for those */ +/* horz and vert mb edges of 16x16mb which are set to 0 by */ +/* ih264d_fill_bs2_horz_vert. This function is used for b slices */ +/* */ +/* Inputs : <What inputs does the function take?> */ +/* Globals : <Does it use any global variables?> */ +/* Processing : If any motion vector component of adjacent 4x4 blocks */ +/* differs by more than 1 integer pel or if reference */ +/* pictures are different, Bs is set to 1. */ +/* */ +/* Outputs : <What does the function produce?> */ +/* Returns : <What does the function return?> */ +/* */ +/* Issues : <List any issues or problems with this function> */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 16 10 2008 Jay Draft */ +/* */ +/*****************************************************************************/ +void ih264d_fill_bs1_16x16mb_bslice(mv_pred_t *ps_cur_mv_pred, + mv_pred_t *ps_top_mv_pred, + void **ppv_map_ref_idx_to_poc, + UWORD32 *pu4_bs_table, /* pointer to the BsTable array */ + mv_pred_t *ps_leftmost_mv_pred, + neighbouradd_t *ps_left_addr, + void **u4_pic_addrress, + WORD32 i4_ver_mvlimit) +{ + WORD16 i2_q_mv0, i2_q_mv1, i2_q_mv2, i2_q_mv3; + WORD16 i2_p_mv0, i2_p_mv1, i2_p_mv2, i2_p_mv3; + void *pv_cur_pic_addr0, *pv_cur_pic_addr1; + void *pv_nbr_pic_addr0, *pv_nbr_pic_addr1; + void **ppv_map_ref_idx_to_poc_l0, **ppv_map_ref_idx_to_poc_l1; + UWORD32 i; + UWORD32 u4_bs_horz = pu4_bs_table[0]; + UWORD32 u4_bs_vert = pu4_bs_table[4]; + + PROFILE_DISABLE_BOUNDARY_STRENGTH() + + ppv_map_ref_idx_to_poc_l0 = ppv_map_ref_idx_to_poc; + ppv_map_ref_idx_to_poc_l1 = ppv_map_ref_idx_to_poc + POC_LIST_L0_TO_L1_DIFF; + i2_q_mv0 = ps_cur_mv_pred->i2_mv[0]; + i2_q_mv1 = ps_cur_mv_pred->i2_mv[1]; + i2_q_mv2 = ps_cur_mv_pred->i2_mv[2]; + i2_q_mv3 = ps_cur_mv_pred->i2_mv[3]; + pv_cur_pic_addr0 = ppv_map_ref_idx_to_poc_l0[ps_cur_mv_pred->i1_ref_frame[0]]; + pv_cur_pic_addr1 = ppv_map_ref_idx_to_poc_l1[ps_cur_mv_pred->i1_ref_frame[1]]; + + /*********************************/ + /* Computing Bs for the top edge */ + /*********************************/ + for(i = 0; i < 4; i++, ps_top_mv_pred++) + { + UWORD32 u4_idx = 24 - (i << 3); + + /*********************************/ + /* check if Bs is already set */ + /*********************************/ + if(!((u4_bs_horz >> u4_idx) & 0xf)) + { + /************************************************************/ + /* If Bs is not set, use left edge and current edge mvs and */ + /* reference pictures addresses to evaluate Bs==1 */ + /************************************************************/ + UWORD32 u4_bs_temp1, u4_bs_temp2; + UWORD32 u4_bs; + + /*********************************************************/ + /* If any motion vector component differs by more than 1 */ + /* integer pel or if reference pictures are different Bs */ + /* is set to 1. Note that this condition shall be met for*/ + /* both (fwd-fwd,bwd-bwd) and (fwd-bwd,bwd-fwd) direction*/ + /*********************************************************/ + i2_p_mv0 = ps_top_mv_pred->i2_mv[0]; + i2_p_mv1 = ps_top_mv_pred->i2_mv[1]; + i2_p_mv2 = ps_top_mv_pred->i2_mv[2]; + i2_p_mv3 = ps_top_mv_pred->i2_mv[3]; + pv_nbr_pic_addr0 = u4_pic_addrress[i & 2]; + pv_nbr_pic_addr1 = u4_pic_addrress[1 + (i & 2)]; + + u4_bs_temp1 = + ((ABS((i2_p_mv0 - i2_q_mv0)) + >= 4) + | (ABS((i2_p_mv1 - i2_q_mv1)) + >= i4_ver_mvlimit) + | (ABS((i2_p_mv2 - i2_q_mv2)) + >= 4) + | (ABS((i2_p_mv3 - i2_q_mv3)) + >= i4_ver_mvlimit)); + + u4_bs_temp2 = + ((ABS((i2_p_mv0 - i2_q_mv2)) + >= 4) + | (ABS((i2_p_mv1 - i2_q_mv3)) + >= i4_ver_mvlimit) + | (ABS((i2_p_mv2 - i2_q_mv0)) + >= 4) + | (ABS((i2_p_mv3 - i2_q_mv1)) + >= i4_ver_mvlimit)); + + u4_bs = ((pv_cur_pic_addr0 != pv_nbr_pic_addr0) + || (pv_cur_pic_addr1 != pv_nbr_pic_addr1) + || u4_bs_temp1) + && ((pv_cur_pic_addr0 != pv_nbr_pic_addr1) + || (pv_cur_pic_addr1 + != pv_nbr_pic_addr0) + || u4_bs_temp2); + + u4_bs_horz |= (u4_bs << u4_idx); + } + } + pu4_bs_table[0] = u4_bs_horz; + + /***********************************/ + /* Computing Bs for the left edge */ + /***********************************/ + for(i = 0; i < 4; i++, ps_leftmost_mv_pred += 4) + { + UWORD32 u4_idx = 24 - (i << 3); + + /*********************************/ + /* check if Bs is already set */ + /*********************************/ + if(!((u4_bs_vert >> u4_idx) & 0xf)) + { + /****************************************************/ + /* If Bs is not set, evalaute conditions for Bs=1 */ + /****************************************************/ + UWORD32 u4_bs_temp1, u4_bs_temp2; + UWORD32 u4_bs; + /*********************************************************/ + /* If any motion vector component differs by more than 1 */ + /* integer pel or if reference pictures are different Bs */ + /* is set to 1. Note that this condition shall be met for*/ + /* both (fwd-fwd,bwd-bwd) and (fwd-bwd,bwd-fwd) direction*/ + /*********************************************************/ + + i2_p_mv0 = ps_leftmost_mv_pred->i2_mv[0]; + i2_p_mv1 = ps_leftmost_mv_pred->i2_mv[1]; + i2_p_mv2 = ps_leftmost_mv_pred->i2_mv[2]; + i2_p_mv3 = ps_leftmost_mv_pred->i2_mv[3]; + pv_nbr_pic_addr0 = ps_left_addr->u4_add[i & 2]; + pv_nbr_pic_addr1 = ps_left_addr->u4_add[1 + (i & 2)]; + + u4_bs_temp1 = + ((ABS((i2_p_mv0 - i2_q_mv0)) + >= 4) + | (ABS((i2_p_mv1 - i2_q_mv1)) + >= i4_ver_mvlimit) + | (ABS((i2_p_mv2 - i2_q_mv2)) + >= 4) + | (ABS((i2_p_mv3 - i2_q_mv3)) + >= i4_ver_mvlimit)); + + u4_bs_temp2 = + ((ABS((i2_p_mv0 - i2_q_mv2)) + >= 4) + | (ABS((i2_p_mv1 - i2_q_mv3)) + >= i4_ver_mvlimit) + | (ABS((i2_p_mv2 - i2_q_mv0)) + >= 4) + | (ABS((i2_p_mv3 - i2_q_mv1)) + >= i4_ver_mvlimit)); + + u4_bs = ((pv_cur_pic_addr0 != pv_nbr_pic_addr0) + || (pv_cur_pic_addr1 != pv_nbr_pic_addr1) + || u4_bs_temp1) + && ((pv_cur_pic_addr0 != pv_nbr_pic_addr1) + || (pv_cur_pic_addr1 + != pv_nbr_pic_addr0) + || u4_bs_temp2); + + u4_bs_vert |= (u4_bs << u4_idx); + } + } + pu4_bs_table[4] = u4_bs_vert; + + return; +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_fill_bs1_non16x16mb_bslice */ +/* */ +/* Description : This function fills boundray strength (=1) for those */ +/* horz and vert edges of non16x16mb which are set to 0 by */ +/* ih264d_fill_bs2_horz_vert. This function is used for b slices */ +/* */ +/* Inputs : <What inputs does the function take?> */ +/* Globals : <Does it use any global variables?> */ +/* Processing : If any motion vector component of adjacent 4x4 blocks */ +/* differs by more than 1 integer pel or if reference */ +/* pictures are different, Bs is set to 1. */ +/* */ +/* Outputs : <What does the function produce?> */ +/* Returns : <What does the function return?> */ +/* */ +/* Issues : <List any issues or problems with this function> */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 16 10 2008 Jay Draft */ +/* */ +/*****************************************************************************/ +void ih264d_fill_bs1_non16x16mb_bslice(mv_pred_t *ps_cur_mv_pred, + mv_pred_t *ps_top_mv_pred, + void **ppv_map_ref_idx_to_poc, + UWORD32 *pu4_bs_table, /* pointer to the BsTable array */ + mv_pred_t *ps_leftmost_mv_pred, + neighbouradd_t *ps_left_addr, + void **u4_pic_addrress, + WORD32 i4_ver_mvlimit) +{ + UWORD32 edge; + void **ppv_map_ref_idx_to_poc_l0, **ppv_map_ref_idx_to_poc_l1; + ppv_map_ref_idx_to_poc_l0 = ppv_map_ref_idx_to_poc; + ppv_map_ref_idx_to_poc_l1 = ppv_map_ref_idx_to_poc + POC_LIST_L0_TO_L1_DIFF; + + PROFILE_DISABLE_BOUNDARY_STRENGTH() + + for(edge = 0; edge < 4; edge++, ps_top_mv_pred = ps_cur_mv_pred - 4) + { + /*********************************************************************/ + /* Each iteration of this loop fills the four BS values of one HORIZ */ + /* edge and one BS value for each of the four VERT edges. */ + /*********************************************************************/ + WORD32 i; + UWORD32 u4_vert_idx = 24 - (edge << 3); + UWORD32 u4_bs_horz = pu4_bs_table[edge]; + mv_pred_t *ps_left_mv_pred = ps_leftmost_mv_pred + (edge << 2); + + for(i = 0; i < 4; i++, ps_top_mv_pred++, ps_cur_mv_pred++) + { + WORD16 i2_cur_mv0, i2_cur_mv1, i16_curMv2, i16_curMv3; + WORD8 i1_cur_ref0, i1_cur_ref1; + void *pv_cur_pic_addr0, *pv_cur_pic_addr1; + void *pv_nbr_pic_addr0, *pv_nbr_pic_addr1; + + /******************************************************/ + /* Each iteration of this inner loop computes a HORIZ */ + /* and a VERT BS value for a 4x4 block */ + /******************************************************/ + UWORD32 u4_bs_vert = (pu4_bs_table[i + 4] >> u4_vert_idx) & 0xf; + UWORD32 u4_horz_idx = 24 - (i << 3); + + /*****************************************************/ + /* check if vert Bs for this block is already set */ + /*****************************************************/ + if(!u4_bs_vert) + { + WORD16 i2_left_mv0, i2_left_mv1, i2_left_mv2, i2_left_mv3; + /************************************************************/ + /* If Bs is not set, use left edge and current edge mvs and */ + /* reference pictures addresses to evaluate Bs==1 */ + /************************************************************/ + i2_left_mv0 = ps_left_mv_pred->i2_mv[0]; + i2_left_mv1 = ps_left_mv_pred->i2_mv[1]; + i2_left_mv2 = ps_left_mv_pred->i2_mv[2]; + i2_left_mv3 = ps_left_mv_pred->i2_mv[3]; + + i2_cur_mv0 = ps_cur_mv_pred->i2_mv[0]; + i2_cur_mv1 = ps_cur_mv_pred->i2_mv[1]; + i16_curMv2 = ps_cur_mv_pred->i2_mv[2]; + i16_curMv3 = ps_cur_mv_pred->i2_mv[3]; + i1_cur_ref0 = ps_cur_mv_pred->i1_ref_frame[0]; + i1_cur_ref1 = ps_cur_mv_pred->i1_ref_frame[1]; + pv_cur_pic_addr0 = ppv_map_ref_idx_to_poc_l0[i1_cur_ref0]; + pv_cur_pic_addr1 = ppv_map_ref_idx_to_poc_l1[i1_cur_ref1]; + + if(i) + { + WORD8 i1_left_ref0, i1_left_ref1; + i1_left_ref0 = ps_left_mv_pred->i1_ref_frame[0]; + i1_left_ref1 = ps_left_mv_pred->i1_ref_frame[1]; + pv_nbr_pic_addr0 = ppv_map_ref_idx_to_poc_l0[i1_left_ref0]; + pv_nbr_pic_addr1 = ppv_map_ref_idx_to_poc_l1[i1_left_ref1]; + } + else + { + pv_nbr_pic_addr0 = ps_left_addr->u4_add[edge & 2]; + pv_nbr_pic_addr1 = ps_left_addr->u4_add[1 + (edge & 2)]; + } + + { + UWORD32 u4_bs_temp1, u4_bs_temp2; + /*********************************************************/ + /* If any motion vector component differs by more than 1 */ + /* integer pel or if reference pictures are different Bs */ + /* is set to 1. Note that this condition shall be met for*/ + /* both (fwd-fwd,bwd-bwd) and (fwd-bwd,bwd-fwd) direction*/ + /*********************************************************/ + + u4_bs_temp1 = + ((ABS((i2_left_mv0 - i2_cur_mv0)) + >= 4) + | (ABS((i2_left_mv1 + - i2_cur_mv1)) + >= i4_ver_mvlimit) + | (ABS((i2_left_mv2 + - i16_curMv2)) + >= 4) + | (ABS((i2_left_mv3 + - i16_curMv3)) + >= i4_ver_mvlimit)); + + u4_bs_temp2 = + ((ABS((i2_left_mv0 - i16_curMv2)) + >= 4) + | (ABS((i2_left_mv1 + - i16_curMv3)) + >= i4_ver_mvlimit) + | (ABS((i2_left_mv2 + - i2_cur_mv0)) + >= 4) + | (ABS((i2_left_mv3 + - i2_cur_mv1)) + >= i4_ver_mvlimit)); + + u4_bs_vert = + ((pv_nbr_pic_addr0 != pv_cur_pic_addr0) + || (pv_nbr_pic_addr1 + != pv_cur_pic_addr1) + || u4_bs_temp1) + && ((pv_nbr_pic_addr0 + != pv_cur_pic_addr1) + || (pv_nbr_pic_addr1 + != pv_cur_pic_addr0) + || u4_bs_temp2); + + pu4_bs_table[i + 4] |= (u4_bs_vert << u4_vert_idx); + } + } + + /*****************************************************/ + /* check if horz Bs for this block is already set */ + /*****************************************************/ + if(!((u4_bs_horz >> u4_horz_idx) & 0xf)) + { + WORD16 i2_top_mv0, i2_top_mv1, i16_topMv2, i16_topMv3; + /************************************************************/ + /* If Bs is not set, use top edge and current edge mvs and */ + /* reference pictures addresses to evaluate Bs==1 */ + /************************************************************/ + i2_cur_mv0 = ps_cur_mv_pred->i2_mv[0]; + i2_cur_mv1 = ps_cur_mv_pred->i2_mv[1]; + i16_curMv2 = ps_cur_mv_pred->i2_mv[2]; + i16_curMv3 = ps_cur_mv_pred->i2_mv[3]; + i1_cur_ref0 = ps_cur_mv_pred->i1_ref_frame[0]; + i1_cur_ref1 = ps_cur_mv_pred->i1_ref_frame[1]; + + i2_top_mv0 = ps_top_mv_pred->i2_mv[0]; + i2_top_mv1 = ps_top_mv_pred->i2_mv[1]; + i16_topMv2 = ps_top_mv_pred->i2_mv[2]; + i16_topMv3 = ps_top_mv_pred->i2_mv[3]; + pv_cur_pic_addr0 = ppv_map_ref_idx_to_poc_l0[i1_cur_ref0]; + pv_cur_pic_addr1 = ppv_map_ref_idx_to_poc_l1[i1_cur_ref1]; + if(edge) + { + WORD8 i1_top_ref0, i1_top_ref1; + i1_top_ref0 = ps_top_mv_pred->i1_ref_frame[0]; + i1_top_ref1 = ps_top_mv_pred->i1_ref_frame[1]; + pv_nbr_pic_addr0 = ppv_map_ref_idx_to_poc_l0[i1_top_ref0]; + pv_nbr_pic_addr1 = ppv_map_ref_idx_to_poc_l1[i1_top_ref1]; + } + else + { + pv_nbr_pic_addr0 = u4_pic_addrress[i & 2]; + pv_nbr_pic_addr1 = u4_pic_addrress[1 + (i & 2)]; + } + + { + UWORD32 u4_bs_temp1, u4_bs_temp2; + UWORD32 u4_bs; + /*********************************************************/ + /* If any motion vector component differs by more than 1 */ + /* integer pel or if reference pictures are different Bs */ + /* is set to 1. Note that this condition shall be met for*/ + /* both (fwd-fwd,bwd-bwd) and (fwd-bwd,bwd-fwd) direction*/ + /*********************************************************/ + + u4_bs_temp1 = + ((ABS((i2_top_mv0 - i2_cur_mv0)) + >= 4) + | (ABS((i2_top_mv1 + - i2_cur_mv1)) + >= i4_ver_mvlimit) + | (ABS((i16_topMv2 + - i16_curMv2)) + >= 4) + | (ABS((i16_topMv3 + - i16_curMv3)) + >= i4_ver_mvlimit)); + + u4_bs_temp2 = + ((ABS((i2_top_mv0 - i16_curMv2)) + >= 4) + | (ABS((i2_top_mv1 + - i16_curMv3)) + >= i4_ver_mvlimit) + | (ABS((i16_topMv2 + - i2_cur_mv0)) + >= 4) + | (ABS((i16_topMv3 + - i2_cur_mv1)) + >= i4_ver_mvlimit)); + + u4_bs = + ((pv_nbr_pic_addr0 != pv_cur_pic_addr0) + || (pv_nbr_pic_addr1 + != pv_cur_pic_addr1) + || u4_bs_temp1) + && ((pv_nbr_pic_addr0 + != pv_cur_pic_addr1) + || (pv_nbr_pic_addr1 + != pv_cur_pic_addr0) + || u4_bs_temp2); + + u4_bs_horz |= (u4_bs << u4_horz_idx); + } + } + + ps_left_mv_pred = ps_cur_mv_pred; + } + + pu4_bs_table[edge] = u4_bs_horz; + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_fill_bs_xtra_left_edge_cur_fld */ +/* */ +/* Description : This function fills boundray strength (= 2 or 1) for */ +/* xtra left mb edge when cur mb is field and left mb is */ +/* frame. */ +/* Inputs : */ +/* */ +/* Globals : <Does it use any global variables?> */ +/* Processing : */ +/* */ +/* */ +/* Outputs : <What does the function produce?> */ +/* Returns : <What does the function return?> */ +/* */ +/* Issues : <List any issues or problems with this function> */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 16 10 2008 Jay Draft */ +/* */ +/*****************************************************************************/ +void ih264d_fill_bs_xtra_left_edge_cur_fld(UWORD32 *pu4_bs, /* Base pointer of BS table */ + WORD32 u4_left_mb_t_csbp, /* left mbpair's top csbp */ + WORD32 u4_left_mb_b_csbp, /* left mbpair's bottom csbp*/ + WORD32 u4_cur_mb_csbp, /* csbp of current mb */ + UWORD32 u4_cur_mb_top /* is top or bottom mb */ + + ) +{ + const UWORD32 *pu4_packed_bs = (const UWORD32 *)gau4_ih264d_packed_bs2; + UWORD32 u4_cur, u4_left, u4_or; + UNUSED(u4_cur_mb_top); + + PROFILE_DISABLE_BOUNDARY_STRENGTH() + + u4_left_mb_t_csbp = ((u4_left_mb_t_csbp & 0x0008) >> 3) + + ((u4_left_mb_t_csbp & 0x0080) >> 6) + + ((u4_left_mb_t_csbp & 0x0800) >> 9) + + ((u4_left_mb_t_csbp & 0x8000) >> 12); + + u4_left_mb_b_csbp = ((u4_left_mb_b_csbp & 0x0008) << 1) + + ((u4_left_mb_b_csbp & 0x0080) >> 2) + + ((u4_left_mb_b_csbp & 0x0800) >> 5) + + ((u4_left_mb_b_csbp & 0x8000) >> 8); + + /*********************************************************************/ + /* u4_cur = 0|0|0|0|0|0|0|0|12C|12C|8C|8C|4C|4C|0C|0C */ + /*********************************************************************/ + u4_cur = (u4_cur_mb_csbp & 0x0001) + ((u4_cur_mb_csbp & 0x0001) << 1) + + ((u4_cur_mb_csbp & 0x0010) >> 2) + + ((u4_cur_mb_csbp & 0x0010) >> 1) + + ((u4_cur_mb_csbp & 0x0100) >> 4) + + ((u4_cur_mb_csbp & 0x0100) >> 3) + + ((u4_cur_mb_csbp & 0x1000) >> 6) + + ((u4_cur_mb_csbp & 0x1000) >> 5); + + /*********************************************************************/ + /* u4_left =0|0|0|0|0|0|0|0|15Lb|11Lb|7Lb|3Lb|15Lt|11Lt|7Lt|3Lt */ + /*********************************************************************/ + u4_left = u4_left_mb_t_csbp + u4_left_mb_b_csbp; + + u4_or = (u4_cur | u4_left); + /*********************************************************************/ + /* Fill vert edges (4,9) boundary strengths using look up table */ + /*********************************************************************/ + pu4_packed_bs += 16; + pu4_bs[4] = pu4_packed_bs[u4_or & 0xF]; + pu4_bs[9] = pu4_packed_bs[(u4_or >> 4)]; +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_fill_bs_xtra_left_edge_cur_frm */ +/* */ +/* Description : This function fills boundray strength (= 2 or 1) for */ +/* xtra left mb edge when cur mb is frame and left mb is */ +/* field. */ +/* Inputs : */ +/* */ +/* Globals : <Does it use any global variables?> */ +/* Processing : */ +/* */ +/* */ +/* Outputs : <What does the function produce?> */ +/* Returns : <What does the function return?> */ +/* */ +/* Issues : <List any issues or problems with this function> */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 16 10 2008 Jay Draft */ +/* */ +/*****************************************************************************/ +void ih264d_fill_bs_xtra_left_edge_cur_frm(UWORD32 *pu4_bs, /* Base pointer of BS table */ + WORD32 u4_left_mb_t_csbp, /* left mbpair's top csbp */ + WORD32 u4_left_mb_b_csbp, /* left mbpair's bottom csbp*/ + WORD32 u4_cur_mb_csbp, /* csbp of current mb */ + UWORD32 u4_cur_mb_bot /* is top or bottom mb */ + + ) +{ + const UWORD32 *pu4_packed_bs = (const UWORD32 *)gau4_ih264d_packed_bs2; + UWORD32 u4_cur, u4_left, u4_or; + UWORD32 u4_right_shift = (u4_cur_mb_bot << 3); + + PROFILE_DISABLE_BOUNDARY_STRENGTH() + + u4_left_mb_t_csbp >>= u4_right_shift; + u4_left_mb_b_csbp >>= u4_right_shift; + + u4_left_mb_t_csbp = ((u4_left_mb_t_csbp & 0x08) >> 3) + + ((u4_left_mb_t_csbp & 0x08) >> 2) + + ((u4_left_mb_t_csbp & 0x80) >> 5) + + ((u4_left_mb_t_csbp & 0x80) >> 4); + + u4_left_mb_b_csbp = ((u4_left_mb_b_csbp & 0x08) << 1) + + ((u4_left_mb_b_csbp & 0x08) << 2) + + ((u4_left_mb_b_csbp & 0x80) >> 1) + + ((u4_left_mb_b_csbp & 0x80)); + + u4_cur = ((u4_cur_mb_csbp & 0x0001)) + ((u4_cur_mb_csbp & 0x0010) >> 3) + + ((u4_cur_mb_csbp & 0x0100) >> 6) + + ((u4_cur_mb_csbp & 0x1000) >> 9); + + u4_cur += (u4_cur << 4); + + u4_left = u4_left_mb_t_csbp + u4_left_mb_b_csbp; + + u4_or = (u4_cur | u4_left); + /*********************************************************************/ + /* Fill vert edges (4,9) boundary strengths using look up table */ + /*********************************************************************/ + pu4_packed_bs += 16; + pu4_bs[4] = pu4_packed_bs[u4_or & 0xF]; + pu4_bs[9] = pu4_packed_bs[(u4_or >> 4)]; +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_fill_bs_xtra_top_edge */ +/* */ +/* Description : This function fills boundray strength (= 2 or 1) for */ +/* xtra top mb edge when cur mb is top mb of frame mb pair */ +/* and top mbpair is field coded. */ +/* Inputs : */ +/* */ +/* Globals : <Does it use any global variables?> */ +/* Processing : */ +/* */ +/* */ +/* Outputs : <What does the function produce?> */ +/* Returns : <What does the function return?> */ +/* */ +/* Issues : <List any issues or problems with this function> */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 16 10 2008 Jay Draft */ +/* */ +/*****************************************************************************/ +void ih264d_fill_bs_xtra_top_edge(UWORD32 *pu4_bs, /* Base pointer of BS table */ + WORD32 u4_topmb_t_csbp, /* top mbpair's top csbp */ + WORD32 u4_topmb_b_csbp, /* top mbpair's bottom csbp*/ + WORD32 u4_cur_mb_csbp /* csbp of current mb */ + + ) +{ + const UWORD32 *pu4_packed_bs = (const UWORD32 *)gau4_ih264d_packed_bs2; + UWORD32 u4_or; + + u4_cur_mb_csbp &= 0xf; + u4_topmb_t_csbp >>= 12; + u4_topmb_b_csbp >>= 12; + + u4_or = (u4_cur_mb_csbp | u4_topmb_t_csbp); + /*********************************************************************/ + /* Fill vert edges (0,8) boundary strengths using look up table */ + /*********************************************************************/ + pu4_packed_bs += 16; + pu4_bs[8] = pu4_packed_bs[u4_or]; + + u4_or = (u4_cur_mb_csbp | u4_topmb_b_csbp); + pu4_bs[0] = pu4_packed_bs[u4_or]; +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_compute_bs_non_mbaff */ +/* */ +/* Description : This function computes the pointers of left,top & current*/ +/* : Nnz, MvPred & deblk_mb_t and supplies to FillBs function for*/ +/* : Boundary Strength Calculation */ +/* Inputs : <What inputs does the function take?> */ +/* Processing : This functions calls deblock MB in the MB increment order*/ +/* */ +/* Outputs : Produces the Boundary Strength for Current Mb */ +/* Returns : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* ITTIAM */ +/*****************************************************************************/ + +void ih264d_compute_bs_non_mbaff(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info, + const UWORD16 u2_mbxn_mb) +{ + /* Mvpred and Nnz for top and Courrent */ + mv_pred_t *ps_cur_mv_pred, *ps_top_mv_pred = NULL, *ps_left_mv_pred; + /* deblk_mb_t Params */ + deblk_mb_t *ps_cur_mb_params; /*< Parameters of current MacroBlock */ + deblkmb_neighbour_t *ps_deblk_top_mb; + + /* Reference Index to POC mapping*/ + void ** apv_map_ref_idx_to_poc; + UWORD32 u4_leftmbtype; + + UWORD16 u2_left_csbp, u2_top_csbp, u2_cur_csbp; + + /* Set of flags */ + UWORD32 u4_cur_mb_intra, u1_top_mb_typ, u4_cur_mb_fld; + UWORD32 u1_cur_mb_type; + UWORD32 * pu4_bs_table; + + /* Neighbour availability */ + /* Initialization */ + const UWORD32 u2_mbx = ps_cur_mb_info->u2_mbx; + const UWORD32 u2_mby = ps_cur_mb_info->u2_mby; + const UWORD32 u1_pingpong = u2_mbx & 0x01; + + PROFILE_DISABLE_BOUNDARY_STRENGTH() + + ps_deblk_top_mb = ps_dec->ps_deblk_top_mb + u2_mbx; + + + /* Pointer assignment for Current DeblkMB, Current Mv Pred */ + ps_cur_mb_params = ps_dec->ps_deblk_mbn + u2_mbxn_mb; + ps_cur_mv_pred = ps_dec->ps_mv_cur + (u2_mbxn_mb << 4); + + apv_map_ref_idx_to_poc = ps_dec->ppv_map_ref_idx_to_poc + 1; + u1_cur_mb_type = ps_cur_mb_params->u1_mb_type; + u1_top_mb_typ = ps_deblk_top_mb->u1_mb_type; + ps_deblk_top_mb->u1_mb_type = u1_cur_mb_type; + + { + UWORD8 mb_qp_temp; + + ps_cur_mb_params->u1_topmb_qp = ps_deblk_top_mb->u1_mb_qp; + ps_deblk_top_mb->u1_mb_qp = ps_cur_mb_params->u1_mb_qp; + + ps_cur_mb_params->u1_left_mb_qp = ps_dec->deblk_left_mb[1].u1_mb_qp; + ps_dec->deblk_left_mb[1].u1_mb_qp = ps_cur_mb_params->u1_mb_qp; + + } + + /* if no deblocking required for current Mb then continue */ + /* Check next Mbs in Mb group */ + if(ps_cur_mb_params->u1_deblocking_mode & MB_DISABLE_FILTERING) + { + void ** pu4_map_ref_idx_to_poc_l1 = apv_map_ref_idx_to_poc + + POC_LIST_L0_TO_L1_DIFF; + { + /* Store Parameter for Top MvPred refernce frame Address */ + + void ** ppv_top_mv_pred_addr = ps_cur_mb_info->ps_curmb->u4_pic_addrress; + WORD8 * p1_refTop0 = (ps_cur_mv_pred + 12)->i1_ref_frame; + WORD8 * p1_refTop1 = (ps_cur_mv_pred + 14)->i1_ref_frame; + + /* Store Left addresses for Next Mb */ + void ** ppv_left_mv_pred_addr = + ps_dec->ps_left_mvpred_addr[!u1_pingpong][1].u4_add; + WORD8 * p1_refleft0 = (ps_cur_mv_pred + 3)->i1_ref_frame; + + + ppv_top_mv_pred_addr[0] = apv_map_ref_idx_to_poc[p1_refTop0[0]]; + ppv_top_mv_pred_addr[1] = pu4_map_ref_idx_to_poc_l1[p1_refTop0[1]]; + + ppv_left_mv_pred_addr[2] = apv_map_ref_idx_to_poc[p1_refTop1[0]]; + ppv_top_mv_pred_addr[2] = apv_map_ref_idx_to_poc[p1_refTop1[0]]; + ppv_left_mv_pred_addr[3] = pu4_map_ref_idx_to_poc_l1[p1_refTop1[1]]; + ppv_top_mv_pred_addr[3] = pu4_map_ref_idx_to_poc_l1[p1_refTop1[1]]; + + ppv_left_mv_pred_addr[0] = apv_map_ref_idx_to_poc[p1_refleft0[0]]; + ppv_left_mv_pred_addr[1] = pu4_map_ref_idx_to_poc_l1[p1_refleft0[1]]; + //} + /* Storing the leftMbtype for next Mb */ + ps_dec->deblk_left_mb[1].u1_mb_type = ps_cur_mb_params->u1_mb_type; + } + + return; + } + + /* Flag for extra left Edge */ + ps_cur_mb_params->u1_single_call = 1; + + /* Update the Left deblk_mb_t and Left MvPred Parameters */ + if(!u2_mbx) + { + u4_leftmbtype = 0; + + /* Initialize the ps_left_mv_pred with Junk but Valid Location */ + /* to avoid invalid memory access */ + /* this is read only pointer */ + ps_left_mv_pred = ps_dec->ps_mv_cur + 3; + } + else + { + u4_leftmbtype = ps_dec->deblk_left_mb[1].u1_mb_type; + + /* Come to Left Most Edge of the MB */ + ps_left_mv_pred = (u2_mbxn_mb) ? + ps_dec->ps_mv_cur + ((u2_mbxn_mb - 1) << 4) + 3 : + ps_dec->ps_mv_left + 3; + } + + if(!u2_mby) + u1_top_mb_typ = 0; + + /* MvPred Pointer Calculation */ + /* CHANGED CODE */ + ps_top_mv_pred = ps_cur_mv_pred - (ps_dec->u2_frm_wd_in_mbs << 4) + 12; + + u4_cur_mb_intra = u1_cur_mb_type & D_INTRA_MB; + u4_cur_mb_fld = !!(u1_cur_mb_type & D_FLD_MB); + /* Compute BS function */ + pu4_bs_table = ps_cur_mb_params->u4_bs_table; + + u2_cur_csbp = ps_cur_mb_info->ps_curmb->u2_luma_csbp; + u2_left_csbp = ps_cur_mb_info->ps_left_mb->u2_luma_csbp; + u2_top_csbp = ps_cur_mb_info->ps_top_mb->u2_luma_csbp; + /* Compute BS function */ + if(ps_dec->ps_cur_sps->u1_profile_idc == HIGH_PROFILE_IDC) + { + if(ps_cur_mb_info->u1_tran_form8x8 == 1) + { + u2_cur_csbp = ih264d_update_csbp_8x8( + ps_cur_mb_info->ps_curmb->u2_luma_csbp); + } + + if(ps_cur_mb_info->ps_left_mb->u1_tran_form8x8 == 1) + { + u2_left_csbp = ih264d_update_csbp_8x8( + ps_cur_mb_info->ps_left_mb->u2_luma_csbp); + } + + if(ps_cur_mb_info->ps_top_mb->u1_tran_form8x8 == 1) + { + u2_top_csbp = ih264d_update_csbp_8x8( + ps_cur_mb_info->ps_top_mb->u2_luma_csbp); + } + } + if(u4_cur_mb_intra) + { + + pu4_bs_table[4] = 0x04040404; + pu4_bs_table[0] = u4_cur_mb_fld ? 0x03030303 : 0x04040404; + pu4_bs_table[1] = 0x03030303; + pu4_bs_table[2] = 0x03030303; + pu4_bs_table[3] = 0x03030303; + pu4_bs_table[5] = 0x03030303; + pu4_bs_table[6] = 0x03030303; + pu4_bs_table[7] = 0x03030303; + } + else + { + UWORD32 u4_is_non16x16 = !!(u1_cur_mb_type & D_PRED_NON_16x16); + UWORD32 u4_is_b = ps_dec->u1_B; + + ih264d_fill_bs2_horz_vert( + pu4_bs_table, u2_left_csbp, u2_top_csbp, u2_cur_csbp, + (const UWORD32 *)(gau4_ih264d_packed_bs2), + (const UWORD16 *)(gau2_ih264d_4x4_v2h_reorder)); + + if(u4_leftmbtype & D_INTRA_MB) + pu4_bs_table[4] = 0x04040404; + + if(u1_top_mb_typ & D_INTRA_MB) + pu4_bs_table[0] = u4_cur_mb_fld ? 0x03030303 : 0x04040404; + + ps_dec->pf_fill_bs1[u4_is_b][u4_is_non16x16]( + ps_cur_mv_pred, ps_top_mv_pred, apv_map_ref_idx_to_poc, + pu4_bs_table, ps_left_mv_pred, + &(ps_dec->ps_left_mvpred_addr[u1_pingpong][1]), + ps_cur_mb_info->ps_top_mb->u4_pic_addrress, + (4 >> u4_cur_mb_fld)); + } + + { + void ** pu4_map_ref_idx_to_poc_l1 = apv_map_ref_idx_to_poc + + POC_LIST_L0_TO_L1_DIFF; + { + /* Store Parameter for Top MvPred refernce frame Address */ + + void ** ppv_top_mv_pred_addr = ps_cur_mb_info->ps_curmb->u4_pic_addrress; + WORD8 * p1_refTop0 = (ps_cur_mv_pred + 12)->i1_ref_frame; + WORD8 * p1_refTop1 = (ps_cur_mv_pred + 14)->i1_ref_frame; + + /* Store Left addresses for Next Mb */ + void ** ppv_left_mv_pred_addr = + ps_dec->ps_left_mvpred_addr[!u1_pingpong][1].u4_add; + WORD8 * p1_refleft0 = (ps_cur_mv_pred + 3)->i1_ref_frame; + + ppv_top_mv_pred_addr[0] = apv_map_ref_idx_to_poc[p1_refTop0[0]]; + ppv_top_mv_pred_addr[1] = pu4_map_ref_idx_to_poc_l1[p1_refTop0[1]]; + + ppv_left_mv_pred_addr[2] = apv_map_ref_idx_to_poc[p1_refTop1[0]]; + ppv_top_mv_pred_addr[2] = apv_map_ref_idx_to_poc[p1_refTop1[0]]; + ppv_left_mv_pred_addr[3] = pu4_map_ref_idx_to_poc_l1[p1_refTop1[1]]; + ppv_top_mv_pred_addr[3] = pu4_map_ref_idx_to_poc_l1[p1_refTop1[1]]; + + ppv_left_mv_pred_addr[0] = apv_map_ref_idx_to_poc[p1_refleft0[0]]; + ppv_left_mv_pred_addr[1] = pu4_map_ref_idx_to_poc_l1[p1_refleft0[1]]; + + /* Storing the leftMbtype for next Mb */ + ps_dec->deblk_left_mb[1].u1_mb_type = ps_cur_mb_params->u1_mb_type; + + } + } + + /* For transform 8x8 disable deblocking of the intrernal edges of a 8x8 block */ + if(ps_cur_mb_info->u1_tran_form8x8) + { + pu4_bs_table[1] = 0; + pu4_bs_table[3] = 0; + pu4_bs_table[5] = 0; + pu4_bs_table[7] = 0; + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_compute_bs_mbaff */ +/* */ +/* Description : This function computes the pointers of left,top & current*/ +/* : Nnz, MvPred & deblk_mb_t and supplies to FillBs function for*/ +/* : Boundary Strength Calculation */ +/* Inputs : <What inputs does the function take?> */ +/* Processing : This functions calls deblock MB in the MB increment order*/ +/* */ +/* Outputs : Produces the Boundary Strength for Current Mb */ +/* Returns : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* ITTIAM */ +/*****************************************************************************/ + +void ih264d_compute_bs_mbaff(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info, + const UWORD16 u2_mbxn_mb) +{ + /* Mvpred and Nnz for top and Courrent */ + mv_pred_t *ps_cur_mv_pred, *ps_top_mv_pred = NULL, *ps_left_mv_pred; + /* deblk_mb_t Params */ + deblk_mb_t *ps_cur_mb_params; /*< Parameters of current MacroBlock */ + neighbouradd_t * ps_left_ngbr; + deblkmb_neighbour_t *ps_deblk_top_mb; + /* Reference Index to POC mapping*/ + void ** apv_map_ref_idx_to_poc; + + UWORD32 u4_leftmbtype; + + + UWORD16 u2_left_csbp, u2_top_csbp, u2_cur_csbp; + + /* Set of flags */ + UWORD32 u4_cur_mb_intra, u4_cur_mb_fld, u4_top_mb_fld, u1_top_mb_typ, u4_left_mb_fld; + UWORD32 u1_cur_mb_type; + UWORD32 * pu4_bs_table; + const UWORD32 u4_bot_mb = (1 - ps_cur_mb_info->u1_topmb); + /* Initialization */ + const UWORD32 u2_mbx = ps_cur_mb_info->u2_mbx; + const UWORD32 u2_mby = ps_cur_mb_info->u2_mby; + /* Load From u1_pingpong and Store in !u1_pingpong */ + const UWORD32 u1_pingpong = u2_mbx & 0x01; + + PROFILE_DISABLE_BOUNDARY_STRENGTH() + + ps_deblk_top_mb = ps_dec->ps_deblk_top_mb + (u2_mbx << 1); + + + /************************************************/ + /* Initialize the left Mb type */ + /* Left MvPred */ + /************************************************/ + + if(!u2_mbx) + { + /************************************************************/ + /* Initialize the ps_left_mv_pred with Junk but Valid Location */ + /* to avoid invalid memory access */ + /* this is read only pointer */ + /************************************************************/ + ps_left_mv_pred = ps_dec->ps_mv_cur + 16; + } + else + { + /* Come to Left Most Edge of the MB */ + ps_left_mv_pred = (u2_mbxn_mb) ? + ps_dec->ps_mv_cur + ((u2_mbxn_mb - 1) << 5) + 3 : + ps_dec->ps_mv_left + 3; + + ps_left_mv_pred += (u4_bot_mb << 4); + } + + u4_leftmbtype = ps_dec->deblk_left_mb[u4_bot_mb].u1_mb_type; + + ps_left_ngbr = &(ps_dec->ps_left_mvpred_addr[u1_pingpong][u4_bot_mb]); + + /************************************************/ + /* Pointer Assignment for Current Mb Parameters */ + /* Pointer Assignment for Current MvPred */ + /************************************************/ + ps_cur_mb_params = ps_dec->ps_deblk_mbn + (u2_mbxn_mb << 1) + u4_bot_mb; + u1_cur_mb_type = ps_cur_mb_params->u1_mb_type; + + ps_cur_mv_pred = ps_dec->ps_mv_cur + (u2_mbxn_mb << 5); + ps_cur_mv_pred += (u4_bot_mb << 4); + + /********************************************/ + /* Pointer Assignment for Top Mb Parameters */ + /* Pointer Assignment for Top MvPred and */ + /* Pointer Assignment for Top Nnz */ + /********************************************/ + + /* CHANGED CODE */ + ps_top_mv_pred = ps_cur_mv_pred - (ps_dec->u2_frm_wd_in_mbs << 5) + 12; + + u4_cur_mb_fld = !!(u1_cur_mb_type & D_FLD_MB); + u4_left_mb_fld = !!(ps_dec->deblk_left_mb[0].u1_mb_type & D_FLD_MB); + + if(u4_left_mb_fld != u4_cur_mb_fld) + { + /* Flag for extra left Edge */ + ps_cur_mb_params->u1_single_call = 0; + + if(u4_bot_mb) + { + ps_left_ngbr--; + ps_left_mv_pred -= 16; + } + } + else + ps_cur_mb_params->u1_single_call = 1; + + apv_map_ref_idx_to_poc = ps_dec->ppv_map_ref_idx_to_poc + 1; + if(u4_cur_mb_fld) + { + if(u4_bot_mb) + { + apv_map_ref_idx_to_poc += BOT_LIST_FLD_L0; + } + else + { + apv_map_ref_idx_to_poc += TOP_LIST_FLD_L0; + } + } + + /**********************************************************/ + /* if no deblocking required for current Mb then continue */ + /**********************************************************/ + if(ps_cur_mb_params->u1_deblocking_mode & MB_DISABLE_FILTERING) + { + void ** pu4_map_ref_idx_to_poc_l1 = apv_map_ref_idx_to_poc + + POC_LIST_L0_TO_L1_DIFF; + + { + /* Store Parameter for Top MvPred refernce frame Address */ + + void ** ppv_top_mv_pred_addr = ps_cur_mb_info->ps_curmb->u4_pic_addrress; + void ** ppv_left_mv_pred_addr = + ps_dec->ps_left_mvpred_addr[!u1_pingpong][u4_bot_mb].u4_add; + WORD8 * p1_refTop0 = (ps_cur_mv_pred + 12)->i1_ref_frame; + WORD8 * p1_refTop1 = (ps_cur_mv_pred + 14)->i1_ref_frame; + WORD8 * p1_refLeft0 = (ps_cur_mv_pred + 3)->i1_ref_frame; + ppv_top_mv_pred_addr[0] = apv_map_ref_idx_to_poc[p1_refTop0[0]]; + ppv_top_mv_pred_addr[1] = pu4_map_ref_idx_to_poc_l1[p1_refTop0[1]]; + ppv_left_mv_pred_addr[2] = apv_map_ref_idx_to_poc[p1_refTop1[0]]; + ppv_top_mv_pred_addr[2] = apv_map_ref_idx_to_poc[p1_refTop1[0]]; + ppv_left_mv_pred_addr[3] = pu4_map_ref_idx_to_poc_l1[p1_refTop1[1]]; + ppv_top_mv_pred_addr[3] = pu4_map_ref_idx_to_poc_l1[p1_refTop1[1]]; + ppv_left_mv_pred_addr[0] = apv_map_ref_idx_to_poc[p1_refLeft0[0]]; + ppv_left_mv_pred_addr[1] = pu4_map_ref_idx_to_poc_l1[p1_refLeft0[1]]; + } + if(u4_bot_mb) + { + /* store The Left Mb Type*/ + ps_dec->deblk_left_mb[0].u1_mb_type = + (ps_cur_mb_params - 1)->u1_mb_type; + ps_dec->deblk_left_mb[1].u1_mb_type = ps_cur_mb_params->u1_mb_type; + + } + ps_deblk_top_mb[u4_bot_mb].u1_mb_type = u1_cur_mb_type; + return; + } + + if(u2_mby) + { + u1_top_mb_typ = ps_deblk_top_mb[1].u1_mb_type; + u4_top_mb_fld = !!(u1_top_mb_typ & D_FLD_MB); + + if(!u4_bot_mb) + { + if(u4_top_mb_fld & u4_cur_mb_fld) + u1_top_mb_typ = ps_deblk_top_mb[0].u1_mb_type; + else + { + ps_top_mv_pred += 16; + } + } + } + else + { + u4_top_mb_fld = u4_cur_mb_fld; + u1_top_mb_typ = 0; + } + + if(u4_bot_mb & !u4_cur_mb_fld) + { + u1_top_mb_typ = ps_deblk_top_mb[0].u1_mb_type; + u4_top_mb_fld = u4_cur_mb_fld; + ps_top_mv_pred = ps_cur_mv_pred - 4; + } + + pu4_bs_table = ps_cur_mb_params->u4_bs_table; + u4_cur_mb_intra = u1_cur_mb_type & D_INTRA_MB; + + u2_cur_csbp = ps_cur_mb_info->ps_curmb->u2_luma_csbp; + u2_left_csbp = ps_cur_mb_info->ps_left_mb->u2_luma_csbp; + u2_top_csbp = ps_cur_mb_info->ps_top_mb->u2_luma_csbp; + /* Compute BS function */ + if(ps_dec->ps_cur_sps->u1_profile_idc == HIGH_PROFILE_IDC) + { + + if(ps_cur_mb_info->u1_tran_form8x8 == 1) + { + u2_cur_csbp = ih264d_update_csbp_8x8( + ps_cur_mb_info->ps_curmb->u2_luma_csbp); + } + + if(ps_cur_mb_info->ps_left_mb->u1_tran_form8x8 == 1) + { + u2_left_csbp = ih264d_update_csbp_8x8( + ps_cur_mb_info->ps_left_mb->u2_luma_csbp); + } + + if(ps_cur_mb_info->ps_top_mb->u1_tran_form8x8 == 1) + { + u2_top_csbp = ih264d_update_csbp_8x8( + ps_cur_mb_info->ps_top_mb->u2_luma_csbp); + } + } + if(u4_cur_mb_intra) + { + + pu4_bs_table[4] = 0x04040404; + if((0 == u4_cur_mb_fld) && (0 == u4_top_mb_fld)) + { + pu4_bs_table[0] = 0x04040404; + } + else + { + pu4_bs_table[0] = 0x03030303; + } + + pu4_bs_table[1] = 0x03030303; + pu4_bs_table[2] = 0x03030303; + pu4_bs_table[3] = 0x03030303; + pu4_bs_table[5] = 0x03030303; + pu4_bs_table[6] = 0x03030303; + pu4_bs_table[7] = 0x03030303; + + /*********************************************************************/ + /* Fill Bs of xtra top and left edge unconditionally to avoid checks */ + /*********************************************************************/ + pu4_bs_table[8] = 0x03030303; + pu4_bs_table[9] = 0x04040404; + } + else + { + UWORD32 u4_is_non16x16 = !!(u1_cur_mb_type & D_PRED_NON_16x16); + UWORD32 u4_is_b = ps_dec->u1_B; + + ih264d_fill_bs2_horz_vert( + pu4_bs_table, u2_left_csbp, u2_top_csbp, u2_cur_csbp, + (const UWORD32 *)(gau4_ih264d_packed_bs2), + (const UWORD16 *)(gau2_ih264d_4x4_v2h_reorder)); + + if(u4_leftmbtype & D_INTRA_MB) + pu4_bs_table[4] = 0x04040404; + + if(u1_top_mb_typ & D_INTRA_MB) + pu4_bs_table[0] = u4_cur_mb_fld ? 0x03030303 : 0x04040404; + else if(u4_cur_mb_fld != u4_top_mb_fld) + { + /****************************************************/ + /* Setting BS for mixed mode edge=1 when (Bs!=2) */ + /****************************************************/ + pu4_bs_table[0] = (pu4_bs_table[0] >> 1) + 0x01010101; + } + + { + /* Call to Compute Boundary Strength for Extra Left Edge */ + if(u2_mbx + && !(ps_cur_mb_params->u1_deblocking_mode + & MB_DISABLE_LEFT_EDGE)) + { + if(u4_cur_mb_fld != u4_left_mb_fld) + { + UWORD32 u4_left_mb_t_csbp = + ps_cur_mb_info->ps_left_mb[0].u2_luma_csbp; + UWORD32 u4_left_mb_b_csbp = + ps_cur_mb_info->ps_left_mb[1].u2_luma_csbp; + if(1 == ps_cur_mb_info->ps_left_mb[0].u1_tran_form8x8) + { + u4_left_mb_t_csbp = (UWORD32)ih264d_update_csbp_8x8( + (UWORD16)u4_left_mb_t_csbp); + } + + if(1 == ps_cur_mb_info->ps_left_mb[1].u1_tran_form8x8) + { + u4_left_mb_b_csbp = (UWORD32)ih264d_update_csbp_8x8( + (UWORD16)u4_left_mb_b_csbp); + } + ps_dec->pf_fill_bs_xtra_left_edge[u4_cur_mb_fld]( + pu4_bs_table, u4_left_mb_t_csbp, + u4_left_mb_b_csbp, u2_cur_csbp, u4_bot_mb); + + if(ps_dec->deblk_left_mb[0].u1_mb_type & D_INTRA_MB) + pu4_bs_table[4] = 0x04040404; + + if(ps_dec->deblk_left_mb[1].u1_mb_type & D_INTRA_MB) + pu4_bs_table[9] = 0x04040404; + + } + } + /* Call to Compute Boundary Strength for Extra Top Edge */ + if(u2_mby + && !(ps_cur_mb_params->u1_deblocking_mode + & MB_DISABLE_TOP_EDGE)) + { + if((((!u4_bot_mb) & (!u4_cur_mb_fld)) && u4_top_mb_fld)) + { + UWORD32 u4_topmb_t_csbp = + ps_cur_mb_info->ps_top_mb[-1].u2_luma_csbp; + UWORD32 u4_topmb_b_csbp = + ps_cur_mb_info->ps_top_mb[0].u2_luma_csbp; + if(1 == ps_cur_mb_info->ps_top_mb[-1].u1_tran_form8x8) + { + u4_topmb_t_csbp = (UWORD32)ih264d_update_csbp_8x8( + (UWORD16)u4_topmb_t_csbp); + } + + if(1 == ps_cur_mb_info->ps_top_mb[0].u1_tran_form8x8) + { + u4_topmb_b_csbp = (UWORD32)ih264d_update_csbp_8x8( + (UWORD16)u4_topmb_b_csbp); + } + ih264d_fill_bs_xtra_top_edge(pu4_bs_table, u4_topmb_t_csbp, + u4_topmb_b_csbp, u2_cur_csbp); + + if(ps_deblk_top_mb[0].u1_mb_type & D_INTRA_MB) + pu4_bs_table[8] = 0x03030303; + + if(ps_deblk_top_mb[1].u1_mb_type & D_INTRA_MB) + pu4_bs_table[0] = 0x03030303; + } + } + } + + ps_dec->pf_fill_bs1[u4_is_b][u4_is_non16x16]( + ps_cur_mv_pred, ps_top_mv_pred, apv_map_ref_idx_to_poc, + pu4_bs_table, ps_left_mv_pred, ps_left_ngbr, + ps_cur_mb_info->ps_top_mb->u4_pic_addrress, + (4 >> u4_cur_mb_fld)); + } + + { + void ** pu4_map_ref_idx_to_poc_l1 = apv_map_ref_idx_to_poc + + POC_LIST_L0_TO_L1_DIFF; + + { + /* Store Parameter for Top MvPred refernce frame Address */ + void ** ppv_top_mv_pred_addr = ps_cur_mb_info->ps_curmb->u4_pic_addrress; + void ** ppv_left_mv_pred_addr = + ps_dec->ps_left_mvpred_addr[!u1_pingpong][u4_bot_mb].u4_add; + WORD8 * p1_refTop0 = (ps_cur_mv_pred + 12)->i1_ref_frame; + WORD8 * p1_refTop1 = (ps_cur_mv_pred + 14)->i1_ref_frame; + WORD8 * p1_refLeft0 = (ps_cur_mv_pred + 3)->i1_ref_frame; + ppv_top_mv_pred_addr[0] = apv_map_ref_idx_to_poc[p1_refTop0[0]]; + ppv_top_mv_pred_addr[1] = pu4_map_ref_idx_to_poc_l1[p1_refTop0[1]]; + ppv_left_mv_pred_addr[2] = apv_map_ref_idx_to_poc[p1_refTop1[0]]; + ppv_top_mv_pred_addr[2] = apv_map_ref_idx_to_poc[p1_refTop1[0]]; + ppv_left_mv_pred_addr[3] = pu4_map_ref_idx_to_poc_l1[p1_refTop1[1]]; + ppv_top_mv_pred_addr[3] = pu4_map_ref_idx_to_poc_l1[p1_refTop1[1]]; + ppv_left_mv_pred_addr[0] = apv_map_ref_idx_to_poc[p1_refLeft0[0]]; + ppv_left_mv_pred_addr[1] = pu4_map_ref_idx_to_poc_l1[p1_refLeft0[1]]; + } + if(u4_bot_mb) + { + /* store The Left Mb Type*/ + ps_dec->deblk_left_mb[0].u1_mb_type = + (ps_cur_mb_params - 1)->u1_mb_type; + ps_dec->deblk_left_mb[1].u1_mb_type = ps_cur_mb_params->u1_mb_type; + + } + ps_deblk_top_mb[u4_bot_mb].u1_mb_type = u1_cur_mb_type; + } + /* For transform 8x8 disable deblocking of the intrernal edges of a 8x8 block */ + if(ps_cur_mb_info->u1_tran_form8x8) + { + pu4_bs_table[1] = 0; + pu4_bs_table[3] = 0; + pu4_bs_table[5] = 0; + pu4_bs_table[7] = 0; + } + +} + + + +/*! + ************************************************************************** + * \if Function name : ih264d_fill_bs_for_mb \endif + * + * \brief + * Determines the boundary strength (Bs), for the complete MB. Bs is + * determined for each block boundary between two neighbouring 4x4 + * luma blocks, then packed in a UWORD32, first Bs placed in MSB and + * so on. Such packed Bs values for all 8 edges are kept in an array. + * + * \return + * Returns the packed boundary strength(Bs) MSB -> LSB Bs0|Bs1|Bs2|Bs3 + * + ************************************************************************** + */ + +void ih264d_fill_bs_for_mb(deblk_mb_t * ps_cur_mb_params, + deblk_mb_t * ps_top_mb_params, + deblk_mb_t * ps_left_mb_params, + mv_pred_t *ps_cur_mv_pred, + mv_pred_t *ps_top_mv_pred, + UWORD8 *puc_cur_nnz, + UWORD8 *puc_top_nnz, + void **ppv_map_ref_idx_to_poc, + UWORD32 ui_mbAff, + UWORD32 ui_bs_table[], /* pointer to the BsTable array */ + mv_pred_t *ps_leftmost_mv_pred, + neighbouradd_t *ps_left_addr, + neighbouradd_t *ps_top_add) +{ + UWORD32 u4_bs_horz = 0; + UWORD8 edge, u1_top_intra = 0, u1_left_intra = 0; + mv_pred_t *ps_left_mv_pred; + WORD16 i2_cur_mv0, i2_cur_mv1, i16_curMv2, i16_curMv3; + WORD16 i2_left_mv0, i2_left_mv1, i2_left_mv2, i2_left_mv3; + WORD16 i2_top_mv0, i2_top_mv1, i16_topMv2, i16_topMv3; + WORD8 i1_cur_ref0, i1_cur_ref1, i1_left_ref0, i1_left_ref1, i1_top_ref0, i1_top_ref1; + UWORD8 uc_cur_nnz, uc_left_nnz, uc_top_nnz, u1_mb_type, uc_Bslice; + void **ppv_map_ref_idx_to_poc_l0, **ppv_map_ref_idx_to_poc_l1; + UWORD8 uc_temp; + UWORD8 uc_cur_mb_fld, uc_top_mb_fld; + UWORD32 c_mv_limit; + + u1_mb_type = ps_cur_mb_params->u1_mb_type; + uc_Bslice = u1_mb_type & D_B_SLICE; + ppv_map_ref_idx_to_poc_l0 = ppv_map_ref_idx_to_poc; + ppv_map_ref_idx_to_poc_l1 = ppv_map_ref_idx_to_poc + POC_LIST_L0_TO_L1_DIFF; + + ps_top_mb_params = ps_top_mb_params ? ps_top_mb_params : ps_cur_mb_params; + u1_top_intra = ps_top_mb_params->u1_mb_type & D_INTRA_MB; + u1_left_intra = ps_left_mb_params->u1_mb_type & D_INTRA_MB; + + ui_bs_table[4] = 0x04040404; //Default for INTRA MB Boundary edges. + uc_cur_mb_fld = (ps_cur_mb_params->u1_mb_type & D_FLD_MB) >> 7; + uc_top_mb_fld = (ps_top_mb_params->u1_mb_type & D_FLD_MB) >> 7; + + c_mv_limit = 4 >> uc_cur_mb_fld; + if((0 == uc_cur_mb_fld) && (0 == uc_top_mb_fld)) + { + ui_bs_table[0] = 0x04040404; + } + else + { + ui_bs_table[0] = 0x03030303; + } + + for(edge = 0; edge < 4; + edge++, ps_top_mv_pred = ps_cur_mv_pred - 4, puc_top_nnz = + puc_cur_nnz - 4) + { + //Each iteration of this loop fills the four BS values of one HORIZ edge and + //one BS value for each of the four VERT edges. + WORD8 i = 0; + UWORD8 uc_bs_horiz, uc_bs_vert; + UWORD32 ui_cnd; + void *ui_ref_pic_addr[4]; + UWORD8 uc_mixed_mode_edge; + + uc_mixed_mode_edge = 0; + + uc_temp = (ui_mbAff << 4) + 13; + + uc_cur_nnz = *(puc_cur_nnz - uc_temp); + ps_left_mv_pred = ps_leftmost_mv_pred + (edge << 2); + + for(i = 0; i < 4; i++, ps_top_mv_pred++, ps_cur_mv_pred++) + { + //Each iteration of this inner loop computes a HORIZ + //and a VERT BS value for a 4x4 block + + uc_left_nnz = uc_cur_nnz; + uc_cur_nnz = *puc_cur_nnz++; + uc_top_nnz = *puc_top_nnz++; + + //VERT edge is assigned BS values first + ui_cnd = !(uc_left_nnz || uc_cur_nnz); + uc_bs_vert = 2; + + if(ui_cnd) + { + i2_left_mv0 = ps_left_mv_pred->i2_mv[0]; + i2_left_mv1 = ps_left_mv_pred->i2_mv[1]; + i2_left_mv2 = ps_left_mv_pred->i2_mv[2]; + i2_left_mv3 = ps_left_mv_pred->i2_mv[3]; + + i2_cur_mv0 = ps_cur_mv_pred->i2_mv[0]; + i2_cur_mv1 = ps_cur_mv_pred->i2_mv[1]; + i16_curMv2 = ps_cur_mv_pred->i2_mv[2]; + i16_curMv3 = ps_cur_mv_pred->i2_mv[3]; + i1_cur_ref0 = ps_cur_mv_pred->i1_ref_frame[0]; + i1_cur_ref1 = ps_cur_mv_pred->i1_ref_frame[1]; + ui_ref_pic_addr[2] = ppv_map_ref_idx_to_poc_l0[i1_cur_ref0]; + ui_ref_pic_addr[3] = ppv_map_ref_idx_to_poc_l1[i1_cur_ref1]; + + if(i) + { + i1_left_ref0 = ps_left_mv_pred->i1_ref_frame[0]; + i1_left_ref1 = ps_left_mv_pred->i1_ref_frame[1]; + ui_ref_pic_addr[0] = ppv_map_ref_idx_to_poc_l0[i1_left_ref0]; + ui_ref_pic_addr[1] = ppv_map_ref_idx_to_poc_l1[i1_left_ref1]; + } + else + { + ui_ref_pic_addr[0] = ps_left_addr->u4_add[edge & 2]; + ui_ref_pic_addr[1] = ps_left_addr->u4_add[1 + (edge & 2)]; + } + if(!uc_Bslice) + { + uc_bs_vert = + (ui_ref_pic_addr[0] != ui_ref_pic_addr[2]) + | (ABS((i2_left_mv0 + - i2_cur_mv0)) + >= 4) + | (ABS((i2_left_mv1 + - i2_cur_mv1)) + >= (UWORD8)c_mv_limit); + } + else + { + UWORD8 uc_bs_temp1, uc_bs_temp2; + + uc_bs_vert = 1; + + uc_bs_temp1 = + ((ABS((i2_left_mv0 - i2_cur_mv0)) + >= 4) + | (ABS((i2_left_mv1 + - i2_cur_mv1)) + >= (UWORD8)c_mv_limit) + | (ABS((i2_left_mv2 + - i16_curMv2)) + >= 4) + | (ABS((i2_left_mv3 + - i16_curMv3)) + >= (UWORD8)c_mv_limit)); + + uc_bs_temp2 = + ((ABS((i2_left_mv0 - i16_curMv2)) + >= 4) + | (ABS((i2_left_mv1 + - i16_curMv3)) + >= (UWORD8)c_mv_limit) + | (ABS((i2_left_mv2 + - i2_cur_mv0)) + >= 4) + | (ABS((i2_left_mv3 + - i2_cur_mv1)) + >= (UWORD8)c_mv_limit)); + + uc_bs_vert = + (((ui_ref_pic_addr[0] != ui_ref_pic_addr[2]) + || (ui_ref_pic_addr[1] + != ui_ref_pic_addr[3])) + || (uc_bs_temp1)) + && (((ui_ref_pic_addr[0] + != ui_ref_pic_addr[3]) + || (ui_ref_pic_addr[1] + != ui_ref_pic_addr[2])) + || (uc_bs_temp2)); + + } + } + //Fill the VERT BS, only if valid i.e., + //if it is a non-edge OR it is an edge, which is not yet filled + uc_bs_vert = (!i && u1_left_intra) ? 4 : uc_bs_vert; + ui_bs_table[i + 4] = (ui_bs_table[i + 4] << 8) | uc_bs_vert; + + //HORIZ edge is assigned BS values next + ui_cnd = !(uc_top_nnz || uc_cur_nnz); + uc_bs_horiz = 2; + + if(ui_cnd) + { + uc_mixed_mode_edge = + (0 == edge) ? (uc_top_mb_fld != uc_cur_mb_fld) : 0; + ui_cnd = 1 - uc_mixed_mode_edge; + uc_bs_horiz = uc_mixed_mode_edge; + } + + if(ui_cnd) + { + i2_cur_mv0 = ps_cur_mv_pred->i2_mv[0]; + i2_cur_mv1 = ps_cur_mv_pred->i2_mv[1]; + i16_curMv2 = ps_cur_mv_pred->i2_mv[2]; + i16_curMv3 = ps_cur_mv_pred->i2_mv[3]; + i1_cur_ref0 = ps_cur_mv_pred->i1_ref_frame[0]; + i1_cur_ref1 = ps_cur_mv_pred->i1_ref_frame[1]; + + i2_top_mv0 = ps_top_mv_pred->i2_mv[0]; + i2_top_mv1 = ps_top_mv_pred->i2_mv[1]; + i16_topMv2 = ps_top_mv_pred->i2_mv[2]; + i16_topMv3 = ps_top_mv_pred->i2_mv[3]; + ui_ref_pic_addr[2] = ppv_map_ref_idx_to_poc_l0[i1_cur_ref0]; + ui_ref_pic_addr[3] = ppv_map_ref_idx_to_poc_l1[i1_cur_ref1]; + if(edge) + { + i1_top_ref0 = ps_top_mv_pred->i1_ref_frame[0]; + i1_top_ref1 = ps_top_mv_pred->i1_ref_frame[1]; + ui_ref_pic_addr[0] = ppv_map_ref_idx_to_poc_l0[i1_top_ref0]; + ui_ref_pic_addr[1] = ppv_map_ref_idx_to_poc_l1[i1_top_ref1]; + } + else + { + ui_ref_pic_addr[0] = ps_top_add->u4_add[i & 2]; + ui_ref_pic_addr[1] = ps_top_add->u4_add[1 + (i & 2)]; + } + if(!uc_Bslice) + { + uc_bs_horiz = + (ui_ref_pic_addr[0] != ui_ref_pic_addr[2]) + | (ABS((i2_top_mv0 + - i2_cur_mv0)) + >= 4) + | (ABS((i2_top_mv1 + - i2_cur_mv1)) + >= (UWORD8)c_mv_limit); + } + else + { + UWORD8 uc_bs_temp1, uc_bs_temp2; + + uc_bs_horiz = 1; + + uc_bs_temp1 = + ((ABS((i2_top_mv0 - i2_cur_mv0)) + >= 4) + | (ABS((i2_top_mv1 + - i2_cur_mv1)) + >= (UWORD8)c_mv_limit) + | (ABS((i16_topMv2 + - i16_curMv2)) + >= 4) + | (ABS((i16_topMv3 + - i16_curMv3)) + >= (UWORD8)c_mv_limit)); + + uc_bs_temp2 = + ((ABS((i2_top_mv0 - i16_curMv2)) + >= 4) + | (ABS((i2_top_mv1 + - i16_curMv3)) + >= (UWORD8)c_mv_limit) + | (ABS((i16_topMv2 + - i2_cur_mv0)) + >= 4) + | (ABS((i16_topMv3 + - i2_cur_mv1)) + >= (UWORD8)c_mv_limit)); + + uc_bs_horiz = + (((ui_ref_pic_addr[0] != ui_ref_pic_addr[2]) + || (ui_ref_pic_addr[1] + != ui_ref_pic_addr[3])) + || (uc_bs_temp1)) + && (((ui_ref_pic_addr[0] + != ui_ref_pic_addr[3]) + || (ui_ref_pic_addr[1] + != ui_ref_pic_addr[2])) + || (uc_bs_temp2)); + + } + } + ps_left_mv_pred = ps_cur_mv_pred; + u4_bs_horz = (u4_bs_horz << 8) + uc_bs_horiz; + } + //Fill the HORIZ BS, only if valid i.e., + //if it is a non-edge OR it is an edge, which is not yet filled + if(edge || (!edge && !u1_top_intra)) + ui_bs_table[edge] = u4_bs_horz; + } +} + +/*! + ************************************************************************** + * \if Function name : ih264d_fill_bs_for_extra_left_edge \endif + * + * \brief + * Fills the boundary strength (Bs), for the top extra edge. ock + * + * \return + * Returns the packed boundary strength(Bs) MSB -> LSB Bs0|Bs1|Bs2|Bs3 + * + ************************************************************************** + */ +void ih264d_fill_bs_for_extra_left_edge(deblk_mb_t *ps_cur_deblk_mb, + deblk_mb_t *ps_leftDeblkMb, + UWORD8* puc_cur_nnz, + UWORD8 uc_botMb) +{ + /* Set the Flag in uc_deblocking_mode variable of current MB*/ + /* for mixed mode edge*/ + ps_cur_deblk_mb->u1_single_call = 0; + + if(ps_cur_deblk_mb->u1_mb_type & D_INTRA_MB) + { + ps_cur_deblk_mb->u4_bs_table[4] = 0x04040404; + ps_cur_deblk_mb->u4_bs_table[9] = 0x04040404; + } + else if((ps_leftDeblkMb->u1_mb_type & D_INTRA_MB) + && ((ps_leftDeblkMb + 1)->u1_mb_type & D_INTRA_MB)) + { + ps_cur_deblk_mb->u4_bs_table[4] = 0x04040404; + ps_cur_deblk_mb->u4_bs_table[9] = 0x04040404; + } + else + { + /* Get strengths of left MB edge */ + UWORD32 u4_bs; + UWORD8 uc_Bs; + WORD32 i; + UWORD32 ui_curMbFld; + UWORD8 *puc_left_nnz; + UWORD32 ui_bs_left_edge[2]; + + ui_curMbFld = (ps_cur_deblk_mb->u1_mb_type & D_FLD_MB) >> 7; + + puc_left_nnz = puc_cur_nnz - 29; + if((ui_curMbFld == 0) && uc_botMb) + { + puc_left_nnz -= 8; + } + else if(ui_curMbFld && uc_botMb) + { + puc_left_nnz -= 16; + } + + if(ui_curMbFld) + { + if(ps_leftDeblkMb->u1_mb_type & D_INTRA_MB) + { + ui_bs_left_edge[0] = 0x04040404; + puc_left_nnz += 16; + puc_cur_nnz += 8; + } + else + { + u4_bs = 0; + for(i = 4; i > 0; i--) + { + uc_Bs = ((*puc_cur_nnz || *puc_left_nnz)) ? 2 : 1; + u4_bs = (u4_bs << 8) | uc_Bs; + puc_left_nnz += 4; + if(i & 0x01) + puc_cur_nnz += 4; + } + ui_bs_left_edge[0] = u4_bs; + } + + if((ps_leftDeblkMb + 1)->u1_mb_type & D_INTRA_MB) + { + ui_bs_left_edge[1] = 0x04040404; + } + else + { + u4_bs = 0; + for(i = 4; i > 0; i--) + { + uc_Bs = ((*puc_cur_nnz || *puc_left_nnz)) ? 2 : 1; + u4_bs = (u4_bs << 8) | uc_Bs; + puc_left_nnz += 4; + if(i & 0x01) + puc_cur_nnz += 4; + } + ui_bs_left_edge[1] = u4_bs; + } + } + else + { + UWORD8 *puc_curNnzB, *puc_leftNnzB; + puc_curNnzB = puc_cur_nnz; + puc_leftNnzB = puc_left_nnz + 16; + if(ps_leftDeblkMb->u1_mb_type & D_INTRA_MB) + { + ui_bs_left_edge[0] = 0x04040404; + } + else + { + u4_bs = 0; + for(i = 4; i > 0; i--, puc_cur_nnz += 4) + { + uc_Bs = ((*puc_cur_nnz || *puc_left_nnz)) ? 2 : 1; + u4_bs = (u4_bs << 8) | uc_Bs; + if(i & 0x01) + puc_left_nnz += 4; + } + ui_bs_left_edge[0] = u4_bs; + } + + if((ps_leftDeblkMb + 1)->u1_mb_type & D_INTRA_MB) + { + ui_bs_left_edge[1] = 0x04040404; + } + else + { + u4_bs = 0; + for(i = 4; i > 0; i--, puc_curNnzB += 4) + { + uc_Bs = ((*puc_curNnzB || *puc_leftNnzB)) ? 2 : 1; + u4_bs = (u4_bs << 8) | uc_Bs; + if(i & 0x01) + puc_leftNnzB += 4; + } + ui_bs_left_edge[1] = u4_bs; + } + } + /* Copy The Values in Cur Deblk Mb Parameters */ + ps_cur_deblk_mb->u4_bs_table[4] = ui_bs_left_edge[0]; + ps_cur_deblk_mb->u4_bs_table[9] = ui_bs_left_edge[1]; + } + +} + +/*! + ************************************************************************** + * \if Function name : ih264d_fill_bs_for_extra_top_edge \endif + * + * \brief + * Fills the boundary strength (Bs), for the top extra edge. ock + * + * \return + * Returns the packed boundary strength(Bs) MSB -> LSB Bs0|Bs1|Bs2|Bs3 + * + ************************************************************************** + */ +void ih264d_fill_bs_for_extra_top_edge(deblk_mb_t *ps_cur_mb_params, + UWORD8 u1_Edge0_mb_typ, + UWORD8 u1_Edge1_mb_typ, + UWORD8 *pu1_curNnz, + UWORD8 *pu1_topNnz) +{ + UWORD32 u4_bs; + UWORD8 uc_Bs; + WORD32 i; + UWORD8 *pu1_cur_nnz_tmp; + UWORD8 *pu1_top_nnz_tmp; + UWORD8 u1_top_edge; + UWORD8 u1_top_mb_type; + for(u1_top_edge = 0; u1_top_edge < 2; u1_top_edge++) + { + u1_top_mb_type = u1_top_edge ? u1_Edge1_mb_typ : u1_Edge0_mb_typ; + pu1_cur_nnz_tmp = pu1_curNnz; + pu1_top_nnz_tmp = pu1_topNnz + (u1_top_edge << 2); + + if((ps_cur_mb_params->u1_mb_type & D_INTRA_MB) + + (u1_top_mb_type & D_INTRA_MB)) + { + u4_bs = 0x03030303; + } + else + { + u4_bs = 0; + for(i = 4; i > 0; i--, pu1_cur_nnz_tmp += 1, pu1_top_nnz_tmp += 1) + { + uc_Bs = ((*pu1_cur_nnz_tmp || *pu1_top_nnz_tmp)) ? 2 : 1; + u4_bs = (u4_bs << 8) | uc_Bs; + } + } + if(u1_top_edge) + ps_cur_mb_params->u4_bs_table[0] = u4_bs; + else + ps_cur_mb_params->u4_bs_table[8] = u4_bs; + } +} + + +void ih264d_fill_bs_mbedge_4(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info, + const UWORD16 u2_mbxn_mb) +{ + + /* deblk_mb_t Params */ + deblk_mb_t *ps_cur_mb_params; /*< Parameters of current MacroBlock */ + deblkmb_neighbour_t *ps_deblk_top_mb; + UWORD32 * pu4_bs_table; + UWORD8 u1_cur_mb_type; + + /* Neighbour availability */ + /* Initialization */ + const UWORD32 u2_mbx = ps_cur_mb_info->u2_mbx; + const UWORD32 u2_mby = ps_cur_mb_info->u2_mby; + const UWORD32 u1_pingpong = u2_mbx & 0x01; + ps_deblk_top_mb = ps_dec->ps_deblk_top_mb + u2_mbx; + + + /* Pointer assignment for Current DeblkMB, Current Mv Pred */ + ps_cur_mb_params = ps_dec->ps_deblk_mbn + u2_mbxn_mb; + + u1_cur_mb_type = ps_cur_mb_params->u1_mb_type; + + ps_deblk_top_mb->u1_mb_type = u1_cur_mb_type; + + { + UWORD8 mb_qp_temp; + + ps_cur_mb_params->u1_topmb_qp = ps_deblk_top_mb->u1_mb_qp; + ps_deblk_top_mb->u1_mb_qp = ps_cur_mb_params->u1_mb_qp; + + ps_cur_mb_params->u1_left_mb_qp = ps_dec->deblk_left_mb[1].u1_mb_qp; + ps_dec->deblk_left_mb[1].u1_mb_qp = ps_cur_mb_params->u1_mb_qp; + + } + + ps_cur_mb_params->u1_single_call = 1; + + ps_dec->deblk_left_mb[1].u1_mb_type = ps_cur_mb_params->u1_mb_type; + /* if no deblocking required for current Mb then continue */ + /* Check next Mbs in Mb group */ + if(ps_cur_mb_params->u1_deblocking_mode & MB_DISABLE_FILTERING) + { + /* Storing the leftMbtype for next Mb */ + return; + } + + /* Compute BS function */ + pu4_bs_table = ps_cur_mb_params->u4_bs_table; + + pu4_bs_table[4] = 0x04040404; + pu4_bs_table[0] = 0x04040404; + pu4_bs_table[1] = 0; + pu4_bs_table[2] = 0; + pu4_bs_table[3] = 0; + pu4_bs_table[5] = 0; + pu4_bs_table[6] = 0; + pu4_bs_table[7] = 0; + +} + +void ih264d_fill_bs_mbedge_2(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info, + const UWORD16 u2_mbxn_mb) +{ + + /* deblk_mb_t Params */ + deblk_mb_t *ps_cur_mb_params; /*< Parameters of current MacroBlock */ + deblkmb_neighbour_t *ps_deblk_top_mb; + UWORD32 * pu4_bs_table; + UWORD8 u1_cur_mb_type; + + /* Neighbour availability */ + /* Initialization */ + const UWORD32 u2_mbx = ps_cur_mb_info->u2_mbx; + const UWORD32 u2_mby = ps_cur_mb_info->u2_mby; + const UWORD32 u1_pingpong = u2_mbx & 0x01; + ps_deblk_top_mb = ps_dec->ps_deblk_top_mb + u2_mbx; + + + /* Pointer assignment for Current DeblkMB, Current Mv Pred */ + ps_cur_mb_params = ps_dec->ps_deblk_mbn + u2_mbxn_mb; + + u1_cur_mb_type = ps_cur_mb_params->u1_mb_type; + + ps_deblk_top_mb->u1_mb_type = u1_cur_mb_type; + + { + UWORD8 mb_qp_temp; + + ps_cur_mb_params->u1_topmb_qp = ps_deblk_top_mb->u1_mb_qp; + ps_deblk_top_mb->u1_mb_qp = ps_cur_mb_params->u1_mb_qp; + + ps_cur_mb_params->u1_left_mb_qp = ps_dec->deblk_left_mb[1].u1_mb_qp; + ps_dec->deblk_left_mb[1].u1_mb_qp = ps_cur_mb_params->u1_mb_qp; + + } + + ps_cur_mb_params->u1_single_call = 1; + + ps_dec->deblk_left_mb[1].u1_mb_type = ps_cur_mb_params->u1_mb_type; + /* if no deblocking required for current Mb then continue */ + /* Check next Mbs in Mb group */ + if(ps_cur_mb_params->u1_deblocking_mode & MB_DISABLE_FILTERING) + { + /* Storing the leftMbtype for next Mb */ + return; + } + + /* Compute BS function */ + pu4_bs_table = ps_cur_mb_params->u4_bs_table; + + { + UWORD32 top_mb_csbp, left_mb_csbp, cur_mb_csbp; + UWORD32 top_edge, left_edge; + + top_mb_csbp = ps_cur_mb_info->ps_top_mb->u2_luma_csbp; + left_mb_csbp = ps_cur_mb_info->ps_left_mb->u2_luma_csbp; + cur_mb_csbp = ps_cur_mb_info->ps_curmb->u2_luma_csbp; + + top_mb_csbp = top_mb_csbp >> 12; + top_edge = top_mb_csbp | (cur_mb_csbp & 0xf); + + if(top_edge) + pu4_bs_table[0] = 0x02020202; + else + pu4_bs_table[0] = 0; + + cur_mb_csbp = cur_mb_csbp & CSBP_LEFT_BLOCK_MASK; + left_mb_csbp = left_mb_csbp & CSBP_RIGHT_BLOCK_MASK; + + left_edge = cur_mb_csbp | left_mb_csbp; + + if(left_edge) + pu4_bs_table[4] = 0x02020202; + else + pu4_bs_table[4] = 0; + + pu4_bs_table[1] = 0; + pu4_bs_table[2] = 0; + pu4_bs_table[3] = 0; + pu4_bs_table[5] = 0; + pu4_bs_table[6] = 0; + pu4_bs_table[7] = 0; + } + +} diff --git a/dependencies/ih264d/decoder/ih264d_deblocking.c b/dependencies/ih264d/decoder/ih264d_deblocking.c new file mode 100644 index 00000000..c5f3657d --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_deblocking.c @@ -0,0 +1,1841 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ + +#include <string.h> + +#include "ih264_typedefs.h" +#include "iv.h" +#include "ivd.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264d_debug.h" +#include "ih264d_defs.h" +#include "ih264d_defs.h" +#include "ih264d_structs.h" +#include "ih264d_deblocking.h" +#include "ih264d_mb_utils.h" +#include "ih264d_error_handler.h" +#include "ih264d_utils.h" + + +#include "ih264d_defs.h" +#include "ih264d_format_conv.h" +#include "ih264d_deblocking.h" +#include "ih264d_tables.h" + +/*! + ************************************************************************* + * \file ih264d_deblocking.c + * + * \brief + * Decoder specific deblocking routines + * + * \author AI + ************************************************************************* + */ + +/*! + ************************************************************************** + * \if Function name : HorizonPad \endif + * + * \brief + * Does the Horizontal padding on a whole pic. + * + * \return + * None + ************************************************************************** + */ + +/*! + ************************************************************************** + * \if Function name : FilterBoundaryLeft \endif + * + * \brief + * Filters MacroBlock Left Boundary egdes. + * + * \return + * None + ************************************************************************** + */ +void ih264d_filter_boundary_left_nonmbaff(dec_struct_t *ps_dec, + tfr_ctxt_t * ps_tfr_cxt, + WORD8 i1_cb_qp_idx_ofst, + WORD8 i1_cr_qp_idx_ofst, + deblk_mb_t * ps_cur_mb, + WORD32 i4_strd_y, + WORD32 i4_strd_uv, + deblk_mb_t * ps_left_mb, + UWORD32 pu4_bs_tab[], + UWORD8 u1_cur_fld) +{ + UWORD8 *pu1_y, *pu1_u, *pu1_v; + WORD32 uc_tmp, qp_avg; + WORD32 alpha_u = 0, beta_u = 0, alpha_v = 0, beta_v = 0; + WORD32 alpha_y = 0, beta_y = 0; + + WORD32 idx_b_u, idx_a_u, idx_b_v, idx_a_v; + WORD32 idx_b_y, idx_a_y; + + UWORD32 u4_bs_val; + + UWORD8 *pu1_cliptab_u, *pu1_cliptab_v, *pu1_cliptab_y; + + UWORD8 u1_double_cl = !ps_cur_mb->u1_single_call; + WORD32 ofst_a = ps_cur_mb->i1_slice_alpha_c0_offset; + WORD32 ofst_b = ps_cur_mb->i1_slice_beta_offset; + + PROFILE_DISABLE_DEBLK() + + pu1_y = ps_tfr_cxt->pu1_mb_y; + pu1_u = ps_tfr_cxt->pu1_mb_u; + pu1_v = ps_tfr_cxt->pu1_mb_v; + + /* LUMA values */ + /* Deblock rounding change */ + qp_avg = + (UWORD8)((ps_cur_mb->u1_left_mb_qp + ps_cur_mb->u1_mb_qp + 1) + >> 1); + + idx_a_y = qp_avg + ofst_a; + alpha_y = gau1_ih264d_alpha_table[12 + idx_a_y]; + idx_b_y = qp_avg + ofst_b; + beta_y = gau1_ih264d_beta_table[12 + idx_b_y]; + + /* Chroma cb values */ + { + WORD32 mb_qp1, mb_qp2; + mb_qp1 = (ps_cur_mb->u1_left_mb_qp + i1_cb_qp_idx_ofst); + mb_qp2 = (ps_cur_mb->u1_mb_qp + i1_cb_qp_idx_ofst); + qp_avg = (UWORD8)((gau1_ih264d_qp_scale_cr[12 + mb_qp1] + + gau1_ih264d_qp_scale_cr[12 + mb_qp2] + 1) >> 1); + } + idx_a_u = qp_avg + ofst_a; + alpha_u = gau1_ih264d_alpha_table[12 + idx_a_u]; + idx_b_u = qp_avg + ofst_b; + beta_u = gau1_ih264d_beta_table[12 + idx_b_u]; + /* Chroma cr values */ + { + WORD32 mb_qp1, mb_qp2; + mb_qp1 = (ps_cur_mb->u1_left_mb_qp + i1_cr_qp_idx_ofst); + mb_qp2 = (ps_cur_mb->u1_mb_qp + i1_cr_qp_idx_ofst); + qp_avg = (UWORD8)((gau1_ih264d_qp_scale_cr[12 + mb_qp1] + + gau1_ih264d_qp_scale_cr[12 + mb_qp2] + 1) >> 1); + } + idx_a_v = qp_avg + ofst_a; + alpha_v = gau1_ih264d_alpha_table[12 + idx_a_v]; + idx_b_v = qp_avg + ofst_b; + beta_v = gau1_ih264d_beta_table[12 + idx_b_v]; + + if(u1_double_cl == 0) + { + u4_bs_val = pu4_bs_tab[4]; + + if(0x04040404 == u4_bs_val) + { + ps_dec->pf_deblk_luma_vert_bs4(pu1_y, i4_strd_y, alpha_y, beta_y); + ps_dec->pf_deblk_chroma_vert_bs4(pu1_u, i4_strd_uv, alpha_u, + beta_u, alpha_v, beta_v); + } + else + { + if(u4_bs_val) + { + + pu1_cliptab_y = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_y]; + pu1_cliptab_u = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_u]; + pu1_cliptab_v = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_v]; + ps_dec->pf_deblk_luma_vert_bslt4(pu1_y, i4_strd_y, alpha_y, + beta_y, u4_bs_val, + pu1_cliptab_y); + ps_dec->pf_deblk_chroma_vert_bslt4(pu1_u, i4_strd_uv, alpha_u, + beta_u, alpha_v, beta_v, + u4_bs_val, pu1_cliptab_u, + pu1_cliptab_v); + + } + } + + } + else + { + + i4_strd_y <<= (!u1_cur_fld); + u4_bs_val = pu4_bs_tab[4]; + i4_strd_uv <<= (!u1_cur_fld); + + if(0x04040404 == u4_bs_val) + { + + ps_dec->pf_deblk_luma_vert_bs4_mbaff(pu1_y, i4_strd_y, alpha_y, + beta_y); + ps_dec->pf_deblk_chroma_vert_bs4_mbaff(pu1_u, i4_strd_uv, alpha_u, + beta_u, alpha_v, beta_v); + + } + else + { + if(u4_bs_val) + { + + pu1_cliptab_y = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_y]; + pu1_cliptab_u = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_u]; + pu1_cliptab_v = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_v]; + + ps_dec->pf_deblk_luma_vert_bslt4_mbaff(pu1_y, i4_strd_y, + alpha_y, beta_y, + u4_bs_val, + pu1_cliptab_y); + ps_dec->pf_deblk_chroma_vert_bslt4_mbaff(pu1_u, i4_strd_uv, + alpha_u, beta_u, + alpha_v, beta_v, + u4_bs_val, + pu1_cliptab_u, + pu1_cliptab_v); + } + } + + { + + UWORD16 u2_shift = (i4_strd_y >> 1) << (u1_cur_fld ? 4 : 0); + pu1_y += u2_shift; + u2_shift = (i4_strd_uv >> 1) << (u1_cur_fld ? 3 : 0); + pu1_u += u2_shift; + pu1_v += u2_shift; + } + + qp_avg = (((ps_left_mb + 1)->u1_mb_qp + ps_cur_mb->u1_mb_qp + 1) >> 1); + + idx_a_y = qp_avg + ofst_a; + alpha_y = gau1_ih264d_alpha_table[12 + idx_a_y]; + idx_b_y = qp_avg + ofst_b; + beta_y = gau1_ih264d_beta_table[12 + idx_b_y]; + u4_bs_val = pu4_bs_tab[9]; + + { + WORD32 mb_qp1, mb_qp2; + mb_qp1 = ((ps_left_mb + 1)->u1_mb_qp + i1_cb_qp_idx_ofst); + mb_qp2 = (ps_cur_mb->u1_mb_qp + i1_cb_qp_idx_ofst); + qp_avg = (UWORD8)((gau1_ih264d_qp_scale_cr[12 + mb_qp1] + + gau1_ih264d_qp_scale_cr[12 + mb_qp2] + 1) >> 1); + } + idx_a_u = qp_avg + ofst_a; + alpha_u = gau1_ih264d_alpha_table[12 + idx_a_u]; + idx_b_u = qp_avg + ofst_b; + beta_u = gau1_ih264d_beta_table[12 + idx_b_u]; + u4_bs_val = pu4_bs_tab[9]; + { + WORD32 mb_qp1, mb_qp2; + mb_qp1 = ((ps_left_mb + 1)->u1_mb_qp + i1_cr_qp_idx_ofst); + mb_qp2 = (ps_cur_mb->u1_mb_qp + i1_cr_qp_idx_ofst); + qp_avg = (UWORD8)((gau1_ih264d_qp_scale_cr[12 + mb_qp1] + + gau1_ih264d_qp_scale_cr[12 + mb_qp2] + 1) >> 1); + } + idx_a_v = qp_avg + ofst_a; + alpha_v = gau1_ih264d_alpha_table[12 + idx_a_v]; + idx_b_v = qp_avg + ofst_b; + beta_v = gau1_ih264d_beta_table[12 + idx_b_v]; + + if(0x04040404 == u4_bs_val) + { + ps_dec->pf_deblk_luma_vert_bs4_mbaff(pu1_y, i4_strd_y, alpha_y, + beta_y); + ps_dec->pf_deblk_chroma_vert_bs4_mbaff(pu1_u, i4_strd_uv, alpha_u, + beta_u, alpha_v, beta_v); + + } + else + { + if(u4_bs_val) + { + + pu1_cliptab_y = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_y]; + pu1_cliptab_u = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_u]; + pu1_cliptab_v = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_v]; + + ps_dec->pf_deblk_luma_vert_bslt4_mbaff(pu1_y, i4_strd_y, + alpha_y, beta_y, + u4_bs_val, + pu1_cliptab_y); + ps_dec->pf_deblk_chroma_vert_bslt4_mbaff(pu1_u, i4_strd_uv, + alpha_u, beta_u, + alpha_v, beta_v, + u4_bs_val, + pu1_cliptab_u, + pu1_cliptab_v); + + } + } + } + +} + +/*! + ************************************************************************** + * \if Function name : FilterBoundaryTop \endif + * + * \brief + * Filters MacroBlock Top Boundary egdes. + * + * \return + * None + ************************************************************************** + */ + +void ih264d_filter_boundary_top_nonmbaff(dec_struct_t *ps_dec, + tfr_ctxt_t * ps_tfr_cxt, + WORD8 i1_cb_qp_idx_ofst, + WORD8 i1_cr_qp_idx_ofst, + deblk_mb_t * ps_cur_mb, + WORD32 i4_strd_y, + WORD32 i4_strd_uv, + deblk_mb_t * ps_top_mb, + UWORD32 u4_bs) +{ + UWORD8 *pu1_y, *pu1_u; + WORD32 alpha_u = 0, beta_u = 0, alpha_v = 0, beta_v = 0; + WORD32 alpha_y = 0, beta_y = 0; + WORD32 qp_avg; + WORD32 idx_b_u, idx_a_u, idx_b_v, idx_a_v; + WORD32 idx_b_y, idx_a_y; + UWORD16 uc_tmp; + + UWORD8 *pu1_cliptab_u, *pu1_cliptab_v, *pu1_cliptab_y; + WORD32 ofst_a = ps_cur_mb->i1_slice_alpha_c0_offset; + WORD32 ofst_b = ps_cur_mb->i1_slice_beta_offset; + + UNUSED(ps_top_mb); + /* LUMA values */ + /* Deblock rounding change */ + uc_tmp = ((ps_cur_mb->u1_topmb_qp + ps_cur_mb->u1_mb_qp + 1) >> 1); + qp_avg = (UWORD8)uc_tmp; + idx_a_y = qp_avg + ofst_a; + alpha_y = gau1_ih264d_alpha_table[12 + idx_a_y]; + idx_b_y = qp_avg + ofst_b; + beta_y = gau1_ih264d_beta_table[12 + idx_b_y]; + pu1_y = ps_tfr_cxt->pu1_mb_y; + + /* CHROMA cb values */ + { + WORD32 mb_qp1, mb_qp2; + mb_qp1 = (ps_cur_mb->u1_topmb_qp + i1_cb_qp_idx_ofst); + mb_qp2 = (ps_cur_mb->u1_mb_qp + i1_cb_qp_idx_ofst); + qp_avg = (UWORD8)((gau1_ih264d_qp_scale_cr[12 + mb_qp1] + + gau1_ih264d_qp_scale_cr[12 + mb_qp2] + 1) >> 1); + } + + idx_a_u = qp_avg + ofst_a; + alpha_u = gau1_ih264d_alpha_table[12 + idx_a_u]; + idx_b_u = qp_avg + ofst_b; + beta_u = gau1_ih264d_beta_table[12 + idx_b_u]; + /* CHROMA cr values */ + { + WORD32 mb_qp1, mb_qp2; + mb_qp1 = (ps_cur_mb->u1_topmb_qp + i1_cr_qp_idx_ofst); + mb_qp2 = (ps_cur_mb->u1_mb_qp + i1_cr_qp_idx_ofst); + qp_avg = (UWORD8)((gau1_ih264d_qp_scale_cr[12 + mb_qp1] + + gau1_ih264d_qp_scale_cr[12 + mb_qp2] + 1) >> 1); + } + + idx_a_v = qp_avg + ofst_a; + alpha_v = gau1_ih264d_alpha_table[12 + idx_a_v]; + idx_b_v = qp_avg + ofst_b; + beta_v = gau1_ih264d_beta_table[12 + idx_b_v]; + pu1_u = ps_tfr_cxt->pu1_mb_u; + + if(u4_bs == 0x04040404) + { + /* Code specific to the assembly module */ + + ps_dec->pf_deblk_luma_horz_bs4(pu1_y, i4_strd_y, alpha_y, beta_y); + ps_dec->pf_deblk_chroma_horz_bs4(pu1_u, i4_strd_uv, alpha_u, beta_u, + alpha_v, beta_v); + } + else + { + if(u4_bs) + { + + pu1_cliptab_y = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_y]; + pu1_cliptab_u = + (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_u]; + pu1_cliptab_v = + (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_v]; + + ps_dec->pf_deblk_luma_horz_bslt4(pu1_y, i4_strd_y, alpha_y, beta_y, + u4_bs, pu1_cliptab_y); + ps_dec->pf_deblk_chroma_horz_bslt4(pu1_u, i4_strd_uv, alpha_u, + beta_u, alpha_v, beta_v, + u4_bs, pu1_cliptab_u, + pu1_cliptab_v); + + } + } + +} + +void ih264d_deblock_mb_nonmbaff(dec_struct_t *ps_dec, + tfr_ctxt_t * ps_tfr_cxt, + WORD8 i1_cb_qp_idx_ofst, + WORD8 i1_cr_qp_idx_ofst, + WORD32 i4_strd_y, + WORD32 i4_strd_uv ) +{ + UWORD8 *pu1_y, *pu1_u; + UWORD32 u4_bs; + + WORD32 alpha, beta, alpha_u, beta_u, alpha_v, beta_v; + + UWORD8 *pu1_cliptab_u; + UWORD8 *pu1_cliptab_v; + UWORD8 *pu1_cliptab_y; + + UWORD32 * pu4_bs_tab; + WORD32 idx_a_y, idx_a_u, idx_a_v; + UWORD32 u4_deb_mode, u4_mbs_next; + UWORD32 u4_image_wd_mb; + deblk_mb_t *ps_top_mb,*ps_left_mb,*ps_cur_mb; + + PROFILE_DISABLE_DEBLK() + /* Return from here to switch off deblocking */ + + u4_image_wd_mb = ps_dec->u2_frm_wd_in_mbs; + + ps_cur_mb = ps_dec->ps_cur_deblk_mb; + pu4_bs_tab = ps_cur_mb->u4_bs_table; + u4_deb_mode = ps_cur_mb->u1_deblocking_mode; + if(!(u4_deb_mode & MB_DISABLE_FILTERING)) + { + + if(ps_dec->u4_deblk_mb_x) + { + ps_left_mb = ps_cur_mb - 1; + + } + else + { + ps_left_mb = NULL; + + } + if(ps_dec->u4_deblk_mb_y != 0) + { + ps_top_mb = ps_cur_mb - (u4_image_wd_mb); + } + else + { + ps_top_mb = NULL; + } + + if(u4_deb_mode & MB_DISABLE_LEFT_EDGE) + ps_left_mb = NULL; + if(u4_deb_mode & MB_DISABLE_TOP_EDGE) + ps_top_mb = NULL; + + /*---------------------------------------------------------------------*/ + /* Filter wrt Left edge */ + /* except */ + /* - Left Egde is Picture Boundary */ + /* - Left Egde is part of Slice Boundary and Deblocking */ + /* parameters of slice disable Filtering of Slice Boundary Edges*/ + /*---------------------------------------------------------------------*/ + if(ps_left_mb) + ih264d_filter_boundary_left_nonmbaff(ps_dec, ps_tfr_cxt, + i1_cb_qp_idx_ofst, + i1_cr_qp_idx_ofst, ps_cur_mb, + i4_strd_y, i4_strd_uv, ps_left_mb, + pu4_bs_tab, 0); + + /*--------------------------------------------------------------------*/ + /* Filter wrt Other Vertical Edges */ + /*--------------------------------------------------------------------*/ + { + WORD32 ofst_a, ofst_b, idx_b_y, idx_b_u, + idx_b_v; + WORD32 qp_avg, qp_avg_u, qp_avg_v; + ofst_a = ps_cur_mb->i1_slice_alpha_c0_offset; + ofst_b = ps_cur_mb->i1_slice_beta_offset; + + qp_avg = ps_cur_mb->u1_mb_qp; + + idx_a_y = qp_avg + ofst_a; + alpha = gau1_ih264d_alpha_table[12 + idx_a_y]; + idx_b_y = qp_avg + ofst_b; + beta = gau1_ih264d_beta_table[12 + idx_b_y]; + + /* CHROMA values */ + /* CHROMA Cb values */ + qp_avg_u = (qp_avg + i1_cb_qp_idx_ofst); + qp_avg_u = gau1_ih264d_qp_scale_cr[12 + qp_avg_u]; + idx_a_u = qp_avg_u + ofst_a; + alpha_u = gau1_ih264d_alpha_table[12 + idx_a_u]; + idx_b_u = qp_avg_u + ofst_b; + beta_u = gau1_ih264d_beta_table[12 + idx_b_u]; + /* CHROMA Cr values */ + qp_avg_v = (qp_avg + i1_cr_qp_idx_ofst); + qp_avg_v = gau1_ih264d_qp_scale_cr[12 + qp_avg_v]; + idx_a_v = qp_avg_v + ofst_a; + alpha_v = gau1_ih264d_alpha_table[12 + idx_a_v]; + idx_b_v = qp_avg_v + ofst_b; + beta_v = gau1_ih264d_beta_table[12 + idx_b_v]; + } + + pu1_cliptab_y = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_y]; //this for Luma + pu1_cliptab_u = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_u]; //this for chroma + pu1_cliptab_v = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_v]; //this for chroma + + //edge=1 + + + u4_bs = pu4_bs_tab[5]; + pu1_y = ps_tfr_cxt->pu1_mb_y; + pu1_u = ps_tfr_cxt->pu1_mb_u; + + if(u4_bs) + { + + ps_dec->pf_deblk_luma_vert_bslt4(pu1_y + 4, i4_strd_y, alpha, beta, + u4_bs, pu1_cliptab_y); + + } + //edge=2 + + u4_bs = pu4_bs_tab[6]; + if(u4_bs) + { + ps_dec->pf_deblk_luma_vert_bslt4(pu1_y + 8, i4_strd_y, alpha, beta, + u4_bs, pu1_cliptab_y); + ps_dec->pf_deblk_chroma_vert_bslt4(pu1_u + 4 * YUV420SP_FACTOR, + i4_strd_uv, alpha_u, beta_u, + alpha_v, beta_v, u4_bs, + pu1_cliptab_u, pu1_cliptab_v); + + } + //edge=3 + + u4_bs = pu4_bs_tab[7]; + if(u4_bs) + { + ps_dec->pf_deblk_luma_vert_bslt4(pu1_y + 12, i4_strd_y, alpha, beta, + u4_bs, pu1_cliptab_y); + + } + + /*--------------------------------------------------------------------*/ + /* Filter wrt Top edge */ + /* except */ + /* - Top Egde is Picture Boundary */ + /* - Top Egde is part of Slice Boundary and Deblocking */ + /* parameters of slice disable Filtering of Slice Boundary Edges*/ + /*--------------------------------------------------------------------*/ + if(ps_top_mb) + { + /** if top MB and MB AFF and cur MB is frame and top is field then */ + /* one extra top edge needs to be deblocked */ + + ih264d_filter_boundary_top_nonmbaff(ps_dec, ps_tfr_cxt, + i1_cb_qp_idx_ofst, + i1_cr_qp_idx_ofst, ps_cur_mb, + i4_strd_y, i4_strd_uv, ps_top_mb, + pu4_bs_tab[0]); + + } + + /*--------------------------------------------------------------------*/ + /* Filter wrt Other Horizontal Edges */ + /*--------------------------------------------------------------------*/ + + //edge1 + u4_bs = pu4_bs_tab[1]; + + if(u4_bs) + { + ps_dec->pf_deblk_luma_horz_bslt4(pu1_y + (i4_strd_y << 2), i4_strd_y, + alpha, beta, u4_bs, pu1_cliptab_y); + + } + //edge2 + u4_bs = pu4_bs_tab[2]; + + if(u4_bs) + { + + ps_dec->pf_deblk_luma_horz_bslt4(pu1_y + (i4_strd_y << 3), i4_strd_y, + alpha, beta, u4_bs, pu1_cliptab_y); + ps_dec->pf_deblk_chroma_horz_bslt4(pu1_u + (i4_strd_uv << 2), + i4_strd_uv, alpha_u, beta_u, + alpha_v, beta_v, u4_bs, + pu1_cliptab_u, pu1_cliptab_v); + + } + //edge3 + u4_bs = pu4_bs_tab[3]; + if(u4_bs) + { + ps_dec->pf_deblk_luma_horz_bslt4( + (pu1_y + (i4_strd_y << 3) + (i4_strd_y << 2)), + i4_strd_y, alpha, beta, u4_bs, pu1_cliptab_y); + + } + } + + ps_dec->u4_deblk_mb_x++; + ps_dec->ps_cur_deblk_mb++; + ps_dec->u4_cur_deblk_mb_num++; + u4_mbs_next = u4_image_wd_mb - ps_dec->u4_deblk_mb_x; + + ps_tfr_cxt->pu1_mb_y += 16; + ps_tfr_cxt->pu1_mb_u += 8 * YUV420SP_FACTOR; + ps_tfr_cxt->pu1_mb_v += 8; + + if(!u4_mbs_next) + { + ps_tfr_cxt->pu1_mb_y += ps_tfr_cxt->u4_y_inc; + ps_tfr_cxt->pu1_mb_u += ps_tfr_cxt->u4_uv_inc; + ps_tfr_cxt->pu1_mb_v += ps_tfr_cxt->u4_uv_inc; + ps_dec->u4_deblk_mb_y++; + ps_dec->u4_deblk_mb_x = 0; + } + +} + +/************************************************************************** + * + * Function Name : ih264d_init_deblk_tfr_ctxt + * + * Description : This function is called once per deblockpicture call + * This sets up the transfer address contexts + * + * Revision History: + * + * DD MM YYYY Author(s) Changes (Describe the changes made) + * 14 06 2005 SWRN Draft + **************************************************************************/ +void ih264d_init_deblk_tfr_ctxt(dec_struct_t * ps_dec, + pad_mgr_t *ps_pad_mgr, + tfr_ctxt_t *ps_tfr_cxt, + UWORD16 u2_image_wd_mb, + UWORD8 u1_mbaff) +{ + + UWORD32 i4_wd_y; + UWORD32 i4_wd_uv; + UWORD8 u1_field_pic_flag = ps_dec->ps_cur_slice->u1_field_pic_flag; /*< Field u4_flag */ + UNUSED(u2_image_wd_mb); + ps_tfr_cxt->pu1_src_y = ps_dec->s_cur_pic.pu1_buf1 - 4; + ps_tfr_cxt->pu1_src_u = ps_dec->s_cur_pic.pu1_buf2 - 4; + ps_tfr_cxt->pu1_src_v = ps_dec->s_cur_pic.pu1_buf3 - 4; + ps_tfr_cxt->pu1_dest_y = ps_tfr_cxt->pu1_src_y; + ps_tfr_cxt->pu1_dest_u = ps_tfr_cxt->pu1_src_u; + ps_tfr_cxt->pu1_dest_v = ps_tfr_cxt->pu1_src_v; + + ps_tfr_cxt->pu1_mb_y = ps_tfr_cxt->pu1_src_y + 4; + ps_tfr_cxt->pu1_mb_u = ps_tfr_cxt->pu1_src_u + 4; + ps_tfr_cxt->pu1_mb_v = ps_tfr_cxt->pu1_src_v + 4; + + i4_wd_y = ps_dec->u2_frm_wd_y << u1_field_pic_flag; + i4_wd_uv = ps_dec->u2_frm_wd_uv << u1_field_pic_flag; + ps_tfr_cxt->u4_y_inc = ((i4_wd_y << u1_mbaff) * 16 + - (ps_dec->u2_frm_wd_in_mbs << 4)); + + ps_tfr_cxt->u4_uv_inc = (i4_wd_uv << u1_mbaff) * 8 + - (ps_dec->u2_frm_wd_in_mbs << 4); + + /* padding related initialisations */ + if(ps_dec->ps_cur_slice->u1_nal_ref_idc) + { + ps_pad_mgr->u1_vert_pad_top = !(ps_dec->ps_cur_slice->u1_field_pic_flag + && ps_dec->ps_cur_slice->u1_bottom_field_flag); + ps_pad_mgr->u1_vert_pad_bot = + ((!ps_dec->ps_cur_slice->u1_field_pic_flag) + || ps_dec->ps_cur_slice->u1_bottom_field_flag); + ps_pad_mgr->u1_horz_pad = 1; + } + else + { + ps_pad_mgr->u1_horz_pad = 0; + ps_pad_mgr->u1_vert_pad_top = 0; + ps_pad_mgr->u1_vert_pad_bot = 0; + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_deblock_picture_mbaff */ +/* */ +/* Description : This function carries out deblocking on a whole picture */ +/* with MBAFF */ +/* */ +/* Inputs : <What inputs does the function take?> */ +/* Processing : This functions calls deblock MB in the MB increment order*/ +/* */ +/* Outputs : Produces the deblocked picture */ +/* Returns : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 17 02 2005 NS Creation */ +/* 14 06 2005 SWRN clean-up */ +/*****************************************************************************/ + +void ih264d_deblock_picture_mbaff(dec_struct_t * ps_dec) +{ + WORD16 i2_mb_x, i2_mb_y; + deblk_mb_t *ps_cur_mb; + deblk_mb_t *ps_top_mb; + deblk_mb_t *ps_left_mb; + + UWORD8 u1_vert_pad_top = 1; + UWORD8 u1_cur_fld, u1_top_fld, u1_left_fld; + UWORD8 u1_first_row; + + UWORD8 * pu1_deb_y, *pu1_deb_u, *pu1_deb_v; + UWORD8 u1_deb_mode, u1_extra_top_edge; + WORD32 i4_wd_y, i4_wd_uv; + + UWORD8 u1_field_pic_flag = ps_dec->ps_cur_slice->u1_field_pic_flag; /*< Field u4_flag */ + UWORD8 u1_bottom_field_flag = ps_dec->ps_cur_slice->u1_bottom_field_flag; /*< Bottom field u4_flag*/ + + /**************************************************/ + /* one time loads from ps_dec which will be used */ + /* frequently throughout the deblocking procedure */ + /**************************************************/ + pad_mgr_t * ps_pad_mgr = &ps_dec->s_pad_mgr; + tfr_ctxt_t s_tfr_ctxt; + tfr_ctxt_t * ps_tfr_cxt = &s_tfr_ctxt; + + UWORD16 u2_image_wd_mb = ps_dec->u2_frm_wd_in_mbs; + UWORD16 u2_image_ht_mb = ps_dec->u2_frm_ht_in_mbs; + UWORD8 u1_mbaff = ps_dec->ps_cur_slice->u1_mbaff_frame_flag; + WORD8 i1_cb_qp_idx_ofst = ps_dec->ps_cur_pps->i1_chroma_qp_index_offset; + WORD8 i1_cr_qp_idx_ofst = + ps_dec->ps_cur_pps->i1_second_chroma_qp_index_offset; + + /* Set up Parameter for DMA transfer */ + ih264d_init_deblk_tfr_ctxt(ps_dec, ps_pad_mgr, ps_tfr_cxt, u2_image_wd_mb, + u1_mbaff); + + /* Pic level Initialisations */ + i2_mb_y = u2_image_ht_mb; + i2_mb_x = 0; + u1_extra_top_edge = 0; + + u1_first_row = 1; + + i4_wd_y = ps_dec->u2_frm_wd_y << u1_field_pic_flag; + i4_wd_uv = ps_dec->u2_frm_wd_uv << u1_field_pic_flag; + /* Initial filling of the buffers with deblocking data */ + + pu1_deb_y = ps_tfr_cxt->pu1_mb_y; + pu1_deb_u = ps_tfr_cxt->pu1_mb_u; + pu1_deb_v = ps_tfr_cxt->pu1_mb_v; + ps_cur_mb = ps_dec->ps_deblk_pic; + + if(ps_dec->u4_app_disable_deblk_frm == 0) + { + { + + while(i2_mb_y > 0) + { + do + { + + u1_deb_mode = ps_cur_mb->u1_deblocking_mode; + if(!(u1_deb_mode & MB_DISABLE_FILTERING)) + { + ps_tfr_cxt->pu1_mb_y = pu1_deb_y; + ps_tfr_cxt->pu1_mb_u = pu1_deb_u; + ps_tfr_cxt->pu1_mb_v = pu1_deb_v; + + u1_cur_fld = (ps_cur_mb->u1_mb_type & D_FLD_MB) >> 7; + u1_cur_fld &= 1; + if(i2_mb_x) + { + ps_left_mb = ps_cur_mb - 2; + } + else + { + ps_left_mb = NULL; + } + if(!u1_first_row) + { + ps_top_mb = ps_cur_mb - (u2_image_wd_mb << 1) + 1; + u1_top_fld = (ps_top_mb->u1_mb_type & D_FLD_MB) + >> 7; + } + else + { + ps_top_mb = NULL; + u1_top_fld = 0; + } + + if((!u1_first_row) & u1_top_fld & u1_cur_fld) + ps_top_mb--; + + /********************************************************/ + /* if top MB and MB AFF and cur MB is frame and top is */ + /* field, then one extra top edge needs to be deblocked */ + /********************************************************/ + u1_extra_top_edge = (!u1_cur_fld) & u1_top_fld; + + if(u1_deb_mode & MB_DISABLE_LEFT_EDGE) + ps_left_mb = NULL; + if(u1_deb_mode & MB_DISABLE_TOP_EDGE) + ps_top_mb = NULL; + + ih264d_deblock_mb_mbaff(ps_dec, ps_tfr_cxt, + i1_cb_qp_idx_ofst, + i1_cr_qp_idx_ofst, ps_cur_mb, + i4_wd_y, i4_wd_uv, ps_top_mb, + ps_left_mb, u1_cur_fld, + u1_extra_top_edge); + } + + ps_cur_mb++; + + u1_deb_mode = ps_cur_mb->u1_deblocking_mode; + if(!(u1_deb_mode & MB_DISABLE_FILTERING)) + { + ps_tfr_cxt->pu1_mb_y = pu1_deb_y; + ps_tfr_cxt->pu1_mb_u = pu1_deb_u; + ps_tfr_cxt->pu1_mb_v = pu1_deb_v; + + u1_cur_fld = (ps_cur_mb->u1_mb_type & D_FLD_MB) >> 7; + u1_cur_fld &= 1; + if(i2_mb_x) + { + ps_left_mb = ps_cur_mb - 2; + u1_left_fld = (ps_left_mb->u1_mb_type & D_FLD_MB) + >> 7; + } + else + { + ps_left_mb = NULL; + u1_left_fld = u1_cur_fld; + } + if(!u1_first_row) + { + ps_top_mb = ps_cur_mb - (u2_image_wd_mb << 1); + } + else + { + ps_top_mb = NULL; + } + + { + UWORD8 u1_row_shift_y = 0, u1_row_shift_uv = 0; + if(!u1_cur_fld) + { + ps_top_mb = ps_cur_mb - 1; + u1_top_fld = (ps_top_mb->u1_mb_type & D_FLD_MB) + >> 7; + u1_row_shift_y = 4; + u1_row_shift_uv = 3; + } + ps_tfr_cxt->pu1_mb_y += i4_wd_y << u1_row_shift_y; + ps_tfr_cxt->pu1_mb_u += + (i4_wd_uv << u1_row_shift_uv); + ps_tfr_cxt->pu1_mb_v += i4_wd_uv << u1_row_shift_uv; + } + + /* point to A if top else A+1 */ + if(u1_left_fld ^ u1_cur_fld) + ps_left_mb--; + + /********************************************************/ + /* if top MB and MB AFF and cur MB is frame and top is */ + /* field, then one extra top edge needs to be deblocked */ + /********************************************************/ + u1_extra_top_edge = 0; + + if(u1_deb_mode & MB_DISABLE_LEFT_EDGE) + ps_left_mb = NULL; + if(u1_deb_mode & MB_DISABLE_TOP_EDGE) + ps_top_mb = NULL; + + ih264d_deblock_mb_mbaff(ps_dec, ps_tfr_cxt, + i1_cb_qp_idx_ofst, + i1_cr_qp_idx_ofst, ps_cur_mb, + i4_wd_y, i4_wd_uv, ps_top_mb, + ps_left_mb, u1_cur_fld, + u1_extra_top_edge); + } + + ps_cur_mb++; + i2_mb_x++; + + pu1_deb_y += 16; + pu1_deb_u += 8 * YUV420SP_FACTOR; + pu1_deb_v += 8; + + } + while(u2_image_wd_mb > i2_mb_x); + + pu1_deb_y += ps_tfr_cxt->u4_y_inc; + pu1_deb_u += ps_tfr_cxt->u4_uv_inc; + pu1_deb_v += ps_tfr_cxt->u4_uv_inc; + + i2_mb_x = 0; + i2_mb_y -= 2; + + u1_first_row = 0; + + } + } + + } + //Padd the Picture + //Horizontal Padd + + if(ps_pad_mgr->u1_horz_pad) + { + UWORD32 u1_field_pic_flag = ps_dec->ps_cur_slice->u1_field_pic_flag; + ps_dec->pf_pad_left_luma(ps_tfr_cxt->pu1_src_y + 4, + ps_dec->u2_frm_wd_y << u1_field_pic_flag, + ps_dec->u2_pic_ht >> u1_field_pic_flag, + PAD_LEN_Y_H); + ps_dec->pf_pad_right_luma( + ps_tfr_cxt->pu1_src_y + 4 + + (ps_dec->u2_frm_wd_in_mbs << 4), + ps_dec->u2_frm_wd_y << u1_field_pic_flag, + ps_dec->u2_pic_ht >> u1_field_pic_flag, PAD_LEN_Y_H); + + ps_dec->pf_pad_left_chroma(ps_tfr_cxt->pu1_src_u + 4, + ps_dec->u2_frm_wd_uv << u1_field_pic_flag, + (ps_dec->u2_pic_ht / 2) >> u1_field_pic_flag, + PAD_LEN_UV_H * YUV420SP_FACTOR); + ps_dec->pf_pad_right_chroma( + ps_tfr_cxt->pu1_src_u + 4 + + (ps_dec->u2_frm_wd_in_mbs << 4), + ps_dec->u2_frm_wd_uv << u1_field_pic_flag, + (ps_dec->u2_pic_ht / 2) >> u1_field_pic_flag, + PAD_LEN_UV_H * YUV420SP_FACTOR); + + } + +//Vertical Padd Top + if(ps_pad_mgr->u1_vert_pad_top) + { + ps_dec->pf_pad_top(ps_dec->ps_cur_pic->pu1_buf1 - PAD_LEN_Y_H, + ps_dec->u2_frm_wd_y, ps_dec->u2_frm_wd_y, + ps_pad_mgr->u1_pad_len_y_v); + ps_dec->pf_pad_top( + ps_dec->ps_cur_pic->pu1_buf2 + - PAD_LEN_UV_H * YUV420SP_FACTOR, + ps_dec->u2_frm_wd_uv, ps_dec->u2_frm_wd_uv, + ps_pad_mgr->u1_pad_len_cr_v); + ps_pad_mgr->u1_vert_pad_top = 0; + } + +//Vertical Padd Bottom + if(ps_pad_mgr->u1_vert_pad_bot) + { + + UWORD8 *pu1_buf; + pu1_buf = ps_dec->ps_cur_pic->pu1_buf1 - PAD_LEN_Y_H; + pu1_buf += ps_dec->u2_pic_ht * ps_dec->u2_frm_wd_y; + ps_dec->pf_pad_bottom(pu1_buf, ps_dec->u2_frm_wd_y, ps_dec->u2_frm_wd_y, + ps_pad_mgr->u1_pad_len_y_v); + pu1_buf = ps_dec->ps_cur_pic->pu1_buf2 - PAD_LEN_UV_H * YUV420SP_FACTOR; + pu1_buf += (ps_dec->u2_pic_ht >> 1) * ps_dec->u2_frm_wd_uv; + + ps_dec->pf_pad_bottom(pu1_buf, ps_dec->u2_frm_wd_uv, + ps_dec->u2_frm_wd_uv, + ps_pad_mgr->u1_pad_len_cr_v); + + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_deblock_picture_non_mbaff */ +/* */ +/* Description : This function carries out deblocking on a whole picture */ +/* without MBAFF */ +/* */ +/* Inputs : <What inputs does the function take?> */ +/* Processing : This functions calls deblock MB in the MB increment order*/ +/* */ +/* Outputs : Produces the deblocked picture */ +/* Returns : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 17 02 2005 NS Creation */ +/* 14 06 2005 SWRN clean-up */ +/*****************************************************************************/ + +void ih264d_deblock_picture_non_mbaff(dec_struct_t * ps_dec) +{ + deblk_mb_t *ps_cur_mb; + + UWORD8 u1_vert_pad_top = 1; + + UWORD8 u1_deb_mode; + WORD32 i4_wd_y, i4_wd_uv; + + UWORD8 u1_field_pic_flag = ps_dec->ps_cur_slice->u1_field_pic_flag; /*< Field u4_flag */ + UWORD8 u1_bottom_field_flag = ps_dec->ps_cur_slice->u1_bottom_field_flag; /*< Bottom field u4_flag */ + + /**************************************************/ + /* one time loads from ps_dec which will be used */ + /* frequently throughout the deblocking procedure */ + /**************************************************/ + pad_mgr_t * ps_pad_mgr = &ps_dec->s_pad_mgr; + tfr_ctxt_t s_tfr_ctxt; + tfr_ctxt_t * ps_tfr_cxt = &s_tfr_ctxt; // = &ps_dec->s_tran_addrecon; + + UWORD16 u2_image_wd_mb = ps_dec->u2_frm_wd_in_mbs; + UWORD16 u2_image_ht_mb = ps_dec->u2_frm_ht_in_mbs; + WORD8 i1_cb_qp_idx_ofst = ps_dec->ps_cur_pps->i1_chroma_qp_index_offset; + WORD8 i1_cr_qp_idx_ofst = + ps_dec->ps_cur_pps->i1_second_chroma_qp_index_offset; + + /* Set up Parameter for DMA transfer */ + ih264d_init_deblk_tfr_ctxt(ps_dec, ps_pad_mgr, ps_tfr_cxt, u2_image_wd_mb, + 0); + + /* Pic level Initialisations */ + + + + i4_wd_y = ps_dec->u2_frm_wd_y << u1_field_pic_flag; + i4_wd_uv = ps_dec->u2_frm_wd_uv << u1_field_pic_flag; + /* Initial filling of the buffers with deblocking data */ + + ps_cur_mb = ps_dec->ps_deblk_pic; + + if(ps_dec->u4_app_disable_deblk_frm == 0) + { + if(ps_dec->ps_cur_sps->u1_mb_aff_flag == 1) + { + while( ps_dec->u4_deblk_mb_y < u2_image_ht_mb) + { + ih264d_deblock_mb_nonmbaff(ps_dec, ps_tfr_cxt, + i1_cb_qp_idx_ofst, + i1_cr_qp_idx_ofst, + i4_wd_y, i4_wd_uv); + ps_cur_mb++; + } + } + + } + + //Padd the Picture + //Horizontal Padd + if(ps_pad_mgr->u1_horz_pad) + { + UWORD32 u1_field_pic_flag = ps_dec->ps_cur_slice->u1_field_pic_flag; + ps_dec->pf_pad_left_luma(ps_tfr_cxt->pu1_src_y + 4, + ps_dec->u2_frm_wd_y << u1_field_pic_flag, + ps_dec->u2_pic_ht >> u1_field_pic_flag, + PAD_LEN_Y_H); + ps_dec->pf_pad_right_luma( + ps_tfr_cxt->pu1_src_y + 4 + + (ps_dec->u2_frm_wd_in_mbs << 4), + ps_dec->u2_frm_wd_y << u1_field_pic_flag, + ps_dec->u2_pic_ht >> u1_field_pic_flag, PAD_LEN_Y_H); + + ps_dec->pf_pad_left_chroma(ps_tfr_cxt->pu1_src_u + 4, + ps_dec->u2_frm_wd_uv << u1_field_pic_flag, + (ps_dec->u2_pic_ht / 2) >> u1_field_pic_flag, + PAD_LEN_UV_H * YUV420SP_FACTOR); + ps_dec->pf_pad_right_chroma( + ps_tfr_cxt->pu1_src_u + 4 + + (ps_dec->u2_frm_wd_in_mbs << 4), + ps_dec->u2_frm_wd_uv << u1_field_pic_flag, + (ps_dec->u2_pic_ht / 2) >> u1_field_pic_flag, + PAD_LEN_UV_H * YUV420SP_FACTOR); + + } + +//Vertical Padd Top + if(ps_pad_mgr->u1_vert_pad_top) + { + ps_dec->pf_pad_top(ps_dec->ps_cur_pic->pu1_buf1 - PAD_LEN_Y_H, + ps_dec->u2_frm_wd_y, ps_dec->u2_frm_wd_y, + ps_pad_mgr->u1_pad_len_y_v); + ps_dec->pf_pad_top( + ps_dec->ps_cur_pic->pu1_buf2 + - PAD_LEN_UV_H * YUV420SP_FACTOR, + ps_dec->u2_frm_wd_uv, ps_dec->u2_frm_wd_uv, + ps_pad_mgr->u1_pad_len_cr_v); + ps_pad_mgr->u1_vert_pad_top = 0; + } + +//Vertical Padd Bottom + if(ps_pad_mgr->u1_vert_pad_bot) + { + + UWORD8 *pu1_buf; + pu1_buf = ps_dec->ps_cur_pic->pu1_buf1 - PAD_LEN_Y_H; + pu1_buf += ps_dec->u2_pic_ht * ps_dec->u2_frm_wd_y; + ps_dec->pf_pad_bottom(pu1_buf, ps_dec->u2_frm_wd_y, ps_dec->u2_frm_wd_y, + ps_pad_mgr->u1_pad_len_y_v); + pu1_buf = ps_dec->ps_cur_pic->pu1_buf2 - PAD_LEN_UV_H * YUV420SP_FACTOR; + pu1_buf += (ps_dec->u2_pic_ht >> 1) * ps_dec->u2_frm_wd_uv; + + ps_dec->pf_pad_bottom(pu1_buf, ps_dec->u2_frm_wd_uv, + ps_dec->u2_frm_wd_uv, + ps_pad_mgr->u1_pad_len_cr_v); + + } +} + +void ih264d_deblock_picture_progressive(dec_struct_t * ps_dec) +{ + deblk_mb_t *ps_cur_mb; + + UWORD8 u1_vert_pad_top = 1; + UWORD8 u1_mbs_next; + UWORD8 u1_deb_mode; + WORD32 i4_wd_y, i4_wd_uv; + + + /**************************************************/ + /* one time loads from ps_dec which will be used */ + /* frequently throughout the deblocking procedure */ + /**************************************************/ + pad_mgr_t * ps_pad_mgr = &ps_dec->s_pad_mgr; + + tfr_ctxt_t s_tfr_ctxt; + tfr_ctxt_t * ps_tfr_cxt = &s_tfr_ctxt; // = &ps_dec->s_tran_addrecon; + UWORD16 u2_image_wd_mb = ps_dec->u2_frm_wd_in_mbs; + UWORD16 u2_image_ht_mb = ps_dec->u2_frm_ht_in_mbs; + UWORD8 u1_mbaff = ps_dec->ps_cur_slice->u1_mbaff_frame_flag; + + WORD8 i1_cb_qp_idx_ofst = ps_dec->ps_cur_pps->i1_chroma_qp_index_offset; + WORD8 i1_cr_qp_idx_ofst = + ps_dec->ps_cur_pps->i1_second_chroma_qp_index_offset; + + /* Set up Parameter for deblocking */ + ih264d_init_deblk_tfr_ctxt(ps_dec, ps_pad_mgr, ps_tfr_cxt, u2_image_wd_mb, + 0); + + /* Pic level Initialisations */ + + i4_wd_y = ps_dec->u2_frm_wd_y; + i4_wd_uv = ps_dec->u2_frm_wd_uv; + /* Initial filling of the buffers with deblocking data */ + ps_cur_mb = ps_dec->ps_deblk_pic; + + if(ps_dec->u4_app_disable_deblk_frm == 0) + { + if(ps_dec->ps_cur_sps->u1_mb_aff_flag == 1) + { + while( ps_dec->u4_deblk_mb_y < u2_image_ht_mb) + { + ih264d_deblock_mb_nonmbaff(ps_dec, ps_tfr_cxt, + i1_cb_qp_idx_ofst, + i1_cr_qp_idx_ofst, + i4_wd_y, i4_wd_uv); + ps_cur_mb++; + } + } + + } + + //Padd the Picture + //Horizontal Padd + if(ps_pad_mgr->u1_horz_pad) + { + UWORD32 u1_field_pic_flag = ps_dec->ps_cur_slice->u1_field_pic_flag; + ps_dec->pf_pad_left_luma(ps_tfr_cxt->pu1_src_y + 4, + ps_dec->u2_frm_wd_y << u1_field_pic_flag, + ps_dec->u2_pic_ht >> u1_field_pic_flag, + PAD_LEN_Y_H); + ps_dec->pf_pad_right_luma( + ps_tfr_cxt->pu1_src_y + 4 + + (ps_dec->u2_frm_wd_in_mbs << 4), + ps_dec->u2_frm_wd_y << u1_field_pic_flag, + ps_dec->u2_pic_ht >> u1_field_pic_flag, PAD_LEN_Y_H); + + ps_dec->pf_pad_left_chroma(ps_tfr_cxt->pu1_src_u + 4, + ps_dec->u2_frm_wd_uv << u1_field_pic_flag, + (ps_dec->u2_pic_ht / 2) >> u1_field_pic_flag, + PAD_LEN_UV_H * YUV420SP_FACTOR); + ps_dec->pf_pad_right_chroma( + ps_tfr_cxt->pu1_src_u + 4 + + (ps_dec->u2_frm_wd_in_mbs << 4), + ps_dec->u2_frm_wd_uv << u1_field_pic_flag, + (ps_dec->u2_pic_ht / 2) >> u1_field_pic_flag, + PAD_LEN_UV_H * YUV420SP_FACTOR); + + } + +//Vertical Padd Top + if(ps_pad_mgr->u1_vert_pad_top) + { + ps_dec->pf_pad_top(ps_dec->ps_cur_pic->pu1_buf1 - PAD_LEN_Y_H, + ps_dec->u2_frm_wd_y, ps_dec->u2_frm_wd_y, + ps_pad_mgr->u1_pad_len_y_v); + ps_dec->pf_pad_top( + ps_dec->ps_cur_pic->pu1_buf2 + - PAD_LEN_UV_H * YUV420SP_FACTOR, + ps_dec->u2_frm_wd_uv, ps_dec->u2_frm_wd_uv, + ps_pad_mgr->u1_pad_len_cr_v); + + } + +//Vertical Padd Bottom + if(ps_pad_mgr->u1_vert_pad_bot) + { + + UWORD8 *pu1_buf; + pu1_buf = ps_dec->ps_cur_pic->pu1_buf1 - PAD_LEN_Y_H; + pu1_buf += ps_dec->u2_pic_ht * ps_dec->u2_frm_wd_y; + ps_dec->pf_pad_bottom(pu1_buf, ps_dec->u2_frm_wd_y, ps_dec->u2_frm_wd_y, + ps_pad_mgr->u1_pad_len_y_v); + pu1_buf = ps_dec->ps_cur_pic->pu1_buf2 - PAD_LEN_UV_H * YUV420SP_FACTOR; + pu1_buf += (ps_dec->u2_pic_ht >> 1) * ps_dec->u2_frm_wd_uv; + + ps_dec->pf_pad_bottom(pu1_buf, ps_dec->u2_frm_wd_uv, + ps_dec->u2_frm_wd_uv, + ps_pad_mgr->u1_pad_len_cr_v); + + } +} + +/*! + ************************************************************************** + * \if Function name : ih264d_set_deblocking_parameters \endif + * + * \brief + * Sets the deblocking parameters of the macroblock + * + * \return + * 0 on Success and Error code otherwise + * + * \note + * Given the neighbour availablity information, and the deblocking + * parameters of the slice,this function will set the deblocking + * mode of the macroblock. + ************************************************************************** + */ + +WORD8 ih264d_set_deblocking_parameters(deblk_mb_t * ps_cur_mb, + dec_slice_params_t * ps_slice, + UWORD8 u1_mb_ngbr_availablity, + UWORD8 u1_mb_field_decoding_flag) +{ + /*------------------------------------------------------------------*/ + /* Set the deblocking parameters */ + /*------------------------------------------------------------------*/ + ps_cur_mb->i1_slice_alpha_c0_offset = ps_slice->i1_slice_alpha_c0_offset; + ps_cur_mb->i1_slice_beta_offset = ps_slice->i1_slice_beta_offset; + ps_cur_mb->u1_mb_type = (u1_mb_field_decoding_flag << 7); + + switch(ps_slice->u1_disable_dblk_filter_idc) + { + case DBLK_ENABLED: + ps_cur_mb->u1_deblocking_mode = MB_ENABLE_FILTERING; + break; + case DBLK_DISABLED: + ps_cur_mb->u1_deblocking_mode = MB_DISABLE_FILTERING; + break; + case SLICE_BOUNDARY_DBLK_DISABLED: + { + ps_cur_mb->u1_deblocking_mode = MB_ENABLE_FILTERING; + if(!(u1_mb_ngbr_availablity & LEFT_MB_AVAILABLE_MASK)) + ps_cur_mb->u1_deblocking_mode |= MB_DISABLE_LEFT_EDGE; + if(!(u1_mb_ngbr_availablity & TOP_MB_AVAILABLE_MASK)) + ps_cur_mb->u1_deblocking_mode |= MB_DISABLE_TOP_EDGE; + break; + } + } + + return (0); +} + +void ih264d_copy_intra_pred_line(dec_struct_t *ps_dec, + dec_mb_info_t *ps_cur_mb_info, + UWORD32 nmb_index) +{ + UWORD8 *pu1_mb_last_row, u1_mb_field_decoding_flag; + UWORD32 u4_recWidth, u4_recwidth_cr; + + u1_mb_field_decoding_flag = ps_cur_mb_info->u1_mb_field_decodingflag; + + u4_recWidth = ps_dec->u2_frm_wd_y << u1_mb_field_decoding_flag; + u4_recwidth_cr = ps_dec->u2_frm_wd_uv << u1_mb_field_decoding_flag; + + pu1_mb_last_row = ps_dec->ps_frame_buf_ip_recon->pu1_dest_y + + (u4_recWidth * (MB_SIZE - 1)); + pu1_mb_last_row += MB_SIZE * nmb_index; + MEMCPY_16BYTES(ps_dec->pu1_cur_y_intra_pred_line, pu1_mb_last_row); + + pu1_mb_last_row = ps_dec->ps_frame_buf_ip_recon->pu1_dest_u + + (u4_recwidth_cr * (BLK8x8SIZE - 1)); + pu1_mb_last_row += BLK8x8SIZE * nmb_index * YUV420SP_FACTOR; + + MEMCPY_16BYTES(ps_dec->pu1_cur_u_intra_pred_line, pu1_mb_last_row); + + ps_dec->pu1_cur_y_intra_pred_line = ps_dec->pu1_cur_y_intra_pred_line_base + + (MB_SIZE * (ps_cur_mb_info->u2_mbx + 1)); + ps_dec->pu1_cur_u_intra_pred_line = ps_dec->pu1_cur_u_intra_pred_line_base + + (BLK8x8SIZE * (ps_cur_mb_info->u2_mbx + 1)) + * YUV420SP_FACTOR; + ps_dec->pu1_cur_v_intra_pred_line = ps_dec->pu1_cur_v_intra_pred_line_base + + (BLK8x8SIZE * (ps_cur_mb_info->u2_mbx + 1)); + + if(ps_cur_mb_info->u2_mbx == (ps_dec->u2_frm_wd_in_mbs - 1)) + { + UWORD8* pu1_temp; + + ps_dec->pu1_cur_y_intra_pred_line = + ps_dec->pu1_cur_y_intra_pred_line_base; + ps_dec->pu1_cur_u_intra_pred_line = + ps_dec->pu1_cur_u_intra_pred_line_base; + ps_dec->pu1_cur_v_intra_pred_line = + ps_dec->pu1_cur_v_intra_pred_line_base; + + /*swap current and previous rows*/ + pu1_temp = ps_dec->pu1_cur_y_intra_pred_line; + ps_dec->pu1_cur_y_intra_pred_line = ps_dec->pu1_prev_y_intra_pred_line; + ps_dec->pu1_prev_y_intra_pred_line = pu1_temp; + + pu1_temp = ps_dec->pu1_cur_u_intra_pred_line; + ps_dec->pu1_cur_u_intra_pred_line = ps_dec->pu1_prev_u_intra_pred_line; + ps_dec->pu1_prev_u_intra_pred_line = pu1_temp; + + pu1_temp = ps_dec->pu1_cur_v_intra_pred_line; + ps_dec->pu1_cur_v_intra_pred_line = ps_dec->pu1_prev_v_intra_pred_line; + ps_dec->pu1_prev_v_intra_pred_line = pu1_temp; + + ps_dec->pu1_cur_y_intra_pred_line_base = + ps_dec->pu1_cur_y_intra_pred_line; + ps_dec->pu1_cur_u_intra_pred_line_base = + ps_dec->pu1_cur_u_intra_pred_line; + ps_dec->pu1_cur_v_intra_pred_line_base = + ps_dec->pu1_cur_v_intra_pred_line; + + + + + + } + +} + + +void ih264d_filter_boundary_left_mbaff(dec_struct_t *ps_dec, + tfr_ctxt_t * ps_tfr_cxt, + WORD8 i1_cb_qp_idx_ofst, + WORD8 i1_cr_qp_idx_ofst, + deblk_mb_t * ps_cur_mb, + WORD32 i4_strd_y, + WORD32 i4_strd_uv, + deblk_mb_t * ps_left_mb, /* Neighbouring MB parameters */ + UWORD32 pu4_bs_tab[], /* pointer to the BsTable array */ + UWORD8 u1_cur_fld) +{ + UWORD8 *pu1_y, *pu1_u, *pu1_v; + UWORD8 uc_tmp, qp_avg; + WORD32 alpha_u = 0, beta_u = 0, alpha_v = 0, beta_v = 0; + WORD32 alpha_y = 0, beta_y = 0; + + WORD32 idx_b_u, idx_a_u, idx_b_v, idx_a_v; + WORD32 idx_b_y, idx_a_y; + + UWORD32 u4_bs_val; + + UWORD8 *pu1_cliptab_u, *pu1_cliptab_v, *pu1_cliptab_y; + + UWORD8 u1_double_cl = !ps_cur_mb->u1_single_call; + WORD32 ofst_a = ps_cur_mb->i1_slice_alpha_c0_offset; + WORD32 ofst_b = ps_cur_mb->i1_slice_beta_offset; + + PROFILE_DISABLE_DEBLK() + + pu1_y = ps_tfr_cxt->pu1_mb_y; + pu1_u = ps_tfr_cxt->pu1_mb_u; + pu1_v = ps_tfr_cxt->pu1_mb_v; + + /* LUMA values */ + /* Deblock rounding change */ + uc_tmp = (UWORD8)((ps_left_mb->u1_mb_qp + ps_cur_mb->u1_mb_qp + 1) >> 1); + qp_avg = uc_tmp; + idx_a_y = qp_avg + ofst_a; + alpha_y = gau1_ih264d_alpha_table[12 + idx_a_y]; + idx_b_y = qp_avg + ofst_b; + beta_y = gau1_ih264d_beta_table[12 + idx_b_y]; + + /* Chroma cb values */ + { + WORD32 mb_qp1, mb_qp2; + mb_qp1 = (ps_left_mb->u1_mb_qp + i1_cb_qp_idx_ofst); + mb_qp2 = (ps_cur_mb->u1_mb_qp + i1_cb_qp_idx_ofst); + qp_avg = (UWORD8)((gau1_ih264d_qp_scale_cr[12 + mb_qp1] + + gau1_ih264d_qp_scale_cr[12 + mb_qp2] + 1) >> 1); + } + idx_a_u = qp_avg + ofst_a; + alpha_u = gau1_ih264d_alpha_table[12 + idx_a_u]; + idx_b_u = qp_avg + ofst_b; + beta_u = gau1_ih264d_beta_table[12 + idx_b_u]; + + /* Chroma cr values */ + { + WORD32 mb_qp1, mb_qp2; + mb_qp1 = (ps_left_mb->u1_mb_qp + i1_cr_qp_idx_ofst); + mb_qp2 = (ps_cur_mb->u1_mb_qp + i1_cr_qp_idx_ofst); + qp_avg = (UWORD8)((gau1_ih264d_qp_scale_cr[12 + mb_qp1] + + gau1_ih264d_qp_scale_cr[12 + mb_qp2] + 1) >> 1); + } + idx_a_v = qp_avg + ofst_a; + alpha_v = gau1_ih264d_alpha_table[12 + idx_a_v]; + idx_b_v = qp_avg + ofst_b; + beta_v = gau1_ih264d_beta_table[12 + idx_b_v]; + + if(u1_double_cl == 0) + { + u4_bs_val = pu4_bs_tab[4]; + + if(0x04040404 == u4_bs_val) + { + ps_dec->pf_deblk_luma_vert_bs4(pu1_y, i4_strd_y, alpha_y, beta_y); + ps_dec->pf_deblk_chroma_vert_bs4(pu1_u, i4_strd_uv, alpha_u, + beta_u, alpha_v, beta_v); + + } + else + { + if(u4_bs_val) + { + + pu1_cliptab_y = (UWORD8 *)&gau1_ih264d_clip_table[12 + + idx_a_y]; + pu1_cliptab_u = (UWORD8 *)&gau1_ih264d_clip_table[12 + + idx_a_u]; + pu1_cliptab_v = (UWORD8 *)&gau1_ih264d_clip_table[12 + + idx_a_v]; + + ps_dec->pf_deblk_luma_vert_bslt4(pu1_y, i4_strd_y, alpha_y, + beta_y, u4_bs_val, + pu1_cliptab_y); + ps_dec->pf_deblk_chroma_vert_bslt4(pu1_u, i4_strd_uv, alpha_u, + beta_u, alpha_v, beta_v, + u4_bs_val, pu1_cliptab_u, + pu1_cliptab_v); + + } + } + + } + else + { + + i4_strd_y <<= (!u1_cur_fld); + u4_bs_val = pu4_bs_tab[4]; + i4_strd_uv <<= (!u1_cur_fld); + + if(0x04040404 == u4_bs_val) + { + ps_dec->pf_deblk_luma_vert_bs4_mbaff(pu1_y, i4_strd_y, alpha_y, + beta_y); + ps_dec->pf_deblk_chroma_vert_bs4_mbaff(pu1_u, i4_strd_uv, alpha_u, + beta_u, alpha_v, beta_v); + } + else + { + if(u4_bs_val) + { + + pu1_cliptab_y = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_y]; + pu1_cliptab_u = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_u]; + pu1_cliptab_v = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_v]; + ps_dec->pf_deblk_luma_vert_bslt4_mbaff(pu1_y, i4_strd_y, + alpha_y, beta_y, + u4_bs_val, + pu1_cliptab_y); + ps_dec->pf_deblk_chroma_vert_bslt4_mbaff(pu1_u, i4_strd_uv, + alpha_u, beta_u, + alpha_v, beta_v, + u4_bs_val, + pu1_cliptab_u, + pu1_cliptab_v); + + } + } + + { + + UWORD16 u2_shift = (i4_strd_y >> 1) << (u1_cur_fld ? 4 : 0); + pu1_y += u2_shift; + u2_shift = (i4_strd_uv >> 1) << (u1_cur_fld ? 3 : 0); + pu1_u += u2_shift; + pu1_v += u2_shift; + } + + uc_tmp = (((ps_left_mb + 1)->u1_mb_qp + ps_cur_mb->u1_mb_qp + 1) >> 1); + qp_avg = uc_tmp; + idx_a_y = qp_avg + ofst_a; + alpha_y = gau1_ih264d_alpha_table[12 + idx_a_y]; + idx_b_y = qp_avg + ofst_b; + beta_y = gau1_ih264d_beta_table[12 + idx_b_y]; + u4_bs_val = pu4_bs_tab[9]; + + { + WORD32 mb_qp1, mb_qp2; + mb_qp1 = ((ps_left_mb + 1)->u1_mb_qp + i1_cb_qp_idx_ofst); + mb_qp2 = (ps_cur_mb->u1_mb_qp + i1_cb_qp_idx_ofst); + qp_avg = (UWORD8)((gau1_ih264d_qp_scale_cr[12 + mb_qp1] + + gau1_ih264d_qp_scale_cr[12 + mb_qp2] + 1) >> 1); + } + idx_a_u = qp_avg + ofst_a; + alpha_u = gau1_ih264d_alpha_table[12 + idx_a_u]; + idx_b_u = qp_avg + ofst_b; + beta_u = gau1_ih264d_beta_table[12 + idx_b_u]; + u4_bs_val = pu4_bs_tab[9]; + { + WORD32 mb_qp1, mb_qp2; + mb_qp1 = ((ps_left_mb + 1)->u1_mb_qp + i1_cr_qp_idx_ofst); + mb_qp2 = (ps_cur_mb->u1_mb_qp + i1_cr_qp_idx_ofst); + qp_avg = (UWORD8)((gau1_ih264d_qp_scale_cr[12 + mb_qp1] + + gau1_ih264d_qp_scale_cr[12 + mb_qp2] + 1) >> 1); + } + idx_a_v = qp_avg + ofst_a; + alpha_v = gau1_ih264d_alpha_table[12 + idx_a_v]; + idx_b_v = qp_avg + ofst_b; + beta_v = gau1_ih264d_beta_table[12 + idx_b_v]; + + if(0x04040404 == u4_bs_val) + { + ps_dec->pf_deblk_luma_vert_bs4_mbaff(pu1_y, i4_strd_y, alpha_y, + beta_y); + ps_dec->pf_deblk_chroma_vert_bs4_mbaff(pu1_u, i4_strd_uv, alpha_u, + beta_u, alpha_v, beta_v); + + } + else + { + if(u4_bs_val) + { + + pu1_cliptab_y = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_y]; + pu1_cliptab_u = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_u]; + pu1_cliptab_v = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_v]; + + ps_dec->pf_deblk_luma_vert_bslt4_mbaff(pu1_y, i4_strd_y, + alpha_y, beta_y, + u4_bs_val, + pu1_cliptab_y); + ps_dec->pf_deblk_chroma_vert_bslt4_mbaff(pu1_u, i4_strd_uv, + alpha_u, beta_u, + alpha_v, beta_v, + u4_bs_val, + pu1_cliptab_u, + pu1_cliptab_v); + + } + } + } + +} + +void ih264d_filter_boundary_topmbaff(dec_struct_t *ps_dec, + tfr_ctxt_t * ps_tfr_cxt, + WORD8 i1_cb_qp_idx_ofst, + WORD8 i1_cr_qp_idx_ofst, + deblk_mb_t * ps_cur_mb, + WORD32 i4_strd_y, + WORD32 i4_strd_uv, + deblk_mb_t * ps_top_mb, + UWORD32 u4_bs) +{ + UWORD8 *pu1_y, *pu1_u; + WORD32 alpha_u = 0, beta_u = 0, alpha_v = 0, beta_v = 0; + WORD32 alpha_y = 0, beta_y = 0; + WORD32 qp_avg; + WORD32 idx_b_u, idx_a_u, idx_b_v, idx_a_v; + WORD32 idx_b_y, idx_a_y; + UWORD16 uc_tmp; + + UWORD8 *pu1_cliptab_u, *pu1_cliptab_v, *pu1_cliptab_y; + WORD32 ofst_a = ps_cur_mb->i1_slice_alpha_c0_offset; + WORD32 ofst_b = ps_cur_mb->i1_slice_beta_offset; + + /* LUMA values */ + /* Deblock rounding change */ + uc_tmp = ((ps_top_mb->u1_mb_qp + ps_cur_mb->u1_mb_qp + 1) >> 1); + qp_avg = (UWORD8)uc_tmp; + idx_a_y = qp_avg + ofst_a; + alpha_y = gau1_ih264d_alpha_table[12 + idx_a_y]; + idx_b_y = qp_avg + ofst_b; + beta_y = gau1_ih264d_beta_table[12 + idx_b_y]; + pu1_y = ps_tfr_cxt->pu1_mb_y; + + /* CHROMA cb values */ + { + WORD32 mb_qp1, mb_qp2; + mb_qp1 = (ps_top_mb->u1_mb_qp + i1_cb_qp_idx_ofst); + mb_qp2 = (ps_cur_mb->u1_mb_qp + i1_cb_qp_idx_ofst); + qp_avg = (UWORD8)((gau1_ih264d_qp_scale_cr[12 + mb_qp1] + + gau1_ih264d_qp_scale_cr[12 + mb_qp2] + 1) >> 1); + } + + idx_a_u = qp_avg + ofst_a; + alpha_u = gau1_ih264d_alpha_table[12 + idx_a_u]; + idx_b_u = qp_avg + ofst_b; + beta_u = gau1_ih264d_beta_table[12 + idx_b_u]; + /* CHROMA cr values */ + { + WORD32 mb_qp1, mb_qp2; + mb_qp1 = (ps_top_mb->u1_mb_qp + i1_cr_qp_idx_ofst); + mb_qp2 = (ps_cur_mb->u1_mb_qp + i1_cr_qp_idx_ofst); + qp_avg = (UWORD8)((gau1_ih264d_qp_scale_cr[12 + mb_qp1] + + gau1_ih264d_qp_scale_cr[12 + mb_qp2] + 1) >> 1); + } + + idx_a_v = qp_avg + ofst_a; + alpha_v = gau1_ih264d_alpha_table[12 + idx_a_v]; + idx_b_v = qp_avg + ofst_b; + beta_v = gau1_ih264d_beta_table[12 + idx_b_v]; + pu1_u = ps_tfr_cxt->pu1_mb_u; + + if(u4_bs == 0x04040404) + { + /* Code specific to the assembly module */ + ps_dec->pf_deblk_luma_horz_bs4(pu1_y, i4_strd_y, alpha_y, beta_y); + ps_dec->pf_deblk_chroma_horz_bs4(pu1_u, i4_strd_uv, alpha_u, beta_u, + alpha_v, beta_v); + + } + else + { + if(u4_bs) + { + + pu1_cliptab_y = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_y]; + pu1_cliptab_u = + (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_u]; + pu1_cliptab_v = + (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_v]; + + ps_dec->pf_deblk_luma_horz_bslt4(pu1_y, i4_strd_y, alpha_y, beta_y, + u4_bs, pu1_cliptab_y); + ps_dec->pf_deblk_chroma_horz_bslt4(pu1_u, i4_strd_uv, alpha_u, + beta_u, alpha_v, beta_v, + u4_bs, pu1_cliptab_u, + pu1_cliptab_v); + + } + } + +} + +void ih264d_deblock_mb_mbaff(dec_struct_t *ps_dec, + tfr_ctxt_t * ps_tfr_cxt, + WORD8 i1_cb_qp_idx_ofst, + WORD8 i1_cr_qp_idx_ofst, + deblk_mb_t * ps_cur_mb, + WORD32 i4_strd_y, + WORD32 i4_strd_uv, + deblk_mb_t * ps_top_mb, + deblk_mb_t * ps_left_mb, + UWORD8 u1_cur_fld, + UWORD8 u1_extra_top_edge) +{ + UWORD8 *pu1_y, *pu1_u; + UWORD32 u4_bs; +// WORD8 edge; + WORD32 alpha, beta, alpha_u, beta_u, alpha_v, beta_v; + + UWORD8 *pu1_cliptab_u; + UWORD8 *pu1_cliptab_v; + UWORD8 *pu1_cliptab_y; + + UWORD32 * pu4_bs_tab = ps_cur_mb->u4_bs_table; + WORD32 idx_a_y, idx_a_u, idx_a_v; + /* Return from here to switch off deblocking */ + PROFILE_DISABLE_DEBLK() + + i4_strd_y <<= u1_cur_fld; + i4_strd_uv <<= u1_cur_fld; + /*--------------------------------------------------------------------*/ + /* Filter wrt Left edge */ + /* except */ + /* - Left Egde is Picture Boundary */ + /* - Left Egde is part of Slice Boundary and Deblocking */ + /* parameters of slice disable Filtering of Slice Boundary Edges*/ + /*--------------------------------------------------------------------*/ + if(ps_left_mb) + ih264d_filter_boundary_left_mbaff(ps_dec, ps_tfr_cxt, i1_cb_qp_idx_ofst, + i1_cr_qp_idx_ofst, ps_cur_mb, + i4_strd_y, i4_strd_uv, ps_left_mb, + pu4_bs_tab, u1_cur_fld); + + /*--------------------------------------------------------------------*/ + /* Filter wrt Other Vertical Edges */ + /*--------------------------------------------------------------------*/ + { + WORD32 ofst_a, ofst_b, idx_b_y, idx_b_u, + idx_b_v; + WORD32 qp_avg, qp_avg_u, qp_avg_v; + ofst_a = ps_cur_mb->i1_slice_alpha_c0_offset; + ofst_b = ps_cur_mb->i1_slice_beta_offset; + qp_avg = ps_cur_mb->u1_mb_qp; + idx_a_y = qp_avg + ofst_a; + alpha = gau1_ih264d_alpha_table[12 + idx_a_y]; + idx_b_y = qp_avg + ofst_b; + beta = gau1_ih264d_beta_table[12 + idx_b_y]; + + /* CHROMA Cb values */ + qp_avg_u = (qp_avg + i1_cb_qp_idx_ofst); + qp_avg_u = gau1_ih264d_qp_scale_cr[12 + qp_avg_u]; + idx_a_u = qp_avg_u + ofst_a; + alpha_u = gau1_ih264d_alpha_table[12 + idx_a_u]; + idx_b_u = qp_avg_u + ofst_b; + beta_u = gau1_ih264d_beta_table[12 + idx_b_u]; + /* CHROMA Cr values */ + qp_avg_v = (qp_avg + i1_cr_qp_idx_ofst); + qp_avg_v = gau1_ih264d_qp_scale_cr[12 + qp_avg_v]; + idx_a_v = qp_avg_v + ofst_a; + alpha_v = gau1_ih264d_alpha_table[12 + idx_a_v]; + idx_b_v = qp_avg_v + ofst_b; + beta_v = gau1_ih264d_beta_table[12 + idx_b_v]; + } + + //STARTL4_FILTER_VERT; + + pu1_cliptab_y = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_y]; //this for Luma + pu1_cliptab_u = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_u]; //this for chroma + pu1_cliptab_v = (UWORD8 *)&gau1_ih264d_clip_table[12 + idx_a_v]; //this for chroma + + //edge=1 + + + u4_bs = pu4_bs_tab[5]; + pu1_y = ps_tfr_cxt->pu1_mb_y; + pu1_u = ps_tfr_cxt->pu1_mb_u; + + if(u4_bs) + { + + ps_dec->pf_deblk_luma_vert_bslt4(pu1_y + 4, i4_strd_y, alpha, beta, + u4_bs, pu1_cliptab_y); + + } + //edge=2 + + u4_bs = pu4_bs_tab[6]; + if(u4_bs) + { + + ps_dec->pf_deblk_luma_vert_bslt4(pu1_y + 8, i4_strd_y, alpha, beta, + u4_bs, pu1_cliptab_y); + ps_dec->pf_deblk_chroma_vert_bslt4(pu1_u + 4 * YUV420SP_FACTOR, + i4_strd_uv, alpha_u, beta_u, + alpha_v, beta_v, u4_bs, + pu1_cliptab_u, pu1_cliptab_v); + } + //edge=3 + + u4_bs = pu4_bs_tab[7]; + if(u4_bs) + { + + ps_dec->pf_deblk_luma_vert_bslt4(pu1_y + 12, i4_strd_y, alpha, beta, + u4_bs, pu1_cliptab_y); + + } + + /*--------------------------------------------------------------------*/ + /* Filter wrt Top edge */ + /* except */ + /* - Top Egde is Picture Boundary */ + /* - Top Egde is part of Slice Boundary and Deblocking */ + /* parameters of slice disable Filtering of Slice Boundary Edges*/ + /*--------------------------------------------------------------------*/ + if(ps_top_mb) + { + /** if top MB and MB AFF and cur MB is frame and top is field then */ + /* one extra top edge needs to be deblocked */ + if(u1_extra_top_edge) + { + ih264d_filter_boundary_topmbaff(ps_dec, ps_tfr_cxt, + i1_cb_qp_idx_ofst, + i1_cr_qp_idx_ofst, ps_cur_mb, + (UWORD16)(i4_strd_y << 1), + (UWORD16)(i4_strd_uv << 1), + ps_top_mb - 1, pu4_bs_tab[8]); + ps_tfr_cxt->pu1_mb_y += i4_strd_y; + ps_tfr_cxt->pu1_mb_u += i4_strd_uv; + ps_tfr_cxt->pu1_mb_v += i4_strd_uv; + + ih264d_filter_boundary_topmbaff(ps_dec, ps_tfr_cxt, + i1_cb_qp_idx_ofst, + i1_cr_qp_idx_ofst, ps_cur_mb, + (UWORD16)(i4_strd_y << 1), + (UWORD16)(i4_strd_uv << 1), + ps_top_mb, pu4_bs_tab[0]); + ps_tfr_cxt->pu1_mb_y -= i4_strd_y; + ps_tfr_cxt->pu1_mb_u -= i4_strd_uv; + ps_tfr_cxt->pu1_mb_v -= i4_strd_uv; + } + else + { + ih264d_filter_boundary_topmbaff(ps_dec, ps_tfr_cxt, + i1_cb_qp_idx_ofst, + i1_cr_qp_idx_ofst, ps_cur_mb, + i4_strd_y, i4_strd_uv, ps_top_mb, + pu4_bs_tab[0]); + } + } + + /*--------------------------------------------------------------------*/ + /* Filter wrt Other Horizontal Edges */ + /*--------------------------------------------------------------------*/ + + //edge1 + u4_bs = pu4_bs_tab[1]; + + if(u4_bs) + { + ps_dec->pf_deblk_luma_horz_bslt4(pu1_y + (i4_strd_y << 2), i4_strd_y, + alpha, beta, u4_bs, pu1_cliptab_y); + + } + //edge2 + u4_bs = pu4_bs_tab[2]; + + if(u4_bs) + { + + ps_dec->pf_deblk_luma_horz_bslt4(pu1_y + (i4_strd_y << 3), i4_strd_y, + alpha, beta, u4_bs, pu1_cliptab_y); + ps_dec->pf_deblk_chroma_horz_bslt4(pu1_u + (i4_strd_uv << 2), + i4_strd_uv, alpha_u, beta_u, + alpha_v, beta_v, u4_bs, + pu1_cliptab_u, pu1_cliptab_v); + + } + //edge3 + u4_bs = pu4_bs_tab[3]; + if(u4_bs) + { + + ps_dec->pf_deblk_luma_horz_bslt4( + (pu1_y + (i4_strd_y << 3) + (i4_strd_y << 2)), + i4_strd_y, alpha, beta, u4_bs, pu1_cliptab_y); + + } + +} + diff --git a/dependencies/ih264d/decoder/ih264d_deblocking.h b/dependencies/ih264d/decoder/ih264d_deblocking.h new file mode 100644 index 00000000..5fe52cf8 --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_deblocking.h @@ -0,0 +1,174 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +#ifndef _IH264D_DEBLOCKING_H_ +#define _IH264D_DEBLOCKING_H_ +/*! + ************************************************************************** + * \file ih264d_deblocking.h + * + * \brief + * Declarations of deblocking functions + * + * \date + * 23/11/2002 + * + * \author AI + ************************************************************************** + */ +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264d_structs.h" + +WORD8 ih264d_set_deblocking_parameters(deblk_mb_t * ps_cur_deblk_mb, + dec_slice_params_t * ps_slice, + UWORD8 u1_mb_ngbr_availablity, + UWORD8 u1_mb_field_decoding_flag); + +void ih264d_copy_intra_pred_line(dec_struct_t *ps_dec, + dec_mb_info_t *ps_cur_mb_info, + UWORD32 nmb_index); + +void FilterBoundaryLeft(tfr_ctxt_t * const ps_tfr_cxt, + const WORD8 i1_cb_qp_idx_ofst, + const WORD8 i1_cr_qp_idx_ofst, + deblk_mb_t * const ps_cur_mb, + UWORD16 u2_strd_y, + UWORD16 u2_strd_uv, + deblk_mb_t * const ps_left_mb, + const UWORD32 pu4_bs_tab[], + const UWORD8 u1_cur_fld); +void FilterBoundaryTop(tfr_ctxt_t * const ps_tfr_cxt, + const WORD8 i1_cb_qp_idx_ofst, + const WORD8 i1_cr_qp_idx_ofst, + deblk_mb_t * const ps_cur_mb, + const UWORD16 u2_strd_y, + const UWORD16 u2_strd_uv, + deblk_mb_t * const ps_top_mb, + const UWORD32 u4_bs); +void deblock_mb(tfr_ctxt_t * const ps_tfr_cxt, + const WORD8 i1_cb_qp_idx_ofst, + const WORD8 i1_cr_qp_idx_ofst, + deblk_mb_t * const ps_cur_mb, + WORD32 i4_strd_y, + WORD32 i4_strd_uv, + deblk_mb_t * const ps_top_mb, + deblk_mb_t * const ps_left_mb, + const UWORD8 u1_cur_fld, + const UWORD8 u1_extra_top_edge); +void ih264d_deblock_mb_mbaff(dec_struct_t *ps_dec, + tfr_ctxt_t * const ps_tfr_cxt, + const WORD8 i1_cb_qp_idx_ofst, + const WORD8 i1_cr_qp_idx_ofst, + deblk_mb_t * const ps_cur_mb, + WORD32 i4_strd_y, + WORD32 i4_strd_uv, + deblk_mb_t * const ps_top_mb, + deblk_mb_t * const ps_left_mb, + const UWORD8 u1_cur_fld, + const UWORD8 u1_extra_top_edge); + +void ih264d_deblock_picture_mbaff(dec_struct_t * const ps_dec); + +void ih264d_deblock_picture_non_mbaff(dec_struct_t * const ps_dec); + +void ih264d_deblock_picture_progressive(dec_struct_t * const ps_dec); + +void ih264d_compute_bs_mbaff(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info, + const UWORD16 u2_mbxn_mb); +void ih264d_compute_bs_non_mbaff(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info, + const UWORD16 u2_mbxn_mb); + +void ih264d_fill_bs_mbedge_2(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info, + const UWORD16 u2_mbxn_mb); + +void ih264d_fill_bs_mbedge_4(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info, + const UWORD16 u2_mbxn_mb); + +void ih264d_fill_bs1_16x16mb_pslice(mv_pred_t *ps_cur_mv_pred, + mv_pred_t *ps_top_mv_pred, + void **ppv_map_ref_idx_to_poc, + UWORD32 *pu4_bs_table, + mv_pred_t *ps_leftmost_mv_pred, + neighbouradd_t *ps_left_addr, + void **u4_pic_addrress, + WORD32 i4_ver_mvlimit); + +void ih264d_fill_bs1_non16x16mb_pslice(mv_pred_t *ps_cur_mv_pred, + mv_pred_t *ps_top_mv_pred, + void **ppv_map_ref_idx_to_poc, + UWORD32 *pu4_bs_table, + mv_pred_t *ps_leftmost_mv_pred, + neighbouradd_t *ps_left_addr, + void **u4_pic_addrress, + WORD32 i4_ver_mvlimit); + +void ih264d_fill_bs1_16x16mb_bslice(mv_pred_t *ps_cur_mv_pred, + mv_pred_t *ps_top_mv_pred, + void **ppv_map_ref_idx_to_poc, + UWORD32 *pu4_bs_table, + mv_pred_t *ps_leftmost_mv_pred, + neighbouradd_t *ps_left_addr, + void **u4_pic_addrress, + WORD32 i4_ver_mvlimit); + +void ih264d_fill_bs1_non16x16mb_bslice(mv_pred_t *ps_cur_mv_pred, + mv_pred_t *ps_top_mv_pred, + void **ppv_map_ref_idx_to_poc, + UWORD32 *pu4_bs_table, + mv_pred_t *ps_leftmost_mv_pred, + neighbouradd_t *ps_left_addr, + void **u4_pic_addrress, + WORD32 i4_ver_mvlimit); + +void ih264d_fill_bs_xtra_left_edge_cur_fld(UWORD32 *pu4_bs, + WORD32 u4_left_mb_t_csbp, + WORD32 u4_left_mb_b_csbp, + WORD32 u4_cur_mb_csbp, + UWORD32 u4_cur_mb_top); + +void ih264d_fill_bs_xtra_left_edge_cur_frm(UWORD32 *pu4_bs, + WORD32 u4_left_mb_t_csbp, + WORD32 u4_left_mb_b_csbp, + WORD32 u4_cur_mb_csbp, + UWORD32 u4_cur_mb_top); + +void ih264d_deblock_mb_nonmbaff(dec_struct_t *ps_dec, + tfr_ctxt_t * const ps_tfr_cxt, + const WORD8 i1_cb_qp_idx_ofst, + const WORD8 i1_cr_qp_idx_ofst, + WORD32 i4_strd_y, + WORD32 i4_strd_uv); + +void ih264d_init_deblk_tfr_ctxt(dec_struct_t * ps_dec, + pad_mgr_t *ps_pad_mgr, + tfr_ctxt_t *ps_tfr_cxt, + UWORD16 u2_image_wd_mb, + UWORD8 u1_mbaff); + +void ih264d_deblock_mb_level(dec_struct_t *ps_dec, + dec_mb_info_t *ps_cur_mb_info, + UWORD32 nmb_index); + +#endif /* _IH264D_DEBLOCKING_H_ */ diff --git a/dependencies/ih264d/decoder/ih264d_debug.h b/dependencies/ih264d/decoder/ih264d_debug.h new file mode 100644 index 00000000..787b6979 --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_debug.h @@ -0,0 +1,135 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +#ifndef _IH264D_DEBUG_H_ +#define _IH264D_DEBUG_H_ + +/*! + ************************************************************************** + * \file ih264d_debug.h + * + * \brief + * Contains declarations used for debugging + * + * \date + * 2/12/2002 + * + * \author AI + ************************************************************************** + */ +#ifdef DEBUG_DEC +#define H264_DEC_DEBUG_PRINT(...) printf("\n[H264_DEBUG] %s/%d:: ", __FUNCTION__, __LINE__);printf(__VA_ARGS__) +#else //DEBUG_DEC +#define H264_DEC_DEBUG_PRINT(...) {} +#endif //DEBUG_DEC +#define STRENGTH_DEBLOCKING 0 //sanjeev +#define DEBUG_RECONSTRUCT_LUMA 0 +#define DEBUG_RECONSTRUCT_CHROMA 0 + +#define DEBUG_IDCT 0 +#define DEBUG_LUMA_IDCT 0 +#define DEBUG_REF_IDCT 0 + +#define BIN_BIT_RATIO 0 +#define MB_PART_HIST 0 + +#define MB_INTRA_PREDICTION 1 + +#ifdef WIN32 +#define CHK_PURIFY 0 +#else +#define CHK_PURIFY 0 +#endif + +#if MB_INTRA_PREDICTION +#define MB_INTRA_CHROMA_PREDICTION_ON 1 +#define MB_INTRA_4x4_PREDICTION_ON 1 +#define MB_INTRA_16x16_PREDICTION_ON 1 +#endif + +#define TRACE 0 +#define DEBUG_CABAC 0 +#define DEBUG_ABS_MVD 0 +#define DEBUG_INTRA_PRED_MODES 0 +#define DEBUG_DEBLOCKING 0 + +#define COPYTHECONTEXT(s,val) +#define PRINT_TRACE +#define PRINT_TRACE_CAB +#define SWITCHOFFTRACE +#define SWITCHONTRACE +#define SWITCHOFFTRACECABAC +#define SWITCHONTRACECABAC + +#define INC_BIN_COUNT(ps_cab_env) +#define INC_DECISION_BINS(ps_cab_env) +#define INC_BYPASS_BINS(ps_cab_env) +#define INC_SYM_COUNT(ps_cab_env) +#define PRINT_BIN_BIT_RATIO(ps_dec) +#define RESET_BIN_COUNTS(ps_cab_env) + + +#ifdef PROFILE_DIS_DEBLK +#define PROFILE_DISABLE_DEBLK() return; +#else +#define PROFILE_DISABLE_DEBLK() ; +#endif + +#ifdef PROFILE_DIS_IQ_IT_RECON +#define PROFILE_DISABLE_IQ_IT_RECON() if (0) +#define PROFILE_DISABLE_IQ_IT_RECON_RETURN() return; +#else +#define PROFILE_DISABLE_IQ_IT_RECON() ; +#define PROFILE_DISABLE_IQ_IT_RECON_RETURN() ; +#endif + +#ifdef PROFILE_DIS_INTRA_PRED +#define PROFILE_DISABLE_INTRA_PRED() if (0) +#else +#define PROFILE_DISABLE_INTRA_PRED() ; +#endif + +#ifdef PROFILE_DIS_UNPACK +#define PROFILE_DISABLE_UNPACK_LUMA() return 0; +#define PROFILE_DISABLE_UNPACK_CHROMA() return ; +#else +#define PROFILE_DISABLE_UNPACK_LUMA() ; +#define PROFILE_DISABLE_UNPACK_CHROMA() ; +#endif + +#ifdef PROFILE_DIS_INTER_PRED +#define PROFILE_DISABLE_INTER_PRED() return; +#else +#define PROFILE_DISABLE_INTER_PRED() ; +#endif + +#ifdef PROFILE_DIS_BOUNDARY_STRENGTH +#define PROFILE_DISABLE_BOUNDARY_STRENGTH() return; +#else +#define PROFILE_DISABLE_BOUNDARY_STRENGTH() ; +#endif + +#ifdef PROFILE_DIS_MB_PART_INFO +#define PROFILE_DISABLE_MB_PART_INFO() return 0; +#else +#define PROFILE_DISABLE_MB_PART_INFO() ; +#endif + +#endif /* _IH264D_DEBUG_H_ */ + diff --git a/dependencies/ih264d/decoder/ih264d_defs.h b/dependencies/ih264d/decoder/ih264d_defs.h new file mode 100644 index 00000000..aeb25204 --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_defs.h @@ -0,0 +1,660 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +#ifndef _IH264D_DEFS_H_ +#define _IH264D_DEFS_H_ + +/** + ************************************************************************ + * \file ih264d_defs.h + * + * \brief + * Type definitions used in the code + * + * \date + * 19/11/2002 + * + * \author Sriram Sethuraman + * + ************************************************************************ + */ +#include <stdint.h> + +#define H264_MAX_FRAME_WIDTH 4080 +#define H264_MAX_FRAME_HEIGHT 4080 +#define H264_MAX_FRAME_SIZE (4096 * 2048) + +#define H264_MIN_FRAME_WIDTH 16 +#define H264_MIN_FRAME_HEIGHT 16 + +#define FMT_CONV_NUM_ROWS 16 + +/** Decoder currently has an additional latency of 2 pictures when + * returning output for display + */ +#define DISPLAY_LATENCY 2 + +/** Bit manipulation macros */ +#define CHECKBIT(a,i) ((a) & (1 << i)) +#define CLEARBIT(a,i) ((a) &= ~(1 << i)) + +/** Macro to check if a number lies in the valid integer range */ +#define IS_OUT_OF_RANGE_S32(a) (((a) < INT32_MIN) || ((a) > INT32_MAX)) + +/** Macro to convert a integer to a boolean value */ +#define BOOLEAN(x) (!!(x)) + +/** Arithmetic operations */ +#define MOD(x,y) ((x)%(y)) +#define DIV(x,y) ((x)/(y)) +#define MUL(x,y) ((x)*(y)) +#define SIGN_POW2_DIV(x, y) (((x) < 0) ? (-((-(x)) >> (y))) : ((x) >> (y))) + +#define MB_ENABLE_FILTERING 0x00 +#define MB_DISABLE_FILTERING 0x01 +#define MB_DISABLE_TOP_EDGE 0x02 +#define MB_DISABLE_LEFT_EDGE 0x04 + +/** Maximum number of reference pics */ +#define MAX_REF_BUFS 32 +#define MAX_DISP_BUFS_NEW 64 +#define MAX_FRAMES 16 + +#define INVALID_FRAME_NUM 0x0fffffff +#define GAP_FRAME_NUM 0x1fffffff + +/** macros for reference picture lists, refIdx to POC mapping */ +// 1 extra entry into reference picture lists for refIdx = -1. +// this entry is always 0. this saves conditional checks in +// FillBs modules. +#define POC_LIST_L0_TO_L1_DIFF (( 2*MAX_FRAMES) + 1) +#define POC_LIST_L0_TO_L1_DIFF_1 ((MAX_FRAMES) + 1) + +#define FRM_LIST_L0 0 //0 +#define FRM_LIST_L1 1 * POC_LIST_L0_TO_L1_DIFF//FRM_LIST_L0 + POC_LIST_L0_TO_L1_DIFF //0+33 //(1 * POC_LIST_L0_TO_L1_DIFF) +#define TOP_LIST_FLD_L0 2 * POC_LIST_L0_TO_L1_DIFF//FRM_LIST_L1 + POC_LIST_L0_TO_L1_DIFF //0+33+33 //(2 * POC_LIST_L0_TO_L1_DIFF) +#define TOP_LIST_FLD_L1 3 * POC_LIST_L0_TO_L1_DIFF//TOP_LIST_FLD_L0 + POC_LIST_L0_TO_L1_DIFF_1 //0+33+33+17 //(3 * POC_LIST_L0_TO_L1_DIFF) +#define BOT_LIST_FLD_L0 4 * POC_LIST_L0_TO_L1_DIFF//TOP_LIST_FLD_L1 + POC_LIST_L0_TO_L1_DIFF_1 //0+33+33+17+17 +#define BOT_LIST_FLD_L1 5 * POC_LIST_L0_TO_L1_DIFF//BOT_LIST_FLD_L0 + POC_LIST_L0_TO_L1_DIFF_1 //0+33+33+17+17+17 +#define TOTAL_LIST_ENTRIES 6 * POC_LIST_L0_TO_L1_DIFF//BOT_LIST_FLD_L1 + POC_LIST_L0_TO_L1_DIFF_1 //0+33+33+17+17+17+17 +#define PAD_MV_BANK_ROW 64 +#define OFFSET_MV_BANK_ROW ((PAD_MV_BANK_ROW)>>1) +#define PAD_PUC_CURNNZ 32 +#define OFFSET_PUC_CURNNZ (PAD_PUC_CURNNZ) +#define PAD_MAP_IDX_POC (1) +#define OFFSET_MAP_IDX_POC (1) + +#define OFFSET_MAP_IDX_POC (1) + +#define NAL_REF_IDC(nal_first_byte) ((nal_first_byte >> 5) & 0x3) +#define NAL_FORBIDDEN_BIT(nal_first_byte) (nal_first_byte>>7) +#define NAL_UNIT_TYPE(nal_first_byte) (nal_first_byte & 0x1F) + +#define INT_PIC_TYPE_I (0x00) + +#define YIELD_CNT_THRESHOLD 8 + + +#define OK 0 +#define END 1 +#define NOT_OK -1 + +/* For 420SP */ +#define YUV420SP_FACTOR 2 + +/*To prevent buffer overflow access; in case the size of nal unit is + * greater than the allocated buffer size*/ +#define EXTRA_BS_OFFSET 16*16*2 + +/** + *************************************************************************** + * Enum to hold various mem records being request + **************************************************************************** + */ +enum +{ + /** + * Codec Object at API level + */ + MEM_REC_IV_OBJ, + + /** + * Codec context + */ + MEM_REC_CODEC, + + /** + * Bitstream buffer which holds emulation prevention removed bytes + */ + MEM_REC_BITSBUF, + + /** + * Buffer to hold coeff data + */ + MEM_REC_COEFF_DATA, + + /** + * Motion vector bank + */ + MEM_REC_MVBANK, + + /** + * Holds mem records passed to the codec. + */ + MEM_REC_BACKUP, + + /** + * Holds SPS + */ + MEM_REC_SPS, + + /** + * Holds PPS + */ + MEM_REC_PPS, + + /** + * Holds Slice Headers + */ + MEM_REC_SLICE_HDR, + + /** + * Holds thread handles + */ + MEM_REC_THREAD_HANDLE, + + /** + * Contains i4_status map indicating parse i4_status per MB basis + */ + MEM_REC_PARSE_MAP, + + /** + * Contains i4_status map indicating processing i4_status per MB basis + */ + MEM_REC_PROC_MAP, + + /** + * Contains slice number info for each MB + */ + + MEM_REC_SLICE_NUM_MAP, + + /** + * Holds dpb manager context + */ + MEM_REC_DPB_MGR, + + /** + * Holds neighbors' info + */ + MEM_REC_NEIGHBOR_INFO, + + /** + * Holds neighbors' info + */ + MEM_REC_PRED_INFO, + + + /** + * Holds inter pred inforamation on packed format info + */ + MEM_REC_PRED_INFO_PKD, + /** + * Holds neighbors' info + */ + MEM_REC_MB_INFO, + + /** + * Holds deblock Mb info structure frame level) + */ + MEM_REC_DEBLK_MB_INFO, + + /** + * Holds reference picture buffers in non-shared mode + */ + MEM_REC_REF_PIC, + + /** + * Holds some misc intermediate_buffers + */ + MEM_REC_EXTRA_MEM, + + /** + * Holds some misc intermediate_buffers + */ + MEM_REC_INTERNAL_SCRATCH, + + /** + * Holds some misc intermediate_buffers + */ + MEM_REC_INTERNAL_PERSIST, + + /* holds structures related to picture buffer manager*/ + MEM_REC_PIC_BUF_MGR, + + /*holds structure related to MV buffer manager*/ + MEM_REC_MV_BUF_MGR, + + /** + * Place holder to compute number of memory records. + */ + MEM_REC_CNT +/* Do not add anything below */ +}; + +#ifdef DEBLOCK_THREAD +#define H264_MUTEX_LOCK(lock) ithread_mutex_lock(lock) +#define H264_MUTEX_UNLOCK(lock) ithread_mutex_unlock(lock) +#else //DEBLOCK_THREAD +#define H264_MUTEX_LOCK(lock) +#define H264_MUTEX_UNLOCK(lock) + +#define DEBUG_THREADS_PRINTF(...) +#define DEBUG_PERF_PRINTF(...) + +/** Profile Types*/ +#define BASE_PROFILE_IDC 66 +#define MAIN_PROFILE_IDC 77 +#define EXTENDED_PROFILE_IDC 88 +#define HIGH_PROFILE_IDC 100 + + +#define MB_SIZE 16 +#define BLK8x8SIZE 8 +#define BLK_SIZE 4 +#define NUM_BLKS_PER_MB 24 +#define NUM_LUM_BLKS_PER_MB 16 +#define LUM_BLK 0 +#define CHROM_BLK 1 +#define NUM_PELS_IN_MB 64 + +/* Level Types */ +#define H264_LEVEL_1_0 10 +#define H264_LEVEL_1_1 11 +#define H264_LEVEL_1_2 12 +#define H264_LEVEL_1_3 13 +#define H264_LEVEL_2_0 20 +#define H264_LEVEL_2_1 21 +#define H264_LEVEL_2_2 22 +#define H264_LEVEL_3_0 30 +#define H264_LEVEL_3_1 31 +#define H264_LEVEL_3_2 32 +#define H264_LEVEL_4_0 40 +#define H264_LEVEL_4_1 41 +#define H264_LEVEL_4_2 42 +#define H264_LEVEL_5_0 50 +#define H264_LEVEL_5_1 51 + +#define MAX_MBS_LEVEL_51 36864 +#define MAX_MBS_LEVEL_50 22080 +#define MAX_MBS_LEVEL_42 8704 +#define MAX_MBS_LEVEL_41 8192 +#define MAX_MBS_LEVEL_40 8192 +#define MAX_MBS_LEVEL_32 5120 +#define MAX_MBS_LEVEL_31 3600 +#define MAX_MBS_LEVEL_30 1620 +#define MAX_MBS_LEVEL_22 1620 +#define MAX_MBS_LEVEL_21 792 +#define MAX_MBS_LEVEL_20 396 +#define MAX_MBS_LEVEL_13 396 +#define MAX_MBS_LEVEL_12 396 +#define MAX_MBS_LEVEL_11 396 +#define MAX_MBS_LEVEL_10 99 + +/** NAL Types */ +#define SLICE_NAL 1 +#define SLICE_DATA_PARTITION_A_NAL 2 +#define SLICE_DATA_PARTITION_B_NAL 3 +#define SLICE_DATA_PARTITION_C_NAL 4 +#define IDR_SLICE_NAL 5 +#define SEI_NAL 6 +#define SEQ_PARAM_NAL 7 +#define PIC_PARAM_NAL 8 +#define ACCESS_UNIT_DELIMITER_RBSP 9 +#define END_OF_SEQ_RBSP 10 +#define END_OF_STREAM_RBSP 11 +#define FILLER_DATA_NAL 12 + +/** Entropy coding modes */ +#define CAVLC 0 +#define CABAC 1 + +/** Picture Types */ +#define I_PIC 0 +#define IP_PIC 1 +#define IPB_PIC 2 +#define SI_PIC 3 +#define SIP_PIC 4 +#define ISI_PIC 5 +#define ISI_PSP_PIC 6 +#define ALL_PIC 7 + +/* Frame or field picture type */ +#define FRM_PIC 0x00 +#define TOP_FLD 0x01 +#define BOT_FLD 0x02 +#define COMP_FLD_PAIR 0x03 /* TOP_FLD | BOT_FLD */ +#define AFRM_PIC 0x04 +#define TOP_REF 0x08 +#define BOT_REF 0x10 +#define PIC_MASK 0x03 +#define NON_EXISTING 0xff + +/* field picture type for display */ +#define DISP_TOP_FLD 0x00 +#define DISP_BOT_FLD 0x01 + +/** Slice Types */ +#define NA_SLICE -1 +#define P_SLICE 0 +#define B_SLICE 1 +#define I_SLICE 2 +#define SP_SLICE 3 +#define SI_SLICE 4 + +/* Definition for picture skip */ +#define SKIP_NONE (0x0) +#define I_SLC_BIT (0x1) +#define P_SLC_BIT (0x2) +#define B_SLC_BIT (0x4) + +/** Macros used for Deblocking */ +#define D_INTER_MB 0 +#define D_INTRA_MB 1 +#define D_PRED_NON_16x16 2 +#define D_B_SLICE 4 +#define D_B_SUBMB 6 //D_B_SLICE | D_PRED_NON_16x16 | D_INTER_MB +#define D_FLD_MB 0x80 + +/** Macros for Cabac checks */ +/** MbType */ +/** |x|x|I_PCM|SKIP| + |S|Inter/Intra|P/B|NON-BD16x16/BD16x16,I16x16/I4x4| */ +#define CAB_INTRA 0x00 /* 0000 00xx */ +#define CAB_INTER 0x04 /* 0000 01xx */ +#define CAB_I4x4 0x00 /* 0000 00x0 */ +#define CAB_I16x16 0x01 /* 0000 00x1 */ +#define CAB_BD16x16 0x04 /* 0000 0100 */ +#define CAB_NON_BD16x16 0x05 /* 0000 0101 */ +#define CAB_P 0x07 /* 0000 0111 */ +#define CAB_SI4x4 0x08 /* 0000 10x0 */ +#define CAB_SI16x16 0x09 /* 0000 10x1 */ +#define CAB_SKIP_MASK 0x10 /* 0001 0000 */ +#define CAB_SKIP 0x10 /* 0001 0000 */ +#define CAB_P_SKIP 0x16 /* 0001 x11x */ +#define CAB_B_SKIP 0x14 /* 0001 x100 */ +#define CAB_BD16x16_MASK 0x07 /* 0000 0111 */ +#define CAB_INTRA_MASK 0x04 /* 0000 0100 */ +#define CAB_I_PCM 0x20 /* 001x xxxx */ + +/**< Binarization types for CABAC */ +/* |x|x|x|x|MSB_FIRST_FLC|FLC|TUNARY|UNARY| */ +#define UNARY 1 +#define TUNARY 2 +#define FLC 4 +#define MSB_FIRST_FLC 12 + +/** Macroblock Types */ +#define I_4x4_MB 0 +#define I_16x16_MB 1 +#define P_MB 2 +#define B_MB 3 +#define SI_MB 4 +#define SP_MB 5 +#define I_PCM_MB 6 + +#define SI4x4_MB 0xFF + +/** Intra luma 16x16 and chroma 8x8 prediction modes */ +#define NUM_INTRA_PRED_MODES 4 +#define VERT 0 +#define HORIZ 1 +#define DC 2 +#define PLANE 3 +#define NOT_VALID -1 +#define DC_DC_DC_DC 0x02020202 /*packed 4 bytes used in Decode Intra Mb*/ + +/** Intra luma 4x4 prediction modes */ +#define NUM_INTRA4x4_PRED_MODES 9 + +/** VERT, HORIZ, DC are applicable to 4x4 as well */ +/** D - Down; U - Up; L - Left; R - Right */ +#define DIAG_DL 3 +#define DIAG_DR 4 +#define VERT_R 5 +#define HORIZ_D 6 +#define VERT_L 7 +#define HORIZ_U 8 + +/** P_MB prediction modes */ +#define NUM_INTER_MB_PRED_MODES 5 +#define PRED_16x16 0 +#define PRED_16x8 1 +#define PRED_8x16 2 +#define PRED_8x8 3 +#define PRED_8x8R0 4 +#define MAGIC_16x16 5 +#define MB_SKIP 255 + +/* P_MB submb modes */ +#define P_L0_8x8 0 +#define P_L0_8x4 1 +#define P_L0_4x8 2 +#define P_L0_4x4 3 + +/* B_MB submb modes */ +#define B_DIRECT_8x8 0 +#define B_L0_8x8 1 +#define B_L1_8x8 2 +#define B_BI_8x8 3 +#define B_L0_8x4 4 +#define B_L0_4x8 5 +#define B_L1_8x4 6 +#define B_L1_4x8 7 +#define B_BI_8x4 8 +#define B_BI_4x8 9 +#define B_L0_4x4 10 +#define B_L1_4x4 11 +#define B_BI_4x4 12 + +/** B_MB prediction modes */ +#define B_8x8 22 +#define PRED_INVALID -1 +#define B_DIRECT 0 +#define PRED_L0 1 +#define PRED_L1 2 +#define BI_PRED 3 +#define B_DIRECT_BI_PRED 23 +#define B_DIRECT_PRED_L0 24 +#define B_DIRECT_PRED_L1 25 +#define B_DIRECT_SPATIAL 26 + +#define B_DIRECT8x8_BI_PRED 13 +#define B_DIRECT8x8_PRED_L0 14 +#define B_DIRECT8x8_PRED_L1 15 + +#define ONE_TO_ONE 0 +#define FRM_TO_FLD 1 +#define FLD_TO_FRM 2 + +/** Inter Sub MB Pred modes */ +#define NUM_INTER_SUBMB_PRED_MODES 4 +#define SUBMB_8x8 0 +#define SUBMB_8x4 1 +#define SUBMB_4x8 2 +#define SUBMB_4x4 3 + +/** Coded Block Pattern - Chroma */ +#define CBPC_ALLZERO 0 +#define CBPC_ACZERO 1 +#define CBPC_NONZERO 2 + +/** Index for accessing the left MB in the MV predictor array */ +#define LEFT 0 +/** Index for accessing the top MB in the MV predictor array */ +#define TOP 1 +/** Index for accessing the top right MB in the MV predictor array */ +#define TOP_R 2 +/** Index for accessing the top Left MB in the MV predictor array */ +#define TOP_L 3 + +/** Maximum number of Sequence Parameter sets */ +#define MAX_NUM_SEQ_PARAMS 32 + +/** Maximum number of Picture Parameter sets */ +#define MAX_NUM_PIC_PARAMS 256 + +#define MASK_ERR_SEQ_SET_ID (0xFFFFFFE0) +#define MASK_ERR_PIC_SET_ID (0xFFFFFF00) + +#define MAX_PIC_ORDER_CNT_TYPE 2 + +#define MAX_BITS_IN_FRAME_NUM 16 +#define MAX_BITS_IN_POC_LSB 16 + +#define H264_MAX_REF_PICS 16 +#define H264_MAX_REF_IDX 32 +#define MAX_WEIGHT_BIPRED_IDC 2 +#define MAX_CABAC_INIT_IDC 2 + +#define H264_DEFAULT_NUM_CORES 1 +#define DEFAULT_SEPARATE_PARSE (H264_DEFAULT_NUM_CORES == 2)? 1 :0 + +/** Maximum number of Slice groups */ +#define MAX_NUM_SLICE_GROUPS 8 +#define MAX_NUM_REF_FRAMES_OFFSET 255 + +/** Deblocking modes for a slice */ +#define SLICE_BOUNDARY_DBLK_DISABLED 2 +#define DBLK_DISABLED 1 +#define DBLK_ENABLED 0 +#define MIN_DBLK_FIL_OFF -12 +#define MAX_DBLK_FIL_OFF 12 + +/** Width of the predictor buffers used for MC */ +#define MB_SIZE 16 +#define BLK8x8SIZE 8 +#define BLK_SIZE 4 +#define NUM_BLKS_PER_MB 24 +#define NUM_LUM_BLKS_PER_MB 16 + +#define SUB_BLK_WIDTH 4 +#define SUB_SUB_BLK_SIZE 4 /* 2x2 pixel i4_size */ +#define SUB_BLK_SIZE ((SUB_BLK_WIDTH) * (SUB_BLK_WIDTH)) +#define MB_LUM_SIZE 256 +#define MB_CHROM_SIZE 64 + +/**< Width to pad the luminance frame buff */ +/**< Height to pad the luminance frame buff */ +/**< Width to pad the chrominance frame buff */ +/**< Height to pad the chrominance frame buff */ + +#define PAD_LEN_Y_H 32 +#define PAD_LEN_Y_V 20 +#define PAD_LEN_UV_H 16 +#define PAD_LEN_UV_V 8 + +#define PAD_MV_BANK_ROW 64 + +/**< Maimum u4_ofst by which the Mvs could point outside the frame buffers + horizontally in the left and vertically in the top direction */ +#define MAX_OFFSET_OUTSIDE_X_FRM -20 +#define MAX_OFFSET_OUTSIDE_Y_FRM -20 +#define MAX_OFFSET_OUTSIDE_UV_FRM -8 + +/** UVLC parsing macros */ +#define UEV 1 +#define SEV 2 +#define TEV 3 + +/** Defines for Boolean values */ +#ifndef TRUE +#define TRUE 1 +#define FALSE 0 +#endif + +#define UNUSED_FOR_REF 0 +#define IS_SHORT_TERM 1 +#define IS_LONG_TERM 2 + +/** Defines for which field gets displayed first */ +#define MAX_FRAMES 16 +#define INVALID_FRAME_NUM 0x0fffffff +#define DO_NOT_DISP 254 +#define DISP_FLD_FIRST_UNDEF 0 +#define DISP_TOP_FLD_FIRST 1 +#define DISP_BOT_FLD_FIRST 2 + +/** Misc error resilience requirements*/ +#define MAX_LOG2_WEIGHT_DENOM 7 +#define PRED_WEIGHT_MIN (-128) +#define PRED_WEIGHT_MAX 127 +#define MAX_REDUNDANT_PIC_CNT 127 + + + +#endif //DEBLOCK_THREAD + +#define NUM_COEFFS_IN_4x4BLK 16 +#define CABAC_BITS_TO_READ 23 + +#define DISPLAY_PRIMARIES_X_UPPER_LIMIT 37000 +#define DISPLAY_PRIMARIES_X_LOWER_LIMIT 5 +#define DISPLAY_PRIMARIES_X_DIVISION_FACTOR 5 + +#define DISPLAY_PRIMARIES_Y_UPPER_LIMIT 42000 +#define DISPLAY_PRIMARIES_Y_LOWER_LIMIT 5 +#define DISPLAY_PRIMARIES_Y_DIVISION_FACTOR 5 + +#define WHITE_POINT_X_UPPER_LIMIT 37000 +#define WHITE_POINT_X_LOWER_LIMIT 5 +#define WHITE_POINT_X_DIVISION_FACTOR 5 + +#define WHITE_POINT_Y_UPPER_LIMIT 42000 +#define WHITE_POINT_Y_LOWER_LIMIT 5 +#define WHITE_POINT_Y_DIVISION_FACTOR 5 + +#define MAX_DISPLAY_MASTERING_LUMINANCE_UPPER_LIMIT 100000000 +#define MAX_DISPLAY_MASTERING_LUMINANCE_LOWER_LIMIT 50000 +#define MAX_DISPLAY_MASTERING_LUMINANCE_DIVISION_FACTOR 10000 + +#define MIN_DISPLAY_MASTERING_LUMINANCE_UPPER_LIMIT 50000 +#define MIN_DISPLAY_MASTERING_LUMINANCE_LOWER_LIMIT 1 + +#define AMBIENT_LIGHT_X_UPPER_LIMIT 50000 +#define AMBIENT_LIGHT_Y_UPPER_LIMIT 50000 + +#define CCV_PRIMARIES_X_UPPER_LIMIT 5000000 +#define CCV_PRIMARIES_X_LOWER_LIMIT -5000000 +#define CCV_PRIMARIES_Y_UPPER_LIMIT 5000000 +#define CCV_PRIMARIES_Y_LOWER_LIMIT -5000000 + + +#define MEMSET_16BYTES(pu4_start,value) \ +{ \ + memset(pu4_start,value,16); \ +} + +#define MEMCPY_16BYTES(dst,src) \ +{ \ + memcpy(dst,src,16); \ +} + + +#endif /*_IH264D_DEFS_H_*/ diff --git a/dependencies/ih264d/decoder/ih264d_dpb_manager.h b/dependencies/ih264d/decoder/ih264d_dpb_manager.h new file mode 100644 index 00000000..57470160 --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_dpb_manager.h @@ -0,0 +1,175 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +#ifndef _IH264D_DPB_MANAGER_H_ +#define _IH264D_DPB_MANAGER_H_ +/*! +*************************************************************************** +* \file ih264d_dpb_manager.h +* +* \brief +* Decoded Picture Buffer Manager Include File +* +* Detailed_description +* +* \date +* 19-12-2002 +* +* \author Sriram Sethuraman +*************************************************************************** +*/ +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264d_bitstrm.h" +#include "ih264d_defs.h" + +#define END_OF_MMCO 0 +#define MARK_ST_PICNUM_AS_NONREF 1 +#define MARK_LT_INDEX_AS_NONREF 2 +#define MARK_ST_PICNUM_AS_LT_INDEX 3 +#define SET_MAX_LT_INDEX 4 +#define RESET_REF_PICTURES 5 +#define SET_LT_INDEX 6 +#define RESET_NONREF_PICTURES 7 +#define RESET_ALL_PICTURES 8 + +#define NO_LONG_TERM_INDICIES 255 +struct field_t +{ + /* picNum of tbe reference field */ + WORD32 i4_pic_num; + + /* assigned when used for long term reference */ + /* else MAX_REF_BUFS+1 */ + UWORD8 u1_long_term_frame_idx; + + /* 0 : unused for reference */ + /* 1 : used for short term reference */ + /* 2 : used for long term reference */ + UWORD8 u1_reference_info; +}; + + +struct dpb_info_t +{ + struct pic_buffer_t *ps_pic_buf; /** Pointer to picture buffer structure */ + WORD32 i4_frame_num; /** frame number of picture - unique for each ref*/ + struct dpb_info_t *ps_prev_short;/** Link to the DPB with previous picNum */ + struct dpb_info_t *ps_prev_long; /** Link to the DPB with previous long term frame*/ + struct field_t s_top_field; /** Contains information of the top_field + reference info, pic num and longt term frame idx */ + struct field_t s_bot_field; /** Contains information of the bot_field + reference info, pic num and longt term frame idx */ + UWORD8 u1_buf_id; /** bufID from bufAPI */ + UWORD8 u1_used_as_ref; /** whether buffer is used as ref for frame or + complementary reference field pair */ + UWORD8 u1_lt_idx; /** If buf is assigned long-term index; else MAX_REF_BUFS+1 */ + +}; + +typedef struct +{ + struct pic_buffer_t *ps_def_dpb[MAX_REF_BUFS];/** DPB in default index order */ + struct pic_buffer_t *ps_mod_dpb[2][2 * MAX_REF_BUFS];/** DPB in reordered index order, 0-fwd,1-bwd */ + struct pic_buffer_t *ps_init_dpb[2][2 * MAX_REF_BUFS];/** DPB in reordered index order, 0-fwd,1-bwd */ + struct dpb_info_t *ps_dpb_st_head; /** Pointer to the most recent picNum */ + struct dpb_info_t *ps_dpb_ht_head; /** Pointer to the smallest LT index */ + struct dpb_info_t as_dpb_info[MAX_REF_BUFS]; /** Physical storage for dpbInfo for ref bufs */ + UWORD8 u1_num_st_ref_bufs; /** Number of short term ref. buffers */ + UWORD8 u1_num_lt_ref_bufs; /** Number of long term ref. buffer */ + UWORD8 u1_max_lt_frame_idx; /** Maximum long term frame index */ + UWORD8 u1_num_gaps; /** Total number of outstanding gaps */ + void * pv_codec_handle; /* For Error Handling */ + WORD32 i4_max_frm_num; /** Max frame number */ + WORD32 ai4_gaps_start_frm_num[MAX_FRAMES];/** start frame number for a gap seqn */ + WORD32 ai4_gaps_end_frm_num[MAX_FRAMES]; /** start frame number for a gap seqn */ + WORD8 ai1_gaps_per_seq[MAX_FRAMES]; /** number of gaps with each gap seqn */ + WORD32 ai4_poc_buf_id_map[MAX_FRAMES][3]; + WORD8 i1_poc_buf_id_entries; + WORD8 i1_gaps_deleted; + UWORD16 u2_pic_wd; + UWORD16 u2_pic_ht; + UWORD8 u1_mmco_error_in_seq; +}dpb_manager_t; + +/** Structure store the MMC Commands */ +struct MMCParams +{ + UWORD32 u4_mmco; /** memory managemet control operation */ + UWORD32 u4_diff_pic_num; /** diff Of Pic Nums Minus1 */ + UWORD32 u4_lt_idx; /** Long Term Pic Idx */ + UWORD32 u4_max_lt_idx_plus1; /** MaxLongTermPicIdxPlus1 */ +}; + +typedef struct +{ + UWORD8 u1_dpb_commands_read; /** Flag to indicate that DBP commands are read */ + UWORD8 u1_buf_mode; /** decoder Pic bugffering mode*/ + UWORD8 u1_num_of_commands; /** Number of MMC commands */ + /* These variables are ised in case of IDR pictures only */ + UWORD8 u1_idr_pic; /** = 1 ,IDR pic */ + UWORD8 u1_no_output_of_prior_pics_flag; + UWORD8 u1_long_term_reference_flag; + struct MMCParams as_mmc_params[MAX_REF_BUFS]; /* < Buffer to store MMC commands */ + UWORD8 u1_dpb_commands_read_slc; +}dpb_commands_t; + +void ih264d_init_ref_bufs(dpb_manager_t *ps_dpb_mgr); + +WORD32 ih264d_insert_st_node(dpb_manager_t *ps_dpb_mgr, + struct pic_buffer_t *ps_pic_buf, + UWORD8 u1_buf_id, + UWORD32 u2_cur_pic_num); +WORD32 ih264d_update_default_index_list(dpb_manager_t *ps_dpb_mgr); +WORD32 ih264d_do_mmco_buffer(dpb_commands_t *ps_dpb_cmds, + dpb_manager_t *ps_dpb_mgr, + UWORD8 u1_numRef_frames_for_seq, + UWORD32 u4_cur_pic_num, + UWORD32 u2_u4_max_pic_num_minus1, + UWORD8 u1_nal_unit_type, + struct pic_buffer_t *ps_pic_buf, + UWORD8 u1_buf_id, + UWORD8 u1_fld_pic_flag, + UWORD8 u1_curr_pic_in_err); +void ih264d_release_pics_in_dpb(void *pv_dec, + UWORD8 u1_disp_bufs); +void ih264d_reset_ref_bufs(dpb_manager_t *ps_dpb_mgr); +WORD32 ih264d_delete_st_node_or_make_lt(dpb_manager_t *ps_dpb_mgr, + WORD32 u4_pic_num, + UWORD32 u4_lt_idx, + UWORD8 u1_fld_pic_flag); + +WORD32 ih264d_delete_gap_frm_mmco(dpb_manager_t *ps_dpb_mgr, + WORD32 i4_frame_num, + UWORD8 *pu1_del_node); + +WORD32 ih264d_delete_gap_frm_sliding(dpb_manager_t *ps_dpb_mgr, + WORD32 i4_frame_num, + UWORD8 *pu1_del_node); + +WORD32 ih264d_do_mmco_for_gaps(dpb_manager_t *ps_dpb_mgr, + UWORD8 u1_num_ref_frames); + +WORD32 ih264d_insert_pic_in_display_list(dpb_manager_t *ps_dpb_mgr, + UWORD8 u1_buf_id, + WORD32 i4_display_poc, + UWORD32 u4_frame_num); +void ih264d_delete_nonref_nondisplay_pics(dpb_manager_t *ps_dpb_mgr); +#endif /* _IH264D_DPB_MANAGER_H_ */ diff --git a/dependencies/ih264d/decoder/ih264d_dpb_mgr.c b/dependencies/ih264d/decoder/ih264d_dpb_mgr.c new file mode 100644 index 00000000..ce977d73 --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_dpb_mgr.c @@ -0,0 +1,2070 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +#ifdef __ANDROID__ +#include <log/log.h> +#endif +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "iv.h" +#include "ih264d_dpb_manager.h" +#include "ih264d_bitstrm.h" +#include "ih264d_parse_cavlc.h" +#include "ih264d_defs.h" +#include "ih264d_structs.h" +#include "ih264d_process_bslice.h" +#include "ih264d_debug.h" +#include "ih264d_tables.h" +#include "ih264d_error_handler.h" +#include "string.h" +#include "ih264d_defs.h" +#include "ih264_error.h" +#include "ih264_buf_mgr.h" +#include "assert.h" + +/*! + *************************************************************************** + * \file ih264d_dpb_mgr.c + * + * \brief + * Functions for managing the decoded picture buffer + * + * Detailed_description + * + * \date + * 19-12-2002 + * + * \author Sriram Sethuraman + *************************************************************************** + */ + +/*! + ************************************************************************** + * \if Function name : ih264d_init_ref_bufs \endif + * + * \brief + * Called at the start for initialization. + * + * \return + * none + ************************************************************************** + */ +void ih264d_init_ref_bufs(dpb_manager_t *ps_dpb_mgr) +{ + UWORD32 i; + struct dpb_info_t *ps_dpb_info = ps_dpb_mgr->as_dpb_info; + for(i = 0; i < MAX_REF_BUFS; i++) + { + ps_dpb_info[i].u1_used_as_ref = UNUSED_FOR_REF; + ps_dpb_info[i].u1_lt_idx = MAX_REF_BUFS + 1; + ps_dpb_info[i].ps_prev_short = NULL; + ps_dpb_info[i].ps_prev_long = NULL; + ps_dpb_info[i].ps_pic_buf = NULL; + ps_dpb_info[i].s_top_field.u1_reference_info = UNUSED_FOR_REF; + ps_dpb_info[i].s_bot_field.u1_reference_info = UNUSED_FOR_REF; + ps_dpb_info[i].s_top_field.u1_long_term_frame_idx = MAX_REF_BUFS + 1; + ps_dpb_info[i].s_bot_field.u1_long_term_frame_idx = MAX_REF_BUFS + 1; + + } + ps_dpb_mgr->u1_num_st_ref_bufs = ps_dpb_mgr->u1_num_lt_ref_bufs = 0; + ps_dpb_mgr->ps_dpb_st_head = NULL; + ps_dpb_mgr->ps_dpb_ht_head = NULL; + ps_dpb_mgr->i1_gaps_deleted = 0; + ps_dpb_mgr->i1_poc_buf_id_entries = 0; + ps_dpb_mgr->u1_mmco_error_in_seq = 0; + + ps_dpb_mgr->u1_num_gaps = 0; + for(i = 0; i < MAX_FRAMES; i++) + { + ps_dpb_mgr->ai4_gaps_start_frm_num[i] = INVALID_FRAME_NUM; + ps_dpb_mgr->ai4_gaps_end_frm_num[i] = 0; + ps_dpb_mgr->ai1_gaps_per_seq[i] = 0; + ps_dpb_mgr->ai4_poc_buf_id_map[i][0] = -1; + ps_dpb_mgr->ai4_poc_buf_id_map[i][1] = 0x7fffffff; + ps_dpb_mgr->ai4_poc_buf_id_map[i][2] = 0; + } + +} + +void ih264d_free_ref_pic_mv_bufs(void* pv_dec, UWORD8 pic_buf_id) +{ + dec_struct_t *ps_dec = (dec_struct_t *)pv_dec; + + if((pic_buf_id == ps_dec->u1_pic_buf_id) && + ps_dec->ps_cur_slice->u1_field_pic_flag && + (ps_dec->u1_top_bottom_decoded == 0)) + { + return; + } + + ih264_buf_mgr_release((buf_mgr_t *)ps_dec->pv_pic_buf_mgr, + pic_buf_id, + BUF_MGR_REF); + ih264_buf_mgr_release((buf_mgr_t *)ps_dec->pv_mv_buf_mgr, + ps_dec->au1_pic_buf_id_mv_buf_id_map[pic_buf_id], + BUF_MGR_REF); +} +/*! + ************************************************************************** + * \if Function name : ih264d_delete_lt_node \endif + * + * \brief + * Delete a buffer with a long term index from the LT linked list + * + * \return + * none + ************************************************************************** + */ +WORD32 ih264d_delete_lt_node(dpb_manager_t *ps_dpb_mgr, + UWORD32 u4_lt_idx, + UWORD8 u1_fld_pic_flag, + struct dpb_info_t *ps_lt_node_to_insert, + WORD32 *pi4_status) +{ + *pi4_status = 0; + if(ps_dpb_mgr->u1_num_lt_ref_bufs > 0) + { + WORD32 i; + struct dpb_info_t *ps_next_dpb; + /* ps_unmark_node points to the node to be removed */ + /* from long term list. */ + struct dpb_info_t *ps_unmark_node; + //Find the node with matching LTIndex + ps_next_dpb = ps_dpb_mgr->ps_dpb_ht_head; + if(ps_next_dpb->u1_lt_idx == u4_lt_idx) + { + ps_unmark_node = ps_next_dpb; + } + else + { + for(i = 1; i < ps_dpb_mgr->u1_num_lt_ref_bufs; i++) + { + if(ps_next_dpb->ps_prev_long->u1_lt_idx == u4_lt_idx) + break; + ps_next_dpb = ps_next_dpb->ps_prev_long; + } + if(i == ps_dpb_mgr->u1_num_lt_ref_bufs) + *pi4_status = 1; + else + ps_unmark_node = ps_next_dpb->ps_prev_long; + } + + if(*pi4_status == 0) + { + if(u1_fld_pic_flag) + { + if(ps_lt_node_to_insert != ps_unmark_node) + { + UWORD8 u1_deleted = 0; + /* for the ps_unmark_node mark the corresponding field */ + /* field as unused for reference */ + + if(ps_unmark_node->s_top_field.u1_long_term_frame_idx + == u4_lt_idx) + { + ps_unmark_node->s_top_field.u1_reference_info = + UNUSED_FOR_REF; + ps_unmark_node->s_top_field.u1_long_term_frame_idx = + MAX_REF_BUFS + 1; + u1_deleted = 1; + } + if(ps_unmark_node->s_bot_field.u1_long_term_frame_idx + == u4_lt_idx) + { + ps_unmark_node->s_bot_field.u1_reference_info = + UNUSED_FOR_REF; + ps_unmark_node->s_bot_field.u1_long_term_frame_idx = + MAX_REF_BUFS + 1; + u1_deleted = 1; + } + + if(!u1_deleted) + { + + UWORD32 i4_error_code; + i4_error_code = ERROR_DBP_MANAGER_T; + + return i4_error_code; + } + } + + ps_unmark_node->u1_used_as_ref = + ps_unmark_node->s_top_field.u1_reference_info + | ps_unmark_node->s_bot_field.u1_reference_info; + } + else + ps_unmark_node->u1_used_as_ref = UNUSED_FOR_REF; + + if(UNUSED_FOR_REF == ps_unmark_node->u1_used_as_ref) + { + if(ps_unmark_node == ps_dpb_mgr->ps_dpb_ht_head) + ps_dpb_mgr->ps_dpb_ht_head = ps_next_dpb->ps_prev_long; + + ps_unmark_node->u1_lt_idx = MAX_REF_BUFS + 1; + ps_unmark_node->s_top_field.u1_reference_info = + UNUSED_FOR_REF; + ps_unmark_node->s_bot_field.u1_reference_info = + UNUSED_FOR_REF; + // Release the physical buffer + ih264d_free_ref_pic_mv_bufs(ps_dpb_mgr->pv_codec_handle, + ps_unmark_node->u1_buf_id); + ps_next_dpb->ps_prev_long = ps_unmark_node->ps_prev_long; //update link + ps_unmark_node->ps_prev_long = NULL; + ps_dpb_mgr->u1_num_lt_ref_bufs--; //decrement LT buf count + } + } + } + return OK; +} + +/*! + ************************************************************************** + * \if Function name : ih264d_insert_lt_node \endif + * + * \brief + * Insert a buffer into the LT linked list at a given LT index + * + * \return + * none + ************************************************************************** + */ +WORD32 ih264d_insert_lt_node(dpb_manager_t *ps_dpb_mgr, + struct dpb_info_t *ps_mov_node, + UWORD32 u4_lt_idx, + UWORD8 u1_fld_pic_flag) +{ + UWORD8 u1_mark_top_field_long_term = 0; + UWORD8 u1_mark_bot_field_long_term = 0; + + { + if(u1_fld_pic_flag) + { + /* Assign corresponding field (top or bottom) long_term_frame_idx */ + + if((ps_mov_node->s_top_field.u1_reference_info == IS_LONG_TERM) + && (ps_mov_node->s_bot_field.u1_reference_info + == IS_LONG_TERM)) + { + if(ps_mov_node->u1_lt_idx == u4_lt_idx) + u1_mark_bot_field_long_term = 1; + else + { + + UWORD32 i4_error_code; + i4_error_code = ERROR_DBP_MANAGER_T; + + return i4_error_code; + + } + } + else if(ps_mov_node->s_top_field.u1_reference_info == IS_LONG_TERM) + { + u1_mark_top_field_long_term = 1; + } + + if(!(u1_mark_top_field_long_term || u1_mark_bot_field_long_term)) + { + UWORD32 i4_error_code; + i4_error_code = ERROR_DBP_MANAGER_T; + return i4_error_code; + } + } + else + { + ps_mov_node->s_top_field.u1_reference_info = IS_LONG_TERM; + ps_mov_node->s_bot_field.u1_reference_info = IS_LONG_TERM; + ps_mov_node->s_top_field.u1_long_term_frame_idx = u4_lt_idx; + ps_mov_node->s_bot_field.u1_long_term_frame_idx = u4_lt_idx; + u1_mark_bot_field_long_term = 1; + u1_mark_top_field_long_term = 1; + } + + ps_mov_node->u1_lt_idx = u4_lt_idx; //Assign the LT index to the node + ps_mov_node->ps_pic_buf->u1_long_term_frm_idx = u4_lt_idx; + ps_mov_node->u1_used_as_ref = IS_LONG_TERM; + + /* Insert the new long term in the LT list with u4_lt_idx */ + /* in ascending order. */ + if(ps_dpb_mgr->u1_num_lt_ref_bufs > 0) + { + struct dpb_info_t *ps_next_dpb = ps_dpb_mgr->ps_dpb_ht_head; + if(u4_lt_idx < ps_next_dpb->u1_lt_idx) + { + //LTIndex to be inserted is the smallest LT index + //Update head and point prev to the next higher index + ps_mov_node->ps_prev_long = ps_next_dpb; + ps_dpb_mgr->ps_dpb_ht_head = ps_mov_node; + } + else + { + WORD32 i; + struct dpb_info_t *ps_nxtDPB = ps_next_dpb; + ps_next_dpb = ps_next_dpb->ps_prev_long; + for(i = 1; i < ps_dpb_mgr->u1_num_lt_ref_bufs; i++) + { + if(ps_next_dpb->u1_lt_idx > u4_lt_idx) + break; + ps_nxtDPB = ps_next_dpb; + ps_next_dpb = ps_next_dpb->ps_prev_long; + } + + ps_nxtDPB->ps_prev_long = ps_mov_node; + ps_mov_node->ps_prev_long = ps_next_dpb; + } + } + else + { + ps_dpb_mgr->ps_dpb_ht_head = ps_mov_node; + ps_mov_node->ps_prev_long = NULL; + } + /* Identify the picture buffer as a long term picture buffer */ + ps_mov_node->ps_pic_buf->u1_is_short = 0; + + /* Increment LT buf count only if new LT node inserted */ + /* If Increment during top_field is done, don't increment */ + /* for bottom field, as both them are part of same pic. */ + if(u1_mark_bot_field_long_term) + ps_dpb_mgr->u1_num_lt_ref_bufs++; + + } + return OK; +} + +/*! + ************************************************************************** + * \if Function name : ih264d_insert_st_node \endif + * + * \brief + * Adds a short term reference picture into the ST linked list + * + * \return + * None + * + * \note + * Called only for a new coded picture with nal_ref_idc!=0 + ************************************************************************** + */ +WORD32 ih264d_insert_st_node(dpb_manager_t *ps_dpb_mgr, + struct pic_buffer_t *ps_pic_buf, + UWORD8 u1_buf_id, + UWORD32 u4_cur_pic_num) +{ + WORD32 i; + struct dpb_info_t *ps_dpb_info = ps_dpb_mgr->as_dpb_info; + UWORD8 u1_picture_type = ps_pic_buf->u1_picturetype; + /* Find an unused dpb location */ + for(i = 0; i < MAX_REF_BUFS; i++) + { + if((ps_dpb_info[i].ps_pic_buf == ps_pic_buf) + && ps_dpb_info[i].u1_used_as_ref) + { + /*signal an error in the case of frame pic*/ + if(ps_dpb_info[i].ps_pic_buf->u1_pic_type == FRM_PIC) + { + return ERROR_DBP_MANAGER_T; + } + else + { + /* Can occur only for field bottom pictures */ + ps_dpb_info[i].s_bot_field.u1_reference_info = IS_SHORT_TERM; + return OK; + } + } + + if((ps_dpb_info[i].u1_used_as_ref == UNUSED_FOR_REF) + && (ps_dpb_info[i].s_top_field.u1_reference_info + == UNUSED_FOR_REF) + && (ps_dpb_info[i].s_bot_field.u1_reference_info + == UNUSED_FOR_REF)) + break; + } + if(i == MAX_REF_BUFS) + { + UWORD32 i4_error_code; + i4_error_code = ERROR_DBP_MANAGER_T; + return i4_error_code; + } + + /* Create dpb info */ + ps_dpb_info[i].ps_pic_buf = ps_pic_buf; + ps_dpb_info[i].ps_prev_short = ps_dpb_mgr->ps_dpb_st_head; + ps_dpb_info[i].u1_buf_id = u1_buf_id; + ps_dpb_info[i].u1_used_as_ref = TRUE; + ps_dpb_info[i].u1_lt_idx = MAX_REF_BUFS + 1; + ps_dpb_info[i].i4_frame_num = u4_cur_pic_num; + ps_dpb_info[i].ps_pic_buf->i4_frame_num = u4_cur_pic_num; + + /* update the head node of linked list to point to the cur Pic */ + ps_dpb_mgr->ps_dpb_st_head = ps_dpb_info + i; + + // Increment Short term bufCount + ps_dpb_mgr->u1_num_st_ref_bufs++; + /* Identify the picture as a short term picture buffer */ + ps_pic_buf->u1_is_short = IS_SHORT_TERM; + + if((u1_picture_type & 0x03) == FRM_PIC) + { + ps_dpb_info[i].u1_used_as_ref = IS_SHORT_TERM; + ps_dpb_info[i].s_top_field.u1_reference_info = IS_SHORT_TERM; + ps_dpb_info[i].s_bot_field.u1_reference_info = IS_SHORT_TERM; + } + + if((u1_picture_type & 0x03) == TOP_FLD) + ps_dpb_info[i].s_top_field.u1_reference_info = IS_SHORT_TERM; + + if((u1_picture_type & 0x03) == BOT_FLD) + ps_dpb_info[i].s_bot_field.u1_reference_info = IS_SHORT_TERM; + + return OK; +} + +/*! + ************************************************************************** + * \if Function name : ih264d_delete_st_node_or_make_lt \endif + * + * \brief + * Delete short term ref with a given picNum from the ST linked list or + * make it an LT node + * + * \return + * 0 - if successful; -1 - otherwise + * + * \note + * Common parts to MMCO==1 and MMCO==3 have been combined here + ************************************************************************** + */ +WORD32 ih264d_delete_st_node_or_make_lt(dpb_manager_t *ps_dpb_mgr, + WORD32 i4_pic_num, + UWORD32 u4_lt_idx, + UWORD8 u1_fld_pic_flag) +{ + WORD32 i; + struct dpb_info_t *ps_next_dpb; + WORD32 i4_frame_num = i4_pic_num; + struct dpb_info_t *ps_unmark_node = NULL; + UWORD8 u1_del_node = 0, u1_del_st = 0; + UWORD8 u1_reference_type = UNUSED_FOR_REF; + WORD32 ret; + + if(u1_fld_pic_flag) + { + i4_frame_num = i4_frame_num >> 1; + + if(u4_lt_idx == (MAX_REF_BUFS + 1)) + u1_reference_type = UNUSED_FOR_REF; + else + u1_reference_type = IS_LONG_TERM; + } + + //Find the node with matching picNum + ps_next_dpb = ps_dpb_mgr->ps_dpb_st_head; + if((WORD32)ps_next_dpb->i4_frame_num == i4_frame_num) + { + ps_unmark_node = ps_next_dpb; + } + else + { + for(i = 1; i < ps_dpb_mgr->u1_num_st_ref_bufs; i++) + { + if((WORD32)ps_next_dpb->ps_prev_short->i4_frame_num == i4_frame_num) + break; + ps_next_dpb = ps_next_dpb->ps_prev_short; + } + + if(i == ps_dpb_mgr->u1_num_st_ref_bufs) + { + if(ps_dpb_mgr->u1_num_gaps) + { + ret = ih264d_delete_gap_frm_mmco(ps_dpb_mgr, i4_frame_num, &u1_del_st); + if(ret != OK) + return ret; + } + else + { + UWORD32 i4_error_code; + i4_error_code = ERROR_DBP_MANAGER_T; + + return i4_error_code; + } + + if(u1_del_st) + { + UWORD32 i4_error_code; + i4_error_code = ERROR_DBP_MANAGER_T; + return i4_error_code; + } + else + { + return 0; + } + } + else + ps_unmark_node = ps_next_dpb->ps_prev_short; + } + + if(u1_fld_pic_flag) + { + /* Mark the corresponding field ( top or bot) as */ + /* UNUSED_FOR_REF or IS_LONG_TERM depending on */ + /* u1_reference_type. */ + if(ps_unmark_node->s_top_field.i4_pic_num == i4_pic_num) + { + ps_unmark_node->s_top_field.u1_reference_info = u1_reference_type; + ps_unmark_node->s_top_field.u1_long_term_frame_idx = u4_lt_idx; + { + UWORD8 *pu1_src = ps_unmark_node->ps_pic_buf->pu1_col_zero_flag; + WORD32 i4_size = ((ps_dpb_mgr->u2_pic_wd + * ps_dpb_mgr->u2_pic_ht) >> 5); + /* memset the colocated zero u4_flag buffer */ + memset(pu1_src, 0, i4_size); + } + } + + else if(ps_unmark_node->s_bot_field.i4_pic_num == i4_pic_num) + { + + ps_unmark_node->s_bot_field.u1_reference_info = u1_reference_type; + ps_unmark_node->s_bot_field.u1_long_term_frame_idx = u4_lt_idx; + { + UWORD8 *pu1_src = + ps_unmark_node->ps_pic_buf->pu1_col_zero_flag + + ((ps_dpb_mgr->u2_pic_wd + * ps_dpb_mgr->u2_pic_ht) + >> 5); + WORD32 i4_size = ((ps_dpb_mgr->u2_pic_wd + * ps_dpb_mgr->u2_pic_ht) >> 5); + /* memset the colocated zero u4_flag buffer */ + memset(pu1_src, 0, i4_size); + } + } + ps_unmark_node->u1_used_as_ref = + ps_unmark_node->s_top_field.u1_reference_info + | ps_unmark_node->s_bot_field.u1_reference_info; + } + else + { + ps_unmark_node->u1_used_as_ref = UNUSED_FOR_REF; + ps_unmark_node->s_top_field.u1_reference_info = UNUSED_FOR_REF; + ps_unmark_node->s_bot_field.u1_reference_info = UNUSED_FOR_REF; + + { + UWORD8 *pu1_src = ps_unmark_node->ps_pic_buf->pu1_col_zero_flag; + + WORD32 i4_size = ((ps_dpb_mgr->u2_pic_wd + * ps_dpb_mgr->u2_pic_ht) >> 4); + /* memset the colocated zero u4_flag buffer */ + memset(pu1_src, 0, i4_size); + } + } + + if(!(ps_unmark_node->u1_used_as_ref & IS_SHORT_TERM)) + { + if(ps_unmark_node == ps_dpb_mgr->ps_dpb_st_head) + ps_dpb_mgr->ps_dpb_st_head = ps_next_dpb->ps_prev_short; + else + ps_next_dpb->ps_prev_short = ps_unmark_node->ps_prev_short; //update link + ps_dpb_mgr->u1_num_st_ref_bufs--; //decrement ST buf count + u1_del_node = 1; + } + + if(u4_lt_idx == MAX_REF_BUFS + 1) + { + if(u1_del_node) + { + // Release the physical buffer + ih264d_free_ref_pic_mv_bufs(ps_dpb_mgr->pv_codec_handle, + ps_unmark_node->u1_buf_id); + ps_unmark_node->ps_prev_short = NULL; + } + } + else + { + WORD32 i4_status; + //If another node has the same LT index, delete that node + ret = ih264d_delete_lt_node(ps_dpb_mgr, u4_lt_idx, + u1_fld_pic_flag, ps_unmark_node, &i4_status); + if(ret != OK) + return ret; + // Now insert the short term node as a long term node + ret = ih264d_insert_lt_node(ps_dpb_mgr, ps_unmark_node, u4_lt_idx, + u1_fld_pic_flag); + if(ret != OK) + return ret; + } + return OK; +} +/*! + ************************************************************************** + * \if Function name : ih264d_reset_ref_bufs \endif + * + * \brief + * Called if MMCO==5/7 or on the first slice of an IDR picture + * + * \return + * none + ************************************************************************** + */ +void ih264d_reset_ref_bufs(dpb_manager_t *ps_dpb_mgr) +{ + WORD32 i; + struct dpb_info_t *ps_dpb_info = ps_dpb_mgr->as_dpb_info; + + for(i = 0; i < MAX_REF_BUFS; i++) + { + if(ps_dpb_info[i].u1_used_as_ref) + { + ps_dpb_info[i].u1_used_as_ref = UNUSED_FOR_REF; + ps_dpb_info[i].u1_lt_idx = MAX_REF_BUFS + 1; + ps_dpb_info[i].ps_prev_short = NULL; + ps_dpb_info[i].ps_prev_long = NULL; + ps_dpb_info[i].ps_pic_buf = NULL; + ps_dpb_info[i].s_top_field.u1_reference_info = UNUSED_FOR_REF; + ps_dpb_info[i].s_bot_field.u1_reference_info = UNUSED_FOR_REF; + ps_dpb_info[i].s_top_field.u1_long_term_frame_idx = MAX_REF_BUFS + 1; + ps_dpb_info[i].s_bot_field.u1_long_term_frame_idx = MAX_REF_BUFS + 1; + + //Release physical buffer + ih264d_free_ref_pic_mv_bufs(ps_dpb_mgr->pv_codec_handle, + ps_dpb_info[i].u1_buf_id); + } + } + ps_dpb_mgr->u1_num_st_ref_bufs = ps_dpb_mgr->u1_num_lt_ref_bufs = 0; + ps_dpb_mgr->ps_dpb_st_head = NULL; + ps_dpb_mgr->ps_dpb_ht_head = NULL; + ps_dpb_mgr->u1_mmco_error_in_seq = 0; + + /* release all gaps */ + ps_dpb_mgr->u1_num_gaps = 0; + for(i = 0; i < MAX_FRAMES; i++) + { + ps_dpb_mgr->ai4_gaps_start_frm_num[i] = INVALID_FRAME_NUM; + ps_dpb_mgr->ai4_gaps_end_frm_num[i] = 0; + ps_dpb_mgr->ai1_gaps_per_seq[i] = 0; + } +} + +/*! + ************************************************************************** + * \if Function name : Name \endif + * + * \brief + * create the default index list after an MMCO + * + * \return + * 0 - if no_error; -1 - error + * + ************************************************************************** + */ +WORD32 ih264d_update_default_index_list(dpb_manager_t *ps_dpb_mgr) +{ + WORD32 i; + struct dpb_info_t *ps_next_dpb = ps_dpb_mgr->ps_dpb_st_head; + + for(i = 0; i < ps_dpb_mgr->u1_num_st_ref_bufs; i++) + { + ps_dpb_mgr->ps_def_dpb[i] = ps_next_dpb->ps_pic_buf; + ps_next_dpb = ps_next_dpb->ps_prev_short; + } + + ps_next_dpb = ps_dpb_mgr->ps_dpb_ht_head; + for(;i< ps_dpb_mgr->u1_num_st_ref_bufs + ps_dpb_mgr->u1_num_lt_ref_bufs; i++) + { + ps_dpb_mgr->ps_def_dpb[i] = ps_next_dpb->ps_pic_buf; + ps_next_dpb = ps_next_dpb->ps_prev_long; + } + return 0; +} + +/*! + ************************************************************************** + * \if Function name : ref_idx_reordering \endif + * + * \brief + * Parse the bitstream and reorder indices for the current slice + * + * \return + * 0 - if no_error; -1 - error + * + * \note + * Called only if ref_idx_reordering_flag_l0 is decoded as 1 + * Remove error checking for unmatching picNum or LTIndex later (if not needed) + * \para + * This section implements 7.3.3.1 and 8.2.6.4 + * Uses the default index list as the starting point and + * remaps the picNums sent to the next higher index in the + * modified list. The unmodified ones are copied from the + * default to modified list retaining their order in the default list. + * + ************************************************************************** + */ +WORD32 ih264d_ref_idx_reordering(dec_struct_t *ps_dec, UWORD8 uc_lx) +{ + dpb_manager_t *ps_dpb_mgr = ps_dec->ps_dpb_mgr; + UWORD16 u4_cur_pic_num = ps_dec->ps_cur_slice->u2_frame_num; + /*< Maximum Picture Number Minus 1 */ + UWORD16 ui_max_frame_num = + ps_dec->ps_cur_sps->u2_u4_max_pic_num_minus1 + 1; + + WORD32 i, count = 0; + UWORD32 ui_remapIdc, ui_nextUev; + WORD16 u2_pred_frame_num = u4_cur_pic_num; + WORD32 i_temp; + UWORD16 u2_def_mod_flag = 0; /* Flag to keep track of which indices have been remapped */ + UWORD8 modCount = 0; + UWORD32 *pu4_bitstrm_buf = ps_dec->ps_bitstrm->pu4_buffer; + UWORD32 *pu4_bitstrm_ofst = &ps_dec->ps_bitstrm->u4_ofst; + dec_slice_params_t *ps_cur_slice = ps_dec->ps_cur_slice; + UWORD8 u1_field_pic_flag = ps_cur_slice->u1_field_pic_flag; + + if(u1_field_pic_flag) + { + u4_cur_pic_num = u4_cur_pic_num * 2 + 1; + ui_max_frame_num = ui_max_frame_num * 2; + } + + u2_pred_frame_num = u4_cur_pic_num; + + ui_remapIdc = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); + + while((ui_remapIdc != 3) + && (count < ps_cur_slice->u1_num_ref_idx_lx_active[uc_lx])) + { + ui_nextUev = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); + if(ui_remapIdc != 2) + { + if(ui_nextUev > ui_max_frame_num) + return ERROR_DBP_MANAGER_T; + + ui_nextUev = ui_nextUev + 1; + + if(ui_remapIdc == 0) + { + // diffPicNum is -ve + i_temp = (WORD32)u2_pred_frame_num - (WORD32)ui_nextUev; + if(i_temp < 0) + i_temp += ui_max_frame_num; + } + else + { + // diffPicNum is +ve + i_temp = (WORD32)u2_pred_frame_num + (WORD32)ui_nextUev; + if(i_temp >= ui_max_frame_num) + i_temp -= ui_max_frame_num; + } + /* Find the dpb with the matching picNum (picNum==frameNum for framePic) */ + + if(i_temp > u4_cur_pic_num) + i_temp = i_temp - ui_max_frame_num; + + for(i = 0; i < (ps_cur_slice->u1_initial_list_size[uc_lx]); i++) + { + if(ps_dpb_mgr->ps_init_dpb[uc_lx][i]->i4_pic_num == i_temp) + break; + } + if(i == (ps_cur_slice->u1_initial_list_size[uc_lx])) + { + UWORD32 i4_error_code; + i4_error_code = ERROR_DBP_MANAGER_T; + return i4_error_code; + } + + u2_def_mod_flag |= (1 << i); + ps_dpb_mgr->ps_mod_dpb[uc_lx][modCount++] = + ps_dpb_mgr->ps_init_dpb[uc_lx][i]; + u2_pred_frame_num = i_temp; //update predictor to be the picNum just obtained + } + else //2 + { + UWORD8 u1_lt_idx; + + if(ui_nextUev > (MAX_REF_BUFS + 1)) + return ERROR_DBP_MANAGER_T; + + u1_lt_idx = (UWORD8)ui_nextUev; + + for(i = 0; i < (ps_cur_slice->u1_initial_list_size[uc_lx]); i++) + { + if(!ps_dpb_mgr->ps_init_dpb[uc_lx][i]->u1_is_short) + { + if(ps_dpb_mgr->ps_init_dpb[uc_lx][i]->u1_long_term_pic_num + == u1_lt_idx) + break; + } + } + if(i == (ps_cur_slice->u1_initial_list_size[uc_lx])) + { + UWORD32 i4_error_code; + i4_error_code = ERROR_DBP_MANAGER_T; + return i4_error_code; + } + + u2_def_mod_flag |= (1 << i); + ps_dpb_mgr->ps_mod_dpb[uc_lx][modCount++] = + ps_dpb_mgr->ps_init_dpb[uc_lx][i]; + } + + ui_remapIdc = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); + /* Get the remapping_idc - 0/1/2/3 */ + count++; + } + + //Handle the ref indices that were not remapped + for(i = 0; i < (ps_cur_slice->u1_num_ref_idx_lx_active[uc_lx]); i++) + { + if(!(u2_def_mod_flag & (1 << i))) + ps_dpb_mgr->ps_mod_dpb[uc_lx][modCount++] = + ps_dpb_mgr->ps_init_dpb[uc_lx][i]; + } + return OK; +} +/*! + ************************************************************************** + * \if Function name : ih264d_read_mmco_commands \endif + * + * \brief + * Parses MMCO commands and stores them in a structure for later use. + * + * \return + * 0 - No error; -1 - Error + * + * \note + * This function stores MMCO commands in structure only for the first time. + * In case of MMCO commands being issued for same Picture Number, they are + * just parsed and not stored them in the structure. + * + ************************************************************************** + */ +WORD32 ih264d_read_mmco_commands(struct _DecStruct * ps_dec) +{ + dec_pic_params_t *ps_pps = ps_dec->ps_cur_pps; + dec_seq_params_t *ps_sps = ps_pps->ps_sps; + dec_bit_stream_t *ps_bitstrm = ps_dec->ps_bitstrm; + dpb_commands_t *ps_dpb_cmds = &(ps_dec->s_dpb_cmds_scratch); + dec_slice_params_t * ps_slice = ps_dec->ps_cur_slice; + WORD32 j; + UWORD8 u1_buf_mode; + struct MMCParams *ps_mmc_params; + UWORD32 *pu4_bitstrm_buf = ps_dec->ps_bitstrm->pu4_buffer; + UWORD32 *pu4_bitstrm_ofst = &ps_bitstrm->u4_ofst; + UWORD32 u4_bit_ofst = ps_dec->ps_bitstrm->u4_ofst; + + ps_slice->u1_mmco_equalto5 = 0; + { + if(ps_dec->u1_nal_unit_type == IDR_SLICE_NAL) + { + ps_slice->u1_no_output_of_prior_pics_flag = + ih264d_get_bit_h264(ps_bitstrm); + COPYTHECONTEXT("SH: no_output_of_prior_pics_flag", + ps_slice->u1_no_output_of_prior_pics_flag); + ps_slice->u1_long_term_reference_flag = ih264d_get_bit_h264( + ps_bitstrm); + COPYTHECONTEXT("SH: long_term_reference_flag", + ps_slice->u1_long_term_reference_flag); + ps_dpb_cmds->u1_idr_pic = 1; + ps_dpb_cmds->u1_no_output_of_prior_pics_flag = + ps_slice->u1_no_output_of_prior_pics_flag; + ps_dpb_cmds->u1_long_term_reference_flag = + ps_slice->u1_long_term_reference_flag; + } + else + { + u1_buf_mode = ih264d_get_bit_h264(ps_bitstrm); //0 - sliding window; 1 - arbitrary + COPYTHECONTEXT("SH: adaptive_ref_pic_buffering_flag", u1_buf_mode); + ps_dpb_cmds->u1_buf_mode = u1_buf_mode; + j = 0; + + if(u1_buf_mode == 1) + { + UWORD32 u4_mmco; + UWORD32 u4_diff_pic_num; + UWORD32 u4_lt_idx, u4_max_lt_idx_plus1; + + u4_mmco = ih264d_uev(pu4_bitstrm_ofst, + pu4_bitstrm_buf); + while(u4_mmco != END_OF_MMCO) + { + if (j >= MAX_REF_BUFS) + { +#ifdef __ANDROID__ + ALOGE("b/25818142"); + android_errorWriteLog(0x534e4554, "25818142"); +#endif + ps_dpb_cmds->u1_num_of_commands = 0; + return -1; + } + ps_mmc_params = &ps_dpb_cmds->as_mmc_params[j]; + ps_mmc_params->u4_mmco = u4_mmco; + switch(u4_mmco) + { + case MARK_ST_PICNUM_AS_NONREF: + u4_diff_pic_num = ih264d_uev(pu4_bitstrm_ofst, + pu4_bitstrm_buf); + //Get absDiffPicnumMinus1 + ps_mmc_params->u4_diff_pic_num = u4_diff_pic_num; + break; + + case MARK_LT_INDEX_AS_NONREF: + u4_lt_idx = ih264d_uev(pu4_bitstrm_ofst, + pu4_bitstrm_buf); + ps_mmc_params->u4_lt_idx = u4_lt_idx; + break; + + case MARK_ST_PICNUM_AS_LT_INDEX: + u4_diff_pic_num = ih264d_uev(pu4_bitstrm_ofst, + pu4_bitstrm_buf); + ps_mmc_params->u4_diff_pic_num = u4_diff_pic_num; + u4_lt_idx = ih264d_uev(pu4_bitstrm_ofst, + pu4_bitstrm_buf); + ps_mmc_params->u4_lt_idx = u4_lt_idx; + break; + + case SET_MAX_LT_INDEX: + { + u4_max_lt_idx_plus1 = ih264d_uev(pu4_bitstrm_ofst, + pu4_bitstrm_buf); + if (u4_max_lt_idx_plus1 > ps_sps->u1_num_ref_frames) + { + /* Invalid max LT ref index */ + return -1; + } + ps_mmc_params->u4_max_lt_idx_plus1 = u4_max_lt_idx_plus1; + break; + } + case RESET_REF_PICTURES: + { + ps_slice->u1_mmco_equalto5 = 1; + break; + } + + case SET_LT_INDEX: + u4_lt_idx = ih264d_uev(pu4_bitstrm_ofst, + pu4_bitstrm_buf); + ps_mmc_params->u4_lt_idx = u4_lt_idx; + break; + + default: + break; + } + u4_mmco = ih264d_uev(pu4_bitstrm_ofst, + pu4_bitstrm_buf); + + j++; + } + ps_dpb_cmds->u1_num_of_commands = j; + } + } + ps_dpb_cmds->u1_dpb_commands_read = 1; + ps_dpb_cmds->u1_dpb_commands_read_slc = 1; + + } + u4_bit_ofst = ps_dec->ps_bitstrm->u4_ofst - u4_bit_ofst; + return u4_bit_ofst; +} + +/*! + ************************************************************************** + * \if Function name : ih264d_do_mmco_buffer \endif + * + * \brief + * Perform decoded picture buffer memory management control operations + * + * \return + * 0 - No error; -1 - Error + * + * \note + * Bitstream is also parsed here to get the MMCOs + * + ************************************************************************** + */ +WORD32 ih264d_do_mmco_buffer(dpb_commands_t *ps_dpb_cmds, + dpb_manager_t *ps_dpb_mgr, + UWORD8 u1_numRef_frames_for_seq, /*!< num_ref_frames from active SeqParSet*/ + UWORD32 u4_cur_pic_num, + UWORD32 u2_u4_max_pic_num_minus1, + UWORD8 u1_nal_unit_type, + struct pic_buffer_t *ps_pic_buf, + UWORD8 u1_buf_id, + UWORD8 u1_fld_pic_flag, + UWORD8 u1_curr_pic_in_err) +{ + WORD32 i; + UWORD8 u1_buf_mode, u1_marked_lt; + struct dpb_info_t *ps_next_dpb; + UWORD8 u1_num_gaps; + UWORD8 u1_del_node = 1; + UWORD8 u1_insert_st_pic = 1; + WORD32 ret; + UNUSED(u1_nal_unit_type); + UNUSED(u2_u4_max_pic_num_minus1); + u1_buf_mode = ps_dpb_cmds->u1_buf_mode; //0 - sliding window; 1 - Adaptive + u1_marked_lt = 0; + u1_num_gaps = ps_dpb_mgr->u1_num_gaps; + + if(!u1_buf_mode) + { + //Sliding window - implements 8.2.5.3 + if((ps_dpb_mgr->u1_num_st_ref_bufs + + ps_dpb_mgr->u1_num_lt_ref_bufs + u1_num_gaps) + == u1_numRef_frames_for_seq) + { + UWORD8 u1_new_node_flag = 1; + if((0 == ps_dpb_mgr->u1_num_st_ref_bufs) && (0 == u1_num_gaps)) + { + UWORD32 i4_error_code; + i4_error_code = ERROR_DBP_MANAGER_T; + return i4_error_code; + } + + // Chase the links to reach the last but one picNum, if available + ps_next_dpb = ps_dpb_mgr->ps_dpb_st_head; + + if(ps_dpb_mgr->u1_num_st_ref_bufs > 1) + { + if(ps_next_dpb->i4_frame_num == (WORD32)u4_cur_pic_num) + { + /* Incase of filed pictures top_field has been allocated */ + /* picture buffer and complementary bottom field pair comes */ + /* then the sliding window mechanism should not allocate a */ + /* new node */ + u1_new_node_flag = 0; + } + + for(i = 1; i < (ps_dpb_mgr->u1_num_st_ref_bufs - 1); i++) + { + if(ps_next_dpb == NULL) + { + UWORD32 i4_error_code; + i4_error_code = ERROR_DBP_MANAGER_T; + return i4_error_code; + } + if(ps_next_dpb->i4_frame_num == (WORD32)u4_cur_pic_num) + { + /* Incase of field pictures top_field has been allocated */ + /* picture buffer and complementary bottom field pair comes */ + /* then the sliding window mechanism should not allocate a */ + /* new node */ + u1_new_node_flag = 0; + } + ps_next_dpb = ps_next_dpb->ps_prev_short; + } + + if(ps_next_dpb->ps_prev_short->ps_prev_short != NULL) + { + UWORD32 i4_error_code; + i4_error_code = ERROR_DBP_MANAGER_T; + return i4_error_code; + } + + if(u1_new_node_flag) + { + if(u1_num_gaps) + { + ret = ih264d_delete_gap_frm_sliding(ps_dpb_mgr, + ps_next_dpb->ps_prev_short->i4_frame_num, + &u1_del_node); + if(ret != OK) + return ret; + } + + if(u1_del_node) + { + ps_dpb_mgr->u1_num_st_ref_bufs--; + ps_next_dpb->ps_prev_short->u1_used_as_ref = + UNUSED_FOR_REF; + ps_next_dpb->ps_prev_short->s_top_field.u1_reference_info = + UNUSED_FOR_REF; + ps_next_dpb->ps_prev_short->s_bot_field.u1_reference_info = + UNUSED_FOR_REF; + ih264d_free_ref_pic_mv_bufs(ps_dpb_mgr->pv_codec_handle, + ps_next_dpb->ps_prev_short->u1_buf_id); + ps_next_dpb->ps_prev_short->ps_pic_buf = NULL; + ps_next_dpb->ps_prev_short = NULL; + } + } + } + else + { + if(ps_dpb_mgr->u1_num_st_ref_bufs) + { + ret = ih264d_delete_gap_frm_sliding(ps_dpb_mgr, + ps_next_dpb->i4_frame_num, + &u1_del_node); + if(ret != OK) + return ret; + if((ps_next_dpb->i4_frame_num != (WORD32)u4_cur_pic_num) + && u1_del_node) + { + ps_dpb_mgr->u1_num_st_ref_bufs--; + ps_next_dpb->u1_used_as_ref = FALSE; + ps_next_dpb->s_top_field.u1_reference_info = + UNUSED_FOR_REF; + ps_next_dpb->s_bot_field.u1_reference_info = + UNUSED_FOR_REF; + ih264d_free_ref_pic_mv_bufs(ps_dpb_mgr->pv_codec_handle, + ps_next_dpb->u1_buf_id); + ps_next_dpb->ps_pic_buf = NULL; + ps_next_dpb->ps_prev_short = NULL; + ps_dpb_mgr->ps_dpb_st_head = NULL; + ps_next_dpb = NULL; + } + else if(ps_next_dpb->i4_frame_num == (WORD32)u4_cur_pic_num) + { + if(u1_curr_pic_in_err) + { + u1_insert_st_pic = 0; + } + else if(ps_dpb_mgr->u1_num_st_ref_bufs > 0) + { + ps_dpb_mgr->u1_num_st_ref_bufs--; + ps_next_dpb->u1_used_as_ref = FALSE; + ps_next_dpb->s_top_field.u1_reference_info = + UNUSED_FOR_REF; + ps_next_dpb->s_bot_field.u1_reference_info = + UNUSED_FOR_REF; + ih264d_free_ref_pic_mv_bufs(ps_dpb_mgr->pv_codec_handle, + ps_next_dpb->u1_buf_id); + ps_next_dpb->ps_pic_buf = NULL; + ps_next_dpb = NULL; + } + } + } + else + { + ret = ih264d_delete_gap_frm_sliding(ps_dpb_mgr, + INVALID_FRAME_NUM, + &u1_del_node); + if(ret != OK) + return ret; + if(u1_del_node) + { + UWORD32 i4_error_code; + i4_error_code = ERROR_DBP_MANAGER_T; + return i4_error_code; + } + } + } + } + } + else + { + //Adaptive memory control - implements 8.2.5.4 + UWORD32 u4_mmco; + UWORD32 u4_diff_pic_num; + WORD32 i4_pic_num; + UWORD32 u4_lt_idx; + WORD32 j; + struct MMCParams *ps_mmc_params; + + for(j = 0; j < ps_dpb_cmds->u1_num_of_commands; j++) + { + ps_mmc_params = &ps_dpb_cmds->as_mmc_params[j]; + u4_mmco = ps_mmc_params->u4_mmco; //Get MMCO + + switch(u4_mmco) + { + case MARK_ST_PICNUM_AS_NONREF: + { + + { + UWORD32 i4_cur_pic_num = u4_cur_pic_num; + WORD64 i8_pic_num; + u4_diff_pic_num = ps_mmc_params->u4_diff_pic_num; //Get absDiffPicnumMinus1 + if(u1_fld_pic_flag) + i4_cur_pic_num = i4_cur_pic_num * 2 + 1; + i8_pic_num = ((WORD64)i4_cur_pic_num - ((WORD64)u4_diff_pic_num + 1)); + if(IS_OUT_OF_RANGE_S32(i8_pic_num)) + { + return ERROR_DBP_MANAGER_T; + } + i4_pic_num = i8_pic_num; + } + + if(ps_dpb_mgr->u1_num_st_ref_bufs > 0) + { + ret = ih264d_delete_st_node_or_make_lt(ps_dpb_mgr, + i4_pic_num, + MAX_REF_BUFS + 1, + u1_fld_pic_flag); + if(ret != OK) + return ret; + } + else + { + UWORD8 u1_dummy; + ret = ih264d_delete_gap_frm_mmco(ps_dpb_mgr, i4_pic_num, &u1_dummy); + if(ret != OK) + return ret; + } + break; + } + case MARK_LT_INDEX_AS_NONREF: + { + WORD32 i4_status; + u4_lt_idx = ps_mmc_params->u4_lt_idx; //Get long term index + ret = ih264d_delete_lt_node(ps_dpb_mgr, + u4_lt_idx, + u1_fld_pic_flag, + 0, &i4_status); + if(ret != OK) + return ret; + if(i4_status) + { + UWORD32 i4_error_code; + i4_error_code = ERROR_DBP_MANAGER_T; + return i4_error_code; + } + break; + } + + case MARK_ST_PICNUM_AS_LT_INDEX: + { + { + UWORD32 i4_cur_pic_num = u4_cur_pic_num; + WORD64 i8_pic_num; + u4_diff_pic_num = ps_mmc_params->u4_diff_pic_num; //Get absDiffPicnumMinus1 + if(u1_fld_pic_flag) + i4_cur_pic_num = i4_cur_pic_num * 2 + 1; + + i8_pic_num = (WORD64)i4_cur_pic_num - ((WORD64)u4_diff_pic_num + 1); + if(IS_OUT_OF_RANGE_S32(i8_pic_num)) + { + return ERROR_DBP_MANAGER_T; + } + i4_pic_num = i8_pic_num; + } + + u4_lt_idx = ps_mmc_params->u4_lt_idx; //Get long term index + + if((ps_dpb_mgr->u1_max_lt_frame_idx == NO_LONG_TERM_INDICIES) || + (u4_lt_idx > ps_dpb_mgr->u1_max_lt_frame_idx)) + { + return ERROR_DBP_MANAGER_T; + } + + if(ps_dpb_mgr->u1_num_st_ref_bufs > 0) + { + ret = ih264d_delete_st_node_or_make_lt(ps_dpb_mgr, + i4_pic_num, u4_lt_idx, + u1_fld_pic_flag); + if(ret != OK) + return ret; + } + break; + } + case SET_MAX_LT_INDEX: + { + UWORD8 uc_numLT = ps_dpb_mgr->u1_num_lt_ref_bufs; + u4_lt_idx = ps_mmc_params->u4_max_lt_idx_plus1; //Get Max_long_term_index_plus1 + if(u4_lt_idx <= ps_dpb_mgr->u1_max_lt_frame_idx + && uc_numLT > 0) + { + struct dpb_info_t *ps_nxtDPB; + //Set all LT buffers with index >= u4_lt_idx to nonreference + ps_nxtDPB = ps_dpb_mgr->ps_dpb_ht_head; + ps_next_dpb = ps_nxtDPB->ps_prev_long; + if(ps_nxtDPB->u1_lt_idx >= u4_lt_idx) + { + i = 0; + ps_dpb_mgr->ps_dpb_ht_head = NULL; + } + else + { + for(i = 1; i < uc_numLT; i++) + { + if(ps_next_dpb->u1_lt_idx >= u4_lt_idx) + break; + ps_nxtDPB = ps_next_dpb; + ps_next_dpb = ps_next_dpb->ps_prev_long; + } + ps_nxtDPB->ps_prev_long = NULL; //Terminate the link of the closest LTIndex that is <=Max + } + ps_dpb_mgr->u1_num_lt_ref_bufs = i; + if(i == 0) + ps_next_dpb = ps_nxtDPB; + + for(; i < uc_numLT; i++) + { + ps_nxtDPB = ps_next_dpb; + ps_nxtDPB->u1_lt_idx = MAX_REF_BUFS + 1; + ps_nxtDPB->u1_used_as_ref = UNUSED_FOR_REF; + ps_nxtDPB->s_top_field.u1_reference_info = + UNUSED_FOR_REF; + ps_nxtDPB->s_bot_field.u1_reference_info = + UNUSED_FOR_REF; + + ps_nxtDPB->ps_pic_buf = NULL; + //Release buffer + ih264d_free_ref_pic_mv_bufs(ps_dpb_mgr->pv_codec_handle, + ps_nxtDPB->u1_buf_id); + ps_next_dpb = ps_nxtDPB->ps_prev_long; + ps_nxtDPB->ps_prev_long = NULL; + } + } + if(u4_lt_idx == 0) + { + ps_dpb_mgr->u1_max_lt_frame_idx = NO_LONG_TERM_INDICIES; + } + else + { + ps_dpb_mgr->u1_max_lt_frame_idx = u4_lt_idx - 1; + } + + break; + } + case SET_LT_INDEX: + { + u4_lt_idx = ps_mmc_params->u4_lt_idx; //Get long term index + if((ps_dpb_mgr->u1_max_lt_frame_idx == NO_LONG_TERM_INDICIES) || + (u4_lt_idx > ps_dpb_mgr->u1_max_lt_frame_idx)) + { + return ERROR_DBP_MANAGER_T; + } + ret = ih264d_insert_st_node(ps_dpb_mgr, ps_pic_buf, u1_buf_id, + u4_cur_pic_num); + if(ret != OK) + return ret; + + if(ps_dpb_mgr->u1_num_st_ref_bufs > 0) + + { + ret = ih264d_delete_st_node_or_make_lt(ps_dpb_mgr, + u4_cur_pic_num, + u4_lt_idx, + u1_fld_pic_flag); + if(ret != OK) + return ret; + } + else + { + return ERROR_DBP_MANAGER_T; + } + + u1_marked_lt = 1; + break; + } + + default: + break; + } + if(u4_mmco == RESET_REF_PICTURES || u4_mmco == RESET_ALL_PICTURES) + { + ih264d_reset_ref_bufs(ps_dpb_mgr); + u4_cur_pic_num = 0; + } + } + } + if(!u1_marked_lt && u1_insert_st_pic) + { + ret = ih264d_insert_st_node(ps_dpb_mgr, ps_pic_buf, u1_buf_id, + u4_cur_pic_num); + if(ret != OK) + return ret; + } + return OK; +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_release_pics_in_dpb */ +/* */ +/* Description : This function deletes all pictures from DPB */ +/* */ +/* Inputs : h_pic_buf_api: pointer to picture buffer API */ +/* u1_disp_bufs: number pictures ready for display */ +/* */ +/* Globals : None */ +/* Outputs : None */ +/* Returns : None */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 22 06 2005 NS Draft */ +/* */ +/*****************************************************************************/ +void ih264d_release_pics_in_dpb(void *pv_dec, + UWORD8 u1_disp_bufs) +{ + WORD8 i; + dec_struct_t *ps_dec = (dec_struct_t *)pv_dec; + + for(i = 0; i < u1_disp_bufs; i++) + { + ih264_buf_mgr_release((buf_mgr_t *)ps_dec->pv_pic_buf_mgr, + i, + BUF_MGR_REF); + ih264_buf_mgr_release((buf_mgr_t *)ps_dec->pv_mv_buf_mgr, + ps_dec->au1_pic_buf_id_mv_buf_id_map[i], + BUF_MGR_REF); + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_delete_gap_frm_sliding */ +/* */ +/* Description : This function deletes a picture from the list of gaps, */ +/* if the frame number of gap frame is lesser than the one */ +/* to be deleted by sliding window */ +/* Inputs : ps_dpb_mgr: pointer to dpb manager */ +/* i4_frame_num: frame number of picture that's going to */ +/* be deleted by sliding window */ +/* pu1_del_node: holds 0 if a gap is deleted else 1 */ +/* Globals : None */ +/* Processing : Function searches for frame number lesser than */ +/* i4_frame_num in the gaps list */ +/* Outputs : None */ +/* Returns : None */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 22 06 2005 NS Draft */ +/* */ +/*****************************************************************************/ +WORD32 ih264d_delete_gap_frm_sliding(dpb_manager_t *ps_dpb_mgr, + WORD32 i4_frame_num, + UWORD8 *pu1_del_node) +{ + WORD8 i1_gap_idx, i, j, j_min; + WORD32 *pi4_gaps_start_frm_num, *pi4_gaps_end_frm_num, i4_gap_frame_num; + WORD32 i4_start_frm_num, i4_end_frm_num; + WORD32 i4_max_frm_num; + WORD32 i4_frm_num, i4_gap_frm_num_min; + + /* find the least frame num from gaps and current DPB node */ + /* Delete the least one */ + *pu1_del_node = 1; + if(0 == ps_dpb_mgr->u1_num_gaps) + return OK; + pi4_gaps_start_frm_num = ps_dpb_mgr->ai4_gaps_start_frm_num; + pi4_gaps_end_frm_num = ps_dpb_mgr->ai4_gaps_end_frm_num; + i4_gap_frame_num = INVALID_FRAME_NUM; + i4_max_frm_num = ps_dpb_mgr->i4_max_frm_num; + + i1_gap_idx = -1; + if(INVALID_FRAME_NUM != i4_frame_num) + { + i4_gap_frame_num = i4_frame_num; + for(i = 0; i < MAX_FRAMES; i++) + { + i4_start_frm_num = pi4_gaps_start_frm_num[i]; + if(INVALID_FRAME_NUM != i4_start_frm_num) + { + i4_end_frm_num = pi4_gaps_end_frm_num[i]; + if(i4_end_frm_num < i4_max_frm_num) + { + if(i4_start_frm_num <= i4_gap_frame_num) + { + i4_gap_frame_num = i4_start_frm_num; + i1_gap_idx = i; + } + } + else + { + if(((i4_start_frm_num <= i4_gap_frame_num) + && (i4_gap_frame_num <= i4_max_frm_num)) + || ((i4_start_frm_num >= i4_gap_frame_num) + && ((i4_gap_frame_num + + i4_max_frm_num) + >= i4_end_frm_num))) + { + i4_gap_frame_num = i4_start_frm_num; + i1_gap_idx = i; + } + } + } + } + } + else + { + /* no valid short term buffers, delete one gap from the least start */ + /* of gap sequence */ + i4_gap_frame_num = pi4_gaps_start_frm_num[0]; + i1_gap_idx = 0; + for(i = 1; i < MAX_FRAMES; i++) + { + if(INVALID_FRAME_NUM != pi4_gaps_start_frm_num[i]) + { + if(pi4_gaps_start_frm_num[i] < i4_gap_frame_num) + { + i4_gap_frame_num = pi4_gaps_start_frm_num[i]; + i1_gap_idx = i; + } + } + } + if(INVALID_FRAME_NUM == i4_gap_frame_num) + { + UWORD32 i4_error_code; + i4_error_code = ERROR_DBP_MANAGER_T; + return i4_error_code; + } + } + + if(-1 != i1_gap_idx) + { + /* find least frame_num in the poc_map, which is in this range */ + i4_start_frm_num = pi4_gaps_start_frm_num[i1_gap_idx]; + if(i4_start_frm_num < 0) + i4_start_frm_num += i4_max_frm_num; + i4_end_frm_num = pi4_gaps_end_frm_num[i1_gap_idx]; + if(i4_end_frm_num < 0) + i4_end_frm_num += i4_max_frm_num; + + i4_gap_frm_num_min = 0xfffffff; + j_min = MAX_FRAMES; + for(j = 0; j < MAX_FRAMES; j++) + { + i4_frm_num = ps_dpb_mgr->ai4_poc_buf_id_map[j][2]; + if((i4_start_frm_num <= i4_frm_num) + && (i4_end_frm_num >= i4_frm_num)) + { + if(i4_frm_num < i4_gap_frm_num_min) + { + j_min = j; + i4_gap_frm_num_min = i4_frm_num; + } + } + } + + if(j_min != MAX_FRAMES) + { + + ps_dpb_mgr->ai4_poc_buf_id_map[j_min][0] = -1; + ps_dpb_mgr->ai4_poc_buf_id_map[j_min][1] = 0x7fffffff; + ps_dpb_mgr->ai4_poc_buf_id_map[j_min][2] = GAP_FRAME_NUM; + ps_dpb_mgr->i1_gaps_deleted++; + + ps_dpb_mgr->ai1_gaps_per_seq[i1_gap_idx]--; + ps_dpb_mgr->u1_num_gaps--; + *pu1_del_node = 0; + if(0 == ps_dpb_mgr->ai1_gaps_per_seq[i1_gap_idx]) + { + ps_dpb_mgr->ai4_gaps_start_frm_num[i1_gap_idx] = + INVALID_FRAME_NUM; + ps_dpb_mgr->ai4_gaps_end_frm_num[i1_gap_idx] = 0; + } + } + } + + return OK; +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_delete_gap_frm_mmco */ +/* */ +/* Description : This function deletes a picture from the list of gaps, */ +/* if the frame number (specified by mmco commands) to be */ +/* deleted is in the range by gap sequence. */ +/* */ +/* Inputs : ps_dpb_mgr: pointer to dpb manager */ +/* i4_frame_num: frame number of picture that's going to */ +/* be deleted by mmco */ +/* pu1_del_node: holds 0 if a gap is deleted else 1 */ +/* Globals : None */ +/* Processing : Function searches for frame number lesser in the range */ +/* specified by gap sequence */ +/* Outputs : None */ +/* Returns : None */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 22 06 2005 NS Draft */ +/* */ +/*****************************************************************************/ +WORD32 ih264d_delete_gap_frm_mmco(dpb_manager_t *ps_dpb_mgr, + WORD32 i4_frame_num, + UWORD8 *pu1_del_node) +{ + WORD8 i, j; + WORD32 *pi4_start, *pi4_end; + WORD32 i4_start_frm_num, i4_end_frm_num, i4_max_frm_num; + + /* find the least frame num from gaps and current DPB node */ + /* Delete the gaps */ + *pu1_del_node = 1; + pi4_start = ps_dpb_mgr->ai4_gaps_start_frm_num; + pi4_end = ps_dpb_mgr->ai4_gaps_end_frm_num; + i4_max_frm_num = ps_dpb_mgr->i4_max_frm_num; + + if(0 == ps_dpb_mgr->u1_num_gaps) + return OK; + + if(i4_frame_num < 0) + i4_frame_num += i4_max_frm_num; + for(i = 0; i < MAX_FRAMES; i++) + { + i4_start_frm_num = pi4_start[i]; + if(i4_start_frm_num < 0) + i4_start_frm_num += i4_max_frm_num; + if(INVALID_FRAME_NUM != i4_start_frm_num) + { + i4_end_frm_num = pi4_end[i]; + if(i4_end_frm_num < 0) + i4_end_frm_num += i4_max_frm_num; + + if((i4_frame_num >= i4_start_frm_num) + && (i4_frame_num <= i4_end_frm_num)) + { + break; + } + else + { + if(((i4_frame_num + i4_max_frm_num) >= i4_start_frm_num) + && ((i4_frame_num + i4_max_frm_num) + <= i4_end_frm_num)) + { + UWORD32 i4_error_code; + i4_error_code = ERROR_DBP_MANAGER_T; + return i4_error_code; + } + } + } + } + + /* find frame_num index, in the poc_map which needs to be deleted */ + for(j = 0; j < MAX_FRAMES; j++) + { + if(i4_frame_num == ps_dpb_mgr->ai4_poc_buf_id_map[j][2]) + break; + } + + if(MAX_FRAMES != i) + { + if(j == MAX_FRAMES) + { + UWORD32 i4_error_code; + i4_error_code = ERROR_DBP_MANAGER_T; + return i4_error_code; + } + + ps_dpb_mgr->ai4_poc_buf_id_map[j][0] = -1; + ps_dpb_mgr->ai4_poc_buf_id_map[j][1] = 0x7fffffff; + ps_dpb_mgr->ai4_poc_buf_id_map[j][2] = GAP_FRAME_NUM; + ps_dpb_mgr->i1_gaps_deleted++; + + ps_dpb_mgr->ai1_gaps_per_seq[i]--; + ps_dpb_mgr->u1_num_gaps--; + *pu1_del_node = 0; + if(0 == ps_dpb_mgr->ai1_gaps_per_seq[i]) + { + ps_dpb_mgr->ai4_gaps_start_frm_num[i] = INVALID_FRAME_NUM; + ps_dpb_mgr->ai4_gaps_end_frm_num[i] = 0; + } + } + else + { + UWORD32 i4_error_code; + i4_error_code = ERROR_DBP_MANAGER_T; + return i4_error_code; + } + + return OK; +} + +/*! + ************************************************************************** + * \if Function name : ih264d_do_mmco_for_gaps \endif + * + * \brief + * Perform decoded picture buffer memory management control operations + * + * \return + * 0 - No error; -1 - Error + * + * \note + * Bitstream is also parsed here to get the MMCOs + * + ************************************************************************** + */ +WORD32 ih264d_do_mmco_for_gaps(dpb_manager_t *ps_dpb_mgr, + UWORD8 u1_num_ref_frames /*!< num_ref_frames from active SeqParSet*/ + ) +{ + struct dpb_info_t *ps_next_dpb; + UWORD8 u1_num_gaps; + UWORD8 u1_st_ref_bufs, u1_lt_ref_bufs, u1_del_node; + WORD8 i; + WORD32 i4_frame_gaps = 1; + WORD32 ret; + + //Sliding window - implements 8.2.5.3, flush out buffers + u1_st_ref_bufs = ps_dpb_mgr->u1_num_st_ref_bufs; + u1_lt_ref_bufs = ps_dpb_mgr->u1_num_lt_ref_bufs; + + while(1) + { + u1_num_gaps = ps_dpb_mgr->u1_num_gaps; + if((u1_st_ref_bufs + u1_lt_ref_bufs + u1_num_gaps + i4_frame_gaps) + > u1_num_ref_frames) + { + if(0 == (u1_st_ref_bufs + u1_num_gaps)) + { + i4_frame_gaps = 0; + ps_dpb_mgr->u1_num_gaps = (u1_num_ref_frames + - u1_lt_ref_bufs); + } + else + { + u1_del_node = 1; + ps_next_dpb = ps_dpb_mgr->ps_dpb_st_head; + + if(u1_st_ref_bufs > 1) + { + for(i = 1; i < (u1_st_ref_bufs - 1); i++) + { + if(ps_next_dpb == NULL) + { + UWORD32 i4_error_code; + i4_error_code = ERROR_DBP_MANAGER_T; + return i4_error_code; + } + ps_next_dpb = ps_next_dpb->ps_prev_short; + } + + if(ps_next_dpb->ps_prev_short->ps_prev_short != NULL) + { + return ERROR_DBP_MANAGER_T; + } + + if(u1_num_gaps) + { + ret = ih264d_delete_gap_frm_sliding(ps_dpb_mgr, + ps_next_dpb->ps_prev_short->i4_frame_num, + &u1_del_node); + if(ret != OK) + return ret; + } + + if(u1_del_node) + { + u1_st_ref_bufs--; + ps_next_dpb->ps_prev_short->u1_used_as_ref = + UNUSED_FOR_REF; + ps_next_dpb->ps_prev_short->s_top_field.u1_reference_info = + UNUSED_FOR_REF; + ps_next_dpb->ps_prev_short->s_bot_field.u1_reference_info = + UNUSED_FOR_REF; + ih264d_free_ref_pic_mv_bufs(ps_dpb_mgr->pv_codec_handle, + ps_next_dpb->ps_prev_short->u1_buf_id); + ps_next_dpb->ps_prev_short->ps_pic_buf = NULL; + ps_next_dpb->ps_prev_short = NULL; + } + } + else + { + if(u1_st_ref_bufs) + { + if(u1_num_gaps) + { + ret = ih264d_delete_gap_frm_sliding(ps_dpb_mgr, + ps_next_dpb->i4_frame_num, + &u1_del_node); + if(ret != OK) + return ret; + } + + if(u1_del_node) + { + u1_st_ref_bufs--; + ps_next_dpb->u1_used_as_ref = FALSE; + ps_next_dpb->s_top_field.u1_reference_info = + UNUSED_FOR_REF; + ps_next_dpb->s_bot_field.u1_reference_info = + UNUSED_FOR_REF; + ih264d_free_ref_pic_mv_bufs(ps_dpb_mgr->pv_codec_handle, + ps_next_dpb->u1_buf_id); + ps_next_dpb->ps_pic_buf = NULL; + ps_next_dpb = NULL; + ps_dpb_mgr->ps_dpb_st_head = NULL; + ps_dpb_mgr->u1_num_st_ref_bufs = u1_st_ref_bufs; + } + } + else + { + ret = ih264d_delete_gap_frm_sliding(ps_dpb_mgr, + INVALID_FRAME_NUM, + &u1_del_node); + if(ret != OK) + return ret; + if(u1_del_node) + { + return ERROR_DBP_MANAGER_T; + } + } + } + } + } + else + { + ps_dpb_mgr->u1_num_gaps += i4_frame_gaps; + break; + } + } + + ps_dpb_mgr->u1_num_st_ref_bufs = u1_st_ref_bufs; + + return OK; +} +/****************************************************************************/ +/* */ +/* Function Name : ih264d_free_node_from_dpb */ +/* */ +/* Description : */ +/* */ +/* Inputs : */ +/* */ +/* Globals : */ +/* */ +/* Processing : */ +/* */ +/* Outputs : */ +/* */ +/* Returns : */ +/* */ +/* Known Issues : */ +/* */ +/* Revision History */ +/* */ +/* DD MM YY Author Changes */ +/* Sarat */ +/****************************************************************************/ +/**** Function Added for Error Resilience *****/ +WORD32 ih264d_free_node_from_dpb(dpb_manager_t *ps_dpb_mgr, + UWORD32 u4_cur_pic_num, + UWORD8 u1_numRef_frames_for_seq) +{ + WORD32 i; + UWORD8 u1_num_gaps = ps_dpb_mgr->u1_num_gaps; + struct dpb_info_t *ps_next_dpb; + UWORD8 u1_del_node = 1; + WORD32 ret; + + //Sliding window - implements 8.2.5.3 + if((ps_dpb_mgr->u1_num_st_ref_bufs + ps_dpb_mgr->u1_num_lt_ref_bufs + + u1_num_gaps) == u1_numRef_frames_for_seq) + { + UWORD8 u1_new_node_flag = 1; + if((0 == ps_dpb_mgr->u1_num_st_ref_bufs) && (0 == u1_num_gaps)) + { + return ERROR_DBP_MANAGER_T; + } + + // Chase the links to reach the last but one picNum, if available + ps_next_dpb = ps_dpb_mgr->ps_dpb_st_head; + + if(ps_dpb_mgr->u1_num_st_ref_bufs > 1) + { + if(ps_next_dpb->i4_frame_num == (WORD32)u4_cur_pic_num) + { + /* Incase of filed pictures top_field has been allocated */ + /* picture buffer and complementary bottom field pair comes */ + /* then the sliding window mechanism should not allocate a */ + /* new node */ + u1_new_node_flag = 0; + } + + for(i = 1; i < (ps_dpb_mgr->u1_num_st_ref_bufs - 1); i++) + { + if(ps_next_dpb == NULL) + return ERROR_DBP_MANAGER_T; + + if(ps_next_dpb->i4_frame_num == (WORD32)u4_cur_pic_num) + { + /* Incase of field pictures top_field has been allocated */ + /* picture buffer and complementary bottom field pair comes */ + /* then the sliding window mechanism should not allocate a */ + /* new node */ + u1_new_node_flag = 0; + } + ps_next_dpb = ps_next_dpb->ps_prev_short; + } + + if(ps_next_dpb->ps_prev_short->ps_prev_short != NULL) + return ERROR_DBP_MANAGER_T; + + if(u1_new_node_flag) + { + if(u1_num_gaps) + { + ret = ih264d_delete_gap_frm_sliding(ps_dpb_mgr, + ps_next_dpb->ps_prev_short->i4_frame_num, + &u1_del_node); + if(ret != OK) + return ret; + } + + if(u1_del_node) + { + ps_dpb_mgr->u1_num_st_ref_bufs--; + ps_next_dpb->ps_prev_short->u1_used_as_ref = UNUSED_FOR_REF; + ps_next_dpb->ps_prev_short->s_top_field.u1_reference_info = + UNUSED_FOR_REF; + ps_next_dpb->ps_prev_short->s_bot_field.u1_reference_info = + UNUSED_FOR_REF; + ih264d_free_ref_pic_mv_bufs(ps_dpb_mgr->pv_codec_handle, + ps_next_dpb->ps_prev_short->u1_buf_id); + ps_next_dpb->ps_prev_short->ps_pic_buf = NULL; + ps_next_dpb->ps_prev_short = NULL; + } + } + } + else + { + if(ps_dpb_mgr->u1_num_st_ref_bufs) + { + ret = ih264d_delete_gap_frm_sliding(ps_dpb_mgr, + ps_next_dpb->i4_frame_num, + &u1_del_node); + if(ret != OK) + return ret; + if((ps_next_dpb->i4_frame_num != (WORD32)u4_cur_pic_num) + && u1_del_node) + { + ps_dpb_mgr->u1_num_st_ref_bufs--; + ps_next_dpb->u1_used_as_ref = FALSE; + ps_next_dpb->s_top_field.u1_reference_info = UNUSED_FOR_REF; + ps_next_dpb->s_bot_field.u1_reference_info = UNUSED_FOR_REF; + ih264d_free_ref_pic_mv_bufs(ps_dpb_mgr->pv_codec_handle, + ps_next_dpb->u1_buf_id); + ps_next_dpb->ps_pic_buf = NULL; + ps_next_dpb = NULL; + } + } + else + { + ret = ih264d_delete_gap_frm_sliding(ps_dpb_mgr, INVALID_FRAME_NUM, &u1_del_node); + if(ret != OK) + return ret; + if(u1_del_node) + return ERROR_DBP_MANAGER_T; + } + } + } + return OK; +} +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_delete_nonref_nondisplay_pics */ +/* */ +/* Description : */ +/* */ +/* */ +/* Inputs : */ +/* Globals : */ +/* Processing : */ +/* */ +/* Outputs : */ +/* Returns : */ +/* */ +/* Issues : */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 05 06 2007 Varun Draft */ +/* */ +/*****************************************************************************/ + +void ih264d_delete_nonref_nondisplay_pics(dpb_manager_t *ps_dpb_mgr) +{ + WORD8 i; + WORD32 (*i4_poc_buf_id_map)[3] = ps_dpb_mgr->ai4_poc_buf_id_map; + + /* remove all gaps marked as unused for ref */ + for(i = 0; (i < MAX_FRAMES) && ps_dpb_mgr->i1_gaps_deleted; i++) + { + if(GAP_FRAME_NUM == i4_poc_buf_id_map[i][2]) + { + ps_dpb_mgr->i1_gaps_deleted--; + ps_dpb_mgr->i1_poc_buf_id_entries--; + i4_poc_buf_id_map[i][0] = -1; + i4_poc_buf_id_map[i][1] = 0x7fffffff; + i4_poc_buf_id_map[i][2] = 0; + } + } +} +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_insert_pic_in_display_list */ +/* */ +/* Description : */ +/* */ +/* */ +/* Inputs : */ +/* Globals : */ +/* Processing : */ +/* */ +/* Outputs : */ +/* Returns : */ +/* */ +/* Issues : */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 05 06 2007 Varun Draft */ +/* */ +/*****************************************************************************/ + +WORD32 ih264d_insert_pic_in_display_list(dpb_manager_t *ps_dpb_mgr, + UWORD8 u1_buf_id, + WORD32 i4_display_poc, + UWORD32 u4_frame_num) +{ + WORD8 i; + WORD32 (*i4_poc_buf_id_map)[3] = ps_dpb_mgr->ai4_poc_buf_id_map; + + for(i = 0; i < MAX_FRAMES; i++) + { + /* Find an empty slot */ + if(i4_poc_buf_id_map[i][0] == -1) + { + if(GAP_FRAME_NUM == i4_poc_buf_id_map[i][2]) + ps_dpb_mgr->i1_gaps_deleted--; + else + ps_dpb_mgr->i1_poc_buf_id_entries++; + + i4_poc_buf_id_map[i][0] = u1_buf_id; + i4_poc_buf_id_map[i][1] = i4_display_poc; + i4_poc_buf_id_map[i][2] = u4_frame_num; + + break; + } + } + + if(MAX_FRAMES == i) + { + + UWORD32 i4_error_code; + i4_error_code = ERROR_GAPS_IN_FRM_NUM; + return i4_error_code; + } + return OK; +} + diff --git a/dependencies/ih264d/decoder/ih264d_error_handler.h b/dependencies/ih264d/decoder/ih264d_error_handler.h new file mode 100644 index 00000000..a651c465 --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_error_handler.h @@ -0,0 +1,138 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ + +#ifndef _IH264D_ERROR_HANDLER_H_ +#define _IH264D_ERROR_HANDLER_H_ + +/*! + ************************************************************************* + * \file ih264d_error_handler.h + * + * \brief + * Contains declaration of ih264d_global_error_handler function + * + * \date + * 21/11/2002 + * + * \author AI + ************************************************************************* + */ + +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264d_structs.h" + +typedef enum +{ + + ERROR_MEM_ALLOC_ISRAM_T = 0x50, + ERROR_MEM_ALLOC_SDRAM_T = 0x51, + ERROR_BUF_MGR = 0x52, + ERROR_DBP_MANAGER_T = 0x53, + ERROR_GAPS_IN_FRM_NUM = 0x54, + ERROR_UNKNOWN_NAL = 0x55, + ERROR_INV_MB_SLC_GRP_T = 0x56, + ERROR_MULTIPLE_SLC_GRP_T = 0x57, + ERROR_UNKNOWN_LEVEL = 0x58, + ERROR_FEATURE_UNAVAIL = 0x59, + ERROR_NOT_SUPP_RESOLUTION = 0x5A, + ERROR_INVALID_PIC_PARAM = 0x5B, + ERROR_INVALID_SEQ_PARAM = 0x5C, + ERROR_EGC_EXCEED_32_1_T = 0x5D, + ERROR_EGC_EXCEED_32_2_T = 0x5E, + ERROR_INV_RANGE_TEV_T = 0x5F, + ERROR_INV_SLC_TYPE_T = 0x60, + ERROR_UNAVAIL_PICBUF_T = 0x61, + ERROR_UNAVAIL_MVBUF_T = 0x62, + ERROR_UNAVAIL_DISPBUF_T = 0x63, + ERROR_INV_POC_TYPE_T = 0x64, + ERROR_PIC1_NOT_FOUND_T = 0x65, + ERROR_PIC0_NOT_FOUND_T = 0x66, + ERROR_NUM_REF = 0x67, + ERROR_REFIDX_ORDER_T = 0x68, + ERROR_EOB_FLUSHBITS_T = 0x69, + ERROR_EOB_GETBITS_T = 0x6A, + ERROR_EOB_GETBIT_T = 0x6B, + ERROR_EOB_BYPASS_T = 0x6C, + ERROR_EOB_DECISION_T = 0x6D, + ERROR_EOB_TERMINATE_T = 0x6E, + ERROR_EOB_READCOEFF4X4CAB_T = 0x6F, + ERROR_INV_RANGE_QP_T = 0x70, + ERROR_END_OF_FRAME_EXPECTED_T = 0x71, + ERROR_MB_TYPE = 0x72, + ERROR_SUB_MB_TYPE = 0x73, + ERROR_CBP = 0x74, + ERROR_REF_IDX = 0x75, + ERROR_NUM_MV = 0x76, + ERROR_CHROMA_PRED_MODE = 0x77, + ERROR_INTRAPRED = 0x78, + ERROR_NEXT_MB_ADDRESS_T = 0x79, + ERROR_MB_ADDRESS_T = 0x7A, + ERROR_MB_GROUP_ASSGN_T = 0x7B, + ERROR_CAVLC_NUM_COEFF_T = 0x7C, + ERROR_CAVLC_SCAN_POS_T = 0x7D, + ERROR_CABAC_RENORM_T = 0x7E, + ERROR_CABAC_SIG_COEFF1_T = 0x7F, + ERROR_CABAC_SIG_COEFF2_T = 0x80, + ERROR_CABAC_ENCODE_COEFF_T = 0x81, + ERROR_INV_SPS_PPS_T = 0x82, + ERROR_INV_SLICE_HDR_T = 0x83, + ERROR_PRED_WEIGHT_TABLE_T = 0x84, + IH264D_VERS_BUF_INSUFFICIENT = 0x85, + ERROR_ACTUAL_LEVEL_GREATER_THAN_INIT = 0x86, + ERROR_CORRUPTED_SLICE = 0x87, + ERROR_FRAME_LIMIT_OVER = 0x88, + ERROR_ACTUAL_RESOLUTION_GREATER_THAN_INIT = 0x89, + ERROR_PROFILE_NOT_SUPPORTED = 0x8A, + ERROR_DISP_WIDTH_RESET_TO_PIC_WIDTH = 0x8B, + ERROR_DISP_WIDTH_INVALID = 0x8C, + ERROR_DANGLING_FIELD_IN_PIC = 0x8D, + ERROR_DYNAMIC_RESOLUTION_NOT_SUPPORTED = 0x8E, + ERROR_INIT_NOT_DONE = 0x8F, + ERROR_LEVEL_UNSUPPORTED = 0x90, + ERROR_START_CODE_NOT_FOUND = 0x91, + ERROR_PIC_NUM_IS_REPEATED = 0x92, + ERROR_IN_LAST_SLICE_OF_PIC = 0x93, + ERROR_NEW_FRAME_EXPECTED = 0x94, + ERROR_INCOMPLETE_FRAME = 0x95, + ERROR_VUI_PARAMS_NOT_FOUND = 0x96, + ERROR_INV_POC = 0x97, + ERROR_SEI_MDCV_PARAMS_NOT_FOUND = 0x98, + ERROR_SEI_CLL_PARAMS_NOT_FOUND = 0x99, + ERROR_SEI_AVE_PARAMS_NOT_FOUND = 0x9A, + ERROR_SEI_CCV_PARAMS_NOT_FOUND = 0x9B, + ERROR_INV_SEI_MDCV_PARAMS = 0x9C, + ERROR_INV_SEI_CLL_PARAMS = 0x9D, + ERROR_INV_SEI_AVE_PARAMS = 0x9E, + ERROR_INV_SEI_CCV_PARAMS = 0x9F, + ERROR_INV_FRAME_NUM = 0xA0 + +} h264_decoder_error_code_t; + +WORD32 ih264d_mark_err_slice_skip(dec_struct_t * ps_dec, + WORD32 num_mb_skip, + UWORD8 u1_is_idr_slice, + UWORD16 u2_frame_num, + pocstruct_t *ps_cur_poc, + WORD32 prev_slice_err); + +void ih264d_err_pic_dispbuf_mgr(dec_struct_t *ps_dec); +#endif /* _IH264D_ERROR_HANDLER_H_ */ diff --git a/dependencies/ih264d/decoder/ih264d_format_conv.c b/dependencies/ih264d/decoder/ih264d_format_conv.c new file mode 100644 index 00000000..631bc23b --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_format_conv.c @@ -0,0 +1,810 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/*****************************************************************************/ +/* */ +/* File Name : ih264d_format_conv.c */ +/* */ +/* Description : Contains functions needed to convert the images in */ +/* different color spaces to yuv 422i color space */ +/* */ +/* */ +/* Issues / Problems : None */ +/* */ +/* Revision History : */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 28 08 2007 Naveen Kumar T Draft */ +/* */ +/*****************************************************************************/ +/*****************************************************************************/ +/* File Includes */ +/*****************************************************************************/ + +/* System include files */ +#include <string.h> +/* User include files */ +#include "ih264_typedefs.h" +#include "iv.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264d_structs.h" +#include "ih264d_format_conv.h" +#include "ih264d_defs.h" + + + +#ifdef LOGO_EN +#include "ih264d_ittiam_logo.h" +#define INSERT_LOGO(pu1_buf_y,pu1_buf_u,pu1_buf_v, u4_stride, u4_x_pos, u4_y_pos, u4_yuv_fmt, u4_disp_wd, u4_disp_ht) \ + ih264d_insert_logo(pu1_buf_y,pu1_buf_u,pu1_buf_v, u4_stride,\ + u4_x_pos, u4_y_pos, u4_yuv_fmt, u4_disp_wd, u4_disp_ht) +#else +#define INSERT_LOGO(pu1_buf_y,pu1_buf_u,pu1_buf_v, u4_stride, u4_x_pos, u4_y_pos, u4_yuv_fmt, u4_disp_wd, u4_disp_ht) +#endif + +/** + ******************************************************************************* + * + * @brief Function used from copying a 420SP buffer + * + * @par Description + * Function used from copying a 420SP buffer + * + * @param[in] pu1_y_src + * Input Y pointer + * + * @param[in] pu1_uv_src + * Input UV pointer (UV is interleaved either in UV or VU format) + * + * @param[in] pu1_y_dst + * Output Y pointer + * + * @param[in] pu1_uv_dst + * Output UV pointer (UV is interleaved in the same format as that of input) + * + * @param[in] wd + * Width + * + * @param[in] ht + * Height + * + * @param[in] src_y_strd + * Input Y Stride + * + * @param[in] src_uv_strd + * Input UV stride + * + * @param[in] dst_y_strd + * Output Y stride + * + * @param[in] dst_uv_strd + * Output UV stride + * + * @returns None + * + * @remarks In case there is a need to perform partial frame copy then + * by passion appropriate source and destination pointers and appropriate + * values for wd and ht it can be done + * + ******************************************************************************* + */ +void ih264d_fmt_conv_420sp_to_rgb565(UWORD8 *pu1_y_src, + UWORD8 *pu1_uv_src, + UWORD16 *pu2_rgb_dst, + WORD32 wd, + WORD32 ht, + WORD32 src_y_strd, + WORD32 src_uv_strd, + WORD32 dst_strd, + WORD32 is_u_first) +{ + + WORD16 i2_r, i2_g, i2_b; + UWORD32 u4_r, u4_g, u4_b; + WORD16 i2_i, i2_j; + UWORD8 *pu1_y_src_nxt; + UWORD16 *pu2_rgb_dst_next_row; + + UWORD8 *pu1_u_src, *pu1_v_src; + + if(is_u_first) + { + pu1_u_src = (UWORD8 *)pu1_uv_src; + pu1_v_src = (UWORD8 *)pu1_uv_src + 1; + } + else + { + pu1_u_src = (UWORD8 *)pu1_uv_src + 1; + pu1_v_src = (UWORD8 *)pu1_uv_src; + } + + pu1_y_src_nxt = pu1_y_src + src_y_strd; + pu2_rgb_dst_next_row = pu2_rgb_dst + dst_strd; + + for(i2_i = 0; i2_i < (ht >> 1); i2_i++) + { + for(i2_j = (wd >> 1); i2_j > 0; i2_j--) + { + i2_b = ((*pu1_u_src - 128) * COEFF4 >> 13); + i2_g = ((*pu1_u_src - 128) * COEFF2 + (*pu1_v_src - 128) * COEFF3) + >> 13; + i2_r = ((*pu1_v_src - 128) * COEFF1) >> 13; + + pu1_u_src += 2; + pu1_v_src += 2; + /* pixel 0 */ + /* B */ + u4_b = CLIP_U8(*pu1_y_src + i2_b); + u4_b >>= 3; + /* G */ + u4_g = CLIP_U8(*pu1_y_src + i2_g); + u4_g >>= 2; + /* R */ + u4_r = CLIP_U8(*pu1_y_src + i2_r); + u4_r >>= 3; + + pu1_y_src++; + *pu2_rgb_dst++ = ((u4_r << 11) | (u4_g << 5) | u4_b); + + /* pixel 1 */ + /* B */ + u4_b = CLIP_U8(*pu1_y_src + i2_b); + u4_b >>= 3; + /* G */ + u4_g = CLIP_U8(*pu1_y_src + i2_g); + u4_g >>= 2; + /* R */ + u4_r = CLIP_U8(*pu1_y_src + i2_r); + u4_r >>= 3; + + pu1_y_src++; + *pu2_rgb_dst++ = ((u4_r << 11) | (u4_g << 5) | u4_b); + + /* pixel 2 */ + /* B */ + u4_b = CLIP_U8(*pu1_y_src_nxt + i2_b); + u4_b >>= 3; + /* G */ + u4_g = CLIP_U8(*pu1_y_src_nxt + i2_g); + u4_g >>= 2; + /* R */ + u4_r = CLIP_U8(*pu1_y_src_nxt + i2_r); + u4_r >>= 3; + + pu1_y_src_nxt++; + *pu2_rgb_dst_next_row++ = ((u4_r << 11) | (u4_g << 5) | u4_b); + + /* pixel 3 */ + /* B */ + u4_b = CLIP_U8(*pu1_y_src_nxt + i2_b); + u4_b >>= 3; + /* G */ + u4_g = CLIP_U8(*pu1_y_src_nxt + i2_g); + u4_g >>= 2; + /* R */ + u4_r = CLIP_U8(*pu1_y_src_nxt + i2_r); + u4_r >>= 3; + + pu1_y_src_nxt++; + *pu2_rgb_dst_next_row++ = ((u4_r << 11) | (u4_g << 5) | u4_b); + + } + + pu1_u_src = pu1_u_src + src_uv_strd - wd; + pu1_v_src = pu1_v_src + src_uv_strd - wd; + + pu1_y_src = pu1_y_src + (src_y_strd << 1) - wd; + pu1_y_src_nxt = pu1_y_src_nxt + (src_y_strd << 1) - wd; + + pu2_rgb_dst = pu2_rgb_dst_next_row - wd + dst_strd; + pu2_rgb_dst_next_row = pu2_rgb_dst_next_row + (dst_strd << 1) - wd; + } + +} + +void ih264d_fmt_conv_420sp_to_rgba8888(UWORD8 *pu1_y_src, + UWORD8 *pu1_uv_src, + UWORD32 *pu4_rgba_dst, + WORD32 wd, + WORD32 ht, + WORD32 src_y_strd, + WORD32 src_uv_strd, + WORD32 dst_strd, + WORD32 is_u_first) +{ + + WORD16 i2_r, i2_g, i2_b; + UWORD32 u4_r, u4_g, u4_b; + WORD16 i2_i, i2_j; + UWORD8 *pu1_y_src_nxt; + UWORD32 *pu4_rgba_dst_next_row; + + UWORD8 *pu1_u_src, *pu1_v_src; + + if(is_u_first) + { + pu1_u_src = (UWORD8 *)pu1_uv_src; + pu1_v_src = (UWORD8 *)pu1_uv_src + 1; + } + else + { + pu1_u_src = (UWORD8 *)pu1_uv_src + 1; + pu1_v_src = (UWORD8 *)pu1_uv_src; + } + + pu1_y_src_nxt = pu1_y_src + src_y_strd; + pu4_rgba_dst_next_row = pu4_rgba_dst + dst_strd; + + for(i2_i = 0; i2_i < (ht >> 1); i2_i++) + { + for(i2_j = (wd >> 1); i2_j > 0; i2_j--) + { + i2_b = ((*pu1_u_src - 128) * COEFF4 >> 13); + i2_g = ((*pu1_u_src - 128) * COEFF2 + (*pu1_v_src - 128) * COEFF3) + >> 13; + i2_r = ((*pu1_v_src - 128) * COEFF1) >> 13; + + pu1_u_src += 2; + pu1_v_src += 2; + /* pixel 0 */ + /* B */ + u4_b = CLIP_U8(*pu1_y_src + i2_b); + /* G */ + u4_g = CLIP_U8(*pu1_y_src + i2_g); + /* R */ + u4_r = CLIP_U8(*pu1_y_src + i2_r); + + pu1_y_src++; + *pu4_rgba_dst++ = ((u4_r << 16) | (u4_g << 8) | (u4_b << 0)); + + /* pixel 1 */ + /* B */ + u4_b = CLIP_U8(*pu1_y_src + i2_b); + /* G */ + u4_g = CLIP_U8(*pu1_y_src + i2_g); + /* R */ + u4_r = CLIP_U8(*pu1_y_src + i2_r); + + pu1_y_src++; + *pu4_rgba_dst++ = ((u4_r << 16) | (u4_g << 8) | (u4_b << 0)); + + /* pixel 2 */ + /* B */ + u4_b = CLIP_U8(*pu1_y_src_nxt + i2_b); + /* G */ + u4_g = CLIP_U8(*pu1_y_src_nxt + i2_g); + /* R */ + u4_r = CLIP_U8(*pu1_y_src_nxt + i2_r); + + pu1_y_src_nxt++; + *pu4_rgba_dst_next_row++ = + ((u4_r << 16) | (u4_g << 8) | (u4_b << 0)); + + /* pixel 3 */ + /* B */ + u4_b = CLIP_U8(*pu1_y_src_nxt + i2_b); + /* G */ + u4_g = CLIP_U8(*pu1_y_src_nxt + i2_g); + /* R */ + u4_r = CLIP_U8(*pu1_y_src_nxt + i2_r); + + pu1_y_src_nxt++; + *pu4_rgba_dst_next_row++ = + ((u4_r << 16) | (u4_g << 8) | (u4_b << 0)); + + } + + pu1_u_src = pu1_u_src + src_uv_strd - wd; + pu1_v_src = pu1_v_src + src_uv_strd - wd; + + pu1_y_src = pu1_y_src + (src_y_strd << 1) - wd; + pu1_y_src_nxt = pu1_y_src_nxt + (src_y_strd << 1) - wd; + + pu4_rgba_dst = pu4_rgba_dst_next_row - wd + dst_strd; + pu4_rgba_dst_next_row = pu4_rgba_dst_next_row + (dst_strd << 1) - wd; + } + +} + +/** + ******************************************************************************* + * + * @brief Function used from copying a 420SP buffer + * + * @par Description + * Function used from copying a 420SP buffer + * + * @param[in] pu1_y_src + * Input Y pointer + * + * @param[in] pu1_uv_src + * Input UV pointer (UV is interleaved either in UV or VU format) + * + * @param[in] pu1_y_dst + * Output Y pointer + * + * @param[in] pu1_uv_dst + * Output UV pointer (UV is interleaved in the same format as that of input) + * + * @param[in] wd + * Width + * + * @param[in] ht + * Height + * + * @param[in] src_y_strd + * Input Y Stride + * + * @param[in] src_uv_strd + * Input UV stride + * + * @param[in] dst_y_strd + * Output Y stride + * + * @param[in] dst_uv_strd + * Output UV stride + * + * @returns None + * + * @remarks In case there is a need to perform partial frame copy then + * by passion appropriate source and destination pointers and appropriate + * values for wd and ht it can be done + * + ******************************************************************************* + */ + +void ih264d_fmt_conv_420sp_to_420sp(UWORD8 *pu1_y_src, + UWORD8 *pu1_uv_src, + UWORD8 *pu1_y_dst, + UWORD8 *pu1_uv_dst, + WORD32 wd, + WORD32 ht, + WORD32 src_y_strd, + WORD32 src_uv_strd, + WORD32 dst_y_strd, + WORD32 dst_uv_strd) +{ + UWORD8 *pu1_src, *pu1_dst; + WORD32 num_rows, num_cols, src_strd, dst_strd; + WORD32 i; + + /* copy luma */ + pu1_src = (UWORD8 *)pu1_y_src; + pu1_dst = (UWORD8 *)pu1_y_dst; + + num_rows = ht; + num_cols = wd; + + src_strd = src_y_strd; + dst_strd = dst_y_strd; + + for(i = 0; i < num_rows; i++) + { + memcpy(pu1_dst, pu1_src, num_cols); + pu1_dst += dst_strd; + pu1_src += src_strd; + } + + /* copy U and V */ + pu1_src = (UWORD8 *)pu1_uv_src; + pu1_dst = (UWORD8 *)pu1_uv_dst; + + num_rows = ht >> 1; + num_cols = wd; + + src_strd = src_uv_strd; + dst_strd = dst_uv_strd; + + for(i = 0; i < num_rows; i++) + { + memcpy(pu1_dst, pu1_src, num_cols); + pu1_dst += dst_strd; + pu1_src += src_strd; + } + return; +} + +/** + ******************************************************************************* + * + * @brief Function used from copying a 420SP buffer + * + * @par Description + * Function used from copying a 420SP buffer + * + * @param[in] pu1_y_src + * Input Y pointer + * + * @param[in] pu1_uv_src + * Input UV pointer (UV is interleaved either in UV or VU format) + * + * @param[in] pu1_y_dst + * Output Y pointer + * + * @param[in] pu1_uv_dst + * Output UV pointer (UV is interleaved in the same format as that of input) + * + * @param[in] wd + * Width + * + * @param[in] ht + * Height + * + * @param[in] src_y_strd + * Input Y Stride + * + * @param[in] src_uv_strd + * Input UV stride + * + * @param[in] dst_y_strd + * Output Y stride + * + * @param[in] dst_uv_strd + * Output UV stride + * + * @returns None + * + * @remarks In case there is a need to perform partial frame copy then + * by passion appropriate source and destination pointers and appropriate + * values for wd and ht it can be done + * + ******************************************************************************* + */ +void ih264d_fmt_conv_420sp_to_420sp_swap_uv(UWORD8 *pu1_y_src, + UWORD8 *pu1_uv_src, + UWORD8 *pu1_y_dst, + UWORD8 *pu1_uv_dst, + WORD32 wd, + WORD32 ht, + WORD32 src_y_strd, + WORD32 src_uv_strd, + WORD32 dst_y_strd, + WORD32 dst_uv_strd) +{ + UWORD8 *pu1_src, *pu1_dst; + WORD32 num_rows, num_cols, src_strd, dst_strd; + WORD32 i; + + /* copy luma */ + pu1_src = (UWORD8 *)pu1_y_src; + pu1_dst = (UWORD8 *)pu1_y_dst; + + num_rows = ht; + num_cols = wd; + + src_strd = src_y_strd; + dst_strd = dst_y_strd; + + for(i = 0; i < num_rows; i++) + { + memcpy(pu1_dst, pu1_src, num_cols); + pu1_dst += dst_strd; + pu1_src += src_strd; + } + + /* copy U and V */ + pu1_src = (UWORD8 *)pu1_uv_src; + pu1_dst = (UWORD8 *)pu1_uv_dst; + + num_rows = ht >> 1; + num_cols = wd; + + src_strd = src_uv_strd; + dst_strd = dst_uv_strd; + + for(i = 0; i < num_rows; i++) + { + WORD32 j; + for(j = 0; j < num_cols; j += 2) + { + pu1_dst[j + 0] = pu1_src[j + 1]; + pu1_dst[j + 1] = pu1_src[j + 0]; + } + pu1_dst += dst_strd; + pu1_src += src_strd; + } + return; +} +/** + ******************************************************************************* + * + * @brief Function used from copying a 420SP buffer + * + * @par Description + * Function used from copying a 420SP buffer + * + * @param[in] pu1_y_src + * Input Y pointer + * + * @param[in] pu1_uv_src + * Input UV pointer (UV is interleaved either in UV or VU format) + * + * @param[in] pu1_y_dst + * Output Y pointer + * + * @param[in] pu1_u_dst + * Output U pointer + * + * @param[in] pu1_v_dst + * Output V pointer + * + * @param[in] wd + * Width + * + * @param[in] ht + * Height + * + * @param[in] src_y_strd + * Input Y Stride + * + * @param[in] src_uv_strd + * Input UV stride + * + * @param[in] dst_y_strd + * Output Y stride + * + * @param[in] dst_uv_strd + * Output UV stride + * + * @param[in] is_u_first + * Flag to indicate if U is the first byte in input chroma part + * + * @returns none + * + * @remarks In case there is a need to perform partial frame copy then + * by passion appropriate source and destination pointers and appropriate + * values for wd and ht it can be done + * + ******************************************************************************* + */ + +void ih264d_fmt_conv_420sp_to_420p(UWORD8 *pu1_y_src, + UWORD8 *pu1_uv_src, + UWORD8 *pu1_y_dst, + UWORD8 *pu1_u_dst, + UWORD8 *pu1_v_dst, + WORD32 wd, + WORD32 ht, + WORD32 src_y_strd, + WORD32 src_uv_strd, + WORD32 dst_y_strd, + WORD32 dst_uv_strd, + WORD32 is_u_first, + WORD32 disable_luma_copy) +{ + UWORD8 *pu1_src, *pu1_dst; + UWORD8 *pu1_u_src, *pu1_v_src; + WORD32 num_rows, num_cols, src_strd, dst_strd; + WORD32 i, j; + + if(0 == disable_luma_copy) + { + /* copy luma */ + pu1_src = (UWORD8 *)pu1_y_src; + pu1_dst = (UWORD8 *)pu1_y_dst; + + num_rows = ht; + num_cols = wd; + + src_strd = src_y_strd; + dst_strd = dst_y_strd; + + for(i = 0; i < num_rows; i++) + { + memcpy(pu1_dst, pu1_src, num_cols); + pu1_dst += dst_strd; + pu1_src += src_strd; + } + } + /* de-interleave U and V and copy to destination */ + if(is_u_first) + { + pu1_u_src = (UWORD8 *)pu1_uv_src; + pu1_v_src = (UWORD8 *)pu1_uv_src + 1; + } + else + { + pu1_u_src = (UWORD8 *)pu1_uv_src + 1; + pu1_v_src = (UWORD8 *)pu1_uv_src; + } + + num_rows = ht >> 1; + num_cols = wd >> 1; + + src_strd = src_uv_strd; + dst_strd = dst_uv_strd; + + for(i = 0; i < num_rows; i++) + { + for(j = 0; j < num_cols; j++) + { + pu1_u_dst[j] = pu1_u_src[j * 2]; + pu1_v_dst[j] = pu1_v_src[j * 2]; + } + + pu1_u_dst += dst_strd; + pu1_v_dst += dst_strd; + pu1_u_src += src_strd; + pu1_v_src += src_strd; + } + return; +} + +/*****************************************************************************/ +/* Function Name : ih264d_format_convert */ +/* */ +/* Description : Implements format conversion/frame copy */ +/* Inputs : ps_dec - Decoder parameters */ +/* Globals : None */ +/* Processing : Refer bumping process in the standard */ +/* Outputs : Assigns display sequence number. */ +/* Returns : None */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 27 04 2005 NS Draft */ +/* */ +/*****************************************************************************/ +void ih264d_format_convert(dec_struct_t *ps_dec, + ivd_get_display_frame_op_t *pv_disp_op, + UWORD32 u4_start_y, + UWORD32 u4_num_rows_y) +{ + UWORD32 convert_uv_only = 0; + iv_yuv_buf_t *ps_op_frm; + UWORD8 *pu1_y_src, *pu1_uv_src; + UWORD32 start_uv = u4_start_y >> 1; + + if(1 == pv_disp_op->u4_error_code) + return; + + ps_op_frm = &(ps_dec->s_disp_frame_info); + + /* Requires u4_start_y and u4_num_rows_y to be even */ + if(u4_start_y & 1) + { + return; + } + + if((1 == ps_dec->u4_share_disp_buf) && + (pv_disp_op->e_output_format == IV_YUV_420SP_UV)) + { + return; + } + + pu1_y_src = (UWORD8 *)ps_op_frm->pv_y_buf; + pu1_y_src += u4_start_y * ps_op_frm->u4_y_strd, + + pu1_uv_src = (UWORD8 *)ps_op_frm->pv_u_buf; + pu1_uv_src += start_uv * ps_op_frm->u4_u_strd; + + if(pv_disp_op->e_output_format == IV_YUV_420P) + { + UWORD8 *pu1_y_dst, *pu1_u_dst, *pu1_v_dst; + IV_COLOR_FORMAT_T e_output_format = pv_disp_op->e_output_format; + + if(0 == ps_dec->u4_share_disp_buf) + { + convert_uv_only = 0; + } + else + { + convert_uv_only = 1; + } + + pu1_y_dst = (UWORD8 *)pv_disp_op->s_disp_frm_buf.pv_y_buf; + pu1_y_dst += u4_start_y * pv_disp_op->s_disp_frm_buf.u4_y_strd; + + pu1_u_dst = (UWORD8 *)pv_disp_op->s_disp_frm_buf.pv_u_buf; + pu1_u_dst += start_uv * pv_disp_op->s_disp_frm_buf.u4_u_strd; + + pu1_v_dst = (UWORD8 *)pv_disp_op->s_disp_frm_buf.pv_v_buf; + pu1_v_dst += start_uv * pv_disp_op->s_disp_frm_buf.u4_v_strd; + + ih264d_fmt_conv_420sp_to_420p(pu1_y_src, + pu1_uv_src, + pu1_y_dst, + pu1_u_dst, + pu1_v_dst, + ps_op_frm->u4_y_wd, + u4_num_rows_y, + ps_op_frm->u4_y_strd, + ps_op_frm->u4_u_strd, + pv_disp_op->s_disp_frm_buf.u4_y_strd, + pv_disp_op->s_disp_frm_buf.u4_u_strd, + 1, + convert_uv_only); + + } + else if((pv_disp_op->e_output_format == IV_YUV_420SP_UV) || + (pv_disp_op->e_output_format == IV_YUV_420SP_VU)) + { + UWORD8* pu1_y_dst, *pu1_uv_dst; + + pu1_y_dst = (UWORD8 *)pv_disp_op->s_disp_frm_buf.pv_y_buf; + pu1_y_dst += u4_start_y * pv_disp_op->s_disp_frm_buf.u4_y_strd; + + pu1_uv_dst = (UWORD8 *)pv_disp_op->s_disp_frm_buf.pv_u_buf; + pu1_uv_dst += start_uv * pv_disp_op->s_disp_frm_buf.u4_u_strd; + + if(pv_disp_op->e_output_format == IV_YUV_420SP_UV) + { + ih264d_fmt_conv_420sp_to_420sp(pu1_y_src, + pu1_uv_src, + pu1_y_dst, + pu1_uv_dst, + ps_op_frm->u4_y_wd, + u4_num_rows_y, + ps_op_frm->u4_y_strd, + ps_op_frm->u4_u_strd, + pv_disp_op->s_disp_frm_buf.u4_y_strd, + pv_disp_op->s_disp_frm_buf.u4_u_strd); + } + else + { + ih264d_fmt_conv_420sp_to_420sp_swap_uv(pu1_y_src, + pu1_uv_src, + pu1_y_dst, + pu1_uv_dst, + ps_op_frm->u4_y_wd, + u4_num_rows_y, + ps_op_frm->u4_y_strd, + ps_op_frm->u4_u_strd, + pv_disp_op->s_disp_frm_buf.u4_y_strd, + pv_disp_op->s_disp_frm_buf.u4_u_strd); + } + } + else if(pv_disp_op->e_output_format == IV_RGB_565) + { + UWORD16 *pu2_rgb_dst; + + pu2_rgb_dst = (UWORD16 *)pv_disp_op->s_disp_frm_buf.pv_y_buf; + pu2_rgb_dst += u4_start_y * pv_disp_op->s_disp_frm_buf.u4_y_strd; + + ih264d_fmt_conv_420sp_to_rgb565(pu1_y_src, + pu1_uv_src, + pu2_rgb_dst, + ps_op_frm->u4_y_wd, + u4_num_rows_y, + ps_op_frm->u4_y_strd, + ps_op_frm->u4_u_strd, + pv_disp_op->s_disp_frm_buf.u4_y_strd, + 1); + } + + if((u4_start_y + u4_num_rows_y) >= ps_dec->s_disp_frame_info.u4_y_ht) + { + + INSERT_LOGO(pv_disp_op->s_disp_frm_buf.pv_y_buf, + pv_disp_op->s_disp_frm_buf.pv_u_buf, + pv_disp_op->s_disp_frm_buf.pv_v_buf, + pv_disp_op->s_disp_frm_buf.u4_y_strd, + ps_dec->u2_disp_width, + ps_dec->u2_disp_height, + pv_disp_op->e_output_format, + ps_op_frm->u4_y_wd, + ps_op_frm->u4_y_ht); + } + + return; +} diff --git a/dependencies/ih264d/decoder/ih264d_format_conv.h b/dependencies/ih264d/decoder/ih264d_format_conv.h new file mode 100644 index 00000000..81a8a0f2 --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_format_conv.h @@ -0,0 +1,120 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/*****************************************************************************/ +/* */ +/* File Name : ih264d_format_conv.h */ +/* */ +/* Description : Contains coefficients and constant reqquired for */ +/* converting from rgb and gray color spaces to yuv422i */ +/* color space */ +/* */ +/* List of Functions : None */ +/* */ +/* Issues / Problems : None */ +/* */ +/* Revision History : */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 27 08 2007 Naveen Kumar T Draft */ +/* */ +/*****************************************************************************/ + +#ifndef _IH264D_FORMAT_CONV_H_ +#define _IH264D_FORMAT_CONV_H_ + +/*****************************************************************************/ +/* Typedefs */ +/*****************************************************************************/ + +#define COEFF_0_Y 66 +#define COEFF_1_Y 129 +#define COEFF_2_Y 25 +#define COEFF_0_U -38 +#define COEFF_1_U -75 +#define COEFF_2_U 112 +#define COEFF_0_V 112 +#define COEFF_1_V -94 +#define COEFF_2_V -18 +#define CONST_RGB_YUV1 4096 +#define CONST_RGB_YUV2 32768 +#define CONST_GRAY_YUV 128 +#define COEF_2_V2_U 0xFFEE0070 + +#define COF_2Y_0Y 0X00190042 +#define COF_1U_0U 0XFFB5FFDA +#define COF_1V_0V 0XFFA20070 + +void ih264d_fmt_conv_420sp_to_420p(UWORD8 *pu1_y_src, + UWORD8 *pu1_uv_src, + UWORD8 *pu1_y_dst, + UWORD8 *pu1_u_dst, + UWORD8 *pu1_v_dst, + WORD32 wd, + WORD32 ht, + WORD32 src_y_strd, + WORD32 src_uv_strd, + WORD32 dst_y_strd, + WORD32 dst_uv_strd, + WORD32 is_u_first, + WORD32 disable_luma_copy); + +void ih264d_fmt_conv_420sp_to_420sp_swap_uv(UWORD8 *pu1_y_src, + UWORD8 *pu1_uv_src, + UWORD8 *pu1_y_dst, + UWORD8 *pu1_uv_dst, + WORD32 wd, + WORD32 ht, + WORD32 src_y_strd, + WORD32 src_uv_strd, + WORD32 dst_y_strd, + WORD32 dst_uv_strd); + +void ih264d_fmt_conv_420sp_to_420sp(UWORD8 *pu1_y_src, + UWORD8 *pu1_uv_src, + UWORD8 *pu1_y_dst, + UWORD8 *pu1_uv_dst, + WORD32 wd, + WORD32 ht, + WORD32 src_y_strd, + WORD32 src_uv_strd, + WORD32 dst_y_strd, + WORD32 dst_uv_strd); + +void ih264d_fmt_conv_420sp_to_rgb565(UWORD8 *pu1_y_src, + UWORD8 *pu1_uv_src, + UWORD16 *pu2_rgb_dst, + WORD32 wd, + WORD32 ht, + WORD32 src_y_strd, + WORD32 src_uv_strd, + WORD32 dst_strd, + WORD32 is_u_first); +#define COEFF1 13073 +#define COEFF2 -3207 +#define COEFF3 -6664 +#define COEFF4 16530 + +void ih264d_format_convert(dec_struct_t *ps_dec, + ivd_get_display_frame_op_t *pv_disp_op, + UWORD32 u4_start_y, + UWORD32 u4_num_rows_y); + + +#endif /* _IH264D_FORMAT_CONV_H_ */ diff --git a/dependencies/ih264d/decoder/ih264d_function_selector.h b/dependencies/ih264d/decoder/ih264d_function_selector.h new file mode 100644 index 00000000..22e2efea --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_function_selector.h @@ -0,0 +1,71 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ + +/** + ******************************************************************************* + * @file + * ih264d_function_selector.h + * + * @brief + * Structure definitions used in the decoder + * + * @author + * Harish + * + * @par List of Functions: + * + * @remarks + * None + * + ******************************************************************************* + */ + +#ifndef _IH264D_FUNCTION_SELECTOR_H_ +#define _IH264D_FUNCTION_SELECTOR_H_ + +#define D_ARCH_NA 1 +#define D_ARCH_ARM_NONEON 2 +#define D_ARCH_ARM_A9Q 3 +#define D_ARCH_ARM_A9A 4 +#define D_ARCH_ARM_A9 5 +#define D_ARCH_ARM_A7 6 +#define D_ARCH_ARM_A5 7 +#define D_ARCH_ARM_A15 8 +#define D_ARCH_ARM_NEONINTR 9 +#define D_ARCH_ARMV8_GENERIC 10 +#define D_ARCH_X86_GENERIC 11 +#define D_ARCH_X86_SSSE3 12 +#define D_ARCH_X86_SSE42 13 +#define D_ARCH_X86_AVX2 14 +#define D_ARCH_MIPS_GENERIC 15 +#define D_ARCH_MIPS_32 16 + +void ih264d_init_arch(dec_struct_t *ps_codec); + +void ih264d_init_function_ptr(dec_struct_t *ps_codec); + +void ih264d_init_function_ptr_generic(dec_struct_t *ps_codec); +void ih264d_init_function_ptr_ssse3(dec_struct_t *ps_codec); +void ih264d_init_function_ptr_sse42(dec_struct_t *ps_codec); + +void ih264d_init_function_ptr_a9q(dec_struct_t *ps_codec); +void ih264d_init_function_ptr_av8(dec_struct_t *ps_codec); + +#endif /* _IH264D_FUNCTION_SELECTOR_H_ */ diff --git a/dependencies/ih264d/decoder/ih264d_function_selector_generic.c b/dependencies/ih264d/decoder/ih264d_function_selector_generic.c new file mode 100644 index 00000000..48956ef6 --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_function_selector_generic.c @@ -0,0 +1,222 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** + ******************************************************************************* + * @file + * ih264e_function_selector_generic.c + * + * @brief + * Contains functions to initialize function pointers of codec context + * + * @author + * Ittiam + * + * @par List of Functions: + * - ih264e_init_function_ptr_generic + * + * @remarks + * None + * + ******************************************************************************* + */ + +/*****************************************************************************/ +/* File Includes */ +/*****************************************************************************/ + +/* System Include files */ +#include <stdio.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> + +/* User Include files */ +#include "ih264_typedefs.h" +#include "iv.h" +#include "ivd.h" +#include "ih264_defs.h" +#include "ih264_size_defs.h" +#include "ih264_error.h" +#include "ih264_trans_quant_itrans_iquant.h" +#include "ih264_inter_pred_filters.h" + +#include "ih264d_structs.h" +#include "ih264d_function_selector.h" + +/** + ******************************************************************************* + * + * @brief Initialize the intra/inter/transform/deblk function pointers of + * codec context + * + * @par Description: the current routine initializes the function pointers of + * codec context basing on the architecture in use + * + * @param[in] ps_codec + * Codec context pointer + * + * @returns none + * + * @remarks none + * + ******************************************************************************* + */ +void ih264d_init_function_ptr_generic(dec_struct_t *ps_codec) +{ + + WORD32 i = 0; + + /* Init function pointers for intra pred leaf level functions luma + * Intra 16x16 */ + ps_codec->apf_intra_pred_luma_16x16[0] = + ih264_intra_pred_luma_16x16_mode_vert; + ps_codec->apf_intra_pred_luma_16x16[1] = + ih264_intra_pred_luma_16x16_mode_horz; + ps_codec->apf_intra_pred_luma_16x16[2] = + ih264_intra_pred_luma_16x16_mode_dc; + ps_codec->apf_intra_pred_luma_16x16[3] = + ih264_intra_pred_luma_16x16_mode_plane; + + /* Init function pointers for intra pred leaf level functions luma + * Intra 4x4 */ + ps_codec->apf_intra_pred_luma_4x4[0] = ih264_intra_pred_luma_4x4_mode_vert; + ps_codec->apf_intra_pred_luma_4x4[1] = ih264_intra_pred_luma_4x4_mode_horz; + ps_codec->apf_intra_pred_luma_4x4[2] = ih264_intra_pred_luma_4x4_mode_dc; + ps_codec->apf_intra_pred_luma_4x4[3] = + ih264_intra_pred_luma_4x4_mode_diag_dl; + ps_codec->apf_intra_pred_luma_4x4[4] = + ih264_intra_pred_luma_4x4_mode_diag_dr; + ps_codec->apf_intra_pred_luma_4x4[5] = + ih264_intra_pred_luma_4x4_mode_vert_r; + ps_codec->apf_intra_pred_luma_4x4[6] = + ih264_intra_pred_luma_4x4_mode_horz_d; + ps_codec->apf_intra_pred_luma_4x4[7] = + ih264_intra_pred_luma_4x4_mode_vert_l; + ps_codec->apf_intra_pred_luma_4x4[8] = + ih264_intra_pred_luma_4x4_mode_horz_u; + + /* Init function pointers for intra pred leaf level functions luma + * Intra 8x8 */ + ps_codec->apf_intra_pred_luma_8x8[0] = ih264_intra_pred_luma_8x8_mode_vert; + ps_codec->apf_intra_pred_luma_8x8[1] = ih264_intra_pred_luma_8x8_mode_horz; + ps_codec->apf_intra_pred_luma_8x8[2] = ih264_intra_pred_luma_8x8_mode_dc; + ps_codec->apf_intra_pred_luma_8x8[3] = + ih264_intra_pred_luma_8x8_mode_diag_dl; + ps_codec->apf_intra_pred_luma_8x8[4] = + ih264_intra_pred_luma_8x8_mode_diag_dr; + ps_codec->apf_intra_pred_luma_8x8[5] = + ih264_intra_pred_luma_8x8_mode_vert_r; + ps_codec->apf_intra_pred_luma_8x8[6] = + ih264_intra_pred_luma_8x8_mode_horz_d; + ps_codec->apf_intra_pred_luma_8x8[7] = + ih264_intra_pred_luma_8x8_mode_vert_l; + ps_codec->apf_intra_pred_luma_8x8[8] = + ih264_intra_pred_luma_8x8_mode_horz_u; + + ps_codec->pf_intra_pred_ref_filtering = + ih264_intra_pred_luma_8x8_mode_ref_filtering; + + /* Init function pointers for intra pred leaf level functions chroma + * Intra 8x8 */ + ps_codec->apf_intra_pred_chroma[0] = ih264_intra_pred_chroma_8x8_mode_vert; + ps_codec->apf_intra_pred_chroma[1] = ih264_intra_pred_chroma_8x8_mode_horz; + ps_codec->apf_intra_pred_chroma[2] = ih264_intra_pred_chroma_8x8_mode_dc; + ps_codec->apf_intra_pred_chroma[3] = ih264_intra_pred_chroma_8x8_mode_plane; + + ps_codec->pf_default_weighted_pred_luma = ih264_default_weighted_pred_luma; + ps_codec->pf_default_weighted_pred_chroma = + ih264_default_weighted_pred_chroma; + ps_codec->pf_weighted_pred_luma = ih264_weighted_pred_luma; + ps_codec->pf_weighted_pred_chroma = ih264_weighted_pred_chroma; + ps_codec->pf_weighted_bi_pred_luma = ih264_weighted_bi_pred_luma; + ps_codec->pf_weighted_bi_pred_chroma = ih264_weighted_bi_pred_chroma; + + /* Padding Functions */ + ps_codec->pf_pad_top = ih264_pad_top; + ps_codec->pf_pad_bottom = ih264_pad_bottom; + ps_codec->pf_pad_left_luma = ih264_pad_left_luma; + ps_codec->pf_pad_left_chroma = ih264_pad_left_chroma; + ps_codec->pf_pad_right_luma = ih264_pad_right_luma; + ps_codec->pf_pad_right_chroma = ih264_pad_right_chroma; + + ps_codec->pf_iquant_itrans_recon_luma_4x4 = ih264_iquant_itrans_recon_4x4; + ps_codec->pf_iquant_itrans_recon_luma_4x4_dc = + ih264_iquant_itrans_recon_4x4_dc; + ps_codec->pf_iquant_itrans_recon_luma_8x8 = ih264_iquant_itrans_recon_8x8; + ps_codec->pf_iquant_itrans_recon_luma_8x8_dc = + ih264_iquant_itrans_recon_8x8_dc; + ps_codec->pf_iquant_itrans_recon_chroma_4x4 = + ih264_iquant_itrans_recon_chroma_4x4; + ps_codec->pf_iquant_itrans_recon_chroma_4x4_dc = + ih264_iquant_itrans_recon_chroma_4x4_dc; + ps_codec->pf_ihadamard_scaling_4x4 = ih264_ihadamard_scaling_4x4; + + /* Init fn ptr luma deblocking */ + ps_codec->pf_deblk_luma_vert_bs4 = ih264_deblk_luma_vert_bs4; + ps_codec->pf_deblk_luma_vert_bslt4 = ih264_deblk_luma_vert_bslt4; + ps_codec->pf_deblk_luma_vert_bs4_mbaff = ih264_deblk_luma_vert_bs4_mbaff; + ps_codec->pf_deblk_luma_vert_bslt4_mbaff = + ih264_deblk_luma_vert_bslt4_mbaff; + + ps_codec->pf_deblk_luma_horz_bs4 = ih264_deblk_luma_horz_bs4; + ps_codec->pf_deblk_luma_horz_bslt4 = ih264_deblk_luma_horz_bslt4; + + /* Init fn ptr chroma deblocking */ + ps_codec->pf_deblk_chroma_vert_bs4 = ih264_deblk_chroma_vert_bs4; + ps_codec->pf_deblk_chroma_vert_bslt4 = ih264_deblk_chroma_vert_bslt4; + ps_codec->pf_deblk_chroma_vert_bs4_mbaff = + ih264_deblk_chroma_vert_bs4_mbaff; + ps_codec->pf_deblk_chroma_vert_bslt4_mbaff = + ih264_deblk_chroma_vert_bslt4_mbaff; + + ps_codec->pf_deblk_chroma_horz_bs4 = ih264_deblk_chroma_horz_bs4; + ps_codec->pf_deblk_chroma_horz_bslt4 = ih264_deblk_chroma_horz_bslt4; + + /* Inter pred leaf level functions */ + ps_codec->apf_inter_pred_luma[0] = ih264_inter_pred_luma_copy; + ps_codec->apf_inter_pred_luma[1] = ih264_inter_pred_luma_horz_qpel; + ps_codec->apf_inter_pred_luma[2] = ih264_inter_pred_luma_horz; + ps_codec->apf_inter_pred_luma[3] = ih264_inter_pred_luma_horz_qpel; + ps_codec->apf_inter_pred_luma[4] = ih264_inter_pred_luma_vert_qpel; + ps_codec->apf_inter_pred_luma[5] = + ih264_inter_pred_luma_horz_qpel_vert_qpel; + ps_codec->apf_inter_pred_luma[6] = + ih264_inter_pred_luma_horz_hpel_vert_qpel; + ps_codec->apf_inter_pred_luma[7] = + ih264_inter_pred_luma_horz_qpel_vert_qpel; + ps_codec->apf_inter_pred_luma[8] = ih264_inter_pred_luma_vert; + ps_codec->apf_inter_pred_luma[9] = + ih264_inter_pred_luma_horz_qpel_vert_hpel; + ps_codec->apf_inter_pred_luma[10] = + ih264_inter_pred_luma_horz_hpel_vert_hpel; + ps_codec->apf_inter_pred_luma[11] = + ih264_inter_pred_luma_horz_qpel_vert_hpel; + ps_codec->apf_inter_pred_luma[12] = ih264_inter_pred_luma_vert_qpel; + ps_codec->apf_inter_pred_luma[13] = + ih264_inter_pred_luma_horz_qpel_vert_qpel; + ps_codec->apf_inter_pred_luma[14] = + ih264_inter_pred_luma_horz_hpel_vert_qpel; + ps_codec->apf_inter_pred_luma[15] = + ih264_inter_pred_luma_horz_qpel_vert_qpel; + + ps_codec->pf_inter_pred_chroma = ih264_inter_pred_chroma; + + return; +} diff --git a/dependencies/ih264d/decoder/ih264d_inter_pred.c b/dependencies/ih264d/decoder/ih264d_inter_pred.c new file mode 100644 index 00000000..b99a77c4 --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_inter_pred.c @@ -0,0 +1,1574 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/*! + ************************************************************************** + * \file ih264d_inter_pred.c + * + * \brief + * This file contains routines to perform MotionCompensation tasks + * + * Detailed_description + * + * \date + * 20/11/2002 + * + * \author Arvind Raman + ************************************************************************** + */ + +#include <string.h> +#include "ih264d_defs.h" +#include "ih264d_mvpred.h" +#include "ih264d_error_handler.h" +#include "ih264d_structs.h" +#include "ih264d_defs.h" +#include "ih264d_inter_pred.h" +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264d_debug.h" +#include "ih264d_tables.h" +#include "ih264d_mb_utils.h" + + +void ih264d_pad_on_demand(pred_info_t *ps_pred, UWORD8 lum_chrom_blk); + + + +void ih264d_copy_multiplex_data(UWORD8 *puc_Source, + UWORD8 *puc_To, + UWORD32 uc_w, + UWORD32 uc_h, + UWORD32 ui16_sourceWidth, + UWORD32 ui16_toWidth) +{ + UWORD8 uc_i, uc_j; + + for(uc_i = 0; uc_i < uc_h; uc_i++) + { + memcpy(puc_To, puc_Source, uc_w); + puc_To += ui16_toWidth; + puc_Source += ui16_sourceWidth; + } +} + + +/*! + ************************************************************************** + * \if Function name : dma_2d1d \endif + * + * \brief + * 2D -> 1D linear DMA into the reference buffers + * + * \return + * None + ************************************************************************** + */ +void ih264d_copy_2d1d(UWORD8 *puc_src, + UWORD8 *puc_dest, + UWORD16 ui16_srcWidth, + UWORD16 ui16_widthToFill, + UWORD16 ui16_heightToFill) +{ + UWORD32 uc_w, uc_h; + for(uc_h = ui16_heightToFill; uc_h != 0; uc_h--) + { + memcpy(puc_dest, puc_src, ui16_widthToFill); + puc_dest += ui16_widthToFill; + puc_src += ui16_srcWidth; + } +} + +/*! + ************************************************************************** + * \if Function name : ih264d_fill_pred_info \endif + * + * \brief + * Fills inter prediction related info + * + * \return + * None + ************************************************************************** + */ +void ih264d_fill_pred_info(WORD16 *pi2_mv,WORD32 part_width,WORD32 part_height, WORD32 sub_mb_num, + WORD32 pred_dir,pred_info_pkd_t *ps_pred_pkd,WORD8 i1_buf_id, + WORD8 i1_ref_idx,UWORD32 *pu4_wt_offset,UWORD8 u1_pic_type) +{ + WORD32 insert_bits; + + ps_pred_pkd->i2_mv[0] = pi2_mv[0]; + ps_pred_pkd->i2_mv[1] = pi2_mv[1]; + + insert_bits = sub_mb_num & 3; /*sub mb x*/ + ps_pred_pkd->i1_size_pos_info = insert_bits; + insert_bits = sub_mb_num >> 2;/*sub mb y*/ + ps_pred_pkd->i1_size_pos_info |= insert_bits << 2; + insert_bits = part_width >> 1; + ps_pred_pkd->i1_size_pos_info |= insert_bits << 4; + insert_bits = part_height >> 1; + ps_pred_pkd->i1_size_pos_info |= insert_bits << 6; + + ps_pred_pkd->i1_ref_idx_info = i1_ref_idx; + ps_pred_pkd->i1_ref_idx_info |= (pred_dir << 6); + ps_pred_pkd->i1_buf_id = i1_buf_id; + ps_pred_pkd->pu4_wt_offst = pu4_wt_offset; + ps_pred_pkd->u1_pic_type = u1_pic_type; + + +} + + + + + + + +/*****************************************************************************/ +/* \if Function name : formMbPartInfo \endif */ +/* */ +/* \brief */ +/* Form the Mb partition information structure, to be used by the MC */ +/* routine */ +/* */ +/* \return */ +/* None */ +/* \note */ +/* c_bufx is used to select PredBuffer, */ +/* if it's only Forward/Backward prediction always buffer used is */ +/* puc_MbLumaPredBuffer[0 to X1],pu1_mb_cb_pred_buffer[0 to X1] and */ +/* pu1_mb_cr_pred_buffer[0 to X1] */ +/* */ +/* if it's bidirect for forward ..PredBuffer[0 to X1] buffer is used and */ +/* ..PredBuffer[X2 to X3] for backward prediction. and */ +/* */ +/* Final predicted samples values are the average of ..PredBuffer[0 to X1]*/ +/* and ..PredBuffer[X2 to X3] */ +/* */ +/* X1 is 255 for Luma and 63 for Chroma */ +/* X2 is 256 for Luma and 64 for Chroma */ +/* X3 is 511 for Luma and 127 for Chroma */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 11 05 2005 SWRN Modified to handle pod */ +/*****************************************************************************/ + +WORD32 ih264d_form_mb_part_info_bp(pred_info_pkd_t *ps_pred_pkd, + dec_struct_t * ps_dec, + UWORD16 u2_mb_x, + UWORD16 u2_mb_y, + WORD32 mb_index, + dec_mb_info_t *ps_cur_mb_info) +{ + /* The reference buffer pointer */ + WORD32 i2_frm_x, i2_frm_y; + WORD32 i2_tmp_mv_x, i2_tmp_mv_y; + WORD32 i2_rec_x, i2_rec_y; + + WORD32 u2_pic_ht; + WORD32 u2_frm_wd; + WORD32 u2_rec_wd; + UWORD8 u1_sub_x = 0,u1_sub_y=0 ; + UWORD8 u1_part_wd = 0,u1_part_ht = 0; + WORD16 i2_mv_x,i2_mv_y; + + /********************************************/ + /* i1_mc_wd width reqd for mcomp */ + /* u1_dma_ht height reqd for mcomp */ + /* u1_dma_wd width aligned to 4 bytes */ + /* u1_dx fractional part of width */ + /* u1_dx fractional part of height */ + /********************************************/ + UWORD32 i1_mc_wd; + + WORD32 u1_dma_ht; + + UWORD32 u1_dma_wd; + UWORD32 u1_dx; + UWORD32 u1_dy; + pred_info_t * ps_pred = ps_dec->ps_pred + ps_dec->u4_pred_info_idx; + dec_slice_params_t * const ps_cur_slice = ps_dec->ps_cur_slice; + tfr_ctxt_t *ps_frame_buf; + struct pic_buffer_t *ps_ref_frm; + UWORD8 u1_scale_ref,u1_mbaff,u1_field; + pic_buffer_t **pps_ref_frame; + WORD8 i1_size_pos_info,i1_buf_id; + + PROFILE_DISABLE_MB_PART_INFO() + + UNUSED(ps_cur_mb_info); + i1_size_pos_info = ps_pred_pkd->i1_size_pos_info; + GET_XPOS_PRED(u1_sub_x,i1_size_pos_info); + GET_YPOS_PRED(u1_sub_y,i1_size_pos_info); + GET_WIDTH_PRED(u1_part_wd,i1_size_pos_info); + GET_HEIGHT_PRED(u1_part_ht,i1_size_pos_info); + i2_mv_x = ps_pred_pkd->i2_mv[0]; + i2_mv_y = ps_pred_pkd->i2_mv[1]; + i1_buf_id = ps_pred_pkd->i1_buf_id; + + + ps_ref_frm = ps_dec->apv_buf_id_pic_buf_map[i1_buf_id]; + + + { + ps_frame_buf = &ps_dec->s_tran_addrecon; + } + + + /* Transfer Setup Y */ + { + UWORD8 *pu1_pred, *pu1_rec; + + /* calculating rounded motion vectors and fractional components */ + i2_tmp_mv_x = i2_mv_x; + i2_tmp_mv_y = i2_mv_y; + u1_dx = i2_tmp_mv_x & 0x3; + u1_dy = i2_tmp_mv_y & 0x3; + i2_tmp_mv_x >>= 2; + i2_tmp_mv_y >>= 2; + i1_mc_wd = u1_part_wd << 2; + u1_dma_ht = u1_part_ht << 2; + if(u1_dx) + { + i2_tmp_mv_x -= 2; + i1_mc_wd += 5; + } + if(u1_dy) + { + i2_tmp_mv_y -= 2; + u1_dma_ht += 5; + } + + /********************************************************************/ + /* Calulating the horizontal and the vertical u4_ofst from top left */ + /* edge of the reference frame, and subsequent clipping */ + /********************************************************************/ + u2_pic_ht = ps_dec->u2_pic_ht; + u2_frm_wd = ps_dec->u2_frm_wd_y; + i2_rec_x = u1_sub_x << 2; + i2_rec_y = u1_sub_y << 2; + + i2_frm_x = (u2_mb_x << 4) + i2_rec_x + i2_tmp_mv_x; + i2_frm_y = (u2_mb_y << 4) + i2_rec_y + i2_tmp_mv_y; + + i2_frm_x = CLIP3(MAX_OFFSET_OUTSIDE_X_FRM, (ps_dec->u2_pic_wd - 1), + i2_frm_x); + i2_frm_y = CLIP3(((1 - u1_dma_ht)), (u2_pic_ht - (1)), i2_frm_y); + + pu1_pred = ps_ref_frm->pu1_buf1 + i2_frm_y * u2_frm_wd + i2_frm_x; + + u1_dma_wd = (i1_mc_wd + 3) & 0xFC; + + /********************************************************************/ + /* Calulating the horizontal and the vertical u4_ofst from top left */ + /* edge of the recon buffer */ + /********************************************************************/ + u2_rec_wd = MB_SIZE; + { + u2_rec_wd = ps_dec->u2_frm_wd_y; + i2_rec_x += (mb_index << 4); + pu1_rec = ps_frame_buf->pu1_dest_y + i2_rec_y * u2_rec_wd + + i2_rec_x; + } + + /* filling the pred and dma structures for Y */ + u2_frm_wd = ps_dec->u2_frm_wd_y; + + ps_pred->u2_u1_ref_buf_wd = u1_dma_wd; + ps_pred->i1_dma_ht = u1_dma_ht; + ps_pred->i1_mc_wd = i1_mc_wd; + ps_pred->u2_frm_wd = u2_frm_wd; + ps_pred->pu1_rec_y_u = pu1_rec; + ps_pred->u2_dst_stride = u2_rec_wd; + + ps_pred->i1_mb_partwidth = u1_part_wd << 2; + ps_pred->i1_mb_partheight = u1_part_ht << 2; + ps_pred->u1_dydx = (u1_dy << 2) + u1_dx; + + ps_pred->pu1_y_ref = pu1_pred; + + } + + /* Increment ps_pred index */ + ps_pred++; + + /* Transfer Setup U & V */ + { + WORD32 i4_ref_offset, i4_rec_offset; + UWORD8 *pu1_pred_u, *pu1_pred_v; + + + /* calculating rounded motion vectors and fractional components */ + i2_tmp_mv_x = i2_mv_x; + i2_tmp_mv_y = i2_mv_y; + + /************************************************************************/ + /* Table 8-9: Derivation of the vertical component of the chroma vector */ + /* in field coding mode */ + /************************************************************************/ + + /* Eighth sample of the chroma MV */ + u1_dx = i2_tmp_mv_x & 0x7; + u1_dy = i2_tmp_mv_y & 0x7; + + /********************************************************************/ + /* Calculating the full pel MV for chroma which is 1/2 of the Luma */ + /* MV in full pel units */ + /********************************************************************/ + i2_mv_x = i2_tmp_mv_x; + i2_mv_y = i2_tmp_mv_y; + i2_tmp_mv_x = SIGN_POW2_DIV(i2_tmp_mv_x, 3); + i2_tmp_mv_y = SIGN_POW2_DIV(i2_tmp_mv_y, 3); + i1_mc_wd = u1_part_wd << 1; + u1_dma_ht = u1_part_ht << 1; + if(u1_dx) + { + i2_tmp_mv_x -= (i2_mv_x < 0); + i1_mc_wd++; + } + if(u1_dy != 0) + { + i2_tmp_mv_y -= (i2_mv_y < 0); + u1_dma_ht++; + } + + /********************************************************************/ + /* Calulating the horizontal and the vertical u4_ofst from top left */ + /* edge of the reference frame, and subsequent clipping */ + /********************************************************************/ + u2_pic_ht >>= 1; + u2_frm_wd = ps_dec->u2_frm_wd_uv; + i2_rec_x = u1_sub_x << 1; + i2_rec_y = u1_sub_y << 1; + + i2_frm_x = (u2_mb_x << 3) + i2_rec_x + i2_tmp_mv_x; + i2_frm_y = (u2_mb_y << 3) + i2_rec_y + i2_tmp_mv_y; + + i2_frm_x = CLIP3(MAX_OFFSET_OUTSIDE_UV_FRM, + ((ps_dec->u2_pic_wd >> 1) - 1), i2_frm_x); + i2_frm_y = CLIP3(((1 - u1_dma_ht)), (u2_pic_ht - (1)), i2_frm_y); + + i4_ref_offset = i2_frm_y * u2_frm_wd + i2_frm_x * YUV420SP_FACTOR; + u1_dma_wd = (i1_mc_wd + 3) & 0xFC; + + /********************************************************************/ + /* Calulating the horizontal and the vertical u4_ofst from top left */ + /* edge of the recon buffer */ + /********************************************************************/ + /* CHANGED CODE */ + u2_rec_wd = BLK8x8SIZE * YUV420SP_FACTOR; + i4_rec_offset = i2_rec_y * u2_rec_wd + i2_rec_x * YUV420SP_FACTOR; + + { + u2_rec_wd = ps_dec->u2_frm_wd_uv; + i2_rec_x += (mb_index << 3); + i4_rec_offset = i2_rec_y * u2_rec_wd + i2_rec_x * YUV420SP_FACTOR; + ps_pred->pu1_rec_y_u = ps_frame_buf->pu1_dest_u + i4_rec_offset; + ps_pred->u1_pi1_wt_ofst_rec_v = ps_frame_buf->pu1_dest_v + + i4_rec_offset; + } + + /* CHANGED CODE */ + + /* filling the common pred structures for U */ + u2_frm_wd = ps_dec->u2_frm_wd_uv; + + ps_pred->u2_u1_ref_buf_wd = u1_dma_wd; + ps_pred->i1_dma_ht = u1_dma_ht; + ps_pred->i1_mc_wd = i1_mc_wd; + + ps_pred->u2_frm_wd = u2_frm_wd; + ps_pred->u2_dst_stride = u2_rec_wd; + + ps_pred->i1_mb_partwidth = u1_part_wd << 1; + ps_pred->i1_mb_partheight = u1_part_ht << 1; + ps_pred->u1_dydx = (u1_dy << 3) + u1_dx; + + pu1_pred_u = ps_ref_frm->pu1_buf2 + i4_ref_offset; + pu1_pred_v = ps_ref_frm->pu1_buf3 + i4_ref_offset; + + /* Copy U & V partitions */ + ps_pred->pu1_u_ref = pu1_pred_u; + + /* Increment the reference buffer Index */ + ps_pred->pu1_v_ref = pu1_pred_v; + } + + /* Increment ps_pred index */ + ps_dec->u4_pred_info_idx += 2; + + return OK; + +} + + +/*****************************************************************************/ +/* \if Function name : formMbPartInfo \endif */ +/* */ +/* \brief */ +/* Form the Mb partition information structure, to be used by the MC */ +/* routine */ +/* */ +/* \return */ +/* None */ +/* \note */ +/* c_bufx is used to select PredBuffer, */ +/* if it's only Forward/Backward prediction always buffer used is */ +/* puc_MbLumaPredBuffer[0 to X1],pu1_mb_cb_pred_buffer[0 to X1] and */ +/* pu1_mb_cr_pred_buffer[0 to X1] */ +/* */ +/* if it's bidirect for forward ..PredBuffer[0 to X1] buffer is used and */ +/* ..PredBuffer[X2 to X3] for backward prediction. and */ +/* */ +/* Final predicted samples values are the average of ..PredBuffer[0 to X1]*/ +/* and ..PredBuffer[X2 to X3] */ +/* */ +/* X1 is 255 for Luma and 63 for Chroma */ +/* X2 is 256 for Luma and 64 for Chroma */ +/* X3 is 511 for Luma and 127 for Chroma */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 11 05 2005 SWRN Modified to handle pod */ +/*****************************************************************************/ +WORD32 ih264d_form_mb_part_info_mp(pred_info_pkd_t *ps_pred_pkd, + dec_struct_t * ps_dec, + UWORD16 u2_mb_x, + UWORD16 u2_mb_y, + WORD32 mb_index, + dec_mb_info_t *ps_cur_mb_info) +{ + /* The reference buffer pointer */ + UWORD8 *pu1_ref_buf; + WORD16 i2_frm_x, i2_frm_y, i2_tmp_mv_x, i2_tmp_mv_y, i2_pod_ht; + WORD16 i2_rec_x, i2_rec_y; + UWORD16 u2_pic_ht, u2_frm_wd, u2_rec_wd; + UWORD8 u1_wght_pred_type, u1_wted_bipred_idc; + UWORD16 u2_tot_ref_scratch_size; + UWORD8 u1_sub_x = 0; + UWORD8 u1_sub_y = 0; + UWORD8 u1_is_bi_dir = 0; + + /********************************************/ + /* i1_mc_wd width reqd for mcomp */ + /* u1_dma_ht height reqd for mcomp */ + /* u1_dma_wd width aligned to 4 bytes */ + /* u1_dx fractional part of width */ + /* u1_dx fractional part of height */ + /********************************************/ + UWORD8 i1_mc_wd, u1_dma_ht, u1_dma_wd, u1_dx, u1_dy; + pred_info_t * ps_pred ; + dec_slice_params_t * const ps_cur_slice = ps_dec->ps_cur_slice; + const UWORD8 u1_slice_type = ps_dec->ps_decode_cur_slice->slice_type; + UWORD8 u1_pod_bot, u1_pod_top; + + /* load the pictype for pod u4_flag & chroma motion vector derivation */ + UWORD8 u1_ref_pic_type ; + + /* set default value to flags specifying field nature of picture & mb */ + UWORD32 u1_mb_fld = 0, u1_mb_or_pic_fld; + UWORD32 u1_mb_bot = 0, u1_pic_bot = 0, u1_mb_or_pic_bot; + tfr_ctxt_t *ps_frame_buf; + /* calculate flags specifying field nature of picture & mb */ + const UWORD32 u1_pic_fld = ps_cur_slice->u1_field_pic_flag; + WORD8 i1_pred; + WORD8 i1_size_pos_info,i1_buf_id,i1_ref_idx; + UWORD8 u1_part_wd,u1_part_ht; + WORD16 i2_mv_x,i2_mv_y; + struct pic_buffer_t *ps_ref_frm; + UWORD32 *pu4_wt_offset; + UWORD8 *pu1_buf1,*pu1_buf2,*pu1_buf3; + + + PROFILE_DISABLE_MB_PART_INFO() + + ps_pred = ps_dec->ps_pred + ps_dec->u4_pred_info_idx; + + + i1_size_pos_info = ps_pred_pkd->i1_size_pos_info; + GET_XPOS_PRED(u1_sub_x,i1_size_pos_info); + GET_YPOS_PRED(u1_sub_y,i1_size_pos_info); + GET_WIDTH_PRED(u1_part_wd,i1_size_pos_info); + GET_HEIGHT_PRED(u1_part_ht,i1_size_pos_info); + i2_mv_x = ps_pred_pkd->i2_mv[0]; + i2_mv_y = ps_pred_pkd->i2_mv[1]; + i1_ref_idx = ps_pred_pkd->i1_ref_idx_info & 0x3f; + i1_buf_id = ps_pred_pkd->i1_buf_id; + ps_ref_frm = ps_dec->apv_buf_id_pic_buf_map[i1_buf_id]; + + i1_pred = (ps_pred_pkd->i1_ref_idx_info & 0xC0) >> 6; + u1_is_bi_dir = (i1_pred == BI_PRED); + + + u1_ref_pic_type = ps_pred_pkd->u1_pic_type & PIC_MASK; + + pu1_buf1 = ps_ref_frm->pu1_buf1; + pu1_buf2 = ps_ref_frm->pu1_buf2; + pu1_buf3 = ps_ref_frm->pu1_buf3; + + if(u1_ref_pic_type == BOT_FLD) + { + pu1_buf1 += ps_ref_frm->u2_frm_wd_y; + pu1_buf2 += ps_ref_frm->u2_frm_wd_uv; + pu1_buf3 += ps_ref_frm->u2_frm_wd_uv; + + } + + + + if(ps_dec->ps_cur_pps->u1_wted_pred_flag) + { + pu4_wt_offset = (UWORD32*)&ps_dec->pu4_wt_ofsts[2 + * X3(i1_ref_idx)]; + } + + + pu4_wt_offset = ps_pred_pkd->pu4_wt_offst; + + + /* Pointer to the frame buffer */ + { + ps_frame_buf = &ps_dec->s_tran_addrecon; + /* CHANGED CODE */ + } + + if(!u1_pic_fld) + { + u1_mb_fld = ps_cur_mb_info->u1_mb_field_decodingflag; + u1_mb_bot = 1 - ps_cur_mb_info->u1_topmb; + } + else + u1_pic_bot = ps_cur_slice->u1_bottom_field_flag; + + /****************************************************************/ + /* calculating the flags the tell whether to use frame-padding */ + /* or use software pad-on-demand */ + /****************************************************************/ + u1_mb_or_pic_bot = u1_mb_bot | u1_pic_bot; + u1_mb_or_pic_fld = u1_mb_fld | u1_pic_fld; + u1_pod_bot = u1_mb_or_pic_fld && (u1_ref_pic_type == TOP_FLD); + u1_pod_top = u1_mb_or_pic_fld && (u1_ref_pic_type == BOT_FLD); + + /* Weighted Pred additions */ + u1_wted_bipred_idc = ps_dec->ps_cur_pps->u1_wted_bipred_idc; + + if((u1_slice_type == P_SLICE) || (u1_slice_type == SP_SLICE)) + { + /* P Slice only */ + u1_wght_pred_type = ps_dec->ps_cur_pps->u1_wted_pred_flag; + + } + else + { + /* B Slice only */ + u1_wght_pred_type = 1 + u1_is_bi_dir; + if(u1_wted_bipred_idc == 0) + u1_wght_pred_type = 0; + if((u1_wted_bipred_idc == 2) && (!u1_is_bi_dir)) + u1_wght_pred_type = 0; + } + /* load the scratch reference buffer index */ + pu1_ref_buf = ps_dec->pu1_ref_buff + ps_dec->u4_dma_buf_idx; + u2_tot_ref_scratch_size = 0; + + + /* Transfer Setup Y */ + { + UWORD8 *pu1_pred, *pu1_rec; + /* calculating rounded motion vectors and fractional components */ + i2_tmp_mv_x = i2_mv_x; + i2_tmp_mv_y = i2_mv_y; + + u1_dx = i2_tmp_mv_x & 0x3; + u1_dy = i2_tmp_mv_y & 0x3; + i2_tmp_mv_x >>= 2; + i2_tmp_mv_y >>= 2; + i1_mc_wd = u1_part_wd << 2; + u1_dma_ht = u1_part_ht << 2; + if(u1_dx) + { + i2_tmp_mv_x -= 2; + i1_mc_wd += 5; + } + if(u1_dy) + { + i2_tmp_mv_y -= 2; + u1_dma_ht += 5; + } + + /********************************************************************/ + /* Calulating the horizontal and the vertical u4_ofst from top left */ + /* edge of the reference frame, and subsequent clipping */ + /********************************************************************/ + u2_pic_ht = ps_dec->u2_pic_ht >> u1_pic_fld; + u2_frm_wd = ps_dec->u2_frm_wd_y << u1_pic_fld; + i2_frm_x = (u2_mb_x << 4) + (u1_sub_x << 2) + i2_tmp_mv_x; + i2_frm_y = ((u2_mb_y + (u1_mb_bot && !u1_mb_fld)) << 4) + + (((u1_sub_y << 2) + i2_tmp_mv_y) << u1_mb_fld); + + i2_frm_x = CLIP3(MAX_OFFSET_OUTSIDE_X_FRM, (ps_dec->u2_pic_wd - 1), + i2_frm_x); + i2_frm_y = CLIP3(((1 - u1_dma_ht) << u1_mb_fld), + (u2_pic_ht - (1 << u1_mb_fld)), i2_frm_y); + + pu1_pred = pu1_buf1 + i2_frm_y * u2_frm_wd + i2_frm_x; + u1_dma_wd = (i1_mc_wd + 3) & 0xFC; + /********************************************************************/ + /* Calulating the horizontal and the vertical u4_ofst from top left */ + /* edge of the recon buffer */ + /********************************************************************/ + /* CHANGED CODE */ + u2_rec_wd = MB_SIZE; + i2_rec_x = u1_sub_x << 2; + i2_rec_y = u1_sub_y << 2; + { + u2_rec_wd = ps_dec->u2_frm_wd_y << u1_mb_or_pic_fld; + i2_rec_x += (mb_index << 4); + pu1_rec = ps_frame_buf->pu1_dest_y + i2_rec_y * u2_rec_wd + + i2_rec_x; + if(u1_mb_bot) + pu1_rec += ps_dec->u2_frm_wd_y << ((u1_mb_fld) ? 0 : 4); + } + + /* CHANGED CODE */ + + /* filling the pred and dma structures for Y */ + u2_frm_wd = ps_dec->u2_frm_wd_y << u1_mb_or_pic_fld; + + ps_pred->pu1_dma_dest_addr = pu1_ref_buf; + ps_pred->u2_u1_ref_buf_wd = u1_dma_wd; + ps_pred->u2_frm_wd = u2_frm_wd; + ps_pred->i1_dma_ht = u1_dma_ht; + ps_pred->i1_mc_wd = i1_mc_wd; + ps_pred->pu1_rec_y_u = pu1_rec; + ps_pred->u2_dst_stride = u2_rec_wd; + + ps_pred->i1_mb_partwidth = u1_part_wd << 2; + ps_pred->i1_mb_partheight = u1_part_ht << 2; + ps_pred->u1_dydx = (u1_dy << 2) + u1_dx; + ps_pred->u1_is_bi_direct = u1_is_bi_dir; + ps_pred->u1_pi1_wt_ofst_rec_v = (UWORD8 *)pu4_wt_offset; + ps_pred->u1_wght_pred_type = u1_wght_pred_type; + ps_pred->i1_pod_ht = 0; + + /* Increment the Reference buffer Indices */ + pu1_ref_buf += u1_dma_wd * u1_dma_ht; + u2_tot_ref_scratch_size += u1_dma_wd * u1_dma_ht; + + /* unrestricted field motion comp for top region outside frame */ + i2_pod_ht = (-i2_frm_y) >> u1_mb_fld; + if((i2_pod_ht > 0) && u1_pod_top) + { + ps_pred->i1_pod_ht = (WORD8)(-i2_pod_ht); + u1_dma_ht -= i2_pod_ht; + pu1_pred += i2_pod_ht * u2_frm_wd; + } + /* unrestricted field motion comp for bottom region outside frame */ + else if(u1_pod_bot) + { + i2_pod_ht = u1_dma_ht + ((i2_frm_y - u2_pic_ht) >> u1_mb_fld); + if(i2_pod_ht > 0) + { + u1_dma_ht -= i2_pod_ht; + ps_pred->i1_pod_ht = (WORD8)i2_pod_ht; + } + } + + /* Copy Y partition */ + + /* + * ps_pred->i1_pod_ht is non zero when MBAFF is present. In case of MBAFF the reference data + * is copied in the Scrath buffer so that the padding_on_demand doesnot corrupt the frame data + */ + if(ps_pred->i1_pod_ht) + { + ps_pred->pu1_pred = pu1_pred; + ps_pred->u1_dma_ht_y = u1_dma_ht; + ps_pred->u1_dma_wd_y = u1_dma_wd; + } + ps_pred->pu1_y_ref = pu1_pred; + } + + + + /* Increment ps_pred index */ + ps_pred++; + + /* Transfer Setup U & V */ + { + WORD32 i4_ref_offset, i4_rec_offset; + UWORD8 *pu1_pred_u, *pu1_pred_v, u1_tmp_dma_ht; + /* CHANGED CODE */ + UWORD8 u1_chroma_cbp = (UWORD8)(ps_cur_mb_info->u1_cbp >> 4); + /* CHANGED CODE */ + + /* calculating rounded motion vectors and fractional components */ + i2_tmp_mv_x = i2_mv_x; + i2_tmp_mv_y = i2_mv_y; + + /************************************************************************/ + /* Table 8-9: Derivation of the vertical component of the chroma vector */ + /* in field coding mode */ + /************************************************************************/ + if(u1_pod_bot && u1_mb_or_pic_bot) + i2_tmp_mv_y += 2; + if(u1_pod_top && !u1_mb_or_pic_bot) + i2_tmp_mv_y -= 2; + + /* Eighth sample of the chroma MV */ + u1_dx = i2_tmp_mv_x & 0x7; + u1_dy = i2_tmp_mv_y & 0x7; + + /********************************************************************/ + /* Calculating the full pel MV for chroma which is 1/2 of the Luma */ + /* MV in full pel units */ + /********************************************************************/ + i2_mv_x = i2_tmp_mv_x; + i2_mv_y = i2_tmp_mv_y; + i2_tmp_mv_x = SIGN_POW2_DIV(i2_tmp_mv_x, 3); + i2_tmp_mv_y = SIGN_POW2_DIV(i2_tmp_mv_y, 3); + i1_mc_wd = u1_part_wd << 1; + u1_dma_ht = u1_part_ht << 1; + if(u1_dx) + { + if(i2_mv_x < 0) + i2_tmp_mv_x -= 1; + i1_mc_wd++; + } + if(u1_dy != 0) + { + if(i2_mv_y < 0) + i2_tmp_mv_y -= 1; + u1_dma_ht++; + } + + /********************************************************************/ + /* Calulating the horizontal and the vertical u4_ofst from top left */ + /* edge of the reference frame, and subsequent clipping */ + /********************************************************************/ + u2_pic_ht >>= 1; + u2_frm_wd = ps_dec->u2_frm_wd_uv << u1_pic_fld; + i2_frm_x = (u2_mb_x << 3) + (u1_sub_x << 1) + i2_tmp_mv_x; + i2_frm_y = ((u2_mb_y + (u1_mb_bot && !u1_mb_fld)) << 3) + + (((u1_sub_y << 1) + i2_tmp_mv_y) << u1_mb_fld); + + i2_frm_x = CLIP3(MAX_OFFSET_OUTSIDE_UV_FRM, + ((ps_dec->u2_pic_wd >> 1) - 1), i2_frm_x); + i2_frm_y = CLIP3(((1 - u1_dma_ht) << u1_mb_fld), + (u2_pic_ht - (1 << u1_mb_fld)), i2_frm_y); + + i4_ref_offset = i2_frm_y * u2_frm_wd + i2_frm_x * YUV420SP_FACTOR; + u1_dma_wd = (i1_mc_wd + 3) & 0xFC; + + /********************************************************************/ + /* Calulating the horizontal and the vertical u4_ofst from top left */ + /* edge of the recon buffer */ + /********************************************************************/ + /* CHANGED CODE */ + u2_rec_wd = BLK8x8SIZE * YUV420SP_FACTOR; + i2_rec_x = u1_sub_x << 1; + i2_rec_y = u1_sub_y << 1; + i4_rec_offset = i2_rec_y * u2_rec_wd + i2_rec_x * YUV420SP_FACTOR; + { + u2_rec_wd = ps_dec->u2_frm_wd_uv << u1_mb_or_pic_fld; + + i2_rec_x += (mb_index << 3); + i4_rec_offset = i2_rec_y * u2_rec_wd + i2_rec_x * YUV420SP_FACTOR; + if(u1_mb_bot) + i4_rec_offset += ps_dec->u2_frm_wd_uv << ((u1_mb_fld) ? 0 : 3); + ps_pred->pu1_rec_y_u = ps_frame_buf->pu1_dest_u + i4_rec_offset; + ps_pred->u1_pi1_wt_ofst_rec_v = ps_frame_buf->pu1_dest_v + + i4_rec_offset; + + } + + /* CHANGED CODE */ + + /* filling the common pred structures for U */ + u2_frm_wd = ps_dec->u2_frm_wd_uv << u1_mb_or_pic_fld; + u1_tmp_dma_ht = u1_dma_ht; + ps_pred->u2_u1_ref_buf_wd = u1_dma_wd; + ps_pred->u2_frm_wd = u2_frm_wd; + ps_pred->i1_dma_ht = u1_dma_ht; + ps_pred->i1_mc_wd = i1_mc_wd; + ps_pred->u2_dst_stride = u2_rec_wd; + + ps_pred->i1_mb_partwidth = u1_part_wd << 1; + ps_pred->i1_mb_partheight = u1_part_ht << 1; + ps_pred->u1_dydx = (u1_dy << 3) + u1_dx; + ps_pred->u1_is_bi_direct = u1_is_bi_dir; + ps_pred->u1_wght_pred_type = u1_wght_pred_type; + ps_pred->i1_pod_ht = 0; + + ps_pred->pu1_dma_dest_addr = pu1_ref_buf; + + /* unrestricted field motion comp for top region outside frame */ + i2_pod_ht = (-i2_frm_y) >> u1_mb_fld; + if((i2_pod_ht > 0) && u1_pod_top) + { + i4_ref_offset += i2_pod_ht * u2_frm_wd; + u1_dma_ht -= i2_pod_ht; + ps_pred->i1_pod_ht = (WORD8)(-i2_pod_ht); + } + /* unrestricted field motion comp for bottom region outside frame */ + else if(u1_pod_bot) + { + i2_pod_ht = u1_dma_ht + ((i2_frm_y - u2_pic_ht) >> u1_mb_fld); + if(i2_pod_ht > 0) + { + u1_dma_ht -= i2_pod_ht; + ps_pred->i1_pod_ht = (WORD8)i2_pod_ht; + } + } + + pu1_pred_u = pu1_buf2 + i4_ref_offset; + pu1_pred_v = pu1_buf3 + i4_ref_offset; + + /* Copy U & V partitions */ + if(ps_pred->i1_pod_ht) + { + ps_pred->pu1_pred_u = pu1_pred_u; + ps_pred->u1_dma_ht_uv = u1_dma_ht; + ps_pred->u1_dma_wd_uv = u1_dma_wd; + + } + ps_pred->pu1_u_ref = pu1_pred_u; + + /* Increment the reference buffer Index */ + u2_tot_ref_scratch_size += (u1_dma_wd * u1_tmp_dma_ht) << 1; + + if(ps_pred->i1_pod_ht) + { + ps_pred->pu1_pred_v = pu1_pred_v; + ps_pred->u1_dma_ht_uv = u1_dma_ht; + ps_pred->u1_dma_wd_uv = u1_dma_wd; + } + + ps_pred->pu1_v_ref = pu1_pred_v; + } + + /* Increment ps_pred index */ + ps_dec->u4_pred_info_idx += 2; + + + /* Increment the reference buffer Index */ + ps_dec->u4_dma_buf_idx += u2_tot_ref_scratch_size; + + if(ps_dec->u4_dma_buf_idx > MAX_REF_BUF_SIZE) + return ERROR_NUM_MV; + + return OK; + + + +} + + +/*! + ************************************************************************** + * \if Function name : MotionCompensate \endif + * + * \brief + * The routine forms predictor blocks for the entire MB and stores it in + * predictor buffers.This function works only for BASELINE profile + * + * \param ps_dec: Pointer to the structure decStruct. This is used to get + * pointers to the current and the reference frame and to the MbParams + * structure. + * + * \return + * None + * + * \note + * The routine forms predictors for all the luma and the chroma MB + * partitions. + ************************************************************************** + */ + +void ih264d_motion_compensate_bp(dec_struct_t * ps_dec, dec_mb_info_t *ps_cur_mb_info) +{ + pred_info_t *ps_pred ; + UWORD8 *puc_ref, *pu1_dest_y; + UWORD8 *pu1_dest_u; + UWORD32 u2_num_pels, u2_ref_wd_y, u2_ref_wd_uv, u2_dst_wd; + + UWORD32 u4_wd_y, u4_ht_y, u4_wd_uv; + UWORD32 u4_ht_uv; + UWORD8 *puc_pred0 = (UWORD8 *)(ps_dec->pi2_pred1); + + + PROFILE_DISABLE_INTER_PRED() + UNUSED(ps_cur_mb_info); + ps_pred = ps_dec->ps_pred ; + + for(u2_num_pels = 0; u2_num_pels < 256;) + { + UWORD32 uc_dx, uc_dy; + /* Pointer to the destination buffer. If the CBPs of all 8x8 blocks in + the MB partition are zero then it would be better to copy the + predictor valus directly to the current frame buffer */ + /* + * ps_pred->i1_pod_ht is non zero when MBAFF is present. In case of MBAFF the reference data + * is copied in the Scrath buffer so that the padding_on_demand doesnot corrupt the frame data + */ + + u2_ref_wd_y = ps_pred->u2_frm_wd; + puc_ref = ps_pred->pu1_y_ref; + if(ps_pred->u1_dydx & 0x3) + puc_ref += 2; + if(ps_pred->u1_dydx >> 2) + puc_ref += 2 * u2_ref_wd_y; + + u4_wd_y = ps_pred->i1_mb_partwidth; + u4_ht_y = ps_pred->i1_mb_partheight; + uc_dx = ps_pred->u1_dydx; + uc_dy = uc_dx >> 2; + uc_dx &= 0x3; + + pu1_dest_y = ps_pred->pu1_rec_y_u; + u2_dst_wd = ps_pred->u2_dst_stride; + + ps_dec->apf_inter_pred_luma[ps_pred->u1_dydx](puc_ref, pu1_dest_y, + u2_ref_wd_y, + u2_dst_wd, + u4_ht_y, + u4_wd_y, puc_pred0, + ps_pred->u1_dydx); + + ps_pred++; + + /* Interpolate samples for the chroma components */ + { + UWORD8 *pu1_ref_u; + + u2_ref_wd_uv = ps_pred->u2_frm_wd; + pu1_ref_u = ps_pred->pu1_u_ref; + + u4_wd_uv = ps_pred->i1_mb_partwidth; + u4_ht_uv = ps_pred->i1_mb_partheight; + uc_dx = ps_pred->u1_dydx; /* 8*dy + dx */ + uc_dy = uc_dx >> 3; + uc_dx &= 0x7; + + pu1_dest_u = ps_pred->pu1_rec_y_u; + u2_dst_wd = ps_pred->u2_dst_stride; + + ps_pred++; + ps_dec->pf_inter_pred_chroma(pu1_ref_u, pu1_dest_u, u2_ref_wd_uv, + u2_dst_wd, uc_dx, uc_dy, + u4_ht_uv, u4_wd_uv); + + } + + u2_num_pels += (UWORD8)u4_wd_y * (UWORD8)u4_ht_y; + + } +} + + +/* + ************************************************************************** + * \if Function name : MotionCompensateB \endif + * + * \brief + * The routine forms predictor blocks for the entire MB and stores it in + * predictor buffers. + * + * \param ps_dec: Pointer to the structure decStruct. This is used to get + * pointers to the current and the reference frame and to the MbParams + * structure. + * + * \return + * None + * + * \note + * The routine forms predictors for all the luma and the chroma MB + * partitions. + ************************************************************************** + */ + +void ih264d_motion_compensate_mp(dec_struct_t * ps_dec, dec_mb_info_t *ps_cur_mb_info) +{ + pred_info_t *ps_pred ; + pred_info_t *ps_pred_y_forw, *ps_pred_y_back, *ps_pred_cr_forw; + UWORD8 *puc_ref, *pu1_dest_y, *puc_pred0, *puc_pred1; + UWORD8 *pu1_dest_u, *pu1_dest_v; + WORD16 *pi16_intm; + UWORD32 u2_num_pels, u2_ref_wd_y, u2_ref_wd_uv, u2_dst_wd; + UWORD32 u2_dest_wd_y, u2_dest_wd_uv; + UWORD32 u2_row_buf_wd_y = 0; + UWORD32 u2_row_buf_wd_uv = 0; + UWORD32 u2_log2Y_crwd; + UWORD32 u4_wd_y, u4_ht_y, u1_dir, u4_wd_uv; + UWORD32 u4_ht_uv; + UWORD8 *pu1_temp_mc_buffer = ps_dec->pu1_temp_mc_buffer; + WORD32 i2_pod_ht; + UWORD32 u2_pic_ht, u2_frm_wd, u2_rec_wd; + UWORD32 u1_pod_bot, u1_pod_top; + UWORD8 *pu1_pred, *pu1_dma_dst; + UWORD32 u1_dma_wd, u1_dma_ht; + + dec_slice_params_t * const ps_cur_slice = ps_dec->ps_cur_slice; + + /* set default value to flags specifying field nature of picture & mb */ + UWORD32 u1_mb_fld = 0, u1_mb_or_pic_fld; + UWORD32 u1_mb_or_pic_bot; + /* calculate flags specifying field nature of picture & mb */ + const UWORD8 u1_pic_fld = ps_cur_slice->u1_field_pic_flag; + + PROFILE_DISABLE_INTER_PRED() + ps_pred = ps_dec->ps_pred ; + /* Initialize both ps_pred_y_forw, ps_pred_cr_forw and ps_pred_y_back + * to avoid static analysis warnings */ + ps_pred_y_forw = ps_pred; + ps_pred_y_back = ps_pred; + ps_pred_cr_forw = ps_pred; + + u2_log2Y_crwd = ps_dec->ps_decode_cur_slice->u2_log2Y_crwd; + + if(!u1_pic_fld) + { + u1_mb_fld = ps_cur_mb_info->u1_mb_field_decodingflag; + } + + u1_mb_or_pic_fld = u1_mb_fld | u1_pic_fld; + + pi16_intm = ps_dec->pi2_pred1; + puc_pred0 = (UWORD8 *)pi16_intm; + puc_pred1 = puc_pred0 + PRED_BUFFER_WIDTH * PRED_BUFFER_HEIGHT * sizeof(WORD16); + + for(u2_num_pels = 0; u2_num_pels < 256;) + { + UWORD8 uc_dx, uc_dy; + const UWORD8 u1_is_bi_direct = ps_pred->u1_is_bi_direct; + for(u1_dir = 0; u1_dir <= u1_is_bi_direct; u1_dir++) + { + /* Pointer to the destination buffer. If the CBPs of all 8x8 blocks in + the MB partition are zero then it would be better to copy the + predictor valus directly to the current frame buffer */ + /* + * ps_pred->i1_pod_ht is non zero when MBAFF is present. In case of MBAFF the reference data + * is copied in the Scrath buffer so that the padding_on_demand doesnot corrupt the frame data + */ + + if(ps_pred->i1_pod_ht) + { + u2_ref_wd_y = ps_pred->u2_u1_ref_buf_wd; + puc_ref = ps_pred->pu1_dma_dest_addr; + } + else + { + u2_ref_wd_y = ps_pred->u2_frm_wd; + puc_ref = ps_pred->pu1_y_ref; + + } + + if(ps_pred->u1_dydx & 0x3) + puc_ref += 2; + if(ps_pred->u1_dydx >> 2) + puc_ref += 2 * u2_ref_wd_y; + u4_wd_y = ps_pred->i1_mb_partwidth; + u4_ht_y = ps_pred->i1_mb_partheight; + + uc_dx = ps_pred->u1_dydx; + uc_dy = uc_dx >> 2; + uc_dx &= 0x3; + if(u1_dir == 0) + { + pu1_dest_y = ps_pred->pu1_rec_y_u; + u2_row_buf_wd_y = ps_pred->u2_dst_stride; + u2_dst_wd = ps_pred->u2_dst_stride; + u2_dest_wd_y = u2_dst_wd; + ps_pred_y_forw = ps_pred; + } + else + { + pu1_dest_y = pu1_temp_mc_buffer; + u2_dst_wd = MB_SIZE; + u2_dest_wd_y = u2_dst_wd; + ps_pred_y_back = ps_pred; + ps_pred_y_back->pu1_rec_y_u = pu1_dest_y; + } + + /* padding on demand (POD) for y done here */ + + if(ps_pred->i1_pod_ht) + { + pu1_pred = ps_pred->pu1_pred; + pu1_dma_dst = ps_pred->pu1_dma_dest_addr; + u1_dma_wd = ps_pred->u1_dma_wd_y; + u1_dma_ht = ps_pred->u1_dma_ht_y; + u2_frm_wd = ps_dec->u2_frm_wd_y << u1_mb_or_pic_fld; + if(ps_pred->i1_pod_ht < 0) + { + pu1_dma_dst = pu1_dma_dst - (ps_pred->i1_pod_ht * ps_pred->u2_u1_ref_buf_wd); + } + ih264d_copy_2d1d(pu1_pred, pu1_dma_dst, u2_frm_wd, u1_dma_wd, + u1_dma_ht); + ih264d_pad_on_demand(ps_pred, LUM_BLK); + } + ps_dec->apf_inter_pred_luma[ps_pred->u1_dydx](puc_ref, pu1_dest_y, + u2_ref_wd_y, + u2_dst_wd, + u4_ht_y, + u4_wd_y, + puc_pred0, + ps_pred->u1_dydx); + ps_pred++; + + /* Interpolate samples for the chroma components */ + { + UWORD8 *pu1_ref_u; + UWORD32 u1_dma_ht; + + /* padding on demand (POD) for U and V done here */ + u1_dma_ht = ps_pred->i1_dma_ht; + + if(ps_pred->i1_pod_ht) + { + pu1_pred = ps_pred->pu1_pred_u; + pu1_dma_dst = ps_pred->pu1_dma_dest_addr; + u1_dma_ht = ps_pred->u1_dma_ht_uv; + u1_dma_wd = ps_pred->u1_dma_wd_uv * YUV420SP_FACTOR; + u2_frm_wd = ps_dec->u2_frm_wd_uv << u1_mb_or_pic_fld; + if(ps_pred->i1_pod_ht < 0) + { + /*Top POD*/ + pu1_dma_dst -= (ps_pred->i1_pod_ht + * ps_pred->u2_u1_ref_buf_wd + * YUV420SP_FACTOR); + } + + ih264d_copy_2d1d(pu1_pred, pu1_dma_dst, u2_frm_wd, + u1_dma_wd, u1_dma_ht); + + pu1_dma_dst += (ps_pred->i1_dma_ht + * ps_pred->u2_u1_ref_buf_wd); + pu1_pred = ps_pred->pu1_pred_v; + + ih264d_pad_on_demand(ps_pred, CHROM_BLK); + } + + if(ps_pred->i1_pod_ht) + { + pu1_ref_u = ps_pred->pu1_dma_dest_addr; + + u2_ref_wd_uv = ps_pred->u2_u1_ref_buf_wd + * YUV420SP_FACTOR; + } + else + { + u2_ref_wd_uv = ps_pred->u2_frm_wd; + pu1_ref_u = ps_pred->pu1_u_ref; + + } + + u4_wd_uv = ps_pred->i1_mb_partwidth; + u4_ht_uv = ps_pred->i1_mb_partheight; + uc_dx = ps_pred->u1_dydx; /* 8*dy + dx */ + uc_dy = uc_dx >> 3; + uc_dx &= 0x7; + if(u1_dir == 0) + { + pu1_dest_u = ps_pred->pu1_rec_y_u; + + pu1_dest_v = ps_pred->u1_pi1_wt_ofst_rec_v; + u2_row_buf_wd_uv = ps_pred->u2_dst_stride; + u2_dst_wd = ps_pred->u2_dst_stride; + u2_dest_wd_uv = u2_dst_wd; + ps_pred_cr_forw = ps_pred; + } + else + { + pu1_dest_u = puc_pred0; + + pu1_dest_v = puc_pred1; + u2_dest_wd_uv = BUFFER_WIDTH; + u2_dst_wd = BUFFER_WIDTH; + ps_pred->pu1_rec_y_u = pu1_dest_u; + ps_pred->u1_pi1_wt_ofst_rec_v = pu1_dest_v; + } + + ps_pred++; + ps_dec->pf_inter_pred_chroma(pu1_ref_u, pu1_dest_u, + u2_ref_wd_uv, u2_dst_wd, + uc_dx, uc_dy, u4_ht_uv, + u4_wd_uv); + + if(ps_cur_mb_info->u1_Mux == 1) + { + /******************************************************************/ + /* padding on demand (POD) for U and V done here */ + /* ps_pred now points to the Y entry of the 0,0 component */ + /* Y need not be checked for POD because Y lies within */ + /* the picture((0,0) mv for Y doesnot get changed. But (0,0) for */ + /* U and V can need POD beacause of cross-field mv adjustments */ + /* (Table 8-9 of standard) */ + /******************************************************************/ + if((ps_pred + 1)->i1_pod_ht) + { + pu1_pred = (ps_pred + 1)->pu1_pred_u; + pu1_dma_dst = (ps_pred + 1)->pu1_dma_dest_addr; + u1_dma_ht = (ps_pred + 1)->u1_dma_ht_uv; + u1_dma_wd = (ps_pred + 1)->u1_dma_wd_uv + * YUV420SP_FACTOR; + u2_frm_wd = ps_dec->u2_frm_wd_uv << u1_mb_or_pic_fld; + if((ps_pred + 1)->i1_pod_ht < 0) + { + /*Top POD*/ + pu1_dma_dst -= ((ps_pred + 1)->i1_pod_ht + * (ps_pred + 1)->u2_u1_ref_buf_wd + * YUV420SP_FACTOR); + } + ih264d_copy_2d1d(pu1_pred, pu1_dma_dst, u2_frm_wd, + u1_dma_wd, u1_dma_ht); + pu1_dma_dst += ((ps_pred + 1)->i1_dma_ht + * (ps_pred + 1)->u2_u1_ref_buf_wd); //(u1_dma_ht * u1_dma_wd);// + pu1_pred = (ps_pred + 1)->pu1_pred_v; + ih264d_pad_on_demand(ps_pred + 1, CHROM_BLK); + + } + + ih264d_multiplex_ref_data(ps_dec, ps_pred, pu1_dest_y, + pu1_dest_u, ps_cur_mb_info, + u2_dest_wd_y, u2_dest_wd_uv, + u1_dir); + ps_pred += 2; + } + } + } + if(u1_dir != 0) + u2_ref_wd_y = MB_SIZE; + + u2_num_pels += u4_wd_y * u4_ht_y; + /* if BI_DIRECT, average the two pred's, and put in ..PredBuffer[0] */ + if((u1_is_bi_direct != 0) || (ps_pred_y_forw->u1_wght_pred_type != 0)) + { + + switch(ps_pred_y_forw->u1_wght_pred_type) + { + case 0: + ps_dec->pf_default_weighted_pred_luma( + ps_pred_y_forw->pu1_rec_y_u, pu1_dest_y, + ps_pred_y_forw->pu1_rec_y_u, + u2_row_buf_wd_y, u2_ref_wd_y, + u2_row_buf_wd_y, u4_ht_uv * 2, + u4_wd_uv * 2); + + ps_dec->pf_default_weighted_pred_chroma( + ps_pred_cr_forw->pu1_rec_y_u, pu1_dest_u, + ps_pred_cr_forw->pu1_rec_y_u, + u2_row_buf_wd_uv, u2_dst_wd, + u2_row_buf_wd_uv, u4_ht_uv, + u4_wd_uv); + + break; + case 1: + { + UWORD32 *pu4_weight_ofst = + (UWORD32*)ps_pred_y_forw->u1_pi1_wt_ofst_rec_v; + UWORD32 u4_wt_ofst_u, u4_wt_ofst_v; + UWORD32 u4_wt_ofst_y = + (UWORD32)(pu4_weight_ofst[0]); + WORD32 weight = (WORD16)(u4_wt_ofst_y & 0xffff); + WORD32 ofst = (WORD8)(u4_wt_ofst_y >> 16); + + ps_dec->pf_weighted_pred_luma(ps_pred_y_forw->pu1_rec_y_u, + ps_pred_y_forw->pu1_rec_y_u, + u2_row_buf_wd_y, + u2_row_buf_wd_y, + (u2_log2Y_crwd & 0x0ff), + weight, ofst, u4_ht_y, + u4_wd_y); + + u4_wt_ofst_u = (UWORD32)(pu4_weight_ofst[2]); + u4_wt_ofst_v = (UWORD32)(pu4_weight_ofst[4]); + weight = ((u4_wt_ofst_v & 0xffff) << 16) + | (u4_wt_ofst_u & 0xffff); + ofst = ((u4_wt_ofst_v >> 16) << 8) + | ((u4_wt_ofst_u >> 16) & 0xFF); + + ps_dec->pf_weighted_pred_chroma( + ps_pred_cr_forw->pu1_rec_y_u, + ps_pred_cr_forw->pu1_rec_y_u, + u2_row_buf_wd_uv, u2_row_buf_wd_uv, + (u2_log2Y_crwd >> 8), weight, ofst, + u4_ht_y >> 1, u4_wd_y >> 1); + } + + break; + case 2: + { + UWORD32 *pu4_weight_ofst = + (UWORD32*)ps_pred_y_forw->u1_pi1_wt_ofst_rec_v; + UWORD32 u4_wt_ofst_u, u4_wt_ofst_v; + UWORD32 u4_wt_ofst_y; + WORD32 weight1, weight2; + WORD32 ofst1, ofst2; + + u4_wt_ofst_y = (UWORD32)(pu4_weight_ofst[0]); + + weight1 = (WORD16)(u4_wt_ofst_y & 0xffff); + ofst1 = (WORD8)(u4_wt_ofst_y >> 16); + + u4_wt_ofst_y = (UWORD32)(pu4_weight_ofst[1]); + weight2 = (WORD16)(u4_wt_ofst_y & 0xffff); + ofst2 = (WORD8)(u4_wt_ofst_y >> 16); + + ps_dec->pf_weighted_bi_pred_luma(ps_pred_y_forw->pu1_rec_y_u, + ps_pred_y_back->pu1_rec_y_u, + ps_pred_y_forw->pu1_rec_y_u, + u2_row_buf_wd_y, + u2_ref_wd_y, + u2_row_buf_wd_y, + (u2_log2Y_crwd & 0x0ff), + weight1, weight2, ofst1, + ofst2, u4_ht_y, + u4_wd_y); + + u4_wt_ofst_u = (UWORD32)(pu4_weight_ofst[2]); + u4_wt_ofst_v = (UWORD32)(pu4_weight_ofst[4]); + weight1 = ((u4_wt_ofst_v & 0xffff) << 16) + | (u4_wt_ofst_u & 0xffff); + ofst1 = ((u4_wt_ofst_v >> 16) << 8) + | ((u4_wt_ofst_u >> 16) & 0xFF); + + u4_wt_ofst_u = (UWORD32)(pu4_weight_ofst[3]); + u4_wt_ofst_v = (UWORD32)(pu4_weight_ofst[5]); + weight2 = ((u4_wt_ofst_v & 0xffff) << 16) + | (u4_wt_ofst_u & 0xffff); + ofst2 = ((u4_wt_ofst_v >> 16) << 8) + | ((u4_wt_ofst_u >> 16) & 0xFF); + + ps_dec->pf_weighted_bi_pred_chroma( + (ps_pred_y_forw + 1)->pu1_rec_y_u, + (ps_pred_y_back + 1)->pu1_rec_y_u, + (ps_pred_y_forw + 1)->pu1_rec_y_u, + u2_row_buf_wd_uv, u2_dst_wd, + u2_row_buf_wd_uv, (u2_log2Y_crwd >> 8), + weight1, weight2, ofst1, ofst2, + u4_ht_y >> 1, u4_wd_y >> 1); + } + + break; + } + + } + } +} + + +/*! + ************************************************************************** + * \if Function name : ih264d_multiplex_ref_data \endif + * + * \brief + * Initializes forward and backward refernce lists for B slice decoding. + * + * + * \return + * 0 on Success and Error code otherwise + ************************************************************************** + */ + +void ih264d_multiplex_ref_data(dec_struct_t * ps_dec, + pred_info_t *ps_pred, + UWORD8* pu1_dest_y, + UWORD8* pu1_dest_u, + dec_mb_info_t *ps_cur_mb_info, + UWORD16 u2_dest_wd_y, + UWORD16 u2_dest_wd_uv, + UWORD8 u1_dir) +{ + UWORD16 u2_mask = ps_cur_mb_info->u2_mask[u1_dir]; + UWORD8 *pu1_ref_y, *pu1_ref_u; + UWORD8 uc_cond, i, j, u1_dydx; + UWORD16 u2_ref_wd_y, u2_ref_wd_uv; + + PROFILE_DISABLE_INTER_PRED() + + if(ps_pred->i1_pod_ht) + { + pu1_ref_y = ps_pred->pu1_dma_dest_addr; + + u2_ref_wd_y = ps_pred->u2_u1_ref_buf_wd; + } + else + { + pu1_ref_y = ps_pred->pu1_y_ref; + u2_ref_wd_y = ps_pred->u2_frm_wd; + } + + ps_pred++; + if(ps_pred->i1_pod_ht) + { + pu1_ref_u = ps_pred->pu1_dma_dest_addr; + u2_ref_wd_uv = ps_pred->u2_u1_ref_buf_wd * YUV420SP_FACTOR; + + } + else + { + pu1_ref_u = ps_pred->pu1_u_ref; + u2_ref_wd_uv = ps_pred->u2_frm_wd; + + } + + u1_dydx = ps_pred->u1_dydx; + + { + UWORD8 uc_dx, uc_dy; + UWORD8 *pu1_scratch_u; + + uc_dx = u1_dydx & 0x3; + uc_dy = u1_dydx >> 3; + if(u1_dydx != 0) + { + pred_info_t * ps_prv_pred = ps_pred - 2; + pu1_scratch_u = ps_prv_pred->pu1_dma_dest_addr; + ps_dec->pf_inter_pred_chroma(pu1_ref_u, pu1_scratch_u, + u2_ref_wd_uv, 16, uc_dx, uc_dy, 8, + 8); + + /* Modify ref pointer and refWidth to point to scratch */ + /* buffer to be used below in ih264d_copy_multiplex_data functions */ + /* CHANGED CODE */ + pu1_ref_u = pu1_scratch_u; + u2_ref_wd_uv = 8 * YUV420SP_FACTOR; + } + } + { + for(i = 0; i < 4; i++) + { + for(j = 0; j < 4; j++) + { + uc_cond = u2_mask & 1; + u2_mask >>= 1; + if(uc_cond) + { + *(UWORD32 *)(pu1_dest_y + u2_dest_wd_y) = + *(UWORD32 *)(pu1_ref_y + u2_ref_wd_y); + *(UWORD32 *)(pu1_dest_y + 2 * u2_dest_wd_y) = + *(UWORD32 *)(pu1_ref_y + 2 * u2_ref_wd_y); + *(UWORD32 *)(pu1_dest_y + 3 * u2_dest_wd_y) = + *(UWORD32 *)(pu1_ref_y + 3 * u2_ref_wd_y); + { + UWORD32 *dst, *src; + dst = (UWORD32 *)pu1_dest_y; + src = (UWORD32 *)pu1_ref_y; + *dst = *src; + dst++; + src++; + pu1_dest_y = (UWORD8 *)dst; + pu1_ref_y = (UWORD8 *)src; + } + *(UWORD32 *)(pu1_dest_u + u2_dest_wd_uv) = + *(UWORD32 *)(pu1_ref_u + u2_ref_wd_uv); + { + UWORD32 *dst, *src; + dst = (UWORD32 *)pu1_dest_u; + src = (UWORD32 *)pu1_ref_u; + *dst = *src; + dst++; + src++; + pu1_dest_u = (UWORD8 *)dst; + pu1_ref_u = (UWORD8 *)src; + } + + } + else + { + pu1_dest_y += 4; + pu1_ref_y += 4; + pu1_dest_u += 2 * YUV420SP_FACTOR; + pu1_ref_u += 2 * YUV420SP_FACTOR; + } + } + pu1_ref_y += 4 * (u2_ref_wd_y - 4); + pu1_ref_u += 2 * (u2_ref_wd_uv - 4 * YUV420SP_FACTOR); + pu1_dest_y += 4 * (u2_dest_wd_y - 4); + pu1_dest_u += 2 * (u2_dest_wd_uv - 4 * YUV420SP_FACTOR); + } + } +} + +void ih264d_pad_on_demand(pred_info_t *ps_pred, UWORD8 lum_chrom_blk) +{ + if(CHROM_BLK == lum_chrom_blk) + { + UWORD32 *pu4_pod_src_u, *pu4_pod_dst_u; + UWORD32 *pu4_pod_src_v, *pu4_pod_dst_v; + WORD32 j, u1_wd_stride; + WORD32 i, u1_dma_ht, i1_ht; + UWORD32 u2_dma_size; + u1_wd_stride = (ps_pred->u2_u1_ref_buf_wd >> 2) * YUV420SP_FACTOR; + u1_dma_ht = ps_pred->i1_dma_ht; + u2_dma_size = u1_wd_stride * u1_dma_ht; + pu4_pod_src_u = (UWORD32 *)ps_pred->pu1_dma_dest_addr; + pu4_pod_dst_u = pu4_pod_src_u; + + pu4_pod_src_v = pu4_pod_src_u + u2_dma_size; + pu4_pod_dst_v = pu4_pod_src_v; + + i1_ht = ps_pred->i1_pod_ht; + pu4_pod_src_u -= u1_wd_stride * i1_ht; + pu4_pod_src_v -= u1_wd_stride * i1_ht; + if(i1_ht < 0) + /* Top POD */ + i1_ht = -i1_ht; + else + { + /* Bottom POD */ + pu4_pod_src_u += (u1_dma_ht - 1) * u1_wd_stride; + pu4_pod_dst_u += (u1_dma_ht - i1_ht) * u1_wd_stride; + pu4_pod_src_v += (u1_dma_ht - 1) * u1_wd_stride; + pu4_pod_dst_v += (u1_dma_ht - i1_ht) * u1_wd_stride; + } + + for(i = 0; i < i1_ht; i++) + for(j = 0; j < u1_wd_stride; j++) + { + *pu4_pod_dst_u++ = *(pu4_pod_src_u + j); + + } + } + else + { + UWORD32 *pu4_pod_src, *pu4_pod_dst; + WORD32 j, u1_wd_stride; + WORD32 i, i1_ht; + pu4_pod_src = (UWORD32 *)ps_pred->pu1_dma_dest_addr; + pu4_pod_dst = pu4_pod_src; + u1_wd_stride = ps_pred->u2_u1_ref_buf_wd >> 2; + i1_ht = ps_pred->i1_pod_ht; + pu4_pod_src -= u1_wd_stride * i1_ht; + if(i1_ht < 0) + /* Top POD */ + i1_ht = -i1_ht; + else + { + /* Bottom POD */ + pu4_pod_src += (ps_pred->i1_dma_ht - 1) * u1_wd_stride; + pu4_pod_dst += (ps_pred->i1_dma_ht - i1_ht) * u1_wd_stride; + } + + for(i = 0; i < i1_ht; i++) + for(j = 0; j < u1_wd_stride; j++) + *pu4_pod_dst++ = *(pu4_pod_src + j); + } +} + diff --git a/dependencies/ih264d/decoder/ih264d_inter_pred.h b/dependencies/ih264d/decoder/ih264d_inter_pred.h new file mode 100644 index 00000000..dda3964a --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_inter_pred.h @@ -0,0 +1,92 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ + +#ifndef _IH264D_INTER_PRED_H_ +#define _IH264D_INTER_PRED_H_ + +/*! + ************************************************************************** + * \file ih264d_inter_pred.h + * + * \brief + * Decalaration for routines defined in MorionCompensate.c + * + * Detailed_description + * + * \date + * creation_date + * + * \author Arvind Raman + ************************************************************************** + */ + +#include "ih264d_structs.h" + +#define BUFFER_WIDTH 16 +/*! + ************************************************************************** + * \brief PRED_BUFFER_WIDTH / HEIGHT + * + * Width and height of the 16 bit (also reused a 2 8 bits buffers). The + * required dimensions for these buffers are 21x21, however to align the + * start of every row to a WORD aligned boundary the width has been increased + * to 24. + ************************************************************************** + */ +//#define PRED_BUFFER_WIDTH 24 +//#define PRED_BUFFER_HEIGHT 21 +#define PRED_BUFFER_WIDTH 24*2 +#define PRED_BUFFER_HEIGHT 24*2 + +void ih264d_fill_pred_info(WORD16 *pi2_mv,WORD32 part_width,WORD32 part_height, WORD32 sub_mb_num, + WORD32 pred_dir,pred_info_pkd_t *ps_pred_pkd,WORD8 i1_buf_id, + WORD8 i1_ref_idx,UWORD32 *pu4_wt_offset,UWORD8 u1_pic_type); + +WORD32 ih264d_form_mb_part_info_bp(pred_info_pkd_t *ps_pred_pkd, + dec_struct_t * ps_dec, + UWORD16 u2_mb_x, + UWORD16 u2_mb_y, + WORD32 mb_index, + dec_mb_info_t *ps_cur_mb_info); + +WORD32 ih264d_form_mb_part_info_mp(pred_info_pkd_t *ps_pred_pkd, + dec_struct_t * ps_dec, + UWORD16 u2_mb_x, + UWORD16 u2_mb_y, + WORD32 mb_index, + dec_mb_info_t *ps_cur_mb_info); + + +void ih264d_motion_compensate_bp(dec_struct_t * ps_dec, dec_mb_info_t *ps_cur_mb_info); +void ih264d_motion_compensate_mp(dec_struct_t * ps_dec, dec_mb_info_t *ps_cur_mb_info); + + +void TransferRefBuffs(dec_struct_t *ps_dec); + +void ih264d_multiplex_ref_data(dec_struct_t * ps_dec, + pred_info_t *ps_pred, + UWORD8* pu1_dest_y, + UWORD8* pu1_dest_u, + dec_mb_info_t *ps_cur_mb_info, + UWORD16 u2_dest_wd_y, + UWORD16 u2_dest_wd_uv, + UWORD8 u1_dir); +#endif /* _IH264D_INTER_PRED_H_ */ + diff --git a/dependencies/ih264d/decoder/ih264d_mb_utils.c b/dependencies/ih264d/decoder/ih264d_mb_utils.c new file mode 100644 index 00000000..ded2756d --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_mb_utils.c @@ -0,0 +1,1470 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/*! + ************************************************************************** + * \file ih264d_mb_utils.c + * + * \brief + * Contains utitlity functions needed for Macroblock decoding + * + * \date + * 18/12/2002 + * + * \author AI + ************************************************************************** + */ +#include <string.h> +#include <stdlib.h> +#include "ih264d_bitstrm.h" +#include "ih264d_defs.h" +#include "ih264d_debug.h" +#include "ih264d_structs.h" +#include "ih264d_defs.h" +#include "ih264d_mb_utils.h" +#include "ih264d_parse_slice.h" +#include "ih264d_error_handler.h" +#include "ih264d_parse_mb_header.h" +#include "ih264d_cabac.h" +#include "ih264d_defs.h" +#include "ih264d_tables.h" + +/*****************************************************************************/ +/* */ +/* Function Name : get_mb_info_cavlc */ +/* */ +/* Description : This function sets the following information of cur MB */ +/* (a) mb_x and mb_y */ +/* (b) Neighbour availablity */ +/* (c) Macroblock location in the frame buffer */ +/* (e) For mbaff predicts field/frame u4_flag for topMb */ +/* and sets the field/frame for botMb. This is */ +/* written in ps_dec->u1_cur_mb_fld_dec_flag */ +/* */ +/* Inputs : pointer to decstruct */ +/* pointer to current mb info */ +/* currentMbaddress */ +/* */ +/* Processing : leftMb and TopMb params are used by DecMbskip and */ +/* DecCtxMbfield modules so that these modules do not */ +/* check for neigbour availability and then find the */ +/* neigbours for context increments */ +/* */ +/* Returns : OK */ +/* */ +/* Issues : <List any issues or problems with this function> */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 13 07 2002 Jay Draft */ +/* */ +/*****************************************************************************/ + +UWORD32 ih264d_get_mb_info_cavlc_nonmbaff(dec_struct_t *ps_dec, + const UWORD16 u2_cur_mb_address, + dec_mb_info_t * ps_cur_mb_info, + UWORD32 u4_mbskip_run) +{ + WORD32 mb_x; + WORD32 mb_y; + UWORD8 u1_mb_ngbr_avail = 0; + UWORD16 u2_frm_width_in_mb = ps_dec->u2_frm_wd_in_mbs; + WORD16 i2_prev_slice_mbx = ps_dec->i2_prev_slice_mbx; + UWORD16 u2_top_right_mask = TOP_RIGHT_DEFAULT_AVAILABLE; + UWORD16 u2_top_left_mask = TOP_LEFT_DEFAULT_AVAILABLE; + UNUSED(u4_mbskip_run); + /*--------------------------------------------------------------------*/ + /* Calculate values of mb_x and mb_y */ + /*--------------------------------------------------------------------*/ + mb_x = (WORD16)ps_dec->u2_mbx; + mb_y = (WORD16)ps_dec->u2_mby; + + ps_dec->u2_cur_mb_addr = u2_cur_mb_address; + + mb_x++; + + if(mb_x == u2_frm_width_in_mb) + { + mb_x = 0; + mb_y++; + } + if(mb_y > ps_dec->i2_prev_slice_mby) + { + /* if not in the immemdiate row of prev slice end then top + will be available */ + if(mb_y > (ps_dec->i2_prev_slice_mby + 1)) + i2_prev_slice_mbx = -1; + + if(mb_x > i2_prev_slice_mbx) + { + u1_mb_ngbr_avail |= TOP_MB_AVAILABLE_MASK; + u2_top_right_mask |= TOP_RIGHT_TOP_AVAILABLE; + u2_top_left_mask |= TOP_LEFT_TOP_AVAILABLE; + } + + if((mb_x > (i2_prev_slice_mbx - 1)) + && (mb_x != (u2_frm_width_in_mb - 1))) + { + u1_mb_ngbr_avail |= TOP_RIGHT_MB_AVAILABLE_MASK; + u2_top_right_mask |= TOP_RIGHT_TOPR_AVAILABLE; + } + + if(mb_x > (i2_prev_slice_mbx + 1)) + { + u1_mb_ngbr_avail |= TOP_LEFT_MB_AVAILABLE_MASK; + u2_top_left_mask |= TOP_LEFT_TOPL_AVAILABLE; + } + + /* Next row Left will be available*/ + i2_prev_slice_mbx = -1; + } + + /* Same row */ + if(mb_x > (i2_prev_slice_mbx + 1)) + { + u1_mb_ngbr_avail |= LEFT_MB_AVAILABLE_MASK; + u2_top_left_mask |= TOP_LEFT_LEFT_AVAILABLE; + } + + { + mb_neigbour_params_t *ps_cur_mb_row = ps_dec->ps_cur_mb_row; + mb_neigbour_params_t *ps_top_mb_row = ps_dec->ps_top_mb_row; + + /* copy the parameters of topleft Mb */ + ps_cur_mb_info->u1_topleft_mbtype = ps_dec->u1_topleft_mbtype; + /* Neighbour pointer assignments*/ + ps_cur_mb_info->ps_curmb = ps_cur_mb_row + mb_x; + ps_cur_mb_info->ps_left_mb = ps_cur_mb_row + mb_x - 1; + ps_cur_mb_info->ps_top_mb = ps_top_mb_row + mb_x; + ps_cur_mb_info->ps_top_right_mb = ps_top_mb_row + mb_x + 1; + + /* Update the parameters of topleftmb*/ + ps_dec->u1_topleft_mbtype = ps_cur_mb_info->ps_top_mb->u1_mb_type; + } + + ps_dec->u2_mby = mb_y; + ps_dec->u2_mbx = mb_x; + ps_cur_mb_info->u2_mbx = mb_x; + ps_cur_mb_info->u2_mby = mb_y; + ps_cur_mb_info->u1_topmb = 1; + ps_dec->i4_submb_ofst += SUB_BLK_SIZE; + ps_dec->u1_mb_ngbr_availablity = u1_mb_ngbr_avail; + ps_cur_mb_info->u1_mb_ngbr_availablity = u1_mb_ngbr_avail; + ps_cur_mb_info->ps_curmb->u1_mb_fld = ps_dec->u1_cur_mb_fld_dec_flag; + ps_cur_mb_info->u1_mb_field_decodingflag = ps_dec->u1_cur_mb_fld_dec_flag; + ps_cur_mb_info->u2_top_left_avail_mask = u2_top_left_mask; + ps_cur_mb_info->u2_top_right_avail_mask = u2_top_right_mask; + return (OK); + +} + +/*****************************************************************************/ +/* */ +/* Function Name : get_mb_info_cavlc */ +/* */ +/* Description : This function sets the following information of cur MB */ +/* (a) mb_x and mb_y */ +/* (b) Neighbour availablity */ +/* (c) Macroblock location in the frame buffer */ +/* (e) For mbaff predicts field/frame u4_flag for topMb */ +/* and sets the field/frame for botMb. This is */ +/* written in ps_dec->u1_cur_mb_fld_dec_flag */ +/* */ +/* Inputs : pointer to decstruct */ +/* pointer to current mb info */ +/* currentMbaddress */ +/* */ +/* Processing : leftMb and TopMb params are used by DecMbskip and */ +/* DecCtxMbfield modules so that these modules do not */ +/* check for neigbour availability and then find the */ +/* neigbours for context increments */ +/* */ +/* Returns : OK */ +/* */ +/* Issues : <List any issues or problems with this function> */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 13 07 2002 Jay Draft */ +/* */ +/*****************************************************************************/ + +UWORD32 ih264d_get_mb_info_cavlc_mbaff(dec_struct_t *ps_dec, + const UWORD16 u2_cur_mb_address, + dec_mb_info_t * ps_cur_mb_info, + UWORD32 u4_mbskip_run) +{ + UWORD16 u2_mb_x; + UWORD16 u2_mb_y; + UWORD8 u1_mb_ngbr_avail = 0; + UWORD16 u2_frm_width_in_mb = ps_dec->u2_frm_wd_in_mbs; + + UWORD8 u1_top_mb = 1 - (u2_cur_mb_address & 0x01); + WORD16 i2_prev_slice_mbx = ps_dec->i2_prev_slice_mbx; + UWORD8 u1_cur_mb_field = 0; + UWORD16 u2_top_right_mask = TOP_RIGHT_DEFAULT_AVAILABLE; + UWORD16 u2_top_left_mask = TOP_LEFT_DEFAULT_AVAILABLE; + + /*--------------------------------------------------------------------*/ + /* Calculate values of mb_x and mb_y */ + /*--------------------------------------------------------------------*/ + u2_mb_x = ps_dec->u2_mbx; + u2_mb_y = ps_dec->u2_mby; + + ps_dec->u2_cur_mb_addr = u2_cur_mb_address; + + + if(u1_top_mb) + { + u2_mb_x++; + if(u2_mb_x == u2_frm_width_in_mb) + { + u2_mb_x = 0; + u2_mb_y += 2; + } + if(u2_mb_y > ps_dec->i2_prev_slice_mby) + { + /* if not in the immemdiate row of prev slice end then top + will be available */ + if(u2_mb_y > (ps_dec->i2_prev_slice_mby + 2)) + i2_prev_slice_mbx = -1; + if(u2_mb_x > i2_prev_slice_mbx) + { + u1_mb_ngbr_avail |= TOP_MB_AVAILABLE_MASK; + u1_cur_mb_field = ps_dec->ps_top_mb_row[u2_mb_x << 1].u1_mb_fld; + u2_top_right_mask |= TOP_RIGHT_TOP_AVAILABLE; + u2_top_left_mask |= TOP_LEFT_TOP_AVAILABLE; + } + if((u2_mb_x > (i2_prev_slice_mbx - 1)) + && (u2_mb_x != (u2_frm_width_in_mb - 1))) + { + u1_mb_ngbr_avail |= TOP_RIGHT_MB_AVAILABLE_MASK; + u2_top_right_mask |= TOP_RIGHT_TOPR_AVAILABLE; + } + + if(u2_mb_x > (i2_prev_slice_mbx + 1)) + { + u1_mb_ngbr_avail |= TOP_LEFT_MB_AVAILABLE_MASK; + u2_top_left_mask |= TOP_LEFT_TOPL_AVAILABLE; + } + + i2_prev_slice_mbx = -1; + } + /* Same row */ + if(u2_mb_x > (i2_prev_slice_mbx + 1)) + { + u1_mb_ngbr_avail |= LEFT_MB_AVAILABLE_MASK; + u1_cur_mb_field = + ps_dec->ps_cur_mb_row[(u2_mb_x << 1) - 1].u1_mb_fld; + u2_top_left_mask |= TOP_LEFT_LEFT_AVAILABLE; + } + /* Read u1_cur_mb_field from the bitstream if u4_mbskip_run <= 1*/ + if(u4_mbskip_run <= 1) + u1_cur_mb_field = (UWORD8)ih264d_get_bit_h264(ps_dec->ps_bitstrm); + + ps_dec->u1_cur_mb_fld_dec_flag = u1_cur_mb_field; + ps_dec->u2_top_left_mask = u2_top_left_mask; + ps_dec->u2_top_right_mask = u2_top_right_mask; + } + else + { + u1_mb_ngbr_avail = ps_dec->u1_mb_ngbr_availablity; + u1_cur_mb_field = ps_dec->u1_cur_mb_fld_dec_flag; + u2_top_left_mask = ps_dec->u2_top_left_mask; + u2_top_right_mask = ps_dec->u2_top_right_mask; + + if(!u1_cur_mb_field) + { + /* Top is available */ + u1_mb_ngbr_avail |= TOP_MB_AVAILABLE_MASK; + u2_top_right_mask |= TOP_RIGHT_TOP_AVAILABLE; + u2_top_left_mask |= TOP_LEFT_TOP_AVAILABLE; + /* Top Right not available */ + u1_mb_ngbr_avail &= TOP_RT_SUBBLOCK_MASK_MOD; + u2_top_right_mask &= (~TOP_RIGHT_TOPR_AVAILABLE); + + if(u1_mb_ngbr_avail & LEFT_MB_AVAILABLE_MASK) + { + u1_mb_ngbr_avail |= TOP_LEFT_MB_AVAILABLE_MASK; + u2_top_left_mask |= TOP_LEFT_LEFT_AVAILABLE; + u2_top_left_mask |= TOP_LEFT_TOPL_AVAILABLE; + } + } + } + + ps_dec->u2_mby = u2_mb_y; + ps_dec->u2_mbx = u2_mb_x; + ps_cur_mb_info->u2_mbx = u2_mb_x; + ps_cur_mb_info->u2_mby = u2_mb_y; + ps_cur_mb_info->u1_topmb = u1_top_mb; + ps_dec->i4_submb_ofst += SUB_BLK_SIZE; + ps_dec->u1_mb_ngbr_availablity = u1_mb_ngbr_avail; + ps_cur_mb_info->u1_mb_ngbr_availablity = u1_mb_ngbr_avail; + ps_cur_mb_info->u1_mb_field_decodingflag = u1_cur_mb_field; + ps_cur_mb_info->u2_top_left_avail_mask = u2_top_left_mask; + ps_cur_mb_info->u2_top_right_avail_mask = u2_top_right_mask; + ih264d_get_mbaff_neighbours(ps_dec, ps_cur_mb_info, u1_cur_mb_field); + return (OK); +} + +/*****************************************************************************/ +/* */ +/* Function Name : get_mb_info_cabac */ +/* */ +/* Description : This function sets the following information of cur MB */ +/* (a) mb_x and mb_y */ +/* (b) Neighbour availablity */ +/* (c) Macroblock location in the frame buffer */ +/* (e) leftMb parama and TopMb params of curMB */ +/* (f) For Mbaff case leftMb params and TopMb params of */ +/* bottomMb are also set if curMB is top */ +/* (g) For mbaff predicts field/frame u4_flag for topMb */ +/* and sets the field/frame for botMb. This is */ +/* written in ps_dec->u1_cur_mb_fld_dec_flag */ +/* */ +/* Inputs : pointer to decstruct */ +/* pointer to current mb info */ +/* currentMbaddress */ +/* */ +/* Processing : leftMb and TopMb params are used by DecMbskip and */ +/* DecCtxMbfield modules so that these modules do not */ +/* check for neigbour availability and then find the */ +/* neigbours for context increments */ +/* */ +/* Returns : OK */ +/* */ +/* Issues : <List any issues or problems with this function> */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 13 07 2002 Jay Draft */ +/* */ +/*****************************************************************************/ +UWORD32 ih264d_get_mb_info_cabac_nonmbaff(dec_struct_t *ps_dec, + const UWORD16 u2_cur_mb_address, + dec_mb_info_t * ps_cur_mb_info, + UWORD32 u4_mbskip) +{ + WORD32 mb_x; + WORD32 mb_y; + UWORD32 u1_mb_ngbr_avail = 0; + UWORD32 u2_frm_width_in_mb = ps_dec->u2_frm_wd_in_mbs; + UWORD32 u1_top_mb = 1; + WORD32 i2_prev_slice_mbx = ps_dec->i2_prev_slice_mbx; + UWORD32 u2_top_right_mask = TOP_RIGHT_DEFAULT_AVAILABLE; + UWORD32 u2_top_left_mask = TOP_LEFT_DEFAULT_AVAILABLE; + ctxt_inc_mb_info_t * const p_ctx_inc_mb_map = ps_dec->p_ctxt_inc_mb_map; + + /*--------------------------------------------------------------------*/ + /* Calculate values of mb_x and mb_y */ + /*--------------------------------------------------------------------*/ + mb_x = (WORD16)ps_dec->u2_mbx; + mb_y = (WORD16)ps_dec->u2_mby; + + ps_dec->u2_cur_mb_addr = u2_cur_mb_address; + + mb_x++; + if((UWORD32)mb_x == u2_frm_width_in_mb) + { + mb_x = 0; + mb_y++; + } + /*********************************************************************/ + /* Cabac Context Initialisations */ + /*********************************************************************/ + ps_dec->ps_curr_ctxt_mb_info = p_ctx_inc_mb_map + mb_x; + ps_dec->p_left_ctxt_mb_info = p_ctx_inc_mb_map - 1; + ps_dec->p_top_ctxt_mb_info = p_ctx_inc_mb_map - 1; + + /********************************************************************/ + /* neighbour availablility */ + /********************************************************************/ + if(mb_y > ps_dec->i2_prev_slice_mby) + { + /* if not in the immemdiate row of prev slice end then top + will be available */ + if(mb_y > (ps_dec->i2_prev_slice_mby + 1)) + i2_prev_slice_mbx = -1; + + if(mb_x > i2_prev_slice_mbx) + { + u1_mb_ngbr_avail |= TOP_MB_AVAILABLE_MASK; + u2_top_right_mask |= TOP_RIGHT_TOP_AVAILABLE; + u2_top_left_mask |= TOP_LEFT_TOP_AVAILABLE; + ps_dec->p_top_ctxt_mb_info = ps_dec->ps_curr_ctxt_mb_info; + } + if((mb_x > (i2_prev_slice_mbx - 1)) + && ((UWORD32)mb_x != (u2_frm_width_in_mb - 1))) + { + u1_mb_ngbr_avail |= TOP_RIGHT_MB_AVAILABLE_MASK; + u2_top_right_mask |= TOP_RIGHT_TOPR_AVAILABLE; + } + + if(mb_x > (i2_prev_slice_mbx + 1)) + { + u1_mb_ngbr_avail |= TOP_LEFT_MB_AVAILABLE_MASK; + u2_top_left_mask |= TOP_LEFT_TOPL_AVAILABLE; + } + /* Next row */ + i2_prev_slice_mbx = -1; + } + /* Same row */ + if(mb_x > (i2_prev_slice_mbx + 1)) + { + u1_mb_ngbr_avail |= LEFT_MB_AVAILABLE_MASK; + u2_top_left_mask |= TOP_LEFT_LEFT_AVAILABLE; + ps_dec->p_left_ctxt_mb_info = ps_dec->ps_curr_ctxt_mb_info - 1; + } + { + mb_neigbour_params_t *ps_cur_mb_row = ps_dec->ps_cur_mb_row; + mb_neigbour_params_t *ps_top_mb_row = ps_dec->ps_top_mb_row; + /* copy the parameters of topleft Mb */ + ps_cur_mb_info->u1_topleft_mbtype = ps_dec->u1_topleft_mbtype; + /* Neighbour pointer assignments*/ + ps_cur_mb_info->ps_curmb = ps_cur_mb_row + mb_x; + ps_cur_mb_info->ps_left_mb = ps_cur_mb_row + mb_x - 1; + ps_cur_mb_info->ps_top_mb = ps_top_mb_row + mb_x; + ps_cur_mb_info->ps_top_right_mb = ps_top_mb_row + mb_x + 1; + + /* Update the parameters of topleftmb*/ + ps_dec->u1_topleft_mbtype = ps_cur_mb_info->ps_top_mb->u1_mb_type; + } + + ps_dec->u2_mby = mb_y; + ps_dec->u2_mbx = mb_x; + ps_cur_mb_info->u2_mbx = mb_x; + ps_cur_mb_info->u2_mby = mb_y; + ps_cur_mb_info->u1_topmb = u1_top_mb; + ps_dec->i4_submb_ofst += SUB_BLK_SIZE; + ps_dec->u1_mb_ngbr_availablity = u1_mb_ngbr_avail; + ps_cur_mb_info->u1_mb_ngbr_availablity = u1_mb_ngbr_avail; + ps_cur_mb_info->ps_curmb->u1_mb_fld = ps_dec->u1_cur_mb_fld_dec_flag; + ps_cur_mb_info->u1_mb_field_decodingflag = ps_dec->u1_cur_mb_fld_dec_flag; + ps_cur_mb_info->u2_top_left_avail_mask = u2_top_left_mask; + ps_cur_mb_info->u2_top_right_avail_mask = u2_top_right_mask; + + /*********************************************************************/ + /* Assign the neigbours */ + /*********************************************************************/ + if(u4_mbskip) + { + UWORD32 u4_ctx_inc = + 2 + - ((!!(ps_dec->p_top_ctxt_mb_info->u1_mb_type + & CAB_SKIP_MASK)) + + (!!(ps_dec->p_left_ctxt_mb_info->u1_mb_type + & CAB_SKIP_MASK))); + + u4_mbskip = ih264d_decode_bin(u4_ctx_inc, ps_dec->p_mb_skip_flag_t, + ps_dec->ps_bitstrm, &ps_dec->s_cab_dec_env); + + if(!u4_mbskip) + { + if(!(u1_mb_ngbr_avail & LEFT_MB_AVAILABLE_MASK)) + { + UWORD32 *pu4_buf; + UWORD8 *pu1_buf; + + pu1_buf = ps_dec->pu1_left_nnz_y; + pu4_buf = (UWORD32 *)pu1_buf; + *pu4_buf = 0; + pu1_buf = ps_dec->pu1_left_nnz_uv; + pu4_buf = (UWORD32 *)pu1_buf; + *pu4_buf = 0; + + + *(ps_dec->pu1_left_yuv_dc_csbp) = 0; + MEMSET_16BYTES(&ps_dec->pu1_left_mv_ctxt_inc[0][0], 0); + *(UWORD32 *)ps_dec->pi1_left_ref_idx_ctxt_inc = 0; + } + if(!(u1_mb_ngbr_avail & TOP_MB_AVAILABLE_MASK)) + { + MEMSET_16BYTES(ps_dec->ps_curr_ctxt_mb_info->u1_mv, 0); + memset(ps_dec->ps_curr_ctxt_mb_info->i1_ref_idx, 0, 4); + } + } + } + return (u4_mbskip); +} + +/*****************************************************************************/ +/* */ +/* Function Name : get_mb_info_cabac */ +/* */ +/* Description : This function sets the following information of cur MB */ +/* (a) mb_x and mb_y */ +/* (b) Neighbour availablity */ +/* (c) Macroblock location in the frame buffer */ +/* (e) leftMb parama and TopMb params of curMB */ +/* (f) For Mbaff case leftMb params and TopMb params of */ +/* bottomMb are also set if curMB is top */ +/* (g) For mbaff predicts field/frame u4_flag for topMb */ +/* and sets the field/frame for botMb. This is */ +/* written in ps_dec->u1_cur_mb_fld_dec_flag */ +/* */ +/* Inputs : pointer to decstruct */ +/* pointer to current mb info */ +/* currentMbaddress */ +/* */ +/* Processing : leftMb and TopMb params are used by DecMbskip and */ +/* DecCtxMbfield modules so that these modules do not */ +/* check for neigbour availability and then find the */ +/* neigbours for context increments */ +/* */ +/* Returns : OK */ +/* */ +/* Issues : <List any issues or problems with this function> */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 13 07 2002 Jay Draft */ +/* */ +/*****************************************************************************/ + +UWORD32 ih264d_get_mb_info_cabac_mbaff(dec_struct_t *ps_dec, + const UWORD16 u2_cur_mb_address, + dec_mb_info_t * ps_cur_mb_info, + UWORD32 u4_mbskip) +{ + WORD32 mb_x; + WORD32 mb_y; + UWORD8 u1_mb_ngbr_avail = 0; + UWORD16 u2_frm_width_in_mb = ps_dec->u2_frm_wd_in_mbs; + ctxt_inc_mb_info_t * const p_ctx_inc_mb_map = ps_dec->p_ctxt_inc_mb_map; + ctxt_inc_mb_info_t *ps_curr_ctxt, *ps_top_ctxt, *ps_left_ctxt; + mb_neigbour_params_t *ps_cur_mb_row = ps_dec->ps_cur_mb_row; + mb_neigbour_params_t *ps_top_mb_row = ps_dec->ps_top_mb_row; + UWORD32 u4_left_mb_pair_fld = 0; + UWORD32 u4_top_mb_pair_fld = 0; + UWORD8 u1_cur_mb_field = 0; + UWORD8 u1_top_mb = 1 - (u2_cur_mb_address & 0x01); + WORD16 i2_prev_slice_mbx = ps_dec->i2_prev_slice_mbx; + UWORD16 u2_top_right_mask = TOP_RIGHT_DEFAULT_AVAILABLE; + UWORD16 u2_top_left_mask = TOP_LEFT_DEFAULT_AVAILABLE; + + /*--------------------------------------------------------------------*/ + /* Calculate values of mb_x and mb_y */ + /*--------------------------------------------------------------------*/ + mb_x = (WORD16)ps_dec->u2_mbx; + mb_y = (WORD16)ps_dec->u2_mby; + + ps_dec->u2_cur_mb_addr = u2_cur_mb_address; + + ps_top_ctxt = ps_left_ctxt = p_ctx_inc_mb_map - 1; + + if(u1_top_mb) + { + ctxt_inc_mb_info_t *ps_left_mb_of_bot = ps_left_ctxt; + ctxt_inc_mb_info_t *ps_top_mb_of_bot = ps_top_ctxt; + + mb_x++; + + if(mb_x == u2_frm_width_in_mb) + { + mb_x = 0; + mb_y += 2; + } + + ps_curr_ctxt = p_ctx_inc_mb_map + (mb_x << 1); + if(mb_y > ps_dec->i2_prev_slice_mby) + { + UWORD8 u1_cur_mb_fld_flag_known = 0; + /* Next row */ + if(mb_x > 0) + { + /***********************************************************************/ + /* Left Mb is avialable */ + /***********************************************************************/ + u1_mb_ngbr_avail |= LEFT_MB_AVAILABLE_MASK; + ps_left_ctxt = ps_curr_ctxt - 2; + ps_left_mb_of_bot = ps_curr_ctxt - 1; + u1_cur_mb_field = u4_left_mb_pair_fld = ps_cur_mb_row[(mb_x + << 1) - 1].u1_mb_fld; + u1_cur_mb_fld_flag_known = 1; + u2_top_left_mask |= TOP_LEFT_LEFT_AVAILABLE; + } + /* if not in the immemdiate row of prev slice end then top + will be available */ + if(mb_y > (ps_dec->i2_prev_slice_mby + 2)) + i2_prev_slice_mbx = -1; + if(mb_x > i2_prev_slice_mbx) + { + /*********************************************************************/ + /* Top Mb is avialable */ + /*********************************************************************/ + u1_mb_ngbr_avail |= TOP_MB_AVAILABLE_MASK; + u2_top_right_mask |= TOP_RIGHT_TOP_AVAILABLE; + u2_top_left_mask |= TOP_LEFT_TOP_AVAILABLE; + + /* point to MbAddrB + 1 */ + ps_top_ctxt = ps_curr_ctxt + 1; + u4_top_mb_pair_fld = ps_top_mb_row[(mb_x << 1)].u1_mb_fld; + + u1_cur_mb_field = + u1_cur_mb_fld_flag_known ? + u1_cur_mb_field : + u4_top_mb_pair_fld; + ps_top_mb_of_bot = u1_cur_mb_field ? ps_top_ctxt : ps_curr_ctxt; + + /* MbAddrB */ + ps_top_ctxt -= (u1_cur_mb_field && u4_top_mb_pair_fld); + } + + if((mb_x > (i2_prev_slice_mbx - 1)) + && (mb_x != (u2_frm_width_in_mb - 1))) + { + u1_mb_ngbr_avail |= TOP_RIGHT_MB_AVAILABLE_MASK; + u2_top_right_mask |= TOP_RIGHT_TOPR_AVAILABLE; + } + + if(mb_x > (i2_prev_slice_mbx + 1)) + { + u1_mb_ngbr_avail |= TOP_LEFT_MB_AVAILABLE_MASK; + u2_top_left_mask |= TOP_LEFT_TOPL_AVAILABLE; + } + } + else + { + /* Same row */ + if(mb_x > (i2_prev_slice_mbx + 1)) + { + /***************************************************************/ + /* Left Mb is avialable */ + /***************************************************************/ + u1_mb_ngbr_avail |= LEFT_MB_AVAILABLE_MASK; + + u1_cur_mb_field = u4_left_mb_pair_fld = ps_cur_mb_row[(mb_x + << 1) - 1].u1_mb_fld; + ps_left_ctxt = ps_curr_ctxt - 2; + ps_left_mb_of_bot = ps_curr_ctxt - 1; + u2_top_left_mask |= TOP_LEFT_LEFT_AVAILABLE; + } + } + /*********************************************************/ + /* Check whether the call is from I slice or Inter slice */ + /*********************************************************/ + if(u4_mbskip) + { + UWORD32 u4_ctx_inc = 2 + - ((!!(ps_top_ctxt->u1_mb_type & CAB_SKIP_MASK)) + + (!!(ps_left_ctxt->u1_mb_type + & CAB_SKIP_MASK))); + dec_bit_stream_t * const ps_bitstrm = ps_dec->ps_bitstrm; + decoding_envirnoment_t *ps_cab_dec_env = &ps_dec->s_cab_dec_env; + bin_ctxt_model_t *p_mb_skip_flag_t = ps_dec->p_mb_skip_flag_t; + + ps_dec->u4_next_mb_skip = 0; + u4_mbskip = ih264d_decode_bin(u4_ctx_inc, p_mb_skip_flag_t, + ps_bitstrm, ps_cab_dec_env); + + if(u4_mbskip) + { + UWORD32 u4_next_mbskip; + ps_curr_ctxt->u1_mb_type = CAB_SKIP; + + u4_ctx_inc = + 2 + - ((!!(ps_top_mb_of_bot->u1_mb_type + & CAB_SKIP_MASK)) + + (!!(ps_left_mb_of_bot->u1_mb_type + & CAB_SKIP_MASK))); + + /* Decode the skip u4_flag of bottom Mb */ + u4_next_mbskip = ih264d_decode_bin(u4_ctx_inc, p_mb_skip_flag_t, + ps_bitstrm, + ps_cab_dec_env); + + ps_dec->u4_next_mb_skip = u4_next_mbskip; + + if(!u4_next_mbskip) + { + u4_ctx_inc = u4_top_mb_pair_fld + u4_left_mb_pair_fld; + + u1_cur_mb_field = ih264d_decode_bin( + u4_ctx_inc, ps_dec->p_mb_field_dec_flag_t, + ps_bitstrm, ps_cab_dec_env); + } + } + } + + if(!u4_mbskip) + { + UWORD32 u4_ctx_inc = u4_top_mb_pair_fld + u4_left_mb_pair_fld; + u1_cur_mb_field = ih264d_decode_bin(u4_ctx_inc, + ps_dec->p_mb_field_dec_flag_t, + ps_dec->ps_bitstrm, + &ps_dec->s_cab_dec_env); + } + + ps_dec->u1_cur_mb_fld_dec_flag = u1_cur_mb_field; + ps_dec->u2_top_left_mask = u2_top_left_mask; + ps_dec->u2_top_right_mask = u2_top_right_mask; + ps_dec->u2_mby = mb_y; + ps_dec->u2_mbx = mb_x; + } + else + { + u1_cur_mb_field = ps_dec->u1_cur_mb_fld_dec_flag; + u1_mb_ngbr_avail = ps_dec->u1_mb_ngbr_availablity; + u2_top_left_mask = ps_dec->u2_top_left_mask; + u2_top_right_mask = ps_dec->u2_top_right_mask; + ps_curr_ctxt = p_ctx_inc_mb_map + (mb_x << 1) + 1; + + if(u1_mb_ngbr_avail & LEFT_MB_AVAILABLE_MASK) + { + u4_left_mb_pair_fld = ps_cur_mb_row[(mb_x << 1) - 1].u1_mb_fld; + + /* point to A if top else A+1 */ + ps_left_ctxt = ps_curr_ctxt - 2 + - (u4_left_mb_pair_fld != u1_cur_mb_field); + } + + if(u1_cur_mb_field) + { + if(u1_mb_ngbr_avail & TOP_MB_AVAILABLE_MASK) + { + /* point to MbAddrB + 1 */ + ps_top_ctxt = ps_curr_ctxt; + } + } + else + { + /* Top is available */ + u1_mb_ngbr_avail |= TOP_MB_AVAILABLE_MASK; + u2_top_right_mask |= TOP_RIGHT_TOP_AVAILABLE; + u2_top_left_mask |= TOP_LEFT_TOP_AVAILABLE; + /* Top Right not available */ + u1_mb_ngbr_avail &= TOP_RT_SUBBLOCK_MASK_MOD; + u2_top_right_mask &= (~TOP_RIGHT_TOPR_AVAILABLE); + + if(u1_mb_ngbr_avail & LEFT_MB_AVAILABLE_MASK) + { + u1_mb_ngbr_avail |= TOP_LEFT_MB_AVAILABLE_MASK; + u2_top_left_mask |= TOP_LEFT_LEFT_AVAILABLE; + u2_top_left_mask |= TOP_LEFT_TOPL_AVAILABLE; + } + + /* CurMbAddr - 1 */ + ps_top_ctxt = ps_curr_ctxt - 1; + } + + if(u4_mbskip) + { + if(ps_curr_ctxt[-1].u1_mb_type & CAB_SKIP_MASK) + { + /* If previous mb is skipped, return value of next mb skip */ + u4_mbskip = ps_dec->u4_next_mb_skip; + + } + else + { + /* If previous mb is not skipped then call DecMbSkip */ + UWORD32 u4_ctx_inc = + 2 + - ((!!(ps_top_ctxt->u1_mb_type + & CAB_SKIP_MASK)) + + (!!(ps_left_ctxt->u1_mb_type + & CAB_SKIP_MASK))); + + u4_mbskip = ih264d_decode_bin(u4_ctx_inc, + ps_dec->p_mb_skip_flag_t, + ps_dec->ps_bitstrm, + &ps_dec->s_cab_dec_env); + } + } + } + + ps_cur_mb_info->u2_mbx = mb_x; + ps_cur_mb_info->u2_mby = mb_y; + ps_cur_mb_info->u1_topmb = u1_top_mb; + ps_dec->i4_submb_ofst += SUB_BLK_SIZE; + ps_dec->u1_mb_ngbr_availablity = u1_mb_ngbr_avail; + ps_cur_mb_info->u1_mb_ngbr_availablity = u1_mb_ngbr_avail; + ps_cur_mb_info->u1_mb_field_decodingflag = u1_cur_mb_field; + ps_cur_mb_info->u2_top_left_avail_mask = u2_top_left_mask; + ps_cur_mb_info->u2_top_right_avail_mask = u2_top_right_mask; + + ih264d_get_mbaff_neighbours(ps_dec, ps_cur_mb_info, u1_cur_mb_field); + { + ih264d_get_cabac_context_mbaff(ps_dec, ps_cur_mb_info, u4_mbskip); + } + + { + bin_ctxt_model_t *p_cabac_ctxt_table_t = ps_dec->p_cabac_ctxt_table_t; + + if(u1_cur_mb_field) + { + p_cabac_ctxt_table_t += SIGNIFICANT_COEFF_FLAG_FLD; + } + else + { + p_cabac_ctxt_table_t += SIGNIFICANT_COEFF_FLAG_FRAME; + } + { + bin_ctxt_model_t * * p_significant_coeff_flag_t = + ps_dec->p_significant_coeff_flag_t; + p_significant_coeff_flag_t[0] = p_cabac_ctxt_table_t + + SIG_COEFF_CTXT_CAT_0_OFFSET; + p_significant_coeff_flag_t[1] = p_cabac_ctxt_table_t + + SIG_COEFF_CTXT_CAT_1_OFFSET; + p_significant_coeff_flag_t[2] = p_cabac_ctxt_table_t + + SIG_COEFF_CTXT_CAT_2_OFFSET; + p_significant_coeff_flag_t[3] = p_cabac_ctxt_table_t + + SIG_COEFF_CTXT_CAT_3_OFFSET; + p_significant_coeff_flag_t[4] = p_cabac_ctxt_table_t + + SIG_COEFF_CTXT_CAT_4_OFFSET; + p_significant_coeff_flag_t[5] = p_cabac_ctxt_table_t + + SIG_COEFF_CTXT_CAT_5_OFFSET; + + } + } + return (u4_mbskip); +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_get_cabac_context_mbaff */ +/* */ +/* Description : Gets the current macroblock Cabac Context and sets the */ +/* top and left cabac context ptrs in CtxIncMbMap */ +/* 1. For Coss field left neigbours it alters coded block */ +/* u4_flag , motion vectors, reference indices, cbp of */ +/* the left neigbours which increases the code i4_size */ +/* 2. For Coss field top neigbours it alters motion */ +/* vectors reference indices of the top neigbours */ +/* which further increases the code i4_size */ +/* */ +/* Inputs : 1. dec_struct_t */ +/* 2. CurMbAddr used for Mbaff (only to see if curMB */ +/* is top or bottom) */ +/* 3. uc_curMbFldDecFlag only for Mbaff */ +/* */ +/* Returns : 0 */ +/* */ +/* Issues : code i4_size can be reduced if ui_CodedBlockFlag storage */ +/* structure in context is changed. This change however */ +/* would break the parseResidual4x4Cabac asm routine. */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 18 06 2005 Jay */ +/* */ +/*****************************************************************************/ +UWORD32 ih264d_get_cabac_context_mbaff(dec_struct_t * ps_dec, + dec_mb_info_t *ps_cur_mb_info, + UWORD32 u4_mbskip) +{ + const UWORD8 u1_mb_ngbr_availablity = ps_dec->u1_mb_ngbr_availablity; + ctxt_inc_mb_info_t * const p_ctx_inc_mb_map = ps_dec->p_ctxt_inc_mb_map; + + UWORD8 (*pu1_left_mv_ctxt_inc_2d)[4] = &ps_dec->pu1_left_mv_ctxt_inc[0]; + WORD8 (*pi1_left_ref_idx_ctxt_inc) = ps_dec->pi1_left_ref_idx_ctxt_inc; + const UWORD8 u1_cur_mb_fld_flag = ps_cur_mb_info->u1_mb_field_decodingflag; + const UWORD8 u1_topmb = ps_cur_mb_info->u1_topmb; + const UWORD8 uc_botMb = 1 - ps_cur_mb_info->u1_topmb; + + ctxt_inc_mb_info_t * ps_leftMB; + + ps_dec->ps_curr_ctxt_mb_info = p_ctx_inc_mb_map + (ps_dec->u2_mbx << 1); + ps_dec->p_top_ctxt_mb_info = ps_dec->ps_curr_ctxt_mb_info; + + if(u1_topmb) + { + pu1_left_mv_ctxt_inc_2d = ps_dec->u1_left_mv_ctxt_inc_arr[0]; + pi1_left_ref_idx_ctxt_inc = &ps_dec->i1_left_ref_idx_ctx_inc_arr[0][0]; + ps_dec->pu1_left_yuv_dc_csbp = &ps_dec->u1_yuv_dc_csbp_topmb; + } + else + { + /* uc_botMb */ + pu1_left_mv_ctxt_inc_2d = ps_dec->u1_left_mv_ctxt_inc_arr[1]; + pi1_left_ref_idx_ctxt_inc = &ps_dec->i1_left_ref_idx_ctx_inc_arr[1][0]; + ps_dec->pu1_left_yuv_dc_csbp = &ps_dec->u1_yuv_dc_csbp_bot_mb; + ps_dec->ps_curr_ctxt_mb_info += 1; + } + + ps_dec->pu1_left_mv_ctxt_inc = pu1_left_mv_ctxt_inc_2d; + ps_dec->pi1_left_ref_idx_ctxt_inc = pi1_left_ref_idx_ctxt_inc; + + if(u1_mb_ngbr_availablity & LEFT_MB_AVAILABLE_MASK) + { + const UWORD8 u1_left_mb_fld_flag = ps_cur_mb_info->ps_left_mb->u1_mb_fld; + + ps_leftMB = ps_dec->ps_curr_ctxt_mb_info - 2; + if(u1_left_mb_fld_flag != u1_cur_mb_fld_flag) + { + ctxt_inc_mb_info_t *ps_tempLeft; + UWORD8 u1_cbp_t, u1_cbp_b; + UWORD8 u1_cr_cpb; + + ps_leftMB -= uc_botMb; + ps_tempLeft = ps_dec->ps_left_mb_ctxt_info; + ps_tempLeft->u1_mb_type = ps_leftMB->u1_mb_type; + ps_tempLeft->u1_intra_chroma_pred_mode = + ps_leftMB->u1_intra_chroma_pred_mode; + + ps_tempLeft->u1_transform8x8_ctxt = ps_leftMB->u1_transform8x8_ctxt; + + u1_cr_cpb = ps_leftMB->u1_cbp; + /*****************************************************************/ + /* reform RefIdx, CBP, MV and CBF ctxInc taking care of A and A+1*/ + /*****************************************************************/ + if(u1_cur_mb_fld_flag) + { + /* current MB is a FLD and left a FRM */ + UWORD8 (* const pu1_left_mv_ctxt_inc_2d_arr_top)[4] = + ps_dec->u1_left_mv_ctxt_inc_arr[0]; + UWORD8 (* const pu1_left_mv_ctxt_inc_2d_arr_bot)[4] = + ps_dec->u1_left_mv_ctxt_inc_arr[1]; + WORD8 (* const i1_left_ref_idx_ctxt_inc_arr_top) = + &ps_dec->i1_left_ref_idx_ctx_inc_arr[0][0]; + WORD8 (* const i1_left_ref_idx_ctxt_inc_arr_bot) = + &ps_dec->i1_left_ref_idx_ctx_inc_arr[1][0]; + + u1_cbp_t = ps_leftMB->u1_cbp; + u1_cbp_b = (ps_leftMB + 1)->u1_cbp; + ps_tempLeft->u1_cbp = (u1_cbp_t & 0x02) + | ((u1_cbp_b & 0x02) << 2); + + // set motionvectors as + // 0T = 0T 0B = 0T + // 1T = 2T 1B = 2T + // 2T = 0B 2B = 0B + // 3T = 2B 3B = 2B + if(u1_topmb) + { + /********************************************/ + /* Bottoms DC CBF = Top DC CBF */ + /********************************************/ + ps_dec->u1_yuv_dc_csbp_bot_mb = + ps_dec->u1_yuv_dc_csbp_topmb; + + *(UWORD32 *)pu1_left_mv_ctxt_inc_2d[3] = + *(UWORD32 *)pu1_left_mv_ctxt_inc_2d_arr_bot[2]; + *(UWORD32 *)pu1_left_mv_ctxt_inc_2d[1] = + *(UWORD32 *)pu1_left_mv_ctxt_inc_2d_arr_top[2]; + *(UWORD32 *)pu1_left_mv_ctxt_inc_2d[2] = + *(UWORD32 *)pu1_left_mv_ctxt_inc_2d_arr_bot[0]; + *(UWORD32 *)pu1_left_mv_ctxt_inc_2d[0] = + *(UWORD32 *)pu1_left_mv_ctxt_inc_2d_arr_top[0]; + + i1_left_ref_idx_ctxt_inc_arr_top[1] = + i1_left_ref_idx_ctxt_inc_arr_bot[0]; + i1_left_ref_idx_ctxt_inc_arr_top[3] = + i1_left_ref_idx_ctxt_inc_arr_bot[2]; + + *(UWORD32 *)(i1_left_ref_idx_ctxt_inc_arr_bot) = + *(UWORD32 *)(i1_left_ref_idx_ctxt_inc_arr_top); + + memcpy(pu1_left_mv_ctxt_inc_2d_arr_bot, + pu1_left_mv_ctxt_inc_2d_arr_top, 16); + } + + { + UWORD8 i; + for(i = 0; i < 4; i++) + { + pu1_left_mv_ctxt_inc_2d[i][1] >>= 1; + pu1_left_mv_ctxt_inc_2d[i][3] >>= 1; + } + } + } + else + { + /* current MB is a FRM and left FLD */ + if(u1_topmb) + { + u1_cbp_t = ps_leftMB->u1_cbp; + u1_cbp_t = (u1_cbp_t & 0x02); + ps_tempLeft->u1_cbp = (u1_cbp_t | (u1_cbp_t << 2)); + + /********************************************/ + /* Bottoms DC CBF = Top DC CBF */ + /********************************************/ + ps_dec->u1_yuv_dc_csbp_bot_mb = + ps_dec->u1_yuv_dc_csbp_topmb; + + // set motionvectors as + // 3B = 2B = 3T + // 1B = 0B = 2T + // 3T = 2T = 1T + // 1T = 0T = 0T + + *(UWORD32 *)pu1_left_mv_ctxt_inc_2d[7] = + *(UWORD32 *)pu1_left_mv_ctxt_inc_2d[3]; + *(UWORD32 *)pu1_left_mv_ctxt_inc_2d[6] = + *(UWORD32 *)pu1_left_mv_ctxt_inc_2d[3]; + + *(UWORD32 *)pu1_left_mv_ctxt_inc_2d[5] = + *(UWORD32 *)pu1_left_mv_ctxt_inc_2d[2]; + *(UWORD32 *)pu1_left_mv_ctxt_inc_2d[4] = + *(UWORD32 *)pu1_left_mv_ctxt_inc_2d[2]; + + *(UWORD32 *)pu1_left_mv_ctxt_inc_2d[3] = + *(UWORD32 *)pu1_left_mv_ctxt_inc_2d[1]; + *(UWORD32 *)pu1_left_mv_ctxt_inc_2d[2] = + *(UWORD32 *)pu1_left_mv_ctxt_inc_2d[1]; + + *(UWORD32 *)pu1_left_mv_ctxt_inc_2d[1] = + *(UWORD32 *)pu1_left_mv_ctxt_inc_2d[0]; + + pi1_left_ref_idx_ctxt_inc[7] = (pi1_left_ref_idx_ctxt_inc[3] + - 1); + pi1_left_ref_idx_ctxt_inc[6] = (pi1_left_ref_idx_ctxt_inc[3] + - 1); + + pi1_left_ref_idx_ctxt_inc[5] = (pi1_left_ref_idx_ctxt_inc[1] + - 1); + pi1_left_ref_idx_ctxt_inc[4] = (pi1_left_ref_idx_ctxt_inc[1] + - 1); + + pi1_left_ref_idx_ctxt_inc[3] = (pi1_left_ref_idx_ctxt_inc[2] + - 1); + pi1_left_ref_idx_ctxt_inc[2] = (pi1_left_ref_idx_ctxt_inc[2] + - 1); + + pi1_left_ref_idx_ctxt_inc[1] = (pi1_left_ref_idx_ctxt_inc[0] + - 1); + pi1_left_ref_idx_ctxt_inc[0] = (pi1_left_ref_idx_ctxt_inc[0] + - 1); + } + else + { + u1_cbp_t = ps_leftMB->u1_cbp; + u1_cbp_t = (u1_cbp_t & 0x08); + ps_tempLeft->u1_cbp = (u1_cbp_t | (u1_cbp_t >> 2)); + } + + { + UWORD8 i; + for(i = 0; i < 4; i++) + { + pu1_left_mv_ctxt_inc_2d[i][1] <<= 1; + pu1_left_mv_ctxt_inc_2d[i][3] <<= 1; + } + } + + } + + ps_tempLeft->u1_cbp = ps_tempLeft->u1_cbp + ((u1_cr_cpb >> 4) << 4); + ps_leftMB = ps_tempLeft; + } + + ps_dec->p_left_ctxt_mb_info = ps_leftMB; + } + else + { + ps_dec->p_left_ctxt_mb_info = p_ctx_inc_mb_map - 1; + if(!u4_mbskip) + { + *(ps_dec->pu1_left_yuv_dc_csbp) = 0; + + MEMSET_16BYTES(&pu1_left_mv_ctxt_inc_2d[0][0], 0); + *(UWORD32 *)pi1_left_ref_idx_ctxt_inc = 0; + } + } + + /*************************************************************************/ + /* Now get the top context mb info */ + /*************************************************************************/ + { + UWORD8 (*u1_top_mv_ctxt_inc_arr_2d)[4] = + ps_dec->ps_curr_ctxt_mb_info->u1_mv; + WORD8 (*pi1_top_ref_idx_ctxt_inc) = + ps_dec->ps_curr_ctxt_mb_info->i1_ref_idx; + UWORD8 uc_topMbFldDecFlag = ps_cur_mb_info->ps_top_mb->u1_mb_fld; + + if(u1_mb_ngbr_availablity & TOP_MB_AVAILABLE_MASK) + { + if(ps_cur_mb_info->i1_offset) + ps_dec->p_top_ctxt_mb_info += 1; + + if(!u4_mbskip) + { + memcpy(u1_top_mv_ctxt_inc_arr_2d, + &ps_dec->p_top_ctxt_mb_info->u1_mv, 16); + memcpy(pi1_top_ref_idx_ctxt_inc, + &ps_dec->p_top_ctxt_mb_info->i1_ref_idx, 4); + if(uc_topMbFldDecFlag ^ u1_cur_mb_fld_flag) + { + UWORD8 i; + if(u1_cur_mb_fld_flag) + { + for(i = 0; i < 4; i++) + { + u1_top_mv_ctxt_inc_arr_2d[i][1] >>= 1; + u1_top_mv_ctxt_inc_arr_2d[i][3] >>= 1; + } + } + else + { + for(i = 0; i < 4; i++) + { + u1_top_mv_ctxt_inc_arr_2d[i][1] <<= 1; + u1_top_mv_ctxt_inc_arr_2d[i][3] <<= 1; + pi1_top_ref_idx_ctxt_inc[i] -= 1; + } + } + } + } + } + else + { + ps_dec->p_top_ctxt_mb_info = p_ctx_inc_mb_map - 1; + if(!u4_mbskip) + { + + MEMSET_16BYTES(&u1_top_mv_ctxt_inc_arr_2d[0][0], 0); + memset(pi1_top_ref_idx_ctxt_inc, 0, 4); + } + } + } + + return OK; +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_update_mbaff_left_nnz */ +/* */ +/* Description : This function updates the left luma and chroma nnz for */ +/* mbaff cases. */ +/* */ +/* Inputs : <What inputs does the function take?> */ +/* Globals : <Does it use any global variables?> */ +/* Processing : <Describe how the function operates - include algorithm */ +/* description> */ +/* Outputs : <What does the function produce?> */ +/* Returns : <What does the function return?> */ +/* */ +/* Issues : <List any issues or problems with this function> */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 13 07 2002 Ittiam Draft */ +/* */ +/*****************************************************************************/ +void ih264d_update_mbaff_left_nnz(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info) +{ + UWORD32 *pu4_buf; + UWORD8 *pu1_buf; + if(ps_cur_mb_info->u1_topmb) + { + pu1_buf = ps_dec->pu1_left_nnz_y; + pu4_buf = (UWORD32 *)pu1_buf; + ps_dec->u4_n_left_temp_y = *pu4_buf; + + pu1_buf = ps_dec->pu1_left_nnz_uv; + pu4_buf = (UWORD32 *)pu1_buf; + ps_dec->u4_n_left_temp_uv = *pu4_buf; + } + else + { + + ps_dec->u4_n_leftY[0] = ps_dec->u4_n_left_temp_y; + pu1_buf = ps_dec->pu1_left_nnz_y; + pu4_buf = (UWORD32 *)pu1_buf; + ps_dec->u4_n_leftY[1] = *pu4_buf; + ps_dec->u4_n_left_cr[0] = ps_dec->u4_n_left_temp_uv; + pu1_buf = ps_dec->pu1_left_nnz_uv; + pu4_buf = (UWORD32 *)pu1_buf; + ps_dec->u4_n_left_cr[1] = *pu4_buf; + + } +} + +/*! + ************************************************************************** + * \if Function name : ih264d_get_mbaff_neighbours \endif + * + * \brief + * Gets the neighbors for the current MB if it is of type MB-AFF + * frame. + * + * \return + * None + * + ************************************************************************** + */ +void ih264d_get_mbaff_neighbours(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info, + UWORD8 uc_curMbFldDecFlag) +{ + + mb_neigbour_params_t *ps_left_mb; + mb_neigbour_params_t *ps_top_mb; + mb_neigbour_params_t *ps_top_right_mb = NULL; + mb_neigbour_params_t *ps_curmb; + const UWORD8 u1_topmb = ps_cur_mb_info->u1_topmb; + const UWORD8 uc_botMb = 1 - u1_topmb; + const UWORD32 u4_mb_x = ps_cur_mb_info->u2_mbx; + + /* Current MbParams location in top row buffer */ + ps_curmb = ps_dec->ps_cur_mb_row + (u4_mb_x << 1) + uc_botMb; + ps_left_mb = ps_curmb - 2; + /* point to A if top else A+1 */ + if(uc_botMb && (ps_left_mb->u1_mb_fld != uc_curMbFldDecFlag)) + { + /* move from A + 1 to A */ + ps_left_mb--; + } + ps_cur_mb_info->i1_offset = 0; + if((uc_curMbFldDecFlag == 0) && uc_botMb) + { + mb_neigbour_params_t *ps_topleft_mb; + /* CurMbAddr - 1 */ + ps_top_mb = ps_curmb - 1; + + /* Mark Top right Not available */ + /* point to A */ + ps_topleft_mb = ps_curmb - 3; + + if(ps_topleft_mb->u1_mb_fld) + { + /* point to A + 1 */ + ps_topleft_mb++; + } + ps_cur_mb_info->u1_topleft_mb_fld = ps_topleft_mb->u1_mb_fld; + ps_cur_mb_info->u1_topleft_mbtype = ps_topleft_mb->u1_mb_type; + } + else + { + /* Top = B + 1 */ + ps_top_mb = ps_dec->ps_top_mb_row + (u4_mb_x << 1) + 1; + ps_top_right_mb = ps_top_mb + 2; + ps_cur_mb_info->i1_offset = 4; + /* TopRight = C + 1 */ + + /* TopLeft = D+1 */ + ps_cur_mb_info->u1_topleft_mb_fld = ps_dec->u1_topleft_mb_fld_bot; + ps_cur_mb_info->u1_topleft_mbtype = ps_dec->u1_topleft_mbtype_bot; + + if(uc_curMbFldDecFlag && u1_topmb) + { + if(ps_top_mb->u1_mb_fld) + { + /* MbAddrB */ + ps_top_mb--; + ps_cur_mb_info->i1_offset = 0; + } + /* If topright is field then point to C */ + ps_top_right_mb -= ps_top_right_mb->u1_mb_fld ? 1 : 0; + if(ps_cur_mb_info->u1_topleft_mb_fld) + { + /* TopLeft = D */ + ps_cur_mb_info->u1_topleft_mb_fld = ps_dec->u1_topleft_mb_fld; + ps_cur_mb_info->u1_topleft_mbtype = ps_dec->u1_topleft_mbtype; + } + } + } + if(u1_topmb) + { + /* Update the parameters of topleftmb*/ + ps_dec->u1_topleft_mb_fld = ps_top_mb->u1_mb_fld; + ps_dec->u1_topleft_mbtype = ps_top_mb->u1_mb_type; + /* Set invscan and dequantMatrixScan*/ + if(uc_curMbFldDecFlag) + { + ps_dec->pu1_inv_scan = (UWORD8 *)gau1_ih264d_inv_scan_fld; + } + else + { + ps_dec->pu1_inv_scan = (UWORD8 *)gau1_ih264d_inv_scan; + } + ps_dec->pu2_quant_scale_y = + gau2_ih264_iquant_scale_4x4[ps_dec->u1_qp_y_rem6]; + ps_dec->pu2_quant_scale_u = + gau2_ih264_iquant_scale_4x4[ps_dec->u1_qp_u_rem6]; + ps_dec->pu2_quant_scale_v = + gau2_ih264_iquant_scale_4x4[ps_dec->u1_qp_v_rem6]; + + } + else + { + /* Update the parameters of topleftmb*/ + mb_neigbour_params_t *ps_top_mb_temp = ps_dec->ps_top_mb_row + + (u4_mb_x << 1) + 1; + ps_dec->u1_topleft_mb_fld_bot = ps_top_mb_temp->u1_mb_fld; + ps_dec->u1_topleft_mbtype_bot = ps_top_mb_temp->u1_mb_type; + } + + ps_cur_mb_info->ps_left_mb = ps_left_mb; + ps_cur_mb_info->ps_top_mb = ps_top_mb; + ps_cur_mb_info->ps_top_right_mb = ps_top_right_mb; + ps_cur_mb_info->ps_curmb = ps_curmb; + ps_curmb->u1_mb_fld = uc_curMbFldDecFlag; + + { + /* Form Left NNZ */ + UWORD8 u1_is_left_mb_fld = ps_left_mb->u1_mb_fld; + UWORD8 *pu1_left_mb_pair_nnz_y = (UWORD8 *)&ps_dec->u4_n_leftY[0]; + UWORD8 *pu1_left_mb_pair_nnz_uv = (UWORD8 *)&ps_dec->u4_n_left_cr[0]; + UWORD8 *pu1_left_nnz_y = ps_dec->pu1_left_nnz_y; + UWORD8 *pu1_left_nnz_uv = ps_dec->pu1_left_nnz_uv; + + if(uc_curMbFldDecFlag == u1_is_left_mb_fld) + { + *(UWORD32 *)pu1_left_nnz_y = *(UWORD32 *)(pu1_left_mb_pair_nnz_y + + (uc_botMb << 2)); + *(UWORD32 *)pu1_left_nnz_uv = *(UWORD32 *)(pu1_left_mb_pair_nnz_uv + + (uc_botMb << 2)); + } + else if((uc_curMbFldDecFlag == 0) && u1_topmb && u1_is_left_mb_fld) + { + /* 0 0 1 1 of u4_n_leftY[0], 0 0 2 2 of u4_n_left_cr[0] */ + pu1_left_nnz_y[0] = pu1_left_nnz_y[1] = pu1_left_mb_pair_nnz_y[0]; + pu1_left_nnz_y[2] = pu1_left_nnz_y[3] = pu1_left_mb_pair_nnz_y[1]; + pu1_left_nnz_uv[0] = pu1_left_nnz_uv[1] = + pu1_left_mb_pair_nnz_uv[0]; + pu1_left_nnz_uv[2] = pu1_left_nnz_uv[3] = + pu1_left_mb_pair_nnz_uv[2]; + } + else if((uc_curMbFldDecFlag == 0) && uc_botMb && u1_is_left_mb_fld) + { + /* 2 2 3 3 of u4_n_leftY[0] , 1 1 3 3 of u4_n_left_cr[0] */ + pu1_left_nnz_y[0] = pu1_left_nnz_y[1] = pu1_left_mb_pair_nnz_y[2]; + pu1_left_nnz_y[2] = pu1_left_nnz_y[3] = pu1_left_mb_pair_nnz_y[3]; + pu1_left_nnz_uv[0] = pu1_left_nnz_uv[1] = + pu1_left_mb_pair_nnz_uv[1]; + pu1_left_nnz_uv[2] = pu1_left_nnz_uv[3] = + pu1_left_mb_pair_nnz_uv[3]; + } + else + { + /* 0 2 0 2 of u4_n_leftY[0], u4_n_leftY[1] */ + pu1_left_nnz_y[0] = pu1_left_mb_pair_nnz_y[0]; + pu1_left_nnz_y[1] = pu1_left_mb_pair_nnz_y[2]; + pu1_left_nnz_y[2] = pu1_left_mb_pair_nnz_y[4 + 0]; + pu1_left_nnz_y[3] = pu1_left_mb_pair_nnz_y[4 + 2]; + + /* 0 of u4_n_left_cr[0] and 0 u4_n_left_cr[1] + 2 of u4_n_left_cr[0] and 2 u4_n_left_cr[1] */ + pu1_left_nnz_uv[0] = pu1_left_mb_pair_nnz_uv[0]; + pu1_left_nnz_uv[1] = pu1_left_mb_pair_nnz_uv[4 + 0]; + pu1_left_nnz_uv[2] = pu1_left_mb_pair_nnz_uv[2]; + pu1_left_nnz_uv[3] = pu1_left_mb_pair_nnz_uv[4 + 2]; + } + } +} + +/* + ************************************************************************** + * \if Function name : ih264d_transfer_mb_group_data \endif + * + * \brief + * Transfer the Following things + * N-Mb DeblkParams Data ( To Ext DeblkParams Buffer ) + * N-Mb Recon Data ( To Ext Frame Buffer ) + * N-Mb Intrapredline Data ( Updated Internally) + * N-Mb MV Data ( To Ext MV Buffer ) + * N-Mb MVTop/TopRight Data ( To Int MV Top Scratch Buffers) + * + * \return + * None + * + ************************************************************************** + */ +void ih264d_transfer_mb_group_data(dec_struct_t * ps_dec, + const UWORD8 u1_num_mbs, + const UWORD8 u1_end_of_row, /* Cur n-Mb End of Row Flag */ + const UWORD8 u1_end_of_row_next /* Next n-Mb End of Row Flag */ + ) +{ + dec_mb_info_t *ps_cur_mb_info = ps_dec->ps_nmb_info; + tfr_ctxt_t *ps_trns_addr = &ps_dec->s_tran_addrecon; + UWORD16 u2_mb_y; + UWORD32 y_offset; + UWORD32 u4_frame_stride; + mb_neigbour_params_t *ps_temp; + const UWORD8 u1_mbaff = ps_dec->ps_cur_slice->u1_mbaff_frame_flag; + UNUSED(u1_end_of_row_next); + + ps_trns_addr->pu1_dest_y += ps_trns_addr->u4_inc_y[u1_end_of_row]; + ps_trns_addr->pu1_dest_u += ps_trns_addr->u4_inc_uv[u1_end_of_row]; + ps_trns_addr->pu1_dest_v += ps_trns_addr->u4_inc_uv[u1_end_of_row]; + + /* Swap top and current pointers */ + if(u1_end_of_row) + { + + if(ps_dec->u1_separate_parse) + { + u2_mb_y = ps_dec->i2_dec_thread_mb_y; + } + else + { + ps_temp = ps_dec->ps_cur_mb_row; + ps_dec->ps_cur_mb_row = ps_dec->ps_top_mb_row; + ps_dec->ps_top_mb_row = ps_temp; + + u2_mb_y = ps_dec->u2_mby + (1 + u1_mbaff); + } + + u4_frame_stride = ps_dec->u2_frm_wd_y + << ps_dec->ps_cur_slice->u1_field_pic_flag; + y_offset = (u2_mb_y * u4_frame_stride) << 4; + ps_trns_addr->pu1_dest_y = ps_dec->s_cur_pic.pu1_buf1 + y_offset; + + u4_frame_stride = ps_dec->u2_frm_wd_uv + << ps_dec->ps_cur_slice->u1_field_pic_flag; + y_offset = (u2_mb_y * u4_frame_stride) << 3; + ps_trns_addr->pu1_dest_u = ps_dec->s_cur_pic.pu1_buf2 + y_offset; + ps_trns_addr->pu1_dest_v = ps_dec->s_cur_pic.pu1_buf3 + y_offset; + + ps_trns_addr->pu1_mb_y = ps_trns_addr->pu1_dest_y; + ps_trns_addr->pu1_mb_u = ps_trns_addr->pu1_dest_u; + ps_trns_addr->pu1_mb_v = ps_trns_addr->pu1_dest_v; + } + + /* + * The Slice boundary is also a valid condition to transfer. So recalculate + * the Left increment, in case the number of MBs is lesser than the + * N MB value. u1_num_mbs will be equal to N of N MB if the entire N Mb is + * decoded. + */ + ps_dec->s_tran_addrecon.u2_mv_left_inc = ((u1_num_mbs >> u1_mbaff) - 1) + << (4 + u1_mbaff); + ps_dec->s_tran_addrecon.u2_mv_top_left_inc = (u1_num_mbs << 2) - 1 + - (u1_mbaff << 2); + + if(ps_dec->u1_separate_parse == 0) + { + /* reassign left MV and cur MV pointers */ + ps_dec->ps_mv_left = ps_dec->ps_mv_cur + + ps_dec->s_tran_addrecon.u2_mv_left_inc; + + ps_dec->ps_mv_cur += (u1_num_mbs << 4); + } + + /* Increment deblock parameters pointer in external memory */ + + if(ps_dec->u1_separate_parse == 0) + { + ps_dec->ps_deblk_mbn += u1_num_mbs; + } + +} + diff --git a/dependencies/ih264d/decoder/ih264d_mb_utils.h b/dependencies/ih264d/decoder/ih264d_mb_utils.h new file mode 100644 index 00000000..158319af --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_mb_utils.h @@ -0,0 +1,293 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +#ifndef _IH264D_MB_UTILS_H_ +#define _IH264D_MB_UTILS_H_ +/*! + ************************************************************************** + * \file ih264d_mb_utils.h + * + * \brief + * Contains declarations of the utility functions needed to decode MB + * + * \date + * 18/12/2002 + * + * \author AI + ************************************************************************** + */ +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264d_structs.h" + +/*--------------------------------------------------------------------*/ +/* Macros to get raster scan position of a block[8x8] / sub block[4x4]*/ +/*--------------------------------------------------------------------*/ + +#define GET_BLK_RASTER_POS_X(x) ((x & 0x01) << 1) +#define GET_BLK_RASTER_POS_Y(y) ((y >> 1) << 1) +#define GET_SUB_BLK_RASTER_POS_X(x) ((x & 0x01)) +#define GET_SUB_BLK_RASTER_POS_Y(y) ((y >> 1)) + +/*--------------------------------------------------------------------*/ +/* Masks used in decoding of Macroblock */ +/*--------------------------------------------------------------------*/ + +#define LEFT_MB_AVAILABLE_MASK 0x01 +#define TOP_LEFT_MB_AVAILABLE_MASK 0x02 +#define TOP_MB_AVAILABLE_MASK 0x04 +#define TOP_RIGHT_MB_AVAILABLE_MASK 0x08 + +#define TOP_RT_SUBBLOCK_MASK_MOD 0xFFF7 + +#define TOP_RIGHT_DEFAULT_AVAILABLE 0x5750 +#define TOP_RIGHT_TOPR_AVAILABLE 0x0008 +#define TOP_RIGHT_TOP_AVAILABLE 0x0007 + +#define TOP_LEFT_DEFAULT_AVAILABLE 0xEEE0 +#define TOP_LEFT_TOPL_AVAILABLE 0x0001 +#define TOP_LEFT_TOP_AVAILABLE 0x000E +#define TOP_LEFT_LEFT_AVAILABLE 0x1110 + +#define CHECK_MB_MAP(u4_mb_num, mb_map, u4_cond) \ +{ \ + UWORD32 u4_bit_number; \ + volatile UWORD8 *pu1_mb_flag; \ + \ + u4_bit_number = u4_mb_num & 0x07; \ + pu1_mb_flag = (UWORD8 *)mb_map + (u4_mb_num >> 3); \ + \ + u4_cond = CHECKBIT((*pu1_mb_flag), u4_bit_number); \ +} + +#define CHECK_MB_MAP_BYTE(u4_mb_num, mb_map, u4_cond) \ +{ \ + volatile UWORD8 *pu1_mb_flag; \ + \ + pu1_mb_flag = (UWORD8 *)mb_map + (u4_mb_num ); \ + \ + u4_cond = (*pu1_mb_flag); \ +} + +#define UPDATE_MB_MAP(u2_frm_wd_in_mbs, u2_mbx, u2_mby, mb_map, mb_count) \ +{ \ + UWORD32 u4_bit_number; \ + UWORD32 u4_mb_number; \ + \ + u4_mb_number = u2_frm_wd_in_mbs * (u2_mby >> u1_mbaff) + u2_mbx; \ + \ + u4_bit_number = u4_mb_number & 0x07; \ + /* \ + * In case of MbAff, update the mb_map only if the entire MB is done. We can check that \ + * by checking if Y is odd, implying that this is the second row in the MbAff MB \ + */ \ + SET_BIT(mb_map[u4_mb_number >> 3], u4_bit_number); \ + \ + if (1 == u1_mbaff) \ + { \ + /* \ + * If MBAFF u4_flag is set, set this MB and the MB just below this. \ + * So, add frame width to the MB number and set that bit. \ + */ \ + /* \ + u4_mb_number += u2_frm_wd_in_mbs; \ + \ + u4_bit_number = u4_mb_number & 0x07; \ + \ + SET_BIT(mb_map[u4_mb_number >> 3], u4_bit_number); \ + */ \ + } \ + \ + /*H264_DEC_DEBUG_PRINT("SETBIT: %d\n", u4_mb_number);*/ \ + mb_count++; \ +} + +#define UPDATE_MB_MAP_MBNUM(mb_map, u4_mb_number) \ +{ \ + UWORD32 u4_bit_number; \ + volatile UWORD8 *pu1_mb_flag; \ + \ + u4_bit_number = u4_mb_number & 0x07; \ + pu1_mb_flag = (UWORD8 *)mb_map + (u4_mb_number >> 3); \ + /* \ + * In case of MbAff, update the mb_map only if the entire MB is done. We can check that \ + * by checking if Y is odd, implying that this is the second row in the MbAff MB \ + */ \ + SET_BIT((*pu1_mb_flag), u4_bit_number); \ +} + +#define UPDATE_MB_MAP_MBNUM_BYTE(mb_map, u4_mb_number) \ +{ \ + volatile UWORD8 *pu1_mb_flag; \ + \ + pu1_mb_flag = (UWORD8 *)mb_map + (u4_mb_number); \ + /* \ + * In case of MbAff, update the mb_map only if the entire MB is done. We can check that \ + * by checking if Y is odd, implying that this is the second row in the MbAff MB \ + */ \ + (*pu1_mb_flag) = 1; \ +} + +#define UPDATE_SLICE_NUM_MAP(slice_map, u4_mb_number,u2_slice_num) \ +{ \ + volatile UWORD16 *pu2_slice_map; \ + \ + pu2_slice_map = (UWORD16 *)slice_map + (u4_mb_number); \ + (*pu2_slice_map) = u2_slice_num; \ +} + +#define GET_SLICE_NUM_MAP(slice_map, mb_number,u2_slice_num) \ +{ \ + volatile UWORD16 *pu2_slice_map; \ + \ + pu2_slice_map = (UWORD16 *)slice_map + (mb_number); \ + u2_slice_num = (*pu2_slice_map) ; \ +} + + +#define GET_XPOS_PRED(u1_out,pkd_info) \ +{ \ + WORD32 bit_field; \ + bit_field = pkd_info & 0x3; \ + u1_out = bit_field; \ +} + + +#define GET_YPOS_PRED(u1_out,pkd_info) \ +{ \ + WORD32 bit_field; \ + bit_field = pkd_info >> 2; \ + u1_out = bit_field & 0x3; \ +} + + + +#define GET_WIDTH_PRED(u1_out,pkd_info) \ +{ \ + WORD32 bit_field; \ + bit_field = pkd_info >> 4; \ + bit_field = (bit_field & 0x3) << 1 ; \ + u1_out = (bit_field == 0)?1:bit_field; \ + } + +#define GET_HEIGHT_PRED(u1_out,pkd_info) \ +{ \ + WORD32 bit_field; \ + bit_field = pkd_info >> 6; \ + bit_field = (bit_field & 0x3) << 1 ; \ + u1_out = (bit_field == 0)?1:bit_field; \ +} + +/*! + ************************************************************************** + * \brief Masks for elements present in the first column but not on the + * first row. + ************************************************************************** + */ +#define FIRST_COL_NOT_FIRST_ROW 0xFAFB +#define FIRST_ROW_MASK 0xFFCC +/*! + ************************************************************************** + * \brief Mask for elements presen in the first row but not in the + * last column. + ************************************************************************** + */ +#define FIRST_ROW_NOT_LAST_COL 0xFFEC +/*! + ************************************************************************** + * \brief Mask for elements presen in the first row but not in the + * first column. + ************************************************************************** + */ +#define FIRST_ROW_NOT_FIRST_COL 0xFFCD +/*! + ************************************************************************** + * \brief Masks for the top right subMB of a 4x4 block + ************************************************************************** + */ +#define TOP_RT_SUBBLOCK_MASK 0xFFDF +/*! + ************************************************************************** + * \brief Masks for the top left subMB of a 4x4 block + ************************************************************************** + */ +#define TOP_LT_SUBBLOCK_MASK 0xFFFE +/*! + ************************************************************************** + * \brief Indicates if a subMB has a top right subMB available + ************************************************************************** + */ +#define TOP_RT_SUBBLOCK_MB_MASK 0x5F4C + +#define FIRST_COL_MASK 0xFAFA + +/*--------------------------------------------------------------------*/ +/* Macros to calculate the current position of a MB wrt picture */ +/*--------------------------------------------------------------------*/ +#define MB_LUMA_PIC_OFFSET(mb_x,mb_y,frmWidthY) (((mb_y)*(frmWidthY) + (mb_x))<<4) +#define MB_CHROMA_PIC_OFFSET(mb_x,mb_y,frmWidthUV) (((mb_y)*(frmWidthUV) + (mb_x))<<3) + +/*--------------------------------------------------------------------*/ +/* Macros to calculate the current position of a MB wrt N[ Num coeff] Array */ +/*--------------------------------------------------------------------*/ +#define MB_PARAM_OFFSET(mb_x,mb_y,frmWidthInMbs,u1_mbaff,u1_topmb) \ + ((mb_x << u1_mbaff) + (1 - u1_topmb) + (mb_y * frmWidthInMbs)) + +UWORD32 ih264d_get_mb_info_cavlc_mbaff(dec_struct_t * ps_dec, + const UWORD16 ui16_curMbAddress, + dec_mb_info_t * ps_cur_mb_info, + UWORD32 u4_mbskip_run); +UWORD32 ih264d_get_mb_info_cavlc_nonmbaff(dec_struct_t * ps_dec, + const UWORD16 ui16_curMbAddress, + dec_mb_info_t * ps_cur_mb_info, + UWORD32 u4_mbskip_run); + +UWORD32 ih264d_get_mb_info_cabac_mbaff(dec_struct_t * ps_dec, + const UWORD16 ui16_curMbAddress, + dec_mb_info_t * ps_cur_mb_info, + UWORD32 u4_mbskip_run); + +UWORD32 ih264d_get_mb_info_cabac_nonmbaff(dec_struct_t * ps_dec, + const UWORD16 ui16_curMbAddress, + dec_mb_info_t * ps_cur_mb_info, + UWORD32 u4_mbskip_run); + +UWORD8 get_cabac_context_non_mbaff(dec_struct_t * ps_dec, UWORD16 u2_mbskip); + +UWORD32 ih264d_get_cabac_context_mbaff(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info, + UWORD32 u4_mbskip); + +WORD32 PutMbToFrame(dec_struct_t * ps_dec); +void ih264d_get_mbaff_neighbours(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info, + UWORD8 uc_curMbFldDecFlag); + +void ih264d_update_mbaff_left_nnz(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info); +void ih264d_transfer_mb_group_data(dec_struct_t * ps_dec, + const UWORD8 u1_num_mbs, + const UWORD8 u1_end_of_row, /* Cur n-Mb End of Row Flag */ + const UWORD8 u1_end_of_row_next /* Next n-Mb End of Row Flag */ + ); + +//void FillRandomData(UWORD8 *pu1_buf, WORD32 u4_bufSize); + +#endif /* _MB_UTILS_H_ */ diff --git a/dependencies/ih264d/decoder/ih264d_mem_request.h b/dependencies/ih264d/decoder/ih264d_mem_request.h new file mode 100644 index 00000000..88e334d5 --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_mem_request.h @@ -0,0 +1,82 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ + +#ifndef _IH264D_MEM_REQUEST_H_ +#define _IH264D_MEM_REQUEST_H_ +/*! + *************************************************************************** + * \file ih264d_mem_request.h + * + * \brief + * This file contains declarations and data structures of the API's which + * required to interact with Picture Buffer. + * + * + * \date + * 11/12/2002 + * + * \author NS + ***************************************************************************/ +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264d_defs.h" +#include "ih264d_structs.h" + +#define MAX_MEM_BLOCKS 64 + 8 + +struct MemBlock +{ + void ** v_memLocation; /** memory location where address of allocated memory should be stored*/ + UWORD32 u4_mem_size; /** Size of the memory block */ +}; + +struct MemReq +{ + UWORD32 u4_num_memBlocks; /** Number of memory blocks */ + struct MemBlock s_memBlock[MAX_MEM_BLOCKS]; /** Pointer to the first memory block */ +}; + +struct PicMemBlock +{ + void * buf1; /** memory location for buf1 */ + void * buf2; /** memory location for buf2 */ + void * buf3; /** memory location for buf3 */ +}; + +struct PicMemReq +{ + WORD32 i4_num_pic_memBlocks; /** Number of memory blocks */ + UWORD32 u4_size1; /** Size of the buf1 in PicMemBlock */ + UWORD32 u4_size2; /** Size of the buf2 in PicMemBlock */ + UWORD32 u4_size3; /** Size of the buf3 in PicMemBlock */ + struct PicMemBlock s_PicMemBlock[MAX_DISP_BUFS_NEW]; +}; + +WORD32 ih264d_create_pic_buffers(UWORD8 u1_num_of_buf, + dec_struct_t *ps_dec); + +WORD32 ih264d_create_mv_bank(void * pv_codec_handle, + UWORD32 u4_wd, + UWORD32 u4_ht); +WORD16 ih264d_allocate_dynamic_bufs(dec_struct_t * ps_dec); + + +#endif /* _IH264D_MEM_REQUEST_H_ */ diff --git a/dependencies/ih264d/decoder/ih264d_mvpred.c b/dependencies/ih264d/decoder/ih264d_mvpred.c new file mode 100644 index 00000000..fb4932f2 --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_mvpred.c @@ -0,0 +1,1193 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/*! + ************************************************************************** + * \file ih264d_mvpred.c + * + * \brief + * This file contains function specific to decoding Motion vector. + * + * Detailed_description + * + * \date + * 10-12-2002 + * + * \author Arvind Raman + ************************************************************************** + */ +#include <string.h> +#include "ih264d_parse_cavlc.h" +#include "ih264d_error_handler.h" +#include "ih264d_structs.h" +#include "ih264d_defs.h" +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264d_mb_utils.h" +#include "ih264d_defs.h" +#include "ih264d_debug.h" +#include "ih264d_tables.h" +#include "ih264d_process_bslice.h" +#include "ih264d_mvpred.h" +#include "ih264d_inter_pred.h" +#include "ih264d_tables.h" + +/*! + ************************************************************************** + * \if ih264d_get_motion_vector_predictor name : Name \endif + * + * \brief + * The routine calculates the motion vector predictor for a given block, + * given the candidate MV predictors. + * + * \param ps_mv_pred: Candidate predictors for the current block + * \param ps_currMv: Pointer to the left top edge of the current block in + * the MV bank + * + * \return + * _mvPred: The x & y components of the MV predictor. + * + * \note + * The code implements the logic as described in sec 8.4.1.2.1. Given + * the candidate predictors and the pointer to the top left edge of the + * block in the MV bank. + * + ************************************************************************** + */ + +void ih264d_get_motion_vector_predictor(mv_pred_t * ps_result, + mv_pred_t **ps_mv_pred, + UWORD8 u1_ref_idx, + UWORD8 u1_B, + const UWORD8 *pu1_mv_pred_condition) +{ + WORD8 c_temp; + UWORD8 uc_B2 = (u1_B << 1); + + /* If only one of the candidate blocks has a reference frame equal to + the current block then use the same block as the final predictor */ + c_temp = + (ps_mv_pred[LEFT]->i1_ref_frame[u1_B] == u1_ref_idx) + | ((ps_mv_pred[TOP]->i1_ref_frame[u1_B] + == u1_ref_idx) << 1) + | ((ps_mv_pred[TOP_R]->i1_ref_frame[u1_B] + == u1_ref_idx) << 2); + c_temp = pu1_mv_pred_condition[c_temp]; + + if(c_temp != -1) + { + /* Case when only when one of the cadidate block has the same + reference frame as the current block */ + ps_result->i2_mv[uc_B2 + 0] = ps_mv_pred[c_temp]->i2_mv[uc_B2 + 0]; + ps_result->i2_mv[uc_B2 + 1] = ps_mv_pred[c_temp]->i2_mv[uc_B2 + 1]; + } + else + { + WORD32 D0, D1; + D0 = MIN(ps_mv_pred[0]->i2_mv[uc_B2 + 0], + ps_mv_pred[1]->i2_mv[uc_B2 + 0]); + D1 = MAX(ps_mv_pred[0]->i2_mv[uc_B2 + 0], + ps_mv_pred[1]->i2_mv[uc_B2 + 0]); + D1 = MIN(D1, ps_mv_pred[2]->i2_mv[uc_B2 + 0]); + ps_result->i2_mv[uc_B2 + 0] = (WORD16)(MAX(D0, D1)); + + D0 = MIN(ps_mv_pred[0]->i2_mv[uc_B2 + 1], + ps_mv_pred[1]->i2_mv[uc_B2 + 1]); + D1 = MAX(ps_mv_pred[0]->i2_mv[uc_B2 + 1], + ps_mv_pred[1]->i2_mv[uc_B2 + 1]); + D1 = MIN(D1, ps_mv_pred[2]->i2_mv[uc_B2 + 1]); + ps_result->i2_mv[uc_B2 + 1] = (WORD16)(MAX(D0, D1)); + + } +} + +/*! + ************************************************************************** + * \if ih264d_mbaff_mv_pred name : Name \endif + * + * \brief + * The routine calculates the motion vector predictor for a given block, + * given the candidate MV predictors. + * + * \param ps_mv_pred: Candidate predictors for the current block + * \param ps_currMv: Pointer to the left top edge of the current block in + * the MV bank + * + * \return + * _mvPred: The x & y components of the MV predictor. + * + * \note + * The code implements the logic as described in sec 8.4.1.2.1. Given + * the candidate predictors and the pointer to the top left edge of the + * block in the MV bank. + * + ************************************************************************** + */ + +void ih264d_mbaff_mv_pred(mv_pred_t **ps_mv_pred, + UWORD8 u1_sub_mb_num, + mv_pred_t *ps_mv_nmb, + mv_pred_t *ps_mv_ntop, + dec_struct_t *ps_dec, + UWORD8 uc_mb_part_width, + dec_mb_info_t *ps_cur_mb_info, + UWORD8* pu0_scale) +{ + UWORD16 u2_a_in = 0, u2_b_in = 0, u2_c_in = 0, u2_d_in = 0; + mv_pred_t *ps_mvpred_l, *ps_mvpred_tmp; + UWORD8 u1_sub_mb_x = (u1_sub_mb_num & 3), uc_sub_mb_y = (u1_sub_mb_num >> 2); + UWORD8 u1_is_cur_mb_fld, u1_is_left_mb_fld, u1_is_top_mb_fld; + UWORD8 u1_is_cur_mb_top; + + u1_is_cur_mb_fld = ps_cur_mb_info->u1_mb_field_decodingflag; + u1_is_cur_mb_top = ps_cur_mb_info->u1_topmb; + + u1_is_left_mb_fld = ps_cur_mb_info->ps_left_mb->u1_mb_fld; + u1_is_top_mb_fld = ps_cur_mb_info->ps_top_mb->u1_mb_fld; + + /* Checking in the subMB exists, calculating their motion vectors to be + used as predictors and the reference frames of those subMBs */ + ps_mv_pred[LEFT] = &ps_dec->s_default_mv_pred; + ps_mv_pred[TOP] = &(ps_dec->s_default_mv_pred); + ps_mv_pred[TOP_R] = &(ps_dec->s_default_mv_pred); + + /* Check if the left subMb is available */ + if(u1_sub_mb_x) + { + u2_a_in = 1; + ps_mv_pred[LEFT] = (ps_mv_nmb - 1); + } + else + { + UWORD8 uc_temp; + u2_a_in = (ps_cur_mb_info->u1_mb_ngbr_availablity & LEFT_MB_AVAILABLE_MASK); + if(u2_a_in) + { + ps_mvpred_l = (ps_dec->u4_num_pmbair) ? + ps_mv_nmb : + (ps_dec->ps_mv_left + (uc_sub_mb_y << 2) + 48 + - (u1_is_cur_mb_top << 4)); + uc_temp = 29; + if(u1_is_cur_mb_fld ^ u1_is_left_mb_fld) + { + if(u1_is_left_mb_fld) + { + uc_temp += + (((uc_sub_mb_y & 1) << 2) + + ((uc_sub_mb_y & 2) << 1)); + uc_temp += ((u1_is_cur_mb_top) ? 0 : 8); + } + else + { + uc_temp = uc_temp - (uc_sub_mb_y << 2); + uc_temp += ((u1_is_cur_mb_top) ? 0 : 16); + } + } + ps_mv_pred[LEFT] = (ps_mvpred_l - uc_temp); + pu0_scale[LEFT] = u1_is_cur_mb_fld - u1_is_left_mb_fld; + } + } + + /* Check if the top subMB is available */ + if((uc_sub_mb_y > 0) || ((u1_is_cur_mb_top | u1_is_cur_mb_fld) == 0)) + { + u2_b_in = 1; + ps_mv_pred[TOP] = ps_mv_nmb - 4; + } + else + { + u2_b_in = (ps_cur_mb_info->u1_mb_ngbr_availablity & TOP_MB_AVAILABLE_MASK); + if(u2_b_in) + { + /* CHANGED CODE */ + + if(u1_is_top_mb_fld && u1_is_cur_mb_fld) + ps_mvpred_tmp = ps_mv_ntop; + else + { + ps_mvpred_tmp = ps_mv_ntop; + if(u1_is_cur_mb_top) + ps_mvpred_tmp += 16; + } + + ps_mv_pred[TOP] = ps_mvpred_tmp; + pu0_scale[TOP] = u1_is_cur_mb_fld - u1_is_top_mb_fld; + } + } + + /* Check if the top right subMb is available. The top right subMb is + defined as the top right subMb at the top right corner of the MB + partition. The top right subMb index starting from the top left + corner of the MB partition is given by + TopRightSubMbIndx = TopLeftSubMbIndx + (WidthOfMbPartition - 6) / 2 + */ + u2_c_in = CHECKBIT(ps_cur_mb_info->u2_top_right_avail_mask, + (u1_sub_mb_num + uc_mb_part_width - 1)); + if(u2_c_in) + { + ps_mv_pred[TOP_R] = ps_mv_pred[TOP] + uc_mb_part_width; + pu0_scale[TOP_R] = pu0_scale[TOP]; + if((uc_sub_mb_y == 0) && ((u1_sub_mb_x + uc_mb_part_width) > 3)) + { + UWORD8 uc_isTopRtMbFld; + uc_isTopRtMbFld = ps_cur_mb_info->ps_top_right_mb->u1_mb_fld; + /* CHANGED CODE */ + ps_mvpred_tmp = ps_mv_ntop + uc_mb_part_width + 12; + ps_mvpred_tmp += (u1_is_cur_mb_top) ? 16 : 0; + ps_mvpred_tmp += (u1_is_cur_mb_fld && u1_is_cur_mb_top && uc_isTopRtMbFld) ? + 0 : 16; + ps_mv_pred[TOP_R] = ps_mvpred_tmp; + pu0_scale[TOP_R] = u1_is_cur_mb_fld - uc_isTopRtMbFld; + } + } + else + { + u2_d_in = CHECKBIT(ps_cur_mb_info->u2_top_left_avail_mask, u1_sub_mb_num); + + /* Check if the the top left subMB is available */ + if(u2_d_in) + { + UWORD8 uc_isTopLtMbFld; + + ps_mv_pred[TOP_R] = ps_mv_pred[TOP] - 1; + pu0_scale[TOP_R] = pu0_scale[TOP]; + + if(u1_sub_mb_x == 0) + { + if((uc_sub_mb_y > 0) || ((u1_is_cur_mb_top | u1_is_cur_mb_fld) == 0)) + { + uc_isTopLtMbFld = u1_is_left_mb_fld; + ps_mvpred_tmp = ps_mv_pred[LEFT] - 4; + + if((u1_is_cur_mb_fld == 0) && uc_isTopLtMbFld) + { + ps_mvpred_tmp = ps_mv_pred[LEFT] + 16; + ps_mvpred_tmp -= (uc_sub_mb_y & 1) ? 0 : 4; + } + } + else + { + UWORD32 u4_cond = ps_dec->u4_num_pmbair; + uc_isTopLtMbFld = ps_cur_mb_info->u1_topleft_mb_fld; + + /* CHANGED CODE */ + ps_mvpred_tmp = ps_mv_ntop - 29; + ps_mvpred_tmp += (u1_is_cur_mb_top) ? 16 : 0; + if(u1_is_cur_mb_fld && u1_is_cur_mb_top) + ps_mvpred_tmp -= (uc_isTopLtMbFld) ? 16 : 0; + } + ps_mv_pred[TOP_R] = ps_mvpred_tmp; + pu0_scale[TOP_R] = u1_is_cur_mb_fld - uc_isTopLtMbFld; + } + } + else if(u2_b_in == 0) + { + /* If all the subMBs B, C, D are all out of the frame then their MV + and their reference picture is equal to that of A */ + ps_mv_pred[TOP] = ps_mv_pred[LEFT]; + ps_mv_pred[TOP_R] = ps_mv_pred[LEFT]; + pu0_scale[TOP] = pu0_scale[LEFT]; + pu0_scale[TOP_R] = pu0_scale[LEFT]; + } + } +} + +/*! + ************************************************************************** + * \if ih264d_non_mbaff_mv_pred name : Name \endif + * + * \brief + * The routine calculates the motion vector predictor for a given block, + * given the candidate MV predictors. + * + * \param ps_mv_pred: Candidate predictors for the current block + * \param ps_currMv: Pointer to the left top edge of the current block in + * the MV bank + * + * \return + * _mvPred: The x & y components of the MV predictor. + * + * \note + * The code implements the logic as described in sec 8.4.1.2.1. Given + * the candidate predictors and the pointer to the top left edge of the + * block in the MV bank. + * + ************************************************************************** + */ +#if(!MVPRED_NONMBAFF) +void ih264d_non_mbaff_mv_pred(mv_pred_t **ps_mv_pred, + UWORD8 u1_sub_mb_num, + mv_pred_t *ps_mv_nmb, + mv_pred_t *ps_mv_ntop, + dec_struct_t *ps_dec, + UWORD8 uc_mb_part_width, + dec_mb_info_t *ps_cur_mb_info) +{ + UWORD16 u2_b_in = 0, u2_c_in = 0, u2_d_in = 0; + UWORD8 u1_sub_mb_x = (u1_sub_mb_num & 3), uc_sub_mb_y = (u1_sub_mb_num >> 2); + + /* Checking in the subMB exists, calculating their motion vectors to be + used as predictors and the reference frames of those subMBs */ + + ps_mv_pred[LEFT] = &ps_dec->s_default_mv_pred; + ps_mv_pred[TOP] = &(ps_dec->s_default_mv_pred); + ps_mv_pred[TOP_R] = &(ps_dec->s_default_mv_pred); + /* Check if the left subMb is available */ + + if(u1_sub_mb_x) + { + ps_mv_pred[LEFT] = (ps_mv_nmb - 1); + } + else + { + if(ps_cur_mb_info->u1_mb_ngbr_availablity & LEFT_MB_AVAILABLE_MASK) + { + ps_mv_pred[LEFT] = (ps_mv_nmb - 13); + } + } + + /* Check if the top subMB is available */ + if(uc_sub_mb_y) + { + u2_b_in = 1; + ps_mv_ntop = ps_mv_nmb - 4; + ps_mv_pred[TOP] = ps_mv_ntop; + + } + else + { + u2_b_in = (ps_cur_mb_info->u1_mb_ngbr_availablity & TOP_MB_AVAILABLE_MASK); + if(u2_b_in) + { + ps_mv_pred[TOP] = ps_mv_ntop; + } + } + + /* Check if the top right subMb is available. The top right subMb is + defined as the top right subMb at the top right corner of the MB + partition. The top right subMb index starting from the top left + corner of the MB partition is given by + TopRightSubMbIndx = TopLeftSubMbIndx + (WidthOfMbPartition - 6) / 2 + */ + u2_c_in = CHECKBIT(ps_cur_mb_info->u2_top_right_avail_mask, + (u1_sub_mb_num + uc_mb_part_width - 1)); + if(u2_c_in) + { + ps_mv_pred[TOP_R] = (ps_mv_ntop + uc_mb_part_width); + + if(uc_sub_mb_y == 0) + { + /* CHANGED CODE */ + if((u1_sub_mb_x + uc_mb_part_width) > 3) + ps_mv_pred[TOP_R] += 12; + } + } + else + { + u2_d_in = CHECKBIT(ps_cur_mb_info->u2_top_left_avail_mask, u1_sub_mb_num); + /* Check if the the top left subMB is available */ + if(u2_d_in) + { + /* CHANGED CODE */ + ps_mv_pred[TOP_R] = (ps_mv_ntop - 1); + if(u1_sub_mb_x == 0) + { + if(uc_sub_mb_y) + { + ps_mv_pred[TOP_R] = (ps_mv_nmb - 17); + } + else + { + /* CHANGED CODE */ + ps_mv_pred[TOP_R] -= 12; + } + } + } + else if(u2_b_in == 0) + { + /* If all the subMBs B, C, D are all out of the frame then their MV + and their reference picture is equal to that of A */ + ps_mv_pred[TOP] = ps_mv_pred[LEFT]; + ps_mv_pred[TOP_R] = ps_mv_pred[LEFT]; + } + } +} +#endif + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_mvpred_nonmbaffB */ +/* */ +/* Description : This function calculates the motion vector predictor, */ +/* for B-Slices */ +/* Inputs : <What inputs does the function take?> */ +/* Globals : None */ +/* Processing : The neighbours A(Left),B(Top),C(TopRight) are calculated */ +/* and based on the type of Mb the prediction is */ +/* appropriately done */ +/* Outputs : populates ps_mv_final_pred structure */ +/* Returns : u1_direct_zero_pred_flag which is used only in */ +/* decodeSpatialdirect() */ +/* */ +/* Issues : <List any issues or problems with this function> */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 03 05 2005 TA First Draft */ +/* */ +/*****************************************************************************/ +#if(!MVPRED_NONMBAFF) +UWORD8 ih264d_mvpred_nonmbaffB(dec_struct_t *ps_dec, + dec_mb_info_t *ps_cur_mb_info, + mv_pred_t *ps_mv_nmb, + mv_pred_t *ps_mv_ntop, + mv_pred_t *ps_mv_final_pred, + UWORD8 u1_sub_mb_num, + UWORD8 uc_mb_part_width, + UWORD8 u1_lx_start, + UWORD8 u1_lxend, + UWORD8 u1_mb_mc_mode) +{ + UWORD8 u1_a_in, u1_b_in, uc_temp1, uc_temp2, uc_temp3; + mv_pred_t *ps_mv_pred[3]; + UWORD8 uc_B2, uc_lx, u1_ref_idx; + UWORD8 u1_direct_zero_pred_flag = 0; + + ih264d_non_mbaff_mv_pred(ps_mv_pred, u1_sub_mb_num, ps_mv_nmb, ps_mv_ntop, + ps_dec, uc_mb_part_width, ps_cur_mb_info); + + for(uc_lx = u1_lx_start; uc_lx < u1_lxend; uc_lx++) + { + u1_ref_idx = ps_mv_final_pred->i1_ref_frame[uc_lx]; + uc_B2 = (uc_lx << 1); + switch(u1_mb_mc_mode) + { + case PRED_16x8: + /* Directional prediction for a 16x8 MB partition */ + if(u1_sub_mb_num == 0) + { + /* Calculating the MV pred for the top 16x8 block */ + if(ps_mv_pred[TOP]->i1_ref_frame[uc_lx] == u1_ref_idx) + { + /* If the reference frame used by the top subMB is same as the + reference frame used by the current block then MV predictor to + be used for the current block is same as the MV of the top + subMB */ + ps_mv_final_pred->i2_mv[uc_B2 + 0] = + ps_mv_pred[TOP]->i2_mv[uc_B2 + 0]; + ps_mv_final_pred->i2_mv[uc_B2 + 1] = + ps_mv_pred[TOP]->i2_mv[uc_B2 + 1]; + } + else + { + /* The MV predictor is calculated according to the process + defined in 8.4.1.2.1 */ + ih264d_get_motion_vector_predictor( + ps_mv_final_pred, + ps_mv_pred, + u1_ref_idx, + uc_lx, + (const UWORD8 *)gau1_ih264d_mv_pred_condition); + } + } + else + { + if(ps_mv_pred[LEFT]->i1_ref_frame[uc_lx] == u1_ref_idx) + { + /* If the reference frame used by the left subMB is same as the + reference frame used by the current block then MV predictor to + be used for the current block is same as the MV of the left + subMB */ + ps_mv_final_pred->i2_mv[uc_B2 + 0] = + ps_mv_pred[LEFT]->i2_mv[uc_B2 + 0]; + ps_mv_final_pred->i2_mv[uc_B2 + 1] = + ps_mv_pred[LEFT]->i2_mv[uc_B2 + 1]; + } + else + { + /* The MV predictor is calculated according to the process + defined in 8.4.1.2.1 */ + ih264d_get_motion_vector_predictor( + ps_mv_final_pred, + ps_mv_pred, + u1_ref_idx, + uc_lx, + (const UWORD8 *)gau1_ih264d_mv_pred_condition); + } + } + break; + case PRED_8x16: + /* Directional prediction for a 8x16 MB partition */ + if(u1_sub_mb_num == 0) + { + if(ps_mv_pred[LEFT]->i1_ref_frame[uc_lx] == u1_ref_idx) + { + /* If the reference frame used by the left subMB is same as the + reference frame used by the current block then MV predictor to + be used for the current block is same as the MV of the left + subMB */ + ps_mv_final_pred->i2_mv[uc_B2 + 0] = + ps_mv_pred[LEFT]->i2_mv[uc_B2 + 0]; + ps_mv_final_pred->i2_mv[uc_B2 + 1] = + ps_mv_pred[LEFT]->i2_mv[uc_B2 + 1]; + } + else + { + /* The MV predictor is calculated according to the process + defined in 8.4.1.2.1 */ + ih264d_get_motion_vector_predictor( + ps_mv_final_pred, + ps_mv_pred, + u1_ref_idx, + uc_lx, + (const UWORD8 *)gau1_ih264d_mv_pred_condition); + } + } + else + { + if(ps_mv_pred[TOP_R]->i1_ref_frame[uc_lx] == u1_ref_idx) + { + /* If the reference frame used by the top right subMB is same as + the reference frame used by the current block then MV + predictor to be used for the current block is same as the MV + of the left subMB */ + ps_mv_final_pred->i2_mv[uc_B2 + 0] = + ps_mv_pred[TOP_R]->i2_mv[uc_B2 + 0]; + ps_mv_final_pred->i2_mv[uc_B2 + 1] = + ps_mv_pred[TOP_R]->i2_mv[uc_B2 + 1]; + } + else + { + /* The MV predictor is calculated according to the process + defined in 8.4.1.2.1 */ + ih264d_get_motion_vector_predictor( + ps_mv_final_pred, + ps_mv_pred, + u1_ref_idx, + uc_lx, + (const UWORD8 *)gau1_ih264d_mv_pred_condition); + } + } + break; + case B_DIRECT_SPATIAL: + /* Case when the MB has been skipped */ + /* If either of left or the top subMB is not present + OR + If both the MV components of either the left or the top subMB are + zero and their reference frame pointer pointing to 0 + then MV for the skipped MB is zero + else the Median of the mv_pred_t is used */ + uc_temp1 = (UWORD8)ps_mv_pred[LEFT]->i1_ref_frame[0]; + uc_temp2 = (UWORD8)ps_mv_pred[TOP]->i1_ref_frame[0]; + uc_temp3 = (UWORD8)ps_mv_pred[TOP_R]->i1_ref_frame[0]; + + ps_mv_final_pred->i1_ref_frame[0] = MIN(uc_temp1, + MIN(uc_temp2, uc_temp3)); + + uc_temp1 = (UWORD8)ps_mv_pred[LEFT]->i1_ref_frame[1]; + uc_temp2 = (UWORD8)ps_mv_pred[TOP]->i1_ref_frame[1]; + uc_temp3 = (UWORD8)ps_mv_pred[TOP_R]->i1_ref_frame[1]; + + ps_mv_final_pred->i1_ref_frame[1] = MIN(uc_temp1, + MIN(uc_temp2, uc_temp3)); + + if((ps_mv_final_pred->i1_ref_frame[0] < 0) + && (ps_mv_final_pred->i1_ref_frame[1] < 0)) + { + u1_direct_zero_pred_flag = 1; + ps_mv_final_pred->i1_ref_frame[0] = 0; + ps_mv_final_pred->i1_ref_frame[1] = 0; + } + ih264d_get_motion_vector_predictor( + ps_mv_final_pred, ps_mv_pred, + ps_mv_final_pred->i1_ref_frame[0], 0, + (const UWORD8 *)gau1_ih264d_mv_pred_condition); + + ih264d_get_motion_vector_predictor( + ps_mv_final_pred, ps_mv_pred, + ps_mv_final_pred->i1_ref_frame[1], 1, + (const UWORD8 *)gau1_ih264d_mv_pred_condition); + + break; + case MB_SKIP: + /* Case when the MB has been skipped */ + /* If either of left or the top subMB is not present + OR + If both the MV components of either the left or the top subMB are + zero and their reference frame pointer pointing to 0 + then MV for the skipped MB is zero + else the Median of the mv_pred_t is used */ + u1_a_in = (ps_cur_mb_info->u1_mb_ngbr_availablity & + LEFT_MB_AVAILABLE_MASK); + u1_b_in = (ps_cur_mb_info->u1_mb_ngbr_availablity & + TOP_MB_AVAILABLE_MASK); + if(((u1_a_in * u1_b_in) == 0) + || ((ps_mv_pred[LEFT]->i2_mv[0] + | ps_mv_pred[LEFT]->i2_mv[1] + | ps_mv_pred[LEFT]->i1_ref_frame[0]) + == 0) + || ((ps_mv_pred[TOP]->i2_mv[0] + | ps_mv_pred[TOP]->i2_mv[1] + | ps_mv_pred[TOP]->i1_ref_frame[0]) + == 0)) + { + ps_mv_final_pred->i2_mv[0] = 0; + ps_mv_final_pred->i2_mv[1] = 0; + break; + } + /* If the condition above is not true calculate the MV predictor + according to the process defined in sec 8.4.1.2.1 */ + default: + ih264d_get_motion_vector_predictor( + ps_mv_final_pred, ps_mv_pred, u1_ref_idx, uc_lx, + (const UWORD8 *)gau1_ih264d_mv_pred_condition); + break; + } + } + return (u1_direct_zero_pred_flag); +} +#endif + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_mvpred_nonmbaff */ +/* */ +/* Description : This function calculates the motion vector predictor, */ +/* for all the slice types other than B_SLICE */ +/* Inputs : <What inputs does the function take?> */ +/* Globals : None */ +/* Processing : The neighbours A(Left),B(Top),C(TopRight) are calculated */ +/* and based on the type of Mb the prediction is */ +/* appropriately done */ +/* Outputs : populates ps_mv_final_pred structure */ +/* Returns : u1_direct_zero_pred_flag which is used only in */ +/* decodeSpatialdirect() */ +/* */ +/* Issues : <List any issues or problems with this function> */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 03 05 2005 TA First Draft */ +/* */ +/*****************************************************************************/ +#if(!MVPRED_NONMBAFF) +UWORD8 ih264d_mvpred_nonmbaff(dec_struct_t *ps_dec, + dec_mb_info_t *ps_cur_mb_info, + mv_pred_t *ps_mv_nmb, + mv_pred_t *ps_mv_ntop, + mv_pred_t *ps_mv_final_pred, + UWORD8 u1_sub_mb_num, + UWORD8 uc_mb_part_width, + UWORD8 u1_lx_start, + UWORD8 u1_lxend, + UWORD8 u1_mb_mc_mode) +{ + UWORD8 u1_a_in, u1_b_in, uc_temp1, uc_temp2, uc_temp3; + mv_pred_t *ps_mv_pred[3]; + UWORD8 u1_ref_idx; + UWORD8 u1_direct_zero_pred_flag = 0; + UNUSED(u1_lx_start); + UNUSED(u1_lxend); + ih264d_non_mbaff_mv_pred(ps_mv_pred, u1_sub_mb_num, ps_mv_nmb, ps_mv_ntop, + ps_dec, uc_mb_part_width, ps_cur_mb_info); + + u1_ref_idx = ps_mv_final_pred->i1_ref_frame[0]; + + switch(u1_mb_mc_mode) + { + case PRED_16x8: + /* Directional prediction for a 16x8 MB partition */ + if(u1_sub_mb_num == 0) + { + /* Calculating the MV pred for the top 16x8 block */ + if(ps_mv_pred[TOP]->i1_ref_frame[0] == u1_ref_idx) + { + /* If the reference frame used by the top subMB is same as the + reference frame used by the current block then MV predictor to + be used for the current block is same as the MV of the top + subMB */ + + ps_mv_final_pred->i2_mv[0] = ps_mv_pred[TOP]->i2_mv[0]; + ps_mv_final_pred->i2_mv[1] = ps_mv_pred[TOP]->i2_mv[1]; + } + else + { + /* The MV predictor is calculated according to the process + defined in 8.4.1.2.1 */ + ih264d_get_motion_vector_predictor( + ps_mv_final_pred, + ps_mv_pred, + u1_ref_idx, + 0, + (const UWORD8 *)gau1_ih264d_mv_pred_condition); + } + } + else + { + if(ps_mv_pred[LEFT]->i1_ref_frame[0] == u1_ref_idx) + { + /* If the reference frame used by the left subMB is same as the + reference frame used by the current block then MV predictor to + be used for the current block is same as the MV of the left + subMB */ + + ps_mv_final_pred->i2_mv[0] = ps_mv_pred[LEFT]->i2_mv[0]; + ps_mv_final_pred->i2_mv[1] = ps_mv_pred[LEFT]->i2_mv[1]; + } + else + { + /* The MV predictor is calculated according to the process + defined in 8.4.1.2.1 */ + ih264d_get_motion_vector_predictor( + ps_mv_final_pred, + ps_mv_pred, + u1_ref_idx, + 0, + (const UWORD8 *)gau1_ih264d_mv_pred_condition); + } + } + break; + case PRED_8x16: + /* Directional prediction for a 8x16 MB partition */ + if(u1_sub_mb_num == 0) + { + if(ps_mv_pred[LEFT]->i1_ref_frame[0] == u1_ref_idx) + { + /* If the reference frame used by the left subMB is same as the + reference frame used by the current block then MV predictor to + be used for the current block is same as the MV of the left + subMB */ + + ps_mv_final_pred->i2_mv[0] = ps_mv_pred[LEFT]->i2_mv[0]; + ps_mv_final_pred->i2_mv[1] = ps_mv_pred[LEFT]->i2_mv[1]; + } + else + { + /* The MV predictor is calculated according to the process + defined in 8.4.1.2.1 */ + ih264d_get_motion_vector_predictor( + ps_mv_final_pred, + ps_mv_pred, + u1_ref_idx, + 0, + (const UWORD8 *)gau1_ih264d_mv_pred_condition); + } + } + else + { + if(ps_mv_pred[TOP_R]->i1_ref_frame[0] == u1_ref_idx) + { + /* If the reference frame used by the top right subMB is same as + the reference frame used by the current block then MV + predictor to be used for the current block is same as the MV + of the left subMB */ + + ps_mv_final_pred->i2_mv[0] = ps_mv_pred[TOP_R]->i2_mv[0]; + ps_mv_final_pred->i2_mv[1] = ps_mv_pred[TOP_R]->i2_mv[1]; + } + else + { + /* The MV predictor is calculated according to the process + defined in 8.4.1.2.1 */ + ih264d_get_motion_vector_predictor( + ps_mv_final_pred, + ps_mv_pred, + u1_ref_idx, + 0, + (const UWORD8 *)gau1_ih264d_mv_pred_condition); + } + } + break; + case B_DIRECT_SPATIAL: + /* Case when the MB has been skipped */ + /* If either of left or the top subMB is not present + OR + If both the MV components of either the left or the top subMB are + zero and their reference frame pointer pointing to 0 + then MV for the skipped MB is zero + else the Median of the mv_pred_t is used */ + uc_temp1 = (UWORD8)ps_mv_pred[LEFT]->i1_ref_frame[0]; + uc_temp2 = (UWORD8)ps_mv_pred[TOP]->i1_ref_frame[0]; + uc_temp3 = (UWORD8)ps_mv_pred[TOP_R]->i1_ref_frame[0]; + + ps_mv_final_pred->i1_ref_frame[0] = MIN(uc_temp1, + MIN(uc_temp2, uc_temp3)); + + uc_temp1 = (UWORD8)ps_mv_pred[LEFT]->i1_ref_frame[1]; + uc_temp2 = (UWORD8)ps_mv_pred[TOP]->i1_ref_frame[1]; + uc_temp3 = (UWORD8)ps_mv_pred[TOP_R]->i1_ref_frame[1]; + + ps_mv_final_pred->i1_ref_frame[1] = MIN(uc_temp1, + MIN(uc_temp2, uc_temp3)); + + if((ps_mv_final_pred->i1_ref_frame[0] < 0) + && (ps_mv_final_pred->i1_ref_frame[1] < 0)) + { + u1_direct_zero_pred_flag = 1; + ps_mv_final_pred->i1_ref_frame[0] = 0; + ps_mv_final_pred->i1_ref_frame[1] = 0; + } + ih264d_get_motion_vector_predictor( + ps_mv_final_pred, ps_mv_pred, + ps_mv_final_pred->i1_ref_frame[0], 0, + (const UWORD8 *)gau1_ih264d_mv_pred_condition); + + ih264d_get_motion_vector_predictor( + ps_mv_final_pred, ps_mv_pred, + ps_mv_final_pred->i1_ref_frame[1], 1, + (const UWORD8 *)gau1_ih264d_mv_pred_condition); + + break; + case MB_SKIP: + /* Case when the MB has been skipped */ + /* If either of left or the top subMB is not present + OR + If both the MV components of either the left or the top subMB are + zero and their reference frame pointer pointing to 0 + then MV for the skipped MB is zero + else the Median of the mv_pred_t is used */ + u1_a_in = (ps_cur_mb_info->u1_mb_ngbr_availablity & + LEFT_MB_AVAILABLE_MASK); + u1_b_in = (ps_cur_mb_info->u1_mb_ngbr_availablity & + TOP_MB_AVAILABLE_MASK); + if(((u1_a_in * u1_b_in) == 0) + || ((ps_mv_pred[LEFT]->i2_mv[0] + | ps_mv_pred[LEFT]->i2_mv[1] + | ps_mv_pred[LEFT]->i1_ref_frame[0]) + == 0) + || ((ps_mv_pred[TOP]->i2_mv[0] + | ps_mv_pred[TOP]->i2_mv[1] + | ps_mv_pred[TOP]->i1_ref_frame[0]) + == 0)) + { + + ps_mv_final_pred->i2_mv[0] = 0; + ps_mv_final_pred->i2_mv[1] = 0; + break; + } + /* If the condition above is not true calculate the MV predictor + according to the process defined in sec 8.4.1.2.1 */ + default: + ih264d_get_motion_vector_predictor( + ps_mv_final_pred, ps_mv_pred, u1_ref_idx, 0, + (const UWORD8 *)gau1_ih264d_mv_pred_condition); + break; + } + + return (u1_direct_zero_pred_flag); +} +#endif + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_mvpred_mbaff */ +/* */ +/* Description : This function calculates the motion vector predictor, */ +/* Inputs : <What inputs does the function take?> */ +/* Globals : None */ +/* Processing : The neighbours A(Left),B(Top),C(TopRight) are calculated */ +/* and based on the type of Mb the prediction is */ +/* appropriately done */ +/* Outputs : populates ps_mv_final_pred structure */ +/* Returns : u1_direct_zero_pred_flag which is used only in */ +/* decodeSpatialdirect() */ +/* */ +/* Issues : <List any issues or problems with this function> */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 03 05 2005 TA First Draft */ +/* */ +/*****************************************************************************/ + +UWORD8 ih264d_mvpred_mbaff(dec_struct_t *ps_dec, + dec_mb_info_t *ps_cur_mb_info, + mv_pred_t *ps_mv_nmb, + mv_pred_t *ps_mv_ntop, + mv_pred_t *ps_mv_final_pred, + UWORD8 u1_sub_mb_num, + UWORD8 uc_mb_part_width, + UWORD8 u1_lx_start, + UWORD8 u1_lxend, + UWORD8 u1_mb_mc_mode) +{ + UWORD8 u1_a_in, u1_b_in, uc_temp1, uc_temp2, uc_temp3; + mv_pred_t *ps_mv_pred[3], s_mvPred[3]; + UWORD8 uc_B2, pu0_scale[3], i, uc_lx, u1_ref_idx; + UWORD8 u1_direct_zero_pred_flag = 0; + + pu0_scale[0] = pu0_scale[1] = pu0_scale[2] = 0; + ih264d_mbaff_mv_pred(ps_mv_pred, u1_sub_mb_num, ps_mv_nmb, ps_mv_ntop, ps_dec, + uc_mb_part_width, ps_cur_mb_info, pu0_scale); + for(i = 0; i < 3; i++) + { + if(pu0_scale[i] != 0) + { + memcpy(&s_mvPred[i], ps_mv_pred[i], sizeof(mv_pred_t)); + if(pu0_scale[i] == 1) + { + s_mvPred[i].i1_ref_frame[0] = s_mvPred[i].i1_ref_frame[0] << 1; + s_mvPred[i].i1_ref_frame[1] = s_mvPred[i].i1_ref_frame[1] << 1; + s_mvPred[i].i2_mv[1] = SIGN_POW2_DIV(s_mvPred[i].i2_mv[1], 1); + s_mvPred[i].i2_mv[3] = SIGN_POW2_DIV(s_mvPred[i].i2_mv[3], 1); + } + else + { + s_mvPred[i].i1_ref_frame[0] = s_mvPred[i].i1_ref_frame[0] >> 1; + s_mvPred[i].i1_ref_frame[1] = s_mvPred[i].i1_ref_frame[1] >> 1; + s_mvPred[i].i2_mv[1] = s_mvPred[i].i2_mv[1] << 1; + s_mvPred[i].i2_mv[3] = s_mvPred[i].i2_mv[3] << 1; + } + ps_mv_pred[i] = &s_mvPred[i]; + } + } + + for(uc_lx = u1_lx_start; uc_lx < u1_lxend; uc_lx++) + { + u1_ref_idx = ps_mv_final_pred->i1_ref_frame[uc_lx]; + uc_B2 = (uc_lx << 1); + switch(u1_mb_mc_mode) + { + case PRED_16x8: + /* Directional prediction for a 16x8 MB partition */ + if(u1_sub_mb_num == 0) + { + /* Calculating the MV pred for the top 16x8 block */ + if(ps_mv_pred[TOP]->i1_ref_frame[uc_lx] == u1_ref_idx) + { + /* If the reference frame used by the top subMB is same as the + reference frame used by the current block then MV predictor to + be used for the current block is same as the MV of the top + subMB */ + ps_mv_final_pred->i2_mv[uc_B2 + 0] = + ps_mv_pred[TOP]->i2_mv[uc_B2 + 0]; + ps_mv_final_pred->i2_mv[uc_B2 + 1] = + ps_mv_pred[TOP]->i2_mv[uc_B2 + 1]; + } + else + { + /* The MV predictor is calculated according to the process + defined in 8.4.1.2.1 */ + ih264d_get_motion_vector_predictor( + ps_mv_final_pred, + ps_mv_pred, + u1_ref_idx, + uc_lx, + (const UWORD8 *)gau1_ih264d_mv_pred_condition); + } + } + else + { + if(ps_mv_pred[LEFT]->i1_ref_frame[uc_lx] == u1_ref_idx) + { + /* If the reference frame used by the left subMB is same as the + reference frame used by the current block then MV predictor to + be used for the current block is same as the MV of the left + subMB */ + ps_mv_final_pred->i2_mv[uc_B2 + 0] = + ps_mv_pred[LEFT]->i2_mv[uc_B2 + 0]; + ps_mv_final_pred->i2_mv[uc_B2 + 1] = + ps_mv_pred[LEFT]->i2_mv[uc_B2 + 1]; + } + else + { + /* The MV predictor is calculated according to the process + defined in 8.4.1.2.1 */ + ih264d_get_motion_vector_predictor( + ps_mv_final_pred, + ps_mv_pred, + u1_ref_idx, + uc_lx, + (const UWORD8 *)gau1_ih264d_mv_pred_condition); + } + } + break; + case PRED_8x16: + /* Directional prediction for a 8x16 MB partition */ + if(u1_sub_mb_num == 0) + { + if(ps_mv_pred[LEFT]->i1_ref_frame[uc_lx] == u1_ref_idx) + { + /* If the reference frame used by the left subMB is same as the + reference frame used by the current block then MV predictor to + be used for the current block is same as the MV of the left + subMB */ + ps_mv_final_pred->i2_mv[uc_B2 + 0] = + ps_mv_pred[LEFT]->i2_mv[uc_B2 + 0]; + ps_mv_final_pred->i2_mv[uc_B2 + 1] = + ps_mv_pred[LEFT]->i2_mv[uc_B2 + 1]; + } + else + { + /* The MV predictor is calculated according to the process + defined in 8.4.1.2.1 */ + ih264d_get_motion_vector_predictor( + ps_mv_final_pred, + ps_mv_pred, + u1_ref_idx, + uc_lx, + (const UWORD8 *)gau1_ih264d_mv_pred_condition); + } + } + else + { + if(ps_mv_pred[TOP_R]->i1_ref_frame[uc_lx] == u1_ref_idx) + { + /* If the reference frame used by the top right subMB is same as + the reference frame used by the current block then MV + predictor to be used for the current block is same as the MV + of the left subMB */ + ps_mv_final_pred->i2_mv[uc_B2 + 0] = + ps_mv_pred[TOP_R]->i2_mv[uc_B2 + 0]; + ps_mv_final_pred->i2_mv[uc_B2 + 1] = + ps_mv_pred[TOP_R]->i2_mv[uc_B2 + 1]; + } + else + { + /* The MV predictor is calculated according to the process + defined in 8.4.1.2.1 */ + ih264d_get_motion_vector_predictor( + ps_mv_final_pred, + ps_mv_pred, + u1_ref_idx, + uc_lx, + (const UWORD8 *)gau1_ih264d_mv_pred_condition); + } + } + break; + case B_DIRECT_SPATIAL: + /* Case when the MB has been skipped */ + /* If either of left or the top subMB is not present + OR + If both the MV components of either the left or the top subMB are + zero and their reference frame pointer pointing to 0 + then MV for the skipped MB is zero + else the Median of the mv_pred_t is used */ + uc_temp1 = (UWORD8)ps_mv_pred[LEFT]->i1_ref_frame[0]; + uc_temp2 = (UWORD8)ps_mv_pred[TOP]->i1_ref_frame[0]; + uc_temp3 = (UWORD8)ps_mv_pred[TOP_R]->i1_ref_frame[0]; + + ps_mv_final_pred->i1_ref_frame[0] = MIN(uc_temp1, + MIN(uc_temp2, uc_temp3)); + + uc_temp1 = (UWORD8)ps_mv_pred[LEFT]->i1_ref_frame[1]; + uc_temp2 = (UWORD8)ps_mv_pred[TOP]->i1_ref_frame[1]; + uc_temp3 = (UWORD8)ps_mv_pred[TOP_R]->i1_ref_frame[1]; + + ps_mv_final_pred->i1_ref_frame[1] = MIN(uc_temp1, + MIN(uc_temp2, uc_temp3)); + + /* If the reference indices are negative clip the scaled reference indices to -1 */ + /* i.e invalid reference index */ + + /*if(ps_mv_final_pred->i1_ref_frame[0] < 0) + ps_mv_final_pred->i1_ref_frame[0] = -1; + + if(ps_mv_final_pred->i1_ref_frame[1] < 0) + ps_mv_final_pred->i1_ref_frame[1] = -1; */ + + if((ps_mv_final_pred->i1_ref_frame[0] < 0) + && (ps_mv_final_pred->i1_ref_frame[1] < 0)) + { + u1_direct_zero_pred_flag = 1; + ps_mv_final_pred->i1_ref_frame[0] = 0; + ps_mv_final_pred->i1_ref_frame[1] = 0; + } + ih264d_get_motion_vector_predictor( + ps_mv_final_pred, ps_mv_pred, + ps_mv_final_pred->i1_ref_frame[0], 0, + (const UWORD8 *)gau1_ih264d_mv_pred_condition); + + ih264d_get_motion_vector_predictor( + ps_mv_final_pred, ps_mv_pred, + ps_mv_final_pred->i1_ref_frame[1], 1, + (const UWORD8 *)gau1_ih264d_mv_pred_condition); + + break; + case MB_SKIP: + /* Case when the MB has been skipped */ + /* If either of left or the top subMB is not present + OR + If both the MV components of either the left or the top subMB are + zero and their reference frame pointer pointing to 0 + then MV for the skipped MB is zero + else the Median of the mv_pred_t is used */ + u1_a_in = (ps_cur_mb_info->u1_mb_ngbr_availablity & + LEFT_MB_AVAILABLE_MASK); + u1_b_in = (ps_cur_mb_info->u1_mb_ngbr_availablity & + TOP_MB_AVAILABLE_MASK); + if(((u1_a_in * u1_b_in) == 0) + || ((ps_mv_pred[LEFT]->i2_mv[0] + | ps_mv_pred[LEFT]->i2_mv[1] + | ps_mv_pred[LEFT]->i1_ref_frame[0]) + == 0) + || ((ps_mv_pred[TOP]->i2_mv[0] + | ps_mv_pred[TOP]->i2_mv[1] + | ps_mv_pred[TOP]->i1_ref_frame[0]) + == 0)) + { + ps_mv_final_pred->i2_mv[0] = 0; + ps_mv_final_pred->i2_mv[1] = 0; + break; + } + /* If the condition above is not true calculate the MV predictor + according to the process defined in sec 8.4.1.2.1 */ + default: + ih264d_get_motion_vector_predictor( + ps_mv_final_pred, ps_mv_pred, u1_ref_idx, uc_lx, + (const UWORD8 *)gau1_ih264d_mv_pred_condition); + break; + } + } + return (u1_direct_zero_pred_flag); +} + + + + +void ih264d_rep_mv_colz(dec_struct_t *ps_dec, + mv_pred_t *ps_mv_pred_src, + mv_pred_t *ps_mv_pred_dst, + UWORD8 u1_sub_mb_num, + UWORD8 u1_colz, + UWORD8 u1_ht, + UWORD8 u1_wd) +{ + + UWORD8 k, m; + UWORD8 *pu1_colz = ps_dec->pu1_col_zero_flag + ps_dec->i4_submb_ofst + + u1_sub_mb_num; + + for(k = 0; k < u1_ht; k++) + { + for(m = 0; m < u1_wd; m++) + { + *(ps_mv_pred_dst + m) = *(ps_mv_pred_src); + *(pu1_colz + m) = u1_colz; + + } + pu1_colz += SUB_BLK_WIDTH; + ps_mv_pred_dst += SUB_BLK_WIDTH; + } +} + diff --git a/dependencies/ih264d/decoder/ih264d_mvpred.h b/dependencies/ih264d/decoder/ih264d_mvpred.h new file mode 100644 index 00000000..66366ca1 --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_mvpred.h @@ -0,0 +1,153 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ + +#ifndef _IH264D_MVPRED_H_ +#define _IH264D_MVPRED_H_ + +/** +************************************************************************** +* \file ih264d_mvpred.h +* +* \brief +* This file contains declarations of functions specific to decoding +* Motion vector. +* +* Detailed_description +* +* \date +* 10-12-2002 +* +* \author Arvind Raman +************************************************************************** +*/ +#include "ih264d_structs.h" +#include "ih264d_defs.h" +//#include "structs.h" + +/** Reference number that is not valid */ +#define OUT_OF_RANGE_REF -1 + +#define ONE_TO_ONE 0 +#define FRM_TO_FLD 1 +#define FLD_TO_FRM 2 + +/** +************************************************************************** +* \brief POSITION_IN_MVBANK +* +* a: Pointer to the top left subMb of the MB in the MV bank array +* b: Horiz posn in terms of subMbs +* c: Vert posn in terms of subMbs +* d: subMb number +************************************************************************** +*/ +#define POSITION_IN_MVBANK(a, b, c, d) (a) + (c) * (d) + (b) + + + +/** +************************************************************************** +* \brief col4x4_t +* +* Container to return the information related to the co-located 4x4 +* sub-macroblock. +************************************************************************** +*/ +typedef struct +{ + mv_pred_t *ps_mv; /** Ptr to the Mv bank */ + UWORD16 u2_mb_addr_col; /** Addr of the co-located MB */ + WORD16 i2_mv[2]; /** Mv of the colocated MB */ + WORD8 i1_ref_idx_col; /** Ref idx of the co-located picture */ + UWORD8 u1_col_pic; /** Idx of the colocated pic */ + UWORD8 u1_yM; /** "y" coord of the colocated MB addr */ + UWORD8 u1_vert_mv_scale; /** as defined in sec 8.4.1.2.1 */ +} col4x4_t; + + + + + +void ih264d_update_nnz_for_skipmb(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info, + UWORD8 u1_entrpy); + +void ih264d_get_motion_vector_predictor(mv_pred_t * ps_result, + mv_pred_t **ps_mv_pred, + UWORD8 u1_ref_idx, + UWORD8 u1_B, + const UWORD8 *pu1_mv_pred_condition); +void ih264d_mbaff_mv_pred(mv_pred_t **ps_mv_pred, + UWORD8 u1_sub_mb_num, + mv_pred_t *ps_mv_nmb, + mv_pred_t *ps_mv_ntop, + dec_struct_t *ps_dec, + UWORD8 uc_mb_part_width, + dec_mb_info_t *ps_cur_mb_info, + UWORD8* pu0_scale); +void ih264d_non_mbaff_mv_pred(mv_pred_t **ps_mv_pred, + UWORD8 u1_sub_mb_num, + mv_pred_t *ps_mv_nmb, + mv_pred_t *ps_mv_ntop, + dec_struct_t *ps_dec, + UWORD8 uc_mb_part_width, + dec_mb_info_t *ps_cur_mb_info); +UWORD8 ih264d_mvpred_nonmbaff(dec_struct_t *ps_dec, + dec_mb_info_t *ps_cur_mb_info, + mv_pred_t *ps_mv_nmb, + mv_pred_t *ps_mv_ntop, + mv_pred_t *ps_mv_final_pred, + UWORD8 u1_sub_mb_num, + UWORD8 uc_mb_part_width, + UWORD8 u1_lx_start, + UWORD8 u1_lxend, + UWORD8 u1_mb_mc_mode); + +UWORD8 ih264d_mvpred_nonmbaffB(dec_struct_t *ps_dec, + dec_mb_info_t *ps_cur_mb_info, + mv_pred_t *ps_mv_nmb, + mv_pred_t *ps_mv_ntop, + mv_pred_t *ps_mv_final_pred, + UWORD8 u1_sub_mb_num, + UWORD8 uc_mb_part_width, + UWORD8 u1_lx_start, + UWORD8 u1_lxend, + UWORD8 u1_mb_mc_mode); + +UWORD8 ih264d_mvpred_mbaff(dec_struct_t *ps_dec, + dec_mb_info_t *ps_cur_mb_info, + mv_pred_t *ps_mv_nmb, + mv_pred_t *ps_mv_ntop, + mv_pred_t *ps_mv_final_pred, + UWORD8 u1_sub_mb_num, + UWORD8 uc_mb_part_width, + UWORD8 u1_lx_start, + UWORD8 u1_lxend, + UWORD8 u1_mb_mc_mode); + +void ih264d_rep_mv_colz(dec_struct_t *ps_dec, + mv_pred_t *ps_mv_pred_src, + mv_pred_t *ps_mv_pred_dst, + UWORD8 u1_sub_mb_num, + UWORD8 u1_colz, + UWORD8 u1_ht, + UWORD8 u1_wd); + +#endif /* _IH264D_MVPRED_H_ */ diff --git a/dependencies/ih264d/decoder/ih264d_nal.c b/dependencies/ih264d/decoder/ih264d_nal.c new file mode 100644 index 00000000..48450c89 --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_nal.c @@ -0,0 +1,393 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/*! + ************************************************************************** + * \file ih264d_nal.c + * + * \brief NAL parsing routines + * + * Detailed_description + * + * \author + * - AI 19 11 2002 Creation + ************************************************************************** + */ +#include "ih264d_bitstrm.h" +#include "ih264d_defs.h" +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264d_defs.h" +#define NUM_OF_ZERO_BYTES_BEFORE_START_CODE 2 +#define EMULATION_PREVENTION_BYTE 0x03 + +#define NAL_FIRST_BYTE_SIZE 1 + +/*! + ************************************************************************** + * \if Function name : ih264d_find_start_code \endif + * + * \brief + * This function searches for the Start Code Prefix. + * + * \param pu1_buf : Pointer to char buffer which contains bitstream. + * \param u4_cur_pos : Current position in the buffer. + * \param u4_max_ofst : Number of bytes in Buffer. + * \param pu4_length_of_start_code : Poiter to length of Start Code. + * + * \return + * Returns 0 on success and -1 on error. + * + ************************************************************************** + */ +#define START_CODE_NOT_FOUND -1 +#define END_OF_STREAM_BUFFER -2 +#define END_OF_STREAM -1 + +void ih264d_check_if_aud(UWORD8 *pu1_buf, + UWORD32 u4_cur_pos, + UWORD32 u4_max_ofst, + UWORD32 *pu4_next_is_aud) +{ + UWORD8 u1_first_byte, u1_nal_unit_type; + if(u4_cur_pos + 1 < u4_max_ofst) + { + u1_first_byte = pu1_buf[u4_cur_pos + 1]; + u1_nal_unit_type = NAL_UNIT_TYPE(u1_first_byte); + + if(u1_nal_unit_type == ACCESS_UNIT_DELIMITER_RBSP) + { + *pu4_next_is_aud = 1; + } + } + +} +WORD32 ih264d_find_start_code(UWORD8 *pu1_buf, + UWORD32 u4_cur_pos, + UWORD32 u4_max_ofst, + UWORD32 *pu4_length_of_start_code, + UWORD32 *pu4_next_is_aud) +{ + WORD32 zero_byte_cnt = 0; + UWORD32 ui_curPosTemp; + + *pu4_length_of_start_code = 0; + /*Find first start code */ + while(u4_cur_pos < u4_max_ofst) + { + if(pu1_buf[u4_cur_pos] == 0) + zero_byte_cnt++; + else if(pu1_buf[u4_cur_pos] + == 0x01 && zero_byte_cnt >= NUM_OF_ZERO_BYTES_BEFORE_START_CODE) + { + /* Found the start code */ + u4_cur_pos++; + break; + } + else + { + zero_byte_cnt = 0; + } + u4_cur_pos++; + } + /*Find Next Start Code */ + *pu4_length_of_start_code = u4_cur_pos; + zero_byte_cnt = 0; + ui_curPosTemp = u4_cur_pos; + while(u4_cur_pos < u4_max_ofst) + { + + if(pu1_buf[u4_cur_pos] == 0) + zero_byte_cnt++; + else if(pu1_buf[u4_cur_pos] + == 0x01 && zero_byte_cnt >= NUM_OF_ZERO_BYTES_BEFORE_START_CODE) + { + /* Found the start code */ + ih264d_check_if_aud(pu1_buf, u4_cur_pos, u4_max_ofst, + pu4_next_is_aud); + return (u4_cur_pos - zero_byte_cnt - ui_curPosTemp); + } + else + { + zero_byte_cnt = 0; + } + u4_cur_pos++; + } + + return (u4_cur_pos - zero_byte_cnt - ui_curPosTemp); //(START_CODE_NOT_FOUND); +} + +/*! + ************************************************************************** + * \if Function name : ih264d_get_next_nal_unit \endif + * + * \brief + * This function reads one NAl unit. + * + * \param ps_nalStream : Poiter to NalUnitStream structure. + * \param ps_nalUnit : Pointer to NalUnit. + * + * \return + * Returns 0 on success and -1 on error. + * + ************************************************************************** + */ +WORD32 ih264d_get_next_nal_unit(UWORD8 *pu1_buf, + UWORD32 u4_cur_pos, + UWORD32 u4_max_ofst, + UWORD32 *pu4_length_of_start_code) +{ + + WORD32 i_length_of_nal_unit = 0; + UWORD32 u4_next_is_aud; + + /* NAL Thread starts */ + + ih264d_find_start_code(pu1_buf, u4_cur_pos, u4_max_ofst, + pu4_length_of_start_code, &u4_next_is_aud); + + return (i_length_of_nal_unit); +} + +/*! + ************************************************************************** + * \if Function name : ih264d_process_nal_unit \endif + * + * \brief + * This function removes emulation byte "0x03" from bitstream (EBSP to RBSP). + * It also converts bytestream format into 32 bit little-endian format. + * + * \param ps_bitstrm : Poiter to dec_bit_stream_t structure. + * \param pu1_nal_unit : Pointer to char buffer of NalUnit. + * \param u4_numbytes_in_nal_unit : Number bytes in NalUnit buffer. + * + * \return + * Returns number of bytes in RBSP ps_bitstrm. + * + * \note + * This function is same as nal_unit() of 7.3.1. Apart from nal_unit() + * implementation it converts char buffer into 32 bit Buffer. This + * facilitates efficient access of bitstream. This has been done taking + * into account present processor architectures. + * + ************************************************************************** + */ +WORD32 ih264d_process_nal_unit(dec_bit_stream_t *ps_bitstrm, + UWORD8 *pu1_nal_unit, + UWORD32 u4_numbytes_in_nal_unit) +{ + UWORD32 u4_num_bytes_in_rbsp; + UWORD8 u1_cur_byte; + WORD32 i = 0; + WORD8 c_count; + UWORD32 ui_word; + UWORD32 *puc_bitstream_buffer = (UWORD32*)pu1_nal_unit; + ps_bitstrm->pu4_buffer = puc_bitstream_buffer; + + /*--------------------------------------------------------------------*/ + /* First Byte of the NAL Unit */ + /*--------------------------------------------------------------------*/ + + ui_word = *pu1_nal_unit++; + + /*--------------------------------------------------------------------*/ + /* Convertion of the EBSP to RBSP */ + /* ie Remove the emulation_prevention_byte [equal to 0x03] */ + /*--------------------------------------------------------------------*/ + u4_num_bytes_in_rbsp = 0; + c_count = 0; + +//first iteration + + u1_cur_byte = *pu1_nal_unit++; + + ui_word = ((ui_word << 8) | u1_cur_byte); + + c_count++; + if(u1_cur_byte != 0x00) + c_count = 0; + +//second iteration + + u1_cur_byte = *pu1_nal_unit++; + + ui_word = ((ui_word << 8) | u1_cur_byte); + u4_num_bytes_in_rbsp = 2; + + c_count++; + if(u1_cur_byte != 0x00) + c_count = 0; + + if(u4_numbytes_in_nal_unit > 2) + { + i = ((u4_numbytes_in_nal_unit - 3)); + } + + for(; i > 8; i -= 4) + { + +// loop 0 + u1_cur_byte = *pu1_nal_unit++; + + if(c_count == NUM_OF_ZERO_BYTES_BEFORE_START_CODE + && u1_cur_byte == EMULATION_PREVENTION_BYTE) + { + c_count = 0; + u1_cur_byte = *pu1_nal_unit++; + i--; + } + + ui_word = ((ui_word << 8) | u1_cur_byte); + *puc_bitstream_buffer = ui_word; + puc_bitstream_buffer++; + c_count++; + if(u1_cur_byte != 0x00) + c_count = 0; + +// loop 1 + u1_cur_byte = *pu1_nal_unit++; + + if(c_count == NUM_OF_ZERO_BYTES_BEFORE_START_CODE + && u1_cur_byte == EMULATION_PREVENTION_BYTE) + { + c_count = 0; + u1_cur_byte = *pu1_nal_unit++; + i--; + } + ui_word = ((ui_word << 8) | u1_cur_byte); + + c_count++; + if(u1_cur_byte != 0x00) + c_count = 0; + +// loop 2 + u1_cur_byte = *pu1_nal_unit++; + + if(c_count == NUM_OF_ZERO_BYTES_BEFORE_START_CODE + && u1_cur_byte == EMULATION_PREVENTION_BYTE) + { + c_count = 0; + u1_cur_byte = *pu1_nal_unit++; + i--; + } + + ui_word = ((ui_word << 8) | u1_cur_byte); + + c_count++; + if(u1_cur_byte != 0x00) + c_count = 0; + +// loop 3 + u1_cur_byte = *pu1_nal_unit++; + + if(c_count == NUM_OF_ZERO_BYTES_BEFORE_START_CODE + && u1_cur_byte == EMULATION_PREVENTION_BYTE) + { + c_count = 0; + u1_cur_byte = *pu1_nal_unit++; + i--; + } + + ui_word = ((ui_word << 8) | u1_cur_byte); + + c_count++; + if(u1_cur_byte != 0x00) + c_count = 0; + + u4_num_bytes_in_rbsp += 4; + + } + + for(; i > 0; i--) + { + u1_cur_byte = *pu1_nal_unit++; + + if(c_count == NUM_OF_ZERO_BYTES_BEFORE_START_CODE + && u1_cur_byte == EMULATION_PREVENTION_BYTE) + { + c_count = 0; + i--; + u1_cur_byte = *pu1_nal_unit++; + } + + ui_word = ((ui_word << 8) | u1_cur_byte); + u4_num_bytes_in_rbsp++; + + if((u4_num_bytes_in_rbsp & 0x03) == 0x03) + { + *puc_bitstream_buffer = ui_word; + puc_bitstream_buffer++; + } + c_count++; + if(u1_cur_byte != 0x00) + c_count = 0; + + } + + *puc_bitstream_buffer = (ui_word + << ((3 - (((u4_num_bytes_in_rbsp << 30) >> 30))) << 3)); + ps_bitstrm->u4_ofst = 0; + ps_bitstrm->u4_max_ofst = ((u4_num_bytes_in_rbsp + NAL_FIRST_BYTE_SIZE) << 3); + + return (u4_num_bytes_in_rbsp); +} + + +/*! + ************************************************************************** + * \if Function name : ih264d_rbsp_to_sodb \endif + * + * \brief + * This function converts RBSP to SODB. + * + * \param ps_bitstrm : Poiter to dec_bit_stream_t structure. + * + * \return + * None. + * + ************************************************************************** + */ +void ih264d_rbsp_to_sodb(dec_bit_stream_t *ps_bitstrm) +{ + UWORD32 ui_lastWord; + UWORD32 ui_word; + UWORD8 uc_lastByte; + WORD8 i; + + ui_lastWord = (ps_bitstrm->u4_max_ofst >> 5); + i = (ps_bitstrm->u4_max_ofst >> 3) & 0x03; + + if(i) + { + ui_word = ps_bitstrm->pu4_buffer[ui_lastWord]; + uc_lastByte = ((ui_word << ((i - 1) << 3)) >> 24); + } + else + { + ui_word = ps_bitstrm->pu4_buffer[ui_lastWord - 1]; + uc_lastByte = ((ui_word << 24) >> 24); + } + /*--------------------------------------------------------------------*/ + /* Find out the rbsp_stop_bit position in the last byte of rbsp */ + /*--------------------------------------------------------------------*/ + for(i = 0; (i < 8) && !CHECKBIT(uc_lastByte, i); ++i) + ; + ps_bitstrm->u4_max_ofst = ps_bitstrm->u4_max_ofst - (i + 1); +} diff --git a/dependencies/ih264d/decoder/ih264d_nal.h b/dependencies/ih264d/decoder/ih264d_nal.h new file mode 100644 index 00000000..3778881d --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_nal.h @@ -0,0 +1,56 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ + +#ifndef _IH264D_NAL_H_ +#define _IH264D_NAL_H_ + +/*! +************************************************************************* +* \file ih264d_nal.h +* +* \brief +* short_description +* +* Detailed_description +* +* \date +* 21/11/2002 +* +* \author AI +************************************************************************* +*/ +#include <stdio.h> +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264d_bitstrm.h" + +WORD32 ih264d_process_nal_unit(dec_bit_stream_t *ps_bitstrm, + UWORD8 *pu1_nal_unit, + UWORD32 u4_numbytes_in_nal_unit); +void ih264d_rbsp_to_sodb(dec_bit_stream_t *ps_bitstrm); +WORD32 ih264d_find_start_code(UWORD8 *pu1_buf, + UWORD32 u4_cur_pos, + UWORD32 u4_max_ofst, + UWORD32 *pu4_length_of_start_code, + UWORD32 *pu4_next_is_aud); + + +#endif /* _IH264D_NAL_H_ */ diff --git a/dependencies/ih264d/decoder/ih264d_parse_bslice.c b/dependencies/ih264d/decoder/ih264d_parse_bslice.c new file mode 100644 index 00000000..936963a7 --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_parse_bslice.c @@ -0,0 +1,1694 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/*! + ************************************************************************** + * \file ih264d_parse_bslice.c + * + * \brief + * Contains routines that decode a I slice type + * + * Detailed_description + * + * \date + * 07/07/2003 + * + * \author NS + ************************************************************************** + */ + +#include <string.h> +#include "ih264_defs.h" +#include "ih264d_bitstrm.h" +#include "ih264d_defs.h" +#include "ih264d_debug.h" +#include "ih264d_tables.h" +#include "ih264d_structs.h" +#include "ih264d_defs.h" +#include "ih264d_parse_cavlc.h" +#include "ih264d_mb_utils.h" +#include "ih264d_parse_slice.h" +#include "ih264d_process_intra_mb.h" +#include "ih264d_mvpred.h" +#include "ih264d_parse_islice.h" +#include "ih264d_inter_pred.h" +#include "ih264d_process_pslice.h" +#include "ih264d_process_bslice.h" +#include "ih264d_deblocking.h" +#include "ih264d_cabac.h" +#include "ih264d_parse_mb_header.h" +#include "ih264d_error_handler.h" +#include "ih264d_mvpred.h" +#include "ih264d_cabac.h" +#include "ih264d_utils.h" + +void ih264d_init_cabac_contexts(UWORD8 u1_slice_type, dec_struct_t * ps_dec); + +/*! + ************************************************************************** + * \if Function name : ParseMb_SubMb_PredBCav\endif + * + * \brief + * Implements sub_mb_pred() of 7.3.5.2. & mb_pred() of 7.3.5.1 + * + * \return + * None. + * + ************************************************************************** + */ +WORD32 ih264d_parse_bmb_non_direct_cavlc(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info, + UWORD8 u1_mb_num, + UWORD8 u1_num_mbsNby2) +{ + dec_bit_stream_t * ps_bitstrm = ps_dec->ps_bitstrm; + UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; + UWORD32 *pu4_bitstrm_ofst = &ps_bitstrm->u4_ofst; + UWORD8 * pu1_sub_mb_pred_modes = (UWORD8 *)(gau1_ih264d_submb_pred_modes) + 4; + const UWORD8 (*pu1_mb_pred_modes)[32] = + (const UWORD8 (*)[32])gau1_ih264d_mb_pred_modes; + const UWORD8 * pu1_num_mb_part = (const UWORD8 *)gau1_ih264d_num_mb_part; + const UWORD8 * pu1_sub_mb_mc_mode = (const UWORD8 *)(gau1_ih264d_submb_mc_mode) + + 4; + + parse_pmbarams_t * ps_parse_mb_data = ps_dec->ps_parse_mb_data + + u1_num_mbsNby2; + UWORD8 * pu1_col_info = ps_parse_mb_data->u1_col_info; + WORD8 (*pi1_ref_idx)[MAX_REFIDX_INFO_PER_MB] = ps_parse_mb_data->i1_ref_idx; + UWORD8 u1_mb_type = ps_cur_mb_info->u1_mb_type; + UWORD8 u1_mb_mc_mode, u1_num_mb_part, u1_sub_mb = !(u1_mb_type ^ B_8x8); + UWORD32 u4_mb_mc_mode = 0, u4_mb_pred_mode = 0; + WORD32 ret; + + if(u1_sub_mb) + { + UWORD8 uc_i; + u1_mb_mc_mode = 0; + u1_num_mb_part = 4; + /* Reading the subMB type */ + for(uc_i = 0; uc_i < 4; uc_i++) + { + + UWORD32 ui_sub_mb_mode; + +//Inlined ih264d_uev + UWORD32 u4_bitstream_offset = *pu4_bitstrm_ofst; + UWORD32 u4_word, u4_ldz; + + /***************************************************************/ + /* Find leading zeros in next 32 bits */ + /***************************************************************/ + NEXTBITS_32(u4_word, u4_bitstream_offset, pu4_bitstrm_buf); + u4_ldz = CLZ(u4_word); + /* Flush the ps_bitstrm */ + u4_bitstream_offset += (u4_ldz + 1); + /* Read the suffix from the ps_bitstrm */ + u4_word = 0; + if(u4_ldz) + GETBITS(u4_word, u4_bitstream_offset, pu4_bitstrm_buf, + u4_ldz); + *pu4_bitstrm_ofst = u4_bitstream_offset; + ui_sub_mb_mode = ((1 << u4_ldz) + u4_word - 1); +//Inlined ih264d_uev + + if(ui_sub_mb_mode > 12) + return ERROR_SUB_MB_TYPE; + else + { + UWORD8 u1_subMbPredMode = pu1_sub_mb_pred_modes[ui_sub_mb_mode]; + u4_mb_mc_mode = (u4_mb_mc_mode << 8) + | pu1_sub_mb_mc_mode[ui_sub_mb_mode]; + u4_mb_pred_mode = (u4_mb_pred_mode << 8) | u1_subMbPredMode; + pi1_ref_idx[0][uc_i] = ((u1_subMbPredMode & PRED_L0) - 1) >> 1; + pi1_ref_idx[1][uc_i] = ((u1_subMbPredMode & PRED_L1) - 1) >> 1; + COPYTHECONTEXT("sub_mb_type", u1_subMbPredMode); + } + /* Storing collocated Mb and SubMb mode information */ + *pu1_col_info++ = ((PRED_8x8) << 6) + | ((pu1_sub_mb_mc_mode[ui_sub_mb_mode] << 4)); + if(ui_sub_mb_mode != B_DIRECT_8x8) + { + if(ui_sub_mb_mode > B_BI_8x8) + { + ps_dec->s_high_profile.u1_no_submb_part_size_lt8x8_flag = 0; + } + } + else if(!ps_dec->s_high_profile.u1_direct_8x8_inference_flag) + { + ps_dec->s_high_profile.u1_no_submb_part_size_lt8x8_flag = 0; + } + } + } + else + { + UWORD8 u1_mb_pred_mode_idx = 5 + u1_mb_type; + UWORD8 u1_mb_pred_mode_part0 = pu1_mb_pred_modes[0][u1_mb_pred_mode_idx]; + UWORD8 u1_mb_pred_mode_part1 = pu1_mb_pred_modes[1][u1_mb_pred_mode_idx]; + u1_mb_mc_mode = ps_cur_mb_info->u1_mb_mc_mode; + u1_num_mb_part = pu1_num_mb_part[u1_mb_mc_mode]; + + pi1_ref_idx[0][0] = ((u1_mb_pred_mode_part0 & PRED_L0) - 1) >> 1; + pi1_ref_idx[1][0] = ((u1_mb_pred_mode_part0 & PRED_L1) - 1) >> 1; + pi1_ref_idx[0][1] = ((u1_mb_pred_mode_part1 & PRED_L0) - 1) >> 1; + pi1_ref_idx[1][1] = ((u1_mb_pred_mode_part1 & PRED_L1) - 1) >> 1; + + u4_mb_pred_mode = (u1_mb_pred_mode_part0 << 8) | u1_mb_pred_mode_part1; + u4_mb_mc_mode = u1_mb_mc_mode | (u1_mb_mc_mode << 8); + u4_mb_mc_mode <<= 16; + u4_mb_pred_mode <<= 16; + + /* Storing collocated Mb and SubMb mode information */ + *pu1_col_info++ = (u1_mb_mc_mode << 6); + if(u1_mb_mc_mode) + *pu1_col_info++ = (u1_mb_mc_mode << 6); + } + + { + UWORD8 u1_mbaff = ps_dec->ps_cur_slice->u1_mbaff_frame_flag; + UWORD8 uc_field = ps_cur_mb_info->u1_mb_field_decodingflag; + UWORD8 *pu1_num_ref_idx_lx_active = + ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active; + const UWORD8 u1_mbaff_field = (u1_mbaff & uc_field); + UWORD8 u4_num_ref_idx_lx_active; + + u4_num_ref_idx_lx_active = (pu1_num_ref_idx_lx_active[0] + << u1_mbaff_field) - 1; + + if(u4_num_ref_idx_lx_active) + { + if(1 == u4_num_ref_idx_lx_active) + ih264d_parse_bmb_ref_index_cavlc_range1( + u1_num_mb_part, ps_bitstrm, pi1_ref_idx[0], + u4_num_ref_idx_lx_active); + else + { + ret = ih264d_parse_bmb_ref_index_cavlc(u1_num_mb_part, ps_bitstrm, + pi1_ref_idx[0], + u4_num_ref_idx_lx_active); + if(ret != OK) + return ret; + } + } + + u4_num_ref_idx_lx_active = (pu1_num_ref_idx_lx_active[1] + << u1_mbaff_field) - 1; + + if(u4_num_ref_idx_lx_active) + { + if(1 == u4_num_ref_idx_lx_active) + ih264d_parse_bmb_ref_index_cavlc_range1( + u1_num_mb_part, ps_bitstrm, pi1_ref_idx[1], + u4_num_ref_idx_lx_active); + else + { + ret = ih264d_parse_bmb_ref_index_cavlc(u1_num_mb_part, ps_bitstrm, + pi1_ref_idx[1], + u4_num_ref_idx_lx_active); + if(ret != OK) + return ret; + } + } + } + + /* Read MotionVectors */ + { + const UWORD8 * pu1_top_left_sub_mb_indx; + + const UWORD8 * pu1_sub_mb_indx_mod = + (const UWORD8 *)(gau1_ih264d_submb_indx_mod) + + (u1_sub_mb * 6); + const UWORD8 * pu1_sub_mb_partw = (const UWORD8 *)gau1_ih264d_submb_partw; + const UWORD8 * pu1_sub_mb_parth = (const UWORD8 *)gau1_ih264d_submb_parth; + const UWORD8 * pu1_num_sub_mb_part = + (const UWORD8 *)gau1_ih264d_num_submb_part; + const UWORD8 * pu1_mb_partw = (const UWORD8 *)gau1_ih264d_mb_partw; + const UWORD8 * pu1_mb_parth = (const UWORD8 *)gau1_ih264d_mb_parth; + UWORD8 u1_p_idx = 0, u1_num_submb_part, uc_lx; + parse_part_params_t * ps_part; + mv_pred_t *ps_mv_start = ps_dec->ps_mv_cur + (u1_mb_num << 4); + UWORD8 u1_mb_part_wd, u1_mb_part_ht; + + /* Initialisations */ + ps_part = ps_dec->ps_part; + /* Default Initialization for Non subMb Case Mode */ + u1_mb_part_wd = pu1_mb_partw[u1_mb_mc_mode]; + u1_mb_part_ht = pu1_mb_parth[u1_mb_mc_mode]; + u1_num_submb_part = 1; + + /* Decoding the MV for the subMB */ + for(uc_lx = 0; uc_lx < 2; uc_lx++) + { + UWORD8 u1_sub_mb_num = 0, u1_pred_mode, uc_i; + UWORD32 u4_mb_mc_mode_tmp = u4_mb_mc_mode; + UWORD32 u4_mb_pred_mode_tmp = u4_mb_pred_mode; + UWORD16 u2_sub_mb_num = 0x028A; // for sub mb case + UWORD8 u1_b2 = uc_lx << 1; + u1_pred_mode = (uc_lx) ? PRED_L1 : PRED_L0; + pu1_top_left_sub_mb_indx = pu1_sub_mb_indx_mod + (u1_mb_mc_mode << 1); + + for(uc_i = 0; uc_i < u1_num_mb_part; uc_i++) + { + UWORD8 u1_mb_mc_mode, uc_j; + UWORD8 i1_pred = u4_mb_pred_mode_tmp >> 24; + u1_mb_mc_mode = u4_mb_mc_mode_tmp >> 24; + u4_mb_pred_mode_tmp <<= 8; + u4_mb_mc_mode_tmp <<= 8; + /* subMb prediction mode */ + if(u1_sub_mb) + { + + u1_mb_part_wd = pu1_sub_mb_partw[u1_mb_mc_mode]; + u1_mb_part_ht = pu1_sub_mb_parth[u1_mb_mc_mode]; + u1_sub_mb_num = u2_sub_mb_num >> 12; + u1_num_submb_part = pu1_num_sub_mb_part[u1_mb_mc_mode]; + pu1_top_left_sub_mb_indx = pu1_sub_mb_indx_mod + + (u1_mb_mc_mode << 1); + u2_sub_mb_num <<= 4; + } + for(uc_j = 0; uc_j < u1_num_submb_part; + uc_j++, pu1_top_left_sub_mb_indx++) + { + mv_pred_t * ps_mv; + u1_sub_mb_num = u1_sub_mb_num + *pu1_top_left_sub_mb_indx; + ps_mv = ps_mv_start + u1_sub_mb_num; + + /* Storing Info for partitions, writing only once */ + if(uc_lx) + { + ps_part->u1_is_direct = (!i1_pred); + ps_part->u1_pred_mode = i1_pred; + ps_part->u1_sub_mb_num = u1_sub_mb_num; + ps_part->u1_partheight = u1_mb_part_ht; + ps_part->u1_partwidth = u1_mb_part_wd; + /* Increment partition Index */ + u1_p_idx++; + ps_part++; + } + + if(i1_pred & u1_pred_mode) + { + WORD16 i2_mvx, i2_mvy; + +//inlining ih264d_sev + { + UWORD32 u4_bitstream_offset = *pu4_bitstrm_ofst; + UWORD32 u4_word, u4_ldz, u4_abs_val; + + /***************************************************************/ + /* Find leading zeros in next 32 bits */ + /***************************************************************/ + NEXTBITS_32(u4_word, u4_bitstream_offset, + pu4_bitstrm_buf); + u4_ldz = CLZ(u4_word); + + /* Flush the ps_bitstrm */ + u4_bitstream_offset += (u4_ldz + 1); + + /* Read the suffix from the ps_bitstrm */ + u4_word = 0; + if(u4_ldz) + GETBITS(u4_word, u4_bitstream_offset, + pu4_bitstrm_buf, u4_ldz); + + *pu4_bitstrm_ofst = u4_bitstream_offset; + u4_abs_val = ((1 << u4_ldz) + u4_word) >> 1; + + if(u4_word & 0x1) + i2_mvx = (-(WORD32)u4_abs_val); + else + i2_mvx = (u4_abs_val); + } +//inlinined ih264d_sev + +//inlining ih264d_sev + { + UWORD32 u4_bitstream_offset = *pu4_bitstrm_ofst; + UWORD32 u4_word, u4_ldz, u4_abs_val; + + /***************************************************************/ + /* Find leading zeros in next 32 bits */ + /***************************************************************/ + NEXTBITS_32(u4_word, u4_bitstream_offset, + pu4_bitstrm_buf); + u4_ldz = CLZ(u4_word); + + /* Flush the ps_bitstrm */ + u4_bitstream_offset += (u4_ldz + 1); + + /* Read the suffix from the ps_bitstrm */ + u4_word = 0; + if(u4_ldz) + GETBITS(u4_word, u4_bitstream_offset, + pu4_bitstrm_buf, u4_ldz); + + *pu4_bitstrm_ofst = u4_bitstream_offset; + u4_abs_val = ((1 << u4_ldz) + u4_word) >> 1; + + if(u4_word & 0x1) + i2_mvy = (-(WORD32)u4_abs_val); + else + i2_mvy = (u4_abs_val); + } +//inlinined ih264d_sev + + /* Storing Mv residuals */ + ps_mv->i2_mv[u1_b2] = i2_mvx; + ps_mv->i2_mv[u1_b2 + 1] = i2_mvy; + } + } + } + } + /* write back to the scratch partition info */ + ps_dec->ps_part = ps_part; + ps_parse_mb_data->u1_num_part = u1_sub_mb ? u1_p_idx : u1_num_mb_part; + + } + return OK; +} + +/*! + ************************************************************************** + * \if Function name : ParseMb_SubMb_PredBCab\endif + * + * \brief + * Implements sub_mb_pred() of 7.3.5.2. & mb_pred() of 7.3.5.1 + * + * \return + * None. + * + ************************************************************************** + */ + +WORD32 ih264d_parse_bmb_non_direct_cabac(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info, + UWORD8 u1_mb_num, + UWORD8 u1_num_mbsNby2) +{ + /* Loads from ps_dec */ + decoding_envirnoment_t * ps_cab_env = &ps_dec->s_cab_dec_env; + dec_bit_stream_t * ps_bitstrm = ps_dec->ps_bitstrm; + ctxt_inc_mb_info_t *p_curr_ctxt = ps_dec->ps_curr_ctxt_mb_info; + parse_pmbarams_t * ps_parse_mb_data = ps_dec->ps_parse_mb_data + + u1_num_mbsNby2; + + /* table pointer loads */ + const UWORD8 * pu1_sub_mb_pred_modes = (UWORD8 *)(gau1_ih264d_submb_pred_modes) + + 4; + const UWORD8 (*pu1_mb_pred_modes)[32] = + (const UWORD8 (*)[32])gau1_ih264d_mb_pred_modes; + const UWORD8 *pu1_num_mb_part = (const UWORD8 *)gau1_ih264d_num_mb_part; + const UWORD8 *pu1_sub_mb_mc_mode = (UWORD8 *)(gau1_ih264d_submb_mc_mode) + 4; + + const UWORD8 u1_mb_type = ps_cur_mb_info->u1_mb_type; + UWORD8 * pu1_col_info = ps_parse_mb_data->u1_col_info; + WORD8 *pi1_ref_idx_l0 = &ps_parse_mb_data->i1_ref_idx[0][0]; + WORD8 *pi1_ref_idx_l1 = &ps_parse_mb_data->i1_ref_idx[1][0]; + UWORD8 u1_dec_ref_l0, u1_dec_ref_l1; + + UWORD8 u1_num_mb_part, u1_mb_mc_mode, u1_sub_mb, u1_mbpred_mode = 5 + + u1_mb_type; + UWORD32 u4_mb_mc_mode = 0, u4_mb_pred_mode = 0; + WORD32 ret; + + p_curr_ctxt->u1_mb_type = CAB_NON_BD16x16; + u1_sub_mb = !(u1_mb_type ^ B_8x8); + + { + UWORD8 u1_mbaff = ps_dec->ps_cur_slice->u1_mbaff_frame_flag; + UWORD8 *pu1_num_ref_idx_lx_active = + ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active; + UWORD8 uc_field = ps_cur_mb_info->u1_mb_field_decodingflag; + UWORD8 u1_mbaff_field = (u1_mbaff & uc_field); + u1_dec_ref_l0 = (pu1_num_ref_idx_lx_active[0] << u1_mbaff_field) - 1; + u1_dec_ref_l1 = (pu1_num_ref_idx_lx_active[1] << u1_mbaff_field) - 1; + } + + if(u1_sub_mb) + { + const UWORD8 u1_colz = ((PRED_8x8) << 6); + UWORD8 uc_i; + u1_mb_mc_mode = 0; + u1_num_mb_part = 4; + /* Reading the subMB type */ + for(uc_i = 0; uc_i < 4; uc_i++) + { + UWORD8 u1_sub_mb_mode, u1_subMbPredModes; + u1_sub_mb_mode = ih264d_parse_submb_type_cabac( + 1, ps_cab_env, ps_bitstrm, + ps_dec->p_sub_mb_type_t); + + if(u1_sub_mb_mode > 12) + return ERROR_SUB_MB_TYPE; + + u1_subMbPredModes = pu1_sub_mb_pred_modes[u1_sub_mb_mode]; + u4_mb_mc_mode = (u4_mb_mc_mode << 8) | pu1_sub_mb_mc_mode[u1_sub_mb_mode]; + u4_mb_pred_mode = (u4_mb_pred_mode << 8) | u1_subMbPredModes; + *pi1_ref_idx_l0++ = + (u1_subMbPredModes & PRED_L0) ? u1_dec_ref_l0 : -1; + *pi1_ref_idx_l1++ = + (u1_subMbPredModes & PRED_L1) ? u1_dec_ref_l1 : -1; + COPYTHECONTEXT("sub_mb_type", u1_sub_mb_mode); + /* Storing collocated Mb and SubMb mode information */ + *pu1_col_info++ = + (u1_colz | (pu1_sub_mb_mc_mode[u1_sub_mb_mode] << 4)); + if(u1_sub_mb_mode != B_DIRECT_8x8) + { + if(u1_sub_mb_mode > B_BI_8x8) + { + ps_dec->s_high_profile.u1_no_submb_part_size_lt8x8_flag = 0; + } + } + else if(!ps_dec->s_high_profile.u1_direct_8x8_inference_flag) + { + ps_dec->s_high_profile.u1_no_submb_part_size_lt8x8_flag = 0; + } + } + pi1_ref_idx_l0 -= 4; + pi1_ref_idx_l1 -= 4; + } + else + { + UWORD8 u1_mb_pred_mode_part0 = pu1_mb_pred_modes[0][u1_mbpred_mode]; + UWORD8 u1_mb_pred_mode_part1 = pu1_mb_pred_modes[1][u1_mbpred_mode]; + u1_mb_mc_mode = ps_cur_mb_info->u1_mb_mc_mode; + u1_num_mb_part = pu1_num_mb_part[u1_mb_mc_mode]; + /* Storing collocated Mb and SubMb mode information */ + *pu1_col_info++ = (u1_mb_mc_mode << 6); + if(u1_mb_mc_mode) + *pu1_col_info++ = (u1_mb_mc_mode << 6); + u4_mb_mc_mode = u1_mb_mc_mode | (u1_mb_mc_mode << 8); + u4_mb_mc_mode <<= 16; + u4_mb_pred_mode = ((u1_mb_pred_mode_part0 << 8) | u1_mb_pred_mode_part1) << 16; + + *pi1_ref_idx_l0++ = (u1_mb_pred_mode_part0 & PRED_L0) ? u1_dec_ref_l0 : -1; + *pi1_ref_idx_l0-- = (u1_mb_pred_mode_part1 & PRED_L0) ? u1_dec_ref_l0 : -1; + *pi1_ref_idx_l1++ = (u1_mb_pred_mode_part0 & PRED_L1) ? u1_dec_ref_l1 : -1; + *pi1_ref_idx_l1-- = (u1_mb_pred_mode_part1 & PRED_L1) ? u1_dec_ref_l1 : -1; + } + { + WORD8 *pi1_lft_cxt = ps_dec->pi1_left_ref_idx_ctxt_inc; + WORD8 *pi1_top_cxt = p_curr_ctxt->i1_ref_idx; + + ret = ih264d_parse_ref_idx_cabac(u1_num_mb_part, 0, u1_dec_ref_l0, + u1_mb_mc_mode, pi1_ref_idx_l0, pi1_lft_cxt, + pi1_top_cxt, ps_cab_env, ps_bitstrm, + ps_dec->p_ref_idx_t); + if(ret != OK) + return ret; + + ret = ih264d_parse_ref_idx_cabac(u1_num_mb_part, 2, u1_dec_ref_l1, + u1_mb_mc_mode, pi1_ref_idx_l1, pi1_lft_cxt, + pi1_top_cxt, ps_cab_env, ps_bitstrm, + ps_dec->p_ref_idx_t); + if(ret != OK) + return ret; + } + /* Read MotionVectors */ + { + const UWORD8 *pu1_top_left_sub_mb_indx; + UWORD8 uc_j, uc_lx; + UWORD8 u1_mb_part_wd, u1_mb_part_ht; + + const UWORD8 *pu1_sub_mb_indx_mod = + (const UWORD8 *)gau1_ih264d_submb_indx_mod + + (u1_sub_mb * 6); + const UWORD8 *pu1_sub_mb_partw = (const UWORD8 *)gau1_ih264d_submb_partw; + const UWORD8 *pu1_sub_mb_parth = (const UWORD8 *)gau1_ih264d_submb_parth; + const UWORD8 *pu1_num_sub_mb_part = + (const UWORD8 *)gau1_ih264d_num_submb_part; + const UWORD8 *pu1_mb_partw = (const UWORD8 *)gau1_ih264d_mb_partw; + const UWORD8 *pu1_mb_parth = (const UWORD8 *)gau1_ih264d_mb_parth; + + UWORD8 u1_p_idx = 0; + UWORD8 u1_num_submb_part; + parse_part_params_t *ps_part; + /* Initialisations */ + mv_pred_t *ps_mv_start = ps_dec->ps_mv_cur + (u1_mb_num << 4); + ps_part = ps_dec->ps_part; + + /* Default initialization for non subMb case */ + u1_mb_part_wd = pu1_mb_partw[u1_mb_mc_mode]; + u1_mb_part_ht = pu1_mb_parth[u1_mb_mc_mode]; + u1_num_submb_part = 1; + + /* Decoding the MV for the subMB */ + for(uc_lx = 0; uc_lx < 2; uc_lx++) + { + UWORD8 u1_sub_mb_num = 0; + UWORD32 u4_mb_pred_mode_tmp = u4_mb_pred_mode; + UWORD32 u4_mb_mc_mode_tmp = u4_mb_mc_mode; + UWORD8 u1_mb_mc_mode_1, u1_pred_mode, uc_i; + UWORD16 u2_sub_mb_num = 0x028A; + UWORD8 u1_b2 = uc_lx << 1; + u1_pred_mode = (uc_lx) ? PRED_L1 : PRED_L0; + /* Default for Cabac */ + pu1_top_left_sub_mb_indx = pu1_sub_mb_indx_mod + (u1_mb_mc_mode << 1); + for(uc_i = 0; uc_i < u1_num_mb_part; uc_i++) + { + + WORD8 i1_pred = (UWORD8)(u4_mb_pred_mode_tmp >> 24); + u1_mb_mc_mode_1 = (UWORD8)(u4_mb_mc_mode_tmp >> 24); + u4_mb_pred_mode_tmp <<= 8; + u4_mb_mc_mode_tmp <<= 8; + + /* subMb prediction mode */ + if(u1_sub_mb) + { + u1_mb_part_wd = pu1_sub_mb_partw[u1_mb_mc_mode_1]; + u1_mb_part_ht = pu1_sub_mb_parth[u1_mb_mc_mode_1]; + u1_sub_mb_num = u2_sub_mb_num >> 12; + pu1_top_left_sub_mb_indx = pu1_sub_mb_indx_mod + (u1_mb_mc_mode_1 << 1); + u1_num_submb_part = pu1_num_sub_mb_part[u1_mb_mc_mode_1]; + u2_sub_mb_num = u2_sub_mb_num << 4; + } + + for(uc_j = 0; uc_j < u1_num_submb_part; + uc_j++, pu1_top_left_sub_mb_indx++) + { + mv_pred_t *ps_mv; + u1_sub_mb_num = u1_sub_mb_num + *pu1_top_left_sub_mb_indx; + ps_mv = ps_mv_start + u1_sub_mb_num; + + /* Storing Info for partitions, writing only once */ + if(uc_lx) + { + ps_part->u1_is_direct = (!i1_pred); + ps_part->u1_pred_mode = i1_pred; + ps_part->u1_sub_mb_num = u1_sub_mb_num; + ps_part->u1_partheight = u1_mb_part_ht; + ps_part->u1_partwidth = u1_mb_part_wd; + + /* Increment partition Index */ + u1_p_idx++; + ps_part++; + } + + ih264d_get_mvd_cabac(u1_sub_mb_num, u1_b2, u1_mb_part_wd, + u1_mb_part_ht, + (UWORD8)(i1_pred & u1_pred_mode), ps_dec, + ps_mv); + } + } + } + /* write back to the scratch partition info */ + + ps_dec->ps_part = ps_part; + ps_parse_mb_data->u1_num_part = u1_sub_mb ? u1_p_idx : u1_num_mb_part; + + } + + return OK; +} + +/*! + ************************************************************************** + * \if Function name : ih264d_parse_bmb_cabac \endif + * + * \brief + * This function parses CABAC syntax of a B MB. + * + * \return + * 0 on Success and Error code otherwise + ************************************************************************** + */ +WORD32 ih264d_parse_bmb_cabac(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info, + UWORD8 u1_mb_num, + UWORD8 u1_num_mbsNby2) +{ + UWORD8 u1_cbp; + deblk_mb_t * ps_cur_deblk_mb = ps_dec->ps_deblk_mbn + u1_mb_num; + const UWORD8 *puc_mb_mc_mode = (const UWORD8 *)gau1_ih264d_mb_mc_mode; + UWORD8 u1_mb_type = ps_cur_mb_info->u1_mb_type; + ctxt_inc_mb_info_t *p_curr_ctxt = ps_dec->ps_curr_ctxt_mb_info; + + WORD32 ret; + UWORD8 u1_Bdirect_tranform_read = 1; + ps_dec->s_high_profile.u1_no_submb_part_size_lt8x8_flag = 1; + + ps_cur_mb_info->u1_mb_mc_mode = puc_mb_mc_mode[5 + u1_mb_type]; + + ps_cur_mb_info->u1_yuv_dc_block_flag = 0; + + ps_cur_deblk_mb->u1_mb_type |= D_B_SLICE; + if(u1_mb_type != B_DIRECT) + { + ret = ih264d_parse_bmb_non_direct_cabac(ps_dec, ps_cur_mb_info, u1_mb_num, + u1_num_mbsNby2); + if(ret != OK) + return ret; + } + else + { + + /************ STORING PARTITION INFO ***********/ + parse_part_params_t * ps_part_info; + ps_part_info = ps_dec->ps_part; + ps_part_info->u1_is_direct = PART_DIRECT_16x16; + ps_part_info->u1_sub_mb_num = 0; + ps_dec->ps_part++; + p_curr_ctxt->u1_mb_type = CAB_BD16x16; + + MEMSET_16BYTES(&ps_dec->pu1_left_mv_ctxt_inc[0][0], 0); + memset(ps_dec->pi1_left_ref_idx_ctxt_inc, 0, 4); + MEMSET_16BYTES(p_curr_ctxt->u1_mv, 0); + memset(p_curr_ctxt->i1_ref_idx, 0, 4); + + /* check whether transform8x8 u4_flag to be read or not */ + u1_Bdirect_tranform_read = + ps_dec->s_high_profile.u1_direct_8x8_inference_flag; + } + + /* Read the Coded block pattern */ + u1_cbp = (WORD8)ih264d_parse_ctx_cbp_cabac(ps_dec); + p_curr_ctxt->u1_cbp = u1_cbp; + ps_cur_mb_info->u1_cbp = u1_cbp; + + if(u1_cbp > 47) + return ERROR_CBP; + + COPYTHECONTEXT("coded_block_pattern", u1_cbp); + + ps_cur_mb_info->u1_tran_form8x8 = 0; + ps_cur_mb_info->ps_curmb->u1_tran_form8x8 = 0; + + if((ps_dec->s_high_profile.u1_transform8x8_present) && (u1_cbp & (0xf)) + && (ps_dec->s_high_profile.u1_no_submb_part_size_lt8x8_flag) + && (u1_Bdirect_tranform_read)) + { + ps_cur_mb_info->u1_tran_form8x8 = ih264d_parse_transform8x8flag_cabac( + ps_dec, ps_cur_mb_info); + COPYTHECONTEXT("transform_size_8x8_flag", ps_cur_mb_info->u1_tran_form8x8); + + ps_cur_mb_info->ps_curmb->u1_tran_form8x8 = ps_cur_mb_info->u1_tran_form8x8; + p_curr_ctxt->u1_transform8x8_ctxt = ps_cur_mb_info->u1_tran_form8x8; + } + else + { + p_curr_ctxt->u1_transform8x8_ctxt = 0; + } + + p_curr_ctxt->u1_intra_chroma_pred_mode = 0; + p_curr_ctxt->u1_yuv_dc_csbp &= 0xFE; + ps_dec->pu1_left_yuv_dc_csbp[0] &= 0x6; + + /* Read mb_qp_delta */ + if(u1_cbp) + { + WORD8 c_temp; + ret = ih264d_parse_mb_qp_delta_cabac(ps_dec, &c_temp); + if(ret != OK) + return ret; + COPYTHECONTEXT("mb_qp_delta", c_temp); + if(c_temp) + { + ret = ih264d_update_qp(ps_dec, c_temp); + if(ret != OK) + return ret; + } + } + else + ps_dec->i1_prev_mb_qp_delta = 0; + + ih264d_parse_residual4x4_cabac(ps_dec, ps_cur_mb_info, 0); + if(EXCEED_OFFSET(ps_dec->ps_bitstrm)) + return ERROR_EOB_TERMINATE_T; + return OK; +} +/*! + ************************************************************************** + * \if Function name : ih264d_parse_bmb_cavlc \endif + * + * \brief + * This function parses CAVLC syntax of a B MB. + * + * \return + * 0 on Success and Error code otherwise + ************************************************************************** + */ +WORD32 ih264d_parse_bmb_cavlc(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info, + UWORD8 u1_mb_num, + UWORD8 u1_num_mbsNby2) +{ + UWORD32 u4_cbp; + deblk_mb_t * ps_cur_deblk_mb = ps_dec->ps_deblk_mbn + u1_mb_num; + dec_bit_stream_t * const ps_bitstrm = ps_dec->ps_bitstrm; + UWORD32 * pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; + UWORD32 *pu4_bitstrm_ofst = &ps_bitstrm->u4_ofst; + const UWORD8 *puc_mb_mc_mode = (const UWORD8 *)gau1_ih264d_mb_mc_mode; + UWORD8 u1_mb_type = ps_cur_mb_info->u1_mb_type; + + WORD32 ret; + UWORD8 u1_Bdirect_tranform_read = 1; + ps_dec->s_high_profile.u1_no_submb_part_size_lt8x8_flag = 1; + ps_cur_mb_info->u1_tran_form8x8 = 0; + ps_cur_mb_info->ps_curmb->u1_tran_form8x8 = 0; + + ps_cur_mb_info->u1_yuv_dc_block_flag = 0; + + ps_cur_mb_info->u1_mb_mc_mode = puc_mb_mc_mode[5 + u1_mb_type]; + + ps_cur_deblk_mb->u1_mb_type |= D_B_SLICE; + if(u1_mb_type != B_DIRECT) + { + ret = ih264d_parse_bmb_non_direct_cavlc(ps_dec, ps_cur_mb_info, u1_mb_num, + u1_num_mbsNby2); + if(ret != OK) + return ret; + } + else + { + /************ STORING PARTITION INFO ***********/ + parse_part_params_t * ps_part_info; + ps_part_info = ps_dec->ps_part; + ps_part_info->u1_is_direct = PART_DIRECT_16x16; + ps_part_info->u1_sub_mb_num = 0; + ps_dec->ps_part++; + /* check whether transform8x8 u4_flag to be read or not */ + u1_Bdirect_tranform_read = + ps_dec->s_high_profile.u1_direct_8x8_inference_flag; + } + + /* Read the Coded block pattern */ + { + const UWORD8 * puc_CbpInter = gau1_ih264d_cbp_inter; +//Inlined ih264d_uev + UWORD32 u4_bitstream_offset = *pu4_bitstrm_ofst; + UWORD32 u4_word, u4_ldz; + + /***************************************************************/ + /* Find leading zeros in next 32 bits */ + /***************************************************************/ + NEXTBITS_32(u4_word, u4_bitstream_offset, pu4_bitstrm_buf); + u4_ldz = CLZ(u4_word); + /* Flush the ps_bitstrm */ + u4_bitstream_offset += (u4_ldz + 1); + /* Read the suffix from the ps_bitstrm */ + u4_word = 0; + if(u4_ldz) + GETBITS(u4_word, u4_bitstream_offset, pu4_bitstrm_buf, u4_ldz); + *pu4_bitstrm_ofst = u4_bitstream_offset; + u4_cbp = ((1 << u4_ldz) + u4_word - 1); +//Inlined ih264d_uev + if(u4_cbp > 47) + return ERROR_CBP; + u4_cbp = puc_CbpInter[u4_cbp]; + + if((ps_dec->s_high_profile.u1_transform8x8_present) && (u4_cbp & (0xf)) + && (ps_dec->s_high_profile.u1_no_submb_part_size_lt8x8_flag) + && (u1_Bdirect_tranform_read)) + { + ps_cur_mb_info->u1_tran_form8x8 = ih264d_get_bit_h264(ps_bitstrm); + COPYTHECONTEXT("transform_size_8x8_flag", ps_cur_mb_info->u1_tran_form8x8); + ps_cur_mb_info->ps_curmb->u1_tran_form8x8 = ps_cur_mb_info->u1_tran_form8x8; + } + + } + + COPYTHECONTEXT("coded_block_pattern", u4_cbp); + ps_cur_mb_info->u1_cbp = u4_cbp; + + /* Read mb_qp_delta */ + if(u4_cbp) + { + WORD32 i_temp; +//inlining ih264d_sev + + UWORD32 u4_bitstream_offset = *pu4_bitstrm_ofst; + UWORD32 u4_word, u4_ldz, u4_abs_val; + + /***************************************************************/ + /* Find leading zeros in next 32 bits */ + /***************************************************************/ + NEXTBITS_32(u4_word, u4_bitstream_offset, pu4_bitstrm_buf); + u4_ldz = CLZ(u4_word); + + /* Flush the ps_bitstrm */ + u4_bitstream_offset += (u4_ldz + 1); + + /* Read the suffix from the ps_bitstrm */ + u4_word = 0; + if(u4_ldz) + GETBITS(u4_word, u4_bitstream_offset, pu4_bitstrm_buf, u4_ldz); + + *pu4_bitstrm_ofst = u4_bitstream_offset; + u4_abs_val = ((1 << u4_ldz) + u4_word) >> 1; + + if(u4_word & 0x1) + i_temp = (-(WORD32)u4_abs_val); + else + i_temp = (u4_abs_val); + + if(i_temp < -26 || i_temp > 25) + return ERROR_INV_RANGE_QP_T; +//inlinined ih264d_sev + COPYTHECONTEXT("mb_qp_delta", i_temp); + if(i_temp) + { + ret = ih264d_update_qp(ps_dec, (WORD8)i_temp); + if(ret != OK) + return ret; + } + + ret = ih264d_parse_residual4x4_cavlc(ps_dec, ps_cur_mb_info, 0); + if(ret != OK) + return ret; + if(EXCEED_OFFSET(ps_bitstrm)) + return ERROR_EOB_TERMINATE_T; + } + else + { + ps_dec->i1_prev_mb_qp_delta = 0; + ih264d_update_nnz_for_skipmb(ps_dec, ps_cur_mb_info, CAVLC); + } + + return OK; +} + +WORD32 ih264d_mv_pred_ref_tfr_nby2_bmb(dec_struct_t * ps_dec, + UWORD8 u1_mb_idx, + UWORD8 u1_num_mbs) +{ + parse_pmbarams_t * ps_mb_part_info; + parse_part_params_t * ps_part; + mv_pred_t *ps_mv_nmb, *ps_mv_nmb_start, *ps_mv_ntop, *ps_mv_ntop_start; + pic_buffer_t * ps_ref_frame; + UWORD8 u1_direct_mode_width; + UWORD8 i, j; + dec_mb_info_t * ps_cur_mb_info; + const UWORD8 u1_mbaff = ps_dec->ps_cur_slice->u1_mbaff_frame_flag; + UWORD8 u1_field; + WORD32 ret = 0; + + ps_dec->i4_submb_ofst -= (u1_num_mbs - u1_mb_idx) << 4; + ps_mb_part_info = ps_dec->ps_parse_mb_data; + ps_part = ps_dec->ps_parse_part_params; + + /* N/2 Mb MvPred and Transfer Setup Loop */ + for(i = u1_mb_idx; i < u1_num_mbs; i++, ps_mb_part_info++) + { + UWORD8 u1_colz = 0; + ps_dec->i4_submb_ofst += SUB_BLK_SIZE; + /* Restore the slice scratch MbX and MbY context */ + ps_cur_mb_info = ps_dec->ps_nmb_info + i; + + + u1_field = ps_cur_mb_info->u1_mb_field_decodingflag; + + ps_mv_nmb_start = ps_dec->ps_mv_cur + (i << 4); + ps_dec->u2_mbx = ps_cur_mb_info->u2_mbx; + ps_dec->u2_mby = ps_cur_mb_info->u2_mby; + ps_dec->u1_currB_type = 0; + ps_dec->u2_mv_2mb[i & 0x1] = 0; + + /* Look for MV Prediction and Reference Transfer in Non-I Mbs */ + if(!ps_mb_part_info->u1_isI_mb) + { + UWORD8 u1_blk_no; + WORD16 i1_ref_idx, i1_ref_idx1; + UWORD8 u1_pred_mode; + UWORD8 u1_sub_mb_x, u1_sub_mb_y, u1_sub_mb_num; + UWORD8 u1_lx, u1_lx_start, u1_lxend, u1_tmp_lx; + UWORD8 u1_num_part, u1_num_ref, u1_wd, u1_ht; + UWORD32 *pu4_wt_offst; + UWORD8 u1_scale_ref, u4_bot_mb; + deblk_mb_t * ps_cur_deblk_mb = ps_dec->ps_deblk_mbn + i; + WORD8 (*pi1_ref_idx)[MAX_REFIDX_INFO_PER_MB] = + ps_mb_part_info->i1_ref_idx; + WORD8 *pi1_ref_idx0 = pi1_ref_idx[0], + *pi1_ref_idx1 = pi1_ref_idx[1]; + UWORD32 **ppu4_wt_ofst = ps_mb_part_info->pu4_wt_offst; + + /* MB Level initialisations */ + ps_dec->u4_num_pmbair = i >> u1_mbaff; + ps_dec->u1_mb_idx_mv = i; + + /* CHANGED CODE */ + ps_mv_ntop_start = ps_mv_nmb_start + - (ps_dec->u2_frm_wd_in_mbs << (4 + u1_mbaff)) + 12; + + u1_num_part = ps_mb_part_info->u1_num_part; + ps_cur_deblk_mb->u1_mb_type |= (u1_num_part > 1) << 1; + u1_direct_mode_width = (1 == ps_mb_part_info->u1_num_part) ? 16 : 8; + + + ps_cur_mb_info->u4_pred_info_pkd_idx = ps_dec->u4_pred_info_pkd_idx; + ps_cur_mb_info->u1_num_pred_parts = 0; + + /****************************************************/ + /* weighted u4_ofst pointer calculations, this loop */ + /* runs maximum 4 times, even in direct cases */ + /****************************************************/ + u1_scale_ref = u1_mbaff & ps_cur_mb_info->u1_mb_field_decodingflag; + u4_bot_mb = 1 - ps_cur_mb_info->u1_topmb; + if(ps_dec->ps_cur_pps->u1_wted_bipred_idc) + { + u1_num_ref = MIN(u1_num_part, 4); + if(PART_DIRECT_16x16 != ps_part->u1_is_direct) + { + for(u1_blk_no = 0; u1_blk_no < u1_num_ref; u1_blk_no++) + { + i1_ref_idx = MAX(pi1_ref_idx0[u1_blk_no], 0); + if(u1_scale_ref) + i1_ref_idx >>= 1; + i1_ref_idx *= + ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[1]; + if(u1_scale_ref) + i1_ref_idx += + (MAX(pi1_ref_idx1[u1_blk_no], 0) + >> 1); + else + i1_ref_idx += MAX(pi1_ref_idx1[u1_blk_no], 0); + pu4_wt_offst = (UWORD32*)&ps_dec->pu4_wt_ofsts[2 + * X3(i1_ref_idx)]; + + if(pi1_ref_idx0[u1_blk_no] < 0) + pu4_wt_offst += 1; + + ppu4_wt_ofst[u1_blk_no] = pu4_wt_offst; + if(u1_scale_ref + && (ps_dec->ps_cur_pps->u1_wted_bipred_idc + == 2)) + { + i1_ref_idx = MAX(pi1_ref_idx0[u1_blk_no], 0); + i1_ref_idx *= + (ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[1] + << 1); + i1_ref_idx += MAX(pi1_ref_idx1[u1_blk_no], 0); + if(u4_bot_mb) + { + i1_ref_idx += + (ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[0] + << 1) + * (ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[1] + << 1); + } + pu4_wt_offst = (UWORD32*)&ps_dec->pu4_mbaff_wt_mat[2 + * X3(i1_ref_idx)]; + ppu4_wt_ofst[u1_blk_no] = pu4_wt_offst; + } + } + } + } + + /**************************************************/ + /* Loop on Partitions */ + /* direct mode is reflected as a single partition */ + /**************************************************/ + for(j = 0; j < u1_num_part; j++, ps_part++) + { + u1_sub_mb_num = ps_part->u1_sub_mb_num; + ps_dec->u1_sub_mb_num = u1_sub_mb_num; + + if(PART_NOT_DIRECT != ps_part->u1_is_direct) + { + /**************************************************/ + /* Direct Mode, Call DecodeSpatial/TemporalDirect */ + /* only (those will in turn call FormMbPartInfo) */ + /**************************************************/ + ret = ps_dec->ps_cur_slice->pf_decodeDirect(ps_dec, + u1_direct_mode_width, + ps_cur_mb_info, i); + if(ret != OK) + return ret; + ps_cur_deblk_mb->u1_mb_type |= (ps_dec->u1_currB_type << 1); + + } + else + { + mv_pred_t s_mvPred; + /**************************************************/ + /* Non Direct Mode, Call Motion Vector Predictor */ + /* and FormMbpartInfo */ + /**************************************************/ + u1_sub_mb_x = u1_sub_mb_num & 0x03; + u1_sub_mb_y = u1_sub_mb_num >> 2; + u1_blk_no = + (u1_num_part < 4) ? + j : + (((u1_sub_mb_y >> 1) << 1) + + (u1_sub_mb_x + >> 1)); + + ps_mv_ntop = ps_mv_ntop_start + u1_sub_mb_x; + ps_mv_nmb = ps_mv_nmb_start + u1_sub_mb_num; + + u1_pred_mode = ps_part->u1_pred_mode; + u1_wd = ps_part->u1_partwidth; + u1_ht = ps_part->u1_partheight; + + u1_lx_start = 0; + u1_lxend = 2; + if( PRED_L0 == u1_pred_mode) + { + s_mvPred.i2_mv[2] = 0; + s_mvPred.i2_mv[3] = 0; + u1_lxend = 1; + } + if( PRED_L1 == u1_pred_mode) + { + s_mvPred.i2_mv[0] = 0; + s_mvPred.i2_mv[1] = 0; + u1_lx_start = 1; + } + + /* Populate the colpic info and reference frames */ + s_mvPred.i1_ref_frame[0] = pi1_ref_idx0[u1_blk_no]; + s_mvPred.i1_ref_frame[1] = pi1_ref_idx1[u1_blk_no]; + + ps_dec->pf_mvpred(ps_dec, ps_cur_mb_info, ps_mv_nmb, ps_mv_ntop, + &s_mvPred, u1_sub_mb_num, u1_wd, + u1_lx_start, u1_lxend, + ps_cur_mb_info->u1_mb_mc_mode); + + /**********************************************************/ + /* Loop on number of predictors, 1 Each for Forw Backw */ + /* Loop 2 times for BiDirect mode */ + /**********************************************************/ + for(u1_lx = u1_lx_start; u1_lx < u1_lxend; u1_lx++) + { + WORD16 i2_mv_x, i2_mv_y; + + /********************************************************/ + /* Predict Mv */ + /* Add Mv Residuals and store back */ + /********************************************************/ + i1_ref_idx = s_mvPred.i1_ref_frame[u1_lx]; + u1_tmp_lx = (u1_lx << 1); + + i2_mv_x = ps_mv_nmb->i2_mv[u1_tmp_lx]; + i2_mv_y = ps_mv_nmb->i2_mv[u1_tmp_lx + 1]; + + i2_mv_x += s_mvPred.i2_mv[u1_tmp_lx]; + i2_mv_y += s_mvPred.i2_mv[u1_tmp_lx + 1]; + s_mvPred.i2_mv[u1_tmp_lx] = i2_mv_x; + s_mvPred.i2_mv[u1_tmp_lx + 1] = i2_mv_y; + + /********************************************************/ + /* Transfer setup call */ + /* convert RefIdx if it is MbAff */ + /* Pass Weight Offset and refFrame */ + /********************************************************/ + i1_ref_idx1 = i1_ref_idx >> u1_scale_ref; + if(u1_scale_ref && ((i1_ref_idx & 0x01) != u4_bot_mb)) + i1_ref_idx1 += MAX_REF_BUFS; + ps_ref_frame = + ps_dec->ps_ref_pic_buf_lx[u1_lx][i1_ref_idx1]; + + /* Storing Colocated-Zero u4_flag */ + if(u1_lx == u1_lx_start) + { + /* Fill colocated info in MvPred structure */ + s_mvPred.u1_col_ref_pic_idx = + ps_ref_frame->u1_mv_buf_id; + s_mvPred.u1_pic_type = ps_ref_frame->u1_pic_type; + + /* Calculating colocated zero information */ + u1_colz = + (u1_field << 1) + | ((i1_ref_idx == 0) + && (ABS(i2_mv_x) + <= 1) + && (ABS(i2_mv_y) + <= 1)); + u1_colz |= ps_mb_part_info->u1_col_info[u1_blk_no]; + } + + pu4_wt_offst = ppu4_wt_ofst[u1_blk_no]; + { + pred_info_pkd_t *ps_pred_pkd; + WORD16 i2_mv[2]; + + i2_mv[0] = i2_mv_x; + i2_mv[1] = i2_mv_y; + + ps_pred_pkd = ps_dec->ps_pred_pkd + ps_dec->u4_pred_info_pkd_idx; + ih264d_fill_pred_info(i2_mv,u1_wd,u1_ht,u1_sub_mb_num,u1_pred_mode, + ps_pred_pkd,ps_ref_frame->u1_pic_buf_id,i1_ref_idx,pu4_wt_offst, + ps_ref_frame->u1_pic_type); + ps_dec->u4_pred_info_pkd_idx++; + ps_cur_mb_info->u1_num_pred_parts++; + + + } + + } + ih264d_rep_mv_colz(ps_dec, &s_mvPred, ps_mv_nmb, + u1_sub_mb_num, u1_colz, u1_ht, + u1_wd); + } + } + + } + else + { + /* Set zero values in case of Intra Mbs */ + mv_pred_t s_mvPred = + { + { 0, 0, 0, 0 }, + { -1, -1 }, 0, 0}; + /* Storing colocated zero information */ + ih264d_rep_mv_colz(ps_dec, &s_mvPred, ps_mv_nmb_start, 0, + (UWORD8)(u1_field << 1), 4, 4); + } + + /*if num _cores is set to 3 ,compute bs will be done in another thread*/ + if(ps_dec->u4_num_cores < 3) + { + if(ps_dec->u4_app_disable_deblk_frm == 0) + ps_dec->pf_compute_bs(ps_dec, ps_cur_mb_info, + (UWORD16)(i >> u1_mbaff)); + } + } + return OK; +} +/*! + ************************************************************************** + * \if Function name : ih264d_get_implicit_weights \endif + * + * \brief + * Calculates Implicit Weights. + * + * \return + * None + * + ************************************************************************** + */ +void ih264d_get_implicit_weights(dec_struct_t *ps_dec) +{ + UWORD32 *pu4_iwt_ofst; + UWORD8 i, j; + struct pic_buffer_t *ps_pic_buff0, *ps_pic_buff1; + WORD16 i2_dist_scale_factor; + WORD16 i2_tb, i2_td, i2_tx; + WORD64 i8_tb, i8_td; + WORD32 i4_poc0, i4_poc1; + UWORD32 ui_temp0, ui_temp1; + UWORD8 uc_num_ref_idx_l0_active, uc_num_ref_idx_l1_active; + + pu4_iwt_ofst = ps_dec->pu4_wts_ofsts_mat; + uc_num_ref_idx_l0_active = + ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[0]; + uc_num_ref_idx_l1_active = + ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[1]; + + for(i = 0; i < uc_num_ref_idx_l0_active; i++) + { + ps_pic_buff0 = ps_dec->ps_ref_pic_buf_lx[0][i]; + i4_poc0 = ps_pic_buff0->i4_avg_poc; + for(j = 0; j < uc_num_ref_idx_l1_active; j++) + { + ps_pic_buff1 = ps_dec->ps_ref_pic_buf_lx[1][j]; + i4_poc1 = ps_pic_buff1->i4_avg_poc; + + if(i4_poc1 != i4_poc0) + { + i8_tb = (WORD64)ps_dec->ps_cur_pic->i4_poc - i4_poc0; + i2_tb = CLIP_S8(i8_tb); + i8_td = (WORD64)i4_poc1 - i4_poc0; + i2_td = CLIP_S8(i8_td); + i2_tx = (16384 + ABS(SIGN_POW2_DIV(i2_td, 1))) / i2_td; + i2_dist_scale_factor = CLIP_S11( + (((i2_tb * i2_tx) + 32) >> 6)); + + if(/*((u4_poc1 - u4_poc0) == 0) ||*/ + (!(ps_pic_buff1->u1_is_short && ps_pic_buff0->u1_is_short)) + || ((i2_dist_scale_factor >> 2) < -64) + || ((i2_dist_scale_factor >> 2) > 128)) + { + /* same for forward and backward, wt=32 and Offset = 0 */ + ui_temp0 = 0x00000020; + ui_temp1 = 0x00000020; + } + else + { + ui_temp0 = 64 - (i2_dist_scale_factor >> 2); + ui_temp1 = (i2_dist_scale_factor >> 2); + } + } + else + { + ui_temp0 = 0x00000020; + ui_temp1 = 0x00000020; + } + pu4_iwt_ofst[0] = pu4_iwt_ofst[2] = pu4_iwt_ofst[4] = ui_temp0; + pu4_iwt_ofst[1] = pu4_iwt_ofst[3] = pu4_iwt_ofst[5] = ui_temp1; + pu4_iwt_ofst += 6; + } + } + if(ps_dec->ps_cur_slice->u1_mbaff_frame_flag) + { + UWORD8 k; + WORD32 i4_cur_poc = ps_dec->ps_cur_pic->i4_top_field_order_cnt; + UWORD32* pu4_wt_mat = ps_dec->pu4_mbaff_wt_mat; + /* Form the Implicit Weighted prediction matrix for field MBs also */ + for(k = 0; k < 2; k++) + { + for(i = 0; i < (uc_num_ref_idx_l0_active << 1); i++) + { + UWORD16 u2_l0_idx; + + /*u2_l0_idx = (i >= uc_num_ref_idx_l0_active) + ?(MAX_REF_BUFS + i - uc_num_ref_idx_l0_active) : (i) ;*/ + + u2_l0_idx = i >> 1; + if((i & 0x01) != k) + { + u2_l0_idx += MAX_REF_BUFS; + } + ps_pic_buff0 = ps_dec->ps_ref_pic_buf_lx[0][u2_l0_idx]; + i4_poc0 = ps_pic_buff0->i4_poc; + for(j = 0; j < (uc_num_ref_idx_l1_active << 1); j++) + { + UWORD16 u2_l1_idx; + /*u2_l1_idx = (j >= uc_num_ref_idx_l1_active) + ? (MAX_REF_BUFS + j - uc_num_ref_idx_l1_active ) : (j) ;*/ + + u2_l1_idx = j >> 1; + if((j & 0x01) != k) + { + u2_l1_idx += MAX_REF_BUFS; + } + ps_pic_buff1 = ps_dec->ps_ref_pic_buf_lx[1][u2_l1_idx]; + i4_poc1 = ps_pic_buff1->i4_poc; + if(i4_poc1 != i4_poc0) + { + i8_tb = (WORD64)i4_cur_poc - i4_poc0; + i2_tb = CLIP_S8(i8_tb); + i8_td = (WORD64)i4_poc1 - i4_poc0; + i2_td = CLIP_S8(i8_td); + i2_tx = (16384 + ABS(SIGN_POW2_DIV(i2_td, 1))) + / i2_td; + i2_dist_scale_factor = CLIP_S11( + (((i2_tb * i2_tx) + 32) >> 6)); + + if(/*((u4_poc1 - u4_poc0) == 0) ||*/ + (!(ps_pic_buff1->u1_is_short && ps_pic_buff0->u1_is_short)) + || ((i2_dist_scale_factor >> 2) < -64) + || ((i2_dist_scale_factor >> 2) > 128)) + { + /* same for forward and backward, wt=32 and Offset = 0 */ + ui_temp0 = 0x00000020; + ui_temp1 = 0x00000020; + } + else + { + ui_temp0 = 64 - (i2_dist_scale_factor >> 2); + ui_temp1 = (i2_dist_scale_factor >> 2); + } + } + else + { + ui_temp0 = 0x00000020; + ui_temp1 = 0x00000020; + } + /* Store in the weight matrix */ + *pu4_wt_mat++ = ui_temp0; + *pu4_wt_mat++ = ui_temp1; + *pu4_wt_mat++ = ui_temp0; + *pu4_wt_mat++ = ui_temp1; + *pu4_wt_mat++ = ui_temp0; + *pu4_wt_mat++ = ui_temp1; + + } + } + i4_cur_poc = ps_dec->ps_cur_pic->i4_bottom_field_order_cnt; + } + } +} + +/*! + ************************************************************************** + * \if Function name : ih264d_decode_bslice \endif + * + * \brief + * Decodes a B Slice + * + * + * \return + * 0 on Success and Error code otherwise + ************************************************************************** + */ +WORD32 ih264d_parse_bslice(dec_struct_t * ps_dec, UWORD16 u2_first_mb_in_slice) +{ + dec_pic_params_t * ps_pps = ps_dec->ps_cur_pps; + dec_slice_params_t * ps_slice = ps_dec->ps_cur_slice; + dec_bit_stream_t * ps_bitstrm = ps_dec->ps_bitstrm; + UWORD8 u1_ref_idx_re_flag_lx; + UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; + UWORD32 *pu4_bitstrm_ofst = &ps_bitstrm->u4_ofst; + + UWORD64 u8_ref_idx_l0, u8_ref_idx_l1; + UWORD32 u4_temp, ui_temp1; + WORD32 i_temp; + WORD32 ret; + + /*--------------------------------------------------------------------*/ + /* Read remaining contents of the slice header */ + /*--------------------------------------------------------------------*/ + { + WORD8 *pi1_buf; + WORD16 *pi2_mv = ps_dec->s_default_mv_pred.i2_mv; + WORD32 *pi4_mv = (WORD32*)pi2_mv; + WORD16 *pi16_refFrame; + pi1_buf = ps_dec->s_default_mv_pred.i1_ref_frame; + pi16_refFrame = (WORD16*)pi1_buf; + *pi4_mv = 0; + *(pi4_mv + 1) = 0; + *pi16_refFrame = OUT_OF_RANGE_REF; + ps_dec->s_default_mv_pred.u1_col_ref_pic_idx = (UWORD8)-1; + ps_dec->s_default_mv_pred.u1_pic_type = (UWORD8)-1; + } + + ps_slice->u1_num_ref_idx_active_override_flag = ih264d_get_bit_h264( + ps_bitstrm); + COPYTHECONTEXT("SH: num_ref_idx_override_flag", + ps_slice->u1_num_ref_idx_active_override_flag); + + u8_ref_idx_l0 = ps_dec->ps_cur_pps->u1_num_ref_idx_lx_active[0]; + u8_ref_idx_l1 = ps_dec->ps_cur_pps->u1_num_ref_idx_lx_active[1]; + if(ps_slice->u1_num_ref_idx_active_override_flag) + { + u8_ref_idx_l0 = (UWORD64)1 + ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); + COPYTHECONTEXT("SH: num_ref_idx_l0_active_minus1", + u8_ref_idx_l0 - 1); + + u8_ref_idx_l1 = (UWORD64)1 + ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); + COPYTHECONTEXT("SH: num_ref_idx_l1_active_minus1", + u8_ref_idx_l1 - 1); + } + + { + UWORD8 u1_max_ref_idx = H264_MAX_REF_PICS; + if(ps_slice->u1_field_pic_flag) + { + u1_max_ref_idx = H264_MAX_REF_PICS << 1; + } + if((u8_ref_idx_l0 > u1_max_ref_idx) || (u8_ref_idx_l1 > u1_max_ref_idx)) + { + return ERROR_NUM_REF; + } + ps_slice->u1_num_ref_idx_lx_active[0] = u8_ref_idx_l0; + ps_slice->u1_num_ref_idx_lx_active[1] = u8_ref_idx_l1; + } + + + ih264d_init_ref_idx_lx_b(ps_dec); + /* Store the value for future slices in the same picture */ + ps_dec->u1_num_ref_idx_lx_active_prev = + ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[0]; + + u1_ref_idx_re_flag_lx = ih264d_get_bit_h264(ps_bitstrm); + COPYTHECONTEXT("SH: ref_pic_list_reordering_flag_l0",u1_ref_idx_re_flag_lx); + + /* Modified temporarily */ + if(u1_ref_idx_re_flag_lx) + { + WORD8 ret; + ps_dec->ps_ref_pic_buf_lx[0] = ps_dec->ps_dpb_mgr->ps_mod_dpb[0]; + ret = ih264d_ref_idx_reordering(ps_dec, 0); + if(ret == -1) + return ERROR_REFIDX_ORDER_T; + } + else + ps_dec->ps_ref_pic_buf_lx[0] = ps_dec->ps_dpb_mgr->ps_init_dpb[0]; + + u1_ref_idx_re_flag_lx = ih264d_get_bit_h264(ps_bitstrm); + COPYTHECONTEXT("SH: ref_pic_list_reordering_flag_l1",u1_ref_idx_re_flag_lx); + + /* Modified temporarily */ + if(u1_ref_idx_re_flag_lx) + { + WORD8 ret; + ps_dec->ps_ref_pic_buf_lx[1] = ps_dec->ps_dpb_mgr->ps_mod_dpb[1]; + ret = ih264d_ref_idx_reordering(ps_dec, 1); + if(ret == -1) + return ERROR_REFIDX_ORDER_T; + } + else + ps_dec->ps_ref_pic_buf_lx[1] = ps_dec->ps_dpb_mgr->ps_init_dpb[1]; + + /* Create refIdx to POC mapping */ + { + void **ppv_map_ref_idx_to_poc_lx; + WORD8 idx; + struct pic_buffer_t *ps_pic; + + ppv_map_ref_idx_to_poc_lx = ps_dec->ppv_map_ref_idx_to_poc + FRM_LIST_L0; + ppv_map_ref_idx_to_poc_lx[0] = 0; + ppv_map_ref_idx_to_poc_lx++; + for(idx = 0; idx < ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[0]; + idx++) + { + ps_pic = ps_dec->ps_ref_pic_buf_lx[0][idx]; + ppv_map_ref_idx_to_poc_lx[idx] = (ps_pic->pu1_buf1); + } + + ppv_map_ref_idx_to_poc_lx = ps_dec->ppv_map_ref_idx_to_poc + FRM_LIST_L1; + + ppv_map_ref_idx_to_poc_lx[0] = 0; + ppv_map_ref_idx_to_poc_lx++; + for(idx = 0; idx < ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[1]; + idx++) + { + ps_pic = ps_dec->ps_ref_pic_buf_lx[1][idx]; + ppv_map_ref_idx_to_poc_lx[idx] = (ps_pic->pu1_buf1); + } + + if(ps_dec->ps_cur_slice->u1_mbaff_frame_flag) + { + void **ppv_map_ref_idx_to_poc_lx_t, **ppv_map_ref_idx_to_poc_lx_b; + + ppv_map_ref_idx_to_poc_lx_t = ps_dec->ppv_map_ref_idx_to_poc + + TOP_LIST_FLD_L0; + ppv_map_ref_idx_to_poc_lx_b = ps_dec->ppv_map_ref_idx_to_poc + + BOT_LIST_FLD_L0; + + ppv_map_ref_idx_to_poc_lx_t[0] = 0; + ppv_map_ref_idx_to_poc_lx_t++; + ppv_map_ref_idx_to_poc_lx_b[0] = 0; + ppv_map_ref_idx_to_poc_lx_b++; + for(idx = 0; idx < ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[0]; + idx++) + { + ps_pic = ps_dec->ps_ref_pic_buf_lx[0][idx]; + ppv_map_ref_idx_to_poc_lx_t[0] = (ps_pic->pu1_buf1); + ppv_map_ref_idx_to_poc_lx_b[1] = (ps_pic->pu1_buf1); + + ppv_map_ref_idx_to_poc_lx_b[0] = (ps_pic->pu1_buf1) + 1; + ppv_map_ref_idx_to_poc_lx_t[1] = (ps_pic->pu1_buf1) + 1; + + ppv_map_ref_idx_to_poc_lx_t += 2; + ppv_map_ref_idx_to_poc_lx_b += 2; + } + + ppv_map_ref_idx_to_poc_lx_t = ps_dec->ppv_map_ref_idx_to_poc + + TOP_LIST_FLD_L1; + ppv_map_ref_idx_to_poc_lx_b = ps_dec->ppv_map_ref_idx_to_poc + + BOT_LIST_FLD_L1; + + ppv_map_ref_idx_to_poc_lx_t[0] = 0; + ppv_map_ref_idx_to_poc_lx_t++; + ppv_map_ref_idx_to_poc_lx_b[0] = 0; + ppv_map_ref_idx_to_poc_lx_b++; + for(idx = 0; idx < ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[1]; + idx++) + { + UWORD8 u1_tmp_idx = idx << 1; + ps_pic = ps_dec->ps_ref_pic_buf_lx[1][idx]; + ppv_map_ref_idx_to_poc_lx_t[u1_tmp_idx] = (ps_pic->pu1_buf1); + ppv_map_ref_idx_to_poc_lx_b[u1_tmp_idx + 1] = (ps_pic->pu1_buf1); + + ppv_map_ref_idx_to_poc_lx_b[u1_tmp_idx] = (ps_pic->pu1_buf1) + 1; + ppv_map_ref_idx_to_poc_lx_t[u1_tmp_idx + 1] = (ps_pic->pu1_buf1) + 1; + + } + } + + if(ps_dec->u4_num_cores >= 3) + { + WORD32 num_entries; + WORD32 size; + num_entries = MAX_FRAMES; + if((1 >= ps_dec->ps_cur_sps->u1_num_ref_frames) && + (0 == ps_dec->i4_display_delay)) + { + num_entries = 1; + } + + num_entries = ((2 * num_entries) + 1); + num_entries *= 2; + + size = num_entries * sizeof(void *); + size += PAD_MAP_IDX_POC * sizeof(void *); + + memcpy((void *)ps_dec->ps_parse_cur_slice->ppv_map_ref_idx_to_poc, + ps_dec->ppv_map_ref_idx_to_poc, + size); + } + + } + + if(ps_dec->ps_cur_slice->u1_mbaff_frame_flag + && (ps_dec->ps_cur_slice->u1_field_pic_flag == 0)) + { + ih264d_convert_frm_mbaff_list(ps_dec); + } + + if(ps_pps->u1_wted_bipred_idc == 1) + { + ret = ih264d_parse_pred_weight_table(ps_slice, ps_bitstrm); + if(ret != OK) + return ret; + ih264d_form_pred_weight_matrix(ps_dec); + ps_dec->pu4_wt_ofsts = ps_dec->pu4_wts_ofsts_mat; + } + else if(ps_pps->u1_wted_bipred_idc == 2) + { + /* Implicit Weighted prediction */ + ps_slice->u2_log2Y_crwd = 0x0505; + ps_dec->pu4_wt_ofsts = ps_dec->pu4_wts_ofsts_mat; + ih264d_get_implicit_weights(ps_dec); + } + else + ps_dec->ps_cur_slice->u2_log2Y_crwd = 0; + + ps_dec->ps_parse_cur_slice->u2_log2Y_crwd = + ps_dec->ps_cur_slice->u2_log2Y_crwd; + + /* G050 */ + if(ps_slice->u1_nal_ref_idc != 0) + { + if(!ps_dec->ps_dpb_cmds->u1_dpb_commands_read) + { + i_temp = ih264d_read_mmco_commands(ps_dec); + if (i_temp < 0) + { + return ERROR_DBP_MANAGER_T; + } + ps_dec->u4_bitoffset = i_temp; + } + else + ps_bitstrm->u4_ofst += ps_dec->u4_bitoffset; + } + /* G050 */ + + if(ps_pps->u1_entropy_coding_mode == CABAC) + { + u4_temp = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); + if(u4_temp > MAX_CABAC_INIT_IDC) + { + return ERROR_INV_SLICE_HDR_T; + } + ps_slice->u1_cabac_init_idc = u4_temp; + COPYTHECONTEXT("SH: cabac_init_idc",ps_slice->u1_cabac_init_idc); + } + + /* Read slice_qp_delta */ + WORD64 i8_temp = (WORD64)ps_pps->u1_pic_init_qp + + ih264d_sev(pu4_bitstrm_ofst, pu4_bitstrm_buf); + if((i8_temp < MIN_H264_QP) || (i8_temp > MAX_H264_QP)) + { + return ERROR_INV_RANGE_QP_T; + } + ps_slice->u1_slice_qp = i8_temp; + COPYTHECONTEXT("SH: slice_qp_delta", + (WORD8)(ps_slice->u1_slice_qp - ps_pps->u1_pic_init_qp)); + + if(ps_pps->u1_deblocking_filter_parameters_present_flag == 1) + { + u4_temp = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); + if(u4_temp > SLICE_BOUNDARY_DBLK_DISABLED) + { + return ERROR_INV_SLICE_HDR_T; + } COPYTHECONTEXT("SH: disable_deblocking_filter_idc", u4_temp); + ps_slice->u1_disable_dblk_filter_idc = u4_temp; + if(u4_temp != 1) + { + i_temp = ih264d_sev(pu4_bitstrm_ofst, pu4_bitstrm_buf) + << 1; + if((MIN_DBLK_FIL_OFF > i_temp) || (i_temp > MAX_DBLK_FIL_OFF)) + { + return ERROR_INV_SLICE_HDR_T; + } + ps_slice->i1_slice_alpha_c0_offset = i_temp; + COPYTHECONTEXT("SH: slice_alpha_c0_offset_div2", + ps_slice->i1_slice_alpha_c0_offset >> 1); + + i_temp = ih264d_sev(pu4_bitstrm_ofst, pu4_bitstrm_buf) + << 1; + if((MIN_DBLK_FIL_OFF > i_temp) || (i_temp > MAX_DBLK_FIL_OFF)) + { + return ERROR_INV_SLICE_HDR_T; + } + ps_slice->i1_slice_beta_offset = i_temp; + COPYTHECONTEXT("SH: slice_beta_offset_div2", + ps_slice->i1_slice_beta_offset >> 1); + + } + else + { + ps_slice->i1_slice_alpha_c0_offset = 0; + ps_slice->i1_slice_beta_offset = 0; + } + } + else + { + ps_slice->u1_disable_dblk_filter_idc = 0; + ps_slice->i1_slice_alpha_c0_offset = 0; + ps_slice->i1_slice_beta_offset = 0; + } + + ps_dec->u1_slice_header_done = 2; + + if(ps_pps->u1_entropy_coding_mode) + { + SWITCHOFFTRACE; SWITCHONTRACECABAC; + ps_dec->pf_parse_inter_slice = ih264d_parse_inter_slice_data_cabac; + ps_dec->pf_parse_inter_mb = ih264d_parse_bmb_cabac; + ih264d_init_cabac_contexts(B_SLICE, ps_dec); + + if(ps_dec->ps_cur_slice->u1_mbaff_frame_flag) + ps_dec->pf_get_mb_info = ih264d_get_mb_info_cabac_mbaff; + else + ps_dec->pf_get_mb_info = ih264d_get_mb_info_cabac_nonmbaff; + } + else + { + SWITCHONTRACE; SWITCHOFFTRACECABAC; + ps_dec->pf_parse_inter_slice = ih264d_parse_inter_slice_data_cavlc; + ps_dec->pf_parse_inter_mb = ih264d_parse_bmb_cavlc; + if(ps_dec->ps_cur_slice->u1_mbaff_frame_flag) + ps_dec->pf_get_mb_info = ih264d_get_mb_info_cavlc_mbaff; + else + ps_dec->pf_get_mb_info = ih264d_get_mb_info_cavlc_nonmbaff; + } + + ret = ih264d_cal_col_pic(ps_dec); + if(ret != OK) + return ret; + ps_dec->u1_B = 1; + ps_dec->pf_mvpred_ref_tfr_nby2mb = ih264d_mv_pred_ref_tfr_nby2_bmb; + ret = ps_dec->pf_parse_inter_slice(ps_dec, ps_slice, u2_first_mb_in_slice); + if(ret != OK) + return ret; + return OK; +} + diff --git a/dependencies/ih264d/decoder/ih264d_parse_cabac.c b/dependencies/ih264d/decoder/ih264d_parse_cabac.c new file mode 100644 index 00000000..c4a3bd5a --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_parse_cabac.c @@ -0,0 +1,1607 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/*! + *************************************************************************** + * \file ih264d_parse_cabac.c + * + * \brief + * This file contains cabac Residual decoding routines. + * + * \date + * 20/03/2003 + * + * \author NS + *************************************************************************** + */ + +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264d_defs.h" +#include "ih264d_structs.h" + +#include "ih264d_cabac.h" +#include "ih264d_bitstrm.h" +#include "ih264d_parse_mb_header.h" +#include "ih264d_debug.h" +#include "ih264d_tables.h" +#include "ih264d_error_handler.h" +#include "ih264d_parse_cabac.h" +#include "ih264d_parse_slice.h" +#include "ih264d_tables.h" +#include "ih264d_mb_utils.h" +#include "ih264d_utils.h" + +/*! + ******************************************************************************** + * \if Function name : ih264d_read_coeff4x4_cabac \endif + * + * \brief This function encodes residual_block_cabac as defined in 7.3.5.3.2. + * + * \return + * Returns the index of last significant coeff. + * + ******************************************************************************** + */ + +UWORD8 ih264d_read_coeff4x4_cabac(dec_bit_stream_t *ps_bitstrm, + UWORD32 u4_ctxcat, + bin_ctxt_model_t *ps_ctxt_sig_coeff, + dec_struct_t *ps_dec, /*!< pointer to access global variables*/ + bin_ctxt_model_t *ps_ctxt_coded) +{ + + decoding_envirnoment_t *ps_cab_env = &ps_dec->s_cab_dec_env; + UWORD32 u4_coded_flag; + UWORD32 u4_offset, *pu4_buffer; + UWORD32 u4_code_int_range, u4_code_int_val_ofst; + tu_sblk4x4_coeff_data_t *ps_tu_4x4; + WORD16 *pi2_coeff_data; + WORD32 num_sig_coeffs = 0; + + /*loading from strcuctures*/ + + ps_tu_4x4 = (tu_sblk4x4_coeff_data_t *)ps_dec->pv_parse_tu_coeff_data; + ps_tu_4x4->u2_sig_coeff_map = 0; + pi2_coeff_data = &ps_tu_4x4->ai2_level[0]; + + u4_offset = ps_bitstrm->u4_ofst; + pu4_buffer = ps_bitstrm->pu4_buffer; + + u4_code_int_range = ps_cab_env->u4_code_int_range; + u4_code_int_val_ofst = ps_cab_env->u4_code_int_val_ofst; + + { + + /*inilined DecodeDecision_onebin begins*/ + + { + + UWORD32 u4_qnt_int_range, u4_int_range_lps; + UWORD32 u4_symbol, u1_mps_state; + + UWORD32 table_lookup; + const UWORD32 *pu4_table = (const UWORD32 *)ps_cab_env->cabac_table; + UWORD32 u4_clz; + + u1_mps_state = (ps_ctxt_coded->u1_mps_state); + u4_clz = CLZ(u4_code_int_range); + u4_qnt_int_range = u4_code_int_range << u4_clz; + u4_qnt_int_range = (u4_qnt_int_range >> 29) & 0x3; + table_lookup = + pu4_table[(u1_mps_state << 2) + u4_qnt_int_range]; + u4_int_range_lps = table_lookup & 0xff; + u4_int_range_lps = u4_int_range_lps << (23 - u4_clz); + u4_code_int_range = u4_code_int_range - u4_int_range_lps; + u4_symbol = ((u1_mps_state >> 6) & 0x1); + u1_mps_state = (table_lookup >> 8) & 0x7F; + + CHECK_IF_LPS(u4_code_int_range, u4_code_int_val_ofst, u4_symbol, + u4_int_range_lps, u1_mps_state, table_lookup) + + if(u4_code_int_range < ONE_RIGHT_SHIFTED_BY_8) + { + + RENORM_RANGE_OFFSET(u4_code_int_range, u4_code_int_val_ofst, + u4_offset, pu4_buffer) + } + + ps_ctxt_coded->u1_mps_state = u1_mps_state; + u4_coded_flag = u4_symbol; + + /*inilined DecodeDecision_onebin ends*/ + + } + + } + + if(u4_coded_flag) + { + + { + bin_ctxt_model_t *p_binCtxt_last, *p_binCtxt_last_org; + UWORD32 uc_last_coeff_idx; + UWORD32 uc_bin; + UWORD32 i; + WORD32 first_coeff_offset = 0; + + if((u4_ctxcat == CHROMA_AC_CTXCAT) || (u4_ctxcat == LUMA_AC_CTXCAT)) + { + first_coeff_offset = 1; + } + + i = 0; + if(u4_ctxcat == CHROMA_DC_CTXCAT) + { + uc_last_coeff_idx = 3; + } + else + { + UWORD32 u4_start; + u4_start = (u4_ctxcat & 1) + (u4_ctxcat >> 2); + uc_last_coeff_idx = 15 - u4_start; + } + p_binCtxt_last_org = ps_ctxt_sig_coeff + + LAST_COEFF_CTXT_MINUS_SIG_COEFF_CTXT; + + do + { + + /*inilined DecodeDecision_onebin begins*/ + { + + UWORD32 u4_qnt_int_range, u4_int_range_lps; + UWORD32 u4_symbol, u1_mps_state; + UWORD32 table_lookup; + const UWORD32 *pu4_table = + (const UWORD32 *)ps_cab_env->cabac_table; + UWORD32 u4_clz; + + u1_mps_state = (ps_ctxt_sig_coeff->u1_mps_state); + + u4_clz = CLZ(u4_code_int_range); + + u4_qnt_int_range = u4_code_int_range << u4_clz; + u4_qnt_int_range = (u4_qnt_int_range >> 29) & 0x3; + + table_lookup = pu4_table[(u1_mps_state << 2) + + u4_qnt_int_range]; + + u4_int_range_lps = table_lookup & 0xff; + + u4_int_range_lps = u4_int_range_lps << (23 - u4_clz); + u4_code_int_range = u4_code_int_range - u4_int_range_lps; + u4_symbol = ((u1_mps_state >> 6) & 0x1); + u1_mps_state = (table_lookup >> 8) & 0x7F; + + CHECK_IF_LPS(u4_code_int_range, u4_code_int_val_ofst, + u4_symbol, u4_int_range_lps, u1_mps_state, + table_lookup) + + if(u4_code_int_range < ONE_RIGHT_SHIFTED_BY_14) + { + + UWORD32 read_bits, u4_clz; + u4_clz = CLZ(u4_code_int_range); + NEXTBITS(read_bits, (u4_offset + 23), pu4_buffer, + u4_clz) + FLUSHBITS(u4_offset, (u4_clz)) + u4_code_int_range = u4_code_int_range << u4_clz; + u4_code_int_val_ofst = (u4_code_int_val_ofst << u4_clz) + | read_bits; + } + + INC_BIN_COUNT( + ps_cab_env) + + ps_ctxt_sig_coeff->u1_mps_state = u1_mps_state; + uc_bin = u4_symbol; + + } + /*incrementing pointer to point to the context of the next bin*/ + ps_ctxt_sig_coeff++; + + /*inilined DecodeDecision_onebin ends*/ + + if(uc_bin) + { + num_sig_coeffs++; + SET_BIT(ps_tu_4x4->u2_sig_coeff_map, (i + first_coeff_offset)); + + p_binCtxt_last = p_binCtxt_last_org + i; + + /*inilined DecodeDecision_onebin begins*/ + + { + + UWORD32 u4_qnt_int_range, u4_int_range_lps; + UWORD32 u4_symbol, u1_mps_state; + UWORD32 table_lookup; + const UWORD32 *pu4_table = + (const UWORD32 *)ps_cab_env->cabac_table; + UWORD32 u4_clz; + + u1_mps_state = (p_binCtxt_last->u1_mps_state); + + u4_clz = CLZ(u4_code_int_range); + u4_qnt_int_range = u4_code_int_range << u4_clz; + u4_qnt_int_range = (u4_qnt_int_range >> 29) + & 0x3; + + table_lookup = pu4_table[(u1_mps_state << 2) + + u4_qnt_int_range]; + u4_int_range_lps = table_lookup & 0xff; + + u4_int_range_lps = u4_int_range_lps + << (23 - u4_clz); + + u4_code_int_range = u4_code_int_range + - u4_int_range_lps; + u4_symbol = ((u1_mps_state >> 6) & 0x1); + u1_mps_state = (table_lookup >> 8) & 0x7F; + + CHECK_IF_LPS(u4_code_int_range, u4_code_int_val_ofst, + u4_symbol, u4_int_range_lps, + u1_mps_state, table_lookup) + + INC_BIN_COUNT(ps_cab_env) + + p_binCtxt_last->u1_mps_state = u1_mps_state; + uc_bin = u4_symbol; + + } + + /*inilined DecodeDecision_onebin ends*/ + if(uc_bin == 1) + goto label_read_levels; + + } + + i = i + 1; + + } + while(i < uc_last_coeff_idx); + + num_sig_coeffs++; + SET_BIT(ps_tu_4x4->u2_sig_coeff_map, (i + first_coeff_offset)); + + label_read_levels: ; + + } + + /// VALUE of No of Coeff in BLOCK = i + 1 for second case else i; + + /* Decode coeff_abs_level_minus1 and coeff_sign_flag */ + { + + WORD32 i2_abs_lvl; + UWORD32 u1_abs_level_equal1 = 1, u1_abs_level_gt1 = 0; + + UWORD32 u4_ctx_inc; + UWORD32 ui_prefix; + bin_ctxt_model_t *p_ctxt_abs_level; + + + p_ctxt_abs_level = ps_dec->p_coeff_abs_level_minus1_t[u4_ctxcat]; + u4_ctx_inc = ((0x51)); + + /*****************************************************/ + /* Main Loop runs for no. of Significant coefficient */ + /*****************************************************/ + + + do + { + + { + INC_SYM_COUNT(&(ps_dec.s_cab_dec_env)); + + /*****************************************************/ + /* inilining a modified ih264d_decode_bins_unary */ + /*****************************************************/ + + { + UWORD32 u4_value; + UWORD32 u4_symbol; + bin_ctxt_model_t *ps_bin_ctxt; + UWORD32 u4_ctx_Inc; + + u4_value = 0; + + u4_ctx_Inc = u4_ctx_inc & 0xf; + ps_bin_ctxt = p_ctxt_abs_level + u4_ctx_Inc; + + do + { + + { + + UWORD32 u4_qnt_int_range, + u4_int_range_lps; + UWORD32 u1_mps_state; + UWORD32 table_lookup; + const UWORD32 *pu4_table = + (const UWORD32 *)ps_cab_env->cabac_table; + UWORD32 u4_clz; + + u1_mps_state = (ps_bin_ctxt->u1_mps_state); + u4_clz = CLZ(u4_code_int_range); + u4_qnt_int_range = u4_code_int_range + << u4_clz; + u4_qnt_int_range = (u4_qnt_int_range + >> 29) & 0x3; + table_lookup = pu4_table[(u1_mps_state << 2) + + u4_qnt_int_range]; + u4_int_range_lps = table_lookup & 0xff; + + u4_int_range_lps = u4_int_range_lps + << (23 - u4_clz); + u4_code_int_range = u4_code_int_range + - u4_int_range_lps; + u4_symbol = ((u1_mps_state >> 6) & 0x1); + u1_mps_state = (table_lookup >> 8) & 0x7F; + + CHECK_IF_LPS(u4_code_int_range, + u4_code_int_val_ofst, u4_symbol, + u4_int_range_lps, u1_mps_state, + table_lookup) + + if(u4_code_int_range < ONE_RIGHT_SHIFTED_BY_9) + { + + RENORM_RANGE_OFFSET(u4_code_int_range, + u4_code_int_val_ofst, + u4_offset, pu4_buffer) + } + + INC_BIN_COUNT(ps_cab_env); + + ps_bin_ctxt->u1_mps_state = u1_mps_state; + } + + INC_BIN_COUNT(ps_cab_env);INC_DECISION_BINS(ps_cab_env); + + u4_value++; + ps_bin_ctxt = p_ctxt_abs_level + (u4_ctx_inc >> 4); + + } + while(u4_symbol && (u4_value < UCOFF_LEVEL)); + + ui_prefix = u4_value - 1 + u4_symbol; + + } + + if(ui_prefix == UCOFF_LEVEL) + { + UWORD32 ui16_sufS = 0; + UWORD32 u1_max_bins; + UWORD32 u4_value; + + i2_abs_lvl = UCOFF_LEVEL; + /*inlining ih264d_decode_bypass_bins_unary begins*/ + + { + UWORD32 uc_bin; + UWORD32 bits_to_flush; + + + bits_to_flush = 0; + /*renormalize to ensure there 23 bits more in the u4_code_int_val_ofst*/ + { + UWORD32 u4_clz, read_bits; + + u4_clz = CLZ(u4_code_int_range); + FLUSHBITS(u4_offset, u4_clz) + NEXTBITS(read_bits, u4_offset, pu4_buffer, CABAC_BITS_TO_READ) + u4_code_int_range = u4_code_int_range << u4_clz; + u4_code_int_val_ofst = (u4_code_int_val_ofst + << u4_clz) | read_bits; + + } + + do + { + bits_to_flush++; + + u4_code_int_range = u4_code_int_range >> 1; + + if(u4_code_int_val_ofst >= u4_code_int_range) + { + /* S=1 */ + uc_bin = 1; + u4_code_int_val_ofst -= u4_code_int_range; + } + else + { + /* S=0 */ + uc_bin = 0; + } + + INC_BIN_COUNT( + ps_cab_env);INC_BYPASS_BINS(ps_cab_env); + + } + while(uc_bin && (bits_to_flush < CABAC_BITS_TO_READ)); + + u4_value = (bits_to_flush - 1); + + } + /*inlining ih264d_decode_bypass_bins_unary ends*/ + + ui16_sufS = (1 << u4_value); + u1_max_bins = u4_value; + + if(u4_value > 0) + { + + /*inline bypassbins_flc begins*/ + + if(u4_value > 10) + { + UWORD32 u4_clz, read_bits; + + u4_clz = CLZ(u4_code_int_range); + FLUSHBITS(u4_offset, u4_clz) + NEXTBITS(read_bits, u4_offset, pu4_buffer, CABAC_BITS_TO_READ) + u4_code_int_range = u4_code_int_range << u4_clz; + u4_code_int_val_ofst = (u4_code_int_val_ofst + << u4_clz) | read_bits; + } + + { + UWORD32 ui_bins; + UWORD32 uc_bin; + UWORD32 bits_to_flush; + + ui_bins = 0; + bits_to_flush = 0; + + do + { + bits_to_flush++; + + u4_code_int_range = u4_code_int_range >> 1; + + if(u4_code_int_val_ofst + >= u4_code_int_range) + { + /* S=1 */ + uc_bin = 1; + u4_code_int_val_ofst -= + u4_code_int_range; + } + else + { + /* S=0 */ + uc_bin = 0; + } + + INC_BIN_COUNT( + ps_cab_env);INC_BYPASS_BINS(ps_cab_env); + + ui_bins = ((ui_bins << 1) | uc_bin); + + } + while(bits_to_flush < u1_max_bins); + + u4_value = ui_bins; + } + + /*inline bypassbins_flc ends*/ + + } + + //Value of K + ui16_sufS += u4_value; + i2_abs_lvl += ui16_sufS; + + } + else + i2_abs_lvl = 1 + ui_prefix; + + if(i2_abs_lvl > 1) + { + u1_abs_level_gt1++; + } + if(!u1_abs_level_gt1) + { + u1_abs_level_equal1++; + u4_ctx_inc = (5 << 4) + MIN(u1_abs_level_equal1, 4); + } + else + u4_ctx_inc = (5 + MIN(u1_abs_level_gt1, 4)) << 4; + + /*u4_ctx_inc = g_table_temp[u1_abs_level_gt1][u1_abs_level_equal1];*/ + + /* encode coeff_sign_flag[i] */ + + { + u4_code_int_range = u4_code_int_range >> 1; + + if(u4_code_int_val_ofst >= (u4_code_int_range)) + { + /* S=1 */ + u4_code_int_val_ofst -= u4_code_int_range; + i2_abs_lvl = (-i2_abs_lvl); + } + + } + num_sig_coeffs--; + *pi2_coeff_data++ = i2_abs_lvl; + } + } + while(num_sig_coeffs > 0); + } + } + + if(u4_coded_flag) + { + WORD32 offset; + offset = (UWORD8 *)pi2_coeff_data - (UWORD8 *)ps_tu_4x4; + offset = ALIGN4(offset); + ps_dec->pv_parse_tu_coeff_data = (void *)((UWORD8 *)ps_dec->pv_parse_tu_coeff_data + offset); + } + + + /*updating structures*/ + ps_cab_env->u4_code_int_val_ofst = u4_code_int_val_ofst; + ps_cab_env->u4_code_int_range = u4_code_int_range; + ps_bitstrm->u4_ofst = u4_offset; + return (u4_coded_flag); +} +/*! + ******************************************************************************** + * \if Function name : ih264d_read_coeff8x8_cabac \endif + * + * \brief This function encodes residual_block_cabac as defined in 7.3.5.3.2. + when transform_8x8_flag = 1 + * + * \return + * Returns the index of last significant coeff. + * + ******************************************************************************** + */ + +void ih264d_read_coeff8x8_cabac(dec_bit_stream_t *ps_bitstrm, + dec_struct_t *ps_dec, /*!< pointer to access global variables*/ + dec_mb_info_t *ps_cur_mb_info) +{ + decoding_envirnoment_t *ps_cab_env = &ps_dec->s_cab_dec_env; + UWORD32 u4_offset, *pu4_buffer; + UWORD32 u4_code_int_range, u4_code_int_val_ofst; + + /* High profile related declarations */ + UWORD8 u1_field_coding_flag = ps_cur_mb_info->ps_curmb->u1_mb_fld; + const UWORD8 *pu1_lastcoeff_context_inc = + (UWORD8 *)gau1_ih264d_lastcoeff_context_inc; + const UWORD8 *pu1_sigcoeff_context_inc; + bin_ctxt_model_t *ps_ctxt_sig_coeff; + WORD32 num_sig_coeffs = 0; + tu_blk8x8_coeff_data_t *ps_tu_8x8; + WORD16 *pi2_coeff_data; + + /*loading from strcuctures*/ + + ps_tu_8x8 = (tu_blk8x8_coeff_data_t *)ps_dec->pv_parse_tu_coeff_data; + ps_tu_8x8->au4_sig_coeff_map[0] = 0; + ps_tu_8x8->au4_sig_coeff_map[1] = 0; + pi2_coeff_data = &ps_tu_8x8->ai2_level[0]; + + + if(!u1_field_coding_flag) + { + pu1_sigcoeff_context_inc = + (UWORD8 *)gau1_ih264d_sigcoeff_context_inc_frame; + + /*******************************************************************/ + /* last coefficient context is derived from significant coeff u4_flag */ + /* only significant coefficient matrix need to be initialized */ + /*******************************************************************/ + ps_ctxt_sig_coeff = ps_dec->s_high_profile.ps_sigcoeff_8x8_frame; + } + else + { + pu1_sigcoeff_context_inc = + (UWORD8 *)gau1_ih264d_sigcoeff_context_inc_field; + + /*******************************************************************/ + /* last coefficient context is derived from significant coeff u4_flag */ + /* only significant coefficient matrix need to be initialized */ + /*******************************************************************/ + ps_ctxt_sig_coeff = ps_dec->s_high_profile.ps_sigcoeff_8x8_field; + } + + /*loading from strcuctures*/ + + u4_offset = ps_bitstrm->u4_ofst; + pu4_buffer = ps_bitstrm->pu4_buffer; + + u4_code_int_range = ps_cab_env->u4_code_int_range; + u4_code_int_val_ofst = ps_cab_env->u4_code_int_val_ofst; + + { + { + bin_ctxt_model_t *p_binCtxt_last, *p_binCtxt_last_org, + *p_ctxt_sig_coeff_org; + UWORD32 uc_last_coeff_idx; + UWORD32 uc_bin; + UWORD32 i; + + i = 0; + + uc_last_coeff_idx = 63; + + p_binCtxt_last_org = ps_ctxt_sig_coeff + + LAST_COEFF_CTXT_MINUS_SIG_COEFF_CTXT_8X8; + + p_ctxt_sig_coeff_org = ps_ctxt_sig_coeff; + + do + { + /*inilined DecodeDecision_onebin begins*/ + { + UWORD32 u4_qnt_int_range, u4_int_range_lps; + UWORD32 u4_symbol, u1_mps_state; + UWORD32 table_lookup; + const UWORD32 *pu4_table = + (const UWORD32 *)ps_cab_env->cabac_table; + UWORD32 u4_clz; + + u1_mps_state = (ps_ctxt_sig_coeff->u1_mps_state); + + u4_clz = CLZ(u4_code_int_range); + + u4_qnt_int_range = u4_code_int_range << u4_clz; + u4_qnt_int_range = (u4_qnt_int_range >> 29) & 0x3; + + table_lookup = pu4_table[(u1_mps_state << 2) + + u4_qnt_int_range]; + + u4_int_range_lps = table_lookup & 0xff; + + u4_int_range_lps = u4_int_range_lps << (23 - u4_clz); + u4_code_int_range = u4_code_int_range - u4_int_range_lps; + u4_symbol = ((u1_mps_state >> 6) & 0x1); + u1_mps_state = (table_lookup >> 8) & 0x7F; + + CHECK_IF_LPS(u4_code_int_range, u4_code_int_val_ofst, + u4_symbol, u4_int_range_lps, u1_mps_state, + table_lookup) + + if(u4_code_int_range < ONE_RIGHT_SHIFTED_BY_14) + { + UWORD32 read_bits, u4_clz; + u4_clz = CLZ(u4_code_int_range); + NEXTBITS(read_bits, (u4_offset + 23), pu4_buffer, + u4_clz) + FLUSHBITS(u4_offset, (u4_clz)) + u4_code_int_range = u4_code_int_range << u4_clz; + u4_code_int_val_ofst = (u4_code_int_val_ofst << u4_clz) + | read_bits; + } + + ps_ctxt_sig_coeff->u1_mps_state = u1_mps_state; + uc_bin = u4_symbol; + } + /*incrementing pointer to point to the context of the next bin*/ + ps_ctxt_sig_coeff = p_ctxt_sig_coeff_org + + pu1_sigcoeff_context_inc[i + 1]; + + /*inilined DecodeDecision_onebin ends*/ + if(uc_bin) + { + num_sig_coeffs++; + SET_BIT(ps_tu_8x8->au4_sig_coeff_map[i>31], (i > 31 ? i - 32:i)); + + p_binCtxt_last = p_binCtxt_last_org + + pu1_lastcoeff_context_inc[i]; + + /*inilined DecodeDecision_onebin begins*/ + + { + UWORD32 u4_qnt_int_range, u4_int_range_lps; + UWORD32 u4_symbol, u1_mps_state; + UWORD32 table_lookup; + const UWORD32 *pu4_table = + (const UWORD32 *)ps_cab_env->cabac_table; + UWORD32 u4_clz; + + u1_mps_state = (p_binCtxt_last->u1_mps_state); + + u4_clz = CLZ(u4_code_int_range); + u4_qnt_int_range = u4_code_int_range << u4_clz; + u4_qnt_int_range = (u4_qnt_int_range >> 29) + & 0x3; + + table_lookup = pu4_table[(u1_mps_state << 2) + + u4_qnt_int_range]; + u4_int_range_lps = table_lookup & 0xff; + + u4_int_range_lps = u4_int_range_lps + << (23 - u4_clz); + + u4_code_int_range = u4_code_int_range + - u4_int_range_lps; + u4_symbol = ((u1_mps_state >> 6) & 0x1); + u1_mps_state = (table_lookup >> 8) & 0x7F; + + CHECK_IF_LPS(u4_code_int_range, u4_code_int_val_ofst, + u4_symbol, u4_int_range_lps, + u1_mps_state, table_lookup) + + p_binCtxt_last->u1_mps_state = u1_mps_state; + uc_bin = u4_symbol; + } + + /*inilined DecodeDecision_onebin ends*/ + if(uc_bin == 1) + goto label_read_levels; + + } + + i = i + 1; + + } + while(i < uc_last_coeff_idx); + + num_sig_coeffs++; + SET_BIT(ps_tu_8x8->au4_sig_coeff_map[i>31], (i > 31 ? i - 32:i)); + + label_read_levels: ; + } + + /// VALUE of No of Coeff in BLOCK = i + 1 for second case else i; + + /* Decode coeff_abs_level_minus1 and coeff_sign_flag */ + { + WORD32 i2_abs_lvl; + UWORD32 u1_abs_level_equal1 = 1, u1_abs_level_gt1 = 0; + + UWORD32 u4_ctx_inc; + UWORD32 ui_prefix; + bin_ctxt_model_t *p_ctxt_abs_level; + + p_ctxt_abs_level = + ps_dec->p_coeff_abs_level_minus1_t[LUMA_8X8_CTXCAT]; + u4_ctx_inc = ((0x51)); + + /*****************************************************/ + /* Main Loop runs for no. of Significant coefficient */ + /*****************************************************/ + do + { + { + + /*****************************************************/ + /* inilining a modified ih264d_decode_bins_unary */ + /*****************************************************/ + + { + UWORD32 u4_value; + UWORD32 u4_symbol; + bin_ctxt_model_t *ps_bin_ctxt; + UWORD32 u4_ctx_Inc; + u4_value = 0; + + u4_ctx_Inc = u4_ctx_inc & 0xf; + ps_bin_ctxt = p_ctxt_abs_level + u4_ctx_Inc; + + do + { + { + UWORD32 u4_qnt_int_range, + u4_int_range_lps; + UWORD32 u1_mps_state; + UWORD32 table_lookup; + const UWORD32 *pu4_table = + (const UWORD32 *)ps_cab_env->cabac_table; + UWORD32 u4_clz; + + u1_mps_state = (ps_bin_ctxt->u1_mps_state); + u4_clz = CLZ(u4_code_int_range); + u4_qnt_int_range = u4_code_int_range + << u4_clz; + u4_qnt_int_range = (u4_qnt_int_range + >> 29) & 0x3; + table_lookup = pu4_table[(u1_mps_state << 2) + + u4_qnt_int_range]; + u4_int_range_lps = table_lookup & 0xff; + + u4_int_range_lps = u4_int_range_lps + << (23 - u4_clz); + u4_code_int_range = u4_code_int_range + - u4_int_range_lps; + u4_symbol = ((u1_mps_state >> 6) & 0x1); + u1_mps_state = (table_lookup >> 8) & 0x7F; + + CHECK_IF_LPS(u4_code_int_range, + u4_code_int_val_ofst, u4_symbol, + u4_int_range_lps, u1_mps_state, + table_lookup) + + if(u4_code_int_range < ONE_RIGHT_SHIFTED_BY_9) + { + + RENORM_RANGE_OFFSET(u4_code_int_range, + u4_code_int_val_ofst, + u4_offset, pu4_buffer) + } + + ps_bin_ctxt->u1_mps_state = u1_mps_state; + } + + u4_value++; + ps_bin_ctxt = p_ctxt_abs_level + (u4_ctx_inc >> 4); + + } + while(u4_symbol && (u4_value < UCOFF_LEVEL)); + + ui_prefix = u4_value - 1 + u4_symbol; + } + + if(ui_prefix == UCOFF_LEVEL) + { + UWORD32 ui16_sufS = 0; + UWORD32 u1_max_bins; + UWORD32 u4_value; + + i2_abs_lvl = UCOFF_LEVEL; + /*inlining ih264d_decode_bypass_bins_unary begins*/ + + { + UWORD32 uc_bin; + UWORD32 bits_to_flush; + + + bits_to_flush = 0; + /*renormalize to ensure there 23 bits more in the u4_code_int_val_ofst*/ + { + UWORD32 u4_clz, read_bits; + + u4_clz = CLZ(u4_code_int_range); + FLUSHBITS(u4_offset, u4_clz) + NEXTBITS(read_bits, u4_offset, pu4_buffer, CABAC_BITS_TO_READ) + u4_code_int_range = u4_code_int_range << u4_clz; + u4_code_int_val_ofst = (u4_code_int_val_ofst + << u4_clz) | read_bits; + } + + do + { + bits_to_flush++; + + u4_code_int_range = u4_code_int_range >> 1; + + if(u4_code_int_val_ofst >= u4_code_int_range) + { + /* S=1 */ + uc_bin = 1; + u4_code_int_val_ofst -= u4_code_int_range; + } + else + { + /* S=0 */ + uc_bin = 0; + } + + } + while(uc_bin && (bits_to_flush < CABAC_BITS_TO_READ)); + + u4_value = (bits_to_flush - 1); + } + /*inlining ih264d_decode_bypass_bins_unary ends*/ + + ui16_sufS = (1 << u4_value); + u1_max_bins = u4_value; + + if(u4_value > 0) + { + /*inline bypassbins_flc begins*/ + + if(u4_value > 10) + { + UWORD32 u4_clz, read_bits; + + u4_clz = CLZ(u4_code_int_range); + FLUSHBITS(u4_offset, u4_clz) + NEXTBITS(read_bits, u4_offset, pu4_buffer, CABAC_BITS_TO_READ) + u4_code_int_range = u4_code_int_range << u4_clz; + u4_code_int_val_ofst = (u4_code_int_val_ofst + << u4_clz) | read_bits; + } + + { + UWORD32 ui_bins; + UWORD32 uc_bin; + UWORD32 bits_to_flush; + + ui_bins = 0; + bits_to_flush = 0; + + do + { + bits_to_flush++; + + u4_code_int_range = u4_code_int_range >> 1; + + if(u4_code_int_val_ofst + >= u4_code_int_range) + { + /* S=1 */ + uc_bin = 1; + u4_code_int_val_ofst -= + u4_code_int_range; + } + else + { + /* S=0 */ + uc_bin = 0; + } + + ui_bins = ((ui_bins << 1) | uc_bin); + + } + while(bits_to_flush < u1_max_bins); + + u4_value = ui_bins; + } + /*inline bypassbins_flc ends*/ + } + + //Value of K + ui16_sufS += u4_value; + i2_abs_lvl += (WORD32)ui16_sufS; + } + else + { + i2_abs_lvl = 1 + ui_prefix; + } + + if(i2_abs_lvl > 1) + { + u1_abs_level_gt1++; + } + if(!u1_abs_level_gt1) + { + u1_abs_level_equal1++; + u4_ctx_inc = (5 << 4) + MIN(u1_abs_level_equal1, 4); + } + else + { + u4_ctx_inc = (5 + MIN(u1_abs_level_gt1, 4)) << 4; + } + + /*u4_ctx_inc = g_table_temp[u1_abs_level_gt1][u1_abs_level_equal1];*/ + + /* encode coeff_sign_flag[i] */ + + { + u4_code_int_range = u4_code_int_range >> 1; + + if(u4_code_int_val_ofst >= (u4_code_int_range)) + { + /* S=1 */ + u4_code_int_val_ofst -= u4_code_int_range; + i2_abs_lvl = (-i2_abs_lvl); + } + } + + *pi2_coeff_data++ = i2_abs_lvl; + num_sig_coeffs--; + } + } + while(num_sig_coeffs > 0); + } + } + + { + WORD32 offset; + offset = (UWORD8 *)pi2_coeff_data - (UWORD8 *)ps_tu_8x8; + offset = ALIGN4(offset); + ps_dec->pv_parse_tu_coeff_data = (void *)((UWORD8 *)ps_dec->pv_parse_tu_coeff_data + offset); + } + + /*updating structures*/ + ps_cab_env->u4_code_int_val_ofst = u4_code_int_val_ofst; + ps_cab_env->u4_code_int_range = u4_code_int_range; + ps_bitstrm->u4_ofst = u4_offset; +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_cabac_parse_8x8block */ +/* */ +/* Description : This function does the residual parsing of 4 subblocks */ +/* in a 8x8 block. */ +/* */ +/* Inputs : pi2_coeff_block : pointer to residual block where */ +/* decoded and inverse scan coefficients are updated */ +/* */ +/* u4_sub_block_strd : indicates the number of sublocks */ +/* in a row. It is 4 for luma and 2 for chroma. */ +/* */ +/* u4_ctx_cat : inidicates context category for residual */ +/* decoding. */ +/* */ +/* ps_dec : pointer to Decstruct (decoder context) */ +/* */ +/* pu1_top_nnz : top nnz pointer */ +/* */ +/* pu1_left_nnz : left nnz pointer */ +/* */ +/* Globals : No */ +/* Processing : Parsing for four subblocks in unrolled, top and left nnz */ +/* are updated on the fly. csbp is set in accordance to */ +/* decoded numcoeff for the subblock index in raster order */ +/* */ +/* Outputs : The updated residue buffer, nnzs and csbp current block */ +/* */ +/* Returns : Returns the coded sub block pattern csbp for the block */ +/* */ +/* Issues : <List any issues or problems with this function> */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 09 10 2008 Jay Draft */ +/* */ +/*****************************************************************************/ +UWORD32 ih264d_cabac_parse_8x8block(WORD16 *pi2_coeff_block, + UWORD32 u4_sub_block_strd, + UWORD32 u4_ctx_cat, + dec_struct_t * ps_dec, + UWORD8 *pu1_top_nnz, + UWORD8 *pu1_left_nnz) +{ + UWORD32 u4_ctxinc, u4_subblock_coded; + UWORD32 u4_top0, u4_top1; + UWORD32 u4_csbp = 0; + UWORD32 u4_idx = 0; + dec_bit_stream_t * const ps_bitstrm = ps_dec->ps_bitstrm; + bin_ctxt_model_t * const ps_cbf = ps_dec->p_cbf_t[u4_ctx_cat]; + bin_ctxt_model_t *ps_src_bin_ctxt; + bin_ctxt_model_t * const ps_sig_coeff_flag = + ps_dec->p_significant_coeff_flag_t[u4_ctx_cat]; + + UWORD8 *pu1_inv_scan = ps_dec->pu1_inv_scan; + + /*------------------------------------------------------*/ + /* Residual 4x4 decoding: SubBlock 0 */ + /*------------------------------------------------------*/ + u4_ctxinc = ((!!pu1_top_nnz[0]) << 1) + (!!pu1_left_nnz[0]); + + ps_src_bin_ctxt = ps_cbf + u4_ctxinc; + + u4_top0 = ih264d_read_coeff4x4_cabac( ps_bitstrm, + u4_ctx_cat, ps_sig_coeff_flag, ps_dec, + ps_src_bin_ctxt); + + INSERT_BIT(u4_csbp, u4_idx, u4_top0); + + /*------------------------------------------------------*/ + /* Residual 4x4 decoding: SubBlock 1 */ + /*------------------------------------------------------*/ + u4_idx++; + pi2_coeff_block += NUM_COEFFS_IN_4x4BLK; + u4_ctxinc = ((!!pu1_top_nnz[1]) << 1) + u4_top0; + + ps_src_bin_ctxt = ps_cbf + u4_ctxinc; + + u4_top1 = ih264d_read_coeff4x4_cabac(ps_bitstrm, + u4_ctx_cat, ps_sig_coeff_flag, ps_dec, + ps_src_bin_ctxt); + + INSERT_BIT(u4_csbp, u4_idx, u4_top1); + pu1_left_nnz[0] = u4_top1; + + /*------------------------------------------------------*/ + /* Residual 4x4 decoding: SubBlock 2 */ + /*------------------------------------------------------*/ + u4_idx += (u4_sub_block_strd - 1); + pi2_coeff_block += ((u4_sub_block_strd - 1) * NUM_COEFFS_IN_4x4BLK); + u4_ctxinc = (u4_top0 << 1) + (!!pu1_left_nnz[1]); + + ps_src_bin_ctxt = ps_cbf + u4_ctxinc; + + u4_subblock_coded = ih264d_read_coeff4x4_cabac(ps_bitstrm, u4_ctx_cat, + ps_sig_coeff_flag, ps_dec, + ps_src_bin_ctxt); + + INSERT_BIT(u4_csbp, u4_idx, u4_subblock_coded); + pu1_top_nnz[0] = u4_subblock_coded; + + /*------------------------------------------------------*/ + /* Residual 4x4 decoding: SubBlock 3 */ + /*------------------------------------------------------*/ + u4_idx++; + pi2_coeff_block += NUM_COEFFS_IN_4x4BLK; + u4_ctxinc = (u4_top1 << 1) + u4_subblock_coded; + + ps_src_bin_ctxt = ps_cbf + u4_ctxinc; + + u4_subblock_coded = ih264d_read_coeff4x4_cabac(ps_bitstrm, u4_ctx_cat, + ps_sig_coeff_flag, ps_dec, + ps_src_bin_ctxt); + + INSERT_BIT(u4_csbp, u4_idx, u4_subblock_coded); + pu1_top_nnz[1] = pu1_left_nnz[1] = u4_subblock_coded; + + return (u4_csbp); +} + +/*! + ************************************************************************** + * \if Function name : ih264d_parse_residual4x4_cabac \endif + * + * \brief + * This function parses CABAC syntax of a Luma and Chroma AC Residuals. + * + * \return + * 0 on Success and Error code otherwise + ************************************************************************** + */ + +WORD32 ih264d_parse_residual4x4_cabac(dec_struct_t * ps_dec, + dec_mb_info_t *ps_cur_mb_info, + UWORD8 u1_offset) +{ + UWORD8 u1_cbp = ps_cur_mb_info->u1_cbp; + UWORD16 ui16_csbp = 0; + WORD16 *pi2_residual_buf; + UWORD8 uc_ctx_cat; + UWORD8 *pu1_top_nnz = ps_cur_mb_info->ps_curmb->pu1_nnz_y; + UWORD8 *pu1_left_nnz = ps_dec->pu1_left_nnz_y; + UWORD8 *pu1_top_nnz_uv = ps_cur_mb_info->ps_curmb->pu1_nnz_uv; + ctxt_inc_mb_info_t *p_curr_ctxt = ps_dec->ps_curr_ctxt_mb_info; + ctxt_inc_mb_info_t *ps_top_ctxt = ps_dec->p_top_ctxt_mb_info; + dec_bit_stream_t * const ps_bitstrm = ps_dec->ps_bitstrm; + UWORD32 u4_nbr_avail = ps_dec->u1_mb_ngbr_availablity; + WORD16 *pi2_coeff_block = NULL; + bin_ctxt_model_t *ps_src_bin_ctxt; + + UWORD8 u1_top_dc_csbp = (ps_top_ctxt->u1_yuv_dc_csbp) >> 1; + UWORD8 u1_left_dc_csbp = (ps_dec->pu1_left_yuv_dc_csbp[0]) >> 1; + + + if(!(u4_nbr_avail & TOP_MB_AVAILABLE_MASK)) + { + if(p_curr_ctxt->u1_mb_type & CAB_INTRA_MASK) + { + *(UWORD32 *)pu1_top_nnz = 0; + u1_top_dc_csbp = 0; + *(UWORD32 *)pu1_top_nnz_uv = 0; + } + else + { + *(UWORD32 *)pu1_top_nnz = 0x01010101; + u1_top_dc_csbp = 0x3; + *(UWORD32 *)pu1_top_nnz_uv = 0x01010101; + } + } + else + { + UWORD32 *pu4_buf; + UWORD8 *pu1_buf; + pu1_buf = ps_cur_mb_info->ps_top_mb->pu1_nnz_y; + pu4_buf = (UWORD32 *)pu1_buf; + *(UWORD32 *)(pu1_top_nnz) = *pu4_buf; + + pu1_buf = ps_cur_mb_info->ps_top_mb->pu1_nnz_uv; + pu4_buf = (UWORD32 *)pu1_buf; + *(UWORD32 *)(pu1_top_nnz_uv) = *pu4_buf; + + } + + if(!(u4_nbr_avail & LEFT_MB_AVAILABLE_MASK)) + { + if(p_curr_ctxt->u1_mb_type & CAB_INTRA_MASK) + { + UWORD32 *pu4_buf; + UWORD8 *pu1_buf; + *(UWORD32 *)pu1_left_nnz = 0; + u1_left_dc_csbp = 0; + pu1_buf = ps_dec->pu1_left_nnz_uv; + pu4_buf = (UWORD32 *)pu1_buf; + *pu4_buf = 0; + } + else + { + UWORD32 *pu4_buf; + UWORD8 *pu1_buf; + *(UWORD32 *)pu1_left_nnz = 0x01010101; + u1_left_dc_csbp = 0x3; + pu1_buf = ps_dec->pu1_left_nnz_uv; + pu4_buf = (UWORD32 *)pu1_buf; + *pu4_buf = 0x01010101; + } + } + + uc_ctx_cat = u1_offset ? LUMA_AC_CTXCAT : LUMA_4X4_CTXCAT; + + ps_cur_mb_info->u1_qp_div6 = ps_dec->u1_qp_y_div6; + ps_cur_mb_info->u1_qpc_div6 = ps_dec->u1_qp_u_div6; + ps_cur_mb_info->u1_qp_rem6 = ps_dec->u1_qp_y_rem6; + ps_cur_mb_info->u1_qpc_rem6 = ps_dec->u1_qp_u_rem6; + // CHECK_THIS + ps_cur_mb_info->u1_qpcr_div6 = ps_dec->u1_qp_v_div6; + ps_cur_mb_info->u1_qpcr_rem6 = ps_dec->u1_qp_v_rem6; + + if(u1_cbp & 0x0f) + { + if(ps_cur_mb_info->u1_tran_form8x8 == 0) + { + /*******************************************************************/ + /* Block 0 residual decoding, check cbp and proceed (subblock = 0) */ + /*******************************************************************/ + if(!(u1_cbp & 0x1)) + { + *(UWORD16 *)(pu1_top_nnz) = 0; + *(UWORD16 *)(pu1_left_nnz) = 0; + } + else + { + ui16_csbp = ih264d_cabac_parse_8x8block(pi2_coeff_block, 4, + uc_ctx_cat, ps_dec, + pu1_top_nnz, + pu1_left_nnz); + } + + /*******************************************************************/ + /* Block 1 residual decoding, check cbp and proceed (subblock = 2) */ + /*******************************************************************/ + pi2_coeff_block += (2 * NUM_COEFFS_IN_4x4BLK); + if(!(u1_cbp & 0x2)) + { + *(UWORD16 *)(pu1_top_nnz + 2) = 0; + *(UWORD16 *)(pu1_left_nnz) = 0; + } + else + { + UWORD32 u4_temp = ih264d_cabac_parse_8x8block(pi2_coeff_block, + 4, uc_ctx_cat, + ps_dec, + (pu1_top_nnz + 2), + pu1_left_nnz); + ui16_csbp |= (u4_temp << 2); + } + + /*******************************************************************/ + /* Block 2 residual decoding, check cbp and proceed (subblock = 8) */ + /*******************************************************************/ + pi2_coeff_block += (6 * NUM_COEFFS_IN_4x4BLK); + if(!(u1_cbp & 0x4)) + { + *(UWORD16 *)(pu1_top_nnz) = 0; + *(UWORD16 *)(pu1_left_nnz + 2) = 0; + } + else + { + UWORD32 u4_temp = ih264d_cabac_parse_8x8block( + pi2_coeff_block, 4, uc_ctx_cat, ps_dec, + pu1_top_nnz, (pu1_left_nnz + 2)); + ui16_csbp |= (u4_temp << 8); + } + + /*******************************************************************/ + /* Block 3 residual decoding, check cbp and proceed (subblock = 10)*/ + /*******************************************************************/ + pi2_coeff_block += (2 * NUM_COEFFS_IN_4x4BLK); + if(!(u1_cbp & 0x8)) + { + *(UWORD16 *)(pu1_top_nnz + 2) = 0; + *(UWORD16 *)(pu1_left_nnz + 2) = 0; + } + else + { + UWORD32 u4_temp = ih264d_cabac_parse_8x8block( + pi2_coeff_block, 4, uc_ctx_cat, ps_dec, + (pu1_top_nnz + 2), (pu1_left_nnz + 2)); + ui16_csbp |= (u4_temp << 10); + } + + } + else + { + ui16_csbp = 0; + + /*******************************************************************/ + /* Block 0 residual decoding, check cbp and proceed (subblock = 0) */ + /*******************************************************************/ + if(!(u1_cbp & 0x1)) + { + *(UWORD16 *)(pu1_top_nnz) = 0; + *(UWORD16 *)(pu1_left_nnz) = 0; + } + else + { + + dec_bit_stream_t * const ps_bitstrm = ps_dec->ps_bitstrm; + + ih264d_read_coeff8x8_cabac( ps_bitstrm, + ps_dec, ps_cur_mb_info); + + pu1_left_nnz[0] = 1; + pu1_left_nnz[1] = 1; + + pu1_top_nnz[0] = 1; + pu1_top_nnz[1] = 1; + + /* added to be used by BS computation module */ + ui16_csbp |= 0x0033; + } + + /*******************************************************************/ + /* Block 1 residual decoding, check cbp and proceed (subblock = 2) */ + /*******************************************************************/ + pi2_coeff_block += 64; + + if(!(u1_cbp & 0x2)) + { + *(UWORD16 *)(pu1_top_nnz + 2) = 0; + *(UWORD16 *)(pu1_left_nnz) = 0; + } + else + { + + + dec_bit_stream_t * const ps_bitstrm = ps_dec->ps_bitstrm; + + ih264d_read_coeff8x8_cabac(ps_bitstrm, + ps_dec, ps_cur_mb_info); + + pu1_left_nnz[0] = 1; + pu1_left_nnz[1] = 1; + + pu1_top_nnz[2] = 1; + pu1_top_nnz[3] = 1; + + /* added to be used by BS computation module */ + ui16_csbp |= 0x00CC; + + } + + /*******************************************************************/ + /* Block 2 residual decoding, check cbp and proceed (subblock = 8) */ + /*******************************************************************/ + pi2_coeff_block += 64; + if(!(u1_cbp & 0x4)) + { + *(UWORD16 *)(pu1_top_nnz) = 0; + *(UWORD16 *)(pu1_left_nnz + 2) = 0; + } + else + { + + dec_bit_stream_t * const ps_bitstrm = ps_dec->ps_bitstrm; + + ih264d_read_coeff8x8_cabac(ps_bitstrm, + ps_dec, ps_cur_mb_info); + + pu1_left_nnz[2] = 1; + pu1_left_nnz[3] = 1; + + pu1_top_nnz[0] = 1; + pu1_top_nnz[1] = 1; + + /* added to be used by BS computation module */ + ui16_csbp |= 0x3300; + } + + /*******************************************************************/ + /* Block 3 residual decoding, check cbp and proceed (subblock = 10)*/ + /*******************************************************************/ + pi2_coeff_block += 64; + + if(!(u1_cbp & 0x8)) + { + *(UWORD16 *)(pu1_top_nnz + 2) = 0; + *(UWORD16 *)(pu1_left_nnz + 2) = 0; + } + else + { + + dec_bit_stream_t * const ps_bitstrm = ps_dec->ps_bitstrm; + + ih264d_read_coeff8x8_cabac(ps_bitstrm, + ps_dec, ps_cur_mb_info); + + pu1_left_nnz[2] = 1; + pu1_left_nnz[3] = 1; + + pu1_top_nnz[2] = 1; + pu1_top_nnz[3] = 1; + + /* added to be used by BS computation module */ + ui16_csbp |= 0xCC00; + } + } + } + else + { + *(UWORD32 *)(pu1_top_nnz) = 0; + *(UWORD32 *)(pu1_left_nnz) = 0; + } + /*--------------------------------------------------------------------*/ + /* Store the last row of N values to top row */ + /*--------------------------------------------------------------------*/ + ps_cur_mb_info->u2_luma_csbp = ui16_csbp; + ps_cur_mb_info->ps_curmb->u2_luma_csbp = ui16_csbp; + { + WORD8 i; + UWORD16 u2_chroma_csbp = 0; + ps_cur_mb_info->u2_chroma_csbp = 0; + + u1_cbp >>= 4; + pu1_top_nnz = pu1_top_nnz_uv; + pu1_left_nnz = ps_dec->pu1_left_nnz_uv; + /*--------------------------------------------------------------------*/ + /* if Chroma Component not present OR no ac values present */ + /* Set the values of N to zero */ + /*--------------------------------------------------------------------*/ + if(u1_cbp == CBPC_ALLZERO) + { + ps_dec->pu1_left_yuv_dc_csbp[0] &= 0x1; + *(UWORD32 *)(pu1_top_nnz) = 0; + *(UWORD32 *)(pu1_left_nnz) = 0; + p_curr_ctxt->u1_yuv_dc_csbp &= 0x1; + return (0); + } + + /*--------------------------------------------------------------------*/ + /* Decode Chroma DC values */ + /*--------------------------------------------------------------------*/ + for(i = 0; i < 2; i++) + { + UWORD8 uc_a = 1, uc_b = 1; + UWORD32 u4_ctx_inc; + UWORD8 uc_codedBlockFlag; + UWORD8 pu1_inv_scan[4] = + { 0, 1, 2, 3 }; + WORD32 u4_scale; + WORD32 i4_mb_inter_inc; + tu_sblk4x4_coeff_data_t *ps_tu_4x4 = + (tu_sblk4x4_coeff_data_t *)ps_dec->pv_parse_tu_coeff_data; + WORD16 *pi2_coeff_data = + (WORD16 *)ps_dec->pv_parse_tu_coeff_data; + WORD16 ai2_dc_coef[4]; + + INC_SYM_COUNT(&(ps_dec->s_cab_dec_env)); + u4_scale = (i) ? + (ps_dec->pu2_quant_scale_v[0] + << ps_dec->u1_qp_v_div6) : + (ps_dec->pu2_quant_scale_u[0] + << ps_dec->u1_qp_u_div6); + + /*--------------------------------------------------------------------*/ + /* Decode Bitstream to get the DC coeff */ + /*--------------------------------------------------------------------*/ + uc_a = (u1_left_dc_csbp >> i) & 0x01; + uc_b = (u1_top_dc_csbp >> i) & 0x01; + u4_ctx_inc = (uc_a + (uc_b << 1)); + + ps_src_bin_ctxt = (ps_dec->p_cbf_t[CHROMA_DC_CTXCAT]) + u4_ctx_inc; + + uc_codedBlockFlag = + ih264d_read_coeff4x4_cabac(ps_bitstrm, + CHROMA_DC_CTXCAT, + ps_dec->p_significant_coeff_flag_t[CHROMA_DC_CTXCAT], + ps_dec, ps_src_bin_ctxt); + + i4_mb_inter_inc = (!((ps_cur_mb_info->ps_curmb->u1_mb_type == I_4x4_MB) + || (ps_cur_mb_info->ps_curmb->u1_mb_type == I_16x16_MB))) + * 3; + + if(ps_dec->s_high_profile.u1_scaling_present) + { + u4_scale *= + ps_dec->s_high_profile.i2_scalinglist4x4[i4_mb_inter_inc + + 1 + i][0]; + + } + else + { + u4_scale <<= 4; + } + + if(uc_codedBlockFlag) + { + WORD32 i_z0, i_z1, i_z2, i_z3; + WORD32 *pi4_scale; + + SET_BIT(u1_top_dc_csbp, i); + SET_BIT(u1_left_dc_csbp, i); + + ai2_dc_coef[0] = 0; + ai2_dc_coef[1] = 0; + ai2_dc_coef[2] = 0; + ai2_dc_coef[3] = 0; + + ih264d_unpack_coeff4x4_dc_4x4blk(ps_tu_4x4, + ai2_dc_coef, + pu1_inv_scan); + i_z0 = (ai2_dc_coef[0] + ai2_dc_coef[2]); + i_z1 = (ai2_dc_coef[0] - ai2_dc_coef[2]); + i_z2 = (ai2_dc_coef[1] - ai2_dc_coef[3]); + i_z3 = (ai2_dc_coef[1] + ai2_dc_coef[3]); + + /*-----------------------------------------------------------*/ + /* Scaling and storing the values back */ + /*-----------------------------------------------------------*/ + *pi2_coeff_data++ = ((i_z0 + i_z3) * u4_scale) >> 5; + *pi2_coeff_data++ = ((i_z0 - i_z3) * u4_scale) >> 5; + *pi2_coeff_data++ = ((i_z1 + i_z2) * u4_scale) >> 5; + *pi2_coeff_data++ = ((i_z1 - i_z2) * u4_scale) >> 5; + + ps_dec->pv_parse_tu_coeff_data = (void *)pi2_coeff_data; + + SET_BIT(ps_cur_mb_info->u1_yuv_dc_block_flag,(i+1)); + } + else + { + CLEARBIT(u1_top_dc_csbp, i); + CLEARBIT(u1_left_dc_csbp, i); + } + } + + /*********************************************************************/ + /* Update the DC csbp */ + /*********************************************************************/ + ps_dec->pu1_left_yuv_dc_csbp[0] &= 0x1; + p_curr_ctxt->u1_yuv_dc_csbp &= 0x1; + ps_dec->pu1_left_yuv_dc_csbp[0] |= (u1_left_dc_csbp << 1); + p_curr_ctxt->u1_yuv_dc_csbp |= (u1_top_dc_csbp << 1); + if(u1_cbp == CBPC_ACZERO) + { + *(UWORD32 *)(pu1_top_nnz) = 0; + *(UWORD32 *)(pu1_left_nnz) = 0; + return (0); + } + /*--------------------------------------------------------------------*/ + /* Decode Chroma AC values */ + /*--------------------------------------------------------------------*/ + { + UWORD32 u4_temp; + /*****************************************************************/ + /* U Block residual decoding, check cbp and proceed (subblock=0)*/ + /*****************************************************************/ + u2_chroma_csbp = ih264d_cabac_parse_8x8block(pi2_coeff_block, 2, + CHROMA_AC_CTXCAT, + ps_dec, pu1_top_nnz, + pu1_left_nnz); + + pi2_coeff_block += MB_CHROM_SIZE; + /*****************************************************************/ + /* V Block residual decoding, check cbp and proceed (subblock=1)*/ + /*****************************************************************/ + u4_temp = ih264d_cabac_parse_8x8block(pi2_coeff_block, 2, + CHROMA_AC_CTXCAT, + ps_dec, (pu1_top_nnz + 2), + (pu1_left_nnz + 2)); + u2_chroma_csbp |= (u4_temp << 4); + } + /*********************************************************************/ + /* Update the AC csbp */ + /*********************************************************************/ + ps_cur_mb_info->u2_chroma_csbp = u2_chroma_csbp; + } + + return (0); +} + diff --git a/dependencies/ih264d/decoder/ih264d_parse_cabac.h b/dependencies/ih264d/decoder/ih264d_parse_cabac.h new file mode 100644 index 00000000..eb66e8c9 --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_parse_cabac.h @@ -0,0 +1,60 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ + +/*! + *************************************************************************** + * \file ih264d_parse_cabac.h + * + * \brief + * This file contains cabac Residual decoding routines. + * + * \date + * 20/03/2003 + * + * \author NS + *************************************************************************** + */ +#ifndef _IH264D_PARSE_CABAC_H_ +#define _IH264D_PARSE_CABAC_H_ + +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" + +#define UCOFF_LEVEL 14 + + +UWORD8 ih264d_read_coeff4x4_cabac(dec_bit_stream_t *ps_bitstrm, + UWORD32 u4_ctxcat, + bin_ctxt_model_t *ps_ctxt_sig_coeff, + dec_struct_t *ps_dec, + bin_ctxt_model_t *ps_ctxt_coded); + +void ih264d_read_coeff8x8_cabac(dec_bit_stream_t *ps_bitstrm, + dec_struct_t *ps_dec, + dec_mb_info_t *ps_cur_mb_info); + +UWORD32 cabac_parse_8x8block_transform8x8_set(WORD16 *pi2_coeff_block, + dec_struct_t * ps_dec, + UWORD8 *pu1_top_nnz, + UWORD8 *pu1_left_nnz, + dec_mb_info_t *ps_cur_mb_info); + +#endif /* _IH264D_PARSE_CABAC_H_ */ diff --git a/dependencies/ih264d/decoder/ih264d_parse_cavlc.c b/dependencies/ih264d/decoder/ih264d_parse_cavlc.c new file mode 100644 index 00000000..d538a4ef --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_parse_cavlc.c @@ -0,0 +1,2719 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/*! + *************************************************************************** + * \file ih264d_parse_cavlc.c + * + * \brief + * This file contains UVLC related functions. + * + * \date + * 20/11/2002 + * + * \author NS + *************************************************************************** + */ + +#include <string.h> +#include <stdio.h> + +#include "ih264d_bitstrm.h" +#include "ih264d_parse_cavlc.h" +#include "ih264d_error_handler.h" +#include "ih264d_defs.h" +#include "ih264d_debug.h" +#include "ih264d_cabac.h" +#include "ih264d_structs.h" +#include "ih264d_tables.h" +#include "ih264d_tables.h" +#include "ih264d_mb_utils.h" + +void ih264d_unpack_coeff4x4_dc_4x4blk(tu_sblk4x4_coeff_data_t *ps_tu_4x4, + WORD16 *pi2_out_coeff_data, + UWORD8 *pu1_inv_scan); + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_uev */ +/* */ +/* Description : Reads the unsigned Exp Golomb codec syntax from the */ +/* ps_bitstrm as specified in section 9.1 of H264 standard */ +/* It also increases bitstream u4_ofst by the number of bits */ +/* parsed for UEV decode operation */ +/* */ +/* Inputs : bitstream base pointer and bitsream u4_ofst in bits */ +/* Globals : None */ +/* Processing : */ +/* Outputs : UEV decoded syntax element and incremented ps_bitstrm u4_ofst */ +/* Returns : UEV decoded syntax element */ +/* */ +/* Issues : Does not check if ps_bitstrm u4_ofst exceeds max ps_bitstrm i4_size */ +/* for performamce. Caller might have to do error resilence */ +/* check for bitstream overflow */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 19 09 2008 Jay Draft */ +/* */ +/*****************************************************************************/ +UWORD32 ih264d_uev(UWORD32 *pu4_bitstrm_ofst, UWORD32 *pu4_bitstrm_buf) +{ + UWORD32 u4_bitstream_offset = *pu4_bitstrm_ofst; + UWORD32 u4_word, u4_ldz; + + /***************************************************************/ + /* Find leading zeros in next 32 bits */ + /***************************************************************/ + NEXTBITS_32(u4_word, u4_bitstream_offset, pu4_bitstrm_buf); + u4_ldz = CLZ(u4_word); + /* Flush the ps_bitstrm */ + u4_bitstream_offset += (u4_ldz + 1); + /* Read the suffix from the ps_bitstrm */ + u4_word = 0; + if(u4_ldz) + GETBITS(u4_word, u4_bitstream_offset, pu4_bitstrm_buf, u4_ldz); + *pu4_bitstrm_ofst = u4_bitstream_offset; + return ((1 << u4_ldz) + u4_word - 1); +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_sev */ +/* */ +/* Description : Reads the signed Exp Golomb codec syntax from the ps_bitstrm */ +/* as specified in section 9.1 of H264 standard. */ +/* It also increases bitstream u4_ofst by the number of bits */ +/* parsed for SEV decode operation */ +/* */ +/* Inputs : bitstream base pointer and bitsream u4_ofst in bits */ +/* Globals : None */ +/* Processing : */ +/* Outputs : SEV decoded syntax element and incremented ps_bitstrm u4_ofst */ +/* Returns : SEV decoded syntax element */ +/* */ +/* Issues : Does not check if ps_bitstrm u4_ofst exceeds max ps_bitstrm i4_size */ +/* for performamce. Caller might have to do error resilence */ +/* check for bitstream overflow */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 19 09 2008 Jay Draft */ +/* */ +/*****************************************************************************/ +WORD32 ih264d_sev(UWORD32 *pu4_bitstrm_ofst, UWORD32 *pu4_bitstrm_buf) +{ + UWORD32 u4_bitstream_offset = *pu4_bitstrm_ofst; + UWORD32 u4_word, u4_ldz, u4_abs_val; + + /***************************************************************/ + /* Find leading zeros in next 32 bits */ + /***************************************************************/ + NEXTBITS_32(u4_word, u4_bitstream_offset, pu4_bitstrm_buf); + u4_ldz = CLZ(u4_word); + + /* Flush the ps_bitstrm */ + u4_bitstream_offset += (u4_ldz + 1); + + /* Read the suffix from the ps_bitstrm */ + u4_word = 0; + if(u4_ldz) + GETBITS(u4_word, u4_bitstream_offset, pu4_bitstrm_buf, u4_ldz); + + *pu4_bitstrm_ofst = u4_bitstream_offset; + u4_abs_val = ((1 << u4_ldz) + u4_word) >> 1; + + if(u4_word & 0x1) + return (-(WORD32)u4_abs_val); + else + return (u4_abs_val); +} + +/*****************************************************************************/ +/* */ +/* Function Name : get_tev_range_1 */ +/* */ +/* Description : Reads the TEV Exp Golomb codec syntax from the ps_bitstrm */ +/* as specified in section 9.1 of H264 standard. This will */ +/* called only when the input range is 1 for TEV decode. */ +/* If range is more than 1, then UEV decode is done */ +/* */ +/* Inputs : bitstream base pointer and bitsream u4_ofst in bits */ +/* Globals : None */ +/* Processing : */ +/* Outputs : TEV decoded syntax element and incremented ps_bitstrm u4_ofst */ +/* Returns : TEV decoded syntax element */ +/* */ +/* Issues : Does not check if ps_bitstrm u4_ofst exceeds max ps_bitstrm i4_size */ +/* for performamce. Caller might have to do error resilence */ +/* check for bitstream overflow */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 19 09 2008 Jay Draft */ +/* */ +/*****************************************************************************/ +UWORD32 ih264d_tev_range1(UWORD32 *pu4_bitstrm_ofst, UWORD32 *pu4_bitstrm_buf) +{ + UWORD32 u4_code; + GETBIT(u4_code, *pu4_bitstrm_ofst, pu4_bitstrm_buf); + return (!u4_code); +} + +/*! + ************************************************************************** + * \if Function name : ih264d_uvlc \endif + * + * \brief + * + * Reads the unsigned/signed/truncated integer Exp-Golomb-coded syntax element + * with the left bit first. The parsing process for this descriptor is specified + * in subclause 9.1. + * + * \param ps_bitstrm : Pointer to Bitstream Structure . + * \param u4_range : Range value in case of Truncated Exp-Golomb-code + * \param pi_bitstrm_ofst : Pointer to the local copy of Bitstream u4_ofst + * \param u1_flag : Flag indicating the case of UEV,SEV or TEV + * \param u4_bitstrm_ofst : Local copy of Bitstream u4_ofst + * \param pu4_bitstrm_buf : Pointer to the Bitstream buffer + * + * \return + * Returns Code Value. + * + ************************************************************************** + */ + +WORD32 ih264d_uvlc(dec_bit_stream_t *ps_bitstrm, + UWORD32 u4_range, + UWORD32 *pi_bitstrm_ofst, + UWORD8 u1_flag, + UWORD32 u4_bitstrm_ofst, + UWORD32 *pu4_bitstrm_buf) +{ + UWORD32 word, word2, cur_bit, cur_word, code_val, code_num, clz; + + SWITCHOFFTRACE; + cur_bit = u4_bitstrm_ofst & 0x1F; + cur_word = u4_bitstrm_ofst >> 5; + word = pu4_bitstrm_buf[cur_word]; + word2 = pu4_bitstrm_buf[cur_word + 1]; + + if(cur_bit != 0) + { + word <<= cur_bit; + word2 >>= (32 - cur_bit); + word |= word2; + } + + if(u1_flag == TEV && u4_range == 1) + { + word >>= 31; + word = 1 - word; + (*pi_bitstrm_ofst)++; + ps_bitstrm->u4_ofst = *pi_bitstrm_ofst; + return (WORD32)word; + } + + //finding clz + { + UWORD32 ui32_code, ui32_mask; + + ui32_code = word; + ui32_mask = 0x80000000; + clz = 0; + + /* DSP implements this with LMBD instruction */ + /* so there we don't need to break the loop */ + while(!(ui32_code & ui32_mask)) + { + clz++; + ui32_mask >>= 1; + if(0 == ui32_mask) + break; + } + } + + if(clz == 0) + { + *pi_bitstrm_ofst = *pi_bitstrm_ofst + (2 * clz) + 1; + ps_bitstrm->u4_ofst = *pi_bitstrm_ofst; + return 0; + } + + word <<= (clz + 1); + word >>= (32 - clz); + code_num = (1 << clz) + word - 1; + *pi_bitstrm_ofst = *pi_bitstrm_ofst + (2 * clz) + 1; + ps_bitstrm->u4_ofst = *pi_bitstrm_ofst; + + if(u1_flag == TEV || u1_flag == UEV) + return (WORD32)code_num; + + code_val = (code_num + 1) >> 1; + if(!(code_num & 0x01)) + return -((WORD32)code_val); + return (WORD32)code_val; + +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_cavlc_4x4res_block_totalcoeff_1 */ +/* */ +/* Description : This function does cavlc decoding of 4x4 block residual */ +/* coefficient when total coeff is equal to 1. The parsing */ +/* is done as defined in section 9.2.2 and 9.2.3 of the */ +/* H264 standard. */ +/* */ +/* Inputs : <What inputs does the function take?> */ +/* Globals : <Does it use any global variables?> */ +/* Processing : <Describe how the function operates - include algorithm */ +/* description> */ +/* Outputs : <What does the function produce?> */ +/* Returns : <What does the function return?> */ +/* */ +/* Issues : <List any issues or problems with this function> */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 25 09 2008 Jay Draft */ +/* */ +/*****************************************************************************/ +WORD32 ih264d_cavlc_4x4res_block_totalcoeff_1(UWORD32 u4_isdc, + UWORD32 u4_total_coeff_trail_one, + dec_bit_stream_t *ps_bitstrm) +{ + + UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; + UWORD32 u4_bitstream_offset = ps_bitstrm->u4_ofst; + UWORD32 u4_trailing_ones = u4_total_coeff_trail_one & 0xFFFF; + WORD32 i2_level; + UWORD32 u4_tot_zero, u4_ldz, u4_scan_pos; + + tu_sblk4x4_coeff_data_t *ps_tu_4x4; + WORD16 *pi2_coeff_data; + dec_struct_t *ps_dec = (dec_struct_t *)ps_bitstrm->pv_codec_handle; + + ps_tu_4x4 = (tu_sblk4x4_coeff_data_t *)ps_dec->pv_parse_tu_coeff_data; + ps_tu_4x4->u2_sig_coeff_map = 0; + pi2_coeff_data = &ps_tu_4x4->ai2_level[0]; + + + if(u4_trailing_ones) + { + UWORD32 u4_sign; + /****************************************************************/ + /* Decode Trailing One as in section 9.2.2 */ + /****************************************************************/ + GETBIT(u4_sign, u4_bitstream_offset, pu4_bitstrm_buf); + i2_level = u4_sign ? -1 : 1; + } + else + { + /****************************************************************/ + /* Decoding Level based on prefix and suffix as in 9.2.2 */ + /****************************************************************/ + UWORD32 u4_lev_suffix, u4_lev_suffix_size; + WORD32 u2_lev_code, u2_abs_value; + UWORD32 u4_lev_prefix; + /***************************************************************/ + /* Find leading zeros in next 32 bits */ + /***************************************************************/ + FIND_ONE_IN_STREAM_32(u4_lev_prefix, u4_bitstream_offset, + pu4_bitstrm_buf); + u2_lev_code = (2 + MIN(u4_lev_prefix, 15)); + + if(14 == u4_lev_prefix) + u4_lev_suffix_size = 4; + else if(15 <= u4_lev_prefix) + { + u2_lev_code += 15; + u4_lev_suffix_size = u4_lev_prefix - 3; + } + else + u4_lev_suffix_size = 0; + + //HP_LEVEL_PREFIX + if(16 <= u4_lev_prefix) + { + u2_lev_code += ((1 << (u4_lev_prefix - 3)) - 4096); + } + if(u4_lev_suffix_size) + { + GETBITS(u4_lev_suffix, u4_bitstream_offset, pu4_bitstrm_buf, + u4_lev_suffix_size); + u2_lev_code += u4_lev_suffix; + } + + u2_abs_value = (u2_lev_code + 2) >> 1; + /*********************************************************/ + /* If Level code is odd, level is negative else positive */ + /*********************************************************/ + i2_level = (u2_lev_code & 1) ? -u2_abs_value : u2_abs_value; + + } + + /****************************************************************/ + /* Decoding total zeros as in section 9.2.3, table 9.7 */ + /****************************************************************/ + FIND_ONE_IN_STREAM_LEN(u4_ldz, u4_bitstream_offset, pu4_bitstrm_buf, 8); + + if(u4_ldz) + { + GETBIT(u4_tot_zero, u4_bitstream_offset, pu4_bitstrm_buf); + u4_tot_zero = (u4_ldz << 1) - u4_tot_zero; + } + else + u4_tot_zero = 0; + + /***********************************************************************/ + /* Inverse scan and store residual coeff. Update the bitstream u4_ofst */ + /***********************************************************************/ + u4_scan_pos = u4_tot_zero + u4_isdc; + if(u4_scan_pos > 15) + return -1; + + SET_BIT(ps_tu_4x4->u2_sig_coeff_map, u4_scan_pos); + *pi2_coeff_data++ = i2_level; + + + { + WORD32 offset; + offset = (UWORD8 *)pi2_coeff_data - (UWORD8 *)ps_tu_4x4; + offset = ALIGN4(offset); + ps_dec->pv_parse_tu_coeff_data = (void *)((UWORD8 *)ps_dec->pv_parse_tu_coeff_data + offset); + } + + ps_bitstrm->u4_ofst = u4_bitstream_offset; + return 0; +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_cavlc_4x4res_block_totalcoeff_2to10 */ +/* */ +/* Description : This function does cavlc decoding of 4x4 block residual */ +/* coefficient when total coeffs are between two and ten */ +/* inclusive. Parsing is done as defined in section 9.2.2 */ +/* and 9.2.3 the H264 standard. */ +/* */ +/* Inputs : <What inputs does the function take?> */ +/* Globals : <Does it use any global variables?> */ +/* Processing : <Describe how the function operates - include algorithm */ +/* description> */ +/* Outputs : <What does the function produce?> */ +/* Returns : <What does the function return?> */ +/* */ +/* Issues : <List any issues or problems with this function> */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 25 09 2008 Jay Draft */ +/* */ +/*****************************************************************************/ + +WORD32 ih264d_cavlc_4x4res_block_totalcoeff_2to10(UWORD32 u4_isdc, + UWORD32 u4_total_coeff_trail_one, /*!<TotalCoefficients<<16+trailingones*/ + dec_bit_stream_t *ps_bitstrm) +{ + UWORD32 u4_total_zeroes; + WORD32 i; + UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; + UWORD32 u4_bitstream_offset = ps_bitstrm->u4_ofst; + UWORD32 u4_trailing_ones = u4_total_coeff_trail_one & 0xFFFF; + UWORD32 u4_total_coeff = u4_total_coeff_trail_one >> 16; + // To avoid error check at 4x4 level, allocating for 3 extra levels(16+3) + // since u4_trailing_ones can at the max be 3. This will be required when + // u4_total_coeff is less than u4_trailing_ones + WORD16 ai2_level_arr[19]; + WORD16 *i2_level_arr = &ai2_level_arr[3]; + + tu_sblk4x4_coeff_data_t *ps_tu_4x4; + WORD16 *pi2_coeff_data; + dec_struct_t *ps_dec = (dec_struct_t *)ps_bitstrm->pv_codec_handle; + + ps_tu_4x4 = (tu_sblk4x4_coeff_data_t *)ps_dec->pv_parse_tu_coeff_data; + ps_tu_4x4->u2_sig_coeff_map = 0; + pi2_coeff_data = &ps_tu_4x4->ai2_level[0]; + + i = u4_total_coeff - 1; + + if(u4_trailing_ones) + { + /*********************************************************************/ + /* Decode Trailing Ones */ + /* read the sign of T1's and put them in level array */ + /*********************************************************************/ + UWORD32 u4_signs, u4_cnt = u4_trailing_ones; + WORD16 (*ppi2_trlone_lkup)[3] = + (WORD16 (*)[3])gai2_ih264d_trailing_one_level; + WORD16 *pi2_trlone_lkup; + + GETBITS(u4_signs, u4_bitstream_offset, pu4_bitstrm_buf, u4_cnt); + + pi2_trlone_lkup = ppi2_trlone_lkup[(1 << u4_cnt) - 2 + u4_signs]; + + while(u4_cnt) + { + i2_level_arr[i--] = *pi2_trlone_lkup++; + u4_cnt--; + } + } + + /****************************************************************/ + /* Decoding Levels Begins */ + /****************************************************************/ + if(i >= 0) + { + /****************************************************************/ + /* First level is decoded outside the loop as it has lot of */ + /* special cases. */ + /****************************************************************/ + UWORD32 u4_lev_suffix, u4_suffix_len, u4_lev_suffix_size; + WORD32 u2_lev_code, u2_abs_value; + UWORD32 u4_lev_prefix; + + /***************************************************************/ + /* u4_suffix_len = 0, Find leading zeros in next 32 bits */ + /***************************************************************/ + FIND_ONE_IN_STREAM_32(u4_lev_prefix, u4_bitstream_offset, + pu4_bitstrm_buf); + + /*********************************************************/ + /* Special decoding case when trailing ones are 3 */ + /*********************************************************/ + u2_lev_code = MIN(15, u4_lev_prefix); + + u2_lev_code += (3 == u4_trailing_ones) ? 0 : 2; + + if(14 == u4_lev_prefix) + u4_lev_suffix_size = 4; + else if(15 <= u4_lev_prefix) + { + u2_lev_code += 15; + u4_lev_suffix_size = u4_lev_prefix - 3; + } + else + u4_lev_suffix_size = 0; + + //HP_LEVEL_PREFIX + if(16 <= u4_lev_prefix) + { + u2_lev_code += ((1 << (u4_lev_prefix - 3)) - 4096); + } + if(u4_lev_suffix_size) + { + GETBITS(u4_lev_suffix, u4_bitstream_offset, pu4_bitstrm_buf, + u4_lev_suffix_size); + u2_lev_code += u4_lev_suffix; + } + + u2_abs_value = (u2_lev_code + 2) >> 1; + /*********************************************************/ + /* If Level code is odd, level is negative else positive */ + /*********************************************************/ + i2_level_arr[i--] = (u2_lev_code & 1) ? -u2_abs_value : u2_abs_value; + + u4_suffix_len = (u2_abs_value > 3) ? 2 : 1; + + /*********************************************************/ + /* Now loop over the remaining levels */ + /*********************************************************/ + while(i >= 0) + { + + /***************************************************************/ + /* Find leading zeros in next 32 bits */ + /***************************************************************/ + FIND_ONE_IN_STREAM_32(u4_lev_prefix, u4_bitstream_offset, + pu4_bitstrm_buf); + + u4_lev_suffix_size = + (15 <= u4_lev_prefix) ? + (u4_lev_prefix - 3) : u4_suffix_len; + + /*********************************************************/ + /* Compute level code using prefix and suffix */ + /*********************************************************/ + GETBITS(u4_lev_suffix, u4_bitstream_offset, pu4_bitstrm_buf, + u4_lev_suffix_size); + u2_lev_code = (MIN(15,u4_lev_prefix) << u4_suffix_len) + + u4_lev_suffix; + + //HP_LEVEL_PREFIX + if(16 <= u4_lev_prefix) + { + u2_lev_code += ((1 << (u4_lev_prefix - 3)) - 4096); + } + u2_abs_value = (u2_lev_code + 2) >> 1; + + /*********************************************************/ + /* If Level code is odd, level is negative else positive */ + /*********************************************************/ + i2_level_arr[i--] = + (u2_lev_code & 1) ? -u2_abs_value : u2_abs_value; + + /*********************************************************/ + /* Increment suffix length if required */ + /*********************************************************/ + u4_suffix_len += + (u4_suffix_len < 6) ? + (u2_abs_value + > (3 + << (u4_suffix_len + - 1))) : + 0; + } + + /****************************************************************/ + /* Decoding Levels Ends */ + /****************************************************************/ + } + + /****************************************************************/ + /* Decoding total zeros as in section 9.2.3, table 9.7 */ + /****************************************************************/ + { + UWORD32 u4_index; + const UWORD8 (*ppu1_total_zero_lkup)[64] = + (const UWORD8 (*)[64])gau1_ih264d_table_total_zero_2to10; + + NEXTBITS(u4_index, u4_bitstream_offset, pu4_bitstrm_buf, 6); + u4_total_zeroes = ppu1_total_zero_lkup[u4_total_coeff - 2][u4_index]; + + FLUSHBITS(u4_bitstream_offset, (u4_total_zeroes >> 4)); + u4_total_zeroes &= 0xf; + } + + /**************************************************************/ + /* Decode the runs and form the coefficient buffer */ + /**************************************************************/ + { + const UWORD8 *pu1_table_runbefore; + UWORD32 u4_run; + WORD32 k; + WORD32 u4_scan_pos = u4_total_coeff + u4_total_zeroes - 1 + u4_isdc; + WORD32 u4_zeroes_left = u4_total_zeroes; + k = u4_total_coeff - 1; + + /**************************************************************/ + /* Decoding Runs Begin for zeros left > 6 */ + /**************************************************************/ + while((u4_zeroes_left > 6) && k) + { + UWORD32 u4_code; + + NEXTBITS(u4_code, u4_bitstream_offset, pu4_bitstrm_buf, 3); + + if(u4_code != 0) + { + FLUSHBITS(u4_bitstream_offset, 3); + u4_run = (7 - u4_code); + } + else + { + + FIND_ONE_IN_STREAM_LEN(u4_code, u4_bitstream_offset, + pu4_bitstrm_buf, 11); + u4_run = (4 + u4_code); + } + + SET_BIT(ps_tu_4x4->u2_sig_coeff_map, u4_scan_pos); + *pi2_coeff_data++ = i2_level_arr[k--]; + u4_zeroes_left -= (WORD32)u4_run; + u4_scan_pos -= (WORD32)(u4_run + 1); + } + + if (u4_zeroes_left < 0 || u4_scan_pos < 0) + return -1; + + /**************************************************************/ + /* Decoding Runs for 0 < zeros left <=6 */ + /**************************************************************/ + pu1_table_runbefore = (UWORD8 *)gau1_ih264d_table_run_before; + while((u4_zeroes_left > 0) && k) + { + UWORD32 u4_code; + NEXTBITS(u4_code, u4_bitstream_offset, pu4_bitstrm_buf, 3); + + u4_code = pu1_table_runbefore[u4_code + (u4_zeroes_left << 3)]; + u4_run = u4_code >> 2; + + FLUSHBITS(u4_bitstream_offset, (u4_code & 0x03)); + + SET_BIT(ps_tu_4x4->u2_sig_coeff_map, u4_scan_pos); + *pi2_coeff_data++ = i2_level_arr[k--]; + u4_zeroes_left -= (WORD32)u4_run; + u4_scan_pos -= (WORD32)(u4_run + 1); + } + if (u4_zeroes_left < 0 || u4_scan_pos < 0) + return -1; + /**************************************************************/ + /* Decoding Runs End */ + /**************************************************************/ + + /**************************************************************/ + /* Copy the remaining coefficients */ + /**************************************************************/ + while(k >= 0) + { + + SET_BIT(ps_tu_4x4->u2_sig_coeff_map, u4_scan_pos); + *pi2_coeff_data++ = i2_level_arr[k--]; + u4_scan_pos--; + } + } + + { + WORD32 offset; + offset = (UWORD8 *)pi2_coeff_data - (UWORD8 *)ps_tu_4x4; + offset = ALIGN4(offset); + ps_dec->pv_parse_tu_coeff_data = (void *)((UWORD8 *)ps_dec->pv_parse_tu_coeff_data + offset); + } + + ps_bitstrm->u4_ofst = u4_bitstream_offset; + return 0; +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_cavlc_4x4res_block_totalcoeff_11to16 */ +/* */ +/* Description : This function does cavlc decoding of 4x4 block residual */ +/* coefficient when total coeffs are greater than ten. */ +/* Parsing is done as defined in section 9.2.2 and 9.2.3 of */ +/* the H264 standard. */ +/* */ +/* Inputs : <What inputs does the function take?> */ +/* Globals : <Does it use any global variables?> */ +/* Processing : <Describe how the function operates - include algorithm */ +/* description> */ +/* Outputs : <What does the function produce?> */ +/* Returns : <What does the function return?> */ +/* */ +/* Issues : <List any issues or problems with this function> */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 25 09 2008 Jay Draft */ +/* */ +/*****************************************************************************/ + +WORD32 ih264d_cavlc_4x4res_block_totalcoeff_11to16(UWORD32 u4_isdc, + UWORD32 u4_total_coeff_trail_one, /*!<TotalCoefficients<<16+trailingones*/ + dec_bit_stream_t *ps_bitstrm ) +{ + UWORD32 u4_total_zeroes; + WORD32 i; + UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; + UWORD32 u4_bitstream_offset = ps_bitstrm->u4_ofst; + UWORD32 u4_trailing_ones = u4_total_coeff_trail_one & 0xFFFF; + UWORD32 u4_total_coeff = u4_total_coeff_trail_one >> 16; + // To avoid error check at 4x4 level, allocating for 3 extra levels(16+3) + // since u4_trailing_ones can at the max be 3. This will be required when + // u4_total_coeff is less than u4_trailing_ones + WORD16 ai2_level_arr[19];// + WORD16 *i2_level_arr = &ai2_level_arr[3]; + + tu_sblk4x4_coeff_data_t *ps_tu_4x4; + WORD16 *pi2_coeff_data; + dec_struct_t *ps_dec = (dec_struct_t *)ps_bitstrm->pv_codec_handle; + + ps_tu_4x4 = (tu_sblk4x4_coeff_data_t *)ps_dec->pv_parse_tu_coeff_data; + ps_tu_4x4->u2_sig_coeff_map = 0; + pi2_coeff_data = &ps_tu_4x4->ai2_level[0]; + + i = u4_total_coeff - 1; + if(u4_trailing_ones) + { + /*********************************************************************/ + /* Decode Trailing Ones */ + /* read the sign of T1's and put them in level array */ + /*********************************************************************/ + UWORD32 u4_signs, u4_cnt = u4_trailing_ones; + WORD16 (*ppi2_trlone_lkup)[3] = + (WORD16 (*)[3])gai2_ih264d_trailing_one_level; + WORD16 *pi2_trlone_lkup; + + GETBITS(u4_signs, u4_bitstream_offset, pu4_bitstrm_buf, u4_cnt); + + pi2_trlone_lkup = ppi2_trlone_lkup[(1 << u4_cnt) - 2 + u4_signs]; + + while(u4_cnt) + { + i2_level_arr[i--] = *pi2_trlone_lkup++; + u4_cnt--; + } + } + + /****************************************************************/ + /* Decoding Levels Begins */ + /****************************************************************/ + if(i >= 0) + { + /****************************************************************/ + /* First level is decoded outside the loop as it has lot of */ + /* special cases. */ + /****************************************************************/ + UWORD32 u4_lev_suffix, u4_suffix_len, u4_lev_suffix_size; + UWORD16 u2_lev_code, u2_abs_value; + UWORD32 u4_lev_prefix; + + if(u4_trailing_ones < 3) + { + /*********************************************************/ + /* u4_suffix_len = 1 */ + /*********************************************************/ + /***************************************************************/ + /* Find leading zeros in next 32 bits */ + /***************************************************************/ + FIND_ONE_IN_STREAM_32(u4_lev_prefix, u4_bitstream_offset, + pu4_bitstrm_buf); + + u4_lev_suffix_size = + (15 <= u4_lev_prefix) ? (u4_lev_prefix - 3) : 1; + + GETBITS(u4_lev_suffix, u4_bitstream_offset, pu4_bitstrm_buf, + u4_lev_suffix_size); + u2_lev_code = 2 + (MIN(u4_lev_prefix,15) << 1) + u4_lev_suffix; + + //HP_LEVEL_PREFIX + if(16 <= u4_lev_prefix) + { + u2_lev_code += ((1 << (u4_lev_prefix - 3)) - 4096); + } + } + else + { + /*********************************************************/ + /*u4_suffix_len = 0 */ + /*********************************************************/ + /***************************************************************/ + /* Find leading zeros in next 32 bits */ + /***************************************************************/ + FIND_ONE_IN_STREAM_32(u4_lev_prefix, u4_bitstream_offset, + pu4_bitstrm_buf); + + /*********************************************************/ + /* Special decoding case when trailing ones are 3 */ + /*********************************************************/ + u2_lev_code = MIN(15, u4_lev_prefix); + + u2_lev_code += (3 == u4_trailing_ones) ? 0 : (2); + + if(14 == u4_lev_prefix) + u4_lev_suffix_size = 4; + else if(15 <= u4_lev_prefix) + { + u2_lev_code += 15; + u4_lev_suffix_size = (u4_lev_prefix - 3); + } + else + u4_lev_suffix_size = 0; + + //HP_LEVEL_PREFIX + if(16 <= u4_lev_prefix) + { + u2_lev_code += ((1 << (u4_lev_prefix - 3)) - 4096); + } + if(u4_lev_suffix_size) + { + GETBITS(u4_lev_suffix, u4_bitstream_offset, pu4_bitstrm_buf, + u4_lev_suffix_size); + u2_lev_code += u4_lev_suffix; + } + } + + u2_abs_value = (u2_lev_code + 2) >> 1; + /*********************************************************/ + /* If Level code is odd, level is negative else positive */ + /*********************************************************/ + i2_level_arr[i--] = (u2_lev_code & 1) ? -u2_abs_value : u2_abs_value; + + u4_suffix_len = (u2_abs_value > 3) ? 2 : 1; + + /*********************************************************/ + /* Now loop over the remaining levels */ + /*********************************************************/ + while(i >= 0) + { + + /***************************************************************/ + /* Find leading zeros in next 32 bits */ + /***************************************************************/ + FIND_ONE_IN_STREAM_32(u4_lev_prefix, u4_bitstream_offset, + pu4_bitstrm_buf); + + u4_lev_suffix_size = + (15 <= u4_lev_prefix) ? + (u4_lev_prefix - 3) : u4_suffix_len; + + /*********************************************************/ + /* Compute level code using prefix and suffix */ + /*********************************************************/ + GETBITS(u4_lev_suffix, u4_bitstream_offset, pu4_bitstrm_buf, + u4_lev_suffix_size); + u2_lev_code = (MIN(15,u4_lev_prefix) << u4_suffix_len) + + u4_lev_suffix; + + //HP_LEVEL_PREFIX + if(16 <= u4_lev_prefix) + { + u2_lev_code += ((1 << (u4_lev_prefix - 3)) - 4096); + } + u2_abs_value = (u2_lev_code + 2) >> 1; + + /*********************************************************/ + /* If Level code is odd, level is negative else positive */ + /*********************************************************/ + i2_level_arr[i--] = + (u2_lev_code & 1) ? -u2_abs_value : u2_abs_value; + + /*********************************************************/ + /* Increment suffix length if required */ + /*********************************************************/ + u4_suffix_len += + (u4_suffix_len < 6) ? + (u2_abs_value + > (3 + << (u4_suffix_len + - 1))) : + 0; + } + + /****************************************************************/ + /* Decoding Levels Ends */ + /****************************************************************/ + } + + if(u4_total_coeff < (16 - u4_isdc)) + { + UWORD32 u4_index; + const UWORD8 (*ppu1_total_zero_lkup)[16] = + (const UWORD8 (*)[16])gau1_ih264d_table_total_zero_11to15; + + NEXTBITS(u4_index, u4_bitstream_offset, pu4_bitstrm_buf, 4); + u4_total_zeroes = ppu1_total_zero_lkup[u4_total_coeff - 11][u4_index]; + + FLUSHBITS(u4_bitstream_offset, (u4_total_zeroes >> 4)); + u4_total_zeroes &= 0xf; + } + else + u4_total_zeroes = 0; + + /**************************************************************/ + /* Decode the runs and form the coefficient buffer */ + /**************************************************************/ + { + const UWORD8 *pu1_table_runbefore; + UWORD32 u4_run; + WORD32 k; + WORD32 u4_scan_pos = u4_total_coeff + u4_total_zeroes - 1 + u4_isdc; + WORD32 u4_zeroes_left = u4_total_zeroes; + k = u4_total_coeff - 1; + + /**************************************************************/ + /* Decoding Runs for 0 < zeros left <=6 */ + /**************************************************************/ + pu1_table_runbefore = (UWORD8 *)gau1_ih264d_table_run_before; + while((u4_zeroes_left > 0) && k) + { + UWORD32 u4_code; + NEXTBITS(u4_code, u4_bitstream_offset, pu4_bitstrm_buf, 3); + + u4_code = pu1_table_runbefore[u4_code + (u4_zeroes_left << 3)]; + u4_run = u4_code >> 2; + + FLUSHBITS(u4_bitstream_offset, (u4_code & 0x03)); + SET_BIT(ps_tu_4x4->u2_sig_coeff_map, u4_scan_pos); + *pi2_coeff_data++ = i2_level_arr[k--]; + u4_zeroes_left -= (WORD32)u4_run; + u4_scan_pos -= (WORD32)(u4_run + 1); + } + if (u4_zeroes_left < 0 || u4_scan_pos < 0) + return -1; + + /**************************************************************/ + /* Decoding Runs End */ + /**************************************************************/ + + /**************************************************************/ + /* Copy the remaining coefficients */ + /**************************************************************/ + while(k >= 0) + { + SET_BIT(ps_tu_4x4->u2_sig_coeff_map, u4_scan_pos); + *pi2_coeff_data++ = i2_level_arr[k--]; + u4_scan_pos--; + } + } + + { + WORD32 offset; + offset = (UWORD8 *)pi2_coeff_data - (UWORD8 *)ps_tu_4x4; + offset = ALIGN4(offset); + ps_dec->pv_parse_tu_coeff_data = (void *)((UWORD8 *)ps_dec->pv_parse_tu_coeff_data + offset); + } + + ps_bitstrm->u4_ofst = u4_bitstream_offset; + return 0; +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_rest_of_residual_cav_chroma_dc_block */ +/* */ +/* Description : This function does the Cavlc parsing of the bitstream */ +/* for chroma dc coefficients */ +/* Inputs : <What inputs does the function take?> */ +/* Globals : <Does it use any global variables?> */ +/* Processing : <Describe how the function operates - include algorithm */ +/* description> */ +/* Outputs : <What does the function produce?> */ +/* Returns : <What does the function return?> */ +/* */ +/* Issues : <List any issues or problems with this function> */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 15 09 2008 Jay Draft */ +/* */ +/*****************************************************************************/ +void ih264d_rest_of_residual_cav_chroma_dc_block(UWORD32 u4_total_coeff_trail_one, + dec_bit_stream_t *ps_bitstrm) +{ + UWORD32 u4_total_zeroes; + WORD16 i; + UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; + UWORD32 u4_bitstream_offset = ps_bitstrm->u4_ofst; + UWORD32 u4_trailing_ones = u4_total_coeff_trail_one & 0xFFFF; + UWORD32 u4_total_coeff = u4_total_coeff_trail_one >> 16; + // To avoid error check at 4x4 level, allocating for 3 extra levels(4+3) + // since u4_trailing_ones can at the max be 3. This will be required when + // u4_total_coeff is less than u4_trailing_ones + WORD16 ai2_level_arr[7];// + WORD16 *i2_level_arr = &ai2_level_arr[3]; + + tu_sblk4x4_coeff_data_t *ps_tu_4x4; + WORD16 *pi2_coeff_data; + dec_struct_t *ps_dec = (dec_struct_t *)ps_bitstrm->pv_codec_handle; + + ps_tu_4x4 = (tu_sblk4x4_coeff_data_t *)ps_dec->pv_parse_tu_coeff_data; + ps_tu_4x4->u2_sig_coeff_map = 0; + pi2_coeff_data = &ps_tu_4x4->ai2_level[0]; + + i = u4_total_coeff - 1; + if(u4_trailing_ones) + { + /*********************************************************************/ + /* Decode Trailing Ones */ + /* read the sign of T1's and put them in level array */ + /*********************************************************************/ + UWORD32 u4_signs, u4_cnt = u4_trailing_ones; + WORD16 (*ppi2_trlone_lkup)[3] = + (WORD16 (*)[3])gai2_ih264d_trailing_one_level; + WORD16 *pi2_trlone_lkup; + + GETBITS(u4_signs, u4_bitstream_offset, pu4_bitstrm_buf, u4_cnt); + + pi2_trlone_lkup = ppi2_trlone_lkup[(1 << u4_cnt) - 2 + u4_signs]; + + while(u4_cnt) + { + i2_level_arr[i--] = *pi2_trlone_lkup++; + u4_cnt--; + } + } + + /****************************************************************/ + /* Decoding Levels Begins */ + /****************************************************************/ + if(i >= 0) + { + /****************************************************************/ + /* First level is decoded outside the loop as it has lot of */ + /* special cases. */ + /****************************************************************/ + UWORD32 u4_lev_suffix, u4_suffix_len, u4_lev_suffix_size; + UWORD16 u2_lev_code, u2_abs_value; + UWORD32 u4_lev_prefix; + + /***************************************************************/ + /* u4_suffix_len = 0, Find leading zeros in next 32 bits */ + /***************************************************************/ + FIND_ONE_IN_STREAM_32(u4_lev_prefix, u4_bitstream_offset, + pu4_bitstrm_buf); + + /*********************************************************/ + /* Special decoding case when trailing ones are 3 */ + /*********************************************************/ + u2_lev_code = MIN(15, u4_lev_prefix); + + u2_lev_code += (3 == u4_trailing_ones) ? 0 : (2); + + if(14 == u4_lev_prefix) + u4_lev_suffix_size = 4; + else if(15 <= u4_lev_prefix) + { + u2_lev_code += 15; + u4_lev_suffix_size = u4_lev_prefix - 3; + } + else + u4_lev_suffix_size = 0; + + //HP_LEVEL_PREFIX + if(16 <= u4_lev_prefix) + { + u2_lev_code += ((1 << (u4_lev_prefix - 3)) - 4096); + } + if(u4_lev_suffix_size) + { + GETBITS(u4_lev_suffix, u4_bitstream_offset, pu4_bitstrm_buf, + u4_lev_suffix_size); + u2_lev_code += u4_lev_suffix; + } + + u2_abs_value = (u2_lev_code + 2) >> 1; + /*********************************************************/ + /* If Level code is odd, level is negative else positive */ + /*********************************************************/ + i2_level_arr[i--] = (u2_lev_code & 1) ? -u2_abs_value : u2_abs_value; + + u4_suffix_len = (u2_abs_value > 3) ? 2 : 1; + + /*********************************************************/ + /* Now loop over the remaining levels */ + /*********************************************************/ + while(i >= 0) + { + + /***************************************************************/ + /* Find leading zeros in next 32 bits */ + /***************************************************************/ + FIND_ONE_IN_STREAM_32(u4_lev_prefix, u4_bitstream_offset, + pu4_bitstrm_buf); + + u4_lev_suffix_size = + (15 <= u4_lev_prefix) ? + (u4_lev_prefix - 3) : u4_suffix_len; + + /*********************************************************/ + /* Compute level code using prefix and suffix */ + /*********************************************************/ + GETBITS(u4_lev_suffix, u4_bitstream_offset, pu4_bitstrm_buf, + u4_lev_suffix_size); + u2_lev_code = (MIN(u4_lev_prefix,15) << u4_suffix_len) + + u4_lev_suffix; + + //HP_LEVEL_PREFIX + if(16 <= u4_lev_prefix) + { + u2_lev_code += ((1 << (u4_lev_prefix - 3)) - 4096); + } + u2_abs_value = (u2_lev_code + 2) >> 1; + + /*********************************************************/ + /* If Level code is odd, level is negative else positive */ + /*********************************************************/ + i2_level_arr[i--] = + (u2_lev_code & 1) ? -u2_abs_value : u2_abs_value; + + /*********************************************************/ + /* Increment suffix length if required */ + /*********************************************************/ + u4_suffix_len += (u2_abs_value > (3 << (u4_suffix_len - 1))); + } + + /****************************************************************/ + /* Decoding Levels Ends */ + /****************************************************************/ + } + + if(u4_total_coeff < 4) + { + UWORD32 u4_max_ldz = (4 - u4_total_coeff); + FIND_ONE_IN_STREAM_LEN(u4_total_zeroes, u4_bitstream_offset, + pu4_bitstrm_buf, u4_max_ldz); + } + else + u4_total_zeroes = 0; + + /**************************************************************/ + /* Decode the runs and form the coefficient buffer */ + /**************************************************************/ + { + const UWORD8 *pu1_table_runbefore; + UWORD32 u4_run; + WORD32 u4_scan_pos = (u4_total_coeff + u4_total_zeroes - 1); + UWORD32 u4_zeroes_left = u4_total_zeroes; + i = u4_total_coeff - 1; + + /**************************************************************/ + /* Decoding Runs for 0 < zeros left <=6 */ + /**************************************************************/ + pu1_table_runbefore = (UWORD8 *)gau1_ih264d_table_run_before; + while(u4_zeroes_left && i) + { + UWORD32 u4_code; + NEXTBITS(u4_code, u4_bitstream_offset, pu4_bitstrm_buf, 3); + + u4_code = pu1_table_runbefore[u4_code + (u4_zeroes_left << 3)]; + u4_run = u4_code >> 2; + + FLUSHBITS(u4_bitstream_offset, (u4_code & 0x03)); + SET_BIT(ps_tu_4x4->u2_sig_coeff_map, u4_scan_pos); + *pi2_coeff_data++ = i2_level_arr[i--]; + u4_zeroes_left -= (WORD32)u4_run; + u4_scan_pos -= (WORD32)(u4_run + 1); + } + /**************************************************************/ + /* Decoding Runs End */ + /**************************************************************/ + + /**************************************************************/ + /* Copy the remaining coefficients */ + /**************************************************************/ + while(i >= 0) + { + SET_BIT(ps_tu_4x4->u2_sig_coeff_map, u4_scan_pos); + *pi2_coeff_data++ = i2_level_arr[i--]; + u4_scan_pos--; + } + } + + { + WORD32 offset; + offset = (UWORD8 *)pi2_coeff_data - (UWORD8 *)ps_tu_4x4; + offset = ALIGN4(offset); + ps_dec->pv_parse_tu_coeff_data = (void *)((UWORD8 *)ps_dec->pv_parse_tu_coeff_data + offset); + } + + ps_bitstrm->u4_ofst = u4_bitstream_offset; +} + +/*! + ************************************************************************** + * \if Function name : CavlcParsingInvScanInvQuant \endif + * + * \brief + * This function do cavlc parsing of coefficient tokens for any block + * type except chromDc and depending + * on whenther any coefficients to be parsed calls module + * RestOfResidualBlockCavlc. + * + * \return + * Returns total number of non-zero coefficients. + * + ************************************************************************** + */ + +WORD32 ih264d_cavlc_parse4x4coeff_n0to7(WORD16 *pi2_coeff_block, + UWORD32 u4_isdc, /* is it a DC block */ + WORD32 u4_n, + dec_struct_t *ps_dec, + UWORD32 *pu4_total_coeff) +{ + dec_bit_stream_t *ps_bitstrm = ps_dec->ps_bitstrm; + UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; + UWORD32 u4_bitstream_offset = ps_bitstrm->u4_ofst; + UWORD32 u4_code, u4_index, u4_ldz; + const UWORD16 *pu2_code = (const UWORD16*)gau2_ih264d_code_gx; + const UWORD16 *pu2_offset_num_vlc = + (const UWORD16 *)gau2_ih264d_offset_num_vlc_tab; + UWORD32 u4_offset_num_vlc = pu2_offset_num_vlc[u4_n]; + + + UNUSED(pi2_coeff_block); + *pu4_total_coeff = 0; + FIND_ONE_IN_STREAM_32(u4_ldz, u4_bitstream_offset, pu4_bitstrm_buf); + NEXTBITS(u4_index, u4_bitstream_offset, pu4_bitstrm_buf, 3); + u4_index += (u4_ldz << 3); + u4_index += u4_offset_num_vlc; + + u4_index = MIN(u4_index, 303); + u4_code = pu2_code[u4_index]; + + FLUSHBITS(u4_bitstream_offset, (u4_code & 0x03)); + ps_bitstrm->u4_ofst = u4_bitstream_offset; + *pu4_total_coeff = (u4_code >> 4); + + if(*pu4_total_coeff) + { + UWORD32 u4_trailing_ones, u4_offset, u4_total_coeff_tone; + const UWORD8 *pu1_offset = + (UWORD8 *)gau1_ih264d_total_coeff_fn_ptr_offset; + WORD32 ret; + u4_trailing_ones = ((u4_code >> 2) & 0x03); + u4_offset = pu1_offset[*pu4_total_coeff - 1]; + u4_total_coeff_tone = (*pu4_total_coeff << 16) | u4_trailing_ones; + + ret = ps_dec->pf_cavlc_4x4res_block[u4_offset](u4_isdc, + u4_total_coeff_tone, + ps_bitstrm); + if(ret != 0) + return ERROR_CAVLC_NUM_COEFF_T; + } + + return OK; +} + +WORD32 ih264d_cavlc_parse4x4coeff_n8(WORD16 *pi2_coeff_block, + UWORD32 u4_isdc, /* is it a DC block */ + WORD32 u4_n, + dec_struct_t *ps_dec, + UWORD32 *pu4_total_coeff) +{ + + dec_bit_stream_t *ps_bitstrm = ps_dec->ps_bitstrm; + UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; + UWORD32 u4_bitstream_offset = ps_bitstrm->u4_ofst; + UWORD32 u4_code; + UNUSED(u4_n); + UNUSED(pi2_coeff_block); + GETBITS(u4_code, u4_bitstream_offset, pu4_bitstrm_buf, 6); + ps_bitstrm->u4_ofst = u4_bitstream_offset; + *pu4_total_coeff = 0; + + if(u4_code != 3) + { + UWORD8 *pu1_offset = (UWORD8 *)gau1_ih264d_total_coeff_fn_ptr_offset; + UWORD32 u4_trailing_ones, u4_offset, u4_total_coeff_tone; + + *pu4_total_coeff = (u4_code >> 2) + 1; + u4_trailing_ones = u4_code & 0x03; + u4_offset = pu1_offset[*pu4_total_coeff - 1]; + u4_total_coeff_tone = (*pu4_total_coeff << 16) | u4_trailing_ones; + + ps_dec->pf_cavlc_4x4res_block[u4_offset](u4_isdc, + u4_total_coeff_tone, + ps_bitstrm); + } + + return OK; +} + +/*! + ************************************************************************** + * \if Function name : ih264d_cavlc_parse_chroma_dc \endif + * + * \brief + * This function do cavlc parsing of coefficient tokens chromDc block + * and depending on whenther any coefficients to be parsed calls module + * ih264d_rest_of_residual_cav_chroma_dc_block. + * + * \return + * Returns total number of non-zero coefficients. + * + ************************************************************************** + */ + +void ih264d_cavlc_parse_chroma_dc(dec_mb_info_t *ps_cur_mb_info, + WORD16 *pi2_coeff_block, + dec_bit_stream_t *ps_bitstrm, + UWORD32 u4_scale_u, + UWORD32 u4_scale_v, + WORD32 i4_mb_inter_inc) +{ + UWORD32 u4_total_coeff, u4_trailing_ones, u4_total_coeff_tone, u4_code; + UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; + UWORD32 u4_bitstream_offset = ps_bitstrm->u4_ofst; + const UWORD8 *pu1_cav_chromdc = (const UWORD8*)gau1_ih264d_cav_chromdc_vld; + UNUSED(i4_mb_inter_inc); + /******************************************************************/ + /* Chroma DC Block for U component */ + /******************************************************************/ + NEXTBITS(u4_code, u4_bitstream_offset, pu4_bitstrm_buf, 8); + + u4_code = pu1_cav_chromdc[u4_code]; + + FLUSHBITS(u4_bitstream_offset, ((u4_code & 0x7) + 1)); + ps_bitstrm->u4_ofst = u4_bitstream_offset; + + u4_total_coeff = (u4_code >> 5); + + if(u4_total_coeff) + { + WORD32 i_z0, i_z1, i_z2, i_z3; + tu_sblk4x4_coeff_data_t *ps_tu_4x4; + dec_struct_t *ps_dec = (dec_struct_t *)ps_bitstrm->pv_codec_handle; + WORD16 ai2_dc_coef[4]; + UWORD8 pu1_inv_scan[4] = + { 0, 1, 2, 3 }; + WORD16 *pi2_coeff_data = + (WORD16 *)ps_dec->pv_parse_tu_coeff_data; + + ps_tu_4x4 = (tu_sblk4x4_coeff_data_t *)ps_dec->pv_parse_tu_coeff_data; + + u4_trailing_ones = ((u4_code >> 3) & 0x3); + u4_total_coeff_tone = (u4_total_coeff << 16) | u4_trailing_ones; + ih264d_rest_of_residual_cav_chroma_dc_block(u4_total_coeff_tone, + ps_bitstrm); + + ai2_dc_coef[0] = 0; + ai2_dc_coef[1] = 0; + ai2_dc_coef[2] = 0; + ai2_dc_coef[3] = 0; + + ih264d_unpack_coeff4x4_dc_4x4blk(ps_tu_4x4, + ai2_dc_coef, + pu1_inv_scan); + /*-------------------------------------------------------------------*/ + /* Inverse 2x2 transform and scaling of chroma DC */ + /*-------------------------------------------------------------------*/ + i_z0 = (ai2_dc_coef[0] + ai2_dc_coef[2]); + i_z1 = (ai2_dc_coef[0] - ai2_dc_coef[2]); + i_z2 = (ai2_dc_coef[1] - ai2_dc_coef[3]); + i_z3 = (ai2_dc_coef[1] + ai2_dc_coef[3]); + + /*-----------------------------------------------------------*/ + /* Scaling and storing the values back */ + /*-----------------------------------------------------------*/ + *pi2_coeff_data++ = (WORD16)(((i_z0 + i_z3) * (WORD32)u4_scale_u) >> 5); + *pi2_coeff_data++ = (WORD16)(((i_z0 - i_z3) * (WORD32)u4_scale_u) >> 5); + *pi2_coeff_data++ = (WORD16)(((i_z1 + i_z2) * (WORD32)u4_scale_u) >> 5); + *pi2_coeff_data++ = (WORD16)(((i_z1 - i_z2) * (WORD32)u4_scale_u) >> 5); + + ps_dec->pv_parse_tu_coeff_data = (void *)pi2_coeff_data; + + SET_BIT(ps_cur_mb_info->u1_yuv_dc_block_flag,1); + } + + /******************************************************************/ + /* Chroma DC Block for V component */ + /******************************************************************/ + pi2_coeff_block += 64; + u4_bitstream_offset = ps_bitstrm->u4_ofst; + + NEXTBITS(u4_code, u4_bitstream_offset, pu4_bitstrm_buf, 8); + + u4_code = pu1_cav_chromdc[u4_code]; + + FLUSHBITS(u4_bitstream_offset, ((u4_code & 0x7) + 1)); + ps_bitstrm->u4_ofst = u4_bitstream_offset; + + u4_total_coeff = (u4_code >> 5); + + if(u4_total_coeff) + { + WORD32 i_z0, i_z1, i_z2, i_z3; + tu_sblk4x4_coeff_data_t *ps_tu_4x4; + dec_struct_t *ps_dec = (dec_struct_t *)ps_bitstrm->pv_codec_handle; + WORD16 ai2_dc_coef[4]; + UWORD8 pu1_inv_scan[4] = + { 0, 1, 2, 3 }; + WORD16 *pi2_coeff_data = + (WORD16 *)ps_dec->pv_parse_tu_coeff_data; + + ps_tu_4x4 = (tu_sblk4x4_coeff_data_t *)ps_dec->pv_parse_tu_coeff_data; + + u4_trailing_ones = ((u4_code >> 3) & 0x3); + u4_total_coeff_tone = (u4_total_coeff << 16) | u4_trailing_ones; + ih264d_rest_of_residual_cav_chroma_dc_block(u4_total_coeff_tone, + ps_bitstrm); + + ai2_dc_coef[0] = 0; + ai2_dc_coef[1] = 0; + ai2_dc_coef[2] = 0; + ai2_dc_coef[3] = 0; + + ih264d_unpack_coeff4x4_dc_4x4blk(ps_tu_4x4, + ai2_dc_coef, + pu1_inv_scan); + + /*-------------------------------------------------------------------*/ + /* Inverse 2x2 transform and scaling of chroma DC */ + /*-------------------------------------------------------------------*/ + i_z0 = (ai2_dc_coef[0] + ai2_dc_coef[2]); + i_z1 = (ai2_dc_coef[0] - ai2_dc_coef[2]); + i_z2 = (ai2_dc_coef[1] - ai2_dc_coef[3]); + i_z3 = (ai2_dc_coef[1] + ai2_dc_coef[3]); + + /*-----------------------------------------------------------*/ + /* Scaling and storing the values back */ + /*-----------------------------------------------------------*/ + *pi2_coeff_data++ = (WORD16)(((i_z0 + i_z3) * (WORD32)u4_scale_v) >> 5); + *pi2_coeff_data++ = (WORD16)(((i_z0 - i_z3) * (WORD32)u4_scale_v) >> 5); + *pi2_coeff_data++ = (WORD16)(((i_z1 + i_z2) * (WORD32)u4_scale_v) >> 5); + *pi2_coeff_data++ = (WORD16)(((i_z1 - i_z2) * (WORD32)u4_scale_v) >> 5); + + ps_dec->pv_parse_tu_coeff_data = (void *)pi2_coeff_data; + + SET_BIT(ps_cur_mb_info->u1_yuv_dc_block_flag,2); + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_parse_pmb_ref_index_cavlc_range1 */ +/* */ +/* Description : This function does the Cavlc TEV range =1 parsing of */ +/* reference index for a P MB. Range is 1 when */ +/* num_ref_idx_active_minus1 is 0 */ +/* */ +/* Inputs : <What inputs does the function take?> */ +/* Globals : <Does it use any global variables?> */ +/* Processing : <Describe how the function operates - include algorithm */ +/* description> */ +/* Outputs : <What does the function produce?> */ +/* Returns : <What does the function return?> */ +/* */ +/* Issues : <List any issues or problems with this function> */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 19 09 2008 Jay Draft */ +/* */ +/*****************************************************************************/ +void ih264d_parse_pmb_ref_index_cavlc_range1(UWORD32 u4_num_part, /* Number of partitions in MB */ + dec_bit_stream_t *ps_bitstrm, /* Pointer to bitstream Structure. */ + WORD8 *pi1_ref_idx, /* pointer to reference index array */ + UWORD32 u4_num_ref_idx_active_minus1 /* Not used for range 1 */ + ) +{ + UWORD32 u4_i; + UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; + UWORD32 *pu4_bitstream_off = &ps_bitstrm->u4_ofst; + UNUSED(u4_num_ref_idx_active_minus1); + for(u4_i = 0; u4_i < u4_num_part; u4_i++) + { + UWORD32 u4_ref_idx; + u4_ref_idx = ih264d_tev_range1(pu4_bitstream_off, pu4_bitstrm_buf); + + /* Storing Reference Idx Information */ + pi1_ref_idx[u4_i] = (WORD8)u4_ref_idx; + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_parse_pmb_ref_index_cavlc */ +/* */ +/* Description : This function does the Cavlc TEV range > 1 parsing of */ +/* reference index for a P MB. */ +/* Range > 1 when num_ref_idx_active_minus1 > 0 */ +/* */ +/* Inputs : <What inputs does the function take?> */ +/* Globals : <Does it use any global variables?> */ +/* Processing : <Describe how the function operates - include algorithm */ +/* description> */ +/* Outputs : <What does the function produce?> */ +/* Returns : <What does the function return?> */ +/* */ +/* Issues : <List any issues or problems with this function> */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 19 09 2008 Jay Draft */ +/* */ +/*****************************************************************************/ + +WORD32 ih264d_parse_pmb_ref_index_cavlc(UWORD32 u4_num_part, /* Number of partitions in MB */ + dec_bit_stream_t *ps_bitstrm, /* Pointer to bitstream Structure. */ + WORD8 *pi1_ref_idx, /* pointer to reference index array */ + UWORD32 u4_num_ref_idx_active_minus1 /* Number of active references - 1 */ + ) +{ + UWORD32 u4_i; + UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; + UWORD32 *pu4_bitstream_off = &ps_bitstrm->u4_ofst; + + for(u4_i = 0; u4_i < u4_num_part; u4_i++) + { + UWORD32 u4_ref_idx; +//Inlined ih264d_uev + UWORD32 u4_bitstream_offset = *pu4_bitstream_off; + UWORD32 u4_word, u4_ldz; + + /***************************************************************/ + /* Find leading zeros in next 32 bits */ + /***************************************************************/ + NEXTBITS_32(u4_word, u4_bitstream_offset, pu4_bitstrm_buf); + u4_ldz = CLZ(u4_word); + /* Flush the ps_bitstrm */ + u4_bitstream_offset += (u4_ldz + 1); + /* Read the suffix from the ps_bitstrm */ + u4_word = 0; + if(u4_ldz) + GETBITS(u4_word, u4_bitstream_offset, pu4_bitstrm_buf, u4_ldz); + *pu4_bitstream_off = u4_bitstream_offset; + u4_ref_idx = ((1 << u4_ldz) + u4_word - 1); +//Inlined ih264d_uev + + if(u4_ref_idx > u4_num_ref_idx_active_minus1) + return ERROR_REF_IDX; + + /* Storing Reference Idx Information */ + pi1_ref_idx[u4_i] = (WORD8)u4_ref_idx; + } + return OK; +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_parse_bmb_ref_index_cavlc_range1 */ +/* */ +/* Description : This function does the Cavlc TEV range =1 parsing of */ +/* reference index for a B MB. Range is 1 when */ +/* num_ref_idx_active_minus1 is 0 */ +/* */ +/* Inputs : <What inputs does the function take?> */ +/* Globals : <Does it use any global variables?> */ +/* Processing : <Describe how the function operates - include algorithm */ +/* description> */ +/* Outputs : <What does the function produce?> */ +/* Returns : <What does the function return?> */ +/* */ +/* Issues : <List any issues or problems with this function> */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 19 09 2008 Jay Draft */ +/* */ +/*****************************************************************************/ +void ih264d_parse_bmb_ref_index_cavlc_range1(UWORD32 u4_num_part, /* Number of partitions in MB */ + dec_bit_stream_t *ps_bitstrm, /* Pointer to bitstream Structure. */ + WORD8 *pi1_ref_idx, /* pointer to reference index array */ + UWORD32 u4_num_ref_idx_active_minus1 /* Not used for range 1 */ + ) +{ + UWORD32 u4_i; + UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; + UWORD32 *pu4_bitstream_off = &ps_bitstrm->u4_ofst; + UNUSED(u4_num_ref_idx_active_minus1); + for(u4_i = 0; u4_i < u4_num_part; u4_i++) + { + if(pi1_ref_idx[u4_i] > -1) + { + UWORD32 u4_ref_idx; + + u4_ref_idx = ih264d_tev_range1(pu4_bitstream_off, pu4_bitstrm_buf); + + /* Storing Reference Idx Information */ + pi1_ref_idx[u4_i] = (WORD8)u4_ref_idx; + } + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_parse_bmb_ref_index_cavlc */ +/* */ +/* Description : This function does the Cavlc TEV range > 1 parsing of */ +/* reference index for a B MB. */ +/* Range > 1 when num_ref_idx_active_minus1 > 0 */ +/* */ +/* Inputs : <What inputs does the function take?> */ +/* Globals : <Does it use any global variables?> */ +/* Processing : <Describe how the function operates - include algorithm */ +/* description> */ +/* Outputs : <What does the function produce?> */ +/* Returns : <What does the function return?> */ +/* */ +/* Issues : <List any issues or problems with this function> */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 19 09 2008 Jay Draft */ +/* */ +/*****************************************************************************/ +WORD32 ih264d_parse_bmb_ref_index_cavlc(UWORD32 u4_num_part, /* Number of partitions in MB */ + dec_bit_stream_t *ps_bitstrm, /* Pointer to bitstream Structure. */ + WORD8 *pi1_ref_idx, /* pointer to reference index array */ + UWORD32 u4_num_ref_idx_active_minus1 /* Number of active references - 1 */ + ) +{ + UWORD32 u4_i; + UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; + UWORD32 *pu4_bitstream_off = &ps_bitstrm->u4_ofst; + + for(u4_i = 0; u4_i < u4_num_part; u4_i++) + { + if(pi1_ref_idx[u4_i] > -1) + { + UWORD32 u4_ref_idx; +//inlining ih264d_uev + UWORD32 u4_bitstream_offset = *pu4_bitstream_off; + UWORD32 u4_word, u4_ldz; + + /***************************************************************/ + /* Find leading zeros in next 32 bits */ + /***************************************************************/ + NEXTBITS_32(u4_word, u4_bitstream_offset, pu4_bitstrm_buf); + u4_ldz = CLZ(u4_word); + /* Flush the ps_bitstrm */ + u4_bitstream_offset += (u4_ldz + 1); + /* Read the suffix from the ps_bitstrm */ + u4_word = 0; + if(u4_ldz) + GETBITS(u4_word, u4_bitstream_offset, pu4_bitstrm_buf, u4_ldz); + *pu4_bitstream_off = u4_bitstream_offset; + u4_ref_idx = ((1 << u4_ldz) + u4_word - 1); +//inlining ih264d_uev + if(u4_ref_idx > u4_num_ref_idx_active_minus1) + return ERROR_REF_IDX; + + /* Storing Reference Idx Information */ + pi1_ref_idx[u4_i] = (WORD8)u4_ref_idx; + } + } + return OK; +} +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_cavlc_parse_8x8block_both_available */ +/* */ +/* Description : This function does the residual parsing of 4 subblocks */ +/* in a 8x8 block when both top and left are available */ +/* */ +/* Inputs : pi2_coeff_block : pointer to residual block where */ +/* decoded and inverse scan coefficients are updated */ +/* */ +/* u4_sub_block_strd : indicates the number of sublocks */ +/* in a row. It is 4 for luma and 2 for chroma. */ +/* */ +/* u4_isdc : required to indicate 4x4 parse modules if the */ +/* current Mb is I_16x16/chroma DC coded. */ +/* */ +/* ps_dec : pointer to Decstruct (decoder context) */ +/* */ +/* pu1_top_nnz : top nnz pointer */ +/* */ +/* pu1_left_nnz : left nnz pointer */ +/* */ +/* Globals : No */ +/* Processing : Parsing for four subblocks in unrolled, top and left nnz */ +/* are updated on the fly. csbp is set in accordance to */ +/* decoded numcoeff for the subblock index in raster order */ +/* */ +/* Outputs : The updated residue buffer, nnzs and csbp current block */ +/* */ +/* Returns : Returns the coded sub block pattern csbp for the block */ +/* */ +/* Issues : <List any issues or problems with this function> */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 09 10 2008 Jay Draft */ +/* */ +/*****************************************************************************/ +WORD32 ih264d_cavlc_parse_8x8block_both_available(WORD16 *pi2_coeff_block, + UWORD32 u4_sub_block_strd, + UWORD32 u4_isdc, + dec_struct_t * ps_dec, + UWORD8 *pu1_top_nnz, + UWORD8 *pu1_left_nnz, + UWORD8 u1_tran_form8x8, + UWORD8 u1_mb_field_decodingflag, + UWORD32 *pu4_csbp) +{ + UWORD32 u4_num_coeff, u4_n, u4_subblock_coded; + UWORD32 u4_top0, u4_top1; + UWORD32 *pu4_dummy; + WORD32 (**pf_cavlc_parse4x4coeff)(WORD16 *pi2_coeff_block, + UWORD32 u4_isdc, + WORD32 u4_n, + struct _DecStruct *ps_dec, + UWORD32 *pu4_dummy) = + ps_dec->pf_cavlc_parse4x4coeff; + UWORD32 u4_idx = 0; + UWORD8 *puc_temp; + WORD32 ret; + + *pu4_csbp = 0; + /* need to change the inverse scan matrices here */ + puc_temp = ps_dec->pu1_inv_scan; + + /*------------------------------------------------------*/ + /* Residual 4x4 decoding: SubBlock 0 */ + /*------------------------------------------------------*/ + if(u1_tran_form8x8) + { + if(!u1_mb_field_decodingflag) + { + ps_dec->pu1_inv_scan = + (UWORD8*)gau1_ih264d_inv_scan_prog8x8_cavlc[0]; + } + else + { + ps_dec->pu1_inv_scan = + (UWORD8*)gau1_ih264d_inv_scan_int8x8_cavlc[0]; + } + } + u4_n = (pu1_top_nnz[0] + pu1_left_nnz[0] + 1) >> 1; + ret = pf_cavlc_parse4x4coeff[(u4_n > 7)](pi2_coeff_block, u4_isdc, + u4_n, ps_dec, &u4_num_coeff); + if(ret != OK) + return ret; + + u4_top0 = u4_num_coeff; + u4_subblock_coded = (u4_num_coeff != 0); + INSERT_BIT(*pu4_csbp, u4_idx, u4_subblock_coded); + + /*------------------------------------------------------*/ + /* Residual 4x4 decoding: SubBlock 1 */ + /*------------------------------------------------------*/ + u4_idx++; + if(u1_tran_form8x8) + { + if(!u1_mb_field_decodingflag) + { + ps_dec->pu1_inv_scan = + (UWORD8*)gau1_ih264d_inv_scan_prog8x8_cavlc[1]; + } + else + { + ps_dec->pu1_inv_scan = + (UWORD8*)gau1_ih264d_inv_scan_int8x8_cavlc[1]; + } + } + else + { + pi2_coeff_block += NUM_COEFFS_IN_4x4BLK; + } + u4_n = (pu1_top_nnz[1] + u4_num_coeff + 1) >> 1; + ret = pf_cavlc_parse4x4coeff[(u4_n > 7)](pi2_coeff_block, u4_isdc, + u4_n, ps_dec, &u4_num_coeff); + if(ret != OK) + return ret; + + u4_top1 = pu1_left_nnz[0] = u4_num_coeff; + u4_subblock_coded = (u4_num_coeff != 0); + INSERT_BIT(*pu4_csbp, u4_idx, u4_subblock_coded); + + /*------------------------------------------------------*/ + /* Residual 4x4 decoding: SubBlock 2 */ + /*------------------------------------------------------*/ + u4_idx += (u4_sub_block_strd - 1); + if(u1_tran_form8x8) + { + if(!u1_mb_field_decodingflag) + { + ps_dec->pu1_inv_scan = + (UWORD8*)gau1_ih264d_inv_scan_prog8x8_cavlc[2]; + } + else + { + ps_dec->pu1_inv_scan = + (UWORD8*)gau1_ih264d_inv_scan_int8x8_cavlc[2]; + } + } + else + { + pi2_coeff_block += ((u4_sub_block_strd - 1) * NUM_COEFFS_IN_4x4BLK); + } + u4_n = (u4_top0 + pu1_left_nnz[1] + 1) >> 1; + ret = pf_cavlc_parse4x4coeff[(u4_n > 7)](pi2_coeff_block, u4_isdc, + u4_n, ps_dec, &u4_num_coeff); + if(ret != OK) + return ret; + + pu1_top_nnz[0] = u4_num_coeff; + u4_subblock_coded = (u4_num_coeff != 0); + INSERT_BIT(*pu4_csbp, u4_idx, u4_subblock_coded); + + /*------------------------------------------------------*/ + /* Residual 4x4 decoding: SubBlock 3 */ + /*------------------------------------------------------*/ + u4_idx++; + if(u1_tran_form8x8) + { + if(!u1_mb_field_decodingflag) + { + ps_dec->pu1_inv_scan = + (UWORD8*)gau1_ih264d_inv_scan_prog8x8_cavlc[3]; + } + else + { + ps_dec->pu1_inv_scan = + (UWORD8*)gau1_ih264d_inv_scan_int8x8_cavlc[3]; + } + } + else + { + pi2_coeff_block += NUM_COEFFS_IN_4x4BLK; + } + u4_n = (u4_top1 + u4_num_coeff + 1) >> 1; + ret = pf_cavlc_parse4x4coeff[(u4_n > 7)](pi2_coeff_block, u4_isdc, + u4_n, ps_dec, &u4_num_coeff); + if(ret != OK) + return ret; + + pu1_top_nnz[1] = pu1_left_nnz[1] = u4_num_coeff; + u4_subblock_coded = (u4_num_coeff != 0); + INSERT_BIT(*pu4_csbp, u4_idx, u4_subblock_coded); + + ps_dec->pu1_inv_scan = puc_temp; + + return OK; +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_cavlc_parse_8x8block_left_available */ +/* */ +/* Description : This function does the residual parsing of 4 subblocks */ +/* in a 8x8 block when only left is available for block */ +/* */ +/* Inputs : pi2_coeff_block : pointer to residual block where */ +/* decoded and inverse scan coefficients are updated */ +/* */ +/* u4_sub_block_strd : indicates the number of sublocks */ +/* in a row. It is 4 for luma and 2 for chroma. */ +/* */ +/* u4_isdc : required to indicate 4x4 parse modules if the */ +/* current Mb is I_16x16/chroma DC coded. */ +/* */ +/* ps_dec : pointer to Decstruct (decoder context) */ +/* */ +/* pu1_top_nnz : top nnz pointer */ +/* */ +/* pu1_left_nnz : left nnz pointer */ +/* */ +/* Globals : No */ +/* Processing : Parsing for four subblocks in unrolled, top and left nnz */ +/* are updated on the fly. csbp is set in accordance to */ +/* decoded numcoeff for the subblock index in raster order */ +/* */ +/* Outputs : The updated residue buffer, nnzs and csbp current block */ +/* */ +/* Returns : Returns the coded sub block pattern csbp for the block */ +/* */ +/* Issues : <List any issues or problems with this function> */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 09 10 2008 Jay Draft */ +/* */ +/*****************************************************************************/ +WORD32 ih264d_cavlc_parse_8x8block_left_available(WORD16 *pi2_coeff_block, + UWORD32 u4_sub_block_strd, + UWORD32 u4_isdc, + dec_struct_t * ps_dec, + UWORD8 *pu1_top_nnz, + UWORD8 *pu1_left_nnz, + UWORD8 u1_tran_form8x8, + UWORD8 u1_mb_field_decodingflag, + UWORD32 *pu4_csbp) +{ + UWORD32 u4_num_coeff, u4_n, u4_subblock_coded; + UWORD32 u4_top0, u4_top1; + UWORD32 *pu4_dummy; + WORD32 (**pf_cavlc_parse4x4coeff)(WORD16 *pi2_coeff_block, + UWORD32 u4_isdc, + WORD32 u4_n, + struct _DecStruct *ps_dec, + UWORD32 *pu4_dummy) = + ps_dec->pf_cavlc_parse4x4coeff; + UWORD32 u4_idx = 0; + UWORD8 *puc_temp; + WORD32 ret; + + *pu4_csbp = 0; + puc_temp = ps_dec->pu1_inv_scan; + + /*------------------------------------------------------*/ + /* Residual 4x4 decoding: SubBlock 0 */ + /*------------------------------------------------------*/ + if(u1_tran_form8x8) + { + if(!u1_mb_field_decodingflag) + { + ps_dec->pu1_inv_scan = + (UWORD8*)gau1_ih264d_inv_scan_prog8x8_cavlc[0]; + } + else + { + ps_dec->pu1_inv_scan = + (UWORD8*)gau1_ih264d_inv_scan_int8x8_cavlc[0]; + } + } + u4_n = pu1_left_nnz[0]; + ret = pf_cavlc_parse4x4coeff[(u4_n > 7)](pi2_coeff_block, u4_isdc, + u4_n, ps_dec, &u4_num_coeff); + if(ret != OK) + return ret; + + u4_top0 = u4_num_coeff; + u4_subblock_coded = (u4_num_coeff != 0); + INSERT_BIT(*pu4_csbp, u4_idx, u4_subblock_coded); + + /*------------------------------------------------------*/ + /* Residual 4x4 decoding: SubBlock 1 */ + /*------------------------------------------------------*/ + u4_idx++; + if(u1_tran_form8x8) + { + if(!u1_mb_field_decodingflag) + { + ps_dec->pu1_inv_scan = + (UWORD8*)gau1_ih264d_inv_scan_prog8x8_cavlc[1]; + } + else + { + ps_dec->pu1_inv_scan = + (UWORD8*)gau1_ih264d_inv_scan_int8x8_cavlc[1]; + } + } + else + { + pi2_coeff_block += NUM_COEFFS_IN_4x4BLK; + } + u4_n = u4_num_coeff; + ret = pf_cavlc_parse4x4coeff[(u4_n > 7)](pi2_coeff_block, u4_isdc, + u4_n, ps_dec, &u4_num_coeff); + if(ret != OK) + return ret; + + u4_top1 = pu1_left_nnz[0] = u4_num_coeff; + u4_subblock_coded = (u4_num_coeff != 0); + INSERT_BIT(*pu4_csbp, u4_idx, u4_subblock_coded); + + /*------------------------------------------------------*/ + /* Residual 4x4 decoding: SubBlock 2 */ + /*------------------------------------------------------*/ + u4_idx += (u4_sub_block_strd - 1); + if(u1_tran_form8x8) + { + if(!u1_mb_field_decodingflag) + { + ps_dec->pu1_inv_scan = + (UWORD8*)gau1_ih264d_inv_scan_prog8x8_cavlc[2]; + } + else + { + ps_dec->pu1_inv_scan = + (UWORD8*)gau1_ih264d_inv_scan_int8x8_cavlc[2]; + } + } + else + { + pi2_coeff_block += ((u4_sub_block_strd - 1) * NUM_COEFFS_IN_4x4BLK); + } + u4_n = (u4_top0 + pu1_left_nnz[1] + 1) >> 1; + ret = pf_cavlc_parse4x4coeff[(u4_n > 7)](pi2_coeff_block, u4_isdc, + u4_n, ps_dec, &u4_num_coeff); + if(ret != OK) + return ret; + + pu1_top_nnz[0] = u4_num_coeff; + u4_subblock_coded = (u4_num_coeff != 0); + INSERT_BIT(*pu4_csbp, u4_idx, u4_subblock_coded); + + /*------------------------------------------------------*/ + /* Residual 4x4 decoding: SubBlock 3 */ + /*------------------------------------------------------*/ + u4_idx++; + if(u1_tran_form8x8) + { + if(!u1_mb_field_decodingflag) + { + ps_dec->pu1_inv_scan = + (UWORD8*)gau1_ih264d_inv_scan_prog8x8_cavlc[3]; + } + else + { + ps_dec->pu1_inv_scan = + (UWORD8*)gau1_ih264d_inv_scan_int8x8_cavlc[3]; + } + } + else + { + pi2_coeff_block += NUM_COEFFS_IN_4x4BLK; + } + u4_n = (u4_top1 + u4_num_coeff + 1) >> 1; + ret = pf_cavlc_parse4x4coeff[(u4_n > 7)](pi2_coeff_block, u4_isdc, + u4_n, ps_dec, &u4_num_coeff); + if(ret != OK) + return ret; + + pu1_top_nnz[1] = pu1_left_nnz[1] = u4_num_coeff; + u4_subblock_coded = (u4_num_coeff != 0); + INSERT_BIT(*pu4_csbp, u4_idx, u4_subblock_coded); + + ps_dec->pu1_inv_scan = puc_temp; + + return OK; +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_cavlc_parse_8x8block_top_available */ +/* */ +/* Description : This function does the residual parsing of 4 subblocks */ +/* in a 8x8 block when only top is available for block */ +/* */ +/* Inputs : pi2_coeff_block : pointer to residual block where */ +/* decoded and inverse scan coefficients are updated */ +/* */ +/* u4_sub_block_strd : indicates the number of sublocks */ +/* in a row. It is 4 for luma and 2 for chroma. */ +/* */ +/* u4_isdc : required to indicate 4x4 parse modules if the */ +/* current Mb is I_16x16/chroma DC coded. */ +/* */ +/* ps_dec : pointer to Decstruct (decoder context) */ +/* */ +/* pu1_top_nnz : top nnz pointer */ +/* */ +/* pu1_left_nnz : left nnz pointer */ +/* */ +/* Globals : No */ +/* Processing : Parsing for four subblocks in unrolled, top and left nnz */ +/* are updated on the fly. csbp is set in accordance to */ +/* decoded numcoeff for the subblock index in raster order */ +/* */ +/* Outputs : The updated residue buffer, nnzs and csbp current block */ +/* */ +/* Returns : Returns the coded sub block pattern csbp for the block */ +/* */ +/* Issues : <List any issues or problems with this function> */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 09 10 2008 Jay Draft */ +/* */ +/*****************************************************************************/ +WORD32 ih264d_cavlc_parse_8x8block_top_available(WORD16 *pi2_coeff_block, + UWORD32 u4_sub_block_strd, + UWORD32 u4_isdc, + dec_struct_t * ps_dec, + UWORD8 *pu1_top_nnz, + UWORD8 *pu1_left_nnz, + UWORD8 u1_tran_form8x8, + UWORD8 u1_mb_field_decodingflag, + UWORD32 *pu4_csbp) +{ + UWORD32 u4_num_coeff, u4_n, u4_subblock_coded; + UWORD32 u4_top0, u4_top1; + UWORD32 *pu4_dummy; + WORD32 (**pf_cavlc_parse4x4coeff)(WORD16 *pi2_coeff_block, + UWORD32 u4_isdc, + WORD32 u4_n, + struct _DecStruct *ps_dec, + UWORD32 *pu4_dummy) = + ps_dec->pf_cavlc_parse4x4coeff; + UWORD32 u4_idx = 0; + UWORD8 *puc_temp; + WORD32 ret; + + *pu4_csbp = 0; + puc_temp = ps_dec->pu1_inv_scan; + + /*------------------------------------------------------*/ + /* Residual 4x4 decoding: SubBlock 0 */ + /*------------------------------------------------------*/ + if(u1_tran_form8x8) + { + if(!u1_mb_field_decodingflag) + { + ps_dec->pu1_inv_scan = + (UWORD8*)gau1_ih264d_inv_scan_prog8x8_cavlc[0]; + } + else + { + ps_dec->pu1_inv_scan = + (UWORD8*)gau1_ih264d_inv_scan_int8x8_cavlc[0]; + } + } + u4_n = pu1_top_nnz[0]; + ret = pf_cavlc_parse4x4coeff[(u4_n > 7)](pi2_coeff_block, u4_isdc, + u4_n, ps_dec, &u4_num_coeff); + if(ret != OK) + return ret; + + u4_top0 = u4_num_coeff; + u4_subblock_coded = (u4_num_coeff != 0); + INSERT_BIT(*pu4_csbp, u4_idx, u4_subblock_coded); + + /*------------------------------------------------------*/ + /* Residual 4x4 decoding: SubBlock 1 */ + /*------------------------------------------------------*/ + u4_idx++; + if(u1_tran_form8x8) + { + if(!u1_mb_field_decodingflag) + { + ps_dec->pu1_inv_scan = + (UWORD8*)gau1_ih264d_inv_scan_prog8x8_cavlc[1]; + } + else + { + ps_dec->pu1_inv_scan = + (UWORD8*)gau1_ih264d_inv_scan_int8x8_cavlc[1]; + } + } + else + { + pi2_coeff_block += NUM_COEFFS_IN_4x4BLK; + } + u4_n = (pu1_top_nnz[1] + u4_num_coeff + 1) >> 1; + ret = pf_cavlc_parse4x4coeff[(u4_n > 7)](pi2_coeff_block, u4_isdc, + u4_n, ps_dec, &u4_num_coeff); + if(ret != OK) + return ret; + + u4_top1 = pu1_left_nnz[0] = u4_num_coeff; + u4_subblock_coded = (u4_num_coeff != 0); + INSERT_BIT(*pu4_csbp, u4_idx, u4_subblock_coded); + + /*------------------------------------------------------*/ + /* Residual 4x4 decoding: SubBlock 2 */ + /*------------------------------------------------------*/ + u4_idx += (u4_sub_block_strd - 1); + if(u1_tran_form8x8) + { + if(!u1_mb_field_decodingflag) + { + ps_dec->pu1_inv_scan = + (UWORD8*)gau1_ih264d_inv_scan_prog8x8_cavlc[2]; + } + else + { + ps_dec->pu1_inv_scan = + (UWORD8*)gau1_ih264d_inv_scan_int8x8_cavlc[2]; + } + } + else + { + pi2_coeff_block += ((u4_sub_block_strd - 1) * NUM_COEFFS_IN_4x4BLK); + } + u4_n = u4_top0; + ret = pf_cavlc_parse4x4coeff[(u4_n > 7)](pi2_coeff_block, u4_isdc, + u4_n, ps_dec, &u4_num_coeff); + if(ret != OK) + return ret; + + pu1_top_nnz[0] = u4_num_coeff; + u4_subblock_coded = (u4_num_coeff != 0); + INSERT_BIT(*pu4_csbp, u4_idx, u4_subblock_coded); + + /*------------------------------------------------------*/ + /* Residual 4x4 decoding: SubBlock 3 */ + /*------------------------------------------------------*/ + u4_idx++; + if(u1_tran_form8x8) + { + if(!u1_mb_field_decodingflag) + { + ps_dec->pu1_inv_scan = + (UWORD8*)gau1_ih264d_inv_scan_prog8x8_cavlc[3]; + } + else + { + ps_dec->pu1_inv_scan = + (UWORD8*)gau1_ih264d_inv_scan_int8x8_cavlc[3]; + } + } + else + { + pi2_coeff_block += NUM_COEFFS_IN_4x4BLK; + } + u4_n = (u4_top1 + u4_num_coeff + 1) >> 1; + ret = pf_cavlc_parse4x4coeff[(u4_n > 7)](pi2_coeff_block, u4_isdc, + u4_n, ps_dec, &u4_num_coeff); + if(ret != OK) + return ret; + + pu1_top_nnz[1] = pu1_left_nnz[1] = u4_num_coeff; + u4_subblock_coded = (u4_num_coeff != 0); + INSERT_BIT(*pu4_csbp, u4_idx, u4_subblock_coded); + + ps_dec->pu1_inv_scan = puc_temp; + + return OK; +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_cavlc_parse_8x8block_none_available */ +/* */ +/* Description : This function does the residual parsing of 4 subblocks */ +/* in a 8x8 block when none of the neigbours are available */ +/* */ +/* Inputs : pi2_coeff_block : pointer to residual block where */ +/* decoded and inverse scan coefficients are updated */ +/* */ +/* u4_sub_block_strd : indicates the number of sublocks */ +/* in a row. It is 4 for luma and 2 for chroma. */ +/* */ +/* u4_isdc : required to indicate 4x4 parse modules if the */ +/* current Mb is I_16x16/chroma DC coded. */ +/* */ +/* ps_dec : pointer to Decstruct (decoder context) */ +/* */ +/* pu1_top_nnz : top nnz pointer */ +/* */ +/* pu1_left_nnz : left nnz pointer */ +/* */ +/* Globals : No */ +/* Processing : Parsing for four subblocks in unrolled, top and left nnz */ +/* are updated on the fly. csbp is set in accordance to */ +/* decoded numcoeff for the subblock index in raster order */ +/* */ +/* Outputs : The updated residue buffer, nnzs and csbp current block */ +/* */ +/* Returns : Returns the coded sub block pattern csbp for the block */ +/* */ +/* Issues : <List any issues or problems with this function> */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 09 10 2008 Jay Draft */ +/* */ +/*****************************************************************************/ +WORD32 ih264d_cavlc_parse_8x8block_none_available(WORD16 *pi2_coeff_block, + UWORD32 u4_sub_block_strd, + UWORD32 u4_isdc, + dec_struct_t * ps_dec, + UWORD8 *pu1_top_nnz, + UWORD8 *pu1_left_nnz, + UWORD8 u1_tran_form8x8, + UWORD8 u1_mb_field_decodingflag, + UWORD32 *pu4_csbp) +{ + UWORD32 u4_num_coeff, u4_n, u4_subblock_coded; + UWORD32 u4_top0, u4_top1; + UWORD32 *pu4_dummy; + WORD32 (**pf_cavlc_parse4x4coeff)(WORD16 *pi2_coeff_block, + UWORD32 u4_isdc, + WORD32 u4_n, + struct _DecStruct *ps_dec, + UWORD32 *pu4_dummy) = + ps_dec->pf_cavlc_parse4x4coeff; + UWORD32 u4_idx = 0; + UWORD8 *puc_temp; + WORD32 ret; + + *pu4_csbp = 0; + puc_temp = ps_dec->pu1_inv_scan; + + /*------------------------------------------------------*/ + /* Residual 4x4 decoding: SubBlock 0 */ + /*------------------------------------------------------*/ + if(u1_tran_form8x8) + { + if(!u1_mb_field_decodingflag) + { + ps_dec->pu1_inv_scan = + (UWORD8*)gau1_ih264d_inv_scan_prog8x8_cavlc[0]; + } + else + { + ps_dec->pu1_inv_scan = + (UWORD8*)gau1_ih264d_inv_scan_int8x8_cavlc[0]; + } + } + ret = pf_cavlc_parse4x4coeff[0](pi2_coeff_block, u4_isdc, 0, + ps_dec, &u4_num_coeff); + if(ret != OK) + return ret; + + u4_top0 = u4_num_coeff; + u4_subblock_coded = (u4_num_coeff != 0); + INSERT_BIT(*pu4_csbp, u4_idx, u4_subblock_coded); + + /*------------------------------------------------------*/ + /* Residual 4x4 decoding: SubBlock 1 */ + /*------------------------------------------------------*/ + u4_idx++; + if(u1_tran_form8x8) + { + if(!u1_mb_field_decodingflag) + { + ps_dec->pu1_inv_scan = + (UWORD8*)gau1_ih264d_inv_scan_prog8x8_cavlc[1]; + } + else + { + ps_dec->pu1_inv_scan = + (UWORD8*)gau1_ih264d_inv_scan_int8x8_cavlc[1]; + } + } + else + { + pi2_coeff_block += NUM_COEFFS_IN_4x4BLK; + } + u4_n = u4_num_coeff; + ret = pf_cavlc_parse4x4coeff[(u4_n > 7)](pi2_coeff_block, u4_isdc, + u4_n, ps_dec, &u4_num_coeff); + if(ret != OK) + return ret; + + u4_top1 = pu1_left_nnz[0] = u4_num_coeff; + u4_subblock_coded = (u4_num_coeff != 0); + INSERT_BIT(*pu4_csbp, u4_idx, u4_subblock_coded); + + /*------------------------------------------------------*/ + /* Residual 4x4 decoding: SubBlock 2 */ + /*------------------------------------------------------*/ + u4_idx += (u4_sub_block_strd - 1); + if(u1_tran_form8x8) + { + if(!u1_mb_field_decodingflag) + { + ps_dec->pu1_inv_scan = + (UWORD8*)gau1_ih264d_inv_scan_prog8x8_cavlc[2]; + } + else + { + ps_dec->pu1_inv_scan = + (UWORD8*)gau1_ih264d_inv_scan_int8x8_cavlc[2]; + } + } + else + { + pi2_coeff_block += ((u4_sub_block_strd - 1) * NUM_COEFFS_IN_4x4BLK); + } + u4_n = u4_top0; + ret = pf_cavlc_parse4x4coeff[(u4_n > 7)](pi2_coeff_block, u4_isdc, + u4_n, ps_dec, &u4_num_coeff); + if(ret != OK) + return ret; + + pu1_top_nnz[0] = u4_num_coeff; + u4_subblock_coded = (u4_num_coeff != 0); + INSERT_BIT(*pu4_csbp, u4_idx, u4_subblock_coded); + + /*------------------------------------------------------*/ + /* Residual 4x4 decoding: SubBlock 3 */ + /*------------------------------------------------------*/ + u4_idx++; + if(u1_tran_form8x8) + { + if(!u1_mb_field_decodingflag) + { + ps_dec->pu1_inv_scan = + (UWORD8*)gau1_ih264d_inv_scan_prog8x8_cavlc[3]; + } + else + { + ps_dec->pu1_inv_scan = + (UWORD8*)gau1_ih264d_inv_scan_int8x8_cavlc[3]; + } + } + else + { + pi2_coeff_block += NUM_COEFFS_IN_4x4BLK; + } + u4_n = (u4_top1 + u4_num_coeff + 1) >> 1; + ret = pf_cavlc_parse4x4coeff[(u4_n > 7)](pi2_coeff_block, u4_isdc, + u4_n, ps_dec, &u4_num_coeff); + if(ret != OK) + return ret; + + pu1_top_nnz[1] = pu1_left_nnz[1] = u4_num_coeff; + u4_subblock_coded = (u4_num_coeff != 0); + INSERT_BIT(*pu4_csbp, u4_idx, u4_subblock_coded); + + ps_dec->pu1_inv_scan = puc_temp; + + return OK; +} + +/*! + ************************************************************************** + * \if Function name : ih264d_parse_residual4x4_cavlc \endif + * + * \brief + * This function parses CAVLC syntax of a Luma and Chroma AC Residuals. + * + * \return + * 0 on Success and Error code otherwise + ************************************************************************** + */ + +WORD32 ih264d_parse_residual4x4_cavlc(dec_struct_t * ps_dec, + dec_mb_info_t *ps_cur_mb_info, + UWORD8 u1_offset) +{ + UWORD8 u1_cbp = ps_cur_mb_info->u1_cbp; + UWORD16 ui16_csbp = 0; + UWORD32 u4_nbr_avl; + WORD16 *pi2_residual_buf; + + UWORD8 u1_is_top_mb_avail; + UWORD8 u1_is_left_mb_avail; + + UWORD8 *pu1_top_nnz = ps_cur_mb_info->ps_curmb->pu1_nnz_y; + UWORD8 *pu1_left_nnz = ps_dec->pu1_left_nnz_y; + WORD16 *pi2_coeff_block = NULL; + UWORD32 *pu4_dummy; + WORD32 ret; + + WORD32 (**pf_cavlc_parse_8x8block)(WORD16 *pi2_coeff_block, + UWORD32 u4_sub_block_strd, + UWORD32 u4_isdc, + struct _DecStruct *ps_dec, + UWORD8 *pu1_top_nnz, + UWORD8 *pu1_left_nnz, + UWORD8 u1_tran_form8x8, + UWORD8 u1_mb_field_decodingflag, + UWORD32 *pu4_dummy) = ps_dec->pf_cavlc_parse_8x8block; + + + { + UWORD8 uc_temp = ps_dec->u1_mb_ngbr_availablity; + u1_is_top_mb_avail = BOOLEAN(uc_temp & TOP_MB_AVAILABLE_MASK); + u1_is_left_mb_avail = BOOLEAN(uc_temp & LEFT_MB_AVAILABLE_MASK); + u4_nbr_avl = (u1_is_top_mb_avail << 1) | u1_is_left_mb_avail; + } + + ps_cur_mb_info->u1_qp_div6 = ps_dec->u1_qp_y_div6; + ps_cur_mb_info->u1_qp_rem6 = ps_dec->u1_qp_y_rem6; + ps_cur_mb_info->u1_qpc_div6 = ps_dec->u1_qp_u_div6; + ps_cur_mb_info->u1_qpc_rem6 = ps_dec->u1_qp_u_rem6; + ps_cur_mb_info->u1_qpcr_div6 = ps_dec->u1_qp_v_div6; + ps_cur_mb_info->u1_qpcr_rem6 = ps_dec->u1_qp_v_rem6; + + if(u1_cbp & 0xf) + { + pu1_top_nnz[0] = ps_cur_mb_info->ps_top_mb->pu1_nnz_y[0]; + pu1_top_nnz[1] = ps_cur_mb_info->ps_top_mb->pu1_nnz_y[1]; + pu1_top_nnz[2] = ps_cur_mb_info->ps_top_mb->pu1_nnz_y[2]; + pu1_top_nnz[3] = ps_cur_mb_info->ps_top_mb->pu1_nnz_y[3]; + + /*******************************************************************/ + /* Block 0 residual decoding, check cbp and proceed (subblock = 0) */ + /*******************************************************************/ + if(!(u1_cbp & 0x1)) + { + *(UWORD16 *)(pu1_top_nnz) = 0; + *(UWORD16 *)(pu1_left_nnz) = 0; + + } + else + { + UWORD32 u4_temp; + ret = pf_cavlc_parse_8x8block[u4_nbr_avl]( + pi2_coeff_block, 4, u1_offset, ps_dec, pu1_top_nnz, + pu1_left_nnz, ps_cur_mb_info->u1_tran_form8x8, + ps_cur_mb_info->u1_mb_field_decodingflag, &u4_temp); + if(ret != OK) + return ret; + ui16_csbp = u4_temp; + } + + /*******************************************************************/ + /* Block 1 residual decoding, check cbp and proceed (subblock = 2) */ + /*******************************************************************/ + if(ps_cur_mb_info->u1_tran_form8x8) + { + pi2_coeff_block += 64; + } + else + { + pi2_coeff_block += (2 * NUM_COEFFS_IN_4x4BLK); + } + + if(!(u1_cbp & 0x2)) + { + *(UWORD16 *)(pu1_top_nnz + 2) = 0; + *(UWORD16 *)(pu1_left_nnz) = 0; + } + else + { + UWORD32 u4_temp = (u4_nbr_avl | 0x1); + ret = pf_cavlc_parse_8x8block[u4_temp]( + pi2_coeff_block, 4, u1_offset, ps_dec, + (pu1_top_nnz + 2), pu1_left_nnz, + ps_cur_mb_info->u1_tran_form8x8, + ps_cur_mb_info->u1_mb_field_decodingflag, &u4_temp); + if(ret != OK) + return ret; + ui16_csbp |= (u4_temp << 2); + } + + /*******************************************************************/ + /* Block 2 residual decoding, check cbp and proceed (subblock = 8) */ + /*******************************************************************/ + if(ps_cur_mb_info->u1_tran_form8x8) + { + pi2_coeff_block += 64; + } + else + { + pi2_coeff_block += (6 * NUM_COEFFS_IN_4x4BLK); + } + + if(!(u1_cbp & 0x4)) + { + *(UWORD16 *)(pu1_top_nnz) = 0; + *(UWORD16 *)(pu1_left_nnz + 2) = 0; + } + else + { + UWORD32 u4_temp = (u4_nbr_avl | 0x2); + ret = pf_cavlc_parse_8x8block[u4_temp]( + pi2_coeff_block, 4, u1_offset, ps_dec, pu1_top_nnz, + (pu1_left_nnz + 2), ps_cur_mb_info->u1_tran_form8x8, + ps_cur_mb_info->u1_mb_field_decodingflag, &u4_temp); + if(ret != OK) + return ret; + ui16_csbp |= (u4_temp << 8); + } + + /*******************************************************************/ + /* Block 3 residual decoding, check cbp and proceed (subblock = 10)*/ + /*******************************************************************/ + if(ps_cur_mb_info->u1_tran_form8x8) + { + pi2_coeff_block += 64; + } + else + { + pi2_coeff_block += (2 * NUM_COEFFS_IN_4x4BLK); + } + + if(!(u1_cbp & 0x8)) + { + *(UWORD16 *)(pu1_top_nnz + 2) = 0; + *(UWORD16 *)(pu1_left_nnz + 2) = 0; + } + else + { + UWORD32 u4_temp; + ret = pf_cavlc_parse_8x8block[0x3]( + pi2_coeff_block, 4, u1_offset, ps_dec, + (pu1_top_nnz + 2), (pu1_left_nnz + 2), + ps_cur_mb_info->u1_tran_form8x8, + ps_cur_mb_info->u1_mb_field_decodingflag, &u4_temp); + if(ret != OK) + return ret; + ui16_csbp |= (u4_temp << 10); + } + } + else + { + *(UWORD32 *)(pu1_top_nnz) = 0; + *(UWORD32 *)(pu1_left_nnz) = 0; + } + + ps_cur_mb_info->u2_luma_csbp = ui16_csbp; + ps_cur_mb_info->ps_curmb->u2_luma_csbp = ui16_csbp; + + { + UWORD16 u2_chroma_csbp = 0; + ps_cur_mb_info->u2_chroma_csbp = 0; + pu1_top_nnz = ps_cur_mb_info->ps_curmb->pu1_nnz_uv; + pu1_left_nnz = ps_dec->pu1_left_nnz_uv; + + u1_cbp >>= 4; + /*--------------------------------------------------------------------*/ + /* if Chroma Component not present OR no ac values present */ + /* Set the values of N to zero */ + /*--------------------------------------------------------------------*/ + if(u1_cbp == CBPC_ALLZERO || u1_cbp == CBPC_ACZERO) + { + *(UWORD32 *)(pu1_top_nnz) = 0; + *(UWORD32 *)(pu1_left_nnz) = 0; + } + + if(u1_cbp == CBPC_ALLZERO) + { + return (0); + } + /*--------------------------------------------------------------------*/ + /* Decode Chroma DC values */ + /*--------------------------------------------------------------------*/ + { + WORD32 u4_scale_u; + WORD32 u4_scale_v; + WORD32 i4_mb_inter_inc; + u4_scale_u = ps_dec->pu2_quant_scale_u[0] << ps_dec->u1_qp_u_div6; + u4_scale_v = ps_dec->pu2_quant_scale_v[0] << ps_dec->u1_qp_v_div6; + i4_mb_inter_inc = (!((ps_cur_mb_info->ps_curmb->u1_mb_type == I_4x4_MB) + || (ps_cur_mb_info->ps_curmb->u1_mb_type == I_16x16_MB))) + * 3; + + if(ps_dec->s_high_profile.u1_scaling_present) + { + u4_scale_u *= + ps_dec->s_high_profile.i2_scalinglist4x4[i4_mb_inter_inc + + 1][0]; + u4_scale_v *= + ps_dec->s_high_profile.i2_scalinglist4x4[i4_mb_inter_inc + + 2][0]; + + } + else + { + u4_scale_u <<= 4; + u4_scale_v <<= 4; + } + + ih264d_cavlc_parse_chroma_dc(ps_cur_mb_info,pi2_coeff_block, ps_dec->ps_bitstrm, + u4_scale_u, u4_scale_v, + i4_mb_inter_inc); + } + + if(u1_cbp == CBPC_ACZERO) + return (0); + + pu1_top_nnz[0] = ps_cur_mb_info->ps_top_mb->pu1_nnz_uv[0]; + pu1_top_nnz[1] = ps_cur_mb_info->ps_top_mb->pu1_nnz_uv[1]; + pu1_top_nnz[2] = ps_cur_mb_info->ps_top_mb->pu1_nnz_uv[2]; + pu1_top_nnz[3] = ps_cur_mb_info->ps_top_mb->pu1_nnz_uv[3]; + /*--------------------------------------------------------------------*/ + /* Decode Chroma AC values */ + /*--------------------------------------------------------------------*/ + { + UWORD32 u4_temp; + /*****************************************************************/ + /* U Block residual decoding, check cbp and proceed (subblock=0)*/ + /*****************************************************************/ + ret = pf_cavlc_parse_8x8block[u4_nbr_avl]( + pi2_coeff_block, 2, 1, ps_dec, pu1_top_nnz, + pu1_left_nnz, 0, 0, &u4_temp); + if(ret != OK) + return ret; + u2_chroma_csbp = u4_temp; + + pi2_coeff_block += MB_CHROM_SIZE; + /*****************************************************************/ + /* V Block residual decoding, check cbp and proceed (subblock=1)*/ + /*****************************************************************/ + ret = pf_cavlc_parse_8x8block[u4_nbr_avl](pi2_coeff_block, 2, 1, + ps_dec, + (pu1_top_nnz + 2), + (pu1_left_nnz + 2), 0, + 0, &u4_temp); + if(ret != OK) + return ret; + u2_chroma_csbp |= (u4_temp << 4); + } + + ps_cur_mb_info->u2_chroma_csbp = u2_chroma_csbp; + } + return OK; +} diff --git a/dependencies/ih264d/decoder/ih264d_parse_cavlc.h b/dependencies/ih264d/decoder/ih264d_parse_cavlc.h new file mode 100644 index 00000000..06105a3f --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_parse_cavlc.h @@ -0,0 +1,165 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +#ifndef _IH264D_PARSE_CAVLC_H_ +#define _IH264D_PARSE_CAVLC_H_ +/*! + ************************************************************************** + * \file ih264d_parse_cavlc.h + * + * \brief + * Declaration of UVLC and CAVLC functions + * + * \date + * 18/12/2002 + * + * \author AI + ************************************************************************** + */ +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264d_bitstrm.h" +#include "ih264d_structs.h" +#include "ih264d_cabac.h" + +enum cavlcTableNum +{ + tableTotalZeroOffset, + tableTotalZero, + tableRunBefore, + codeGx, + chromTab, + offsetNumVlcTab +}; + +WORD32 ih264d_uvlc(dec_bit_stream_t *ps_bitstrm, + UWORD32 u4_range, + UWORD32 *pi_bitstrm_ofst, + UWORD8 u1_flag, + UWORD32 u4_bitstrm_ofst, + UWORD32 *pi_bitstrm_buf); + +UWORD32 ih264d_uev(UWORD32 *pu4_bitstrm_ofst, UWORD32 *pu4_bitstrm_buf); + +WORD32 ih264d_sev(UWORD32 *pu4_bitstrm_ofst, UWORD32 *pu4_bitstrm_buf); + +UWORD32 ih264d_tev_range1(UWORD32 *pu4_bitstrm_ofst, + UWORD32 *pu4_bitstrm_buf); + +UWORD8 RestOfResidualBlockCavlc(WORD16 *pi2_coeff_block, + UWORD32 u1_ofst_is_dc_max_coef_scale_fact, + UWORD32 u4_total_coeff_trail_one, + dec_bit_stream_t *ps_bitstrm, + UWORD8 *pu1_invscan); + +WORD32 ih264d_cavlc_4x4res_block_totalcoeff_1( UWORD32 u4_isdc, + UWORD32 u4_total_coeff_trail_one, + dec_bit_stream_t *ps_bitstrm); + +WORD32 ih264d_cavlc_4x4res_block_totalcoeff_2to10(UWORD32 u4_isdc, + UWORD32 u4_total_coeff_trail_one, + dec_bit_stream_t *ps_bitstrm); + +WORD32 ih264d_cavlc_4x4res_block_totalcoeff_11to16(UWORD32 u4_isdc, + UWORD32 u4_total_coeff_trail_one, + dec_bit_stream_t *ps_bitstrm); + +WORD32 ih264d_cavlc_parse4x4coeff_n0to7(WORD16 *pi2_coeff_block, + UWORD32 u4_isdc, + WORD32 u4_n, + dec_struct_t *ps_dec, + UWORD32 *pu4_total_coeff); + +WORD32 ih264d_cavlc_parse4x4coeff_n8(WORD16 *pi2_coeff_block, + UWORD32 u4_isdc, + WORD32 u4_n, + dec_struct_t *ps_dec, + UWORD32 *pu4_total_coeff); + +void ih264d_cavlc_parse_chroma_dc(dec_mb_info_t *ps_cur_mb_info, + WORD16 *pi2_coeff_block, + dec_bit_stream_t *ps_bitstrm, + UWORD32 u4_scale_u, + UWORD32 u4_scale_v, + WORD32 i4_mb_inter_inc); + +WORD32 ih264d_cavlc_parse_8x8block_none_available(WORD16 *pi2_coeff_block, + UWORD32 u4_sub_block_strd, + UWORD32 u4_isdc, + dec_struct_t * ps_dec, + UWORD8 *pu1_top_nnz, + UWORD8 *pu1_left_nnz, + UWORD8 u1_tran_form8x8, + UWORD8 u1_mb_field_decodingflag, + UWORD32 *pu4_csbp); + +WORD32 ih264d_cavlc_parse_8x8block_left_available(WORD16 *pi2_coeff_block, + UWORD32 u4_sub_block_strd, + UWORD32 u4_isdc, + dec_struct_t * ps_dec, + UWORD8 *pu1_top_nnz, + UWORD8 *pu1_left_nnz, + UWORD8 u1_tran_form8x8, + UWORD8 u1_mb_field_decodingflag, + UWORD32 *pu4_csbp); + +WORD32 ih264d_cavlc_parse_8x8block_top_available(WORD16 *pi2_coeff_block, + UWORD32 u4_sub_block_strd, + UWORD32 u4_isdc, + dec_struct_t * ps_dec, + UWORD8 *pu1_top_nnz, + UWORD8 *pu1_left_nnz, + UWORD8 u1_tran_form8x8, + UWORD8 u1_mb_field_decodingflag, + UWORD32 *pu4_csbp); + +WORD32 ih264d_cavlc_parse_8x8block_both_available(WORD16 *pi2_coeff_block, + UWORD32 u4_sub_block_strd, + UWORD32 u4_isdc, + dec_struct_t * ps_dec, + UWORD8 *pu1_top_nnz, + UWORD8 *pu1_left_nnz, + UWORD8 u1_tran_form8x8, + UWORD8 u1_mb_field_decodingflag, + UWORD32 *pu4_csbp); + +WORD8 ResidualBlockChromaDC(WORD16 *pi2_level, dec_bit_stream_t *ps_bitstrm); + +void ih264d_parse_pmb_ref_index_cavlc_range1(UWORD32 u4_num_part, + dec_bit_stream_t *ps_bitstrm, + WORD8 *pi1_ref_idx, + UWORD32 u4_num_ref_idx_active_minus1); + +WORD32 ih264d_parse_pmb_ref_index_cavlc(UWORD32 u4_num_part, + dec_bit_stream_t *ps_bitstrm, + WORD8 *pi1_ref_idx, + UWORD32 u4_num_ref_idx_active_minus1); + +void ih264d_parse_bmb_ref_index_cavlc_range1(UWORD32 u4_num_part, + dec_bit_stream_t *ps_bitstrm, + WORD8 *pi1_ref_idx, + UWORD32 u4_num_ref_idx_active_minus1); + +WORD32 ih264d_parse_bmb_ref_index_cavlc(UWORD32 u4_num_part, + dec_bit_stream_t *ps_bitstrm, + WORD8 *pi1_ref_idx, + UWORD32 u4_num_ref_idx_active_minus1); + +#endif /* _IH264D_PARSE_CAVLC_H_ */ diff --git a/dependencies/ih264d/decoder/ih264d_parse_headers.c b/dependencies/ih264d/decoder/ih264d_parse_headers.c new file mode 100644 index 00000000..5dca4066 --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_parse_headers.c @@ -0,0 +1,1391 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore + * Modified for use with Cemu emulator project + */ +/*! + ************************************************************************** + * \file ih264d_parse_headers.c + * + * \brief + * Contains High level syntax[above slice] parsing routines + * + * \date + * 19/12/2002 + * + * \author AI + ************************************************************************** + */ +#include <string.h> + +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264_defs.h" +#include "ih264d_bitstrm.h" +#include "ih264d_structs.h" +#include "ih264d_parse_cavlc.h" +#include "ih264d_defs.h" +#include "ih264d_defs.h" +#include "ih264d_defs.h" +#include "ih264d_parse_slice.h" +#include "ih264d_tables.h" +#include "ih264d_utils.h" +#include "ih264d_nal.h" +#include "ih264d_deblocking.h" + +#include "ih264d_mem_request.h" +#include "ih264d_debug.h" +#include "ih264d_error_handler.h" +#include "ih264d_mb_utils.h" +#include "ih264d_sei.h" +#include "ih264d_vui.h" +#include "ih264d_thread_parse_decode.h" +#include "ih264d_thread_compute_bs.h" +#include "ih264d_quant_scaling.h" +#include "ih264d_defs.h" +#include "ivd.h" +#include "ih264d.h" + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_get_pre_sei_params */ +/* */ +/* Description : Gets valid pre-sei params in decoder struct from parse */ +/* struct. */ +/* Inputs : u1_nal_unit_type slice type */ +/* ps_dec Decoder parameters */ +/* Globals : None */ +/* Outputs : None */ +/* Returns : None */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* Draft */ +/* */ +/*****************************************************************************/ + +void ih264d_get_pre_sei_params(dec_struct_t *ps_dec, UWORD8 u1_nal_unit_type) +{ + if((NULL != ps_dec->ps_sei) && + ((0 == ps_dec->ps_sei->s_sei_ccv_params.u1_ccv_cancel_flag) && + (0 == ps_dec->ps_sei->s_sei_ccv_params.u1_ccv_persistence_flag))) + { + ps_dec->ps_sei->u1_sei_ccv_params_present_flag = 0; + memset(&ps_dec->ps_sei->s_sei_ccv_params, 0, sizeof(sei_ccv_params_t)); + } + + if((NULL != ps_dec->ps_cur_sps) && + ((1 == ps_dec->ps_cur_sps->u1_vui_parameters_present_flag) && + ((2 != ps_dec->ps_cur_sps->s_vui.u1_colour_primaries) && + (2 != ps_dec->ps_cur_sps->s_vui.u1_matrix_coeffs) && + (2 != ps_dec->ps_cur_sps->s_vui.u1_tfr_chars) && + (4 != ps_dec->ps_cur_sps->s_vui.u1_tfr_chars) && + (5 != ps_dec->ps_cur_sps->s_vui.u1_tfr_chars)))) + { + if((1 == ps_dec->ps_sei_parse->u1_sei_ccv_params_present_flag) || + (IDR_SLICE_NAL == u1_nal_unit_type)) + { + ps_dec->ps_sei->u1_sei_ccv_params_present_flag = + ps_dec->ps_sei_parse->u1_sei_ccv_params_present_flag; + ps_dec->ps_sei->s_sei_ccv_params = ps_dec->ps_sei_parse->s_sei_ccv_params; + } + } + else + { + ps_dec->ps_sei->u1_sei_ccv_params_present_flag = 0; + memset(&ps_dec->ps_sei->s_sei_ccv_params, 0, sizeof(sei_ccv_params_t)); + } + + if(IDR_SLICE_NAL == u1_nal_unit_type) + { + ps_dec->ps_sei->u1_sei_mdcv_params_present_flag = + ps_dec->ps_sei_parse->u1_sei_mdcv_params_present_flag; + ps_dec->ps_sei->s_sei_mdcv_params = ps_dec->ps_sei_parse->s_sei_mdcv_params; + ps_dec->ps_sei->u1_sei_cll_params_present_flag = + ps_dec->ps_sei_parse->u1_sei_cll_params_present_flag; + ps_dec->ps_sei->s_sei_cll_params = ps_dec->ps_sei_parse->s_sei_cll_params; + ps_dec->ps_sei->u1_sei_ave_params_present_flag = + ps_dec->ps_sei_parse->u1_sei_ave_params_present_flag; + ps_dec->ps_sei->s_sei_ave_params = ps_dec->ps_sei_parse->s_sei_ave_params; + } + + ps_dec->ps_sei_parse->u1_sei_mdcv_params_present_flag = 0; + memset(&ps_dec->ps_sei_parse->s_sei_mdcv_params, 0, sizeof(sei_mdcv_params_t)); + ps_dec->ps_sei_parse->u1_sei_cll_params_present_flag = 0; + memset(&ps_dec->ps_sei_parse->s_sei_cll_params, 0, sizeof(sei_cll_params_t)); + ps_dec->ps_sei_parse->u1_sei_ave_params_present_flag = 0; + memset(&ps_dec->ps_sei_parse->s_sei_ave_params, 0, sizeof(sei_ave_params_t)); + ps_dec->ps_sei_parse->u1_sei_ccv_params_present_flag = 0; + memset(&ps_dec->ps_sei_parse->s_sei_ccv_params, 0, sizeof(sei_ccv_params_t)); + +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_parse_slice_partition */ +/* */ +/* Description : This function is intended to parse and decode slice part */ +/* itions. Currently it's not implemented. Decoder will */ +/* print a message, skips this NAL and continues */ +/* Inputs : ps_dec Decoder parameters */ +/* ps_bitstrm Bitstream */ +/* Globals : None */ +/* Processing : This functionality needs to be implemented */ +/* Outputs : None */ +/* Returns : None */ +/* */ +/* Issues : Not implemented */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 06 05 2002 NS Draft */ +/* */ +/*****************************************************************************/ + +WORD32 ih264d_parse_slice_partition(dec_struct_t * ps_dec, + dec_bit_stream_t * ps_bitstrm) +{ + H264_DEC_DEBUG_PRINT("\nSlice partition not supported"); + UNUSED(ps_dec); + UNUSED(ps_bitstrm); + return (0); +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_parse_sei */ +/* */ +/* Description : This function is intended to parse and decode SEI */ +/* Currently it's not implemented. Decoder will print a */ +/* message, skips this NAL and continues */ +/* Inputs : ps_dec Decoder parameters */ +/* ps_bitstrm Bitstream */ +/* Globals : None */ +/* Processing : This functionality needs to be implemented */ +/* Outputs : None */ +/* Returns : None */ +/* */ +/* Issues : Not implemented */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 06 05 2002 NS Draft */ +/* */ +/*****************************************************************************/ +WORD32 ih264d_parse_sei(dec_struct_t * ps_dec, dec_bit_stream_t * ps_bitstrm) +{ + UNUSED(ps_dec); + UNUSED(ps_bitstrm); + return (0); +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_parse_filler_data */ +/* */ +/* Description : This function is intended to parse and decode filler */ +/* data NAL. Currently it's not implemented. Decoder will */ +/* print a message, skips this NAL and continues */ +/* Inputs : ps_dec Decoder parameters */ +/* ps_bitstrm Bitstream */ +/* Globals : None */ +/* Processing : This functionality needs to be implemented */ +/* Outputs : None */ +/* Returns : None */ +/* */ +/* Issues : Not implemented */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 06 05 2002 NS Draft */ +/* */ +/*****************************************************************************/ +WORD32 ih264d_parse_filler_data(dec_struct_t * ps_dec, + dec_bit_stream_t * ps_bitstrm) +{ + UNUSED(ps_dec); + UNUSED(ps_bitstrm); + return (0); +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_parse_end_of_stream */ +/* */ +/* Description : This function is intended to parse and decode end of */ +/* sequence. Currently it's not implemented. Decoder will */ +/* print a message, skips this NAL and continues */ +/* Inputs : ps_dec Decoder parameters */ +/* Globals : None */ +/* Processing : This functionality needs to be implemented */ +/* Outputs : None */ +/* Returns : None */ +/* */ +/* Issues : Not implemented */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 06 05 2002 NS Draft */ +/* */ +/*****************************************************************************/ +void ih264d_parse_end_of_stream(dec_struct_t * ps_dec) +{ + UNUSED(ps_dec); + return; +} + +/*! + ************************************************************************** + * \if Function name : ih264d_parse_pps \endif + * + * \brief + * Decodes Picture Parameter set + * + * \return + * 0 on Success and Error code otherwise + ************************************************************************** + */ +WORD32 ih264d_parse_pps(dec_struct_t * ps_dec, dec_bit_stream_t * ps_bitstrm) +{ + UWORD8 uc_temp; + dec_seq_params_t * ps_sps = NULL; + dec_pic_params_t * ps_pps = NULL; + UWORD32 *pu4_bitstrm_buf = ps_dec->ps_bitstrm->pu4_buffer; + UWORD32 *pu4_bitstrm_ofst = &ps_dec->ps_bitstrm->u4_ofst; + + /* Variables used for error resilience checks */ + UWORD64 u8_temp; + UWORD32 u4_temp; + WORD32 i_temp; + + /* For High profile related syntax elements */ + UWORD8 u1_more_data_flag; + WORD32 i4_i; + + if(!(ps_dec->i4_header_decoded & 1)) + return ERROR_INV_SPS_PPS_T; + + /*--------------------------------------------------------------------*/ + /* Decode pic_parameter_set_id and find corresponding pic params */ + /*--------------------------------------------------------------------*/ + u4_temp = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); + if(u4_temp & MASK_ERR_PIC_SET_ID) + return ERROR_INV_SPS_PPS_T; + ps_pps = ps_dec->pv_scratch_sps_pps; + *ps_pps = ps_dec->ps_pps[u4_temp]; + ps_pps->u1_pic_parameter_set_id = (WORD8)u4_temp; + COPYTHECONTEXT("PPS: pic_parameter_set_id",ps_pps->u1_pic_parameter_set_id); + + /************************************************/ + /* initilization of High profile syntax element */ + /************************************************/ + ps_pps->i4_transform_8x8_mode_flag = 0; + ps_pps->i4_pic_scaling_matrix_present_flag = 0; + + /*--------------------------------------------------------------------*/ + /* Decode seq_parameter_set_id and map it to a seq_parameter_set */ + /*--------------------------------------------------------------------*/ + u4_temp = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); + if(u4_temp & MASK_ERR_SEQ_SET_ID) + return ERROR_INV_SPS_PPS_T; + COPYTHECONTEXT("PPS: seq_parameter_set_id",u4_temp); + ps_sps = &ps_dec->ps_sps[u4_temp]; + + if(FALSE == ps_sps->u1_is_valid) + return ERROR_INV_SPS_PPS_T; + ps_pps->ps_sps = ps_sps; + + /*--------------------------------------------------------------------*/ + /* Decode entropy_coding_mode */ + /*--------------------------------------------------------------------*/ + ps_pps->u1_entropy_coding_mode = ih264d_get_bit_h264(ps_bitstrm); + COPYTHECONTEXT("PPS: entropy_coding_mode_flag",ps_pps->u1_entropy_coding_mode); + + ps_pps->u1_pic_order_present_flag = ih264d_get_bit_h264(ps_bitstrm); + COPYTHECONTEXT("PPS: pic_order_present_flag",ps_pps->u1_pic_order_present_flag); + + /*--------------------------------------------------------------------*/ + /* Decode num_slice_groups_minus1 */ + /*--------------------------------------------------------------------*/ + u8_temp = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf) + (UWORD64)1; + if(u8_temp != 1) + { + return ERROR_FEATURE_UNAVAIL; + } + ps_pps->u1_num_slice_groups = u8_temp; + COPYTHECONTEXT("PPS: num_slice_groups_minus1",ps_pps->u1_num_slice_groups -1); + + /*--------------------------------------------------------------------*/ + /* Other parameter set values */ + /*--------------------------------------------------------------------*/ + u8_temp = (UWORD64)1 + ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); + if(u8_temp > H264_MAX_REF_IDX) + return ERROR_REF_IDX; + ps_pps->u1_num_ref_idx_lx_active[0] = u8_temp; + COPYTHECONTEXT("PPS: num_ref_idx_l0_active_minus1", + ps_pps->u1_num_ref_idx_lx_active[0] - 1); + + u8_temp = (UWORD64)1 + ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); + if(u8_temp > H264_MAX_REF_IDX) + return ERROR_REF_IDX; + ps_pps->u1_num_ref_idx_lx_active[1] = u8_temp; + COPYTHECONTEXT("PPS: num_ref_idx_l1_active_minus1", + ps_pps->u1_num_ref_idx_lx_active[1] - 1); + + ps_pps->u1_wted_pred_flag = ih264d_get_bit_h264(ps_bitstrm); + COPYTHECONTEXT("PPS: weighted prediction u4_flag",ps_pps->u1_wted_pred_flag); + uc_temp = ih264d_get_bits_h264(ps_bitstrm, 2); + COPYTHECONTEXT("PPS: weighted_bipred_idc",uc_temp); + ps_pps->u1_wted_bipred_idc = uc_temp; + + if(ps_pps->u1_wted_bipred_idc > MAX_WEIGHT_BIPRED_IDC) + return ERROR_INV_SPS_PPS_T; + + WORD64 i8_temp = (WORD64)26 + + ih264d_sev(pu4_bitstrm_ofst, pu4_bitstrm_buf); + + if((i8_temp < MIN_H264_QP) || (i8_temp > MAX_H264_QP)) + return ERROR_INV_RANGE_QP_T; + + ps_pps->u1_pic_init_qp = i8_temp; + COPYTHECONTEXT("PPS: pic_init_qp_minus26",ps_pps->u1_pic_init_qp - 26); + + i8_temp = (WORD64)26 + ih264d_sev(pu4_bitstrm_ofst, pu4_bitstrm_buf); + + if((i8_temp < MIN_H264_QP) || (i8_temp > MAX_H264_QP)) + return ERROR_INV_RANGE_QP_T; + + ps_pps->u1_pic_init_qs = i8_temp; + COPYTHECONTEXT("PPS: pic_init_qs_minus26",ps_pps->u1_pic_init_qs - 26); + + i_temp = ih264d_sev(pu4_bitstrm_ofst, pu4_bitstrm_buf); + if((i_temp < -12) || (i_temp > 12)) + return ERROR_INV_RANGE_QP_T; + ps_pps->i1_chroma_qp_index_offset = i_temp; + COPYTHECONTEXT("PPS: chroma_qp_index_offset",ps_pps->i1_chroma_qp_index_offset); + + /***************************************************************************/ + /* initialize second_chroma_qp_index_offset to i1_chroma_qp_index_offset if */ + /* second_chroma_qp_index_offset is not present in bit-ps_bitstrm */ + /***************************************************************************/ + ps_pps->i1_second_chroma_qp_index_offset = + ps_pps->i1_chroma_qp_index_offset; + + ps_pps->u1_deblocking_filter_parameters_present_flag = ih264d_get_bit_h264( + ps_bitstrm); + COPYTHECONTEXT("PPS: deblocking_filter_control_present_flag", + ps_pps->u1_deblocking_filter_parameters_present_flag); + ps_pps->u1_constrained_intra_pred_flag = ih264d_get_bit_h264(ps_bitstrm); + COPYTHECONTEXT("PPS: constrained_intra_pred_flag", + ps_pps->u1_constrained_intra_pred_flag); + ps_pps->u1_redundant_pic_cnt_present_flag = ih264d_get_bit_h264(ps_bitstrm); + COPYTHECONTEXT("PPS: redundant_pic_cnt_present_flag", + ps_pps->u1_redundant_pic_cnt_present_flag); + + /* High profile related syntax elements */ + u1_more_data_flag = MORE_RBSP_DATA(ps_bitstrm); + if(u1_more_data_flag && (ps_pps->ps_sps->u1_profile_idc == HIGH_PROFILE_IDC)) + { + /* read transform_8x8_mode_flag */ + ps_pps->i4_transform_8x8_mode_flag = (WORD32)ih264d_get_bit_h264( + ps_bitstrm); + + /* read pic_scaling_matrix_present_flag */ + ps_pps->i4_pic_scaling_matrix_present_flag = + (WORD32)ih264d_get_bit_h264(ps_bitstrm); + + if(ps_pps->i4_pic_scaling_matrix_present_flag) + { + /* read the scaling matrices */ + for(i4_i = 0; i4_i < (6 + (ps_pps->i4_transform_8x8_mode_flag << 1)); i4_i++) + { + ps_pps->u1_pic_scaling_list_present_flag[i4_i] = + ih264d_get_bit_h264(ps_bitstrm); + + if(ps_pps->u1_pic_scaling_list_present_flag[i4_i]) + { + WORD32 ret; + if(i4_i < 6) + { + ret = ih264d_scaling_list( + ps_pps->i2_pic_scalinglist4x4[i4_i], + 16, + &ps_pps->u1_pic_use_default_scaling_matrix_flag[i4_i], + ps_bitstrm); + } + else + { + ret = ih264d_scaling_list( + ps_pps->i2_pic_scalinglist8x8[i4_i - 6], + 64, + &ps_pps->u1_pic_use_default_scaling_matrix_flag[i4_i], + ps_bitstrm); + } + + if(ret != OK) + { + return ret; + } + } + } + } + + /* read second_chroma_qp_index_offset syntax element */ + i_temp = ih264d_sev( + pu4_bitstrm_ofst, pu4_bitstrm_buf); + + if((i_temp < -12) || (i_temp > 12)) + return ERROR_INV_RANGE_QP_T; + + ps_pps->i1_second_chroma_qp_index_offset = i_temp; + } + + /* In case bitstream read has exceeded the filled size, then + return an error */ + if(EXCEED_OFFSET(ps_bitstrm)) + { + return ERROR_INV_SPS_PPS_T; + } + ps_pps->u1_is_valid = TRUE; + ps_dec->ps_pps[ps_pps->u1_pic_parameter_set_id] = *ps_pps; + return OK; +} + +/*! + ************************************************************************** + * \if Function name : ih264d_parse_sps \endif + * + * \brief + * Decodes Sequence parameter set from the bitstream + * + * \return + * 0 on Success and Error code otherwise + ************************************************************************** + */ +UWORD32 ih264d_correct_level_idc(UWORD32 u4_level_idc, UWORD32 u4_total_mbs) +{ + UWORD32 u4_max_mbs_allowed; + + switch(u4_level_idc) + { + case H264_LEVEL_1_0: + u4_max_mbs_allowed = MAX_MBS_LEVEL_10; + break; + case H264_LEVEL_1_1: + u4_max_mbs_allowed = MAX_MBS_LEVEL_11; + break; + case H264_LEVEL_1_2: + u4_max_mbs_allowed = MAX_MBS_LEVEL_12; + break; + case H264_LEVEL_1_3: + u4_max_mbs_allowed = MAX_MBS_LEVEL_13; + break; + case H264_LEVEL_2_0: + u4_max_mbs_allowed = MAX_MBS_LEVEL_20; + break; + case H264_LEVEL_2_1: + u4_max_mbs_allowed = MAX_MBS_LEVEL_21; + break; + case H264_LEVEL_2_2: + u4_max_mbs_allowed = MAX_MBS_LEVEL_22; + break; + case H264_LEVEL_3_0: + u4_max_mbs_allowed = MAX_MBS_LEVEL_30; + break; + case H264_LEVEL_3_1: + u4_max_mbs_allowed = MAX_MBS_LEVEL_31; + break; + case H264_LEVEL_3_2: + u4_max_mbs_allowed = MAX_MBS_LEVEL_32; + break; + case H264_LEVEL_4_0: + u4_max_mbs_allowed = MAX_MBS_LEVEL_40; + break; + case H264_LEVEL_4_1: + u4_max_mbs_allowed = MAX_MBS_LEVEL_41; + break; + case H264_LEVEL_4_2: + u4_max_mbs_allowed = MAX_MBS_LEVEL_42; + break; + case H264_LEVEL_5_0: + u4_max_mbs_allowed = MAX_MBS_LEVEL_50; + break; + case H264_LEVEL_5_1: + default: + u4_max_mbs_allowed = MAX_MBS_LEVEL_51; + break; + + } + + /*correct of the level is incorrect*/ + if(u4_total_mbs > u4_max_mbs_allowed) + { + if(u4_total_mbs > MAX_MBS_LEVEL_50) + u4_level_idc = H264_LEVEL_5_1; + else if(u4_total_mbs > MAX_MBS_LEVEL_42) + u4_level_idc = H264_LEVEL_5_0; + else if(u4_total_mbs > MAX_MBS_LEVEL_41) + u4_level_idc = H264_LEVEL_4_2; + else if(u4_total_mbs > MAX_MBS_LEVEL_40) + u4_level_idc = H264_LEVEL_4_1; + else if(u4_total_mbs > MAX_MBS_LEVEL_32) + u4_level_idc = H264_LEVEL_4_0; + else if(u4_total_mbs > MAX_MBS_LEVEL_31) + u4_level_idc = H264_LEVEL_3_2; + else if(u4_total_mbs > MAX_MBS_LEVEL_30) + u4_level_idc = H264_LEVEL_3_1; + else if(u4_total_mbs > MAX_MBS_LEVEL_21) + u4_level_idc = H264_LEVEL_3_0; + else if(u4_total_mbs > MAX_MBS_LEVEL_20) + u4_level_idc = H264_LEVEL_2_1; + else if(u4_total_mbs > MAX_MBS_LEVEL_10) + u4_level_idc = H264_LEVEL_2_0; + } + + return (u4_level_idc); + +} +WORD32 ih264d_parse_sps(dec_struct_t *ps_dec, dec_bit_stream_t *ps_bitstrm) +{ + UWORD8 i; + dec_seq_params_t *ps_seq = NULL; + UWORD8 u1_profile_idc, u1_level_idc, u1_seq_parameter_set_id, u1_mb_aff_flag = 0; + UWORD16 i2_max_frm_num; + UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; + UWORD32 *pu4_bitstrm_ofst = &ps_bitstrm->u4_ofst; + UWORD8 u1_frm, uc_constraint_set0_flag, uc_constraint_set1_flag; + WORD32 i4_cropped_ht, i4_cropped_wd; + UWORD32 u4_temp; + UWORD64 u8_temp; + UWORD32 u4_pic_height_in_map_units, u4_pic_width_in_mbs; + UWORD32 u2_pic_wd = 0; + UWORD32 u2_pic_ht = 0; + UWORD32 u2_frm_wd_y = 0; + UWORD32 u2_frm_ht_y = 0; + UWORD32 u2_frm_wd_uv = 0; + UWORD32 u2_frm_ht_uv = 0; + UWORD32 u2_crop_offset_y = 0; + UWORD32 u2_crop_offset_uv = 0; + WORD32 ret; + WORD32 num_reorder_frames; + /* High profile related syntax element */ + WORD32 i4_i; + /* G050 */ + UWORD8 u1_frame_cropping_flag, u1_frame_cropping_rect_left_ofst, + u1_frame_cropping_rect_right_ofst, + u1_frame_cropping_rect_top_ofst, + u1_frame_cropping_rect_bottom_ofst; + /* G050 */ + /*--------------------------------------------------------------------*/ + /* Decode seq_parameter_set_id and profile and level values */ + /*--------------------------------------------------------------------*/ + SWITCHONTRACE; + u1_profile_idc = ih264d_get_bits_h264(ps_bitstrm, 8); + COPYTHECONTEXT("SPS: profile_idc",u1_profile_idc); + + /* G050 */ + uc_constraint_set0_flag = ih264d_get_bit_h264(ps_bitstrm); + uc_constraint_set1_flag = ih264d_get_bit_h264(ps_bitstrm); + ih264d_get_bit_h264(ps_bitstrm); + + /*****************************************************/ + /* Read 5 bits for uc_constraint_set3_flag (1 bit) */ + /* and reserved_zero_4bits (4 bits) - Sushant */ + /*****************************************************/ + ih264d_get_bits_h264(ps_bitstrm, 5); + /* G050 */ + + /* Check whether particular profile is suported or not */ + /* Check whether particular profile is suported or not */ + if((u1_profile_idc != MAIN_PROFILE_IDC) && + + (u1_profile_idc != BASE_PROFILE_IDC) && + + (u1_profile_idc != HIGH_PROFILE_IDC) + + ) + { + + /* Apart from Baseline, main and high profile, + * only extended profile is supported provided + * uc_constraint_set0_flag or uc_constraint_set1_flag are set to 1 + */ + if((u1_profile_idc != EXTENDED_PROFILE_IDC) || + ((uc_constraint_set1_flag != 1) && (uc_constraint_set0_flag != 1))) + { + return (ERROR_FEATURE_UNAVAIL); + } + } + + u1_level_idc = ih264d_get_bits_h264(ps_bitstrm, 8); + + + + COPYTHECONTEXT("SPS: u4_level_idc",u1_level_idc); + + u4_temp = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); + if(u4_temp & MASK_ERR_SEQ_SET_ID) + return ERROR_INV_SPS_PPS_T; + u1_seq_parameter_set_id = u4_temp; + COPYTHECONTEXT("SPS: seq_parameter_set_id", + u1_seq_parameter_set_id); + + /*--------------------------------------------------------------------*/ + /* Find an seq param entry in seqparam array of decStruct */ + /*--------------------------------------------------------------------*/ + + ps_seq = ps_dec->pv_scratch_sps_pps; + memset(ps_seq, 0, sizeof(dec_seq_params_t)); + + if(ps_dec->i4_header_decoded & 1) + { + *ps_seq = *ps_dec->ps_cur_sps; + } + + + if((ps_dec->i4_header_decoded & 1) && (ps_seq->u1_profile_idc != u1_profile_idc)) + { + ps_dec->u1_res_changed = 1; + return IVD_RES_CHANGED; + } + + if((ps_dec->i4_header_decoded & 1) && (ps_seq->u1_level_idc != u1_level_idc)) + { + ps_dec->u1_res_changed = 1; + return IVD_RES_CHANGED; + } + + ps_seq->u1_profile_idc = u1_profile_idc; + ps_seq->u1_level_idc = u1_level_idc; + ps_seq->u1_seq_parameter_set_id = u1_seq_parameter_set_id; + + /*******************************************************************/ + /* Initializations for high profile - Sushant */ + /*******************************************************************/ + ps_seq->i4_chroma_format_idc = 1; + ps_seq->i4_bit_depth_luma_minus8 = 0; + ps_seq->i4_bit_depth_chroma_minus8 = 0; + ps_seq->i4_qpprime_y_zero_transform_bypass_flag = 0; + ps_seq->i4_seq_scaling_matrix_present_flag = 0; + if(u1_profile_idc == HIGH_PROFILE_IDC) + { + + /* reading chroma_format_idc */ + ps_seq->i4_chroma_format_idc = ih264d_uev(pu4_bitstrm_ofst, + pu4_bitstrm_buf); + + /* Monochrome is not supported */ + if(ps_seq->i4_chroma_format_idc != 1) + { + return ERROR_FEATURE_UNAVAIL; + } + + /* reading bit_depth_luma_minus8 */ + ps_seq->i4_bit_depth_luma_minus8 = ih264d_uev(pu4_bitstrm_ofst, + pu4_bitstrm_buf); + + if(ps_seq->i4_bit_depth_luma_minus8 != 0) + { + return ERROR_FEATURE_UNAVAIL; + } + + /* reading bit_depth_chroma_minus8 */ + ps_seq->i4_bit_depth_chroma_minus8 = ih264d_uev(pu4_bitstrm_ofst, + pu4_bitstrm_buf); + + if(ps_seq->i4_bit_depth_chroma_minus8 != 0) + { + return ERROR_FEATURE_UNAVAIL; + } + + /* reading qpprime_y_zero_transform_bypass_flag */ + ps_seq->i4_qpprime_y_zero_transform_bypass_flag = + (WORD32)ih264d_get_bit_h264(ps_bitstrm); + + if(ps_seq->i4_qpprime_y_zero_transform_bypass_flag != 0) + { + return ERROR_INV_SPS_PPS_T; + } + + /* reading seq_scaling_matrix_present_flag */ + ps_seq->i4_seq_scaling_matrix_present_flag = + (WORD32)ih264d_get_bit_h264(ps_bitstrm); + + if(ps_seq->i4_seq_scaling_matrix_present_flag) + { + for(i4_i = 0; i4_i < 8; i4_i++) + { + ps_seq->u1_seq_scaling_list_present_flag[i4_i] = + ih264d_get_bit_h264(ps_bitstrm); + + /* initialize u1_use_default_scaling_matrix_flag[i4_i] to zero */ + /* before calling scaling list */ + ps_seq->u1_use_default_scaling_matrix_flag[i4_i] = 0; + + if(ps_seq->u1_seq_scaling_list_present_flag[i4_i]) + { + if(i4_i < 6) + { + ret = ih264d_scaling_list( + ps_seq->i2_scalinglist4x4[i4_i], + 16, + &ps_seq->u1_use_default_scaling_matrix_flag[i4_i], + ps_bitstrm); + } + else + { + ret = ih264d_scaling_list( + ps_seq->i2_scalinglist8x8[i4_i - 6], + 64, + &ps_seq->u1_use_default_scaling_matrix_flag[i4_i], + ps_bitstrm); + } + if(ret != OK) + { + return ret; + } + } + } + } + } + /*--------------------------------------------------------------------*/ + /* Decode MaxFrameNum */ + /*--------------------------------------------------------------------*/ + u8_temp = (UWORD64)4 + ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); + if(u8_temp > MAX_BITS_IN_FRAME_NUM) + { + return ERROR_INV_SPS_PPS_T; + } + ps_seq->u1_bits_in_frm_num = u8_temp; + COPYTHECONTEXT("SPS: log2_max_frame_num_minus4", + (ps_seq->u1_bits_in_frm_num - 4)); + + i2_max_frm_num = (1 << (ps_seq->u1_bits_in_frm_num)); + ps_seq->u2_u4_max_pic_num_minus1 = i2_max_frm_num - 1; + /*--------------------------------------------------------------------*/ + /* Decode picture order count and related values */ + /*--------------------------------------------------------------------*/ + u4_temp = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); + + if(u4_temp > MAX_PIC_ORDER_CNT_TYPE) + { + return ERROR_INV_POC_TYPE_T; + } + ps_seq->u1_pic_order_cnt_type = u4_temp; + COPYTHECONTEXT("SPS: pic_order_cnt_type",ps_seq->u1_pic_order_cnt_type); + + ps_seq->u1_num_ref_frames_in_pic_order_cnt_cycle = 1; + if(ps_seq->u1_pic_order_cnt_type == 0) + { + u8_temp = (UWORD64)4 + ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); + if(u8_temp > MAX_BITS_IN_POC_LSB) + { + return ERROR_INV_SPS_PPS_T; + } + ps_seq->u1_log2_max_pic_order_cnt_lsb_minus = u8_temp; + ps_seq->i4_max_pic_order_cntLsb = (1 << u8_temp); + COPYTHECONTEXT("SPS: log2_max_pic_order_cnt_lsb_minus4",(u8_temp - 4)); + } + else if(ps_seq->u1_pic_order_cnt_type == 1) + { + ps_seq->u1_delta_pic_order_always_zero_flag = ih264d_get_bit_h264( + ps_bitstrm); + COPYTHECONTEXT("SPS: delta_pic_order_always_zero_flag", + ps_seq->u1_delta_pic_order_always_zero_flag); + + ps_seq->i4_ofst_for_non_ref_pic = ih264d_sev(pu4_bitstrm_ofst, + pu4_bitstrm_buf); + COPYTHECONTEXT("SPS: offset_for_non_ref_pic", + ps_seq->i4_ofst_for_non_ref_pic); + + ps_seq->i4_ofst_for_top_to_bottom_field = ih264d_sev( + pu4_bitstrm_ofst, pu4_bitstrm_buf); + COPYTHECONTEXT("SPS: offset_for_top_to_bottom_field", + ps_seq->i4_ofst_for_top_to_bottom_field); + + u4_temp = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); + if(u4_temp > 255) + return ERROR_INV_SPS_PPS_T; + ps_seq->u1_num_ref_frames_in_pic_order_cnt_cycle = u4_temp; + COPYTHECONTEXT("SPS: num_ref_frames_in_pic_order_cnt_cycle", + ps_seq->u1_num_ref_frames_in_pic_order_cnt_cycle); + + for(i = 0; i < ps_seq->u1_num_ref_frames_in_pic_order_cnt_cycle; i++) + { + ps_seq->i4_ofst_for_ref_frame[i] = ih264d_sev( + pu4_bitstrm_ofst, pu4_bitstrm_buf); + COPYTHECONTEXT("SPS: offset_for_ref_frame", + ps_seq->i4_ofst_for_ref_frame[i]); + } + } + + u4_temp = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); + + if((u4_temp > H264_MAX_REF_PICS)) + { + return ERROR_NUM_REF; + } + + /* Compare with older num_ref_frames is header is already once */ + if((ps_dec->i4_header_decoded & 1) && (ps_seq->u1_num_ref_frames != u4_temp)) + { + ps_dec->u1_res_changed = 1; + return IVD_RES_CHANGED; + } + + ps_seq->u1_num_ref_frames = u4_temp; + COPYTHECONTEXT("SPS: num_ref_frames",ps_seq->u1_num_ref_frames); + + ps_seq->u1_gaps_in_frame_num_value_allowed_flag = ih264d_get_bit_h264( + ps_bitstrm); + COPYTHECONTEXT("SPS: gaps_in_frame_num_value_allowed_flag", + ps_seq->u1_gaps_in_frame_num_value_allowed_flag); + + /*--------------------------------------------------------------------*/ + /* Decode FrameWidth and FrameHeight and related values */ + /*--------------------------------------------------------------------*/ + u8_temp = (UWORD64)1 + ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); + /* Check for unsupported resolutions*/ + if(u8_temp > (H264_MAX_FRAME_WIDTH >> 4)) + { + return IVD_STREAM_WIDTH_HEIGHT_NOT_SUPPORTED; + } + u4_pic_width_in_mbs = u8_temp; + COPYTHECONTEXT("SPS: pic_width_in_mbs_minus1", + u4_pic_width_in_mbs - 1); + + u8_temp = (UWORD64)1 + ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); + if (u8_temp > (H264_MAX_FRAME_HEIGHT >> 4)) + { + return IVD_STREAM_WIDTH_HEIGHT_NOT_SUPPORTED; + } + u4_pic_height_in_map_units = u8_temp; + + ps_seq->u2_frm_wd_in_mbs = u4_pic_width_in_mbs; + ps_seq->u2_frm_ht_in_mbs = u4_pic_height_in_map_units; + + u2_pic_wd = (u4_pic_width_in_mbs << 4); + u2_pic_ht = (u4_pic_height_in_map_units << 4); + /*--------------------------------------------------------------------*/ + /* Get the value of MaxMbAddress and Number of bits needed for it */ + /*--------------------------------------------------------------------*/ + ps_seq->u2_max_mb_addr = (ps_seq->u2_frm_wd_in_mbs + * ps_seq->u2_frm_ht_in_mbs) - 1; + + ps_seq->u2_total_num_of_mbs = ps_seq->u2_max_mb_addr + 1; + + ps_seq->u1_level_idc = ih264d_correct_level_idc( + u1_level_idc, ps_seq->u2_total_num_of_mbs); + + u1_frm = ih264d_get_bit_h264(ps_bitstrm); + if((ps_dec->i4_header_decoded & 1) && (ps_seq->u1_frame_mbs_only_flag != u1_frm)) + { + ps_dec->u1_res_changed = 1; + return IVD_RES_CHANGED; + } + + ps_seq->u1_frame_mbs_only_flag = u1_frm; + + COPYTHECONTEXT("SPS: frame_mbs_only_flag", u1_frm); + + if(!u1_frm) + u1_mb_aff_flag = ih264d_get_bit_h264(ps_bitstrm); + + if((ps_dec->i4_header_decoded & 1) + && (ps_seq->u1_mb_aff_flag != u1_mb_aff_flag)) + { + ps_dec->u1_res_changed = 1; + return IVD_RES_CHANGED; + } + + if(!u1_frm) + { + u2_pic_ht <<= 1; + ps_seq->u1_mb_aff_flag = u1_mb_aff_flag; + COPYTHECONTEXT("SPS: mb_adaptive_frame_field_flag", + ps_seq->u1_mb_aff_flag); + + } + else + ps_seq->u1_mb_aff_flag = 0; + + ps_seq->u1_direct_8x8_inference_flag = ih264d_get_bit_h264(ps_bitstrm); + + COPYTHECONTEXT("SPS: direct_8x8_inference_flag", + ps_seq->u1_direct_8x8_inference_flag); + + /* G050 */ + u1_frame_cropping_flag = ih264d_get_bit_h264(ps_bitstrm); + COPYTHECONTEXT("SPS: frame_cropping_flag",u1_frame_cropping_flag); + + if(u1_frame_cropping_flag) + { + u1_frame_cropping_rect_left_ofst = ih264d_uev(pu4_bitstrm_ofst, + pu4_bitstrm_buf); + COPYTHECONTEXT("SPS: frame_cropping_rect_left_offset", + u1_frame_cropping_rect_left_ofst); + u1_frame_cropping_rect_right_ofst = ih264d_uev(pu4_bitstrm_ofst, + pu4_bitstrm_buf); + COPYTHECONTEXT("SPS: frame_cropping_rect_right_offset", + u1_frame_cropping_rect_right_ofst); + u1_frame_cropping_rect_top_ofst = ih264d_uev(pu4_bitstrm_ofst, + pu4_bitstrm_buf); + COPYTHECONTEXT("SPS: frame_cropping_rect_top_offset", + u1_frame_cropping_rect_top_ofst); + u1_frame_cropping_rect_bottom_ofst = ih264d_uev(pu4_bitstrm_ofst, + pu4_bitstrm_buf); + COPYTHECONTEXT("SPS: frame_cropping_rect_bottom_offset", + u1_frame_cropping_rect_bottom_ofst); + + ps_dec->u1_frame_cropping_flag = u1_frame_cropping_flag; + ps_dec->u1_frame_cropping_rect_left_ofst = u1_frame_cropping_rect_left_ofst; + ps_dec->u1_frame_cropping_rect_right_ofst = u1_frame_cropping_rect_right_ofst; + ps_dec->u1_frame_cropping_rect_top_ofst = u1_frame_cropping_rect_top_ofst; + ps_dec->u1_frame_cropping_rect_bottom_ofst = u1_frame_cropping_rect_bottom_ofst; + } + else + { + ps_dec->u1_frame_cropping_flag = 0; + ps_dec->u1_frame_cropping_rect_left_ofst = 0; + ps_dec->u1_frame_cropping_rect_right_ofst = 0; + ps_dec->u1_frame_cropping_rect_top_ofst = 0; + ps_dec->u1_frame_cropping_rect_bottom_ofst = 0; + } + + /* G050 */ + + ps_seq->u1_vui_parameters_present_flag = ih264d_get_bit_h264(ps_bitstrm); + COPYTHECONTEXT("SPS: vui_parameters_present_flag", + ps_seq->u1_vui_parameters_present_flag); + + u2_frm_wd_y = u2_pic_wd + (UWORD8)(PAD_LEN_Y_H << 1); + if(1 == ps_dec->u4_share_disp_buf) + { + if(ps_dec->u4_app_disp_width > u2_frm_wd_y) + u2_frm_wd_y = ps_dec->u4_app_disp_width; + } + + u2_frm_ht_y = u2_pic_ht + (UWORD8)(PAD_LEN_Y_V << 2); + u2_frm_wd_uv = u2_pic_wd + (UWORD8)(PAD_LEN_UV_H << 2); + u2_frm_wd_uv = MAX(u2_frm_wd_uv, u2_frm_wd_y); + + u2_frm_ht_uv = (u2_pic_ht >> 1) + (UWORD8)(PAD_LEN_UV_V << 2); + u2_frm_ht_uv = MAX(u2_frm_ht_uv, (u2_frm_ht_y >> 1)); + + + /* Calculate display picture width, height and start u4_ofst from YUV420 */ + /* pictute buffers as per cropping information parsed above */ + { + UWORD16 u2_rgt_ofst = 0; + UWORD16 u2_lft_ofst = 0; + UWORD16 u2_top_ofst = 0; + UWORD16 u2_btm_ofst = 0; + UWORD8 u1_frm_mbs_flag; + UWORD8 u1_vert_mult_factor; + + //if(u1_frame_cropping_flag) + //{ + // /* Calculate right and left u4_ofst for cropped picture */ + // u2_rgt_ofst = u1_frame_cropping_rect_right_ofst << 1; + // u2_lft_ofst = u1_frame_cropping_rect_left_ofst << 1; + + // /* Know frame MBs only u4_flag */ + // u1_frm_mbs_flag = (1 == ps_seq->u1_frame_mbs_only_flag); + + // /* Simplify the vertical u4_ofst calculation from field/frame */ + // u1_vert_mult_factor = (2 - u1_frm_mbs_flag); + + // /* Calculate bottom and top u4_ofst for cropped picture */ + // u2_btm_ofst = (u1_frame_cropping_rect_bottom_ofst + // << u1_vert_mult_factor); + // u2_top_ofst = (u1_frame_cropping_rect_top_ofst + // << u1_vert_mult_factor); + //} + + /* Calculate u4_ofst from start of YUV 420 picture buffer to start of*/ + /* cropped picture buffer */ + u2_crop_offset_y = (u2_frm_wd_y * u2_top_ofst) + (u2_lft_ofst); + u2_crop_offset_uv = (u2_frm_wd_uv * (u2_top_ofst >> 1)) + + (u2_lft_ofst >> 1) * YUV420SP_FACTOR; + /* Calculate the display picture width and height based on crop */ + /* information */ + i4_cropped_ht = (WORD32)u2_pic_ht - (WORD32)(u2_btm_ofst + u2_top_ofst); + i4_cropped_wd = (WORD32)u2_pic_wd - (WORD32)(u2_rgt_ofst + u2_lft_ofst); + + if((i4_cropped_ht < MB_SIZE) || (i4_cropped_wd < MB_SIZE)) + { + return ERROR_INV_SPS_PPS_T; + } + + if((ps_dec->i4_header_decoded & 1) && (ps_dec->u2_pic_wd != u2_pic_wd)) + { + ps_dec->u1_res_changed = 1; + return IVD_RES_CHANGED; + } + + if((ps_dec->i4_header_decoded & 1) && (ps_dec->u2_disp_width != i4_cropped_wd)) + { + ps_dec->u1_res_changed = 1; + return IVD_RES_CHANGED; + } + + if((ps_dec->i4_header_decoded & 1) && (ps_dec->u2_pic_ht != u2_pic_ht)) + { + ps_dec->u1_res_changed = 1; + return IVD_RES_CHANGED; + } + + if((ps_dec->i4_header_decoded & 1) && (ps_dec->u2_disp_height != i4_cropped_ht)) + { + ps_dec->u1_res_changed = 1; + return IVD_RES_CHANGED; + } + + /* Check again for unsupported resolutions with updated values*/ + if((u2_pic_wd > H264_MAX_FRAME_WIDTH) || (u2_pic_ht > H264_MAX_FRAME_HEIGHT) + || (u2_pic_wd < H264_MIN_FRAME_WIDTH) || (u2_pic_ht < H264_MIN_FRAME_HEIGHT) + || (u2_pic_wd * (UWORD32)u2_pic_ht > H264_MAX_FRAME_SIZE)) + { + return IVD_STREAM_WIDTH_HEIGHT_NOT_SUPPORTED; + } + + /* If MBAff is enabled, decoder support is limited to streams with + * width less than half of H264_MAX_FRAME_WIDTH. + * In case of MBAff decoder processes two rows at a time + */ + if((u2_pic_wd << ps_seq->u1_mb_aff_flag) > H264_MAX_FRAME_WIDTH) + { + return IVD_STREAM_WIDTH_HEIGHT_NOT_SUPPORTED; + } + + } + + /* Backup num_reorder_frames if header is already decoded */ + if((ps_dec->i4_header_decoded & 1) && + (1 == ps_seq->u1_vui_parameters_present_flag) && + (1 == ps_seq->s_vui.u1_bitstream_restriction_flag)) + { + num_reorder_frames = (WORD32)ps_seq->s_vui.u4_num_reorder_frames; + } + else + { + num_reorder_frames = -1; + } + if(1 == ps_seq->u1_vui_parameters_present_flag) + { + ret = ih264d_parse_vui_parametres(&ps_seq->s_vui, ps_bitstrm); + if(ret != OK) + return ret; + } + + /* Compare older num_reorder_frames with the new one if header is already decoded */ + if((ps_dec->i4_header_decoded & 1) && + (-1 != num_reorder_frames) && + (1 == ps_seq->u1_vui_parameters_present_flag) && + (1 == ps_seq->s_vui.u1_bitstream_restriction_flag) && + ((WORD32)ps_seq->s_vui.u4_num_reorder_frames != num_reorder_frames)) + { + ps_dec->u1_res_changed = 1; + return IVD_RES_CHANGED; + } + + /* In case bitstream read has exceeded the filled size, then + return an error */ + if (EXCEED_OFFSET(ps_bitstrm)) + { + return ERROR_INV_SPS_PPS_T; + } + + /*--------------------------------------------------------------------*/ + /* All initializations to ps_dec are beyond this point */ + /*--------------------------------------------------------------------*/ + { + WORD32 reorder_depth = ih264d_get_dpb_size(ps_seq); + if((1 == ps_seq->u1_vui_parameters_present_flag) && + (1 == ps_seq->s_vui.u1_bitstream_restriction_flag)) + { + reorder_depth = ps_seq->s_vui.u4_num_reorder_frames + 1; + } + + if (reorder_depth > H264_MAX_REF_PICS) + { + return ERROR_INV_SPS_PPS_T; + } + + if(ps_seq->u1_frame_mbs_only_flag != 1) + reorder_depth *= 2; + ps_dec->i4_reorder_depth = reorder_depth + DISPLAY_LATENCY; + } + ps_dec->u2_disp_height = i4_cropped_ht; + ps_dec->u2_disp_width = i4_cropped_wd; + + ps_dec->u2_pic_wd = u2_pic_wd; + ps_dec->u2_pic_ht = u2_pic_ht; + + /* Determining the Width and Height of Frame from that of Picture */ + ps_dec->u2_frm_wd_y = u2_frm_wd_y; + ps_dec->u2_frm_ht_y = u2_frm_ht_y; + + ps_dec->u2_frm_wd_uv = u2_frm_wd_uv; + ps_dec->u2_frm_ht_uv = u2_frm_ht_uv; + ps_dec->s_pad_mgr.u1_pad_len_y_v = (UWORD8)(PAD_LEN_Y_V << (1 - u1_frm)); + ps_dec->s_pad_mgr.u1_pad_len_cr_v = (UWORD8)(PAD_LEN_UV_V << (1 - u1_frm)); + + ps_dec->u2_frm_wd_in_mbs = ps_seq->u2_frm_wd_in_mbs; + ps_dec->u2_frm_ht_in_mbs = ps_seq->u2_frm_ht_in_mbs; + + ps_dec->u2_crop_offset_y = u2_crop_offset_y; + ps_dec->u2_crop_offset_uv = u2_crop_offset_uv; + + ps_seq->u1_is_valid = TRUE; + ps_dec->ps_sps[u1_seq_parameter_set_id] = *ps_seq; + ps_dec->ps_cur_sps = &ps_dec->ps_sps[u1_seq_parameter_set_id]; + + return OK; +} + +/*! + ************************************************************************** + * \if Function name : ih264d_parse_end_of_sequence \endif + * + * \brief + * Decodes End of Sequence. + * + * \param ps_bitstrm : Pointer to bit ps_bitstrm containing the NAL unit + * + * \return + * 0 on Success and error code otherwise + ************************************************************************** + */ +WORD32 ih264d_parse_end_of_sequence(dec_struct_t * ps_dec) +{ + WORD32 ret; + + ret = ih264d_end_of_pic_processing(ps_dec); + return ret; +} + +/*! + ************************************************************************** + * \if Function name : AcessUnitDelimiterRbsp \endif + * + * \brief + * Decodes AcessUnitDelimiterRbsp. + * + * \param ps_bitstrm : Pointer to bit ps_bitstrm containing the NAL unit + * + * \return + * 0 on Success and error code otherwise + ************************************************************************** + */ + +WORD32 ih264d_access_unit_delimiter_rbsp(dec_struct_t * ps_dec) +{ + UWORD8 u1_primary_pic_type; + u1_primary_pic_type = ih264d_get_bits_h264(ps_dec->ps_bitstrm, 3); + switch(u1_primary_pic_type) + { + case I_PIC: + case SI_PIC: + case ISI_PIC: + ps_dec->ps_dec_err_status->u1_pic_aud_i = PIC_TYPE_I; + break; + default: + ps_dec->ps_dec_err_status->u1_pic_aud_i = PIC_TYPE_UNKNOWN; + } + return (0); +} +/*! + ************************************************************************** + * \if Function name : ih264d_parse_nal_unit \endif + * + * \brief + * Decodes NAL unit + * + * \return + * 0 on Success and error code otherwise + ************************************************************************** + */ + +WORD32 ih264d_parse_nal_unit(iv_obj_t *dec_hdl, + ivd_video_decode_op_t *ps_dec_op, + UWORD8 *pu1_buf, + UWORD32 u4_length) +{ + + dec_bit_stream_t *ps_bitstrm; + + + dec_struct_t *ps_dec = (dec_struct_t *)dec_hdl->pv_codec_handle; + ivd_video_decode_ip_t *ps_dec_in = + (ivd_video_decode_ip_t *)ps_dec->pv_dec_in; + dec_slice_params_t * ps_cur_slice = ps_dec->ps_cur_slice; + UWORD8 u1_first_byte, u1_nal_ref_idc; + UWORD8 u1_nal_unit_type; + WORD32 i_status = OK; + ps_bitstrm = ps_dec->ps_bitstrm; + + if(pu1_buf) + { + if(u4_length) + { + ps_dec_op->u4_frame_decoded_flag = 0; + ih264d_process_nal_unit(ps_dec->ps_bitstrm, pu1_buf, + u4_length); + + SWITCHOFFTRACE; + u1_first_byte = ih264d_get_bits_h264(ps_bitstrm, 8); + + if(NAL_FORBIDDEN_BIT(u1_first_byte)) + { + H264_DEC_DEBUG_PRINT("\nForbidden bit set in Nal Unit, Let's try\n"); + } + u1_nal_unit_type = NAL_UNIT_TYPE(u1_first_byte); + // if any other nal unit other than slice nal is encountered in between a + // frame break out of loop without consuming header + if ((ps_dec->u4_slice_start_code_found == 1) + && (ps_dec->u1_pic_decode_done != 1) + && (u1_nal_unit_type > IDR_SLICE_NAL)) + { + return ERROR_INCOMPLETE_FRAME; + } + ps_dec->u1_nal_unit_type = u1_nal_unit_type; + u1_nal_ref_idc = (UWORD8)(NAL_REF_IDC(u1_first_byte)); + //Skip all NALUs if SPS and PPS are not decoded + switch(u1_nal_unit_type) + { + case SLICE_DATA_PARTITION_A_NAL: + case SLICE_DATA_PARTITION_B_NAL: + case SLICE_DATA_PARTITION_C_NAL: + if(!ps_dec->i4_decode_header) + ih264d_parse_slice_partition(ps_dec, ps_bitstrm); + + break; + + case IDR_SLICE_NAL: + case SLICE_NAL: + + /* ! */ + DEBUG_THREADS_PRINTF("Decoding a slice NAL\n"); + if(!ps_dec->i4_decode_header) + { + if(ps_dec->i4_header_decoded == 3) + { + ih264d_get_pre_sei_params(ps_dec, u1_nal_unit_type); + /* ! */ + ps_dec->u4_slice_start_code_found = 1; + + ih264d_rbsp_to_sodb(ps_dec->ps_bitstrm); + + i_status = ih264d_parse_decode_slice( + (UWORD8)(u1_nal_unit_type + == IDR_SLICE_NAL), + u1_nal_ref_idc, ps_dec); + + if(i_status != OK) + { + return i_status; + } + } + else + { + H264_DEC_DEBUG_PRINT( + "\nSlice NAL Supplied but no header has been supplied\n"); + } + } + break; + + case SEI_NAL: + if(!ps_dec->i4_decode_header) + { + ih264d_rbsp_to_sodb(ps_dec->ps_bitstrm); + i_status = ih264d_parse_sei_message(ps_dec, ps_bitstrm); + if(i_status != OK) + return i_status; + ih264d_parse_sei(ps_dec, ps_bitstrm); + } + break; + case SEQ_PARAM_NAL: + /* ! */ + ih264d_rbsp_to_sodb(ps_dec->ps_bitstrm); + i_status = ih264d_parse_sps(ps_dec, ps_bitstrm); + ps_dec->u4_sps_cnt_in_process++; + /*If a resolution change happens within a process call, due to multiple sps + * we will not support it. + */ + if((ps_dec->u4_sps_cnt_in_process > 1 ) && + (i_status == IVD_RES_CHANGED)) + { + i_status = ERROR_INV_SPS_PPS_T; + ps_dec->u1_res_changed = 0; + } + if(i_status == ERROR_INV_SPS_PPS_T) + return i_status; + if(!i_status) + ps_dec->i4_header_decoded |= 0x1; + break; + + case PIC_PARAM_NAL: + /* ! */ + ih264d_rbsp_to_sodb(ps_dec->ps_bitstrm); + i_status = ih264d_parse_pps(ps_dec, ps_bitstrm); + if(i_status == ERROR_INV_SPS_PPS_T) + return i_status; + if(!i_status) + ps_dec->i4_header_decoded |= 0x2; + break; + case ACCESS_UNIT_DELIMITER_RBSP: + if(!ps_dec->i4_decode_header) + { + ih264d_access_unit_delimiter_rbsp(ps_dec); + } + break; + //Let us ignore the END_OF_SEQ_RBSP NAL and decode even after this NAL + case END_OF_STREAM_RBSP: + if(!ps_dec->i4_decode_header) + { + ih264d_parse_end_of_stream(ps_dec); + } + break; + case FILLER_DATA_NAL: + if(!ps_dec->i4_decode_header) + { + ih264d_parse_filler_data(ps_dec, ps_bitstrm); + } + break; + default: + H264_DEC_DEBUG_PRINT("\nUnknown NAL type %d\n", u1_nal_unit_type); + break; + } + + } + + } + + return i_status; + +} + diff --git a/dependencies/ih264d/decoder/ih264d_parse_headers.h b/dependencies/ih264d/decoder/ih264d_parse_headers.h new file mode 100644 index 00000000..b6b997f0 --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_parse_headers.h @@ -0,0 +1,47 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore + * Modified for use with Cemu emulator project +*/ +#ifndef _IH264D_PARSE_HEADERS_H_ +#define _IH264D_PARSE_HEADERS_H_ +/*! +************************************************************************** +* \file ih264d_parse_headers.h +* +* \brief +* Contains declarations high level syntax[above slice] +* parsing routines +* +* \date +* 19/12/2002 +* +* \author AI +************************************************************************** +*/ +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264d_bitstrm.h" +#include "ih264d_structs.h" +WORD32 ih264d_parse_nal_unit(iv_obj_t *dec_hdl, + ivd_video_decode_op_t *ps_dec_op, + UWORD8 *pu1_buf, + UWORD32 u4_length); + +#endif /* _IH264D_PARSE_HEADERS_H_ */ diff --git a/dependencies/ih264d/decoder/ih264d_parse_islice.c b/dependencies/ih264d/decoder/ih264d_parse_islice.c new file mode 100644 index 00000000..0b8111a8 --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_parse_islice.c @@ -0,0 +1,1497 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ + +/*! + ************************************************************************** + * \file ih264d_parse_islice.c + * + * \brief + * Contains routines that decode a I slice type + * + * Detailed_description + * + * \date + * 07/07/2003 + * + * \author NS + ************************************************************************** + */ +#include <string.h> +#include "ih264_defs.h" +#include "ih264d_error_handler.h" +#include "ih264d_debug.h" +#include "ih264d_bitstrm.h" +#include "ih264d_defs.h" +#include "ih264d_debug.h" +#include "ih264d_tables.h" +#include "ih264d_structs.h" +#include "ih264d_defs.h" +#include "ih264d_parse_cavlc.h" +#include "ih264d_mb_utils.h" +#include "ih264d_deblocking.h" +#include "ih264d_cabac.h" +#include "ih264d_parse_cabac.h" +#include "ih264d_parse_mb_header.h" +#include "ih264d_parse_slice.h" +#include "ih264d_process_pslice.h" +#include "ih264d_process_intra_mb.h" +#include "ih264d_parse_islice.h" +#include "ih264d_error_handler.h" +#include "ih264d_mvpred.h" +#include "ih264d_defs.h" +#include "ih264d_thread_parse_decode.h" +#include "ithread.h" +#include "ih264d_parse_mb_header.h" +#include "assert.h" +#include "ih264d_utils.h" +#include "ih264d_format_conv.h" + +void ih264d_init_cabac_contexts(UWORD8 u1_slice_type, dec_struct_t * ps_dec); + +void ih264d_itrans_recon_luma_dc(dec_struct_t *ps_dec, + WORD16* pi2_src, + WORD16* pi2_coeff_block, + const UWORD16 *pu2_weigh_mat); + + + +/*! + ************************************************************************** + * \if Function name : ParseIMb \endif + * + * \brief + * This function parses CAVLC syntax of a I MB. If 16x16 Luma DC transform + * is also done here. Transformed Luma DC values are copied in their + * 0th pixel location of corrosponding CoeffBlock. + * + * \return + * 0 on Success and Error code otherwise + ************************************************************************** + */ +WORD32 ih264d_parse_imb_cavlc(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info, + UWORD8 u1_mb_num, + UWORD8 u1_mb_type) +{ + WORD32 i4_delta_qp; + UWORD32 u4_temp; + UWORD32 ui_is_top_mb_available; + UWORD32 ui_is_left_mb_available; + UWORD32 u4_cbp; + UWORD32 u4_offset; + UWORD32 *pu4_bitstrm_buf; + WORD32 ret; + + dec_bit_stream_t * const ps_bitstrm = ps_dec->ps_bitstrm; + UWORD32 *pu4_bitstrm_ofst = &ps_bitstrm->u4_ofst; + UNUSED(u1_mb_num); + ps_cur_mb_info->u1_tran_form8x8 = 0; + ps_cur_mb_info->ps_curmb->u1_tran_form8x8 = 0; + + ps_cur_mb_info->u1_yuv_dc_block_flag = 0; + + u4_temp = ps_dec->u1_mb_ngbr_availablity; + ui_is_top_mb_available = BOOLEAN(u4_temp & TOP_MB_AVAILABLE_MASK); + ui_is_left_mb_available = BOOLEAN(u4_temp & LEFT_MB_AVAILABLE_MASK); + + pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; + + if(u1_mb_type == I_4x4_MB) + { + ps_cur_mb_info->ps_curmb->u1_mb_type = I_4x4_MB; + u4_offset = 0; + + /*--------------------------------------------------------------------*/ + /* Read transform_size_8x8_flag if present */ + /*--------------------------------------------------------------------*/ + if(ps_dec->s_high_profile.u1_transform8x8_present) + { + ps_cur_mb_info->u1_tran_form8x8 = ih264d_get_bit_h264(ps_bitstrm); + COPYTHECONTEXT("transform_size_8x8_flag", ps_cur_mb_info->u1_tran_form8x8); + ps_cur_mb_info->ps_curmb->u1_tran_form8x8 = ps_cur_mb_info->u1_tran_form8x8; + } + + /*--------------------------------------------------------------------*/ + /* Read the IntraPrediction modes for LUMA */ + /*--------------------------------------------------------------------*/ + if (!ps_cur_mb_info->u1_tran_form8x8) + { + UWORD8 *pu1_temp; + ih264d_read_intra_pred_modes(ps_dec, + ((UWORD8 *)ps_dec->pv_parse_tu_coeff_data), + ((UWORD8 *)ps_dec->pv_parse_tu_coeff_data+16), + ps_cur_mb_info->u1_tran_form8x8); + pu1_temp = (UWORD8 *)ps_dec->pv_parse_tu_coeff_data; + pu1_temp += 32; + ps_dec->pv_parse_tu_coeff_data = (void *)pu1_temp; + } + else + { + UWORD8 *pu1_temp; + ih264d_read_intra_pred_modes(ps_dec, + ((UWORD8 *)ps_dec->pv_parse_tu_coeff_data), + ((UWORD8 *)ps_dec->pv_parse_tu_coeff_data+4), + ps_cur_mb_info->u1_tran_form8x8); + pu1_temp = (UWORD8 *)ps_dec->pv_parse_tu_coeff_data; + pu1_temp += 8; + ps_dec->pv_parse_tu_coeff_data = (void *)pu1_temp; + } + /*--------------------------------------------------------------------*/ + /* Read the IntraPrediction mode for CHROMA */ + /*--------------------------------------------------------------------*/ +//Inlined ih264d_uev + { + UWORD32 u4_bitstream_offset = *pu4_bitstrm_ofst; + UWORD32 u4_word, u4_ldz, u4_temp; + + /***************************************************************/ + /* Find leading zeros in next 32 bits */ + /***************************************************************/ + NEXTBITS_32(u4_word, u4_bitstream_offset, pu4_bitstrm_buf); + u4_ldz = CLZ(u4_word); + /* Flush the ps_bitstrm */ + u4_bitstream_offset += (u4_ldz + 1); + /* Read the suffix from the ps_bitstrm */ + u4_word = 0; + if(u4_ldz) + { + GETBITS(u4_word, u4_bitstream_offset, pu4_bitstrm_buf, + u4_ldz); + } + *pu4_bitstrm_ofst = u4_bitstream_offset; + u4_temp = ((1 << u4_ldz) + u4_word - 1); + if(u4_temp > 3) + { + return ERROR_CHROMA_PRED_MODE; + } + ps_cur_mb_info->u1_chroma_pred_mode = u4_temp; + COPYTHECONTEXT("intra_chroma_pred_mode", ps_cur_mb_info->u1_chroma_pred_mode); + } + /*--------------------------------------------------------------------*/ + /* Read the Coded block pattern */ + /*--------------------------------------------------------------------*/ + { + UWORD32 u4_bitstream_offset = *pu4_bitstrm_ofst; + UWORD32 u4_word, u4_ldz; + + /***************************************************************/ + /* Find leading zeros in next 32 bits */ + /***************************************************************/ + NEXTBITS_32(u4_word, u4_bitstream_offset, pu4_bitstrm_buf); + u4_ldz = CLZ(u4_word); + /* Flush the ps_bitstrm */ + u4_bitstream_offset += (u4_ldz + 1); + /* Read the suffix from the ps_bitstrm */ + u4_word = 0; + if(u4_ldz) + { + GETBITS(u4_word, u4_bitstream_offset, pu4_bitstrm_buf, + u4_ldz); + } + *pu4_bitstrm_ofst = u4_bitstream_offset; + u4_cbp = ((1 << u4_ldz) + u4_word - 1); + } + if(u4_cbp > 47) + { + return ERROR_CBP; + } + + u4_cbp = gau1_ih264d_cbp_table[u4_cbp][0]; + COPYTHECONTEXT("coded_block_pattern", u1_cbp); + ps_cur_mb_info->u1_cbp = u4_cbp; + + /*--------------------------------------------------------------------*/ + /* Read mb_qp_delta */ + /*--------------------------------------------------------------------*/ + if(ps_cur_mb_info->u1_cbp) + { + UWORD32 u4_bitstream_offset = *pu4_bitstrm_ofst; + UWORD32 u4_word, u4_ldz, u4_abs_val; + + /***************************************************************/ + /* Find leading zeros in next 32 bits */ + /***************************************************************/ + NEXTBITS_32(u4_word, u4_bitstream_offset, pu4_bitstrm_buf); + u4_ldz = CLZ(u4_word); + + /* Flush the ps_bitstrm */ + u4_bitstream_offset += (u4_ldz + 1); + + /* Read the suffix from the ps_bitstrm */ + u4_word = 0; + if(u4_ldz) + { + GETBITS(u4_word, u4_bitstream_offset, pu4_bitstrm_buf, + u4_ldz); + } + + *pu4_bitstrm_ofst = u4_bitstream_offset; + u4_abs_val = ((1 << u4_ldz) + u4_word) >> 1; + + if(u4_word & 0x1) + { + i4_delta_qp = (-(WORD32)u4_abs_val); + } + else + { + i4_delta_qp = (u4_abs_val); + } + + if((i4_delta_qp < -26) || (i4_delta_qp > 25)) + { + return ERROR_INV_RANGE_QP_T; + } + + COPYTHECONTEXT("mb_qp_delta", i1_delta_qp); + if(i4_delta_qp != 0) + { + ret = ih264d_update_qp(ps_dec, (WORD8)i4_delta_qp); + if(ret != OK) + return ret; + } + } + + } + else + { + u4_offset = 1; + ps_cur_mb_info->ps_curmb->u1_mb_type = I_16x16_MB; + /*-------------------------------------------------------------------*/ + /* Read the IntraPrediction mode for CHROMA */ + /*-------------------------------------------------------------------*/ + { + UWORD32 u4_bitstream_offset = *pu4_bitstrm_ofst; + UWORD32 u4_word, u4_ldz; + + /***************************************************************/ + /* Find leading zeros in next 32 bits */ + /***************************************************************/ + NEXTBITS_32(u4_word, u4_bitstream_offset, pu4_bitstrm_buf); + u4_ldz = CLZ(u4_word); + /* Flush the ps_bitstrm */ + u4_bitstream_offset += (u4_ldz + 1); + /* Read the suffix from the ps_bitstrm */ + u4_word = 0; + if(u4_ldz) + { + GETBITS(u4_word, u4_bitstream_offset, pu4_bitstrm_buf, + u4_ldz); + } + *pu4_bitstrm_ofst = u4_bitstream_offset; + u4_temp = ((1 << u4_ldz) + u4_word - 1); + +//Inlined ih264d_uev + + if(u4_temp > 3) + { + return ERROR_CHROMA_PRED_MODE; + } + ps_cur_mb_info->u1_chroma_pred_mode = u4_temp; + COPYTHECONTEXT("intra_chroma_pred_mode", ps_cur_mb_info->u1_chroma_pred_mode); + } + /*-------------------------------------------------------------------*/ + /* Read the Coded block pattern */ + /*-------------------------------------------------------------------*/ + u4_cbp = gau1_ih264d_cbp_tab[(u1_mb_type - 1) >> 2]; + ps_cur_mb_info->u1_cbp = u4_cbp; + + /*-------------------------------------------------------------------*/ + /* Read mb_qp_delta */ + /*-------------------------------------------------------------------*/ + { + UWORD32 u4_bitstream_offset = *pu4_bitstrm_ofst; + UWORD32 u4_word, u4_ldz, u4_abs_val; + + /***************************************************************/ + /* Find leading zeros in next 32 bits */ + /***************************************************************/ + NEXTBITS_32(u4_word, u4_bitstream_offset, pu4_bitstrm_buf); + u4_ldz = CLZ(u4_word); + + /* Flush the ps_bitstrm */ + u4_bitstream_offset += (u4_ldz + 1); + + /* Read the suffix from the ps_bitstrm */ + u4_word = 0; + if(u4_ldz) + GETBITS(u4_word, u4_bitstream_offset, pu4_bitstrm_buf, + u4_ldz); + + *pu4_bitstrm_ofst = u4_bitstream_offset; + u4_abs_val = ((1 << u4_ldz) + u4_word) >> 1; + + if(u4_word & 0x1) + i4_delta_qp = (-(WORD32)u4_abs_val); + else + i4_delta_qp = (u4_abs_val); + + if((i4_delta_qp < -26) || (i4_delta_qp > 25)) + return ERROR_INV_RANGE_QP_T; + + } +//inlinined ih264d_sev + COPYTHECONTEXT("Delta quant", i1_delta_qp); + + if(i4_delta_qp != 0) + { + ret = ih264d_update_qp(ps_dec, (WORD8)i4_delta_qp); + if(ret != OK) + return ret; + } + + { + WORD16 i_scaleFactor; + UWORD32 ui_N = 0; + WORD16 *pi2_scale_matrix_ptr; + /*******************************************************************/ + /* for luma DC coefficients the scaling is done during the parsing */ + /* to preserve the precision */ + /*******************************************************************/ + if(ps_dec->s_high_profile.u1_scaling_present) + { + pi2_scale_matrix_ptr = + ps_dec->s_high_profile.i2_scalinglist4x4[0]; + } + else + { + i_scaleFactor = 16; + pi2_scale_matrix_ptr = &i_scaleFactor; + } + + /*---------------------------------------------------------------*/ + /* Decode DC coefficients */ + /*---------------------------------------------------------------*/ + /*---------------------------------------------------------------*/ + /* Calculation of N */ + /*---------------------------------------------------------------*/ + if(ui_is_left_mb_available) + { + + if(ui_is_top_mb_available) + { + ui_N = ((ps_cur_mb_info->ps_top_mb->pu1_nnz_y[0] + + ps_dec->pu1_left_nnz_y[0] + 1) >> 1); + } + else + { + ui_N = ps_dec->pu1_left_nnz_y[0]; + } + } + else if(ui_is_top_mb_available) + { + ui_N = ps_cur_mb_info->ps_top_mb->pu1_nnz_y[0]; + } + + { + WORD16 pi2_dc_coef[16]; + WORD32 pi4_tmp[16]; + tu_sblk4x4_coeff_data_t *ps_tu_4x4 = + (tu_sblk4x4_coeff_data_t *)ps_dec->pv_parse_tu_coeff_data; + WORD16 *pi2_coeff_block = + (WORD16 *)ps_dec->pv_parse_tu_coeff_data; + UWORD32 u4_num_coeff; + ps_tu_4x4->u2_sig_coeff_map = 0; + + ret = ps_dec->pf_cavlc_parse4x4coeff[(ui_N > 7)](pi2_dc_coef, 0, ui_N, + ps_dec, &u4_num_coeff); + if(ret != OK) + return ret; + + if(EXCEED_OFFSET(ps_bitstrm)) + return ERROR_EOB_TERMINATE_T; + if(ps_tu_4x4->u2_sig_coeff_map) + { + memset(pi2_dc_coef,0,sizeof(pi2_dc_coef)); + ih264d_unpack_coeff4x4_dc_4x4blk(ps_tu_4x4, + pi2_dc_coef, + ps_dec->pu1_inv_scan); + + PROFILE_DISABLE_IQ_IT_RECON() + ps_dec->pf_ihadamard_scaling_4x4(pi2_dc_coef, + pi2_coeff_block, + ps_dec->pu2_quant_scale_y, + (UWORD16 *)pi2_scale_matrix_ptr, + ps_dec->u1_qp_y_div6, + pi4_tmp); + pi2_coeff_block += 16; + ps_dec->pv_parse_tu_coeff_data = (void *)pi2_coeff_block; + SET_BIT(ps_cur_mb_info->u1_yuv_dc_block_flag,0); + } + + } + } + } + + + if(u4_cbp) + { + + ret = ih264d_parse_residual4x4_cavlc(ps_dec, ps_cur_mb_info, + (UWORD8)u4_offset); + if(ret != OK) + return ret; + if(EXCEED_OFFSET(ps_bitstrm)) + return ERROR_EOB_TERMINATE_T; + + /* Store Left Mb NNZ and TOP chroma NNZ */ + } + else + { + ps_cur_mb_info->u1_qp_div6 = ps_dec->u1_qp_y_div6; + ps_cur_mb_info->u1_qpc_div6 = ps_dec->u1_qp_u_div6; + ps_cur_mb_info->u1_qpcr_div6 = ps_dec->u1_qp_v_div6; + ps_cur_mb_info->u1_qp_rem6 = ps_dec->u1_qp_y_rem6; + ps_cur_mb_info->u1_qpc_rem6 = ps_dec->u1_qp_u_rem6; + ps_cur_mb_info->u1_qpcr_rem6 = ps_dec->u1_qp_v_rem6; + ih264d_update_nnz_for_skipmb(ps_dec, ps_cur_mb_info, CAVLC); + } + + return OK; +} + +/*! + ************************************************************************** + * \if Function name : ParseIMbCab \endif + * + * \brief + * This function parses CABAC syntax of a I MB. If 16x16 Luma DC transform + * is also done here. Transformed Luma DC values are copied in their + * 0th pixel location of corrosponding CoeffBlock. + * + * \return + * 0 on Success and Error code otherwise + ************************************************************************** + */ +WORD32 ih264d_parse_imb_cabac(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info, + UWORD8 u1_mb_type) +{ + WORD8 i1_delta_qp; + UWORD8 u1_cbp; + UWORD8 u1_offset; + /* Variables for handling Cabac contexts */ + ctxt_inc_mb_info_t *p_curr_ctxt = ps_dec->ps_curr_ctxt_mb_info; + ctxt_inc_mb_info_t *ps_left_ctxt = ps_dec->p_left_ctxt_mb_info; + dec_bit_stream_t * const ps_bitstrm = ps_dec->ps_bitstrm; + bin_ctxt_model_t *p_bin_ctxt; + + UWORD8 u1_intra_chrom_pred_mode; + UWORD8 u1_dc_block_flag = 0; + WORD32 ret; + + ps_cur_mb_info->u1_yuv_dc_block_flag = 0; + + if(ps_left_ctxt == ps_dec->ps_def_ctxt_mb_info) + { + ps_dec->pu1_left_yuv_dc_csbp[0] = 0xf; + } + + if(ps_dec->ps_cur_slice->u1_slice_type != I_SLICE) + { + WORD32 *pi4_buf; + WORD8 *pi1_buf; + MEMSET_16BYTES(&ps_dec->pu1_left_mv_ctxt_inc[0][0], 0); + *((UWORD32 *)ps_dec->pi1_left_ref_idx_ctxt_inc) = 0; + MEMSET_16BYTES(p_curr_ctxt->u1_mv, 0); + memset(p_curr_ctxt->i1_ref_idx, 0, 4); + } + + if(u1_mb_type == I_4x4_MB) + { + ps_cur_mb_info->ps_curmb->u1_mb_type = I_4x4_MB; + p_curr_ctxt->u1_mb_type = CAB_I4x4; + u1_offset = 0; + + ps_cur_mb_info->u1_tran_form8x8 = 0; + ps_cur_mb_info->ps_curmb->u1_tran_form8x8 = 0; + + /*--------------------------------------------------------------------*/ + /* Read transform_size_8x8_flag if present */ + /*--------------------------------------------------------------------*/ + if(ps_dec->s_high_profile.u1_transform8x8_present) + { + ps_cur_mb_info->u1_tran_form8x8 = ih264d_parse_transform8x8flag_cabac( + ps_dec, ps_cur_mb_info); + COPYTHECONTEXT("transform_size_8x8_flag", ps_cur_mb_info->u1_tran_form8x8); + p_curr_ctxt->u1_transform8x8_ctxt = ps_cur_mb_info->u1_tran_form8x8; + ps_cur_mb_info->ps_curmb->u1_tran_form8x8 = ps_cur_mb_info->u1_tran_form8x8; + } + else + { + p_curr_ctxt->u1_transform8x8_ctxt = 0; + } + + /*--------------------------------------------------------------------*/ + /* Read the IntraPrediction modes for LUMA */ + /*--------------------------------------------------------------------*/ + if (!ps_cur_mb_info->u1_tran_form8x8) + { + UWORD8 *pu1_temp; + ih264d_read_intra_pred_modes_cabac( + ps_dec, + ((UWORD8 *)ps_dec->pv_parse_tu_coeff_data), + ((UWORD8 *)ps_dec->pv_parse_tu_coeff_data+16), + ps_cur_mb_info->u1_tran_form8x8); + pu1_temp = (UWORD8 *)ps_dec->pv_parse_tu_coeff_data; + pu1_temp += 32; + ps_dec->pv_parse_tu_coeff_data = (void *)pu1_temp; + } + else + { + UWORD8 *pu1_temp; + ih264d_read_intra_pred_modes_cabac( + ps_dec, + ((UWORD8 *)ps_dec->pv_parse_tu_coeff_data), + ((UWORD8 *)ps_dec->pv_parse_tu_coeff_data+4), + ps_cur_mb_info->u1_tran_form8x8); + pu1_temp = (UWORD8 *)ps_dec->pv_parse_tu_coeff_data; + pu1_temp += 8; + ps_dec->pv_parse_tu_coeff_data = (void *)pu1_temp; + } + /*--------------------------------------------------------------------*/ + /* Read the IntraPrediction mode for CHROMA */ + /*--------------------------------------------------------------------*/ + u1_intra_chrom_pred_mode = ih264d_parse_chroma_pred_mode_cabac(ps_dec); + COPYTHECONTEXT("intra_chroma_pred_mode", u1_intra_chrom_pred_mode); + p_curr_ctxt->u1_intra_chroma_pred_mode = ps_cur_mb_info->u1_chroma_pred_mode = + u1_intra_chrom_pred_mode; + + /*--------------------------------------------------------------------*/ + /* Read the Coded block pattern */ + /*--------------------------------------------------------------------*/ + u1_cbp = ih264d_parse_ctx_cbp_cabac(ps_dec); + COPYTHECONTEXT("coded_block_pattern", u1_cbp); + ps_cur_mb_info->u1_cbp = u1_cbp; + p_curr_ctxt->u1_cbp = u1_cbp; + + /*--------------------------------------------------------------------*/ + /* Read mb_qp_delta */ + /*--------------------------------------------------------------------*/ + if(ps_cur_mb_info->u1_cbp) + { + ret = ih264d_parse_mb_qp_delta_cabac(ps_dec, &i1_delta_qp); + if(ret != OK) + return ret; + COPYTHECONTEXT("mb_qp_delta", i1_delta_qp); + if(i1_delta_qp != 0) + { + ret = ih264d_update_qp(ps_dec, i1_delta_qp); + if(ret != OK) + return ret; + } + } + else + ps_dec->i1_prev_mb_qp_delta = 0; + p_curr_ctxt->u1_yuv_dc_csbp &= 0xFE; + } + else + { + u1_offset = 1; + ps_cur_mb_info->ps_curmb->u1_mb_type = I_16x16_MB; + p_curr_ctxt->u1_mb_type = CAB_I16x16; + ps_cur_mb_info->u1_tran_form8x8 = 0; + p_curr_ctxt->u1_transform8x8_ctxt = 0; + ps_cur_mb_info->ps_curmb->u1_tran_form8x8 = 0; + /*--------------------------------------------------------------------*/ + /* Read the IntraPrediction mode for CHROMA */ + /*--------------------------------------------------------------------*/ + u1_intra_chrom_pred_mode = ih264d_parse_chroma_pred_mode_cabac(ps_dec); + if(u1_intra_chrom_pred_mode > 3) + return ERROR_CHROMA_PRED_MODE; + + COPYTHECONTEXT("Chroma intra_chroma_pred_mode pred mode", u1_intra_chrom_pred_mode); + p_curr_ctxt->u1_intra_chroma_pred_mode = ps_cur_mb_info->u1_chroma_pred_mode = + u1_intra_chrom_pred_mode; + + /*--------------------------------------------------------------------*/ + /* Read the Coded block pattern */ + /*--------------------------------------------------------------------*/ + u1_cbp = gau1_ih264d_cbp_tab[(u1_mb_type - 1) >> 2]; + ps_cur_mb_info->u1_cbp = u1_cbp; + p_curr_ctxt->u1_cbp = u1_cbp; + + /*--------------------------------------------------------------------*/ + /* Read mb_qp_delta */ + /*--------------------------------------------------------------------*/ + ret = ih264d_parse_mb_qp_delta_cabac(ps_dec, &i1_delta_qp); + if(ret != OK) + return ret; + COPYTHECONTEXT("mb_qp_delta", i1_delta_qp); + if(i1_delta_qp != 0) + { + ret = ih264d_update_qp(ps_dec, i1_delta_qp); + if(ret != OK) + return ret; + } + + { + WORD16 i_scaleFactor; + WORD16* pi2_scale_matrix_ptr; + /*******************************************************************/ + /* for luma DC coefficients the scaling is done during the parsing */ + /* to preserve the precision */ + /*******************************************************************/ + if(ps_dec->s_high_profile.u1_scaling_present) + { + pi2_scale_matrix_ptr = + ps_dec->s_high_profile.i2_scalinglist4x4[0]; + + } + else + { + i_scaleFactor = 16; + pi2_scale_matrix_ptr = &i_scaleFactor; + } + { + ctxt_inc_mb_info_t *ps_top_ctxt = ps_dec->p_top_ctxt_mb_info; + UWORD8 uc_a, uc_b; + UWORD32 u4_ctx_inc; + + INC_SYM_COUNT(&(ps_dec->s_cab_dec_env)); + + /* if MbAddrN not available then CondTermN = 1 */ + uc_b = ((ps_top_ctxt->u1_yuv_dc_csbp) & 0x01); + + /* if MbAddrN not available then CondTermN = 1 */ + uc_a = ((ps_dec->pu1_left_yuv_dc_csbp[0]) & 0x01); + + u4_ctx_inc = (uc_a + (uc_b << 1)); + + { + WORD16 pi2_dc_coef[16]; + tu_sblk4x4_coeff_data_t *ps_tu_4x4 = + (tu_sblk4x4_coeff_data_t *)ps_dec->pv_parse_tu_coeff_data; + WORD16 *pi2_coeff_block = + (WORD16 *)ps_dec->pv_parse_tu_coeff_data; + + p_bin_ctxt = (ps_dec->p_cbf_t[LUMA_DC_CTXCAT]) + u4_ctx_inc; + + u1_dc_block_flag = + ih264d_read_coeff4x4_cabac(ps_bitstrm, + LUMA_DC_CTXCAT, + ps_dec->p_significant_coeff_flag_t[LUMA_DC_CTXCAT], + ps_dec, p_bin_ctxt); + + /* Store coded_block_flag */ + p_curr_ctxt->u1_yuv_dc_csbp &= 0xFE; + p_curr_ctxt->u1_yuv_dc_csbp |= u1_dc_block_flag; + if(u1_dc_block_flag) + { + WORD32 pi4_tmp[16]; + memset(pi2_dc_coef,0,sizeof(pi2_dc_coef)); + ih264d_unpack_coeff4x4_dc_4x4blk(ps_tu_4x4, + pi2_dc_coef, + ps_dec->pu1_inv_scan); + + PROFILE_DISABLE_IQ_IT_RECON() + ps_dec->pf_ihadamard_scaling_4x4(pi2_dc_coef, + pi2_coeff_block, + ps_dec->pu2_quant_scale_y, + (UWORD16 *)pi2_scale_matrix_ptr, + ps_dec->u1_qp_y_div6, + pi4_tmp); + pi2_coeff_block += 16; + ps_dec->pv_parse_tu_coeff_data = (void *)pi2_coeff_block; + SET_BIT(ps_cur_mb_info->u1_yuv_dc_block_flag,0); + } + + } + + } + } + } + + ps_dec->pu1_left_yuv_dc_csbp[0] &= 0x6; + ps_dec->pu1_left_yuv_dc_csbp[0] |= u1_dc_block_flag; + + ih264d_parse_residual4x4_cabac(ps_dec, ps_cur_mb_info, u1_offset); + if(EXCEED_OFFSET(ps_bitstrm)) + return ERROR_EOB_TERMINATE_T; + return OK; +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_parse_islice_data_cavlc */ +/* */ +/* Description : This function parses cabac syntax of a inter slice on */ +/* N MB basis. */ +/* */ +/* Inputs : ps_dec */ +/* sliceparams */ +/* firstMbInSlice */ +/* */ +/* Processing : 1. After parsing syntax for N MBs those N MBs are */ +/* decoded till the end of slice. */ +/* */ +/* Returns : 0 */ +/* */ +/* Issues : <List any issues or problems with this function> */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 24 06 2005 ARNY Draft */ +/* */ +/*****************************************************************************/ +WORD32 ih264d_parse_islice_data_cavlc(dec_struct_t * ps_dec, + dec_slice_params_t * ps_slice, + UWORD16 u2_first_mb_in_slice) +{ + UWORD8 uc_more_data_flag; + UWORD8 u1_num_mbs, u1_mb_idx; + dec_mb_info_t *ps_cur_mb_info; + deblk_mb_t *ps_cur_deblk_mb; + dec_bit_stream_t * const ps_bitstrm = ps_dec->ps_bitstrm; + UWORD32 *pu4_bitstrm_ofst = &ps_bitstrm->u4_ofst; + UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; + UWORD16 i2_pic_wdin_mbs = ps_dec->u2_frm_wd_in_mbs; + WORD16 i2_cur_mb_addr; + UWORD8 u1_mbaff; + UWORD8 u1_num_mbs_next, u1_end_of_row, u1_tfr_n_mb; + WORD32 ret = OK; + + ps_dec->u1_qp = ps_slice->u1_slice_qp; + ih264d_update_qp(ps_dec, 0); + u1_mbaff = ps_slice->u1_mbaff_frame_flag; + + /* initializations */ + u1_mb_idx = ps_dec->u1_mb_idx; + u1_num_mbs = u1_mb_idx; + + uc_more_data_flag = 1; + i2_cur_mb_addr = u2_first_mb_in_slice << u1_mbaff; + + do + { + UWORD8 u1_mb_type; + + ps_dec->pv_prev_mb_parse_tu_coeff_data = ps_dec->pv_parse_tu_coeff_data; + + if(i2_cur_mb_addr > ps_dec->ps_cur_sps->u2_max_mb_addr) + { + break; + } + + ps_cur_mb_info = ps_dec->ps_nmb_info + u1_num_mbs; + ps_dec->u4_num_mbs_cur_nmb = u1_num_mbs; + ps_dec->u4_num_pmbair = (u1_num_mbs >> u1_mbaff); + + ps_cur_mb_info->u1_end_of_slice = 0; + + /***************************************************************/ + /* Get the required information for decoding of MB */ + /* mb_x, mb_y , neighbour availablity, */ + /***************************************************************/ + ps_dec->pf_get_mb_info(ps_dec, i2_cur_mb_addr, ps_cur_mb_info, 0); + + /***************************************************************/ + /* Set the deblocking parameters for this MB */ + /***************************************************************/ + ps_cur_deblk_mb = ps_dec->ps_deblk_mbn + u1_num_mbs; + + if(ps_dec->u4_app_disable_deblk_frm == 0) + ih264d_set_deblocking_parameters(ps_cur_deblk_mb, ps_slice, + ps_dec->u1_mb_ngbr_availablity, + ps_dec->u1_cur_mb_fld_dec_flag); + + ps_cur_deblk_mb->u1_mb_type = ps_cur_deblk_mb->u1_mb_type | D_INTRA_MB; + + /**************************************************************/ + /* Macroblock Layer Begins, Decode the u1_mb_type */ + /**************************************************************/ +//Inlined ih264d_uev + { + UWORD32 u4_bitstream_offset = *pu4_bitstrm_ofst; + UWORD32 u4_word, u4_ldz, u4_temp; + + /***************************************************************/ + /* Find leading zeros in next 32 bits */ + /***************************************************************/ + NEXTBITS_32(u4_word, u4_bitstream_offset, pu4_bitstrm_buf); + u4_ldz = CLZ(u4_word); + /* Flush the ps_bitstrm */ + u4_bitstream_offset += (u4_ldz + 1); + /* Read the suffix from the ps_bitstrm */ + u4_word = 0; + if(u4_ldz) + GETBITS(u4_word, u4_bitstream_offset, pu4_bitstrm_buf, + u4_ldz); + *pu4_bitstrm_ofst = u4_bitstream_offset; + u4_temp = ((1 << u4_ldz) + u4_word - 1); + if(u4_temp > 25) + return ERROR_MB_TYPE; + u1_mb_type = u4_temp; + + } +//Inlined ih264d_uev + ps_cur_mb_info->u1_mb_type = u1_mb_type; + COPYTHECONTEXT("u1_mb_type", u1_mb_type); + + /**************************************************************/ + /* Parse Macroblock data */ + /**************************************************************/ + if(25 == u1_mb_type) + { + /* I_PCM_MB */ + ps_cur_mb_info->ps_curmb->u1_mb_type = I_PCM_MB; + ret = ih264d_parse_ipcm_mb(ps_dec, ps_cur_mb_info, u1_num_mbs); + if(ret != OK) + return ret; + ps_cur_deblk_mb->u1_mb_qp = 0; + } + else + { + ret = ih264d_parse_imb_cavlc(ps_dec, ps_cur_mb_info, u1_num_mbs, u1_mb_type); + if(ret != OK) + return ret; + ps_cur_deblk_mb->u1_mb_qp = ps_dec->u1_qp; + } + + uc_more_data_flag = MORE_RBSP_DATA(ps_bitstrm); + + if(u1_mbaff) + { + ih264d_update_mbaff_left_nnz(ps_dec, ps_cur_mb_info); + if(!uc_more_data_flag && (0 == (i2_cur_mb_addr & 1))) + { + return ERROR_EOB_FLUSHBITS_T; + } + } + /**************************************************************/ + /* Get next Macroblock address */ + /**************************************************************/ + + i2_cur_mb_addr++; + + + /* Store the colocated information */ + { + mv_pred_t *ps_mv_nmb_start = ps_dec->ps_mv_cur + (u1_num_mbs << 4); + + mv_pred_t s_mvPred = + { + { 0, 0, 0, 0 }, + { -1, -1 }, 0, 0}; + ih264d_rep_mv_colz(ps_dec, &s_mvPred, ps_mv_nmb_start, 0, + (UWORD8)(ps_dec->u1_cur_mb_fld_dec_flag << 1), 4, + 4); + } + + /*if num _cores is set to 3,compute bs will be done in another thread*/ + if(ps_dec->u4_num_cores < 3) + { + if(ps_dec->u4_app_disable_deblk_frm == 0) + ps_dec->pf_compute_bs(ps_dec, ps_cur_mb_info, + (UWORD16)(u1_num_mbs >> u1_mbaff)); + } + u1_num_mbs++; + + /****************************************************************/ + /* Check for End Of Row */ + /****************************************************************/ + u1_num_mbs_next = i2_pic_wdin_mbs - ps_dec->u2_mbx - 1; + u1_end_of_row = (!u1_num_mbs_next) && (!(u1_mbaff && (u1_num_mbs & 0x01))); + u1_tfr_n_mb = (u1_num_mbs == ps_dec->u1_recon_mb_grp) || u1_end_of_row + || (!uc_more_data_flag); + ps_cur_mb_info->u1_end_of_slice = (!uc_more_data_flag); + + /*H264_DEC_DEBUG_PRINT("Pic: %d Mb_X=%d Mb_Y=%d", + ps_slice->i4_poc >> ps_slice->u1_field_pic_flag, + ps_dec->u2_mbx,ps_dec->u2_mby + (1 - ps_cur_mb_info->u1_topmb)); + H264_DEC_DEBUG_PRINT("u1_tfr_n_mb || (!uc_more_data_flag): %d", u1_tfr_n_mb || (!uc_more_data_flag));*/ + if(u1_tfr_n_mb || (!uc_more_data_flag)) + { + + if(ps_dec->u1_separate_parse) + { + ih264d_parse_tfr_nmb(ps_dec, u1_mb_idx, u1_num_mbs, + u1_num_mbs_next, u1_tfr_n_mb, u1_end_of_row); + ps_dec->ps_nmb_info += u1_num_mbs; + } + else + { + ih264d_decode_recon_tfr_nmb(ps_dec, u1_mb_idx, u1_num_mbs, + u1_num_mbs_next, u1_tfr_n_mb, + u1_end_of_row); + } + ps_dec->u2_total_mbs_coded += u1_num_mbs; + if(u1_tfr_n_mb) + u1_num_mbs = 0; + u1_mb_idx = u1_num_mbs; + ps_dec->u1_mb_idx = u1_num_mbs; + + } + } + while(uc_more_data_flag); + + ps_dec->u4_num_mbs_cur_nmb = 0; + ps_dec->ps_cur_slice->u4_mbs_in_slice = i2_cur_mb_addr + + - (u2_first_mb_in_slice << u1_mbaff); + + return ret; +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_parse_islice_data_cabac */ +/* */ +/* Description : This function parses cabac syntax of a inter slice on */ +/* N MB basis. */ +/* */ +/* Inputs : ps_dec */ +/* sliceparams */ +/* firstMbInSlice */ +/* */ +/* Processing : 1. After parsing syntax for N MBs those N MBs are */ +/* decoded till the end of slice. */ +/* */ +/* Returns : 0 */ +/* */ +/* Issues : <List any issues or problems with this function> */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 24 06 2005 ARNY Draft */ +/* */ +/*****************************************************************************/ +WORD32 ih264d_parse_islice_data_cabac(dec_struct_t * ps_dec, + dec_slice_params_t * ps_slice, + UWORD16 u2_first_mb_in_slice) +{ + UWORD8 uc_more_data_flag; + UWORD8 u1_num_mbs, u1_mb_idx; + dec_mb_info_t *ps_cur_mb_info; + deblk_mb_t *ps_cur_deblk_mb; + + dec_bit_stream_t * const ps_bitstrm = ps_dec->ps_bitstrm; + UWORD16 i2_pic_wdin_mbs = ps_dec->u2_frm_wd_in_mbs; + WORD16 i2_cur_mb_addr; + UWORD8 u1_mbaff; + UWORD8 u1_num_mbs_next, u1_end_of_row, u1_tfr_n_mb; + WORD32 ret = OK; + + ps_dec->u1_qp = ps_slice->u1_slice_qp; + ih264d_update_qp(ps_dec, 0); + u1_mbaff = ps_slice->u1_mbaff_frame_flag; + + if(ps_bitstrm->u4_ofst & 0x07) + { + ps_bitstrm->u4_ofst += 8; + ps_bitstrm->u4_ofst &= 0xFFFFFFF8; + } + ret = ih264d_init_cabac_dec_envirnoment(&(ps_dec->s_cab_dec_env), ps_bitstrm); + if(ret != OK) + return ret; + ih264d_init_cabac_contexts(I_SLICE, ps_dec); + + ps_dec->i1_prev_mb_qp_delta = 0; + + /* initializations */ + u1_mb_idx = ps_dec->u1_mb_idx; + u1_num_mbs = u1_mb_idx; + + uc_more_data_flag = 1; + i2_cur_mb_addr = u2_first_mb_in_slice << u1_mbaff; + do + { + UWORD16 u2_mbx; + + ps_dec->pv_prev_mb_parse_tu_coeff_data = ps_dec->pv_parse_tu_coeff_data; + + if(i2_cur_mb_addr > ps_dec->ps_cur_sps->u2_max_mb_addr) + { + break; + } + + { + UWORD8 u1_mb_type; + + ps_cur_mb_info = ps_dec->ps_nmb_info + u1_num_mbs; + ps_dec->u4_num_mbs_cur_nmb = u1_num_mbs; + ps_dec->u4_num_pmbair = (u1_num_mbs >> u1_mbaff); + + ps_cur_mb_info->u1_end_of_slice = 0; + + /***************************************************************/ + /* Get the required information for decoding of MB */ + /* mb_x, mb_y , neighbour availablity, */ + /***************************************************************/ + ps_dec->pf_get_mb_info(ps_dec, i2_cur_mb_addr, ps_cur_mb_info, 0); + u2_mbx = ps_dec->u2_mbx; + + /*********************************************************************/ + /* initialize u1_tran_form8x8 to zero to aviod uninitialized accesses */ + /*********************************************************************/ + ps_cur_mb_info->u1_tran_form8x8 = 0; + ps_cur_mb_info->ps_curmb->u1_tran_form8x8 = 0; + + /***************************************************************/ + /* Set the deblocking parameters for this MB */ + /***************************************************************/ + ps_cur_deblk_mb = ps_dec->ps_deblk_mbn + u1_num_mbs; + if(ps_dec->u4_app_disable_deblk_frm == 0) + ih264d_set_deblocking_parameters( + ps_cur_deblk_mb, ps_slice, + ps_dec->u1_mb_ngbr_availablity, + ps_dec->u1_cur_mb_fld_dec_flag); + + ps_cur_deblk_mb->u1_mb_type = ps_cur_deblk_mb->u1_mb_type + | D_INTRA_MB; + + /* Macroblock Layer Begins */ + /* Decode the u1_mb_type */ + u1_mb_type = ih264d_parse_mb_type_intra_cabac(0, ps_dec); + if(u1_mb_type > 25) + return ERROR_MB_TYPE; + ps_cur_mb_info->u1_mb_type = u1_mb_type; + COPYTHECONTEXT("u1_mb_type", u1_mb_type); + + /* Parse Macroblock Data */ + if(25 == u1_mb_type) + { + /* I_PCM_MB */ + ps_cur_mb_info->ps_curmb->u1_mb_type = I_PCM_MB; + ret = ih264d_parse_ipcm_mb(ps_dec, ps_cur_mb_info, u1_num_mbs); + if(ret != OK) + return ret; + ps_cur_deblk_mb->u1_mb_qp = 0; + } + else + { + ret = ih264d_parse_imb_cabac(ps_dec, ps_cur_mb_info, u1_mb_type); + if(ret != OK) + return ret; + ps_cur_deblk_mb->u1_mb_qp = ps_dec->u1_qp; + } + + if(u1_mbaff) + { + ih264d_update_mbaff_left_nnz(ps_dec, ps_cur_mb_info); + } + + + if(ps_cur_mb_info->u1_topmb && u1_mbaff) + uc_more_data_flag = 1; + else + { + uc_more_data_flag = ih264d_decode_terminate(&ps_dec->s_cab_dec_env, + ps_bitstrm); + uc_more_data_flag = !uc_more_data_flag; + COPYTHECONTEXT("Decode Sliceterm",!uc_more_data_flag); + } + + if(u1_mbaff) + { + if(!uc_more_data_flag && (0 == (i2_cur_mb_addr & 1))) + { + return ERROR_EOB_FLUSHBITS_T; + } + } + /* Next macroblock information */ + i2_cur_mb_addr++; + /* Store the colocated information */ + { + + mv_pred_t *ps_mv_nmb_start = ps_dec->ps_mv_cur + (u1_num_mbs << 4); + mv_pred_t s_mvPred = + { + { 0, 0, 0, 0 }, + { -1, -1 }, 0, 0}; + ih264d_rep_mv_colz( + ps_dec, &s_mvPred, ps_mv_nmb_start, 0, + (UWORD8)(ps_dec->u1_cur_mb_fld_dec_flag << 1), + 4, 4); + } + /*if num _cores is set to 3,compute bs will be done in another thread*/ + if(ps_dec->u4_num_cores < 3) + { + if(ps_dec->u4_app_disable_deblk_frm == 0) + ps_dec->pf_compute_bs(ps_dec, ps_cur_mb_info, + (UWORD16)(u1_num_mbs >> u1_mbaff)); + } + u1_num_mbs++; + + } + + /****************************************************************/ + /* Check for End Of Row */ + /****************************************************************/ + u1_num_mbs_next = i2_pic_wdin_mbs - u2_mbx - 1; + u1_end_of_row = (!u1_num_mbs_next) && (!(u1_mbaff && (u1_num_mbs & 0x01))); + u1_tfr_n_mb = (u1_num_mbs == ps_dec->u1_recon_mb_grp) || u1_end_of_row + || (!uc_more_data_flag); + ps_cur_mb_info->u1_end_of_slice = (!uc_more_data_flag); + + if(u1_tfr_n_mb || (!uc_more_data_flag)) + { + + + if(ps_dec->u1_separate_parse) + { + ih264d_parse_tfr_nmb(ps_dec, u1_mb_idx, u1_num_mbs, + u1_num_mbs_next, u1_tfr_n_mb, u1_end_of_row); + ps_dec->ps_nmb_info += u1_num_mbs; + } + else + { + ih264d_decode_recon_tfr_nmb(ps_dec, u1_mb_idx, u1_num_mbs, + u1_num_mbs_next, u1_tfr_n_mb, + u1_end_of_row); + } + ps_dec->u2_total_mbs_coded += u1_num_mbs; + if(u1_tfr_n_mb) + u1_num_mbs = 0; + u1_mb_idx = u1_num_mbs; + ps_dec->u1_mb_idx = u1_num_mbs; + + } + } + while(uc_more_data_flag); + + ps_dec->u4_num_mbs_cur_nmb = 0; + ps_dec->ps_cur_slice->u4_mbs_in_slice = i2_cur_mb_addr + + - (u2_first_mb_in_slice << u1_mbaff); + + return ret; +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_parse_ipcm_mb */ +/* */ +/* Description : This function decodes the pixel values of I_PCM Mb. */ +/* */ +/* Inputs : ps_dec, ps_cur_mb_info and mb number */ +/* */ +/* Description : This function reads the luma and chroma pixels directly */ +/* from the bitstream when the mbtype is I_PCM and stores */ +/* them in recon buffer. If the entropy coding mode is */ +/* cabac, decoding engine is re-initialized. The nnzs and */ +/* cabac contexts are appropriately modified. */ +/* Returns : void */ +/* */ +/* Revision History: */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 13 07 2002 Jay */ +/* */ +/*****************************************************************************/ + +WORD32 ih264d_parse_ipcm_mb(dec_struct_t * ps_dec, + dec_mb_info_t *ps_cur_mb_info, + UWORD8 u1_mbNum) +{ + dec_bit_stream_t * const ps_bitstrm = ps_dec->ps_bitstrm; + UWORD8 u1_mbaff = ps_dec->ps_cur_slice->u1_mbaff_frame_flag; + UWORD8 *pu1_y, *pu1_u, *pu1_v; + WORD32 ret; + + UWORD32 u4_rec_width_y, u4_rec_width_uv; + UWORD32 u1_num_mb_pair; + UWORD8 u1_x, u1_y; + /* CHANGED CODE */ + tfr_ctxt_t *ps_frame_buf; + UWORD8 u1_mb_field_decoding_flag; + UWORD32 *pu4_buf; + UWORD8 *pu1_buf; + /* CHANGED CODE */ + + if(ps_dec->u1_separate_parse) + { + ps_frame_buf = &ps_dec->s_tran_addrecon_parse; + } + else + { + ps_frame_buf = &ps_dec->s_tran_addrecon; + } + /* align bistream to byte boundary. */ + /* pcm_alignment_zero_bit discarded */ + /* For XX GotoByteBoundary */ + if(ps_bitstrm->u4_ofst & 0x07) + { + ps_bitstrm->u4_ofst += 8; + ps_bitstrm->u4_ofst &= 0xFFFFFFF8; + } + + /* Store left Nnz as 16 for each 4x4 blk */ + + pu1_buf = ps_dec->pu1_left_nnz_y; + pu4_buf = (UWORD32 *)pu1_buf; + *pu4_buf = 0x10101010; + pu1_buf = ps_cur_mb_info->ps_curmb->pu1_nnz_y; + pu4_buf = (UWORD32 *)pu1_buf; + *pu4_buf = 0x10101010; + pu1_buf = ps_cur_mb_info->ps_curmb->pu1_nnz_uv; + pu4_buf = (UWORD32 *)pu1_buf; + *pu4_buf = 0x10101010; + pu1_buf = ps_dec->pu1_left_nnz_uv; + pu4_buf = (UWORD32 *)pu1_buf; + *pu4_buf = 0x10101010; + ps_cur_mb_info->u1_cbp = 0xff; + + ps_dec->i1_prev_mb_qp_delta = 0; + /* Get neighbour MB's */ + u1_num_mb_pair = (u1_mbNum >> u1_mbaff); + + /*****************************************************************************/ + /* calculate the RECON buffer YUV pointers for the PCM data */ + /*****************************************************************************/ + /* CHANGED CODE */ + u1_mb_field_decoding_flag = ps_cur_mb_info->u1_mb_field_decodingflag; + pu1_y = ps_frame_buf->pu1_dest_y + (u1_num_mb_pair << 4); + pu1_u = ps_frame_buf->pu1_dest_u + (u1_num_mb_pair << 4); + pu1_v = pu1_u + 1; + + u4_rec_width_y = ps_dec->u2_frm_wd_y << u1_mb_field_decoding_flag; + u4_rec_width_uv = ps_dec->u2_frm_wd_uv << u1_mb_field_decoding_flag; + /* CHANGED CODE */ + + if(u1_mbaff) + { + UWORD8 u1_top_mb; + + u1_top_mb = ps_cur_mb_info->u1_topmb; + + if(u1_top_mb == 0) + { + pu1_y += (u1_mb_field_decoding_flag ? + (u4_rec_width_y >> 1) : (u4_rec_width_y << 4)); + pu1_u += (u1_mb_field_decoding_flag ? + (u4_rec_width_uv) : (u4_rec_width_uv << 4)); + pu1_v = pu1_u + 1; + } + } + + /* Read Luma samples */ + for(u1_y = 0; u1_y < 16; u1_y++) + { + for(u1_x = 0; u1_x < 16; u1_x++) + pu1_y[u1_x] = ih264d_get_bits_h264(ps_bitstrm, 8); + + pu1_y += u4_rec_width_y; + } + + /* Read Chroma samples */ + for(u1_y = 0; u1_y < 8; u1_y++) + { + for(u1_x = 0; u1_x < 8; u1_x++) + pu1_u[u1_x * YUV420SP_FACTOR] = ih264d_get_bits_h264(ps_bitstrm, 8); + + pu1_u += u4_rec_width_uv; + } + + for(u1_y = 0; u1_y < 8; u1_y++) + { + for(u1_x = 0; u1_x < 8; u1_x++) + pu1_v[u1_x * YUV420SP_FACTOR] = ih264d_get_bits_h264(ps_bitstrm, 8); + + pu1_v += u4_rec_width_uv; + } + + if(CABAC == ps_dec->ps_cur_pps->u1_entropy_coding_mode) + { + UWORD32 *pu4_buf; + UWORD8 *pu1_buf; + ctxt_inc_mb_info_t *p_curr_ctxt = ps_dec->ps_curr_ctxt_mb_info; + /* Re-initialize the cabac decoding engine. */ + ret = ih264d_init_cabac_dec_envirnoment(&(ps_dec->s_cab_dec_env), ps_bitstrm); + if(ret != OK) + return ret; + /* update the cabac contetxs */ + p_curr_ctxt->u1_mb_type = CAB_I_PCM; + p_curr_ctxt->u1_cbp = 47; + p_curr_ctxt->u1_intra_chroma_pred_mode = 0; + p_curr_ctxt->u1_transform8x8_ctxt = 0; + ps_cur_mb_info->ps_curmb->u1_tran_form8x8 = 0; + + pu1_buf = ps_dec->pu1_left_nnz_y; + pu4_buf = (UWORD32 *)pu1_buf; + *pu4_buf = 0x01010101; + + pu1_buf = ps_cur_mb_info->ps_curmb->pu1_nnz_y; + pu4_buf = (UWORD32 *)pu1_buf; + *pu4_buf = 0x01010101; + + pu1_buf = ps_cur_mb_info->ps_curmb->pu1_nnz_uv; + pu4_buf = (UWORD32 *)pu1_buf; + *pu4_buf = 0x01010101; + + pu1_buf = ps_dec->pu1_left_nnz_uv; + pu4_buf = (UWORD32 *)pu1_buf; + *pu4_buf = 0x01010101; + + p_curr_ctxt->u1_yuv_dc_csbp = 0x7; + ps_dec->pu1_left_yuv_dc_csbp[0] = 0x7; + if(ps_dec->ps_cur_slice->u1_slice_type != I_SLICE) + { + + MEMSET_16BYTES(&ps_dec->pu1_left_mv_ctxt_inc[0][0], 0); + memset(ps_dec->pi1_left_ref_idx_ctxt_inc, 0, 4); + MEMSET_16BYTES(p_curr_ctxt->u1_mv, 0); + memset(p_curr_ctxt->i1_ref_idx, 0, 4); + + } + } + return OK; +} + +/*! + ************************************************************************** + * \if Function name : ih264d_decode_islice \endif + * + * \brief + * Decodes an I Slice + * + * + * \return + * 0 on Success and Error code otherwise + ************************************************************************** + */ +WORD32 ih264d_parse_islice(dec_struct_t *ps_dec, + UWORD16 u2_first_mb_in_slice) +{ + dec_pic_params_t * ps_pps = ps_dec->ps_cur_pps; + dec_slice_params_t * ps_slice = ps_dec->ps_cur_slice; + UWORD32 *pu4_bitstrm_buf = ps_dec->ps_bitstrm->pu4_buffer; + UWORD32 *pu4_bitstrm_ofst = &ps_dec->ps_bitstrm->u4_ofst; + UWORD32 u4_temp; + WORD32 i_temp; + WORD32 ret; + + /*--------------------------------------------------------------------*/ + /* Read remaining contents of the slice header */ + /*--------------------------------------------------------------------*/ + /* dec_ref_pic_marking function */ + /* G050 */ + if(ps_slice->u1_nal_ref_idc != 0) + { + if(!ps_dec->ps_dpb_cmds->u1_dpb_commands_read) + { + i_temp = ih264d_read_mmco_commands(ps_dec); + if (i_temp < 0) + { + return ERROR_DBP_MANAGER_T; + } + ps_dec->u4_bitoffset = i_temp; + } + else + ps_dec->ps_bitstrm->u4_ofst += ps_dec->u4_bitoffset; + } + /* G050 */ + + /* Read slice_qp_delta */ + WORD64 i8_temp = (WORD64)ps_pps->u1_pic_init_qp + + ih264d_sev(pu4_bitstrm_ofst, pu4_bitstrm_buf); + if((i8_temp < MIN_H264_QP) || (i8_temp > MAX_H264_QP)) + return ERROR_INV_RANGE_QP_T; + ps_slice->u1_slice_qp = i8_temp; + COPYTHECONTEXT("SH: slice_qp_delta", + ps_slice->u1_slice_qp - ps_pps->u1_pic_init_qp); + + if(ps_pps->u1_deblocking_filter_parameters_present_flag == 1) + { + u4_temp = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); + COPYTHECONTEXT("SH: disable_deblocking_filter_idc", u4_temp); + + if(u4_temp > SLICE_BOUNDARY_DBLK_DISABLED) + { + return ERROR_INV_SLICE_HDR_T; + } + ps_slice->u1_disable_dblk_filter_idc = u4_temp; + if(u4_temp != 1) + { + i_temp = ih264d_sev(pu4_bitstrm_ofst, pu4_bitstrm_buf) + << 1; + if((MIN_DBLK_FIL_OFF > i_temp) || (i_temp > MAX_DBLK_FIL_OFF)) + { + return ERROR_INV_SLICE_HDR_T; + } + ps_slice->i1_slice_alpha_c0_offset = i_temp; + COPYTHECONTEXT("SH: slice_alpha_c0_offset_div2", + ps_slice->i1_slice_alpha_c0_offset >> 1); + + i_temp = ih264d_sev(pu4_bitstrm_ofst, pu4_bitstrm_buf) + << 1; + if((MIN_DBLK_FIL_OFF > i_temp) || (i_temp > MAX_DBLK_FIL_OFF)) + { + return ERROR_INV_SLICE_HDR_T; + } + ps_slice->i1_slice_beta_offset = i_temp; + COPYTHECONTEXT("SH: slice_beta_offset_div2", + ps_slice->i1_slice_beta_offset >> 1); + + } + else + { + ps_slice->i1_slice_alpha_c0_offset = 0; + ps_slice->i1_slice_beta_offset = 0; + } + } + else + { + ps_slice->u1_disable_dblk_filter_idc = 0; + ps_slice->i1_slice_alpha_c0_offset = 0; + ps_slice->i1_slice_beta_offset = 0; + } + + /* Initialization to check if number of motion vector per 2 Mbs */ + /* are exceeding the range or not */ + ps_dec->u2_mv_2mb[0] = 0; + ps_dec->u2_mv_2mb[1] = 0; + + + /*set slice header cone to 2 ,to indicate correct header*/ + ps_dec->u1_slice_header_done = 2; + + if(ps_pps->u1_entropy_coding_mode) + { + SWITCHOFFTRACE; SWITCHONTRACECABAC; + if(ps_dec->ps_cur_slice->u1_mbaff_frame_flag) + { + ps_dec->pf_get_mb_info = ih264d_get_mb_info_cabac_mbaff; + } + else + ps_dec->pf_get_mb_info = ih264d_get_mb_info_cabac_nonmbaff; + + ret = ih264d_parse_islice_data_cabac(ps_dec, ps_slice, + u2_first_mb_in_slice); + if(ret != OK) + return ret; + SWITCHONTRACE; SWITCHOFFTRACECABAC; + } + else + { + if(ps_dec->ps_cur_slice->u1_mbaff_frame_flag) + { + ps_dec->pf_get_mb_info = ih264d_get_mb_info_cavlc_mbaff; + } + else + ps_dec->pf_get_mb_info = ih264d_get_mb_info_cavlc_nonmbaff; + ret = ih264d_parse_islice_data_cavlc(ps_dec, ps_slice, + u2_first_mb_in_slice); + if(ret != OK) + return ret; + } + + return OK; +} diff --git a/dependencies/ih264d/decoder/ih264d_parse_islice.h b/dependencies/ih264d/decoder/ih264d_parse_islice.h new file mode 100644 index 00000000..6a43d7b3 --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_parse_islice.h @@ -0,0 +1,113 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ + +/*! + ************************************************************************** + * \file ih264d_parse_islice.h + * + * \brief + * Contains routines that decode a I slice type + * + * Detailed_description + * + * \date + * 07/07/2003 + * + * \author NS + ************************************************************************** + */ + +#ifndef _IH264D_PARSE_ISLICE_H_ +#define _IH264D_PARSE_ISLICE_H_ + +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264d_tables.h" + +WORD32 ih264d_parse_residual4x4_cavlc(dec_struct_t * ps_dec, + dec_mb_info_t *ps_cur_mb_info, + UWORD8 u1_offset); +WORD32 ih264d_parse_residual4x4_cabac(dec_struct_t * ps_dec, + dec_mb_info_t *ps_cur_mb_info, + UWORD8 u1_offset); +WORD32 ih264d_parse_imb_cavlc(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info, + UWORD8 u1_mb_num, + UWORD8 u1_mb_type); +WORD32 ih264d_parse_imb_cabac(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info, + UWORD8 u1_mb_type); + +WORD32 ih264d_parse_islice_data_cavlc(dec_struct_t * ps_dec, + dec_slice_params_t * ps_slice, + UWORD16 u2_first_mb_in_slice); +WORD32 ih264d_parse_islice_data_cabac(dec_struct_t * ps_dec, + dec_slice_params_t * ps_slice, + UWORD16 u2_first_mb_in_slice); +WORD32 ih264d_parse_pmb_cavlc(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info, + UWORD8 u1_mb_num, + UWORD8 u1_num_mbsNby2); +WORD32 ih264d_parse_pmb_cabac(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info, + UWORD8 u1_mb_num, + UWORD8 u1_num_mbsNby2); + +WORD32 ih264d_parse_bmb_non_direct_cavlc(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info, + UWORD8 u1_mb_num, + UWORD8 u1_mbNumModNBy2); + +WORD32 ih264d_parse_bmb_non_direct_cabac(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info, + UWORD8 u1_mb_num, + UWORD8 u1_mbNumModNBy2); + +WORD32 ih264d_parse_bmb_cavlc(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info, + UWORD8 u1_mb_num, + UWORD8 u1_num_mbsNby2); + +WORD32 ih264d_parse_bmb_cabac(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info, + UWORD8 u1_mb_num, + UWORD8 u1_num_mbsNby2); + +WORD32 ih264d_parse_inter_slice_data_cavlc(dec_struct_t * ps_dec, + dec_slice_params_t * ps_slice, + UWORD16 u2_first_mb_in_slice); + +WORD32 ih264d_parse_inter_slice_data_cabac(dec_struct_t * ps_dec, + dec_slice_params_t * ps_slice, + UWORD16 u2_first_mb_in_slice); + +WORD32 ParseBMb(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info, + UWORD8 u1_mb_num, + UWORD8 u1_num_mbsNby2); + +WORD32 ih264d_parse_ipcm_mb(dec_struct_t * ps_dec, + dec_mb_info_t *ps_cur_mb_info, + UWORD8 u1_mbNum); +WORD32 ih264d_parse_islice(dec_struct_t *ps_dec, + UWORD16 u2_first_mb_in_slice); + +#endif /* _IH264D_PARSE_ISLICE_H_ */ diff --git a/dependencies/ih264d/decoder/ih264d_parse_mb_header.c b/dependencies/ih264d/decoder/ih264d_parse_mb_header.c new file mode 100644 index 00000000..9a6a1f9a --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_parse_mb_header.c @@ -0,0 +1,1356 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/*! + *************************************************************************** + * \file ih264d_parse_mb_header.c + * + * \brief + * This file contains context identifier encoding routines. + * + * \date + * 04/02/2003 + * + * \author NS + *************************************************************************** + */ +#include <string.h> +#include "ih264d_structs.h" +#include "ih264d_bitstrm.h" +#include "ih264d_cabac.h" +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264d_defs.h" +#include "ih264d_error_handler.h" +#include "ih264d_tables.h" +#include "ih264d_debug.h" +#include "ih264d_defs.h" +#include "ih264d_defs.h" +#include "ih264d_mb_utils.h" +#include "ih264d_parse_mb_header.h" +#include "ih264d_defs.h" + +/*! < CtxtInc index 0 - CtxMbTypeI, CtxMbTypeSISuffix + index 1 - CtxMbTypePSuffix, CtxMbTypeBSuffix + */ + + + +/*! + ************************************************************************** + * \if Function name : ih264d_parse_mb_type_intra_cabac \endif + * + * \brief + * This function decodes MB type using CABAC entropy coding mode. + * + * \return + * MBType. + * + ************************************************************************** + */ +UWORD8 ih264d_parse_mb_type_intra_cabac(UWORD8 u1_inter, + struct _DecStruct * ps_dec) +{ + decoding_envirnoment_t * ps_cab_env = &ps_dec->s_cab_dec_env; + dec_bit_stream_t * ps_bitstrm = ps_dec->ps_bitstrm; + ctxt_inc_mb_info_t * ps_left_ctxt = ps_dec->p_left_ctxt_mb_info; + ctxt_inc_mb_info_t * ps_top_ctxt = ps_dec->p_top_ctxt_mb_info; + bin_ctxt_model_t *ps_mb_bin_ctxt = ps_dec->p_mb_type_t; + WORD8 u1_mb_type, u1_bin; + UWORD32 u4_cxt_inc; + + u4_cxt_inc = 0; + if(!u1_inter) + { + if(ps_left_ctxt != ps_dec->ps_def_ctxt_mb_info) + u4_cxt_inc += ((ps_left_ctxt->u1_mb_type != CAB_I4x4) ? 1 : 0); + if(ps_top_ctxt != ps_dec->ps_def_ctxt_mb_info) + u4_cxt_inc += ((ps_top_ctxt->u1_mb_type != CAB_I4x4) ? 1 : 0); + } + else + { + ps_mb_bin_ctxt = ps_mb_bin_ctxt + 3 + (ps_dec->u1_B << 1); + } + + /* b0 */ + u1_mb_type = (UWORD8)ih264d_decode_bin(u4_cxt_inc, ps_mb_bin_ctxt, ps_bitstrm, + ps_cab_env); + if(u1_mb_type) + { + /* I16x16 or I_PCM mode */ + /* b1 */ + u1_bin = ih264d_decode_terminate(ps_cab_env, ps_bitstrm); + if(u1_bin == 0) + { + /* I16x16 mode */ + /* Read b2 and b3 */ + u4_cxt_inc = (u1_inter) ? 0x021 : 0x043; + + u1_bin = ih264d_decode_bins(2, u4_cxt_inc, ps_mb_bin_ctxt, ps_bitstrm, + ps_cab_env); + + if(u1_bin & 0x01) + u1_mb_type += 4; + + if(u1_bin & 0x02) + u1_mb_type += 12; + + if(u1_bin & 0x01) + { + /* since b3=1, Read three bins */ + u4_cxt_inc = (u1_inter) ? 0x0332 : 0x0765; + u1_bin = (UWORD8)ih264d_decode_bins(3, u4_cxt_inc, ps_mb_bin_ctxt, + ps_bitstrm, ps_cab_env); + + } + else + { + /* Read two bins */ + u4_cxt_inc = (u1_inter) ? 0x033 : 0x076; + u1_bin = (UWORD8)ih264d_decode_bins(2, u4_cxt_inc, ps_mb_bin_ctxt, + ps_bitstrm, ps_cab_env); + } + u1_mb_type += u1_bin; + } + else + { + /* I_PCM mode */ + /* b1=1 */ + u1_mb_type = 25; + } + } + return (u1_mb_type); +} + +/*! + ************************************************************************** + * \if Function name : ih264d_parse_mb_type_cabac \endif + * + * \brief + * This function decodes MB type using CABAC entropy coding mode. + * + * \return + * MBType. + * + ************************************************************************** + */ +UWORD32 ih264d_parse_mb_type_cabac(struct _DecStruct * ps_dec) +{ + const UWORD8 uc_slice_type = ps_dec->ps_cur_slice->u1_slice_type; + decoding_envirnoment_t *ps_cab_env = &ps_dec->s_cab_dec_env; + dec_bit_stream_t *ps_bitstrm = ps_dec->ps_bitstrm; + ctxt_inc_mb_info_t *ps_left_ctxt = ps_dec->p_left_ctxt_mb_info; + ctxt_inc_mb_info_t *ps_top_ctxt = ps_dec->p_top_ctxt_mb_info; + WORD8 c_ctxt_inc; + bin_ctxt_model_t *ps_mb_bin_ctxt = ps_dec->p_mb_type_t; + WORD8 u1_mb_type = 0, u1_bin; + UWORD32 u4_cxt_inc; + + INC_SYM_COUNT(ps_cab_env); + + c_ctxt_inc = 0; + + if(uc_slice_type == SI_SLICE) + { + /* b0 */ + if(ps_left_ctxt != ps_dec->ps_def_ctxt_mb_info) + c_ctxt_inc += ((ps_left_ctxt->u1_mb_type != CAB_SI4x4) ? 1 : 0); + if(ps_top_ctxt != ps_dec->ps_def_ctxt_mb_info) + c_ctxt_inc += ((ps_top_ctxt->u1_mb_type != CAB_SI4x4) ? 1 : 0); + + u4_cxt_inc = c_ctxt_inc; + u1_bin = (UWORD8)ih264d_decode_bin(u4_cxt_inc, ps_mb_bin_ctxt, ps_bitstrm, + ps_cab_env); + if(u1_bin == 0) + { + /* SI MB */ + u1_mb_type = 0; + } + else + { + u1_mb_type = 1 + ih264d_parse_mb_type_intra_cabac(0, ps_dec); + } + } + else if(uc_slice_type == P_SLICE) + { + /* P Slice */ + /* b0 */ + u4_cxt_inc = 0; + u1_bin = (UWORD8)ih264d_decode_bin(u4_cxt_inc, ps_mb_bin_ctxt, ps_bitstrm, + ps_cab_env); + if(!u1_bin) + { + /* Inter MB types */ + /* b1 */ + u4_cxt_inc = 0x01; + u1_bin = (UWORD8)ih264d_decode_bin(u4_cxt_inc, ps_mb_bin_ctxt, + ps_bitstrm, ps_cab_env); + /* b2 */ + u4_cxt_inc = u1_bin + 2; + u1_mb_type = (UWORD8)ih264d_decode_bin(u4_cxt_inc, ps_mb_bin_ctxt, + ps_bitstrm, ps_cab_env); + u1_mb_type = (u1_bin << 1) + u1_mb_type; + if(u1_mb_type) + u1_mb_type = 4 - u1_mb_type; + } + else + { + /* Intra Prefix 1 found */ + /* Intra MB type */ + u1_mb_type = 5 + ih264d_parse_mb_type_intra_cabac(1, ps_dec); + } + } + else if(uc_slice_type == B_SLICE) + { + WORD8 a, b; + /* B Slice */ + /* b0 */ + /* a = b = 0, if B slice and MB is a SKIP or B_DIRECT16x16 */ + a = 0; + b = 0; + u1_mb_type = 0; + if(ps_left_ctxt != ps_dec->ps_def_ctxt_mb_info) + a = ((ps_left_ctxt->u1_mb_type & CAB_BD16x16_MASK) != CAB_BD16x16); + if(ps_top_ctxt != ps_dec->ps_def_ctxt_mb_info) + b = ((ps_top_ctxt->u1_mb_type & CAB_BD16x16_MASK) != CAB_BD16x16); + + u4_cxt_inc = a + b; + + u1_bin = (UWORD8)ih264d_decode_bin(u4_cxt_inc, ps_mb_bin_ctxt, ps_bitstrm, + ps_cab_env); + + if(u1_bin) + { + + /* b1 */ + u4_cxt_inc = 0x03; + u1_bin = (UWORD8)ih264d_decode_bin(u4_cxt_inc, ps_mb_bin_ctxt, + ps_bitstrm, ps_cab_env); + + if(!u1_bin) + { + /* b2 */ + u4_cxt_inc = 0x05; + u1_bin = (UWORD8)ih264d_decode_bin(u4_cxt_inc, ps_mb_bin_ctxt, + ps_bitstrm, ps_cab_env); + + u1_mb_type = u1_bin + 1; + } + else + { + u1_mb_type = 3; + /* b2 */ + u4_cxt_inc = 0x04; + u1_bin = (UWORD8)ih264d_decode_bin(u4_cxt_inc, ps_mb_bin_ctxt, + ps_bitstrm, ps_cab_env); + + if(u1_bin) + { + u1_mb_type += 8; + /* b3 */ + u4_cxt_inc = 0x05; + u1_bin = (UWORD8)ih264d_decode_bin(u4_cxt_inc, ps_mb_bin_ctxt, + ps_bitstrm, ps_cab_env); + + if(!u1_bin) + { + u1_mb_type++; + /* b4, b5, b6 */ + u4_cxt_inc = 0x0555; + u1_bin = (UWORD8)ih264d_decode_bins(3, u4_cxt_inc, + ps_mb_bin_ctxt, + ps_bitstrm, + ps_cab_env); + + + + u1_mb_type += u1_bin; + } + else + { + /* b4 */ + u4_cxt_inc = 0x05; + u1_bin = (UWORD8)ih264d_decode_bin(u4_cxt_inc, + ps_mb_bin_ctxt, + ps_bitstrm, + ps_cab_env); + + if(u1_bin) + { + /* b5 */ + u1_bin = (UWORD8)ih264d_decode_bin(u4_cxt_inc, + ps_mb_bin_ctxt, + ps_bitstrm, + ps_cab_env); + + u1_mb_type += (u1_bin ? 11 : 0); + } + else + { + u1_mb_type = 20; + /* b5 */ + u1_bin = (UWORD8)ih264d_decode_bin(u4_cxt_inc, + ps_mb_bin_ctxt, + ps_bitstrm, + ps_cab_env); + + if(!u1_bin) + { + /* b6 */ + u1_bin = (UWORD8)ih264d_decode_bin(u4_cxt_inc, + ps_mb_bin_ctxt, + ps_bitstrm, + ps_cab_env); + + u1_mb_type += u1_bin; + } + else + { + /* Intra Prefix 111101 found */ + /* Intra MB type */ + u1_mb_type = + 23 + + ih264d_parse_mb_type_intra_cabac( + 1, + ps_dec); + } + } + } + } + else + { + /* b3, b4, b5 */ + u4_cxt_inc = 0x0555; + u1_bin = (UWORD8)ih264d_decode_bins(3, u4_cxt_inc, + ps_mb_bin_ctxt, ps_bitstrm, + ps_cab_env); + + + + + u1_mb_type += u1_bin; + } + } + } + } + return ((UWORD32)u1_mb_type); +} + +/*! + ************************************************************************** + * \if Function name : DecSubMBType \endif + * + * \brief + * This function decodes MB type using CABAC entropy coding mode. + * + * \return + * MBType. + * + ************************************************************************** + */ +UWORD32 ih264d_parse_submb_type_cabac(const UWORD8 u1_slc_type_b, + decoding_envirnoment_t * ps_cab_env, + dec_bit_stream_t * ps_bitstrm, + bin_ctxt_model_t * ps_sub_mb_cxt) +{ + WORD8 u1_sub_mb_type, u1_bin; + + INC_SYM_COUNT(ps_cab_env); + + u1_sub_mb_type = 0; + u1_bin = (UWORD8)ih264d_decode_bin(0, ps_sub_mb_cxt, ps_bitstrm, + ps_cab_env); + + if(u1_slc_type_b ^ u1_bin) + return 0; + + if(!u1_slc_type_b) + { + /* P Slice */ + u1_sub_mb_type = 1; + u1_bin = (UWORD8)ih264d_decode_bin(1, ps_sub_mb_cxt, ps_bitstrm, + ps_cab_env); + if(u1_bin == 1) + { + u1_bin = (UWORD8)ih264d_decode_bin(2, ps_sub_mb_cxt, ps_bitstrm, + ps_cab_env); + u1_sub_mb_type = (2 + (!u1_bin)); + } + + return u1_sub_mb_type; + } + else + { + /* B Slice */ + + /* b1 */ + u1_bin = (UWORD8)ih264d_decode_bin(1, ps_sub_mb_cxt, ps_bitstrm, + ps_cab_env); + if(u1_bin) + { + /* b2 */ + u1_bin = (UWORD8)ih264d_decode_bin(2, ps_sub_mb_cxt, ps_bitstrm, + ps_cab_env); + if(u1_bin) + { + /* b3 */ + u1_sub_mb_type = 7; + u1_bin = (UWORD8)ih264d_decode_bin(3, ps_sub_mb_cxt, ps_bitstrm, + ps_cab_env); + u1_sub_mb_type += u1_bin << 2; + u1_bin = !u1_bin; + /* b4 */ + if(u1_bin == 0) + { + u1_bin = ih264d_decode_bin(3, ps_sub_mb_cxt, ps_bitstrm, + ps_cab_env); + } + else + { + u1_bin = (UWORD8)ih264d_decode_bins(2, 0x33, ps_sub_mb_cxt, + ps_bitstrm, ps_cab_env); + } + + return (u1_sub_mb_type + u1_bin); + } + else + { + /* b3 */ + u1_bin = (UWORD8)ih264d_decode_bins(2, 0x33, ps_sub_mb_cxt, + ps_bitstrm, ps_cab_env); + return (3 + u1_bin); + } + } + else + { + /* b2 */ + u1_bin = (UWORD8)ih264d_decode_bin(3, ps_sub_mb_cxt, ps_bitstrm, + ps_cab_env); + return (1 + u1_bin); + } + } +} + +/*! + ************************************************************************** + * \if Function name : ih264d_parse_ref_idx_cabac \endif + * + * \brief + * This function decodes Reference Index using CABAC entropy coding mode. + * + * \return + * None + * + ************************************************************************** + */ +WORD32 ih264d_parse_ref_idx_cabac(const UWORD8 u1_num_part, + const UWORD8 u1_b2, + const UWORD8 u1_max_ref_minus1, + const UWORD8 u1_mb_mode, + WORD8 * pi1_ref_idx, + WORD8 * const pi1_lft_cxt, + WORD8 * const pi1_top_cxt, + decoding_envirnoment_t * const ps_cab_env, + dec_bit_stream_t * const ps_bitstrm, + bin_ctxt_model_t * const ps_ref_cxt) +{ + UWORD8 u1_a, u1_b; + UWORD32 u4_cxt_inc; + UWORD8 u1_blk_no, u1_i, u1_idx_lft, u1_idx_top; + WORD8 i1_ref_idx; + + for(u1_blk_no = 0, u1_i = 0; u1_i < u1_num_part; u1_i++, pi1_ref_idx++) + { + u1_idx_lft = ((u1_blk_no & 0x02) >> 1) + u1_b2; + u1_idx_top = (u1_blk_no & 0x01) + u1_b2; + i1_ref_idx = *pi1_ref_idx; + + if(i1_ref_idx > 0) + { + u1_a = pi1_lft_cxt[u1_idx_lft] > 0; + u1_b = pi1_top_cxt[u1_idx_top] > 0; + + u4_cxt_inc = u1_a + (u1_b << 1); + u4_cxt_inc = (u4_cxt_inc | 0x55540); + + i1_ref_idx = (WORD8)ih264d_decode_bins_unary(32, u4_cxt_inc, + ps_ref_cxt, ps_bitstrm, + ps_cab_env); + + if((i1_ref_idx > u1_max_ref_minus1) || (i1_ref_idx < 0)) + { + return ERROR_REF_IDX; + } + + *pi1_ref_idx = i1_ref_idx; + + INC_SYM_COUNT(ps_cab_env); + + } + + /* Storing Reference Idx Information */ + pi1_lft_cxt[u1_idx_lft] = i1_ref_idx; + pi1_top_cxt[u1_idx_top] = i1_ref_idx; + u1_blk_no = u1_blk_no + 1 + (u1_mb_mode & 0x01); + } + /* if(!u1_sub_mb) */ + if(u1_num_part != 4) + { + pi1_lft_cxt[(!(u1_mb_mode & 0x1)) + u1_b2] = pi1_lft_cxt[u1_b2]; + pi1_top_cxt[(!(u1_mb_mode & 0x2)) + u1_b2] = pi1_top_cxt[u1_b2]; + } + return OK; +} + +/*! + ************************************************************************** + * \if Function name : ih264d_parse_mb_qp_delta_cabac \endif + * + * \brief + * This function decodes MB Qp delta using CABAC entropy coding mode. + * + * \return + * None + * + ************************************************************************** + */ +WORD32 ih264d_parse_mb_qp_delta_cabac(struct _DecStruct * ps_dec, + WORD8 *pi1_mb_qp_delta) +{ + decoding_envirnoment_t * ps_cab_env = &ps_dec->s_cab_dec_env; + dec_bit_stream_t * ps_bitstrm = ps_dec->ps_bitstrm; + + UWORD8 u1_code_num; + bin_ctxt_model_t *ps_mb_qp_delta_ctxt = ps_dec->p_mb_qp_delta_t; + UWORD32 u4_cxt_inc; + + INC_SYM_COUNT(ps_cab_env); + + u4_cxt_inc = (!(!(ps_dec->i1_prev_mb_qp_delta))); + + u1_code_num = 0; + u4_cxt_inc = (u4_cxt_inc | 0x33320); + /* max number of bins = 53, + since Range for MbQpDelta= -26 to +25 inclusive, UNARY code */ + u1_code_num = ih264d_decode_bins_unary(32, u4_cxt_inc, ps_mb_qp_delta_ctxt, + ps_bitstrm, ps_cab_env); + if(u1_code_num == 32) + { + /* Read remaining 21 bins */ + UWORD8 uc_codeNumX; + u4_cxt_inc = 0x33333; + uc_codeNumX = ih264d_decode_bins_unary(21, u4_cxt_inc, ps_mb_qp_delta_ctxt, + ps_bitstrm, ps_cab_env); + u1_code_num = u1_code_num + uc_codeNumX; + } + + *pi1_mb_qp_delta = (u1_code_num + 1) >> 1; + /* Table 9.3: If code_num is even Syntax Element has -ve value */ + if(!(u1_code_num & 0x01)) + *pi1_mb_qp_delta = -(*pi1_mb_qp_delta); + + /* Range of MbQpDelta= -26 to +25 inclusive */ + if((*pi1_mb_qp_delta < -26) || (*pi1_mb_qp_delta > 25)) + return ERROR_INV_RANGE_QP_T; + ps_dec->i1_prev_mb_qp_delta = *pi1_mb_qp_delta; + return OK; +} +/*! + ************************************************************************** + * \if Function name : ih264d_parse_chroma_pred_mode_cabac \endif + * + * \brief + * This function decodes Chroma Pred mode using CABAC entropy coding mode. + * + * \return + * None + * + ************************************************************************** + */ +WORD8 ih264d_parse_chroma_pred_mode_cabac(struct _DecStruct * ps_dec) +{ + decoding_envirnoment_t * ps_cab_env = &ps_dec->s_cab_dec_env; + dec_bit_stream_t * ps_bitstrm = ps_dec->ps_bitstrm; + ctxt_inc_mb_info_t * ps_left_ctxt = ps_dec->p_left_ctxt_mb_info; + ctxt_inc_mb_info_t * ps_top_ctxt = ps_dec->p_top_ctxt_mb_info; + WORD8 i1_chroma_pred_mode, a, b; + UWORD32 u4_cxt_inc; + + INC_SYM_COUNT(ps_cab_env); + + /* Binarization is TU and Cmax=3 */ + i1_chroma_pred_mode = 0; + a = 0; + b = 0; + + a = ((ps_left_ctxt->u1_intra_chroma_pred_mode != 0) ? 1 : 0); + + b = ((ps_top_ctxt->u1_intra_chroma_pred_mode != 0) ? 1 : 0); + u4_cxt_inc = a + b; + + u4_cxt_inc = (u4_cxt_inc | 0x330); + + i1_chroma_pred_mode = ih264d_decode_bins_tunary( + 3, u4_cxt_inc, ps_dec->p_intra_chroma_pred_mode_t, + ps_bitstrm, ps_cab_env); + + return (i1_chroma_pred_mode); +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_parse_transform8x8flag_cabac */ +/* */ +/* Description : */ +/* Inputs : */ +/* */ +/* */ +/* Returns : */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* Rajasekhar Creation */ +/* */ +/*****************************************************************************/ +UWORD8 ih264d_parse_transform8x8flag_cabac(struct _DecStruct * ps_dec, + dec_mb_info_t * ps_cur_mb_info) +{ + decoding_envirnoment_t * ps_cab_env = &ps_dec->s_cab_dec_env; + dec_bit_stream_t * ps_bitstrm = ps_dec->ps_bitstrm; + ctxt_inc_mb_info_t * ps_left_ctxt = ps_dec->p_left_ctxt_mb_info; + ctxt_inc_mb_info_t * ps_top_ctxt = ps_dec->p_top_ctxt_mb_info; + UWORD8 u1_transform_8x8flag; + UWORD8 u1_mb_ngbr_avail = ps_cur_mb_info->u1_mb_ngbr_availablity; + + WORD8 a, b; + UWORD32 u4_cxt_inc; + + /* for calculating the context increment for transform8x8 u4_flag */ + /* it reads transform8x8 u4_flag of the neighbors through */ + + /* Binarization is FLC */ + a = 0; + b = 0; + + if(u1_mb_ngbr_avail & LEFT_MB_AVAILABLE_MASK) + { + a = ps_left_ctxt->u1_transform8x8_ctxt; + } + if(u1_mb_ngbr_avail & TOP_MB_AVAILABLE_MASK) + { + b = ps_top_ctxt->u1_transform8x8_ctxt; + + } + + u4_cxt_inc = a + b; + + u1_transform_8x8flag = ih264d_decode_bin( + u4_cxt_inc, ps_dec->s_high_profile.ps_transform8x8_flag, + ps_bitstrm, ps_cab_env); + + return (u1_transform_8x8flag); +} + +/*! + ************************************************************************** + * \if Function name : ih264d_read_intra_pred_modes_cabac \endif + * + * \brief + * Reads the intra pred mode related values of I4x4 MB from bitstream. + * + * This function will read the prev intra pred mode flags and + * stores it in pu1_prev_intra4x4_pred_mode_flag. If the u4_flag + * indicates that most probable mode is not intra pred mode, then + * the rem_intra4x4_pred_mode is read and stored in + * pu1_rem_intra4x4_pred_mode array. + * + * + * \return + * 0 on success and Error code otherwise + * + ************************************************************************** + */ +WORD32 ih264d_read_intra_pred_modes_cabac(dec_struct_t * ps_dec, + UWORD8 * pu1_prev_intra4x4_pred_mode_flag, + UWORD8 * pu1_rem_intra4x4_pred_mode, + UWORD8 u1_tran_form8x8) +{ + WORD32 i4x4_luma_blk_idx = 0; + dec_bit_stream_t * ps_bitstrm = ps_dec->ps_bitstrm; + decoding_envirnoment_t * ps_cab_env = &ps_dec->s_cab_dec_env; + bin_ctxt_model_t *ps_ctxt_ipred_luma_mpm, *ps_ctx_ipred_luma_rm; + WORD32 i4_rem_intra4x4_pred_mode; + UWORD32 u4_prev_intra4x4_pred_mode_flag; + UWORD32 u4_code_int_range, u4_code_int_val_ofst; + const UWORD32 *pu4_table = (const UWORD32 *)ps_cab_env->cabac_table; + + ps_ctxt_ipred_luma_mpm = ps_dec->p_prev_intra4x4_pred_mode_flag_t; + ps_ctx_ipred_luma_rm = ps_dec->p_rem_intra4x4_pred_mode_t; + SWITCHOFFTRACE; + + i4x4_luma_blk_idx = (0 == u1_tran_form8x8) ? 16 : 4; + + u4_code_int_range = ps_cab_env->u4_code_int_range; + u4_code_int_val_ofst = ps_cab_env->u4_code_int_val_ofst; + + do + { + + DECODE_ONE_BIN_MACRO(ps_ctxt_ipred_luma_mpm, u4_code_int_range, + u4_code_int_val_ofst, pu4_table, ps_bitstrm, + u4_prev_intra4x4_pred_mode_flag) + *pu1_prev_intra4x4_pred_mode_flag = u4_prev_intra4x4_pred_mode_flag; + + i4_rem_intra4x4_pred_mode = -1; + if(!u4_prev_intra4x4_pred_mode_flag) + { + + /*inlining DecodeDecisionBins_FLC*/ + + { + + UWORD8 u1_max_bins = 3; + UWORD32 u4_value; + UWORD32 u4_symbol, i; + + i = 0; + u4_value = 0; + + do + { + + DECODE_ONE_BIN_MACRO(ps_ctx_ipred_luma_rm, u4_code_int_range, + u4_code_int_val_ofst, pu4_table, + ps_bitstrm, u4_symbol) + + INC_BIN_COUNT(ps_cab_env);INC_DECISION_BINS(ps_cab_env); + + u4_value = u4_value | (u4_symbol << i); + + i++; + } + while(i < u1_max_bins); + + i4_rem_intra4x4_pred_mode = (u4_value); + + } + + } + + (*pu1_rem_intra4x4_pred_mode) = i4_rem_intra4x4_pred_mode; + + COPYTHECONTEXT("intra4x4_pred_mode", i4_rem_intra4x4_pred_mode); + + pu1_prev_intra4x4_pred_mode_flag++; + pu1_rem_intra4x4_pred_mode++; + + i4x4_luma_blk_idx--; + } + while(i4x4_luma_blk_idx); + + ps_cab_env->u4_code_int_range = u4_code_int_range; + ps_cab_env->u4_code_int_val_ofst = u4_code_int_val_ofst; + + return (0); + +} + +/*! + ************************************************************************** + * \if Function name : ih264d_parse_ctx_cbp_cabac \endif + * + * \brief + * This function decodes CtxCbpLuma and CtxCbpChroma (CBP of a Macroblock). + * using CABAC entropy coding mode. + * + * \return + * CBP of a MB. + * + ************************************************************************** + */ +UWORD32 ih264d_parse_ctx_cbp_cabac(struct _DecStruct * ps_dec) +{ + + UWORD32 u4_cxt_inc; + decoding_envirnoment_t * ps_cab_env = &ps_dec->s_cab_dec_env; + dec_bit_stream_t * ps_bitstrm = ps_dec->ps_bitstrm; + ctxt_inc_mb_info_t * ps_left_ctxt = ps_dec->p_left_ctxt_mb_info; + ctxt_inc_mb_info_t * ps_top_ctxt = ps_dec->p_top_ctxt_mb_info; + bin_ctxt_model_t *ps_ctxt_cbp_luma = ps_dec->p_cbp_luma_t, *ps_bin_ctxt; + WORD8 c_Cbp; //,i,j; + UWORD32 u4_code_int_range, u4_code_int_val_ofst; + UWORD32 u4_offset, *pu4_buffer; + const UWORD32 *pu4_table = (const UWORD32 *)ps_cab_env->cabac_table; + + INC_SYM_COUNT(ps_cab_env); + + + + /* CBP Luma, FL, Cmax = 15, L = 4 */ + u4_cxt_inc = (!((ps_top_ctxt->u1_cbp >> 2) & 0x01)) << 1; + u4_cxt_inc += !((ps_left_ctxt->u1_cbp >> 1) & 0x01); + + u4_offset = ps_bitstrm->u4_ofst; + pu4_buffer = ps_bitstrm->pu4_buffer; + + u4_code_int_range = ps_cab_env->u4_code_int_range; + u4_code_int_val_ofst = ps_cab_env->u4_code_int_val_ofst; + /*renormalize to ensure there 23 bits more in the u4_code_int_val_ofst*/ + { + UWORD32 u4_clz, read_bits; + + u4_clz = CLZ(u4_code_int_range); + FLUSHBITS(u4_offset, u4_clz) + NEXTBITS(read_bits, u4_offset, pu4_buffer, 23) + u4_code_int_range = u4_code_int_range << u4_clz; + u4_code_int_val_ofst = (u4_code_int_val_ofst << u4_clz) | read_bits; + } + + ps_bin_ctxt = ps_ctxt_cbp_luma + u4_cxt_inc; + + /*inlining DecodeDecision_onebin without renorm*/ + { + + UWORD32 u4_qnt_int_range, u4_int_range_lps; + UWORD32 u4_symbol, u1_mps_state; + UWORD32 table_lookup; + UWORD32 u4_clz; + + u1_mps_state = (ps_bin_ctxt->u1_mps_state); + + u4_clz = CLZ(u4_code_int_range); + u4_qnt_int_range = u4_code_int_range << u4_clz; + u4_qnt_int_range = (u4_qnt_int_range >> 29) & 0x3; + + table_lookup = pu4_table[(u1_mps_state << 2) + u4_qnt_int_range]; + u4_int_range_lps = table_lookup & 0xff; + + u4_int_range_lps = u4_int_range_lps << (23 - u4_clz); + u4_code_int_range = u4_code_int_range - u4_int_range_lps; + + u4_symbol = ((u1_mps_state >> 6) & 0x1); + + /*if mps*/ + u1_mps_state = (table_lookup >> 8) & 0x7F; + + CHECK_IF_LPS(u4_code_int_range, u4_code_int_val_ofst, u4_symbol, + u4_int_range_lps, u1_mps_state, table_lookup) + + INC_BIN_COUNT(ps_cab_env); + + ps_bin_ctxt->u1_mps_state = u1_mps_state; + + c_Cbp = u4_symbol; + + } + + u4_cxt_inc = (!((ps_top_ctxt->u1_cbp >> 3) & 0x01)) << 1; + u4_cxt_inc += !(c_Cbp & 0x01); + ps_bin_ctxt = ps_ctxt_cbp_luma + u4_cxt_inc; + /*inlining DecodeDecision_onebin without renorm*/ + + { + + UWORD32 u4_qnt_int_range, u4_int_range_lps; + UWORD32 u4_symbol, u1_mps_state; + UWORD32 table_lookup; + UWORD32 u4_clz; + + u1_mps_state = (ps_bin_ctxt->u1_mps_state); + + u4_clz = CLZ(u4_code_int_range); + u4_qnt_int_range = u4_code_int_range << u4_clz; + u4_qnt_int_range = (u4_qnt_int_range >> 29) & 0x3; + + table_lookup = pu4_table[(u1_mps_state << 2) + u4_qnt_int_range]; + u4_int_range_lps = table_lookup & 0xff; + + u4_int_range_lps = u4_int_range_lps << (23 - u4_clz); + u4_code_int_range = u4_code_int_range - u4_int_range_lps; + + u4_symbol = ((u1_mps_state >> 6) & 0x1); + + /*if mps*/ + u1_mps_state = (table_lookup >> 8) & 0x7F; + + CHECK_IF_LPS(u4_code_int_range, u4_code_int_val_ofst, u4_symbol, + u4_int_range_lps, u1_mps_state, table_lookup) + + INC_BIN_COUNT(ps_cab_env); + + ps_bin_ctxt->u1_mps_state = u1_mps_state; + + c_Cbp |= u4_symbol << 1; + + } + + u4_cxt_inc = (!(c_Cbp & 0x01)) << 1; + u4_cxt_inc += !((ps_left_ctxt->u1_cbp >> 3) & 0x01); + ps_bin_ctxt = ps_ctxt_cbp_luma + u4_cxt_inc; + /*inlining DecodeDecision_onebin without renorm*/ + + { + + UWORD32 u4_qnt_int_range, u4_int_range_lps; + UWORD32 u4_symbol, u1_mps_state; + UWORD32 table_lookup; + UWORD32 u4_clz; + + u1_mps_state = (ps_bin_ctxt->u1_mps_state); + + u4_clz = CLZ(u4_code_int_range); + u4_qnt_int_range = u4_code_int_range << u4_clz; + u4_qnt_int_range = (u4_qnt_int_range >> 29) & 0x3; + + table_lookup = pu4_table[(u1_mps_state << 2) + u4_qnt_int_range]; + u4_int_range_lps = table_lookup & 0xff; + + u4_int_range_lps = u4_int_range_lps << (23 - u4_clz); + u4_code_int_range = u4_code_int_range - u4_int_range_lps; + + u4_symbol = ((u1_mps_state >> 6) & 0x1); + + /*if mps*/ + u1_mps_state = (table_lookup >> 8) & 0x7F; + + CHECK_IF_LPS(u4_code_int_range, u4_code_int_val_ofst, u4_symbol, + u4_int_range_lps, u1_mps_state, table_lookup) + + INC_BIN_COUNT(ps_cab_env); + + ps_bin_ctxt->u1_mps_state = u1_mps_state; + + c_Cbp |= u4_symbol << 2; + + } + + u4_cxt_inc = (!((c_Cbp >> 1) & 0x01)) << 1; + u4_cxt_inc += !((c_Cbp >> 2) & 0x01); + ps_bin_ctxt = ps_ctxt_cbp_luma + u4_cxt_inc; + /*inlining DecodeDecision_onebin without renorm*/ + + { + + UWORD32 u4_qnt_int_range, u4_int_range_lps; + UWORD32 u4_symbol, u1_mps_state; + UWORD32 table_lookup; + UWORD32 u4_clz; + + u1_mps_state = (ps_bin_ctxt->u1_mps_state); + + u4_clz = CLZ(u4_code_int_range); + u4_qnt_int_range = u4_code_int_range << u4_clz; + u4_qnt_int_range = (u4_qnt_int_range >> 29) & 0x3; + + table_lookup = pu4_table[(u1_mps_state << 2) + u4_qnt_int_range]; + u4_int_range_lps = table_lookup & 0xff; + + u4_int_range_lps = u4_int_range_lps << (23 - u4_clz); + u4_code_int_range = u4_code_int_range - u4_int_range_lps; + + u4_symbol = ((u1_mps_state >> 6) & 0x1); + + /*if mps*/ + u1_mps_state = (table_lookup >> 8) & 0x7F; + + CHECK_IF_LPS(u4_code_int_range, u4_code_int_val_ofst, u4_symbol, + u4_int_range_lps, u1_mps_state, table_lookup) + + INC_BIN_COUNT(ps_cab_env); + + ps_bin_ctxt->u1_mps_state = u1_mps_state; + + c_Cbp |= u4_symbol << 3; + + } + + if(u4_code_int_range < ONE_RIGHT_SHIFTED_BY_8) + { + + RENORM_RANGE_OFFSET(u4_code_int_range, u4_code_int_val_ofst, u4_offset, + pu4_buffer) + + } + + { + UWORD32 u4_cxt_inc; + WORD8 a, b, c, d; + bin_ctxt_model_t *p_CtxtCbpChroma = ps_dec->p_cbp_chroma_t; + + /* CBP Chroma, TU, Cmax = 2 */ + a = 0; + b = 0; + c = 0; + d = 0; + + { + a = (ps_top_ctxt->u1_cbp > 15) ? 2 : 0; + c = (ps_top_ctxt->u1_cbp > 31) ? 2 : 0; + } + + { + b = (ps_left_ctxt->u1_cbp > 15) ? 1 : 0; + d = (ps_left_ctxt->u1_cbp > 31) ? 1 : 0; + } + u4_cxt_inc = a + b; + u4_cxt_inc = (u4_cxt_inc | ((4 + c + d) << 4)); + + /*inlining ih264d_decode_bins_tunary */ + + { + + UWORD8 u1_max_bins = 2; + UWORD32 u4_ctx_inc = u4_cxt_inc; + + UWORD32 u4_value; + UWORD32 u4_symbol; + UWORD8 u4_ctx_Inc; + bin_ctxt_model_t *ps_bin_ctxt; + u4_value = 0; + + do + { + u4_ctx_Inc = u4_ctx_inc & 0xF; + u4_ctx_inc = u4_ctx_inc >> 4; + + ps_bin_ctxt = p_CtxtCbpChroma + u4_ctx_Inc; + /*inlining DecodeDecision_onebin*/ + { + + UWORD32 u4_qnt_int_range, u4_int_range_lps; + + UWORD32 u1_mps_state; + UWORD32 table_lookup; + UWORD32 u4_clz; + + u1_mps_state = (ps_bin_ctxt->u1_mps_state); + + u4_clz = CLZ(u4_code_int_range); + u4_qnt_int_range = u4_code_int_range << u4_clz; + u4_qnt_int_range = (u4_qnt_int_range >> 29) & 0x3; + + table_lookup = pu4_table[(u1_mps_state << 2) + + u4_qnt_int_range]; + u4_int_range_lps = table_lookup & 0xff; + + u4_int_range_lps = u4_int_range_lps << (23 - u4_clz); + u4_code_int_range = u4_code_int_range - u4_int_range_lps; + + u4_symbol = ((u1_mps_state >> 6) & 0x1); + + /*if mps*/ + u1_mps_state = (table_lookup >> 8) & 0x7F; + + CHECK_IF_LPS(u4_code_int_range, u4_code_int_val_ofst, + u4_symbol, u4_int_range_lps, u1_mps_state, + table_lookup) + + if(u4_code_int_range < ONE_RIGHT_SHIFTED_BY_8) + { + RENORM_RANGE_OFFSET(u4_code_int_range, + u4_code_int_val_ofst, u4_offset, + pu4_buffer) + } + ps_bin_ctxt->u1_mps_state = u1_mps_state; + } + + INC_BIN_COUNT(ps_cab_env);INC_DECISION_BINS( + ps_cab_env); + + u4_value++; + } + while((u4_value < u1_max_bins) & (u4_symbol)); + + u4_value = u4_value - 1 + u4_symbol; + + a = (u4_value); + + } + +c_Cbp = (c_Cbp | (a << 4)); +} + +ps_bitstrm->u4_ofst = u4_offset; + +ps_cab_env->u4_code_int_range = u4_code_int_range; +ps_cab_env->u4_code_int_val_ofst = u4_code_int_val_ofst; + +return (c_Cbp); +} + +/*! + ************************************************************************** + * \if Function name : ih264d_get_mvd_cabac \endif + * + * \brief + * This function decodes Horz and Vert mvd_l0 and mvd_l1 using CABAC entropy + * coding mode as defined in 9.3.2.3. + * + * \return + * None + * + ************************************************************************** + */ +void ih264d_get_mvd_cabac(UWORD8 u1_sub_mb, + UWORD8 u1_b2, + UWORD8 u1_part_wd, + UWORD8 u1_part_ht, + UWORD8 u1_dec_mvd, + dec_struct_t *ps_dec, + mv_pred_t *ps_mv) +{ + UWORD8 u1_abs_mvd_x = 0, u1_abs_mvd_y = 0; + UWORD8 u1_sub_mb_x, u1_sub_mb_y; + UWORD8 *pu1_top_mv_ctxt, *pu1_lft_mv_ctxt; + WORD16 *pi2_mv; + + u1_sub_mb_x = (UWORD8)(u1_sub_mb & 0x03); + u1_sub_mb_y = (UWORD8)(u1_sub_mb >> 2); + pu1_top_mv_ctxt = &ps_dec->ps_curr_ctxt_mb_info->u1_mv[u1_sub_mb_x][u1_b2]; + pu1_lft_mv_ctxt = &ps_dec->pu1_left_mv_ctxt_inc[u1_sub_mb_y][u1_b2]; + pi2_mv = &ps_mv->i2_mv[u1_b2]; + + if(u1_dec_mvd) + { + WORD16 i2_mv_x, i2_mv_y; + WORD32 i2_temp; + { + decoding_envirnoment_t * ps_cab_env = &ps_dec->s_cab_dec_env; + dec_bit_stream_t * ps_bitstrm = ps_dec->ps_bitstrm; + UWORD16 u2_abs_mvd_x_a, u2_abs_mvd_x_b, u2_abs_mvd_y_a, + u2_abs_mvd_y_b; + + u2_abs_mvd_x_b = (UWORD16)pu1_top_mv_ctxt[0]; + u2_abs_mvd_y_b = (UWORD16)pu1_top_mv_ctxt[1]; + u2_abs_mvd_x_a = (UWORD16)pu1_lft_mv_ctxt[0]; + u2_abs_mvd_y_a = (UWORD16)pu1_lft_mv_ctxt[1]; + + i2_temp = u2_abs_mvd_x_a + u2_abs_mvd_x_b; + + i2_mv_x = ih264d_parse_mvd_cabac(ps_bitstrm, ps_cab_env, + ps_dec->p_mvd_x_t, i2_temp); + + i2_temp = u2_abs_mvd_y_a + u2_abs_mvd_y_b; + + i2_mv_y = ih264d_parse_mvd_cabac(ps_bitstrm, ps_cab_env, + ps_dec->p_mvd_y_t, i2_temp); + } + + /***********************************************************************/ + /* Store the abs_mvd_values in cabac contexts */ + /* The follownig code can be easily optimzed if mvX, mvY clip values */ + /* are packed in 16 bits follwed by memcpy */ + /***********************************************************************/ + u1_abs_mvd_x = CLIP3(0, 127, ABS(i2_mv_x)); + u1_abs_mvd_y = CLIP3(0, 127, ABS(i2_mv_y)); + + COPYTHECONTEXT("MVD", i2_mv_x);COPYTHECONTEXT("MVD", i2_mv_y); + + /* Storing Mv residuals */ + pi2_mv[0] = i2_mv_x; + pi2_mv[1] = i2_mv_y; + } + + /***************************************************************/ + /* Store abs_mvd_values cabac contexts */ + /***************************************************************/ + { + UWORD8 u1_i; + for(u1_i = 0; u1_i < u1_part_wd; u1_i++, pu1_top_mv_ctxt += 4) + { + pu1_top_mv_ctxt[0] = u1_abs_mvd_x; + pu1_top_mv_ctxt[1] = u1_abs_mvd_y; + } + + for(u1_i = 0; u1_i < u1_part_ht; u1_i++, pu1_lft_mv_ctxt += 4) + { + pu1_lft_mv_ctxt[0] = u1_abs_mvd_x; + pu1_lft_mv_ctxt[1] = u1_abs_mvd_y; + } + } +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_parse_mvd_cabac */ +/* */ +/* Description : This cabac function decodes the mvd in a given direction */ +/* direction ( x or y ) as defined in 9.3.2.3. */ +/* */ +/* Inputs : 1. pointer to Bitstream */ +/* 2. pointer to cabac decoding environmnet */ +/* 3. pointer to Mvd context */ +/* 4. abs(Top mvd) = u2_abs_mvd_b */ +/* 5. abs(left mvd)= u2_abs_mvd_a */ +/* */ +/* Processing : see section 9.3.2.3 of the standard */ +/* */ +/* Outputs : i2_mvd */ +/* Returns : i2_mvd */ +/* */ +/* Issues : none */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 16 06 2005 Jay Draft */ +/* */ +/*****************************************************************************/ +WORD16 ih264d_parse_mvd_cabac(dec_bit_stream_t * ps_bitstrm, + decoding_envirnoment_t * ps_cab_env, + bin_ctxt_model_t * p_ctxt_mvd, + UWORD32 i4_temp) + +{ + WORD8 k; + WORD16 i2_suf; + WORD16 i2_mvd; + UWORD16 u2_abs_mvd; + UWORD32 u4_ctx_inc; + UWORD32 u4_prefix; + const UWORD32 *pu4_table = (const UWORD32 *)ps_cab_env->cabac_table; + UWORD32 u4_code_int_range, u4_code_int_val_ofst; + + /* if mvd < 9 */ + /* mvd = Prefix */ + /* else */ + /* mvd = Prefix + Suffix */ + /* decode sign bit */ + /* Prefix TU decoding Cmax =Ucoff and Suffix 3rd order Exp-Golomb */ + + u2_abs_mvd = (UWORD16)i4_temp; + u4_ctx_inc = 1; + + if(u2_abs_mvd < 3) + u4_ctx_inc = 0; + else if(u2_abs_mvd > 32) + u4_ctx_inc = 2; + + u4_ctx_inc = (u4_ctx_inc | 0x65430); + + /*inlining modified version of ih264d_decode_bins_unary*/ + + { + UWORD8 u1_max_bins = 9; + UWORD32 u4_value; + UWORD32 u4_symbol; + bin_ctxt_model_t *ps_bin_ctxt; + UWORD32 u4_ctx_Inc; + + u4_value = 0; + u4_code_int_range = ps_cab_env->u4_code_int_range; + u4_code_int_val_ofst = ps_cab_env->u4_code_int_val_ofst; + + do + { + u4_ctx_Inc = u4_ctx_inc & 0xf; + u4_ctx_inc = u4_ctx_inc >> 4; + + ps_bin_ctxt = p_ctxt_mvd + u4_ctx_Inc; + + DECODE_ONE_BIN_MACRO(ps_bin_ctxt, u4_code_int_range, + u4_code_int_val_ofst, pu4_table, ps_bitstrm, + u4_symbol) + + INC_BIN_COUNT(ps_cab_env);INC_DECISION_BINS(ps_cab_env); + + u4_value++; + + } + while(u4_symbol && u4_value < 5); + + ps_bin_ctxt = p_ctxt_mvd + 6; + + if(u4_symbol && (u4_value < u1_max_bins)) + { + + do + { + + DECODE_ONE_BIN_MACRO(ps_bin_ctxt, u4_code_int_range, + u4_code_int_val_ofst, pu4_table, + ps_bitstrm, u4_symbol) + + INC_BIN_COUNT(ps_cab_env);INC_DECISION_BINS(ps_cab_env); + u4_value++; + } + while(u4_symbol && (u4_value < u1_max_bins)); + + } + + ps_cab_env->u4_code_int_range = u4_code_int_range; + ps_cab_env->u4_code_int_val_ofst = u4_code_int_val_ofst; + u4_value = u4_value - 1 + u4_symbol; + u4_prefix = (u4_value); + } + + i2_mvd = u4_prefix; + + if(i2_mvd == 9) + { + /* Read Suffix */ + k = ih264d_decode_bypass_bins_unary(ps_cab_env, ps_bitstrm); + i2_suf = (1 << k) - 1; + k = k + 3; + i2_suf = (i2_suf << 3); + i2_mvd += i2_suf; + i2_suf = ih264d_decode_bypass_bins(ps_cab_env, k, ps_bitstrm); + i2_mvd += i2_suf; + } + /* Read Sign bit */ + if(!i2_mvd) + return (i2_mvd); + + else + { + UWORD32 u4_code_int_val_ofst, u4_code_int_range; + + u4_code_int_val_ofst = ps_cab_env->u4_code_int_val_ofst; + u4_code_int_range = ps_cab_env->u4_code_int_range; + + if(u4_code_int_range < ONE_RIGHT_SHIFTED_BY_9) + { + UWORD32 *pu4_buffer, u4_offset; + + pu4_buffer = ps_bitstrm->pu4_buffer; + u4_offset = ps_bitstrm->u4_ofst; + + RENORM_RANGE_OFFSET(u4_code_int_range, u4_code_int_val_ofst, + u4_offset, pu4_buffer) + ps_bitstrm->u4_ofst = u4_offset; + } + + u4_code_int_range = u4_code_int_range >> 1; + + if(u4_code_int_val_ofst >= u4_code_int_range) + { + /* S=1 */ + u4_code_int_val_ofst -= u4_code_int_range; + i2_mvd = (-i2_mvd); + } + + ps_cab_env->u4_code_int_val_ofst = u4_code_int_val_ofst; + ps_cab_env->u4_code_int_range = u4_code_int_range; + + return (i2_mvd); + + } +} diff --git a/dependencies/ih264d/decoder/ih264d_parse_mb_header.h b/dependencies/ih264d/decoder/ih264d_parse_mb_header.h new file mode 100644 index 00000000..63067b9d --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_parse_mb_header.h @@ -0,0 +1,88 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/*! + *************************************************************************** + * \file ih264d_parse_mb_header.h + * + * \brief + * This file contains context identifier decoding routines. + * + * \date + * 04/02/2003 + * + * \author NS + *************************************************************************** + */ +#ifndef _IH264D_PARSE_MB_HEADER_H_ +#define _IH264D_PARSE_MB_HEADER_H_ + +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264d_structs.h" +#include "ih264d_cabac.h" + +WORD32 ih264d_read_intra_pred_modes_cabac(dec_struct_t * ps_dec, + UWORD8 * pu1_prev_intra4x4_pred_mode_flag, + UWORD8 * pu1_rem_intra4x4_pred_mode, + UWORD8 u1_tran_form8x8); + +UWORD32 ih264d_parse_mb_type_cabac(struct _DecStruct * ps_dec); +UWORD8 ih264d_parse_mb_type_intra_cabac(UWORD8 u1_inter, + struct _DecStruct * ps_dec); + +UWORD32 ih264d_parse_submb_type_cabac(const UWORD8 u1_slc_type_p, + decoding_envirnoment_t * ps_cab_env, + dec_bit_stream_t * ps_bitstrm, + bin_ctxt_model_t * ps_sub_mb_cxt); +WORD32 ih264d_parse_ref_idx_cabac(const UWORD8 u1_num_part, + const UWORD8 u1_b2, + const UWORD8 u1_max_ref_minus1, + const UWORD8 u1_mb_mode, + WORD8 * pi1_ref_idx, + WORD8 * const pi1_lft_cxt, + WORD8 * const pi1_top_cxt, + decoding_envirnoment_t * const ps_cab_env, + dec_bit_stream_t * const ps_bitstrm, + bin_ctxt_model_t * const ps_ref_cxt); + +WORD32 ih264d_parse_mb_qp_delta_cabac(struct _DecStruct * ps_dec, + WORD8 *pi1_mb_qp_delta); +WORD8 ih264d_parse_chroma_pred_mode_cabac(struct _DecStruct * ps_dec); + +UWORD32 ih264d_parse_ctx_cbp_cabac(struct _DecStruct * ps_dec); + +UWORD8 ih264d_parse_transform8x8flag_cabac(struct _DecStruct * ps_dec, + dec_mb_info_t * ps_cur_mb_info); + +void ih264d_get_mvd_cabac(UWORD8 u1_sub_mb, + UWORD8 u1_b2, + UWORD8 u1_part_wd, + UWORD8 u1_part_ht, + UWORD8 u1_dec_mvd, + dec_struct_t *ps_dec, + mv_pred_t *ps_mv); + +WORD16 ih264d_parse_mvd_cabac(dec_bit_stream_t * ps_bitstrm, + decoding_envirnoment_t * ps_cab_env, + bin_ctxt_model_t * p_ctxt_mvd, + UWORD32 temp); + +#endif /* _IH264D_PARSE_MB_HEADER_H_ */ diff --git a/dependencies/ih264d/decoder/ih264d_parse_pslice.c b/dependencies/ih264d/decoder/ih264d_parse_pslice.c new file mode 100644 index 00000000..6f8df9ec --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_parse_pslice.c @@ -0,0 +1,2221 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ + +/*! + ************************************************************************** + * \file ih264d_parse_pslice.c + * + * \brief + * Contains routines that decode a I slice type + * + * Detailed_description + * + * \date + * 07/07/2003 + * + * \author NS + ************************************************************************** + */ + +#include <string.h> +#include "ih264_defs.h" +#include "ih264d_bitstrm.h" +#include "ih264d_defs.h" +#include "ih264d_debug.h" +#include "ih264d_tables.h" +#include "ih264d_structs.h" +#include "ih264d_defs.h" +#include "ih264d_parse_cavlc.h" +#include "ih264d_mb_utils.h" +#include "ih264d_parse_slice.h" +#include "ih264d_mvpred.h" +#include "ih264d_parse_islice.h" +#include "ih264d_process_intra_mb.h" +#include "ih264d_inter_pred.h" +#include "ih264d_process_pslice.h" +#include "ih264d_deblocking.h" +#include "ih264d_cabac.h" +#include "ih264d_parse_mb_header.h" +#include "ih264d_error_handler.h" +#include "ih264d_defs.h" +#include "ih264d_format_conv.h" +#include "ih264d_quant_scaling.h" +#include "ih264d_thread_parse_decode.h" +#include "ih264d_thread_compute_bs.h" +#include "ih264d_process_bslice.h" +#include "ithread.h" +#include "ih264d_utils.h" +#include "ih264d_format_conv.h" + +void ih264d_init_cabac_contexts(UWORD8 u1_slice_type, dec_struct_t * ps_dec); +void ih264d_deblock_mb_level(dec_struct_t *ps_dec, + dec_mb_info_t *ps_cur_mb_info, + UWORD32 nmb_index); + +/*! + ************************************************************************** + * \if Function name : ih264d_parse_pmb_cavlc \endif + * + * \brief + * This function parses CAVLC syntax of a P MB. + * + * \return + * 0 on Success and Error code otherwise + ************************************************************************** + */ +WORD32 ih264d_parse_pmb_cavlc(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info, + UWORD8 u1_mb_num, + UWORD8 u1_num_mbsNby2) +{ + UWORD32 u1_num_mb_part; + UWORD32 uc_sub_mb; + dec_bit_stream_t * const ps_bitstrm = ps_dec->ps_bitstrm; + UWORD32 * const pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; + UWORD32 *pu4_bitstrm_ofst = &ps_bitstrm->u4_ofst; + + parse_pmbarams_t * ps_parse_mb_data = ps_dec->ps_parse_mb_data + + u1_num_mbsNby2; + WORD8 * pi1_ref_idx = ps_parse_mb_data->i1_ref_idx[0]; + const UWORD8 u1_mbaff = ps_dec->ps_cur_slice->u1_mbaff_frame_flag; + const UWORD8 * pu1_num_mb_part = (const UWORD8 *)gau1_ih264d_num_mb_part; + UWORD8 * pu1_col_info = ps_parse_mb_data->u1_col_info; + + UWORD32 u1_mb_type = ps_cur_mb_info->u1_mb_type; + UWORD32 u4_sum_mb_mode_pack = 0; + WORD32 ret; + + UWORD8 u1_no_submb_part_size_lt8x8_flag = 1; + ps_cur_mb_info->u1_tran_form8x8 = 0; + ps_cur_mb_info->ps_curmb->u1_tran_form8x8 = 0; + + ps_cur_mb_info->u1_yuv_dc_block_flag = 0; + + ps_cur_mb_info->u1_mb_mc_mode = u1_mb_type; + uc_sub_mb = ((u1_mb_type == PRED_8x8) | (u1_mb_type == PRED_8x8R0)); + + /* Reading the subMB type */ + if(uc_sub_mb) + { + WORD32 i; + UWORD8 u1_colz = (PRED_8x8 << 6); + + for(i = 0; i < 4; i++) + { + UWORD32 ui_sub_mb_mode; + + //Inlined ih264d_uev + UWORD32 u4_bitstream_offset = *pu4_bitstrm_ofst; + UWORD32 u4_word, u4_ldz; + + /***************************************************************/ + /* Find leading zeros in next 32 bits */ + /***************************************************************/ + NEXTBITS_32(u4_word, u4_bitstream_offset, pu4_bitstrm_buf); + u4_ldz = CLZ(u4_word); + /* Flush the ps_bitstrm */ + u4_bitstream_offset += (u4_ldz + 1); + /* Read the suffix from the ps_bitstrm */ + u4_word = 0; + if(u4_ldz) + GETBITS(u4_word, u4_bitstream_offset, pu4_bitstrm_buf, + u4_ldz); + *pu4_bitstrm_ofst = u4_bitstream_offset; + ui_sub_mb_mode = ((1 << u4_ldz) + u4_word - 1); + //Inlined ih264d_uev + + if(ui_sub_mb_mode > 3) + { + return ERROR_SUB_MB_TYPE; + } + else + { + u4_sum_mb_mode_pack = (u4_sum_mb_mode_pack << 8) | ui_sub_mb_mode; + /* Storing collocated information */ + *pu1_col_info++ = u1_colz | (UWORD8)(ui_sub_mb_mode << 4); + + COPYTHECONTEXT("sub_mb_type", ui_sub_mb_mode); + } + + /* check if Motion compensation is done below 8x8 */ + if(ui_sub_mb_mode != P_L0_8x8) + { + u1_no_submb_part_size_lt8x8_flag = 0; + } + } + + // + u1_num_mb_part = 4; + } + else + { + *pu1_col_info++ = (u1_mb_type << 6); + if(u1_mb_type) + *pu1_col_info++ = (u1_mb_type << 6); + u1_num_mb_part = pu1_num_mb_part[u1_mb_type]; + + } + + /* Decoding reference index 0: For simple profile the following */ + /* conditions are always true (mb_field_decoding_flag == 0); */ + /* (MbPartPredMode != PredL1) */ + + { + + UWORD8 uc_field = ps_cur_mb_info->u1_mb_field_decodingflag; + UWORD8 uc_num_ref_idx_l0_active_minus1 = + (ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[0] + << (u1_mbaff & uc_field)) - 1; + + if((uc_num_ref_idx_l0_active_minus1 > 0) & (u1_mb_type != PRED_8x8R0)) + { + if(1 == uc_num_ref_idx_l0_active_minus1) + ih264d_parse_pmb_ref_index_cavlc_range1( + u1_num_mb_part, ps_bitstrm, pi1_ref_idx, + uc_num_ref_idx_l0_active_minus1); + else + { + ret = ih264d_parse_pmb_ref_index_cavlc( + u1_num_mb_part, ps_bitstrm, pi1_ref_idx, + uc_num_ref_idx_l0_active_minus1); + if(ret != OK) + return ret; + } + } + else + { + /* When there exists only a single frame to predict from */ + UWORD8 uc_i; + for(uc_i = 0; uc_i < u1_num_mb_part; uc_i++) + /* Storing Reference Idx Information */ + pi1_ref_idx[uc_i] = 0; + } + } + + { + UWORD8 u1_p_idx, uc_i; + parse_part_params_t * ps_part = ps_dec->ps_part; + UWORD8 u1_sub_mb_mode, u1_num_subpart, u1_mb_part_width, u1_mb_part_height; + UWORD8 u1_sub_mb_num; + const UWORD8 * pu1_top_left_sub_mb_indx; + mv_pred_t * ps_mv, *ps_mv_start = ps_dec->ps_mv_cur + (u1_mb_num << 4); + /* Loading the table pointers */ + const UWORD8 * pu1_mb_partw = (const UWORD8 *)gau1_ih264d_mb_partw; + const UWORD8 * pu1_mb_parth = (const UWORD8 *)gau1_ih264d_mb_parth; + const UWORD8 * pu1_sub_mb_indx_mod = + (const UWORD8 *)(gau1_ih264d_submb_indx_mod) + + (uc_sub_mb * 6); + const UWORD8 * pu1_sub_mb_partw = (const UWORD8 *)gau1_ih264d_submb_partw; + const UWORD8 * pu1_sub_mb_parth = (const UWORD8 *)gau1_ih264d_submb_parth; + const UWORD8 * pu1_num_sub_mb_part = + (const UWORD8 *)gau1_ih264d_num_submb_part; + + UWORD16 u2_sub_mb_num = 0x028A; + + /*********************************************************/ + /* default initialisations for condition (uc_sub_mb == 0) */ + /* i.e. all are subpartitions of 8x8 */ + /*********************************************************/ + u1_sub_mb_mode = 0; + u1_num_subpart = 1; + u1_mb_part_width = pu1_mb_partw[u1_mb_type]; + u1_mb_part_height = pu1_mb_parth[u1_mb_type]; + pu1_top_left_sub_mb_indx = pu1_sub_mb_indx_mod + (u1_mb_type << 1); + u1_sub_mb_num = 0; + + /* Loop on number of partitions */ + for(uc_i = 0, u1_p_idx = 0; uc_i < u1_num_mb_part; uc_i++) + { + UWORD8 uc_j; + if(uc_sub_mb) + { + u1_sub_mb_mode = u4_sum_mb_mode_pack >> 24; + u1_num_subpart = pu1_num_sub_mb_part[u1_sub_mb_mode]; + u1_mb_part_width = pu1_sub_mb_partw[u1_sub_mb_mode]; + u1_mb_part_height = pu1_sub_mb_parth[u1_sub_mb_mode]; + pu1_top_left_sub_mb_indx = pu1_sub_mb_indx_mod + (u1_sub_mb_mode << 1); + u1_sub_mb_num = u2_sub_mb_num >> 12; + u4_sum_mb_mode_pack <<= 8; + u2_sub_mb_num <<= 4; + } + + /* Loop on Number of sub-partitions */ + for(uc_j = 0; uc_j < u1_num_subpart; uc_j++, pu1_top_left_sub_mb_indx++) + { + WORD16 i2_mvx, i2_mvy; + u1_sub_mb_num += *pu1_top_left_sub_mb_indx; + ps_mv = ps_mv_start + u1_sub_mb_num; + + /* Reading the differential Mv from the bitstream */ + //i2_mvx = ih264d_sev(pu4_bitstrm_ofst, pu4_bitstrm_buf); + //inlining ih264d_sev + { + UWORD32 u4_bitstream_offset = *pu4_bitstrm_ofst; + UWORD32 u4_word, u4_ldz, u4_abs_val; + + /***************************************************************/ + /* Find leading zeros in next 32 bits */ + /***************************************************************/ + NEXTBITS_32(u4_word, u4_bitstream_offset, + pu4_bitstrm_buf); + u4_ldz = CLZ(u4_word); + + /* Flush the ps_bitstrm */ + u4_bitstream_offset += (u4_ldz + 1); + + /* Read the suffix from the ps_bitstrm */ + u4_word = 0; + if(u4_ldz) + GETBITS(u4_word, u4_bitstream_offset, + pu4_bitstrm_buf, u4_ldz); + + *pu4_bitstrm_ofst = u4_bitstream_offset; + u4_abs_val = ((1 << u4_ldz) + u4_word) >> 1; + + if(u4_word & 0x1) + i2_mvx = (-(WORD32)u4_abs_val); + else + i2_mvx = (u4_abs_val); + } + //inlinined ih264d_sev + COPYTHECONTEXT("MVD", i2_mvx); + i2_mvy = ih264d_sev(pu4_bitstrm_ofst, + pu4_bitstrm_buf); + COPYTHECONTEXT("MVD", i2_mvy); + + /* Storing Info for partitions */ + ps_part->u1_is_direct = PART_NOT_DIRECT; + ps_part->u1_sub_mb_num = u1_sub_mb_num; + ps_part->u1_partheight = u1_mb_part_height; + ps_part->u1_partwidth = u1_mb_part_width; + + /* Storing Mv residuals */ + ps_mv->i2_mv[0] = i2_mvx; + ps_mv->i2_mv[1] = i2_mvy; + + /* Increment partition Index */ + u1_p_idx++; + ps_part++; + } + } + ps_parse_mb_data->u1_num_part = u1_p_idx; + ps_dec->ps_part = ps_part; + } + + { + UWORD32 u4_cbp; + + /* Read the Coded block pattern */ + UWORD32 u4_bitstream_offset = *pu4_bitstrm_ofst; + UWORD32 u4_word, u4_ldz; + + /***************************************************************/ + /* Find leading zeros in next 32 bits */ + /***************************************************************/ + NEXTBITS_32(u4_word, u4_bitstream_offset, pu4_bitstrm_buf); + u4_ldz = CLZ(u4_word); + /* Flush the ps_bitstrm */ + u4_bitstream_offset += (u4_ldz + 1); + /* Read the suffix from the ps_bitstrm */ + u4_word = 0; + if(u4_ldz) + GETBITS(u4_word, u4_bitstream_offset, pu4_bitstrm_buf, u4_ldz); + *pu4_bitstrm_ofst = u4_bitstream_offset; + u4_cbp = ((1 << u4_ldz) + u4_word - 1); + + if(u4_cbp > 47) + return ERROR_CBP; + + u4_cbp = *((UWORD8*)gau1_ih264d_cbp_inter + u4_cbp); + COPYTHECONTEXT("coded_block_pattern", u4_cbp); + ps_cur_mb_info->u1_cbp = u4_cbp; + + /* Read the transform8x8 u4_flag if present */ + if((ps_dec->s_high_profile.u1_transform8x8_present) && (u4_cbp & 0xf) + && u1_no_submb_part_size_lt8x8_flag) + { + ps_cur_mb_info->u1_tran_form8x8 = ih264d_get_bit_h264(ps_bitstrm); + COPYTHECONTEXT("transform_size_8x8_flag", ps_cur_mb_info->u1_tran_form8x8); + ps_cur_mb_info->ps_curmb->u1_tran_form8x8 = ps_cur_mb_info->u1_tran_form8x8; + } + + /* Read mb_qp_delta */ + if(u4_cbp) + { + WORD32 i_temp; + + UWORD32 u4_bitstream_offset = *pu4_bitstrm_ofst; + UWORD32 u4_word, u4_ldz, u4_abs_val; + + /***************************************************************/ + /* Find leading zeros in next 32 bits */ + /***************************************************************/ + NEXTBITS_32(u4_word, u4_bitstream_offset, pu4_bitstrm_buf); + u4_ldz = CLZ(u4_word); + + /* Flush the ps_bitstrm */ + u4_bitstream_offset += (u4_ldz + 1); + + /* Read the suffix from the ps_bitstrm */ + u4_word = 0; + if(u4_ldz) + GETBITS(u4_word, u4_bitstream_offset, pu4_bitstrm_buf, + u4_ldz); + + *pu4_bitstrm_ofst = u4_bitstream_offset; + u4_abs_val = ((1 << u4_ldz) + u4_word) >> 1; + + if(u4_word & 0x1) + i_temp = (-(WORD32)u4_abs_val); + else + i_temp = (u4_abs_val); + + if((i_temp < -26) || (i_temp > 25)) + return ERROR_INV_RANGE_QP_T; + //inlinined ih264d_sev + + COPYTHECONTEXT("mb_qp_delta", i_temp); + if(i_temp) + { + ret = ih264d_update_qp(ps_dec, (WORD8)i_temp); + if(ret != OK) + return ret; + } + + ret = ih264d_parse_residual4x4_cavlc(ps_dec, ps_cur_mb_info, 0); + if(ret != OK) + return ret; + if(EXCEED_OFFSET(ps_bitstrm)) + return ERROR_EOB_TERMINATE_T; + } + else + { + ih264d_update_nnz_for_skipmb(ps_dec, ps_cur_mb_info, CAVLC); + } + + + + } + + return OK; +} + +/*! + ************************************************************************** + * \if Function name : ih264d_parse_pmb_cabac \endif + * + * \brief + * This function parses CABAC syntax of a P MB. + * + * \return + * 0 on Success and Error code otherwise + ************************************************************************** + */ +WORD32 ih264d_parse_pmb_cabac(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info, + UWORD8 u1_mb_num, + UWORD8 u1_num_mbsNby2) +{ + UWORD32 u1_num_mb_part; + UWORD32 uc_sub_mb; + parse_pmbarams_t * ps_parse_mb_data = ps_dec->ps_parse_mb_data + + u1_num_mbsNby2; + WORD8 * pi1_ref_idx = ps_parse_mb_data->i1_ref_idx[0]; + const UWORD8 * pu1_num_mb_part = (const UWORD8 *)gau1_ih264d_num_mb_part; + const UWORD32 u1_mb_type = ps_cur_mb_info->u1_mb_type; + UWORD8 * pu1_col_info = ps_parse_mb_data->u1_col_info; + UWORD32 u1_mb_mc_mode = u1_mb_type; + ctxt_inc_mb_info_t * p_curr_ctxt = ps_dec->ps_curr_ctxt_mb_info; + decoding_envirnoment_t * ps_cab_env = &ps_dec->s_cab_dec_env; + dec_bit_stream_t * ps_bitstrm = ps_dec->ps_bitstrm; + UWORD32 u4_sub_mb_pack = 0; + WORD32 ret; + + UWORD8 u1_no_submb_part_size_lt8x8_flag = 1; + ps_cur_mb_info->u1_tran_form8x8 = 0; + ps_cur_mb_info->ps_curmb->u1_tran_form8x8 = 0; + + ps_cur_mb_info->u1_yuv_dc_block_flag = 0; + + p_curr_ctxt->u1_mb_type = CAB_P; + ps_cur_mb_info->u1_mb_mc_mode = u1_mb_type; + uc_sub_mb = ((u1_mb_type == PRED_8x8) | (u1_mb_type == PRED_8x8R0)); + + /* Reading the subMB type */ + if(uc_sub_mb) + { + + UWORD8 u1_colz = (PRED_8x8 << 6); + u1_mb_mc_mode = 0; + + { + UWORD8 u1_sub_mb_mode; + u1_sub_mb_mode = ih264d_parse_submb_type_cabac( + 0, ps_cab_env, ps_bitstrm, + ps_dec->p_sub_mb_type_t); + if(u1_sub_mb_mode > 3) + return ERROR_SUB_MB_TYPE; + + u4_sub_mb_pack = (u4_sub_mb_pack << 8) | u1_sub_mb_mode; + /* Storing collocated information */ + *pu1_col_info++ = u1_colz | ((UWORD8)(u1_sub_mb_mode << 4)); + COPYTHECONTEXT("sub_mb_type", u1_sub_mb_mode); + /* check if Motion compensation is done below 8x8 */ + if(u1_sub_mb_mode != P_L0_8x8) + { + u1_no_submb_part_size_lt8x8_flag = 0; + } + } + { + UWORD8 u1_sub_mb_mode; + u1_sub_mb_mode = ih264d_parse_submb_type_cabac( + 0, ps_cab_env, ps_bitstrm, + ps_dec->p_sub_mb_type_t); + if(u1_sub_mb_mode > 3) + return ERROR_SUB_MB_TYPE; + + u4_sub_mb_pack = (u4_sub_mb_pack << 8) | u1_sub_mb_mode; + /* Storing collocated information */ + *pu1_col_info++ = u1_colz | ((UWORD8)(u1_sub_mb_mode << 4)); + COPYTHECONTEXT("sub_mb_type", u1_sub_mb_mode); + /* check if Motion compensation is done below 8x8 */ + if(u1_sub_mb_mode != P_L0_8x8) + { + u1_no_submb_part_size_lt8x8_flag = 0; + } + } + { + UWORD8 u1_sub_mb_mode; + u1_sub_mb_mode = ih264d_parse_submb_type_cabac( + 0, ps_cab_env, ps_bitstrm, + ps_dec->p_sub_mb_type_t); + if(u1_sub_mb_mode > 3) + return ERROR_SUB_MB_TYPE; + + u4_sub_mb_pack = (u4_sub_mb_pack << 8) | u1_sub_mb_mode; + /* Storing collocated information */ + *pu1_col_info++ = u1_colz | ((UWORD8)(u1_sub_mb_mode << 4)); + COPYTHECONTEXT("sub_mb_type", u1_sub_mb_mode); + /* check if Motion compensation is done below 8x8 */ + if(u1_sub_mb_mode != P_L0_8x8) + { + u1_no_submb_part_size_lt8x8_flag = 0; + } + } + { + UWORD8 u1_sub_mb_mode; + u1_sub_mb_mode = ih264d_parse_submb_type_cabac( + 0, ps_cab_env, ps_bitstrm, + ps_dec->p_sub_mb_type_t); + if(u1_sub_mb_mode > 3) + return ERROR_SUB_MB_TYPE; + + u4_sub_mb_pack = (u4_sub_mb_pack << 8) | u1_sub_mb_mode; + /* Storing collocated information */ + *pu1_col_info++ = u1_colz | ((UWORD8)(u1_sub_mb_mode << 4)); + COPYTHECONTEXT("sub_mb_type", u1_sub_mb_mode); + /* check if Motion compensation is done below 8x8 */ + if(u1_sub_mb_mode != P_L0_8x8) + { + u1_no_submb_part_size_lt8x8_flag = 0; + } + } + u1_num_mb_part = 4; + } + else + { + u1_num_mb_part = pu1_num_mb_part[u1_mb_type]; + /* Storing collocated Mb and SubMb mode information */ + *pu1_col_info++ = (u1_mb_type << 6); + if(u1_mb_type) + *pu1_col_info++ = (u1_mb_type << 6); + } + /* Decoding reference index 0: For simple profile the following */ + /* conditions are always true (mb_field_decoding_flag == 0); */ + /* (MbPartPredMode != PredL1) */ + { + WORD8 * pi1_top_ref_idx_ctx_inc_arr = p_curr_ctxt->i1_ref_idx; + WORD8 * pi1_left_ref_idx_ctxt_inc = ps_dec->pi1_left_ref_idx_ctxt_inc; + UWORD8 u1_mbaff = ps_dec->ps_cur_slice->u1_mbaff_frame_flag; + UWORD8 uc_field = ps_cur_mb_info->u1_mb_field_decodingflag; + UWORD8 uc_num_ref_idx_l0_active_minus1 = + (ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[0] + << (u1_mbaff & uc_field)) - 1; + + if((uc_num_ref_idx_l0_active_minus1 > 0) & (u1_mb_type != PRED_8x8R0)) + { + /* force the routine to decode ref idx for each partition */ + *((UWORD32 *)pi1_ref_idx) = 0x01010101; + ret = ih264d_parse_ref_idx_cabac(u1_num_mb_part, 0, + uc_num_ref_idx_l0_active_minus1, + u1_mb_mc_mode, pi1_ref_idx, + pi1_left_ref_idx_ctxt_inc, + pi1_top_ref_idx_ctx_inc_arr, ps_cab_env, + ps_bitstrm, ps_dec->p_ref_idx_t); + if(ret != OK) + return ret; + } + else + { + /* When there exists only a single frame to predict from */ + pi1_left_ref_idx_ctxt_inc[0] = 0; + pi1_left_ref_idx_ctxt_inc[1] = 0; + pi1_top_ref_idx_ctx_inc_arr[0] = 0; + pi1_top_ref_idx_ctx_inc_arr[1] = 0; + *((UWORD32 *)pi1_ref_idx) = 0; + } + } + + { + UWORD8 u1_p_idx, uc_i; + parse_part_params_t * ps_part = ps_dec->ps_part; + UWORD8 u1_sub_mb_mode, u1_num_subpart, u1_mb_part_width, u1_mb_part_height; + UWORD8 u1_sub_mb_num; + const UWORD8 * pu1_top_left_sub_mb_indx; + mv_pred_t *ps_mv_start = ps_dec->ps_mv_cur + (u1_mb_num << 4); + UWORD16 u2_sub_mb_num_pack = 0x028A; + + /* Loading the table pointers */ + const UWORD8 * pu1_mb_partw = (const UWORD8 *)gau1_ih264d_mb_partw; + const UWORD8 * pu1_mb_parth = (const UWORD8 *)gau1_ih264d_mb_parth; + const UWORD8 * pu1_sub_mb_indx_mod = + (const UWORD8 *)(gau1_ih264d_submb_indx_mod) + + (uc_sub_mb * 6); + const UWORD8 * pu1_sub_mb_partw = (const UWORD8 *)gau1_ih264d_submb_partw; + const UWORD8 * pu1_sub_mb_parth = (const UWORD8 *)gau1_ih264d_submb_parth; + const UWORD8 * pu1_num_sub_mb_part = + (const UWORD8 *)gau1_ih264d_num_submb_part; + + /*********************************************************/ + /* default initialisations for condition (uc_sub_mb == 0) */ + /* i.e. all are subpartitions of 8x8 */ + /*********************************************************/ + u1_sub_mb_mode = 0; + u1_num_subpart = 1; + u1_mb_part_width = pu1_mb_partw[u1_mb_type]; + u1_mb_part_height = pu1_mb_parth[u1_mb_type]; + pu1_top_left_sub_mb_indx = pu1_sub_mb_indx_mod + (u1_mb_type << 1); + u1_sub_mb_num = 0; + + /* Loop on number of partitions */ + for(uc_i = 0, u1_p_idx = 0; uc_i < u1_num_mb_part; uc_i++) + { + UWORD8 uc_j; + if(uc_sub_mb) + { + u1_sub_mb_mode = u4_sub_mb_pack >> 24; + u1_num_subpart = pu1_num_sub_mb_part[u1_sub_mb_mode]; + u1_mb_part_width = pu1_sub_mb_partw[u1_sub_mb_mode]; + u1_mb_part_height = pu1_sub_mb_parth[u1_sub_mb_mode]; + pu1_top_left_sub_mb_indx = pu1_sub_mb_indx_mod + (u1_sub_mb_mode << 1); + u1_sub_mb_num = u2_sub_mb_num_pack >> 12; + u4_sub_mb_pack <<= 8; + u2_sub_mb_num_pack <<= 4; + } + /* Loop on Number of sub-partitions */ + for(uc_j = 0; uc_j < u1_num_subpart; uc_j++, pu1_top_left_sub_mb_indx++) + { + mv_pred_t * ps_mv; + + u1_sub_mb_num += *pu1_top_left_sub_mb_indx; + ps_mv = ps_mv_start + u1_sub_mb_num; + + /* Storing Info for partitions */ + ps_part->u1_is_direct = PART_NOT_DIRECT; + ps_part->u1_sub_mb_num = u1_sub_mb_num; + ps_part->u1_partheight = u1_mb_part_height; + ps_part->u1_partwidth = u1_mb_part_width; + + /* Increment partition Index */ + u1_p_idx++; + ps_part++; + + ih264d_get_mvd_cabac(u1_sub_mb_num, 0, u1_mb_part_width, + u1_mb_part_height, 1, ps_dec, ps_mv); + } + } + ps_parse_mb_data->u1_num_part = u1_p_idx; + ps_dec->ps_part = ps_part; + } + { + UWORD8 u1_cbp; + + /* Read the Coded block pattern */ + u1_cbp = (WORD8)ih264d_parse_ctx_cbp_cabac(ps_dec); + COPYTHECONTEXT("coded_block_pattern", u1_cbp); + ps_cur_mb_info->u1_cbp = u1_cbp; + p_curr_ctxt->u1_cbp = u1_cbp; + p_curr_ctxt->u1_intra_chroma_pred_mode = 0; + p_curr_ctxt->u1_yuv_dc_csbp &= 0xFE; + ps_dec->pu1_left_yuv_dc_csbp[0] &= 0x6; + + if(u1_cbp > 47) + return ERROR_CBP; + + ps_cur_mb_info->u1_tran_form8x8 = 0; + ps_cur_mb_info->ps_curmb->u1_tran_form8x8 = 0; + + /* Read the transform8x8 u4_flag if present */ + if((ps_dec->s_high_profile.u1_transform8x8_present) && (u1_cbp & 0xf) + && u1_no_submb_part_size_lt8x8_flag) + { + ps_cur_mb_info->u1_tran_form8x8 = ih264d_parse_transform8x8flag_cabac( + ps_dec, ps_cur_mb_info); + COPYTHECONTEXT("transform_size_8x8_flag", ps_cur_mb_info->u1_tran_form8x8); + p_curr_ctxt->u1_transform8x8_ctxt = ps_cur_mb_info->u1_tran_form8x8; + ps_cur_mb_info->ps_curmb->u1_tran_form8x8 = ps_cur_mb_info->u1_tran_form8x8; + + } + else + { + p_curr_ctxt->u1_transform8x8_ctxt = 0; + } + + /* Read mb_qp_delta */ + if(u1_cbp) + { + WORD8 c_temp; + ret = ih264d_parse_mb_qp_delta_cabac(ps_dec, &c_temp); + if(ret != OK) + return ret; + COPYTHECONTEXT("mb_qp_delta", c_temp); + if(c_temp != 0) + { + ret = ih264d_update_qp(ps_dec, c_temp); + if(ret != OK) + return ret; + } + } + else + ps_dec->i1_prev_mb_qp_delta = 0; + + + + ih264d_parse_residual4x4_cabac(ps_dec, ps_cur_mb_info, 0); + if(EXCEED_OFFSET(ps_dec->ps_bitstrm)) + return ERROR_EOB_TERMINATE_T; + } + return OK; +} + +/*! + ************************************************************************** + * \if Function name : parsePSliceData \endif + * + * \brief + * This function parses CAVLC syntax of N MB's of a P slice. + * 1. After parsing syntax of N MB's, for those N MB's (less than N, incase + * of end of slice or end of row), MB is decoded. This process is carried + * for one complete MB row or till end of slice. + * 2. Bottom one row of current MB is copied to IntraPredLine buffers. + * IntraPredLine buffers are used for Intra prediction of next row. + * 3. Current MB row along with previous 4 rows of Luma (and 2 of Chroma) are + * deblocked. + * 4. 4 rows (2 for Chroma) previous row and 12 rows (6 for Chroma) are + * DMA'ed to picture buffers. + * + * \return + * 0 on Success and Error code otherwise + ************************************************************************** + */ + +/*! + ************************************************************************** + * \if Function name : ih264d_update_nnz_for_skipmb \endif + * + * \brief + * + * \return + * None + * + ************************************************************************** + */ +void ih264d_update_nnz_for_skipmb(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info, + UWORD8 u1_entrpy) +{ + UWORD32 *pu4_buf; + UWORD8 *pu1_buf; + UNUSED(u1_entrpy); + pu1_buf = ps_dec->pu1_left_nnz_y; + pu4_buf = (UWORD32 *)pu1_buf; + *pu4_buf = 0; + pu1_buf = ps_dec->pu1_left_nnz_uv; + pu4_buf = (UWORD32 *)pu1_buf; + *pu4_buf = 0; + pu1_buf = ps_cur_mb_info->ps_curmb->pu1_nnz_y; + pu4_buf = (UWORD32 *)pu1_buf; + *pu4_buf = 0; + pu1_buf = ps_cur_mb_info->ps_curmb->pu1_nnz_uv; + pu4_buf = (UWORD32 *)pu1_buf; + *pu4_buf = 0; + ps_cur_mb_info->ps_curmb->u2_luma_csbp = 0; + ps_cur_mb_info->u2_luma_csbp = 0; + ps_cur_mb_info->u2_chroma_csbp = 0; +} + + + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_parse_inter_slice_data_cabac */ +/* */ +/* Description : This function parses cabac syntax of a inter slice on */ +/* N MB basis. */ +/* */ +/* Inputs : ps_dec */ +/* sliceparams */ +/* firstMbInSlice */ +/* */ +/* Processing : 1. After parsing syntax for N MBs those N MBs are */ +/* decoded till the end of slice. */ +/* 2. MV prediction and DMA happens on a N/2 MB basis. */ +/* */ +/* Returns : 0 */ +/* */ +/* Issues : <List any issues or problems with this function> */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 13 07 2002 Jay Draft */ +/* */ +/*****************************************************************************/ +WORD32 ih264d_parse_inter_slice_data_cabac(dec_struct_t * ps_dec, + dec_slice_params_t * ps_slice, + UWORD16 u2_first_mb_in_slice) +{ + UWORD32 uc_more_data_flag; + WORD32 i2_cur_mb_addr; + UWORD32 u1_num_mbs, u1_num_mbsNby2, u1_mb_idx; + UWORD32 u1_mbaff; + UWORD32 u1_num_mbs_next, u1_end_of_row; + const UWORD16 i2_pic_wdin_mbs = ps_dec->u2_frm_wd_in_mbs; + UWORD32 u1_slice_end = 0; + UWORD32 u1_tfr_n_mb = 0; + UWORD32 u1_decode_nmb = 0; + + + deblk_mb_t *ps_cur_deblk_mb; + dec_mb_info_t *ps_cur_mb_info; + + parse_pmbarams_t *ps_parse_mb_data = ps_dec->ps_parse_mb_data; + UWORD32 u1_inter_mb_skip_type; + UWORD32 u1_inter_mb_type; + UWORD32 u1_deblk_mb_type; + UWORD32 u1_mb_threshold; + dec_bit_stream_t * const ps_bitstrm = ps_dec->ps_bitstrm; + WORD32 ret = OK; + + /******************************************************/ + /* Initialisations specific to B or P slice */ + /******************************************************/ + if(ps_slice->u1_slice_type == P_SLICE) + { + u1_inter_mb_skip_type = CAB_P_SKIP; + u1_inter_mb_type = P_MB; + u1_deblk_mb_type = D_INTER_MB; + u1_mb_threshold = 5; + } + else // B_SLICE + { + u1_inter_mb_skip_type = CAB_B_SKIP; + u1_inter_mb_type = B_MB; + u1_deblk_mb_type = D_B_SLICE; + u1_mb_threshold = 23; + } + + /******************************************************/ + /* Slice Level Initialisations */ + /******************************************************/ + i2_cur_mb_addr = u2_first_mb_in_slice; + ps_dec->u1_qp = ps_slice->u1_slice_qp; + ih264d_update_qp(ps_dec, 0); + u1_mb_idx = ps_dec->u1_mb_idx; + u1_num_mbs = u1_mb_idx; + u1_num_mbsNby2 = 0; + u1_mbaff = ps_slice->u1_mbaff_frame_flag; + i2_cur_mb_addr = u2_first_mb_in_slice << u1_mbaff; + uc_more_data_flag = 1; + + /* Initialisations specific to cabac */ + if(ps_bitstrm->u4_ofst & 0x07) + { + ps_bitstrm->u4_ofst += 8; + ps_bitstrm->u4_ofst &= 0xFFFFFFF8; + } + + ret = ih264d_init_cabac_dec_envirnoment(&(ps_dec->s_cab_dec_env), ps_bitstrm); + if(ret != OK) + return ret; + + ps_dec->i1_prev_mb_qp_delta = 0; + + while(!u1_slice_end) + { + UWORD8 u1_mb_type; + UWORD32 u4_mb_skip; + + ps_dec->pv_prev_mb_parse_tu_coeff_data = ps_dec->pv_parse_tu_coeff_data; + + if(i2_cur_mb_addr > ps_dec->ps_cur_sps->u2_max_mb_addr) + { + break; + } + + ps_cur_mb_info = ps_dec->ps_nmb_info + u1_num_mbs; + ps_dec->u4_num_mbs_cur_nmb = u1_num_mbs; + + ps_cur_mb_info->u1_Mux = 0; + ps_dec->u4_num_pmbair = (u1_num_mbs >> u1_mbaff); + ps_cur_deblk_mb = ps_dec->ps_deblk_mbn + u1_num_mbs; + + ps_cur_mb_info->u1_end_of_slice = 0; + + /* Storing Default partition info */ + ps_parse_mb_data->u1_num_part = 1; + ps_parse_mb_data->u1_isI_mb = 0; + + /***************************************************************/ + /* Get the required information for decoding of MB */ + /* mb_x, mb_y , neighbour availablity, */ + /***************************************************************/ + u4_mb_skip = ps_dec->pf_get_mb_info(ps_dec, i2_cur_mb_addr, ps_cur_mb_info, 1); + + /*********************************************************************/ + /* initialize u1_tran_form8x8 to zero to aviod uninitialized accesses */ + /*********************************************************************/ + ps_cur_mb_info->u1_tran_form8x8 = 0; + ps_cur_mb_info->ps_curmb->u1_tran_form8x8 = 0; + + /***************************************************************/ + /* Set the deblocking parameters for this MB */ + /***************************************************************/ + if(ps_dec->u4_app_disable_deblk_frm == 0) + ih264d_set_deblocking_parameters(ps_cur_deblk_mb, ps_slice, + ps_dec->u1_mb_ngbr_availablity, + ps_dec->u1_cur_mb_fld_dec_flag); + + if(u4_mb_skip) + { + + /* Set appropriate flags in ps_cur_mb_info and ps_dec */ + memset(ps_dec->ps_curr_ctxt_mb_info, 0, sizeof(ctxt_inc_mb_info_t)); + ps_dec->ps_curr_ctxt_mb_info->u1_mb_type = u1_inter_mb_skip_type; + + MEMSET_16BYTES(&ps_dec->pu1_left_mv_ctxt_inc[0][0], 0); + + *((UWORD32 *)ps_dec->pi1_left_ref_idx_ctxt_inc) = 0; + *(ps_dec->pu1_left_yuv_dc_csbp) = 0; + + ps_dec->i1_prev_mb_qp_delta = 0; + ps_cur_mb_info->u1_mb_type = MB_SKIP; + ps_cur_mb_info->u1_cbp = 0; + + { + /* Storing Skip partition info */ + parse_part_params_t *ps_part_info = ps_dec->ps_part; + ps_part_info->u1_is_direct = PART_DIRECT_16x16; + ps_part_info->u1_sub_mb_num = 0; + ps_dec->ps_part++; + } + + /* Update Nnzs */ + ih264d_update_nnz_for_skipmb(ps_dec, ps_cur_mb_info, CABAC); + + ps_cur_mb_info->ps_curmb->u1_mb_type = u1_inter_mb_type; + ps_cur_deblk_mb->u1_mb_type |= u1_deblk_mb_type; + ps_cur_deblk_mb->u1_mb_qp = ps_dec->u1_qp; + + } + else + { + + /* Macroblock Layer Begins */ + /* Decode the u1_mb_type */ + u1_mb_type = ih264d_parse_mb_type_cabac(ps_dec); + ps_cur_mb_info->u1_mb_type = u1_mb_type; + if(u1_mb_type > (25 + u1_mb_threshold)) + return ERROR_MB_TYPE; + + /* Parse Macroblock Data */ + if(u1_mb_type < u1_mb_threshold) + { + ps_cur_mb_info->ps_curmb->u1_mb_type = u1_inter_mb_type; + *(ps_dec->pu1_left_yuv_dc_csbp) &= 0x6; + + ret = ps_dec->pf_parse_inter_mb(ps_dec, ps_cur_mb_info, u1_num_mbs, + u1_num_mbsNby2); + if(ret != OK) + return ret; + ps_cur_deblk_mb->u1_mb_qp = ps_dec->u1_qp; + ps_cur_deblk_mb->u1_mb_type |= u1_deblk_mb_type; + } + else + { + /* Storing Intra partition info */ + ps_parse_mb_data->u1_num_part = 0; + ps_parse_mb_data->u1_isI_mb = 1; + + if((25 + u1_mb_threshold) == u1_mb_type) + { + /* I_PCM_MB */ + ps_cur_mb_info->ps_curmb->u1_mb_type = I_PCM_MB; + ret = ih264d_parse_ipcm_mb(ps_dec, ps_cur_mb_info, u1_num_mbs); + if(ret != OK) + return ret; + ps_cur_deblk_mb->u1_mb_qp = 0; + } + else + { + if(u1_mb_type == u1_mb_threshold) + ps_cur_mb_info->ps_curmb->u1_mb_type = I_4x4_MB; + else + ps_cur_mb_info->ps_curmb->u1_mb_type = I_16x16_MB; + + ret = ih264d_parse_imb_cabac( + ps_dec, ps_cur_mb_info, + (UWORD8)(u1_mb_type - u1_mb_threshold)); + if(ret != OK) + return ret; + ps_cur_deblk_mb->u1_mb_qp = ps_dec->u1_qp; + } + ps_cur_deblk_mb->u1_mb_type |= D_INTRA_MB; + + } + + } + + if(u1_mbaff) + { + ih264d_update_mbaff_left_nnz(ps_dec, ps_cur_mb_info); + } + + + if(ps_cur_mb_info->u1_topmb && u1_mbaff) + uc_more_data_flag = 1; + else + { + uc_more_data_flag = ih264d_decode_terminate(&ps_dec->s_cab_dec_env, + ps_bitstrm); + uc_more_data_flag = !uc_more_data_flag; + COPYTHECONTEXT("Decode Sliceterm",!uc_more_data_flag); + } + + if(u1_mbaff) + { + if(!uc_more_data_flag && (0 == (i2_cur_mb_addr & 1))) + { + return ERROR_EOB_FLUSHBITS_T; + } + } + /* Next macroblock information */ + i2_cur_mb_addr++; + u1_num_mbs++; + u1_num_mbsNby2++; + ps_parse_mb_data++; + + /****************************************************************/ + /* Check for End Of Row and other flags that determine when to */ + /* do DMA setup for N/2-Mb, Decode for N-Mb, and Transfer for */ + /* N-Mb */ + /****************************************************************/ + u1_num_mbs_next = i2_pic_wdin_mbs - ps_dec->u2_mbx - 1; + u1_end_of_row = (!u1_num_mbs_next) && (!(u1_mbaff && (u1_num_mbs & 0x01))); + u1_slice_end = !uc_more_data_flag; + u1_tfr_n_mb = (u1_num_mbs == ps_dec->u1_recon_mb_grp) || u1_end_of_row + || u1_slice_end; + u1_decode_nmb = u1_tfr_n_mb || u1_slice_end; + ps_cur_mb_info->u1_end_of_slice = u1_slice_end; + /*u1_dma_nby2mb = u1_decode_nmb || + (u1_num_mbsNby2 == ps_dec->u1_recon_mb_grp_pair);*/ + +//if(u1_dma_nby2mb) + if(u1_decode_nmb) + { + + ps_dec->pf_mvpred_ref_tfr_nby2mb(ps_dec, u1_mb_idx, u1_num_mbs); + u1_num_mbsNby2 = 0; + + { + ps_parse_mb_data = ps_dec->ps_parse_mb_data; + ps_dec->ps_part = ps_dec->ps_parse_part_params; + } + } + + /*H264_DEC_DEBUG_PRINT("Pic: %d Mb_X=%d Mb_Y=%d", + ps_slice->i4_poc >> ps_slice->u1_field_pic_flag, + ps_dec->u2_mbx,ps_dec->u2_mby + (1 - ps_cur_mb_info->u1_topmb)); + H264_DEC_DEBUG_PRINT("u1_decode_nmb: %d, u1_num_mbs: %d", u1_decode_nmb, u1_num_mbs);*/ + if(u1_decode_nmb) + { + + if(ps_dec->u1_separate_parse) + { + ih264d_parse_tfr_nmb(ps_dec, u1_mb_idx, u1_num_mbs, + u1_num_mbs_next, u1_tfr_n_mb, u1_end_of_row); + ps_dec->ps_nmb_info += u1_num_mbs; + } + else + { + ih264d_decode_recon_tfr_nmb(ps_dec, u1_mb_idx, u1_num_mbs, + u1_num_mbs_next, u1_tfr_n_mb, + u1_end_of_row); + } + ps_dec->u2_total_mbs_coded += u1_num_mbs; + if(u1_tfr_n_mb) + u1_num_mbs = 0; + u1_mb_idx = u1_num_mbs; + ps_dec->u1_mb_idx = u1_num_mbs; + + } + } + + + ps_dec->u4_num_mbs_cur_nmb = 0; + ps_dec->ps_cur_slice->u4_mbs_in_slice = i2_cur_mb_addr + + - (u2_first_mb_in_slice << u1_mbaff); + + return ret; +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_parse_inter_slice_data_cavlc */ +/* */ +/* Description : This function parses cavlc syntax of a inter slice on */ +/* N MB basis. */ +/* */ +/* Inputs : ps_dec */ +/* sliceparams */ +/* firstMbInSlice */ +/* */ +/* Processing : 1. After parsing syntax for N MBs those N MBs are */ +/* decoded till the end of slice. */ +/* 2. MV prediction and DMA happens on a N/2 MB basis. */ +/* */ +/* Returns : 0 */ +/* */ +/* Issues : <List any issues or problems with this function> */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 13 07 2002 Jay Draft */ +/* */ +/*****************************************************************************/ + +WORD32 ih264d_parse_inter_slice_data_cavlc(dec_struct_t * ps_dec, + dec_slice_params_t * ps_slice, + UWORD16 u2_first_mb_in_slice) +{ + UWORD32 uc_more_data_flag; + WORD32 i2_cur_mb_addr; + UWORD32 u1_num_mbs, u1_num_mbsNby2, u1_mb_idx; + UWORD32 i2_mb_skip_run; + UWORD32 u1_read_mb_type; + + UWORD32 u1_mbaff; + UWORD32 u1_num_mbs_next, u1_end_of_row; + const UWORD32 i2_pic_wdin_mbs = ps_dec->u2_frm_wd_in_mbs; + UWORD32 u1_slice_end = 0; + UWORD32 u1_tfr_n_mb = 0; + UWORD32 u1_decode_nmb = 0; + + dec_bit_stream_t * const ps_bitstrm = ps_dec->ps_bitstrm; + UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; + UWORD32 *pu4_bitstrm_ofst = &ps_bitstrm->u4_ofst; + deblk_mb_t *ps_cur_deblk_mb; + dec_mb_info_t *ps_cur_mb_info; + parse_pmbarams_t *ps_parse_mb_data = ps_dec->ps_parse_mb_data; + UWORD32 u1_inter_mb_type; + UWORD32 u1_deblk_mb_type; + UWORD32 u1_mb_threshold; + WORD32 ret = OK; + + /******************************************************/ + /* Initialisations specific to B or P slice */ + /******************************************************/ + + if(ps_slice->u1_slice_type == P_SLICE) + { + u1_inter_mb_type = P_MB; + u1_deblk_mb_type = D_INTER_MB; + u1_mb_threshold = 5; + } + else // B_SLICE + { + u1_inter_mb_type = B_MB; + u1_deblk_mb_type = D_B_SLICE; + u1_mb_threshold = 23; + } + /******************************************************/ + /* Slice Level Initialisations */ + /******************************************************/ + ps_dec->u1_qp = ps_slice->u1_slice_qp; + ih264d_update_qp(ps_dec, 0); + u1_mb_idx = ps_dec->u1_mb_idx; + u1_num_mbs = u1_mb_idx; + + u1_num_mbsNby2 = 0; + u1_mbaff = ps_slice->u1_mbaff_frame_flag; + i2_cur_mb_addr = u2_first_mb_in_slice << u1_mbaff; + i2_mb_skip_run = 0; + uc_more_data_flag = 1; + u1_read_mb_type = 0; + + while(!u1_slice_end) + { + UWORD8 u1_mb_type; + + ps_dec->pv_prev_mb_parse_tu_coeff_data = ps_dec->pv_parse_tu_coeff_data; + + if(i2_cur_mb_addr > ps_dec->ps_cur_sps->u2_max_mb_addr) + { + break; + } + + + ps_cur_mb_info = ps_dec->ps_nmb_info + u1_num_mbs; + ps_dec->u4_num_mbs_cur_nmb = u1_num_mbs; + + ps_cur_mb_info->u1_Mux = 0; + ps_dec->u4_num_pmbair = (u1_num_mbs >> u1_mbaff); + ps_cur_deblk_mb = ps_dec->ps_deblk_mbn + u1_num_mbs; + + ps_cur_mb_info->u1_end_of_slice = 0; + + /* Storing Default partition info */ + ps_parse_mb_data->u1_num_part = 1; + ps_parse_mb_data->u1_isI_mb = 0; + + if((!i2_mb_skip_run) && (!u1_read_mb_type)) + { + + //Inlined ih264d_uev + UWORD32 u4_bitstream_offset = *pu4_bitstrm_ofst; + UWORD32 u4_word, u4_ldz; + + /***************************************************************/ + /* Find leading zeros in next 32 bits */ + /***************************************************************/ + NEXTBITS_32(u4_word, u4_bitstream_offset, pu4_bitstrm_buf); + + u4_ldz = CLZ(u4_word); + + /* Flush the ps_bitstrm */ + u4_bitstream_offset += (u4_ldz + 1); + /* Read the suffix from the ps_bitstrm */ + u4_word = 0; + if(u4_ldz) + { + GETBITS(u4_word, u4_bitstream_offset, pu4_bitstrm_buf, + u4_ldz); + } + *pu4_bitstrm_ofst = u4_bitstream_offset; + i2_mb_skip_run = ((1 << u4_ldz) + u4_word - 1); + //Inlined ih264d_uev + COPYTHECONTEXT("mb_skip_run", i2_mb_skip_run); + uc_more_data_flag = MORE_RBSP_DATA(ps_bitstrm); + u1_read_mb_type = uc_more_data_flag; + } + + /***************************************************************/ + /* Get the required information for decoding of MB */ + /* mb_x, mb_y , neighbour availablity, */ + /***************************************************************/ + ps_dec->pf_get_mb_info(ps_dec, i2_cur_mb_addr, ps_cur_mb_info, i2_mb_skip_run); + + /***************************************************************/ + /* Set the deblocking parameters for this MB */ + /***************************************************************/ + if(ps_dec->u4_app_disable_deblk_frm == 0) + ih264d_set_deblocking_parameters(ps_cur_deblk_mb, ps_slice, + ps_dec->u1_mb_ngbr_availablity, + ps_dec->u1_cur_mb_fld_dec_flag); + + if(i2_mb_skip_run) + { + /* Set appropriate flags in ps_cur_mb_info and ps_dec */ + ps_dec->i1_prev_mb_qp_delta = 0; + ps_dec->u1_sub_mb_num = 0; + ps_cur_mb_info->u1_mb_type = MB_SKIP; + ps_cur_mb_info->u1_mb_mc_mode = PRED_16x16; + ps_cur_mb_info->u1_cbp = 0; + + { + /* Storing Skip partition info */ + parse_part_params_t *ps_part_info = ps_dec->ps_part; + ps_part_info->u1_is_direct = PART_DIRECT_16x16; + ps_part_info->u1_sub_mb_num = 0; + ps_dec->ps_part++; + } + + /* Update Nnzs */ + ih264d_update_nnz_for_skipmb(ps_dec, ps_cur_mb_info, CAVLC); + + ps_cur_mb_info->ps_curmb->u1_mb_type = u1_inter_mb_type; + ps_cur_deblk_mb->u1_mb_type |= u1_deblk_mb_type; + + i2_mb_skip_run--; + } + else + { + u1_read_mb_type = 0; + /**************************************************************/ + /* Macroblock Layer Begins, Decode the u1_mb_type */ + /**************************************************************/ + { + UWORD32 u4_bitstream_offset = *pu4_bitstrm_ofst; + UWORD32 u4_word, u4_ldz, u4_temp; + + + //Inlined ih264d_uev + /***************************************************************/ + /* Find leading zeros in next 32 bits */ + /***************************************************************/ + NEXTBITS_32(u4_word, u4_bitstream_offset, pu4_bitstrm_buf); + u4_ldz = CLZ(u4_word); + /* Flush the ps_bitstrm */ + u4_bitstream_offset += (u4_ldz + 1); + /* Read the suffix from the ps_bitstrm */ + u4_word = 0; + if(u4_ldz) + GETBITS(u4_word, u4_bitstream_offset, pu4_bitstrm_buf, + u4_ldz); + *pu4_bitstrm_ofst = u4_bitstream_offset; + u4_temp = ((1 << u4_ldz) + u4_word - 1); + //Inlined ih264d_uev + if(u4_temp > (UWORD32)(25 + u1_mb_threshold)) + return ERROR_MB_TYPE; + u1_mb_type = u4_temp; + COPYTHECONTEXT("u1_mb_type", u1_mb_type); + } + ps_cur_mb_info->u1_mb_type = u1_mb_type; + + /**************************************************************/ + /* Parse Macroblock data */ + /**************************************************************/ + if(u1_mb_type < u1_mb_threshold) + { + ps_cur_mb_info->ps_curmb->u1_mb_type = u1_inter_mb_type; + + ret = ps_dec->pf_parse_inter_mb(ps_dec, ps_cur_mb_info, u1_num_mbs, + u1_num_mbsNby2); + if(ret != OK) + return ret; + ps_cur_deblk_mb->u1_mb_type |= u1_deblk_mb_type; + } + else + { + /* Storing Intra partition info */ + ps_parse_mb_data->u1_num_part = 0; + ps_parse_mb_data->u1_isI_mb = 1; + + if((25 + u1_mb_threshold) == u1_mb_type) + { + /* I_PCM_MB */ + ps_cur_mb_info->ps_curmb->u1_mb_type = I_PCM_MB; + ret = ih264d_parse_ipcm_mb(ps_dec, ps_cur_mb_info, u1_num_mbs); + if(ret != OK) + return ret; + ps_dec->u1_qp = 0; + } + else + { + ret = ih264d_parse_imb_cavlc( + ps_dec, ps_cur_mb_info, u1_num_mbs, + (UWORD8)(u1_mb_type - u1_mb_threshold)); + if(ret != OK) + return ret; + } + + ps_cur_deblk_mb->u1_mb_type |= D_INTRA_MB; + } + uc_more_data_flag = MORE_RBSP_DATA(ps_bitstrm); + } + ps_cur_deblk_mb->u1_mb_qp = ps_dec->u1_qp; + + if(u1_mbaff) + { + ih264d_update_mbaff_left_nnz(ps_dec, ps_cur_mb_info); + if(!uc_more_data_flag && !i2_mb_skip_run && (0 == (i2_cur_mb_addr & 1))) + { + return ERROR_EOB_FLUSHBITS_T; + } + } + /**************************************************************/ + /* Get next Macroblock address */ + /**************************************************************/ + i2_cur_mb_addr++; + + u1_num_mbs++; + u1_num_mbsNby2++; + ps_parse_mb_data++; + + /****************************************************************/ + /* Check for End Of Row and other flags that determine when to */ + /* do DMA setup for N/2-Mb, Decode for N-Mb, and Transfer for */ + /* N-Mb */ + /****************************************************************/ + u1_num_mbs_next = i2_pic_wdin_mbs - ps_dec->u2_mbx - 1; + u1_end_of_row = (!u1_num_mbs_next) && (!(u1_mbaff && (u1_num_mbs & 0x01))); + u1_slice_end = (!(uc_more_data_flag || i2_mb_skip_run)); + u1_tfr_n_mb = (u1_num_mbs == ps_dec->u1_recon_mb_grp) || u1_end_of_row + || u1_slice_end; + u1_decode_nmb = u1_tfr_n_mb || u1_slice_end; + ps_cur_mb_info->u1_end_of_slice = u1_slice_end; + + /*u1_dma_nby2mb = u1_decode_nmb || + (u1_num_mbsNby2 == ps_dec->u1_recon_mb_grp_pair);*/ + +//if(u1_dma_nby2mb) + if(u1_decode_nmb) + { + ps_dec->pf_mvpred_ref_tfr_nby2mb(ps_dec, u1_mb_idx, u1_num_mbs); + u1_num_mbsNby2 = 0; + + { + ps_parse_mb_data = ps_dec->ps_parse_mb_data; + ps_dec->ps_part = ps_dec->ps_parse_part_params; + } + } + + /*H264_DEC_DEBUG_PRINT("Pic: %d Mb_X=%d Mb_Y=%d", + ps_slice->i4_poc >> ps_slice->u1_field_pic_flag, + ps_dec->u2_mbx,ps_dec->u2_mby + (1 - ps_cur_mb_info->u1_topmb)); + H264_DEC_DEBUG_PRINT("u1_decode_nmb: %d", u1_decode_nmb);*/ + if(u1_decode_nmb) + { + + + + if(ps_dec->u1_separate_parse) + { + ih264d_parse_tfr_nmb(ps_dec, u1_mb_idx, u1_num_mbs, + u1_num_mbs_next, u1_tfr_n_mb, u1_end_of_row); + ps_dec->ps_nmb_info += u1_num_mbs; + } + else + { + ih264d_decode_recon_tfr_nmb(ps_dec, u1_mb_idx, u1_num_mbs, + u1_num_mbs_next, u1_tfr_n_mb, + u1_end_of_row); + } + ps_dec->u2_total_mbs_coded += u1_num_mbs; + if(u1_tfr_n_mb) + u1_num_mbs = 0; + u1_mb_idx = u1_num_mbs; + ps_dec->u1_mb_idx = u1_num_mbs; + + } +//ps_dec->ps_pred++; + } + + ps_dec->u4_num_mbs_cur_nmb = 0; + ps_dec->ps_cur_slice->u4_mbs_in_slice = i2_cur_mb_addr + - (u2_first_mb_in_slice << u1_mbaff); + + + return ret; +} + +WORD32 ih264d_mark_err_slice_skip(dec_struct_t * ps_dec, + WORD32 num_mb_skip, + UWORD8 u1_is_idr_slice, + UWORD16 u2_frame_num, + pocstruct_t *ps_cur_poc, + WORD32 prev_slice_err) +{ + WORD32 i2_cur_mb_addr; + UWORD32 u1_num_mbs, u1_num_mbsNby2; + UWORD32 u1_mb_idx = ps_dec->u1_mb_idx; + UWORD32 i2_mb_skip_run; + + UWORD32 u1_num_mbs_next, u1_end_of_row; + const UWORD32 i2_pic_wdin_mbs = ps_dec->u2_frm_wd_in_mbs; + UWORD32 u1_slice_end; + UWORD32 u1_tfr_n_mb; + UWORD32 u1_decode_nmb; + dec_bit_stream_t * const ps_bitstrm = ps_dec->ps_bitstrm; + dec_slice_params_t * ps_slice = ps_dec->ps_cur_slice; + UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; + UWORD32 *pu4_bitstrm_ofst = &ps_bitstrm->u4_ofst; + deblk_mb_t *ps_cur_deblk_mb; + dec_mb_info_t *ps_cur_mb_info; + parse_pmbarams_t *ps_parse_mb_data; + UWORD32 u1_inter_mb_type; + UWORD32 u1_deblk_mb_type; + UWORD16 u2_total_mbs_coded; + UWORD32 u1_mbaff; + parse_part_params_t *ps_part_info; + WORD32 ret; + UNUSED(u1_is_idr_slice); + + if(ps_dec->ps_dec_err_status->u1_err_flag & REJECT_CUR_PIC) + { + ih264d_err_pic_dispbuf_mgr(ps_dec); + return 0; + } + + if(ps_dec->ps_cur_slice->u1_mbaff_frame_flag && (num_mb_skip & 1)) + { + num_mb_skip++; + } + ps_dec->ps_dpb_cmds->u1_long_term_reference_flag = 0; + if(prev_slice_err == 1) + { + /* first slice - missing/header corruption */ + ps_dec->ps_cur_slice->u2_frame_num = u2_frame_num; + { + WORD32 i, j, poc = 0; + + ps_dec->ps_cur_slice->u2_first_mb_in_slice = 0; + + ps_dec->pf_mvpred = ih264d_mvpred_nonmbaff; + ps_dec->p_form_mb_part_info = ih264d_form_mb_part_info_bp; + ps_dec->p_motion_compensate = ih264d_motion_compensate_bp; + + if(ps_dec->ps_cur_pic != NULL) + { + poc = ps_dec->ps_cur_pic->i4_poc; + if (poc <= INT32_MAX - 2) + poc += 2; + } + + j = -1; + for(i = 0; i < MAX_NUM_PIC_PARAMS; i++) + { + if(ps_dec->ps_pps[i].u1_is_valid == TRUE) + { + if(ps_dec->ps_pps[i].ps_sps->u1_is_valid == TRUE) + { + j = i; + break; + } + } + } + + //if valid SPS PPS is not found return error + if(j == -1) + { + return ERROR_INV_SLICE_HDR_T; + } + + /* call ih264d_start_of_pic only if it was not called earlier*/ + if(ps_dec->u4_pic_buf_got == 0) + { + //initialize slice params required by ih264d_start_of_pic to valid values + ps_dec->ps_cur_slice->u1_slice_type = P_SLICE; + ps_dec->ps_cur_slice->u1_nal_ref_idc = 1; + ps_dec->ps_cur_slice->u1_nal_unit_type = 1; + ret = ih264d_start_of_pic(ps_dec, poc, ps_cur_poc, + ps_dec->ps_cur_slice->u2_frame_num, + &ps_dec->ps_pps[j]); + + if(ret != OK) + { + return ret; + } + } + + ps_dec->ps_ref_pic_buf_lx[0][0]->u1_pic_buf_id = 0; + + ps_dec->u4_output_present = 0; + + { + ih264d_get_next_display_field(ps_dec, + ps_dec->ps_out_buffer, + &(ps_dec->s_disp_op)); + /* If error code is non-zero then there is no buffer available for display, + hence avoid format conversion */ + + if(0 != ps_dec->s_disp_op.u4_error_code) + { + ps_dec->u4_fmt_conv_cur_row = ps_dec->s_disp_frame_info.u4_y_ht; + } + else + ps_dec->u4_output_present = 1; + } + + if(ps_dec->u1_separate_parse == 1) + { + if(ps_dec->u4_dec_thread_created == 0) + { + ithread_create(ps_dec->pv_dec_thread_handle, NULL, + (void *)ih264d_decode_picture_thread, + (void *)ps_dec); + + ps_dec->u4_dec_thread_created = 1; + } + + if((ps_dec->u4_num_cores == 3) && + ((ps_dec->u4_app_disable_deblk_frm == 0) || ps_dec->i1_recon_in_thread3_flag) + && (ps_dec->u4_bs_deblk_thread_created == 0)) + { + ps_dec->u4_start_recon_deblk = 0; + ithread_create(ps_dec->pv_bs_deblk_thread_handle, NULL, + (void *)ih264d_recon_deblk_thread, + (void *)ps_dec); + ps_dec->u4_bs_deblk_thread_created = 1; + } + } + } + } + else + { + // Middle / last slice + + dec_slice_struct_t *ps_parse_cur_slice; + ps_parse_cur_slice = ps_dec->ps_dec_slice_buf + ps_dec->u2_cur_slice_num; + + if(ps_dec->u1_slice_header_done + && ps_parse_cur_slice == ps_dec->ps_parse_cur_slice) + { + // Slice data corrupted + // in the case of mbaff, conceal from the even mb. + if((ps_dec->ps_cur_slice->u1_mbaff_frame_flag) && (ps_dec->u4_num_mbs_cur_nmb & 1)) + { + ps_dec->u4_num_mbs_cur_nmb = ps_dec->u4_num_mbs_cur_nmb - 1; + ps_dec->u2_cur_mb_addr--; + } + + u1_num_mbs = ps_dec->u4_num_mbs_cur_nmb; + if(u1_num_mbs) + { + ps_cur_mb_info = ps_dec->ps_nmb_info + u1_num_mbs - 1; + } + else + { + if(ps_dec->u1_separate_parse) + { + ps_cur_mb_info = ps_dec->ps_nmb_info; + } + else + { + ps_cur_mb_info = ps_dec->ps_nmb_info + + ps_dec->u4_num_mbs_prev_nmb - 1; + } + } + + ps_dec->u2_mby = ps_cur_mb_info->u2_mby; + ps_dec->u2_mbx = ps_cur_mb_info->u2_mbx; + + ps_dec->u1_mb_ngbr_availablity = + ps_cur_mb_info->u1_mb_ngbr_availablity; + + if(u1_num_mbs) + { + // Going back 1 mb + ps_dec->pv_parse_tu_coeff_data = ps_dec->pv_prev_mb_parse_tu_coeff_data; + ps_dec->u2_cur_mb_addr--; + ps_dec->i4_submb_ofst -= SUB_BLK_SIZE; + + // Parse/decode N-MB left unparsed + if (ps_dec->u1_pr_sl_type == P_SLICE + || ps_dec->u1_pr_sl_type == B_SLICE) + { + ps_dec->pf_mvpred_ref_tfr_nby2mb(ps_dec, u1_mb_idx, u1_num_mbs); + ps_dec->ps_part = ps_dec->ps_parse_part_params; + } + + u1_num_mbs_next = i2_pic_wdin_mbs - ps_dec->u2_mbx - 1; + u1_end_of_row = (!u1_num_mbs_next) + && (!(ps_dec->ps_cur_slice->u1_mbaff_frame_flag && (u1_num_mbs & 0x01))); + u1_slice_end = 1; + u1_tfr_n_mb = 1; + ps_cur_mb_info->u1_end_of_slice = u1_slice_end; + + if(ps_dec->u1_separate_parse) + { + ih264d_parse_tfr_nmb(ps_dec, u1_mb_idx, u1_num_mbs, + u1_num_mbs_next, u1_tfr_n_mb, u1_end_of_row); + ps_dec->ps_nmb_info += u1_num_mbs; + } + else + { + ih264d_decode_recon_tfr_nmb(ps_dec, u1_mb_idx, u1_num_mbs, + u1_num_mbs_next, u1_tfr_n_mb, u1_end_of_row); + } + ps_dec->u2_total_mbs_coded += u1_num_mbs; + ps_dec->u1_mb_idx = 0; + ps_dec->u4_num_mbs_cur_nmb = 0; + } + + if(ps_dec->u2_total_mbs_coded + >= ps_dec->u2_frm_ht_in_mbs * ps_dec->u2_frm_wd_in_mbs) + { + ps_dec->u1_pic_decode_done = 1; + return 0; + } + + /* Inserting new slice only if the current slice has atleast 1 MB*/ + if(ps_dec->ps_parse_cur_slice->u4_first_mb_in_slice < + (UWORD32)(ps_dec->u2_total_mbs_coded >> ps_slice->u1_mbaff_frame_flag)) + { + ps_dec->i2_prev_slice_mbx = ps_dec->u2_mbx; + ps_dec->i2_prev_slice_mby = ps_dec->u2_mby; + ps_dec->u2_cur_slice_num++; + ps_dec->ps_parse_cur_slice++; + } + + } + else + { + // Slice missing / header corrupted + ps_dec->ps_parse_cur_slice = ps_dec->ps_dec_slice_buf + + ps_dec->u2_cur_slice_num; + } + } + + /******************************************************/ + /* Initializations to new slice */ + /******************************************************/ + { + WORD32 num_entries; + WORD32 size; + UWORD8 *pu1_buf; + + num_entries = MAX_FRAMES; + if((1 >= ps_dec->ps_cur_sps->u1_num_ref_frames) && + (0 == ps_dec->i4_display_delay)) + { + num_entries = 1; + } + num_entries = ((2 * num_entries) + 1); + num_entries *= 2; + + size = num_entries * sizeof(void *); + size += PAD_MAP_IDX_POC * sizeof(void *); + + pu1_buf = (UWORD8 *)ps_dec->pv_map_ref_idx_to_poc_buf; + pu1_buf += size * ps_dec->u2_cur_slice_num; + ps_dec->ps_parse_cur_slice->ppv_map_ref_idx_to_poc = (volatile void **)pu1_buf; + } + u1_mbaff = ps_slice->u1_mbaff_frame_flag; + ps_dec->ps_cur_slice->u2_first_mb_in_slice = ps_dec->u2_total_mbs_coded >> u1_mbaff; + ps_dec->ps_cur_slice->i1_slice_alpha_c0_offset = 0; + ps_dec->ps_cur_slice->i1_slice_beta_offset = 0; + + if(ps_dec->ps_cur_slice->u1_field_pic_flag) + ps_dec->u2_prv_frame_num = ps_dec->ps_cur_slice->u2_frame_num; + + ps_dec->ps_parse_cur_slice->u4_first_mb_in_slice = ps_dec->u2_total_mbs_coded >> u1_mbaff; + ps_dec->ps_parse_cur_slice->u2_log2Y_crwd = ps_dec->ps_cur_slice->u2_log2Y_crwd; + + + if(ps_dec->u1_separate_parse) + { + ps_dec->ps_parse_cur_slice->pv_tu_coeff_data_start = ps_dec->pv_parse_tu_coeff_data; + } + else + { + ps_dec->pv_proc_tu_coeff_data = ps_dec->pv_parse_tu_coeff_data; + } + + /******************************************************/ + /* Initializations specific to P slice */ + /******************************************************/ + u1_inter_mb_type = P_MB; + u1_deblk_mb_type = D_INTER_MB; + + ps_dec->ps_cur_slice->u1_slice_type = P_SLICE; + ps_dec->ps_parse_cur_slice->slice_type = P_SLICE; + ps_dec->pf_mvpred_ref_tfr_nby2mb = ih264d_mv_pred_ref_tfr_nby2_pmb; + ps_dec->ps_part = ps_dec->ps_parse_part_params; + ps_dec->u2_mbx = + (MOD(ps_dec->ps_cur_slice->u2_first_mb_in_slice - 1, ps_dec->u2_frm_wd_in_mbs)); + ps_dec->u2_mby = + (DIV(ps_dec->ps_cur_slice->u2_first_mb_in_slice - 1, ps_dec->u2_frm_wd_in_mbs)); + ps_dec->u2_mby <<= u1_mbaff; + + /******************************************************/ + /* Parsing / decoding the slice */ + /******************************************************/ + ps_dec->u1_slice_header_done = 2; + ps_dec->u1_qp = ps_slice->u1_slice_qp; + ih264d_update_qp(ps_dec, 0); + u1_mb_idx = ps_dec->u1_mb_idx; + ps_parse_mb_data = ps_dec->ps_parse_mb_data; + u1_num_mbs = u1_mb_idx; + + u1_slice_end = 0; + u1_tfr_n_mb = 0; + u1_decode_nmb = 0; + u1_num_mbsNby2 = 0; + i2_cur_mb_addr = ps_dec->u2_total_mbs_coded; + i2_mb_skip_run = num_mb_skip; + + while(!u1_slice_end) + { + UWORD8 u1_mb_type; + + if(i2_cur_mb_addr > ps_dec->ps_cur_sps->u2_max_mb_addr) + break; + + ps_cur_mb_info = ps_dec->ps_nmb_info + u1_num_mbs; + ps_dec->u4_num_mbs_cur_nmb = u1_num_mbs; + + ps_cur_mb_info->u1_Mux = 0; + ps_dec->u4_num_pmbair = (u1_num_mbs >> u1_mbaff); + ps_cur_deblk_mb = ps_dec->ps_deblk_mbn + u1_num_mbs; + + ps_cur_mb_info->u1_end_of_slice = 0; + + /* Storing Default partition info */ + ps_parse_mb_data->u1_num_part = 1; + ps_parse_mb_data->u1_isI_mb = 0; + + /**************************************************************/ + /* Get the required information for decoding of MB */ + /**************************************************************/ + /* mb_x, mb_y, neighbor availablity, */ + if (u1_mbaff) + ih264d_get_mb_info_cavlc_mbaff(ps_dec, i2_cur_mb_addr, ps_cur_mb_info, i2_mb_skip_run); + else + ih264d_get_mb_info_cavlc_nonmbaff(ps_dec, i2_cur_mb_addr, ps_cur_mb_info, i2_mb_skip_run); + + /* Set the deblocking parameters for this MB */ + if(ps_dec->u4_app_disable_deblk_frm == 0) + { + ih264d_set_deblocking_parameters(ps_cur_deblk_mb, ps_slice, + ps_dec->u1_mb_ngbr_availablity, + ps_dec->u1_cur_mb_fld_dec_flag); + } + + /* Set appropriate flags in ps_cur_mb_info and ps_dec */ + ps_dec->i1_prev_mb_qp_delta = 0; + ps_dec->u1_sub_mb_num = 0; + ps_cur_mb_info->u1_mb_type = MB_SKIP; + ps_cur_mb_info->u1_mb_mc_mode = PRED_16x16; + ps_cur_mb_info->u1_cbp = 0; + + /* Storing Skip partition info */ + ps_part_info = ps_dec->ps_part; + ps_part_info->u1_is_direct = PART_DIRECT_16x16; + ps_part_info->u1_sub_mb_num = 0; + ps_dec->ps_part++; + + /* Update Nnzs */ + ih264d_update_nnz_for_skipmb(ps_dec, ps_cur_mb_info, CAVLC); + + ps_cur_mb_info->ps_curmb->u1_mb_type = u1_inter_mb_type; + ps_cur_deblk_mb->u1_mb_type |= u1_deblk_mb_type; + + i2_mb_skip_run--; + + ps_cur_deblk_mb->u1_mb_qp = ps_dec->u1_qp; + + if (u1_mbaff) + { + ih264d_update_mbaff_left_nnz(ps_dec, ps_cur_mb_info); + } + + /**************************************************************/ + /* Get next Macroblock address */ + /**************************************************************/ + i2_cur_mb_addr++; + + u1_num_mbs++; + u1_num_mbsNby2++; + ps_parse_mb_data++; + + /****************************************************************/ + /* Check for End Of Row and other flags that determine when to */ + /* do DMA setup for N/2-Mb, Decode for N-Mb, and Transfer for */ + /* N-Mb */ + /****************************************************************/ + u1_num_mbs_next = i2_pic_wdin_mbs - ps_dec->u2_mbx - 1; + u1_end_of_row = (!u1_num_mbs_next) && (!(u1_mbaff && (u1_num_mbs & 0x01))); + u1_slice_end = !i2_mb_skip_run; + u1_tfr_n_mb = (u1_num_mbs == ps_dec->u1_recon_mb_grp) || u1_end_of_row + || u1_slice_end; + u1_decode_nmb = u1_tfr_n_mb || u1_slice_end; + ps_cur_mb_info->u1_end_of_slice = u1_slice_end; + + if(u1_decode_nmb) + { + ps_dec->pf_mvpred_ref_tfr_nby2mb(ps_dec, u1_mb_idx, u1_num_mbs); + u1_num_mbsNby2 = 0; + + ps_parse_mb_data = ps_dec->ps_parse_mb_data; + ps_dec->ps_part = ps_dec->ps_parse_part_params; + + if(ps_dec->u1_separate_parse) + { + ih264d_parse_tfr_nmb(ps_dec, u1_mb_idx, u1_num_mbs, + u1_num_mbs_next, u1_tfr_n_mb, u1_end_of_row); + ps_dec->ps_nmb_info += u1_num_mbs; + } + else + { + ih264d_decode_recon_tfr_nmb(ps_dec, u1_mb_idx, u1_num_mbs, u1_num_mbs_next, + u1_tfr_n_mb, u1_end_of_row); + } + ps_dec->u2_total_mbs_coded += u1_num_mbs; + if(u1_tfr_n_mb) + u1_num_mbs = 0; + u1_mb_idx = u1_num_mbs; + ps_dec->u1_mb_idx = u1_num_mbs; + } + } + + ps_dec->u4_num_mbs_cur_nmb = 0; + ps_dec->ps_cur_slice->u4_mbs_in_slice = i2_cur_mb_addr + - ps_dec->ps_parse_cur_slice->u4_first_mb_in_slice; + + H264_DEC_DEBUG_PRINT("Mbs in slice: %d\n", ps_dec->ps_cur_slice->u4_mbs_in_slice); + + + /* incremented here only if first slice is inserted */ + if(ps_dec->u4_first_slice_in_pic != 0) + { + ps_dec->ps_parse_cur_slice++; + ps_dec->u2_cur_slice_num++; + } + + ps_dec->i2_prev_slice_mbx = ps_dec->u2_mbx; + ps_dec->i2_prev_slice_mby = ps_dec->u2_mby; + + if(ps_dec->u2_total_mbs_coded + >= ps_dec->u2_frm_ht_in_mbs * ps_dec->u2_frm_wd_in_mbs) + { + ps_dec->u1_pic_decode_done = 1; + } + + return 0; + +} + +/*! + ************************************************************************** + * \if Function name : ih264d_decode_pslice \endif + * + * \brief + * Decodes a P Slice + * + * + * \return + * 0 on Success and Error code otherwise + ************************************************************************** + */ +WORD32 ih264d_parse_pslice(dec_struct_t *ps_dec, UWORD16 u2_first_mb_in_slice) +{ + dec_pic_params_t * ps_pps = ps_dec->ps_cur_pps; + dec_slice_params_t * ps_cur_slice = ps_dec->ps_cur_slice; + dec_bit_stream_t *ps_bitstrm = ps_dec->ps_bitstrm; + UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; + UWORD32 *pu4_bitstrm_ofst = &ps_bitstrm->u4_ofst; + UWORD8 u1_mbaff = ps_dec->ps_cur_slice->u1_mbaff_frame_flag; //ps_dec->ps_cur_sps->u1_mb_aff_flag; + UWORD8 u1_field_pic_flag = ps_cur_slice->u1_field_pic_flag; + + UWORD64 u8_ref_idx_l0; + UWORD32 u4_temp; + WORD32 i_temp; + WORD32 ret; + + /*--------------------------------------------------------------------*/ + /* Read remaining contents of the slice header */ + /*--------------------------------------------------------------------*/ + { + WORD8 *pi1_buf; + WORD16 *pi2_mv = ps_dec->s_default_mv_pred.i2_mv; + WORD32 *pi4_mv = (WORD32*)pi2_mv; + WORD16 *pi16_refFrame; + + pi1_buf = ps_dec->s_default_mv_pred.i1_ref_frame; + pi16_refFrame = (WORD16*)pi1_buf; + *pi4_mv = 0; + *(pi4_mv + 1) = 0; + *pi16_refFrame = OUT_OF_RANGE_REF; + ps_dec->s_default_mv_pred.u1_col_ref_pic_idx = (UWORD8)-1; + ps_dec->s_default_mv_pred.u1_pic_type = (UWORD8)-1; + } + + ps_cur_slice->u1_num_ref_idx_active_override_flag = ih264d_get_bit_h264( + ps_bitstrm); + + COPYTHECONTEXT("SH: num_ref_idx_override_flag", + ps_cur_slice->u1_num_ref_idx_active_override_flag); + + u8_ref_idx_l0 = ps_dec->ps_cur_pps->u1_num_ref_idx_lx_active[0]; + if(ps_cur_slice->u1_num_ref_idx_active_override_flag) + { + u8_ref_idx_l0 = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf) + (UWORD64)1; + } + + { + UWORD8 u1_max_ref_idx = H264_MAX_REF_PICS << u1_field_pic_flag; + if(u8_ref_idx_l0 > u1_max_ref_idx) + { + return ERROR_NUM_REF; + } + ps_cur_slice->u1_num_ref_idx_lx_active[0] = u8_ref_idx_l0; + COPYTHECONTEXT("SH: num_ref_idx_l0_active_minus1", + ps_cur_slice->u1_num_ref_idx_lx_active[0] - 1); + + } + + { + UWORD8 uc_refIdxReFlagL0 = ih264d_get_bit_h264(ps_bitstrm); + COPYTHECONTEXT("SH: ref_pic_list_reordering_flag_l0",uc_refIdxReFlagL0); + + ih264d_init_ref_idx_lx_p(ps_dec); + /* Store the value for future slices in the same picture */ + ps_dec->u1_num_ref_idx_lx_active_prev = + ps_cur_slice->u1_num_ref_idx_lx_active[0]; + + /* Modified temporarily */ + if(uc_refIdxReFlagL0) + { + WORD8 ret; + ps_dec->ps_ref_pic_buf_lx[0] = ps_dec->ps_dpb_mgr->ps_mod_dpb[0]; + ret = ih264d_ref_idx_reordering(ps_dec, 0); + if(ret == -1) + return ERROR_REFIDX_ORDER_T; + ps_dec->ps_ref_pic_buf_lx[0] = ps_dec->ps_dpb_mgr->ps_mod_dpb[0]; + } + else + ps_dec->ps_ref_pic_buf_lx[0] = + ps_dec->ps_dpb_mgr->ps_init_dpb[0]; + } + /* Create refIdx to POC mapping */ + { + void **pui_map_ref_idx_to_poc_lx0, **pui_map_ref_idx_to_poc_lx1; + WORD8 idx; + struct pic_buffer_t *ps_pic; + + pui_map_ref_idx_to_poc_lx0 = ps_dec->ppv_map_ref_idx_to_poc + FRM_LIST_L0; + pui_map_ref_idx_to_poc_lx0[0] = 0; //For ref_idx = -1 + pui_map_ref_idx_to_poc_lx0++; + for(idx = 0; idx < ps_cur_slice->u1_num_ref_idx_lx_active[0]; idx++) + { + ps_pic = ps_dec->ps_ref_pic_buf_lx[0][idx]; + pui_map_ref_idx_to_poc_lx0[idx] = (ps_pic->pu1_buf1); + } + + /* Bug Fix Deblocking */ + pui_map_ref_idx_to_poc_lx1 = ps_dec->ppv_map_ref_idx_to_poc + FRM_LIST_L1; + pui_map_ref_idx_to_poc_lx1[0] = 0; + + if(u1_mbaff) + { + void **ppv_map_ref_idx_to_poc_lx_t, **ppv_map_ref_idx_to_poc_lx_b; + void **ppv_map_ref_idx_to_poc_lx_t1, **ppv_map_ref_idx_to_poc_lx_b1; + ppv_map_ref_idx_to_poc_lx_t = ps_dec->ppv_map_ref_idx_to_poc + + TOP_LIST_FLD_L0; + ppv_map_ref_idx_to_poc_lx_b = ps_dec->ppv_map_ref_idx_to_poc + + BOT_LIST_FLD_L0; + + ppv_map_ref_idx_to_poc_lx_t[0] = 0; // For ref_idx = -1 + ppv_map_ref_idx_to_poc_lx_t++; + ppv_map_ref_idx_to_poc_lx_b[0] = 0; // For ref_idx = -1 + ppv_map_ref_idx_to_poc_lx_b++; + + idx = 0; + for(idx = 0; idx < ps_cur_slice->u1_num_ref_idx_lx_active[0]; idx++) + { + ps_pic = ps_dec->ps_ref_pic_buf_lx[0][idx]; + ppv_map_ref_idx_to_poc_lx_t[0] = (ps_pic->pu1_buf1); + ppv_map_ref_idx_to_poc_lx_b[1] = (ps_pic->pu1_buf1); + + ppv_map_ref_idx_to_poc_lx_b[0] = (ps_pic->pu1_buf1) + 1; + ppv_map_ref_idx_to_poc_lx_t[1] = (ps_pic->pu1_buf1) + 1; + + ppv_map_ref_idx_to_poc_lx_t += 2; + ppv_map_ref_idx_to_poc_lx_b += 2; + } + ppv_map_ref_idx_to_poc_lx_t1 = ps_dec->ppv_map_ref_idx_to_poc + + TOP_LIST_FLD_L1; + ppv_map_ref_idx_to_poc_lx_t1[0] = 0; + ppv_map_ref_idx_to_poc_lx_b1 = ps_dec->ppv_map_ref_idx_to_poc + + BOT_LIST_FLD_L1; + ppv_map_ref_idx_to_poc_lx_b1[0] = 0; + + } + + if(ps_dec->u4_num_cores >= 3) + { + WORD32 num_entries; + WORD32 size; + + num_entries = MAX_FRAMES; + if((1 >= ps_dec->ps_cur_sps->u1_num_ref_frames) && + (0 == ps_dec->i4_display_delay)) + { + num_entries = 1; + } + num_entries = ((2 * num_entries) + 1); + num_entries *= 2; + + size = num_entries * sizeof(void *); + size += PAD_MAP_IDX_POC * sizeof(void *); + + memcpy((void *)ps_dec->ps_parse_cur_slice->ppv_map_ref_idx_to_poc, + ps_dec->ppv_map_ref_idx_to_poc, + size); + } + + + } + if(ps_pps->u1_wted_pred_flag) + { + ret = ih264d_parse_pred_weight_table(ps_cur_slice, ps_bitstrm); + if(ret != OK) + return ret; + ih264d_form_pred_weight_matrix(ps_dec); + ps_dec->pu4_wt_ofsts = ps_dec->pu4_wts_ofsts_mat; + } + else + { + ps_dec->ps_cur_slice->u2_log2Y_crwd = 0; + ps_dec->pu4_wt_ofsts = ps_dec->pu4_wts_ofsts_mat; + } + + ps_dec->ps_parse_cur_slice->u2_log2Y_crwd = + ps_dec->ps_cur_slice->u2_log2Y_crwd; + + if(u1_mbaff && (u1_field_pic_flag == 0)) + { + ih264d_convert_frm_mbaff_list(ps_dec); + } + + /* G050 */ + if(ps_cur_slice->u1_nal_ref_idc != 0) + { + if(!ps_dec->ps_dpb_cmds->u1_dpb_commands_read) + { + i_temp = ih264d_read_mmco_commands(ps_dec); + if (i_temp < 0) + { + return ERROR_DBP_MANAGER_T; + } + ps_dec->u4_bitoffset = i_temp; + } + else + ps_bitstrm->u4_ofst += ps_dec->u4_bitoffset; + + } + /* G050 */ + + if(ps_pps->u1_entropy_coding_mode == CABAC) + { + u4_temp = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); + + if(u4_temp > MAX_CABAC_INIT_IDC) + { + return ERROR_INV_SLICE_HDR_T; + } + ps_cur_slice->u1_cabac_init_idc = u4_temp; + COPYTHECONTEXT("SH: cabac_init_idc",ps_cur_slice->u1_cabac_init_idc); + } + + /* Read slice_qp_delta */ + WORD64 i8_temp = (WORD64)ps_pps->u1_pic_init_qp + + ih264d_sev(pu4_bitstrm_ofst, pu4_bitstrm_buf); + if((i8_temp < MIN_H264_QP) || (i8_temp > MAX_H264_QP)) + { + return ERROR_INV_RANGE_QP_T; + } + ps_cur_slice->u1_slice_qp = i8_temp; + COPYTHECONTEXT("SH: slice_qp_delta", + (WORD8)(ps_cur_slice->u1_slice_qp - ps_pps->u1_pic_init_qp)); + + if(ps_pps->u1_deblocking_filter_parameters_present_flag == 1) + { + u4_temp = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); + if(u4_temp > SLICE_BOUNDARY_DBLK_DISABLED) + { + return ERROR_INV_SLICE_HDR_T; + } + + COPYTHECONTEXT("SH: disable_deblocking_filter_idc", u4_temp); + ps_cur_slice->u1_disable_dblk_filter_idc = u4_temp; + if(u4_temp != 1) + { + i_temp = ih264d_sev(pu4_bitstrm_ofst, pu4_bitstrm_buf) + << 1; + if((MIN_DBLK_FIL_OFF > i_temp) || (i_temp > MAX_DBLK_FIL_OFF)) + { + return ERROR_INV_SLICE_HDR_T; + } + ps_cur_slice->i1_slice_alpha_c0_offset = i_temp; + COPYTHECONTEXT("SH: slice_alpha_c0_offset_div2", + ps_cur_slice->i1_slice_alpha_c0_offset >> 1); + + i_temp = ih264d_sev(pu4_bitstrm_ofst, pu4_bitstrm_buf) + << 1; + if((MIN_DBLK_FIL_OFF > i_temp) || (i_temp > MAX_DBLK_FIL_OFF)) + { + return ERROR_INV_SLICE_HDR_T; + } + ps_cur_slice->i1_slice_beta_offset = i_temp; + COPYTHECONTEXT("SH: slice_beta_offset_div2", + ps_cur_slice->i1_slice_beta_offset >> 1); + } + else + { + ps_cur_slice->i1_slice_alpha_c0_offset = 0; + ps_cur_slice->i1_slice_beta_offset = 0; + } + } + else + { + ps_cur_slice->u1_disable_dblk_filter_idc = 0; + ps_cur_slice->i1_slice_alpha_c0_offset = 0; + ps_cur_slice->i1_slice_beta_offset = 0; + } + + ps_dec->u1_slice_header_done = 2; + + if(ps_pps->u1_entropy_coding_mode) + { + SWITCHOFFTRACE; SWITCHONTRACECABAC; + ps_dec->pf_parse_inter_slice = ih264d_parse_inter_slice_data_cabac; + ps_dec->pf_parse_inter_mb = ih264d_parse_pmb_cabac; + ih264d_init_cabac_contexts(P_SLICE, ps_dec); + + if(ps_dec->ps_cur_slice->u1_mbaff_frame_flag) + ps_dec->pf_get_mb_info = ih264d_get_mb_info_cabac_mbaff; + else + ps_dec->pf_get_mb_info = ih264d_get_mb_info_cabac_nonmbaff; + } + else + { + SWITCHONTRACE; SWITCHOFFTRACECABAC; + ps_dec->pf_parse_inter_slice = ih264d_parse_inter_slice_data_cavlc; + ps_dec->pf_parse_inter_mb = ih264d_parse_pmb_cavlc; + if(ps_dec->ps_cur_slice->u1_mbaff_frame_flag) + { + ps_dec->pf_get_mb_info = ih264d_get_mb_info_cavlc_mbaff; + } + else + ps_dec->pf_get_mb_info = ih264d_get_mb_info_cavlc_nonmbaff; + } + + ps_dec->u1_B = 0; + ps_dec->pf_mvpred_ref_tfr_nby2mb = ih264d_mv_pred_ref_tfr_nby2_pmb; + ret = ps_dec->pf_parse_inter_slice(ps_dec, ps_cur_slice, u2_first_mb_in_slice); + if(ret != OK) + return ret; +// ps_dec->curr_slice_in_error = 0 ; + return OK; +} diff --git a/dependencies/ih264d/decoder/ih264d_parse_slice.c b/dependencies/ih264d/decoder/ih264d_parse_slice.c new file mode 100644 index 00000000..266c69bf --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_parse_slice.c @@ -0,0 +1,1938 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/*! + ************************************************************************** + * \file ih264d_parse_slice.c + * + * \brief + * Contains routines that decodes a slice NAL unit + * + * \date + * 19/12/2002 + * + * \author AI + ************************************************************************** + */ +#include <string.h> +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ithread.h" +#include "ih264d_structs.h" +#include "ih264d_debug.h" +#include "ih264d_bitstrm.h" +#include "ih264d_parse_mb_header.h" +#include "ih264d_process_bslice.h" +#include "ih264d_process_pslice.h" +#include "ih264d_parse_cavlc.h" +#include "ih264d_utils.h" +#include "ih264d_deblocking.h" +#include "ih264d_defs.h" +#include "ih264d_error_handler.h" +#include "ih264d_tables.h" +#include "ih264d_defs.h" +#include "ih264d_mem_request.h" +#include "ih264d_parse_islice.h" +#include "ih264d_parse_slice.h" +#include "ih264d_mvpred.h" +#include "ih264d_mb_utils.h" + +#include "ih264d_defs.h" +#include "ih264d_quant_scaling.h" + +#include "ih264d_inter_pred.h" + +#include "ih264d_sei.h" +#include "ih264d.h" +#include "ih264_error.h" +#include "ih264_disp_mgr.h" +#include "ih264_buf_mgr.h" + +#include "ih264d_thread_parse_decode.h" +#include "ih264d_thread_compute_bs.h" +#include "ih264d_dpb_manager.h" +#include <assert.h> +#include "ih264d_parse_islice.h" +#define RET_LAST_SKIP 0x80000000 + +WORD32 check_app_out_buf_size(dec_struct_t *ps_dec); +/*! + ************************************************************************** + * \if Function name : ih264d_form_pred_weight_matrix \endif + * + * \brief + * Forms pred weight matrix. + * + * \return + * None + * + ************************************************************************** + */ + +void ih264d_form_pred_weight_matrix(dec_struct_t *ps_dec) +{ + dec_slice_params_t *ps_cur_slice; + UWORD8 uc_num_ref_idx_l0_active, uc_num_ref_idx_l1_active; + UWORD8 i, j; + UWORD32 *pu4_mat_iwt_ofst; + UWORD16 i2_idx; + UWORD32 *pui32_weight_offset_l0, *pui32_weight_offset_l1; + UWORD32 u4_temp; + + ps_cur_slice = ps_dec->ps_cur_slice; + uc_num_ref_idx_l0_active = ps_cur_slice->u1_num_ref_idx_lx_active[0]; + uc_num_ref_idx_l1_active = ps_cur_slice->u1_num_ref_idx_lx_active[1]; + + pu4_mat_iwt_ofst = ps_dec->pu4_wts_ofsts_mat; + + if(ps_cur_slice->u1_slice_type == B_SLICE) + { + for(i = 0; i < uc_num_ref_idx_l0_active; i++) + { + pui32_weight_offset_l0 = ps_cur_slice->u4_wt_ofst_lx[0][i]; + for(j = 0; j < uc_num_ref_idx_l1_active; j++) + { + pui32_weight_offset_l1 = ps_cur_slice->u4_wt_ofst_lx[1][j]; + i2_idx = i * uc_num_ref_idx_l0_active + j; + i2_idx = X3(i2_idx); + /* u4_temp = (pui32_weight_offset_l0[0] | (pui32_weight_offset_l1[0] << 16)); + pu4_mat_iwt_ofst[0] = u4_temp; + u4_temp = (pui32_weight_offset_l0[1] | (pui32_weight_offset_l1[1] << 16)); + pu4_mat_iwt_ofst[1] = u4_temp; + u4_temp = (pui32_weight_offset_l0[2] | (pui32_weight_offset_l1[2] << 16)); + pu4_mat_iwt_ofst[2] = u4_temp; + pu4_mat_iwt_ofst += 3;*/ + pu4_mat_iwt_ofst[0] = pui32_weight_offset_l0[0]; + pu4_mat_iwt_ofst[1] = pui32_weight_offset_l1[0]; + pu4_mat_iwt_ofst[2] = pui32_weight_offset_l0[1]; + pu4_mat_iwt_ofst[3] = pui32_weight_offset_l1[1]; + pu4_mat_iwt_ofst[4] = pui32_weight_offset_l0[2]; + pu4_mat_iwt_ofst[5] = pui32_weight_offset_l1[2]; + pu4_mat_iwt_ofst += 6; + } + } + } + else + { + for(i = 0; i < uc_num_ref_idx_l0_active; i++) + { + pui32_weight_offset_l0 = ps_cur_slice->u4_wt_ofst_lx[0][i]; + i2_idx = X3(i); + u4_temp = (UWORD32)pui32_weight_offset_l0[0]; + pu4_mat_iwt_ofst[0] = u4_temp; + u4_temp = (UWORD32)pui32_weight_offset_l0[1]; + pu4_mat_iwt_ofst[2] = u4_temp; + u4_temp = (UWORD32)pui32_weight_offset_l0[2]; + pu4_mat_iwt_ofst[4] = u4_temp; + pu4_mat_iwt_ofst += 6; + } + } +} + + +/*! + ************************************************************************** + * \if Function name : init_firstSliceParam \endif + * + * \brief + * Initialize the Parameter required for all the slices for a picture + * + * \return : Nothing + * + ************************************************************************** + */ + +WORD32 ih264d_start_of_pic(dec_struct_t *ps_dec, + WORD32 i4_poc, + pocstruct_t *ps_temp_poc, + UWORD16 u2_frame_num, + dec_pic_params_t *ps_pps) +{ + pocstruct_t *ps_prev_poc = &ps_dec->s_cur_pic_poc; + pocstruct_t *ps_cur_poc = ps_temp_poc; + + pic_buffer_t *pic_buf; + + ivd_video_decode_op_t * ps_dec_output = + (ivd_video_decode_op_t *)ps_dec->pv_dec_out; + dec_slice_params_t *ps_cur_slice = ps_dec->ps_cur_slice; + dec_seq_params_t *ps_seq = ps_pps->ps_sps; + UWORD8 u1_bottom_field_flag = ps_cur_slice->u1_bottom_field_flag; + UWORD8 u1_field_pic_flag = ps_cur_slice->u1_field_pic_flag; + /* high profile related declarations */ + high_profile_tools_t s_high_profile; + WORD32 ret; + + H264_MUTEX_LOCK(&ps_dec->process_disp_mutex); + + /* check output buffer size given by the application */ + if(check_app_out_buf_size(ps_dec) != IV_SUCCESS) + return IVD_DISP_FRM_ZERO_OP_BUF_SIZE; + + ps_prev_poc->i4_pic_order_cnt_lsb = ps_cur_poc->i4_pic_order_cnt_lsb; + ps_prev_poc->i4_pic_order_cnt_msb = ps_cur_poc->i4_pic_order_cnt_msb; + ps_prev_poc->i4_delta_pic_order_cnt_bottom = + ps_cur_poc->i4_delta_pic_order_cnt_bottom; + ps_prev_poc->i4_delta_pic_order_cnt[0] = + ps_cur_poc->i4_delta_pic_order_cnt[0]; + ps_prev_poc->i4_delta_pic_order_cnt[1] = + ps_cur_poc->i4_delta_pic_order_cnt[1]; + ps_prev_poc->u1_bot_field = ps_dec->ps_cur_slice->u1_bottom_field_flag; + ps_prev_poc->i4_prev_frame_num_ofst = ps_cur_poc->i4_prev_frame_num_ofst; + ps_prev_poc->u2_frame_num = u2_frame_num; + ps_dec->i1_prev_mb_qp_delta = 0; + ps_dec->i1_next_ctxt_idx = 0; + + + ps_dec->u4_nmb_deblk = 0; + if(ps_dec->u4_num_cores == 1) + ps_dec->u4_nmb_deblk = 1; + + + + if(ps_seq->u1_mb_aff_flag == 1) + { + ps_dec->u4_nmb_deblk = 0; + if(ps_dec->u4_num_cores > 2) + ps_dec->u4_num_cores = 2; + } + + ps_dec->u4_use_intrapred_line_copy = 0; + + + + if (ps_seq->u1_mb_aff_flag == 0) + { + ps_dec->u4_use_intrapred_line_copy = 1; + } + + ps_dec->u4_app_disable_deblk_frm = 0; + /* If degrade is enabled, set the degrade flags appropriately */ + if(ps_dec->i4_degrade_type && ps_dec->i4_degrade_pics) + { + WORD32 degrade_pic; + ps_dec->i4_degrade_pic_cnt++; + degrade_pic = 0; + + /* If degrade is to be done in all frames, then do not check further */ + switch(ps_dec->i4_degrade_pics) + { + case 4: + { + degrade_pic = 1; + break; + } + case 3: + { + if(ps_cur_slice->u1_slice_type != I_SLICE) + degrade_pic = 1; + + break; + } + case 2: + { + + /* If pic count hits non-degrade interval or it is an islice, then do not degrade */ + if((ps_cur_slice->u1_slice_type != I_SLICE) + && (ps_dec->i4_degrade_pic_cnt + != ps_dec->i4_nondegrade_interval)) + degrade_pic = 1; + + break; + } + case 1: + { + /* Check if the current picture is non-ref */ + if(0 == ps_cur_slice->u1_nal_ref_idc) + { + degrade_pic = 1; + } + break; + } + + } + if(degrade_pic) + { + if(ps_dec->i4_degrade_type & 0x2) + ps_dec->u4_app_disable_deblk_frm = 1; + + /* MC degrading is done only for non-ref pictures */ + if(0 == ps_cur_slice->u1_nal_ref_idc) + { + if(ps_dec->i4_degrade_type & 0x4) + ps_dec->i4_mv_frac_mask = 0; + + if(ps_dec->i4_degrade_type & 0x8) + ps_dec->i4_mv_frac_mask = 0; + } + } + else + ps_dec->i4_degrade_pic_cnt = 0; + } + + { + dec_err_status_t * ps_err = ps_dec->ps_dec_err_status; + if((ps_cur_slice->u1_slice_type == I_SLICE) + || (ps_cur_slice->u1_slice_type == SI_SLICE)) + ps_err->u1_cur_pic_type = PIC_TYPE_I; + else + ps_err->u1_cur_pic_type = PIC_TYPE_UNKNOWN; + + if(ps_err->u1_pic_aud_i == PIC_TYPE_I) + { + ps_err->u1_cur_pic_type = PIC_TYPE_I; + ps_err->u1_pic_aud_i = PIC_TYPE_UNKNOWN; + } + + if(ps_cur_slice->u1_nal_unit_type == IDR_SLICE_NAL) + { + if(ps_err->u1_err_flag) + ih264d_reset_ref_bufs(ps_dec->ps_dpb_mgr); + ps_err->u1_err_flag = ACCEPT_ALL_PICS; + } + } + + if(ps_dec->u1_init_dec_flag && ps_dec->s_prev_seq_params.u1_eoseq_pending) + { + /* Reset the decoder picture buffers */ + WORD32 j; + for(j = 0; j < MAX_DISP_BUFS_NEW; j++) + { + + ih264_buf_mgr_release((buf_mgr_t *)ps_dec->pv_pic_buf_mgr, + j, + BUF_MGR_REF); + ih264_buf_mgr_release((buf_mgr_t *)ps_dec->pv_mv_buf_mgr, + ps_dec->au1_pic_buf_id_mv_buf_id_map[j], + BUF_MGR_REF); + ih264_buf_mgr_release((buf_mgr_t *)ps_dec->pv_pic_buf_mgr, + j, + BUF_MGR_IO); + } + + /* reset the decoder structure parameters related to buffer handling */ + ps_dec->u1_second_field = 0; + ps_dec->i4_cur_display_seq = 0; + + /********************************************************************/ + /* indicate in the decoder output i4_status that some frames are being */ + /* dropped, so that it resets timestamp and wait for a new sequence */ + /********************************************************************/ + + ps_dec->s_prev_seq_params.u1_eoseq_pending = 0; + } + ret = ih264d_init_pic(ps_dec, u2_frame_num, i4_poc, ps_pps); + if(ret != OK) + return ret; + + ps_dec->pv_parse_tu_coeff_data = ps_dec->pv_pic_tu_coeff_data; + ps_dec->pv_proc_tu_coeff_data = ps_dec->pv_pic_tu_coeff_data; + ps_dec->ps_nmb_info = ps_dec->ps_frm_mb_info; + if(ps_dec->u1_separate_parse) + { + UWORD16 pic_wd; + UWORD16 pic_ht; + UWORD32 num_mbs; + + pic_wd = ps_dec->u2_pic_wd; + pic_ht = ps_dec->u2_pic_ht; + num_mbs = (pic_wd * pic_ht) >> 8; + + if(ps_dec->pu1_dec_mb_map) + { + memset((void *)ps_dec->pu1_dec_mb_map, 0, num_mbs); + } + + if(ps_dec->pu1_recon_mb_map) + { + + memset((void *)ps_dec->pu1_recon_mb_map, 0, num_mbs); + } + + if(ps_dec->pu2_slice_num_map) + { + memset((void *)ps_dec->pu2_slice_num_map, 0, + (num_mbs * sizeof(UWORD16))); + } + + } + + ps_dec->ps_parse_cur_slice = &(ps_dec->ps_dec_slice_buf[0]); + ps_dec->ps_decode_cur_slice = &(ps_dec->ps_dec_slice_buf[0]); + ps_dec->ps_computebs_cur_slice = &(ps_dec->ps_dec_slice_buf[0]); + ps_dec->u2_cur_slice_num = 0; + + /* Initialize all the HP toolsets to zero */ + ps_dec->s_high_profile.u1_scaling_present = 0; + ps_dec->s_high_profile.u1_transform8x8_present = 0; + + /* Get Next Free Picture */ + if(1 == ps_dec->u4_share_disp_buf) + { + UWORD32 i; + /* Free any buffer that is in the queue to be freed */ + for(i = 0; i < MAX_DISP_BUFS_NEW; i++) + { + if(0 == ps_dec->u4_disp_buf_to_be_freed[i]) + continue; + ih264_buf_mgr_release((buf_mgr_t *)ps_dec->pv_pic_buf_mgr, i, + BUF_MGR_IO); + ps_dec->u4_disp_buf_to_be_freed[i] = 0; + ps_dec->u4_disp_buf_mapping[i] = 0; + + } + } + if(!(u1_field_pic_flag && 0 != ps_dec->u1_top_bottom_decoded)) //ps_dec->u1_second_field)) + { + pic_buffer_t *ps_cur_pic; + WORD32 cur_pic_buf_id, cur_mv_buf_id; + col_mv_buf_t *ps_col_mv; + while(1) + { + ps_cur_pic = (pic_buffer_t *)ih264_buf_mgr_get_next_free( + (buf_mgr_t *)ps_dec->pv_pic_buf_mgr, + &cur_pic_buf_id); + if(ps_cur_pic == NULL) + { + ps_dec->i4_error_code = ERROR_UNAVAIL_PICBUF_T; + return ERROR_UNAVAIL_PICBUF_T; + } + if(0 == ps_dec->u4_disp_buf_mapping[cur_pic_buf_id]) + { + break; + } + + } + ps_col_mv = (col_mv_buf_t *)ih264_buf_mgr_get_next_free((buf_mgr_t *)ps_dec->pv_mv_buf_mgr, + &cur_mv_buf_id); + if(ps_col_mv == NULL) + { + ps_dec->i4_error_code = ERROR_UNAVAIL_MVBUF_T; + return ERROR_UNAVAIL_MVBUF_T; + } + + ps_dec->ps_cur_pic = ps_cur_pic; + ps_dec->u1_pic_buf_id = cur_pic_buf_id; + ps_cur_pic->u4_ts = ps_dec->u4_ts; + memcpy(&ps_cur_pic->s_sei_pic, ps_dec->ps_sei, sizeof(sei)); + + ps_cur_pic->u1_mv_buf_id = cur_mv_buf_id; + ps_dec->au1_pic_buf_id_mv_buf_id_map[cur_pic_buf_id] = cur_mv_buf_id; + + ps_cur_pic->pu1_col_zero_flag = (UWORD8 *)ps_col_mv->pv_col_zero_flag; + ps_cur_pic->ps_mv = (mv_pred_t *)ps_col_mv->pv_mv; + ps_dec->au1_pic_buf_ref_flag[cur_pic_buf_id] = 0; + + { + /*make first entry of list0 and list1 point to cur pic, + *so that if first slice is in error, ref pic struct will have valid entries*/ + ps_dec->ps_ref_pic_buf_lx[0] = ps_dec->ps_dpb_mgr->ps_init_dpb[0]; + ps_dec->ps_ref_pic_buf_lx[1] = ps_dec->ps_dpb_mgr->ps_init_dpb[1]; + *(ps_dec->ps_dpb_mgr->ps_init_dpb[0][0]) = *ps_cur_pic; + /* Initialize for field reference as well */ + *(ps_dec->ps_dpb_mgr->ps_init_dpb[0][MAX_REF_BUFS]) = *ps_cur_pic; + + *(ps_dec->ps_dpb_mgr->ps_mod_dpb[0][0]) = *ps_cur_pic; + /* Initialize for field reference as well */ + *(ps_dec->ps_dpb_mgr->ps_mod_dpb[0][MAX_REF_BUFS]) = *ps_cur_pic; + *(ps_dec->ps_dpb_mgr->ps_init_dpb[1][0]) = *ps_cur_pic; + /* Initialize for field reference as well */ + *(ps_dec->ps_dpb_mgr->ps_init_dpb[1][MAX_REF_BUFS]) = *ps_cur_pic; + *(ps_dec->ps_dpb_mgr->ps_mod_dpb[1][0]) = *ps_cur_pic; + /* Initialize for field reference as well */ + *(ps_dec->ps_dpb_mgr->ps_mod_dpb[1][MAX_REF_BUFS]) = *ps_cur_pic; + } + + if(!ps_dec->ps_cur_pic) + { + WORD32 j; + H264_DEC_DEBUG_PRINT("------- Display Buffers Reset --------\n"); + for(j = 0; j < MAX_DISP_BUFS_NEW; j++) + { + + ih264_buf_mgr_release((buf_mgr_t *)ps_dec->pv_pic_buf_mgr, + j, + BUF_MGR_REF); + ih264_buf_mgr_release((buf_mgr_t *)ps_dec->pv_mv_buf_mgr, + ps_dec->au1_pic_buf_id_mv_buf_id_map[j], + BUF_MGR_REF); + ih264_buf_mgr_release((buf_mgr_t *)ps_dec->pv_pic_buf_mgr, + j, + BUF_MGR_IO); + } + + ps_dec->i4_cur_display_seq = 0; + ps_dec->i4_prev_max_display_seq = 0; + ps_dec->i4_max_poc = 0; + + ps_cur_pic = (pic_buffer_t *)ih264_buf_mgr_get_next_free( + (buf_mgr_t *)ps_dec->pv_pic_buf_mgr, + &cur_pic_buf_id); + if(ps_cur_pic == NULL) + { + ps_dec->i4_error_code = ERROR_UNAVAIL_PICBUF_T; + return ERROR_UNAVAIL_PICBUF_T; + } + + ps_col_mv = (col_mv_buf_t *)ih264_buf_mgr_get_next_free((buf_mgr_t *)ps_dec->pv_mv_buf_mgr, + &cur_mv_buf_id); + if(ps_col_mv == NULL) + { + ps_dec->i4_error_code = ERROR_UNAVAIL_MVBUF_T; + return ERROR_UNAVAIL_MVBUF_T; + } + + ps_dec->ps_cur_pic = ps_cur_pic; + ps_dec->u1_pic_buf_id = cur_pic_buf_id; + ps_cur_pic->u4_ts = ps_dec->u4_ts; + ps_dec->apv_buf_id_pic_buf_map[cur_pic_buf_id] = (void *)ps_cur_pic; + + ps_cur_pic->u1_mv_buf_id = cur_mv_buf_id; + ps_dec->au1_pic_buf_id_mv_buf_id_map[cur_pic_buf_id] = cur_mv_buf_id; + + ps_cur_pic->pu1_col_zero_flag = (UWORD8 *)ps_col_mv->pv_col_zero_flag; + ps_cur_pic->ps_mv = (mv_pred_t *)ps_col_mv->pv_mv; + ps_dec->au1_pic_buf_ref_flag[cur_pic_buf_id] = 0; + + } + + ps_dec->ps_cur_pic->u1_picturetype = u1_field_pic_flag; + ps_dec->ps_cur_pic->u4_pack_slc_typ = SKIP_NONE; + H264_DEC_DEBUG_PRINT("got a buffer\n"); + } + else + { + H264_DEC_DEBUG_PRINT("did not get a buffer\n"); + } + + ps_dec->u4_pic_buf_got = 1; + + ps_dec->ps_cur_pic->i4_poc = i4_poc; + ps_dec->ps_cur_pic->i4_frame_num = u2_frame_num; + ps_dec->ps_cur_pic->i4_pic_num = u2_frame_num; + ps_dec->ps_cur_pic->i4_top_field_order_cnt = ps_pps->i4_top_field_order_cnt; + ps_dec->ps_cur_pic->i4_bottom_field_order_cnt = + ps_pps->i4_bottom_field_order_cnt; + ps_dec->ps_cur_pic->i4_avg_poc = ps_pps->i4_avg_poc; + ps_dec->ps_cur_pic->u4_time_stamp = ps_dec->u4_pts; + + ps_dec->s_cur_pic = *(ps_dec->ps_cur_pic); + if(u1_field_pic_flag && u1_bottom_field_flag) + { + WORD32 i4_temp_poc; + WORD32 i4_top_field_order_poc, i4_bot_field_order_poc; + /* Point to odd lines, since it's bottom field */ + ps_dec->s_cur_pic.pu1_buf1 += ps_dec->s_cur_pic.u2_frm_wd_y; + ps_dec->s_cur_pic.pu1_buf2 += ps_dec->s_cur_pic.u2_frm_wd_uv; + ps_dec->s_cur_pic.pu1_buf3 += ps_dec->s_cur_pic.u2_frm_wd_uv; + ps_dec->s_cur_pic.ps_mv += + ((ps_dec->u2_pic_ht * ps_dec->u2_pic_wd) >> 5); + ps_dec->s_cur_pic.pu1_col_zero_flag += ((ps_dec->u2_pic_ht + * ps_dec->u2_pic_wd) >> 5); + ps_dec->ps_cur_pic->u1_picturetype |= BOT_FLD; + i4_top_field_order_poc = ps_dec->ps_cur_pic->i4_top_field_order_cnt; + i4_bot_field_order_poc = ps_dec->ps_cur_pic->i4_bottom_field_order_cnt; + i4_temp_poc = MIN(i4_top_field_order_poc, + i4_bot_field_order_poc); + ps_dec->ps_cur_pic->i4_avg_poc = i4_temp_poc; + } + + ps_cur_slice->u1_mbaff_frame_flag = ps_seq->u1_mb_aff_flag + && (!u1_field_pic_flag); + + ps_dec->ps_cur_pic->u1_picturetype |= (ps_cur_slice->u1_mbaff_frame_flag + << 2); + + ps_dec->ps_cur_mb_row = ps_dec->ps_nbr_mb_row; //[0]; + //Increment by 2 ,so that left mb (mbaff decrements by 2) will always be valid + ps_dec->ps_cur_mb_row += 2; + ps_dec->ps_top_mb_row = ps_dec->ps_nbr_mb_row; + ps_dec->ps_top_mb_row += ((ps_dec->u2_frm_wd_in_mbs + 2) << (1 - ps_dec->ps_cur_sps->u1_frame_mbs_only_flag)); + //Increment by 2 ,so that left mb (mbaff decrements by 2) will always be valid + ps_dec->ps_top_mb_row += 2; + + /* CHANGED CODE */ + ps_dec->ps_mv_cur = ps_dec->s_cur_pic.ps_mv; + ps_dec->ps_mv_top = ps_dec->ps_mv_top_p[0]; + /* CHANGED CODE */ + ps_dec->u1_mv_top_p = 0; + ps_dec->u1_mb_idx = 0; + /* CHANGED CODE */ + ps_dec->ps_mv_left = ps_dec->s_cur_pic.ps_mv; + ps_dec->u2_total_mbs_coded = 0; + ps_dec->i4_submb_ofst = -(SUB_BLK_SIZE); + ps_dec->u4_pred_info_idx = 0; + ps_dec->u4_pred_info_pkd_idx = 0; + ps_dec->u4_dma_buf_idx = 0; + ps_dec->ps_mv = ps_dec->s_cur_pic.ps_mv; + ps_dec->ps_mv_bank_cur = ps_dec->s_cur_pic.ps_mv; + ps_dec->pu1_col_zero_flag = ps_dec->s_cur_pic.pu1_col_zero_flag; + ps_dec->ps_part = ps_dec->ps_parse_part_params; + ps_dec->i2_prev_slice_mbx = -1; + ps_dec->i2_prev_slice_mby = 0; + ps_dec->u2_mv_2mb[0] = 0; + ps_dec->u2_mv_2mb[1] = 0; + ps_dec->u1_last_pic_not_decoded = 0; + + ps_dec->u2_cur_slice_num_dec_thread = 0; + ps_dec->u2_cur_slice_num_bs = 0; + ps_dec->u4_intra_pred_line_ofst = 0; + ps_dec->pu1_cur_y_intra_pred_line = ps_dec->pu1_y_intra_pred_line; + ps_dec->pu1_cur_u_intra_pred_line = ps_dec->pu1_u_intra_pred_line; + ps_dec->pu1_cur_v_intra_pred_line = ps_dec->pu1_v_intra_pred_line; + + ps_dec->pu1_cur_y_intra_pred_line_base = ps_dec->pu1_y_intra_pred_line; + ps_dec->pu1_cur_u_intra_pred_line_base = ps_dec->pu1_u_intra_pred_line; + ps_dec->pu1_cur_v_intra_pred_line_base = ps_dec->pu1_v_intra_pred_line; + + + + + + ps_dec->pu1_prev_y_intra_pred_line = ps_dec->pu1_y_intra_pred_line + + (ps_dec->u2_frm_wd_in_mbs * MB_SIZE); + + ps_dec->pu1_prev_u_intra_pred_line = ps_dec->pu1_u_intra_pred_line + + ps_dec->u2_frm_wd_in_mbs * BLK8x8SIZE * YUV420SP_FACTOR; + ps_dec->pu1_prev_v_intra_pred_line = ps_dec->pu1_v_intra_pred_line + + ps_dec->u2_frm_wd_in_mbs * BLK8x8SIZE; + + ps_dec->ps_deblk_mbn = ps_dec->ps_deblk_pic; + /* Initialize The Function Pointer Depending Upon the Entropy and MbAff Flag */ + { + if(ps_cur_slice->u1_mbaff_frame_flag) + { + ps_dec->pf_compute_bs = ih264d_compute_bs_mbaff; + ps_dec->pf_mvpred = ih264d_mvpred_mbaff; + } + else + { + ps_dec->pf_compute_bs = ih264d_compute_bs_non_mbaff; + ps_dec->u1_cur_mb_fld_dec_flag = ps_cur_slice->u1_field_pic_flag; + } + } + /* Set up the Parameter for DMA transfer */ + { + UWORD8 u1_field_pic_flag = ps_dec->ps_cur_slice->u1_field_pic_flag; + + UWORD8 u1_mbaff = ps_cur_slice->u1_mbaff_frame_flag; + + UWORD8 uc_lastmbs = (((ps_dec->u2_pic_wd) >> 4) + % (ps_dec->u1_recon_mb_grp >> u1_mbaff)); + UWORD16 ui16_lastmbs_widthY = + (uc_lastmbs ? (uc_lastmbs << 4) : ((ps_dec->u1_recon_mb_grp + >> u1_mbaff) << 4)); + UWORD16 ui16_lastmbs_widthUV = + uc_lastmbs ? (uc_lastmbs << 3) : ((ps_dec->u1_recon_mb_grp + >> u1_mbaff) << 3); + + ps_dec->s_tran_addrecon.pu1_dest_y = ps_dec->s_cur_pic.pu1_buf1; + ps_dec->s_tran_addrecon.pu1_dest_u = ps_dec->s_cur_pic.pu1_buf2; + ps_dec->s_tran_addrecon.pu1_dest_v = ps_dec->s_cur_pic.pu1_buf3; + + ps_dec->s_tran_addrecon.u2_frm_wd_y = ps_dec->u2_frm_wd_y + << u1_field_pic_flag; + ps_dec->s_tran_addrecon.u2_frm_wd_uv = ps_dec->u2_frm_wd_uv + << u1_field_pic_flag; + + if(u1_field_pic_flag) + { + ui16_lastmbs_widthY += ps_dec->u2_frm_wd_y; + ui16_lastmbs_widthUV += ps_dec->u2_frm_wd_uv; + } + + /* Normal Increment of Pointer */ + ps_dec->s_tran_addrecon.u4_inc_y[0] = ((ps_dec->u1_recon_mb_grp << 4) + >> u1_mbaff); + ps_dec->s_tran_addrecon.u4_inc_uv[0] = ((ps_dec->u1_recon_mb_grp << 4) + >> u1_mbaff); + + /* End of Row Increment */ + ps_dec->s_tran_addrecon.u4_inc_y[1] = (ui16_lastmbs_widthY + + (PAD_LEN_Y_H << 1) + + ps_dec->s_tran_addrecon.u2_frm_wd_y + * ((15 << u1_mbaff) + u1_mbaff)); + ps_dec->s_tran_addrecon.u4_inc_uv[1] = (ui16_lastmbs_widthUV + + (PAD_LEN_UV_H << 2) + + ps_dec->s_tran_addrecon.u2_frm_wd_uv + * ((15 << u1_mbaff) + u1_mbaff)); + + /* Assign picture numbers to each frame/field */ + /* only once per picture. */ + ih264d_assign_pic_num(ps_dec); + ps_dec->s_tran_addrecon.u2_mv_top_left_inc = (ps_dec->u1_recon_mb_grp + << 2) - 1 - (u1_mbaff << 2); + ps_dec->s_tran_addrecon.u2_mv_left_inc = ((ps_dec->u1_recon_mb_grp + >> u1_mbaff) - 1) << (4 + u1_mbaff); + } + /**********************************************************************/ + /* High profile related initialization at pictrue level */ + /**********************************************************************/ + if(ps_seq->u1_profile_idc == HIGH_PROFILE_IDC) + { + if((ps_seq->i4_seq_scaling_matrix_present_flag) + || (ps_pps->i4_pic_scaling_matrix_present_flag)) + { + ret = ih264d_form_scaling_matrix_picture(ps_seq, ps_pps, ps_dec); + ps_dec->s_high_profile.u1_scaling_present = 1; + } + else + { + ret = ih264d_form_default_scaling_matrix(ps_dec); + } + + if(ps_pps->i4_transform_8x8_mode_flag) + { + ps_dec->s_high_profile.u1_transform8x8_present = 1; + } + } + else + { + ret = ih264d_form_default_scaling_matrix(ps_dec); + } + + if(ret != OK) + return ret; + + /* required while reading the transform_size_8x8 u4_flag */ + ps_dec->s_high_profile.u1_direct_8x8_inference_flag = + ps_seq->u1_direct_8x8_inference_flag; + ps_dec->s_high_profile.s_cavlc_ctxt = ps_dec->s_cavlc_ctxt; + + ps_dec->i1_recon_in_thread3_flag = 1; + ps_dec->ps_frame_buf_ip_recon = &ps_dec->s_tran_addrecon; + if(ps_dec->u1_separate_parse) + { + memcpy(&ps_dec->s_tran_addrecon_parse, &ps_dec->s_tran_addrecon, + sizeof(tfr_ctxt_t)); + if(ps_dec->u4_num_cores >= 3 && ps_dec->i1_recon_in_thread3_flag) + { + memcpy(&ps_dec->s_tran_iprecon, &ps_dec->s_tran_addrecon, + sizeof(tfr_ctxt_t)); + ps_dec->ps_frame_buf_ip_recon = &ps_dec->s_tran_iprecon; + } + } + + + ih264d_init_deblk_tfr_ctxt(ps_dec,&(ps_dec->s_pad_mgr), &(ps_dec->s_tran_addrecon), + ps_dec->u2_frm_wd_in_mbs, 0); + + ps_dec->ps_cur_deblk_mb = ps_dec->ps_deblk_pic; + ps_dec->u4_cur_deblk_mb_num = 0; + + ps_dec->u4_deblk_mb_x = 0; + ps_dec->u4_deblk_mb_y = 0; + ps_dec->pu4_wt_ofsts = ps_dec->pu4_wts_ofsts_mat; + + ps_dec->u4_first_slice_in_pic = 0; + H264_MUTEX_UNLOCK(&ps_dec->process_disp_mutex); + return OK; +} + +/*! + ************************************************************************** + * \if Function name : ih264d_deblock_display \endif + * + * \brief : The function callls the deblocking routine and manages + : the Recon buffers and displays . + * \return : Nothing + * + ************************************************************************** + */ +WORD32 ih264d_end_of_pic_dispbuf_mgr(dec_struct_t * ps_dec) +{ + dec_slice_params_t *ps_cur_slice = ps_dec->ps_cur_slice; + UWORD8 u1_num_of_users = 0; + WORD32 ret; + + H264_MUTEX_LOCK(&ps_dec->process_disp_mutex); + if(1) + { + + { + ih264d_delete_nonref_nondisplay_pics(ps_dec->ps_dpb_mgr); + if(ps_cur_slice->u1_mmco_equalto5 + || (ps_cur_slice->u1_nal_unit_type == IDR_SLICE_NAL)) + { + ps_dec->ps_cur_pic->i4_poc = 0; + if(ps_dec->u2_total_mbs_coded + == (ps_dec->ps_cur_sps->u2_max_mb_addr + 1)) + ih264d_reset_ref_bufs(ps_dec->ps_dpb_mgr); + ih264d_release_display_bufs(ps_dec); + } + if(IVD_DECODE_FRAME_OUT != ps_dec->e_frm_out_mode) + { + ret = ih264d_assign_display_seq(ps_dec); + if(ret != OK) + return ret; + } + } + + if(ps_cur_slice->u1_nal_ref_idc) + { + /* Mark pic buf as needed for reference */ + ih264_buf_mgr_set_status((buf_mgr_t *)ps_dec->pv_pic_buf_mgr, + ps_dec->u1_pic_buf_id, + BUF_MGR_REF); + /* Mark mv buf as needed for reference */ + ih264_buf_mgr_set_status((buf_mgr_t *)ps_dec->pv_mv_buf_mgr, + ps_dec->au1_pic_buf_id_mv_buf_id_map[ps_dec->u1_pic_buf_id], + BUF_MGR_REF); + ps_dec->au1_pic_buf_ref_flag[ps_dec->u1_pic_buf_id] = 1; + } + + /* 420 consumer */ + /* Increment the number of users by 1 for display based upon */ + /*the SEEK KEY FRAME control sent to decoder */ + if(((0 == ps_dec->u1_last_pic_not_decoded) + && (0 + == (ps_dec->ps_cur_pic->u4_pack_slc_typ + & ps_dec->u4_skip_frm_mask))) + || (ps_cur_slice->u1_nal_unit_type == IDR_SLICE_NAL)) + { + /* Mark pic buf as needed for display */ + ih264_buf_mgr_set_status((buf_mgr_t *)ps_dec->pv_pic_buf_mgr, + ps_dec->u1_pic_buf_id, + BUF_MGR_IO); + + } + + if(!ps_cur_slice->u1_field_pic_flag + || ((TOP_FIELD_ONLY | BOT_FIELD_ONLY) + != ps_dec->u1_top_bottom_decoded)) + { + pic_buffer_t *ps_cur_pic = ps_dec->ps_cur_pic; + ps_cur_pic->u2_disp_width = ps_dec->u2_disp_width; + ps_cur_pic->u2_disp_height = ps_dec->u2_disp_height >> 1; + + ps_cur_pic->u2_crop_offset_y = ps_dec->u2_crop_offset_y; + ps_cur_pic->u2_crop_offset_uv = ps_dec->u2_crop_offset_uv; + ps_cur_pic->u1_pic_type = 0; + { + WORD64 i8_display_poc; + i8_display_poc = (WORD64)ps_dec->i4_prev_max_display_seq + + ps_dec->ps_cur_pic->i4_poc; + if(IS_OUT_OF_RANGE_S32(i8_display_poc)) + { + ps_dec->i4_prev_max_display_seq = 0; + } + } + ret = ih264d_insert_pic_in_display_list( + ps_dec->ps_dpb_mgr, + ps_dec->u1_pic_buf_id, + ps_dec->i4_prev_max_display_seq + + ps_dec->ps_cur_pic->i4_poc, + ps_dec->ps_cur_pic->i4_frame_num); + if(ret != OK) + return ret; + + { + ivd_video_decode_op_t * ps_dec_output = + (ivd_video_decode_op_t *)ps_dec->pv_dec_out; + + ps_dec_output->u4_frame_decoded_flag = 1; + } + if(ps_dec->au1_pic_buf_ref_flag[ps_dec->u1_pic_buf_id] == 0) + { + ih264_buf_mgr_release((buf_mgr_t *)ps_dec->pv_mv_buf_mgr, + ps_dec->au1_pic_buf_id_mv_buf_id_map[ps_dec->u1_pic_buf_id], + BUF_MGR_REF); + ps_dec->au1_pic_buf_ref_flag[ps_dec->u1_pic_buf_id] = 0; + + } + } + else + { + H264_DEC_DEBUG_PRINT("pic not inserted display %d %d\n", + ps_cur_slice->u1_field_pic_flag, + ps_dec->u1_second_field); + } + + if(!ps_cur_slice->u1_field_pic_flag + || ((TOP_FIELD_ONLY | BOT_FIELD_ONLY) + == ps_dec->u1_top_bottom_decoded)) + { + if(IVD_DECODE_FRAME_OUT == ps_dec->e_frm_out_mode) + { + ret = ih264d_assign_display_seq(ps_dec); + if(ret != OK) + return ret; + } + } + } + + H264_MUTEX_UNLOCK(&ps_dec->process_disp_mutex); + + return OK; +} + +void ih264d_err_pic_dispbuf_mgr(dec_struct_t *ps_dec) +{ + dec_slice_params_t *ps_cur_slice = ps_dec->ps_cur_slice; + ivd_video_decode_op_t * ps_dec_output = + (ivd_video_decode_op_t *)ps_dec->pv_dec_out; + + ih264_buf_mgr_release((buf_mgr_t *)ps_dec->pv_pic_buf_mgr, + ps_dec->u1_pic_buf_id, + BUF_MGR_REF); + ih264_buf_mgr_release((buf_mgr_t *)ps_dec->pv_mv_buf_mgr, + ps_dec->au1_pic_buf_id_mv_buf_id_map[ps_dec->u1_pic_buf_id], + BUF_MGR_REF); + ih264_buf_mgr_release((buf_mgr_t *)ps_dec->pv_pic_buf_mgr, + ps_dec->u1_pic_buf_id, + BUF_MGR_IO); +} + +void ih264d_deblock_picture(void *ptr) +{ + dec_struct_t *ps_dec = (dec_struct_t *)ptr; + + { + /*Deblock picture only if all the mb's in the frame have been decoded*/ + if(ps_dec->u1_pic_decode_done == 1) + { + if(ps_dec->ps_cur_slice->u1_mbaff_frame_flag + || ps_dec->ps_cur_slice->u1_field_pic_flag) + { + ps_dec->p_DeblockPicture[ps_dec->ps_cur_slice->u1_mbaff_frame_flag]( + ps_dec); + } + else + + { + + ih264d_deblock_picture_progressive(ps_dec); + } + + } + } + +} + +/*! + ************************************************************************** + * \if Function name : ih264d_deblock_display \endif + * + * \brief : The function callls the deblocking routine and manages + : the Recon buffers and displays . + * \return : Nothing + * + ************************************************************************** + */ +WORD32 ih264d_deblock_display(dec_struct_t *ps_dec) +{ + WORD32 ret; + /* Call deblocking */ + ih264d_deblock_picture(ps_dec); + + ret = ih264d_end_of_pic_dispbuf_mgr(ps_dec); + if(ret != OK) + return ret; + + return OK; +} + +/* + *! + ************************************************************************** + * \if Function name : EndofPoc \endif + * + * \brief + * EndofPoc Processing + * + * \return + * 0 on Success and Error code otherwise + ************************************************************************** + */ + +WORD32 ih264d_end_of_pic(dec_struct_t *ps_dec) +{ + dec_slice_params_t *ps_cur_slice = ps_dec->ps_cur_slice; + WORD32 ret; + + { + dec_err_status_t * ps_err = ps_dec->ps_dec_err_status; + if(ps_err->u1_err_flag & REJECT_CUR_PIC) + { + ih264d_err_pic_dispbuf_mgr(ps_dec); + return ERROR_NEW_FRAME_EXPECTED; + } + } + + H264_MUTEX_LOCK(&ps_dec->process_disp_mutex); + ret = ih264d_end_of_pic_processing(ps_dec); + if(ret != OK) + return ret; + /*--------------------------------------------------------------------*/ + /* ih264d_decode_pic_order_cnt - calculate the Pic Order Cnt */ + /* Needed to detect end of picture */ + /*--------------------------------------------------------------------*/ + + H264_MUTEX_UNLOCK(&ps_dec->process_disp_mutex); + + return OK; +} + + +/*! + ************************************************************************** + * \if Function name : ih264d_fix_error_in_dpb \endif + * + * \brief + * fix error in DPB + * + * \return + * Number of node(s) deleted + ************************************************************************** + */ + +WORD32 ih264d_fix_error_in_dpb(dec_struct_t *ps_dec) +{ + /*--------------------------------------------------------------------*/ + /* If there is common node in lt_list and st_list then delete it from */ + /* st_list */ + /*--------------------------------------------------------------------*/ + UWORD8 no_of_nodes_deleted = 0; + UWORD8 lt_ref_num = ps_dec->ps_dpb_mgr->u1_num_lt_ref_bufs; + struct dpb_info_t *ps_lt_curr_dpb = ps_dec->ps_dpb_mgr->ps_dpb_ht_head; + while(lt_ref_num && ps_lt_curr_dpb) + { + if(ps_dec->ps_dpb_mgr->ps_dpb_st_head + && ((ps_lt_curr_dpb->s_bot_field.u1_reference_info + | ps_lt_curr_dpb->s_top_field.u1_reference_info) + == (IS_SHORT_TERM | IS_LONG_TERM))) + { + struct dpb_info_t *ps_st_next_dpb = ps_dec->ps_dpb_mgr->ps_dpb_st_head; + struct dpb_info_t *ps_st_curr_dpb = ps_dec->ps_dpb_mgr->ps_dpb_st_head; + UWORD8 st_ref_num = ps_dec->ps_dpb_mgr->u1_num_st_ref_bufs; + while(st_ref_num && ps_st_curr_dpb) + { + if(ps_st_curr_dpb == ps_lt_curr_dpb) + { + if(st_ref_num == ps_dec->ps_dpb_mgr->u1_num_st_ref_bufs) + { + ps_dec->ps_dpb_mgr->ps_dpb_st_head = + ps_dec->ps_dpb_mgr->ps_dpb_st_head->ps_prev_short; + ps_st_curr_dpb = ps_dec->ps_dpb_mgr->ps_dpb_st_head; + } + else + { + ps_st_next_dpb->ps_prev_short = ps_st_curr_dpb->ps_prev_short; + } + ps_dec->ps_dpb_mgr->u1_num_st_ref_bufs--; + no_of_nodes_deleted++; + break; + } + ps_st_next_dpb = ps_st_curr_dpb; + ps_st_curr_dpb = ps_st_curr_dpb->ps_prev_short; + st_ref_num--; + } + } + ps_lt_curr_dpb = ps_lt_curr_dpb->ps_prev_long; + lt_ref_num--; + } + return no_of_nodes_deleted; +} + + +/*! + ************************************************************************** + * \if Function name : DecodeSlice \endif + * + * \brief + * Parses a slice + * + * \return + * 0 on Success and Error code otherwise + ************************************************************************** + */ + +WORD32 ih264d_parse_decode_slice(UWORD8 u1_is_idr_slice, + UWORD8 u1_nal_ref_idc, + dec_struct_t *ps_dec /* Decoder parameters */ + ) +{ + dec_bit_stream_t * ps_bitstrm = ps_dec->ps_bitstrm; + dec_pic_params_t *ps_pps; + dec_seq_params_t *ps_seq; + dec_slice_params_t *ps_cur_slice = ps_dec->ps_cur_slice; + pocstruct_t s_tmp_poc = {0}; + WORD32 i_delta_poc[2]; + WORD32 i4_poc = 0; + UWORD16 u2_first_mb_in_slice, u2_frame_num; + UWORD8 u1_field_pic_flag, u1_redundant_pic_cnt = 0, u1_slice_type; + UWORD32 u4_idr_pic_id = 0; + UWORD8 u1_bottom_field_flag, u1_pic_order_cnt_type; + + UWORD8 u1_nal_unit_type; + UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; + UWORD32 *pu4_bitstrm_ofst = &ps_bitstrm->u4_ofst; + WORD8 i1_is_end_of_poc; + + WORD32 ret, end_of_frame; + WORD32 prev_slice_err, num_mb_skipped; + UWORD8 u1_mbaff; + pocstruct_t *ps_cur_poc; + + UWORD32 u4_temp; + WORD32 i_temp; + UWORD32 u4_call_end_of_pic = 0; + + /* read FirstMbInSlice and slice type*/ + ps_dec->ps_dpb_cmds->u1_dpb_commands_read_slc = 0; + u2_first_mb_in_slice = ih264d_uev(pu4_bitstrm_ofst, + pu4_bitstrm_buf); + if(u2_first_mb_in_slice + >= (ps_dec->u2_frm_ht_in_mbs * ps_dec->u2_frm_wd_in_mbs)) + { + + return ERROR_CORRUPTED_SLICE; + } + + /*we currently don not support ASO*/ + if(((u2_first_mb_in_slice << ps_cur_slice->u1_mbaff_frame_flag) + <= ps_dec->u2_cur_mb_addr) && (ps_dec->u4_first_slice_in_pic == 0)) + { + return ERROR_CORRUPTED_SLICE; + } + + COPYTHECONTEXT("SH: first_mb_in_slice",u2_first_mb_in_slice); + + u4_temp = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); + + if(u4_temp > 9) + return ERROR_INV_SLC_TYPE_T; + + u1_slice_type = u4_temp; + COPYTHECONTEXT("SH: slice_type",(u1_slice_type)); + /* Find Out the Slice Type is 5 to 9 or not then Set the Flag */ + /* u1_sl_typ_5_9 = 1 .Which tells that all the slices in the Pic*/ + /* will be of same type of current */ + if(u1_slice_type > 4) + { + u1_slice_type -= 5; + } + + u4_temp = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); + if(u4_temp & MASK_ERR_PIC_SET_ID) + return ERROR_INV_SLICE_HDR_T; + /* discard slice if pic param is invalid */ + COPYTHECONTEXT("SH: pic_parameter_set_id", u4_temp); + ps_pps = &ps_dec->ps_pps[u4_temp]; + if(FALSE == ps_pps->u1_is_valid) + { + return ERROR_INV_SLICE_HDR_T; + } + ps_seq = ps_pps->ps_sps; + if(!ps_seq) + return ERROR_INV_SLICE_HDR_T; + if(FALSE == ps_seq->u1_is_valid) + return ERROR_INV_SLICE_HDR_T; + + /* Get the frame num */ + u2_frame_num = ih264d_get_bits_h264(ps_bitstrm, + ps_seq->u1_bits_in_frm_num); +// H264_DEC_DEBUG_PRINT("FRAME %d First MB in slice: %d\n", u2_frame_num, u2_first_mb_in_slice); + + COPYTHECONTEXT("SH: frame_num", u2_frame_num); +// H264_DEC_DEBUG_PRINT("Second field: %d frame num: %d prv_frame_num: %d \n", ps_dec->u1_second_field, u2_frame_num, ps_dec->u2_prv_frame_num); + if(!ps_dec->u1_first_slice_in_stream && ps_dec->u4_first_slice_in_pic) + { + pocstruct_t *ps_prev_poc = &ps_dec->s_prev_pic_poc; + pocstruct_t *ps_cur_poc = &ps_dec->s_cur_pic_poc; + + ps_dec->u2_mbx = 0xffff; + ps_dec->u2_mby = 0; + + if((0 == u1_is_idr_slice) && ps_cur_slice->u1_nal_ref_idc) + ps_dec->u2_prev_ref_frame_num = ps_cur_slice->u2_frame_num; + + if(u1_is_idr_slice || ps_cur_slice->u1_mmco_equalto5) + ps_dec->u2_prev_ref_frame_num = 0; + + if(ps_dec->ps_cur_sps->u1_gaps_in_frame_num_value_allowed_flag) + { + ih264d_decode_gaps_in_frame_num(ps_dec, u2_frame_num); + } + + ps_prev_poc->i4_prev_frame_num_ofst = ps_cur_poc->i4_prev_frame_num_ofst; + ps_prev_poc->u2_frame_num = ps_cur_poc->u2_frame_num; + ps_prev_poc->u1_mmco_equalto5 = ps_cur_slice->u1_mmco_equalto5; + if(ps_cur_slice->u1_nal_ref_idc) + { + ps_prev_poc->i4_pic_order_cnt_lsb = ps_cur_poc->i4_pic_order_cnt_lsb; + ps_prev_poc->i4_pic_order_cnt_msb = ps_cur_poc->i4_pic_order_cnt_msb; + ps_prev_poc->i4_delta_pic_order_cnt_bottom = + ps_cur_poc->i4_delta_pic_order_cnt_bottom; + ps_prev_poc->i4_delta_pic_order_cnt[0] = + ps_cur_poc->i4_delta_pic_order_cnt[0]; + ps_prev_poc->i4_delta_pic_order_cnt[1] = + ps_cur_poc->i4_delta_pic_order_cnt[1]; + ps_prev_poc->u1_bot_field = ps_cur_poc->u1_bot_field; + } + + ps_dec->u2_total_mbs_coded = 0; + } + /* Get the field related flags */ + if(!ps_seq->u1_frame_mbs_only_flag) + { + + u1_field_pic_flag = ih264d_get_bit_h264(ps_bitstrm); + COPYTHECONTEXT("SH: field_pic_flag", u1_field_pic_flag); + u1_bottom_field_flag = 0; + + if(u1_field_pic_flag) + { + ps_dec->pu1_inv_scan = (UWORD8 *)gau1_ih264d_inv_scan_fld; + u1_bottom_field_flag = ih264d_get_bit_h264(ps_bitstrm); + COPYTHECONTEXT("SH: bottom_field_flag", u1_bottom_field_flag); + + } + else + { + ps_dec->pu1_inv_scan = (UWORD8 *)gau1_ih264d_inv_scan; + } + } + else + { + u1_field_pic_flag = 0; + u1_bottom_field_flag = 0; + + ps_dec->pu1_inv_scan = (UWORD8 *)gau1_ih264d_inv_scan; + } + + u1_nal_unit_type = SLICE_NAL; + if(u1_is_idr_slice) + { + u1_nal_unit_type = IDR_SLICE_NAL; + u4_idr_pic_id = ih264d_uev(pu4_bitstrm_ofst, + pu4_bitstrm_buf); + if(u4_idr_pic_id > 65535) + return ERROR_INV_SLICE_HDR_T; + COPYTHECONTEXT("SH: ", u4_idr_pic_id); + } + + /* read delta pic order count information*/ + i_delta_poc[0] = i_delta_poc[1] = 0; + s_tmp_poc.i4_pic_order_cnt_lsb = 0; + s_tmp_poc.i4_delta_pic_order_cnt_bottom = 0; + u1_pic_order_cnt_type = ps_seq->u1_pic_order_cnt_type; + if(u1_pic_order_cnt_type == 0) + { + i_temp = ih264d_get_bits_h264( + ps_bitstrm, + ps_seq->u1_log2_max_pic_order_cnt_lsb_minus); + if(i_temp < 0 || i_temp >= ps_seq->i4_max_pic_order_cntLsb) + return ERROR_INV_SLICE_HDR_T; + s_tmp_poc.i4_pic_order_cnt_lsb = i_temp; + COPYTHECONTEXT("SH: pic_order_cnt_lsb", s_tmp_poc.i4_pic_order_cnt_lsb); + + if((ps_pps->u1_pic_order_present_flag == 1) && (!u1_field_pic_flag)) + { + s_tmp_poc.i4_delta_pic_order_cnt_bottom = ih264d_sev( + pu4_bitstrm_ofst, pu4_bitstrm_buf); + //if(s_tmp_poc.i4_delta_pic_order_cnt_bottom > ps_seq->i4_max_pic_order_cntLsb) + COPYTHECONTEXT("SH: delta_pic_order_cnt_bottom", + s_tmp_poc.i4_delta_pic_order_cnt_bottom); + } + } + + s_tmp_poc.i4_delta_pic_order_cnt[0] = 0; + s_tmp_poc.i4_delta_pic_order_cnt[1] = 0; + if(u1_pic_order_cnt_type == 1 + && (!ps_seq->u1_delta_pic_order_always_zero_flag)) + { + s_tmp_poc.i4_delta_pic_order_cnt[0] = ih264d_sev(pu4_bitstrm_ofst, + pu4_bitstrm_buf); + COPYTHECONTEXT("SH: delta_pic_order_cnt[0]", + s_tmp_poc.i4_delta_pic_order_cnt[0]); + + if(ps_pps->u1_pic_order_present_flag && !u1_field_pic_flag) + { + s_tmp_poc.i4_delta_pic_order_cnt[1] = ih264d_sev( + pu4_bitstrm_ofst, pu4_bitstrm_buf); + COPYTHECONTEXT("SH: delta_pic_order_cnt[1]", + s_tmp_poc.i4_delta_pic_order_cnt[1]); + } + } + + if(ps_pps->u1_redundant_pic_cnt_present_flag) + { + u4_temp = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); + if(u4_temp > MAX_REDUNDANT_PIC_CNT) + return ERROR_INV_SLICE_HDR_T; + u1_redundant_pic_cnt = u4_temp; + COPYTHECONTEXT("SH: redundant_pic_cnt", u1_redundant_pic_cnt); + } + + /*--------------------------------------------------------------------*/ + /* Check if the slice is part of new picture */ + /*--------------------------------------------------------------------*/ + /* First slice of a picture is always considered as part of new picture */ + i1_is_end_of_poc = 1; + ps_dec->ps_dec_err_status->u1_err_flag &= MASK_REJECT_CUR_PIC; + + if(ps_dec->u4_first_slice_in_pic == 0) + { + i1_is_end_of_poc = ih264d_is_end_of_pic(u2_frame_num, u1_nal_ref_idc, + &s_tmp_poc, &ps_dec->s_cur_pic_poc, + ps_cur_slice, u1_pic_order_cnt_type, + u1_nal_unit_type, u4_idr_pic_id, + u1_field_pic_flag, + u1_bottom_field_flag); + if(i1_is_end_of_poc) + { + ps_dec->u1_first_slice_in_stream = 0; + return ERROR_INCOMPLETE_FRAME; + } + + } + + /*--------------------------------------------------------------------*/ + /* Check for error in slice and parse the missing/corrupted MB's */ + /* as skip-MB's in an inserted P-slice */ + /*--------------------------------------------------------------------*/ + u1_mbaff = ps_seq->u1_mb_aff_flag && (!u1_field_pic_flag); + prev_slice_err = 0; + + if(i1_is_end_of_poc || ps_dec->u1_first_slice_in_stream) + { + /* If the current slice is not a field or frame number of the current + * slice doesn't match with previous slice, and decoder is expecting + * to decode a field i.e. ps_dec->u1_top_bottom_decoded is not 0 and + * is not (TOP_FIELD_ONLY | BOT_FIELD_ONLY), treat it as a dangling + * field */ + if((u1_field_pic_flag == 0 || u2_frame_num != ps_dec->u2_prv_frame_num) + && ps_dec->u1_top_bottom_decoded != 0 + && ps_dec->u1_top_bottom_decoded + != (TOP_FIELD_ONLY | BOT_FIELD_ONLY)) + { + ps_dec->u1_dangling_field = 1; + if(ps_dec->u4_first_slice_in_pic) + { + // first slice - dangling field + prev_slice_err = 1; + } + else + { + // last slice - dangling field + prev_slice_err = 2; + } + + if(ps_dec->u1_top_bottom_decoded ==TOP_FIELD_ONLY) + ps_cur_slice->u1_bottom_field_flag = 1; + else + ps_cur_slice->u1_bottom_field_flag = 0; + + num_mb_skipped = (ps_dec->u2_frm_ht_in_mbs * ps_dec->u2_frm_wd_in_mbs) + - ps_dec->u2_total_mbs_coded; + ps_cur_poc = &ps_dec->s_cur_pic_poc; + + u1_is_idr_slice = ps_cur_slice->u1_nal_unit_type == IDR_SLICE_NAL; + } + else if(ps_dec->u4_first_slice_in_pic) + { + if(u2_first_mb_in_slice > 0) + { + // first slice - missing/header corruption + prev_slice_err = 1; + num_mb_skipped = u2_first_mb_in_slice << u1_mbaff; + ps_cur_poc = &s_tmp_poc; + + // initializing slice parameters + ps_cur_slice->u4_idr_pic_id = u4_idr_pic_id; + ps_cur_slice->u1_field_pic_flag = u1_field_pic_flag; + ps_cur_slice->u1_bottom_field_flag = u1_bottom_field_flag; + ps_cur_slice->i4_pic_order_cnt_lsb = + s_tmp_poc.i4_pic_order_cnt_lsb; + ps_cur_slice->u1_nal_unit_type = u1_nal_unit_type; + ps_cur_slice->u1_redundant_pic_cnt = u1_redundant_pic_cnt; + ps_cur_slice->u1_nal_ref_idc = u1_nal_ref_idc; + ps_cur_slice->u1_pic_order_cnt_type = u1_pic_order_cnt_type; + ps_cur_slice->u1_mbaff_frame_flag = ps_seq->u1_mb_aff_flag + && (!u1_field_pic_flag); + } + } + else + { + /* since i1_is_end_of_poc is set ,means new frame num is encountered. so conceal the current frame + * completely */ + prev_slice_err = 2; + num_mb_skipped = (ps_dec->u2_frm_ht_in_mbs + * ps_dec->u2_frm_wd_in_mbs) + - ps_dec->u2_total_mbs_coded; + ps_cur_poc = &s_tmp_poc; + } + } + else + { + if((u2_first_mb_in_slice << u1_mbaff) > ps_dec->u2_total_mbs_coded) + { + // previous slice - missing/corruption + prev_slice_err = 2; + num_mb_skipped = (u2_first_mb_in_slice << u1_mbaff) + - ps_dec->u2_total_mbs_coded; + ps_cur_poc = &s_tmp_poc; + } + else if((u2_first_mb_in_slice << u1_mbaff) < ps_dec->u2_total_mbs_coded) + { + return ERROR_CORRUPTED_SLICE; + } + } + + if(prev_slice_err) + { + ret = ih264d_mark_err_slice_skip(ps_dec, num_mb_skipped, u1_is_idr_slice, u2_frame_num, ps_cur_poc, prev_slice_err); + + if(ps_dec->u1_dangling_field == 1) + { + ps_dec->u1_second_field = 1 - ps_dec->u1_second_field; + ps_dec->u1_first_slice_in_stream = 0; + ps_dec->u1_top_bottom_decoded = TOP_FIELD_ONLY | BOT_FIELD_ONLY; + return ERROR_DANGLING_FIELD_IN_PIC; + } + + if(prev_slice_err == 2) + { + ps_dec->u1_first_slice_in_stream = 0; + return ERROR_INCOMPLETE_FRAME; + } + + if(ps_dec->u2_total_mbs_coded + >= ps_dec->u2_frm_ht_in_mbs * ps_dec->u2_frm_wd_in_mbs) + { + /* return if all MBs in frame are parsed*/ + ps_dec->u1_first_slice_in_stream = 0; + return ERROR_IN_LAST_SLICE_OF_PIC; + } + + if(ps_dec->ps_dec_err_status->u1_err_flag & REJECT_CUR_PIC) + { + ih264d_err_pic_dispbuf_mgr(ps_dec); + return ERROR_NEW_FRAME_EXPECTED; + } + + if(ret != OK) + return ret; + + i1_is_end_of_poc = 0; + } + + /* Increment only if the current slice has atleast 1 more MB */ + if (ps_dec->u4_first_slice_in_pic == 0 && + (ps_dec->ps_parse_cur_slice->u4_first_mb_in_slice < + (UWORD32)(ps_dec->u2_total_mbs_coded >> ps_dec->ps_cur_slice->u1_mbaff_frame_flag))) + { + ps_dec->ps_parse_cur_slice++; + ps_dec->u2_cur_slice_num++; + // in the case of single core increment ps_decode_cur_slice + if(ps_dec->u1_separate_parse == 0) + { + ps_dec->ps_decode_cur_slice++; + } + } + + ps_dec->u1_slice_header_done = 0; + + + if(u1_field_pic_flag) + { + ps_dec->u2_prv_frame_num = u2_frame_num; + } + + if(ps_cur_slice->u1_mmco_equalto5) + { + WORD32 i4_temp_poc; + WORD32 i4_top_field_order_poc, i4_bot_field_order_poc; + + if(!ps_cur_slice->u1_field_pic_flag) // or a complementary field pair + { + i4_top_field_order_poc = ps_dec->ps_cur_pic->i4_top_field_order_cnt; + i4_bot_field_order_poc = + ps_dec->ps_cur_pic->i4_bottom_field_order_cnt; + i4_temp_poc = MIN(i4_top_field_order_poc, + i4_bot_field_order_poc); + } + else if(!ps_cur_slice->u1_bottom_field_flag) + i4_temp_poc = ps_dec->ps_cur_pic->i4_top_field_order_cnt; + else + i4_temp_poc = ps_dec->ps_cur_pic->i4_bottom_field_order_cnt; + + WORD64 i8_result = (WORD64)i4_temp_poc + - ps_dec->ps_cur_pic->i4_top_field_order_cnt; + if(IS_OUT_OF_RANGE_S32(i8_result)) + { + return ERROR_INV_POC; + } + ps_dec->ps_cur_pic->i4_top_field_order_cnt = i8_result; + i8_result = (WORD64)i4_temp_poc + - ps_dec->ps_cur_pic->i4_bottom_field_order_cnt; + if(IS_OUT_OF_RANGE_S32(i8_result)) + { + return ERROR_INV_POC; + } + ps_dec->ps_cur_pic->i4_bottom_field_order_cnt = i8_result; + ps_dec->ps_cur_pic->i4_poc = i4_temp_poc; + ps_dec->ps_cur_pic->i4_avg_poc = i4_temp_poc; + } + if(ps_dec->u4_first_slice_in_pic) + { + ret = ih264d_decode_pic_order_cnt(u1_is_idr_slice, u2_frame_num, + &ps_dec->s_prev_pic_poc, + &s_tmp_poc, ps_cur_slice, ps_pps, + u1_nal_ref_idc, + u1_bottom_field_flag, + u1_field_pic_flag, &i4_poc); + if(ret != OK) + return ret; + /* Display seq no calculations */ + if(i4_poc >= ps_dec->i4_max_poc) + ps_dec->i4_max_poc = i4_poc; + /* IDR Picture or POC wrap around */ + if(i4_poc == 0) + { + WORD64 i8_temp; + i8_temp = (WORD64)ps_dec->i4_prev_max_display_seq + + ps_dec->i4_max_poc + + ps_dec->u1_max_dec_frame_buffering + 1; + /*If i4_prev_max_display_seq overflows integer range, reset it */ + ps_dec->i4_prev_max_display_seq = IS_OUT_OF_RANGE_S32(i8_temp)? + 0 : i8_temp; + ps_dec->i4_max_poc = 0; + } + } + + /*--------------------------------------------------------------------*/ + /* Copy the values read from the bitstream to the slice header and then*/ + /* If the slice is first slice in picture, then do Start of Picture */ + /* processing. */ + /*--------------------------------------------------------------------*/ + ps_cur_slice->i4_delta_pic_order_cnt[0] = i_delta_poc[0]; + ps_cur_slice->i4_delta_pic_order_cnt[1] = i_delta_poc[1]; + ps_cur_slice->u4_idr_pic_id = u4_idr_pic_id; + ps_cur_slice->u2_first_mb_in_slice = u2_first_mb_in_slice; + ps_cur_slice->u1_field_pic_flag = u1_field_pic_flag; + ps_cur_slice->u1_bottom_field_flag = u1_bottom_field_flag; + ps_cur_slice->u1_slice_type = u1_slice_type; + ps_cur_slice->i4_pic_order_cnt_lsb = s_tmp_poc.i4_pic_order_cnt_lsb; + + ps_cur_slice->u1_nal_unit_type = u1_nal_unit_type; + ps_cur_slice->u1_redundant_pic_cnt = u1_redundant_pic_cnt; + ps_cur_slice->u1_nal_ref_idc = u1_nal_ref_idc; + ps_cur_slice->u1_pic_order_cnt_type = u1_pic_order_cnt_type; + + if(ps_seq->u1_frame_mbs_only_flag) + ps_cur_slice->u1_direct_8x8_inference_flag = + ps_seq->u1_direct_8x8_inference_flag; + else + ps_cur_slice->u1_direct_8x8_inference_flag = 1; + + if(u1_slice_type == B_SLICE) + { + ps_cur_slice->u1_direct_spatial_mv_pred_flag = ih264d_get_bit_h264( + ps_bitstrm); + COPYTHECONTEXT("SH: direct_spatial_mv_pred_flag", + ps_cur_slice->u1_direct_spatial_mv_pred_flag); + + if(ps_cur_slice->u1_direct_spatial_mv_pred_flag) + ps_cur_slice->pf_decodeDirect = ih264d_decode_spatial_direct; + else + ps_cur_slice->pf_decodeDirect = ih264d_decode_temporal_direct; + if(!((ps_pps->ps_sps->u1_mb_aff_flag) && (!u1_field_pic_flag))) + ps_dec->pf_mvpred = ih264d_mvpred_nonmbaffB; + } + else + { + if(!((ps_pps->ps_sps->u1_mb_aff_flag) && (!u1_field_pic_flag))) + ps_dec->pf_mvpred = ih264d_mvpred_nonmbaff; + } + + if(ps_dec->u4_first_slice_in_pic) + { + if(u2_first_mb_in_slice == 0) + { + ret = ih264d_start_of_pic(ps_dec, i4_poc, &s_tmp_poc, u2_frame_num, ps_pps); + if(ret != OK) + return ret; + } + + ps_dec->u4_output_present = 0; + + { + ih264d_get_next_display_field(ps_dec, + ps_dec->ps_out_buffer, + &(ps_dec->s_disp_op)); + /* If error code is non-zero then there is no buffer available for display, + hence avoid format conversion */ + + if(0 != ps_dec->s_disp_op.u4_error_code) + { + ps_dec->u4_fmt_conv_cur_row = ps_dec->s_disp_frame_info.u4_y_ht; + } + else + ps_dec->u4_output_present = 1; + } + if(ps_dec->u1_separate_parse == 1) + { + if(ps_dec->u4_dec_thread_created == 0) + { + ithread_create(ps_dec->pv_dec_thread_handle, NULL, + (void *)ih264d_decode_picture_thread, + (void *)ps_dec); + + ps_dec->u4_dec_thread_created = 1; + } + + if((ps_dec->u4_num_cores == 3) && + ((ps_dec->u4_app_disable_deblk_frm == 0) || ps_dec->i1_recon_in_thread3_flag) + && (ps_dec->u4_bs_deblk_thread_created == 0)) + { + ps_dec->u4_start_recon_deblk = 0; + ithread_create(ps_dec->pv_bs_deblk_thread_handle, NULL, + (void *)ih264d_recon_deblk_thread, + (void *)ps_dec); + ps_dec->u4_bs_deblk_thread_created = 1; + } + } + + } + + /* INITIALIZATION of fn ptrs for MC and formMbPartInfo functions */ + { + UWORD8 uc_nofield_nombaff; + + + + uc_nofield_nombaff = ((ps_dec->ps_cur_slice->u1_field_pic_flag == 0) + && (ps_dec->ps_cur_slice->u1_mbaff_frame_flag == 0) + && (u1_slice_type != B_SLICE) + && (ps_dec->ps_cur_pps->u1_wted_pred_flag == 0)); + + /* Initialise MC and formMbPartInfo fn ptrs one time based on profile_idc */ + + if(uc_nofield_nombaff) + { + ps_dec->p_form_mb_part_info = ih264d_form_mb_part_info_bp; + ps_dec->p_motion_compensate = ih264d_motion_compensate_bp; + } + else + { + ps_dec->p_form_mb_part_info = ih264d_form_mb_part_info_mp; + ps_dec->p_motion_compensate = ih264d_motion_compensate_mp; + } + + + } + + /* + * Decide whether to decode the current picture or not + */ + { + dec_err_status_t * ps_err = ps_dec->ps_dec_err_status; + if(ps_err->u4_frm_sei_sync == u2_frame_num) + { + ps_err->u1_err_flag = ACCEPT_ALL_PICS; + ps_err->u4_frm_sei_sync = SYNC_FRM_DEFAULT; + } + ps_err->u4_cur_frm = u2_frame_num; + } + + /* Decision for decoding if the picture is to be skipped */ + { + WORD32 i4_skip_b_pic, i4_skip_p_pic; + + i4_skip_b_pic = (ps_dec->u4_skip_frm_mask & B_SLC_BIT) + && (B_SLICE == u1_slice_type) && (0 == u1_nal_ref_idc); + + i4_skip_p_pic = (ps_dec->u4_skip_frm_mask & P_SLC_BIT) + && (P_SLICE == u1_slice_type) && (0 == u1_nal_ref_idc); + + /**************************************************************/ + /* Skip the B picture if skip mask is set for B picture and */ + /* Current B picture is a non reference B picture or there is */ + /* no user for reference B picture */ + /**************************************************************/ + if(i4_skip_b_pic) + { + ps_dec->ps_cur_pic->u4_pack_slc_typ |= B_SLC_BIT; + /* Don't decode the picture in SKIP-B mode if that picture is B */ + /* and also it is not to be used as a reference picture */ + ps_dec->u1_last_pic_not_decoded = 1; + + return OK; + } + /**************************************************************/ + /* Skip the P picture if skip mask is set for P picture and */ + /* Current P picture is a non reference P picture or there is */ + /* no user for reference P picture */ + /**************************************************************/ + if(i4_skip_p_pic) + { + ps_dec->ps_cur_pic->u4_pack_slc_typ |= P_SLC_BIT; + /* Don't decode the picture in SKIP-P mode if that picture is P */ + /* and also it is not to be used as a reference picture */ + ps_dec->u1_last_pic_not_decoded = 1; + + return OK; + } + } + + { + UWORD16 u2_mb_x, u2_mb_y; + + ps_dec->i4_submb_ofst = ((u2_first_mb_in_slice + << ps_cur_slice->u1_mbaff_frame_flag) * SUB_BLK_SIZE) + - SUB_BLK_SIZE; + if(u2_first_mb_in_slice) + { + UWORD8 u1_mb_aff; + UWORD8 u1_field_pic; + UWORD16 u2_frm_wd_in_mbs; + u2_frm_wd_in_mbs = ps_seq->u2_frm_wd_in_mbs; + u1_mb_aff = ps_cur_slice->u1_mbaff_frame_flag; + u1_field_pic = ps_cur_slice->u1_field_pic_flag; + + { + UWORD32 x_offset; + UWORD32 y_offset; + UWORD32 u4_frame_stride; + tfr_ctxt_t *ps_trns_addr; // = &ps_dec->s_tran_addrecon_parse; + + if(ps_dec->u1_separate_parse) + { + ps_trns_addr = &ps_dec->s_tran_addrecon_parse; + } + else + { + ps_trns_addr = &ps_dec->s_tran_addrecon; + } + u2_mb_x = MOD(u2_first_mb_in_slice, u2_frm_wd_in_mbs); + u2_mb_y = DIV(u2_first_mb_in_slice, u2_frm_wd_in_mbs); + + u2_mb_y <<= u1_mb_aff; + + if((u2_mb_x > u2_frm_wd_in_mbs - 1) + || (u2_mb_y > ps_dec->u2_frm_ht_in_mbs - 1)) + { + return ERROR_CORRUPTED_SLICE; + } + + u4_frame_stride = ps_dec->u2_frm_wd_y << u1_field_pic; + x_offset = u2_mb_x << 4; + y_offset = (u2_mb_y * u4_frame_stride) << 4; + + ps_trns_addr->pu1_dest_y = ps_dec->s_cur_pic.pu1_buf1 + x_offset + + y_offset; + + u4_frame_stride = ps_dec->u2_frm_wd_uv << u1_field_pic; + x_offset >>= 1; + y_offset = (u2_mb_y * u4_frame_stride) << 3; + + x_offset *= YUV420SP_FACTOR; + + ps_trns_addr->pu1_dest_u = ps_dec->s_cur_pic.pu1_buf2 + x_offset + + y_offset; + ps_trns_addr->pu1_dest_v = ps_dec->s_cur_pic.pu1_buf3 + x_offset + + y_offset; + + ps_trns_addr->pu1_mb_y = ps_trns_addr->pu1_dest_y; + ps_trns_addr->pu1_mb_u = ps_trns_addr->pu1_dest_u; + ps_trns_addr->pu1_mb_v = ps_trns_addr->pu1_dest_v; + + + // assign the deblock structure pointers to start of slice + if(ps_dec->u1_separate_parse == 1) + { + ps_dec->ps_deblk_mbn = ps_dec->ps_deblk_pic + + (u2_first_mb_in_slice << u1_mb_aff); + } + else + { + ps_dec->ps_deblk_mbn = ps_dec->ps_deblk_pic + + (u2_first_mb_in_slice << u1_mb_aff); + } + + ps_dec->u2_cur_mb_addr = (u2_first_mb_in_slice << u1_mb_aff); + + ps_dec->ps_mv_cur = ps_dec->s_cur_pic.ps_mv + + ((u2_first_mb_in_slice << u1_mb_aff) << 4); + } + } + else + { + tfr_ctxt_t *ps_trns_addr; + + if(ps_dec->u1_separate_parse) + { + ps_trns_addr = &ps_dec->s_tran_addrecon_parse; + } + else + { + ps_trns_addr = &ps_dec->s_tran_addrecon; + } + + u2_mb_x = 0xffff; + u2_mb_y = 0; + // assign the deblock structure pointers to start of slice + ps_dec->u2_cur_mb_addr = 0; + ps_dec->ps_deblk_mbn = ps_dec->ps_deblk_pic; + ps_dec->ps_mv_cur = ps_dec->s_cur_pic.ps_mv; + ps_trns_addr->pu1_dest_y = ps_dec->s_cur_pic.pu1_buf1; + ps_trns_addr->pu1_dest_u = ps_dec->s_cur_pic.pu1_buf2; + ps_trns_addr->pu1_dest_v = ps_dec->s_cur_pic.pu1_buf3; + + ps_trns_addr->pu1_mb_y = ps_trns_addr->pu1_dest_y; + ps_trns_addr->pu1_mb_u = ps_trns_addr->pu1_dest_u; + ps_trns_addr->pu1_mb_v = ps_trns_addr->pu1_dest_v; + + } + + ps_dec->ps_part = ps_dec->ps_parse_part_params; + + ps_dec->u2_mbx = + (MOD(u2_first_mb_in_slice - 1, ps_seq->u2_frm_wd_in_mbs)); + ps_dec->u2_mby = + (DIV(u2_first_mb_in_slice - 1, ps_seq->u2_frm_wd_in_mbs)); + ps_dec->u2_mby <<= ps_cur_slice->u1_mbaff_frame_flag; + ps_dec->i2_prev_slice_mbx = ps_dec->u2_mbx; + ps_dec->i2_prev_slice_mby = ps_dec->u2_mby; + } + + /* RBSP stop bit is used for CABAC decoding*/ + ps_bitstrm->u4_max_ofst += ps_dec->ps_cur_pps->u1_entropy_coding_mode; + + ps_dec->u1_B = (u1_slice_type == B_SLICE); + ps_dec->u4_next_mb_skip = 0; + + ps_dec->ps_parse_cur_slice->u4_first_mb_in_slice = + ps_dec->ps_cur_slice->u2_first_mb_in_slice; + ps_dec->ps_parse_cur_slice->slice_type = + ps_dec->ps_cur_slice->u1_slice_type; + + + ps_dec->u4_start_recon_deblk = 1; + { + WORD32 num_entries; + WORD32 size; + UWORD8 *pu1_buf; + + num_entries = MAX_FRAMES; + if((1 >= ps_dec->ps_cur_sps->u1_num_ref_frames) && + (0 == ps_dec->i4_display_delay)) + { + num_entries = 1; + } + num_entries = ((2 * num_entries) + 1); + num_entries *= 2; + + + size = num_entries * sizeof(void *); + size += PAD_MAP_IDX_POC * sizeof(void *); + + pu1_buf = (UWORD8 *)ps_dec->pv_map_ref_idx_to_poc_buf; + pu1_buf += size * ps_dec->u2_cur_slice_num; + ps_dec->ps_parse_cur_slice->ppv_map_ref_idx_to_poc = ( void *)pu1_buf; + } + + if(ps_dec->u1_separate_parse) + { + ps_dec->ps_parse_cur_slice->pv_tu_coeff_data_start = ps_dec->pv_parse_tu_coeff_data; + } + else + { + ps_dec->pv_proc_tu_coeff_data = ps_dec->pv_parse_tu_coeff_data; + } + + ret = ih264d_fix_error_in_dpb(ps_dec); + if(ret < 0) + return ERROR_DBP_MANAGER_T; + + if(u1_slice_type == I_SLICE) + { + ps_dec->ps_cur_pic->u4_pack_slc_typ |= I_SLC_BIT; + + ret = ih264d_parse_islice(ps_dec, u2_first_mb_in_slice); + ps_dec->u1_pr_sl_type = u1_slice_type; + if(ps_dec->i4_pic_type != B_SLICE && ps_dec->i4_pic_type != P_SLICE) + ps_dec->i4_pic_type = I_SLICE; + + } + else if(u1_slice_type == P_SLICE) + { + ps_dec->ps_cur_pic->u4_pack_slc_typ |= P_SLC_BIT; + ret = ih264d_parse_pslice(ps_dec, u2_first_mb_in_slice); + ps_dec->u1_pr_sl_type = u1_slice_type; + if(ps_dec->i4_pic_type != B_SLICE) + ps_dec->i4_pic_type = P_SLICE; + } + else if(u1_slice_type == B_SLICE) + { + ps_dec->ps_cur_pic->u4_pack_slc_typ |= B_SLC_BIT; + ret = ih264d_parse_bslice(ps_dec, u2_first_mb_in_slice); + ps_dec->u1_pr_sl_type = u1_slice_type; + ps_dec->i4_pic_type = B_SLICE; + } + else + return ERROR_INV_SLC_TYPE_T; + + if(ps_dec->u1_slice_header_done) + { + /* set to zero to indicate a valid slice has been decoded */ + ps_dec->u1_first_slice_in_stream = 0; + } + + if(ret != OK) + return ret; + + if(u1_nal_ref_idc != 0) + { + if(!ps_dec->ps_dpb_cmds->u1_dpb_commands_read) + { + memcpy((void *)ps_dec->ps_dpb_cmds, (void *)(&(ps_dec->s_dpb_cmds_scratch)), + sizeof(dpb_commands_t)); + } + } + + /* storing last Mb X and MbY of the slice */ + ps_dec->i2_prev_slice_mbx = ps_dec->u2_mbx; + ps_dec->i2_prev_slice_mby = ps_dec->u2_mby; + + /* End of Picture detection */ + + if(ps_dec->u2_total_mbs_coded >= (ps_seq->u2_max_mb_addr + 1)) + { + ps_dec->u1_pic_decode_done = 1; + + } + + { + dec_err_status_t * ps_err = ps_dec->ps_dec_err_status; + if((ps_err->u1_err_flag & REJECT_PB_PICS) + && (ps_err->u1_cur_pic_type == PIC_TYPE_I)) + { + ps_err->u1_err_flag = ACCEPT_ALL_PICS; + } + } + + PRINT_BIN_BIT_RATIO(ps_dec) + + return ret; +} + diff --git a/dependencies/ih264d/decoder/ih264d_parse_slice.h b/dependencies/ih264d/decoder/ih264d_parse_slice.h new file mode 100644 index 00000000..0f82ec9d --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_parse_slice.h @@ -0,0 +1,56 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +#ifndef _IH264D_PARSE_SLICE_H_ +#define _IH264D_PARSE_SLICE_H_ +/*! + ************************************************************************** + * \file ih264d_parse_slice.h + * + * \brief + * Contains routines that decodes a slice NAL unit + * + * \date + * 19/12/2002 + * + * \author AI + ************************************************************************** + */ +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264d_structs.h" +#include "ih264d_error_handler.h" +WORD32 ih264d_fix_error_in_dpb(dec_struct_t * ps_dec); +WORD32 ih264d_parse_decode_slice(UWORD8 u1_is_idr_slice, + UWORD8 u1_nal_ref_idc, + dec_struct_t * ps_dec ); + +WORD32 ih264d_end_of_pic(dec_struct_t *ps_dec); +WORD32 ih264d_start_of_pic(dec_struct_t *ps_dec, + WORD32 i4_poc, + pocstruct_t *ps_temp_poc, + UWORD16 u2_frame_num, + dec_pic_params_t *ps_pps); + +WORD32 ih264d_ref_idx_reordering(dec_struct_t * ps_dec, UWORD8 u1_isB); +WORD32 ih264d_read_mmco_commands(dec_struct_t * ps_dec); +void ih264d_form_pred_weight_matrix(dec_struct_t *ps_dec); +WORD32 ih264d_end_of_pic_dispbuf_mgr(dec_struct_t * ps_dec); +#endif /* _IH264D_PARSE_SLICE_H_ */ diff --git a/dependencies/ih264d/decoder/ih264d_process_bslice.c b/dependencies/ih264d/decoder/ih264d_process_bslice.c new file mode 100644 index 00000000..fffa586b --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_process_bslice.c @@ -0,0 +1,2354 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/*! + ************************************************************************** + * \file ih264d_process_bslice.c + * + * \brief + * Contains routines that decode B slice type + * + * Detailed_description + * + * \date + * 21/12/2002 + * + * \author NS + ************************************************************************** + */ +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" + +#include <string.h> +#include "ih264d_structs.h" +#include "ih264d_bitstrm.h" +#include "ih264d_parse_cavlc.h" +#include "ih264d_mb_utils.h" +#include "ih264d_mvpred.h" +#include "ih264d_inter_pred.h" +#include "ih264d_process_pslice.h" +#include "ih264d_error_handler.h" +#include "ih264d_tables.h" +#include "ih264d_parse_slice.h" +#include "ih264d_process_pslice.h" +#include "ih264d_process_bslice.h" +#include "ih264d_tables.h" +#include "ih264d_parse_islice.h" +#include "ih264d_mvpred.h" + +void ih264d_init_cabac_contexts(UWORD8 u1_slice_type, dec_struct_t * ps_dec); +//UWORD32 g_hits = 0; +//UWORD32 g_miss = 0; +/*! + ************************************************************************** + * \if Function name : ih264d_decode_spatial_direct \endif + * + * \brief + * Decodes spatial direct mode. + * + * \return + * None. + * Arunoday T + ************************************************************************** + */ +WORD32 ih264d_decode_spatial_direct(dec_struct_t * ps_dec, + UWORD8 u1_wd_x, + dec_mb_info_t * ps_cur_mb_info, + UWORD8 u1_mb_num) +{ + mv_pred_t s_mv_pred, *ps_mv; + UWORD8 u1_col_zero_flag, u1_sub_mb_num, u1_direct_zero_pred_flag = 0; + UWORD8 u1_mbaff = ps_dec->ps_cur_slice->u1_mbaff_frame_flag; + mv_pred_t *ps_mv_ntop_start; + mv_pred_t *ps_mv_nmb_start = ps_dec->ps_mv_cur + (u1_mb_num << 4); + UWORD8 partition_size, sub_partition, u1_mb_partw, u1_mb_parth; + UWORD8 i; + WORD8 i1_pred, i1_ref_frame0, i1_ref_frame1; + struct pic_buffer_t *ps_ref_frame = NULL, *ps_col_pic, *ps_pic_buff0 = NULL, + *ps_pic_buff1 = NULL; + + UWORD8 u1_zero_pred_cond_f, u1_zero_pred_cond_b; + WORD16 i2_def_mv[2], i2_spat_pred_mv[4], *pi2_final_mv0, *pi2_final_mv1; + UWORD16 ui2_mask_fwd = 0, ui2_mask_bwd = 0, u2_mask = 0; + UWORD32 *pui32_weight_ofsts = NULL; + directmv_t s_mvdirect; + UWORD8 u1_colz; + UWORD8 u1_final_ref_idx = 0; + const UWORD8 *pu1_mb_parth = (const UWORD8 *)gau1_ih264d_mb_parth; + const UWORD8 *pu1_mb_partw = (const UWORD8 *)gau1_ih264d_mb_partw; + const UWORD16 sub_mask_table[] = + { 0x33, 0x3, 0x11, 0x1 }; + const UWORD16 mask_table[] = + { 0xffff, /*16x16 NA */ + 0xff, /* 16x8*/ + 0x3333, /* 8x16*/ + 0x33 };/* 8x8*/ + mv_pred_t s_temp_mv_pred; + WORD32 ret = 0; + + /* CHANGED CODE */ + ps_mv_ntop_start = ps_dec->ps_mv_cur + (u1_mb_num << 4) + - (ps_dec->u2_frm_wd_in_mbs << (4 + u1_mbaff)) + 12; + + /* assign default values for MotionVector as zero */ + i2_def_mv[0] = 0; + i2_def_mv[1] = 0; + + u1_direct_zero_pred_flag = ps_dec->pf_mvpred(ps_dec, ps_cur_mb_info, ps_mv_nmb_start, + ps_mv_ntop_start, &s_mv_pred, 0, 4, + 0, 1, B_DIRECT_SPATIAL); + + i2_spat_pred_mv[0] = s_mv_pred.i2_mv[0]; + i2_spat_pred_mv[1] = s_mv_pred.i2_mv[1]; + i2_spat_pred_mv[2] = s_mv_pred.i2_mv[2]; + i2_spat_pred_mv[3] = s_mv_pred.i2_mv[3]; + + i1_ref_frame0 = s_mv_pred.i1_ref_frame[0]; + i1_ref_frame1 = s_mv_pred.i1_ref_frame[1]; + + i1_ref_frame0 = (i1_ref_frame0 < 0) ? -1 : i1_ref_frame0; + i1_ref_frame1 = (i1_ref_frame1 < 0) ? -1 : i1_ref_frame1; + + i1_pred = 0; + + { + WORD8 u1_ref_idx, u1_ref_idx1; + UWORD32 uc_Idx, uc_Idx1; + UWORD8 u1_scale_ref = (ps_dec->ps_cur_slice->u1_mbaff_frame_flag + && ps_cur_mb_info->u1_mb_field_decodingflag); + u1_final_ref_idx = i1_ref_frame0; + if(i1_ref_frame0 >= 0) + { + /* convert RefIdx if it is MbAff */ + u1_ref_idx = i1_ref_frame0; + u1_ref_idx1 = i1_ref_frame0; + if(u1_scale_ref) + { + u1_ref_idx1 = u1_ref_idx >> 1; + if((u1_ref_idx & 0x01) != (1 - ps_cur_mb_info->u1_topmb)) + u1_ref_idx1 += MAX_REF_BUFS; + } + /* If i1_ref_frame0 < 0 then refIdxCol is obtained from ps_pic_buff1 */ + ps_pic_buff0 = ps_dec->ps_ref_pic_buf_lx[0][u1_ref_idx1]; + ps_ref_frame = ps_pic_buff0; + i1_pred = PRED_L0; + } + + if(i1_ref_frame1 >= 0) + { + /* convert RefIdx if it is MbAff */ + u1_ref_idx = i1_ref_frame1; + u1_ref_idx1 = i1_ref_frame1; + if(u1_scale_ref) + { + u1_ref_idx1 = u1_ref_idx >> 1; + if((u1_ref_idx & 0x01) != (1 - ps_cur_mb_info->u1_topmb)) + u1_ref_idx1 += MAX_REF_BUFS; + } + ps_pic_buff1 = ps_dec->ps_ref_pic_buf_lx[1][u1_ref_idx1]; + i1_pred = i1_pred | PRED_L1; + } + if(i1_ref_frame0 < 0) + { + ps_ref_frame = ps_pic_buff1; + u1_final_ref_idx = i1_ref_frame1; + } + + u1_zero_pred_cond_f = (u1_direct_zero_pred_flag) || (i1_ref_frame0 < 0); + u1_zero_pred_cond_b = (u1_direct_zero_pred_flag) || (i1_ref_frame1 < 0); + + if(ps_dec->ps_cur_pps->u1_wted_bipred_idc) + { + uc_Idx = ((i1_ref_frame0 < 1) ? 0 : i1_ref_frame0) + * ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[1]; + if(u1_scale_ref) + uc_Idx >>= 1; + uc_Idx1 = (i1_ref_frame1 < 0) ? 0 : i1_ref_frame1; + uc_Idx += (u1_scale_ref) ? (uc_Idx1 >> 1) : uc_Idx1; + pui32_weight_ofsts = + (UWORD32*)&ps_dec->pu4_wt_ofsts[2 * X3(uc_Idx)]; + + if(i1_ref_frame0 < 0) + pui32_weight_ofsts += 1; + + if(u1_scale_ref && (ps_dec->ps_cur_pps->u1_wted_bipred_idc == 2)) + { + WORD16 i2_ref_idx; + i2_ref_idx = MAX(i1_ref_frame0, 0); + i2_ref_idx *= (ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[1] + << 1); + i2_ref_idx += MAX(i1_ref_frame1, 0); + if(!ps_cur_mb_info->u1_topmb) + i2_ref_idx += + (ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[0] + << 1) + * (ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[1] + << 1); + pui32_weight_ofsts = (UWORD32*)&ps_dec->pu4_mbaff_wt_mat[2 + * X3(i2_ref_idx)]; + } + } + } + + s_temp_mv_pred.i1_ref_frame[0] = i1_ref_frame0; + s_temp_mv_pred.i1_ref_frame[1] = i1_ref_frame1; + s_temp_mv_pred.u1_col_ref_pic_idx = ps_ref_frame->u1_mv_buf_id; + s_temp_mv_pred.u1_pic_type = ps_ref_frame->u1_pic_type; + + /**********************************************************************/ + /* Call the function which gets the number of partitions and */ + /* partition info of colocated Mb */ + /**********************************************************************/ + + ps_dec->pf_parse_mvdirect(ps_dec, ps_dec->ps_col_pic, &s_mvdirect, u1_wd_x, + ps_dec->i4_submb_ofst, ps_cur_mb_info); + ps_col_pic = ps_dec->ps_col_pic; + if((s_mvdirect.u1_col_zeroflag_change == 0) || u1_direct_zero_pred_flag) + { + WORD16 i2_mv_x, i2_mv_y, i2_mvX1, i2_mvY1; + /* Most probable case */ + u1_col_zero_flag = *(ps_col_pic->pu1_col_zero_flag + + s_mvdirect.i4_mv_indices[0]); + u1_col_zero_flag = u1_col_zero_flag & 0x01; + + if(u1_zero_pred_cond_f || ((i1_ref_frame0 == 0) && (u1_col_zero_flag == 1))) + { + i2_mv_x = 0; + i2_mv_y = 0; + } + else + { + i2_mv_x = i2_spat_pred_mv[0]; + i2_mv_y = i2_spat_pred_mv[1]; + + } + + if(u1_zero_pred_cond_b || ((i1_ref_frame1 == 0) && (u1_col_zero_flag == 1))) + { + i2_mvX1 = 0; + i2_mvY1 = 0; + } + else + { + i2_mvX1 = i2_spat_pred_mv[2]; + i2_mvY1 = i2_spat_pred_mv[3]; + } + + u1_sub_mb_num = ps_dec->u1_sub_mb_num; + u1_mb_partw = (u1_wd_x >> 2); + + + if(i1_ref_frame0 >= 0) + { + { + pred_info_pkd_t *ps_pred_pkd; + WORD16 i2_mv[2]; + WORD8 i1_ref_idx= 0; + + i2_mv[0] = i2_mv_x; + i2_mv[1] = i2_mv_y; + + ps_pred_pkd = ps_dec->ps_pred_pkd + ps_dec->u4_pred_info_pkd_idx; + ih264d_fill_pred_info(i2_mv,u1_mb_partw,u1_mb_partw,u1_sub_mb_num,i1_pred, + ps_pred_pkd,ps_pic_buff0->u1_pic_buf_id,i1_ref_idx,pui32_weight_ofsts, + ps_pic_buff0->u1_pic_type); + ps_dec->u4_pred_info_pkd_idx++; + ps_cur_mb_info->u1_num_pred_parts++; + + + } + + } + + if(i1_ref_frame1 >= 0) + { + { + pred_info_pkd_t *ps_pred_pkd; + WORD16 i2_mv[2]; + WORD8 i1_ref_idx= 0; + + i2_mv[0] = i2_mvX1; + i2_mv[1] = i2_mvY1; + + ps_pred_pkd = ps_dec->ps_pred_pkd + ps_dec->u4_pred_info_pkd_idx; + ih264d_fill_pred_info(i2_mv,u1_mb_partw,u1_mb_partw,u1_sub_mb_num,i1_pred, + ps_pred_pkd,ps_pic_buff1->u1_pic_buf_id,i1_ref_idx,pui32_weight_ofsts, + ps_pic_buff1->u1_pic_type); + ps_dec->u4_pred_info_pkd_idx++; + ps_cur_mb_info->u1_num_pred_parts++; + + + } + } + + + /* Replication optimisation */ + s_temp_mv_pred.i2_mv[0] = i2_mv_x; + s_temp_mv_pred.i2_mv[1] = i2_mv_y; + s_temp_mv_pred.i2_mv[2] = i2_mvX1; + s_temp_mv_pred.i2_mv[3] = i2_mvY1; + + /* Calculating colocated zero information */ + { + /*************************************/ + /* If(bit2 and bit3 set) */ + /* then */ + /* (bit0 and bit1) => submmbmode */ + /* (bit2 and bit3) => mbmode */ + /* else */ + /* (bit0 and bit1) => mbmode */ + /*************************************/ + /*UWORD8 u1_packed_mb_sub_mb_mode = sub_partition ? + (s_mvdirect.i1_partitionsize[0]) : ((s_mvdirect.i1_partitionsize[0]) << 2);*/ + UWORD8 u1_packed_mb_sub_mb_mode = (u1_mb_partw == 2) ? 0x03 : 0; + + if(i1_ref_frame0 < 0) + { + i2_mv_x = i2_mvX1; + i2_mv_y = i2_mvY1; + } + + /* Change from left shift 4 to 6 - Varun */ + u1_colz = (ps_cur_mb_info->u1_mb_field_decodingflag << 1) + | ((u1_final_ref_idx == 0) && (ABS(i2_mv_x) <= 1) + && (ABS(i2_mv_y) <= 1)); + u1_colz |= (u1_packed_mb_sub_mb_mode << 6); + } + ps_mv = ps_mv_nmb_start + u1_sub_mb_num; + ih264d_rep_mv_colz(ps_dec, &s_temp_mv_pred, ps_mv, u1_sub_mb_num, u1_colz, + u1_mb_partw, u1_mb_partw); + if(u1_wd_x == MB_SIZE) + ps_dec->u1_currB_type = 0; + + + + return OK; + } + /***************************************************************************/ + /* If present MB is 16x16 and the partition of colocated Mb is >= PRED_8x8 */ + /* i.e 8x8 or less than 8x8 partitions then set up DMA for (0,0) and */ + /* spatially predicted motion vector and do the multiplexing after */ + /* motion compensation */ + /***************************************************************************/ + + + if((u1_wd_x == MB_SIZE) && (s_mvdirect.i1_num_partitions > 2)) + { + ps_cur_mb_info->u1_Mux = 1; + if(i1_ref_frame0 >= 0) + { + + { + pred_info_pkd_t *ps_pred_pkd; + WORD8 i1_ref_idx= 0; + + ps_pred_pkd = ps_dec->ps_pred_pkd + ps_dec->u4_pred_info_pkd_idx; + ih264d_fill_pred_info(&(i2_spat_pred_mv[0]),4,4,0,i1_pred, + ps_pred_pkd,ps_pic_buff0->u1_pic_buf_id,i1_ref_idx,pui32_weight_ofsts, + ps_pic_buff0->u1_pic_type); + ps_dec->u4_pred_info_pkd_idx++; + ps_cur_mb_info->u1_num_pred_parts++; + + + } + + /****** (0,0) Motion vectors DMA *****/ + { + pred_info_pkd_t *ps_pred_pkd; + WORD16 i2_mv[2]; + WORD8 i1_ref_idx= 0; + + i2_mv[0] = 0; + i2_mv[1] = 0; + + ps_pred_pkd = ps_dec->ps_pred_pkd + ps_dec->u4_pred_info_pkd_idx; + ih264d_fill_pred_info(i2_mv,4,4,0,i1_pred, + ps_pred_pkd,ps_pic_buff0->u1_pic_buf_id,i1_ref_idx,pui32_weight_ofsts, + ps_pic_buff0->u1_pic_type); + ps_dec->u4_pred_info_pkd_idx++; + ps_cur_mb_info->u1_num_pred_parts++; + + + } + } + if(i1_ref_frame1 >= 0) + { + { + pred_info_pkd_t *ps_pred_pkd; + WORD16 i2_mv[2]; + WORD8 i1_ref_idx= 0; + + ps_pred_pkd = ps_dec->ps_pred_pkd + ps_dec->u4_pred_info_pkd_idx; + ih264d_fill_pred_info(&(i2_spat_pred_mv[2]),4,4,0,i1_pred, + ps_pred_pkd,ps_pic_buff1->u1_pic_buf_id,i1_ref_idx,pui32_weight_ofsts, + ps_pic_buff1->u1_pic_type); + ps_dec->u4_pred_info_pkd_idx++; + ps_cur_mb_info->u1_num_pred_parts++; + + + } + + /****** (0,0) Motion vectors DMA *****/ + + { + pred_info_pkd_t *ps_pred_pkd; + WORD16 i2_mv[2]; + WORD8 i1_ref_idx= 0; + + i2_mv[0] = 0; + i2_mv[1] = 0; + + ps_pred_pkd = ps_dec->ps_pred_pkd + ps_dec->u4_pred_info_pkd_idx; + ih264d_fill_pred_info(i2_mv,4,4,0,i1_pred, + ps_pred_pkd,ps_pic_buff1->u1_pic_buf_id,i1_ref_idx,pui32_weight_ofsts, + ps_pic_buff1->u1_pic_type); + ps_dec->u4_pred_info_pkd_idx++; + ps_cur_mb_info->u1_num_pred_parts++; + + + } + } + } + + /*u1_col = *(ps_col_pic->pu1_col_zero_flag + s_mvdirect.i4_mv_indices[0]); + u1_col &= 1; + u1_init = 0;*/ + + for(i = 0; i < s_mvdirect.i1_num_partitions; i++) + { + partition_size = s_mvdirect.i1_partitionsize[i]; + u1_sub_mb_num = s_mvdirect.i1_submb_num[i]; + + sub_partition = partition_size >> 2; + partition_size &= 0x3; + u1_mb_partw = pu1_mb_partw[partition_size]; + u1_mb_parth = pu1_mb_parth[partition_size]; + u2_mask = mask_table[partition_size]; + if(sub_partition != 0) + { + u1_mb_partw >>= 1; + u1_mb_parth >>= 1; + u2_mask = sub_mask_table[partition_size]; + } + + u1_col_zero_flag = *(ps_col_pic->pu1_col_zero_flag + + s_mvdirect.i4_mv_indices[i]); + u1_col_zero_flag = u1_col_zero_flag & 0x01; + + /*if(u1_col != u1_col_zero_flag) + u1_init = 1;*/ + + if(u1_zero_pred_cond_f || ((i1_ref_frame0 == 0) && (u1_col_zero_flag == 1))) + { + pi2_final_mv0 = &i2_def_mv[0]; + ui2_mask_fwd |= (u2_mask << u1_sub_mb_num); + } + else + pi2_final_mv0 = &i2_spat_pred_mv[0]; + + if(u1_zero_pred_cond_b || ((i1_ref_frame1 == 0) && (u1_col_zero_flag == 1))) + { + pi2_final_mv1 = &i2_def_mv[0]; + ui2_mask_bwd |= (u2_mask << u1_sub_mb_num); + } + else + pi2_final_mv1 = &i2_spat_pred_mv[2]; + + if(ps_cur_mb_info->u1_Mux != 1) + { + /*u1_sub_mb_x = u1_sub_mb_num & 0x03; + uc_sub_mb_y = (u1_sub_mb_num >> 2);*/ + if(i1_ref_frame0 >= 0) + { + + { + pred_info_pkd_t *ps_pred_pkd; + WORD8 i1_ref_idx= 0; + + ps_pred_pkd = ps_dec->ps_pred_pkd + ps_dec->u4_pred_info_pkd_idx; + ih264d_fill_pred_info(pi2_final_mv0,u1_mb_partw,u1_mb_parth,u1_sub_mb_num,i1_pred, + ps_pred_pkd,ps_pic_buff0->u1_pic_buf_id,i1_ref_idx,pui32_weight_ofsts, + ps_pic_buff0->u1_pic_type); + ps_dec->u4_pred_info_pkd_idx++; + ps_cur_mb_info->u1_num_pred_parts++; + + + } + + } + + if(i1_ref_frame1 >= 0) + { + { + pred_info_pkd_t *ps_pred_pkd; + WORD8 i1_ref_idx= 0; + + ps_pred_pkd = ps_dec->ps_pred_pkd + ps_dec->u4_pred_info_pkd_idx; + ih264d_fill_pred_info(pi2_final_mv1,u1_mb_partw,u1_mb_parth,u1_sub_mb_num,i1_pred, + ps_pred_pkd,ps_pic_buff1->u1_pic_buf_id,i1_ref_idx,pui32_weight_ofsts, + ps_pic_buff1->u1_pic_type); + ps_dec->u4_pred_info_pkd_idx++; + ps_cur_mb_info->u1_num_pred_parts++; + + + } + } + } + + /* Replication optimisation */ + s_temp_mv_pred.i2_mv[0] = pi2_final_mv0[0]; + s_temp_mv_pred.i2_mv[1] = pi2_final_mv0[1]; + s_temp_mv_pred.i2_mv[2] = pi2_final_mv1[0]; + s_temp_mv_pred.i2_mv[3] = pi2_final_mv1[1]; + + /* Calculating colocated zero information */ + { + WORD16 i2_mv_x = 0, i2_mv_y = 0; + /*************************************/ + /* If(bit2 and bit3 set) */ + /* then */ + /* (bit0 and bit1) => submmbmode */ + /* (bit2 and bit3) => mbmode */ + /* else */ + /* (bit0 and bit1) => mbmode */ + /*************************************/ + UWORD8 u1_packed_mb_sub_mb_mode = + sub_partition ? (s_mvdirect.i1_partitionsize[i]) : ((s_mvdirect.i1_partitionsize[i]) + << 2); + + if(i1_ref_frame0 >= 0) + { + i2_mv_x = pi2_final_mv0[0]; + i2_mv_y = pi2_final_mv0[1]; + } + else + { + i2_mv_x = pi2_final_mv1[0]; + i2_mv_y = pi2_final_mv1[1]; + } + + u1_colz = (ps_cur_mb_info->u1_mb_field_decodingflag << 1) + | ((u1_final_ref_idx == 0) && (ABS(i2_mv_x) <= 1) + && (ABS(i2_mv_y) <= 1)); + u1_colz |= (u1_packed_mb_sub_mb_mode << 4); + } + ps_mv = ps_mv_nmb_start + u1_sub_mb_num; + ih264d_rep_mv_colz(ps_dec, &s_temp_mv_pred, ps_mv, u1_sub_mb_num, u1_colz, + u1_mb_parth, u1_mb_partw); + } + i = 0; + if(i1_ref_frame0 >= 0) + ps_cur_mb_info->u2_mask[i++] = ui2_mask_fwd; + if(i1_ref_frame1 >= 0) + ps_cur_mb_info->u2_mask[i] = ui2_mask_bwd; + + /*if(u1_init) + H264_DEC_DEBUG_PRINT("hit\n"); + else + H264_DEC_DEBUG_PRINT("miss\n");*/ + + return OK; +} + +/*! + ************************************************************************** + * \if Function name : ih264d_decode_temporal_direct \endif + * + * \brief + * Decodes temporal direct mode. + * + * \return + * None. + * + ************************************************************************** + */ +WORD32 ih264d_decode_temporal_direct(dec_struct_t * ps_dec, + UWORD8 u1_wd_x, + dec_mb_info_t * ps_cur_mb_info, + UWORD8 u1_mb_num) +{ + struct pic_buffer_t *ps_pic_buff0, *ps_pic_buff1, *ps_col_pic; + mv_pred_t *ps_mv, s_temp_mv_pred; + UWORD8 u1_sub_mb_num; + UWORD8 u1_mbaff = ps_dec->ps_cur_slice->u1_mbaff_frame_flag; + WORD16 i2_mv_x0, i2_mv_y0, i2_mv_x1, i2_mv_y1; + UWORD8 u1_mb_partw, u1_mb_parth; + UWORD8 i, partition_size, sub_partition; + UWORD32 *pui32_weight_ofsts = NULL; + directmv_t s_mvdirect; + const UWORD8 *pu1_mb_parth = (const UWORD8 *)gau1_ih264d_mb_parth; + const UWORD8 *pu1_mb_partw = (const UWORD8 *)gau1_ih264d_mb_partw; + WORD8 c_refFrm0, c_refFrm1; + UWORD8 u1_ref_idx0, u1_is_cur_mb_fld; + WORD32 pic0_poc, pic1_poc, cur_poc; + WORD32 ret = 0; + + u1_is_cur_mb_fld = ps_cur_mb_info->u1_mb_field_decodingflag; + ps_pic_buff1 = ps_dec->ps_ref_pic_buf_lx[1][0]; + + /**********************************************************************/ + /* Call the function which gets the number of partitions and */ + /* partition info of colocated Mb */ + /**********************************************************************/ + ps_dec->pf_parse_mvdirect(ps_dec, ps_dec->ps_col_pic, &s_mvdirect, u1_wd_x, + ps_dec->i4_submb_ofst, ps_cur_mb_info); + ps_col_pic = ps_dec->ps_col_pic; + + for(i = 0; i < s_mvdirect.i1_num_partitions; i++) + { + UWORD8 u1_colz; + partition_size = s_mvdirect.i1_partitionsize[i]; + u1_sub_mb_num = s_mvdirect.i1_submb_num[i]; + ps_mv = ps_col_pic->ps_mv + s_mvdirect.i4_mv_indices[i]; + + /* This should be removed to catch unitialized memory read */ + u1_ref_idx0 = 0; + + sub_partition = partition_size >> 2; + partition_size &= 0x3; + u1_mb_partw = pu1_mb_partw[partition_size]; + u1_mb_parth = pu1_mb_parth[partition_size]; + if(sub_partition != 0) + { + u1_mb_partw >>= 1; + u1_mb_parth >>= 1; + } + c_refFrm0 = ps_mv->i1_ref_frame[0]; + c_refFrm1 = ps_mv->i1_ref_frame[1]; + + if((c_refFrm0 == -1) && (c_refFrm1 == -1)) + { + u1_ref_idx0 = 0; + ps_pic_buff0 = ps_dec->ps_ref_pic_buf_lx[0][0]; + if(u1_mbaff && u1_is_cur_mb_fld) + { + if(ps_cur_mb_info->u1_topmb) + { + pic0_poc = ps_pic_buff0->i4_top_field_order_cnt; + pic1_poc = ps_pic_buff1->i4_top_field_order_cnt; + cur_poc = ps_dec->ps_cur_pic->i4_top_field_order_cnt; + } + else + { + pic1_poc = ps_pic_buff1->i4_bottom_field_order_cnt; + cur_poc = ps_dec->ps_cur_pic->i4_bottom_field_order_cnt; + ps_pic_buff1 = ps_dec->ps_ref_pic_buf_lx[1][MAX_REF_BUFS]; + pic0_poc = ps_pic_buff0->i4_bottom_field_order_cnt; + ps_pic_buff0 = ps_dec->ps_ref_pic_buf_lx[0][MAX_REF_BUFS]; + } + } + else + { + pic0_poc = ps_pic_buff0->i4_avg_poc; + pic1_poc = ps_pic_buff1->i4_avg_poc; + cur_poc = ps_dec->ps_cur_pic->i4_poc; + } + } + else + { + UWORD8 uc_i, u1_num_frw_ref_pics; + UWORD8 buf_id, u1_pic_type; + buf_id = ps_mv->u1_col_ref_pic_idx; + u1_pic_type = ps_mv->u1_pic_type; + if(ps_dec->ps_cur_slice->u1_field_pic_flag) + { + if(s_mvdirect.u1_vert_mv_scale == FRM_TO_FLD) + { + u1_pic_type = TOP_FLD; + if(ps_dec->ps_cur_slice->u1_bottom_field_flag) + u1_pic_type = BOT_FLD; + } + } + u1_num_frw_ref_pics = + ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[0]; + + for(uc_i = 0; uc_i < u1_num_frw_ref_pics; uc_i++) + { + if(ps_dec->ps_cur_slice->u1_field_pic_flag) + { + if(ps_dec->ps_ref_pic_buf_lx[0][uc_i]->u1_mv_buf_id == buf_id) + { + if(ps_dec->ps_ref_pic_buf_lx[0][uc_i]->u1_pic_type + == u1_pic_type) + { + u1_ref_idx0 = uc_i; + break; + } + } + } + else + { + if(ps_dec->ps_ref_pic_buf_lx[0][uc_i]->u1_mv_buf_id == buf_id) + { + u1_ref_idx0 = uc_i; + break; + } + } + } + + ps_pic_buff0 = ps_dec->ps_ref_pic_buf_lx[0][u1_ref_idx0]; + ps_pic_buff1 = ps_dec->ps_ref_pic_buf_lx[1][0]; + + if(u1_mbaff && u1_is_cur_mb_fld) + { + pic0_poc = ps_pic_buff0->i4_top_field_order_cnt; + u1_ref_idx0 <<= 1; + if(s_mvdirect.u1_vert_mv_scale == ONE_TO_ONE) + { + if(u1_pic_type == BOT_FLD) + { + pic0_poc = ps_pic_buff0->i4_bottom_field_order_cnt; + ps_pic_buff0 = ps_dec->ps_ref_pic_buf_lx[0][(u1_ref_idx0 + >> 1) + MAX_REF_BUFS]; + if(ps_cur_mb_info->u1_topmb) + u1_ref_idx0++; + } + else + { + if(1 - ps_cur_mb_info->u1_topmb) + u1_ref_idx0++; + } + } + if(s_mvdirect.u1_vert_mv_scale == FRM_TO_FLD) + { + if(1 - ps_cur_mb_info->u1_topmb) + { + pic0_poc = ps_pic_buff0->i4_bottom_field_order_cnt; + ps_pic_buff0 = ps_dec->ps_ref_pic_buf_lx[0][(u1_ref_idx0 + >> 1) + MAX_REF_BUFS]; + } + } + if(ps_cur_mb_info->u1_topmb) + { + pic1_poc = ps_pic_buff1->i4_top_field_order_cnt; + cur_poc = ps_dec->ps_cur_pic->i4_top_field_order_cnt; + } + else + { + pic1_poc = ps_pic_buff1->i4_bottom_field_order_cnt; + cur_poc = ps_dec->ps_cur_pic->i4_bottom_field_order_cnt; + ps_pic_buff1 = ps_dec->ps_ref_pic_buf_lx[1][MAX_REF_BUFS]; + } + } + else + { + pic0_poc = ps_pic_buff0->i4_avg_poc; + pic1_poc = ps_pic_buff1->i4_avg_poc; + cur_poc = ps_dec->ps_cur_pic->i4_poc; + } + } + { + WORD16 i16_td; + WORD64 diff; + if(c_refFrm0 >= 0) + { + i2_mv_x0 = ps_mv->i2_mv[0]; + i2_mv_y0 = ps_mv->i2_mv[1]; + } + else if(c_refFrm1 >= 0) + { + i2_mv_x0 = ps_mv->i2_mv[2]; + i2_mv_y0 = ps_mv->i2_mv[3]; + } + else + { + i2_mv_x0 = 0; + i2_mv_y0 = 0; + } + /* If FRM_TO_FLD or FLD_TO_FRM scale the "y" component of the colocated Mv*/ + if(s_mvdirect.u1_vert_mv_scale == FRM_TO_FLD) + { + i2_mv_y0 /= 2; + } + else if(s_mvdirect.u1_vert_mv_scale == FLD_TO_FRM) + { + i2_mv_y0 *= 2; + } + + diff = (WORD64)pic1_poc - pic0_poc; + i16_td = CLIP_S8(diff); + if((ps_pic_buff0->u1_is_short == 0) || (i16_td == 0)) + { + i2_mv_x1 = 0; + i2_mv_y1 = 0; + } + else + { + WORD16 i2_tb, i2_tx, i2_dist_scale_factor, i2_temp; + + diff = (WORD64)cur_poc - pic0_poc; + i2_tb = CLIP_S8(diff); + + i2_tx = (16384 + ABS(SIGN_POW2_DIV(i16_td, 1))) / i16_td; + i2_dist_scale_factor = CLIP_S11( + (((i2_tb * i2_tx) + 32) >> 6)); + i2_temp = (i2_mv_x0 * i2_dist_scale_factor + 128) >> 8; + i2_mv_x1 = i2_temp - i2_mv_x0; + i2_mv_x0 = i2_temp; + + i2_temp = (i2_mv_y0 * i2_dist_scale_factor + 128) >> 8; + i2_mv_y1 = i2_temp - i2_mv_y0; + i2_mv_y0 = i2_temp; + } + { + mv_pred_t *ps_mv; + + /*u1_sub_mb_x = u1_sub_mb_num & 0x03; + uc_sub_mb_y = u1_sub_mb_num >> 2;*/ + if(ps_dec->ps_cur_pps->u1_wted_bipred_idc) + { + UWORD8 u1_idx = + u1_ref_idx0 + * ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[1]; + UWORD8 u1_scale_ref = u1_mbaff && u1_is_cur_mb_fld; + if(u1_scale_ref) + u1_idx >>= 1; + pui32_weight_ofsts = (UWORD32*)&ps_dec->pu4_wt_ofsts[2 + * X3(u1_idx)]; + if(u1_scale_ref + && (ps_dec->ps_cur_pps->u1_wted_bipred_idc + == 2)) + { + WORD16 i2_ref_idx; + i2_ref_idx = u1_ref_idx0; + i2_ref_idx *= + (ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[1] + << 1); + if(!ps_cur_mb_info->u1_topmb) + i2_ref_idx += + (ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[0] + << 1) + * (ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[1] + << 1); + pui32_weight_ofsts = + (UWORD32*)&ps_dec->pu4_mbaff_wt_mat[2 + * X3(i2_ref_idx)]; + } + } + { + pred_info_pkd_t *ps_pred_pkd; + WORD16 i2_mv[2]; + WORD8 i1_ref_idx= 0; + + i2_mv[0] = i2_mv_x0; + i2_mv[1] = i2_mv_y0; + + ps_pred_pkd = ps_dec->ps_pred_pkd + ps_dec->u4_pred_info_pkd_idx; + ih264d_fill_pred_info(i2_mv,u1_mb_partw,u1_mb_parth,u1_sub_mb_num,PRED_L0 | PRED_L1, + ps_pred_pkd,ps_pic_buff0->u1_pic_buf_id,i1_ref_idx,pui32_weight_ofsts, + ps_pic_buff0->u1_pic_type); + ps_dec->u4_pred_info_pkd_idx++; + ps_cur_mb_info->u1_num_pred_parts++; + + + } + { + pred_info_pkd_t *ps_pred_pkd; + WORD16 i2_mv[2]; + WORD8 i1_ref_idx= 0; + + i2_mv[0] = i2_mv_x1; + i2_mv[1] = i2_mv_y1; + + ps_pred_pkd = ps_dec->ps_pred_pkd + ps_dec->u4_pred_info_pkd_idx; + ih264d_fill_pred_info(i2_mv,u1_mb_partw,u1_mb_parth,u1_sub_mb_num,PRED_L0 | PRED_L1, + ps_pred_pkd,ps_pic_buff1->u1_pic_buf_id,i1_ref_idx,pui32_weight_ofsts, + ps_pic_buff1->u1_pic_type); + ps_dec->u4_pred_info_pkd_idx++; + ps_cur_mb_info->u1_num_pred_parts++; + + + } + + /* Replication optimisation */ + s_temp_mv_pred.i2_mv[0] = i2_mv_x0; + s_temp_mv_pred.i2_mv[1] = i2_mv_y0; + s_temp_mv_pred.i2_mv[2] = i2_mv_x1; + s_temp_mv_pred.i2_mv[3] = i2_mv_y1; + s_temp_mv_pred.i1_ref_frame[0] = u1_ref_idx0; + s_temp_mv_pred.i1_ref_frame[1] = 0; + s_temp_mv_pred.u1_col_ref_pic_idx = ps_pic_buff0->u1_mv_buf_id; + s_temp_mv_pred.u1_pic_type = ps_pic_buff0->u1_pic_type; + ps_mv = ps_dec->ps_mv_cur + (u1_mb_num << 4) + u1_sub_mb_num; + + { + WORD16 i2_mv_x = 0, i2_mv_y = 0; + UWORD8 u1_packed_mb_sub_mb_mode = + sub_partition ? (s_mvdirect.i1_partitionsize[i]) : ((s_mvdirect.i1_partitionsize[i]) + << 2); + + if(c_refFrm0 >= 0) + { + i2_mv_x = i2_mv_x0; + i2_mv_y = i2_mv_y0; + } + else + { + i2_mv_x = i2_mv_x1; + i2_mv_y = i2_mv_y1; + } + + u1_colz = + (ps_cur_mb_info->u1_mb_field_decodingflag << 1) + | ((u1_ref_idx0 == 0) + && (ABS(i2_mv_x) + <= 1) + && (ABS(i2_mv_y) + <= 1)); + u1_colz |= (u1_packed_mb_sub_mb_mode << 4); + } + ih264d_rep_mv_colz(ps_dec, &s_temp_mv_pred, ps_mv, u1_sub_mb_num, + u1_colz, u1_mb_parth, u1_mb_partw); + } + } + } + /* return value set to UWORD8 to make it homogeneous */ + /* with decodespatialdirect */ + return OK; +} + +void ih264d_convert_frm_to_fld_list(struct pic_buffer_t *ps_ref_pic_buf_lx, + UWORD8 *pu1_L0, + dec_struct_t *ps_dec, + UWORD8 u1_num_short_term_bufs) +{ + UWORD8 uc_count = *pu1_L0, i, uc_l1, uc_lx, j; + struct pic_buffer_t *ps_ref_lx[2], *ps_ref_pic_lx; + UWORD8 u1_bottom_field_flag; + dec_slice_params_t *ps_cur_slice; + UWORD8 u1_ref[2], u1_fld[2], u1_same_fld, u1_op_fld; + UWORD32 ui_half_num_of_sub_mbs; + + uc_l1 = 0; + uc_lx = 0; + ps_cur_slice = ps_dec->ps_cur_slice; + ps_ref_pic_lx = ps_ref_pic_buf_lx - MAX_REF_BUFS; + ps_ref_lx[0] = ps_ref_pic_buf_lx; + ps_ref_lx[1] = ps_ref_pic_buf_lx; + u1_bottom_field_flag = ps_cur_slice->u1_bottom_field_flag; + ui_half_num_of_sub_mbs = ((ps_dec->u2_pic_ht * ps_dec->u2_pic_wd) >> 5); + if(u1_bottom_field_flag) + { + u1_ref[0] = BOT_REF; + u1_ref[1] = TOP_REF; + u1_fld[0] = BOT_FLD; + u1_fld[1] = TOP_FLD; + u1_same_fld = BOT_FLD; + u1_op_fld = TOP_FLD; + } + else + { + u1_ref[0] = TOP_REF; + u1_ref[1] = BOT_REF; + u1_fld[0] = TOP_FLD; + u1_fld[1] = BOT_FLD; + u1_same_fld = TOP_FLD; + u1_op_fld = BOT_FLD; + } + + /* Create the field list starting with all the short term */ + /* frames followed by all the long term frames. No long term */ + /* reference field should have a list idx less than a short */ + /* term reference field during initiailization. */ + + for(j = 0; j < 2; j++) + { + i = ((j == 0) ? 0 : u1_num_short_term_bufs); + uc_count = ((j == 0) ? u1_num_short_term_bufs : *pu1_L0); + for(; i < uc_count; i++, ps_ref_lx[0]++) + { + /* Search field of same parity in Frame list */ + if((ps_ref_lx[0]->u1_pic_type & u1_ref[0])) // || ((ps_ref_lx[0]->u1_picturetype & 0x3) == 0)) + { + /* Insert PIC of same parity in RefPicList */ + ih264d_insert_pic_in_ref_pic_listx(ps_ref_pic_lx, ps_ref_lx[0]); + ps_ref_pic_lx->i4_pic_num = (ps_ref_pic_lx->i4_pic_num * 2 + 1); + ps_ref_pic_lx->u1_long_term_pic_num = + (ps_ref_pic_lx->u1_long_term_frm_idx * 2 + 1); + ps_ref_pic_lx->u1_pic_type = u1_same_fld; + if(u1_fld[0] & BOT_FLD) + { + ps_ref_pic_lx->u1_pic_type = BOT_FLD; + ps_ref_pic_lx->pu1_buf1 += ps_ref_pic_lx->u2_frm_wd_y; + ps_ref_pic_lx->pu1_buf2 += ps_ref_pic_lx->u2_frm_wd_uv; + ps_ref_pic_lx->pu1_buf3 += ps_ref_pic_lx->u2_frm_wd_uv; + if(ps_ref_pic_lx->u1_picturetype & 0x3) + { + ps_ref_pic_lx->pu1_col_zero_flag += ui_half_num_of_sub_mbs; + ps_ref_pic_lx->ps_mv += ui_half_num_of_sub_mbs; + } + ps_ref_pic_lx->i4_poc = + ps_ref_pic_lx->i4_bottom_field_order_cnt; + ps_ref_pic_lx->i4_avg_poc = + ps_ref_pic_lx->i4_bottom_field_order_cnt; + } + else + { + ps_ref_pic_lx->u1_pic_type = TOP_FLD; + ps_ref_pic_lx->i4_poc = ps_ref_pic_lx->i4_top_field_order_cnt; + ps_ref_pic_lx->i4_avg_poc = + ps_ref_pic_lx->i4_top_field_order_cnt; + } + + ps_ref_pic_lx++; + uc_lx++; + /* Find field of opposite parity */ + if(uc_l1 < uc_count && ps_ref_lx[1]) + { + while(!(ps_ref_lx[1]->u1_pic_type & u1_ref[1])) + { + ps_ref_lx[1]++; + uc_l1++; + if(uc_l1 >= uc_count) + ps_ref_lx[1] = 0; + if(!ps_ref_lx[1]) + break; + } + + if(ps_ref_lx[1]) + { + uc_l1++; + ih264d_insert_pic_in_ref_pic_listx(ps_ref_pic_lx, + ps_ref_lx[1]); + ps_ref_pic_lx->u1_pic_type = u1_op_fld; + ps_ref_pic_lx->i4_pic_num = (ps_ref_pic_lx->i4_pic_num * 2); + ps_ref_pic_lx->u1_long_term_pic_num = + (ps_ref_pic_lx->u1_long_term_frm_idx * 2); + if(u1_fld[1] & BOT_FLD) + { + ps_ref_pic_lx->u1_pic_type = BOT_FLD; + ps_ref_pic_lx->pu1_buf1 += ps_ref_pic_lx->u2_frm_wd_y; + ps_ref_pic_lx->pu1_buf2 += ps_ref_pic_lx->u2_frm_wd_uv; + ps_ref_pic_lx->pu1_buf3 += ps_ref_pic_lx->u2_frm_wd_uv; + if(ps_ref_pic_lx->u1_picturetype & 0x3) + { + ps_ref_pic_lx->pu1_col_zero_flag += + ui_half_num_of_sub_mbs; + ps_ref_pic_lx->ps_mv += ui_half_num_of_sub_mbs; + } + ps_ref_pic_lx->i4_poc = + ps_ref_pic_lx->i4_bottom_field_order_cnt; + ps_ref_pic_lx->i4_avg_poc = + ps_ref_pic_lx->i4_bottom_field_order_cnt; + } + else + { + ps_ref_pic_lx->u1_pic_type = TOP_FLD; + ps_ref_pic_lx->i4_poc = + ps_ref_pic_lx->i4_top_field_order_cnt; + ps_ref_pic_lx->i4_avg_poc = + ps_ref_pic_lx->i4_top_field_order_cnt; + } + ps_ref_pic_lx++; + uc_lx++; + ps_ref_lx[1]++; + } + } + } + } + + /* Same parity fields are over, now insert left over opposite parity fields */ + /** Added if(ps_ref_lx[1]) for error checks */ + if(ps_ref_lx[1]) + { + for(; uc_l1 < uc_count; uc_l1++) + { + if(ps_ref_lx[1]->u1_pic_type & u1_ref[1]) + { + /* Insert PIC of opposite parity in RefPicList */ + ih264d_insert_pic_in_ref_pic_listx(ps_ref_pic_lx, + ps_ref_lx[1]); + ps_ref_pic_lx->u1_pic_type = u1_op_fld; + ps_ref_pic_lx->i4_pic_num = (ps_ref_pic_lx->i4_pic_num * 2); + ps_ref_pic_lx->u1_long_term_pic_num = + (ps_ref_pic_lx->u1_long_term_frm_idx * 2); + if(u1_op_fld == BOT_FLD) + { + ps_ref_pic_lx->u1_pic_type = BOT_FLD; + ps_ref_pic_lx->pu1_buf1 += ps_ref_pic_lx->u2_frm_wd_y; + ps_ref_pic_lx->pu1_buf2 += ps_ref_pic_lx->u2_frm_wd_uv; + ps_ref_pic_lx->pu1_buf3 += ps_ref_pic_lx->u2_frm_wd_uv; + if(ps_ref_pic_lx->u1_picturetype & 0x3) + { + ps_ref_pic_lx->pu1_col_zero_flag += + ui_half_num_of_sub_mbs; + ps_ref_pic_lx->ps_mv += ui_half_num_of_sub_mbs; + } + ps_ref_pic_lx->i4_poc = + ps_ref_pic_lx->i4_bottom_field_order_cnt; + ps_ref_pic_lx->i4_avg_poc = + ps_ref_pic_lx->i4_bottom_field_order_cnt; + } + else + { + ps_ref_pic_lx->i4_poc = + ps_ref_pic_lx->i4_top_field_order_cnt; + ps_ref_pic_lx->i4_avg_poc = + ps_ref_pic_lx->i4_top_field_order_cnt; + } + ps_ref_pic_lx++; + uc_lx++; + ps_ref_lx[1]++; + } + } + } + } + *pu1_L0 = uc_lx; +} + +void ih264d_convert_frm_mbaff_list(dec_struct_t *ps_dec) +{ + struct pic_buffer_t **ps_ref_pic_lx; + UWORD8 u1_max_ref_idx, idx; + UWORD16 u2_frm_wd_y, u2_frm_wd_uv; + struct pic_buffer_t **ps_ref_pic_buf_lx; + UWORD32 u4_half_num_of_sub_mbs = ((ps_dec->u2_pic_ht * ps_dec->u2_pic_wd) >> 5); + + ps_ref_pic_buf_lx = ps_dec->ps_ref_pic_buf_lx[0]; + ps_ref_pic_lx = ps_dec->ps_ref_pic_buf_lx[0]; + u1_max_ref_idx = ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[0]; + for(idx = 0; idx < u1_max_ref_idx; idx++) + { + ps_ref_pic_lx[idx]->u1_pic_type = TOP_FLD; + ps_ref_pic_lx[idx]->i4_poc = ps_ref_pic_lx[idx]->i4_top_field_order_cnt; + + } + u2_frm_wd_y = ps_dec->u2_frm_wd_y; + u2_frm_wd_uv = ps_dec->u2_frm_wd_uv; + + for(idx = 0; idx < u1_max_ref_idx; idx++) + { + *ps_ref_pic_lx[idx + MAX_REF_BUFS] = *ps_ref_pic_buf_lx[idx]; + ps_ref_pic_lx[idx + MAX_REF_BUFS]->pu1_buf1 = + ps_ref_pic_buf_lx[idx]->pu1_buf1 + u2_frm_wd_y; + ps_ref_pic_lx[idx + MAX_REF_BUFS]->pu1_buf2 = + ps_ref_pic_buf_lx[idx]->pu1_buf2 + u2_frm_wd_uv; + ps_ref_pic_lx[idx + MAX_REF_BUFS]->pu1_buf3 = + ps_ref_pic_buf_lx[idx]->pu1_buf3 + u2_frm_wd_uv; + + ps_ref_pic_lx[idx + MAX_REF_BUFS]->u1_pic_type = BOT_FLD; + ps_ref_pic_lx[idx + MAX_REF_BUFS]->i4_poc = + ps_ref_pic_buf_lx[idx]->i4_bottom_field_order_cnt; + if(ps_ref_pic_buf_lx[idx]->u1_picturetype & 0x3) + { + ps_ref_pic_lx[idx + MAX_REF_BUFS]->pu1_col_zero_flag = + ps_ref_pic_buf_lx[idx]->pu1_col_zero_flag + + u4_half_num_of_sub_mbs; + ps_ref_pic_lx[idx + MAX_REF_BUFS]->ps_mv = + ps_ref_pic_buf_lx[idx]->ps_mv + u4_half_num_of_sub_mbs; + } + } + + if(ps_dec->u1_B) + { + ps_ref_pic_buf_lx = ps_dec->ps_ref_pic_buf_lx[1]; + ps_ref_pic_lx = ps_dec->ps_ref_pic_buf_lx[1]; + u1_max_ref_idx = ps_dec->ps_cur_slice->u1_num_ref_idx_lx_active[1]; + for(idx = 0; idx < u1_max_ref_idx; idx++) + { + ps_ref_pic_lx[idx]->u1_pic_type = TOP_FLD; + ps_ref_pic_lx[idx]->i4_poc = ps_ref_pic_lx[idx]->i4_top_field_order_cnt; + + } + + for(idx = 0; idx < u1_max_ref_idx; idx++) + { + *ps_ref_pic_lx[idx + MAX_REF_BUFS] = *ps_ref_pic_buf_lx[idx]; + ps_ref_pic_lx[idx + MAX_REF_BUFS]->pu1_buf1 = + ps_ref_pic_buf_lx[idx]->pu1_buf1 + u2_frm_wd_y; + ps_ref_pic_lx[idx + MAX_REF_BUFS]->pu1_buf2 = + ps_ref_pic_buf_lx[idx]->pu1_buf2 + u2_frm_wd_uv; + ps_ref_pic_lx[idx + MAX_REF_BUFS]->pu1_buf3 = + ps_ref_pic_buf_lx[idx]->pu1_buf3 + u2_frm_wd_uv; + ps_ref_pic_lx[idx + MAX_REF_BUFS]->u1_pic_type = BOT_FLD; + ps_ref_pic_lx[idx + MAX_REF_BUFS]->i4_poc = + ps_ref_pic_buf_lx[idx]->i4_bottom_field_order_cnt; + + if(ps_ref_pic_buf_lx[idx]->u1_picturetype & 0x3) + { + ps_ref_pic_lx[idx + MAX_REF_BUFS]->pu1_col_zero_flag = + ps_ref_pic_buf_lx[idx]->pu1_col_zero_flag + + u4_half_num_of_sub_mbs; + ps_ref_pic_lx[idx + MAX_REF_BUFS]->ps_mv = + ps_ref_pic_buf_lx[idx]->ps_mv + + u4_half_num_of_sub_mbs; + } + } + } +} +static int poc_compare(const void *pv_pic1, const void *pv_pic2) +{ + struct pic_buffer_t *ps_pic1 = *(struct pic_buffer_t **) pv_pic1; + struct pic_buffer_t *ps_pic2 = *(struct pic_buffer_t **) pv_pic2; + if (ps_pic1->i4_poc < ps_pic2->i4_poc) + { + return -1; + } + else if (ps_pic1->i4_poc > ps_pic2->i4_poc) + { + return 1; + } + else + { + return 0; + } +} +/*! + ************************************************************************** + * \if Function name : ih264d_init_ref_idx_lx_b \endif + * + * \brief + * Initializes forward and backward refernce lists for B slice decoding. + * + * + * \return + * 0 on Success and Error code otherwise + ************************************************************************** + */ +void ih264d_init_ref_idx_lx_b(dec_struct_t *ps_dec) +{ + struct pic_buffer_t *ps_ref_pic_buf_lx; + dpb_manager_t *ps_dpb_mgr; + struct dpb_info_t *ps_next_dpb; + WORD32 i_cur_poc, i_max_st_poc, i_min_st_poc, i_ref_poc, i_temp_poc; + WORD8 i, j; + UWORD8 u1_max_lt_index, u1_min_lt_index; + UWORD32 u4_lt_index; + WORD32 i_cur_idx; + UWORD8 u1_field_pic_flag; + dec_slice_params_t *ps_cur_slice; + UWORD8 u1_L0, u1_L1; + UWORD8 u1_num_short_term_bufs; + UWORD8 u1_max_ref_idx_l0, u1_max_ref_idx_l1; + struct pic_buffer_t *aps_st_pic_bufs[2 * MAX_REF_BUFS] = {NULL}; + ps_cur_slice = ps_dec->ps_cur_slice; + u1_field_pic_flag = ps_cur_slice->u1_field_pic_flag; + u1_max_ref_idx_l0 = ps_cur_slice->u1_num_ref_idx_lx_active[0] + << u1_field_pic_flag; + u1_max_ref_idx_l1 = ps_cur_slice->u1_num_ref_idx_lx_active[1] + << u1_field_pic_flag; + + ps_dpb_mgr = ps_dec->ps_dpb_mgr; + /* Get the current POC */ + i_cur_poc = ps_dec->ps_cur_pic->i4_poc; + + /* Get MaxStPOC,MinStPOC,MaxLt,MinLt */ + i_max_st_poc = i_cur_poc; + i_min_st_poc = i_cur_poc; + u1_max_lt_index = MAX_REF_BUFS + 1; + u1_min_lt_index = MAX_REF_BUFS + 1; + /* Start from ST head */ + ps_next_dpb = ps_dpb_mgr->ps_dpb_st_head; + for(i = 0; i < ps_dpb_mgr->u1_num_st_ref_bufs; i++) + { + i_ref_poc = ps_next_dpb->ps_pic_buf->i4_poc; + if(i_ref_poc < i_cur_poc) + { + /* RefPic Buf POC is before Current POC in display order */ + i_min_st_poc = MIN(i_min_st_poc, i_ref_poc); + } + else + { + /* RefPic Buf POC is after Current POC in display order */ + i_max_st_poc = MAX(i_max_st_poc, i_ref_poc); + } + + /* Chase the next link */ + ps_next_dpb = ps_next_dpb->ps_prev_short; + } + + /* Sort ST ref pocs in ascending order */ + ps_next_dpb = ps_dpb_mgr->ps_dpb_st_head; + for (j = 0; j < ps_dpb_mgr->u1_num_st_ref_bufs; j++) + { + aps_st_pic_bufs[j] = ps_next_dpb->ps_pic_buf; + ps_next_dpb = ps_next_dpb->ps_prev_short; + } + qsort(aps_st_pic_bufs, ps_dpb_mgr->u1_num_st_ref_bufs, + sizeof(aps_st_pic_bufs[0]), poc_compare); + + /* Start from LT head */ + ps_next_dpb = ps_dpb_mgr->ps_dpb_ht_head; + if(ps_next_dpb) + { + u1_max_lt_index = ps_next_dpb->u1_lt_idx; + u1_min_lt_index = ps_next_dpb->u1_lt_idx; + } + for(i = 0; i < ps_dpb_mgr->u1_num_lt_ref_bufs; i++) + { + u4_lt_index = ps_next_dpb->u1_lt_idx; + u1_max_lt_index = (UWORD8)(MAX(u1_max_lt_index, u4_lt_index)); + u1_min_lt_index = (UWORD8)(MIN(u1_min_lt_index, u4_lt_index)); + + /* Chase the next link */ + ps_next_dpb = ps_next_dpb->ps_prev_long; + } + + /* 1. Initialize refIdxL0 */ + u1_L0 = 0; + i_temp_poc = i_cur_poc; + if(u1_field_pic_flag) + { + ps_ref_pic_buf_lx = ps_dpb_mgr->ps_init_dpb[0][0]; + ps_ref_pic_buf_lx += MAX_REF_BUFS; + } + else + { + ps_ref_pic_buf_lx = ps_dpb_mgr->ps_init_dpb[0][0]; + /* Avoid integer overflow while decrementing by one */ + if (i_temp_poc > INT32_MIN) + i_temp_poc--; + } + + i_cur_idx = -1; + for(j = 0; j < ps_dpb_mgr->u1_num_st_ref_bufs; j++) + { + if (NULL == aps_st_pic_bufs[j]) + { + break; + } + if (aps_st_pic_bufs[j]->i4_poc <= i_temp_poc) + { + i_cur_idx = j; + } + } + /* Arrange all short term buffers in output order as given by POC */ + /* 1.1 Arrange POC's less than CurrPOC in the descending POC order starting + from (CurrPOC - 1)*/ + for(j = i_cur_idx; j >= 0; j--) + { + if(aps_st_pic_bufs[j]) + { + /* Copy info in pic buffer */ + ih264d_insert_pic_in_ref_pic_listx(ps_ref_pic_buf_lx, + aps_st_pic_bufs[j]); + ps_ref_pic_buf_lx++; + u1_L0++; + } + } + + /* 1.2. Arrange POC's more than CurrPOC in the ascending POC order starting + from (CurrPOC + 1)*/ + for(j = i_cur_idx + 1; j < ps_dpb_mgr->u1_num_st_ref_bufs; j++) + { + if(aps_st_pic_bufs[j]) + { + ih264d_insert_pic_in_ref_pic_listx(ps_ref_pic_buf_lx, + aps_st_pic_bufs[j]); + ps_ref_pic_buf_lx++; + u1_L0++; + } + } + + /* 1.3 Arrange all Long term buffers in ascending order, in LongtermIndex */ + /* Start from ST head */ + + u1_num_short_term_bufs = u1_L0; + for(u4_lt_index = u1_min_lt_index; u4_lt_index <= u1_max_lt_index; u4_lt_index++) + { + ps_next_dpb = ps_dpb_mgr->ps_dpb_ht_head; + for(i = 0; i < ps_dpb_mgr->u1_num_lt_ref_bufs; i++) + { + if(ps_next_dpb->u1_lt_idx == u4_lt_index) + { + ih264d_insert_pic_in_ref_pic_listx(ps_ref_pic_buf_lx, + ps_next_dpb->ps_pic_buf); + ps_ref_pic_buf_lx->u1_long_term_pic_num = + ps_ref_pic_buf_lx->u1_long_term_frm_idx; + + ps_ref_pic_buf_lx++; + u1_L0++; + break; + } + ps_next_dpb = ps_next_dpb->ps_prev_long; + } + } + + if(u1_field_pic_flag) + { + /* Initialize the rest of the entries in the */ + /* reference list to handle of errors */ + { + UWORD8 u1_i; + pic_buffer_t ref_pic; + + ref_pic = *(ps_dpb_mgr->ps_init_dpb[0][0] + MAX_REF_BUFS); + + if(NULL == ref_pic.pu1_buf1) + { + ref_pic = *ps_dec->ps_cur_pic; + } + for(u1_i = u1_L0; u1_i < u1_max_ref_idx_l0; u1_i++) + { + *ps_ref_pic_buf_lx = ref_pic; + ps_ref_pic_buf_lx++; + } + } + ih264d_convert_frm_to_fld_list( + ps_dpb_mgr->ps_init_dpb[0][0] + MAX_REF_BUFS, &u1_L0, + ps_dec, u1_num_short_term_bufs); + + ps_ref_pic_buf_lx = ps_dpb_mgr->ps_init_dpb[0][0] + u1_L0; + } + + ps_dec->ps_cur_slice->u1_initial_list_size[0] = u1_L0; + + /* Initialize the rest of the entries in the */ + /* reference list to handle of errors */ + { + UWORD8 u1_i; + pic_buffer_t ref_pic; + + ref_pic = *(ps_dpb_mgr->ps_init_dpb[0][0]); + + if(NULL == ref_pic.pu1_buf1) + { + ref_pic = *ps_dec->ps_cur_pic; + } + for(u1_i = u1_L0; u1_i < u1_max_ref_idx_l0; u1_i++) + { + *ps_ref_pic_buf_lx = ref_pic; + ps_ref_pic_buf_lx++; + } + } + { + /* 2. Initialize refIdxL1 */ + u1_L1 = 0; + if(u1_field_pic_flag) + { + ps_ref_pic_buf_lx = ps_dpb_mgr->ps_init_dpb[1][0] + MAX_REF_BUFS; + } + else + { + ps_ref_pic_buf_lx = ps_dpb_mgr->ps_init_dpb[1][0]; + } + + /* 2.1. Arrange POC's more than CurrPOC in the ascending POC order starting + from (CurrPOC + 1)*/ + for(j = i_cur_idx + 1; j < ps_dpb_mgr->u1_num_st_ref_bufs; j++) + { + if(aps_st_pic_bufs[j]) + { + /* Start from ST head */ + ih264d_insert_pic_in_ref_pic_listx(ps_ref_pic_buf_lx, + aps_st_pic_bufs[j]); + ps_ref_pic_buf_lx++; + u1_L1++; + } + } + + /* Arrange all short term buffers in output order as given by POC */ + /* 2.2 Arrange POC's less than CurrPOC in the descending POC order starting + from (CurrPOC - 1)*/ + for(j = i_cur_idx; j >= 0; j--) + { + if(aps_st_pic_bufs[j]) + { + ih264d_insert_pic_in_ref_pic_listx(ps_ref_pic_buf_lx, + aps_st_pic_bufs[j]); + ps_ref_pic_buf_lx++; + u1_L1++; + } + } + + /* 2.3 Arrange all Long term buffers in ascending order, in LongtermIndex */ + /* Start from ST head */ + u1_num_short_term_bufs = u1_L1; + + for(u4_lt_index = u1_min_lt_index; u4_lt_index <= u1_max_lt_index; + u4_lt_index++) + { + ps_next_dpb = ps_dpb_mgr->ps_dpb_ht_head; + for(i = 0; i < ps_dpb_mgr->u1_num_lt_ref_bufs; i++) + { + if(ps_next_dpb->u1_lt_idx == u4_lt_index) + { + ih264d_insert_pic_in_ref_pic_listx(ps_ref_pic_buf_lx, + ps_next_dpb->ps_pic_buf); + ps_ref_pic_buf_lx->u1_long_term_pic_num = + ps_ref_pic_buf_lx->u1_long_term_frm_idx; + ps_ref_pic_buf_lx++; + u1_L1++; + break; + } + ps_next_dpb = ps_next_dpb->ps_prev_long; + } + } + + if(u1_field_pic_flag) + { + /* Initialize the rest of the entries in the */ + /* reference list to handle of errors */ + { + UWORD8 u1_i; + pic_buffer_t ref_pic; + + ref_pic = *(ps_dpb_mgr->ps_init_dpb[0][0] + MAX_REF_BUFS); + + if(NULL == ref_pic.pu1_buf1) + { + ref_pic = *ps_dec->ps_cur_pic; + } + for(u1_i = u1_L1; u1_i < u1_max_ref_idx_l1; u1_i++) + { + *ps_ref_pic_buf_lx = ref_pic; + ps_ref_pic_buf_lx++; + } + } + + ih264d_convert_frm_to_fld_list( + ps_dpb_mgr->ps_init_dpb[1][0] + MAX_REF_BUFS, + &u1_L1, ps_dec, u1_num_short_term_bufs); + ps_ref_pic_buf_lx = ps_dpb_mgr->ps_init_dpb[1][0] + u1_L1; + } + + ps_dec->ps_cur_slice->u1_initial_list_size[1] = u1_L1; + + /* Initialize the rest of the entries in the */ + /* reference list to handle of errors */ + { + UWORD8 u1_i; + pic_buffer_t ref_pic; + + ref_pic = *(ps_dpb_mgr->ps_init_dpb[0][0]); + + if(NULL == ref_pic.pu1_buf1) + { + ref_pic = *ps_dec->ps_cur_pic; + } + for(u1_i = u1_L1; u1_i < u1_max_ref_idx_l1; u1_i++) + { + *ps_ref_pic_buf_lx = ref_pic; + ps_ref_pic_buf_lx++; + } + } + + /* If list0 and list 1 ebtries are same then swap the 0th and 1st entry */ + /* of list 1 */ + { + struct pic_buffer_t *ps_ref_pic1_buf_l0, *ps_ref_pic1_buf_l1; + struct pic_buffer_t s_ref_pic1_buf_temp; + + ps_ref_pic1_buf_l0 = ps_dpb_mgr->ps_init_dpb[0][0]; + ps_ref_pic1_buf_l1 = ps_dpb_mgr->ps_init_dpb[1][0]; + + if((u1_L0 == u1_L1) && (u1_L0 > 1)) + { + WORD32 i_index, i_swap; + + i_swap = 1; + + for(i_index = 0; i_index < u1_L0; i_index++) + { + if((ps_ref_pic1_buf_l0[i_index]).pu1_buf1 + != (ps_ref_pic1_buf_l1[i_index]).pu1_buf1) + { + i_swap = 0; + break; + } + } + if(1 == i_swap) + { + memcpy(&s_ref_pic1_buf_temp, &ps_ref_pic1_buf_l1[1], + sizeof(struct pic_buffer_t)); + memcpy(&ps_ref_pic1_buf_l1[1], &ps_ref_pic1_buf_l1[0], + sizeof(struct pic_buffer_t)); + memcpy(&ps_ref_pic1_buf_l1[0], &s_ref_pic1_buf_temp, + sizeof(struct pic_buffer_t)); + } + } + } + } +} + + + +void ih264d_get_implicit_weights(dec_struct_t *ps_dec); + +/*! + ************************************************************************** + * \if Function name : ih264d_one_to_one \endif + * + * \brief + * Initializes forward and backward refernce lists for B slice decoding. + * + * + * \return + * 0 on Success and Error code otherwise + ************************************************************************** + */ +void ih264d_one_to_one(dec_struct_t *ps_dec, + struct pic_buffer_t *ps_col_pic, + directmv_t *ps_direct, + UWORD8 u1_wd_x, + WORD32 u2_sub_mb_ofst, + dec_mb_info_t * ps_cur_mb_info) +{ + UWORD8 *pu1_col_zero_flag_start, u1_col_mb_pred_mode, u1_num_blks, u1_sub_mb_num; + UWORD8 u1_init_colzero_flag; + UNUSED(ps_cur_mb_info); + pu1_col_zero_flag_start = ps_col_pic->pu1_col_zero_flag + u2_sub_mb_ofst; + u1_col_mb_pred_mode = pu1_col_zero_flag_start[ps_dec->u1_sub_mb_num]; + u1_init_colzero_flag = u1_col_mb_pred_mode & 1; + u1_col_mb_pred_mode >>= 6; + ps_direct->u1_vert_mv_scale = ONE_TO_ONE; + ps_direct->u1_col_zeroflag_change = 0; + + if(u1_wd_x == MB_SIZE) + { + ps_dec->u1_currB_type = (!!u1_col_mb_pred_mode); + if(u1_col_mb_pred_mode == PRED_16x16) + { + ps_direct->i1_num_partitions = 1; + ps_direct->i4_mv_indices[0] = u2_sub_mb_ofst; + ps_direct->i1_submb_num[0] = 0; + ps_direct->i1_partitionsize[0] = PRED_16x16; + + return; + } + else if(u1_col_mb_pred_mode < PRED_8x8) + { + ps_direct->i1_num_partitions = 2; + ps_direct->i4_mv_indices[0] = u2_sub_mb_ofst; + ps_direct->i1_submb_num[0] = 0; + ps_direct->i1_partitionsize[0] = u1_col_mb_pred_mode; + u1_sub_mb_num = (u1_col_mb_pred_mode == PRED_16x8) ? 8 : 2; + ps_direct->i1_submb_num[1] = u1_sub_mb_num; + ps_direct->i4_mv_indices[1] = u2_sub_mb_ofst + + ps_direct->i1_submb_num[1]; + ps_direct->i1_partitionsize[1] = u1_col_mb_pred_mode; + if((pu1_col_zero_flag_start[u1_sub_mb_num] & 1) != u1_init_colzero_flag) + ps_direct->u1_col_zeroflag_change = 1; + return; + } + else + { + u1_num_blks = 4; + } + } + else + { + u1_num_blks = 1; + } + + { + const UWORD8 *pu1_top_lt_mb_part_idx; + UWORD8 u1_col_sub_mb_pred_mode, uc_blk, u1_sub_blk, u1_submb_col = 0; + UWORD8 u1_num_sub_blks, uc_direct8x8inf, *pu1_col_zero_flag, u1_sub_mb_num; + const UWORD8 *pu1_num_sub_mb_part = + (const UWORD8 *)gau1_ih264d_num_submb_part; + UWORD8 i1_num_partitions = 0, partition_size; + WORD32 mv_index; + const UWORD8 *pu1_top_lt_sub_mb_idx = gau1_ih264d_submb_indx_mod_sp_drct; + + u1_sub_mb_num = ps_dec->u1_sub_mb_num; + uc_direct8x8inf = ps_dec->ps_cur_slice->u1_direct_8x8_inference_flag; + pu1_top_lt_mb_part_idx = gau1_ih264d_top_left_mb_part_indx_mod + + (PRED_8x8 << 1) + 1; + + for(uc_blk = 0; uc_blk < u1_num_blks; uc_blk++) + { + partition_size = PRED_8x8; + pu1_top_lt_sub_mb_idx = gau1_ih264d_submb_indx_mod_sp_drct; + if(uc_direct8x8inf == 1) + { + u1_submb_col = u1_sub_mb_num | (u1_sub_mb_num >> 1); + mv_index = u2_sub_mb_ofst + u1_submb_col; + u1_num_sub_blks = 1; + } + else + { + /* colMbPart is either 8x8, 8x4, 4x8, 4x4 */ + pu1_col_zero_flag = pu1_col_zero_flag_start + u1_sub_mb_num; + u1_col_sub_mb_pred_mode = *pu1_col_zero_flag; + u1_col_sub_mb_pred_mode = (u1_col_sub_mb_pred_mode & 0x30) >> 4; + partition_size = (UWORD8)((u1_col_sub_mb_pred_mode) + | (PRED_8x8 << 2)); + mv_index = u2_sub_mb_ofst + u1_sub_mb_num; + pu1_top_lt_sub_mb_idx += (u1_col_sub_mb_pred_mode << 1); + u1_num_sub_blks = pu1_num_sub_mb_part[u1_col_sub_mb_pred_mode]; + + } + + for(u1_sub_blk = 0; u1_sub_blk < u1_num_sub_blks; + u1_sub_blk++, pu1_top_lt_sub_mb_idx++) + { + u1_sub_mb_num += *pu1_top_lt_sub_mb_idx; + mv_index += *pu1_top_lt_sub_mb_idx; + ps_direct->i4_mv_indices[i1_num_partitions] = mv_index; + ps_direct->i1_submb_num[i1_num_partitions] = u1_sub_mb_num; + ps_direct->i1_partitionsize[i1_num_partitions] = partition_size; + i1_num_partitions++; + if(!uc_direct8x8inf) + u1_submb_col = u1_sub_mb_num; + if((pu1_col_zero_flag_start[u1_submb_col] & 1) + != u1_init_colzero_flag) + ps_direct->u1_col_zeroflag_change = 1; + } + u1_sub_mb_num = *pu1_top_lt_mb_part_idx++; + } + ps_direct->i1_num_partitions = i1_num_partitions; + } +} +/*! + ************************************************************************** + * \if Function name : ih264d_mbaff_cross_pmbair \endif + * + * \brief + * Initializes forward and backward refernce lists for B slice decoding. + * + * + * \return + * 0 on Success and Error code otherwise + ************************************************************************** + */ +void ih264d_mbaff_cross_pmbair(dec_struct_t *ps_dec, + struct pic_buffer_t *ps_col_pic, + directmv_t *ps_direct, + UWORD8 u1_wd_x, + WORD32 u2_sub_mb_ofst, + dec_mb_info_t * ps_cur_mb_info) +{ + UWORD8 *pu1_col_zero_flag_start, *pu1_col_zero_flag, u1_sub_mb_num, + uc_sub_mb_num_col; + UWORD8 *pu1_col_zero_flag_right_half; + WORD32 i4_force_8X8; + UWORD8 u1_num_blks, u1_col_mb_pred_mode, uc_blk, u1_col_sub_mb_pred_mode, + u1_col_sub_mb_pred_mode_rt; + UWORD8 i1_num_partitions = 0, partition_size; + + WORD32 mv_index; + + UWORD8 u1_num_sub_blks; + UWORD8 u1_is_cur_mb_fld, i; + UWORD8 u1_init_colzero_flag; + + u1_is_cur_mb_fld = ps_cur_mb_info->u1_mb_field_decodingflag; + u1_sub_mb_num = ps_dec->u1_sub_mb_num; + ps_direct->u1_col_zeroflag_change = 0; + /*pu1_col_zero_flag_start = ps_col_pic->pu1_col_zero_flag + u2_sub_mb_ofst; + u1_col_mb_pred_mode = pu1_col_zero_flag_start[u1_sub_mb_num]; + u1_init_colzero_flag = u1_col_mb_pred_mode & 1; + u1_col_mb_pred_mode >>= 6; */ + if(0 == u1_is_cur_mb_fld) + { + ps_direct->u1_vert_mv_scale = FLD_TO_FRM; + if(u1_wd_x == MB_SIZE) + { + pu1_col_zero_flag_start = ps_col_pic->pu1_col_zero_flag + + u2_sub_mb_ofst; + u1_col_mb_pred_mode = pu1_col_zero_flag_start[0]; + u1_init_colzero_flag = u1_col_mb_pred_mode & 1; + u1_col_mb_pred_mode >>= 6; + + + if(u1_col_mb_pred_mode & 0x2) + { + ps_dec->u1_currB_type = 1; + if(u1_col_mb_pred_mode == PRED_8x16) + { + ps_direct->i1_num_partitions = 2; + ps_direct->i4_mv_indices[0] = u2_sub_mb_ofst; + ps_direct->i1_submb_num[0] = 0; + ps_direct->i1_partitionsize[0] = PRED_8x16; + ps_direct->i4_mv_indices[1] = u2_sub_mb_ofst + 2; + ps_direct->i1_submb_num[1] = 2; + ps_direct->i1_partitionsize[1] = PRED_8x16; + if((pu1_col_zero_flag_start[2] & 1) != u1_init_colzero_flag) + ps_direct->u1_col_zeroflag_change = 1; + } + else + { + pu1_col_zero_flag = pu1_col_zero_flag_start + u1_sub_mb_num; + u1_col_sub_mb_pred_mode = (*pu1_col_zero_flag & 0x10);/* 8x4 or 4x4 mode */ + + pu1_col_zero_flag_right_half = pu1_col_zero_flag_start + + u1_sub_mb_num + 2; + u1_col_sub_mb_pred_mode_rt = + (*pu1_col_zero_flag_right_half & 0x10);/* 8x4 or 4x4 mode */ + + i4_force_8X8 = (u1_col_sub_mb_pred_mode) + || (u1_col_sub_mb_pred_mode_rt); + if(i4_force_8X8) + { + u1_num_sub_blks = 2; + partition_size = PRED_8x8; + } + else + { + partition_size = PRED_8x16; + u1_num_sub_blks = 1; + } + + for(i = 0; i < 2; i++) + { + for(uc_blk = 0; uc_blk < u1_num_sub_blks; uc_blk++) + { + uc_sub_mb_num_col = u1_sub_mb_num | (u1_sub_mb_num >> 1); + uc_sub_mb_num_col &= 0x7; + mv_index = u2_sub_mb_ofst + uc_sub_mb_num_col; + + ps_direct->i4_mv_indices[i1_num_partitions] = + mv_index; + ps_direct->i1_submb_num[i1_num_partitions] = + u1_sub_mb_num; + ps_direct->i1_partitionsize[i1_num_partitions] = + partition_size; + i1_num_partitions++; + if((pu1_col_zero_flag_start[uc_sub_mb_num_col] & 1) + != u1_init_colzero_flag) + ps_direct->u1_col_zeroflag_change = 1; + u1_sub_mb_num += 8; + } + u1_sub_mb_num = 2; /* move to second half of Cur MB */ + } + ps_direct->i1_num_partitions = i1_num_partitions; + return; + } + } + else + { + ps_direct->i1_num_partitions = 1; + ps_direct->i4_mv_indices[0] = u2_sub_mb_ofst; + ps_direct->i1_submb_num[0] = 0; + ps_direct->i1_partitionsize[0] = PRED_16x16; + ps_dec->u1_currB_type = 0; + return; + } + } + else + { + uc_sub_mb_num_col = u1_sub_mb_num | (u1_sub_mb_num >> 1); + uc_sub_mb_num_col &= 0x7; + + ps_direct->i4_mv_indices[0] = u2_sub_mb_ofst + uc_sub_mb_num_col; + ps_direct->i1_submb_num[0] = u1_sub_mb_num; + ps_direct->i1_partitionsize[0] = PRED_8x8; + ps_direct->i1_num_partitions = 1; + } + } + else + { + ps_direct->u1_vert_mv_scale = FRM_TO_FLD; + pu1_col_zero_flag_start = ps_col_pic->pu1_col_zero_flag + u2_sub_mb_ofst; + u1_init_colzero_flag = pu1_col_zero_flag_start[0] & 1; + + if(u1_wd_x == MB_SIZE) + { + UWORD8 u1_submb_col; + UWORD8 *puc_colZeroFlagStart_bot_mb, uc_colMbPredMode_bot_mb; + + pu1_col_zero_flag_start = ps_col_pic->pu1_col_zero_flag + + u2_sub_mb_ofst; + u1_col_mb_pred_mode = pu1_col_zero_flag_start[u1_sub_mb_num] >> 6; + + puc_colZeroFlagStart_bot_mb = ps_col_pic->pu1_col_zero_flag + + u2_sub_mb_ofst + 16; + uc_colMbPredMode_bot_mb = puc_colZeroFlagStart_bot_mb[8] >> 6; + + i4_force_8X8 = (u1_col_mb_pred_mode & 0x2) + || (uc_colMbPredMode_bot_mb & 0x2); + if(i4_force_8X8) + { + u1_num_blks = 2; + partition_size = PRED_8x8; + } + else + { + u1_num_blks = 1; + partition_size = PRED_16x8; + } + + ps_dec->u1_currB_type = 1; + /*As this mb is derived from 2 Mbs min no of partitions = 2*/ + for(i = 0; i < 2; i++) + { + + pu1_col_zero_flag_start = ps_col_pic->pu1_col_zero_flag + + u2_sub_mb_ofst; + u1_col_mb_pred_mode = pu1_col_zero_flag_start[u1_sub_mb_num] >> 6; + + for(uc_blk = 0; uc_blk < u1_num_blks; uc_blk++) + { + u1_submb_col = (u1_sub_mb_num & 0x7) ? 1 : 0; + u1_submb_col += u1_sub_mb_num; + mv_index = u2_sub_mb_ofst + u1_submb_col; + + + ps_direct->i4_mv_indices[i1_num_partitions] = mv_index; + ps_direct->i1_submb_num[i1_num_partitions] = u1_sub_mb_num; + ps_direct->i1_partitionsize[i1_num_partitions] = + partition_size; + i1_num_partitions++; + if((pu1_col_zero_flag_start[u1_submb_col] & 1) + != u1_init_colzero_flag) + ps_direct->u1_col_zeroflag_change = 1; + u1_sub_mb_num += 2; + } + u1_sub_mb_num = 8; /* move to second half of Cur MB */ + u2_sub_mb_ofst += 16;/* move to next Colocated MB */ + } + ps_direct->i1_num_partitions = i1_num_partitions; + return; + } + else + { + uc_sub_mb_num_col = u1_sub_mb_num | (u1_sub_mb_num >> 1); + uc_sub_mb_num_col &= 0xb; + u2_sub_mb_ofst += (u1_sub_mb_num >> 3) ? 16 : 0; + + ps_direct->i4_mv_indices[0] = u2_sub_mb_ofst + uc_sub_mb_num_col; + ps_direct->i1_submb_num[0] = u1_sub_mb_num; + ps_direct->i1_partitionsize[0] = PRED_8x8; + ps_direct->i1_num_partitions = 1; + return; + } + } +} +/*! + ************************************************************************** + * \if Function name : ih264d_cal_col_pic \endif + * + * \brief + * Finds the colocated picture. + * + * + * \return + * 0 on Success and Error code otherwise + ************************************************************************** + */ +WORD32 ih264d_cal_col_pic(dec_struct_t *ps_dec) +{ + struct pic_buffer_t* ps_col_pic = ps_dec->ps_col_pic; + UWORD8 uc_curpictype, uc_colpictype; + ps_col_pic = ps_dec->ps_ref_pic_buf_lx[1][0]; + uc_curpictype = (ps_dec->ps_cur_pic->u1_picturetype & 0x7); + uc_colpictype = (ps_col_pic->u1_picturetype & 0x7); + if(uc_curpictype == FRM_PIC) + { + if(uc_colpictype == FRM_PIC) + ps_dec->pf_parse_mvdirect = ih264d_one_to_one; + else if(uc_colpictype == COMP_FLD_PAIR) + { + ps_dec->pf_parse_mvdirect = ih264d_fld_to_frm; + if(ps_col_pic->i4_top_field_order_cnt + >= ps_col_pic->i4_bottom_field_order_cnt) + { + struct pic_buffer_t* ps_tempPic = ps_col_pic; + UWORD32 ui_half_num_of_sub_mbs = ((ps_dec->u2_pic_ht + * ps_dec->u2_pic_wd) >> 5); + ps_col_pic = ps_dec->ps_ref_pic_buf_lx[1][MAX_REF_BUFS]; + /* memcpy ps_tempPic to ps_col_pic */ + *ps_col_pic = *ps_tempPic; + ps_col_pic->pu1_buf1 = ps_tempPic->pu1_buf1 + + ps_tempPic->u2_frm_wd_y; + ps_col_pic->pu1_buf2 = ps_tempPic->pu1_buf2 + + ps_tempPic->u2_frm_wd_uv; + ps_col_pic->pu1_buf3 = ps_tempPic->pu1_buf3 + + ps_tempPic->u2_frm_wd_uv; + ps_col_pic->pu1_col_zero_flag = ps_tempPic->pu1_col_zero_flag + + ui_half_num_of_sub_mbs; + ps_col_pic->ps_mv = ps_tempPic->ps_mv + ui_half_num_of_sub_mbs; + + + ps_col_pic->u1_pic_type = 0;/*complementary reference field pair-refering as frame */ + + + + } + } + else + { + UWORD32 i4_error_code; + i4_error_code = ERROR_DBP_MANAGER_T; +// i4_error_code |= 1<<IVD_CORRUPTEDDATA; + return i4_error_code; + } + } + else if(uc_curpictype == AFRM_PIC) + { + ps_dec->pf_parse_mvdirect = ih264d_fld_to_mbaff; + } + else /* must be a field*/ + { + if(uc_colpictype == FRM_PIC) + ps_dec->pf_parse_mvdirect = ih264d_frm_to_fld; + else if(uc_colpictype == AFRM_PIC) + ps_dec->pf_parse_mvdirect = ih264d_mbaff_to_fld; + else + ps_dec->pf_parse_mvdirect = ih264d_one_to_one; + } + ps_dec->ps_col_pic = ps_col_pic; + return OK; +} + +/*! + ************************************************************************** + * \if Function name : ih264d_frm_to_fld \endif + * + * \brief + * Initializes forward and backward refernce lists for B slice decoding. + * + * + * \return + * 0 on Success and Error code otherwise + ************************************************************************** + */ +void ih264d_frm_to_fld(dec_struct_t *ps_dec, + struct pic_buffer_t *ps_col_pic, + directmv_t *ps_direct, + UWORD8 u1_wd_x, + WORD32 u2_sub_mb_ofst, + dec_mb_info_t * ps_cur_mb_info) +{ + UWORD8 *pu1_col_zero_flag_start, u1_sub_mb_num; + UWORD8 u1_num_blks, u1_col_mb_pred_mode, uc_blk; + UWORD8 i1_num_partitions = 0, partition_size, i; + WORD32 mv_index; + UWORD32 increment; + WORD32 i4_force_8X8; + UNUSED(ps_cur_mb_info); + ps_direct->u1_col_zeroflag_change = 1; + ps_direct->u1_vert_mv_scale = FRM_TO_FLD; + u1_sub_mb_num = ps_dec->u1_sub_mb_num; + + /* new calculation specific to this function */ + if((ps_col_pic->u1_picturetype & 0x7) == FRM_PIC) + { + UWORD16 u2_frm_wd_in_mbs = ps_dec->u2_frm_wd_in_mbs; + increment = (u2_frm_wd_in_mbs << 4); + /*mbAddrCol = mbAddrCol1 */ + u2_sub_mb_ofst = (ps_dec->u2_mbx + + (2 * ps_dec->u2_mby * u2_frm_wd_in_mbs)) << 4; + } + else + increment = 16; + + if(u1_wd_x == MB_SIZE) + { + ps_dec->u1_currB_type = 1; + + { + UWORD8 *puc_colZeroFlagStart_bot_mb, uc_colMbPredMode_bot_mb; + + pu1_col_zero_flag_start = ps_col_pic->pu1_col_zero_flag + + u2_sub_mb_ofst; + u1_col_mb_pred_mode = (*pu1_col_zero_flag_start >> 6); + + puc_colZeroFlagStart_bot_mb = ps_col_pic->pu1_col_zero_flag + + u2_sub_mb_ofst + increment; + uc_colMbPredMode_bot_mb = (*puc_colZeroFlagStart_bot_mb >> 6); + + i4_force_8X8 = (u1_col_mb_pred_mode & 0x2) + || (uc_colMbPredMode_bot_mb & 0x2); + + if(i4_force_8X8) + { + u1_num_blks = 2; + partition_size = PRED_8x8; + } + else + { + partition_size = PRED_16x8; + u1_num_blks = 1; + } + } + + /*As this mb is derived from 2 Mbs, min no of partitions = 2*/ + for(i = 0; i < 2; i++) + { + for(uc_blk = 0; uc_blk < u1_num_blks; uc_blk++) + { + mv_index = u2_sub_mb_ofst + u1_sub_mb_num; + mv_index += (u1_sub_mb_num & 0x7) ? 1 : 0; + + ps_direct->i4_mv_indices[i1_num_partitions] = mv_index; + ps_direct->i1_submb_num[i1_num_partitions] = u1_sub_mb_num; + ps_direct->i1_partitionsize[i1_num_partitions] = partition_size; + i1_num_partitions++; + + u1_sub_mb_num += 2; + } + u1_sub_mb_num = 8; /* move to second half of Cur MB */ + u2_sub_mb_ofst += increment;/* move to next Colocated MB */ + } + ps_direct->i1_num_partitions = i1_num_partitions; + return; + } + else + { + UWORD8 u1_sub_mb_num_col; + u1_sub_mb_num_col = u1_sub_mb_num | (u1_sub_mb_num >> 1); + u1_sub_mb_num_col &= 0xb; + u2_sub_mb_ofst += (u1_sub_mb_num >> 3) ? increment : 0; + + ps_direct->i4_mv_indices[0] = u2_sub_mb_ofst + u1_sub_mb_num_col; + ps_direct->i1_submb_num[0] = u1_sub_mb_num; + ps_direct->i1_partitionsize[0] = PRED_8x8; + ps_direct->i1_num_partitions = 1; + return; + } +} +/*! + ************************************************************************** + * \if Function name : ih264d_fld_to_frm \endif + * + * \brief + * Initializes forward and backward refernce lists for B slice decoding. + * + * + * \return + * 0 on Success and Error code otherwise + ************************************************************************** + */ +void ih264d_fld_to_frm(dec_struct_t *ps_dec, + struct pic_buffer_t *ps_col_pic, + directmv_t *ps_direct, + UWORD8 u1_wd_x, + WORD32 u2_sub_mb_ofst, + dec_mb_info_t * ps_cur_mb_info) +{ + UWORD8 *pu1_col_zero_flag_start, *pu1_col_zero_flag, + *pu1_col_zero_flag_right_half, u1_sub_mb_num, uc_sub_mb_num_col; + UWORD8 u1_col_mb_pred_mode, uc_blk; + WORD32 i4_force_8X8; + + UNUSED(ps_cur_mb_info); + ps_direct->u1_vert_mv_scale = FLD_TO_FRM; + ps_direct->u1_col_zeroflag_change = 1; + /* new calculation specific to this function for u2_sub_mb_ofst*/ + u2_sub_mb_ofst = (ps_dec->u2_mbx + + ((ps_dec->u2_mby >> 1) * ps_dec->u2_frm_wd_in_mbs)) << 4; + u2_sub_mb_ofst += ((ps_dec->u2_mby & 1) << 3); + + if(u1_wd_x == MB_SIZE) + { + pu1_col_zero_flag_start = ps_col_pic->pu1_col_zero_flag + u2_sub_mb_ofst; + u1_col_mb_pred_mode = (*pu1_col_zero_flag_start >> 6); + ps_dec->u1_currB_type = (!!u1_col_mb_pred_mode); + + if(u1_col_mb_pred_mode & 0x2) + { + if(u1_col_mb_pred_mode == PRED_8x16) + { + ps_direct->i1_num_partitions = 2; + ps_direct->i4_mv_indices[0] = u2_sub_mb_ofst; + ps_direct->i1_submb_num[0] = 0; + ps_direct->i1_partitionsize[0] = PRED_8x16; + ps_direct->i4_mv_indices[1] = u2_sub_mb_ofst + 2; + ps_direct->i1_submb_num[1] = 2; + ps_direct->i1_partitionsize[1] = PRED_8x16; + } + else + { + UWORD8 i1_num_partitions = 0, partition_size; + UWORD32 mv_index; + UWORD8 u1_num_sub_blks, i, u1_col_sub_mb_pred_mode, + u1_col_sub_mb_pred_mode_rt; + + u1_sub_mb_num = ps_dec->u1_sub_mb_num; + + pu1_col_zero_flag = pu1_col_zero_flag_start + u1_sub_mb_num; + u1_col_sub_mb_pred_mode = (*pu1_col_zero_flag & 0x10);/* 8x4 or 4x4 mode */ + + pu1_col_zero_flag_right_half = pu1_col_zero_flag_start + u1_sub_mb_num + + 2; + u1_col_sub_mb_pred_mode_rt = (*pu1_col_zero_flag_right_half + & 0x10);/* 8x4 or 4x4 mode */ + + i4_force_8X8 = (u1_col_sub_mb_pred_mode) + || (u1_col_sub_mb_pred_mode_rt); + if(i4_force_8X8) + { + u1_num_sub_blks = 2; + partition_size = PRED_8x8; + } + else + { + partition_size = PRED_8x16; + u1_num_sub_blks = 1; + } + + for(i = 0; i < 2; i++) + { + for(uc_blk = 0; uc_blk < u1_num_sub_blks; uc_blk++) + { + uc_sub_mb_num_col = u1_sub_mb_num | (u1_sub_mb_num >> 1); + uc_sub_mb_num_col &= 0x7; + mv_index = u2_sub_mb_ofst + uc_sub_mb_num_col; + + ps_direct->i4_mv_indices[i1_num_partitions] = mv_index; + ps_direct->i1_submb_num[i1_num_partitions] = + u1_sub_mb_num; + ps_direct->i1_partitionsize[i1_num_partitions] = + partition_size; + i1_num_partitions++; + u1_sub_mb_num += 8; + } + + u1_sub_mb_num = 2; /* move to second half of Cur MB */ + + } + ps_direct->i1_num_partitions = i1_num_partitions; + return; + } + } + else + { + ps_direct->i1_num_partitions = 1; + ps_direct->i4_mv_indices[0] = u2_sub_mb_ofst; + ps_direct->i1_submb_num[0] = 0; + ps_direct->i1_partitionsize[0] = PRED_16x16; + return; + } + } + else + { + u1_sub_mb_num = ps_dec->u1_sub_mb_num; + uc_sub_mb_num_col = u1_sub_mb_num | (u1_sub_mb_num >> 1); + uc_sub_mb_num_col &= 0x7; + + ps_direct->i4_mv_indices[0] = u2_sub_mb_ofst + uc_sub_mb_num_col; + ps_direct->i1_submb_num[0] = u1_sub_mb_num; + ps_direct->i1_partitionsize[0] = PRED_8x8; + ps_direct->i1_num_partitions = 1; + } +} +/*! + ************************************************************************** + * \if Function name : ih264d_one_to_one \endif + * + * \brief + * Initializes forward and backward refernce lists for B slice decoding. + * + * + * \return + * 0 on Success and Error code otherwise + ************************************************************************** + */ +void ih264d_mbaff_to_fld(dec_struct_t *ps_dec, + struct pic_buffer_t *ps_col_pic, + directmv_t *ps_direct, + UWORD8 u1_wd_x, + WORD32 u2_sub_mb_ofst, + dec_mb_info_t * ps_cur_mb_info) +{ + UWORD8* pu1_col_zero_flag, u1_iscol_mb_fld; + u2_sub_mb_ofst <<= 1; + pu1_col_zero_flag = ps_col_pic->pu1_col_zero_flag + u2_sub_mb_ofst; + u1_iscol_mb_fld = (*pu1_col_zero_flag & 0x2) >> 1; + if(u1_iscol_mb_fld) + { + u2_sub_mb_ofst += (ps_dec->ps_cur_slice->u1_bottom_field_flag << 4); + ih264d_one_to_one(ps_dec, ps_col_pic, ps_direct, u1_wd_x, + u2_sub_mb_ofst, ps_cur_mb_info); + } + else + ih264d_frm_to_fld(ps_dec, ps_col_pic, ps_direct, u1_wd_x, + u2_sub_mb_ofst, ps_cur_mb_info); +} +/*! + ************************************************************************** + * \if Function name : ih264d_one_to_one \endif + * + * \brief + * Initializes forward and backward refernce lists for B slice decoding. + * + * + * \return + * 0 on Success and Error code otherwise + ************************************************************************** + */ +void ih264d_fld_to_mbaff(dec_struct_t *ps_dec, + struct pic_buffer_t *ps_col_pic, + directmv_t *ps_direct, + UWORD8 u1_wd_x, + WORD32 u2_sub_mb_ofst, + dec_mb_info_t * ps_cur_mb_info) +{ + if((ps_col_pic->u1_picturetype & 0x7) == COMP_FLD_PAIR) + { + /* first calculate the colocated picture which varies with Mb */ + UWORD8 u1_is_cur_mb_fld; + u1_is_cur_mb_fld = ps_cur_mb_info->u1_mb_field_decodingflag; + u2_sub_mb_ofst = (u2_sub_mb_ofst & 0xffe0); /* mbaddrCol5 = curmbaddr/2;*/ + u2_sub_mb_ofst >>= 1; + + ps_col_pic = ps_dec->ps_ref_pic_buf_lx[1][0]; + if(u1_is_cur_mb_fld) + { + if(1 - ps_cur_mb_info->u1_topmb) + ps_col_pic = ps_dec->ps_ref_pic_buf_lx[1][MAX_REF_BUFS]; + + ih264d_one_to_one(ps_dec, ps_col_pic, ps_direct, u1_wd_x, + u2_sub_mb_ofst, ps_cur_mb_info); + } + else + { + + if(ABS(ps_col_pic->i4_top_field_order_cnt + - (WORD64)ps_dec->ps_cur_pic->i4_poc) >= + ABS((WORD64)ps_dec->ps_cur_pic->i4_poc + - ps_col_pic->i4_bottom_field_order_cnt)) + { + ps_col_pic = ps_dec->ps_ref_pic_buf_lx[1][MAX_REF_BUFS]; + } + + if(ps_cur_mb_info->u1_topmb == 0) + u2_sub_mb_ofst += 8; + ih264d_mbaff_cross_pmbair(ps_dec, ps_col_pic, ps_direct, u1_wd_x, + u2_sub_mb_ofst, ps_cur_mb_info); + } + ps_dec->ps_col_pic = ps_col_pic; + } + else + { + UWORD8* pu1_col_zero_flag = ps_col_pic->pu1_col_zero_flag + + u2_sub_mb_ofst; + UWORD8 temp, u1_is_cur_mb_fld, u1_iscol_mb_fld; + + u1_iscol_mb_fld = (*pu1_col_zero_flag & 0x2) >> 1; + u1_is_cur_mb_fld = ps_cur_mb_info->u1_mb_field_decodingflag; + temp = (u1_iscol_mb_fld ^ u1_is_cur_mb_fld); + + if(temp == 0) + ih264d_one_to_one(ps_dec, ps_col_pic, ps_direct, u1_wd_x, + u2_sub_mb_ofst, ps_cur_mb_info); + else + { + u2_sub_mb_ofst &= 0xffef; + if(u1_is_cur_mb_fld == 0) + { + if(ABS(ps_col_pic->i4_top_field_order_cnt + - (WORD64)ps_dec->ps_cur_pic->i4_poc) >= + ABS((WORD64)ps_dec->ps_cur_pic->i4_poc + - ps_col_pic->i4_bottom_field_order_cnt)) + { + u2_sub_mb_ofst += 0x10; + } + if(ps_cur_mb_info->u1_topmb == 0) + u2_sub_mb_ofst += 8; + } + ih264d_mbaff_cross_pmbair(ps_dec, ps_col_pic, ps_direct, u1_wd_x, + u2_sub_mb_ofst, ps_cur_mb_info); + } + } +} + diff --git a/dependencies/ih264d/decoder/ih264d_process_bslice.h b/dependencies/ih264d/decoder/ih264d_process_bslice.h new file mode 100644 index 00000000..5aa76e34 --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_process_bslice.h @@ -0,0 +1,108 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +#ifndef _IH264D_PARSE_BSLICE_H_ +#define _IH264D_PARSE_BSLICE_H_ +/*! +************************************************************************** +* \file ih264d_process_bslice.h +* +* \brief +* Contains declarations of routines that decode a B slice type +* +* Detailed_description +* +* \date +* 21/12/2002 +* +* \author NS +************************************************************************** +*/ +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264d_structs.h" +WORD32 ih264d_parse_bslice(dec_struct_t * ps_dec, + UWORD16 u2_first_mb_in_slice); +WORD32 ih264d_decode_spatial_direct(dec_struct_t * ps_dec, + UWORD8 u1_wd_x, + dec_mb_info_t * ps_cur_mb_info, + UWORD8 u1_mb_num); +WORD32 ih264d_decode_temporal_direct(dec_struct_t * ps_dec, + UWORD8 u1_wd_x, + dec_mb_info_t * ps_cur_mb_info, + UWORD8 u1_mb_num); +WORD32 parseBSliceData(dec_struct_t * ps_dec, + dec_slice_params_t * ps_slice, + UWORD16 u2_first_mb_in_slice); +WORD32 parseBSliceData(dec_struct_t * ps_dec, + dec_slice_params_t * ps_slice, + UWORD16 u2_first_mb_in_slice); + +void ih264d_init_ref_idx_lx_b(dec_struct_t *ps_dec); + +void ih264d_convert_frm_to_fld_list(struct pic_buffer_t *ps_ref_pic_buf_lx, + UWORD8 *pu1_L0, + dec_struct_t *ps_dec, + UWORD8 u1_num_short_term_bufs); + +void ih264d_convert_frm_mbaff_list(dec_struct_t *ps_dec); +void ih264d_one_to_one(dec_struct_t *ps_dec, + struct pic_buffer_t *ps_col_pic, + directmv_t *ps_direct, + UWORD8 u1_wd_x, + WORD32 u2_sub_mb_ofst, + dec_mb_info_t * ps_cur_mb_info); +void ih264d_mbaff_cross_pmbair(dec_struct_t *ps_dec, + struct pic_buffer_t *ps_col_pic, + directmv_t *ps_direct, + UWORD8 u1_wd_x, + WORD32 u2_sub_mb_ofst, + dec_mb_info_t * ps_cur_mb_info); +void ih264d_frm_to_fld(dec_struct_t *ps_dec, + struct pic_buffer_t *ps_col_pic, + directmv_t *ps_direct, + UWORD8 u1_wd_x, + WORD32 u2_sub_mb_ofst, + dec_mb_info_t * ps_cur_mb_info); +void ih264d_fld_to_frm(dec_struct_t *ps_dec, + struct pic_buffer_t *ps_col_pic, + directmv_t *ps_direct, + UWORD8 u1_wd_x, + WORD32 u2_sub_mb_ofst, + dec_mb_info_t * ps_cur_mb_info); +void ih264d_mbaff_to_fld(dec_struct_t *ps_dec, + struct pic_buffer_t *ps_col_pic, + directmv_t *ps_direct, + UWORD8 u1_wd_x, + WORD32 u2_sub_mb_ofst, + dec_mb_info_t * ps_cur_mb_info); +void ih264d_fld_to_mbaff(dec_struct_t *ps_dec, + struct pic_buffer_t *ps_col_pic, + directmv_t *ps_direct, + UWORD8 u1_wd_x, + WORD32 u2_sub_mb_ofst, + dec_mb_info_t * ps_cur_mb_info); +WORD32 ih264d_cal_col_pic(dec_struct_t *ps_dec); + +WORD32 ih264d_mv_pred_ref_tfr_nby2_bmb(dec_struct_t * ps_dec, + UWORD8 u1_num_mbs, + UWORD8 u1_num_mbsNby2); + +#endif /* _IH264D_PARSE_BSLICE_H_ */ diff --git a/dependencies/ih264d/decoder/ih264d_process_intra_mb.c b/dependencies/ih264d/decoder/ih264d_process_intra_mb.c new file mode 100644 index 00000000..2d4d006f --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_process_intra_mb.c @@ -0,0 +1,2022 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/*! + ************************************************************************** + * \file ih264d_process_intra_mb.c + * + * \brief + * Contains routines that decode a I slice type + * + * Detailed_description + * + * \date + * 07/07/2003 + * + * \author NS + ************************************************************************** + */ + +#include <string.h> +#include "ih264d_bitstrm.h" +#include "ih264d_defs.h" +#include "ih264d_debug.h" +#include "ih264d_tables.h" +#include "ih264d_structs.h" +#include "ih264d_defs.h" +#include "ih264d_parse_cavlc.h" +#include "ih264d_mb_utils.h" +#include "ih264d_parse_slice.h" +#include "ih264d_process_intra_mb.h" +#include "ih264d_error_handler.h" +#include "ih264d_quant_scaling.h" +#include "ih264d_tables.h" + +/*! + ************************************************************************** + * \if Function name : ih264d_itrans_recon_luma_dc \endif + * + * \brief + * This function does InvTransform, scaling and reconstruction of Luma DC. + * + * \return + * 0 on Success and Error code otherwise + ************************************************************************** + */ +void ih264d_itrans_recon_luma_dc(dec_struct_t *ps_dec, + WORD16* pi2_src, + WORD16* pi2_coeff_block, + const UWORD16 *pu2_weigh_mat) +{ + WORD32 i; + WORD16 pi2_out[16]; + WORD32 pi4_tmp[16]; + WORD16 *pi2_out_ptr = &pi2_out[0]; + PROFILE_DISABLE_IQ_IT_RECON_RETURN() + ps_dec->pf_ihadamard_scaling_4x4(pi2_src, pi2_out, + ps_dec->pu2_quant_scale_y, pu2_weigh_mat, + ps_dec->u1_qp_y_div6, pi4_tmp); + for(i = 0; i < 4; i++) + { + pi2_coeff_block[0] = pi2_out_ptr[0]; + pi2_coeff_block[4 * 16] = pi2_out_ptr[4]; + pi2_coeff_block[8 * 16] = pi2_out_ptr[8]; + pi2_coeff_block[12 * 16] = pi2_out_ptr[12]; + + pi2_out_ptr++; /* Point to next column */ + pi2_coeff_block += 16; + } +} +/*! + ************************************************************************** + * \if Function name : ih264d_read_intra_pred_modes \endif + * + * \brief + * Reads the intra pred mode related values of I4x4 MB from bitstream. + * + * This function will read the prev intra pred mode flags and + * stores it in pu1_prev_intra4x4_pred_mode_flag. If the u4_flag + * indicates that most probable mode is not intra pred mode, then + * the rem_intra4x4_pred_mode is read and stored in + * pu1_rem_intra4x4_pred_mode array. + * + * + * \return + * 0 on success and Error code otherwise + * + ************************************************************************** + */ +WORD32 ih264d_read_intra_pred_modes(dec_struct_t * ps_dec, + UWORD8 * pu1_prev_intra4x4_pred_mode_flag, + UWORD8 * pu1_rem_intra4x4_pred_mode, + UWORD32 u4_trans_form8x8) +{ + WORD32 i4x4_luma_blk_idx = 0, i8x8_luma_blk_idx = 0; + + dec_bit_stream_t * ps_bitstrm = ps_dec->ps_bitstrm; + + if(!u4_trans_form8x8) + { + for(i4x4_luma_blk_idx = 0; i4x4_luma_blk_idx < 16; ++i4x4_luma_blk_idx) + { + UWORD32 u4_temp; + SWITCHOFFTRACE; + + GETBIT(u4_temp, ps_bitstrm->u4_ofst, ps_bitstrm->pu4_buffer); + *pu1_prev_intra4x4_pred_mode_flag = (UWORD8)u4_temp; + if(!(*pu1_prev_intra4x4_pred_mode_flag)) + { + GETBITS(u4_temp, ps_bitstrm->u4_ofst, ps_bitstrm->pu4_buffer, 3); + + *(pu1_rem_intra4x4_pred_mode) = (UWORD8)u4_temp; + } + + pu1_prev_intra4x4_pred_mode_flag++; + pu1_rem_intra4x4_pred_mode++; + } + } + else + { + /**********************************************************************/ + /* prev_intra4x4_pred_modes to be interpreted as */ + /* prev_intra8x8_pred_modes in case of transform 8x8 */ + /**********************************************************************/ + for(i8x8_luma_blk_idx = 0; i8x8_luma_blk_idx < 4; i8x8_luma_blk_idx++) + { + UWORD32 u4_temp; + GETBIT(u4_temp, ps_bitstrm->u4_ofst, ps_bitstrm->pu4_buffer); + *pu1_prev_intra4x4_pred_mode_flag = (UWORD8)u4_temp; + if(!(*pu1_prev_intra4x4_pred_mode_flag)) + { + GETBITS(u4_temp, ps_bitstrm->u4_ofst, ps_bitstrm->pu4_buffer, 3); + + (*pu1_rem_intra4x4_pred_mode) = (UWORD8)u4_temp; + } + pu1_prev_intra4x4_pred_mode_flag++; + pu1_rem_intra4x4_pred_mode++; + } + } + return (0); +} +WORD32 ih264d_unpack_coeff4x4_4x4blk(dec_struct_t * ps_dec, + WORD16 *pi2_out_coeff_data, + UWORD8 *pu1_inv_scan) +{ + tu_sblk4x4_coeff_data_t *ps_tu_4x4 = (tu_sblk4x4_coeff_data_t *)ps_dec->pv_proc_tu_coeff_data; + UWORD16 u2_sig_coeff_map = ps_tu_4x4->u2_sig_coeff_map; + WORD32 idx = 0; + WORD16 *pi2_coeff_data = &ps_tu_4x4->ai2_level[0]; + WORD32 dc_only_flag = 0; + WORD32 num_coeff = 0; + + PROFILE_DISABLE_UNPACK_LUMA() + while(u2_sig_coeff_map) + { + idx = CLZ(u2_sig_coeff_map); + + idx = 31 - idx; + RESET_BIT(u2_sig_coeff_map,idx); + + idx = pu1_inv_scan[idx]; + pi2_out_coeff_data[idx] = *pi2_coeff_data++; + num_coeff++; + } + + if((num_coeff == 1) && (idx == 0)) + { + dc_only_flag = 1; + } + + { + WORD32 offset; + offset = (UWORD8 *)pi2_coeff_data - (UWORD8 *)ps_tu_4x4; + offset = ALIGN4(offset); + ps_dec->pv_proc_tu_coeff_data = (void *)((UWORD8 *)ps_dec->pv_proc_tu_coeff_data + offset); + } + + return dc_only_flag; +} + +UWORD32 ih264d_unpack_coeff4x4_8x8blk(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info, + UWORD16 ui2_luma_csbp, + WORD16 *pi2_out_coeff_data) +{ + UWORD8 *pu1_inv_scan; + UWORD8 u1_mb_field_decoding_flag = ps_cur_mb_info->u1_mb_field_decodingflag; + UWORD8 u1_field_coding_flag = ps_cur_mb_info->ps_curmb->u1_mb_fld; + UWORD32 u4_luma_dc_only_csbp = 0; + WORD32 dc_only_flag = 0; + + PROFILE_DISABLE_UNPACK_LUMA() + if(u1_field_coding_flag || u1_mb_field_decoding_flag) + { + pu1_inv_scan = (UWORD8 *)gau1_ih264d_inv_scan_fld; + } + else + { + pu1_inv_scan = (UWORD8 *)gau1_ih264d_inv_scan; + } + + // sub 0 + if(ui2_luma_csbp & 0x1) + { + memset(pi2_out_coeff_data,0,16*sizeof(WORD16)); + dc_only_flag = ih264d_unpack_coeff4x4_4x4blk(ps_dec, + pi2_out_coeff_data, + pu1_inv_scan); + + INSERT_BIT(u4_luma_dc_only_csbp, 0, dc_only_flag); + } + + pi2_out_coeff_data += 16; + // sub 1 + if(ui2_luma_csbp & 0x2) + { + memset(pi2_out_coeff_data,0,16*sizeof(WORD16)); + dc_only_flag = ih264d_unpack_coeff4x4_4x4blk(ps_dec, + pi2_out_coeff_data, + pu1_inv_scan); + INSERT_BIT(u4_luma_dc_only_csbp, 1, dc_only_flag); + } + + pi2_out_coeff_data += 16 + 32; + // sub 2 + if(ui2_luma_csbp & 0x10) + { + memset(pi2_out_coeff_data,0,16*sizeof(WORD16)); + dc_only_flag = ih264d_unpack_coeff4x4_4x4blk(ps_dec, + pi2_out_coeff_data, + pu1_inv_scan); + INSERT_BIT(u4_luma_dc_only_csbp, 4, dc_only_flag); + } + + pi2_out_coeff_data += 16; + // sub 3 + if(ui2_luma_csbp & 0x20) + { + memset(pi2_out_coeff_data,0,16*sizeof(WORD16)); + dc_only_flag = ih264d_unpack_coeff4x4_4x4blk(ps_dec, + pi2_out_coeff_data, + pu1_inv_scan); + INSERT_BIT(u4_luma_dc_only_csbp, 5, dc_only_flag); + } + return u4_luma_dc_only_csbp; +} +WORD32 ih264d_unpack_coeff8x8_8x8blk_cavlc(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info, + UWORD16 ui2_luma_csbp, + WORD16 *pi2_out_coeff_data) +{ + UWORD8 *pu1_inv_scan; + UWORD8 u1_mb_field_decoding_flag = ps_cur_mb_info->u1_mb_field_decodingflag; + UWORD8 u1_field_coding_flag = ps_cur_mb_info->ps_curmb->u1_mb_fld; + WORD32 dc_only_flag = 0; + + PROFILE_DISABLE_UNPACK_LUMA() + if(ui2_luma_csbp & 0x33) + { + memset(pi2_out_coeff_data,0,64*sizeof(WORD16)); + } + + if(!u1_mb_field_decoding_flag) + { + pu1_inv_scan = + (UWORD8*)gau1_ih264d_inv_scan_prog8x8_cavlc[0]; + } + else + { + pu1_inv_scan = + (UWORD8*)gau1_ih264d_inv_scan_int8x8_cavlc[0]; + } + // sub 0 + if(ui2_luma_csbp & 0x1) + { + dc_only_flag = ih264d_unpack_coeff4x4_4x4blk(ps_dec, + pi2_out_coeff_data, + pu1_inv_scan); + } + + if(!u1_mb_field_decoding_flag) + { + pu1_inv_scan = + (UWORD8*)gau1_ih264d_inv_scan_prog8x8_cavlc[1]; + } + else + { + pu1_inv_scan = + (UWORD8*)gau1_ih264d_inv_scan_int8x8_cavlc[1]; + } + // sub 1 + if(ui2_luma_csbp & 0x2) + { + dc_only_flag = 0; + ih264d_unpack_coeff4x4_4x4blk(ps_dec, + pi2_out_coeff_data, + pu1_inv_scan); + } + + if(!u1_mb_field_decoding_flag) + { + pu1_inv_scan = + (UWORD8*)gau1_ih264d_inv_scan_prog8x8_cavlc[2]; + } + else + { + pu1_inv_scan = + (UWORD8*)gau1_ih264d_inv_scan_int8x8_cavlc[2]; + } + // sub 2 + if(ui2_luma_csbp & 0x10) + { + dc_only_flag = 0; + ih264d_unpack_coeff4x4_4x4blk(ps_dec, + pi2_out_coeff_data, + pu1_inv_scan); + } + + if(!u1_mb_field_decoding_flag) + { + pu1_inv_scan = + (UWORD8*)gau1_ih264d_inv_scan_prog8x8_cavlc[3]; + } + else + { + pu1_inv_scan = + (UWORD8*)gau1_ih264d_inv_scan_int8x8_cavlc[3]; + } + // sub 3 + if(ui2_luma_csbp & 0x20) + { + dc_only_flag = 0; + ih264d_unpack_coeff4x4_4x4blk(ps_dec, + pi2_out_coeff_data, + pu1_inv_scan); + } + return dc_only_flag; +} +void ih264d_unpack_coeff4x4_8x8blk_chroma(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info, + UWORD16 ui2_chroma_csbp, + WORD16 *pi2_out_coeff_data) +{ + UWORD8 *pu1_inv_scan; + UWORD8 u1_mb_field_decoding_flag = ps_cur_mb_info->u1_mb_field_decodingflag; + UWORD8 u1_field_coding_flag = ps_cur_mb_info->ps_curmb->u1_mb_fld; + + PROFILE_DISABLE_UNPACK_CHROMA() + if(u1_field_coding_flag || u1_mb_field_decoding_flag) + { + pu1_inv_scan = (UWORD8 *)gau1_ih264d_inv_scan_fld; + } + else + { + pu1_inv_scan = (UWORD8 *)gau1_ih264d_inv_scan; + } + + if(ui2_chroma_csbp & 0x1) + { + memset(pi2_out_coeff_data,0,16*sizeof(WORD16)); + ih264d_unpack_coeff4x4_4x4blk(ps_dec, + pi2_out_coeff_data, + pu1_inv_scan); + } + pi2_out_coeff_data += 16; + if(ui2_chroma_csbp & 0x2) + { + memset(pi2_out_coeff_data,0,16*sizeof(WORD16)); + ih264d_unpack_coeff4x4_4x4blk(ps_dec, + pi2_out_coeff_data, + pu1_inv_scan); + } + + pi2_out_coeff_data += 16; + if(ui2_chroma_csbp & 0x4) + { + memset(pi2_out_coeff_data,0,16*sizeof(WORD16)); + ih264d_unpack_coeff4x4_4x4blk(ps_dec, + pi2_out_coeff_data, + pu1_inv_scan); + } + + pi2_out_coeff_data += 16; + if(ui2_chroma_csbp & 0x8) + { + memset(pi2_out_coeff_data,0,16*sizeof(WORD16)); + ih264d_unpack_coeff4x4_4x4blk(ps_dec, + pi2_out_coeff_data, + pu1_inv_scan); + } +} +UWORD32 ih264d_unpack_luma_coeff4x4_mb(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info, + UWORD8 intra_flag) +{ + UWORD8 u1_mb_type = ps_cur_mb_info->u1_mb_type; + UWORD16 ui2_luma_csbp = ps_cur_mb_info->u2_luma_csbp; + UWORD8 *pu1_inv_scan = ps_dec->pu1_inv_scan; + WORD16 *pi2_coeff_data = ps_dec->pi2_coeff_data; + + PROFILE_DISABLE_UNPACK_LUMA() + if(!ps_cur_mb_info->u1_tran_form8x8) + { + UWORD32 u4_luma_dc_only_csbp = 0; + UWORD32 u4_temp = 0; + WORD16* pi2_dc_val = NULL; + /* + * Reserve the pointer to dc vals. The dc vals will be copied + * after unpacking of ac vals since memset to 0 inside. + */ + if(intra_flag && (u1_mb_type != I_4x4_MB)) + { + if(CHECKBIT(ps_cur_mb_info->u1_yuv_dc_block_flag,0)) + { + pi2_dc_val = (WORD16 *)ps_dec->pv_proc_tu_coeff_data; + + ps_dec->pv_proc_tu_coeff_data = (void *)(pi2_dc_val + 16); + } + } + + if(ui2_luma_csbp) + { + pi2_coeff_data = ps_dec->pi2_coeff_data; + u4_temp = ih264d_unpack_coeff4x4_8x8blk(ps_dec, + ps_cur_mb_info, + ui2_luma_csbp, + pi2_coeff_data); + u4_luma_dc_only_csbp = u4_temp; + + pi2_coeff_data += 32; + + ui2_luma_csbp = ui2_luma_csbp >> 2; + u4_temp = ih264d_unpack_coeff4x4_8x8blk(ps_dec, + ps_cur_mb_info, + ui2_luma_csbp, + pi2_coeff_data); + + u4_luma_dc_only_csbp |= (u4_temp << 2); + + pi2_coeff_data += 32 + 64; + + ui2_luma_csbp = ui2_luma_csbp >> 6; + u4_temp = ih264d_unpack_coeff4x4_8x8blk(ps_dec, + ps_cur_mb_info, + ui2_luma_csbp, + pi2_coeff_data); + + u4_luma_dc_only_csbp |= (u4_temp << 8); + + pi2_coeff_data += 32; + + ui2_luma_csbp = ui2_luma_csbp >> 2; + u4_temp = ih264d_unpack_coeff4x4_8x8blk(ps_dec, + ps_cur_mb_info, + ui2_luma_csbp, + pi2_coeff_data); + u4_luma_dc_only_csbp |= (u4_temp << 10); + } + + if(pi2_dc_val != NULL) + { + WORD32 i; + pi2_coeff_data = ps_dec->pi2_coeff_data; + for(i = 0; i < 4; i++) + { + pi2_coeff_data[0] = pi2_dc_val[0]; + pi2_coeff_data[4 * 16] = pi2_dc_val[4]; + pi2_coeff_data[8 * 16] = pi2_dc_val[8]; + pi2_coeff_data[12 * 16] = pi2_dc_val[12]; + + pi2_dc_val++; /* Point to next column */ + pi2_coeff_data += 16; + } + u4_luma_dc_only_csbp = ps_cur_mb_info->u2_luma_csbp ^ 0xFFFF; + } + return u4_luma_dc_only_csbp; + } + else + { + UWORD32 u4_luma_dc_only_cbp = 0; + WORD32 dc_only_flag; + if(ui2_luma_csbp) + { + pi2_coeff_data = ps_dec->pi2_coeff_data; + dc_only_flag = ih264d_unpack_coeff8x8_8x8blk_cavlc(ps_dec, + ps_cur_mb_info, + ui2_luma_csbp, + pi2_coeff_data); + INSERT_BIT(u4_luma_dc_only_cbp, 0, dc_only_flag); + + pi2_coeff_data += 64; + + ui2_luma_csbp = ui2_luma_csbp >> 2; + dc_only_flag = ih264d_unpack_coeff8x8_8x8blk_cavlc(ps_dec, + ps_cur_mb_info, + ui2_luma_csbp, + pi2_coeff_data); + + INSERT_BIT(u4_luma_dc_only_cbp, 1, dc_only_flag); + + pi2_coeff_data += 64; + + ui2_luma_csbp = ui2_luma_csbp >> 6; + dc_only_flag = ih264d_unpack_coeff8x8_8x8blk_cavlc(ps_dec, + ps_cur_mb_info, + ui2_luma_csbp, + pi2_coeff_data); + + INSERT_BIT(u4_luma_dc_only_cbp, 2, dc_only_flag); + + pi2_coeff_data += 64; + ui2_luma_csbp = ui2_luma_csbp >> 2; + dc_only_flag = ih264d_unpack_coeff8x8_8x8blk_cavlc(ps_dec, + ps_cur_mb_info, + ui2_luma_csbp, + pi2_coeff_data); + INSERT_BIT(u4_luma_dc_only_cbp, 3, dc_only_flag); + } + return u4_luma_dc_only_cbp; + } + +} + +void ih264d_unpack_chroma_coeff4x4_mb(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info) +{ + UWORD8 u1_mb_type = ps_cur_mb_info->u1_mb_type; + UWORD16 ui2_chroma_csbp = ps_cur_mb_info->u2_chroma_csbp; + UWORD8 *pu1_inv_scan = ps_dec->pu1_inv_scan; + WORD16 *pi2_coeff_data = ps_dec->pi2_coeff_data; + WORD32 i; + WORD16 *pi2_dc_val_u = NULL; + WORD16 *pi2_dc_val_v = NULL; + + PROFILE_DISABLE_UNPACK_CHROMA() + if((ps_cur_mb_info->u1_cbp >> 4) == CBPC_ALLZERO) + return; + + /* + * Reserve the pointers to dc vals. The dc vals will be copied + * after unpacking of ac vals since memset to 0 inside. + */ + if(CHECKBIT(ps_cur_mb_info->u1_yuv_dc_block_flag,1)) + { + pi2_dc_val_u = (WORD16 *)ps_dec->pv_proc_tu_coeff_data; + + ps_dec->pv_proc_tu_coeff_data = (void *)(pi2_dc_val_u + 4); + } + if(CHECKBIT(ps_cur_mb_info->u1_yuv_dc_block_flag,2)) + { + pi2_dc_val_v = (WORD16 *)ps_dec->pv_proc_tu_coeff_data; + + ps_dec->pv_proc_tu_coeff_data = (void *)(pi2_dc_val_v + 4); + } + + if((ps_cur_mb_info->u1_cbp >> 4) == CBPC_NONZERO) + { + pi2_coeff_data = ps_dec->pi2_coeff_data; + ih264d_unpack_coeff4x4_8x8blk_chroma(ps_dec, + ps_cur_mb_info, + ui2_chroma_csbp, + pi2_coeff_data); + + pi2_coeff_data += 64; + ui2_chroma_csbp = ui2_chroma_csbp >> 4; + ih264d_unpack_coeff4x4_8x8blk_chroma(ps_dec, + ps_cur_mb_info, + ui2_chroma_csbp, + pi2_coeff_data); + + } + + pi2_coeff_data = ps_dec->pi2_coeff_data; + if(pi2_dc_val_u != NULL) + { + pi2_coeff_data[0] = *pi2_dc_val_u++; + pi2_coeff_data[1 * 16] = *pi2_dc_val_u++; + pi2_coeff_data[2 * 16] = *pi2_dc_val_u++; + pi2_coeff_data[3 * 16] = *pi2_dc_val_u++; + } + else + { + pi2_coeff_data[0] = 0; + pi2_coeff_data[1 * 16] = 0; + pi2_coeff_data[2 * 16] = 0; + pi2_coeff_data[3 * 16] = 0; + } + pi2_coeff_data += 64; + if(pi2_dc_val_v != NULL) + { + pi2_coeff_data[0] = *pi2_dc_val_v++; + pi2_coeff_data[1 * 16] = *pi2_dc_val_v++; + pi2_coeff_data[2 * 16] = *pi2_dc_val_v++; + pi2_coeff_data[3 * 16] = *pi2_dc_val_v++; + } + else + { + pi2_coeff_data[0] = 0; + pi2_coeff_data[1 * 16] = 0; + pi2_coeff_data[2 * 16] = 0; + pi2_coeff_data[3 * 16] = 0; + } +} +UWORD32 ih264d_unpack_luma_coeff8x8_mb(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info) +{ + WORD32 blk_8x8_cnt; + WORD16 *pi2_out_coeff_data = ps_dec->pi2_coeff_data; + UWORD8 u1_field_coding_flag = ps_cur_mb_info->ps_curmb->u1_mb_fld; + UWORD8 *pu1_inv_scan; + UWORD32 u4_luma_dc_only_cbp = 0; + + PROFILE_DISABLE_UNPACK_LUMA() + if(!u1_field_coding_flag) + { + /*******************************************************************/ + /* initializing inverse scan matrices */ + /*******************************************************************/ + pu1_inv_scan = (UWORD8 *)gau1_ih264d_inv_scan_prog8x8_cabac; + } + else + { + /*******************************************************************/ + /* initializing inverse scan matrices */ + /*******************************************************************/ + pu1_inv_scan = (UWORD8 *)gau1_ih264d_inv_scan_int8x8_cabac; + } + + for(blk_8x8_cnt = 0; blk_8x8_cnt < 4; blk_8x8_cnt++) + { + if(CHECKBIT(ps_cur_mb_info->u1_cbp, blk_8x8_cnt)) + { + tu_blk8x8_coeff_data_t *ps_tu_8x8 = (tu_blk8x8_coeff_data_t *)ps_dec->pv_proc_tu_coeff_data; + UWORD32 u4_sig_coeff_map; + WORD32 idx = 0; + WORD16 *pi2_coeff_data = &ps_tu_8x8->ai2_level[0]; + WORD32 num_coeff = 0; + + /* memset 64 coefficient to zero */ + memset(pi2_out_coeff_data,0,64*sizeof(WORD16)); + + u4_sig_coeff_map = ps_tu_8x8->au4_sig_coeff_map[1]; + + while(u4_sig_coeff_map) + { + idx = CLZ(u4_sig_coeff_map); + + idx = 31 - idx; + RESET_BIT(u4_sig_coeff_map,idx); + + idx = pu1_inv_scan[idx + 32]; + pi2_out_coeff_data[idx] = *pi2_coeff_data++; + num_coeff++; + } + + u4_sig_coeff_map = ps_tu_8x8->au4_sig_coeff_map[0]; + while(u4_sig_coeff_map) + { + idx = CLZ(u4_sig_coeff_map); + + idx = 31 - idx; + RESET_BIT(u4_sig_coeff_map,idx); + + idx = pu1_inv_scan[idx]; + pi2_out_coeff_data[idx] = *pi2_coeff_data++; + num_coeff++; + } + + if((num_coeff == 1) && (idx == 0)) + { + SET_BIT(u4_luma_dc_only_cbp,blk_8x8_cnt); + } + + + { + WORD32 offset; + offset = (UWORD8 *)pi2_coeff_data - (UWORD8 *)ps_tu_8x8; + offset = ALIGN4(offset); + ps_dec->pv_proc_tu_coeff_data = (void *)((UWORD8 *)ps_dec->pv_proc_tu_coeff_data + offset); + } + } + pi2_out_coeff_data += 64; + } + + return u4_luma_dc_only_cbp; +} +/*! + ************************************************************************** + * \if Function name : ih264d_process_intra_mb \endif + * + * \brief + * This function decodes an I MB. Intraprediction is carried out followed + * by InvTramsform. Both IntraPrediction and Reconstrucion are carried out + * row buffer itself. + * + * + * \return + * 0 on Success and Error code otherwise + ************************************************************************** + */ +WORD32 ih264d_process_intra_mb(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info, + UWORD8 u1_mb_num) +{ + UWORD8 u1_mb_type = ps_cur_mb_info->u1_mb_type; + UWORD8 uc_temp = ps_cur_mb_info->u1_mb_ngbr_availablity; + UWORD8 u1_top_available = BOOLEAN(uc_temp & TOP_MB_AVAILABLE_MASK); + UWORD8 u1_left_available = BOOLEAN(uc_temp & LEFT_MB_AVAILABLE_MASK); + UWORD8 u1_use_top_right_mb = BOOLEAN(uc_temp & TOP_RIGHT_MB_AVAILABLE_MASK); + UWORD8 u1_use_top_left_mb = BOOLEAN(uc_temp & TOP_LEFT_MB_AVAILABLE_MASK); + UWORD8 uc_useTopMB = u1_top_available; + UWORD16 u2_use_left_mb = u1_left_available; + UWORD16 u2_use_left_mb_pack; + UWORD8 *pu1_luma_pred_buffer; + /* CHANGED CODE */ + UWORD8 *pu1_luma_rec_buffer; + UWORD8 *puc_top; + + mb_neigbour_params_t *ps_left_mb; + mb_neigbour_params_t *ps_top_mb; + mb_neigbour_params_t *ps_top_right_mb; + mb_neigbour_params_t *ps_curmb; + + UWORD16 u2_mbx = ps_cur_mb_info->u2_mbx; + UWORD32 ui_pred_width, ui_rec_width; + WORD16 *pi2_y_coeff; + UWORD8 u1_mbaff, u1_topmb, u1_mb_field_decoding_flag; + UWORD32 u4_num_pmbair; + UWORD16 ui2_luma_csbp = ps_cur_mb_info->u2_luma_csbp; + UWORD8 *pu1_yleft, *pu1_ytop_left; + /* Chroma variables*/ + UWORD8 *pu1_top_u; + UWORD8 *pu1_uleft; + UWORD8 *pu1_u_top_left; + /* CHANGED CODE */ + UWORD8 *pu1_mb_cb_rei1_buffer, *pu1_mb_cr_rei1_buffer; + UWORD32 u4_recwidth_cr; + /* CHANGED CODE */ + tfr_ctxt_t *ps_frame_buf = ps_dec->ps_frame_buf_ip_recon; + UWORD32 u4_luma_dc_only_csbp = 0; + UWORD32 u4_luma_dc_only_cbp = 0; + + UWORD8 *pu1_prev_intra4x4_pred_mode_data = (UWORD8 *)ps_dec->pv_proc_tu_coeff_data; //Pointer to keep track of intra4x4_pred_mode data in pv_proc_tu_coeff_data buffer + u1_mbaff = ps_dec->ps_cur_slice->u1_mbaff_frame_flag; + u1_topmb = ps_cur_mb_info->u1_topmb; + u4_num_pmbair = (u1_mb_num >> u1_mbaff); + + + /*--------------------------------------------------------------------*/ + /* Find the current MB's mb params */ + /*--------------------------------------------------------------------*/ + u1_mb_field_decoding_flag = ps_cur_mb_info->u1_mb_field_decodingflag; + + ps_curmb = ps_cur_mb_info->ps_curmb; + ps_top_mb = ps_cur_mb_info->ps_top_mb; + ps_left_mb = ps_cur_mb_info->ps_left_mb; + ps_top_right_mb = ps_cur_mb_info->ps_top_right_mb; + + /*--------------------------------------------------------------------*/ + /* Check whether neighbouring MB is Inter MB and */ + /* constrained intra pred is 1. */ + /*--------------------------------------------------------------------*/ + u2_use_left_mb_pack = (u2_use_left_mb << 8) + u2_use_left_mb; + + if(ps_dec->ps_cur_pps->u1_constrained_intra_pred_flag) + { + UWORD8 u1_left = (UWORD8)u2_use_left_mb; + + uc_useTopMB = uc_useTopMB + && ((ps_top_mb->u1_mb_type != P_MB) + && (ps_top_mb->u1_mb_type != B_MB)); + u2_use_left_mb = u2_use_left_mb + && ((ps_left_mb->u1_mb_type != P_MB) + && (ps_left_mb->u1_mb_type != B_MB)); + + u2_use_left_mb_pack = (u2_use_left_mb << 8) + u2_use_left_mb; + if(u1_mbaff) + { + if(u1_mb_field_decoding_flag ^ ps_left_mb->u1_mb_fld) + { + u1_left = u1_left + && (((ps_left_mb + 1)->u1_mb_type != P_MB) + && ((ps_left_mb + 1)->u1_mb_type + != B_MB)); + u2_use_left_mb = u2_use_left_mb && u1_left; + if(u1_mb_field_decoding_flag) + u2_use_left_mb_pack = (u1_left << 8) + + (u2_use_left_mb_pack & 0xff); + else + u2_use_left_mb_pack = (u2_use_left_mb << 8) + + (u2_use_left_mb); + } + } + u1_use_top_right_mb = + u1_use_top_right_mb + && ((ps_top_right_mb->u1_mb_type != P_MB) + && (ps_top_right_mb->u1_mb_type + != B_MB)); + + u1_use_top_left_mb = + u1_use_top_left_mb + && ((ps_cur_mb_info->u1_topleft_mbtype != P_MB) + && (ps_cur_mb_info->u1_topleft_mbtype + != B_MB)); + } + + /*********************Common pointer calculations *************************/ + /* CHANGED CODE */ + pu1_luma_pred_buffer = ps_dec->pu1_y; + pu1_luma_rec_buffer = ps_frame_buf->pu1_dest_y + (u4_num_pmbair << 4); + pu1_mb_cb_rei1_buffer = ps_frame_buf->pu1_dest_u + + (u4_num_pmbair << 3) * YUV420SP_FACTOR; + pu1_mb_cr_rei1_buffer = ps_frame_buf->pu1_dest_v + (u4_num_pmbair << 3); + ui_pred_width = MB_SIZE; + ui_rec_width = ps_dec->u2_frm_wd_y << u1_mb_field_decoding_flag; + u4_recwidth_cr = ps_dec->u2_frm_wd_uv << u1_mb_field_decoding_flag; + /************* Current and top luma pointer *****************/ + + if(u1_mbaff) + { + if(u1_topmb == 0) + { + pu1_luma_rec_buffer += ( + u1_mb_field_decoding_flag ? + (ui_rec_width >> 1) : + (ui_rec_width << 4)); + pu1_mb_cb_rei1_buffer += ( + u1_mb_field_decoding_flag ? + (u4_recwidth_cr >> 1) : + (u4_recwidth_cr << 3)); + pu1_mb_cr_rei1_buffer += ( + u1_mb_field_decoding_flag ? + (u4_recwidth_cr >> 1) : + (u4_recwidth_cr << 3)); + } + } + + /* CHANGED CODE */ + if(ps_dec->u4_use_intrapred_line_copy == 1) + { + puc_top = ps_dec->pu1_prev_y_intra_pred_line + (ps_cur_mb_info->u2_mbx << 4); + pu1_top_u = ps_dec->pu1_prev_u_intra_pred_line + + (ps_cur_mb_info->u2_mbx << 3) * YUV420SP_FACTOR; + } + else + { + puc_top = pu1_luma_rec_buffer - ui_rec_width; + pu1_top_u = pu1_mb_cb_rei1_buffer - u4_recwidth_cr; + } + /* CHANGED CODE */ + + /************* Left pointer *****************/ + pu1_yleft = pu1_luma_rec_buffer - 1; + pu1_uleft = pu1_mb_cb_rei1_buffer - 1 * YUV420SP_FACTOR; + + /**************Top Left pointer calculation**********/ + pu1_ytop_left = puc_top - 1; + pu1_u_top_left = pu1_top_u - 1 * YUV420SP_FACTOR; + + /* CHANGED CODE */ + PROFILE_DISABLE_INTRA_PRED() + { + pu1_prev_intra4x4_pred_mode_data = (UWORD8 *)ps_dec->pv_proc_tu_coeff_data; + if(u1_mb_type == I_4x4_MB && ps_cur_mb_info->u1_tran_form8x8 == 0) + { + ps_dec->pv_proc_tu_coeff_data = (void *)((UWORD8 *)ps_dec->pv_proc_tu_coeff_data + 32); + + } + else if (u1_mb_type == I_4x4_MB && ps_cur_mb_info->u1_tran_form8x8 == 1) + { + ps_dec->pv_proc_tu_coeff_data = (void *)((UWORD8 *)ps_dec->pv_proc_tu_coeff_data + 8); + } + } + if(!ps_cur_mb_info->u1_tran_form8x8) + { + u4_luma_dc_only_csbp = ih264d_unpack_luma_coeff4x4_mb(ps_dec, + ps_cur_mb_info, + 1); + } + else + { + if(!ps_dec->ps_cur_pps->u1_entropy_coding_mode) + { + u4_luma_dc_only_cbp = ih264d_unpack_luma_coeff4x4_mb(ps_dec, + ps_cur_mb_info, + 1); + } + else + { + u4_luma_dc_only_cbp = ih264d_unpack_luma_coeff8x8_mb(ps_dec, + ps_cur_mb_info); + } + } + + pi2_y_coeff = ps_dec->pi2_coeff_data; + + if(u1_mb_type != I_4x4_MB) + { + UWORD8 u1_intrapred_mode = MB_TYPE_TO_INTRA_16x16_MODE(u1_mb_type); + /*--------------------------------------------------------------------*/ + /* 16x16 IntraPrediction */ + /*--------------------------------------------------------------------*/ + { + UWORD8 u1_packed_modes = (u1_top_available << 1) + + u1_left_available; + UWORD8 u1_err_code = + (u1_intrapred_mode & 1) ? + u1_intrapred_mode : + (u1_intrapred_mode ^ 2); + + if((u1_err_code & u1_packed_modes) ^ u1_err_code) + { + u1_intrapred_mode = 0; + ps_dec->i4_error_code = ERROR_INTRAPRED; + } + } + { + /* Align the size to multiple of 8, so that SIMD functions + can read 64 bits at a time. Only 33 bytes are actaully used */ + UWORD8 au1_ngbr_pels[40]; + /* Get neighbour pixels */ + /* left pels */ + if(u2_use_left_mb) + { + WORD32 i; + for(i = 0; i < 16; i++) + au1_ngbr_pels[16 - 1 - i] = pu1_yleft[i * ui_rec_width]; + } + else + { + memset(au1_ngbr_pels, 0, 16); + } + + /* top left pels */ + au1_ngbr_pels[16] = *pu1_ytop_left; + + /* top pels */ + if(uc_useTopMB) + { + memcpy(au1_ngbr_pels + 16 + 1, puc_top, 16); + } + else + { + memset(au1_ngbr_pels + 16 + 1, 0, 16); + } + PROFILE_DISABLE_INTRA_PRED() + ps_dec->apf_intra_pred_luma_16x16[u1_intrapred_mode]( + au1_ngbr_pels, pu1_luma_rec_buffer, 1, ui_rec_width, + ((uc_useTopMB << 2) | u2_use_left_mb)); + } + { + UWORD32 i; + WORD16 ai2_tmp[16]; + for(i = 0; i < 16; i++) + { + WORD16 *pi2_level = pi2_y_coeff + (i << 4); + UWORD8 *pu1_pred_sblk = pu1_luma_rec_buffer + + ((i & 0x3) * BLK_SIZE) + + (i >> 2) * (ui_rec_width << 2); + PROFILE_DISABLE_IQ_IT_RECON() + { + if(CHECKBIT(ps_cur_mb_info->u2_luma_csbp, i)) + { + ps_dec->pf_iquant_itrans_recon_luma_4x4( + pi2_level, + pu1_pred_sblk, + pu1_pred_sblk, + ui_rec_width, + ui_rec_width, + gau2_ih264_iquant_scale_4x4[ps_cur_mb_info->u1_qp_rem6], + (UWORD16 *)ps_dec->s_high_profile.i2_scalinglist4x4[0], + ps_cur_mb_info->u1_qp_div6, ai2_tmp, 1, + pi2_level); + } + else if((CHECKBIT(u4_luma_dc_only_csbp, i)) && pi2_level[0] != 0) + { + ps_dec->pf_iquant_itrans_recon_luma_4x4_dc( + pi2_level, + pu1_pred_sblk, + pu1_pred_sblk, + ui_rec_width, + ui_rec_width, + gau2_ih264_iquant_scale_4x4[ps_cur_mb_info->u1_qp_rem6], + (UWORD16 *)ps_dec->s_high_profile.i2_scalinglist4x4[0], + ps_cur_mb_info->u1_qp_div6, ai2_tmp, 1, + pi2_level); + } + } + } + } + } + else if(!ps_cur_mb_info->u1_tran_form8x8) + { + UWORD8 u1_is_left_sub_block, u1_is_top_sub_block = uc_useTopMB; + UWORD8 u1_sub_blk_x, u1_sub_blk_y, u1_sub_mb_num; + WORD8 i1_top_pred_mode; + WORD8 i1_left_pred_mode; + UWORD8 *pu1_top, *pu1_left, *pu1_top_left, *pu1_top_right; + WORD8 *pi1_cur_pred_mode, *pi1_left_pred_mode, *pc_topPredMode; + UWORD16 ui2_left_pred_buf_width = 0xffff; + WORD8 i1_intra_pred; + UWORD8 *pu1_prev_intra4x4_pred_mode_flag = pu1_prev_intra4x4_pred_mode_data; + UWORD8 *pu1_rem_intra4x4_pred_mode = pu1_prev_intra4x4_pred_mode_data + 16; + WORD16 *pi2_y_coeff1; + UWORD8 u1_cur_sub_block; + UWORD16 ui2_top_rt_mask; + + /*--------------------------------------------------------------------*/ + /* 4x4 IntraPrediction */ + /*--------------------------------------------------------------------*/ + /* Calculation of Top Right subblock mask */ + /* */ + /* (a) Set it to default mask */ + /* [It has 0 for sublocks which will never have top-right sub block] */ + /* */ + /* (b) If top MB is not available */ + /* Clear the bits of the first row sub blocks */ + /* */ + /* (c) Set/Clear bit for top-right sublock of MB */ + /* [5 sub-block in decoding order] based on TOP RIGHT MB availablity */ + /*--------------------------------------------------------------------*/ + + pu1_top = puc_top; + + ui2_top_rt_mask = (u1_use_top_right_mb << 3) | (0x5750); + if(uc_useTopMB) + ui2_top_rt_mask |= 0x7; + + /*Top Related initialisations*/ + + + pi1_cur_pred_mode = ps_cur_mb_info->ps_curmb->pi1_intrapredmodes; + pc_topPredMode = ps_cur_mb_info->ps_top_mb->pi1_intrapredmodes; + /*-------------------------------------- + if(u1_mbaff) + { + + pi1_cur_pred_mode += (u2_mbx << 2); + pc_topPredMode = pi1_cur_pred_mode + ps_cur_mb_info->i1_offset; + pi1_cur_pred_mode += (u1_topmb) ? 0: 4; + }*/ + + if(u1_top_available) + { + if(ps_top_mb->u1_mb_type == I_4x4_MB) + *(WORD32*)pi1_cur_pred_mode = *(WORD32*)pc_topPredMode; + else + *(WORD32*)pi1_cur_pred_mode = + (uc_useTopMB) ? DC_DC_DC_DC : NOT_VALID; + } + else + *(WORD32*)pi1_cur_pred_mode = NOT_VALID; + /* CHANGED CODE */ + + /* CHANGED CODE */ + + /*Left Related initialisations*/ + pi1_left_pred_mode = ps_dec->pi1_left_pred_mode; + if(!u1_mbaff) + { + + if(u1_left_available) + { + + if(ps_left_mb->u1_mb_type != I_4x4_MB) + *(WORD32*)pi1_left_pred_mode = + (u2_use_left_mb_pack) ? + DC_DC_DC_DC : + NOT_VALID; + + } + else + { + + *(WORD32*)pi1_left_pred_mode = NOT_VALID; + } + + } + else + { + UWORD8 u1_curMbfld = ps_cur_mb_info->u1_mb_field_decodingflag; + UWORD8 u1_leftMbfld = ps_left_mb->u1_mb_fld; + + if(u1_curMbfld ^ u1_leftMbfld) + { + + if(u1_topmb + | ((u1_topmb == 0) + && ((ps_curmb - 1)->u1_mb_type + != I_4x4_MB))) + { + if(u1_left_available) + { + if(ps_left_mb->u1_mb_type != I_4x4_MB) + { + if(CHECKBIT(u2_use_left_mb_pack,0) == 0) + *(WORD32*)pi1_left_pred_mode = NOT_VALID; + else + *(WORD32*)pi1_left_pred_mode = DC_DC_DC_DC; + } + } + else + *(WORD32*)pi1_left_pred_mode = NOT_VALID; + + if(u1_curMbfld) + { + if(u1_left_available) + { + if((ps_left_mb + 1)->u1_mb_type != I_4x4_MB) + { + if(u2_use_left_mb_pack >> 8) + *(WORD32*)(pi1_left_pred_mode + 4) = + DC_DC_DC_DC; + else + *(WORD32*)(pi1_left_pred_mode + 4) = + NOT_VALID; + } + } + else + *(WORD32*)(pi1_left_pred_mode + 4) = NOT_VALID; + pi1_left_pred_mode[1] = pi1_left_pred_mode[2]; + pi1_left_pred_mode[2] = pi1_left_pred_mode[4]; + pi1_left_pred_mode[3] = pi1_left_pred_mode[6]; + *(WORD32*)(pi1_left_pred_mode + 4) = + *(WORD32*)pi1_left_pred_mode; + } + else + { + + pi1_left_pred_mode[7] = pi1_left_pred_mode[3]; + pi1_left_pred_mode[6] = pi1_left_pred_mode[3]; + pi1_left_pred_mode[5] = pi1_left_pred_mode[2]; + pi1_left_pred_mode[4] = pi1_left_pred_mode[2]; + pi1_left_pred_mode[3] = pi1_left_pred_mode[1]; + pi1_left_pred_mode[2] = pi1_left_pred_mode[1]; + pi1_left_pred_mode[1] = pi1_left_pred_mode[0]; + } + } + pi1_left_pred_mode += (u1_topmb) ? 0 : 4; + } + else + { + + pi1_left_pred_mode += (u1_topmb) ? 0 : 4; + if(u1_left_available) + { + + if(ps_left_mb->u1_mb_type != I_4x4_MB) + *(WORD32*)pi1_left_pred_mode = + (u2_use_left_mb_pack) ? + DC_DC_DC_DC : + NOT_VALID; + } + else + *(WORD32*)pi1_left_pred_mode = NOT_VALID; + } + } + /* One time pointer initialisations*/ + pi2_y_coeff1 = pi2_y_coeff; + pu1_top_left = pu1_ytop_left; + + /* Scan the sub-blocks in Raster Scan Order */ + for(u1_sub_mb_num = 0; u1_sub_mb_num < 16; u1_sub_mb_num++) + { + /* Align the size to multiple of 8, so that SIMD functions + can read 64 bits at a time. Only 13 bytes are actaully used */ + UWORD8 au1_ngbr_pels[16]; + + u1_sub_blk_x = u1_sub_mb_num & 0x3; + u1_sub_blk_y = u1_sub_mb_num >> 2; + i1_top_pred_mode = pi1_cur_pred_mode[u1_sub_blk_x]; + i1_left_pred_mode = pi1_left_pred_mode[u1_sub_blk_y]; + u1_use_top_right_mb = (!!CHECKBIT(ui2_top_rt_mask, u1_sub_mb_num)); + + /*********** left subblock availability**********/ + if(u1_sub_blk_x) + u1_is_left_sub_block = 1; + else + u1_is_left_sub_block = + (u1_sub_blk_y < 2) ? + (CHECKBIT(u2_use_left_mb_pack, + 0)) : + (u2_use_left_mb_pack >> 8); + + /* CHANGED CODE */ + if(u1_sub_blk_y) + u1_is_top_sub_block = 1; + + /* CHANGED CODE */ + /***************** Top *********************/ + if(ps_dec->u4_use_intrapred_line_copy == 1) + { + + if(u1_sub_blk_y) + pu1_top = pu1_luma_rec_buffer - ui_rec_width; + else + pu1_top = puc_top + (u1_sub_blk_x << 2); + } + else + { + pu1_top = pu1_luma_rec_buffer - ui_rec_width; + } + /***************** Top Right *********************/ + pu1_top_right = pu1_top + 4; + /***************** Top Left *********************/ + pu1_top_left = pu1_top - 1; + /***************** Left *********************/ + pu1_left = pu1_luma_rec_buffer - 1; + /* CHANGED CODE */ + + /*---------------------------------------------------------------*/ + /* Calculation of Intra prediction mode */ + /*---------------------------------------------------------------*/ + i1_intra_pred = ((i1_left_pred_mode < 0) | (i1_top_pred_mode < 0)) ? + DC : MIN(i1_left_pred_mode, i1_top_pred_mode); + { + UWORD8 u1_packed_modes = (u1_is_top_sub_block << 1) + + u1_is_left_sub_block; + UWORD8 *pu1_intra_err_codes = + (UWORD8 *)gau1_ih264d_intra_pred_err_code; + UWORD8 uc_b2b0 = ((u1_sub_mb_num & 4) >> 1) | (u1_sub_mb_num & 1); + UWORD8 uc_b3b1 = ((u1_sub_mb_num & 8) >> 2) + | ((u1_sub_mb_num & 2) >> 1); + + u1_cur_sub_block = (uc_b3b1 << 2) + uc_b2b0; + PROFILE_DISABLE_INTRA_PRED() + if(!pu1_prev_intra4x4_pred_mode_flag[u1_cur_sub_block]) + { + i1_intra_pred = + pu1_rem_intra4x4_pred_mode[u1_cur_sub_block] + + (pu1_rem_intra4x4_pred_mode[u1_cur_sub_block] + >= i1_intra_pred); + } + i1_intra_pred = CLIP3(0, 8, i1_intra_pred); + { + UWORD8 u1_err_code = pu1_intra_err_codes[i1_intra_pred]; + + if((u1_err_code & u1_packed_modes) ^ u1_err_code) + { + i1_intra_pred = 0; + ps_dec->i4_error_code = ERROR_INTRAPRED; + } + + } + } + { + /* Get neighbour pixels */ + /* left pels */ + if(u1_is_left_sub_block) + { + WORD32 i; + for(i = 0; i < 4; i++) + au1_ngbr_pels[4 - 1 - i] = pu1_left[i * ui_rec_width]; + } + else + { + memset(au1_ngbr_pels, 0, 4); + } + + /* top left pels */ + au1_ngbr_pels[4] = *pu1_top_left; + + /* top pels */ + if(u1_is_top_sub_block) + { + memcpy(au1_ngbr_pels + 4 + 1, pu1_top, 4); + } + else + { + memset(au1_ngbr_pels + 4 + 1, 0, 4); + } + + /* top right pels */ + if(u1_use_top_right_mb) + { + memcpy(au1_ngbr_pels + 4 * 2 + 1, pu1_top_right, 4); + } + else if(u1_is_top_sub_block) + { + memset(au1_ngbr_pels + 4 * 2 + 1, au1_ngbr_pels[4 * 2], 4); + } + } + PROFILE_DISABLE_INTRA_PRED() + ps_dec->apf_intra_pred_luma_4x4[i1_intra_pred]( + au1_ngbr_pels, pu1_luma_rec_buffer, 1, + ui_rec_width, + ((u1_is_top_sub_block << 2) | u1_is_left_sub_block)); + + /* CHANGED CODE */ + if(CHECKBIT(ui2_luma_csbp, u1_sub_mb_num)) + { + WORD16 ai2_tmp[16]; + PROFILE_DISABLE_IQ_IT_RECON() + { + if(CHECKBIT(u4_luma_dc_only_csbp, u1_sub_mb_num)) + { + ps_dec->pf_iquant_itrans_recon_luma_4x4_dc( + pi2_y_coeff1, + pu1_luma_rec_buffer, + pu1_luma_rec_buffer, + ui_rec_width, + ui_rec_width, + gau2_ih264_iquant_scale_4x4[ps_cur_mb_info->u1_qp_rem6], + (UWORD16 *)ps_dec->s_high_profile.i2_scalinglist4x4[0], + ps_cur_mb_info->u1_qp_div6, ai2_tmp, 0, + NULL); + } + else + { + ps_dec->pf_iquant_itrans_recon_luma_4x4( + pi2_y_coeff1, + pu1_luma_rec_buffer, + pu1_luma_rec_buffer, + ui_rec_width, + ui_rec_width, + gau2_ih264_iquant_scale_4x4[ps_cur_mb_info->u1_qp_rem6], + (UWORD16 *)ps_dec->s_high_profile.i2_scalinglist4x4[0], + ps_cur_mb_info->u1_qp_div6, ai2_tmp, 0, + NULL); + } + } + + } + + /*---------------------------------------------------------------*/ + /* Update sub block number */ + /*---------------------------------------------------------------*/ + pi2_y_coeff1 += 16; + pu1_luma_rec_buffer += + (u1_sub_blk_x == 3) ? (ui_rec_width << 2) - 12 : 4; + pu1_luma_pred_buffer += + (u1_sub_blk_x == 3) ? (ui_pred_width << 2) - 12 : 4; + /* CHANGED CODE */ + pi1_cur_pred_mode[u1_sub_blk_x] = i1_intra_pred; + pi1_left_pred_mode[u1_sub_blk_y] = i1_intra_pred; + } + } + else if((u1_mb_type == I_4x4_MB) && (ps_cur_mb_info->u1_tran_form8x8 == 1)) + { + UWORD8 u1_is_left_sub_block, u1_is_top_sub_block = uc_useTopMB; + UWORD8 u1_sub_blk_x, u1_sub_blk_y, u1_sub_mb_num; + WORD8 i1_top_pred_mode; + WORD8 i1_left_pred_mode; + UWORD8 *pu1_top, *pu1_left, *pu1_top_left; + WORD8 *pi1_cur_pred_mode, *pi1_left_pred_mode, *pc_topPredMode; + UWORD16 ui2_left_pred_buf_width = 0xffff; + WORD8 i1_intra_pred; + UWORD8 *pu1_prev_intra4x4_pred_mode_flag = pu1_prev_intra4x4_pred_mode_data; + UWORD8 *pu1_rem_intra4x4_pred_mode = pu1_prev_intra4x4_pred_mode_data + 4; + WORD16 *pi2_y_coeff1; + UWORD16 ui2_top_rt_mask; + UWORD32 u4_4x4_left_offset = 0; + + /*--------------------------------------------------------------------*/ + /* 8x8 IntraPrediction */ + /*--------------------------------------------------------------------*/ + /* Calculation of Top Right subblock mask */ + /* */ + /* (a) Set it to default mask */ + /* [It has 0 for sublocks which will never have top-right sub block] */ + /* */ + /* (b) If top MB is not available */ + /* Clear the bits of the first row sub blocks */ + /* */ + /* (c) Set/Clear bit for top-right sublock of MB */ + /* [5 sub-block in decoding order] based on TOP RIGHT MB availablity */ + /* */ + /* ui2_top_rt_mask: marks availibility of top right(neighbour) */ + /* in the 8x8 Block ordering */ + /* */ + /* tr0 tr1 */ + /* 0 1 tr3 */ + /* 2 3 */ + /* */ + /* Top rights for 0 is in top MB */ + /* top right of 1 will be in top right MB */ + /* top right of 3 in right MB and hence not available */ + /* This corresponds to ui2_top_rt_mask having default value 0x4 */ + /*--------------------------------------------------------------------*/ + + ui2_top_rt_mask = (u1_use_top_right_mb << 1) | (0x4); + + if(uc_useTopMB) + { + ui2_top_rt_mask |= 0x1; + } + + /* Top Related initialisations */ + pi1_cur_pred_mode = ps_cur_mb_info->ps_curmb->pi1_intrapredmodes; + pc_topPredMode = ps_cur_mb_info->ps_top_mb->pi1_intrapredmodes; + /* + if(u1_mbaff) + { + pi1_cur_pred_mode += (u2_mbx << 2); + pc_topPredMode = pi1_cur_pred_mode + ps_cur_mb_info->i1_offset; + pi1_cur_pred_mode += (u1_topmb) ? 0: 4; + } + */ + if(u1_top_available) + { + if(ps_top_mb->u1_mb_type == I_4x4_MB) + { + *(WORD32*)pi1_cur_pred_mode = *(WORD32*)pc_topPredMode; + } + else + { + *(WORD32*)pi1_cur_pred_mode = + (uc_useTopMB) ? DC_DC_DC_DC : NOT_VALID; + } + } + else + { + *(WORD32*)pi1_cur_pred_mode = NOT_VALID; + } + + pu1_top = puc_top - 8; + + /*Left Related initialisations*/ + pi1_left_pred_mode = ps_dec->pi1_left_pred_mode; + + if(!u1_mbaff) + { + if(u1_left_available) + { + if(ps_left_mb->u1_mb_type != I_4x4_MB) + { + *(WORD32*)pi1_left_pred_mode = + (u2_use_left_mb_pack) ? + DC_DC_DC_DC : + NOT_VALID; + } + } + else + { + *(WORD32*)pi1_left_pred_mode = NOT_VALID; + } + } + else + { + UWORD8 u1_curMbfld = ps_cur_mb_info->u1_mb_field_decodingflag; + + UWORD8 u1_leftMbfld = ps_left_mb->u1_mb_fld; + + if((!u1_curMbfld) && (u1_leftMbfld)) + { + u4_4x4_left_offset = 1; + } + + if(u1_curMbfld ^ u1_leftMbfld) + { + + if(u1_topmb + | ((u1_topmb == 0) + && ((ps_curmb - 1)->u1_mb_type + != I_4x4_MB))) + + { + if(u1_left_available) + { + if(ps_left_mb->u1_mb_type != I_4x4_MB) + { + if(CHECKBIT(u2_use_left_mb_pack,0) == 0) + { + *(WORD32*)pi1_left_pred_mode = NOT_VALID; + } + else + { + *(WORD32*)pi1_left_pred_mode = DC_DC_DC_DC; + } + } + } + else + { + *(WORD32*)pi1_left_pred_mode = NOT_VALID; + } + + if(u1_curMbfld) + { + if(u1_left_available) + { + if((ps_left_mb + 1)->u1_mb_type != I_4x4_MB) + { + if(u2_use_left_mb_pack >> 8) + { + *(WORD32*)(pi1_left_pred_mode + 4) = + DC_DC_DC_DC; + } + else + { + *(WORD32*)(pi1_left_pred_mode + 4) = + NOT_VALID; + } + } + } + else + { + *(WORD32*)(pi1_left_pred_mode + 4) = NOT_VALID; + } + + pi1_left_pred_mode[1] = pi1_left_pred_mode[2]; + pi1_left_pred_mode[2] = pi1_left_pred_mode[4]; + pi1_left_pred_mode[3] = pi1_left_pred_mode[6]; + *(WORD32*)(pi1_left_pred_mode + 4) = + *(WORD32*)pi1_left_pred_mode; + } + else + { + pi1_left_pred_mode[7] = pi1_left_pred_mode[3]; + pi1_left_pred_mode[6] = pi1_left_pred_mode[3]; + pi1_left_pred_mode[5] = pi1_left_pred_mode[2]; + pi1_left_pred_mode[4] = pi1_left_pred_mode[2]; + pi1_left_pred_mode[3] = pi1_left_pred_mode[1]; + pi1_left_pred_mode[2] = pi1_left_pred_mode[1]; + pi1_left_pred_mode[1] = pi1_left_pred_mode[0]; + } + } + pi1_left_pred_mode += (u1_topmb) ? 0 : 4; + } + else + { + pi1_left_pred_mode += (u1_topmb) ? 0 : 4; + + if(u1_left_available) + { + if(ps_left_mb->u1_mb_type != I_4x4_MB) + { + *(WORD32*)pi1_left_pred_mode = + (u2_use_left_mb_pack) ? + DC_DC_DC_DC : + NOT_VALID; + } + } + else + { + *(WORD32*)pi1_left_pred_mode = NOT_VALID; + } + } + } + + /* One time pointer initialisations*/ + pi2_y_coeff1 = pi2_y_coeff; + + if(u1_use_top_left_mb) + { + pu1_top_left = pu1_ytop_left; + } + else + { + pu1_top_left = NULL; + } + + /* Scan the sub-blocks in Raster Scan Order */ + for(u1_sub_mb_num = 0; u1_sub_mb_num < 4; u1_sub_mb_num++) + { + u1_sub_blk_x = (u1_sub_mb_num & 0x1); + u1_sub_blk_y = (u1_sub_mb_num >> 1); + i1_top_pred_mode = pi1_cur_pred_mode[u1_sub_blk_x << 1]; + i1_left_pred_mode = pi1_left_pred_mode[u1_sub_blk_y << 1]; + + if(2 == u1_sub_mb_num) + { + i1_left_pred_mode = pi1_left_pred_mode[(u1_sub_blk_y << 1) + + u4_4x4_left_offset]; + } + + u1_use_top_right_mb = (!!CHECKBIT(ui2_top_rt_mask, u1_sub_mb_num)); + + /*********** left subblock availability**********/ + if(u1_sub_blk_x) + { + u1_is_left_sub_block = 1; + } + else + { + u1_is_left_sub_block = + (u1_sub_blk_y < 1) ? + (CHECKBIT(u2_use_left_mb_pack, + 0)) : + (u2_use_left_mb_pack >> 8); + } + + /***************** Top *********************/ + if(u1_sub_blk_y) + { + u1_is_top_sub_block = 1; + // sushant + pu1_top = /*pu1_luma_pred_buffer*/pu1_luma_rec_buffer - ui_rec_width; + } + else + { + pu1_top += 8; + } + + /***************** Left *********************/ + if((u1_sub_blk_x) | (u4_num_pmbair != 0)) + { + // sushant + pu1_left = /*pu1_luma_pred_buffer*/pu1_luma_rec_buffer - 1; + ui2_left_pred_buf_width = ui_rec_width; + } + else + { + pu1_left = pu1_yleft; + pu1_yleft += (ui_rec_width << 3); + ui2_left_pred_buf_width = ui_rec_width; + } + + /***************** Top Left *********************/ + if(u1_sub_mb_num) + { + pu1_top_left = (u1_sub_blk_x) ? + pu1_top - 1 : pu1_left - ui_rec_width; + + if((u1_sub_blk_x && (!u1_is_top_sub_block)) + || ((!u1_sub_blk_x) && (!u1_is_left_sub_block))) + { + pu1_top_left = NULL; + } + } + + /*---------------------------------------------------------------*/ + /* Calculation of Intra prediction mode */ + /*---------------------------------------------------------------*/ + i1_intra_pred = ((i1_left_pred_mode < 0) | (i1_top_pred_mode < 0)) ? + DC : MIN(i1_left_pred_mode, i1_top_pred_mode); + { + UWORD8 u1_packed_modes = (u1_is_top_sub_block << 1) + + u1_is_left_sub_block; + UWORD8 *pu1_intra_err_codes = + (UWORD8 *)gau1_ih264d_intra_pred_err_code; + + /********************************************************************/ + /* Same intra4x4_pred_mode array is filled with intra4x4_pred_mode */ + /* for a MB with 8x8 intrapredicition */ + /********************************************************************/ + PROFILE_DISABLE_INTRA_PRED() + if(!pu1_prev_intra4x4_pred_mode_flag[u1_sub_mb_num]) + { + i1_intra_pred = pu1_rem_intra4x4_pred_mode[u1_sub_mb_num] + + (pu1_rem_intra4x4_pred_mode[u1_sub_mb_num] + >= i1_intra_pred); + } + i1_intra_pred = CLIP3(0, 8, i1_intra_pred); + { + UWORD8 u1_err_code = pu1_intra_err_codes[i1_intra_pred]; + + if((u1_err_code & u1_packed_modes) ^ u1_err_code) + { + i1_intra_pred = 0; + ps_dec->i4_error_code = ERROR_INTRAPRED; + } + } + } + + { + /* Align the size to multiple of 8, so that SIMD functions + can read 64 bits at a time. Only 25 bytes are actaully used */ + UWORD8 au1_ngbr_pels[32] = {0}; + WORD32 ngbr_avail; + ngbr_avail = u1_is_left_sub_block << 0; + ngbr_avail |= u1_is_top_sub_block << 2; + + if(pu1_top_left) + ngbr_avail |= 1 << 1; + + ngbr_avail |= u1_use_top_right_mb << 3; + PROFILE_DISABLE_INTRA_PRED() + { + ps_dec->pf_intra_pred_ref_filtering(pu1_left, pu1_top_left, + pu1_top, au1_ngbr_pels, + ui2_left_pred_buf_width, + ngbr_avail); + + ps_dec->apf_intra_pred_luma_8x8[i1_intra_pred]( + au1_ngbr_pels, pu1_luma_rec_buffer, 1, + ui_rec_width, + ((u1_is_top_sub_block << 2) | u1_is_left_sub_block)); + } + } + + /* Inverse Transform and Reconstruction */ + if(CHECKBIT(ps_cur_mb_info->u1_cbp, u1_sub_mb_num)) + { + WORD16 *pi2_scale_matrix_ptr; + WORD16 ai2_tmp[64]; + + pi2_scale_matrix_ptr = + ps_dec->s_high_profile.i2_scalinglist8x8[0]; + PROFILE_DISABLE_IQ_IT_RECON() + { + if(CHECKBIT(u4_luma_dc_only_cbp, u1_sub_mb_num)) + { + ps_dec->pf_iquant_itrans_recon_luma_8x8_dc( + pi2_y_coeff1, + pu1_luma_rec_buffer, + pu1_luma_rec_buffer, + ui_rec_width, + ui_rec_width, + gau1_ih264d_dequant8x8_cavlc[ps_cur_mb_info->u1_qp_rem6], + (UWORD16 *)pi2_scale_matrix_ptr, + ps_cur_mb_info->u1_qp_div6, ai2_tmp, 0, + NULL); + } + else + { + ps_dec->pf_iquant_itrans_recon_luma_8x8( + pi2_y_coeff1, + pu1_luma_rec_buffer, + pu1_luma_rec_buffer, + ui_rec_width, + ui_rec_width, + gau1_ih264d_dequant8x8_cavlc[ps_cur_mb_info->u1_qp_rem6], + (UWORD16 *)pi2_scale_matrix_ptr, + ps_cur_mb_info->u1_qp_div6, ai2_tmp, 0, + NULL); + } + } + + } + + /*---------------------------------------------------------------*/ + /* Update sub block number */ + /*---------------------------------------------------------------*/ + pi2_y_coeff1 += 64; + + pu1_luma_rec_buffer += + (u1_sub_blk_x == 1) ? + (ui_rec_width << 3) - (8 * 1) : 8; + + /*---------------------------------------------------------------*/ + /* Pred mode filled in terms of 4x4 block so replicated in 2 */ + /* locations. */ + /*---------------------------------------------------------------*/ + pi1_cur_pred_mode[u1_sub_blk_x << 1] = i1_intra_pred; + pi1_cur_pred_mode[(u1_sub_blk_x << 1) + 1] = i1_intra_pred; + pi1_left_pred_mode[u1_sub_blk_y << 1] = i1_intra_pred; + pi1_left_pred_mode[(u1_sub_blk_y << 1) + 1] = i1_intra_pred; + } + } + /* Decode Chroma Block */ + ih264d_unpack_chroma_coeff4x4_mb(ps_dec, + ps_cur_mb_info); + /*--------------------------------------------------------------------*/ + /* Chroma Blocks decoding */ + /*--------------------------------------------------------------------*/ + { + UWORD8 u1_intra_chrom_pred_mode; + UWORD8 u1_chroma_cbp = (UWORD8)(ps_cur_mb_info->u1_cbp >> 4); + + /*--------------------------------------------------------------------*/ + /* Perform Chroma intra prediction */ + /*--------------------------------------------------------------------*/ + + u1_intra_chrom_pred_mode = CHROMA_TO_LUMA_INTRA_MODE( + ps_cur_mb_info->u1_chroma_pred_mode); + + { + UWORD8 u1_packed_modes = (u1_top_available << 1) + + u1_left_available; + UWORD8 u1_err_code = + (u1_intra_chrom_pred_mode & 1) ? + u1_intra_chrom_pred_mode : + (u1_intra_chrom_pred_mode ^ 2); + if((u1_err_code & u1_packed_modes) ^ u1_err_code) + { + u1_intra_chrom_pred_mode = 0; + ps_dec->i4_error_code = ERROR_INTRAPRED; + } + } + + /* CHANGED CODE */ + if(u1_chroma_cbp != CBPC_ALLZERO) + { + UWORD16 u2_chroma_csbp = + (u1_chroma_cbp == CBPC_ACZERO) ? + 0 : ps_cur_mb_info->u2_chroma_csbp; + UWORD32 u4_scale_u; + UWORD32 u4_scale_v; + + { + UWORD16 au2_ngbr_pels[33]; + UWORD8 *pu1_ngbr_pels = (UWORD8 *)au2_ngbr_pels; + UWORD16 *pu2_left_uv; + UWORD16 *pu2_topleft_uv; + WORD32 use_left1 = (u2_use_left_mb_pack & 0x0ff); + WORD32 use_left2 = (u2_use_left_mb_pack & 0xff00) >> 8; + + pu2_left_uv = (UWORD16 *)pu1_uleft; + pu2_topleft_uv = (UWORD16 *)pu1_u_top_left; + /* Get neighbour pixels */ + /* left pels */ + if(u2_use_left_mb_pack) + { + WORD32 i; + if(use_left1) + { + for(i = 0; i < 4; i++) + au2_ngbr_pels[8 - 1 - i] = pu2_left_uv[i + * u4_recwidth_cr / YUV420SP_FACTOR]; + } + else + { + memset(au2_ngbr_pels + 4, 0, 4 * sizeof(UWORD16)); + } + + if(use_left2) + { + for(i = 4; i < 8; i++) + au2_ngbr_pels[8 - 1 - i] = pu2_left_uv[i + * u4_recwidth_cr / YUV420SP_FACTOR]; + } + else + { + memset(au2_ngbr_pels, 0, 4 * sizeof(UWORD16)); + } + } + else + { + memset(au2_ngbr_pels, 0, 8 * sizeof(UWORD16)); + } + + /* top left pels */ + au2_ngbr_pels[8] = *pu2_topleft_uv; + + /* top pels */ + if(uc_useTopMB) + { + memcpy(au2_ngbr_pels + 8 + 1, pu1_top_u, + 8 * sizeof(UWORD16)); + } + else + { + memset(au2_ngbr_pels + 8 + 1, 0, 8 * sizeof(UWORD16)); + } + + PROFILE_DISABLE_INTRA_PRED() + ps_dec->apf_intra_pred_chroma[u1_intra_chrom_pred_mode]( + pu1_ngbr_pels, + pu1_mb_cb_rei1_buffer, + 1, + u4_recwidth_cr, + ((uc_useTopMB << 2) | (use_left2 << 4) + | use_left1)); + } + u4_scale_u = ps_cur_mb_info->u1_qpc_div6; + u4_scale_v = ps_cur_mb_info->u1_qpcr_div6; + pi2_y_coeff = ps_dec->pi2_coeff_data; + + { + UWORD32 i; + WORD16 ai2_tmp[16]; + for(i = 0; i < 4; i++) + { + WORD16 *pi2_level = pi2_y_coeff + (i << 4); + UWORD8 *pu1_pred_sblk = pu1_mb_cb_rei1_buffer + + ((i & 0x1) * BLK_SIZE * YUV420SP_FACTOR) + + (i >> 1) * (u4_recwidth_cr << 2); + PROFILE_DISABLE_IQ_IT_RECON() + { + if(CHECKBIT(u2_chroma_csbp, i)) + { + ps_dec->pf_iquant_itrans_recon_chroma_4x4( + pi2_level, + pu1_pred_sblk, + pu1_pred_sblk, + u4_recwidth_cr, + u4_recwidth_cr, + gau2_ih264_iquant_scale_4x4[ps_cur_mb_info->u1_qpc_rem6], + (UWORD16 *)ps_dec->s_high_profile.i2_scalinglist4x4[1], + u4_scale_u, ai2_tmp, pi2_level); + } + else if(pi2_level[0] != 0) + { + ps_dec->pf_iquant_itrans_recon_chroma_4x4_dc( + pi2_level, + pu1_pred_sblk, + pu1_pred_sblk, + u4_recwidth_cr, + u4_recwidth_cr, + gau2_ih264_iquant_scale_4x4[ps_cur_mb_info->u1_qpc_rem6], + (UWORD16 *)ps_dec->s_high_profile.i2_scalinglist4x4[1], + u4_scale_u, ai2_tmp, pi2_level); + } + } + + } + } + + pi2_y_coeff += MB_CHROM_SIZE; + u2_chroma_csbp = u2_chroma_csbp >> 4; + { + UWORD32 i; + WORD16 ai2_tmp[16]; + for(i = 0; i < 4; i++) + { + WORD16 *pi2_level = pi2_y_coeff + (i << 4); + UWORD8 *pu1_pred_sblk = pu1_mb_cb_rei1_buffer + 1 + + ((i & 0x1) * BLK_SIZE * YUV420SP_FACTOR) + + (i >> 1) * (u4_recwidth_cr << 2); + PROFILE_DISABLE_IQ_IT_RECON() + { + if(CHECKBIT(u2_chroma_csbp, i)) + { + ps_dec->pf_iquant_itrans_recon_chroma_4x4( + pi2_level, + pu1_pred_sblk, + pu1_pred_sblk, + u4_recwidth_cr, + u4_recwidth_cr, + gau2_ih264_iquant_scale_4x4[ps_cur_mb_info->u1_qpcr_rem6], + (UWORD16 *)ps_dec->s_high_profile.i2_scalinglist4x4[2], + u4_scale_v, ai2_tmp, pi2_level); + } + else if(pi2_level[0] != 0) + { + ps_dec->pf_iquant_itrans_recon_chroma_4x4_dc( + pi2_level, + pu1_pred_sblk, + pu1_pred_sblk, + u4_recwidth_cr, + u4_recwidth_cr, + gau2_ih264_iquant_scale_4x4[ps_cur_mb_info->u1_qpcr_rem6], + (UWORD16 *)ps_dec->s_high_profile.i2_scalinglist4x4[2], + u4_scale_v, ai2_tmp, pi2_level); + } + } + } + } + + } + else + { + /* If no inverse transform is needed, pass recon buffer pointer */ + /* to Intraprediction module instead of pred buffer pointer */ + { + UWORD16 au2_ngbr_pels[33]; + UWORD8 *pu1_ngbr_pels = (UWORD8 *)au2_ngbr_pels; + UWORD16 *pu2_left_uv; + UWORD16 *pu2_topleft_uv; + WORD32 use_left1 = (u2_use_left_mb_pack & 0x0ff); + WORD32 use_left2 = (u2_use_left_mb_pack & 0xff00) >> 8; + + pu2_topleft_uv = (UWORD16 *)pu1_u_top_left; + pu2_left_uv = (UWORD16 *)pu1_uleft; + + /* Get neighbour pixels */ + /* left pels */ + if(u2_use_left_mb_pack) + { + WORD32 i; + if(use_left1) + { + for(i = 0; i < 4; i++) + au2_ngbr_pels[8 - 1 - i] = pu2_left_uv[i + * u4_recwidth_cr / YUV420SP_FACTOR]; + } + else + { + memset(au2_ngbr_pels + 4, 0, 4 * sizeof(UWORD16)); + } + + if(use_left2) + { + for(i = 4; i < 8; i++) + au2_ngbr_pels[8 - 1 - i] = pu2_left_uv[i + * u4_recwidth_cr / YUV420SP_FACTOR]; + } + else + { + memset(au2_ngbr_pels, 0, 4 * sizeof(UWORD16)); + } + + } + else + { + memset(au2_ngbr_pels, 0, 8 * sizeof(UWORD16)); + } + + /* top left pels */ + au2_ngbr_pels[8] = *pu2_topleft_uv; + + /* top pels */ + if(uc_useTopMB) + { + memcpy(au2_ngbr_pels + 8 + 1, pu1_top_u, + 8 * sizeof(UWORD16)); + } + else + { + memset(au2_ngbr_pels + 8 + 1, 0, 8 * sizeof(UWORD16)); + } + + PROFILE_DISABLE_INTRA_PRED() + ps_dec->apf_intra_pred_chroma[u1_intra_chrom_pred_mode]( + pu1_ngbr_pels, + pu1_mb_cb_rei1_buffer, + 1, + u4_recwidth_cr, + ((uc_useTopMB << 2) | (use_left2 << 4) + | use_left1)); + } + + } + + } + return OK; +} diff --git a/dependencies/ih264d/decoder/ih264d_process_intra_mb.h b/dependencies/ih264d/decoder/ih264d_process_intra_mb.h new file mode 100644 index 00000000..30d7819f --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_process_intra_mb.h @@ -0,0 +1,65 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ + +/*! + ************************************************************************** + * \file ih264d_process_intra_mb.h + * + * \brief + * Contains routines that decode a I slice type + * + * Detailed_description + * + * \date + * 07/07/2003 + * + * \author NS + ************************************************************************** + */ +#ifndef _IH264D_PROCESS_INTRA_MB_H_ +#define _IH264D_PROCESS_INTRA_MB_H_ + +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264d_structs.h" + +#define CHROMA_TO_LUMA_INTRA_MODE(x) (x ^ ( (!(x & 0x01)) << 1)) +#define MB_TYPE_TO_INTRA_16x16_MODE(x) ((x - 1) & 0x03) + +UWORD32 ih264d_unpack_luma_coeff4x4_mb(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info, + UWORD8 intra_flag); +void ih264d_unpack_chroma_coeff4x4_mb(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info); +UWORD32 ih264d_unpack_luma_coeff8x8_mb(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info); + +WORD32 ih264d_read_intra_pred_modes(dec_struct_t *ps_dec, + UWORD8 *pu1_prev_intra4x4_pred_mode_flag, + UWORD8 *pu1_rem_intra4x4_pred_mode, + UWORD32 u4_trans_form8x8); + +WORD32 ih264d_process_intra_mb(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info, + UWORD8 u1_mb_num); + +#endif /* _IH264D_PROCESS_INTRA_MB_H_ */ + diff --git a/dependencies/ih264d/decoder/ih264d_process_pslice.c b/dependencies/ih264d/decoder/ih264d_process_pslice.c new file mode 100644 index 00000000..c29063aa --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_process_pslice.c @@ -0,0 +1,1157 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/*! + ************************************************************************** + * \file ih264d_process_pslice.c + * + * \brief + * Contains routines that decode a I slice type + * + * Detailed_description + * + * \date + * 21/12/2002 + * + * \author NS + ************************************************************************** + */ +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" + +#include <string.h> +#include "ih264d_bitstrm.h" +#include "ih264d_defs.h" +#include "ih264d_debug.h" +#include "ih264d_structs.h" +#include "ih264d_defs.h" +#include "ih264d_parse_cavlc.h" +#include "ih264d_mb_utils.h" +#include "ih264d_deblocking.h" +#include "ih264d_dpb_manager.h" +#include "ih264d_mvpred.h" +#include "ih264d_inter_pred.h" +#include "ih264d_process_pslice.h" +#include "ih264d_error_handler.h" +#include "ih264d_cabac.h" +#include "ih264d_debug.h" +#include "ih264d_tables.h" +#include "ih264d_parse_slice.h" +#include "ih264d_utils.h" +#include "ih264d_parse_islice.h" +#include "ih264d_process_bslice.h" +#include "ih264d_process_intra_mb.h" + +void ih264d_init_cabac_contexts(UWORD8 u1_slice_type, dec_struct_t * ps_dec); + +void ih264d_insert_pic_in_ref_pic_listx(struct pic_buffer_t *ps_ref_pic_buf_lx, + struct pic_buffer_t *ps_pic) +{ + *ps_ref_pic_buf_lx = *ps_pic; +} + +WORD32 ih264d_mv_pred_ref_tfr_nby2_pmb(dec_struct_t * ps_dec, + UWORD8 u1_mb_idx, + UWORD8 u1_num_mbs) +{ + parse_pmbarams_t * ps_mb_part_info; + parse_part_params_t * ps_part; + mv_pred_t *ps_mv_nmb, *ps_mv_nmb_start, *ps_mv_ntop, *ps_mv_ntop_start; + UWORD32 i, j; + const UWORD32 u1_mbaff = ps_dec->ps_cur_slice->u1_mbaff_frame_flag; + dec_mb_info_t * ps_cur_mb_info; + WORD32 i2_mv_x, i2_mv_y; + WORD32 ret; + + ps_dec->i4_submb_ofst -= (u1_num_mbs - u1_mb_idx) << 4; + ps_mb_part_info = ps_dec->ps_parse_mb_data; // + u1_mb_idx; + ps_part = ps_dec->ps_parse_part_params; // + u1_mb_idx; + + /* N/2 Mb MvPred and Transfer Setup Loop */ + for(i = u1_mb_idx; i < u1_num_mbs; i++, ps_mb_part_info++) + { + UWORD32 u1_colz; + UWORD32 u1_field; + mv_pred_t s_mvPred; + mv_pred_t *ps_mv_pred = &s_mvPred; + + + + *ps_mv_pred = ps_dec->s_default_mv_pred; + + ps_dec->i4_submb_ofst += SUB_BLK_SIZE; + + /* Restore the slice scratch MbX and MbY context */ + ps_cur_mb_info = ps_dec->ps_nmb_info + i; + u1_field = ps_cur_mb_info->u1_mb_field_decodingflag; + + + + ps_mv_nmb_start = ps_dec->ps_mv_cur + (i << 4); + ps_dec->u2_mbx = ps_cur_mb_info->u2_mbx; + ps_dec->u2_mby = ps_cur_mb_info->u2_mby; + ps_dec->u2_mv_2mb[i & 0x1] = 0; + + /* Look for MV Prediction and Reference Transfer in Non-I Mbs */ + if(!ps_mb_part_info->u1_isI_mb) + { + UWORD32 u1_blk_no; + WORD32 i1_ref_idx, i1_ref_idx1; + UWORD32 u1_sub_mb_x, u1_sub_mb_y, u1_sub_mb_num; + UWORD32 u1_num_part, u1_num_ref, u1_wd, u1_ht; + UWORD32 *pu4_wt_offst, **ppu4_wt_ofst; + UWORD32 u1_scale_ref, u4_bot_mb; + WORD8 *pi1_ref_idx = ps_mb_part_info->i1_ref_idx[0]; + pic_buffer_t *ps_ref_frame, **pps_ref_frame; + deblk_mb_t * ps_cur_deblk_mb = ps_dec->ps_deblk_mbn + i; + + /* MB Level initialisations */ + ps_dec->u4_num_pmbair = i >> u1_mbaff; + ps_dec->u1_mb_idx_mv = i; + ppu4_wt_ofst = ps_mb_part_info->pu4_wt_offst; + pps_ref_frame = ps_dec->ps_ref_pic_buf_lx[0]; + /* CHANGED CODE */ + ps_mv_ntop_start = ps_mv_nmb_start + - (ps_dec->u2_frm_wd_in_mbs << (4 + u1_mbaff)) + 12; + + u1_num_part = ps_mb_part_info->u1_num_part; + ps_cur_deblk_mb->u1_mb_type |= (u1_num_part > 1) << 1; + ps_cur_mb_info->u4_pred_info_pkd_idx = ps_dec->u4_pred_info_pkd_idx; + ps_cur_mb_info->u1_num_pred_parts = 0; + + + /****************************************************/ + /* weighted u4_ofst pointer calculations, this loop */ + /* runs maximum 4 times, even in direct cases */ + /****************************************************/ + u1_scale_ref = u1_mbaff & u1_field; + + u4_bot_mb = 1 - ps_cur_mb_info->u1_topmb; + if(ps_dec->ps_cur_pps->u1_wted_pred_flag) + { + u1_num_ref = MIN(u1_num_part, 4); + for(u1_blk_no = 0; u1_blk_no < u1_num_ref; u1_blk_no++) + { + i1_ref_idx = pi1_ref_idx[u1_blk_no]; + if(u1_scale_ref) + i1_ref_idx >>= 1; + pu4_wt_offst = (UWORD32*)&ps_dec->pu4_wt_ofsts[2 + * X3(i1_ref_idx)]; + ppu4_wt_ofst[u1_blk_no] = pu4_wt_offst; + } + } + else + { + ppu4_wt_ofst[0] = NULL; + ppu4_wt_ofst[1] = NULL; + ppu4_wt_ofst[2] = NULL; + ppu4_wt_ofst[3] = NULL; + } + + /**************************************************/ + /* Loop on Partitions */ + /**************************************************/ + for(j = 0; j < u1_num_part; j++, ps_part++) + { + + u1_sub_mb_num = ps_part->u1_sub_mb_num; + ps_dec->u1_sub_mb_num = u1_sub_mb_num; + + if(PART_NOT_DIRECT != ps_part->u1_is_direct) + { + /* Mb Skip Mode */ + /* Setting the default and other members of MvPred Structure */ + s_mvPred.i2_mv[2] = -1; + s_mvPred.i2_mv[3] = -1; + s_mvPred.i1_ref_frame[0] = 0; + i1_ref_idx = (u1_scale_ref && u4_bot_mb) ? MAX_REF_BUFS : 0; + ps_ref_frame = pps_ref_frame[i1_ref_idx]; + s_mvPred.u1_col_ref_pic_idx = ps_ref_frame->u1_mv_buf_id; + s_mvPred.u1_pic_type = ps_ref_frame->u1_pic_type; + pu4_wt_offst = (UWORD32*)&ps_dec->pu4_wt_ofsts[0]; + + ps_dec->pf_mvpred(ps_dec, ps_cur_mb_info, ps_mv_nmb_start, + ps_mv_ntop_start, &s_mvPred, 0, 4, 0, 1, + MB_SKIP); + + + + + + + { + pred_info_pkd_t *ps_pred_pkd; + ps_pred_pkd = ps_dec->ps_pred_pkd + ps_dec->u4_pred_info_pkd_idx; + ih264d_fill_pred_info (s_mvPred.i2_mv,4,4,0,PRED_L0,ps_pred_pkd,ps_ref_frame->u1_pic_buf_id, + (i1_ref_idx >> u1_scale_ref),pu4_wt_offst, + ps_ref_frame->u1_pic_type); + + + ps_dec->u4_pred_info_pkd_idx++; + ps_cur_mb_info->u1_num_pred_parts++; + } + + + + /* Storing colocated zero information */ + u1_colz = ((ABS(s_mvPred.i2_mv[0]) <= 1) + && (ABS(s_mvPred.i2_mv[1]) <= 1)) + + (u1_field << 1); + + ih264d_rep_mv_colz(ps_dec, &s_mvPred, ps_mv_nmb_start, 0, + u1_colz, 4, 4); + } + else + { + u1_sub_mb_x = u1_sub_mb_num & 0x03; + u1_sub_mb_y = u1_sub_mb_num >> 2; + u1_blk_no = + (u1_num_part < 4) ? + j : + (((u1_sub_mb_y >> 1) << 1) + + (u1_sub_mb_x + >> 1)); + + ps_mv_ntop = ps_mv_ntop_start + u1_sub_mb_x; + ps_mv_nmb = ps_mv_nmb_start + u1_sub_mb_num; + + u1_wd = ps_part->u1_partwidth; + u1_ht = ps_part->u1_partheight; + + /* Populate the colpic info and reference frames */ + i1_ref_idx = pi1_ref_idx[u1_blk_no]; + s_mvPred.i1_ref_frame[0] = i1_ref_idx; + + /********************************************************/ + /* Predict Mv */ + /* Add Mv Residuals and store back */ + /********************************************************/ + ps_dec->pf_mvpred(ps_dec, ps_cur_mb_info, ps_mv_nmb, ps_mv_ntop, + &s_mvPred, u1_sub_mb_num, u1_wd, 0, 1, + ps_cur_mb_info->u1_mb_mc_mode); + i2_mv_x = ps_mv_nmb->i2_mv[0]; + i2_mv_y = ps_mv_nmb->i2_mv[1]; + i2_mv_x += s_mvPred.i2_mv[0]; + i2_mv_y += s_mvPred.i2_mv[1]; + s_mvPred.i2_mv[0] = i2_mv_x; + s_mvPred.i2_mv[1] = i2_mv_y; + + /********************************************************/ + /* Transfer setup call */ + /* convert RefIdx if it is MbAff */ + /* Pass Weight Offset and refFrame */ + /********************************************************/ + i1_ref_idx1 = i1_ref_idx >> u1_scale_ref; + if(u1_scale_ref && ((i1_ref_idx & 0x01) != u4_bot_mb)) + i1_ref_idx1 += MAX_REF_BUFS; + ps_ref_frame = pps_ref_frame[i1_ref_idx1]; + pu4_wt_offst = ppu4_wt_ofst[u1_blk_no]; + + + + + + + { + pred_info_pkd_t *ps_pred_pkd; + ps_pred_pkd = ps_dec->ps_pred_pkd + ps_dec->u4_pred_info_pkd_idx; + ih264d_fill_pred_info (s_mvPred.i2_mv,u1_wd,u1_ht,u1_sub_mb_num,PRED_L0,ps_pred_pkd, + ps_ref_frame->u1_pic_buf_id,(i1_ref_idx >> u1_scale_ref),pu4_wt_offst, + ps_ref_frame->u1_pic_type); + + ps_dec->u4_pred_info_pkd_idx++; + ps_cur_mb_info->u1_num_pred_parts++; + } + + + + /* Fill colocated info in MvPred structure */ + s_mvPred.u1_col_ref_pic_idx = ps_ref_frame->u1_mv_buf_id; + s_mvPred.u1_pic_type = ps_ref_frame->u1_pic_type; + + /* Calculating colocated zero information */ + u1_colz = + (u1_field << 1) + | ((i1_ref_idx == 0) + && (ABS(i2_mv_x) + <= 1) + && (ABS(i2_mv_y) + <= 1)); + u1_colz |= ps_mb_part_info->u1_col_info[u1_blk_no]; + + /* Replicate the motion vectors and colzero u4_flag */ + /* for all sub-partitions */ + + ih264d_rep_mv_colz(ps_dec, &s_mvPred, ps_mv_nmb, + u1_sub_mb_num, u1_colz, u1_ht, + u1_wd); + } + } + + } + else + { + /* Storing colocated zero information */ + ih264d_rep_mv_colz(ps_dec, &s_mvPred, ps_mv_nmb_start, 0, + (UWORD8)(u1_field << 1), 4, 4); + + } + /*if num _cores is set to 3,compute bs will be done in another thread*/ + if(ps_dec->u4_num_cores < 3) + { + + if(ps_dec->u4_app_disable_deblk_frm == 0) + ps_dec->pf_compute_bs(ps_dec, ps_cur_mb_info, + (UWORD16)(i >> u1_mbaff)); + } + } + + + + return OK; +} + + +WORD32 ih264d_decode_recon_tfr_nmb(dec_struct_t * ps_dec, + UWORD8 u1_mb_idx, + UWORD8 u1_num_mbs, + UWORD8 u1_num_mbs_next, + UWORD8 u1_tfr_n_mb, + UWORD8 u1_end_of_row) +{ + WORD32 i,j; + UWORD32 u1_end_of_row_next; + dec_mb_info_t * ps_cur_mb_info; + UWORD32 u4_update_mbaff = 0; + WORD32 ret; + const UWORD32 u1_mbaff = ps_dec->ps_cur_slice->u1_mbaff_frame_flag; + const UWORD32 u1_slice_type = ps_dec->ps_cur_slice->u1_slice_type; + const WORD32 u1_skip_th = ( + (u1_slice_type != I_SLICE) ? + (ps_dec->u1_B ? B_8x8 : PRED_8x8R0) : -1); + const UWORD32 u1_ipcm_th = ( + (u1_slice_type != I_SLICE) ? (ps_dec->u1_B ? 23 : 5) : 0); + + + + + + /* N Mb MC Loop */ + for(i = u1_mb_idx; i < u1_num_mbs; i++) + { + ps_cur_mb_info = ps_dec->ps_nmb_info + i; + ps_dec->u4_dma_buf_idx = 0; + ps_dec->u4_pred_info_idx = 0; + + if(ps_cur_mb_info->u1_mb_type <= u1_skip_th) + { + { + WORD32 pred_cnt = 0; + pred_info_pkd_t *ps_pred_pkd; + UWORD32 u4_pred_info_pkd_idx; + WORD8 i1_pred; + + u4_pred_info_pkd_idx = ps_cur_mb_info->u4_pred_info_pkd_idx; + + while(pred_cnt < ps_cur_mb_info->u1_num_pred_parts) + { + + ps_pred_pkd = ps_dec->ps_pred_pkd + u4_pred_info_pkd_idx; + + ps_dec->p_form_mb_part_info(ps_pred_pkd,ps_dec, + ps_cur_mb_info->u2_mbx,ps_cur_mb_info->u2_mby,(i >> u1_mbaff), + ps_cur_mb_info); + u4_pred_info_pkd_idx++; + pred_cnt++; + } + } + + ps_dec->p_motion_compensate(ps_dec, ps_cur_mb_info); + + } + else if(ps_cur_mb_info->u1_mb_type == MB_SKIP) + { + { + WORD32 pred_cnt = 0; + pred_info_pkd_t *ps_pred_pkd; + UWORD32 u4_pred_info_pkd_idx; + WORD8 i1_pred; + + u4_pred_info_pkd_idx = ps_cur_mb_info->u4_pred_info_pkd_idx; + + while(pred_cnt < ps_cur_mb_info->u1_num_pred_parts) + { + + ps_pred_pkd = ps_dec->ps_pred_pkd + u4_pred_info_pkd_idx; + + ps_dec->p_form_mb_part_info(ps_pred_pkd,ps_dec, + ps_cur_mb_info->u2_mbx,ps_cur_mb_info->u2_mby,(i >> u1_mbaff), + ps_cur_mb_info); + + u4_pred_info_pkd_idx++; + pred_cnt++; + } + } + /* Decode MB skip */ + ps_dec->p_motion_compensate(ps_dec, ps_cur_mb_info); + + } + + } + + + /* N Mb IQ IT RECON Loop */ + for(j = u1_mb_idx; j < i; j++) + { + ps_cur_mb_info = ps_dec->ps_nmb_info + j; + + if(ps_cur_mb_info->u1_mb_type <= u1_skip_th) + { + ih264d_process_inter_mb(ps_dec, ps_cur_mb_info, j); + + } + else if(ps_cur_mb_info->u1_mb_type != MB_SKIP) + { + if((u1_ipcm_th + 25) != ps_cur_mb_info->u1_mb_type) + { + ps_cur_mb_info->u1_mb_type -= (u1_skip_th + 1); + ih264d_process_intra_mb(ps_dec, ps_cur_mb_info, j); + } + } + + + if(ps_dec->u4_use_intrapred_line_copy) + { + ih264d_copy_intra_pred_line(ps_dec, ps_cur_mb_info, j); + } + + } + + /*N MB deblocking*/ + if(ps_dec->u4_nmb_deblk == 1) + { + + UWORD32 u4_cur_mb, u4_right_mb; + UWORD32 u4_mb_x, u4_mb_y; + UWORD32 u4_wd_y, u4_wd_uv; + tfr_ctxt_t *ps_tfr_cxt = &(ps_dec->s_tran_addrecon); + UWORD8 u1_field_pic_flag = ps_dec->ps_cur_slice->u1_field_pic_flag; + const WORD32 i4_cb_qp_idx_ofst = + ps_dec->ps_cur_pps->i1_chroma_qp_index_offset; + const WORD32 i4_cr_qp_idx_ofst = + ps_dec->ps_cur_pps->i1_second_chroma_qp_index_offset; + + u4_wd_y = ps_dec->u2_frm_wd_y << u1_field_pic_flag; + u4_wd_uv = ps_dec->u2_frm_wd_uv << u1_field_pic_flag; + + + ps_cur_mb_info = ps_dec->ps_nmb_info + u1_mb_idx; + + ps_dec->u4_deblk_mb_x = ps_cur_mb_info->u2_mbx; + ps_dec->u4_deblk_mb_y = ps_cur_mb_info->u2_mby; + + for(j = u1_mb_idx; j < i; j++) + { + + ih264d_deblock_mb_nonmbaff(ps_dec, ps_tfr_cxt, + i4_cb_qp_idx_ofst, i4_cr_qp_idx_ofst, + u4_wd_y, u4_wd_uv); + + + } + + + + } + + + + if(u1_tfr_n_mb) + { + /****************************************************************/ + /* Check for End Of Row in Next iteration */ + /****************************************************************/ + u1_end_of_row_next = + u1_num_mbs_next + && (u1_num_mbs_next + <= (ps_dec->u1_recon_mb_grp + >> u1_mbaff)); + + /****************************************************************/ + /* Transfer the Following things */ + /* N-Mb DeblkParams Data ( To Ext DeblkParams Buffer ) */ + /* N-Mb Recon Data ( To Ext Frame Buffer ) */ + /* N-Mb Intrapredline Data ( Updated Internally) */ + /* N-Mb MV Data ( To Ext MV Buffer ) */ + /* N-Mb MVTop/TopRight Data ( To Int MV Top Scratch Buffers) */ + /****************************************************************/ + ih264d_transfer_mb_group_data(ps_dec, u1_num_mbs, u1_end_of_row, + u1_end_of_row_next); + ps_dec->u4_num_mbs_prev_nmb = u1_num_mbs; + + ps_dec->u4_pred_info_idx = 0; + ps_dec->u4_dma_buf_idx = 0; + + + } + return OK; +} + +/*! + ************************************************************************** + * \if Function name : ih264d_process_inter_mb \endif + * + * \brief + * This function decodes an Inter MB. + * + * + * \return + * 0 on Success and Error code otherwise + ************************************************************************** + */ +WORD32 ih264d_process_inter_mb(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info, + UWORD8 u1_mb_num) +{ + /* CHANGED CODE */ + UWORD8 *pu1_rec_y, *pu1_rec_u, *pu1_rec_v; + + /*CHANGED CODE */ + UWORD32 ui_rec_width, u4_recwidth_cr; + WORD16 *pi2_y_coeff; + UWORD32 u1_mb_field_decoding_flag; + const UWORD8 u1_mbaff = ps_dec->ps_cur_slice->u1_mbaff_frame_flag; + UWORD32 uc_botMb; + UWORD32 u4_num_pmbair; + /* CHANGED CODE */ + tfr_ctxt_t *ps_frame_buf = ps_dec->ps_frame_buf_ip_recon; + UWORD32 u4_luma_dc_only_csbp = 0; + UWORD32 u4_luma_dc_only_cbp = 0; + /* CHANGED CODE */ + + uc_botMb = 1 - ps_cur_mb_info->u1_topmb; + u4_num_pmbair = (u1_mb_num >> u1_mbaff); + u1_mb_field_decoding_flag = ps_cur_mb_info->u1_mb_field_decodingflag; + + + /* CHANGED CODE */ + pu1_rec_y = ps_frame_buf->pu1_dest_y + (u4_num_pmbair << 4); + pu1_rec_u = + ps_frame_buf->pu1_dest_u + + (u4_num_pmbair << 3) * YUV420SP_FACTOR; + pu1_rec_v = ps_frame_buf->pu1_dest_v + (u4_num_pmbair << 3); + ui_rec_width = ps_dec->u2_frm_wd_y << u1_mb_field_decoding_flag; + u4_recwidth_cr = ps_dec->u2_frm_wd_uv << u1_mb_field_decoding_flag; + + /* CHANGED CODE */ + + if(u1_mbaff) + { + if(uc_botMb) + { + pu1_rec_y += (u1_mb_field_decoding_flag ? + (ui_rec_width >> 1) : (ui_rec_width << 4)); + pu1_rec_u += (u1_mb_field_decoding_flag ? + (u4_recwidth_cr >> 1) : (u4_recwidth_cr << 3)); + pu1_rec_v += (u1_mb_field_decoding_flag ? + (u4_recwidth_cr >> 1) : (u4_recwidth_cr << 3)); + } + } + + if(!ps_cur_mb_info->u1_tran_form8x8) + { + u4_luma_dc_only_csbp = ih264d_unpack_luma_coeff4x4_mb(ps_dec, + ps_cur_mb_info, + 0); + } + else + { + if(!ps_dec->ps_cur_pps->u1_entropy_coding_mode) + { + u4_luma_dc_only_cbp = ih264d_unpack_luma_coeff4x4_mb(ps_dec, + ps_cur_mb_info, + 0); + } + else + { + u4_luma_dc_only_cbp = ih264d_unpack_luma_coeff8x8_mb(ps_dec, + ps_cur_mb_info); + } + } + + pi2_y_coeff = ps_dec->pi2_coeff_data; + /* Inverse Transform and Reconstruction */ + if(ps_cur_mb_info->u1_cbp & 0x0f) + { + /* CHANGED CODE */ + if(!ps_cur_mb_info->u1_tran_form8x8) + { + UWORD32 i; + WORD16 ai2_tmp[16]; + for(i = 0; i < 16; i++) + { + if(CHECKBIT(ps_cur_mb_info->u2_luma_csbp, i)) + { + WORD16 *pi2_level = pi2_y_coeff + (i << 4); + UWORD8 *pu1_pred_sblk = pu1_rec_y + ((i & 0x3) * BLK_SIZE) + + (i >> 2) * (ui_rec_width << 2); + PROFILE_DISABLE_IQ_IT_RECON() + { + if(CHECKBIT(u4_luma_dc_only_csbp, i)) + { + ps_dec->pf_iquant_itrans_recon_luma_4x4_dc( + pi2_level, + pu1_pred_sblk, + pu1_pred_sblk, + ui_rec_width, + ui_rec_width, + gau2_ih264_iquant_scale_4x4[ps_cur_mb_info->u1_qp_rem6], + (UWORD16 *)ps_dec->s_high_profile.i2_scalinglist4x4[3], + ps_cur_mb_info->u1_qp_div6, ai2_tmp, 0, + NULL); + } + else + { + ps_dec->pf_iquant_itrans_recon_luma_4x4( + pi2_level, + pu1_pred_sblk, + pu1_pred_sblk, + ui_rec_width, + ui_rec_width, + gau2_ih264_iquant_scale_4x4[ps_cur_mb_info->u1_qp_rem6], + (UWORD16 *)ps_dec->s_high_profile.i2_scalinglist4x4[3], + ps_cur_mb_info->u1_qp_div6, ai2_tmp, 0, + NULL); + } + } + } + } + } + else + { + WORD16 *pi2_scale_matrix_ptr; + WORD32 i; + + pi2_scale_matrix_ptr = + ps_dec->s_high_profile.i2_scalinglist8x8[1]; + + for(i = 0; i < 4; i++) + { + WORD16 ai2_tmp[64]; + WORD16 *pi16_levelBlock = pi2_y_coeff + (i << 6); /* move to the next 8x8 adding 64 */ + + UWORD8 *pu1_pred_sblk = pu1_rec_y + ((i & 0x1) * BLK8x8SIZE) + + (i >> 1) * (ui_rec_width << 3); + if(CHECKBIT(ps_cur_mb_info->u1_cbp, i)) + { + PROFILE_DISABLE_IQ_IT_RECON() + { + if(CHECKBIT(u4_luma_dc_only_cbp, i)) + { + ps_dec->pf_iquant_itrans_recon_luma_8x8_dc( + pi16_levelBlock, + pu1_pred_sblk, + pu1_pred_sblk, + ui_rec_width, + ui_rec_width, + gau1_ih264d_dequant8x8_cavlc[ps_cur_mb_info->u1_qp_rem6], + (UWORD16 *)pi2_scale_matrix_ptr, + ps_cur_mb_info->u1_qp_div6, ai2_tmp, 0, + NULL); + } + else + { + ps_dec->pf_iquant_itrans_recon_luma_8x8( + pi16_levelBlock, + pu1_pred_sblk, + pu1_pred_sblk, + ui_rec_width, + ui_rec_width, + gau1_ih264d_dequant8x8_cavlc[ps_cur_mb_info->u1_qp_rem6], + (UWORD16 *)pi2_scale_matrix_ptr, + ps_cur_mb_info->u1_qp_div6, ai2_tmp, 0, + NULL); + } + } + } + } + + } + } + + /* Decode Chroma Block */ + ih264d_unpack_chroma_coeff4x4_mb(ps_dec, + ps_cur_mb_info); + /*--------------------------------------------------------------------*/ + /* Chroma Blocks decoding */ + /*--------------------------------------------------------------------*/ + { + UWORD8 u1_chroma_cbp = (UWORD8)(ps_cur_mb_info->u1_cbp >> 4); + + if(u1_chroma_cbp != CBPC_ALLZERO) + { + UWORD32 u4_scale_u = ps_cur_mb_info->u1_qpc_div6; + UWORD32 u4_scale_v = ps_cur_mb_info->u1_qpcr_div6; + UWORD16 u2_chroma_csbp = ps_cur_mb_info->u2_chroma_csbp; + + pi2_y_coeff = ps_dec->pi2_coeff_data; + + { + UWORD32 i; + WORD16 ai2_tmp[16]; + for(i = 0; i < 4; i++) + { + WORD16 *pi2_level = pi2_y_coeff + (i << 4); + UWORD8 *pu1_pred_sblk = pu1_rec_u + + ((i & 0x1) * BLK_SIZE * YUV420SP_FACTOR) + + (i >> 1) * (u4_recwidth_cr << 2); + PROFILE_DISABLE_IQ_IT_RECON() + { + if(CHECKBIT(u2_chroma_csbp, i)) + { + ps_dec->pf_iquant_itrans_recon_chroma_4x4( + pi2_level, + pu1_pred_sblk, + pu1_pred_sblk, + u4_recwidth_cr, + u4_recwidth_cr, + gau2_ih264_iquant_scale_4x4[ps_cur_mb_info->u1_qpc_rem6], + (UWORD16 *)ps_dec->s_high_profile.i2_scalinglist4x4[4], + u4_scale_u, ai2_tmp, pi2_level); + } + else if(pi2_level[0] != 0) + { + ps_dec->pf_iquant_itrans_recon_chroma_4x4_dc( + pi2_level, + pu1_pred_sblk, + pu1_pred_sblk, + u4_recwidth_cr, + u4_recwidth_cr, + gau2_ih264_iquant_scale_4x4[ps_cur_mb_info->u1_qpc_rem6], + (UWORD16 *)ps_dec->s_high_profile.i2_scalinglist4x4[4], + u4_scale_u, ai2_tmp, pi2_level); + } + } + } + } + + pi2_y_coeff += MB_CHROM_SIZE; + u2_chroma_csbp >>= 4; + + { + UWORD32 i; + WORD16 ai2_tmp[16]; + for(i = 0; i < 4; i++) + { + WORD16 *pi2_level = pi2_y_coeff + (i << 4); + UWORD8 *pu1_pred_sblk = pu1_rec_u + 1 + + ((i & 0x1) * BLK_SIZE * YUV420SP_FACTOR) + + (i >> 1) * (u4_recwidth_cr << 2); + PROFILE_DISABLE_IQ_IT_RECON() + { + if(CHECKBIT(u2_chroma_csbp, i)) + { + ps_dec->pf_iquant_itrans_recon_chroma_4x4( + pi2_level, + pu1_pred_sblk, + pu1_pred_sblk, + u4_recwidth_cr, + u4_recwidth_cr, + gau2_ih264_iquant_scale_4x4[ps_cur_mb_info->u1_qpcr_rem6], + (UWORD16 *)ps_dec->s_high_profile.i2_scalinglist4x4[5], + u4_scale_v, ai2_tmp, pi2_level); + } + else if(pi2_level[0] != 0) + { + ps_dec->pf_iquant_itrans_recon_chroma_4x4_dc( + pi2_level, + pu1_pred_sblk, + pu1_pred_sblk, + u4_recwidth_cr, + u4_recwidth_cr, + gau2_ih264_iquant_scale_4x4[ps_cur_mb_info->u1_qpcr_rem6], + (UWORD16 *)ps_dec->s_high_profile.i2_scalinglist4x4[5], + u4_scale_v, ai2_tmp, pi2_level); + } + } + } + } + } + } + return (0); +} + +/*! + ************************************************************************** + * \if Function name : ih264d_parse_pred_weight_table \endif + * + * \brief + * Implements pred_weight_table() of 7.3.3.2. + * + * \return + * None + * + ************************************************************************** + */ +WORD32 ih264d_parse_pred_weight_table(dec_slice_params_t * ps_cur_slice, + dec_bit_stream_t * ps_bitstrm) +{ + UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; + UWORD32 *pu4_bitstrm_ofst = &ps_bitstrm->u4_ofst; + WORD8 i, cont, lx; + UWORD8 uc_weight_flag; + UWORD32 *pui32_weight_offset_lx; + WORD16 c_weight, c_offset; + UWORD32 ui32_y_def_weight_ofst, ui32_cr_def_weight_ofst; + UWORD32 ui32_temp; + UWORD8 uc_luma_log2_weight_denom; + UWORD8 uc_chroma_log2_weight_denom; + + /* Variables for error resilience checks */ + UWORD32 u4_temp; + WORD32 i_temp; + + u4_temp = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); + if(u4_temp > MAX_LOG2_WEIGHT_DENOM) + { + return ERROR_PRED_WEIGHT_TABLE_T; + } + uc_luma_log2_weight_denom = u4_temp; + COPYTHECONTEXT("SH: luma_log2_weight_denom",uc_luma_log2_weight_denom); + ui32_y_def_weight_ofst = (1 << uc_luma_log2_weight_denom); + + u4_temp = ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); + if(u4_temp > MAX_LOG2_WEIGHT_DENOM) + { + return ERROR_PRED_WEIGHT_TABLE_T; + } + uc_chroma_log2_weight_denom = u4_temp; + COPYTHECONTEXT("SH: chroma_log2_weight_denom",uc_chroma_log2_weight_denom); + ui32_cr_def_weight_ofst = (1 << uc_chroma_log2_weight_denom); + + ps_cur_slice->u2_log2Y_crwd = uc_luma_log2_weight_denom + | (uc_chroma_log2_weight_denom << 8); + + cont = (ps_cur_slice->u1_slice_type == B_SLICE); + lx = 0; + do + { + for(i = 0; i < ps_cur_slice->u1_num_ref_idx_lx_active[lx]; i++) + { + pui32_weight_offset_lx = ps_cur_slice->u4_wt_ofst_lx[lx][i]; + + uc_weight_flag = ih264d_get_bit_h264(ps_bitstrm); + pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; + COPYTHECONTEXT("SH: luma_weight_l0_flag",uc_weight_flag); + if(uc_weight_flag) + { + i_temp = ih264d_sev(pu4_bitstrm_ofst, + pu4_bitstrm_buf); + if((i_temp < PRED_WEIGHT_MIN) || (i_temp > PRED_WEIGHT_MAX)) + return ERROR_PRED_WEIGHT_TABLE_T; + c_weight = i_temp; + COPYTHECONTEXT("SH: luma_weight_l0",c_weight); + + i_temp = ih264d_sev(pu4_bitstrm_ofst, + pu4_bitstrm_buf); + if((i_temp < PRED_WEIGHT_MIN) || (i_temp > PRED_WEIGHT_MAX)) + return ERROR_PRED_WEIGHT_TABLE_T; + c_offset = i_temp; + COPYTHECONTEXT("SH: luma_offset_l0",c_offset); + + ui32_temp = (c_offset << 16) | (c_weight & 0xFFFF); + pui32_weight_offset_lx[0] = ui32_temp; + } + else + { + + pui32_weight_offset_lx[0] = ui32_y_def_weight_ofst; + } + + { + WORD8 c_weightCb, c_weightCr, c_offsetCb, c_offsetCr; + uc_weight_flag = ih264d_get_bit_h264(ps_bitstrm); + pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; + COPYTHECONTEXT("SH: chroma_weight_l0_flag",uc_weight_flag); + if(uc_weight_flag) + { + i_temp = ih264d_sev(pu4_bitstrm_ofst, + pu4_bitstrm_buf); + if((i_temp < PRED_WEIGHT_MIN) || (i_temp > PRED_WEIGHT_MAX)) + return ERROR_PRED_WEIGHT_TABLE_T; + c_weightCb = i_temp; + COPYTHECONTEXT("SH: chroma_weight_l0",c_weightCb); + + i_temp = ih264d_sev(pu4_bitstrm_ofst, + pu4_bitstrm_buf); + if((i_temp < PRED_WEIGHT_MIN) || (i_temp > PRED_WEIGHT_MAX)) + return ERROR_PRED_WEIGHT_TABLE_T; + c_offsetCb = i_temp; + COPYTHECONTEXT("SH: chroma_weight_l0",c_offsetCb); + + ui32_temp = (c_offsetCb << 16) | (c_weightCb & 0xFFFF); + pui32_weight_offset_lx[1] = ui32_temp; + + i_temp = ih264d_sev(pu4_bitstrm_ofst, + pu4_bitstrm_buf); + if((i_temp < PRED_WEIGHT_MIN) || (i_temp > PRED_WEIGHT_MAX)) + return ERROR_PRED_WEIGHT_TABLE_T; + c_weightCr = i_temp; + COPYTHECONTEXT("SH: chroma_weight_l0",c_weightCr); + + i_temp = ih264d_sev(pu4_bitstrm_ofst, + pu4_bitstrm_buf); + if((i_temp < PRED_WEIGHT_MIN) || (i_temp > PRED_WEIGHT_MAX)) + return ERROR_PRED_WEIGHT_TABLE_T; + c_offsetCr = i_temp; + COPYTHECONTEXT("SH: chroma_weight_l0",c_offsetCr); + + ui32_temp = (c_offsetCr << 16) | (c_weightCr & 0xFFFF); + pui32_weight_offset_lx[2] = ui32_temp; + } + else + { + pui32_weight_offset_lx[1] = ui32_cr_def_weight_ofst; + pui32_weight_offset_lx[2] = ui32_cr_def_weight_ofst; + } + } + } + lx++; + } + while(cont--); + + return OK; +} + +static int pic_num_compare(const void *pv_pic1, const void *pv_pic2) +{ + struct pic_buffer_t *ps_pic1 = *(struct pic_buffer_t **) pv_pic1; + struct pic_buffer_t *ps_pic2 = *(struct pic_buffer_t **) pv_pic2; + if (ps_pic1->i4_pic_num < ps_pic2->i4_pic_num) + { + return -1; + } + else if (ps_pic1->i4_pic_num > ps_pic2->i4_pic_num) + { + return 1; + } + else + { + return 0; + } +} +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_init_ref_idx_lx_p */ +/* */ +/* Description : This function initializes the reference picture L0 list */ +/* for P slices as per section 8.2.4.2.1 and 8.2.4.2.2. */ +/* */ +/* Inputs : pointer to ps_dec struture */ +/* Globals : NO */ +/* Processing : arranges all the short term pictures according to */ +/* pic_num in descending order starting from curr pic_num. */ +/* and inserts it in L0 list followed by all Long term */ +/* pictures in ascending order. */ +/* */ +/* Returns : void */ +/* */ +/* Issues : <List any issues or problems with this function> */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 13 07 2002 Jay Draft */ +/* */ +/*****************************************************************************/ +void ih264d_init_ref_idx_lx_p(dec_struct_t *ps_dec) +{ + struct pic_buffer_t *ps_ref_pic_buf_lx; + dpb_manager_t *ps_dpb_mgr; + struct dpb_info_t *ps_next_dpb; + WORD8 i, j; + UWORD8 u1_max_lt_index, u1_min_lt_index; + UWORD32 u4_lt_index; + UWORD8 u1_field_pic_flag; + dec_slice_params_t *ps_cur_slice; + UWORD8 u1_L0; + WORD32 i4_cur_pic_num, i4_min_st_pic_num; + WORD32 i4_temp_pic_num, i4_ref_pic_num; + UWORD8 u1_num_short_term_bufs; + UWORD8 u1_max_ref_idx_l0; + struct pic_buffer_t *aps_st_pic_bufs[2 * MAX_REF_BUFS] = {NULL}; + + ps_cur_slice = ps_dec->ps_cur_slice; + u1_field_pic_flag = ps_cur_slice->u1_field_pic_flag; + u1_max_ref_idx_l0 = ps_cur_slice->u1_num_ref_idx_lx_active[0] + << u1_field_pic_flag; + + ps_dpb_mgr = ps_dec->ps_dpb_mgr; + /* Get the current frame number */ + i4_cur_pic_num = ps_dec->ps_cur_pic->i4_pic_num; + + /* Get Min pic_num,MinLt */ + i4_min_st_pic_num = i4_cur_pic_num; + u1_max_lt_index = MAX_REF_BUFS + 1; + u1_min_lt_index = MAX_REF_BUFS + 1; + + /* Start from ST head */ + ps_next_dpb = ps_dpb_mgr->ps_dpb_st_head; + for(i = 0; i < ps_dpb_mgr->u1_num_st_ref_bufs; i++) + { + i4_ref_pic_num = ps_next_dpb->ps_pic_buf->i4_pic_num; + if(i4_ref_pic_num < i4_cur_pic_num) + { + /* RefPic Buf pic_num is before Current pic_num in decode order */ + i4_min_st_pic_num = MIN(i4_min_st_pic_num, i4_ref_pic_num); + } + + /* Chase the next link */ + ps_next_dpb = ps_next_dpb->ps_prev_short; + } + + /* Sort ST ref pocs in ascending order */ + ps_next_dpb = ps_dpb_mgr->ps_dpb_st_head; + for (j = 0; j < ps_dpb_mgr->u1_num_st_ref_bufs; j++) + { + aps_st_pic_bufs[j] = ps_next_dpb->ps_pic_buf; + ps_next_dpb = ps_next_dpb->ps_prev_short; + } + qsort(aps_st_pic_bufs, ps_dpb_mgr->u1_num_st_ref_bufs, + sizeof(aps_st_pic_bufs[0]), pic_num_compare); + + /* Start from LT head */ + ps_next_dpb = ps_dpb_mgr->ps_dpb_ht_head; + if(ps_next_dpb) + { + u1_max_lt_index = ps_next_dpb->u1_lt_idx; + u1_min_lt_index = ps_next_dpb->u1_lt_idx; + + for(i = 0; i < ps_dpb_mgr->u1_num_lt_ref_bufs; i++) + { + u4_lt_index = ps_next_dpb->u1_lt_idx; + u1_max_lt_index = (UWORD8)(MAX(u1_max_lt_index, u4_lt_index)); + u1_min_lt_index = (UWORD8)(MIN(u1_min_lt_index, u4_lt_index)); + + /* Chase the next link */ + ps_next_dpb = ps_next_dpb->ps_prev_long; + } + } + /* 1. Initialize refIdxL0 */ + u1_L0 = 0; + if(u1_field_pic_flag) + { + ps_ref_pic_buf_lx = ps_dpb_mgr->ps_init_dpb[0][0]; + ps_ref_pic_buf_lx += MAX_REF_BUFS; + i4_temp_pic_num = i4_cur_pic_num; + } + else + { + ps_ref_pic_buf_lx = ps_dpb_mgr->ps_init_dpb[0][0]; + i4_temp_pic_num = i4_cur_pic_num; + } + /* Arrange all short term buffers in output order as given by pic_num */ + /* Arrange pic_num's less than Curr pic_num in the descending pic_num */ + /* order starting from (Curr pic_num - 1) */ + for(j = ps_dpb_mgr->u1_num_st_ref_bufs - 1; j >= 0; j--) + { + if(aps_st_pic_bufs[j]) + { + /* Copy info in pic buffer */ + ih264d_insert_pic_in_ref_pic_listx(ps_ref_pic_buf_lx, + aps_st_pic_bufs[j]); + ps_ref_pic_buf_lx++; + u1_L0++; + } + } + + /* Arrange all Long term buffers in ascending order, in LongtermIndex */ + /* Start from LT head */ + u1_num_short_term_bufs = u1_L0; + for(u4_lt_index = u1_min_lt_index; u4_lt_index <= u1_max_lt_index; + u4_lt_index++) + { + ps_next_dpb = ps_dpb_mgr->ps_dpb_ht_head; + for(i = 0; i < ps_dpb_mgr->u1_num_lt_ref_bufs; i++) + { + if(ps_next_dpb->u1_lt_idx == u4_lt_index) + { + ih264d_insert_pic_in_ref_pic_listx(ps_ref_pic_buf_lx, + ps_next_dpb->ps_pic_buf); + + ps_ref_pic_buf_lx->u1_long_term_pic_num = + ps_ref_pic_buf_lx->u1_long_term_frm_idx; + ps_ref_pic_buf_lx++; + u1_L0++; + break; + } + ps_next_dpb = ps_next_dpb->ps_prev_long; + } + } + + if(u1_field_pic_flag) + { + /* Initialize the rest of the entries in the */ + /* reference list to handle of errors */ + { + UWORD8 u1_i; + pic_buffer_t ref_pic; + + ref_pic = *(ps_dpb_mgr->ps_init_dpb[0][0] + MAX_REF_BUFS); + + if(NULL == ref_pic.pu1_buf1) + { + ref_pic = *ps_dec->ps_cur_pic; + } + for(u1_i = u1_L0; u1_i < u1_max_ref_idx_l0; u1_i++) + { + *ps_ref_pic_buf_lx = ref_pic; + ps_ref_pic_buf_lx++; + } + } + + ih264d_convert_frm_to_fld_list( + ps_dpb_mgr->ps_init_dpb[0][0] + MAX_REF_BUFS, &u1_L0, + ps_dec, u1_num_short_term_bufs); + + ps_ref_pic_buf_lx = ps_dpb_mgr->ps_init_dpb[0][0] + u1_L0; + } + + /* Initialize the rest of the entries in the */ + /* reference list to handle of errors */ + { + UWORD8 u1_i; + pic_buffer_t ref_pic; + + ref_pic = *(ps_dpb_mgr->ps_init_dpb[0][0]); + + if(NULL == ref_pic.pu1_buf1) + { + ref_pic = *ps_dec->ps_cur_pic; + } + for(u1_i = u1_L0; u1_i < u1_max_ref_idx_l0; u1_i++) + { + *ps_ref_pic_buf_lx = ref_pic; + ps_ref_pic_buf_lx++; + } + } + ps_dec->ps_cur_slice->u1_initial_list_size[0] = u1_L0; +} + diff --git a/dependencies/ih264d/decoder/ih264d_process_pslice.h b/dependencies/ih264d/decoder/ih264d_process_pslice.h new file mode 100644 index 00000000..8740eb40 --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_process_pslice.h @@ -0,0 +1,69 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +#ifndef _IH264D_PROCESS_PSLICE_H_ +#define _IH264D_PROCESS_PSLICE_H_ +/*! +************************************************************************** +* \file ih264d_process_pslice.h +* +* \brief +* Contains declarations of routines that decode a P slice type +* +* Detailed_description +* +* \date +* 21/12/2002 +* +* \author NS +************************************************************************** +*/ +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264d_structs.h" +WORD32 ih264d_parse_pslice(dec_struct_t *ps_dec, + UWORD16 u2_first_mb_in_slice); +WORD32 ih264d_parse_pred_weight_table(dec_slice_params_t * ps_cur_slice, + dec_bit_stream_t * ps_bitstrm); + +WORD32 parsePSliceData(dec_struct_t * ps_dec, + dec_slice_params_t * ps_slice, + UWORD16 u2_first_mb_in_slice); + +WORD32 ih264d_process_inter_mb(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info, + UWORD8 u1_mb_num); + +void ih264d_init_ref_idx_lx_p(dec_struct_t *ps_dec); + +WORD32 ih264d_mv_pred_ref_tfr_nby2_pmb(dec_struct_t * ps_dec, + UWORD8 u1_num_mbs, + UWORD8 u1_num_mbsNby2); + +WORD32 ih264d_decode_recon_tfr_nmb(dec_struct_t * ps_dec, + UWORD8 u1_mb_idx, + UWORD8 u1_num_mbs, + UWORD8 u1_num_mbs_next, + UWORD8 u1_tfr_n_mb, + UWORD8 u1_end_of_row); + +void ih264d_insert_pic_in_ref_pic_listx(struct pic_buffer_t *ps_ref_pic_buf_lx, + struct pic_buffer_t *ps_pic); +#endif /* _IH264D_PROCESS_PSLICE_H_ */ diff --git a/dependencies/ih264d/decoder/ih264d_quant_scaling.c b/dependencies/ih264d/decoder/ih264d_quant_scaling.c new file mode 100644 index 00000000..4e5c58d5 --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_quant_scaling.c @@ -0,0 +1,289 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264_defs.h" +#include "ih264d_bitstrm.h" +#include "ih264d_structs.h" +#include "ih264d_parse_cavlc.h" +#include "ih264d_defs.h" +#include "ih264d_defs.h" +#include "ih264d_defs.h" + +#include "ih264d_parse_slice.h" +#include "ih264d_tables.h" +#include "ih264d_utils.h" +#include "ih264d_nal.h" +#include "ih264d_deblocking.h" + +#include "ih264d_mem_request.h" +#include "ih264d_debug.h" + +#include "ih264d_error_handler.h" +#include "ih264d_mb_utils.h" +#include "ih264d_sei.h" +#include "ih264d_vui.h" +#include "ih264d_tables.h" + +#define IDCT_BLOCK_WIDTH8X8 8 + +WORD32 ih264d_scaling_list(WORD16 *pi2_scaling_list, + WORD32 i4_size_of_scalinglist, + UWORD8 *pu1_use_default_scaling_matrix_flag, + dec_bit_stream_t *ps_bitstrm) +{ + WORD32 i4_j, i4_delta_scale, i4_lastScale = 8, i4_nextScale = 8; + UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; + UWORD32 *pu4_bitstrm_ofst = &ps_bitstrm->u4_ofst; + + *pu1_use_default_scaling_matrix_flag = 0; + + for(i4_j = 0; i4_j < i4_size_of_scalinglist; i4_j++) + { + if(i4_nextScale != 0) + { + i4_delta_scale = ih264d_sev(pu4_bitstrm_ofst, + pu4_bitstrm_buf); + + if(i4_delta_scale < MIN_H264_DELTA_SCALE || + i4_delta_scale > MAX_H264_DELTA_SCALE) + { + return ERROR_INV_RANGE_QP_T; + } + i4_nextScale = ((i4_lastScale + i4_delta_scale + 256) & 0xff); + + *pu1_use_default_scaling_matrix_flag = ((i4_j == 0) + && (i4_nextScale == 0)); + + } + pi2_scaling_list[i4_j] = + (i4_nextScale == 0) ? (i4_lastScale) : (i4_nextScale); + i4_lastScale = pi2_scaling_list[i4_j]; + } + return OK; +} + +WORD32 ih264d_form_default_scaling_matrix(dec_struct_t *ps_dec) +{ + + /*************************************************************************/ + /* perform the inverse scanning for the frame and field scaling matrices */ + /*************************************************************************/ + { + UWORD8 *pu1_inv_scan; + WORD32 i4_i, i4_j; + + pu1_inv_scan = (UWORD8 *)gau1_ih264d_inv_scan; + + /* for all 4x4 matrices */ + for(i4_i = 0; i4_i < 6; i4_i++) + { + for(i4_j = 0; i4_j < 16; i4_j++) + { + ps_dec->s_high_profile.i2_scalinglist4x4[i4_i][pu1_inv_scan[i4_j]] = + 16; + + } + } + + /* for all 8x8 matrices */ + for(i4_i = 0; i4_i < 2; i4_i++) + { + for(i4_j = 0; i4_j < 64; i4_j++) + { + ps_dec->s_high_profile.i2_scalinglist8x8[i4_i][gau1_ih264d_inv_scan_prog8x8_cabac[i4_j]] = + 16; + + } + } + } + return OK; +} + +WORD32 ih264d_form_scaling_matrix_picture(dec_seq_params_t *ps_seq, + dec_pic_params_t *ps_pic, + dec_struct_t *ps_dec) +{ + /* default scaling matrices */ + WORD32 i4_i; + + /* check the SPS first */ + if(ps_seq->i4_seq_scaling_matrix_present_flag) + { + for(i4_i = 0; i4_i < 8; i4_i++) + { + if(i4_i < 6) + { + /* fall-back rule A */ + if(!ps_seq->u1_seq_scaling_list_present_flag[i4_i]) + { + if((i4_i == 0) || (i4_i == 3)) + { + ps_dec->s_high_profile.pi2_scale_mat[i4_i] = + (i4_i == 0) ? (WORD16 *)(gai2_ih264d_default_intra4x4) : (WORD16 *)(gai2_ih264d_default_inter4x4); + } + else + { + ps_dec->s_high_profile.pi2_scale_mat[i4_i] = + ps_dec->s_high_profile.pi2_scale_mat[i4_i + - 1]; + } + } + else + { + if(ps_seq->u1_use_default_scaling_matrix_flag[i4_i]) + { + ps_dec->s_high_profile.pi2_scale_mat[i4_i] = + (i4_i < 3) ? (WORD16 *)(gai2_ih264d_default_intra4x4) : (WORD16 *)(gai2_ih264d_default_inter4x4); + } + else + { + ps_dec->s_high_profile.pi2_scale_mat[i4_i] = + ps_seq->i2_scalinglist4x4[i4_i]; + } + } + + } + else + { + /* fall-back rule A */ + if((!ps_seq->u1_seq_scaling_list_present_flag[i4_i]) + || (ps_seq->u1_use_default_scaling_matrix_flag[i4_i])) + { + ps_dec->s_high_profile.pi2_scale_mat[i4_i] = + (i4_i == 6) ? ((WORD16*)gai2_ih264d_default_intra8x8) : ((WORD16*)gai2_ih264d_default_inter8x8); + } + else + { + ps_dec->s_high_profile.pi2_scale_mat[i4_i] = + ps_seq->i2_scalinglist8x8[i4_i - 6]; + } + } + } + } + + /* checking for the PPS */ + + if(ps_pic->i4_pic_scaling_matrix_present_flag) + { + for(i4_i = 0; i4_i < 8; i4_i++) + { + if(i4_i < 6) + { + /* fall back rule B */ + if(!ps_pic->u1_pic_scaling_list_present_flag[i4_i]) + { + if((i4_i == 0) || (i4_i == 3)) + { + if(!ps_seq->i4_seq_scaling_matrix_present_flag) + { + ps_dec->s_high_profile.pi2_scale_mat[i4_i] = + (i4_i == 0) ? (WORD16 *)(gai2_ih264d_default_intra4x4) : (WORD16 *)(gai2_ih264d_default_inter4x4); + } + } + else + { + ps_dec->s_high_profile.pi2_scale_mat[i4_i] = + ps_dec->s_high_profile.pi2_scale_mat[i4_i + - 1]; + } + } + else + { + if(ps_pic->u1_pic_use_default_scaling_matrix_flag[i4_i]) + { + ps_dec->s_high_profile.pi2_scale_mat[i4_i] = + (i4_i < 3) ? (WORD16 *)(gai2_ih264d_default_intra4x4) : (WORD16 *)(gai2_ih264d_default_inter4x4); + } + else + { + ps_dec->s_high_profile.pi2_scale_mat[i4_i] = + ps_pic->i2_pic_scalinglist4x4[i4_i]; + } + } + } + else + { + if(!ps_pic->u1_pic_scaling_list_present_flag[i4_i]) + { + if(!ps_seq->i4_seq_scaling_matrix_present_flag) + { + ps_dec->s_high_profile.pi2_scale_mat[i4_i] = + (i4_i == 6) ? ((WORD16*)gai2_ih264d_default_intra8x8) : ((WORD16*)gai2_ih264d_default_inter8x8); + } + } + else + { + if(ps_pic->u1_pic_use_default_scaling_matrix_flag[i4_i]) + { + ps_dec->s_high_profile.pi2_scale_mat[i4_i] = + (i4_i == 6) ? (WORD16 *)(gai2_ih264d_default_intra8x8) : (WORD16 *)(gai2_ih264d_default_inter8x8); + } + else + { + ps_dec->s_high_profile.pi2_scale_mat[i4_i] = + ps_pic->i2_pic_scalinglist8x8[i4_i - 6]; + } + } + } + } + } + + /*************************************************************************/ + /* perform the inverse scanning for the frame and field scaling matrices */ + /*************************************************************************/ + { + UWORD8 *pu1_inv_scan_4x4; + WORD32 i4_i, i4_j; + + pu1_inv_scan_4x4 = (UWORD8 *)gau1_ih264d_inv_scan; + + /* for all 4x4 matrices */ + for(i4_i = 0; i4_i < 6; i4_i++) + { + if(ps_dec->s_high_profile.pi2_scale_mat[i4_i] == NULL) + return ERROR_CORRUPTED_SLICE; + + for(i4_j = 0; i4_j < 16; i4_j++) + { + ps_dec->s_high_profile.i2_scalinglist4x4[i4_i][pu1_inv_scan_4x4[i4_j]] = + ps_dec->s_high_profile.pi2_scale_mat[i4_i][i4_j]; + + } + } + + /* for all 8x8 matrices */ + for(i4_i = 0; i4_i < 2; i4_i++) + { + if(ps_dec->s_high_profile.pi2_scale_mat[i4_i + 6] == NULL) + return ERROR_CORRUPTED_SLICE; + + for(i4_j = 0; i4_j < 64; i4_j++) + { + ps_dec->s_high_profile.i2_scalinglist8x8[i4_i][gau1_ih264d_inv_scan_prog8x8_cabac[i4_j]] = + ps_dec->s_high_profile.pi2_scale_mat[i4_i + 6][i4_j]; + + } + } + } + return OK; +} + diff --git a/dependencies/ih264d/decoder/ih264d_quant_scaling.h b/dependencies/ih264d/decoder/ih264d_quant_scaling.h new file mode 100644 index 00000000..1a9b7d1d --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_quant_scaling.h @@ -0,0 +1,37 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +#ifndef _IH264D_QUANT_SCALING_H_ +#define _IH264D_QUANT_SCALING_H_ +WORD32 ih264d_scaling_list(WORD16 *pi2_scaling_list, + WORD32 i4_size_of_scalinglist, + UWORD8 *pu1_use_default_scaling_matrix_flag, + dec_bit_stream_t *ps_bitstrm); + + +WORD32 ih264d_form_scaling_matrix_picture(dec_seq_params_t *ps_seq, + dec_pic_params_t *ps_pic, + dec_struct_t *ps_dec); + +WORD32 ih264d_form_default_scaling_matrix(dec_struct_t *ps_dec); + + + + +#endif diff --git a/dependencies/ih264d/decoder/ih264d_sei.c b/dependencies/ih264d/decoder/ih264d_sei.c new file mode 100644 index 00000000..ac4d056b --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_sei.c @@ -0,0 +1,987 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ + +/*****************************************************************************/ +/* */ +/* File Name : ih264d_sei.c */ +/* */ +/* Description : This file contains routines to parse SEI NAL's */ +/* */ +/* List of Functions : <List the functions defined in this file> */ +/* */ +/* Issues / Problems : None */ +/* */ +/* Revision History : */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 25 05 2005 NS Draft */ +/* */ +/*****************************************************************************/ + +#include <string.h> + +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264d_bitstrm.h" +#include "ih264d_structs.h" +#include "ih264d_error_handler.h" +#include "ih264d_vui.h" +#include "ih264d_parse_cavlc.h" +#include "ih264d_defs.h" + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_parse_buffering_period */ +/* */ +/* Description : This function parses SEI message buffering_period */ +/* Inputs : ps_buf_prd pointer to struct buf_period_t */ +/* ps_bitstrm Bitstream */ +/* Globals : None */ +/* Processing : Parses SEI payload buffering period. */ +/* Outputs : None */ +/* Return : 0 for successfull parsing, else error message */ +/* */ +/* Issues : Not implemented fully */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 06 05 2002 NS Draft */ +/* */ +/*****************************************************************************/ + +WORD32 ih264d_parse_buffering_period(buf_period_t *ps_buf_prd, + dec_bit_stream_t *ps_bitstrm, + dec_struct_t *ps_dec) +{ + UWORD8 u1_seq_parameter_set_id; + dec_seq_params_t *ps_seq; + UWORD8 u1_nal_hrd_present, u1_vcl_hrd_present; + UWORD32 i; + UWORD32 *pu4_bitstrm_ofst = &ps_bitstrm->u4_ofst; + UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; + UNUSED(ps_buf_prd); + u1_seq_parameter_set_id = ih264d_uev(pu4_bitstrm_ofst, + pu4_bitstrm_buf); + if(u1_seq_parameter_set_id >= MAX_NUM_SEQ_PARAMS) + return ERROR_INVALID_SEQ_PARAM; + ps_seq = &ps_dec->ps_sps[u1_seq_parameter_set_id]; + if(TRUE != ps_seq->u1_is_valid) + return ERROR_INVALID_SEQ_PARAM; + + ps_dec->ps_sei->u1_seq_param_set_id = u1_seq_parameter_set_id; + ps_dec->ps_cur_sps = ps_seq; + if(FALSE == ps_seq->u1_is_valid) + return ERROR_INVALID_SEQ_PARAM; + if(1 == ps_seq->u1_vui_parameters_present_flag) + { + u1_nal_hrd_present = ps_seq->s_vui.u1_nal_hrd_params_present; + if(u1_nal_hrd_present) + { + for(i = 0; i < ps_seq->s_vui.s_nal_hrd.u4_cpb_cnt; i++) + { + ih264d_get_bits_h264( + ps_bitstrm, + ps_seq->s_vui.s_nal_hrd.u1_initial_cpb_removal_delay); + ih264d_get_bits_h264( + ps_bitstrm, + ps_seq->s_vui.s_nal_hrd.u1_initial_cpb_removal_delay); + } + } + + u1_vcl_hrd_present = ps_seq->s_vui.u1_vcl_hrd_params_present; + if(u1_vcl_hrd_present) + { + for(i = 0; i < ps_seq->s_vui.s_vcl_hrd.u4_cpb_cnt; i++) + { + ih264d_get_bits_h264( + ps_bitstrm, + ps_seq->s_vui.s_vcl_hrd.u1_initial_cpb_removal_delay); + ih264d_get_bits_h264( + ps_bitstrm, + ps_seq->s_vui.s_vcl_hrd.u1_initial_cpb_removal_delay); + } + } + } + return (OK); +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_parse_pic_timing */ +/* */ +/* Description : This function parses SEI message pic_timing */ +/* Inputs : ps_bitstrm Bitstream */ +/* ps_dec Poniter decoder context */ +/* ui4_payload_size pay load i4_size */ +/* Globals : None */ +/* Processing : Parses SEI payload picture timing */ +/* Outputs : None */ +/* Return : 0 */ +/* */ +/* Issues : Not implemented fully */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 06 05 2002 NS Draft */ +/* */ +/*****************************************************************************/ +WORD32 ih264d_parse_pic_timing(dec_bit_stream_t *ps_bitstrm, + dec_struct_t *ps_dec, + UWORD32 ui4_payload_size) +{ + sei *ps_sei; + vui_t *ps_vu4; + UWORD8 u1_cpb_dpb_present; + UWORD8 u1_pic_struct_present_flag; + UWORD32 u4_start_offset, u4_bits_consumed; + UWORD8 u1_cpb_removal_delay_length, u1_dpb_output_delay_length; + + ps_sei = (sei *)ps_dec->ps_sei; + ps_vu4 = &ps_dec->ps_cur_sps->s_vui; + + u1_cpb_dpb_present = ps_vu4->u1_vcl_hrd_params_present + + ps_vu4->u1_nal_hrd_params_present; + + if(ps_vu4->u1_vcl_hrd_params_present) + { + u1_cpb_removal_delay_length = + ps_vu4->s_vcl_hrd.u1_cpb_removal_delay_length; + u1_dpb_output_delay_length = + ps_vu4->s_vcl_hrd.u1_dpb_output_delay_length; + } + else if(ps_vu4->u1_nal_hrd_params_present) + { + u1_cpb_removal_delay_length = + ps_vu4->s_nal_hrd.u1_cpb_removal_delay_length; + u1_dpb_output_delay_length = + ps_vu4->s_nal_hrd.u1_dpb_output_delay_length; + } + else + { + u1_cpb_removal_delay_length = 24; + u1_dpb_output_delay_length = 24; + + } + + u4_start_offset = ps_bitstrm->u4_ofst; + if(u1_cpb_dpb_present) + { + ih264d_get_bits_h264(ps_bitstrm, u1_cpb_removal_delay_length); + ih264d_get_bits_h264(ps_bitstrm, u1_dpb_output_delay_length); + } + + u1_pic_struct_present_flag = ps_vu4->u1_pic_struct_present_flag; + if(u1_pic_struct_present_flag) + { + ps_sei->u1_pic_struct = ih264d_get_bits_h264(ps_bitstrm, 4); + ps_dec->u1_pic_struct_copy = ps_sei->u1_pic_struct; + ps_sei->u1_is_valid = 1; + } + u4_bits_consumed = ps_bitstrm->u4_ofst - u4_start_offset; + + if((ui4_payload_size << 3) < u4_bits_consumed) + return ERROR_CORRUPTED_SLICE; + + ih264d_flush_bits_h264(ps_bitstrm, + (ui4_payload_size << 3) - u4_bits_consumed); + + return (OK); +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_parse_recovery_point */ +/* */ +/* Description : This function parses SEI message recovery point */ +/* Inputs : ps_bitstrm Bitstream */ +/* ps_dec Poniter decoder context */ +/* ui4_payload_size pay load i4_size */ +/* Globals : None */ +/* Processing : Parses SEI payload picture timing */ +/* Outputs : None */ +/* Return : 0 */ +/* */ +/* Issues : Not implemented fully */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 06 05 2002 NS Draft */ +/* */ +/*****************************************************************************/ +WORD32 ih264d_parse_recovery_point(dec_bit_stream_t *ps_bitstrm, + dec_struct_t *ps_dec, + UWORD32 ui4_payload_size) +{ + sei *ps_sei = ps_dec->ps_sei; + dec_err_status_t *ps_err = ps_dec->ps_dec_err_status; + UWORD32 *pu4_bitstrm_ofst = &ps_bitstrm->u4_ofst; + UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; + UNUSED(ui4_payload_size); + ps_sei->u2_recovery_frame_cnt = ih264d_uev(pu4_bitstrm_ofst, + pu4_bitstrm_buf); + ps_err->u4_frm_sei_sync = ps_err->u4_cur_frm + + ps_sei->u2_recovery_frame_cnt; + ps_sei->u1_exact_match_flag = ih264d_get_bit_h264(ps_bitstrm); + ps_sei->u1_broken_link_flag = ih264d_get_bit_h264(ps_bitstrm); + ps_sei->u1_changing_slice_grp_idc = ih264d_get_bits_h264(ps_bitstrm, 2); + + return (OK); +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_parse_mdcv */ +/* */ +/* Description : This function parses SEI message mdcv */ +/* Inputs : ps_bitstrm Bitstream */ +/* ps_dec Poniter decoder context */ +/* ui4_payload_size pay load i4_size */ +/* Globals : None */ +/* Processing : */ +/* Outputs : None */ +/* Return : 0 for successfull parsing, else -1 */ +/* */ +/* Issues : */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* Draft */ +/* */ +/*****************************************************************************/ +WORD32 ih264d_parse_mdcv(dec_bit_stream_t *ps_bitstrm, + dec_struct_t *ps_dec, + UWORD32 ui4_payload_size) +{ + sei *ps_sei = ps_dec->ps_sei_parse; + dec_err_status_t *ps_err = ps_dec->ps_dec_err_status; + UWORD32 *pu4_bitstrm_ofst = &ps_bitstrm->u4_ofst; + UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; + UWORD32 u4_count; + UNUSED(ui4_payload_size); + + if((ps_dec == NULL) || (ps_sei == NULL)) + { + return NOT_OK; + } + + ps_sei->u1_sei_mdcv_params_present_flag = 1; + + /* display primaries x */ + for(u4_count = 0; u4_count < NUM_SEI_MDCV_PRIMARIES; u4_count++) + { + ps_sei->s_sei_mdcv_params.au2_display_primaries_x[u4_count] = + (UWORD16)ih264d_get_bits_h264(ps_bitstrm, 16); + + if((ps_sei->s_sei_mdcv_params.au2_display_primaries_x[u4_count] > + DISPLAY_PRIMARIES_X_UPPER_LIMIT) || + (ps_sei->s_sei_mdcv_params.au2_display_primaries_x[u4_count] < + DISPLAY_PRIMARIES_X_LOWER_LIMIT) || + ((ps_sei->s_sei_mdcv_params.au2_display_primaries_x[u4_count] % + DISPLAY_PRIMARIES_X_DIVISION_FACTOR) != 0)) + { + ps_sei->u1_sei_mdcv_params_present_flag = 0; + return ERROR_INV_SEI_MDCV_PARAMS; + } + + ps_sei->s_sei_mdcv_params.au2_display_primaries_y[u4_count] = + (UWORD16)ih264d_get_bits_h264(ps_bitstrm, 16); + + if((ps_sei->s_sei_mdcv_params.au2_display_primaries_y[u4_count] > + DISPLAY_PRIMARIES_Y_UPPER_LIMIT) || + (ps_sei->s_sei_mdcv_params.au2_display_primaries_y[u4_count] < + DISPLAY_PRIMARIES_Y_LOWER_LIMIT) || + ((ps_sei->s_sei_mdcv_params.au2_display_primaries_y[u4_count] % + DISPLAY_PRIMARIES_Y_DIVISION_FACTOR) != 0)) + { + ps_sei->u1_sei_mdcv_params_present_flag = 0; + return ERROR_INV_SEI_MDCV_PARAMS; + } + } + + /* white point x */ + ps_sei->s_sei_mdcv_params.u2_white_point_x = (UWORD16)ih264d_get_bits_h264(ps_bitstrm, 16); + + if((ps_sei->s_sei_mdcv_params.u2_white_point_x > WHITE_POINT_X_UPPER_LIMIT) || + (ps_sei->s_sei_mdcv_params.u2_white_point_x < WHITE_POINT_X_LOWER_LIMIT) || + ((ps_sei->s_sei_mdcv_params.u2_white_point_x % WHITE_POINT_X_DIVISION_FACTOR) != 0)) + { + ps_sei->u1_sei_mdcv_params_present_flag = 0; + return ERROR_INV_SEI_MDCV_PARAMS; + } + /* white point y */ + ps_sei->s_sei_mdcv_params.u2_white_point_y = (UWORD16)ih264d_get_bits_h264(ps_bitstrm, 16); + + if((ps_sei->s_sei_mdcv_params.u2_white_point_y > WHITE_POINT_Y_UPPER_LIMIT) || + (ps_sei->s_sei_mdcv_params.u2_white_point_y < WHITE_POINT_Y_LOWER_LIMIT) || + ((ps_sei->s_sei_mdcv_params.u2_white_point_y % WHITE_POINT_Y_DIVISION_FACTOR) != 0)) + { + ps_sei->u1_sei_mdcv_params_present_flag = 0; + return ERROR_INV_SEI_MDCV_PARAMS; + } + /* max display mastering luminance */ + ps_sei->s_sei_mdcv_params.u4_max_display_mastering_luminance = + (UWORD32)ih264d_get_bits_h264(ps_bitstrm, 32); + + if((ps_sei->s_sei_mdcv_params.u4_max_display_mastering_luminance > + MAX_DISPLAY_MASTERING_LUMINANCE_UPPER_LIMIT) || + (ps_sei->s_sei_mdcv_params.u4_max_display_mastering_luminance < + MAX_DISPLAY_MASTERING_LUMINANCE_LOWER_LIMIT) || + ((ps_sei->s_sei_mdcv_params.u4_max_display_mastering_luminance % + MAX_DISPLAY_MASTERING_LUMINANCE_DIVISION_FACTOR) != 0)) + { + ps_sei->u1_sei_mdcv_params_present_flag = 0; + return ERROR_INV_SEI_MDCV_PARAMS; + } + /* min display mastering luminance */ + ps_sei->s_sei_mdcv_params.u4_min_display_mastering_luminance = + (UWORD32)ih264d_get_bits_h264(ps_bitstrm, 32); + + if((ps_sei->s_sei_mdcv_params.u4_min_display_mastering_luminance > + MIN_DISPLAY_MASTERING_LUMINANCE_UPPER_LIMIT) || + (ps_sei->s_sei_mdcv_params.u4_min_display_mastering_luminance < + MIN_DISPLAY_MASTERING_LUMINANCE_LOWER_LIMIT)) + { + ps_sei->u1_sei_mdcv_params_present_flag = 0; + return ERROR_INV_SEI_MDCV_PARAMS; + } + if(ps_sei->s_sei_mdcv_params.u4_max_display_mastering_luminance <= + ps_sei->s_sei_mdcv_params.u4_min_display_mastering_luminance) + { + ps_sei->u1_sei_mdcv_params_present_flag = 0; + return ERROR_INV_SEI_MDCV_PARAMS; + } + return (OK); +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_parse_cll */ +/* */ +/* Description : This function parses SEI message cll */ +/* Inputs : ps_bitstrm Bitstream */ +/* ps_dec Poniter decoder context */ +/* ui4_payload_size pay load i4_size */ +/* Globals : None */ +/* Processing : */ +/* Outputs : None */ +/* Return : 0 for successfull parsing, else -1 */ +/* */ +/* Issues : */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* Draft */ +/* */ +/*****************************************************************************/ +WORD32 ih264d_parse_cll(dec_bit_stream_t *ps_bitstrm, + dec_struct_t *ps_dec, + UWORD32 ui4_payload_size) +{ + sei *ps_sei = ps_dec->ps_sei_parse; + dec_err_status_t *ps_err = ps_dec->ps_dec_err_status; + UWORD32 *pu4_bitstrm_ofst = &ps_bitstrm->u4_ofst; + UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; + UNUSED(ui4_payload_size); + + if((ps_dec == NULL) || (ps_sei == NULL)) + { + return NOT_OK; + } + + ps_sei->u1_sei_cll_params_present_flag = 1; + + ps_sei->s_sei_cll_params.u2_max_content_light_level = + (UWORD16)ih264d_get_bits_h264(ps_bitstrm, 16); + ps_sei->s_sei_cll_params.u2_max_pic_average_light_level = + (UWORD16)ih264d_get_bits_h264(ps_bitstrm, 16); + /*No any sanity checks done for CLL params*/ + + return (OK); +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_parse_ave */ +/* */ +/* Description : This function parses SEI message ave */ +/* Inputs : ps_bitstrm Bitstream */ +/* ps_dec Poniter decoder context */ +/* ui4_payload_size pay load i4_size */ +/* Globals : None */ +/* Processing : */ +/* Outputs : None */ +/* Return : 0 for successfull parsing, else -1 */ +/* */ +/* Issues : */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* Draft */ +/* */ +/*****************************************************************************/ +WORD32 ih264d_parse_ave(dec_bit_stream_t *ps_bitstrm, + dec_struct_t *ps_dec, + UWORD32 ui4_payload_size) +{ + sei *ps_sei = ps_dec->ps_sei_parse; + dec_err_status_t *ps_err = ps_dec->ps_dec_err_status; + UWORD32 *pu4_bitstrm_ofst = &ps_bitstrm->u4_ofst; + UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; + UNUSED(ui4_payload_size); + + if((ps_dec == NULL) || (ps_sei == NULL)) + { + return NOT_OK; + } + + ps_sei->u1_sei_ave_params_present_flag = 1; + + ps_sei->s_sei_ave_params.u4_ambient_illuminance = (UWORD32)ih264d_get_bits_h264(ps_bitstrm, 32); + if(0 == ps_sei->s_sei_ave_params.u4_ambient_illuminance) + { + ps_sei->u1_sei_ave_params_present_flag = 0; + return ERROR_INV_SEI_AVE_PARAMS; + } + + ps_sei->s_sei_ave_params.u2_ambient_light_x = (UWORD16)ih264d_get_bits_h264(ps_bitstrm, 16); + if(ps_sei->s_sei_ave_params.u2_ambient_light_x > AMBIENT_LIGHT_X_UPPER_LIMIT) + { + ps_sei->u1_sei_ave_params_present_flag = 0; + return ERROR_INV_SEI_AVE_PARAMS; + } + + ps_sei->s_sei_ave_params.u2_ambient_light_y = (UWORD16)ih264d_get_bits_h264(ps_bitstrm, 16); + if(ps_sei->s_sei_ave_params.u2_ambient_light_y > AMBIENT_LIGHT_Y_UPPER_LIMIT) + { + ps_sei->u1_sei_ave_params_present_flag = 0; + return ERROR_INV_SEI_AVE_PARAMS; + } + return (OK); +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_parse_ccv */ +/* */ +/* Description : This function parses SEI message ccv */ +/* Inputs : ps_bitstrm Bitstream */ +/* ps_dec Poniter decoder context */ +/* ui4_payload_size pay load i4_size */ +/* Globals : None */ +/* Processing : */ +/* Outputs : None */ +/* Return : 0 for successfull parsing, else -1 */ +/* */ +/* Issues : */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* Draft */ +/* */ +/*****************************************************************************/ +WORD32 ih264d_parse_ccv(dec_bit_stream_t *ps_bitstrm, + dec_struct_t *ps_dec, + UWORD32 ui4_payload_size) +{ + sei *ps_sei = ps_dec->ps_sei_parse; + dec_err_status_t *ps_err = ps_dec->ps_dec_err_status; + UWORD32 *pu4_bitstrm_ofst = &ps_bitstrm->u4_ofst; + UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; + UWORD32 u4_count; + UNUSED(ui4_payload_size); + + if((ps_dec == NULL) || (ps_sei == NULL)) + { + return NOT_OK; + } + + ps_sei->u1_sei_ccv_params_present_flag = 0; + + ps_sei->s_sei_ccv_params.u1_ccv_cancel_flag = (UWORD8)ih264d_get_bit_h264(ps_bitstrm); + + if(ps_sei->s_sei_ccv_params.u1_ccv_cancel_flag > 1) + { + return ERROR_INV_SEI_CCV_PARAMS; + } + if(0 == ps_sei->s_sei_ccv_params.u1_ccv_cancel_flag) + { + ps_sei->s_sei_ccv_params.u1_ccv_persistence_flag = + (UWORD8)ih264d_get_bit_h264(ps_bitstrm); + if(ps_sei->s_sei_ccv_params.u1_ccv_persistence_flag > 1) + { + return ERROR_INV_SEI_CCV_PARAMS; + } + ps_sei->s_sei_ccv_params.u1_ccv_primaries_present_flag = + (UWORD8)ih264d_get_bit_h264(ps_bitstrm); + if(ps_sei->s_sei_ccv_params.u1_ccv_primaries_present_flag > 1) + { + return ERROR_INV_SEI_CCV_PARAMS; + } + ps_sei->s_sei_ccv_params.u1_ccv_min_luminance_value_present_flag = + (UWORD8)ih264d_get_bit_h264(ps_bitstrm); + if(ps_sei->s_sei_ccv_params.u1_ccv_min_luminance_value_present_flag > 1) + { + return ERROR_INV_SEI_CCV_PARAMS; + } + ps_sei->s_sei_ccv_params.u1_ccv_max_luminance_value_present_flag = + (UWORD8)ih264d_get_bit_h264(ps_bitstrm); + if(ps_sei->s_sei_ccv_params.u1_ccv_max_luminance_value_present_flag > 1) + { + return ERROR_INV_SEI_CCV_PARAMS; + } + ps_sei->s_sei_ccv_params.u1_ccv_avg_luminance_value_present_flag = + (UWORD8)ih264d_get_bit_h264(ps_bitstrm); + if(ps_sei->s_sei_ccv_params.u1_ccv_avg_luminance_value_present_flag > 1) + { + return ERROR_INV_SEI_CCV_PARAMS; + } + + if((ps_sei->s_sei_ccv_params.u1_ccv_primaries_present_flag == 0) && + (ps_sei->s_sei_ccv_params.u1_ccv_min_luminance_value_present_flag == 0) && + (ps_sei->s_sei_ccv_params.u1_ccv_max_luminance_value_present_flag == 0) && + (ps_sei->s_sei_ccv_params.u1_ccv_avg_luminance_value_present_flag == 0)) + { + return ERROR_INV_SEI_CCV_PARAMS; + } + + ps_sei->s_sei_ccv_params.u1_ccv_reserved_zero_2bits = + (UWORD8)ih264d_get_bits_h264(ps_bitstrm, 2); + if((ps_sei->s_sei_ccv_params.u1_ccv_reserved_zero_2bits != 0)) + { + return ERROR_INV_SEI_CCV_PARAMS; + } + + /* ccv primaries */ + if(1 == ps_sei->s_sei_ccv_params.u1_ccv_primaries_present_flag) + { + for(u4_count = 0; u4_count < NUM_SEI_CCV_PRIMARIES; u4_count++) + { + ps_sei->s_sei_ccv_params.ai4_ccv_primaries_x[u4_count] = + (WORD32)ih264d_get_bits_h264(ps_bitstrm, 32); + if((ps_sei->s_sei_ccv_params.ai4_ccv_primaries_x[u4_count] > + CCV_PRIMARIES_X_UPPER_LIMIT) || + (ps_sei->s_sei_ccv_params.ai4_ccv_primaries_x[u4_count] < + CCV_PRIMARIES_X_LOWER_LIMIT)) + { + return ERROR_INV_SEI_CCV_PARAMS; + } + + ps_sei->s_sei_ccv_params.ai4_ccv_primaries_y[u4_count] = + (WORD32)ih264d_get_bits_h264(ps_bitstrm, 32); + if((ps_sei->s_sei_ccv_params.ai4_ccv_primaries_y[u4_count] > + CCV_PRIMARIES_Y_UPPER_LIMIT) || + (ps_sei->s_sei_ccv_params.ai4_ccv_primaries_y[u4_count] < + CCV_PRIMARIES_Y_LOWER_LIMIT)) + { + return ERROR_INV_SEI_CCV_PARAMS; + } + } + } + + if(1 == ps_sei->s_sei_ccv_params.u1_ccv_min_luminance_value_present_flag) + { + ps_sei->s_sei_ccv_params.u4_ccv_min_luminance_value = + (UWORD32)ih264d_get_bits_h264(ps_bitstrm, 32); + } + + if(1 == ps_sei->s_sei_ccv_params.u1_ccv_max_luminance_value_present_flag) + { + ps_sei->s_sei_ccv_params.u4_ccv_max_luminance_value = + (UWORD32)ih264d_get_bits_h264(ps_bitstrm, 32); + if((1 == ps_sei->s_sei_ccv_params.u1_ccv_min_luminance_value_present_flag) && + (ps_sei->s_sei_ccv_params.u4_ccv_max_luminance_value < + ps_sei->s_sei_ccv_params.u4_ccv_min_luminance_value)) + { + return ERROR_INV_SEI_CCV_PARAMS; + } + } + if(1 == ps_sei->s_sei_ccv_params.u1_ccv_avg_luminance_value_present_flag) + { + ps_sei->s_sei_ccv_params.u4_ccv_avg_luminance_value = + (UWORD32)ih264d_get_bits_h264(ps_bitstrm, 32); + if((1 == ps_sei->s_sei_ccv_params.u1_ccv_min_luminance_value_present_flag) && + (ps_sei->s_sei_ccv_params.u4_ccv_avg_luminance_value < + ps_sei->s_sei_ccv_params.u4_ccv_min_luminance_value)) + { + return ERROR_INV_SEI_CCV_PARAMS; + } + if((1 == ps_sei->s_sei_ccv_params.u1_ccv_max_luminance_value_present_flag) && + (ps_sei->s_sei_ccv_params.u4_ccv_max_luminance_value < + ps_sei->s_sei_ccv_params.u4_ccv_avg_luminance_value)) + { + return ERROR_INV_SEI_CCV_PARAMS; + } + } + } + ps_sei->u1_sei_ccv_params_present_flag = 1; + return (OK); +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_parse_sei_payload */ +/* */ +/* Description : This function parses SEI pay loads. Currently it's */ +/* implemented partially. */ +/* Inputs : ps_bitstrm Bitstream */ +/* ui4_payload_type SEI payload type */ +/* ui4_payload_size SEI payload i4_size */ +/* Globals : None */ +/* Processing : Parses SEI payloads units and stores the info */ +/* Outputs : None */ +/* Return : status for successful parsing, else -1 */ +/* */ +/* Issues : Not implemented fully */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 06 05 2002 NS Draft */ +/* */ +/*****************************************************************************/ + +WORD32 ih264d_parse_sei_payload(dec_bit_stream_t *ps_bitstrm, + UWORD32 ui4_payload_type, + UWORD32 ui4_payload_size, + dec_struct_t *ps_dec) +{ + sei *ps_sei; + WORD32 i4_status = 0; + ps_sei = (sei *)ps_dec->ps_sei_parse; + + if(ui4_payload_size == 0) + return -1; + if(NULL == ps_bitstrm) + { + return NOT_OK; + } + + switch(ui4_payload_type) + { + case SEI_BUF_PERIOD: + + i4_status = ih264d_parse_buffering_period(&ps_sei->s_buf_period, + ps_bitstrm, ps_dec); + break; + case SEI_PIC_TIMING: + if(NULL == ps_dec->ps_cur_sps) + i4_status = ih264d_flush_bits_h264(ps_bitstrm, (ui4_payload_size << 3)); + else + i4_status = ih264d_parse_pic_timing(ps_bitstrm, ps_dec, + ui4_payload_size); + break; + case SEI_RECOVERY_PT: + i4_status = ih264d_parse_recovery_point(ps_bitstrm, ps_dec, + ui4_payload_size); + break; + case SEI_MASTERING_DISP_COL_VOL: + + i4_status = ih264d_parse_mdcv(ps_bitstrm, ps_dec, + ui4_payload_size); + break; + case SEI_CONTENT_LIGHT_LEVEL_DATA: + + i4_status = ih264d_parse_cll(ps_bitstrm, ps_dec, + ui4_payload_size); + break; + case SEI_AMBIENT_VIEWING_ENVIRONMENT: + + i4_status = ih264d_parse_ave(ps_bitstrm, ps_dec, + ui4_payload_size); + break; + case SEI_CONTENT_COLOR_VOLUME: + + i4_status = ih264d_parse_ccv(ps_bitstrm, ps_dec, + ui4_payload_size); + break; + default: + i4_status = ih264d_flush_bits_h264(ps_bitstrm, (ui4_payload_size << 3)); + break; + } + return (i4_status); +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_parse_sei_message */ +/* */ +/* Description : This function is parses and decode SEI. Currently it's */ +/* not implemented fully. */ +/* Inputs : ps_dec Decoder parameters */ +/* ps_bitstrm Bitstream */ +/* Globals : None */ +/* Processing : Parses SEI NAL units and stores the info */ +/* Outputs : None */ +/* Returns : None */ +/* */ +/* Issues : Not implemented fully */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 06 05 2002 NS Draft */ +/* */ +/*****************************************************************************/ + +WORD32 ih264d_parse_sei_message(dec_struct_t *ps_dec, + dec_bit_stream_t *ps_bitstrm) +{ + UWORD32 ui4_payload_type, ui4_payload_size; + UWORD32 u4_bits; + WORD32 i4_status = 0; + + do + { + ui4_payload_type = 0; + + if(!CHECK_BITS_SUFFICIENT(ps_bitstrm, 8)) + { + return ERROR_EOB_GETBITS_T; + } + u4_bits = ih264d_get_bits_h264(ps_bitstrm, 8); + while(0xff == u4_bits && CHECK_BITS_SUFFICIENT(ps_bitstrm, 8)) + { + u4_bits = ih264d_get_bits_h264(ps_bitstrm, 8); + ui4_payload_type += 255; + } + ui4_payload_type += u4_bits; + + ui4_payload_size = 0; + if(!CHECK_BITS_SUFFICIENT(ps_bitstrm, 8)) + { + return ERROR_EOB_GETBITS_T; + } + u4_bits = ih264d_get_bits_h264(ps_bitstrm, 8); + while(0xff == u4_bits && CHECK_BITS_SUFFICIENT(ps_bitstrm, 8)) + { + u4_bits = ih264d_get_bits_h264(ps_bitstrm, 8); + ui4_payload_size += 255; + } + ui4_payload_size += u4_bits; + + if(!CHECK_BITS_SUFFICIENT(ps_bitstrm, (ui4_payload_size << 3))) + { + return ERROR_EOB_GETBITS_T; + } + i4_status = ih264d_parse_sei_payload(ps_bitstrm, ui4_payload_type, + ui4_payload_size, ps_dec); + if(i4_status != OK) + return i4_status; + + if(ih264d_check_byte_aligned(ps_bitstrm) == 0) + { + u4_bits = ih264d_get_bit_h264(ps_bitstrm); + if(0 == u4_bits) + { + H264_DEC_DEBUG_PRINT("\nError in parsing SEI message"); + } + while(0 == ih264d_check_byte_aligned(ps_bitstrm) + && CHECK_BITS_SUFFICIENT(ps_bitstrm, 1)) + { + u4_bits = ih264d_get_bit_h264(ps_bitstrm); + if(u4_bits) + { + H264_DEC_DEBUG_PRINT("\nError in parsing SEI message"); + } + } + } + } + while(MORE_RBSP_DATA(ps_bitstrm)); + return (i4_status); +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_export_sei_mdcv_params */ +/* */ +/* Description : This function populates SEI mdcv message in */ +/* output structure */ +/* Inputs : ps_sei_mdcv_op pointer to sei mdcv o\p struct */ +/* : ps_sei pointer to decoded sei params */ +/* Outputs : */ +/* Returns : returns 0 for success; -1 for failure */ +/* */ +/* Issues : none */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* */ +/* */ +/*****************************************************************************/ +WORD32 ih264d_export_sei_mdcv_params(ivd_sei_decode_op_t *ps_sei_decode_op, + sei *ps_sei, sei *ps_sei_export) +{ + if((ps_sei_export == NULL) || (ps_sei == NULL)) + { + return NOT_OK; + } + + ps_sei_export->u1_sei_mdcv_params_present_flag = ps_sei->u1_sei_mdcv_params_present_flag; + ps_sei_decode_op->u1_sei_mdcv_params_present_flag = ps_sei->u1_sei_mdcv_params_present_flag; + + if(0 == ps_sei_export->u1_sei_mdcv_params_present_flag) + { + memset(&ps_sei_export->s_sei_mdcv_params, 0, sizeof(sei_mdcv_params_t)); + } + else + { + memcpy(&ps_sei_export->s_sei_mdcv_params, &ps_sei->s_sei_mdcv_params, + sizeof(sei_mdcv_params_t)); + } + + return (OK); +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_export_sei_cll_params */ +/* */ +/* Description : This function populates SEI cll message in */ +/* output structure */ +/* Inputs : ps_sei_cll_op pointer to sei cll o\p struct */ +/* : ps_sei pointer to decoded sei params */ +/* Outputs : */ +/* Returns : returns 0 for success; -1 for failure */ +/* */ +/* Issues : none */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* */ +/* */ +/*****************************************************************************/ +WORD32 ih264d_export_sei_cll_params(ivd_sei_decode_op_t *ps_sei_decode_op, + sei *ps_sei, sei *ps_sei_export) +{ + if((ps_sei_export == NULL) || (ps_sei == NULL)) + { + return NOT_OK; + } + + ps_sei_export->u1_sei_cll_params_present_flag = ps_sei->u1_sei_cll_params_present_flag; + ps_sei_decode_op->u1_sei_cll_params_present_flag = ps_sei->u1_sei_cll_params_present_flag; + + if(0 == ps_sei_export->u1_sei_cll_params_present_flag) + { + memset(&ps_sei_export->s_sei_cll_params, 0, sizeof(sei_cll_params_t)); + } + else + { + memcpy(&ps_sei_export->s_sei_cll_params, &ps_sei->s_sei_cll_params, + sizeof(sei_cll_params_t)); + } + return (OK); +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_export_sei_ave_params */ +/* */ +/* Description : This function populates SEI ave message in */ +/* output structure */ +/* Inputs : ps_sei_ave_op pointer to sei ave o\p struct */ +/* : ps_sei pointer to decoded sei params */ +/* Outputs : */ +/* Returns : returns 0 for success; -1 for failure */ +/* */ +/* Issues : none */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* */ +/* */ +/*****************************************************************************/ +WORD32 ih264d_export_sei_ave_params(ivd_sei_decode_op_t *ps_sei_decode_op, + sei *ps_sei, sei *ps_sei_export) +{ + if((ps_sei_export == NULL) || (ps_sei == NULL)) + { + return NOT_OK; + } + + ps_sei_export->u1_sei_ave_params_present_flag = ps_sei->u1_sei_ave_params_present_flag; + ps_sei_decode_op->u1_sei_ave_params_present_flag = ps_sei->u1_sei_ave_params_present_flag; + + if(0 == ps_sei_export->u1_sei_ave_params_present_flag) + { + memset(&ps_sei_export->s_sei_ave_params, 0, sizeof(sei_ave_params_t)); + } + else + { + memcpy(&ps_sei_export->s_sei_ave_params, &ps_sei->s_sei_ave_params, + sizeof(sei_ave_params_t)); + } + + return (OK); +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_export_sei_ccv_params */ +/* */ +/* Description : This function populates SEI ccv message in */ +/* output structure */ +/* Inputs : ps_sei_ccv_op pointer to sei ccv o\p struct */ +/* : ps_sei pointer to decoded sei params */ +/* Outputs : */ +/* Returns : returns 0 for success; -1 for failure */ +/* */ +/* Issues : none */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* */ +/* */ +/*****************************************************************************/ +WORD32 ih264d_export_sei_ccv_params(ivd_sei_decode_op_t *ps_sei_decode_op, + sei *ps_sei, sei *ps_sei_export) +{ + if((ps_sei_export == NULL) || (ps_sei == NULL)) + { + return NOT_OK; + } + + ps_sei_export->u1_sei_ccv_params_present_flag = ps_sei->u1_sei_ccv_params_present_flag; + ps_sei_decode_op->u1_sei_ccv_params_present_flag = ps_sei->u1_sei_ccv_params_present_flag; + + if(0 == ps_sei_export->u1_sei_ccv_params_present_flag) + { + memset(&ps_sei_export->s_sei_ccv_params, 0, sizeof(sei_ccv_params_t)); + } + else + { + memcpy(&ps_sei_export->s_sei_ccv_params, &ps_sei->s_sei_ccv_params, + sizeof(sei_ccv_params_t)); + } + return (OK); +} + diff --git a/dependencies/ih264d/decoder/ih264d_sei.h b/dependencies/ih264d/decoder/ih264d_sei.h new file mode 100644 index 00000000..af143ac6 --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_sei.h @@ -0,0 +1,304 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ + +/*****************************************************************************/ +/* */ +/* File Name : ih264d_sei.h */ +/* */ +/* Description : This file contains routines to parse SEI NAL's */ +/* */ +/* List of Functions : <List the functions defined in this file> */ +/* */ +/* Issues / Problems : None */ +/* */ +/* Revision History : */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 25 05 2005 NS Draft */ +/* */ +/*****************************************************************************/ + +#ifndef _IH264D_SEI_H_ +#define _IH264D_SEI_H_ + +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264d_bitstrm.h" +#include "ih264d_structs.h" +#include "ih264d.h" + +#define SEI_BUF_PERIOD 0 +#define SEI_PIC_TIMING 1 +#define SEI_PAN_SCAN_RECT 2 +#define SEI_FILLER 3 +#define SEI_UD_REG_T35 4 +#define SEI_UD_UN_REG 5 +#define SEI_RECOVERY_PT 6 +#define SEI_DEC_REF_MARK 7 +#define SEI_SPARE_PIC 8 +#define SEI_SCENE_INFO 9 +#define SEI_SUB_SEQN_INFO 10 +#define SEI_SUB_SEQN_LAY_CHAR 11 +#define SEI_SUB_SEQN_CHAR 12 +#define SEI_FULL_FRAME_FREEZE 13 +#define SEI_FULL_FRAME_FREEZE_REL 14 +#define SEI_FULL_FRAME_SNAP_SHOT 15 +#define SEI_PROG_REF_SEGMENT_START 16 +#define SEI_PROG_REF_SEGMENT_END 17 +#define SEI_MOT_CON_SLICE_GRP_SET 18 +#define SEI_MASTERING_DISP_COL_VOL 137 +#define SEI_CONTENT_LIGHT_LEVEL_DATA 144 +#define SEI_AMBIENT_VIEWING_ENVIRONMENT 148 +#define SEI_CONTENT_COLOR_VOLUME 149 + +/* Declaration of dec_struct_t to avoid CCS compilation Error */ +struct _DecStruct; +WORD32 ih264d_parse_sei_message(struct _DecStruct *ps_dec, + dec_bit_stream_t *ps_bitstrm); +typedef struct +{ + UWORD8 u1_seq_parameter_set_id; + UWORD32 u4_initial_cpb_removal_delay; + UWORD32 u4_nitial_cpb_removal_delay_offset; + +} buf_period_t; + +/** + * Structure to hold Mastering Display Color Volume SEI + */ +typedef struct +{ + /** + * Array to store the display_primaries_x values + */ + UWORD16 au2_display_primaries_x[NUM_SEI_MDCV_PRIMARIES]; + + /** + * Array to store the display_primaries_y values + */ + UWORD16 au2_display_primaries_y[NUM_SEI_MDCV_PRIMARIES]; + + /** + * Variable to store the white point x value + */ + UWORD16 u2_white_point_x; + + /** + * Variable to store the white point y value + */ + UWORD16 u2_white_point_y; + + /** + * Variable to store the max display mastering luminance value + */ + UWORD32 u4_max_display_mastering_luminance; + + /** + * Variable to store the min display mastering luminance value + */ + UWORD32 u4_min_display_mastering_luminance; + +}sei_mdcv_params_t; + + +/** + * Structure for Content Light Level Info + * + */ +typedef struct +{ + /** + * The maximum pixel intensity of all samples + */ + UWORD16 u2_max_content_light_level; + + /** + * The average pixel intensity of all samples + */ + UWORD16 u2_max_pic_average_light_level; + +}sei_cll_params_t; + + +/** + * Structure to hold Ambient viewing environment SEI + */ +typedef struct +{ + + /** + * specifies the environmental illuminance of the ambient viewing environment + */ + UWORD32 u4_ambient_illuminance; + + /* + * specify the normalized x chromaticity coordinates of the + * environmental ambient light in the nominal viewing environment + */ + UWORD16 u2_ambient_light_x; + + /* + * specify the normalized y chromaticity coordinates of the + * environmental ambient light in the nominal viewing environment + */ + UWORD16 u2_ambient_light_y; + +}sei_ave_params_t; + + +/** + * Structure to hold Content color volume SEI + */ +typedef struct +{ + /* + * Flag used to control persistence of CCV SEI messages + */ + UWORD8 u1_ccv_cancel_flag; + + /* + * specifies the persistence of the CCV SEI message for the current layer + */ + UWORD8 u1_ccv_persistence_flag; + + /* + * specifies the presence of syntax elements ccv_primaries_x and ccv_primaries_y + */ + UWORD8 u1_ccv_primaries_present_flag; + + /* + * specifies that the syntax element ccv_min_luminance_value is present + */ + UWORD8 u1_ccv_min_luminance_value_present_flag; + + /* + * specifies that the syntax element ccv_max_luminance_value is present + */ + UWORD8 u1_ccv_max_luminance_value_present_flag; + + /* + * specifies that the syntax element ccv_avg_luminance_value is present + */ + UWORD8 u1_ccv_avg_luminance_value_present_flag; + + /* + * shall be equal to 0 in bitstreams conforming to this version. Other values + * for reserved_zero_2bits are reserved for future use + */ + UWORD8 u1_ccv_reserved_zero_2bits; + + /* + * specify the normalized x chromaticity coordinates of the colour + * primary component c of the nominal content colour volume + */ + WORD32 ai4_ccv_primaries_x[NUM_SEI_CCV_PRIMARIES]; + + /* + * specify the normalized y chromaticity coordinates of the colour + * primary component c of the nominal content colour volume + */ + WORD32 ai4_ccv_primaries_y[NUM_SEI_CCV_PRIMARIES]; + + /* + * specifies the normalized minimum luminance value + */ + UWORD32 u4_ccv_min_luminance_value; + + /* + * specifies the normalized maximum luminance value + */ + UWORD32 u4_ccv_max_luminance_value; + + /* + * specifies the normalized average luminance value + */ + UWORD32 u4_ccv_avg_luminance_value; + +}sei_ccv_params_t; + +struct _sei +{ + UWORD8 u1_seq_param_set_id; + buf_period_t s_buf_period; + UWORD8 u1_pic_struct; + UWORD16 u2_recovery_frame_cnt; + UWORD8 u1_exact_match_flag; + UWORD8 u1_broken_link_flag; + UWORD8 u1_changing_slice_grp_idc; + UWORD8 u1_is_valid; + + /** + * mastering display color volume info present flag + */ + UWORD8 u1_sei_mdcv_params_present_flag; + + /* + * MDCV parameters + */ + sei_mdcv_params_t s_sei_mdcv_params; + + /** + * content light level info present flag + */ + UWORD8 u1_sei_cll_params_present_flag; + + /* + * CLL parameters + */ + sei_cll_params_t s_sei_cll_params; + + /** + * ambient viewing environment info present flag + */ + UWORD8 u1_sei_ave_params_present_flag; + + /* + * AVE parameters + */ + sei_ave_params_t s_sei_ave_params; + + /** + * content color volume info present flag + */ + UWORD8 u1_sei_ccv_params_present_flag; + + /* + * CCV parameters + */ + sei_ccv_params_t s_sei_ccv_params; + +}; +typedef struct _sei sei; + +WORD32 ih264d_export_sei_mdcv_params(ivd_sei_decode_op_t *ps_sei_decode_op, + sei *ps_sei, sei *ps_sei_export); + +WORD32 ih264d_export_sei_cll_params(ivd_sei_decode_op_t *ps_sei_decode_op, + sei *ps_sei, sei *ps_sei_export); + +WORD32 ih264d_export_sei_ave_params(ivd_sei_decode_op_t *ps_sei_decode_op, + sei *ps_sei, sei *ps_sei_export); + +WORD32 ih264d_export_sei_ccv_params(ivd_sei_decode_op_t *ps_sei_decode_op, + sei *ps_sei, sei *ps_sei_export); + +#endif /* _IH264D_SEI_H_ */ + diff --git a/dependencies/ih264d/decoder/ih264d_structs.h b/dependencies/ih264d/decoder/ih264d_structs.h new file mode 100644 index 00000000..989bd33c --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_structs.h @@ -0,0 +1,1478 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +#ifndef _IH264D_STRUCTS_H_ +#define _IH264D_STRUCTS_H_ + +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "iv.h" +#include "ivd.h" + +#include "ih264d_transfer_address.h" +#include "ih264d_defs.h" +#include "ih264d_defs.h" +#include "ih264d_bitstrm.h" +#include "ih264d_debug.h" +#include "ih264d_dpb_manager.h" +/* includes for CABAC */ +#include "ih264d_cabac.h" +#include "ih264d_dpb_manager.h" + +#include "ih264d_vui.h" +#include "ih264d_sei.h" +#include "iv.h" +#include "ivd.h" + +#include "ih264_weighted_pred.h" +#include "ih264_trans_quant_itrans_iquant.h" +#include "ih264_inter_pred_filters.h" +#include "ih264_mem_fns.h" +#include "ih264_padding.h" +#include "ih264_intra_pred_filters.h" +#include "ih264_deblk_edge_filters.h" + + + + + +/** Number of Mb's whoose syntax will be read */ +/************************************************************/ +/* MB_GROUP should be a multiple of 2 */ +/************************************************************/ +#define PARSE_MB_GROUP_4 4 + +/* MV_SCRATCH_BUFS assumed to be pow(2) */ +#define MV_SCRATCH_BUFS 4 + +#define TOP_FIELD_ONLY 0x02 +#define BOT_FIELD_ONLY 0x01 + +#define MAX_REF_BUF_SIZE (3776*2*2) + +struct _DecStruct; +struct _DecMbInfo; + +typedef enum +{ + MB_TYPE_SI_SLICE = 0, + MB_TYPE_I_SLICE = 3, + MB_SKIP_FLAG_P_SLICE = 11, + MB_TYPE_P_SLICE = 14, + SUB_MB_TYPE_P_SLICE = 21, + MB_SKIP_FLAG_B_SLICE = 24, + MB_TYPE_B_SLICE = 27, + SUB_MB_TYPE_B_SLICE = 36, + MVD_X = 40, + MVD_Y = 47, + REF_IDX = 54, + MB_QP_DELTA = 60, + INTRA_CHROMA_PRED_MODE = 64, + PREV_INTRA4X4_PRED_MODE_FLAG = 68, + REM_INTRA4X4_PRED_MODE = 69, + MB_FIELD_DECODING_FLAG = 70, + CBP_LUMA = 73, + CBP_CHROMA = 77, + CBF = 85, + SIGNIFICANT_COEFF_FLAG_FRAME = 105, + SIGNIFICANT_COEFF_FLAG_FLD = 277, + LAST_SIGNIFICANT_COEFF_FLAG_FRAME = 166, + LAST_SIGNIFICANT_COEFF_FLAG_FLD = 338, + COEFF_ABS_LEVEL_MINUS1 = 227, + + /* High profile related Syntax element CABAC offsets */ + TRANSFORM_SIZE_8X8_FLAG = 399, + SIGNIFICANT_COEFF_FLAG_8X8_FRAME = 402, + LAST_SIGNIFICANT_COEFF_FLAG_8X8_FRAME = 417, + COEFF_ABS_LEVEL_MINUS1_8X8 = 426, + SIGNIFICANT_COEFF_FLAG_8X8_FIELD = 436, + LAST_SIGNIFICANT_COEFF_FLAG_8X8_FIELD = 451 + +} cabac_table_num_t; + +typedef enum +{ + SIG_COEFF_CTXT_CAT_0_OFFSET = 0, + SIG_COEFF_CTXT_CAT_1_OFFSET = 15, + SIG_COEFF_CTXT_CAT_2_OFFSET = 29, + SIG_COEFF_CTXT_CAT_3_OFFSET = 44, + SIG_COEFF_CTXT_CAT_4_OFFSET = 47, + SIG_COEFF_CTXT_CAT_5_OFFSET = 0, + COEFF_ABS_LEVEL_CAT_0_OFFSET = 0, + COEFF_ABS_LEVEL_CAT_1_OFFSET = 10, + COEFF_ABS_LEVEL_CAT_2_OFFSET = 20, + COEFF_ABS_LEVEL_CAT_3_OFFSET = 30, + COEFF_ABS_LEVEL_CAT_4_OFFSET = 39, + COEFF_ABS_LEVEL_CAT_5_OFFSET = 0 +} cabac_blk_cat_offset_t; + +/** Structure for the MV bank */ +typedef struct _mv_pred_t +{ + WORD16 i2_mv[4]; /** 0- mvFwdX, 1- mvFwdY, 2- mvBwdX, 3- mvBwdY */ + WORD8 i1_ref_frame[2]; + + UWORD8 u1_col_ref_pic_idx; /** Idx into the pic buff array */ + UWORD8 u1_pic_type; /** Idx into the pic buff array */ + +} mv_pred_t; + +typedef struct +{ + WORD32 i4_mv_indices[16]; + WORD8 i1_submb_num[16]; + WORD8 i1_partitionsize[16]; + WORD8 i1_num_partitions; + WORD8 u1_vert_mv_scale; + UWORD8 u1_col_zeroflag_change; +} directmv_t; + +typedef struct pic_buffer_t +{ + /**Different components of the picture */ + UWORD8 *pu1_buf1; + UWORD8 *pu1_buf2; + UWORD8 *pu1_buf3; + UWORD16 u2_disp_width; /** Width of the display luma frame in pixels */ + UWORD16 u2_disp_height; /** Height of the display luma frame in pixels */ + UWORD32 u4_time_stamp; /** Time at which frame has to be displayed */ + UWORD16 u2_frm_wd_y; /** Width of the luma frame in pixels */ + UWORD16 u2_frm_wd_uv; /** Width of the chroma frame */ + UWORD16 u2_frm_ht_y; /** Height of the luma frame in pixels */ + UWORD16 u2_frm_ht_uv; /** Height of the chroma frame */ + /* Upto this is resembling the structure IH264DEC_DispUnit */ + + /* If any member is to be added, add below this */ + + /* u4_ofst from start of picture buffer to display position for Y buffer */ + UWORD16 u2_crop_offset_y; + + /* u4_ofst from start of picture buffer to display position for UV buffer */ + UWORD16 u2_crop_offset_uv; + + UWORD8 u1_is_short; /** (1: short 0: long) term ref pic */ + UWORD8 u1_pic_type; /** frame / field / complementary field pair */ + UWORD8 u1_pic_buf_id; /** Idx into the picBufAPI array */ + UWORD8 u1_mv_buf_id; + WORD32 i4_seq; + UWORD8 *pu1_col_zero_flag; + mv_pred_t *ps_mv; /** Pointer to the MV bank array */ + WORD32 i4_poc; /** POC */ + WORD32 i4_pic_num; + WORD32 i4_frame_num; + WORD32 i4_top_field_order_cnt; /** TopPOC */ + WORD32 i4_bottom_field_order_cnt; /** BottomPOC */ + WORD32 i4_avg_poc; /** minPOC */ + UWORD8 u1_picturetype; /*Same as u1_pic_type..u1_pic_type gets overwritten whereas + this doesnot get overwritten ...stores the pictype of + frame/complementary field pair/ mbaff */ + UWORD8 u1_long_term_frm_idx; + UWORD8 u1_long_term_pic_num; + UWORD32 u4_pack_slc_typ; /* It will contain information about types of slices */ + + /* ! */ + UWORD32 u4_ts; + UWORD8 u1_pic_struct;/* Refer to SEI table D-1 */ + sei s_sei_pic; + +} pic_buffer_t; + +typedef struct +{ + void *u4_add[4]; +} neighbouradd_t; + +typedef struct +{ + const UWORD8 *pu1_inv_scan; + void *pv_table[6]; +} cavlc_cntxt_t; + +/** + ************************************************************************ + * \file ih264d_structs.h + * + * \brief + * Structures used in the H.264 decoder + * + * \date + * 18/11/2002 + * + * \author Sriram Sethuraman + * + ************************************************************************ + */ + +/** + * Structure to represent a MV Bank buffer and col flag + */ +typedef struct +{ + /** + * Pointer to buffer that holds col flag. + */ + void *pv_col_zero_flag; + + /** + * Pointer to buffer that holds mv_pred + */ + void *pv_mv; + + }col_mv_buf_t; + + +typedef struct +{ + UWORD8 u1_dydx; /** 4*dy + dx for Y comp / 8*dy + dx for UV comp */ + UWORD8 u1_is_bi_direct; /** 1: is bi-direct 0: forward / backward only */ + UWORD8 u1_wght_pred_type; /** 0-default 1-singleWeighted 2-BiWeighted */ + WORD8 i1_mb_partwidth; /** Width of MB partition */ + WORD8 i1_mb_partheight; /** Height of MB partition */ + WORD8 i1_mc_wd; /** Number of bytes in a DMA stride */ + WORD8 i1_dma_ht; /** Number of strides */ + + WORD8 i1_pod_ht; /** Flag specifying height of pad on demand */ + /** 0 (No pod) -ve(Top pod) +ve(Bottom pod) */ + UWORD16 u2_dst_stride; /** Stride value of the destination */ + UWORD16 u2_u1_ref_buf_wd; /** Width of the ref buffer */ + UWORD16 u2_frm_wd; + UWORD16 u2_dummy; + + UWORD8 *u1_pi1_wt_ofst_rec_v; /** Pointer to packed weight and u4_ofst */ + UWORD8 *pu1_rec_y_u; /** MB partition address in row buffer */ + UWORD8 *pu1_dma_dest_addr; /** Destination address for DMA transfer */ + UWORD8 *pu1_y_ref; + UWORD8 *pu1_u_ref; + UWORD8 *pu1_v_ref; + + UWORD8 *pu1_pred; + UWORD8 *pu1_pred_u; + UWORD8 *pu1_pred_v; + UWORD8 u1_dma_wd_y; + UWORD8 u1_dma_ht_y; + UWORD8 u1_dma_wd_uv; + UWORD8 u1_dma_ht_uv; +} pred_info_t; + +typedef struct +{ + UWORD32 *pu4_wt_offst; + WORD16 i2_mv[2]; + + /***************************************************/ + /*packing information i1_size_pos_info */ + /* bit 1:0 -> X position in terms of (4x4) units */ + /* bit 3:2 -> Y position in terms of (4x4) units */ + /* bit 5:4 -> PU width 0:4,1:8,2:16 */ + /* bit 7:6 -> PU height 0:4,1:8,2:16 */ + /***************************************************/ + WORD8 i1_size_pos_info; + + /***************************************************/ + /*packing information ref idx info */ + /* bit 5:0 ->ref_idx */ + /* bit 6:7 -> 0:l0,1:l1,2:bipred */ + /***************************************************/ + WORD8 i1_ref_idx_info; + + WORD8 i1_buf_id; + + + UWORD8 u1_pic_type; /** frame /top field/bottom field/mbaff / complementary field pair */ + +}pred_info_pkd_t; +/*! Sequence level parameters */ + +typedef struct +{ + UWORD8 u1_seq_parameter_set_id; /** id for the seq par set 0-31 */ + UWORD8 u1_is_valid; /** is Seq Param set valid */ + + UWORD16 u2_frm_wd_in_mbs; /** Frame width expressed in MB units */ + UWORD16 u2_frm_ht_in_mbs; /** Frame height expressed in MB units */ + + /* Following are derived from the above two */ + UWORD16 u2_fld_ht_in_mbs; /** Field height expressed in MB units */ + UWORD16 u2_max_mb_addr; /** Total number of macroblocks in a coded picture */ + UWORD16 u2_total_num_of_mbs; /** Total number of macroblocks in a coded picture */ + UWORD32 u4_fld_ht; /** field height */ + UWORD32 u4_cwidth; /** chroma width */ + UWORD32 u4_chr_frm_ht; /** chroma height */ + UWORD32 u4_chr_fld_ht; /** chroma field height */ + UWORD8 u1_mb_aff_flag; /** 0 - no mb_aff; 1 - uses mb_aff */ + + UWORD8 u1_profile_idc; /** profile value */ + UWORD8 u1_level_idc; /** level value */ + + /* high profile related syntax elements */ + WORD32 i4_chroma_format_idc; + WORD32 i4_bit_depth_luma_minus8; + WORD32 i4_bit_depth_chroma_minus8; + WORD32 i4_qpprime_y_zero_transform_bypass_flag; + WORD32 i4_seq_scaling_matrix_present_flag; + UWORD8 u1_seq_scaling_list_present_flag[8]; + UWORD8 u1_use_default_scaling_matrix_flag[8]; + WORD16 i2_scalinglist4x4[6][16]; + WORD16 i2_scalinglist8x8[2][64]; + UWORD8 u1_more_than_one_slice_group_allowed_flag; + UWORD8 u1_arbitrary_slice_order_allowed_flag; + UWORD8 u1_redundant_slices_allowed_flag; + UWORD8 u1_bits_in_frm_num; /** Number of bits in frame num */ + UWORD16 u2_u4_max_pic_num_minus1; /** Maximum frame num minus 1 */ + UWORD8 u1_pic_order_cnt_type; /** 0 - 2 indicates the method to code picture order count */ + UWORD8 u1_log2_max_pic_order_cnt_lsb_minus; + WORD32 i4_max_pic_order_cntLsb; + UWORD8 u1_num_ref_frames_in_pic_order_cnt_cycle; + UWORD8 u1_delta_pic_order_always_zero_flag; + WORD32 i4_ofst_for_non_ref_pic; + WORD32 i4_ofst_for_top_to_bottom_field; + WORD32 i4_ofst_for_ref_frame[MAX_NUM_REF_FRAMES_OFFSET]; + UWORD8 u1_num_ref_frames; + UWORD8 u1_gaps_in_frame_num_value_allowed_flag; + UWORD8 u1_frame_mbs_only_flag; /** 1 - frame only; 0 - field/frame pic */ + UWORD8 u1_direct_8x8_inference_flag; + UWORD8 u1_vui_parameters_present_flag; + vui_t s_vui; +} dec_seq_params_t; + +typedef struct +{ + UWORD16 u2_frm_wd_in_mbs; /** Frame width expressed in MB units */ + UWORD16 u2_frm_ht_in_mbs; /** Frame height expressed in MB units */ + UWORD8 u1_frame_mbs_only_flag; /** 1 - frame only; 0 - field/frame pic */ + UWORD8 u1_profile_idc; /** profile value */ + UWORD8 u1_level_idc; /** level value */ + UWORD8 u1_direct_8x8_inference_flag; + UWORD8 u1_eoseq_pending; +} prev_seq_params_t; + +/** Picture level parameters */ +typedef struct +{ + dec_seq_params_t *ps_sps; /** applicable seq. parameter set */ + + /* High profile related syntax elements */ + WORD32 i4_transform_8x8_mode_flag; + WORD32 i4_pic_scaling_matrix_present_flag; + UWORD8 u1_pic_scaling_list_present_flag[8]; + UWORD8 u1_pic_use_default_scaling_matrix_flag[8]; + WORD16 i2_pic_scalinglist4x4[6][16]; + WORD16 i2_pic_scalinglist8x8[2][64]; + WORD8 i1_second_chroma_qp_index_offset; + + UWORD32 u4_slice_group_change_rate; + UWORD8 *pu1_slice_groupmb_map; /** MB map with slice membership labels */ + UWORD8 u1_pic_parameter_set_id; /** id for the picture par set 0-255*/ + UWORD8 u1_entropy_coding_mode; /** Entropy coding : 0-VLC; 1 - CABAC */ + UWORD8 u1_num_slice_groups; /** Number of slice groups */ + UWORD8 u1_pic_init_qp; /** Initial QPY for the picture {-26,25}*/ + WORD8 i1_chroma_qp_index_offset; /** Chroma QP u4_ofst w.r.t QPY {-12,12} */ + UWORD8 u1_dblk_filter_parms_flag; /** Slice layer has deblocking filter parameters */ + UWORD8 u1_constrained_intra_pred_flag; /** Constrained intra prediction u4_flag */ + UWORD8 u1_redundant_pic_cnt_present_flag; /** Redundant_pic_cnt is in slices using this PPS */ + UWORD8 u1_pic_order_present_flag; /** Pic order present u4_flag */ + UWORD8 u1_num_ref_idx_lx_active[2]; /** Maximum reference picture index in the reference list 0 : range [1 - 15] */ + UWORD8 u1_wted_pred_flag; + UWORD8 u1_wted_bipred_idc; + UWORD8 u1_pic_init_qs; + UWORD8 u1_deblocking_filter_parameters_present_flag; + UWORD8 u1_vui_pic_parameters_flag; + UWORD8 u1_mb_slice_group_map_type; + UWORD8 u1_slice_group_change_direction_flag; + UWORD8 u1_frame_cropping_flag; + UWORD8 u1_frame_cropping_rect_left_ofst; + UWORD8 u1_frame_cropping_rect_right_ofst; + UWORD8 u1_frame_cropping_rect_top_ofst; + UWORD8 u1_frame_cropping_rect_bottom_ofst; + void * pv_codec_handle; /* For Error Handling */ + WORD32 i4_top_field_order_cnt; + WORD32 i4_bottom_field_order_cnt; + WORD32 i4_avg_poc; + UWORD8 u1_is_valid; /** is Pic Param set valid */ +} dec_pic_params_t; + +/** Picture Order Count Paramsters */ +typedef struct +{ + WORD32 i4_pic_order_cnt_lsb; + WORD32 i4_pic_order_cnt_msb; + WORD32 i4_delta_pic_order_cnt_bottom; + WORD32 i4_delta_pic_order_cnt[2]; + WORD32 i4_prev_frame_num_ofst; + UWORD8 u1_mmco_equalto5; + UWORD8 u1_bot_field; + UWORD16 u2_frame_num; + WORD32 i4_top_field_order_count; + WORD32 i4_bottom_field_order_count; +} pocstruct_t; + +/*****************************************************************************/ +/* parse_mb_pers_info contains necessary mb info data required persistently */ +/* in the form of top and left neighbours. */ +/*****************************************************************************/ +typedef struct +{ + void *u4_pic_addrress[4]; /* picture address for BS calc */ + WORD8 pi1_intrapredmodes[4]; /* calc Intra pred modes */ + UWORD8 pu1_nnz_y[4]; + UWORD8 pu1_nnz_uv[4]; + UWORD8 u1_mb_fld; + UWORD8 u1_mb_type; + UWORD16 u2_luma_csbp; /* Luma csbp used for BS calc */ + UWORD8 u1_tran_form8x8; +} mb_neigbour_params_t; + +/* This info is required for decoding purposes except Deblockng */ +typedef struct _DecMbInfo +{ + UWORD8 u1_mb_type; /** macroblock type: I/P/B/SI/SP */ + UWORD8 u1_chroma_pred_mode; + UWORD8 u1_cbp; + UWORD8 u1_mb_mc_mode; /** 16x16, 2 16x8, 2 8x16, 4 8x8 */ + UWORD8 u1_topmb; /** top Mb u4_flag */ + UWORD8 u1_mb_ngbr_availablity; + UWORD8 u1_end_of_slice; + UWORD8 u1_mb_field_decodingflag; + UWORD8 u1_topleft_mb_fld; + UWORD8 u1_topleft_mbtype; + WORD8 i1_offset; + UWORD8 u1_Mux; + UWORD8 u1_qp_div6; + UWORD8 u1_qp_rem6; + UWORD8 u1_qpc_div6; + UWORD8 u1_qpcr_div6; + UWORD8 u1_qpc_rem6; + UWORD8 u1_qpcr_rem6; + UWORD8 u1_tran_form8x8; + UWORD8 u1_num_pred_parts; + UWORD8 u1_yuv_dc_block_flag; + UWORD16 u2_top_right_avail_mask; + UWORD16 u2_top_left_avail_mask; + UWORD16 u2_luma_csbp; /** Coded 4x4 Sub Block Pattern */ + UWORD16 u2_chroma_csbp; /** Coded 4x4 Sub Block Pattern */ + UWORD16 u2_mbx; + UWORD16 u2_mby; + UWORD16 u2_mask[2]; + + UWORD32 u4_pred_info_pkd_idx; + + mb_neigbour_params_t *ps_left_mb; + mb_neigbour_params_t *ps_top_mb; + mb_neigbour_params_t *ps_top_right_mb; + mb_neigbour_params_t *ps_curmb; +} dec_mb_info_t; + + +/** Slice level parameters */ +typedef struct +{ + dec_pic_params_t *ps_pps; /** PPS used */ + WORD32 i4_delta_pic_order_cnt[2]; + WORD32 i4_poc; /** Pic order cnt of picture to which slice belongs*/ + UWORD32 u4_idr_pic_id; /** IDR pic ID */ + UWORD16 u2_first_mb_in_slice; /** Address of first MB in slice*/ + UWORD16 u2_frame_num; /** Frame number from prev IDR pic */ + + UWORD8 u1_mbaff_frame_flag; /** Mb adaptive frame field u4_flag */ + UWORD8 u1_field_pic_flag; /** Field picture or not */ + UWORD8 u1_bottom_field_flag; /** If slice belongs to bot field pic */ + UWORD8 u1_slice_type; /** I/P/B/SI/SP */ + WORD32 i4_pic_order_cnt_lsb; /** Picture Order Count */ + UWORD8 u1_slice_qp; /** Add slice_qp_delta to pic_init_QP */ + UWORD8 u1_disable_dblk_filter_idc; /** 0-dblk all edges; 1 - suppress; 2 - suppress only edges */ + WORD8 i1_slice_alpha_c0_offset; /** dblk: alpha and C0 table u4_ofst {-12,12}*/ + WORD8 i1_slice_beta_offset; /** dblk: beta table u4_ofst {-12, 12}*/ + UWORD8 u1_sp_for_switch_flag; + UWORD8 u1_no_output_of_prior_pics_flag; + UWORD8 u1_long_term_reference_flag; + UWORD8 u1_num_ref_idx_lx_active[2]; + UWORD8 u1_cabac_init_idc; /** cabac_init_idc */ + UWORD8 u1_num_ref_idx_active_override_flag; + UWORD8 u1_direct_spatial_mv_pred_flag; + WORD32 (*pf_decodeDirect)(struct _DecStruct *ps_dec, + UWORD8 u1_wd_x, + dec_mb_info_t *ps_cur_mb_info, + UWORD8 u1_mb_num); + UWORD8 u1_redundant_pic_cnt; + WORD8 i1_slice_qs_delta; + UWORD8 u1_nal_ref_idc; /** NAL ref idc of the Slice NAL unit */ + UWORD8 u1_nal_unit_type; /** NAL unit type of the Slice NAL */ + UWORD8 u1_direct_8x8_inference_flag; + UWORD8 u1_mmco_equalto5; /** any of the MMCO command equal to 5 */ + UWORD8 u1_pic_order_cnt_type; + pocstruct_t s_POC; + /* DataStructures required for weighted prediction */ + UWORD16 u2_log2Y_crwd; /** Packed luma and chroma log2_weight_denom */ + /* [list0/list1]:[ref pics index]:[0-Y 1-Cb 2-Cr] [weight/u4_ofst], + weights and offsets are signed numbers, since they are packed, it is defined + unsigned. LSB byte : weight and MSB byte: u4_ofst */ + UWORD32 u4_wt_ofst_lx[2][MAX_REF_BUFS][3]; + void * pv_codec_handle; /* For Error Handling */ + + /* This is used when reordering is done in Forward or */ + /* backward lists. This is because reordering can point */ + /* to any valid entry in initial list irrespective of */ + /* num_ref_idx_active which could be overwritten using */ + /* ref_idx_reorder_flag */ + UWORD8 u1_initial_list_size[2]; + UWORD32 u4_mbs_in_slice; +} dec_slice_params_t; + + +typedef struct +{ + UWORD8 u1_mb_type; /* Bit representations, X- reserved */ + /** |Field/Frame|X|X|X|X|Bslice u4_flag|PRED_NON_16x16 u4_flag |Intra Mbflag| */ + UWORD8 u1_mb_qp; + UWORD8 u1_deblocking_mode; /** dblk: Mode [ NO / NO TOP / NO LEFT] filter */ + WORD8 i1_slice_alpha_c0_offset; /** dblk: alpha and C0 table u4_ofst {-12,12}*/ + WORD8 i1_slice_beta_offset; /** dblk: beta table u4_ofst {-12, 12}*/ + UWORD8 u1_single_call; + UWORD8 u1_topmb_qp; + UWORD8 u1_left_mb_qp; + UWORD32 u4_bs_table[10]; /* Boundary strength */ + +} deblk_mb_t; + +typedef struct +{ + UWORD8 u1_mb_type; + UWORD8 u1_mb_qp; +} deblkmb_neighbour_t; + +#define MAX_MV_RESIDUAL_INFO_PER_MB 32 +#define MAX_REFIDX_INFO_PER_MB 4 +#define PART_NOT_DIRECT 0 +#define PART_DIRECT_8x8 1 +#define PART_DIRECT_16x16 2 +typedef struct +{ + UWORD8 u1_is_direct; + UWORD8 u1_pred_mode; + UWORD8 u1_sub_mb_num; + UWORD8 u1_partheight; + UWORD8 u1_partwidth; +} parse_part_params_t; + +typedef struct +{ + UWORD8 u1_isI_mb; + UWORD8 u1_num_part; + UWORD32 *pu4_wt_offst[MAX_REFIDX_INFO_PER_MB]; + WORD8 i1_ref_idx[2][MAX_REFIDX_INFO_PER_MB]; + UWORD8 u1_col_info[MAX_REFIDX_INFO_PER_MB]; +} parse_pmbarams_t; + +typedef struct +{ + UWORD8 u1_vert_pad_top; /* flip-flop u4_flag remembering pad area (Vert) */ + UWORD8 u1_vert_pad_bot; /* flip-flop u4_flag remembering pad area (Vert) */ + UWORD8 u1_horz_pad; /* flip-flop u4_flag remembering pad area (Vert) */ + UWORD8 u1_pad_len_y_v; /* vertical pad amount for luma */ + UWORD8 u1_pad_len_cr_v; /* vertical pad amount for chroma */ +} pad_mgr_t; + + +#define ACCEPT_ALL_PICS (0x00) +#define REJECT_CUR_PIC (0x01) +#define REJECT_PB_PICS (0x02) + +#define MASK_REJECT_CUR_PIC (0xFE) +#define MASK_REJECT_PB_PICS (0xFD) + +#define PIC_TYPE_UNKNOWN (0xFF) +#define PIC_TYPE_I (0x00) +#define SYNC_FRM_DEFAULT (0xFFFFFFFF) +#define INIT_FRAME (0xFFFFFF) + +typedef struct dec_err_status_t +{ + UWORD8 u1_cur_pic_type; + UWORD8 u1_pic_aud_i; + UWORD8 u1_err_flag; + UWORD32 u4_frm_sei_sync; + UWORD32 u4_cur_frm; +} dec_err_status_t; + +/**************************************************************************/ +/* Structure holds information about all high profile toolsets */ +/**************************************************************************/ +typedef struct +{ + /*****************************************/ + /* variables required for scaling */ + /*****************************************/ + UWORD8 u1_scaling_present; + WORD16 *pi2_scale_mat[8]; + + /*************************************************/ + /* scaling matrices for frame macroblocks after */ + /* inverse scanning */ + /*************************************************/ + WORD16 i2_scalinglist4x4[6][16]; + WORD16 i2_scalinglist8x8[2][64]; + + + /*****************************************/ + /* variables required for transform8x8 */ + /*****************************************/ + UWORD8 u1_transform8x8_present; + UWORD8 u1_direct_8x8_inference_flag; + /* temporary variable to get noSubMbPartSizeLessThan8x8Flag from ih264d_parse_bmb_non_direct_cavlc */ + UWORD8 u1_no_submb_part_size_lt8x8_flag; + + /* needed for inverse scanning */ + cavlc_cntxt_t s_cavlc_ctxt; + + /* contexts for the CABAC related parsing */ + bin_ctxt_model_t *ps_transform8x8_flag; + bin_ctxt_model_t *ps_sigcoeff_8x8_frame; + bin_ctxt_model_t *ps_last_sigcoeff_8x8_frame; + bin_ctxt_model_t *ps_coeff_abs_levelminus1; + bin_ctxt_model_t *ps_sigcoeff_8x8_field; + bin_ctxt_model_t *ps_last_sigcoeff_8x8_field; + +/* variables required for intra8x8 */ + +/* variables required for handling different Qp for Cb and Cr */ + +} high_profile_tools_t; + +typedef struct +{ + UWORD32 u4_num_bufs; /* Number of buffers in each display frame. 2 for 420SP and 3 for 420P and so on */ + void *buf[3]; /* Pointers to each of the components */ + UWORD32 u4_bufsize[3]; + UWORD32 u4_ofst[3]; +} disp_buf_t; +typedef struct _dec_slice_struct +{ + volatile UWORD32 u4_first_mb_in_slice; + volatile UWORD32 slice_type; + volatile UWORD16 u2_log2Y_crwd; + volatile void **ppv_map_ref_idx_to_poc; + volatile void *pv_tu_coeff_data_start; +} dec_slice_struct_t; + +/** + * Structure to hold coefficient info for a 4x4 transform + */ +typedef struct +{ + /** + * significant coefficient map + */ + UWORD16 u2_sig_coeff_map; + + /** + * holds coefficients + */ + WORD16 ai2_level[16]; +}tu_sblk4x4_coeff_data_t; + +/** + * Structure to hold coefficient info for a 8x8 transform + */ +typedef struct +{ + + /** + * significant coefficient map + */ + UWORD32 au4_sig_coeff_map[2]; + + /** + * holds coefficients + */ + WORD16 ai2_level[64]; +}tu_blk8x8_coeff_data_t; + +/** Aggregating structure that is globally available */ +typedef struct _DecStruct +{ + + /* Add below all other static memory allocations and pointers to items + that are dynamically allocated once per session */ + dec_bit_stream_t *ps_bitstrm; + dec_seq_params_t *ps_cur_sps; + dec_pic_params_t *ps_cur_pps; + dec_slice_params_t *ps_cur_slice; + + dec_pic_params_t *ps_pps; + dec_seq_params_t *ps_sps; + const UWORD16 *pu2_quant_scale_y; + const UWORD16 *pu2_quant_scale_u; + const UWORD16 *pu2_quant_scale_v; + UWORD16 u2_mbx; + UWORD16 u2_mby; + + UWORD16 u2_frm_wd_y; /** Width for luma buff */ + UWORD16 u2_frm_ht_y; /** Height for luma buff */ + UWORD16 u2_frm_wd_uv; /** Width for chroma buff */ + UWORD16 u2_frm_ht_uv; /** Height for chroma buff */ + UWORD16 u2_frm_wd_in_mbs; /** Frame width expressed in MB units */ + UWORD16 u2_frm_ht_in_mbs; /** Frame height expressed in MB units */ + WORD32 i4_submb_ofst; /** Offset in subMbs from the top left edge */ + /* Pointer to colocated Zero frame Image, will be used in B_DIRECT mode */ + /* colZeroFlag | // 0th bit + field_flag | // 1st bit + XX | // 2:3 bit don't cares + subMbMode | // 4:5 bit + MbMode | // 6:7 bit */ + + UWORD8 *pu1_col_zero_flag; + + UWORD16 u2_pic_wd; /** Width of the picture being decoded */ + UWORD16 u2_pic_ht; /** Height of the picture being decoded */ + + UWORD8 u1_first_slice_in_stream; + UWORD8 u1_mb_ngbr_availablity; + UWORD8 u1_ref_idxl0_active_minus1; + UWORD8 u1_qp; + UWORD8 u1_qp_y_div6; + UWORD8 u1_qp_u_div6; + UWORD8 u1_qp_y_rem6; + UWORD8 u1_qp_u_rem6; + + /*********************************/ + /* configurable mb-group numbers */ + /* very critical to the decoder */ + /*********************************/ + /************************************************************/ + /* MB_GROUP should be a multiple of 2 */ + /************************************************************/ + UWORD8 u1_recon_mb_grp; + UWORD8 u1_recon_mb_grp_pair; + /* Variables to handle Cabac */ + decoding_envirnoment_t s_cab_dec_env; /* < Structure for decoding_envirnoment_t */ + /* These things need to be updated at each MbLevel */ + WORD8 i1_next_ctxt_idx; /* < next Ctxt Index */ + UWORD8 u1_currB_type; + WORD8 i1_prev_mb_qp_delta; /* Prev MbQpDelta */ + UWORD8 u1_nal_unit_type; + + ctxt_inc_mb_info_t *p_ctxt_inc_mb_map; /* Pointer to ctxt_inc_mb_info_t map */ + ctxt_inc_mb_info_t *p_left_ctxt_mb_info; /* Pointer to left ctxt_inc_mb_info_t */ + ctxt_inc_mb_info_t *p_top_ctxt_mb_info; /* Pointer to top ctxt_inc_mb_info_t */ + ctxt_inc_mb_info_t *ps_curr_ctxt_mb_info; /* Pointer to current ctxt_inc_mb_info_t */ + ctxt_inc_mb_info_t *ps_def_ctxt_mb_info; /* Pointer to default ctxt_inc_mb_info_t */ + + /* mv contexts for mv decoding using cabac */ + //UWORD8 u1_top_mv_ctxt_inc[4][4]; + /* Dimensions for u1_left_mv_ctxt_inc_arr is [2][4][4] for Mbaff case */ + UWORD8 u1_left_mv_ctxt_inc_arr[2][4][4]; + UWORD8 (*pu1_left_mv_ctxt_inc)[4]; + + UWORD8 u1_sub_mb_num; + UWORD8 u1_B; /** if B slice u1_B = 1 else 0 */ + WORD16 i2_only_backwarddma_info_idx; + mv_pred_t *ps_mv; /** Pointer to the MV bank array */ + mv_pred_t *ps_mv_bank_cur; /** Pointer to the MV bank array */ + mv_pred_t s_default_mv_pred; /** Structure containing the default values + for MV predictor */ + + pred_info_t *ps_pred; /** Stores info to cfg MC */ + pred_info_t *ps_pred_start; + + UWORD32 u4_pred_info_idx; + pred_info_pkd_t *ps_pred_pkd; + pred_info_pkd_t *ps_pred_pkd_start; + UWORD32 u4_pred_info_pkd_idx; + UWORD8 *pu1_ref_buff; /** Destination buffer for DMAs */ + UWORD32 u4_dma_buf_idx; + + UWORD8 *pu1_y; + UWORD8 *pu1_u; + UWORD8 *pu1_v; + + WORD16 *pi2_y_coeff; + UWORD8 *pu1_inv_scan; + + /** + * Pointer frame level TU subblock coeff data + */ + void *pv_pic_tu_coeff_data; + + /** + * Pointer to TU subblock coeff data and number of subblocks and scan idx + * Incremented each time a coded subblock is processed + * + */ + void *pv_parse_tu_coeff_data; + void *pv_prev_mb_parse_tu_coeff_data; + + void *pv_proc_tu_coeff_data; + + WORD16 *pi2_coeff_data; + + cavlc_cntxt_t s_cavlc_ctxt; + + UWORD32 u4_n_leftY[2]; + UWORD32 u4_n_left_cr[2]; + UWORD32 u4_n_left_temp_y; + + UWORD8 pu1_left_nnz_y[4]; + UWORD8 pu1_left_nnz_uv[4]; + UWORD32 u4_n_left_temp_uv; + /***************************************************************************/ + /* Base pointer to all the cabac contexts */ + /***************************************************************************/ + bin_ctxt_model_t *p_cabac_ctxt_table_t; + + /***************************************************************************/ + /* cabac context pointers for every SE mapped into in p_cabac_ctxt_table_t */ + /***************************************************************************/ + bin_ctxt_model_t *p_mb_type_t; + bin_ctxt_model_t *p_mb_skip_flag_t; + bin_ctxt_model_t *p_sub_mb_type_t; + bin_ctxt_model_t *p_mvd_x_t; + bin_ctxt_model_t *p_mvd_y_t; + bin_ctxt_model_t *p_ref_idx_t; + bin_ctxt_model_t *p_mb_qp_delta_t; + bin_ctxt_model_t *p_intra_chroma_pred_mode_t; + bin_ctxt_model_t *p_prev_intra4x4_pred_mode_flag_t; + bin_ctxt_model_t *p_rem_intra4x4_pred_mode_t; + bin_ctxt_model_t *p_mb_field_dec_flag_t; + bin_ctxt_model_t *p_cbp_luma_t; + bin_ctxt_model_t *p_cbp_chroma_t; + bin_ctxt_model_t *p_cbf_t[NUM_CTX_CAT]; + bin_ctxt_model_t *p_significant_coeff_flag_t[NUM_CTX_CAT]; + bin_ctxt_model_t *p_coeff_abs_level_minus1_t[NUM_CTX_CAT]; + + UWORD32 u4_num_pmbair; /** MB pair number */ + mv_pred_t *ps_mv_left; /** Pointer to left motion vector bank */ + mv_pred_t *ps_mv_top_left; /** Pointer to top left motion vector bank */ + mv_pred_t *ps_mv_top_right; /** Pointer to top right motion vector bank */ + + UWORD8 *pu1_left_yuv_dc_csbp; + + + deblkmb_neighbour_t deblk_left_mb[2]; + deblkmb_neighbour_t *ps_deblk_top_mb; + neighbouradd_t (*ps_left_mvpred_addr)[2]; /* Left MvPred Address Ping Pong*/ + + /***************************************************************************/ + /* Ref_idx contexts are stored in the following way */ + /* Array Idx 0,1 for reference indices in Forward direction */ + /* Array Idx 2,3 for reference indices in backward direction */ + /***************************************************************************/ + + /* Dimensions for u1_left_ref_ctxt_inc_arr is [2][4] for Mbaff:Top and Bot */ + WORD8 i1_left_ref_idx_ctx_inc_arr[2][4]; + WORD8 *pi1_left_ref_idx_ctxt_inc; + + /*************************************************************************/ + /* Arrangnment of DC CSBP */ + /* bits: b7 b6 b5 b4 b3 b2 b1 b0 */ + /* CSBP: x x x x x Vdc Udc Ydc */ + /*************************************************************************/ + /*************************************************************************/ + /* Points either to u1_yuv_dc_csbp_topmb or u1_yuv_dc_csbp_bot_mb */ + /*************************************************************************/ + UWORD8 u1_yuv_dc_csbp_topmb; + UWORD8 u1_yuv_dc_csbp_bot_mb; + + /* DMA SETUP */ + tfr_ctxt_t s_tran_addrecon_parse; + tfr_ctxt_t s_tran_addrecon; + tfr_ctxt_t s_tran_iprecon; + tfr_ctxt_t *ps_frame_buf_ip_recon; + WORD8 i1_recon_in_thread3_flag; + + /* slice Header Simplification */ + UWORD8 u1_pr_sl_type; + WORD32 i4_frametype; + UWORD32 u4_app_disp_width; + WORD32 i4_error_code; + UWORD32 u4_bitoffset; + + /* Variables added to handle field pics */ + + UWORD8 u1_second_field; + WORD32 i4_pic_type; + WORD32 i4_content_type; + WORD32 i4_decode_header; + WORD32 i4_header_decoded; + UWORD32 u4_total_frames_decoded; + + ctxt_inc_mb_info_t *ps_left_mb_ctxt_info; /* structure containing the left MB's + context info, incase of Mbaff */ + pocstruct_t s_prev_pic_poc; + pocstruct_t s_cur_pic_poc; + WORD32 i4_cur_display_seq; + WORD32 i4_prev_max_display_seq; + WORD32 i4_max_poc; + deblk_mb_t *ps_cur_deblk_mb; + + /* Pointers to local scratch buffers */ + deblk_mb_t *ps_deblk_pic; + + /* Pointers to Picture Buffers (Given by BufAPI Lib) */ + struct pic_buffer_t *ps_cur_pic; /** Pointer to Current picture buffer */ + + /* Scratch Picture Buffers (Given by BufAPI Lib) */ + struct pic_buffer_t s_cur_pic; + + /* Current Slice related information */ + volatile UWORD16 u2_cur_slice_num; + volatile UWORD16 u2_cur_slice_num_dec_thread; + + /* Variables needed for Buffer API handling */ + UWORD8 u1_nal_buf_id; + UWORD8 u1_pic_buf_id; + UWORD8 u1_pic_bufs; + + WORD16 *pi2_pred1; //[441]; /** Temp predictor buffer for MC */ + /* Pointer to refernce Pic buffers list, 0:fwd, 1:bwd */ + pic_buffer_t **ps_ref_pic_buf_lx[2]; + /* refIdx to POC mapping */ + void **ppv_map_ref_idx_to_poc; + void **ppv_map_ref_idx_to_poc_base; + UWORD32 *pu4_wts_ofsts_mat; + UWORD32 *pu4_wt_ofsts; + UWORD32 *pu4_mbaff_wt_mat; + /* Function pointers to read Params common to CAVLC and CABAC */ + WORD32 (*pf_parse_inter_mb)(struct _DecStruct * ps_dec, + dec_mb_info_t * ps_cur_mb_info, + UWORD8 u1_mb_num, + UWORD8 u1_num_mbsNby2); + WORD32 (*pf_mvpred_ref_tfr_nby2mb)(struct _DecStruct * ps_dec, + UWORD8 u1_num_mbs, + UWORD8 u1_num_mbsNby2); + + WORD32 (*pf_parse_inter_slice)(struct _DecStruct * ps_dec, + dec_slice_params_t * ps_slice, + UWORD16 u2_first_mb_in_slice); + + UWORD32 (*pf_get_mb_info)(struct _DecStruct * ps_dec, + const UWORD16 u2_cur_mb_address, + dec_mb_info_t * ps_cur_mb_info, + UWORD32 u4_mbskip_run); + + /* Variables for Decode Buffer Management */ + dpb_manager_t *ps_dpb_mgr; + dpb_commands_t *ps_dpb_cmds; + dpb_commands_t s_dpb_cmds_scratch; + + /* Variables Required for N MB design */ + dec_mb_info_t *ps_nmb_info; + + UWORD8 *pu1_y_intra_pred_line; + UWORD8 *pu1_u_intra_pred_line; + UWORD8 *pu1_v_intra_pred_line; + + UWORD8 *pu1_cur_y_intra_pred_line; + UWORD8 *pu1_cur_u_intra_pred_line; + UWORD8 *pu1_cur_v_intra_pred_line; + + UWORD8 *pu1_cur_y_intra_pred_line_base; + UWORD8 *pu1_cur_u_intra_pred_line_base; + UWORD8 *pu1_cur_v_intra_pred_line_base; + + UWORD8 *pu1_prev_y_intra_pred_line; + UWORD8 *pu1_prev_u_intra_pred_line; + UWORD8 *pu1_prev_v_intra_pred_line; + + UWORD32 u4_intra_pred_line_ofst; + + UWORD8 u1_res_changed; + + mv_pred_t *ps_mv_cur; /** pointer to current motion vector bank */ + mv_pred_t *ps_mv_top; /** pointer to top motion vector bank */ + mv_pred_t *ps_mv_top_right2;/** Pointer to top right motion vector bank */ + mv_pred_t *ps_mv_p[2]; /** Scratch ping motion vector bank */ + mv_pred_t *ps_mv_top_p[MV_SCRATCH_BUFS]; /** Scratch top pong motion vector bank */ + UWORD8 u1_mv_top_p; + + deblk_mb_t *ps_deblk_mbn; + + UWORD8 *pu1_temp_mc_buffer; + + struct _sei *ps_sei; + struct _sei *ps_sei_parse; + struct _sei s_sei_export; + + void *pv_disp_sei_params; + + UWORD8 u1_pic_struct_copy; + /* Variables required for cropping */ + UWORD16 u2_disp_width; + UWORD16 u2_disp_height; + UWORD16 u2_crop_offset_y; + UWORD16 u2_crop_offset_uv; + + /* Crop info from SPS */ + UWORD8 u1_frame_cropping_flag; + UWORD8 u1_frame_cropping_rect_left_ofst; + UWORD8 u1_frame_cropping_rect_right_ofst; + UWORD8 u1_frame_cropping_rect_top_ofst; + UWORD8 u1_frame_cropping_rect_bottom_ofst; + + /* Variable required to get presentation time stamp through application */ + UWORD32 u4_pts; + + /* Variables used for gaps in frame number */ + UWORD16 u2_prev_ref_frame_num; + + UWORD8 u1_mb_idx; + struct pic_buffer_t *ps_col_pic; + void (*pf_parse_mvdirect)(struct _DecStruct*, + struct pic_buffer_t*, + directmv_t*, + UWORD8, + WORD32, + dec_mb_info_t *); + void *pv_dec_out; + void *pv_dec_in; + void *pv_scratch_sps_pps; /*used temeporarily store sps/ spps while parsing*/ + + /* state pointers to mb and partition information */ + parse_pmbarams_t *ps_parse_mb_data; + parse_part_params_t *ps_parse_part_params; + + /* scratch pointers to mb and partition information */ + parse_part_params_t *ps_part; + + UWORD8 u1_max_dec_frame_buffering; + pad_mgr_t s_pad_mgr; + UWORD8 (*pf_mvpred)(struct _DecStruct *ps_dec, + struct _DecMbInfo *ps_cur_mb_info, + mv_pred_t *ps_mv_pred, + mv_pred_t *ps_mv_nmb, + mv_pred_t *ps_mv_ntop, + UWORD8 u1_sub_mb_num, + UWORD8 uc_mb_part_width, + UWORD8 uc_lxstart, + UWORD8 uc_lxend, + UWORD8 u1_mb_mc_mode); + void (*pf_compute_bs)(struct _DecStruct * ps_dec, + struct _DecMbInfo * ps_cur_mb_info, + const UWORD16 u2_mbxn_mb); + UWORD8 u1_init_dec_flag; + WORD32 i4_reorder_depth; + prev_seq_params_t s_prev_seq_params; + UWORD8 u1_cur_mb_fld_dec_flag; /* current Mb fld or Frm */ + + UWORD8 u1_topleft_mb_fld; + UWORD8 u1_topleft_mbtype; + UWORD8 u1_topleft_mb_fld_bot; + UWORD8 u1_topleft_mbtype_bot; + WORD16 i2_prev_slice_mbx; + WORD16 i2_prev_slice_mby; + UWORD16 u2_top_left_mask; + UWORD16 u2_top_right_mask; + dec_err_status_t * ps_dec_err_status; + /* Ensure pi1_left_pred_mode is aligned to 4 byte boundary, + by declaring this after a pointer or an integer */ + WORD8 pi1_left_pred_mode[8]; + + UWORD8 u1_mb_idx_mv; + UWORD16 u2_mv_2mb[2]; + UWORD32 u4_skip_frm_mask; + + /* variable for finding the no.of mbs decoded in the current picture */ + UWORD16 u2_total_mbs_coded; + /* member added for supporting fragmented annex - B */ +// frg_annex_read_t s_frag_annex_read; + /* added for vui_t, sei support*/ + WORD32 i4_vui_frame_rate; + /* To Store the value of ref_idx_active for previous slice */ + /* useful in error handling */ + UWORD8 u1_num_ref_idx_lx_active_prev; + /* Flag added to come out of process call in annex-b if&if frame is decoded */ + /* presence of access unit delimters and pps and sps */ + UWORD8 u1_frame_decoded_flag; + + /* To keep track of whether the last picture was decoded or not */ + /* in case of skip mode set by the application */ + UWORD8 u1_last_pic_not_decoded; + + WORD32 e_dec_status; + UWORD32 u4_num_fld_in_frm; + + /* Function pointer for 4x4 residual cavlc parsing based on total coeff */ + WORD32 (*pf_cavlc_4x4res_block[3])(UWORD32 u4_isdc, + UWORD32 u4_total_coeff_trail_one, /**TotalCoefficients<<16+trailingones*/ + dec_bit_stream_t *ps_bitstrm); + + /* Function pointer array for interpolate functions in called from motion compensattion module */ + void (*p_mc_interpolate_x_y[16][3])(UWORD8*, + UWORD8*, + UWORD8*, + UWORD8, + UWORD16, + UWORD16, + UWORD8); + + /**************************************************************************/ + /* Function pointer for 4x4 totalcoeff, trlone and residual cavlc parsing */ + /* based on u4_n (neigbourinng nnz average) */ + /* These point to two functions depending on (u4_n > 7) and (u4_n <= 7) */ + /**************************************************************************/ + WORD32 (*pf_cavlc_parse4x4coeff[2])(WORD16 *pi2_coeff_block, + UWORD32 u4_isdc, /* is it a DC block */ + WORD32 u4_n, + struct _DecStruct *ps_dec, /** Decoder Parameters */ + UWORD32 *pu4_total_coeff); + + /**************************************************************************/ + /* Function pointer for luma 8x8block cavlc parsing based on top and left */ + /* neigbour availability. */ + /**************************************************************************/ + WORD32 (*pf_cavlc_parse_8x8block[4])(WORD16 *pi2_coeff_block, + UWORD32 u4_sub_block_strd, + UWORD32 u4_isdc, + struct _DecStruct *ps_dec, + UWORD8 *pu1_top_nnz, + UWORD8 *pu1_left_nnz, + UWORD8 u1_tran_form8x8, + UWORD8 u1_mb_field_decodingflag, + UWORD32 *pu4_csbp); + + /**************************************************************************/ + /* Ping pong top and current rows of mb neigbour_params */ + /**************************************************************************/ + mb_neigbour_params_t *ps_nbr_mb_row; + mb_neigbour_params_t *ps_cur_mb_row; + mb_neigbour_params_t *ps_top_mb_row; + + /**************************************************************************/ + /* Function pointer for 16x16 and non16x16 Bs1 calculations depending on */ + /* P and B slice. */ + /***************************************************************************/ + void (*pf_fill_bs1[2][2])(mv_pred_t *ps_cur_mv_pred, + mv_pred_t *ps_top_mv_pred, + void **ppv_map_ref_idx_to_poc, + UWORD32 *pu4_bs_table, /* pointer to the BsTable array */ + mv_pred_t *ps_leftmost_mv_pred, + neighbouradd_t *ps_left_addr, + void **u4_pic_addrress, + WORD32 i4_ver_mvlimit); + + void (*pf_fill_bs_xtra_left_edge[2])(UWORD32 *pu4_bs, /* Base pointer of BS table */ + WORD32 u4_left_mb_t_csbp, /* left mbpair's top csbp */ + WORD32 u4_left_mb_b_csbp, /* left mbpair's bottom csbp*/ + WORD32 u4_cur_mb_csbp, /* csbp of current mb */ + UWORD32 u4_cur_mb_bot /* is top or bottom mb */ + + ); + /* Function pointer array for BP and MP functions for MC*/ + void (*p_motion_compensate)(struct _DecStruct * ps_dec, + dec_mb_info_t *ps_cur_mb_info); + + + void (*p_mc_dec_thread)(struct _DecStruct * ps_dec, dec_mb_info_t *ps_cur_mb_info); + + /* Function pointer array for BP and MP functions for formMbPartInfo*/ + + WORD32 (*p_form_mb_part_info)(pred_info_pkd_t *ps_pred_pkd, + struct _DecStruct * ps_dec, + UWORD16 u2_mb_x, + UWORD16 u2_mb_y, + WORD32 mb_index, + dec_mb_info_t *ps_cur_mb_info); + + WORD32 (*p_form_mb_part_info_thread)(pred_info_pkd_t *ps_pred_pkd, + struct _DecStruct * ps_dec, + UWORD16 u2_mb_x, + UWORD16 u2_mb_y, + WORD32 mb_index, + dec_mb_info_t *ps_cur_mb_info); + + + /* Required for cabac mbaff bottom mb */ + UWORD32 u4_next_mb_skip; + + void (*p_DeblockPicture[2])(struct _DecStruct *); + + /* ! */ + UWORD32 u4_ts; + UWORD8 u1_flushfrm; + + /* Output format sent by the application */ + UWORD8 u1_chroma_format; + UWORD8 u1_pic_decode_done; + UWORD8 u1_slice_header_done; + WORD32 init_done; + + /******************************************/ + /* For the high profile related variables */ + /******************************************/ + high_profile_tools_t s_high_profile; + /* CBCR */ + UWORD8 u1_qp_v_div6; + UWORD8 u1_qp_v_rem6; + /* + * TO help solve the dangling field case. + * Check for the previous frame number and the current frame number. + */ + UWORD16 u2_prv_frame_num; + UWORD8 u1_top_bottom_decoded; + UWORD8 u1_dangling_field; + + IVD_DISPLAY_FRAME_OUT_MODE_T e_frm_out_mode; + + UWORD8 *pu1_bits_buf_static; + UWORD8 *pu1_bits_buf_dynamic; + + UWORD32 u4_static_bits_buf_size; + UWORD32 u4_dynamic_bits_buf_size; + + UWORD32 u4_num_disp_bufs_requested; + WORD32 i4_display_delay; + UWORD32 u4_slice_start_code_found; + + UWORD32 u4_nmb_deblk; + UWORD32 u4_use_intrapred_line_copy; + UWORD32 u4_num_mbs_prev_nmb; + UWORD32 u4_num_mbs_cur_nmb; + UWORD32 u4_app_deblk_disable_level; + UWORD32 u4_app_disable_deblk_frm; + WORD32 i4_mv_frac_mask; + + disp_buf_t disp_bufs[MAX_DISP_BUFS_NEW]; + UWORD32 u4_disp_buf_mapping[MAX_DISP_BUFS_NEW]; + UWORD32 u4_disp_buf_to_be_freed[MAX_DISP_BUFS_NEW]; + UWORD32 u4_share_disp_buf; + UWORD32 u4_num_disp_bufs; + + UWORD32 u4_bs_deblk_thread_created; + volatile UWORD32 u4_start_recon_deblk; + void *pv_bs_deblk_thread_handle; + + UWORD32 u4_cur_bs_mb_num; + UWORD32 u4_bs_cur_slice_num_mbs; + UWORD32 u4_cur_deblk_mb_num; + UWORD32 u4_sps_cnt_in_process; + volatile UWORD16 u2_cur_slice_num_bs; + + UWORD32 u4_deblk_mb_x; + UWORD32 u4_deblk_mb_y; + + + + iv_yuv_buf_t s_disp_frame_info; + UWORD32 u4_fmt_conv_num_rows; + UWORD32 u4_fmt_conv_cur_row; + ivd_out_bufdesc_t *ps_out_buffer; + ivd_get_display_frame_op_t s_disp_op; + UWORD32 u4_output_present; + + volatile UWORD16 cur_dec_mb_num; + volatile UWORD16 cur_recon_mb_num; + volatile UWORD16 u2_cur_mb_addr; + WORD16 i2_dec_thread_mb_y; + WORD16 i2_recon_thread_mb_y; + + UWORD8 u1_separate_parse; + UWORD32 u4_dec_thread_created; + void *pv_dec_thread_handle; + volatile UWORD8 *pu1_dec_mb_map; + volatile UWORD8 *pu1_recon_mb_map; + volatile UWORD16 *pu2_slice_num_map; + dec_slice_struct_t *ps_dec_slice_buf; + void *pv_map_ref_idx_to_poc_buf; + dec_mb_info_t *ps_frm_mb_info; + volatile dec_slice_struct_t * volatile ps_parse_cur_slice; + volatile dec_slice_struct_t * volatile ps_decode_cur_slice; + volatile dec_slice_struct_t * volatile ps_computebs_cur_slice; + UWORD32 u4_cur_slice_decode_done; + UWORD32 u4_extra_mem_used; + + /* 2 first slice not parsed , 1 :first slice parsed , 0 :first valid slice header parsed*/ + UWORD32 u4_first_slice_in_pic; + UWORD32 u4_num_cores; + IVD_ARCH_T e_processor_arch; + IVD_SOC_T e_processor_soc; + + /** + * Pictures that are are degraded + * 0 : No degrade + * 1 : Only on non-reference frames + * 2 : Use interval specified by u4_nondegrade_interval + * 3 : All non-key frames + * 4 : All frames + */ + WORD32 i4_degrade_pics; + + /** + * Interval for pictures which are completely decoded without any degradation + */ + WORD32 i4_nondegrade_interval; + + /** + * bit position (lsb is zero): Type of degradation + * 1 : Disable deblocking + * 2 : Faster inter prediction filters + * 3 : Fastest inter prediction filters + */ + WORD32 i4_degrade_type; + + /** Degrade pic count, Used to maintain the interval between non-degraded pics + * + */ + WORD32 i4_degrade_pic_cnt; + WORD32 i4_display_index; + UWORD32 u4_pic_buf_got; + + /** + * Col flag and mv pred buffer manager + */ + void *pv_mv_buf_mgr; + + /** + * Picture buffer manager + */ + void *pv_pic_buf_mgr; + + /** + * Display buffer manager + */ + void *pv_disp_buf_mgr; + + void *apv_buf_id_pic_buf_map[MAX_DISP_BUFS_NEW]; + + UWORD8 au1_pic_buf_id_mv_buf_id_map[MAX_DISP_BUFS_NEW]; + + UWORD8 au1_pic_buf_ref_flag[MAX_DISP_BUFS_NEW]; + + struct pic_buffer_t *ps_pic_buf_base; + + UWORD8 *pu1_ref_buff_base; + col_mv_buf_t *ps_col_mv_base; + void *(*pf_aligned_alloc)(void *pv_mem_ctxt, WORD32 alignment, WORD32 size); + void (*pf_aligned_free)(void *pv_mem_ctxt, void *pv_buf); + void *pv_mem_ctxt; + + UWORD8 *pu1_pic_buf_base; + UWORD8 *pu1_mv_bank_buf_base; + UWORD8 *pu1_init_dpb_base; + + ih264_default_weighted_pred_ft *pf_default_weighted_pred_luma; + + ih264_default_weighted_pred_ft *pf_default_weighted_pred_chroma; + + ih264_weighted_pred_ft *pf_weighted_pred_luma; + + ih264_weighted_pred_ft *pf_weighted_pred_chroma; + + ih264_weighted_bi_pred_ft *pf_weighted_bi_pred_luma; + + ih264_weighted_bi_pred_ft *pf_weighted_bi_pred_chroma; + + ih264_pad *pf_pad_top; + ih264_pad *pf_pad_bottom; + ih264_pad *pf_pad_left_luma; + ih264_pad *pf_pad_left_chroma; + ih264_pad *pf_pad_right_luma; + ih264_pad *pf_pad_right_chroma; + + ih264_inter_pred_chroma_ft *pf_inter_pred_chroma; + + ih264_inter_pred_luma_ft *apf_inter_pred_luma[16]; + + ih264_intra_pred_luma_ft *apf_intra_pred_luma_16x16[4]; + + ih264_intra_pred_luma_ft *apf_intra_pred_luma_8x8[9]; + + ih264_intra_pred_luma_ft *apf_intra_pred_luma_4x4[9]; + + ih264_intra_pred_ref_filtering_ft *pf_intra_pred_ref_filtering; + + ih264_intra_pred_chroma_ft *apf_intra_pred_chroma[4]; + + ih264_iquant_itrans_recon_ft *pf_iquant_itrans_recon_luma_4x4; + + ih264_iquant_itrans_recon_ft *pf_iquant_itrans_recon_luma_4x4_dc; + + ih264_iquant_itrans_recon_ft *pf_iquant_itrans_recon_luma_8x8; + + ih264_iquant_itrans_recon_ft *pf_iquant_itrans_recon_luma_8x8_dc; + + ih264_iquant_itrans_recon_chroma_ft *pf_iquant_itrans_recon_chroma_4x4; + + ih264_iquant_itrans_recon_chroma_ft *pf_iquant_itrans_recon_chroma_4x4_dc; + + ih264_ihadamard_scaling_ft *pf_ihadamard_scaling_4x4; + + /** + * deblock vertical luma edge with blocking strength 4 + */ + ih264_deblk_edge_bs4_ft *pf_deblk_luma_vert_bs4; + + /** + * deblock vertical luma edge with blocking strength less than 4 + */ + ih264_deblk_edge_bslt4_ft *pf_deblk_luma_vert_bslt4; + + /** + * deblock vertical luma edge with blocking strength 4 for mbaff + */ + ih264_deblk_edge_bs4_ft *pf_deblk_luma_vert_bs4_mbaff; + + /** + * deblock vertical luma edge with blocking strength less than 4 for mbaff + */ + ih264_deblk_edge_bslt4_ft *pf_deblk_luma_vert_bslt4_mbaff; + + /** + * deblock vertical chroma edge with blocking strength 4 + */ + ih264_deblk_chroma_edge_bs4_ft *pf_deblk_chroma_vert_bs4; + + /** + * deblock vertical chroma edge with blocking strength less than 4 + */ + ih264_deblk_chroma_edge_bslt4_ft *pf_deblk_chroma_vert_bslt4; + + /** + * deblock vertical chroma edge with blocking strength 4 for mbaff + */ + ih264_deblk_chroma_edge_bs4_ft *pf_deblk_chroma_vert_bs4_mbaff; + + /** + * deblock vertical chroma edge with blocking strength less than 4 for mbaff + */ + ih264_deblk_chroma_edge_bslt4_ft *pf_deblk_chroma_vert_bslt4_mbaff; + + /** + * deblock horizontal luma edge with blocking strength 4 + */ + ih264_deblk_edge_bs4_ft *pf_deblk_luma_horz_bs4; + + /** + * deblock horizontal luma edge with blocking strength less than 4 + */ + ih264_deblk_edge_bslt4_ft *pf_deblk_luma_horz_bslt4; + + /** + * deblock horizontal chroma edge with blocking strength 4 + */ + ih264_deblk_chroma_edge_bs4_ft *pf_deblk_chroma_horz_bs4; + + /** + * deblock horizontal chroma edge with blocking strength less than 4 + */ + ih264_deblk_chroma_edge_bslt4_ft *pf_deblk_chroma_horz_bslt4; + + +} dec_struct_t; + +#endif /* _H264_DEC_STRUCTS_H */ diff --git a/dependencies/ih264d/decoder/ih264d_tables.c b/dependencies/ih264d/decoder/ih264d_tables.c new file mode 100644 index 00000000..c45525ab --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_tables.c @@ -0,0 +1,874 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ + +/** + ************************************************************************** + * \file ih264d_tables.c + * + * \brief + * Defination of all tables used by h264 decoder + * + * \date + * 17/09/2004 + * + * \author MA + ************************************************************************** + */ +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264d_defs.h" +#include "ih264d_tables.h" + +const UWORD8 gau1_ih264d_qp_scale_cr[] = + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 29, 30, 31, 32, 32, 33, 34, 34, 35, 35, 36, 36, 37, 37, 37, 38, 38, 38, + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39 }; +const UWORD8 gau1_ih264d_alpha_table[] = + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 4, 4, 5, 6, 7, 8, 9, 10, 12, 13, 15, 17, 20, 22, 25, 28, 32, 36, + 40, 45, 50, 56, 63, 71, 80, 90, 101, 113, 127, 144, 162, 182, 203, 226, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }; +const UWORD8 gau1_ih264d_beta_table[] = + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, + 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 18, 18, 18, + 18, 18, 18, 18, 18, 18, 18, 18, 18 }; + +const UWORD8 gau1_ih264d_clip_table[][4] = + { + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 0 }, + { 0, 0, 0, 1 }, + { 0, 0, 0, 1 }, + { 0, 0, 0, 1 }, + { 0, 0, 0, 1 }, + { 0, 0, 1, 1 }, + { 0, 0, 1, 1 }, + { 0, 1, 1, 1 }, + { 0, 1, 1, 1 }, + { 0, 1, 1, 1 }, + { 0, 1, 1, 1 }, + { 0, 1, 1, 2 }, + { 0, 1, 1, 2 }, + { 0, 1, 1, 2 }, + { 0, 1, 1, 2 }, + { 0, 1, 2, 3 }, + { 0, 1, 2, 3 }, + { 0, 2, 2, 3 }, + { 0, 2, 2, 4 }, + { 0, 2, 3, 4 }, + { 0, 2, 3, 4 }, + { 0, 3, 3, 5 }, + { 0, 3, 4, 6 }, + { 0, 3, 4, 6 }, + { 0, 4, 5, 7 }, + { 0, 4, 5, 8 }, + { 0, 4, 6, 9 }, + { 0, 5, 7, 10 }, + { 0, 6, 8, 11 }, + { 0, 6, 8, 13 }, + { 0, 7, 10, 14 }, + { 0, 8, 11, 16 }, + { 0, 9, 12, 18 }, + { 0, 10, 13, 20 }, + { 0, 11, 15, 23 }, + { 0, 13, 17, 25 }, + + { 0, 13, 17, 25 }, + { 0, 13, 17, 25 }, + { 0, 13, 17, 25 }, + { 0, 13, 17, 25 }, + { 0, 13, 17, 25 }, + { 0, 13, 17, 25 }, + { 0, 13, 17, 25 }, + { 0, 13, 17, 25 }, + { 0, 13, 17, 25 }, + { 0, 13, 17, 25 }, + { 0, 13, 17, 25 }, + { 0, 13, 17, 25 }, + { 0, 13, 17, 25 }, + { 0, 13, 17, 25 }, + { 0, 13, 17, 25 }, + { 0, 13, 17, 25 }, + { 0, 13, 17, 25 }, + { 0, 13, 17, 25 }, + { 0, 13, 17, 25 }, + { 0, 13, 17, 25 }, + { 0, 13, 17, 25 }, + { 0, 13, 17, 25 }, + { 0, 13, 17, 25 }, + { 0, 13, 17, 25 } + + }; +const UWORD8 gau1_ih264d_clip_table_deblock[] = + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51 }; + +/****************DEBLOCKING TABLES ENDS*******************/ + +/*************************************************************/ +/* BS CALCULATION TABLES */ +/*************************************************************/ +UWORD32 const gau4_ih264d_packed_bs2[32] = + { + /*************************************************************/ + /* BS TABLES FOR NORMAL EDGES */ + /*************************************************************/ + 0x00000000, + 0x02000000, 0x00020000, 0x02020000, 0x00000200, 0x02000200, 0x00020200, + 0x02020200, 0x00000002, 0x02000002, 0x00020002, 0x02020002, 0x00000202, + 0x02000202, 0x00020202, 0x02020202, + + /*************************************************************/ + /* BS TABLES FOR XTRA LEFT MB EDGES IN MBAFF CASE */ + /*************************************************************/ + 0x01010101, + 0x02010101, 0x01020101, 0x02020101, 0x01010201, 0x02010201, 0x01020201, + 0x02020201, 0x01010102, 0x02010102, 0x01020102, 0x02020102, 0x01010202, + 0x02010202, 0x01020202, 0x02020202, }; + +UWORD16 const gau2_ih264d_4x4_v2h_reorder[16] = + { 0x0000, 0x0001, 0x0010, 0x0011, 0x0100, 0x0101, 0x0110, 0x0111, 0x1000, + 0x1001, 0x1010, 0x1011, 0x1100, 0x1101, 0x1110, 0x1111 }; + +/****************SCALING TABLES STARTS *****************/ +const WORD16 gai2_ih264d_default_intra4x4[16] = + { 6, 13, 13, 20, 20, 20, 28, 28, 28, 28, 32, 32, 32, 37, 37, 42 }; + +const WORD16 gai2_ih264d_default_inter4x4[16] = + { 10, 14, 14, 20, 20, 20, 24, 24, 24, 24, 27, 27, 27, 30, 30, 34 }; + +const WORD16 gai2_ih264d_default_intra8x8[64] = + { 6, 10, 10, 13, 11, 13, 16, 16, 16, 16, 18, 18, 18, 18, 18, 23, 23, 23, 23, + 23, 23, 25, 25, 25, 25, 25, 25, 25, 27, 27, 27, 27, 27, 27, 27, 27, 29, + 29, 29, 29, 29, 29, 29, 31, 31, 31, 31, 31, 31, 33, 33, 33, 33, 33, 36, + 36, 36, 36, 38, 38, 38, 40, 40, 42 }; + +const WORD16 gai2_ih264d_default_inter8x8[64] = + { 9, 13, 13, 15, 13, 15, 17, 17, 17, 17, 19, 19, 19, 19, 19, 21, 21, 21, 21, + 21, 21, 22, 22, 22, 22, 22, 22, 22, 24, 24, 24, 24, 24, 24, 24, 24, 25, + 25, 25, 25, 25, 25, 25, 27, 27, 27, 27, 27, 27, 28, 28, 28, 28, 28, 30, + 30, 30, 30, 32, 32, 32, 33, 33, 35 }; + +const WORD16 gai2_ih264d_flat_4x4[16] = + { 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16 }; + +const WORD16 gai2_ih264d_flat_8x8[64] = + { 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16 }; + +/****************SCALING TABLES ENDS *****************/ + +/*Inverse scan tables for individual 4x4 blocks of 8x8 transform coeffs of CAVLC */ + +/* progressive */ + +const UWORD8 gau1_ih264d_inv_scan_prog8x8_cavlc[4][16] = + { + { 0, 9, 17, 18, 12, 40, 27, 7, 35, 57, 29, 30, 58, 38, 53, 47 }, /* for First subblock */ + { 1, 2, 24, 11, 19, 48, 20, 14, 42, 50, 22, 37, 59, 31, 60, 55 }, /* for second subblock */ + { 8, 3, 32, 4, 26, 41, 13, 21, 49, 43, 15, 44, 52, 39, 61, 62 }, /* for third subblock */ + { 16, 10, 25, 5, 33, 34, 6, 28, 56, 36, 23, 51, 45, 46, 54, 63 } /* for fourth subblock */ + }; + +const UWORD8 gau1_ih264d_inv_scan_int8x8_cavlc[4][16] = + { + { 0, 9, 2, 56, 18, 26, 34, 27, 35, 28, 36, 29, 45, 7, 54, 39 }, /* for First subblock */ + { 8, 24, 25, 33, 41, 11, 42, 12, 43, 13, 44, 14, 53, 15, 62, 47 }, /* for second subblock */ + { 16, 32, 40, 10, 49, 4, 50, 5, 51, 6, 52, 22, 61, 38, 23, 55 }, /* for third subblock */ + { 1, 17, 48, 3, 57, 19, 58, 20, 59, 21, 60, 37, 30, 46, 31, 63 } /* for fourth subblock */ + }; + +/*Inverse scan tables for individual 8x8 blocks of 8x8 transform coeffs of CABAC */ +/* progressive */ + +const UWORD8 gau1_ih264d_inv_scan_prog8x8_cabac[64] = + { 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, + 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, + 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, + 60, 61, 54, 47, 55, 62, 63 }; + +/* interlace */ + +const UWORD8 gau1_ih264d_inv_scan_int8x8_cabac[64] = + { 0, 8, 16, 1, 9, 24, 32, 17, 2, 25, 40, 48, 56, 33, 10, 3, 18, 41, 49, 57, + 26, 11, 4, 19, 34, 42, 50, 58, 27, 12, 5, 20, 35, 43, 51, 59, 28, 13, 6, + 21, 36, 44, 52, 60, 29, 14, 22, 37, 45, 53, 61, 30, 7, 15, 38, 46, 54, 62, + 23, 31, 39, 47, 55, 63 }; + +/****************PARSING TABLES *******************/ +UWORD8 const gau1_ih264d_subblk_offset[16] = + { 8, 9, 12, 13, 10, 11, 14, 15, 16, 17, 20, 21, 18, 19, 22, 23 }; + +const UWORD8 gau1_ih264d_cbp_tab[6] = + { 0, 16, 32, 15, 31, 47 }; + +/** gives CBP value from codeword number, both for intra and inter */ + +const UWORD8 gau1_ih264d_cbp_table[48][2] = + { + { 47, 0 }, + { 31, 16 }, + { 15, 1 }, + { 0, 2 }, + { 23, 4 }, + { 27, 8 }, + { 29, 32 }, + { 30, 3 }, + { 7, 5 }, + { 11, 10 }, + { 13, 12 }, + { 14, 15 }, + { 39, 47 }, + { 43, 7 }, + { 45, 11 }, + { 46, 13 }, + { 16, 14 }, + { 3, 6 }, + { 5, 9 }, + { 10, 31 }, + { 12, 35 }, + { 19, 37 }, + { 21, 42 }, + { 26, 44 }, + { 28, 33 }, + { 35, 34 }, + { 37, 36 }, + { 42, 40 }, + { 44, 39 }, + { 1, 43 }, + { 2, 45 }, + { 4, 46 }, + { 8, 17 }, + { 17, 18 }, + { 18, 20 }, + { 20, 24 }, + { 24, 19 }, + { 6, 21 }, + { 9, 26 }, + { 22, 28 }, + { 25, 23 }, + { 32, 27 }, + { 33, 29 }, + { 34, 30 }, + { 36, 22 }, + { 40, 25 }, + { 38, 38 }, + { 41, 41 }, }; +/****************PARSING TABLES ENDS *******************/ + +/****************DECODE SLICE TABLES STARTS *******************/ +/*Definition of Tables needed by functions of this file */ +const UWORD8 gau1_ih264d_inv_scan[16] = + { 0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15 }; + +const UWORD8 gau1_ih264d_inv_scan_fld[16] = + { 0, 4, 1, 8, 12, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15 }; + +const UWORD8 gau1_ih264d_dequant_matrix[6][16] = +{ + { 10, 13, 10, 13, 13, 16, 13, 16, 10, 13, 10 ,13, 13, 16, 13, 16}, + { 11, 14, 11, 14, 14, 18, 14, 18, 11, 14, 11 ,14, 14, 18, 14, 18}, + { 13, 16, 13, 16, 16, 20, 16, 20, 13, 16, 13 ,16, 16, 20, 16, 20}, + { 14, 18, 14, 18, 18, 23, 18, 23, 14, 18, 14, 18, 18, 23, 18, 23}, + { 16, 20, 16, 20, 20, 25, 20, 25, 16, 20, 16, 20, 20, 25, 20, 25}, + { 18, 23, 18, 23, 23, 29, 23, 29, 18, 23, 18, 23, 23, 29, 23, 29} +}; + +const UWORD16 gau2_ih264_iquant_scale_4x4[6][16] = + { + { 10, 13, 10, 13, 13, 16, 13, 16, 10, 13, 10, 13, 13, 16, 13, 16 }, + { 11, 14, 11, 14, 14, 18, 14, 18, 11, 14, 11, 14, 14, 18, 14, 18 }, + { 13, 16, 13, 16, 16, 20, 16, 20, 13, 16, 13, 16, 16, 20, 16, 20 }, + { 14, 18, 14, 18, 18, 23, 18, 23, 14, 18, 14, 18, 18, 23, 18, 23 }, + { 16, 20, 16, 20, 20, 25, 20, 25, 16, 20, 16, 20, 20, 25, 20, 25 }, + { 18, 23, 18, 23, 23, 29, 23, 29, 18, 23, 18, 23, 23, 29, 23, 29 } }; + +const UWORD8 gau1_ih264d_dequant8x8_zigzag_cavlc[4][6][16] = + { + { + { 20, 18, 24, 32, 19, 19, 18, 19, 19, 18, 18, 24, + 24, 25, 24, 18 }, /* for First subblock */ + { 22, 19, 26, 35, 21, 21, 19, 21, 21, 19, 19, 26, + 26, 28, 26, 19 }, + { 26, 23, 31, 42, 24, 24, 23, 24, 24, 23, 23, 31, + 31, 33, 31, 23 }, + { 28, 25, 33, 45, 26, 26, 25, 26, 26, 25, 25, 33, + 33, 35, 33, 25 }, + { 32, 28, 38, 51, 30, 30, 28, 30, 30, 28, 28, 38, + 38, 40, 38, 28 }, + { 36, 32, 43, 58, 34, 34, 32, 34, 34, 32, 32, 43, + 43, 46, 43, 32 } }, + { + { 19, 25, 19, 18, 24, 25, 25, 24, 24, 32, 32, 19, + 18, 18, 19, 24 }, /* for second subblock */ + { 21, 28, 21, 19, 26, 28, 28, 26, 26, 35, 35, + 21, 19, 19, 21, 26 }, + { 24, 33, 24, 23, 31, 33, 33, 31, 31, 42, 42, + 24, 23, 23, 24, 31 }, + { 26, 35, 26, 25, 33, 35, 35, 33, 33, 45, 45, + 26, 25, 25, 26, 33 }, + { 30, 40, 30, 28, 38, 40, 40, 38, 38, 51, 51, + 30, 28, 28, 30, 38 }, + { 34, 46, 34, 32, 43, 46, 46, 43, 43, 58, 58, + 34, 32, 32, 34, 43 } }, + { + { 19, 19, 20, 20, 24, 18, 18, 24, 24, 18, 18, 19, + 25, 19, 18, 24 }, /* for third subblock */ + { 21, 21, 22, 22, 26, 19, 19, 26, 26, 19, 19, + 21, 28, 21, 19, 26 }, + { 24, 24, 26, 26, 31, 23, 23, 31, 31, 23, 23, + 24, 33, 24, 23, 31 }, + { 26, 26, 28, 28, 33, 25, 25, 33, 33, 25, 25, + 26, 35, 26, 25, 33 }, + { 30, 30, 32, 32, 38, 28, 28, 38, 38, 28, 28, + 30, 40, 30, 28, 38 }, + { 34, 34, 36, 36, 43, 32, 32, 43, 43, 32, 32, + 34, 46, 34, 32, 43 } }, + { + { 25, 24, 18, 19, 19, 25, 25, 19, 19, 20, 24, 24, + 18, 24, 32, 18 }, /* for fourth subblock */ + { 28, 26, 19, 21, 21, 28, 28, 21, 21, 22, 26, + 26, 19, 26, 35, 19 }, + { 33, 31, 23, 24, 24, 33, 33, 24, 24, 26, 31, + 31, 23, 31, 42, 23 }, + { 35, 33, 25, 26, 26, 35, 35, 26, 26, 28, 33, + 33, 25, 33, 45, 25 }, + { 40, 38, 28, 30, 30, 40, 40, 30, 30, 32, 38, + 38, 28, 38, 51, 28 }, + { 46, 43, 32, 34, 34, 46, 46, 34, 34, 36, 43, + 43, 32, 43, 58, 32 } } + + }; + +const UWORD16 gau1_ih264d_dequant8x8_cavlc[6][64] = + { + { 20, 19, 25, 19, 20, 19, 25, 19, 19, 18, 24, 18, 19, + 18, 24, 18, 25, 24, 32, 24, 25, 24, 32, 24, 19, 18, + 24, 18, 19, 18, 24, 18, 20, 19, 25, 19, 20, 19, 25, + 19, 19, 18, 24, 18, 19, 18, 24, 18, 25, 24, 32, 24, + 25, 24, 32, 24, 19, 18, 24, 18, 19, 18, 24, 18 }, + { 22, 21, 28, 21, 22, 21, 28, 21, 21, 19, 26, 19, 21, + 19, 26, 19, 28, 26, 35, 26, 28, 26, 35, 26, 21, 19, + 26, 19, 21, 19, 26, 19, 22, 21, 28, 21, 22, 21, 28, + 21, 21, 19, 26, 19, 21, 19, 26, 19, 28, 26, 35, 26, + 28, 26, 35, 26, 21, 19, 26, 19, 21, 19, 26, 19 }, + { 26, 24, 33, 24, 26, 24, 33, 24, 24, 23, 31, 23, 24, + 23, 31, 23, 33, 31, 42, 31, 33, 31, 42, 31, 24, 23, + 31, 23, 24, 23, 31, 23, 26, 24, 33, 24, 26, 24, 33, + 24, 24, 23, 31, 23, 24, 23, 31, 23, 33, 31, 42, 31, + 33, 31, 42, 31, 24, 23, 31, 23, 24, 23, 31, 23 }, + { 28, 26, 35, 26, 28, 26, 35, 26, 26, 25, 33, 25, 26, + 25, 33, 25, 35, 33, 45, 33, 35, 33, 45, 33, 26, 25, + 33, 25, 26, 25, 33, 25, 28, 26, 35, 26, 28, 26, 35, + 26, 26, 25, 33, 25, 26, 25, 33, 25, 35, 33, 45, 33, + 35, 33, 45, 33, 26, 25, 33, 25, 26, 25, 33, 25 }, + { 32, 30, 40, 30, 32, 30, 40, 30, 30, 28, 38, 28, 30, + 28, 38, 28, 40, 38, 51, 38, 40, 38, 51, 38, 30, 28, + 38, 28, 30, 28, 38, 28, 32, 30, 40, 30, 32, 30, 40, + 30, 30, 28, 38, 28, 30, 28, 38, 28, 40, 38, 51, 38, + 40, 38, 51, 38, 30, 28, 38, 28, 30, 28, 38, 28 }, + { 36, 34, 46, 34, 36, 34, 46, 34, 34, 32, 43, 32, 34, + 32, 43, 32, 46, 43, 58, 43, 46, 43, 58, 43, 34, 32, + 43, 32, 34, 32, 43, 32, 36, 34, 46, 34, 36, 34, 46, + 34, 34, 32, 43, 32, 34, 32, 43, 32, 46, 43, 58, 43, + 46, 43, 58, 43, 34, 32, 43, 32, 34, 32, 43, 32 }, }; + +/****************DECODE SLICE TABLES ENDS *******************/ + +/****************MOTION VECTOR DECODING TABLES STARTS *******************/ + +/** + ************************************************************************** + * \brief This array is used to evaluate the condition when only one of + * predictor subMbs has a reference frame equal to that of E subMb. + ************************************************************************** + */ + +const WORD8 gau1_ih264d_mv_pred_condition[] = + { -1, 0, 1, -1, 2, -1, -1, -1 }; + +/** Number of subMbs for the 8x8 prediction mode */ +const UWORD8 gau1_ih264d_num_submb_part[] = + { 1, 2, 2, 4 }; + +/** Width of the 8x8 prediction mode in terms of subMbs */ +const UWORD8 gau1_ih264d_submb_partw[] = + { 2, 2, 1, 1 }; + +/** Height of the 8x8 prediction mode in terms of subMbs */ +const UWORD8 gau1_ih264d_submb_parth[] = + { 2, 1, 2, 1 }; + +/** Number of MB partitions for the MB prediction mode */ +const UWORD8 gau1_ih264d_num_mb_part[] = + { 1, 2, 2, 4 }; + +/** Width of the MB partition in terms of subMbs */ +const UWORD8 gau1_ih264d_mb_partw[] = + { 4, 4, 2, 2, 2 }; + +/** Height of the MB partition in terms of subMbs */ +const UWORD8 gau1_ih264d_mb_parth[] = + { 4, 2, 4, 2, 2 }; + +/** MB partition information is packed into a UWORD32 {0,number,width,height} */ +const UWORD32 gau4_ih264d_submb_part[] = + { 0x00010202, 0x00020201, 0x00020102, 0x00040101 }; + +const UWORD8 gau1_ih264d_submb_indx_mod[] = + { 0, 0, /* 16x16 */ + 0, 8, /* 16x8 */ + 0, 2, /* 8x16 */ + 0, 0, /* 8x8 */ + 0, 4, /* 8x4 */ + 0, 1, /* 4x8 */ + 0, 1, 3, 1 /* 4x4 */ + }; + +/** This table is used to assign CBPs to Inter MBs. */ +const UWORD8 gau1_ih264d_cbp_inter[] = + { 0, 16, 1, 2, 4, 8, 32, 3, 5, 10, 12, 15, 47, 7, 11, 13, 14, 6, 9, 31, 35, + 37, 42, 44, 33, 34, 36, 40, 39, 43, 45, 46, 17, 18, 20, 24, 19, 21, 26, + 28, 23, 27, 29, 30, 22, 25, 38, 41 }; + +/** Motion comp modes for P followed by B, + 0 to 4 : P Mbs + 5 to 27 : B Mbs + 28 to 30 : DIRECT */ +const UWORD8 gau1_ih264d_mb_mc_mode[] = + { + PRED_16x16, + PRED_16x8, PRED_8x16, PRED_8x8, PRED_8x8R0, + PRED_16x16, + PRED_16x16, PRED_16x16, PRED_16x16, PRED_16x8, PRED_8x16, + PRED_16x8, + PRED_8x16, PRED_16x8, PRED_8x16, PRED_16x8, PRED_8x16, + PRED_16x8, + PRED_8x16, PRED_16x8, PRED_8x16, PRED_16x8, PRED_8x16, + PRED_16x8, + PRED_8x16, PRED_16x8, PRED_8x16, PRED_8x8, + /* Self defined modes for B_SKIP and DIRECT16x16 */ + PRED_8x8, + PRED_8x8, PRED_8x8 }; + +const UWORD8 gau1_ih264d_submb_mc_mode[] = + { SUBMB_8x8, SUBMB_8x4, SUBMB_4x8, SUBMB_4x4, + SUBMB_8x8, + SUBMB_8x8, SUBMB_8x8, SUBMB_8x8, SUBMB_8x4, SUBMB_4x8, + SUBMB_8x4, + SUBMB_4x8, SUBMB_8x4, SUBMB_4x8, SUBMB_4x4, SUBMB_4x4, SUBMB_4x4, + /* Self defined modes B DIRECT8x8 */ + SUBMB_4x4, + SUBMB_4x4, SUBMB_4x4 }; + +/** Sub MB pred modes for B slice */ +const UWORD8 gau1_ih264d_submb_pred_modes[] = + { + PRED_L0, + PRED_L0, PRED_L0, PRED_L0, + B_DIRECT, + PRED_L0, PRED_L1, BI_PRED, PRED_L0, PRED_L0, PRED_L1, + PRED_L1, + BI_PRED, BI_PRED, PRED_L0, PRED_L1, BI_PRED, + /* Self defined modes for B DIRECT8x8 */ + BI_PRED, + PRED_L0, PRED_L1, }; + +/** MB pred modes for P and B slice */ +const WORD8 gau1_ih264d_mb_pred_modes[2][32] = + { + { PRED_L0, PRED_L0, PRED_L0, PRED_INVALID, PRED_INVALID, + B_DIRECT, + PRED_L0, PRED_L1, BI_PRED, PRED_L0, PRED_L0, PRED_L1, PRED_L1, + PRED_L0, + PRED_L0, PRED_L1, PRED_L1, PRED_L0, PRED_L0, PRED_L1, PRED_L1, + BI_PRED, + BI_PRED, BI_PRED, BI_PRED, BI_PRED, BI_PRED, PRED_INVALID, + /* Self defined modes for B_SKIP and DIRECT16x16 */ + BI_PRED, + PRED_L0, PRED_L1, }, + { PRED_INVALID, PRED_L0, PRED_L0, PRED_INVALID, PRED_INVALID, + PRED_INVALID, + PRED_INVALID, PRED_INVALID, PRED_INVALID, PRED_L0, PRED_L0, + PRED_L1, + PRED_L1, PRED_L1, PRED_L1, PRED_L0, PRED_L0, BI_PRED, BI_PRED, + BI_PRED, + BI_PRED, PRED_L0, PRED_L0, PRED_L1, PRED_L1, BI_PRED, BI_PRED, + PRED_INVALID, + /* Self defined modes for B_SKIP and DIRECT16x16 */ + PRED_INVALID, + PRED_INVALID, PRED_INVALID } }; + +/****************MOTION VECTOR DECODING TABLES ENDS *******************/ + +/****************CAVLC DECODING TABLES STARTS *******************/ + +/*****************************************************************************/ +/* 6 Bit table look for total zeros (totalcoeff = 2to10) as in Table 9.7 */ +/* of H264 standard. In each table entry, lower 4 bits represent total zeros */ +/* decoded while upper 4 bit represent the bits to be flushed from ps_bitstrm */ +/*****************************************************************************/ +const UWORD8 gau1_ih264d_table_total_zero_2to10[9][64] = + { + /* For total coeff = 2 */ + { 0x6E, 0x6D, 0x6C, 0x6B, 0x5A, 0x5A, 0x59, 0x59, 0x48, 0x48, 0x48, + 0x48, 0x47, 0x47, 0x47, 0x47, 0x46, 0x46, 0x46, 0x46, 0x45, 0x45, + 0x45, 0x45, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x33, + 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, + 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, }, + + /* For total coeff = 3 */ + { 0x6D, 0x6B, 0x5C, 0x5C, 0x5A, 0x5A, 0x59, 0x59, 0x48, 0x48, 0x48, + 0x48, 0x45, 0x45, 0x45, 0x45, 0x44, 0x44, 0x44, 0x44, 0x40, 0x40, + 0x40, 0x40, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x33, 0x33, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, }, + + /* For total coeff = 4 */ + { 0x5C, 0x5C, 0x5B, 0x5B, 0x5A, 0x5A, 0x50, 0x50, 0x49, 0x49, 0x49, + 0x49, 0x47, 0x47, 0x47, 0x47, 0x43, 0x43, 0x43, 0x43, 0x42, 0x42, + 0x42, 0x42, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x35, 0x35, 0x35, 0x35, + 0x35, 0x35, 0x35, 0x35, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, }, + + /* For total coeff = 5 */ + { 0x5B, 0x5B, 0x59, 0x59, 0x4A, 0x4A, 0x4A, 0x4A, 0x48, 0x48, 0x48, + 0x48, 0x42, 0x42, 0x42, 0x42, 0x41, 0x41, 0x41, 0x41, 0x40, 0x40, + 0x40, 0x40, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x35, 0x35, 0x35, 0x35, + 0x35, 0x35, 0x35, 0x35, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, }, + + /* For total coeff = 6 */ + { 0x6A, 0x60, 0x51, 0x51, 0x48, 0x48, 0x48, 0x48, 0x39, 0x39, 0x39, + 0x39, 0x39, 0x39, 0x39, 0x39, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, + 0x37, 0x37, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x35, + 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, }, + + /* For total coeff = 7 */ + { 0x69, 0x60, 0x51, 0x51, 0x47, 0x47, 0x47, 0x47, 0x38, 0x38, 0x38, + 0x38, 0x38, 0x38, 0x38, 0x38, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x33, + 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, }, + + /* For total coeff = 8 */ + { 0x68, 0x60, 0x52, 0x52, 0x41, 0x41, 0x41, 0x41, 0x37, 0x37, 0x37, + 0x37, 0x37, 0x37, 0x37, 0x37, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, }, + + /* For total coeff = 9 */ + { 0x61, 0x60, 0x57, 0x57, 0x42, 0x42, 0x42, 0x42, 0x35, 0x35, 0x35, + 0x35, 0x35, 0x35, 0x35, 0x35, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, }, + + /* For total coeff = 10 */ + { 0x51, 0x51, 0x50, 0x50, 0x46, 0x46, 0x46, 0x46, 0x32, 0x32, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x32, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, } + + }; + +/*****************************************************************************/ +/* 4 Bit table look for total zeros (totalcoeff = 11to15) as in Table 9.7 */ +/* of H264 standard. In each table entry, lower 4 bits represent total zeros */ +/* decoded while upper 4 bit represent the bits to be flushed from ps_bitstrm */ +/*****************************************************************************/ +const UWORD8 gau1_ih264d_table_total_zero_11to15[5][16] = + { + /* For total coeff = 11 */ + { 0x40, 0x41, 0x32, 0x32, 0x33, 0x33, 0x35, 0x35, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, }, + + /* For total coeff = 12 */ + { 0x40, 0x41, 0x34, 0x34, 0x22, 0x22, 0x22, 0x22, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, }, + + /* For total coeff = 13 */ + { 0x30, 0x30, 0x31, 0x31, 0x23, 0x23, 0x23, 0x23, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, }, + + /* For total coeff = 14 */ + { 0x20, 0x20, 0x20, 0x20, 0x21, 0x21, 0x21, 0x21, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, }, + + /* For total coeff = 15 */ + { 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, }, }; + +/** Tables used to read "Run Before", Below tables are packed to reduce lookups */ +/** (Base addess of Gx << 2) + (Max code length for that Gx) */ +const UWORD8 gau1_ih264d_table_run_before[64] = + { 0, 0, 0, 0, 0, 0, 0, 0, 5, 5, 5, 5, 1, 1, 1, 1, 10, 10, 6, 6, 1, 1, 1, 1, + 14, 14, 10, 10, 6, 6, 2, 2, 19, 15, 10, 10, 6, 6, 2, 2, 23, 19, 15, 11, 6, + 6, 2, 2, 7, 11, 19, 15, 27, 23, 2, 2, 27, 27, 23, 19, 15, 11, 7, 3 }; + +/*****************************************************************************/ +/* Lookup table for CAVLC 4x4 total_coeff,trailing_ones as pers Table 9-5 */ +/* in the standard. Starting form lsb first 2 bits=flushbits, next 2bits= */ +/* trailing ones, next 5 bits=total_coeff. Total bits used = 9 out of 16 */ +/*****************************************************************************/ +const UWORD16 gau2_ih264d_code_gx[304] = + { + /* Lookup for 0 <= nC < 2 */ + 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0014, 0x0014, + 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0014, 0x0028, 0x0028, 0x0028, + 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0026, 0x0026, 0x0012, 0x0012, + 0x003D, 0x003D, 0x003D, 0x003D, 0x005E, 0x005E, 0x003A, 0x003A, 0x004D, + 0x004D, 0x004D, 0x004D, 0x006E, 0x006E, 0x004A, 0x004A, 0x0036, 0x0036, + 0x0022, 0x0022, 0x007E, 0x007E, 0x005A, 0x005A, 0x0046, 0x0046, 0x0032, + 0x0032, 0x008E, 0x008E, 0x006A, 0x006A, 0x0056, 0x0056, 0x0042, 0x0042, + 0x009E, 0x009E, 0x007A, 0x007A, 0x0066, 0x0066, 0x0052, 0x0052, 0x0083, + 0x009B, 0x0087, 0x0073, 0x00AF, 0x008B, 0x0077, 0x0063, 0x00CF, 0x00BB, + 0x00A7, 0x00A3, 0x00BF, 0x00AB, 0x0097, 0x0093, 0x00EF, 0x00DB, 0x00C7, + 0x00C3, 0x00DF, 0x00CB, 0x00B7, 0x00B3, 0x010F, 0x00FB, 0x00F7, 0x00E3, + 0x00FF, 0x00EB, 0x00E7, 0x00D3, 0x0102, 0x0102, 0x010A, 0x010A, 0x0106, + 0x0106, 0x00F2, 0x00F2, 0x00D4, 0x00D4, 0x00D4, 0x00D4, 0x00D4, 0x00D4, + 0x00D4, 0x00D4, + + /* Lookup for 2 <= nC < 4 */ + 0x0015, + 0x0015, 0x0015, 0x0015, 0x0001, 0x0001, 0x0001, 0x0001, 0x004E, 0x004E, + 0x003E, 0x003E, 0x0029, 0x0029, 0x0029, 0x0029, 0x006F, 0x003B, 0x0037, + 0x0013, 0x005E, 0x005E, 0x0026, 0x0026, 0x007E, 0x007E, 0x004A, 0x004A, + 0x0046, 0x0046, 0x0022, 0x0022, 0x008E, 0x008E, 0x005A, 0x005A, 0x0056, + 0x0056, 0x0032, 0x0032, 0x0052, 0x0052, 0x006A, 0x006A, 0x0066, 0x0066, + 0x0042, 0x0042, 0x009E, 0x009E, 0x007A, 0x007A, 0x0076, 0x0076, 0x0062, + 0x0062, 0x00BF, 0x009B, 0x0097, 0x0083, 0x00AF, 0x008B, 0x0087, 0x0073, + 0x00B3, 0x00BB, 0x00B7, 0x00A3, 0x00CF, 0x00AB, 0x00A7, 0x0093, 0x00EF, + 0x00DB, 0x00D7, 0x00D3, 0x00DF, 0x00CB, 0x00C7, 0x00C3, 0x00F7, 0x00F3, + 0x00FB, 0x00E7, 0x00EA, 0x00EA, 0x00E2, 0x00E2, 0x010E, 0x010E, 0x010A, + 0x010A, 0x0106, 0x0106, 0x0102, 0x0102, 0x00FC, 0x00FC, 0x00FC, 0x00FC, + 0x00FC, 0x00FC, 0x00FC, 0x00FC, + + /* Lookup for 4 <= nC < 8 */ + 0x007F, + 0x006F, 0x005F, 0x004F, 0x003F, 0x002B, 0x0017, 0x0003, 0x0057, 0x005B, + 0x0047, 0x004B, 0x0037, 0x008F, 0x003B, 0x0027, 0x0033, 0x007B, 0x0077, + 0x0023, 0x009F, 0x006B, 0x0067, 0x0013, 0x0073, 0x0063, 0x009B, 0x0053, + 0x00AF, 0x008B, 0x0087, 0x0043, 0x00CF, 0x00BB, 0x00A7, 0x0093, 0x00BF, + 0x00AB, 0x0097, 0x0083, 0x00C3, 0x00DB, 0x00C7, 0x00B3, 0x00DF, 0x00CB, + 0x00B7, 0x00A3, 0x00F7, 0x00E3, 0x00EF, 0x00EB, 0x00E7, 0x00D3, 0x00D6, + 0x00D6, 0x0106, 0x0106, 0x00F2, 0x00F2, 0x00FE, 0x00FE, 0x00FA, 0x00FA, + 0x010D, 0x010D, 0x010D, 0x010D, 0x0109, 0x0109, 0x0109, 0x0109, 0x0100, + 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100 }; + +/*****************************************************************************/ +/* Lookup table for CAVLC ChromaDC total_coeff,trailing_ones parsing as per */ +/* Table 9-5 in the standard. Starting from msb, First 4bits=total_coeff, */ +/* next 2bits=trailing_ones and last 2bits=flushbits-1 */ +/*****************************************************************************/ +const UWORD8 gau1_ih264d_cav_chromdc_vld[256] = + { 0x9E, 0x9E, 0x97, 0x8F, 0x76, 0x76, 0x6E, 0x6E, 0x85, 0x85, 0x85, 0x85, + 0x65, 0x65, 0x65, 0x65, 0x45, 0x45, 0x45, 0x45, 0x7D, 0x7D, 0x7D, 0x7D, + 0x4D, 0x4D, 0x4D, 0x4D, 0x25, 0x25, 0x25, 0x25, + + 0x52, + 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, + 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, + 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, + + 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, + + 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, }; + +const UWORD16 gau2_ih264d_offset_num_vlc_tab[9] = + { 0, 0, 120, 120, 224, 224, 224, 224, 224 }; + +/*****************************************************************************/ +/* Function pointer u4_ofst table lookup for parsing 4x4 residual blocks in */ +/* CAVLC. The u4_ofst is dependent on total coeffs coded */ +/*****************************************************************************/ +const UWORD8 gau1_ih264d_total_coeff_fn_ptr_offset[16] = + { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2 }; + +/****************************************************************************/ +/* gai2_ih264d_trailing_one_level lookup tables based on trailing one bits */ +/* All zeroes are u2_dummy in the table are u2_dummy to keep 3 uniform elements */ +/****************************************************************************/ +const WORD16 gai2_ih264d_trailing_one_level[14][3] = + { + /* All zeroes are u2_dummy */ + /**********************************************************************/ + /* Levels for trailing ones = 1, bits read can be 0 or 1 */ + /**********************************************************************/ + { 1, 0, 0 }, /* 0 */ + { -1, 0, 0 }, /* 1 */ + + /**********************************************************************/ + /* Levels for trailing ones = 2, bits read can be 00, 01, 10 ,11 */ + /**********************************************************************/ + { 1, 1, 0 }, /* 00 */ + { 1, -1, 0 }, /* 01 */ + { -1, 1, 0 }, /* 10 */ + { -1, -1, 0 }, /* 11 */ + + /**********************************************************************/ + /* Levels for trailing ones = 3, bits read can be 000 - 111 */ + /**********************************************************************/ + { 1, 1, 1 }, /* 000 */ + { 1, 1, -1 }, /* 001 */ + { 1, -1, 1 }, /* 010 */ + { 1, -1, -1 }, /* 011 */ + { -1, 1, 1 }, /* 100 */ + { -1, 1, -1 }, /* 101 */ + { -1, -1, 1 }, /* 110 */ + { -1, -1, -1 }, /* 111 */ + }; +/****************CAVLC DECODING TABLES ENDS *******************/ + +/****************************************************************************/ +/* These are the codes used for error detection in intra pred4x4 modes */ +/****************************************************************************/ +const UWORD8 gau1_ih264d_intra_pred_err_code[9] = + { 2, 1, 0, 2, 3, 3, 3, 2, 1 }; + +/* Number of users for top field , bottom field, which field needs to be */ +/* displayed first */ +const UWORD8 gau1_ih264d_sei_fld_usage[9][3] = + { + { 1, 1, DISP_FLD_FIRST_UNDEF }, + { 1, 0, DISP_TOP_FLD_FIRST }, + { 0, 1, DISP_BOT_FLD_FIRST }, + { 1, 1, DISP_TOP_FLD_FIRST }, + { 1, 1, DISP_BOT_FLD_FIRST }, + { 2, 1, DISP_TOP_FLD_FIRST }, + { 1, 2, DISP_BOT_FLD_FIRST }, + { 2, 2, DISP_FLD_FIRST_UNDEF }, + { 3, 3, DISP_FLD_FIRST_UNDEF } }; + +/*****************************************************************/ +/* Context increment for significant coefficient(CABAC) */ +/* Requires only 63 elements. But the last element with value -1 */ +/* is kept to make it 64 */ +/*****************************************************************/ +const UWORD8 gau1_ih264d_sigcoeff_context_inc_frame[64] = + { 0, 1, 2, 3, 4, 5, 5, 4, 4, 3, 3, 4, 4, 4, 5, 5, 4, 4, 4, 4, 3, 3, 6, 7, 7, + 7, 8, 9, 10, 9, 8, 7, 7, 6, 11, 12, 13, 11, 6, 7, 8, 9, 14, 10, 9, 8, 6, + 11, 12, 13, 11, 6, 9, 14, 10, 9, 11, 12, 13, 11, 14, 10, 12, -1 }; + +const UWORD8 gau1_ih264d_sigcoeff_context_inc_field[64] = + { 0, 1, 1, 2, 2, 3, 3, 4, 5, 6, 7, 7, 7, 8, 4, 5, 6, 9, 10, 10, 8, 11, 12, + 11, 9, 9, 10, 10, 8, 11, 12, 11, 9, 9, 10, 10, 8, 11, 12, 11, 9, 9, 10, + 10, 8, 13, 13, 9, 9, 10, 10, 8, 13, 13, 9, 9, 10, 10, 14, 14, 14, 14, 14, + -1 }; + +const UWORD8 gau1_ih264d_lastcoeff_context_inc[64] = + { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, + 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, -1 }; + +/*! + ************************************************************************** + * \brief gau1_ih264d_top_left_mb_part_indx_mod + * + * SubBlk number of the top left subBlk in each of the MB partition + * (16x16, 16x8, 8x16, 8x8) + ************************************************************************** + */ +const UWORD8 gau1_ih264d_top_left_mb_part_indx_mod[] = + { 0, 0 /* Junk */, /* 16x16 */ + 0, 8, /* 16x8 */ + 0, 2, /* 8x16 */ + 0, 2, 8, 10 /* 8x8 */, + 0 /* One extra entry is read at the end of loop, but not used */ + }; + +/*! + ************************************************************************** + * \brief gau1_ih264d_submb_indx_mod_sp_drct + * + * Contains increments to the subBlk num in a given subMb partition. + ************************************************************************** + */ +const UWORD8 gau1_ih264d_submb_indx_mod_sp_drct[] = + { 0, 0 /* Junk */, /* 8x8 */ + 0, 4, /* 8x4 */ + 0, 1, /* 4x8 */ + 0, 1, 3, 1 /* 4x4 */ + }; diff --git a/dependencies/ih264d/decoder/ih264d_tables.h b/dependencies/ih264d/decoder/ih264d_tables.h new file mode 100644 index 00000000..88af4ec9 --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_tables.h @@ -0,0 +1,157 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ + +#ifndef _IH264D_TABLES_H_ +#define _IH264D_TABLES_H_ + +/** + ************************************************************************** + * \file ih264d_tables.h + * + * \brief + * Declaration of all tables used by h264 decoder + * + * \date + * 17/09/2004 + * + * \author MA + ************************************************************************** + */ +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264d_cabac.h" + +/*Deblocking Table declaration*/ +extern const UWORD8 gau1_ih264d_qp_scale_cr[]; +extern const UWORD8 gau1_ih264d_alpha_table[]; +extern const UWORD8 gau1_ih264d_clip_table_deblock[]; +extern const UWORD8 gau1_ih264d_beta_table[]; +extern const UWORD8 gau1_ih264d_clip_table[][4]; + +/*Parsing Table declaration*/ +extern const UWORD8 gau1_ih264d_cbp_tab[6]; +extern const UWORD32 gau4_ih264d_packed_bs2[32]; +extern const UWORD16 gau2_ih264d_4x4_v2h_reorder[16]; +extern const UWORD8 gau1_ih264d_subblk_offset[16]; +extern const UWORD8 gau1_ih264d_cbp_table[48][2]; + +/*Decode Slice Table declaration*/ +extern const UWORD8 gau1_ih264d_inv_scan[16]; +extern const UWORD8 gau1_ih264d_inv_scan_fld[16]; +extern const UWORD8 gau1_ih264d_dequant_matrix[6][16]; +extern const UWORD16 gau2_ih264_iquant_scale_4x4[6][16]; +extern const UWORD8 gau1_ih264d_dequant8x8_zigzag_cavlc[4][6][16]; +extern const UWORD16 gau1_ih264d_dequant8x8_cavlc[6][64]; + +extern const UWORD8 gau1_ih264d_inv_scan_prog8x8_cavlc[4][16]; +extern const UWORD8 gau1_ih264d_inv_scan_int8x8_cavlc[4][16]; +extern const UWORD8 gau1_ih264d_inv_scan_prog8x8_cabac[64]; +extern const UWORD8 gau1_ih264d_inv_scan_int8x8_cabac[64]; + +extern const UWORD8 gau1_ih264d_lastcoeff_context_inc[64]; +extern const UWORD8 gau1_ih264d_sigcoeff_context_inc_frame[64]; +extern const UWORD8 gau1_ih264d_sigcoeff_context_inc_field[64]; + +/* scaling related table declaration */ +extern const WORD16 gai2_ih264d_default_intra4x4[16]; +extern const WORD16 gai2_ih264d_default_inter4x4[16]; +extern const WORD16 gai2_ih264d_default_intra8x8[64]; +extern const WORD16 gai2_ih264d_default_inter8x8[64]; +extern const WORD16 gai2_ih264d_flat_4x4[16]; +extern const WORD16 gai2_ih264d_flat_8x8[64]; + +/*Decode MV Table declaration*/ +extern const WORD8 gau1_ih264d_mv_pred_condition[]; + +/** Number of subMbs for the 8x8 prediction mode */ +extern const UWORD8 gau1_ih264d_num_submb_part[]; + +/** Width of the 8x8 prediction mode in terms of subMbs */ +extern const UWORD8 gau1_ih264d_submb_partw[]; + +/** Height of the 8x8 prediction mode in terms of subMbs */ +extern const UWORD8 gau1_ih264d_submb_parth[]; + +/** Number of MB partitions for the MB prediction mode */ +extern const UWORD8 gau1_ih264d_num_mb_part[]; + +/** Width of the MB partition in terms of subMbs */ +extern const UWORD8 gau1_ih264d_mb_partw[]; + +/** Height of the MB partition in terms of subMbs */ +extern const UWORD8 gau1_ih264d_mb_parth[]; + +/** MB partition information is packed into a UWORD32 {0,number,width,height} */ +extern const UWORD32 gau4_ih264d_submb_part[]; + +extern const UWORD8 gau1_ih264d_submb_indx_mod[]; + +/** This table is used to assign CBPs to Inter MBs. */ +extern const UWORD8 gau1_ih264d_cbp_inter[]; + +/** Motion comp modes for P followed by B, + 0 to 4 : P Mbs + 5 to 27 : B Mbs + 28 to 30 : DIRECT */ +extern const UWORD8 gau1_ih264d_mb_mc_mode[]; + +extern const UWORD8 gau1_ih264d_submb_mc_mode[]; + +/** Sub MB pred modes for B slice */ +extern const UWORD8 gau1_ih264d_submb_pred_modes[]; + +/** MB pred modes for P and B slice */ +extern const WORD8 gau1_ih264d_mb_pred_modes[2][32]; + +/*Decode CAVLC Table declaration*/ +extern const UWORD8 gau1_ih264d_table_total_zero_2to10[9][64]; +extern const UWORD8 gau1_ih264d_table_total_zero_11to15[5][16]; +extern const UWORD8 gau1_ih264d_table_run_before[64]; +extern const UWORD16 gau2_ih264d_code_gx[304]; +extern const UWORD8 gau1_ih264d_cav_chromdc_vld[256]; +extern const UWORD16 gau2_ih264d_offset_num_vlc_tab[9]; +extern const UWORD8 gau1_ih264d_total_coeff_fn_ptr_offset[16]; +extern const WORD16 gai2_ih264d_trailing_one_level[14][3]; + +/*Decode CABAC Table declaration*/ +extern const UWORD32 gau4_ih264d_cabac_table[128][4]; + +/****************************************************************************/ +/* For error detection in intra pred4x4 modes */ +/****************************************************************************/ +extern const UWORD8 gau1_ih264d_intra_pred_err_code[9]; + +/*****************************************************************************/ +/* Cabac tables for context initialization depending upon type of Slice, */ +/* cabac init Idc value and Qp. */ +/*****************************************************************************/ +extern const UWORD8 gau1_ih264d_cabac_ctxt_init_table[NUM_CAB_INIT_IDC_PLUS_ONE][QP_RANGE][NUM_CABAC_CTXTS]; + +/*****************************************************************************/ +/* SEI tables for field usge and which field first */ +/*****************************************************************************/ +extern const UWORD8 gau1_ih264d_sei_fld_usage[9][3]; + + +extern const UWORD8 gau1_ih264d_top_left_mb_part_indx_mod[]; +extern const UWORD8 gau1_ih264d_submb_indx_mod_sp_drct[]; + +#endif /*TABLES_H*/ diff --git a/dependencies/ih264d/decoder/ih264d_thread_compute_bs.c b/dependencies/ih264d/decoder/ih264d_thread_compute_bs.c new file mode 100644 index 00000000..951cef4f --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_thread_compute_bs.c @@ -0,0 +1,737 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ + +/*! + ************************************************************************** + * \file ih264d_thread_compute_bs.c + * + * \brief + * Contains routines that for multi-thread decoder + * + * Detailed_description + * + * \date + * 20/02/2012 + * + * \author ZR + ************************************************************************** + */ +#include "ih264d_error_handler.h" +#include "ih264d_debug.h" +#include <string.h> +#include "ih264d_defs.h" +#include "ih264d_debug.h" +#include "ih264d_tables.h" +#include "ih264d_structs.h" +#include "ih264d_defs.h" +#include "ih264d_mb_utils.h" + +#include "ih264d_thread_compute_bs.h" +#include "ithread.h" +#include "ih264d_deblocking.h" +#include "ih264d_process_pslice.h" +#include "ih264d_process_intra_mb.h" +#include "ih264d_mb_utils.h" +#include "ih264d_tables.h" +#include "ih264d_format_conv.h" +#include "ih264d_defs.h" +UWORD16 ih264d_update_csbp_8x8(UWORD16 u2_luma_csbp); +void ih264d_fill_bs2_horz_vert(UWORD32 *pu4_bs, /* Base pointer of BS table */ + WORD32 u4_left_mb_csbp, /* csbp of left mb */ + WORD32 u4_top_mb_csbp, /* csbp of top mb */ + WORD32 u4_cur_mb_csbp, /* csbp of current mb */ + const UWORD32 *pu4_packed_bs2, const UWORD16 *pu2_4x4_v2h_reorder); +void ih264d_copy_intra_pred_line(dec_struct_t *ps_dec, + dec_mb_info_t *ps_cur_mb_info, + UWORD32 nmb_index); + +#define BS_MB_GROUP 4 +#define DEBLK_MB_GROUP 1 +#define FORMAT_CONV_MB_GROUP 4 + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_compute_bs_non_mbaff_thread */ +/* */ +/* Description : This function computes the pointers of left,top & current*/ +/* : Nnz, MvPred & deblk_mb_t and supplies to FillBs function for*/ +/* : Boundary Strength Calculation .this function is used */ +/* : BS being calculated in separate thread */ +/* Inputs : pointer to decoder context,cur_mb_info,u4_mb_num */ +/* Processing : */ +/* */ +/* Outputs : Produces the Boundary Strength for Current Mb */ +/* Returns : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* ITTIAM */ +/*****************************************************************************/ + +void ih264d_compute_bs_non_mbaff_thread(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info, + UWORD32 u4_mb_num) +{ + /* Mvpred and Nnz for top and Courrent */ + mv_pred_t *ps_cur_mv_pred, *ps_top_mv_pred = NULL, *ps_left_mv_pred; + /* deblk_mb_t Params */ + deblk_mb_t *ps_cur_mb_params; /*< Parameters of current MacroBlock */ + deblkmb_neighbour_t *ps_deblk_top_mb; + + /* Reference Index to POC mapping*/ + void ** apv_map_ref_idx_to_poc; + UWORD32 u4_leftmbtype; + + UWORD16 u2_left_csbp, u2_top_csbp, u2_cur_csbp; + + /* Set of flags */ + UWORD32 u4_cur_mb_intra, u1_top_mb_typ, u4_cur_mb_fld; + UWORD32 u1_cur_mb_type; + UWORD32 * pu4_bs_table; + + /* Neighbour availability */ + /* Initialization */ + const UWORD32 u2_mbx = ps_cur_mb_info->u2_mbx; + const UWORD32 u2_mby = ps_cur_mb_info->u2_mby; + const UWORD32 u1_pingpong = u2_mbx & 0x01; + + PROFILE_DISABLE_BOUNDARY_STRENGTH() + ps_deblk_top_mb = ps_dec->ps_deblk_top_mb + u2_mbx; + + /* Pointer assignment for Current DeblkMB, Current Mv Pred */ + ps_cur_mb_params = ps_dec->ps_deblk_pic + u4_mb_num; + ps_cur_mv_pred = ps_dec->s_cur_pic.ps_mv + (u4_mb_num << 4); + + apv_map_ref_idx_to_poc = + (void **)ps_dec->ps_computebs_cur_slice->ppv_map_ref_idx_to_poc + + 1; + u1_cur_mb_type = ps_cur_mb_params->u1_mb_type; + u1_top_mb_typ = ps_deblk_top_mb->u1_mb_type; + ps_deblk_top_mb->u1_mb_type = u1_cur_mb_type; + + { + ps_cur_mb_params->u1_topmb_qp = ps_deblk_top_mb->u1_mb_qp; + ps_deblk_top_mb->u1_mb_qp = ps_cur_mb_params->u1_mb_qp; + + ps_cur_mb_params->u1_left_mb_qp = ps_dec->deblk_left_mb[1].u1_mb_qp; + ps_dec->deblk_left_mb[1].u1_mb_qp = ps_cur_mb_params->u1_mb_qp; + + } + + /* if no deblocking required for current Mb then continue */ + /* Check next Mbs in Mb group */ + if(ps_cur_mb_params->u1_deblocking_mode & MB_DISABLE_FILTERING) + { + void ** pu4_map_ref_idx_to_poc_l1 = apv_map_ref_idx_to_poc + + POC_LIST_L0_TO_L1_DIFF; + { + /* Store Parameter for Top MvPred refernce frame Address */ + + void ** ppv_top_mv_pred_addr = ps_cur_mb_info->ps_curmb->u4_pic_addrress; + WORD8 * p1_refTop0 = (ps_cur_mv_pred + 12)->i1_ref_frame; + WORD8 * p1_refTop1 = (ps_cur_mv_pred + 14)->i1_ref_frame; + + /* Store Left addresses for Next Mb */ + void ** ppv_left_mv_pred_addr = + ps_dec->ps_left_mvpred_addr[!u1_pingpong][1].u4_add; + WORD8 * p1_refleft0 = (ps_cur_mv_pred + 3)->i1_ref_frame; + + + ppv_top_mv_pred_addr[0] = apv_map_ref_idx_to_poc[p1_refTop0[0]]; + ppv_top_mv_pred_addr[1] = pu4_map_ref_idx_to_poc_l1[p1_refTop0[1]]; + + ppv_left_mv_pred_addr[2] = apv_map_ref_idx_to_poc[p1_refTop1[0]]; + ppv_top_mv_pred_addr[2] = apv_map_ref_idx_to_poc[p1_refTop1[0]]; + ppv_left_mv_pred_addr[3] = pu4_map_ref_idx_to_poc_l1[p1_refTop1[1]]; + ppv_top_mv_pred_addr[3] = pu4_map_ref_idx_to_poc_l1[p1_refTop1[1]]; + + ppv_left_mv_pred_addr[0] = apv_map_ref_idx_to_poc[p1_refleft0[0]]; + ppv_left_mv_pred_addr[1] = pu4_map_ref_idx_to_poc_l1[p1_refleft0[1]]; + //} + /* Storing the leftMbtype for next Mb */ + ps_dec->deblk_left_mb[1].u1_mb_type = ps_cur_mb_params->u1_mb_type; + } + + return; + } + + /* Flag for extra left Edge */ + ps_cur_mb_params->u1_single_call = 1; + + /* Update the Left deblk_mb_t and Left MvPred Parameters */ + if(!u2_mbx) + { + u4_leftmbtype = 0; + + /* Initialize the ps_left_mv_pred with Junk but Valid Location */ + /* to avoid invalid memory access */ + /* this is read only pointer */ + ps_left_mv_pred = ps_cur_mv_pred + 3; + } + else + { + u4_leftmbtype = ps_dec->deblk_left_mb[1].u1_mb_type; + + /* Come to Left Most Edge of the MB */ + ps_left_mv_pred = ps_cur_mv_pred - (1 << 4) + 3; + } + + if(!u2_mby) + u1_top_mb_typ = 0; + + /* MvPred Pointer Calculation */ + /* CHANGED CODE */ + ps_top_mv_pred = ps_cur_mv_pred - (ps_dec->u2_frm_wd_in_mbs << 4) + 12; + + u4_cur_mb_intra = u1_cur_mb_type & D_INTRA_MB; + u4_cur_mb_fld = !!(u1_cur_mb_type & D_FLD_MB); + /* Compute BS function */ + pu4_bs_table = ps_cur_mb_params->u4_bs_table; + + u2_cur_csbp = ps_cur_mb_info->ps_curmb->u2_luma_csbp; + u2_left_csbp = ps_cur_mb_info->ps_left_mb->u2_luma_csbp; + u2_top_csbp = ps_cur_mb_info->ps_top_mb->u2_luma_csbp; + + /* Compute BS function */ + if(ps_dec->ps_cur_sps->u1_profile_idc == HIGH_PROFILE_IDC) + { + if(ps_cur_mb_info->u1_tran_form8x8 == 1) + { + u2_cur_csbp = ih264d_update_csbp_8x8( + ps_cur_mb_info->ps_curmb->u2_luma_csbp); + } + + if(ps_cur_mb_info->ps_left_mb->u1_tran_form8x8 == 1) + { + u2_left_csbp = ih264d_update_csbp_8x8( + ps_cur_mb_info->ps_left_mb->u2_luma_csbp); + } + + if(ps_cur_mb_info->ps_top_mb->u1_tran_form8x8 == 1) + { + u2_top_csbp = ih264d_update_csbp_8x8( + ps_cur_mb_info->ps_top_mb->u2_luma_csbp); + } + } + if(u4_cur_mb_intra) + { + + pu4_bs_table[4] = 0x04040404; + pu4_bs_table[0] = u4_cur_mb_fld ? 0x03030303 : 0x04040404; + pu4_bs_table[1] = 0x03030303; + pu4_bs_table[2] = 0x03030303; + pu4_bs_table[3] = 0x03030303; + pu4_bs_table[5] = 0x03030303; + pu4_bs_table[6] = 0x03030303; + pu4_bs_table[7] = 0x03030303; + } + else + { + UWORD32 u4_is_non16x16 = !!(u1_cur_mb_type & D_PRED_NON_16x16); + UWORD32 u4_is_b = + (ps_dec->ps_computebs_cur_slice->slice_type == B_SLICE); + + + + + + + ih264d_fill_bs2_horz_vert(pu4_bs_table, u2_left_csbp, u2_top_csbp, + u2_cur_csbp, gau4_ih264d_packed_bs2, + gau2_ih264d_4x4_v2h_reorder); + + if(u4_leftmbtype & D_INTRA_MB) + pu4_bs_table[4] = 0x04040404; + + if(u1_top_mb_typ & D_INTRA_MB) + pu4_bs_table[0] = u4_cur_mb_fld ? 0x03030303 : 0x04040404; + + ps_dec->pf_fill_bs1[u4_is_b][u4_is_non16x16]( + ps_cur_mv_pred, ps_top_mv_pred, apv_map_ref_idx_to_poc, + pu4_bs_table, ps_left_mv_pred, + &(ps_dec->ps_left_mvpred_addr[u1_pingpong][1]), + ps_cur_mb_info->ps_top_mb->u4_pic_addrress, + (4 >> u4_cur_mb_fld)); + } + + { + void ** pu4_map_ref_idx_to_poc_l1 = apv_map_ref_idx_to_poc + + POC_LIST_L0_TO_L1_DIFF; + { + /* Store Parameter for Top MvPred refernce frame Address */ + + void ** ppv_top_mv_pred_addr = ps_cur_mb_info->ps_curmb->u4_pic_addrress; + WORD8 * p1_refTop0 = (ps_cur_mv_pred + 12)->i1_ref_frame; + WORD8 * p1_refTop1 = (ps_cur_mv_pred + 14)->i1_ref_frame; + + /* Store Left addresses for Next Mb */ + void ** ppv_left_mv_pred_addr = + ps_dec->ps_left_mvpred_addr[!u1_pingpong][1].u4_add; + WORD8 * p1_refleft0 = (ps_cur_mv_pred + 3)->i1_ref_frame; + + ppv_top_mv_pred_addr[0] = apv_map_ref_idx_to_poc[p1_refTop0[0]]; + ppv_top_mv_pred_addr[1] = pu4_map_ref_idx_to_poc_l1[p1_refTop0[1]]; + + ppv_left_mv_pred_addr[2] = apv_map_ref_idx_to_poc[p1_refTop1[0]]; + ppv_top_mv_pred_addr[2] = apv_map_ref_idx_to_poc[p1_refTop1[0]]; + ppv_left_mv_pred_addr[3] = pu4_map_ref_idx_to_poc_l1[p1_refTop1[1]]; + ppv_top_mv_pred_addr[3] = pu4_map_ref_idx_to_poc_l1[p1_refTop1[1]]; + + ppv_left_mv_pred_addr[0] = apv_map_ref_idx_to_poc[p1_refleft0[0]]; + ppv_left_mv_pred_addr[1] = pu4_map_ref_idx_to_poc_l1[p1_refleft0[1]]; + + /* Storing the leftMbtype for next Mb */ + ps_dec->deblk_left_mb[1].u1_mb_type = ps_cur_mb_params->u1_mb_type; + + } + } + + /* For transform 8x8 disable deblocking of the intrernal edges of a 8x8 block */ + if(ps_cur_mb_info->u1_tran_form8x8) + { + pu4_bs_table[1] = 0; + pu4_bs_table[3] = 0; + pu4_bs_table[5] = 0; + pu4_bs_table[7] = 0; + } +} + + +void ih264d_check_mb_map_deblk(dec_struct_t *ps_dec, + UWORD32 deblk_mb_grp, + tfr_ctxt_t *ps_tfr_cxt, + UWORD32 u4_check_mb_map) +{ + UWORD32 i = 0; + UWORD32 u4_mb_num; + UWORD32 u4_cond; + volatile UWORD8 *mb_map = ps_dec->pu1_recon_mb_map; + const WORD32 i4_cb_qp_idx_ofst = + ps_dec->ps_cur_pps->i1_chroma_qp_index_offset; + const WORD32 i4_cr_qp_idx_ofst = + ps_dec->ps_cur_pps->i1_second_chroma_qp_index_offset; + + UWORD32 u4_wd_y, u4_wd_uv; + UWORD8 u1_field_pic_flag = ps_dec->ps_cur_slice->u1_field_pic_flag; + + + u4_wd_y = ps_dec->u2_frm_wd_y << u1_field_pic_flag; + u4_wd_uv = ps_dec->u2_frm_wd_uv << u1_field_pic_flag; + + + for(i = 0; i < deblk_mb_grp; i++) + { + WORD32 nop_cnt = 8*128; + while(u4_check_mb_map == 1) + { + u4_mb_num = ps_dec->u4_cur_deblk_mb_num; + /*we wait for the right mb because of intra pred data dependency*/ + u4_mb_num = MIN(u4_mb_num + 1, (ps_dec->u4_deblk_mb_y + 1) * ps_dec->u2_frm_wd_in_mbs - 1); + CHECK_MB_MAP_BYTE(u4_mb_num, mb_map, u4_cond); + + if(u4_cond) + { + break; + } + else + { + if(nop_cnt > 0) + { + nop_cnt -= 128; + NOP(128); + } + else + { + nop_cnt = 8*128; + ithread_yield(); + } + } + } + + ih264d_deblock_mb_nonmbaff(ps_dec, ps_tfr_cxt, + i4_cb_qp_idx_ofst, i4_cr_qp_idx_ofst, + u4_wd_y, u4_wd_uv); + + + } + + +} +void ih264d_recon_deblk_slice(dec_struct_t *ps_dec, tfr_ctxt_t *ps_tfr_cxt) +{ + dec_mb_info_t *p_cur_mb; + UWORD32 u4_max_addr; + WORD32 i; + UWORD32 u1_mb_aff; + UWORD16 u2_slice_num; + UWORD32 u4_mb_num; + UWORD16 u2_first_mb_in_slice; + UWORD32 i2_pic_wdin_mbs; + UWORD8 u1_num_mbsleft, u1_end_of_row; + UWORD8 u1_mbaff; + UWORD16 i16_mb_x, i16_mb_y; + WORD32 j; + dec_mb_info_t * ps_cur_mb_info; + UWORD32 u1_slice_type, u1_B; + WORD32 u1_skip_th; + UWORD32 u1_ipcm_th; + WORD32 ret; + tfr_ctxt_t *ps_trns_addr; + UWORD32 u4_frame_stride; + UWORD32 x_offset, y_offset; + UWORD32 u4_slice_end; + pad_mgr_t *ps_pad_mgr ; + + /*check for mb map of first mb in slice to ensure slice header is parsed*/ + while(1) + { + UWORD32 u4_mb_num = ps_dec->cur_recon_mb_num; + UWORD32 u4_cond = 0; + WORD32 nop_cnt = 8*128; + + CHECK_MB_MAP_BYTE(u4_mb_num, ps_dec->pu1_recon_mb_map, u4_cond); + if(u4_cond) + { + break; + } + else + { + if(nop_cnt > 0) + { + nop_cnt -= 128; + NOP(128); + } + else + { + if(ps_dec->u4_output_present && + (ps_dec->u4_fmt_conv_cur_row < ps_dec->s_disp_frame_info.u4_y_ht)) + { + ps_dec->u4_fmt_conv_num_rows = + MIN(FMT_CONV_NUM_ROWS, + (ps_dec->s_disp_frame_info.u4_y_ht + - ps_dec->u4_fmt_conv_cur_row)); + ih264d_format_convert(ps_dec, &(ps_dec->s_disp_op), + ps_dec->u4_fmt_conv_cur_row, + ps_dec->u4_fmt_conv_num_rows); + ps_dec->u4_fmt_conv_cur_row += ps_dec->u4_fmt_conv_num_rows; + } + else + { + nop_cnt = 8*128; + ithread_yield(); + } + } + DEBUG_THREADS_PRINTF("waiting for mb mapcur_dec_mb_num = %d,ps_dec->u2_cur_mb_addr = %d\n",u2_cur_dec_mb_num, + ps_dec->u2_cur_mb_addr); + + } + } + + u4_max_addr = ps_dec->ps_cur_sps->u2_max_mb_addr; + u1_mb_aff = ps_dec->ps_cur_slice->u1_mbaff_frame_flag; + u2_first_mb_in_slice = ps_dec->ps_computebs_cur_slice->u4_first_mb_in_slice; + i2_pic_wdin_mbs = ps_dec->u2_frm_wd_in_mbs; + u1_mbaff = ps_dec->ps_cur_slice->u1_mbaff_frame_flag; + ps_pad_mgr = &ps_dec->s_pad_mgr; + + if(u2_first_mb_in_slice == 0) + ih264d_init_deblk_tfr_ctxt(ps_dec, ps_pad_mgr, ps_tfr_cxt, + ps_dec->u2_frm_wd_in_mbs, 0); + + + i16_mb_x = MOD(u2_first_mb_in_slice, i2_pic_wdin_mbs); + i16_mb_y = DIV(u2_first_mb_in_slice, i2_pic_wdin_mbs); + i16_mb_y <<= u1_mbaff; + ps_dec->i2_recon_thread_mb_y = i16_mb_y; + u4_frame_stride = ps_dec->u2_frm_wd_y + << ps_dec->ps_cur_slice->u1_field_pic_flag; + + x_offset = i16_mb_x << 4; + y_offset = (i16_mb_y * u4_frame_stride) << 4; + ps_trns_addr = &ps_dec->s_tran_iprecon; + + ps_trns_addr->pu1_dest_y = ps_dec->s_cur_pic.pu1_buf1 + x_offset + y_offset; + + u4_frame_stride = ps_dec->u2_frm_wd_uv + << ps_dec->ps_cur_slice->u1_field_pic_flag; + x_offset >>= 1; + y_offset = (i16_mb_y * u4_frame_stride) << 3; + + x_offset *= YUV420SP_FACTOR; + + ps_trns_addr->pu1_dest_u = ps_dec->s_cur_pic.pu1_buf2 + x_offset + y_offset; + ps_trns_addr->pu1_dest_v = ps_dec->s_cur_pic.pu1_buf3 + x_offset + y_offset; + + ps_trns_addr->pu1_mb_y = ps_trns_addr->pu1_dest_y; + ps_trns_addr->pu1_mb_u = ps_trns_addr->pu1_dest_u; + ps_trns_addr->pu1_mb_v = ps_trns_addr->pu1_dest_v; + + ps_dec->cur_recon_mb_num = u2_first_mb_in_slice << u1_mbaff; + + u4_slice_end = 0; + ps_dec->u4_bs_cur_slice_num_mbs = 0; + ps_dec->u4_cur_bs_mb_num = + (ps_dec->ps_computebs_cur_slice->u4_first_mb_in_slice) + << u1_mb_aff; + + if(ps_dec->i1_recon_in_thread3_flag) + { + ps_dec->pv_proc_tu_coeff_data = + (void *) ps_dec->ps_computebs_cur_slice->pv_tu_coeff_data_start; + } + + u1_slice_type = ps_dec->ps_computebs_cur_slice->slice_type; + + u1_B = (u1_slice_type == B_SLICE); + + u1_skip_th = ((u1_slice_type != I_SLICE) ? + (u1_B ? B_8x8 : PRED_8x8R0) : -1); + + u1_ipcm_th = ((u1_slice_type != I_SLICE) ? (u1_B ? 23 : 5) : 0); + + + + while(u4_slice_end != 1) + { + WORD32 recon_mb_grp,bs_mb_grp; + WORD32 nop_cnt = 8*128; + u1_num_mbsleft = ((i2_pic_wdin_mbs - i16_mb_x) << u1_mbaff); + if(u1_num_mbsleft <= ps_dec->u1_recon_mb_grp) + { + recon_mb_grp = u1_num_mbsleft; + u1_end_of_row = 1; + i16_mb_x = 0; + } + else + { + recon_mb_grp = ps_dec->u1_recon_mb_grp; + u1_end_of_row = 0; + i16_mb_x += (recon_mb_grp >> u1_mbaff); + } + + + while(1) + { + UWORD32 u4_cond = 0; + UWORD32 u4_mb_num = ps_dec->cur_recon_mb_num + recon_mb_grp - 1; + + /* + * Wait for one extra mb of MC, because some chroma IQ-IT functions + * sometimes loads the pixels of the right mb and stores with the loaded + * values. + */ + u4_mb_num = MIN(u4_mb_num + 1, (ps_dec->i2_recon_thread_mb_y + 1) * i2_pic_wdin_mbs - 1); + + CHECK_MB_MAP_BYTE(u4_mb_num, ps_dec->pu1_recon_mb_map, u4_cond); + if(u4_cond) + { + break; + } + else + { + if(nop_cnt > 0) + { + nop_cnt -= 128; + NOP(128); + } + else + { + if(ps_dec->u4_output_present && + (ps_dec->u4_fmt_conv_cur_row < ps_dec->s_disp_frame_info.u4_y_ht)) + { + ps_dec->u4_fmt_conv_num_rows = + MIN(FMT_CONV_NUM_ROWS, + (ps_dec->s_disp_frame_info.u4_y_ht + - ps_dec->u4_fmt_conv_cur_row)); + ih264d_format_convert(ps_dec, &(ps_dec->s_disp_op), + ps_dec->u4_fmt_conv_cur_row, + ps_dec->u4_fmt_conv_num_rows); + ps_dec->u4_fmt_conv_cur_row += ps_dec->u4_fmt_conv_num_rows; + } + else + { + nop_cnt = 8*128; + ithread_yield(); + } + } + } + } + + for(j = 0; j < recon_mb_grp; j++) + { + GET_SLICE_NUM_MAP(ps_dec->pu2_slice_num_map, ps_dec->cur_recon_mb_num, + u2_slice_num); + + if(u2_slice_num != ps_dec->u2_cur_slice_num_bs) + { + u4_slice_end = 1; + break; + } + if(ps_dec->i1_recon_in_thread3_flag) + { + ps_cur_mb_info = &ps_dec->ps_frm_mb_info[ps_dec->cur_recon_mb_num]; + + if(ps_cur_mb_info->u1_mb_type <= u1_skip_th) + { + ih264d_process_inter_mb(ps_dec, ps_cur_mb_info, j); + } + else if(ps_cur_mb_info->u1_mb_type != MB_SKIP) + { + if((u1_ipcm_th + 25) != ps_cur_mb_info->u1_mb_type) + { + ps_cur_mb_info->u1_mb_type -= (u1_skip_th + 1); + ih264d_process_intra_mb(ps_dec, ps_cur_mb_info, j); + } + } + + ih264d_copy_intra_pred_line(ps_dec, ps_cur_mb_info, j); + } + ps_dec->cur_recon_mb_num++; + } + + if(j != recon_mb_grp) + { + u1_end_of_row = 0; + } + + { + tfr_ctxt_t *ps_trns_addr = &ps_dec->s_tran_iprecon; + UWORD16 u2_mb_y; + + ps_trns_addr->pu1_dest_y += ps_trns_addr->u4_inc_y[u1_end_of_row]; + ps_trns_addr->pu1_dest_u += ps_trns_addr->u4_inc_uv[u1_end_of_row]; + ps_trns_addr->pu1_dest_v += ps_trns_addr->u4_inc_uv[u1_end_of_row]; + + if(u1_end_of_row) + { + ps_dec->i2_recon_thread_mb_y += (1 << u1_mbaff); + u2_mb_y = ps_dec->i2_recon_thread_mb_y; + y_offset = (u2_mb_y * u4_frame_stride) << 4; + ps_trns_addr->pu1_dest_y = ps_dec->s_cur_pic.pu1_buf1 + y_offset; + + u4_frame_stride = ps_dec->u2_frm_wd_uv + << ps_dec->ps_cur_slice->u1_field_pic_flag; + y_offset = (u2_mb_y * u4_frame_stride) << 3; + ps_trns_addr->pu1_dest_u = ps_dec->s_cur_pic.pu1_buf2 + y_offset; + ps_trns_addr->pu1_dest_v = ps_dec->s_cur_pic.pu1_buf3 + y_offset; + + ps_trns_addr->pu1_mb_y = ps_trns_addr->pu1_dest_y; + ps_trns_addr->pu1_mb_u = ps_trns_addr->pu1_dest_u; + ps_trns_addr->pu1_mb_v = ps_trns_addr->pu1_dest_v; + + } + } + + bs_mb_grp = j; + /* Compute BS for NMB group*/ + for(i = 0; i < bs_mb_grp; i++) + { + p_cur_mb = &ps_dec->ps_frm_mb_info[ps_dec->u4_cur_bs_mb_num]; + + DEBUG_THREADS_PRINTF("ps_dec->u4_cur_bs_mb_num = %d\n",ps_dec->u4_cur_bs_mb_num); + ih264d_compute_bs_non_mbaff_thread(ps_dec, p_cur_mb, + ps_dec->u4_cur_bs_mb_num); + ps_dec->u4_cur_bs_mb_num++; + ps_dec->u4_bs_cur_slice_num_mbs++; + + } + + if(ps_dec->u4_cur_bs_mb_num > u4_max_addr) + { + u4_slice_end = 1; + } + + /*deblock MB group*/ + { + UWORD32 u4_num_mbs; + + if(ps_dec->u4_cur_bs_mb_num > ps_dec->u4_cur_deblk_mb_num) + { + if(u1_end_of_row) + { + u4_num_mbs = ps_dec->u4_cur_bs_mb_num + - ps_dec->u4_cur_deblk_mb_num; + } + else + { + u4_num_mbs = ps_dec->u4_cur_bs_mb_num + - ps_dec->u4_cur_deblk_mb_num - 1; + } + } + else + u4_num_mbs = 0; + + ih264d_check_mb_map_deblk(ps_dec, u4_num_mbs, ps_tfr_cxt,0); + } + + } +} + +void ih264d_recon_deblk_thread(dec_struct_t *ps_dec) +{ + tfr_ctxt_t s_tfr_ctxt; + tfr_ctxt_t *ps_tfr_cxt = &s_tfr_ctxt; // = &ps_dec->s_tran_addrecon; + + + UWORD32 yield_cnt = 0; + + ithread_set_name("ih264d_recon_deblk_thread"); + + while(1) + { + + DEBUG_THREADS_PRINTF(" Entering compute bs slice\n"); + ih264d_recon_deblk_slice(ps_dec, ps_tfr_cxt); + + DEBUG_THREADS_PRINTF(" Exit compute bs slice \n"); + + if(ps_dec->cur_recon_mb_num > ps_dec->ps_cur_sps->u2_max_mb_addr) + { + break; + } + else + { + ps_dec->ps_computebs_cur_slice++; + ps_dec->u2_cur_slice_num_bs++; + } + DEBUG_THREADS_PRINTF("CBS thread:Got next slice/end of frame signal \n "); + + } + + if(ps_dec->u4_output_present && + (3 == ps_dec->u4_num_cores) && + (ps_dec->u4_fmt_conv_cur_row < ps_dec->s_disp_frame_info.u4_y_ht)) + { + ps_dec->u4_fmt_conv_num_rows = + (ps_dec->s_disp_frame_info.u4_y_ht + - ps_dec->u4_fmt_conv_cur_row); + ih264d_format_convert(ps_dec, &(ps_dec->s_disp_op), + ps_dec->u4_fmt_conv_cur_row, + ps_dec->u4_fmt_conv_num_rows); + ps_dec->u4_fmt_conv_cur_row += ps_dec->u4_fmt_conv_num_rows; + + } + + + +} + + diff --git a/dependencies/ih264d/decoder/ih264d_thread_compute_bs.h b/dependencies/ih264d/decoder/ih264d_thread_compute_bs.h new file mode 100644 index 00000000..e98423f4 --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_thread_compute_bs.h @@ -0,0 +1,38 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/* + * ih264d_thread_parse_decode.h + * + * Created on: Feb 21, 2012 + * Author: 100492 + */ + +#ifndef _IH264D_THREAD_COMPUTE_BS_H_ +#define _IH264D_THREAD_COMPUTE_BS_H_ +void ih264d_compute_bs_non_mbaff_thread(dec_struct_t * ps_dec, + dec_mb_info_t * ps_cur_mb_info, + UWORD32 u4_mb_num); + +void ih264d_recon_deblk_thread(dec_struct_t *ps_dec); +void ih264d_check_mb_map_deblk(dec_struct_t *ps_dec, + UWORD32 deblk_mb_grp, + tfr_ctxt_t *ps_tfr_cxt, + UWORD32 u4_check_mb_map); +#endif /* _IH264D_THREAD_COMPUTE_BS_H_ */ diff --git a/dependencies/ih264d/decoder/ih264d_thread_parse_decode.c b/dependencies/ih264d/decoder/ih264d_thread_parse_decode.c new file mode 100644 index 00000000..131c8082 --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_thread_parse_decode.c @@ -0,0 +1,648 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/*! + ************************************************************************** + * \file ih264d_thread_parse_decode.c + * + * \brief + * Contains routines that for multi-thread decoder + * + * Detailed_description + * + * \date + * 20/02/2012 + * + * \author ZR + ************************************************************************** + */ + +#include "ih264d_error_handler.h" +#include "ih264d_debug.h" +#include "ithread.h" +#include <string.h> +#include "ih264d_defs.h" +#include "ih264d_debug.h" +#include "ih264d_tables.h" +#include "ih264d_structs.h" +#include "ih264d_defs.h" +#include "ih264d_mb_utils.h" +#include "ih264d_thread_parse_decode.h" +#include "ih264d_inter_pred.h" + +#include "ih264d_process_pslice.h" +#include "ih264d_process_intra_mb.h" +#include "ih264d_deblocking.h" +#include "ih264d_format_conv.h" + +void ih264d_deblock_mb_level(dec_struct_t *ps_dec, + dec_mb_info_t *ps_cur_mb_info, + UWORD32 nmb_index); + +void ih264d_copy_intra_pred_line(dec_struct_t *ps_dec, + dec_mb_info_t *ps_cur_mb_info, + UWORD32 nmb_index); + +void ih264d_parse_tfr_nmb(dec_struct_t * ps_dec, + UWORD8 u1_mb_idx, + UWORD8 u1_num_mbs, + UWORD8 u1_num_mbs_next, + UWORD8 u1_tfr_n_mb, + UWORD8 u1_end_of_row) +{ + WORD32 i, u4_mb_num; + + const UWORD32 u1_mbaff = ps_dec->ps_cur_slice->u1_mbaff_frame_flag; + UWORD32 u4_n_mb_start; + + UNUSED(u1_mb_idx); + UNUSED(u1_num_mbs_next); + if(u1_tfr_n_mb) + { + + + u4_n_mb_start = (ps_dec->u2_cur_mb_addr + 1) - u1_num_mbs; + + // copy into s_frmMbInfo + + u4_mb_num = u4_n_mb_start; + u4_mb_num = (ps_dec->u2_cur_mb_addr + 1) - u1_num_mbs; + + for(i = 0; i < u1_num_mbs; i++) + { + UPDATE_SLICE_NUM_MAP(ps_dec->pu2_slice_num_map, u4_mb_num, + ps_dec->u2_cur_slice_num); + DATA_SYNC(); + UPDATE_MB_MAP_MBNUM_BYTE(ps_dec->pu1_dec_mb_map, u4_mb_num); + + u4_mb_num++; + } + + /****************************************************************/ + /* Check for End Of Row in Next iteration */ + /****************************************************************/ + + /****************************************************************/ + /* Transfer the Following things */ + /* N-Mb DeblkParams Data ( To Ext DeblkParams Buffer ) */ + /* N-Mb Recon Data ( To Ext Frame Buffer ) */ + /* N-Mb Intrapredline Data ( Updated Internally) */ + /* N-Mb MV Data ( To Ext MV Buffer ) */ + /* N-Mb MVTop/TopRight Data ( To Int MV Top Scratch Buffers) */ + /****************************************************************/ + + /* Swap top and current pointers */ + + ps_dec->s_tran_addrecon_parse.pu1_dest_y += + ps_dec->s_tran_addrecon_parse.u4_inc_y[u1_end_of_row]; + ps_dec->s_tran_addrecon_parse.pu1_dest_u += + ps_dec->s_tran_addrecon_parse.u4_inc_uv[u1_end_of_row]; + ps_dec->s_tran_addrecon_parse.pu1_dest_v += + ps_dec->s_tran_addrecon_parse.u4_inc_uv[u1_end_of_row]; + + if(u1_end_of_row) + { + UWORD16 u2_mb_y; + UWORD32 u4_frame_stride, y_offset; + + ps_dec->ps_top_mb_row = ps_dec->ps_cur_mb_row; + ps_dec->ps_cur_mb_row += ((ps_dec->u2_frm_wd_in_mbs) << u1_mbaff); + + u2_mb_y = ps_dec->u2_mby + (1 + u1_mbaff); + u4_frame_stride = ps_dec->u2_frm_wd_y + << ps_dec->ps_cur_slice->u1_field_pic_flag; + y_offset = (u2_mb_y * u4_frame_stride) << 4; + ps_dec->s_tran_addrecon_parse.pu1_dest_y = + ps_dec->s_cur_pic.pu1_buf1 + y_offset; + + u4_frame_stride = ps_dec->u2_frm_wd_uv + << ps_dec->ps_cur_slice->u1_field_pic_flag; + y_offset = (u2_mb_y * u4_frame_stride) << 3; + ps_dec->s_tran_addrecon_parse.pu1_dest_u = + ps_dec->s_cur_pic.pu1_buf2 + y_offset; + ps_dec->s_tran_addrecon_parse.pu1_dest_v = + ps_dec->s_cur_pic.pu1_buf3 + y_offset; + + } + + ps_dec->ps_deblk_mbn += u1_num_mbs; + + /* + * The Slice boundary is also a valid condition to transfer. So recalculate + * the Left increment, in case the number of MBs is lesser than the + * N MB value. c_numMbs will be equal to N of N MB if the entire N Mb is + * decoded. + */ + ps_dec->s_tran_addrecon.u2_mv_left_inc = ((u1_num_mbs >> u1_mbaff) - 1) + << (4 + u1_mbaff); + ps_dec->s_tran_addrecon.u2_mv_top_left_inc = (u1_num_mbs << 2) - 1 + - (u1_mbaff << 2); + + /* reassign left MV and cur MV pointers */ + ps_dec->ps_mv_left = ps_dec->ps_mv_cur + + ps_dec->s_tran_addrecon.u2_mv_left_inc; + + ps_dec->ps_mv_cur += (u1_num_mbs << 4); + ps_dec->u4_num_mbs_prev_nmb = u1_num_mbs; + + } +} + +void ih264d_decode_tfr_nmb(dec_struct_t * ps_dec, + UWORD8 u1_num_mbs, + UWORD8 u1_num_mbs_next, + UWORD8 u1_end_of_row) +{ + + UWORD32 u1_end_of_row_next; + + const UWORD32 u1_mbaff = ps_dec->ps_cur_slice->u1_mbaff_frame_flag; + + /****************************************************************/ + /* Check for End Of Row in Next iteration */ + /****************************************************************/ + u1_end_of_row_next = u1_num_mbs_next && + ((u1_num_mbs_next) <= (ps_dec->u1_recon_mb_grp >> u1_mbaff)); + + /****************************************************************/ + /* Transfer the Following things */ + /* N-Mb DeblkParams Data ( To Ext DeblkParams Buffer ) */ + /* N-Mb Recon Data ( To Ext Frame Buffer ) */ + /* N-Mb Intrapredline Data ( Updated Internally) */ + /* N-Mb MV Data ( To Ext MV Buffer ) */ + /* N-Mb MVTop/TopRight Data ( To Int MV Top Scratch Buffers) */ + /****************************************************************/ + if(u1_end_of_row) + { + ps_dec->i2_dec_thread_mb_y += (1 << u1_mbaff); + } + ih264d_transfer_mb_group_data(ps_dec, u1_num_mbs, u1_end_of_row, + u1_end_of_row_next); + +} + +WORD32 ih264d_decode_recon_tfr_nmb_thread(dec_struct_t * ps_dec, + UWORD8 u1_num_mbs, + UWORD8 u1_num_mbs_next, + UWORD8 u1_end_of_row) +{ + WORD32 i,j; + dec_mb_info_t * ps_cur_mb_info; + UWORD32 u4_update_mbaff = 0; + const UWORD32 u1_mbaff = ps_dec->ps_cur_slice->u1_mbaff_frame_flag; + UWORD32 u1_slice_type, u1_B; + WORD32 u1_skip_th; + UWORD32 u1_ipcm_th; + UWORD32 u4_cond; + UWORD16 u2_slice_num,u2_cur_dec_mb_num; + WORD32 ret; + UWORD32 u4_mb_num; + WORD32 nop_cnt = 8*128; + u1_slice_type = ps_dec->ps_decode_cur_slice->slice_type; + + u1_B = (u1_slice_type == B_SLICE); + + u1_skip_th = ((u1_slice_type != I_SLICE) ? + (u1_B ? B_8x8 : PRED_8x8R0) : -1); + + u1_ipcm_th = ((u1_slice_type != I_SLICE) ? (u1_B ? 23 : 5) : 0); + + u2_cur_dec_mb_num = ps_dec->cur_dec_mb_num; + + while(1) + { + + UWORD32 u4_max_mb = (UWORD32)(ps_dec->i2_dec_thread_mb_y + (1 << u1_mbaff)) * ps_dec->u2_frm_wd_in_mbs - 1; + u4_mb_num = u2_cur_dec_mb_num; + /*introducing 1 MB delay*/ + u4_mb_num = MIN(u4_mb_num + u1_num_mbs + 1, u4_max_mb); + + CHECK_MB_MAP_BYTE(u4_mb_num, ps_dec->pu1_dec_mb_map, u4_cond); + if(u4_cond) + { + break; + } + else + { + if(nop_cnt > 0) + { + nop_cnt -= 128; + NOP(128); + } + else + { + if(ps_dec->u4_output_present && (2 == ps_dec->u4_num_cores) && + (ps_dec->u4_fmt_conv_cur_row < ps_dec->s_disp_frame_info.u4_y_ht)) + { + ps_dec->u4_fmt_conv_num_rows = + MIN(FMT_CONV_NUM_ROWS, + (ps_dec->s_disp_frame_info.u4_y_ht + - ps_dec->u4_fmt_conv_cur_row)); + ih264d_format_convert(ps_dec, &(ps_dec->s_disp_op), + ps_dec->u4_fmt_conv_cur_row, + ps_dec->u4_fmt_conv_num_rows); + ps_dec->u4_fmt_conv_cur_row += ps_dec->u4_fmt_conv_num_rows; + } + else + { + nop_cnt = 8*128; + ithread_yield(); + } + } + } + } + /* N Mb MC Loop */ + for(i = 0; i < u1_num_mbs; i++) + { + u4_mb_num = u2_cur_dec_mb_num; + + GET_SLICE_NUM_MAP(ps_dec->pu2_slice_num_map, u2_cur_dec_mb_num, + u2_slice_num); + + if(u2_slice_num != ps_dec->u2_cur_slice_num_dec_thread) + { + ps_dec->u4_cur_slice_decode_done = 1; + break; + } + + ps_cur_mb_info = &ps_dec->ps_frm_mb_info[u2_cur_dec_mb_num]; + + ps_dec->u4_dma_buf_idx = 0; + ps_dec->u4_pred_info_idx = 0; + + if(ps_cur_mb_info->u1_mb_type <= u1_skip_th) + { + WORD32 pred_cnt = 0; + pred_info_pkd_t *ps_pred_pkd; + UWORD32 u4_pred_info_pkd_idx; + WORD8 i1_pred; + + u4_pred_info_pkd_idx = ps_cur_mb_info->u4_pred_info_pkd_idx; + + while(pred_cnt < ps_cur_mb_info->u1_num_pred_parts) + { + ps_pred_pkd = ps_dec->ps_pred_pkd + u4_pred_info_pkd_idx; + + ps_dec->p_form_mb_part_info_thread(ps_pred_pkd,ps_dec, + ps_cur_mb_info->u2_mbx, + ps_cur_mb_info->u2_mby, + (i >> u1_mbaff), + ps_cur_mb_info); + + u4_pred_info_pkd_idx++; + pred_cnt++; + } + ps_dec->p_mc_dec_thread(ps_dec, ps_cur_mb_info); + } + else if(ps_cur_mb_info->u1_mb_type == MB_SKIP) + { + WORD32 pred_cnt = 0; + pred_info_pkd_t *ps_pred_pkd; + UWORD32 u4_pred_info_pkd_idx; + WORD8 i1_pred; + + u4_pred_info_pkd_idx = ps_cur_mb_info->u4_pred_info_pkd_idx; + + while(pred_cnt < ps_cur_mb_info->u1_num_pred_parts) + { + ps_pred_pkd = ps_dec->ps_pred_pkd + u4_pred_info_pkd_idx; + + ps_dec->p_form_mb_part_info_thread(ps_pred_pkd,ps_dec, + ps_cur_mb_info->u2_mbx, + ps_cur_mb_info->u2_mby, + (i >> u1_mbaff), + ps_cur_mb_info); + + u4_pred_info_pkd_idx++; + pred_cnt++; + } + /* Decode MB skip */ + ps_dec->p_mc_dec_thread(ps_dec, ps_cur_mb_info); + } + + u2_cur_dec_mb_num++; + } + + /* N Mb IQ IT RECON Loop */ + for(j = 0; j < i; j++) + { + ps_cur_mb_info = &ps_dec->ps_frm_mb_info[ps_dec->cur_dec_mb_num]; + + if((ps_dec->u4_num_cores == 2) || !ps_dec->i1_recon_in_thread3_flag) + { + if(ps_cur_mb_info->u1_mb_type <= u1_skip_th) + { + ih264d_process_inter_mb(ps_dec, ps_cur_mb_info, j); + } + else if(ps_cur_mb_info->u1_mb_type != MB_SKIP) + { + if((u1_ipcm_th + 25) != ps_cur_mb_info->u1_mb_type) + { + ps_cur_mb_info->u1_mb_type -= (u1_skip_th + 1); + ih264d_process_intra_mb(ps_dec, ps_cur_mb_info, j); + } + } + + + if(ps_dec->u4_use_intrapred_line_copy == 1) + ih264d_copy_intra_pred_line(ps_dec, ps_cur_mb_info, j); + } + + DATA_SYNC(); + + if(u1_mbaff) + { + if(u4_update_mbaff) + { + UWORD32 u4_mb_num = ps_cur_mb_info->u2_mbx + + ps_dec->u2_frm_wd_in_mbs + * (ps_cur_mb_info->u2_mby >> 1); + UPDATE_MB_MAP_MBNUM_BYTE(ps_dec->pu1_recon_mb_map, u4_mb_num); + u4_update_mbaff = 0; + } + else + { + u4_update_mbaff = 1; + } + } + else + { + UWORD32 u4_mb_num = ps_cur_mb_info->u2_mbx + + ps_dec->u2_frm_wd_in_mbs * ps_cur_mb_info->u2_mby; + UPDATE_MB_MAP_MBNUM_BYTE(ps_dec->pu1_recon_mb_map, u4_mb_num); + } + ps_dec->cur_dec_mb_num++; + } + + /*N MB deblocking*/ + if(ps_dec->u4_nmb_deblk == 1) + { + UWORD32 u4_wd_y, u4_wd_uv; + tfr_ctxt_t *ps_tfr_cxt = &(ps_dec->s_tran_addrecon); + UWORD8 u1_field_pic_flag = ps_dec->ps_cur_slice->u1_field_pic_flag; + const WORD32 i4_cb_qp_idx_ofst = + ps_dec->ps_cur_pps->i1_chroma_qp_index_offset; + const WORD32 i4_cr_qp_idx_ofst = + ps_dec->ps_cur_pps->i1_second_chroma_qp_index_offset; + + u4_wd_y = ps_dec->u2_frm_wd_y << u1_field_pic_flag; + u4_wd_uv = ps_dec->u2_frm_wd_uv << u1_field_pic_flag; + + ps_cur_mb_info = &ps_dec->ps_frm_mb_info[ps_dec->u4_cur_deblk_mb_num]; + + ps_dec->u4_deblk_mb_x = ps_cur_mb_info->u2_mbx; + ps_dec->u4_deblk_mb_y = ps_cur_mb_info->u2_mby; + + + for(j = 0; j < i; j++) + { + ih264d_deblock_mb_nonmbaff(ps_dec, ps_tfr_cxt, + i4_cb_qp_idx_ofst, i4_cr_qp_idx_ofst, + u4_wd_y, u4_wd_uv); + + } + } + + /*handle the last mb in picture case*/ + if(ps_dec->cur_dec_mb_num > ps_dec->ps_cur_sps->u2_max_mb_addr) + ps_dec->u4_cur_slice_decode_done = 1; + + if(i != u1_num_mbs) + { + u1_end_of_row = 0; + /*Number of MB's left in row*/ + u1_num_mbs_next = u1_num_mbs_next + ((u1_num_mbs - i) >> u1_mbaff); + } + + ih264d_decode_tfr_nmb(ps_dec, (i), u1_num_mbs_next, u1_end_of_row); + + return OK; +} + +WORD32 ih264d_decode_slice_thread(dec_struct_t *ps_dec) +{ + UWORD8 u1_num_mbs_next, u1_num_mbsleft, u1_end_of_row = 0; + const UWORD32 i2_pic_wdin_mbs = ps_dec->u2_frm_wd_in_mbs; + UWORD8 u1_mbaff, u1_num_mbs; + + UWORD16 u2_first_mb_in_slice; + UWORD16 i16_mb_x, i16_mb_y; + UWORD8 u1_field_pic; + UWORD32 u4_frame_stride, x_offset, y_offset; + WORD32 ret; + + tfr_ctxt_t *ps_trns_addr; + + /*check for mb map of first mb in slice to ensure slice header is parsed*/ + while(1) + { + UWORD32 u4_mb_num = ps_dec->cur_dec_mb_num; + UWORD32 u4_cond = 0; + WORD32 nop_cnt = 8 * 128; + CHECK_MB_MAP_BYTE(u4_mb_num, ps_dec->pu1_dec_mb_map, u4_cond); + if(u4_cond) + { + break; + } + else + { + if(nop_cnt > 0) + { + nop_cnt -= 128; + NOP(128); + } + else if(ps_dec->u4_output_present && (2 == ps_dec->u4_num_cores) && + (ps_dec->u4_fmt_conv_cur_row < ps_dec->s_disp_frame_info.u4_y_ht)) + { + ps_dec->u4_fmt_conv_num_rows = + MIN(FMT_CONV_NUM_ROWS, + (ps_dec->s_disp_frame_info.u4_y_ht + - ps_dec->u4_fmt_conv_cur_row)); + ih264d_format_convert(ps_dec, &(ps_dec->s_disp_op), + ps_dec->u4_fmt_conv_cur_row, + ps_dec->u4_fmt_conv_num_rows); + ps_dec->u4_fmt_conv_cur_row += ps_dec->u4_fmt_conv_num_rows; + } + else + { + nop_cnt = 8*128; + ithread_yield(); + } + DEBUG_THREADS_PRINTF("waiting for mb mapcur_dec_mb_num = %d,ps_dec->u2_cur_mb_addr = %d\n",u2_cur_dec_mb_num, + ps_dec->u2_cur_mb_addr); + + } + } + + + + u1_mbaff = ps_dec->ps_cur_slice->u1_mbaff_frame_flag; + + u2_first_mb_in_slice = ps_dec->ps_decode_cur_slice->u4_first_mb_in_slice; + + i16_mb_x = MOD(u2_first_mb_in_slice, i2_pic_wdin_mbs); + i16_mb_y = DIV(u2_first_mb_in_slice, i2_pic_wdin_mbs); + i16_mb_y <<= u1_mbaff; + ps_dec->i2_dec_thread_mb_y = i16_mb_y; + + + ps_dec->cur_dec_mb_num = u2_first_mb_in_slice << u1_mbaff; + + if((ps_dec->u4_num_cores == 2) || !ps_dec->i1_recon_in_thread3_flag) + { + ps_dec->pv_proc_tu_coeff_data = + (void *) ps_dec->ps_decode_cur_slice->pv_tu_coeff_data_start; + } + + // recalculate recon pointers + u1_field_pic = ps_dec->ps_cur_slice->u1_field_pic_flag; + u4_frame_stride = ps_dec->u2_frm_wd_y << u1_field_pic; + x_offset = i16_mb_x << 4; + y_offset = (i16_mb_y * u4_frame_stride) << 4; + + ps_trns_addr = &(ps_dec->s_tran_addrecon); + + ps_trns_addr->pu1_dest_y = ps_dec->s_cur_pic.pu1_buf1 + x_offset + y_offset; + + u4_frame_stride = ps_dec->u2_frm_wd_uv << u1_field_pic; + x_offset >>= 1; + y_offset = (i16_mb_y * u4_frame_stride) << 3; + + x_offset *= YUV420SP_FACTOR; + + ps_trns_addr->pu1_dest_u = ps_dec->s_cur_pic.pu1_buf2 + x_offset + y_offset; + ps_trns_addr->pu1_dest_v = ps_dec->s_cur_pic.pu1_buf3 + x_offset + y_offset; + + ps_trns_addr->pu1_mb_y = ps_trns_addr->pu1_dest_y; + ps_trns_addr->pu1_mb_u = ps_trns_addr->pu1_dest_u; + ps_trns_addr->pu1_mb_v = ps_trns_addr->pu1_dest_v; + + + /* Initialise MC and formMbPartInfo fn ptrs one time based on profile_idc */ + { + ps_dec->p_mc_dec_thread = ih264d_motion_compensate_bp; + ps_dec->p_form_mb_part_info_thread = ih264d_form_mb_part_info_bp; + } + { + UWORD8 uc_nofield_nombaff; + uc_nofield_nombaff = ((ps_dec->ps_cur_slice->u1_field_pic_flag == 0) + && (ps_dec->ps_cur_slice->u1_mbaff_frame_flag == 0) + && (ps_dec->ps_decode_cur_slice->slice_type != B_SLICE) + && (ps_dec->ps_cur_pps->u1_wted_pred_flag == 0)); + + if(uc_nofield_nombaff == 0) + { + ps_dec->p_mc_dec_thread = ih264d_motion_compensate_mp; + ps_dec->p_form_mb_part_info_thread = ih264d_form_mb_part_info_mp; + } + + } + + ps_dec->u4_cur_slice_decode_done = 0; + + + while(ps_dec->u4_cur_slice_decode_done != 1) + { + + u1_num_mbsleft = ((i2_pic_wdin_mbs - i16_mb_x) << u1_mbaff); + + if(u1_num_mbsleft <= ps_dec->u1_recon_mb_grp) + { + u1_num_mbs = u1_num_mbsleft; + + /*Indicate number of mb's left in a row*/ + u1_num_mbs_next = 0; + u1_end_of_row = 1; + i16_mb_x = 0; + } + else + { + u1_num_mbs = ps_dec->u1_recon_mb_grp; + + /*Indicate number of mb's left in a row*/ + u1_num_mbs_next = i2_pic_wdin_mbs - i16_mb_x + - (ps_dec->u1_recon_mb_grp >> u1_mbaff); + i16_mb_x += (u1_num_mbs >> u1_mbaff); + u1_end_of_row = 0; + + } + ret = ih264d_decode_recon_tfr_nmb_thread(ps_dec, u1_num_mbs, u1_num_mbs_next, + u1_end_of_row); + if(ret != OK) + return ret; + } + return OK; +} + +void ih264d_decode_picture_thread(dec_struct_t *ps_dec ) +{ + ithread_set_name("ih264d_decode_picture_thread"); + while(1) + { + /*Complete all writes before processing next slice*/ + + DEBUG_THREADS_PRINTF(" Entering decode slice\n"); + + ih264d_decode_slice_thread(ps_dec); + DEBUG_THREADS_PRINTF(" Exit ih264d_decode_slice_thread \n"); + + + if(ps_dec->cur_dec_mb_num + > ps_dec->ps_cur_sps->u2_max_mb_addr) + { + /*Last slice in frame*/ + break; + } + else + { + ps_dec->ps_decode_cur_slice++; + ps_dec->u2_cur_slice_num_dec_thread++; + } + + } + if(ps_dec->u4_output_present && (2 == ps_dec->u4_num_cores) && + (ps_dec->u4_fmt_conv_cur_row < ps_dec->s_disp_frame_info.u4_y_ht)) + { + ps_dec->u4_fmt_conv_num_rows = + (ps_dec->s_disp_frame_info.u4_y_ht + - ps_dec->u4_fmt_conv_cur_row); + ih264d_format_convert(ps_dec, &(ps_dec->s_disp_op), + ps_dec->u4_fmt_conv_cur_row, + ps_dec->u4_fmt_conv_num_rows); + ps_dec->u4_fmt_conv_cur_row += ps_dec->u4_fmt_conv_num_rows; + } +} + +void ih264d_signal_decode_thread(dec_struct_t *ps_dec) +{ + if(ps_dec->u4_dec_thread_created == 1) + { + ithread_join(ps_dec->pv_dec_thread_handle, NULL); + ps_dec->u4_dec_thread_created = 0; + } +} +void ih264d_signal_bs_deblk_thread(dec_struct_t *ps_dec) +{ + if(ps_dec->u4_bs_deblk_thread_created) + { + ithread_join(ps_dec->pv_bs_deblk_thread_handle, NULL); + ps_dec->u4_bs_deblk_thread_created = 0; + } + +} diff --git a/dependencies/ih264d/decoder/ih264d_thread_parse_decode.h b/dependencies/ih264d/decoder/ih264d_thread_parse_decode.h new file mode 100644 index 00000000..5c2c7623 --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_thread_parse_decode.h @@ -0,0 +1,48 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/* + * ih264d_thread_parse_decode.h + * + * Created on: Feb 21, 2012 + * Author: 100492 + */ + +#ifndef _IH264D_THREAD_PARSE_DECPDE_H_ +#define _IH264D_THREAD_PARSE_DECPDE_H_ +void ih264d_parse_tfr_nmb(dec_struct_t *ps_dec, + UWORD8 u1_mb_idx, + UWORD8 u1_num_mbs, + UWORD8 u1_num_mbs_next, + UWORD8 u1_tfr_n_mb, + UWORD8 u1_end_of_row); +void ih264d_decode_tfr_nmb(dec_struct_t *ps_dec, + UWORD8 u1_num_mbs, + UWORD8 u1_num_mbs_next, + UWORD8 u1_end_of_row); +WORD32 ih264d_decode_recon_tfr_nmb_thread(dec_struct_t * ps_dec, + UWORD8 u1_num_mbs, + UWORD8 u1_num_mbs_next, + UWORD8 u1_end_of_row); +void ih264d_decode_picture_thread(dec_struct_t *ps_dec); +WORD32 ih264d_decode_slice_thread(dec_struct_t *ps_dec); + + + +#endif /* _IH264D_THREAD_PARSE_DECPDE_H_ */ diff --git a/dependencies/ih264d/decoder/ih264d_transfer_address.h b/dependencies/ih264d/decoder/ih264d_transfer_address.h new file mode 100644 index 00000000..aa64b854 --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_transfer_address.h @@ -0,0 +1,45 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +#ifndef _IH264D_TRANSFER_ADDRESS_H_ +#define _IH264D_TRANSFER_ADDRESS_H_ + +typedef struct +{ + UWORD8 *pu1_src_y; + UWORD8 *pu1_src_u; + UWORD8 *pu1_src_v; + UWORD8 *pu1_dest_y; + UWORD8 *pu1_dest_u; + UWORD8 *pu1_dest_v; + UWORD32 u4_inc_y[2]; + UWORD32 u4_inc_uv[2]; + UWORD16 u2_frm_wd_y; + UWORD16 u2_frm_wd_uv; + UWORD8 *pu1_mb_y; + UWORD8 *pu1_mb_u; + UWORD8 *pu1_mb_v; + UWORD16 u2_mv_left_inc; + UWORD16 u2_mv_top_left_inc; + UWORD32 u4_y_inc; + UWORD32 u4_uv_inc; + +} tfr_ctxt_t; + +#endif diff --git a/dependencies/ih264d/decoder/ih264d_utils.c b/dependencies/ih264d/decoder/ih264d_utils.c new file mode 100644 index 00000000..93c379b3 --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_utils.c @@ -0,0 +1,2420 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/*! + ************************************************************************** + * \file ih264d_utils.c + * + * \brief + * Contains routines that handle of start and end of pic processing + * + * \date + * 19/12/2002 + * + * \author AI + ************************************************************************** + */ + +#include <string.h> +#include "ih264_typedefs.h" +#include "ithread.h" +#include "ih264d_deblocking.h" +#include "ih264d_parse_slice.h" +#include "ih264d_parse_cavlc.h" +#include "ih264d_dpb_manager.h" +#include "ih264d_defs.h" +#include "ih264d_structs.h" +#include "ih264d_mem_request.h" +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264d_tables.h" +#include "ih264d_debug.h" +#include "ih264d_mb_utils.h" +#include "ih264d_error_handler.h" +#include "ih264d_dpb_manager.h" +#include "ih264d_utils.h" +#include "ih264d_defs.h" +#include "ih264d_tables.h" +#include "ih264d_inter_pred.h" +#include "ih264d_dpb_manager.h" +#include "iv.h" +#include "ivd.h" +#include "ih264d_format_conv.h" +#include "ih264_error.h" +#include "ih264_disp_mgr.h" +#include "ih264_buf_mgr.h" +#include "ih264d_utils.h" + +/*! + ************************************************************************** + * \if Function name : ih264d_is_end_of_pic \endif + * + * \brief + * Determines whether current slice is first slice of a new picture as + * defined in 7.4.1.2.4 of 14496-10. + * + * \return + * Return 1 if current slice is first slice of a new picture + * Otherwise it returns 0 + ************************************************************************** + */ +UWORD8 ih264d_is_end_of_pic(UWORD16 u2_frame_num, + UWORD8 u1_nal_ref_idc, + pocstruct_t *ps_cur_poc, + pocstruct_t *ps_prev_poc, + dec_slice_params_t * ps_prev_slice, /*!< Previous slice parameters*/ + UWORD8 u1_pic_order_cnt_type, + UWORD8 u1_nal_unit_type, + UWORD32 u4_idr_pic_id, + UWORD8 u1_field_pic_flag, + UWORD8 u1_bottom_field_flag) +{ + WORD8 i1_is_end_of_pic; + WORD8 a, b, c, d, e, f, g, h; + + a = b = c = d = e = f = g = h = 0; + a = (ps_prev_slice->u2_frame_num != u2_frame_num); + b = (ps_prev_slice->u1_field_pic_flag != u1_field_pic_flag); + if(u1_field_pic_flag && ps_prev_slice->u1_field_pic_flag) + c = (u1_bottom_field_flag != ps_prev_slice->u1_bottom_field_flag); + d = + (u1_nal_ref_idc == 0 && ps_prev_slice->u1_nal_ref_idc != 0) + || (u1_nal_ref_idc != 0 + && ps_prev_slice->u1_nal_ref_idc + == 0); + if(!a) + { + if((u1_pic_order_cnt_type == 0) + && (ps_prev_slice->u1_pic_order_cnt_type == 0)) + { + e = + ((ps_cur_poc->i4_pic_order_cnt_lsb + != ps_prev_poc->i4_pic_order_cnt_lsb) + || (ps_cur_poc->i4_delta_pic_order_cnt_bottom + != ps_prev_poc->i4_delta_pic_order_cnt_bottom)); + } + + if((u1_pic_order_cnt_type == 1) + && (ps_prev_slice->u1_pic_order_cnt_type == 1)) + { + f = + ((ps_cur_poc->i4_delta_pic_order_cnt[0] + != ps_prev_poc->i4_delta_pic_order_cnt[0]) + || (ps_cur_poc->i4_delta_pic_order_cnt[1] + != ps_prev_poc->i4_delta_pic_order_cnt[1])); + } + } + + if((u1_nal_unit_type == IDR_SLICE_NAL) + && (ps_prev_slice->u1_nal_unit_type == IDR_SLICE_NAL)) + { + g = (u4_idr_pic_id != ps_prev_slice->u4_idr_pic_id); + } + + if((u1_nal_unit_type == IDR_SLICE_NAL) + && (ps_prev_slice->u1_nal_unit_type != IDR_SLICE_NAL)) + { + h = 1; + } + i1_is_end_of_pic = a + b + c + d + e + f + g + h; + return (i1_is_end_of_pic); +} + +/*! + ************************************************************************** + * \if Function name : ih264d_decode_pic_order_cnt \endif + * + * \brief + * Calculates picture order count of picture. + * + * \return + * Returns the pic order count of the picture to which current + * Slice belongs. + * + ************************************************************************** + */ +WORD32 ih264d_decode_pic_order_cnt(UWORD8 u1_is_idr_slice, + UWORD32 u2_frame_num, + pocstruct_t *ps_prev_poc, + pocstruct_t *ps_cur_poc, + dec_slice_params_t *ps_cur_slice, /*!< Pointer to current slice Params*/ + dec_pic_params_t * ps_pps, + UWORD8 u1_nal_ref_idc, + UWORD8 u1_bottom_field_flag, + UWORD8 u1_field_pic_flag, + WORD32 *pi4_poc) +{ + WORD64 i8_pic_msb; + WORD32 i4_top_field_order_cnt = 0, i4_bottom_field_order_cnt = 0; + dec_seq_params_t *ps_seq = ps_pps->ps_sps; + WORD32 i4_prev_frame_num_ofst; + + switch(ps_seq->u1_pic_order_cnt_type) + { + case 0: + /* POC TYPE 0 */ + if(u1_is_idr_slice) + { + ps_prev_poc->i4_pic_order_cnt_msb = 0; + ps_prev_poc->i4_pic_order_cnt_lsb = 0; + } + if(ps_prev_poc->u1_mmco_equalto5) + { + if(ps_prev_poc->u1_bot_field != 1) + { + ps_prev_poc->i4_pic_order_cnt_msb = 0; + ps_prev_poc->i4_pic_order_cnt_lsb = + ps_prev_poc->i4_top_field_order_count; + } + else + { + ps_prev_poc->i4_pic_order_cnt_msb = 0; + ps_prev_poc->i4_pic_order_cnt_lsb = 0; + } + } + + if((ps_cur_poc->i4_pic_order_cnt_lsb + < ps_prev_poc->i4_pic_order_cnt_lsb) + && ((ps_prev_poc->i4_pic_order_cnt_lsb + - ps_cur_poc->i4_pic_order_cnt_lsb) + >= (ps_seq->i4_max_pic_order_cntLsb + >> 1))) + { + i8_pic_msb = (WORD64)ps_prev_poc->i4_pic_order_cnt_msb + + ps_seq->i4_max_pic_order_cntLsb; + } + else if((ps_cur_poc->i4_pic_order_cnt_lsb + > ps_prev_poc->i4_pic_order_cnt_lsb) + && ((ps_cur_poc->i4_pic_order_cnt_lsb + - ps_prev_poc->i4_pic_order_cnt_lsb) + >= (ps_seq->i4_max_pic_order_cntLsb + >> 1))) + { + i8_pic_msb = (WORD64)ps_prev_poc->i4_pic_order_cnt_msb + - ps_seq->i4_max_pic_order_cntLsb; + } + else + { + i8_pic_msb = ps_prev_poc->i4_pic_order_cnt_msb; + } + + if(!u1_field_pic_flag || !u1_bottom_field_flag) + { + WORD64 i8_result = i8_pic_msb + ps_cur_poc->i4_pic_order_cnt_lsb; + if(IS_OUT_OF_RANGE_S32(i8_result)) + { + return ERROR_INV_POC; + } + i4_top_field_order_cnt = i8_result; + } + + if(!u1_field_pic_flag) + { + WORD64 i8_result = (WORD64)i4_top_field_order_cnt + + ps_cur_poc->i4_delta_pic_order_cnt_bottom; + if(IS_OUT_OF_RANGE_S32(i8_result)) + { + return ERROR_INV_POC; + } + i4_bottom_field_order_cnt = i8_result; + } + else if(u1_bottom_field_flag) + { + WORD64 i8_result = i8_pic_msb + ps_cur_poc->i4_pic_order_cnt_lsb; + if(IS_OUT_OF_RANGE_S32(i8_result)) + { + return ERROR_INV_POC; + } + i4_bottom_field_order_cnt = i8_result; + } + + if(IS_OUT_OF_RANGE_S32(i8_pic_msb)) + { + return ERROR_INV_POC; + } + ps_cur_poc->i4_pic_order_cnt_msb = i8_pic_msb; + break; + + case 1: + { + /* POC TYPE 1 */ + UWORD8 i; + WORD32 prev_frame_num; + WORD32 frame_num_ofst; + WORD32 abs_frm_num; + WORD32 poc_cycle_cnt, frame_num_in_poc_cycle; + WORD64 i8_expected_delta_poc_cycle; + WORD32 expected_poc; + WORD64 i8_result; + + prev_frame_num = (WORD32)ps_cur_slice->u2_frame_num; + if(!u1_is_idr_slice) + { + if(ps_cur_slice->u1_mmco_equalto5) + { + prev_frame_num = 0; + i4_prev_frame_num_ofst = 0; + } + else + { + i4_prev_frame_num_ofst = ps_prev_poc->i4_prev_frame_num_ofst; + } + } + else + i4_prev_frame_num_ofst = 0; + + /* 1. Derivation for FrameNumOffset */ + if(u1_is_idr_slice) + { + frame_num_ofst = 0; + ps_cur_poc->i4_delta_pic_order_cnt[0] = 0; + ps_cur_poc->i4_delta_pic_order_cnt[1] = 0; + } + else if(prev_frame_num > ((WORD32)u2_frame_num)) + { + WORD64 i8_result = i4_prev_frame_num_ofst + + (WORD64)ps_seq->u2_u4_max_pic_num_minus1 + 1; + if(IS_OUT_OF_RANGE_S32(i8_result)) + { + return ERROR_INV_FRAME_NUM; + } + frame_num_ofst = i8_result; + } + else + frame_num_ofst = i4_prev_frame_num_ofst; + + /* 2. Derivation for absFrameNum */ + if(0 != ps_seq->u1_num_ref_frames_in_pic_order_cnt_cycle) + { + WORD64 i8_result = frame_num_ofst + (WORD64)u2_frame_num; + if(IS_OUT_OF_RANGE_S32(i8_result)) + { + return ERROR_INV_FRAME_NUM; + } + abs_frm_num = i8_result; + } + else + abs_frm_num = 0; + if((u1_nal_ref_idc == 0) && (abs_frm_num > 0)) + abs_frm_num = abs_frm_num - 1; + + /* 4. expectedDeltaPerPicOrderCntCycle is derived as */ + i8_expected_delta_poc_cycle = 0; + for(i = 0; i < ps_seq->u1_num_ref_frames_in_pic_order_cnt_cycle; + i++) + { + i8_expected_delta_poc_cycle += + ps_seq->i4_ofst_for_ref_frame[i]; + } + + /* 3. When absFrameNum > 0, picOrderCntCycleCnt and + frame_num_in_poc_cycle are derived as : */ + /* 5. expectedPicOrderCnt is derived as : */ + if(abs_frm_num > 0) + { + poc_cycle_cnt = + DIV((abs_frm_num - 1), + ps_seq->u1_num_ref_frames_in_pic_order_cnt_cycle); + frame_num_in_poc_cycle = + MOD((abs_frm_num - 1), + ps_seq->u1_num_ref_frames_in_pic_order_cnt_cycle); + + i8_result = poc_cycle_cnt + * i8_expected_delta_poc_cycle; + + for(i = 0; i <= frame_num_in_poc_cycle; i++) + { + i8_result = i8_result + + ps_seq->i4_ofst_for_ref_frame[i]; + } + + if(IS_OUT_OF_RANGE_S32(i8_result)) + return ERROR_INV_POC; + + expected_poc = (WORD32)i8_result; + + } + else + expected_poc = 0; + + if(u1_nal_ref_idc == 0) + { + i8_result = (WORD64)expected_poc + + ps_seq->i4_ofst_for_non_ref_pic; + + if(IS_OUT_OF_RANGE_S32(i8_result)) + return ERROR_INV_POC; + + expected_poc = (WORD32)i8_result; + } + + /* 6. TopFieldOrderCnt or BottomFieldOrderCnt are derived as */ + if(!u1_field_pic_flag) + { + i8_result = (WORD64)expected_poc + + ps_cur_poc->i4_delta_pic_order_cnt[0]; + + if(IS_OUT_OF_RANGE_S32(i8_result)) + return ERROR_INV_POC; + i4_top_field_order_cnt = (WORD32)i8_result; + + i8_result = (WORD64)i4_top_field_order_cnt + + ps_seq->i4_ofst_for_top_to_bottom_field + + ps_cur_poc->i4_delta_pic_order_cnt[1]; + + if(IS_OUT_OF_RANGE_S32(i8_result)) + return ERROR_INV_POC; + i4_bottom_field_order_cnt = (WORD32)i8_result; + } + else if(!u1_bottom_field_flag) + { + i8_result = (WORD64)expected_poc + + ps_cur_poc->i4_delta_pic_order_cnt[0]; + + if(IS_OUT_OF_RANGE_S32(i8_result)) + return ERROR_INV_POC; + i4_top_field_order_cnt = (WORD32)i8_result; + } + else + { + i8_result = (WORD64)expected_poc + + ps_seq->i4_ofst_for_top_to_bottom_field + + ps_cur_poc->i4_delta_pic_order_cnt[0]; + + if(IS_OUT_OF_RANGE_S32(i8_result)) + return ERROR_INV_POC; + i4_bottom_field_order_cnt = (WORD32)i8_result; + } + /* Copy the current POC info into Previous POC structure */ + ps_cur_poc->i4_prev_frame_num_ofst = frame_num_ofst; + } + + break; + case 2: + { + /* POC TYPE 2 */ + WORD32 prev_frame_num; + WORD32 frame_num_ofst; + WORD32 tmp_poc; + + prev_frame_num = (WORD32)ps_cur_slice->u2_frame_num; + if(!u1_is_idr_slice) + { + if(ps_cur_slice->u1_mmco_equalto5) + { + prev_frame_num = 0; + i4_prev_frame_num_ofst = 0; + } + else + i4_prev_frame_num_ofst = ps_prev_poc->i4_prev_frame_num_ofst; + } + else + i4_prev_frame_num_ofst = 0; + + /* 1. Derivation for FrameNumOffset */ + if(u1_is_idr_slice) + { + frame_num_ofst = 0; + ps_cur_poc->i4_delta_pic_order_cnt[0] = 0; + ps_cur_poc->i4_delta_pic_order_cnt[1] = 0; + } + else if(prev_frame_num > ((WORD32)u2_frame_num)) + { + WORD64 i8_result = i4_prev_frame_num_ofst + + (WORD64)ps_seq->u2_u4_max_pic_num_minus1 + 1; + if(IS_OUT_OF_RANGE_S32(i8_result)) + { + return ERROR_INV_FRAME_NUM; + } + frame_num_ofst = i8_result; + } + else + frame_num_ofst = i4_prev_frame_num_ofst; + + /* 2. Derivation for tempPicOrderCnt */ + if(u1_is_idr_slice) + tmp_poc = 0; + else if(u1_nal_ref_idc == 0) + { + WORD64 i8_result = ((frame_num_ofst + (WORD64)u2_frame_num) << 1) - 1; + if(IS_OUT_OF_RANGE_S32(i8_result)) + { + return ERROR_INV_POC; + } + tmp_poc = i8_result; + } + else + { + WORD64 i8_result = (frame_num_ofst + (WORD64)u2_frame_num) << 1; + if(IS_OUT_OF_RANGE_S32(i8_result)) + { + return ERROR_INV_POC; + } + tmp_poc = i8_result; + } + + /* 6. TopFieldOrderCnt or BottomFieldOrderCnt are derived as */ + if(!u1_field_pic_flag) + { + i4_top_field_order_cnt = tmp_poc; + i4_bottom_field_order_cnt = tmp_poc; + } + else if(!u1_bottom_field_flag) + i4_top_field_order_cnt = tmp_poc; + else + i4_bottom_field_order_cnt = tmp_poc; + + /* Copy the current POC info into Previous POC structure */ + ps_prev_poc->i4_prev_frame_num_ofst = frame_num_ofst; + ps_cur_poc->i4_prev_frame_num_ofst = frame_num_ofst; + } + break; + default: + return ERROR_INV_POC_TYPE_T; + break; + } + + if(!u1_field_pic_flag) // or a complementary field pair + { + *pi4_poc = MIN(i4_top_field_order_cnt, i4_bottom_field_order_cnt); + ps_pps->i4_top_field_order_cnt = i4_top_field_order_cnt; + ps_pps->i4_bottom_field_order_cnt = i4_bottom_field_order_cnt; + } + else if(!u1_bottom_field_flag) + { + *pi4_poc = i4_top_field_order_cnt; + ps_pps->i4_top_field_order_cnt = i4_top_field_order_cnt; + } + else + { + *pi4_poc = i4_bottom_field_order_cnt; + ps_pps->i4_bottom_field_order_cnt = i4_bottom_field_order_cnt; + } + + ps_pps->i4_avg_poc = *pi4_poc; + + return OK; +} + +/*! + ************************************************************************** + * \if Function name : ih264d_end_of_pic_processing \endif + * + * \brief + * Performs the end of picture processing. + * + * It performs deblocking on the current picture and sets the i4_status of + * current picture as decoded. + * + * \return + * 0 on Success and Error code otherwise. + ************************************************************************** + */ +WORD32 ih264d_end_of_pic_processing(dec_struct_t *ps_dec) +{ + UWORD8 u1_pic_type, u1_nal_ref_idc; + dec_slice_params_t *ps_cur_slice = ps_dec->ps_cur_slice; + + /* If nal_ref_idc is equal to 0 for one slice or slice data partition NAL + unit of a particular picture, it shall be equal to 0 for all slice and + slice data partition NAL units of the picture. nal_ref_idc greater + than 0 indicates that the content of the NAL unit belongs to a decoded + picture that is stored and marked for use as a reference picture in the + decoded picture buffer. */ + + /* 1. Do MMCO + 2. Add Cur Pic to list of reference pics. + */ + + /* Call MMCO */ + u1_pic_type = 0; + u1_nal_ref_idc = ps_cur_slice->u1_nal_ref_idc; + + if(u1_nal_ref_idc) + { + if(ps_cur_slice->u1_nal_unit_type == IDR_SLICE_NAL) + { + ps_dec->ps_dpb_mgr->u1_mmco_error_in_seq = 0; + if(ps_dec->ps_dpb_cmds->u1_long_term_reference_flag == 0) + { + ih264d_reset_ref_bufs(ps_dec->ps_dpb_mgr); + /* ignore DPB errors */ + ih264d_insert_st_node(ps_dec->ps_dpb_mgr, + ps_dec->ps_cur_pic, + ps_dec->u1_pic_buf_id, + ps_cur_slice->u2_frame_num); + ps_dec->ps_dpb_mgr->u1_max_lt_frame_idx = NO_LONG_TERM_INDICIES; + } + else + { + /* Equivalent of inserting a pic directly as longterm Pic */ + + { + /* ignore DPB errors */ + ih264d_insert_st_node(ps_dec->ps_dpb_mgr, + ps_dec->ps_cur_pic, + ps_dec->u1_pic_buf_id, + ps_cur_slice->u2_frame_num); + + /* Set longTermIdx = 0, MaxLongTermFrameIdx = 0 */ + ih264d_delete_st_node_or_make_lt( + ps_dec->ps_dpb_mgr, + ps_cur_slice->u2_frame_num, 0, + ps_cur_slice->u1_field_pic_flag); + + ps_dec->ps_dpb_mgr->u1_max_lt_frame_idx = 0; + } + } + } + else + { + + { + UWORD16 u2_pic_num = ps_cur_slice->u2_frame_num; + + if(!ps_dec->ps_dpb_mgr->u1_mmco_error_in_seq) + { + WORD32 ret = ih264d_do_mmco_buffer(ps_dec->ps_dpb_cmds, ps_dec->ps_dpb_mgr, + ps_dec->ps_cur_sps->u1_num_ref_frames, u2_pic_num, + (ps_dec->ps_cur_sps->u2_u4_max_pic_num_minus1), + ps_dec->u1_nal_unit_type, ps_dec->ps_cur_pic, + ps_dec->u1_pic_buf_id, + ps_cur_slice->u1_field_pic_flag, + ps_dec->e_dec_status); + ps_dec->ps_dpb_mgr->u1_mmco_error_in_seq = ret != OK; + } + } + } + ih264d_update_default_index_list(ps_dec->ps_dpb_mgr); + } + + if(ps_cur_slice->u1_field_pic_flag) + { + if(ps_cur_slice->u1_bottom_field_flag) + { + if(u1_nal_ref_idc) + u1_pic_type = u1_pic_type | BOT_REF; + u1_pic_type = u1_pic_type | BOT_FLD; + } + else + { + if(u1_nal_ref_idc) + u1_pic_type = u1_pic_type | TOP_REF; + u1_pic_type = u1_pic_type | TOP_FLD; + } + } + else + u1_pic_type = TOP_REF | BOT_REF; + ps_dec->ps_cur_pic->u1_pic_type |= u1_pic_type; + + + if(ps_cur_slice->u1_field_pic_flag) + { + H264_DEC_DEBUG_PRINT("Toggling secondField\n"); + ps_dec->u1_second_field = 1 - ps_dec->u1_second_field; + } + + return OK; +} + +/*****************************************************************************/ +/* */ +/* Function Name : init_dpb_size */ +/* */ +/* Description : This function calculates the DBP i4_size in frames */ +/* Inputs : ps_seq - current sequence params */ +/* */ +/* Globals : None */ +/* */ +/* Outputs : None */ +/* */ +/* Returns : DPB in frames */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 28 04 2005 NS Draft */ +/* */ +/*****************************************************************************/ +WORD32 ih264d_get_dpb_size(dec_seq_params_t *ps_seq) +{ + WORD32 i4_size; + UWORD8 u1_level_idc; + + u1_level_idc = ps_seq->u1_level_idc; + + switch(u1_level_idc) + { + case 10: + i4_size = 152064; + break; + case 11: + i4_size = 345600; + break; + case 12: + i4_size = 912384; + break; + case 13: + i4_size = 912384; + break; + case 20: + i4_size = 912384; + break; + case 21: + i4_size = 1824768; + break; + case 22: + i4_size = 3110400; + break; + case 30: + i4_size = 3110400; + break; + case 31: + i4_size = 6912000; + break; + case 32: + i4_size = 7864320; + break; + case 40: + i4_size = 12582912; + break; + case 41: + i4_size = 12582912; + break; + case 42: + i4_size = 12582912; + break; + case 50: + i4_size = 42393600; + break; + case 51: + i4_size = 70778880; + break; + case 52: + i4_size = 70778880; + break; + default: + i4_size = 70778880; + break; + } + + i4_size /= (ps_seq->u2_frm_wd_in_mbs * (ps_seq->u2_frm_ht_in_mbs << (1 - ps_seq->u1_frame_mbs_only_flag))); + i4_size /= 384; + i4_size = MIN(i4_size, 16); + i4_size = MAX(i4_size, 1); + return (i4_size); +} + +/**************************************************************************/ +/* This function initialises the value of ps_dec->u1_recon_mb_grp */ +/* ps_dec->u1_recon_mb_grp must satisfy the following criteria */ +/* - multiple of 2 (required for N/2 parse-mvpred design) */ +/* - multiple of 4 (if it is not a frame_mbs_only sequence), */ +/* in this case N/2 itself needs to be even for mbpair processing */ +/* - lesser than ps_dec->u2_frm_wd_in_mbs/2 (at least 3 N-Chunks */ +/* should make a row to ensure proper MvTop transferring) */ +/**************************************************************************/ +WORD32 ih264d_init_dec_mb_grp(dec_struct_t *ps_dec) +{ + dec_seq_params_t *ps_seq = ps_dec->ps_cur_sps; + UWORD8 u1_frm = ps_seq->u1_frame_mbs_only_flag; + + ps_dec->u1_recon_mb_grp = ps_dec->u2_frm_wd_in_mbs << ps_seq->u1_mb_aff_flag; + + ps_dec->u1_recon_mb_grp_pair = ps_dec->u1_recon_mb_grp >> 1; + + if(!ps_dec->u1_recon_mb_grp) + { + return ERROR_MB_GROUP_ASSGN_T; + } + + ps_dec->u4_num_mbs_prev_nmb = ps_dec->u1_recon_mb_grp; + + return OK; +} + + +/*! + ************************************************************************** + * \if Function name : ih264d_init_pic \endif + * + * \brief + * Initializes the picture. + * + * \return + * 0 on Success and Error code otherwise + * + * \note + * This function is called when first slice of the + * NON -IDR picture is encountered. + ************************************************************************** + */ +WORD32 ih264d_init_pic(dec_struct_t *ps_dec, + UWORD16 u2_frame_num, + WORD32 i4_poc, + dec_pic_params_t *ps_pps) +{ + dec_seq_params_t *ps_seq = ps_pps->ps_sps; + prev_seq_params_t * ps_prev_seq_params = &ps_dec->s_prev_seq_params; + WORD32 i4_pic_bufs; + WORD32 ret; + + ps_dec->ps_cur_slice->u2_frame_num = u2_frame_num; + ps_dec->ps_cur_slice->i4_poc = i4_poc; + ps_dec->ps_cur_pps = ps_pps; + ps_dec->ps_cur_pps->pv_codec_handle = ps_dec; + + ps_dec->ps_cur_sps = ps_seq; + ps_dec->ps_dpb_mgr->i4_max_frm_num = ps_seq->u2_u4_max_pic_num_minus1 + + 1; + + ps_dec->ps_dpb_mgr->u2_pic_ht = ps_dec->u2_pic_ht; + ps_dec->ps_dpb_mgr->u2_pic_wd = ps_dec->u2_pic_wd; + ps_dec->i4_pic_type = NA_SLICE; + ps_dec->i4_frametype = IV_NA_FRAME; + ps_dec->i4_content_type = IV_CONTENTTYPE_NA; + + /*--------------------------------------------------------------------*/ + /* Get the value of MaxMbAddress and frmheight in Mbs */ + /*--------------------------------------------------------------------*/ + ps_seq->u2_max_mb_addr = + (ps_seq->u2_frm_wd_in_mbs + * (ps_dec->u2_pic_ht + >> (4 + + ps_dec->ps_cur_slice->u1_field_pic_flag))) + - 1; + ps_dec->u2_frm_ht_in_mbs = (ps_dec->u2_pic_ht + >> (4 + ps_dec->ps_cur_slice->u1_field_pic_flag)); + + /***************************************************************************/ + /* If change in Level or the required PicBuffers i4_size is more than the */ + /* current one FREE the current PicBuffers and allocate affresh */ + /***************************************************************************/ + if(!ps_dec->u1_init_dec_flag) + { + ps_dec->u1_max_dec_frame_buffering = ih264d_get_dpb_size(ps_seq); + + ps_dec->i4_display_delay = ps_dec->u1_max_dec_frame_buffering; + if((1 == ps_seq->u1_vui_parameters_present_flag) && + (1 == ps_seq->s_vui.u1_bitstream_restriction_flag)) + { + if(ps_seq->u1_frame_mbs_only_flag == 1) + ps_dec->i4_display_delay = ps_seq->s_vui.u4_num_reorder_frames + 1; + else + ps_dec->i4_display_delay = ps_seq->s_vui.u4_num_reorder_frames * 2 + 2; + } + + if(IVD_DECODE_FRAME_OUT == ps_dec->e_frm_out_mode) + ps_dec->i4_display_delay = 0; + + if(ps_dec->u4_share_disp_buf == 0) + { + if(ps_seq->u1_frame_mbs_only_flag == 1) + ps_dec->u1_pic_bufs = ps_dec->i4_display_delay + ps_seq->u1_num_ref_frames + 1; + else + ps_dec->u1_pic_bufs = ps_dec->i4_display_delay + ps_seq->u1_num_ref_frames * 2 + 2; + } + else + { + ps_dec->u1_pic_bufs = (WORD32)ps_dec->u4_num_disp_bufs; + } + + /* Ensure at least two buffers are allocated */ + ps_dec->u1_pic_bufs = MAX(ps_dec->u1_pic_bufs, 2); + + if(ps_dec->u4_share_disp_buf == 0) + ps_dec->u1_pic_bufs = MIN(ps_dec->u1_pic_bufs, + (H264_MAX_REF_PICS * 2)); + + ps_dec->u1_max_dec_frame_buffering = MIN( + ps_dec->u1_max_dec_frame_buffering, + ps_dec->u1_pic_bufs); + + /* Temporary hack to run Tractor Cav/Cab/MbAff Profiler streams also for CAFI1_SVA_C.264 in conformance*/ + if(ps_dec->u1_init_dec_flag) + { + ih264d_release_pics_in_dpb((void *)ps_dec, + ps_dec->u1_pic_bufs); + ih264d_release_display_bufs(ps_dec); + ih264d_reset_ref_bufs(ps_dec->ps_dpb_mgr); + } + + /*********************************************************************/ + /* Configuring decoder parameters based on level and then */ + /* fresh pointer initialisation in decoder scratch and state buffers */ + /*********************************************************************/ + if(!ps_dec->u1_init_dec_flag || + ((ps_seq->u1_level_idc < H264_LEVEL_3_0) ^ (ps_prev_seq_params->u1_level_idc < H264_LEVEL_3_0))) + { + ret = ih264d_init_dec_mb_grp(ps_dec); + if(ret != OK) + return ret; + } + + ret = ih264d_allocate_dynamic_bufs(ps_dec); + if(ret != OK) + { + /* Free any dynamic buffers that are allocated */ + ih264d_free_dynamic_bufs(ps_dec); + ps_dec->i4_error_code = IVD_MEM_ALLOC_FAILED; + return IVD_MEM_ALLOC_FAILED; + } + + ret = ih264d_create_pic_buffers(ps_dec->u1_pic_bufs, + ps_dec); + if(ret != OK) + return ret; + + + + ret = ih264d_create_mv_bank(ps_dec, ps_dec->u2_pic_wd, + ps_dec->u2_pic_ht); + if(ret != OK) + return ret; + + /* In shared mode, set all of them as used by display */ + if(ps_dec->u4_share_disp_buf == 1) + { + WORD32 i; + + for(i = 0; i < ps_dec->u1_pic_bufs; i++) + { + ih264_buf_mgr_set_status((buf_mgr_t *)ps_dec->pv_pic_buf_mgr, i, + BUF_MGR_IO); + } + } + + ps_dec->u1_init_dec_flag = 1; + ps_prev_seq_params->u2_frm_wd_in_mbs = ps_seq->u2_frm_wd_in_mbs; + ps_prev_seq_params->u1_level_idc = ps_seq->u1_level_idc; + ps_prev_seq_params->u1_profile_idc = ps_seq->u1_profile_idc; + ps_prev_seq_params->u2_frm_ht_in_mbs = ps_seq->u2_frm_ht_in_mbs; + ps_prev_seq_params->u1_frame_mbs_only_flag = + ps_seq->u1_frame_mbs_only_flag; + ps_prev_seq_params->u1_direct_8x8_inference_flag = + ps_seq->u1_direct_8x8_inference_flag; + + ps_dec->i4_cur_display_seq = 0; + ps_dec->i4_prev_max_display_seq = 0; + ps_dec->i4_max_poc = 0; + + { + /* 0th entry of CtxtIncMbMap will be always be containing default values + for CABAC context representing MB not available */ + ctxt_inc_mb_info_t *p_DefCtxt = ps_dec->p_ctxt_inc_mb_map - 1; + UWORD8 *pu1_temp; + WORD8 i; + p_DefCtxt->u1_mb_type = CAB_SKIP; + + p_DefCtxt->u1_cbp = 0x0f; + p_DefCtxt->u1_intra_chroma_pred_mode = 0; + + p_DefCtxt->u1_yuv_dc_csbp = 0x7; + + p_DefCtxt->u1_transform8x8_ctxt = 0; + + pu1_temp = (UWORD8*)p_DefCtxt->i1_ref_idx; + for(i = 0; i < 4; i++, pu1_temp++) + (*pu1_temp) = 0; + pu1_temp = (UWORD8*)p_DefCtxt->u1_mv; + for(i = 0; i < 16; i++, pu1_temp++) + (*pu1_temp) = 0; + ps_dec->ps_def_ctxt_mb_info = p_DefCtxt; + } + + } + /* reset DBP commands read u4_flag */ + ps_dec->ps_dpb_cmds->u1_dpb_commands_read = 0; + + return OK; +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_get_next_display_field */ +/* */ +/* Description : Application calls this module to get the next field */ +/* to be displayed */ +/* */ +/* Inputs : 1. IBUFAPI_Handle Hnadle to the Display buffer */ +/* 2. IH264DEC_DispUnit Pointer to the display struct */ +/* */ +/* Globals : */ +/* */ +/* */ +/* Processing : None */ +/* Outputs : None */ +/* Returns : None */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 27 05 2005 Ittiam Draft */ +/* */ +/*****************************************************************************/ + +WORD32 ih264d_get_next_display_field(dec_struct_t * ps_dec, + ivd_out_bufdesc_t *ps_out_buffer, + ivd_get_display_frame_op_t *pv_disp_op) +{ + pic_buffer_t *pic_buf; + + UWORD8 i1_cur_fld; + WORD32 u4_api_ret = -1; + WORD32 i4_disp_buf_id; + iv_yuv_buf_t *ps_op_frm; + + + + ps_op_frm = &(ps_dec->s_disp_frame_info); + H264_MUTEX_LOCK(&ps_dec->process_disp_mutex); + pic_buf = (pic_buffer_t *)ih264_disp_mgr_get( + (disp_mgr_t *)ps_dec->pv_disp_buf_mgr, &i4_disp_buf_id); + ps_dec->u4_num_fld_in_frm = 0; + u4_api_ret = -1; + pv_disp_op->u4_ts = 0; + pv_disp_op->e_output_format = ps_dec->u1_chroma_format; + + pv_disp_op->s_disp_frm_buf.pv_y_buf = ps_out_buffer->pu1_bufs[0]; + pv_disp_op->s_disp_frm_buf.pv_u_buf = ps_out_buffer->pu1_bufs[1]; + pv_disp_op->s_disp_frm_buf.pv_v_buf = ps_out_buffer->pu1_bufs[2]; + ps_dec->i4_display_index = DEFAULT_POC; + if(pic_buf != NULL) + { + ps_dec->pv_disp_sei_params = &pic_buf->s_sei_pic; + pv_disp_op->e4_fld_type = 0; + pv_disp_op->u4_disp_buf_id = i4_disp_buf_id; + + ps_op_frm->u4_y_ht = pic_buf->u2_disp_height << 1; + ps_op_frm->u4_u_ht = ps_op_frm->u4_v_ht = ps_op_frm->u4_y_ht >> 1; + ps_op_frm->u4_y_wd = pic_buf->u2_disp_width; + + ps_op_frm->u4_u_wd = ps_op_frm->u4_v_wd = ps_op_frm->u4_y_wd >> 1; + + ps_op_frm->u4_y_strd = pic_buf->u2_frm_wd_y; + ps_op_frm->u4_u_strd = ps_op_frm->u4_v_strd = pic_buf->u2_frm_wd_uv; + + /* ! */ + pv_disp_op->u4_ts = pic_buf->u4_ts; + ps_dec->i4_display_index = pic_buf->i4_poc; + + /* set the start of the Y, U and V buffer pointer for display */ + ps_op_frm->pv_y_buf = pic_buf->pu1_buf1 + pic_buf->u2_crop_offset_y; + ps_op_frm->pv_u_buf = pic_buf->pu1_buf2 + pic_buf->u2_crop_offset_uv; + ps_op_frm->pv_v_buf = pic_buf->pu1_buf3 + pic_buf->u2_crop_offset_uv; + ps_dec->u4_num_fld_in_frm++; + ps_dec->u4_num_fld_in_frm++; + u4_api_ret = 0; + + if(pic_buf->u1_picturetype == 0) + pv_disp_op->u4_progressive_frame_flag = 1; + else + pv_disp_op->u4_progressive_frame_flag = 0; + + } H264_MUTEX_UNLOCK(&ps_dec->process_disp_mutex); + pv_disp_op->u4_error_code = u4_api_ret; + pv_disp_op->e_pic_type = 0xFFFFFFFF; //Junk; + + if(u4_api_ret) + { + pv_disp_op->u4_error_code = 1; //put a proper error code here + } + else + { + + //Release the buffer if being sent for display + UWORD32 temp; + UWORD32 dest_inc_Y = 0, dest_inc_UV = 0; + + pv_disp_op->s_disp_frm_buf.u4_y_wd = temp = MIN(ps_op_frm->u4_y_wd, + ps_op_frm->u4_y_strd); + pv_disp_op->s_disp_frm_buf.u4_u_wd = pv_disp_op->s_disp_frm_buf.u4_y_wd + >> 1; + pv_disp_op->s_disp_frm_buf.u4_v_wd = pv_disp_op->s_disp_frm_buf.u4_y_wd + >> 1; + + pv_disp_op->s_disp_frm_buf.u4_y_ht = ps_op_frm->u4_y_ht; + pv_disp_op->s_disp_frm_buf.u4_u_ht = pv_disp_op->s_disp_frm_buf.u4_y_ht + >> 1; + pv_disp_op->s_disp_frm_buf.u4_v_ht = pv_disp_op->s_disp_frm_buf.u4_y_ht + >> 1; + if(0 == ps_dec->u4_share_disp_buf) + { + pv_disp_op->s_disp_frm_buf.u4_y_strd = + pv_disp_op->s_disp_frm_buf.u4_y_wd; + pv_disp_op->s_disp_frm_buf.u4_u_strd = + pv_disp_op->s_disp_frm_buf.u4_y_wd >> 1; + pv_disp_op->s_disp_frm_buf.u4_v_strd = + pv_disp_op->s_disp_frm_buf.u4_y_wd >> 1; + + } + else + { + pv_disp_op->s_disp_frm_buf.u4_y_strd = ps_op_frm->u4_y_strd; + } + + if(ps_dec->u4_app_disp_width) + { + pv_disp_op->s_disp_frm_buf.u4_y_strd = MAX( + ps_dec->u4_app_disp_width, + pv_disp_op->s_disp_frm_buf.u4_y_strd); + } + + pv_disp_op->u4_error_code = 0; + if(pv_disp_op->e_output_format == IV_YUV_420P) + { + UWORD32 i; + pv_disp_op->s_disp_frm_buf.u4_u_strd = + pv_disp_op->s_disp_frm_buf.u4_y_strd >> 1; + pv_disp_op->s_disp_frm_buf.u4_v_strd = + pv_disp_op->s_disp_frm_buf.u4_y_strd >> 1; + + pv_disp_op->s_disp_frm_buf.u4_u_wd = ps_op_frm->u4_y_wd >> 1; + pv_disp_op->s_disp_frm_buf.u4_v_wd = ps_op_frm->u4_y_wd >> 1; + + if(1 == ps_dec->u4_share_disp_buf) + { + pv_disp_op->s_disp_frm_buf.pv_y_buf = ps_op_frm->pv_y_buf; + + for(i = 0; i < MAX_DISP_BUFS_NEW; i++) + { + UWORD8 *buf = ps_dec->disp_bufs[i].buf[0]; + buf += ps_dec->disp_bufs[i].u4_ofst[0]; + if(((UWORD8 *)pv_disp_op->s_disp_frm_buf.pv_y_buf + - pic_buf->u2_crop_offset_y) == buf) + { + buf = ps_dec->disp_bufs[i].buf[1]; + buf += ps_dec->disp_bufs[i].u4_ofst[1]; + pv_disp_op->s_disp_frm_buf.pv_u_buf = buf + + (pic_buf->u2_crop_offset_uv + / YUV420SP_FACTOR); + + buf = ps_dec->disp_bufs[i].buf[2]; + buf += ps_dec->disp_bufs[i].u4_ofst[2]; + pv_disp_op->s_disp_frm_buf.pv_v_buf = buf + + (pic_buf->u2_crop_offset_uv + / YUV420SP_FACTOR); + + } + } + } + + } + else if((pv_disp_op->e_output_format == IV_YUV_420SP_UV) + || (pv_disp_op->e_output_format == IV_YUV_420SP_VU)) + { + pv_disp_op->s_disp_frm_buf.u4_u_strd = + pv_disp_op->s_disp_frm_buf.u4_y_strd; + pv_disp_op->s_disp_frm_buf.u4_v_strd = 0; + + if(1 == ps_dec->u4_share_disp_buf) + { + UWORD32 i; + + pv_disp_op->s_disp_frm_buf.pv_y_buf = ps_op_frm->pv_y_buf; + + for(i = 0; i < MAX_DISP_BUFS_NEW; i++) + { + UWORD8 *buf = ps_dec->disp_bufs[i].buf[0]; + buf += ps_dec->disp_bufs[i].u4_ofst[0]; + if((UWORD8 *)pv_disp_op->s_disp_frm_buf.pv_y_buf + - pic_buf->u2_crop_offset_y == buf) + { + buf = ps_dec->disp_bufs[i].buf[1]; + buf += ps_dec->disp_bufs[i].u4_ofst[1]; + pv_disp_op->s_disp_frm_buf.pv_u_buf = buf + + pic_buf->u2_crop_offset_uv; + ; + + buf = ps_dec->disp_bufs[i].buf[2]; + buf += ps_dec->disp_bufs[i].u4_ofst[2]; + pv_disp_op->s_disp_frm_buf.pv_v_buf = buf + + pic_buf->u2_crop_offset_uv; + ; + } + } + } + pv_disp_op->s_disp_frm_buf.u4_u_wd = + pv_disp_op->s_disp_frm_buf.u4_y_wd; + pv_disp_op->s_disp_frm_buf.u4_v_wd = 0; + + } + else if((pv_disp_op->e_output_format == IV_RGB_565) + || (pv_disp_op->e_output_format == IV_YUV_422ILE)) + { + + pv_disp_op->s_disp_frm_buf.u4_u_strd = 0; + pv_disp_op->s_disp_frm_buf.u4_v_strd = 0; + pv_disp_op->s_disp_frm_buf.u4_u_wd = 0; + pv_disp_op->s_disp_frm_buf.u4_v_wd = 0; + pv_disp_op->s_disp_frm_buf.u4_u_ht = 0; + pv_disp_op->s_disp_frm_buf.u4_v_ht = 0; + + } + + + } + + return u4_api_ret; +} + + +/*****************************************************************************/ +/* Function Name : ih264d_release_display_field */ +/* */ +/* Description : This function releases the display field that was returned */ +/* here. */ +/* Inputs : ps_dec - Decoder parameters */ +/* Globals : None */ +/* Processing : Refer bumping process in the standard */ +/* Outputs : Assigns display sequence number. */ +/* Returns : None */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 27 04 2005 NS Draft */ +/* */ +/*****************************************************************************/ +void ih264d_release_display_field(dec_struct_t *ps_dec, + ivd_get_display_frame_op_t *pv_disp_op) +{ + if(1 == pv_disp_op->u4_error_code) + { + if(1 == ps_dec->u1_flushfrm) + { + UWORD32 i; + + if(1 == ps_dec->u4_share_disp_buf) + { + H264_MUTEX_LOCK(&ps_dec->process_disp_mutex); + for(i = 0; i < (MAX_DISP_BUFS_NEW); i++) + { + if(1 == ps_dec->u4_disp_buf_mapping[i]) + { + ih264_buf_mgr_release( + (buf_mgr_t *)ps_dec->pv_pic_buf_mgr, i, + BUF_MGR_IO); + ps_dec->u4_disp_buf_mapping[i] = 0; + } + } H264_MUTEX_UNLOCK(&ps_dec->process_disp_mutex); + + memset(ps_dec->u4_disp_buf_to_be_freed, 0, + (MAX_DISP_BUFS_NEW) * sizeof(UWORD32)); + for(i = 0; i < ps_dec->u1_pic_bufs; i++) + ps_dec->u4_disp_buf_mapping[i] = 1; + } + ps_dec->u1_flushfrm = 0; + + } + } + else + { + H264_MUTEX_LOCK(&ps_dec->process_disp_mutex); + + if(0 == ps_dec->u4_share_disp_buf) + { + ih264_buf_mgr_release((buf_mgr_t *)ps_dec->pv_pic_buf_mgr, + pv_disp_op->u4_disp_buf_id, + BUF_MGR_IO); + + } + else + { + ps_dec->u4_disp_buf_mapping[pv_disp_op->u4_disp_buf_id] = 1; + } H264_MUTEX_UNLOCK(&ps_dec->process_disp_mutex); + + } +} +/*****************************************************************************/ +/* Function Name : ih264d_assign_display_seq */ +/* */ +/* Description : This function implments bumping process. Every outgoing */ +/* frame from DPB is assigned a display sequence number */ +/* which increases monotonically. System looks for this */ +/* number to display a frame. */ +/* here. */ +/* Inputs : ps_dec - Decoder parameters */ +/* Globals : None */ +/* Processing : Refer bumping process in the standard */ +/* Outputs : Assigns display sequence number. */ +/* Returns : None */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 27 04 2005 NS Draft */ +/* */ +/*****************************************************************************/ +WORD32 ih264d_assign_display_seq(dec_struct_t *ps_dec) +{ + WORD32 i; + WORD32 i4_min_poc; + WORD32 i4_min_poc_buf_id; + WORD32 i4_min_index; + dpb_manager_t *ps_dpb_mgr = ps_dec->ps_dpb_mgr; + WORD32 (*i4_poc_buf_id_map)[3] = ps_dpb_mgr->ai4_poc_buf_id_map; + + i4_min_poc = 0x7fffffff; + i4_min_poc_buf_id = -1; + i4_min_index = -1; + + if(ps_dpb_mgr->i1_poc_buf_id_entries >= ps_dec->i4_display_delay) + { + for(i = 0; i < MAX_FRAMES; i++) + { + if((i4_poc_buf_id_map[i][0] != -1) + && (DO_NOT_DISP + != ps_dpb_mgr->ai4_poc_buf_id_map[i][0])) + { + /* Checking for <= is necessary to handle cases where there is one + valid buffer with poc set to 0x7FFFFFFF. */ + if(i4_poc_buf_id_map[i][1] <= i4_min_poc) + { + i4_min_poc = i4_poc_buf_id_map[i][1]; + i4_min_poc_buf_id = i4_poc_buf_id_map[i][0]; + i4_min_index = i; + } + } + } + + if((i4_min_index != -1) && (DO_NOT_DISP != i4_min_poc_buf_id)) + { + ps_dec->i4_cur_display_seq++; + ih264_disp_mgr_add( + (disp_mgr_t *)ps_dec->pv_disp_buf_mgr, + i4_min_poc_buf_id, ps_dec->i4_cur_display_seq, + ps_dec->apv_buf_id_pic_buf_map[i4_min_poc_buf_id]); + i4_poc_buf_id_map[i4_min_index][0] = -1; + i4_poc_buf_id_map[i4_min_index][1] = 0x7fffffff; + ps_dpb_mgr->i1_poc_buf_id_entries--; + } + else if(DO_NOT_DISP == i4_min_poc_buf_id) + { + WORD32 i4_error_code; + i4_error_code = ERROR_GAPS_IN_FRM_NUM; +// i4_error_code |= 1<<IVD_CORRUPTEDDATA; + return i4_error_code; + } + } + return OK; +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_release_display_bufs */ +/* */ +/* Description : This function implments bumping process when mmco = 5. */ +/* Each outgoing frame from DPB is assigned a display */ +/* sequence number which increases monotonically. System */ +/* looks for this number to display a frame. */ +/* Inputs : ps_dec - Decoder parameters */ +/* Globals : None */ +/* Processing : Refer bumping process in the standard for mmco = 5 */ +/* Outputs : Assigns display sequence number. */ +/* Returns : None */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 27 04 2005 NS Draft */ +/* */ +/*****************************************************************************/ +void ih264d_release_display_bufs(dec_struct_t *ps_dec) +{ + WORD32 i, j; + WORD32 i4_min_poc; + WORD32 i4_min_poc_buf_id; + WORD32 i4_min_index; + WORD64 i8_temp; + dpb_manager_t *ps_dpb_mgr = ps_dec->ps_dpb_mgr; + WORD32 (*i4_poc_buf_id_map)[3] = ps_dpb_mgr->ai4_poc_buf_id_map; + + i4_min_poc = 0x7fffffff; + i4_min_poc_buf_id = 0; + i4_min_index = 0; + + ih264d_delete_nonref_nondisplay_pics(ps_dpb_mgr); + + for(j = 0; j < ps_dpb_mgr->i1_poc_buf_id_entries; j++) + { + i4_min_poc = 0x7fffffff; + for(i = 0; i < MAX_FRAMES; i++) + { + if(i4_poc_buf_id_map[i][0] != -1) + { + /* Checking for <= is necessary to handle cases where there is one + valid buffer with poc set to 0x7FFFFFFF. */ + if(i4_poc_buf_id_map[i][1] <= i4_min_poc) + { + i4_min_poc = i4_poc_buf_id_map[i][1]; + i4_min_poc_buf_id = i4_poc_buf_id_map[i][0]; + i4_min_index = i; + } + } + } + + if(DO_NOT_DISP != i4_min_poc_buf_id) + { + ps_dec->i4_cur_display_seq++; + ih264_disp_mgr_add( + (disp_mgr_t *)ps_dec->pv_disp_buf_mgr, + i4_min_poc_buf_id, ps_dec->i4_cur_display_seq, + ps_dec->apv_buf_id_pic_buf_map[i4_min_poc_buf_id]); + i4_poc_buf_id_map[i4_min_index][0] = -1; + i4_poc_buf_id_map[i4_min_index][1] = 0x7fffffff; + ps_dpb_mgr->ai4_poc_buf_id_map[i4_min_index][2] = 0; + } + else + { + i4_poc_buf_id_map[i4_min_index][0] = -1; + i4_poc_buf_id_map[i4_min_index][1] = 0x7fffffff; + ps_dpb_mgr->ai4_poc_buf_id_map[i4_min_index][2] = 0; + } + } + ps_dpb_mgr->i1_poc_buf_id_entries = 0; + i8_temp = (WORD64)ps_dec->i4_prev_max_display_seq + ps_dec->i4_max_poc + + ps_dec->u1_max_dec_frame_buffering + 1; + /*If i4_prev_max_display_seq overflows integer range, reset it */ + ps_dec->i4_prev_max_display_seq = IS_OUT_OF_RANGE_S32(i8_temp)? + 0 : i8_temp; + ps_dec->i4_max_poc = 0; +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_assign_pic_num */ +/* */ +/* Description : This function assigns pic num to each reference frame */ +/* depending on the cur_frame_num as speified in section */ +/* 8.2.4.1 */ +/* */ +/* Inputs : ps_dec */ +/* */ +/* Globals : NO globals used */ +/* */ +/* Processing : for all ST pictures */ +/* if( FrameNum > cur_frame_num) */ +/* PicNum = FrameNum - MaxFrameNum */ +/* else */ +/* PicNum = FrameNum */ +/* */ +/* Returns : void */ +/* */ +/* Issues : NO */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 13 07 2002 Jay Draft */ +/* */ +/*****************************************************************************/ + +void ih264d_assign_pic_num(dec_struct_t *ps_dec) +{ + dpb_manager_t *ps_dpb_mgr; + struct dpb_info_t *ps_next_dpb; + WORD8 i; + WORD32 i4_cur_frame_num, i4_max_frame_num; + WORD32 i4_ref_frame_num; + UWORD8 u1_fld_pic_flag = ps_dec->ps_cur_slice->u1_field_pic_flag; + + i4_max_frame_num = ps_dec->ps_cur_sps->u2_u4_max_pic_num_minus1 + 1; + i4_cur_frame_num = ps_dec->ps_cur_pic->i4_frame_num; + ps_dpb_mgr = ps_dec->ps_dpb_mgr; + + /* Start from ST head */ + ps_next_dpb = ps_dpb_mgr->ps_dpb_st_head; + for(i = 0; i < ps_dpb_mgr->u1_num_st_ref_bufs; i++) + { + WORD32 i4_pic_num; + + i4_ref_frame_num = ps_next_dpb->ps_pic_buf->i4_frame_num; + if(i4_ref_frame_num > i4_cur_frame_num) + { + /* RefPic Buf frame_num is before Current frame_num in decode order */ + i4_pic_num = i4_ref_frame_num - i4_max_frame_num; + } + else + { + /* RefPic Buf frame_num is after Current frame_num in decode order */ + i4_pic_num = i4_ref_frame_num; + } + + ps_next_dpb->ps_pic_buf->i4_pic_num = i4_pic_num; + ps_next_dpb->i4_frame_num = i4_pic_num; + ps_next_dpb->ps_pic_buf->u1_long_term_frm_idx = MAX_REF_BUFS + 1; + if(u1_fld_pic_flag) + { + /* Assign the pic num to top fields and bot fields */ + + ps_next_dpb->s_top_field.i4_pic_num = i4_pic_num * 2 + + !(ps_dec->ps_cur_slice->u1_bottom_field_flag); + ps_next_dpb->s_bot_field.i4_pic_num = i4_pic_num * 2 + + ps_dec->ps_cur_slice->u1_bottom_field_flag; + } + /* Chase the next link */ + ps_next_dpb = ps_next_dpb->ps_prev_short; + } + + if(ps_dec->ps_cur_sps->u1_gaps_in_frame_num_value_allowed_flag + && ps_dpb_mgr->u1_num_gaps) + { + WORD32 i4_start_frm, i4_end_frm; + /* Assign pic numbers for gaps */ + for(i = 0; i < MAX_FRAMES; i++) + { + i4_start_frm = ps_dpb_mgr->ai4_gaps_start_frm_num[i]; + if(i4_start_frm != INVALID_FRAME_NUM) + { + if(i4_start_frm > i4_cur_frame_num) + { + /* gap's frame_num is before Current frame_num in + decode order */ + i4_start_frm -= i4_max_frame_num; + } + ps_dpb_mgr->ai4_gaps_start_frm_num[i] = i4_start_frm; + i4_end_frm = ps_dpb_mgr->ai4_gaps_end_frm_num[i]; + + if(i4_end_frm > i4_cur_frame_num) + { + /* gap's frame_num is before Current frame_num in + decode order */ + i4_end_frm -= i4_max_frame_num; + } + ps_dpb_mgr->ai4_gaps_end_frm_num[i] = i4_end_frm; + } + } + } +} + +/*! + ************************************************************************** + * \if Function name : ih264d_update_qp \endif + * + * \brief + * Updates the values of QP and its related entities + * + * \return + * 0 on Success and Error code otherwise + * + ************************************************************************** + */ +WORD32 ih264d_update_qp(dec_struct_t * ps_dec, const WORD8 i1_qp) +{ + WORD32 i_temp; + i_temp = (ps_dec->u1_qp + i1_qp + 52) % 52; + + if((i_temp < 0) || (i_temp > 51) || (i1_qp < -26) || (i1_qp > 25)) + return ERROR_INV_RANGE_QP_T; + + ps_dec->u1_qp = i_temp; + ps_dec->u1_qp_y_rem6 = ps_dec->u1_qp % 6; + ps_dec->u1_qp_y_div6 = ps_dec->u1_qp / 6; + i_temp = CLIP3(0, 51, ps_dec->u1_qp + ps_dec->ps_cur_pps->i1_chroma_qp_index_offset); + ps_dec->u1_qp_u_rem6 = MOD(gau1_ih264d_qp_scale_cr[12 + i_temp], 6); + ps_dec->u1_qp_u_div6 = DIV(gau1_ih264d_qp_scale_cr[12 + i_temp], 6); + + i_temp = CLIP3(0, 51, ps_dec->u1_qp + ps_dec->ps_cur_pps->i1_second_chroma_qp_index_offset); + ps_dec->u1_qp_v_rem6 = MOD(gau1_ih264d_qp_scale_cr[12 + i_temp], 6); + ps_dec->u1_qp_v_div6 = DIV(gau1_ih264d_qp_scale_cr[12 + i_temp], 6); + + ps_dec->pu2_quant_scale_y = + gau2_ih264_iquant_scale_4x4[ps_dec->u1_qp_y_rem6]; + ps_dec->pu2_quant_scale_u = + gau2_ih264_iquant_scale_4x4[ps_dec->u1_qp_u_rem6]; + ps_dec->pu2_quant_scale_v = + gau2_ih264_iquant_scale_4x4[ps_dec->u1_qp_v_rem6]; + return OK; +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_decode_gaps_in_frame_num */ +/* */ +/* Description : This function decodes gaps in frame number */ +/* */ +/* Inputs : ps_dec Decoder parameters */ +/* u2_frame_num current frame number */ +/* */ +/* Globals : None */ +/* Processing : This functionality needs to be implemented */ +/* Outputs : None */ +/* Returns : None */ +/* */ +/* Issues : Not implemented */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 06 05 2002 NS Draft */ +/* */ +/*****************************************************************************/ +WORD32 ih264d_decode_gaps_in_frame_num(dec_struct_t *ps_dec, + UWORD16 u2_frame_num) +{ + UWORD32 u4_next_frm_num, u4_start_frm_num; + UWORD32 u4_max_frm_num; + pocstruct_t s_tmp_poc; + WORD32 i4_poc; + dec_slice_params_t *ps_cur_slice; + + dec_pic_params_t *ps_pic_params; + WORD8 i1_gap_idx; + WORD32 *i4_gaps_start_frm_num; + dpb_manager_t *ps_dpb_mgr; + WORD32 i4_frame_gaps; + WORD8 *pi1_gaps_per_seq; + WORD32 ret; + + ps_cur_slice = ps_dec->ps_cur_slice; + if(ps_cur_slice->u1_field_pic_flag) + { + if(ps_dec->u2_prev_ref_frame_num == u2_frame_num) + return 0; + } + + u4_next_frm_num = ps_dec->u2_prev_ref_frame_num + 1; + u4_max_frm_num = ps_dec->ps_cur_sps->u2_u4_max_pic_num_minus1 + 1; + + // check + if(u4_next_frm_num >= u4_max_frm_num) + { + u4_next_frm_num -= u4_max_frm_num; + } + + if(u4_next_frm_num == u2_frame_num) + { + return (0); + } + + // check + if((ps_dec->u1_nal_unit_type == IDR_SLICE_NAL) + && (u4_next_frm_num >= u2_frame_num)) + { + return (0); + } + u4_start_frm_num = u4_next_frm_num; + + s_tmp_poc.i4_pic_order_cnt_lsb = 0; + s_tmp_poc.i4_delta_pic_order_cnt_bottom = 0; + s_tmp_poc.i4_pic_order_cnt_lsb = 0; + s_tmp_poc.i4_delta_pic_order_cnt_bottom = 0; + s_tmp_poc.i4_delta_pic_order_cnt[0] = 0; + s_tmp_poc.i4_delta_pic_order_cnt[1] = 0; + + ps_cur_slice = ps_dec->ps_cur_slice; + ps_pic_params = ps_dec->ps_cur_pps; + + i4_frame_gaps = 0; + ps_dpb_mgr = ps_dec->ps_dpb_mgr; + + /* Find a empty slot to store gap seqn info */ + i4_gaps_start_frm_num = ps_dpb_mgr->ai4_gaps_start_frm_num; + for(i1_gap_idx = 0; i1_gap_idx < MAX_FRAMES; i1_gap_idx++) + { + if(INVALID_FRAME_NUM == i4_gaps_start_frm_num[i1_gap_idx]) + break; + } + if(MAX_FRAMES == i1_gap_idx) + { + UWORD32 i4_error_code; + i4_error_code = ERROR_DBP_MANAGER_T; +// i4_error_code |= 1<<IVD_CORRUPTEDDATA; + return i4_error_code; + } + + i4_poc = 0; + i4_gaps_start_frm_num[i1_gap_idx] = u4_start_frm_num; + ps_dpb_mgr->ai4_gaps_end_frm_num[i1_gap_idx] = u2_frame_num - 1; + pi1_gaps_per_seq = ps_dpb_mgr->ai1_gaps_per_seq; + pi1_gaps_per_seq[i1_gap_idx] = 0; + while(u4_next_frm_num != u2_frame_num) + { + ih264d_delete_nonref_nondisplay_pics(ps_dpb_mgr); + if(ps_pic_params->ps_sps->u1_pic_order_cnt_type) + { + /* allocate a picture buffer and insert it as ST node */ + ret = ih264d_decode_pic_order_cnt(0, u4_next_frm_num, + &ps_dec->s_prev_pic_poc, + &s_tmp_poc, ps_cur_slice, + ps_pic_params, 1, 0, 0, + &i4_poc); + if(ret != OK) + return ret; + + /* Display seq no calculations */ + if(i4_poc >= ps_dec->i4_max_poc) + ps_dec->i4_max_poc = i4_poc; + /* IDR Picture or POC wrap around */ + if(i4_poc == 0) + { + WORD64 i8_temp; + i8_temp = (WORD64)ps_dec->i4_prev_max_display_seq + + ps_dec->i4_max_poc + + ps_dec->u1_max_dec_frame_buffering + 1; + /*If i4_prev_max_display_seq overflows integer range, reset it */ + ps_dec->i4_prev_max_display_seq = IS_OUT_OF_RANGE_S32(i8_temp)? + 0 : i8_temp; + ps_dec->i4_max_poc = 0; + } + + ps_cur_slice->u1_mmco_equalto5 = 0; + ps_cur_slice->u2_frame_num = u4_next_frm_num; + } + + // check + if(ps_dpb_mgr->i1_poc_buf_id_entries + >= ps_dec->u1_max_dec_frame_buffering) + { + ret = ih264d_assign_display_seq(ps_dec); + if(ret != OK) + return ret; + } + + { + WORD64 i8_display_poc; + i8_display_poc = (WORD64)ps_dec->i4_prev_max_display_seq + + i4_poc; + if(IS_OUT_OF_RANGE_S32(i8_display_poc)) + { + ps_dec->i4_prev_max_display_seq = 0; + } + } + ret = ih264d_insert_pic_in_display_list( + ps_dec->ps_dpb_mgr, (WORD8) DO_NOT_DISP, + (WORD32)(ps_dec->i4_prev_max_display_seq + i4_poc), + u4_next_frm_num); + if(ret != OK) + return ret; + + pi1_gaps_per_seq[i1_gap_idx]++; + ret = ih264d_do_mmco_for_gaps(ps_dpb_mgr, + ps_dec->ps_cur_sps->u1_num_ref_frames); + if(ret != OK) + return ret; + + ih264d_delete_nonref_nondisplay_pics(ps_dpb_mgr); + + u4_next_frm_num++; + if(u4_next_frm_num >= u4_max_frm_num) + { + u4_next_frm_num -= u4_max_frm_num; + } + + i4_frame_gaps++; + } + + return OK; +} + +/*! + ************************************************************************** + * \if Function name : ih264d_create_pic_buffers \endif + * + * \brief + * This function creates Picture Buffers. + * + * \return + * 0 on Success and -1 on error + ************************************************************************** + */ +WORD32 ih264d_create_pic_buffers(UWORD8 u1_num_of_buf, + dec_struct_t *ps_dec) +{ + struct pic_buffer_t *ps_pic_buf; + UWORD8 i; + UWORD32 u4_luma_size, u4_chroma_size; + UWORD8 u1_frm = ps_dec->ps_cur_sps->u1_frame_mbs_only_flag; + WORD32 j; + UWORD8 *pu1_buf; + + ps_pic_buf = ps_dec->ps_pic_buf_base; + ih264_disp_mgr_init((disp_mgr_t *)ps_dec->pv_disp_buf_mgr); + ih264_buf_mgr_init((buf_mgr_t *)ps_dec->pv_pic_buf_mgr); + u4_luma_size = ps_dec->u2_frm_wd_y * ps_dec->u2_frm_ht_y; + u4_chroma_size = ps_dec->u2_frm_wd_uv * ps_dec->u2_frm_ht_uv; + + { + if(ps_dec->u4_share_disp_buf == 1) + { + /* In case of buffers getting shared between application and library + there is no need of reference memtabs. Instead of setting the i4_size + to zero, it is reduced to a small i4_size to ensure that changes + in the code are minimal */ + if((ps_dec->u1_chroma_format == IV_YUV_420SP_UV) + || (ps_dec->u1_chroma_format == IV_YUV_420SP_VU) + || (ps_dec->u1_chroma_format == IV_YUV_420P)) + { + u4_luma_size = 64; + } + + if(ps_dec->u1_chroma_format == IV_YUV_420SP_UV) + { + u4_chroma_size = 64; + } + + } + } + + pu1_buf = ps_dec->pu1_pic_buf_base; + + /* Allocate memory for refernce buffers */ + for(i = 0; i < u1_num_of_buf; i++) + { + UWORD32 u4_offset; + WORD32 buf_ret; + UWORD8 *pu1_luma, *pu1_chroma; + void *pv_mem_ctxt = ps_dec->pv_mem_ctxt; + + pu1_luma = pu1_buf; + pu1_buf += ALIGN64(u4_luma_size); + pu1_chroma = pu1_buf; + pu1_buf += ALIGN64(u4_chroma_size); + + /* Offset to the start of the pic from the top left corner of the frame + buffer */ + + if((0 == ps_dec->u4_share_disp_buf) + || (NULL == ps_dec->disp_bufs[i].buf[0])) + { + UWORD32 pad_len_h, pad_len_v; + + u4_offset = ps_dec->u2_frm_wd_y * (PAD_LEN_Y_V << 1) + PAD_LEN_Y_H; + ps_pic_buf->pu1_buf1 = (UWORD8 *)(pu1_luma) + u4_offset; + + pad_len_h = MAX(PAD_LEN_UV_H, (PAD_LEN_Y_H >> 1)); + pad_len_v = MAX(PAD_LEN_UV_V, PAD_LEN_Y_V); + + u4_offset = ps_dec->u2_frm_wd_uv * pad_len_v + pad_len_h; + + ps_pic_buf->pu1_buf2 = (UWORD8 *)(pu1_chroma) + u4_offset; + ps_pic_buf->pu1_buf3 = (UWORD8 *)(NULL) + u4_offset; + + } + else + { + UWORD32 pad_len_h, pad_len_v; + u4_offset = ps_dec->u2_frm_wd_y * (PAD_LEN_Y_V << 1) + PAD_LEN_Y_H; + ps_pic_buf->pu1_buf1 = (UWORD8 *)ps_dec->disp_bufs[i].buf[0] + + u4_offset; + + ps_dec->disp_bufs[i].u4_ofst[0] = u4_offset; + + if(ps_dec->u1_chroma_format == IV_YUV_420P) + { + pad_len_h = MAX(PAD_LEN_UV_H * YUV420SP_FACTOR, + (PAD_LEN_Y_H >> 1)); + pad_len_v = MAX(PAD_LEN_UV_V, PAD_LEN_Y_V); + + u4_offset = ps_dec->u2_frm_wd_uv * pad_len_v + pad_len_h; + ps_pic_buf->pu1_buf2 = (UWORD8 *)(pu1_chroma) + u4_offset; + ps_pic_buf->pu1_buf3 = (UWORD8 *)(NULL) + u4_offset; + + ps_dec->disp_bufs[i].u4_ofst[1] = u4_offset; + ps_dec->disp_bufs[i].u4_ofst[2] = u4_offset; + + } + else + { + pad_len_h = MAX(PAD_LEN_UV_H * YUV420SP_FACTOR, + (PAD_LEN_Y_H >> 1)); + pad_len_v = MAX(PAD_LEN_UV_V, PAD_LEN_Y_V); + + u4_offset = ps_dec->u2_frm_wd_uv * pad_len_v + pad_len_h; + ps_pic_buf->pu1_buf2 = (UWORD8 *)(ps_dec->disp_bufs[i].buf[1]) + + u4_offset; + ps_pic_buf->pu1_buf3 = (UWORD8 *)(ps_dec->disp_bufs[i].buf[1]) + + u4_offset; + + ps_dec->disp_bufs[i].u4_ofst[1] = u4_offset; + ps_dec->disp_bufs[i].u4_ofst[2] = u4_offset; + } + } + + ps_pic_buf->u2_frm_ht_y = ps_dec->u2_frm_ht_y; + ps_pic_buf->u2_frm_ht_uv = ps_dec->u2_frm_ht_uv; + ps_pic_buf->u2_frm_wd_y = ps_dec->u2_frm_wd_y; + ps_pic_buf->u2_frm_wd_uv = ps_dec->u2_frm_wd_uv; + + ps_pic_buf->u1_pic_buf_id = i; + + buf_ret = ih264_buf_mgr_add((buf_mgr_t *)ps_dec->pv_pic_buf_mgr, + ps_pic_buf, i); + if(0 != buf_ret) + { + ps_dec->i4_error_code = ERROR_BUF_MGR; + return ERROR_BUF_MGR; + } + + ps_dec->apv_buf_id_pic_buf_map[i] = (void *)ps_pic_buf; + ps_pic_buf++; + } + + if(1 == ps_dec->u4_share_disp_buf) + { + for(i = 0; i < u1_num_of_buf; i++) + ps_dec->u4_disp_buf_mapping[i] = 1; + } + return OK; +} + +/*! + ************************************************************************** + * \if Function name : ih264d_allocate_dynamic_bufs \endif + * + * \brief + * This function allocates memory required by Decoder. + * + * \param ps_dec: Pointer to dec_struct_t. + * + * \return + * Returns i4_status as returned by MemManager. + * + ************************************************************************** + */ +WORD16 ih264d_allocate_dynamic_bufs(dec_struct_t * ps_dec) +{ + struct MemReq s_MemReq; + struct MemBlock *p_MemBlock; + + pred_info_t *ps_pred_frame; + dec_mb_info_t *ps_frm_mb_info; + dec_slice_struct_t *ps_dec_slice_buf; + UWORD8 *pu1_dec_mb_map, *pu1_recon_mb_map; + UWORD16 *pu2_slice_num_map; + + WORD16 *pi16_res_coeff; + WORD16 i16_status = 0; + UWORD8 uc_frmOrFld = (1 - ps_dec->ps_cur_sps->u1_frame_mbs_only_flag); + UWORD16 u4_luma_wd = ps_dec->u2_frm_wd_y; + UWORD16 u4_chroma_wd = ps_dec->u2_frm_wd_uv; + WORD8 c_i = 0; + dec_seq_params_t *ps_sps = ps_dec->ps_cur_sps; + UWORD32 u4_total_mbs = ps_sps->u2_total_num_of_mbs << uc_frmOrFld; + UWORD32 u4_wd_mbs = ps_dec->u2_frm_wd_in_mbs; + UWORD32 u4_ht_mbs = ps_dec->u2_frm_ht_in_mbs; + UWORD32 u4_blk_wd; + UWORD32 ui_size = 0; + UWORD32 u4_int_scratch_size = 0, u4_ref_pred_size = 0; + UWORD8 *pu1_buf; + WORD32 num_entries; + WORD32 size; + void *pv_buf; + UWORD32 u4_num_bufs; + UWORD32 u4_luma_size, u4_chroma_size; + void *pv_mem_ctxt = ps_dec->pv_mem_ctxt; + + size = u4_total_mbs; + pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + memset(pv_buf, 0, size); + ps_dec->pu1_dec_mb_map = pv_buf; + + size = u4_total_mbs; + pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + memset(pv_buf, 0, size); + ps_dec->pu1_recon_mb_map = pv_buf; + + size = u4_total_mbs * sizeof(UWORD16); + pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + memset(pv_buf, 0, size); + ps_dec->pu2_slice_num_map = pv_buf; + + /************************************************************/ + /* Post allocation Initialisations */ + /************************************************************/ + ps_dec->ps_parse_cur_slice = &(ps_dec->ps_dec_slice_buf[0]); + ps_dec->ps_decode_cur_slice = &(ps_dec->ps_dec_slice_buf[0]); + ps_dec->ps_computebs_cur_slice = &(ps_dec->ps_dec_slice_buf[0]); + + ps_dec->ps_pred_start = ps_dec->ps_pred; + + size = sizeof(parse_pmbarams_t) * (ps_dec->u1_recon_mb_grp); + pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + memset(pv_buf, 0, size); + ps_dec->ps_parse_mb_data = pv_buf; + + size = sizeof(parse_part_params_t) + * ((ps_dec->u1_recon_mb_grp) << 4); + pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + memset(pv_buf, 0, size); + ps_dec->ps_parse_part_params = pv_buf; + + size = ((u4_wd_mbs * sizeof(deblkmb_neighbour_t)) << uc_frmOrFld); + pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + memset(pv_buf, 0, size); + ps_dec->ps_deblk_top_mb = pv_buf; + + size = ((sizeof(ctxt_inc_mb_info_t)) + * (((u4_wd_mbs + 1) << uc_frmOrFld) + 1)); + pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + memset(pv_buf, 0, size); + ps_dec->p_ctxt_inc_mb_map = pv_buf; + + /* 0th entry of CtxtIncMbMap will be always be containing default values + for CABAC context representing MB not available */ + ps_dec->p_ctxt_inc_mb_map += 1; + + size = (sizeof(mv_pred_t) * ps_dec->u1_recon_mb_grp + * 16); + pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + memset(pv_buf, 0, size); + ps_dec->ps_mv_p[0] = pv_buf; + + size = (sizeof(mv_pred_t) * ps_dec->u1_recon_mb_grp + * 16); + pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + memset(pv_buf, 0, size); + ps_dec->ps_mv_p[1] = pv_buf; + + { + UWORD8 i; + for(i = 0; i < MV_SCRATCH_BUFS; i++) + { + size = (sizeof(mv_pred_t) + * ps_dec->u1_recon_mb_grp * 4); + pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + memset(pv_buf, 0, size); + ps_dec->ps_mv_top_p[i] = pv_buf; + } + } + + size = sizeof(UWORD8) * ((u4_wd_mbs + 2) * MB_SIZE) * 2; + pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + ps_dec->pu1_y_intra_pred_line = pv_buf; + memset(ps_dec->pu1_y_intra_pred_line, 0, size); + ps_dec->pu1_y_intra_pred_line += MB_SIZE; + + size = sizeof(UWORD8) * ((u4_wd_mbs + 2) * MB_SIZE) * 2; + pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + ps_dec->pu1_u_intra_pred_line = pv_buf; + memset(ps_dec->pu1_u_intra_pred_line, 0, size); + ps_dec->pu1_u_intra_pred_line += MB_SIZE; + + size = sizeof(UWORD8) * ((u4_wd_mbs + 2) * MB_SIZE) * 2; + pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + ps_dec->pu1_v_intra_pred_line = pv_buf; + memset(ps_dec->pu1_v_intra_pred_line, 0, size); + ps_dec->pu1_v_intra_pred_line += MB_SIZE; + + if(ps_dec->u1_separate_parse) + { + /* Needs one extra row of info, to hold top row data */ + size = sizeof(mb_neigbour_params_t) + * 2 * ((u4_wd_mbs + 2) * (u4_ht_mbs + 1)); + } + else + { + size = sizeof(mb_neigbour_params_t) + * 2 * ((u4_wd_mbs + 2) << uc_frmOrFld); + } + pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + + ps_dec->ps_nbr_mb_row = pv_buf; + memset(ps_dec->ps_nbr_mb_row, 0, size); + + /* Allocate deblock MB info */ + size = (u4_total_mbs + u4_wd_mbs) * sizeof(deblk_mb_t); + + pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + ps_dec->ps_deblk_pic = pv_buf; + + memset(ps_dec->ps_deblk_pic, 0, size); + + /* Allocate frame level mb info */ + size = sizeof(dec_mb_info_t) * u4_total_mbs; + pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + ps_dec->ps_frm_mb_info = pv_buf; + memset(ps_dec->ps_frm_mb_info, 0, size); + + /* Allocate memory for slice headers dec_slice_struct_t */ + num_entries = MAX_FRAMES; + if((1 >= ps_dec->ps_cur_sps->u1_num_ref_frames) && + (0 == ps_dec->i4_display_delay)) + { + num_entries = 1; + } + num_entries = ((2 * num_entries) + 1); + num_entries *= 2; + + size = num_entries * sizeof(void *); + size += PAD_MAP_IDX_POC * sizeof(void *); + size *= u4_total_mbs; + size += sizeof(dec_slice_struct_t) * u4_total_mbs; + pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + + ps_dec->ps_dec_slice_buf = pv_buf; + memset(ps_dec->ps_dec_slice_buf, 0, size); + pu1_buf = (UWORD8 *)ps_dec->ps_dec_slice_buf; + pu1_buf += sizeof(dec_slice_struct_t) * u4_total_mbs; + ps_dec->pv_map_ref_idx_to_poc_buf = (void *)pu1_buf; + + /* Allocate memory for packed pred info */ + num_entries = u4_total_mbs; + num_entries *= 16 * 2; + + size = sizeof(pred_info_pkd_t) * num_entries; + pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + memset(pv_buf, 0, size); + ps_dec->ps_pred_pkd = pv_buf; + + /* Allocate memory for coeff data */ + size = MB_LUM_SIZE * sizeof(WORD16); + /*For I16x16 MBs, 16 4x4 AC coeffs and 1 4x4 DC coeff TU blocks will be sent + For all MBs along with 8 4x4 AC coeffs 2 2x2 DC coeff TU blocks will be sent + So use 17 4x4 TU blocks for luma and 9 4x4 TU blocks for chroma */ + size += u4_total_mbs * (MAX(17 * sizeof(tu_sblk4x4_coeff_data_t),4 * sizeof(tu_blk8x8_coeff_data_t)) + + 9 * sizeof(tu_sblk4x4_coeff_data_t)); + //32 bytes for each mb to store u1_prev_intra4x4_pred_mode and u1_rem_intra4x4_pred_mode data + size += u4_total_mbs * 32; + pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + memset(pv_buf, 0, size); + + ps_dec->pi2_coeff_data = pv_buf; + + ps_dec->pv_pic_tu_coeff_data = (void *)(ps_dec->pi2_coeff_data + MB_LUM_SIZE); + + /* Allocate MV bank buffer */ + { + UWORD32 col_flag_buffer_size, mvpred_buffer_size; + + col_flag_buffer_size = ((ps_dec->u2_pic_wd * ps_dec->u2_pic_ht) >> 4); + mvpred_buffer_size = sizeof(mv_pred_t) + * ((ps_dec->u2_pic_wd * (ps_dec->u2_pic_ht + PAD_MV_BANK_ROW)) >> 4); + + u4_num_bufs = ps_dec->ps_cur_sps->u1_num_ref_frames + 1; + + u4_num_bufs = MIN(u4_num_bufs, ps_dec->u1_pic_bufs); + u4_num_bufs = MAX(u4_num_bufs, 2); + size = ALIGN64(mvpred_buffer_size) + ALIGN64(col_flag_buffer_size); + size *= u4_num_bufs; + pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + memset(pv_buf, 0, size); + ps_dec->pu1_mv_bank_buf_base = pv_buf; + } + + /* Allocate Pic buffer */ + u4_luma_size = ps_dec->u2_frm_wd_y * ps_dec->u2_frm_ht_y; + u4_chroma_size = ps_dec->u2_frm_wd_uv * ps_dec->u2_frm_ht_uv; + + { + if(ps_dec->u4_share_disp_buf == 1) + { + /* In case of buffers getting shared between application and library + there is no need of reference memtabs. Instead of setting the i4_size + to zero, it is reduced to a small i4_size to ensure that changes + in the code are minimal */ + if((ps_dec->u1_chroma_format == IV_YUV_420SP_UV) + || (ps_dec->u1_chroma_format == IV_YUV_420SP_VU) + || (ps_dec->u1_chroma_format == IV_YUV_420P)) + { + u4_luma_size = 64; + } + + if(ps_dec->u1_chroma_format == IV_YUV_420SP_UV) + { + u4_chroma_size = 64; + } + + } + } + + size = ALIGN64(u4_luma_size) + ALIGN64(u4_chroma_size); + size *= ps_dec->u1_pic_bufs; + pv_buf = ps_dec->pf_aligned_alloc(pv_mem_ctxt, 128, size); + RETURN_IF((NULL == pv_buf), IV_FAIL); + memset(pv_buf, 0, size); + ps_dec->pu1_pic_buf_base = pv_buf; + + /* Post allocation Increment Actions */ + + /***************************************************************************/ + /*Initialize cabac context pointers for every SE that has fixed contextIdx */ + /***************************************************************************/ + { + bin_ctxt_model_t * const p_cabac_ctxt_table_t = + ps_dec->p_cabac_ctxt_table_t; + bin_ctxt_model_t * * p_coeff_abs_level_minus1_t = + ps_dec->p_coeff_abs_level_minus1_t; + bin_ctxt_model_t * * p_cbf_t = ps_dec->p_cbf_t; + + ps_dec->p_mb_field_dec_flag_t = p_cabac_ctxt_table_t + + MB_FIELD_DECODING_FLAG; + ps_dec->p_prev_intra4x4_pred_mode_flag_t = p_cabac_ctxt_table_t + + PREV_INTRA4X4_PRED_MODE_FLAG; + ps_dec->p_rem_intra4x4_pred_mode_t = p_cabac_ctxt_table_t + + REM_INTRA4X4_PRED_MODE; + ps_dec->p_intra_chroma_pred_mode_t = p_cabac_ctxt_table_t + + INTRA_CHROMA_PRED_MODE; + ps_dec->p_mb_qp_delta_t = p_cabac_ctxt_table_t + MB_QP_DELTA; + ps_dec->p_ref_idx_t = p_cabac_ctxt_table_t + REF_IDX; + ps_dec->p_mvd_x_t = p_cabac_ctxt_table_t + MVD_X; + ps_dec->p_mvd_y_t = p_cabac_ctxt_table_t + MVD_Y; + p_cbf_t[0] = p_cabac_ctxt_table_t + CBF + 0; + p_cbf_t[1] = p_cabac_ctxt_table_t + CBF + 4; + p_cbf_t[2] = p_cabac_ctxt_table_t + CBF + 8; + p_cbf_t[3] = p_cabac_ctxt_table_t + CBF + 12; + p_cbf_t[4] = p_cabac_ctxt_table_t + CBF + 16; + ps_dec->p_cbp_luma_t = p_cabac_ctxt_table_t + CBP_LUMA; + ps_dec->p_cbp_chroma_t = p_cabac_ctxt_table_t + CBP_CHROMA; + + p_coeff_abs_level_minus1_t[LUMA_DC_CTXCAT] = p_cabac_ctxt_table_t + + COEFF_ABS_LEVEL_MINUS1 + COEFF_ABS_LEVEL_CAT_0_OFFSET; + + p_coeff_abs_level_minus1_t[LUMA_AC_CTXCAT] = p_cabac_ctxt_table_t + + COEFF_ABS_LEVEL_MINUS1 + COEFF_ABS_LEVEL_CAT_1_OFFSET; + + p_coeff_abs_level_minus1_t[LUMA_4X4_CTXCAT] = p_cabac_ctxt_table_t + + COEFF_ABS_LEVEL_MINUS1 + COEFF_ABS_LEVEL_CAT_2_OFFSET; + + p_coeff_abs_level_minus1_t[CHROMA_DC_CTXCAT] = p_cabac_ctxt_table_t + + COEFF_ABS_LEVEL_MINUS1 + COEFF_ABS_LEVEL_CAT_3_OFFSET; + + p_coeff_abs_level_minus1_t[CHROMA_AC_CTXCAT] = p_cabac_ctxt_table_t + + COEFF_ABS_LEVEL_MINUS1 + COEFF_ABS_LEVEL_CAT_4_OFFSET; + + p_coeff_abs_level_minus1_t[LUMA_8X8_CTXCAT] = p_cabac_ctxt_table_t + + COEFF_ABS_LEVEL_MINUS1_8X8 + + COEFF_ABS_LEVEL_CAT_5_OFFSET; + + /********************************************************/ + /* context for the high profile related syntax elements */ + /* This is maintained seperately in s_high_profile */ + /********************************************************/ + { + + ps_dec->s_high_profile.ps_transform8x8_flag = p_cabac_ctxt_table_t + + TRANSFORM_SIZE_8X8_FLAG; + + ps_dec->s_high_profile.ps_sigcoeff_8x8_frame = p_cabac_ctxt_table_t + + SIGNIFICANT_COEFF_FLAG_8X8_FRAME; + + ps_dec->s_high_profile.ps_last_sigcoeff_8x8_frame = + p_cabac_ctxt_table_t + + LAST_SIGNIFICANT_COEFF_FLAG_8X8_FRAME; + + ps_dec->s_high_profile.ps_coeff_abs_levelminus1 = + p_cabac_ctxt_table_t + COEFF_ABS_LEVEL_MINUS1_8X8; + + ps_dec->s_high_profile.ps_sigcoeff_8x8_field = p_cabac_ctxt_table_t + + SIGNIFICANT_COEFF_FLAG_8X8_FIELD; + + ps_dec->s_high_profile.ps_last_sigcoeff_8x8_field = + p_cabac_ctxt_table_t + + LAST_SIGNIFICANT_COEFF_FLAG_8X8_FIELD; + } + } + return (i16_status); +} + +/*! + ************************************************************************** + * \if Function name : ih264d_free_dynamic_bufs \endif + * + * \brief + * This function frees dynamic memory allocated by Decoder. + * + * \param ps_dec: Pointer to dec_struct_t. + * + * \return + * Returns i4_status as returned by MemManager. + * + ************************************************************************** + */ +WORD16 ih264d_free_dynamic_bufs(dec_struct_t * ps_dec) +{ + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pu1_bits_buf_dynamic); + + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_deblk_pic); + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pu1_dec_mb_map); + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pu1_recon_mb_map); + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pu2_slice_num_map); + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_dec_slice_buf); + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_frm_mb_info); + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pi2_coeff_data); + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_parse_mb_data); + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_parse_part_params); + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_deblk_top_mb); + + if(ps_dec->p_ctxt_inc_mb_map) + { + ps_dec->p_ctxt_inc_mb_map -= 1; + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->p_ctxt_inc_mb_map); + } + + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_mv_p[0]); + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_mv_p[1]); + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_pred_pkd); + { + UWORD8 i; + for(i = 0; i < MV_SCRATCH_BUFS; i++) + { + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_mv_top_p[i]); + } + } + + if(ps_dec->pu1_y_intra_pred_line) + { + ps_dec->pu1_y_intra_pred_line -= MB_SIZE; + } + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pu1_y_intra_pred_line); + + if(ps_dec->pu1_u_intra_pred_line) + { + ps_dec->pu1_u_intra_pred_line -= MB_SIZE; + } + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pu1_u_intra_pred_line); + + if(ps_dec->pu1_v_intra_pred_line) + { + ps_dec->pu1_v_intra_pred_line -= MB_SIZE; + } + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pu1_v_intra_pred_line); + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->ps_nbr_mb_row); + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pu1_mv_bank_buf_base); + PS_DEC_ALIGNED_FREE(ps_dec, ps_dec->pu1_pic_buf_base); + return 0; +} + +/*! + ************************************************************************** + * \if Function name : ih264d_create_mv_bank \endif + * + * \brief + * This function creates MV bank. + * + * \param memType : Type of memory being handled + * 0: Display Buffer + * 1: Decoder Buffer + * 2: Internal Buffer + * \param u1_num_of_buf: Number of decode or display buffers. + * \param u4_wd : Frame width. + * \param u4_ht : Frame Height. + * \param ps_pic_buf_api : Pointer to Picture Buffer API. + * \param ih264d_dec_mem_manager : Memory manager utility supplied by system. + * + * \return + * 0 on Success and -1 on error + * + ************************************************************************** + */ +WORD32 ih264d_create_mv_bank(void *pv_dec, + UWORD32 ui_width, + UWORD32 ui_height) +{ + UWORD8 i; + UWORD32 col_flag_buffer_size, mvpred_buffer_size; + UWORD8 *pu1_mv_buf_mgr_base, *pu1_mv_bank_base; + col_mv_buf_t *ps_col_mv; + mv_pred_t *ps_mv; + UWORD8 *pu1_col_zero_flag_buf; + dec_struct_t *ps_dec = (dec_struct_t *)pv_dec; + WORD32 buf_ret; + UWORD32 u4_num_bufs; + UWORD8 *pu1_buf; + WORD32 size; + void *pv_mem_ctxt = ps_dec->pv_mem_ctxt; + + col_flag_buffer_size = ((ui_width * ui_height) >> 4); + mvpred_buffer_size = sizeof(mv_pred_t) + * ((ui_width * (ui_height + PAD_MV_BANK_ROW)) >> 4); + + ih264_buf_mgr_init((buf_mgr_t *)ps_dec->pv_mv_buf_mgr); + + ps_col_mv = ps_dec->ps_col_mv_base; + + u4_num_bufs = ps_dec->ps_cur_sps->u1_num_ref_frames + 1; + + u4_num_bufs = MIN(u4_num_bufs, ps_dec->u1_pic_bufs); + u4_num_bufs = MAX(u4_num_bufs, 2); + pu1_buf = ps_dec->pu1_mv_bank_buf_base; + for(i = 0 ; i < u4_num_bufs ; i++) + { + pu1_col_zero_flag_buf = pu1_buf; + pu1_buf += ALIGN64(col_flag_buffer_size); + + ps_mv = (mv_pred_t *)pu1_buf; + pu1_buf += ALIGN64(mvpred_buffer_size); + + memset(ps_mv, 0, ((ui_width * OFFSET_MV_BANK_ROW) >> 4) * sizeof(mv_pred_t)); + ps_mv += (ui_width*OFFSET_MV_BANK_ROW) >> 4; + + ps_col_mv->pv_col_zero_flag = (void *)pu1_col_zero_flag_buf; + ps_col_mv->pv_mv = (void *)ps_mv; + buf_ret = ih264_buf_mgr_add((buf_mgr_t *)ps_dec->pv_mv_buf_mgr, ps_col_mv, i); + if(0 != buf_ret) + { + ps_dec->i4_error_code = ERROR_BUF_MGR; + return ERROR_BUF_MGR; + } + ps_col_mv++; + } + return OK; +} + +void ih264d_unpack_coeff4x4_dc_4x4blk(tu_sblk4x4_coeff_data_t *ps_tu_4x4, + WORD16 *pi2_out_coeff_data, + UWORD8 *pu1_inv_scan) +{ + UWORD16 u2_sig_coeff_map = ps_tu_4x4->u2_sig_coeff_map; + WORD32 idx; + WORD16 *pi2_coeff_data = &ps_tu_4x4->ai2_level[0]; + + while(u2_sig_coeff_map) + { + idx = CLZ(u2_sig_coeff_map); + + idx = 31 - idx; + RESET_BIT(u2_sig_coeff_map,idx); + + idx = pu1_inv_scan[idx]; + pi2_out_coeff_data[idx] = *pi2_coeff_data++; + + } +} diff --git a/dependencies/ih264d/decoder/ih264d_utils.h b/dependencies/ih264d/decoder/ih264d_utils.h new file mode 100644 index 00000000..ddef744c --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_utils.h @@ -0,0 +1,102 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +#ifndef _IH264D_UTILS_H_ +#define _IH264D_UTILS_H_ +/*! +************************************************************************** +* \file ih264d_utils.h +* +* \brief +* Contains declaration of routines +* that handle of start and end of pic processing +* +* \date +* 19/12/2002 +* +* \author AI +************************************************************************** +*/ +#include "ih264d_defs.h" +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264d_structs.h" +#include "ih264d_parse_cavlc.h" + +#define PS_DEC_ALIGNED_FREE(ps_dec, y) \ +if(y) {ps_dec->pf_aligned_free(ps_dec->pv_mem_ctxt, ((void *)y)); (y) = NULL;} +void pad_frm_buff_vert(dec_struct_t *ps_dec); + +UWORD8 ih264d_is_end_of_pic(UWORD16 u2_frame_num, + UWORD8 u1_nal_ref_idc, + pocstruct_t *ps_cur_poc, + pocstruct_t *ps_prev_poc, + dec_slice_params_t * ps_prev_slice, + UWORD8 u1_pic_order_cnt_type, + UWORD8 u1_nal_unit_type, + UWORD32 u4_idr_pic_id, + UWORD8 u1_field_pic_flag, + UWORD8 u1_bottom_field_flag); + +WORD32 ih264d_end_of_pic_processing(dec_struct_t * ps_dec); + +WORD32 ih264d_init_pic(dec_struct_t *ps_dec, + UWORD16 u2_frame_num, + WORD32 i4_poc, + dec_pic_params_t * ps_pps); + +WORD32 ih264d_end_of_pic_processing(dec_struct_t * ps_dec); +WORD32 ih264d_decode_pic_order_cnt(UWORD8 u1_is_idr_slice, + UWORD32 u2_frame_num, + pocstruct_t *ps_prev_poc, + pocstruct_t *ps_cur_poc, + dec_slice_params_t *ps_cur_slice, + dec_pic_params_t * ps_pps, + UWORD8 u1_nal_ref_idc, + UWORD8 u1_bottom_field_flag, + UWORD8 u1_field_pic_flag, + WORD32 *pi4_poc); +void ih264d_release_display_bufs(dec_struct_t *ps_dec); +WORD32 ih264d_assign_display_seq(dec_struct_t *ps_dec); +void ih264d_assign_pic_num(dec_struct_t *ps_dec); + +void ih264d_unpack_coeff4x4_dc_4x4blk(tu_sblk4x4_coeff_data_t *ps_tu_4x4, + WORD16 *pi2_out_coeff_data, + UWORD8 *pu1_inv_scan); + +WORD32 ih264d_update_qp(dec_struct_t * ps_dec, const WORD8 i1_qp); +WORD32 ih264d_decode_gaps_in_frame_num(dec_struct_t *ps_dec, + UWORD16 u2_frame_num); + +WORD32 ih264d_get_next_display_field(dec_struct_t * ps_dec, + ivd_out_bufdesc_t *ps_out_buffer, + ivd_get_display_frame_op_t *pv_disp_op); + +void ih264d_release_display_field(dec_struct_t *ps_dec, + ivd_get_display_frame_op_t *pv_disp_op); +void ih264d_close_video_decoder(iv_obj_t *iv_obj_t); +WORD32 ih264d_get_dpb_size(dec_seq_params_t *ps_seq); +WORD32 ih264d_get_next_nal_unit(UWORD8 *pu1_buf, + UWORD32 u4_cur_pos, + UWORD32 u4_max_ofst, + UWORD32 *pu4_length_of_start_code); + +WORD16 ih264d_free_dynamic_bufs(dec_struct_t * ps_dec); +#endif /* _IH264D_UTILS_H_ */ diff --git a/dependencies/ih264d/decoder/ih264d_vui.c b/dependencies/ih264d/decoder/ih264d_vui.c new file mode 100644 index 00000000..15f1c007 --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_vui.c @@ -0,0 +1,248 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ + +/*****************************************************************************/ +/* */ +/* File Name : ih264d_vui.c */ +/* */ +/* Description : This file contains routines to parse VUI NAL's */ +/* */ +/* List of Functions : <List the functions defined in this file> */ +/* */ +/* Issues / Problems : None */ +/* */ +/* Revision History : */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 25 05 2005 NS Draft */ +/* */ +/*****************************************************************************/ + +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264d_vui.h" +#include "ih264d_bitstrm.h" +#include "ih264d_parse_cavlc.h" +#include "ih264d_structs.h" +#include "ih264d_error_handler.h" + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_parse_hrd_parametres */ +/* */ +/* Description : This function parses hrd_t parametres */ +/* Inputs : ps_hrd pointer to HRD params */ +/* ps_bitstrm Bitstream */ +/* Globals : None */ +/* Processing : Parses HRD params */ +/* Outputs : None */ +/* Returns : None */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 06 05 2002 NS Draft */ +/* */ +/*****************************************************************************/ + +WORD32 ih264d_parse_hrd_parametres(hrd_t *ps_hrd, + dec_bit_stream_t *ps_bitstrm) +{ + UWORD8 u1_index; + UWORD32 *pu4_bitstrm_ofst = &ps_bitstrm->u4_ofst; + UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; + + ps_hrd->u4_cpb_cnt = 1 + + ih264d_uev(pu4_bitstrm_ofst, pu4_bitstrm_buf); + if(ps_hrd->u4_cpb_cnt > 31) + return ERROR_INV_SPS_PPS_T; + ps_hrd->u1_bit_rate_scale = ih264d_get_bits_h264(ps_bitstrm, 4); + ps_hrd->u1_cpb_size_scale = ih264d_get_bits_h264(ps_bitstrm, 4); + + for(u1_index = 0; u1_index < (UWORD8)ps_hrd->u4_cpb_cnt; u1_index++) + { + ps_hrd->u4_bit_rate[u1_index] = 1 + + ih264d_uev(pu4_bitstrm_ofst, + pu4_bitstrm_buf); + ps_hrd->u4_cpb_size[u1_index] = 1 + + ih264d_uev(pu4_bitstrm_ofst, + pu4_bitstrm_buf); + ps_hrd->u1_cbr_flag[u1_index] = ih264d_get_bits_h264(ps_bitstrm, 1); + } + + ps_hrd->u1_initial_cpb_removal_delay = 1 + + ih264d_get_bits_h264(ps_bitstrm, 5); + ps_hrd->u1_cpb_removal_delay_length = 1 + + ih264d_get_bits_h264(ps_bitstrm, 5); + ps_hrd->u1_dpb_output_delay_length = 1 + + ih264d_get_bits_h264(ps_bitstrm, 5); + ps_hrd->u1_time_offset_length = ih264d_get_bits_h264(ps_bitstrm, 5); + + return OK; +} + +/*****************************************************************************/ +/* */ +/* Function Name : ih264d_parse_vui_parametres */ +/* */ +/* Description : This function parses VUI NALs. */ +/* Inputs : ps_vu4 pointer to VUI params */ +/* ps_bitstrm Bitstream */ +/* Globals : None */ +/* Processing : Parses VUI NAL's units and stores the info */ +/* Outputs : None */ +/* Returns : None */ +/* */ +/* Issues : None */ +/* */ +/* Revision History: */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 06 05 2002 NS Draft */ +/* */ +/*****************************************************************************/ + +WORD32 ih264d_parse_vui_parametres(vui_t *ps_vu4, + dec_bit_stream_t *ps_bitstrm) +{ + UWORD8 u4_bits; + UWORD32 *pu4_bitstrm_ofst = &ps_bitstrm->u4_ofst; + UWORD32 *pu4_bitstrm_buf = ps_bitstrm->pu4_buffer; + WORD32 ret; + + u4_bits = ih264d_get_bits_h264(ps_bitstrm, 1); + if(u4_bits) + { + u4_bits = ih264d_get_bits_h264(ps_bitstrm, 8); + ps_vu4->u1_aspect_ratio_idc = (UWORD8)u4_bits; + if(VUI_EXTENDED_SAR == u4_bits) + { + ps_vu4->u2_sar_width = ih264d_get_bits_h264(ps_bitstrm, 16); + ps_vu4->u2_sar_height = ih264d_get_bits_h264(ps_bitstrm, 16); + } + } + + u4_bits = ih264d_get_bits_h264(ps_bitstrm, 1); + if(u4_bits) + { + ps_vu4->u1_overscan_appropriate_flag = ih264d_get_bits_h264( + ps_bitstrm, 1); + } + u4_bits = ih264d_get_bits_h264(ps_bitstrm, 1); + /* Initialize to unspecified (5 for video_format and + 2 for colour_primaries, tfr_chars, matrix_coeffs */ + ps_vu4->u1_video_format = 5; + ps_vu4->u1_video_full_range_flag = 0; + ps_vu4->u1_colour_primaries = 2; + ps_vu4->u1_tfr_chars = 2; + ps_vu4->u1_matrix_coeffs = 2; + + if(u4_bits) + { + ps_vu4->u1_video_format = ih264d_get_bits_h264(ps_bitstrm, 3); + ps_vu4->u1_video_full_range_flag = ih264d_get_bits_h264(ps_bitstrm, + 1); + u4_bits = ih264d_get_bits_h264(ps_bitstrm, 1); + if(u4_bits) + { + ps_vu4->u1_colour_primaries = ih264d_get_bits_h264(ps_bitstrm, + 8); + ps_vu4->u1_tfr_chars = ih264d_get_bits_h264(ps_bitstrm, 8); + ps_vu4->u1_matrix_coeffs = ih264d_get_bits_h264(ps_bitstrm, 8); + } + } + + u4_bits = ih264d_get_bits_h264(ps_bitstrm, 1); + if(u4_bits) + { + ps_vu4->u1_cr_top_field = ih264d_uev(pu4_bitstrm_ofst, + pu4_bitstrm_buf); + ps_vu4->u1_cr_bottom_field = ih264d_uev(pu4_bitstrm_ofst, + pu4_bitstrm_buf); + } + + u4_bits = ih264d_get_bits_h264(ps_bitstrm, 1); + if(u4_bits) + { + ps_vu4->u4_num_units_in_tick = ih264d_get_bits_h264(ps_bitstrm, 32); + ps_vu4->u4_time_scale = ih264d_get_bits_h264(ps_bitstrm, 32); + ps_vu4->u1_fixed_frame_rate_flag = ih264d_get_bits_h264(ps_bitstrm, + 1); + } + + u4_bits = ih264d_get_bits_h264(ps_bitstrm, 1); + ps_vu4->u1_nal_hrd_params_present = u4_bits; + if(u4_bits) + { + ret = ih264d_parse_hrd_parametres(&ps_vu4->s_nal_hrd, ps_bitstrm); + if(ret != OK) + return ret; + } + u4_bits = ih264d_get_bits_h264(ps_bitstrm, 1); + ps_vu4->u1_vcl_hrd_params_present = u4_bits; + if(u4_bits) + { + ret = ih264d_parse_hrd_parametres(&ps_vu4->s_vcl_hrd, ps_bitstrm); + if(ret != OK) + return ret; + } + + if(ps_vu4->u1_nal_hrd_params_present || u4_bits) + { + ps_vu4->u1_low_delay_hrd_flag = ih264d_get_bits_h264(ps_bitstrm, 1); + } + ps_vu4->u1_pic_struct_present_flag = ih264d_get_bits_h264(ps_bitstrm, 1); + + ps_vu4->u1_bitstream_restriction_flag = ih264d_get_bits_h264(ps_bitstrm, 1); + + if(ps_vu4->u1_bitstream_restriction_flag) + { + ps_vu4->u1_mv_over_pic_boundaries_flag = ih264d_get_bits_h264( + ps_bitstrm, 1); + ps_vu4->u4_max_bytes_per_pic_denom = ih264d_uev(pu4_bitstrm_ofst, + pu4_bitstrm_buf); + ps_vu4->u4_max_bits_per_mb_denom = ih264d_uev(pu4_bitstrm_ofst, + pu4_bitstrm_buf); + ps_vu4->u4_log2_max_mv_length_horz = ih264d_uev(pu4_bitstrm_ofst, + pu4_bitstrm_buf); + ps_vu4->u4_log2_max_mv_length_vert = ih264d_uev(pu4_bitstrm_ofst, + pu4_bitstrm_buf); + ps_vu4->u4_num_reorder_frames = ih264d_uev(pu4_bitstrm_ofst, + pu4_bitstrm_buf); + ps_vu4->u4_max_dec_frame_buffering = ih264d_uev(pu4_bitstrm_ofst, + pu4_bitstrm_buf); + if((ps_vu4->u4_max_dec_frame_buffering > (H264_MAX_REF_PICS * 2)) || + (ps_vu4->u4_num_reorder_frames > ps_vu4->u4_max_dec_frame_buffering)) + { + return ERROR_INV_SPS_PPS_T; + } + } + else + { + /* Setting this to a large value if not present */ + ps_vu4->u4_num_reorder_frames = 64; + ps_vu4->u4_max_dec_frame_buffering = 64; + } + + return OK; +} diff --git a/dependencies/ih264d/decoder/ih264d_vui.h b/dependencies/ih264d/decoder/ih264d_vui.h new file mode 100644 index 00000000..b882f724 --- /dev/null +++ b/dependencies/ih264d/decoder/ih264d_vui.h @@ -0,0 +1,97 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ + +/*****************************************************************************/ +/* */ +/* File Name : ih264d_vui.h */ +/* */ +/* Description : This file contains routines to parse SEI NAL's */ +/* */ +/* List of Functions : <List the functions defined in this file> */ +/* */ +/* Issues / Problems : None */ +/* */ +/* Revision History : */ +/* */ +/* DD MM YYYY Author(s) Changes (Describe the changes made) */ +/* 25 05 2005 NS Draft */ +/* */ +/*****************************************************************************/ + +#ifndef _IH264D_VUI_H_ +#define _IH264D_VUI_H_ + +#include "ih264_typedefs.h" +#include "ih264_macros.h" +#include "ih264_platform_macros.h" +#include "ih264d_bitstrm.h" + +#define VUI_EXTENDED_SAR 255 + +typedef struct +{ + UWORD32 u4_cpb_cnt; + UWORD8 u1_bit_rate_scale; + UWORD8 u1_cpb_size_scale; + UWORD32 u4_bit_rate[32]; + UWORD32 u4_cpb_size[32]; + UWORD8 u1_cbr_flag[32]; + UWORD8 u1_initial_cpb_removal_delay; + UWORD8 u1_cpb_removal_delay_length; + UWORD8 u1_dpb_output_delay_length; + UWORD8 u1_time_offset_length; +} hrd_t; + +typedef struct +{ + UWORD8 u1_aspect_ratio_idc; + UWORD16 u2_sar_width; + UWORD16 u2_sar_height; + UWORD8 u1_overscan_appropriate_flag; + UWORD8 u1_video_format; + UWORD8 u1_video_full_range_flag; + UWORD8 u1_colour_primaries; + UWORD8 u1_tfr_chars; + UWORD8 u1_matrix_coeffs; + UWORD8 u1_cr_top_field; + UWORD8 u1_cr_bottom_field; + UWORD32 u4_num_units_in_tick; + UWORD32 u4_time_scale; + UWORD8 u1_fixed_frame_rate_flag; + UWORD8 u1_nal_hrd_params_present; + hrd_t s_nal_hrd; + UWORD8 u1_vcl_hrd_params_present; + hrd_t s_vcl_hrd; + UWORD8 u1_low_delay_hrd_flag; + UWORD8 u1_pic_struct_present_flag; + UWORD8 u1_bitstream_restriction_flag; + UWORD8 u1_mv_over_pic_boundaries_flag; + UWORD32 u4_max_bytes_per_pic_denom; + UWORD32 u4_max_bits_per_mb_denom; + UWORD32 u4_log2_max_mv_length_horz; + UWORD32 u4_log2_max_mv_length_vert; + UWORD32 u4_num_reorder_frames; + UWORD32 u4_max_dec_frame_buffering; +} vui_t; + +WORD32 ih264d_parse_vui_parametres(vui_t *ps_vu4, + dec_bit_stream_t *ps_bitstrm); +#endif /* _SEI_H_ */ + diff --git a/dependencies/ih264d/decoder/iv.h b/dependencies/ih264d/decoder/iv.h new file mode 100644 index 00000000..a963f938 --- /dev/null +++ b/dependencies/ih264d/decoder/iv.h @@ -0,0 +1,239 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore + * Modified for use with Cemu emulator project +*/ +/** +******************************************************************************* +* @file +* iv.h +* +* @brief +* This file contains all the necessary structure and enumeration +* definitions needed for the Application Program Interface(API) of the +* Ittiam Video and Image codecs +* +* @author +* 100239(RCY) +* +* @par List of Functions: +* +* @remarks +* None +* +******************************************************************************* +*/ + + +#ifndef _IV_H +#define _IV_H + +/*****************************************************************************/ +/* Constant Macros */ +/*****************************************************************************/ + + +/*****************************************************************************/ +/* Typedefs */ +/*****************************************************************************/ + +/*****************************************************************************/ +/* Enums */ +/*****************************************************************************/ + + +/* IV_API_CALL_STATUS_T:This is only to return the FAIL/PASS status to the */ +/* application for the current API call */ + +typedef enum { + IV_STATUS_NA = 0x7FFFFFFF, + IV_SUCCESS = 0x0, + IV_FAIL = 0x1, +}IV_API_CALL_STATUS_T; + + +/* IV_COLOR_FORMAT_T: This enumeration lists all the color formats which */ +/* finds usage in video/image codecs */ + +typedef enum { + IV_CHROMA_NA = 0x7FFFFFFF, + IV_YUV_420P = 0x1, + IV_YUV_422P = 0x2, + IV_420_UV_INTL = 0x3, + IV_YUV_422IBE = 0x4, + IV_YUV_422ILE = 0x5, + IV_YUV_444P = 0x6, + IV_YUV_411P = 0x7, + IV_GRAY = 0x8, + IV_RGB_565 = 0x9, + IV_RGB_24 = 0xa, + IV_YUV_420SP_UV = 0xb, + IV_YUV_420SP_VU = 0xc, + IV_RGBA_8888 = 0xd +}IV_COLOR_FORMAT_T; + +/* IV_PICTURE_CODING_TYPE_T: VOP/Frame coding type Enumeration */ + +typedef enum { + IV_NA_FRAME = 0x7FFFFFFF, + IV_I_FRAME = 0x0, + IV_P_FRAME = 0x1, + IV_B_FRAME = 0x2, + IV_IDR_FRAME = 0x3, + IV_II_FRAME = 0x4, + IV_IP_FRAME = 0x5, + IV_IB_FRAME = 0x6, + IV_PI_FRAME = 0x7, + IV_PP_FRAME = 0x8, + IV_PB_FRAME = 0x9, + IV_BI_FRAME = 0xa, + IV_BP_FRAME = 0xb, + IV_BB_FRAME = 0xc, + IV_MBAFF_I_FRAME = 0xd, + IV_MBAFF_P_FRAME = 0xe, + IV_MBAFF_B_FRAME = 0xf, + IV_MBAFF_IDR_FRAME = 0x10, + IV_NOT_CODED_FRAME = 0x11, + IV_FRAMETYPE_DEFAULT = IV_I_FRAME +}IV_PICTURE_CODING_TYPE_T; + +/* IV_FLD_TYPE_T: field type Enumeration */ + +typedef enum { + IV_NA_FLD = 0x7FFFFFFF, + IV_TOP_FLD = 0x0, + IV_BOT_FLD = 0x1, + IV_FLD_TYPE_DEFAULT = IV_TOP_FLD +}IV_FLD_TYPE_T; + +/* IV_CONTENT_TYPE_T: Video content type */ + +typedef enum { + IV_CONTENTTYPE_NA = 0x7FFFFFFF, + IV_PROGRESSIVE = 0x0, + IV_INTERLACED = 0x1, + IV_PROGRESSIVE_FRAME = 0x2, + IV_INTERLACED_FRAME = 0x3, + IV_INTERLACED_TOPFIELD = 0x4, + IV_INTERLACED_BOTTOMFIELD = 0x5, + IV_CONTENTTYPE_DEFAULT = IV_PROGRESSIVE, +}IV_CONTENT_TYPE_T; + +/* IV_API_COMMAND_TYPE_T:API command type */ +typedef enum { + IV_CMD_NA = 0x7FFFFFFF, + IV_CMD_DUMMY_ELEMENT = 0x4, +}IV_API_COMMAND_TYPE_T; + +/*****************************************************************************/ +/* Structure */ +/*****************************************************************************/ + +/* IV_OBJ_T: This structure defines the handle for the codec instance */ + +typedef struct { + /** + * u4_size of the structure + */ + UWORD32 u4_size; + + /** + * Pointer to the API function pointer table of the codec + */ + void *pv_fxns; + + /** + * Pointer to the handle of the codec + */ + void *pv_codec_handle; +}iv_obj_t; + + +/* IV_YUV_BUF_T: This structure defines attributes for the yuv buffer */ + +typedef struct { + /** + * u4_size of the structure + */ + UWORD32 u4_size; + + /** + * Pointer to Luma (Y) Buffer + */ + + void *pv_y_buf; + /** + * Pointer to Chroma (Cb) Buffer + */ + void *pv_u_buf; + + /** + * Pointer to Chroma (Cr) Buffer + */ + void *pv_v_buf; + + /** + * Width of the Luma (Y) Buffer + */ + UWORD32 u4_y_wd; + + /** + * Height of the Luma (Y) Buffer + */ + UWORD32 u4_y_ht; + + /** + * Stride/Pitch of the Luma (Y) Buffer + */ + UWORD32 u4_y_strd; + + /** + * Width of the Chroma (Cb) Buffer + */ + UWORD32 u4_u_wd; + + /** + * Height of the Chroma (Cb) Buffer + */ + UWORD32 u4_u_ht; + + /** + * Stride/Pitch of the Chroma (Cb) Buffer + */ + UWORD32 u4_u_strd; + + /** + * Width of the Chroma (Cr) Buffer + */ + UWORD32 u4_v_wd; + + /** + * Height of the Chroma (Cr) Buffer + */ + UWORD32 u4_v_ht; + + /** + * Stride/Pitch of the Chroma (Cr) Buffer + */ + UWORD32 u4_v_strd; +}iv_yuv_buf_t; + + + +#endif /* _IV_H */ + diff --git a/dependencies/ih264d/decoder/ivd.h b/dependencies/ih264d/decoder/ivd.h new file mode 100644 index 00000000..8419e8e9 --- /dev/null +++ b/dependencies/ih264d/decoder/ivd.h @@ -0,0 +1,1052 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** +******************************************************************************* +* @file +* ivd.h +* +* @brief +* This file contains all the necessary structure and enumeration +* definitions needed for the Application Program Interface(API) of the +* Ittiam Video Decoders +* +* @author +* 100239(RCY) +* +* @remarks +* None +* +******************************************************************************* +*/ + +#ifndef _IVD_H +#define _IVD_H + +/*****************************************************************************/ +/* Constant Macros */ +/*****************************************************************************/ +#define IVD_VIDDEC_MAX_IO_BUFFERS 64 + +/** SEI macros */ +/* + * @brief specifies the number of colour primary components of the mastering display + */ +#define NUM_SEI_MDCV_PRIMARIES 3 + +/* + * @brief specifies the number of colour primary components of the nominal content colour volume + */ +#define NUM_SEI_CCV_PRIMARIES 3 + +/*****************************************************************************/ +/* Typedefs */ +/*****************************************************************************/ + +/*****************************************************************************/ +/* Enums */ +/*****************************************************************************/ + +/* IVD_ARCH_T: Architecture Enumeration */ +typedef enum +{ + ARCH_NA = 0x7FFFFFFF, + ARCH_ARM_NONEON = 0x0, + ARCH_ARM_A9Q, + ARCH_ARM_A9A, + ARCH_ARM_A9, + ARCH_ARM_A7, + ARCH_ARM_A5, + ARCH_ARM_A15, + ARCH_ARM_NEONINTR, + ARCH_ARMV8_GENERIC, + ARCH_X86_GENERIC = 0x100, + ARCH_X86_SSSE3, + ARCH_X86_SSE42, + ARCH_X86_AVX2, + ARCH_MIPS_GENERIC = 0x200, + ARCH_MIPS_32 +}IVD_ARCH_T; + +/* IVD_SOC_T: SOC Enumeration */ +typedef enum +{ + SOC_NA = 0x7FFFFFFF, + SOC_GENERIC = 0x0, + SOC_HISI_37X = 0x100, +}IVD_SOC_T; + +/* IVD_FRAME_SKIP_MODE_T:Skip mode Enumeration */ + +typedef enum { + IVD_SKIP_NONE = 0x7FFFFFFF, + IVD_SKIP_P = 0x1, + IVD_SKIP_B = 0x2, + IVD_SKIP_I = 0x3, + IVD_SKIP_IP = 0x4, + IVD_SKIP_IB = 0x5, + IVD_SKIP_PB = 0x6, + IVD_SKIP_IPB = 0x7, + IVD_SKIP_IDR = 0x8, + IVD_SKIP_DEFAULT = IVD_SKIP_NONE, +}IVD_FRAME_SKIP_MODE_T; + +/* IVD_VIDEO_DECODE_MODE_T: Set decoder to decode either frame worth of data */ +/* or only header worth of data */ + +typedef enum { + IVD_DECODE_MODE_NA = 0x7FFFFFFF, + + /* This enables the codec to process all decodable units */ + IVD_DECODE_FRAME = 0x0, + + /* This enables the codec to decode header only */ + IVD_DECODE_HEADER = 0x1, + + + +}IVD_VIDEO_DECODE_MODE_T; + + +/* IVD_DISPLAY_FRAME_OUT_MODE_T: Video Display Frame Output Mode */ + +typedef enum { + + IVD_DISPLAY_ORDER_NA = 0x7FFFFFFF, + /* To set codec to fill output buffers in display order */ + IVD_DISPLAY_FRAME_OUT = 0x0, + + /* To set codec to fill output buffers in decode order */ + IVD_DECODE_FRAME_OUT = 0x1, +}IVD_DISPLAY_FRAME_OUT_MODE_T; + + +/* IVD_API_COMMAND_TYPE_T:API command type */ +typedef enum { + IVD_CMD_VIDEO_NA = 0x7FFFFFFF, + IVD_CMD_CREATE = IV_CMD_DUMMY_ELEMENT + 1, + IVD_CMD_DELETE, + IVD_CMD_VIDEO_CTL, + IVD_CMD_VIDEO_DECODE, + IVD_CMD_GET_DISPLAY_FRAME, + IVD_CMD_REL_DISPLAY_FRAME, + IVD_CMD_SET_DISPLAY_FRAME +}IVD_API_COMMAND_TYPE_T; + +/* IVD_CONTROL_API_COMMAND_TYPE_T: Video Control API command type */ + +typedef enum { + IVD_CMD_NA = 0x7FFFFFFF, + IVD_CMD_CTL_GETPARAMS = 0x0, + IVD_CMD_CTL_SETPARAMS = 0x1, + IVD_CMD_CTL_RESET = 0x2, + IVD_CMD_CTL_SETDEFAULT = 0x3, + IVD_CMD_CTL_FLUSH = 0x4, + IVD_CMD_CTL_GETBUFINFO = 0x5, + IVD_CMD_CTL_GETVERSION = 0x6, + IVD_CMD_CTL_CODEC_SUBCMD_START = 0x7 +}IVD_CONTROL_API_COMMAND_TYPE_T; + + +/* IVD_ERROR_BITS_T: A UWORD32 container will be used for reporting the error*/ +/* code to the application. The first 8 bits starting from LSB have been */ +/* reserved for the codec to report internal error details. The rest of the */ +/* bits will be generic for all video decoders and each bit has an associated*/ +/* meaning as mentioned below. The unused bit fields are reserved for future */ +/* extenstions and will be zero in the current implementation */ + +typedef enum { + /* Bit 8 - Applied concealment. */ + IVD_APPLIEDCONCEALMENT = 0x8, + /* Bit 9 - Insufficient input data. */ + IVD_INSUFFICIENTDATA = 0x9, + /* Bit 10 - Data problem/corruption. */ + IVD_CORRUPTEDDATA = 0xa, + /* Bit 11 - Header problem/corruption. */ + IVD_CORRUPTEDHEADER = 0xb, + /* Bit 12 - Unsupported feature/parameter in input. */ + IVD_UNSUPPORTEDINPUT = 0xc, + /* Bit 13 - Unsupported input parameter orconfiguration. */ + IVD_UNSUPPORTEDPARAM = 0xd, + /* Bit 14 - Fatal error (stop the codec).If there is an */ + /* error and this bit is not set, the error is a recoverable one. */ + IVD_FATALERROR = 0xe, + /* Bit 15 - Invalid bitstream. Applies when Bitstream/YUV frame */ + /* buffer for encode/decode call is made with non-valid or zero u4_size */ + /* data */ + IVD_INVALID_BITSTREAM = 0xf, + /* Bit 16 */ + IVD_INCOMPLETE_BITSTREAM = 0x10, + IVD_ERROR_BITS_T_DUMMY_ELEMENT = 0x7FFFFFFF +}IVD_ERROR_BITS_T; + + +/* IVD_CONTROL_API_COMMAND_TYPE_T: Video Control API command type */ +typedef enum { + IVD_ERROR_NONE = 0x0, + IVD_NUM_MEM_REC_FAILED = 0x1, + IVD_NUM_REC_NOT_SUFFICIENT = 0x2, + IVD_FILL_MEM_REC_FAILED = 0x3, + IVD_REQUESTED_WIDTH_NOT_SUPPPORTED = 0x4, + IVD_REQUESTED_HEIGHT_NOT_SUPPPORTED = 0x5, + IVD_INIT_DEC_FAILED = 0x6, + IVD_INIT_DEC_NOT_SUFFICIENT = 0x7, + IVD_INIT_DEC_WIDTH_NOT_SUPPPORTED = 0x8, + IVD_INIT_DEC_HEIGHT_NOT_SUPPPORTED = 0x9, + IVD_INIT_DEC_MEM_NOT_ALIGNED = 0xa, + IVD_INIT_DEC_COL_FMT_NOT_SUPPORTED = 0xb, + IVD_INIT_DEC_MEM_REC_NOT_SUFFICIENT = 0xc, + IVD_GET_VERSION_DATABUFFER_SZ_INSUFFICIENT = 0xd, + IVD_BUFFER_SIZE_SET_TO_ZERO = 0xe, + IVD_UNEXPECTED_END_OF_STREAM = 0xf, + IVD_SEQUENCE_HEADER_NOT_DECODED = 0x10, + IVD_STREAM_WIDTH_HEIGHT_NOT_SUPPORTED = 0x11, + IVD_MAX_FRAME_LIMIT_REACHED = 0x12, + IVD_IP_API_STRUCT_SIZE_INCORRECT = 0x13, + IVD_OP_API_STRUCT_SIZE_INCORRECT = 0x14, + IVD_HANDLE_NULL = 0x15, + IVD_HANDLE_STRUCT_SIZE_INCORRECT = 0x16, + IVD_INVALID_HANDLE_NULL = 0x17, + IVD_INVALID_API_CMD = 0x18, + IVD_UNSUPPORTED_API_CMD = 0x19, + IVD_MEM_REC_STRUCT_SIZE_INCORRECT = 0x1a, + IVD_DISP_FRM_ZERO_OP_BUFS = 0x1b, + IVD_DISP_FRM_OP_BUF_NULL = 0x1c, + IVD_DISP_FRM_ZERO_OP_BUF_SIZE = 0x1d, + IVD_DEC_FRM_BS_BUF_NULL = 0x1e, + IVD_SET_CONFG_INVALID_DEC_MODE = 0x1f, + IVD_SET_CONFG_UNSUPPORTED_DISP_WIDTH = 0x20, + IVD_RESET_FAILED = 0x21, + IVD_INIT_DEC_MEM_REC_OVERLAP_ERR = 0x22, + IVD_INIT_DEC_MEM_REC_BASE_NULL = 0x23, + IVD_INIT_DEC_MEM_REC_ALIGNMENT_ERR = 0x24, + IVD_INIT_DEC_MEM_REC_INSUFFICIENT_SIZE = 0x25, + IVD_INIT_DEC_MEM_REC_INCORRECT_TYPE = 0x26, + IVD_DEC_NUMBYTES_INV = 0x27, + IVD_DEC_REF_BUF_NULL = 0x28, + IVD_DEC_FRM_SKIPPED = 0x29, + IVD_RES_CHANGED = 0x2a, + IVD_MEM_ALLOC_FAILED = 0x2b, + IVD_DUMMY_ELEMENT_FOR_CODEC_EXTENSIONS = 0xD0, +}IVD_ERROR_CODES_T; + + +/*****************************************************************************/ +/* Structure */ +/*****************************************************************************/ +/* structure for passing output buffers to codec during get display buffer */ +/* call */ +typedef struct { + + /** + * number of output buffers + */ + UWORD32 u4_num_bufs; + + /** + *list of pointers to output buffers + */ + UWORD8 *pu1_bufs[IVD_VIDDEC_MAX_IO_BUFFERS]; + + /** + * sizes of each output buffer + */ + UWORD32 u4_min_out_buf_size[IVD_VIDDEC_MAX_IO_BUFFERS]; + +}ivd_out_bufdesc_t; + +/*****************************************************************************/ +/* Create decoder */ +/*****************************************************************************/ + +/* IVD_API_COMMAND_TYPE_T::e_cmd = IVD_CMD_CREATE */ + + +typedef struct { + /** + * u4_size of the structure + */ + UWORD32 u4_size; + + /** + * e_cmd + */ + IVD_API_COMMAND_TYPE_T e_cmd; + + /** + * format in which codec has to give out frame data for display + */ + IV_COLOR_FORMAT_T e_output_format; + + /** + * Flag to indicate shared display buffer mode + */ + UWORD32 u4_share_disp_buf; + + /** + * Pointer to a function for aligned allocation. + */ + void *(*pf_aligned_alloc)(void *pv_mem_ctxt, WORD32 alignment, WORD32 size); + + /** + * Pointer to a function for aligned free. + */ + void (*pf_aligned_free)(void *pv_mem_ctxt, void *pv_buf); + + /** + * Pointer to memory context that is needed during alloc/free for custom + * memory managers. This will be passed as first argument to pf_aligned_alloc and + * pf_aligned_free. + * If application is using standard memory functions like + * malloc/aligned_malloc/memalign/free/aligned_free, + * then this is not needed and can be set to NULL + */ + void *pv_mem_ctxt; + +}ivd_create_ip_t; + + +typedef struct{ + /** + * u4_size of the structure + */ + UWORD32 u4_size; + + /** + * u4_error_code + */ + UWORD32 u4_error_code; + + /** + * Codec Handle + */ + void *pv_handle; + +}ivd_create_op_t; + + +/*****************************************************************************/ +/* Delete decoder */ +/*****************************************************************************/ + +/* IVD_API_COMMAND_TYPE_T::e_cmd = IVD_CMD_DELETE */ + + + +typedef struct { + /** + * u4_size of the structure + */ + UWORD32 u4_size; + + /** + * cmd + */ + IVD_API_COMMAND_TYPE_T e_cmd; + +}ivd_delete_ip_t; + + +typedef struct{ + /** + * u4_size of the structure + */ + UWORD32 u4_size; + + /** + * error_code + */ + UWORD32 u4_error_code; + +}ivd_delete_op_t; + +/*****************************************************************************/ +/* Video Decode */ +/*****************************************************************************/ + +/* SEI params deocde */ +typedef struct { + UWORD8 u1_sei_mdcv_params_present_flag; + + UWORD8 u1_sei_cll_params_present_flag; + + UWORD8 u1_sei_ave_params_present_flag; + + UWORD8 u1_sei_ccv_params_present_flag; + +}ivd_sei_decode_op_t; + +/* IVD_API_COMMAND_TYPE_T::e_cmd = IVD_CMD_VIDEO_DECODE */ + + +typedef struct { + /** + * u4_size of the structure + */ + UWORD32 u4_size; + + /** + * e_cmd + */ + IVD_API_COMMAND_TYPE_T e_cmd; + + /** + * u4_ts + */ + UWORD32 u4_ts; + + /** + * u4_num_Bytes + */ + UWORD32 u4_num_Bytes; + + /** + * pv_stream_buffer + */ + void *pv_stream_buffer; + + /** + * output buffer desc + */ + ivd_out_bufdesc_t s_out_buffer; + +}ivd_video_decode_ip_t; + + +typedef struct{ + /** + * u4_size of the structure + */ + UWORD32 u4_size; + + /** + * u4_error_code + */ + UWORD32 u4_error_code; + + /** + * num_bytes_consumed + */ + UWORD32 u4_num_bytes_consumed; + + /** + * pic_wd + */ + UWORD32 u4_pic_wd; + + /** + * pic_ht + */ + UWORD32 u4_pic_ht; + + /** + * pic_type + */ + IV_PICTURE_CODING_TYPE_T e_pic_type; + + /** + * frame_decoded_flag + */ + UWORD32 u4_frame_decoded_flag; + + /** + * new_seq + */ + UWORD32 u4_new_seq; + + /** + * output_present + */ + UWORD32 u4_output_present; + + /** + * progressive_frame_flag + */ + UWORD32 u4_progressive_frame_flag; + + /** + * is_ref_flag + */ + UWORD32 u4_is_ref_flag; + + /** + * output_format + */ + IV_COLOR_FORMAT_T e_output_format; + + /** + * disp_frm_buf + */ + iv_yuv_buf_t s_disp_frm_buf; + + /** + * sei params o/p struct + */ + ivd_sei_decode_op_t s_sei_decode_op; + + /** + * fld_type + */ + IV_FLD_TYPE_T e4_fld_type; + + /** + * ts + */ + UWORD32 u4_ts; + + /** + * disp_buf_id + */ + UWORD32 u4_disp_buf_id; + + /** + * reorder_depth + */ + WORD32 i4_reorder_depth; + + /** + * disp_buf_id + */ + WORD32 i4_display_index; + + /* crop info from SPS */ + UWORD8 u1_frame_cropping_flag; + UWORD8 u1_frame_cropping_rect_left_ofst; + UWORD8 u1_frame_cropping_rect_right_ofst; + UWORD8 u1_frame_cropping_rect_top_ofst; + UWORD8 u1_frame_cropping_rect_bottom_ofst; + + /* uncropped image size */ + UWORD32 u4_raw_wd; + UWORD32 u4_raw_ht; + +}ivd_video_decode_op_t; + + +/*****************************************************************************/ +/* Get Display Frame */ +/*****************************************************************************/ + + +/* IVD_API_COMMAND_TYPE_T::e_cmd = IVD_CMD_GET_DISPLAY_FRAME */ + +typedef struct +{ + /** + * u4_size of the structure + */ + UWORD32 u4_size; + + /** + * e_cmd + */ + IVD_API_COMMAND_TYPE_T e_cmd; + + /** + * output buffer desc + */ + ivd_out_bufdesc_t s_out_buffer; + +}ivd_get_display_frame_ip_t; + + +typedef struct +{ + /** + * u4_size of the structure + */ + UWORD32 u4_size; + + /** + * error_code + */ + UWORD32 u4_error_code; + + /** + * progressive_frame_flag + */ + UWORD32 u4_progressive_frame_flag; + + /** + * pic_type + */ + IV_PICTURE_CODING_TYPE_T e_pic_type; + + /** + * is_ref_flag + */ + UWORD32 u4_is_ref_flag; + + /** + * output_format + */ + IV_COLOR_FORMAT_T e_output_format; + + /** + * disp_frm_buf + */ + iv_yuv_buf_t s_disp_frm_buf; + + /** + * fld_type + */ + IV_FLD_TYPE_T e4_fld_type; + + /** + * ts + */ + UWORD32 u4_ts; + + /** + * disp_buf_id + */ + UWORD32 u4_disp_buf_id; +}ivd_get_display_frame_op_t; + +/*****************************************************************************/ +/* Set Display Frame */ +/*****************************************************************************/ + + +/* IVD_API_COMMAND_TYPE_T::e_cmd = IVD_CMD_SET_DISPLAY_FRAME */ + +typedef struct +{ + /** + * u4_size of the structure + */ + UWORD32 u4_size; + + /** + * cmd + */ + IVD_API_COMMAND_TYPE_T e_cmd; + + /** + * num_disp_bufs + */ + UWORD32 num_disp_bufs; + + /** + * output buffer desc + */ + ivd_out_bufdesc_t s_disp_buffer[IVD_VIDDEC_MAX_IO_BUFFERS]; + +}ivd_set_display_frame_ip_t; + + +typedef struct +{ + /** + * u4_size of the structure + */ + UWORD32 u4_size; + + /** + * error code + */ + UWORD32 u4_error_code; +}ivd_set_display_frame_op_t; + + +/*****************************************************************************/ +/* Release Display Frame */ +/*****************************************************************************/ + + +/* IVD_API_COMMAND_TYPE_T::e_cmd = IVD_CMD_SET_DISPLAY_FRAME */ + +typedef struct +{ + /** + * u4_size of the structure + */ + UWORD32 u4_size; + + /** + * e_cmd + */ + IVD_API_COMMAND_TYPE_T e_cmd; + + /** + * disp_buf_id + */ + UWORD32 u4_disp_buf_id; +}ivd_rel_display_frame_ip_t; + + +typedef struct +{ + /** + * u4_size of the structure + */ + UWORD32 u4_size; + + /** + * error code + */ + UWORD32 u4_error_code; +}ivd_rel_display_frame_op_t; + +/*****************************************************************************/ +/* Video control Flush */ +/*****************************************************************************/ +/* IVD_API_COMMAND_TYPE_T::e_cmd = IVD_CMD_VIDEO_CTL */ +/* IVD_CONTROL_API_COMMAND_TYPE_T::e_sub_cmd = IVD_CMD_ctl_FLUSH */ + + + +typedef struct{ + /** + * u4_size of the structure + */ + UWORD32 u4_size; + + /** + * cmd + */ + IVD_API_COMMAND_TYPE_T e_cmd; + + /** + * sub_cmd + */ + IVD_CONTROL_API_COMMAND_TYPE_T e_sub_cmd; +}ivd_ctl_flush_ip_t; + + +typedef struct{ + /** + * u4_size of the structure + */ + UWORD32 u4_size; + + /** + * error code + */ + UWORD32 u4_error_code; +}ivd_ctl_flush_op_t; + +/*****************************************************************************/ +/* Video control reset */ +/*****************************************************************************/ +/* IVD_API_COMMAND_TYPE_T::e_cmd = IVD_CMD_VIDEO_CTL */ +/* IVD_CONTROL_API_COMMAND_TYPE_T::e_sub_cmd = IVD_CMD_ctl_RESET */ + + +typedef struct{ + /** + * u4_size of the structure + */ + UWORD32 u4_size; + + /** + * cmd + */ + IVD_API_COMMAND_TYPE_T e_cmd; + + /** + * sub_cmd + */ + + IVD_CONTROL_API_COMMAND_TYPE_T e_sub_cmd; +}ivd_ctl_reset_ip_t; + + +typedef struct{ + /** + * u4_size of the structure + */ + UWORD32 u4_size; + + /** + * error code + */ + UWORD32 u4_error_code; +}ivd_ctl_reset_op_t; + + +/*****************************************************************************/ +/* Video control Set Params */ +/*****************************************************************************/ +/* IVD_API_COMMAND_TYPE_T::e_cmd = IVD_CMD_VIDEO_CTL */ +/* IVD_CONTROL_API_COMMAND_TYPE_T::e_sub_cmd=IVD_CMD_ctl_SETPARAMS */ +/* IVD_CONTROL_API_COMMAND_TYPE_T::e_sub_cmd=IVD_CMD_ctl_SETDEFAULT */ + + + +typedef struct { + /** + * u4_size of the structure + */ + UWORD32 u4_size; + + /** + * cmd + */ + IVD_API_COMMAND_TYPE_T e_cmd; + + /** + * sub_cmd + */ + IVD_CONTROL_API_COMMAND_TYPE_T e_sub_cmd; + + /** + * vid_dec_mode + */ + IVD_VIDEO_DECODE_MODE_T e_vid_dec_mode; + + /** + * disp_wd + */ + UWORD32 u4_disp_wd; + + /** + * frm_skip_mode + */ + IVD_FRAME_SKIP_MODE_T e_frm_skip_mode; + + /** + * frm_out_mode + */ + IVD_DISPLAY_FRAME_OUT_MODE_T e_frm_out_mode; +}ivd_ctl_set_config_ip_t; + + +typedef struct{ + /** + * u4_size of the structure + */ + UWORD32 u4_size; + + /** + * u4_error_code + */ + UWORD32 u4_error_code; +}ivd_ctl_set_config_op_t; + +/*****************************************************************************/ +/* Video control:Get Buf Info */ +/*****************************************************************************/ + +/* IVD_API_COMMAND_TYPE_T::e_cmd = IVD_CMD_VIDEO_CTL */ +/* IVD_CONTROL_API_COMMAND_TYPE_T::e_sub_cmd=IVD_CMD_ctl_GETBUFINFO */ + + +typedef struct{ + /** + * u4_size of the structure + */ + UWORD32 u4_size; + + /** + * e_cmd + */ + IVD_API_COMMAND_TYPE_T e_cmd; + + /** + * sub_cmd + */ + IVD_CONTROL_API_COMMAND_TYPE_T e_sub_cmd; +}ivd_ctl_getbufinfo_ip_t; + + +typedef struct{ + /** + * u4_size of the structure + */ + UWORD32 u4_size; + + /** + * error code + */ + UWORD32 u4_error_code; + + /** + * no of display buffer sets required by codec + */ + UWORD32 u4_num_disp_bufs; + + /** + * no of input buffers required for codec + */ + UWORD32 u4_min_num_in_bufs; + + /** + * no of output buffers required for codec + */ + UWORD32 u4_min_num_out_bufs; + + /** + * sizes of each input buffer required + */ + UWORD32 u4_min_in_buf_size[IVD_VIDDEC_MAX_IO_BUFFERS]; + + /** + * sizes of each output buffer required + */ + UWORD32 u4_min_out_buf_size[IVD_VIDDEC_MAX_IO_BUFFERS]; +}ivd_ctl_getbufinfo_op_t; + + +/*****************************************************************************/ +/* Video control:Getstatus Call */ +/*****************************************************************************/ + + +/* IVD_API_COMMAND_TYPE_T::e_cmd = IVD_CMD_VIDEO_CTL */ +/* IVD_CONTROL_API_COMMAND_TYPE_T::e_sub_cmd=IVD_CMD_ctl_GETPARAMS */ + + +typedef struct{ + /** + * u4_size of the structure + */ + UWORD32 u4_size; + + /** + * cmd + */ + IVD_API_COMMAND_TYPE_T e_cmd; + + /** + * sub_cmd + */ + IVD_CONTROL_API_COMMAND_TYPE_T e_sub_cmd; +}ivd_ctl_getstatus_ip_t; + + +typedef struct{ + + /** + * u4_size of the structure + */ + UWORD32 u4_size; + + /** + * error code + */ + UWORD32 u4_error_code; + + /** + * no of display buffer sets required by codec + */ + UWORD32 u4_num_disp_bufs; + + /** + * u4_pic_ht + */ + UWORD32 u4_pic_ht; + + /** + * u4_pic_wd + */ + UWORD32 u4_pic_wd; + + /** + * frame_rate + */ + UWORD32 u4_frame_rate; + + /** + * u4_bit_rate + */ + UWORD32 u4_bit_rate; + + /** + * content_type + */ + IV_CONTENT_TYPE_T e_content_type; + + /** + * output_chroma_format + */ + IV_COLOR_FORMAT_T e_output_chroma_format; + + /** + * no of input buffers required for codec + */ + UWORD32 u4_min_num_in_bufs; + + /** + * no of output buffers required for codec + */ + UWORD32 u4_min_num_out_bufs; + + /** + * sizes of each input buffer required + */ + UWORD32 u4_min_in_buf_size[IVD_VIDDEC_MAX_IO_BUFFERS]; + + /** + * sizes of each output buffer required + */ + UWORD32 u4_min_out_buf_size[IVD_VIDDEC_MAX_IO_BUFFERS]; +}ivd_ctl_getstatus_op_t; + + +/*****************************************************************************/ +/* Video control:Get Version Info */ +/*****************************************************************************/ + +/* IVD_API_COMMAND_TYPE_T::e_cmd = IVD_CMD_VIDEO_CTL */ +/* IVD_CONTROL_API_COMMAND_TYPE_T::e_sub_cmd=IVD_CMD_ctl_GETVERSION */ + + +typedef struct{ + /** + * u4_size of the structure + */ + UWORD32 u4_size; + + /** + * cmd + */ + IVD_API_COMMAND_TYPE_T e_cmd; + + /** + * sub_cmd + */ + IVD_CONTROL_API_COMMAND_TYPE_T e_sub_cmd; + + /** + * pv_version_buffer + */ + void *pv_version_buffer; + + /** + * version_buffer_size + */ + UWORD32 u4_version_buffer_size; +}ivd_ctl_getversioninfo_ip_t; + + +typedef struct{ + /** + * u4_size of the structure + */ + UWORD32 u4_size; + + /** + * error code + */ + UWORD32 u4_error_code; +}ivd_ctl_getversioninfo_op_t; + +#endif /* __IVD_H__ */ + diff --git a/dependencies/ih264d/decoder/mips/ih264d_function_selector.c b/dependencies/ih264d/decoder/mips/ih264d_function_selector.c new file mode 100644 index 00000000..13680ed8 --- /dev/null +++ b/dependencies/ih264d/decoder/mips/ih264d_function_selector.c @@ -0,0 +1,66 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** +******************************************************************************* +* @file +* imp2d_function_selector.c +* +* @brief +* Contains functions to initialize function pointers used in hevc +* +* @author +* Naveen +* +* @par List of Functions: +* @remarks +* None +* +******************************************************************************* +*/ +/*****************************************************************************/ +/* File Includes */ +/*****************************************************************************/ + +#include <stdio.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> + +/* User Include files */ +#include "ih264_typedefs.h" +#include "iv.h" +#include "ivd.h" +#include "ih264_defs.h" +#include "ih264_size_defs.h" +#include "ih264_error.h" +#include "ih264_trans_quant_itrans_iquant.h" +#include "ih264_inter_pred_filters.h" + +#include "ih264d_structs.h" +#include "ih264d_function_selector.h" + +void ih264d_init_function_ptr(dec_struct_t *ps_codec) +{ + ih264d_init_function_ptr_generic(ps_codec); +} +void ih264d_init_arch(dec_struct_t *ps_codec) +{ + ps_codec->e_processor_arch = ARCH_NA; +} diff --git a/dependencies/ih264d/decoder/x86/ih264d_function_selector.c b/dependencies/ih264d/decoder/x86/ih264d_function_selector.c new file mode 100644 index 00000000..8b4b0ca3 --- /dev/null +++ b/dependencies/ih264d/decoder/x86/ih264d_function_selector.c @@ -0,0 +1,108 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore + * Modified for use with Cemu emulator project +*/ +/** +******************************************************************************* +* @file +* imp2d_function_selector.c +* +* @brief +* Contains functions to initialize function pointers used in hevc +* +* @author +* Naveen +* +* @par List of Functions: +* @remarks +* None +* +******************************************************************************* +*/ +/*****************************************************************************/ +/* File Includes */ +/*****************************************************************************/ + +#include <stdio.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> + +/* User Include files */ +#include "ih264_typedefs.h" +#include "iv.h" +#include "ivd.h" +#include "ih264_defs.h" +#include "ih264_size_defs.h" +#include "ih264_error.h" +#include "ih264_trans_quant_itrans_iquant.h" +#include "ih264_inter_pred_filters.h" + +#include "ih264d_structs.h" +#include "ih264d_function_selector.h" + +void ih264d_init_function_ptr(dec_struct_t *ps_codec) +{ + + ih264d_init_function_ptr_generic(ps_codec); + switch(ps_codec->e_processor_arch) + { + case ARCH_X86_GENERIC: + ih264d_init_function_ptr_generic(ps_codec); + break; + case ARCH_X86_SSSE3: + ih264d_init_function_ptr_ssse3(ps_codec); + break; + case ARCH_X86_SSE42: + default: + ih264d_init_function_ptr_ssse3(ps_codec); + ih264d_init_function_ptr_sse42(ps_codec); + break; + } +} + +#ifdef __clang__ +#include <cpuid.h> + +void __cpuid2(signed int* cpuInfo, unsigned int level) +{ + __get_cpuid (level, (unsigned int*)cpuInfo+0, (unsigned int*)cpuInfo+1, (unsigned int*)cpuInfo+2, (unsigned int*)cpuInfo+3); +} + +#define __getCPUId __cpuid2 + +#else +#define __getCPUId __cpuid +#endif + +void ih264d_init_arch(dec_struct_t *ps_codec) +{ + int cpuInfo[4]; + UWORD8 hasSSSE3; + UWORD8 hasSSE4_2; + __getCPUId(cpuInfo, 0x1); + hasSSSE3 = ((cpuInfo[2] >> 9) & 1); + hasSSE4_2 = ((cpuInfo[2] >> 20) & 1); + if (hasSSE4_2 != 0) + ps_codec->e_processor_arch = ARCH_X86_SSE42; + else if (hasSSSE3 != 0) + ps_codec->e_processor_arch = ARCH_X86_SSSE3; + else + ps_codec->e_processor_arch = ARCH_X86_GENERIC; +} diff --git a/dependencies/ih264d/decoder/x86/ih264d_function_selector_sse42.c b/dependencies/ih264d/decoder/x86/ih264d_function_selector_sse42.c new file mode 100644 index 00000000..0c493d22 --- /dev/null +++ b/dependencies/ih264d/decoder/x86/ih264d_function_selector_sse42.c @@ -0,0 +1,95 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** +******************************************************************************* +* @file +* ih264e_function_selector_generic.c +* +* @brief +* Contains functions to initialize function pointers of codec context +* +* @author +* Ittiam +* +* @par List of Functions: +* - ih264e_init_function_ptr_generic +* +* @remarks +* None +* +******************************************************************************* +*/ + + +/*****************************************************************************/ +/* File Includes */ +/*****************************************************************************/ + +/* System Include files */ +#include <stdio.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> + +/* User Include files */ +#include "ih264_typedefs.h" +#include "iv.h" +#include "ivd.h" +#include "ih264_defs.h" +#include "ih264_size_defs.h" +#include "ih264_error.h" +#include "ih264_trans_quant_itrans_iquant.h" +#include "ih264_inter_pred_filters.h" + +#include "ih264d_structs.h" + + +/** +******************************************************************************* +* +* @brief Initialize the intra/inter/transform/deblk function pointers of +* codec context +* +* @par Description: the current routine initializes the function pointers of +* codec context basing on the architecture in use +* +* @param[in] ps_codec +* Codec context pointer +* +* @returns none +* +* @remarks none +* +******************************************************************************* +*/ +void ih264d_init_function_ptr_sse42(dec_struct_t *ps_codec) +{ + ps_codec->pf_default_weighted_pred_luma = ih264_default_weighted_pred_luma_sse42; + ps_codec->pf_default_weighted_pred_chroma = ih264_default_weighted_pred_chroma_sse42; + ps_codec->pf_weighted_pred_luma = ih264_weighted_pred_luma_sse42; + ps_codec->pf_weighted_pred_chroma = ih264_weighted_pred_chroma_sse42; + ps_codec->pf_weighted_bi_pred_luma = ih264_weighted_bi_pred_luma_sse42; + ps_codec->pf_weighted_bi_pred_chroma = ih264_weighted_bi_pred_chroma_sse42; + + ps_codec->pf_iquant_itrans_recon_luma_4x4 = ih264_iquant_itrans_recon_4x4_sse42; + ps_codec->pf_iquant_itrans_recon_chroma_4x4 = ih264_iquant_itrans_recon_chroma_4x4_sse42; + ps_codec->pf_ihadamard_scaling_4x4 = ih264_ihadamard_scaling_4x4_sse42; + return; +} diff --git a/dependencies/ih264d/decoder/x86/ih264d_function_selector_ssse3.c b/dependencies/ih264d/decoder/x86/ih264d_function_selector_ssse3.c new file mode 100644 index 00000000..17862139 --- /dev/null +++ b/dependencies/ih264d/decoder/x86/ih264d_function_selector_ssse3.c @@ -0,0 +1,181 @@ +/****************************************************************************** + * + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ***************************************************************************** + * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore +*/ +/** +******************************************************************************* +* @file +* ih264e_function_selector_generic.c +* +* @brief +* Contains functions to initialize function pointers of codec context +* +* @author +* Ittiam +* +* @par List of Functions: +* - ih264e_init_function_ptr_generic +* +* @remarks +* None +* +******************************************************************************* +*/ + + +/*****************************************************************************/ +/* File Includes */ +/*****************************************************************************/ + +/* System Include files */ +#include <stdio.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> + +/* User Include files */ +#include "ih264_typedefs.h" +#include "iv.h" +#include "ivd.h" +#include "ih264_defs.h" +#include "ih264_size_defs.h" +#include "ih264_error.h" +#include "ih264_trans_quant_itrans_iquant.h" +#include "ih264_inter_pred_filters.h" + +#include "ih264d_structs.h" + + +/** +******************************************************************************* +* +* @brief Initialize the intra/inter/transform/deblk function pointers of +* codec context +* +* @par Description: the current routine initializes the function pointers of +* codec context basing on the architecture in use +* +* @param[in] ps_codec +* Codec context pointer +* +* @returns none +* +* @remarks none +* +******************************************************************************* +*/ +void ih264d_init_function_ptr_ssse3(dec_struct_t *ps_codec) +{ + + + + /* Init function pointers for intra pred leaf level functions luma + * Intra 16x16 */ + ps_codec->apf_intra_pred_luma_16x16[0] = ih264_intra_pred_luma_16x16_mode_vert_ssse3; + ps_codec->apf_intra_pred_luma_16x16[1] = ih264_intra_pred_luma_16x16_mode_horz_ssse3; + ps_codec->apf_intra_pred_luma_16x16[2] = ih264_intra_pred_luma_16x16_mode_dc_ssse3; + ps_codec->apf_intra_pred_luma_16x16[3] = ih264_intra_pred_luma_16x16_mode_plane_ssse3; + + /* Init function pointers for intra pred leaf level functions luma + * Intra 4x4 */ + ps_codec->apf_intra_pred_luma_4x4[0] = ih264_intra_pred_luma_4x4_mode_vert_ssse3; + ps_codec->apf_intra_pred_luma_4x4[1] = ih264_intra_pred_luma_4x4_mode_horz_ssse3; + ps_codec->apf_intra_pred_luma_4x4[2] = ih264_intra_pred_luma_4x4_mode_dc_ssse3; + ps_codec->apf_intra_pred_luma_4x4[3] = ih264_intra_pred_luma_4x4_mode_diag_dl_ssse3; + ps_codec->apf_intra_pred_luma_4x4[4] = ih264_intra_pred_luma_4x4_mode_diag_dr_ssse3; + ps_codec->apf_intra_pred_luma_4x4[5] = ih264_intra_pred_luma_4x4_mode_vert_r_ssse3; + ps_codec->apf_intra_pred_luma_4x4[6] = ih264_intra_pred_luma_4x4_mode_horz_d_ssse3; + ps_codec->apf_intra_pred_luma_4x4[7] = ih264_intra_pred_luma_4x4_mode_vert_l_ssse3; + ps_codec->apf_intra_pred_luma_4x4[8] = ih264_intra_pred_luma_4x4_mode_horz_u_ssse3; + + /* Init function pointers for intra pred leaf level functions luma + * Intra 8x8 */ + ps_codec->apf_intra_pred_luma_8x8[0] = ih264_intra_pred_luma_8x8_mode_vert_ssse3; + ps_codec->apf_intra_pred_luma_8x8[1] = ih264_intra_pred_luma_8x8_mode_horz_ssse3; + ps_codec->apf_intra_pred_luma_8x8[2] = ih264_intra_pred_luma_8x8_mode_dc_ssse3; + ps_codec->apf_intra_pred_luma_8x8[3] = ih264_intra_pred_luma_8x8_mode_diag_dl_ssse3; + ps_codec->apf_intra_pred_luma_8x8[4] = ih264_intra_pred_luma_8x8_mode_diag_dr_ssse3; + ps_codec->apf_intra_pred_luma_8x8[5] = ih264_intra_pred_luma_8x8_mode_vert_r_ssse3; + ps_codec->apf_intra_pred_luma_8x8[6] = ih264_intra_pred_luma_8x8_mode_horz_d_ssse3; + ps_codec->apf_intra_pred_luma_8x8[7] = ih264_intra_pred_luma_8x8_mode_vert_l_ssse3; + ps_codec->apf_intra_pred_luma_8x8[8] = ih264_intra_pred_luma_8x8_mode_horz_u_ssse3; + + ps_codec->pf_intra_pred_ref_filtering = ih264_intra_pred_luma_8x8_mode_ref_filtering; + + /* Init function pointers for intra pred leaf level functions chroma + * Intra 8x8 */ + ps_codec->apf_intra_pred_chroma[0] = ih264_intra_pred_chroma_8x8_mode_vert_ssse3; + ps_codec->apf_intra_pred_chroma[1] = ih264_intra_pred_chroma_8x8_mode_horz_ssse3; + ps_codec->apf_intra_pred_chroma[2] = ih264_intra_pred_chroma_8x8_mode_dc; + ps_codec->apf_intra_pred_chroma[3] = ih264_intra_pred_chroma_8x8_mode_plane_ssse3; + + + ps_codec->pf_pad_left_luma = ih264_pad_left_luma_ssse3; + ps_codec->pf_pad_left_chroma = ih264_pad_left_chroma_ssse3; + ps_codec->pf_pad_right_luma = ih264_pad_right_luma_ssse3; + ps_codec->pf_pad_right_chroma = ih264_pad_right_chroma_ssse3; + + + ps_codec->pf_iquant_itrans_recon_luma_4x4 = ih264_iquant_itrans_recon_4x4_ssse3; + ps_codec->pf_iquant_itrans_recon_luma_4x4_dc = ih264_iquant_itrans_recon_4x4_dc_ssse3; + ps_codec->pf_iquant_itrans_recon_luma_8x8 = ih264_iquant_itrans_recon_8x8_ssse3; + ps_codec->pf_iquant_itrans_recon_luma_8x8_dc = ih264_iquant_itrans_recon_8x8_dc_ssse3; + + ps_codec->pf_iquant_itrans_recon_chroma_4x4_dc = ih264_iquant_itrans_recon_chroma_4x4_dc_ssse3; + + /* Init fn ptr luma deblocking */ + ps_codec->pf_deblk_luma_vert_bs4 = ih264_deblk_luma_vert_bs4_ssse3; + ps_codec->pf_deblk_luma_vert_bslt4 = ih264_deblk_luma_vert_bslt4_ssse3; + ps_codec->pf_deblk_luma_vert_bs4_mbaff = ih264_deblk_luma_vert_bs4_mbaff_ssse3; + ps_codec->pf_deblk_luma_vert_bslt4_mbaff = ih264_deblk_luma_vert_bslt4_mbaff_ssse3; + + ps_codec->pf_deblk_luma_horz_bs4 = ih264_deblk_luma_horz_bs4_ssse3; + ps_codec->pf_deblk_luma_horz_bslt4 = ih264_deblk_luma_horz_bslt4_ssse3; + + /* Init fn ptr chroma deblocking */ + ps_codec->pf_deblk_chroma_vert_bs4 = ih264_deblk_chroma_vert_bs4_ssse3; + ps_codec->pf_deblk_chroma_horz_bs4 = ih264_deblk_chroma_horz_bs4_ssse3; + ps_codec->pf_deblk_chroma_vert_bs4_mbaff = ih264_deblk_chroma_vert_bs4_mbaff_ssse3; + ps_codec->pf_deblk_chroma_vert_bslt4 = ih264_deblk_chroma_vert_bslt4_ssse3; + ps_codec->pf_deblk_chroma_horz_bslt4 = ih264_deblk_chroma_horz_bslt4_ssse3; + ps_codec->pf_deblk_chroma_vert_bslt4_mbaff = ih264_deblk_chroma_vert_bslt4_mbaff_ssse3; + + /* Inter pred leaf level functions */ + + ps_codec->apf_inter_pred_luma[0] = ih264_inter_pred_luma_copy_ssse3; + ps_codec->apf_inter_pred_luma[1] = ih264_inter_pred_luma_horz_qpel_ssse3; + ps_codec->apf_inter_pred_luma[2] = ih264_inter_pred_luma_horz_ssse3; + ps_codec->apf_inter_pred_luma[3] = ih264_inter_pred_luma_horz_qpel_ssse3; + ps_codec->apf_inter_pred_luma[4] = ih264_inter_pred_luma_vert_qpel_ssse3; + ps_codec->apf_inter_pred_luma[5] = ih264_inter_pred_luma_horz_qpel_vert_qpel_ssse3; + ps_codec->apf_inter_pred_luma[6] = ih264_inter_pred_luma_horz_hpel_vert_qpel_ssse3; + ps_codec->apf_inter_pred_luma[7] = ih264_inter_pred_luma_horz_qpel_vert_qpel_ssse3; + ps_codec->apf_inter_pred_luma[8] = ih264_inter_pred_luma_vert_ssse3; + ps_codec->apf_inter_pred_luma[9] = ih264_inter_pred_luma_horz_qpel_vert_hpel_ssse3; + ps_codec->apf_inter_pred_luma[10] = ih264_inter_pred_luma_horz_hpel_vert_hpel_ssse3; + ps_codec->apf_inter_pred_luma[11] = ih264_inter_pred_luma_horz_qpel_vert_hpel_ssse3; + ps_codec->apf_inter_pred_luma[12] = ih264_inter_pred_luma_vert_qpel_ssse3; + ps_codec->apf_inter_pred_luma[13] = ih264_inter_pred_luma_horz_qpel_vert_qpel_ssse3; + ps_codec->apf_inter_pred_luma[14] = ih264_inter_pred_luma_horz_hpel_vert_qpel_ssse3; + ps_codec->apf_inter_pred_luma[15] = ih264_inter_pred_luma_horz_qpel_vert_qpel_ssse3; + + ps_codec->pf_inter_pred_chroma = ih264_inter_pred_chroma_ssse3; + + + return; +} diff --git a/dependencies/vcpkg_overlay_ports/fmt/fix-warning4189.patch b/dependencies/vcpkg_overlay_ports/fmt/fix-warning4189.patch new file mode 100644 index 00000000..0efab0f1 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports/fmt/fix-warning4189.patch @@ -0,0 +1,12 @@ +diff --git a/include/fmt/format.h b/include/fmt/format.h +index 4e96539..0f1d179 100644 +--- a/include/fmt/format.h ++++ b/include/fmt/format.h +@@ -33,6 +33,7 @@ + #ifndef FMT_FORMAT_H_ + #define FMT_FORMAT_H_ + ++#pragma warning(disable:4189) + #include <algorithm> + #include <cerrno> + #include <cmath> diff --git a/dependencies/vcpkg_overlay_ports/fmt/portfile.cmake b/dependencies/vcpkg_overlay_ports/fmt/portfile.cmake new file mode 100644 index 00000000..aef43b47 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports/fmt/portfile.cmake @@ -0,0 +1,65 @@ +vcpkg_from_github( + OUT_SOURCE_PATH SOURCE_PATH + REPO fmtlib/fmt + REF 7bdf0628b1276379886c7f6dda2cef2b3b374f0b # v7.1.3 + SHA512 52ea8f9d2c0cb52ec3a740e38fcdfd6a0318566e3b599bd2e8d557168642d005c0a59bc213cff2641a88fed3bb771d15f46c39035ccd64809569af982aba47aa + HEAD_REF master + PATCHES fix-warning4189.patch +) + +vcpkg_cmake_configure( + SOURCE_PATH ${SOURCE_PATH} + DISABLE_PARALLEL_CONFIGURE # with MSBuild (on UWP), fmt writes into the source directory + OPTIONS + -DFMT_CMAKE_DIR=share/fmt + -DFMT_TEST=OFF + -DFMT_DOC=OFF +) + +vcpkg_cmake_install() +file(INSTALL ${SOURCE_PATH}/LICENSE.rst DESTINATION ${CURRENT_PACKAGES_DIR}/share/${PORT} RENAME copyright) +if(VCPKG_LIBRARY_LINKAGE STREQUAL dynamic) + if(VCPKG_TARGET_IS_WINDOWS) + if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") + if(EXISTS "${CURRENT_PACKAGES_DIR}/debug/lib/fmtd.dll") + file(MAKE_DIRECTORY ${CURRENT_PACKAGES_DIR}/debug/bin) + file(RENAME ${CURRENT_PACKAGES_DIR}/debug/lib/fmtd.dll ${CURRENT_PACKAGES_DIR}/debug/bin/fmtd.dll) + endif() + endif() + if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") + if(EXISTS "${CURRENT_PACKAGES_DIR}/lib/fmt.dll") + file(MAKE_DIRECTORY ${CURRENT_PACKAGES_DIR}/bin) + file(RENAME ${CURRENT_PACKAGES_DIR}/lib/fmt.dll ${CURRENT_PACKAGES_DIR}/bin/fmt.dll) + endif() + endif() + endif() + + vcpkg_replace_string(${CURRENT_PACKAGES_DIR}/include/fmt/core.h + "defined(FMT_SHARED)" + "1" + ) +endif() +file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/include) + +vcpkg_cmake_config_fixup() +vcpkg_fixup_pkgconfig() + +if(VCPKG_TARGET_IS_WINDOWS) + if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") + vcpkg_replace_string(${CURRENT_PACKAGES_DIR}/share/fmt/fmt-targets-debug.cmake + "lib/fmtd.dll" + "bin/fmtd.dll" + ) + endif() + if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") + vcpkg_replace_string(${CURRENT_PACKAGES_DIR}/share/fmt/fmt-targets-release.cmake + "lib/fmt.dll" + "bin/fmt.dll" + ) + endif() +endif() +file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/share) + +# Handle post-build CMake instructions +vcpkg_copy_pdbs() +file(INSTALL ${CMAKE_CURRENT_LIST_DIR}/usage DESTINATION ${CURRENT_PACKAGES_DIR}/share/${PORT}) diff --git a/dependencies/vcpkg_overlay_ports/fmt/usage b/dependencies/vcpkg_overlay_ports/fmt/usage new file mode 100644 index 00000000..c9988aa7 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports/fmt/usage @@ -0,0 +1,7 @@ +The package fmt provides CMake targets: + + find_package(fmt CONFIG REQUIRED) + target_link_libraries(main PRIVATE fmt::fmt) + + # Or use the header-only version + target_link_libraries(main PRIVATE fmt::fmt-header-only) diff --git a/dependencies/vcpkg_overlay_ports/fmt/vcpkg.json b/dependencies/vcpkg_overlay_ports/fmt/vcpkg.json new file mode 100644 index 00000000..880adf38 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports/fmt/vcpkg.json @@ -0,0 +1,17 @@ +{ + "name": "fmt", + "version": "7.1.3", + "port-version": 2, + "description": "Formatting library for C++. It can be used as a safe alternative to printf or as a fast alternative to IOStreams.", + "homepage": "https://github.com/fmtlib/fmt", + "dependencies": [ + { + "name": "vcpkg-cmake", + "host": true + }, + { + "name": "vcpkg-cmake-config", + "host": true + } + ] +} diff --git a/dependencies/vcpkg_overlay_ports/wxwidgets/example/CMakeLists.txt b/dependencies/vcpkg_overlay_ports/wxwidgets/example/CMakeLists.txt new file mode 100644 index 00000000..229b7107 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports/wxwidgets/example/CMakeLists.txt @@ -0,0 +1,31 @@ +cmake_minimum_required(VERSION 3.7) + +project(wxwidgets-example) + +add_executable(main WIN32 popup.cpp) + +find_package(wxWidgets REQUIRED) +target_compile_definitions(main PRIVATE ${wxWidgets_DEFINITIONS} "$<$<CONFIG:DEBUG>:${wxWidgets_DEFINITIONS_DEBUG}>") +target_include_directories(main PRIVATE ${wxWidgets_INCLUDE_DIRS}) +target_link_libraries(main PRIVATE ${wxWidgets_LIBRARIES}) + +add_executable(main2 WIN32 popup.cpp) + +find_package(wxWidgets CONFIG REQUIRED) +target_link_libraries(main2 PRIVATE wx::core wx::base) + +option(USE_WXRC "Use the wxrc resource compiler" ON) +if(USE_WXRC) + execute_process( + COMMAND "${wxWidgets_wxrc_EXECUTABLE}" --help + RESULTS_VARIABLE error_result + ) + if(error_result) + message(FATAL_ERROR "Failed to run wxWidgets_wxrc_EXECUTABLE (${wxWidgets_wxrc_EXECUTABLE})") + endif() +endif() + +set(PRINT_VARS "" CACHE STRING "Variables to print at the end of configuration") +foreach(var IN LISTS PRINT_VARS) + message(STATUS "${var}:=${${var}}") +endforeach() diff --git a/dependencies/vcpkg_overlay_ports/wxwidgets/fix-libs-export.patch b/dependencies/vcpkg_overlay_ports/wxwidgets/fix-libs-export.patch new file mode 100644 index 00000000..064c3a7e --- /dev/null +++ b/dependencies/vcpkg_overlay_ports/wxwidgets/fix-libs-export.patch @@ -0,0 +1,21 @@ +diff --git a/build/cmake/config.cmake b/build/cmake/config.cmake +index 52ae69d3f6..f261d5d262 100644 +--- a/build/cmake/config.cmake ++++ b/build/cmake/config.cmake +@@ -39,8 +39,14 @@ macro(wx_get_dependencies var lib) + else() + # For the value like $<$<CONFIG:DEBUG>:LIB_PATH> + # Or $<$<NOT:$<CONFIG:DEBUG>>:LIB_PATH> +- string(REGEX REPLACE "^.+>:(.+)>$" "\\1" dep_name ${dep}) +- if (NOT dep_name) ++ if(dep MATCHES "^(.+>):(.+)>$") ++ if(CMAKE_BUILD_TYPE STREQUAL "Debug" AND CMAKE_MATCH_1 STREQUAL [[$<$<NOT:$<CONFIG:DEBUG>>]]) ++ continue() ++ elseif(CMAKE_BUILD_TYPE STREQUAL "Release" AND CMAKE_MATCH_1 STREQUAL [[$<$<CONFIG:DEBUG>]]) ++ continue() ++ endif() ++ set(dep_name "${CMAKE_MATCH_2}") ++ else() + set(dep_name ${dep}) + endif() + endif() diff --git a/dependencies/vcpkg_overlay_ports/wxwidgets/fix-pcre2.patch b/dependencies/vcpkg_overlay_ports/wxwidgets/fix-pcre2.patch new file mode 100644 index 00000000..20063f44 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports/wxwidgets/fix-pcre2.patch @@ -0,0 +1,23 @@ +diff --git a/build/cmake/modules/FindPCRE2.cmake b/build/cmake/modules/FindPCRE2.cmake +index a27693a..455675a 100644 +--- a/build/cmake/modules/FindPCRE2.cmake ++++ b/build/cmake/modules/FindPCRE2.cmake +@@ -24,7 +24,10 @@ set(PCRE2_CODE_UNIT_WIDTH_USED "${PCRE2_CODE_UNIT_WIDTH}" CACHE INTERNAL "") + + find_package(PkgConfig QUIET) + pkg_check_modules(PC_PCRE2 QUIET libpcre2-${PCRE2_CODE_UNIT_WIDTH}) ++set(PCRE2_LIBRARIES ${PC_PCRE2_LINK_LIBRARIES}) ++set(PCRE2_INCLUDE_DIRS ${PC_PCRE2_INCLUDE_DIRS}) + ++if (0) + find_path(PCRE2_INCLUDE_DIRS + NAMES pcre2.h + HINTS ${PC_PCRE2_INCLUDEDIR} +@@ -36,6 +39,7 @@ find_library(PCRE2_LIBRARIES + HINTS ${PC_PCRE2_LIBDIR} + ${PC_PCRE2_LIBRARY_DIRS} + ) ++endif() + + include(FindPackageHandleStandardArgs) + FIND_PACKAGE_HANDLE_STANDARD_ARGS(PCRE2 REQUIRED_VARS PCRE2_LIBRARIES PCRE2_INCLUDE_DIRS VERSION_VAR PC_PCRE2_VERSION) diff --git a/dependencies/vcpkg_overlay_ports/wxwidgets/gtk3-link-libraries.patch b/dependencies/vcpkg_overlay_ports/wxwidgets/gtk3-link-libraries.patch new file mode 100644 index 00000000..fe2736b2 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports/wxwidgets/gtk3-link-libraries.patch @@ -0,0 +1,12 @@ +diff --git a/build/cmake/modules/FindGTK3.cmake b/build/cmake/modules/FindGTK3.cmake +index d2939a1..daf33fe 100644 +--- a/build/cmake/modules/FindGTK3.cmake ++++ b/build/cmake/modules/FindGTK3.cmake +@@ -47,6 +47,7 @@ include(CheckSymbolExists) + set(CMAKE_REQUIRED_INCLUDES ${GTK3_INCLUDE_DIRS}) + check_symbol_exists(GDK_WINDOWING_WAYLAND "gdk/gdk.h" wxHAVE_GDK_WAYLAND) + check_symbol_exists(GDK_WINDOWING_X11 "gdk/gdk.h" wxHAVE_GDK_X11) ++set(GTK3_LIBRARIES "${GTK3_LINK_LIBRARIES}" CACHE INTERNAL "") + include(FindPackageHandleStandardArgs) + FIND_PACKAGE_HANDLE_STANDARD_ARGS(GTK3 DEFAULT_MSG GTK3_INCLUDE_DIRS GTK3_LIBRARIES VERSION_OK) + diff --git a/dependencies/vcpkg_overlay_ports/wxwidgets/install-layout.patch b/dependencies/vcpkg_overlay_ports/wxwidgets/install-layout.patch new file mode 100644 index 00000000..e55381d3 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports/wxwidgets/install-layout.patch @@ -0,0 +1,52 @@ +diff --git a/build/cmake/functions.cmake b/build/cmake/functions.cmake +index 32bd959..74f31ed 100644 +--- a/build/cmake/functions.cmake ++++ b/build/cmake/functions.cmake +@@ -418,7 +418,7 @@ macro(wx_add_library name) + set_target_properties(${name} PROPERTIES PROJECT_LABEL ${name_short}) + + # Setup install +- set(runtime_dir "lib") ++ set(runtime_dir "bin") + if(WIN32 AND NOT WIN32_MSVC_NAMING) + # configure puts the .dll in the bin directory + set(runtime_dir "bin") +diff --git a/build/cmake/init.cmake b/build/cmake/init.cmake +index 3ff14ab..7bd00d3 100644 +--- a/build/cmake/init.cmake ++++ b/build/cmake/init.cmake +@@ -146,7 +146,7 @@ if(WIN32) + endif() + endif() + +-if(WIN32_MSVC_NAMING) ++if(0) + if(wxBUILD_SHARED) + set(lib_suffix "_dll") + else() +diff --git a/build/cmake/install.cmake b/build/cmake/install.cmake +index 84cb9f5..e2f460a 100644 +--- a/build/cmake/install.cmake ++++ b/build/cmake/install.cmake +@@ -48,7 +48,7 @@ else() + + install(DIRECTORY DESTINATION "bin") + install(CODE "execute_process( \ +- COMMAND ${CMAKE_COMMAND} -E create_symlink \ ++ COMMAND ${CMAKE_COMMAND} -E copy \ + ${CMAKE_INSTALL_PREFIX}/lib/wx/config/${wxBUILD_FILE_ID} \ + ${CMAKE_INSTALL_PREFIX}/bin/wx-config \ + )" +diff --git a/build/cmake/utils/CMakeLists.txt b/build/cmake/utils/CMakeLists.txt +index d6b3465..870897b 100644 +--- a/build/cmake/utils/CMakeLists.txt ++++ b/build/cmake/utils/CMakeLists.txt +@@ -38,7 +38,7 @@ if(wxUSE_XRC) + endif() + + wx_install(CODE "execute_process( \ +- COMMAND ${CMAKE_COMMAND} -E create_symlink \ ++ COMMAND ${CMAKE_COMMAND} -E copy \ + ${CMAKE_INSTALL_PREFIX}/bin/${wxrc_output_name}${EXE_SUFFIX} \ + ${CMAKE_INSTALL_PREFIX}/bin/wxrc${EXE_SUFFIX} \ + )" diff --git a/dependencies/vcpkg_overlay_ports/wxwidgets/nanosvg-ext-depend.patch b/dependencies/vcpkg_overlay_ports/wxwidgets/nanosvg-ext-depend.patch new file mode 100644 index 00000000..029b8c70 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports/wxwidgets/nanosvg-ext-depend.patch @@ -0,0 +1,42 @@ +diff --git a/build/cmake/lib/nanosvg.cmake b/build/cmake/lib/nanosvg.cmake +index 401bf48..b9e4b57 100644 +--- a/build/cmake/lib/nanosvg.cmake ++++ b/build/cmake/lib/nanosvg.cmake +@@ -16,9 +16,9 @@ elseif(wxUSE_NANOSVG) + set(NANOSVG_INCLUDE_DIRS ) + set(wxUSE_NANOSVG_EXTERNAL_ENABLE_IMPL TRUE) + +- find_package(NanoSVG REQUIRED) ++ find_package(unofficial-nanosvg CONFIG REQUIRED) + +- foreach(TARGETNAME NanoSVG::nanosvg NanoSVG::nanosvgrast unofficial::nanosvg) ++ foreach(TARGETNAME unofficial::nanosvg::nanosvg) + if(NOT TARGET ${TARGETNAME}) + continue() + endif() +diff --git a/build/cmake/options.cmake b/build/cmake/options.cmake +index 49c536a..5630526 100644 +--- a/build/cmake/options.cmake ++++ b/build/cmake/options.cmake +@@ -114,7 +114,7 @@ wx_add_thirdparty_library(wxUSE_EXPAT EXPAT "use expat for XML parsing" DEFAULT_ + wx_add_thirdparty_library(wxUSE_LIBJPEG JPEG "use libjpeg (JPEG file format)") + wx_add_thirdparty_library(wxUSE_LIBPNG PNG "use libpng (PNG image format)") + wx_add_thirdparty_library(wxUSE_LIBTIFF TIFF "use libtiff (TIFF file format)") +-wx_add_thirdparty_library(wxUSE_NANOSVG NanoSVG "use NanoSVG for rasterizing SVG") ++wx_add_thirdparty_library(wxUSE_NANOSVG unofficial-nanosvg "use NanoSVG for rasterizing SVG") + + wx_option(wxUSE_LIBLZMA "use LZMA compression" OFF) + set(wxTHIRD_PARTY_LIBRARIES ${wxTHIRD_PARTY_LIBRARIES} wxUSE_LIBLZMA "use liblzma for LZMA compression") +diff --git a/build/cmake/wxWidgetsConfig.cmake.in b/build/cmake/wxWidgetsConfig.cmake.in +index 6ef5a6e..248a701 100644 +--- a/build/cmake/wxWidgetsConfig.cmake.in ++++ b/build/cmake/wxWidgetsConfig.cmake.in +@@ -1,5 +1,8 @@ + @PACKAGE_INIT@ + ++include(CMakeFindDependencyMacro) ++find_dependency(unofficial-nanosvg CONFIG) ++ + # determine target from compiler, platform and library type + if(WIN32 AND NOT CYGWIN AND NOT MSYS) + if(${CMAKE_CXX_COMPILER_ID} STREQUAL MSVC) diff --git a/dependencies/vcpkg_overlay_ports/wxwidgets/portfile.cmake b/dependencies/vcpkg_overlay_ports/wxwidgets/portfile.cmake new file mode 100644 index 00000000..984fc083 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports/wxwidgets/portfile.cmake @@ -0,0 +1,236 @@ +vcpkg_from_github( + OUT_SOURCE_PATH SOURCE_PATH + REPO wxWidgets/wxWidgets + REF v3.2.0 + SHA512 0bb40ccab51f5e83a38feeaf462c9d1852f821d19592328327f829890d89a3abb2a991c43cdbac55da8f5ee40aab8bd5fea6abcd052198302770292f92f9f9ad + HEAD_REF master + PATCHES + install-layout.patch + relocatable-wx-config.patch + fix-libs-export.patch + fix-pcre2.patch + gtk3-link-libraries.patch +) + +if(VCPKG_TARGET_IS_LINUX) + message(WARNING [[ +Port wxwidgets currently requires the following packages from the system package manager: + pkg-config + GTK 3 + libsecret + libgcrypt + libsystemd +These development packages can be installed on Ubuntu systems via + sudo apt-get install pkg-config libgtk-3-dev libsecret-1-dev libgcrypt20-dev libsystemd-dev +]]) + foreach(conflicting_port IN ITEMS freetype glib) + if(EXISTS "${CURRENT_INSTALLED_DIR}/share/${conflicting_port}/copyright") + message(FATAL_ERROR "Port ${conflicting_port} must not be installed when building ${PORT}:${TARGET_TRIPLET}.") + endif() + endforeach() +endif() + +vcpkg_check_features( + OUT_FEATURE_OPTIONS FEATURE_OPTIONS + FEATURES + sound wxUSE_SOUND + fonts wxUSE_PRIVATE_FONTS +) + +set(OPTIONS_RELEASE "") +if(NOT "debug-support" IN_LIST FEATURES) + list(APPEND OPTIONS_RELEASE "-DwxBUILD_DEBUG_LEVEL=0") +endif() + +set(OPTIONS "") +if(VCPKG_TARGET_IS_WINDOWS AND (VCPKG_TARGET_ARCHITECTURE STREQUAL "arm64" OR VCPKG_TARGET_ARCHITECTURE STREQUAL "arm")) + list(APPEND OPTIONS + -DwxUSE_OPENGL=OFF + -DwxUSE_STACKWALKER=OFF + ) +endif() + +if(VCPKG_TARGET_IS_WINDOWS OR VCPKG_TARGET_IS_OSX) + list(APPEND OPTIONS -DwxUSE_WEBREQUEST_CURL=OFF) +else() + list(APPEND OPTIONS -DwxUSE_WEBREQUEST_CURL=ON) +endif() + +if(DEFINED ENV{PKG_CONFIG}) + set(PKGCONFIG "$ENV{PKG_CONFIG}") +elseif(VCPKG_TARGET_IS_LINUX AND NOT VCPKG_CROSSCOMPILING) + # wxWidgets on Linux currently needs to find the system's `gtk+-3.0.pc`. + # vcpkg's port pkgconf would prevent this lookup. + find_program(system_pkg_config NAMES pkg-config) + if(system_pkg_config) + set(PKGCONFIG "${system_pkg_config}") + endif() + if(VCPKG_LIBRARY_LINKAGE STREQUAL "static") + list(APPEND OPTIONS -DPKG_CONFIG_ARGN=--static) + endif() +endif() +vcpkg_find_acquire_program(PKGCONFIG) + +# This may be set to ON by users in a custom triplet. +# The use of 'wxUSE_STL' and 'WXWIDGETS_USE_STD_CONTAINERS' (ON or OFF) are not API compatible +# which is why they must be set in a custom triplet rather than a port feature. +if(NOT DEFINED WXWIDGETS_USE_STL) + #set(WXWIDGETS_USE_STL OFF) +endif() + +if(NOT DEFINED WXWIDGETS_USE_STD_CONTAINERS) + set(WXWIDGETS_USE_STD_CONTAINERS OFF) +endif() + +vcpkg_cmake_configure( + SOURCE_PATH "${SOURCE_PATH}" + OPTIONS + ${FEATURE_OPTIONS} + -DwxUSE_REGEX=sys + -DwxUSE_ZLIB=sys + -DwxUSE_EXPAT=sys + -DwxUSE_LIBJPEG=sys + -DwxUSE_LIBPNG=sys + -DwxUSE_LIBTIFF=sys + -DwxUSE_SECRETSTORE=FALSE + -DwxUSE_STL=ON + -DwxUSE_STD_CONTAINERS=${WXWIDGETS_USE_STD_CONTAINERS} + -DwxBUILD_DISABLE_PLATFORM_LIB_DIR=ON + -DwxUSE_LIBLZMA=OFF + -DwxUSE_JOYSTICK=OFF + -DwxUSE_SOCKETS=OFF + -DwxUSE_IPV6=OFF + -DwxUSE_FS_ZIP=OFF + -DwxUSE_FS_ARCHIVE=OFF + -DwxUSE_FS_INET=OFF + -DwxUSE_ARCHIVE_STREAMS=OFF + -DwxUSE_ZIPSTREAM=OFF + -DwxUSE_TARSTREAM=OFF + -DwxUSE_PROTOCOL=OFF + -DwxUSE_PROTOCOL_FTP=OFF + -DwxUSE_PROTOCOL_HTTP=OFF + -DwxUSE_URL=OFF + -DwxUSE_SOUND=OFF + -DwxUSE_WEBVIEW=OFF + -DwxUSE_RICHTEXT=OFF + -DwxUSE_SVG=OFF + -DwxUSE_GIF=OFF + -DwxUSE_PNM=OFF + -DwxUSE_PCX=OFF + -DwxUSE_WEBREQUEST=OFF + -DwxUSE_ACTIVEX=OFF + -DwxUSE_REGEX=OFF + -DwxUSE_NANOSVG=OFF + -DwxUSE_NANOSVG_EXTERNAL=OFF + ${OPTIONS} + "-DPKG_CONFIG_EXECUTABLE=${PKGCONFIG}" + # The minimum cmake version requirement for Cotire is 2.8.12. + # however, we need to declare that the minimum cmake version requirement is at least 3.1 to use CMAKE_PREFIX_PATH as the path to find .pc. + -DPKG_CONFIG_USE_CMAKE_PREFIX_PATH=ON + OPTIONS_RELEASE + ${OPTIONS_RELEASE} +) + +vcpkg_cmake_install() +vcpkg_cmake_config_fixup(CONFIG_PATH lib/cmake/wxWidgets) + +# The CMake export is not ready for use: It lacks a config file. +file(REMOVE_RECURSE + ${CURRENT_PACKAGES_DIR}/lib/cmake + ${CURRENT_PACKAGES_DIR}/debug/lib/cmake +) + +set(tools wxrc) +if(NOT VCPKG_TARGET_IS_WINDOWS OR NOT VCPKG_HOST_IS_WINDOWS) + list(APPEND tools wxrc-3.2) + file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/tools/${PORT}") + file(RENAME "${CURRENT_PACKAGES_DIR}/bin/wx-config" "${CURRENT_PACKAGES_DIR}/tools/${PORT}/wx-config") + if(NOT VCPKG_BUILD_TYPE) + file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/tools/${PORT}/debug") + file(RENAME "${CURRENT_PACKAGES_DIR}/debug/bin/wx-config" "${CURRENT_PACKAGES_DIR}/tools/${PORT}/debug/wx-config") + endif() +endif() +vcpkg_copy_tools(TOOL_NAMES ${tools} AUTO_CLEAN) + +# do the copy pdbs now after the dlls got moved to the expected /bin folder above +vcpkg_copy_pdbs() + +file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/include/msvc") +file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include") +file(GLOB_RECURSE INCLUDES "${CURRENT_PACKAGES_DIR}/include/*.h") +if(EXISTS "${CURRENT_PACKAGES_DIR}/lib/mswu/wx/setup.h") + list(APPEND INCLUDES "${CURRENT_PACKAGES_DIR}/lib/mswu/wx/setup.h") +endif() +if(EXISTS "${CURRENT_PACKAGES_DIR}/debug/lib/mswud/wx/setup.h") + list(APPEND INCLUDES "${CURRENT_PACKAGES_DIR}/debug/lib/mswud/wx/setup.h") +endif() +foreach(INC IN LISTS INCLUDES) + file(READ "${INC}" _contents) + if(VCPKG_LIBRARY_LINKAGE STREQUAL "static") + string(REPLACE "defined(WXUSINGDLL)" "0" _contents "${_contents}") + else() + string(REPLACE "defined(WXUSINGDLL)" "1" _contents "${_contents}") + endif() + # Remove install prefix from setup.h to ensure package is relocatable + string(REGEX REPLACE "\n#define wxINSTALL_PREFIX [^\n]*" "\n#define wxINSTALL_PREFIX \"\"" _contents "${_contents}") + file(WRITE "${INC}" "${_contents}") +endforeach() + +if(NOT EXISTS "${CURRENT_PACKAGES_DIR}/include/wx/setup.h") + file(GLOB_RECURSE WX_SETUP_H_FILES_DBG "${CURRENT_PACKAGES_DIR}/debug/lib/*.h") + file(GLOB_RECURSE WX_SETUP_H_FILES_REL "${CURRENT_PACKAGES_DIR}/lib/*.h") + + if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") + vcpkg_replace_string("${WX_SETUP_H_FILES_REL}" "${CURRENT_PACKAGES_DIR}" "") + + string(REPLACE "${CURRENT_PACKAGES_DIR}/lib/" "" WX_SETUP_H_FILES_REL "${WX_SETUP_H_FILES_REL}") + string(REPLACE "/setup.h" "" WX_SETUP_H_REL_RELATIVE "${WX_SETUP_H_FILES_REL}") + endif() + if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") + vcpkg_replace_string("${WX_SETUP_H_FILES_DBG}" "${CURRENT_PACKAGES_DIR}" "") + + string(REPLACE "${CURRENT_PACKAGES_DIR}/debug/lib/" "" WX_SETUP_H_FILES_DBG "${WX_SETUP_H_FILES_DBG}") + string(REPLACE "/setup.h" "" WX_SETUP_H_DBG_RELATIVE "${WX_SETUP_H_FILES_DBG}") + endif() + + configure_file("${CMAKE_CURRENT_LIST_DIR}/setup.h.in" "${CURRENT_PACKAGES_DIR}/include/wx/setup.h" @ONLY) +endif() + +file(GLOB configs LIST_DIRECTORIES false "${CURRENT_PACKAGES_DIR}/lib/wx/config/*" "${CURRENT_PACKAGES_DIR}/tools/${PORT}/wx-config") +foreach(config IN LISTS configs) + vcpkg_replace_string("${config}" "${CURRENT_INSTALLED_DIR}" [[${prefix}]]) +endforeach() +file(GLOB configs LIST_DIRECTORIES false "${CURRENT_PACKAGES_DIR}/debug/lib/wx/config/*" "${CURRENT_PACKAGES_DIR}/tools/${PORT}/debug/wx-config") +foreach(config IN LISTS configs) + vcpkg_replace_string("${config}" "${CURRENT_INSTALLED_DIR}/debug" [[${prefix}]]) +endforeach() + +# For CMake multi-config in connection with wrapper +if(EXISTS "${CURRENT_PACKAGES_DIR}/debug/lib/mswud/wx/setup.h") + file(INSTALL "${CURRENT_PACKAGES_DIR}/debug/lib/mswud/wx/setup.h" + DESTINATION "${CURRENT_PACKAGES_DIR}/lib/mswud/wx" + ) +endif() + +if(NOT "debug-support" IN_LIST FEATURES) + if(VCPKG_TARGET_IS_WINDOWS AND VCPKG_HOST_IS_WINDOWS) + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/include/wx/debug.h" "#define wxDEBUG_LEVEL 1" "#define wxDEBUG_LEVEL 0") + else() + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/include/wx-3.2/wx/debug.h" "#define wxDEBUG_LEVEL 1" "#define wxDEBUG_LEVEL 0") + endif() +endif() + +if("example" IN_LIST FEATURES) + file(INSTALL + "${CMAKE_CURRENT_LIST_DIR}/example/CMakeLists.txt" + "${SOURCE_PATH}/samples/popup/popup.cpp" + "${SOURCE_PATH}/samples/sample.xpm" + DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}/example" + ) + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/share/${PORT}/example/popup.cpp" "../sample.xpm" "sample.xpm") +endif() + +configure_file("${CMAKE_CURRENT_LIST_DIR}/vcpkg-cmake-wrapper.cmake" "${CURRENT_PACKAGES_DIR}/share/${PORT}/vcpkg-cmake-wrapper.cmake" @ONLY) + +file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") +file(INSTALL "${SOURCE_PATH}/docs/licence.txt" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}" RENAME copyright) diff --git a/dependencies/vcpkg_overlay_ports/wxwidgets/relocatable-wx-config.patch b/dependencies/vcpkg_overlay_ports/wxwidgets/relocatable-wx-config.patch new file mode 100644 index 00000000..8a5e7c4d --- /dev/null +++ b/dependencies/vcpkg_overlay_ports/wxwidgets/relocatable-wx-config.patch @@ -0,0 +1,49 @@ +diff --git a/wx-config.in b/wx-config.in +index 441f88c..b326867 100755 +--- a/wx-config.in ++++ b/wx-config.in +@@ -91,7 +91,7 @@ EOF + + + # Contentious tools determined by configure. +-EGREP="@EGREP@" ++EGREP="grep -E" # no absolute path from host + + + # For the people who know what they want, or think they do: +@@ -402,8 +402,23 @@ is_cross() { [ "x@cross_compiling@" = "xyes" ]; } + + + # Determine the base directories we require. +-prefix=${input_option_prefix-${this_prefix:-@prefix@}} +-exec_prefix=${input_option_exec_prefix-${input_option_prefix-${this_exec_prefix:-@exec_prefix@}}} ++vcpkg_prefix=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P) ++case "$vcpkg_prefix" in ++ */lib/wx/config) ++ vcpkg_prefix=${vcpkg_prefix%/*/*/*} ++ ;; ++ */tools/wxwidgets/debug) ++ vcpkg_prefix=${vcpkg_prefix%/*/*/*}/debug ++ ;; ++ */tools/wxwidgets) ++ vcpkg_prefix=${vcpkg_prefix%/*/*} ++ ;; ++esac ++if [ -n "@MINGW@" -a -n "@CMAKE_HOST_WIN32@" ]; then ++ vcpkg_prefix=$(cygpath -m "$vcpkg_prefix") ++fi ++prefix=${input_option_prefix-${this_prefix:-$vcpkg_prefix}} ++exec_prefix=${input_option_exec_prefix-${input_option_prefix-${this_exec_prefix:-$prefix}}} + wxconfdir="@libdir@/wx/config" + + installed_configs=`cd "$wxconfdir" 2> /dev/null && ls | grep -v "^inplace-"` +@@ -940,6 +949,9 @@ prefix=${this_prefix-$prefix} + exec_prefix=${this_exec_prefix-$exec_prefix} + + includedir="@includedir@" ++if [ "@CMAKE_BUILD_TYPE@" = "Debug" ] ; then ++ includedir="${includedir%/debug/include}/include" ++fi + libdir="@libdir@" + bindir="@bindir@" + diff --git a/dependencies/vcpkg_overlay_ports/wxwidgets/setup.h.in b/dependencies/vcpkg_overlay_ports/wxwidgets/setup.h.in new file mode 100644 index 00000000..b927735b --- /dev/null +++ b/dependencies/vcpkg_overlay_ports/wxwidgets/setup.h.in @@ -0,0 +1,5 @@ +#ifdef _DEBUG +#include "../../debug/lib/@WX_SETUP_H_DBG_RELATIVE@/setup.h" +#else +#include "../../lib/@WX_SETUP_H_REL_RELATIVE@/setup.h" +#endif diff --git a/dependencies/vcpkg_overlay_ports/wxwidgets/usage b/dependencies/vcpkg_overlay_ports/wxwidgets/usage new file mode 100644 index 00000000..bf1043e8 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports/wxwidgets/usage @@ -0,0 +1,4 @@ +The package wxwidgets provides CMake targets: + + find_package(wxWidgets CONFIG REQUIRED) + target_link_libraries(main PRIVATE wx::core wx::base) diff --git a/dependencies/vcpkg_overlay_ports/wxwidgets/vcpkg-cmake-wrapper.cmake b/dependencies/vcpkg_overlay_ports/wxwidgets/vcpkg-cmake-wrapper.cmake new file mode 100644 index 00000000..b605525a --- /dev/null +++ b/dependencies/vcpkg_overlay_ports/wxwidgets/vcpkg-cmake-wrapper.cmake @@ -0,0 +1,77 @@ +cmake_policy(PUSH) +cmake_policy(SET CMP0012 NEW) +cmake_policy(SET CMP0054 NEW) + +get_filename_component(_vcpkg_wx_root "${CMAKE_CURRENT_LIST_DIR}/../.." ABSOLUTE) +set(wxWidgets_ROOT_DIR "${_vcpkg_wx_root}" CACHE INTERNAL "") +set(WX_ROOT_DIR "${_vcpkg_wx_root}" CACHE INTERNAL "") +unset(_vcpkg_wx_root) + +if(WIN32 AND CMAKE_HOST_WIN32) + # Find all libs with "32" infix which is unknown to FindwxWidgets.cmake + function(z_vcpkg_wxwidgets_find_base_library BASENAME) + find_library(WX_${BASENAME}d wx${BASENAME}32ud NAMES wx${BASENAME}d PATHS "${wxWidgets_ROOT_DIR}/debug/lib" NO_DEFAULT_PATH) + find_library(WX_${BASENAME} wx${BASENAME}32u NAMES wx${BASENAME} PATHS "${wxWidgets_ROOT_DIR}/lib" NO_DEFAULT_PATH REQUIRED) + endfunction() + function(z_vcpkg_wxwidgets_find_suffix_library BASENAME) + foreach(lib IN LISTS ARGN) + find_library(WX_${lib}d NAMES wx${BASENAME}32ud_${lib} PATHS "${wxWidgets_ROOT_DIR}/debug/lib" NO_DEFAULT_PATH) + find_library(WX_${lib} NAMES wx${BASENAME}32u_${lib} PATHS "${wxWidgets_ROOT_DIR}/lib" NO_DEFAULT_PATH) + endforeach() + endfunction() + z_vcpkg_wxwidgets_find_base_library(base) + z_vcpkg_wxwidgets_find_suffix_library(base net odbc xml) + z_vcpkg_wxwidgets_find_suffix_library(msw core adv aui html media xrc dbgrid gl qa richtext stc ribbon propgrid webview) + if(WX_stc AND "@VCPKG_LIBRARY_LINKAGE@" STREQUAL "static") + z_vcpkg_wxwidgets_find_base_library(scintilla) + endif() + # Force FindwxWidgets.cmake win32 mode for all windows targets built on windows + set(_vcpkg_wxwidgets_backup_crosscompiling "${CMAKE_CROSSCOMPILING}") + set(CMAKE_CROSSCOMPILING 0) + set(wxWidgets_LIB_DIR "${wxWidgets_ROOT_DIR}/lib" CACHE INTERNAL "") +else() + # FindwxWidgets.cmake unix mode, single-config + if(MINGW) + # Force FindwxWidgets.cmake unix mode for mingw cross builds + set(_vcpkg_wxwidgets_backup_crosscompiling "${CMAKE_CROSSCOMPILING}") + set(CMAKE_CROSSCOMPILING 1) + endif() + set(_vcpkg_wxconfig "") + if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR "Debug" IN_LIST MAP_IMPORTED_CONFIG_${CMAKE_BUILD_TYPE}) + # Debug + set(wxWidgets_LIB_DIR "${wxWidgets_ROOT_DIR}/debug/lib" CACHE INTERNAL "") + file(GLOB _vcpkg_wxconfig LIST_DIRECTORIES false "${wxWidgets_LIB_DIR}/wx/config/*") + endif() + if(NOT _vcpkg_wxconfig) + # Release or fallback + set(wxWidgets_LIB_DIR "${wxWidgets_ROOT_DIR}/lib" CACHE INTERNAL "") + file(GLOB _vcpkg_wxconfig LIST_DIRECTORIES false "${wxWidgets_LIB_DIR}/wx/config/*") + endif() + set(wxWidgets_CONFIG_EXECUTABLE "${_vcpkg_wxconfig}" CACHE INTERNAL "") + unset(_vcpkg_wxconfig) +endif() +set(WX_LIB_DIR "${wxWidgets_LIB_DIR}" CACHE INTERNAL "") + +_find_package(${ARGS}) + +if(DEFINED _vcpkg_wxwidgets_backup_crosscompiling) + set(CMAKE_CROSSCOMPILING "${_vcpkg_wxwidgets_backup_crosscompiling}") + unset(_vcpkg_wxwidgets_backup_crosscompiling) +endif() + +if(WIN32 AND CMAKE_HOST_WIN32 AND "@VCPKG_LIBRARY_LINKAGE@" STREQUAL "static" AND NOT "wx::core" IN_LIST wxWidgets_LIBRARIES) + find_package(EXPAT QUIET) + find_package(JPEG QUIET) + find_package(PNG QUIET) + find_package(TIFF QUIET) + find_package(ZLIB QUIET) + list(APPEND wxWidgets_LIBRARIES + ${EXPAT_LIBRARIES} + ${JPEG_LIBRARIES} + ${PNG_LIBRARIES} + ${TIFF_LIBRARIES} + ${ZLIB_LIBRARIES} + ) +endif() + +cmake_policy(POP) diff --git a/dependencies/vcpkg_overlay_ports/wxwidgets/vcpkg.json b/dependencies/vcpkg_overlay_ports/wxwidgets/vcpkg.json new file mode 100644 index 00000000..3f04aece --- /dev/null +++ b/dependencies/vcpkg_overlay_ports/wxwidgets/vcpkg.json @@ -0,0 +1,67 @@ +{ + "name": "wxwidgets", + "version": "3.2.0", + "port-version": 1, + "description": [ + "Widget toolkit and tools library for creating graphical user interfaces (GUIs) for cross-platform applications. ", + "Set WXWIDGETS_USE_STL in a custom triplet to build with the wxUSE_STL build option.", + "Set WXWIDGETS_USE_STD_CONTAINERS in a custom triplet to build with the wxUSE_STD_CONTAINERS build option." + ], + "homepage": "https://github.com/wxWidgets/wxWidgets", + "license": "LGPL-2.0-or-later WITH WxWindows-exception-3.1", + "supports": "!uwp", + "dependencies": [ + { + "name": "curl", + "default-features": false, + "platform": "!windows & !osx" + }, + "expat", + "libpng", + "tiff", + { + "name": "vcpkg-cmake", + "host": true + }, + { + "name": "vcpkg-cmake-config", + "host": true + }, + "zlib" + ], + "default-features": [ + "debug-support", + "sound" + ], + "features": { + "debug-support": { + "description": "Enable wxWidgets debugging support hooks even for release builds (wxDEBUG_LEVEL 1)" + }, + "example": { + "description": "Example source code and CMake project" + }, + "fonts": { + "description": "Enable to use the font functionality of wxWidgets", + "dependencies": [ + { + "name": "fontconfig", + "platform": "!windows & !osx" + }, + { + "name": "pango", + "platform": "!windows & !osx" + } + ] + }, + "sound": { + "description": "Build wxSound support", + "dependencies": [ + { + "name": "sdl2", + "default-features": false, + "platform": "!windows & !osx" + } + ] + } + } +} diff --git a/generate_vs_solution.bat b/generate_vs_solution.bat new file mode 100644 index 00000000..21060027 --- /dev/null +++ b/generate_vs_solution.bat @@ -0,0 +1,2 @@ +"C:\PROGRAM FILES\MICROSOFT VISUAL STUDIO\2022\COMMUNITY\COMMON7\IDE\COMMONEXTENSIONS\MICROSOFT\CMAKE\CMake\bin\cmake.exe" -B build/ +pause \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100644 index 00000000..50662e69 --- /dev/null +++ b/readme.md @@ -0,0 +1,29 @@ +# Cemu - Wii U emulator + +This is the official repository for Cemu, a Wii U emulator that runs on x64 Windows and Linux. + +[Official website](https://cemu.info/) + +## Releases + +The latest release is available on the github [releases page](https://github.com/cemu-project/Cemu/releases/) + +Pre-2.0 releases can be found on the [Cemu website](http://cemu.info/changelog.html). + +## Build instructions + +See [building.md](https://github.com/cemu-project/Cemu/blob/master/building.md) + +## State of linux builds + +Linux builds of Cemu are somewhat functional but lack a lot of polish and some features. +Currently we offer prebuilt binaries for ubuntu 20.04. For other distributions you'll have to compile it yourself. In the future we will look into providing appimage and/or flatpak releases. + +**Important: Cemu currently requires all files to be placed in a case-insensitive filesystem** + +## If you want to contribute + +Pull requests are welcome. For easier coordination we invite you to join our developer discussion channel on Discord: [https://discord.gg/5psYsup](https://discord.gg/5psYsup) + +## License +Cemu is licensed under [MPL](https://github.com/cemu-project/cemu/blob/master/LICENSE). Exempt from this are all files in the dependencies directory for which the licenses of the original code applies as well as some individual files in the src folder, as specified in their file headers. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 00000000..005cfa2a --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,101 @@ +project(cemuMain) + +option(CEMU_CXX_FLAGS "Additional flags used for compiling Cemu source code") +if(CEMU_CXX_FLAGS) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CEMU_CXX_FLAGS}") +endif() + +if(CMAKE_SIZEOF_VOID_P EQUAL 8) + # all ok +else() + message( FATAL_ERROR "Pointers are not 64bit" ) +endif() + +if(MSVC) + add_definitions(-DWIN32_LEAN_AND_MEAN) + add_definitions(-DCURL_STATICLIB) + #add_definitions(-DVK_USE_PLATFORM_WIN32_KHR) + # _CRT_SECURE_NO_WARNINGS + # _WINSOCK_DEPRECATED_NO_WARNINGS + # _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING + # _SILENCE_ALL_CXX17_DEPRECATION_WARNINGS +elseif(UNIX) + add_definitions(-fms-extensions) + add_definitions(-fms-compatibility-version=19.14) + add_definitions(-fdelayed-template-parsing) + add_definitions(-DVK_USE_PLATFORM_XLIB_KHR) # legacy. Do we need to support XLIB surfaces? + add_definitions(-DVK_USE_PLATFORM_XCB_KHR) + add_definitions(-maes) + # warnings + add_compile_options(-Wno-switch -Wno-ignored-attributes -Wno-deprecated-enum-enum-conversion -Wno-ambiguous-reversed-operator) +endif() + +add_definitions(-DVK_NO_PROTOTYPES) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +add_subdirectory(Common) +add_subdirectory(gui) +add_subdirectory(Cafe) +add_subdirectory(Cemu) +add_subdirectory(config) +add_subdirectory(input) +add_subdirectory(audio) +add_subdirectory(util) +add_subdirectory(imgui) +add_subdirectory(resource) +add_subdirectory(asm) + +if(ENABLE_CEMUHOOK) + add_definitions(-DUSE_CEMUHOOK) + add_subdirectory(cemuhook) +endif() + +if(PUBLIC_RELEASE) +add_executable(CemuBin WIN32 +main.cpp +mainLLE.cpp +) +else() +add_executable(CemuBin +main.cpp +mainLLE.cpp +) +endif() + +target_precompile_headers(CemuBin PRIVATE Common/precompiled.h) + +if(WIN32) + target_sources(CemuBin PRIVATE + resource/cemu.rc + exports.def # for Cemuhook + ) +endif() + +set_property(TARGET CemuBin PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>") + +set_target_properties(CemuBin PROPERTIES + RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_CURRENT_SOURCE_DIR}/../bin/ + RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_CURRENT_SOURCE_DIR}/../bin/ + OUTPUT_NAME "Cemu" + ) + +target_link_libraries(CemuBin PRIVATE CemuCommon CemuComponents CemuCafe CemuConfig CemuGui CemuAudio CemuInput CemuUtil) +target_link_libraries(CemuBin PRIVATE CemuAsm) +target_link_libraries(CemuBin PRIVATE OpenSSL::SSL) +target_link_libraries(CemuBin PRIVATE ZLIB::ZLIB) +target_link_libraries(CemuBin PRIVATE ${wxWidgets_LIBRARIES}) +target_link_libraries(CemuBin PRIVATE CURL::libcurl) +target_link_libraries(CemuBin PRIVATE imgui::imgui) +target_link_libraries(CemuBin PRIVATE pugixml pugixml::static pugixml::pugixml) + +if(ENABLE_CEMUHOOK) +target_link_libraries(CemuBin PRIVATE CemuCemuhook) +endif() + +target_link_libraries(CemuBin PUBLIC +CemuCommon CemuAudio CemuInput CemuComponents CemuCafe CemuConfig CemuGui imguiImpl) + +# needed because of some cyclic dependencies. fix this +target_link_libraries(CemuBin PUBLIC +CemuCommon CemuInput CemuComponents CemuCafe CemuResource CemuGui CemuAsm) diff --git a/src/Cafe/Account/Account.cpp b/src/Cafe/Account/Account.cpp new file mode 100644 index 00000000..0ea74f65 --- /dev/null +++ b/src/Cafe/Account/Account.cpp @@ -0,0 +1,578 @@ +#include "Account.h" +#include "util/helpers/helpers.h" +#include "gui/CemuApp.h" +#include "util/helpers/SystemException.h" + +#include <random> + +#include "config/ActiveSettings.h" +#include "Cafe/IOSU/legacy/iosu_crypto.h" +#include "Common/filestream.h" + +std::vector<Account> Account::s_account_list; + +Account::Account(uint32 persistent_id) + : m_persistent_id(persistent_id) {} + + +typedef struct +{ + uint32be high; + uint32be low; +}FFLDataID_t; + +typedef struct +{ + /* +0x00 */ uint32 uknFlags; + /* +0x04 */ FFLDataID_t miiId; // bytes 8 and 9 are part of the CRC? (miiId is based on account transferable id?) + /* +0x0C */ uint8 ukn0C[0xA]; + /* +0x16 */ uint8 ukn16[2]; + /* +0x18 */ uint16 ukn18; + /* +0x1A */ uint16be miiName[10]; + /* +0x2E */ uint16 ukn2E; + /* +0x30 */ uint8 ukn30[96 - 0x30]; +}FFLData_t; + +uint16 FFLCalculateCRC16(uint8* input, sint32 length) +{ + cemu_assert_debug((length % 8) == 0); + + uint16 crc = 0; + for (sint32 c = 0; c < length; c++) + { + for (sint32 f = 0; f < 8; f++) + { + if ((crc & 0x8000) != 0) + { + uint16 t = crc << 1; + crc = t ^ 0x1021; + } + else + { + crc <<= 1; + } + } + crc ^= (uint16)input[c]; + } + return crc; +} + +Account::Account(uint32 persistent_id, std::wstring_view mii_name) + : m_persistent_id(persistent_id) +{ + if (mii_name.empty()) + throw std::system_error(AccountErrc::InvalidMiiName); + + static std::random_device s_random_device; + static std::mt19937 s_mte(s_random_device()); + std::uniform_int_distribution<uint16> dist(std::numeric_limits<uint8>::min(), std::numeric_limits<uint8>::max()); + std::generate(m_uuid.begin(), m_uuid.end(), [&]() { return (uint8)dist(s_mte); }); + + // 1000004 or 2000004 | lower uint32 from uuid from uuid + m_transferable_id_base = (0x2000004ULL << 32); + m_transferable_id_base |= ((uint64)m_uuid[12] << 24) | ((uint64)m_uuid[13] << 16) | ((uint64)m_uuid[14] << 8) | (uint64)m_uuid[15]; + + SetMiiName(mii_name); + + // todo: generate mii data + // iosuAct_generateDefaultMii + // void* pMiiData = &_actAccountData[accountIndex].miiData; + uint8* fflByteDataBE = m_mii_data.data(); + + uint16* fflDataBE = (uint16*)m_mii_data.data(); + // FFLCreateId is derived from the words at location: 0x7 - 0xb (5 words, so it's a 80 bit id) + for (sint32 i = 0; i < 96 / 2; i++) + fflDataBE[i] = _swapEndianU16(1); + + *(uint16*)(fflByteDataBE + 0x3A) = _swapEndianU16(1 | (3 << 9)); + *(uint16*)(fflByteDataBE + 0x2) = _swapEndianU16(1 | (1 << 12)); + + //*(uint32*)(fflByteDataBE + 4) = 0; // transferable id high ? + *(uint32*)(fflByteDataBE + 8) = _swapEndianU32(0x33333 + 0 + 1); // mii id low + + // swap endian (apparently it's stored little-endian) + for (sint32 i = 0; i < 96 / 2; i++) + fflDataBE[i] = _swapEndianU16(fflDataBE[i]); + + // set default name + FFLData_t* fflData = (FFLData_t*)m_mii_data.data(); + const auto tmp_name = GetMiiName(); + std::copy(tmp_name.cbegin(), tmp_name.cend(), fflData->miiName); + + // calculate checksum + uint32 crcCounter = 0; + while (FFLCalculateCRC16(m_mii_data.data(), 96) != 0) + { + *(uint32*)(fflDataBE + 2) = _swapEndianU32(crcCounter); + crcCounter++; + } + + const auto error = CheckValid(); + if (error) + throw std::system_error(error); +} + +Account::Account(std::wstring_view file_name) +{ + if (!fs::exists(file_name.data())) + throw std::runtime_error("given file doesn't exist"); + + std::unique_ptr<FileStream> file(FileStream::openFile2(file_name)); + if (!file) + throw std::runtime_error("can't open file"); + + ParseFile(file.get()); + const auto error = CheckValid(); + if (error) + throw std::system_error(error); +} + +std::error_code Account::CheckValid() const +{ + if (m_persistent_id < kMinPersistendId) + return AccountErrc::InvalidPersistentId; + + if (m_mii_name[0] == '\0') + return AccountErrc::InvalidMiiName; + + if (m_mii_data == decltype(m_mii_data){}) + return AccountErrc::InvalidMiiData; + + // todo: check for other needed properties + + return AccountErrc::NoError; +} + +std::error_code Account::Load() +{ + const auto persistent_id = m_persistent_id; + const fs::path path = GetFileName(); + try + { + std::unique_ptr<FileStream> file(FileStream::openFile2(path)); + if (!file) + throw std::runtime_error("can't open file"); + ParseFile(file.get()); + // fix persistent id if it's in the wrong folder + m_persistent_id = persistent_id; + return CheckValid(); + } + catch (const std::system_error& ex) + { + return ex.code(); + } + catch(const std::exception& ex) + { + forceLog_printf("handled error in Account::Load: %s", ex.what()); + return AccountErrc::ParseError; + } +} + +std::error_code Account::Save() +{ + fs::path path = CemuApp::GetMLCPath(fmt::format(L"usr/save/system/act/{:08x}", m_persistent_id)).ToStdWstring(); + if (!fs::exists(path)) + { + std::error_code ec; + fs::create_directories(path, ec); + if (ec) + return ec; + } + + path /= L"account.dat"; + + try + { + std::ofstream file; + file.exceptions(std::ios::badbit); + file.open(path); + + file << "AccountInstance_20120705" << std::endl; + file << fmt::format("PersistentId={:08x}", m_persistent_id) << std::endl; + file << fmt::format("TransferableIdBase={:x}", m_transferable_id_base) << std::endl; + + file << fmt::format("Uuid="); + for (const auto& b : m_uuid) + file << fmt::format("{:02x}", b); + file << std::endl; + + file << fmt::format("MiiData="); + for (const auto& b : m_mii_data) + file << fmt::format("{:02x}", b); + file << std::endl; + + file << fmt::format("MiiName="); + for (const auto& b : m_mii_name) + file << fmt::format("{:04x}", (uint16)b); + file << std::endl; + + file << fmt::format("AccountId={}", m_account_id) << std::endl; + file << fmt::format("BirthYear={:x}", m_birth_year) << std::endl; + file << fmt::format("BirthMonth={:x}", m_birth_month) << std::endl; + file << fmt::format("BirthDay={:x}", m_birth_day) << std::endl; + file << fmt::format("Gender={:x}", m_gender) << std::endl; + file << fmt::format("EmailAddress={}", m_email) << std::endl; + file << fmt::format("Country={:x}", m_country) << std::endl; + file << fmt::format("SimpleAddressId={:x}", m_simple_address_id) << std::endl; + file << fmt::format("PrincipalId={:x}", m_principal_id) << std::endl; + file << fmt::format("IsPasswordCacheEnabled={:x}", m_password_cache_enabled) << std::endl; + + file << fmt::format("AccountPasswordCache="); + for (const auto& b : m_account_password_cache) + file << fmt::format("{:02x}", b); + file << std::endl; + + // write rest of stuff we got + for(const auto& [key, value] : m_storage) + { + file << fmt::format("{}={}", key, value) << std::endl; + } + + file.flush(); + file.close(); + return CheckValid(); + } + catch (const std::system_error& e) + { + return e.code(); + } +} + +OnlineAccountError Account::GetOnlineAccountError() const +{ + if (m_account_id.empty()) + return OnlineAccountError::kNoAccountId; + + if (!IsPasswordCacheEnabled()) + return OnlineAccountError::kNoPasswordCached; + + if (m_account_password_cache == decltype(m_account_password_cache){}) + return OnlineAccountError::kPasswordCacheEmpty; + + /*if (m_simple_address_id == 0) not really needed + return false;*/ + + if (m_principal_id == 0) + return OnlineAccountError::kNoPrincipalId; + + // TODO + return OnlineAccountError::kNone; +} + +bool Account::IsValidOnlineAccount() const +{ + return GetOnlineAccountError() == OnlineAccountError::kNone; +} + +fs::path Account::GetFileName() const +{ + return GetFileName(m_persistent_id); +} + +std::wstring_view Account::GetMiiName() const +{ + const auto it = std::find(m_mii_name.cbegin(), m_mii_name.cend(), '\0'); + if(it == m_mii_name.cend()) + return { m_mii_name.data(), m_mii_name.size() - 1 }; + + const size_t count = std::distance(m_mii_name.cbegin(), it); + return { m_mii_name.data(), count}; +} + +std::string_view Account::GetStorageValue(std::string_view key) const +{ + const auto it = m_storage.find(key.data()); + if (it == m_storage.cend()) + return {}; + + return it->second; +} + +void Account::SetMiiName(std::wstring_view name) +{ + m_mii_name = {}; + std::copy(name.data(), name.data() + std::min(name.size(), m_mii_name.size() - 1), m_mii_name.begin()); +} + +const std::vector<Account>& Account::RefreshAccounts() +{ + std::vector<Account> result; + const fs::path path = CemuApp::GetMLCPath(L"usr/save/system/act").ToStdWstring(); + if (fs::exists(path)) + { + for (const auto& it : fs::directory_iterator(path)) + { + if (!fs::is_directory(it)) + continue; + + const auto file_name = it.path().filename().string(); + if (file_name.size() != 8) + continue; + + const auto persistent_id = ConvertString<uint32>(file_name, 16); + if (persistent_id < kMinPersistendId) + continue; + + Account account(persistent_id); + const auto error = account.Load(); + if (!error) + result.emplace_back(account); + } + } + + // we always force at least one account + if (result.empty()) + { + result.emplace_back(kMinPersistendId, L"default"); + result.begin()->Save(); + } + + s_account_list = result; + UpdatePersisidDat(); + return s_account_list; +} + +void Account::UpdatePersisidDat() +{ + const auto max_id = std::max(kMinPersistendId, GetNextPersistentId() - 1); + const auto file = ActiveSettings::GetMlcPath("usr/save/system/act/persisid.dat"); + std::ofstream f(file); + if(f.is_open()) + { + f << "PersistentIdManager_20120607" << std::endl << "PersistentIdHead=" << std::hex << max_id << std::endl << std::endl; + f.flush(); + f.close(); + } + else + forceLog_printf("Unable to save persisid.dat"); +} + +bool Account::HasFreeAccountSlots() +{ + return s_account_list.size() < 12; +} + +const std::vector<Account>& Account::GetAccounts() +{ + if (!s_account_list.empty()) + return s_account_list; + + return RefreshAccounts(); +} + +const Account& Account::GetAccount(uint32 persistent_id) +{ + for (const auto& account : GetAccounts()) + { + if (account.GetPersistentId() == persistent_id) + return account; + } + + return *GetAccounts().begin(); +} + +const Account& Account::GetCurrentAccount() +{ + return GetAccount(ActiveSettings::GetPersistentId()); +} + +uint32 Account::GetNextPersistentId() +{ + uint32 result = kMinPersistendId; + const auto file = ActiveSettings::GetMlcPath("usr/save/system/act/persisid.dat"); + if(fs::exists(file)) + { + std::ifstream f(file); + if(f.is_open()) + { + std::string line; + while(std::getline(f, line)) + { + if(boost::starts_with(line, "PersistentIdHead=")) + { + result = ConvertString<uint32>(line.data() + sizeof("PersistentIdHead=") - 1, 16); + break; + } + } + } + } + + // next id + ++result; + + const auto it = std::max_element(s_account_list.cbegin(), s_account_list.cend(), [](const Account& acc1, const Account& acc2) {return acc1.GetPersistentId() < acc2.GetPersistentId(); }); + if (it != s_account_list.cend()) + return std::max(result, it->GetPersistentId() + 1); + else + return result; +} + +fs::path Account::GetFileName(uint32 persistent_id) +{ + if (persistent_id < kMinPersistendId) + throw std::invalid_argument(fmt::format("persistent id {:#x} is invalid", persistent_id)); + + return CemuApp::GetMLCPath(fmt::format(L"usr\\save\\system\\act\\{:08x}\\account.dat", persistent_id)).ToStdWstring(); +} + +OnlineValidator Account::ValidateOnlineFiles() const +{ + OnlineValidator result{}; + + const auto otp = ActiveSettings::GetPath("otp.bin"); + if (!fs::exists(otp)) + result.otp = OnlineValidator::FileState::Missing; + else if (fs::file_size(otp) != 1024) + result.otp = OnlineValidator::FileState::Corrupted; + else + result.otp = OnlineValidator::FileState::Ok; + + const auto seeprom = ActiveSettings::GetPath("seeprom.bin"); + if (!fs::exists(seeprom)) + result.seeprom = OnlineValidator::FileState::Missing; + else if (fs::file_size(seeprom) != 512) + result.seeprom = OnlineValidator::FileState::Corrupted; + else + result.seeprom = OnlineValidator::FileState::Ok; + + for(const auto& v : iosuCrypt_getCertificateKeys()) + { + const auto p = ActiveSettings::GetMlcPath(L"sys/title/0005001b/10054000/content/{}", v); + if (!fs::exists(p) || !fs::is_regular_file(p)) + result.missing_files.emplace_back(p.generic_wstring()); + } + + for (const auto& v : iosuCrypt_getCertificateNames()) + { + const auto p = ActiveSettings::GetMlcPath(L"sys/title/0005001b/10054000/content/{}", v); + if (!fs::exists(p) || !fs::is_regular_file(p)) + result.missing_files.emplace_back(p.generic_wstring()); + } + + result.valid_account = IsValidOnlineAccount(); + result.account_error = GetOnlineAccountError(); + + return result; +} + +void Account::ParseFile(class FileStream* file) +{ + std::vector<std::string> buffer; + + std::string tmp; + while (file->readLine(tmp)) + buffer.emplace_back(tmp); + for (const auto& s : buffer) + { + std::string_view view = s; + const auto find = view.find(L'='); + if (find == std::string_view::npos) + continue; + + const auto key = view.substr(0, find); + const auto value = view.substr(find + 1); + if (key == "PersistentId") + m_persistent_id = ConvertString<uint32>(value, 16); + else if (key == "TransferableIdBase") + m_transferable_id_base = ConvertString<uint64>(value, 16); + else if (key == "Uuid") + { + if (value.size() != m_uuid.size() * 2) // = 32 + throw std::system_error(AccountErrc::InvalidUuid); + + for (size_t i = 0; i < m_uuid.size(); ++i) + { + m_uuid[i] = ConvertString<uint8>(value.substr(i * 2, 2), 16); + } + } + else if (key == "MiiData") + { + if (value.size() != m_mii_data.size() * 2) // = 192 + throw std::system_error(AccountErrc::InvalidMiiData); + + for (size_t i = 0; i < m_mii_data.size(); ++i) + { + m_mii_data[i] = ConvertString<uint8>(value.substr(i * 2, 2), 16); + } + } + else if (key == "MiiName") + { + if(value.size() != m_mii_name.size() * 4) // = 44 + throw std::system_error(AccountErrc::InvalidMiiName); + + for (size_t i = 0; i < m_mii_name.size(); ++i) + { + m_mii_name[i] = (wchar_t)ConvertString<uint16>(value.substr(i * 4, 4), 16); + } + } + else if (key == "AccountId") + m_account_id = value; + else if (key == "BirthYear") + m_birth_year = ConvertString<uint16>(value, 16); + else if (key == "BirthMonth") + m_birth_month = ConvertString<uint8>(value, 16); + else if (key == "BirthDay") + m_birth_day = ConvertString<uint8>(value, 16); + else if (key == "Gender") + m_gender = ConvertString<uint8>(value, 16); + else if (key == "EmailAddress") + m_email = value; + else if (key == "Country") + m_country = ConvertString<uint32>(value, 16); + else if (key == "SimpleAddressId") + m_simple_address_id = ConvertString<uint32>(value, 16); + else if (key == "PrincipalId") + m_principal_id = ConvertString<uint32>(value, 16); + else if (key == "IsPasswordCacheEnabled") + m_password_cache_enabled = ConvertString<uint8>(value, 16); + else if (key == "AccountPasswordCache") + { + for (size_t i = 0; i < m_account_password_cache.size(); ++i) + { + m_account_password_cache[i] = ConvertString<uint8>(value.substr(i * 2, 2), 16); + } + } + else // store anything else not needed for now + m_storage[std::string(key)] = value; + } +} + +#include"openssl/sha.h" + +void makePWHash(uint8* input, sint32 length, uint32 magic, uint8* output) +{ + uint8 buffer[64 + 8]; + if (length > (sizeof(buffer) - 8)) + { + cemu_assert_debug(false); + memset(output, 0, 32); + return; + } + buffer[4] = 0x02; + buffer[5] = 0x65; + buffer[6] = 0x43; + buffer[7] = 0x46; + buffer[0] = (magic >> 0) & 0xFF; + buffer[1] = (magic >> 8) & 0xFF; + buffer[2] = (magic >> 16) & 0xFF; + buffer[3] = (magic >> 24) & 0xFF; + memcpy(buffer + 8, input, length); + uint8 md[32]; + SHA256(buffer, 8 + length, md); + memcpy(output, md, 32); +} + +void actPwTest() +{ + uint8 pwHash[32]; + + uint32 principalId = 0x12345678; + + uint32 pid = 0x12345678; + + makePWHash((uint8*)"pass123", 7, pid, pwHash); // calculates AccountPasswordCache + + makePWHash(pwHash, 32, pid, pwHash); // calculates AccountPasswordHash + + assert_dbg(); +} \ No newline at end of file diff --git a/src/Cafe/Account/Account.h b/src/Cafe/Account/Account.h new file mode 100644 index 00000000..63eb5082 --- /dev/null +++ b/src/Cafe/Account/Account.h @@ -0,0 +1,150 @@ +#pragma once + +#include "AccountError.h" + +#include <string> +#include <string_view> +#include <system_error> +#include <vector> +#include <optional> + +enum class OnlineAccountError +{ + kNone, + kNoAccountId, + kNoPasswordCached, + kPasswordCacheEmpty, + kNoPrincipalId, +}; +template <> +struct fmt::formatter<OnlineAccountError> : formatter<string_view> { + template <typename FormatContext> + auto format(const OnlineAccountError v, FormatContext& ctx) { + switch (v) + { + case OnlineAccountError::kNoAccountId: return formatter<string_view>::format("AccountId missing (The account is not connected to a NNID)", ctx); + case OnlineAccountError::kNoPasswordCached: return formatter<string_view>::format("IsPasswordCacheEnabled is set to false (The remember password option on your Wii U must be enabled for this account before dumping it)", ctx); + case OnlineAccountError::kPasswordCacheEmpty: return formatter<string_view>::format("AccountPasswordCache is empty (The remember password option on your Wii U must be enabled for this account before dumping it)", ctx); + case OnlineAccountError::kNoPrincipalId: return formatter<string_view>::format("PrincipalId missing", ctx); + default: break; + } + return formatter<string_view>::format("no error", ctx); + } +}; + + +struct OnlineValidator +{ + enum class FileState + { + Missing, + Corrupted, + Ok, + }; + + bool valid_account = false; + FileState otp = FileState::Missing; + FileState seeprom = FileState::Missing; + std::vector<std::wstring> missing_files; + OnlineAccountError account_error = OnlineAccountError::kNone; + + bool IsValid() const + { + return valid_account && otp == FileState::Ok && seeprom == FileState::Ok && missing_files.empty(); + } + + explicit operator bool() const + { + return IsValid(); + } +}; + +class Account +{ +public: + static constexpr uint32 kMinPersistendId = 0x80000001; + + // create dummy account object from scratch + Account(uint32 persistent_id, std::wstring_view mii_name); + + // load an existing account + Account(std::wstring_view file_name); + + std::error_code Load(); + std::error_code Save(); + + [[nodiscard]] std::wstring ToString() const { return fmt::format(L"{} ({:x})", GetMiiName(), GetPersistentId()); } + + // test if the account file has all fields set required for online play + [[nodiscard]] bool IsValidOnlineAccount() const; + [[nodiscard]] OnlineAccountError GetOnlineAccountError() const; + [[nodiscard]] fs::path GetFileName() const; + + [[nodiscard]] uint32 GetPersistentId() const { return m_persistent_id; } + [[nodiscard]] uint64 GetTransferableIdBase() const { return m_transferable_id_base; } + [[nodiscard]] const std::array<uint8, 16>& GetUuid() const { return m_uuid; } + [[nodiscard]] const std::array<uint8, 96>& GetMiiData() const { return m_mii_data; } + [[nodiscard]] std::wstring_view GetMiiName() const; // only max 10 characters excluding '\0' + [[nodiscard]] std::string_view GetAccountId() const { return m_account_id; } + [[nodiscard]] uint16 GetBirthYear() const { return m_birth_year; } + [[nodiscard]] uint8 GetBirthMonth() const { return m_birth_month; } + [[nodiscard]] uint8 GetBirthDay() const { return m_birth_day; } + [[nodiscard]] uint8 GetGender() const { return m_gender; } + [[nodiscard]] std::string_view GetEmail() const { return m_email; } + [[nodiscard]] uint32 GetCountry() const { return m_country; } + [[nodiscard]] uint32 GetSimpleAddressId() const { return m_simple_address_id; } + [[nodiscard]] uint32 GetPrincipalId() const { return m_principal_id; } + [[nodiscard]] bool IsPasswordCacheEnabled() const { return m_password_cache_enabled != 0; } + [[nodiscard]] const std::array<uint8, 32>& GetAccountPasswordCache() const { return m_account_password_cache; } + + [[nodiscard]] std::string_view GetStorageValue(std::string_view key) const; + + void SetMiiName(std::wstring_view name); + void SetBirthYear(uint16 birth_year) { m_birth_year = birth_year; } + void SetBirthMonth(uint8 birth_month) { m_birth_month = birth_month; } + void SetBirthDay(uint8 birth_day) { m_birth_day = birth_day; } + void SetGender(uint8 gender) { m_gender = gender; } + void SetEmail(std::string_view email) { m_email = email; } + void SetCountry(uint32 country) { m_country = country; } + + // this will always return at least one account (default one) + static const std::vector<Account>& RefreshAccounts(); + static void UpdatePersisidDat(); + + [[nodiscard]] static bool HasFreeAccountSlots(); + [[nodiscard]] static const std::vector<Account>& GetAccounts(); + [[nodiscard]] static const Account& GetAccount(uint32 persistent_id); + [[nodiscard]] static const Account& GetCurrentAccount(); + [[nodiscard]] static uint32 GetNextPersistentId(); + [[nodiscard]] static fs::path GetFileName(uint32 persistent_id); + [[nodiscard]] OnlineValidator ValidateOnlineFiles() const; +private: + Account(uint32 persistent_id); + + [[nodiscard]] std::error_code CheckValid() const; + void ParseFile(class FileStream* file); + + uint32 m_persistent_id = 0; + uint64 m_transferable_id_base = 0; + std::array<uint8, 16> m_uuid {}; + std::array<uint8, 96> m_mii_data{}; + std::array<wchar_t, 11> m_mii_name{}; + std::string m_account_id; + + uint16 m_birth_year = 0; + uint8 m_birth_month = 0; + uint8 m_birth_day = 0; + uint8 m_gender = 0; + + std::string m_email; + uint32 m_country = 0; + uint32 m_simple_address_id = 0; + uint32 m_principal_id = 0; + uint8 m_password_cache_enabled = 0; + std::array<uint8, 32> m_account_password_cache{}; + + // misc storage for unused local properties + std::unordered_map<std::string, std::string> m_storage; + + static std::vector<Account> s_account_list; +}; diff --git a/src/Cafe/Account/AccountError.h b/src/Cafe/Account/AccountError.h new file mode 100644 index 00000000..a6818c5c --- /dev/null +++ b/src/Cafe/Account/AccountError.h @@ -0,0 +1,60 @@ +#pragma once + +#include <system_error> + +enum class AccountErrc +{ + NoError = 0, + ParseError, + InvalidPersistentId, + InvalidUuid, + InvalidMiiName, + InvalidMiiData, +}; +namespace std +{ + template <> struct is_error_code_enum<AccountErrc> : true_type {}; +} +namespace detail +{ + // Define a custom error code category derived from std::error_category + class AccountErrc_category : public std::error_category + { + public: + // Return a short descriptive name for the category + [[nodiscard]] const char* name() const noexcept override final { return "AccountError"; } + + // Return what each enum means in text + [[nodiscard]] std::string message(int c) const override final + { + switch (static_cast<AccountErrc>(c)) + { + case AccountErrc::NoError: + return "no error"; + case AccountErrc::InvalidPersistentId: + return "invalid PersistentId"; + case AccountErrc::InvalidMiiName: + return "invalid MiiName"; + default: + return "unknown error"; + } + } + + [[nodiscard]] std::error_condition default_error_condition(int c) const noexcept override final + { + switch (static_cast<AccountErrc>(c)) + { + case AccountErrc::InvalidPersistentId: + return make_error_condition(std::errc::invalid_argument); + default: + return std::error_condition(c, *this); + } + } + }; +} + +inline std::error_code make_error_code(AccountErrc e) +{ + static detail::AccountErrc_category c; + return { static_cast<int>(e), c }; +} \ No newline at end of file diff --git a/src/Cafe/CMakeLists.txt b/src/Cafe/CMakeLists.txt new file mode 100644 index 00000000..ff0f3e11 --- /dev/null +++ b/src/Cafe/CMakeLists.txt @@ -0,0 +1,29 @@ +project(CemuCafe) + +include_directories(".") +file(GLOB_RECURSE CPP_FILES *.cpp) +file(GLOB_RECURSE H_FILES *.h) +add_library(CemuCafe ${CPP_FILES} ${H_FILES}) + +set_property(TARGET CemuCafe PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>") + +target_precompile_headers(CemuCafe PRIVATE ../Common/precompiled.h) + +target_include_directories(CemuCafe PRIVATE ../) + +#target_link_libraries(CemuCafe ZArchivexx) +#target_link_libraries(CemuCafe CemuCommon CemuCore CemuConfig CemuUtil CemuResource) +#target_link_libraries(CemuCafe OpenSSL::SSL) +#target_link_libraries(CemuCafe ZLIB::ZLIB) +#target_link_libraries(CemuCafe imgui::imgui) +#target_link_libraries(CemuCafe imguiImpl) +#target_link_libraries(CemuCafe pugixml pugixml::static pugixml::pugixml) +#target_link_libraries(CemuCafe libzip::zip) +target_link_libraries(CemuCafe glslang SPIRV) +target_link_libraries(CemuCafe ih264d zarchive) +#target_link_libraries(CemuCafe zstd::libzstd_static) + + +IF(WIN32) +target_link_libraries(CemuCafe iphlpapi) +ENDIF() \ No newline at end of file diff --git a/src/Cafe/CafeSystem.cpp b/src/Cafe/CafeSystem.cpp new file mode 100644 index 00000000..53a9ec6a --- /dev/null +++ b/src/Cafe/CafeSystem.cpp @@ -0,0 +1,859 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "gui/wxgui.h" +#include "Cafe/OS/libs/gx2/GX2.h" +#include "Cafe/GameProfile/GameProfile.h" +#include "Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h" +#include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h" +#include "audio/IAudioAPI.h" +#include "Cafe/HW/Espresso/Debugger/Debugger.h" + +#include "config/ActiveSettings.h" +#include "Cafe/TitleList/GameInfo.h" +#include "util/helpers/SystemException.h" +#include "Cafe/GraphicPack/GraphicPack.h" + +#include "input/InputManager.h" + +#include "Cafe/CafeSystem.h" +#include "Cafe/TitleList/TitleList.h" +#include "Cafe/TitleList/GameInfo.h" +#include "Cafe/OS/libs/coreinit/coreinit_Alarm.h" +#include "Cafe/OS/libs/snd_core/ax.h" +#include "Cafe/OS/RPL/rpl.h" +#include "Cafe/HW/Latte/Core/Latte.h" + +#include "Cafe/Filesystem/FST/FST.h" + +#include "Common/filestream.h" + +#include "GamePatch.h" + +#include <time.h> + +#include "Cafe/IOSU/legacy/iosu_ioctl.h" +#include "Cafe/IOSU/legacy/iosu_act.h" +#include "Cafe/IOSU/legacy/iosu_fpd.h" +#include "Cafe/IOSU/legacy/iosu_crypto.h" +#include "Cafe/IOSU/legacy/iosu_mcp.h" +#include "Cafe/IOSU/legacy/iosu_acp.h" +#include "Cafe/IOSU/legacy/iosu_boss.h" +#include "Cafe/IOSU/legacy/iosu_nim.h" +#include "Cafe/IOSU/PDM/iosu_pdm.h" + +// IOSU initializer functions +#include "Cafe/IOSU/kernel/iosu_kernel.h" +#include "Cafe/IOSU/fsa/iosu_fsa.h" + +// Cafe OS initializer functions +#include "Cafe/OS/libs/avm/avm.h" +#include "Cafe/OS/libs/drmapp/drmapp.h" +#include "Cafe/OS/libs/TCL/TCL.h" +#include "Cafe/OS/libs/snd_user/snd_user.h" +#include "Cafe/OS/libs/h264_avc/h264dec.h" +#include "Cafe/OS/libs/snd_core/ax.h" +#include "Cafe/OS/libs/gx2/GX2.h" +#include "Cafe/OS/libs/gx2/GX2_Misc.h" +#include "Cafe/OS/libs/mic/mic.h" +#include "Cafe/OS/libs/nn_aoc/nn_aoc.h" +#include "Cafe/OS/libs/nn_pdm/nn_pdm.h" +#include "Cafe/OS/libs/nn_cmpt/nn_cmpt.h" +#include "Cafe/OS/libs/nn_ccr/nn_ccr.h" +#include "Cafe/OS/libs/nn_temp/nn_temp.h" + +// HW interfaces +#include "Cafe/HW/SI/si.h" + +// dependency to be removed +#include "gui/guiWrapper.h" + +std::string _pathToExecutable; +std::string _pathToBaseExecutable; + +RPLModule* applicationRPX = nullptr; +uint32 currentBaseApplicationHash = 0; +uint32 currentUpdatedApplicationHash = 0; + +bool isLaunchTypeELF = false; + +MPTR _entryPoint = MPTR_NULL; + +uint32 generateHashFromRawRPXData(uint8* rpxData, sint32 size) +{ + uint32 h = 0x3416DCBF; + for (sint32 i = 0; i < size; i++) + { + uint32 c = rpxData[i]; + h = (h << 3) | (h >> 29); + h += c; + } + return h; +} + +bool ScanForRPX() +{ + bool rpxFound = false; + sint32 fscStatus = 0; + FSCVirtualFile* fscDirItr = fsc_openDirIterator("/internal/current_title/code/", &fscStatus); + if (fscDirItr) + { + FSCDirEntry dirEntry; + while (fsc_nextDir(fscDirItr, &dirEntry)) + { + sint32 dirItrPathLen = strlen(dirEntry.path); + if (dirItrPathLen < 4) + continue; + if (boost::iequals(dirEntry.path + dirItrPathLen - 4, ".rpx")) + { + rpxFound = true; + _pathToExecutable = fmt::format("/internal/current_title/code/{}", dirEntry.path); + break; + } + } + fsc_close(fscDirItr); + } + return rpxFound; +} + +void SetEntryPoint(MPTR entryPoint) +{ + _entryPoint = entryPoint; +} + +// load executable into virtual memory and set entrypoint +void LoadMainExecutable() +{ + isLaunchTypeELF = false; + // when launching from a disc image _pathToExecutable is initially empty + if (_pathToExecutable.empty()) + { + // try to get the RPX path from the meta files + // todo + // otherwise search for first file with .rpx extension in the code folder + if (!ScanForRPX()) + { + forceLog_printf("Unable to find RPX executable"); + cemuLog_waitForFlush(); + cemu_assert(false); + } + } + // extract and load RPX + uint32 rpxSize = 0; + uint8* rpxData = fsc_extractFile(_pathToExecutable.c_str(), &rpxSize); + if (rpxData == nullptr) + { + forceLog_printf("Failed to load \"%s\"", _pathToExecutable.c_str()); + cemuLog_waitForFlush(); + cemu_assert(false); + } + currentUpdatedApplicationHash = generateHashFromRawRPXData(rpxData, rpxSize); + // determine if this file is an ELF + const uint8 elfHeaderMagic[9] = { 0x7F,0x45,0x4C,0x46,0x01,0x02,0x01,0x00,0x00 }; + if (rpxSize >= 10 && memcmp(rpxData, elfHeaderMagic, sizeof(elfHeaderMagic)) == 0) + { + // ELF + SetEntryPoint(ELF_LoadFromMemory(rpxData, rpxSize, _pathToExecutable.c_str())); + isLaunchTypeELF = true; + } + else + { + // RPX + RPLLoader_AddDependency(_pathToExecutable.c_str()); + applicationRPX = rpl_loadFromMem(rpxData, rpxSize, (char*)_pathToExecutable.c_str()); + if (!applicationRPX) + { + wxMessageBox(_("Failed to run this title because the executable is damaged")); + cemuLog_createLogFile(false); + cemuLog_waitForFlush(); + exit(0); + } + RPLLoader_SetMainModule(applicationRPX); + SetEntryPoint(RPLLoader_GetModuleEntrypoint(applicationRPX)); + } + free(rpxData); + // get RPX hash of game without update + uint32 baseRpxSize = 0; + uint8* baseRpxData = fsc_extractFile(!_pathToBaseExecutable.empty() ? _pathToBaseExecutable.c_str() : _pathToExecutable.c_str(), &baseRpxSize, FSC_PRIORITY_BASE); + if (baseRpxData == nullptr) + { + currentBaseApplicationHash = currentUpdatedApplicationHash; + } + else + { + currentBaseApplicationHash = generateHashFromRawRPXData(baseRpxData, baseRpxSize); + } + free(baseRpxData); + debug_printf("RPXHash: 0x%08x\n", currentBaseApplicationHash); +} + +fs::path getTitleSavePath() +{ + const uint64 titleId = CafeSystem::GetForegroundTitleId(); + return ActiveSettings::GetMlcPath("usr/save/{:08X}/{:08X}/user/", (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF)); +} + +void InfoLog_TitleLoaded() +{ + cemuLog_createLogFile(false); + uint64 titleId = CafeSystem::GetForegroundTitleId(); + cemuLog_log(LogType::Force, "------- Loaded title -------"); + cemuLog_log(LogType::Force, "TitleId: {:08x}-{:08x}", (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF)); + cemuLog_log(LogType::Force, "TitleVersion: v{}", CafeSystem::GetForegroundTitleVersion()); + CafeConsoleRegion region = CafeSystem::GetForegroundTitleRegion(); + if(region == CafeConsoleRegion::JPN) + cemuLog_log(LogType::Force, "TitleRegion: JP"); + else if (region == CafeConsoleRegion::EUR) + cemuLog_log(LogType::Force, "TitleRegion: EU"); + else if (region == CafeConsoleRegion::USA) + cemuLog_log(LogType::Force, "TitleRegion: US"); + + fs::path effectiveSavePath = getTitleSavePath(); + std::error_code ec; + const bool saveDirExists = fs::exists(effectiveSavePath, ec); + cemuLog_force("Save path: {}{}", _utf8Wrapper(effectiveSavePath), saveDirExists ? "" : " (not present)"); + + // log shader cache name + cemuLog_log(LogType::Force, "Shader cache file: shaderCache/transferable/{:016x}.bin", titleId); + // game profile info + std::string gameProfilePath; + if(g_current_game_profile->IsDefaultProfile()) + gameProfilePath = fmt::format("gameProfiles/default/{:016x}.ini", titleId); + else + gameProfilePath = fmt::format("gameProfiles/{:016x}.ini", titleId); + cemuLog_log(LogType::Force, "gameprofile path: {}", g_current_game_profile->IsLoaded() ? gameProfilePath : std::string(" (not present)")); + // rpx hash of updated game + cemuLog_log(LogType::Force, "RPX hash (updated): {:08x}", currentUpdatedApplicationHash); + cemuLog_log(LogType::Force, "RPX hash (base): {:08x}", currentBaseApplicationHash); + + memory_logModifiedMemoryRanges(); +} + +void InfoLog_PrintActiveSettings() +{ + const auto& config = GetConfig(); + forceLog_printf("------- Active settings -------"); + + // settings to log: + forceLog_printf("CPU-Mode: %s%s", fmt::format("{}", ActiveSettings::GetCPUMode()).c_str(), g_current_game_profile->GetCPUMode().has_value() ? " (gameprofile)" : ""); + forceLog_printf("Load shared libraries: %s%s", ActiveSettings::LoadSharedLibrariesEnabled() ? "true" : "false", g_current_game_profile->ShouldLoadSharedLibraries().has_value() ? " (gameprofile)" : ""); + forceLog_printf("Use precompiled shaders: %s%s", fmt::format("{}", ActiveSettings::GetPrecompiledShadersOption()).c_str(), g_current_game_profile->GetPrecompiledShadersState().has_value() ? " (gameprofile)" : ""); + forceLog_printf("Full sync at GX2DrawDone: %s", ActiveSettings::WaitForGX2DrawDoneEnabled() ? "true" : "false"); + if (ActiveSettings::GetGraphicsAPI() == GraphicAPI::kVulkan) + { + forceLog_printf("Async compile: %s", GetConfig().async_compile.GetValue() ? "true" : "false"); + if(!GetConfig().vk_accurate_barriers.GetValue()) + forceLog_printf("Accurate barriers are disabled!"); + } + + forceLog_printf("Console language: %s", fmt::format("{}", config.console_language).c_str()); +} + +void PPCCore_setupSPR(PPCInterpreter_t* hCPU, uint32 coreIndex) +{ + hCPU->sprExtended.PVR = 0x70010001; + hCPU->spr.UPIR = coreIndex; + hCPU->sprExtended.msr |= MSR_FP; // enable floating point +} + +struct SharedDataEntry +{ + /* +0x00 */ uint32be name; + /* +0x04 */ uint32be fileType; // 2 = font + /* +0x08 */ uint32be kernelFilenamePtr; + /* +0x0C */ MEMPTR<void> data; + /* +0x10 */ uint32be size; + /* +0x14 */ uint32be ukn14; + /* +0x18 */ uint32be ukn18; +}; + +struct +{ + uint32 name; + uint32 fileType; + const char* fileName; + const char* resourcePath; + const char* mlcPath; +}shareddataDef[] = +{ + 0xFFCAFE01, 2, "CafeCn.ttf", "resources/sharedFonts/CafeCn.ttf", "sys/title/0005001b/10042400/content/CafeCn.ttf", + 0xFFCAFE02, 2, "CafeKr.ttf", "resources/sharedFonts/CafeKr.ttf", "sys/title/0005001b/10042400/content/CafeKr.ttf", + 0xFFCAFE03, 2, "CafeStd.ttf", "resources/sharedFonts/CafeStd.ttf", "sys/title/0005001b/10042400/content/CafeStd.ttf", + 0xFFCAFE04, 2, "CafeTw.ttf", "resources/sharedFonts/CafeTw.ttf", "sys/title/0005001b/10042400/content/CafeTw.ttf" +}; + +static_assert(sizeof(SharedDataEntry) == 0x1C); + +__declspec(dllexport) uint32 loadSharedData() +{ + // check if font files are dumped + bool hasAllShareddataFiles = true; + for (sint32 i = 0; i < sizeof(shareddataDef) / sizeof(shareddataDef[0]); i++) + { + bool existsInMLC = fs::exists(ActiveSettings::GetMlcPath(shareddataDef[i].mlcPath)); + bool existsInResources = fs::exists(ActiveSettings::GetPath(shareddataDef[i].resourcePath)); + + if (!existsInMLC && !existsInResources) + { + cemuLog_log(LogType::Force, "Shared font {} is not present", shareddataDef[i].fileName); + hasAllShareddataFiles = false; + break; + } + } + sint32 numEntries = sizeof(shareddataDef) / sizeof(shareddataDef[0]); + if (hasAllShareddataFiles) + { + // all shareddata font files are present -> load them + SharedDataEntry* shareddataTable = (SharedDataEntry*)memory_getPointerFromVirtualOffset(0xF8000000); + memset(shareddataTable, 0, sizeof(SharedDataEntry) * numEntries); + uint8* dataWritePtr = memory_getPointerFromVirtualOffset(0xF8000000 + sizeof(SharedDataEntry) * numEntries); + // setup entries + for (sint32 i = 0; i < numEntries; i++) + { + // try to read font from MLC first + auto path = ActiveSettings::GetMlcPath(shareddataDef[i].mlcPath); + FileStream* fontFile = FileStream::openFile2(path); + // alternatively fall back to our shared fonts + if (!fontFile) + { + path = ActiveSettings::GetPath(shareddataDef[i].resourcePath); + fontFile = FileStream::openFile2(path); + } + if (!fontFile) + { + cemuLog_log(LogType::Force, "Failed to load shared font {}", shareddataDef[i].fileName); + continue; + } + uint32 fileSize = fontFile->GetSize(); + fontFile->readData(dataWritePtr, fileSize); + delete fontFile; + // setup entry + shareddataTable[i].name = shareddataDef[i].name; + shareddataTable[i].fileType = shareddataDef[i].fileType; + shareddataTable[i].kernelFilenamePtr = 0x00000000; + shareddataTable[i].data = dataWritePtr; + shareddataTable[i].size = fileSize; + shareddataTable[i].ukn14 = 0x00000000; + shareddataTable[i].ukn18 = 0x00000000; + // advance write offset and pad to 16 byte alignment + dataWritePtr += ((fileSize + 15) & ~15); + } + forceLog_printfW(L"COS: System fonts found. Generated shareddata (%dKB)", (uint32)(dataWritePtr - (uint8*)shareddataTable) / 1024); + return memory_getVirtualOffsetFromPointer(dataWritePtr); + } + // alternative method: load RAM dump + const auto path = ActiveSettings::GetPath("shareddata.bin"); + FileStream* ramDumpFile = FileStream::openFile2(path); + if (ramDumpFile) + { + ramDumpFile->readData(memory_getPointerFromVirtualOffset(0xF8000000), 0x02000000); + delete ramDumpFile; + return (mmuRange_SHARED_AREA.getBase() + 0x02000000); + } + return mmuRange_SHARED_AREA.getBase() + sizeof(SharedDataEntry) * numEntries; +} + +void cemu_initForGame() +{ + gui_updateWindowTitles(false, true, 0.0); + // input manager apply game profile + InputManager::instance().apply_game_profile(); + // log info for launched title + InfoLog_TitleLoaded(); + // determine cycle offset since 1.1.2000 + uint64 secondsSince2000_UTC = (uint64)(time(NULL) - 946684800); + ppcCyclesSince2000_UTC = secondsSince2000_UTC * (uint64)ESPRESSO_CORE_CLOCK; + time_t theTime = (time(NULL) - 946684800); + { + tm* lt = localtime(&theTime); +#if BOOST_OS_WINDOWS > 0 + theTime = _mkgmtime(lt); +#else + theTime = timegm(lt); +#endif + } + ppcCyclesSince2000 = theTime * (uint64)ESPRESSO_CORE_CLOCK; + ppcCyclesSince2000TimerClock = ppcCyclesSince2000 / 20ULL; + PPCTimer_start(); + // this must happen after the RPX/RPL files are mapped to memory (coreinit sets up heaps so that they don't overwrite RPX/RPL data) + osLib_load(); + // link all modules + uint32 linkTimeStart = GetTickCount(); + RPLLoader_UpdateDependencies(); + RPLLoader_Link(); + RPLLoader_NotifyControlPassedToApplication(); + uint32 linkTime = GetTickCount() - linkTimeStart; + forceLog_printf("RPL link time: %dms", linkTime); + // for HBL ELF: Setup OS-specifics struct + if (isLaunchTypeELF) + { + memory_writeU32(0x801500, rpl_mapHLEImport(nullptr, "coreinit", "OSDynLoad_Acquire", true)); + memory_writeU32(0x801504, rpl_mapHLEImport(nullptr, "coreinit", "OSDynLoad_FindExport", true)); + } + else + { + // replace any known function signatures with our HLE implementations and patch bugs in the games + GamePatch_scan(); + } + InfoLog_PrintActiveSettings(); + Latte_Start(); + // check for debugger entrypoint bp + debugger_handleEntryBreakpoint(_entryPoint); + // load graphic packs + forceLog_printf("------- Activate graphic packs -------"); + graphicPack_activateForCurrentTitle(CafeSystem::GetForegroundTitleId()); + // print audio log + IAudioAPI::PrintLogging(); + // everything initialized + forceLog_printf("------- Run title -------"); + // wait till GPU thread is initialized + while (g_isGPUInitFinished == false) std::this_thread::sleep_for(std::chrono::milliseconds(50)); + // init initial thread + OSThread_t* initialThread = coreinit::OSGetDefaultThread(1); + coreinit::OSSetThreadPriority(initialThread, 16); + coreinit::OSRunThread(initialThread, PPCInterpreter_makeCallableExportDepr(coreinit_start), 0, nullptr); + // init AX and start AX I/O thread + snd_core::AXOut_init(); + // init ppc recompiler + PPCRecompiler_init(); +} + +void cemu_deinitForGame() +{ + // reset audio + snd_core::AXOut_reset(); + snd_core::reset(); + // reset alarms + coreinit::OSAlarm_resetAll(); + // delete all threads + PPCCore_deleteAllThreads(); + // reset mount paths + fsc_unmountAll(); + // reset RPL loader + RPLLoader_ResetState(); + // reset GX2 + GX2::_GX2DriverReset(); +} + +namespace CafeSystem +{ + void InitVirtualMlcStorage(); + void MlcStorageMountTitle(TitleInfo& titleInfo); + + bool sLaunchModeIsStandalone = false; + + bool sSystemRunning = false; + TitleId sForegroundTitleId = 0; + + GameInfo2 sGameInfo_ForegroundTitle; + + void Initialize() + { + static bool s_initialized = false; + if (s_initialized) + return; + s_initialized = true; + // allocate memory for all SysAllocators + // must happen before all COS modules, but also before iosu::kernel::Init() + SysAllocatorContainer::GetInstance().Initialize(); + // init IOSU + iosu::kernel::Initialize(); + iosu::fsa::Initialize(); + iosuIoctl_init(); + iosuAct_init_depr(); + iosu::act::Initialize(); + iosu::fpd::Initialize(); + iosu::iosuMcp_init(); + iosu::mcp::Init(); + iosu::iosuAcp_init(); + iosu::boss_init(); + iosu::nim::Initialize(); + iosu::pdm::Initialize(); + // init Cafe OS + avm::Initialize(); + drmapp::Initialize(); + TCL::Initialize(); + nn::cmpt::Initialize(); + nn::ccr::Initialize(); + nn::temp::Initialize(); + nn::aoc::Initialize(); + nn::pdm::Initialize(); + snd::user::Initialize(); + H264::Initialize(); + snd_core::Initialize(); + mic::Initialize(); + // init hardware register interfaces + HW_SI::Initialize(); + } + + std::string GetInternalVirtualCodeFolder() + { + return "/internal/current_title/code/"; + } + + STATUS_CODE LoadAndMountForegroundTitle(TitleId titleId) + { + cemuLog_log(LogType::Force, "Mounting title {:016x}", (uint64)titleId); + sGameInfo_ForegroundTitle = CafeTitleList::GetGameInfo(titleId); + if (!sGameInfo_ForegroundTitle.IsValid()) + { + cemuLog_log(LogType::Force, "Mounting failed: Game meta information is either missing, inaccessible or not valid (missing or invalid .xml files in code and meta folder)"); + return STATUS_CODE::UNABLE_TO_MOUNT; + } + // check base + TitleInfo& titleBase = sGameInfo_ForegroundTitle.GetBase(); + if (!titleBase.IsValid()) + return STATUS_CODE::UNABLE_TO_MOUNT; + if(!titleBase.ParseXmlInfo()) + return STATUS_CODE::UNABLE_TO_MOUNT; + cemuLog_log(LogType::Force, "Base: {}", titleBase.GetPrintPath()); + // mount base + if (!titleBase.Mount("/vol/content", "content", FSC_PRIORITY_BASE) || !titleBase.Mount(GetInternalVirtualCodeFolder(), "code", FSC_PRIORITY_BASE)) + { + cemuLog_log(LogType::Force, "Mounting failed"); + return STATUS_CODE::UNABLE_TO_MOUNT; + } + // check update + TitleInfo& titleUpdate = sGameInfo_ForegroundTitle.GetUpdate(); + if (titleUpdate.IsValid()) + { + if (!titleUpdate.ParseXmlInfo()) + return STATUS_CODE::UNABLE_TO_MOUNT; + cemuLog_log(LogType::Force, "Update: {}", titleUpdate.GetPrintPath()); + // mount update + if (!titleUpdate.Mount("/vol/content", "content", FSC_PRIORITY_PATCH) || !titleUpdate.Mount(GetInternalVirtualCodeFolder(), "code", FSC_PRIORITY_PATCH)) + { + cemuLog_log(LogType::Force, "Mounting failed"); + return STATUS_CODE::UNABLE_TO_MOUNT; + } + } + else + cemuLog_log(LogType::Force, "Update: Not present"); + // check AOC + auto aocList = sGameInfo_ForegroundTitle.GetAOC(); + if (!aocList.empty()) + { + // todo - support for multi-title AOC + TitleInfo& titleAOC = aocList[0]; + if (!titleAOC.ParseXmlInfo()) + return STATUS_CODE::UNABLE_TO_MOUNT; + cemu_assert_debug(titleAOC.IsValid()); + cemuLog_log(LogType::Force, "DLC: {}", titleAOC.GetPrintPath()); + // mount AOC + if (!titleAOC.Mount(fmt::format("/vol/aoc{:016x}", titleAOC.GetAppTitleId()), "content", FSC_PRIORITY_PATCH)) + { + cemuLog_log(LogType::Force, "Mounting failed"); + return STATUS_CODE::UNABLE_TO_MOUNT; + } + } + else + cemuLog_log(LogType::Force, "DLC: Not present"); + sForegroundTitleId = titleId; + return STATUS_CODE::SUCCESS; + } + + STATUS_CODE SetupExecutable() + { + // mount mlc directories + fscDeviceHostFS_mapBaseDirectories_deprecated(); + // set rpx path from cos.xml if available + _pathToBaseExecutable = _pathToExecutable; + if (!sLaunchModeIsStandalone) + { + std::string _argstr = CafeSystem::GetForegroundTitleArgStr(); + const char* argstr = _argstr.c_str(); + if (argstr && *argstr != '\0') + { + const std::string tmp = argstr; + const auto index = tmp.find(".rpx"); + if (index != std::string::npos) + { + fs::path rpx = _pathToExecutable; + rpx.replace_filename(tmp.substr(0, index + 4)); // cut off after .rpx + + std::string rpxPath; + rpxPath = "/internal/current_title/code/"; + rpxPath.append(rpx.generic_string()); + + int status; + const auto file = fsc_open(rpxPath.c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &status); + if (file) + { + _pathToExecutable = rpxPath; + fsc_close(file); + } + } + } + } + LoadMainExecutable(); + gameProfile_load(); + return STATUS_CODE::SUCCESS; + } + + STATUS_CODE PrepareForegroundTitle(TitleId titleId) + { + CafeTitleList::WaitForMandatoryScan(); + sLaunchModeIsStandalone = false; + TitleIdParser tip(titleId); + if (tip.GetType() == TitleIdParser::TITLE_TYPE::AOC || tip.GetType() == TitleIdParser::TITLE_TYPE::BASE_TITLE_UPDATE) + cemuLog_log(LogType::Force, "Launched titleId is not the base of a title"); + + // mount title folders + STATUS_CODE r = LoadAndMountForegroundTitle(titleId); + if (r != STATUS_CODE::SUCCESS) + return r; + // map memory + memory_mapForCurrentTitle(); + // load RPX + r = SetupExecutable(); + if (r != STATUS_CODE::SUCCESS) + return r; + + loadSharedData(); + InitVirtualMlcStorage(); + return STATUS_CODE::SUCCESS; + } + + STATUS_CODE PrepareForegroundTitleFromStandaloneRPX(const fs::path& path) + { + sLaunchModeIsStandalone = true; + cemuLog_log(LogType::Force, "Launching executable in standalone mode due to incorrect layout or missing meta files"); + fs::path executablePath = path; + std::string dirName = _utf8Wrapper(executablePath.parent_path().filename()); + if (boost::iequals(dirName, "code")) + { + // check for content folder + fs::path contentPath = executablePath.parent_path().parent_path().append("content"); + std::error_code ec; + if (fs::is_directory(contentPath, ec)) + { + // mounting content folder + bool r = FSCDeviceHostFS_Mount(std::string("/vol/content").c_str(), boost::nowide::widen(_utf8Wrapper(contentPath)).c_str(), FSC_PRIORITY_BASE); + if (!r) + { + cemuLog_log(LogType::Force, "Failed to mount {}", _utf8Wrapper(contentPath).c_str()); + return STATUS_CODE::UNABLE_TO_MOUNT; + } + } + } + // mount code folder to a virtual temporary path + FSCDeviceHostFS_Mount(std::string("/internal/code/").c_str(), boost::nowide::widen(_utf8Wrapper(executablePath.parent_path())).c_str(), FSC_PRIORITY_BASE); + std::string internalExecutablePath = "/internal/code/"; + internalExecutablePath.append(_utf8Wrapper(executablePath.filename())); + _pathToExecutable = internalExecutablePath; + // since a lot of systems (including save folder location) rely on a TitleId, we derive a placeholder id from the executable hash + auto execData = fsc_extractFile(_pathToExecutable.c_str()); + if (!execData) + return STATUS_CODE::INVALID_RPX; + uint32 h = generateHashFromRawRPXData(execData->data(), execData->size()); + sForegroundTitleId = 0xFFFFFFFF00000000ULL | (uint64)h; + cemuLog_log(LogType::Force, "Generated placeholder TitleId: {:016x}", sForegroundTitleId); + // load executable + memory_mapForCurrentTitle(); + SetupExecutable(); + loadSharedData(); + InitVirtualMlcStorage(); + return STATUS_CODE::SUCCESS; + } + + void _LaunchTitleThread() + { + // init + cemu_initForGame(); + // enter scheduler + if (ActiveSettings::GetCPUMode() == CPUMode::MulticoreRecompiler) + coreinit::OSSchedulerBegin(3); + else + coreinit::OSSchedulerBegin(1); + iosu::pdm::StartTrackingTime(GetForegroundTitleId()); + } + + void LaunchForegroundTitle() + { + PPCTimer_waitForInit(); + // start system + sSystemRunning = true; + gui_notifyGameLoaded(); + std::thread t(_LaunchTitleThread); + t.detach(); + } + + bool IsTitleRunning() + { + return sSystemRunning; + } + + TitleId GetForegroundTitleId() + { + cemu_assert_debug(sForegroundTitleId != 0); + return sForegroundTitleId; + } + + uint16 GetForegroundTitleVersion() + { + if (sLaunchModeIsStandalone) + return 0; + return sGameInfo_ForegroundTitle.GetVersion(); + } + + CafeConsoleRegion GetForegroundTitleRegion() + { + if (sLaunchModeIsStandalone) + return CafeConsoleRegion::USA; + return sGameInfo_ForegroundTitle.GetRegion(); + } + + std::string GetForegroundTitleName() + { + if (sLaunchModeIsStandalone) + return "Missing meta data"; + // todo - use language based on Cemu console language + return sGameInfo_ForegroundTitle.GetBase().GetMetaInfo()->GetShortName(CafeConsoleLanguage::EN); + } + + std::string GetForegroundTitleArgStr() + { + if (sLaunchModeIsStandalone) + return ""; + auto& update = sGameInfo_ForegroundTitle.GetUpdate(); + if (update.IsValid()) + return update.GetArgStr(); + return sGameInfo_ForegroundTitle.GetBase().GetArgStr(); + } + + // pick platform region based on title region + CafeConsoleRegion GetPlatformRegion() + { + CafeConsoleRegion titleRegion = GetForegroundTitleRegion(); + CafeConsoleRegion platformRegion = CafeConsoleRegion::USA; + if (HAS_FLAG(titleRegion, CafeConsoleRegion::JPN)) + platformRegion = CafeConsoleRegion::JPN; + else if (HAS_FLAG(titleRegion, CafeConsoleRegion::EUR)) + platformRegion = CafeConsoleRegion::EUR; + else if (HAS_FLAG(titleRegion, CafeConsoleRegion::USA)) + platformRegion = CafeConsoleRegion::USA; + return platformRegion; + } + + void UnmountCurrentTitle() + { + TitleInfo& titleBase = sGameInfo_ForegroundTitle.GetBase(); + if (titleBase.IsValid()) + titleBase.UnmountAll(); + if (sGameInfo_ForegroundTitle.HasUpdate()) + { + TitleInfo& titleUpdate = sGameInfo_ForegroundTitle.GetUpdate(); + if (titleUpdate.IsValid()) + titleUpdate.UnmountAll(); + } + if (sGameInfo_ForegroundTitle.HasAOC()) + { + auto titleInfoList = sGameInfo_ForegroundTitle.GetAOC(); + for(auto& it : titleInfoList) + { + if (it.IsValid()) + it.UnmountAll(); + } + } + fsc_unmount("/internal/code/", FSC_PRIORITY_BASE); + } + + void ShutdownTitle() + { + if(!sSystemRunning) + return; + coreinit::OSSchedulerEnd(); + Latte_Stop(); + iosu::pdm::Stop(); + iosu::act::Stop(); + iosu::mcp::Shutdown(); + iosu::fsa::Shutdown(); + UnmountCurrentTitle(); + sSystemRunning = false; + } + + /* Virtual mlc storage */ + + void InitVirtualMlcStorage() + { + // starting with Cemu 1.27.0 /vol/storage_mlc01/ is virtualized, meaning that it doesn't point to one singular host os folder anymore + // instead it now uses a more complex solution to source titles with various formats (folder, wud, wua) from the game paths and host mlc path + + // todo - mount /vol/storage_mlc01/ with base priority to the host mlc? + + // since mounting titles is an expensive operation we have to avoid mounting all titles at once + // only the current title gets mounted immediately, every other title should be mounted lazily on first access + + // always mount the currently running title + if (sGameInfo_ForegroundTitle.GetBase().IsValid()) + MlcStorageMountTitle(sGameInfo_ForegroundTitle.GetBase()); + if (sGameInfo_ForegroundTitle.GetUpdate().IsValid()) + MlcStorageMountTitle(sGameInfo_ForegroundTitle.GetUpdate()); + for(auto& it : sGameInfo_ForegroundTitle.GetAOC()) + MlcStorageMountTitle(it); + + // setup system for lazy-mounting of other known titles + // todo - how to handle this? + // when something iterates /vol/storage_mlc01/usr/title/ we can use a fake FS device mounted to /vol/storage_mlc01/usr/title and sys/title that simulates the title id folders + // the same device would then have to mount titles when their folders are actually accessed + } + + // /vol/storage_mlc01/<usr or sys>/title/<titleIdHigh>/<titleIdLow> + std::string GetMlcStoragePath(TitleId titleId) + { + TitleIdParser tip(titleId); + return fmt::format("/vol/storage_mlc01/{}/title/{:08x}/{:08x}", tip.IsSystemTitle() ? "sys" : "usr", (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF)); + } + + std::map<TitleId, TitleInfo*> m_mlcMountedTitles; + + // mount title to our virtual MLC storage + // /vol/storage_mlc01/<usr or sys>/title/<titleIdHigh>/<titleIdLow> + void MlcStorageMountTitle(TitleInfo& titleInfo) + { + if (!titleInfo.IsValid()) + { + cemu_assert_suspicious(); + return; + } + TitleId titleId = titleInfo.GetAppTitleId(); + if (m_mlcMountedTitles.find(titleId) != m_mlcMountedTitles.end()) + { + cemu_assert_suspicious(); // already mounted + return; + } + std::string mlcStoragePath = GetMlcStoragePath(titleId); + TitleInfo* mountTitleInfo = new TitleInfo(titleInfo); + if (!mountTitleInfo->Mount(mlcStoragePath, "", FSC_PRIORITY_BASE)) + { + cemuLog_log(LogType::Force, "Failed to mount title to virtual storage"); + delete mountTitleInfo; + return; + } + m_mlcMountedTitles.emplace(titleId, mountTitleInfo); + } + + void MlcStorageMountTitle(TitleId titleId) + { + TitleInfo titleInfo; + if (!CafeTitleList::GetFirstByTitleId(titleId, titleInfo)) + return; + MlcStorageMountTitle(titleInfo); + } + + void MlcStorageMountAllTitles() + { + std::vector<uint64> titleIds = CafeTitleList::GetAllTitleIds(); + for (auto& it : titleIds) + MlcStorageMountTitle(it); + } + + uint32 GetRPXHashBase() + { + return currentBaseApplicationHash; + } + + uint32 GetRPXHashUpdated() + { + return currentUpdatedApplicationHash; + } + +} \ No newline at end of file diff --git a/src/Cafe/CafeSystem.h b/src/Cafe/CafeSystem.h new file mode 100644 index 00000000..90b42a89 --- /dev/null +++ b/src/Cafe/CafeSystem.h @@ -0,0 +1,44 @@ +#pragma once +#include "Cafe/OS/RPL/rpl.h" +#include "util/helpers/Semaphore.h" +#include "Cafe/TitleList/TitleId.h" +#include "config/CemuConfig.h" + +namespace CafeSystem +{ + enum class STATUS_CODE + { + SUCCESS, + INVALID_RPX, + UNABLE_TO_MOUNT, // failed to mount through TitleInfo (most likely caused by an invalid or outdated path) + //BAD_META_DATA, - the title list only stores titles with valid meta, so this error code is impossible + }; + + void Initialize(); + STATUS_CODE PrepareForegroundTitle(TitleId titleId); + STATUS_CODE PrepareForegroundTitleFromStandaloneRPX(const fs::path& path); + void LaunchForegroundTitle(); + bool IsTitleRunning(); + + TitleId GetForegroundTitleId(); + uint16 GetForegroundTitleVersion(); + CafeConsoleRegion GetForegroundTitleRegion(); + CafeConsoleRegion GetPlatformRegion(); + std::string GetForegroundTitleName(); + std::string GetForegroundTitleArgStr(); + + void ShutdownTitle(); + + std::string GetMlcStoragePath(TitleId titleId); + void MlcStorageMountAllTitles(); + + std::string GetInternalVirtualCodeFolder(); + + uint32 GetRPXHashBase(); + uint32 GetRPXHashUpdated(); +}; + +extern RPLModule* applicationRPX; + +extern std::atomic_bool g_isGameRunning; +extern std::atomic_bool g_isGPUInitFinished; diff --git a/src/Cafe/Filesystem/FST/FST.cpp b/src/Cafe/Filesystem/FST/FST.cpp new file mode 100644 index 00000000..b9b511f1 --- /dev/null +++ b/src/Cafe/Filesystem/FST/FST.cpp @@ -0,0 +1,1113 @@ +#include "Common/precompiled.h" +#include "Common/filestream.h" +#include "Cemu/ncrypto/ncrypto.h" +#include "Cafe/Filesystem/WUD/wud.h" +#include "util/crypto/aes128.h" +#include "openssl/sha.h" +#include "fstUtil.h" + +#include "FST.h" +#include "KeyCache.h" + +#include "boost/range/adaptor/reversed.hpp" + +class FSTDataSource +{ +public: + virtual uint64 readData(uint16 clusterIndex, uint64 clusterOffset, uint64 offset, void* data, uint64 size) = 0; + virtual ~FSTDataSource() {}; + +protected: + FSTDataSource() {}; + + bool m_isOpen; +}; + +class FSTDataSourceWUD : public FSTDataSource +{ +public: + + static FSTDataSourceWUD* Open(const fs::path& path) + { + wud_t* wudFile = wud_open(path); + if (!wudFile) + return nullptr; + FSTDataSourceWUD* ds = new FSTDataSourceWUD(); + ds->m_wudFile = wudFile; + return ds; + } + + void SetBaseOffset(uint64 baseOffset) + { + m_baseOffset = baseOffset; + } + + uint64 GetBaseOffset() const + { + return m_baseOffset; + } + + uint64 readData(uint16 clusterIndex, uint64 clusterOffset, uint64 offset, void* data, uint64 size) override + { + cemu_assert_debug(size <= 0xFFFFFFFF); + return wud_readData(m_wudFile, data, (uint32)size, clusterOffset + offset + m_baseOffset); + } + + ~FSTDataSourceWUD() override + { + if(m_wudFile) + wud_close(m_wudFile); + } + +protected: + FSTDataSourceWUD() {} + wud_t* m_wudFile; + uint64 m_baseOffset{}; + std::vector<uint64> m_clusterOffset; +}; + +class FSTDataSourceApp : public FSTDataSource +{ +public: + static FSTDataSourceApp* Open(fs::path path, NCrypto::TMDParser& tmd) + { + std::vector<std::unique_ptr<FileStream>> clusterFile; + uint32 maxIndex = 0; + for (auto& itr : tmd.GetContentList()) + maxIndex = std::max(maxIndex, (uint32)itr.index); + clusterFile.resize(maxIndex + 1); + // open all the app files + for (auto& itr : tmd.GetContentList()) + { + FileStream* appFile = FileStream::openFile2(path / fmt::format("{:08x}.app", itr.contentId)); + if (!appFile) + return nullptr; + clusterFile[itr.index].reset(appFile); + } + // construct FSTDataSourceApp + FSTDataSourceApp* dsApp = new FSTDataSourceApp(std::move(clusterFile)); + return dsApp; + } + + uint64 readData(uint16 clusterIndex, uint64 clusterOffset, uint64 offset, void* data, uint64 size) override + { + // ignore clusterOffset for .app files since each file is already relative to the cluster base + cemu_assert_debug(clusterIndex < m_clusterFile.size()); + cemu_assert_debug(m_clusterFile[clusterIndex].get()); + cemu_assert_debug(size <= 0xFFFFFFFF); + if (!m_clusterFile[clusterIndex].get()) + return 0; + m_clusterFile[clusterIndex].get()->SetPosition(offset); + return m_clusterFile[clusterIndex].get()->readData(data, (uint32)size); + } + + ~FSTDataSourceApp() override + { + } + +private: + FSTDataSourceApp(std::vector<std::unique_ptr<FileStream>>&& clusterFiles) + { + m_clusterFile = std::move(clusterFiles); + } + + std::vector<std::unique_ptr<FileStream>> m_clusterFile; +}; + +constexpr size_t DISC_SECTOR_SIZE = 0x8000; + +struct DiscHeaderA +{ + // header in first sector (0x0) + uint8 productCode[22]; // ? +}; + +struct DiscHeaderB +{ + // header at 0x10000 + static constexpr uint32 MAGIC_VALUE = 0xCC549EB9; + + /* +0x00 */ uint32be magic; +}; + +static_assert(sizeof(DiscHeaderB) == 0x04); + +struct DiscPartitionTableHeader +{ + // header at 0x18000, encrypted + static constexpr uint32 MAGIC_VALUE = 0xCCA6E67B; + + /* +0x00 */ uint32be magic; + /* +0x04 */ uint32be sectorSize; // must be 0x8000? + /* +0x08 */ uint8 partitionTableHash[20]; // hash of the data range at +0x800 to end of sector (0x8000) + /* +0x1C */ uint32be numPartitions; +}; + +static_assert(sizeof(DiscPartitionTableHeader) == 0x20); + +struct DiscPartitionTableEntry +{ + /* +0x00 */ uint8be partitionName[31]; + /* +0x1F */ uint8be numAddresses; // ? + /* +0x20 */ uint32be partitionAddress; // this is an array? + /* +0x24 */ uint8 padding[0x80 - 0x24]; +}; + +static_assert(sizeof(DiscPartitionTableEntry) == 0x80); + +struct DiscPartitionHeader +{ + // header at the beginning of each partition + static constexpr uint32 MAGIC_VALUE = 0xCC93A4F5; + + /* +0x00 */ uint32be magic; + /* +0x04 */ uint32be sectorSize; // must match DISC_SECTOR_SIZE + + /* +0x08 */ uint32be ukn008; + /* +0x0C */ uint32be ukn00C; + /* +0x10 */ uint32be h3HashNum; + /* +0x14 */ uint32be fstSize; // in bytes + /* +0x18 */ uint32be fstSector; // relative to partition start + /* +0x1C */ uint32be ukn01C; + /* +0x20 */ uint32be ukn020; + + // the hash and encryption mode for the FST cluster + /* +0x24 */ uint8 fstHashType; + /* +0x25 */ uint8 fstEncryptionType; // purpose of this isn't really understood. Maybe it controls which key is being used? (1 -> disc key, 2 -> partition key) + + /* +0x26 */ uint8 versionA; + /* +0x27 */ uint8 ukn027; // also a version field? + + // there is an array at +0x40 ? Related to H3 list. Also related to value at +0x0C and h3HashNum +}; + +static_assert(sizeof(DiscPartitionHeader) == 0x28); + +bool FSTVolume::FindDiscKey(const fs::path& path, NCrypto::AesKey& discTitleKey) +{ + std::unique_ptr<FSTDataSourceWUD> dataSource(FSTDataSourceWUD::Open(path)); + if (!dataSource) + return false; + + // read section of header which should only contain zero bytes if decrypted + uint8 header[16*3]; + if (dataSource->readData(0, 0, 0x18000 + 0x100, header, sizeof(header)) != sizeof(header)) + return false; + + // try all the keys + uint8 headerDecrypted[sizeof(header)-16]; + for (sint32 i = 0; i < 0x7FFFFFFF; i++) + { + uint8* key128 = KeyCache_GetAES128(i); + if (key128 == NULL) + break; + AES128_CBC_decrypt(headerDecrypted, header + 16, sizeof(headerDecrypted), key128, header); + if (std::all_of(headerDecrypted, headerDecrypted + sizeof(headerDecrypted), [](const uint8 v) {return v == 0; })) + { + // key found + std::memcpy(discTitleKey.b, key128, 16); + return true; + } + } + return false; +} + +// open WUD image using key cache +// if no matching key is found then keyFound will return false +FSTVolume* FSTVolume::OpenFromDiscImage(const fs::path& path, bool* keyFound) +{ + KeyCache_Prepare(); + NCrypto::AesKey discTitleKey; + if (!FindDiscKey(path, discTitleKey)) + { + if(keyFound) + *keyFound = false; + return nullptr; + } + if(keyFound) + *keyFound = true; + return OpenFromDiscImage(path, discTitleKey); +} + +// open WUD image +FSTVolume* FSTVolume::OpenFromDiscImage(const fs::path& path, NCrypto::AesKey& discTitleKey) +{ + // WUD images support multiple partitions, each with their own key and FST + // the process for loading game data FSTVolume from a WUD image is as follows: + // 1) parse WUD headers and verify + // 2) read SI partition FST + // 3) find main GM partition + // 4) use SI information to get titleKey for GM partition + // 5) Load FST for GM + std::unique_ptr<FSTDataSourceWUD> dataSource(FSTDataSourceWUD::Open(path)); + if (!dataSource) + return nullptr; + // check HeaderA (only contains product code?) + DiscHeaderA headerA{}; + if (dataSource->readData(0, 0, 0, &headerA, sizeof(headerA)) != sizeof(headerA)) + return nullptr; + // check HeaderB + DiscHeaderB headerB{}; + if (dataSource->readData(0, 0, DISC_SECTOR_SIZE * 2, &headerB, sizeof(headerB)) != sizeof(headerB)) + return nullptr; + if (headerB.magic != headerB.MAGIC_VALUE) + return nullptr; + + // read, decrypt and parse partition table + uint8 partitionSector[DISC_SECTOR_SIZE]; + if (dataSource->readData(0, 0, DISC_SECTOR_SIZE * 3, partitionSector, DISC_SECTOR_SIZE) != DISC_SECTOR_SIZE) + return nullptr; + uint8 iv[16]{}; + AES128_CBC_decrypt(partitionSector, partitionSector, DISC_SECTOR_SIZE, discTitleKey.b, iv); + // parse partition info + DiscPartitionTableHeader* partitionHeader = (DiscPartitionTableHeader*)partitionSector; + if (partitionHeader->magic != DiscPartitionTableHeader::MAGIC_VALUE) + { + cemuLog_log(LogType::Force, "Disc image rejected because decryption failed"); + return nullptr; + } + if (partitionHeader->sectorSize != DISC_SECTOR_SIZE) + { + cemuLog_log(LogType::Force, "Disc image rejected because partition sector size is invalid"); + return nullptr; + } + uint32 numPartitions = partitionHeader->numPartitions; + if (numPartitions > 30) // there is space for up to 240 partitions but we use a more reasonable limit + { + cemuLog_log(LogType::Force, "Disc image rejected due to exceeding the partition limit (has {} partitions)", numPartitions); + return nullptr; + } + DiscPartitionTableEntry* partitionArray = (DiscPartitionTableEntry*)(partitionSector + 0x800); + // validate partitions and find SI partition + uint32 siPartitionIndex = std::numeric_limits<uint32>::max(); + uint32 gmPartitionIndex = std::numeric_limits<uint32>::max(); + for (uint32 i = 0; i < numPartitions; i++) + { + if (partitionArray[i].numAddresses != 1) + { + cemuLog_log(LogType::Force, "Disc image has unsupported partition with {} addresses", (uint32)partitionArray[i].numAddresses); + return nullptr; + } + auto& name = partitionArray[i].partitionName; + if (name[0] == 'S' && name[1] == 'I') + { + if (siPartitionIndex != std::numeric_limits<uint32>::max()) + { + cemuLog_log(LogType::Force, "Disc image has multiple SI partitions. Not supported"); + return nullptr; + } + siPartitionIndex = i; + } + if (name[0] == 'G' && name[1] == 'M') + { + if (gmPartitionIndex == std::numeric_limits<uint32>::max()) + gmPartitionIndex = i; // we use the first GM partition we find. This is likely not correct but it seems to work for practically all disc images + } + } + if (siPartitionIndex == std::numeric_limits<uint32>::max() || gmPartitionIndex == std::numeric_limits<uint32>::max()) + { + cemuLog_log(LogType::Force, "Disc image has no SI or GM partition. Cannot read game data"); + return nullptr; + } + + // read and verify partition headers for SI and GM + auto readPartitionHeader = [&](DiscPartitionHeader& partitionHeader, uint32 partitionIndex) -> bool + { + cemu_assert_debug(dataSource->GetBaseOffset() == 0); + if (dataSource->readData(0, 0, partitionArray[partitionIndex].partitionAddress * DISC_SECTOR_SIZE, &partitionHeader, sizeof(DiscPartitionHeader)) != sizeof(DiscPartitionHeader)) + return false; + if (partitionHeader.magic != partitionHeader.MAGIC_VALUE && partitionHeader.sectorSize != DISC_SECTOR_SIZE) + return false; + return true; + }; + + // SI partition + DiscPartitionHeader partitionHeaderSI{}; + if (!readPartitionHeader(partitionHeaderSI, siPartitionIndex)) + { + cemuLog_log(LogType::Force, "Disc image SI partition header is invalid"); + return nullptr; + } + + cemu_assert_debug(partitionHeaderSI.fstHashType == 0); + cemu_assert_debug(partitionHeaderSI.fstEncryptionType == 1); + // todo - check other fields? + + // GM partition + DiscPartitionHeader partitionHeaderGM{}; + if (!readPartitionHeader(partitionHeaderGM, gmPartitionIndex)) + { + cemuLog_log(LogType::Force, "Disc image GM partition header is invalid"); + return nullptr; + } + cemu_assert_debug(partitionHeaderGM.fstHashType == 1); + cemu_assert_debug(partitionHeaderGM.fstEncryptionType == 2); + + // if decryption is necessary + // load SI FST + dataSource->SetBaseOffset((uint64)partitionArray[siPartitionIndex].partitionAddress * DISC_SECTOR_SIZE); + auto siFST = OpenFST(dataSource.get(), (uint64)partitionHeaderSI.fstSector * DISC_SECTOR_SIZE, partitionHeaderSI.fstSize, &discTitleKey, static_cast<FSTVolume::ClusterHashMode>(partitionHeaderSI.fstHashType)); + if (!siFST) + return nullptr; + // load ticket file for partition that we want to decrypt + NCrypto::ETicketParser ticketParser; + std::vector<uint8> ticketData = siFST->ExtractFile(fmt::format("{:02x}/title.tik", gmPartitionIndex)); + if (ticketData.empty() || !ticketParser.parse(ticketData.data(), ticketData.size())) + { + cemuLog_log(LogType::Force, "Disc image ticket file is invalid"); + return nullptr; + } + delete siFST; + + NCrypto::AesKey gmTitleKey; + ticketParser.GetTitleKey(gmTitleKey); + + // load GM partition + dataSource->SetBaseOffset((uint64)partitionArray[gmPartitionIndex].partitionAddress * DISC_SECTOR_SIZE); + return OpenFST(std::move(dataSource), (uint64)partitionHeaderGM.fstSector * DISC_SECTOR_SIZE, partitionHeaderGM.fstSize, &gmTitleKey, static_cast<FSTVolume::ClusterHashMode>(partitionHeaderGM.fstHashType)); +} + +FSTVolume* FSTVolume::OpenFromContentFolder(fs::path folderPath) +{ + // load TMD + FileStream* tmdFile = FileStream::openFile2(folderPath / "title.tmd"); + if (!tmdFile) + return nullptr; + std::vector<uint8> tmdData; + tmdFile->extract(tmdData); + delete tmdFile; + NCrypto::TMDParser tmdParser; + if (!tmdParser.parse(tmdData.data(), tmdData.size())) + return nullptr; + // load ticket + FileStream* ticketFile = FileStream::openFile2(folderPath / "title.tik"); + if (!ticketFile) + return nullptr; + std::vector<uint8> ticketData; + ticketFile->extract(ticketData); + delete ticketFile; + NCrypto::ETicketParser ticketParser; + if (!ticketParser.parse(ticketData.data(), ticketData.size())) + return nullptr; + NCrypto::AesKey titleKey; + ticketParser.GetTitleKey(titleKey); + // open data source + std::unique_ptr<FSTDataSource> dataSource(FSTDataSourceApp::Open(folderPath, tmdParser)); + if (!dataSource) + return nullptr; + // get info about FST from first cluster (todo - is this correct or does the TMD store info about the fst?) + ClusterHashMode fstHashMode = ClusterHashMode::RAW; + uint32 fstSize = 0; + for (auto& itr : tmdParser.GetContentList()) + { + if (itr.index == 0) + { + if (HAS_FLAG(itr.contentFlags, NCrypto::TMDParser::TMDContentFlags::FLAG_HASHED_CONTENT)) + fstHashMode = ClusterHashMode::HASH_INTERLEAVED; + cemu_assert_debug(itr.size <= 0xFFFFFFFF); + fstSize = (uint32)itr.size; + } + } + // load FST + // fstSize = size of first cluster? + FSTVolume* fstVolume = FSTVolume::OpenFST(std::move(dataSource), 0, fstSize, &titleKey, fstHashMode); + return fstVolume; +} + +FSTVolume* FSTVolume::OpenFST(FSTDataSource* dataSource, uint64 fstOffset, uint32 fstSize, NCrypto::AesKey* partitionTitleKey, ClusterHashMode fstHashMode) +{ + cemu_assert_debug(fstHashMode != ClusterHashMode::RAW || fstHashMode != ClusterHashMode::RAW2); + if (fstSize < sizeof(FSTHeader)) + return nullptr; + constexpr uint64 FST_CLUSTER_OFFSET = 0; + uint32 fstSizePadded = (fstSize + 15) & ~15; // pad to AES block size + // read FST data and decrypt + std::vector<uint8> fstData(fstSizePadded); + if (dataSource->readData(0, FST_CLUSTER_OFFSET, fstOffset, fstData.data(), fstSizePadded) != fstSizePadded) + return nullptr; + uint8 iv[16]{}; + AES128_CBC_decrypt(fstData.data(), fstData.data(), fstSizePadded, partitionTitleKey->b, iv); + // validate header + FSTHeader* fstHeader = (FSTHeader*)fstData.data(); + const void* fstEnd = fstData.data() + fstSize; + if (fstHeader->magic != 0x46535400 || fstHeader->numCluster >= 0x1000) + { + cemuLog_log(LogType::Force, "FST has invalid header"); + return nullptr; + } + // load cluster table + uint32 numCluster = fstHeader->numCluster; + FSTHeader_ClusterEntry* clusterDataTable = (FSTHeader_ClusterEntry*)(fstData.data() + sizeof(FSTHeader)); + if ((clusterDataTable + numCluster) > fstEnd) + return nullptr; + std::vector<FSTCluster> clusterTable; + clusterTable.resize(numCluster); + for (size_t i = 0; i < numCluster; i++) + { + clusterTable[i].offset = clusterDataTable[i].offset; + clusterTable[i].size = clusterDataTable[i].size; + clusterTable[i].hashMode = static_cast<FSTVolume::ClusterHashMode>((uint8)clusterDataTable[i].hashMode); + } + // preprocess FST table + FSTHeader_FileEntry* fileTable = (FSTHeader_FileEntry*)(clusterDataTable + numCluster); + if ((fileTable + 1) > fstEnd) + return nullptr; + if (fileTable[0].GetType() != FSTHeader_FileEntry::TYPE::DIRECTORY) + return nullptr; + uint32 numFileEntries = fileTable[0].size; + if (numFileEntries == 0 || (fileTable + numFileEntries) > fstEnd) + return nullptr; + // load name string table + ptrdiff_t nameLookupTableSize = ((const uint8*)fstEnd - (const uint8*)(fileTable + numFileEntries)); + if (nameLookupTableSize < 1) + return nullptr; + std::vector<char> nameStringTable(nameLookupTableSize); + std::memcpy(nameStringTable.data(), (fileTable + numFileEntries), nameLookupTableSize); + // process FST + std::vector<FSTEntry> fstEntries; + if (!ProcessFST(fileTable, numFileEntries, numCluster, nameStringTable, fstEntries)) + return nullptr; + // construct FSTVolume from the processed data + FSTVolume* fstVolume = new FSTVolume(); + fstVolume->m_dataSource = dataSource; + fstVolume->m_offsetFactor = fstHeader->offsetFactor; + fstVolume->m_sectorSize = DISC_SECTOR_SIZE; + fstVolume->m_partitionTitlekey = *partitionTitleKey; + std::swap(fstVolume->m_cluster, clusterTable); + std::swap(fstVolume->m_entries, fstEntries); + std::swap(fstVolume->m_nameStringTable, nameStringTable); + return fstVolume; +} + +FSTVolume* FSTVolume::OpenFST(std::unique_ptr<FSTDataSource> dataSource, uint64 fstOffset, uint32 fstSize, NCrypto::AesKey* partitionTitleKey, ClusterHashMode fstHashMode) +{ + FSTDataSource* ds = dataSource.release(); + FSTVolume* fstVolume = OpenFST(ds, fstOffset, fstSize, partitionTitleKey, fstHashMode); + if (!fstVolume) + { + delete ds; + return nullptr; + } + fstVolume->m_sourceIsOwned = true; + return fstVolume; +} + +bool FSTVolume::ProcessFST(FSTHeader_FileEntry* fileTable, uint32 numFileEntries, uint32 numCluster, std::vector<char>& nameStringTable, std::vector<FSTEntry>& fstEntries) +{ + struct DirHierachyInfo + { + DirHierachyInfo(uint32 parentIndex, uint32 endIndex) : parentIndex(parentIndex), endIndex(endIndex) {}; + + uint32 parentIndex; + uint32 endIndex; + }; + std::vector<DirHierachyInfo> currentDirEnd; + currentDirEnd.reserve(32); + currentDirEnd.emplace_back(0, numFileEntries); // create a fake parent for the root directory, the root's parent index is zero (referencing itself) + uint32 currentIndex = 0; + FSTHeader_FileEntry* pFileIn = fileTable + currentIndex; + fstEntries.resize(numFileEntries); + FSTEntry* pFileOut = fstEntries.data(); + // validate root directory + if (pFileIn->GetType() != FSTHeader_FileEntry::TYPE::DIRECTORY || pFileIn->GetDirectoryEndIndex() != numFileEntries || pFileIn->GetDirectoryParent() != 0) + { + cemuLog_log(LogType::Force, "FSTVolume::ProcessFST() - root node is invalid"); + return false; + } + for (; currentIndex < numFileEntries; currentIndex++) + { + while (currentIndex >= currentDirEnd.back().endIndex) + currentDirEnd.pop_back(); + // process entry name + uint32 nameOffset = pFileIn->GetNameOffset(); + uint32 pos = nameOffset; + while (true) + { + if (pos >= nameStringTable.size()) + return false; // name exceeds string table + if (nameStringTable[pos] == '\0') + break; + pos++; + } + uint32 nameLen = pos - nameOffset; + pFileOut->nameOffset = nameOffset; + pFileOut->nameHash = _QuickNameHash(nameStringTable.data() + nameOffset, nameLen); + // parent directory index + pFileOut->parentDirIndex = currentDirEnd.back().parentIndex; + //if (currentDirEnd.back().parentIndex == 0) + // pFileOut->parentDirIndex = std::numeric_limits<uint32>::max(); + //else + // pFileOut->parentDirIndex = currentDirEnd.back().parentIndex; + // process type specific data + auto entryType = pFileIn->GetType(); + + uint8 flags = 0; + if (pFileIn->HasFlagLink()) + flags |= FSTEntry::FLAG_LINK; + if (pFileIn->HasUknFlag02()) + flags |= FSTEntry::FLAG_UKN02; + pFileOut->SetFlags((FSTEntry::FLAGS)flags); + + if (entryType == FSTHeader_FileEntry::TYPE::FILE) + { + bool isSysLink = entryType == FSTHeader_FileEntry::TYPE::FILE; + if (pFileIn->clusterIndex >= numCluster) + { + cemuLog_log(LogType::Force, "FST: File references cluster out of range"); + return false; + } + cemu_assert_debug(pFileIn->flagsOrPermissions != 0x4004); + pFileOut->SetType(FSTEntry::TYPE::FILE); + pFileOut->fileInfo.fileOffset = pFileIn->offset; + pFileOut->fileInfo.fileSize = pFileIn->size; + pFileOut->fileInfo.clusterIndex = pFileIn->clusterIndex; + } + else if (entryType == FSTHeader_FileEntry::TYPE::DIRECTORY) + { + cemu_assert_debug(pFileIn->flagsOrPermissions != 0x4004); + pFileOut->SetType(FSTEntry::TYPE::DIRECTORY); + uint32 endIndex = pFileIn->GetDirectoryEndIndex(); + uint32 parentIndex = pFileIn->GetDirectoryParent(); + if (endIndex < currentIndex || endIndex > currentDirEnd.back().endIndex) + { + cemuLog_log(LogType::Force, "FST: Directory range out of bounds"); + return false; // dir index out of range + } + if (parentIndex != currentDirEnd.back().parentIndex) + { + cemuLog_log(LogType::Force, "FST: Parent index does not match"); + cemu_assert_debug(false); + return false; + } + currentDirEnd.emplace_back(currentIndex, endIndex); + pFileOut->dirInfo.endIndex = endIndex; + } + else + { + cemuLog_log(LogType::Force, "FST: Encountered node with unknown type"); + cemu_assert_debug(false); + return false; + } + pFileIn++; + pFileOut++; + } + // end remaining directory hierarchy with final index + cemu_assert_debug(currentIndex == numFileEntries); + while (!currentDirEnd.empty() && currentIndex >= currentDirEnd.back().endIndex) + currentDirEnd.pop_back(); + cemu_assert_debug(currentDirEnd.empty()); // no entries should remain + return true; +} + +uint32 FSTVolume::GetFileCount() const +{ + uint32 fileCount = 0; + for (auto& itr : m_entries) + { + if (itr.GetType() == FSTEntry::TYPE::FILE) + fileCount++; + } + return fileCount; +} + +bool FSTVolume::OpenFile(std::string_view path, FSTFileHandle& fileHandleOut, bool openOnlyFiles) +{ + FSCPath fscPath(path); + if (fscPath.GetNodeCount() == 0) + { + // empty path pointers to root directory + if(openOnlyFiles) + return false; + fileHandleOut.m_fstIndex = 0; + return true; + } + + // scan directory and find sub folder or file + // skips iterating subdirectories + auto findSubentry = [this](size_t firstIndex, size_t lastIndex, std::string_view nodeName) -> sint32 + { + uint16 nodeHash = _QuickNameHash(nodeName.data(), nodeName.size()); + size_t index = firstIndex; + while (index < lastIndex) + { + if (m_entries[index].nameHash == nodeHash && MatchFSTEntryName(m_entries[index], nodeName)) + return (sint32)index; + if (m_entries[index].GetType() == FSTEntry::TYPE::DIRECTORY) + index = m_entries[index].dirInfo.endIndex; + else + index++; + } + return -1; + }; + + // current FST range we iterate, starting with root directory which covers all entries + uint32 parentIndex = std::numeric_limits<uint32>::max(); + size_t curDirStart = 1; // skip root directory + size_t curDirEnd = m_entries[0].dirInfo.endIndex; + + // find the subdirectory + for (size_t nodeIndex = 0; nodeIndex < fscPath.GetNodeCount() - 1; nodeIndex++) + { + // get hash of node name + sint32 fstIndex = findSubentry(curDirStart, curDirEnd, fscPath.GetNodeName(nodeIndex)); + if (fstIndex < 0) + return false; + if (m_entries[fstIndex].GetType() != FSTEntry::TYPE::DIRECTORY) + return false; + parentIndex = fstIndex; + curDirStart = fstIndex + 1; + curDirEnd = m_entries[fstIndex].dirInfo.endIndex; + } + // find the entry + sint32 fstIndex = findSubentry(curDirStart, curDirEnd, fscPath.GetNodeName(fscPath.GetNodeCount() - 1)); + if (fstIndex < 0) + return false; + if (openOnlyFiles && m_entries[fstIndex].GetType() != FSTEntry::TYPE::FILE) + return false; + fileHandleOut.m_fstIndex = fstIndex; + return true; +} + +bool FSTVolume::IsDirectory(FSTFileHandle& fileHandle) const +{ + cemu_assert_debug(fileHandle.m_fstIndex < m_entries.size()); + return m_entries[fileHandle.m_fstIndex].GetType() == FSTEntry::TYPE::DIRECTORY; +}; + +bool FSTVolume::IsFile(FSTFileHandle& fileHandle) const +{ + cemu_assert_debug(fileHandle.m_fstIndex < m_entries.size()); + return m_entries[fileHandle.m_fstIndex].GetType() == FSTEntry::TYPE::FILE; +}; + +bool FSTVolume::HasLinkFlag(FSTFileHandle& fileHandle) const +{ + cemu_assert_debug(fileHandle.m_fstIndex < m_entries.size()); + return HAS_FLAG(m_entries[fileHandle.m_fstIndex].GetFlags(), FSTEntry::FLAGS::FLAG_LINK); +}; + +std::string_view FSTVolume::GetName(FSTFileHandle& fileHandle) const +{ + if (fileHandle.m_fstIndex > m_entries.size()) + return ""; + const char* entryName = m_nameStringTable.data() + m_entries[fileHandle.m_fstIndex].nameOffset; + return entryName; +} + +std::string FSTVolume::GetPath(FSTFileHandle& fileHandle) const +{ + std::string path; + auto& entry = m_entries[fileHandle.m_fstIndex]; + // get parent chain + boost::container::small_vector<uint32, 8> parentChain; + if (entry.HasNonRootNodeParent()) + { + parentChain.emplace_back(entry.parentDirIndex); + auto* parentItr = &m_entries[entry.parentDirIndex]; + while (parentItr->HasNonRootNodeParent()) + { + cemu_assert_debug(parentItr->GetType() == FSTEntry::TYPE::DIRECTORY); + parentChain.emplace_back(parentItr->parentDirIndex); + parentItr = &m_entries[parentItr->parentDirIndex]; + } + } + // build path + cemu_assert_debug(parentChain.size() <= 1); // test this case + for (auto& itr : parentChain | boost::adaptors::reversed) + { + const char* name = m_nameStringTable.data() + m_entries[itr].nameOffset; + path.append(name); + path.push_back('/'); + } + // append node name + const char* name = m_nameStringTable.data() + entry.nameOffset; + path.append(name); + return path; +} + +uint32 FSTVolume::GetFileSize(FSTFileHandle& fileHandle) const +{ + if (m_entries[fileHandle.m_fstIndex].GetType() != FSTEntry::TYPE::FILE) + return 0; + return m_entries[fileHandle.m_fstIndex].fileInfo.fileSize; +} + +uint32 FSTVolume::ReadFile(FSTFileHandle& fileHandle, uint32 offset, uint32 size, void* dataOut) +{ + FSTEntry& entry = m_entries[fileHandle.m_fstIndex]; + if (entry.GetType() != FSTEntry::TYPE::FILE) + return 0; + cemu_assert_debug(!HAS_FLAG(entry.GetFlags(), FSTEntry::FLAGS::FLAG_LINK)); + FSTCluster& cluster = m_cluster[entry.fileInfo.clusterIndex]; + if (cluster.hashMode == ClusterHashMode::RAW || cluster.hashMode == ClusterHashMode::RAW2) + return ReadFile_HashModeRaw(entry.fileInfo.clusterIndex, entry, offset, size, dataOut); + else if (cluster.hashMode == ClusterHashMode::HASH_INTERLEAVED) + return ReadFile_HashModeHashed(entry.fileInfo.clusterIndex, entry, offset, size, dataOut); + cemu_assert_debug(false); + return 0; +} + +uint32 FSTVolume::ReadFile_HashModeRaw(uint32 clusterIndex, FSTEntry& entry, uint32 readOffset, uint32 readSize, void* dataOut) +{ + const uint32 readSizeInput = readSize; + uint8* dataOutU8 = (uint8*)dataOut; + if (readOffset >= entry.fileInfo.fileSize) + return 0; + else if ((readOffset + readSize) >= entry.fileInfo.fileSize) + readSize = (entry.fileInfo.fileSize - readOffset); + + const FSTCluster& cluster = m_cluster[clusterIndex]; + uint64 clusterOffset = (uint64)cluster.offset * m_sectorSize; + uint64 absFileOffset = entry.fileInfo.fileOffset * m_offsetFactor + readOffset; + + // make sure the raw range we read is aligned to AES block size (16) + uint64 readAddrStart = absFileOffset & ~0xF; + uint64 readAddrEnd = (absFileOffset + readSize + 0xF) & ~0xF; + + bool usesInitialIV = readOffset < 16; + if (!usesInitialIV) + readAddrStart -= 16; // read previous AES block since we require it for the IV + uint32 prePadding = (uint32)(absFileOffset - readAddrStart); // number of extra bytes we read before readOffset (for AES alignment and IV calculation) + uint32 postPadding = (uint32)(readAddrEnd - (absFileOffset + readSize)); + + uint8 readBuffer[64 * 1024]; + // read first chunk + // if file read offset (readOffset) is within the first AES-block then use initial IV calculated from cluster index + // otherwise read previous AES-block is the IV (AES-CBC) + uint64 readAddrCurrent = readAddrStart; + uint32 rawBytesToRead = (uint32)std::min((readAddrEnd - readAddrStart), (uint64)sizeof(readBuffer)); + if (m_dataSource->readData(clusterIndex, clusterOffset, readAddrCurrent, readBuffer, rawBytesToRead) != rawBytesToRead) + { + cemuLog_log(LogType::Force, "FST read error in raw content"); + return 0; + } + readAddrCurrent += rawBytesToRead; + + uint8 iv[16]{}; + if (usesInitialIV) + { + // for the first AES block, the IV is initialized from cluster index + iv[0] = (uint8)(clusterIndex >> 8); + iv[1] = (uint8)(clusterIndex >> 0); + AES128_CBC_decrypt_updateIV(readBuffer, readBuffer, rawBytesToRead, m_partitionTitlekey.b, iv); + std::memcpy(dataOutU8, readBuffer + prePadding, rawBytesToRead - prePadding - postPadding); + dataOutU8 += (rawBytesToRead - prePadding - postPadding); + readSize -= (rawBytesToRead - prePadding - postPadding); + } + else + { + // IV is initialized from previous AES block (AES-CBC) + std::memcpy(iv, readBuffer, 16); + AES128_CBC_decrypt_updateIV(readBuffer + 16, readBuffer + 16, rawBytesToRead - 16, m_partitionTitlekey.b, iv); + std::memcpy(dataOutU8, readBuffer + prePadding, rawBytesToRead - prePadding - postPadding); + dataOutU8 += (rawBytesToRead - prePadding - postPadding); + readSize -= (rawBytesToRead - prePadding - postPadding); + } + + // read remaining chunks + while (readSize > 0) + { + uint32 bytesToRead = (uint32)std::min((uint32)sizeof(readBuffer), readSize); + uint32 alignedBytesToRead = (bytesToRead + 15) & ~0xF; + if (m_dataSource->readData(clusterIndex, clusterOffset, readAddrCurrent, readBuffer, alignedBytesToRead) != alignedBytesToRead) + { + cemuLog_log(LogType::Force, "FST read error in raw content"); + return 0; + } + AES128_CBC_decrypt_updateIV(readBuffer, readBuffer, alignedBytesToRead, m_partitionTitlekey.b, iv); + std::memcpy(dataOutU8, readBuffer, bytesToRead); + dataOutU8 += bytesToRead; + readSize -= bytesToRead; + readAddrCurrent += alignedBytesToRead; + } + + return readSizeInput - readSize; +} + +constexpr size_t BLOCK_SIZE = 0x10000; +constexpr size_t BLOCK_HASH_SIZE = 0x0400; +constexpr size_t BLOCK_FILE_SIZE = 0xFC00; + +struct FSTHashedBlock +{ + uint8 rawData[BLOCK_SIZE]; + + uint8* getHashData() + { + return rawData; + } + + uint8* getH0Hash(uint32 index) + { + cemu_assert_debug(index < 16); + return getHashData() + 20 * index; + } + + uint8* getH1Hash(uint32 index) + { + cemu_assert_debug(index < 16); + return getHashData() + (20 * 16) * 1 + 20 * index; + } + + uint8* getH2Hash(uint32 index) + { + cemu_assert_debug(index < 16); + return getHashData() + (20 * 16) * 2 + 20 * index; + } + + uint8* getFileData() + { + return rawData + BLOCK_HASH_SIZE; + } + + uint8* getH0Hash(size_t index) + { + cemu_assert_debug(index < 16); + return rawData + index * 20; + } +}; + +static_assert(sizeof(FSTHashedBlock) == BLOCK_SIZE); + +struct FSTCachedHashedBlock +{ + FSTHashedBlock blockData; + uint64 lastAccess; +}; + +FSTCachedHashedBlock* FSTVolume::GetDecryptedHashedBlock(uint32 clusterIndex, uint32 blockIndex) +{ + const FSTCluster& cluster = m_cluster[clusterIndex]; + uint64 clusterOffset = (uint64)cluster.offset * m_sectorSize; + // generate id for cache + uint64 cacheBlockId = ((uint64)clusterIndex << (64 - 16)) | (uint64)blockIndex; + // lookup block in cache + FSTCachedHashedBlock* block = nullptr; + auto itr = m_cacheDecryptedHashedBlocks.find(cacheBlockId); + if (itr != m_cacheDecryptedHashedBlocks.end()) + { + block = itr->second; + block->lastAccess = ++m_cacheAccessCounter; + return block; + } + // if cache already full, drop least recently accessed block (but recycle the FSTHashedBlock* object) + if (m_cacheDecryptedHashedBlocks.size() >= 16) + { + auto dropItr = std::min_element(m_cacheDecryptedHashedBlocks.begin(), m_cacheDecryptedHashedBlocks.end(), [](const auto& a, const auto& b) -> bool + { return a.second->lastAccess < b.second->lastAccess; }); + block = dropItr->second; + m_cacheDecryptedHashedBlocks.erase(dropItr); + } + else + block = new FSTCachedHashedBlock(); + // block not cached, read new + block->lastAccess = ++m_cacheAccessCounter; + if (m_dataSource->readData(clusterIndex, clusterOffset, blockIndex * BLOCK_SIZE, block->blockData.rawData, BLOCK_SIZE) != BLOCK_SIZE) + { + cemuLog_log(LogType::Force, "Failed to read FST block"); + delete block; + return nullptr; + } + // decrypt hash data + uint8 iv[16]{}; + AES128_CBC_decrypt(block->blockData.getHashData(), block->blockData.getHashData(), BLOCK_HASH_SIZE, m_partitionTitlekey.b, iv); + // decrypt file data + AES128_CBC_decrypt(block->blockData.getFileData(), block->blockData.getFileData(), BLOCK_FILE_SIZE, m_partitionTitlekey.b, block->blockData.getH0Hash(blockIndex%16)); + // register in cache + m_cacheDecryptedHashedBlocks.emplace(cacheBlockId, block); + return block; +} + +uint32 FSTVolume::ReadFile_HashModeHashed(uint32 clusterIndex, FSTEntry& entry, uint32 readOffset, uint32 readSize, void* dataOut) +{ + /* + Data is divided into 0x10000 (64KiB) blocks + Layout: + +0x0000 Hash20[16] H0 hashes + +0x0140 Hash20[16] H1 hashes + +0x0240 Hash20[16] H2 hashes + +0x03C0 uint8[64] padding + +0x0400 uint8[0xFC00] fileData + + The hash part (0-0x3FF) uses AES-CBC with IV initialized to zero + The file part (0x400 - 0xFFFF) uses AES-CBC with IV initialized to block->h0Hash[blockIndex % 16] + + The hash data itself is calculated over 4096 blocks. Where each individual H0 entry hashes a single 0xFC00 file data block (unencrypted) + Each H1 hash is calculated from 16 H0 hashes + Each H2 hash is calculated from 16 H1 hashes. + The H3 hash is calculated from 16 H2 hashes. + Thus for each 4096 block group we end up with: + 4096 H0 hashes + 256 H1 hashes + 16 H2 hashes + 1 H3 hash + + The embedded H0/H1 hashes per block are only a slice of the larger array. Whereas H2 always get embedded as a whole, due to only having 16 hashes in total + There is also a H4 hash that covers all H3 hashes and is stored in the TMD + + */ + + const FSTCluster& cluster = m_cluster[clusterIndex]; + uint64 clusterBaseOffset = (uint64)cluster.offset * m_sectorSize; + uint64 fileReadOffset = entry.fileInfo.fileOffset * m_offsetFactor + readOffset; + uint32 blockIndex = (uint32)(fileReadOffset / BLOCK_FILE_SIZE); + uint32 bytesRemaining = readSize; + uint32 offsetWithinBlock = (uint32)(fileReadOffset % BLOCK_FILE_SIZE); + while (bytesRemaining > 0) + { + FSTCachedHashedBlock* block = GetDecryptedHashedBlock(clusterIndex, blockIndex); + if (!block) + return 0; + uint32 bytesToRead = std::min(bytesRemaining, (uint32)BLOCK_FILE_SIZE - offsetWithinBlock); + std::memcpy(dataOut, block->blockData.getFileData() + offsetWithinBlock, bytesToRead); + dataOut = (uint8*)dataOut + bytesToRead; + bytesRemaining -= bytesToRead; + blockIndex++; + offsetWithinBlock = 0; + } + return readSize - bytesRemaining; +} + +bool FSTVolume::OpenDirectoryIterator(std::string_view path, FSTDirectoryIterator& directoryIteratorOut) +{ + FSTFileHandle fileHandle; + if (!OpenFile(path, fileHandle, false)) + return false; + if (!IsDirectory(fileHandle)) + return false; + auto const& fstEntry = m_entries[fileHandle.m_fstIndex]; + directoryIteratorOut.startIndex = fileHandle.m_fstIndex + 1; + directoryIteratorOut.endIndex = fstEntry.dirInfo.endIndex; + directoryIteratorOut.currentIndex = directoryIteratorOut.startIndex; + return true; +} + +bool FSTVolume::Next(FSTDirectoryIterator& directoryIterator, FSTFileHandle& fileHandleOut) +{ + if (directoryIterator.currentIndex >= directoryIterator.endIndex) + return false; + auto const& fstEntry = m_entries[directoryIterator.currentIndex]; + fileHandleOut.m_fstIndex = directoryIterator.currentIndex; + if (fstEntry.GetType() == FSTEntry::TYPE::DIRECTORY) + { + cemu_assert_debug(fstEntry.dirInfo.endIndex > directoryIterator.currentIndex); + directoryIterator.currentIndex = fstEntry.dirInfo.endIndex; + } + else + directoryIterator.currentIndex++; + return true; +} + +FSTVolume::~FSTVolume() +{ + for (auto& itr : m_cacheDecryptedHashedBlocks) + delete itr.second; + if (m_sourceIsOwned) + delete m_dataSource; +} + +bool FSTVerifier::VerifyContentFile(FileStream* fileContent, const NCrypto::AesKey* key, uint32 contentIndex, uint32 contentSize, uint32 contentSizePadded, bool isSHA1, const uint8* tmdContentHash) +{ + cemu_assert_debug(isSHA1); // test this case + cemu_assert_debug(((contentSize+0xF)&~0xF) == contentSizePadded); + + std::vector<uint8> buffer; + buffer.resize(64 * 1024); + if ((uint32)fileContent->GetSize() != contentSizePadded) + return false; + fileContent->SetPosition(0); + uint8 iv[16]{}; + iv[0] = (contentIndex >> 8) & 0xFF; + iv[1] = (contentIndex >> 0) & 0xFF; + // raw content + uint64 remainingBytes = contentSize; + SHA_CTX sha1Ctx; + SHA256_CTX sha256Ctx; + if (isSHA1) + SHA1_Init(&sha1Ctx); + else + SHA256_Init(&sha256Ctx); + while (remainingBytes > 0) + { + uint32 bytesToRead = (uint32)std::min(remainingBytes, buffer.size()); + uint32 bytesToReadPadded = ((bytesToRead + 0xF) & ~0xF); + uint32 bytesRead = fileContent->readData(buffer.data(), bytesToReadPadded); + if (bytesRead != bytesToReadPadded) + return false; + AES128_CBC_decrypt_updateIV(buffer.data(), buffer.data(), bytesToReadPadded, key->b, iv); + if (isSHA1) + SHA1_Update(&sha1Ctx, buffer.data(), bytesToRead); + else + SHA256_Update(&sha256Ctx, buffer.data(), bytesToRead); + remainingBytes -= bytesToRead; + } + uint8 calculatedHash[32]; + if (isSHA1) + SHA1_Final(calculatedHash, &sha1Ctx); + else + SHA256_Final(calculatedHash, &sha256Ctx); + return memcmp(calculatedHash, tmdContentHash, isSHA1 ? 20 : 32) == 0; +} + +void _SHA1Hash(void* data, size_t length, NCrypto::CHash160& hashOut) +{ + SHA_CTX sha1Ctx; + SHA1_Init(&sha1Ctx); + SHA1_Update(&sha1Ctx, data, length); + SHA1_Final(hashOut.b, &sha1Ctx); +} + +bool FSTVerifier::VerifyHashedContentFile(FileStream* fileContent, const NCrypto::AesKey* key, uint32 contentIndex, uint32 contentSize, uint32 contentSizePadded, bool isSHA1, const uint8* tmdContentHash) +{ + if (!isSHA1) + return false; // not supported + if ((contentSize % sizeof(FSTHashedBlock)) != 0) + return false; + if ((uint32)fileContent->GetSize() != contentSize) + return false; + fileContent->SetPosition(0); + + std::vector<NCrypto::CHash160> h0List(4096); + + FSTHashedBlock block; + uint32 numBlocks = contentSize / sizeof(FSTHashedBlock); + for (uint32 blockIndex = 0; blockIndex < numBlocks; blockIndex++) + { + if (fileContent->readData(&block, sizeof(FSTHashedBlock)) != sizeof(FSTHashedBlock)) + return false; + uint32 h0Index = (blockIndex % 4096); + // decrypt hash data and file data + uint8 iv[16]{}; + AES128_CBC_decrypt(block.getHashData(), block.getHashData(), BLOCK_HASH_SIZE, key->b, iv); + AES128_CBC_decrypt(block.getFileData(), block.getFileData(), BLOCK_FILE_SIZE, key->b, block.getH0Hash(blockIndex % 16)); + + // generate H0 hash and compare + NCrypto::CHash160 h0; + _SHA1Hash(block.getFileData(), BLOCK_FILE_SIZE, h0); + if (memcmp(h0.b, block.getH0Hash(h0Index & 0xF), 20) != 0) + return false; + std::memcpy(h0List[h0Index].b, h0.b, 20); + + // Sixteen H0 hashes become one H1 hash + if (((h0Index + 1) % 16) == 0 && h0Index > 0) + { + uint32 h1Index = ((h0Index - 15) / 16); + + NCrypto::CHash160 h1; + _SHA1Hash(h0List.data() + h1Index * 16, sizeof(NCrypto::CHash160) * 16, h1); + if (memcmp(h1.b, block.getH1Hash(h1Index&0xF), 20) != 0) + return false; + } + // todo - repeat same for H1 and H2 + // At the end all H3 hashes are hashed into a single H4 hash which is then compared with the content hash from the TMD + + // Checking only H0 and H1 is sufficient enough for verifying if the file data is intact + // but if we wanted to be strict and only allow correctly signed data we would have to hash all the way up to H4 + } + return true; +} + +void FSTVolumeTest() +{ + FSTPathUnitTest(); +} \ No newline at end of file diff --git a/src/Cafe/Filesystem/FST/FST.h b/src/Cafe/Filesystem/FST/FST.h new file mode 100644 index 00000000..3f59152f --- /dev/null +++ b/src/Cafe/Filesystem/FST/FST.h @@ -0,0 +1,299 @@ +#pragma once +#include "Cemu/ncrypto/ncrypto.h" + +struct FSTFileHandle +{ + friend class FSTVolume; +private: + uint32 m_fstIndex; +}; + +struct FSTDirectoryIterator +{ + friend class FSTVolume; +private: + uint32 startIndex; + uint32 endIndex; + uint32 currentIndex; +}; + +class FSTVolume +{ +public: + static bool FindDiscKey(const fs::path& path, NCrypto::AesKey& discTitleKey); + + static FSTVolume* OpenFromDiscImage(const fs::path& path, NCrypto::AesKey& discTitleKey); + static FSTVolume* OpenFromDiscImage(const fs::path& path, bool* keyFound = nullptr); + static FSTVolume* OpenFromContentFolder(fs::path folderPath); + + ~FSTVolume(); + + uint32 GetFileCount() const; + + bool OpenFile(std::string_view path, FSTFileHandle& fileHandleOut, bool openOnlyFiles = false); + + // file and directory functions + bool IsDirectory(FSTFileHandle& fileHandle) const; + bool IsFile(FSTFileHandle& fileHandle) const; + bool HasLinkFlag(FSTFileHandle& fileHandle) const; + + std::string_view GetName(FSTFileHandle& fileHandle) const; + std::string GetPath(FSTFileHandle& fileHandle) const; + + // file functions + uint32 GetFileSize(FSTFileHandle& fileHandle) const; + uint32 ReadFile(FSTFileHandle& fileHandle, uint32 offset, uint32 size, void* dataOut); + + // directory iterator + bool OpenDirectoryIterator(std::string_view path, FSTDirectoryIterator& directoryIteratorOut); + bool Next(FSTDirectoryIterator& directoryIterator, FSTFileHandle& fileHandleOut); + + // helper function to read whole file + std::vector<uint8> ExtractFile(std::string_view path, bool* success = nullptr) + { + if (success) + *success = false; + std::vector<uint8> fileData; + FSTFileHandle fileHandle; + if (!OpenFile(path, fileHandle, true)) + return fileData; + fileData.resize(GetFileSize(fileHandle)); + ReadFile(fileHandle, 0, (uint32)fileData.size(), fileData.data()); + if (success) + *success = true; + return fileData; + } + +private: + + /* FST data (in memory) */ + enum class ClusterHashMode : uint8 + { + RAW = 0, // raw data + encryption, no hashing? + RAW2 = 1, // raw data + encryption, with hash stored in tmd? + HASH_INTERLEAVED = 2, // hashes + raw interleaved in 0x10000 blocks (0x400 bytes of hashes at the beginning, followed by 0xFC00 bytes of data) + }; + + struct FSTCluster + { + uint32 offset; + uint32 size; + ClusterHashMode hashMode; + }; + + struct FSTEntry + { + enum class TYPE : uint8 + { + FILE, + DIRECTORY, + }; + + enum FLAGS : uint8 + { + FLAG_NONE = 0x0, + FLAG_LINK = 0x1, + FLAG_UKN02 = 0x2, // seen in Super Mario Galaxy. Used for vWii files? + }; + + uint32 nameOffset; + uint32 parentDirIndex; // index of parent directory + uint16 nameHash; + uint8 typeAndFlags; + + TYPE GetType() const + { + return (TYPE)(typeAndFlags & 0xF); + } + + void SetType(TYPE t) + { + typeAndFlags &= ~0x0F; + typeAndFlags |= ((uint8)t); + } + + FLAGS GetFlags() const + { + return (FLAGS)(typeAndFlags >> 4); + } + + void SetFlags(FLAGS flags) + { + typeAndFlags &= ~0xF0; + typeAndFlags |= ((uint8)flags << 4); + } + + // note: The root node is not considered a valid parent + bool HasNonRootNodeParent() const + { + return parentDirIndex != 0; + } + + union + { + struct + { + uint32 endIndex; + }dirInfo; + struct + { + uint32 fileOffset; + uint32 fileSize; + uint16 clusterIndex; + }fileInfo; + }; + }; + + class FSTDataSource* m_dataSource; + bool m_sourceIsOwned{}; + uint32 m_sectorSize{}; // for cluster offsets + uint32 m_offsetFactor{}; // for file offsets + std::vector<FSTCluster> m_cluster; + std::vector<FSTEntry> m_entries; + std::vector<char> m_nameStringTable; + NCrypto::AesKey m_partitionTitlekey; + + /* Cache for decrypted hashed blocks */ + std::unordered_map<uint64, struct FSTCachedHashedBlock*> m_cacheDecryptedHashedBlocks; + uint64 m_cacheAccessCounter{}; + + struct FSTCachedHashedBlock* GetDecryptedHashedBlock(uint32 clusterIndex, uint32 blockIndex); + + /* File reading */ + uint32 ReadFile_HashModeRaw(uint32 clusterIndex, FSTEntry& entry, uint32 readOffset, uint32 readSize, void* dataOut); + uint32 ReadFile_HashModeHashed(uint32 clusterIndex, FSTEntry& entry, uint32 readOffset, uint32 readSize, void* dataOut); + + /* FST parsing */ + struct FSTHeader + { + /* +0x00 */ uint32be magic; + /* +0x04 */ uint32be offsetFactor; + /* +0x08 */ uint32be numCluster; + /* +0x0C */ uint32be ukn0C; + /* +0x10 */ uint32be ukn10; + /* +0x14 */ uint32be ukn14; + /* +0x18 */ uint32be ukn18; + /* +0x1C */ uint32be ukn1C; + }; + + static_assert(sizeof(FSTHeader) == 0x20); + + struct FSTHeader_ClusterEntry + { + /* +0x00 */ uint32be offset; + /* +0x04 */ uint32be size; + /* +0x08 */ uint64be ownerTitleId; + /* +0x10 */ uint32be groupId; + /* +0x14 */ uint8be hashMode; + /* +0x15 */ uint8be padding[0xB]; // ? + }; + static_assert(sizeof(FSTHeader_ClusterEntry) == 0x20); + + struct FSTHeader_FileEntry + { + enum class TYPE : uint8 + { + FILE = 0, + DIRECTORY = 1, + }; + + /* +0x00 */ uint32be typeAndNameOffset; + /* +0x04 */ uint32be offset; // for directories: parent directory index + /* +0x08 */ uint32be size; // for directories: end index + /* +0x0C */ uint16be flagsOrPermissions; // three entries, each one shifted by 4. (so 0xXYZ). Possible bits per value seem to be 0x1 and 0x4 ? These are probably permissions + /* +0x0E */ uint16be clusterIndex; + + TYPE GetType() + { + uint8 v = GetTypeFlagField(); + cemu_assert_debug((v & ~0x83) == 0); // unknown type/flag + return static_cast<TYPE>(v & 0x01); + } + + bool HasFlagLink() + { + uint8 v = GetTypeFlagField(); + return (v & 0x80) != 0; + } + + bool HasUknFlag02() + { + uint8 v = GetTypeFlagField(); + return (v & 0x02) != 0; + } + + uint32 GetNameOffset() + { + return (uint32)typeAndNameOffset & 0xFFFFFF; + } + + uint32 GetDirectoryParent() + { + return offset; + } + + uint32 GetDirectoryEndIndex() + { + return size; + } + + private: + uint8 GetTypeFlagField() + { + return static_cast<uint8>((typeAndNameOffset >> 24) & 0xFF); + } + }; + + static_assert(sizeof(FSTHeader_FileEntry) == 0x10); + + static FSTVolume* OpenFST(FSTDataSource* dataSource, uint64 fstOffset, uint32 fstSize, NCrypto::AesKey* partitionTitleKey, ClusterHashMode fstHashMode); + static FSTVolume* OpenFST(std::unique_ptr<FSTDataSource> dataSource, uint64 fstOffset, uint32 fstSize, NCrypto::AesKey* partitionTitleKey, ClusterHashMode fstHashMode); + static bool ProcessFST(FSTHeader_FileEntry* fileTable, uint32 numFileEntries, uint32 numCluster, std::vector<char>& nameStringTable, std::vector<FSTEntry>& fstEntries); + + bool MatchFSTEntryName(FSTEntry& entry, std::string_view comparedName) + { + const char* entryName = m_nameStringTable.data() + entry.nameOffset; + const char* comparedNameCur = comparedName.data(); + const char* comparedNameEnd = comparedName.data() + comparedName.size(); + while (comparedNameCur < comparedNameEnd) + { + uint8 c1 = *entryName; + uint8 c2 = *comparedNameCur; + if (c1 >= (uint8)'A' && c1 <= (uint8)'Z') + c1 = c1 - ((uint8)'A' - (uint8)'a'); + if (c2 >= (uint8)'A' && c2 <= (uint8)'Z') + c2 = c2 - ((uint8)'A' - (uint8)'a'); + if (c1 != c2) + return false; + entryName++; + comparedNameCur++; + } + return *entryName == '\0'; // all the characters match, check for same length + } + + // we utilize hashes to accelerate string comparisons when doing path lookups + static uint16 _QuickNameHash(const char* fileName, size_t len) + { + uint16 v = 0; + const char* fileNameEnd = fileName + len; + while (fileName < fileNameEnd) + { + uint8 c = (uint8)*fileName; + if (c >= (uint8)'A' && c <= (uint8)'Z') + c = c - ((uint8)'A' - (uint8)'a'); + v += (uint16)c; + v = (v >> 3) | (v << 13); + fileName++; + } + return v; + } + +}; + +class FSTVerifier +{ +public: + static bool VerifyContentFile(class FileStream* fileContent, const NCrypto::AesKey* key, uint32 contentIndex, uint32 contentSize, uint32 contentSizePadded, bool isSHA1, const uint8* tmdContentHash); + static bool VerifyHashedContentFile(class FileStream* fileContent, const NCrypto::AesKey* key, uint32 contentIndex, uint32 contentSize, uint32 contentSizePadded, bool isSHA1, const uint8* tmdContentHash); + +}; \ No newline at end of file diff --git a/src/Cafe/Filesystem/FST/KeyCache.cpp b/src/Cafe/Filesystem/FST/KeyCache.cpp new file mode 100644 index 00000000..7fedc3dd --- /dev/null +++ b/src/Cafe/Filesystem/FST/KeyCache.cpp @@ -0,0 +1,130 @@ +#include <wx/msgdlg.h> +#include <mutex> + +#include "config/ActiveSettings.h" +#include "util/crypto/aes128.h" +#include "Common/filestream.h" +#include "util/helpers/StringHelpers.h" + +std::mutex mtxKeyCache; + +struct KeyCacheEntry +{ + uint8 aes128key[16]; +}; + +std::vector<KeyCacheEntry> g_keyCache; + +bool strishex(std::string_view str) +{ + for(size_t i=0; i<str.size(); i++) + { + char c = str[i]; + if( (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') ) + continue; + return false; + } + return true; +} + +/* + * Returns AES-128 key from the key cache + * nullptr is returned if index >= max_keys + */ +uint8* KeyCache_GetAES128(sint32 index) +{ + if( index < 0 || index >= (sint32)g_keyCache.size()) + return nullptr; + KeyCacheEntry* keyCacheEntry = &g_keyCache[index]; + return keyCacheEntry->aes128key; +} + +void KeyCache_AddKey128(uint8* key) +{ + KeyCacheEntry newEntry = {0}; + memcpy(newEntry.aes128key, key, 16); + g_keyCache.emplace_back(newEntry); +} + +bool sKeyCachePrepared = false; + +void KeyCache_Prepare() +{ + mtxKeyCache.lock(); + if (sKeyCachePrepared) + { + mtxKeyCache.unlock(); + return; + } + sKeyCachePrepared = true; + g_keyCache.clear(); + // load keys + auto keysPath = ActiveSettings::GetPath("keys.txt"); + FileStream* fs_keys = FileStream::openFile2(keysPath); + if( !fs_keys ) + { + fs_keys = FileStream::createFile2(keysPath); + if(fs_keys) + { + fs_keys->writeString("# this file contains keys needed for decryption of disc file system data (WUD/WUX)\r\n"); + fs_keys->writeString("# 1 key per line, any text after a '#' character is considered a comment\r\n"); + fs_keys->writeString("# the emulator will automatically pick the right key\r\n"); + fs_keys->writeString("541b9889519b27d363cd21604b97c67a # example key (can be deleted)\r\n"); + delete fs_keys; + } + else + { + wxMessageBox("Unable to create file keys.txt\nThis can happen if Cemu does not have write permission to it's own directory, the disk is full or if anti-virus software is blocking Cemu.", "Error", wxOK | wxCENTRE | wxICON_ERROR); + } + mtxKeyCache.unlock(); + return; + } + sint32 lineNumber = 0; + std::string line; + while( fs_keys->readLine(line) ) + { + lineNumber++; + // truncate anything after '#' or ';' + for(size_t i=0; i<line.size(); i++) + { + if(line[i] == '#' || line[i] == ';' ) + { + line.resize(i); + break; + } + } + // remove whitespaces and other common formatting characters + auto itr = line.begin(); + while (itr != line.end()) + { + char c = *itr; + if (c == ' ' || c == '\t' || c == '-' || c == '_') + itr = line.erase(itr); + else + itr++; + } + if (line.empty()) + continue; + if( strishex(line) == false ) + { + // show error message + char errorMsg[512]; + sprintf(errorMsg, "Error in keys.txt in line %d\n", lineNumber); + wxMessageBox(errorMsg, "Error", wxOK | wxCENTRE | wxICON_ERROR); + continue; + } + if(line.size() == 32 ) + { + // 128-bit key + uint8 keyData128[16]; + StringHelpers::ParseHexString(line, keyData128, 16); + KeyCache_AddKey128(keyData128); + } + else + { + // invalid key length + } + } + delete fs_keys; + mtxKeyCache.unlock(); +} \ No newline at end of file diff --git a/src/Cafe/Filesystem/FST/KeyCache.h b/src/Cafe/Filesystem/FST/KeyCache.h new file mode 100644 index 00000000..9e01252e --- /dev/null +++ b/src/Cafe/Filesystem/FST/KeyCache.h @@ -0,0 +1,5 @@ +#pragma once + +void KeyCache_Prepare(); + +uint8* KeyCache_GetAES128(sint32 index); \ No newline at end of file diff --git a/src/Cafe/Filesystem/FST/fstUtil.h b/src/Cafe/Filesystem/FST/fstUtil.h new file mode 100644 index 00000000..d0779e59 --- /dev/null +++ b/src/Cafe/Filesystem/FST/fstUtil.h @@ -0,0 +1,490 @@ +#pragma once +#include <wchar.h> + +class parsedPathW +{ + static const int MAX_NODES = 32; + +public: + parsedPathW(std::wstring_view path) + { + // init vars + this->numNodes = 0; + // init parsed path data + if (path.front() == '/') + path.remove_prefix(1); + pathData.assign(path.begin(), path.end()); + pathData.push_back('\0'); + // start parsing + sint32 offset = 0; + sint32 startOffset = 0; + if (offset < pathData.size()-1) + { + this->nodeOffset[this->numNodes] = offset; + this->numNodes++; + } + while (offset < pathData.size() - 1) + { + if (this->pathData[offset] == '/' || this->pathData[offset] == '\\') + { + this->pathData[offset] = '\0'; + offset++; + // double slashes are ignored and instead are handled like a single slash + if (this->pathData[offset] == '/' || this->pathData[offset] == '\\') + { + // if we're in the beginning and having a \\ it's a network path + if (offset != 1) + { + this->pathData[offset] = '\0'; + offset++; + } + } + // start new node + if (this->numNodes < MAX_NODES) + { + if (offset < pathData.size() - 1) + { + this->nodeOffset[this->numNodes] = offset; + this->numNodes++; + } + } + continue; + } + offset++; + } + // handle special nodes like '.' or '..' + sint32 nodeIndex = 0; + while (nodeIndex < this->numNodes) + { + if (compareNodeName(nodeIndex, L"..")) + { + assert(false); + } + else if (compareNodeName(nodeIndex, L".")) + { + removeNode(nodeIndex); + continue; + } + nodeIndex++; + } + } + + // returns true if the names match (case sensitive) + bool compareNodeName(sint32 index, std::wstring_view name) + { + if (index < 0 || index >= this->numNodes) + return false; + const wchar_t* nodeName = this->pathData.data() + this->nodeOffset[index]; + if (boost::iequals(nodeName, name)) + return true; + return false; + } + + static bool compareNodeName(std::wstring_view name1, std::wstring_view name2) + { + if (boost::iequals(name1, name2)) + return true; + return false; + } + + static bool compareNodeNameCaseInsensitive(std::wstring_view name1, std::wstring_view name2) + { + if (boost::iequals(name1, name2)) + return true; + return false; + } + + const wchar_t* getNodeName(sint32 index) + { + if (index < 0 || index >= this->numNodes) + return nullptr; + return this->pathData.data() + this->nodeOffset[index]; + } + + void removeNode(sint32 index) + { + if (index < 0 || index >= numNodes) + return; + numNodes--; + for (sint32 i = 0; i < numNodes; i++) + { + nodeOffset[i] = nodeOffset[i + 1]; + } + // remove empty space + if (numNodes > 0) + updateOffsets(nodeOffset[0]); + } + + void prependNode(wchar_t* newNode) + { + if (numNodes >= MAX_NODES) + return; + sint32 len = (sint32)wcslen(newNode); + updateOffsets(-(len + 1)); + numNodes++; + for (sint32 i = numNodes - 1; i >= 1; i--) + { + nodeOffset[i] = nodeOffset[i - 1]; + } + nodeOffset[0] = 0; + memcpy(pathData.data() + 0, newNode, (len + 1) * sizeof(wchar_t)); + } + + void buildPathString(std::wstring& pathStr, bool appendSlash = false) + { + pathStr.resize(0); + for (sint32 i = 0; i < numNodes; i++) + { + if (numNodes > 1) + pathStr.append(L"/"); + pathStr.append(pathData.data() + nodeOffset[i]); + } + if(appendSlash) + pathStr.append(L"/"); + } +private: + void updateOffsets(sint32 newBaseOffset) + { + if (numNodes == 0 || newBaseOffset == 0) + return; + cemu_assert_debug(newBaseOffset <= nodeOffset[0]); + if (newBaseOffset > 0) + { + // decrease size + memmove(pathData.data(), pathData.data() + newBaseOffset, (pathData.size() - newBaseOffset) * sizeof(wchar_t)); + pathData.resize(pathData.size() - newBaseOffset); + // update node offsets + for (sint32 i = 0; i < numNodes; i++) + { + nodeOffset[i] -= newBaseOffset; + } + } + else + { + // increase size + newBaseOffset = -newBaseOffset; + pathData.resize(pathData.size() + newBaseOffset); + memmove(pathData.data() + newBaseOffset, pathData.data(), (pathData.size() - newBaseOffset) * sizeof(wchar_t)); + // update node offsets + for (sint32 i = 0; i < numNodes; i++) + { + nodeOffset[i] += newBaseOffset; + } + } + } + +private: + //std::wstring pathData; + std::vector<wchar_t> pathData; + sint32 nodeOffset[MAX_NODES + 1]; + +public: + sint32 numNodes; +}; + + +template<typename F, bool isCaseSensitive> +class FileTree +{ +public: + +private: + + enum NODETYPE : uint8 + { + NODETYPE_DIRECTORY, + NODETYPE_FILE, + }; + + typedef struct _node_t + { + std::wstring name; + std::vector<_node_t*> subnodes; + F* custom; + NODETYPE type; + }node_t; + + node_t* getByNodePath(parsedPathW& p, sint32 numNodes, bool createAsDirectories) + { + node_t* currentNode = &rootNode; + for (sint32 i = 0; i < numNodes; i++) + { + // find subnode by path + node_t* foundSubnode = getSubnode(currentNode, p.getNodeName(i)); + if (foundSubnode == nullptr) + { + // no subnode found -> create new directory node (if requested) + if (createAsDirectories == false) + return nullptr; // path not found + currentNode = newNode(currentNode, NODETYPE_DIRECTORY, p.getNodeName(i)); + } + else + { + currentNode = foundSubnode; + } + } + return currentNode; + } + + node_t* getSubnode(node_t* parentNode, std::wstring_view name) + { + for (auto& sn : parentNode->subnodes) + { + if constexpr (isCaseSensitive) + { + if (parsedPathW::compareNodeName(sn->name.c_str(), name)) + return sn; + } + else + { + if (parsedPathW::compareNodeNameCaseInsensitive(sn->name.c_str(), name)) + return sn; + } + } + return nullptr; + } + + node_t* newNode(node_t* parentNode, NODETYPE type, std::wstring_view name) + { + node_t* newNode = new node_t; + newNode->name = std::wstring(name); + newNode->type = type; + newNode->custom = nullptr; + parentNode->subnodes.push_back(newNode); + return newNode; + } + +public: + FileTree() + { + rootNode.type = NODETYPE_DIRECTORY; + } + + bool addFile(const wchar_t* path, F* custom) + { + parsedPathW p(path); + if (p.numNodes == 0) + return false; + node_t* directoryNode = getByNodePath(p, p.numNodes - 1, true); + // check if a node with same name already exists + if (getSubnode(directoryNode, p.getNodeName(p.numNodes - 1)) != nullptr) + return false; // node already exists + // add file node + node_t* fileNode = newNode(directoryNode, NODETYPE_FILE, p.getNodeName(p.numNodes - 1)); + fileNode->custom = custom; + return true; + } + + bool getFile(std::wstring_view path, F* &custom) + { + parsedPathW p(path); + if (p.numNodes == 0) + return false; + node_t* node = getByNodePath(p, p.numNodes, false); + if (node == nullptr) + return false; + if (node->type != NODETYPE_FILE) + return false; + custom = node->custom; + return true; + } + + bool removeFile(std::wstring_view path) + { + parsedPathW p(path); + if (p.numNodes == 0) + return false; + node_t* directoryNode = getByNodePath(p, p.numNodes - 1, false); + if (directoryNode == nullptr) + return false; + // find node + node_t* fileNode = getSubnode(directoryNode, p.getNodeName(p.numNodes - 1)); + if (fileNode == nullptr) + return false; + if (fileNode->type != NODETYPE_FILE) + return false; + if (fileNode->subnodes.empty() == false) + { + // files shouldn't have subnodes + assert(false); + } + // remove node from parent + directoryNode->subnodes.erase(std::remove(directoryNode->subnodes.begin(), directoryNode->subnodes.end(), fileNode), directoryNode->subnodes.end()); + // delete node + delete fileNode; + return true; + } + + template<typename TFunc> + bool listDirectory(const wchar_t* path, TFunc fn) + { + parsedPathW p(path); + node_t* node = getByNodePath(p, p.numNodes, false); + if (node == nullptr) + return false; + if (node->type != NODETYPE_DIRECTORY) + return false; + for (auto& it : node->subnodes) + { + if (it->type == NODETYPE_DIRECTORY) + { + fn(it->name, true, it->custom); + } + else if (it->type == NODETYPE_FILE) + { + fn(it->name, false, it->custom); + } + } + return true; + } + +private: + node_t rootNode; +}; + +#include <boost/container/small_vector.hpp> + +// path parser and utility class for Wii U paths +// optimized to be allocation-free for common path lengths +class FSCPath +{ + struct PathNode + { + PathNode(uint16 offset, uint16 len) : offset(offset), len(len) {}; + + uint16 offset; + uint16 len; + }; + + boost::container::small_vector<PathNode, 8> m_nodes; + boost::container::small_vector<char, 64> m_names; + bool m_isAbsolute{}; + + inline bool isSlash(char c) + { + return c == '\\' || c == '/'; + } + + void appendNode(const char* name, uint16 nameLen) + { + if (m_names.size() > 0xFFFF) + return; + m_nodes.emplace_back((uint16)m_names.size(), nameLen); + m_names.insert(m_names.end(), name, name + nameLen); + } + +public: + FSCPath(std::string_view path) + { + if (path.empty()) + return; + if (isSlash(path.front())) + { + m_isAbsolute = true; + path.remove_prefix(1); + // skip any additional leading slashes + while(!path.empty() && isSlash(path.front())) + path.remove_prefix(1); + } + // parse nodes + size_t n = 0; + size_t nodeNameStartIndex = 0; + while (n < path.size()) + { + if (isSlash(path[n])) + { + size_t nodeNameLen = n - nodeNameStartIndex; + if (nodeNameLen > 0xFFFF) + nodeNameLen = 0xFFFF; // truncate suspiciously long node names + cemu_assert_debug(nodeNameLen > 0); + appendNode(path.data() + nodeNameStartIndex, (uint16)nodeNameLen); + // skip any repeating slashes + while (n < path.size() && isSlash(path[n])) + n++; + nodeNameStartIndex = n; + continue; + } + n++; + } + if (nodeNameStartIndex < n) + { + size_t nodeNameLen = n - nodeNameStartIndex; + if (nodeNameLen > 0xFFFF) + nodeNameLen = 0xFFFF; // truncate suspiciously long node names + appendNode(path.data() + nodeNameStartIndex, (uint16)nodeNameLen); + } + } + + size_t GetNodeCount() const + { + return m_nodes.size(); + } + + std::string_view GetNodeName(size_t index) const + { + if (index < 0 || index >= m_nodes.size()) + return std::basic_string_view<char>(); + return std::basic_string_view<char>(m_names.data() + m_nodes[index].offset, m_nodes[index].len); + } + + bool MatchNode(sint32 index, std::string_view name) const + { + if (index < 0 || index >= (sint32)m_nodes.size()) + return false; + auto nodeName = GetNodeName(index); + if (nodeName.size() != name.size()) + return false; + for (size_t i = 0; i < nodeName.size(); i++) + { + char c1 = nodeName[i]; + char c2 = name[i]; + if (c1 >= 'A' && c1 <= 'Z') + c1 += ('a' - 'A'); + if (c2 >= 'A' && c2 <= 'Z') + c2 += ('a' - 'A'); + if (c1 != c2) + return false; + } + return true; + } +}; + +static void FSTPathUnitTest() +{ + // test 1 + FSCPath p1("/vol/content"); + cemu_assert_debug(p1.GetNodeCount() == 2); + cemu_assert_debug(p1.MatchNode(0, "tst") == false); + cemu_assert_debug(p1.MatchNode(0, "vol")); + cemu_assert_debug(p1.MatchNode(1, "CONTENT")); + // test 2 + FSCPath p2("/vol/content/"); + cemu_assert_debug(p2.GetNodeCount() == 2); + cemu_assert_debug(p2.MatchNode(0, "vol")); + cemu_assert_debug(p2.MatchNode(1, "content")); + // test 3 + FSCPath p3("/vol//content/\\/"); + cemu_assert_debug(p3.GetNodeCount() == 2); + cemu_assert_debug(p3.MatchNode(0, "vol")); + cemu_assert_debug(p3.MatchNode(1, "content")); + // test 4 + FSCPath p4("vol/content/"); + cemu_assert_debug(p4.GetNodeCount() == 2); + // test 5 + FSCPath p5("/vol/content/test.bin"); + cemu_assert_debug(p5.GetNodeCount() == 3); + cemu_assert_debug(p5.MatchNode(0, "vol")); + cemu_assert_debug(p5.MatchNode(1, "content")); + cemu_assert_debug(p5.MatchNode(2, "TEST.BIN")); + // test 6 - empty paths + FSCPath p6(""); + cemu_assert_debug(p6.GetNodeCount() == 0); + p6 = FSCPath("/////////////"); + cemu_assert_debug(p6.GetNodeCount() == 0); +} + + + + + + diff --git a/src/Cafe/Filesystem/WUD/wud.cpp b/src/Cafe/Filesystem/WUD/wud.cpp new file mode 100644 index 00000000..205a5db0 --- /dev/null +++ b/src/Cafe/Filesystem/WUD/wud.cpp @@ -0,0 +1,128 @@ +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include "wud.h" +#include "Common/filestream.h" + +wud_t* wud_open(const fs::path& path) +{ + FileStream* fs = FileStream::openFile2(path); + if( !fs ) + return nullptr; + // allocate wud struct + wud_t* wud = (wud_t*)malloc(sizeof(wud_t)); + memset(wud, 0x00, sizeof(wud_t)); + wud->fs = fs; + // get size of file + long long inputFileSize = wud->fs->GetSize(); + // determine whether the WUD is compressed or not + wuxHeader_t wuxHeader = {0}; + if( wud->fs->readData(&wuxHeader, sizeof(wuxHeader_t)) != sizeof(wuxHeader_t)) + { + // file is too short to be either + wud_close(wud); + return nullptr; + } + if( wuxHeader.magic0 == WUX_MAGIC_0 && wuxHeader.magic1 == WUX_MAGIC_1 ) + { + // this is a WUX file + wud->isCompressed = true; + wud->sectorSize = wuxHeader.sectorSize; + wud->uncompressedSize = wuxHeader.uncompressedSize; + // validate header values + if( wud->sectorSize < 0x100 || wud->sectorSize >= 0x10000000 ) + { + wud_close(wud); + return nullptr; + } + // calculate offsets and sizes + wud->indexTableEntryCount = (unsigned int)((wud->uncompressedSize+(long long)(wud->sectorSize-1)) / (long long)wud->sectorSize); + wud->offsetIndexTable = sizeof(wuxHeader_t); + wud->offsetSectorArray = (wud->offsetIndexTable + (long long)wud->indexTableEntryCount*sizeof(unsigned int)); + // align to SECTOR_SIZE + wud->offsetSectorArray = (wud->offsetSectorArray + (long long)(wud->sectorSize-1)); + wud->offsetSectorArray = wud->offsetSectorArray - (wud->offsetSectorArray%(long long)wud->sectorSize); + // read index table + unsigned int indexTableSize = sizeof(unsigned int) * wud->indexTableEntryCount; + wud->indexTable = (unsigned int*)malloc(indexTableSize); + wud->fs->SetPosition(wud->offsetIndexTable); + if( wud->fs->readData(wud->indexTable, indexTableSize) != indexTableSize ) + { + // could not read index table + wud_close(wud); + return nullptr; + } + } + else + { + // uncompressed file + wud->uncompressedSize = inputFileSize; + } + return wud; +} + +void wud_close(wud_t* wud) +{ + delete wud->fs; + if( wud->indexTable ) + free(wud->indexTable); + free(wud); +} + +bool wud_isWUXCompressed(wud_t* wud) +{ + return wud->isCompressed; +} + +/* + * Read data from WUD file + * Can read up to 4GB at once + */ +unsigned int wud_readData(wud_t* wud, void* buffer, unsigned int length, long long offset) +{ + // make sure there is no out-of-bounds read + long long fileBytesLeft = wud->uncompressedSize - offset; + if( fileBytesLeft <= 0 ) + return 0; + if( fileBytesLeft < (long long)length ) + length = (unsigned int)fileBytesLeft; + // read data + unsigned int readBytes = 0; + if( wud->isCompressed == false ) + { + // uncompressed read is straight forward + wud->fs->SetPosition(offset); + readBytes = (unsigned int)wud->fs->readData(buffer, length); + } + else + { + // compressed read must be handled on a per-sector level + while( length > 0 ) + { + unsigned int sectorOffset = (unsigned int)(offset % (long long)wud->sectorSize); + unsigned int remainingSectorBytes = wud->sectorSize - sectorOffset; + unsigned int sectorIndex = (unsigned int)(offset / (long long)wud->sectorSize); + unsigned int bytesToRead = (remainingSectorBytes<length)?remainingSectorBytes:length; // read only up to the end of the current sector + // look up real sector index + sectorIndex = wud->indexTable[sectorIndex]; + wud->fs->SetPosition(wud->offsetSectorArray + (long long)sectorIndex * (long long)wud->sectorSize + (long long)sectorOffset); + readBytes += (unsigned int)wud->fs->readData(buffer, bytesToRead); + // progress read offset, write pointer and decrease length + buffer = (void*)((char*)buffer + bytesToRead); + length -= bytesToRead; + offset += bytesToRead; + + } + } + return readBytes; +} + +/* + * Returns the size of the data + * For .wud: Size of file + * For .wux: Size of uncompressed data + */ +long long wud_getWUDSize(wud_t* wud) +{ + return wud->uncompressedSize; +} \ No newline at end of file diff --git a/src/Cafe/Filesystem/WUD/wud.h b/src/Cafe/Filesystem/WUD/wud.h new file mode 100644 index 00000000..e58b4073 --- /dev/null +++ b/src/Cafe/Filesystem/WUD/wud.h @@ -0,0 +1,34 @@ +#pragma once + +struct wuxHeader_t +{ + unsigned int magic0; + unsigned int magic1; + unsigned int sectorSize; + unsigned long long uncompressedSize; + unsigned int flags; +}; + +struct wud_t +{ + class FileStream* fs; + long long uncompressedSize; + bool isCompressed; + // data used when compressed + unsigned int sectorSize; + unsigned int indexTableEntryCount; + unsigned int* indexTable; + long long offsetIndexTable; + long long offsetSectorArray; +}; + +#define WUX_MAGIC_0 '0XUW' // "WUX0" +#define WUX_MAGIC_1 0x1099d02e + +// wud and wux functions +wud_t* wud_open(const fs::path& path); // transparently handles wud and wux files +void wud_close(wud_t* wud); + +bool wud_isWUXCompressed(wud_t* wud); +unsigned int wud_readData(wud_t* wud, void* buffer, unsigned int length, long long offset); +long long wud_getWUDSize(wud_t* wud); \ No newline at end of file diff --git a/src/Cafe/Filesystem/fsc.cpp b/src/Cafe/Filesystem/fsc.cpp new file mode 100644 index 00000000..bab2d19e --- /dev/null +++ b/src/Cafe/Filesystem/fsc.cpp @@ -0,0 +1,801 @@ +#include "Cafe/Filesystem/fsc.h" + +struct FSCMountPathNode +{ + std::string path; + std::vector<FSCMountPathNode*> subnodes; + FSCMountPathNode* parent; + // device target and path (if list_subnodes is nullptr) + fscDeviceC* device{ nullptr }; + void* ctx{ nullptr }; + std::wstring targetPath; + // priority + sint32 priority{}; + + FSCMountPathNode(FSCMountPathNode* parent) : parent(parent) + { + } + + ~FSCMountPathNode() + { + for (auto& itr : subnodes) + delete itr; + subnodes.clear(); + } +}; + +// compare two file or directory names using FS rules +bool FSA_CompareNodeName(std::string_view a, std::string_view b) +{ + if (a.size() != b.size()) + return false; + for (size_t i = 0; i < a.size(); i++) + { + uint8 ac = (uint8)a[i]; + uint8 bc = (uint8)b[i]; + // lower case compare + if (ac >= (uint8)'A' && ac <= (uint8)'Z') + ac -= ((uint8)'A' - (uint8)'a'); + if (bc >= (uint8)'A' && bc <= (uint8)'Z') + bc -= ((uint8)'A' - (uint8)'a'); + if (ac != bc) + return false; + } + return true; +} + +FSCMountPathNode* s_fscRootNodePerPrio[FSC_PRIORITY_COUNT]{}; + +std::recursive_mutex s_fscMutex; + +#define fscEnter() s_fscMutex.lock(); +#define fscLeave() s_fscMutex.unlock(); + +FSCMountPathNode* fsc_lookupPathVirtualNode(const char* path, sint32 priority = FSC_PRIORITY_BASE); + +void fsc_reset() +{ + // delete existing nodes + for (auto& itr : s_fscRootNodePerPrio) + { + delete itr; + itr = nullptr; + } + // init root node for each priority + for (sint32 i = 0; i < FSC_PRIORITY_COUNT; i++) + s_fscRootNodePerPrio[i] = new FSCMountPathNode(nullptr); +} + +/* + * Creates a node chain for the given mount path. Returns the bottom node. + * If the path already exists for the given priority, NULL is returned (we can't mount two devices to the same path with the same priority) + * But we can map devices to subdirectories. Something like this is possible: + * /vol/content -> Map to WUD (includes all subdirectories except /data, which is handled by the entry below. This exclusion rule applies only if the priority of both mount entries is the same) + * /vol/content/data -> Map to HostFS + * If overlapping paths with different priority are created, then the higher priority one will be checked first + */ +FSCMountPathNode* fsc_createMountPath(CoreinitFSParsedPath* parsedMountPath, sint32 priority) +{ + cemu_assert(priority >= 0 && priority < FSC_PRIORITY_COUNT); + fscEnter(); + FSCMountPathNode* nodeParent = s_fscRootNodePerPrio[priority]; + for(sint32 i=0; i<parsedMountPath->numNodes; i++) + { + // search for subdirectory + FSCMountPathNode* nodeSub = nullptr; // set if we found a subnode with a matching name, else this is used to store the new nodes + for(auto& nodeItr : nodeParent->subnodes) + { + if( coreinitFS_checkNodeName(parsedMountPath, i, nodeItr->path.c_str()) ) + { + // subnode found + nodeSub = nodeItr; + break; + } + } + if( nodeSub ) + { + // traverse subnode + nodeParent = nodeSub; + continue; + } + // no matching subnode, add new entry + nodeSub = new FSCMountPathNode(nodeParent); + nodeSub->path = coreinitFS_getNodeName(parsedMountPath, i); + nodeSub->priority = priority; + nodeParent->subnodes.emplace_back(nodeSub); + if( i == (parsedMountPath->numNodes-1) ) + { + // last node + fscLeave(); + return nodeSub; + } + // traverse subnode + nodeParent = nodeSub; + } + // path is empty or already mounted + fscLeave(); + if (parsedMountPath->numNodes == 0) + return nodeParent; + return nullptr; +} + +// Map a virtual FSC directory to a device and device directory +sint32 fsc_mount(const char* mountPath, const wchar_t* _targetPath, fscDeviceC* fscDevice, void* ctx, sint32 priority) +{ + cemu_assert(fscDevice); // device must not be nullptr + // make sure the target path ends with a slash + std::wstring targetPath(_targetPath); + if (!targetPath.empty() && (targetPath.back() != '/' && targetPath.back() != '\\')) + targetPath.push_back('/'); + + // parse mount path + CoreinitFSParsedPath parsedMountPath; + coreinitFS_parsePath(&parsedMountPath, mountPath); + // register path + fscEnter(); + FSCMountPathNode* node = fsc_createMountPath(&parsedMountPath, priority); + if( !node ) + { + // path empty, invalid or already used + cemuLog_log(LogType::Force, "fsc_mount failed (virtual path: %s)", mountPath); + fscLeave(); + return FSC_STATUS_INVALID_PATH; + } + node->device = fscDevice; + node->ctx = ctx; + node->targetPath = targetPath; + fscLeave(); + return FSC_STATUS_OK; +} + +bool fsc_unmount(const char* mountPath, sint32 priority) +{ + CoreinitFSParsedPath parsedMountPath; + coreinitFS_parsePath(&parsedMountPath, mountPath); + + fscEnter(); + FSCMountPathNode* mountPathNode = fsc_lookupPathVirtualNode(mountPath, priority); + if (!mountPathNode) + { + fscLeave(); + return false; + } + cemu_assert(mountPathNode->priority == priority); + cemu_assert(mountPathNode->device); + // delete node + while (mountPathNode && mountPathNode->parent) + { + FSCMountPathNode* parent = mountPathNode->parent; + cemu_assert(!(!mountPathNode->subnodes.empty() && mountPathNode->device)); + if (!mountPathNode->subnodes.empty()) + break; + parent->subnodes.erase(std::find(parent->subnodes.begin(), parent->subnodes.end(), mountPathNode)); + delete mountPathNode; + mountPathNode = parent; + } + fscLeave(); + return true; +} + +void fsc_unmountAll() +{ + fscEnter(); + fsc_reset(); + fscLeave(); +} + +// lookup virtual path and find mounted device and relative device directory +bool fsc_lookupPath(const char* path, std::wstring& devicePathOut, fscDeviceC** fscDeviceOut, void** ctxOut, sint32 priority = FSC_PRIORITY_BASE) +{ + // parse path + CoreinitFSParsedPath parsedPath; + coreinitFS_parsePath(&parsedPath, path); + FSCMountPathNode* nodeParent = s_fscRootNodePerPrio[priority]; + sint32 i; + fscEnter(); + for (i = 0; i < parsedPath.numNodes; i++) + { + // search for subdirectory + FSCMountPathNode* nodeSub = nullptr; + for(auto& nodeItr : nodeParent->subnodes) + { + if (coreinitFS_checkNodeName(&parsedPath, i, nodeItr->path.c_str())) + { + nodeSub = nodeItr; + break; + } + } + if (nodeSub) + { + nodeParent = nodeSub; + continue; + } + // no matching subnode + break; + } + // find deepest device mount point + while (nodeParent) + { + if (nodeParent->device) + { + devicePathOut = nodeParent->targetPath; + for (sint32 f = i; f < parsedPath.numNodes; f++) + { + const char* nodeName = coreinitFS_getNodeName(&parsedPath, f); + devicePathOut.append(boost::nowide::widen(nodeName)); + if (f < (parsedPath.numNodes - 1)) + devicePathOut.push_back('/'); + } + *fscDeviceOut = nodeParent->device; + *ctxOut = nodeParent->ctx; + fscLeave(); + return true; + } + nodeParent = nodeParent->parent; + i--; + } + fscLeave(); + return false; +} + +// lookup path and find virtual device node +FSCMountPathNode* fsc_lookupPathVirtualNode(const char* path, sint32 priority) +{ + // parse path + CoreinitFSParsedPath parsedPath; + coreinitFS_parsePath(&parsedPath, path); + FSCMountPathNode* nodeCurrentDir = s_fscRootNodePerPrio[priority]; + sint32 i; + fscEnter(); + for (i = 0; i < parsedPath.numNodes; i++) + { + // search for subdirectory + FSCMountPathNode* nodeSub = nullptr; + for (auto& nodeItr : nodeCurrentDir->subnodes) + { + if (coreinitFS_checkNodeName(&parsedPath, i, nodeItr->path.c_str())) + { + nodeSub = nodeItr; + break; + } + } + if (nodeSub) + { + // traverse subdirectory + nodeCurrentDir = nodeSub; + continue; + } + fscLeave(); + return nullptr; + } + fscLeave(); + return nodeCurrentDir; +} + +// this wraps multiple iterated directories from different devices into one unified virtual representation +class FSCVirtualFileDirectoryIterator : public FSCVirtualFile +{ +public: + sint32 fscGetType() override + { + return FSC_TYPE_DIRECTORY; + } + + FSCVirtualFileDirectoryIterator(std::string_view path, std::span<FSCVirtualFile*> mappedFolders) + : m_path(path), m_folders(mappedFolders.begin(), mappedFolders.end()) + { + dirIterator = nullptr; + } + + ~FSCVirtualFileDirectoryIterator() + { + // dirIterator is deleted in base constructor + for (auto& itr : m_folders) + delete itr; + } + + bool fscDirNext(FSCDirEntry* dirEntry) override + { + if (!dirIterator) + { + // lazily populate list only if directory is actually iterated + PopulateIterationList(); + cemu_assert_debug(dirIterator); + } + if (dirIterator->index >= dirIterator->dirEntries.size()) + return false; + *dirEntry = dirIterator->dirEntries[dirIterator->index]; + dirIterator->index++; + return true; + } + + void addUniqueDirEntry(const FSCDirEntry& dirEntry) + { + // skip if already in list + for (auto& itr : dirIterator->dirEntries) + { + if (FSA_CompareNodeName(dirEntry.path, itr.path)) + return; + } + dirIterator->dirEntries.emplace_back(dirEntry); + } + +private: + void PopulateIterationList() + { + cemu_assert_debug(!dirIterator); + dirIterator = new FSCVirtualFile::FSCDirIteratorState(); + FSCDirEntry dirEntry; + fscEnter(); + for (auto& itr : m_folders) + { + while (itr->fscDirNext(&dirEntry)) + addUniqueDirEntry(dirEntry); + } + for (sint32 prio = FSC_PRIORITY_COUNT - 1; prio >= 0; prio--) + { + FSCMountPathNode* nodeVirtualPath = fsc_lookupPathVirtualNode(m_path.c_str(), prio); + if (nodeVirtualPath) + { + for (auto& itr : nodeVirtualPath->subnodes) + { + dirEntry = {}; + dirEntry.isDirectory = true; + strncpy(dirEntry.path, itr->path.c_str(), sizeof(dirEntry.path) - 1); + dirEntry.path[sizeof(dirEntry.path) - 1] = '\0'; + dirEntry.fileSize = 0; + addUniqueDirEntry(dirEntry); + } + } + } + fscLeave(); + } + +private: + std::string m_path; + std::vector<FSCVirtualFile*> m_folders; // list of all folders mapped to the same directory (at different priorities) +}; + +// Open file or directory from virtual file system +FSCVirtualFile* fsc_open(const char* path, FSC_ACCESS_FLAG accessFlags, sint32* fscStatus, sint32 maxPriority) +{ + cemu_assert_debug(HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_FILE) || HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_DIR)); // must open either file or directory + + FSCVirtualFile* dirList[FSC_PRIORITY_COUNT]; + uint8 dirListCount = 0; + + std::wstring devicePath; + fscDeviceC* fscDevice = NULL; + *fscStatus = FSC_STATUS_UNDEFINED; + void* ctx; + fscEnter(); + for (sint32 prio = maxPriority; prio >= 0; prio--) + { + if (fsc_lookupPath(path, devicePath, &fscDevice, &ctx, prio)) + { + FSCVirtualFile* fscVirtualFile = fscDevice->fscDeviceOpenByPath(devicePath, accessFlags, ctx, fscStatus); + if (fscVirtualFile) + { + if (fscVirtualFile->fscGetType() == FSC_TYPE_DIRECTORY) + { + cemu_assert_debug(HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_DIR)); + // collect all folders + dirList[dirListCount] = fscVirtualFile; + dirListCount++; + } + else + { + // return first found file + cemu_assert_debug(HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_FILE)); + fscLeave(); + return fscVirtualFile; + } + } + } + } + // for directories we create a virtual representation of the enumerated files of all priorities as well as the FSC folder structure itself + if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_DIR)) + { + // create a virtual directory VirtualFile that represents all the mounted folders as well as the virtual FSC folder structure + bool folderExists = dirListCount > 0; + for (sint32 prio = FSC_PRIORITY_COUNT - 1; prio >= 0; prio--) + { + if (folderExists) + break; + folderExists |= (fsc_lookupPathVirtualNode(path, prio) != 0); + } + if (folderExists) + { + FSCVirtualFileDirectoryIterator* dirIteratorFile = new FSCVirtualFileDirectoryIterator(path, { dirList, dirListCount}); + *fscStatus = FSC_STATUS_OK; + fscLeave(); + return dirIteratorFile; + } + } + else + { + cemu_assert_debug(dirListCount == 0); + } + fscLeave(); + *fscStatus = FSC_STATUS_FILE_NOT_FOUND; + return nullptr; +} + +/* + * Open file using virtual path + */ +FSCVirtualFile* fsc_openDirIterator(const char* path, sint32* fscStatus) +{ + return fsc_open(path, FSC_ACCESS_FLAG::OPEN_DIR, fscStatus); +} + +/* +* Iterate next node in directory +* Returns false if there is no node left +*/ +bool fsc_nextDir(FSCVirtualFile* fscFile, FSCDirEntry* dirEntry) +{ + fscEnter(); + if (fscFile->fscGetType() != FSC_TYPE_DIRECTORY) + { + cemu_assert_suspicious(); + fscLeave(); + return false; + } + bool r = fscFile->fscDirNext(dirEntry); + fscLeave(); + return r; +} + +/* + * Create directory + */ +bool fsc_createDir(char* path, sint32* fscStatus) +{ + fscDeviceC* fscDevice = NULL; + *fscStatus = FSC_STATUS_UNDEFINED; + void* ctx; + std::wstring devicePath; + fscEnter(); + if( fsc_lookupPath(path, devicePath, &fscDevice, &ctx) ) + { + sint32 status = fscDevice->fscDeviceCreateDir(devicePath, ctx, fscStatus); + fscLeave(); + return status; + } + fscLeave(); + return false; +} + +/* + * Rename file or directory + */ +bool fsc_rename(char* srcPath, char* dstPath, sint32* fscStatus) +{ + std::wstring srcDevicePath; + std::wstring dstDevicePath; + void* srcCtx; + void* dstCtx; + fscDeviceC* fscSrcDevice = NULL; + fscDeviceC* fscDstDevice = NULL; + *fscStatus = FSC_STATUS_UNDEFINED; + if( fsc_lookupPath(srcPath, srcDevicePath, &fscSrcDevice, &srcCtx) && fsc_lookupPath(dstPath, dstDevicePath, &fscDstDevice, &dstCtx) ) + { + if( fscSrcDevice == fscDstDevice ) + return fscSrcDevice->fscDeviceRename(srcDevicePath, dstDevicePath, srcCtx, fscStatus); + } + return false; +} + +/* + * Delete file or subdirectory + */ +bool fsc_remove(char* path, sint32* fscStatus) +{ + std::wstring devicePath; + fscDeviceC* fscDevice = NULL; + *fscStatus = FSC_STATUS_UNDEFINED; + void* ctx; + if( fsc_lookupPath(path, devicePath, &fscDevice, &ctx) ) + { + return fscDevice->fscDeviceRemoveFileOrDir(devicePath, ctx, fscStatus); + } + return false; +} + +/* + * Close file handle + */ +void fsc_close(FSCVirtualFile* fscFile) +{ + fscEnter(); + delete fscFile; + fscLeave(); +} + +/* + * Return size of file + */ +uint32 fsc_getFileSize(FSCVirtualFile* fscFile) +{ + return (uint32)fscFile->fscQueryValueU64(FSC_QUERY_SIZE); +} + +/* + * Return file position + */ +uint32 fsc_getFileSeek(FSCVirtualFile* fscFile) +{ + return (uint32)fscFile->fscGetSeek(); +} + +/* + * Set file seek + * For writable files the seek pointer can be set past the end of the file + */ +void fsc_setFileSeek(FSCVirtualFile* fscFile, uint32 newSeek) +{ + fscEnter(); + uint32 fileSize = fsc_getFileSize(fscFile); + if (fsc_isWritable(fscFile) == false) + newSeek = std::min(newSeek, fileSize); + fscFile->fscSetSeek((uint64)newSeek); + fscLeave(); +} + +// set file length +void fsc_setFileLength(FSCVirtualFile* fscFile, uint32 newEndOffset) +{ + fscEnter(); + uint32 fileSize = fsc_getFileSize(fscFile); + if (!fsc_isWritable(fscFile)) + { + cemuLog_force("TruncateFile called on read-only file"); + } + else + { + fscFile->fscSetFileLength((uint64)newEndOffset); + } + fscLeave(); +} + +/* + * Returns true if the file object is a directory + */ +bool fsc_isDirectory(FSCVirtualFile* fscFile) +{ + return fscFile->fscGetType() == FSC_TYPE_DIRECTORY; +} + +/* + * Returns true if the file object is a file + */ +bool fsc_isFile(FSCVirtualFile* fscFile) +{ + return fscFile->fscGetType() == FSC_TYPE_FILE; +} + +/* + * Returns true if the file is writable + */ +bool fsc_isWritable(FSCVirtualFile* fscFile) +{ + return fscFile->fscQueryValueU64(FSC_QUERY_WRITEABLE) != 0; +} + +/* + * Read data from file + * Returns number of bytes successfully read + */ +uint32 fsc_readFile(FSCVirtualFile* fscFile, void* buffer, uint32 size) +{ + fscEnter(); + uint32 fscStatus = fscFile->fscReadData(buffer, size); + fscLeave(); + return fscStatus; +} + +/* + * Write data to file + * Returns number of bytes successfully written + */ +uint32 fsc_writeFile(FSCVirtualFile* fscFile, void* buffer, uint32 size) +{ + fscEnter(); + if (fsc_isWritable(fscFile) == false) + { + fscLeave(); + return 0; + } + uint32 fscStatus = fscFile->fscWriteData(buffer, size); + fscLeave(); + return fscStatus; +} + +// helper function to load a file into memory +uint8* fsc_extractFile(const char* path, uint32* fileSize, sint32 maxPriority) +{ + fscDeviceC* fscDevice = nullptr; + sint32 fscStatus = FSC_STATUS_UNDEFINED; + fscEnter(); + FSCVirtualFile* fscFile = fsc_open(path, FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus, maxPriority); + if( !fscFile ) + { + *fileSize = 0; + fscLeave(); + return nullptr; + } + uint32 fscFileSize = fsc_getFileSize(fscFile); + *fileSize = fscFileSize; + uint8* fileMem = (uint8*)malloc(fscFileSize); + if( fsc_readFile(fscFile, fileMem, fscFileSize) != fscFileSize ) + { + free(fileMem); + fsc_close(fscFile); + *fileSize = 0; + fscLeave(); + return nullptr; + } + fsc_close(fscFile); + fscLeave(); + return fileMem; +} + +std::optional<std::vector<uint8>> fsc_extractFile(const char* path, sint32 maxPriority) +{ + fscDeviceC* fscDevice = nullptr; + sint32 fscStatus = FSC_STATUS_UNDEFINED; + fscEnter(); + FSCVirtualFile* fscFile = fsc_open(path, FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus, maxPriority); + if (!fscFile) + { + fscLeave(); + return std::nullopt; + } + std::vector<uint8> fileData; + uint32 fscFileSize = fsc_getFileSize(fscFile); + fileData.resize(fscFileSize); + + uint32 readOffset = 0; + while (readOffset < fscFileSize) + { + uint32 stepReadSize = std::min(fscFileSize - readOffset, (uint32)1024 * 1024 * 32); + uint32 numBytesRead = fsc_readFile(fscFile, fileData.data() + readOffset, stepReadSize); + if (numBytesRead != stepReadSize) + { + fsc_close(fscFile); + fscLeave(); + return std::nullopt; + } + readOffset += stepReadSize; + } + fsc_close(fscFile); + fscLeave(); + return fileData; +} + +// helper function to check if a file exists +bool fsc_doesFileExist(const char* path, sint32 maxPriority) +{ + fscDeviceC* fscDevice = nullptr; + sint32 fscStatus = FSC_STATUS_UNDEFINED; + fscEnter(); + FSCVirtualFile* fscFile = fsc_open(path, FSC_ACCESS_FLAG::OPEN_FILE, &fscStatus, maxPriority); + if (!fscFile) + { + fscLeave(); + return false; + } + fsc_close(fscFile); + fscLeave(); + return true; +} + +// helper function to check if a folder exists +bool fsc_doesDirectoryExist(const char* path, sint32 maxPriority) +{ + fscDeviceC* fscDevice = nullptr; + sint32 fscStatus = FSC_STATUS_UNDEFINED; + fscEnter(); + FSCVirtualFile* fscFile = fsc_open(path, FSC_ACCESS_FLAG::OPEN_DIR, &fscStatus, maxPriority); + if (!fscFile) + { + fscLeave(); + return false; + } + fsc_close(fscFile); + fscLeave(); + return true; +} + + +void coreinitFS_parsePath(CoreinitFSParsedPath* parsedPath, const char* path) +{ + // if the path starts with a '/', skip it + if (*path == '/') + path++; + // init parsedPath struct + memset(parsedPath, 0x00, sizeof(CoreinitFSParsedPath)); + // init parsed path data + size_t pathLength = std::min((size_t)640, strlen(path)); + memcpy(parsedPath->pathData, path, pathLength); + // start parsing + sint32 offset = 0; + sint32 startOffset = 0; + if (offset < pathLength) + { + parsedPath->nodeOffset[parsedPath->numNodes] = offset; + parsedPath->numNodes++; + } + while (offset < pathLength) + { + if (parsedPath->pathData[offset] == '/' || parsedPath->pathData[offset] == '\\') + { + parsedPath->pathData[offset] = '\0'; + offset++; + // double slashes are ignored and instead are handled like a single slash + if (parsedPath->pathData[offset] == '/' || parsedPath->pathData[offset] == '\\') + { + // if we're in the beginning and having a \\ it's a network path + if (offset != 1) + { + parsedPath->pathData[offset] = '\0'; + offset++; + } + } + // start new node + if (parsedPath->numNodes < FSC_PARSED_PATH_NODES_MAX) + { + if (offset < pathLength) + { + parsedPath->nodeOffset[parsedPath->numNodes] = offset; + parsedPath->numNodes++; + } + } + continue; + } + offset++; + } + // handle special nodes like '.' or '..' + sint32 nodeIndex = 0; + while (nodeIndex < parsedPath->numNodes) + { + if (coreinitFS_checkNodeName(parsedPath, nodeIndex, "..")) + cemu_assert_suspicious(); // how does Cafe OS handle .. ? + else if (coreinitFS_checkNodeName(parsedPath, nodeIndex, ".")) + { + // remove this node and shift back all following nodes by 1 + parsedPath->numNodes--; + for (sint32 i = nodeIndex; i < parsedPath->numNodes; i++) + { + parsedPath->nodeOffset[i] = parsedPath->nodeOffset[i + 1]; + } + // continue without increasing nodeIndex + continue; + } + nodeIndex++; + } +} + +bool coreinitFS_checkNodeName(CoreinitFSParsedPath* parsedPath, sint32 index, const char* name) +{ + if (index < 0 || index >= parsedPath->numNodes) + return false; + char* nodeName = parsedPath->pathData + parsedPath->nodeOffset[index]; + if (boost::iequals(nodeName, name)) + return true; + return false; +} + +char* coreinitFS_getNodeName(CoreinitFSParsedPath* parsedPath, sint32 index) +{ + if (index < 0 || index >= parsedPath->numNodes) + return nullptr; + return parsedPath->pathData + parsedPath->nodeOffset[index]; +} + +// Initialize Cemu's virtual filesystem +void fsc_init() +{ + fsc_reset(); +} diff --git a/src/Cafe/Filesystem/fsc.h b/src/Cafe/Filesystem/fsc.h new file mode 100644 index 00000000..d63b7c2d --- /dev/null +++ b/src/Cafe/Filesystem/fsc.h @@ -0,0 +1,219 @@ +#pragma once + +struct FSCVirtualFile; + +#define FSC_TYPE_INVALID (0) +#define FSC_TYPE_FILE (1) +#define FSC_TYPE_DIRECTORY (2) + +#define FSC_QUERY_SIZE (1) // file size, 0 for directories +#define FSC_QUERY_WRITEABLE (2) // non-zero if file is writeable, else 0 + +enum class FSC_ACCESS_FLAG : uint8 +{ + NONE = 0, + + // file permissions + READ_PERMISSION = (1<<0), + WRITE_PERMISSION = (1<<1), + + // file open mode (incompatible with OPEN_DIR flag) + FILE_ALLOW_CREATE = (1 << 2), // create file if it does not exist + FILE_ALWAYS_CREATE = (1 << 3), // overwrite any existing file + + // which types can be opened + // invalid operation if neither is set + OPEN_DIR = (1 << 4), + OPEN_FILE = (1 << 5) +}; +DEFINE_ENUM_FLAG_OPERATORS(FSC_ACCESS_FLAG); + +#define FSC_STATUS_UNDEFINED (-1) +#define FSC_STATUS_OK (0) +#define FSC_STATUS_INVALID_PATH (1) +#define FSC_STATUS_FILE_NOT_FOUND (2) +#define FSC_STATUS_ALREADY_EXISTS (3) +// note: Unlike the native Wii U filesystem, FSC does not provide separate error codes for NOT_A_FILE and NOT_A_DIRECTORY +// to determine them manually, open with both modes (file and dir) and check the type + +#define FSC_MAX_DIR_NAME_LENGTH (256) +#define FSC_MAX_DEVICE_PATH_LENGTH ((std::max)(260,FSA_PATH_SIZE_MAX)) // max length for FSC device paths (should be at least equal or greater than supported by host filesystem) + +struct FSCDirEntry +{ + char path[FSC_MAX_DIR_NAME_LENGTH]; + // stats + bool isDirectory; + bool isFile; + uint32 fileSize; + + std::string_view GetPath() + { + size_t len = strnlen(path, FSC_MAX_DIR_NAME_LENGTH); + return std::basic_string_view<char>(path, len); + } +}; + +class fscDeviceC +{ +public: + virtual FSCVirtualFile* fscDeviceOpenByPath(std::wstring_view path, FSC_ACCESS_FLAG accessFlags, void* ctx, sint32* fscStatus) + { + cemu_assert_unimplemented(); + return nullptr; + } + + virtual bool fscDeviceCreateDir(std::wstring_view path, void* ctx, sint32* fscStatus) + { + cemu_assert_unimplemented(); + return false; + } + + virtual bool fscDeviceRemoveFileOrDir(std::wstring_view path, void* ctx, sint32* fscStatus) + { + cemu_assert_unimplemented(); + return false; + } + + virtual bool fscDeviceRename(std::wstring_view srcPath, std::wstring_view dstPath, void* ctx, sint32* fscStatus) + { + cemu_assert_unimplemented(); + return false; + } + +}; + + +struct FSCVirtualFile +{ + struct FSCDirIteratorState + { + sint32 index; + std::vector<FSCDirEntry> dirEntries; + }; + + FSCVirtualFile() + { + + } + + virtual ~FSCVirtualFile() + { + if (dirIterator) + delete dirIterator; + } + + virtual sint32 fscGetType() + { + cemu_assert_unimplemented(); + return 0; + } + + virtual uint64 fscQueryValueU64(uint32 id) + { + cemu_assert_unimplemented(); + return 0; + } + + virtual uint32 fscWriteData(void* buffer, uint32 size) + { + cemu_assert_unimplemented(); + return 0; + } + + virtual uint32 fscReadData(void* buffer, uint32 size) + { + cemu_assert_unimplemented(); + return 0; + } + + virtual void fscSetSeek(uint64 seek) + { + cemu_assert_unimplemented(); + } + + virtual uint64 fscGetSeek() + { + cemu_assert_unimplemented(); + return 0; + } + + virtual void fscSetFileLength(uint64 endOffset) + { + cemu_assert_unimplemented(); + } + + virtual bool fscDirNext(FSCDirEntry* dirEntry) + { + cemu_assert_unimplemented(); + return false; + } + + FSCDirIteratorState* dirIterator{}; +}; + +#define FSC_PRIORITY_BASE (0) +#define FSC_PRIORITY_AOC (1) +#define FSC_PRIORITY_PATCH (2) +#define FSC_PRIORITY_REDIRECT (3) +#define FSC_PRIORITY_MAX (3) + +#define FSC_PRIORITY_COUNT (4) + +void fsc_init(); +sint32 fsc_mount(const char* mountPath, const wchar_t* targetPath, fscDeviceC* fscDevice, void* ctx, sint32 priority=0); +bool fsc_unmount(const char* mountPath, sint32 priority); +void fsc_unmountAll(); + +FSCVirtualFile* fsc_open(const char* path, FSC_ACCESS_FLAG accessFlags, sint32* fscStatus, sint32 maxPriority=FSC_PRIORITY_MAX); +FSCVirtualFile* fsc_openDirIterator(const char* path, sint32* fscStatus); +bool fsc_createDir(char* path, sint32* fscStatus); +bool fsc_rename(char* srcPath, char* dstPath, sint32* fscStatus); +bool fsc_remove(char* path, sint32* fscStatus); +bool fsc_nextDir(FSCVirtualFile* fscFile, FSCDirEntry* dirEntry); +void fsc_close(FSCVirtualFile* fscFile); +uint32 fsc_getFileSize(FSCVirtualFile* fscFile); +uint32 fsc_getFileSeek(FSCVirtualFile* fscFile); +void fsc_setFileSeek(FSCVirtualFile* fscFile, uint32 newSeek); +void fsc_setFileLength(FSCVirtualFile* fscFile, uint32 newEndOffset); +bool fsc_isDirectory(FSCVirtualFile* fscFile); +bool fsc_isFile(FSCVirtualFile* fscFile); +bool fsc_isWritable(FSCVirtualFile* fscFile); +uint32 fsc_readFile(FSCVirtualFile* fscFile, void* buffer, uint32 size); +uint32 fsc_writeFile(FSCVirtualFile* fscFile, void* buffer, uint32 size); + +uint8* fsc_extractFile(const char* path, uint32* fileSize, sint32 maxPriority = FSC_PRIORITY_MAX); +std::optional<std::vector<uint8>> fsc_extractFile(const char* path, sint32 maxPriority = FSC_PRIORITY_MAX); +bool fsc_doesFileExist(const char* path, sint32 maxPriority = FSC_PRIORITY_MAX); +bool fsc_doesDirectoryExist(const char* path, sint32 maxPriority = FSC_PRIORITY_MAX); + +// wud device +bool FSCDeviceWUD_Mount(const char* mountPath, std::string_view destinationBaseDir, class FSTVolume* mountedVolume, sint32 priority); + +// wua device +bool FSCDeviceWUA_Mount(const char* mountPath, std::string_view destinationBaseDir, class ZArchiveReader* archive, sint32 priority); + +// hostFS device +void fscDeviceHostFS_mapBaseDirectories_deprecated(); +bool FSCDeviceHostFS_Mount(const char* mountPath, const wchar_t* hostFSPath, sint32 priority); + +// redirect device +void fscDeviceRedirect_map(); +void fscDeviceRedirect_add(std::string_view virtualSourcePath, const fs::path& targetFilePath, sint32 priority); + + +// Old path parser helper functions +// Replace with FSCPath + +#define FSC_PARSED_PATH_NODES_MAX (32) + +struct CoreinitFSParsedPath +{ + char pathData[640 + 1]; + uint16 nodeOffset[FSC_PARSED_PATH_NODES_MAX]; + sint32 numNodes; +}; + +void coreinitFS_parsePath(CoreinitFSParsedPath* parsedPath, const char* path); +bool coreinitFS_checkNodeName(CoreinitFSParsedPath* parsedPath, sint32 index, const char* name); +char* coreinitFS_getNodeName(CoreinitFSParsedPath* parsedPath, sint32 index); \ No newline at end of file diff --git a/src/Cafe/Filesystem/fscDeviceHostFS.cpp b/src/Cafe/Filesystem/fscDeviceHostFS.cpp new file mode 100644 index 00000000..43ae6b4f --- /dev/null +++ b/src/Cafe/Filesystem/fscDeviceHostFS.cpp @@ -0,0 +1,306 @@ +#include "config/ActiveSettings.h" +#include "Cafe/Filesystem/fsc.h" +#include "Cafe/Filesystem/fscDeviceHostFS.h" + +#include "Common/filestream.h" + +/* FSCVirtualFile implementation for HostFS */ + +FSCVirtualFile_Host::~FSCVirtualFile_Host() +{ + if (m_type == FSC_TYPE_FILE) + delete m_fs; +} + +sint32 FSCVirtualFile_Host::fscGetType() +{ + return m_type; +} + +uint32 FSCVirtualFile_Host::fscDeviceHostFSFile_getFileSize() +{ + if (m_type == FSC_TYPE_FILE) + { + if (m_fileSize > 0xFFFFFFFFULL) + cemu_assert_suspicious(); // files larger than 4GB are not supported by Wii U filesystem + return (uint32)m_fileSize; + } + else if (m_type == FSC_TYPE_DIRECTORY) + { + // todo + return (uint32)0; + } + return 0; +} + +uint64 FSCVirtualFile_Host::fscQueryValueU64(uint32 id) +{ + if (m_type == FSC_TYPE_FILE) + { + if (id == FSC_QUERY_SIZE) + return fscDeviceHostFSFile_getFileSize(); + else if (id == FSC_QUERY_WRITEABLE) + return m_isWritable; + else + cemu_assert_unimplemented(); + } + else if (m_type == FSC_TYPE_DIRECTORY) + { + if (id == FSC_QUERY_SIZE) + return fscDeviceHostFSFile_getFileSize(); + else + cemu_assert_unimplemented(); + } + cemu_assert_unimplemented(); + return 0; +} + +uint32 FSCVirtualFile_Host::fscWriteData(void* buffer, uint32 size) +{ + if (m_type != FSC_TYPE_FILE) + return 0; + if (size >= (2UL * 1024UL * 1024UL * 1024UL)) + { + cemu_assert_suspicious(); + return 0; + } + sint32 writtenBytes = m_fs->writeData(buffer, (sint32)size); + m_seek += (uint64)writtenBytes; + m_fileSize = std::max(m_fileSize, m_seek); + return (uint32)writtenBytes; +} + +uint32 FSCVirtualFile_Host::fscReadData(void* buffer, uint32 size) +{ + if (m_type != FSC_TYPE_FILE) + return 0; + if (size >= (2UL * 1024UL * 1024UL * 1024UL)) + { + cemu_assert_suspicious(); + return 0; + } + uint32 bytesLeft = (uint32)(m_fileSize - m_seek); + bytesLeft = std::min(bytesLeft, 0x7FFFFFFFu); + sint32 bytesToRead = std::min(bytesLeft, size); + uint32 bytesRead = m_fs->readData(buffer, bytesToRead); + m_seek += bytesRead; + return bytesRead; +} + +void FSCVirtualFile_Host::fscSetSeek(uint64 seek) +{ + if (m_type != FSC_TYPE_FILE) + return; + this->m_seek = seek; + cemu_assert_debug(seek <= m_fileSize); + m_fs->SetPosition(seek); +} + +uint64 FSCVirtualFile_Host::fscGetSeek() +{ + if (m_type != FSC_TYPE_FILE) + return 0; + return m_seek; +} + +void FSCVirtualFile_Host::fscSetFileLength(uint64 endOffset) +{ + if (m_type != FSC_TYPE_FILE) + return; + m_fs->SetPosition(endOffset); + bool r = m_fs->SetEndOfFile(); + m_seek = std::min(m_seek, endOffset); + m_fileSize = m_seek; + m_fs->SetPosition(m_seek); + if (!r) + cemuLog_force("fscSetFileLength: Failed to set size to 0x{:x}", endOffset); +} + +bool FSCVirtualFile_Host::fscDirNext(FSCDirEntry* dirEntry) +{ + if (m_type != FSC_TYPE_DIRECTORY) + return false; + + if (!m_dirIterator) + { + // init iterator on first iteration attempt + m_dirIterator.reset(new fs::directory_iterator(*m_path)); + if (!m_dirIterator) + { + cemuLog_force("Failed to iterate directory: {}", _utf8Wrapper(m_path->generic_u8string())); + return false; + } + } + if (*m_dirIterator == fs::end(*m_dirIterator)) + return false; + + const fs::directory_entry& entry = **m_dirIterator; + + std::string fileName = entry.path().filename().generic_string(); + if (fileName.size() >= sizeof(dirEntry->path) - 1) + fileName.resize(sizeof(dirEntry->path) - 1); + strncpy(dirEntry->path, fileName.data(), sizeof(dirEntry->path)); + if (entry.is_directory()) + { + dirEntry->isDirectory = true; + dirEntry->isFile = false; + dirEntry->fileSize = 0; + } + else + { + dirEntry->isDirectory = false; + dirEntry->isFile = true; + dirEntry->fileSize = entry.file_size(); + } + + (*m_dirIterator)++; + return true; +} + +FSCVirtualFile* FSCVirtualFile_Host::OpenFile(const fs::path& path, FSC_ACCESS_FLAG accessFlags, sint32& fscStatus) +{ + if (!HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_FILE) && !HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_DIR)) + cemu_assert_debug(false); // not allowed. At least one of both flags must be set + + // attempt to open as file + if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_FILE)) + { + FileStream* fs{}; + bool writeAccessRequested = HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::WRITE_PERMISSION); + if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::FILE_ALLOW_CREATE)) + { + fs = FileStream::openFile2(path, writeAccessRequested); + if (!fs) + { + cemu_assert_debug(writeAccessRequested); + fs = FileStream::createFile2(path); + if (!fs) + cemuLog_force("FSC: File create failed for {}", _utf8Wrapper(path)); + } + } + else if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::FILE_ALWAYS_CREATE)) + { + fs = FileStream::createFile2(path); + if (!fs) + cemuLog_force("FSC: File create failed for {}", _utf8Wrapper(path)); + } + else + { + fs = FileStream::openFile2(path, writeAccessRequested); + } + if (fs) + { + FSCVirtualFile_Host* vf = new FSCVirtualFile_Host(FSC_TYPE_FILE); + vf->m_fs = fs; + vf->m_isWritable = writeAccessRequested; + vf->m_fileSize = fs->GetSize(); + fscStatus = FSC_STATUS_OK; + return vf; + } + } + + // attempt to open as directory + if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_DIR)) + { + std::error_code ec; + bool isExistingDir = fs::is_directory(path, ec); + if (isExistingDir) + { + FSCVirtualFile_Host* vf = new FSCVirtualFile_Host(FSC_TYPE_DIRECTORY); + vf->m_path.reset(new std::filesystem::path(path)); + fscStatus = FSC_STATUS_OK; + return vf; + } + } + fscStatus = FSC_STATUS_FILE_NOT_FOUND; + return nullptr; +} + +/* Device implementation */ + +class fscDeviceHostFSC : public fscDeviceC +{ +public: + FSCVirtualFile* fscDeviceOpenByPath(std::wstring_view path, FSC_ACCESS_FLAG accessFlags, void* ctx, sint32* fscStatus) override + { + *fscStatus = FSC_STATUS_OK; + FSCVirtualFile* vf = FSCVirtualFile_Host::OpenFile(path, accessFlags, *fscStatus); + cemu_assert_debug((bool)vf == (*fscStatus == FSC_STATUS_OK)); + return vf; + } + + bool fscDeviceCreateDir(std::wstring_view path, void* ctx, sint32* fscStatus) override + { + fs::path dirPath(path); + if (fs::exists(path)) + { + if (!fs::is_directory(dirPath)) + cemuLog_force("CreateDir: {} already exists but is not a directory", _utf8Wrapper(dirPath)); + *fscStatus = FSC_STATUS_ALREADY_EXISTS; + return false; + } + std::error_code ec; + bool r = fs::create_directories(dirPath, ec); + if(!r) + cemuLog_force("CreateDir: Failed to create {}", _utf8Wrapper(dirPath)); + *fscStatus = FSC_STATUS_OK; + return true; + } + + bool fscDeviceRemoveFileOrDir(std::wstring_view path, void* ctx, sint32* fscStatus) override + { + *fscStatus = FSC_STATUS_OK; + fs::path _path(path); + std::error_code ec; + if (!fs::exists(_path, ec)) + { + *fscStatus = FSC_STATUS_FILE_NOT_FOUND; + return false; + } + if (!fs::remove(_path, ec)) + { + cemu_assert_unimplemented(); // return correct error (e.g. if directory is non-empty) + *fscStatus = FSC_STATUS_FILE_NOT_FOUND; + } + *fscStatus = FSC_STATUS_FILE_NOT_FOUND; + return true; + } + + bool fscDeviceRename(std::wstring_view srcPath, std::wstring_view dstPath, void* ctx, sint32* fscStatus) override + { + *fscStatus = FSC_STATUS_OK; + fs::path _srcPath(srcPath); + fs::path _dstPath(dstPath); + std::error_code ec; + if (!fs::exists(_srcPath, ec)) + { + *fscStatus = FSC_STATUS_FILE_NOT_FOUND; + return false; + } + fs::rename(_srcPath, _dstPath, ec); + return true; + } + + // singleton +public: + static fscDeviceHostFSC& instance() + { + static fscDeviceHostFSC _instance; + return _instance; + } +}; + +void fscDeviceHostFS_mapBaseDirectories_deprecated() +{ + const auto mlc = ActiveSettings::GetMlcPath(); + fsc_mount("/cemuBossStorage/", (mlc / "usr/boss/").generic_wstring().c_str(), &fscDeviceHostFSC::instance(), NULL, FSC_PRIORITY_BASE); + fsc_mount("/vol/storage_mlc01/", (mlc / "").generic_wstring().c_str(), &fscDeviceHostFSC::instance(), NULL, FSC_PRIORITY_BASE); +} + +bool FSCDeviceHostFS_Mount(const char* mountPath, const wchar_t* hostFSPath, sint32 priority) +{ + std::wstring hostTargetPath(hostFSPath); + if (!hostTargetPath.empty() && (hostTargetPath.back() != '/' && hostTargetPath.back() != '\\')) + hostTargetPath.push_back('/'); + return fsc_mount(mountPath, hostTargetPath.c_str(), &fscDeviceHostFSC::instance(), nullptr, priority) == FSC_STATUS_OK; +} \ No newline at end of file diff --git a/src/Cafe/Filesystem/fscDeviceHostFS.h b/src/Cafe/Filesystem/fscDeviceHostFS.h new file mode 100644 index 00000000..7121594c --- /dev/null +++ b/src/Cafe/Filesystem/fscDeviceHostFS.h @@ -0,0 +1,34 @@ +#include "Cafe/Filesystem/fsc.h" + +class FSCVirtualFile_Host : public FSCVirtualFile +{ +public: + static FSCVirtualFile* OpenFile(const fs::path& path, FSC_ACCESS_FLAG accessFlags, sint32& fscStatus); + ~FSCVirtualFile_Host() override; + + sint32 fscGetType() override; + + uint32 fscDeviceHostFSFile_getFileSize(); + + uint64 fscQueryValueU64(uint32 id) override; + uint32 fscWriteData(void* buffer, uint32 size) override; + uint32 fscReadData(void* buffer, uint32 size) override; + void fscSetSeek(uint64 seek) override; + uint64 fscGetSeek() override; + void fscSetFileLength(uint64 endOffset) override; + bool fscDirNext(FSCDirEntry* dirEntry) override; + +private: + FSCVirtualFile_Host(uint32 type) : m_type(type) {}; + +private: + uint32 m_type; // FSC_TYPE_* + class FileStream* m_fs{}; + // file + uint64 m_seek{ 0 }; + uint64 m_fileSize{ 0 }; + bool m_isWritable{ false }; + // directory + std::unique_ptr<std::filesystem::path> m_path{}; + std::unique_ptr<std::filesystem::directory_iterator> m_dirIterator{}; +}; \ No newline at end of file diff --git a/src/Cafe/Filesystem/fscDeviceRedirect.cpp b/src/Cafe/Filesystem/fscDeviceRedirect.cpp new file mode 100644 index 00000000..5d5edb9e --- /dev/null +++ b/src/Cafe/Filesystem/fscDeviceRedirect.cpp @@ -0,0 +1,57 @@ +#include "util/helpers/helpers.h" +#include "Cafe/Filesystem/fscDeviceHostFS.h" +#include "FST/fstUtil.h" + +struct RedirectEntry +{ + RedirectEntry(const fs::path& dstPath, sint32 priority) : dstPath(dstPath), priority(priority) {} + fs::path dstPath; + sint32 priority; +}; + +FileTree<RedirectEntry, false> redirectTree; + +void fscDeviceRedirect_add(std::string_view virtualSourcePath, const fs::path& targetFilePath, sint32 priority) +{ + std::wstring virtualSourcePathW = boost::nowide::widen(std::string(virtualSourcePath)); + // check if source already has a redirection + RedirectEntry* existingEntry; + if (redirectTree.getFile(virtualSourcePathW, existingEntry)) + { + if (existingEntry->priority >= priority) + return; // dont replace entries with equal or higher priority + // unregister existing entry + redirectTree.removeFile(virtualSourcePathW.c_str()); + delete existingEntry; + } + RedirectEntry* entry = new RedirectEntry(targetFilePath, priority); + redirectTree.addFile(virtualSourcePathW.c_str(), entry); +} + +class fscDeviceTypeRedirect : public fscDeviceC +{ + FSCVirtualFile* fscDeviceOpenByPath(std::wstring_view pathW, FSC_ACCESS_FLAG accessFlags, void* ctx, sint32* fscStatus) override + { + RedirectEntry* redirectionEntry; + if (redirectTree.getFile(pathW, redirectionEntry)) + return FSCVirtualFile_Host::OpenFile(redirectionEntry->dstPath, accessFlags, *fscStatus); + return nullptr; + } + +public: + static fscDeviceTypeRedirect& instance() + { + static fscDeviceTypeRedirect _instance; + return _instance; + } +}; + +bool _redirectMapped = false; + +void fscDeviceRedirect_map() +{ + if (_redirectMapped) + return; + fsc_mount("/", L"/", &fscDeviceTypeRedirect::instance(), nullptr, FSC_PRIORITY_REDIRECT); + _redirectMapped = true; +} diff --git a/src/Cafe/Filesystem/fscDeviceWua.cpp b/src/Cafe/Filesystem/fscDeviceWua.cpp new file mode 100644 index 00000000..96eb920a --- /dev/null +++ b/src/Cafe/Filesystem/fscDeviceWua.cpp @@ -0,0 +1,178 @@ +#include "Cafe/Filesystem/fsc.h" +#include <zarchive/zarchivereader.h> + +class FSCDeviceWuaFileCtx : public FSCVirtualFile +{ + friend class fscDeviceWUAC; + +protected: + FSCDeviceWuaFileCtx(ZArchiveReader* archive, ZArchiveNodeHandle fstFileHandle, uint32 fscType) + { + this->m_archive = archive; + this->m_fscType = fscType; + this->m_nodeHandle = fstFileHandle; + this->m_seek = 0; + }; + +public: + sint32 fscGetType() override + { + return m_fscType; + } + + uint32 fscDeviceWuaFile_getFileSize() + { + return (uint32)m_archive->GetFileSize(m_nodeHandle); + } + + uint64 fscQueryValueU64(uint32 id) override + { + if (m_fscType == FSC_TYPE_FILE) + { + if (id == FSC_QUERY_SIZE) + return fscDeviceWuaFile_getFileSize(); + else if (id == FSC_QUERY_WRITEABLE) + return 0; // WUD images are read-only + else + cemu_assert_error(); + } + else + { + cemu_assert_unimplemented(); + } + return 0; + } + + uint32 fscWriteData(void* buffer, uint32 size) override + { + cemu_assert_error(); + return 0; + } + + uint32 fscReadData(void* buffer, uint32 size) override + { + if (m_fscType != FSC_TYPE_FILE) + return 0; + cemu_assert(size < (2ULL * 1024 * 1024 * 1024)); // single read operation larger than 2GiB not supported + uint32 bytesLeft = fscDeviceWuaFile_getFileSize() - m_seek; + uint32 bytesToRead = (std::min)(bytesLeft, (uint32)size); + uint32 bytesSuccessfullyRead = (uint32)m_archive->ReadFromFile(m_nodeHandle, m_seek, bytesToRead, buffer); + m_seek += bytesSuccessfullyRead; + return bytesSuccessfullyRead; + } + + void fscSetSeek(uint64 seek) override + { + if (m_fscType != FSC_TYPE_FILE) + return; + cemu_assert_debug(seek <= 0xFFFFFFFFULL); + this->m_seek = (uint32)seek; + } + + uint64 fscGetSeek() override + { + if (m_fscType != FSC_TYPE_FILE) + return 0; + return m_seek; + } + + bool fscDirNext(FSCDirEntry* dirEntry) override + { + if (m_fscType != FSC_TYPE_DIRECTORY) + return false; + + ZArchiveReader::DirEntry zarDirEntry; + if (!m_archive->GetDirEntry(m_nodeHandle, m_iteratorIndex, zarDirEntry)) + return false; + m_iteratorIndex++; + + if (zarDirEntry.isDirectory) + { + dirEntry->isDirectory = true; + dirEntry->isFile = false; + dirEntry->fileSize = 0; + } + else if(zarDirEntry.isFile) + { + dirEntry->isDirectory = false; + dirEntry->isFile = true; + dirEntry->fileSize = (uint32)zarDirEntry.size; + } + else + { + cemu_assert_suspicious(); + } + std::memset(dirEntry->path, 0, sizeof(dirEntry->path)); + std::strncpy(dirEntry->path, zarDirEntry.name.data(), std::min(sizeof(dirEntry->path) - 1, zarDirEntry.name.size())); + return true; + } + +private: + ZArchiveReader* m_archive{nullptr}; + sint32 m_fscType; + ZArchiveNodeHandle m_nodeHandle; + // file + uint32 m_seek{0}; + // directory + uint32 m_iteratorIndex{0}; +}; + +class fscDeviceWUAC : public fscDeviceC +{ + FSCVirtualFile* fscDeviceOpenByPath(std::wstring_view path, FSC_ACCESS_FLAG accessFlags, void* ctx, sint32* fscStatus) override + { + ZArchiveReader* archive = (ZArchiveReader*)ctx; + cemu_assert_debug(!HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::WRITE_PERMISSION)); // writing to WUA is not supported + + std::string pathU8 = boost::nowide::narrow(path.data(), path.size()); + + ZArchiveNodeHandle fileHandle = archive->LookUp(pathU8, true, true); + if (fileHandle == ZARCHIVE_INVALID_NODE) + { + *fscStatus = FSC_STATUS_FILE_NOT_FOUND; + return nullptr; + } + if (archive->IsFile(fileHandle)) + { + if (!HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_FILE)) + { + *fscStatus = FSC_STATUS_FILE_NOT_FOUND; + return nullptr; + } + *fscStatus = FSC_STATUS_OK; + return new FSCDeviceWuaFileCtx(archive, fileHandle, FSC_TYPE_FILE); + } + else if (archive->IsDirectory(fileHandle)) + { + if (!HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_DIR)) + { + *fscStatus = FSC_STATUS_FILE_NOT_FOUND; + return nullptr; + } + *fscStatus = FSC_STATUS_OK; + return new FSCDeviceWuaFileCtx(archive, fileHandle, FSC_TYPE_DIRECTORY); + } + else + { + cemu_assert_suspicious(); + } + *fscStatus = FSC_STATUS_FILE_NOT_FOUND; + return nullptr; + } + + // singleton +public: + static fscDeviceWUAC& instance() + { + static fscDeviceWUAC _instance; + return _instance; + } +}; + +bool FSCDeviceWUA_Mount(const char* mountPath, std::string_view destinationBaseDir, ZArchiveReader* archive, sint32 priority) +{ + std::wstring hostTargetPath(boost::nowide::widen(destinationBaseDir.data(), destinationBaseDir.size())); + if (!hostTargetPath.empty() && (hostTargetPath.back() != '/' && hostTargetPath.back() != '\\')) + hostTargetPath.push_back('/'); + return fsc_mount(mountPath, hostTargetPath.c_str(), &fscDeviceWUAC::instance(), archive, priority) == FSC_STATUS_OK; +} \ No newline at end of file diff --git a/src/Cafe/Filesystem/fscDeviceWud.cpp b/src/Cafe/Filesystem/fscDeviceWud.cpp new file mode 100644 index 00000000..10054bad --- /dev/null +++ b/src/Cafe/Filesystem/fscDeviceWud.cpp @@ -0,0 +1,166 @@ +#include "Cafe/Filesystem/fsc.h" +#include "Cafe/Filesystem/FST/FST.h" + +class FSCDeviceWudFileCtx : public FSCVirtualFile +{ + friend class fscDeviceWUDC; + +protected: + FSCDeviceWudFileCtx(FSTVolume* _volume, FSTFileHandle _fstFileHandle) + { + this->m_volume = _volume; + this->m_fscType = FSC_TYPE_FILE; + this->m_fstFileHandle = _fstFileHandle; + this->m_seek = 0; + }; + + FSCDeviceWudFileCtx(FSTVolume* _volume, FSTDirectoryIterator _dirIterator) + { + this->m_volume = _volume; + this->m_fscType = FSC_TYPE_DIRECTORY; + this->m_dirIterator = _dirIterator; + } + +public: + sint32 fscGetType() override + { + return m_fscType; + } + + uint32 fscDeviceWudFile_getFileSize() + { + return m_volume->GetFileSize(m_fstFileHandle); + } + + uint64 fscQueryValueU64(uint32 id) override + { + if (m_fscType == FSC_TYPE_FILE) + { + if (id == FSC_QUERY_SIZE) + return fscDeviceWudFile_getFileSize(); + else if (id == FSC_QUERY_WRITEABLE) + return 0; // WUD images are read-only + else + cemu_assert_error(); + } + else + { + cemu_assert_unimplemented(); + } + return 0; + } + + uint32 fscWriteData(void* buffer, uint32 size) override + { + cemu_assert_error(); + return 0; + } + + uint32 fscReadData(void* buffer, uint32 size) override + { + if (m_fscType != FSC_TYPE_FILE) + return 0; + cemu_assert(size < (2ULL * 1024 * 1024 * 1024)); // single read operation larger than 2GiB not supported + uint32 bytesLeft = fscDeviceWudFile_getFileSize() - m_seek; + uint32 bytesToRead = (std::min)(bytesLeft, (uint32)size); + uint32 bytesSuccessfullyRead = m_volume->ReadFile(m_fstFileHandle, m_seek, bytesToRead, buffer); + m_seek += bytesSuccessfullyRead; + return bytesSuccessfullyRead; + } + + void fscSetSeek(uint64 seek) override + { + if (m_fscType != FSC_TYPE_FILE) + return; + cemu_assert_debug(seek <= 0xFFFFFFFFULL); + this->m_seek = (uint32)seek; + } + + uint64 fscGetSeek() override + { + if (m_fscType != FSC_TYPE_FILE) + return 0; + return m_seek; + } + + bool fscDirNext(FSCDirEntry* dirEntry) override + { + if (m_fscType != FSC_TYPE_DIRECTORY) + return false; + FSTFileHandle entryItr; + if (!m_volume->Next(m_dirIterator, entryItr)) + return false; + if (m_volume->IsDirectory(entryItr)) + { + dirEntry->isDirectory = true; + dirEntry->isFile = false; + dirEntry->fileSize = 0; + } + else + { + dirEntry->isDirectory = false; + dirEntry->isFile = true; + dirEntry->fileSize = m_volume->GetFileSize(entryItr); + } + auto path = m_volume->GetName(entryItr); + std::memset(dirEntry->path, 0, sizeof(dirEntry->path)); + std::strncpy(dirEntry->path, path.data(), std::min(sizeof(dirEntry->path) - 1, path.size())); + return true; + } + +private: + FSTVolume* m_volume{nullptr}; + sint32 m_fscType; + FSTFileHandle m_fstFileHandle; + // file + uint32 m_seek{0}; + // directory + FSTDirectoryIterator m_dirIterator{}; +}; + +class fscDeviceWUDC : public fscDeviceC +{ + FSCVirtualFile* fscDeviceOpenByPath(std::wstring_view path, FSC_ACCESS_FLAG accessFlags, void* ctx, sint32* fscStatus) override + { + FSTVolume* mountedVolume = (FSTVolume*)ctx; + cemu_assert_debug(!HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::WRITE_PERMISSION)); // writing to FST is never allowed + + std::string pathU8 = boost::nowide::narrow(path.data(), path.size()); + if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_FILE)) + { + FSTFileHandle fstFileHandle; + if (mountedVolume->OpenFile(pathU8, fstFileHandle, true)) + { + *fscStatus = FSC_STATUS_OK; + return new FSCDeviceWudFileCtx(mountedVolume, fstFileHandle); + } + } + if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_DIR)) + { + FSTDirectoryIterator dirIterator; + if (mountedVolume->OpenDirectoryIterator(pathU8, dirIterator)) + { + *fscStatus = FSC_STATUS_OK; + return new FSCDeviceWudFileCtx(mountedVolume, dirIterator); + } + } + *fscStatus = FSC_STATUS_FILE_NOT_FOUND; + return nullptr; + } + + // singleton +public: + static fscDeviceWUDC& instance() + { + static fscDeviceWUDC _instance; + return _instance; + } +}; + +bool FSCDeviceWUD_Mount(const char* mountPath, std::string_view destinationBaseDir, FSTVolume* mountedVolume, sint32 priority) +{ + std::wstring hostTargetPath(boost::nowide::widen(destinationBaseDir.data(), destinationBaseDir.size())); + if (!hostTargetPath.empty() && (hostTargetPath.back() != '/' && hostTargetPath.back() != '\\')) + hostTargetPath.push_back('/'); + return fsc_mount(mountPath, hostTargetPath.c_str(), &fscDeviceWUDC::instance(), mountedVolume, priority) == FSC_STATUS_OK; +} \ No newline at end of file diff --git a/src/Cafe/GamePatch.cpp b/src/Cafe/GamePatch.cpp new file mode 100644 index 00000000..e5a67d98 --- /dev/null +++ b/src/Cafe/GamePatch.cpp @@ -0,0 +1,474 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/RPL/rpl.h" +#include "Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h" +#include "CafeSystem.h" + +void hleExport_breathOfTheWild_busyLoop(PPCInterpreter_t* hCPU) +{ + uint32 queue7C = memory_readU32(hCPU->gpr[24] + 0x7C); + uint32 queue80b = memory_readU8(hCPU->gpr[24] + 0x80); + + if (!(queue80b == 0 || hCPU->gpr[22] != 0 || queue7C > 0)) + { + PPCInterpreter_relinquishTimeslice(); + } + + hCPU->gpr[6] = hCPU->gpr[29]; + hCPU->instructionPointer += 4; +} + +void hleExport_breathOfTheWild_busyLoop2(PPCInterpreter_t* hCPU) +{ + uint32 queue7C = memory_readU32(hCPU->gpr[24] + 0x7C); + uint32 queue80b = memory_readU8(hCPU->gpr[24] + 0x80); + + if (!(queue80b == 0 || hCPU->gpr[22] != 0 || queue7C > 0)) + { + PPCInterpreter_relinquishTimeslice(); + } + + hCPU->gpr[12] = hCPU->gpr[29]; + hCPU->instructionPointer += 4; +} + +void hleExport_ffl_swapEndianFloatArray(PPCInterpreter_t* hCPU) +{ + ppcDefineParamStructPtr(valueArray, uint32, 0); + ppcDefineParamS32(valueCount, 1); + for (sint32 i = 0; i < valueCount; i++) + { + valueArray[i] = _swapEndianU32(valueArray[i]); + } + osLib_returnFromFunction(hCPU, 0); +} + +typedef struct +{ + std::atomic<uint32be> count; + uint32be ownerThreadId; + uint32 ukn08; +}xcxCS_t; + +void hleExport_xcx_enterCriticalSection(PPCInterpreter_t* hCPU) +{ + ppcDefineParamStructPtr(xcxCS, xcxCS_t, 0); + uint32 threadId = coreinitThread_getCurrentThreadMPTRDepr(hCPU); + cemu_assert_debug(xcxCS->ukn08 != 0); + cemu_assert_debug(threadId); + if (xcxCS->ownerThreadId == (uint32be)threadId) + { + xcxCS->count.store(xcxCS->count.load() + 1); + osLib_returnFromFunction(hCPU, 0); + return; + } + + // quick check + uint32be newCount = xcxCS->count.load() + 1; + uint32be expectedCount = 0; + if(xcxCS->count.compare_exchange_strong(expectedCount, newCount)) + { + xcxCS->ownerThreadId = threadId; + osLib_returnFromFunction(hCPU, 0); + return; + } + + // spinloop for a bit to reduce the time we occupy the scheduler lock (via PPCCore_switchToScheduler) + while (true) + { + for (sint32 i = 0; i < 50; i++) + { + if (xcxCS->count.compare_exchange_strong(expectedCount, newCount)) + { + xcxCS->ownerThreadId = threadId; + osLib_returnFromFunction(hCPU, 0); + return; + } + _mm_pause(); + } + PPCCore_switchToScheduler(); + } + osLib_returnFromFunction(hCPU, 0); +} + +bool mh3u_raceConditionWorkaround = true; + +void hleExport_mh3u_raceConditionWorkaround(PPCInterpreter_t* hCPU) // new style HLE method, does not need entry in hle_load (but can only be reached via BL and not the usual HLE instruction) +{ + uint8 b = memory_readU8(hCPU->gpr[3] + 0x3E5); + b ^= 1; + if (mh3u_raceConditionWorkaround) + { + b = 0; + mh3u_raceConditionWorkaround = false; + } + osLib_returnFromFunction(hCPU, b); +} + +void hleExport_pmcs_yellowPaintStarCrashWorkaround(PPCInterpreter_t* hCPU) +{ + hCPU->gpr[7] = hCPU->gpr[3] * 4; + MPTR parentLR = memory_readU32(hCPU->gpr[1] + 0x4C); + if (hCPU->gpr[3] >= 0x00800000) + { + hCPU->instructionPointer = parentLR; + hCPU->gpr[1] += 0x48; + hCPU->gpr[3] = 0; + return; + } + hCPU->instructionPointer = hCPU->spr.LR; +} + +uint8 hleSignature_wwhd_0173B2A0[] = {0x8D,0x43,0x00,0x01,0x7C,0xC9,0x52,0x78,0x55,0x2C,0x15,0xBA,0x7C,0x0C,0x28,0x2E,0x54,0xC8,0xC2,0x3E,0x7D,0x06,0x02,0x78,0x42,0x00,0xFF,0xE8,0x7C,0xC3,0x30,0xF8}; + +void hle_scan(uint8* data, sint32 dataLength, char* hleFunctionName) +{ + sint32 functionIndex = osLib_getFunctionIndex("hle", hleFunctionName); + if( functionIndex < 0 ) + { + debug_printf("HLE function unknown\n"); + return; + } + + uint8* scanStart = memory_getPointerFromVirtualOffset(0x01000000); + uint8* scanEnd = scanStart + 0x0F000000 - dataLength; + uint8* scanCurrent = scanStart; + while( scanCurrent < scanEnd ) + { + if( memcmp(scanCurrent, data, dataLength) == 0 ) + { + uint32 offset = (uint32)(scanCurrent - scanStart) + 0x01000000; + debug_printf("HLE signature for '%s' found at 0x%08x\n", hleFunctionName, offset); + uint32 opcode = (1<<26)|(functionIndex+0x1000); // opcode for HLE: 0x1000 + FunctionIndex + memory_writeU32Direct(offset, opcode); + break; + } + scanCurrent += 4; + } +} + +MPTR hle_locate(uint8* data, sint32 dataLength) +{ + uint8* scanStart = memory_getPointerFromVirtualOffset(0x01000000); + uint8* scanEnd = scanStart + 0x0F000000 - dataLength; + uint8* scanCurrent = scanStart; + while( scanCurrent < scanEnd ) + { + if( memcmp(scanCurrent, data, dataLength) == 0 ) + { + return memory_getVirtualOffsetFromPointer(scanCurrent); + } + scanCurrent += 4; + } + return MPTR_NULL; +} + +bool compareMasked(uint8* mem, uint8* compare, uint8* mask, sint32 length) +{ + while( length ) + { + uint8 m = *mask; + if( (*mem&m) != (*compare&m) ) + return false; + mem++; + compare++; + mask++; + length--; + } + return true; +} + +MPTR hle_locate(uint8* data, uint8* mask, sint32 dataLength) +{ + uint8* scanStart = memory_getPointerFromVirtualOffset(MEMORY_CODEAREA_ADDR); + uint8* scanEnd = memory_getPointerFromVirtualOffset(RPLLoader_GetMaxCodeOffset() - dataLength); + uint8* scanCurrent = scanStart; + if( mask ) + { + if (dataLength >= 4 && *(uint32*)mask == 0xFFFFFFFF) + { + // fast path + uint32 firstDword = *(uint32*)data; + while (scanCurrent < scanEnd) + { + if (*(uint32*)scanCurrent == firstDword && compareMasked(scanCurrent, data, mask, dataLength)) + { + return memory_getVirtualOffsetFromPointer(scanCurrent); + } + scanCurrent += 4; + } + } + else + { +#ifndef PUBLIC_RELEASE + if (mask[0] != 0xFF) + assert_dbg(); +#endif + uint8 firstByte = data[0]; + while (scanCurrent < scanEnd) + { + if (scanCurrent[0] == firstByte && compareMasked(scanCurrent, data, mask, dataLength)) + { + return memory_getVirtualOffsetFromPointer(scanCurrent); + } + scanCurrent += 4; + } + } + } + else + { + while( scanCurrent < scanEnd ) + { + if( memcmp(scanCurrent, data, dataLength) == 0 ) + { + return memory_getVirtualOffsetFromPointer(scanCurrent); + } + scanCurrent += 4; + } + } + return MPTR_NULL; +} + +uint8 xcx_gpuHangDetection_degradeFramebuffer[] = {0x3B,0x39,0x00,0x01,0x28,0x19,0x4E,0x20,0x40,0x81,0x00,0x44}; + +uint8 xcx_framebufferReductionSignature[] = {0x80,0xC9,0x00,0x1C,0x38,0xA0,0x00,0x01,0x80,0x7E,0x00,0x80,0x80,0x9E,0x02,0xEC,0x80,0xE9,0x00,0x20,0x48,0x06,0x1E,0xD1,0x7E,0x73,0x1B,0x78}; +uint8 xcx_framebufferReductionMask[] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF}; + +uint8 botw_busyLoopSignature[] = {0x80,0xE6,0x00,0x00,0x2C,0x07,0x00,0x01,0x41,0x82,0xFF,0xF8,0x7D,0x00,0x30,0x28,0x2C,0x08,0x00,0x00,0x40,0x82,0xFF,0xF8,0x7C,0x00,0x30,0x6C,0x39,0x00,0x00,0x01,0x7D,0x00,0x31,0x2D,0x40,0x82,0xFF,0xE8 }; +uint8 botw_busyLoopMask[] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF }; + +uint8 botw_busyLoopSignature2[] = {0x80,0x0C,0x00,0x00,0x2C,0x00,0x00,0x01,0x41,0x82,0xFF,0xF8,0x7C,0xA0,0x60,0x28,0x2C,0x05,0x00,0x00,0x40,0x82,0xFF,0xF8,0x7C,0x00,0x60,0x6C,0x38,0x80,0x00,0x01,0x7C,0x80,0x61,0x2D,0x40,0x82,0xFF,0xE8}; +uint8 botw_busyLoopMask2[] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF}; + +uint8 botw_crashFuncSignature[] = { 0x94,0x21,0xFF,0xD8,0x7C,0x08,0x02,0xA6,0xBF,0x41,0x00,0x10,0x7C,0xC7,0x33,0x78,0x7C,0xBE,0x2B,0x78,0x90,0x01,0x00,0x2C,0x7C,0x9D,0x23,0x78,0x38,0x00,0x00,0x00,0x7F,0xC6,0xF3,0x78,0x38,0x81,0x00,0x0C,0x90,0x01,0x00,0x0C,0x38,0xA1,0x00,0x08 }; +uint8 botw_crashFuncMask[] = { 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF }; + +uint8 ffl_floatArrayEndianSwap[] = { 0x7C,0x08,0x02,0xA6,0x94,0x21,0xFF,0xE8,0x93,0xC1,0x00,0x10,0x7C,0x7E,0x1B,0x78,0x93,0xE1,0x00,0x14,0x93,0x81,0x00,0x08,0x7C,0x9F,0x23,0x78,0x93,0xA1,0x00,0x0C,0x90,0x01,0x00,0x1C,0x3B,0xA0,0x00,0x00,0x7C,0x1D,0xF8,0x40,0x40,0x80,0x00,0x20,0x57,0xBC,0x10,0x3A,0x7C,0x3E,0xE4,0x2E }; + +uint8 xcx_enterCriticalSectionSignature[] = { 0x94,0x21,0xFF,0xE0,0xBF,0x41,0x00,0x08,0x7C,0x08,0x02,0xA6,0x90,0x01,0x00,0x24,0x7C,0x7E,0x1B,0x78,0x80,0x1E,0x00,0x08,0x2C,0x00,0x00,0x00,0x41,0x82,0x00,0xC0,0x48,0x01,0xD7,0xA1,0x7C,0x7A,0x1B,0x79,0x41,0x82,0x00,0xB4,0x81,0x3E,0x00,0x04,0x7C,0x09,0xD0,0x40,0x40,0x82,0x00,0x2C,0x7D,0x20,0xF0,0x28,0x7C,0x00,0xF0,0x6C }; +uint8 xcx_enterCriticalSectionMask[] = { 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF }; + +uint8 smash4_softlockFixV0Signature[] = { 0x2C,0x03,0x00,0x00,0x41,0x82,0x00,0x20,0x38,0x60,0x00,0x0A,0x48,0x33,0xB8,0xAD,0x7F,0xA3,0xEB,0x78,0x7F,0xC4,0xF3,0x78,0x4B,0xFF,0xFF,0x09,0x2C,0x03,0x00,0x00 }; +uint8 smash4_softlockFixV0Mask[] = { 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF }; + +uint8 mh3u_raceConditionWorkaroundV0Signature[] = { 0x38,0x21,0x00,0x40,0x38,0x60,0x00,0x00,0x4E,0x80,0x00,0x20,0x80,0x7B,0xDB,0x9C,0x48,0x11,0x6B,0x81,0x2C,0x03,0x00,0x00 }; +uint8 mh3u_raceConditionWorkaroundV0Mask[] = { 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF }; + +uint8 pmcs_yellowPaintStarCrashV0Signature[] = { 0x94,0x21,0xFF,0xB8,0xBE,0x61,0x00,0x14,0x7C,0x08,0x02,0xA6,0x7C,0xB3,0x2B,0x78,0x90,0x01,0x00,0x4C,0x7C,0x9D,0x23,0x78,0x83,0x3D,0x00,0x0C,0x81,0x39,0x04,0xA8,0x54,0x67,0x10,0x3A,0x7F,0xC7,0x48,0x2E,0x83,0x1E,0x00,0xDC,0x82,0xF8,0x00,0x08,0x2C,0x17,0x00,0x00 }; +//uint8 mh3u_raceConditionWorkaroundV0Mask[] = { 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF }; + +uint8 bayo2_audioQueueFixSignature[] = { 0x80,0x03,0x00,0x3C,0x81,0x43,0x00,0x5C,0x81,0x83,0x00,0x40,0x55,0x48,0xB2,0xBE,0x3D,0x40,0x10,0x1D,0x7D,0x6C,0x42,0x14,0x39,0x4A,0x46,0xF0,0x7D,0x8B,0x00,0x50 }; +uint8 bayo2_audioQueueFixMask[] = { 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0xFF,0xFF,0xFF,0xFF }; + +uint8 sm3dw_dynFrameBufferResScale[] = { 0x94,0x21,0xFF,0xB8,0xBF,0x21,0x00,0x2C,0x7C,0x08,0x02,0xA6,0x90,0x01,0x00,0x4C,0x7C,0x7E,0x1B,0x78,0x81,0x7E,0x07,0xD8,0x38,0x80,0x00,0x02,0x38,0x6B,0x00,0x03 }; + +uint8 tww_waitFunc[] = { 0x7C,0x08,0x02,0xA6,0x94,0x21,0xFF,0xF0,0x93,0xE1,0x00,0x0C,0x7C,0x7F,0x1B,0x78,0x90,0x01,0x00,0x14,0x80,0x7F,0x02,0xE0,0x81,0x83,0x00,0x0C,0x80,0x0C,0x00,0x1C,0x7C,0x09,0x03,0xA6,0x38,0xA0,0x00,0x00,0x38,0x9F,0x03,0x68 }; + +static_assert(sizeof(xcx_enterCriticalSectionSignature) == sizeof(xcx_enterCriticalSectionMask), "xcx_enterCriticalSection signature and size mismatch"); +static_assert(sizeof(bayo2_audioQueueFixSignature) == sizeof(bayo2_audioQueueFixMask), "bayo2_audioQueueFix signature and size mismatch"); + +uint8 cars3_avro_schema_incref[] = { 0x2C,0x03,0x00,0x00,0x94,0x21,0xFF,0xE8,0x41,0x82,0x00,0x40,0x39,0x03,0x00,0x08,0x39,0x41,0x00,0x08,0x91,0x01,0x00,0x08,0x7D,0x80,0x50,0x28,0x2C,0x0C,0xFF,0xFF,0x41,0x82,0x00,0x28,0x39,0x21,0x00,0x0C,0x38,0x0C,0x00,0x01,0x38,0xE0,0x00,0x01,0x91,0x01,0x00,0x0C,0x7C,0x00,0x49,0x2D }; + + +sint32 hleIndex_h000000001 = -1; +sint32 hleIndex_h000000002 = -1; +sint32 hleIndex_h000000003 = -1; +sint32 hleIndex_h000000004 = -1; + +/* + * Returns true for all HLE functions that do not jump to LR + * Used by recompiler to determine function code flow + */ +bool GamePatch_IsNonReturnFunction(uint32 hleIndex) +{ + if (hleIndex == hleIndex_h000000001) + return true; + if (hleIndex == hleIndex_h000000002) + return true; + if (hleIndex == hleIndex_h000000003) + return false; + if (hleIndex == hleIndex_h000000004) + return false; + return false; +} + +void GamePatch_scan() +{ + MPTR hleAddr; + uint32 hleInstallStart = GetTickCount(); + + hleAddr = hle_locate(xcx_gpuHangDetection_degradeFramebuffer, NULL, sizeof(xcx_gpuHangDetection_degradeFramebuffer)); + if( hleAddr ) + { +#ifndef PUBLIC_RELEASE + forceLog_printf("HLE: XCX GPU hang detection"); +#endif + // remove the ADDI r25, r25, 1 instruction + memory_writeU32(hleAddr, memory_readU32(hleAddr+4)); + } + + hleAddr = hle_locate(xcx_framebufferReductionSignature, xcx_framebufferReductionMask, sizeof(xcx_framebufferReductionSignature)); + if( hleAddr ) + { +#ifndef PUBLIC_RELEASE + forceLog_printf("HLE: Prevent XCX rendertarget reduction"); +#endif + uint32 bl = memory_readU32(hleAddr+0x14); + uint32 func_isReductionBuffer = hleAddr + 0x14 + (bl&0x3FFFFFC); + + // patch isReductionBuffer + memory_writeU32(func_isReductionBuffer, 0x38600000); // LI R3, 0 + memory_writeU32(func_isReductionBuffer+4, 0x4E800020); // BLR + + } + + hleIndex_h000000001 = osLib_getFunctionIndex("hle", "h000000001"); + hleAddr = hle_locate(botw_busyLoopSignature, botw_busyLoopMask, sizeof(botw_busyLoopSignature)); + if (hleAddr) + { +#ifndef PUBLIC_RELEASE + forceLog_printf("HLE: Patch BotW busy loop 1 at 0x%08x", hleAddr); +#endif + sint32 functionIndex = hleIndex_h000000001; + uint32 opcode = (1 << 26) | (functionIndex); // opcode for HLE: 0x1000 + FunctionIndex + memory_writeU32Direct(hleAddr - 4, opcode); + } + hleIndex_h000000002 = osLib_getFunctionIndex("hle", "h000000002"); + hleAddr = hle_locate(botw_busyLoopSignature2, botw_busyLoopMask2, sizeof(botw_busyLoopSignature2)); + if (hleAddr) + { +#ifndef PUBLIC_RELEASE + forceLog_printf("HLE: Patch BotW busy loop 2 at 0x%08x", hleAddr); +#endif + sint32 functionIndex = hleIndex_h000000002; + uint32 opcode = (1 << 26) | (functionIndex); // opcode for HLE: 0x1000 + FunctionIndex + memory_writeU32Direct(hleAddr - 4, opcode); + } + + // FFL library float array endian conversion + // original function needs invalid float values to remain intact between LFSX -> STFSX, which is not supported in recompiler mode + hleIndex_h000000003 = osLib_getFunctionIndex("hle", "h000000003"); + hleAddr = hle_locate(ffl_floatArrayEndianSwap, NULL, sizeof(ffl_floatArrayEndianSwap)); + if (hleAddr) + { + forceLogDebug_printf("HLE: Hook FFL float array endian swap function at 0x%08x", hleAddr); + sint32 functionIndex = hleIndex_h000000003; + uint32 opcode = (1 << 26) | (functionIndex); // opcode for HLE: 0x1000 + FunctionIndex + memory_writeU32Direct(hleAddr, opcode); + } + + // XCX freeze workaround + //hleAddr = hle_locate(xcx_enterCriticalSectionSignature, xcx_enterCriticalSectionMask, sizeof(xcx_enterCriticalSectionSignature)); + //if (hleAddr) + //{ + // forceLogDebug_printf("HLE: Hook XCX enterCriticalSection function at 0x%08x", hleAddr); + // hleIndex_h000000004 = osLib_getFunctionIndex("hle", "h000000004"); + // sint32 functionIndex = hleIndex_h000000004; + // uint32 opcode = (1 << 26) | (functionIndex); // opcode for HLE: 0x1000 + FunctionIndex + // memory_writeU32Direct(hleAddr, opcode); + //} + + // MH3U race condition (tested for EU+US 1.2) + hleAddr = hle_locate(mh3u_raceConditionWorkaroundV0Signature, mh3u_raceConditionWorkaroundV0Mask, sizeof(mh3u_raceConditionWorkaroundV0Mask)); + if (hleAddr) + { + uint32 patchAddr = hleAddr + 0x10; + forceLog_printf("HLE: Patch MH3U race condition candidate at 0x%08x", patchAddr); + uint32 funcAddr = PPCInterpreter_makeCallableExportDepr(hleExport_mh3u_raceConditionWorkaround); + // set absolute jump + uint32 opc = 0x48000000; + opc |= PPC_OPC_LK; + opc |= PPC_OPC_AA; + opc |= funcAddr; + memory_writeU32(patchAddr, opc); + } + + // Super Smash Bros softlock fix + // fixes random softlocks that can occur after matches + hleAddr = hle_locate(smash4_softlockFixV0Signature, smash4_softlockFixV0Mask, sizeof(smash4_softlockFixV0Signature)); + if (hleAddr) + { + forceLogDebug_printf("Smash softlock fix: 0x%08x", hleAddr); + memory_writeU32(hleAddr+0x20, memory_readU32(hleAddr+0x1C)); + } + + // Color Splash Yellow paint star crash workaround + // fixes the crash at the beginning of the dream sequence cutscene after collecting the yellow paint star + hleAddr = hle_locate(pmcs_yellowPaintStarCrashV0Signature, nullptr, sizeof(pmcs_yellowPaintStarCrashV0Signature)); + if (hleAddr) + { + forceLogDebug_printf("Color Splash crash fix: 0x%08x", hleAddr); + uint32 funcAddr = PPCInterpreter_makeCallableExportDepr(hleExport_pmcs_yellowPaintStarCrashWorkaround); + // set absolute jump + uint32 opc = 0x48000000; + opc |= PPC_OPC_LK; + opc |= PPC_OPC_AA; + opc |= funcAddr; + memory_writeU32(hleAddr+0x20, opc); + } + + // Bayonetta 2 sound queue patch (fixes audio starting to loop infinitely when there is stutter) + hleAddr = hle_locate(bayo2_audioQueueFixSignature, bayo2_audioQueueFixMask, sizeof(bayo2_audioQueueFixSignature)); + if (hleAddr) + { + // replace CMPL with CMP + forceLog_printf("Patching Bayonetta 2 audio bug at: 0x%08x", hleAddr+0x34); + uint32 opc = memory_readU32(hleAddr + 0x34); + opc &= ~(0x3FF << 1); // turn CMPL to CMP + memory_writeU32(hleAddr + 0x34, opc); + } + + if (CafeSystem::GetRPXHashUpdated() == 0xb1c033dd) // Wind Waker US + { + uint32 p = memory_readU32(0x02813878); + if (p == 0x40800018) + { + debug_printf("HLE: TWW US dsp kill channel patch\n"); + uint32 li = 0x18; + uint32 opcode = (li & 0x3FFFFFC) | (18 << 26); // replace BGE with B instruction + memory_writeU32(0x02813878, opcode); + } + } + else if (CafeSystem::GetRPXHashUpdated() == 0xCDC68ACD) // Wind Waker EU + { + uint32 p = memory_readU32(0x2814138); + if (p == 0x40800018) + { + debug_printf("HLE: TWW EU dsp kill channel patch\n"); + uint32 li = 0x18; + uint32 opcode = (li & 0x3FFFFFC) | (18 << 26); // replace BGE with B instruction + memory_writeU32(0x02814138, opcode); + } + } + + // disable SM3DW dynamic resolution scaling (fixes level 1-5 spamming lots of texture creates when gradually resizing framebuffer) + hleAddr = hle_locate(sm3dw_dynFrameBufferResScale, nullptr, sizeof(sm3dw_dynFrameBufferResScale)); + if (hleAddr) + { + forceLog_printf("Patching SM3DW dynamic resolution scaling at: 0x%08x", hleAddr); + memory_writeU32(hleAddr, 0x4E800020); // BLR + } + + // remove unnecessary lock from a wait function in TWW + // this resolves a deadlock in singlecore mode + hleAddr = hle_locate(tww_waitFunc, nullptr, sizeof(tww_waitFunc)); + if (hleAddr) + { + forceLog_printf("Patching TWW race conditon at: 0x%08x", hleAddr); + // NOP calls to Lock/Unlock mutex + memory_writeU32(hleAddr + 0x34, 0x60000000); + memory_writeU32(hleAddr + 0x48, 0x60000000); + memory_writeU32(hleAddr + 0x50, 0x60000000); + memory_writeU32(hleAddr + 0x64, 0x60000000); + } + + uint32 hleInstallEnd = GetTickCount(); + forceLog_printf("HLE scan time: %dms", hleInstallEnd-hleInstallStart); +} + +RunAtCemuBoot _loadGamePatchAPI([]() + { + osLib_addFunction("hle", "h000000001", hleExport_breathOfTheWild_busyLoop); + osLib_addFunction("hle", "h000000002", hleExport_breathOfTheWild_busyLoop2); + osLib_addFunction("hle", "h000000003", hleExport_ffl_swapEndianFloatArray); + osLib_addFunction("hle", "h000000004", hleExport_xcx_enterCriticalSection); + }); diff --git a/src/Cafe/GamePatch.h b/src/Cafe/GamePatch.h new file mode 100644 index 00000000..ab35642d --- /dev/null +++ b/src/Cafe/GamePatch.h @@ -0,0 +1,2 @@ +void GamePatch_scan(); +bool GamePatch_IsNonReturnFunction(uint32 hleIndex); \ No newline at end of file diff --git a/src/Cafe/GameProfile/GameProfile.cpp b/src/Cafe/GameProfile/GameProfile.cpp new file mode 100644 index 00000000..8e1186c9 --- /dev/null +++ b/src/Cafe/GameProfile/GameProfile.cpp @@ -0,0 +1,402 @@ +#include "Cafe/GameProfile/GameProfile.h" +#include "util/helpers/helpers.h" + +#include "boost/nowide/convert.hpp" + +#include "config/ActiveSettings.h" +#include "Common/filestream.h" +#include "util/IniParser/IniParser.h" +#include "util/helpers/StringHelpers.h" +#include "Cafe/CafeSystem.h" + +std::unique_ptr<GameProfile> g_current_game_profile = std::make_unique<GameProfile>(); + +struct gameProfileBooleanOption_t +{ + bool isPresent = false; + bool value; +}; + +/* +* Attempts to load a boolean option +* If the option exists, true is returned. +* The boolean is stored in *optionValue +*/ +__declspec(dllexport) bool gameProfile_loadBooleanOption(IniParser* iniParser, char* optionName, gameProfileBooleanOption_t* option) +{ + auto option_value = iniParser->FindOption(optionName); + option->isPresent = false; + option->value = false; + if (!option_value) + return false; + // parse option + if (boost::iequals(*option_value, "false") || boost::iequals(*option_value, "0")) + { + option->isPresent = true; + option->value = false; + return true; + } + else if (boost::iequals(*option_value, "true") || boost::iequals(*option_value, "1")) + { + option->isPresent = true; + option->value = true; + return true; + } + else + cemuLog_force("Unknown value '{}' for option '{}' in game profile", *option_value, optionName); + return false; +} + + +bool gameProfile_loadBooleanOption2(IniParser& iniParser, const char* optionName, bool& option) +{ + auto option_value = iniParser.FindOption(optionName); + if (!option_value) + return false; + if (boost::iequals(*option_value, "1") || boost::iequals(*option_value, "true")) + { + option = true; + return true; + } + else if (boost::iequals(*option_value, "0") || boost::iequals(*option_value, "false")) + { + option = false; + return true; + } + else + cemuLog_force("Unknown value '{}' for option '{}' in game profile", *option_value, optionName); + return false; +} + +bool gameProfile_loadBooleanOption2(IniParser& iniParser, const char* optionName, std::optional<bool>& option) +{ + bool tmp; + const auto result = gameProfile_loadBooleanOption2(iniParser, optionName, tmp); + if(result) + option = tmp; + return result; +} + +/* +* Attempts to load a integer option +* Allows to specify min and max value (error is logged if out of range and default value is picked) +*/ +__declspec(dllexport) bool gameProfile_loadIntegerOption(IniParser* iniParser, const char* optionName, gameProfileIntegerOption_t* option, sint32 defaultValue, sint32 minVal, sint32 maxVal) +{ + auto option_value = iniParser->FindOption(optionName); + option->isPresent = false; + if (!option_value) + { + option->value = defaultValue; + return false; + } + // parse option + sint32 val = StringHelpers::ToInt(*option_value, defaultValue); + if (val < minVal || val > maxVal) + { + cemuLog_force("Value '{}' is out of range for option '{}' in game profile", *option_value, optionName); + option->value = defaultValue; + return false; + } + option->isPresent = true; + option->value = val; + return true; +} + +template <typename T> +bool gameProfile_loadIntegerOption(IniParser& iniParser, const char* optionName, T& option, T minVal, T maxVal) +{ + static_assert(std::is_integral<T>::value); + auto option_value = iniParser.FindOption(optionName); + if (!option_value) + return false; + // parse option + try + { + T val = ConvertString<T>(*option_value); + if (val < minVal || val > maxVal) + { + cemuLog_force("Value '{}' is out of range for option '{}' in game profile", *option_value, optionName); + return false; + } + + option = val; + return true; + } + catch(std::exception&) + { + cemuLog_force("Value '{}' is out of range for option '{}' in game profile", *option_value, optionName); + return false; + } +} + +template<typename T> +bool gameProfile_loadEnumOption(IniParser& iniParser, const char* optionName, T& option) +{ + static_assert(std::is_enum<T>::value); + auto option_value = iniParser.FindOption(optionName); + if (!option_value) + return false; + for(const T& v : T()) + { + // test integer option + if (boost::iequals(fmt::format("{}", static_cast<typename std::underlying_type<T>::type>(v)), *option_value)) + { + option = v; + return true; + } + + // test enum name + if(boost::iequals(fmt::format("{}", v), *option_value)) + { + option = v; + return true; + } + } + return false; +} + +template<typename T> +bool gameProfile_loadEnumOption(IniParser& iniParser, const char* optionName, std::optional<T>& option) +{ + T tmp; + const auto result = gameProfile_loadEnumOption(iniParser, optionName, tmp); + if(result) + option = tmp; + return result; +} + +#pragma optimize( "", off ) +__declspec(dllexport) __declspec(noinline) void gameProfile_categoryBegin(IniParser* iniParser) +{ + // do nothing +} +#pragma optimize( "", on ) + +void gameProfile_load() +{ + g_current_game_profile->ResetOptional(); // reset with global values as optional + g_current_game_profile->Load(CafeSystem::GetForegroundTitleId(), true); + + // apply some settings immediately + ppcThreadQuantum = g_current_game_profile->GetThreadQuantum(); + + if (ppcThreadQuantum != GameProfile::kThreadQuantumDefault) + cemuLog_force("Thread quantum set to {}", ppcThreadQuantum); +} + +bool GameProfile::Load(uint64_t title_id, bool notifyCemuhook) +{ + auto gameProfilePath = ActiveSettings::GetPath("gameProfiles/{:016x}.ini", title_id); + + std::optional<std::vector<uint8>> profileContents = FileStream::LoadIntoMemory(gameProfilePath); + if (!profileContents) + { + gameProfilePath = ActiveSettings::GetPath("gameProfiles/default/{:016x}.ini", title_id); + profileContents = FileStream::LoadIntoMemory(gameProfilePath); + if (!profileContents) + return false; + m_is_default = true; + } + else + m_is_default = false; + + m_is_loaded = true; + // most official gameprofiles start with "# gamename" + std::vector<char> game_name; + if (profileContents->size() > 0 && profileContents->data()[0] == '#') + { + char c; + size_t idx = 1; + while (idx < profileContents->size() && (c = profileContents->data()[idx]) != '\n' && idx < 128) + { + game_name.emplace_back(c); + idx++; + } + m_gameName = std::string(game_name.begin(), game_name.end()); + trim(m_gameName.value()); + } + IniParser iniParser(*profileContents, gameProfilePath.string()); + // parse ini + while (iniParser.NextSection()) + { + //if (notifyCemuhook) + // gameProfile_categoryBegin(gameProfile); // hookable export for Cemuhook + + if (boost::iequals(iniParser.GetCurrentSectionName(), "General")) + { + gameProfile_loadBooleanOption2(iniParser, "loadSharedLibraries", m_loadSharedLibraries); + gameProfile_loadBooleanOption2(iniParser, "startWithPadView", m_startWithPadView); + } + else if (boost::iequals(iniParser.GetCurrentSectionName(), "Graphics")) + { + gameProfileIntegerOption_t graphicsApi; + gameProfile_loadIntegerOption(&iniParser, "graphics_api", &graphicsApi, -1, 0, 1); + if (graphicsApi.value != -1) + m_graphics_api = (GraphicAPI)graphicsApi.value; + + gameProfile_loadEnumOption(iniParser, "accurateShaderMul", m_accurateShaderMul); + + // legacy support + auto option_precompiledShaders = iniParser.FindOption("precompiledShaders"); + if (option_precompiledShaders) + { + if (boost::iequals(*option_precompiledShaders, "1") || boost::iequals(*option_precompiledShaders, "true")) + m_precompiledShaders = PrecompiledShaderOption::Enable; + else if (boost::iequals(*option_precompiledShaders, "0") || boost::iequals(*option_precompiledShaders, "false")) + m_precompiledShaders = PrecompiledShaderOption::Disable; + else + m_precompiledShaders = PrecompiledShaderOption::Auto; + } + else + m_precompiledShaders = PrecompiledShaderOption::Auto; + } + else if (boost::iequals(iniParser.GetCurrentSectionName(), "Audio")) + { + gameProfile_loadBooleanOption2(iniParser, "disableAudio", m_disableAudio); + } + else if (boost::iequals(iniParser.GetCurrentSectionName(), "CPU")) + { + gameProfile_loadIntegerOption(iniParser, "threadQuantum", m_threadQuantum, 1000U, 536870912U); + if (!gameProfile_loadEnumOption(iniParser, "cpuMode", m_cpuMode)) + { + // try to load the old enum value strings + std::optional<CPUModeLegacy> cpu_mode_legacy; + if (gameProfile_loadEnumOption(iniParser, "cpuMode", cpu_mode_legacy) && cpu_mode_legacy.has_value()) + { + m_cpuMode = (CPUMode)cpu_mode_legacy.value(); + if (m_cpuMode == CPUMode::DualcoreRecompiler) + m_cpuMode = CPUMode::MulticoreRecompiler; + } + } + } + else if (boost::iequals(iniParser.GetCurrentSectionName(), "Controller")) + { + for (int i = 0; i < 8; ++i) + { + auto option_value = iniParser.FindOption(fmt::format("controller{}", (i + 1))); + if (option_value) + m_controllerProfile[i] = std::string(*option_value); + } + + } + } + return true; +} + +void GameProfile::Save(uint64_t title_id) +{ + auto gameProfilePath = ActiveSettings::GetPath("gameProfiles/{:016x}.ini", title_id); + FileStream* fs = FileStream::createFile2(gameProfilePath); + if (!fs) + { + cemuLog_force("Failed to write game profile"); + return; + } + + if (m_gameName) + fs->writeLine(fmt::format("# {}\n", m_gameName.value()).c_str()); + +#define WRITE_OPTIONAL_ENTRY(__NAME) if (m_##__NAME) fs->writeLine(fmt::format("{} = {}", #__NAME, m_##__NAME.value()).c_str()); +#define WRITE_ENTRY(__NAME) fs->writeLine(fmt::format("{} = {}", #__NAME, m_##__NAME).c_str()); + + fs->writeLine("[General]"); + WRITE_OPTIONAL_ENTRY(loadSharedLibraries); + WRITE_ENTRY(startWithPadView); + + fs->writeLine(""); + + + fs->writeLine("[CPU]"); + WRITE_OPTIONAL_ENTRY(cpuMode); + WRITE_ENTRY(threadQuantum); + + fs->writeLine(""); + + fs->writeLine("[Graphics]"); + //WRITE_OPTIONAL_ENTRY(gpuBufferCacheAccuracy); + WRITE_ENTRY(accurateShaderMul); + WRITE_OPTIONAL_ENTRY(precompiledShaders); + WRITE_OPTIONAL_ENTRY(graphics_api); + fs->writeLine(""); + + /*stream_writeLine(stream_gameProfile, "[Audio]"); + WRITE_ENTRY(disableAudio); + stream_writeLine(stream_gameProfile, "");*/ + + fs->writeLine("[Controller]"); + for (int i = 0; i < 8; ++i) + { + if (m_controllerProfile[i]) + fs->writeLine(fmt::format("controller{} = {}", (i + 1), m_controllerProfile[i].value()).c_str()); + } + + fs->writeLine(""); + +#undef WRITE_OPTIONAL_ENTRY +#undef WRITE_ENTRY + + delete fs; +} + +void GameProfile::ResetOptional() +{ + m_gameName.reset(); + + // general settings + m_loadSharedLibraries.reset(); // true; + m_startWithPadView = false; + + // graphic settings + m_accurateShaderMul = AccurateShaderMulOption::True; + // cpu settings + m_threadQuantum = kThreadQuantumDefault; + m_cpuMode.reset(); // CPUModeOption::kSingleCoreRecompiler; + // audio + m_disableAudio = false; + // controller settings + for (auto& profile : m_controllerProfile) + profile.reset(); +} + +void GameProfile::Reset() +{ + m_gameName.reset(); + + // general settings + m_loadSharedLibraries = true; + m_startWithPadView = false; + + // graphic settings + m_accurateShaderMul = AccurateShaderMulOption::True; + m_precompiledShaders = PrecompiledShaderOption::Auto; + // cpu settings + m_threadQuantum = kThreadQuantumDefault; + m_cpuMode = CPUMode::Auto; + // audio + m_disableAudio = false; + // controller settings + for (auto& profile : m_controllerProfile) + profile.reset(); +} + +// legacy code for Cemuhook +__declspec(dllexport) char* gameProfile_loadStringOption(IniParser* iniParser, char* optionName) +{ + return nullptr; +} +__declspec(dllexport) char* gameProfile_getCurrentCategoryName(IniParser* iniParser) +{ + return nullptr; +} + +struct gpNamedOptionEntry_t +{ + char* name; + sint32 value; +}; + +__declspec(dllexport) bool gameProfile_loadIntegerNamedOption(IniParser* iniParser, char* optionName, gameProfileIntegerOption_t* option, sint32 defaultValue, const gpNamedOptionEntry_t* nameValues, sint32 numNameValues) +{ + return false; +} \ No newline at end of file diff --git a/src/Cafe/GameProfile/GameProfile.h b/src/Cafe/GameProfile/GameProfile.h new file mode 100644 index 00000000..00b4e0d1 --- /dev/null +++ b/src/Cafe/GameProfile/GameProfile.h @@ -0,0 +1,68 @@ +#pragma once + +#include <optional> +#include "config/CemuConfig.h" + +struct gameProfileIntegerOption_t +{ + bool isPresent = false; + sint32 value; +}; + +class GameProfile +{ + friend class GameProfileWindow; + +public: + static const uint32 kThreadQuantumDefault = 45000; + + bool Load(uint64_t title_id, bool notifyCemuhook); + void Save(uint64_t title_id); + void ResetOptional(); + void Reset(); + + [[nodiscard]] uint64 GetTitleId() const { return m_title_id; } + [[nodiscard]] bool IsLoaded() const { return m_is_loaded; } + [[nodiscard]] bool IsDefaultProfile() const { return m_is_default; } + [[nodiscard]] const std::optional<std::string>& GetGameName() const { return m_gameName; } + + [[nodiscard]] const std::optional<bool>& ShouldLoadSharedLibraries() const { return m_loadSharedLibraries; } + [[nodiscard]] bool StartWithGamepadView() const { return m_startWithPadView; } + + [[nodiscard]] const std::optional<GraphicAPI>& GetGraphicsAPI() const { return m_graphics_api; } + [[nodiscard]] const AccurateShaderMulOption& GetAccurateShaderMul() const { return m_accurateShaderMul; } + [[nodiscard]] const std::optional<PrecompiledShaderOption>& GetPrecompiledShadersState() const { return m_precompiledShaders; } + + [[nodiscard]] uint32 GetThreadQuantum() const { return m_threadQuantum; } + [[nodiscard]] const std::optional<CPUMode>& GetCPUMode() const { return m_cpuMode; } + + [[nodiscard]] bool IsAudioDisabled() const { return m_disableAudio; } + + [[nodiscard]] const std::array< std::optional<std::string>, 8>& GetControllerProfile() const { return m_controllerProfile; } + +private: + uint64_t m_title_id = 0; + bool m_is_loaded = false; + bool m_is_default = true; + + std::optional<std::string> m_gameName{}; + + // general settings + std::optional<bool> m_loadSharedLibraries{}; // = true; + bool m_startWithPadView = false; + + // graphic settings + std::optional<GraphicAPI> m_graphics_api{}; + AccurateShaderMulOption m_accurateShaderMul = AccurateShaderMulOption::True; + std::optional<PrecompiledShaderOption> m_precompiledShaders{}; + // cpu settings + uint32 m_threadQuantum = kThreadQuantumDefault; // values: 20000 45000 60000 80000 100000 + std::optional<CPUMode> m_cpuMode{}; // = CPUModeOption::kSingleCoreRecompiler; + // audio + bool m_disableAudio = false; + // controller settings + std::array< std::optional<std::string>, 8> m_controllerProfile{}; +}; +extern std::unique_ptr<GameProfile> g_current_game_profile; + +__declspec(dllexport) void gameProfile_load(); diff --git a/src/Cafe/GraphicPack/GraphicPack.cpp b/src/Cafe/GraphicPack/GraphicPack.cpp new file mode 100644 index 00000000..b55ac82a --- /dev/null +++ b/src/Cafe/GraphicPack/GraphicPack.cpp @@ -0,0 +1,152 @@ +#include "gui/wxgui.h" +#include "GraphicPack.h" + +#include "config/ActiveSettings.h" +#include "Cafe/GraphicPack/GraphicPack2.h" + +typedef struct +{ + int placeholder; +}graphicPack_t; + +// scans the graphic pack directory for shaders +__declspec(dllexport) void graphicPack_loadGraphicPackShaders(graphicPack_t* gp, wchar_t* graphicPackPath) +{ + // this function is part of the deprecated/removed v1 graphic pack code + // as of Cemuhook 0.5.7.3 this function must exist with a minimum length for detour + // otherwise Cemuhook graphic pack stuff will error out, so we just create some pointless instructions which wont be optimized away + forceLog_printf("STUB1"); + forceLog_printf("STUB2"); + forceLog_printf("STUB3"); +} + +// for cemuhook compatibility only +__declspec(dllexport) bool config_isGraphicPackEnabled(uint64 id) +{ + forceLog_printf("STUB4"); + forceLog_printf("STUB5"); + forceLog_printf("STUB6"); + return false; +} + +/* + * Loads the graphic pack if the titleId is referenced in rules.ini + */ +void graphicPack_loadGraphicPack(wchar_t* graphicPackPath) +{ + fs::path rulesPath = fs::path(graphicPackPath); + rulesPath.append("rules.txt"); + std::unique_ptr<FileStream> fs_rules(FileStream::openFile2(rulesPath)); + if (!fs_rules) + return; + std::vector<uint8> rulesData; + fs_rules->extract(rulesData); + IniParser iniParser(rulesData, rulesPath.string()); + + if (!iniParser.NextSection()) + { + cemuLog_force(u8"{}: Does not contain any sections", rulesPath.generic_u8string()); + return; + } + if (!boost::iequals(iniParser.GetCurrentSectionName(), "Definition")) + { + cemuLog_force(u8"{}: [Definition] must be the first section", rulesPath.generic_u8string()); + return; + } + + + auto option_version = iniParser.FindOption("version"); + if (option_version) + { + sint32 versionNum = -1; + auto [ptr, ec] = std::from_chars(option_version->data(), option_version->data() + option_version->size(), versionNum); + if (ec != std::errc{}) + { + cemuLog_force(u8"{}: Unable to parse version", rulesPath.generic_u8string()); + return; + } + + if (versionNum > GP_LEGACY_VERSION) + { + GraphicPack2::LoadGraphicPack(rulesPath.generic_wstring(), iniParser); + return; + } + } + cemuLog_force(u8"{}: Outdated graphic pack", rulesPath.generic_u8string()); +} + +void graphicPack_scanForGFXPackFolders(const fs::path& currentPath, std::wstring& relativePath) +{ + // check if this directory has rules txt + fs::path rulesPath = fs::path(currentPath); + rulesPath.append("rules.txt"); + + if (fs::exists(rulesPath) && relativePath.length() != 0) + { + graphicPack_loadGraphicPack((wchar_t*)currentPath.generic_wstring().c_str()); + return; // when a rules.txt file is found stop recursion + } + + if (!fs::exists(currentPath)) + return; + + for (auto& p : fs::directory_iterator(currentPath)) + { + auto& path = p.path(); + if (fs::is_directory(p.status())) + { + // dir + sint32 origSize = relativePath.size(); + relativePath.append(L"/"); + relativePath.append(path.filename().generic_wstring()); + graphicPack_scanForGFXPackFolders(path, relativePath); + relativePath.resize(origSize); + } + } +} + +void graphicPack_loadAll() +{ + // recursively iterate all directories in graphicPacks/ folder + std::wstring graphicPackRelativePath; + graphicPack_scanForGFXPackFolders(ActiveSettings::GetPath("graphicPacks/"), graphicPackRelativePath); +} + +void graphicPack_activateForCurrentTitle(uint64 titleId) +{ + // activate graphic packs + for (const auto& gp : GraphicPack2::GetGraphicPacks()) + { + if (!gp->IsEnabled()) + continue; + + if (!gp->ContainsTitleId(titleId)) + continue; + + if(GraphicPack2::ActivateGraphicPack(gp)) + { + if (gp->GetPresets().empty()) + { + forceLog_printf("Activate graphic pack: %s", gp->GetPath().c_str()); + } + else + { + std::string logLine; + logLine.assign(fmt::format("Activate graphic pack: {} [Presets: ", gp->GetPath())); + bool isFirst = true; + for (auto& itr : gp->GetPresets()) + { + if(!itr->active) + continue; + if (isFirst) + isFirst = false; + else + logLine.append(","); + logLine.append(itr->name); + } + logLine.append("]"); + cemuLog_log(LogType::Force, logLine); + } + } + } +} \ No newline at end of file diff --git a/src/Cafe/GraphicPack/GraphicPack.h b/src/Cafe/GraphicPack/GraphicPack.h new file mode 100644 index 00000000..0a79ef4b --- /dev/null +++ b/src/Cafe/GraphicPack/GraphicPack.h @@ -0,0 +1,4 @@ +#define GP_LEGACY_VERSION (2) + +void graphicPack_loadAll(); +void graphicPack_activateForCurrentTitle(uint64 titleId); \ No newline at end of file diff --git a/src/Cafe/GraphicPack/GraphicPack2.cpp b/src/Cafe/GraphicPack/GraphicPack2.cpp new file mode 100644 index 00000000..62765f2c --- /dev/null +++ b/src/Cafe/GraphicPack/GraphicPack2.cpp @@ -0,0 +1,1193 @@ +#include "Cafe/GraphicPack/GraphicPack2.h" +#include "GraphicPack.h" +#include "config/CemuConfig.h" +#include "openssl/sha.h" +#include "Cafe/HW/Latte/Renderer/RendererOuputShader.h" +#include "Cafe/Filesystem/fsc.h" +#include "boost/algorithm/string.hpp" +#include "util/helpers/MapAdaptor.h" +#include "util/helpers/StringParser.h" +#include "Cafe/HW/Latte/Core/LatteTiming.h" +#include "util/IniParser/IniParser.h" +#include "util/helpers/StringHelpers.h" +#include "Cafe/CafeSystem.h" + +std::vector<GraphicPackPtr> GraphicPack2::s_graphic_packs; +std::vector<GraphicPackPtr> GraphicPack2::s_active_graphic_packs; + +bool GraphicPack2::LoadGraphicPack(const std::wstring& filename, IniParser& rules) +{ + try + { + auto gp = std::make_shared<GraphicPack2>(filename, rules); + + // check if enabled and preset set + const auto& config_entries = g_config.data().graphic_pack_entries; + + // legacy absolute path checking for not breaking compatibility + auto file = gp->GetFilename2(); + auto it = config_entries.find(file.lexically_normal()); + if (it == config_entries.cend()) + { + // check for relative path + it = config_entries.find(MakeRelativePath(gp->GetFilename2()).lexically_normal()); + } + + if (it != config_entries.cend()) + { + bool enabled = true; + for (auto& kv : it->second) + { + if (boost::iequals(kv.first, "_disabled")) + { + enabled = false; + continue; + } + + gp->SetActivePreset(kv.first, kv.second, false); + } + + gp->SetEnabled(enabled); + } + + gp->UpdatePresetVisibility(); + gp->ValidatePresetSelections(); + + s_graphic_packs.emplace_back(gp); + return true; + } + catch (const std::exception&) + { + return false; + } + +} + +bool GraphicPack2::ActivateGraphicPack(const std::shared_ptr<GraphicPack2>& graphic_pack) +{ + if (graphic_pack->Activate()) + { + s_active_graphic_packs.push_back(graphic_pack); + return true; + } + + return false; +} + +bool GraphicPack2::DeactivateGraphicPack(const std::shared_ptr<GraphicPack2>& graphic_pack) +{ + if (!graphic_pack->IsActivated()) + return false; + + const auto it = std::find_if(s_active_graphic_packs.begin(), s_active_graphic_packs.end(), + [graphic_pack](const GraphicPackPtr& gp) + { + return gp->GetFilename() == graphic_pack->GetFilename(); + } + ); + + if (it == s_active_graphic_packs.end()) + return false; + + graphic_pack->Deactivate(); + s_active_graphic_packs.erase(it); + return true; +} + +void GraphicPack2::ClearGraphicPacks() +{ + s_graphic_packs.clear(); + s_active_graphic_packs.clear(); +} + +GraphicPack2::GraphicPack2(std::wstring filename) + : m_filename(std::move(filename)) +{ + // unused for now +} + +std::unordered_map<std::string, GraphicPack2::PresetVar> GraphicPack2::ParsePresetVars(IniParser& rules) const +{ + ExpressionParser parser; + std::unordered_map<std::string, PresetVar> vars; + for(auto& itr : rules.GetAllOptions()) + { + auto option_name = itr.first; + auto option_value = itr.second; + if (option_name.empty() || option_name[0] != '$') + continue; + VarType type = kDouble; + std::string name(option_name); + const auto index = name.find(':'); + if(index != std::string::npos) + { + auto type_name = name.substr(index + 1); + name = name.substr(0, index); + + trim(name); + trim(type_name); + + if (type_name == "double") + type = kDouble; + else if (type_name == "int") + type = kInt; + } + const double value = parser.Evaluate(option_value); + vars.try_emplace(name, std::make_pair(type, value)); + parser.AddConstant(name, value); + } + return vars; +} + +GraphicPack2::GraphicPack2(std::wstring filename, IniParser& rules) + : m_filename(std::move(filename)) +{ + // we're already in [Definition] + auto option_version = rules.FindOption("version"); + if (!option_version) + throw std::exception(); + m_version = StringHelpers::ToInt(*option_version, -1); + if (m_version < 0) + { + cemuLog_force(L"{}: Invalid version", m_filename); + throw std::exception(); + } + + auto option_rendererFilter = rules.FindOption("rendererFilter"); + if (option_rendererFilter) + { + if (boost::iequals(*option_rendererFilter, "vulkan")) + m_renderer_api = RendererAPI::Vulkan; + else if (boost::iequals(*option_rendererFilter, "opengl")) + m_renderer_api = RendererAPI::OpenGL; + else + cemuLog_force("Unknown value '{}' for rendererFilter option", *option_rendererFilter); + } + + auto option_defaultEnabled = rules.FindOption("default"); + if(option_defaultEnabled) + { + m_default_enabled = boost::iequals(*option_defaultEnabled, "true") || boost::iequals(*option_defaultEnabled, "1"); + m_enabled = m_default_enabled; + } + + auto option_vendorFilter = rules.FindOption("vendorFilter"); + if (option_vendorFilter) + { + if (boost::iequals(*option_vendorFilter, "amd")) + m_gfx_vendor = GfxVendor::AMD; + else if (boost::iequals(*option_vendorFilter, "intel")) + m_gfx_vendor = GfxVendor::Intel; + else if (boost::iequals(*option_vendorFilter, "mesa")) + m_gfx_vendor = GfxVendor::Mesa; + else if (boost::iequals(*option_vendorFilter, "nvidia")) + m_gfx_vendor = GfxVendor::Nvidia; + else + cemuLog_force("Unknown value '{}' for vendorFilter", *option_vendorFilter); + } + + auto option_path = rules.FindOption("path"); + if (!option_path) + { + auto gp_name_log = rules.FindOption("name"); + cemuLog_force("[Definition] section from '{}' graphic pack must contain option: path", gp_name_log.has_value() ? *gp_name_log : "Unknown"); + throw std::exception(); + } + m_path = *option_path; + + auto option_gp_name = rules.FindOption("name"); + if (option_gp_name) + m_name = *option_gp_name; + + auto option_description = rules.FindOption("description"); + if (option_description) + { + m_description = *option_description; + std::replace(m_description.begin(), m_description.end(), '|', '\n'); + } + + m_title_ids = ParseTitleIds(rules, "titleIds"); + if(m_title_ids.empty()) + throw std::exception(); + + auto option_fsPriority = rules.FindOption("fsPriority"); + if (option_fsPriority) + { + std::string tmp(*option_fsPriority); + m_fs_priority = std::stoi(tmp); + } + + // load presets + while (rules.NextSection()) + { + auto currentSectionName = rules.GetCurrentSectionName(); + if (boost::iequals(currentSectionName, "Default")) + { + m_preset_vars = ParsePresetVars(rules); + } + else if (boost::iequals(currentSectionName, "Preset")) + { + const auto preset_name = rules.FindOption("name"); + if (!preset_name) + { + cemuLog_force("Graphic pack \"{}\": Preset in line {} skipped because it has no name option defined", m_name, rules.GetCurrentSectionLineNumber()); + continue; + } + + const auto category = rules.FindOption("category"); + const auto condition = rules.FindOption("condition"); + const auto default_selected = rules.FindOption("default"); + + try + { + const auto vars = ParsePresetVars(rules); + PresetPtr preset; + if (category && condition) + preset = std::make_shared<Preset>(*category, *preset_name, *condition, vars); + else if (category) + preset = std::make_shared<Preset>(*category, *preset_name, vars); + else + preset = std::make_shared<Preset>(*preset_name, vars); + if (default_selected) + preset->is_default = StringHelpers::ToInt(*default_selected) != 0; + m_presets.emplace_back(preset); + } + catch (const std::exception & ex) + { + cemuLog_force("Graphic pack \"{}\": Can't parse preset \"{}\": {}", m_name, *preset_name, ex.what()); + } + } + else if (boost::iequals(currentSectionName, "RAM")) + { + for (uint32 i = 0; i < 32; i++) + { + char optionNameBuf[64]; + *fmt::format_to(optionNameBuf, "mapping{}", i) = '\0'; + const auto mappingOption = rules.FindOption(optionNameBuf); + if (mappingOption) + { + if (m_version <= 5) + { + cemuLog_force("Graphic pack \"{}\": [RAM] options are only available for graphic pack version 6 or higher", m_name, optionNameBuf); + throw std::exception(); + } + + StringTokenParser parser(*mappingOption); + uint32 addrStart = 0, addrEnd = 0; + if (parser.parseU32(addrStart) && parser.matchWordI("-") && parser.parseU32(addrEnd) && parser.isEndOfString()) + { + if (addrEnd <= addrStart) + { + cemuLog_force("Graphic pack \"{}\": start address (0x{:08x}) must be greater than end address (0x{:08x}) for {}", m_name, addrStart, addrEnd, optionNameBuf); + throw std::exception(); + } + else if ((addrStart & 0xFFF) != 0 || (addrEnd & 0xFFF) != 0) + { + cemuLog_force("Graphic pack \"{}\": addresses for %s are not aligned to 0x1000", m_name, optionNameBuf); + throw std::exception(); + } + else + { + m_ramMappings.emplace_back(addrStart, addrEnd); + } + } + else + { + cemuLog_force("Graphic pack \"{}\": has invalid syntax for option {}", m_name, optionNameBuf); + throw std::exception(); + } + } + } + } + } + + if (m_version >= 5) + { + // store by category + std::unordered_map<std::string, std::vector<PresetPtr>> tmp_map; + + // all vars must be defined in the default preset vars before + for (const auto& entry : m_presets) + { + tmp_map[entry->category].emplace_back(entry); + + for (auto& kv : entry->variables) + { + const auto it = m_preset_vars.find(kv.first); + if (it == m_preset_vars.cend()) + { + cemuLog_force("Graphic pack: \"{}\" contains preset variables which are not defined in the default section", m_name); + throw std::exception(); + } + + // overwrite var type with default var type + kv.second.first = it->second.first; + } + } + + // have first entry be default active for every category if no default= is set + for(auto entry : get_values(tmp_map)) + { + if (!entry.empty()) + { + const auto it = std::find_if(entry.cbegin(), entry.cend(), [](const PresetPtr& preset) { return preset->is_default; }); + if (it != entry.cend()) + (*it)->active = true; + else + (*entry.begin())->active = true; + } + } + } + else + { + // verify preset data to contain the same keys + std::unordered_map<std::string, std::vector<PresetPtr>> tmp_map; + for (const auto& entry : m_presets) + tmp_map[entry->category].emplace_back(entry); + + for (const auto& kv : tmp_map) + { + bool has_default = false; + for (size_t i = 0; i + 1 < kv.second.size(); ++i) + { + auto& p1 = kv.second[i]; + auto& p2 = kv.second[i + 1]; + if (p1->variables.size() != p2->variables.size()) + { + cemuLog_force("Graphic pack: \"{}\" contains inconsistent preset variables", m_name); + throw std::exception(); + } + + std::set<std::string> keys1(get_keys(p1->variables).begin(), get_keys(p1->variables).end()); + std::set<std::string> keys2(get_keys(p2->variables).begin(), get_keys(p2->variables).end()); + if (keys1 != keys2) + { + cemuLog_force("Graphic pack: \"{}\" contains inconsistent preset variables", m_name); + throw std::exception(); + } + + if(p1->is_default) + { + if(has_default) + cemuLog_force("Graphic pack: \"{}\" has more than one preset with the default key set for the same category \"{}\"", m_name, p1->name); + p1->active = true; + has_default = true; + } + } + + // have first entry by default active if no default is set + if (!has_default) + kv.second[0]->active = true; + } + } +} + +bool GraphicPack2::Reload() +{ + Deactivate(); + return Activate(); +} + +bool GraphicPack2::ContainsTitleId(uint64_t title_id) const +{ + const auto it = std::find_if(m_title_ids.begin(), m_title_ids.end(), [title_id](uint64 id) { return id == title_id; }); + return it != m_title_ids.end(); +} + +bool GraphicPack2::HasActivePreset() const +{ + return std::any_of(m_presets.cbegin(), m_presets.cend(), [](const PresetPtr& preset) + { + return preset->active; + }); +} + +std::string GraphicPack2::GetActivePreset(std::string_view category) const +{ + const auto it = std::find_if(m_presets.cbegin(), m_presets.cend(), [category](const PresetPtr& preset) + { + return preset->active && preset->category == category; + }); + return it != m_presets.cend() ? (*it)->name : std::string{ "" }; +} + +void GraphicPack2::UpdatePresetVisibility() +{ + // update visiblity of each preset + std::for_each(m_presets.begin(), m_presets.end(), [this](PresetPtr& p) + { + p->visible = m_version >= 5 ? IsPresetVisible(p) : true; + }); +} + +void GraphicPack2::ValidatePresetSelections() +{ + if (m_version < 5) + return; // only applies to new categorized presets + + // if any preset is changed then other categories might be affected indirectly + // + // example: selecting the aspect ratio in a resolution graphic pack would change the available presets in the resolution category + // how to handle: select the first available resolution (or the one marked as default) + // + // example: a preset category might be hidden entirely (e.g. due to a separate advanced options dropdown) + // how to handle: leave the previously selected preset + // + // the logic is therefore as follows: + // if there is a preset category with at least 1 visible preset entry then make sure one of those is actually selected + // for completely hidden preset categories we leave the selection as-is + + std::vector<std::string> order; + std::unordered_map<std::string, std::vector<GraphicPack2::PresetPtr>> categorizedPresets = GraphicPack2::GetCategorizedPresets(order); + + bool changedPresets = false; + for (auto& categoryItr : categorizedPresets) + { + // get selection of this category + size_t numVisiblePresets = 0; + GraphicPack2::PresetPtr defaultSelection = nullptr; + GraphicPack2::PresetPtr selectedPreset = nullptr; + for (auto& presetItr : categoryItr.second) + { + if (presetItr->visible) + { + numVisiblePresets++; + if (!defaultSelection || presetItr->is_default) // the preset marked as default becomes the default selection, otherwise pick first visible one + defaultSelection = presetItr; + } + if (presetItr->active) + { + if (selectedPreset) + { + // multiple selections inside the same group are invalid + presetItr->active = false; + changedPresets = true; + } + else + selectedPreset = presetItr; + } + } + if (numVisiblePresets == 0) + continue; // do not touch selection + if (!selectedPreset) + { + // no selection at all + if (defaultSelection) + { + selectedPreset = defaultSelection; + selectedPreset->active = true; + } + continue; + } + // if the currently selected preset is invisible, update it to the preferred visible selection + if (!selectedPreset->visible) + { + selectedPreset->active = false; + defaultSelection->active = true; + changedPresets = true; + } + } + if (changedPresets) + UpdatePresetVisibility(); +} + +bool GraphicPack2::SetActivePreset(std::string_view category, std::string_view name, bool update_visibility) +{ + // disable currently active preset + std::for_each(m_presets.begin(), m_presets.end(), [category](PresetPtr& p) + { + if(p->category == category) + p->active = false; + }); + + if (name.empty()) + return true; + + // enable new preset + const auto it = std::find_if(m_presets.cbegin(), m_presets.cend(), [category, name](const PresetPtr& preset) + { + return preset->category == category && preset->name == name; + }); + + bool result; + if (it != m_presets.cend()) + { + (*it)->active = true; + cemu_assert_debug(std::count_if(m_presets.cbegin(), m_presets.cend(), [category](const PresetPtr& p) { return p->category == category && p->active; }) == 1); + result = true; + } + else + result = false; + + if (update_visibility) + { + UpdatePresetVisibility(); + ValidatePresetSelections(); + } + + return result; +} + +void GraphicPack2::LoadShaders() +{ + fs::path path(m_filename); + for (auto& it : fs::directory_iterator(path.remove_filename())) + { + if (!is_regular_file(it)) + continue; + + try + { + const auto& p = it.path(); + auto filename = p.filename().wstring(); + uint64 shader_base_hash = 0; + uint64 shader_aux_hash = 0; + wchar_t shader_type[256]{}; + if (filename.size() < 256 && swscanf(filename.c_str(), L"%I64x_%I64x_%ls", &shader_base_hash, &shader_aux_hash, shader_type) == 3) + { + if (shader_type[0] == 'p' && shader_type[1] == 's') + m_custom_shaders.emplace_back(LoadShader(p, shader_base_hash, shader_aux_hash, GP_SHADER_TYPE::PIXEL)); + else if (shader_type[0] == 'v' && shader_type[1] == 's') + m_custom_shaders.emplace_back(LoadShader(p, shader_base_hash, shader_aux_hash, GP_SHADER_TYPE::VERTEX)); + else if (shader_type[0] == 'g' && shader_type[1] == 's') + m_custom_shaders.emplace_back(LoadShader(p, shader_base_hash, shader_aux_hash, GP_SHADER_TYPE::GEOMETRY)); + } + else if (filename == L"output.glsl") + { + std::ifstream file(p); + if (!file.is_open()) + throw std::runtime_error(fmt::format("can't open graphic pack file: {}", p.filename().string()).c_str()); + + file.seekg(0, std::ios::end); + m_output_shader_source.reserve(file.tellg()); + file.seekg(0, std::ios::beg); + + m_output_shader_source.assign(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()); + ApplyShaderPresets(m_output_shader_source); + } + else if (filename == L"upscaling.glsl") + { + std::ifstream file(p); + if (!file.is_open()) + throw std::runtime_error(fmt::format("can't open graphic pack file: {}", p.filename().string()).c_str()); + + file.seekg(0, std::ios::end); + m_upscaling_shader_source.reserve(file.tellg()); + file.seekg(0, std::ios::beg); + + m_upscaling_shader_source.assign(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()); + ApplyShaderPresets(m_upscaling_shader_source); + } + else if (filename == L"downscaling.glsl") + { + std::ifstream file(p); + if (!file.is_open()) + throw std::runtime_error(fmt::format("can't open graphic pack file: {}", p.filename().string()).c_str()); + + file.seekg(0, std::ios::end); + m_downscaling_shader_source.reserve(file.tellg()); + file.seekg(0, std::ios::beg); + + m_downscaling_shader_source.assign(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()); + ApplyShaderPresets(m_downscaling_shader_source); + } + } + catch (const std::exception& ex) + { + forceLog_printf("graphicPack: error while loading custom shader: %s", ex.what()); + } + } +} + +#pragma optimize( "", off ) + +DLLEXPORT __declspec(noinline) void GraphicPack2_notifyActivate(GraphicPack2* gp, ExpressionParser* ep) +{ + // for Cemuhook + int placeholder = 0xDEADDEAD; +} + +DLLEXPORT __declspec(noinline) void GraphicPack2_notifyDeactivate(GraphicPack2* gp) +{ + // for Cemuhook + int placeholder = 0xDEADDEAD; +} + +#pragma optimize( "", on ) + + +bool GraphicPack2::SetActivePreset(std::string_view name) +{ + return SetActivePreset("", name); +} + +bool GraphicPack2::IsPresetVisible(const PresetPtr& preset) const +{ + if (preset->condition.empty()) + return true; + + try + { + TExpressionParser<int> p; + FillPresetConstants(p); + return p.Evaluate(preset->condition) != 0; + } + catch (const std::exception& ex) + { + forceLog_printf("error when trying to check visiblity of preset: %s", ex.what()); + return false; + } +} + +std::optional<GraphicPack2::PresetVar> GraphicPack2::GetPresetVariable(const std::vector<PresetPtr>& presets, std::string_view var_name) const +{ + // no priority and visibility filter + if(m_version < 5) + { + for (const auto& preset : presets) + { + const auto it = std::find_if(preset->variables.cbegin(), preset->variables.cend(), [&var_name](auto p) { return p.first == var_name; }); + if (it != preset->variables.cend()) + return it->second; + } + + return {}; + } + + // visible > none visible > default + for (const auto& preset : presets) + { + if (preset->visible) + { + const auto it = std::find_if(preset->variables.cbegin(), preset->variables.cend(), [&var_name](auto p) { return p.first == var_name; }); + if (it != preset->variables.cend()) + return it->second; + } + } + + for (const auto& preset : presets) + { + if (!preset->visible) + { + const auto it = std::find_if(preset->variables.cbegin(), preset->variables.cend(), [&var_name](auto p) { return p.first == var_name; }); + if (it != preset->variables.cend()) + return it->second; + } + } + + const auto it = std::find_if(m_preset_vars.cbegin(), m_preset_vars.cend(), [&var_name](auto p) { return p.first == var_name; }); + if (it != m_preset_vars.cend()) + { + return it->second; + } + + return {}; +} + +void GraphicPack2::AddConstantsForCurrentPreset(ExpressionParser& ep) +{ + if (m_version < 5) + { + for (const auto& preset : GetActivePresets()) + { + for (auto& v : preset->variables) + { + ep.AddConstant(v.first, v.second.second); + } + } + } + else + { + FillPresetConstants(ep); + } +} + +void GraphicPack2::_iterateReplacedFiles(const fs::path& currentPath, std::wstring& internalPath, bool isAOC) +{ + uint64 currentTitleId = CafeSystem::GetForegroundTitleId(); + uint64 aocTitleId = (currentTitleId & 0xFFFFFFFFull) | 0x0005000c00000000ull; + for (auto& it : fs::recursive_directory_iterator(currentPath)) + { + if (fs::is_regular_file(it)) + { + fs::path virtualMountPath = fs::relative(it.path(), currentPath); + if (isAOC) + { + virtualMountPath = fs::path(fmt::format("/vol/aoc{:016x}/", aocTitleId)) / virtualMountPath; + } + else + { + virtualMountPath = fs::path("vol/content/") / virtualMountPath; + } + fscDeviceRedirect_add(virtualMountPath.generic_string(), it.path().generic_string(), m_fs_priority); + } + } +} + +void GraphicPack2::LoadReplacedFiles() +{ + if (m_patchedFilesLoaded) + return; + m_patchedFilesLoaded = true; + + fs::path gfxPackPath(m_filename.c_str()); + gfxPackPath = gfxPackPath.remove_filename(); + + // /content/ + fs::path contentPath(gfxPackPath); + contentPath.append("content"); + + std::error_code ec; + if (fs::exists(contentPath, ec)) + { + std::wstring internalPath(L"/vol/content/"); + // setup redirections + fscDeviceRedirect_map(); + _iterateReplacedFiles(contentPath, internalPath, false); + } + // /aoc/ + fs::path aocPath(gfxPackPath); + aocPath.append("aoc"); + + if (fs::exists(aocPath, ec)) + { + uint64 aocTitleId = CafeSystem::GetForegroundTitleId(); + aocTitleId = aocTitleId & 0xFFFFFFFFULL; + aocTitleId |= 0x0005000c00000000ULL; + wchar_t internalAocPath[128]; + swprintf(internalAocPath, sizeof(internalAocPath)/sizeof(wchar_t), L"/aoc/%016llx/", aocTitleId); + + std::wstring internalPath(internalAocPath); + // setup redirections + fscDeviceRedirect_map(); + _iterateReplacedFiles(aocPath, internalPath, true); + } +} + +bool GraphicPack2::Activate() +{ + if (m_activated) + return true; + + // check if gp should be loaded + if (m_renderer_api.has_value() && m_renderer_api.value() != g_renderer->GetType()) + return false; + + if (m_gfx_vendor.has_value()) + { + auto vendor = g_renderer->GetVendor(); + if (vendor == GfxVendor::IntelLegacy || vendor == GfxVendor::IntelNoLegacy) + vendor = GfxVendor::Intel; + + if (m_gfx_vendor.value() != vendor) + return false; + } + + FileStream* fs_rules = FileStream::openFile2({ m_filename }); + if (!fs_rules) + return false; + std::vector<uint8> rulesData; + fs_rules->extract(rulesData); + delete fs_rules; + + IniParser rules({ (char*)rulesData.data(), rulesData.size()}, boost::nowide::narrow(m_filename)); + + // load rules + try + { + ExpressionParser parser; + AddConstantsForCurrentPreset(parser); + + while (rules.NextSection()) + { + //const char* category_name = sPref_currentCategoryName(rules); + std::string_view category_name = rules.GetCurrentSectionName(); + if (boost::iequals(category_name, "TextureRedefine")) + { + TextureRule rule{}; + ParseRule(parser, rules, "width", &rule.filter_settings.width); + ParseRule(parser, rules, "height", &rule.filter_settings.height); + ParseRule(parser, rules, "depth", &rule.filter_settings.depth); + + bool inMem1 = false; + if (ParseRule(parser, rules, "inMEM1", &inMem1)) + rule.filter_settings.inMEM1 = inMem1 ? TextureRule::FILTER_SETTINGS::MEM1_FILTER::INSIDE : TextureRule::FILTER_SETTINGS::MEM1_FILTER::OUTSIDE; + + rule.filter_settings.format_whitelist = ParseList<sint32>(parser, rules, "formats"); + rule.filter_settings.format_blacklist = ParseList<sint32>(parser, rules, "formatsExcluded"); + rule.filter_settings.tilemode_whitelist = ParseList<sint32>(parser, rules, "tilemodes"); + rule.filter_settings.tilemode_blacklist = ParseList<sint32>(parser, rules, "tilemodesExcluded"); + + ParseRule(parser, rules, "overwriteWidth", &rule.overwrite_settings.width); + ParseRule(parser, rules, "overwriteHeight", &rule.overwrite_settings.height); + ParseRule(parser, rules, "overwriteDepth", &rule.overwrite_settings.depth); + ParseRule(parser, rules, "overwriteFormat", &rule.overwrite_settings.format); + + float lod_bias; + if(ParseRule(parser, rules, "overwriteLodBias", &lod_bias)) + rule.overwrite_settings.lod_bias = (sint32)(lod_bias * 64.0f); + + if(ParseRule(parser, rules, "overwriteRelativeLodBias", &lod_bias)) + rule.overwrite_settings.relative_lod_bias = (sint32)(lod_bias * 64.0f); + + sint32 anisotropyValue; + if (ParseRule(parser, rules, "overwriteAnisotropy", &anisotropyValue)) + { + if (anisotropyValue == 1) + rule.overwrite_settings.anistropic_value = 0; + else if (anisotropyValue == 2) + rule.overwrite_settings.anistropic_value = 1; + else if (anisotropyValue == 4) + rule.overwrite_settings.anistropic_value = 2; + else if (anisotropyValue == 8) + rule.overwrite_settings.anistropic_value = 3; + else if (anisotropyValue == 16) + rule.overwrite_settings.anistropic_value = 4; + else + cemuLog_log(LogType::Force, fmt::format(L"Invalid value {} for overwriteAnisotropy in graphic pack {}. Only the values 1, 2, 4, 8 or 16 are allowed.", anisotropyValue, m_filename)); + } + m_texture_rules.emplace_back(rule); + } + else if (boost::iequals(category_name, "Control")) + { + ParseRule(parser, rules, "vsyncFrequency", &m_vsync_frequency); + } + else if (boost::iequals(category_name, "OutputShader")) + { + auto option_upscale = rules.FindOption("upscaleMagFilter"); + if(option_upscale && boost::iequals(*option_upscale, "NearestNeighbor")) + m_output_settings.upscale_filter = LatteTextureView::MagFilter::kNearestNeighbor; + auto option_downscale = rules.FindOption("NearestNeighbor"); + if (option_downscale && boost::iequals(*option_downscale, "NearestNeighbor")) + m_output_settings.downscale_filter = LatteTextureView::MagFilter::kNearestNeighbor; + } + } + GraphicPack2_notifyActivate(this, &parser); + } + catch(const std::exception& ex) + { + forceLog_printf((char*)ex.what()); + return false; + } + + // load shaders + LoadShaders(); + + // load patches + LoadPatchFiles(); + + // enable patch groups + EnablePatches(); + + // load replaced files + LoadReplacedFiles(); + + // set custom vsync + if (HasCustomVSyncFrequency()) + { + sint32 customVsyncFreq = GetCustomVSyncFrequency(); + sint32 globalCustomVsyncFreq = 0; + if (LatteTiming_getCustomVsyncFrequency(globalCustomVsyncFreq)) + { + if (customVsyncFreq != globalCustomVsyncFreq) + forceLog_printf("rules.txt error: Mismatching vsync frequency %d in graphic pack \'%s\'", customVsyncFreq, GetPath().c_str()); + } + else + { + forceLog_printf("Set vsync frequency to %d (graphic pack %s)", customVsyncFreq, GetPath().c_str()); + LatteTiming_setCustomVsyncFrequency(customVsyncFreq); + } + } + m_activated = true; + return true; +} + +bool GraphicPack2::Deactivate() +{ + if (!m_activated) + return false; + + UnloadPatches(); + + m_activated = false; + m_custom_shaders.clear(); + m_texture_rules.clear(); + + m_output_shader.reset(); + m_upscaling_shader.reset(); + m_downscaling_shader.reset(); + + m_output_shader_ud.reset(); + m_upscaling_shader_ud.reset(); + m_downscaling_shader_ud.reset(); + + m_output_shader_source = ""; + m_upscaling_shader_source = ""; + m_downscaling_shader_source = ""; + + if (HasCustomVSyncFrequency()) + { + m_vsync_frequency = -1; + + LatteTiming_disableCustomVsyncFrequency(); + } + + GraphicPack2_notifyDeactivate(this); + return true; +} + +const std::string* GraphicPack2::FindCustomShaderSource(uint64 shaderBaseHash, uint64 shaderAuxHash, GP_SHADER_TYPE type, bool isVulkanRenderer) +{ + for (const auto& gp : GraphicPack2::GetActiveGraphicPacks()) + { + const auto it = std::find_if(gp->m_custom_shaders.begin(), gp->m_custom_shaders.end(), + [shaderBaseHash, shaderAuxHash, type](const auto& s) { return s.shader_base_hash == shaderBaseHash && s.shader_aux_hash == shaderAuxHash && s.type == type; }); + + if (it == gp->m_custom_shaders.end()) + continue; + + if(isVulkanRenderer && (*it).isPreVulkanShader) + continue; + + return &it->source; + } + return nullptr; +} + +std::unordered_map<std::string, std::vector<GraphicPack2::PresetPtr>> GraphicPack2::GetCategorizedPresets(std::vector<std::string>& order) const +{ + order.clear(); + + std::unordered_map<std::string, std::vector<PresetPtr>> result; + for(const auto& entry : m_presets) + { + result[entry->category].emplace_back(entry); + const auto it = std::find(order.cbegin(), order.cend(), entry->category); + if (it == order.cend()) + order.emplace_back(entry->category); + } + + return result; +} + +bool GraphicPack2::HasShaders() const +{ + return !GetCustomShaders().empty() + || !m_output_shader_source.empty() || !m_upscaling_shader_source.empty() || !m_downscaling_shader_source.empty(); +} + +RendererOutputShader* GraphicPack2::GetOuputShader(bool render_upside_down) +{ + if(render_upside_down) + { + if (m_output_shader_ud) + return m_output_shader_ud.get(); + + if (!m_output_shader_source.empty()) + m_output_shader_ud = std::make_unique<RendererOutputShader>(RendererOutputShader::GetOpenGlVertexSource(render_upside_down), m_output_shader_source); + + return m_output_shader_ud.get(); + } + else + { + if (m_output_shader) + return m_output_shader.get(); + + if (!m_output_shader_source.empty()) + m_output_shader = std::make_unique<RendererOutputShader>(RendererOutputShader::GetOpenGlVertexSource(render_upside_down), m_output_shader_source); + + return m_output_shader.get(); + } +} + +RendererOutputShader* GraphicPack2::GetUpscalingShader(bool render_upside_down) +{ + if (render_upside_down) + { + if (m_upscaling_shader_ud) + return m_upscaling_shader_ud.get(); + + if (!m_upscaling_shader_source.empty()) + m_upscaling_shader_ud = std::make_unique<RendererOutputShader>(RendererOutputShader::GetOpenGlVertexSource(render_upside_down), m_upscaling_shader_source); + + return m_upscaling_shader_ud.get(); + } + else + { + if (m_upscaling_shader) + return m_upscaling_shader.get(); + + if (!m_upscaling_shader_source.empty()) + m_upscaling_shader = std::make_unique<RendererOutputShader>(RendererOutputShader::GetOpenGlVertexSource(render_upside_down), m_upscaling_shader_source); + + return m_upscaling_shader.get(); + } +} + +RendererOutputShader* GraphicPack2::GetDownscalingShader(bool render_upside_down) +{ + if (render_upside_down) + { + if (m_downscaling_shader_ud) + return m_downscaling_shader_ud.get(); + + if (!m_downscaling_shader_source.empty()) + m_downscaling_shader_ud = std::make_unique<RendererOutputShader>(RendererOutputShader::GetOpenGlVertexSource(render_upside_down), m_downscaling_shader_source); + + return m_downscaling_shader_ud.get(); + } + else + { + if (m_downscaling_shader) + return m_downscaling_shader.get(); + + if (!m_downscaling_shader_source.empty()) + m_downscaling_shader = std::make_unique<RendererOutputShader>(RendererOutputShader::GetOpenGlVertexSource(render_upside_down), m_downscaling_shader_source); + + return m_downscaling_shader.get(); + } +} + + +std::vector<GraphicPack2::PresetPtr> GraphicPack2::GetActivePresets() const +{ + std::vector<PresetPtr> result; + result.reserve(m_presets.size()); + std::copy_if(m_presets.cbegin(), m_presets.cend(), std::back_inserter(result), [](const PresetPtr& p) { return p->active; }); + return result; +} + +std::vector<uint64> GraphicPack2::ParseTitleIds(IniParser& rules, const char* option_name) const +{ + std::vector<uint64> result; + + auto option_text = rules.FindOption(option_name); + if (!option_text) + return result; + + for (auto& token : TokenizeView(*option_text, ',')) + { + try + { + result.emplace_back(ConvertString<uint64>(token, 16)); + } + catch (const std::invalid_argument&) {} + } + + return result; +} + +void GraphicPack2::ApplyShaderPresets(std::string& shader_source) const +{ + const auto active_presets = GetActivePresets(); + const std::regex regex(R"(\$[a-zA-Z\_0-9]+)"); + + std::smatch match; + size_t offset = 0; + while (std::regex_search(shader_source.cbegin() + offset, shader_source.cend(), match, regex)) + { + if (active_presets.empty()) + throw std::runtime_error("found variable in shader but no preset is active"); + + const auto str = match.str(); + + std::optional<PresetVar> var = GetPresetVariable(active_presets, str); + if(!var) + throw std::runtime_error("using an unknown preset variable in shader"); + + std::string new_value; + if (var->first == kInt) + new_value = fmt::format("{}", (int)var->second); + else + new_value = fmt::format("{:f}", var->second); + + shader_source.replace(match.position() + offset, match.length(), new_value); + offset += match.position() + new_value.length(); + } +} + +GraphicPack2::CustomShader GraphicPack2::LoadShader(const fs::path& path, uint64 shader_base_hash, uint64 shader_aux_hash, GP_SHADER_TYPE shader_type) const +{ + CustomShader shader; + + std::ifstream file(path); + if (!file.is_open()) + throw std::runtime_error("can't open shader file"); + + file.seekg(0, std::ios::end); + shader.source.reserve(file.tellg()); + file.seekg(0, std::ios::beg); + + shader.source.assign(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()); + ApplyShaderPresets(shader.source); + + shader.shader_base_hash = shader_base_hash; + shader.shader_aux_hash = shader_aux_hash; + shader.type = shader_type; + shader.isPreVulkanShader = this->m_version <= 3; + + return shader; +} + +std::vector<std::pair<MPTR, MPTR>> GraphicPack2::GetActiveRAMMappings() +{ + uint64 currentTitleId = CafeSystem::GetForegroundTitleId(); + std::vector<std::pair<MPTR, MPTR>> v; + for (const auto& gp : GraphicPack2::GetGraphicPacks()) + { + if (!gp->IsEnabled()) + continue; + if (!gp->ContainsTitleId(currentTitleId)) + continue; + if (!gp->m_ramMappings.empty()) + v.insert(v.end(), gp->m_ramMappings.begin(), gp->m_ramMappings.end()); + } + std::sort(v.begin(), v.end(), + [](const std::pair<MPTR, MPTR>& a, const std::pair<MPTR, MPTR>& b) -> bool + { + return a.first < b.first; + }); + return v; +} + +// C-style exports for Cemuhook + +DLLEXPORT const wchar_t* GraphicPack2_GetFilename(GraphicPack2* gp) +{ + return gp->GetFilename().c_str(); +} + +DLLEXPORT const char* GraphicPack2_GetName(GraphicPack2* gp) +{ + if (!gp->HasName()) + return ""; + + return gp->GetName().c_str(); +} + +DLLEXPORT const char* GraphicPack2_GetPath(GraphicPack2* gp) +{ + return gp->GetPath().c_str(); +} + +DLLEXPORT const char* GraphicPack2_GetDescription(GraphicPack2* gp) +{ + return gp->GetDescription().c_str(); +} + +DLLEXPORT const sint32 GraphicPack2_GetTitleIdCount(GraphicPack2* gp) +{ + return gp->GetTitleIds().size(); +} + +DLLEXPORT const uint64* GraphicPack2_GetTitleIdList(GraphicPack2* gp) +{ + return &gp->GetTitleIds()[0]; +} + +DLLEXPORT ExpressionParser* GraphicPack2_CreateExpressionParser(GraphicPack2* gp) +{ + auto ep = new ExpressionParser(); + gp->AddConstantsForCurrentPreset((ExpressionParser&)*ep); + return ep; +} diff --git a/src/Cafe/GraphicPack/GraphicPack2.h b/src/Cafe/GraphicPack/GraphicPack2.h new file mode 100644 index 00000000..b02dbf84 --- /dev/null +++ b/src/Cafe/GraphicPack/GraphicPack2.h @@ -0,0 +1,323 @@ +#pragma once + +#include "util/helpers/helpers.h" +#include "Cemu/ExpressionParser/ExpressionParser.h" +#include "Cafe/HW/Latte/Renderer/RendererOuputShader.h" +#include "util/helpers/Serializer.h" +#include "Cafe/OS/RPL/rpl.h" +#include "Cemu/PPCAssembler/ppcAssembler.h" +#include <variant> +#include "Cafe/HW/Latte/Renderer/Renderer.h" +#include "GraphicPack2Patches.h" +#include "util/IniParser/IniParser.h" + +class GraphicPack2 +{ +public: + enum class GP_SHADER_TYPE : uint8 + { + PIXEL = 0, + VERTEX = 1, + GEOMETRY = 2, + }; + + enum + { + GFXPACK_VERSION_5 = 5, + GFXPACK_VERSION_6 = 6, // added memory extensions + GFXPACK_VERSION_7 = 7, // added fine-grained origin control in patch format (no more forced 4 byte alignment), .string directive (an alias to .byte) and support for more than one constant per data directive + }; + + struct TextureRule + { + // filter (texture must match these settings) + struct FILTER_SETTINGS + { + enum class MEM1_FILTER { + BOTH, + INSIDE, + OUTSIDE, + }; + sint32 width = -1; + sint32 height = -1; + sint32 depth = -1; + MEM1_FILTER inMEM1 = MEM1_FILTER::BOTH; + std::vector<sint32> format_whitelist{}; + std::vector<sint32> format_blacklist{}; + std::vector<sint32> tilemode_whitelist{}; + std::vector<sint32> tilemode_blacklist{}; + } filter_settings; + // overwrite (if match found, these settings are overwritten) + struct OVERWRITE_SETTINGS + { + sint32 width = -1; + sint32 height = -1; + sint32 depth = -1; + sint32 format = -1; + sint32 lod_bias = -1; // in 1/64th steps + sint32 relative_lod_bias = -1; // in 1/64th steps + sint32 anistropic_value = -1; // 1<<n + } overwrite_settings; + }; + + struct CustomShader + { + std::string source; + uint64 shader_base_hash; + uint64 shader_aux_hash; + GP_SHADER_TYPE type; + bool isPreVulkanShader{}; // set to true for V3 packs since the shaders are not compatible with the Vulkan renderer + }; + + enum VarType + { + kDouble = 0, + kInt = 1, + }; + using PresetVar = std::pair<VarType, double>; + + struct Preset + { + std::string category; // preset category (empty for default) + std::string name; // displayed name + std::string condition; + std::unordered_map<std::string, PresetVar> variables; + bool active = false; // selected/active preset + bool visible = true; // set by condition or true + bool is_default = false; // selected by default + + Preset(std::string_view name, std::unordered_map<std::string, PresetVar> vars) + : name(name), variables(std::move(vars)) {} + + Preset(std::string_view category, std::string_view name, std::unordered_map<std::string, PresetVar> vars) + : category(category), name(name), variables(std::move(vars)) {} + + Preset(std::string_view category, std::string_view name, std::string_view condition, std::unordered_map<std::string, PresetVar> vars) + : category(category), name(name), condition(condition), variables(std::move(vars)) {} + }; + using PresetPtr = std::shared_ptr<Preset>; + + GraphicPack2(std::wstring filename); + GraphicPack2(std::wstring filename, IniParser& rules); + + bool IsEnabled() const { return m_enabled; } + bool IsActivated() const { return m_activated; } + sint32 GetVersion() const { return m_version; } + const std::wstring& GetFilename() const { return m_filename; } + const fs::path GetFilename2() const { return fs::path(m_filename); } + bool Reload(); + + bool HasName() const { return !m_name.empty(); } + + const std::string& GetName() const { return m_name.empty() ? m_path : m_name; } + const std::string& GetPath() const { return m_path; } + const std::string& GetDescription() const { return m_description; } + bool IsDefaultEnabled() const { return m_default_enabled; } + + void SetEnabled(bool state) { m_enabled = state; } + + bool ContainsTitleId(uint64_t title_id) const; + const std::vector<uint64_t>& GetTitleIds() const { return m_title_ids; } + bool HasCustomVSyncFrequency() const { return m_vsync_frequency >= 1; } + sint32 GetCustomVSyncFrequency() const { return m_vsync_frequency; } + + // texture rules + const std::vector<TextureRule>& GetTextureRules() const { return m_texture_rules; } + + // presets + [[nodiscard]] bool HasActivePreset() const; + [[nodiscard]] std::string GetActivePreset(std::string_view category = "") const; + [[nodiscard]] std::vector<PresetPtr> GetActivePresets() const; + [[nodiscard]] bool IsPresetVisible(const PresetPtr& preset) const; + [[nodiscard]] std::optional<PresetVar> GetPresetVariable(const std::vector<PresetPtr>& presets, std::string_view var_name) const; + + void ValidatePresetSelections(); + bool SetActivePreset(std::string_view category, std::string_view name, bool update_visibility = true); + bool SetActivePreset(std::string_view name); + void UpdatePresetVisibility(); + + void AddConstantsForCurrentPreset(ExpressionParser& ep); + bool ResolvePresetConstant(const std::string& varname, double& value) const; + + [[nodiscard]] const std::vector<PresetPtr>& GetPresets() const { return m_presets; } + [[nodiscard]] std::unordered_map<std::string, std::vector<PresetPtr>> GetCategorizedPresets(std::vector<std::string>& order) const; + + // shaders + void LoadShaders(); + bool HasShaders() const; + const std::vector<CustomShader>& GetCustomShaders() const { return m_custom_shaders; } + + static const std::string* FindCustomShaderSource(uint64 shaderBaseHash, uint64 shaderAuxHash, GP_SHADER_TYPE type, bool isVulkanRenderer); + + const std::string& GetOutputShaderSource() const { return m_output_shader_source; } + const std::string& GetDownscalingShaderSource() const { return m_downscaling_shader_source; } + const std::string& GetUpscalingShaderSource() const { return m_upscaling_shader_source; } + RendererOutputShader* GetOuputShader(bool render_upside_down); + RendererOutputShader* GetUpscalingShader(bool render_upside_down); + RendererOutputShader* GetDownscalingShader(bool render_upside_down); + LatteTextureView::MagFilter GetUpscalingMagFilter() const { return m_output_settings.upscale_filter; } + LatteTextureView::MagFilter GetDownscalingMagFilter() const { return m_output_settings.downscale_filter; } + + // static methods + static const std::vector<std::shared_ptr<GraphicPack2>>& GetGraphicPacks() { return s_graphic_packs; } + static const std::vector<std::shared_ptr<GraphicPack2>>& GetActiveGraphicPacks() { return s_active_graphic_packs; } + static bool LoadGraphicPack(const std::wstring& filename, class IniParser& rules); + static bool ActivateGraphicPack(const std::shared_ptr<GraphicPack2>& graphic_pack); + static bool DeactivateGraphicPack(const std::shared_ptr<GraphicPack2>& graphic_pack); + static void ClearGraphicPacks(); +private: + __declspec(dllexport) bool Activate(); + __declspec(dllexport) bool Deactivate(); + + static std::vector<std::shared_ptr<GraphicPack2>> s_graphic_packs; + static std::vector<std::shared_ptr<GraphicPack2>> s_active_graphic_packs; + + template<typename TType> + void FillPresetConstants(TExpressionParser<TType>& parser) const + { + // fils preset variables with priority + // active && visible > active > default + const auto active_presets = GetActivePresets(); + for(const auto& preset : active_presets) + { + if(preset->visible) + { + for (auto& var : preset->variables) + parser.AddConstant(var.first, (TType)var.second.second); + } + } + for(const auto& preset : active_presets) + { + if(!preset->visible) + { + for (auto& var : preset->variables) + parser.TryAddConstant(var.first, (TType)var.second.second); + } + } + + for (auto& var : m_preset_vars) + parser.TryAddConstant(var.first, (TType)var.second.second); + } + + std::wstring m_filename; + + sint32 m_version; + std::string m_name; + std::string m_path; + std::string m_description; + + bool m_default_enabled = false; + + // filter + std::optional<RendererAPI> m_renderer_api; + std::optional<GfxVendor> m_gfx_vendor; + + bool m_enabled = false; + bool m_activated = false; // set if the graphic pack is currently used by the running game + std::vector<uint64_t> m_title_ids; + bool m_patchedFilesLoaded = false; // set to true once patched files are loaded + + sint32 m_vsync_frequency = -1; + sint32 m_fs_priority = 100; + + struct + { + LatteTextureView::MagFilter upscale_filter = LatteTextureView::MagFilter::kLinear; + LatteTextureView::MagFilter downscale_filter = LatteTextureView::MagFilter::kLinear; + } m_output_settings; + + std::vector<PresetPtr> m_presets; + // default preset vars + std::unordered_map<std::string, PresetVar> m_preset_vars; + + std::vector<CustomShader> m_custom_shaders; + std::vector<TextureRule> m_texture_rules; + std::string m_output_shader_source, m_upscaling_shader_source, m_downscaling_shader_source; + std::unique_ptr<RendererOutputShader> m_output_shader, m_upscaling_shader, m_downscaling_shader, m_output_shader_ud, m_upscaling_shader_ud, m_downscaling_shader_ud; + + template<typename T> + bool ParseRule(const ExpressionParser& parser, IniParser& iniParser, const char* option_name, T* value_out) const; + + template<typename T> + std::vector<T> ParseList(const ExpressionParser& parser, IniParser& iniParser, const char* option_name) const; + + std::unordered_map<std::string, PresetVar> ParsePresetVars(IniParser& rules) const; + + std::vector<uint64> ParseTitleIds(IniParser& rules, const char* option_name) const; + + CustomShader LoadShader(const fs::path& path, uint64 shader_base_hash, uint64 shader_aux_hash, GP_SHADER_TYPE shader_type) const; + void ApplyShaderPresets(std::string& shader_source) const; + void LoadReplacedFiles(); + void _iterateReplacedFiles(const fs::path& currentPath, std::wstring& internalPath, bool isAOC); + + // ram mappings + std::vector<std::pair<MPTR, MPTR>> m_ramMappings; + + // patches + void LoadPatchFiles(); // loads Cemuhook or Cemu patches + bool LoadCemuPatches(); + + void ParseCemuhookPatchesTxtInternal(MemStreamReader& patchesStream); + bool ParseCemuPatchesTxtInternal(MemStreamReader& patchesStream); + void CancelParsingPatches(); + + void ApplyPatchGroups(std::vector<PatchGroup*>& groups, const RPLModule* rpl); + void UndoPatchGroups(std::vector<PatchGroup*>& groups, const RPLModule* rpl); + + void AddPatchGroup(PatchGroup* group); + sint32 GetLengthWithoutComment(const char* str, size_t length); + void LogPatchesSyntaxError(sint32 lineNumber, std::string_view errorMsg); + + std::vector<PatchGroup*> list_patchGroups; + + static std::recursive_mutex mtx_patches; + static std::vector<const RPLModule*> list_modules; + +public: + static std::vector<std::pair<MPTR, MPTR>> GetActiveRAMMappings(); + void EnablePatches(); + void UnloadPatches(); + bool HasPatches(); + const std::vector<PatchGroup*>& GetPatchGroups(); + void ApplyPatchesForModule(const RPLModule* rpl); + void RevertPatchesForModule(const RPLModule* rpl); + + static void NotifyModuleLoaded(const RPLModule* rpl); + static void NotifyModuleUnloaded(const RPLModule* rpl); +}; + +using GraphicPackPtr = std::shared_ptr<GraphicPack2>; + +template <typename T> +bool GraphicPack2::ParseRule(const ExpressionParser& parser, IniParser& iniParser, const char* option_name, T* value_out) const +{ + auto option_value = iniParser.FindOption(option_name); + if (option_value) + { + *value_out = parser.Evaluate<T>(*option_value); + return true; + } + + return false; +} + +template <typename T> +std::vector<T> GraphicPack2::ParseList(const ExpressionParser& parser, IniParser& iniParser, const char* option_name) const +{ + std::vector<T> result; + + auto option_text = iniParser.FindOption(option_name); + if (!option_text) + return result; + + for(auto& token : Tokenize(*option_text, ',')) + { + try + { + result.emplace_back(parser.Evaluate<T>(token)); + } + catch (const std::invalid_argument&) {} + } + + return result; +} \ No newline at end of file diff --git a/src/Cafe/GraphicPack/GraphicPack2Patches.cpp b/src/Cafe/GraphicPack/GraphicPack2Patches.cpp new file mode 100644 index 00000000..3e4015c9 --- /dev/null +++ b/src/Cafe/GraphicPack/GraphicPack2Patches.cpp @@ -0,0 +1,220 @@ +#include "Cafe/GraphicPack/GraphicPack2.h" +#include "Common/filestream.h" +#include "util/helpers/StringParser.h" +#include "Cemu/PPCAssembler/ppcAssembler.h" +#include "Cafe/OS/RPL/rpl_structs.h" +#include "boost/algorithm/string.hpp" + +#include "gui/wxgui.h" // for wxMessageBox + +// error handler +void PatchErrorHandler::printError(class PatchGroup* patchGroup, sint32 lineNumber, std::string_view errorMsg) +{ + if (m_anyErrorTriggered == false) + { + // stage error msg + cemu_assert(m_gp); + if (m_stage == STAGE::PARSER) + cemuLog_writeLineToLog(fmt::format("An error occurred while trying to parse the patches for graphic pack \'{}\'", m_gp->GetName()), true, true); + else if (m_stage == STAGE::APPLY) + cemuLog_writeLineToLog(fmt::format("An error occurred while trying to apply the patches for graphic pack \'{}\'", m_gp->GetName()), true, true); + } + + std::string msg; + if (patchGroup == nullptr && lineNumber >= 0) + msg.append(fmt::format("[Line {}] ", lineNumber)); + else if (patchGroup && lineNumber >= 0) + msg.append(fmt::format("[{}, Line {}] ", patchGroup->getName(), lineNumber)); + else if (patchGroup && lineNumber < 0) + msg.append(fmt::format("[{}] ", patchGroup->getName())); + + msg.append(errorMsg); + + cemuLog_writeLineToLog(msg, true, true); + m_anyErrorTriggered = true; + + if (cafeLog_isLoggingFlagEnabled(LOG_TYPE_PATCHES)) + errorMessages.emplace_back(msg); +} + +void PatchErrorHandler::showStageErrorMessageBox() +{ + std::string errorMsg; + if (m_gp) + { + if (m_stage == STAGE::PARSER) + errorMsg.assign(fmt::format("Failed to load patches for graphic pack \'{}\'", m_gp->GetName())); + else + errorMsg.assign(fmt::format("Failed to apply patches for graphic pack \'{}\'", m_gp->GetName())); + } + else + { + cemu_assert_debug(false); // graphic pack should always be set + } + if (cafeLog_isLoggingFlagEnabled(LOG_TYPE_PATCHES)) + { + errorMsg.append("\n \nDetails:\n"); + for (auto& itr : errorMessages) + { + errorMsg.append(itr); + errorMsg.append("\n"); + } + } + + wxMessageBox(errorMsg, "Graphic pack error"); +} + +// loads Cemu-style patches (patch_<anything>.asm) +// returns true if at least one file was found even if it could not be successfully parsed +bool GraphicPack2::LoadCemuPatches() +{ + // todo - once we have updated to C++20 we can replace these with the new std::string functions + auto startsWith = [](const std::wstring& str, const std::wstring& prefix) + { + return str.size() >= prefix.size() && 0 == str.compare(0, prefix.size(), prefix); + }; + + auto endsWith = [](const std::wstring& str, const std::wstring& suffix) + { + return str.size() >= suffix.size() && 0 == str.compare(str.size() - suffix.size(), suffix.size(), suffix); + }; + + bool foundPatches = false; + fs::path path(m_filename); + path.remove_filename(); + for (auto& p : fs::directory_iterator(path)) + { + auto& path = p.path(); + if (fs::is_regular_file(p.status()) && path.has_filename()) + { + // check if filename matches + std::wstring filename = path.filename().generic_wstring(); + if (boost::istarts_with(filename, L"patch_") && boost::iends_with(filename, L".asm")) + { + FileStream* patchFile = FileStream::openFile(path.generic_wstring().c_str()); + if (patchFile) + { + // read file + std::vector<uint8> fileData; + patchFile->extract(fileData); + delete patchFile; + MemStreamReader patchesStream(fileData.data(), (sint32)fileData.size()); + // load Cemu style patch file + if (!ParseCemuPatchesTxtInternal(patchesStream)) + { + forceLog_printfW(L"Error while processing \"%s\". No patches for this graphic pack will be applied.", path.c_str()); + cemu_assert_debug(list_patchGroups.empty()); + return true; // return true since a .asm patch was found even if we could not parse it + } + } + else + { + forceLog_printfW(L"Unable to load patch file \"%s\"", path.c_str()); + } + foundPatches = true; + } + } + } + return foundPatches; +} + +void GraphicPack2::LoadPatchFiles() +{ + // order of loading patches: + // 1) If Cemuhook is loaded: + // 1.1) Check if patches.txt exists and if it does, stop here and do nothing (Cemuhook takes over patching) + // 1.2) Load Cemu-style patches (patch_<name>.asm) + // 2) If Cemuhook is not loaded: + // 1.1) Load Cemu-style patches (patch_<name>.asm), stop here if at least one patch file exists + // 1.2) Load Cemuhook patches.txt + + // update: As of 1.20.2b Cemu always takes over patching since Cemuhook patching broke due to other internal changes (memory allocation changed and some reordering on when graphic packs get loaded) + if (LoadCemuPatches()) + return; // exit if at least one Cemu style patch file was found + // fall back to Cemuhook patches.txt to guarantee backward compatibility + fs::path path(m_filename); + path.remove_filename(); + path.append("patches.txt"); + + FileStream* patchFile = FileStream::openFile(path.generic_wstring().c_str()); + + if (patchFile == nullptr) + return; + + // read file + std::vector<uint8> fileData; + patchFile->extract(fileData); + delete patchFile; + + cemu_assert_debug(list_patchGroups.empty()); + + // parse + MemStreamReader patchesStream(fileData.data(), (sint32)fileData.size()); + ParseCemuhookPatchesTxtInternal(patchesStream); +} + +void GraphicPack2::EnablePatches() +{ + std::lock_guard<std::recursive_mutex> lock(mtx_patches); + for (auto& itr : list_modules) + ApplyPatchesForModule(itr); +} + +void GraphicPack2::UnloadPatches() +{ + if (list_patchGroups.empty()) + return; + std::lock_guard<std::recursive_mutex> lock(mtx_patches); + // if any patch groups were applied then revert here + // do this by calling RevertPatchesForModule for every module? + for (auto& itr : list_modules) + RevertPatchesForModule(itr); + // delete all patches + for (auto itr : list_patchGroups) + delete itr; + list_patchGroups.clear(); +} + +bool GraphicPack2::HasPatches() +{ + return !list_patchGroups.empty(); +} + +const std::vector<PatchGroup*>& GraphicPack2::GetPatchGroups() { + return list_patchGroups; +} + +void GraphicPack2::ApplyPatchesForModule(const RPLModule* rpl) +{ + if (list_patchGroups.empty()) + return; + // gather list of all patch groups that apply to this module + std::vector<PatchGroup*> list_groups; + for (auto itr : list_patchGroups) + { + if (itr->matchesCRC(rpl->patchCRC)) + list_groups.emplace_back(itr); + } + // apply all groups at once + if (!list_groups.empty()) + ApplyPatchGroups(list_groups, rpl); +} + +void GraphicPack2::RevertPatchesForModule(const RPLModule* rpl) +{ + if (list_patchGroups.empty()) + return; + // gather list of all patch groups that apply to this module + std::vector<PatchGroup*> list_groups; + for (auto itr : list_patchGroups) + { + if (itr->matchesCRC(rpl->patchCRC)) + list_groups.emplace_back(itr); + } + // undo all groups at once + if (!list_groups.empty()) + UndoPatchGroups(list_groups, rpl); +} + +std::recursive_mutex GraphicPack2::mtx_patches; +std::vector<const RPLModule*> GraphicPack2::list_modules; diff --git a/src/Cafe/GraphicPack/GraphicPack2Patches.h b/src/Cafe/GraphicPack/GraphicPack2Patches.h new file mode 100644 index 00000000..b33cabf9 --- /dev/null +++ b/src/Cafe/GraphicPack/GraphicPack2Patches.h @@ -0,0 +1,262 @@ +#pragma once + +class PatchGroup; + +#include "GraphicPackError.h" + +struct PatchContext_t +{ + struct UnresolvedSymbol + { + sint32 lineNumber; + PatchGroup* patchGroup; + std::string symbolName; + + UnresolvedSymbol(sint32 _lineNumber, PatchGroup* _patchGroup, std::string _symbolName) : lineNumber(_lineNumber), patchGroup(_patchGroup), symbolName(_symbolName) {}; + + bool operator < (const UnresolvedSymbol &other) const + { + if (lineNumber == other.lineNumber) + return symbolName.compare(other.symbolName); + return lineNumber < other.lineNumber; + } + }; + + class GraphicPack2* graphicPack; + //MEMPTR<void> codeCaveStart; + //MEMPTR<void> codeCaveEnd; + const RPLModule* matchedModule; + std::unordered_map<std::string, uint32> map_values; + // error information + //std::unordered_set<std::string> unresolvedSymbols; + std::set<UnresolvedSymbol> unresolvedSymbols; + //std::unordered_multiset<sint32, std::greater<std::string>> unresolvedSymbols; + // error handler + PatchErrorHandler errorHandler{}; +}; + +enum class PATCH_RESOLVE_RESULT +{ + RESOLVED, // successfully resolved any expressions or relocations + EXPRESSION_ERROR, // syntax error in expression (usually this should be detected during the parsing stage already) + VALUE_ERROR, // expression evaluated but the result is not useable (e.g. branch target out of range) + UNKNOWN_VARIABLE, // variable not known or referencing unresolved variable (try again) + VARIABLE_CONFLICT, // a variable or label with the same name was already defined + INVALID_ADDRESS, // attempted to relocate address that is not within any known section + UNDEFINED_ERROR, // unexpected error +}; + +enum class EXPRESSION_RESOLVE_RESULT +{ + AVAILABLE, + EXPRESSION_ERROR, + UNKNOWN_VARIABLE +}; + +class PatchEntry +{ +public: + PatchEntry() {}; + virtual ~PatchEntry() {}; + + // apply relocation or evaluate any expressions for this entry + virtual PATCH_RESOLVE_RESULT resolve(PatchContext_t& ctx) = 0; +}; + +// represents symbol assignment (always treated like an address) +// <symbolName> = <expression> +class PatchEntryCemuhookSymbolValue : public PatchEntry +{ +public: + PatchEntryCemuhookSymbolValue(sint32 lineNumber, const char* symbolName, const sint32 symbolNameLen, const char* expressionStr, const sint32 expressionLen) : PatchEntry(), m_lineNumber(lineNumber) + { + m_symbolName.assign(symbolName, symbolNameLen); + m_expressionString.assign(expressionStr, expressionLen); + } + + sint32 getLineNumber() { return m_lineNumber; } + + PATCH_RESOLVE_RESULT resolve(PatchContext_t& ctx) override; + + std::string& getSymbolName() { return m_symbolName; } + +private: + sint32 m_lineNumber; + std::string m_symbolName; + std::string m_expressionString; + uint32 m_resolvedValue; + bool m_isResolved{}; +}; + +enum class PATCHVARTYPE +{ + DOUBLE, + INT, // 32bit signed integer + UINT, // 32bit unsigned integer or pointer + //BOOL, // boolean +}; + +// represents variable value assignment +// unlike Cemu symbols these are treated as a +// <symbolName> = <expression> +class PatchEntryVariableValue : public PatchEntry +{ +public: + + PatchEntryVariableValue(sint32 lineNumber, const char* symbolName, const sint32 symbolNameLen, PATCHVARTYPE varType, const char* expressionStr, const sint32 expressionLen) : PatchEntry(), m_lineNumber(lineNumber), m_varType(varType) + { + m_symbolName.assign(symbolName, symbolNameLen); + m_expressionString.assign(expressionStr, expressionLen); + } + + sint32 getLineNumber() { return m_lineNumber; } + + PATCH_RESOLVE_RESULT resolve(PatchContext_t& ctx) override; + + std::string& getSymbolName() { return m_symbolName; } + //uint32 getSymbolValue() { return m_resolvedValue; } + +private: + sint32 m_lineNumber; + std::string m_symbolName; + std::string m_expressionString; + PATCHVARTYPE m_varType; + std::variant<sint32, uint32, double> m_resolvedValue; + bool m_isResolved{}; +}; + +// represents a label +class PatchEntryLabel : public PatchEntry +{ +public: + PatchEntryLabel(sint32 lineNumber, const char* symbolName, const sint32 symbolNameLen) : PatchEntry(), m_lineNumber(lineNumber) + { + m_symbolName.assign(symbolName, symbolNameLen); + } + + sint32 getLineNumber() { return m_lineNumber; } + + PATCH_RESOLVE_RESULT resolve(PatchContext_t& ctx) override; + + std::string& getSymbolName() { return m_symbolName; } + uint32 getSymbolValue() { return m_relocatedAddress; } + + void setAssignedVA(uint32 virtualAddress) + { + m_address = virtualAddress; + } + +private: + sint32 m_lineNumber; + std::string m_symbolName; + uint32 m_address; + uint32 m_relocatedAddress; + bool m_isResolved{}; +}; + +// represents assembled code/data +class PatchEntryInstruction : public PatchEntry +{ +public: + PatchEntryInstruction(sint32 lineNumber, uint32 patchAddr, std::span<uint8> data, std::vector<PPCAssemblerReloc>& list_relocs) : PatchEntry(), m_lineNumber(lineNumber), m_addr(patchAddr), m_size((uint32)data.size()), m_relocs(list_relocs) + { + sint32 dataLength = (sint32)data.size(); + m_length = dataLength; + m_data = new uint8[dataLength]; + m_dataWithRelocs = new uint8[dataLength]; + m_dataBackup = new uint8[dataLength]; + memcpy(m_data, data.data(), dataLength); + memcpy(m_dataWithRelocs, data.data(), dataLength); + } + + ~PatchEntryInstruction() + { + if (m_data) + delete[] m_data; + if (m_dataWithRelocs) + delete[] m_dataWithRelocs; + if (m_dataBackup) + delete[] m_dataBackup; + } + + uint32 getAddr() const + { + return m_addr; + } + + uint32 getRelocatedAddr() + { + return m_relocatedAddr; + } + uint32 getSize() const + { + return m_size; + } + + PATCH_RESOLVE_RESULT resolve(PatchContext_t& ctx) override; + PATCH_RESOLVE_RESULT resolveReloc(PatchContext_t& ctx, PPCAssemblerReloc* reloc); + + void applyPatch(); + void undoPatch(); + +private: + uint8* m_data{}; // original unrelocated data + uint8* m_dataWithRelocs{}; // data with relocs applied + uint8* m_dataBackup{}; // original data before patch was applied + sint32 m_length{}; + std::vector<PPCAssemblerReloc> m_relocs; + uint32 m_lineNumber; + uint32 m_addr; + uint32 m_size; + uint32 m_relocatedAddr; + bool m_addrRelocated{}; +}; + +class PatchGroup +{ + friend class GraphicPack2; + +public: + PatchGroup(class GraphicPack2* gp, const char* nameStr, sint32 nameLength) : graphicPack(gp) + { + name = std::string(nameStr, nameLength); + } + + bool matchesCRC(uint32 crc) + { + for (auto& chk : list_moduleMatches) + { + if (chk == crc) + return true; + } + return false; + } + + uint32 getCodeCaveBase() + { + return codeCaveMem.GetMPTR(); + } + + uint32 getCodeCaveSize() + { + return codeCaveSize; + } + + std::string_view getName() + { + return name; + } + + void setApplied() { m_isApplied = true; } + void resetApplied() { m_isApplied = false; } + bool isApplied() const { return m_isApplied; } + +private: + class GraphicPack2* graphicPack; + std::string name; + std::vector<uint32> list_moduleMatches; + std::vector<PatchEntry*> list_patches; + uint32 codeCaveSize; + MEMPTR<void> codeCaveMem; + bool m_isApplied{}; +}; \ No newline at end of file diff --git a/src/Cafe/GraphicPack/GraphicPack2PatchesApply.cpp b/src/Cafe/GraphicPack/GraphicPack2PatchesApply.cpp new file mode 100644 index 00000000..ec94689e --- /dev/null +++ b/src/Cafe/GraphicPack/GraphicPack2PatchesApply.cpp @@ -0,0 +1,753 @@ +#include "Cafe/GraphicPack/GraphicPack2.h" +#include "Common/filestream.h" +#include "Cemu/PPCAssembler/ppcAssembler.h" +#include "Cafe/OS/RPL/rpl_structs.h" +#include "Cafe/OS/RPL/rpl_symbol_storage.h" +#include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h" +#include "Cafe/HW/Espresso/Debugger/DebugSymbolStorage.h" + +bool _relocateAddress(PatchGroup* group, PatchContext_t* ctx, uint32 addr, uint32& relocatedAddress) +{ + if (addr >= 0 && addr <= 1024 * 1024 * 8) + { + // codecave address + relocatedAddress = group->getCodeCaveBase() + addr; + return true; + } + // check if address is within module section + for (sint32 i = 0; i < ctx->matchedModule->rplHeader.sectionTableEntryCount; i++) + { + auto sect = ctx->matchedModule->sectionTablePtr + i; + if (addr >= sect->virtualAddress && addr < (sect->virtualAddress + sect->sectionSize)) + { + relocatedAddress = addr - sect->virtualAddress + memory_getVirtualOffsetFromPointer(ctx->matchedModule->sectionAddressTable2[i].ptr); + return true; + } + } + relocatedAddress = 0; + return false; +} + +struct +{ + bool hasUnknownVariable; + PatchContext_t* activePatchContext; + PatchGroup* currentGroup; + // additional error information tracking + sint32 lineNumber; // line number of the expression being processed, negative if not available + bool captureUnresolvedSymbols; +}resolverState{}; + +bool GraphicPack2::ResolvePresetConstant(const std::string& varname, double& value) const +{ + const auto var = GetPresetVariable(GetActivePresets(), varname); + if (var) + { + value = var->second; + return true; + } + return false; +} + +template<typename T> +T _expressionFuncHA(T input) +{ + uint32 u32 = (uint32)input; + u32 = (((u32 >> 16) + ((u32 & 0x8000) ? 1 : 0)) & 0xffff); + return (T)u32; +} + +template<typename T> +T _expressionFuncHI(T input) +{ + uint32 u32 = (uint32)input; + u32 = (u32 >> 16) & 0xffff; + return (T)u32; +} + +template<typename T> +T _expressionFuncLO(T input) +{ + uint32 u32 = (uint32)input; + u32 &= 0xffff; + return (T)u32; +} + +template<typename T> +T _expressionFuncReloc(T input) +{ + uint32 addr = (uint32)input; + uint32 relocatedAddress = 0; + if(!_relocateAddress(resolverState.currentGroup, resolverState.activePatchContext, addr, relocatedAddress)) + { + resolverState.activePatchContext->errorHandler.printError(resolverState.currentGroup, resolverState.lineNumber, fmt::format("reloc({0:#08x}): Address does not point to a known memory region", addr)); + return (T)0; + } + return (T)relocatedAddress; +} + +double _cbResolveConstant(std::string_view varname) +{ + std::string varnameOnly; + std::string tokenOnly; + // detect suffix + bool hasSuffix = false; + const auto idx = varname.find('@'); + if (idx != std::string_view::npos) + { + hasSuffix = true; + varnameOnly = varname.substr(0, idx); + tokenOnly = varname.substr(idx + 1); + } + else + varnameOnly = varname; + + double value; + if (varnameOnly.length() >= 1 && varnameOnly[0] == '$') + { + // resolve preset variable + if (!resolverState.activePatchContext->graphicPack->ResolvePresetConstant(varnameOnly, value)) + { + resolverState.hasUnknownVariable = true; + if (resolverState.captureUnresolvedSymbols) + resolverState.activePatchContext->unresolvedSymbols.emplace(resolverState.lineNumber, resolverState.currentGroup, varnameOnly); + return 0.0; + } + } + else if (varnameOnly.length() >= 7 && boost::iequals(varnameOnly.substr(0, 7), "import.")) + { + // resolve import + std::string importName = varnameOnly.substr(7); + // detect imports + const auto idxDot = importName.find('.'); + bool isValidImport = false; + std::string_view importError = ""; + if (idxDot != std::string_view::npos) + { + std::string moduleName = importName.substr(0, idxDot); + std::string functionName = importName.substr(idxDot + 1); + uint32 rplHandle = RPLLoader_GetHandleByModuleName(moduleName.c_str()); + if (rplHandle == RPL_INVALID_HANDLE) + { + importError = " (module not found)"; + } + else + { + MPTR exportResult = RPLLoader_FindModuleOrHLEExport(rplHandle, false, functionName.c_str()); + if (exportResult) + { + isValidImport = true; + value = (double)exportResult; + } + else + importError = " (function not found)"; + } + } + else + importError = " (invalid import syntax)"; + // error output + if (!isValidImport) + { + resolverState.hasUnknownVariable = true; + if (resolverState.captureUnresolvedSymbols) + { + std::string detailedSymbolName; + detailedSymbolName.assign(importName); + detailedSymbolName.append(importError); + resolverState.activePatchContext->unresolvedSymbols.emplace(resolverState.lineNumber, resolverState.currentGroup, detailedSymbolName); + } + return 0.0; + } + } + else + { + // resolve variable + const auto v = resolverState.activePatchContext->map_values.find(varnameOnly); + if (v == resolverState.activePatchContext->map_values.end()) + { + resolverState.hasUnknownVariable = true; + if (resolverState.captureUnresolvedSymbols) + resolverState.activePatchContext->unresolvedSymbols.emplace(resolverState.lineNumber, resolverState.currentGroup, varnameOnly); + return 0.0; + } + value = v->second; + } + if (hasSuffix) + { + std::transform(tokenOnly.cbegin(), tokenOnly.cend(), tokenOnly.begin(), tolower); + if (tokenOnly == "ha") + { + value = _expressionFuncHA<double>(value); + } + else if (tokenOnly == "h" || tokenOnly == "hi") + { + value = _expressionFuncHI<double>(value); + } + else if (tokenOnly == "l" || tokenOnly == "lo") + { + value = _expressionFuncLO<double>(value); + } + else + { + // we treat unknown suffixes as unresolveable symbols + resolverState.hasUnknownVariable = true; + if (resolverState.captureUnresolvedSymbols) + { + std::string detailedSymbolName; + detailedSymbolName.assign(varnameOnly); + detailedSymbolName.append("@"); + detailedSymbolName.append(tokenOnly); + detailedSymbolName.append(" (invalid suffix)"); + resolverState.activePatchContext->unresolvedSymbols.emplace(resolverState.lineNumber, resolverState.currentGroup, detailedSymbolName); + } + return 0.0; + } + } + return value; +} + +double _cbResolveFunction(std::string_view funcname, double input) +{ + std::string funcnameLC(funcname); + std::transform(funcnameLC.cbegin(), funcnameLC.cend(), funcnameLC.begin(), tolower); + double value = input; + if (funcnameLC == "ha" || funcnameLC == "ha16") + value = _expressionFuncHA<double>(value); + else if (funcnameLC == "hi" || funcnameLC == "hi16") + value = _expressionFuncHI<double>(value); + else if (funcnameLC == "lo" || funcnameLC == "lo16") + value = _expressionFuncLO<double>(value); + else if (funcnameLC == "reloc") + value = _expressionFuncReloc<double>(value); + else + { + // unresolvable function + resolverState.hasUnknownVariable = true; + if (resolverState.captureUnresolvedSymbols) + { + std::string detailedSymbolName; + detailedSymbolName.assign(funcname); + detailedSymbolName.append("() (unknown function)"); + resolverState.activePatchContext->unresolvedSymbols.emplace(resolverState.lineNumber, resolverState.currentGroup, detailedSymbolName); + } + return 0.0; + } + return value; +} + +template<typename T> +EXPRESSION_RESOLVE_RESULT _resolveExpression(PatchContext_t& ctx, std::string& expressionString, T& result, sint32 associatedLineNumber = -1) +{ + resolverState.lineNumber = associatedLineNumber; + ExpressionParser ep; + try + { + // add all the graphic pack constants + ep.AddConstantCallback(_cbResolveConstant); + ep.SetFunctionCallback(_cbResolveFunction); + resolverState.hasUnknownVariable = false; + result = (T)ep.Evaluate(expressionString); + if (resolverState.hasUnknownVariable) + return EXPRESSION_RESOLVE_RESULT::UNKNOWN_VARIABLE; + } + catch (const std::exception&) + { + cemu_assert_debug(false); + ctx.errorHandler.printError(nullptr, -1, fmt::format("Unexpected error in expression \"{}\"", expressionString)); + return EXPRESSION_RESOLVE_RESULT::EXPRESSION_ERROR; + } + return EXPRESSION_RESOLVE_RESULT::AVAILABLE; +} + +PATCH_RESOLVE_RESULT translateExpressionResult(EXPRESSION_RESOLVE_RESULT expressionResult) +{ + if (expressionResult == EXPRESSION_RESOLVE_RESULT::AVAILABLE) + return PATCH_RESOLVE_RESULT::RESOLVED; + else if (expressionResult == EXPRESSION_RESOLVE_RESULT::EXPRESSION_ERROR) + return PATCH_RESOLVE_RESULT::EXPRESSION_ERROR; + else if (expressionResult == EXPRESSION_RESOLVE_RESULT::UNKNOWN_VARIABLE) + return PATCH_RESOLVE_RESULT::UNKNOWN_VARIABLE; + cemu_assert(false); + return PATCH_RESOLVE_RESULT::EXPRESSION_ERROR; +} + +PATCH_RESOLVE_RESULT PatchEntryInstruction::resolveReloc(PatchContext_t& ctx, PPCAssemblerReloc* reloc) +{ + MPTR finalRelocAddr = m_relocatedAddr + reloc->m_byteOffset; + if (reloc->m_relocType == PPCASM_RELOC::FLOAT) + { + // resolve float expression + float result; + auto r = _resolveExpression<float>(ctx, reloc->m_expression, result, m_lineNumber); + if (r == EXPRESSION_RESOLVE_RESULT::AVAILABLE) + { + cemu_assert((reloc->m_byteOffset + sizeof(betype<float>)) <= m_length); + *(betype<float>*)(m_dataWithRelocs + reloc->m_byteOffset) = result; + DebugSymbolStorage::StoreDataType(finalRelocAddr, DEBUG_SYMBOL_TYPE::FLOAT); + return PATCH_RESOLVE_RESULT::RESOLVED; + } + else + return translateExpressionResult(r); + } + else if (reloc->m_relocType == PPCASM_RELOC::DOUBLE) + { + // resolve double expression + double result; + auto r = _resolveExpression<double>(ctx, reloc->m_expression, result, m_lineNumber); + if (r == EXPRESSION_RESOLVE_RESULT::AVAILABLE) + { + cemu_assert((reloc->m_byteOffset + sizeof(betype<double>)) <= m_length); + *(betype<double>*)(m_dataWithRelocs + reloc->m_byteOffset) = result; + DebugSymbolStorage::StoreDataType(finalRelocAddr, DEBUG_SYMBOL_TYPE::DOUBLE); + return PATCH_RESOLVE_RESULT::RESOLVED; + } + else + return translateExpressionResult(r); + } + else + { + // resolve uint32 expression + uint32 result; + auto r = _resolveExpression<uint32>(ctx, reloc->m_expression, result, m_lineNumber); + if (r != EXPRESSION_RESOLVE_RESULT::AVAILABLE) + return translateExpressionResult(r); + if (reloc->m_relocType == PPCASM_RELOC::U32) + { + cemu_assert((reloc->m_byteOffset + sizeof(betype<uint32>)) <= m_length); + *(betype<uint32>*)(m_dataWithRelocs + reloc->m_byteOffset) = result; + DebugSymbolStorage::StoreDataType(finalRelocAddr, DEBUG_SYMBOL_TYPE::U32); + return PATCH_RESOLVE_RESULT::RESOLVED; + } + else if (reloc->m_relocType == PPCASM_RELOC::U16) + { + cemu_assert((reloc->m_byteOffset + sizeof(betype<uint16>)) <= m_length); + *(betype<uint16>*)(m_dataWithRelocs + reloc->m_byteOffset) = (uint16)result; + DebugSymbolStorage::StoreDataType(finalRelocAddr, DEBUG_SYMBOL_TYPE::U16); + return PATCH_RESOLVE_RESULT::RESOLVED; + } + else if (reloc->m_relocType == PPCASM_RELOC::U8) + { + cemu_assert((reloc->m_byteOffset + sizeof(betype<uint8>)) <= m_length); + *(betype<uint8>*)(m_dataWithRelocs + reloc->m_byteOffset) = (uint8)result; + DebugSymbolStorage::StoreDataType(finalRelocAddr, DEBUG_SYMBOL_TYPE::U8); + return PATCH_RESOLVE_RESULT::RESOLVED; + } + else if (reloc->m_relocType == PPCASM_RELOC::U32_MASKED_IMM) + { + cemu_assert((reloc->m_byteOffset + sizeof(betype<uint32>)) <= m_length); + uint32 opcode = *(betype<uint32>*)(m_dataWithRelocs + reloc->m_byteOffset); + cemu_assert_debug(reloc->m_bitCount != 0); + uint32 mask = 0xFFFFFFFF >> (32 - reloc->m_bitCount); + mask <<= reloc->m_bitOffset; + opcode &= ~mask; + opcode |= ((result << reloc->m_bitOffset) & mask); + *(betype<uint32>*)(m_dataWithRelocs + reloc->m_byteOffset) = opcode; + return PATCH_RESOLVE_RESULT::RESOLVED; + } + else if (reloc->m_relocType == PPCASM_RELOC::BRANCH_S26) + { + cemu_assert((reloc->m_byteOffset + sizeof(betype<uint32>)) <= m_length); + uint32 opcode = *(betype<uint32>*)(m_dataWithRelocs + reloc->m_byteOffset); + if (opcode & 2) + { + // absolute + if (result >= 0x3FFFFFC) + { + forceLog_printf("Target \'%s\' for branch at line %d out of range", reloc->m_expression.c_str(), m_lineNumber); + return PATCH_RESOLVE_RESULT::VALUE_ERROR; + } + opcode &= ~0x3FFFFFC; + opcode |= (result & 0x3FFFFFC); + } + else + { + // relative + uint32 instrAddr = this->getRelocatedAddr() + reloc->m_byteOffset; + if (result < instrAddr) + { + // jump backwards + uint32 jumpB = instrAddr - result; + if (jumpB > 0x1FFFFFF) + { + ctx.errorHandler.printError(nullptr, m_lineNumber, fmt::format("Target \'{0}\' for branch out of range (use MTCTR + BCTR or similar for long distance branches)", reloc->m_expression.c_str())); + return PATCH_RESOLVE_RESULT::VALUE_ERROR; + } + opcode &= ~0x3FFFFFC; + opcode |= ((~jumpB + 1) & 0x3FFFFFC); + } + else + { + // jump forwards + uint32 jumpF = result - instrAddr; + if (jumpF >= 0x1FFFFFF) + { + ctx.errorHandler.printError(nullptr, m_lineNumber, fmt::format("Target \'{0}\' for branch out of range (use MTCTR + BCTR or similar for long distance branches)", reloc->m_expression.c_str())); + return PATCH_RESOLVE_RESULT::VALUE_ERROR; + } + opcode &= ~0x3FFFFFC; + opcode |= (jumpF & 0x3FFFFFC); + } + } + *(betype<uint32>*)(m_dataWithRelocs + reloc->m_byteOffset) = opcode; + return PATCH_RESOLVE_RESULT::RESOLVED; + } + else if (reloc->m_relocType == PPCASM_RELOC::BRANCH_S16) + { + cemu_assert((reloc->m_byteOffset + sizeof(betype<uint32>)) <= m_length); + uint32 opcode = *(betype<uint32>*)(m_dataWithRelocs + reloc->m_byteOffset); + uint32 instrAddr = this->getRelocatedAddr() + reloc->m_byteOffset; + if (result < instrAddr) + { + // jump backwards + uint32 jumpB = instrAddr - result; + if (jumpB > 0x8000) + { + ctx.errorHandler.printError(nullptr, m_lineNumber, fmt::format("Target \'{0}\' for branch out of range (use MTCTR + BCTR or similar for long distance branches)", reloc->m_expression.c_str())); + return PATCH_RESOLVE_RESULT::VALUE_ERROR; + } + opcode &= ~0xFFFC; + opcode |= ((~jumpB + 1) & 0xFFFC); + } + else + { + // jump forwards + uint32 jumpF = result - instrAddr; + if (jumpF >= 0x8000) + { + ctx.errorHandler.printError(nullptr, m_lineNumber, fmt::format("Target \'{0}\' for branch out of range (use MTCTR + BCTR or similar for long distance branches)", reloc->m_expression.c_str())); + return PATCH_RESOLVE_RESULT::VALUE_ERROR; + } + opcode &= ~0xFFFC; + opcode |= (jumpF & 0xFFFC); + } + *(betype<uint32>*)(m_dataWithRelocs + reloc->m_byteOffset) = opcode; + return PATCH_RESOLVE_RESULT::RESOLVED; + } + + + // *internalCtx.opcode |= (relativeAddr & 0xFFFC); + cemu_assert_debug(false); + } + return PATCH_RESOLVE_RESULT::UNDEFINED_ERROR; +} + +PATCH_RESOLVE_RESULT PatchEntryInstruction::resolve(PatchContext_t& ctx) +{ + // relocate patch address + if (!m_addrRelocated) + { + if (_relocateAddress(resolverState.currentGroup, &ctx, m_addr, m_relocatedAddr) == false) + { + forceLog_printf("Patches: Address 0x%08x (line %d) is not within code cave or any module section", this->getAddr(), this->m_lineNumber); + cemu_assert_debug(false); + return PATCH_RESOLVE_RESULT::INVALID_ADDRESS; + } + m_addrRelocated = true; + } + // apply relocations to instruction + for (auto& itr : this->m_relocs) + { + if(itr.isApplied()) + continue; + // evaluate expression and apply reloc to internal buffer + auto r = resolveReloc(ctx, &itr); + if (r == PATCH_RESOLVE_RESULT::RESOLVED) + { + itr.setApplied(); + continue; + } + return r; + } + return PATCH_RESOLVE_RESULT::RESOLVED; +} + +void PatchEntryInstruction::applyPatch() +{ + const uint32 addr = getRelocatedAddr(); + if (addr == 0) + { + cemu_assert_debug(false); + return; + } + uint8* patchAddr = (uint8*)memory_base + addr; + memcpy(m_dataBackup, patchAddr, m_length); + memcpy(patchAddr, m_dataWithRelocs, m_length); + PPCRecompiler_invalidateRange(addr, addr + m_length); +} + +void PatchEntryInstruction::undoPatch() +{ + const uint32 addr = getRelocatedAddr(); + if (addr == 0) + { + cemu_assert_debug(false); + return; + } + uint8* patchAddr = (uint8*)memory_base + addr; + memcpy(patchAddr, m_dataBackup, m_length); + PPCRecompiler_invalidateRange(addr, addr + m_length); + rplSymbolStorage_removeRange(addr, m_length); + DebugSymbolStorage::ClearRange(addr, m_length); +} + +// returns true on success, false if variable with same name already exists +bool registerU32Variable(PatchContext_t& ctx, std::string& name, uint32 value, PatchGroup* associatedPatchGroup, uint32 associatedLineNumber, bool isAddress) +{ + cemuLog_log(LogType::Patches, "Resolved symbol {} with value 0x{:08x}", name.c_str(), value); + if (ctx.map_values.find(name) != ctx.map_values.end()) + { + return false; + } + ctx.map_values[name] = value; + // keep track of address symbols for the debugger + rplSymbolStorage_store(ctx.graphicPack->GetName().data(), name.data(), value); + + return true; +} + +PATCH_RESOLVE_RESULT PatchEntryCemuhookSymbolValue::resolve(PatchContext_t& ctx) +{ + uint32 addr; + auto r = _resolveExpression<uint32>(ctx, m_expressionString, addr, m_lineNumber); + if (r == EXPRESSION_RESOLVE_RESULT::AVAILABLE) + { + if (_relocateAddress(resolverState.currentGroup, &ctx, addr, m_resolvedValue)) + { + m_isResolved = true; + // register variable + if (!registerU32Variable(ctx, m_symbolName, m_resolvedValue, resolverState.currentGroup, getLineNumber(), true)) + { + if (resolverState.captureUnresolvedSymbols) + ctx.errorHandler.printError(resolverState.currentGroup, m_lineNumber, fmt::format("Symbol {} is already defined", m_symbolName)); + return PATCH_RESOLVE_RESULT::VARIABLE_CONFLICT; + } + return PATCH_RESOLVE_RESULT::RESOLVED; + } + return PATCH_RESOLVE_RESULT::INVALID_ADDRESS; + } + return translateExpressionResult(r); +} + +PATCH_RESOLVE_RESULT PatchEntryLabel::resolve(PatchContext_t& ctx) +{ + if (_relocateAddress(resolverState.currentGroup, &ctx, m_address, m_relocatedAddress)) + { + m_isResolved = true; + // register variable + if (!registerU32Variable(ctx, m_symbolName, m_relocatedAddress, resolverState.currentGroup, getLineNumber(), true)) + { + if (resolverState.captureUnresolvedSymbols) + ctx.errorHandler.printError(resolverState.currentGroup, m_lineNumber, fmt::format("Label {} is already defined", m_symbolName)); + return PATCH_RESOLVE_RESULT::VARIABLE_CONFLICT; + } + return PATCH_RESOLVE_RESULT::RESOLVED; + } + if(resolverState.captureUnresolvedSymbols) + ctx.errorHandler.printError(resolverState.currentGroup, m_lineNumber, fmt::format("Address {:#08x} of label {} does not point to any module section or code cave", m_address, m_symbolName)); + return PATCH_RESOLVE_RESULT::INVALID_ADDRESS; +} + +PATCH_RESOLVE_RESULT PatchEntryVariableValue::resolve(PatchContext_t& ctx) +{ + uint32 v; + auto r = _resolveExpression<uint32>(ctx, m_expressionString, v, m_lineNumber); + if (r == EXPRESSION_RESOLVE_RESULT::AVAILABLE) + { + // register variable + if (!registerU32Variable(ctx, m_symbolName, v, resolverState.currentGroup, getLineNumber(), false)) + { + if (resolverState.captureUnresolvedSymbols) + ctx.errorHandler.printError(resolverState.currentGroup, m_lineNumber, fmt::format("Variable {} is already defined", m_symbolName)); + return PATCH_RESOLVE_RESULT::VARIABLE_CONFLICT; + } + return PATCH_RESOLVE_RESULT::RESOLVED; + } + return translateExpressionResult(r); +} + +struct UnresolvedPatches_t +{ + PatchGroup* patchGroup; + std::vector<PatchEntry*> list_unresolvedPatches; +}; + +// returns number of resolved entries +bool _resolverPass(PatchContext_t& patchContext, std::vector<UnresolvedPatches_t>& unresolvedPatches, bool captureUnresolvedSymbols = false) +{ + resolverState.captureUnresolvedSymbols = captureUnresolvedSymbols; + sint32 numResolvedEntries = 0; + for (auto& unresolvedGroup : unresolvedPatches) + { + resolverState.currentGroup = unresolvedGroup.patchGroup; + auto& list_unresolvedPatches = unresolvedGroup.list_unresolvedPatches; + for (auto it = list_unresolvedPatches.begin(); it != list_unresolvedPatches.end();) + { + auto r = (*it)->resolve(patchContext); + if (r == PATCH_RESOLVE_RESULT::RESOLVED) + { + // remove from list + it = list_unresolvedPatches.erase(it); + numResolvedEntries++; + continue; + } + else if (r == PATCH_RESOLVE_RESULT::UNKNOWN_VARIABLE) + { + // dependency on other not yet resolved entry, continue iterating + it++; + continue; + } + else if (r == PATCH_RESOLVE_RESULT::INVALID_ADDRESS || + r == PATCH_RESOLVE_RESULT::VARIABLE_CONFLICT) + { + // errors handled and printed inside resolve() + it++; + continue; + } + else + { + // unknown error + patchContext.errorHandler.printError(resolverState.currentGroup, -1, "Internal error"); + it++; + } + } + } + return numResolvedEntries; +} + +void GraphicPack2::ApplyPatchGroups(std::vector<PatchGroup*>& groups, const RPLModule* rpl) +{ + // init context information + PatchContext_t patchContext{}; + patchContext.graphicPack = this; + patchContext.matchedModule = rpl; + resolverState.activePatchContext = &patchContext; + // setup error handler + patchContext.errorHandler.setCurrentGraphicPack(this); + patchContext.errorHandler.setStage(PatchErrorHandler::STAGE::APPLY); + // no group can be applied more than once + for (auto patchGroup : groups) + { + if (patchGroup->isApplied()) + { + patchContext.errorHandler.printError(patchGroup, -1, "Group already applied to a different module."); + return; + } + } + // allocate code cave for every group + for (auto patchGroup : groups) + { + if (patchGroup->codeCaveSize > 0) + { + auto codeCaveMem = RPLLoader_AllocateCodeCaveMem(256, patchGroup->codeCaveSize); + forceLog_printf("Applying patch group \'%s\' (Codecave: %08x-%08x)", patchGroup->name.c_str(), codeCaveMem.GetMPTR(), codeCaveMem.GetMPTR() + patchGroup->codeCaveSize); + patchGroup->codeCaveMem = codeCaveMem; + } + else + { + forceLog_printf("Applying patch group \'%s\'", patchGroup->name.c_str()); + patchGroup->codeCaveMem = nullptr; + } + } + // resolve the patch entries + // this means: + // - resolving the expressions for variables and registering them + // - calculating relocated addresses + // - applying relocations to temporary patch buffer + + // multiple passes may be necessary since forward and backward references are allowed as well as references across group boundaries + + // create a copy of all the patch references and keep the group association intact + std::vector<UnresolvedPatches_t> unresolvedPatches; + unresolvedPatches.resize(groups.size()); + for (size_t i = 0; i < groups.size(); i++) + { + unresolvedPatches[i].patchGroup = groups[i]; + unresolvedPatches[i].list_unresolvedPatches = groups[i]->list_patches; + } + + auto isUnresolvedPatchesEmpty = [&unresolvedPatches]() + { + for (auto& itr : unresolvedPatches) + if (!itr.list_unresolvedPatches.empty()) + return false; + return true; + }; + // resolve and relocate + for (sint32 pass = 0; pass < 30; pass++) + { + bool isLastPass = (pass == 29); + sint32 numResolvedEntries = _resolverPass(patchContext, unresolvedPatches, false); + if (isUnresolvedPatchesEmpty()) + break; + if (numResolvedEntries == 0 || isLastPass) + { + // stuck due to reference to undefined variable or unresolvable cross-references + // iterate all remaining expressions and output them to log + // execute another resolver pass but capture all the unresolved variables this time + patchContext.unresolvedSymbols.clear(); + _resolverPass(patchContext, unresolvedPatches, true); + // generate messages + if(isLastPass) + patchContext.errorHandler.printError(nullptr, -1, "Some symbols could not be resolved because the dependency chain is too deep"); + for (auto& itr : patchContext.unresolvedSymbols) + patchContext.errorHandler.printError(itr.patchGroup, itr.lineNumber, fmt::format("Unresolved symbol: {}", itr.symbolName)); + patchContext.errorHandler.showStageErrorMessageBox(); + return; + } + } + if (!isUnresolvedPatchesEmpty() || patchContext.errorHandler.hasError()) + { + patchContext.errorHandler.showStageErrorMessageBox(); + return; + } + // apply relocated patches + for (auto patchGroup : groups) + { + for (auto& patch : patchGroup->list_patches) + { + PatchEntryInstruction* patchInstruction = dynamic_cast<PatchEntryInstruction*>(patch); + if (patchInstruction == nullptr) + continue; + patchInstruction->applyPatch(); + } + } + // mark groups as applied + for (auto patchGroup : groups) + patchGroup->setApplied(); +} + +void GraphicPack2::UndoPatchGroups(std::vector<PatchGroup*>& groups, const RPLModule* rpl) +{ + // restore original data + for (auto patchGroup : groups) + { + if (!patchGroup->isApplied()) + continue; + for (auto& patch : patchGroup->list_patches) + { + PatchEntryInstruction* patchInstruction = dynamic_cast<PatchEntryInstruction*>(patch); + if (patchInstruction == nullptr) + continue; + patchInstruction->undoPatch(); + } + } + // mark groups as not applied + for (auto patchGroup : groups) + patchGroup->resetApplied(); +} + +void GraphicPack2::NotifyModuleLoaded(const RPLModule* rpl) +{ + cemuLog_force("Loaded module \'{}\' with checksum 0x{:08x}", rpl->moduleName2, rpl->patchCRC); + + std::lock_guard<std::recursive_mutex> lock(mtx_patches); + list_modules.emplace_back(rpl); + + // todo - iterate all active graphic packs and apply any matching patch groups +} + +void GraphicPack2::NotifyModuleUnloaded(const RPLModule* rpl) +{ + std::lock_guard<std::recursive_mutex> lock(mtx_patches); + list_modules.erase(std::remove(list_modules.begin(), list_modules.end(), rpl), list_modules.end()); +} diff --git a/src/Cafe/GraphicPack/GraphicPack2PatchesParser.cpp b/src/Cafe/GraphicPack/GraphicPack2PatchesParser.cpp new file mode 100644 index 00000000..bb052bc2 --- /dev/null +++ b/src/Cafe/GraphicPack/GraphicPack2PatchesParser.cpp @@ -0,0 +1,532 @@ +#include "Cafe/GraphicPack/GraphicPack2.h" +#include "Common/filestream.h" +#include "util/helpers/StringParser.h" +#include "Cemu/PPCAssembler/ppcAssembler.h" +#include "Cafe/OS/RPL/rpl_structs.h" + +sint32 GraphicPack2::GetLengthWithoutComment(const char* str, size_t length) +{ + sint32 index = 0; + bool isInString = false; + while (index < length) + { + const char c = str[index]; + if (c == '\"') + isInString = !isInString; + else if (c == '#' || c == ';') + { + if (!isInString) + return index; + } + index++; + } + return (sint32)length; +} + +void GraphicPack2::LogPatchesSyntaxError(sint32 lineNumber, std::string_view errorMsg) +{ + cemuLog_log(LogType::Force, fmt::format(L"Syntax error while parsing patch for graphic pack '{}':", this->GetFilename())); + if(lineNumber >= 0) + cemuLog_log(LogType::Force, fmt::format("Line {0}: {1}", lineNumber, errorMsg)); + else + cemuLog_log(LogType::Force, fmt::format("{0}", errorMsg)); + list_patchGroups.clear(); +} + +void GraphicPack2::CancelParsingPatches() +{ + // unload everything, set error flag + cemu_assert_debug(false); +} + +void GraphicPack2::AddPatchGroup(PatchGroup* group) +{ + if (group->list_moduleMatches.empty()) + { + LogPatchesSyntaxError(-1, fmt::format("Group \"{}\" has no moduleMatches definition", group->name)); + CancelParsingPatches(); + delete group; + return; + } + // calculate code cave size + uint32 codeCaveMaxAddr = 0; + for (auto& itr : group->list_patches) + { + PatchEntryInstruction* patchData = dynamic_cast<PatchEntryInstruction*>(itr); + if (patchData) + { + uint32 patchAddr = patchData->getAddr(); + if (patchAddr < 0x00100000) + { + // everything in low 1MB of memory we consider part of the code cave + codeCaveMaxAddr = std::max(codeCaveMaxAddr, patchAddr + patchData->getSize()); + } + } + + } + uint32 numEstimatedCodeCaveInstr = codeCaveMaxAddr / 4; + if (group->list_patches.size() < (numEstimatedCodeCaveInstr / 8)) + { + // if less than 1/8th of the code cave is filled print a warning + forceLog_printf("Graphic pack patches: Code cave for group [%s] in gfx pack \"%s\" ranges from 0 to 0x%x but has only few instructions. Is this intentional?", group->name.c_str(), this->m_name.c_str(), codeCaveMaxAddr); + } + group->codeCaveSize = codeCaveMaxAddr; + list_patchGroups.emplace_back(group); +} + +void GraphicPack2::ParseCemuhookPatchesTxtInternal(MemStreamReader& patchesStream) +{ + sint32 lineNumber = 0; + PatchGroup* currentGroup = nullptr; + while (true) + { + auto lineStr = patchesStream.readLine(); + lineNumber++; + if (patchesStream.hasError()) + break; + // trim comment + size_t lineLength = GetLengthWithoutComment(lineStr.data(), lineStr.size()); + + StringTokenParser parser(lineStr.data(), (sint32)lineLength); + + // skip whitespaces at the beginning + parser.skipWhitespaces(); + // parse line + if (parser.isEndOfString()) + continue; + if (parser.compareCharacter(0, '[')) + { + // group + parser.skipCharacters(1); + // find end of group name + const char* groupNameStr = parser.getCurrentPtr(); + sint32 groupNameLength = parser.skipToCharacter(']'); + if (groupNameLength < 0) + { + + LogPatchesSyntaxError(lineNumber, "Expected ']'"); + CancelParsingPatches(); + return; + } + parser.skipCharacters(1); // skip the ']' + parser.skipWhitespaces(); + if (!parser.isEndOfString()) + { + LogPatchesSyntaxError(lineNumber, "Unexpected characters after ']'"); + CancelParsingPatches(); + return; + } + // begin new group + if (currentGroup) + { + AddPatchGroup(currentGroup); + } + currentGroup = new PatchGroup(this, groupNameStr, groupNameLength); + } + else if (parser.compareCharacter(0, '0') && parser.compareCharacterI(1, 'x')) + { + // if the line starts with a hex address then it is a patched location + uint32 patchedAddress; + if (!parser.parseU32(patchedAddress)) + { + LogPatchesSyntaxError(lineNumber, "Malformed address"); + CancelParsingPatches(); + return; + } + if (parser.matchWordI("=") == false) + { + LogPatchesSyntaxError(lineNumber, "Expected '=' after address"); + CancelParsingPatches(); + return; + } + parser.skipWhitespaces(); + parser.trimWhitespaces(); + // assemble instruction + std::string instrText(parser.getCurrentPtr(), parser.getCurrentLen()); + PPCAssemblerInOut ctx{}; + ctx.virtualAddress = patchedAddress; + if (!ppcAssembler_assembleSingleInstruction(instrText.c_str(), &ctx)) + { + LogPatchesSyntaxError(lineNumber, fmt::format("Error in assembler: {}", ctx.errorMsg)); + CancelParsingPatches(); + return; + } + currentGroup->list_patches.emplace_back(new PatchEntryInstruction(lineNumber, patchedAddress, { ctx.outputData.data(), ctx.outputData.size() }, ctx.list_relocs)); + } + else if (parser.matchWordI("moduleMatches")) + { + if (currentGroup == nullptr) + { + LogPatchesSyntaxError(lineNumber, "Specified 'ModuleMatches' outside of a group"); + CancelParsingPatches(); + return; + } + if (parser.matchWordI("=") == false) + { + LogPatchesSyntaxError(lineNumber, "Expected '=' after ModuleMatches"); + CancelParsingPatches(); + return; + } + // read the checksums + while (true) + { + uint32 checksum = 0; + if (parser.parseU32(checksum) == false) + { + LogPatchesSyntaxError(lineNumber, "Invalid value for ModuleMatches"); + CancelParsingPatches(); + return; + } + currentGroup->list_moduleMatches.emplace_back(checksum); + if (parser.matchWordI(",") == false) + break; + } + parser.skipWhitespaces(); + if (!parser.isEndOfString()) + { + LogPatchesSyntaxError(lineNumber, "Unexpected character in line"); + CancelParsingPatches(); + return; + } + continue; + } + else + { + // Cemuhook requires that user defined symbols start with _ but we are more lenient and allow them to start with letters too + // the downside is that there is some ambiguity and parsing gets a little bit more complex + + // check for <symbolName> = pattern + StringTokenParser bakParser; + const char* symbolStr; + sint32 symbolLen; + parser.storeParserState(&bakParser); + if (parser.parseSymbolName(symbolStr, symbolLen) && parser.matchWordI("=")) + { + // matches pattern: <symbolName> = ... + parser.skipWhitespaces(); + parser.trimWhitespaces(); + const char* expressionStr = parser.getCurrentPtr(); + sint32 expressionLen = parser.getCurrentLen(); + // create entry for symbol value assignment + currentGroup->list_patches.emplace_back(new PatchEntryCemuhookSymbolValue(lineNumber, symbolStr, symbolLen, expressionStr, expressionLen)); + continue; + } + else + { + LogPatchesSyntaxError(lineNumber, fmt::format("Invalid syntax")); + CancelParsingPatches(); + return; + } + } + } + if (currentGroup) + AddPatchGroup(currentGroup); +} + +static inline uint32 INVALID_ORIGIN = 0xFFFFFFFF; + +bool GraphicPack2::ParseCemuPatchesTxtInternal(MemStreamReader& patchesStream) +{ + sint32 lineNumber = 0; + PatchGroup* currentGroup = nullptr; + + struct + { + void reset() + { + currentOrigin = INVALID_ORIGIN; + codeCaveOrigin = 0; + } + + void setOrigin(uint32 origin) + { + currentOrigin = origin; + } + + void setOriginCodeCave() + { + currentOrigin = codeCaveOrigin; + } + + bool isValidOrigin() + { + return currentOrigin != INVALID_ORIGIN; + } + + void incrementOrigin(uint32 size) + { + currentOrigin += size; + if (currentOrigin <= 32 * 1024 * 1024) + codeCaveOrigin = std::max(codeCaveOrigin, currentOrigin); + } + + uint32 currentOrigin{}; + uint32 codeCaveOrigin{}; + }originInfo; + // labels dont get emitted immediately, instead they are assigned a VA after the next alignment zone + std::vector<PatchEntryLabel*> scheduledLabels; + // this is to prevent code like this from putting alignment bytes after the label. (The label 'sticks' to the data after it) + // .byte 123 + // Label: + // BLR + + auto flushLabels = [&]() + { + // flush remaining labels + for (auto& itr : scheduledLabels) + { + itr->setAssignedVA(originInfo.currentOrigin); + currentGroup->list_patches.emplace_back(itr); + } + scheduledLabels.clear(); + }; + + while (true) + { + size_t lineLength; + auto lineStr = patchesStream.readLine(); + lineNumber++; + if (patchesStream.hasError()) + break; + // trim comment + lineLength = GetLengthWithoutComment(lineStr.data(), lineStr.size()); + + StringTokenParser parser(lineStr.data(), (sint32)lineLength); + + // skip whitespaces at the beginning + parser.skipWhitespaces(); + // parse line + if (parser.isEndOfString()) + continue; + if (parser.compareCharacter(0, '[')) + { + // group + parser.skipCharacters(1); + // find end of group name + const char* groupNameStr = parser.getCurrentPtr(); + sint32 groupNameLength = parser.skipToCharacter(']'); + if (groupNameLength < 0) + { + LogPatchesSyntaxError(lineNumber, "Expected ']'"); + CancelParsingPatches(); + return false; + } + parser.skipCharacters(1); // skip the ']' + parser.skipWhitespaces(); + if (!parser.isEndOfString()) + { + LogPatchesSyntaxError(lineNumber, "Unexpected characters after ']'"); + CancelParsingPatches(); + return false; + } + // begin new group + if (currentGroup) + { + flushLabels(); + AddPatchGroup(currentGroup); + } + currentGroup = new PatchGroup(this, groupNameStr, groupNameLength); + // reset origin tracking + originInfo.reset(); + continue; + } + else if (parser.matchWordI("moduleMatches")) + { + if (currentGroup == nullptr) + { + LogPatchesSyntaxError(lineNumber, "Specified 'ModuleMatches' outside of a group"); + CancelParsingPatches(); + return false; + } + if (parser.matchWordI("=") == false) + { + LogPatchesSyntaxError(lineNumber, "Expected '=' after ModuleMatches"); + CancelParsingPatches(); + return false; + } + // read the checksums + while (true) + { + uint32 checksum = 0; + if (parser.parseU32(checksum) == false) + { + LogPatchesSyntaxError(lineNumber, "Invalid value for ModuleMatches"); + CancelParsingPatches(); + return false; + } + currentGroup->list_moduleMatches.emplace_back(checksum); + if (parser.matchWordI(",") == false) + break; + } + parser.skipWhitespaces(); + if (!parser.isEndOfString()) + { + LogPatchesSyntaxError(lineNumber, "Unexpected character"); + CancelParsingPatches(); + return false; + } + continue; + } + + // if a line starts with <hex_address> = then it temporarily overwrites the origin for the current line + uint32 overwriteOrigin = INVALID_ORIGIN; + if (parser.compareCharacter(0, '0') && parser.compareCharacterI(1, 'x')) + { + uint32 patchedAddress; + if (!parser.parseU32(patchedAddress)) + { + LogPatchesSyntaxError(lineNumber, "Malformed address"); + CancelParsingPatches(); + return false; + } + if (parser.matchWordI("=") == false) + { + LogPatchesSyntaxError(lineNumber, "Expected '=' after address"); + CancelParsingPatches(); + return false; + } + parser.skipWhitespaces(); + parser.trimWhitespaces(); + overwriteOrigin = patchedAddress; + } + // check for known directives + if (parser.matchWordI(".origin")) + { + // .origin = <origin> directive + if (overwriteOrigin != INVALID_ORIGIN) + { + LogPatchesSyntaxError(lineNumber, fmt::format(".origin directive must appear alone without <address> = prefix.")); + CancelParsingPatches(); + return false; + } + if (!parser.matchWordI("=")) + { + LogPatchesSyntaxError(lineNumber, fmt::format("Missing '=' after .origin")); + CancelParsingPatches(); + return false; + } + // parse origin + uint32 originAddress; + if (parser.matchWordI("codecave")) + { + // keyword codecave means we set the origin to the end of the current known codecave size + originInfo.setOriginCodeCave(); + } + else if(parser.parseU32(originAddress)) + { + // hex address + originInfo.setOrigin(originAddress); + } + else + { + LogPatchesSyntaxError(lineNumber, fmt::format("\'.origin =\' must be followed by the keyword codecave or a valid address")); + CancelParsingPatches(); + return false; + } + continue; + } + + // next we attempt to parse symbol assignment + // symbols can be labels or variables. The type is determined by what comes after the symbol name + // <symbolName> = <expression> defines a variable + // <symbolName>: defines a label + + StringTokenParser bakParser; + const char* symbolStr; + sint32 symbolLen; + parser.storeParserState(&bakParser); + + // check for pattern <symbolName>: + if (parser.parseSymbolName(symbolStr, symbolLen) && parser.matchWordI(":")) + { + // label + parser.skipWhitespaces(); + if (!parser.isEndOfString()) + { + LogPatchesSyntaxError(lineNumber, fmt::format("Unexpected characters after label")); + CancelParsingPatches(); + return false; + } + uint32 labelAddress; + if (overwriteOrigin != INVALID_ORIGIN) + labelAddress = overwriteOrigin; + else + { + if (!originInfo.isValidOrigin()) + { + LogPatchesSyntaxError(lineNumber, fmt::format("Defined label has no address assigned or there is no active .origin")); + CancelParsingPatches(); + return false; + } + labelAddress = originInfo.currentOrigin; + } + if (overwriteOrigin == INVALID_ORIGIN) + { + // if label is part of code flow, delay emitting it until the next data instruction + // this is so we can avoid generating alignment padding, whose size is unknown in advance, between labels and data instructions + scheduledLabels.emplace_back(new PatchEntryLabel(lineNumber, symbolStr, symbolLen)); + } + else + { + PatchEntryLabel* patchLabel = new PatchEntryLabel(lineNumber, symbolStr, symbolLen); + patchLabel->setAssignedVA(labelAddress); + currentGroup->list_patches.emplace_back(patchLabel); + } + continue; + } + parser.restoreParserState(&bakParser); + // check for pattern <symbolName> = + if (parser.parseSymbolName(symbolStr, symbolLen) && parser.matchWordI("=")) + { + // variable definition + parser.skipWhitespaces(); + parser.trimWhitespaces(); + const char* expressionStr = parser.getCurrentPtr(); + sint32 expressionLen = parser.getCurrentLen(); + // create entry for symbol/variable value assignment + currentGroup->list_patches.emplace_back(new PatchEntryVariableValue(lineNumber, symbolStr, symbolLen, PATCHVARTYPE::UINT, expressionStr, expressionLen)); + continue; + } + // if all patterns mismatch then we assume it's an assembly instruction + parser.restoreParserState(&bakParser); + std::string instrText(parser.getCurrentPtr(), parser.getCurrentLen()); + PPCAssemblerInOut ctx{}; + ctx.forceNoAlignment = overwriteOrigin != INVALID_ORIGIN; // dont auto-align when a fixed address is assigned + if (overwriteOrigin != INVALID_ORIGIN) + ctx.virtualAddress = overwriteOrigin; + else if(originInfo.isValidOrigin()) + ctx.virtualAddress = originInfo.currentOrigin; + else + { + LogPatchesSyntaxError(lineNumber, fmt::format("Trying to assemble line but no address specified. (Declare .origin or prefix line with <address> = )")); + CancelParsingPatches(); + return false; + } + if (!ppcAssembler_assembleSingleInstruction(instrText.c_str(), &ctx)) + { + LogPatchesSyntaxError(lineNumber, fmt::format("Error in assembler: {}", ctx.errorMsg)); + CancelParsingPatches(); + return false; + } + cemu_assert_debug(ctx.alignmentRequirement != 0); + if (overwriteOrigin == INVALID_ORIGIN) + { + originInfo.incrementOrigin((sint32)ctx.alignmentPaddingSize); // alignment padding + originInfo.incrementOrigin((sint32)ctx.outputData.size()); // instruction size + } + // flush labels + for (auto& itr : scheduledLabels) + { + itr->setAssignedVA(ctx.virtualAddressAligned); + currentGroup->list_patches.emplace_back(itr); + } + scheduledLabels.clear(); + // append instruction + currentGroup->list_patches.emplace_back(new PatchEntryInstruction(lineNumber, ctx.virtualAddressAligned, { ctx.outputData.data(), ctx.outputData.size() }, ctx.list_relocs)); + } + flushLabels(); + + if (currentGroup) + AddPatchGroup(currentGroup); + return true; +} diff --git a/src/Cafe/GraphicPack/GraphicPackError.h b/src/Cafe/GraphicPack/GraphicPackError.h new file mode 100644 index 00000000..91cdb130 --- /dev/null +++ b/src/Cafe/GraphicPack/GraphicPackError.h @@ -0,0 +1,32 @@ +#pragma once + +class PatchErrorHandler +{ +public: + enum class STAGE + { + PARSER, + APPLY, + }; + + + void setCurrentGraphicPack(class GraphicPack2* gp) + { + m_gp = gp; + } + + void setStage(STAGE s) + { + m_stage = s; + } + + void printError(class PatchGroup* patchGroup, sint32 lineNumber, std::string_view errorMsg); + void showStageErrorMessageBox(); + + bool hasError() const { return m_anyErrorTriggered; }; + + class GraphicPack2* m_gp{}; + bool m_anyErrorTriggered{}; + STAGE m_stage{ STAGE::PARSER }; + std::vector<std::string> errorMessages; // if patch logging is enabled also remember error msgs for the message box +}; diff --git a/src/Cafe/HW/ACR/ACR.cpp b/src/Cafe/HW/ACR/ACR.cpp new file mode 100644 index 00000000..b8a48a8a --- /dev/null +++ b/src/Cafe/HW/ACR/ACR.cpp @@ -0,0 +1,68 @@ +#include "Cafe/HW/MMU/MMU.h" +#include "Cafe/HW/Common/HwReg.h" + +namespace HW_ACR +{ + + struct + { + HWREG::ACR_VI_ADDR viAddr; + HWREG::ACR_VI_CTRL viCtrl; + }g_acr; + + /* + Is this some kind of VI emulation interface? + Pattern seen in Twilight Princess HD: + - If Hollywood hardware then read/write old 16bit GC VI register directly + - Otherwise these steps are performed: + VICONTROL |= 1 + VIADDR = registerIndex + VIDATA = data + VICONTROL &= ~1 + All the register accesses here are 32bit + */ + + /* 0x0D00021C | Accesses VI register currently selected by VIADDR */ + HWREG::ACR_VI_DATA ACR_VIDATA_R32(PAddr addr) + { + forceLogDebug_printf("ACR_VIDATA read with selected reg %08x", g_acr.viAddr.get_ADDR()); + return HWREG::ACR_VI_DATA(); + } + + void ACR_VIDATA_W32(PAddr addr, HWREG::ACR_VI_DATA newValue) + { + forceLogDebug_printf("ACR_VIDATA write %08x with selected reg %08x", newValue.get_DATA(), g_acr.viAddr.get_ADDR()); + } + + /* 0x0D000224 | Controls the selected VI register? */ + HWREG::ACR_VI_ADDR ACR_VIADDR_R32(PAddr addr) + { + return g_acr.viAddr; + } + + void ACR_VIADDR_W32(PAddr addr, HWREG::ACR_VI_ADDR newValue) + { + g_acr.viAddr = newValue; + } + + /* 0x0D000228 | Some kind of VI interface control? */ + HWREG::ACR_VI_CTRL ACR_VICONTROL_R32(PAddr addr) + { + return g_acr.viCtrl; + } + + void ACR_VICONTROL_W32(PAddr addr, HWREG::ACR_VI_CTRL newValue) + { + g_acr.viCtrl = newValue; + } + + RunAtCemuBoot _initACR([]() + { + MMU::RegisterMMIO_32<HWREG::ACR_VI_DATA, ACR_VIDATA_R32, ACR_VIDATA_W32>(MMU::MMIOInterface::INTERFACE_0D000000, 0x21C); + MMU::RegisterMMIO_32<HWREG::ACR_VI_ADDR, ACR_VIADDR_R32, ACR_VIADDR_W32>(MMU::MMIOInterface::INTERFACE_0D000000, 0x224); + MMU::RegisterMMIO_32<HWREG::ACR_VI_CTRL, ACR_VICONTROL_R32, ACR_VICONTROL_W32>(MMU::MMIOInterface::INTERFACE_0D000000, 0x228); + + // init + }); + +} diff --git a/src/Cafe/HW/AI/AI.cpp b/src/Cafe/HW/AI/AI.cpp new file mode 100644 index 00000000..ffd63a0e --- /dev/null +++ b/src/Cafe/HW/AI/AI.cpp @@ -0,0 +1,19 @@ +#include "Cafe/HW/MMU/MMU.h" + + +namespace HW_AI +{ + + void AI_STATUS_W16(uint32 addr, uint16 value) + { + + } + + RunAtCemuBoot _init([]() + { + //using MMIOFuncWrite16 = void (*)(uint32 addr, uint16 value); + //using MMIOFuncWrite32 = void (*)(uint32 addr, uint32 value); + + //void RegisterMMIO_W16(MMIOFuncWrite16 ptr); + }); +} diff --git a/src/Cafe/HW/AI/AI.h b/src/Cafe/HW/AI/AI.h new file mode 100644 index 00000000..e69de29b diff --git a/src/Cafe/HW/Common/HwReg.h b/src/Cafe/HW/Common/HwReg.h new file mode 100644 index 00000000..88515901 --- /dev/null +++ b/src/Cafe/HW/Common/HwReg.h @@ -0,0 +1,212 @@ +#pragma once + +namespace HWREG +{ + +#define REGISTER_FULL_FIELD(__regname) \ +auto& set_##__regname(uint32 newValue) \ +{ \ + v = newValue; \ + return *this; \ +} \ +uint32 get_##__regname() const \ +{ \ + return v; \ +} + +#define REGISTER_BITFIELD(__regname, __bitIndex, __bitWidth) \ +auto& set_##__regname(uint32 newValue) \ +{ \ + cemu_assert_debug(newValue < (1u << (__bitWidth))); \ + v &= ~((((1u << (__bitWidth)) - 1u) << (__bitIndex))); \ + v |= (newValue << (__bitIndex)); \ + return *this; \ +} \ +uint32 get_##__regname() const \ +{ \ + return (v >> (__bitIndex))&((1u << (__bitWidth)) - 1u); \ +} + +#define REGISTER_BITFIELD_SIGNED(__regname, __bitIndex, __bitWidth) \ +auto& set_##__regname(sint32 newValue) \ +{ \ + cemu_assert_debug(newValue < (1 << ((__bitWidth)-1))); \ + cemu_assert_debug(newValue >= -(1 << ((__bitWidth)-1))); \ + v &= ~((((1u << (__bitWidth)) - 1u) << (__bitIndex))); \ + v |= (((uint32)newValue & ((1u << (__bitWidth)) - 1u)) << (__bitIndex)); \ + return *this; \ +} \ +sint32 get_##__regname() const \ +{ \ + sint32 r = (v >> (__bitIndex))&((1u << (__bitWidth)) - 1u); \ + r = (r << (32 - (__bitWidth))); \ + r = (r >> (32 - (__bitWidth))); \ + return r; \ +} + +#define REGISTER_BITFIELD_BOOL(__regname, __bitIndex) \ +auto& set_##__regname(bool newValue) \ +{ \ + if(newValue) \ + v |= (1u << (__bitIndex)); \ + else \ + v &= ~(1u << (__bitIndex)); \ + return *this; \ +} \ +bool get_##__regname() const \ +{ \ + return (v&(1u << (__bitIndex))) != 0; \ +} + +#define REGISTER_BITFIELD_TYPED(__regname, __bitIndex, __bitWidth, __typename) \ +auto& set_##__regname(__typename newValue) \ +{ \ + cemu_assert_debug(static_cast<uint32>(newValue) < (1u << (__bitWidth))); \ + v &= ~((((1u << (__bitWidth)) - 1u) << (__bitIndex))); \ + v |= (static_cast<uint32>(newValue) << (__bitIndex)); \ + return *this; \ +} \ +__typename get_##__regname() const \ +{ \ + return static_cast<__typename>((v >> (__bitIndex))&((1u << (__bitWidth)) - 1u)); \ +} + +#define REGISTER_BITFIELD_FLOAT(__regname) \ +auto& set_##__regname(float newValue) \ +{ \ + *(float*)&v = newValue; \ + return *this; \ +} \ +float get_##__regname() const \ +{ \ + return *(float*)&v; \ +} + + class HWREG + { + public: + uint32 getRawValue() const + { + return v; + } + + uint32 getRawValueBE() const + { + return _swapEndianU32(v); + } + + void setFromRaw(uint32 regValue) + { + v = regValue; + } + + protected: + uint32 v{}; + }; + + /* ACR */ + + struct ACR_VI_DATA : HWREG // 0x0D00021C - official name unknown + { + REGISTER_FULL_FIELD(DATA); + }; + + struct ACR_VI_ADDR : HWREG // 0x0D000224 - official name unknown + { + REGISTER_FULL_FIELD(ADDR); + }; + + struct ACR_VI_CTRL : HWREG // 0x0D000228 - official name unknown + { + REGISTER_BITFIELD_BOOL(HAS_OWNERSHIP, 0); // exact purpose not understood + // other fields unknown + }; + + + /* SI */ + + struct SICOUTBUF : HWREG // 0x6400/0x640C/0x6418/0x6424 + { + REGISTER_BITFIELD(OUTPUT1, 0, 8); + REGISTER_BITFIELD(OUTPUT0, 8, 8); + REGISTER_BITFIELD(CMD, 16, 8); + + }; + + struct SICINBUFH : HWREG // 0x6404/0x6410/0x641C/0x6428 + { + REGISTER_BITFIELD(INPUT3, 0, 8); + REGISTER_BITFIELD(INPUT2, 8, 8); + REGISTER_BITFIELD(INPUT1, 16, 8); + REGISTER_BITFIELD(INPUT0, 24, 6); + REGISTER_BITFIELD(ERRLATCH, 30, 1); + REGISTER_BITFIELD(ERRSTAT, 31, 1); + }; + + struct SICINBUFL : HWREG // 0x6408/0x6414/0x6420/0x642C + { + REGISTER_BITFIELD(INPUT4, 0, 8); + REGISTER_BITFIELD(INPUT5, 8, 8); + REGISTER_BITFIELD(INPUT6, 16, 8); + REGISTER_BITFIELD(INPUT7, 24, 8); + }; + + struct SIPOLL : HWREG // 0x6430 + { + REGISTER_BITFIELD(X, 16, 10); + REGISTER_BITFIELD(Y, 8, 8); + REGISTER_BITFIELD(EN, 4, 4); + REGISTER_BITFIELD(VBCPY, 0, 4); + }; + + struct SICOMCSR : HWREG // 0x6434 + { + REGISTER_BITFIELD(TCINT, 31, 1); + REGISTER_BITFIELD(TCINTMASK, 30, 1); + REGISTER_BITFIELD(COMERR, 29, 1); + REGISTER_BITFIELD(RDSTINT, 28, 1); + REGISTER_BITFIELD(RDSTINTMSK, 27, 1); + REGISTER_BITFIELD(UKN_CHANNEL_NUM_MAYBE, 25, 2); + REGISTER_BITFIELD(CHANNELENABLE, 24, 1); + REGISTER_BITFIELD(OUTLNGTH, 16, 7); + REGISTER_BITFIELD(INLNGTH, 8, 7); + REGISTER_BITFIELD(COMMAND_ENABLE, 7, 1); + REGISTER_BITFIELD(CALLBACK_ENABLE, 6, 1); + REGISTER_BITFIELD(CHANNEL, 1, 2); + REGISTER_BITFIELD(TRANSFER_START, 0, 1); + }; + + struct SISR : HWREG // 0x6438 + { + REGISTER_BITFIELD(WR, 31, 1); + // joy-channel 0 + REGISTER_BITFIELD(RDST0, 29, 1); + REGISTER_BITFIELD(WRST0, 28, 1); + REGISTER_BITFIELD(NOREP0, 27, 1); + REGISTER_BITFIELD(COLL0, 26, 1); + REGISTER_BITFIELD(OVRUN0, 25, 1); + REGISTER_BITFIELD(UNRUN0, 24, 1); + // joy-channel 1 + REGISTER_BITFIELD(RDST1, 21, 1); + REGISTER_BITFIELD(WRST1, 20, 1); + REGISTER_BITFIELD(NOREP1, 19, 1); + REGISTER_BITFIELD(COLL1, 18, 1); + REGISTER_BITFIELD(OVRUN1, 17, 1); + REGISTER_BITFIELD(UNRUN1, 16, 1); + // joy-channel 2 + REGISTER_BITFIELD(RDST2, 13, 1); + REGISTER_BITFIELD(WRST2, 12, 1); + REGISTER_BITFIELD(NOREP2, 11, 1); + REGISTER_BITFIELD(COLL2, 10, 1); + REGISTER_BITFIELD(OVRUN2, 9, 1); + REGISTER_BITFIELD(UNRUN2, 8, 1); + // joy-channel 3 + REGISTER_BITFIELD(RDST3, 5, 1); + REGISTER_BITFIELD(WRST3, 4, 1); + REGISTER_BITFIELD(NOREP3, 3, 1); + REGISTER_BITFIELD(COLL3, 2, 1); + REGISTER_BITFIELD(OVRUN3, 1, 1); + REGISTER_BITFIELD(UNRUN3, 0, 1); + }; + +} \ No newline at end of file diff --git a/src/Cafe/HW/Espresso/Const.h b/src/Cafe/HW/Espresso/Const.h new file mode 100644 index 00000000..88a1a9de --- /dev/null +++ b/src/Cafe/HW/Espresso/Const.h @@ -0,0 +1,11 @@ +#pragma once + +namespace Espresso +{ + constexpr inline int CORE_COUNT = 3; + + constexpr inline uint64 CORE_CLOCK = 1243125000; + constexpr inline uint64 BUS_CLOCK = 248625000; + constexpr inline uint64 TIMER_CLOCK = BUS_CLOCK / 4; + +}; \ No newline at end of file diff --git a/src/Cafe/HW/Espresso/Debugger/DebugSymbolStorage.cpp b/src/Cafe/HW/Espresso/Debugger/DebugSymbolStorage.cpp new file mode 100644 index 00000000..4f603583 --- /dev/null +++ b/src/Cafe/HW/Espresso/Debugger/DebugSymbolStorage.cpp @@ -0,0 +1,5 @@ +#include "Common/precompiled.h" +#include "DebugSymbolStorage.h" + +FSpinlock DebugSymbolStorage::s_lock; +std::unordered_map<MPTR, DEBUG_SYMBOL_TYPE> DebugSymbolStorage::s_typeStorage; diff --git a/src/Cafe/HW/Espresso/Debugger/DebugSymbolStorage.h b/src/Cafe/HW/Espresso/Debugger/DebugSymbolStorage.h new file mode 100644 index 00000000..0d7b7d49 --- /dev/null +++ b/src/Cafe/HW/Espresso/Debugger/DebugSymbolStorage.h @@ -0,0 +1,63 @@ +#pragma once +#include "util/helpers/fspinlock.h" + +enum class DEBUG_SYMBOL_TYPE +{ + UNDEFINED, + CODE, + // big-endian types + U64, + U32, + U16, + U8, + S64, + S32, + S16, + S8, + FLOAT, + DOUBLE, +}; + + +class DebugSymbolStorage +{ +public: + static void StoreDataType(MPTR address, DEBUG_SYMBOL_TYPE type) + { + s_lock.acquire(); + s_typeStorage[address] = type; + s_lock.release(); + } + + static DEBUG_SYMBOL_TYPE GetDataType(MPTR address) + { + s_lock.acquire(); + auto itr = s_typeStorage.find(address); + if (itr == s_typeStorage.end()) + { + s_lock.release(); + return DEBUG_SYMBOL_TYPE::UNDEFINED; + } + DEBUG_SYMBOL_TYPE t = itr->second; + s_lock.release(); + return t; + } + + static void ClearRange(MPTR address, uint32 length) + { + s_lock.acquire(); + while (length > 0) + { + auto itr = s_typeStorage.find(address); + if (itr != s_typeStorage.end()) + s_typeStorage.erase(itr); + address += 4; + length -= 4; + } + s_lock.release(); + } + +private: + static FSpinlock s_lock; + static std::unordered_map<MPTR, DEBUG_SYMBOL_TYPE> s_typeStorage; +}; \ No newline at end of file diff --git a/src/Cafe/HW/Espresso/Debugger/Debugger.cpp b/src/Cafe/HW/Espresso/Debugger/Debugger.cpp new file mode 100644 index 00000000..51417633 --- /dev/null +++ b/src/Cafe/HW/Espresso/Debugger/Debugger.cpp @@ -0,0 +1,573 @@ +#include "gui/guiWrapper.h" +#include "Debugger.h" +#include "Cemu/PPCAssembler/ppcAssembler.h" +#include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h" +#include "Cemu/ExpressionParser/ExpressionParser.h" + +#include "gui/debugger/DebuggerWindow2.h" + +#include "Cafe/OS/libs/coreinit/coreinit.h" + +#if BOOST_OS_WINDOWS > 0 +#include <Windows.h> +#endif + +debuggerState_t debuggerState{ }; + +DebuggerBreakpoint* debugger_getFirstBP(uint32 address) +{ + for (auto& it : debuggerState.breakpoints) + { + if (it->address == address) + return it; + } + return nullptr; +} + +DebuggerBreakpoint* debugger_getFirstBP(uint32 address, uint8 bpType) +{ + for (auto& it : debuggerState.breakpoints) + { + if (it->address == address) + { + DebuggerBreakpoint* bpItr = it; + while (bpItr) + { + if (bpItr->bpType == bpType) + return bpItr; + bpItr = bpItr->next; + } + return nullptr; + } + } + return nullptr; +} + +bool debuggerBPChain_hasType(DebuggerBreakpoint* bp, uint8 bpType) +{ + while (bp) + { + if (bp->bpType == bpType) + return true; + bp = bp->next; + } + return false; +} + +void debuggerBPChain_add(uint32 address, DebuggerBreakpoint* bp) +{ + bp->next = nullptr; + DebuggerBreakpoint* existingBP = debugger_getFirstBP(address); + if (existingBP) + { + while (existingBP->next) + existingBP = existingBP->next; + existingBP->next = bp; + return; + } + // no breakpoint chain exists for this address + debuggerState.breakpoints.push_back(bp); +} + +uint32 debugger_getAddressOriginalOpcode(uint32 address) +{ + auto bpItr = debugger_getFirstBP(address); + while (bpItr) + { + if (bpItr->bpType == DEBUGGER_BP_T_NORMAL || bpItr->bpType == DEBUGGER_BP_T_ONE_SHOT) + return bpItr->originalOpcodeValue; + bpItr = bpItr->next; + } + return memory_readU32(address); +} + +void debugger_updateMemoryU32(uint32 address, uint32 newValue) +{ + bool memChanged = false; + if (newValue != memory_readU32(address)) + memChanged = true; + memory_writeU32(address, newValue); + if(memChanged) + PPCRecompiler_invalidateRange(address, address + 4); +} + +void debugger_updateExecutionBreakpoint(uint32 address, bool forceRestore) +{ + auto bpItr = debugger_getFirstBP(address); + bool hasBP = false; + uint32 originalOpcodeValue; + while (bpItr) + { + if (bpItr->isExecuteBP()) + { + if (bpItr->enabled && forceRestore == false) + { + // write TW instruction to memory + debugger_updateMemoryU32(address, (31 << 26) | (4 << 1)); + return; + } + else + { + originalOpcodeValue = bpItr->originalOpcodeValue; + hasBP = true; + } + } + bpItr = bpItr->next; + } + if (hasBP) + { + // restore instruction + debugger_updateMemoryU32(address, originalOpcodeValue); + } +} + +void debugger_createExecuteBreakpoint(uint32 address) +{ + // check if breakpoint already exists + auto existingBP = debugger_getFirstBP(address); + if (existingBP && debuggerBPChain_hasType(existingBP, DEBUGGER_BP_T_NORMAL)) + return; // breakpoint already exists + // get original opcode at address + uint32 originalOpcode = debugger_getAddressOriginalOpcode(address); + // init breakpoint object + DebuggerBreakpoint* bp = new DebuggerBreakpoint(address, originalOpcode, DEBUGGER_BP_T_NORMAL, true); + debuggerBPChain_add(address, bp); + debugger_updateExecutionBreakpoint(address); +} + +void debugger_createSingleShotExecuteBreakpoint(uint32 address) +{ + // check if breakpoint already exists + auto existingBP = debugger_getFirstBP(address); + if (existingBP && debuggerBPChain_hasType(existingBP, DEBUGGER_BP_T_ONE_SHOT)) + return; // breakpoint already exists + // get original opcode at address + uint32 originalOpcode = debugger_getAddressOriginalOpcode(address); + // init breakpoint object + DebuggerBreakpoint* bp = new DebuggerBreakpoint(address, originalOpcode, DEBUGGER_BP_T_ONE_SHOT, true); + debuggerBPChain_add(address, bp); + debugger_updateExecutionBreakpoint(address); +} + +namespace coreinit +{ + std::vector<std::thread::native_handle_type>& OSGetSchedulerThreads(); +} + +void debugger_updateMemoryBreakpoint(DebuggerBreakpoint* bp) +{ + std::vector<std::thread::native_handle_type> schedulerThreadHandles = coreinit::OSGetSchedulerThreads(); + +#if BOOST_OS_WINDOWS > 0 + debuggerState.activeMemoryBreakpoint = bp; + for (auto& hThreadNH : schedulerThreadHandles) + { + HANDLE hThread = (HANDLE)hThreadNH; + CONTEXT ctx{}; + ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS; + SuspendThread(hThread); + GetThreadContext(hThread, &ctx); + if (debuggerState.activeMemoryBreakpoint) + { + ctx.Dr0 = (DWORD64)memory_getPointerFromVirtualOffset(bp->address); + ctx.Dr1 = (DWORD64)memory_getPointerFromVirtualOffset(bp->address); + ctx.Dr7 = 1 | (1 << 16) | (3 << 18); // enable dr0, track write, 4 byte length + ctx.Dr7 |= (4 | (3 << 20) | (3 << 22)); // enable dr1, track read+write, 4 byte length + } + else + { + ctx.Dr0 = (DWORD64)0; + ctx.Dr1 = (DWORD64)0; + ctx.Dr7 = 0; // disable dr0 + } + SetThreadContext(hThread, &ctx); + ResumeThread(hThread); + } + #else + cemuLog_log(LogType::Force, "Debugger breakpoints are not supported"); + #endif +} + +void debugger_handleSingleStepException(uint32 drMask) +{ + bool triggeredDR0 = (drMask & (1 << 0)) != 0; // write + bool triggeredDR1 = (drMask & (1 << 1)) != 0; // read + bool catchBP = false; + if (triggeredDR0 && triggeredDR1) + { + // write (and read) access + if (debuggerState.activeMemoryBreakpoint && debuggerState.activeMemoryBreakpoint->bpType == DEBUGGER_BP_T_MEMORY_WRITE) + catchBP = true; + } + else + { + // read access + if (debuggerState.activeMemoryBreakpoint && debuggerState.activeMemoryBreakpoint->bpType == DEBUGGER_BP_T_MEMORY_READ) + catchBP = true; + } + if (catchBP) + { + debugger_createSingleShotExecuteBreakpoint(ppcInterpreterCurrentInstance->instructionPointer + 4); + } +} + +void debugger_createMemoryBreakpoint(uint32 address, bool onRead, bool onWrite) +{ + // init breakpoint object + uint8 bpType; + if (onRead && onWrite) + assert_dbg(); + else if (onRead) + bpType = DEBUGGER_BP_T_MEMORY_READ; + else + bpType = DEBUGGER_BP_T_MEMORY_WRITE; + + DebuggerBreakpoint* bp = new DebuggerBreakpoint(address, 0xFFFFFFFF, bpType, true); + debuggerBPChain_add(address, bp); + // disable any already existing memory breakpoint + if (debuggerState.activeMemoryBreakpoint) + { + debuggerState.activeMemoryBreakpoint->enabled = false; + debuggerState.activeMemoryBreakpoint = nullptr; + } + // activate new breakpoint + debugger_updateMemoryBreakpoint(bp); +} + +void debugger_handleEntryBreakpoint(uint32 address) +{ + if (!debuggerState.breakOnEntry) + return; + + debugger_createExecuteBreakpoint(address); +} + +void debugger_deleteBreakpoint(DebuggerBreakpoint* bp) +{ + for (auto& it : debuggerState.breakpoints) + { + if (it->address == bp->address) + { + // for execution breakpoints make sure the instruction is restored + if (bp->isExecuteBP()) + { + bp->enabled = false; + debugger_updateExecutionBreakpoint(bp->address); + } + // remove + if (it == bp) + { + // remove first in list + debuggerState.breakpoints.erase(std::remove(debuggerState.breakpoints.begin(), debuggerState.breakpoints.end(), bp), debuggerState.breakpoints.end()); + DebuggerBreakpoint* nextBP = bp->next; + if (nextBP) + debuggerState.breakpoints.push_back(nextBP); + } + else + { + // remove from list + DebuggerBreakpoint* bpItr = it; + while (bpItr->next != bp) + { + bpItr = bpItr->next; + } + cemu_assert_debug(bpItr->next != bp); + bpItr->next = bp->next; + } + delete bp; + return; + } + } +} + +void debugger_toggleExecuteBreakpoint(uint32 address) +{ + auto existingBP = debugger_getFirstBP(address, DEBUGGER_BP_T_NORMAL); + if (existingBP) + { + // delete existing breakpoint + debugger_deleteBreakpoint(existingBP); + return; + } + // create new + debugger_createExecuteBreakpoint(address); +} + +void debugger_forceBreak() +{ + debuggerState.debugSession.shouldBreak = true; +} + +bool debugger_isTrapped() +{ + return debuggerState.debugSession.isTrapped; +} + +void debugger_resume() +{ + // if there is a breakpoint on the current instruction then do a single 'step into' to skip it + debuggerState.debugSession.run = true; +} + +void debugger_toggleBreakpoint(uint32 address, bool state, DebuggerBreakpoint* bp) +{ + DebuggerBreakpoint* bpItr = debugger_getFirstBP(address); + while (bpItr) + { + if (bpItr == bp) + { + if (bpItr->bpType == DEBUGGER_BP_T_NORMAL) + { + bp->enabled = state; + debugger_updateExecutionBreakpoint(address); + debuggerWindow_updateViewThreadsafe2(); + } + else if (bpItr->isMemBP()) + { + // disable other memory breakpoints + for (auto& it : debuggerState.breakpoints) + { + DebuggerBreakpoint* bpItr2 = it; + while (bpItr2) + { + if (bpItr2->isMemBP() && bpItr2 != bp) + { + bpItr2->enabled = false; + } + bpItr2 = bpItr2->next; + } + } + bpItr->enabled = state; + if (state) + debugger_updateMemoryBreakpoint(bpItr); + else + debugger_updateMemoryBreakpoint(nullptr); + debuggerWindow_updateViewThreadsafe2(); + } + return; + } + bpItr = bpItr->next; + } +} + +void debugger_createPatch(uint32 address, std::span<uint8> patchData) +{ + DebuggerPatch* patch = new DebuggerPatch(); + patch->address = address; + patch->length = patchData.size(); + patch->data.resize(4); + patch->origData.resize(4); + memcpy(&patch->data.front(), patchData.data(), patchData.size()); + memcpy(&patch->origData.front(), memory_getPointerFromVirtualOffset(address), patchData.size()); + // get original data from breakpoints + if ((address & 3) != 0) + cemu_assert_debug(false); + for (sint32 i = 0; i < patchData.size() / 4; i++) + { + DebuggerBreakpoint* bpItr = debugger_getFirstBP(address); + while (bpItr) + { + if (bpItr->isExecuteBP()) + { + *(uint32*)(&patch->origData.front() + i * 4) = _swapEndianU32(bpItr->originalOpcodeValue); + } + bpItr = bpItr->next; + } + } + // merge with existing patches if the ranges touch + for(sint32 i=0; i<debuggerState.patches.size(); i++) + { + auto& patchItr = debuggerState.patches[i]; + if (address + patchData.size() >= patchItr->address && address <= patchItr->address + patchItr->length) + { + uint32 newAddress = std::min(patch->address, patchItr->address); + uint32 newEndAddress = std::max(patch->address + patch->length, patchItr->address + patchItr->length); + uint32 newLength = newEndAddress - newAddress; + + DebuggerPatch* newPatch = new DebuggerPatch(); + newPatch->address = newAddress; + newPatch->length = newLength; + newPatch->data.resize(newLength); + newPatch->origData.resize(newLength); + memcpy(&newPatch->data.front() + (address - newAddress), &patch->data.front(), patch->length); + memcpy(&newPatch->data.front() + (patchItr->address - newAddress), &patchItr->data.front(), patchItr->length); + + memcpy(&newPatch->origData.front() + (address - newAddress), &patch->origData.front(), patch->length); + memcpy(&newPatch->origData.front() + (patchItr->address - newAddress), &patchItr->origData.front(), patchItr->length); + + delete patch; + patch = newPatch; + delete patchItr; + // remove currently iterated patch + debuggerState.patches.erase(debuggerState.patches.begin()+i); + i--; + } + } + debuggerState.patches.push_back(patch); + // apply patch (if breakpoints exist then update those instead of actual data) + if ((address & 3) != 0) + cemu_assert_debug(false); + if ((patchData.size() & 3) != 0) + cemu_assert_debug(false); + for (sint32 i = 0; i < patchData.size() / 4; i++) + { + DebuggerBreakpoint* bpItr = debugger_getFirstBP(address); + bool hasActiveExecuteBP = false; + while (bpItr) + { + if (bpItr->isExecuteBP()) + { + bpItr->originalOpcodeValue = *(uint32be*)(patchData.data() + i * 4); + if (bpItr->enabled) + hasActiveExecuteBP = true; + } + bpItr = bpItr->next; + } + if (hasActiveExecuteBP == false) + { + memcpy(memory_getPointerFromVirtualOffset(address + i * 4), patchData.data() + i * 4, 4); + PPCRecompiler_invalidateRange(address, address + 4); + } + } +} + +bool debugger_hasPatch(uint32 address) +{ + for (auto& patch : debuggerState.patches) + { + if (address + 4 > patch->address && address < patch->address + patch->length) + return true; + } + return false; +} + +void debugger_stepInto(PPCInterpreter_t* hCPU, bool updateDebuggerWindow = true) +{ + bool isRecEnabled = ppcRecompilerEnabled; + ppcRecompilerEnabled = false; + uint32 initialIP = debuggerState.debugSession.instructionPointer; + debugger_updateExecutionBreakpoint(initialIP, true); + PPCInterpreterSlim_executeInstruction(hCPU); + debugger_updateExecutionBreakpoint(initialIP); + debuggerState.debugSession.instructionPointer = hCPU->instructionPointer; + if(updateDebuggerWindow) + debuggerWindow_moveIP(); + ppcRecompilerEnabled = isRecEnabled; +} + +bool debugger_stepOver(PPCInterpreter_t* hCPU) +{ + bool isRecEnabled = ppcRecompilerEnabled; + ppcRecompilerEnabled = false; + // disassemble current instruction + PPCDisassembledInstruction disasmInstr = { 0 }; + uint32 initialIP = debuggerState.debugSession.instructionPointer; + debugger_updateExecutionBreakpoint(initialIP, true); + ppcAssembler_disassemble(initialIP, memory_readU32(initialIP), &disasmInstr); + if (disasmInstr.ppcAsmCode != PPCASM_OP_BL && + disasmInstr.ppcAsmCode != PPCASM_OP_BCTRL) + { + // nothing to skip, use step-into + debugger_stepInto(hCPU); + debugger_updateExecutionBreakpoint(initialIP); + debuggerWindow_moveIP(); + ppcRecompilerEnabled = isRecEnabled; + return false; + } + // create one-shot breakpoint at next instruction + debugger_createSingleShotExecuteBreakpoint(initialIP +4); + // step over current instruction (to avoid breakpoint) + debugger_stepInto(hCPU); + debuggerWindow_moveIP(); + // restore breakpoints + debugger_updateExecutionBreakpoint(initialIP); + // run + ppcRecompilerEnabled = isRecEnabled; + return true; +} + +void debugger_createPPCStateSnapshot(PPCInterpreter_t* hCPU) +{ + memcpy(debuggerState.debugSession.ppcSnapshot.gpr, hCPU->gpr, sizeof(uint32) * 32); + memcpy(debuggerState.debugSession.ppcSnapshot.fpr, hCPU->fpr, sizeof(FPR_t) * 32); + debuggerState.debugSession.ppcSnapshot.spr_lr = hCPU->spr.LR; + for (uint32 i = 0; i < 32; i++) + debuggerState.debugSession.ppcSnapshot.cr[i] = hCPU->cr[i]; +} + +void debugger_enterTW(PPCInterpreter_t* hCPU) +{ + debuggerState.debugSession.isTrapped = true; + debuggerState.debugSession.debuggedThreadMPTR = coreinitThread_getCurrentThreadMPTRDepr(hCPU); + debuggerState.debugSession.instructionPointer = hCPU->instructionPointer; + debuggerState.debugSession.hCPU = hCPU; + debugger_createPPCStateSnapshot(hCPU); + // remove one-shot breakpoint if it exists + DebuggerBreakpoint* singleshotBP = debugger_getFirstBP(debuggerState.debugSession.instructionPointer, DEBUGGER_BP_T_ONE_SHOT); + if (singleshotBP) + debugger_deleteBreakpoint(singleshotBP); + debuggerWindow_notifyDebugBreakpointHit2(); + debuggerWindow_updateViewThreadsafe2(); + // reset step control + debuggerState.debugSession.stepInto = false; + debuggerState.debugSession.stepOver = false; + debuggerState.debugSession.run = false; + while (true) + { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + // check for step commands + if (debuggerState.debugSession.stepOver) + { + if (debugger_stepOver(hCPU)) + { + debugger_createPPCStateSnapshot(hCPU); + break; // if true is returned, continue with execution + } + debugger_createPPCStateSnapshot(hCPU); + debuggerWindow_updateViewThreadsafe2(); + debuggerState.debugSession.stepOver = false; + } + if (debuggerState.debugSession.stepInto) + { + debugger_stepInto(hCPU); + debugger_createPPCStateSnapshot(hCPU); + debuggerWindow_updateViewThreadsafe2(); + debuggerState.debugSession.stepInto = false; + continue; + } + if (debuggerState.debugSession.run) + { + debugger_createPPCStateSnapshot(hCPU); + debugger_stepInto(hCPU, false); + PPCInterpreterSlim_executeInstruction(hCPU); + debuggerState.debugSession.instructionPointer = hCPU->instructionPointer; + debuggerState.debugSession.run = false; + break; + } + } + + debuggerState.debugSession.isTrapped = false; + debuggerState.debugSession.hCPU = nullptr; + debuggerWindow_updateViewThreadsafe2(); + debuggerWindow_notifyRun(); +} + +void debugger_shouldBreak(PPCInterpreter_t* hCPU) +{ + if(debuggerState.debugSession.shouldBreak + // exclude emulator trampoline area + && (hCPU->instructionPointer < MEMORY_CODE_TRAMPOLINE_AREA_ADDR || hCPU->instructionPointer > MEMORY_CODE_TRAMPOLINE_AREA_ADDR + MEMORY_CODE_TRAMPOLINE_AREA_SIZE)) + { + debuggerState.debugSession.shouldBreak = false; + + const uint32 address = (uint32)hCPU->instructionPointer; + assert_dbg(); + //debugger_createBreakpoint(address, DEBUGGER_BP_TYPE_ONE_SHOT); + } +} + +void debugger_addParserSymbols(class ExpressionParser& ep) +{ + for (sint32 i = 0; i < 32; i++) + ep.AddConstant(fmt::format("r{}", i), debuggerState.debugSession.ppcSnapshot.gpr[i]); +} \ No newline at end of file diff --git a/src/Cafe/HW/Espresso/Debugger/Debugger.h b/src/Cafe/HW/Espresso/Debugger/Debugger.h new file mode 100644 index 00000000..3bb90bea --- /dev/null +++ b/src/Cafe/HW/Espresso/Debugger/Debugger.h @@ -0,0 +1,125 @@ +#pragma once + +#include <set> +#include "Cafe/HW/Espresso/PPCState.h" + +//#define DEBUGGER_BP_TYPE_NORMAL (1<<0) // normal breakpoint +//#define DEBUGGER_BP_TYPE_ONE_SHOT (1<<1) // normal breakpoint +//#define DEBUGGER_BP_TYPE_MEMORY_READ (1<<2) // memory breakpoint +//#define DEBUGGER_BP_TYPE_MEMORY_WRITE (1<<3) // memory breakpoint + +#define DEBUGGER_BP_T_NORMAL 0 // normal breakpoint +#define DEBUGGER_BP_T_ONE_SHOT 1 // normal breakpoint, deletes itself after trigger (used for stepping) +#define DEBUGGER_BP_T_MEMORY_READ 2 // memory breakpoint +#define DEBUGGER_BP_T_MEMORY_WRITE 3 // memory breakpoint + +#define DEBUGGER_BP_T_GDBSTUB 1 // breakpoint created by GDBStub +#define DEBUGGER_BP_T_DEBUGGER 2 // breakpoint created by Cemu's debugger + + +struct DebuggerBreakpoint +{ + uint32 address; + uint32 originalOpcodeValue; + mutable uint8 bpType; + mutable bool enabled; + mutable std::wstring comment; + mutable uint8 dbType = DEBUGGER_BP_T_DEBUGGER; + + DebuggerBreakpoint(uint32 address, uint32 originalOpcode, uint8 bpType = 0, bool enabled = true, std::wstring comment = std::wstring()) + :address(address), originalOpcodeValue(originalOpcode), bpType(bpType), enabled(enabled), comment(std::move(comment)) + { + next = nullptr; + } + + + bool operator<(const DebuggerBreakpoint& rhs) const + { + return address < rhs.address; + } + bool operator==(const DebuggerBreakpoint& rhs) const + { + return address == rhs.address; + } + + bool isExecuteBP() const + { + return bpType == DEBUGGER_BP_T_NORMAL || bpType == DEBUGGER_BP_T_ONE_SHOT; + } + + bool isMemBP() const + { + return bpType == DEBUGGER_BP_T_MEMORY_READ || bpType == DEBUGGER_BP_T_MEMORY_WRITE; + } + + DebuggerBreakpoint* next; +}; + +struct DebuggerPatch +{ + uint32 address; + sint32 length; + std::vector<uint8> data; + std::vector<uint8> origData; +}; + +struct PPCSnapshot +{ + uint32 gpr[32]; + FPR_t fpr[32]; + uint8 cr[32]; + uint32 spr_lr; +}; + +typedef struct +{ + bool breakOnEntry; + // breakpoints + std::vector<DebuggerBreakpoint*> breakpoints; + std::vector<DebuggerPatch*> patches; + DebuggerBreakpoint* activeMemoryBreakpoint; + // debugging state + struct + { + volatile bool shouldBreak; // debugger window requests a break asap + volatile bool isTrapped; // if set, breakpoint is active and stepping through the code is possible + uint32 debuggedThreadMPTR; + volatile uint32 instructionPointer; + PPCInterpreter_t* hCPU; + // step control + volatile bool stepOver; + volatile bool stepInto; + volatile bool run; + // snapshot of PPC state + PPCSnapshot ppcSnapshot; + }debugSession; + +}debuggerState_t; + +extern debuggerState_t debuggerState; + +// new API +DebuggerBreakpoint* debugger_getFirstBP(uint32 address); +void debugger_toggleExecuteBreakpoint(uint32 address); // create/remove execute breakpoint +void debugger_createExecuteBreakpoint(uint32 address); +void debugger_toggleBreakpoint(uint32 address, bool state, DebuggerBreakpoint* bp); + +void debugger_createMemoryBreakpoint(uint32 address, bool onRead, bool onWrite); + +void debugger_handleEntryBreakpoint(uint32 address); + +void debugger_deleteBreakpoint(DebuggerBreakpoint* bp); + +void debugger_updateExecutionBreakpoint(uint32 address, bool forceRestore = false); + +void debugger_createPatch(uint32 address, std::span<uint8> patchData); +bool debugger_hasPatch(uint32 address); + +void debugger_forceBreak(); // force breakpoint at the next possible instruction +bool debugger_isTrapped(); +void debugger_resume(); + +void debugger_enterTW(PPCInterpreter_t* hCPU); +void debugger_shouldBreak(PPCInterpreter_t* hCPU); + +void debugger_addParserSymbols(class ExpressionParser& ep); \ No newline at end of file diff --git a/src/Cafe/HW/Espresso/EspressoISA.h b/src/Cafe/HW/Espresso/EspressoISA.h new file mode 100644 index 00000000..4c630e4c --- /dev/null +++ b/src/Cafe/HW/Espresso/EspressoISA.h @@ -0,0 +1,197 @@ +#pragma once + +namespace Espresso +{ + enum CR_BIT + { + CR_BIT_INDEX_LT = 0, + CR_BIT_INDEX_GT = 1, + CR_BIT_INDEX_EQ = 2, + CR_BIT_INDEX_SO = 3, + }; + + enum class PrimaryOpcode + { + // underscore at the end of the name means that this instruction always updates CR0 (as if RC bit is set) + ZERO = 0, + VIRTUAL_HLE = 1, + + // 3 = TWI + GROUP_4 = 4, + MULLI = 7, + SUBFIC = 8, + CMPLI = 10, + CMPI = 11, + ADDIC = 12, + ADDIC_ = 13, + ADDI = 14, + ADDIS = 15, + BC = 16, // conditional branch + GROUP_17 = 17, // SC + B = 18, // unconditional branch + GROUP_19 = 19, + RLWIMI = 20, + RLWINM = 21, + // 22 ? + RLWNM = 23, + ORI = 24, + ORIS = 25, + XORI = 26, + XORIS = 27, + ANDI_ = 28, + ANDIS_ = 29, + GROUP_31 = 31, + LWZ = 32, + LWZU = 33, + LBZ = 34, + LBZU = 35, + STW = 36, + STWU = 37, + STB = 38, + STBU = 39, + LHZ = 40, + LHZU = 41, + LHA = 42, + LHAU = 43, + STH = 44, + STHU = 45, + LMW = 46, + STMW = 47, + LFS = 48, + LFSU = 49, + LFD = 50, + LFDU = 51, + STFS = 52, + STFSU = 53, + STFD = 54, + STFDU = 55, + PSQ_L = 56, + PSQ_LU = 57, + // 58 ? + GROUP_59 = 59, + PSQ_ST = 60, + PSQ_STU = 61, + // 62 ? + GROUP_63 = 63, + }; + + enum class Opcode19 + { + MCRF = 0, + BCLR = 16, + CRNOR = 33, + RFI = 50, + CRANDC = 129, + ISYNC = 150, + CRXOR = 193, + CRAND = 257, + CREQV = 289, + CRORC = 417, + CROR = 449, + BCCTR = 528 + }; + + enum class OPCODE_31 + { + + }; + + inline PrimaryOpcode GetPrimaryOpcode(uint32 opcode) { return (PrimaryOpcode)(opcode >> 26); }; + inline Opcode19 GetGroup19Opcode(uint32 opcode) { return (Opcode19)((opcode >> 1) & 0x3FF); }; + + struct BOField + { + BOField() {}; + BOField(uint8 bo) : bo(bo) {}; + + bool conditionInverted() const + { + return (bo & 8) == 0; + } + + bool decrementerIgnore() const + { + return (bo & 4) != 0; + } + + bool decrementerMustBeZero() const + { + return (bo & 2) != 0; + } + + bool conditionIgnore() const + { + return (bo & 16) != 0; + } + + bool branchAlways() + { + return conditionIgnore() && decrementerIgnore(); + } + + uint8 bo; + }; + + inline void _decodeForm_I(uint32 opcode, uint32& LI, bool& AA, bool& LK) + { + LI = opcode & 0x3fffffc; + if (LI & 0x02000000) + LI |= 0xfc000000; + AA = (opcode & 2) != 0; + LK = (opcode & 1) != 0; + } + + inline void _decodeForm_D_branch(uint32 opcode, uint32& BD, BOField& BO, uint32& BI, bool& AA, bool& LK) + { + BD = opcode & 0xfffc; + if (BD & 0x8000) + BD |= 0xffff0000; + BO = { (uint8)((opcode >> 21) & 0x1F) }; + BI = (opcode >> 16) & 0x1F; + AA = (opcode & 2) != 0; + LK = (opcode & 1) != 0; + } + + inline void _decodeForm_D_SImm(uint32 opcode, uint32& rD, uint32& rA, uint32& imm) + { + rD = (opcode >> 21) & 0x1F; + rA = (opcode >> 16) & 0x1F; + imm = (uint32)(sint32)(sint16)(opcode & 0xFFFF); + } + + inline void _decodeForm_XL(uint32 opcode, BOField& BO, uint32& BI, bool& LK) + { + BO = { (uint8)((opcode >> 21) & 0x1F) }; + BI = (opcode >> 16) & 0x1F; + LK = (opcode & 1) != 0; + } + + inline void decodeOp_ADDI(uint32 opcode, uint32& rD, uint32& rA, uint32& imm) + { + _decodeForm_D_SImm(opcode, rD, rA, imm); + } + + inline void decodeOp_B(uint32 opcode, uint32& LI, bool& AA, bool& LK) + { + // form I + _decodeForm_I(opcode, LI, AA, LK); + } + + inline void decodeOp_BC(uint32 opcode, uint32& BD, BOField& BO, uint32& BI, bool& AA, bool& LK) + { + // form D + _decodeForm_D_branch(opcode, BD, BO, BI, AA, LK); + } + + inline void decodeOp_BCLR(uint32 opcode, BOField& BO, uint32& BI, bool& LK) + { + // form XL (with BD field expected to be zero) + _decodeForm_XL(opcode, BO, BI, LK); + } + + inline void decodeOp_BCCTR(uint32 opcode, BOField& BO, uint32& BI, bool& LK) + { + // form XL (with BD field expected to be zero) + _decodeForm_XL(opcode, BO, BI, LK); + } +} diff --git a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterALU.hpp b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterALU.hpp new file mode 100644 index 00000000..a3e45679 --- /dev/null +++ b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterALU.hpp @@ -0,0 +1,978 @@ + +static void PPCInterpreter_setXerOV(PPCInterpreter_t* hCPU, bool hasOverflow) +{ + if (hasOverflow) + { + hCPU->spr.XER |= XER_SO; + hCPU->spr.XER |= XER_OV; + } + else + { + hCPU->spr.XER &= ~XER_OV; + } +} + +static bool checkAdditionOverflow(uint32 x, uint32 y, uint32 r) +{ + // todo - update remaining *O instructions to use this function + + /* + x y r result (has overflow) + 0 0 0 0 + 1 0 0 0 + 0 1 0 0 + 1 1 0 1 + 0 0 1 1 + 1 0 1 0 + 0 1 1 0 + 1 1 1 0 + + */ + return (((x ^ r) & (y ^ r)) >> 31) != 0; +} + +static void PPCInterpreter_ADD(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL3_XO(); + hCPU->gpr[rD] = (int)hCPU->gpr[rA] + (int)hCPU->gpr[rB]; + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rD]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_ADDO(PPCInterpreter_t* hCPU, uint32 opcode) +{ + // untested (Don't Starve Giant Edition uses this instruction + BSO) + PPC_OPC_TEMPL3_XO(); + uint64 result = (uint64)hCPU->gpr[rA] + (uint64)hCPU->gpr[rB]; + hCPU->gpr[rD] = (uint32)result; + if (result >= 0x100000000ULL) + { + hCPU->spr.XER |= XER_SO; + hCPU->spr.XER |= XER_OV; + } + else + { + hCPU->spr.XER &= ~XER_OV; + } + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rD]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_ADDC(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL3_XO(); + uint32 a = hCPU->gpr[rA]; + hCPU->gpr[rD] = a + hCPU->gpr[rB]; + if (hCPU->gpr[rD] < a) + hCPU->xer_ca = 1; + else + hCPU->xer_ca = 0; + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rD]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_ADDCO(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL3_XO(); + uint32 a = hCPU->gpr[rA]; + uint32 b = hCPU->gpr[rB]; + hCPU->gpr[rD] = a + b; + if (hCPU->gpr[rD] < a) + hCPU->xer_ca = 1; + else + hCPU->xer_ca = 0; + // set SO/OV + if (hCPU->gpr[rD] < a) + { + hCPU->spr.XER |= XER_OV; + hCPU->spr.XER |= XER_SO; + } + else + hCPU->spr.XER &= ~XER_OV; + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rD]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_ADDE(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL3_XO(); + uint32 a = hCPU->gpr[rA]; + uint32 b = hCPU->gpr[rB]; + uint32 ca = hCPU->xer_ca; + hCPU->gpr[rD] = a + b + ca; + // update xer + if (ppc_carry_3(a, b, ca)) + hCPU->xer_ca = 1; + else + hCPU->xer_ca = 0; + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rD]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_ADDEO(PPCInterpreter_t* hCPU, uint32 opcode) +{ + // used by DS Virtual Console (Super Mario 64 DS) + PPC_OPC_TEMPL3_XO(); + uint32 a = hCPU->gpr[rA]; + uint32 b = hCPU->gpr[rB]; + uint32 ca = hCPU->xer_ca; + hCPU->gpr[rD] = a + b + ca; + // update xer carry + if (ppc_carry_3(a, b, ca)) + hCPU->xer_ca = 1; + else + hCPU->xer_ca = 0; + PPCInterpreter_setXerOV(hCPU, checkAdditionOverflow(a, b, hCPU->gpr[rD])); + // update CR + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rD]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_ADDI(PPCInterpreter_t* hCPU, uint32 opcode) +{ + sint32 rD, rA; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); + hCPU->gpr[rD] = (rA ? (int)hCPU->gpr[rA] : 0) + (int)imm; + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_ADDIC(PPCInterpreter_t* hCPU, uint32 opcode) +{ + int rD, rA; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); + uint32 a = hCPU->gpr[rA]; + hCPU->gpr[rD] = a + imm; + // update XER + if (hCPU->gpr[rD] < a) + hCPU->xer_ca = 1; + else + hCPU->xer_ca = 0; + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_ADDIC_(PPCInterpreter_t* hCPU, uint32 opcode) +{ + int rD, rA; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); + uint32 a = hCPU->gpr[rA]; + hCPU->gpr[rD] = a + imm; + // update XER + if (hCPU->gpr[rD] < a) + hCPU->xer_ca = 1; + else + hCPU->xer_ca = 0; + // update cr0 flags + ppc_update_cr0(hCPU, hCPU->gpr[rD]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_ADDIS(PPCInterpreter_t* hCPU, uint32 opcode) +{ + int rD, rA; + uint32 imm; + PPC_OPC_TEMPL_D_Shift16(opcode, rD, rA, imm); + hCPU->gpr[rD] = (rA ? hCPU->gpr[rA] : 0) + imm; + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_ADDZE(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL3_XO(); + PPC_ASSERT(rB == 0); + uint32 a = hCPU->gpr[rA]; + uint32 ca = hCPU->xer_ca; + hCPU->gpr[rD] = a + ca; + if ((a == 0xffffffff) && ca) + hCPU->xer_ca = 1; + else + hCPU->xer_ca = 0; + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rD]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_ADDME(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL3_XO(); + PPC_ASSERT(rB == 0); + uint32 a = hCPU->gpr[rA]; + uint32 ca = hCPU->xer_ca; + hCPU->gpr[rD] = a + ca + 0xffffffff; + if (a || ca) + hCPU->xer_ca = 1; + else + hCPU->xer_ca = 0; + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rD]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_SUBF(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL3_XO(); + hCPU->gpr[rD] = ~hCPU->gpr[rA] + hCPU->gpr[rB] + 1; + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rD]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_SUBFO(PPCInterpreter_t* hCPU, uint32 opcode) +{ + // untested (Don't Starve Giant Edition uses this) + // also used by DS Virtual Console (Super Mario 64 DS) + PPC_OPC_TEMPL3_XO(); + hCPU->gpr[rD] = ~hCPU->gpr[rA] + hCPU->gpr[rB] + 1; + PPCInterpreter_setXerOV(hCPU, checkAdditionOverflow(~hCPU->gpr[rA], hCPU->gpr[rB], hCPU->gpr[rD])); + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rD]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_SUBFC(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL3_XO(); + uint32 a = hCPU->gpr[rA]; + uint32 b = hCPU->gpr[rB]; + hCPU->gpr[rD] = ~a + b + 1; + // update xer + if (ppc_carry_3(~a, b, 1)) + hCPU->xer_ca = 1; + else + hCPU->xer_ca = 0; + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rD]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_SUBFCO(PPCInterpreter_t* hCPU, uint32 opcode) +{ + // used by DS Virtual Console (Super Mario 64 DS) + PPC_OPC_TEMPL3_XO(); + uint32 a = hCPU->gpr[rA]; + uint32 b = hCPU->gpr[rB]; + hCPU->gpr[rD] = ~a + b + 1; + // update xer + if (ppc_carry_3(~a, b, 1)) + hCPU->xer_ca = 1; + else + hCPU->xer_ca = 0; + // update xer SO/OV + if (checkAdditionOverflow(~a, b, hCPU->gpr[rD])) + { + hCPU->spr.XER |= XER_SO; + hCPU->spr.XER |= XER_OV; + } + else + { + hCPU->spr.XER &= ~XER_OV; + } + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rD]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_SUBFIC(PPCInterpreter_t* hCPU, uint32 opcode) +{ + int rD, rA; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); + uint32 a = hCPU->gpr[rA]; + hCPU->gpr[rD] = ~a + imm + 1; + if (ppc_carry_3(~a, imm, 1)) + hCPU->xer_ca = 1; + else + hCPU->xer_ca = 0; + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_SUBFE(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL3_XO(); + uint32 a = hCPU->gpr[rA]; + uint32 b = hCPU->gpr[rB]; + uint32 ca = hCPU->xer_ca; + hCPU->gpr[rD] = ~a + b + ca; + // update xer carry + if (ppc_carry_3(~a, b, ca)) + hCPU->xer_ca = 1; + else + hCPU->xer_ca = 0; + // update cr0 + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rD]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_SUBFEO(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL3_XO(); + uint32 a = hCPU->gpr[rA]; + uint32 b = hCPU->gpr[rB]; + uint32 ca = hCPU->xer_ca; + uint32 result = ~a + b + ca; + hCPU->gpr[rD] = result; + // update xer carry + if (ppc_carry_3(~a, b, ca)) + hCPU->xer_ca = 1; + else + hCPU->xer_ca = 0; + if (checkAdditionOverflow(~a, b, result)) + { + hCPU->spr.XER |= XER_SO; + hCPU->spr.XER |= XER_OV; + } + else + { + hCPU->spr.XER &= ~XER_OV; + } + // update cr0 + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rD]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_SUBFZE(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL3_XO(); + PPC_ASSERT(rB == 0); + uint32 a = hCPU->gpr[rA]; + uint32 ca = hCPU->xer_ca; + hCPU->gpr[rD] = ~a + ca; + if (a == 0 && ca) + hCPU->xer_ca = 1; + else + hCPU->xer_ca = 0; + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rD]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_SUBFME(PPCInterpreter_t* hCPU, uint32 opcode) +{ + // untested + PPC_OPC_TEMPL3_XO(); + PPC_ASSERT(rB == 0); + uint32 a = hCPU->gpr[rA]; + uint32 ca = hCPU->xer_ca; + hCPU->gpr[rD] = ~a + 0xFFFFFFFF + ca; + // update xer carry + if (ppc_carry_3(~a, 0xFFFFFFFF, ca)) + hCPU->xer_ca = 1; + else + hCPU->xer_ca = 0; + // update cr0 + if (opcode & PPC_OPC_RC) + ppc_update_cr0(hCPU, hCPU->gpr[rD]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_MULHW_(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL3_XO(); + sint64 a = (sint32)hCPU->gpr[rA]; + sint64 b = (sint32)hCPU->gpr[rB]; + sint64 c = a * b; + hCPU->gpr[rD] = ((uint64)c) >> 32; + if (opcode & PPC_OPC_RC) { + // update cr0 flags +#ifndef PUBLIC_RELEASE + assert_dbg(); +#endif + ppc_update_cr0(hCPU, hCPU->gpr[rD]); + } + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_MULHWU_(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL3_XO(); + uint64 a = hCPU->gpr[rA]; + uint64 b = hCPU->gpr[rB]; + uint64 c = a * b; + hCPU->gpr[rD] = c >> 32; + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rD]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_MULLW(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL3_XO(); + sint64 result = (sint64)hCPU->gpr[rA] * (sint64)hCPU->gpr[rB]; + hCPU->gpr[rD] = (uint32)result; + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rD]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_MULLWO(PPCInterpreter_t* hCPU, uint32 opcode) +{ + // Don't Starve Giant Edition uses this instruction + BSO + // also used by FullBlast when a save file exists + it uses mfxer to access overflow result + PPC_OPC_TEMPL3_XO(); + sint64 result = (sint64)hCPU->gpr[rA] * (sint64)hCPU->gpr[rB]; + hCPU->gpr[rD] = (uint32)result; + if (result < -0x80000000ll && result > 0x7FFFFFFFLL) + { + hCPU->spr.XER |= XER_SO; + hCPU->spr.XER |= XER_OV; + } + else + { + hCPU->spr.XER &= ~XER_OV; + } + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rD]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_MULLI(PPCInterpreter_t* hCPU, uint32 opcode) +{ + int rD, rA; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); + hCPU->gpr[rD] = hCPU->gpr[rA] * imm; + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_DIVW(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL3_XO(); + sint32 a = hCPU->gpr[rA]; + sint32 b = hCPU->gpr[rB]; + if (b == 0) + { + forceLogDebug_printf("Error: Division by zero! [%08X]\n", (uint32)hCPU->instructionPointer); + b++; + } + hCPU->gpr[rD] = a / b; + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rD]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_DIVWO(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL3_XO(); + sint32 a = hCPU->gpr[rA]; + sint32 b = hCPU->gpr[rB]; + if (b == 0) + { + if (opcode & PPC_OPC_OE) + hCPU->spr.XER |= XER_OV; + PPCInterpreter_nextInstruction(hCPU); + return; + } + hCPU->gpr[rD] = a / b; + if (opcode & PPC_OPC_OE) + hCPU->spr.XER &= ~XER_OV; + // todo: Handle SO + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rD]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_DIVWU(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL3_XO(); + if (hCPU->gpr[rB] == 0) + { + PPCInterpreter_nextInstruction(hCPU); + return; + } + hCPU->gpr[rD] = hCPU->gpr[rA] / hCPU->gpr[rB]; + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rD]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_DIVWUO(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL3_XO(); + if (hCPU->gpr[rB] == 0) + { + if (opcode & PPC_OPC_OE) + hCPU->spr.XER |= XER_OV; + PPCInterpreter_nextInstruction(hCPU); + return; + } + hCPU->gpr[rD] = hCPU->gpr[rA] / hCPU->gpr[rB]; + if (opcode & PPC_OPC_OE) + hCPU->spr.XER &= ~XER_OV; + // todo: Handle SO + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rD]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_CREQV(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL_X_CR(); + ppc_setCRBit(hCPU, crD, ppc_getCRBit(hCPU, crA) ^ ppc_getCRBit(hCPU, crB) ^ 1); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_CRAND(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL_X_CR(); + ppc_setCRBit(hCPU, crD, ppc_getCRBit(hCPU, crA)&ppc_getCRBit(hCPU, crB)); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_CRANDC(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL_X_CR(); + ppc_setCRBit(hCPU, crD, ppc_getCRBit(hCPU, crA)&(ppc_getCRBit(hCPU, crB) ^ 1)); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_CROR(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL_X_CR(); + ppc_setCRBit(hCPU, crD, ppc_getCRBit(hCPU, crA) | ppc_getCRBit(hCPU, crB)); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_CRORC(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL_X_CR(); + ppc_setCRBit(hCPU, crD, ppc_getCRBit(hCPU, crA) | (ppc_getCRBit(hCPU, crB) ^ 1)); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_CRNOR(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL_X_CR(); + ppc_setCRBit(hCPU, crD, (ppc_getCRBit(hCPU, crA) | ppc_getCRBit(hCPU, crB)) ^ 1); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_CRXOR(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL_X_CR(); + ppc_setCRBit(hCPU, crD, ppc_getCRBit(hCPU, crA) ^ ppc_getCRBit(hCPU, crB)); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_NEG(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL3_XO(); + PPC_ASSERT(rB == 0); + hCPU->gpr[rD] = (uint32)-((sint32)hCPU->gpr[rA]); + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rD]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_NEGO(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL3_XO(); + PPC_ASSERT(rB == 0); + if (hCPU->gpr[rA] == 0x80000000) + { + hCPU->spr.XER |= XER_SO; + hCPU->spr.XER |= XER_OV; + } + else + { + hCPU->spr.XER &= ~XER_OV; + } + hCPU->gpr[rD] = (uint32)-((sint32)hCPU->gpr[rA]); + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rD]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_ANDX(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL3_XO(); + hCPU->gpr[rA] = hCPU->gpr[rD] & hCPU->gpr[rB]; + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rA]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_ANDCX(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL3_XO(); + hCPU->gpr[rA] = hCPU->gpr[rD] & ~hCPU->gpr[rB]; + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rA]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_ANDI_(PPCInterpreter_t* hCPU, uint32 opcode) +{ + int rS, rA; + uint32 imm; + PPC_OPC_TEMPL_D_UImm(opcode, rS, rA, imm); + hCPU->gpr[rA] = hCPU->gpr[rS] & imm; + ppc_update_cr0(hCPU, hCPU->gpr[rA]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_ANDIS_(PPCInterpreter_t* hCPU, uint32 opcode) +{ + int rS, rA; + uint32 imm; + PPC_OPC_TEMPL_D_Shift16(opcode, rS, rA, imm); + hCPU->gpr[rA] = hCPU->gpr[rS] & imm; + ppc_update_cr0(hCPU, hCPU->gpr[rA]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_NANDX(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL3_XO(); + hCPU->gpr[rA] = ~(hCPU->gpr[rD] & hCPU->gpr[rB]); + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rA]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_OR(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL3_XO(); + hCPU->gpr[rA] = hCPU->gpr[rD] | hCPU->gpr[rB]; + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rA]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_ORC(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL3_XO(); + hCPU->gpr[rA] = hCPU->gpr[rD] | ~hCPU->gpr[rB]; + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rA]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_ORI(PPCInterpreter_t* hCPU, uint32 opcode) +{ + int rS, rA; + uint32 imm; + PPC_OPC_TEMPL_D_UImm(opcode, rS, rA, imm); + hCPU->gpr[rA] = hCPU->gpr[rS] | imm; + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_ORIS(PPCInterpreter_t* hCPU, uint32 opcode) +{ + int rS, rA; + uint32 imm; + PPC_OPC_TEMPL_D_Shift16(opcode, rS, rA, imm); + hCPU->gpr[rA] = hCPU->gpr[rS] | imm; + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_NORX(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL3_XO(); + hCPU->gpr[rA] = ~(hCPU->gpr[rD] | hCPU->gpr[rB]); + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rA]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_XOR(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL3_XO(); + hCPU->gpr[rA] = hCPU->gpr[rD] ^ hCPU->gpr[rB]; + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rA]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_XORI(PPCInterpreter_t* hCPU, uint32 opcode) +{ + int rS, rA; + uint32 imm; + PPC_OPC_TEMPL_D_UImm(opcode, rS, rA, imm); + hCPU->gpr[rA] = hCPU->gpr[rS] ^ imm; + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_XORIS(PPCInterpreter_t* hCPU, uint32 opcode) +{ + int rS, rA; + uint32 imm; + PPC_OPC_TEMPL_D_Shift16(opcode, rS, rA, imm); + hCPU->gpr[rA] = hCPU->gpr[rS] ^ imm; + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_EQV(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL3_XO(); + hCPU->gpr[rA] = ~(hCPU->gpr[rD] ^ hCPU->gpr[rB]); + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rA]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_RLWIMI(PPCInterpreter_t* hCPU, uint32 opcode) +{ + int rS, rA, SH, MB, ME; + PPC_OPC_TEMPL_M(opcode, rS, rA, SH, MB, ME); + uint32 v = ppc_word_rotl(hCPU->gpr[rS], SH); + uint32 mask = ppc_mask(MB, ME); + hCPU->gpr[rA] = (v & mask) | (hCPU->gpr[rA] & ~mask); + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rA]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_RLWINM(PPCInterpreter_t* hCPU, uint32 opcode) +{ + sint32 rS, rA, SH, MB, ME; + PPC_OPC_TEMPL_M(opcode, rS, rA, SH, MB, ME); + uint32 v = ppc_word_rotl(hCPU->gpr[rS], SH); + uint32 mask = ppc_mask(MB, ME); + hCPU->gpr[rA] = v & mask; + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rA]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_RLWNM(PPCInterpreter_t* hCPU, uint32 opcode) +{ + int rS, rA, rB, MB, ME; + PPC_OPC_TEMPL_M(opcode, rS, rA, rB, MB, ME); + uint32 v = ppc_word_rotl(hCPU->gpr[rS], hCPU->gpr[rB]); + uint32 mask = ppc_mask(MB, ME); + hCPU->gpr[rA] = v & mask; + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rA]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_SLWX(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL3_XO(); + uint32 s = hCPU->gpr[rB] & 0x3f; + if (s > 31) + hCPU->gpr[rA] = 0; + else + hCPU->gpr[rA] = hCPU->gpr[rD] << s; + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rA]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_SRAW(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL3_XO(); + uint32 sh = hCPU->gpr[rB] & 0x3f; + hCPU->gpr[rA] = hCPU->gpr[rD]; + if (sh > 31) + { + hCPU->xer_ca = (hCPU->gpr[rA] >> 31) & 1; // copy sign bit to ca + hCPU->gpr[rA] = (uint32)((sint32)hCPU->gpr[rA] >> 31); // fill all bits with sign bit + } + else + { + // ca is set when input is negative and non-zero bits are dropped by shift operation + uint8 caBit = (hCPU->gpr[rA] >> 31) & 1; + uint32 shiftedBits = hCPU->gpr[rA] & ~(0xFFFFFFFF << sh); + caBit &= (shiftedBits != 0 ? 1 : 0); + hCPU->xer_ca = caBit; + hCPU->gpr[rA] = (uint32)((sint32)hCPU->gpr[rA] >> sh); + } + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rA]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_SRWX(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL3_XO(); + uint32 v = hCPU->gpr[rB] & 0x3f; + if (v > 31) + hCPU->gpr[rA] = 0; + else + hCPU->gpr[rA] = hCPU->gpr[rD] >> v; + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rA]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_SRAWI(PPCInterpreter_t* hCPU, uint32 opcode) +{ + sint32 rS, rA; + uint32 SH; + PPC_OPC_TEMPL_X(opcode, rS, rA, SH); + hCPU->gpr[rA] = hCPU->gpr[rS]; + hCPU->xer_ca = 0; + if (hCPU->gpr[rA] & 0x80000000) + { + uint32 ca = 0; + for (uint32 i = 0; i < SH; i++) + { + if (hCPU->gpr[rA] & 1) + ca = 1; + hCPU->gpr[rA] >>= 1; + hCPU->gpr[rA] |= 0x80000000; + } + if (ca) + hCPU->xer_ca = 1; + } + else + { + if (SH > 31) + hCPU->gpr[rA] = 0; + else + hCPU->gpr[rA] >>= SH; + } + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rA]); + PPCInterpreter_nextInstruction(hCPU); +} + +static uint32 _CNTLZW(uint32 v) +{ + uint32 result = 0; + if (v == 0) + return 32; + if ((v & 0xFFFF0000) != 0) { result |= 16; v >>= 16; } + if ((v & 0xFF00FF00) != 0) { result |= 8; v >>= 8; } + if ((v & 0xF0F0F0F0) != 0) { result |= 4; v >>= 4; } + if ((v & 0xCCCCCCCC) != 0) { result |= 2; v >>= 2; } + if ((v & 0xAAAAAAAA) != 0) { result |= 1; } + result = 31 - result; + return result; +} + +static void PPCInterpreter_CNTLZW(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL3_XO(); + PPC_ASSERT(rB == 0); + hCPU->gpr[rA] = _CNTLZW(hCPU->gpr[rD]); + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rA]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_EXTSB(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL3_XO(); + PPC_ASSERT(rB == 0); + hCPU->gpr[rA] = (uint32)(sint32)(sint8)hCPU->gpr[rD]; + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rA]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_EXTSH(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL3_XO(); + PPC_ASSERT(rB == 0); + hCPU->gpr[rA] = (uint32)(sint32)(sint16)hCPU->gpr[rD]; + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rA]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_CMP(PPCInterpreter_t* hCPU, uint32 opcode) +{ + uint32 cr; + sint32 rA, rB; + PPC_OPC_TEMPL_X(opcode, cr, rA, rB); + cr >>= 2; + sint32 a = hCPU->gpr[rA]; + sint32 b = hCPU->gpr[rB]; + hCPU->cr[cr * 4 + 0] = 0; + hCPU->cr[cr * 4 + 1] = 0; + hCPU->cr[cr * 4 + 2] = 0; + hCPU->cr[cr * 4 + 3] = 0; + if (a < b) + hCPU->cr[cr * 4 + CR_BIT_LT] = 1; + else if (a > b) + hCPU->cr[cr * 4 + CR_BIT_GT] = 1; + else + hCPU->cr[cr * 4 + CR_BIT_EQ] = 1; + if ((hCPU->spr.XER & XER_SO) != 0) + hCPU->cr[cr * 4 + CR_BIT_SO] = 1; + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_CMPL(PPCInterpreter_t* hCPU, uint32 opcode) +{ + uint32 cr; + int rA, rB; + PPC_OPC_TEMPL_X(opcode, cr, rA, rB); + cr >>= 2; + uint32 a = hCPU->gpr[rA]; + uint32 b = hCPU->gpr[rB]; + hCPU->cr[cr * 4 + 0] = 0; + hCPU->cr[cr * 4 + 1] = 0; + hCPU->cr[cr * 4 + 2] = 0; + hCPU->cr[cr * 4 + 3] = 0; + if (a < b) + hCPU->cr[cr * 4 + CR_BIT_LT] = 1; + else if (a > b) + hCPU->cr[cr * 4 + CR_BIT_GT] = 1; + else + hCPU->cr[cr * 4 + CR_BIT_EQ] = 1; + if ((hCPU->spr.XER & XER_SO) != 0) + hCPU->cr[cr * 4 + CR_BIT_SO] = 1; + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_CMPI(PPCInterpreter_t* hCPU, uint32 opcode) +{ + uint32 cr; + int rA; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(opcode, cr, rA, imm); + cr >>= 2; + sint32 a = hCPU->gpr[rA]; + sint32 b = imm; + hCPU->cr[cr * 4 + 0] = 0; + hCPU->cr[cr * 4 + 1] = 0; + hCPU->cr[cr * 4 + 2] = 0; + hCPU->cr[cr * 4 + 3] = 0; + if (a < b) + hCPU->cr[cr * 4 + CR_BIT_LT] = 1; + else if (a > b) + hCPU->cr[cr * 4 + CR_BIT_GT] = 1; + else + hCPU->cr[cr * 4 + CR_BIT_EQ] = 1; + if (hCPU->spr.XER & XER_SO) + hCPU->cr[cr * 4 + CR_BIT_SO] = 1; + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_CMPLI(PPCInterpreter_t* hCPU, uint32 opcode) +{ + uint32 cr; + int rA; + uint32 imm; + PPC_OPC_TEMPL_D_UImm(opcode, cr, rA, imm); + cr >>= 2; + uint32 a = hCPU->gpr[rA]; + uint32 b = imm; + hCPU->cr[cr * 4 + 0] = 0; + hCPU->cr[cr * 4 + 1] = 0; + hCPU->cr[cr * 4 + 2] = 0; + hCPU->cr[cr * 4 + 3] = 0; + if (a < b) + hCPU->cr[cr * 4 + CR_BIT_LT] = 1; + else if (a > b) + hCPU->cr[cr * 4 + CR_BIT_GT] = 1; + else + hCPU->cr[cr * 4 + CR_BIT_EQ] = 1; + if (hCPU->spr.XER & XER_SO) + hCPU->cr[cr * 4 + CR_BIT_SO] = 1; + PPCInterpreter_nextInstruction(hCPU); +} + diff --git a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterFPU.cpp b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterFPU.cpp new file mode 100644 index 00000000..a33a1be6 --- /dev/null +++ b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterFPU.cpp @@ -0,0 +1,700 @@ +#include "../PPCState.h" +#include "PPCInterpreterInternal.h" +#include "PPCInterpreterHelper.h" + +#include<math.h> + +// floating point utility + +#include <limits> +#include <array> + +const int ieee_double_e_bits = 11; // exponent bits +const int ieee_double_m_bits = 52; // mantissa bits + +const int espresso_frsqrte_i_bits = 5; // index bits (the highest bit is the LSB of the exponent) + +typedef struct +{ + uint32 offset; + uint32 step; +}espresso_frsqrte_entry_t; + +espresso_frsqrte_entry_t frsqrteLookupTable[32] = +{ + {0x1a7e800, 0x568},{0x17cb800, 0x4f3},{0x1552800, 0x48d},{0x130c000, 0x435}, + {0x10f2000, 0x3e7},{0xeff000, 0x3a2},{0xd2e000, 0x365},{0xb7c000, 0x32e}, + {0x9e5000, 0x2fc},{0x867000, 0x2d0},{0x6ff000, 0x2a8},{0x5ab800, 0x283}, + {0x46a000, 0x261},{0x339800, 0x243},{0x218800, 0x226},{0x105800, 0x20b}, + {0x3ffa000, 0x7a4},{0x3c29000, 0x700},{0x38aa000, 0x670},{0x3572000, 0x5f2}, + {0x3279000, 0x584},{0x2fb7000, 0x524},{0x2d26000, 0x4cc},{0x2ac0000, 0x47e}, + {0x2881000, 0x43a},{0x2665000, 0x3fa},{0x2468000, 0x3c2},{0x2287000, 0x38e}, + {0x20c1000, 0x35e},{0x1f12000, 0x332},{0x1d79000, 0x30a},{0x1bf4000, 0x2e6}, +}; + +double frsqrte_espresso(double input) +{ + unsigned long long x = *(unsigned long long*)&input; + + // 0.0 and -0.0 + if ((x << 1) == 0) + { + // result is inf or -inf + x &= ~0x7FFFFFFFFFFFFFFF; + x |= 0x7FF0000000000000; + return *(double*)&x; + } + // get exponent + uint32 e = (x >> ieee_double_m_bits) & ((1ull << ieee_double_e_bits) - 1ull); + // NaN or INF + if (e == 0x7FF) + { + if ((x&((1ull << ieee_double_m_bits) - 1)) == 0) + { + // negative INF returns +NaN + if ((sint64)x < 0) + { + x = 0x7FF8000000000000; + return *(double*)&x; + } + // positive INF returns +0.0 + return 0.0; + } + // result is NaN with same sign and same mantissa (todo: verify) + return *(double*)&x; + } + // negative number (other than -0.0) + if ((sint64)x < 0) + { + // result is positive NaN + x = 0x7FF8000000000000; + return *(double*)&x; + } + // todo: handle denormals + + // get index (lsb of exponent, remaining bits of mantissa) + uint32 idx = (x >> (ieee_double_m_bits - espresso_frsqrte_i_bits + 1ull))&((1 << espresso_frsqrte_i_bits) - 1); + // get step multiplier + uint32 stepMul = (x >> (ieee_double_m_bits - espresso_frsqrte_i_bits + 1 - 11))&((1 << 11) - 1); + + sint32 sum = frsqrteLookupTable[idx].offset - frsqrteLookupTable[idx].step * stepMul; + + e = 1023 - ((e - 1021) >> 1); + x &= ~(((1ull << ieee_double_e_bits) - 1ull) << ieee_double_m_bits); + x |= ((unsigned long long)e << ieee_double_m_bits); + + x &= ~((1ull << ieee_double_m_bits) - 1ull); + x += ((unsigned long long)sum << 26ull); + + return *(double*)&x; +} + +const int espresso_fres_i_bits = 5; // index bits +const int espresso_fres_s_bits = 10; // step multiplier bits + +typedef struct +{ + uint32 offset; + uint32 step; +}espresso_fres_entry_t; + +espresso_fres_entry_t fresLookupTable[32] = +{ + // table calculated by fres_gen_table() + {0x7ff800, 0x3e1}, {0x783800, 0x3a7}, {0x70ea00, 0x371}, {0x6a0800, 0x340}, + {0x638800, 0x313}, {0x5d6200, 0x2ea}, {0x579000, 0x2c4}, {0x520800, 0x2a0}, + {0x4cc800, 0x27f}, {0x47ca00, 0x261}, {0x430800, 0x245}, {0x3e8000, 0x22a}, + {0x3a2c00, 0x212}, {0x360800, 0x1fb}, {0x321400, 0x1e5}, {0x2e4a00, 0x1d1}, + {0x2aa800, 0x1be}, {0x272c00, 0x1ac}, {0x23d600, 0x19b}, {0x209e00, 0x18b}, + {0x1d8800, 0x17c}, {0x1a9000, 0x16e}, {0x17ae00, 0x15b}, {0x14f800, 0x15b}, + {0x124400, 0x143}, {0xfbe00, 0x143}, {0xd3800, 0x12d}, {0xade00, 0x12d}, + {0x88400, 0x11a}, {0x65000, 0x11a}, {0x41c00, 0x108}, {0x20c00, 0x106} +}; + +double fres_espresso(double input) +{ + // based on testing we know that fres uses only the first 15 bits of the mantissa + // seee eeee eeee mmmm mmmm mmmm mmmx xxxx .... (s = sign, e = exponent, m = mantissa, x = not used) + // the mantissa bits are interpreted as following: + // 0000 0000 0000 iiii ifff ffff fff0 ... (i = table look up index , f = step multiplier) + unsigned long long x = *(unsigned long long*)&input; + + // get index + uint32 idx = (x >> (ieee_double_m_bits - espresso_fres_i_bits))&((1 << espresso_fres_i_bits) - 1); + // get step multiplier + uint32 stepMul = (x >> (ieee_double_m_bits - espresso_fres_i_bits - 10))&((1 << 10) - 1); + + + uint32 sum = fresLookupTable[idx].offset - (fresLookupTable[idx].step * stepMul + 1) / 2; + + // get exponent + uint32 e = (x >> ieee_double_m_bits) & ((1ull << ieee_double_e_bits) - 1ull); + if (e == 0) + { + // todo? + //x &= 0x7FFFFFFFFFFFFFFFull; + x |= 0x7FF0000000000000ull; + return *(double*)&x; + } + else if (e == 0x7ff) // NaN or INF + { + if ((x&((1ull << ieee_double_m_bits) - 1)) == 0) + { + // negative INF returns -0.0 + if ((sint64)x < 0) + { + x = 0x8000000000000000; + return *(double*)&x; + } + // positive INF returns +0.0 + return 0.0; + } + // result is NaN with same sign and same mantissa (todo: verify) + return *(double*)&x; + } + // todo - needs more testing (especially NaN and INF values) + + e = 2045 - e; + x &= ~(((1ull << ieee_double_e_bits) - 1ull) << ieee_double_m_bits); + x |= ((unsigned long long)e << ieee_double_m_bits); + + x &= ~((1ull << ieee_double_m_bits) - 1ull); + x += ((unsigned long long)sum << 29ull); + + return *(double*)&x; +} + +void fcmpu_espresso(PPCInterpreter_t* hCPU, int crfD, double a, double b) +{ + uint32 c; + + ppc_setCRBit(hCPU, crfD + 0, 0); + ppc_setCRBit(hCPU, crfD + 1, 0); + ppc_setCRBit(hCPU, crfD + 2, 0); + ppc_setCRBit(hCPU, crfD + 3, 0); + + if (IS_NAN(*(uint64*)&a) || IS_NAN(*(uint64*)&b)) + { + c = 1; + ppc_setCRBit(hCPU, crfD + CR_BIT_SO, 1); + } + else if (a < b) + { + c = 8; + ppc_setCRBit(hCPU, crfD + CR_BIT_LT, 1); + } + else if (a > b) + { + c = 4; + ppc_setCRBit(hCPU, crfD + CR_BIT_GT, 1); + } + else + { + c = 2; + ppc_setCRBit(hCPU, crfD + CR_BIT_EQ, 1); + } + + if (IS_SNAN(*(uint64*)&a) || IS_SNAN(*(uint64*)&b)) + hCPU->fpscr |= FPSCR_VXSNAN; + + hCPU->fpscr = (hCPU->fpscr & 0xffff0fff) | (c << 12); +} + +void PPCInterpreter_FMR(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + int frD, rA, frB; + PPC_OPC_TEMPL_X(Opcode, frD, rA, frB); + PPC_ASSERT(rA==0); + hCPU->fpr[frD].fpr = hCPU->fpr[frB].fpr; + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_FSEL(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + int frD, frA, frB, frC; + PPC_OPC_TEMPL_A(Opcode, frD, frA, frB, frC); + if ( hCPU->fpr[frA].fp0 >= -0.0f ) + hCPU->fpr[frD] = hCPU->fpr[frC]; + else + hCPU->fpr[frD] = hCPU->fpr[frB]; + PPC_ASSERT((Opcode & PPC_OPC_RC) != 0); // update CR1 flags + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_FCTIWZ(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + int frD, frA, frB; + PPC_OPC_TEMPL_X(Opcode, frD, frA, frB); + PPC_ASSERT(frA==0); + + double b = hCPU->fpr[frB].fpr; + uint64 v; + if (b > (double)0x7FFFFFFF) + { + v = (uint64)0x7FFFFFFF; + } + else if (b < -(double)0x80000000) + { + v = (uint64)0x80000000; + } + else + { + v = (uint64)(uint32)(sint32)b; + } + + hCPU->fpr[frD].guint = 0xFFF8000000000000ULL | v; + if (v == 0 && ((*(uint64*)&b) >> 63)) + hCPU->fpr[frD].guint |= 0x100000000ull; + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_FCTIW(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + int frD, frA, frB; + PPC_OPC_TEMPL_X(Opcode, frD, frA, frB); + PPC_ASSERT(frA==0); + + double b = hCPU->fpr[frB].fpr; + uint64 v; + if (b > (double)0x7FFFFFFF) + { + v = (uint64)0x7FFFFFFF; + } + else if (b < -(double)0x80000000) + { + v = (uint64)0x80000000; + } + else + { + // todo: Support for other rounding modes than NEAR + double t = b + 0.5; + sint32 i = (sint32)t; + if (t - i < 0 || (t - i == 0 && b > 0)) + { + i--; + } + v = (uint64)i; + } + hCPU->fpr[frD].guint = 0xFFF8000000000000ULL | v; + if (v == 0 && ((*(uint64*)&b) >> 63)) + hCPU->fpr[frD].guint |= 0x100000000ull; + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_FNEG(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + int frD, frA, frB; + PPC_OPC_TEMPL_X(Opcode, frD, frA, frB); + PPC_ASSERT(frA==0); + + hCPU->fpr[frD].guint = hCPU->fpr[frB].guint ^ (1ULL << 63); + + PPC_ASSERT((Opcode & PPC_OPC_RC) != 0); // update CR1 flags + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_FRSP(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + int frD, frA, frB; + PPC_OPC_TEMPL_X(Opcode, frD, frA, frB); + PPC_ASSERT(frA==0); + + if( PPC_PSE ) + { + hCPU->fpr[frD].fp0 = (float)hCPU->fpr[frB].fpr; + hCPU->fpr[frD].fp1 = hCPU->fpr[frD].fp0; + } + else + { + hCPU->fpr[frD].fpr = (float)hCPU->fpr[frB].fpr; + } + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_FRSQRTE(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + int frD, frA, frB, frC; + PPC_OPC_TEMPL_A(Opcode, frD, frA, frB, frC); + PPC_ASSERT(frA==0 && frC==0); + + hCPU->fpr[frD].fpr = frsqrte_espresso(hCPU->fpr[frB].fpr); + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_FRES(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + int frD, frA, frB, frC; + PPC_OPC_TEMPL_A(Opcode, frD, frA, frB, frC); + PPC_ASSERT(frA==0 && frC==0); + + hCPU->fpr[frD].fpr = fres_espresso(hCPU->fpr[frB].fpr); + + if(PPC_PSE) + hCPU->fpr[frD].fp1 = hCPU->fpr[frD].fp0; + + PPCInterpreter_nextInstruction(hCPU); +} + +// Floating point ALU + +void PPCInterpreter_FABS(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + int frD, frA, frB; + PPC_OPC_TEMPL_X(Opcode, frD, frA, frB); + PPC_ASSERT(frA==0); + + hCPU->fpr[frD].guint = hCPU->fpr[frB].guint & ~0x8000000000000000; + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_FNABS(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + int frD, frA, frB; + PPC_OPC_TEMPL_X(Opcode, frD, frA, frB); + PPC_ASSERT(frA==0); + + hCPU->fpr[frD].guint = hCPU->fpr[frB].guint | 0x8000000000000000; + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_FADD(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + int frD, frA, frB, frC; + PPC_OPC_TEMPL_A(Opcode, frD, frA, frB, frC); + PPC_ASSERT(frC==0); + + hCPU->fpr[frD].fpr = hCPU->fpr[frA].fpr + hCPU->fpr[frB].fpr; + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_FDIV(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + int frD, frA, frB, frC; + PPC_OPC_TEMPL_A(Opcode, frD, frA, frB, frC); + PPC_ASSERT(frC==0); + + hCPU->fpr[frD].fpr = hCPU->fpr[frA].fpr / hCPU->fpr[frB].fpr; + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_FSUB(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + int frD, frA, frB, frC; + PPC_OPC_TEMPL_A(Opcode, frD, frA, frB, frC); + PPC_ASSERT(frC==0); + + hCPU->fpr[frD].fpr = hCPU->fpr[frA].fpr - hCPU->fpr[frB].fpr; + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_FMUL(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + int frD, frA, frB, frC; + PPC_OPC_TEMPL_A(Opcode, frD, frA, frB, frC); + PPC_ASSERT(frC == 0); + + hCPU->fpr[frD].fpr = hCPU->fpr[frA].fpr * hCPU->fpr[frC].fpr; + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_FMADD(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + int frD, frA, frB, frC; + PPC_OPC_TEMPL_A(Opcode, frD, frA, frB, frC); + + hCPU->fpr[frD].fpr = hCPU->fpr[frA].fpr * hCPU->fpr[frC].fpr + hCPU->fpr[frB].fpr; + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_FNMADD(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + int frD, frA, frB, frC; + PPC_OPC_TEMPL_A(Opcode, frD, frA, frB, frC); + + hCPU->fpr[frD].fpr = -(hCPU->fpr[frA].fpr * hCPU->fpr[frC].fpr + hCPU->fpr[frB].fpr); + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_FMSUB(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + int frD, frA, frB, frC; + PPC_OPC_TEMPL_A(Opcode, frD, frA, frB, frC); + + hCPU->fpr[frD].fpr = (hCPU->fpr[frA].fpr * hCPU->fpr[frC].fpr - hCPU->fpr[frB].fpr); + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_FNMSUB(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + int frD, frA, frB, frC; + PPC_OPC_TEMPL_A(Opcode, frD, frA, frB, frC); + + hCPU->fpr[frD].fpr = -(hCPU->fpr[frA].fpr * hCPU->fpr[frC].fpr - hCPU->fpr[frB].fpr); + + PPCInterpreter_nextInstruction(hCPU); +} + +// Move + +void PPCInterpreter_MFFS(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + int frD, rA, rB; + PPC_OPC_TEMPL_X(Opcode, frD, rA, rB); + PPC_ASSERT(rA==0 && rB==0); + hCPU->fpr[frD].guint = (uint64)hCPU->fpscr; + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_MTFSF(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + int frB; + uint32 fm, FM; + PPC_OPC_TEMPL_XFL(Opcode, frB, fm); + FM = ((fm&0x80)?0xf0000000:0)|((fm&0x40)?0x0f000000:0)|((fm&0x20)?0x00f00000:0)|((fm&0x10)?0x000f0000:0)| + ((fm&0x08)?0x0000f000:0)|((fm&0x04)?0x00000f00:0)|((fm&0x02)?0x000000f0:0)|((fm&0x01)?0x0000000f:0); + hCPU->fpscr = (hCPU->fpr[frB].guint & FM) | (hCPU->fpscr & ~FM); + + PPC_ASSERT((Opcode & PPC_OPC_RC) != 0); // update CR1 flags + + static bool logFPSCRWriteOnce = false; + if( logFPSCRWriteOnce == false ) + { + forceLog_printf("Unsupported write to FPSCR\n"); + logFPSCRWriteOnce = true; + } + PPCInterpreter_nextInstruction(hCPU); +} + +// single precision + +void PPCInterpreter_FADDS(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + int frD, frA, frB, frC; + PPC_OPC_TEMPL_A(Opcode, frD, frA, frB, frC); + PPC_ASSERT(frB == 0); + + // todo: check for RC + + hCPU->fpr[frD].fpr = (float)(hCPU->fpr[frA].fpr + hCPU->fpr[frB].fpr); + if (PPC_PSE) + hCPU->fpr[frD].fp1 = hCPU->fpr[frD].fp0; + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_FSUBS(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + int frD, frA, frB, frC; + PPC_OPC_TEMPL_A(Opcode, frD, frA, frB, frC); + PPC_ASSERT(frB == 0); + + hCPU->fpr[frD].fpr = (float)(hCPU->fpr[frA].fpr - hCPU->fpr[frB].fpr); + if (PPC_PSE) + hCPU->fpr[frD].fp1 = hCPU->fpr[frD].fp0; + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_FDIVS(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + int frD, frA, frB, frC; + PPC_OPC_TEMPL_A(Opcode, frD, frA, frB, frC); + PPC_ASSERT(frB==0); + + hCPU->fpr[frD].fpr = (float)(hCPU->fpr[frA].fpr / hCPU->fpr[frB].fpr); + if( PPC_PSE ) + hCPU->fpr[frD].fp1 = hCPU->fpr[frD].fp0; + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_FMULS(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + int frD, frA, frB, frC; + PPC_OPC_TEMPL_A(Opcode, frD, frA, frB, frC); + PPC_ASSERT(frB == 0); + + hCPU->fpr[frD].fpr = (float)(hCPU->fpr[frA].fpr * roundTo25BitAccuracy(hCPU->fpr[frC].fpr)); + if (PPC_PSE) + hCPU->fpr[frD].fp1 = hCPU->fpr[frD].fp0; + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_FMADDS(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + int frD, frA, frB, frC; + PPC_OPC_TEMPL_A(Opcode, frD, frA, frB, frC); + + hCPU->fpr[frD].fpr = (float)(hCPU->fpr[frA].fpr * roundTo25BitAccuracy(hCPU->fpr[frC].fpr) + hCPU->fpr[frB].fpr); + if (PPC_PSE) + hCPU->fpr[frD].fp1 = hCPU->fpr[frD].fp0; + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_FNMADDS(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + int frD, frA, frB, frC; + PPC_OPC_TEMPL_A(Opcode, frD, frA, frB, frC); + + hCPU->fpr[frD].fpr = (float)-(hCPU->fpr[frA].fpr * roundTo25BitAccuracy(hCPU->fpr[frC].fpr) + hCPU->fpr[frB].fpr); + if (PPC_PSE) + hCPU->fpr[frD].fp1 = hCPU->fpr[frD].fp0; + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_FMSUBS(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + int frD, frA, frB, frC; + PPC_OPC_TEMPL_A(Opcode, frD, frA, frB, frC); + + hCPU->fpr[frD].fp0 = (float)(hCPU->fpr[frA].fp0 * roundTo25BitAccuracy(hCPU->fpr[frC].fp0) - hCPU->fpr[frB].fp0); + if (PPC_PSE) + hCPU->fpr[frD].fp1 = hCPU->fpr[frD].fp0; + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_FNMSUBS(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + int frD, frA, frB, frC; + PPC_OPC_TEMPL_A(Opcode, frD, frA, frB, frC); + + hCPU->fpr[frD].fp0 = (float)-(hCPU->fpr[frA].fp0 * roundTo25BitAccuracy(hCPU->fpr[frC].fp0) - hCPU->fpr[frB].fp0); + if (PPC_PSE) + hCPU->fpr[frD].fp1 = hCPU->fpr[frD].fp0; + + PPCInterpreter_nextInstruction(hCPU); +} + +// Compare + +void PPCInterpreter_FCMPO(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + int crfD, frA, frB; + PPC_OPC_TEMPL_X(Opcode, crfD, frA, frB); + crfD >>= 2; + hCPU->cr[crfD*4+0] = 0; + hCPU->cr[crfD*4+1] = 0; + hCPU->cr[crfD*4+2] = 0; + hCPU->cr[crfD*4+3] = 0; + + uint32 c; + if(IS_NAN(hCPU->fpr[frA].guint) || IS_NAN(hCPU->fpr[frB].guint)) + { + c = 1; + hCPU->cr[crfD*4+CR_BIT_SO] = 1; + } + else if(hCPU->fpr[frA].fpr < hCPU->fpr[frB].fpr) + { + c = 8; + hCPU->cr[crfD*4+CR_BIT_LT] = 1; + } + else if(hCPU->fpr[frA].fpr > hCPU->fpr[frB].fpr) + { + c = 4; + hCPU->cr[crfD*4+CR_BIT_GT] = 1; + } + else + { + c = 2; + hCPU->cr[crfD*4+CR_BIT_EQ] = 1; + } + + hCPU->fpscr = (hCPU->fpscr & 0xffff0fff) | (c << 12); + + if (IS_SNAN (hCPU->fpr[frA].guint) || IS_SNAN (hCPU->fpr[frB].guint)) + hCPU->fpscr |= FPSCR_VXSNAN; + else if (!(hCPU->fpscr & FPSCR_VE) || IS_QNAN (hCPU->fpr[frA].guint) || IS_QNAN (hCPU->fpr[frB].guint)) + hCPU->fpscr |= FPSCR_VXVC; + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_FCMPU(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + int crfD, frA, frB; + PPC_OPC_TEMPL_X(Opcode, crfD, frA, frB); + cemu_assert_debug((crfD % 4) == 0); + fcmpu_espresso(hCPU, crfD, hCPU->fpr[frA].fp0, hCPU->fpr[frB].fp0); + + PPCInterpreter_nextInstruction(hCPU); +} \ No newline at end of file diff --git a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterHLE.cpp b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterHLE.cpp new file mode 100644 index 00000000..6aa1fcfa --- /dev/null +++ b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterHLE.cpp @@ -0,0 +1,64 @@ +#include "../PPCState.h" +#include "PPCInterpreterInternal.h" +#include "PPCInterpreterHelper.h" + +std::unordered_set<std::string> sUnsupportedHLECalls; + +void PPCInterpreter_handleUnsupportedHLECall(PPCInterpreter_t* hCPU) +{ + const char* libFuncName = (char*)memory_getPointerFromVirtualOffset(hCPU->instructionPointer + 8); + std::string tempString = fmt::format("Unsupported lib call: {}", libFuncName); + if (sUnsupportedHLECalls.find(tempString) == sUnsupportedHLECalls.end()) + { + cemuLog_log(LogType::UnsupportedAPI, "{}", tempString); + sUnsupportedHLECalls.emplace(tempString); + } + hCPU->gpr[3] = 0; + PPCInterpreter_nextInstruction(hCPU); +} + +std::vector<void(*)(PPCInterpreter_t* hCPU)>* sPPCHLETable{}; + +HLEIDX PPCInterpreter_registerHLECall(HLECALL hleCall) +{ + if (!sPPCHLETable) + sPPCHLETable = new std::vector<void(*)(PPCInterpreter_t* hCPU)>(); + for (sint32 i = 0; i < sPPCHLETable->size(); i++) + { + if ((*sPPCHLETable)[i] == hleCall) + return i; + } + HLEIDX newFuncIndex = (sint32)sPPCHLETable->size(); + sPPCHLETable->resize(sPPCHLETable->size() + 1); + (*sPPCHLETable)[newFuncIndex] = hleCall; + return newFuncIndex; +} + +HLECALL PPCInterpreter_getHLECall(HLEIDX funcIndex) +{ + if (funcIndex < 0 || funcIndex >= sPPCHLETable->size()) + return nullptr; + return sPPCHLETable->data()[funcIndex]; +} + +std::mutex g_hleLogMutex; + +void PPCInterpreter_virtualHLE(PPCInterpreter_t* hCPU, unsigned int opcode) +{ + uint32 hleFuncId = opcode & 0xFFFF; + if (hleFuncId == 0xFFD0) + { + g_hleLogMutex.lock(); + PPCInterpreter_handleUnsupportedHLECall(hCPU); + g_hleLogMutex.unlock(); + return; + } + else + { + // os lib function + cemu_assert(hleFuncId < sPPCHLETable->size()); + auto hleCall = (*sPPCHLETable)[hleFuncId]; + cemu_assert(hleCall); + hleCall(hCPU); + } +} \ No newline at end of file diff --git a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterHelper.h b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterHelper.h new file mode 100644 index 00000000..4d8e5f11 --- /dev/null +++ b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterHelper.h @@ -0,0 +1,189 @@ + +static uint32 ppc_cmp_and_mask[8] = { + 0xfffffff0, + 0xffffff0f, + 0xfffff0ff, + 0xffff0fff, + 0xfff0ffff, + 0xff0fffff, + 0xf0ffffff, + 0x0fffffff, +}; + + +#define ppc_word_rotl(_data, _n) (_rotl(_data,(_n)&0x1F)) + +static inline uint32 ppc_mask(int MB, int ME) +{ + uint32 maskMB = 0xFFFFFFFF >> MB; + uint32 maskME = 0xFFFFFFFF << (31-ME); + uint32 mask2 = (MB <= ME) ? maskMB & maskME : maskMB | maskME; + return mask2; +} + +static inline bool ppc_carry_3(uint32 a, uint32 b, uint32 c) +{ + if ((a+b) < a) { + return true; + } + if ((a+b+c) < c) { + return true; + } + return false; +} + +#define PPC_getBits(__value, __index, __bitCount) ((__value>>(31-__index))&((1<<__bitCount)-1)) + +const static float LD_SCALE[] = { +1.000000f, 0.500000f, 0.250000f, 0.125000f, 0.062500f, 0.031250f, 0.015625f, +0.007813f, 0.003906f, 0.001953f, 0.000977f, 0.000488f, 0.000244f, 0.000122f, +0.000061f, 0.000031f, 0.000015f, 0.000008f, 0.000004f, 0.000002f, 0.000001f, +0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, +0.000000f, 0.000000f, 0.000000f, 0.000000f, 4294967296.000000f, 2147483648.000000f, +1073741824.000000f, 536870912.000000f, 268435456.000000f, 134217728.000000f, 67108864.000000f, +33554432.000000f, 16777216.000000f, 8388608.000000f, 4194304.000000f, 2097152.000000f, 1048576.000000f, +524288.000000f, 262144.000000f, 131072.000000f, 65536.000000f, 32768.000000f, 16384.000000f, +8192.000000f, 4096.000000f, 2048.000000f, 1024.000000f, 512.000000f, 256.000000f, 128.000000f, 64.000000f, 32.000000f, +16.000000f, 8.000000f, 4.000000f, 2.000000f }; + +const static float ST_SCALE[] = { +1.000000f, 2.000000f, 4.000000f, 8.000000f, +16.000000f, 32.000000f, 64.000000f, 128.000000f, +256.000000f, 512.000000f, 1024.000000f, 2048.000000f, +4096.000000f, 8192.000000f, 16384.000000f, 32768.000000f, +65536.000000f, 131072.000000f, 262144.000000f, 524288.000000f, +1048576.000000f, 2097152.000000f, 4194304.000000f, 8388608.000000f, +16777216.000000f, 33554432.000000f, 67108864.000000f, 134217728.000000f, +268435456.000000f, 536870912.000000f, 1073741824.000000f, 2147483648.000000f, +0.000000f, 0.000000f, 0.000000f, 0.000000f, +0.000000f, 0.000000f, 0.000000f, 0.000000f, +0.000000f, 0.000000f, 0.000000f, 0.000000f, +0.000001f, 0.000002f, 0.000004f, 0.000008f, +0.000015f, 0.000031f, 0.000061f, 0.000122f, +0.000244f, 0.000488f, 0.000977f, 0.001953f, +0.003906f, 0.007813f, 0.015625f, 0.031250f, +0.062500f, 0.125000f, 0.250000f, 0.500000f }; + +static float dequantize(uint32 data, sint32 type, uint8 scale) +{ + float f; + switch (type) + { + case 4: // u8 + f = (float)(uint8)data; + f *= LD_SCALE[scale]; + break; + case 5: // u16 + f = (float)(uint16)data; + f *= LD_SCALE[scale]; + break; + case 6: // s8 + f = (float)(sint8)data; + f *= LD_SCALE[scale]; + break; + case 7: // float + f = (float)(sint16)data; + f *= LD_SCALE[scale]; + break; + case 0: + default: + f = *((float *)&data); + // scale does not apply when loading floats + break; + } + return f; +} + +static uint32 quantize(float data, sint32 type, uint8 scale) +{ + uint32 val; + + switch (type) + { + case 4: // u8 + data *= ST_SCALE[scale]; + if (data < 0) data = 0; + if (data > 255) data = 255; + val = (uint8)(uint32)data; + break; + case 5: // u16 + data *= ST_SCALE[scale]; + if (data < 0) data = 0; + if (data > 65535) data = 65535; + val = (uint16)(uint32)data; + break; + case 6: // s8 + data *= ST_SCALE[scale]; + if (data < -128) data = -128; + if (data > 127) data = 127; + val = (sint8)(uint8)(sint32)(uint32)data; + break; + case 7: // s16 + data *= ST_SCALE[scale]; + if (data < -32768) data = -32768; + if (data > 32767) data = 32767; + val = (sint16)(uint16)(sint32)(uint32)data; + break; + case 0: // float + default: + // scale does not apply when storing floats + *((float*)&val) = data; + break; + } + return val; +} + +#define _uint32_fastSignExtend(__v, __bits) (uint32)(((sint32)(__v)<<(31-(__bits)))>>(31-(__bits))); + +static inline uint64 ConvertToDoubleNoFTZ(uint32 value) +{ + // http://www.freescale.com/files/product/doc/MPCFPE32B.pdf + + uint64 x = value; + uint64 exp = (x >> 23) & 0xff; + uint64 frac = x & 0x007fffff; + + if (exp > 0 && exp < 255) + { + uint64 y = !(exp >> 7); + uint64 z = y << 61 | y << 60 | y << 59; + return ((x & 0xc0000000) << 32) | z | ((x & 0x3fffffff) << 29); + } + else if (exp == 0 && frac != 0) // denormal + { + exp = 1023 - 126; + do + { + frac <<= 1; + exp -= 1; + } while ((frac & 0x00800000) == 0); + + return ((x & 0x80000000) << 32) | (exp << 52) | ((frac & 0x007fffff) << 29); + } + else // QNaN, SNaN or Zero + { + uint64 y = exp >> 7; + uint64 z = y << 61 | y << 60 | y << 59; + return ((x & 0xc0000000) << 32) | z | ((x & 0x3fffffff) << 29); + } +} + +static inline uint32 ConvertToSingleNoFTZ(uint64 x) +{ + uint32 exp = (x >> 52) & 0x7ff; + if (exp > 896 || (x & ~0x8000000000000000ULL) == 0) + { + return ((x >> 32) & 0xc0000000) | ((x >> 29) & 0x3fffffff); + } + else if (exp >= 874) + { + uint32 t = (uint32)(0x80000000 | ((x & 0x000FFFFFFFFFFFFFULL) >> 21)); + t = t >> (905 - exp); + t |= (x >> 32) & 0x80000000; + return t; + } + else + { + return ((x >> 32) & 0xc0000000) | ((x >> 29) & 0x3fffffff); + } +} diff --git a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterImpl.cpp b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterImpl.cpp new file mode 100644 index 00000000..6e3de535 --- /dev/null +++ b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterImpl.cpp @@ -0,0 +1,1231 @@ +#include "PPCInterpreterInternal.h" +#include "PPCInterpreterHelper.h" +#include "Cafe/HW/Espresso/Debugger/Debugger.h" + +class PPCItpCafeOSUsermode +{ +public: + static const bool allowSupervisorMode = false; + static const bool allowDSI = false; + + inline static uint32 memory_readCodeU32(PPCInterpreter_t* hCPU, uint32 address) + { + return _swapEndianU32(*(uint32*)(memory_base + address)); + } + + inline static void ppcMem_writeDataDouble(PPCInterpreter_t* hCPU, uint32 address, double vf) + { + uint64 v = *(uint64*)&vf; + uint32 v1 = v & 0xFFFFFFFF; + uint32 v2 = v >> 32; + uint8* ptr = memory_getPointerFromVirtualOffset(address); + *(uint32*)(ptr + 4) = CPU_swapEndianU32(v1); + *(uint32*)(ptr + 0) = CPU_swapEndianU32(v2); + } + + inline static void ppcMem_writeDataU64(PPCInterpreter_t* hCPU, uint32 address, uint64 v) + { + *(uint64*)(memory_getPointerFromVirtualOffset(address)) = CPU_swapEndianU64(v); + } + + inline static void ppcMem_writeDataU32(PPCInterpreter_t* hCPU, uint32 address, uint32 v) + { + *(uint32*)(memory_getPointerFromVirtualOffset(address)) = CPU_swapEndianU32(v); + } + + inline static void ppcMem_writeDataU16(PPCInterpreter_t* hCPU, uint32 address, uint16 v) + { + *(uint16*)(memory_getPointerFromVirtualOffset(address)) = CPU_swapEndianU16(v); + } + + inline static void ppcMem_writeDataU8(PPCInterpreter_t* hCPU, uint32 address, uint8 v) + { + *(uint8*)(memory_getPointerFromVirtualOffset(address)) = v; + } + + inline static double ppcMem_readDataDouble(PPCInterpreter_t* hCPU, uint32 address) + { + uint32 v[2]; + v[1] = *(uint32*)(memory_getPointerFromVirtualOffset(address)); + v[0] = *(uint32*)(memory_getPointerFromVirtualOffset(address) + 4); + v[0] = CPU_swapEndianU32(v[0]); + v[1] = CPU_swapEndianU32(v[1]); + return *(double*)v; + } + + inline static float ppcMem_readDataFloat(PPCInterpreter_t* hCPU, uint32 address) + { + uint32 v = *(uint32*)(memory_getPointerFromVirtualOffset(address)); + v = CPU_swapEndianU32(v); + return *(float*)&v; + } + + inline static uint64 ppcMem_readDataU64(PPCInterpreter_t* hCPU, uint32 address) + { + uint64 v = *(uint64*)(memory_getPointerFromVirtualOffset(address)); + return CPU_swapEndianU64(v); + } + + inline static uint32 ppcMem_readDataU32(PPCInterpreter_t* hCPU, uint32 address) + { + uint32 v = *(uint32*)(memory_getPointerFromVirtualOffset(address)); + return CPU_swapEndianU32(v); + } + + inline static uint16 ppcMem_readDataU16(PPCInterpreter_t* hCPU, uint32 address) + { + uint16 v = *(uint16*)(memory_getPointerFromVirtualOffset(address)); + return CPU_swapEndianU16(v); + } + + inline static uint8 ppcMem_readDataU8(PPCInterpreter_t* hCPU, uint32 address) + { + return *(uint8*)(memory_getPointerFromVirtualOffset(address)); + } + + inline static uint64 ppcMem_readDataFloatEx(PPCInterpreter_t* hCPU, uint32 addr) + { + return ConvertToDoubleNoFTZ(_swapEndianU32(*(uint32*)(memory_base + addr))); + } + + inline static void ppcMem_writeDataFloatEx(PPCInterpreter_t* hCPU, uint32 addr, uint64 value) + { + *(uint32*)(memory_base + addr) = _swapEndianU32(ConvertToSingleNoFTZ(value)); + } + + inline static uint64 getTB(PPCInterpreter_t* hCPU) + { + return PPCInterpreter_getMainCoreCycleCounter(); + } +}; + +uint32 debug_lastTranslatedHit; + +void generateDSIException(PPCInterpreter_t* hCPU, uint32 dataAddress) +{ + // todo - check if we are already inside an interrupt handler (in which case the DSI exception is queued and not executed immediately?) + + // set flag to cancel current instruction + hCPU->memoryException = true; + + hCPU->sprExtended.srr0 = hCPU->instructionPointer; + hCPU->sprExtended.srr1 = hCPU->sprExtended.msr & 0x87C0FFFF; + + hCPU->sprExtended.dar = dataAddress; + + hCPU->sprExtended.msr &= ~0x04EF36; + + hCPU->instructionPointer = 0xFFF00300; + + + uint32 dsisr = 0; + dsisr |= (1<<(31-1)); // set if no TLB/BAT match found + + hCPU->sprExtended.dsisr = dsisr; + +} + +class PPCItpSupervisorWithMMU +{ +public: + static const bool allowSupervisorMode = true; + static const bool allowDSI = true; + + inline static uint32 ppcMem_translateVirtualDataToPhysicalAddr(PPCInterpreter_t* hCPU, uint32 vAddr) + { + // check if address translation is disabled for data accesses + if (GET_MSR_BIT(MSR_DR) == 0) + { + return vAddr; + } + +#ifndef PUBLIC_RELEASE + if (hCPU->memoryException) + assert_dbg(); // should not be set anymore +#endif + + // how to determine if BAT is valid: + // BAT_entry_valid = (Vs & ~MSR[PR]) | (Vp & MSR[PR]) (The entry has separate enable flags for usermode and supervisor mode) + for (sint32 i = 0; i < 8; i++) + { + // upper + uint32 batU = hCPU->sprExtended.dbatU[i]; + uint32 BEPI = ((batU >> 17) & 0x7FFF) << 17; + uint32 Vp = (batU >> 0) & 1; + uint32 Vs = (batU >> 1) & 1; + uint32 BL = (((batU >> 2) & 0x7FF) ^ 0x7FF) << 17; + BL |= 0xF0000000; + if (Vs == 0) + continue; // todo - check if in supervisor/usermode + // lower + uint32 batL = hCPU->sprExtended.dbatL[i]; + uint32 PP = (batL >> 0) & 3; + uint32 WIMG = (batL >> 3) & 0xF; + uint32 BRPN = ((batL >> 17) & 0x7FFF) << 17; + + // check for match + if ((vAddr&BL) == BEPI) + { + // match + vAddr = (vAddr&~BL) | (BRPN&BL); + debug_lastTranslatedHit = vAddr; + return vAddr; + } + } + + // no match + debug_lastTranslatedHit = 0xFFFFFFFF; + + // find segment + uint32 segmentIndex = (vAddr>>28); + //uint32 pageIndex = (vAddr >> 12) & 0xFFFF; // for 4KB pages + // uint32 byteOffset = vAddr & 0xFFF; // for 4KB pages + uint32 pageIndex = (vAddr >> 17) & 0x7FF; // for 128KB pages + uint32 byteOffset = vAddr & 0x1FFFF; + uint32 srValue = hCPU->sprExtended.sr[segmentIndex]; + + uint8 sr_ks = (srValue >> 30) & 1; // supervisor + uint8 sr_kp = (srValue >> 29) & 1; // user mode + uint8 sr_n = (srValue >> 28) & 1; // no-execute + uint32 sr_vsid = (srValue & 0xFFFFFF); + //uint32 vpn = pageIndex | (sr_vsid << 16); // 40bit virtual page number + + + // look up in page table + //uint32 lookupHash = (sr_vsid ^ pageIndex) & 0x7FFFF; // not correct for 4KB pages? sr_vsid must be shifted? + //uint32 lookupHash = (sr_vsid ^ pageIndex) & 0x7FFFF; + //uint32 lookupHash = ((sr_vsid>>8) ^ pageIndex) & 0x7FFFF; + uint32 lookupHash = ((sr_vsid >> 0) ^ pageIndex) & 0x7FFFF; + + //lookupHash ^= 0x7FFFF; + + uint32 pageTableAddr = hCPU->sprExtended.sdr1&0xFFFF0000; + uint32 pageTableMask = hCPU->sprExtended.sdr1&0x1FF; + + for (uint32 ch = 0; ch < 2; ch++) + { + uint32 ptegSelectLow = (lookupHash & 0x3FF); + uint32 maskOR = (lookupHash >> 10) & pageTableMask; + + uint32* pteg = (uint32*)(memory_base + (pageTableAddr | (maskOR << 16)) + ptegSelectLow * 64); + for (sint32 t = 0; t < 8; t++) + { + uint32 w0 = _swapEndianU32(pteg[0]); + uint32 w1 = _swapEndianU32(pteg[1]); + pteg += 2; + if ((w0 & 0x80000000) == 0) + continue; // entry not valid + + uint32 abPageIndex = (w0 >> 0) & 0x3F; + uint8 h = (w0 >> 6) & 1; + uint32 ptegVSID = (w0 >> 7) & 0xFFFFFF; + + if (abPageIndex == (pageIndex >> 5) && ptegVSID == sr_vsid && h == ch) + { + if (ch == 1) + assert_dbg(); + // match + uint32 ptegPhysicalPage = (w1 >> 12) & 0xFFFFF; + // replace page (128KB) + vAddr = (vAddr & ~0xFFFE0000) | (ptegPhysicalPage << 12); + return vAddr; + + } + } + // calculate hash 2 + lookupHash = ~lookupHash; + } + + forceLogDebug_printf("DSI exception at 0x%08x LR 0x%08x DataAddress %08x", hCPU->instructionPointer, hCPU->spr.LR, vAddr); + + generateDSIException(hCPU, vAddr); + + // todo: Check hash func 1 + // todo: Check protection bits + // todo: Check supervisor/usermode bits + + + // also use this function in all the mem stuff below + + // note: bat has higher priority than TLB + + // since iterating the bats and page table is too slow, we need to pre-process the data somehow. + + return vAddr; + } + + inline static uint32 ppcMem_translateVirtualCodeToPhysicalAddr(PPCInterpreter_t* hCPU, uint32 vAddr) + { + // check if address translation is disabled for instruction accesses + if (GET_MSR_BIT(MSR_IR) == 0) + { + return vAddr; + } + + // how to determine if BAT is valid: + // BAT_entry_valid = (Vs & ~MSR[PR]) | (Vp & MSR[PR]) (The entry has separate enable flags for usermode and supervisor mode) + for (sint32 i = 0; i < 8; i++) + { + // upper + uint32 batU = hCPU->sprExtended.ibatU[i]; + uint32 BEPI = ((batU >> 17) & 0x7FFF) << 17; + uint32 Vp = (batU >> 0) & 1; + uint32 Vs = (batU >> 1) & 1; + uint32 BL = (((batU >> 2) & 0x7FF) ^ 0x7FF) << 17; + BL |= 0xF0000000; + if (Vs == 0) + continue; // todo - check if in supervisor/usermode + // lower + uint32 batL = hCPU->sprExtended.ibatL[i]; + uint32 PP = (batL >> 0) & 3; + uint32 WIMG = (batL >> 3) & 0xF; + uint32 BRPN = ((batL >> 17) & 0x7FFF) << 17; + + // check for match + if ((vAddr&BL) == BEPI) + { + // match + vAddr = (vAddr&~BL) | (BRPN&BL); + debug_lastTranslatedHit = vAddr; + return vAddr; + } + } + assert_dbg(); + + // no match + // todo - throw exception if translation is enabled? + return vAddr; + } + + static uint32 memory_readCodeU32(PPCInterpreter_t* hCPU, uint32 address) + { + return _swapEndianU32(*(uint32*)(memory_base + ppcMem_translateVirtualCodeToPhysicalAddr(hCPU, address))); + } + + inline static uint8* ppcMem_getDataPtr(PPCInterpreter_t* hCPU, uint32 vAddr) + { + return memory_base + ppcMem_translateVirtualDataToPhysicalAddr(hCPU, vAddr); + } + + inline static void ppcMem_writeDataDouble(PPCInterpreter_t* hCPU, uint32 address, double vf) + { + uint64 v = *(uint64*)&vf; + uint32 v1 = v & 0xFFFFFFFF; + uint32 v2 = v >> 32; + uint8* ptr = ppcMem_getDataPtr(hCPU, address); + *(uint32*)(ptr + 4) = CPU_swapEndianU32(v1); + *(uint32*)(ptr + 0) = CPU_swapEndianU32(v2); + } + + inline static void ppcMem_writeDataU64(PPCInterpreter_t* hCPU, uint32 address, uint64 v) + { + *(uint64*)(ppcMem_getDataPtr(hCPU, address)) = CPU_swapEndianU64(v); + } + + inline static void ppcMem_writeDataU32(PPCInterpreter_t* hCPU, uint32 address, uint32 v) + { + uint32 pAddr = ppcMem_translateVirtualDataToPhysicalAddr(hCPU, address); + if (hCPU->memoryException) + return; + + if (pAddr >= 0x0c000000 && pAddr < 0x0d100000) + { + cemu_assert_unimplemented(); + return; + } + *(uint32*)(memory_base + pAddr) = CPU_swapEndianU32(v); + } + + inline static void ppcMem_writeDataU16(PPCInterpreter_t* hCPU, uint32 address, uint16 v) + { + *(uint16*)(ppcMem_getDataPtr(hCPU, address)) = CPU_swapEndianU16(v); + } + + inline static void ppcMem_writeDataU8(PPCInterpreter_t* hCPU, uint32 address, uint8 v) + { + *(uint8*)(ppcMem_getDataPtr(hCPU, address)) = v; + } + + inline static double ppcMem_readDataDouble(PPCInterpreter_t* hCPU, uint32 address) + { + uint32 v[2]; + v[1] = *(uint32*)(ppcMem_getDataPtr(hCPU, address)); + v[0] = *(uint32*)(ppcMem_getDataPtr(hCPU, address) + 4); + v[0] = CPU_swapEndianU32(v[0]); + v[1] = CPU_swapEndianU32(v[1]); + return *(double*)v; + } + + inline static float ppcMem_readDataFloat(PPCInterpreter_t* hCPU, uint32 address) + { + uint32 v = *(uint32*)(ppcMem_getDataPtr(hCPU, address)); + v = CPU_swapEndianU32(v); + return *(float*)&v; + } + + inline static uint64 ppcMem_readDataU64(PPCInterpreter_t* hCPU, uint32 address) + { + uint64 v = *(uint64*)(ppcMem_getDataPtr(hCPU, address)); + return CPU_swapEndianU64(v); + } + + inline static uint32 ppcMem_readDataU32(PPCInterpreter_t* hCPU, uint32 address) + { + uint32 pAddr = ppcMem_translateVirtualDataToPhysicalAddr(hCPU, address); + if (hCPU->memoryException) + return 0; + if (pAddr >= 0x01FFF000 && pAddr < 0x02000000) + { + debug_printf("Access u32 boot param block 0x%08x IP %08x LR %08x\n", pAddr, hCPU->instructionPointer, hCPU->spr.LR); + forceLogDebug_printf("Access u32 boot param block 0x%08x (org %08x) IP %08x LR %08x\n", pAddr, address, hCPU->instructionPointer, hCPU->spr.LR); + } + if (pAddr >= 0xFFEB73B0 && pAddr < (0xFFEB73B0+0x40C)) + { + debug_printf("Access cached u32 boot param block 0x%08x IP %08x LR %08x\n", pAddr, hCPU->instructionPointer, hCPU->spr.LR); + forceLogDebug_printf("Access cached u32 boot param block 0x%08x (org %08x) IP %08x LR %08x\n", pAddr, address, hCPU->instructionPointer, hCPU->spr.LR); + } + + if (pAddr >= 0x0c000000 && pAddr < 0x0d100000) + { + cemu_assert_unimplemented(); + return 0; + } + uint32 v = *(uint32*)(memory_base + pAddr); + return CPU_swapEndianU32(v); + } + + inline static uint16 ppcMem_readDataU16(PPCInterpreter_t* hCPU, uint32 address) + { + uint16 v = *(uint16*)(ppcMem_getDataPtr(hCPU, address)); + return CPU_swapEndianU16(v); + } + + inline static uint8 ppcMem_readDataU8(PPCInterpreter_t* hCPU, uint32 address) + { + uint32 pAddr = ppcMem_translateVirtualDataToPhysicalAddr(hCPU, address); + if (pAddr >= 0x0c000000 && pAddr < 0x0d100000) + { + cemu_assert_unimplemented(); + return 0; + } + return *(uint8*)(memory_base + pAddr); + } + + inline static uint64 ppcMem_readDataFloatEx(PPCInterpreter_t* hCPU, uint32 addr) + { + return ConvertToDoubleNoFTZ(_swapEndianU32(*(uint32*)(memory_base + addr))); + } + + inline static void ppcMem_writeDataFloatEx(PPCInterpreter_t* hCPU, uint32 addr, uint64 value) + { + *(uint32*)(memory_base + addr) = _swapEndianU32(ConvertToSingleNoFTZ(value)); + } + + inline static uint64 getTB(PPCInterpreter_t* hCPU) + { + return hCPU->global->tb / 20ULL; + } +}; + +uint32 testIP[100]; +uint32 testIPC = 0; + +template <typename ppcItpCtrl> +class PPCInterpreterContainer +{ +public: +#include "PPCInterpreterSPR.hpp" +#include "PPCInterpreterOPC.hpp" +#include "PPCInterpreterLoadStore.hpp" +#include "PPCInterpreterALU.hpp" + + static void executeInstruction(PPCInterpreter_t* hCPU) + { + if constexpr(ppcItpCtrl::allowSupervisorMode) + { + hCPU->global->tb++; + } + +#ifdef __DEBUG_OUTPUT_INSTRUCTION + debug_printf("%08x: ", hCPU->instructionPointer); +#endif + + uint32 opcode = ppcItpCtrl::memory_readCodeU32(hCPU, hCPU->instructionPointer); + + switch ((opcode >> 26)) + { + case 0: + debug_printf("ZERO[NOP] | 0x%08X\n", (unsigned int)hCPU->instructionPointer); + #ifndef PUBLIC_RELEASE + assert_dbg(); + while (true) std::this_thread::sleep_for(std::chrono::seconds(1)); + #endif + hCPU->instructionPointer += 4; + break; + case 1: // virtual HLE + PPCInterpreter_virtualHLE(hCPU, opcode); + break; + case 4: + switch (PPC_getBits(opcode, 30, 5)) + { + case 0: // subcategory compare + switch (PPC_getBits(opcode, 25, 5)) + { + case 0: // Sonic All Stars Racing + PPCInterpreter_PS_CMPU0(hCPU, opcode); + break; + case 1: + PPCInterpreter_PS_CMPO0(hCPU, opcode); + break; + case 2: // Assassin's Creed 3, Sonic All Stars Racing + PPCInterpreter_PS_CMPU1(hCPU, opcode); + break; + default: + debug_printf("Unknown execute %04X as [4->0] at %08X\n", PPC_getBits(opcode, 25, 5), hCPU->instructionPointer); + cemu_assert_unimplemented(); + break; + } + break; + case 6: + PPCInterpreter_PSQ_LX(hCPU, opcode); + break; + case 7: + PPCInterpreter_PSQ_STX(hCPU, opcode); + break; + case 8: + switch (PPC_getBits(opcode, 25, 5)) + { + case 1: + PPCInterpreter_PS_NEG(hCPU, opcode); + break; + case 2: + PPCInterpreter_PS_MR(hCPU, opcode); + break; + case 4: + PPCInterpreter_PS_NABS(hCPU, opcode); + break; + case 8: + PPCInterpreter_PS_ABS(hCPU, opcode); + break; + default: + debug_printf("Unknown execute %04X as [4->8] at %08X\n", PPC_getBits(opcode, 25, 5), hCPU->instructionPointer); + cemu_assert_unimplemented(); + break; + } + break; + case 10: + PPCInterpreter_PS_SUM0(hCPU, opcode); + break; + case 11: + PPCInterpreter_PS_SUM1(hCPU, opcode); + break; + case 12: + PPCInterpreter_PS_MULS0(hCPU, opcode); + break; + case 13: + PPCInterpreter_PS_MULS1(hCPU, opcode); + break; + case 14: + PPCInterpreter_PS_MADDS0(hCPU, opcode); + break; + case 15: + PPCInterpreter_PS_MADDS1(hCPU, opcode); + break; + case 16: // sub category - merge + switch (PPC_getBits(opcode, 25, 5)) + { + case 16: + PPCInterpreter_PS_MERGE00(hCPU, opcode); + break; + case 17: + PPCInterpreter_PS_MERGE01(hCPU, opcode); + break; + case 18: + PPCInterpreter_PS_MERGE10(hCPU, opcode); + break; + case 19: + PPCInterpreter_PS_MERGE11(hCPU, opcode); + break; + default: + debug_printf("Unknown execute %04X as [4->16] at %08X\n", PPC_getBits(opcode, 25, 5), hCPU->instructionPointer); + debugBreakpoint(); + break; + } + break; + case 18: + PPCInterpreter_PS_DIV(hCPU, opcode); + break; + case 20: + PPCInterpreter_PS_SUB(hCPU, opcode); + break; + case 21: + PPCInterpreter_PS_ADD(hCPU, opcode); + break; + case 22: + PPCInterpreter_DCBZL(hCPU, opcode); + break; + case 23: + PPCInterpreter_PS_SEL(hCPU, opcode); + break; + case 24: + PPCInterpreter_PS_RES(hCPU, opcode); + break; + case 25: + PPCInterpreter_PS_MUL(hCPU, opcode); + break; + case 26: // sub category with only one entry - RSQRTE + PPCInterpreter_PS_RSQRTE(hCPU, opcode); + break; + case 28: + PPCInterpreter_PS_MSUB(hCPU, opcode); + break; + case 29: + PPCInterpreter_PS_MADD(hCPU, opcode); + break; + case 30: + PPCInterpreter_PS_NMSUB(hCPU, opcode); + break; + case 31: + PPCInterpreter_PS_NMADD(hCPU, opcode); + break; + default: + debug_printf("Unknown execute %04X as [4] at %08X\n", PPC_getBits(opcode, 30, 5), hCPU->instructionPointer); + cemu_assert_unimplemented(); + break; + } + break; + case 7: + PPCInterpreter_MULLI(hCPU, opcode); + break; + case 8: + PPCInterpreter_SUBFIC(hCPU, opcode); + break; + case 10: + PPCInterpreter_CMPLI(hCPU, opcode); + break; + case 11: + PPCInterpreter_CMPI(hCPU, opcode); + break; + case 12: + PPCInterpreter_ADDIC(hCPU, opcode); + break; + case 13: + PPCInterpreter_ADDIC_(hCPU, opcode); + break; + case 14: + PPCInterpreter_ADDI(hCPU, opcode); + break; + case 15: + PPCInterpreter_ADDIS(hCPU, opcode); + break; + case 16: + PPCInterpreter_BCX(hCPU, opcode); + break; + case 17: + if (PPC_getBits(opcode, 30, 1) == 1) { + PPCInterpreter_SC(hCPU, opcode); + } + else { + debug_printf("Unsupported Opcode [0x17 --> 0x0]\n"); + cemu_assert_unimplemented(); + } + break; + case 18: + PPCInterpreter_BX(hCPU, opcode); + break; + case 19: // opcode category + switch (PPC_getBits(opcode, 30, 10)) + { + case 0: + PPCInterpreter_MCRF(hCPU, opcode); + break; + case 16: + PPCInterpreter_BCLRX(hCPU, opcode); + break; + case 33: + PPCInterpreter_CRNOR(hCPU, opcode); + break; + case 50: + PPCInterpreter_RFI(hCPU, opcode); + break; + case 129: + PPCInterpreter_CRANDC(hCPU, opcode); + break; + case 150: + PPCInterpreter_ISYNC(hCPU, opcode); + break; + case 193: + PPCInterpreter_CRXOR(hCPU, opcode); + break; + case 257: + PPCInterpreter_CRAND(hCPU, opcode); + break; + case 289: + PPCInterpreter_CREQV(hCPU, opcode); + break; + case 417: + PPCInterpreter_CRORC(hCPU, opcode); + break; + case 449: + PPCInterpreter_CROR(hCPU, opcode); + break; + case 528: + PPCInterpreter_BCCTR(hCPU, opcode); + break; + default: + debug_printf("Unknown execute %04X as [19] at %08X\n", PPC_getBits(opcode, 30, 10), hCPU->instructionPointer); + cemu_assert_unimplemented(); + break; + } + break; + case 20: + PPCInterpreter_RLWIMI(hCPU, opcode); + break; + case 21: + PPCInterpreter_RLWINM(hCPU, opcode); + break; + case 23: + PPCInterpreter_RLWNM(hCPU, opcode); + break; + case 24: + PPCInterpreter_ORI(hCPU, opcode); + break; + case 25: + PPCInterpreter_ORIS(hCPU, opcode); + break; + case 26: + PPCInterpreter_XORI(hCPU, opcode); + break; + case 27: + PPCInterpreter_XORIS(hCPU, opcode); + break; + case 28: + PPCInterpreter_ANDI_(hCPU, opcode); + break; + case 29: + PPCInterpreter_ANDIS_(hCPU, opcode); + break; + case 31: // opcode category + switch (PPC_getBits(opcode, 30, 10)) + { + case 0: + PPCInterpreter_CMP(hCPU, opcode); + break; + case 4: + #ifndef PUBLIC_RELEASE + debug_printf("TW instruction executed at %08x\n", hCPU->instructionPointer); + #endif + PPCInterpreter_TW(hCPU, opcode); + break; + case 8: + PPCInterpreter_SUBFC(hCPU, opcode); + break; + case 10: + PPCInterpreter_ADDC(hCPU, opcode); + break; + case 11: + PPCInterpreter_MULHWU_(hCPU, opcode); + break; + case 19: + PPCInterpreter_MFCR(hCPU, opcode); + break; + case 20: + PPCInterpreter_LWARX(hCPU, opcode); + break; + case 23: + PPCInterpreter_LWZX(hCPU, opcode); + break; + case 24: + PPCInterpreter_SLWX(hCPU, opcode); + break; + case 26: + PPCInterpreter_CNTLZW(hCPU, opcode); + break; + case 28: + PPCInterpreter_ANDX(hCPU, opcode); + break; + case 32: + PPCInterpreter_CMPL(hCPU, opcode); + break; + case 40: + PPCInterpreter_SUBF(hCPU, opcode); + break; + case 54: + PPCInterpreter_DCBST(hCPU, opcode); + break; + case 55: + PPCInterpreter_LWZXU(hCPU, opcode); + break; + case 60: + PPCInterpreter_ANDCX(hCPU, opcode); + break; + case 75: + PPCInterpreter_MULHW_(hCPU, opcode); + break; + case 83: + PPCInterpreter_MFMSR(hCPU, opcode); + break; + case 86: + PPCInterpreter_DCBF(hCPU, opcode); + break; + case 87: + PPCInterpreter_LBZX(hCPU, opcode); + break; + case 104: + PPCInterpreter_NEG(hCPU, opcode); + break; + case 119: // Sonic Lost World + PPCInterpreter_LBZXU(hCPU, opcode); + break; + case 124: + PPCInterpreter_NORX(hCPU, opcode); + break; + case 136: + PPCInterpreter_SUBFE(hCPU, opcode); + break; + case 138: + PPCInterpreter_ADDE(hCPU, opcode); + break; + case 144: + PPCInterpreter_MTCRF(hCPU, opcode); + break; + case 146: + PPCInterpreter_MTMSR(hCPU, opcode); + break; + case 150: + PPCInterpreter_STWCX(hCPU, opcode); + break; + case 151: + PPCInterpreter_STWX(hCPU, opcode); + break; + case 183: + PPCInterpreter_STWUX(hCPU, opcode); + break; + case 200: + PPCInterpreter_SUBFZE(hCPU, opcode); + break; + case 202: + PPCInterpreter_ADDZE(hCPU, opcode); + break; + case 210: + PPCInterpreter_MTSR(hCPU, opcode); + break; + case 215: + PPCInterpreter_STBX(hCPU, opcode); + break; + case 232: // Trine 2 + PPCInterpreter_SUBFME(hCPU, opcode); + break; + case 234: + PPCInterpreter_ADDME(hCPU, opcode); + break; + case 235: + PPCInterpreter_MULLW(hCPU, opcode); + break; + case 247: + PPCInterpreter_STBUX(hCPU, opcode); + break; + case 266: + PPCInterpreter_ADD(hCPU, opcode); + break; + case 278: + PPCInterpreter_DCBT(hCPU, opcode); + break; + case 279: + PPCInterpreter_LHZX(hCPU, opcode); + break; + case 284: + PPCInterpreter_EQV(hCPU, opcode); + break; + case 306: + PPCInterpreter_TLBIE(hCPU, opcode); + break; + case 311: // Wii U Menu v177 (US) + PPCInterpreter_LHZUX(hCPU, opcode); + break; + case 316: + PPCInterpreter_XOR(hCPU, opcode); + break; + case 339: + PPCInterpreter_MFSPR(hCPU, opcode); + break; + case 343: + PPCInterpreter_LHAX(hCPU, opcode); + break; + case 371: + PPCInterpreter_MFTB(hCPU, opcode); + break; + case 375: // Wii U Menu v177 (US) + PPCInterpreter_LHAUX(hCPU, opcode); + break; + case 407: + PPCInterpreter_STHX(hCPU, opcode); + break; + case 412: + PPCInterpreter_ORC(hCPU, opcode); + break; + case 439: + PPCInterpreter_STHUX(hCPU, opcode); + break; + case 444: + PPCInterpreter_OR(hCPU, opcode); + break; + case 459: + PPCInterpreter_DIVWU(hCPU, opcode); + break; + case 467: + PPCInterpreter_MTSPR(hCPU, opcode); + break; + case 470: + PPCInterpreter_DCBI(hCPU, opcode); + break; + case 476: + PPCInterpreter_NANDX(hCPU, opcode); + break; + case 491: + PPCInterpreter_DIVW(hCPU, opcode); + break; + case 512: + PPCInterpreter_MCRXR(hCPU, opcode); + break; + case 520: // Affordable Space Adventures + other Unity games + PPCInterpreter_SUBFCO(hCPU, opcode); + break; + case 522: + PPCInterpreter_ADDCO(hCPU, opcode); + break; + case 534: + PPCInterpreter_LWBRX(hCPU, opcode); + break; + case 535: + PPCInterpreter_LFSX(hCPU, opcode); + break; + case 536: + PPCInterpreter_SRWX(hCPU, opcode); + break; + case 552: + PPCInterpreter_SUBFO(hCPU, opcode); + break; + case 566: + PPCInterpreter_TLBSYNC(hCPU, opcode); + break; + case 567: + PPCInterpreter_LFSUX(hCPU, opcode); + break; + case 595: + PPCInterpreter_MFSR(hCPU, opcode); + break; + case 597: + PPCInterpreter_LSWI(hCPU, opcode); + break; + case 598: + PPCInterpreter_SYNC(hCPU, opcode); + break; + case 599: + PPCInterpreter_LFDX(hCPU, opcode); + break; + case 616: + PPCInterpreter_NEGO(hCPU, opcode); + break; + case 631: + PPCInterpreter_LFDUX(hCPU, opcode); + break; + case 648: // 136 | OE + PPCInterpreter_SUBFEO(hCPU, opcode); + break; + case 650: // 138 | OE + PPCInterpreter_ADDEO(hCPU, opcode); + break; + case 662: + PPCInterpreter_STWBRX(hCPU, opcode); + break; + case 663: + PPCInterpreter_STFSX(hCPU, opcode); + break; + case 695: + PPCInterpreter_STFSUX(hCPU, opcode); + break; + case 725: + PPCInterpreter_STSWI(hCPU, opcode); + break; + case 727: + PPCInterpreter_STFDX(hCPU, opcode); + break; + case 747: + PPCInterpreter_MULLWO(hCPU, opcode); + break; + case 759: + PPCInterpreter_STFDUX(hCPU, opcode); + break; + case 778: + PPCInterpreter_ADDO(hCPU, opcode); + break; + case 790: + PPCInterpreter_LHBRX(hCPU, opcode); + break; + case 792: + PPCInterpreter_SRAW(hCPU, opcode); + break; + case 824: + PPCInterpreter_SRAWI(hCPU, opcode); + break; + case 854: + PPCInterpreter_EIEIO(hCPU, opcode); + break; + case 918: + PPCInterpreter_STHBRX(hCPU, opcode); + break; + case 922: + PPCInterpreter_EXTSH(hCPU, opcode); + break; + case 954: + PPCInterpreter_EXTSB(hCPU, opcode); + break; + case 971: + PPCInterpreter_DIVWUO(hCPU, opcode); + break; + case 982: + PPCInterpreter_ICBI(hCPU, opcode); + break; + case 983: + PPCInterpreter_STFIWX(hCPU, opcode); + break; + case 1003: + PPCInterpreter_DIVWO(hCPU, opcode); + break; + case 1014: + PPCInterpreter_DCBZ(hCPU, opcode); + break; + default: + debug_printf("Unknown execute %04X as [31] at %08X\n", PPC_getBits(opcode, 30, 10), hCPU->instructionPointer); + #ifndef PUBLIC_RELEASE + assert_dbg(); + #endif + hCPU->instructionPointer += 4; + break; + } + break; + case 32: + PPCInterpreter_LWZ(hCPU, opcode); + break; + case 33: + PPCInterpreter_LWZU(hCPU, opcode); + break; + case 34: + PPCInterpreter_LBZ(hCPU, opcode); + break; + case 35: + PPCInterpreter_LBZU(hCPU, opcode); + break; + case 36: + PPCInterpreter_STW(hCPU, opcode); + break; + case 37: + PPCInterpreter_STWU(hCPU, opcode); + break; + case 38: + PPCInterpreter_STB(hCPU, opcode); + break; + case 39: + PPCInterpreter_STBU(hCPU, opcode); + break; + case 40: + PPCInterpreter_LHZ(hCPU, opcode); + break; + case 41: + PPCInterpreter_LHZU(hCPU, opcode); + break; + case 42: + PPCInterpreter_LHA(hCPU, opcode); + break; + case 43: + PPCInterpreter_LHAU(hCPU, opcode); + break; + case 44: + PPCInterpreter_STH(hCPU, opcode); + break; + case 45: + PPCInterpreter_STHU(hCPU, opcode); + break; + case 46: + PPCInterpreter_LMW(hCPU, opcode); + break; + case 47: + PPCInterpreter_STMW(hCPU, opcode); + break; + case 48: + PPCInterpreter_LFS(hCPU, opcode); + break; + case 49: + PPCInterpreter_LFSU(hCPU, opcode); + break; + case 50: + PPCInterpreter_LFD(hCPU, opcode); + break; + case 51: + PPCInterpreter_LFDU(hCPU, opcode); + break; + case 52: + PPCInterpreter_STFS(hCPU, opcode); + break; + case 53: + PPCInterpreter_STFSU(hCPU, opcode); + break; + case 54: + PPCInterpreter_STFD(hCPU, opcode); + break; + case 55: + PPCInterpreter_STFDU(hCPU, opcode); + break; + case 56: + PPCInterpreter_PSQ_L(hCPU, opcode); + break; + case 57: + PPCInterpreter_PSQ_LU(hCPU, opcode); + break; + case 59: //Opcode category + switch (PPC_getBits(opcode, 30, 5)) + { + case 18: + PPCInterpreter_FDIVS(hCPU, opcode); + break; + case 20: + PPCInterpreter_FSUBS(hCPU, opcode); + break; + case 21: + PPCInterpreter_FADDS(hCPU, opcode); + break; + case 24: + PPCInterpreter_FRES(hCPU, opcode); + break; + case 25: + PPCInterpreter_FMULS(hCPU, opcode); + break; + case 28: + PPCInterpreter_FMSUBS(hCPU, opcode); + break; + case 29: + PPCInterpreter_FMADDS(hCPU, opcode); + break; + case 30: + PPCInterpreter_FNMSUBS(hCPU, opcode); + break; + case 31: + PPCInterpreter_FNMADDS(hCPU, opcode); + break; + default: + debug_printf("Unknown execute %04X as [59] at %08X\n", PPC_getBits(opcode, 30, 10), hCPU->instructionPointer); + cemu_assert_unimplemented(); + break; + } + break; + case 60: + PPCInterpreter_PSQ_ST(hCPU, opcode); + break; + case 61: + PPCInterpreter_PSQ_STU(hCPU, opcode); + break; + case 63: // opcode category + switch (PPC_getBits(opcode, 30, 5)) + { + case 0: + PPCInterpreter_FCMPU(hCPU, opcode); + break; + case 12: + PPCInterpreter_FRSP(hCPU, opcode); + break; + case 15: + PPCInterpreter_FCTIWZ(hCPU, opcode); + break; + case 18: + PPCInterpreter_FDIV(hCPU, opcode); + break; + case 20: + PPCInterpreter_FSUB(hCPU, opcode); + break; + case 21: + PPCInterpreter_FADD(hCPU, opcode); + break; + case 23: + PPCInterpreter_FSEL(hCPU, opcode); + break; + case 25: + PPCInterpreter_FMUL(hCPU, opcode); + break; + case 26: + PPCInterpreter_FRSQRTE(hCPU, opcode); + break; + case 28: + PPCInterpreter_FMSUB(hCPU, opcode); + break; + case 29: + PPCInterpreter_FMADD(hCPU, opcode); + break; + case 30: + PPCInterpreter_FNMSUB(hCPU, opcode); + break; + case 31: + PPCInterpreter_FNMADD(hCPU, opcode); + break; + default: + switch (PPC_getBits(opcode, 30, 10)) + { + case 14: + PPCInterpreter_FCTIW(hCPU, opcode); + break; + case 32: + PPCInterpreter_FCMPO(hCPU, opcode); + break; + case 38: + PPCInterpreter_MTFSB1X(hCPU, opcode); + break; + case 40: + PPCInterpreter_FNEG(hCPU, opcode); + break; + case 72: + PPCInterpreter_FMR(hCPU, opcode); + break; + case 136: // Darksiders 2 + PPCInterpreter_FNABS(hCPU, opcode); + break; + case 264: + PPCInterpreter_FABS(hCPU, opcode); + break; + case 583: + PPCInterpreter_MFFS(hCPU, opcode); + break; + case 711: // IBM documentation has this wrong as 771? + PPCInterpreter_MTFSF(hCPU, opcode); + break; + default: + debug_printf("Unknown execute %04X as [63] at %08X\n", PPC_getBits(opcode, 30, 10), hCPU->instructionPointer); + cemu_assert_unimplemented(); + break; + } + } + break; + default: + debug_printf("Unknown execute %04X at %08X\n", PPC_getBits(opcode, 5, 6), (unsigned int)hCPU->instructionPointer); + cemu_assert_unimplemented(); + } + } +}; + +// Slim interpreter, trades some features for extra performance +// Used when emulator runs in CafeOS HLE mode +// Assumes the following: +// - No MMU (linear memory with 1:1 mapping of physical to virtual) +// - No interrupts +// - Always runs in user mode +// - Paired single mode is always enabled +void PPCInterpreterSlim_executeInstruction(PPCInterpreter_t* hCPU) +{ + PPCInterpreterContainer<PPCItpCafeOSUsermode>::executeInstruction(hCPU); +} + +// Full interpreter, supports most PowerPC features +// Used when emulator runs in LLE mode +void PPCInterpreterFull_executeInstruction(PPCInterpreter_t* hCPU) +{ + PPCInterpreterContainer<PPCItpSupervisorWithMMU>::executeInstruction(hCPU); +} diff --git a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h new file mode 100644 index 00000000..bc8458d9 --- /dev/null +++ b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h @@ -0,0 +1,300 @@ +#pragma once + +#include "Cafe/HW/Espresso/PPCState.h" + +// SPR constants +#define SPR_XER 1 +#define SPR_LR 8 +#define SPR_CTR 9 +#define SPR_DEC 22 +#define SPR_SRR0 26 +#define SPR_SRR1 27 +#define SPR_HID0 1008 +#define SPR_HID1 1009 +#define SPR_HID2 920 +#define SPR_TBL 268 +#define SPR_TBU 269 +#define SPR_DMAU 922 +#define SPR_DMAL 923 + +// graphics quantization registers +#define SPR_GQR0 912 +#define SPR_GQR1 913 +#define SPR_GQR2 914 +#define SPR_GQR3 915 +#define SPR_GQR4 916 +#define SPR_GQR5 917 +#define SPR_GQR6 918 +#define SPR_GQR7 919 + +// user graphics quantization registers +#define SPR_UGQR0 896 +#define SPR_UGQR1 897 +#define SPR_UGQR2 898 +#define SPR_UGQR3 899 +#define SPR_UGQR4 900 +#define SPR_UGQR5 901 +#define SPR_UGQR6 902 +#define SPR_UGQR7 903 + +#define SPR_FPECR 1022 // used by the OS to store values + +#define SPR_PVR 287 // processor version, for Wii U this must be 0x7001xxxx - this register is only readable +#define SPR_UPIR 1007 // core index +#define SPR_SCR 947 // core control +#define SPR_SDR1 25 + +// reversed CR bit indices +#define CR_BIT_LT 0 +#define CR_BIT_GT 1 +#define CR_BIT_EQ 2 +#define CR_BIT_SO 3 + +#define XER_SO (1<<31) // summary overflow bit +#define XER_OV (1<<30) // overflow bit +#define XER_BIT_CA (29) // carry bit index. To accelerate frequent access, this bit is stored as a separate uint8 + +// FPSCR +#define FPSCR_VXSNAN (1<<24) +#define FPSCR_VXVC (1<<19) + +#define MSR_SF (1<<31) +#define MSR_UNKNOWN (1<<30) +#define MSR_UNKNOWN2 (1<<27) +#define MSR_VEC (1<<25) +#define MSR_POW (1<<18) +#define MSR_TGPR (1<<15) +#define MSR_ILE (1<<16) +#define MSR_EE (1<<15) +#define MSR_PR (1<<14) +#define MSR_FP (1<<13) +#define MSR_ME (1<<12) +#define MSR_FE0 (1<<11) +#define MSR_SE (1<<10) +#define MSR_BE (1<<9) +#define MSR_FE1 (1<<8) +#define MSR_IP (1<<6) +#define MSR_IR (1<<5) +#define MSR_DR (1<<4) +#define MSR_PM (1<<2) +#define MSR_RI (1<<1) +#define MSR_LE (1) + +// helpers + +#define GET_MSR_BIT(__bit) ((hCPU->sprExtended.msr&(__bit))!=0) + +#define opHasRC() ((opcode & PPC_OPC_RC) != 0) + +// assume fixed values for PSE/LSQE. This optimization is possible because Wii U applications run only in user mode (todo - handle this correctly in LLE mode) +//#define PPC_LSQE (hCPU->LSQE) +//#define PPC_PSE (hCPU->PSE) + +#define PPC_LSQE (1) +#define PPC_PSE (1) + +#define PPC_ASSERT(v) + +#define PPC_OPC_RC 1 +#define PPC_OPC_OE (1<<10) +#define PPC_OPC_LK 1 +#define PPC_OPC_AA (1<<1) + +#define PPC_OPC_TEMPL_A(opc, rD, rA, rB, rC) {rD=((opc)>>21)&0x1f;rA=((opc)>>16)&0x1f;rB=((opc)>>11)&0x1f;rC=((opc)>>6)&0x1f;} +#define PPC_OPC_TEMPL_B(opc, BO, BI, BD) {BO=((opc)>>21)&0x1f;BI=((opc)>>16)&0x1f;BD=(uint32)(sint32)(sint16)((opc)&0xfffc);} +#define PPC_OPC_TEMPL_D_SImm(opc, rD, rA, imm) {rD=((opc)>>21)&0x1f;rA=((opc)>>16)&0x1f;imm=(uint32)(sint32)(sint16)((opc)&0xffff);} +#define PPC_OPC_TEMPL_D_UImm(opc, rD, rA, imm) {rD=((opc)>>21)&0x1f;rA=((opc)>>16)&0x1f;imm=(opc)&0xffff;} +#define PPC_OPC_TEMPL_D_Shift16(opc, rD, rA, imm) {rD=((opc)>>21)&0x1f;rA=((opc)>>16)&0x1f;imm=(opc)<<16;} +#define PPC_OPC_TEMPL_I(opc, LI) {LI=(opc)&0x3fffffc;if (LI&0x02000000) LI |= 0xfc000000;} +#define PPC_OPC_TEMPL_M(opc, rS, rA, SH, MB, ME) {rS=((opc)>>21)&0x1f;rA=((opc)>>16)&0x1f;SH=((opc)>>11)&0x1f;MB=((opc)>>6)&0x1f;ME=((opc)>>1)&0x1f;} +#define PPC_OPC_TEMPL_X(opc, rS, rA, rB) {rS=((opc)>>21)&0x1f;rA=((opc)>>16)&0x1f;rB=((opc)>>11)&0x1f;} +#define PPC_OPC_TEMPL_XFX(opc, rS, CRM) {rS=((opc)>>21)&0x1f;CRM=((opc)>>12)&0xff;} +#define PPC_OPC_TEMPL_XO(opc, rS, rA, rB) {rS=((opc)>>21)&0x1f;rA=((opc)>>16)&0x1f;rB=((opc)>>11)&0x1f;} +#define PPC_OPC_TEMPL_XL(opc, BO, BI, BD) {BO=((opc)>>21)&0x1f;BI=((opc)>>16)&0x1f;BD=((opc)>>11)&0x1f;} +#define PPC_OPC_TEMPL_XFL(opc, rB, FM) {rB=((opc)>>11)&0x1f;FM=((opc)>>17)&0xff;} + +#define PPC_OPC_TEMPL3_XO() sint32 rD, rA, rB; rD=((opcode)>>21)&0x1f;rA=((opcode)>>16)&0x1f;rB=((opcode)>>11)&0x1f +#define PPC_OPC_TEMPL_X_CR() sint32 crD, crA, crB; crD=((opcode)>>21)&0x1f;crA=((opcode)>>16)&0x1f;crB=((opcode)>>11)&0x1f + +static inline void ppc_update_cr0(PPCInterpreter_t* hCPU, uint32 r) +{ + hCPU->cr[CR_BIT_SO] = (hCPU->spr.XER&XER_SO) ? 1 : 0; + hCPU->cr[CR_BIT_LT] = ((r != 0) ? 1 : 0) & ((r & 0x80000000) ? 1 : 0); + hCPU->cr[CR_BIT_EQ] = (r == 0); + hCPU->cr[CR_BIT_GT] = hCPU->cr[CR_BIT_EQ] ^ hCPU->cr[CR_BIT_LT] ^ 1; // this works because EQ and LT can never be set at the same time. So the only case where GT becomes 1 is when LT=0 and EQ=0 +} + +static inline uint8 ppc_getCRBit(PPCInterpreter_t* hCPU, uint32 r) +{ + return hCPU->cr[r]; +} + +static inline bool ppc_MTCRFMaskHasCRFieldSet(const uint32 mtcrfMask, const uint32 crIndex) +{ + // 1000 0000 (0x80) -> cr0 + // 0000 0001 (0x01) -> cr7 + return (mtcrfMask & (1 << (7 - crIndex))) != 0; +} + +// returns CR mask with CR0.LT in LSB +static inline uint32 ppc_MTCRFMaskToCRBitMask(const uint32 mtcrfMask) +{ + uint32 crMask = 0; + for (uint32 crF = 0; crF < 8; crF++) + { + if (ppc_MTCRFMaskHasCRFieldSet(mtcrfMask, crF)) + crMask |= (0xF << (crF * 4)); + } + return crMask; +} + +static inline void ppc_setCRBit(PPCInterpreter_t* hCPU, uint32 r, uint8 v) +{ + hCPU->cr[r] = v; +} + +static inline void ppc_setCR(PPCInterpreter_t* hCPU, uint32 cr) +{ + uint32 tempCr = cr; + for (sint32 i = 31; i >= 0; i--) + { + ppc_setCRBit(hCPU, i, tempCr & 1); + tempCr >>= 1; + } +} + +static inline uint32 ppc_getCR(PPCInterpreter_t* hCPU) +{ + uint32 cr = 0; + for (sint32 i = 0; i < 32; i++) + { + cr <<= 1; + if (ppc_getCRBit(hCPU, i)) + cr |= 1; + } + return cr; +} + +// FPU helper + +#define IS_NAN(X) ((((X) & 0x000fffffffffffffULL) != 0) && (((X) & 0x7ff0000000000000ULL) == 0x7ff0000000000000ULL)) +#define IS_QNAN(X) ((((X) & 0x000fffffffffffffULL) != 0) && (((X) & 0x7ff8000000000000ULL) == 0x7ff8000000000000ULL)) +#define IS_SNAN(X) ((((X) & 0x000fffffffffffffULL) != 0) && (((X) & 0x7ff8000000000000ULL) == 0x7ff0000000000000ULL)) + +#define FPSCR_VE (1 << 7) + +inline double roundTo25BitAccuracy(double d) +{ + uint64 v = *(uint64*)&d; + v = (v & 0xFFFFFFFFF8000000ULL) + (v & 0x8000000ULL); + return *(double*)&v; +} + +double fres_espresso(double input); +double frsqrte_espresso(double input); + +void fcmpu_espresso(PPCInterpreter_t* hCPU, int crfD, double a, double b); + +// OPC +void PPCInterpreter_virtualHLE(PPCInterpreter_t* hCPU, unsigned int opcode); + +void PPCInterpreter_MFMSR(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_MTMSR(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_MFTB(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_MTFSB1X(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_MFCR(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_MCRF(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_MTCRF(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_MCRXR(PPCInterpreter_t* hCPU, uint32 Opcode); + +void PPCInterpreter_TLBIE(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_TLBSYNC(PPCInterpreter_t* hCPU, uint32 Opcode); + +void PPCInterpreter_DCBT(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_DCBST(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_DCBZL(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_DCBF(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_DCBI(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_DCBZ(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_ICBI(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_EIEIO(PPCInterpreter_t* hCPU, uint32 Opcode); + +void PPCInterpreter_SC(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_SYNC(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_ISYNC(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_RFI(PPCInterpreter_t* hCPU, uint32 Opcode); + +void PPCInterpreter_BX(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_BCX(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_BCLRX(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_BCCTR(PPCInterpreter_t* hCPU, uint32 Opcode); + +// FPU + +void PPCInterpreter_FCMPO(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_FCMPU(PPCInterpreter_t* hCPU, uint32 Opcode); + +void PPCInterpreter_FMR(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_FSEL(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_FCTIWZ(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_FCTIW(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_FNEG(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_FRSP(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_FRSQRTE(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_FRES(PPCInterpreter_t* hCPU, uint32 Opcode); + +void PPCInterpreter_FABS(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_FNABS(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_FADD(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_FMUL(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_FDIV(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_FSUB(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_FMADD(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_FMSUB(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_FMSUBS(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_FNMADD(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_FNMSUB(PPCInterpreter_t* hCPU, uint32 Opcode); + +void PPCInterpreter_MFFS(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_MTFSF(PPCInterpreter_t* hCPU, uint32 Opcode); + +void PPCInterpreter_FDIVS(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_FMULS(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_FADDS(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_FSUBS(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_FMADDS(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_FNMADDS(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_FNMSUBS(PPCInterpreter_t* hCPU, uint32 Opcode); + +void PPCInterpreter_PS_MERGE00(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_PS_MERGE01(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_PS_MERGE10(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_PS_MERGE11(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_PS_MR(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_PS_NEG(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_PS_ABS(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_PS_NABS(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_PS_RES(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_PS_RSQRTE(PPCInterpreter_t* hCPU, uint32 Opcode); + +void PPCInterpreter_PS_ADD(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_PS_SUB(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_PS_MUL(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_PS_DIV(PPCInterpreter_t* hCPU, uint32 Opcode); + +void PPCInterpreter_PS_MADD(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_PS_NMADD(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_PS_MADDS0(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_PS_MADDS1(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_PS_MSUB(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_PS_NMSUB(PPCInterpreter_t* hCPU, uint32 Opcode); + +void PPCInterpreter_PS_SEL(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_PS_SUM0(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_PS_SUM1(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_PS_MULS0(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_PS_MULS1(PPCInterpreter_t* hCPU, uint32 Opcode); + +void PPCInterpreter_PS_CMPO0(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_PS_CMPU0(PPCInterpreter_t* hCPU, uint32 Opcode); +void PPCInterpreter_PS_CMPU1(PPCInterpreter_t* hCPU, uint32 Opcode); diff --git a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterLoadStore.hpp b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterLoadStore.hpp new file mode 100644 index 00000000..694e05e6 --- /dev/null +++ b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterLoadStore.hpp @@ -0,0 +1,1097 @@ + +#define _signExtend16To32(__v) ((uint32)(sint32)(sint16)(__v)) + +// store + +#define DSI_EXIT() \ + if constexpr(ppcItpCtrl::allowDSI) \ + { \ + if (hCPU->memoryException) \ + { \ + hCPU->memoryException = false; \ + return; \ + } \ + } + +static void PPCInterpreter_STW(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + sint32 rA, rS; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(Opcode, rS, rA, imm); + if (rA != 0) + { + ppcItpCtrl::ppcMem_writeDataU32(hCPU, hCPU->gpr[rA] + imm, hCPU->gpr[rS]); + } + else + { + PPC_ASSERT(true); + } + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_STWU(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + int rA, rS; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(Opcode, rS, rA, imm); + ppcItpCtrl::ppcMem_writeDataU32(hCPU, hCPU->gpr[rA] + imm, hCPU->gpr[rS]); + // check for rA != 0 ? + hCPU->gpr[rA] += imm; + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_STWX(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + int rA, rS, rB; + PPC_OPC_TEMPL_X(Opcode, rS, rA, rB); + ppcItpCtrl::ppcMem_writeDataU32(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB], hCPU->gpr[rS]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_STWCX(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + // http://www.ibm.com/developerworks/library/pa-atom/ + sint32 rA, rS, rB; + PPC_OPC_TEMPL_X(Opcode, rS, rA, rB); + uint32 ea = (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB]; + // check if we hold a reservation for the memory location + + // todo - this isnt accurate. STWCX can succeed even with a different EA if the reserved value remained untouched + if (hCPU->reservedMemAddr == ea) + { + uint32be reservedValue = hCPU->reservedMemValue; // this is the value we expect in memory (if it does not match, STWCX fails) + std::atomic<uint32be>* wordPtr; + if constexpr(ppcItpCtrl::allowSupervisorMode) + { + wordPtr = _rawPtrToAtomic((uint32be*)(memory_base + ppcItpCtrl::ppcMem_translateVirtualDataToPhysicalAddr(hCPU, ea))); + DSI_EXIT(); + } + else + { + wordPtr = _rawPtrToAtomic((uint32be*)memory_getPointerFromVirtualOffset(ea)); + } + uint32be newValue = hCPU->gpr[rS]; + if (!wordPtr->compare_exchange_strong(reservedValue, newValue)) + { + // failed + ppc_setCRBit(hCPU, CR_BIT_LT, 0); + ppc_setCRBit(hCPU, CR_BIT_GT, 0); + ppc_setCRBit(hCPU, CR_BIT_EQ, 0); + } + else + { + // success, new value has been written + ppc_setCRBit(hCPU, CR_BIT_LT, 0); + ppc_setCRBit(hCPU, CR_BIT_GT, 0); + ppc_setCRBit(hCPU, CR_BIT_EQ, 1); + } + ppc_setCRBit(hCPU, CR_BIT_SO, (hCPU->spr.XER&XER_SO) != 0 ? 1 : 0); + // remove reservation + hCPU->reservedMemAddr = 0; + hCPU->reservedMemValue = 0; + } + else + { + // failed + ppc_setCRBit(hCPU, CR_BIT_LT, 0); + ppc_setCRBit(hCPU, CR_BIT_GT, 0); + ppc_setCRBit(hCPU, CR_BIT_EQ, 0); + } + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_STWUX(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + int rA, rS, rB; + PPC_OPC_TEMPL_X(Opcode, rS, rA, rB); + ppcItpCtrl::ppcMem_writeDataU32(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB], hCPU->gpr[rS]); + if (rA) + hCPU->gpr[rA] += hCPU->gpr[rB]; + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_STWBRX(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + int rA, rS, rB; + PPC_OPC_TEMPL_X(Opcode, rS, rA, rB); + ppcItpCtrl::ppcMem_writeDataU32(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB], _swapEndianU32(hCPU->gpr[rS])); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_STMW(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + int rS, rA; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(Opcode, rS, rA, imm); + uint32 ea = (rA ? hCPU->gpr[rA] : 0) + imm; + while (rS <= 31) + { + ppcItpCtrl::ppcMem_writeDataU32(hCPU, ea, hCPU->gpr[rS]); + rS++; + ea += 4; + } + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_STH(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + int rA, rS; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(Opcode, rS, rA, imm); + ppcItpCtrl::ppcMem_writeDataU16(hCPU, (rA ? hCPU->gpr[rA] : 0) + imm, (uint16)hCPU->gpr[rS]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_STHU(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + int rA, rS; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(Opcode, rS, rA, imm); + ppcItpCtrl::ppcMem_writeDataU16(hCPU, (rA ? hCPU->gpr[rA] : 0) + imm, (uint16)hCPU->gpr[rS]); + if (rA) + hCPU->gpr[rA] += imm; + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_STHX(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + int rA, rS, rB; + PPC_OPC_TEMPL_X(Opcode, rS, rA, rB); + ppcItpCtrl::ppcMem_writeDataU16(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB], (uint16)hCPU->gpr[rS]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_STHUX(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + int rA, rS, rB; + PPC_OPC_TEMPL_X(Opcode, rS, rA, rB); + ppcItpCtrl::ppcMem_writeDataU16(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB], (uint16)hCPU->gpr[rS]); + if (rA) + hCPU->gpr[rA] += hCPU->gpr[rB]; + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_STHBRX(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + int rA, rS, rB; + PPC_OPC_TEMPL_X(Opcode, rS, rA, rB); + ppcItpCtrl::ppcMem_writeDataU16(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB], _swapEndianU16((uint16)hCPU->gpr[rS])); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_STB(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + int rA, rS; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(Opcode, rS, rA, imm); + ppcItpCtrl::ppcMem_writeDataU8(hCPU, (rA ? hCPU->gpr[rA] : 0) + imm, (uint8)hCPU->gpr[rS]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_STBU(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + int rA, rS; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(Opcode, rS, rA, imm); + ppcItpCtrl::ppcMem_writeDataU8(hCPU, hCPU->gpr[rA] + imm, (uint8)hCPU->gpr[rS]); + hCPU->gpr[rA] += imm; + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_STBX(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + int rA, rS, rB; + PPC_OPC_TEMPL_X(Opcode, rS, rA, rB); + ppcItpCtrl::ppcMem_writeDataU8(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB], (uint8)hCPU->gpr[rS]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_STBUX(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + int rA, rS, rB; + PPC_OPC_TEMPL_X(Opcode, rS, rA, rB); + ppcItpCtrl::ppcMem_writeDataU8(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB], (uint8)hCPU->gpr[rS]); + if (rA) + hCPU->gpr[rA] += hCPU->gpr[rB]; + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_STSWI(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + int rA, rS, nb; + PPC_OPC_TEMPL_X(Opcode, rS, rA, nb); + if (nb == 0) nb = 32; + uint32 ea = rA ? hCPU->gpr[rA] : 0; + uint32 r = 0; + int i = 0; + while (nb > 0) + { + if (i == 0) + { + r = hCPU->gpr[rS]; + rS++; + rS %= 32; + i = 4; + } + ppcItpCtrl::ppcMem_writeDataU8(hCPU, ea, (r >> 24)); + r <<= 8; + ea++; + i--; + nb--; + } + PPCInterpreter_nextInstruction(hCPU); +} + +// load + +static void PPCInterpreter_LWZ(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + sint32 rA, rD; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(Opcode, rD, rA, imm); + uint32 v = ppcItpCtrl::ppcMem_readDataU32(hCPU, (rA ? hCPU->gpr[rA] : 0) + imm); + DSI_EXIT(); + hCPU->gpr[rD] = v; + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_LWZU(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + sint32 rA, rD; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(Opcode, rD, rA, imm); + hCPU->gpr[rA] += imm; + hCPU->gpr[rD] = ppcItpCtrl::ppcMem_readDataU32(hCPU, hCPU->gpr[rA]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_LMW(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + sint32 rD, rA; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(Opcode, rD, rA, imm); + uint32 ea = (rA ? hCPU->gpr[rA] : 0) + imm; + while (rD <= 31) + { + hCPU->gpr[rD] = ppcItpCtrl::ppcMem_readDataU32(hCPU, ea); + rD++; + ea += 4; + } + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_LWZX(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + sint32 rA, rD, rB; + PPC_OPC_TEMPL_X(Opcode, rD, rA, rB); + hCPU->gpr[rD] = ppcItpCtrl::ppcMem_readDataU32(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_LWZXU(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + sint32 rA, rD, rB; + PPC_OPC_TEMPL_X(Opcode, rD, rA, rB); + uint32 ea = (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB]; + hCPU->gpr[rD] = ppcItpCtrl::ppcMem_readDataU32(hCPU, ea); + if (rA && rA != rD) + hCPU->gpr[rA] = ea; + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_LWBRX(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + sint32 rA, rD, rB; + PPC_OPC_TEMPL_X(Opcode, rD, rA, rB); + hCPU->gpr[rD] = CPU_swapEndianU32(ppcItpCtrl::ppcMem_readDataU32(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB])); + + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_LWARX(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + sint32 rA, rD, rB; + PPC_OPC_TEMPL_X(Opcode, rD, rA, rB); + uint32 ea = (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB]; + hCPU->gpr[rD] = ppcItpCtrl::ppcMem_readDataU32(hCPU, ea); + // set reservation + hCPU->reservedMemAddr = ea; + hCPU->reservedMemValue = hCPU->gpr[rD]; + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_LHZ(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + sint32 rA, rD; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(Opcode, rD, rA, imm); + hCPU->gpr[rD] = ppcItpCtrl::ppcMem_readDataU16(hCPU, (rA ? hCPU->gpr[rA] : 0) + imm); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_LHZU(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + sint32 rA, rD; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(Opcode, rD, rA, imm); + // FIXME: rA!=0 + hCPU->gpr[rD] = ppcItpCtrl::ppcMem_readDataU16(hCPU, hCPU->gpr[rA] + imm); + hCPU->gpr[rA] += imm; + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_LHZX(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + sint32 rA, rD, rB; + PPC_OPC_TEMPL_X(Opcode, rD, rA, rB); + hCPU->gpr[rD] = ppcItpCtrl::ppcMem_readDataU16(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_LHZUX(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + sint32 rA, rD, rB; + PPC_OPC_TEMPL_X(Opcode, rD, rA, rB); + uint32 ea = (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB]; + hCPU->gpr[rD] = ppcItpCtrl::ppcMem_readDataU16(hCPU, ea); + if (rA && rA != rD) + hCPU->gpr[rA] = ea; + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_LHBRX(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + sint32 rA, rD, rB; + PPC_OPC_TEMPL_X(Opcode, rD, rA, rB); + hCPU->gpr[rD] = CPU_swapEndianU16(ppcItpCtrl::ppcMem_readDataU16(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB])); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_LHA(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + sint32 rA, rD; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(Opcode, rD, rA, imm); + hCPU->gpr[rD] = ppcItpCtrl::ppcMem_readDataU16(hCPU, (rA ? hCPU->gpr[rA] : 0) + imm); + hCPU->gpr[rD] = _signExtend16To32(hCPU->gpr[rD]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_LHAU(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + sint32 rA, rD; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(Opcode, rD, rA, imm); + hCPU->gpr[rD] = ppcItpCtrl::ppcMem_readDataU16(hCPU, (rA ? hCPU->gpr[rA] : 0) + imm); + if (rA && rA != rD) + hCPU->gpr[rA] += imm; + hCPU->gpr[rD] = _signExtend16To32(hCPU->gpr[rD]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_LHAUX(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + sint32 rA, rD, rB; + PPC_OPC_TEMPL_X(Opcode, rD, rA, rB); + uint32 ea = (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB]; + hCPU->gpr[rD] = ppcItpCtrl::ppcMem_readDataU16(hCPU, ea); + if (rA && rA != rD) + hCPU->gpr[rA] = ea; + hCPU->gpr[rD] = _signExtend16To32(hCPU->gpr[rD]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_LHAX(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + sint32 rA, rS, rB; + PPC_OPC_TEMPL_X(Opcode, rS, rA, rB); + + hCPU->gpr[rS] = ppcItpCtrl::ppcMem_readDataU16(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB]); + hCPU->gpr[rS] = _signExtend16To32(hCPU->gpr[rS]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_LBZ(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + sint32 rA, rD; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(Opcode, rD, rA, imm); + hCPU->gpr[rD] = ppcItpCtrl::ppcMem_readDataU8(hCPU, (rA ? hCPU->gpr[rA] : 0) + imm); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_LBZX(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + sint32 rA, rD, rB; + PPC_OPC_TEMPL_X(Opcode, rD, rA, rB); + hCPU->gpr[rD] = ppcItpCtrl::ppcMem_readDataU8(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_LBZXU(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + sint32 rA, rD, rB; + PPC_OPC_TEMPL_X(Opcode, rD, rA, rB); + uint32 ea = (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB]; + hCPU->gpr[rD] = ppcItpCtrl::ppcMem_readDataU8(hCPU, ea); + if (rA && rA != rD) + hCPU->gpr[rA] = ea; + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_LBZU(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + sint32 rA, rD; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(Opcode, rD, rA, imm); + PPC_ASSERT(rA == 0); + uint8 r; + uint32 ea = hCPU->gpr[rA] + imm; + hCPU->gpr[rA] = ea; + r = ppcItpCtrl::ppcMem_readDataU8(hCPU, ea); + hCPU->gpr[rD] = r; + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_LSWI(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + sint32 rA, rD, nb; + PPC_OPC_TEMPL_X(Opcode, rD, rA, nb); + if (nb == 0) + nb = 32; + + uint32 ea = rA ? hCPU->gpr[rA] : 0; + uint32 r = 0; + int i = 4; + uint8 v; + while (nb>0) + { + if (i == 0) + { + i = 4; + hCPU->gpr[rD] = r; + rD++; + rD %= 32; + r = 0; + } + v = ppcItpCtrl::ppcMem_readDataU8(hCPU, ea); + r <<= 8; + r |= v; + ea++; + i--; + nb--; + } + while (i) + { + r <<= 8; + i--; + } + hCPU->gpr[rD] = r; + PPCInterpreter_nextInstruction(hCPU); +} + +// floating point load + +static void PPCInterpreter_LFS(PPCInterpreter_t* hCPU, uint32 Opcode) //Copied +{ + FPUCheckAvailable(); + sint32 rA, frD; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(Opcode, frD, rA, imm); + + uint64 val; + //*(uint32*)&Val = ppcItpCtrl::ppcMem_readDataU32(hCPU, (rA?hCPU->gpr[rA]:0)+imm); + val = ppcItpCtrl::ppcMem_readDataFloatEx(hCPU, (rA ? hCPU->gpr[rA] : 0) + imm); + + if (PPC_LSQE) + hCPU->fpr[frD].fp0int = hCPU->fpr[frD].fp1int = val; + else + hCPU->fpr[frD].fp0int = val; + + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_LFSX(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + sint32 rA, frD, rB; + PPC_OPC_TEMPL_X(Opcode, frD, rA, rB); + + uint64 val; + val = ppcItpCtrl::ppcMem_readDataFloatEx(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB]); + + if (PPC_LSQE) + hCPU->fpr[frD].fp0int = hCPU->fpr[frD].fp1int = val; + else + hCPU->fpr[frD].fp0int = val; + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_LFSUX(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + sint32 rA, frD, rB; + PPC_OPC_TEMPL_X(Opcode, frD, rA, rB); + + uint64 Val; + //*(uint32*)&Val = ppcItpCtrl::ppcMem_readDataU32(hCPU, (rA?hCPU->gpr[rA]:0)+hCPU->gpr[rB]); + Val = ppcItpCtrl::ppcMem_readDataFloatEx(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB]); + if (rA) + hCPU->gpr[rA] += hCPU->gpr[rB]; + + if (PPC_LSQE) + hCPU->fpr[frD].fp0int = hCPU->fpr[frD].fp1int = Val; + else + hCPU->fpr[frD].fp0int = Val;//ppcItpCtrl::ppcMem_readDataFloat((rA?hCPU->gpr[rA]:0)+hCPU->gpr[rB]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_LFSU(PPCInterpreter_t* hCPU, uint32 Opcode) //Copied +{ + FPUCheckAvailable(); + sint32 rA, frD; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(Opcode, frD, rA, imm); + uint64 Val; + + //(uint32*)&Val = ppcItpCtrl::ppcMem_readDataU32(hCPU, (rA?hCPU->gpr[rA]:0)+imm); + Val = ppcItpCtrl::ppcMem_readDataFloatEx(hCPU, (rA ? hCPU->gpr[rA] : 0) + imm); + + + if (PPC_LSQE) + hCPU->fpr[frD].fp0int = hCPU->fpr[frD].fp1int = Val; + else + hCPU->fpr[frD].fp0int = Val; + + if (rA) + hCPU->gpr[rA] += imm; + + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_LFD(PPCInterpreter_t* hCPU, uint32 Opcode) //Copied +{ + FPUCheckAvailable(); + sint32 rA, frD; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(Opcode, frD, rA, imm); + hCPU->fpr[frD].fpr = ppcItpCtrl::ppcMem_readDataDouble(hCPU, (rA ? hCPU->gpr[rA] : 0) + imm);//ppcItpCtrl::ppcMem_readDataQUAD((rA?hCPU->gpr[rA]:0)+imm); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_LFDU(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + sint32 rA, frD; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(Opcode, frD, rA, imm); + + hCPU->fpr[frD].fpr = ppcItpCtrl::ppcMem_readDataDouble(hCPU, (rA ? hCPU->gpr[rA] : 0) + imm);//ppcItpCtrl::ppcMem_readDataQUAD((rA?hCPU->gpr[rA]:0)+imm); + if (rA) + hCPU->gpr[rA] += imm; + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_LFDX(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + sint32 rA, frD, rB; + PPC_OPC_TEMPL_X(Opcode, frD, rA, rB); + hCPU->fpr[frD].fpr = ppcItpCtrl::ppcMem_readDataDouble(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_LFDUX(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + sint32 rA, frD, rB; + PPC_OPC_TEMPL_X(Opcode, frD, rA, rB); + hCPU->fpr[frD].fpr = ppcItpCtrl::ppcMem_readDataDouble(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB]); + if (rA) + hCPU->gpr[rA] += hCPU->gpr[rB]; + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_STFS(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + sint32 rA, frD; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(Opcode, frD, rA, imm); + if (PPC_LSQE) + ppcItpCtrl::ppcMem_writeDataFloatEx(hCPU, (rA ? hCPU->gpr[rA] : 0) + imm, hCPU->fpr[frD].fp0int); + else + ppcItpCtrl::ppcMem_writeDataFloatEx(hCPU, (rA ? hCPU->gpr[rA] : 0) + imm, hCPU->fpr[frD].fp0int); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_STFSU(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + sint32 rA, frD; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(Opcode, frD, rA, imm); + + if (PPC_LSQE) + ppcItpCtrl::ppcMem_writeDataFloatEx(hCPU, (rA ? hCPU->gpr[rA] : 0) + imm, hCPU->fpr[frD].fp0int); + else + ppcItpCtrl::ppcMem_writeDataFloatEx(hCPU, (rA ? hCPU->gpr[rA] : 0) + imm, hCPU->fpr[frD].fp0int); + + if (rA) + hCPU->gpr[rA] += imm; + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_STFSX(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + sint32 rA, frS, rB; + PPC_OPC_TEMPL_X(Opcode, frS, rA, rB); + + if (PPC_LSQE) + ppcItpCtrl::ppcMem_writeDataFloatEx(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB], hCPU->fpr[frS].fp0int); + else + ppcItpCtrl::ppcMem_writeDataFloatEx(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB], hCPU->fpr[frS].fp0int); + + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_STFSUX(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + // next instruction + PPCInterpreter_nextInstruction(hCPU); + + int rA, frS, rB; + PPC_OPC_TEMPL_X(Opcode, frS, rA, rB); + + if (PPC_LSQE) + ppcItpCtrl::ppcMem_writeDataFloatEx(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB], hCPU->fpr[frS].fp0int); + else + ppcItpCtrl::ppcMem_writeDataFloatEx(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB], hCPU->fpr[frS].fp0int); + + if (rA) + hCPU->gpr[rA] += hCPU->gpr[rB]; +} + + +static void PPCInterpreter_STFD(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + // next instruction + PPCInterpreter_nextInstruction(hCPU); + + int rA, frD; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(Opcode, frD, rA, imm); + + ppcItpCtrl::ppcMem_writeDataDouble(hCPU, (rA ? hCPU->gpr[rA] : 0) + imm, hCPU->fpr[frD].fpr); + + // debug output +#ifdef __DEBUG_OUTPUT_INSTRUCTION + debug_printf("STFD f%d, %d(r%d)\n", frD, imm, rA); +#endif +} + +static void PPCInterpreter_STFDU(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + // next instruction + PPCInterpreter_nextInstruction(hCPU); + + int rA, frD; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(Opcode, frD, rA, imm); + + if (rA) + { + hCPU->gpr[rA] += imm; + } + else + { + PPC_ASSERT(true); + } + + ppcItpCtrl::ppcMem_writeDataDouble(hCPU, hCPU->gpr[rA], hCPU->fpr[frD].fpr); + + // debug output +#ifdef __DEBUG_OUTPUT_INSTRUCTION + debug_printf("STFD f%d, %d(r%d)\n", frD, imm, rA); +#endif +} + +static void PPCInterpreter_STFDX(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + // next instruction + PPCInterpreter_nextInstruction(hCPU); + + int rA, frS, rB; + PPC_OPC_TEMPL_X(Opcode, frS, rA, rB); + + ppcItpCtrl::ppcMem_writeDataDouble(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB], hCPU->fpr[frS].fpr); + + // debug output +#ifdef __DEBUG_OUTPUT_INSTRUCTION + debug_printf("STFD f%d, r%d+r%d\n", frS, rA, rB); +#endif +} + +static void PPCInterpreter_STFDUX(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + // next instruction + PPCInterpreter_nextInstruction(hCPU); + + int rA, frS, rB; + PPC_OPC_TEMPL_X(Opcode, frS, rA, rB); + + if (rA == 0) + { + ppcItpCtrl::ppcMem_writeDataDouble(hCPU, hCPU->gpr[rB], hCPU->fpr[frS].fpr); + } + else + { + hCPU->gpr[rA] += hCPU->gpr[rB]; + ppcItpCtrl::ppcMem_writeDataDouble(hCPU, hCPU->gpr[rA], hCPU->fpr[frS].fpr); + } + +} + +static void PPCInterpreter_STFIWX(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + sint32 rA, frS, rB; + PPC_OPC_TEMPL_X(Opcode, frS, rA, rB); + + uint32 val = (uint32)hCPU->fpr[frS].fp0int; + ppcItpCtrl::ppcMem_writeDataU32(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB], val); + // next instruction + PPCInterpreter_nextInstruction(hCPU); +} + +// paired single + +// ST_TYPE: +// 4 - uint8 +// 5 - uint16 +// 6 - sint8 +// 7 - sint16 +// 0 - float32 + +#define LD_SCALE(n) ((hCPU->spr.UGQR[0+n] >> 24) & 0x3f) +#define LD_TYPE(n) ((hCPU->spr.UGQR[0+n] >> 16) & 7) +#define ST_SCALE(n) ((hCPU->spr.UGQR[0+n] >> 8) & 0x3f) +#define ST_TYPE(n) ((hCPU->spr.UGQR[0+n] ) & 7) +#define PSW (opcode & 0x8000) +#define PSI ((opcode >> 12) & 7) + +#define PSWX (opcode & (1<<(7+3))) +#define PSIX ((opcode >> 7) & 7) + +static void PPCInterpreter_PSQ_ST(PPCInterpreter_t* hCPU, unsigned int opcode) +{ + FPUCheckAvailable(); + sint32 rA, frD; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(opcode, frD, rA, imm); + + uint32 ea = _uint32_fastSignExtend(imm, 11); + + ea += (rA ? hCPU->gpr[rA] : 0); + + sint32 type = ST_TYPE(PSI); + uint8 scale = (uint8)ST_SCALE(PSI); + + if (opcode & 0x8000) // PSW? + { + if ((type == 4) || (type == 6)) ppcItpCtrl::ppcMem_writeDataU8(hCPU, ea, quantize((float)hCPU->fpr[frD].fp0, type, scale)); + else if ((type == 5) || (type == 7)) ppcItpCtrl::ppcMem_writeDataU16(hCPU, ea, quantize((float)hCPU->fpr[frD].fp0, type, scale)); + else ppcItpCtrl::ppcMem_writeDataU32(hCPU, ea, quantize((float)hCPU->fpr[frD].fp0, type, scale)); + } + else + { + if ((type == 4) || (type == 6)) ppcItpCtrl::ppcMem_writeDataU8(hCPU, ea, quantize((float)hCPU->fpr[frD].fp0, type, scale)); + else if ((type == 5) || (type == 7)) ppcItpCtrl::ppcMem_writeDataU16(hCPU, ea, quantize((float)hCPU->fpr[frD].fp0, type, scale)); + else ppcItpCtrl::ppcMem_writeDataU32(hCPU, ea, quantize((float)hCPU->fpr[frD].fp0, type, scale)); + + if ((type == 4) || (type == 6)) ppcItpCtrl::ppcMem_writeDataU8(hCPU, ea + 1, quantize((float)hCPU->fpr[frD].fp1, type, scale)); + else if ((type == 5) || (type == 7)) ppcItpCtrl::ppcMem_writeDataU16(hCPU, ea + 2, quantize((float)hCPU->fpr[frD].fp1, type, scale)); + else ppcItpCtrl::ppcMem_writeDataU32(hCPU, ea + 4, quantize((float)hCPU->fpr[frD].fp1, type, scale)); + } + + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_PSQ_STU(PPCInterpreter_t* hCPU, unsigned int opcode) +{ + FPUCheckAvailable(); + sint32 rA, frD; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(opcode, frD, rA, imm); + uint32 ea = _uint32_fastSignExtend(imm, 11); + + if (ea & 0x800) + ea |= 0xfffff000; + + ea += (rA ? hCPU->gpr[rA] : 0); + if (rA) + hCPU->gpr[rA] = ea; + + sint32 type = ST_TYPE((opcode >> 12) & 0x7); + uint8 scale = (uint8)ST_SCALE(PSI); + + if (opcode & 0x8000) //PSW? + { + if ((type == 4) || (type == 6)) ppcItpCtrl::ppcMem_writeDataU8(hCPU, ea, quantize((float)hCPU->fpr[frD].fp0, type, scale)); + else if ((type == 5) || (type == 7)) ppcItpCtrl::ppcMem_writeDataU16(hCPU, ea, quantize((float)hCPU->fpr[frD].fp0, type, scale)); + else ppcItpCtrl::ppcMem_writeDataU32(hCPU, ea, quantize((float)hCPU->fpr[frD].fp0, type, scale)); + } + else + { + if ((type == 4) || (type == 6)) ppcItpCtrl::ppcMem_writeDataU8(hCPU, ea, quantize((float)hCPU->fpr[frD].fp0, type, scale)); + else if ((type == 5) || (type == 7)) ppcItpCtrl::ppcMem_writeDataU16(hCPU, ea, quantize((float)hCPU->fpr[frD].fp0, type, scale)); + else ppcItpCtrl::ppcMem_writeDataU32(hCPU, ea, quantize((float)hCPU->fpr[frD].fp0, type, scale)); + + if ((type == 4) || (type == 6)) ppcItpCtrl::ppcMem_writeDataU8(hCPU, ea + 1, quantize((float)hCPU->fpr[frD].fp1, type, scale)); + else if ((type == 5) || (type == 7)) ppcItpCtrl::ppcMem_writeDataU16(hCPU, ea + 2, quantize((float)hCPU->fpr[frD].fp1, type, scale)); + else ppcItpCtrl::ppcMem_writeDataU32(hCPU, ea + 4, quantize((float)hCPU->fpr[frD].fp1, type, scale)); + } + + PPCInterpreter_nextInstruction(hCPU); +} + + +static void PPCInterpreter_PSQ_STX(PPCInterpreter_t* hCPU, unsigned int opcode) +{ + FPUCheckAvailable(); + + // next instruction + PPCInterpreter_nextInstruction(hCPU); + + sint32 frD; + uint32 rA, rB; + frD = (opcode >> (31 - 10)) & 0x1F; + rA = (opcode >> (31 - 15)) & 0x1F; + rB = (opcode >> (31 - 20)) & 0x1F; + uint32 EA = (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB]; + + sint32 type = ST_TYPE(PSIX); + uint8 scale = (uint8)ST_SCALE(PSIX); + + if (PSWX) + { + if ((type == 4) || (type == 6)) ppcItpCtrl::ppcMem_writeDataU8(hCPU, EA, quantize((float)hCPU->fpr[frD].fp0, type, scale)); + else if ((type == 5) || (type == 7)) ppcItpCtrl::ppcMem_writeDataU16(hCPU, EA, quantize((float)hCPU->fpr[frD].fp0, type, scale)); + else ppcItpCtrl::ppcMem_writeDataU32(hCPU, EA, quantize((float)hCPU->fpr[frD].fp0, type, scale)); + } + else + { + if ((type == 4) || (type == 6)) ppcItpCtrl::ppcMem_writeDataU8(hCPU, EA, quantize((float)hCPU->fpr[frD].fp0, type, scale)); + else if ((type == 5) || (type == 7)) ppcItpCtrl::ppcMem_writeDataU16(hCPU, EA, quantize((float)hCPU->fpr[frD].fp0, type, scale)); + else ppcItpCtrl::ppcMem_writeDataU32(hCPU, EA, quantize((float)hCPU->fpr[frD].fp0, type, scale)); + + if ((type == 4) || (type == 6)) ppcItpCtrl::ppcMem_writeDataU8(hCPU, EA + 1, quantize((float)hCPU->fpr[frD].fp1, type, scale)); + else if ((type == 5) || (type == 7)) ppcItpCtrl::ppcMem_writeDataU16(hCPU, EA + 2, quantize((float)hCPU->fpr[frD].fp1, type, scale)); + else ppcItpCtrl::ppcMem_writeDataU32(hCPU, EA + 4, quantize((float)hCPU->fpr[frD].fp1, type, scale)); + } +} + +static void PPCInterpreter_PSQ_L(PPCInterpreter_t* hCPU, unsigned int opcode) +{ + FPUCheckAvailable(); + // next instruction + PPCInterpreter_nextInstruction(hCPU); + + sint32 rA, frD; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(opcode, frD, rA, imm); + + uint32 EA, data0 = 0, data1 = 0; + sint32 type = LD_TYPE(PSI); + uint8 scale = (uint8)LD_SCALE(PSI); + + EA = _uint32_fastSignExtend(opcode, 11); + + if (rA) EA += hCPU->gpr[rA]; + + if (opcode & 0x8000) + { + if ((type == 4) || (type == 6)) *(uint8*)&data0 = ppcItpCtrl::ppcMem_readDataU8(hCPU, EA); + else if ((type == 5) || (type == 7)) *(uint16*)&data0 = ppcItpCtrl::ppcMem_readDataU16(hCPU, EA); + else *(uint32*)&data0 = ppcItpCtrl::ppcMem_readDataU32(hCPU, EA); + if (type == 6) if (data0 & 0x80) data0 |= 0xffffff00; + if (type == 7) if (data0 & 0x8000) data0 |= 0xffff0000; + + hCPU->fpr[frD].fp0 = (double)dequantize(data0, type, scale); + hCPU->fpr[frD].fp1 = 1.0f; + } + else + { + if ((type == 4) || (type == 6)) + { + *(uint8*)&data0 = ppcItpCtrl::ppcMem_readDataU8(hCPU, EA); + *(uint8*)&data1 = ppcItpCtrl::ppcMem_readDataU8(hCPU, EA + 1); + } + else if ((type == 5) || (type == 7)) + { + *(uint16*)&data0 = ppcItpCtrl::ppcMem_readDataU16(hCPU, EA); + *(uint16*)&data1 = ppcItpCtrl::ppcMem_readDataU16(hCPU, EA + 2); + } + else + { + *(uint32*)&data0 = ppcItpCtrl::ppcMem_readDataU32(hCPU, EA); + *(uint32*)&data1 = ppcItpCtrl::ppcMem_readDataU32(hCPU, EA + 4); + } + if (type == 6) + { + if (data0 & 0x80) data0 |= 0xffffff00; + if (data1 & 0x80) data1 |= 0xffffff00; + } + if (type == 7) + { + if (data0 & 0x8000) data0 |= 0xffff0000; + if (data1 & 0x8000) data1 |= 0xffff0000; + } + + hCPU->fpr[frD].fp0 = (double)dequantize(data0, type, scale); + hCPU->fpr[frD].fp1 = (double)dequantize(data1, type, scale); + } +} + +static void PPCInterpreter_PSQ_LU(PPCInterpreter_t* hCPU, unsigned int opcode) +{ + FPUCheckAvailable(); + // next instruction + PPCInterpreter_nextInstruction(hCPU); + + int rA, frD; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(opcode, frD, rA, imm); + + uint32 EA = opcode & 0xfff, data0 = 0, data1 = 0; + sint32 type = LD_TYPE(PSI); + uint8 scale = (uint8)LD_SCALE(PSI); + + if (EA & 0x800) EA |= 0xfffff000; + + if (rA) + { + EA += hCPU->gpr[rA]; + hCPU->gpr[rA] = EA; + } + + if (opcode & 0x8000) + { + if ((type == 4) || (type == 6)) *(uint8*)&data0 = ppcItpCtrl::ppcMem_readDataU8(hCPU, EA); + else if ((type == 5) || (type == 7)) *(uint16*)&data0 = ppcItpCtrl::ppcMem_readDataU16(hCPU, EA); + else *(uint32*)&data0 = ppcItpCtrl::ppcMem_readDataU32(hCPU, EA); + if (type == 6) if (data0 & 0x80) data0 |= 0xffffff00; + if (type == 7) if (data0 & 0x8000) data0 |= 0xffff0000; + + hCPU->fpr[frD].fp0 = (double)dequantize(data0, type, scale); + hCPU->fpr[frD].fp1 = 1.0f; + } + else + { + if ((type == 4) || (type == 6)) *(uint8*)&data0 = ppcItpCtrl::ppcMem_readDataU8(hCPU, EA); + else if ((type == 5) || (type == 7)) *(uint16*)&data0 = ppcItpCtrl::ppcMem_readDataU16(hCPU, EA); + else *(uint32*)&data0 = ppcItpCtrl::ppcMem_readDataU32(hCPU, EA); + if (type == 6) if (data0 & 0x80) data0 |= 0xffffff00; + if (type == 7) if (data0 & 0x8000) data0 |= 0xffff0000; + + if ((type == 4) || (type == 6)) *(uint8*)&data1 = ppcItpCtrl::ppcMem_readDataU8(hCPU, EA + 1); + else if ((type == 5) || (type == 7)) *(uint16*)&data1 = ppcItpCtrl::ppcMem_readDataU16(hCPU, EA + 2); + else *(uint32*)&data1 = ppcItpCtrl::ppcMem_readDataU32(hCPU, EA + 4); + if (type == 6) if (data1 & 0x80) data1 |= 0xffffff00; + if (type == 7) if (data1 & 0x8000) data1 |= 0xffff0000; + + hCPU->fpr[frD].fp0 = (double)dequantize(data0, type, scale); + hCPU->fpr[frD].fp1 = (double)dequantize(data1, type, scale); + } +} + +static void PPCInterpreter_PSQ_LX(PPCInterpreter_t* hCPU, unsigned int opcode) +{ + FPUCheckAvailable(); + // next instruction + PPCInterpreter_nextInstruction(hCPU); + + sint32 frD; + uint32 rA, rB; + + frD = (opcode >> (32 - 11)) & 0x1F; + rA = (opcode >> (32 - 16)) & 0x1F; + rB = (opcode >> (32 - 21)) & 0x1F; + + uint32 EA = (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB]; + + uint32 data0 = 0, data1 = 0; + sint32 type = LD_TYPE(PSIX); + uint8 scale = (uint8)LD_SCALE(PSIX); + + if (PSWX) + { + if ((type == 4) || (type == 6)) *(uint8*)&data0 = ppcItpCtrl::ppcMem_readDataU8(hCPU, EA); + else if ((type == 5) || (type == 7)) *(uint16*)&data0 = ppcItpCtrl::ppcMem_readDataU16(hCPU, EA); + else *(uint32*)&data0 = ppcItpCtrl::ppcMem_readDataU32(hCPU, EA); + if (type == 6) if (data0 & 0x80) data0 |= 0xffffff00; + if (type == 7) if (data0 & 0x8000) data0 |= 0xffff0000; + + hCPU->fpr[frD].fp0 = (double)dequantize(data0, type, scale); + hCPU->fpr[frD].fp1 = 1.0f; + } + else + { + if ((type == 4) || (type == 6)) *(uint8*)&data0 = ppcItpCtrl::ppcMem_readDataU8(hCPU, EA); + else if ((type == 5) || (type == 7)) *(uint16*)&data0 = ppcItpCtrl::ppcMem_readDataU16(hCPU, EA); + else *(uint32*)&data0 = ppcItpCtrl::ppcMem_readDataU32(hCPU, EA); + if (type == 6) if (data0 & 0x80) data0 |= 0xffffff00; + if (type == 7) if (data0 & 0x8000) data0 |= 0xffff0000; + + if ((type == 4) || (type == 6)) *(uint8*)&data1 = ppcItpCtrl::ppcMem_readDataU8(hCPU, EA + 1); + else if ((type == 5) || (type == 7)) *(uint16*)&data1 = ppcItpCtrl::ppcMem_readDataU16(hCPU, EA + 2); + else *(uint32*)&data1 = ppcItpCtrl::ppcMem_readDataU32(hCPU, EA + 4); + if (type == 6) if (data1 & 0x80) data1 |= 0xffffff00; + if (type == 7) if (data1 & 0x8000) data1 |= 0xffff0000; + + hCPU->fpr[frD].fp0 = (double)dequantize(data0, type, scale); + hCPU->fpr[frD].fp1 = (double)dequantize(data1, type, scale); + } +} + +// misc + +static void PPCInterpreter_DCBZ(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + int rA, rB; + rA = (Opcode >> (31 - 15)) & 0x1F; + rB = (Opcode >> (31 - 20)) & 0x1F; + + uint32 ea = (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB]; + ea &= ~31; + if constexpr(ppcItpCtrl::allowSupervisorMode) + { + // todo - optimize + ppcItpCtrl::ppcMem_writeDataU32(hCPU, ea + 0, 0); + DSI_EXIT(); + ppcItpCtrl::ppcMem_writeDataU32(hCPU, ea + 4, 0); + ppcItpCtrl::ppcMem_writeDataU32(hCPU, ea + 8, 0); + ppcItpCtrl::ppcMem_writeDataU32(hCPU, ea + 12, 0); + ppcItpCtrl::ppcMem_writeDataU32(hCPU, ea + 16, 0); + ppcItpCtrl::ppcMem_writeDataU32(hCPU, ea + 20, 0); + ppcItpCtrl::ppcMem_writeDataU32(hCPU, ea + 24, 0); + ppcItpCtrl::ppcMem_writeDataU32(hCPU, ea + 28, 0); + } + else + { + memset((void*)memory_getPointerFromVirtualOffset(ea), 0x00, 0x20); + } + + // debug output +#ifdef __DEBUG_OUTPUT_INSTRUCTION + debug_printf("DCBZ\n"); +#endif + // next instruction + PPCInterpreter_nextInstruction(hCPU); +} \ No newline at end of file diff --git a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterMain.cpp b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterMain.cpp new file mode 100644 index 00000000..c130ec8f --- /dev/null +++ b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterMain.cpp @@ -0,0 +1,107 @@ +#include "PPCInterpreterInternal.h" +#include "Cafe/OS/RPL/rpl.h" +#include "Cafe/GameProfile/GameProfile.h" +#include "Cafe/HW/Espresso/Debugger/Debugger.h" + +thread_local PPCInterpreter_t* ppcInterpreterCurrentInstance; + +// main thread instruction counter and timing +volatile uint64 ppcMainThreadCycleCounter = 0; +uint64 ppcMainThreadDECCycleValue = 0; // value that was set to dec register +uint64 ppcMainThreadDECCycleStart = 0; // at which cycle the dec register was set, if == 0 -> dec is 0 +uint64 ppcCyclesSince2000 = 0; +uint64 ppcCyclesSince2000TimerClock = 0; +uint64 ppcCyclesSince2000_UTC = 0; + +PPCInterpreter_t* PPCInterpreter_createInstance(unsigned int Entrypoint) +{ + PPCInterpreter_t* pData; + // create instance + uint32 prefixAreaSize = 0x6000; // we need to allocate some bytes before the interpreter struct because the recompiler will use it as stack area (specifically when the exception handler is called) + pData = (PPCInterpreter_t*)((uint8*)malloc(sizeof(PPCInterpreter_t)+prefixAreaSize)+prefixAreaSize); + memset((void*)pData, 0x00, sizeof(PPCInterpreter_t)); + // set instruction pointer to entrypoint + pData->instructionPointer = (uint32)Entrypoint; + // set initial register values + pData->gpr[GPR_SP] = 0x00000000; + pData->spr.LR = 0; + // return instance + return pData; +} + +PPCInterpreter_t* PPCInterpreter_getCurrentInstance() +{ + return ppcInterpreterCurrentInstance; +} + +__declspec(noinline) uint64 PPCInterpreter_getMainCoreCycleCounter() +{ + return PPCTimer_getFromRDTSC(); +} + +void PPCInterpreter_nextInstruction(PPCInterpreter_t* cpuInterpreter) +{ + cpuInterpreter->instructionPointer += 4; +} + +void PPCInterpreter_jumpToInstruction(PPCInterpreter_t* cpuInterpreter, uint32 newIP) +{ + cpuInterpreter->instructionPointer = (uint32)newIP; +} + +void PPCInterpreter_setDEC(PPCInterpreter_t* hCPU, uint32 newValue) +{ + hCPU->sprExtended.DEC = newValue; + ppcMainThreadDECCycleStart = PPCInterpreter_getMainCoreCycleCounter(); + ppcMainThreadDECCycleValue = newValue; +} + +uint32 PPCInterpreter_getXER(PPCInterpreter_t* hCPU) +{ + uint32 xerValue = hCPU->spr.XER; + xerValue &= ~(1<<XER_BIT_CA); + if( hCPU->xer_ca ) + xerValue |= (1<<XER_BIT_CA); + return xerValue; +} + +void PPCInterpreter_setXER(PPCInterpreter_t* hCPU, uint32 v) +{ + hCPU->spr.XER = v; + hCPU->xer_ca = (v>>XER_BIT_CA)&1; +} + +uint32 PPCInterpreter_getCoreIndex(PPCInterpreter_t* hCPU) +{ + return hCPU->spr.UPIR; +}; + +uint32 PPCInterpreter_getCurrentCoreIndex() +{ + return ppcInterpreterCurrentInstance->spr.UPIR; +}; + +uint8* PPCInterpreterGetStackPointer() +{ + return memory_getPointerFromVirtualOffset(ppcInterpreterCurrentInstance->gpr[1]); +} + +uint8* PPCInterpreterGetAndModifyStackPointer(sint32 offset) +{ + uint8* result = memory_getPointerFromVirtualOffset(ppcInterpreterCurrentInstance->gpr[1] - offset); + ppcInterpreterCurrentInstance->gpr[1] -= offset; + return result; +} + +void PPCInterpreterModifyStackPointer(sint32 offset) +{ + ppcInterpreterCurrentInstance->gpr[1] -= offset; +} + +uint32 RPLLoader_MakePPCCallable(void(*ppcCallableExport)(PPCInterpreter_t* hCPU)); + +// deprecated wrapper, use RPLLoader_MakePPCCallable directly +uint32 PPCInterpreter_makeCallableExportDepr(void (*ppcCallableExport)(PPCInterpreter_t* hCPU)) +{ + return RPLLoader_MakePPCCallable(ppcCallableExport); +} diff --git a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterOPC.cpp b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterOPC.cpp new file mode 100644 index 00000000..7178eaeb --- /dev/null +++ b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterOPC.cpp @@ -0,0 +1,360 @@ +#include "../PPCState.h" +#include "PPCInterpreterInternal.h" +#include "PPCInterpreterHelper.h" + +#include "Cafe/OS/libs/coreinit/coreinit_CodeGen.h" + +#include "../Recompiler/PPCRecompiler.h" +#include "../Recompiler/PPCRecompilerX64.h" + +#include <float.h> +#include "Cafe/HW/Latte/Core/LatteBufferCache.h" + +void PPCInterpreter_MFMSR(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + forceLogDebug_printf("Rare instruction: MFMSR"); + if (hCPU->sprExtended.msr & MSR_PR) + { + PPC_ASSERT(true); + return; + } + int rD, rA, rB; + PPC_OPC_TEMPL_X(Opcode, rD, rA, rB); + hCPU->gpr[rD] = hCPU->sprExtended.msr; + + PPCInterpreter_nextInstruction(hCPU); + +} + +void PPCInterpreter_MTMSR(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + forceLogDebug_printf("Rare instruction: MTMSR"); + if (hCPU->sprExtended.msr & MSR_PR) + { + PPC_ASSERT(true); + return; + } + int rS, rA, rB; + PPC_OPC_TEMPL_X(Opcode, rS, rA, rB); + + hCPU->sprExtended.msr = hCPU->gpr[rS]; + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_MTFSB1X(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + forceLogDebug_printf("Rare instruction: MTFSB1X"); + int crbD, n1, n2; + PPC_OPC_TEMPL_X(Opcode, crbD, n1, n2); + if (crbD != 1 && crbD != 2) + { + hCPU->fpscr |= 1 << (31 - crbD); + } + if (Opcode & PPC_OPC_RC) + { + // update cr1 flags + PPC_ASSERT(true); + } + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_MCRF(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + uint32 crD, crS, b; + PPC_OPC_TEMPL_X(Opcode, crD, crS, b); + crD >>= 2; + crS >>= 2; + for (sint32 i = 0; i<4; i++) + ppc_setCRBit(hCPU, crD * 4 + i, ppc_getCRBit(hCPU, crS * 4 + i)); + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_MFCR(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + // frequently used by GCC compiled code (e.g. SM64 port) + int rD, rA, rB; + PPC_OPC_TEMPL_X(Opcode, rD, rA, rB); + + // in our array: cr0.LT is entry with index 0 + // in GPR: cr0.LT is in MSB + uint32 cr = 0; + for (sint32 i = 0; i < 32; i++) + { + cr <<= 1; + if (ppc_getCRBit(hCPU, i) != 0) + cr |= 1; + } + hCPU->gpr[rD] = cr; + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_MTCRF(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + // frequently used by GCC compiled code (e.g. SM64 port) + // tested + + uint32 rS; + uint32 crfMask; + PPC_OPC_TEMPL_XFX(Opcode, rS, crfMask); + + for (sint32 crIndex = 0; crIndex < 8; crIndex++) + { + if (!ppc_MTCRFMaskHasCRFieldSet(crfMask, crIndex)) + continue; + + uint32 crBitBase = crIndex * 4; + uint8 nibble = (uint8)(hCPU->gpr[rS] >> (28 - crIndex * 4)); + ppc_setCRBit(hCPU, crBitBase + 0, (nibble >> 3) & 1); + ppc_setCRBit(hCPU, crBitBase + 1, (nibble >> 2) & 1); + ppc_setCRBit(hCPU, crBitBase + 2, (nibble >> 1) & 1); + ppc_setCRBit(hCPU, crBitBase + 3, (nibble >> 0) & 1); + } + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_MCRXR(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + // used in Dont Starve: Giant Edition + // also used frequently by Web Browser (webkit?) + uint32 cr; + cr = (Opcode >> (31 - 8)) & 7; + cr >>= 2; + + uint32 xer = PPCInterpreter_getXER(hCPU); + uint32 xerBits = (xer >> 28) & 0xF; + + // todo - is the order correct? + ppc_setCRBit(hCPU, cr * 4 + 0, (xerBits >> 0) & 1); + ppc_setCRBit(hCPU, cr * 4 + 1, (xerBits >> 1) & 1); + ppc_setCRBit(hCPU, cr * 4 + 2, (xerBits >> 2) & 1); + ppc_setCRBit(hCPU, cr * 4 + 3, (xerBits >> 3) & 1); + + // reset copied bits + PPCInterpreter_setXER(hCPU, xer&~0xF0000000); + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_TLBIE(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + int rS, rA, rB; + PPC_OPC_TEMPL_X(Opcode, rS, rA, rB); + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_TLBSYNC(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + cemu_assert_unimplemented(); + PPCInterpreter_nextInstruction(hCPU); +} + +// branch instructions + +void PPCInterpreter_BX(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + uint32 li; + PPC_OPC_TEMPL_I(Opcode, li); + if ((Opcode & PPC_OPC_AA) == 0) + li += (unsigned int)hCPU->instructionPointer; + if (Opcode & PPC_OPC_LK) + { + // update LR and IP + hCPU->spr.LR = (unsigned int)hCPU->instructionPointer + 4; + hCPU->instructionPointer = li; + PPCInterpreter_jumpToInstruction(hCPU, li); + PPCRecompiler_attemptEnter(hCPU, li); + return; + } + PPCInterpreter_jumpToInstruction(hCPU, li); +} + + +void PPCInterpreter_BCX(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + uint32 BO, BI, BD; + PPC_OPC_TEMPL_B(Opcode, BO, BI, BD); + if (!(BO & 4)) + hCPU->spr.CTR--; + bool bo2 = (BO & 2) != 0; + bool bo8 = (BO & 8) != 0; // branch condition true + bool cr = ppc_getCRBit(hCPU, BI) != 0; + if (((BO & 4) || ((hCPU->spr.CTR != 0) ^ bo2)) + && ((BO & 16) || (!(cr ^ bo8)))) + { + if (!(Opcode & PPC_OPC_AA)) + { + BD += (unsigned int)hCPU->instructionPointer; + } + else + { + // should never happen + cemu_assert_unimplemented(); + } + if (Opcode & PPC_OPC_LK) + hCPU->spr.LR = ((unsigned int)hCPU->instructionPointer) + 4; + PPCInterpreter_jumpToInstruction(hCPU, BD); + } + else + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_BCLRX(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + uint32 BO, BI, BD; + PPC_OPC_TEMPL_XL(Opcode, BO, BI, BD); + PPC_ASSERT(BD == 0); + if (!(BO & 4)) + { + if (hCPU->spr.CTR == 0) + { + PPC_ASSERT(true); + forceLogDebug_printf("Decrementer underflow!\n"); + } + hCPU->spr.CTR--; + } + bool bo2 = (BO & 2) ? true : false; + bool bo8 = (BO & 8) ? true : false; + bool cr = ppc_getCRBit(hCPU, BI) != 0; + if (((BO & 4) || ((hCPU->spr.CTR != 0) ^ bo2)) + && ((BO & 16) || (!(cr ^ bo8)))) + { + BD = hCPU->spr.LR & 0xfffffffc; + if (Opcode & PPC_OPC_LK) + { + hCPU->spr.LR = (unsigned int)hCPU->instructionPointer + 4; + } + PPCInterpreter_jumpToInstruction(hCPU, BD); + PPCRecompiler_attemptEnter(hCPU, BD); + return; + } + else + { + BD = (unsigned int)hCPU->instructionPointer + 4; + PPCInterpreter_nextInstruction(hCPU); + } +} + +void PPCInterpreter_BCCTR(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + uint32 x = (unsigned int)hCPU->instructionPointer; + uint32 BO, BI, BD; + PPC_OPC_TEMPL_XL(Opcode, BO, BI, BD); + PPC_ASSERT(BD == 0); + PPC_ASSERT(!(BO & 2)); + bool bo8 = (BO & 8) ? true : false; + bool cr = ppc_getCRBit(hCPU, BI) != 0; + if ((BO & 16) || (!(cr ^ bo8))) + { + if (Opcode & PPC_OPC_LK) + { + hCPU->spr.LR = (unsigned int)hCPU->instructionPointer + 4; + hCPU->instructionPointer = (unsigned int)(hCPU->spr.CTR & 0xfffffffc); + } + else + { + hCPU->instructionPointer = (unsigned int)(hCPU->spr.CTR & 0xfffffffc); + } + PPCRecompiler_attemptEnter(hCPU, hCPU->instructionPointer); + } + else + { + hCPU->instructionPointer += 4; + } +} + +void PPCInterpreter_DCBT(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + sint32 rA, rB; + rA = (Opcode >> (31 - 15)) & 0x1F; + rB = (Opcode >> (31 - 20)) & 0x1F; + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_DCBST(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + sint32 rA, rB; + rA = (Opcode >> (31 - 15)) & 0x1F; + rB = (Opcode >> (31 - 20)) & 0x1F; + + uint32 ea = (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB]; + + LatteBufferCache_notifyDCFlush(ea, 32); + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_DCBF(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + sint32 rA, rB; + rA = (Opcode >> (31 - 15)) & 0x1F; + rB = (Opcode >> (31 - 20)) & 0x1F; + + uint32 ea = (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB]; + + LatteBufferCache_notifyDCFlush(ea, 32); + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_DCBZL(PPCInterpreter_t* hCPU, uint32 Opcode) //Undocumented +{ + // no-op + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_DCBI(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + // no-op + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_ICBI(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + sint32 rD, rA, rB; + PPC_OPC_TEMPL_X(Opcode, rD, rA, rB); + uint32 ea = (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB]; + // invalidate range + coreinit::codeGenHandleICBI(ea); + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_EIEIO(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + // no effect + // next instruction + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_SC(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + forceLogDebug_printf("SC executed at 0x%08x", hCPU->instructionPointer); + // next instruction + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_SYNC(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + // no-op + // next instruction + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_ISYNC(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + // no-op + // next instruction + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_RFI(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + forceLogDebug_printf("RFI"); + hCPU->sprExtended.msr &= ~(0x87C0FF73 | 0x00040000); + hCPU->sprExtended.msr |= hCPU->sprExtended.srr1 & 0x87c0ff73; + hCPU->sprExtended.msr |= MSR_RI; + hCPU->instructionPointer = (unsigned int)(hCPU->sprExtended.srr0); +} diff --git a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterOPC.hpp b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterOPC.hpp new file mode 100644 index 00000000..4da41590 --- /dev/null +++ b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterOPC.hpp @@ -0,0 +1,73 @@ + +static void PPCInterpreter_MFSPR(PPCInterpreter_t* hCPU, uint32 opcode) +{ + uint32 rD, spr1, spr2, spr; + PPC_OPC_TEMPL_XO(opcode, rD, spr1, spr2); + spr = spr1 | (spr2 << 5); + // copy SPR + hCPU->gpr[rD] = PPCSpr_get(hCPU, spr); + // next instruction + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_MTSPR(PPCInterpreter_t* hCPU, uint32 opcode) +{ + uint32 rD, spr1, spr2, spr; + PPC_OPC_TEMPL_XO(opcode, rD, spr1, spr2); + spr = spr1 | (spr2 << 5); + PPCSpr_set(hCPU, spr, hCPU->gpr[rD]); + // next instruction + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_MFSR(PPCInterpreter_t* hCPU, uint32 opcode) +{ + uint32 rD, SR, rB; + PPC_OPC_TEMPL_X(opcode, rD, SR, rB); + hCPU->gpr[rD] = getSR(hCPU, SR & 0xF); + // next instruction + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_MTSR(PPCInterpreter_t* hCPU, uint32 opcode) +{ + uint32 rS, SR, rB; + PPC_OPC_TEMPL_X(opcode, rS, SR, rB); + setSR(hCPU, SR&0xF, hCPU->gpr[rS]); + // next instruction + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_MFTB(PPCInterpreter_t* hCPU, uint32 opcode) +{ + uint32 rD, spr1, spr2, spr; + // get SPR ID + PPC_OPC_TEMPL_XO(opcode, rD, spr1, spr2); + spr = spr1 | (spr2 << 5); + // get core cycle counter + uint64 coreTime = ppcItpCtrl::getTB(hCPU); + + switch (spr) + { + case 268: // TBL + hCPU->gpr[rD] = (uint32)(coreTime & 0xFFFFFFFF); + break; + case 269: // TBU + hCPU->gpr[rD] = (uint32)((coreTime >> 32) & 0xFFFFFFFF); + break; + default: + assert_dbg(); + } + // next instruction + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_TW(PPCInterpreter_t* hCPU, uint32 opcode) +{ + sint32 to, rA, rB; + PPC_OPC_TEMPL_X(opcode, to, rB, rA); + + cemu_assert_debug(to == 0); + + debugger_enterTW(hCPU); +} diff --git a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterPS.cpp b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterPS.cpp new file mode 100644 index 00000000..942817a0 --- /dev/null +++ b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterPS.cpp @@ -0,0 +1,506 @@ +#include "PPCInterpreterInternal.h" + +// Gekko paired single math + +void PPCInterpreter_PS_ADD(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + sint32 frD, frA, frB; + frB = (Opcode>>11)&0x1F; + frA = (Opcode>>16)&0x1F; + frD = (Opcode>>21)&0x1F; + + hCPU->fpr[frD].fp0 = (float)(hCPU->fpr[frA].fp0 + hCPU->fpr[frB].fp0); + hCPU->fpr[frD].fp1 = (float)(hCPU->fpr[frA].fp1 + hCPU->fpr[frB].fp1); + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_PS_SUB(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + sint32 frD, frA, frB; + frB = (Opcode>>11)&0x1F; + frA = (Opcode>>16)&0x1F; + frD = (Opcode>>21)&0x1F; + + hCPU->fpr[frD].fp0 = (float)(hCPU->fpr[frA].fp0 - hCPU->fpr[frB].fp0); + hCPU->fpr[frD].fp1 = (float)(hCPU->fpr[frA].fp1 - hCPU->fpr[frB].fp1); + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_PS_MUL(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + sint32 frD, frA, frC; + frC = (Opcode>>6)&0x1F; + frA = (Opcode>>16)&0x1F; + frD = (Opcode>>21)&0x1F; + + hCPU->fpr[frD].fp0 = flushDenormalToZero((float)(hCPU->fpr[frA].fp0 * roundTo25BitAccuracy(hCPU->fpr[frC].fp0))); + hCPU->fpr[frD].fp1 = flushDenormalToZero((float)(hCPU->fpr[frA].fp1 * roundTo25BitAccuracy(hCPU->fpr[frC].fp1))); + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_PS_DIV(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + sint32 frD, frA, frB; + frB = (Opcode>>11)&0x1F; + frA = (Opcode>>16)&0x1F; + frD = (Opcode>>21)&0x1F; + + hCPU->fpr[frD].fp0 = (float)(hCPU->fpr[frA].fp0 / hCPU->fpr[frB].fp0); + hCPU->fpr[frD].fp1 = (float)(hCPU->fpr[frA].fp1 / hCPU->fpr[frB].fp1); + + PPCInterpreter_nextInstruction(hCPU); +} + + +void PPCInterpreter_PS_MADD(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + sint32 frD, frA, frB, frC; + frC = (Opcode>>6)&0x1F; + frB = (Opcode>>11)&0x1F; + frA = (Opcode>>16)&0x1F; + frD = (Opcode>>21)&0x1F; + + float s0 = (float)((float)(hCPU->fpr[frA].fp0 * roundTo25BitAccuracy(hCPU->fpr[frC].fp0)) + hCPU->fpr[frB].fp0); + float s1 = (float)((float)(hCPU->fpr[frA].fp1 * roundTo25BitAccuracy(hCPU->fpr[frC].fp1)) + hCPU->fpr[frB].fp1); + + hCPU->fpr[frD].fp0 = flushDenormalToZero(s0); + hCPU->fpr[frD].fp1 = flushDenormalToZero(s1); + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_PS_NMADD(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + sint32 frD, frA, frB, frC; + frC = (Opcode>>6)&0x1F; + frB = (Opcode>>11)&0x1F; + frA = (Opcode>>16)&0x1F; + frD = (Opcode>>21)&0x1F; + + float s0 = (float)-(hCPU->fpr[frA].fp0 * roundTo25BitAccuracy(hCPU->fpr[frC].fp0) + hCPU->fpr[frB].fp0); + float s1 = (float)-(hCPU->fpr[frA].fp1 * roundTo25BitAccuracy(hCPU->fpr[frC].fp1) + hCPU->fpr[frB].fp1); + + hCPU->fpr[frD].fp0 = s0; + hCPU->fpr[frD].fp1 = s1; + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_PS_MSUB(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + sint32 frD, frA, frB, frC; + frC = (Opcode >> 6) & 0x1F; + frB = (Opcode >> 11) & 0x1F; + frA = (Opcode >> 16) & 0x1F; + frD = (Opcode >> 21) & 0x1F; + + float s0 = (float)(hCPU->fpr[frA].fp0 * roundTo25BitAccuracy(hCPU->fpr[frC].fp0) - hCPU->fpr[frB].fp0); + float s1 = (float)(hCPU->fpr[frA].fp1 * roundTo25BitAccuracy(hCPU->fpr[frC].fp1) - hCPU->fpr[frB].fp1); + + hCPU->fpr[frD].fp0 = s0; + hCPU->fpr[frD].fp1 = s1; + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_PS_NMSUB(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + sint32 frD, frA, frB, frC; + frC = (Opcode >> 6) & 0x1F; + frB = (Opcode >> 11) & 0x1F; + frA = (Opcode >> 16) & 0x1F; + frD = (Opcode >> 21) & 0x1F; + + float s0 = (float)-(hCPU->fpr[frA].fp0 * roundTo25BitAccuracy(hCPU->fpr[frC].fp0) - hCPU->fpr[frB].fp0); + float s1 = (float)-(hCPU->fpr[frA].fp1 * roundTo25BitAccuracy(hCPU->fpr[frC].fp1) - hCPU->fpr[frB].fp1); + + hCPU->fpr[frD].fp0 = s0; + hCPU->fpr[frD].fp1 = s1; + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_PS_MADDS0(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + sint32 frD, frA, frB, frC; + frC = (Opcode>>6)&0x1F; + frB = (Opcode>>11)&0x1F; + frA = (Opcode>>16)&0x1F; + frD = (Opcode>>21)&0x1F; + + double c = roundTo25BitAccuracy(hCPU->fpr[frC].fp0); + float s0 = (float)(hCPU->fpr[frA].fp0 * c + hCPU->fpr[frB].fp0); + float s1 = (float)(hCPU->fpr[frA].fp1 * c + hCPU->fpr[frB].fp1); + + hCPU->fpr[frD].fp0 = s0; + hCPU->fpr[frD].fp1 = s1; + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_PS_MADDS1(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + sint32 frD, frA, frB, frC; + frC = (Opcode>>6)&0x1F; + frB = (Opcode>>11)&0x1F; + frA = (Opcode>>16)&0x1F; + frD = (Opcode>>21)&0x1F; + + double c = roundTo25BitAccuracy(hCPU->fpr[frC].fp1); + float s0 = (float)(hCPU->fpr[frA].fp0 * c + hCPU->fpr[frB].fp0); + float s1 = (float)(hCPU->fpr[frA].fp1 * c + hCPU->fpr[frB].fp1); + + hCPU->fpr[frD].fp0 = s0; + hCPU->fpr[frD].fp1 = s1; + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_PS_SEL(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + sint32 frD, frA, frB, frC; + frC = (Opcode>>6)&0x1F; + frB = (Opcode>>11)&0x1F; + frA = (Opcode>>16)&0x1F; + frD = (Opcode>>21)&0x1F; + + + if( hCPU->fpr[frA].fp0 >= -0.0f ) + hCPU->fpr[frD].fp0 = hCPU->fpr[frC].fp0; + else + hCPU->fpr[frD].fp0 = hCPU->fpr[frB].fp0; + + if( hCPU->fpr[frA].fp1 >= -0.0f ) + hCPU->fpr[frD].fp1 = hCPU->fpr[frC].fp1; + else + hCPU->fpr[frD].fp1 = hCPU->fpr[frB].fp1; + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_PS_SUM0(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + sint32 frD, frA, frB, frC; + frC = (Opcode>>6)&0x1F; + frB = (Opcode>>11)&0x1F; + frA = (Opcode>>16)&0x1F; + frD = (Opcode>>21)&0x1F; + + float s0 = (float)(hCPU->fpr[frA].fp0 + hCPU->fpr[frB].fp1); + float s1 = (float)hCPU->fpr[frC].fp1; + + hCPU->fpr[frD].fp0 = s0; + hCPU->fpr[frD].fp1 = s1; + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_PS_SUM1(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + sint32 frD, frA, frB, frC; + frC = (Opcode>>6)&0x1F; + frB = (Opcode>>11)&0x1F; + frA = (Opcode>>16)&0x1F; + frD = (Opcode>>21)&0x1F; + + float s0 = (float)hCPU->fpr[frC].fp0; + float s1 = (float)(hCPU->fpr[frA].fp0 + hCPU->fpr[frB].fp1); + + hCPU->fpr[frD].fp0 = s0; + hCPU->fpr[frD].fp1 = s1; + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_PS_MULS0(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + sint32 frD, frA, frC; + frC = (Opcode>>6)&0x1F; + frA = (Opcode>>16)&0x1F; + frD = (Opcode>>21)&0x1F; + + double c = roundTo25BitAccuracy(hCPU->fpr[frC].fp0); + float s0 = (float)(hCPU->fpr[frA].fp0 * c); + float s1 = (float)(hCPU->fpr[frA].fp1 * c); + + hCPU->fpr[frD].fp0 = s0; + hCPU->fpr[frD].fp1 = s1; + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_PS_MULS1(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + sint32 frD, frA, frC; + frC = (Opcode>>6)&0x1F; + frA = (Opcode>>16)&0x1F; + frD = (Opcode>>21)&0x1F; + + double c = roundTo25BitAccuracy(hCPU->fpr[frC].fp1); + float s0 = (float)(hCPU->fpr[frA].fp0 * c); + float s1 = (float)(hCPU->fpr[frA].fp1 * c); + + hCPU->fpr[frD].fp0 = s0; + hCPU->fpr[frD].fp1 = s1; + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_PS_MR(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + sint32 frD, frB; + frB = (Opcode>>11)&0x1F; + frD = (Opcode>>21)&0x1F; + + hCPU->fpr[frD].fp0 = hCPU->fpr[frB].fp0; + hCPU->fpr[frD].fp1 = hCPU->fpr[frB].fp1; + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_PS_NEG(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + sint32 frD, frB; + frB = (Opcode>>11)&0x1F; + frD = (Opcode>>21)&0x1F; + + hCPU->fpr[frD].fp0 = -hCPU->fpr[frB].fp0; + hCPU->fpr[frD].fp1 = -hCPU->fpr[frB].fp1; + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_PS_ABS(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + sint32 frD, frB; + frB = (Opcode>>11)&0x1F; + frD = (Opcode>>21)&0x1F; + + hCPU->fpr[frD].fp0int = hCPU->fpr[frB].fp0int & ~(1ULL << 63); + hCPU->fpr[frD].fp1int = hCPU->fpr[frB].fp1int & ~(1ULL << 63); + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_PS_NABS(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + sint32 frD, frB; + frB = (Opcode>>11)&0x1F; + frD = (Opcode>>21)&0x1F; + + hCPU->fpr[frD].fp0int = hCPU->fpr[frB].fp0int | (1ULL << 63); + hCPU->fpr[frD].fp1int = hCPU->fpr[frB].fp1int | (1ULL << 63); + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_PS_RSQRTE(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + sint32 frD, frB; + frB = (Opcode>>11)&0x1F; + frD = (Opcode>>21)&0x1F; + + hCPU->fpr[frD].fp0 = (float)frsqrte_espresso(hCPU->fpr[frB].fp0); + hCPU->fpr[frD].fp1 = (float)frsqrte_espresso(hCPU->fpr[frB].fp1); + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_PS_MERGE00(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + sint32 frD, frA, frB; + frB = (Opcode>>11)&0x1F; + frA = (Opcode>>16)&0x1F; + frD = (Opcode>>21)&0x1F; + double s0 = hCPU->fpr[frA].fp0; + double s1 = hCPU->fpr[frB].fp0; + + hCPU->fpr[frD].fp0 = s0; + hCPU->fpr[frD].fp1 = s1; + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_PS_MERGE01(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + sint32 frD, frA, frB; + frB = (Opcode>>11)&0x1F; + frA = (Opcode>>16)&0x1F; + frD = (Opcode>>21)&0x1F; + + double s0 = hCPU->fpr[frA].fp0; + double s1 = hCPU->fpr[frB].fp1; + + hCPU->fpr[frD].fp0 = s0; + hCPU->fpr[frD].fp1 = s1; + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_PS_MERGE10(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + sint32 frD, frA, frB; + frB = (Opcode>>11)&0x1F; + frA = (Opcode>>16)&0x1F; + frD = (Opcode>>21)&0x1F; + + double s0 = hCPU->fpr[frA].fp1; + double s1 = hCPU->fpr[frB].fp0; + + hCPU->fpr[frD].fp0 = s0; + hCPU->fpr[frD].fp1 = s1; + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_PS_MERGE11(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + sint32 frD, frA, frB; + frB = (Opcode>>11)&0x1F; + frA = (Opcode>>16)&0x1F; + frD = (Opcode>>21)&0x1F; + + double s0 = hCPU->fpr[frA].fp1; + double s1 = hCPU->fpr[frB].fp1; + + hCPU->fpr[frD].fp0 = s0; + hCPU->fpr[frD].fp1 = s1; + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_PS_RES(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + sint32 frD, frB; + frB = (Opcode>>11)&0x1F; + frD = (Opcode>>21)&0x1F; + + hCPU->fpr[frD].fp0 = (float)fres_espresso(hCPU->fpr[frB].fp0); + hCPU->fpr[frD].fp1 = (float)fres_espresso(hCPU->fpr[frB].fp1); + + PPCInterpreter_nextInstruction(hCPU); +} + +// PS compare + +void PPCInterpreter_PS_CMPO0(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + + sint32 crfD, frA, frB; + uint32 c=0; + frB = (Opcode>>11)&0x1F; + frA = (Opcode>>16)&0x1F; + crfD = (Opcode>>23)&0x7; + + + double a = hCPU->fpr[frA].fp0; + double b = hCPU->fpr[frB].fp0; + + ppc_setCRBit(hCPU, crfD*4+0, 0); + ppc_setCRBit(hCPU, crfD*4+1, 0); + ppc_setCRBit(hCPU, crfD*4+2, 0); + ppc_setCRBit(hCPU, crfD*4+3, 0); + + if(IS_NAN(*(uint64*)&a) || IS_NAN(*(uint64*)&b)) + { + c = 1; + ppc_setCRBit(hCPU, crfD*4+CR_BIT_SO, 1); + } + else if(a < b) + { + c = 8; + ppc_setCRBit(hCPU, crfD*4+CR_BIT_LT, 1); + } + else if(a > b) + { + c = 4; + ppc_setCRBit(hCPU, crfD*4+CR_BIT_GT, 1); + } + else + { + c = 2; + ppc_setCRBit(hCPU, crfD*4+CR_BIT_EQ, 1); + } + + hCPU->fpscr = (hCPU->fpscr & 0xffff0fff) | (c << 12); + + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_PS_CMPU0(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + sint32 crfD, frA, frB; + frB = (Opcode >> 11) & 0x1F; + frA = (Opcode >> 16) & 0x1F; + crfD = (Opcode >> 21) & (0x7<<2); + fcmpu_espresso(hCPU, crfD, hCPU->fpr[frA].fp0, hCPU->fpr[frB].fp0); + PPCInterpreter_nextInstruction(hCPU); +} + +void PPCInterpreter_PS_CMPU1(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + FPUCheckAvailable(); + sint32 crfD, frA, frB; + frB = (Opcode >> 11) & 0x1F; + frA = (Opcode >> 16) & 0x1F; + crfD = (Opcode >> 21) & (0x7 << 2); + double a = hCPU->fpr[frA].fp1; + double b = hCPU->fpr[frB].fp1; + fcmpu_espresso(hCPU, crfD, hCPU->fpr[frA].fp1, hCPU->fpr[frB].fp1); + PPCInterpreter_nextInstruction(hCPU); +} \ No newline at end of file diff --git a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterSPR.hpp b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterSPR.hpp new file mode 100644 index 00000000..819f317a --- /dev/null +++ b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterSPR.hpp @@ -0,0 +1,875 @@ +#define SPR_TBL_WRITE (284) +#define SPR_TBU_WRITE (285) + +#define SPR_DBATU_0 (536) +#define SPR_DBATU_1 (538) +#define SPR_DBATU_2 (540) +#define SPR_DBATU_3 (542) +#define SPR_DBATU_4 (568) +#define SPR_DBATU_5 (570) +#define SPR_DBATU_6 (572) +#define SPR_DBATU_7 (574) + +#define SPR_DBATL_0 (537) +#define SPR_DBATL_1 (539) +#define SPR_DBATL_2 (541) +#define SPR_DBATL_3 (543) +#define SPR_DBATL_4 (569) +#define SPR_DBATL_5 (571) +#define SPR_DBATL_6 (573) +#define SPR_DBATL_7 (575) + +#define SPR_IBATU_0 (528) +#define SPR_IBATU_1 (530) +#define SPR_IBATU_2 (532) +#define SPR_IBATU_3 (534) +#define SPR_IBATU_4 (560) +#define SPR_IBATU_5 (562) +#define SPR_IBATU_6 (564) +#define SPR_IBATU_7 (566) + +#define SPR_IBATL_0 (529) +#define SPR_IBATL_1 (531) +#define SPR_IBATL_2 (533) +#define SPR_IBATL_3 (535) +#define SPR_IBATL_4 (561) +#define SPR_IBATL_5 (563) +#define SPR_IBATL_6 (565) +#define SPR_IBATL_7 (567) + +#define SPR_DSISR (18) +#define SPR_DAR (19) + +#define SPR_SPRG0 (272) +#define SPR_SPRG1 (273) +#define SPR_SPRG2 (274) +#define SPR_SPRG3 (275) + +//#define SPR_HID0 (1008) +//#define SPR_HID2 (920) +#define SPR_HID4 (1011) +#define SPR_HID5 (944) + +#define SPR_L2CR (1017) // L2 cache control + +#define SPR_CAR (948) // global +#define SPR_BCR (949) // global + +static uint32 getPVR(PPCInterpreter_t* hCPU) +{ + return 0x70010101; // guessed +} + +static uint32 getFPECR(PPCInterpreter_t* hCPU) +{ + return hCPU->sprExtended.fpecr; +} + +static void setFPECR(PPCInterpreter_t* hCPU, uint32 newValue) +{ + hCPU->sprExtended.fpecr = newValue; +} + +static void setDEC(PPCInterpreter_t* hCPU, uint32 newValue) +{ + debug_printf("Set DEC to 0x%08x\n", newValue); + //hCPU->sprExtended.fpecr = newValue; +} + +static uint32 getSPRG(PPCInterpreter_t* hCPU, uint32 sprgIndex) +{ + return hCPU->sprExtended.sprg[sprgIndex]; +} + +static void setSPRG(PPCInterpreter_t* hCPU, uint32 sprgIndex, uint32 newValue) +{ + hCPU->sprExtended.sprg[sprgIndex] = newValue; +} + +static uint32 getDAR(PPCInterpreter_t* hCPU) +{ + return hCPU->sprExtended.dar; +} + +static uint32 getDSISR(PPCInterpreter_t* hCPU) +{ + return hCPU->sprExtended.dsisr; +} + +static uint32 getHID0(PPCInterpreter_t* hCPU) +{ + return 0; // todo +} + +static void setHID0(PPCInterpreter_t* hCPU, uint32 newValue) +{ + // todo + debug_printf("Set HID0 to 0x%08x\n", newValue); +} + +static uint32 getHID1(PPCInterpreter_t* hCPU) +{ + debug_printf("Get HID1 IP 0x%08x\n", hCPU->instructionPointer); + return 0; // todo +} + +static uint32 getHID2(PPCInterpreter_t* hCPU) +{ + debug_printf("Get HID2 IP 0x%08x\n", hCPU->instructionPointer); + return 0; // todo +} + +static void setHID2(PPCInterpreter_t* hCPU, uint32 newValue) +{ + // todo + debug_printf("Set HID2 to 0x%08x\n", newValue); +} + +static uint32 getHID4(PPCInterpreter_t* hCPU) +{ + debug_printf("Get HID4 IP 0x%08x\n", hCPU->instructionPointer); + return 0; // todo +} + +static void setHID4(PPCInterpreter_t* hCPU, uint32 newValue) +{ + // todo + debug_printf("Set HID4 to 0x%08x\n", newValue); +} + +static uint32 getHID5(PPCInterpreter_t* hCPU) +{ + // Wii-U only + debug_printf("Get HID5 IP 0x%08x\n", hCPU->instructionPointer); + return 0; // todo +} + +static void setHID5(PPCInterpreter_t* hCPU, uint32 newValue) +{ + // Wii-U only + // todo + debug_printf("Set HID5 to 0x%08x\n", newValue); +} + +static uint32 getSCR(PPCInterpreter_t* hCPU) +{ + // WiiU mode only? + return 0; // todo +} + +static void setSCR(PPCInterpreter_t* hCPU, uint32 newValue) +{ + uint32 previousSCR = hCPU->global->sprGlobal.scr; + newValue |= (previousSCR&0x80000000); // this bit always sticks? + if ((previousSCR&0x80000000) == 0 && (newValue & 0x80000000) != 0) + { + // this bit is used to disable bootrom mapping, but we use it to know when to copy the decrypted ancast image into kernel memory + debug_printf("SCR MSB set. Unmap bootrom?\n"); + + //memcpy(memory_base + 0xFFE00000, memory_base + 0x08000000, 0x180000); + // hack - clear low memory (where bootrom was mapped/loaded) + memset(memory_base, 0, 0x4000); + //// todo - normally IOSU sets up some stuff here (probably) + + // for debugging purposes make lowest page read-only +#ifdef _WIN32 + DWORD oldProtect; + VirtualProtect(memory_base, 0x1000, PAGE_READONLY, &oldProtect); +#endif + } + debug_printf("Set SCR to 0x%08x\n", newValue); + hCPU->global->sprGlobal.scr = newValue; +} + +// SCR probably has bits to control following: +// disable bootrom (bit 0x80000000) +// disable PPC OTP +// bits to start the extra cores + +static uint32 getCAR(PPCInterpreter_t* hCPU) +{ + // global + // WiiU mode only + return 0; // todo +} + +static void setCAR(PPCInterpreter_t* hCPU, uint32 newValue) +{ + // global + // WiiU mode only + debug_printf("Set CAR to 0x%08x\n", newValue); +} + +static uint32 getBCR(PPCInterpreter_t* hCPU) +{ + // global + // WiiU mode only + return 0; // todo +} + +static void setBCR(PPCInterpreter_t* hCPU, uint32 newValue) +{ + // global + // WiiU mode only + debug_printf("Set BCR to 0x%08x\n", newValue); +} + + +static uint32 getL2CR(PPCInterpreter_t* hCPU) +{ + return 0; // todo +} + +static void setL2CR(PPCInterpreter_t* hCPU, uint32 newValue) +{ + // todo +} + +static void setSRR0(PPCInterpreter_t* hCPU, uint32 newValue) +{ + hCPU->sprExtended.srr0 = newValue; +} + +static void setSRR1(PPCInterpreter_t* hCPU, uint32 newValue) +{ + hCPU->sprExtended.srr1 = newValue; +} + +static void setDMAU(PPCInterpreter_t* hCPU, uint32 newValue) +{ + hCPU->sprExtended.dmaU = newValue; +} + +static void setDMAL(PPCInterpreter_t* hCPU, uint32 newValue) +{ + hCPU->sprExtended.dmaL = newValue; + // LC DMA + if(newValue &0x2 ) + { + uint32 transferLength = (((hCPU->sprExtended.dmaU>>0)&0x1F)<<2)|((newValue>>2)&3); + uint32 memAddr = (hCPU->sprExtended.dmaU)&0xFFFFFFE0; + uint32 cacheAddr = (newValue)&0xFFFFFFE0; + if( transferLength == 0 ) + transferLength = 128; + transferLength *= 32; + bool isLoad = ((newValue>>4)&1)!=0; + if( (cacheAddr>>28) != 0xE ) + { + debug_printf("LCTransfer: Not a cache address\n"); + cacheAddr = 0; + } + else + { + cacheAddr -= 0xE0000000; + } + if( isLoad == 0 ) + { + // locked cache -> memory + debug_printf("L2->MEM %08x -> %08x size: 0x%x\n", memAddr, 0xE0000000 + cacheAddr, transferLength); + memcpy(memory_getPointerFromVirtualOffset(memAddr), memory_base+0xE0000000+cacheAddr, transferLength); + } + else + { + // memory -> locked cache + debug_printf("MEM->L2 %08x -> %08x size: 0x%x\n", 0xE0000000 + cacheAddr, memAddr, transferLength); + memcpy(memory_base + 0xE0000000 + cacheAddr, memory_getPointerFromVirtualOffset(memAddr), transferLength); + } + newValue &= ~2; + hCPU->sprExtended.dmaL = newValue; + } +} + +static void setDBATL(PPCInterpreter_t* hCPU, uint32 index, uint32 newValue) +{ + debug_printf("Set DBATL%d to 0x%08x\n", index, newValue); + hCPU->sprExtended.dbatL[index] = newValue; +} + +static void setDBATU(PPCInterpreter_t* hCPU, uint32 index, uint32 newValue) +{ + debug_printf("Set DBATU%d to 0x%08x\n", index, newValue); + hCPU->sprExtended.dbatU[index] = newValue; +} + +static void setIBATL(PPCInterpreter_t* hCPU, uint32 index, uint32 newValue) +{ + debug_printf("Set IBATL%d to 0x%08x\n", index, newValue); + hCPU->sprExtended.ibatL[index] = newValue; +} + +static void setIBATU(PPCInterpreter_t* hCPU, uint32 index, uint32 newValue) +{ + debug_printf("Set IBATU%d to 0x%08x\n", index, newValue); + hCPU->sprExtended.ibatU[index] = newValue; +} + +static uint32 getDBATL(PPCInterpreter_t* hCPU, uint32 index) +{ + return hCPU->sprExtended.dbatL[index]; +} + +static uint32 getDBATU(PPCInterpreter_t* hCPU, uint32 index) +{ + return hCPU->sprExtended.dbatU[index]; +} + +static uint32 getIBATL(PPCInterpreter_t* hCPU, uint32 index) +{ + return hCPU->sprExtended.ibatL[index]; +} + +static uint32 getIBATU(PPCInterpreter_t* hCPU, uint32 index) +{ + return hCPU->sprExtended.ibatU[index]; +} + +static void setSR(PPCInterpreter_t* hCPU, uint32 index, uint32 newValue) +{ + debug_printf("Set SR%d to 0x%08x IP %08x LR %08x\n", index, newValue, hCPU->instructionPointer, hCPU->spr.LR); + hCPU->sprExtended.sr[index] = newValue; +} + +static uint32 getSR(PPCInterpreter_t* hCPU, uint32 index) +{ + return hCPU->sprExtended.sr[index]; +} + +static void setSDR1(PPCInterpreter_t* hCPU, uint32 newValue) +{ + debug_printf("Set SDR1 to 0x%08x\n", newValue); + hCPU->sprExtended.sdr1 = newValue; +} + +static void setTBL(PPCInterpreter_t* hCPU, uint32 newValue) +{ + if (newValue != 0) + assert_dbg(); + debug_printf("Reset TB\n"); + hCPU->global->tb = 0; +} + +static void setTBU(PPCInterpreter_t* hCPU, uint32 newValue) +{ + if (newValue != 0) + assert_dbg(); + debug_printf("Reset TB\n"); + hCPU->global->tb = 0; +} + +static void PPCSprSupervisor_set(PPCInterpreter_t* hCPU, uint32 spr, uint32 newValue) +{ + switch (spr) + { + case SPR_LR: + hCPU->spr.LR = newValue; + break; + case SPR_CTR: + hCPU->spr.CTR = newValue; + break; + case SPR_DEC: + setDEC(hCPU, newValue); + break; + case SPR_XER: + PPCInterpreter_setXER(hCPU, newValue); + break; + case SPR_UGQR0: + case SPR_UGQR1: + case SPR_UGQR2: + case SPR_UGQR3: + case SPR_UGQR4: + case SPR_UGQR5: + case SPR_UGQR6: + case SPR_UGQR7: + hCPU->spr.UGQR[spr - SPR_UGQR0] = newValue; + break; + // values above are user mode accessible + case SPR_TBL_WRITE: // TBL + setTBL(hCPU, newValue); + break; + case SPR_TBU_WRITE: // TBU + setTBU(hCPU, newValue); + break; + case SPR_FPECR: + setFPECR(hCPU, newValue); + break; + case SPR_HID0: + setHID0(hCPU, newValue); + break; + case SPR_HID2: + setHID2(hCPU, newValue); + break; + case SPR_HID4: + setHID4(hCPU, newValue); + break; + case SPR_HID5: + setHID5(hCPU, newValue); + break; + case SPR_L2CR: + setL2CR(hCPU, newValue); + break; + case SPR_SRR0: + setSRR0(hCPU, newValue); + break; + case SPR_SRR1: + setSRR1(hCPU, newValue); + break; + case SPR_SPRG0: + setSPRG(hCPU, 0, newValue); + break; + case SPR_SPRG1: + setSPRG(hCPU, 1, newValue); + break; + case SPR_SPRG2: + setSPRG(hCPU, 2, newValue); + break; + case SPR_SPRG3: + setSPRG(hCPU, 3, newValue); + break; + case SPR_SCR: + setSCR(hCPU, newValue); + break; + case SPR_CAR: + setCAR(hCPU, newValue); + break; + case SPR_BCR: + setBCR(hCPU, newValue); + break; + case SPR_DMAU: + setDMAU(hCPU, newValue); + break; + case SPR_DMAL: + setDMAL(hCPU, newValue); + break; + case SPR_DBATU_0: + setDBATU(hCPU, 0, newValue); + break; + case SPR_DBATU_1: + setDBATU(hCPU, 1, newValue); + break; + case SPR_DBATU_2: + setDBATU(hCPU, 2, newValue); + break; + case SPR_DBATU_3: + setDBATU(hCPU, 3, newValue); + break; + case SPR_DBATU_4: + setDBATU(hCPU, 4, newValue); + break; + case SPR_DBATU_5: + setDBATU(hCPU, 5, newValue); + break; + case SPR_DBATU_6: + setDBATU(hCPU, 6, newValue); + break; + case SPR_DBATU_7: + setDBATU(hCPU, 7, newValue); + break; + case SPR_DBATL_0: + setDBATL(hCPU, 0, newValue); + break; + case SPR_DBATL_1: + setDBATL(hCPU, 1, newValue); + break; + case SPR_DBATL_2: + setDBATL(hCPU, 2, newValue); + break; + case SPR_DBATL_3: + setDBATL(hCPU, 3, newValue); + break; + case SPR_DBATL_4: + setDBATL(hCPU, 4, newValue); + break; + case SPR_DBATL_5: + setDBATL(hCPU, 5, newValue); + break; + case SPR_DBATL_6: + setDBATL(hCPU, 6, newValue); + break; + case SPR_DBATL_7: + setDBATL(hCPU, 7, newValue); + break; + case SPR_IBATU_0: + setIBATU(hCPU, 0, newValue); + break; + case SPR_IBATU_1: + setIBATU(hCPU, 1, newValue); + break; + case SPR_IBATU_2: + setIBATU(hCPU, 2, newValue); + break; + case SPR_IBATU_3: + setIBATU(hCPU, 3, newValue); + break; + case SPR_IBATU_4: + setIBATU(hCPU, 4, newValue); + break; + case SPR_IBATU_5: + setIBATU(hCPU, 5, newValue); + break; + case SPR_IBATU_6: + setIBATU(hCPU, 6, newValue); + break; + case SPR_IBATU_7: + setIBATU(hCPU, 7, newValue); + break; + case SPR_IBATL_0: + setIBATL(hCPU, 0, newValue); + break; + case SPR_IBATL_1: + setIBATL(hCPU, 1, newValue); + break; + case SPR_IBATL_2: + setIBATL(hCPU, 2, newValue); + break; + case SPR_IBATL_3: + setIBATL(hCPU, 3, newValue); + break; + case SPR_IBATL_4: + setIBATL(hCPU, 4, newValue); + break; + case SPR_IBATL_5: + setIBATL(hCPU, 5, newValue); + break; + case SPR_IBATL_6: + setIBATL(hCPU, 6, newValue); + break; + case SPR_IBATL_7: + setIBATL(hCPU, 7, newValue); + break; + case SPR_SDR1: + setSDR1(hCPU, newValue); + break; + case 0x3B8: // mmcr0 + debug_printf("Write performance monitor SPR mmcr0 0x%08x", newValue); + break; + case 0x3B9: // PMC1 + debug_printf("Write performance monitor SPR PMC1 0x%08x", newValue); + break; + case 0x3BA: // PMC2 + debug_printf("Write performance monitor SPR PMC2 0x%08x", newValue); + break; + case 0x3BC: // mmcr1 + debug_printf("Write performance monitor SPR mmcr1 0x%08x", newValue); + break; + case 0x3BD: // PMC3 + debug_printf("Write performance monitor SPR PMC3 0x%08x", newValue); + break; + case 0x3BE: // PMC4 + debug_printf("Write performance monitor SPR PMC4 0x%08x", newValue); + break; + default: + debug_printf("[C%d] Set unhandled SPR 0x%x to %08x (supervisor mode)\n", hCPU->spr.UPIR, spr, newValue); +#ifndef PUBLIC_RELEASE + assert_dbg(); +#endif + break; + } +} + +static void PPCSpr_set(PPCInterpreter_t* hCPU, uint32 spr, uint32 newValue) +{ + if constexpr(ppcItpCtrl::allowSupervisorMode) + { + // todo - check if in supervisor mode or user mode + PPCSprSupervisor_set(hCPU, spr, newValue); + return; + } + + switch (spr) + { + case SPR_LR: + hCPU->spr.LR = newValue; + break; + case SPR_CTR: + hCPU->spr.CTR = newValue; + break; + case SPR_XER: + PPCInterpreter_setXER(hCPU, newValue); + break; + case SPR_UGQR0: + case SPR_UGQR1: + case SPR_UGQR2: + case SPR_UGQR3: + case SPR_UGQR4: + case SPR_UGQR5: + case SPR_UGQR6: + case SPR_UGQR7: + hCPU->spr.UGQR[spr - SPR_UGQR0] = newValue; + break; + default: + debug_printf("[C%d] Set unhandled SPR %d to %08x\n", hCPU->spr.UPIR, spr, newValue); +#ifndef PUBLIC_RELEASE + assert_dbg(); +#endif + break; + } +} + +static uint32 PPCSprSupervisor_get(PPCInterpreter_t* hCPU, uint32 spr) +{ + uint32 v = 0; + switch (spr) + { + case SPR_LR: + v = hCPU->spr.LR; + break; + case SPR_CTR: + v = hCPU->spr.CTR; + break; + case SPR_XER: + v = PPCInterpreter_getXER(hCPU); + break; + case SPR_UPIR: + v = hCPU->spr.UPIR; + break; + case SPR_UGQR0: + case SPR_UGQR1: + case SPR_UGQR2: + case SPR_UGQR3: + case SPR_UGQR4: + case SPR_UGQR5: + case SPR_UGQR6: + case SPR_UGQR7: + v = hCPU->spr.UGQR[spr - SPR_UGQR0]; + break; + // above are registers accessible in user mode + case SPR_PVR: + v = getPVR(hCPU); + break; + case SPR_HID0: + v = getHID0(hCPU); + break; + case SPR_HID1: + v = getHID1(hCPU); + break; + case SPR_HID2: + v = getHID2(hCPU); + break; + case SPR_HID4: + v = getHID4(hCPU); + break; + case SPR_HID5: + v = getHID5(hCPU); + break; + case SPR_SCR: + v = getSCR(hCPU); + break; + case SPR_CAR: + v = getCAR(hCPU); + break; + case SPR_BCR: + v = getBCR(hCPU); + break; + case SPR_DAR: + v = getDAR(hCPU); + break; + case SPR_DSISR: + v = getDSISR(hCPU); + break; + case SPR_L2CR: + v = getL2CR(hCPU); + break; + case SPR_FPECR: + v = getFPECR(hCPU); + break; + case SPR_SPRG0: + v = getSPRG(hCPU, 0); + break; + case SPR_SPRG1: + v = getSPRG(hCPU, 1); + break; + case SPR_SPRG2: + v = getSPRG(hCPU, 2); + break; + case SPR_SPRG3: + v = getSPRG(hCPU, 3); + break; + case SPR_DBATU_0: + v = getDBATU(hCPU, 0); + break; + case SPR_DBATU_1: + v = getDBATU(hCPU, 1); + break; + case SPR_DBATU_2: + v = getDBATU(hCPU, 2); + break; + case SPR_DBATU_3: + v = getDBATU(hCPU, 3); + break; + case SPR_DBATU_4: + v = getDBATU(hCPU, 4); + break; + case SPR_DBATU_5: + v = getDBATU(hCPU, 5); + break; + case SPR_DBATU_6: + v = getDBATU(hCPU, 6); + break; + case SPR_DBATU_7: + v = getDBATU(hCPU, 7); + break; + case SPR_DBATL_0: + v = getDBATL(hCPU, 0); + break; + case SPR_DBATL_1: + v = getDBATL(hCPU, 1); + break; + case SPR_DBATL_2: + v = getDBATL(hCPU, 2); + break; + case SPR_DBATL_3: + v = getDBATL(hCPU, 3); + break; + case SPR_DBATL_4: + v = getDBATL(hCPU, 4); + break; + case SPR_DBATL_5: + v = getDBATL(hCPU, 5); + break; + case SPR_DBATL_6: + v = getDBATL(hCPU, 6); + break; + case SPR_DBATL_7: + v = getDBATL(hCPU, 7); + break; + case SPR_IBATU_0: + v = getIBATU(hCPU, 0); + break; + case SPR_IBATU_1: + v = getIBATU(hCPU, 1); + break; + case SPR_IBATU_2: + v = getIBATU(hCPU, 2); + break; + case SPR_IBATU_3: + v = getIBATU(hCPU, 3); + break; + case SPR_IBATU_4: + v = getIBATU(hCPU, 4); + break; + case SPR_IBATU_5: + v = getIBATU(hCPU, 5); + break; + case SPR_IBATU_6: + v = getIBATU(hCPU, 6); + break; + case SPR_IBATU_7: + v = getIBATU(hCPU, 7); + break; + case SPR_IBATL_0: + v = getIBATL(hCPU, 0); + break; + case SPR_IBATL_1: + v = getIBATL(hCPU, 1); + break; + case SPR_IBATL_2: + v = getIBATL(hCPU, 2); + break; + case SPR_IBATL_3: + v = getIBATL(hCPU, 3); + break; + case SPR_IBATL_4: + v = getIBATL(hCPU, 4); + break; + case SPR_IBATL_5: + v = getIBATL(hCPU, 5); + break; + case SPR_IBATL_6: + v = getIBATL(hCPU, 6); + break; + case SPR_IBATL_7: + v = getIBATL(hCPU, 7); + break; + default: + debug_printf("[C%d] Get unhandled SPR %d\n", hCPU->spr.UPIR, spr); +#ifndef PUBLIC_RELEASE + assert_dbg(); +#endif + break; + } + return v; +} + +static uint32 PPCSpr_get(PPCInterpreter_t* hCPU, uint32 spr) +{ + if constexpr(ppcItpCtrl::allowSupervisorMode) + { + // todo - check if in supervisor mode or user mode + return PPCSprSupervisor_get(hCPU, spr); + } + + uint32 v = 0; + switch (spr) + { + case SPR_LR: + v = hCPU->spr.LR; + break; + case SPR_CTR: + v = hCPU->spr.CTR; + break; + case SPR_XER: + v = PPCInterpreter_getXER(hCPU); + break; + case SPR_DEC: + // special handling for DEC register + { + assert_dbg(); + uint64 passedCycled = PPCInterpreter_getMainCoreCycleCounter() - ppcMainThreadDECCycleStart; + if (passedCycled >= (uint64)ppcMainThreadDECCycleValue) + v = 0; + else + v = (uint32)(ppcMainThreadDECCycleValue - passedCycled); + } + break; + case SPR_UPIR: + v = hCPU->spr.UPIR; + break; + case SPR_PVR: + assert_dbg(); + //v = hCPU->sprNew.PVR; + break; + case SPR_UGQR0: + case SPR_UGQR1: + case SPR_UGQR2: + case SPR_UGQR3: + case SPR_UGQR4: + case SPR_UGQR5: + case SPR_UGQR6: + case SPR_UGQR7: + v = hCPU->spr.UGQR[spr - SPR_UGQR0]; + break; + default: + debug_printf("[C%d] Get unhandled SPR %d\n", hCPU->spr.UPIR, spr); +#ifndef PUBLIC_RELEASE + assert_dbg(); +#endif + break; + } + + + + //if( spr == SPR_LR || spr == SPR_PVR || spr == SPR_UPIR || spr == SPR_SCR || (spr >= SPR_UGQR0 && spr <= SPR_UGQR7) ) + //{ + // // readable registers + // v = hCPU->spr[spr]; + //} + //else if( spr == SPR_DEC ) + //{ + // // special handling for DEC register + // uint64 passedCycled = PPCInterpreter_getMainCoreCycleCounter() - ppcMainThreadDECCycleStart; + // if( passedCycled >= (uint64)ppcMainThreadDECCycleValue ) + // v = 0; + // else + // v = ppcMainThreadDECCycleValue - passedCycled; + //} + //else if( spr == SPR_XER ) + //{ + // v = PPCInterpreter_getXER(hCPU); + //} + //else + //{ + // debug_printf("[C%d] Get unhandled SPR %d value: %08x\n", hCPU->spr[SPR_UPIR], spr, hCPU->spr[spr]); + // v = hCPU->spr[spr]; + //} + return v; +} \ No newline at end of file diff --git a/src/Cafe/HW/Espresso/PPCCallback.h b/src/Cafe/HW/Espresso/PPCCallback.h new file mode 100644 index 00000000..0771020d --- /dev/null +++ b/src/Cafe/HW/Espresso/PPCCallback.h @@ -0,0 +1,75 @@ +#pragma once +#include "PPCState.h" + +struct PPCCoreCallbackData_t +{ + sint32 gprCount = 0; + sint32 floatCount = 0; +}; + +// callback functions +inline uint32 PPCCoreCallback(MPTR function, const PPCCoreCallbackData_t& data) +{ + return PPCCore_executeCallbackInternal(function)->gpr[3]; +} + +template <typename T, typename... TArgs> +uint32 PPCCoreCallback(MPTR function, PPCCoreCallbackData_t& data, T currentArg, TArgs... args) +{ + cemu_assert_debug(data.gprCount <= 8); + cemu_assert_debug(data.floatCount <= 8); + if constexpr (std::is_pointer_v<T>) + { + ppcInterpreterCurrentInstance->gpr[3 + data.gprCount] = MEMPTR(currentArg).GetMPTR(); + data.gprCount++; + } + else if constexpr (std::is_base_of_v<MEMPTRBase, std::remove_reference_t<T>>) + { + ppcInterpreterCurrentInstance->gpr[3 + data.gprCount] = currentArg.GetMPTR(); + data.gprCount++; + } + else if constexpr (std::is_reference_v<T>) + { + ppcInterpreterCurrentInstance->gpr[3 + data.gprCount] = MEMPTR(¤tArg).GetMPTR(); + data.gprCount++; + } + else if constexpr(std::is_enum_v<T>) + { + using TEnum = typename std::underlying_type<T>::type; + return PPCCoreCallback<TEnum>(function, data, (TEnum)currentArg, std::forward(args)...); + } + else if constexpr (std::is_floating_point_v<T>) + { + ppcInterpreterCurrentInstance->fpr[1 + data.floatCount].fpr = (double)currentArg; + data.floatCount++; + } + else if constexpr (std::is_integral_v<T> && sizeof(T) == sizeof(uint64)) + { + ppcInterpreterCurrentInstance->gpr[3 + data.gprCount] = (uint32)(currentArg >> 32); // high + ppcInterpreterCurrentInstance->gpr[3 + data.gprCount + 1] = (uint32)currentArg; // low + + data.gprCount += 2; + } + else + { + ppcInterpreterCurrentInstance->gpr[3 + data.gprCount] = (uint32)currentArg; + data.gprCount++; + } + + return PPCCoreCallback(function, data, args...); +} + +template <typename... TArgs> +uint32 PPCCoreCallback(MPTR function, TArgs... args) +{ + PPCCoreCallbackData_t data{}; + return PPCCoreCallback(function, data, std::forward<TArgs>(args)...); +} + +template <typename... TArgs> +uint32 PPCCoreCallback(void* functionPtr, TArgs... args) +{ + MEMPTR<void> _tmp{ functionPtr }; + PPCCoreCallbackData_t data{}; + return PPCCoreCallback(_tmp.GetMPTR(), data, std::forward<TArgs>(args)...); +} diff --git a/src/Cafe/HW/Espresso/PPCScheduler.cpp b/src/Cafe/HW/Espresso/PPCScheduler.cpp new file mode 100644 index 00000000..ebe6a473 --- /dev/null +++ b/src/Cafe/HW/Espresso/PPCScheduler.cpp @@ -0,0 +1,115 @@ +#include "Cafe/OS/libs/gx2/GX2.h" +#include "Cafe/HW/Latte/Core/Latte.h" +#include "Cafe/OS/libs/coreinit/coreinit_Alarm.h" +#include "Cafe/OS/libs/coreinit/coreinit_Thread.h" +#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" + +#include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h" +#include "Cafe/CafeSystem.h" + +uint32 ppcThreadQuantum = 45000; // execute 45000 instructions before thread reschedule happens, this value can be overwritten by game profiles + +void PPCInterpreter_relinquishTimeslice() +{ + if( ppcInterpreterCurrentInstance->remainingCycles >= 0 ) + { + ppcInterpreterCurrentInstance->skippedCycles = ppcInterpreterCurrentInstance->remainingCycles + 1; + ppcInterpreterCurrentInstance->remainingCycles = -1; + } +} + +void PPCCore_boostQuantum(sint32 numCycles) +{ + ppcInterpreterCurrentInstance->remainingCycles += numCycles; +} + +void PPCCore_deboostQuantum(sint32 numCycles) +{ + ppcInterpreterCurrentInstance->remainingCycles -= numCycles; +} + +namespace coreinit +{ + void __OSThreadSwitchToNext(); +} + +void PPCCore_switchToScheduler() +{ + cemu_assert_debug(__OSHasSchedulerLock() == false); // scheduler lock must not be hold past thread time slice + cemu_assert_debug(ppcInterpreterCurrentInstance->coreInterruptMask != 0 || CafeSystem::GetForegroundTitleId() == 0x000500001019e600); + __OSLockScheduler(); + coreinit::__OSThreadSwitchToNext(); + __OSUnlockScheduler(); +} + +void PPCCore_switchToSchedulerWithLock() +{ + cemu_assert_debug(__OSHasSchedulerLock() == true); // scheduler lock must be hold + cemu_assert_debug(ppcInterpreterCurrentInstance->coreInterruptMask != 0 || CafeSystem::GetForegroundTitleId() == 0x000500001019e600); + coreinit::__OSThreadSwitchToNext(); +} + +void _PPCCore_callbackExit(PPCInterpreter_t* hCPU) +{ + PPCInterpreter_relinquishTimeslice(); + hCPU->instructionPointer = 0; +} + +PPCInterpreter_t* PPCCore_executeCallbackInternal(uint32 functionMPTR) +{ + cemu_assert_debug(functionMPTR != 0); + PPCInterpreter_t* hCPU = ppcInterpreterCurrentInstance; + // remember LR and instruction pointer + uint32 lr = hCPU->spr.LR; + uint32 ip = hCPU->instructionPointer; + // save area + hCPU->gpr[1] -= 16 * 4; + // set LR + hCPU->spr.LR = PPCInterpreter_makeCallableExportDepr(_PPCCore_callbackExit); + // set instruction pointer + hCPU->instructionPointer = functionMPTR; + // execute code until we return from the function + while (true) + { + hCPU->remainingCycles = ppcThreadQuantum; + hCPU->skippedCycles = 0; + if (hCPU->remainingCycles > 0) + { + // try to enter recompiler immediately + PPCRecompiler_attemptEnter(hCPU, hCPU->instructionPointer); + // execute any remaining instructions in interpreter + while ((--hCPU->remainingCycles) >= 0) + { + PPCInterpreterSlim_executeInstruction(hCPU); + }; + } + if (hCPU->instructionPointer == 0) + { + // restore remaining cycles + hCPU->remainingCycles += hCPU->skippedCycles; + hCPU->skippedCycles = 0; + break; + } + coreinit::OSYieldThread(); + } + // save area + hCPU->gpr[1] += 16 * 4; + // restore LR and instruction pointer + hCPU->spr.LR = lr; + hCPU->instructionPointer = ip; + return hCPU; +} + +__declspec(dllexport) void PPCCore_executeCallback(uint32 functionMPTR) +{ + PPCCore_executeCallbackInternal(functionMPTR); +} + +void PPCCore_deleteAllThreads() +{ + assert_dbg(); +} + +void PPCCore_init() +{ +} diff --git a/src/Cafe/HW/Espresso/PPCSchedulerLLE.cpp b/src/Cafe/HW/Espresso/PPCSchedulerLLE.cpp new file mode 100644 index 00000000..4732586a --- /dev/null +++ b/src/Cafe/HW/Espresso/PPCSchedulerLLE.cpp @@ -0,0 +1,245 @@ + +struct PPCInterpreterLLEContext_t +{ + uint8 padding[1024 * 128]; // reserved memory for stack (for recompiler mode) + PPCInterpreter_t cores[3]; +}; + +PPCInterpreterGlobal_t globalCPUState = { 0 }; + +void PPCCoreLLE_initCore(PPCInterpreter_t* hCPU, uint32 coreIndex) +{ + hCPU->spr.UPIR = coreIndex; + hCPU->global = &globalCPUState; +} + +#define SCR_C2 (0x200000) // enable core 2 +#define SCR_C1 (0x400000) // enable core 1 + +typedef struct +{ + uint32be ukn000; + uint32be ukn004; + uint32be ukn008; + uint32be ukn00C; + uint32be ukn010; + uint32be ukn014; + uint32be busFreq; + uint32be ukn01C; + uint32be ukn020[4]; + uint32be ukn030[4]; + uint32be ukn040[4]; + uint32be ukn050[4]; + uint32be ukn060[4]; + uint32be ukn070[4]; + uint32be ukn080[4]; + uint32be ukn090[4]; + uint32be ukn0A0[4]; + uint32be ukn0B0[4]; + uint32be ukn0C0; + struct + { + uint32be id; + uint32be baseAddress; + uint32be size; + }ramInfo[3]; + uint32 ukn0E8; + uint32 ukn0EC; + uint32 ukn0F0[4]; + uint32 ukn100[8]; + uint32 ukn120[8]; + uint32 ukn140[8]; + uint32 ukn160[8]; + uint32 ukn180[8]; + uint32 ukn1A0[8]; + uint32 ukn1C0[8]; + uint32 ukn1E0[8]; + uint32 ukn200[8]; + uint32 ukn220[8]; + uint32 ukn240[8]; + uint32 ukn260[8]; + uint32 ukn280[8]; + uint32 ukn2A0[8]; + uint32 ukn2C0[8]; + uint32 ukn2E0[8]; + uint32 ukn300[8]; + uint32 ukn320[8]; + uint32 ukn340[8]; + uint32 ukn360[8]; + uint32 ukn380[8]; + uint32be ukn3A0; + uint32be ukn3A4; + uint32be ukn3A8; + uint32be ukn3AC; + uint32be ukn3B0; + uint32be smdpAreaPtr; + uint32be ukn3B8; + uint32be ukn3BC; + uint32 ukn3C0[8]; + uint32 ukn3E0[8]; + uint32 ukn400; + uint32 ukn404; + uint32 ukn408; +}ppcBootParamBlock_t; // for kernel 5.5.2 + +static_assert(offsetof(ppcBootParamBlock_t, ramInfo) == 0xC4, ""); +static_assert(offsetof(ppcBootParamBlock_t, busFreq) == 0x18, ""); +static_assert(offsetof(ppcBootParamBlock_t, smdpAreaPtr) == 0x3B4, ""); +static_assert(offsetof(ppcBootParamBlock_t, ukn400) == 0x400, ""); + +void PPCCoreLLE_setupBootParamBlock() +{ + ppcBootParamBlock_t* bootParamBlock = (ppcBootParamBlock_t*)memory_getPointerFromPhysicalOffset(0x01FFF000); + memset(bootParamBlock, 0, sizeof(ppcBootParamBlock_t)); + + // setup RAM info + //PPCBaseAddress 0x8000000 0x00000000 0x28000000 + //PPCSize 0x120000 0x2000000 0xA8000000 + + bootParamBlock->ukn004 = 0x40C; + + bootParamBlock->busFreq = ESPRESSO_BUS_CLOCK; + + bootParamBlock->ramInfo[0].id = 0; + bootParamBlock->ramInfo[0].baseAddress = 0x8000000; + bootParamBlock->ramInfo[0].size = 0x120000; + bootParamBlock->ramInfo[1].id = 1; + bootParamBlock->ramInfo[1].baseAddress = 0x00000000; + bootParamBlock->ramInfo[1].size = 0x2000000; + bootParamBlock->ramInfo[2].id = 2; + bootParamBlock->ramInfo[2].baseAddress = 0x28000000; + bootParamBlock->ramInfo[2].size = 0xA8000000; + +} +typedef struct +{ + uint32be magic; + uint32be count; + uint32 _padding08[14]; + /* +0x0040 */ uint32be commandsReadIndex; // written by IOSU + uint32 _padding44[15]; + /* +0x0080 */ uint32be commandsWriteIndex; + uint32 _padding84[15]; + /* +0x00C0 */ uint32be resultsReadIndex; + uint32 _paddingC4[15]; + /* +0x0100 */ uint32be resultsWriteIndex; // written by IOSU + uint32 _padding104[15]; + /* +0x0140 */ uint32be commandPtrs[0xC00]; + /* +0x3140 */ uint32be resultPtrs[0xC00]; +}smdpArea_t; + +static_assert(offsetof(smdpArea_t, commandsReadIndex) == 0x0040, ""); +static_assert(offsetof(smdpArea_t, commandsWriteIndex) == 0x0080, ""); +static_assert(offsetof(smdpArea_t, resultsReadIndex) == 0x00C0, ""); +static_assert(offsetof(smdpArea_t, resultsWriteIndex) == 0x0100, ""); +static_assert(offsetof(smdpArea_t, resultPtrs) == 0x3140, ""); + +typedef struct +{ + uint32be type; + uint32be ukn04; + uint32be ukn08; + uint32be ukn0C; + uint32be ukn10; + uint32be ukn14; + uint32be ukn18; + uint32be ukn1C; + uint32be ukn20; + uint32be ukn24; + uint32be ukn28; + uint32be ukn2C; +}smdpCommand_t; + +void smdpArea_pushResult(smdpArea_t* smdpArea, MPTR result) +{ + //smdpArea. + smdpArea->resultPtrs[(uint32)smdpArea->resultsWriteIndex] = result; + smdpArea->resultsWriteIndex = ((uint32)smdpArea->resultsWriteIndex + 1)%(uint32)smdpArea->count; +} + +void smdpArea_processCommand(smdpArea_t* smdpArea, smdpCommand_t* cmd) +{ + if (cmd->type == 1) + { + cmd->ukn08 = 1; + // cmd->ukn2C ? + forceLogDebug_printf("SMDP command received - todo"); + smdpArea_pushResult(smdpArea, memory_getVirtualOffsetFromPointer(cmd)); + } + else + { + assert_dbg(); + } +} + +void smdpArea_thread() +{ + while (true) + { + ppcBootParamBlock_t* bootParamBlock = (ppcBootParamBlock_t*)memory_getPointerFromPhysicalOffset(0x01FFF000); + if(bootParamBlock->smdpAreaPtr != MPTR_NULL) + { + smdpArea_t* smdpArea = (smdpArea_t*)memory_getPointerFromPhysicalOffset(bootParamBlock->smdpAreaPtr); + if (smdpArea->magic == 'smdp') + { + uint32 cmdReadIndex = smdpArea->commandsReadIndex; + uint32 cmdWriteIndex = smdpArea->commandsWriteIndex; + if (cmdReadIndex != cmdWriteIndex) + { + // new command + smdpArea_processCommand(smdpArea, (smdpCommand_t*)memory_getPointerFromPhysicalOffset(smdpArea->commandPtrs[cmdReadIndex])); + // increment read counter + cmdReadIndex = (cmdReadIndex + 1) % (uint32)smdpArea->count; + smdpArea->commandsReadIndex = cmdReadIndex; + } + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } +} + +void PPCCoreLLE_startSingleCoreScheduler(uint32 entrypoint) +{ + PPCInterpreterLLEContext_t* cpuContext = (PPCInterpreterLLEContext_t*)malloc(sizeof(PPCInterpreterLLEContext_t)); + memset(cpuContext, 0, sizeof(PPCInterpreterLLEContext_t)); + + PPCCoreLLE_setupBootParamBlock(); + + PPCCoreLLE_initCore(cpuContext->cores + 0, 0); + PPCCoreLLE_initCore(cpuContext->cores + 1, 1); + PPCCoreLLE_initCore(cpuContext->cores + 2, 2); + + cpuContext->cores[0].instructionPointer = entrypoint; + cpuContext->cores[1].instructionPointer = 0xFFF00100; + cpuContext->cores[2].instructionPointer = 0xFFF00100; + // todo - calculate instruction pointer when core 1/2 is enabled (because entry point is determined by MSR exception vector bit) + std::thread(smdpArea_thread).detach(); + + while (true) + { + for (uint32 coreIndex = 0; coreIndex < 3; coreIndex++) + { + PPCInterpreter_t* hCPU = cpuContext->cores+coreIndex; + ppcInterpreterCurrentInstance = hCPU; + if (coreIndex == 1) + { + // check SCR core 1 enable bit + if ((globalCPUState.sprGlobal.scr&SCR_C1) == 0) + continue; + } + else if (coreIndex == 2) + { + // check SCR core 2 enable bit + if ((globalCPUState.sprGlobal.scr&SCR_C2) == 0) + continue; + } + + hCPU->remainingCycles = 10000; + while ((--hCPU->remainingCycles) >= 0) + { + PPCInterpreterFull_executeInstruction(hCPU); + }; + } + } + assert_dbg(); +} \ No newline at end of file diff --git a/src/Cafe/HW/Espresso/PPCState.h b/src/Cafe/HW/Espresso/PPCState.h new file mode 100644 index 00000000..540bbd4e --- /dev/null +++ b/src/Cafe/HW/Espresso/PPCState.h @@ -0,0 +1,255 @@ +#pragma once +#include "Cafe/HW/MMU/MMU.h" + +enum +{ + CPUException_NOTHING, + CPUException_FPUUNAVAILABLE, + CPUException_EXTERNAL, + CPUException_SYSTEMCALL +}; + +#define PPC_LWARX_RESERVATION_MAX (4) + +union FPR_t +{ + double fpr; + struct + { + double fp0; + double fp1; + }; + struct + { + uint64 guint; + }; + struct + { + uint64 fp0int; + uint64 fp1int; + }; +}; + +typedef struct +{ + struct + { + uint32 scr; + uint32 car; + //uint32 bcr; + }sprGlobal; + uint64 tb; +}PPCInterpreterGlobal_t; + +struct PPCInterpreter_t +{ + uint32 instructionPointer; + uint32 gpr[32]; + FPR_t fpr[32]; + uint32 fpscr; + uint8 cr[32]; // 0 -> bit not set, 1 -> bit set (upper 7 bits of each byte must always be zero) (cr0 starts at index 0, cr1 at index 4 ..) + uint8 xer_ca; // carry from xer + uint8 LSQE; + uint8 PSE; + // thread remaining cycles + sint32 remainingCycles; // if this value goes below zero, the next thread is scheduled + sint32 skippedCycles; // number of skipped cycles + struct + { + uint32 LR; + uint32 CTR; + uint32 XER; + uint32 UPIR; + uint32 UGQR[8]; + }spr; + // LWARX and STWCX + uint32 reservedMemAddr; + uint32 reservedMemValue; + /* Note: Everything above is potentially hardcoded into Cemuhook. Do not touch anything or it will risk breaking compatibility */ + // temporary storage for recompiler + FPR_t temporaryFPR[8]; + uint32 temporaryGPR[4]; + // values below this are not used by Cafe OS usermode + struct + { + uint32 fpecr; // is this the same register as fpscr ? + uint32 DEC; + uint32 srr0; + uint32 srr1; + uint32 PVR; + uint32 msr; + uint32 sprg[4]; + // DSI/ISI + uint32 dar; + uint32 dsisr; + // DMA + uint32 dmaU; + uint32 dmaL; + // MMU + uint32 dbatU[8]; + uint32 dbatL[8]; + uint32 ibatU[8]; + uint32 ibatL[8]; + uint32 sr[16]; + uint32 sdr1; + }sprExtended; + // global CPU values + PPCInterpreterGlobal_t* global; + // interpreter control + bool memoryException; + // core context (starts at 0xFFFFFF00?) + /* 0xFFFFFFE4 */ uint32 coreInterruptMask; + + // extra variables for recompiler + void* rspTemp; +}; + +// parameter access (legacy C style) + +static uint32 PPCInterpreter_getCallParamU32(PPCInterpreter_t* hCPU, uint32 index) +{ + if (index >= 8) + return memory_readU32(hCPU->gpr[1] + 8 + (index - 8) * 4); + return hCPU->gpr[3 + index]; +} + +static uint64 PPCInterpreter_getCallParamU64(PPCInterpreter_t* hCPU, uint32 index) +{ + uint64 v = ((uint64)PPCInterpreter_getCallParamU32(hCPU, index)) << 32ULL; + v |= ((uint64)PPCInterpreter_getCallParamU32(hCPU, index+1)); + return v; +} + +#define ppcGetCallParamU32(__index) PPCInterpreter_getCallParamU32(hCPU, __index) +#define ppcGetCallParamU16(__index) ((uint16)(PPCInterpreter_getCallParamU32(hCPU, __index)&0xFFFF)) +#define ppcGetCallParamU8(__index) ((uint8)(PPCInterpreter_getCallParamU32(hCPU, __index)&0xFF)) +#define ppcGetCallParamStruct(__index, __type) ((__type*)memory_getPointerFromVirtualOffsetAllowNull(PPCInterpreter_getCallParamU32(hCPU, __index))) + +// legacy way of accessing parameters +#define ppcDefineParamU32(__name, __index) uint32 __name = PPCInterpreter_getCallParamU32(hCPU, __index) +#define ppcDefineParamU16(__name, __index) uint16 __name = (uint16)PPCInterpreter_getCallParamU32(hCPU, __index) +#define ppcDefineParamU32BEPtr(__name, __index) uint32be* __name = (uint32be*)((uint8*)memory_getPointerFromVirtualOffsetAllowNull(PPCInterpreter_getCallParamU32(hCPU, __index))) +#define ppcDefineParamS32(__name, __index) sint32 __name = (sint32)PPCInterpreter_getCallParamU32(hCPU, __index) +#define ppcDefineParamU64(__name, __index) uint64 __name = PPCInterpreter_getCallParamU64(hCPU, __index) +#define ppcDefineParamMPTR(__name, __index) MPTR __name = (MPTR)PPCInterpreter_getCallParamU32(hCPU, __index) +#define ppcDefineParamMEMPTR(__name, __type, __index) MEMPTR<__type> __name{PPCInterpreter_getCallParamU32(hCPU, __index)} +#define ppcDefineParamU8(__name, __index) uint8 __name = (PPCInterpreter_getCallParamU32(hCPU, __index)&0xFF) +#define ppcDefineParamStructPtr(__name, __type, __index) __type* __name = ((__type*)memory_getPointerFromVirtualOffsetAllowNull(PPCInterpreter_getCallParamU32(hCPU, __index))) +#define ppcDefineParamTypePtr(__name, __type, __index) __type* __name = ((__type*)memory_getPointerFromVirtualOffsetAllowNull(PPCInterpreter_getCallParamU32(hCPU, __index))) +#define ppcDefineParamPtr(__name, __type, __index) __type* __name = ((__type*)memory_getPointerFromVirtualOffsetAllowNull(PPCInterpreter_getCallParamU32(hCPU, __index))) +#define ppcDefineParamStr(__name, __index) char* __name = ((char*)memory_getPointerFromVirtualOffsetAllowNull(PPCInterpreter_getCallParamU32(hCPU, __index))) +#define ppcDefineParamUStr(__name, __index) uint8* __name = ((uint8*)memory_getPointerFromVirtualOffsetAllowNull(PPCInterpreter_getCallParamU32(hCPU, __index))) +#define ppcDefineParamWStr(__name, __index) wchar_t* __name = ((wchar_t*)memory_getPointerFromVirtualOffsetAllowNull(PPCInterpreter_getCallParamU32(hCPU, __index))) +#define ppcDefineParamWStrBE(__name, __index) uint16be* __name = ((uint16be*)memory_getPointerFromVirtualOffsetAllowNull(PPCInterpreter_getCallParamU32(hCPU, __index))) + +// GPR constants + +#define GPR_SP 1 + +// interpreter functions + +PPCInterpreter_t* PPCInterpreter_createInstance(unsigned int Entrypoint); +PPCInterpreter_t* PPCInterpreter_getCurrentInstance(); + +uint64 PPCInterpreter_getMainCoreCycleCounter(); + +void PPCInterpreter_nextInstruction(PPCInterpreter_t* cpuInterpreter); +void PPCInterpreter_jumpToInstruction(PPCInterpreter_t* cpuInterpreter, uint32 newIP); + +void PPCInterpreterSlim_executeInstruction(PPCInterpreter_t* hCPU); +void PPCInterpreterFull_executeInstruction(PPCInterpreter_t* hCPU); + +// misc + +uint32 PPCInterpreter_getXER(PPCInterpreter_t* hCPU); +void PPCInterpreter_setXER(PPCInterpreter_t* hCPU, uint32 v); + +// Wii U clocks (deprecated. Moved to Espresso/Const.h) +#define ESPRESSO_CORE_CLOCK 1243125000 +#define ESPRESSO_BUS_CLOCK 248625000 +#define ESPRESSO_TIMER_CLOCK (ESPRESSO_BUS_CLOCK/4) // 62156250 + +#define ESPRESSO_CORE_CLOCK_TO_TIMER_CLOCK(__cc) ((__cc)/20ULL) + +// interrupt vectors +#define CPU_EXCEPTION_DSI 0x00000300 +#define CPU_EXCEPTION_INTERRUPT 0x00000500 // todo: validate +#define CPU_EXCEPTION_FPUUNAVAIL 0x00000800 // todo: validate +#define CPU_EXCEPTION_SYSTEMCALL 0x00000C00 // todo: validate +#define CPU_EXCEPTION_DECREMENTER 0x00000900 // todo: validate + +// FPU available check +//#define FPUCheckAvailable() if ((hCPU->msr & MSR_FP) == 0) { IPTException(hCPU, CPU_EXCEPTION_FPUUNAVAIL); return; } +#define FPUCheckAvailable() // since the emulated code always runs in usermode we can assume that MSR_FP is always set + +// spr +void PPCSpr_set(PPCInterpreter_t* hCPU, uint32 spr, uint32 newValue); +uint32 PPCSpr_get(PPCInterpreter_t* hCPU, uint32 spr); + +uint32 PPCInterpreter_getCoreIndex(PPCInterpreter_t* hCPU); +uint32 PPCInterpreter_getCurrentCoreIndex(); + +// decrement register +void PPCInterpreter_setDEC(PPCInterpreter_t* hCPU, uint32 newValue); + +// timing for main processor +extern volatile uint64 ppcMainThreadCycleCounter; +extern uint64 ppcCyclesSince2000; // on init this is set to the cycles that passed since 1.1.2000 +extern uint64 ppcCyclesSince2000TimerClock; // on init this is set to the cycles that passed since 1.1.2000 / 20 +extern uint64 ppcCyclesSince2000_UTC; +extern uint64 ppcMainThreadDECCycleValue; // value that was set to dec register +extern uint64 ppcMainThreadDECCycleStart; // at which cycle the dec register was set + +// PPC timer +void PPCTimer_init(); +void PPCTimer_waitForInit(); +uint64 PPCTimer_getFromRDTSC(); +bool PPCTimer_hasInvariantRDTSCSupport(); + +uint64 PPCTimer_microsecondsToTsc(uint64 us); +uint64 PPCTimer_tscToMicroseconds(uint64 us); +uint64 PPCTimer_getRawTsc(); + +void PPCTimer_start(); + +// core info and control +extern uint32 ppcThreadQuantum; + +extern thread_local PPCInterpreter_t *ppcInterpreterCurrentInstance; +uint8* PPCInterpreterGetAndModifyStackPointer(sint32 offset); +uint8* PPCInterpreterGetStackPointer(); +void PPCInterpreterModifyStackPointer(sint32 offset); + +uint32 PPCInterpreter_makeCallableExportDepr(void (*ppcCallableExport)(PPCInterpreter_t* hCPU)); + +static inline float flushDenormalToZero(float f) +{ + uint32 v = *(uint32*)&f; + return *(float*)&v; +} + +// HLE interface + +typedef void(*HLECALL)(PPCInterpreter_t* hCPU); + +typedef sint32 HLEIDX; +HLEIDX PPCInterpreter_registerHLECall(HLECALL hleCall); +HLECALL PPCInterpreter_getHLECall(HLEIDX funcIndex); + +// HLE scheduler + +void PPCCore_deleteAllThreads(); +void PPCInterpreter_relinquishTimeslice(); + +void PPCCore_boostQuantum(sint32 numCycles); +void PPCCore_deboostQuantum(sint32 numCycles); + +void PPCCore_switchToScheduler(); +void PPCCore_switchToSchedulerWithLock(); + +PPCInterpreter_t* PPCCore_executeCallbackInternal(uint32 functionMPTR); +void PPCCore_init(); + +// LLE scheduler + +void PPCCoreLLE_startSingleCoreScheduler(uint32 entrypoint); diff --git a/src/Cafe/HW/Espresso/PPCTimer.cpp b/src/Cafe/HW/Espresso/PPCTimer.cpp new file mode 100644 index 00000000..160b776a --- /dev/null +++ b/src/Cafe/HW/Espresso/PPCTimer.cpp @@ -0,0 +1,194 @@ +#include "Cafe/HW/Espresso/Const.h" +#include <immintrin.h> +#include "asm/x64util.h" +#include "config/ActiveSettings.h" +#include "util/helpers/fspinlock.h" +#include "util/highresolutiontimer/HighResolutionTimer.h" + +#if BOOST_OS_LINUX > 0 +static __inline__ +unsigned __int64 _umul128(unsigned __int64, + unsigned __int64, + unsigned __int64*); +#endif + +uint64 _rdtscLastMeasure = 0; +uint64 _rdtscFrequency = 0; + +struct uint128_t +{ + uint64 low; + uint64 high; +}; + +static_assert(sizeof(uint128_t) == 16); + +uint128_t _rdtscAcc{}; + +#pragma intrinsic(__rdtsc) + +uint64 muldiv64(uint64 a, uint64 b, uint64 d) +{ + uint64 diva = a / d; + uint64 moda = a % d; + uint64 divb = b / d; + uint64 modb = b % d; + return diva * b + moda * divb + moda * modb / d; +} + +bool PPCTimer_hasInvariantRDTSCSupport() +{ + uint32 cpuv[4]; + __cpuid((int*)cpuv, 0x80000007); + return ((cpuv[3] >> 8) & 1); +} + +uint64 PPCTimer_estimateRDTSCFrequency() +{ + if (PPCTimer_hasInvariantRDTSCSupport() == false) + forceLog_printf("Invariant TSC not supported"); + + _mm_mfence(); + unsigned __int64 tscStart = __rdtsc(); + unsigned int startTime = GetTickCount(); + HRTick startTick = HighResolutionTimer::now().getTick(); + // wait roughly 3 seconds + while (true) + { + if ((GetTickCount() - startTime) >= 3000) + break; + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + _mm_mfence(); + HRTick stopTick = HighResolutionTimer::now().getTick(); + unsigned __int64 tscEnd = __rdtsc(); + // derive frequency approximation from measured time difference + uint64 tsc_diff = tscEnd - tscStart; + uint64 hrtFreq = 0; + uint64 hrtDiff = HighResolutionTimer::getTimeDiffEx(startTick, stopTick, hrtFreq); + uint64 tsc_freq = muldiv64(tsc_diff, hrtFreq, hrtDiff); + + // uint64 freqMultiplier = tsc_freq / hrtFreq; + //forceLog_printf("RDTSC measurement test:"); + //forceLog_printf("TSC-diff: 0x%016llx", tsc_diff); + //forceLog_printf("TSC-freq: 0x%016llx", tsc_freq); + //forceLog_printf("HPC-diff: 0x%016llx", qpc_diff); + //forceLog_printf("HPC-freq: 0x%016llx", (uint64)qpc_freq.QuadPart); + //forceLog_printf("Multiplier: 0x%016llx", freqMultiplier); + + return tsc_freq; +} + +int PPCTimer_initThread() +{ + _rdtscFrequency = PPCTimer_estimateRDTSCFrequency(); + return 0; +} + +void PPCTimer_init() +{ + std::thread t(PPCTimer_initThread); + t.detach(); + _rdtscLastMeasure = __rdtsc(); +} + +uint64 _tickSummary = 0; + +void PPCTimer_start() +{ + _rdtscLastMeasure = __rdtsc(); + _tickSummary = 0; +} + +uint64 PPCTimer_getRawTsc() +{ + return __rdtsc(); +} + +uint64 PPCTimer_microsecondsToTsc(uint64 us) +{ + return (us * _rdtscFrequency) / 1000000ULL; +} + +uint64 PPCTimer_tscToMicroseconds(uint64 us) +{ + uint128_t r{}; + #if BOOST_OS_WINDOWS > 0 + r.low = _umul128(us, 1000000ULL, &r.high); + #else + r.low = _umul128(us, 1000000ULL, (unsigned long long*)&r.high); + #endif + + + uint64 remainder; +#if _MSC_VER < 1923 + const uint64 microseconds = udiv128(r.low, r.high, _rdtscFrequency, &remainder); +#else + const uint64 microseconds = _udiv128(r.high, r.low, _rdtscFrequency, &remainder); +#endif + + return microseconds; +} + +bool PPCTimer_isReady() +{ + return _rdtscFrequency != 0; +} + +void PPCTimer_waitForInit() +{ + while (!PPCTimer_isReady()) std::this_thread::sleep_for(std::chrono::milliseconds(10)); +} + +FSpinlock sTimerSpinlock; + +// thread safe +uint64 PPCTimer_getFromRDTSC() +{ + sTimerSpinlock.acquire(); + _mm_mfence(); + uint64 rdtscCurrentMeasure = __rdtsc(); + uint64 rdtscDif = rdtscCurrentMeasure - _rdtscLastMeasure; + // optimized max(rdtscDif, 0) without conditionals + rdtscDif = rdtscDif & ~(uint64)((sint64)rdtscDif >> 63); + + uint128_t diff{}; +#if BOOST_OS_WINDOWS > 0 + diff.low = _umul128(rdtscDif, Espresso::CORE_CLOCK, &diff.high); +#else + diff.low = _umul128(rdtscDif, Espresso::CORE_CLOCK, (unsigned long long*)&diff.high); +#endif + + if(rdtscCurrentMeasure > _rdtscLastMeasure) + _rdtscLastMeasure = rdtscCurrentMeasure; // only travel forward in time + + uint8 c = 0; + #if BOOST_OS_WINDOWS > 0 + c = _addcarry_u64(c, _rdtscAcc.low, diff.low, &_rdtscAcc.low); + _addcarry_u64(c, _rdtscAcc.high, diff.high, &_rdtscAcc.high); + #else + // requires casting because of long / long long nonesense + c = _addcarry_u64(c, _rdtscAcc.low, diff.low, (unsigned long long*)&_rdtscAcc.low); + _addcarry_u64(c, _rdtscAcc.high, diff.high, (unsigned long long*)&_rdtscAcc.high); + #endif + + uint64 remainder; +#if _MSC_VER < 1923 + uint64 elapsedTick = udiv128(_rdtscAcc.low, _rdtscAcc.high, _rdtscFrequency, &remainder); +#else + uint64 elapsedTick = _udiv128(_rdtscAcc.high, _rdtscAcc.low, _rdtscFrequency, &remainder); +#endif + + _rdtscAcc.low = remainder; + _rdtscAcc.high = 0; + + // timer scaling + elapsedTick <<= 3ull; // *8 + uint8 timerShiftFactor = ActiveSettings::GetTimerShiftFactor(); + elapsedTick >>= timerShiftFactor; + + _tickSummary += elapsedTick; + + sTimerSpinlock.release(); + return _tickSummary; +} diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCFunctionBoundaryTracker.h b/src/Cafe/HW/Espresso/Recompiler/PPCFunctionBoundaryTracker.h new file mode 100644 index 00000000..3fc48a93 --- /dev/null +++ b/src/Cafe/HW/Espresso/Recompiler/PPCFunctionBoundaryTracker.h @@ -0,0 +1,293 @@ +#pragma once +#include "Cafe/HW/Espresso/EspressoISA.h" +#include "Cafe/HW/MMU/MMU.h" + +bool GamePatch_IsNonReturnFunction(uint32 hleIndex); + +// utility class to determine shape of a function +class PPCFunctionBoundaryTracker +{ +public: + struct PPCRange_t + { + PPCRange_t() {}; + PPCRange_t(uint32 _startAddress) : startAddress(_startAddress) {}; + + uint32 startAddress{}; + uint32 length{}; + //bool isProcessed{false}; + + uint32 getEndAddress() const { return startAddress + length; }; + }; + +public: + void trackStartPoint(MPTR startAddress) + { + processRange(startAddress, nullptr, nullptr); + processBranchTargets(); + } + + bool getRangeForAddress(uint32 address, PPCRange_t& range) + { + for (auto itr : map_ranges) + { + if (address >= itr->startAddress && address < (itr->startAddress + itr->length)) + { + range = *itr; + return true; + } + } + return false; + } + +private: + void addBranchDestination(PPCRange_t* sourceRange, MPTR address) + { + map_branchTargets.emplace(address); + } + + // process flow of instruction + // returns false if the IP cannot increment past the current instruction + bool processInstruction(PPCRange_t* range, MPTR address) + { + // parse instructions + uint32 opcode = memory_readU32(address); + switch (Espresso::GetPrimaryOpcode(opcode)) + { + case Espresso::PrimaryOpcode::ZERO: + { + if (opcode == 0) + return false; // invalid instruction + break; + } + case Espresso::PrimaryOpcode::VIRTUAL_HLE: + { + // end of function + // is there a jump to a instruction after this one? + uint32 hleFuncId = opcode & 0xFFFF; + if (hleFuncId >= 0x1000 && hleFuncId < 0x4000) + { + if (GamePatch_IsNonReturnFunction(hleFuncId - 0x1000) == false) + { + return true; + } + } + return false; + } + case Espresso::PrimaryOpcode::BC: + { + uint32 BD, BI; + Espresso::BOField BO; + bool AA, LK; + Espresso::decodeOp_BC(opcode, BD, BO, BI, AA, LK); + uint32 branchTarget = AA ? BD : BD + address; + if (!LK) + addBranchDestination(range, branchTarget); + break; + } + case Espresso::PrimaryOpcode::B: + { + uint32 LI; + bool AA, LK; + Espresso::decodeOp_B(opcode, LI, AA, LK); + uint32 branchTarget = AA ? LI : LI + address; + if (!LK) + { + addBranchDestination(range, branchTarget); + // if the next two or previous two instructions are branch instructions, we assume that they are destinations of a jump table + // todo - can we make this more reliable by checking for BCTR or similar instructions first? + // example: The Swapper 0x01B1FC04 + if (PPCRecompilerCalcFuncSize_isUnconditionalBranchInstruction(memory_readU32(address + 4)) && PPCRecompilerCalcFuncSize_isUnconditionalBranchInstruction(memory_readU32(address + 8)) || + PPCRecompilerCalcFuncSize_isUnconditionalBranchInstruction(memory_readU32(address - 8)) && PPCRecompilerCalcFuncSize_isUnconditionalBranchInstruction(memory_readU32(address - 4))) + { + return true; + } + return false; // current flow ends at unconditional branch instruction + } + break; + } + case Espresso::PrimaryOpcode::GROUP_19: + switch (Espresso::GetGroup19Opcode(opcode)) + { + case Espresso::Opcode19::BCLR: + { + Espresso::BOField BO; + uint32 BI; + bool LK; + Espresso::decodeOp_BCLR(opcode, BO, BI, LK); + if (BO.branchAlways() && !LK) + { + // unconditional BLR + return false; + } + break; + } + case Espresso::Opcode19::BCCTR: + if (opcode == 0x4E800420) + { + // unconditional BCTR + // this instruction is often used for switch statements, therefore we should be wary of ending the function here + // It's better to overestimate function size than to predict sizes that are too short + + // Currently we only end the function if the BCTR is followed by a NOP (alignment) or invalid instruction + // todo: improve robustness, find better ways to handle false positives + uint32 nextOpcode = memory_readU32(address + 4); + + if (nextOpcode == 0x60000000 || PPCRecompilerCalcFuncSize_isValidInstruction(nextOpcode) == false) + { + return false; + } + return true; + } + // conditional BCTR + return true; + default: + break; + } + break; + default: + break; + } + return true; + } + + void checkForCollisions() + { +#ifndef PUBLIC_RELEASE + uint32 endOfPrevious = 0; + for (auto itr : map_ranges) + { + if (endOfPrevious > itr->startAddress) + { + cemu_assert_debug(false); + } + endOfPrevious = itr->startAddress + itr->length; + } +#endif + } + + // nextRange must point to the closest range after startAddress, or NULL if there is none + void processRange(MPTR startAddress, PPCRange_t* previousRange, PPCRange_t* nextRange) + { + checkForCollisions(); + cemu_assert_debug(previousRange == nullptr || (startAddress == (previousRange->startAddress + previousRange->length))); + PPCRange_t* newRange; + if (previousRange && (previousRange->startAddress + previousRange->length) == startAddress) + { + newRange = previousRange; + } + else + { + cemu_assert_debug(previousRange == nullptr); + newRange = new PPCRange_t(startAddress); + map_ranges.emplace(newRange); + } + // process instruction flow until it is interrupted by a non-conditional branch + MPTR currentAddress = startAddress; + MPTR endAddress = 0xFFFFFFFF; + if (nextRange) + endAddress = nextRange->startAddress; + while (currentAddress < endAddress) + { + if (!processInstruction(newRange, currentAddress)) + { + currentAddress += 4; + break; + } + currentAddress += 4; + } + newRange->length = currentAddress - newRange->startAddress; + + if (nextRange && currentAddress >= nextRange->startAddress) + { + // merge with next range + newRange->length = (nextRange->startAddress + nextRange->length) - newRange->startAddress; + map_ranges.erase(nextRange); + delete nextRange; + checkForCollisions(); + return; + } + checkForCollisions(); + } + + // find first unvisited branch target and start a new range there + // return true if method should be called again + bool processBranchTargetsSinglePass() + { + cemu_assert_debug(!map_ranges.empty()); + auto rangeItr = map_ranges.begin(); + + PPCRange_t* previousRange = nullptr; + for (std::set<uint32_t>::const_iterator targetItr = map_branchTargets.begin() ; targetItr != map_branchTargets.end(); ) + { + while (rangeItr != map_ranges.end() && ((*rangeItr)->startAddress + (*rangeItr)->length) <= (*targetItr)) + { + previousRange = *rangeItr; + rangeItr++; + if (rangeItr == map_ranges.end()) + { + // last range reached + if ((previousRange->startAddress + previousRange->length) == *targetItr) + processRange(*targetItr, previousRange, nullptr); + else + processRange(*targetItr, nullptr, nullptr); + return true; + } + } + + if ((*targetItr) >= (*rangeItr)->startAddress && + (*targetItr) < ((*rangeItr)->startAddress + (*rangeItr)->length)) + { + // delete visited targets + targetItr = map_branchTargets.erase(targetItr); + continue; + } + + cemu_assert_debug((*rangeItr)->startAddress > (*targetItr)); + if (previousRange && (previousRange->startAddress + previousRange->length) == *targetItr) + processRange(*targetItr, previousRange, *rangeItr); // extend previousRange + else + processRange(*targetItr, nullptr, *rangeItr); + return true; + } + return false; + } + + void processBranchTargets() + { + while (processBranchTargetsSinglePass()); + } + + private: + bool PPCRecompilerCalcFuncSize_isUnconditionalBranchInstruction(uint32 opcode) + { + if (Espresso::GetPrimaryOpcode(opcode) == Espresso::PrimaryOpcode::B) + { + uint32 LI; + bool AA, LK; + Espresso::decodeOp_B(opcode, LI, AA, LK); + if (!LK) + return true; + } + return false; + } + + bool PPCRecompilerCalcFuncSize_isValidInstruction(uint32 opcode) + { + if ((opcode >> 26) == 0) + return false; + return true; + } + +private: + struct RangePtrCmp + { + bool operator()(const PPCRange_t* lhs, const PPCRange_t* rhs) const + { + return lhs->startAddress < rhs->startAddress; + } + }; + + std::set<PPCRange_t*, RangePtrCmp> map_ranges; + std::set<uint32> map_branchTargets; +}; \ No newline at end of file diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp b/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp new file mode 100644 index 00000000..75adee35 --- /dev/null +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp @@ -0,0 +1,593 @@ +#include "Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h" +#include "PPCFunctionBoundaryTracker.h" +#include "PPCRecompiler.h" +#include "PPCRecompilerIml.h" +#include "PPCRecompilerX64.h" +#include "Cafe/OS/RPL/rpl.h" +#include "util/containers/RangeStore.h" +#include "Cafe/OS/libs/coreinit/coreinit_CodeGen.h" +#include "config/ActiveSettings.h" +#include "config/LaunchSettings.h" + +#include "util/helpers/fspinlock.h" +#include "Common/ExceptionHandler/ExceptionHandler.h" +#include "util/helpers/helpers.h" + +#include "util/MemMapper/MemMapper.h" + +struct PPCInvalidationRange +{ + MPTR startAddress; + uint32 size; + + PPCInvalidationRange(MPTR _startAddress, uint32 _size) : startAddress(_startAddress), size(_size) {}; +}; + +struct +{ + FSpinlock recompilerSpinlock; + std::queue<MPTR> targetQueue; + std::vector<PPCInvalidationRange> invalidationRanges; +}PPCRecompilerState; + +RangeStore<PPCRecFunction_t*, uint32, 7703, 0x2000> rangeStore_ppcRanges; + +void ATTR_MS_ABI (*PPCRecompiler_enterRecompilerCode)(uint64 codeMem, uint64 ppcInterpreterInstance); +void ATTR_MS_ABI (*PPCRecompiler_leaveRecompilerCode_visited)(); +void ATTR_MS_ABI (*PPCRecompiler_leaveRecompilerCode_unvisited)(); + +PPCRecompilerInstanceData_t* ppcRecompilerInstanceData; + +bool ppcRecompilerEnabled = false; + +// this function does never block and can fail if the recompiler lock cannot be acquired immediately +void PPCRecompiler_visitAddressNoBlock(uint32 enterAddress) +{ + // quick read-only check without lock + if (ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[enterAddress / 4] != PPCRecompiler_leaveRecompilerCode_unvisited) + return; + // try to acquire lock + if (!PPCRecompilerState.recompilerSpinlock.tryAcquire()) + return; + auto funcPtr = ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[enterAddress / 4]; + if (funcPtr != PPCRecompiler_leaveRecompilerCode_unvisited) + { + // was visited since previous check + PPCRecompilerState.recompilerSpinlock.release(); + return; + } + // add to recompilation queue and flag as visited + PPCRecompilerState.targetQueue.emplace(enterAddress); + ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[enterAddress / 4] = PPCRecompiler_leaveRecompilerCode_visited; + + PPCRecompilerState.recompilerSpinlock.release(); +} + +void PPCRecompiler_recompileIfUnvisited(uint32 enterAddress) +{ + if (ppcRecompilerEnabled == false) + return; + PPCRecompiler_visitAddressNoBlock(enterAddress); +} + +void PPCRecompiler_enter(PPCInterpreter_t* hCPU, PPCREC_JUMP_ENTRY funcPtr) +{ +#if BOOST_OS_WINDOWS > 0 + uint32 prevState = _controlfp(0, 0); + _controlfp(_RC_NEAR, _MCW_RC); + PPCRecompiler_enterRecompilerCode((uint64)funcPtr, (uint64)hCPU); + _controlfp(prevState, _MCW_RC); + // debug recompiler exit - useful to find frequently executed functions which couldn't be recompiled + #ifndef PUBLIC_RELEASE + if (hCPU->remainingCycles > 0 && GetAsyncKeyState(VK_F4)) + { + auto t = std::chrono::high_resolution_clock::now(); + auto dur = std::chrono::duration_cast<std::chrono::microseconds>(t.time_since_epoch()).count(); + forceLog_printf("Recompiler exit: 0x%08x LR: 0x%08x Timestamp %lld.%04lld", hCPU->instructionPointer, hCPU->spr.LR, dur / 1000LL, (dur % 1000LL)); + } + #endif +#else + PPCRecompiler_enterRecompilerCode((uint64)funcPtr, (uint64)hCPU); +#endif + // after leaving recompiler prematurely attempt to recompile the code at the new location + if (hCPU->remainingCycles > 0) + { + PPCRecompiler_visitAddressNoBlock(hCPU->instructionPointer); + } +} + +void PPCRecompiler_attemptEnterWithoutRecompile(PPCInterpreter_t* hCPU, uint32 enterAddress) +{ + cemu_assert_debug(hCPU->instructionPointer == enterAddress); + if (ppcRecompilerEnabled == false) + return; + auto funcPtr = ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[enterAddress / 4]; + if (funcPtr != PPCRecompiler_leaveRecompilerCode_unvisited && funcPtr != PPCRecompiler_leaveRecompilerCode_visited) + { + cemu_assert_debug(ppcRecompilerInstanceData != nullptr); + PPCRecompiler_enter(hCPU, funcPtr); + } +} + +void PPCRecompiler_attemptEnter(PPCInterpreter_t* hCPU, uint32 enterAddress) +{ + cemu_assert_debug(hCPU->instructionPointer == enterAddress); + if (ppcRecompilerEnabled == false) + return; + if (hCPU->remainingCycles <= 0) + return; + auto funcPtr = ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[enterAddress / 4]; + if (funcPtr == PPCRecompiler_leaveRecompilerCode_unvisited) + { + PPCRecompiler_visitAddressNoBlock(enterAddress); + } + else if (funcPtr != PPCRecompiler_leaveRecompilerCode_visited) + { + // enter + cemu_assert_debug(ppcRecompilerInstanceData != nullptr); + PPCRecompiler_enter(hCPU, funcPtr); + } +} + +PPCRecFunction_t* PPCRecompiler_recompileFunction(PPCFunctionBoundaryTracker::PPCRange_t range, std::set<uint32>& entryAddresses, std::vector<std::pair<MPTR, uint32>>& entryPointsOut) +{ + if (range.startAddress >= PPC_REC_CODE_AREA_END) + { + cemuLog_force("Attempting to recompile function outside of allowed code area"); + return nullptr; + } + + uint32 codeGenRangeStart; + uint32 codeGenRangeSize = 0; + coreinit::OSGetCodegenVirtAddrRangeInternal(codeGenRangeStart, codeGenRangeSize); + if (codeGenRangeSize != 0) + { + if (range.startAddress >= codeGenRangeStart && range.startAddress < (codeGenRangeStart + codeGenRangeSize)) + { + if (coreinit::codeGenShouldAvoid()) + { + return nullptr; + } + } + } + + PPCRecFunction_t* ppcRecFunc = new PPCRecFunction_t(); + ppcRecFunc->ppcAddress = range.startAddress; + ppcRecFunc->ppcSize = range.length; + // generate intermediate code + ppcImlGenContext_t ppcImlGenContext = { 0 }; + bool compiledSuccessfully = PPCRecompiler_generateIntermediateCode(ppcImlGenContext, ppcRecFunc, entryAddresses); + if (compiledSuccessfully == false) + { + // todo: Free everything + PPCRecompiler_freeContext(&ppcImlGenContext); + delete ppcRecFunc; + return NULL; + } + // emit x64 code + bool x64GenerationSuccess = PPCRecompiler_generateX64Code(ppcRecFunc, &ppcImlGenContext); + if (x64GenerationSuccess == false) + { + PPCRecompiler_freeContext(&ppcImlGenContext); + return nullptr; + } + + // collect list of PPC-->x64 entry points + entryPointsOut.clear(); + for (sint32 s = 0; s < ppcImlGenContext.segmentListCount; s++) + { + PPCRecImlSegment_t* imlSegment = ppcImlGenContext.segmentList[s]; + if (imlSegment->isEnterable == false) + continue; + + uint32 ppcEnterOffset = imlSegment->enterPPCAddress; + uint32 x64Offset = imlSegment->x64Offset; + + entryPointsOut.emplace_back(ppcEnterOffset, x64Offset); + } + + PPCRecompiler_freeContext(&ppcImlGenContext); + return ppcRecFunc; +} + +bool PPCRecompiler_makeRecompiledFunctionActive(uint32 initialEntryPoint, PPCFunctionBoundaryTracker::PPCRange_t& range, PPCRecFunction_t* ppcRecFunc, std::vector<std::pair<MPTR, uint32>>& entryPoints) +{ + // update jump table + PPCRecompilerState.recompilerSpinlock.acquire(); + + // check if the initial entrypoint is still flagged for recompilation + // its possible that the range has been invalidated during the time it took to translate the function + if (ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[initialEntryPoint / 4] != PPCRecompiler_leaveRecompilerCode_visited) + { + PPCRecompilerState.recompilerSpinlock.release(); + return false; + } + + // check if the current range got invalidated in the time it took to recompile it + bool isInvalidated = false; + for (auto& invRange : PPCRecompilerState.invalidationRanges) + { + MPTR rStartAddr = invRange.startAddress; + MPTR rEndAddr = rStartAddr + invRange.size; + for (auto& recFuncRange : ppcRecFunc->list_ranges) + { + if (recFuncRange.ppcAddress < (rEndAddr) && (recFuncRange.ppcAddress + recFuncRange.ppcSize) >= rStartAddr) + { + isInvalidated = true; + break; + } + } + } + PPCRecompilerState.invalidationRanges.clear(); + if (isInvalidated) + { + PPCRecompilerState.recompilerSpinlock.release(); + return false; + } + + + // update jump table + for (auto& itr : entryPoints) + { + ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[itr.first / 4] = (PPCREC_JUMP_ENTRY)((uint8*)ppcRecFunc->x86Code + itr.second); + } + + + // due to inlining, some entrypoints can get optimized away + // therefore we reset all addresses that are still marked as visited (but not recompiled) + // we dont remove the points from the queue but any address thats not marked as visited won't get recompiled + // if they are reachable, the interpreter will queue them again + for (uint32 v = range.startAddress; v <= (range.startAddress + range.length); v += 4) + { + auto funcPtr = ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[v / 4]; + if (funcPtr == PPCRecompiler_leaveRecompilerCode_visited) + ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[v / 4] = PPCRecompiler_leaveRecompilerCode_unvisited; + } + + // register ranges + for (auto& r : ppcRecFunc->list_ranges) + { + r.storedRange = rangeStore_ppcRanges.storeRange(ppcRecFunc, r.ppcAddress, r.ppcAddress + r.ppcSize); + } + PPCRecompilerState.recompilerSpinlock.release(); + + + return true; +} + +void PPCRecompiler_recompileAtAddress(uint32 address) +{ + cemu_assert_debug(ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[address / 4] == PPCRecompiler_leaveRecompilerCode_visited); + + // get size + PPCFunctionBoundaryTracker funcBoundaries; + funcBoundaries.trackStartPoint(address); + // get range that encompasses address + PPCFunctionBoundaryTracker::PPCRange_t range; + if (funcBoundaries.getRangeForAddress(address, range) == false) + { + cemu_assert_debug(false); + } + + // todo - use info from previously compiled ranges to determine full size of this function (and merge all the entryAddresses) + + // collect all currently known entry points for this range + PPCRecompilerState.recompilerSpinlock.acquire(); + + std::set<uint32> entryAddresses; + + entryAddresses.emplace(address); + + PPCRecompilerState.recompilerSpinlock.release(); + + std::vector<std::pair<MPTR, uint32>> functionEntryPoints; + auto func = PPCRecompiler_recompileFunction(range, entryAddresses, functionEntryPoints); + + if (!func) + { + return; // recompilation failed + } + bool r = PPCRecompiler_makeRecompiledFunctionActive(address, range, func, functionEntryPoints); +} + +void PPCRecompiler_thread() +{ + SetThreadName("PPCRecompiler_thread"); + while (true) + { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + // asynchronous recompilation: + // 1) take address from queue + // 2) check if address is still marked as visited + // 3) if yes -> calculate size, gather all entry points, recompile and update jump table + while (true) + { + PPCRecompilerState.recompilerSpinlock.acquire(); + if (PPCRecompilerState.targetQueue.empty()) + { + PPCRecompilerState.recompilerSpinlock.release(); + break; + } + auto enterAddress = PPCRecompilerState.targetQueue.front(); + PPCRecompilerState.targetQueue.pop(); + + auto funcPtr = ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[enterAddress / 4]; + if (funcPtr != PPCRecompiler_leaveRecompilerCode_visited) + { + // only recompile functions if marked as visited + PPCRecompilerState.recompilerSpinlock.release(); + continue; + } + PPCRecompilerState.recompilerSpinlock.release(); + + PPCRecompiler_recompileAtAddress(enterAddress); + } + } +} + +#define PPC_REC_ALLOC_BLOCK_SIZE (4*1024*1024) // 4MB + +std::bitset<(MEMORY_CODEAREA_ADDR + MEMORY_CODEAREA_SIZE) / PPC_REC_ALLOC_BLOCK_SIZE> ppcRecompiler_reservedBlockMask; + +void PPCRecompiler_reserveLookupTableBlock(uint32 offset) +{ + uint32 blockIndex = offset / PPC_REC_ALLOC_BLOCK_SIZE; + offset = blockIndex * PPC_REC_ALLOC_BLOCK_SIZE; + + if (ppcRecompiler_reservedBlockMask[blockIndex]) + return; + ppcRecompiler_reservedBlockMask[blockIndex] = true; + + void* p1 = MemMapper::AllocateMemory(&(ppcRecompilerInstanceData->ppcRecompilerFuncTable[offset/4]), (PPC_REC_ALLOC_BLOCK_SIZE/4)*sizeof(void*), MemMapper::PAGE_PERMISSION::P_RW, true); + void* p3 = MemMapper::AllocateMemory(&(ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[offset/4]), (PPC_REC_ALLOC_BLOCK_SIZE/4)*sizeof(void*), MemMapper::PAGE_PERMISSION::P_RW, true); + if( !p1 || !p3 ) + { + forceLog_printf("Failed to allocate memory for recompiler (0x%08x)", offset); + cemu_assert(false); + return; + } + for(uint32 i=0; i<PPC_REC_ALLOC_BLOCK_SIZE/4; i++) + { + ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[offset/4+i] = PPCRecompiler_leaveRecompilerCode_unvisited; + } +} + +void PPCRecompiler_allocateRange(uint32 startAddress, uint32 size) +{ + if (ppcRecompilerInstanceData == nullptr) + return; + uint32 endAddress = (startAddress + size + PPC_REC_ALLOC_BLOCK_SIZE - 1) & ~(PPC_REC_ALLOC_BLOCK_SIZE-1); + startAddress = (startAddress) & ~(PPC_REC_ALLOC_BLOCK_SIZE-1); + startAddress = std::min(startAddress, (uint32)MEMORY_CODEAREA_ADDR + MEMORY_CODEAREA_SIZE); + endAddress = std::min(endAddress, (uint32)MEMORY_CODEAREA_ADDR + MEMORY_CODEAREA_SIZE); + for (uint32 i = startAddress; i < endAddress; i += PPC_REC_ALLOC_BLOCK_SIZE) + { + PPCRecompiler_reserveLookupTableBlock(i); + } +} + +struct ppcRecompilerFuncRange_t +{ + MPTR ppcStart; + uint32 ppcSize; + void* x86Start; + size_t x86Size; +}; + +DLLEXPORT bool PPCRecompiler_findFuncRanges(uint32 addr, ppcRecompilerFuncRange_t* rangesOut, size_t* countInOut) +{ + PPCRecompilerState.recompilerSpinlock.acquire(); + size_t countIn = *countInOut; + size_t countOut = 0; + + rangeStore_ppcRanges.findRanges(addr, addr + 4, [rangesOut, countIn, &countOut](uint32 start, uint32 end, PPCRecFunction_t* func) + { + if (countOut < countIn) + { + rangesOut[countOut].ppcStart = start; + rangesOut[countOut].ppcSize = (end-start); + rangesOut[countOut].x86Start = func->x86Code; + rangesOut[countOut].x86Size = func->x86Size; + } + countOut++; + } + ); + PPCRecompilerState.recompilerSpinlock.release(); + *countInOut = countOut; + if (countOut > countIn) + return false; + return true; +} + +DLLEXPORT uintptr_t* PPCRecompiler_getJumpTableBase() +{ + if (ppcRecompilerInstanceData == nullptr) + return nullptr; + return (uintptr_t*)ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable; +} + +void PPCRecompiler_invalidateTableRange(uint32 offset, uint32 size) +{ + if (ppcRecompilerInstanceData == nullptr) + return; + for (uint32 i = 0; i < size / 4; i++) + { + ppcRecompilerInstanceData->ppcRecompilerFuncTable[offset / 4 + i] = nullptr; + ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[offset / 4 + i] = PPCRecompiler_leaveRecompilerCode_unvisited; + } +} + +void PPCRecompiler_deleteFunction(PPCRecFunction_t* func) +{ + // assumes PPCRecompilerState.recompilerSpinlock is already held + cemu_assert_debug(PPCRecompilerState.recompilerSpinlock.isHolding()); + for (auto& r : func->list_ranges) + { + PPCRecompiler_invalidateTableRange(r.ppcAddress, r.ppcSize); + if(r.storedRange) + rangeStore_ppcRanges.deleteRange(r.storedRange); + r.storedRange = nullptr; + } + // todo - free x86 code +} + +DLLEXPORT void PPCRecompiler_invalidateRange(uint32 startAddr, uint32 endAddr) +{ + if (ppcRecompilerEnabled == false) + return; + if (startAddr >= PPC_REC_CODE_AREA_SIZE) + return; + cemu_assert_debug(endAddr >= startAddr); + + PPCRecompilerState.recompilerSpinlock.acquire(); + + uint32 rStart; + uint32 rEnd; + PPCRecFunction_t* rFunc; + + // mark range as unvisited + for (uint64 currentAddr = (uint64)startAddr&~3; currentAddr < (uint64)(endAddr&~3); currentAddr += 4) + ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[currentAddr / 4] = PPCRecompiler_leaveRecompilerCode_unvisited; + + // add entry to invalidation queue + PPCRecompilerState.invalidationRanges.emplace_back(startAddr, endAddr-startAddr); + + + while (rangeStore_ppcRanges.findFirstRange(startAddr, endAddr, rStart, rEnd, rFunc) ) + { + PPCRecompiler_deleteFunction(rFunc); + } + + PPCRecompilerState.recompilerSpinlock.release(); +} + +void PPCRecompiler_init() +{ + if (ActiveSettings::GetCPUMode() == CPUMode::SinglecoreInterpreter) + { + ppcRecompilerEnabled = false; + return; + } + if (LaunchSettings::ForceInterpreter()) + { + cemuLog_log(LogType::Force, "Recompiler disabled. Command line --force-interpreter was passed"); + return; + } + if (ppcRecompilerInstanceData) + { + MemMapper::FreeReservation(ppcRecompilerInstanceData, sizeof(PPCRecompilerInstanceData_t)); + ppcRecompilerInstanceData = nullptr; + } + debug_printf("Allocating %dMB for recompiler instance data...\n", (sint32)(sizeof(PPCRecompilerInstanceData_t) / 1024 / 1024)); + ppcRecompilerInstanceData = (PPCRecompilerInstanceData_t*)MemMapper::ReserveMemory(nullptr, sizeof(PPCRecompilerInstanceData_t), MemMapper::PAGE_PERMISSION::P_RW); + MemMapper::AllocateMemory(&(ppcRecompilerInstanceData->_x64XMM_xorNegateMaskBottom), sizeof(PPCRecompilerInstanceData_t) - offsetof(PPCRecompilerInstanceData_t, _x64XMM_xorNegateMaskBottom), MemMapper::PAGE_PERMISSION::P_RW, true); + PPCRecompilerX64Gen_generateRecompilerInterfaceFunctions(); + + uint32 codeRegionEnd = RPLLoader_GetMaxCodeOffset(); + codeRegionEnd = (codeRegionEnd + PPC_REC_ALLOC_BLOCK_SIZE - 1) & ~(PPC_REC_ALLOC_BLOCK_SIZE - 1); + + uint32 codeRegionSize = codeRegionEnd - PPC_REC_CODE_AREA_START; + forceLogDebug_printf("Allocating recompiler tables for range 0x%08x-0x%08x", PPC_REC_CODE_AREA_START, codeRegionEnd); + + for (uint32 i = 0; i < codeRegionSize; i += PPC_REC_ALLOC_BLOCK_SIZE) + { + PPCRecompiler_reserveLookupTableBlock(i); + } + + // init x64 recompiler instance data + ppcRecompilerInstanceData->_x64XMM_xorNegateMaskBottom[0] = 1ULL << 63ULL; + ppcRecompilerInstanceData->_x64XMM_xorNegateMaskBottom[1] = 0ULL; + ppcRecompilerInstanceData->_x64XMM_xorNegateMaskPair[0] = 1ULL << 63ULL; + ppcRecompilerInstanceData->_x64XMM_xorNegateMaskPair[1] = 1ULL << 63ULL; + ppcRecompilerInstanceData->_x64XMM_xorNOTMask[0] = 0xFFFFFFFFFFFFFFFFULL; + ppcRecompilerInstanceData->_x64XMM_xorNOTMask[1] = 0xFFFFFFFFFFFFFFFFULL; + ppcRecompilerInstanceData->_x64XMM_andAbsMaskBottom[0] = ~(1ULL << 63ULL); + ppcRecompilerInstanceData->_x64XMM_andAbsMaskBottom[1] = ~0ULL; + ppcRecompilerInstanceData->_x64XMM_andAbsMaskPair[0] = ~(1ULL << 63ULL); + ppcRecompilerInstanceData->_x64XMM_andAbsMaskPair[1] = ~(1ULL << 63ULL); + ppcRecompilerInstanceData->_x64XMM_andFloatAbsMaskBottom[0] = ~(1 << 31); + ppcRecompilerInstanceData->_x64XMM_andFloatAbsMaskBottom[1] = 0xFFFFFFFF; + ppcRecompilerInstanceData->_x64XMM_andFloatAbsMaskBottom[2] = 0xFFFFFFFF; + ppcRecompilerInstanceData->_x64XMM_andFloatAbsMaskBottom[3] = 0xFFFFFFFF; + ppcRecompilerInstanceData->_x64XMM_singleWordMask[0] = 0xFFFFFFFFULL; + ppcRecompilerInstanceData->_x64XMM_singleWordMask[1] = 0ULL; + ppcRecompilerInstanceData->_x64XMM_constDouble1_1[0] = 1.0; + ppcRecompilerInstanceData->_x64XMM_constDouble1_1[1] = 1.0; + ppcRecompilerInstanceData->_x64XMM_constDouble0_0[0] = 0.0; + ppcRecompilerInstanceData->_x64XMM_constDouble0_0[1] = 0.0; + ppcRecompilerInstanceData->_x64XMM_constFloat0_0[0] = 0.0f; + ppcRecompilerInstanceData->_x64XMM_constFloat0_0[1] = 0.0f; + ppcRecompilerInstanceData->_x64XMM_constFloat1_1[0] = 1.0f; + ppcRecompilerInstanceData->_x64XMM_constFloat1_1[1] = 1.0f; + *(uint32*)&ppcRecompilerInstanceData->_x64XMM_constFloatMin[0] = 0x00800000; + *(uint32*)&ppcRecompilerInstanceData->_x64XMM_constFloatMin[1] = 0x00800000; + ppcRecompilerInstanceData->_x64XMM_flushDenormalMask1[0] = 0x7F800000; + ppcRecompilerInstanceData->_x64XMM_flushDenormalMask1[1] = 0x7F800000; + ppcRecompilerInstanceData->_x64XMM_flushDenormalMask1[2] = 0x7F800000; + ppcRecompilerInstanceData->_x64XMM_flushDenormalMask1[3] = 0x7F800000; + ppcRecompilerInstanceData->_x64XMM_flushDenormalMaskResetSignBits[0] = ~0x80000000; + ppcRecompilerInstanceData->_x64XMM_flushDenormalMaskResetSignBits[1] = ~0x80000000; + ppcRecompilerInstanceData->_x64XMM_flushDenormalMaskResetSignBits[2] = ~0x80000000; + ppcRecompilerInstanceData->_x64XMM_flushDenormalMaskResetSignBits[3] = ~0x80000000; + + // setup GQR scale tables + + for (uint32 i = 0; i < 32; i++) + { + float a = 1.0f / (float)(1u << i); + float b = 0; + if (i == 0) + b = 4294967296.0f; + else + b = (float)(1u << (32u - i)); + + float ar = (float)(1u << i); + float br = 0; + if (i == 0) + br = 1.0f / 4294967296.0f; + else + br = 1.0f / (float)(1u << (32u - i)); + + ppcRecompilerInstanceData->_psq_ld_scale_ps0_1[i * 2 + 0] = a; + ppcRecompilerInstanceData->_psq_ld_scale_ps0_1[i * 2 + 1] = 1.0f; + ppcRecompilerInstanceData->_psq_ld_scale_ps0_1[(i + 32) * 2 + 0] = b; + ppcRecompilerInstanceData->_psq_ld_scale_ps0_1[(i + 32) * 2 + 1] = 1.0f; + + ppcRecompilerInstanceData->_psq_ld_scale_ps0_ps1[i * 2 + 0] = a; + ppcRecompilerInstanceData->_psq_ld_scale_ps0_ps1[i * 2 + 1] = a; + ppcRecompilerInstanceData->_psq_ld_scale_ps0_ps1[(i + 32) * 2 + 0] = b; + ppcRecompilerInstanceData->_psq_ld_scale_ps0_ps1[(i + 32) * 2 + 1] = b; + + ppcRecompilerInstanceData->_psq_st_scale_ps0_1[i * 2 + 0] = ar; + ppcRecompilerInstanceData->_psq_st_scale_ps0_1[i * 2 + 1] = 1.0f; + ppcRecompilerInstanceData->_psq_st_scale_ps0_1[(i + 32) * 2 + 0] = br; + ppcRecompilerInstanceData->_psq_st_scale_ps0_1[(i + 32) * 2 + 1] = 1.0f; + + ppcRecompilerInstanceData->_psq_st_scale_ps0_ps1[i * 2 + 0] = ar; + ppcRecompilerInstanceData->_psq_st_scale_ps0_ps1[i * 2 + 1] = ar; + ppcRecompilerInstanceData->_psq_st_scale_ps0_ps1[(i + 32) * 2 + 0] = br; + ppcRecompilerInstanceData->_psq_st_scale_ps0_ps1[(i + 32) * 2 + 1] = br; + } + + // mxcsr + ppcRecompilerInstanceData->_x64XMM_mxCsr_ftzOn = 0x1F80 | 0x8000; + ppcRecompilerInstanceData->_x64XMM_mxCsr_ftzOff = 0x1F80; + + // query processor extensions + int cpuInfo[4]; + __cpuid(cpuInfo, 0x80000001); + hasLZCNTSupport = ((cpuInfo[2] >> 5) & 1) != 0; + __cpuid(cpuInfo, 0x1); + hasMOVBESupport = ((cpuInfo[2] >> 22) & 1) != 0; + hasAVXSupport = ((cpuInfo[2] >> 28) & 1) != 0; + __cpuidex(cpuInfo, 0x7, 0); + hasBMI2Support = ((cpuInfo[1] >> 8) & 1) != 0; + + forceLog_printf("Recompiler initialized. CPU extensions: %s%s%s", hasLZCNTSupport ? "LZCNT " : "", hasMOVBESupport ? "MOVBE " : "", hasAVXSupport ? "AVX " : ""); + + ppcRecompilerEnabled = true; + + // launch recompilation thread + std::thread t_recompiler(PPCRecompiler_thread); + t_recompiler.detach(); +} diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.h b/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.h new file mode 100644 index 00000000..f87778de --- /dev/null +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.h @@ -0,0 +1,399 @@ +#include <vector> + +#define PPC_REC_CODE_AREA_START (0x00000000) // lower bound of executable memory area. Recompiler expects this address to be 0 +#define PPC_REC_CODE_AREA_END (0x10000000) // upper bound of executable memory area +#define PPC_REC_CODE_AREA_SIZE (PPC_REC_CODE_AREA_END - PPC_REC_CODE_AREA_START) + +#define PPC_REC_ALIGN_TO_4MB(__v) (((__v)+4*1024*1024-1)&~(4*1024*1024-1)) + +#define PPC_REC_MAX_VIRTUAL_GPR (40) // enough to store 32 GPRs + a few SPRs + temp registers (usually only 1-2) + +typedef struct +{ + uint32 ppcAddress; + uint32 ppcSize; + //void* x86Start; + //size_t x86Size; + void* storedRange; +}ppcRecRange_t; + +typedef struct +{ + uint32 ppcAddress; + uint32 ppcSize; // ppc code size of function + void* x86Code; // pointer to x86 code + size_t x86Size; + std::vector<ppcRecRange_t> list_ranges; +}PPCRecFunction_t; + +#define PPCREC_IML_OP_FLAG_SIGNEXTEND (1<<0) +#define PPCREC_IML_OP_FLAG_SWITCHENDIAN (1<<1) +#define PPCREC_IML_OP_FLAG_NOT_EXPANDED (1<<2) // set single-precision load instructions to indicate that the value should not be rounded to double-precision +#define PPCREC_IML_OP_FLAG_UNUSED (1<<7) // used to mark instructions that are not used + +typedef struct +{ + uint8 type; + uint8 operation; + uint8 crRegister; // set to 0xFF if not set, not all IML instruction types support cr. + uint8 crMode; // only used when crRegister is valid, used to differentiate between various forms of condition flag set/clear behavior + uint32 crIgnoreMask; // bit set for every respective CR bit that doesn't need to be updated + uint32 associatedPPCAddress; // ppc address that is associated with this instruction + union + { + struct + { + uint8 _padding[7]; + }padding; + struct + { + // R (op) A [update cr* in mode *] + uint8 registerResult; + uint8 registerA; + }op_r_r; + struct + { + // R = A (op) B [update cr* in mode *] + uint8 registerResult; + uint8 registerA; + uint8 registerB; + }op_r_r_r; + struct + { + // R = A (op) immS32 [update cr* in mode *] + uint8 registerResult; + uint8 registerA; + sint32 immS32; + }op_r_r_s32; + struct + { + // R/F = NAME or NAME = R/F + uint8 registerIndex; + uint8 copyWidth; + uint32 name; + uint8 flags; + }op_r_name; + struct + { + // R (op) s32 [update cr* in mode *] + uint8 registerIndex; + sint32 immS32; + }op_r_immS32; + struct + { + uint32 address; + uint8 flags; + }op_jumpmark; + struct + { + uint32 param; + uint32 param2; + uint16 paramU16; + }op_macro; + struct + { + uint32 jumpmarkAddress; + bool jumpAccordingToSegment; //PPCRecImlSegment_t* destinationSegment; // if set, this replaces jumpmarkAddress + uint8 condition; // only used when crRegisterIndex is 8 or above (update: Apparently only used to mark jumps without a condition? -> Cleanup) + uint8 crRegisterIndex; + uint8 crBitIndex; + bool bitMustBeSet; + }op_conditionalJump; + struct + { + uint8 registerData; + uint8 registerMem; + uint8 registerMem2; + uint8 registerGQR; + uint8 copyWidth; + //uint8 flags; + struct + { + bool swapEndian : 1; + bool signExtend : 1; + bool notExpanded : 1; // for floats + }flags2; + uint8 mode; // transfer mode (copy width, ps0/ps1 behavior) + sint32 immS32; + }op_storeLoad; + struct + { + struct + { + uint8 registerMem; + sint32 immS32; + }src; + struct + { + uint8 registerMem; + sint32 immS32; + }dst; + uint8 copyWidth; + }op_mem2mem; + struct + { + uint8 registerResult; + uint8 registerOperand; + uint8 flags; + }op_fpr_r_r; + struct + { + uint8 registerResult; + uint8 registerOperandA; + uint8 registerOperandB; + uint8 flags; + }op_fpr_r_r_r; + struct + { + uint8 registerResult; + uint8 registerOperandA; + uint8 registerOperandB; + uint8 registerOperandC; + uint8 flags; + }op_fpr_r_r_r_r; + struct + { + uint8 registerResult; + //uint8 flags; + }op_fpr_r; + struct + { + uint32 ppcAddress; + uint32 x64Offset; + }op_ppcEnter; + struct + { + uint8 crD; // crBitIndex (result) + uint8 crA; // crBitIndex + uint8 crB; // crBitIndex + }op_cr; + // conditional operations (emitted if supported by target platform) + struct + { + // r_s32 + uint8 registerIndex; + sint32 immS32; + // condition + uint8 crRegisterIndex; + uint8 crBitIndex; + bool bitMustBeSet; + }op_conditional_r_s32; + }; +}PPCRecImlInstruction_t; + +typedef struct _PPCRecImlSegment_t PPCRecImlSegment_t; + +typedef struct _ppcRecompilerSegmentPoint_t +{ + sint32 index; + PPCRecImlSegment_t* imlSegment; + _ppcRecompilerSegmentPoint_t* next; + _ppcRecompilerSegmentPoint_t* prev; +}ppcRecompilerSegmentPoint_t; + +struct raLivenessLocation_t +{ + sint32 index; + bool isRead; + bool isWrite; + + raLivenessLocation_t() {}; + + raLivenessLocation_t(sint32 index, bool isRead, bool isWrite) + : index(index), isRead(isRead), isWrite(isWrite) {}; +}; + +struct raLivenessSubrangeLink_t +{ + struct raLivenessSubrange_t* prev; + struct raLivenessSubrange_t* next; +}; + +struct raLivenessSubrange_t +{ + struct raLivenessRange_t* range; + PPCRecImlSegment_t* imlSegment; + ppcRecompilerSegmentPoint_t start; + ppcRecompilerSegmentPoint_t end; + // dirty state tracking + bool _noLoad; + bool hasStore; + bool hasStoreDelayed; + // next + raLivenessSubrange_t* subrangeBranchTaken; + raLivenessSubrange_t* subrangeBranchNotTaken; + // processing + uint32 lastIterationIndex; + // instruction locations + std::vector<raLivenessLocation_t> list_locations; + // linked list (subranges with same GPR virtual register) + raLivenessSubrangeLink_t link_sameVirtualRegisterGPR; + // linked list (all subranges for this segment) + raLivenessSubrangeLink_t link_segmentSubrangesGPR; +}; + +struct raLivenessRange_t +{ + sint32 virtualRegister; + sint32 physicalRegister; + sint32 name; + std::vector<raLivenessSubrange_t*> list_subranges; +}; + +struct PPCSegmentRegisterAllocatorInfo_t +{ + // analyzer stage + bool isPartOfProcessedLoop{}; // used during loop detection + sint32 lastIterationIndex{}; + // linked lists + raLivenessSubrange_t* linkedList_allSubranges{}; + raLivenessSubrange_t* linkedList_perVirtualGPR[PPC_REC_MAX_VIRTUAL_GPR]{}; +}; + +struct PPCRecVGPRDistances_t +{ + struct _RegArrayEntry + { + sint32 usageStart{}; + sint32 usageEnd{}; + }reg[PPC_REC_MAX_VIRTUAL_GPR]; + bool isProcessed[PPC_REC_MAX_VIRTUAL_GPR]{}; +}; + +typedef struct _PPCRecImlSegment_t +{ + sint32 momentaryIndex{}; // index in segment list, generally not kept up to date except if needed (necessary for loop detection) + sint32 startOffset{}; // offset to first instruction in iml instruction list + sint32 count{}; // number of instructions in segment + uint32 ppcAddress{}; // ppc address (0xFFFFFFFF if not associated with an address) + uint32 x64Offset{}; // x64 code offset of segment start + uint32 cycleCount{}; // number of PPC cycles required to execute this segment (roughly) + // list of intermediate instructions in this segment + PPCRecImlInstruction_t* imlList{}; + sint32 imlListSize{}; + sint32 imlListCount{}; + // segment link + _PPCRecImlSegment_t* nextSegmentBranchNotTaken{}; // this is also the default for segments where there is no branch + _PPCRecImlSegment_t* nextSegmentBranchTaken{}; + bool nextSegmentIsUncertain{}; + sint32 loopDepth{}; + //sList_t* list_prevSegments; + std::vector<_PPCRecImlSegment_t*> list_prevSegments{}; + // PPC range of segment + uint32 ppcAddrMin{}; + uint32 ppcAddrMax{}; + // enterable segments + bool isEnterable{}; // this segment can be entered from outside the recompiler (no preloaded registers necessary) + uint32 enterPPCAddress{}; // used if isEnterable is true + // jump destination segments + bool isJumpDestination{}; // segment is a destination for one or more (conditional) jumps + uint32 jumpDestinationPPCAddress{}; + // PPC FPR use mask + bool ppcFPRUsed[32]{}; // same as ppcGPRUsed, but for FPR + // CR use mask + uint32 crBitsInput{}; // bits that are expected to be set from the previous segment (read in this segment but not overwritten) + uint32 crBitsRead{}; // all bits that are read in this segment + uint32 crBitsWritten{}; // bits that are written in this segment + // register allocator info + PPCSegmentRegisterAllocatorInfo_t raInfo{}; + PPCRecVGPRDistances_t raDistances{}; + bool raRangeExtendProcessed{}; + // segment points + ppcRecompilerSegmentPoint_t* segmentPointList{}; +}PPCRecImlSegment_t; + +struct ppcImlGenContext_t +{ + PPCRecFunction_t* functionRef; + uint32* currentInstruction; + uint32 ppcAddressOfCurrentInstruction; + // fpr mode + bool LSQE{ true }; + bool PSE{ true }; + // cycle counter + uint32 cyclesSinceLastBranch; // used to track ppc cycles + // temporary general purpose registers + uint32 mappedRegister[PPC_REC_MAX_VIRTUAL_GPR]; + // temporary floating point registers (single and double precision) + uint32 mappedFPRRegister[256]; + // list of intermediate instructions + PPCRecImlInstruction_t* imlList; + sint32 imlListSize; + sint32 imlListCount; + // list of segments + PPCRecImlSegment_t** segmentList; + sint32 segmentListSize; + sint32 segmentListCount; + // code generation control + bool hasFPUInstruction; // if true, PPCEnter macro will create FP_UNAVAIL checks -> Not needed in user mode + // register allocator info + struct + { + std::vector<raLivenessRange_t*> list_ranges; + }raInfo; + // analysis info + struct + { + bool modifiesGQR[8]; + }tracking; +}; + +typedef void ATTR_MS_ABI (*PPCREC_JUMP_ENTRY)(); + +typedef struct +{ + PPCRecFunction_t* ppcRecompilerFuncTable[PPC_REC_ALIGN_TO_4MB(PPC_REC_CODE_AREA_SIZE/4)]; // one virtual-function pointer for each potential ppc instruction + PPCREC_JUMP_ENTRY ppcRecompilerDirectJumpTable[PPC_REC_ALIGN_TO_4MB(PPC_REC_CODE_AREA_SIZE/4)]; // lookup table for ppc offset to native code function + // x64 data + uint64 __declspec(align(16)) _x64XMM_xorNegateMaskBottom[2]; + uint64 __declspec(align(16)) _x64XMM_xorNegateMaskPair[2]; + uint64 __declspec(align(16)) _x64XMM_xorNOTMask[2]; + uint64 __declspec(align(16)) _x64XMM_andAbsMaskBottom[2]; + uint64 __declspec(align(16)) _x64XMM_andAbsMaskPair[2]; + uint32 __declspec(align(16)) _x64XMM_andFloatAbsMaskBottom[4]; + uint64 __declspec(align(16)) _x64XMM_singleWordMask[2]; + double __declspec(align(16)) _x64XMM_constDouble1_1[2]; + double __declspec(align(16)) _x64XMM_constDouble0_0[2]; + float __declspec(align(16)) _x64XMM_constFloat0_0[2]; + float __declspec(align(16)) _x64XMM_constFloat1_1[2]; + float __declspec(align(16)) _x64XMM_constFloatMin[2]; + uint32 __declspec(align(16)) _x64XMM_flushDenormalMask1[4]; + uint32 __declspec(align(16)) _x64XMM_flushDenormalMaskResetSignBits[4]; + // PSQ load/store scale tables + double _psq_ld_scale_ps0_ps1[64 * 2]; + double _psq_ld_scale_ps0_1[64 * 2]; + double _psq_st_scale_ps0_ps1[64 * 2]; + double _psq_st_scale_ps0_1[64 * 2]; + // MXCSR + uint32 _x64XMM_mxCsr_ftzOn; + uint32 _x64XMM_mxCsr_ftzOff; +}PPCRecompilerInstanceData_t; + +extern __declspec(dllexport) PPCRecompilerInstanceData_t* ppcRecompilerInstanceData; +extern bool ppcRecompilerEnabled; + +__declspec(dllexport) void PPCRecompiler_init(); + +void PPCRecompiler_allocateRange(uint32 startAddress, uint32 size); + +DLLEXPORT void PPCRecompiler_invalidateRange(uint32 startAddr, uint32 endAddr); + +extern void ATTR_MS_ABI (*PPCRecompiler_enterRecompilerCode)(uint64 codeMem, uint64 ppcInterpreterInstance); +extern void ATTR_MS_ABI (*PPCRecompiler_leaveRecompilerCode_visited)(); +extern void ATTR_MS_ABI (*PPCRecompiler_leaveRecompilerCode_unvisited)(); + +#define PPC_REC_INVALID_FUNCTION ((PPCRecFunction_t*)-1) + +// CPUID +extern __declspec(dllexport) bool hasLZCNTSupport; +extern __declspec(dllexport) bool hasMOVBESupport; +extern __declspec(dllexport) bool hasBMI2Support; +extern __declspec(dllexport) bool hasAVXSupport; + +// todo - move some of the stuff above into PPCRecompilerInternal.h + +// recompiler interface + +void PPCRecompiler_recompileIfUnvisited(uint32 enterAddress); +void PPCRecompiler_attemptEnter(struct PPCInterpreter_t* hCPU, uint32 enterAddress); +void PPCRecompiler_attemptEnterWithoutRecompile(struct PPCInterpreter_t* hCPU, uint32 enterAddress); \ No newline at end of file diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerIml.h b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerIml.h new file mode 100644 index 00000000..86af33b2 --- /dev/null +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerIml.h @@ -0,0 +1,422 @@ + +#define PPCREC_CR_REG_TEMP 8 // there are only 8 cr registers (0-7) we use the 8th as temporary cr register that is never stored (BDNZ instruction for example) + +enum +{ + PPCREC_IML_OP_ASSIGN, // '=' operator + PPCREC_IML_OP_ENDIAN_SWAP, // '=' operator with 32bit endian swap + PPCREC_IML_OP_ADD, // '+' operator + PPCREC_IML_OP_SUB, // '-' operator + PPCREC_IML_OP_SUB_CARRY_UPDATE_CARRY, // complex operation, result = operand + ~operand2 + carry bit, updates carry bit + PPCREC_IML_OP_COMPARE_SIGNED, // arithmetic/signed comparison operator (updates cr) + PPCREC_IML_OP_COMPARE_UNSIGNED, // logical/unsigned comparison operator (updates cr) + PPCREC_IML_OP_MULTIPLY_SIGNED, // '*' operator (signed multiply) + PPCREC_IML_OP_MULTIPLY_HIGH_UNSIGNED, // unsigned 64bit multiply, store only high 32bit-word of result + PPCREC_IML_OP_MULTIPLY_HIGH_SIGNED, // signed 64bit multiply, store only high 32bit-word of result + PPCREC_IML_OP_DIVIDE_SIGNED, // '/' operator (signed divide) + PPCREC_IML_OP_DIVIDE_UNSIGNED, // '/' operator (unsigned divide) + PPCREC_IML_OP_ADD_CARRY, // complex operation, result = operand + carry bit, updates carry bit + PPCREC_IML_OP_ADD_CARRY_ME, // complex operation, result = operand + carry bit + (-1), updates carry bit + PPCREC_IML_OP_ADD_UPDATE_CARRY, // '+' operator but also updates carry flag + PPCREC_IML_OP_ADD_CARRY_UPDATE_CARRY, // '+' operator and also adds carry, updates carry flag + // assign operators with cast + PPCREC_IML_OP_ASSIGN_S16_TO_S32, // copy 16bit and sign extend + PPCREC_IML_OP_ASSIGN_S8_TO_S32, // copy 8bit and sign extend + // binary operation + PPCREC_IML_OP_OR, // '|' operator + PPCREC_IML_OP_ORC, // '|' operator, second operand is complemented first + PPCREC_IML_OP_AND, // '&' operator + PPCREC_IML_OP_XOR, // '^' operator + PPCREC_IML_OP_LEFT_ROTATE, // left rotate operator + PPCREC_IML_OP_LEFT_SHIFT, // shift left operator + PPCREC_IML_OP_RIGHT_SHIFT, // right shift operator (unsigned) + PPCREC_IML_OP_NOT, // complement each bit + PPCREC_IML_OP_NEG, // negate + // ppc + PPCREC_IML_OP_RLWIMI, // RLWIMI instruction (rotate, merge based on mask) + PPCREC_IML_OP_SRAW, // SRAWI/SRAW instruction (algebraic shift right, sets ca flag) + PPCREC_IML_OP_SLW, // SLW (shift based on register by up to 63 bits) + PPCREC_IML_OP_SRW, // SRW (shift based on register by up to 63 bits) + PPCREC_IML_OP_CNTLZW, + PPCREC_IML_OP_SUBFC, // SUBFC and SUBFIC (subtract from and set carry) + PPCREC_IML_OP_DCBZ, // clear 32 bytes aligned to 0x20 + PPCREC_IML_OP_MFCR, // copy cr to gpr + PPCREC_IML_OP_MTCRF, // copy gpr to cr (with mask) + // condition register + PPCREC_IML_OP_CR_CLEAR, // clear cr bit + PPCREC_IML_OP_CR_SET, // set cr bit + PPCREC_IML_OP_CR_OR, // OR cr bits + PPCREC_IML_OP_CR_ORC, // OR cr bits, complement second input operand bit first + PPCREC_IML_OP_CR_AND, // AND cr bits + PPCREC_IML_OP_CR_ANDC, // AND cr bits, complement second input operand bit first + // FPU + PPCREC_IML_OP_FPR_ADD_BOTTOM, + PPCREC_IML_OP_FPR_ADD_PAIR, + PPCREC_IML_OP_FPR_SUB_PAIR, + PPCREC_IML_OP_FPR_SUB_BOTTOM, + PPCREC_IML_OP_FPR_MULTIPLY_BOTTOM, + PPCREC_IML_OP_FPR_MULTIPLY_PAIR, + PPCREC_IML_OP_FPR_DIVIDE_BOTTOM, + PPCREC_IML_OP_FPR_DIVIDE_PAIR, + PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM_AND_TOP, + PPCREC_IML_OP_FPR_COPY_TOP_TO_BOTTOM_AND_TOP, + PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, + PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_TOP, // leave bottom of destination untouched + PPCREC_IML_OP_FPR_COPY_TOP_TO_TOP, // leave bottom of destination untouched + PPCREC_IML_OP_FPR_COPY_TOP_TO_BOTTOM, // leave top of destination untouched + PPCREC_IML_OP_FPR_COPY_BOTTOM_AND_TOP_SWAPPED, + PPCREC_IML_OP_FPR_EXPAND_BOTTOM32_TO_BOTTOM64_AND_TOP64, // expand bottom f32 to f64 in bottom and top half + PPCREC_IML_OP_FPR_BOTTOM_FRES_TO_BOTTOM_AND_TOP, // calculate reciprocal with Espresso accuracy of source bottom half and write result to destination bottom and top half + PPCREC_IML_OP_FPR_FCMPO_BOTTOM, + PPCREC_IML_OP_FPR_FCMPU_BOTTOM, + PPCREC_IML_OP_FPR_FCMPU_TOP, + PPCREC_IML_OP_FPR_NEGATE_BOTTOM, + PPCREC_IML_OP_FPR_NEGATE_PAIR, + PPCREC_IML_OP_FPR_ABS_BOTTOM, // abs(fp0) + PPCREC_IML_OP_FPR_ABS_PAIR, + PPCREC_IML_OP_FPR_FRES_PAIR, // 1.0/fp approx (Espresso accuracy) + PPCREC_IML_OP_FPR_FRSQRTE_PAIR, // 1.0/sqrt(fp) approx (Espresso accuracy) + PPCREC_IML_OP_FPR_NEGATIVE_ABS_BOTTOM, // -abs(fp0) + PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_BOTTOM, // round 64bit double to 64bit double with 32bit float precision (in bottom half of xmm register) + PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_PAIR, // round two 64bit doubles to 64bit double with 32bit float precision + PPCREC_IML_OP_FPR_BOTTOM_RECIPROCAL_SQRT, + PPCREC_IML_OP_FPR_BOTTOM_FCTIWZ, + PPCREC_IML_OP_FPR_SELECT_BOTTOM, // selectively copy bottom value from operand B or C based on value in operand A + PPCREC_IML_OP_FPR_SELECT_PAIR, // selectively copy top/bottom from operand B or C based on value in top/bottom of operand A + // PS + PPCREC_IML_OP_FPR_SUM0, + PPCREC_IML_OP_FPR_SUM1, +}; + +#define PPCREC_IML_OP_FPR_COPY_PAIR (PPCREC_IML_OP_ASSIGN) + +enum +{ + PPCREC_IML_MACRO_BLR, // macro for BLR instruction code + PPCREC_IML_MACRO_BLRL, // macro for BLRL instruction code + PPCREC_IML_MACRO_BCTR, // macro for BCTR instruction code + PPCREC_IML_MACRO_BCTRL, // macro for BCTRL instruction code + PPCREC_IML_MACRO_BL, // call to different function (can be within same function) + PPCREC_IML_MACRO_B_FAR, // branch to different function + PPCREC_IML_MACRO_COUNT_CYCLES, // decrease current remaining thread cycles by a certain amount + PPCREC_IML_MACRO_HLE, // HLE function call + PPCREC_IML_MACRO_MFTB, // get TB register value (low or high) + PPCREC_IML_MACRO_LEAVE, // leaves recompiler and switches to interpeter + // debugging + PPCREC_IML_MACRO_DEBUGBREAK, // throws a debugbreak +}; + +enum +{ + PPCREC_JUMP_CONDITION_NONE, + PPCREC_JUMP_CONDITION_E, // equal / zero + PPCREC_JUMP_CONDITION_NE, // not equal / not zero + PPCREC_JUMP_CONDITION_LE, // less or equal + PPCREC_JUMP_CONDITION_L, // less + PPCREC_JUMP_CONDITION_GE, // greater or equal + PPCREC_JUMP_CONDITION_G, // greater + // special case: + PPCREC_JUMP_CONDITION_SUMMARYOVERFLOW, // needs special handling + PPCREC_JUMP_CONDITION_NSUMMARYOVERFLOW, // not summaryoverflow + +}; + +enum +{ + PPCREC_CR_MODE_COMPARE_SIGNED, + PPCREC_CR_MODE_COMPARE_UNSIGNED, // alias logic compare + // others: PPCREC_CR_MODE_ARITHMETIC, + PPCREC_CR_MODE_ARITHMETIC, // arithmetic use (for use with add/sub instructions without generating extra code) + PPCREC_CR_MODE_LOGICAL, +}; + +enum +{ + PPCREC_IML_TYPE_NONE, + PPCREC_IML_TYPE_NO_OP, // no-op instruction + PPCREC_IML_TYPE_JUMPMARK, // possible jump destination (generated before each ppc instruction) + PPCREC_IML_TYPE_R_R, // r* (op) *r + PPCREC_IML_TYPE_R_R_R, // r* = r* (op) r* + PPCREC_IML_TYPE_R_R_S32, // r* = r* (op) s32* + PPCREC_IML_TYPE_LOAD, // r* = [r*+s32*] + PPCREC_IML_TYPE_LOAD_INDEXED, // r* = [r*+r*] + PPCREC_IML_TYPE_STORE, // [r*+s32*] = r* + PPCREC_IML_TYPE_STORE_INDEXED, // [r*+r*] = r* + PPCREC_IML_TYPE_R_NAME, // r* = name + PPCREC_IML_TYPE_NAME_R, // name* = r* + PPCREC_IML_TYPE_R_S32, // r* (op) imm + PPCREC_IML_TYPE_MACRO, + PPCREC_IML_TYPE_CJUMP, // conditional jump + PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK, // jumps only if remaining thread cycles >= 0 + PPCREC_IML_TYPE_PPC_ENTER, // used to mark locations that should be written to recompilerCallTable + PPCREC_IML_TYPE_CR, // condition register specific operations (one or more operands) + // conditional + PPCREC_IML_TYPE_CONDITIONAL_R_S32, + // FPR + PPCREC_IML_TYPE_FPR_R_NAME, // name = f* + PPCREC_IML_TYPE_FPR_NAME_R, // f* = name + PPCREC_IML_TYPE_FPR_LOAD, // r* = (bitdepth) [r*+s32*] (single or paired single mode) + PPCREC_IML_TYPE_FPR_LOAD_INDEXED, // r* = (bitdepth) [r*+r*] (single or paired single mode) + PPCREC_IML_TYPE_FPR_STORE, // (bitdepth) [r*+s32*] = r* (single or paired single mode) + PPCREC_IML_TYPE_FPR_STORE_INDEXED, // (bitdepth) [r*+r*] = r* (single or paired single mode) + PPCREC_IML_TYPE_FPR_R_R, + PPCREC_IML_TYPE_FPR_R_R_R, + PPCREC_IML_TYPE_FPR_R_R_R_R, + PPCREC_IML_TYPE_FPR_R, + // special + PPCREC_IML_TYPE_MEM2MEM, // memory to memory copy (deprecated) + +}; + +enum +{ + PPCREC_NAME_NONE, + PPCREC_NAME_TEMPORARY, + PPCREC_NAME_R0 = 1000, + PPCREC_NAME_SPR0 = 2000, + PPCREC_NAME_FPR0 = 3000, + PPCREC_NAME_TEMPORARY_FPR0 = 4000, // 0 to 7 + //PPCREC_NAME_CR0 = 3000, // value mapped condition register (usually it isn't needed and can be optimized away) +}; + +// special cases for LOAD/STORE +#define PPC_REC_LOAD_LWARX_MARKER (100) // lwarx instruction (similar to LWZX but sets reserved address/value) +#define PPC_REC_STORE_STWCX_MARKER (100) // stwcx instruction (similar to STWX but writes only if reservation from LWARX is valid) +#define PPC_REC_STORE_STSWI_1 (200) // stswi nb = 1 +#define PPC_REC_STORE_STSWI_2 (201) // stswi nb = 2 +#define PPC_REC_STORE_STSWI_3 (202) // stswi nb = 3 +#define PPC_REC_STORE_LSWI_1 (200) // lswi nb = 1 +#define PPC_REC_STORE_LSWI_2 (201) // lswi nb = 2 +#define PPC_REC_STORE_LSWI_3 (202) // lswi nb = 3 + +#define PPC_REC_INVALID_REGISTER 0xFF + +#define PPCREC_CR_BIT_LT 0 +#define PPCREC_CR_BIT_GT 1 +#define PPCREC_CR_BIT_EQ 2 +#define PPCREC_CR_BIT_SO 3 + +enum +{ + // fpr load + PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0, + PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0_PS1, + PPCREC_FPR_LD_MODE_DOUBLE_INTO_PS0, + PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0, + PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0_PS1, + PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0, + PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0_PS1, + PPCREC_FPR_LD_MODE_PSQ_S16_PS0, + PPCREC_FPR_LD_MODE_PSQ_S16_PS0_PS1, + PPCREC_FPR_LD_MODE_PSQ_U16_PS0, + PPCREC_FPR_LD_MODE_PSQ_U16_PS0_PS1, + PPCREC_FPR_LD_MODE_PSQ_S8_PS0, + PPCREC_FPR_LD_MODE_PSQ_S8_PS0_PS1, + PPCREC_FPR_LD_MODE_PSQ_U8_PS0, + PPCREC_FPR_LD_MODE_PSQ_U8_PS0_PS1, + // fpr store + PPCREC_FPR_ST_MODE_SINGLE_FROM_PS0, // store 1 single precision float from ps0 + PPCREC_FPR_ST_MODE_DOUBLE_FROM_PS0, // store 1 double precision float from ps0 + + PPCREC_FPR_ST_MODE_UI32_FROM_PS0, // store raw low-32bit of PS0 + + PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0_PS1, + PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0, + PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0_PS1, + PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0, + PPCREC_FPR_ST_MODE_PSQ_S8_PS0, + PPCREC_FPR_ST_MODE_PSQ_S8_PS0_PS1, + PPCREC_FPR_ST_MODE_PSQ_U8_PS0, + PPCREC_FPR_ST_MODE_PSQ_U8_PS0_PS1, + PPCREC_FPR_ST_MODE_PSQ_U16_PS0, + PPCREC_FPR_ST_MODE_PSQ_U16_PS0_PS1, + PPCREC_FPR_ST_MODE_PSQ_S16_PS0, + PPCREC_FPR_ST_MODE_PSQ_S16_PS0_PS1, +}; + +bool PPCRecompiler_generateIntermediateCode(ppcImlGenContext_t& ppcImlGenContext, PPCRecFunction_t* PPCRecFunction, std::set<uint32>& entryAddresses); +void PPCRecompiler_freeContext(ppcImlGenContext_t* ppcImlGenContext); // todo - move to destructor + +PPCRecImlInstruction_t* PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext_t* ppcImlGenContext); +void PPCRecompiler_pushBackIMLInstructions(PPCRecImlSegment_t* imlSegment, sint32 index, sint32 shiftBackCount); +PPCRecImlInstruction_t* PPCRecompiler_insertInstruction(PPCRecImlSegment_t* imlSegment, sint32 index); + +void PPCRecompilerIml_insertSegments(ppcImlGenContext_t* ppcImlGenContext, sint32 index, sint32 count); + +void PPCRecompilerIml_setSegmentPoint(ppcRecompilerSegmentPoint_t* segmentPoint, PPCRecImlSegment_t* imlSegment, sint32 index); +void PPCRecompilerIml_removeSegmentPoint(ppcRecompilerSegmentPoint_t* segmentPoint); + +// GPR register management +uint32 PPCRecompilerImlGen_loadRegister(ppcImlGenContext_t* ppcImlGenContext, uint32 mappedName, bool loadNew = false); +uint32 PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext_t* ppcImlGenContext, uint32 mappedName); + +// FPR register management +uint32 PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext_t* ppcImlGenContext, uint32 mappedName, bool loadNew = false); +uint32 PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext_t* ppcImlGenContext, uint32 mappedName); + +// IML instruction generation +void PPCRecompilerImlGen_generateNewInstruction_jump(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlInstruction_t* imlInstruction, uint32 jumpmarkAddress); +void PPCRecompilerImlGen_generateNewInstruction_jumpSegment(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlInstruction_t* imlInstruction); + +void PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext_t* ppcImlGenContext, uint32 operation, uint8 registerIndex, sint32 immS32, uint32 copyWidth, bool signExtend, bool bigEndian, uint8 crRegister, uint32 crMode); +void PPCRecompilerImlGen_generateNewInstruction_conditional_r_s32(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlInstruction_t* imlInstruction, uint32 operation, uint8 registerIndex, sint32 immS32, uint32 crRegisterIndex, uint32 crBitIndex, bool bitMustBeSet); +void PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlInstruction_t* imlInstruction, uint32 operation, uint8 registerResult, uint8 registerA, uint8 crRegister = PPC_REC_INVALID_REGISTER, uint8 crMode = 0); + + + +// IML instruction generation (new style, can generate new instructions but also overwrite existing ones) + +void PPCRecompilerImlGen_generateNewInstruction_noOp(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlInstruction_t* imlInstruction); +void PPCRecompilerImlGen_generateNewInstruction_memory_memory(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlInstruction_t* imlInstruction, uint8 srcMemReg, sint32 srcImmS32, uint8 dstMemReg, sint32 dstImmS32, uint8 copyWidth); + +void PPCRecompilerImlGen_generateNewInstruction_fpr_r(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlInstruction_t* imlInstruction, sint32 operation, uint8 registerResult, sint32 crRegister = PPC_REC_INVALID_REGISTER); + +// IML generation - FPU +bool PPCRecompilerImlGen_LFS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_LFSU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_LFSX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_LFSUX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_LFD(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_LFDU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_LFDX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_LFDUX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_STFS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_STFSU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_STFSX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_STFSUX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_STFIWX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_STFD(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_STFDU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_STFDX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_FADD(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_FSUB(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_FMUL(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_FDIV(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_FMADD(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_FMSUB(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_FNMSUB(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_FMULS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_FDIVS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_FADDS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_FSUBS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_FMADDS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_FMSUBS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_FNMSUBS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_FCMPO(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_FCMPU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_FMR(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_FABS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_FNABS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_FRES(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_FRSP(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_FNEG(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_FSEL(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_FRSQRTE(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_FCTIWZ(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_PSQ_L(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_PSQ_LU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_PSQ_ST(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_PSQ_STU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_PS_MULS0(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_PS_MULS1(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_PS_MADDS0(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_PS_MADDS1(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_PS_ADD(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_PS_SUB(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_PS_MUL(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_PS_DIV(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_PS_MADD(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_PS_NMADD(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_PS_MSUB(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_PS_NMSUB(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_PS_SUM0(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_PS_SUM1(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_PS_NEG(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_PS_ABS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_PS_RES(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_PS_RSQRTE(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_PS_MR(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_PS_SEL(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_PS_MERGE00(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_PS_MERGE01(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_PS_MERGE10(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_PS_MERGE11(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_PS_CMPO0(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_PS_CMPU0(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_PS_CMPU1(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); + +// IML general + +bool PPCRecompiler_isSuffixInstruction(PPCRecImlInstruction_t* iml); +void PPCRecompilerIML_linkSegments(ppcImlGenContext_t* ppcImlGenContext); +void PPCRecompilerIml_setLinkBranchNotTaken(PPCRecImlSegment_t* imlSegmentSrc, PPCRecImlSegment_t* imlSegmentDst); +void PPCRecompilerIml_setLinkBranchTaken(PPCRecImlSegment_t* imlSegmentSrc, PPCRecImlSegment_t* imlSegmentDst); +void PPCRecompilerIML_relinkInputSegment(PPCRecImlSegment_t* imlSegmentOrig, PPCRecImlSegment_t* imlSegmentNew); +void PPCRecompilerIML_removeLink(PPCRecImlSegment_t* imlSegmentSrc, PPCRecImlSegment_t* imlSegmentDst); +void PPCRecompilerIML_isolateEnterableSegments(ppcImlGenContext_t* ppcImlGenContext); + +PPCRecImlInstruction_t* PPCRecompilerIML_getLastInstruction(PPCRecImlSegment_t* imlSegment); + +// IML analyzer +typedef struct +{ + uint32 readCRBits; + uint32 writtenCRBits; +}PPCRecCRTracking_t; + +bool PPCRecompilerImlAnalyzer_isTightFiniteLoop(PPCRecImlSegment_t* imlSegment); +bool PPCRecompilerImlAnalyzer_canTypeWriteCR(PPCRecImlInstruction_t* imlInstruction); +void PPCRecompilerImlAnalyzer_getCRTracking(PPCRecImlInstruction_t* imlInstruction, PPCRecCRTracking_t* crTracking); + +// IML optimizer +bool PPCRecompiler_reduceNumberOfFPRRegisters(ppcImlGenContext_t* ppcImlGenContext); + +bool PPCRecompiler_manageFPRRegisters(ppcImlGenContext_t* ppcImlGenContext); + +void PPCRecompiler_removeRedundantCRUpdates(ppcImlGenContext_t* ppcImlGenContext); +void PPCRecompiler_optimizeDirectFloatCopies(ppcImlGenContext_t* ppcImlGenContext); +void PPCRecompiler_optimizeDirectIntegerCopies(ppcImlGenContext_t* ppcImlGenContext); + +void PPCRecompiler_optimizePSQLoadAndStore(ppcImlGenContext_t* ppcImlGenContext); + +// IML register allocator +void PPCRecompilerImm_allocateRegisters(ppcImlGenContext_t* ppcImlGenContext); + +// late optimizations +void PPCRecompiler_reorderConditionModifyInstructions(ppcImlGenContext_t* ppcImlGenContext); + +// debug + +void PPCRecompiler_dumpIMLSegment(PPCRecImlSegment_t* imlSegment, sint32 segmentIndex, bool printLivenessRangeInfo = false); + + +typedef struct +{ + union + { + struct + { + sint16 readNamedReg1; + sint16 readNamedReg2; + sint16 readNamedReg3; + sint16 writtenNamedReg1; + }; + sint16 gpr[4]; // 3 read + 1 write + }; + // FPR + union + { + struct + { + // note: If destination operand is not fully written, it will be added as a read FPR as well + sint16 readFPR1; + sint16 readFPR2; + sint16 readFPR3; + sint16 readFPR4; // usually this is set to the result FPR if only partially overwritten + sint16 writtenFPR1; + }; + sint16 fpr[4]; + }; +}PPCImlOptimizerUsedRegisters_t; + +void PPCRecompiler_checkRegisterUsage(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlInstruction_t* imlInstruction, PPCImlOptimizerUsedRegisters_t* registersUsed); diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlAnalyzer.cpp b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlAnalyzer.cpp new file mode 100644 index 00000000..4962d30d --- /dev/null +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlAnalyzer.cpp @@ -0,0 +1,137 @@ +#include "PPCRecompiler.h" +#include "PPCRecompilerIml.h" +#include "util/helpers/fixedSizeList.h" +#include "Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h" + +/* + * Initializes a single segment and returns true if it is a finite loop + */ +bool PPCRecompilerImlAnalyzer_isTightFiniteLoop(PPCRecImlSegment_t* imlSegment) +{ + bool isTightFiniteLoop = false; + // base criteria, must jump to beginning of same segment + if (imlSegment->nextSegmentBranchTaken != imlSegment) + return false; + // loops using BDNZ are assumed to always be finite + for (sint32 t = 0; t < imlSegment->imlListCount; t++) + { + if (imlSegment->imlList[t].type == PPCREC_IML_TYPE_R_S32 && imlSegment->imlList[t].operation == PPCREC_IML_OP_SUB && imlSegment->imlList[t].crRegister == 8) + { + return true; + } + } + // for non-BDNZ loops, check for common patterns + // risky approach, look for ADD/SUB operations and assume that potential overflow means finite (does not include r_r_s32 ADD/SUB) + // this catches most loops with load-update and store-update instructions, but also those with decrementing counters + FixedSizeList<sint32, 64, true> list_modifiedRegisters; + for (sint32 t = 0; t < imlSegment->imlListCount; t++) + { + if (imlSegment->imlList[t].type == PPCREC_IML_TYPE_R_S32 && (imlSegment->imlList[t].operation == PPCREC_IML_OP_ADD || imlSegment->imlList[t].operation == PPCREC_IML_OP_SUB) ) + { + list_modifiedRegisters.addUnique(imlSegment->imlList[t].op_r_immS32.registerIndex); + } + } + if (list_modifiedRegisters.count > 0) + { + // remove all registers from the list that are modified by non-ADD/SUB instructions + // todo: We should also cover the case where ADD+SUB on the same register cancel the effect out + PPCImlOptimizerUsedRegisters_t registersUsed; + for (sint32 t = 0; t < imlSegment->imlListCount; t++) + { + if (imlSegment->imlList[t].type == PPCREC_IML_TYPE_R_S32 && (imlSegment->imlList[t].operation == PPCREC_IML_OP_ADD || imlSegment->imlList[t].operation == PPCREC_IML_OP_SUB)) + continue; + PPCRecompiler_checkRegisterUsage(NULL, imlSegment->imlList + t, ®istersUsed); + if(registersUsed.writtenNamedReg1 < 0) + continue; + list_modifiedRegisters.remove(registersUsed.writtenNamedReg1); + } + if (list_modifiedRegisters.count > 0) + { + return true; + } + } + return false; +} + +/* +* Returns true if the imlInstruction can overwrite CR (depending on value of ->crRegister) +*/ +bool PPCRecompilerImlAnalyzer_canTypeWriteCR(PPCRecImlInstruction_t* imlInstruction) +{ + if (imlInstruction->type == PPCREC_IML_TYPE_R_R) + return true; + if (imlInstruction->type == PPCREC_IML_TYPE_R_R_R) + return true; + if (imlInstruction->type == PPCREC_IML_TYPE_R_R_S32) + return true; + if (imlInstruction->type == PPCREC_IML_TYPE_R_S32) + return true; + if (imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R) + return true; + if (imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R_R) + return true; + if (imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R_R_R) + return true; + if (imlInstruction->type == PPCREC_IML_TYPE_FPR_R) + return true; + return false; +} + +void PPCRecompilerImlAnalyzer_getCRTracking(PPCRecImlInstruction_t* imlInstruction, PPCRecCRTracking_t* crTracking) +{ + crTracking->readCRBits = 0; + crTracking->writtenCRBits = 0; + if (imlInstruction->type == PPCREC_IML_TYPE_CJUMP) + { + if (imlInstruction->op_conditionalJump.condition != PPCREC_JUMP_CONDITION_NONE) + { + uint32 crBitFlag = 1 << (imlInstruction->op_conditionalJump.crRegisterIndex * 4 + imlInstruction->op_conditionalJump.crBitIndex); + crTracking->readCRBits = (crBitFlag); + } + } + else if (imlInstruction->type == PPCREC_IML_TYPE_CONDITIONAL_R_S32) + { + uint32 crBitFlag = 1 << (imlInstruction->op_conditional_r_s32.crRegisterIndex * 4 + imlInstruction->op_conditional_r_s32.crBitIndex); + crTracking->readCRBits = crBitFlag; + } + else if (imlInstruction->type == PPCREC_IML_TYPE_R_S32 && imlInstruction->operation == PPCREC_IML_OP_MFCR) + { + crTracking->readCRBits = 0xFFFFFFFF; + } + else if (imlInstruction->type == PPCREC_IML_TYPE_R_S32 && imlInstruction->operation == PPCREC_IML_OP_MTCRF) + { + crTracking->writtenCRBits |= ppc_MTCRFMaskToCRBitMask((uint32)imlInstruction->op_r_immS32.immS32); + } + else if (imlInstruction->type == PPCREC_IML_TYPE_CR) + { + if (imlInstruction->operation == PPCREC_IML_OP_CR_CLEAR || + imlInstruction->operation == PPCREC_IML_OP_CR_SET) + { + uint32 crBitFlag = 1 << (imlInstruction->op_cr.crD); + crTracking->writtenCRBits = crBitFlag; + } + else if (imlInstruction->operation == PPCREC_IML_OP_CR_OR || + imlInstruction->operation == PPCREC_IML_OP_CR_ORC || + imlInstruction->operation == PPCREC_IML_OP_CR_AND || + imlInstruction->operation == PPCREC_IML_OP_CR_ANDC) + { + uint32 crBitFlag = 1 << (imlInstruction->op_cr.crD); + crTracking->writtenCRBits = crBitFlag; + crBitFlag = 1 << (imlInstruction->op_cr.crA); + crTracking->readCRBits = crBitFlag; + crBitFlag = 1 << (imlInstruction->op_cr.crB); + crTracking->readCRBits |= crBitFlag; + } + else + assert_dbg(); + } + else if (PPCRecompilerImlAnalyzer_canTypeWriteCR(imlInstruction) && imlInstruction->crRegister >= 0 && imlInstruction->crRegister <= 7) + { + crTracking->writtenCRBits |= (0xF << (imlInstruction->crRegister * 4)); + } + else if ((imlInstruction->type == PPCREC_IML_TYPE_STORE || imlInstruction->type == PPCREC_IML_TYPE_STORE_INDEXED) && imlInstruction->op_storeLoad.copyWidth == PPC_REC_STORE_STWCX_MARKER) + { + // overwrites CR0 + crTracking->writtenCRBits |= (0xF << 0); + } +} diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlGen.cpp b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlGen.cpp new file mode 100644 index 00000000..7291ac41 --- /dev/null +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlGen.cpp @@ -0,0 +1,5026 @@ +#include "Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h" +#include "Cafe/HW/Espresso/Interpreter/PPCInterpreterHelper.h" +#include "PPCRecompiler.h" +#include "PPCRecompilerIml.h" +#include "PPCRecompilerX64.h" +#include "PPCRecompilerImlRanges.h" +#include "util/helpers/StringBuf.h" + +bool PPCRecompiler_decodePPCInstruction(ppcImlGenContext_t* ppcImlGenContext); +uint32 PPCRecompiler_iterateCurrentInstruction(ppcImlGenContext_t* ppcImlGenContext); +uint32 PPCRecompiler_getInstructionByOffset(ppcImlGenContext_t* ppcImlGenContext, uint32 offset); + +PPCRecImlInstruction_t* PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext_t* ppcImlGenContext) +{ + if( ppcImlGenContext->imlListCount+1 > ppcImlGenContext->imlListSize ) + { + sint32 newSize = ppcImlGenContext->imlListCount*2 + 2; + ppcImlGenContext->imlList = (PPCRecImlInstruction_t*)realloc(ppcImlGenContext->imlList, sizeof(PPCRecImlInstruction_t)*newSize); + ppcImlGenContext->imlListSize = newSize; + } + PPCRecImlInstruction_t* imlInstruction = ppcImlGenContext->imlList+ppcImlGenContext->imlListCount; + memset(imlInstruction, 0x00, sizeof(PPCRecImlInstruction_t)); + imlInstruction->crRegister = PPC_REC_INVALID_REGISTER; // dont update any cr register by default + imlInstruction->associatedPPCAddress = ppcImlGenContext->ppcAddressOfCurrentInstruction; + ppcImlGenContext->imlListCount++; + return imlInstruction; +} + +void PPCRecompilerImlGen_generateNewInstruction_jumpmark(ppcImlGenContext_t* ppcImlGenContext, uint32 address) +{ + // no-op that indicates possible destination of a jump + PPCRecImlInstruction_t* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); + imlInstruction->type = PPCREC_IML_TYPE_JUMPMARK; + imlInstruction->op_jumpmark.address = address; +} + +void PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext_t* ppcImlGenContext, uint32 macroId, uint32 param, uint32 param2, uint16 paramU16) +{ + // no-op that indicates possible destination of a jump + PPCRecImlInstruction_t* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); + imlInstruction->type = PPCREC_IML_TYPE_MACRO; + imlInstruction->operation = macroId; + imlInstruction->op_macro.param = param; + imlInstruction->op_macro.param2 = param2; + imlInstruction->op_macro.paramU16 = paramU16; +} + +/* + * Generates a marker for Interpreter -> Recompiler entrypoints + * PPC_ENTER iml instructions have no associated PPC address but the instruction itself has one + */ +void PPCRecompilerImlGen_generateNewInstruction_ppcEnter(ppcImlGenContext_t* ppcImlGenContext, uint32 ppcAddress) +{ + // no-op that indicates possible destination of a jump + PPCRecImlInstruction_t* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); + imlInstruction->type = PPCREC_IML_TYPE_PPC_ENTER; + imlInstruction->operation = 0; + imlInstruction->op_ppcEnter.ppcAddress = ppcAddress; + imlInstruction->op_ppcEnter.x64Offset = 0; + imlInstruction->associatedPPCAddress = 0; +} + +void PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlInstruction_t* imlInstruction, uint32 operation, uint8 registerResult, uint8 registerA, uint8 crRegister, uint8 crMode) +{ + // operation with two register operands (e.g. "t0 = t1") + if(imlInstruction == NULL) + imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); + imlInstruction->type = PPCREC_IML_TYPE_R_R; + imlInstruction->operation = operation; + imlInstruction->crRegister = crRegister; + imlInstruction->crMode = crMode; + imlInstruction->op_r_r.registerResult = registerResult; + imlInstruction->op_r_r.registerA = registerA; +} + +void PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext_t* ppcImlGenContext, uint32 operation, uint8 registerResult, uint8 registerA, uint8 registerB, uint8 crRegister=PPC_REC_INVALID_REGISTER, uint8 crMode=0) +{ + // operation with three register operands (e.g. "t0 = t1 + t4") + PPCRecImlInstruction_t* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); + imlInstruction->type = PPCREC_IML_TYPE_R_R_R; + imlInstruction->operation = operation; + imlInstruction->crRegister = crRegister; + imlInstruction->crMode = crMode; + imlInstruction->op_r_r_r.registerResult = registerResult; + imlInstruction->op_r_r_r.registerA = registerA; + imlInstruction->op_r_r_r.registerB = registerB; +} + +void PPCRecompilerImlGen_generateNewInstruction_r_r_s32(ppcImlGenContext_t* ppcImlGenContext, uint32 operation, uint8 registerResult, uint8 registerA, sint32 immS32, uint8 crRegister=PPC_REC_INVALID_REGISTER, uint8 crMode=0) +{ + // operation with two register operands and one signed immediate (e.g. "t0 = t1 + 1234") + PPCRecImlInstruction_t* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); + imlInstruction->type = PPCREC_IML_TYPE_R_R_S32; + imlInstruction->operation = operation; + imlInstruction->crRegister = crRegister; + imlInstruction->crMode = crMode; + imlInstruction->op_r_r_s32.registerResult = registerResult; + imlInstruction->op_r_r_s32.registerA = registerA; + imlInstruction->op_r_r_s32.immS32 = immS32; +} + +void PPCRecompilerImlGen_generateNewInstruction_name_r(ppcImlGenContext_t* ppcImlGenContext, uint32 operation, uint8 registerIndex, uint32 name, uint32 copyWidth, bool signExtend, bool bigEndian) +{ + // Store name (e.g. "'r3' = t0" which translates to MOV [ESP+offset_r3], reg32) + PPCRecImlInstruction_t* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); + imlInstruction->type = PPCREC_IML_TYPE_NAME_R; + imlInstruction->operation = operation; + imlInstruction->op_r_name.registerIndex = registerIndex; + imlInstruction->op_r_name.name = name; + imlInstruction->op_r_name.copyWidth = copyWidth; + imlInstruction->op_r_name.flags = (signExtend?PPCREC_IML_OP_FLAG_SIGNEXTEND:0)|(bigEndian?PPCREC_IML_OP_FLAG_SWITCHENDIAN:0); +} + +void PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext_t* ppcImlGenContext, uint32 operation, uint8 registerIndex, sint32 immS32, uint32 copyWidth, bool signExtend, bool bigEndian, uint8 crRegister, uint32 crMode) +{ + // two variations: + // operation without store (e.g. "'r3' < 123" which has no effect other than updating a condition flags register) + // operation with store (e.g. "'r3' = 123") + PPCRecImlInstruction_t* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); + imlInstruction->type = PPCREC_IML_TYPE_R_S32; + imlInstruction->operation = operation; + imlInstruction->crRegister = crRegister; + imlInstruction->crMode = crMode; + imlInstruction->op_r_immS32.registerIndex = registerIndex; + imlInstruction->op_r_immS32.immS32 = immS32; +} + +void PPCRecompilerImlGen_generateNewInstruction_conditional_r_s32(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlInstruction_t* imlInstruction, uint32 operation, uint8 registerIndex, sint32 immS32, uint32 crRegisterIndex, uint32 crBitIndex, bool bitMustBeSet) +{ + if(imlInstruction == NULL) + imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); + else + memset(imlInstruction, 0, sizeof(PPCRecImlInstruction_t)); + imlInstruction->type = PPCREC_IML_TYPE_CONDITIONAL_R_S32; + imlInstruction->operation = operation; + imlInstruction->crRegister = PPC_REC_INVALID_REGISTER; + // r_s32 operation + imlInstruction->op_conditional_r_s32.registerIndex = registerIndex; + imlInstruction->op_conditional_r_s32.immS32 = immS32; + // condition + imlInstruction->op_conditional_r_s32.crRegisterIndex = crRegisterIndex; + imlInstruction->op_conditional_r_s32.crBitIndex = crBitIndex; + imlInstruction->op_conditional_r_s32.bitMustBeSet = bitMustBeSet; +} + + +void PPCRecompilerImlGen_generateNewInstruction_jump(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlInstruction_t* imlInstruction, uint32 jumpmarkAddress) +{ + // jump + if (imlInstruction == NULL) + imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); + else + memset(imlInstruction, 0, sizeof(PPCRecImlInstruction_t)); + imlInstruction->type = PPCREC_IML_TYPE_CJUMP; + imlInstruction->crRegister = PPC_REC_INVALID_REGISTER; + imlInstruction->op_conditionalJump.jumpmarkAddress = jumpmarkAddress; + imlInstruction->op_conditionalJump.jumpAccordingToSegment = false; + imlInstruction->op_conditionalJump.condition = PPCREC_JUMP_CONDITION_NONE; + imlInstruction->op_conditionalJump.crRegisterIndex = 0; + imlInstruction->op_conditionalJump.crBitIndex = 0; + imlInstruction->op_conditionalJump.bitMustBeSet = false; +} + +// jump based on segment branches +void PPCRecompilerImlGen_generateNewInstruction_jumpSegment(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlInstruction_t* imlInstruction) +{ + // jump + if (imlInstruction == NULL) + imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); + imlInstruction->associatedPPCAddress = 0; + imlInstruction->type = PPCREC_IML_TYPE_CJUMP; + imlInstruction->crRegister = PPC_REC_INVALID_REGISTER; + imlInstruction->op_conditionalJump.jumpmarkAddress = 0; + imlInstruction->op_conditionalJump.jumpAccordingToSegment = true; + imlInstruction->op_conditionalJump.condition = PPCREC_JUMP_CONDITION_NONE; + imlInstruction->op_conditionalJump.crRegisterIndex = 0; + imlInstruction->op_conditionalJump.crBitIndex = 0; + imlInstruction->op_conditionalJump.bitMustBeSet = false; +} + +void PPCRecompilerImlGen_generateNewInstruction_noOp(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlInstruction_t* imlInstruction) +{ + if (imlInstruction == NULL) + imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); + imlInstruction->type = PPCREC_IML_TYPE_NO_OP; + imlInstruction->operation = 0; + imlInstruction->crRegister = PPC_REC_INVALID_REGISTER; + imlInstruction->crMode = 0; +} + +void PPCRecompilerImlGen_generateNewInstruction_cr(ppcImlGenContext_t* ppcImlGenContext, uint32 operation, uint8 crD, uint8 crA, uint8 crB) +{ + // multiple variations: + // operation involving only one cr bit (like clear crD bit) + // operation involving three cr bits (like crD = crA or crB) + PPCRecImlInstruction_t* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); + imlInstruction->type = PPCREC_IML_TYPE_CR; + imlInstruction->operation = operation; + imlInstruction->crRegister = PPC_REC_INVALID_REGISTER; + imlInstruction->crMode = 0; + imlInstruction->op_cr.crD = crD; + imlInstruction->op_cr.crA = crA; + imlInstruction->op_cr.crB = crB; +} + +void PPCRecompilerImlGen_generateNewInstruction_conditionalJump(ppcImlGenContext_t* ppcImlGenContext, uint32 jumpmarkAddress, uint32 jumpCondition, uint32 crRegisterIndex, uint32 crBitIndex, bool bitMustBeSet) +{ + // conditional jump + PPCRecImlInstruction_t* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); + imlInstruction->type = PPCREC_IML_TYPE_CJUMP; + imlInstruction->crRegister = PPC_REC_INVALID_REGISTER; + imlInstruction->op_conditionalJump.jumpmarkAddress = jumpmarkAddress; + imlInstruction->op_conditionalJump.condition = jumpCondition; + imlInstruction->op_conditionalJump.crRegisterIndex = crRegisterIndex; + imlInstruction->op_conditionalJump.crBitIndex = crBitIndex; + imlInstruction->op_conditionalJump.bitMustBeSet = bitMustBeSet; +} + +void PPCRecompilerImlGen_generateNewInstruction_r_memory(ppcImlGenContext_t* ppcImlGenContext, uint8 registerDestination, uint8 registerMemory, sint32 immS32, uint32 copyWidth, bool signExtend, bool switchEndian) +{ + // load from memory + PPCRecImlInstruction_t* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); + imlInstruction->type = PPCREC_IML_TYPE_LOAD; + imlInstruction->operation = 0; + imlInstruction->crRegister = PPC_REC_INVALID_REGISTER; + imlInstruction->op_storeLoad.registerData = registerDestination; + imlInstruction->op_storeLoad.registerMem = registerMemory; + imlInstruction->op_storeLoad.immS32 = immS32; + imlInstruction->op_storeLoad.copyWidth = copyWidth; + //imlInstruction->op_storeLoad.flags = (signExtend ? PPCREC_IML_OP_FLAG_SIGNEXTEND : 0) | (switchEndian ? PPCREC_IML_OP_FLAG_SWITCHENDIAN : 0); + imlInstruction->op_storeLoad.flags2.swapEndian = switchEndian; + imlInstruction->op_storeLoad.flags2.signExtend = signExtend; +} + +void PPCRecompilerImlGen_generateNewInstruction_r_memory_indexed(ppcImlGenContext_t* ppcImlGenContext, uint8 registerDestination, uint8 registerMemory1, uint8 registerMemory2, uint32 copyWidth, bool signExtend, bool switchEndian) +{ + // load from memory + PPCRecImlInstruction_t* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); + imlInstruction->type = PPCREC_IML_TYPE_LOAD_INDEXED; + imlInstruction->operation = 0; + imlInstruction->crRegister = PPC_REC_INVALID_REGISTER; + imlInstruction->op_storeLoad.registerData = registerDestination; + imlInstruction->op_storeLoad.registerMem = registerMemory1; + imlInstruction->op_storeLoad.registerMem2 = registerMemory2; + imlInstruction->op_storeLoad.copyWidth = copyWidth; + //imlInstruction->op_storeLoad.flags = (signExtend?PPCREC_IML_OP_FLAG_SIGNEXTEND:0)|(switchEndian?PPCREC_IML_OP_FLAG_SWITCHENDIAN:0); + imlInstruction->op_storeLoad.flags2.swapEndian = switchEndian; + imlInstruction->op_storeLoad.flags2.signExtend = signExtend; +} + +void PPCRecompilerImlGen_generateNewInstruction_memory_r(ppcImlGenContext_t* ppcImlGenContext, uint8 registerSource, uint8 registerMemory, sint32 immS32, uint32 copyWidth, bool switchEndian) +{ + // load from memory + PPCRecImlInstruction_t* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); + imlInstruction->type = PPCREC_IML_TYPE_STORE; + imlInstruction->operation = 0; + imlInstruction->crRegister = PPC_REC_INVALID_REGISTER; + imlInstruction->op_storeLoad.registerData = registerSource; + imlInstruction->op_storeLoad.registerMem = registerMemory; + imlInstruction->op_storeLoad.immS32 = immS32; + imlInstruction->op_storeLoad.copyWidth = copyWidth; + //imlInstruction->op_storeLoad.flags = (switchEndian?PPCREC_IML_OP_FLAG_SWITCHENDIAN:0); + imlInstruction->op_storeLoad.flags2.swapEndian = switchEndian; + imlInstruction->op_storeLoad.flags2.signExtend = false; +} + +void PPCRecompilerImlGen_generateNewInstruction_memory_r_indexed(ppcImlGenContext_t* ppcImlGenContext, uint8 registerDestination, uint8 registerMemory1, uint8 registerMemory2, uint32 copyWidth, bool signExtend, bool switchEndian) +{ + // load from memory + PPCRecImlInstruction_t* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); + imlInstruction->type = PPCREC_IML_TYPE_STORE_INDEXED; + imlInstruction->operation = 0; + imlInstruction->crRegister = PPC_REC_INVALID_REGISTER; + imlInstruction->op_storeLoad.registerData = registerDestination; + imlInstruction->op_storeLoad.registerMem = registerMemory1; + imlInstruction->op_storeLoad.registerMem2 = registerMemory2; + imlInstruction->op_storeLoad.copyWidth = copyWidth; + //imlInstruction->op_storeLoad.flags = (signExtend?PPCREC_IML_OP_FLAG_SIGNEXTEND:0)|(switchEndian?PPCREC_IML_OP_FLAG_SWITCHENDIAN:0); + imlInstruction->op_storeLoad.flags2.swapEndian = switchEndian; + imlInstruction->op_storeLoad.flags2.signExtend = signExtend; +} + +void PPCRecompilerImlGen_generateNewInstruction_memory_memory(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlInstruction_t* imlInstruction, uint8 srcMemReg, sint32 srcImmS32, uint8 dstMemReg, sint32 dstImmS32, uint8 copyWidth) +{ + // copy from memory to memory + if(imlInstruction == NULL) + imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); + imlInstruction->type = PPCREC_IML_TYPE_MEM2MEM; + imlInstruction->operation = 0; + imlInstruction->crRegister = PPC_REC_INVALID_REGISTER; + imlInstruction->op_mem2mem.src.registerMem = srcMemReg; + imlInstruction->op_mem2mem.src.immS32 = srcImmS32; + imlInstruction->op_mem2mem.dst.registerMem = dstMemReg; + imlInstruction->op_mem2mem.dst.immS32 = dstImmS32; + imlInstruction->op_mem2mem.copyWidth = copyWidth; +} + +uint32 PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext_t* ppcImlGenContext, uint32 mappedName) +{ + if( mappedName == PPCREC_NAME_NONE ) + { + debug_printf("PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(): Invalid mappedName parameter\n"); + return PPC_REC_INVALID_REGISTER; + } + for(uint32 i=0; i<(PPC_REC_MAX_VIRTUAL_GPR-1); i++) + { + if( ppcImlGenContext->mappedRegister[i] == PPCREC_NAME_NONE ) + { + ppcImlGenContext->mappedRegister[i] = mappedName; + return i; + } + } + return 0; +} + +uint32 PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext_t* ppcImlGenContext, uint32 mappedName) +{ + for(uint32 i=0; i< PPC_REC_MAX_VIRTUAL_GPR; i++) + { + if( ppcImlGenContext->mappedRegister[i] == mappedName ) + { + return i; + } + } + return PPC_REC_INVALID_REGISTER; +} + +uint32 PPCRecompilerImlGen_getAndLockFreeTemporaryFPR(ppcImlGenContext_t* ppcImlGenContext, uint32 mappedName) +{ + if( mappedName == PPCREC_NAME_NONE ) + { + debug_printf("PPCRecompilerImlGen_getAndLockFreeTemporaryFPR(): Invalid mappedName parameter\n"); + return PPC_REC_INVALID_REGISTER; + } + for(uint32 i=0; i<255; i++) + { + if( ppcImlGenContext->mappedFPRRegister[i] == PPCREC_NAME_NONE ) + { + ppcImlGenContext->mappedFPRRegister[i] = mappedName; + return i; + } + } + return 0; +} + +uint32 PPCRecompilerImlGen_findFPRRegisterByMappedName(ppcImlGenContext_t* ppcImlGenContext, uint32 mappedName) +{ + for(uint32 i=0; i<255; i++) + { + if( ppcImlGenContext->mappedFPRRegister[i] == mappedName ) + { + return i; + } + } + return PPC_REC_INVALID_REGISTER; +} + +/* + * Loads a PPC gpr into any of the available IML registers + * If loadNew is false, it will reuse already loaded instances + */ +uint32 PPCRecompilerImlGen_loadRegister(ppcImlGenContext_t* ppcImlGenContext, uint32 mappedName, bool loadNew) +{ + if( loadNew == false ) + { + uint32 loadedRegisterIndex = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, mappedName); + if( loadedRegisterIndex != PPC_REC_INVALID_REGISTER ) + return loadedRegisterIndex; + } + uint32 registerIndex = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, mappedName); + return registerIndex; +} + +/* + * Reuse already loaded register if present + * Otherwise create new IML register and map the name. The register contents will be undefined + */ +uint32 PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext_t* ppcImlGenContext, uint32 mappedName) +{ + uint32 loadedRegisterIndex = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, mappedName); + if( loadedRegisterIndex != PPC_REC_INVALID_REGISTER ) + return loadedRegisterIndex; + uint32 registerIndex = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, mappedName); + return registerIndex; +} + +/* + * Loads a PPC fpr into any of the available IML FPU registers + * If loadNew is false, it will check first if the fpr is already loaded into any IML register + */ +uint32 PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext_t* ppcImlGenContext, uint32 mappedName, bool loadNew) +{ + if( loadNew == false ) + { + uint32 loadedRegisterIndex = PPCRecompilerImlGen_findFPRRegisterByMappedName(ppcImlGenContext, mappedName); + if( loadedRegisterIndex != PPC_REC_INVALID_REGISTER ) + return loadedRegisterIndex; + } + uint32 registerIndex = PPCRecompilerImlGen_getAndLockFreeTemporaryFPR(ppcImlGenContext, mappedName); + return registerIndex; +} + +/* + * Checks if a PPC fpr register is already loaded into any IML register + * If no, it will create a new undefined temporary IML FPU register and map the name (effectively overwriting the old ppc register) + */ +uint32 PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext_t* ppcImlGenContext, uint32 mappedName) +{ + uint32 loadedRegisterIndex = PPCRecompilerImlGen_findFPRRegisterByMappedName(ppcImlGenContext, mappedName); + if( loadedRegisterIndex != PPC_REC_INVALID_REGISTER ) + return loadedRegisterIndex; + uint32 registerIndex = PPCRecompilerImlGen_getAndLockFreeTemporaryFPR(ppcImlGenContext, mappedName); + return registerIndex; +} + +void PPCRecompilerImlGen_TW(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ +//#ifndef PUBLIC_RELEASE +// PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_DEBUGBREAK, ppcImlGenContext->ppcAddressOfCurrentInstruction, 0, 0); +//#endif + PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_LEAVE, ppcImlGenContext->ppcAddressOfCurrentInstruction, 0, 0); +} + +bool PPCRecompilerImlGen_MTSPR(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + uint32 rD, spr1, spr2, spr; + PPC_OPC_TEMPL_XO(opcode, rD, spr1, spr2); + spr = spr1 | (spr2<<5); + if (spr == SPR_CTR || spr == SPR_LR) + { + uint32 gprReg = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0 + rD); + if (gprReg == PPC_REC_INVALID_REGISTER) + gprReg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rD); + uint32 sprReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + spr); + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, sprReg, gprReg); + } + else if (spr >= SPR_UGQR0 && spr <= SPR_UGQR7) + { + uint32 gprReg = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0 + rD); + if (gprReg == PPC_REC_INVALID_REGISTER) + gprReg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rD); + uint32 sprReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + spr); + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, sprReg, gprReg); + ppcImlGenContext->tracking.modifiesGQR[spr - SPR_UGQR0] = true; + } + else + return false; + return true; +} + +bool PPCRecompilerImlGen_MFSPR(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + uint32 rD, spr1, spr2, spr; + PPC_OPC_TEMPL_XO(opcode, rD, spr1, spr2); + spr = spr1 | (spr2<<5); + if (spr == SPR_LR || spr == SPR_CTR) + { + uint32 sprReg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + spr); + uint32 gprReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0 + rD); + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, gprReg, sprReg); + } + else if (spr >= SPR_UGQR0 && spr <= SPR_UGQR7) + { + uint32 sprReg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + spr); + uint32 gprReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0 + rD); + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, gprReg, sprReg); + } + else + return false; + return true; +} + +bool PPCRecompilerImlGen_MFTB(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + uint32 rD, spr1, spr2, spr; + PPC_OPC_TEMPL_XO(opcode, rD, spr1, spr2); + spr = spr1 | (spr2<<5); + + if (spr == 268 || spr == 269) + { + // TBL / TBU + uint32 param2 = spr | (rD << 16); + PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_MFTB, ppcImlGenContext->ppcAddressOfCurrentInstruction, param2, 0); + return true; + } + return false; +} + +bool PPCRecompilerImlGen_MFCR(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rD, rA, rB; + PPC_OPC_TEMPL_X(opcode, rD, rA, rB); + uint32 gprReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0 + rD); + PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_MFCR, gprReg, 0, 0, false, false, PPC_REC_INVALID_REGISTER, 0); + return true; +} + +bool PPCRecompilerImlGen_MTCRF(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + uint32 rS; + uint32 crMask; + PPC_OPC_TEMPL_XFX(opcode, rS, crMask); + uint32 gprReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0 + rS); + PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_MTCRF, gprReg, crMask, 0, false, false, PPC_REC_INVALID_REGISTER, 0); + return true; +} + +void PPCRecompilerImlGen_CMP(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + uint32 cr; + int rA, rB; + PPC_OPC_TEMPL_X(opcode, cr, rA, rB); + cr >>= 2; + uint32 gprRegisterA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + uint32 gprRegisterB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_COMPARE_SIGNED, gprRegisterA, gprRegisterB, cr, PPCREC_CR_MODE_COMPARE_SIGNED); +} + +void PPCRecompilerImlGen_CMPL(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + uint32 cr; + int rA, rB; + PPC_OPC_TEMPL_X(opcode, cr, rA, rB); + cr >>= 2; + uint32 gprRegisterA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + uint32 gprRegisterB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_COMPARE_UNSIGNED, gprRegisterA, gprRegisterB, cr, PPCREC_CR_MODE_COMPARE_UNSIGNED); +} + +void PPCRecompilerImlGen_CMPI(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + uint32 cr; + int rA; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(opcode, cr, rA, imm); + cr >>= 2; + sint32 b = imm; + // load gpr into register + uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_COMPARE_SIGNED, gprRegister, b, 0, false, false, cr, PPCREC_CR_MODE_COMPARE_SIGNED); +} + +void PPCRecompilerImlGen_CMPLI(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + uint32 cr; + int rA; + uint32 imm; + PPC_OPC_TEMPL_D_UImm(opcode, cr, rA, imm); + cr >>= 2; + uint32 b = imm; + // load gpr into register + uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_COMPARE_UNSIGNED, gprRegister, (sint32)b, 0, false, false, cr, PPCREC_CR_MODE_COMPARE_UNSIGNED); +} + +bool PPCRecompiler_canInlineFunction(MPTR functionPtr, sint32* functionInstructionCount) +{ + for (sint32 i = 0; i < 6; i++) + { + uint32 opcode = memory_readU32(functionPtr+i*4); + switch ((opcode >> 26)) + { + case 14: // ADDI + case 15: // ADDIS + continue; + case 19: // opcode category 19 + switch (PPC_getBits(opcode, 30, 10)) + { + case 16: + if (opcode == 0x4E800020) + { + *functionInstructionCount = i; + return true; // BLR + } + return false; + } + return false; + case 32: // LWZ + case 33: // LWZU + case 34: // LBZ + case 35: // LBZU + case 36: // STW + case 37: // STWU + case 38: // STB + case 39: // STBU + case 40: // LHZ + case 41: // LHZU + case 42: // LHA + case 43: // LHAU + case 44: // STH + case 45: // STHU + case 46: // LMW + case 47: // STMW + case 48: // LFS + case 49: // LFSU + case 50: // LFD + case 51: // LFDU + case 52: // STFS + case 53: // STFSU + case 54: // STFD + case 55: // STFDU + continue; + default: + return false; + } + } + return false; +} + +void PPCRecompiler_generateInlinedCode(ppcImlGenContext_t* ppcImlGenContext, uint32 startAddress, sint32 instructionCount) +{ + for (sint32 i = 0; i < instructionCount; i++) + { + ppcImlGenContext->ppcAddressOfCurrentInstruction = startAddress + i*4; + ppcImlGenContext->cyclesSinceLastBranch++; + if (PPCRecompiler_decodePPCInstruction(ppcImlGenContext)) + { + assert_dbg(); + } + } + // add range + ppcRecRange_t recRange; + recRange.ppcAddress = startAddress; + recRange.ppcSize = instructionCount*4 + 4; // + 4 because we have to include the BLR + ppcImlGenContext->functionRef->list_ranges.push_back(recRange); +} + +bool PPCRecompilerImlGen_B(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + uint32 li; + PPC_OPC_TEMPL_I(opcode, li); + uint32 jumpAddressDest = li; + if( (opcode&PPC_OPC_AA) == 0 ) + { + jumpAddressDest = li + (unsigned int)ppcImlGenContext->ppcAddressOfCurrentInstruction; + } + if( opcode&PPC_OPC_LK ) + { + // function call + // check if function can be inlined + sint32 inlineFuncInstructionCount = 0; + if (PPCRecompiler_canInlineFunction(jumpAddressDest, &inlineFuncInstructionCount)) + { + // generate NOP iml instead of BL macro (this assures that segment PPC range remains intact) + PPCRecompilerImlGen_generateNewInstruction_noOp(ppcImlGenContext, NULL); + //forceLog_printf("Inline func 0x%08x at %08x", jumpAddressDest, ppcImlGenContext->ppcAddressOfCurrentInstruction); + uint32* prevInstructionPtr = ppcImlGenContext->currentInstruction; + ppcImlGenContext->currentInstruction = (uint32*)memory_getPointerFromVirtualOffset(jumpAddressDest); + PPCRecompiler_generateInlinedCode(ppcImlGenContext, jumpAddressDest, inlineFuncInstructionCount); + ppcImlGenContext->currentInstruction = prevInstructionPtr; + return true; + } + // generate funtion call instructions + PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_BL, ppcImlGenContext->ppcAddressOfCurrentInstruction, jumpAddressDest, ppcImlGenContext->cyclesSinceLastBranch); + PPCRecompilerImlGen_generateNewInstruction_ppcEnter(ppcImlGenContext, ppcImlGenContext->ppcAddressOfCurrentInstruction+4); + return true; + } + // is jump destination within recompiled function? + if( jumpAddressDest >= ppcImlGenContext->functionRef->ppcAddress && jumpAddressDest < (ppcImlGenContext->functionRef->ppcAddress + ppcImlGenContext->functionRef->ppcSize) ) + { + // generate instruction + PPCRecompilerImlGen_generateNewInstruction_jump(ppcImlGenContext, NULL, jumpAddressDest); + } + else + { + // todo: Inline this jump destination if possible (in many cases it's a bunch of GPR/FPR store instructions + BLR) + PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_B_FAR, ppcImlGenContext->ppcAddressOfCurrentInstruction, jumpAddressDest, ppcImlGenContext->cyclesSinceLastBranch); + } + return true; +} + +bool PPCRecompilerImlGen_BC(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + uint32 BO, BI, BD; + PPC_OPC_TEMPL_B(opcode, BO, BI, BD); + + uint32 crRegister = BI/4; + uint32 crBit = BI%4; + uint32 jumpCondition = 0; + bool conditionMustBeTrue = (BO&8)!=0; + bool useDecrementer = (BO&4)==0; // bit not set -> decrement + bool decrementerMustBeZero = (BO&2)!=0; // bit set -> branch if CTR = 0, bit not set -> branch if CTR != 0 + bool ignoreCondition = (BO&16)!=0; + + uint32 jumpAddressDest = BD; + if( (opcode&PPC_OPC_AA) == 0 ) + { + jumpAddressDest = BD + (unsigned int)ppcImlGenContext->ppcAddressOfCurrentInstruction; + } + + if( opcode&PPC_OPC_LK ) + { + // conditional function calls are not supported + if( ignoreCondition == false ) + { + // generate jump condition + if( conditionMustBeTrue ) + { + if( crBit == 0 ) + jumpCondition = PPCREC_JUMP_CONDITION_GE; + else if( crBit == 1 ) + jumpCondition = PPCREC_JUMP_CONDITION_LE; + else if( crBit == 2 ) + jumpCondition = PPCREC_JUMP_CONDITION_NE; + else if( crBit == 3 ) + jumpCondition = PPCREC_JUMP_CONDITION_NSUMMARYOVERFLOW; + } + else + { + if( crBit == 0 ) + jumpCondition = PPCREC_JUMP_CONDITION_L; + else if( crBit == 1 ) + jumpCondition = PPCREC_JUMP_CONDITION_G; + else if( crBit == 2 ) + jumpCondition = PPCREC_JUMP_CONDITION_E; + else if( crBit == 3 ) + jumpCondition = PPCREC_JUMP_CONDITION_SUMMARYOVERFLOW; + } + // generate instruction + //PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_DEBUGBREAK, ppcImlGenContext->ppcAddressOfCurrentInstruction, 0, 0); + PPCRecompilerImlGen_generateNewInstruction_conditionalJump(ppcImlGenContext, ppcImlGenContext->ppcAddressOfCurrentInstruction+4, jumpCondition, crRegister, crBit, !conditionMustBeTrue); + PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_BL, ppcImlGenContext->ppcAddressOfCurrentInstruction, jumpAddressDest, ppcImlGenContext->cyclesSinceLastBranch); + PPCRecompilerImlGen_generateNewInstruction_ppcEnter(ppcImlGenContext, ppcImlGenContext->ppcAddressOfCurrentInstruction+4); + return true; + } + return false; + } + // generate iml instructions depending on flags + if( useDecrementer ) + { + if( ignoreCondition == false ) + return false; // not supported for the moment + uint32 ctrRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0+SPR_CTR, false); + PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_SUB, ctrRegister, 1, 0, false, false, PPCREC_CR_REG_TEMP, PPCREC_CR_MODE_ARITHMETIC); + if( decrementerMustBeZero ) + PPCRecompilerImlGen_generateNewInstruction_conditionalJump(ppcImlGenContext, jumpAddressDest, PPCREC_JUMP_CONDITION_E, PPCREC_CR_REG_TEMP, 0, false); + else + PPCRecompilerImlGen_generateNewInstruction_conditionalJump(ppcImlGenContext, jumpAddressDest, PPCREC_JUMP_CONDITION_NE, PPCREC_CR_REG_TEMP, 0, false); + return true; + } + else + { + if( ignoreCondition ) + { + // branch always, no condition and no decrementer + debugBreakpoint(); + crRegister = PPC_REC_INVALID_REGISTER; // not necessary but lets optimizer know we dont care for cr register on this instruction + } + else + { + // generate jump condition + if( conditionMustBeTrue ) + { + if( crBit == 0 ) + jumpCondition = PPCREC_JUMP_CONDITION_GE; + else if( crBit == 1 ) + jumpCondition = PPCREC_JUMP_CONDITION_LE; + else if( crBit == 2 ) + jumpCondition = PPCREC_JUMP_CONDITION_NE; + else if( crBit == 3 ) + jumpCondition = PPCREC_JUMP_CONDITION_NSUMMARYOVERFLOW; + } + else + { + if( crBit == 0 ) + jumpCondition = PPCREC_JUMP_CONDITION_L; + else if( crBit == 1 ) + jumpCondition = PPCREC_JUMP_CONDITION_G; + else if( crBit == 2 ) + jumpCondition = PPCREC_JUMP_CONDITION_E; + else if( crBit == 3 ) + jumpCondition = PPCREC_JUMP_CONDITION_SUMMARYOVERFLOW; + } + + if (jumpAddressDest >= ppcImlGenContext->functionRef->ppcAddress && jumpAddressDest < (ppcImlGenContext->functionRef->ppcAddress + ppcImlGenContext->functionRef->ppcSize)) + { + // near jump + PPCRecompilerImlGen_generateNewInstruction_conditionalJump(ppcImlGenContext, jumpAddressDest, jumpCondition, crRegister, crBit, conditionMustBeTrue); + } + else + { + // far jump + PPCRecompilerImlGen_generateNewInstruction_conditionalJump(ppcImlGenContext, ppcImlGenContext->ppcAddressOfCurrentInstruction + 4, jumpCondition, crRegister, crBit, !conditionMustBeTrue); + PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_B_FAR, ppcImlGenContext->ppcAddressOfCurrentInstruction, jumpAddressDest, ppcImlGenContext->cyclesSinceLastBranch); + PPCRecompilerImlGen_generateNewInstruction_ppcEnter(ppcImlGenContext, ppcImlGenContext->ppcAddressOfCurrentInstruction + 4); + } + } + } + return true; +} + +bool PPCRecompilerImlGen_BCLR(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + uint32 BO, BI, BD; + PPC_OPC_TEMPL_XL(opcode, BO, BI, BD); + + uint32 crRegister = BI/4; + uint32 crBit = BI%4; + + uint32 jumpCondition = 0; + + bool conditionMustBeTrue = (BO&8)!=0; + bool useDecrementer = (BO&4)==0; // bit not set -> decrement + bool decrementerMustBeZero = (BO&2)!=0; // bit set -> branch if CTR = 0, bit not set -> branch if CTR != 0 + bool ignoreCondition = (BO&16)!=0; + bool saveLR = (opcode&PPC_OPC_LK)!=0; + // since we skip this instruction if the condition is true, we need to invert the logic + bool invertedConditionMustBeTrue = !conditionMustBeTrue; + if( useDecrementer ) + { + cemu_assert_debug(false); + return false; // unsupported + } + else + { + if( ignoreCondition ) + { + // store LR + if( saveLR ) + { + PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_BLRL, ppcImlGenContext->ppcAddressOfCurrentInstruction, 0, ppcImlGenContext->cyclesSinceLastBranch); + PPCRecompilerImlGen_generateNewInstruction_ppcEnter(ppcImlGenContext, ppcImlGenContext->ppcAddressOfCurrentInstruction+4); + } + else + { + // branch always, no condition and no decrementer + PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_BLR, ppcImlGenContext->ppcAddressOfCurrentInstruction, 0, ppcImlGenContext->cyclesSinceLastBranch); + } + } + else + { + // store LR + if( saveLR ) + { + uint32 registerLR = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_SPR0+SPR_LR); + PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_ASSIGN, registerLR, (ppcImlGenContext->ppcAddressOfCurrentInstruction+4)&0x7FFFFFFF, 0, false, false, PPC_REC_INVALID_REGISTER, 0); + } + // generate jump condition + if( invertedConditionMustBeTrue ) + { + if( crBit == 0 ) + jumpCondition = PPCREC_JUMP_CONDITION_L; + else if( crBit == 1 ) + jumpCondition = PPCREC_JUMP_CONDITION_G; + else if( crBit == 2 ) + jumpCondition = PPCREC_JUMP_CONDITION_E; + else if( crBit == 3 ) + jumpCondition = PPCREC_JUMP_CONDITION_SUMMARYOVERFLOW; + } + else + { + if( crBit == 0 ) + jumpCondition = PPCREC_JUMP_CONDITION_GE; + else if( crBit == 1 ) + jumpCondition = PPCREC_JUMP_CONDITION_LE; + else if( crBit == 2 ) + jumpCondition = PPCREC_JUMP_CONDITION_NE; + else if( crBit == 3 ) + jumpCondition = PPCREC_JUMP_CONDITION_NSUMMARYOVERFLOW; + } + // jump if BCLR condition NOT met (jump to jumpmark of next instruction, essentially skipping current instruction) + PPCRecompilerImlGen_generateNewInstruction_conditionalJump(ppcImlGenContext, ppcImlGenContext->ppcAddressOfCurrentInstruction+4, jumpCondition, crRegister, crBit, invertedConditionMustBeTrue); + PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_BLR, ppcImlGenContext->ppcAddressOfCurrentInstruction, 0, ppcImlGenContext->cyclesSinceLastBranch); + } + } + return true; +} + +bool PPCRecompilerImlGen_BCCTR(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + uint32 BO, BI, BD; + PPC_OPC_TEMPL_XL(opcode, BO, BI, BD); + + uint32 crRegister = BI/4; + uint32 crBit = BI%4; + + uint32 jumpCondition = 0; + + bool conditionMustBeTrue = (BO&8)!=0; + bool useDecrementer = (BO&4)==0; // bit not set -> decrement + bool decrementerMustBeZero = (BO&2)!=0; // bit set -> branch if CTR = 0, bit not set -> branch if CTR != 0 + bool ignoreCondition = (BO&16)!=0; + bool saveLR = (opcode&PPC_OPC_LK)!=0; + // since we skip this instruction if the condition is true, we need to invert the logic + bool invertedConditionMustBeTrue = !conditionMustBeTrue; + if( useDecrementer ) + { + assert_dbg(); + // if added, dont forget inverted logic + debug_printf("Rec: BCLR unsupported decrementer\n"); + return false; // unsupported + } + else + { + if( ignoreCondition ) + { + // store LR + if( saveLR ) + { + uint32 registerLR = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_SPR0+SPR_LR); + PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_ASSIGN, registerLR, (ppcImlGenContext->ppcAddressOfCurrentInstruction+4)&0x7FFFFFFF, 0, false, false, PPC_REC_INVALID_REGISTER, 0); + PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_BCTRL, ppcImlGenContext->ppcAddressOfCurrentInstruction, 0, ppcImlGenContext->cyclesSinceLastBranch); + PPCRecompilerImlGen_generateNewInstruction_ppcEnter(ppcImlGenContext, ppcImlGenContext->ppcAddressOfCurrentInstruction+4); + } + else + { + // branch always, no condition and no decrementer + PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_BCTR, ppcImlGenContext->ppcAddressOfCurrentInstruction, 0, ppcImlGenContext->cyclesSinceLastBranch); + } + } + else + { + // store LR + if( saveLR ) + { + uint32 registerLR = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_SPR0+SPR_LR); + PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_ASSIGN, registerLR, (ppcImlGenContext->ppcAddressOfCurrentInstruction+4)&0x7FFFFFFF, 0, false, false, PPC_REC_INVALID_REGISTER, 0); + } + // generate jump condition + if( invertedConditionMustBeTrue ) + { + if( crBit == 0 ) + jumpCondition = PPCREC_JUMP_CONDITION_L; + else if( crBit == 1 ) + jumpCondition = PPCREC_JUMP_CONDITION_G; + else if( crBit == 2 ) + jumpCondition = PPCREC_JUMP_CONDITION_E; + else if( crBit == 3 ) + jumpCondition = PPCREC_JUMP_CONDITION_SUMMARYOVERFLOW; + } + else + { + if( crBit == 0 ) + jumpCondition = PPCREC_JUMP_CONDITION_GE; + else if( crBit == 1 ) + jumpCondition = PPCREC_JUMP_CONDITION_LE; + else if( crBit == 2 ) + jumpCondition = PPCREC_JUMP_CONDITION_NE; + else if( crBit == 3 ) + jumpCondition = PPCREC_JUMP_CONDITION_NSUMMARYOVERFLOW; + } + // jump if BCLR condition NOT met (jump to jumpmark of next instruction, essentially skipping current instruction) + PPCRecompilerImlGen_generateNewInstruction_conditionalJump(ppcImlGenContext, ppcImlGenContext->ppcAddressOfCurrentInstruction+4, jumpCondition, crRegister, crBit, invertedConditionMustBeTrue); + PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_BCTR, ppcImlGenContext->ppcAddressOfCurrentInstruction, 0, ppcImlGenContext->cyclesSinceLastBranch); + } + } + return true; +} + +bool PPCRecompilerImlGen_ISYNC(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + // does not need to be translated + return true; +} + +bool PPCRecompilerImlGen_SYNC(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + // does not need to be translated + return true; +} + +bool PPCRecompilerImlGen_ADD(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rD, rA, rB; + PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); + //hCPU->gpr[rD] = (int)hCPU->gpr[rA] + (int)hCPU->gpr[rB]; + uint32 registerRA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + uint32 registerRB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + uint32 registerRD = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rD); + if( opcode&PPC_OPC_RC ) + { + PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_ADD, registerRD, registerRA, registerRB, 0, PPCREC_CR_MODE_LOGICAL); + } + else + { + PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_ADD, registerRD, registerRA, registerRB); + } + return true; +} + +bool PPCRecompilerImlGen_ADDC(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rD, rA, rB; + PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); + //hCPU->gpr[rD] = (int)hCPU->gpr[rA] + (int)hCPU->gpr[rB]; -> Update carry + uint32 registerRA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + uint32 registerRB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + uint32 registerRD = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rD); + if( opcode&PPC_OPC_RC ) + PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_ADD_UPDATE_CARRY, registerRD, registerRA, registerRB, 0, PPCREC_CR_MODE_LOGICAL); + else + PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_ADD_UPDATE_CARRY, registerRD, registerRA, registerRB); + return true; +} + +bool PPCRecompilerImlGen_ADDE(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rD, rA, rB; + PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); + // hCPU->gpr[rD] = hCPU->gpr[rA] + hCPU->gpr[rB] + ca; + uint32 registerRA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + uint32 registerRB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + uint32 registerRD = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rD); + if( opcode&PPC_OPC_RC ) + PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_ADD_CARRY_UPDATE_CARRY, registerRD, registerRB, registerRA, 0, PPCREC_CR_MODE_LOGICAL); + else + PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_ADD_CARRY_UPDATE_CARRY, registerRD, registerRB, registerRA); + return true; +} + +bool PPCRecompilerImlGen_ADDZE(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rD, rA, rB; + PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); + PPC_ASSERT(rB == 0); + //uint32 a = hCPU->gpr[rA]; + //uint32 ca = hCPU->xer_ca; + //hCPU->gpr[rD] = a + ca; + + uint32 registerRA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + uint32 registerRD = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rD); + // move rA to rD + if( registerRA != registerRD ) + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, registerRD, registerRA); + } + if( opcode&PPC_OPC_RC ) + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ADD_CARRY, registerRD, registerRD, 0, PPCREC_CR_MODE_LOGICAL); + } + else + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ADD_CARRY, registerRD, registerRD); + } + return true; +} + +bool PPCRecompilerImlGen_ADDME(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rD, rA, rB; + PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); + PPC_ASSERT(rB == 0); + //uint32 a = hCPU->gpr[rA]; + //uint32 ca = hCPU->xer_ca; + //hCPU->gpr[rD] = a + ca + -1; + + uint32 registerRA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + uint32 registerRD = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rD); + // move rA to rD + if( registerRA != registerRD ) + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, registerRD, registerRA); + } + if( opcode&PPC_OPC_RC ) + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ADD_CARRY_ME, registerRD, registerRD, 0, PPCREC_CR_MODE_LOGICAL); + } + else + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ADD_CARRY_ME, registerRD, registerRD); + } + return true; +} + +bool PPCRecompilerImlGen_ADDI(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rD, rA; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); + //hCPU->gpr[rD] = (rA ? (int)hCPU->gpr[rA] : 0) + (int)imm; + if( rA != 0 ) + { + uint32 registerRA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + // check if rD is already loaded, else use new temporary register + uint32 registerRD = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rD); + PPCRecompilerImlGen_generateNewInstruction_r_r_s32(ppcImlGenContext, PPCREC_IML_OP_ADD, registerRD, registerRA, imm); + } + else + { + // rA not used, instruction is value assignment + // rD = imm + uint32 registerRD = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rD); + PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_ASSIGN, registerRD, imm, 0, false, false, PPC_REC_INVALID_REGISTER, 0); + } + // never updates any cr + return true; +} + +bool PPCRecompilerImlGen_ADDIS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + int rD, rA; + uint32 imm; + PPC_OPC_TEMPL_D_Shift16(opcode, rD, rA, imm); + if( rA != 0 ) + { + uint32 registerRA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + // check if rD is already loaded, else use new temporary register + uint32 registerRD = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rD); + PPCRecompilerImlGen_generateNewInstruction_r_r_s32(ppcImlGenContext, PPCREC_IML_OP_ADD, registerRD, registerRA, (sint32)imm); + } + else + { + // rA not used, instruction turns into simple value assignment + // rD = imm + uint32 registerRD = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rD); + PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_ASSIGN, registerRD, (sint32)imm, 0, false, false, PPC_REC_INVALID_REGISTER, 0); + } + // never updates any cr + return true; +} + +bool PPCRecompilerImlGen_ADDIC(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rD, rA; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); + // rD = rA + imm; + uint32 registerRA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + // check if rD is already loaded, else use new temporary register + uint32 registerRD = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rD); + PPCRecompilerImlGen_generateNewInstruction_r_r_s32(ppcImlGenContext, PPCREC_IML_OP_ADD_UPDATE_CARRY, registerRD, registerRA, imm); + // never updates any cr + return true; +} + +bool PPCRecompilerImlGen_ADDIC_(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + // this opcode is identical to ADDIC but additionally it updates CR0 + sint32 rD, rA; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); + // rD = rA + imm; + uint32 registerRA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + // check if rD is already loaded, else use new temporary register + uint32 registerRD = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rD); + PPCRecompilerImlGen_generateNewInstruction_r_r_s32(ppcImlGenContext, PPCREC_IML_OP_ADD_UPDATE_CARRY, registerRD, registerRA, imm, 0, PPCREC_CR_MODE_LOGICAL); + return true; +} + +bool PPCRecompilerImlGen_SUBF(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rD, rA, rB; + PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); + // hCPU->gpr[rD] = ~hCPU->gpr[rA] + hCPU->gpr[rB] + 1; + // rD = rB - rA + uint32 registerRA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + uint32 registerRB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + uint32 registerRD = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rD); + if( opcode&PPC_OPC_RC ) + PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_SUB, registerRD, registerRB, registerRA, 0, PPCREC_CR_MODE_LOGICAL); + else + PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_SUB, registerRD, registerRB, registerRA); + return true; +} + +bool PPCRecompilerImlGen_SUBFE(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rD, rA, rB; + PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); + // hCPU->gpr[rD] = ~hCPU->gpr[rA] + hCPU->gpr[rB] + ca; + uint32 registerRA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + uint32 registerRB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + uint32 registerRD = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rD); + if( opcode&PPC_OPC_RC ) + PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_SUB_CARRY_UPDATE_CARRY, registerRD, registerRB, registerRA, 0, PPCREC_CR_MODE_LOGICAL); + else + PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_SUB_CARRY_UPDATE_CARRY, registerRD, registerRB, registerRA); + return true; +} + +bool PPCRecompilerImlGen_SUBFZE(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rD, rA, rB; + PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); + if( rB != 0 ) + debugBreakpoint(); + uint32 registerRA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + uint32 registerRD = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rD); + if( opcode&PPC_OPC_RC ) + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_SUB_CARRY_UPDATE_CARRY, registerRD, registerRA, 0, PPCREC_CR_MODE_LOGICAL); + else + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_SUB_CARRY_UPDATE_CARRY, registerRD, registerRA); + return true; +} + +bool PPCRecompilerImlGen_SUBFC(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rD, rA, rB; + PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); + // hCPU->gpr[rD] = ~hCPU->gpr[rA] + hCPU->gpr[rB] + 1; + // rD = rB - rA + uint32 registerRA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + uint32 registerRB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + uint32 registerRD = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rD); + PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_SUBFC, registerRD, registerRA, registerRB); + if (opcode & PPC_OPC_RC) + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_AND, registerRD, registerRD, 0, PPCREC_CR_MODE_LOGICAL); + return true; +} + +bool PPCRecompilerImlGen_SUBFIC(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rD, rA; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); + //uint32 a = hCPU->gpr[rA]; + //hCPU->gpr[rD] = ~a + imm + 1; + // cr0 is never affected + uint32 registerRA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + uint32 registerRD = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rD); + PPCRecompilerImlGen_generateNewInstruction_r_r_s32(ppcImlGenContext, PPCREC_IML_OP_SUBFC, registerRD, registerRA, imm); + return true; +} + +bool PPCRecompilerImlGen_MULLI(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + int rD, rA; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); + // mulli instruction does not modify any flags + uint32 registerResult = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rD, false); + uint32 registerOperand = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + PPCRecompilerImlGen_generateNewInstruction_r_r_s32(ppcImlGenContext, PPCREC_IML_OP_MULTIPLY_SIGNED, registerResult, registerOperand, (sint32)imm); + return true; +} + +bool PPCRecompilerImlGen_MULLW(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rD, rA, rB; + PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); + //hCPU->gpr[rD] = hCPU->gpr[rA] * hCPU->gpr[rB]; + uint32 registerResult = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rD, false); + uint32 registerOperand1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + uint32 registerOperand2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + if (opcode & PPC_OPC_OE) + { + return false; + } + if( opcode&PPC_OPC_RC ) + PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_MULTIPLY_SIGNED, registerResult, registerOperand1, registerOperand2, 0, PPCREC_CR_MODE_LOGICAL); + else + PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_MULTIPLY_SIGNED, registerResult, registerOperand1, registerOperand2); + return true; +} + +bool PPCRecompilerImlGen_MULHW(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rD, rA, rB; + PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); + //hCPU->gpr[rD] = ((sint64)(sint32)hCPU->gpr[rA] * (sint64)(sint32)hCPU->gpr[rB])>>32; + uint32 registerResult = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rD, false); + uint32 registerOperand1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + uint32 registerOperand2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + if( opcode&PPC_OPC_RC ) + PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_MULTIPLY_HIGH_SIGNED, registerResult, registerOperand1, registerOperand2, 0, PPCREC_CR_MODE_LOGICAL); + else + PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_MULTIPLY_HIGH_SIGNED, registerResult, registerOperand1, registerOperand2); + return true; +} + +bool PPCRecompilerImlGen_MULHWU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rD, rA, rB; + PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); + //hCPU->gpr[rD] = (hCPU->gpr[rA] * hCPU->gpr[rB])>>32; + uint32 registerResult = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rD, false); + uint32 registerOperand1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + uint32 registerOperand2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + if( opcode&PPC_OPC_RC ) + PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_MULTIPLY_HIGH_UNSIGNED, registerResult, registerOperand1, registerOperand2, 0, PPCREC_CR_MODE_LOGICAL); + else + PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_MULTIPLY_HIGH_UNSIGNED, registerResult, registerOperand1, registerOperand2); + return true; +} + +bool PPCRecompilerImlGen_DIVW(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rD, rA, rB; + PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); + // hCPU->gpr[rD] = (sint32)a / (sint32)b; + uint32 registerResult = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rD, false); + uint32 registerOperand1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + uint32 registerOperand2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + if (opcode & PPC_OPC_RC) + { + PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_DIVIDE_SIGNED, registerResult, registerOperand1, registerOperand2, 0, PPCREC_CR_MODE_ARITHMETIC); + } + else + { + PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_DIVIDE_SIGNED, registerResult, registerOperand1, registerOperand2); + } + return true; +} + +bool PPCRecompilerImlGen_DIVWU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rD, rA, rB; + PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); + // hCPU->gpr[rD] = (uint32)a / (uint32)b; + uint32 registerResult = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rD, false); + uint32 registerOperand1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + uint32 registerOperand2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + if (opcode & PPC_OPC_RC) + { + PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_DIVIDE_UNSIGNED, registerResult, registerOperand1, registerOperand2, 0, PPCREC_CR_MODE_ARITHMETIC); + } + else + { + PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_DIVIDE_UNSIGNED, registerResult, registerOperand1, registerOperand2); + } + return true; +} + +bool PPCRecompilerImlGen_RLWINM(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + int rS, rA, SH, MB, ME; + PPC_OPC_TEMPL_M(opcode, rS, rA, SH, MB, ME); + uint32 mask = ppc_mask(MB, ME); + //uint32 v = ppc_word_rotl(hCPU->gpr[rS], SH); + //hCPU->gpr[rA] = v & mask; + + uint32 registerRS = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS, false); + uint32 registerRA = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + // handle special forms of RLWINM + if( SH == 0 && SH == (ME-SH) && MB == 0 ) + { + // CLRRWI + // todo + } + else if( ME == (31-SH) && MB == 0 ) + { + // SLWI + if(opcode&PPC_OPC_RC) + PPCRecompilerImlGen_generateNewInstruction_r_r_s32(ppcImlGenContext, PPCREC_IML_OP_LEFT_SHIFT, registerRA, registerRS, SH, 0, PPCREC_CR_MODE_LOGICAL); + else + PPCRecompilerImlGen_generateNewInstruction_r_r_s32(ppcImlGenContext, PPCREC_IML_OP_LEFT_SHIFT, registerRA, registerRS, SH); + return true; + } + else if( SH == (32-MB) && ME == 31 ) + { + // SRWI + if(opcode&PPC_OPC_RC) + PPCRecompilerImlGen_generateNewInstruction_r_r_s32(ppcImlGenContext, PPCREC_IML_OP_RIGHT_SHIFT, registerRA, registerRS, MB, 0, PPCREC_CR_MODE_LOGICAL); + else + PPCRecompilerImlGen_generateNewInstruction_r_r_s32(ppcImlGenContext, PPCREC_IML_OP_RIGHT_SHIFT, registerRA, registerRS, MB); + return true; + } + // general handler + if( registerRA != registerRS ) + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, registerRA, registerRS); + if( SH != 0 ) + PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_LEFT_ROTATE, registerRA, SH, 0, false, false, PPC_REC_INVALID_REGISTER, 0); + if(opcode&PPC_OPC_RC) + { + PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_AND, registerRA, (sint32)mask, 0, false, false, 0, PPCREC_CR_MODE_LOGICAL); + } + else + { + if( mask != 0xFFFFFFFF ) + PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_AND, registerRA, (sint32)mask, 0, false, false, PPC_REC_INVALID_REGISTER, 0); + } + return true; +} + +bool PPCRecompilerImlGen_RLWIMI(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + int rS, rA, SH, MB, ME; + PPC_OPC_TEMPL_M(opcode, rS, rA, SH, MB, ME); + + uint32 registerRS = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS, false); + uint32 registerRA = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + // pack RLWIMI parameters into single integer + uint32 vImm = MB|(ME<<8)|(SH<<16); + PPCRecompilerImlGen_generateNewInstruction_r_r_s32(ppcImlGenContext, PPCREC_IML_OP_RLWIMI, registerRA, registerRS, (sint32)vImm, PPC_REC_INVALID_REGISTER, 0); + if (opcode & PPC_OPC_RC) + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_AND, registerRA, registerRA, 0, PPCREC_CR_MODE_LOGICAL); + return true; +} + +bool PPCRecompilerImlGen_RLWNM(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rS, rA, rB, MB, ME; + PPC_OPC_TEMPL_M(opcode, rS, rA, rB, MB, ME); + // uint32 v = ppc_word_rotl(hCPU->gpr[rS], hCPU->gpr[rB]); + uint32 mask = ppc_mask(MB, ME); + // uint32 v = ppc_word_rotl(hCPU->gpr[rS], hCPU->gpr[rB]); + // hCPU->gpr[rA] = v & mask; + uint32 registerRS = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS, false); + uint32 registerRB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + uint32 registerRA = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + + PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_LEFT_ROTATE, registerRA, registerRS, registerRB); + if (opcode & PPC_OPC_RC) + { + PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_AND, registerRA, (sint32)mask, 32, false, false, 0, PPCREC_CR_MODE_LOGICAL); + } + else + { + if( mask != 0xFFFFFFFF ) + PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_AND, registerRA, (sint32)mask, 32, false, false, PPC_REC_INVALID_REGISTER, 0); + } + return true; +} + +bool PPCRecompilerImlGen_SRAW(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rS, rA, rB; + PPC_OPC_TEMPL_X(opcode, rS, rA, rB); + //uint32 SH = hCPU->gpr[rB] & 0x3f; + //hCPU->gpr[rA] = hCPU->gpr[rS]; + //hCPU->xer_ca = 0; + //if (hCPU->gpr[rA] & 0x80000000) { + // uint32 ca = 0; + // for (uint32 i=0; i < SH; i++) { + // if (hCPU->gpr[rA] & 1) ca = 1; + // hCPU->gpr[rA] >>= 1; + // hCPU->gpr[rA] |= 0x80000000; + // } + // if (ca) hCPU->xer_ca = 1; + //} else { + // if (SH > 31) { + // hCPU->gpr[rA] = 0; + // } else { + // hCPU->gpr[rA] >>= SH; + // } + //} + //if (Opcode & PPC_OPC_RC) { + // // update cr0 flags + // ppc_update_cr0(hCPU, hCPU->gpr[rA]); + //} + + uint32 registerRS = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS, false); + uint32 registerRB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + uint32 registerRA = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + if( (opcode&PPC_OPC_RC) != 0 ) + PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_SRAW, registerRA, registerRS, registerRB, 0, PPCREC_CR_MODE_LOGICAL); + else + PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_SRAW, registerRA, registerRS, registerRB); + return true; +} + +bool PPCRecompilerImlGen_SRAWI(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + int rS, rA; + uint32 SH; + PPC_OPC_TEMPL_X(opcode, rS, rA, SH); + cemu_assert_debug(SH < 32); + uint32 registerRS = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS, false); + uint32 registerRA = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + if( opcode&PPC_OPC_RC ) + PPCRecompilerImlGen_generateNewInstruction_r_r_s32(ppcImlGenContext, PPCREC_IML_OP_SRAW, registerRA, registerRS, (sint32)SH, 0, PPCREC_CR_MODE_LOGICAL); + else + PPCRecompilerImlGen_generateNewInstruction_r_r_s32(ppcImlGenContext, PPCREC_IML_OP_SRAW, registerRA, registerRS, (sint32)SH); + return true; +} + +bool PPCRecompilerImlGen_SLW(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + int rS, rA, rB; + PPC_OPC_TEMPL_X(opcode, rS, rA, rB); + + uint32 registerRS = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS, false); + uint32 registerRB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + uint32 registerRA = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + if (opcode & PPC_OPC_RC) + { + PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_SLW, registerRA, registerRS, registerRB, 0, PPCREC_CR_MODE_LOGICAL); + } + else + { + PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_SLW, registerRA, registerRS, registerRB, PPC_REC_INVALID_REGISTER, 0); + } + return true; +} + +bool PPCRecompilerImlGen_SRW(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + int rS, rA, rB; + PPC_OPC_TEMPL_X(opcode, rS, rA, rB); + + uint32 registerRS = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS, false); + uint32 registerRB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + uint32 registerRA = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + if (opcode & PPC_OPC_RC) + { + PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_SRW, registerRA, registerRS, registerRB, 0, PPCREC_CR_MODE_LOGICAL); + } + else + { + PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_SRW, registerRA, registerRS, registerRB, PPC_REC_INVALID_REGISTER, 0); + } + return true; +} + + +bool PPCRecompilerImlGen_EXTSH(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + int rS, rA, rB; + PPC_OPC_TEMPL_X(opcode, rS, rA, rB); + PPC_ASSERT(rB==0); + uint32 registerRS = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS, false); + uint32 registerRA = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + if ( opcode&PPC_OPC_RC ) + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN_S16_TO_S32, registerRA, registerRS, 0, PPCREC_CR_MODE_ARITHMETIC); + } + else + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN_S16_TO_S32, registerRA, registerRS); + } + return true; +} + +bool PPCRecompilerImlGen_EXTSB(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rS, rA, rB; + PPC_OPC_TEMPL_X(opcode, rS, rA, rB); + uint32 registerRS = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS, false); + uint32 registerRA = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + if ( opcode&PPC_OPC_RC ) + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN_S8_TO_S32, registerRA, registerRS, 0, PPCREC_CR_MODE_ARITHMETIC); + } + else + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN_S8_TO_S32, registerRA, registerRS); + } + return true; +} + +bool PPCRecompilerImlGen_CNTLZW(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rS, rA, rB; + PPC_OPC_TEMPL_X(opcode, rS, rA, rB); + PPC_ASSERT(rB==0); + if( opcode&PPC_OPC_RC ) + { + return false; + } + uint32 registerRS = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS, false); + uint32 registerRA = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_CNTLZW, registerRA, registerRS); + + //uint32 n=0; + //uint32 x=0x80000000; + //uint32 v=hCPU->gpr[rS]; + //while (!(v & x)) { + // n++; + // if (n==32) break; + // x>>=1; + //} + //hCPU->gpr[rA] = n; + //if (Opcode & PPC_OPC_RC) { + // // update cr0 flags + // ppc_update_cr0(hCPU, hCPU->gpr[rA]); + //} + + + return true; +} + +bool PPCRecompilerImlGen_NEG(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rD, rA, rB; + PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); + PPC_ASSERT(rB == 0); + //hCPU->gpr[rD] = -((signed int)hCPU->gpr[rA]); + //if (Opcode & PPC_OPC_RC) { + // // update cr0 flags + // ppc_update_cr0(hCPU, hCPU->gpr[rD]); + //} + uint32 registerRA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + uint32 registerRD = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rD); + if( opcode&PPC_OPC_RC ) + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_NEG, registerRD, registerRA, 0, PPCREC_CR_MODE_ARITHMETIC); + } + else + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_NEG, registerRD, registerRA); + } + return true; +} + +void PPCRecompilerImlGen_LWZ(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + int rA, rD; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); + if( rA == 0 ) + { + // special form where gpr is ignored and only imm is used + PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_DEBUGBREAK, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->cyclesSinceLastBranch); + return; + } + // load memory gpr into register + uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + // check if destination register is already loaded + uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0+rD); + if( destinationRegister == PPC_REC_INVALID_REGISTER ) + destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0+rD); // else just create new register + // load half + PPCRecompilerImlGen_generateNewInstruction_r_memory(ppcImlGenContext, destinationRegister, gprRegister, imm, 32, false, true); +} + +void PPCRecompilerImlGen_LWZU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + int rA, rD; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); + if( rA == 0 ) + { + // special form where gpr is ignored and only imm is used + PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_DEBUGBREAK, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->cyclesSinceLastBranch); + return; + } + // load memory gpr into register + uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + // add imm to memory register + PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_ADD, gprRegister, (sint32)imm, 0, false, false, PPC_REC_INVALID_REGISTER, 0); + // check if destination register is already loaded + uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0+rD); + if( destinationRegister == PPC_REC_INVALID_REGISTER ) + destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0+rD); // else just create new register + // load half + PPCRecompilerImlGen_generateNewInstruction_r_memory(ppcImlGenContext, destinationRegister, gprRegister, 0, 32, false, true); +} + +void PPCRecompilerImlGen_LHA(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + int rA, rD; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); + if( rA == 0 ) + { + // special form where gpr is ignored and only imm is used + PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_DEBUGBREAK, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->cyclesSinceLastBranch); + return; + } + // load memory gpr into register + uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + // check if destination register is already loaded + uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0+rD); + if( destinationRegister == PPC_REC_INVALID_REGISTER ) + destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0+rD); // else just create new temporary register + // load half + PPCRecompilerImlGen_generateNewInstruction_r_memory(ppcImlGenContext, destinationRegister, gprRegister, imm, 16, true, true); +} + +void PPCRecompilerImlGen_LHAU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rA, rD; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); + if( rA == 0 ) + { + // special form where gpr is ignored and only imm is used + PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_DEBUGBREAK, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->cyclesSinceLastBranch); + return; + } + // load memory gpr into register + uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + // add imm to memory register + PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_ADD, gprRegister, (sint32)imm, 0, false, false, PPC_REC_INVALID_REGISTER, 0); + // check if destination register is already loaded + uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0+rD); + if( destinationRegister == PPC_REC_INVALID_REGISTER ) + destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0+rD); // else just create new temporary register + // load half + PPCRecompilerImlGen_generateNewInstruction_r_memory(ppcImlGenContext, destinationRegister, gprRegister, 0, 16, true, true); +} + +void PPCRecompilerImlGen_LHZ(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rA, rD; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); + if( rA == 0 ) + { + // special form where gpr is ignored and only imm is used + // note: Darksiders 2 has this instruction form but it is never executed. + PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_DEBUGBREAK, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->cyclesSinceLastBranch); + return; + } + // load memory gpr into register + uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + // check if destination register is already loaded + uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0+rD); + if( destinationRegister == PPC_REC_INVALID_REGISTER ) + destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0+rD); // else just create new temporary register + // load half + PPCRecompilerImlGen_generateNewInstruction_r_memory(ppcImlGenContext, destinationRegister, gprRegister, imm, 16, false, true); +} + +void PPCRecompilerImlGen_LHZU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rA, rD; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); + if( rA == 0 ) + { + // special form where gpr is ignored and only imm is used + PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_DEBUGBREAK, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->cyclesSinceLastBranch); + return; + } + // load memory gpr into register + uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + // add imm to memory register + PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_ADD, gprRegister, (sint32)imm, 0, false, false, PPC_REC_INVALID_REGISTER, 0); + // check if destination register is already loaded + uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0+rD); + if( destinationRegister == PPC_REC_INVALID_REGISTER ) + destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0+rD); // else just create new temporary register + // load half + PPCRecompilerImlGen_generateNewInstruction_r_memory(ppcImlGenContext, destinationRegister, gprRegister, 0, 16, false, true); +} + +void PPCRecompilerImlGen_LBZ(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + int rA, rD; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); + if( rA == 0 ) + { + // special form where gpr is ignored and only imm is used + PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_DEBUGBREAK, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->cyclesSinceLastBranch); + return; + } + // load memory gpr into register + uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + // check if destination register is already loaded + uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0+rD); + if( destinationRegister == PPC_REC_INVALID_REGISTER ) + destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0+rD); // else just create new register + // load byte + PPCRecompilerImlGen_generateNewInstruction_r_memory(ppcImlGenContext, destinationRegister, gprRegister, imm, 8, false, true); +} + +void PPCRecompilerImlGen_LBZU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + int rA, rD; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); + if( rA == 0 ) + { + // special form where gpr is ignored and only imm is used + PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_DEBUGBREAK, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->cyclesSinceLastBranch); + return; + } + // load memory gpr into register + uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + // add imm to memory register + PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_ADD, gprRegister, (sint32)imm, 0, false, false, PPC_REC_INVALID_REGISTER, 0); + // check if destination register is already loaded + uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0+rD); + if( destinationRegister == PPC_REC_INVALID_REGISTER ) + destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0+rD); // else just create new register + // load byte + PPCRecompilerImlGen_generateNewInstruction_r_memory(ppcImlGenContext, destinationRegister, gprRegister, 0, 8, false, true); +} + +bool PPCRecompilerImlGen_LWZX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rA, rD, rB; + PPC_OPC_TEMPL_X(opcode, rD, rA, rB); + if( rA == 0 ) + { + return false; + } + // hCPU->gpr[rD] = memory_readU8((rA?hCPU->gpr[rA]:0)+hCPU->gpr[rB]); + // load memory rA and rB into register + uint32 gprRegisterA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + uint32 gprRegisterB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + // check if destination register is already loaded + uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0+rD); + if( destinationRegister == PPC_REC_INVALID_REGISTER ) + destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0+rD); // else just create new register + // load word + PPCRecompilerImlGen_generateNewInstruction_r_memory_indexed(ppcImlGenContext, destinationRegister, gprRegisterA, gprRegisterB, 32, false, true); + return true; +} + +bool PPCRecompilerImlGen_LWZUX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rA, rD, rB; + PPC_OPC_TEMPL_X(opcode, rD, rA, rB); + if( rA == 0 ) + { + return false; + } + // load memory rA and rB into register + uint32 gprRegisterA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + uint32 gprRegisterB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + // check if destination register is already loaded + uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0+rD); + if( destinationRegister == PPC_REC_INVALID_REGISTER ) + destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0+rD); // else just create new register + // add rB to rA + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ADD, gprRegisterA, gprRegisterB); + // load word + PPCRecompilerImlGen_generateNewInstruction_r_memory(ppcImlGenContext, destinationRegister, gprRegisterA, 0, 32, false, true); + return true; +} + +bool PPCRecompilerImlGen_LWBRX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rA, rD, rB; + PPC_OPC_TEMPL_X(opcode, rD, rA, rB); + // load memory rA and rB into register + uint32 gprRegisterA = 0; + if( rA ) + gprRegisterA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rA, false); + uint32 gprRegisterB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rB, false); + // check if destination register is already loaded + uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0 + rD); + if (destinationRegister == PPC_REC_INVALID_REGISTER) + destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0 + rD); // else just create new register + // load word + if( rA ) + PPCRecompilerImlGen_generateNewInstruction_r_memory_indexed(ppcImlGenContext, destinationRegister, gprRegisterA, gprRegisterB, 32, false, false); + else + PPCRecompilerImlGen_generateNewInstruction_r_memory(ppcImlGenContext, destinationRegister, gprRegisterB, 0, 32, false, false); + return true; +} + +bool PPCRecompilerImlGen_LHAX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rA, rD, rB; + PPC_OPC_TEMPL_X(opcode, rD, rA, rB); + if( rA == 0 ) + { + // special form where gpr is ignored and only imm is used + PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_DEBUGBREAK, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->cyclesSinceLastBranch); + return true; + } + // load memory rA and rB into register + uint32 gprRegisterA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + uint32 gprRegisterB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + // check if destination register is already loaded + uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0+rD); + if( destinationRegister == PPC_REC_INVALID_REGISTER ) + destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0+rD); // else just create new register + // load half word + PPCRecompilerImlGen_generateNewInstruction_r_memory_indexed(ppcImlGenContext, destinationRegister, gprRegisterA, gprRegisterB, 16, true, true); + return true; +} + +bool PPCRecompilerImlGen_LHAUX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rA, rD, rB; + PPC_OPC_TEMPL_X(opcode, rD, rA, rB); + if( rA == 0 ) + { + // special form where gpr is ignored and only imm is used + PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_DEBUGBREAK, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->cyclesSinceLastBranch); + return true; + } + // load memory rA and rB into register + uint32 gprRegisterA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + uint32 gprRegisterB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + // check if destination register is already loaded + uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0+rD); + if( destinationRegister == PPC_REC_INVALID_REGISTER ) + destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0+rD); // else just create new register + // add rB to rA + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ADD, gprRegisterA, gprRegisterB); + // load half word + PPCRecompilerImlGen_generateNewInstruction_r_memory(ppcImlGenContext, destinationRegister, gprRegisterA, 0, 16, true, true); + return true; +} + +bool PPCRecompilerImlGen_LHZX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rA, rD, rB; + PPC_OPC_TEMPL_X(opcode, rD, rA, rB); + if( rA == 0 ) + { + // special form where gpr is ignored and only imm is used + PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_DEBUGBREAK, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->cyclesSinceLastBranch); + return true; + } + // load memory rA and rB into register + uint32 gprRegisterA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + uint32 gprRegisterB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + // check if destination register is already loaded + uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0+rD); + if( destinationRegister == PPC_REC_INVALID_REGISTER ) + destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0+rD); // else just create new register + // load half word + PPCRecompilerImlGen_generateNewInstruction_r_memory_indexed(ppcImlGenContext, destinationRegister, gprRegisterA, gprRegisterB, 16, false, true); + return true; +} + +bool PPCRecompilerImlGen_LHZUX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rA, rD, rB; + PPC_OPC_TEMPL_X(opcode, rD, rA, rB); + if( rA == 0 ) + { + // special form where gpr is ignored and only imm is used + PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_DEBUGBREAK, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->cyclesSinceLastBranch); + return true; + } + // load memory rA and rB into register + uint32 gprRegisterA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + uint32 gprRegisterB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + // check if destination register is already loaded + uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0+rD); + if( destinationRegister == PPC_REC_INVALID_REGISTER ) + destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0+rD); // else just create new register + // add rB to rA + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ADD, gprRegisterA, gprRegisterB); + // load hald word + PPCRecompilerImlGen_generateNewInstruction_r_memory(ppcImlGenContext, destinationRegister, gprRegisterA, 0, 16, false, true); + return true; +} + +void PPCRecompilerImlGen_LHBRX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rA, rD, rB; + PPC_OPC_TEMPL_X(opcode, rD, rA, rB); + // load memory rA and rB into register + uint32 gprRegisterA = rA != 0 ? PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rA, false) : 0; + uint32 gprRegisterB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rB, false); + // check if destination register is already loaded + uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0 + rD); + if (destinationRegister == PPC_REC_INVALID_REGISTER) + destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0 + rD); // else just create new register + // load half word (little-endian) + if (rA == 0) + PPCRecompilerImlGen_generateNewInstruction_r_memory(ppcImlGenContext, destinationRegister, gprRegisterB, 0, 16, false, false); + else + PPCRecompilerImlGen_generateNewInstruction_r_memory_indexed(ppcImlGenContext, destinationRegister, gprRegisterA, gprRegisterB, 16, false, false); +} + +bool PPCRecompilerImlGen_LBZX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rA, rD, rB; + PPC_OPC_TEMPL_X(opcode, rD, rA, rB); + if( rA == 0 ) + { + // special case where rA is ignored and only rB is used + return false; + } + // hCPU->gpr[rD] = memory_readU8((rA?hCPU->gpr[rA]:0)+hCPU->gpr[rB]); + // load memory rA and rB into register + uint32 gprRegisterA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + uint32 gprRegisterB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + // check if destination register is already loaded + uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0+rD); + if( destinationRegister == PPC_REC_INVALID_REGISTER ) + destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0+rD); // else just create new register + // load byte + PPCRecompilerImlGen_generateNewInstruction_r_memory_indexed(ppcImlGenContext, destinationRegister, gprRegisterA, gprRegisterB, 8, false, true); + return true; +} + +bool PPCRecompilerImlGen_LBZUX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rA, rD, rB; + PPC_OPC_TEMPL_X(opcode, rD, rA, rB); + if (rA == 0) + { + // special form where gpr is ignored and only imm is used + PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_DEBUGBREAK, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->cyclesSinceLastBranch); + return true; + } + // load memory rA and rB into register + uint32 gprRegisterA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rA, false); + uint32 gprRegisterB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rB, false); + // check if destination register is already loaded + uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0 + rD); + if (destinationRegister == PPC_REC_INVALID_REGISTER) + destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0 + rD); // else just create new register + // add rB to rA + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ADD, gprRegisterA, gprRegisterB); + // load byte + PPCRecompilerImlGen_generateNewInstruction_r_memory(ppcImlGenContext, destinationRegister, gprRegisterA, 0, 8, false, true); + return true; +} + +bool PPCRecompilerImlGen_LWARX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rA, rD, rB; + PPC_OPC_TEMPL_X(opcode, rD, rA, rB); + // load memory rA and rB into register + uint32 gprRegisterA = rA != 0?PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false):0; + uint32 gprRegisterB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + // check if destination register is already loaded + uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0+rD); + if( destinationRegister == PPC_REC_INVALID_REGISTER ) + destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0+rD); // else just create new register + // load word + if( rA != 0 ) + PPCRecompilerImlGen_generateNewInstruction_r_memory_indexed(ppcImlGenContext, destinationRegister, gprRegisterA, gprRegisterB, PPC_REC_LOAD_LWARX_MARKER, false, true); + else + PPCRecompilerImlGen_generateNewInstruction_r_memory(ppcImlGenContext, destinationRegister, gprRegisterB, 0, PPC_REC_LOAD_LWARX_MARKER, false, true); + return true; +} + +void PPCRecompilerImlGen_LMW(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rD, rA; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); + //uint32 ea = (rA ? hCPU->gpr[rA] : 0) + imm; + sint32 index = 0; + while( rD <= 31 ) + { + // load memory gpr into register + uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + // check if destination register is already loaded + uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0+rD); + if( destinationRegister == PPC_REC_INVALID_REGISTER ) + destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0+rD); // else just create new register + // load word + PPCRecompilerImlGen_generateNewInstruction_r_memory(ppcImlGenContext, destinationRegister, gprRegister, imm+index*4, 32, false, true); + // next + rD++; + index++; + } +} + +void PPCRecompilerImlGen_STW(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + int rA, rD; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); + if( rA == 0 ) + { + // special form where gpr is ignored and only imm is used + // note: Darksiders 2 has this instruction form but it is never executed. + //PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_DEBUGBREAK, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->cyclesSinceLastBranch); + return; + } + // load memory gpr into register + uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + // load source register + uint32 sourceRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rD, false); // can be the same as gprRegister + // store word + PPCRecompilerImlGen_generateNewInstruction_memory_r(ppcImlGenContext, sourceRegister, gprRegister, imm, 32, true); +} + +void PPCRecompilerImlGen_STWU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + int rA, rD; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); + if( rA == 0 ) + { + // special form where gpr is ignored and only imm is used + PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_DEBUGBREAK, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->cyclesSinceLastBranch); + return; + } + // store&update instructions where rD==rA store the register contents without added imm, therefore we need to handle it differently + // get memory gpr register + uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + // get source register + uint32 sourceRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rD, false); // can be the same as gprRegister + // add imm to memory register early if possible + if( rD != rA ) + PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_ADD, gprRegister, (sint32)imm, 0, false, false, PPC_REC_INVALID_REGISTER, 0); + // store word + PPCRecompilerImlGen_generateNewInstruction_memory_r(ppcImlGenContext, sourceRegister, gprRegister, (rD==rA)?imm:0, 32, true); + // add imm to memory register late if we couldn't do it early + if( rD == rA ) + PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_ADD, gprRegister, (sint32)imm, 0, false, false, PPC_REC_INVALID_REGISTER, 0); +} + +void PPCRecompilerImlGen_STH(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + int rA, rD; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); + if( rA == 0 ) + { + // special form where gpr is ignored and only imm is used + PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_DEBUGBREAK, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->cyclesSinceLastBranch); + return; + } + // load memory gpr into register + uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + // load source register + uint32 sourceRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rD, false); // can be the same as gprRegister + // load half + PPCRecompilerImlGen_generateNewInstruction_memory_r(ppcImlGenContext, sourceRegister, gprRegister, imm, 16, true); +} + +void PPCRecompilerImlGen_STHU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + int rA, rD; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); + if( rA == 0 ) + { + // special form where gpr is ignored and only imm is used + PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_DEBUGBREAK, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->cyclesSinceLastBranch); + return; + } + // get memory gpr register + uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + // get source register + uint32 sourceRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rD, false); // can be the same as gprRegister + // add imm to memory register early if possible + if( rD != rA ) + PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_ADD, gprRegister, (sint32)imm, 0, false, false, PPC_REC_INVALID_REGISTER, 0); + // store word + PPCRecompilerImlGen_generateNewInstruction_memory_r(ppcImlGenContext, sourceRegister, gprRegister, (rD==rA)?imm:0, 16, true); + // add imm to memory register late if we couldn't do it early + if( rD == rA ) + PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_ADD, gprRegister, (sint32)imm, 0, false, false, PPC_REC_INVALID_REGISTER, 0); +} + +void PPCRecompilerImlGen_STB(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + int rA, rS; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(opcode, rS, rA, imm); + if( rA == 0 ) + { + // special form where gpr is ignored and only imm is used + PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_DEBUGBREAK, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->cyclesSinceLastBranch); + return; + } + // load memory gpr into register + uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + // load source register + uint32 sourceRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS, false); // can be the same as gprRegister + // store byte + PPCRecompilerImlGen_generateNewInstruction_memory_r(ppcImlGenContext, sourceRegister, gprRegister, imm, 8, true); +} + +void PPCRecompilerImlGen_STBU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + int rA, rD; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); + if( rA == 0 ) + { + // special form where gpr is ignored and only imm is used + PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_DEBUGBREAK, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->cyclesSinceLastBranch); + return; + } + // get memory gpr register + uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + // get source register + uint32 sourceRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rD, false); // can be the same as gprRegister + // add imm to memory register early if possible + if( rD != rA ) + PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_ADD, gprRegister, (sint32)imm, 0, false, false, PPC_REC_INVALID_REGISTER, 0); + // store byte + PPCRecompilerImlGen_generateNewInstruction_memory_r(ppcImlGenContext, sourceRegister, gprRegister, (rD==rA)?imm:0, 8, true); + // add imm to memory register late if we couldn't do it early + if( rD == rA ) + PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_ADD, gprRegister, (sint32)imm, 0, false, false, PPC_REC_INVALID_REGISTER, 0); +} + +// generic indexed store (STWX, STHX, STBX, STWUX. If bitReversed == true -> STHBRX) +bool PPCRecompilerImlGen_STORE_INDEXED(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, uint32 storeBitWidth, bool byteReversed = false) +{ + sint32 rA, rS, rB; + PPC_OPC_TEMPL_X(opcode, rS, rA, rB); + // prepare registers + uint32 gprRegisterA; + if(rA != 0) + gprRegisterA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rA, false); + uint32 gprRegisterB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + uint32 destinationRegister = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); + // store word + if (rA == 0) + { + PPCRecompilerImlGen_generateNewInstruction_memory_r(ppcImlGenContext, destinationRegister, gprRegisterB, 0, storeBitWidth, !byteReversed); + } + else + PPCRecompilerImlGen_generateNewInstruction_memory_r_indexed(ppcImlGenContext, destinationRegister, gprRegisterA, gprRegisterB, storeBitWidth, false, !byteReversed); + return true; +} + +bool PPCRecompilerImlGen_STORE_INDEXED_UPDATE(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, uint32 storeBitWidth) +{ + sint32 rA, rS, rB; + PPC_OPC_TEMPL_X(opcode, rS, rA, rB); + if( rA == 0 ) + { + // not supported + return false; + } + if( rS == rA || rS == rB ) + { + // prepare registers + uint32 gprRegisterA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + uint32 gprRegisterB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + uint32 destinationRegister = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); + // store word + PPCRecompilerImlGen_generateNewInstruction_memory_r_indexed(ppcImlGenContext, destinationRegister, gprRegisterA, gprRegisterB, storeBitWidth, false, true); + // update EA after store + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ADD, gprRegisterA, gprRegisterB); + return true; + } + // prepare registers + uint32 gprRegisterA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + uint32 gprRegisterB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + uint32 sourceRegister = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); + // update EA + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ADD, gprRegisterA, gprRegisterB); + // store word + PPCRecompilerImlGen_generateNewInstruction_memory_r(ppcImlGenContext, sourceRegister, gprRegisterA, 0, storeBitWidth, true); + return true; +} + +bool PPCRecompilerImlGen_STWCX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rA, rS, rB; + PPC_OPC_TEMPL_X(opcode, rS, rA, rB); + // prepare registers + uint32 gprRegisterA = rA!=0?PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false):0; + uint32 gprRegisterB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + uint32 destinationRegister = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); + // store word + if( rA != 0 ) + PPCRecompilerImlGen_generateNewInstruction_memory_r_indexed(ppcImlGenContext, destinationRegister, gprRegisterA, gprRegisterB, PPC_REC_STORE_STWCX_MARKER, false, true); + else + PPCRecompilerImlGen_generateNewInstruction_memory_r(ppcImlGenContext, destinationRegister, gprRegisterB, 0, PPC_REC_STORE_STWCX_MARKER, true); + return true; +} + +bool PPCRecompilerImlGen_STWBRX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rA, rS, rB; + PPC_OPC_TEMPL_X(opcode, rS, rA, rB); + // prepare registers + uint32 gprRegisterA = rA!=0?PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false):0; + uint32 gprRegisterB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + uint32 destinationRegister = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); + // store word + if( rA != 0 ) + PPCRecompilerImlGen_generateNewInstruction_memory_r_indexed(ppcImlGenContext, destinationRegister, gprRegisterA, gprRegisterB, 32, false, false); + else + PPCRecompilerImlGen_generateNewInstruction_memory_r(ppcImlGenContext, destinationRegister, gprRegisterB, 0, 32, false); + return true; +} + +void PPCRecompilerImlGen_STMW(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rS, rA; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(opcode, rS, rA, imm); + sint32 index = 0; + while( rS <= 31 ) + { + // load memory gpr into register + uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + // load source register + uint32 sourceRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS, false); // can be the same as gprRegister + // store word + PPCRecompilerImlGen_generateNewInstruction_memory_r(ppcImlGenContext, sourceRegister, gprRegister, imm+index*4, 32, true); + // next + rS++; + index++; + } +} + +bool PPCRecompilerImlGen_LSWI(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + int rA, rD, nb; + PPC_OPC_TEMPL_X(opcode, rD, rA, nb); + if( nb == 0 ) + nb = 32; + if( nb == 4 ) + { + // if nb == 4 this instruction immitates LWZ + if( rA == 0 ) + { +#ifndef PUBLIC_RELEASE + assert_dbg(); // special form where gpr is ignored and only imm is used +#endif + return false; + } + // load memory gpr into register + uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + // check if destination register is already loaded + uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0+rD); + if( destinationRegister == PPC_REC_INVALID_REGISTER ) + destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0+rD); // else just create new register + // load half + PPCRecompilerImlGen_generateNewInstruction_r_memory(ppcImlGenContext, destinationRegister, gprRegister, 0, 32, false, true); + return true; + } + else if( nb == 2 ) + { + // if nb == 2 this instruction immitates a LHZ but the result is shifted left by 16 bits + if( rA == 0 ) + { +#ifndef PUBLIC_RELEASE + assert_dbg(); // special form where gpr is ignored and only imm is used +#endif + return false; + } + // load memory gpr into register + uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + // check if destination register is already loaded + uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0+rD); + if( destinationRegister == PPC_REC_INVALID_REGISTER ) + destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0+rD); // else just create new register + // load half + PPCRecompilerImlGen_generateNewInstruction_r_memory(ppcImlGenContext, destinationRegister, gprRegister, 0, 16, false, true); + // shift + PPCRecompilerImlGen_generateNewInstruction_r_r_s32(ppcImlGenContext, PPCREC_IML_OP_LEFT_SHIFT, destinationRegister, destinationRegister, 16); + return true; + } + else if( nb == 3 ) + { + // if nb == 3 this instruction loads a 3-byte big-endian and the result is shifted left by 8 bits + if( rA == 0 ) + { +#ifndef PUBLIC_RELEASE + assert_dbg(); // special form where gpr is ignored and only imm is used +#endif + return false; + } + // load memory gpr into register + uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + // check if destination register is already loaded + uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0+rD); + if( destinationRegister == PPC_REC_INVALID_REGISTER ) + destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0+rD); // else just create new register + // load half + PPCRecompilerImlGen_generateNewInstruction_r_memory(ppcImlGenContext, destinationRegister, gprRegister, 0, PPC_REC_STORE_LSWI_3, false, true); + return true; + } + debug_printf("PPCRecompilerImlGen_LSWI(): Unsupported nb value %d\n", nb); + return false; +} + +bool PPCRecompilerImlGen_STSWI(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + int rA, rS, nb; + PPC_OPC_TEMPL_X(opcode, rS, rA, nb); + if( nb == 0 ) + nb = 32; + if( nb == 4 ) + { + // load memory gpr into register + uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + // load source register + uint32 sourceRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS, false); // can be the same as gprRegister + // store word + PPCRecompilerImlGen_generateNewInstruction_memory_r(ppcImlGenContext, sourceRegister, gprRegister, 0, 32, true); + return true; + } + else if( nb == 2 ) + { + // load memory gpr into register + uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + // load source register + uint32 sourceRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS, false); // can be the same as gprRegister + // store half-word (shifted << 16) + PPCRecompilerImlGen_generateNewInstruction_memory_r(ppcImlGenContext, sourceRegister, gprRegister, 0, PPC_REC_STORE_STSWI_2, false); + return true; + } + else if( nb == 3 ) + { + // load memory gpr into register + uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + // load source register + uint32 sourceRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS, false); // can be the same as gprRegister + // store 3-byte-word (shifted << 8) + PPCRecompilerImlGen_generateNewInstruction_memory_r(ppcImlGenContext, sourceRegister, gprRegister, 0, PPC_REC_STORE_STSWI_3, false); + return true; + } + debug_printf("PPCRecompilerImlGen_STSWI(): Unsupported nb value %d\n", nb); + return false; +} + +bool PPCRecompilerImlGen_DCBZ(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rA, rB; + rA = (opcode>>16)&0x1F; + rB = (opcode>>11)&0x1F; + // prepare registers + uint32 gprRegisterA = rA!=0?PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false):0; + uint32 gprRegisterB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + // store + if( rA != 0 ) + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_DCBZ, gprRegisterA, gprRegisterB); + else + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_DCBZ, gprRegisterB, gprRegisterB); + return true; +} + +bool PPCRecompilerImlGen_OR(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + int rS, rA, rB; + PPC_OPC_TEMPL_X(opcode, rS, rA, rB); + // check for MR mnemonic + if( rS == rB ) + { + // simple register copy + if( rA != rS ) // check if no-op + { + sint32 gprSourceReg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); + sint32 gprDestReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + if( opcode&PPC_OPC_RC ) + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, gprDestReg, gprSourceReg, 0, PPCREC_CR_MODE_LOGICAL); + } + else + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, gprDestReg, gprSourceReg); + } + } + else + { + if( opcode&PPC_OPC_RC ) + { + // no effect but CR is updated + sint32 gprSourceReg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, gprSourceReg, gprSourceReg, 0, PPCREC_CR_MODE_LOGICAL); + } + else + { + // no-op + } + } + } + else + { + // rA = rS | rA + sint32 gprSource1Reg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); + sint32 gprSource2Reg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB); + sint32 gprDestReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + if( gprSource1Reg == gprDestReg || gprSource2Reg == gprDestReg ) + { + // make sure we don't overwrite rS or rA + if( gprSource1Reg == gprDestReg ) + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_OR, gprDestReg, gprSource2Reg); + } + else + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_OR, gprDestReg, gprSource1Reg); + } + if( opcode&PPC_OPC_RC ) + { + // fixme: merge CR update into OR instruction above + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_AND, gprDestReg, gprDestReg, 0, PPCREC_CR_MODE_LOGICAL); + } + } + else + { + // rA = rS + if( gprDestReg != gprSource1Reg ) + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, gprDestReg, gprSource1Reg); + } + // rA |= rB + if( opcode&PPC_OPC_RC ) + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_OR, gprDestReg, gprSource2Reg, 0, PPCREC_CR_MODE_LOGICAL); + } + else + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_OR, gprDestReg, gprSource2Reg); + } + } + } + return true; +} + +bool PPCRecompilerImlGen_ORC(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rS, rA, rB; + PPC_OPC_TEMPL_X(opcode, rS, rA, rB); + // hCPU->gpr[rA] = hCPU->gpr[rS] | ~hCPU->gpr[rB]; + sint32 gprSource1Reg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); + sint32 gprSource2Reg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB); + sint32 gprDestReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + if( opcode&PPC_OPC_RC ) + PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_ORC, gprDestReg, gprSource1Reg, gprSource2Reg, 0, PPCREC_CR_MODE_LOGICAL); + else + PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_ORC, gprDestReg, gprSource1Reg, gprSource2Reg); + return true; +} + +bool PPCRecompilerImlGen_NOR(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + int rS, rA, rB; + PPC_OPC_TEMPL_X(opcode, rS, rA, rB); + //hCPU->gpr[rA] = ~(hCPU->gpr[rS] | hCPU->gpr[rB]); + // check for NOT mnemonic + if( rS == rB ) + { + // simple register copy with NOT + sint32 gprSourceReg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); + sint32 gprDestReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + if( gprDestReg != gprSourceReg ) + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, gprDestReg, gprSourceReg); + if( opcode&PPC_OPC_RC ) + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_NOT, gprDestReg, gprDestReg, 0, PPCREC_CR_MODE_ARITHMETIC); + } + else + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_NOT, gprDestReg, gprDestReg); + } + } + else + { + // rA = rS | rA + sint32 gprSource1Reg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); + sint32 gprSource2Reg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB); + sint32 gprDestReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + if( gprSource1Reg == gprDestReg || gprSource2Reg == gprDestReg ) + { + // make sure we don't overwrite rS or rA + if( gprSource1Reg == gprDestReg ) + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_OR, gprDestReg, gprSource2Reg); + } + else + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_OR, gprDestReg, gprSource1Reg); + } + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_NOT, gprDestReg, gprDestReg); + if( opcode&PPC_OPC_RC ) + { + // fixme: merge CR update into OR instruction above + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_AND, gprDestReg, gprDestReg, 0, PPCREC_CR_MODE_LOGICAL); + } + } + else + { + // rA = rS + if( gprDestReg != gprSource1Reg ) + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, gprDestReg, gprSource1Reg); + } + // rA |= rB + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_OR, gprDestReg, gprSource2Reg); + if( opcode&PPC_OPC_RC ) + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_NOT, gprDestReg, gprDestReg, 0, PPCREC_CR_MODE_ARITHMETIC); + } + else + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_NOT, gprDestReg, gprDestReg); + } + } + } + return true; +} + +bool PPCRecompilerImlGen_AND(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rS, rA, rB; + PPC_OPC_TEMPL_X(opcode, rS, rA, rB); + // check for MR mnemonic + if( rS == rB ) + { + // simple register copy + if( rA != rS ) // check if no-op + { + sint32 gprSourceReg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); + sint32 gprDestReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + if( opcode&PPC_OPC_RC ) + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, gprDestReg, gprSourceReg, 0, PPCREC_CR_MODE_LOGICAL); + } + else + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, gprDestReg, gprSourceReg); + } + } + else + { + cemu_assert_unimplemented(); // no-op -> verify this case + } + } + else + { + // rA = rS & rA + sint32 gprSource1Reg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); + sint32 gprSource2Reg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB); + sint32 gprDestReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + if( gprSource1Reg == gprDestReg || gprSource2Reg == gprDestReg ) + { + // make sure we don't overwrite rS or rA + if( gprSource1Reg == gprDestReg ) + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_AND, gprDestReg, gprSource2Reg); + } + else + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_AND, gprDestReg, gprSource1Reg); + } + if( opcode&PPC_OPC_RC ) + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_AND, gprDestReg, gprDestReg, 0, PPCREC_CR_MODE_LOGICAL); + } + } + else + { + // rA = rS + if( gprDestReg != gprSource1Reg ) + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, gprDestReg, gprSource1Reg); + } + // rA &= rB + if( opcode&PPC_OPC_RC ) + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_AND, gprDestReg, gprSource2Reg, 0, PPCREC_CR_MODE_LOGICAL); + } + else + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_AND, gprDestReg, gprSource2Reg); + } + } + } + return true; +} + +bool PPCRecompilerImlGen_ANDC(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rS, rA, rB; + PPC_OPC_TEMPL_X(opcode, rS, rA, rB); + //hCPU->gpr[rA] = hCPU->gpr[rS] & ~hCPU->gpr[rB]; + //if (Opcode & PPC_OPC_RC) { + if( rS == rB ) + { + // result is always 0 -> replace with XOR rA,rA + sint32 gprDestReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + if( opcode&PPC_OPC_RC ) + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_XOR, gprDestReg, gprDestReg, 0, PPCREC_CR_MODE_LOGICAL); + } + else + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_XOR, gprDestReg, gprDestReg); + } + } + else if( rA == rB ) + { + // rB already in rA, therefore we complement rA first and then AND it with rS + sint32 gprRS = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); + sint32 gprRA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + // rA = ~rA + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_NOT, gprRA, gprRA); + // rA &= rS + if( opcode&PPC_OPC_RC ) + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_AND, gprRA, gprRS, 0, PPCREC_CR_MODE_LOGICAL); + } + else + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_AND, gprRA, gprRS); + } + } + else + { + // a & (~b) is the same as ~((~a) | b) + sint32 gprRA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + sint32 gprRB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB); + sint32 gprRS = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); + // move rS to rA (if required) + if( gprRA != gprRS ) + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, gprRA, gprRS); + } + // rS already in rA, therefore we complement rS first and then OR it with rB + // rA = ~rA + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_NOT, gprRA, gprRA); + // rA |= rB + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_OR, gprRA, gprRB); + // rA = ~rA + if( opcode&PPC_OPC_RC ) + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_NOT, gprRA, gprRA, 0, PPCREC_CR_MODE_LOGICAL); + } + else + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_NOT, gprRA, gprRA); + } + } + return true; +} + +void PPCRecompilerImlGen_ANDI(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rS, rA; + uint32 imm; + PPC_OPC_TEMPL_D_UImm(opcode, rS, rA, imm); + // ANDI. always sets cr0 flags + sint32 gprSourceReg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); + sint32 gprDestReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + // rA = rS + if( gprDestReg != gprSourceReg ) + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, gprDestReg, gprSourceReg); + // rA &= imm32 + PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_AND, gprDestReg, (sint32)imm, 0, false, false, 0, PPCREC_CR_MODE_LOGICAL); +} + +void PPCRecompilerImlGen_ANDIS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rS, rA; + uint32 imm; + PPC_OPC_TEMPL_D_Shift16(opcode, rS, rA, imm); + // ANDI. always sets cr0 flags + sint32 gprSourceReg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); + sint32 gprDestReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + // rA = rS + if( gprDestReg != gprSourceReg ) + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, gprDestReg, gprSourceReg); + // rA &= imm32 + PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_AND, gprDestReg, (sint32)imm, 0, false, false, 0, PPCREC_CR_MODE_LOGICAL); +} + +bool PPCRecompilerImlGen_XOR(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rS, rA, rB; + PPC_OPC_TEMPL_X(opcode, rS, rA, rB); + if( rS == rB ) + { + // xor register with itself + sint32 gprDestReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + if( opcode&PPC_OPC_RC ) + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_XOR, gprDestReg, gprDestReg, 0, PPCREC_CR_MODE_LOGICAL); + } + else + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_XOR, gprDestReg, gprDestReg); + } + } + else + { + // rA = rS ^ rA + sint32 gprSource1Reg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); + sint32 gprSource2Reg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB); + sint32 gprDestReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + if( gprSource1Reg == gprDestReg || gprSource2Reg == gprDestReg ) + { + // make sure we don't overwrite rS or rA + if( gprSource1Reg == gprDestReg ) + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_XOR, gprDestReg, gprSource2Reg); + } + else + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_XOR, gprDestReg, gprSource1Reg); + } + if( opcode&PPC_OPC_RC ) + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_AND, gprDestReg, gprDestReg, 0, PPCREC_CR_MODE_LOGICAL); + } + } + else + { + // rA = rS + if( gprDestReg != gprSource1Reg ) + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, gprDestReg, gprSource1Reg); + } + // rA ^= rB + if( opcode&PPC_OPC_RC ) + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_XOR, gprDestReg, gprSource2Reg, 0, PPCREC_CR_MODE_LOGICAL); + } + else + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_XOR, gprDestReg, gprSource2Reg); + } + } + } + return true; +} + + +bool PPCRecompilerImlGen_EQV(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rS, rA, rB; + PPC_OPC_TEMPL_X(opcode, rS, rA, rB); + if( rS == rB ) + { + // xor register with itself, then invert + sint32 gprDestReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_XOR, gprDestReg, gprDestReg); + if( opcode&PPC_OPC_RC ) + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_NOT, gprDestReg, gprDestReg, 0, PPCREC_CR_MODE_LOGICAL); + else + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_NOT, gprDestReg, gprDestReg); + } + else + { + // rA = ~(rS ^ rA) + sint32 gprSource1Reg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); + sint32 gprSource2Reg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB); + sint32 gprDestReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + if( gprSource1Reg == gprDestReg || gprSource2Reg == gprDestReg ) + { + // make sure we don't overwrite rS or rA + if( gprSource1Reg == gprDestReg ) + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_XOR, gprDestReg, gprSource2Reg); + } + else + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_XOR, gprDestReg, gprSource1Reg); + } + } + else + { + // rA = rS + if( gprDestReg != gprSource1Reg ) + { + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, gprDestReg, gprSource1Reg); + } + // rA ^= rB + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_XOR, gprDestReg, gprSource2Reg); + } + if( opcode&PPC_OPC_RC ) + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_NOT, gprDestReg, gprDestReg, 0, PPCREC_CR_MODE_LOGICAL); + else + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_NOT, gprDestReg, gprDestReg); + } + return true; +} + +void PPCRecompilerImlGen_ORI(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rS, rA; + uint32 imm; + PPC_OPC_TEMPL_D_UImm(opcode, rS, rA, imm); + // ORI does not set cr0 flags + //hCPU->gpr[rA] = hCPU->gpr[rS] | imm; + sint32 gprSourceReg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); + sint32 gprDestReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + // rA = rS + if( gprDestReg != gprSourceReg ) + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, gprDestReg, gprSourceReg); + // rA |= imm32 + PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_OR, gprDestReg, (sint32)imm, 0, false, false, PPC_REC_INVALID_REGISTER, 0); +} + +void PPCRecompilerImlGen_ORIS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rS, rA; + uint32 imm; + PPC_OPC_TEMPL_D_Shift16(opcode, rS, rA, imm); + // ORI does not set cr0 flags + //hCPU->gpr[rA] = hCPU->gpr[rS] | imm; + sint32 gprSourceReg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); + sint32 gprDestReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + // rA = rS + if( gprDestReg != gprSourceReg ) + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, gprDestReg, gprSourceReg); + // rA |= imm32 + PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_OR, gprDestReg, (sint32)imm, 0, false, false, PPC_REC_INVALID_REGISTER, 0); +} + +void PPCRecompilerImlGen_XORI(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rS, rA; + uint32 imm; + PPC_OPC_TEMPL_D_UImm(opcode, rS, rA, imm); + //hCPU->gpr[rA] = hCPU->gpr[rS] ^ imm; + // XORI does not set cr0 flags + sint32 gprSourceReg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); + sint32 gprDestReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + // rA = rS + if( gprDestReg != gprSourceReg ) + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, gprDestReg, gprSourceReg); + // rA |= imm32 + PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_XOR, gprDestReg, (sint32)imm, 0, false, false, PPC_REC_INVALID_REGISTER, 0); +} + +void PPCRecompilerImlGen_XORIS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rS, rA; + uint32 imm; + PPC_OPC_TEMPL_D_Shift16(opcode, rS, rA, imm); + //hCPU->gpr[rA] = hCPU->gpr[rS] ^ imm; + // XORIS does not set cr0 flags + sint32 gprSourceReg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); + sint32 gprDestReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + // rA = rS + if( gprDestReg != gprSourceReg ) + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, gprDestReg, gprSourceReg); + // rA |= imm32 + PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_XOR, gprDestReg, (sint32)imm, 0, false, false, PPC_REC_INVALID_REGISTER, 0); +} + +bool PPCRecompilerImlGen_CROR(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + int crD, crA, crB; + PPC_OPC_TEMPL_X(opcode, crD, crA, crB); + PPCRecompilerImlGen_generateNewInstruction_cr(ppcImlGenContext, PPCREC_IML_OP_CR_OR, crD, crA, crB); + return true; +} + +bool PPCRecompilerImlGen_CRORC(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + int crD, crA, crB; + PPC_OPC_TEMPL_X(opcode, crD, crA, crB); + PPCRecompilerImlGen_generateNewInstruction_cr(ppcImlGenContext, PPCREC_IML_OP_CR_ORC, crD, crA, crB); + return true; +} + +bool PPCRecompilerImlGen_CRAND(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + int crD, crA, crB; + PPC_OPC_TEMPL_X(opcode, crD, crA, crB); + PPCRecompilerImlGen_generateNewInstruction_cr(ppcImlGenContext, PPCREC_IML_OP_CR_AND, crD, crA, crB); + return true; +} + +bool PPCRecompilerImlGen_CRANDC(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + int crD, crA, crB; + PPC_OPC_TEMPL_X(opcode, crD, crA, crB); + PPCRecompilerImlGen_generateNewInstruction_cr(ppcImlGenContext, PPCREC_IML_OP_CR_ANDC, crD, crA, crB); + return true; +} + +bool PPCRecompilerImlGen_CRXOR(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + int crD, crA, crB; + PPC_OPC_TEMPL_X(opcode, crD, crA, crB); + if (crA == crB) + { + // both operands equal, clear bit in crD + // PPC's assert() uses this to pass a parameter to OSPanic + PPCRecompilerImlGen_generateNewInstruction_cr(ppcImlGenContext, PPCREC_IML_OP_CR_CLEAR, crD, 0, 0); + return true; + } + else + { + return false; + } + return true; +} + +bool PPCRecompilerImlGen_CREQV(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + int crD, crA, crB; + PPC_OPC_TEMPL_X(opcode, crD, crA, crB); + if (crA == crB) + { + // both operands equal, set bit in crD + PPCRecompilerImlGen_generateNewInstruction_cr(ppcImlGenContext, PPCREC_IML_OP_CR_SET, crD, 0, 0); + return true; + } + else + { + return false; + } + return true; +} + +bool PPCRecompilerImlGen_HLE(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + uint32 hleFuncId = opcode&0xFFFF; + PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_HLE, ppcImlGenContext->ppcAddressOfCurrentInstruction, hleFuncId, 0); + return true; +} + +uint32 PPCRecompiler_iterateCurrentInstruction(ppcImlGenContext_t* ppcImlGenContext) +{ + uint32 v = CPU_swapEndianU32(*(ppcImlGenContext->currentInstruction)); + ppcImlGenContext->currentInstruction += 1; + return v; +} + +uint32 PPCRecompiler_getInstructionByOffset(ppcImlGenContext_t* ppcImlGenContext, uint32 offset) +{ + uint32 v = CPU_swapEndianU32(*(ppcImlGenContext->currentInstruction + offset/4)); + return v; +} + +uint32 PPCRecompiler_getCurrentInstruction(ppcImlGenContext_t* ppcImlGenContext) +{ + uint32 v = CPU_swapEndianU32(*(ppcImlGenContext->currentInstruction)); + return v; +} + +uint32 PPCRecompiler_getPreviousInstruction(ppcImlGenContext_t* ppcImlGenContext) +{ + uint32 v = CPU_swapEndianU32(*(ppcImlGenContext->currentInstruction-1)); + return v; +} + +char _tempOpcodename[32]; + +const char* PPCRecompiler_getOpcodeDebugName(PPCRecImlInstruction_t* iml) +{ + uint32 op = iml->operation; + if (op == PPCREC_IML_OP_ASSIGN) + return "MOV"; + else if (op == PPCREC_IML_OP_ADD) + return "ADD"; + else if (op == PPCREC_IML_OP_SUB) + return "SUB"; + else if (op == PPCREC_IML_OP_ADD_CARRY_UPDATE_CARRY) + return "ADDCSC"; + else if (op == PPCREC_IML_OP_OR) + return "OR"; + else if (op == PPCREC_IML_OP_AND) + return "AND"; + else if (op == PPCREC_IML_OP_XOR) + return "XOR"; + else if (op == PPCREC_IML_OP_LEFT_SHIFT) + return "LSH"; + else if (op == PPCREC_IML_OP_RIGHT_SHIFT) + return "RSH"; + else if (op == PPCREC_IML_OP_MULTIPLY_SIGNED) + return "MULS"; + else if (op == PPCREC_IML_OP_DIVIDE_SIGNED) + return "DIVS"; + + sprintf(_tempOpcodename, "OP0%02x_T%d", iml->operation, iml->type); + return _tempOpcodename; +} + +void PPCRecDebug_addRegisterParam(StringBuf& strOutput, sint32 virtualRegister, bool isLast = false) +{ + if (isLast) + { + if (virtualRegister < 10) + strOutput.addFmt("t{} ", virtualRegister); + else + strOutput.addFmt("t{}", virtualRegister); + return; + } + if (virtualRegister < 10) + strOutput.addFmt("t{} , ", virtualRegister); + else + strOutput.addFmt("t{}, ", virtualRegister); +} + +void PPCRecDebug_addS32Param(StringBuf& strOutput, sint32 val, bool isLast = false) +{ + if (isLast) + { + strOutput.addFmt("0x{:08x}", val); + return; + } + strOutput.addFmt("0x{:08x}, ", val); +} + +void PPCRecompilerDebug_printLivenessRangeInfo(StringBuf& currentLineText, PPCRecImlSegment_t* imlSegment, sint32 offset) +{ + // pad to 70 characters + sint32 index = currentLineText.getLen(); + while (index < 70) + { + debug_printf(" "); + index++; + } + raLivenessSubrange_t* subrangeItr = imlSegment->raInfo.linkedList_allSubranges; + while (subrangeItr) + { + if (offset == subrangeItr->start.index) + { + if (false)//subrange->isDirtied && i == subrange->becomesDirtyAtIndex.index) + { + debug_printf("*%-2d", subrangeItr->range->virtualRegister); + } + else + { + debug_printf("|%-2d", subrangeItr->range->virtualRegister); + } + } + else if (false)//subrange->isDirtied && i == subrange->becomesDirtyAtIndex.index ) + { + debug_printf("* "); + } + else if (offset >= subrangeItr->start.index && offset < subrangeItr->end.index) + { + debug_printf("| "); + } + else + { + debug_printf(" "); + } + index += 3; + // next + subrangeItr = subrangeItr->link_segmentSubrangesGPR.next; + } +} + +void PPCRecompiler_dumpIMLSegment(PPCRecImlSegment_t* imlSegment, sint32 segmentIndex, bool printLivenessRangeInfo) +{ + StringBuf strOutput(1024); + + strOutput.addFmt("SEGMENT 0x{:04x} 0x{:08x} PPC 0x{:08x} - 0x{:08x} Loop-depth {}", segmentIndex, imlSegment->ppcAddress, imlSegment->ppcAddrMin, imlSegment->ppcAddrMax, imlSegment->loopDepth); + if (imlSegment->isEnterable) + { + strOutput.addFmt(" ENTERABLE (0x{:08x})", imlSegment->enterPPCAddress); + } + else if( imlSegment->isJumpDestination ) + { + strOutput.addFmt(" JUMP-DEST (0x{:08x})", imlSegment->jumpDestinationPPCAddress); + } + + debug_printf("%s\n", strOutput.c_str()); + + strOutput.reset(); + strOutput.addFmt("SEGMENT NAME 0x{:016x}", (uintptr_t)imlSegment); + debug_printf("%s", strOutput.c_str()); + + if (printLivenessRangeInfo) + { + PPCRecompilerDebug_printLivenessRangeInfo(strOutput, imlSegment, RA_INTER_RANGE_START); + } + debug_printf("\n"); + + sint32 lineOffsetParameters = 18; + + for(sint32 i=0; i<imlSegment->imlListCount; i++) + { + // don't log NOP instructions unless they have an associated PPC address + if(imlSegment->imlList[i].type == PPCREC_IML_TYPE_NO_OP && imlSegment->imlList[i].associatedPPCAddress == MPTR_NULL) + continue; + strOutput.reset(); + strOutput.addFmt("{:08x} ", imlSegment->imlList[i].associatedPPCAddress); + if( imlSegment->imlList[i].type == PPCREC_IML_TYPE_R_NAME || imlSegment->imlList[i].type == PPCREC_IML_TYPE_NAME_R) + { + if(imlSegment->imlList[i].type == PPCREC_IML_TYPE_R_NAME) + strOutput.add("LD_NAME"); + else + strOutput.add("ST_NAME"); + while ((sint32)strOutput.getLen() < lineOffsetParameters) + strOutput.add(" "); + + PPCRecDebug_addRegisterParam(strOutput, imlSegment->imlList[i].op_r_name.registerIndex); + + strOutput.addFmt("name_{} (", imlSegment->imlList[i].op_r_name.registerIndex, imlSegment->imlList[i].op_r_name.name); + if( imlSegment->imlList[i].op_r_name.name >= PPCREC_NAME_R0 && imlSegment->imlList[i].op_r_name.name < (PPCREC_NAME_R0+999) ) + { + strOutput.addFmt("r{}", imlSegment->imlList[i].op_r_name.name-PPCREC_NAME_R0); + } + else if( imlSegment->imlList[i].op_r_name.name >= PPCREC_NAME_SPR0 && imlSegment->imlList[i].op_r_name.name < (PPCREC_NAME_SPR0+999) ) + { + strOutput.addFmt("spr{}", imlSegment->imlList[i].op_r_name.name-PPCREC_NAME_SPR0); + } + else + strOutput.add("ukn"); + strOutput.add(")"); + } + else if( imlSegment->imlList[i].type == PPCREC_IML_TYPE_R_R ) + { + strOutput.addFmt("{}", PPCRecompiler_getOpcodeDebugName(imlSegment->imlList+i)); + while ((sint32)strOutput.getLen() < lineOffsetParameters) + strOutput.add(" "); + PPCRecDebug_addRegisterParam(strOutput, imlSegment->imlList[i].op_r_r.registerResult); + PPCRecDebug_addRegisterParam(strOutput, imlSegment->imlList[i].op_r_r.registerA, true); + + if( imlSegment->imlList[i].crRegister != PPC_REC_INVALID_REGISTER ) + { + strOutput.addFmt(" -> CR{}", imlSegment->imlList[i].crRegister); + } + } + else if( imlSegment->imlList[i].type == PPCREC_IML_TYPE_R_R_R ) + { + strOutput.addFmt("{}", PPCRecompiler_getOpcodeDebugName(imlSegment->imlList + i)); + while ((sint32)strOutput.getLen() < lineOffsetParameters) + strOutput.add(" "); + PPCRecDebug_addRegisterParam(strOutput, imlSegment->imlList[i].op_r_r_r.registerResult); + PPCRecDebug_addRegisterParam(strOutput, imlSegment->imlList[i].op_r_r_r.registerA); + PPCRecDebug_addRegisterParam(strOutput, imlSegment->imlList[i].op_r_r_r.registerB, true); + if( imlSegment->imlList[i].crRegister != PPC_REC_INVALID_REGISTER ) + { + strOutput.addFmt(" -> CR{}", imlSegment->imlList[i].crRegister); + } + } + else if (imlSegment->imlList[i].type == PPCREC_IML_TYPE_R_R_S32) + { + strOutput.addFmt("{}", PPCRecompiler_getOpcodeDebugName(imlSegment->imlList + i)); + while ((sint32)strOutput.getLen() < lineOffsetParameters) + strOutput.add(" "); + + PPCRecDebug_addRegisterParam(strOutput, imlSegment->imlList[i].op_r_r_s32.registerResult); + PPCRecDebug_addRegisterParam(strOutput, imlSegment->imlList[i].op_r_r_s32.registerA); + PPCRecDebug_addS32Param(strOutput, imlSegment->imlList[i].op_r_r_s32.immS32, true); + + if (imlSegment->imlList[i].crRegister != PPC_REC_INVALID_REGISTER) + { + strOutput.addFmt(" -> CR{}", imlSegment->imlList[i].crRegister); + } + } + else if (imlSegment->imlList[i].type == PPCREC_IML_TYPE_R_S32) + { + strOutput.addFmt("{}", PPCRecompiler_getOpcodeDebugName(imlSegment->imlList + i)); + while ((sint32)strOutput.getLen() < lineOffsetParameters) + strOutput.add(" "); + + PPCRecDebug_addRegisterParam(strOutput, imlSegment->imlList[i].op_r_immS32.registerIndex); + PPCRecDebug_addS32Param(strOutput, imlSegment->imlList[i].op_r_immS32.immS32, true); + + if (imlSegment->imlList[i].crRegister != PPC_REC_INVALID_REGISTER) + { + strOutput.addFmt(" -> CR{}", imlSegment->imlList[i].crRegister); + } + } + else if( imlSegment->imlList[i].type == PPCREC_IML_TYPE_JUMPMARK ) + { + strOutput.addFmt("jm_{:08x}:", imlSegment->imlList[i].op_jumpmark.address); + } + else if( imlSegment->imlList[i].type == PPCREC_IML_TYPE_PPC_ENTER ) + { + strOutput.addFmt("ppcEnter_{:08x}:", imlSegment->imlList[i].op_ppcEnter.ppcAddress); + } + else if(imlSegment->imlList[i].type == PPCREC_IML_TYPE_LOAD || imlSegment->imlList[i].type == PPCREC_IML_TYPE_STORE || + imlSegment->imlList[i].type == PPCREC_IML_TYPE_LOAD_INDEXED || imlSegment->imlList[i].type == PPCREC_IML_TYPE_STORE_INDEXED ) + { + if(imlSegment->imlList[i].type == PPCREC_IML_TYPE_LOAD || imlSegment->imlList[i].type == PPCREC_IML_TYPE_LOAD_INDEXED) + strOutput.add("LD_"); + else + strOutput.add("ST_"); + + if (imlSegment->imlList[i].op_storeLoad.flags2.signExtend) + strOutput.add("S"); + else + strOutput.add("U"); + strOutput.addFmt("{}", imlSegment->imlList[i].op_storeLoad.copyWidth); + + while ((sint32)strOutput.getLen() < lineOffsetParameters) + strOutput.add(" "); + + PPCRecDebug_addRegisterParam(strOutput, imlSegment->imlList[i].op_storeLoad.registerData); + + if(imlSegment->imlList[i].type == PPCREC_IML_TYPE_LOAD_INDEXED || imlSegment->imlList[i].type == PPCREC_IML_TYPE_STORE_INDEXED) + strOutput.addFmt("[t{}+t{}]", imlSegment->imlList[i].op_storeLoad.registerMem, imlSegment->imlList[i].op_storeLoad.registerMem2); + else + strOutput.addFmt("[t{}+{}]", imlSegment->imlList[i].op_storeLoad.registerMem, imlSegment->imlList[i].op_storeLoad.immS32); + } + else if (imlSegment->imlList[i].type == PPCREC_IML_TYPE_MEM2MEM) + { + strOutput.addFmt("{} [t{}+{}] = [t{}+{}]", imlSegment->imlList[i].op_mem2mem.copyWidth, imlSegment->imlList[i].op_mem2mem.dst.registerMem, imlSegment->imlList[i].op_mem2mem.dst.immS32, imlSegment->imlList[i].op_mem2mem.src.registerMem, imlSegment->imlList[i].op_mem2mem.src.immS32); + } + else if( imlSegment->imlList[i].type == PPCREC_IML_TYPE_CJUMP ) + { + if (imlSegment->imlList[i].op_conditionalJump.condition == PPCREC_JUMP_CONDITION_E) + strOutput.add("JE"); + else if (imlSegment->imlList[i].op_conditionalJump.condition == PPCREC_JUMP_CONDITION_NE) + strOutput.add("JNE"); + else if (imlSegment->imlList[i].op_conditionalJump.condition == PPCREC_JUMP_CONDITION_G) + strOutput.add("JG"); + else if (imlSegment->imlList[i].op_conditionalJump.condition == PPCREC_JUMP_CONDITION_GE) + strOutput.add("JGE"); + else if (imlSegment->imlList[i].op_conditionalJump.condition == PPCREC_JUMP_CONDITION_L) + strOutput.add("JL"); + else if (imlSegment->imlList[i].op_conditionalJump.condition == PPCREC_JUMP_CONDITION_LE) + strOutput.add("JLE"); + else if (imlSegment->imlList[i].op_conditionalJump.condition == PPCREC_JUMP_CONDITION_NONE) + strOutput.add("JALW"); // jump always + else + cemu_assert_unimplemented(); + strOutput.addFmt(" jm_{:08x} (cr{})", imlSegment->imlList[i].op_conditionalJump.jumpmarkAddress, imlSegment->imlList[i].crRegister); + } + else if( imlSegment->imlList[i].type == PPCREC_IML_TYPE_NO_OP ) + { + strOutput.add("NOP"); + } + else if( imlSegment->imlList[i].type == PPCREC_IML_TYPE_MACRO ) + { + if( imlSegment->imlList[i].operation == PPCREC_IML_MACRO_BLR ) + { + strOutput.addFmt("MACRO BLR 0x{:08x} cycles (depr): {}", imlSegment->imlList[i].op_macro.param, (sint32)imlSegment->imlList[i].op_macro.paramU16); + } + else if( imlSegment->imlList[i].operation == PPCREC_IML_MACRO_BLRL ) + { + strOutput.addFmt("MACRO BLRL 0x{:08x} cycles (depr): {}", imlSegment->imlList[i].op_macro.param, (sint32)imlSegment->imlList[i].op_macro.paramU16); + } + else if( imlSegment->imlList[i].operation == PPCREC_IML_MACRO_BCTR ) + { + strOutput.addFmt("MACRO BCTR 0x{:08x} cycles (depr): {}", imlSegment->imlList[i].op_macro.param, (sint32)imlSegment->imlList[i].op_macro.paramU16); + } + else if( imlSegment->imlList[i].operation == PPCREC_IML_MACRO_BCTRL ) + { + strOutput.addFmt("MACRO BCTRL 0x{:08x} cycles (depr): {}", imlSegment->imlList[i].op_macro.param, (sint32)imlSegment->imlList[i].op_macro.paramU16); + } + else if( imlSegment->imlList[i].operation == PPCREC_IML_MACRO_BL ) + { + strOutput.addFmt("MACRO BL 0x{:08x} -> 0x{:08x} cycles (depr): {}", imlSegment->imlList[i].op_macro.param, imlSegment->imlList[i].op_macro.param2, (sint32)imlSegment->imlList[i].op_macro.paramU16); + } + else if( imlSegment->imlList[i].operation == PPCREC_IML_MACRO_B_FAR ) + { + strOutput.addFmt("MACRO B_FAR 0x{:08x} -> 0x{:08x} cycles (depr): {}", imlSegment->imlList[i].op_macro.param, imlSegment->imlList[i].op_macro.param2, (sint32)imlSegment->imlList[i].op_macro.paramU16); + } + else if( imlSegment->imlList[i].operation == PPCREC_IML_MACRO_LEAVE ) + { + strOutput.addFmt("MACRO LEAVE ppc: 0x{:08x}", imlSegment->imlList[i].op_macro.param); + } + else if( imlSegment->imlList[i].operation == PPCREC_IML_MACRO_HLE ) + { + strOutput.addFmt("MACRO HLE ppcAddr: 0x{:08x} funcId: 0x{:08x}", imlSegment->imlList[i].op_macro.param, imlSegment->imlList[i].op_macro.param2); + } + else if( imlSegment->imlList[i].operation == PPCREC_IML_MACRO_MFTB ) + { + strOutput.addFmt("MACRO MFTB ppcAddr: 0x{:08x} sprId: 0x{:08x}", imlSegment->imlList[i].op_macro.param, imlSegment->imlList[i].op_macro.param2); + } + else if( imlSegment->imlList[i].operation == PPCREC_IML_MACRO_COUNT_CYCLES ) + { + strOutput.addFmt("MACRO COUNT_CYCLES cycles: {}", imlSegment->imlList[i].op_macro.param); + } + else + { + strOutput.addFmt("MACRO ukn operation {}", imlSegment->imlList[i].operation); + } + } + else if( imlSegment->imlList[i].type == PPCREC_IML_TYPE_FPR_R_NAME ) + { + strOutput.addFmt("fpr_t{} = name_{} (", imlSegment->imlList[i].op_r_name.registerIndex, imlSegment->imlList[i].op_r_name.name); + if( imlSegment->imlList[i].op_r_name.name >= PPCREC_NAME_FPR0 && imlSegment->imlList[i].op_r_name.name < (PPCREC_NAME_FPR0+999) ) + { + strOutput.addFmt("fpr{}", imlSegment->imlList[i].op_r_name.name-PPCREC_NAME_FPR0); + } + else if( imlSegment->imlList[i].op_r_name.name >= PPCREC_NAME_TEMPORARY_FPR0 && imlSegment->imlList[i].op_r_name.name < (PPCREC_NAME_TEMPORARY_FPR0+999) ) + { + strOutput.addFmt("tempFpr{}", imlSegment->imlList[i].op_r_name.name-PPCREC_NAME_TEMPORARY_FPR0); + } + else + strOutput.add("ukn"); + strOutput.add(")"); + } + else if( imlSegment->imlList[i].type == PPCREC_IML_TYPE_FPR_NAME_R ) + { + strOutput.addFmt("name_{} (", imlSegment->imlList[i].op_r_name.name); + if( imlSegment->imlList[i].op_r_name.name >= PPCREC_NAME_FPR0 && imlSegment->imlList[i].op_r_name.name < (PPCREC_NAME_FPR0+999) ) + { + strOutput.addFmt("fpr{}", imlSegment->imlList[i].op_r_name.name-PPCREC_NAME_FPR0); + } + else if( imlSegment->imlList[i].op_r_name.name >= PPCREC_NAME_TEMPORARY_FPR0 && imlSegment->imlList[i].op_r_name.name < (PPCREC_NAME_TEMPORARY_FPR0+999) ) + { + strOutput.addFmt("tempFpr{}", imlSegment->imlList[i].op_r_name.name-PPCREC_NAME_TEMPORARY_FPR0); + } + else + strOutput.add("ukn"); + strOutput.addFmt(") = fpr_t{}", imlSegment->imlList[i].op_r_name.registerIndex); + } + else if( imlSegment->imlList[i].type == PPCREC_IML_TYPE_FPR_LOAD ) + { + strOutput.addFmt("fpr_t{} = ", imlSegment->imlList[i].op_storeLoad.registerData); + if( imlSegment->imlList[i].op_storeLoad.flags2.signExtend ) + strOutput.add("S"); + else + strOutput.add("U"); + strOutput.addFmt("{} [t{}+{}] mode {}", imlSegment->imlList[i].op_storeLoad.copyWidth / 8, imlSegment->imlList[i].op_storeLoad.registerMem, imlSegment->imlList[i].op_storeLoad.immS32, imlSegment->imlList[i].op_storeLoad.mode); + if (imlSegment->imlList[i].op_storeLoad.flags2.notExpanded) + { + strOutput.addFmt(" <No expand>"); + } + } + else if( imlSegment->imlList[i].type == PPCREC_IML_TYPE_FPR_STORE ) + { + if( imlSegment->imlList[i].op_storeLoad.flags2.signExtend ) + strOutput.add("S"); + else + strOutput.add("U"); + strOutput.addFmt("{} [t{}+{}]", imlSegment->imlList[i].op_storeLoad.copyWidth/8, imlSegment->imlList[i].op_storeLoad.registerMem, imlSegment->imlList[i].op_storeLoad.immS32); + strOutput.addFmt("= fpr_t{} mode {}\n", imlSegment->imlList[i].op_storeLoad.registerData, imlSegment->imlList[i].op_storeLoad.mode); + } + else if( imlSegment->imlList[i].type == PPCREC_IML_TYPE_FPR_R_R ) + { + strOutput.addFmt("{:-6} ", PPCRecompiler_getOpcodeDebugName(&imlSegment->imlList[i])); + strOutput.addFmt("fpr{:02d}, fpr{:02d}", imlSegment->imlList[i].op_fpr_r_r.registerResult, imlSegment->imlList[i].op_fpr_r_r.registerOperand); + } + else if( imlSegment->imlList[i].type == PPCREC_IML_TYPE_FPR_R_R_R_R ) + { + strOutput.addFmt("{:-6} ", PPCRecompiler_getOpcodeDebugName(&imlSegment->imlList[i])); + strOutput.addFmt("fpr{:02d}, fpr{:02d}, fpr{:02d}, fpr{:02d}", imlSegment->imlList[i].op_fpr_r_r_r_r.registerResult, imlSegment->imlList[i].op_fpr_r_r_r_r.registerOperandA, imlSegment->imlList[i].op_fpr_r_r_r_r.registerOperandB, imlSegment->imlList[i].op_fpr_r_r_r_r.registerOperandC); + } + else if( imlSegment->imlList[i].type == PPCREC_IML_TYPE_FPR_R_R_R ) + { + strOutput.addFmt("{:-6} ", PPCRecompiler_getOpcodeDebugName(&imlSegment->imlList[i])); + strOutput.addFmt("fpr{:02d}, fpr{:02d}, fpr{:02d}", imlSegment->imlList[i].op_fpr_r_r_r.registerResult, imlSegment->imlList[i].op_fpr_r_r_r.registerOperandA, imlSegment->imlList[i].op_fpr_r_r_r.registerOperandB); + } + else if (imlSegment->imlList[i].type == PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK) + { + strOutput.addFmt("CYCLE_CHECK jm_{:08x}\n", imlSegment->imlList[i].op_conditionalJump.jumpmarkAddress); + } + else if (imlSegment->imlList[i].type == PPCREC_IML_TYPE_CONDITIONAL_R_S32) + { + strOutput.addFmt("t{} ", imlSegment->imlList[i].op_conditional_r_s32.registerIndex); + bool displayAsHex = false; + if (imlSegment->imlList[i].operation == PPCREC_IML_OP_ASSIGN) + { + displayAsHex = true; + strOutput.add("="); + } + else + strOutput.addFmt("(unknown operation CONDITIONAL_R_S32 {})", imlSegment->imlList[i].operation); + if (displayAsHex) + strOutput.addFmt(" 0x{:x}", imlSegment->imlList[i].op_conditional_r_s32.immS32); + else + strOutput.addFmt(" {}", imlSegment->imlList[i].op_conditional_r_s32.immS32); + strOutput.add(" (conditional)"); + if (imlSegment->imlList[i].crRegister != PPC_REC_INVALID_REGISTER) + { + strOutput.addFmt(" -> and update CR{}", imlSegment->imlList[i].crRegister); + } + } + else + { + strOutput.addFmt("Unknown iml type {}", imlSegment->imlList[i].type); + } + debug_printf("%s", strOutput.c_str()); + if (printLivenessRangeInfo) + { + PPCRecompilerDebug_printLivenessRangeInfo(strOutput, imlSegment, i); + } + debug_printf("\n"); + } + // all ranges + if (printLivenessRangeInfo) + { + debug_printf("Ranges-VirtReg "); + raLivenessSubrange_t* subrangeItr = imlSegment->raInfo.linkedList_allSubranges; + while(subrangeItr) + { + debug_printf("v%-2d", subrangeItr->range->virtualRegister); + subrangeItr = subrangeItr->link_segmentSubrangesGPR.next; + } + debug_printf("\n"); + debug_printf("Ranges-PhysReg "); + subrangeItr = imlSegment->raInfo.linkedList_allSubranges; + while (subrangeItr) + { + debug_printf("p%-2d", subrangeItr->range->physicalRegister); + subrangeItr = subrangeItr->link_segmentSubrangesGPR.next; + } + debug_printf("\n"); + } + // branch info + debug_printf("Links from: "); + for (sint32 i = 0; i < imlSegment->list_prevSegments.size(); i++) + { + if (i) + debug_printf(", "); + debug_printf("%p", (void*)imlSegment->list_prevSegments[i]); + } + debug_printf("\n"); + debug_printf("Links to: "); + if (imlSegment->nextSegmentBranchNotTaken) + debug_printf("%p (no branch), ", (void*)imlSegment->nextSegmentBranchNotTaken); + if (imlSegment->nextSegmentBranchTaken) + debug_printf("%p (branch)", (void*)imlSegment->nextSegmentBranchTaken); + debug_printf("\n"); +} + +void PPCRecompiler_dumpIML(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext) +{ + for(sint32 f=0; f<ppcImlGenContext->segmentListCount; f++) + { + PPCRecImlSegment_t* imlSegment = ppcImlGenContext->segmentList[f]; + PPCRecompiler_dumpIMLSegment(imlSegment, f); + debug_printf("\n"); + } +} + +void PPCRecompilerIml_setSegmentPoint(ppcRecompilerSegmentPoint_t* segmentPoint, PPCRecImlSegment_t* imlSegment, sint32 index) +{ + segmentPoint->imlSegment = imlSegment; + segmentPoint->index = index; + if (imlSegment->segmentPointList) + imlSegment->segmentPointList->prev = segmentPoint; + segmentPoint->prev = nullptr; + segmentPoint->next = imlSegment->segmentPointList; + imlSegment->segmentPointList = segmentPoint; +} + +void PPCRecompilerIml_removeSegmentPoint(ppcRecompilerSegmentPoint_t* segmentPoint) +{ + if (segmentPoint->prev) + segmentPoint->prev->next = segmentPoint->next; + else + segmentPoint->imlSegment->segmentPointList = segmentPoint->next; + if (segmentPoint->next) + segmentPoint->next->prev = segmentPoint->prev; +} + +/* +* Insert multiple no-op instructions +* Warning: Can invalidate any previous instruction structs from the same segment +*/ +void PPCRecompiler_pushBackIMLInstructions(PPCRecImlSegment_t* imlSegment, sint32 index, sint32 shiftBackCount) +{ + cemu_assert(index >= 0 && index <= imlSegment->imlListCount); + + if (imlSegment->imlListCount + shiftBackCount > imlSegment->imlListSize) + { + sint32 newSize = imlSegment->imlListCount + shiftBackCount + std::max(2, imlSegment->imlListSize/2); + imlSegment->imlList = (PPCRecImlInstruction_t*)realloc(imlSegment->imlList, sizeof(PPCRecImlInstruction_t)*newSize); + imlSegment->imlListSize = newSize; + } + for (sint32 i = (sint32)imlSegment->imlListCount - 1; i >= index; i--) + { + memcpy(imlSegment->imlList + (i + shiftBackCount), imlSegment->imlList + i, sizeof(PPCRecImlInstruction_t)); + } + // fill empty space with NOP instructions + for (sint32 i = 0; i < shiftBackCount; i++) + { + imlSegment->imlList[index + i].type = PPCREC_IML_TYPE_NONE; + } + imlSegment->imlListCount += shiftBackCount; + + if (imlSegment->segmentPointList) + { + ppcRecompilerSegmentPoint_t* segmentPoint = imlSegment->segmentPointList; + while (segmentPoint) + { + if (segmentPoint->index != RA_INTER_RANGE_START && segmentPoint->index != RA_INTER_RANGE_END) + { + if (segmentPoint->index >= index) + segmentPoint->index += shiftBackCount; + } + // next + segmentPoint = segmentPoint->next; + } + } +} + +/* +* Insert and return new instruction at index +* Warning: Can invalidate any previous instruction structs from the same segment +*/ +PPCRecImlInstruction_t* PPCRecompiler_insertInstruction(PPCRecImlSegment_t* imlSegment, sint32 index) +{ + PPCRecompiler_pushBackIMLInstructions(imlSegment, index, 1); + return imlSegment->imlList + index; +} + +/* +* Append and return new instruction at the end of the segment +* Warning: Can invalidate any previous instruction structs from the same segment +*/ +PPCRecImlInstruction_t* PPCRecompiler_appendInstruction(PPCRecImlSegment_t* imlSegment) +{ + sint32 index = imlSegment->imlListCount; + if (index >= imlSegment->imlListSize) + { + sint32 newSize = index+1; + imlSegment->imlList = (PPCRecImlInstruction_t*)realloc(imlSegment->imlList, sizeof(PPCRecImlInstruction_t)*newSize); + imlSegment->imlListSize = newSize; + } + imlSegment->imlListCount++; + memset(imlSegment->imlList + index, 0, sizeof(PPCRecImlInstruction_t)); + return imlSegment->imlList + index; +} + +void PPCRecompilerIml_insertSegments(ppcImlGenContext_t* ppcImlGenContext, sint32 index, sint32 count) +{ + if( (ppcImlGenContext->segmentListCount+count) > ppcImlGenContext->segmentListSize ) + { + // allocate space for more segments + ppcImlGenContext->segmentListSize += count; + ppcImlGenContext->segmentList = (PPCRecImlSegment_t**)realloc(ppcImlGenContext->segmentList, ppcImlGenContext->segmentListSize*sizeof(PPCRecImlSegment_t*)); + } + for(sint32 i=(sint32)ppcImlGenContext->segmentListCount-1; i>=index; i--) + { + memcpy(ppcImlGenContext->segmentList+(i+count), ppcImlGenContext->segmentList+i, sizeof(PPCRecImlSegment_t*)); + } + ppcImlGenContext->segmentListCount += count; + for(sint32 i=0; i<count; i++) + { + //memset(ppcImlGenContext->segmentList+index+i, 0x00, sizeof(PPCRecImlSegment_t*)); + ppcImlGenContext->segmentList[index+i] = (PPCRecImlSegment_t*)malloc(sizeof(PPCRecImlSegment_t)); + memset(ppcImlGenContext->segmentList[index+i], 0x00, sizeof(PPCRecImlSegment_t)); + ppcImlGenContext->segmentList[index + i]->list_prevSegments = std::vector<PPCRecImlSegment_t*>(); + } +} + +/* + * Allocate and init a new iml instruction segment + */ +PPCRecImlSegment_t* PPCRecompiler_generateImlSegment(ppcImlGenContext_t* ppcImlGenContext) +{ + if( ppcImlGenContext->segmentListCount >= ppcImlGenContext->segmentListSize ) + { + // allocate space for more segments + ppcImlGenContext->segmentListSize *= 2; + ppcImlGenContext->segmentList = (PPCRecImlSegment_t**)realloc(ppcImlGenContext->segmentList, ppcImlGenContext->segmentListSize*sizeof(PPCRecImlSegment_t*)); + } + PPCRecImlSegment_t* ppcRecSegment = new PPCRecImlSegment_t(); + ppcImlGenContext->segmentList[ppcImlGenContext->segmentListCount] = ppcRecSegment; + ppcImlGenContext->segmentListCount++; + return ppcRecSegment; +} + +void PPCRecompiler_freeContext(ppcImlGenContext_t* ppcImlGenContext) +{ + if (ppcImlGenContext->imlList) + { + free(ppcImlGenContext->imlList); + ppcImlGenContext->imlList = nullptr; + } + for(sint32 i=0; i<ppcImlGenContext->segmentListCount; i++) + { + free(ppcImlGenContext->segmentList[i]->imlList); + delete ppcImlGenContext->segmentList[i]; + } + ppcImlGenContext->segmentListCount = 0; + if (ppcImlGenContext->segmentList) + { + free(ppcImlGenContext->segmentList); + ppcImlGenContext->segmentList = nullptr; + } +} + +bool PPCRecompiler_isSuffixInstruction(PPCRecImlInstruction_t* iml) +{ + if (iml->type == PPCREC_IML_TYPE_MACRO && (iml->operation == PPCREC_IML_MACRO_BLR || iml->operation == PPCREC_IML_MACRO_BCTR) || + iml->type == PPCREC_IML_TYPE_MACRO && iml->operation == PPCREC_IML_MACRO_BL || + iml->type == PPCREC_IML_TYPE_MACRO && iml->operation == PPCREC_IML_MACRO_B_FAR || + iml->type == PPCREC_IML_TYPE_MACRO && iml->operation == PPCREC_IML_MACRO_BLRL || + iml->type == PPCREC_IML_TYPE_MACRO && iml->operation == PPCREC_IML_MACRO_BCTRL || + iml->type == PPCREC_IML_TYPE_MACRO && iml->operation == PPCREC_IML_MACRO_LEAVE || + iml->type == PPCREC_IML_TYPE_MACRO && iml->operation == PPCREC_IML_MACRO_HLE || + iml->type == PPCREC_IML_TYPE_MACRO && iml->operation == PPCREC_IML_MACRO_MFTB || + iml->type == PPCREC_IML_TYPE_PPC_ENTER || + iml->type == PPCREC_IML_TYPE_CJUMP || + iml->type == PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK) + return true; + return false; +} + +bool PPCRecompiler_decodePPCInstruction(ppcImlGenContext_t* ppcImlGenContext) +{ + bool unsupportedInstructionFound = false; + + uint32 opcode = PPCRecompiler_iterateCurrentInstruction(ppcImlGenContext); + switch ((opcode >> 26)) + { + case 1: + if (PPCRecompilerImlGen_HLE(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 4: // opcode category - paired single + switch (PPC_getBits(opcode, 30, 5)) + { + case 0: // subcategory compare + switch (PPC_getBits(opcode, 25, 5)) + { + case 0: + PPCRecompilerImlGen_PS_CMPU0(ppcImlGenContext, opcode); + ppcImlGenContext->hasFPUInstruction = true; + break; + case 1: + PPCRecompilerImlGen_PS_CMPO0(ppcImlGenContext, opcode); + ppcImlGenContext->hasFPUInstruction = true; + break; + case 2: + PPCRecompilerImlGen_PS_CMPU1(ppcImlGenContext, opcode); + ppcImlGenContext->hasFPUInstruction = true; + break; + default: + unsupportedInstructionFound = true; + break; + } + break; + case 8: //Sub category - move/negate + switch (PPC_getBits(opcode, 25, 5)) + { + case 1: // PS negate + if (PPCRecompilerImlGen_PS_NEG(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 2: // PS move register + if (PPCRecompilerImlGen_PS_MR(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 8: // PS abs + if (PPCRecompilerImlGen_PS_ABS(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + default: + unsupportedInstructionFound = true; + break; + } + break; + case 10: + if (PPCRecompilerImlGen_PS_SUM0(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 11: + if (PPCRecompilerImlGen_PS_SUM1(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 12: // multiply scalar + if (PPCRecompilerImlGen_PS_MULS0(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 13: // multiply scalar + if (PPCRecompilerImlGen_PS_MULS1(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 14: // multiply add scalar + if (PPCRecompilerImlGen_PS_MADDS0(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 15: // multiply add scalar + if (PPCRecompilerImlGen_PS_MADDS1(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 16: // sub category - merge + switch (PPC_getBits(opcode, 25, 5)) + { + case 16: + if (PPCRecompilerImlGen_PS_MERGE00(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 17: + if (PPCRecompilerImlGen_PS_MERGE01(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 18: + if (PPCRecompilerImlGen_PS_MERGE10(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 19: + if (PPCRecompilerImlGen_PS_MERGE11(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + default: + unsupportedInstructionFound = true; + break; + } + break; + case 18: // divide paired + if (PPCRecompilerImlGen_PS_DIV(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 20: // sub paired + if (PPCRecompilerImlGen_PS_SUB(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 21: // add paired + if (PPCRecompilerImlGen_PS_ADD(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 23: // select paired + if (PPCRecompilerImlGen_PS_SEL(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 25: // multiply paired + if (PPCRecompilerImlGen_PS_MUL(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 24: // reciprocal paired + if (PPCRecompilerImlGen_PS_RES(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 26: // reciprocal squareroot paired + if (PPCRecompilerImlGen_PS_RSQRTE(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 28: // multiply sub paired + if (PPCRecompilerImlGen_PS_MSUB(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 29: // multiply add paired + if (PPCRecompilerImlGen_PS_MADD(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 30: // negative multiply sub paired + if (PPCRecompilerImlGen_PS_NMSUB(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 31: // negative multiply add paired + if (PPCRecompilerImlGen_PS_NMADD(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + default: + unsupportedInstructionFound = true; + break; + } + break; + case 7: // MULLI + PPCRecompilerImlGen_MULLI(ppcImlGenContext, opcode); + break; + case 8: // SUBFIC + PPCRecompilerImlGen_SUBFIC(ppcImlGenContext, opcode); + break; + case 10: // CMPLI + PPCRecompilerImlGen_CMPLI(ppcImlGenContext, opcode); + break; + case 11: // CMPI + PPCRecompilerImlGen_CMPI(ppcImlGenContext, opcode); + break; + case 12: // ADDIC + if (PPCRecompilerImlGen_ADDIC(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 13: // ADDIC. + if (PPCRecompilerImlGen_ADDIC_(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 14: // ADDI + if (PPCRecompilerImlGen_ADDI(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 15: // ADDIS + if (PPCRecompilerImlGen_ADDIS(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 16: // BC + if (PPCRecompilerImlGen_BC(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 17: + if (PPC_getBits(opcode, 30, 1) == 1) + { + // SC -> no-op + } + else + { + unsupportedInstructionFound = true; + } + break; + case 18: // B + if (PPCRecompilerImlGen_B(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 19: // opcode category 19 + switch (PPC_getBits(opcode, 30, 10)) + { + case 16: + if (PPCRecompilerImlGen_BCLR(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 129: + if (PPCRecompilerImlGen_CRANDC(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 150: + if (PPCRecompilerImlGen_ISYNC(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 193: + if (PPCRecompilerImlGen_CRXOR(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 257: + if (PPCRecompilerImlGen_CRAND(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 289: + if (PPCRecompilerImlGen_CREQV(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 417: + if (PPCRecompilerImlGen_CRORC(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 449: + if (PPCRecompilerImlGen_CROR(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 528: + if (PPCRecompilerImlGen_BCCTR(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + default: + unsupportedInstructionFound = true; + break; + } + break; + case 20: + if (PPCRecompilerImlGen_RLWIMI(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 21: + if (PPCRecompilerImlGen_RLWINM(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 23: + if (PPCRecompilerImlGen_RLWNM(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 24: + PPCRecompilerImlGen_ORI(ppcImlGenContext, opcode); + break; + case 25: + PPCRecompilerImlGen_ORIS(ppcImlGenContext, opcode); + break; + case 26: + PPCRecompilerImlGen_XORI(ppcImlGenContext, opcode); + break; + case 27: + PPCRecompilerImlGen_XORIS(ppcImlGenContext, opcode); + break; + case 28: + PPCRecompilerImlGen_ANDI(ppcImlGenContext, opcode); + break; + case 29: + PPCRecompilerImlGen_ANDIS(ppcImlGenContext, opcode); + break; + case 31: // opcode category + switch (PPC_getBits(opcode, 30, 10)) + { + case 0: + PPCRecompilerImlGen_CMP(ppcImlGenContext, opcode); + break; + case 4: + PPCRecompilerImlGen_TW(ppcImlGenContext, opcode); + break; + case 8: + // todo: Check if we can optimize this pattern: + // SUBFC + SUBFE + // SUBFC + if (PPCRecompilerImlGen_SUBFC(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 10: + if (PPCRecompilerImlGen_ADDC(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 11: + if (PPCRecompilerImlGen_MULHWU(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 19: + if (PPCRecompilerImlGen_MFCR(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 20: + if (PPCRecompilerImlGen_LWARX(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 23: + if (PPCRecompilerImlGen_LWZX(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 24: + if (PPCRecompilerImlGen_SLW(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 26: + if (PPCRecompilerImlGen_CNTLZW(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 28: + if (PPCRecompilerImlGen_AND(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 32: + PPCRecompilerImlGen_CMPL(ppcImlGenContext, opcode); + break; + case 40: + if (PPCRecompilerImlGen_SUBF(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 54: + // DBCST - Generates no code + break; + case 55: + if (PPCRecompilerImlGen_LWZUX(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 60: + if (PPCRecompilerImlGen_ANDC(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 75: + if (PPCRecompilerImlGen_MULHW(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 86: + // DCBF -> No-Op + break; + case 87: + if (PPCRecompilerImlGen_LBZX(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 104: + if (PPCRecompilerImlGen_NEG(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 119: + if (PPCRecompilerImlGen_LBZUX(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 124: + if (PPCRecompilerImlGen_NOR(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 136: + if (PPCRecompilerImlGen_SUBFE(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 138: + if (PPCRecompilerImlGen_ADDE(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 144: + PPCRecompilerImlGen_MTCRF(ppcImlGenContext, opcode); + break; + case 150: + if (PPCRecompilerImlGen_STWCX(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 151: + if (PPCRecompilerImlGen_STORE_INDEXED(ppcImlGenContext, opcode, 32) == false) + unsupportedInstructionFound = true; + break; + case 183: + if (PPCRecompilerImlGen_STORE_INDEXED_UPDATE(ppcImlGenContext, opcode, 32) == false) + unsupportedInstructionFound = true; + break; + case 200: + if (PPCRecompilerImlGen_SUBFZE(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 202: + if (PPCRecompilerImlGen_ADDZE(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 215: + if (PPCRecompilerImlGen_STORE_INDEXED(ppcImlGenContext, opcode, 8) == false) + unsupportedInstructionFound = true; + break; + case 234: + if (PPCRecompilerImlGen_ADDME(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 235: + if (PPCRecompilerImlGen_MULLW(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 247: + if (PPCRecompilerImlGen_STORE_INDEXED_UPDATE(ppcImlGenContext, opcode, 8) == false) + unsupportedInstructionFound = true; + break; + case 266: + if (PPCRecompilerImlGen_ADD(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 279: + if (PPCRecompilerImlGen_LHZX(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 284: + PPCRecompilerImlGen_EQV(ppcImlGenContext, opcode); + break; + case 311: + if (PPCRecompilerImlGen_LHZUX(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 316: + if (PPCRecompilerImlGen_XOR(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 339: + if (PPCRecompilerImlGen_MFSPR(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 343: + if (PPCRecompilerImlGen_LHAX(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 371: + if (PPCRecompilerImlGen_MFTB(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 375: + if (PPCRecompilerImlGen_LHAUX(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 407: + if (PPCRecompilerImlGen_STORE_INDEXED(ppcImlGenContext, opcode, 16) == false) + unsupportedInstructionFound = true; + break; + case 412: + if (PPCRecompilerImlGen_ORC(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 439: + if (PPCRecompilerImlGen_STORE_INDEXED_UPDATE(ppcImlGenContext, opcode, 16) == false) + unsupportedInstructionFound = true; + break; + case 444: + if (PPCRecompilerImlGen_OR(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 459: + PPCRecompilerImlGen_DIVWU(ppcImlGenContext, opcode); + break; + case 467: + if (PPCRecompilerImlGen_MTSPR(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 491: + if (PPCRecompilerImlGen_DIVW(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 534: + if (PPCRecompilerImlGen_LWBRX(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 535: + if (PPCRecompilerImlGen_LFSX(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 536: + if (PPCRecompilerImlGen_SRW(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 567: + if (PPCRecompilerImlGen_LFSUX(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 597: + if (PPCRecompilerImlGen_LSWI(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 598: + PPCRecompilerImlGen_SYNC(ppcImlGenContext, opcode); + break; + case 599: + if (PPCRecompilerImlGen_LFDX(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 631: + if (PPCRecompilerImlGen_LFDUX(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 662: + if (PPCRecompilerImlGen_STWBRX(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 663: + if (PPCRecompilerImlGen_STFSX(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 695: + if (PPCRecompilerImlGen_STFSUX(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 725: + if (PPCRecompilerImlGen_STSWI(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 727: + if (PPCRecompilerImlGen_STFDX(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 790: + PPCRecompilerImlGen_LHBRX(ppcImlGenContext, opcode); + break; + case 792: + if (PPCRecompilerImlGen_SRAW(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 824: + if (PPCRecompilerImlGen_SRAWI(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 918: // STHBRX + if (PPCRecompilerImlGen_STORE_INDEXED(ppcImlGenContext, opcode, 16, true) == false) + unsupportedInstructionFound = true; + break; + case 922: + if (PPCRecompilerImlGen_EXTSH(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 954: + if (PPCRecompilerImlGen_EXTSB(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 983: + if (PPCRecompilerImlGen_STFIWX(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + case 1014: + if (PPCRecompilerImlGen_DCBZ(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + break; + default: + unsupportedInstructionFound = true; + break; + } + break; + case 32: + PPCRecompilerImlGen_LWZ(ppcImlGenContext, opcode); + break; + case 33: + PPCRecompilerImlGen_LWZU(ppcImlGenContext, opcode); + break; + case 34: + PPCRecompilerImlGen_LBZ(ppcImlGenContext, opcode); + break; + case 35: + PPCRecompilerImlGen_LBZU(ppcImlGenContext, opcode); + break; + case 36: + PPCRecompilerImlGen_STW(ppcImlGenContext, opcode); + break; + case 37: + PPCRecompilerImlGen_STWU(ppcImlGenContext, opcode); + break; + case 38: + PPCRecompilerImlGen_STB(ppcImlGenContext, opcode); + break; + case 39: + PPCRecompilerImlGen_STBU(ppcImlGenContext, opcode); + break; + case 40: + PPCRecompilerImlGen_LHZ(ppcImlGenContext, opcode); + break; + case 41: + PPCRecompilerImlGen_LHZU(ppcImlGenContext, opcode); + break; + case 42: + PPCRecompilerImlGen_LHA(ppcImlGenContext, opcode); + break; + case 43: + PPCRecompilerImlGen_LHAU(ppcImlGenContext, opcode); + break; + case 44: + PPCRecompilerImlGen_STH(ppcImlGenContext, opcode); + break; + case 45: + PPCRecompilerImlGen_STHU(ppcImlGenContext, opcode); + break; + case 46: + PPCRecompilerImlGen_LMW(ppcImlGenContext, opcode); + break; + case 47: + PPCRecompilerImlGen_STMW(ppcImlGenContext, opcode); + break; + case 48: + if (PPCRecompilerImlGen_LFS(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 49: + if (PPCRecompilerImlGen_LFSU(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 50: + if (PPCRecompilerImlGen_LFD(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 51: + if (PPCRecompilerImlGen_LFDU(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 52: + if (PPCRecompilerImlGen_STFS(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 53: + if (PPCRecompilerImlGen_STFSU(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 54: + if (PPCRecompilerImlGen_STFD(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 55: + if (PPCRecompilerImlGen_STFDU(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 56: + if (PPCRecompilerImlGen_PSQ_L(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 57: + if (PPCRecompilerImlGen_PSQ_LU(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 59: // opcode category + switch (PPC_getBits(opcode, 30, 5)) + { + case 18: + if (PPCRecompilerImlGen_FDIVS(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 20: + if (PPCRecompilerImlGen_FSUBS(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 21: + if (PPCRecompilerImlGen_FADDS(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 24: + if (PPCRecompilerImlGen_FRES(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 25: + if (PPCRecompilerImlGen_FMULS(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 28: + if (PPCRecompilerImlGen_FMSUBS(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 29: + if (PPCRecompilerImlGen_FMADDS(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 30: + if (PPCRecompilerImlGen_FNMSUBS(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + default: + unsupportedInstructionFound = true; + break; + } + break; + case 60: + if (PPCRecompilerImlGen_PSQ_ST(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 61: + if (PPCRecompilerImlGen_PSQ_STU(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 63: // opcode category + switch (PPC_getBits(opcode, 30, 5)) + { + case 0: + if (PPCRecompilerImlGen_FCMPU(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 12: + if (PPCRecompilerImlGen_FRSP(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 15: + if (PPCRecompilerImlGen_FCTIWZ(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 18: + if (PPCRecompilerImlGen_FDIV(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 20: + if (PPCRecompilerImlGen_FSUB(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 21: + if (PPCRecompilerImlGen_FADD(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 23: + if (PPCRecompilerImlGen_FSEL(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 25: + if (PPCRecompilerImlGen_FMUL(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 26: + if (PPCRecompilerImlGen_FRSQRTE(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 28: + if (PPCRecompilerImlGen_FMSUB(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 29: + if (PPCRecompilerImlGen_FMADD(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 30: + if (PPCRecompilerImlGen_FNMSUB(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + default: + switch (PPC_getBits(opcode, 30, 10)) + { + case 32: + if (PPCRecompilerImlGen_FCMPO(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 40: + if (PPCRecompilerImlGen_FNEG(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 72: + if (PPCRecompilerImlGen_FMR(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 136: + if (PPCRecompilerImlGen_FNABS(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + case 264: + if (PPCRecompilerImlGen_FABS(ppcImlGenContext, opcode) == false) + unsupportedInstructionFound = true; + ppcImlGenContext->hasFPUInstruction = true; + break; + default: + unsupportedInstructionFound = true; + break; + } + break; + } + break; + default: + unsupportedInstructionFound = true; + break; + } + return unsupportedInstructionFound; +} + +bool PPCRecompiler_generateIntermediateCode(ppcImlGenContext_t& ppcImlGenContext, PPCRecFunction_t* ppcRecFunc, std::set<uint32>& entryAddresses) +{ + //ppcImlGenContext_t ppcImlGenContext = { 0 }; + ppcImlGenContext.functionRef = ppcRecFunc; + // add entire range + ppcRecRange_t recRange; + recRange.ppcAddress = ppcRecFunc->ppcAddress; + recRange.ppcSize = ppcRecFunc->ppcSize; + ppcRecFunc->list_ranges.push_back(recRange); + // process ppc instructions + ppcImlGenContext.currentInstruction = (uint32*)memory_getPointerFromVirtualOffset(ppcRecFunc->ppcAddress); + bool unsupportedInstructionFound = false; + sint32 numPPCInstructions = ppcRecFunc->ppcSize/4; + sint32 unsupportedInstructionCount = 0; + uint32 unsupportedInstructionLastOffset = 0; + uint32* firstCurrentInstruction = ppcImlGenContext.currentInstruction; + uint32* endCurrentInstruction = ppcImlGenContext.currentInstruction + numPPCInstructions; + + while(ppcImlGenContext.currentInstruction < endCurrentInstruction) + { + uint32 addressOfCurrentInstruction = (uint32)((uint8*)ppcImlGenContext.currentInstruction - memory_base); + ppcImlGenContext.ppcAddressOfCurrentInstruction = addressOfCurrentInstruction; + ppcImlGenContext.cyclesSinceLastBranch++; + PPCRecompilerImlGen_generateNewInstruction_jumpmark(&ppcImlGenContext, addressOfCurrentInstruction); + + if (entryAddresses.find(addressOfCurrentInstruction) != entryAddresses.end()) + { + // add PPCEnter for addresses that are in entryAddresses + PPCRecompilerImlGen_generateNewInstruction_ppcEnter(&ppcImlGenContext, addressOfCurrentInstruction); + } + else if(ppcImlGenContext.currentInstruction != firstCurrentInstruction) + { + // add PPCEnter mark if code is seemingly unreachable (for example if between two unconditional jump instructions without jump goal) + uint32 opcodeCurrent = PPCRecompiler_getCurrentInstruction(&ppcImlGenContext); + uint32 opcodePrevious = PPCRecompiler_getPreviousInstruction(&ppcImlGenContext); + if( ((opcodePrevious>>26) == 18) && ((opcodeCurrent>>26) == 18) ) + { + // between two B(L) instructions + // todo: for BL only if they are not inlineable + + bool canInlineFunction = false; + if ((opcodePrevious & PPC_OPC_LK) && (opcodePrevious & PPC_OPC_AA) == 0) + { + uint32 li; + PPC_OPC_TEMPL_I(opcodePrevious, li); + sint32 inlineSize = 0; + if (PPCRecompiler_canInlineFunction(li + addressOfCurrentInstruction - 4, &inlineSize)) + canInlineFunction = true; + } + if( canInlineFunction == false && (opcodePrevious & PPC_OPC_LK) == false) + PPCRecompilerImlGen_generateNewInstruction_ppcEnter(&ppcImlGenContext, addressOfCurrentInstruction); + } + if( ((opcodePrevious>>26) == 19) && PPC_getBits(opcodePrevious, 30, 10) == 528 ) + { + uint32 BO, BI, BD; + PPC_OPC_TEMPL_XL(opcodePrevious, BO, BI, BD); + if( (BO & 16) && (opcodePrevious&PPC_OPC_LK) == 0 ) + { + // after unconditional BCTR instruction + PPCRecompilerImlGen_generateNewInstruction_ppcEnter(&ppcImlGenContext, addressOfCurrentInstruction); + } + } + } + + unsupportedInstructionFound = PPCRecompiler_decodePPCInstruction(&ppcImlGenContext); + if( unsupportedInstructionFound ) + { + unsupportedInstructionCount++; + unsupportedInstructionLastOffset = ppcImlGenContext.ppcAddressOfCurrentInstruction; + unsupportedInstructionFound = false; + //break; + } + } + ppcImlGenContext.ppcAddressOfCurrentInstruction = 0; // reset current instruction offset (any future generated IML instruction will be assigned to ppc address 0) + if( unsupportedInstructionCount > 0 || unsupportedInstructionFound ) + { + // could not compile function + debug_printf("Failed recompile due to unknown instruction at 0x%08x\n", unsupportedInstructionLastOffset); + PPCRecompiler_freeContext(&ppcImlGenContext); + return false; + } + // optimize unused jumpmarks away + // first, flag all jumpmarks as unused + std::map<uint32, PPCRecImlInstruction_t*> map_jumpMarks; + for(sint32 i=0; i<ppcImlGenContext.imlListCount; i++) + { + if( ppcImlGenContext.imlList[i].type == PPCREC_IML_TYPE_JUMPMARK ) + { + ppcImlGenContext.imlList[i].op_jumpmark.flags |= PPCREC_IML_OP_FLAG_UNUSED; +#ifndef PUBLIC_RELEASE + if (map_jumpMarks.find(ppcImlGenContext.imlList[i].op_jumpmark.address) != map_jumpMarks.end()) + assert_dbg(); +#endif + map_jumpMarks.emplace(ppcImlGenContext.imlList[i].op_jumpmark.address, ppcImlGenContext.imlList+i); + } + } + // second, unflag jumpmarks that have at least one reference + for(sint32 i=0; i<ppcImlGenContext.imlListCount; i++) + { + if( ppcImlGenContext.imlList[i].type == PPCREC_IML_TYPE_CJUMP ) + { + uint32 jumpDest = ppcImlGenContext.imlList[i].op_conditionalJump.jumpmarkAddress; + auto jumpMarkIml = map_jumpMarks.find(jumpDest); + if (jumpMarkIml != map_jumpMarks.end()) + jumpMarkIml->second->op_jumpmark.flags &= ~PPCREC_IML_OP_FLAG_UNUSED; + } + } + // lastly, remove jumpmarks that still have the unused flag set + sint32 currentImlIndex = 0; + for(sint32 i=0; i<ppcImlGenContext.imlListCount; i++) + { + if( ppcImlGenContext.imlList[i].type == PPCREC_IML_TYPE_JUMPMARK && (ppcImlGenContext.imlList[i].op_jumpmark.flags&PPCREC_IML_OP_FLAG_UNUSED) ) + { + continue; // skip this instruction + } + // move back instruction + if( currentImlIndex < i ) + { + memcpy(ppcImlGenContext.imlList+currentImlIndex, ppcImlGenContext.imlList+i, sizeof(PPCRecImlInstruction_t)); + } + currentImlIndex++; + } + // fix intermediate instruction count + ppcImlGenContext.imlListCount = currentImlIndex; + // divide iml instructions into segments + // each segment is defined by one or more instructions with no branches or jump destinations in between + // a branch instruction may only be the very last instruction of a segment + ppcImlGenContext.segmentListCount = 0; + ppcImlGenContext.segmentListSize = 2; + ppcImlGenContext.segmentList = (PPCRecImlSegment_t**)malloc(ppcImlGenContext.segmentListSize*sizeof(PPCRecImlSegment_t*)); + sint32 segmentStart = 0; + sint32 segmentImlIndex = 0; + while( segmentImlIndex < ppcImlGenContext.imlListCount ) + { + bool genNewSegment = false; + // segment definition: + // If we encounter a branch instruction -> end of segment after current instruction + // If we encounter a jumpmark -> end of segment before current instruction + // If we encounter ppc_enter -> end of segment before current instruction + if( ppcImlGenContext.imlList[segmentImlIndex].type == PPCREC_IML_TYPE_CJUMP || + (ppcImlGenContext.imlList[segmentImlIndex].type == PPCREC_IML_TYPE_MACRO && (ppcImlGenContext.imlList[segmentImlIndex].operation == PPCREC_IML_MACRO_BLR || ppcImlGenContext.imlList[segmentImlIndex].operation == PPCREC_IML_MACRO_BLRL || ppcImlGenContext.imlList[segmentImlIndex].operation == PPCREC_IML_MACRO_BCTR || ppcImlGenContext.imlList[segmentImlIndex].operation == PPCREC_IML_MACRO_BCTRL)) || + (ppcImlGenContext.imlList[segmentImlIndex].type == PPCREC_IML_TYPE_MACRO && (ppcImlGenContext.imlList[segmentImlIndex].operation == PPCREC_IML_MACRO_BL)) || + (ppcImlGenContext.imlList[segmentImlIndex].type == PPCREC_IML_TYPE_MACRO && (ppcImlGenContext.imlList[segmentImlIndex].operation == PPCREC_IML_MACRO_B_FAR)) || + (ppcImlGenContext.imlList[segmentImlIndex].type == PPCREC_IML_TYPE_MACRO && (ppcImlGenContext.imlList[segmentImlIndex].operation == PPCREC_IML_MACRO_LEAVE)) || + (ppcImlGenContext.imlList[segmentImlIndex].type == PPCREC_IML_TYPE_MACRO && (ppcImlGenContext.imlList[segmentImlIndex].operation == PPCREC_IML_MACRO_HLE)) || + (ppcImlGenContext.imlList[segmentImlIndex].type == PPCREC_IML_TYPE_MACRO && (ppcImlGenContext.imlList[segmentImlIndex].operation == PPCREC_IML_MACRO_MFTB)) ) + { + // segment ends after current instruction + PPCRecImlSegment_t* ppcRecSegment = PPCRecompiler_generateImlSegment(&ppcImlGenContext); + ppcRecSegment->startOffset = segmentStart; + ppcRecSegment->count = segmentImlIndex-segmentStart+1; + ppcRecSegment->ppcAddress = 0xFFFFFFFF; + segmentStart = segmentImlIndex+1; + } + else if( ppcImlGenContext.imlList[segmentImlIndex].type == PPCREC_IML_TYPE_JUMPMARK || + ppcImlGenContext.imlList[segmentImlIndex].type == PPCREC_IML_TYPE_PPC_ENTER ) + { + // segment ends before current instruction + if( segmentImlIndex > segmentStart ) + { + PPCRecImlSegment_t* ppcRecSegment = PPCRecompiler_generateImlSegment(&ppcImlGenContext); + ppcRecSegment->startOffset = segmentStart; + ppcRecSegment->count = segmentImlIndex-segmentStart; + ppcRecSegment->ppcAddress = 0xFFFFFFFF; + segmentStart = segmentImlIndex; + } + } + segmentImlIndex++; + } + if( segmentImlIndex != segmentStart ) + { + // final segment + PPCRecImlSegment_t* ppcRecSegment = PPCRecompiler_generateImlSegment(&ppcImlGenContext); + ppcRecSegment->startOffset = segmentStart; + ppcRecSegment->count = segmentImlIndex-segmentStart; + ppcRecSegment->ppcAddress = 0xFFFFFFFF; + segmentStart = segmentImlIndex; + } + // move iml instructions into the segments + for(sint32 s=0; s<ppcImlGenContext.segmentListCount; s++) + { + uint32 imlStartIndex = ppcImlGenContext.segmentList[s]->startOffset; + uint32 imlCount = ppcImlGenContext.segmentList[s]->count; + if( imlCount > 0 ) + { + ppcImlGenContext.segmentList[s]->imlListSize = imlCount + 4; + ppcImlGenContext.segmentList[s]->imlList = (PPCRecImlInstruction_t*)malloc(sizeof(PPCRecImlInstruction_t)*ppcImlGenContext.segmentList[s]->imlListSize); + ppcImlGenContext.segmentList[s]->imlListCount = imlCount; + memcpy(ppcImlGenContext.segmentList[s]->imlList, ppcImlGenContext.imlList+imlStartIndex, sizeof(PPCRecImlInstruction_t)*imlCount); + } + else + { + // empty segments are allowed so we can handle multiple PPC entry addresses pointing to the same code + ppcImlGenContext.segmentList[s]->imlList = NULL; + ppcImlGenContext.segmentList[s]->imlListSize = 0; + ppcImlGenContext.segmentList[s]->imlListCount = 0; + } + ppcImlGenContext.segmentList[s]->startOffset = 9999999; + ppcImlGenContext.segmentList[s]->count = 9999999; + } + // clear segment-independent iml list + free(ppcImlGenContext.imlList); + ppcImlGenContext.imlList = NULL; + ppcImlGenContext.imlListCount = 999999; // set to high number to force crash in case old code still uses ppcImlGenContext.imlList + // calculate PPC address of each segment based on iml instructions inside that segment (we need this info to calculate how many cpu cycles each segment takes) + for(sint32 s=0; s<ppcImlGenContext.segmentListCount; s++) + { + uint32 segmentPPCAddrMin = 0xFFFFFFFF; + uint32 segmentPPCAddrMax = 0x00000000; + for(sint32 i=0; i<ppcImlGenContext.segmentList[s]->imlListCount; i++) + { + if( ppcImlGenContext.segmentList[s]->imlList[i].associatedPPCAddress == 0 ) + continue; + //if( ppcImlGenContext.segmentList[s]->imlList[i].type == PPCREC_IML_TYPE_JUMPMARK || ppcImlGenContext.segmentList[s]->imlList[i].type == PPCREC_IML_TYPE_NO_OP ) + // continue; // jumpmarks and no-op instructions must not affect segment ppc address range + segmentPPCAddrMin = std::min(ppcImlGenContext.segmentList[s]->imlList[i].associatedPPCAddress, segmentPPCAddrMin); + segmentPPCAddrMax = std::max(ppcImlGenContext.segmentList[s]->imlList[i].associatedPPCAddress, segmentPPCAddrMax); + } + if( segmentPPCAddrMin != 0xFFFFFFFF ) + { + ppcImlGenContext.segmentList[s]->ppcAddrMin = segmentPPCAddrMin; + ppcImlGenContext.segmentList[s]->ppcAddrMax = segmentPPCAddrMax; + } + else + { + ppcImlGenContext.segmentList[s]->ppcAddrMin = 0; + ppcImlGenContext.segmentList[s]->ppcAddrMax = 0; + } + } + // certain instructions can change the segment state + // ppcEnter instruction marks a segment as enterable (BL, BCTR, etc. instructions can enter at this location from outside) + // jumpmarks mark the segment as a jump destination (within the same function) + for(sint32 s=0; s<ppcImlGenContext.segmentListCount; s++) + { + while( ppcImlGenContext.segmentList[s]->imlListCount > 0 ) + { + if( ppcImlGenContext.segmentList[s]->imlList[0].type == PPCREC_IML_TYPE_PPC_ENTER ) + { + // mark segment as enterable + if( ppcImlGenContext.segmentList[s]->isEnterable ) + assert_dbg(); // should not happen? + ppcImlGenContext.segmentList[s]->isEnterable = true; + ppcImlGenContext.segmentList[s]->enterPPCAddress = ppcImlGenContext.segmentList[s]->imlList[0].op_ppcEnter.ppcAddress; + // remove ppc_enter instruction + ppcImlGenContext.segmentList[s]->imlList[0].type = PPCREC_IML_TYPE_NO_OP; + ppcImlGenContext.segmentList[s]->imlList[0].crRegister = PPC_REC_INVALID_REGISTER; + ppcImlGenContext.segmentList[s]->imlList[0].associatedPPCAddress = 0; + } + else if( ppcImlGenContext.segmentList[s]->imlList[0].type == PPCREC_IML_TYPE_JUMPMARK ) + { + // mark segment as jump destination + if( ppcImlGenContext.segmentList[s]->isJumpDestination ) + assert_dbg(); // should not happen? + ppcImlGenContext.segmentList[s]->isJumpDestination = true; + ppcImlGenContext.segmentList[s]->jumpDestinationPPCAddress = ppcImlGenContext.segmentList[s]->imlList[0].op_jumpmark.address; + // remove jumpmark instruction + ppcImlGenContext.segmentList[s]->imlList[0].type = PPCREC_IML_TYPE_NO_OP; + ppcImlGenContext.segmentList[s]->imlList[0].crRegister = PPC_REC_INVALID_REGISTER; + ppcImlGenContext.segmentList[s]->imlList[0].associatedPPCAddress = 0; + } + else + break; + } + } + // the first segment is always enterable as the recompiled functions entrypoint + ppcImlGenContext.segmentList[0]->isEnterable = true; + ppcImlGenContext.segmentList[0]->enterPPCAddress = ppcImlGenContext.functionRef->ppcAddress; + + // link segments for further inter-segment optimization + PPCRecompilerIML_linkSegments(&ppcImlGenContext); + + // optimization pass - replace segments with conditional MOVs if possible + for (sint32 s = 0; s < ppcImlGenContext.segmentListCount; s++) + { + PPCRecImlSegment_t* imlSegment = ppcImlGenContext.segmentList[s]; + if (imlSegment->nextSegmentBranchNotTaken == NULL || imlSegment->nextSegmentBranchTaken == NULL) + continue; // not a branching segment + PPCRecImlInstruction_t* lastInstruction = PPCRecompilerIML_getLastInstruction(imlSegment); + if (lastInstruction->type != PPCREC_IML_TYPE_CJUMP || lastInstruction->op_conditionalJump.crRegisterIndex != 0) + continue; + PPCRecImlSegment_t* conditionalSegment = imlSegment->nextSegmentBranchNotTaken; + PPCRecImlSegment_t* finalSegment = imlSegment->nextSegmentBranchTaken; + if(imlSegment->nextSegmentBranchTaken != imlSegment->nextSegmentBranchNotTaken->nextSegmentBranchNotTaken) + continue; + if (imlSegment->nextSegmentBranchNotTaken->imlListCount > 4) + continue; + if(conditionalSegment->list_prevSegments.size() != 1) + continue; // the reduced segment must not be the target of any other branch + if(conditionalSegment->isEnterable) + continue; + // check if the segment contains only iml instructions that can be turned into conditional moves (Value assignment, register assignment) + bool canReduceSegment = true; + for (sint32 f = 0; f < conditionalSegment->imlListCount; f++) + { + PPCRecImlInstruction_t* imlInstruction = conditionalSegment->imlList+f; + if( imlInstruction->type == PPCREC_IML_TYPE_R_S32 && imlInstruction->operation == PPCREC_IML_OP_ASSIGN) + continue; + // todo: Register to register copy + canReduceSegment = false; + break; + } + + if( canReduceSegment == false ) + continue; + + // remove the branch instruction + uint8 branchCond_crRegisterIndex = lastInstruction->op_conditionalJump.crRegisterIndex; + uint8 branchCond_crBitIndex = lastInstruction->op_conditionalJump.crBitIndex; + bool branchCond_bitMustBeSet = lastInstruction->op_conditionalJump.bitMustBeSet; + + PPCRecompilerImlGen_generateNewInstruction_noOp(&ppcImlGenContext, lastInstruction); + + // append conditional moves based on branch condition + for (sint32 f = 0; f < conditionalSegment->imlListCount; f++) + { + PPCRecImlInstruction_t* imlInstruction = conditionalSegment->imlList + f; + if (imlInstruction->type == PPCREC_IML_TYPE_R_S32 && imlInstruction->operation == PPCREC_IML_OP_ASSIGN) + PPCRecompilerImlGen_generateNewInstruction_conditional_r_s32(&ppcImlGenContext, PPCRecompiler_appendInstruction(imlSegment), PPCREC_IML_OP_ASSIGN, imlInstruction->op_r_immS32.registerIndex, imlInstruction->op_r_immS32.immS32, branchCond_crRegisterIndex, branchCond_crBitIndex, !branchCond_bitMustBeSet); + else + assert_dbg(); + } + // update segment links + // source segment: imlSegment, conditional/removed segment: conditionalSegment, final segment: finalSegment + PPCRecompilerIML_removeLink(imlSegment, conditionalSegment); + PPCRecompilerIML_removeLink(imlSegment, finalSegment); + PPCRecompilerIML_removeLink(conditionalSegment, finalSegment); + PPCRecompilerIml_setLinkBranchNotTaken(imlSegment, finalSegment); + // remove all instructions from conditional segment + conditionalSegment->imlListCount = 0; + + // if possible, merge imlSegment with finalSegment + if (finalSegment->isEnterable == false && finalSegment->list_prevSegments.size() == 1) + { + // todo: Clean this up and move into separate function PPCRecompilerIML_mergeSegments() + PPCRecompilerIML_removeLink(imlSegment, finalSegment); + if (finalSegment->nextSegmentBranchNotTaken) + { + PPCRecImlSegment_t* tempSegment = finalSegment->nextSegmentBranchNotTaken; + PPCRecompilerIML_removeLink(finalSegment, tempSegment); + PPCRecompilerIml_setLinkBranchNotTaken(imlSegment, tempSegment); + } + if (finalSegment->nextSegmentBranchTaken) + { + PPCRecImlSegment_t* tempSegment = finalSegment->nextSegmentBranchTaken; + PPCRecompilerIML_removeLink(finalSegment, tempSegment); + PPCRecompilerIml_setLinkBranchTaken(imlSegment, tempSegment); + } + // copy IML instructions + for (sint32 f = 0; f < finalSegment->imlListCount; f++) + { + memcpy(PPCRecompiler_appendInstruction(imlSegment), finalSegment->imlList + f, sizeof(PPCRecImlInstruction_t)); + } + finalSegment->imlListCount = 0; + + //PPCRecompiler_dumpIML(ppcRecFunc, &ppcImlGenContext); + } + + // todo: If possible, merge with the segment following conditionalSegment (merging is only possible if the segment is not an entry point or has no other jump sources) + } + + // insert cycle counter instruction in every segment that has a cycle count greater zero + for(sint32 s=0; s<ppcImlGenContext.segmentListCount; s++) + { + PPCRecImlSegment_t* imlSegment = ppcImlGenContext.segmentList[s]; + if( imlSegment->ppcAddrMin == 0 ) + continue; + // count number of PPC instructions in segment + // note: This algorithm correctly counts inlined functions but it doesn't count NO-OP instructions like ISYNC + uint32 lastPPCInstAddr = 0; + uint32 ppcCount2 = 0; + for (sint32 i = 0; i < imlSegment->imlListCount; i++) + { + if (imlSegment->imlList[i].associatedPPCAddress == 0) + continue; + if (imlSegment->imlList[i].associatedPPCAddress == lastPPCInstAddr) + continue; + lastPPCInstAddr = imlSegment->imlList[i].associatedPPCAddress; + ppcCount2++; + } + //uint32 ppcCount = imlSegment->ppcAddrMax-imlSegment->ppcAddrMin+4; -> No longer works with inlined functions + uint32 cycleCount = ppcCount2;// ppcCount / 4; + if( cycleCount > 0 ) + { + PPCRecompiler_pushBackIMLInstructions(imlSegment, 0, 1); + imlSegment->imlList[0].type = PPCREC_IML_TYPE_MACRO; + imlSegment->imlList[0].crRegister = PPC_REC_INVALID_REGISTER; + imlSegment->imlList[0].operation = PPCREC_IML_MACRO_COUNT_CYCLES; + imlSegment->imlList[0].op_macro.param = cycleCount; + } + } + + // find segments that have a (conditional) jump instruction that points in reverse direction of code flow + // for these segments there is a risk that the recompiler could get trapped in an infinite busy loop. + // todo: We should do a loop-detection prepass where we flag segments that are actually in a loop. We can then use this information below to avoid generating the scheduler-exit code for segments that aren't actually in a loop despite them referencing an earlier segment (which could be an exit segment for example) + uint32 currentLoopEscapeJumpMarker = 0xFF000000; // start in an area where no valid code can be located + for(sint32 s=0; s<ppcImlGenContext.segmentListCount; s++) + { + // todo: This currently uses segment->ppcAddrMin which isn't really reliable. (We already had a problem where function inlining would generate falsified segment ranges by omitting the branch instruction). Find a better solution (use jumpmark/enterable offsets?) + PPCRecImlSegment_t* imlSegment = ppcImlGenContext.segmentList[s]; + if( imlSegment->imlListCount == 0 ) + continue; + if (imlSegment->imlList[imlSegment->imlListCount - 1].type != PPCREC_IML_TYPE_CJUMP || imlSegment->imlList[imlSegment->imlListCount - 1].op_conditionalJump.jumpmarkAddress > imlSegment->ppcAddrMin) + continue; + if (imlSegment->imlList[imlSegment->imlListCount - 1].type != PPCREC_IML_TYPE_CJUMP || imlSegment->imlList[imlSegment->imlListCount - 1].op_conditionalJump.jumpAccordingToSegment) + continue; + // exclude non-infinite tight loops + if (PPCRecompilerImlAnalyzer_isTightFiniteLoop(imlSegment)) + continue; + // potential loop segment found, split this segment into four: + // P0: This segment checks if the remaining cycles counter is still above zero. If yes, it jumps to segment P2 (it's also the jump destination for other segments) + // P1: This segment consists only of a single ppc_leave instruction and is usually skipped. Register unload instructions are later inserted here. + // P2: This segment contains the iml instructions of the original segment + // PEntry: This segment is used to enter the function, it jumps to P0 + // All segments are considered to be part of the same PPC instruction range + // The first segment also retains the jump destination and enterable properties from the original segment. + //debug_printf("--- Insert cycle counter check ---\n"); + //PPCRecompiler_dumpIML(ppcRecFunc, &ppcImlGenContext); + + PPCRecompilerIml_insertSegments(&ppcImlGenContext, s, 2); + imlSegment = NULL; + PPCRecImlSegment_t* imlSegmentP0 = ppcImlGenContext.segmentList[s+0]; + PPCRecImlSegment_t* imlSegmentP1 = ppcImlGenContext.segmentList[s+1]; + PPCRecImlSegment_t* imlSegmentP2 = ppcImlGenContext.segmentList[s+2]; + // create entry point segment + PPCRecompilerIml_insertSegments(&ppcImlGenContext, ppcImlGenContext.segmentListCount, 1); + PPCRecImlSegment_t* imlSegmentPEntry = ppcImlGenContext.segmentList[ppcImlGenContext.segmentListCount-1]; + // relink segments + PPCRecompilerIML_relinkInputSegment(imlSegmentP2, imlSegmentP0); + PPCRecompilerIml_setLinkBranchNotTaken(imlSegmentP0, imlSegmentP1); + PPCRecompilerIml_setLinkBranchTaken(imlSegmentP0, imlSegmentP2); + PPCRecompilerIml_setLinkBranchTaken(imlSegmentPEntry, imlSegmentP0); + // update segments + uint32 enterPPCAddress = imlSegmentP2->ppcAddrMin; + if (imlSegmentP2->isEnterable) + enterPPCAddress = imlSegmentP2->enterPPCAddress; + imlSegmentP0->ppcAddress = 0xFFFFFFFF; + imlSegmentP1->ppcAddress = 0xFFFFFFFF; + imlSegmentP2->ppcAddress = 0xFFFFFFFF; + cemu_assert_debug(imlSegmentP2->ppcAddrMin != 0); + // move segment properties from segment P2 to segment P0 + imlSegmentP0->isJumpDestination = imlSegmentP2->isJumpDestination; + imlSegmentP0->jumpDestinationPPCAddress = imlSegmentP2->jumpDestinationPPCAddress; + imlSegmentP0->isEnterable = false; + //imlSegmentP0->enterPPCAddress = imlSegmentP2->enterPPCAddress; + imlSegmentP0->ppcAddrMin = imlSegmentP2->ppcAddrMin; + imlSegmentP0->ppcAddrMax = imlSegmentP2->ppcAddrMax; + imlSegmentP2->isJumpDestination = false; + imlSegmentP2->jumpDestinationPPCAddress = 0; + imlSegmentP2->isEnterable = false; + imlSegmentP2->enterPPCAddress = 0; + imlSegmentP2->ppcAddrMin = 0; + imlSegmentP2->ppcAddrMax = 0; + // setup enterable segment + if( enterPPCAddress != 0 && enterPPCAddress != 0xFFFFFFFF ) + { + imlSegmentPEntry->isEnterable = true; + imlSegmentPEntry->ppcAddress = enterPPCAddress; + imlSegmentPEntry->enterPPCAddress = enterPPCAddress; + } + // assign new jumpmark to segment P2 + imlSegmentP2->isJumpDestination = true; + imlSegmentP2->jumpDestinationPPCAddress = currentLoopEscapeJumpMarker; + currentLoopEscapeJumpMarker++; + // create ppc_leave instruction in segment P1 + PPCRecompiler_pushBackIMLInstructions(imlSegmentP1, 0, 1); + imlSegmentP1->imlList[0].type = PPCREC_IML_TYPE_MACRO; + imlSegmentP1->imlList[0].operation = PPCREC_IML_MACRO_LEAVE; + imlSegmentP1->imlList[0].crRegister = PPC_REC_INVALID_REGISTER; + imlSegmentP1->imlList[0].op_macro.param = imlSegmentP0->ppcAddrMin; + imlSegmentP1->imlList[0].associatedPPCAddress = imlSegmentP0->ppcAddrMin; + // create cycle-based conditional instruction in segment P0 + PPCRecompiler_pushBackIMLInstructions(imlSegmentP0, 0, 1); + imlSegmentP0->imlList[0].type = PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK; + imlSegmentP0->imlList[0].operation = 0; + imlSegmentP0->imlList[0].crRegister = PPC_REC_INVALID_REGISTER; + imlSegmentP0->imlList[0].op_conditionalJump.jumpmarkAddress = imlSegmentP2->jumpDestinationPPCAddress; + imlSegmentP0->imlList[0].associatedPPCAddress = imlSegmentP0->ppcAddrMin; + // jump instruction for PEntry + PPCRecompiler_pushBackIMLInstructions(imlSegmentPEntry, 0, 1); + PPCRecompilerImlGen_generateNewInstruction_jumpSegment(&ppcImlGenContext, imlSegmentPEntry->imlList + 0); + + // skip the newly created segments + s += 2; + } + + // isolate entry points from function flow (enterable segments must not be the target of any other segment) + // this simplifies logic during register allocation + PPCRecompilerIML_isolateEnterableSegments(&ppcImlGenContext); + + // if GQRs can be predicted, optimize PSQ load/stores + PPCRecompiler_optimizePSQLoadAndStore(&ppcImlGenContext); + + // count number of used registers + uint32 numLoadedFPRRegisters = 0; + for(uint32 i=0; i<255; i++) + { + if( ppcImlGenContext.mappedFPRRegister[i] ) + numLoadedFPRRegisters++; + } + + // insert name store instructions at the end of each segment but before branch instructions + for(sint32 s=0; s<ppcImlGenContext.segmentListCount; s++) + { + PPCRecImlSegment_t* imlSegment = ppcImlGenContext.segmentList[s]; + if( ppcImlGenContext.segmentList[s]->imlListCount == 0 ) + continue; // ignore empty segments + // analyze segment for register usage + PPCImlOptimizerUsedRegisters_t registersUsed; + for(sint32 i=0; i<imlSegment->imlListCount; i++) + { + PPCRecompiler_checkRegisterUsage(&ppcImlGenContext, imlSegment->imlList+i, ®istersUsed); + //PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, registersUsed.readGPR1); + sint32 accessedTempReg[5]; + // intermediate FPRs + accessedTempReg[0] = registersUsed.readFPR1; + accessedTempReg[1] = registersUsed.readFPR2; + accessedTempReg[2] = registersUsed.readFPR3; + accessedTempReg[3] = registersUsed.readFPR4; + accessedTempReg[4] = registersUsed.writtenFPR1; + for(sint32 f=0; f<5; f++) + { + if( accessedTempReg[f] == -1 ) + continue; + uint32 regName = ppcImlGenContext.mappedFPRRegister[accessedTempReg[f]]; + if( regName >= PPCREC_NAME_FPR0 && regName < PPCREC_NAME_FPR0+32 ) + { + imlSegment->ppcFPRUsed[regName - PPCREC_NAME_FPR0] = true; + } + } + } + } + + // merge certain float load+store patterns (must happen before FPR register remapping) + PPCRecompiler_optimizeDirectFloatCopies(&ppcImlGenContext); + // delay byte swapping for certain load+store patterns + PPCRecompiler_optimizeDirectIntegerCopies(&ppcImlGenContext); + + if (numLoadedFPRRegisters > 0) + { + if (PPCRecompiler_manageFPRRegisters(&ppcImlGenContext) == false) + { + PPCRecompiler_freeContext(&ppcImlGenContext); + return false; + } + } + + PPCRecompilerImm_allocateRegisters(&ppcImlGenContext); + + // remove redundant name load and store instructions + PPCRecompiler_reorderConditionModifyInstructions(&ppcImlGenContext); + PPCRecompiler_removeRedundantCRUpdates(&ppcImlGenContext); + return true; +} diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlGenFPU.cpp b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlGenFPU.cpp new file mode 100644 index 00000000..e30a9683 --- /dev/null +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlGenFPU.cpp @@ -0,0 +1,1926 @@ +#include "../Interpreter/PPCInterpreterInternal.h" +#include "PPCRecompiler.h" +#include "PPCRecompilerIml.h" +#include "Cafe/GameProfile/GameProfile.h" + +bool hasSSE1Support = true; +bool hasSSE2Support = true; +bool hasSSE3Support = true; +bool hasLZCNTSupport = false; +bool hasMOVBESupport = false; +bool hasBMI2Support = false; +bool hasAVXSupport = false; + +void PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory(ppcImlGenContext_t* ppcImlGenContext, uint8 registerDestination, uint8 registerMemory, sint32 immS32, uint32 mode, bool switchEndian, uint8 registerGQR = PPC_REC_INVALID_REGISTER) +{ + // load from memory + PPCRecImlInstruction_t* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); + imlInstruction->type = PPCREC_IML_TYPE_FPR_LOAD; + imlInstruction->crRegister = PPC_REC_INVALID_REGISTER; + imlInstruction->operation = 0; + imlInstruction->op_storeLoad.registerData = registerDestination; + imlInstruction->op_storeLoad.registerMem = registerMemory; + imlInstruction->op_storeLoad.registerGQR = registerGQR; + imlInstruction->op_storeLoad.immS32 = immS32; + imlInstruction->op_storeLoad.mode = mode; + imlInstruction->op_storeLoad.flags2.swapEndian = switchEndian; +} + +void PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory_indexed(ppcImlGenContext_t* ppcImlGenContext, uint8 registerDestination, uint8 registerMemory1, uint8 registerMemory2, uint32 mode, bool switchEndian, uint8 registerGQR = PPC_REC_INVALID_REGISTER) +{ + // load from memory + PPCRecImlInstruction_t* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); + imlInstruction->type = PPCREC_IML_TYPE_FPR_LOAD_INDEXED; + imlInstruction->crRegister = PPC_REC_INVALID_REGISTER; + imlInstruction->operation = 0; + imlInstruction->op_storeLoad.registerData = registerDestination; + imlInstruction->op_storeLoad.registerMem = registerMemory1; + imlInstruction->op_storeLoad.registerMem2 = registerMemory2; + imlInstruction->op_storeLoad.registerGQR = registerGQR; + imlInstruction->op_storeLoad.immS32 = 0; + imlInstruction->op_storeLoad.mode = mode; + imlInstruction->op_storeLoad.flags2.swapEndian = switchEndian; +} + +void PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r(ppcImlGenContext_t* ppcImlGenContext, uint8 registerSource, uint8 registerMemory, sint32 immS32, uint32 mode, bool switchEndian, uint8 registerGQR = PPC_REC_INVALID_REGISTER) +{ + // store to memory + PPCRecImlInstruction_t* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); + imlInstruction->type = PPCREC_IML_TYPE_FPR_STORE; + imlInstruction->crRegister = PPC_REC_INVALID_REGISTER; + imlInstruction->operation = 0; + imlInstruction->op_storeLoad.registerData = registerSource; + imlInstruction->op_storeLoad.registerMem = registerMemory; + imlInstruction->op_storeLoad.registerGQR = registerGQR; + imlInstruction->op_storeLoad.immS32 = immS32; + imlInstruction->op_storeLoad.mode = mode; + imlInstruction->op_storeLoad.flags2.swapEndian = switchEndian; +} + +void PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r_indexed(ppcImlGenContext_t* ppcImlGenContext, uint8 registerSource, uint8 registerMemory1, uint8 registerMemory2, sint32 immS32, uint32 mode, bool switchEndian, uint8 registerGQR = 0) +{ + // store to memory + PPCRecImlInstruction_t* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); + imlInstruction->type = PPCREC_IML_TYPE_FPR_STORE_INDEXED; + imlInstruction->crRegister = PPC_REC_INVALID_REGISTER; + imlInstruction->operation = 0; + imlInstruction->op_storeLoad.registerData = registerSource; + imlInstruction->op_storeLoad.registerMem = registerMemory1; + imlInstruction->op_storeLoad.registerMem2 = registerMemory2; + imlInstruction->op_storeLoad.registerGQR = registerGQR; + imlInstruction->op_storeLoad.immS32 = immS32; + imlInstruction->op_storeLoad.mode = mode; + imlInstruction->op_storeLoad.flags2.swapEndian = switchEndian; +} + +void PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext_t* ppcImlGenContext, sint32 operation, uint8 registerResult, uint8 registerOperand, sint32 crRegister=PPC_REC_INVALID_REGISTER) +{ + // fpr OP fpr + PPCRecImlInstruction_t* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); + imlInstruction->type = PPCREC_IML_TYPE_FPR_R_R; + imlInstruction->operation = operation; + imlInstruction->op_fpr_r_r.registerResult = registerResult; + imlInstruction->op_fpr_r_r.registerOperand = registerOperand; + imlInstruction->crRegister = crRegister; + imlInstruction->op_fpr_r_r.flags = 0; +} + +void PPCRecompilerImlGen_generateNewInstruction_fpr_r_r_r(ppcImlGenContext_t* ppcImlGenContext, sint32 operation, uint8 registerResult, uint8 registerOperand1, uint8 registerOperand2, sint32 crRegister=PPC_REC_INVALID_REGISTER) +{ + // fpr = OP (fpr,fpr) + PPCRecImlInstruction_t* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); + imlInstruction->type = PPCREC_IML_TYPE_FPR_R_R_R; + imlInstruction->operation = operation; + imlInstruction->op_fpr_r_r_r.registerResult = registerResult; + imlInstruction->op_fpr_r_r_r.registerOperandA = registerOperand1; + imlInstruction->op_fpr_r_r_r.registerOperandB = registerOperand2; + imlInstruction->crRegister = crRegister; + imlInstruction->op_fpr_r_r_r.flags = 0; +} + +void PPCRecompilerImlGen_generateNewInstruction_fpr_r_r_r_r(ppcImlGenContext_t* ppcImlGenContext, sint32 operation, uint8 registerResult, uint8 registerOperandA, uint8 registerOperandB, uint8 registerOperandC, sint32 crRegister=PPC_REC_INVALID_REGISTER) +{ + // fpr = OP (fpr,fpr,fpr) + PPCRecImlInstruction_t* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); + imlInstruction->type = PPCREC_IML_TYPE_FPR_R_R_R_R; + imlInstruction->operation = operation; + imlInstruction->op_fpr_r_r_r_r.registerResult = registerResult; + imlInstruction->op_fpr_r_r_r_r.registerOperandA = registerOperandA; + imlInstruction->op_fpr_r_r_r_r.registerOperandB = registerOperandB; + imlInstruction->op_fpr_r_r_r_r.registerOperandC = registerOperandC; + imlInstruction->crRegister = crRegister; + imlInstruction->op_fpr_r_r_r_r.flags = 0; +} + +void PPCRecompilerImlGen_generateNewInstruction_fpr_r(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlInstruction_t* imlInstruction, sint32 operation, uint8 registerResult, sint32 crRegister) +{ + // OP (fpr) + if(imlInstruction == NULL) + imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); + imlInstruction->type = PPCREC_IML_TYPE_FPR_R; + imlInstruction->operation = operation; + imlInstruction->op_fpr_r.registerResult = registerResult; + imlInstruction->crRegister = crRegister; +} + +/* + * Rounds the bottom double to single precision (if single precision accuracy is emulated) + */ +void PPRecompilerImmGen_optionalRoundBottomFPRToSinglePrecision(ppcImlGenContext_t* ppcImlGenContext, uint32 fprRegister, bool flushDenormals=false) +{ + PPCRecompilerImlGen_generateNewInstruction_fpr_r(ppcImlGenContext, NULL, PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_BOTTOM, fprRegister); + if( flushDenormals ) + assert_dbg(); +} + +/* + * Rounds pair of doubles to single precision (if single precision accuracy is emulated) + */ +void PPRecompilerImmGen_optionalRoundPairFPRToSinglePrecision(ppcImlGenContext_t* ppcImlGenContext, uint32 fprRegister, bool flushDenormals=false) +{ + PPCRecompilerImlGen_generateNewInstruction_fpr_r(ppcImlGenContext, NULL, PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_PAIR, fprRegister); + if( flushDenormals ) + assert_dbg(); +} + +bool PPCRecompilerImlGen_LFS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + if( hasSSE1Support == false ) + return false; + sint32 rA, frD; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(opcode, frD, rA, imm); + // get memory gpr register index + uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + // get fpr register index + uint32 fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + if( ppcImlGenContext->LSQE ) + { + PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory(ppcImlGenContext, fprRegister, gprRegister, imm, PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0_PS1, true); + } + else + { + PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory(ppcImlGenContext, fprRegister, gprRegister, imm, PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0, true); + } + return true; +} + +bool PPCRecompilerImlGen_LFSU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + if( hasSSE1Support == false ) + return false; + sint32 rA, frD; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(opcode, frD, rA, imm); + // get memory gpr register index + uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + // add imm to memory register + PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_ADD, gprRegister, (sint32)imm, 0, false, false, PPC_REC_INVALID_REGISTER, 0); + // get fpr register index + uint32 fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + if( ppcImlGenContext->LSQE ) + { + PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory(ppcImlGenContext, fprRegister, gprRegister, 0, PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0_PS1, true); + } + else + { + PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory(ppcImlGenContext, fprRegister, gprRegister, 0, PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0, true); + } + return true; +} + +bool PPCRecompilerImlGen_LFSX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + if( hasSSE2Support == false ) + return false; + sint32 rA, frD, rB; + PPC_OPC_TEMPL_X(opcode, frD, rA, rB); + if( rA == 0 ) + { + debugBreakpoint(); + return false; + } + // get memory gpr registers + uint32 gprRegister1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + uint32 gprRegister2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + // get fpr register index + uint32 fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + if( ppcImlGenContext->LSQE ) + { + PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory_indexed(ppcImlGenContext, fprRegister, gprRegister1, gprRegister2, PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0_PS1, true); + } + else + { + PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory_indexed(ppcImlGenContext, fprRegister, gprRegister1, gprRegister2, PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0, true); + } + return true; +} + +bool PPCRecompilerImlGen_LFSUX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + if( hasSSE2Support == false ) + return false; + sint32 rA, frD, rB; + PPC_OPC_TEMPL_X(opcode, frD, rA, rB); + if( rA == 0 ) + { + debugBreakpoint(); + return false; + } + // get memory gpr registers + uint32 gprRegister1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + uint32 gprRegister2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + // add rB to rA (if rA != 0) + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ADD, gprRegister1, gprRegister2); + // get fpr register index + uint32 fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + if( ppcImlGenContext->LSQE ) + { + PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory(ppcImlGenContext, fprRegister, gprRegister1, 0, PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0_PS1, true); + } + else + { + PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory(ppcImlGenContext, fprRegister, gprRegister1, 0, PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0, true); + } + return true; +} + +bool PPCRecompilerImlGen_LFD(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + if( hasSSE1Support == false ) + return false; + sint32 rA, frD; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(opcode, frD, rA, imm); + if( rA == 0 ) + { + assert_dbg(); + } + // get memory gpr register index + uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + // get fpr register index + uint32 fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory(ppcImlGenContext, fprRegister, gprRegister, imm, PPCREC_FPR_LD_MODE_DOUBLE_INTO_PS0, true); + return true; +} + +bool PPCRecompilerImlGen_LFDU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + if( hasSSE1Support == false ) + return false; + sint32 rA, frD; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(opcode, frD, rA, imm); + if( rA == 0 ) + { + assert_dbg(); + } + // get memory gpr register index + uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + // add imm to memory register + PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_ADD, gprRegister, (sint32)imm, 0, false, false, PPC_REC_INVALID_REGISTER, 0); + // get fpr register index + uint32 fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + // emit load iml + PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory(ppcImlGenContext, fprRegister, gprRegister, 0, PPCREC_FPR_LD_MODE_DOUBLE_INTO_PS0, true); + return true; +} + +bool PPCRecompilerImlGen_LFDX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + if( hasSSE2Support == false ) + return false; + sint32 rA, frD, rB; + PPC_OPC_TEMPL_X(opcode, frD, rA, rB); + if( rA == 0 ) + { + debugBreakpoint(); + return false; + } + // get memory gpr registers + uint32 gprRegister1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + uint32 gprRegister2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + // get fpr register index + uint32 fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory_indexed(ppcImlGenContext, fprRegister, gprRegister1, gprRegister2, PPCREC_FPR_LD_MODE_DOUBLE_INTO_PS0, true); + return true; +} + +bool PPCRecompilerImlGen_LFDUX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + if( hasSSE2Support == false ) + return false; + sint32 rA, frD, rB; + PPC_OPC_TEMPL_X(opcode, frD, rA, rB); + if( rA == 0 ) + { + debugBreakpoint(); + return false; + } + // get memory gpr registers + uint32 gprRegister1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + uint32 gprRegister2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + // add rB to rA (if rA != 0) + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ADD, gprRegister1, gprRegister2); + // get fpr register index + uint32 fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory(ppcImlGenContext, fprRegister, gprRegister1, 0, PPCREC_FPR_LD_MODE_DOUBLE_INTO_PS0, true); + return true; +} + +bool PPCRecompilerImlGen_STFS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + if( hasSSE1Support == false ) + return false; + sint32 rA, frD; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(opcode, frD, rA, imm); + // get memory gpr register index + uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + // get fpr register index + uint32 fprRegister = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + + PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r(ppcImlGenContext, fprRegister, gprRegister, imm, PPCREC_FPR_ST_MODE_SINGLE_FROM_PS0, true); + return true; +} + +bool PPCRecompilerImlGen_STFSU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + if( hasSSE1Support == false ) + return false; + sint32 rA, frD; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(opcode, frD, rA, imm); + // get memory gpr register index + uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + // add imm to memory register + PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_ADD, gprRegister, (sint32)imm, 0, false, false, PPC_REC_INVALID_REGISTER, 0); + // get fpr register index + uint32 fprRegister = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + + PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r(ppcImlGenContext, fprRegister, gprRegister, 0, PPCREC_FPR_ST_MODE_SINGLE_FROM_PS0, true); + return true; +} + +bool PPCRecompilerImlGen_STFSX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + if( hasSSE2Support == false ) + return false; + sint32 rA, frS, rB; + PPC_OPC_TEMPL_X(opcode, frS, rA, rB); + if( rA == 0 ) + { + debugBreakpoint(); + return false; + } + // get memory gpr registers + uint32 gprRegister1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + uint32 gprRegister2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + // get fpr register index + uint32 fprRegister = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frS); + if( ppcImlGenContext->LSQE ) + { + PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r_indexed(ppcImlGenContext, fprRegister, gprRegister1, gprRegister2, 0, PPCREC_FPR_ST_MODE_SINGLE_FROM_PS0, true); + } + else + { + PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r_indexed(ppcImlGenContext, fprRegister, gprRegister1, gprRegister2, 0, PPCREC_FPR_ST_MODE_SINGLE_FROM_PS0, true); + } + return true; +} + + +bool PPCRecompilerImlGen_STFSUX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + if( hasSSE2Support == false ) + return false; + sint32 rA, frS, rB; + PPC_OPC_TEMPL_X(opcode, frS, rA, rB); + if( rA == 0 ) + { + debugBreakpoint(); + return false; + } + // get memory gpr registers + uint32 gprRegister1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + uint32 gprRegister2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + // get fpr register index + uint32 fprRegister = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frS); + // calculate EA in rA + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ADD, gprRegister1, gprRegister2); + + PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r(ppcImlGenContext, fprRegister, gprRegister1, 0, PPCREC_FPR_ST_MODE_SINGLE_FROM_PS0, true); + return true; +} + +bool PPCRecompilerImlGen_STFD(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + if( hasSSE1Support == false ) + return false; + sint32 rA, frD; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(opcode, frD, rA, imm); + if( rA == 0 ) + { + debugBreakpoint(); + return false; + } + // get memory gpr register index + uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + // get fpr register index + uint32 fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r(ppcImlGenContext, fprRegister, gprRegister, imm, PPCREC_FPR_ST_MODE_DOUBLE_FROM_PS0, true); + return true; +} + +bool PPCRecompilerImlGen_STFDU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + if( hasSSE1Support == false ) + return false; + sint32 rA, frD; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(opcode, frD, rA, imm); + if( rA == 0 ) + { + debugBreakpoint(); + return false; + } + // get memory gpr register index + uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + // add imm to memory register + PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_ADD, gprRegister, (sint32)imm, 0, false, false, PPC_REC_INVALID_REGISTER, 0); + // get fpr register index + uint32 fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + + PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r(ppcImlGenContext, fprRegister, gprRegister, 0, PPCREC_FPR_ST_MODE_DOUBLE_FROM_PS0, true); + return true; +} + +bool PPCRecompilerImlGen_STFDX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + if( hasSSE2Support == false ) + return false; + sint32 rA, frS, rB; + PPC_OPC_TEMPL_X(opcode, frS, rA, rB); + if( rA == 0 ) + { + debugBreakpoint(); + return false; + } + // get memory gpr registers + uint32 gprRegister1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + uint32 gprRegister2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + // get fpr register index + uint32 fprRegister = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frS); + if( ppcImlGenContext->LSQE ) + { + PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r_indexed(ppcImlGenContext, fprRegister, gprRegister1, gprRegister2, 0, PPCREC_FPR_ST_MODE_DOUBLE_FROM_PS0, true); + } + else + { + PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r_indexed(ppcImlGenContext, fprRegister, gprRegister1, gprRegister2, 0, PPCREC_FPR_ST_MODE_DOUBLE_FROM_PS0, true); + } + return true; +} + +bool PPCRecompilerImlGen_STFIWX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + if( hasSSE2Support == false ) + return false; + sint32 rA, frS, rB; + PPC_OPC_TEMPL_X(opcode, frS, rA, rB); + // get memory gpr registers + uint32 gprRegister1; + uint32 gprRegister2; + if( rA != 0 ) + { + gprRegister1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + gprRegister2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + } + else + { + // rA is not used + gprRegister1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + gprRegister2 = 0; + } + // get fpr register index + uint32 fprRegister = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frS); + if( rA != 0 ) + PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r_indexed(ppcImlGenContext, fprRegister, gprRegister1, gprRegister2, 0, PPCREC_FPR_ST_MODE_UI32_FROM_PS0, true); + else + PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r(ppcImlGenContext, fprRegister, gprRegister1, 0, PPCREC_FPR_ST_MODE_UI32_FROM_PS0, true); + return true; +} + +bool PPCRecompilerImlGen_FADD(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frA, frB, frC; + PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC); + PPC_ASSERT(frC==0); + + // load registers + uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_ADD_BOTTOM, fprRegisterD, fprRegisterA, fprRegisterB); + return true; +} + +bool PPCRecompilerImlGen_FSUB(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frA, frB, frC; + PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC); + PPC_ASSERT(frC==0); + + // load registers + uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + // subtract bottom double of frB from bottom double of frD + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_SUB_BOTTOM, fprRegisterD, fprRegisterA, fprRegisterB); + return true; +} + +bool PPCRecompilerImlGen_FMUL(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frA, frB_unused, frC; + PPC_OPC_TEMPL_A(opcode, frD, frA, frB_unused, frC); + if( frD == frC ) + { + // swap frA and frB + sint32 temp = frA; + frA = frC; + frC = temp; + } + // load registers + uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + uint32 fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + // move frA to frD (if different register) + if( fprRegisterD != fprRegisterA ) + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_ASSIGN, fprRegisterD, fprRegisterA); // always copy ps0 and ps1 + // multiply bottom double of frD with bottom double of frB + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_BOTTOM, fprRegisterD, fprRegisterC); + return true; +} + +bool PPCRecompilerImlGen_FDIV(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frA, frB, frC_unused; + PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC_unused); + PPC_ASSERT(frB==0); + // load registers + uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + + if( frB == frD && frA != frB ) + { + uint32 fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); + // move frA to temporary register + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_ASSIGN, fprRegisterTemp, fprRegisterA); + // divide bottom double of temporary register by bottom double of frB + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_DIVIDE_BOTTOM, fprRegisterTemp, fprRegisterB); + // move result to frD + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, fprRegisterD, fprRegisterTemp); + return true; + } + // move frA to frD (if different register) + if( fprRegisterD != fprRegisterA ) + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, fprRegisterD, fprRegisterA); // copy ps0 + // divide bottom double of frD by bottom double of frB + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_DIVIDE_BOTTOM, fprRegisterD, fprRegisterB); + return true; +} + +bool PPCRecompilerImlGen_FMADD(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frA, frB, frC; + PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC); + // load registers + uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + uint32 fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + // if frB is already in frD we need a temporary register to store the product of frA*frC + if( frB == frD ) + { + uint32 fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); + // move frA to temporary register + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_ASSIGN, fprRegisterTemp, fprRegisterA); + // multiply bottom double of temporary register with bottom double of frC + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_BOTTOM, fprRegisterTemp, fprRegisterC); + // add result to frD + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_ADD_BOTTOM, fprRegisterD, fprRegisterTemp); + return true; + } + // if frC == frD -> swap registers, we assume that frC != frD + if( fprRegisterD == fprRegisterC ) + { + // swap frA and frC + sint32 temp = fprRegisterA; + fprRegisterA = fprRegisterC; + fprRegisterC = temp; + } + // move frA to frD (if different register) + if( fprRegisterD != fprRegisterA ) + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_ASSIGN, fprRegisterD, fprRegisterA); // always copy ps0 and ps1 + // multiply bottom double of frD with bottom double of frC + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_BOTTOM, fprRegisterD, fprRegisterC); + // add frB + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_ADD_BOTTOM, fprRegisterD, fprRegisterB); + return true; +} + +bool PPCRecompilerImlGen_FMSUB(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frA, frB, frC; + PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC); + // load registers + uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + uint32 fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + // if frB is already in frD we need a temporary register to store the product of frA*frC + if( frB == frD ) + { + // not implemented + return false; + } + // if frC == frD -> swap registers, we assume that frC != frD + if( fprRegisterD == fprRegisterC ) + { + // swap frA and frC + sint32 temp = fprRegisterA; + fprRegisterA = fprRegisterC; + fprRegisterC = temp; + } + // move frA to frD (if different register) + if( fprRegisterD != fprRegisterA ) + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_ASSIGN, fprRegisterD, fprRegisterA); // always copy ps0 and ps1 + // multiply bottom double of frD with bottom double of frC + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_BOTTOM, fprRegisterD, fprRegisterC); + // sub frB + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_SUB_BOTTOM, fprRegisterD, fprRegisterB); + return true; +} + +bool PPCRecompilerImlGen_FNMSUB(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frA, frB, frC; + PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC); + + // load registers + uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + uint32 fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + // if frB is already in frD we need a temporary register to store the product of frA*frC + if( frB == frD ) + { + // hCPU->fpr[frD].fpr = -(hCPU->fpr[frA].fpr * hCPU->fpr[frC].fpr - hCPU->fpr[frD].fpr); + uint32 fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); + //// negate frB/frD + //PPCRecompilerImlGen_generateNewInstruction_fpr_r(ppcImlGenContext, NULL,PPCREC_IML_OP_FPR_NEGATE_BOTTOM, fprRegisterD, true); + // move frA to temporary register + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, fprRegisterTemp, fprRegisterA); + // multiply bottom double of temporary register with bottom double of frC + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_BOTTOM, fprRegisterTemp, fprRegisterC); + // sub frB from temporary register + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_SUB_BOTTOM, fprRegisterTemp, fprRegisterB); + // negate result + PPCRecompilerImlGen_generateNewInstruction_fpr_r(ppcImlGenContext, NULL,PPCREC_IML_OP_FPR_NEGATE_BOTTOM, fprRegisterTemp); + // move result to frD + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, fprRegisterD, fprRegisterTemp); + return true; + } + // if frC == frD -> swap registers, we assume that frC != frD + if( fprRegisterD == fprRegisterC ) + { + // swap frA and frC + sint32 temp = fprRegisterA; + fprRegisterA = fprRegisterC; + fprRegisterC = temp; + } + // move frA to frD (if different register) + if( fprRegisterD != fprRegisterA ) + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, fprRegisterD, fprRegisterA); // always copy ps0 and ps1 + // multiply bottom double of frD with bottom double of frC + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_BOTTOM, fprRegisterD, fprRegisterC); + // sub frB + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_SUB_BOTTOM, fprRegisterD, fprRegisterB); + // negate result + PPCRecompilerImlGen_generateNewInstruction_fpr_r(ppcImlGenContext, NULL,PPCREC_IML_OP_FPR_NEGATE_BOTTOM, fprRegisterD); + return true; +} + +bool PPCRecompilerImlGen_FMULS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frA, frB_unused, frC; + PPC_OPC_TEMPL_A(opcode, frD, frA, frB_unused, frC); + + if( frD == frC ) + { + // swap frA and frC + sint32 temp = frA; + frA = frC; + frC = temp; + } + // load registers + uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + uint32 fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + // move frA to frD (if different register) + if( fprRegisterD != fprRegisterA ) + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_ASSIGN, fprRegisterD, fprRegisterA); // always copy ps0 and ps1 + + // multiply bottom double of frD with bottom double of frB + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_BOTTOM, fprRegisterD, fprRegisterC); + // adjust accuracy + PPRecompilerImmGen_optionalRoundBottomFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); + // if paired single mode, copy frD ps0 to ps1 + if( ppcImlGenContext->PSE ) + { + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM_AND_TOP, fprRegisterD, fprRegisterD); + } + + return true; +} + +bool PPCRecompilerImlGen_FDIVS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frA, frB, frC_unused; + PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC_unused); + PPC_ASSERT(frB==0); + /*hCPU->fpr[frD].fpr = (float)(hCPU->fpr[frA].fpr / hCPU->fpr[frB].fpr); + if( hCPU->PSE ) + hCPU->fpr[frD].fp1 = hCPU->fpr[frD].fp0;*/ + // load registers + uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + + if( frB == frD && frA != frB ) + { + uint32 fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); + // move frA to temporary register + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_ASSIGN, fprRegisterTemp, fprRegisterA); + // divide bottom double of temporary register by bottom double of frB + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_DIVIDE_BOTTOM, fprRegisterTemp, fprRegisterB); + // move result to frD + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, fprRegisterD, fprRegisterTemp); + // adjust accuracy + PPRecompilerImmGen_optionalRoundBottomFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); + // if paired single mode, copy frD ps0 to ps1 + if( ppcImlGenContext->PSE ) + { + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM_AND_TOP, fprRegisterD, fprRegisterD); + } + return true; + } + // move frA to frD (if different register) + if( fprRegisterD != fprRegisterA ) + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_ASSIGN, fprRegisterD, fprRegisterA); // always copy ps0 and ps1 + // subtract bottom double of frB from bottom double of frD + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_DIVIDE_BOTTOM, fprRegisterD, fprRegisterB); + // adjust accuracy + PPRecompilerImmGen_optionalRoundBottomFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); + // if paired single mode, copy frD ps0 to ps1 + if( ppcImlGenContext->PSE ) + { + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM_AND_TOP, fprRegisterD, fprRegisterD); + } + return true; +} + +bool PPCRecompilerImlGen_FADDS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frA, frB, frC; + PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC); + + if( frD == frB ) + { + // swap frA and frB + sint32 temp = frA; + frA = frB; + frB = temp; + } + // load registers + uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + // move frA to frD (if different register) + if( fprRegisterD != fprRegisterA ) + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_ASSIGN, fprRegisterD, fprRegisterA); // always copy ps0 and ps1 + // add bottom double of frD and bottom double of frB + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_ADD_BOTTOM, fprRegisterD, fprRegisterB); + // adjust accuracy + PPRecompilerImmGen_optionalRoundBottomFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); + // if paired single mode, copy frD ps0 to ps1 + if( ppcImlGenContext->PSE ) + { + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM_AND_TOP, fprRegisterD, fprRegisterD); + } + return true; +} + +bool PPCRecompilerImlGen_FSUBS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + int frD, frA, frB, frC; + PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC); + PPC_ASSERT(frB==0); + + // load registers + uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + // subtract bottom + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_SUB_BOTTOM, fprRegisterD, fprRegisterA, fprRegisterB); + // adjust accuracy + PPRecompilerImmGen_optionalRoundBottomFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); + // if paired single mode, copy frD ps0 to ps1 + if( ppcImlGenContext->PSE ) + { + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM_AND_TOP, fprRegisterD, fprRegisterD); + } + return true; +} + +bool PPCRecompilerImlGen_FMADDS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frA, frB, frC; + PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC); + //FPRD(RD) = FPRD(RA) * FPRD(RC) + FPRD(RB); + //hCPU->fpr[frD].fpr = hCPU->fpr[frA].fpr * hCPU->fpr[frC].fpr + hCPU->fpr[frB].fpr; + //if( hCPU->PSE ) + // hCPU->fpr[frD].fp1 = hCPU->fpr[frD].fp0; + // load registers + uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + uint32 fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + uint32 fprRegisterTemp; + // if none of the operand registers overlap with the result register then we can avoid the usage of a temporary register + if( fprRegisterD != fprRegisterA && fprRegisterD != fprRegisterB && fprRegisterD != fprRegisterC ) + fprRegisterTemp = fprRegisterD; + else + fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_BOTTOM, fprRegisterTemp, fprRegisterA, fprRegisterC); + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_ADD_BOTTOM, fprRegisterTemp, fprRegisterB); + // adjust accuracy + PPRecompilerImmGen_optionalRoundBottomFPRToSinglePrecision(ppcImlGenContext, fprRegisterTemp); + // set result + if( ppcImlGenContext->PSE ) + { + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM_AND_TOP, fprRegisterD, fprRegisterTemp); + } + else if( fprRegisterD != fprRegisterTemp ) + { + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, fprRegisterD, fprRegisterTemp); + } + return true; +} + +bool PPCRecompilerImlGen_FMSUBS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frA, frB, frC; + PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC); + //hCPU->fpr[frD].fp0 = (float)(hCPU->fpr[frA].fp0 * hCPU->fpr[frC].fp0 - hCPU->fpr[frB].fp0); + //if( hCPU->PSE ) + // hCPU->fpr[frD].fp1 = hCPU->fpr[frD].fp0; + // load registers + uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + uint32 fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + uint32 fprRegisterTemp; + // if none of the operand registers overlap with the result register then we can avoid the usage of a temporary register + if( fprRegisterD != fprRegisterA && fprRegisterD != fprRegisterB && fprRegisterD != fprRegisterC ) + fprRegisterTemp = fprRegisterD; + else + fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_BOTTOM, fprRegisterTemp, fprRegisterA, fprRegisterC); + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_SUB_BOTTOM, fprRegisterTemp, fprRegisterB); + // adjust accuracy + PPRecompilerImmGen_optionalRoundBottomFPRToSinglePrecision(ppcImlGenContext, fprRegisterTemp); + // set result + if( ppcImlGenContext->PSE ) + { + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM_AND_TOP, fprRegisterD, fprRegisterTemp); + } + else if( fprRegisterD != fprRegisterTemp ) + { + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, fprRegisterD, fprRegisterTemp); + } + return true; +} + +bool PPCRecompilerImlGen_FNMSUBS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frA, frB, frC; + PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC); + + //[FP1(RD) = ]FP0(RD) = -(FP0(RA) * FP0(RC) - FP0(RB)); + //hCPU->fpr[frD].fp0 = (float)-(hCPU->fpr[frA].fp0 * hCPU->fpr[frC].fp0 - hCPU->fpr[frB].fp0); + //if( PPC_PSE ) + // hCPU->fpr[frD].fp1 = hCPU->fpr[frD].fp0; + + // load registers + uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + uint32 fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + uint32 fprRegisterTemp; + // if none of the operand registers overlap with the result register then we can avoid the usage of a temporary register + if( fprRegisterD != fprRegisterA && fprRegisterD != fprRegisterB && fprRegisterD != fprRegisterC ) + fprRegisterTemp = fprRegisterD; + else + fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_BOTTOM, fprRegisterTemp, fprRegisterA, fprRegisterC); + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_SUB_BOTTOM, fprRegisterTemp, fprRegisterB); + PPCRecompilerImlGen_generateNewInstruction_fpr_r(ppcImlGenContext, NULL,PPCREC_IML_OP_FPR_NEGATE_BOTTOM, fprRegisterTemp); + // adjust accuracy + PPRecompilerImmGen_optionalRoundBottomFPRToSinglePrecision(ppcImlGenContext, fprRegisterTemp); + // set result + if( ppcImlGenContext->PSE ) + { + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM_AND_TOP, fprRegisterD, fprRegisterTemp); + } + else if( fprRegisterD != fprRegisterTemp ) + { + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, fprRegisterD, fprRegisterTemp); + } + return true; +} + +bool PPCRecompilerImlGen_FCMPO(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 crfD, frA, frB; + PPC_OPC_TEMPL_X(opcode, crfD, frA, frB); + crfD >>= 2; + if( hasSSE2Support == false ) + { + return false; + } + uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_FCMPO_BOTTOM, fprRegisterA, fprRegisterB, crfD); + return true; +} + +bool PPCRecompilerImlGen_FCMPU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 crfD, frA, frB; + PPC_OPC_TEMPL_X(opcode, crfD, frA, frB); + crfD >>= 2; + if( hasSSE2Support == false ) + { + return false; + } + uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_FCMPU_BOTTOM, fprRegisterA, fprRegisterB, crfD); + return true; +} + +bool PPCRecompilerImlGen_FMR(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, rA, frB; + PPC_OPC_TEMPL_X(opcode, frD, rA, frB); + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, fprRegisterD, fprRegisterB); + return true; +} + +bool PPCRecompilerImlGen_FABS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frA, frB; + PPC_OPC_TEMPL_X(opcode, frD, frA, frB); + PPC_ASSERT(frA==0); + // load registers + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + // move frB to frD (if different register) + if( fprRegisterD != fprRegisterB ) + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, fprRegisterD, fprRegisterB); + // abs frD + PPCRecompilerImlGen_generateNewInstruction_fpr_r(ppcImlGenContext, NULL,PPCREC_IML_OP_FPR_ABS_BOTTOM, fprRegisterD); + return true; +} + +bool PPCRecompilerImlGen_FNABS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frA, frB; + PPC_OPC_TEMPL_X(opcode, frD, frA, frB); + PPC_ASSERT(frA==0); + // load registers + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + // move frB to frD (if different register) + if( fprRegisterD != fprRegisterB ) + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, fprRegisterD, fprRegisterB); + // abs frD + PPCRecompilerImlGen_generateNewInstruction_fpr_r(ppcImlGenContext, NULL,PPCREC_IML_OP_FPR_NEGATIVE_ABS_BOTTOM, fprRegisterD); + return true; +} + +bool PPCRecompilerImlGen_FRES(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frA, frB; + PPC_OPC_TEMPL_X(opcode, frD, frA, frB); + PPC_ASSERT(frA==0); + // load registers + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_BOTTOM_FRES_TO_BOTTOM_AND_TOP, fprRegisterD, fprRegisterB); + // adjust accuracy + PPRecompilerImmGen_optionalRoundBottomFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); + return true; +} + +bool PPCRecompilerImlGen_FRSP(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frA, frB; + PPC_OPC_TEMPL_X(opcode, frD, frA, frB); + PPC_ASSERT(frA==0); + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + if( fprRegisterD != fprRegisterB ) + { + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, fprRegisterD, fprRegisterB); + } + PPCRecompilerImlGen_generateNewInstruction_fpr_r(ppcImlGenContext, NULL,PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_BOTTOM, fprRegisterD); + if( ppcImlGenContext->PSE ) + { + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM_AND_TOP, fprRegisterD, fprRegisterD); + } + return true; +} + +bool PPCRecompilerImlGen_FNEG(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frA, frB; + PPC_OPC_TEMPL_X(opcode, frD, frA, frB); + PPC_ASSERT(frA==0); + if( opcode&PPC_OPC_RC ) + { + return false; + } + // load registers + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + // move frB to frD (if different register) + if( fprRegisterD != fprRegisterB ) + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, fprRegisterD, fprRegisterB); + // negate frD + PPCRecompilerImlGen_generateNewInstruction_fpr_r(ppcImlGenContext, NULL,PPCREC_IML_OP_FPR_NEGATE_BOTTOM, fprRegisterD); + return true; +} + +bool PPCRecompilerImlGen_FSEL(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frA, frB, frC; + PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC); + if( opcode&PPC_OPC_RC ) + { + return false; + } + uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + uint32 fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_SELECT_BOTTOM, fprRegisterD, fprRegisterA, fprRegisterB, fprRegisterC); + return true; +} + +bool PPCRecompilerImlGen_FRSQRTE(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frA, frB, frC; + PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC); + // hCPU->fpr[frD].fpr = 1.0 / sqrt(hCPU->fpr[frB].fpr); + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_BOTTOM_RECIPROCAL_SQRT, fprRegisterD, fprRegisterB); + // adjust accuracy + PPRecompilerImmGen_optionalRoundBottomFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); + return true; +} + +bool PPCRecompilerImlGen_FCTIWZ(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frA, frB; + PPC_OPC_TEMPL_X(opcode, frD, frA, frB); + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_BOTTOM_FCTIWZ, fprRegisterD, fprRegisterB); + return true; +} + +bool PPCRecompilerImlGen_PSQ_L(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + if (hasSSE2Support == false) + return false; + int rA, frD; + uint32 immUnused; + PPC_OPC_TEMPL_D_SImm(opcode, frD, rA, immUnused); + + sint32 gqrIndex = ((opcode >> 12) & 7); + uint32 imm = opcode & 0xFFF; + if (imm & 0x800) + imm |= ~0xFFF; + + bool readPS1 = (opcode & 0x8000) == false; + + // get gqr register + uint32 gqrRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + SPR_UGQR0 + gqrIndex, false); + // get memory gpr register index + uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rA, false); + // get fpr register index + uint32 fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frD); + // psq load + PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory(ppcImlGenContext, fprRegister, gprRegister, imm, readPS1 ? PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0_PS1 : PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0, true, gqrRegister); + return true; +} + +bool PPCRecompilerImlGen_PSQ_LU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + if (hasSSE2Support == false) + return false; + int rA, frD; + uint32 immUnused; + PPC_OPC_TEMPL_D_SImm(opcode, frD, rA, immUnused); + if (rA == 0) + return false; + + sint32 gqrIndex = ((opcode >> 12) & 7); + uint32 imm = opcode & 0xFFF; + if (imm & 0x800) + imm |= ~0xFFF; + + bool readPS1 = (opcode & 0x8000) == false; + + // get gqr register + uint32 gqrRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + SPR_UGQR0 + gqrIndex, false); + // get memory gpr register index + uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rA, false); + // add imm to memory register + PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_ADD, gprRegister, (sint32)imm, 0, false, false, PPC_REC_INVALID_REGISTER, 0); + // get fpr register index + uint32 fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frD); + // paired load + PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory(ppcImlGenContext, fprRegister, gprRegister, 0, readPS1 ? PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0_PS1 : PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0, true, gqrRegister); + return true; +} + +bool PPCRecompilerImlGen_PSQ_ST(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + int rA, frD; + uint32 immUnused; + PPC_OPC_TEMPL_D_SImm(opcode, frD, rA, immUnused); + uint32 imm = opcode & 0xFFF; + if (imm & 0x800) + imm |= ~0xFFF; + sint32 gqrIndex = ((opcode >> 12) & 7); + + bool storePS1 = (opcode & 0x8000) == false; + + // get gqr register + uint32 gqrRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + SPR_UGQR0 + gqrIndex, false); + // get memory gpr register index + uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rA, false); + // get fpr register index + uint32 fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frD); + // paired store + PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r(ppcImlGenContext, fprRegister, gprRegister, imm, storePS1 ? PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0_PS1 : PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0, true, gqrRegister); + return true; +} + +bool PPCRecompilerImlGen_PSQ_STU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + int rA, frD; + uint32 immUnused; + PPC_OPC_TEMPL_D_SImm(opcode, frD, rA, immUnused); + if (rA == 0) + return false; + + uint32 imm = opcode & 0xFFF; + if (imm & 0x800) + imm |= ~0xFFF; + sint32 gqrIndex = ((opcode >> 12) & 7); + + bool storePS1 = (opcode & 0x8000) == false; + + // get gqr register + uint32 gqrRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + SPR_UGQR0 + gqrIndex, false); + // get memory gpr register index + uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rA, false); + // add imm to memory register + PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_ADD, gprRegister, (sint32)imm, 0, false, false, PPC_REC_INVALID_REGISTER, 0); + // get fpr register index + uint32 fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frD); + // paired store + PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r(ppcImlGenContext, fprRegister, gprRegister, 0, storePS1 ? PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0_PS1 : PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0, true, gqrRegister); + return true; +} + +bool PPCRecompilerImlGen_PS_MULS0(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frA, frC; + frC = (opcode>>6)&0x1F; + frA = (opcode>>16)&0x1F; + frD = (opcode>>21)&0x1F; + // load registers + uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + uint32 fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + // we need a temporary register to store frC.fp0 in low and high half + uint32 fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM_AND_TOP, fprRegisterTemp, fprRegisterC); + // if frD == frA we can multiply frD immediately and safe a copy instruction + if( frD == frA ) + { + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_PAIR, fprRegisterD, fprRegisterTemp); + } + else + { + // we multiply temporary by frA + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_PAIR, fprRegisterTemp, fprRegisterA); + // copy result to frD + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_PAIR, fprRegisterD, fprRegisterTemp); + } + // adjust accuracy + PPRecompilerImmGen_optionalRoundPairFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); + return true; +} + +bool PPCRecompilerImlGen_PS_MULS1(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frA, frC; + frC = (opcode>>6)&0x1F; + frA = (opcode>>16)&0x1F; + frD = (opcode>>21)&0x1F; + // load registers + uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + uint32 fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + // we need a temporary register to store frC.fp0 in low and high half + uint32 fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_TOP_TO_BOTTOM_AND_TOP, fprRegisterTemp, fprRegisterC); + // if frD == frA we can multiply frD immediately and safe a copy instruction + if( frD == frA ) + { + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_PAIR, fprRegisterD, fprRegisterTemp); + } + else + { + // we multiply temporary by frA + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_PAIR, fprRegisterTemp, fprRegisterA); + // copy result to frD + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_PAIR, fprRegisterD, fprRegisterTemp); + } + // adjust accuracy + PPRecompilerImmGen_optionalRoundPairFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); + return true; +} + +bool PPCRecompilerImlGen_PS_MADDS0(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frA, frB, frC; + frC = (opcode>>6)&0x1F; + frB = (opcode>>11)&0x1F; + frA = (opcode>>16)&0x1F; + frD = (opcode>>21)&0x1F; + //float s0 = (float)(hCPU->fpr[frA].fp0 * hCPU->fpr[frC].fp0 + hCPU->fpr[frB].fp0); + //float s1 = (float)(hCPU->fpr[frA].fp1 * hCPU->fpr[frC].fp0 + hCPU->fpr[frB].fp1); + // load registers + uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + uint32 fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + // we need a temporary register to store frC.fp0 in low and high half + uint32 fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM_AND_TOP, fprRegisterTemp, fprRegisterC); + // if frD == frA and frD != frB we can multiply frD immediately and safe a copy instruction + if( frD == frA && frD != frB ) + { + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_PAIR, fprRegisterD, fprRegisterTemp); + // add frB + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_ADD_PAIR, fprRegisterD, fprRegisterB); + } + else + { + // we multiply temporary by frA + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_PAIR, fprRegisterTemp, fprRegisterA); + // add frB + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_ADD_PAIR, fprRegisterTemp, fprRegisterB); + // copy result to frD + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_PAIR, fprRegisterD, fprRegisterTemp); + } + // adjust accuracy + PPRecompilerImmGen_optionalRoundPairFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); + return true; +} + +bool PPCRecompilerImlGen_PS_MADDS1(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frA, frB, frC; + frC = (opcode>>6)&0x1F; + frB = (opcode>>11)&0x1F; + frA = (opcode>>16)&0x1F; + frD = (opcode>>21)&0x1F; + //float s0 = (float)(hCPU->fpr[frA].fp0 * hCPU->fpr[frC].fp1 + hCPU->fpr[frB].fp0); + //float s1 = (float)(hCPU->fpr[frA].fp1 * hCPU->fpr[frC].fp1 + hCPU->fpr[frB].fp1); + // load registers + uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + uint32 fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + // we need a temporary register to store frC.fp1 in bottom and top half + uint32 fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_TOP_TO_BOTTOM_AND_TOP, fprRegisterTemp, fprRegisterC); + // if frD == frA and frD != frB we can multiply frD immediately and safe a copy instruction + if( frD == frA && frD != frB ) + { + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_PAIR, fprRegisterD, fprRegisterTemp); + // add frB + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_ADD_PAIR, fprRegisterD, fprRegisterB); + } + else + { + // we multiply temporary by frA + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_PAIR, fprRegisterTemp, fprRegisterA); + // add frB + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_ADD_PAIR, fprRegisterTemp, fprRegisterB); + // copy result to frD + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_PAIR, fprRegisterD, fprRegisterTemp); + } + // adjust accuracy + PPRecompilerImmGen_optionalRoundPairFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); + return true; +} + +bool PPCRecompilerImlGen_PS_ADD(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frA, frB; + frB = (opcode>>11)&0x1F; + frA = (opcode>>16)&0x1F; + frD = (opcode>>21)&0x1F; + //hCPU->fpr[frD].fp0 = hCPU->fpr[frA].fp0 + hCPU->fpr[frB].fp0; + //hCPU->fpr[frD].fp1 = hCPU->fpr[frA].fp1 + hCPU->fpr[frB].fp1; + // load registers + uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + if( frD == frA ) + { + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_ADD_PAIR, fprRegisterD, fprRegisterB); + } + else if( frD == frB ) + { + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_ADD_PAIR, fprRegisterD, fprRegisterA); + } + else + { + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_PAIR, fprRegisterD, fprRegisterA); + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_ADD_PAIR, fprRegisterD, fprRegisterB); + } + // adjust accuracy + PPRecompilerImmGen_optionalRoundPairFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); + return true; +} + +bool PPCRecompilerImlGen_PS_SUB(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frA, frB; + frB = (opcode>>11)&0x1F; + frA = (opcode>>16)&0x1F; + frD = (opcode>>21)&0x1F; + //hCPU->fpr[frD].fp0 = hCPU->fpr[frA].fp0 - hCPU->fpr[frB].fp0; + //hCPU->fpr[frD].fp1 = hCPU->fpr[frA].fp1 - hCPU->fpr[frB].fp1; + // load registers + uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_SUB_PAIR, fprRegisterD, fprRegisterA, fprRegisterB); + // adjust accuracy + PPRecompilerImmGen_optionalRoundPairFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); + return true; +} + +bool PPCRecompilerImlGen_PS_MUL(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frA, frC; + frC = (opcode >> 6) & 0x1F; + frA = (opcode >> 16) & 0x1F; + frD = (opcode >> 21) & 0x1F; + // load registers + uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frA); + uint32 fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frC); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frD); + // we need a temporary register + uint32 fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0 + 0); + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_PAIR, fprRegisterTemp, fprRegisterC); + // todo-optimize: This instruction can be optimized so that it doesn't always use a temporary register + // if frD == frA we can multiply frD immediately and safe a copy instruction + if (frD == frA) + { + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_PAIR, fprRegisterD, fprRegisterTemp); + } + else + { + // we multiply temporary by frA + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_PAIR, fprRegisterTemp, fprRegisterA); + // copy result to frD + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_PAIR, fprRegisterD, fprRegisterTemp); + } + // adjust accuracy + PPRecompilerImmGen_optionalRoundPairFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); + return true; +} + +bool PPCRecompilerImlGen_PS_DIV(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frA, frB; + frB = (opcode >> 11) & 0x1F; + frA = (opcode >> 16) & 0x1F; + frD = (opcode >> 21) & 0x1F; + //hCPU->fpr[frD].fp0 = hCPU->fpr[frA].fp0 / hCPU->fpr[frB].fp0; + //hCPU->fpr[frD].fp1 = hCPU->fpr[frA].fp1 / hCPU->fpr[frB].fp1; + // load registers + uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frA); + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frB); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frD); + // todo-optimize: This instruction can be optimized so that it doesn't always use a temporary register + // if frD == frA we can divide frD immediately and safe a copy instruction + if (frD == frA) + { + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_DIVIDE_PAIR, fprRegisterD, fprRegisterB); + } + else + { + // we need a temporary register + uint32 fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0 + 0); + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_PAIR, fprRegisterTemp, fprRegisterA); + // we divide temporary by frB + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_DIVIDE_PAIR, fprRegisterTemp, fprRegisterB); + // copy result to frD + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_PAIR, fprRegisterD, fprRegisterTemp); + } + // adjust accuracy + PPRecompilerImmGen_optionalRoundPairFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); + return true; +} + +bool PPCRecompilerImlGen_PS_MADD(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frA, frB, frC; + frC = (opcode>>6)&0x1F; + frB = (opcode>>11)&0x1F; + frA = (opcode>>16)&0x1F; + frD = (opcode>>21)&0x1F; + //float s0 = (float)(hCPU->fpr[frA].fp0 * hCPU->fpr[frC].fp0 + hCPU->fpr[frB].fp0); + //float s1 = (float)(hCPU->fpr[frA].fp1 * hCPU->fpr[frC].fp1 + hCPU->fpr[frB].fp1); + + // load registers + uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + uint32 fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + // we need a temporary register to store frC.fp0 in low and high half + uint32 fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_PAIR, fprRegisterTemp, fprRegisterC); + // todo-optimize: This instruction can be optimized so that it doesn't always use a temporary register + // if frD == frA and frD != frB we can multiply frD immediately and save a copy instruction + if( frD == frA && frD != frB ) + { + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_PAIR, fprRegisterD, fprRegisterTemp); + // add frB + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_ADD_PAIR, fprRegisterD, fprRegisterB); + } + else + { + // we multiply temporary by frA + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_PAIR, fprRegisterTemp, fprRegisterA); + // add frB + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_ADD_PAIR, fprRegisterTemp, fprRegisterB); + // copy result to frD + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_PAIR, fprRegisterD, fprRegisterTemp); + } + // adjust accuracy + PPRecompilerImmGen_optionalRoundPairFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); + return true; +} + +bool PPCRecompilerImlGen_PS_NMADD(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frA, frB, frC; + frC = (opcode>>6)&0x1F; + frB = (opcode>>11)&0x1F; + frA = (opcode>>16)&0x1F; + frD = (opcode>>21)&0x1F; + + // load registers + uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + uint32 fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + // we need a temporary register to store frC.fp0 in low and high half + uint32 fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_PAIR, fprRegisterTemp, fprRegisterC); + // todo-optimize: This instruction can be optimized so that it doesn't always use a temporary register + // if frD == frA and frD != frB we can multiply frD immediately and safe a copy instruction + if( frD == frA && frD != frB ) + { + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_PAIR, fprRegisterD, fprRegisterTemp); + // add frB + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_ADD_PAIR, fprRegisterD, fprRegisterB); + } + else + { + // we multiply temporary by frA + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_PAIR, fprRegisterTemp, fprRegisterA); + // add frB + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_ADD_PAIR, fprRegisterTemp, fprRegisterB); + // copy result to frD + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_PAIR, fprRegisterD, fprRegisterTemp); + } + // negate + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_NEGATE_PAIR, fprRegisterD, fprRegisterD); + // adjust accuracy + //PPRecompilerImmGen_optionalRoundPairFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); + // Splatoon requires that we emulate flush-to-denormals for this instruction + //PPCRecompilerImlGen_generateNewInstruction_fpr_r(ppcImlGenContext, NULL,PPCREC_IML_OP_FPR_ROUND_FLDN_TO_SINGLE_PRECISION_PAIR, fprRegisterD, false); + return true; +} + +bool PPCRecompilerImlGen_PS_MSUB(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frA, frB, frC; + frC = (opcode>>6)&0x1F; + frB = (opcode>>11)&0x1F; + frA = (opcode>>16)&0x1F; + frD = (opcode>>21)&0x1F; + //hCPU->fpr[frD].fp0 = (hCPU->fpr[frA].fp0 * hCPU->fpr[frC].fp0 - hCPU->fpr[frB].fp0); + //hCPU->fpr[frD].fp1 = (hCPU->fpr[frA].fp1 * hCPU->fpr[frC].fp1 - hCPU->fpr[frB].fp1); + + // load registers + uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + uint32 fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + // we need a temporary register to store frC.fp0 in low and high half + uint32 fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_PAIR, fprRegisterTemp, fprRegisterC); + // todo-optimize: This instruction can be optimized so that it doesn't always use a temporary register + // if frD == frA and frD != frB we can multiply frD immediately and safe a copy instruction + if( frD == frA && frD != frB ) + { + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_PAIR, fprRegisterD, fprRegisterTemp); + // sub frB + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_SUB_PAIR, fprRegisterD, fprRegisterB); + } + else + { + // we multiply temporary by frA + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_PAIR, fprRegisterTemp, fprRegisterA); + // sub frB + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_SUB_PAIR, fprRegisterTemp, fprRegisterB); + // copy result to frD + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_PAIR, fprRegisterD, fprRegisterTemp); + } + // adjust accuracy + PPRecompilerImmGen_optionalRoundPairFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); + return true; +} + +bool PPCRecompilerImlGen_PS_NMSUB(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frA, frB, frC; + frC = (opcode>>6)&0x1F; + frB = (opcode>>11)&0x1F; + frA = (opcode>>16)&0x1F; + frD = (opcode>>21)&0x1F; + + // load registers + uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + uint32 fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + // we need a temporary register to store frC.fp0 in low and high half + uint32 fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_PAIR, fprRegisterTemp, fprRegisterC); + // todo-optimize: This instruction can be optimized so that it doesn't always use a temporary register + // if frD == frA and frD != frB we can multiply frD immediately and safe a copy instruction + if( frD == frA && frD != frB ) + { + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_PAIR, fprRegisterD, fprRegisterTemp); + // sub frB + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_SUB_PAIR, fprRegisterD, fprRegisterB); + } + else + { + // we multiply temporary by frA + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_PAIR, fprRegisterTemp, fprRegisterA); + // sub frB + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_SUB_PAIR, fprRegisterTemp, fprRegisterB); + // copy result to frD + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_PAIR, fprRegisterD, fprRegisterTemp); + } + // negate result + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_NEGATE_PAIR, fprRegisterD, fprRegisterD); + // adjust accuracy + PPRecompilerImmGen_optionalRoundPairFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); + return true; +} + +bool PPCRecompilerImlGen_PS_SUM0(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frA, frB, frC; + frC = (opcode>>6)&0x1F; + frB = (opcode>>11)&0x1F; + frA = (opcode>>16)&0x1F; + frD = (opcode>>21)&0x1F; + //float s0 = (float)(hCPU->fpr[frA].fp0 + hCPU->fpr[frB].fp1); + //float s1 = (float)hCPU->fpr[frC].fp1; + //hCPU->fpr[frD].fp0 = s0; + //hCPU->fpr[frD].fp1 = s1; + // load registers + uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + uint32 fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_SUM0, fprRegisterD, fprRegisterA, fprRegisterB, fprRegisterC); + // adjust accuracy + PPRecompilerImmGen_optionalRoundPairFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); + return true; +} + +bool PPCRecompilerImlGen_PS_SUM1(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frA, frB, frC; + frC = (opcode>>6)&0x1F; + frB = (opcode>>11)&0x1F; + frA = (opcode>>16)&0x1F; + frD = (opcode>>21)&0x1F; + //float s0 = (float)hCPU->fpr[frC].fp0; + //float s1 = (float)(hCPU->fpr[frA].fp0 + hCPU->fpr[frB].fp1); + //hCPU->fpr[frD].fp0 = s0; + //hCPU->fpr[frD].fp1 = s1; + // load registers + uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + uint32 fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_SUM1, fprRegisterD, fprRegisterA, fprRegisterB, fprRegisterC); + // adjust accuracy + PPRecompilerImmGen_optionalRoundPairFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); + return true; +} + +bool PPCRecompilerImlGen_PS_NEG(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frB; + frB = (opcode>>11)&0x1F; + frD = (opcode>>21)&0x1F; + //hCPU->fpr[frD].fp0 = -hCPU->fpr[frB].fp0; + //hCPU->fpr[frD].fp1 = -hCPU->fpr[frB].fp1; + // load registers + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_NEGATE_PAIR, fprRegisterD, fprRegisterB); + return true; +} + +bool PPCRecompilerImlGen_PS_ABS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frB; + frB = (opcode>>11)&0x1F; + frD = (opcode>>21)&0x1F; + // load registers + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_ABS_PAIR, fprRegisterD, fprRegisterB); + return true; +} + +bool PPCRecompilerImlGen_PS_RES(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frB; + frB = (opcode>>11)&0x1F; + frD = (opcode>>21)&0x1F; + //hCPU->fpr[frD].fp0 = (float)(1.0f / (float)hCPU->fpr[frB].fp0); + //hCPU->fpr[frD].fp1 = (float)(1.0f / (float)hCPU->fpr[frB].fp1); + + // load registers + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_FRES_PAIR, fprRegisterD, fprRegisterB); + return true; +} + +bool PPCRecompilerImlGen_PS_RSQRTE(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frB; + frB = (opcode>>11)&0x1F; + frD = (opcode>>21)&0x1F; + //hCPU->fpr[frD].fp0 = (float)(1.0f / (float)sqrt(hCPU->fpr[frB].fp0)); + //hCPU->fpr[frD].fp1 = (float)(1.0f / (float)sqrt(hCPU->fpr[frB].fp1)); + + // load registers + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_FRSQRTE_PAIR, fprRegisterD, fprRegisterB); + return true; +} + +bool PPCRecompilerImlGen_PS_MR(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frB; + frB = (opcode>>11)&0x1F; + frD = (opcode>>21)&0x1F; + //hCPU->fpr[frD].fp0 = hCPU->fpr[frB].fp0; + //hCPU->fpr[frD].fp1 = hCPU->fpr[frB].fp1; + // load registers + if( frB != frD ) + { + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_PAIR, fprRegisterD, fprRegisterB); + } + return true; +} + +bool PPCRecompilerImlGen_PS_SEL(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frA, frB, frC; + frC = (opcode>>6)&0x1F; + frB = (opcode>>11)&0x1F; + frA = (opcode>>16)&0x1F; + frD = (opcode>>21)&0x1F; + + uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + uint32 fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_SELECT_PAIR, fprRegisterD, fprRegisterA, fprRegisterB, fprRegisterC); + return true; +} + +bool PPCRecompilerImlGen_PS_MERGE00(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frA, frB; + frB = (opcode>>11)&0x1F; + frA = (opcode>>16)&0x1F; + frD = (opcode>>21)&0x1F; + //float s0 = (float)hCPU->fpr[frA].fp0; + //float s1 = (float)hCPU->fpr[frB].fp0; + //hCPU->fpr[frD].fp0 = s0; + //hCPU->fpr[frD].fp1 = s1; + uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + // unpcklpd + if( frA == frB ) + { + // simply duplicate bottom into bottom and top of destination register + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM_AND_TOP, fprRegisterD, fprRegisterA); + } + else + { + // copy bottom of frB to top first + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_TOP, fprRegisterD, fprRegisterB); + // copy bottom of frA + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, fprRegisterD, fprRegisterA); + } + return true; +} + +bool PPCRecompilerImlGen_PS_MERGE01(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frA, frB; + frB = (opcode>>11)&0x1F; + frA = (opcode>>16)&0x1F; + frD = (opcode>>21)&0x1F; + // hCPU->fpr[frD].fp0 = hCPU->fpr[frA].fp0; + // hCPU->fpr[frD].fp1 = hCPU->fpr[frB].fp1; + uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + + if( fprRegisterD != fprRegisterB ) + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_TOP_TO_TOP, fprRegisterD, fprRegisterB); + if( fprRegisterD != fprRegisterA ) + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, fprRegisterD, fprRegisterA); + + return true; +} + +bool PPCRecompilerImlGen_PS_MERGE10(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frA, frB; + frB = (opcode>>11)&0x1F; + frA = (opcode>>16)&0x1F; + frD = (opcode>>21)&0x1F; + + uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + if( frA == frB ) + { + // swap bottom and top + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_AND_TOP_SWAPPED, fprRegisterD, fprRegisterA); + } + else if( frA == frD ) + { + // copy frB bottom to frD bottom + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, fprRegisterD, fprRegisterB); + // swap lower and upper half of frD + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_AND_TOP_SWAPPED, fprRegisterD, fprRegisterD); + } + else if( frB == frD ) + { + // copy upper half of frA to upper half of frD + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_TOP_TO_TOP, fprRegisterD, fprRegisterA); + // swap lower and upper half of frD + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_AND_TOP_SWAPPED, fprRegisterD, fprRegisterD); + } + else + { + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_TOP_TO_BOTTOM_AND_TOP, fprRegisterD, fprRegisterA); + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, fprRegisterD, fprRegisterB); + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_AND_TOP_SWAPPED, fprRegisterD, fprRegisterD); + } + return true; +} + +bool PPCRecompilerImlGen_PS_MERGE11(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 frD, frA, frB; + frB = (opcode>>11)&0x1F; + frA = (opcode>>16)&0x1F; + frD = (opcode>>21)&0x1F; + + uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + if( fprRegisterA == fprRegisterB ) + { + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_TOP_TO_BOTTOM_AND_TOP, fprRegisterD, fprRegisterA); + } + else if( fprRegisterD != fprRegisterB ) + { + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_TOP_TO_BOTTOM_AND_TOP, fprRegisterD, fprRegisterA); + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_TOP_TO_TOP, fprRegisterD, fprRegisterB); + } + else if( fprRegisterD == fprRegisterB ) + { + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_TOP_TO_BOTTOM, fprRegisterD, fprRegisterA); + } + else + { + debugBreakpoint(); + return false; + } + return true; +} + +bool PPCRecompilerImlGen_PS_CMPO0(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 crfD, frA, frB; + uint32 c=0; + frB = (opcode>>11)&0x1F; + frA = (opcode>>16)&0x1F; + crfD = (opcode>>23)&0x7; + + uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_FCMPO_BOTTOM, fprRegisterA, fprRegisterB, crfD); + return true; +} + +bool PPCRecompilerImlGen_PS_CMPU0(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 crfD, frA, frB; + frB = (opcode >> 11) & 0x1F; + frA = (opcode >> 16) & 0x1F; + crfD = (opcode >> 23) & 0x7; + uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frA); + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frB); + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_FCMPU_BOTTOM, fprRegisterA, fprRegisterB, crfD); + return true; +} + +bool PPCRecompilerImlGen_PS_CMPU1(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 crfD, frA, frB; + frB = (opcode >> 11) & 0x1F; + frA = (opcode >> 16) & 0x1F; + crfD = (opcode >> 23) & 0x7; + uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frA); + uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frB); + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_FCMPU_TOP, fprRegisterA, fprRegisterB, crfD); + return true; +} \ No newline at end of file diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlOptimizer.cpp b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlOptimizer.cpp new file mode 100644 index 00000000..1a15bd22 --- /dev/null +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlOptimizer.cpp @@ -0,0 +1,2175 @@ +#include "../Interpreter/PPCInterpreterInternal.h" +#include "PPCRecompiler.h" +#include "PPCRecompilerIml.h" +#include "PPCRecompilerX64.h" + +void PPCRecompiler_checkRegisterUsage(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlInstruction_t* imlInstruction, PPCImlOptimizerUsedRegisters_t* registersUsed) +{ + registersUsed->readNamedReg1 = -1; + registersUsed->readNamedReg2 = -1; + registersUsed->readNamedReg3 = -1; + registersUsed->writtenNamedReg1 = -1; + registersUsed->readFPR1 = -1; + registersUsed->readFPR2 = -1; + registersUsed->readFPR3 = -1; + registersUsed->readFPR4 = -1; + registersUsed->writtenFPR1 = -1; + if( imlInstruction->type == PPCREC_IML_TYPE_R_NAME ) + { + registersUsed->writtenNamedReg1 = imlInstruction->op_r_name.registerIndex; + } + else if( imlInstruction->type == PPCREC_IML_TYPE_NAME_R ) + { + registersUsed->readNamedReg1 = imlInstruction->op_r_name.registerIndex; + } + else if( imlInstruction->type == PPCREC_IML_TYPE_R_R ) + { + if (imlInstruction->operation == PPCREC_IML_OP_COMPARE_SIGNED || imlInstruction->operation == PPCREC_IML_OP_COMPARE_UNSIGNED || imlInstruction->operation == PPCREC_IML_OP_DCBZ) + { + // both operands are read only + registersUsed->readNamedReg1 = imlInstruction->op_r_r.registerResult; + registersUsed->readNamedReg2 = imlInstruction->op_r_r.registerA; + } + else if ( + imlInstruction->operation == PPCREC_IML_OP_OR || + imlInstruction->operation == PPCREC_IML_OP_AND || + imlInstruction->operation == PPCREC_IML_OP_XOR || + imlInstruction->operation == PPCREC_IML_OP_ADD || + imlInstruction->operation == PPCREC_IML_OP_ADD_CARRY || + imlInstruction->operation == PPCREC_IML_OP_ADD_CARRY_ME || + imlInstruction->operation == PPCREC_IML_OP_SUB_CARRY_UPDATE_CARRY) + { + // result is read and written, operand is read + registersUsed->writtenNamedReg1 = imlInstruction->op_r_r.registerResult; + registersUsed->readNamedReg1 = imlInstruction->op_r_r.registerResult; + registersUsed->readNamedReg2 = imlInstruction->op_r_r.registerA; + } + else if ( + imlInstruction->operation == PPCREC_IML_OP_ASSIGN || + imlInstruction->operation == PPCREC_IML_OP_ENDIAN_SWAP || + imlInstruction->operation == PPCREC_IML_OP_CNTLZW || + imlInstruction->operation == PPCREC_IML_OP_NOT || + imlInstruction->operation == PPCREC_IML_OP_NEG || + imlInstruction->operation == PPCREC_IML_OP_ASSIGN_S16_TO_S32 || + imlInstruction->operation == PPCREC_IML_OP_ASSIGN_S8_TO_S32) + { + // result is written, operand is read + registersUsed->writtenNamedReg1 = imlInstruction->op_r_r.registerResult; + registersUsed->readNamedReg1 = imlInstruction->op_r_r.registerA; + } + else + cemu_assert_unimplemented(); + } + else if (imlInstruction->type == PPCREC_IML_TYPE_R_S32) + { + if (imlInstruction->operation == PPCREC_IML_OP_COMPARE_SIGNED || imlInstruction->operation == PPCREC_IML_OP_COMPARE_UNSIGNED || imlInstruction->operation == PPCREC_IML_OP_MTCRF) + { + // operand register is read only + registersUsed->readNamedReg1 = imlInstruction->op_r_immS32.registerIndex; + } + else if (imlInstruction->operation == PPCREC_IML_OP_ADD || + imlInstruction->operation == PPCREC_IML_OP_SUB || + imlInstruction->operation == PPCREC_IML_OP_AND || + imlInstruction->operation == PPCREC_IML_OP_OR || + imlInstruction->operation == PPCREC_IML_OP_XOR || + imlInstruction->operation == PPCREC_IML_OP_LEFT_ROTATE) + { + // operand register is read and write + registersUsed->readNamedReg1 = imlInstruction->op_r_immS32.registerIndex; + registersUsed->writtenNamedReg1 = imlInstruction->op_r_immS32.registerIndex; + } + else + { + // operand register is write only + // todo - use explicit lists, avoid default cases + registersUsed->writtenNamedReg1 = imlInstruction->op_r_immS32.registerIndex; + } + } + else if (imlInstruction->type == PPCREC_IML_TYPE_CONDITIONAL_R_S32) + { + if (imlInstruction->operation == PPCREC_IML_OP_ASSIGN) + { + // result is written, but also considered read (in case the condition fails) + registersUsed->readNamedReg1 = imlInstruction->op_conditional_r_s32.registerIndex; + registersUsed->writtenNamedReg1 = imlInstruction->op_conditional_r_s32.registerIndex; + } + else + cemu_assert_unimplemented(); + } + else if( imlInstruction->type == PPCREC_IML_TYPE_R_R_S32 ) + { + if( imlInstruction->operation == PPCREC_IML_OP_RLWIMI ) + { + // result and operand register are both read, result is written + registersUsed->writtenNamedReg1 = imlInstruction->op_r_r_s32.registerResult; + registersUsed->readNamedReg1 = imlInstruction->op_r_r_s32.registerResult; + registersUsed->readNamedReg2 = imlInstruction->op_r_r_s32.registerA; + } + else + { + // result is write only and operand is read only + registersUsed->writtenNamedReg1 = imlInstruction->op_r_r_s32.registerResult; + registersUsed->readNamedReg1 = imlInstruction->op_r_r_s32.registerA; + } + } + else if( imlInstruction->type == PPCREC_IML_TYPE_R_R_R ) + { + // in all cases result is written and other operands are read only + registersUsed->writtenNamedReg1 = imlInstruction->op_r_r_r.registerResult; + registersUsed->readNamedReg1 = imlInstruction->op_r_r_r.registerA; + registersUsed->readNamedReg2 = imlInstruction->op_r_r_r.registerB; + } + else if( imlInstruction->type == PPCREC_IML_TYPE_CJUMP || imlInstruction->type == PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK ) + { + // no effect on registers + } + else if( imlInstruction->type == PPCREC_IML_TYPE_NO_OP ) + { + // no effect on registers + } + else if( imlInstruction->type == PPCREC_IML_TYPE_MACRO ) + { + if( imlInstruction->operation == PPCREC_IML_MACRO_BL || imlInstruction->operation == PPCREC_IML_MACRO_B_FAR || imlInstruction->operation == PPCREC_IML_MACRO_BLR || imlInstruction->operation == PPCREC_IML_MACRO_BLRL || imlInstruction->operation == PPCREC_IML_MACRO_BCTR || imlInstruction->operation == PPCREC_IML_MACRO_BCTRL || imlInstruction->operation == PPCREC_IML_MACRO_LEAVE || imlInstruction->operation == PPCREC_IML_MACRO_DEBUGBREAK || imlInstruction->operation == PPCREC_IML_MACRO_COUNT_CYCLES || imlInstruction->operation == PPCREC_IML_MACRO_HLE || imlInstruction->operation == PPCREC_IML_MACRO_MFTB ) + { + // no effect on registers + } + else + cemu_assert_unimplemented(); + } + else if (imlInstruction->type == PPCREC_IML_TYPE_LOAD) + { + registersUsed->writtenNamedReg1 = imlInstruction->op_storeLoad.registerData; + if (imlInstruction->op_storeLoad.registerMem != PPC_REC_INVALID_REGISTER) + registersUsed->readNamedReg1 = imlInstruction->op_storeLoad.registerMem; + } + else if (imlInstruction->type == PPCREC_IML_TYPE_MEM2MEM) + { + registersUsed->readNamedReg1 = imlInstruction->op_mem2mem.src.registerMem; + registersUsed->readNamedReg2 = imlInstruction->op_mem2mem.dst.registerMem; + } + else if( imlInstruction->type == PPCREC_IML_TYPE_LOAD_INDEXED ) + { + registersUsed->writtenNamedReg1 = imlInstruction->op_storeLoad.registerData; + if( imlInstruction->op_storeLoad.registerMem != PPC_REC_INVALID_REGISTER ) + registersUsed->readNamedReg1 = imlInstruction->op_storeLoad.registerMem; + if( imlInstruction->op_storeLoad.registerMem2 != PPC_REC_INVALID_REGISTER ) + registersUsed->readNamedReg2 = imlInstruction->op_storeLoad.registerMem2; + } + else if( imlInstruction->type == PPCREC_IML_TYPE_STORE ) + { + registersUsed->readNamedReg1 = imlInstruction->op_storeLoad.registerData; + if( imlInstruction->op_storeLoad.registerMem != PPC_REC_INVALID_REGISTER ) + registersUsed->readNamedReg2 = imlInstruction->op_storeLoad.registerMem; + } + else if( imlInstruction->type == PPCREC_IML_TYPE_STORE_INDEXED ) + { + registersUsed->readNamedReg1 = imlInstruction->op_storeLoad.registerData; + if( imlInstruction->op_storeLoad.registerMem != PPC_REC_INVALID_REGISTER ) + registersUsed->readNamedReg2 = imlInstruction->op_storeLoad.registerMem; + if( imlInstruction->op_storeLoad.registerMem2 != PPC_REC_INVALID_REGISTER ) + registersUsed->readNamedReg3 = imlInstruction->op_storeLoad.registerMem2; + } + else if( imlInstruction->type == PPCREC_IML_TYPE_CR ) + { + // only affects cr register + } + else if( imlInstruction->type == PPCREC_IML_TYPE_JUMPMARK ) + { + // no effect on registers + } + else if( imlInstruction->type == PPCREC_IML_TYPE_PPC_ENTER ) + { + // no op + } + else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_R_NAME ) + { + // fpr operation + registersUsed->writtenFPR1 = imlInstruction->op_r_name.registerIndex; + } + else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_NAME_R ) + { + // fpr operation + registersUsed->readFPR1 = imlInstruction->op_r_name.registerIndex; + } + else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD ) + { + // fpr load operation + registersUsed->writtenFPR1 = imlInstruction->op_storeLoad.registerData; + // address is in gpr register + if (imlInstruction->op_storeLoad.registerMem != PPC_REC_INVALID_REGISTER) + registersUsed->readNamedReg1 = imlInstruction->op_storeLoad.registerMem; + // determine partially written result + switch (imlInstruction->op_storeLoad.mode) + { + case PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0: + case PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0_PS1: + cemu_assert_debug(imlInstruction->op_storeLoad.registerGQR != PPC_REC_INVALID_REGISTER); + registersUsed->readNamedReg2 = imlInstruction->op_storeLoad.registerGQR; + break; + case PPCREC_FPR_LD_MODE_DOUBLE_INTO_PS0: + // PS1 remains the same + registersUsed->readFPR4 = imlInstruction->op_storeLoad.registerData; + break; + case PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0_PS1: + case PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0_PS1: + case PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0: + case PPCREC_FPR_LD_MODE_PSQ_S16_PS0: + case PPCREC_FPR_LD_MODE_PSQ_S16_PS0_PS1: + case PPCREC_FPR_LD_MODE_PSQ_U16_PS0_PS1: + case PPCREC_FPR_LD_MODE_PSQ_U16_PS0: + case PPCREC_FPR_LD_MODE_PSQ_S8_PS0_PS1: + case PPCREC_FPR_LD_MODE_PSQ_U8_PS0_PS1: + case PPCREC_FPR_LD_MODE_PSQ_U8_PS0: + case PPCREC_FPR_LD_MODE_PSQ_S8_PS0: + break; + default: + cemu_assert_unimplemented(); + } + } + else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD_INDEXED ) + { + // fpr load operation + registersUsed->writtenFPR1 = imlInstruction->op_storeLoad.registerData; + // address is in gpr registers + if (imlInstruction->op_storeLoad.registerMem != PPC_REC_INVALID_REGISTER) + registersUsed->readNamedReg1 = imlInstruction->op_storeLoad.registerMem; + if (imlInstruction->op_storeLoad.registerMem2 != PPC_REC_INVALID_REGISTER) + registersUsed->readNamedReg2 = imlInstruction->op_storeLoad.registerMem2; + // determine partially written result + switch (imlInstruction->op_storeLoad.mode) + { + case PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0: + case PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0_PS1: + cemu_assert_debug(imlInstruction->op_storeLoad.registerGQR != PPC_REC_INVALID_REGISTER); + registersUsed->readNamedReg3 = imlInstruction->op_storeLoad.registerGQR; + break; + case PPCREC_FPR_LD_MODE_DOUBLE_INTO_PS0: + // PS1 remains the same + registersUsed->readFPR4 = imlInstruction->op_storeLoad.registerData; + break; + case PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0_PS1: + case PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0_PS1: + case PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0: + case PPCREC_FPR_LD_MODE_PSQ_S16_PS0: + case PPCREC_FPR_LD_MODE_PSQ_S16_PS0_PS1: + case PPCREC_FPR_LD_MODE_PSQ_U16_PS0_PS1: + case PPCREC_FPR_LD_MODE_PSQ_U16_PS0: + case PPCREC_FPR_LD_MODE_PSQ_S8_PS0_PS1: + case PPCREC_FPR_LD_MODE_PSQ_U8_PS0_PS1: + case PPCREC_FPR_LD_MODE_PSQ_U8_PS0: + break; + default: + cemu_assert_unimplemented(); + } + } + else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE ) + { + // fpr store operation + registersUsed->readFPR1 = imlInstruction->op_storeLoad.registerData; + if( imlInstruction->op_storeLoad.registerMem != PPC_REC_INVALID_REGISTER ) + registersUsed->readNamedReg1 = imlInstruction->op_storeLoad.registerMem; + // PSQ generic stores also access GQR + switch (imlInstruction->op_storeLoad.mode) + { + case PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0: + case PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0_PS1: + cemu_assert_debug(imlInstruction->op_storeLoad.registerGQR != PPC_REC_INVALID_REGISTER); + registersUsed->readNamedReg2 = imlInstruction->op_storeLoad.registerGQR; + break; + default: + break; + } + } + else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE_INDEXED ) + { + // fpr store operation + registersUsed->readFPR1 = imlInstruction->op_storeLoad.registerData; + // address is in gpr registers + if( imlInstruction->op_storeLoad.registerMem != PPC_REC_INVALID_REGISTER ) + registersUsed->readNamedReg1 = imlInstruction->op_storeLoad.registerMem; + if( imlInstruction->op_storeLoad.registerMem2 != PPC_REC_INVALID_REGISTER ) + registersUsed->readNamedReg2 = imlInstruction->op_storeLoad.registerMem2; + // PSQ generic stores also access GQR + switch (imlInstruction->op_storeLoad.mode) + { + case PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0: + case PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0_PS1: + cemu_assert_debug(imlInstruction->op_storeLoad.registerGQR != PPC_REC_INVALID_REGISTER); + registersUsed->readNamedReg3 = imlInstruction->op_storeLoad.registerGQR; + break; + default: + break; + } + } + else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R ) + { + // fpr operation + if( imlInstruction->operation == PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM_AND_TOP || + imlInstruction->operation == PPCREC_IML_OP_FPR_COPY_TOP_TO_BOTTOM_AND_TOP || + imlInstruction->operation == PPCREC_IML_OP_FPR_COPY_BOTTOM_AND_TOP_SWAPPED || + imlInstruction->operation == PPCREC_IML_OP_ASSIGN || + imlInstruction->operation == PPCREC_IML_OP_FPR_BOTTOM_FRES_TO_BOTTOM_AND_TOP || + imlInstruction->operation == PPCREC_IML_OP_FPR_NEGATE_PAIR || + imlInstruction->operation == PPCREC_IML_OP_FPR_ABS_PAIR || + imlInstruction->operation == PPCREC_IML_OP_FPR_FRES_PAIR || + imlInstruction->operation == PPCREC_IML_OP_FPR_FRSQRTE_PAIR ) + { + // operand read, result written + registersUsed->readFPR1 = imlInstruction->op_fpr_r_r.registerOperand; + registersUsed->writtenFPR1 = imlInstruction->op_fpr_r_r.registerResult; + } + else if( + imlInstruction->operation == PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM || + imlInstruction->operation == PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_TOP || + imlInstruction->operation == PPCREC_IML_OP_FPR_COPY_TOP_TO_TOP || + imlInstruction->operation == PPCREC_IML_OP_FPR_COPY_TOP_TO_BOTTOM || + imlInstruction->operation == PPCREC_IML_OP_FPR_EXPAND_BOTTOM32_TO_BOTTOM64_AND_TOP64 || + imlInstruction->operation == PPCREC_IML_OP_FPR_BOTTOM_FCTIWZ || + imlInstruction->operation == PPCREC_IML_OP_FPR_BOTTOM_RECIPROCAL_SQRT + ) + { + // operand read, result read and (partially) written + registersUsed->readFPR1 = imlInstruction->op_fpr_r_r.registerOperand; + registersUsed->readFPR4 = imlInstruction->op_fpr_r_r.registerResult; + registersUsed->writtenFPR1 = imlInstruction->op_fpr_r_r.registerResult; + } + else if( imlInstruction->operation == PPCREC_IML_OP_FPR_MULTIPLY_BOTTOM || + imlInstruction->operation == PPCREC_IML_OP_FPR_MULTIPLY_PAIR || + imlInstruction->operation == PPCREC_IML_OP_FPR_DIVIDE_BOTTOM || + imlInstruction->operation == PPCREC_IML_OP_FPR_DIVIDE_PAIR || + imlInstruction->operation == PPCREC_IML_OP_FPR_ADD_BOTTOM || + imlInstruction->operation == PPCREC_IML_OP_FPR_ADD_PAIR || + imlInstruction->operation == PPCREC_IML_OP_FPR_SUB_PAIR || + imlInstruction->operation == PPCREC_IML_OP_FPR_SUB_BOTTOM ) + { + // operand read, result read and written + registersUsed->readFPR1 = imlInstruction->op_fpr_r_r.registerOperand; + registersUsed->readFPR2 = imlInstruction->op_fpr_r_r.registerResult; + registersUsed->writtenFPR1 = imlInstruction->op_fpr_r_r.registerResult; + + } + else if(imlInstruction->operation == PPCREC_IML_OP_FPR_FCMPU_BOTTOM || + imlInstruction->operation == PPCREC_IML_OP_FPR_FCMPU_TOP || + imlInstruction->operation == PPCREC_IML_OP_FPR_FCMPO_BOTTOM) + { + // operand read, result read + registersUsed->readFPR1 = imlInstruction->op_fpr_r_r.registerOperand; + registersUsed->readFPR2 = imlInstruction->op_fpr_r_r.registerResult; + } + else + cemu_assert_unimplemented(); + } + else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R_R ) + { + // fpr operation + registersUsed->readFPR1 = imlInstruction->op_fpr_r_r_r.registerOperandA; + registersUsed->readFPR2 = imlInstruction->op_fpr_r_r_r.registerOperandB; + registersUsed->writtenFPR1 = imlInstruction->op_fpr_r_r_r.registerResult; + // handle partially written result + switch (imlInstruction->operation) + { + case PPCREC_IML_OP_FPR_MULTIPLY_BOTTOM: + case PPCREC_IML_OP_FPR_ADD_BOTTOM: + case PPCREC_IML_OP_FPR_SUB_BOTTOM: + registersUsed->readFPR4 = imlInstruction->op_fpr_r_r_r.registerResult; + break; + case PPCREC_IML_OP_FPR_SUB_PAIR: + break; + default: + cemu_assert_unimplemented(); + } + } + else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R_R_R ) + { + // fpr operation + registersUsed->readFPR1 = imlInstruction->op_fpr_r_r_r_r.registerOperandA; + registersUsed->readFPR2 = imlInstruction->op_fpr_r_r_r_r.registerOperandB; + registersUsed->readFPR3 = imlInstruction->op_fpr_r_r_r_r.registerOperandC; + registersUsed->writtenFPR1 = imlInstruction->op_fpr_r_r_r_r.registerResult; + // handle partially written result + switch (imlInstruction->operation) + { + case PPCREC_IML_OP_FPR_SELECT_BOTTOM: + registersUsed->readFPR4 = imlInstruction->op_fpr_r_r_r_r.registerResult; + break; + case PPCREC_IML_OP_FPR_SUM0: + case PPCREC_IML_OP_FPR_SUM1: + case PPCREC_IML_OP_FPR_SELECT_PAIR: + break; + default: + cemu_assert_unimplemented(); + } + } + else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_R ) + { + // fpr operation + if( imlInstruction->operation == PPCREC_IML_OP_FPR_NEGATE_BOTTOM || + imlInstruction->operation == PPCREC_IML_OP_FPR_ABS_BOTTOM || + imlInstruction->operation == PPCREC_IML_OP_FPR_NEGATIVE_ABS_BOTTOM || + imlInstruction->operation == PPCREC_IML_OP_FPR_EXPAND_BOTTOM32_TO_BOTTOM64_AND_TOP64 || + imlInstruction->operation == PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_BOTTOM || + imlInstruction->operation == PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_PAIR ) + { + registersUsed->readFPR1 = imlInstruction->op_fpr_r.registerResult; + registersUsed->writtenFPR1 = imlInstruction->op_fpr_r.registerResult; + } + else + cemu_assert_unimplemented(); + } + else + { + cemu_assert_unimplemented(); + } +} + +#define replaceRegister(__x,__r,__n) (((__x)==(__r))?(__n):(__x)) + +sint32 replaceRegisterMultiple(sint32 reg, sint32 match[4], sint32 replaced[4]) +{ + for (sint32 i = 0; i < 4; i++) + { + if(match[i] < 0) + continue; + if (reg == match[i]) + { + return replaced[i]; + } + } + return reg; +} + +void PPCRecompiler_replaceGPRRegisterUsageMultiple(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlInstruction_t* imlInstruction, sint32 gprRegisterSearched[4], sint32 gprRegisterReplaced[4]) +{ + if (imlInstruction->type == PPCREC_IML_TYPE_R_NAME) + { + imlInstruction->op_r_name.registerIndex = replaceRegisterMultiple(imlInstruction->op_r_name.registerIndex, gprRegisterSearched, gprRegisterReplaced); + } + else if (imlInstruction->type == PPCREC_IML_TYPE_NAME_R) + { + imlInstruction->op_r_name.registerIndex = replaceRegisterMultiple(imlInstruction->op_r_name.registerIndex, gprRegisterSearched, gprRegisterReplaced); + } + else if (imlInstruction->type == PPCREC_IML_TYPE_R_R) + { + imlInstruction->op_r_r.registerResult = replaceRegisterMultiple(imlInstruction->op_r_r.registerResult, gprRegisterSearched, gprRegisterReplaced); + imlInstruction->op_r_r.registerA = replaceRegisterMultiple(imlInstruction->op_r_r.registerA, gprRegisterSearched, gprRegisterReplaced); + } + else if (imlInstruction->type == PPCREC_IML_TYPE_R_S32) + { + imlInstruction->op_r_immS32.registerIndex = replaceRegisterMultiple(imlInstruction->op_r_immS32.registerIndex, gprRegisterSearched, gprRegisterReplaced); + } + else if (imlInstruction->type == PPCREC_IML_TYPE_CONDITIONAL_R_S32) + { + imlInstruction->op_conditional_r_s32.registerIndex = replaceRegisterMultiple(imlInstruction->op_conditional_r_s32.registerIndex, gprRegisterSearched, gprRegisterReplaced); + } + else if (imlInstruction->type == PPCREC_IML_TYPE_R_R_S32) + { + // in all cases result is written and other operand is read only + imlInstruction->op_r_r_s32.registerResult = replaceRegisterMultiple(imlInstruction->op_r_r_s32.registerResult, gprRegisterSearched, gprRegisterReplaced); + imlInstruction->op_r_r_s32.registerA = replaceRegisterMultiple(imlInstruction->op_r_r_s32.registerA, gprRegisterSearched, gprRegisterReplaced); + } + else if (imlInstruction->type == PPCREC_IML_TYPE_R_R_R) + { + // in all cases result is written and other operands are read only + imlInstruction->op_r_r_r.registerResult = replaceRegisterMultiple(imlInstruction->op_r_r_r.registerResult, gprRegisterSearched, gprRegisterReplaced); + imlInstruction->op_r_r_r.registerA = replaceRegisterMultiple(imlInstruction->op_r_r_r.registerA, gprRegisterSearched, gprRegisterReplaced); + imlInstruction->op_r_r_r.registerB = replaceRegisterMultiple(imlInstruction->op_r_r_r.registerB, gprRegisterSearched, gprRegisterReplaced); + } + else if (imlInstruction->type == PPCREC_IML_TYPE_CJUMP || imlInstruction->type == PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK) + { + // no effect on registers + } + else if (imlInstruction->type == PPCREC_IML_TYPE_NO_OP) + { + // no effect on registers + } + else if (imlInstruction->type == PPCREC_IML_TYPE_MACRO) + { + if (imlInstruction->operation == PPCREC_IML_MACRO_BL || imlInstruction->operation == PPCREC_IML_MACRO_B_FAR || imlInstruction->operation == PPCREC_IML_MACRO_BLR || imlInstruction->operation == PPCREC_IML_MACRO_BLRL || imlInstruction->operation == PPCREC_IML_MACRO_BCTR || imlInstruction->operation == PPCREC_IML_MACRO_BCTRL || imlInstruction->operation == PPCREC_IML_MACRO_LEAVE || imlInstruction->operation == PPCREC_IML_MACRO_DEBUGBREAK || imlInstruction->operation == PPCREC_IML_MACRO_HLE || imlInstruction->operation == PPCREC_IML_MACRO_MFTB || imlInstruction->operation == PPCREC_IML_MACRO_COUNT_CYCLES ) + { + // no effect on registers + } + else + { + cemu_assert_unimplemented(); + } + } + else if (imlInstruction->type == PPCREC_IML_TYPE_LOAD) + { + imlInstruction->op_storeLoad.registerData = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerData, gprRegisterSearched, gprRegisterReplaced); + if (imlInstruction->op_storeLoad.registerMem != PPC_REC_INVALID_REGISTER) + { + imlInstruction->op_storeLoad.registerMem = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerMem, gprRegisterSearched, gprRegisterReplaced); + } + } + else if (imlInstruction->type == PPCREC_IML_TYPE_LOAD_INDEXED) + { + imlInstruction->op_storeLoad.registerData = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerData, gprRegisterSearched, gprRegisterReplaced); + if (imlInstruction->op_storeLoad.registerMem != PPC_REC_INVALID_REGISTER) + imlInstruction->op_storeLoad.registerMem = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerMem, gprRegisterSearched, gprRegisterReplaced); + if (imlInstruction->op_storeLoad.registerMem2 != PPC_REC_INVALID_REGISTER) + imlInstruction->op_storeLoad.registerMem2 = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerMem2, gprRegisterSearched, gprRegisterReplaced); + } + else if (imlInstruction->type == PPCREC_IML_TYPE_STORE) + { + imlInstruction->op_storeLoad.registerData = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerData, gprRegisterSearched, gprRegisterReplaced); + if (imlInstruction->op_storeLoad.registerMem != PPC_REC_INVALID_REGISTER) + imlInstruction->op_storeLoad.registerMem = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerMem, gprRegisterSearched, gprRegisterReplaced); + } + else if (imlInstruction->type == PPCREC_IML_TYPE_STORE_INDEXED) + { + imlInstruction->op_storeLoad.registerData = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerData, gprRegisterSearched, gprRegisterReplaced); + if (imlInstruction->op_storeLoad.registerMem != PPC_REC_INVALID_REGISTER) + imlInstruction->op_storeLoad.registerMem = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerMem, gprRegisterSearched, gprRegisterReplaced); + if (imlInstruction->op_storeLoad.registerMem2 != PPC_REC_INVALID_REGISTER) + imlInstruction->op_storeLoad.registerMem2 = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerMem2, gprRegisterSearched, gprRegisterReplaced); + } + else if (imlInstruction->type == PPCREC_IML_TYPE_CR) + { + // only affects cr register + } + else if (imlInstruction->type == PPCREC_IML_TYPE_JUMPMARK) + { + // no effect on registers + } + else if (imlInstruction->type == PPCREC_IML_TYPE_PPC_ENTER) + { + // no op + } + else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_R_NAME) + { + + } + else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_NAME_R) + { + + } + else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD) + { + if (imlInstruction->op_storeLoad.registerMem != PPC_REC_INVALID_REGISTER) + { + imlInstruction->op_storeLoad.registerMem = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerMem, gprRegisterSearched, gprRegisterReplaced); + } + if (imlInstruction->op_storeLoad.registerGQR != PPC_REC_INVALID_REGISTER) + { + imlInstruction->op_storeLoad.registerGQR = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerGQR, gprRegisterSearched, gprRegisterReplaced); + } + } + else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD_INDEXED) + { + if (imlInstruction->op_storeLoad.registerMem != PPC_REC_INVALID_REGISTER) + { + imlInstruction->op_storeLoad.registerMem = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerMem, gprRegisterSearched, gprRegisterReplaced); + } + if (imlInstruction->op_storeLoad.registerMem2 != PPC_REC_INVALID_REGISTER) + { + imlInstruction->op_storeLoad.registerMem2 = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerMem2, gprRegisterSearched, gprRegisterReplaced); + } + if (imlInstruction->op_storeLoad.registerGQR != PPC_REC_INVALID_REGISTER) + { + imlInstruction->op_storeLoad.registerGQR = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerGQR, gprRegisterSearched, gprRegisterReplaced); + } + } + else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE) + { + if (imlInstruction->op_storeLoad.registerMem != PPC_REC_INVALID_REGISTER) + { + imlInstruction->op_storeLoad.registerMem = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerMem, gprRegisterSearched, gprRegisterReplaced); + } + if (imlInstruction->op_storeLoad.registerGQR != PPC_REC_INVALID_REGISTER) + { + imlInstruction->op_storeLoad.registerGQR = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerGQR, gprRegisterSearched, gprRegisterReplaced); + } + } + else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE_INDEXED) + { + if (imlInstruction->op_storeLoad.registerMem != PPC_REC_INVALID_REGISTER) + { + imlInstruction->op_storeLoad.registerMem = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerMem, gprRegisterSearched, gprRegisterReplaced); + } + if (imlInstruction->op_storeLoad.registerMem2 != PPC_REC_INVALID_REGISTER) + { + imlInstruction->op_storeLoad.registerMem2 = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerMem2, gprRegisterSearched, gprRegisterReplaced); + } + if (imlInstruction->op_storeLoad.registerGQR != PPC_REC_INVALID_REGISTER) + { + imlInstruction->op_storeLoad.registerGQR = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerGQR, gprRegisterSearched, gprRegisterReplaced); + } + } + else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R) + { + } + else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R_R) + { + } + else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R_R_R) + { + } + else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_R) + { + } + else + { + cemu_assert_unimplemented(); + } +} + +void PPCRecompiler_replaceFPRRegisterUsageMultiple(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlInstruction_t* imlInstruction, sint32 fprRegisterSearched[4], sint32 fprRegisterReplaced[4]) +{ + if (imlInstruction->type == PPCREC_IML_TYPE_R_NAME) + { + // not affected + } + else if (imlInstruction->type == PPCREC_IML_TYPE_NAME_R) + { + // not affected + } + else if (imlInstruction->type == PPCREC_IML_TYPE_R_R) + { + // not affected + } + else if (imlInstruction->type == PPCREC_IML_TYPE_R_S32) + { + // not affected + } + else if (imlInstruction->type == PPCREC_IML_TYPE_R_R_S32) + { + // not affected + } + else if (imlInstruction->type == PPCREC_IML_TYPE_R_R_R) + { + // not affected + } + else if (imlInstruction->type == PPCREC_IML_TYPE_CJUMP || imlInstruction->type == PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK) + { + // no effect on registers + } + else if (imlInstruction->type == PPCREC_IML_TYPE_NO_OP) + { + // no effect on registers + } + else if (imlInstruction->type == PPCREC_IML_TYPE_MACRO) + { + // not affected + } + else if (imlInstruction->type == PPCREC_IML_TYPE_LOAD) + { + // not affected + } + else if (imlInstruction->type == PPCREC_IML_TYPE_MEM2MEM) + { + // not affected + } + else if (imlInstruction->type == PPCREC_IML_TYPE_LOAD_INDEXED) + { + // not affected + } + else if (imlInstruction->type == PPCREC_IML_TYPE_STORE) + { + // not affected + } + else if (imlInstruction->type == PPCREC_IML_TYPE_STORE_INDEXED) + { + // not affected + } + else if (imlInstruction->type == PPCREC_IML_TYPE_CR) + { + // only affects cr register + } + else if (imlInstruction->type == PPCREC_IML_TYPE_JUMPMARK) + { + // no effect on registers + } + else if (imlInstruction->type == PPCREC_IML_TYPE_PPC_ENTER) + { + // no op + } + else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_R_NAME) + { + imlInstruction->op_r_name.registerIndex = replaceRegisterMultiple(imlInstruction->op_r_name.registerIndex, fprRegisterSearched, fprRegisterReplaced); + } + else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_NAME_R) + { + imlInstruction->op_r_name.registerIndex = replaceRegisterMultiple(imlInstruction->op_r_name.registerIndex, fprRegisterSearched, fprRegisterReplaced); + } + else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD) + { + imlInstruction->op_storeLoad.registerData = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerData, fprRegisterSearched, fprRegisterReplaced); + } + else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD_INDEXED) + { + imlInstruction->op_storeLoad.registerData = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerData, fprRegisterSearched, fprRegisterReplaced); + } + else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE) + { + imlInstruction->op_storeLoad.registerData = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerData, fprRegisterSearched, fprRegisterReplaced); + } + else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE_INDEXED) + { + imlInstruction->op_storeLoad.registerData = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerData, fprRegisterSearched, fprRegisterReplaced); + } + else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R) + { + imlInstruction->op_fpr_r_r.registerResult = replaceRegisterMultiple(imlInstruction->op_fpr_r_r.registerResult, fprRegisterSearched, fprRegisterReplaced); + imlInstruction->op_fpr_r_r.registerOperand = replaceRegisterMultiple(imlInstruction->op_fpr_r_r.registerOperand, fprRegisterSearched, fprRegisterReplaced); + } + else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R_R) + { + imlInstruction->op_fpr_r_r_r.registerResult = replaceRegisterMultiple(imlInstruction->op_fpr_r_r_r.registerResult, fprRegisterSearched, fprRegisterReplaced); + imlInstruction->op_fpr_r_r_r.registerOperandA = replaceRegisterMultiple(imlInstruction->op_fpr_r_r_r.registerOperandA, fprRegisterSearched, fprRegisterReplaced); + imlInstruction->op_fpr_r_r_r.registerOperandB = replaceRegisterMultiple(imlInstruction->op_fpr_r_r_r.registerOperandB, fprRegisterSearched, fprRegisterReplaced); + } + else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R_R_R) + { + imlInstruction->op_fpr_r_r_r_r.registerResult = replaceRegisterMultiple(imlInstruction->op_fpr_r_r_r_r.registerResult, fprRegisterSearched, fprRegisterReplaced); + imlInstruction->op_fpr_r_r_r_r.registerOperandA = replaceRegisterMultiple(imlInstruction->op_fpr_r_r_r_r.registerOperandA, fprRegisterSearched, fprRegisterReplaced); + imlInstruction->op_fpr_r_r_r_r.registerOperandB = replaceRegisterMultiple(imlInstruction->op_fpr_r_r_r_r.registerOperandB, fprRegisterSearched, fprRegisterReplaced); + imlInstruction->op_fpr_r_r_r_r.registerOperandC = replaceRegisterMultiple(imlInstruction->op_fpr_r_r_r_r.registerOperandC, fprRegisterSearched, fprRegisterReplaced); + } + else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_R) + { + imlInstruction->op_fpr_r.registerResult = replaceRegisterMultiple(imlInstruction->op_fpr_r.registerResult, fprRegisterSearched, fprRegisterReplaced); + } + else + { + cemu_assert_unimplemented(); + } +} + +void PPCRecompiler_replaceFPRRegisterUsage(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlInstruction_t* imlInstruction, sint32 fprRegisterSearched, sint32 fprRegisterReplaced) +{ + if( imlInstruction->type == PPCREC_IML_TYPE_R_NAME ) + { + // not affected + } + else if( imlInstruction->type == PPCREC_IML_TYPE_NAME_R ) + { + // not affected + } + else if( imlInstruction->type == PPCREC_IML_TYPE_R_R ) + { + // not affected + } + else if( imlInstruction->type == PPCREC_IML_TYPE_R_S32 ) + { + // not affected + } + else if( imlInstruction->type == PPCREC_IML_TYPE_R_R_S32 ) + { + // not affected + } + else if( imlInstruction->type == PPCREC_IML_TYPE_R_R_R ) + { + // not affected + } + else if( imlInstruction->type == PPCREC_IML_TYPE_CJUMP || imlInstruction->type == PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK ) + { + // no effect on registers + } + else if( imlInstruction->type == PPCREC_IML_TYPE_NO_OP ) + { + // no effect on registers + } + else if( imlInstruction->type == PPCREC_IML_TYPE_MACRO ) + { + // not affected + } + else if( imlInstruction->type == PPCREC_IML_TYPE_LOAD ) + { + // not affected + } + else if (imlInstruction->type == PPCREC_IML_TYPE_MEM2MEM) + { + // not affected + } + else if( imlInstruction->type == PPCREC_IML_TYPE_LOAD_INDEXED ) + { + // not affected + } + else if( imlInstruction->type == PPCREC_IML_TYPE_STORE ) + { + // not affected + } + else if( imlInstruction->type == PPCREC_IML_TYPE_STORE_INDEXED ) + { + // not affected + } + else if( imlInstruction->type == PPCREC_IML_TYPE_CR ) + { + // only affects cr register + } + else if( imlInstruction->type == PPCREC_IML_TYPE_JUMPMARK ) + { + // no effect on registers + } + else if( imlInstruction->type == PPCREC_IML_TYPE_PPC_ENTER ) + { + // no op + } + else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_R_NAME ) + { + imlInstruction->op_r_name.registerIndex = replaceRegister(imlInstruction->op_r_name.registerIndex, fprRegisterSearched, fprRegisterReplaced); + } + else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_NAME_R ) + { + imlInstruction->op_r_name.registerIndex = replaceRegister(imlInstruction->op_r_name.registerIndex, fprRegisterSearched, fprRegisterReplaced); + } + else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD ) + { + imlInstruction->op_storeLoad.registerData = replaceRegister(imlInstruction->op_storeLoad.registerData, fprRegisterSearched, fprRegisterReplaced); + } + else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD_INDEXED ) + { + imlInstruction->op_storeLoad.registerData = replaceRegister(imlInstruction->op_storeLoad.registerData, fprRegisterSearched, fprRegisterReplaced); + } + else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE ) + { + imlInstruction->op_storeLoad.registerData = replaceRegister(imlInstruction->op_storeLoad.registerData, fprRegisterSearched, fprRegisterReplaced); + } + else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE_INDEXED ) + { + imlInstruction->op_storeLoad.registerData = replaceRegister(imlInstruction->op_storeLoad.registerData, fprRegisterSearched, fprRegisterReplaced); + } + else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R ) + { + imlInstruction->op_fpr_r_r.registerResult = replaceRegister(imlInstruction->op_fpr_r_r.registerResult, fprRegisterSearched, fprRegisterReplaced); + imlInstruction->op_fpr_r_r.registerOperand = replaceRegister(imlInstruction->op_fpr_r_r.registerOperand, fprRegisterSearched, fprRegisterReplaced); + } + else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R_R ) + { + imlInstruction->op_fpr_r_r_r.registerResult = replaceRegister(imlInstruction->op_fpr_r_r_r.registerResult, fprRegisterSearched, fprRegisterReplaced); + imlInstruction->op_fpr_r_r_r.registerOperandA = replaceRegister(imlInstruction->op_fpr_r_r_r.registerOperandA, fprRegisterSearched, fprRegisterReplaced); + imlInstruction->op_fpr_r_r_r.registerOperandB = replaceRegister(imlInstruction->op_fpr_r_r_r.registerOperandB, fprRegisterSearched, fprRegisterReplaced); + } + else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R_R_R ) + { + imlInstruction->op_fpr_r_r_r_r.registerResult = replaceRegister(imlInstruction->op_fpr_r_r_r_r.registerResult, fprRegisterSearched, fprRegisterReplaced); + imlInstruction->op_fpr_r_r_r_r.registerOperandA = replaceRegister(imlInstruction->op_fpr_r_r_r_r.registerOperandA, fprRegisterSearched, fprRegisterReplaced); + imlInstruction->op_fpr_r_r_r_r.registerOperandB = replaceRegister(imlInstruction->op_fpr_r_r_r_r.registerOperandB, fprRegisterSearched, fprRegisterReplaced); + imlInstruction->op_fpr_r_r_r_r.registerOperandC = replaceRegister(imlInstruction->op_fpr_r_r_r_r.registerOperandC, fprRegisterSearched, fprRegisterReplaced); + } + else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_R ) + { + imlInstruction->op_fpr_r.registerResult = replaceRegister(imlInstruction->op_fpr_r.registerResult, fprRegisterSearched, fprRegisterReplaced); + } + else + { + cemu_assert_unimplemented(); + } +} + +typedef struct +{ + struct + { + sint32 instructionIndex; + sint32 registerPreviousName; + sint32 registerNewName; + sint32 index; // new index + sint32 previousIndex; // previous index (always out of range) + bool nameMustBeMaintained; // must be stored before replacement and loaded after replacement ends + }replacedRegisterEntry[PPC_X64_GPR_USABLE_REGISTERS]; + sint32 count; +}replacedRegisterTracker_t; + +bool PPCRecompiler_checkIfGPRRegisterIsAccessed(PPCImlOptimizerUsedRegisters_t* registersUsed, sint32 gprRegister) +{ + if( registersUsed->readNamedReg1 == gprRegister ) + return true; + if( registersUsed->readNamedReg2 == gprRegister ) + return true; + if( registersUsed->readNamedReg3 == gprRegister ) + return true; + if( registersUsed->writtenNamedReg1 == gprRegister ) + return true; + return false; +} + +/* + * Returns index of register to replace + * If no register needs to be replaced, -1 is returned + */ +sint32 PPCRecompiler_getNextRegisterToReplace(PPCImlOptimizerUsedRegisters_t* registersUsed) +{ + // get index of register to replace + sint32 gprToReplace = -1; + if( registersUsed->readNamedReg1 >= PPC_X64_GPR_USABLE_REGISTERS ) + gprToReplace = registersUsed->readNamedReg1; + else if( registersUsed->readNamedReg2 >= PPC_X64_GPR_USABLE_REGISTERS ) + gprToReplace = registersUsed->readNamedReg2; + else if( registersUsed->readNamedReg3 >= PPC_X64_GPR_USABLE_REGISTERS ) + gprToReplace = registersUsed->readNamedReg3; + else if( registersUsed->writtenNamedReg1 >= PPC_X64_GPR_USABLE_REGISTERS ) + gprToReplace = registersUsed->writtenNamedReg1; + // return + return gprToReplace; +} + +bool PPCRecompiler_findAvailableRegisterDepr(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegment, sint32 imlIndexStart, replacedRegisterTracker_t* replacedRegisterTracker, sint32* registerIndex, sint32* registerName, bool* isUsed) +{ + PPCImlOptimizerUsedRegisters_t registersUsed; + PPCRecompiler_checkRegisterUsage(ppcImlGenContext, imlSegment->imlList+imlIndexStart, ®istersUsed); + // mask all registers used by this instruction + uint32 instructionReservedRegisterMask = 0;//(1<<(PPC_X64_GPR_USABLE_REGISTERS+1))-1; + if( registersUsed.readNamedReg1 != -1 ) + instructionReservedRegisterMask |= (1<<(registersUsed.readNamedReg1)); + if( registersUsed.readNamedReg2 != -1 ) + instructionReservedRegisterMask |= (1<<(registersUsed.readNamedReg2)); + if( registersUsed.readNamedReg3 != -1 ) + instructionReservedRegisterMask |= (1<<(registersUsed.readNamedReg3)); + if( registersUsed.writtenNamedReg1 != -1 ) + instructionReservedRegisterMask |= (1<<(registersUsed.writtenNamedReg1)); + // mask all registers that are reserved for other replacements + uint32 replacementReservedRegisterMask = 0; + for(sint32 i=0; i<replacedRegisterTracker->count; i++) + { + replacementReservedRegisterMask |= (1<<replacedRegisterTracker->replacedRegisterEntry[i].index); + } + + // potential improvement: Scan ahead a few instructions and look for registers that are the least used (or ideally never used) + + // pick available register + const uint32 allRegisterMask = (1<<(PPC_X64_GPR_USABLE_REGISTERS+1))-1; // mask with set bit for every register + uint32 reservedRegisterMask = instructionReservedRegisterMask | replacementReservedRegisterMask; + cemu_assert(instructionReservedRegisterMask != allRegisterMask); // no usable register! (Need to store a register from the replacedRegisterTracker) + sint32 usedRegisterIndex = -1; + for(sint32 i=0; i<PPC_X64_GPR_USABLE_REGISTERS; i++) + { + if( (reservedRegisterMask&(1<<i)) == 0 ) + { + if( (instructionReservedRegisterMask&(1<<i)) == 0 && ppcImlGenContext->mappedRegister[i] != -1 ) + { + // register is reserved by segment -> In use + *isUsed = true; + *registerName = ppcImlGenContext->mappedRegister[i]; + } + else + { + *isUsed = false; + *registerName = -1; + } + *registerIndex = i; + return true; + } + } + return false; + +} + +bool PPCRecompiler_hasSuffixInstruction(PPCRecImlSegment_t* imlSegment) +{ + if( imlSegment->imlListCount == 0 ) + return false; + PPCRecImlInstruction_t* imlInstruction = imlSegment->imlList+imlSegment->imlListCount-1; + if( imlInstruction->type == PPCREC_IML_TYPE_MACRO && (imlInstruction->operation == PPCREC_IML_MACRO_BLR || imlInstruction->operation == PPCREC_IML_MACRO_BCTR) || + imlInstruction->type == PPCREC_IML_TYPE_MACRO && imlInstruction->operation == PPCREC_IML_MACRO_BL || + imlInstruction->type == PPCREC_IML_TYPE_MACRO && imlInstruction->operation == PPCREC_IML_MACRO_B_FAR || + imlInstruction->type == PPCREC_IML_TYPE_MACRO && imlInstruction->operation == PPCREC_IML_MACRO_BLRL || + imlInstruction->type == PPCREC_IML_TYPE_MACRO && imlInstruction->operation == PPCREC_IML_MACRO_BCTRL || + imlInstruction->type == PPCREC_IML_TYPE_MACRO && imlInstruction->operation == PPCREC_IML_MACRO_LEAVE || + imlInstruction->type == PPCREC_IML_TYPE_MACRO && imlInstruction->operation == PPCREC_IML_MACRO_HLE || + imlInstruction->type == PPCREC_IML_TYPE_MACRO && imlInstruction->operation == PPCREC_IML_MACRO_MFTB || + imlInstruction->type == PPCREC_IML_TYPE_PPC_ENTER || + imlInstruction->type == PPCREC_IML_TYPE_CJUMP || + imlInstruction->type == PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK ) + return true; + return false; +} + +void PPCRecompiler_storeReplacedRegister(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegment, replacedRegisterTracker_t* replacedRegisterTracker, sint32 registerTrackerIndex, sint32* imlIndex) +{ + // store register + sint32 imlIndexEdit = *imlIndex; + PPCRecompiler_pushBackIMLInstructions(imlSegment, imlIndexEdit, 1); + // name_unusedRegister = unusedRegister + PPCRecImlInstruction_t* imlInstructionItr = imlSegment->imlList+(imlIndexEdit+0); + memset(imlInstructionItr, 0x00, sizeof(PPCRecImlInstruction_t)); + imlInstructionItr->type = PPCREC_IML_TYPE_NAME_R; + imlInstructionItr->crRegister = PPC_REC_INVALID_REGISTER; + imlInstructionItr->operation = PPCREC_IML_OP_ASSIGN; + imlInstructionItr->op_r_name.registerIndex = replacedRegisterTracker->replacedRegisterEntry[registerTrackerIndex].index; + imlInstructionItr->op_r_name.name = replacedRegisterTracker->replacedRegisterEntry[registerTrackerIndex].registerNewName; + imlInstructionItr->op_r_name.copyWidth = 32; + imlInstructionItr->op_r_name.flags = 0; + imlIndexEdit++; + // load new register if required + if( replacedRegisterTracker->replacedRegisterEntry[registerTrackerIndex].nameMustBeMaintained ) + { + PPCRecompiler_pushBackIMLInstructions(imlSegment, imlIndexEdit, 1); + PPCRecImlInstruction_t* imlInstructionItr = imlSegment->imlList+(imlIndexEdit+0); + memset(imlInstructionItr, 0x00, sizeof(PPCRecImlInstruction_t)); + imlInstructionItr->type = PPCREC_IML_TYPE_R_NAME; + imlInstructionItr->crRegister = PPC_REC_INVALID_REGISTER; + imlInstructionItr->operation = PPCREC_IML_OP_ASSIGN; + imlInstructionItr->op_r_name.registerIndex = replacedRegisterTracker->replacedRegisterEntry[registerTrackerIndex].index; + imlInstructionItr->op_r_name.name = replacedRegisterTracker->replacedRegisterEntry[registerTrackerIndex].registerPreviousName;//ppcImlGenContext->mappedRegister[replacedRegisterTracker.replacedRegisterEntry[i].index]; + imlInstructionItr->op_r_name.copyWidth = 32; + imlInstructionItr->op_r_name.flags = 0; + imlIndexEdit += 1; + } + // move last entry to current one + memcpy(replacedRegisterTracker->replacedRegisterEntry+registerTrackerIndex, replacedRegisterTracker->replacedRegisterEntry+replacedRegisterTracker->count-1, sizeof(replacedRegisterTracker->replacedRegisterEntry[0])); + replacedRegisterTracker->count--; + *imlIndex = imlIndexEdit; +} + +bool PPCRecompiler_reduceNumberOfFPRRegisters(ppcImlGenContext_t* ppcImlGenContext) +{ + // only xmm0 to xmm14 may be used, xmm15 is reserved + // this method will reduce the number of fpr registers used + // inefficient algorithm for optimizing away excess registers + // we simply load, use and store excess registers into other unused registers when we need to + // first we remove all name load and store instructions that involve out-of-bounds registers + for(sint32 s=0; s<ppcImlGenContext->segmentListCount; s++) + { + PPCRecImlSegment_t* imlSegment = ppcImlGenContext->segmentList[s]; + sint32 imlIndex = 0; + while( imlIndex < imlSegment->imlListCount ) + { + PPCRecImlInstruction_t* imlInstructionItr = imlSegment->imlList+imlIndex; + if( imlInstructionItr->type == PPCREC_IML_TYPE_FPR_R_NAME || imlInstructionItr->type == PPCREC_IML_TYPE_FPR_NAME_R ) + { + if( imlInstructionItr->op_r_name.registerIndex >= PPC_X64_FPR_USABLE_REGISTERS ) + { + // convert to NO-OP instruction + imlInstructionItr->type = PPCREC_IML_TYPE_NO_OP; + imlInstructionItr->associatedPPCAddress = 0; + } + } + imlIndex++; + } + } + // replace registers + for(sint32 s=0; s<ppcImlGenContext->segmentListCount; s++) + { + PPCRecImlSegment_t* imlSegment = ppcImlGenContext->segmentList[s]; + sint32 imlIndex = 0; + while( imlIndex < imlSegment->imlListCount ) + { + PPCImlOptimizerUsedRegisters_t registersUsed; + while( true ) + { + PPCRecompiler_checkRegisterUsage(ppcImlGenContext, imlSegment->imlList+imlIndex, ®istersUsed); + if( registersUsed.readFPR1 >= PPC_X64_FPR_USABLE_REGISTERS || registersUsed.readFPR2 >= PPC_X64_FPR_USABLE_REGISTERS || registersUsed.readFPR3 >= PPC_X64_FPR_USABLE_REGISTERS || registersUsed.readFPR4 >= PPC_X64_FPR_USABLE_REGISTERS || registersUsed.writtenFPR1 >= PPC_X64_FPR_USABLE_REGISTERS ) + { + // get index of register to replace + sint32 fprToReplace = -1; + if( registersUsed.readFPR1 >= PPC_X64_FPR_USABLE_REGISTERS ) + fprToReplace = registersUsed.readFPR1; + else if( registersUsed.readFPR2 >= PPC_X64_FPR_USABLE_REGISTERS ) + fprToReplace = registersUsed.readFPR2; + else if (registersUsed.readFPR3 >= PPC_X64_FPR_USABLE_REGISTERS) + fprToReplace = registersUsed.readFPR3; + else if (registersUsed.readFPR4 >= PPC_X64_FPR_USABLE_REGISTERS) + fprToReplace = registersUsed.readFPR4; + else if( registersUsed.writtenFPR1 >= PPC_X64_FPR_USABLE_REGISTERS ) + fprToReplace = registersUsed.writtenFPR1; + // generate mask of useable registers + uint8 useableRegisterMask = 0x7F; // lowest bit is fpr register 0 + if( registersUsed.readFPR1 != -1 ) + useableRegisterMask &= ~(1<<(registersUsed.readFPR1)); + if( registersUsed.readFPR2 != -1 ) + useableRegisterMask &= ~(1<<(registersUsed.readFPR2)); + if (registersUsed.readFPR3 != -1) + useableRegisterMask &= ~(1 << (registersUsed.readFPR3)); + if (registersUsed.readFPR4 != -1) + useableRegisterMask &= ~(1 << (registersUsed.readFPR4)); + if( registersUsed.writtenFPR1 != -1 ) + useableRegisterMask &= ~(1<<(registersUsed.writtenFPR1)); + // get highest unused register index (0-6 range) + sint32 unusedRegisterIndex = -1; + for(sint32 f=0; f<PPC_X64_FPR_USABLE_REGISTERS; f++) + { + if( useableRegisterMask&(1<<f) ) + { + unusedRegisterIndex = f; + } + } + if( unusedRegisterIndex == -1 ) + assert_dbg(); + // determine if the placeholder register is actually used (if not we must not load/store it) + uint32 unusedRegisterName = ppcImlGenContext->mappedFPRRegister[unusedRegisterIndex]; + bool replacedRegisterIsUsed = true; + if( unusedRegisterName >= PPCREC_NAME_FPR0 && unusedRegisterName < (PPCREC_NAME_FPR0+32) ) + { + replacedRegisterIsUsed = imlSegment->ppcFPRUsed[unusedRegisterName-PPCREC_NAME_FPR0]; + } + // replace registers that are out of range + PPCRecompiler_replaceFPRRegisterUsage(ppcImlGenContext, imlSegment->imlList+imlIndex, fprToReplace, unusedRegisterIndex); + // add load/store name after instruction + PPCRecompiler_pushBackIMLInstructions(imlSegment, imlIndex+1, 2); + // add load/store before current instruction + PPCRecompiler_pushBackIMLInstructions(imlSegment, imlIndex, 2); + // name_unusedRegister = unusedRegister + PPCRecImlInstruction_t* imlInstructionItr = imlSegment->imlList+(imlIndex+0); + memset(imlInstructionItr, 0x00, sizeof(PPCRecImlInstruction_t)); + if( replacedRegisterIsUsed ) + { + imlInstructionItr->type = PPCREC_IML_TYPE_FPR_NAME_R; + imlInstructionItr->operation = PPCREC_IML_OP_ASSIGN; + imlInstructionItr->op_r_name.registerIndex = unusedRegisterIndex; + imlInstructionItr->op_r_name.name = ppcImlGenContext->mappedFPRRegister[unusedRegisterIndex]; + imlInstructionItr->op_r_name.copyWidth = 32; + imlInstructionItr->op_r_name.flags = 0; + } + else + imlInstructionItr->type = PPCREC_IML_TYPE_NO_OP; + imlInstructionItr = imlSegment->imlList+(imlIndex+1); + memset(imlInstructionItr, 0x00, sizeof(PPCRecImlInstruction_t)); + imlInstructionItr->type = PPCREC_IML_TYPE_FPR_R_NAME; + imlInstructionItr->operation = PPCREC_IML_OP_ASSIGN; + imlInstructionItr->op_r_name.registerIndex = unusedRegisterIndex; + imlInstructionItr->op_r_name.name = ppcImlGenContext->mappedFPRRegister[fprToReplace]; + imlInstructionItr->op_r_name.copyWidth = 32; + imlInstructionItr->op_r_name.flags = 0; + // name_gprToReplace = unusedRegister + imlInstructionItr = imlSegment->imlList+(imlIndex+3); + memset(imlInstructionItr, 0x00, sizeof(PPCRecImlInstruction_t)); + imlInstructionItr->type = PPCREC_IML_TYPE_FPR_NAME_R; + imlInstructionItr->operation = PPCREC_IML_OP_ASSIGN; + imlInstructionItr->op_r_name.registerIndex = unusedRegisterIndex; + imlInstructionItr->op_r_name.name = ppcImlGenContext->mappedFPRRegister[fprToReplace]; + imlInstructionItr->op_r_name.copyWidth = 32; + imlInstructionItr->op_r_name.flags = 0; + // unusedRegister = name_unusedRegister + imlInstructionItr = imlSegment->imlList+(imlIndex+4); + memset(imlInstructionItr, 0x00, sizeof(PPCRecImlInstruction_t)); + if( replacedRegisterIsUsed ) + { + imlInstructionItr->type = PPCREC_IML_TYPE_FPR_R_NAME; + imlInstructionItr->operation = PPCREC_IML_OP_ASSIGN; + imlInstructionItr->op_r_name.registerIndex = unusedRegisterIndex; + imlInstructionItr->op_r_name.name = ppcImlGenContext->mappedFPRRegister[unusedRegisterIndex]; + imlInstructionItr->op_r_name.copyWidth = 32; + imlInstructionItr->op_r_name.flags = 0; + } + else + imlInstructionItr->type = PPCREC_IML_TYPE_NO_OP; + } + else + break; + } + imlIndex++; + } + } + return true; +} + +typedef struct +{ + bool isActive; + uint32 virtualReg; + sint32 lastUseIndex; +}ppcRecRegisterMapping_t; + +typedef struct +{ + ppcRecRegisterMapping_t currentMapping[PPC_X64_FPR_USABLE_REGISTERS]; + sint32 ppcRegToMapping[64]; + sint32 currentUseIndex; +}ppcRecManageRegisters_t; + +ppcRecRegisterMapping_t* PPCRecompiler_findAvailableRegisterDepr(ppcRecManageRegisters_t* rCtx, PPCImlOptimizerUsedRegisters_t* instructionUsedRegisters) +{ + // find free register + for (sint32 i = 0; i < PPC_X64_FPR_USABLE_REGISTERS; i++) + { + if (rCtx->currentMapping[i].isActive == false) + { + rCtx->currentMapping[i].isActive = true; + rCtx->currentMapping[i].virtualReg = -1; + rCtx->currentMapping[i].lastUseIndex = rCtx->currentUseIndex; + return rCtx->currentMapping + i; + } + } + // all registers are used + return nullptr; +} + +ppcRecRegisterMapping_t* PPCRecompiler_findUnloadableRegister(ppcRecManageRegisters_t* rCtx, PPCImlOptimizerUsedRegisters_t* instructionUsedRegisters, uint32 unloadLockedMask) +{ + // find unloadable register (with lowest lastUseIndex) + sint32 unloadIndex = -1; + sint32 unloadIndexLastUse = 0x7FFFFFFF; + for (sint32 i = 0; i < PPC_X64_FPR_USABLE_REGISTERS; i++) + { + if (rCtx->currentMapping[i].isActive == false) + continue; + if( (unloadLockedMask&(1<<i)) != 0 ) + continue; + uint32 virtualReg = rCtx->currentMapping[i].virtualReg; + bool isReserved = false; + for (sint32 f = 0; f < 4; f++) + { + if (virtualReg == (sint32)instructionUsedRegisters->fpr[f]) + { + isReserved = true; + break; + } + } + if (isReserved) + continue; + if (rCtx->currentMapping[i].lastUseIndex < unloadIndexLastUse) + { + unloadIndexLastUse = rCtx->currentMapping[i].lastUseIndex; + unloadIndex = i; + } + } + cemu_assert(unloadIndex != -1); + return rCtx->currentMapping + unloadIndex; +} + +bool PPCRecompiler_manageFPRRegistersForSegment(ppcImlGenContext_t* ppcImlGenContext, sint32 segmentIndex) +{ + ppcRecManageRegisters_t rCtx = { 0 }; + for (sint32 i = 0; i < 64; i++) + rCtx.ppcRegToMapping[i] = -1; + PPCRecImlSegment_t* imlSegment = ppcImlGenContext->segmentList[segmentIndex]; + sint32 idx = 0; + sint32 currentUseIndex = 0; + PPCImlOptimizerUsedRegisters_t registersUsed; + while (idx < imlSegment->imlListCount) + { + if ( PPCRecompiler_isSuffixInstruction(imlSegment->imlList + idx) ) + break; + PPCRecompiler_checkRegisterUsage(ppcImlGenContext, imlSegment->imlList + idx, ®istersUsed); + sint32 fprMatch[4]; + sint32 fprReplace[4]; + fprMatch[0] = -1; + fprMatch[1] = -1; + fprMatch[2] = -1; + fprMatch[3] = -1; + fprReplace[0] = -1; + fprReplace[1] = -1; + fprReplace[2] = -1; + fprReplace[3] = -1; + // generate a mask of registers that we may not free + sint32 numReplacedOperands = 0; + uint32 unloadLockedMask = 0; + for (sint32 f = 0; f < 5; f++) + { + sint32 virtualFpr; + if (f == 0) + virtualFpr = registersUsed.readFPR1; + else if (f == 1) + virtualFpr = registersUsed.readFPR2; + else if (f == 2) + virtualFpr = registersUsed.readFPR3; + else if (f == 3) + virtualFpr = registersUsed.readFPR4; + else if (f == 4) + virtualFpr = registersUsed.writtenFPR1; + if( virtualFpr < 0 ) + continue; + cemu_assert_debug(virtualFpr < 64); + // check if this virtual FPR is already loaded in any real register + ppcRecRegisterMapping_t* regMapping; + if (rCtx.ppcRegToMapping[virtualFpr] == -1) + { + // not loaded + // find available register + while (true) + { + regMapping = PPCRecompiler_findAvailableRegisterDepr(&rCtx, ®istersUsed); + if (regMapping == NULL) + { + // unload least recently used register and try again + ppcRecRegisterMapping_t* unloadRegMapping = PPCRecompiler_findUnloadableRegister(&rCtx, ®istersUsed, unloadLockedMask); + // mark as locked + unloadLockedMask |= (1<<(unloadRegMapping- rCtx.currentMapping)); + // create unload instruction + PPCRecompiler_pushBackIMLInstructions(imlSegment, idx, 1); + PPCRecImlInstruction_t* imlInstructionTemp = imlSegment->imlList + idx; + memset(imlInstructionTemp, 0x00, sizeof(PPCRecImlInstruction_t)); + imlInstructionTemp->type = PPCREC_IML_TYPE_FPR_NAME_R; + imlInstructionTemp->operation = PPCREC_IML_OP_ASSIGN; + imlInstructionTemp->op_r_name.registerIndex = (uint8)(unloadRegMapping - rCtx.currentMapping); + imlInstructionTemp->op_r_name.name = ppcImlGenContext->mappedFPRRegister[unloadRegMapping->virtualReg]; + imlInstructionTemp->op_r_name.copyWidth = 32; + imlInstructionTemp->op_r_name.flags = 0; + idx++; + // update mapping + unloadRegMapping->isActive = false; + rCtx.ppcRegToMapping[unloadRegMapping->virtualReg] = -1; + } + else + break; + } + // create load instruction + PPCRecompiler_pushBackIMLInstructions(imlSegment, idx, 1); + PPCRecImlInstruction_t* imlInstructionTemp = imlSegment->imlList + idx; + memset(imlInstructionTemp, 0x00, sizeof(PPCRecImlInstruction_t)); + imlInstructionTemp->type = PPCREC_IML_TYPE_FPR_R_NAME; + imlInstructionTemp->operation = PPCREC_IML_OP_ASSIGN; + imlInstructionTemp->op_r_name.registerIndex = (uint8)(regMapping-rCtx.currentMapping); + imlInstructionTemp->op_r_name.name = ppcImlGenContext->mappedFPRRegister[virtualFpr]; + imlInstructionTemp->op_r_name.copyWidth = 32; + imlInstructionTemp->op_r_name.flags = 0; + idx++; + // update mapping + regMapping->virtualReg = virtualFpr; + rCtx.ppcRegToMapping[virtualFpr] = (sint32)(regMapping - rCtx.currentMapping); + regMapping->lastUseIndex = rCtx.currentUseIndex; + rCtx.currentUseIndex++; + } + else + { + regMapping = rCtx.currentMapping + rCtx.ppcRegToMapping[virtualFpr]; + regMapping->lastUseIndex = rCtx.currentUseIndex; + rCtx.currentUseIndex++; + } + // replace FPR + bool entryFound = false; + for (sint32 t = 0; t < numReplacedOperands; t++) + { + if (fprMatch[t] == virtualFpr) + { + cemu_assert_debug(fprReplace[t] == (regMapping - rCtx.currentMapping)); + entryFound = true; + break; + } + } + if (entryFound == false) + { + cemu_assert_debug(numReplacedOperands != 4); + fprMatch[numReplacedOperands] = virtualFpr; + fprReplace[numReplacedOperands] = (sint32)(regMapping - rCtx.currentMapping); + numReplacedOperands++; + } + } + if (numReplacedOperands > 0) + { + PPCRecompiler_replaceFPRRegisterUsageMultiple(ppcImlGenContext, imlSegment->imlList + idx, fprMatch, fprReplace); + } + // next + idx++; + } + // count loaded registers + sint32 numLoadedRegisters = 0; + for (sint32 i = 0; i < PPC_X64_FPR_USABLE_REGISTERS; i++) + { + if (rCtx.currentMapping[i].isActive) + numLoadedRegisters++; + } + // store all loaded registers + if (numLoadedRegisters > 0) + { + PPCRecompiler_pushBackIMLInstructions(imlSegment, idx, numLoadedRegisters); + for (sint32 i = 0; i < PPC_X64_FPR_USABLE_REGISTERS; i++) + { + if (rCtx.currentMapping[i].isActive == false) + continue; + PPCRecImlInstruction_t* imlInstructionTemp = imlSegment->imlList + idx; + memset(imlInstructionTemp, 0x00, sizeof(PPCRecImlInstruction_t)); + imlInstructionTemp->type = PPCREC_IML_TYPE_FPR_NAME_R; + imlInstructionTemp->operation = PPCREC_IML_OP_ASSIGN; + imlInstructionTemp->op_r_name.registerIndex = i; + imlInstructionTemp->op_r_name.name = ppcImlGenContext->mappedFPRRegister[rCtx.currentMapping[i].virtualReg]; + imlInstructionTemp->op_r_name.copyWidth = 32; + imlInstructionTemp->op_r_name.flags = 0; + idx++; + } + } + return true; +} + +bool PPCRecompiler_manageFPRRegisters(ppcImlGenContext_t* ppcImlGenContext) +{ + for (sint32 s = 0; s < ppcImlGenContext->segmentListCount; s++) + { + if (PPCRecompiler_manageFPRRegistersForSegment(ppcImlGenContext, s) == false) + return false; + } + return true; +} + + +/* + * Returns true if the loaded value is guaranteed to be overwritten + */ +bool PPCRecompiler_trackRedundantNameLoadInstruction(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegment, sint32 startIndex, PPCRecImlInstruction_t* nameStoreInstruction, sint32 scanDepth) +{ + sint16 registerIndex = nameStoreInstruction->op_r_name.registerIndex; + for(sint32 i=startIndex; i<imlSegment->imlListCount; i++) + { + PPCRecImlInstruction_t* imlInstruction = imlSegment->imlList+i; + //nameStoreInstruction->op_r_name.registerIndex + PPCImlOptimizerUsedRegisters_t registersUsed; + PPCRecompiler_checkRegisterUsage(ppcImlGenContext, imlSegment->imlList+i, ®istersUsed); + if( registersUsed.readNamedReg1 == registerIndex || registersUsed.readNamedReg2 == registerIndex || registersUsed.readNamedReg3 == registerIndex ) + return false; + if( registersUsed.writtenNamedReg1 == registerIndex ) + return true; + } + // todo: Scan next segment(s) + return false; +} + +/* + * Returns true if the loaded value is guaranteed to be overwritten + */ +bool PPCRecompiler_trackRedundantFPRNameLoadInstruction(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegment, sint32 startIndex, PPCRecImlInstruction_t* nameStoreInstruction, sint32 scanDepth) +{ + sint16 registerIndex = nameStoreInstruction->op_r_name.registerIndex; + for(sint32 i=startIndex; i<imlSegment->imlListCount; i++) + { + PPCRecImlInstruction_t* imlInstruction = imlSegment->imlList+i; + PPCImlOptimizerUsedRegisters_t registersUsed; + PPCRecompiler_checkRegisterUsage(ppcImlGenContext, imlSegment->imlList+i, ®istersUsed); + if( registersUsed.readFPR1 == registerIndex || registersUsed.readFPR2 == registerIndex || registersUsed.readFPR3 == registerIndex || registersUsed.readFPR4 == registerIndex) + return false; + if( registersUsed.writtenFPR1 == registerIndex ) + return true; + } + // todo: Scan next segment(s) + return false; +} + +/* + * Returns true if the loaded name is never changed + */ +bool PPCRecompiler_trackRedundantNameStoreInstruction(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegment, sint32 startIndex, PPCRecImlInstruction_t* nameStoreInstruction, sint32 scanDepth) +{ + sint16 registerIndex = nameStoreInstruction->op_r_name.registerIndex; + for(sint32 i=startIndex; i>=0; i--) + { + PPCRecImlInstruction_t* imlInstruction = imlSegment->imlList+i; + PPCImlOptimizerUsedRegisters_t registersUsed; + PPCRecompiler_checkRegisterUsage(ppcImlGenContext, imlSegment->imlList+i, ®istersUsed); + if( registersUsed.writtenNamedReg1 == registerIndex ) + { + if( imlSegment->imlList[i].type == PPCREC_IML_TYPE_R_NAME ) + return true; + return false; + } + } + return false; +} + +sint32 debugCallCounter1 = 0; + +/* + * Returns true if the name is overwritten in the current or any following segments + */ +bool PPCRecompiler_trackOverwrittenNameStoreInstruction(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegment, sint32 startIndex, PPCRecImlInstruction_t* nameStoreInstruction, sint32 scanDepth) +{ + //sint16 registerIndex = nameStoreInstruction->op_r_name.registerIndex; + uint32 name = nameStoreInstruction->op_r_name.name; + for(sint32 i=startIndex; i<imlSegment->imlListCount; i++) + { + PPCRecImlInstruction_t* imlInstruction = imlSegment->imlList+i; + if( imlSegment->imlList[i].type == PPCREC_IML_TYPE_R_NAME ) + { + // name is loaded before being written + if( imlSegment->imlList[i].op_r_name.name == name ) + return false; + } + else if( imlSegment->imlList[i].type == PPCREC_IML_TYPE_NAME_R ) + { + // name is written before being loaded + if( imlSegment->imlList[i].op_r_name.name == name ) + return true; + } + } + if( scanDepth >= 2 ) + return false; + if( imlSegment->nextSegmentIsUncertain ) + return false; + if( imlSegment->nextSegmentBranchTaken && PPCRecompiler_trackOverwrittenNameStoreInstruction(ppcImlGenContext, imlSegment->nextSegmentBranchTaken, 0, nameStoreInstruction, scanDepth+1) == false ) + return false; + if( imlSegment->nextSegmentBranchNotTaken && PPCRecompiler_trackOverwrittenNameStoreInstruction(ppcImlGenContext, imlSegment->nextSegmentBranchNotTaken, 0, nameStoreInstruction, scanDepth+1) == false ) + return false; + if( imlSegment->nextSegmentBranchTaken == NULL && imlSegment->nextSegmentBranchNotTaken == NULL ) + return false; + + return true; +} + +/* + * Returns true if the loaded FPR name is never changed + */ +bool PPCRecompiler_trackRedundantFPRNameStoreInstruction(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegment, sint32 startIndex, PPCRecImlInstruction_t* nameStoreInstruction, sint32 scanDepth) +{ + sint16 registerIndex = nameStoreInstruction->op_r_name.registerIndex; + for(sint32 i=startIndex; i>=0; i--) + { + PPCRecImlInstruction_t* imlInstruction = imlSegment->imlList+i; + PPCImlOptimizerUsedRegisters_t registersUsed; + PPCRecompiler_checkRegisterUsage(ppcImlGenContext, imlSegment->imlList+i, ®istersUsed); + if( registersUsed.writtenFPR1 == registerIndex ) + { + if( imlSegment->imlList[i].type == PPCREC_IML_TYPE_FPR_R_NAME ) + return true; + return false; + } + } + // todo: Scan next segment(s) + return false; +} + +uint32 _PPCRecompiler_getCROverwriteMask(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegment, uint32 currentOverwriteMask, uint32 currentReadMask, uint32 scanDepth) +{ + // is any bit overwritten but not read? + uint32 overwriteMask = imlSegment->crBitsWritten&~imlSegment->crBitsInput; + currentOverwriteMask |= overwriteMask; + // next segment + if( imlSegment->nextSegmentIsUncertain == false && scanDepth < 3 ) + { + uint32 nextSegmentOverwriteMask = 0; + if( imlSegment->nextSegmentBranchTaken && imlSegment->nextSegmentBranchNotTaken ) + { + uint32 mask0 = _PPCRecompiler_getCROverwriteMask(ppcImlGenContext, imlSegment->nextSegmentBranchTaken, 0, 0, scanDepth+1); + uint32 mask1 = _PPCRecompiler_getCROverwriteMask(ppcImlGenContext, imlSegment->nextSegmentBranchNotTaken, 0, 0, scanDepth+1); + nextSegmentOverwriteMask = mask0&mask1; + } + else if( imlSegment->nextSegmentBranchNotTaken) + { + nextSegmentOverwriteMask = _PPCRecompiler_getCROverwriteMask(ppcImlGenContext, imlSegment->nextSegmentBranchNotTaken, 0, 0, scanDepth+1); + } + nextSegmentOverwriteMask &= ~imlSegment->crBitsRead; + currentOverwriteMask |= nextSegmentOverwriteMask; + } + else if (imlSegment->nextSegmentIsUncertain) + { + if (ppcImlGenContext->segmentListCount >= 5) + { + return 7; // for more complex functions we assume that CR is not passed on + } + } + return currentOverwriteMask; +} + +/* + * Returns a mask of all CR bits that are overwritten (written but not read) in the segment and all it's following segments + * If the write state of a CR bit cannot be determined, it is returned as 0 (not overwritten) + */ +uint32 PPCRecompiler_getCROverwriteMask(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegment) +{ + if (imlSegment->nextSegmentIsUncertain) + { + return 0; + } + if( imlSegment->nextSegmentBranchTaken && imlSegment->nextSegmentBranchNotTaken ) + { + uint32 mask0 = _PPCRecompiler_getCROverwriteMask(ppcImlGenContext, imlSegment->nextSegmentBranchTaken, 0, 0, 0); + uint32 mask1 = _PPCRecompiler_getCROverwriteMask(ppcImlGenContext, imlSegment->nextSegmentBranchNotTaken, 0, 0, 0); + return mask0&mask1; // only return bits that are overwritten in both branches + } + else if( imlSegment->nextSegmentBranchNotTaken ) + { + uint32 mask = _PPCRecompiler_getCROverwriteMask(ppcImlGenContext, imlSegment->nextSegmentBranchNotTaken, 0, 0, 0); + return mask; + } + else + { + // not implemented + } + return 0; +} + +void PPCRecompiler_removeRedundantCRUpdates(ppcImlGenContext_t* ppcImlGenContext) +{ + for(sint32 s=0; s<ppcImlGenContext->segmentListCount; s++) + { + PPCRecImlSegment_t* imlSegment = ppcImlGenContext->segmentList[s]; + + for(sint32 i=0; i<imlSegment->imlListCount; i++) + { + PPCRecImlInstruction_t* imlInstruction = imlSegment->imlList+i; + if (imlInstruction->type == PPCREC_IML_TYPE_CJUMP) + { + if (imlInstruction->op_conditionalJump.condition != PPCREC_JUMP_CONDITION_NONE) + { + uint32 crBitFlag = 1 << (imlInstruction->op_conditionalJump.crRegisterIndex * 4 + imlInstruction->op_conditionalJump.crBitIndex); + imlSegment->crBitsInput |= (crBitFlag&~imlSegment->crBitsWritten); // flag bits that have not already been written + imlSegment->crBitsRead |= (crBitFlag); + } + } + else if (imlInstruction->type == PPCREC_IML_TYPE_CONDITIONAL_R_S32) + { + uint32 crBitFlag = 1 << (imlInstruction->op_conditional_r_s32.crRegisterIndex * 4 + imlInstruction->op_conditional_r_s32.crBitIndex); + imlSegment->crBitsInput |= (crBitFlag&~imlSegment->crBitsWritten); // flag bits that have not already been written + imlSegment->crBitsRead |= (crBitFlag); + } + else if (imlInstruction->type == PPCREC_IML_TYPE_R_S32 && imlInstruction->operation == PPCREC_IML_OP_MFCR) + { + imlSegment->crBitsRead |= 0xFFFFFFFF; + } + else if (imlInstruction->type == PPCREC_IML_TYPE_R_S32 && imlInstruction->operation == PPCREC_IML_OP_MTCRF) + { + imlSegment->crBitsWritten |= ppc_MTCRFMaskToCRBitMask((uint32)imlInstruction->op_r_immS32.immS32); + } + else if( imlInstruction->type == PPCREC_IML_TYPE_CR ) + { + if (imlInstruction->operation == PPCREC_IML_OP_CR_CLEAR || + imlInstruction->operation == PPCREC_IML_OP_CR_SET) + { + uint32 crBitFlag = 1 << (imlInstruction->op_cr.crD); + imlSegment->crBitsWritten |= (crBitFlag & ~imlSegment->crBitsWritten); + } + else if (imlInstruction->operation == PPCREC_IML_OP_CR_OR || + imlInstruction->operation == PPCREC_IML_OP_CR_ORC || + imlInstruction->operation == PPCREC_IML_OP_CR_AND || + imlInstruction->operation == PPCREC_IML_OP_CR_ANDC) + { + uint32 crBitFlag = 1 << (imlInstruction->op_cr.crD); + imlSegment->crBitsWritten |= (crBitFlag & ~imlSegment->crBitsWritten); + crBitFlag = 1 << (imlInstruction->op_cr.crA); + imlSegment->crBitsRead |= (crBitFlag & ~imlSegment->crBitsRead); + crBitFlag = 1 << (imlInstruction->op_cr.crB); + imlSegment->crBitsRead |= (crBitFlag & ~imlSegment->crBitsRead); + } + else + cemu_assert_unimplemented(); + } + else if( PPCRecompilerImlAnalyzer_canTypeWriteCR(imlInstruction) && imlInstruction->crRegister >= 0 && imlInstruction->crRegister <= 7 ) + { + imlSegment->crBitsWritten |= (0xF<<(imlInstruction->crRegister*4)); + } + else if( (imlInstruction->type == PPCREC_IML_TYPE_STORE || imlInstruction->type == PPCREC_IML_TYPE_STORE_INDEXED) && imlInstruction->op_storeLoad.copyWidth == PPC_REC_STORE_STWCX_MARKER ) + { + // overwrites CR0 + imlSegment->crBitsWritten |= (0xF<<0); + } + } + } + // flag instructions that write to CR where we can ignore individual CR bits + for(sint32 s=0; s<ppcImlGenContext->segmentListCount; s++) + { + PPCRecImlSegment_t* imlSegment = ppcImlGenContext->segmentList[s]; + for(sint32 i=0; i<imlSegment->imlListCount; i++) + { + PPCRecImlInstruction_t* imlInstruction = imlSegment->imlList+i; + if( PPCRecompilerImlAnalyzer_canTypeWriteCR(imlInstruction) && imlInstruction->crRegister >= 0 && imlInstruction->crRegister <= 7 ) + { + uint32 crBitFlags = 0xF<<((uint32)imlInstruction->crRegister*4); + uint32 crOverwriteMask = PPCRecompiler_getCROverwriteMask(ppcImlGenContext, imlSegment); + uint32 crIgnoreMask = crOverwriteMask & ~imlSegment->crBitsRead; + imlInstruction->crIgnoreMask = crIgnoreMask; + } + } + } +} + +bool PPCRecompiler_checkIfGPRIsModifiedInRange(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegment, sint32 startIndex, sint32 endIndex, sint32 vreg) +{ + PPCImlOptimizerUsedRegisters_t registersUsed; + for (sint32 i = startIndex; i <= endIndex; i++) + { + PPCRecImlInstruction_t* imlInstruction = imlSegment->imlList + i; + PPCRecompiler_checkRegisterUsage(ppcImlGenContext, imlInstruction, ®istersUsed); + if (registersUsed.writtenNamedReg1 == vreg) + return true; + } + return false; +} + +sint32 PPCRecompiler_scanBackwardsForReusableRegister(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* startSegment, sint32 startIndex, sint32 name) +{ + // current segment + sint32 currentIndex = startIndex; + PPCRecImlSegment_t* currentSegment = startSegment; + sint32 segmentIterateCount = 0; + sint32 foundRegister = -1; + while (true) + { + // stop scanning if segment is enterable + if (currentSegment->isEnterable) + return -1; + while (currentIndex >= 0) + { + if (currentSegment->imlList[currentIndex].type == PPCREC_IML_TYPE_NAME_R && currentSegment->imlList[currentIndex].op_r_name.name == name) + { + foundRegister = currentSegment->imlList[currentIndex].op_r_name.registerIndex; + break; + } + // previous instruction + currentIndex--; + } + if (foundRegister >= 0) + break; + // continue at previous segment (if there is only one) + if (segmentIterateCount >= 1) + return -1; + if (currentSegment->list_prevSegments.size() != 1) + return -1; + currentSegment = currentSegment->list_prevSegments[0]; + currentIndex = currentSegment->imlListCount - 1; + segmentIterateCount++; + } + // scan again to make sure the register is not modified inbetween + currentIndex = startIndex; + currentSegment = startSegment; + segmentIterateCount = 0; + PPCImlOptimizerUsedRegisters_t registersUsed; + while (true) + { + while (currentIndex >= 0) + { + // check if register is modified + PPCRecompiler_checkRegisterUsage(ppcImlGenContext, currentSegment->imlList+currentIndex, ®istersUsed); + if (registersUsed.writtenNamedReg1 == foundRegister) + return -1; + // check if end of scan reached + if (currentSegment->imlList[currentIndex].type == PPCREC_IML_TYPE_NAME_R && currentSegment->imlList[currentIndex].op_r_name.name == name) + { + //foundRegister = currentSegment->imlList[currentIndex].op_r_name.registerIndex; + return foundRegister; + } + // previous instruction + currentIndex--; + } + // continue at previous segment (if there is only one) + if (segmentIterateCount >= 1) + return -1; + if (currentSegment->list_prevSegments.size() != 1) + return -1; + currentSegment = currentSegment->list_prevSegments[0]; + currentIndex = currentSegment->imlListCount - 1; + segmentIterateCount++; + } + return -1; +} + +void PPCRecompiler_optimizeDirectFloatCopiesScanForward(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegment, sint32 imlIndexLoad, sint32 fprIndex) +{ + PPCRecImlInstruction_t* imlInstructionLoad = imlSegment->imlList + imlIndexLoad; + if (imlInstructionLoad->op_storeLoad.flags2.notExpanded) + return; + + PPCImlOptimizerUsedRegisters_t registersUsed; + sint32 scanRangeEnd = std::min(imlIndexLoad + 25, imlSegment->imlListCount); // don't scan too far (saves performance and also the chances we can merge the load+store become low at high distances) + bool foundMatch = false; + sint32 lastStore = -1; + for (sint32 i = imlIndexLoad + 1; i < scanRangeEnd; i++) + { + PPCRecImlInstruction_t* imlInstruction = imlSegment->imlList + i; + if (PPCRecompiler_isSuffixInstruction(imlInstruction)) + { + break; + } + + // check if FPR is stored + if ((imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE && imlInstruction->op_storeLoad.mode == PPCREC_FPR_ST_MODE_SINGLE_FROM_PS0) || + (imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE_INDEXED && imlInstruction->op_storeLoad.mode == PPCREC_FPR_ST_MODE_SINGLE_FROM_PS0)) + { + if (imlInstruction->op_storeLoad.registerData == fprIndex) + { + if (foundMatch == false) + { + // flag the load-single instruction as "don't expand" (leave single value as-is) + imlInstructionLoad->op_storeLoad.flags2.notExpanded = true; + } + // also set the flag for the store instruction + PPCRecImlInstruction_t* imlInstructionStore = imlInstruction; + imlInstructionStore->op_storeLoad.flags2.notExpanded = true; + + foundMatch = true; + lastStore = i + 1; + + continue; + } + } + + // check if FPR is overwritten (we can actually ignore read operations?) + PPCRecompiler_checkRegisterUsage(ppcImlGenContext, imlInstruction, ®istersUsed); + if (registersUsed.writtenFPR1 == fprIndex) + break; + if (registersUsed.readFPR1 == fprIndex) + break; + if (registersUsed.readFPR2 == fprIndex) + break; + if (registersUsed.readFPR3 == fprIndex) + break; + if (registersUsed.readFPR4 == fprIndex) + break; + } + + if (foundMatch) + { + // insert expand instruction after store + PPCRecImlInstruction_t* newExpand = PPCRecompiler_insertInstruction(imlSegment, lastStore); + PPCRecompilerImlGen_generateNewInstruction_fpr_r(ppcImlGenContext, newExpand, PPCREC_IML_OP_FPR_EXPAND_BOTTOM32_TO_BOTTOM64_AND_TOP64, fprIndex); + } +} + +/* +* Scans for patterns: +* <Load sp float into register f> +* <Random unrelated instructions> +* <Store sp float from register f> +* For these patterns the store and load is modified to work with un-extended values (float remains as float, no double conversion) +* The float->double extension is then executed later +* Advantages: +* Keeps denormals and other special float values intact +* Slightly improves performance +*/ +void PPCRecompiler_optimizeDirectFloatCopies(ppcImlGenContext_t* ppcImlGenContext) +{ + for (sint32 s = 0; s < ppcImlGenContext->segmentListCount; s++) + { + PPCRecImlSegment_t* imlSegment = ppcImlGenContext->segmentList[s]; + + for (sint32 i = 0; i < imlSegment->imlListCount; i++) + { + PPCRecImlInstruction_t* imlInstruction = imlSegment->imlList + i; + if (imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD && imlInstruction->op_storeLoad.mode == PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0_PS1) + { + PPCRecompiler_optimizeDirectFloatCopiesScanForward(ppcImlGenContext, imlSegment, i, imlInstruction->op_storeLoad.registerData); + } + else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD_INDEXED && imlInstruction->op_storeLoad.mode == PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0_PS1) + { + PPCRecompiler_optimizeDirectFloatCopiesScanForward(ppcImlGenContext, imlSegment, i, imlInstruction->op_storeLoad.registerData); + } + } + } +} + +void PPCRecompiler_optimizeDirectIntegerCopiesScanForward(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegment, sint32 imlIndexLoad, sint32 gprIndex) +{ + PPCRecImlInstruction_t* imlInstructionLoad = imlSegment->imlList + imlIndexLoad; + if ( imlInstructionLoad->op_storeLoad.flags2.swapEndian == false ) + return; + bool foundMatch = false; + PPCImlOptimizerUsedRegisters_t registersUsed; + sint32 scanRangeEnd = std::min(imlIndexLoad + 25, imlSegment->imlListCount); // don't scan too far (saves performance and also the chances we can merge the load+store become low at high distances) + sint32 i = imlIndexLoad + 1; + for (; i < scanRangeEnd; i++) + { + PPCRecImlInstruction_t* imlInstruction = imlSegment->imlList + i; + if (PPCRecompiler_isSuffixInstruction(imlInstruction)) + { + break; + } + // check if GPR is stored + if ((imlInstruction->type == PPCREC_IML_TYPE_STORE && imlInstruction->op_storeLoad.copyWidth == 32 ) ) + { + if (imlInstruction->op_storeLoad.registerMem == gprIndex) + break; + if (imlInstruction->op_storeLoad.registerData == gprIndex) + { + PPCRecImlInstruction_t* imlInstructionStore = imlInstruction; + if (foundMatch == false) + { + // switch the endian swap flag for the load instruction + imlInstructionLoad->op_storeLoad.flags2.swapEndian = !imlInstructionLoad->op_storeLoad.flags2.swapEndian; + foundMatch = true; + } + // switch the endian swap flag for the store instruction + imlInstructionStore->op_storeLoad.flags2.swapEndian = !imlInstructionStore->op_storeLoad.flags2.swapEndian; + // keep scanning + continue; + } + } + // check if GPR is accessed + PPCRecompiler_checkRegisterUsage(ppcImlGenContext, imlInstruction, ®istersUsed); + if (registersUsed.readNamedReg1 == gprIndex || + registersUsed.readNamedReg2 == gprIndex || + registersUsed.readNamedReg3 == gprIndex) + { + break; + } + if (registersUsed.writtenNamedReg1 == gprIndex) + return; // GPR overwritten, we don't need to byte swap anymore + } + if (foundMatch) + { + // insert expand instruction + PPCRecImlInstruction_t* newExpand = PPCRecompiler_insertInstruction(imlSegment, i); + PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, newExpand, PPCREC_IML_OP_ENDIAN_SWAP, gprIndex, gprIndex); + } +} + +/* +* Scans for patterns: +* <Load sp integer into register r> +* <Random unrelated instructions> +* <Store sp integer from register r> +* For these patterns the store and load is modified to work with non-swapped values +* The big_endian->little_endian conversion is then executed later +* Advantages: +* Slightly improves performance +*/ +void PPCRecompiler_optimizeDirectIntegerCopies(ppcImlGenContext_t* ppcImlGenContext) +{ + for (sint32 s = 0; s < ppcImlGenContext->segmentListCount; s++) + { + PPCRecImlSegment_t* imlSegment = ppcImlGenContext->segmentList[s]; + + for (sint32 i = 0; i < imlSegment->imlListCount; i++) + { + PPCRecImlInstruction_t* imlInstruction = imlSegment->imlList + i; + if (imlInstruction->type == PPCREC_IML_TYPE_LOAD && imlInstruction->op_storeLoad.copyWidth == 32 && imlInstruction->op_storeLoad.flags2.swapEndian ) + { + PPCRecompiler_optimizeDirectIntegerCopiesScanForward(ppcImlGenContext, imlSegment, i, imlInstruction->op_storeLoad.registerData); + } + } + } +} + +sint32 _getGQRIndexFromRegister(ppcImlGenContext_t* ppcImlGenContext, sint32 registerIndex) +{ + if (registerIndex == PPC_REC_INVALID_REGISTER) + return -1; + sint32 namedReg = ppcImlGenContext->mappedRegister[registerIndex]; + if (namedReg >= (PPCREC_NAME_SPR0 + SPR_UGQR0) && namedReg <= (PPCREC_NAME_SPR0 + SPR_UGQR7)) + { + return namedReg - (PPCREC_NAME_SPR0 + SPR_UGQR0); + } + return -1; +} + +bool PPCRecompiler_isUGQRValueKnown(ppcImlGenContext_t* ppcImlGenContext, sint32 gqrIndex, uint32& gqrValue) +{ + // UGQR 2 to 7 are initialized by the OS and we assume that games won't ever permanently touch those + // todo - hack - replace with more accurate solution + if (gqrIndex == 2) + gqrValue = 0x00040004; + else if (gqrIndex == 3) + gqrValue = 0x00050005; + else if (gqrIndex == 4) + gqrValue = 0x00060006; + else if (gqrIndex == 5) + gqrValue = 0x00070007; + else + return false; + return true; +} + +/* + * If value of GQR can be predicted for a given PSQ load or store instruction then replace it with an optimized version + */ +void PPCRecompiler_optimizePSQLoadAndStore(ppcImlGenContext_t* ppcImlGenContext) +{ + for (sint32 s = 0; s < ppcImlGenContext->segmentListCount; s++) + { + PPCRecImlSegment_t* imlSegment = ppcImlGenContext->segmentList[s]; + for (sint32 i = 0; i < imlSegment->imlListCount; i++) + { + PPCRecImlInstruction_t* imlInstruction = imlSegment->imlList + i; + if (imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD || imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD_INDEXED) + { + if(imlInstruction->op_storeLoad.mode != PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0 && + imlInstruction->op_storeLoad.mode != PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0_PS1 ) + continue; + // get GQR value + cemu_assert_debug(imlInstruction->op_storeLoad.registerGQR != PPC_REC_INVALID_REGISTER); + sint32 gqrIndex = _getGQRIndexFromRegister(ppcImlGenContext, imlInstruction->op_storeLoad.registerGQR); + cemu_assert(gqrIndex >= 0); + if (ppcImlGenContext->tracking.modifiesGQR[gqrIndex]) + continue; + //uint32 gqrValue = ppcInterpreterCurrentInstance->sprNew.UGQR[gqrIndex]; + uint32 gqrValue; + if (!PPCRecompiler_isUGQRValueKnown(ppcImlGenContext, gqrIndex, gqrValue)) + continue; + + uint32 formatType = (gqrValue >> 16) & 7; + uint32 scale = (gqrValue >> 24) & 0x3F; + if (scale != 0) + continue; // only generic handler supports scale + if (imlInstruction->op_storeLoad.mode == PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0) + { + if (formatType == 0) + imlInstruction->op_storeLoad.mode = PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0; + else if (formatType == 4) + imlInstruction->op_storeLoad.mode = PPCREC_FPR_LD_MODE_PSQ_U8_PS0; + else if (formatType == 5) + imlInstruction->op_storeLoad.mode = PPCREC_FPR_LD_MODE_PSQ_U16_PS0; + else if (formatType == 6) + imlInstruction->op_storeLoad.mode = PPCREC_FPR_LD_MODE_PSQ_S8_PS0; + else if (formatType == 7) + imlInstruction->op_storeLoad.mode = PPCREC_FPR_LD_MODE_PSQ_S16_PS0; + } + else if (imlInstruction->op_storeLoad.mode == PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0_PS1) + { + if (formatType == 0) + imlInstruction->op_storeLoad.mode = PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0_PS1; + else if (formatType == 4) + imlInstruction->op_storeLoad.mode = PPCREC_FPR_LD_MODE_PSQ_U8_PS0_PS1; + else if (formatType == 5) + imlInstruction->op_storeLoad.mode = PPCREC_FPR_LD_MODE_PSQ_U16_PS0_PS1; + else if (formatType == 6) + imlInstruction->op_storeLoad.mode = PPCREC_FPR_LD_MODE_PSQ_S8_PS0_PS1; + else if (formatType == 7) + imlInstruction->op_storeLoad.mode = PPCREC_FPR_LD_MODE_PSQ_S16_PS0_PS1; + } + } + else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE || imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE_INDEXED) + { + if(imlInstruction->op_storeLoad.mode != PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0 && + imlInstruction->op_storeLoad.mode != PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0_PS1) + continue; + // get GQR value + cemu_assert_debug(imlInstruction->op_storeLoad.registerGQR != PPC_REC_INVALID_REGISTER); + sint32 gqrIndex = _getGQRIndexFromRegister(ppcImlGenContext, imlInstruction->op_storeLoad.registerGQR); + cemu_assert(gqrIndex >= 0); + if (ppcImlGenContext->tracking.modifiesGQR[gqrIndex]) + continue; + uint32 gqrValue; + if(!PPCRecompiler_isUGQRValueKnown(ppcImlGenContext, gqrIndex, gqrValue)) + continue; + uint32 formatType = (gqrValue >> 16) & 7; + uint32 scale = (gqrValue >> 24) & 0x3F; + if (scale != 0) + continue; // only generic handler supports scale + if (imlInstruction->op_storeLoad.mode == PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0) + { + if (formatType == 0) + imlInstruction->op_storeLoad.mode = PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0; + else if (formatType == 4) + imlInstruction->op_storeLoad.mode = PPCREC_FPR_ST_MODE_PSQ_U8_PS0; + else if (formatType == 5) + imlInstruction->op_storeLoad.mode = PPCREC_FPR_ST_MODE_PSQ_U16_PS0; + else if (formatType == 6) + imlInstruction->op_storeLoad.mode = PPCREC_FPR_ST_MODE_PSQ_S8_PS0; + else if (formatType == 7) + imlInstruction->op_storeLoad.mode = PPCREC_FPR_ST_MODE_PSQ_S16_PS0; + } + else if (imlInstruction->op_storeLoad.mode == PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0_PS1) + { + if (formatType == 0) + imlInstruction->op_storeLoad.mode = PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0_PS1; + else if (formatType == 4) + imlInstruction->op_storeLoad.mode = PPCREC_FPR_ST_MODE_PSQ_U8_PS0_PS1; + else if (formatType == 5) + imlInstruction->op_storeLoad.mode = PPCREC_FPR_ST_MODE_PSQ_U16_PS0_PS1; + else if (formatType == 6) + imlInstruction->op_storeLoad.mode = PPCREC_FPR_ST_MODE_PSQ_S8_PS0_PS1; + else if (formatType == 7) + imlInstruction->op_storeLoad.mode = PPCREC_FPR_ST_MODE_PSQ_S16_PS0_PS1; + } + } + } + } +} + +/* + * Returns true if registerWrite overwrites any of the registers read by registerRead + */ +bool PPCRecompilerAnalyzer_checkForGPROverwrite(PPCImlOptimizerUsedRegisters_t* registerRead, PPCImlOptimizerUsedRegisters_t* registerWrite) +{ + if (registerWrite->writtenNamedReg1 < 0) + return false; + + if (registerWrite->writtenNamedReg1 == registerRead->readNamedReg1) + return true; + if (registerWrite->writtenNamedReg1 == registerRead->readNamedReg2) + return true; + if (registerWrite->writtenNamedReg1 == registerRead->readNamedReg3) + return true; + return false; +} + +void _reorderConditionModifyInstructions(PPCRecImlSegment_t* imlSegment) +{ + PPCRecImlInstruction_t* lastInstruction = PPCRecompilerIML_getLastInstruction(imlSegment); + // last instruction a conditional branch? + if (lastInstruction == nullptr || lastInstruction->type != PPCREC_IML_TYPE_CJUMP) + return; + if (lastInstruction->op_conditionalJump.crRegisterIndex >= 8) + return; + // get CR bitmask of bit required for conditional jump + PPCRecCRTracking_t crTracking; + PPCRecompilerImlAnalyzer_getCRTracking(lastInstruction, &crTracking); + uint32 requiredCRBits = crTracking.readCRBits; + + // scan backwards until we find the instruction that sets the CR + sint32 crSetterInstructionIndex = -1; + sint32 unsafeInstructionIndex = -1; + for (sint32 i = imlSegment->imlListCount-2; i >= 0; i--) + { + PPCRecImlInstruction_t* imlInstruction = imlSegment->imlList + i; + PPCRecompilerImlAnalyzer_getCRTracking(imlInstruction, &crTracking); + if (crTracking.readCRBits != 0) + return; // dont handle complex cases for now + if (crTracking.writtenCRBits != 0) + { + if ((crTracking.writtenCRBits&requiredCRBits) != 0) + { + crSetterInstructionIndex = i; + break; + } + else + { + return; // other CR bits overwritten (dont handle complex cases) + } + } + // is safe? (no risk of overwriting x64 eflags) + if ((imlInstruction->type == PPCREC_IML_TYPE_NAME_R || imlInstruction->type == PPCREC_IML_TYPE_R_NAME || imlInstruction->type == PPCREC_IML_TYPE_NO_OP) || + (imlInstruction->type == PPCREC_IML_TYPE_FPR_NAME_R || imlInstruction->type == PPCREC_IML_TYPE_FPR_R_NAME) || + (imlInstruction->type == PPCREC_IML_TYPE_R_S32 && (imlInstruction->operation == PPCREC_IML_OP_ASSIGN)) || + (imlInstruction->type == PPCREC_IML_TYPE_R_R && (imlInstruction->operation == PPCREC_IML_OP_ASSIGN)) ) + continue; + // not safe + //hasUnsafeInstructions = true; + if (unsafeInstructionIndex == -1) + unsafeInstructionIndex = i; + } + if (crSetterInstructionIndex < 0) + return; + if (unsafeInstructionIndex < 0) + return; // no danger of overwriting eflags, don't reorder + // check if we can move the CR setter instruction to after unsafeInstructionIndex + PPCRecCRTracking_t crTrackingSetter = crTracking; + PPCImlOptimizerUsedRegisters_t regTrackingCRSetter; + PPCRecompiler_checkRegisterUsage(NULL, imlSegment->imlList+crSetterInstructionIndex, ®TrackingCRSetter); + if (regTrackingCRSetter.writtenFPR1 >= 0 || regTrackingCRSetter.readFPR1 >= 0 || regTrackingCRSetter.readFPR2 >= 0 || regTrackingCRSetter.readFPR3 >= 0 || regTrackingCRSetter.readFPR4 >= 0) + return; // we don't handle FPR dependency yet so just ignore FPR instructions + PPCImlOptimizerUsedRegisters_t registerTracking; + if (regTrackingCRSetter.writtenNamedReg1 >= 0) + { + // CR setter does write GPR + for (sint32 i = crSetterInstructionIndex + 1; i <= unsafeInstructionIndex; i++) + { + PPCRecompiler_checkRegisterUsage(NULL, imlSegment->imlList + i, ®isterTracking); + // reads register written by CR setter? + if (PPCRecompilerAnalyzer_checkForGPROverwrite(®isterTracking, ®TrackingCRSetter)) + { + return; // cant move CR setter because of dependency + } + // writes register read by CR setter? + if (PPCRecompilerAnalyzer_checkForGPROverwrite(®TrackingCRSetter, ®isterTracking)) + { + return; // cant move CR setter because of dependency + } + // overwrites register written by CR setter? + if (regTrackingCRSetter.writtenNamedReg1 == registerTracking.writtenNamedReg1) + return; + } + } + else + { + // CR setter does not write GPR + for (sint32 i = crSetterInstructionIndex + 1; i <= unsafeInstructionIndex; i++) + { + PPCRecompiler_checkRegisterUsage(NULL, imlSegment->imlList + i, ®isterTracking); + // writes register read by CR setter? + if (PPCRecompilerAnalyzer_checkForGPROverwrite(®TrackingCRSetter, ®isterTracking)) + { + return; // cant move CR setter because of dependency + } + } + } + + // move CR setter instruction +#ifndef PUBLIC_RELEASE + if ((unsafeInstructionIndex + 1) <= crSetterInstructionIndex) + assert_dbg(); +#endif + PPCRecImlInstruction_t* newCRSetterInstruction = PPCRecompiler_insertInstruction(imlSegment, unsafeInstructionIndex+1); + memcpy(newCRSetterInstruction, imlSegment->imlList + crSetterInstructionIndex, sizeof(PPCRecImlInstruction_t)); + PPCRecompilerImlGen_generateNewInstruction_noOp(NULL, imlSegment->imlList + crSetterInstructionIndex); +} + +/* + * Move instructions which update the condition flags closer to the instruction that consumes them + * On x64 this improves performance since we often can avoid storing CR in memory + */ +void PPCRecompiler_reorderConditionModifyInstructions(ppcImlGenContext_t* ppcImlGenContext) +{ + // check if this segment has a conditional branch + for (sint32 s = 0; s < ppcImlGenContext->segmentListCount; s++) + { + PPCRecImlSegment_t* imlSegment = ppcImlGenContext->segmentList[s]; + _reorderConditionModifyInstructions(imlSegment); + } +} diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlRanges.cpp b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlRanges.cpp new file mode 100644 index 00000000..f6370d8c --- /dev/null +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlRanges.cpp @@ -0,0 +1,399 @@ +#include "PPCRecompiler.h" +#include "PPCRecompilerIml.h" +#include "PPCRecompilerX64.h" +#include "PPCRecompilerImlRanges.h" +#include "util/helpers/MemoryPool.h" + +void PPCRecRARange_addLink_perVirtualGPR(raLivenessSubrange_t** root, raLivenessSubrange_t* subrange) +{ +#ifndef PUBLIC_RELEASE + if ((*root) && (*root)->range->virtualRegister != subrange->range->virtualRegister) + assert_dbg(); +#endif + subrange->link_sameVirtualRegisterGPR.next = *root; + if (*root) + (*root)->link_sameVirtualRegisterGPR.prev = subrange; + subrange->link_sameVirtualRegisterGPR.prev = nullptr; + *root = subrange; +} + +void PPCRecRARange_addLink_allSubrangesGPR(raLivenessSubrange_t** root, raLivenessSubrange_t* subrange) +{ + subrange->link_segmentSubrangesGPR.next = *root; + if (*root) + (*root)->link_segmentSubrangesGPR.prev = subrange; + subrange->link_segmentSubrangesGPR.prev = nullptr; + *root = subrange; +} + +void PPCRecRARange_removeLink_perVirtualGPR(raLivenessSubrange_t** root, raLivenessSubrange_t* subrange) +{ + raLivenessSubrange_t* tempPrev = subrange->link_sameVirtualRegisterGPR.prev; + if (subrange->link_sameVirtualRegisterGPR.prev) + subrange->link_sameVirtualRegisterGPR.prev->link_sameVirtualRegisterGPR.next = subrange->link_sameVirtualRegisterGPR.next; + else + (*root) = subrange->link_sameVirtualRegisterGPR.next; + if (subrange->link_sameVirtualRegisterGPR.next) + subrange->link_sameVirtualRegisterGPR.next->link_sameVirtualRegisterGPR.prev = tempPrev; +#ifndef PUBLIC_RELEASE + subrange->link_sameVirtualRegisterGPR.prev = (raLivenessSubrange_t*)1; + subrange->link_sameVirtualRegisterGPR.next = (raLivenessSubrange_t*)1; +#endif +} + +void PPCRecRARange_removeLink_allSubrangesGPR(raLivenessSubrange_t** root, raLivenessSubrange_t* subrange) +{ + raLivenessSubrange_t* tempPrev = subrange->link_segmentSubrangesGPR.prev; + if (subrange->link_segmentSubrangesGPR.prev) + subrange->link_segmentSubrangesGPR.prev->link_segmentSubrangesGPR.next = subrange->link_segmentSubrangesGPR.next; + else + (*root) = subrange->link_segmentSubrangesGPR.next; + if (subrange->link_segmentSubrangesGPR.next) + subrange->link_segmentSubrangesGPR.next->link_segmentSubrangesGPR.prev = tempPrev; +#ifndef PUBLIC_RELEASE + subrange->link_segmentSubrangesGPR.prev = (raLivenessSubrange_t*)1; + subrange->link_segmentSubrangesGPR.next = (raLivenessSubrange_t*)1; +#endif +} + +MemoryPoolPermanentObjects<raLivenessRange_t> memPool_livenessRange(4096); +MemoryPoolPermanentObjects<raLivenessSubrange_t> memPool_livenessSubrange(4096); + +raLivenessRange_t* PPCRecRA_createRangeBase(ppcImlGenContext_t* ppcImlGenContext, uint32 virtualRegister, uint32 name) +{ + raLivenessRange_t* livenessRange = memPool_livenessRange.acquireObj(); + livenessRange->list_subranges.resize(0); + livenessRange->virtualRegister = virtualRegister; + livenessRange->name = name; + livenessRange->physicalRegister = -1; + ppcImlGenContext->raInfo.list_ranges.push_back(livenessRange); + return livenessRange; +} + +raLivenessSubrange_t* PPCRecRA_createSubrange(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange_t* range, PPCRecImlSegment_t* imlSegment, sint32 startIndex, sint32 endIndex) +{ + raLivenessSubrange_t* livenessSubrange = memPool_livenessSubrange.acquireObj(); + livenessSubrange->list_locations.resize(0); + livenessSubrange->range = range; + livenessSubrange->imlSegment = imlSegment; + PPCRecompilerIml_setSegmentPoint(&livenessSubrange->start, imlSegment, startIndex); + PPCRecompilerIml_setSegmentPoint(&livenessSubrange->end, imlSegment, endIndex); + // default values + livenessSubrange->hasStore = false; + livenessSubrange->hasStoreDelayed = false; + livenessSubrange->lastIterationIndex = 0; + livenessSubrange->subrangeBranchNotTaken = nullptr; + livenessSubrange->subrangeBranchTaken = nullptr; + livenessSubrange->_noLoad = false; + // add to range + range->list_subranges.push_back(livenessSubrange); + // add to segment + PPCRecRARange_addLink_perVirtualGPR(&(imlSegment->raInfo.linkedList_perVirtualGPR[range->virtualRegister]), livenessSubrange); + PPCRecRARange_addLink_allSubrangesGPR(&imlSegment->raInfo.linkedList_allSubranges, livenessSubrange); + return livenessSubrange; +} + +void _unlinkSubrange(raLivenessSubrange_t* subrange) +{ + PPCRecImlSegment_t* imlSegment = subrange->imlSegment; + PPCRecRARange_removeLink_perVirtualGPR(&imlSegment->raInfo.linkedList_perVirtualGPR[subrange->range->virtualRegister], subrange); + PPCRecRARange_removeLink_allSubrangesGPR(&imlSegment->raInfo.linkedList_allSubranges, subrange); +} + +void PPCRecRA_deleteSubrange(ppcImlGenContext_t* ppcImlGenContext, raLivenessSubrange_t* subrange) +{ + _unlinkSubrange(subrange); + subrange->range->list_subranges.erase(std::find(subrange->range->list_subranges.begin(), subrange->range->list_subranges.end(), subrange)); + subrange->list_locations.clear(); + PPCRecompilerIml_removeSegmentPoint(&subrange->start); + PPCRecompilerIml_removeSegmentPoint(&subrange->end); + memPool_livenessSubrange.releaseObj(subrange); +} + +void _PPCRecRA_deleteSubrangeNoUnlinkFromRange(ppcImlGenContext_t* ppcImlGenContext, raLivenessSubrange_t* subrange) +{ + _unlinkSubrange(subrange); + PPCRecompilerIml_removeSegmentPoint(&subrange->start); + PPCRecompilerIml_removeSegmentPoint(&subrange->end); + memPool_livenessSubrange.releaseObj(subrange); +} + +void PPCRecRA_deleteRange(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange_t* range) +{ + for (auto& subrange : range->list_subranges) + { + _PPCRecRA_deleteSubrangeNoUnlinkFromRange(ppcImlGenContext, subrange); + } + ppcImlGenContext->raInfo.list_ranges.erase(std::find(ppcImlGenContext->raInfo.list_ranges.begin(), ppcImlGenContext->raInfo.list_ranges.end(), range)); + memPool_livenessRange.releaseObj(range); +} + +void PPCRecRA_deleteRangeNoUnlink(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange_t* range) +{ + for (auto& subrange : range->list_subranges) + { + _PPCRecRA_deleteSubrangeNoUnlinkFromRange(ppcImlGenContext, subrange); + } + memPool_livenessRange.releaseObj(range); +} + +void PPCRecRA_deleteAllRanges(ppcImlGenContext_t* ppcImlGenContext) +{ + for(auto& range : ppcImlGenContext->raInfo.list_ranges) + { + PPCRecRA_deleteRangeNoUnlink(ppcImlGenContext, range); + } + ppcImlGenContext->raInfo.list_ranges.clear(); +} + +void PPCRecRA_mergeRanges(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange_t* range, raLivenessRange_t* absorbedRange) +{ + cemu_assert_debug(range != absorbedRange); + cemu_assert_debug(range->virtualRegister == absorbedRange->virtualRegister); + // move all subranges from absorbedRange to range + for (auto& subrange : absorbedRange->list_subranges) + { + range->list_subranges.push_back(subrange); + subrange->range = range; + } + absorbedRange->list_subranges.clear(); + PPCRecRA_deleteRange(ppcImlGenContext, absorbedRange); +} + +void PPCRecRA_mergeSubranges(ppcImlGenContext_t* ppcImlGenContext, raLivenessSubrange_t* subrange, raLivenessSubrange_t* absorbedSubrange) +{ +#ifndef PUBLIC_RELEASE + PPCRecRA_debugValidateSubrange(subrange); + PPCRecRA_debugValidateSubrange(absorbedSubrange); + if (subrange->imlSegment != absorbedSubrange->imlSegment) + assert_dbg(); + if (subrange->end.index > absorbedSubrange->start.index) + assert_dbg(); + if (subrange->subrangeBranchTaken || subrange->subrangeBranchNotTaken) + assert_dbg(); + if (subrange == absorbedSubrange) + assert_dbg(); +#endif + subrange->subrangeBranchTaken = absorbedSubrange->subrangeBranchTaken; + subrange->subrangeBranchNotTaken = absorbedSubrange->subrangeBranchNotTaken; + + // merge usage locations + for (auto& location : absorbedSubrange->list_locations) + { + subrange->list_locations.push_back(location); + } + absorbedSubrange->list_locations.clear(); + + subrange->end.index = absorbedSubrange->end.index; + + PPCRecRA_debugValidateSubrange(subrange); + + PPCRecRA_deleteSubrange(ppcImlGenContext, absorbedSubrange); +} + +// remove all inter-segment connections from the range and split it into local ranges (also removes empty ranges) +void PPCRecRA_explodeRange(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange_t* range) +{ + if (range->list_subranges.size() == 1) + assert_dbg(); + for (auto& subrange : range->list_subranges) + { + if (subrange->list_locations.empty()) + continue; + raLivenessRange_t* newRange = PPCRecRA_createRangeBase(ppcImlGenContext, range->virtualRegister, range->name); + raLivenessSubrange_t* newSubrange = PPCRecRA_createSubrange(ppcImlGenContext, newRange, subrange->imlSegment, subrange->list_locations.data()[0].index, subrange->list_locations.data()[subrange->list_locations.size() - 1].index + 1); + // copy locations + for (auto& location : subrange->list_locations) + { + newSubrange->list_locations.push_back(location); + } + } + // remove original range + PPCRecRA_deleteRange(ppcImlGenContext, range); +} + +#ifndef PUBLIC_RELEASE +void PPCRecRA_debugValidateSubrange(raLivenessSubrange_t* subrange) +{ + // validate subrange + if (subrange->subrangeBranchTaken && subrange->subrangeBranchTaken->imlSegment != subrange->imlSegment->nextSegmentBranchTaken) + assert_dbg(); + if (subrange->subrangeBranchNotTaken && subrange->subrangeBranchNotTaken->imlSegment != subrange->imlSegment->nextSegmentBranchNotTaken) + assert_dbg(); +} +#else +void PPCRecRA_debugValidateSubrange(raLivenessSubrange_t* subrange) {} +#endif + +// split subrange at the given index +// After the split there will be two ranges/subranges: +// head -> subrange is shortned to end at splitIndex +// tail -> a new subrange that reaches from splitIndex to the end of the original subrange +// if head has a physical register assigned it will not carry over to tail +// The return value is the tail subrange +// If trimToHole is true, the end of the head subrange and the start of the tail subrange will be moved to fit the locations +// Ranges that begin at RA_INTER_RANGE_START are allowed and can be split +raLivenessSubrange_t* PPCRecRA_splitLocalSubrange(ppcImlGenContext_t* ppcImlGenContext, raLivenessSubrange_t* subrange, sint32 splitIndex, bool trimToHole) +{ + // validation +#ifndef PUBLIC_RELEASE + if (subrange->end.index == RA_INTER_RANGE_END || subrange->end.index == RA_INTER_RANGE_START) + assert_dbg(); + if (subrange->start.index >= splitIndex) + assert_dbg(); + if (subrange->end.index <= splitIndex) + assert_dbg(); +#endif + // create tail + raLivenessRange_t* tailRange = PPCRecRA_createRangeBase(ppcImlGenContext, subrange->range->virtualRegister, subrange->range->name); + raLivenessSubrange_t* tailSubrange = PPCRecRA_createSubrange(ppcImlGenContext, tailRange, subrange->imlSegment, splitIndex, subrange->end.index); + // copy locations + for (auto& location : subrange->list_locations) + { + if (location.index >= splitIndex) + tailSubrange->list_locations.push_back(location); + } + // remove tail locations from head + for (sint32 i = 0; i < subrange->list_locations.size(); i++) + { + raLivenessLocation_t* location = subrange->list_locations.data() + i; + if (location->index >= splitIndex) + { + subrange->list_locations.resize(i); + break; + } + } + // adjust start/end + if (trimToHole) + { + if (subrange->list_locations.empty()) + { + subrange->end.index = subrange->start.index+1; + } + else + { + subrange->end.index = subrange->list_locations.back().index + 1; + } + if (tailSubrange->list_locations.empty()) + { + assert_dbg(); // should not happen? (In this case we can just avoid generating a tail at all) + } + else + { + tailSubrange->start.index = tailSubrange->list_locations.front().index; + } + } + return tailSubrange; +} + +void PPCRecRA_updateOrAddSubrangeLocation(raLivenessSubrange_t* subrange, sint32 index, bool isRead, bool isWrite) +{ + if (subrange->list_locations.empty()) + { + subrange->list_locations.emplace_back(index, isRead, isWrite); + return; + } + raLivenessLocation_t* lastLocation = subrange->list_locations.data() + (subrange->list_locations.size() - 1); + cemu_assert_debug(lastLocation->index <= index); + if (lastLocation->index == index) + { + // update + lastLocation->isRead = lastLocation->isRead || isRead; + lastLocation->isWrite = lastLocation->isWrite || isWrite; + return; + } + // add new + subrange->list_locations.emplace_back(index, isRead, isWrite); +} + +sint32 PPCRecRARange_getReadWriteCost(PPCRecImlSegment_t* imlSegment) +{ + sint32 v = imlSegment->loopDepth + 1; + v *= 5; + return v*v; // 25, 100, 225, 400 +} + +// calculate cost of entire range +// ignores data flow and does not detect avoidable reads/stores +sint32 PPCRecRARange_estimateCost(raLivenessRange_t* range) +{ + sint32 cost = 0; + + // todo - this algorithm isn't accurate. If we have 10 parallel branches with a load each then the actual cost is still only that of one branch (plus minimal extra cost for generating more code). + + // currently we calculate the cost based on the most expensive entry/exit point + + sint32 mostExpensiveRead = 0; + sint32 mostExpensiveWrite = 0; + sint32 readCount = 0; + sint32 writeCount = 0; + + for (auto& subrange : range->list_subranges) + { + if (subrange->start.index != RA_INTER_RANGE_START) + { + //cost += PPCRecRARange_getReadWriteCost(subrange->imlSegment); + mostExpensiveRead = std::max(mostExpensiveRead, PPCRecRARange_getReadWriteCost(subrange->imlSegment)); + readCount++; + } + if (subrange->end.index != RA_INTER_RANGE_END) + { + //cost += PPCRecRARange_getReadWriteCost(subrange->imlSegment); + mostExpensiveWrite = std::max(mostExpensiveWrite, PPCRecRARange_getReadWriteCost(subrange->imlSegment)); + writeCount++; + } + } + cost = mostExpensiveRead + mostExpensiveWrite; + cost = cost + (readCount + writeCount) / 10; + return cost; +} + +// calculate cost of range that it would have after calling PPCRecRA_explodeRange() on it +sint32 PPCRecRARange_estimateAdditionalCostAfterRangeExplode(raLivenessRange_t* range) +{ + sint32 cost = -PPCRecRARange_estimateCost(range); + for (auto& subrange : range->list_subranges) + { + if (subrange->list_locations.empty()) + continue; + cost += PPCRecRARange_getReadWriteCost(subrange->imlSegment) * 2; // we assume a read and a store + } + return cost; +} + +sint32 PPCRecRARange_estimateAdditionalCostAfterSplit(raLivenessSubrange_t* subrange, sint32 splitIndex) +{ + // validation +#ifndef PUBLIC_RELEASE + if (subrange->end.index == RA_INTER_RANGE_END) + assert_dbg(); +#endif + + sint32 cost = 0; + // find split position in location list + if (subrange->list_locations.empty()) + { + assert_dbg(); // should not happen? + return 0; + } + if (splitIndex <= subrange->list_locations.front().index) + return 0; + if (splitIndex > subrange->list_locations.back().index) + return 0; + + // todo - determine exact cost of split subranges + + cost += PPCRecRARange_getReadWriteCost(subrange->imlSegment) * 2; // currently we assume that the additional region will require a read and a store + + //for (sint32 f = 0; f < subrange->list_locations.size(); f++) + //{ + // raLivenessLocation_t* location = subrange->list_locations.data() + f; + // if (location->index >= splitIndex) + // { + // ... + // return cost; + // } + //} + + return cost; +} diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlRanges.h b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlRanges.h new file mode 100644 index 00000000..01970bbf --- /dev/null +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlRanges.h @@ -0,0 +1,27 @@ +#pragma once + +raLivenessRange_t* PPCRecRA_createRangeBase(ppcImlGenContext_t* ppcImlGenContext, uint32 virtualRegister, uint32 name); +raLivenessSubrange_t* PPCRecRA_createSubrange(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange_t* range, PPCRecImlSegment_t* imlSegment, sint32 startIndex, sint32 endIndex); +void PPCRecRA_deleteSubrange(ppcImlGenContext_t* ppcImlGenContext, raLivenessSubrange_t* subrange); +void PPCRecRA_deleteRange(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange_t* range); +void PPCRecRA_deleteAllRanges(ppcImlGenContext_t* ppcImlGenContext); + +void PPCRecRA_mergeRanges(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange_t* range, raLivenessRange_t* absorbedRange); +void PPCRecRA_explodeRange(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange_t* range); + +void PPCRecRA_mergeSubranges(ppcImlGenContext_t* ppcImlGenContext, raLivenessSubrange_t* subrange, raLivenessSubrange_t* absorbedSubrange); + +raLivenessSubrange_t* PPCRecRA_splitLocalSubrange(ppcImlGenContext_t* ppcImlGenContext, raLivenessSubrange_t* subrange, sint32 splitIndex, bool trimToHole = false); + +void PPCRecRA_updateOrAddSubrangeLocation(raLivenessSubrange_t* subrange, sint32 index, bool isRead, bool isWrite); +void PPCRecRA_debugValidateSubrange(raLivenessSubrange_t* subrange); + +// cost estimation +sint32 PPCRecRARange_getReadWriteCost(PPCRecImlSegment_t* imlSegment); +sint32 PPCRecRARange_estimateCost(raLivenessRange_t* range); +sint32 PPCRecRARange_estimateAdditionalCostAfterRangeExplode(raLivenessRange_t* range); +sint32 PPCRecRARange_estimateAdditionalCostAfterSplit(raLivenessSubrange_t* subrange, sint32 splitIndex); + +// special values to mark the index of ranges that reach across the segment border +#define RA_INTER_RANGE_START (-1) +#define RA_INTER_RANGE_END (0x70000000) diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlRegisterAllocator.cpp b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlRegisterAllocator.cpp new file mode 100644 index 00000000..92fbd9b0 --- /dev/null +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlRegisterAllocator.cpp @@ -0,0 +1,1012 @@ +#include "PPCRecompiler.h" +#include "PPCRecompilerIml.h" +#include "PPCRecompilerX64.h" +#include "PPCRecompilerImlRanges.h" + +void PPCRecompiler_replaceGPRRegisterUsageMultiple(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlInstruction_t* imlInstruction, sint32 gprRegisterSearched[4], sint32 gprRegisterReplaced[4]); + +bool PPCRecompiler_isSuffixInstruction(PPCRecImlInstruction_t* iml); + +uint32 recRACurrentIterationIndex = 0; + +uint32 PPCRecRA_getNextIterationIndex() +{ + recRACurrentIterationIndex++; + return recRACurrentIterationIndex; +} + +bool _detectLoop(PPCRecImlSegment_t* currentSegment, sint32 depth, uint32 iterationIndex, PPCRecImlSegment_t* imlSegmentLoopBase) +{ + if (currentSegment == imlSegmentLoopBase) + return true; + if (currentSegment->raInfo.lastIterationIndex == iterationIndex) + return currentSegment->raInfo.isPartOfProcessedLoop; + if (depth >= 9) + return false; + currentSegment->raInfo.lastIterationIndex = iterationIndex; + currentSegment->raInfo.isPartOfProcessedLoop = false; + + if (currentSegment->nextSegmentIsUncertain) + return false; + if (currentSegment->nextSegmentBranchNotTaken) + { + if (currentSegment->nextSegmentBranchNotTaken->momentaryIndex > currentSegment->momentaryIndex) + { + currentSegment->raInfo.isPartOfProcessedLoop = _detectLoop(currentSegment->nextSegmentBranchNotTaken, depth + 1, iterationIndex, imlSegmentLoopBase); + } + } + if (currentSegment->nextSegmentBranchTaken) + { + if (currentSegment->nextSegmentBranchTaken->momentaryIndex > currentSegment->momentaryIndex) + { + currentSegment->raInfo.isPartOfProcessedLoop = _detectLoop(currentSegment->nextSegmentBranchTaken, depth + 1, iterationIndex, imlSegmentLoopBase); + } + } + if (currentSegment->raInfo.isPartOfProcessedLoop) + currentSegment->loopDepth++; + return currentSegment->raInfo.isPartOfProcessedLoop; +} + +void PPCRecRA_detectLoop(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegmentLoopBase) +{ + uint32 iterationIndex = PPCRecRA_getNextIterationIndex(); + imlSegmentLoopBase->raInfo.lastIterationIndex = iterationIndex; + if (_detectLoop(imlSegmentLoopBase->nextSegmentBranchTaken, 0, iterationIndex, imlSegmentLoopBase)) + { + imlSegmentLoopBase->loopDepth++; + } +} + +void PPCRecRA_identifyLoop(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegment) +{ + if (imlSegment->nextSegmentIsUncertain) + return; + // check if this segment has a branch that links to itself (tight loop) + if (imlSegment->nextSegmentBranchTaken == imlSegment) + { + // segment loops over itself + imlSegment->loopDepth++; + return; + } + // check if this segment has a branch that goes backwards (potential complex loop) + if (imlSegment->nextSegmentBranchTaken && imlSegment->nextSegmentBranchTaken->momentaryIndex < imlSegment->momentaryIndex) + { + PPCRecRA_detectLoop(ppcImlGenContext, imlSegment); + } +} + +typedef struct +{ + sint32 name; + sint32 virtualRegister; + sint32 physicalRegister; + bool isDirty; +}raRegisterState_t; + +const sint32 _raInfo_physicalGPRCount = PPC_X64_GPR_USABLE_REGISTERS; + +raRegisterState_t* PPCRecRA_getRegisterState(raRegisterState_t* regState, sint32 virtualRegister) +{ + for (sint32 i = 0; i < _raInfo_physicalGPRCount; i++) + { + if (regState[i].virtualRegister == virtualRegister) + { +#ifndef PUBLIC_RELEASE + if (regState[i].physicalRegister < 0) + assert_dbg(); +#endif + return regState + i; + } + } + return nullptr; +} + +raRegisterState_t* PPCRecRA_getFreePhysicalRegister(raRegisterState_t* regState) +{ + for (sint32 i = 0; i < _raInfo_physicalGPRCount; i++) + { + if (regState[i].physicalRegister < 0) + { + regState[i].physicalRegister = i; + return regState + i; + } + } + return nullptr; +} + +typedef struct +{ + uint16 registerIndex; + uint16 registerName; +}raLoadStoreInfo_t; + +void PPCRecRA_insertGPRLoadInstruction(PPCRecImlSegment_t* imlSegment, sint32 insertIndex, sint32 registerIndex, sint32 registerName) +{ + PPCRecompiler_pushBackIMLInstructions(imlSegment, insertIndex, 1); + PPCRecImlInstruction_t* imlInstructionItr = imlSegment->imlList + (insertIndex + 0); + memset(imlInstructionItr, 0x00, sizeof(PPCRecImlInstruction_t)); + imlInstructionItr->type = PPCREC_IML_TYPE_R_NAME; + imlInstructionItr->operation = PPCREC_IML_OP_ASSIGN; + imlInstructionItr->op_r_name.registerIndex = registerIndex; + imlInstructionItr->op_r_name.name = registerName; + imlInstructionItr->op_r_name.copyWidth = 32; + imlInstructionItr->op_r_name.flags = 0; +} + +void PPCRecRA_insertGPRLoadInstructions(PPCRecImlSegment_t* imlSegment, sint32 insertIndex, raLoadStoreInfo_t* loadList, sint32 loadCount) +{ + PPCRecompiler_pushBackIMLInstructions(imlSegment, insertIndex, loadCount); + memset(imlSegment->imlList + (insertIndex + 0), 0x00, sizeof(PPCRecImlInstruction_t)*loadCount); + for (sint32 i = 0; i < loadCount; i++) + { + PPCRecImlInstruction_t* imlInstructionItr = imlSegment->imlList + (insertIndex + i); + imlInstructionItr->type = PPCREC_IML_TYPE_R_NAME; + imlInstructionItr->operation = PPCREC_IML_OP_ASSIGN; + imlInstructionItr->op_r_name.registerIndex = (uint8)loadList[i].registerIndex; + imlInstructionItr->op_r_name.name = (uint32)loadList[i].registerName; + imlInstructionItr->op_r_name.copyWidth = 32; + imlInstructionItr->op_r_name.flags = 0; + } +} + +void PPCRecRA_insertGPRStoreInstruction(PPCRecImlSegment_t* imlSegment, sint32 insertIndex, sint32 registerIndex, sint32 registerName) +{ + PPCRecompiler_pushBackIMLInstructions(imlSegment, insertIndex, 1); + PPCRecImlInstruction_t* imlInstructionItr = imlSegment->imlList + (insertIndex + 0); + memset(imlInstructionItr, 0x00, sizeof(PPCRecImlInstruction_t)); + imlInstructionItr->type = PPCREC_IML_TYPE_NAME_R; + imlInstructionItr->operation = PPCREC_IML_OP_ASSIGN; + imlInstructionItr->op_r_name.registerIndex = registerIndex; + imlInstructionItr->op_r_name.name = registerName; + imlInstructionItr->op_r_name.copyWidth = 32; + imlInstructionItr->op_r_name.flags = 0; +} + +void PPCRecRA_insertGPRStoreInstructions(PPCRecImlSegment_t* imlSegment, sint32 insertIndex, raLoadStoreInfo_t* storeList, sint32 storeCount) +{ + PPCRecompiler_pushBackIMLInstructions(imlSegment, insertIndex, storeCount); + memset(imlSegment->imlList + (insertIndex + 0), 0x00, sizeof(PPCRecImlInstruction_t)*storeCount); + for (sint32 i = 0; i < storeCount; i++) + { + PPCRecImlInstruction_t* imlInstructionItr = imlSegment->imlList + (insertIndex + i); + memset(imlInstructionItr, 0x00, sizeof(PPCRecImlInstruction_t)); + imlInstructionItr->type = PPCREC_IML_TYPE_NAME_R; + imlInstructionItr->operation = PPCREC_IML_OP_ASSIGN; + imlInstructionItr->op_r_name.registerIndex = (uint8)storeList[i].registerIndex; + imlInstructionItr->op_r_name.name = (uint32)storeList[i].registerName; + imlInstructionItr->op_r_name.copyWidth = 32; + imlInstructionItr->op_r_name.flags = 0; + } +} + +#define SUBRANGE_LIST_SIZE (128) + +sint32 PPCRecRA_countInstructionsUntilNextUse(raLivenessSubrange_t* subrange, sint32 startIndex) +{ + for (sint32 i = 0; i < subrange->list_locations.size(); i++) + { + if (subrange->list_locations.data()[i].index >= startIndex) + return subrange->list_locations.data()[i].index - startIndex; + } + return INT_MAX; +} + +// count how many instructions there are until physRegister is used by any subrange (returns 0 if register is in use at startIndex, and INT_MAX if not used for the remainder of the segment) +sint32 PPCRecRA_countInstructionsUntilNextLocalPhysRegisterUse(PPCRecImlSegment_t* imlSegment, sint32 startIndex, sint32 physRegister) +{ + sint32 minDistance = INT_MAX; + // next + raLivenessSubrange_t* subrangeItr = imlSegment->raInfo.linkedList_allSubranges; + while(subrangeItr) + { + if (subrangeItr->range->physicalRegister != physRegister) + { + subrangeItr = subrangeItr->link_segmentSubrangesGPR.next; + continue; + } + if (startIndex >= subrangeItr->start.index && startIndex < subrangeItr->end.index) + return 0; + if (subrangeItr->start.index >= startIndex) + { + minDistance = std::min(minDistance, (subrangeItr->start.index - startIndex)); + } + subrangeItr = subrangeItr->link_segmentSubrangesGPR.next; + } + return minDistance; +} + +typedef struct +{ + raLivenessSubrange_t* liveRangeList[64]; + sint32 liveRangesCount; +}raLiveRangeInfo_t; + +// return a bitmask that contains only registers that are not used by any colliding range +uint32 PPCRecRA_getAllowedRegisterMaskForFullRange(raLivenessRange_t* range) +{ + uint32 physRegisterMask = (1 << PPC_X64_GPR_USABLE_REGISTERS) - 1; + for (auto& subrange : range->list_subranges) + { + PPCRecImlSegment_t* imlSegment = subrange->imlSegment; + raLivenessSubrange_t* subrangeItr = imlSegment->raInfo.linkedList_allSubranges; + while(subrangeItr) + { + if (subrange == subrangeItr) + { + // next + subrangeItr = subrangeItr->link_segmentSubrangesGPR.next; + continue; + } + + if (subrange->start.index < subrangeItr->end.index && subrange->end.index > subrangeItr->start.index || + (subrange->start.index == RA_INTER_RANGE_START && subrange->start.index == subrangeItr->start.index) || + (subrange->end.index == RA_INTER_RANGE_END && subrange->end.index == subrangeItr->end.index) ) + { + if(subrangeItr->range->physicalRegister >= 0) + physRegisterMask &= ~(1<<(subrangeItr->range->physicalRegister)); + } + // next + subrangeItr = subrangeItr->link_segmentSubrangesGPR.next; + } + } + return physRegisterMask; +} + +bool _livenessRangeStartCompare(raLivenessSubrange_t* lhs, raLivenessSubrange_t* rhs) { return lhs->start.index < rhs->start.index; } + +void _sortSegmentAllSubrangesLinkedList(PPCRecImlSegment_t* imlSegment) +{ + raLivenessSubrange_t* subrangeList[4096+1]; + sint32 count = 0; + // disassemble linked list + raLivenessSubrange_t* subrangeItr = imlSegment->raInfo.linkedList_allSubranges; + while (subrangeItr) + { + if (count >= 4096) + assert_dbg(); + subrangeList[count] = subrangeItr; + count++; + // next + subrangeItr = subrangeItr->link_segmentSubrangesGPR.next; + } + if (count == 0) + { + imlSegment->raInfo.linkedList_allSubranges = nullptr; + return; + } + // sort + std::sort(subrangeList, subrangeList + count, _livenessRangeStartCompare); + //for (sint32 i1 = 0; i1 < count; i1++) + //{ + // for (sint32 i2 = i1+1; i2 < count; i2++) + // { + // if (subrangeList[i1]->start.index > subrangeList[i2]->start.index) + // { + // // swap + // raLivenessSubrange_t* temp = subrangeList[i1]; + // subrangeList[i1] = subrangeList[i2]; + // subrangeList[i2] = temp; + // } + // } + //} + // reassemble linked list + subrangeList[count] = nullptr; + imlSegment->raInfo.linkedList_allSubranges = subrangeList[0]; + subrangeList[0]->link_segmentSubrangesGPR.prev = nullptr; + subrangeList[0]->link_segmentSubrangesGPR.next = subrangeList[1]; + for (sint32 i = 1; i < count; i++) + { + subrangeList[i]->link_segmentSubrangesGPR.prev = subrangeList[i - 1]; + subrangeList[i]->link_segmentSubrangesGPR.next = subrangeList[i + 1]; + } + // validate list +#ifndef PUBLIC_RELEASE + sint32 count2 = 0; + subrangeItr = imlSegment->raInfo.linkedList_allSubranges; + sint32 currentStartIndex = RA_INTER_RANGE_START; + while (subrangeItr) + { + count2++; + if (subrangeItr->start.index < currentStartIndex) + assert_dbg(); + currentStartIndex = subrangeItr->start.index; + // next + subrangeItr = subrangeItr->link_segmentSubrangesGPR.next; + } + if (count != count2) + assert_dbg(); +#endif +} + +bool PPCRecRA_assignSegmentRegisters(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegment) +{ + + // sort subranges ascending by start index + + //std::sort(imlSegment->raInfo.list_subranges.begin(), imlSegment->raInfo.list_subranges.end(), _sortSubrangesByStartIndexDepr); + _sortSegmentAllSubrangesLinkedList(imlSegment); + + raLiveRangeInfo_t liveInfo; + liveInfo.liveRangesCount = 0; + //sint32 subrangeIndex = 0; + //for (auto& subrange : imlSegment->raInfo.list_subranges) + raLivenessSubrange_t* subrangeItr = imlSegment->raInfo.linkedList_allSubranges; + while(subrangeItr) + { + sint32 currentIndex = subrangeItr->start.index; + // validate subrange + PPCRecRA_debugValidateSubrange(subrangeItr); + // expire ranges + for (sint32 f = 0; f < liveInfo.liveRangesCount; f++) + { + raLivenessSubrange_t* liverange = liveInfo.liveRangeList[f]; + if (liverange->end.index <= currentIndex && liverange->end.index != RA_INTER_RANGE_END) + { +#ifndef PUBLIC_RELEASE + if (liverange->subrangeBranchTaken || liverange->subrangeBranchNotTaken) + assert_dbg(); // infinite subranges should not expire +#endif + // remove entry + liveInfo.liveRangesCount--; + liveInfo.liveRangeList[f] = liveInfo.liveRangeList[liveInfo.liveRangesCount]; + f--; + } + } + // check if subrange already has register assigned + if (subrangeItr->range->physicalRegister >= 0) + { + // verify if register is actually available +#ifndef PUBLIC_RELEASE + for (sint32 f = 0; f < liveInfo.liveRangesCount; f++) + { + raLivenessSubrange_t* liverangeItr = liveInfo.liveRangeList[f]; + if (liverangeItr->range->physicalRegister == subrangeItr->range->physicalRegister) + { + // this should never happen because we try to preventively avoid register conflicts + assert_dbg(); + } + } +#endif + // add to live ranges + liveInfo.liveRangeList[liveInfo.liveRangesCount] = subrangeItr; + liveInfo.liveRangesCount++; + // next + subrangeItr = subrangeItr->link_segmentSubrangesGPR.next; + continue; + } + // find free register + uint32 physRegisterMask = (1<<PPC_X64_GPR_USABLE_REGISTERS)-1; + for (sint32 f = 0; f < liveInfo.liveRangesCount; f++) + { + raLivenessSubrange_t* liverange = liveInfo.liveRangeList[f]; + if (liverange->range->physicalRegister < 0) + assert_dbg(); + physRegisterMask &= ~(1<<liverange->range->physicalRegister); + } + // check intersections with other ranges and determine allowed registers + uint32 allowedPhysRegisterMask = 0; + uint32 unusedRegisterMask = physRegisterMask; // mask of registers that are currently not used (does not include range checks) + if (physRegisterMask != 0) + { + allowedPhysRegisterMask = PPCRecRA_getAllowedRegisterMaskForFullRange(subrangeItr->range); + physRegisterMask &= allowedPhysRegisterMask; + } + if (physRegisterMask == 0) + { + struct + { + // estimated costs and chosen candidates for the different spill strategies + // hole cutting into a local range + struct + { + sint32 distance; + raLivenessSubrange_t* largestHoleSubrange; + sint32 cost; // additional cost of choosing this candidate + }localRangeHoleCutting; + // split current range (this is generally only a good choice when the current range is long but rarely used) + struct + { + sint32 cost; + sint32 physRegister; + sint32 distance; // size of hole + }availableRegisterHole; + // explode a inter-segment range (prefer ranges that are not read/written in this segment) + struct + { + raLivenessRange_t* range; + sint32 cost; + sint32 distance; // size of hole + // note: If we explode a range, we still have to check the size of the hole that becomes available, if too small then we need to add cost of splitting local subrange + }explodeRange; + // todo - add more strategies, make cost estimation smarter (for example, in some cases splitting can have reduced or no cost if read/store can be avoided due to data flow) + }spillStrategies; + // cant assign register + // there might be registers available, we just can't use them due to range conflicts + if (subrangeItr->end.index != RA_INTER_RANGE_END) + { + // range ends in current segment + + // Current algo looks like this: + // 1) Get the size of the largest possible hole that we can cut into any of the live local subranges + // 1.1) Check if the hole is large enough to hold the current subrange + // 2) If yes, cut hole and return false (full retry) + // 3) If no, try to reuse free register (need to determine how large the region is we can use) + // 4) If there is no free register or the range is extremely short go back to step 1+2 but additionally split the current subrange at where the hole ends + + cemu_assert_debug(currentIndex == subrangeItr->start.index); + + sint32 requiredSize = subrangeItr->end.index - subrangeItr->start.index; + // evaluate strategy: Cut hole into local subrange + spillStrategies.localRangeHoleCutting.distance = -1; + spillStrategies.localRangeHoleCutting.largestHoleSubrange = nullptr; + spillStrategies.localRangeHoleCutting.cost = INT_MAX; + if (currentIndex >= 0) + { + for (sint32 f = 0; f < liveInfo.liveRangesCount; f++) + { + raLivenessSubrange_t* candidate = liveInfo.liveRangeList[f]; + if (candidate->end.index == RA_INTER_RANGE_END) + continue; + sint32 distance = PPCRecRA_countInstructionsUntilNextUse(candidate, currentIndex); + if (distance < 2) + continue; // not even worth the consideration + // calculate split cost of candidate + sint32 cost = PPCRecRARange_estimateAdditionalCostAfterSplit(candidate, currentIndex + distance); + // calculate additional split cost of currentRange if hole is not large enough + if (distance < requiredSize) + { + cost += PPCRecRARange_estimateAdditionalCostAfterSplit(subrangeItr, currentIndex + distance); + // we also slightly increase cost in relation to the remaining length (in order to make the algorithm prefer larger holes) + cost += (requiredSize - distance) / 10; + } + // compare cost with previous candidates + if (cost < spillStrategies.localRangeHoleCutting.cost) + { + spillStrategies.localRangeHoleCutting.cost = cost; + spillStrategies.localRangeHoleCutting.distance = distance; + spillStrategies.localRangeHoleCutting.largestHoleSubrange = candidate; + } + } + } + // evaluate strategy: Split current range to fit in available holes + spillStrategies.availableRegisterHole.cost = INT_MAX; + spillStrategies.availableRegisterHole.distance = -1; + spillStrategies.availableRegisterHole.physRegister = -1; + if (currentIndex >= 0) + { + if (unusedRegisterMask != 0) + { + for (sint32 t = 0; t < PPC_X64_GPR_USABLE_REGISTERS; t++) + { + if ((unusedRegisterMask&(1 << t)) == 0) + continue; + // get size of potential hole for this register + sint32 distance = PPCRecRA_countInstructionsUntilNextLocalPhysRegisterUse(imlSegment, currentIndex, t); + if (distance < 2) + continue; // not worth consideration + // calculate additional cost due to split + if (distance >= requiredSize) + assert_dbg(); // should not happen or else we would have selected this register + sint32 cost = PPCRecRARange_estimateAdditionalCostAfterSplit(subrangeItr, currentIndex + distance); + // add small additional cost for the remaining range (prefer larger holes) + cost += (requiredSize - distance) / 10; + if (cost < spillStrategies.availableRegisterHole.cost) + { + spillStrategies.availableRegisterHole.cost = cost; + spillStrategies.availableRegisterHole.distance = distance; + spillStrategies.availableRegisterHole.physRegister = t; + } + } + } + } + // evaluate strategy: Explode inter-segment ranges + spillStrategies.explodeRange.cost = INT_MAX; + spillStrategies.explodeRange.range = nullptr; + spillStrategies.explodeRange.distance = -1; + for (sint32 f = 0; f < liveInfo.liveRangesCount; f++) + { + raLivenessSubrange_t* candidate = liveInfo.liveRangeList[f]; + if (candidate->end.index != RA_INTER_RANGE_END) + continue; + sint32 distance = PPCRecRA_countInstructionsUntilNextUse(liveInfo.liveRangeList[f], currentIndex); + if( distance < 2) + continue; + sint32 cost; + cost = PPCRecRARange_estimateAdditionalCostAfterRangeExplode(candidate->range); + // if the hole is not large enough, add cost of splitting current subrange + if (distance < requiredSize) + { + cost += PPCRecRARange_estimateAdditionalCostAfterSplit(subrangeItr, currentIndex + distance); + // add small additional cost for the remaining range (prefer larger holes) + cost += (requiredSize - distance) / 10; + } + // compare with current best candidate for this strategy + if (cost < spillStrategies.explodeRange.cost) + { + spillStrategies.explodeRange.cost = cost; + spillStrategies.explodeRange.distance = distance; + spillStrategies.explodeRange.range = candidate->range; + } + } + // choose strategy + if (spillStrategies.explodeRange.cost != INT_MAX && spillStrategies.explodeRange.cost <= spillStrategies.localRangeHoleCutting.cost && spillStrategies.explodeRange.cost <= spillStrategies.availableRegisterHole.cost) + { + // explode range + PPCRecRA_explodeRange(ppcImlGenContext, spillStrategies.explodeRange.range); + // split current subrange if necessary + if( requiredSize > spillStrategies.explodeRange.distance) + PPCRecRA_splitLocalSubrange(ppcImlGenContext, subrangeItr, currentIndex+spillStrategies.explodeRange.distance, true); + } + else if (spillStrategies.availableRegisterHole.cost != INT_MAX && spillStrategies.availableRegisterHole.cost <= spillStrategies.explodeRange.cost && spillStrategies.availableRegisterHole.cost <= spillStrategies.localRangeHoleCutting.cost) + { + // use available register + PPCRecRA_splitLocalSubrange(ppcImlGenContext, subrangeItr, currentIndex + spillStrategies.availableRegisterHole.distance, true); + } + else if (spillStrategies.localRangeHoleCutting.cost != INT_MAX && spillStrategies.localRangeHoleCutting.cost <= spillStrategies.explodeRange.cost && spillStrategies.localRangeHoleCutting.cost <= spillStrategies.availableRegisterHole.cost) + { + // cut hole + PPCRecRA_splitLocalSubrange(ppcImlGenContext, spillStrategies.localRangeHoleCutting.largestHoleSubrange, currentIndex + spillStrategies.localRangeHoleCutting.distance, true); + // split current subrange if necessary + if (requiredSize > spillStrategies.localRangeHoleCutting.distance) + PPCRecRA_splitLocalSubrange(ppcImlGenContext, subrangeItr, currentIndex + spillStrategies.localRangeHoleCutting.distance, true); + } + else if (subrangeItr->start.index == RA_INTER_RANGE_START) + { + // alternative strategy if we have no other choice: explode current range + PPCRecRA_explodeRange(ppcImlGenContext, subrangeItr->range); + } + else + assert_dbg(); + + return false; + } + else + { + // range exceeds segment border + // simple but bad solution -> explode the entire range (no longer allow it to cross segment boundaries) + // better solutions: 1) Depending on the situation, we can explode other ranges to resolve the conflict. Thus we should explode the range with the lowest extra cost + // 2) Or we explode the range only partially + // explode the range with the least cost + spillStrategies.explodeRange.cost = INT_MAX; + spillStrategies.explodeRange.range = nullptr; + spillStrategies.explodeRange.distance = -1; + for (sint32 f = 0; f < liveInfo.liveRangesCount; f++) + { + raLivenessSubrange_t* candidate = liveInfo.liveRangeList[f]; + if (candidate->end.index != RA_INTER_RANGE_END) + continue; + // only select candidates that clash with current subrange + if (candidate->range->physicalRegister < 0 && candidate != subrangeItr) + continue; + + sint32 cost; + cost = PPCRecRARange_estimateAdditionalCostAfterRangeExplode(candidate->range); + // compare with current best candidate for this strategy + if (cost < spillStrategies.explodeRange.cost) + { + spillStrategies.explodeRange.cost = cost; + spillStrategies.explodeRange.distance = INT_MAX; + spillStrategies.explodeRange.range = candidate->range; + } + } + // add current range as a candidate too + sint32 ownCost; + ownCost = PPCRecRARange_estimateAdditionalCostAfterRangeExplode(subrangeItr->range); + if (ownCost < spillStrategies.explodeRange.cost) + { + spillStrategies.explodeRange.cost = ownCost; + spillStrategies.explodeRange.distance = INT_MAX; + spillStrategies.explodeRange.range = subrangeItr->range; + } + if (spillStrategies.explodeRange.cost == INT_MAX) + assert_dbg(); // should not happen + PPCRecRA_explodeRange(ppcImlGenContext, spillStrategies.explodeRange.range); + } + return false; + } + // assign register to range + sint32 registerIndex = -1; + for (sint32 f = 0; f < PPC_X64_GPR_USABLE_REGISTERS; f++) + { + if ((physRegisterMask&(1 << f)) != 0) + { + registerIndex = f; + break; + } + } + subrangeItr->range->physicalRegister = registerIndex; + // add to live ranges + liveInfo.liveRangeList[liveInfo.liveRangesCount] = subrangeItr; + liveInfo.liveRangesCount++; + // next + subrangeItr = subrangeItr->link_segmentSubrangesGPR.next; + } + return true; +} + +void PPCRecRA_assignRegisters(ppcImlGenContext_t* ppcImlGenContext) +{ + // start with frequently executed segments first + sint32 maxLoopDepth = 0; + for (sint32 i = 0; i < ppcImlGenContext->segmentListCount; i++) + { + maxLoopDepth = std::max(maxLoopDepth, ppcImlGenContext->segmentList[i]->loopDepth); + } + while (true) + { + bool done = false; + for (sint32 d = maxLoopDepth; d >= 0; d--) + { + for (sint32 i = 0; i < ppcImlGenContext->segmentListCount; i++) + { + PPCRecImlSegment_t* imlSegment = ppcImlGenContext->segmentList[i]; + if (imlSegment->loopDepth != d) + continue; + done = PPCRecRA_assignSegmentRegisters(ppcImlGenContext, imlSegment); + if (done == false) + break; + } + if (done == false) + break; + } + if (done) + break; + } +} + +typedef struct +{ + raLivenessSubrange_t* subrangeList[SUBRANGE_LIST_SIZE]; + sint32 subrangeCount; + bool hasUndefinedEndings; +}subrangeEndingInfo_t; + +void _findSubrangeWriteEndings(raLivenessSubrange_t* subrange, uint32 iterationIndex, sint32 depth, subrangeEndingInfo_t* info) +{ + if (depth >= 30) + { + info->hasUndefinedEndings = true; + return; + } + if (subrange->lastIterationIndex == iterationIndex) + return; // already processed + subrange->lastIterationIndex = iterationIndex; + if (subrange->hasStoreDelayed) + return; // no need to traverse this subrange + PPCRecImlSegment_t* imlSegment = subrange->imlSegment; + if (subrange->end.index != RA_INTER_RANGE_END) + { + // ending segment + if (info->subrangeCount >= SUBRANGE_LIST_SIZE) + { + info->hasUndefinedEndings = true; + return; + } + else + { + info->subrangeList[info->subrangeCount] = subrange; + info->subrangeCount++; + } + return; + } + + // traverse next subranges in flow + if (imlSegment->nextSegmentBranchNotTaken) + { + if (subrange->subrangeBranchNotTaken == nullptr) + { + info->hasUndefinedEndings = true; + } + else + { + _findSubrangeWriteEndings(subrange->subrangeBranchNotTaken, iterationIndex, depth + 1, info); + } + } + if (imlSegment->nextSegmentBranchTaken) + { + if (subrange->subrangeBranchTaken == nullptr) + { + info->hasUndefinedEndings = true; + } + else + { + _findSubrangeWriteEndings(subrange->subrangeBranchTaken, iterationIndex, depth + 1, info); + } + } +} + +void _analyzeRangeDataFlow(raLivenessSubrange_t* subrange) +{ + if (subrange->end.index != RA_INTER_RANGE_END) + return; + // analyze data flow across segments (if this segment has writes) + if (subrange->hasStore) + { + subrangeEndingInfo_t writeEndingInfo; + writeEndingInfo.subrangeCount = 0; + writeEndingInfo.hasUndefinedEndings = false; + _findSubrangeWriteEndings(subrange, PPCRecRA_getNextIterationIndex(), 0, &writeEndingInfo); + if (writeEndingInfo.hasUndefinedEndings == false) + { + // get cost of delaying store into endings + sint32 delayStoreCost = 0; + bool alreadyStoredInAllEndings = true; + for (sint32 i = 0; i < writeEndingInfo.subrangeCount; i++) + { + raLivenessSubrange_t* subrangeItr = writeEndingInfo.subrangeList[i]; + if( subrangeItr->hasStore ) + continue; // this ending already stores, no extra cost + alreadyStoredInAllEndings = false; + sint32 storeCost = PPCRecRARange_getReadWriteCost(subrangeItr->imlSegment); + delayStoreCost = std::max(storeCost, delayStoreCost); + } + if (alreadyStoredInAllEndings) + { + subrange->hasStore = false; + subrange->hasStoreDelayed = true; + } + else if (delayStoreCost <= PPCRecRARange_getReadWriteCost(subrange->imlSegment)) + { + subrange->hasStore = false; + subrange->hasStoreDelayed = true; + for (sint32 i = 0; i < writeEndingInfo.subrangeCount; i++) + { + raLivenessSubrange_t* subrangeItr = writeEndingInfo.subrangeList[i]; + subrangeItr->hasStore = true; + } + } + } + } +} + +void PPCRecRA_generateSegmentInstructions(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegment) +{ + sint16 virtualReg2PhysReg[PPC_REC_MAX_VIRTUAL_GPR]; + for (sint32 i = 0; i < PPC_REC_MAX_VIRTUAL_GPR; i++) + virtualReg2PhysReg[i] = -1; + + raLiveRangeInfo_t liveInfo; + liveInfo.liveRangesCount = 0; + sint32 index = 0; + sint32 suffixInstructionCount = (imlSegment->imlListCount > 0 && PPCRecompiler_isSuffixInstruction(imlSegment->imlList + imlSegment->imlListCount - 1)) ? 1 : 0; + // load register ranges that are supplied from previous segments + raLivenessSubrange_t* subrangeItr = imlSegment->raInfo.linkedList_allSubranges; + //for (auto& subrange : imlSegment->raInfo.list_subranges) + while(subrangeItr) + { + if (subrangeItr->start.index == RA_INTER_RANGE_START) + { + liveInfo.liveRangeList[liveInfo.liveRangesCount] = subrangeItr; + liveInfo.liveRangesCount++; +#ifndef PUBLIC_RELEASE + // load GPR + if (subrangeItr->_noLoad == false) + { + assert_dbg(); + } + // update translation table + if (virtualReg2PhysReg[subrangeItr->range->virtualRegister] != -1) + assert_dbg(); +#endif + virtualReg2PhysReg[subrangeItr->range->virtualRegister] = subrangeItr->range->physicalRegister; + } + // next + subrangeItr = subrangeItr->link_segmentSubrangesGPR.next; + } + // process instructions + while(index < imlSegment->imlListCount+1) + { + // expire ranges + for (sint32 f = 0; f < liveInfo.liveRangesCount; f++) + { + raLivenessSubrange_t* liverange = liveInfo.liveRangeList[f]; + if (liverange->end.index <= index) + { + // update translation table + if (virtualReg2PhysReg[liverange->range->virtualRegister] == -1) + assert_dbg(); + virtualReg2PhysReg[liverange->range->virtualRegister] = -1; + // store GPR + if (liverange->hasStore) + { + PPCRecRA_insertGPRStoreInstruction(imlSegment, std::min(index, imlSegment->imlListCount - suffixInstructionCount), liverange->range->physicalRegister, liverange->range->name); + index++; + } + // remove entry + liveInfo.liveRangesCount--; + liveInfo.liveRangeList[f] = liveInfo.liveRangeList[liveInfo.liveRangesCount]; + f--; + } + } + // load new ranges + subrangeItr = imlSegment->raInfo.linkedList_allSubranges; + while(subrangeItr) + { + if (subrangeItr->start.index == index) + { + liveInfo.liveRangeList[liveInfo.liveRangesCount] = subrangeItr; + liveInfo.liveRangesCount++; + // load GPR + if (subrangeItr->_noLoad == false) + { + PPCRecRA_insertGPRLoadInstruction(imlSegment, std::min(index, imlSegment->imlListCount - suffixInstructionCount), subrangeItr->range->physicalRegister, subrangeItr->range->name); + index++; + subrangeItr->start.index--; + } + // update translation table + cemu_assert_debug(virtualReg2PhysReg[subrangeItr->range->virtualRegister] == -1); + virtualReg2PhysReg[subrangeItr->range->virtualRegister] = subrangeItr->range->physicalRegister; + } + subrangeItr = subrangeItr->link_segmentSubrangesGPR.next; + } + // replace registers + if (index < imlSegment->imlListCount) + { + PPCImlOptimizerUsedRegisters_t gprTracking; + PPCRecompiler_checkRegisterUsage(NULL, imlSegment->imlList + index, &gprTracking); + + sint32 inputGpr[4]; + inputGpr[0] = gprTracking.gpr[0]; + inputGpr[1] = gprTracking.gpr[1]; + inputGpr[2] = gprTracking.gpr[2]; + inputGpr[3] = gprTracking.gpr[3]; + sint32 replaceGpr[4]; + for (sint32 f = 0; f < 4; f++) + { + sint32 virtualRegister = gprTracking.gpr[f]; + if (virtualRegister < 0) + { + replaceGpr[f] = -1; + continue; + } + if (virtualRegister >= PPC_REC_MAX_VIRTUAL_GPR) + assert_dbg(); + replaceGpr[f] = virtualReg2PhysReg[virtualRegister]; + cemu_assert_debug(replaceGpr[f] >= 0); + } + PPCRecompiler_replaceGPRRegisterUsageMultiple(ppcImlGenContext, imlSegment->imlList + index, inputGpr, replaceGpr); + } + // next iml instruction + index++; + } + // expire infinite subranges (subranges that cross the segment border) + sint32 storeLoadListLength = 0; + raLoadStoreInfo_t loadStoreList[PPC_REC_MAX_VIRTUAL_GPR]; + for (sint32 f = 0; f < liveInfo.liveRangesCount; f++) + { + raLivenessSubrange_t* liverange = liveInfo.liveRangeList[f]; + if (liverange->end.index == RA_INTER_RANGE_END) + { + // update translation table + cemu_assert_debug(virtualReg2PhysReg[liverange->range->virtualRegister] != -1); + virtualReg2PhysReg[liverange->range->virtualRegister] = -1; + // store GPR + if (liverange->hasStore) + { + loadStoreList[storeLoadListLength].registerIndex = liverange->range->physicalRegister; + loadStoreList[storeLoadListLength].registerName = liverange->range->name; + storeLoadListLength++; + } + // remove entry + liveInfo.liveRangesCount--; + liveInfo.liveRangeList[f] = liveInfo.liveRangeList[liveInfo.liveRangesCount]; + f--; + } + else + { + cemu_assert_suspicious(); + } + } + if (storeLoadListLength > 0) + { + PPCRecRA_insertGPRStoreInstructions(imlSegment, imlSegment->imlListCount - suffixInstructionCount, loadStoreList, storeLoadListLength); + } + // load subranges for next segments + subrangeItr = imlSegment->raInfo.linkedList_allSubranges; + storeLoadListLength = 0; + while(subrangeItr) + { + if (subrangeItr->start.index == RA_INTER_RANGE_END) + { + liveInfo.liveRangeList[liveInfo.liveRangesCount] = subrangeItr; + liveInfo.liveRangesCount++; + // load GPR + if (subrangeItr->_noLoad == false) + { + loadStoreList[storeLoadListLength].registerIndex = subrangeItr->range->physicalRegister; + loadStoreList[storeLoadListLength].registerName = subrangeItr->range->name; + storeLoadListLength++; + } + // update translation table + cemu_assert_debug(virtualReg2PhysReg[subrangeItr->range->virtualRegister] == -1); + virtualReg2PhysReg[subrangeItr->range->virtualRegister] = subrangeItr->range->physicalRegister; + } + // next + subrangeItr = subrangeItr->link_segmentSubrangesGPR.next; + } + if (storeLoadListLength > 0) + { + PPCRecRA_insertGPRLoadInstructions(imlSegment, imlSegment->imlListCount - suffixInstructionCount, loadStoreList, storeLoadListLength); + } +} + +void PPCRecRA_generateMoveInstructions(ppcImlGenContext_t* ppcImlGenContext) +{ + for (sint32 s = 0; s < ppcImlGenContext->segmentListCount; s++) + { + PPCRecImlSegment_t* imlSegment = ppcImlGenContext->segmentList[s]; + PPCRecRA_generateSegmentInstructions(ppcImlGenContext, imlSegment); + } +} + +void PPCRecRA_calculateLivenessRangesV2(ppcImlGenContext_t* ppcImlGenContext); +void PPCRecRA_processFlowAndCalculateLivenessRangesV2(ppcImlGenContext_t* ppcImlGenContext); +void PPCRecRA_analyzeRangeDataFlowV2(ppcImlGenContext_t* ppcImlGenContext); + +void PPCRecompilerImm_prepareForRegisterAllocation(ppcImlGenContext_t* ppcImlGenContext) +{ + // insert empty segments after every non-taken branch if the linked segment has more than one input + // this gives the register allocator more room to create efficient spill code + sint32 segmentIndex = 0; + while (segmentIndex < ppcImlGenContext->segmentListCount) + { + PPCRecImlSegment_t* imlSegment = ppcImlGenContext->segmentList[segmentIndex]; + if (imlSegment->nextSegmentIsUncertain) + { + segmentIndex++; + continue; + } + if (imlSegment->nextSegmentBranchTaken == nullptr || imlSegment->nextSegmentBranchNotTaken == nullptr) + { + segmentIndex++; + continue; + } + if (imlSegment->nextSegmentBranchNotTaken->list_prevSegments.size() <= 1) + { + segmentIndex++; + continue; + } + if (imlSegment->nextSegmentBranchNotTaken->isEnterable) + { + segmentIndex++; + continue; + } + PPCRecompilerIml_insertSegments(ppcImlGenContext, segmentIndex + 1, 1); + PPCRecImlSegment_t* imlSegmentP0 = ppcImlGenContext->segmentList[segmentIndex + 0]; + PPCRecImlSegment_t* imlSegmentP1 = ppcImlGenContext->segmentList[segmentIndex + 1]; + PPCRecImlSegment_t* nextSegment = imlSegment->nextSegmentBranchNotTaken; + PPCRecompilerIML_removeLink(imlSegmentP0, nextSegment); + PPCRecompilerIml_setLinkBranchNotTaken(imlSegmentP1, nextSegment); + PPCRecompilerIml_setLinkBranchNotTaken(imlSegmentP0, imlSegmentP1); + segmentIndex++; + } + // detect loops + for (sint32 s = 0; s < ppcImlGenContext->segmentListCount; s++) + { + PPCRecImlSegment_t* imlSegment = ppcImlGenContext->segmentList[s]; + imlSegment->momentaryIndex = s; + } + for (sint32 s = 0; s < ppcImlGenContext->segmentListCount; s++) + { + PPCRecImlSegment_t* imlSegment = ppcImlGenContext->segmentList[s]; + PPCRecRA_identifyLoop(ppcImlGenContext, imlSegment); + } +} + +void PPCRecompilerImm_allocateRegisters(ppcImlGenContext_t* ppcImlGenContext) +{ + PPCRecompilerImm_prepareForRegisterAllocation(ppcImlGenContext); + + ppcImlGenContext->raInfo.list_ranges = std::vector<raLivenessRange_t*>(); + + // calculate liveness + PPCRecRA_calculateLivenessRangesV2(ppcImlGenContext); + PPCRecRA_processFlowAndCalculateLivenessRangesV2(ppcImlGenContext); + + PPCRecRA_assignRegisters(ppcImlGenContext); + + PPCRecRA_analyzeRangeDataFlowV2(ppcImlGenContext); + PPCRecRA_generateMoveInstructions(ppcImlGenContext); + + PPCRecRA_deleteAllRanges(ppcImlGenContext); +} \ No newline at end of file diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlRegisterAllocator2.cpp b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlRegisterAllocator2.cpp new file mode 100644 index 00000000..e2070703 --- /dev/null +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlRegisterAllocator2.cpp @@ -0,0 +1,414 @@ +#include "PPCRecompiler.h" +#include "PPCRecompilerIml.h" +#include "PPCRecompilerX64.h" +#include "PPCRecompilerImlRanges.h" +#include <queue> + +bool _isRangeDefined(PPCRecImlSegment_t* imlSegment, sint32 vGPR) +{ + return (imlSegment->raDistances.reg[vGPR].usageStart != INT_MAX); +} + +void PPCRecRA_calculateSegmentMinMaxRanges(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegment) +{ + for (sint32 i = 0; i < PPC_REC_MAX_VIRTUAL_GPR; i++) + { + imlSegment->raDistances.reg[i].usageStart = INT_MAX; + imlSegment->raDistances.reg[i].usageEnd = INT_MIN; + } + // scan instructions for usage range + sint32 index = 0; + PPCImlOptimizerUsedRegisters_t gprTracking; + while (index < imlSegment->imlListCount) + { + // end loop at suffix instruction + if (PPCRecompiler_isSuffixInstruction(imlSegment->imlList + index)) + break; + // get accessed GPRs + PPCRecompiler_checkRegisterUsage(NULL, imlSegment->imlList + index, &gprTracking); + for (sint32 t = 0; t < 4; t++) + { + sint32 virtualRegister = gprTracking.gpr[t]; + if (virtualRegister < 0) + continue; + cemu_assert_debug(virtualRegister < PPC_REC_MAX_VIRTUAL_GPR); + imlSegment->raDistances.reg[virtualRegister].usageStart = std::min(imlSegment->raDistances.reg[virtualRegister].usageStart, index); // index before/at instruction + imlSegment->raDistances.reg[virtualRegister].usageEnd = std::max(imlSegment->raDistances.reg[virtualRegister].usageEnd, index+1); // index after instruction + } + // next instruction + index++; + } +} + +void PPCRecRA_calculateLivenessRangesV2(ppcImlGenContext_t* ppcImlGenContext) +{ + // for each register calculate min/max index of usage range within each segment + for (sint32 s = 0; s < ppcImlGenContext->segmentListCount; s++) + { + PPCRecRA_calculateSegmentMinMaxRanges(ppcImlGenContext, ppcImlGenContext->segmentList[s]); + } +} + +raLivenessSubrange_t* PPCRecRA_convertToMappedRanges(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegment, sint32 vGPR, raLivenessRange_t* range) +{ + if (imlSegment->raDistances.isProcessed[vGPR]) + { + // return already existing segment + return imlSegment->raInfo.linkedList_perVirtualGPR[vGPR]; + } + imlSegment->raDistances.isProcessed[vGPR] = true; + if (_isRangeDefined(imlSegment, vGPR) == false) + return nullptr; + // create subrange + cemu_assert_debug(imlSegment->raInfo.linkedList_perVirtualGPR[vGPR] == nullptr); + raLivenessSubrange_t* subrange = PPCRecRA_createSubrange(ppcImlGenContext, range, imlSegment, imlSegment->raDistances.reg[vGPR].usageStart, imlSegment->raDistances.reg[vGPR].usageEnd); + // traverse forward + if (imlSegment->raDistances.reg[vGPR].usageEnd == RA_INTER_RANGE_END) + { + if (imlSegment->nextSegmentBranchTaken && imlSegment->nextSegmentBranchTaken->raDistances.reg[vGPR].usageStart == RA_INTER_RANGE_START) + { + subrange->subrangeBranchTaken = PPCRecRA_convertToMappedRanges(ppcImlGenContext, imlSegment->nextSegmentBranchTaken, vGPR, range); + cemu_assert_debug(subrange->subrangeBranchTaken->start.index == RA_INTER_RANGE_START); + } + if (imlSegment->nextSegmentBranchNotTaken && imlSegment->nextSegmentBranchNotTaken->raDistances.reg[vGPR].usageStart == RA_INTER_RANGE_START) + { + subrange->subrangeBranchNotTaken = PPCRecRA_convertToMappedRanges(ppcImlGenContext, imlSegment->nextSegmentBranchNotTaken, vGPR, range); + cemu_assert_debug(subrange->subrangeBranchNotTaken->start.index == RA_INTER_RANGE_START); + } + } + // traverse backward + if (imlSegment->raDistances.reg[vGPR].usageStart == RA_INTER_RANGE_START) + { + for (auto& it : imlSegment->list_prevSegments) + { + if (it->raDistances.reg[vGPR].usageEnd == RA_INTER_RANGE_END) + PPCRecRA_convertToMappedRanges(ppcImlGenContext, it, vGPR, range); + } + } + // return subrange + return subrange; +} + +void PPCRecRA_createSegmentLivenessRanges(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegment) +{ + for (sint32 i = 0; i < PPC_REC_MAX_VIRTUAL_GPR; i++) + { + if( _isRangeDefined(imlSegment, i) == false ) + continue; + if( imlSegment->raDistances.isProcessed[i]) + continue; + raLivenessRange_t* range = PPCRecRA_createRangeBase(ppcImlGenContext, i, ppcImlGenContext->mappedRegister[i]); + PPCRecRA_convertToMappedRanges(ppcImlGenContext, imlSegment, i, range); + } + // create lookup table of ranges + raLivenessSubrange_t* vGPR2Subrange[PPC_REC_MAX_VIRTUAL_GPR]; + for (sint32 i = 0; i < PPC_REC_MAX_VIRTUAL_GPR; i++) + { + vGPR2Subrange[i] = imlSegment->raInfo.linkedList_perVirtualGPR[i]; +#ifndef PUBLIC_RELEASE + if (vGPR2Subrange[i] && vGPR2Subrange[i]->link_sameVirtualRegisterGPR.next != nullptr) + assert_dbg(); +#endif + } + // parse instructions and convert to locations + sint32 index = 0; + PPCImlOptimizerUsedRegisters_t gprTracking; + while (index < imlSegment->imlListCount) + { + // end loop at suffix instruction + if (PPCRecompiler_isSuffixInstruction(imlSegment->imlList + index)) + break; + // get accessed GPRs + PPCRecompiler_checkRegisterUsage(NULL, imlSegment->imlList + index, &gprTracking); + // handle accessed GPR + for (sint32 t = 0; t < 4; t++) + { + sint32 virtualRegister = gprTracking.gpr[t]; + if (virtualRegister < 0) + continue; + bool isWrite = (t == 3); + // add location + PPCRecRA_updateOrAddSubrangeLocation(vGPR2Subrange[virtualRegister], index, isWrite == false, isWrite); +#ifndef PUBLIC_RELEASE + if (index < vGPR2Subrange[virtualRegister]->start.index) + assert_dbg(); + if (index+1 > vGPR2Subrange[virtualRegister]->end.index) + assert_dbg(); +#endif + } + // next instruction + index++; + } +} + +void PPCRecRA_extendRangeToEndOfSegment(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegment, sint32 vGPR) +{ + if (_isRangeDefined(imlSegment, vGPR) == false) + { + imlSegment->raDistances.reg[vGPR].usageStart = RA_INTER_RANGE_END; + imlSegment->raDistances.reg[vGPR].usageEnd = RA_INTER_RANGE_END; + return; + } + imlSegment->raDistances.reg[vGPR].usageEnd = RA_INTER_RANGE_END; +} + +void PPCRecRA_extendRangeToBeginningOfSegment(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegment, sint32 vGPR) +{ + if (_isRangeDefined(imlSegment, vGPR) == false) + { + imlSegment->raDistances.reg[vGPR].usageStart = RA_INTER_RANGE_START; + imlSegment->raDistances.reg[vGPR].usageEnd = RA_INTER_RANGE_START; + } + else + { + imlSegment->raDistances.reg[vGPR].usageStart = RA_INTER_RANGE_START; + } + // propagate backwards + for (auto& it : imlSegment->list_prevSegments) + { + PPCRecRA_extendRangeToEndOfSegment(ppcImlGenContext, it, vGPR); + } +} + +void _PPCRecRA_connectRanges(ppcImlGenContext_t* ppcImlGenContext, sint32 vGPR, PPCRecImlSegment_t** route, sint32 routeDepth) +{ +#ifndef PUBLIC_RELEASE + if (routeDepth < 2) + assert_dbg(); +#endif + // extend starting range to end of segment + PPCRecRA_extendRangeToEndOfSegment(ppcImlGenContext, route[0], vGPR); + // extend all the connecting segments in both directions + for (sint32 i = 1; i < (routeDepth - 1); i++) + { + PPCRecRA_extendRangeToEndOfSegment(ppcImlGenContext, route[i], vGPR); + PPCRecRA_extendRangeToBeginningOfSegment(ppcImlGenContext, route[i], vGPR); + } + // extend the final segment towards the beginning + PPCRecRA_extendRangeToBeginningOfSegment(ppcImlGenContext, route[routeDepth-1], vGPR); +} + +void _PPCRecRA_checkAndTryExtendRange(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* currentSegment, sint32 vGPR, sint32 distanceLeft, PPCRecImlSegment_t** route, sint32 routeDepth) +{ + if (routeDepth >= 64) + { + forceLogDebug_printf("Recompiler RA route maximum depth exceeded for function 0x%08x\n", ppcImlGenContext->functionRef->ppcAddress); + return; + } + route[routeDepth] = currentSegment; + if (currentSegment->raDistances.reg[vGPR].usageStart == INT_MAX) + { + // measure distance to end of segment + distanceLeft -= currentSegment->imlListCount; + if (distanceLeft > 0) + { + if (currentSegment->nextSegmentBranchNotTaken) + _PPCRecRA_checkAndTryExtendRange(ppcImlGenContext, currentSegment->nextSegmentBranchNotTaken, vGPR, distanceLeft, route, routeDepth + 1); + if (currentSegment->nextSegmentBranchTaken) + _PPCRecRA_checkAndTryExtendRange(ppcImlGenContext, currentSegment->nextSegmentBranchTaken, vGPR, distanceLeft, route, routeDepth + 1); + } + return; + } + else + { + // measure distance to range + if (currentSegment->raDistances.reg[vGPR].usageStart == RA_INTER_RANGE_END) + { + if (distanceLeft < currentSegment->imlListCount) + return; // range too far away + } + else if (currentSegment->raDistances.reg[vGPR].usageStart != RA_INTER_RANGE_START && currentSegment->raDistances.reg[vGPR].usageStart > distanceLeft) + return; // out of range + // found close range -> connect ranges + _PPCRecRA_connectRanges(ppcImlGenContext, vGPR, route, routeDepth + 1); + } +} + +void PPCRecRA_checkAndTryExtendRange(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* currentSegment, sint32 vGPR) +{ +#ifndef PUBLIC_RELEASE + if (currentSegment->raDistances.reg[vGPR].usageEnd < 0) + assert_dbg(); +#endif + // count instructions to end of initial segment + if (currentSegment->raDistances.reg[vGPR].usageEnd == RA_INTER_RANGE_START) + assert_dbg(); + sint32 instructionsUntilEndOfSeg; + if (currentSegment->raDistances.reg[vGPR].usageEnd == RA_INTER_RANGE_END) + instructionsUntilEndOfSeg = 0; + else + instructionsUntilEndOfSeg = currentSegment->imlListCount - currentSegment->raDistances.reg[vGPR].usageEnd; + +#ifndef PUBLIC_RELEASE + if (instructionsUntilEndOfSeg < 0) + assert_dbg(); +#endif + sint32 remainingScanDist = 45 - instructionsUntilEndOfSeg; + if (remainingScanDist <= 0) + return; // can't reach end + + // also dont forget: Extending is easier if we allow 'non symetric' branches. E.g. register range one enters one branch + PPCRecImlSegment_t* route[64]; + route[0] = currentSegment; + if (currentSegment->nextSegmentBranchNotTaken) + { + _PPCRecRA_checkAndTryExtendRange(ppcImlGenContext, currentSegment->nextSegmentBranchNotTaken, vGPR, remainingScanDist, route, 1); + } + if (currentSegment->nextSegmentBranchTaken) + { + _PPCRecRA_checkAndTryExtendRange(ppcImlGenContext, currentSegment->nextSegmentBranchTaken, vGPR, remainingScanDist, route, 1); + } +} + +void PPCRecRA_mergeCloseRangesForSegmentV2(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegment) +{ + for (sint32 i = 0; i < PPC_REC_MAX_VIRTUAL_GPR; i++) // todo: Use dynamic maximum or list of used vGPRs so we can avoid parsing empty entries + { + if(imlSegment->raDistances.reg[i].usageStart == INT_MAX) + continue; // not used + // check and extend if possible + PPCRecRA_checkAndTryExtendRange(ppcImlGenContext, imlSegment, i); + } +#ifndef PUBLIC_RELEASE + if (imlSegment->list_prevSegments.empty() == false && imlSegment->isEnterable) + assert_dbg(); + if ((imlSegment->nextSegmentBranchNotTaken != nullptr || imlSegment->nextSegmentBranchTaken != nullptr) && imlSegment->nextSegmentIsUncertain) + assert_dbg(); +#endif +} + +void PPCRecRA_followFlowAndExtendRanges(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegment) +{ + std::vector<PPCRecImlSegment_t*> list_segments; + list_segments.reserve(1000); + sint32 index = 0; + imlSegment->raRangeExtendProcessed = true; + list_segments.push_back(imlSegment); + while (index < list_segments.size()) + { + PPCRecImlSegment_t* currentSegment = list_segments[index]; + PPCRecRA_mergeCloseRangesForSegmentV2(ppcImlGenContext, currentSegment); + // follow flow + if (currentSegment->nextSegmentBranchNotTaken && currentSegment->nextSegmentBranchNotTaken->raRangeExtendProcessed == false) + { + currentSegment->nextSegmentBranchNotTaken->raRangeExtendProcessed = true; + list_segments.push_back(currentSegment->nextSegmentBranchNotTaken); + } + if (currentSegment->nextSegmentBranchTaken && currentSegment->nextSegmentBranchTaken->raRangeExtendProcessed == false) + { + currentSegment->nextSegmentBranchTaken->raRangeExtendProcessed = true; + list_segments.push_back(currentSegment->nextSegmentBranchTaken); + } + index++; + } +} + +void PPCRecRA_mergeCloseRangesV2(ppcImlGenContext_t* ppcImlGenContext) +{ + for (sint32 s = 0; s < ppcImlGenContext->segmentListCount; s++) + { + PPCRecImlSegment_t* imlSegment = ppcImlGenContext->segmentList[s]; + if (imlSegment->list_prevSegments.empty()) + { + if (imlSegment->raRangeExtendProcessed) + assert_dbg(); // should not happen + PPCRecRA_followFlowAndExtendRanges(ppcImlGenContext, imlSegment); + } + } +} + +void PPCRecRA_extendRangesOutOfLoopsV2(ppcImlGenContext_t* ppcImlGenContext) +{ + for (sint32 s = 0; s < ppcImlGenContext->segmentListCount; s++) + { + PPCRecImlSegment_t* imlSegment = ppcImlGenContext->segmentList[s]; + auto localLoopDepth = imlSegment->loopDepth; + if( localLoopDepth <= 0 ) + continue; // not inside a loop + // look for loop exit + bool hasLoopExit = false; + if (imlSegment->nextSegmentBranchTaken && imlSegment->nextSegmentBranchTaken->loopDepth < localLoopDepth) + { + hasLoopExit = true; + } + if (imlSegment->nextSegmentBranchNotTaken && imlSegment->nextSegmentBranchNotTaken->loopDepth < localLoopDepth) + { + hasLoopExit = true; + } + if(hasLoopExit == false) + continue; + + // extend looping ranges into all exits (this allows the data flow analyzer to move stores out of the loop) + for (sint32 i = 0; i < PPC_REC_MAX_VIRTUAL_GPR; i++) // todo: Use dynamic maximum or list of used vGPRs so we can avoid parsing empty entries + { + if (imlSegment->raDistances.reg[i].usageEnd != RA_INTER_RANGE_END) + continue; // range not set or does not reach end of segment + if(imlSegment->nextSegmentBranchTaken) + PPCRecRA_extendRangeToBeginningOfSegment(ppcImlGenContext, imlSegment->nextSegmentBranchTaken, i); + if(imlSegment->nextSegmentBranchNotTaken) + PPCRecRA_extendRangeToBeginningOfSegment(ppcImlGenContext, imlSegment->nextSegmentBranchNotTaken, i); + } + } +} + +void PPCRecRA_processFlowAndCalculateLivenessRangesV2(ppcImlGenContext_t* ppcImlGenContext) +{ + // merge close ranges + PPCRecRA_mergeCloseRangesV2(ppcImlGenContext); + // extra pass to move register stores out of loops + PPCRecRA_extendRangesOutOfLoopsV2(ppcImlGenContext); + // calculate liveness ranges + for (sint32 s = 0; s < ppcImlGenContext->segmentListCount; s++) + { + PPCRecImlSegment_t* imlSegment = ppcImlGenContext->segmentList[s]; + PPCRecRA_createSegmentLivenessRanges(ppcImlGenContext, imlSegment); + } +} + +void PPCRecRA_analyzeSubrangeDataDependencyV2(raLivenessSubrange_t* subrange) +{ + bool isRead = false; + bool isWritten = false; + bool isOverwritten = false; + for (auto& location : subrange->list_locations) + { + if (location.isRead) + { + isRead = true; + } + if (location.isWrite) + { + if (isRead == false) + isOverwritten = true; + isWritten = true; + } + } + subrange->_noLoad = isOverwritten; + subrange->hasStore = isWritten; + + if (subrange->start.index == RA_INTER_RANGE_START) + subrange->_noLoad = true; +} + +void _analyzeRangeDataFlow(raLivenessSubrange_t* subrange); + +void PPCRecRA_analyzeRangeDataFlowV2(ppcImlGenContext_t* ppcImlGenContext) +{ + // this function is called after _assignRegisters(), which means that all ranges are already final and wont change anymore + // first do a per-subrange pass + for (auto& range : ppcImlGenContext->raInfo.list_ranges) + { + for (auto& subrange : range->list_subranges) + { + PPCRecRA_analyzeSubrangeDataDependencyV2(subrange); + } + } + // then do a second pass where we scan along subrange flow + for (auto& range : ppcImlGenContext->raInfo.list_ranges) + { + for (auto& subrange : range->list_subranges) // todo - traversing this backwards should be faster and yield better results due to the nature of the algorithm + { + _analyzeRangeDataFlow(subrange); + } + } +} \ No newline at end of file diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerIntermediate.cpp b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerIntermediate.cpp new file mode 100644 index 00000000..fcbe64be --- /dev/null +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerIntermediate.cpp @@ -0,0 +1,173 @@ +#include "PPCRecompiler.h" +#include "PPCRecompilerIml.h" + +PPCRecImlSegment_t* PPCRecompiler_getSegmentByPPCJumpAddress(ppcImlGenContext_t* ppcImlGenContext, uint32 ppcOffset) +{ + for(sint32 s=0; s<ppcImlGenContext->segmentListCount; s++) + { + if( ppcImlGenContext->segmentList[s]->isJumpDestination && ppcImlGenContext->segmentList[s]->jumpDestinationPPCAddress == ppcOffset ) + { + return ppcImlGenContext->segmentList[s]; + } + } + debug_printf("PPCRecompiler_getSegmentByPPCJumpAddress(): Unable to find segment (ppcOffset 0x%08x)\n", ppcOffset); + return NULL; +} + +void PPCRecompilerIml_setLinkBranchNotTaken(PPCRecImlSegment_t* imlSegmentSrc, PPCRecImlSegment_t* imlSegmentDst) +{ + // make sure segments aren't already linked + if (imlSegmentSrc->nextSegmentBranchNotTaken == imlSegmentDst) + return; + // add as next segment for source + if (imlSegmentSrc->nextSegmentBranchNotTaken != NULL) + assert_dbg(); + imlSegmentSrc->nextSegmentBranchNotTaken = imlSegmentDst; + // add as previous segment for destination + imlSegmentDst->list_prevSegments.push_back(imlSegmentSrc); +} + +void PPCRecompilerIml_setLinkBranchTaken(PPCRecImlSegment_t* imlSegmentSrc, PPCRecImlSegment_t* imlSegmentDst) +{ + // make sure segments aren't already linked + if (imlSegmentSrc->nextSegmentBranchTaken == imlSegmentDst) + return; + // add as next segment for source + if (imlSegmentSrc->nextSegmentBranchTaken != NULL) + assert_dbg(); + imlSegmentSrc->nextSegmentBranchTaken = imlSegmentDst; + // add as previous segment for destination + imlSegmentDst->list_prevSegments.push_back(imlSegmentSrc); +} + +void PPCRecompilerIML_removeLink(PPCRecImlSegment_t* imlSegmentSrc, PPCRecImlSegment_t* imlSegmentDst) +{ + if (imlSegmentSrc->nextSegmentBranchNotTaken == imlSegmentDst) + { + imlSegmentSrc->nextSegmentBranchNotTaken = NULL; + } + else if (imlSegmentSrc->nextSegmentBranchTaken == imlSegmentDst) + { + imlSegmentSrc->nextSegmentBranchTaken = NULL; + } + else + assert_dbg(); + + bool matchFound = false; + for (sint32 i = 0; i < imlSegmentDst->list_prevSegments.size(); i++) + { + if (imlSegmentDst->list_prevSegments[i] == imlSegmentSrc) + { + imlSegmentDst->list_prevSegments.erase(imlSegmentDst->list_prevSegments.begin()+i); + matchFound = true; + break; + } + } + if (matchFound == false) + assert_dbg(); +} + +/* + * Replaces all links to segment orig with linkts to segment new + */ +void PPCRecompilerIML_relinkInputSegment(PPCRecImlSegment_t* imlSegmentOrig, PPCRecImlSegment_t* imlSegmentNew) +{ + while (imlSegmentOrig->list_prevSegments.size() != 0) + { + PPCRecImlSegment_t* prevSegment = imlSegmentOrig->list_prevSegments[0]; + if (prevSegment->nextSegmentBranchNotTaken == imlSegmentOrig) + { + PPCRecompilerIML_removeLink(prevSegment, imlSegmentOrig); + PPCRecompilerIml_setLinkBranchNotTaken(prevSegment, imlSegmentNew); + } + else if (prevSegment->nextSegmentBranchTaken == imlSegmentOrig) + { + PPCRecompilerIML_removeLink(prevSegment, imlSegmentOrig); + PPCRecompilerIml_setLinkBranchTaken(prevSegment, imlSegmentNew); + } + else + { + assert_dbg(); + } + } +} + +void PPCRecompilerIML_linkSegments(ppcImlGenContext_t* ppcImlGenContext) +{ + for(sint32 s=0; s<ppcImlGenContext->segmentListCount; s++) + { + PPCRecImlSegment_t* imlSegment = ppcImlGenContext->segmentList[s]; + + bool isLastSegment = (s+1)>=ppcImlGenContext->segmentListCount; + PPCRecImlSegment_t* nextSegment = isLastSegment?NULL:ppcImlGenContext->segmentList[s+1]; + // handle empty segment + if( imlSegment->imlListCount == 0 ) + { + if (isLastSegment == false) + PPCRecompilerIml_setLinkBranchNotTaken(imlSegment, ppcImlGenContext->segmentList[s+1]); // continue execution to next segment + else + imlSegment->nextSegmentIsUncertain = true; + continue; + } + // check last instruction of segment + PPCRecImlInstruction_t* imlInstruction = imlSegment->imlList+(imlSegment->imlListCount-1); + if( imlInstruction->type == PPCREC_IML_TYPE_CJUMP || imlInstruction->type == PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK ) + { + // find destination segment by ppc jump address + PPCRecImlSegment_t* jumpDestSegment = PPCRecompiler_getSegmentByPPCJumpAddress(ppcImlGenContext, imlInstruction->op_conditionalJump.jumpmarkAddress); + if( jumpDestSegment ) + { + if (imlInstruction->op_conditionalJump.condition != PPCREC_JUMP_CONDITION_NONE) + PPCRecompilerIml_setLinkBranchNotTaken(imlSegment, nextSegment); + PPCRecompilerIml_setLinkBranchTaken(imlSegment, jumpDestSegment); + } + else + { + imlSegment->nextSegmentIsUncertain = true; + } + } + else if( imlInstruction->type == PPCREC_IML_TYPE_MACRO ) + { + // currently we assume that the next segment is unknown for all macros + imlSegment->nextSegmentIsUncertain = true; + } + else + { + // all other instruction types do not branch + //imlSegment->nextSegment[0] = nextSegment; + PPCRecompilerIml_setLinkBranchNotTaken(imlSegment, nextSegment); + //imlSegment->nextSegmentIsUncertain = true; + } + } +} + +void PPCRecompilerIML_isolateEnterableSegments(ppcImlGenContext_t* ppcImlGenContext) +{ + sint32 initialSegmentCount = ppcImlGenContext->segmentListCount; + for (sint32 i = 0; i < ppcImlGenContext->segmentListCount; i++) + { + PPCRecImlSegment_t* imlSegment = ppcImlGenContext->segmentList[i]; + if (imlSegment->list_prevSegments.empty() == false && imlSegment->isEnterable) + { + // spawn new segment at end + PPCRecompilerIml_insertSegments(ppcImlGenContext, ppcImlGenContext->segmentListCount, 1); + PPCRecImlSegment_t* entrySegment = ppcImlGenContext->segmentList[ppcImlGenContext->segmentListCount-1]; + entrySegment->isEnterable = true; + entrySegment->enterPPCAddress = imlSegment->enterPPCAddress; + // create jump instruction + PPCRecompiler_pushBackIMLInstructions(entrySegment, 0, 1); + PPCRecompilerImlGen_generateNewInstruction_jumpSegment(ppcImlGenContext, entrySegment->imlList + 0); + PPCRecompilerIml_setLinkBranchTaken(entrySegment, imlSegment); + // remove enterable flag from original segment + imlSegment->isEnterable = false; + imlSegment->enterPPCAddress = 0; + } + } +} + +PPCRecImlInstruction_t* PPCRecompilerIML_getLastInstruction(PPCRecImlSegment_t* imlSegment) +{ + if (imlSegment->imlListCount == 0) + return nullptr; + return imlSegment->imlList + (imlSegment->imlListCount - 1); +} diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64.cpp b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64.cpp new file mode 100644 index 00000000..cda0e60a --- /dev/null +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64.cpp @@ -0,0 +1,2682 @@ +#include "Cafe/HW/Espresso/PPCState.h" +#include "Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h" +#include "Cafe/HW/Espresso/Interpreter/PPCInterpreterHelper.h" +#include "PPCRecompiler.h" +#include "PPCRecompilerIml.h" +#include "PPCRecompilerX64.h" +#include "Cafe/OS/libs/coreinit/coreinit_Time.h" + +#include "util/MemMapper/MemMapper.h" + +sint32 x64Gen_registerMap[12] = // virtual GPR to x64 register mapping +{ + REG_RAX, REG_RDX, REG_RBX, REG_RBP, REG_RSI, REG_RDI, REG_R8, REG_R9, REG_R10, REG_R11, REG_R12, REG_RCX +}; + +/* +* Remember current instruction output offset for reloc +* The instruction generated after this method has been called will be adjusted +*/ +void PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext_t* x64GenContext, uint8 type, void* extraInfo = nullptr) +{ + if( x64GenContext->relocateOffsetTableCount >= x64GenContext->relocateOffsetTableSize ) + { + x64GenContext->relocateOffsetTableSize = std::max(4, x64GenContext->relocateOffsetTableSize*2); + x64GenContext->relocateOffsetTable = (x64RelocEntry_t*)realloc(x64GenContext->relocateOffsetTable, sizeof(x64RelocEntry_t)*x64GenContext->relocateOffsetTableSize); + } + x64GenContext->relocateOffsetTable[x64GenContext->relocateOffsetTableCount].offset = x64GenContext->codeBufferIndex; + x64GenContext->relocateOffsetTable[x64GenContext->relocateOffsetTableCount].type = type; + x64GenContext->relocateOffsetTable[x64GenContext->relocateOffsetTableCount].extraInfo = extraInfo; + x64GenContext->relocateOffsetTableCount++; +} + +/* +* Overwrites the currently cached (in x64 cf) cr* register +* Should be called before each x64 instruction which overwrites the current status flags (with mappedCRRegister set to PPCREC_CR_TEMPORARY unless explicitly set by PPC instruction) +*/ +void PPCRecompilerX64Gen_crConditionFlags_set(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, sint32 mappedCRRegister, sint32 crState) +{ + x64GenContext->activeCRRegister = mappedCRRegister; + x64GenContext->activeCRState = crState; +} + +/* +* Reset cached cr* register without storing it first +*/ +void PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext) +{ + x64GenContext->activeCRRegister = PPC_REC_INVALID_REGISTER; +} + +void PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext_t* x64GenContext, sint32 jumpInstructionOffset, sint32 destinationOffset) +{ + uint8* instructionData = x64GenContext->codeBuffer + jumpInstructionOffset; + if (instructionData[0] == 0x0F && (instructionData[1] >= 0x80 && instructionData[1] <= 0x8F)) + { + // far conditional jump + *(uint32*)(instructionData + 2) = (destinationOffset - (jumpInstructionOffset + 6)); + } + else if (instructionData[0] >= 0x70 && instructionData[0] <= 0x7F) + { + // short conditional jump + sint32 distance = (sint32)((destinationOffset - (jumpInstructionOffset + 2))); + cemu_assert_debug(distance >= -128 && distance <= 127); + *(uint8*)(instructionData + 1) = (uint8)distance; + } + else if (instructionData[0] == 0xE9) + { + *(uint32*)(instructionData + 1) = (destinationOffset - (jumpInstructionOffset + 5)); + } + else if (instructionData[0] == 0xEB) + { + sint32 distance = (sint32)((destinationOffset - (jumpInstructionOffset + 2))); + cemu_assert_debug(distance >= -128 && distance <= 127); + *(uint8*)(instructionData + 1) = (uint8)distance; + } + else + { + assert_dbg(); + } +} + +void PPCRecompilerX64Gen_updateCRLogical(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction) +{ + sint32 crRegister = imlInstruction->crRegister; + if( (imlInstruction->crIgnoreMask&(1<<(crRegister*4+PPCREC_CR_BIT_LT))) == 0 ) + x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_SIGN, REG_RSP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_LT)); // check for sign instead of _BELOW (CF) which is not set by TEST + if( (imlInstruction->crIgnoreMask&(1<<(crRegister*4+PPCREC_CR_BIT_GT))) == 0 ) + x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_SIGNED_GREATER, REG_RSP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_GT)); + if( (imlInstruction->crIgnoreMask&(1<<(crRegister*4+PPCREC_CR_BIT_EQ))) == 0 ) + x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_EQUAL, REG_RSP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_EQ)); + // todo: Set CR SO if XER SO bit is set + PPCRecompilerX64Gen_crConditionFlags_set(PPCRecFunction, ppcImlGenContext, x64GenContext, crRegister, PPCREC_CR_STATE_TYPE_LOGICAL); +} + +void* ATTR_MS_ABI PPCRecompiler_virtualHLE(PPCInterpreter_t* hCPU, uint32 hleFuncId) +{ + void* prevRSPTemp = hCPU->rspTemp; + if( hleFuncId == 0xFFD0 ) + { + hCPU->remainingCycles -= 500; // let subtract about 500 cycles for each HLE call + hCPU->gpr[3] = 0; + PPCInterpreter_nextInstruction(hCPU); + return ppcInterpreterCurrentInstance; + } + else + { + auto hleCall = PPCInterpreter_getHLECall(hleFuncId); + cemu_assert(hleCall != nullptr); + hleCall(hCPU); + } + hCPU->rspTemp = prevRSPTemp; + return ppcInterpreterCurrentInstance; +} + +void ATTR_MS_ABI PPCRecompiler_getTBL(PPCInterpreter_t* hCPU, uint32 gprIndex) +{ + uint64 coreTime = coreinit::coreinit_getTimerTick(); + hCPU->gpr[gprIndex] = (uint32)(coreTime&0xFFFFFFFF); +} + +void ATTR_MS_ABI PPCRecompiler_getTBU(PPCInterpreter_t* hCPU, uint32 gprIndex) +{ + uint64 coreTime = coreinit::coreinit_getTimerTick(); + hCPU->gpr[gprIndex] = (uint32)((coreTime>>32)&0xFFFFFFFF); +} + +bool PPCRecompilerX64Gen_imlInstruction_macro(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction) +{ + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + if( imlInstruction->operation == PPCREC_IML_MACRO_BLR || imlInstruction->operation == PPCREC_IML_MACRO_BLRL ) + { + uint32 currentInstructionAddress = imlInstruction->op_macro.param; + // MOV EDX, [SPR_LR] + x64Emit_mov_reg64_mem32(x64GenContext, REG_RDX, REG_RSP, offsetof(PPCInterpreter_t, spr.LR)); + // if BLRL, then update SPR LR + if (imlInstruction->operation == PPCREC_IML_MACRO_BLRL) + x64Gen_mov_mem32Reg64_imm32(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, spr.LR), currentInstructionAddress + 4); + // JMP [offset+RDX*(8/4)+R15] + x64Gen_writeU8(x64GenContext, 0x41); + x64Gen_writeU8(x64GenContext, 0xFF); + x64Gen_writeU8(x64GenContext, 0xA4); + x64Gen_writeU8(x64GenContext, 0x57); + x64Gen_writeU32(x64GenContext, (uint32)offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable)); + return true; + } + else if( imlInstruction->operation == PPCREC_IML_MACRO_BCTR || imlInstruction->operation == PPCREC_IML_MACRO_BCTRL ) + { + uint32 currentInstructionAddress = imlInstruction->op_macro.param; + // MOV EDX, [SPR_CTR] + x64Emit_mov_reg64_mem32(x64GenContext, REG_RDX, REG_RSP, offsetof(PPCInterpreter_t, spr.CTR)); + // if BCTRL, then update SPR LR + if (imlInstruction->operation == PPCREC_IML_MACRO_BCTRL) + x64Gen_mov_mem32Reg64_imm32(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, spr.LR), currentInstructionAddress + 4); + // JMP [offset+RDX*(8/4)+R15] + x64Gen_writeU8(x64GenContext, 0x41); + x64Gen_writeU8(x64GenContext, 0xFF); + x64Gen_writeU8(x64GenContext, 0xA4); + x64Gen_writeU8(x64GenContext, 0x57); + x64Gen_writeU32(x64GenContext, (uint32)offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable)); + return true; + } + else if( imlInstruction->operation == PPCREC_IML_MACRO_BL ) + { + // MOV DWORD [SPR_LinkRegister], newLR + uint32 newLR = imlInstruction->op_macro.param + 4; + x64Gen_mov_mem32Reg64_imm32(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, spr.LR), newLR); + // remember new instruction pointer in RDX + uint32 newIP = imlInstruction->op_macro.param2; + x64Gen_mov_reg64Low32_imm32(x64GenContext, REG_RDX, newIP); + // since RDX is constant we can use JMP [R15+const_offset] if jumpTableOffset+RDX*2 does not exceed the 2GB boundary + uint64 lookupOffset = (uint64)offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable) + (uint64)newIP * 2ULL; + if (lookupOffset >= 0x80000000ULL) + { + // JMP [offset+RDX*(8/4)+R15] + x64Gen_writeU8(x64GenContext, 0x41); + x64Gen_writeU8(x64GenContext, 0xFF); + x64Gen_writeU8(x64GenContext, 0xA4); + x64Gen_writeU8(x64GenContext, 0x57); + x64Gen_writeU32(x64GenContext, (uint32)offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable)); + } + else + { + x64Gen_writeU8(x64GenContext, 0x41); + x64Gen_writeU8(x64GenContext, 0xFF); + x64Gen_writeU8(x64GenContext, 0xA7); + x64Gen_writeU32(x64GenContext, (uint32)lookupOffset); + } + return true; + } + else if( imlInstruction->operation == PPCREC_IML_MACRO_B_FAR ) + { + // remember new instruction pointer in RDX + uint32 newIP = imlInstruction->op_macro.param2; + x64Gen_mov_reg64Low32_imm32(x64GenContext, REG_RDX, newIP); + // Since RDX is constant we can use JMP [R15+const_offset] if jumpTableOffset+RDX*2 does not exceed the 2GB boundary + uint64 lookupOffset = (uint64)offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable) + (uint64)newIP * 2ULL; + if (lookupOffset >= 0x80000000ULL) + { + // JMP [offset+RDX*(8/4)+R15] + x64Gen_writeU8(x64GenContext, 0x41); + x64Gen_writeU8(x64GenContext, 0xFF); + x64Gen_writeU8(x64GenContext, 0xA4); + x64Gen_writeU8(x64GenContext, 0x57); + x64Gen_writeU32(x64GenContext, (uint32)offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable)); + } + else + { + x64Gen_writeU8(x64GenContext, 0x41); + x64Gen_writeU8(x64GenContext, 0xFF); + x64Gen_writeU8(x64GenContext, 0xA7); + x64Gen_writeU32(x64GenContext, (uint32)lookupOffset); + } + return true; + } + else if( imlInstruction->operation == PPCREC_IML_MACRO_LEAVE ) + { + uint32 currentInstructionAddress = imlInstruction->op_macro.param; + // remember PC value in REG_EDX + x64Gen_mov_reg64Low32_imm32(x64GenContext, REG_RDX, currentInstructionAddress); + + uint32 newIP = 0; // special value for recompiler exit + uint64 lookupOffset = (uint64)&(((PPCRecompilerInstanceData_t*)NULL)->ppcRecompilerDirectJumpTable) + (uint64)newIP * 2ULL; + // JMP [R15+offset] + x64Gen_writeU8(x64GenContext, 0x41); + x64Gen_writeU8(x64GenContext, 0xFF); + x64Gen_writeU8(x64GenContext, 0xA7); + x64Gen_writeU32(x64GenContext, (uint32)lookupOffset); + return true; + } + else if( imlInstruction->operation == PPCREC_IML_MACRO_DEBUGBREAK ) + { + x64Gen_mov_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, imlInstruction->op_macro.param2); + x64Gen_int3(x64GenContext); + return true; + } + else if( imlInstruction->operation == PPCREC_IML_MACRO_COUNT_CYCLES ) + { + uint32 cycleCount = imlInstruction->op_macro.param; + x64Gen_sub_mem32reg64_imm32(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, remainingCycles), cycleCount); + return true; + } + else if( imlInstruction->operation == PPCREC_IML_MACRO_HLE ) + { + uint32 ppcAddress = imlInstruction->op_macro.param; + uint32 funcId = imlInstruction->op_macro.param2; + //x64Gen_int3(x64GenContext); + // update instruction pointer + x64Gen_mov_mem32Reg64_imm32(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, instructionPointer), ppcAddress); + //// save hCPU (RSP) + //x64Gen_mov_reg64_imm64(x64GenContext, REG_RESV_TEMP, (uint64)&ppcRecompilerX64_hCPUTemp); + //x64Emit_mov_mem64_reg64(x64GenContext, REG_RESV_TEMP, 0, REG_RSP); + // set parameters + x64Gen_mov_reg64_reg64(x64GenContext, REG_RCX, REG_RSP); + x64Gen_mov_reg64_imm64(x64GenContext, REG_RDX, funcId); + // restore stackpointer from executionContext/hCPU->rspTemp + x64Emit_mov_reg64_mem64(x64GenContext, REG_RSP, REG_RESV_HCPU, offsetof(PPCInterpreter_t, rspTemp)); + //x64Emit_mov_reg64_mem64(x64GenContext, REG_RSP, REG_R14, 0); + //x64Gen_int3(x64GenContext); + // reserve space on stack for call parameters + x64Gen_sub_reg64_imm32(x64GenContext, REG_RSP, 8*11); // must be uneven number in order to retain stack 0x10 alignment + x64Gen_mov_reg64_imm64(x64GenContext, REG_RBP, 0); + // call HLE function + x64Gen_mov_reg64_imm64(x64GenContext, REG_RAX, (uint64)PPCRecompiler_virtualHLE); + x64Gen_call_reg64(x64GenContext, REG_RAX); + // restore RSP to hCPU (from RAX, result of PPCRecompiler_virtualHLE) + //x64Gen_mov_reg64_imm64(x64GenContext, REG_RESV_TEMP, (uint64)&ppcRecompilerX64_hCPUTemp); + //x64Emit_mov_reg64_mem64Reg64(x64GenContext, REG_RSP, REG_RESV_TEMP, 0); + x64Gen_mov_reg64_reg64(x64GenContext, REG_RSP, REG_RAX); + // MOV R15, ppcRecompilerInstanceData + x64Gen_mov_reg64_imm64(x64GenContext, REG_R15, (uint64)ppcRecompilerInstanceData); + // MOV R13, memory_base + x64Gen_mov_reg64_imm64(x64GenContext, REG_R13, (uint64)memory_base); + // check if cycles where decreased beyond zero, if yes -> leave recompiler + x64Gen_bt_mem8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, remainingCycles), 31); // check if negative + sint32 jumpInstructionOffset1 = x64GenContext->codeBufferIndex; + x64Gen_jmpc_near(x64GenContext, X86_CONDITION_NOT_CARRY, 0); + //x64Gen_int3(x64GenContext); + //x64Gen_mov_reg64Low32_imm32(x64GenContext, REG_RDX, ppcAddress); + + x64Emit_mov_reg64_mem32(x64GenContext, REG_RDX, REG_RSP, offsetof(PPCInterpreter_t, instructionPointer)); + // set EAX to 0 (we assume that ppcRecompilerDirectJumpTable[0] will be a recompiler escape function) + x64Gen_xor_reg32_reg32(x64GenContext, REG_RAX, REG_RAX); + // ADD RAX, R15 (R15 -> Pointer to ppcRecompilerInstanceData + x64Gen_add_reg64_reg64(x64GenContext, REG_RAX, REG_R15); + //// JMP [recompilerCallTable+EAX/4*8] + //x64Gen_int3(x64GenContext); + x64Gen_jmp_memReg64(x64GenContext, REG_RAX, (uint32)offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable)); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset1, x64GenContext->codeBufferIndex); + // check if instruction pointer was changed + // assign new instruction pointer to EAX + x64Emit_mov_reg64_mem32(x64GenContext, REG_RAX, REG_RSP, offsetof(PPCInterpreter_t, instructionPointer)); + // remember instruction pointer in REG_EDX + x64Gen_mov_reg64_reg64(x64GenContext, REG_RDX, REG_RAX); + // EAX *= 2 + x64Gen_add_reg64_reg64(x64GenContext, REG_RAX, REG_RAX); + // ADD RAX, R15 (R15 -> Pointer to ppcRecompilerInstanceData + x64Gen_add_reg64_reg64(x64GenContext, REG_RAX, REG_R15); + // JMP [ppcRecompilerDirectJumpTable+RAX/4*8] + x64Gen_jmp_memReg64(x64GenContext, REG_RAX, (uint32)offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable)); + return true; + } + else if( imlInstruction->operation == PPCREC_IML_MACRO_MFTB ) + { + uint32 ppcAddress = imlInstruction->op_macro.param; + uint32 sprId = imlInstruction->op_macro.param2&0xFFFF; + uint32 gprIndex = (imlInstruction->op_macro.param2>>16)&0x1F; + // update instruction pointer + x64Gen_mov_mem32Reg64_imm32(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, instructionPointer), ppcAddress); + // set parameters + x64Gen_mov_reg64_reg64(x64GenContext, REG_RCX, REG_RSP); + x64Gen_mov_reg64_imm64(x64GenContext, REG_RDX, gprIndex); + // restore stackpointer to original RSP + x64Emit_mov_reg64_mem64(x64GenContext, REG_RSP, REG_RESV_HCPU, offsetof(PPCInterpreter_t, rspTemp)); + // push hCPU on stack + x64Gen_push_reg64(x64GenContext, REG_RCX); + // reserve space on stack for call parameters + x64Gen_sub_reg64_imm32(x64GenContext, REG_RSP, 8*11 + 8); + x64Gen_mov_reg64_imm64(x64GenContext, REG_RBP, 0); + // call HLE function + if( sprId == SPR_TBL ) + x64Gen_mov_reg64_imm64(x64GenContext, REG_RAX, (uint64)PPCRecompiler_getTBL); + else if( sprId == SPR_TBU ) + x64Gen_mov_reg64_imm64(x64GenContext, REG_RAX, (uint64)PPCRecompiler_getTBU); + else + assert_dbg(); + x64Gen_call_reg64(x64GenContext, REG_RAX); + // restore hCPU from stack + x64Gen_add_reg64_imm32(x64GenContext, REG_RSP, 8 * 11 + 8); + x64Gen_pop_reg64(x64GenContext, REG_RSP); + // MOV R15, ppcRecompilerInstanceData + x64Gen_mov_reg64_imm64(x64GenContext, REG_R15, (uint64)ppcRecompilerInstanceData); + // MOV R13, memory_base + x64Gen_mov_reg64_imm64(x64GenContext, REG_R13, (uint64)memory_base); + return true; + } + else + { + debug_printf("Unknown recompiler macro operation %d\n", imlInstruction->operation); + assert_dbg(); + } + return false; +} + +/* +* Load from memory +*/ +bool PPCRecompilerX64Gen_imlInstruction_load(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction, bool indexed) +{ + sint32 realRegisterData = tempToRealRegister(imlInstruction->op_storeLoad.registerData); + sint32 realRegisterMem = tempToRealRegister(imlInstruction->op_storeLoad.registerMem); + sint32 realRegisterMem2 = PPC_REC_INVALID_REGISTER; + if( indexed ) + realRegisterMem2 = tempToRealRegister(imlInstruction->op_storeLoad.registerMem2); + if( false )//imlInstruction->op_storeLoad.flags & PPCREC_IML_OP_FLAG_FASTMEMACCESS ) + { + // load u8/u16/u32 via direct memory access + optional sign extend + assert_dbg(); // todo + } + else + { + if( indexed && realRegisterMem == realRegisterMem2 ) + { + return false; + } + if( indexed && realRegisterData == realRegisterMem2 ) + { + // for indexed memory access realRegisterData must not be the same register as the second memory register, + // this can easily be fixed by swapping the logic of realRegisterMem and realRegisterMem2 + sint32 temp = realRegisterMem; + realRegisterMem = realRegisterMem2; + realRegisterMem2 = temp; + } + + bool signExtend = imlInstruction->op_storeLoad.flags2.signExtend; + bool switchEndian = imlInstruction->op_storeLoad.flags2.swapEndian; + if( imlInstruction->op_storeLoad.copyWidth == 32 ) + { + //if( indexed ) + // PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + if (indexed) + { + x64Gen_lea_reg64Low32_reg64Low32PlusReg64Low32(x64GenContext, REG_RESV_TEMP, realRegisterMem, realRegisterMem2); + } + if( hasMOVBESupport && switchEndian ) + { + if (indexed) + { + x64Gen_movBEZeroExtend_reg64_mem32Reg64PlusReg64(x64GenContext, realRegisterData, REG_R13, REG_RESV_TEMP, imlInstruction->op_storeLoad.immS32); + //if (indexed && realRegisterMem != realRegisterData) + // x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); + } + else + { + x64Gen_movBEZeroExtend_reg64_mem32Reg64PlusReg64(x64GenContext, realRegisterData, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32); + } + } + else + { + if (indexed) + { + x64Emit_mov_reg32_mem32(x64GenContext, realRegisterData, REG_R13, REG_RESV_TEMP, imlInstruction->op_storeLoad.immS32); + //if (realRegisterMem != realRegisterData) + // x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); + if (switchEndian) + x64Gen_bswap_reg64Lower32bit(x64GenContext, realRegisterData); + } + else + { + x64Emit_mov_reg32_mem32(x64GenContext, realRegisterData, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32); + if (switchEndian) + x64Gen_bswap_reg64Lower32bit(x64GenContext, realRegisterData); + } + } + } + else if( imlInstruction->op_storeLoad.copyWidth == 16 ) + { + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); // todo: We can avoid this if MOVBE is available + if (indexed) + { + x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); + } + if( hasMOVBESupport && switchEndian ) + { + x64Gen_movBEZeroExtend_reg64Low16_mem16Reg64PlusReg64(x64GenContext, realRegisterData, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32); + if( indexed && realRegisterMem != realRegisterData ) + x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); + } + else + { + x64Gen_movZeroExtend_reg64Low16_mem16Reg64PlusReg64(x64GenContext, realRegisterData, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32); + if( indexed && realRegisterMem != realRegisterData ) + x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); + if( switchEndian ) + x64Gen_rol_reg64Low16_imm8(x64GenContext, realRegisterData, 8); + } + if( signExtend ) + x64Gen_movSignExtend_reg64Low32_reg64Low16(x64GenContext, realRegisterData, realRegisterData); + else + x64Gen_movZeroExtend_reg64Low32_reg64Low16(x64GenContext, realRegisterData, realRegisterData); + } + else if( imlInstruction->op_storeLoad.copyWidth == 8 ) + { + if( indexed ) + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + // todo: Optimize by using only MOVZX/MOVSX + if( indexed ) + x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); + // todo: Use sign extend move from memory instead of separate sign-extend? + if( signExtend ) + x64Gen_movSignExtend_reg64Low32_mem8Reg64PlusReg64(x64GenContext, realRegisterData, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32); + else + x64Emit_movZX_reg32_mem8(x64GenContext, realRegisterData, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32); + if( indexed && realRegisterMem != realRegisterData ) + x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); + } + else if( imlInstruction->op_storeLoad.copyWidth == PPC_REC_LOAD_LWARX_MARKER ) + { + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + if( imlInstruction->op_storeLoad.immS32 != 0 ) + assert_dbg(); // not supported + if( indexed ) + x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); + x64Emit_mov_mem32_reg32(x64GenContext, REG_RSP, (uint32)offsetof(PPCInterpreter_t, reservedMemAddr), realRegisterMem); // remember EA for reservation + x64Emit_mov_reg32_mem32(x64GenContext, realRegisterData, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32); + if( indexed && realRegisterMem != realRegisterData ) + x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); + if( switchEndian ) + x64Gen_bswap_reg64Lower32bit(x64GenContext, realRegisterData); + x64Emit_mov_mem32_reg32(x64GenContext, REG_RSP, (uint32)offsetof(PPCInterpreter_t, reservedMemValue), realRegisterData); // remember value for reservation + // LWARX instruction costs extra cycles (this speeds up busy loops) + x64Gen_sub_mem32reg64_imm32(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, remainingCycles), 20); + } + else if( imlInstruction->op_storeLoad.copyWidth == PPC_REC_STORE_LSWI_3 ) + { + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + if( switchEndian == false ) + assert_dbg(); + if( indexed ) + x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); // can be replaced with LEA temp, [memReg1+memReg2] (this way we can avoid the SUB instruction after the move) + if( hasMOVBESupport ) + { + x64Gen_movBEZeroExtend_reg64_mem32Reg64PlusReg64(x64GenContext, realRegisterData, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32); + if( indexed && realRegisterMem != realRegisterData ) + x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); + } + else + { + x64Emit_mov_reg32_mem32(x64GenContext, realRegisterData, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32); + if( indexed && realRegisterMem != realRegisterData ) + x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); + x64Gen_bswap_reg64Lower32bit(x64GenContext, realRegisterData); + } + x64Gen_and_reg64Low32_imm32(x64GenContext, realRegisterData, 0xFFFFFF00); + } + else + return false; + return true; + } + return false; +} + +/* +* Write to memory +*/ +bool PPCRecompilerX64Gen_imlInstruction_store(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction, bool indexed) +{ + sint32 realRegisterData = tempToRealRegister(imlInstruction->op_storeLoad.registerData); + sint32 realRegisterMem = tempToRealRegister(imlInstruction->op_storeLoad.registerMem); + sint32 realRegisterMem2 = PPC_REC_INVALID_REGISTER; + if (indexed) + realRegisterMem2 = tempToRealRegister(imlInstruction->op_storeLoad.registerMem2); + + if (false)//imlInstruction->op_storeLoad.flags & PPCREC_IML_OP_FLAG_FASTMEMACCESS ) + { + // load u8/u16/u32 via direct memory access + optional sign extend + assert_dbg(); // todo + } + else + { + if (indexed && realRegisterMem == realRegisterMem2) + { + return false; + } + if (indexed && realRegisterData == realRegisterMem2) + { + // for indexed memory access realRegisterData must not be the same register as the second memory register, + // this can easily be fixed by swapping the logic of realRegisterMem and realRegisterMem2 + sint32 temp = realRegisterMem; + realRegisterMem = realRegisterMem2; + realRegisterMem2 = temp; + } + + bool signExtend = imlInstruction->op_storeLoad.flags2.signExtend; + bool swapEndian = imlInstruction->op_storeLoad.flags2.swapEndian; + if (imlInstruction->op_storeLoad.copyWidth == 32) + { + if (indexed) + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + uint32 valueRegister; + if ((swapEndian == false || hasMOVBESupport) && realRegisterMem != realRegisterData) + { + valueRegister = realRegisterData; + } + else + { + x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, realRegisterData); + valueRegister = REG_RESV_TEMP; + } + if (hasMOVBESupport == false && swapEndian) + x64Gen_bswap_reg64Lower32bit(x64GenContext, valueRegister); + if (indexed) + x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); + if (hasMOVBESupport && swapEndian) + x64Gen_movBETruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32, valueRegister); + else + x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32, valueRegister); + if (indexed) + x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); + } + else if (imlInstruction->op_storeLoad.copyWidth == 16) + { + if (indexed || swapEndian) + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, realRegisterData); + if (swapEndian) + x64Gen_rol_reg64Low16_imm8(x64GenContext, REG_RESV_TEMP, 8); + if (indexed) + x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); + x64Gen_movTruncate_mem16Reg64PlusReg64_reg64(x64GenContext, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32, REG_RESV_TEMP); + if (indexed) + x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); + // todo: Optimize this, e.g. by using MOVBE + } + else if (imlInstruction->op_storeLoad.copyWidth == 8) + { + if (indexed) + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + if (indexed && realRegisterMem == realRegisterData) + { + x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, realRegisterData); + realRegisterData = REG_RESV_TEMP; + } + if (indexed) + x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); + x64Gen_movTruncate_mem8Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32, realRegisterData); + if (indexed) + x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); + } + else if (imlInstruction->op_storeLoad.copyWidth == PPC_REC_STORE_STWCX_MARKER) + { + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + if (imlInstruction->op_storeLoad.immS32 != 0) + assert_dbg(); // todo + // reset cr0 LT, GT and EQ + sint32 crRegister = 0; + x64Gen_mov_mem8Reg64_imm8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, cr) + sizeof(uint8)*(crRegister * 4 + PPCREC_CR_BIT_LT), 0); + x64Gen_mov_mem8Reg64_imm8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, cr) + sizeof(uint8)*(crRegister * 4 + PPCREC_CR_BIT_GT), 0); + x64Gen_mov_mem8Reg64_imm8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, cr) + sizeof(uint8)*(crRegister * 4 + PPCREC_CR_BIT_EQ), 0); + // calculate effective address + x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, realRegisterData); + if (swapEndian) + x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP); + if (indexed) + x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); + // realRegisterMem now holds EA + x64Gen_cmp_reg64Low32_mem32reg64(x64GenContext, realRegisterMem, REG_RESV_HCPU, offsetof(PPCInterpreter_t, reservedMemAddr)); + sint32 jumpInstructionOffsetJumpToEnd = x64GenContext->codeBufferIndex; + x64Gen_jmpc_near(x64GenContext, X86_CONDITION_NOT_EQUAL, 0); + // EA matches reservation + // backup EAX (since it's an explicit operand of CMPXCHG and will be overwritten) + x64Emit_mov_mem32_reg32(x64GenContext, REG_RSP, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[0]), REG_EAX); + // backup REG_RESV_MEMBASE + x64Emit_mov_mem64_reg64(x64GenContext, REG_RESV_HCPU, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[2]), REG_RESV_MEMBASE); + // add mem register to REG_RESV_MEMBASE + x64Gen_add_reg64_reg64(x64GenContext, REG_RESV_MEMBASE, realRegisterMem); + // load reserved value in EAX + x64Emit_mov_reg64_mem32(x64GenContext, REG_EAX, REG_RESV_HCPU, offsetof(PPCInterpreter_t, reservedMemValue)); + // bswap EAX + x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_EAX); + + //x64Gen_lock_cmpxchg_mem32Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, realRegisterMem, 0, REG_RESV_TEMP); + x64Gen_lock_cmpxchg_mem32Reg64_reg64(x64GenContext, REG_RESV_MEMBASE, 0, REG_RESV_TEMP); + + x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_EQUAL, REG_RSP, offsetof(PPCInterpreter_t, cr) + sizeof(uint8)*(crRegister * 4 + PPCREC_CR_BIT_EQ)); + + // reset reservation + x64Gen_mov_mem32Reg64_imm32(x64GenContext, REG_RESV_HCPU, (uint32)offsetof(PPCInterpreter_t, reservedMemAddr), 0); + x64Gen_mov_mem32Reg64_imm32(x64GenContext, REG_RESV_HCPU, (uint32)offsetof(PPCInterpreter_t, reservedMemValue), 0); + + // restore EAX + x64Emit_mov_reg64_mem32(x64GenContext, REG_EAX, REG_RESV_HCPU, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[0])); + // restore REG_RESV_MEMBASE + x64Emit_mov_reg64_mem64(x64GenContext, REG_RESV_MEMBASE, REG_RESV_HCPU, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[2])); + + // copy XER SO to CR0 SO + x64Gen_bt_mem8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, spr.XER), 31); + x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_CARRY, REG_RESV_HCPU, offsetof(PPCInterpreter_t, cr) + sizeof(uint8)*(crRegister * 4 + PPCREC_CR_BIT_SO)); + // end + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffsetJumpToEnd, x64GenContext->codeBufferIndex); + } + else if (imlInstruction->op_storeLoad.copyWidth == PPC_REC_STORE_STSWI_2) + { + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, realRegisterData); + x64Gen_shr_reg64Low32_imm8(x64GenContext, REG_RESV_TEMP, 16); // store upper 2 bytes .. + x64Gen_rol_reg64Low16_imm8(x64GenContext, REG_RESV_TEMP, 8); // .. as big-endian + if (indexed) + x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); + + x64Gen_movTruncate_mem16Reg64PlusReg64_reg64(x64GenContext, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32, REG_RESV_TEMP); + if (indexed) + x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); + } + else if (imlInstruction->op_storeLoad.copyWidth == PPC_REC_STORE_STSWI_3) + { + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, realRegisterData); + if (indexed) + x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); + + x64Gen_shr_reg64Low32_imm8(x64GenContext, REG_RESV_TEMP, 8); + x64Gen_movTruncate_mem8Reg64PlusReg64_reg64(x64GenContext, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32 + 2, REG_RESV_TEMP); + x64Gen_shr_reg64Low32_imm8(x64GenContext, REG_RESV_TEMP, 8); + x64Gen_movTruncate_mem8Reg64PlusReg64_reg64(x64GenContext, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32 + 1, REG_RESV_TEMP); + x64Gen_shr_reg64Low32_imm8(x64GenContext, REG_RESV_TEMP, 8); + x64Gen_movTruncate_mem8Reg64PlusReg64_reg64(x64GenContext, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32 + 0, REG_RESV_TEMP); + + if (indexed) + x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); + } + else + return false; + return true; + } + return false; +} + +/* + * Copy byte/word/dword from memory to memory + */ +void PPCRecompilerX64Gen_imlInstruction_mem2mem(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction) +{ + sint32 realSrcMemReg = tempToRealRegister(imlInstruction->op_mem2mem.src.registerMem); + sint32 realSrcMemImm = imlInstruction->op_mem2mem.src.immS32; + sint32 realDstMemReg = tempToRealRegister(imlInstruction->op_mem2mem.dst.registerMem); + sint32 realDstMemImm = imlInstruction->op_mem2mem.dst.immS32; + // PPCRecompilerX64Gen_crConditionFlags_forget() is not needed here, since MOVs don't affect eflags + if (imlInstruction->op_mem2mem.copyWidth == 32) + { + x64Emit_mov_reg32_mem32(x64GenContext, REG_RESV_TEMP, REG_R13, realSrcMemReg, realSrcMemImm); + x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_R13, realDstMemReg, realDstMemImm, REG_RESV_TEMP); + } + else + { + assert_dbg(); + } +} + +bool PPCRecompilerX64Gen_imlInstruction_r_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction) +{ + if (imlInstruction->operation == PPCREC_IML_OP_ASSIGN) + { + // registerResult = registerA + if (imlInstruction->crRegister != PPC_REC_INVALID_REGISTER) + { + x64Gen_mov_reg64_reg64(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), tempToRealRegister(imlInstruction->op_r_r.registerA)); + if (imlInstruction->crMode == PPCREC_CR_MODE_LOGICAL) + { + // since MOV doesn't set eflags we need another test instruction + x64Gen_test_reg64Low32_reg64Low32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), tempToRealRegister(imlInstruction->op_r_r.registerResult)); + // set cr bits + PPCRecompilerX64Gen_updateCRLogical(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction); + } + else + { + assert_dbg(); + } + } + else + { + x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), tempToRealRegister(imlInstruction->op_r_r.registerA)); + } + } + else if (imlInstruction->operation == PPCREC_IML_OP_ENDIAN_SWAP) + { + // registerResult = endianSwap32(registerA) + if (imlInstruction->op_r_r.registerA != imlInstruction->op_r_r.registerResult) + assert_dbg(); + x64Gen_bswap_reg64Lower32bit(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult)); + } + else if( imlInstruction->operation == PPCREC_IML_OP_ADD ) + { + // registerResult += registerA + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); + x64Gen_add_reg64Low32_reg64Low32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), tempToRealRegister(imlInstruction->op_r_r.registerA)); + } + else if( imlInstruction->operation == PPCREC_IML_OP_ASSIGN_S8_TO_S32 ) + { + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + x64Gen_movSignExtend_reg64Low32_reg64Low8(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), tempToRealRegister(imlInstruction->op_r_r.registerA)); + if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) + { + if( imlInstruction->crMode == PPCREC_CR_MODE_ARITHMETIC ) + { + x64Gen_test_reg64Low32_reg64Low32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), tempToRealRegister(imlInstruction->op_r_r.registerResult)); + // set cr bits + PPCRecompilerX64Gen_updateCRLogical(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction); + } + else + { + debug_printf("PPCRecompilerX64Gen_imlInstruction_r_r(): Unsupported operation\n"); + assert_dbg(); + } + } + } + else if( imlInstruction->operation == PPCREC_IML_OP_OR || imlInstruction->operation == PPCREC_IML_OP_AND || imlInstruction->operation == PPCREC_IML_OP_XOR ) + { + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + if( imlInstruction->operation == PPCREC_IML_OP_OR ) + { + // registerResult |= registerA + x64Gen_or_reg64Low32_reg64Low32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), tempToRealRegister(imlInstruction->op_r_r.registerA)); + } + else if( imlInstruction->operation == PPCREC_IML_OP_AND ) + { + // registerResult &= registerA + x64Gen_and_reg64Low32_reg64Low32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), tempToRealRegister(imlInstruction->op_r_r.registerA)); + } + else + { + // registerResult ^= registerA + x64Gen_xor_reg64Low32_reg64Low32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), tempToRealRegister(imlInstruction->op_r_r.registerA)); + } + if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) + { + // set cr bits + PPCRecompilerX64Gen_updateCRLogical(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction); + } + } + else if( imlInstruction->operation == PPCREC_IML_OP_NOT ) + { + // copy register content if different registers + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + if( imlInstruction->op_r_r.registerResult != imlInstruction->op_r_r.registerA ) + { + x64Gen_mov_reg64_reg64(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), tempToRealRegister(imlInstruction->op_r_r.registerA)); + } + // NOT destination register + x64Gen_not_reg64Low32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult)); + // update cr bits + if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) + { + // NOT instruction does not update flags, so we have to generate an additional TEST instruction + x64Gen_test_reg64Low32_reg64Low32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), tempToRealRegister(imlInstruction->op_r_r.registerResult)); + // set cr bits + PPCRecompilerX64Gen_updateCRLogical(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction); + } + } + else if( imlInstruction->operation == PPCREC_IML_OP_CNTLZW ) + { + // count leading zeros + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); + // LZCNT instruction (part of SSE4, CPUID.80000001H:ECX.ABM[Bit 5]) + if( hasLZCNTSupport ) + { + x64Gen_lzcnt_reg64Low32_reg64Low32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), tempToRealRegister(imlInstruction->op_r_r.registerA)); + } + else + { + x64Gen_test_reg64Low32_reg64Low32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerA), tempToRealRegister(imlInstruction->op_r_r.registerA)); + sint32 jumpInstructionOffset1 = x64GenContext->codeBufferIndex; + x64Gen_jmpc_near(x64GenContext, X86_CONDITION_EQUAL, 0); + x64Gen_bsr_reg64Low32_reg64Low32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), tempToRealRegister(imlInstruction->op_r_r.registerA)); + x64Gen_neg_reg64Low32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult)); + x64Gen_add_reg64Low32_imm32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), 32-1); + sint32 jumpInstructionOffset2 = x64GenContext->codeBufferIndex; + x64Gen_jmpc_near(x64GenContext, X86_CONDITION_NONE, 0); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset1, x64GenContext->codeBufferIndex); + x64Gen_mov_reg64Low32_imm32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), 32); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset2, x64GenContext->codeBufferIndex); + } + } + else if( imlInstruction->operation == PPCREC_IML_OP_COMPARE_SIGNED || imlInstruction->operation == PPCREC_IML_OP_COMPARE_UNSIGNED ) + { + // registerA CMP registerB (arithmetic compare) + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + if( imlInstruction->crRegister == PPC_REC_INVALID_REGISTER ) + { + return false; // a NO-OP instruction + } + if( imlInstruction->crRegister >= 8 ) + { + return false; + } + // update state of cr register + if( imlInstruction->operation == PPCREC_IML_OP_COMPARE_SIGNED ) + PPCRecompilerX64Gen_crConditionFlags_set(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction->crRegister, PPCREC_CR_STATE_TYPE_SIGNED_ARITHMETIC); + else + PPCRecompilerX64Gen_crConditionFlags_set(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction->crRegister, PPCREC_CR_STATE_TYPE_UNSIGNED_ARITHMETIC); + // create compare instruction + x64Gen_cmp_reg64Low32_reg64Low32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), tempToRealRegister(imlInstruction->op_r_r.registerA)); + // set cr bits + sint32 crRegister = imlInstruction->crRegister; + if( imlInstruction->operation == PPCREC_IML_OP_COMPARE_SIGNED ) + { + if( (imlInstruction->crIgnoreMask&(1<<(crRegister*4+PPCREC_CR_BIT_LT))) == 0 ) + x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_SIGNED_LESS, REG_ESP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_LT)); + if( (imlInstruction->crIgnoreMask&(1<<(crRegister*4+PPCREC_CR_BIT_GT))) == 0 ) + x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_SIGNED_GREATER, REG_ESP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_GT)); + if( (imlInstruction->crIgnoreMask&(1<<(crRegister*4+PPCREC_CR_BIT_EQ))) == 0 ) + x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_EQUAL, REG_ESP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_EQ)); + // todo: Also set summary overflow if xer bit is set + } + else if( imlInstruction->operation == PPCREC_IML_OP_COMPARE_UNSIGNED ) + { + if( (imlInstruction->crIgnoreMask&(1<<(crRegister*4+PPCREC_CR_BIT_LT))) == 0 ) + x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_UNSIGNED_BELOW, REG_ESP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_LT)); + if( (imlInstruction->crIgnoreMask&(1<<(crRegister*4+PPCREC_CR_BIT_GT))) == 0 ) + x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_UNSIGNED_ABOVE, REG_ESP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_GT)); + if( (imlInstruction->crIgnoreMask&(1<<(crRegister*4+PPCREC_CR_BIT_EQ))) == 0 ) + x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_EQUAL, REG_ESP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_EQ)); + // todo: Also set summary overflow if xer bit is set + } + else + assert_dbg(); + } + else if( imlInstruction->operation == PPCREC_IML_OP_NEG ) + { + // copy register content if different registers + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + if( imlInstruction->op_r_r.registerResult != imlInstruction->op_r_r.registerA ) + { + x64Gen_mov_reg64_reg64(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), tempToRealRegister(imlInstruction->op_r_r.registerA)); + } + // NEG destination register + x64Gen_neg_reg64Low32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult)); + // update cr bits + if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) + { + // set cr bits + PPCRecompilerX64Gen_updateCRLogical(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction); + } + } + else if( imlInstruction->operation == PPCREC_IML_OP_ADD_CARRY ) + { + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + // copy operand to result if different registers + if( imlInstruction->op_r_r.registerResult != imlInstruction->op_r_r.registerA ) + { + x64Gen_mov_reg64_reg64(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), tempToRealRegister(imlInstruction->op_r_r.registerA)); + } + // copy xer_ca to eflags carry + x64Gen_bt_mem8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, xer_ca), 0); + // add carry bit + x64Gen_adc_reg64Low32_imm32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), 0); + // update xer carry + x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_CARRY, REG_RSP, offsetof(PPCInterpreter_t, xer_ca)); + if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) + { + // set cr bits + sint32 crRegister = imlInstruction->crRegister; + x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_SIGN, REG_RSP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_LT)); // check for sign instead of _BELOW (CF) which is not set by AND/OR + x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_UNSIGNED_ABOVE, REG_RSP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_GT)); + x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_EQUAL, REG_RSP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_EQ)); + // todo: Use different version of PPCRecompilerX64Gen_updateCRLogical(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction) + // todo: Also set summary overflow if xer bit is set + } + } + else if( imlInstruction->operation == PPCREC_IML_OP_ADD_CARRY_ME ) + { + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + // copy operand to result if different registers + if( imlInstruction->op_r_r.registerResult != imlInstruction->op_r_r.registerA ) + { + x64Gen_mov_reg64_reg64(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), tempToRealRegister(imlInstruction->op_r_r.registerA)); + } + // copy xer_ca to eflags carry + x64Gen_bt_mem8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, xer_ca), 0); + // add carry bit + x64Gen_adc_reg64Low32_imm32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), (uint32)-1); + // update xer carry + x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_CARRY, REG_RSP, offsetof(PPCInterpreter_t, xer_ca)); + if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) + { + // set cr bits + sint32 crRegister = imlInstruction->crRegister; + x64Gen_test_reg64Low32_reg64Low32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), tempToRealRegister(imlInstruction->op_r_r.registerResult)); + PPCRecompilerX64Gen_updateCRLogical(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction); + } + } + else if( imlInstruction->operation == PPCREC_IML_OP_SUB_CARRY_UPDATE_CARRY ) + { + // registerResult = ~registerOperand1 + carry + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + sint32 rRegResult = tempToRealRegister(imlInstruction->op_r_r.registerResult); + sint32 rRegOperand1 = tempToRealRegister(imlInstruction->op_r_r.registerA); + // copy operand to result register + x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand1); + // execute NOT on result + x64Gen_not_reg64Low32(x64GenContext, rRegResult); + // copy xer_ca to eflags carry + x64Gen_bt_mem8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, xer_ca), 0); + // add carry + x64Gen_adc_reg64Low32_imm32(x64GenContext, rRegResult, 0); + // update carry + x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_CARRY, REG_RSP, offsetof(PPCInterpreter_t, xer_ca)); + // update cr if requested + if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) + { + if( imlInstruction->crMode == PPCREC_CR_MODE_LOGICAL ) + { + x64Gen_test_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegResult); + // set cr bits + PPCRecompilerX64Gen_updateCRLogical(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction); + } + else + { + assert_dbg(); + } + } + } + else if( imlInstruction->operation == PPCREC_IML_OP_ASSIGN_S16_TO_S32 ) + { + // registerResult = (uint32)(sint32)(sint16)registerA + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + x64Gen_movSignExtend_reg64Low32_reg64Low16(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), reg32ToReg16(tempToRealRegister(imlInstruction->op_r_r.registerA))); + if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) + { + if( imlInstruction->crMode == PPCREC_CR_MODE_ARITHMETIC ) + { + x64Gen_test_reg64Low32_reg64Low32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), tempToRealRegister(imlInstruction->op_r_r.registerResult)); + // set cr bits + PPCRecompilerX64Gen_updateCRLogical(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction); + } + else + { + debug_printf("PPCRecompilerX64Gen_imlInstruction_r_r(): Unsupported operation\n"); + assert_dbg(); + } + } + } + else if( imlInstruction->operation == PPCREC_IML_OP_DCBZ ) + { + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + if( imlInstruction->op_r_r.registerResult != imlInstruction->op_r_r.registerA ) + { + x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, tempToRealRegister(imlInstruction->op_r_r.registerA)); + x64Gen_add_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, tempToRealRegister(imlInstruction->op_r_r.registerResult)); + x64Gen_and_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, ~0x1F); + x64Gen_add_reg64_reg64(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE); + for(sint32 f=0; f<0x20; f+=8) + x64Gen_mov_mem64Reg64_imm32(x64GenContext, REG_RESV_TEMP, f, 0); + } + else + { + // calculate effective address + x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, tempToRealRegister(imlInstruction->op_r_r.registerA)); + x64Gen_and_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, ~0x1F); + x64Gen_add_reg64_reg64(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE); + for(sint32 f=0; f<0x20; f+=8) + x64Gen_mov_mem64Reg64_imm32(x64GenContext, REG_RESV_TEMP, f, 0); + } + } + else + { + debug_printf("PPCRecompilerX64Gen_imlInstruction_r_r(): Unsupported operation 0x%x\n", imlInstruction->operation); + return false; + } + return true; +} + +bool PPCRecompilerX64Gen_imlInstruction_r_s32(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction) +{ + if( imlInstruction->operation == PPCREC_IML_OP_ASSIGN ) + { + // registerResult = immS32 + cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); + x64Gen_mov_reg64Low32_imm32(x64GenContext, tempToRealRegister(imlInstruction->op_r_immS32.registerIndex), (uint32)imlInstruction->op_r_immS32.immS32); + } + else if( imlInstruction->operation == PPCREC_IML_OP_ADD ) + { + // registerResult += immS32 + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) + { + assert_dbg(); + } + x64Gen_add_reg64Low32_imm32(x64GenContext, tempToRealRegister(imlInstruction->op_r_immS32.registerIndex), (uint32)imlInstruction->op_r_immS32.immS32); + } + else if( imlInstruction->operation == PPCREC_IML_OP_SUB ) + { + // registerResult -= immS32 + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + if (imlInstruction->crRegister == PPCREC_CR_REG_TEMP) + { + // do nothing -> SUB is for BDNZ instruction + } + else if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) + { + // update cr register + assert_dbg(); + } + x64Gen_sub_reg64Low32_imm32(x64GenContext, tempToRealRegister(imlInstruction->op_r_immS32.registerIndex), (uint32)imlInstruction->op_r_immS32.immS32); + } + else if( imlInstruction->operation == PPCREC_IML_OP_AND ) + { + // registerResult &= immS32 + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + x64Gen_and_reg64Low32_imm32(x64GenContext, tempToRealRegister(imlInstruction->op_r_immS32.registerIndex), (uint32)imlInstruction->op_r_immS32.immS32); + if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) + { + if( imlInstruction->crMode != PPCREC_CR_MODE_LOGICAL ) + { + assert_dbg(); + } + // set cr bits + sint32 crRegister = imlInstruction->crRegister; + PPCRecompilerX64Gen_updateCRLogical(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction); + // todo: Set CR SO if XER SO bit is set + } + } + else if( imlInstruction->operation == PPCREC_IML_OP_OR ) + { + // registerResult |= immS32 + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); + x64Gen_or_reg64Low32_imm32(x64GenContext, tempToRealRegister(imlInstruction->op_r_immS32.registerIndex), (uint32)imlInstruction->op_r_immS32.immS32); + } + else if( imlInstruction->operation == PPCREC_IML_OP_XOR ) + { + // registerResult ^= immS32 + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); + x64Gen_xor_reg64Low32_imm32(x64GenContext, tempToRealRegister(imlInstruction->op_r_immS32.registerIndex), (uint32)imlInstruction->op_r_immS32.immS32); + } + else if( imlInstruction->operation == PPCREC_IML_OP_LEFT_ROTATE ) + { + // registerResult <<<= immS32 + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); + if( (imlInstruction->op_r_immS32.immS32&0x80) ) + assert_dbg(); // should not happen + x64Gen_rol_reg64Low32_imm8(x64GenContext, tempToRealRegister(imlInstruction->op_r_immS32.registerIndex), (uint8)imlInstruction->op_r_immS32.immS32); + } + else if( imlInstruction->operation == PPCREC_IML_OP_COMPARE_SIGNED || imlInstruction->operation == PPCREC_IML_OP_COMPARE_UNSIGNED ) + { + // registerResult CMP immS32 (arithmetic compare) + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + if( imlInstruction->crRegister == PPC_REC_INVALID_REGISTER ) + { + debug_printf("PPCRecompilerX64Gen_imlInstruction_r_s32(): No-Op CMP found\n"); + return true; // a NO-OP instruction + } + if( imlInstruction->crRegister >= 8 ) + { + debug_printf("PPCRecompilerX64Gen_imlInstruction_r_s32(): Unsupported CMP with crRegister = 8\n"); + return false; + } + // update state of cr register + if( imlInstruction->operation == PPCREC_IML_OP_COMPARE_SIGNED ) + PPCRecompilerX64Gen_crConditionFlags_set(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction->crRegister, PPCREC_CR_STATE_TYPE_SIGNED_ARITHMETIC); + else + PPCRecompilerX64Gen_crConditionFlags_set(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction->crRegister, PPCREC_CR_STATE_TYPE_UNSIGNED_ARITHMETIC); + // create compare instruction + x64Gen_cmp_reg64Low32_imm32(x64GenContext, tempToRealRegister(imlInstruction->op_r_immS32.registerIndex), imlInstruction->op_r_immS32.immS32); + // set cr bits + uint32 crRegister = imlInstruction->crRegister; + if( imlInstruction->operation == PPCREC_IML_OP_COMPARE_SIGNED ) + { + if( (imlInstruction->crIgnoreMask&(1<<(crRegister*4+PPCREC_CR_BIT_LT))) == 0 ) + x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_SIGNED_LESS, REG_ESP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_LT)); + if( (imlInstruction->crIgnoreMask&(1<<(crRegister*4+PPCREC_CR_BIT_GT))) == 0 ) + x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_SIGNED_GREATER, REG_ESP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_GT)); + if( (imlInstruction->crIgnoreMask&(1<<(crRegister*4+PPCREC_CR_BIT_EQ))) == 0 ) + x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_EQUAL, REG_ESP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_EQ)); + } + else if( imlInstruction->operation == PPCREC_IML_OP_COMPARE_UNSIGNED ) + { + if( (imlInstruction->crIgnoreMask&(1<<(crRegister*4+PPCREC_CR_BIT_LT))) == 0 ) + x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_UNSIGNED_BELOW, REG_ESP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_LT)); + if( (imlInstruction->crIgnoreMask&(1<<(crRegister*4+PPCREC_CR_BIT_GT))) == 0 ) + x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_UNSIGNED_ABOVE, REG_ESP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_GT)); + if( (imlInstruction->crIgnoreMask&(1<<(crRegister*4+PPCREC_CR_BIT_EQ))) == 0 ) + x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_EQUAL, REG_ESP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_EQ)); + } + else + assert_dbg(); + // todo: Also set summary overflow if xer bit is set? + } + else if( imlInstruction->operation == PPCREC_IML_OP_MFCR ) + { + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + uint32 destRegister = tempToRealRegister(imlInstruction->op_r_immS32.registerIndex); + x64Gen_xor_reg64Low32_reg64Low32(x64GenContext, destRegister, destRegister); + for(sint32 f=0; f<32; f++) + { + x64Gen_bt_mem8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, cr)+f, 0); + x64Gen_adc_reg64Low32_reg64Low32(x64GenContext, destRegister, destRegister); + } + } + else if (imlInstruction->operation == PPCREC_IML_OP_MTCRF) + { + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + uint32 srcRegister = tempToRealRegister(imlInstruction->op_r_immS32.registerIndex); + uint32 crBitMask = ppc_MTCRFMaskToCRBitMask((uint32)imlInstruction->op_r_immS32.immS32); + for (sint32 f = 0; f < 32; f++) + { + if(((crBitMask >> f) & 1) == 0) + continue; + x64Gen_mov_mem8Reg64_imm8(x64GenContext, REG_ESP, offsetof(PPCInterpreter_t, cr) + sizeof(uint8) * (f), 0); + x64Gen_test_reg64Low32_imm32(x64GenContext, srcRegister, 0x80000000>>f); + x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_NOT_EQUAL, REG_ESP, offsetof(PPCInterpreter_t, cr) + sizeof(uint8) * (f)); + } + } + else + { + debug_printf("PPCRecompilerX64Gen_imlInstruction_r_s32(): Unsupported operation 0x%x\n", imlInstruction->operation); + return false; + } + return true; +} + +bool PPCRecompilerX64Gen_imlInstruction_conditional_r_s32(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction) +{ + if (imlInstruction->operation == PPCREC_IML_OP_ASSIGN) + { + // registerResult = immS32 (conditional) + if (imlInstruction->crRegister != PPC_REC_INVALID_REGISTER) + { + assert_dbg(); + } + + x64Gen_mov_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, (uint32)imlInstruction->op_conditional_r_s32.immS32); + + uint8 crBitIndex = imlInstruction->op_conditional_r_s32.crRegisterIndex * 4 + imlInstruction->op_conditional_r_s32.crBitIndex; + if (imlInstruction->op_conditional_r_s32.crRegisterIndex == x64GenContext->activeCRRegister) + { + if (x64GenContext->activeCRState == PPCREC_CR_STATE_TYPE_UNSIGNED_ARITHMETIC) + { + if (imlInstruction->op_conditional_r_s32.crBitIndex == CR_BIT_LT) + { + x64Gen_cmovcc_reg64Low32_reg64Low32(x64GenContext, imlInstruction->op_conditional_r_s32.bitMustBeSet ? X86_CONDITION_CARRY : X86_CONDITION_NOT_CARRY, tempToRealRegister(imlInstruction->op_conditional_r_s32.registerIndex), REG_RESV_TEMP); + return true; + } + else if (imlInstruction->op_conditional_r_s32.crBitIndex == CR_BIT_EQ) + { + x64Gen_cmovcc_reg64Low32_reg64Low32(x64GenContext, imlInstruction->op_conditional_r_s32.bitMustBeSet ? X86_CONDITION_EQUAL : X86_CONDITION_NOT_EQUAL, tempToRealRegister(imlInstruction->op_conditional_r_s32.registerIndex), REG_RESV_TEMP); + return true; + } + else if (imlInstruction->op_conditional_r_s32.crBitIndex == CR_BIT_GT) + { + x64Gen_cmovcc_reg64Low32_reg64Low32(x64GenContext, imlInstruction->op_conditional_r_s32.bitMustBeSet ? X86_CONDITION_UNSIGNED_ABOVE : X86_CONDITION_UNSIGNED_BELOW_EQUAL, tempToRealRegister(imlInstruction->op_conditional_r_s32.registerIndex), REG_RESV_TEMP); + return true; + } + } + else if (x64GenContext->activeCRState == PPCREC_CR_STATE_TYPE_SIGNED_ARITHMETIC) + { + if (imlInstruction->op_conditional_r_s32.crBitIndex == CR_BIT_LT) + { + x64Gen_cmovcc_reg64Low32_reg64Low32(x64GenContext, imlInstruction->op_conditional_r_s32.bitMustBeSet ? X86_CONDITION_SIGNED_LESS : X86_CONDITION_SIGNED_GREATER_EQUAL, tempToRealRegister(imlInstruction->op_conditional_r_s32.registerIndex), REG_RESV_TEMP); + return true; + } + else if (imlInstruction->op_conditional_r_s32.crBitIndex == CR_BIT_EQ) + { + x64Gen_cmovcc_reg64Low32_reg64Low32(x64GenContext, imlInstruction->op_conditional_r_s32.bitMustBeSet ? X86_CONDITION_EQUAL : X86_CONDITION_NOT_EQUAL, tempToRealRegister(imlInstruction->op_conditional_r_s32.registerIndex), REG_RESV_TEMP); + return true; + } + else if (imlInstruction->op_conditional_r_s32.crBitIndex == CR_BIT_GT) + { + x64Gen_cmovcc_reg64Low32_reg64Low32(x64GenContext, imlInstruction->op_conditional_r_s32.bitMustBeSet ? X86_CONDITION_SIGNED_GREATER : X86_CONDITION_SIGNED_LESS_EQUAL, tempToRealRegister(imlInstruction->op_conditional_r_s32.registerIndex), REG_RESV_TEMP); + return true; + } + } + else if (x64GenContext->activeCRState == PPCREC_CR_STATE_TYPE_LOGICAL) + { + if (imlInstruction->op_conditional_r_s32.crBitIndex == CR_BIT_LT) + { + x64Gen_cmovcc_reg64Low32_reg64Low32(x64GenContext, imlInstruction->op_conditional_r_s32.bitMustBeSet ? X86_CONDITION_SIGN : X86_CONDITION_NOT_SIGN, tempToRealRegister(imlInstruction->op_conditional_r_s32.registerIndex), REG_RESV_TEMP); + return true; + } + else if (imlInstruction->op_conditional_r_s32.crBitIndex == CR_BIT_EQ) + { + x64Gen_cmovcc_reg64Low32_reg64Low32(x64GenContext, imlInstruction->op_conditional_r_s32.bitMustBeSet ? X86_CONDITION_EQUAL : X86_CONDITION_NOT_EQUAL, tempToRealRegister(imlInstruction->op_conditional_r_s32.registerIndex), REG_RESV_TEMP); + return true; + } + else if (imlInstruction->op_conditional_r_s32.crBitIndex == CR_BIT_GT) + { + x64Gen_cmovcc_reg64Low32_reg64Low32(x64GenContext, imlInstruction->op_conditional_r_s32.bitMustBeSet ? X86_CONDITION_SIGNED_GREATER : X86_CONDITION_SIGNED_LESS_EQUAL, tempToRealRegister(imlInstruction->op_conditional_r_s32.registerIndex), REG_RESV_TEMP); + return true; + } + } + } + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + x64Gen_bt_mem8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, cr) + crBitIndex * sizeof(uint8), 0); + if (imlInstruction->op_conditional_r_s32.bitMustBeSet) + x64Gen_cmovcc_reg64Low32_reg64Low32(x64GenContext, X86_CONDITION_CARRY, tempToRealRegister(imlInstruction->op_conditional_r_s32.registerIndex), REG_RESV_TEMP); + else + x64Gen_cmovcc_reg64Low32_reg64Low32(x64GenContext, X86_CONDITION_NOT_CARRY, tempToRealRegister(imlInstruction->op_conditional_r_s32.registerIndex), REG_RESV_TEMP); + return true; + } + return false; +} + +bool PPCRecompilerX64Gen_imlInstruction_r_r_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction) +{ + if( imlInstruction->operation == PPCREC_IML_OP_ADD || imlInstruction->operation == PPCREC_IML_OP_ADD_UPDATE_CARRY || imlInstruction->operation == PPCREC_IML_OP_ADD_CARRY_UPDATE_CARRY ) + { + // registerResult = registerOperand1 + registerOperand2 + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + sint32 rRegResult = tempToRealRegister(imlInstruction->op_r_r_r.registerResult); + sint32 rRegOperand1 = tempToRealRegister(imlInstruction->op_r_r_r.registerA); + sint32 rRegOperand2 = tempToRealRegister(imlInstruction->op_r_r_r.registerB); + + bool addCarry = imlInstruction->operation == PPCREC_IML_OP_ADD_CARRY_UPDATE_CARRY; + if( (rRegResult == rRegOperand1) || (rRegResult == rRegOperand2) ) + { + // be careful not to overwrite the operand before we use it + if( rRegResult == rRegOperand1 ) + { + if( addCarry ) + { + x64Gen_bt_mem8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, xer_ca), 0); + x64Gen_adc_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand2); + } + else + x64Gen_add_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand2); + } + else + { + if( addCarry ) + { + x64Gen_bt_mem8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, xer_ca), 0); + x64Gen_adc_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand1); + } + else + x64Gen_add_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand1); + } + } + else + { + // copy operand1 to destination register before doing addition + x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, rRegOperand1); + // add operand2 + if( addCarry ) + { + x64Gen_bt_mem8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, xer_ca), 0); + x64Gen_adc_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand2); + } + else + x64Gen_add_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand2); + } + // update carry + if( imlInstruction->operation == PPCREC_IML_OP_ADD_UPDATE_CARRY || imlInstruction->operation == PPCREC_IML_OP_ADD_CARRY_UPDATE_CARRY ) + { + x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_CARRY, REG_RSP, offsetof(PPCInterpreter_t, xer_ca)); + } + // set cr bits if enabled + if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) + { + if( imlInstruction->crMode != PPCREC_CR_MODE_LOGICAL ) + { + assert_dbg(); + } + sint32 crRegister = imlInstruction->crRegister; + PPCRecompilerX64Gen_updateCRLogical(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction); + return true; + } + } + else if( imlInstruction->operation == PPCREC_IML_OP_SUB ) + { + // registerResult = registerOperand1 - registerOperand2 + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + sint32 rRegResult = tempToRealRegister(imlInstruction->op_r_r_r.registerResult); + sint32 rRegOperand1 = tempToRealRegister(imlInstruction->op_r_r_r.registerA); + sint32 rRegOperand2 = tempToRealRegister(imlInstruction->op_r_r_r.registerB); + if( rRegOperand1 == rRegOperand2 ) + { + // result = operand1 - operand1 -> 0 + x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegResult); + } + else if( rRegResult == rRegOperand1 ) + { + // result = result - operand2 + x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand2); + } + else if ( rRegResult == rRegOperand2 ) + { + // result = operand1 - result + // NEG result + x64Gen_neg_reg64Low32(x64GenContext, rRegResult); + // ADD result, operand1 + x64Gen_add_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand1); + } + else + { + // copy operand1 to destination register before doing addition + x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, rRegOperand1); + // sub operand2 + x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand2); + } + // set cr bits if enabled + if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) + { + if( imlInstruction->crMode != PPCREC_CR_MODE_LOGICAL ) + { + assert_dbg(); + } + sint32 crRegister = imlInstruction->crRegister; + PPCRecompilerX64Gen_updateCRLogical(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction); + return true; + } + } + else if( imlInstruction->operation == PPCREC_IML_OP_SUB_CARRY_UPDATE_CARRY ) + { + // registerResult = registerOperand1 - registerOperand2 + carry + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + sint32 rRegResult = tempToRealRegister(imlInstruction->op_r_r_r.registerResult); + sint32 rRegOperand1 = tempToRealRegister(imlInstruction->op_r_r_r.registerA); + sint32 rRegOperand2 = tempToRealRegister(imlInstruction->op_r_r_r.registerB); + if( rRegOperand1 == rRegOperand2 ) + { + // copy xer_ca to eflags carry + x64Gen_bt_mem8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, xer_ca), 0); + x64Gen_cmc(x64GenContext); + // result = operand1 - operand1 -> 0 + x64Gen_sbb_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegResult); + } + else if( rRegResult == rRegOperand1 ) + { + // copy inverted xer_ca to eflags carry + x64Gen_bt_mem8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, xer_ca), 0); + x64Gen_cmc(x64GenContext); + // result = result - operand2 + x64Gen_sbb_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand2); + } + else if ( rRegResult == rRegOperand2 ) + { + // result = operand1 - result + // NOT result + x64Gen_not_reg64Low32(x64GenContext, rRegResult); + // copy xer_ca to eflags carry + x64Gen_bt_mem8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, xer_ca), 0); + // ADC result, operand1 + x64Gen_adc_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand1); + } + else + { + // copy operand1 to destination register before doing addition + x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, rRegOperand1); + // copy xer_ca to eflags carry + x64Gen_bt_mem8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, xer_ca), 0); + x64Gen_cmc(x64GenContext); + // sub operand2 + x64Gen_sbb_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand2); + } + // update carry flag (todo: is this actually correct in all cases?) + x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_CARRY, REG_RSP, offsetof(PPCInterpreter_t, xer_ca)); + // update cr0 if requested + if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) + { + if( imlInstruction->crMode != PPCREC_CR_MODE_LOGICAL ) + assert_dbg(); + x64Gen_test_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegResult); + PPCRecompilerX64Gen_updateCRLogical(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction); + } + } + else if( imlInstruction->operation == PPCREC_IML_OP_MULTIPLY_SIGNED ) + { + // registerResult = registerOperand1 * registerOperand2 + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + sint32 rRegResult = tempToRealRegister(imlInstruction->op_r_r_r.registerResult); + sint32 rRegOperand1 = tempToRealRegister(imlInstruction->op_r_r_r.registerA); + sint32 rRegOperand2 = tempToRealRegister(imlInstruction->op_r_r_r.registerB); + if( (rRegResult == rRegOperand1) || (rRegResult == rRegOperand2) ) + { + // be careful not to overwrite the operand before we use it + if( rRegResult == rRegOperand1 ) + x64Gen_imul_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand2); + else + x64Gen_imul_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand1); + } + else + { + // copy operand1 to destination register before doing multiplication + x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, rRegOperand1); + // add operand2 + x64Gen_imul_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand2); + } + // set cr bits if enabled + if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) + { + if( imlInstruction->crMode != PPCREC_CR_MODE_LOGICAL ) + { + assert_dbg(); + } + // since IMUL instruction leaves relevant flags undefined, we have to use another TEST instruction to get the correct results + x64Gen_test_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegResult); + PPCRecompilerX64Gen_updateCRLogical(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction); + } + } + else if( imlInstruction->operation == PPCREC_IML_OP_SUBFC ) + { + // registerResult = registerOperand2(rB) - registerOperand1(rA) + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + // updates carry flag + if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) + { + return false; + } + sint32 rRegResult = tempToRealRegister(imlInstruction->op_r_r_r.registerResult); + sint32 rRegOperandA = tempToRealRegister(imlInstruction->op_r_r_r.registerA); + sint32 rRegOperandB = tempToRealRegister(imlInstruction->op_r_r_r.registerB); + // update carry flag + // carry flag is detected this way: + //if ((~a+b) < a) { + // return true; + //} + //if ((~a+b+1) < 1) { + // return true; + //} + // set carry to zero + x64Gen_mov_mem8Reg64_imm8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, xer_ca), 0); + // ((~a+b)<~a) == true -> ca = 1 + x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, rRegOperandA); + x64Gen_not_reg64Low32(x64GenContext, REG_RESV_TEMP); + x64Gen_add_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, rRegOperandB); + x64Gen_not_reg64Low32(x64GenContext, rRegOperandA); + x64Gen_cmp_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, rRegOperandA); + x64Gen_not_reg64Low32(x64GenContext, rRegOperandA); + sint32 jumpInstructionOffset1 = x64GenContext->codeBufferIndex; + x64Gen_jmpc_near(x64GenContext, X86_CONDITION_UNSIGNED_ABOVE_EQUAL, 0); + // reset carry flag + jump destination afterwards + x64Gen_mov_mem8Reg64_imm8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, xer_ca), 1); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset1, x64GenContext->codeBufferIndex); + // OR ((~a+b+1)<1) == true -> ca = 1 + x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, rRegOperandA); + // todo: Optimize by reusing result in REG_RESV_TEMP from above and only add 1 + x64Gen_not_reg64Low32(x64GenContext, REG_RESV_TEMP); + x64Gen_add_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, rRegOperandB); + x64Gen_add_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, 1); + x64Gen_cmp_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, 1); + sint32 jumpInstructionOffset2 = x64GenContext->codeBufferIndex; + x64Gen_jmpc_near(x64GenContext, X86_CONDITION_UNSIGNED_ABOVE_EQUAL, 0); + // reset carry flag + jump destination afterwards + x64Gen_mov_mem8Reg64_imm8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, xer_ca), 1); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset2, x64GenContext->codeBufferIndex); + // do subtraction + if( rRegOperandB == rRegOperandA ) + { + // result = operandA - operandA -> 0 + x64Gen_xor_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegResult); + } + else if( rRegResult == rRegOperandB ) + { + // result = result - operandA + x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperandA); + } + else if ( rRegResult == rRegOperandA ) + { + // result = operandB - result + // NEG result + x64Gen_neg_reg64Low32(x64GenContext, rRegResult); + // ADD result, operandB + x64Gen_add_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperandB); + } + else + { + // copy operand1 to destination register before doing addition + x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, rRegOperandB); + // sub operand2 + x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperandA); + } + } + else if( imlInstruction->operation == PPCREC_IML_OP_SLW || imlInstruction->operation == PPCREC_IML_OP_SRW ) + { + // registerResult = registerOperand1(rA) >> registerOperand2(rB) (up to 63 bits) + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + sint32 rRegResult = tempToRealRegister(imlInstruction->op_r_r_r.registerResult); + sint32 rRegOperand1 = tempToRealRegister(imlInstruction->op_r_r_r.registerA); + sint32 rRegOperand2 = tempToRealRegister(imlInstruction->op_r_r_r.registerB); + + if (hasBMI2Support && imlInstruction->operation == PPCREC_IML_OP_SRW) + { + // use BMI2 SHRX if available + x64Gen_shrx_reg64_reg64_reg64(x64GenContext, rRegResult, rRegOperand1, rRegOperand2); + } + else if (hasBMI2Support && imlInstruction->operation == PPCREC_IML_OP_SLW) + { + // use BMI2 SHLX if available + x64Gen_shlx_reg64_reg64_reg64(x64GenContext, rRegResult, rRegOperand1, rRegOperand2); + x64Gen_and_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegResult); // trim result to 32bit + } + else + { + // lazy and slow way to do shift by register without relying on ECX/CL or BMI2 + x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, rRegOperand1); + for (sint32 b = 0; b < 6; b++) + { + x64Gen_test_reg64Low32_imm32(x64GenContext, rRegOperand2, (1 << b)); + sint32 jumpInstructionOffset = x64GenContext->codeBufferIndex; + x64Gen_jmpc_near(x64GenContext, X86_CONDITION_EQUAL, 0); // jump if bit not set + if (b == 5) + { + x64Gen_xor_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, REG_RESV_TEMP); + } + else + { + if (imlInstruction->operation == PPCREC_IML_OP_SLW) + x64Gen_shl_reg64Low32_imm8(x64GenContext, REG_RESV_TEMP, (1 << b)); + else + x64Gen_shr_reg64Low32_imm8(x64GenContext, REG_RESV_TEMP, (1 << b)); + } + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset, x64GenContext->codeBufferIndex); + } + x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, REG_RESV_TEMP); + } + // set cr bits if enabled + if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) + { + if( imlInstruction->crMode != PPCREC_CR_MODE_LOGICAL ) + { + assert_dbg(); + } + x64Gen_test_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegResult); + PPCRecompilerX64Gen_updateCRLogical(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction); + } + } + else if( imlInstruction->operation == PPCREC_IML_OP_LEFT_ROTATE ) + { + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + sint32 rRegResult = tempToRealRegister(imlInstruction->op_r_r_r.registerResult); + sint32 rRegOperand1 = tempToRealRegister(imlInstruction->op_r_r_r.registerA); + sint32 rRegOperand2 = tempToRealRegister(imlInstruction->op_r_r_r.registerB); + // todo: Use BMI2 rotate if available + // check if CL/ECX/RCX is available + if( rRegResult != REG_RCX && rRegOperand1 != REG_RCX && rRegOperand2 != REG_RCX ) + { + // swap operand 2 with RCX + x64Gen_xchg_reg64_reg64(x64GenContext, REG_RCX, rRegOperand2); + // move operand 1 to temp register + x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, rRegOperand1); + // rotate + x64Gen_rol_reg64Low32_cl(x64GenContext, REG_RESV_TEMP); + // undo swap operand 2 with RCX + x64Gen_xchg_reg64_reg64(x64GenContext, REG_RCX, rRegOperand2); + // copy to result register + x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, REG_RESV_TEMP); + } + else + { + x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, rRegOperand1); + // lazy and slow way to do shift by register without relying on ECX/CL + for(sint32 b=0; b<5; b++) + { + x64Gen_test_reg64Low32_imm32(x64GenContext, rRegOperand2, (1<<b)); + sint32 jumpInstructionOffset = x64GenContext->codeBufferIndex; + x64Gen_jmpc_near(x64GenContext, X86_CONDITION_EQUAL, 0); // jump if bit not set + x64Gen_rol_reg64Low32_imm8(x64GenContext, REG_RESV_TEMP, (1<<b)); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset, x64GenContext->codeBufferIndex); + } + x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, REG_RESV_TEMP); + } + // set cr bits if enabled + if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) + { + if( imlInstruction->crMode != PPCREC_CR_MODE_LOGICAL ) + { + assert_dbg(); + } + x64Gen_test_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegResult); + PPCRecompilerX64Gen_updateCRLogical(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction); + } + } + else if( imlInstruction->operation == PPCREC_IML_OP_SRAW ) + { + // registerResult = (sint32)registerOperand1(rA) >> (sint32)registerOperand2(rB) (up to 63 bits) + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + sint32 rRegResult = tempToRealRegister(imlInstruction->op_r_r_r.registerResult); + sint32 rRegOperand1 = tempToRealRegister(imlInstruction->op_r_r_r.registerA); + sint32 rRegOperand2 = tempToRealRegister(imlInstruction->op_r_r_r.registerB); + // save cr + if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) + { + return false; + } + // todo: Use BMI instructions if available? + // MOV registerResult, registerOperand (if different) + x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, rRegOperand1); + // reset carry + x64Gen_mov_mem8Reg64_imm8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, xer_ca), 0); + // we use the same shift by register approach as in SLW/SRW, but we have to differentiate by signed/unsigned shift since it influences how the carry flag is set + x64Gen_test_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, 0x80000000); + sint32 jumpInstructionJumpToSignedShift = x64GenContext->codeBufferIndex; + x64Gen_jmpc_far(x64GenContext, X86_CONDITION_NOT_EQUAL, 0); + //sint32 jumpInstructionJumpToEnd = x64GenContext->codeBufferIndex; + //x64Gen_jmpc(x64GenContext, X86_CONDITION_EQUAL, 0); + // unsigned shift (MSB of input register is not set) + for(sint32 b=0; b<6; b++) + { + x64Gen_test_reg64Low32_imm32(x64GenContext, rRegOperand2, (1<<b)); + sint32 jumpInstructionOffset = x64GenContext->codeBufferIndex; + x64Gen_jmpc_near(x64GenContext, X86_CONDITION_EQUAL, 0); // jump if bit not set + if( b == 5 ) + { + x64Gen_sar_reg64Low32_imm8(x64GenContext, REG_RESV_TEMP, (1<<b)/2); + x64Gen_sar_reg64Low32_imm8(x64GenContext, REG_RESV_TEMP, (1<<b)/2); + } + else + { + x64Gen_sar_reg64Low32_imm8(x64GenContext, REG_RESV_TEMP, (1<<b)); + } + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset, x64GenContext->codeBufferIndex); + } + sint32 jumpInstructionJumpToEnd = x64GenContext->codeBufferIndex; + x64Gen_jmpc_far(x64GenContext, X86_CONDITION_NONE, 0); + // signed shift + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionJumpToSignedShift, x64GenContext->codeBufferIndex); + for(sint32 b=0; b<6; b++) + { + // check if we need to shift by (1<<bit) + x64Gen_test_reg64Low32_imm32(x64GenContext, rRegOperand2, (1<<b)); + sint32 jumpInstructionOffset = x64GenContext->codeBufferIndex; + x64Gen_jmpc_near(x64GenContext, X86_CONDITION_EQUAL, 0); // jump if bit not set + // set ca if any non-zero bit is shifted out + x64Gen_test_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, (1<<(1<<b))-1); + sint32 jumpInstructionJumpToAfterCa = x64GenContext->codeBufferIndex; + x64Gen_jmpc_near(x64GenContext, X86_CONDITION_EQUAL, 0); // jump if no bit is set + x64Gen_mov_mem8Reg64_imm8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, xer_ca), 1); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionJumpToAfterCa, x64GenContext->codeBufferIndex); + // arithmetic shift + if( b == 5 ) + { + // copy sign bit into all bits + x64Gen_sar_reg64Low32_imm8(x64GenContext, REG_RESV_TEMP, (1<<b)/2); + x64Gen_sar_reg64Low32_imm8(x64GenContext, REG_RESV_TEMP, (1<<b)/2); + } + else + { + x64Gen_sar_reg64Low32_imm8(x64GenContext, REG_RESV_TEMP, (1<<b)); + } + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset, x64GenContext->codeBufferIndex); + } + // end + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionJumpToEnd, x64GenContext->codeBufferIndex); + x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, REG_RESV_TEMP); + // update CR if requested + // todo + } + else if( imlInstruction->operation == PPCREC_IML_OP_DIVIDE_SIGNED || imlInstruction->operation == PPCREC_IML_OP_DIVIDE_UNSIGNED ) + { + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + sint32 rRegResult = tempToRealRegister(imlInstruction->op_r_r_r.registerResult); + sint32 rRegOperand1 = tempToRealRegister(imlInstruction->op_r_r_r.registerA); + sint32 rRegOperand2 = tempToRealRegister(imlInstruction->op_r_r_r.registerB); + + x64Emit_mov_mem32_reg32(x64GenContext, REG_RSP, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[0]), REG_EAX); + x64Emit_mov_mem32_reg32(x64GenContext, REG_RSP, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[1]), REG_EDX); + // mov operand 2 to temp register + x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, rRegOperand2); + // mov operand1 to EAX + x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, REG_EAX, rRegOperand1); + // sign or zero extend EAX to EDX:EAX based on division sign mode + if( imlInstruction->operation == PPCREC_IML_OP_DIVIDE_SIGNED ) + x64Gen_cdq(x64GenContext); + else + x64Gen_xor_reg64Low32_reg64Low32(x64GenContext, REG_EDX, REG_EDX); + // make sure we avoid division by zero + x64Gen_test_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, REG_RESV_TEMP); + x64Gen_jmpc_near(x64GenContext, X86_CONDITION_EQUAL, 3); + // divide + if( imlInstruction->operation == PPCREC_IML_OP_DIVIDE_SIGNED ) + x64Gen_idiv_reg64Low32(x64GenContext, REG_RESV_TEMP); + else + x64Gen_div_reg64Low32(x64GenContext, REG_RESV_TEMP); + // result of division is now stored in EAX, move it to result register + if( rRegResult != REG_EAX ) + x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, REG_EAX); + // restore EAX / EDX + if( rRegResult != REG_RAX ) + x64Emit_mov_reg64_mem32(x64GenContext, REG_EAX, REG_RSP, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[0])); + if( rRegResult != REG_RDX ) + x64Emit_mov_reg64_mem32(x64GenContext, REG_EDX, REG_RSP, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[1])); + // set cr bits if requested + if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) + { + if( imlInstruction->crMode != PPCREC_CR_MODE_ARITHMETIC ) + { + assert_dbg(); + } + x64Gen_test_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegResult); + PPCRecompilerX64Gen_updateCRLogical(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction); + } + } + else if( imlInstruction->operation == PPCREC_IML_OP_MULTIPLY_HIGH_SIGNED || imlInstruction->operation == PPCREC_IML_OP_MULTIPLY_HIGH_UNSIGNED ) + { + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + sint32 rRegResult = tempToRealRegister(imlInstruction->op_r_r_r.registerResult); + sint32 rRegOperand1 = tempToRealRegister(imlInstruction->op_r_r_r.registerA); + sint32 rRegOperand2 = tempToRealRegister(imlInstruction->op_r_r_r.registerB); + + x64Emit_mov_mem32_reg32(x64GenContext, REG_RSP, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[0]), REG_EAX); + x64Emit_mov_mem32_reg32(x64GenContext, REG_RSP, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[1]), REG_EDX); + // mov operand 2 to temp register + x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, rRegOperand2); + // mov operand1 to EAX + x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, REG_EAX, rRegOperand1); + if( imlInstruction->operation == PPCREC_IML_OP_MULTIPLY_HIGH_SIGNED ) + { + // zero extend EAX to EDX:EAX + x64Gen_xor_reg64Low32_reg64Low32(x64GenContext, REG_EDX, REG_EDX); + } + else + { + // sign extend EAX to EDX:EAX + x64Gen_cdq(x64GenContext); + } + // multiply + if( imlInstruction->operation == PPCREC_IML_OP_MULTIPLY_HIGH_SIGNED ) + x64Gen_imul_reg64Low32(x64GenContext, REG_RESV_TEMP); + else + x64Gen_mul_reg64Low32(x64GenContext, REG_RESV_TEMP); + // result of multiplication is now stored in EDX:EAX, move it to result register + if( rRegResult != REG_EDX ) + x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, REG_EDX); + // restore EAX / EDX + if( rRegResult != REG_RAX ) + x64Emit_mov_reg64_mem32(x64GenContext, REG_EAX, REG_RSP, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[0])); + if( rRegResult != REG_RDX ) + x64Emit_mov_reg64_mem32(x64GenContext, REG_EDX, REG_RSP, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[1])); + // set cr bits if requested + if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) + { + if( imlInstruction->crMode != PPCREC_CR_MODE_LOGICAL ) + { + assert_dbg(); + } + x64Gen_test_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegResult); + PPCRecompilerX64Gen_updateCRLogical(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction); + } + } + else if( imlInstruction->operation == PPCREC_IML_OP_ORC ) + { + // registerResult = registerOperand1 | ~registerOperand2 + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + sint32 rRegResult = tempToRealRegister(imlInstruction->op_r_r_r.registerResult); + sint32 rRegOperand1 = tempToRealRegister(imlInstruction->op_r_r_r.registerA); + sint32 rRegOperand2 = tempToRealRegister(imlInstruction->op_r_r_r.registerB); + + x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, rRegOperand2); + x64Gen_not_reg64Low32(x64GenContext, REG_RESV_TEMP); + if( rRegResult != rRegOperand1 ) + x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand1); + x64Gen_or_reg64Low32_reg64Low32(x64GenContext, rRegResult, REG_RESV_TEMP); + + // set cr bits if enabled + if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) + { + if( imlInstruction->crMode != PPCREC_CR_MODE_LOGICAL ) + { + assert_dbg(); + } + PPCRecompilerX64Gen_updateCRLogical(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction); + return true; + } + } + else + { + debug_printf("PPCRecompilerX64Gen_imlInstruction_r_r_r(): Unsupported operation 0x%x\n", imlInstruction->operation); + return false; + } + return true; +} + +bool PPCRecompilerX64Gen_imlInstruction_r_r_s32(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction) +{ + if( imlInstruction->operation == PPCREC_IML_OP_ADD ) + { + // registerResult = registerOperand + immS32 + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); + sint32 rRegResult = tempToRealRegister(imlInstruction->op_r_r_s32.registerResult); + sint32 rRegOperand = tempToRealRegister(imlInstruction->op_r_r_s32.registerA); + uint32 immU32 = (uint32)imlInstruction->op_r_r_s32.immS32; + if( rRegResult != rRegOperand ) + { + // copy value to destination register before doing addition + x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, rRegOperand); + } + x64Gen_add_reg64Low32_imm32(x64GenContext, rRegResult, (uint32)immU32); + } + else if( imlInstruction->operation == PPCREC_IML_OP_ADD_UPDATE_CARRY ) + { + // registerResult = registerOperand + immS32 + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + sint32 rRegResult = tempToRealRegister(imlInstruction->op_r_r_s32.registerResult); + sint32 rRegOperand = tempToRealRegister(imlInstruction->op_r_r_s32.registerA); + uint32 immU32 = (uint32)imlInstruction->op_r_r_s32.immS32; + if( rRegResult != rRegOperand ) + { + // copy value to destination register before doing addition + x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, rRegOperand); + } + x64Gen_add_reg64Low32_imm32(x64GenContext, rRegResult, (uint32)immU32); + // update carry flag + x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_CARRY, REG_RSP, offsetof(PPCInterpreter_t, xer_ca)); + // set cr bits if enabled + if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) + { + if( imlInstruction->crMode != PPCREC_CR_MODE_LOGICAL ) + { + assert_dbg(); + } + sint32 crRegister = imlInstruction->crRegister; + //x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_SIGN, REG_RSP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_LT)); + //x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_SIGNED_GREATER, REG_RSP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_GT)); + //x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_EQUAL, REG_RSP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_EQ)); + PPCRecompilerX64Gen_updateCRLogical(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction); + } + } + else if( imlInstruction->operation == PPCREC_IML_OP_SUBFC ) + { + // registerResult = immS32 - registerOperand + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); + sint32 rRegResult = tempToRealRegister(imlInstruction->op_r_r_s32.registerResult); + sint32 rRegOperand = tempToRealRegister(imlInstruction->op_r_r_s32.registerA); + sint32 immS32 = (sint32)imlInstruction->op_r_r_s32.immS32; + if( rRegResult != rRegOperand ) + { + // copy value to destination register before doing addition + x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, rRegOperand); + } + // set carry to zero + x64Gen_mov_mem8Reg64_imm8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, xer_ca), 0); + // ((~a+b)<~a) == true -> ca = 1 + x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, rRegOperand); + x64Gen_not_reg64Low32(x64GenContext, REG_RESV_TEMP); + x64Gen_add_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, (uint32)immS32); + x64Gen_not_reg64Low32(x64GenContext, rRegOperand); + x64Gen_cmp_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, rRegOperand); + x64Gen_not_reg64Low32(x64GenContext, rRegOperand); + sint32 jumpInstructionOffset1 = x64GenContext->codeBufferIndex; + x64Gen_jmpc_far(x64GenContext, X86_CONDITION_UNSIGNED_ABOVE_EQUAL, 0); + // reset carry flag + jump destination afterwards + x64Gen_mov_mem8Reg64_imm8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, xer_ca), 1); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset1, x64GenContext->codeBufferIndex); + // OR ((~a+b+1)<1) == true -> ca = 1 + x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, rRegOperand); + // todo: Optimize by reusing result in REG_RESV_TEMP from above and only add 1 + x64Gen_not_reg64Low32(x64GenContext, REG_RESV_TEMP); + x64Gen_add_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, (uint32)immS32); + x64Gen_add_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, 1); + x64Gen_cmp_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, 1); + sint32 jumpInstructionOffset2 = x64GenContext->codeBufferIndex; + x64Gen_jmpc_far(x64GenContext, X86_CONDITION_UNSIGNED_ABOVE_EQUAL, 0); + // reset carry flag + jump destination afterwards + x64Gen_mov_mem8Reg64_imm8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, xer_ca), 1); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset2, x64GenContext->codeBufferIndex); + // do actual computation of value, note: a - b is equivalent to a + ~b + 1 + x64Gen_not_reg64Low32(x64GenContext, rRegResult); + x64Gen_add_reg64Low32_imm32(x64GenContext, rRegResult, (uint32)immS32 + 1); + } + else if( imlInstruction->operation == PPCREC_IML_OP_RLWIMI ) + { + // registerResult = ((registerResult<<<SH)&mask) | (registerOperand&~mask) + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + uint32 vImm = (uint32)imlInstruction->op_r_r_s32.immS32; + uint32 mb = (vImm>>0)&0xFF; + uint32 me = (vImm>>8)&0xFF; + uint32 sh = (vImm>>16)&0xFF; + uint32 mask = ppc_mask(mb, me); + // save cr + cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); + // copy rS to temporary register + x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, tempToRealRegister(imlInstruction->op_r_r_s32.registerA)); + // rotate destination register + if( sh ) + x64Gen_rol_reg64Low32_imm8(x64GenContext, REG_RESV_TEMP, (uint8)sh&0x1F); + // AND destination register with inverted mask + x64Gen_and_reg64Low32_imm32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r_s32.registerResult), ~mask); + // AND temporary rS register with mask + x64Gen_and_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, mask); + // OR result with temporary + x64Gen_or_reg64Low32_reg64Low32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r_s32.registerResult), REG_RESV_TEMP); + } + else if( imlInstruction->operation == PPCREC_IML_OP_MULTIPLY_SIGNED ) + { + // registerResult = registerOperand * immS32 + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + sint32 rRegResult = tempToRealRegister(imlInstruction->op_r_r_s32.registerResult); + sint32 rRegOperand = tempToRealRegister(imlInstruction->op_r_r_s32.registerA); + sint32 immS32 = (uint32)imlInstruction->op_r_r_s32.immS32; + x64Gen_mov_reg64_imm64(x64GenContext, REG_RESV_TEMP, (sint64)immS32); // todo: Optimize + if( rRegResult != rRegOperand ) + x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, rRegOperand); + x64Gen_imul_reg64Low32_reg64Low32(x64GenContext, rRegResult, REG_RESV_TEMP); + } + else if( imlInstruction->operation == PPCREC_IML_OP_SRAW ) + { + // registerResult = registerOperand>>SH and set xer ca flag + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + uint32 sh = (uint32)imlInstruction->op_r_r_s32.immS32; + // MOV registerResult, registerOperand (if different) + if( imlInstruction->op_r_r_s32.registerA != imlInstruction->op_r_r_s32.registerResult ) + x64Gen_mov_reg64_reg64(x64GenContext, tempToRealRegister(imlInstruction->op_r_r_s32.registerResult), tempToRealRegister(imlInstruction->op_r_r_s32.registerA)); + // todo: Detect if we don't need to update carry + // generic case + // TEST registerResult, (1<<(SH+1))-1 + uint32 caTestMask = 0; + if (sh >= 31) + caTestMask = 0x7FFFFFFF; + else + caTestMask = (1 << (sh)) - 1; + x64Gen_test_reg64Low32_imm32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r_s32.registerResult), caTestMask); + // SETNE/NZ [ESP+XER_CA] + x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_NOT_EQUAL, REG_RSP, offsetof(PPCInterpreter_t, xer_ca)); + // SAR registerResult, SH + x64Gen_sar_reg64Low32_imm8(x64GenContext, tempToRealRegister(imlInstruction->op_r_r_s32.registerResult), sh); + // JNS <skipInstruction> (if sign not set) + sint32 jumpInstructionOffset = x64GenContext->codeBufferIndex; + x64Gen_jmpc_near(x64GenContext, X86_CONDITION_SIGN, 0); // todo: Can use 2-byte form of jump instruction here + // MOV BYTE [ESP+xer_ca], 0 + x64Gen_mov_mem8Reg64_imm8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, xer_ca), 0); + // jump destination + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset, x64GenContext->codeBufferIndex); + // CR update + if (imlInstruction->crRegister != PPC_REC_INVALID_REGISTER) + { + sint32 crRegister = imlInstruction->crRegister; + x64Gen_test_reg64Low32_reg64Low32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r_s32.registerResult), tempToRealRegister(imlInstruction->op_r_r_s32.registerResult)); + x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_SIGN, REG_RSP, offsetof(PPCInterpreter_t, cr) + sizeof(uint8)*(crRegister * 4 + PPCREC_CR_BIT_LT)); + x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_SIGNED_GREATER, REG_RSP, offsetof(PPCInterpreter_t, cr) + sizeof(uint8)*(crRegister * 4 + PPCREC_CR_BIT_GT)); + x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_EQUAL, REG_RSP, offsetof(PPCInterpreter_t, cr) + sizeof(uint8)*(crRegister * 4 + PPCREC_CR_BIT_EQ)); + } + } + else if( imlInstruction->operation == PPCREC_IML_OP_LEFT_SHIFT || + imlInstruction->operation == PPCREC_IML_OP_RIGHT_SHIFT ) + { + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + // MOV registerResult, registerOperand (if different) + if( imlInstruction->op_r_r_s32.registerA != imlInstruction->op_r_r_s32.registerResult ) + x64Gen_mov_reg64_reg64(x64GenContext, tempToRealRegister(imlInstruction->op_r_r_s32.registerResult), tempToRealRegister(imlInstruction->op_r_r_s32.registerA)); + // Shift + if( imlInstruction->operation == PPCREC_IML_OP_LEFT_SHIFT ) + x64Gen_shl_reg64Low32_imm8(x64GenContext, tempToRealRegister(imlInstruction->op_r_r_s32.registerResult), imlInstruction->op_r_r_s32.immS32); + else + x64Gen_shr_reg64Low32_imm8(x64GenContext, tempToRealRegister(imlInstruction->op_r_r_s32.registerResult), imlInstruction->op_r_r_s32.immS32); + // CR update + if (imlInstruction->crRegister != PPC_REC_INVALID_REGISTER) + { + // since SHL/SHR only modifies the OF flag we need another TEST reg,reg here + x64Gen_test_reg64Low32_reg64Low32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r_s32.registerResult), tempToRealRegister(imlInstruction->op_r_r_s32.registerResult)); + PPCRecompilerX64Gen_updateCRLogical(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction); + } + } + else + { + debug_printf("PPCRecompilerX64Gen_imlInstruction_r_r_s32(): Unsupported operation 0x%x\n", imlInstruction->operation); + return false; + } + return true; +} + +bool PPCRecompilerX64Gen_imlInstruction_conditionalJump(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlSegment_t* imlSegment, PPCRecImlInstruction_t* imlInstruction) +{ + if( imlInstruction->op_conditionalJump.condition == PPCREC_JUMP_CONDITION_NONE ) + { + // jump always + if (imlInstruction->op_conditionalJump.jumpAccordingToSegment) + { + // jump to segment + if (imlSegment->nextSegmentBranchTaken == nullptr) + assert_dbg(); + PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext, X64_RELOC_LINK_TO_SEGMENT, imlSegment->nextSegmentBranchTaken); + x64Gen_jmp_imm32(x64GenContext, 0); + } + else + { + // deprecated (jump to jumpmark) + PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext, X64_RELOC_LINK_TO_PPC, (void*)(size_t)imlInstruction->op_conditionalJump.jumpmarkAddress); + x64Gen_jmp_imm32(x64GenContext, 0); + } + } + else + { + if (imlInstruction->op_conditionalJump.jumpAccordingToSegment) + assert_dbg(); + // generate jump update marker + if( imlInstruction->op_conditionalJump.crRegisterIndex == PPCREC_CR_TEMPORARY || imlInstruction->op_conditionalJump.crRegisterIndex >= 8 ) + { + // temporary cr is used, which means we use the currently active eflags + PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext, X64_RELOC_LINK_TO_PPC, (void*)(size_t)imlInstruction->op_conditionalJump.jumpmarkAddress); + sint32 condition = imlInstruction->op_conditionalJump.condition; + if( condition == PPCREC_JUMP_CONDITION_E ) + x64Gen_jmpc_far(x64GenContext, X86_CONDITION_EQUAL, 0); + else if( condition == PPCREC_JUMP_CONDITION_NE ) + x64Gen_jmpc_far(x64GenContext, X86_CONDITION_NOT_EQUAL, 0); + else + assert_dbg(); + } + else + { + uint8 crBitIndex = imlInstruction->op_conditionalJump.crRegisterIndex*4 + imlInstruction->op_conditionalJump.crBitIndex; + if (imlInstruction->op_conditionalJump.crRegisterIndex == x64GenContext->activeCRRegister ) + { + if (x64GenContext->activeCRState == PPCREC_CR_STATE_TYPE_UNSIGNED_ARITHMETIC) + { + if (imlInstruction->op_conditionalJump.crBitIndex == CR_BIT_LT) + { + PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext, X64_RELOC_LINK_TO_PPC, (void*)(size_t)imlInstruction->op_conditionalJump.jumpmarkAddress); + x64Gen_jmpc_far(x64GenContext, imlInstruction->op_conditionalJump.bitMustBeSet ? X86_CONDITION_CARRY : X86_CONDITION_NOT_CARRY, 0); + return true; + } + else if (imlInstruction->op_conditionalJump.crBitIndex == CR_BIT_EQ) + { + PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext, X64_RELOC_LINK_TO_PPC, (void*)(size_t)imlInstruction->op_conditionalJump.jumpmarkAddress); + x64Gen_jmpc_far(x64GenContext, imlInstruction->op_conditionalJump.bitMustBeSet ? X86_CONDITION_EQUAL : X86_CONDITION_NOT_EQUAL, 0); + return true; + } + else if (imlInstruction->op_conditionalJump.crBitIndex == CR_BIT_GT) + { + PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext, X64_RELOC_LINK_TO_PPC, (void*)(size_t)imlInstruction->op_conditionalJump.jumpmarkAddress); + x64Gen_jmpc_far(x64GenContext, imlInstruction->op_conditionalJump.bitMustBeSet ? X86_CONDITION_UNSIGNED_ABOVE : X86_CONDITION_UNSIGNED_BELOW_EQUAL, 0); + return true; + } + } + else if (x64GenContext->activeCRState == PPCREC_CR_STATE_TYPE_SIGNED_ARITHMETIC) + { + if (imlInstruction->op_conditionalJump.crBitIndex == CR_BIT_LT) + { + PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext, X64_RELOC_LINK_TO_PPC, (void*)(size_t)imlInstruction->op_conditionalJump.jumpmarkAddress); + x64Gen_jmpc_far(x64GenContext, imlInstruction->op_conditionalJump.bitMustBeSet ? X86_CONDITION_SIGNED_LESS : X86_CONDITION_SIGNED_GREATER_EQUAL, 0); + return true; + } + else if (imlInstruction->op_conditionalJump.crBitIndex == CR_BIT_EQ) + { + PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext, X64_RELOC_LINK_TO_PPC, (void*)(size_t)imlInstruction->op_conditionalJump.jumpmarkAddress); + x64Gen_jmpc_far(x64GenContext, imlInstruction->op_conditionalJump.bitMustBeSet ? X86_CONDITION_EQUAL : X86_CONDITION_NOT_EQUAL, 0); + return true; + } + else if (imlInstruction->op_conditionalJump.crBitIndex == CR_BIT_GT) + { + PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext, X64_RELOC_LINK_TO_PPC, (void*)(size_t)imlInstruction->op_conditionalJump.jumpmarkAddress); + x64Gen_jmpc_far(x64GenContext, imlInstruction->op_conditionalJump.bitMustBeSet ? X86_CONDITION_SIGNED_GREATER : X86_CONDITION_SIGNED_LESS_EQUAL, 0); + return true; + } + } + else if (x64GenContext->activeCRState == PPCREC_CR_STATE_TYPE_LOGICAL) + { + if (imlInstruction->op_conditionalJump.crBitIndex == CR_BIT_LT) + { + PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext, X64_RELOC_LINK_TO_PPC, (void*)(size_t)imlInstruction->op_conditionalJump.jumpmarkAddress); + x64Gen_jmpc_far(x64GenContext, imlInstruction->op_conditionalJump.bitMustBeSet ? X86_CONDITION_SIGN : X86_CONDITION_NOT_SIGN, 0); + return true; + } + else if (imlInstruction->op_conditionalJump.crBitIndex == CR_BIT_EQ) + { + PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext, X64_RELOC_LINK_TO_PPC, (void*)(size_t)imlInstruction->op_conditionalJump.jumpmarkAddress); + x64Gen_jmpc_far(x64GenContext, imlInstruction->op_conditionalJump.bitMustBeSet ? X86_CONDITION_EQUAL : X86_CONDITION_NOT_EQUAL, 0); + return true; + } + else if (imlInstruction->op_conditionalJump.crBitIndex == CR_BIT_GT) + { + PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext, X64_RELOC_LINK_TO_PPC, (void*)(size_t)imlInstruction->op_conditionalJump.jumpmarkAddress); + x64Gen_jmpc_far(x64GenContext, imlInstruction->op_conditionalJump.bitMustBeSet ? X86_CONDITION_SIGNED_GREATER : X86_CONDITION_SIGNED_LESS_EQUAL, 0); + return true; + } + } + } + x64Gen_bt_mem8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, cr) + crBitIndex * sizeof(uint8), 0); + PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext, X64_RELOC_LINK_TO_PPC, (void*)(size_t)imlInstruction->op_conditionalJump.jumpmarkAddress); + if( imlInstruction->op_conditionalJump.bitMustBeSet ) + { + x64Gen_jmpc_far(x64GenContext, X86_CONDITION_CARRY, 0); + } + else + { + x64Gen_jmpc_far(x64GenContext, X86_CONDITION_NOT_CARRY, 0); + } + } + } + return true; +} + +bool PPCRecompilerX64Gen_imlInstruction_conditionalJumpCycleCheck(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction) +{ + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + // some tests (all performed on a i7-4790K) + // 1) DEC [mem] + JNS has significantly worse performance than BT + JNC (probably due to additional memory write) + // 2) CMP [mem], 0 + JG has about equal (or slightly worse) performance than BT + JNC + + // BT + x64Gen_bt_mem8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, remainingCycles), 31); // check if negative + PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext, X64_RELOC_LINK_TO_PPC, (void*)(size_t)imlInstruction->op_conditionalJump.jumpmarkAddress); + x64Gen_jmpc_far(x64GenContext, X86_CONDITION_NOT_CARRY, 0); + return true; +} + +/* +* PPC condition register operation +*/ +bool PPCRecompilerX64Gen_imlInstruction_cr(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction) +{ + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); // while these instruction do not directly affect eflags, they change the CR bit + if (imlInstruction->operation == PPCREC_IML_OP_CR_CLEAR) + { + // clear cr bit + x64Gen_mov_mem8Reg64_imm8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, cr) + sizeof(uint8)*imlInstruction->op_cr.crD, 0); + return true; + } + else if (imlInstruction->operation == PPCREC_IML_OP_CR_SET) + { + // set cr bit + x64Gen_mov_mem8Reg64_imm8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, cr) + sizeof(uint8)*imlInstruction->op_cr.crD, 1); + return true; + } + else if(imlInstruction->operation == PPCREC_IML_OP_CR_OR || imlInstruction->operation == PPCREC_IML_OP_CR_ORC || + imlInstruction->operation == PPCREC_IML_OP_CR_AND || imlInstruction->operation == PPCREC_IML_OP_CR_ANDC ) + { + x64Emit_movZX_reg64_mem8(x64GenContext, REG_RESV_TEMP, REG_RSP, offsetof(PPCInterpreter_t, cr) + sizeof(uint8)*imlInstruction->op_cr.crB); + if (imlInstruction->operation == PPCREC_IML_OP_CR_ORC || imlInstruction->operation == PPCREC_IML_OP_CR_ANDC) + { + return false; // untested + x64Gen_int3(x64GenContext); + x64Gen_xor_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, 1); // complement + } + if(imlInstruction->operation == PPCREC_IML_OP_CR_OR || imlInstruction->operation == PPCREC_IML_OP_CR_ORC) + x64Gen_or_reg64Low8_mem8Reg64(x64GenContext, REG_RESV_TEMP, REG_RSP, offsetof(PPCInterpreter_t, cr) + sizeof(uint8)*imlInstruction->op_cr.crA); + else + x64Gen_and_reg64Low8_mem8Reg64(x64GenContext, REG_RESV_TEMP, REG_RSP, offsetof(PPCInterpreter_t, cr) + sizeof(uint8)*imlInstruction->op_cr.crA); + + x64Gen_mov_mem8Reg64_reg64Low8(x64GenContext, REG_RESV_TEMP, REG_RSP, offsetof(PPCInterpreter_t, cr) + sizeof(uint8)*imlInstruction->op_cr.crD); + + return true; + } + else + { + assert_dbg(); + } + return false; +} + + +void PPCRecompilerX64Gen_imlInstruction_ppcEnter(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction) +{ + imlInstruction->op_ppcEnter.x64Offset = x64GenContext->codeBufferIndex; + // generate code + if( ppcImlGenContext->hasFPUInstruction ) + { + // old FPU unavailable code + //PPCRecompilerX86_crConditionFlags_saveBeforeOverwrite(PPCRecFunction, ppcImlGenContext, x64GenContext); + //// skip if FP bit in MSR is set + //// #define MSR_FP (1<<13) + //x64Gen_bt_mem8(x64GenContext, REG_ESP, offsetof(PPCInterpreter_t, msr), 13); + //uint32 jmpCodeOffset = x64GenContext->codeBufferIndex; + //x64Gen_jmpc(x64GenContext, X86_CONDITION_CARRY, 0); + //x64Gen_mov_reg32_imm32(x64GenContext, REG_EAX, imlInstruction->op_ppcEnter.ppcAddress&0x7FFFFFFF); + //PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext, X86_RELOC_MAKE_RELATIVE); + //x64Gen_jmp_imm32(x64GenContext, (uint32)PPCRecompiler_recompilerCallEscapeAndCallFPUUnavailable); + //// patch jump + //*(uint32*)(x64GenContext->codeBuffer+jmpCodeOffset+2) = x64GenContext->codeBufferIndex-jmpCodeOffset-6; + } +} + +void PPCRecompilerX64Gen_imlInstruction_r_name(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction) +{ + uint32 name = imlInstruction->op_r_name.name; + if( name >= PPCREC_NAME_R0 && name < PPCREC_NAME_R0+32 ) + { + x64Emit_mov_reg64_mem32(x64GenContext, tempToRealRegister(imlInstruction->op_r_name.registerIndex), REG_RSP, offsetof(PPCInterpreter_t, gpr)+sizeof(uint32)*(name-PPCREC_NAME_R0)); + } + else if( name >= PPCREC_NAME_SPR0 && name < PPCREC_NAME_SPR0+999 ) + { + sint32 sprIndex = (name - PPCREC_NAME_SPR0); + if (sprIndex == SPR_LR) + x64Emit_mov_reg64_mem32(x64GenContext, tempToRealRegister(imlInstruction->op_r_name.registerIndex), REG_RSP, offsetof(PPCInterpreter_t, spr.LR)); + else if (sprIndex == SPR_CTR) + x64Emit_mov_reg64_mem32(x64GenContext, tempToRealRegister(imlInstruction->op_r_name.registerIndex), REG_RSP, offsetof(PPCInterpreter_t, spr.CTR)); + else if (sprIndex == SPR_XER) + x64Emit_mov_reg64_mem32(x64GenContext, tempToRealRegister(imlInstruction->op_r_name.registerIndex), REG_RSP, offsetof(PPCInterpreter_t, spr.XER)); + else if (sprIndex >= SPR_UGQR0 && sprIndex <= SPR_UGQR7) + x64Emit_mov_reg64_mem32(x64GenContext, tempToRealRegister(imlInstruction->op_r_name.registerIndex), REG_RSP, offsetof(PPCInterpreter_t, spr.UGQR[sprIndex - SPR_UGQR0])); + else + assert_dbg(); + //x64Emit_mov_reg64_mem32(x64GenContext, tempToRealRegister(imlInstruction->op_r_name.registerIndex), REG_RSP, offsetof(PPCInterpreter_t, spr)+sizeof(uint32)*(name-PPCREC_NAME_SPR0)); + } + else + assert_dbg(); +} + +void PPCRecompilerX64Gen_imlInstruction_name_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction) +{ + uint32 name = imlInstruction->op_r_name.name; + if( name >= PPCREC_NAME_R0 && name < PPCREC_NAME_R0+32 ) + { + x64Emit_mov_mem32_reg64(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, gpr)+sizeof(uint32)*(name-PPCREC_NAME_R0), tempToRealRegister(imlInstruction->op_r_name.registerIndex)); + } + else if( name >= PPCREC_NAME_SPR0 && name < PPCREC_NAME_SPR0+999 ) + { + uint32 sprIndex = (name - PPCREC_NAME_SPR0); + if (sprIndex == SPR_LR) + x64Emit_mov_mem32_reg64(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, spr.LR), tempToRealRegister(imlInstruction->op_r_name.registerIndex)); + else if (sprIndex == SPR_CTR) + x64Emit_mov_mem32_reg64(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, spr.CTR), tempToRealRegister(imlInstruction->op_r_name.registerIndex)); + else if (sprIndex == SPR_XER) + x64Emit_mov_mem32_reg64(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, spr.XER), tempToRealRegister(imlInstruction->op_r_name.registerIndex)); + else if (sprIndex >= SPR_UGQR0 && sprIndex <= SPR_UGQR7) + x64Emit_mov_mem32_reg64(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, spr.UGQR[sprIndex-SPR_UGQR0]), tempToRealRegister(imlInstruction->op_r_name.registerIndex)); + else + assert_dbg(); + } + else + assert_dbg(); +} + +uint8* codeMemoryBlock = nullptr; +sint32 codeMemoryBlockIndex = 0; +sint32 codeMemoryBlockSize = 0; + +std::mutex mtx_allocExecutableMemory; + +uint8* PPCRecompilerX86_allocateExecutableMemory(sint32 size) +{ + std::lock_guard<std::mutex> lck(mtx_allocExecutableMemory); + if( codeMemoryBlockIndex+size > codeMemoryBlockSize ) + { + // allocate new block + codeMemoryBlockSize = std::max(1024*1024*4, size+1024); // 4MB (or more if the function is larger than 4MB) + codeMemoryBlockIndex = 0; + codeMemoryBlock = (uint8*)MemMapper::AllocateMemory(nullptr, codeMemoryBlockSize, MemMapper::PAGE_PERMISSION::P_RWX); + } + uint8* codeMem = codeMemoryBlock + codeMemoryBlockIndex; + codeMemoryBlockIndex += size; + // pad to 4 byte alignment + while (codeMemoryBlockIndex & 3) + { + codeMemoryBlock[codeMemoryBlockIndex] = 0x90; + codeMemoryBlockIndex++; + } + return codeMem; +} + +void PPCRecompiler_dumpIML(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext); + +bool PPCRecompiler_generateX64Code(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext) +{ + x64GenContext_t x64GenContext = {0}; + x64GenContext.codeBufferSize = 1024; + x64GenContext.codeBuffer = (uint8*)malloc(x64GenContext.codeBufferSize); + x64GenContext.codeBufferIndex = 0; + x64GenContext.activeCRRegister = PPC_REC_INVALID_REGISTER; + + // generate iml instruction code + bool codeGenerationFailed = false; + for(sint32 s=0; s<ppcImlGenContext->segmentListCount; s++) + { + PPCRecImlSegment_t* imlSegment = ppcImlGenContext->segmentList[s]; + ppcImlGenContext->segmentList[s]->x64Offset = x64GenContext.codeBufferIndex; + for(sint32 i=0; i<imlSegment->imlListCount; i++) + { + PPCRecImlInstruction_t* imlInstruction = imlSegment->imlList+i; + + if( imlInstruction->type == PPCREC_IML_TYPE_R_NAME ) + { + PPCRecompilerX64Gen_imlInstruction_r_name(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); + } + else if( imlInstruction->type == PPCREC_IML_TYPE_NAME_R ) + { + PPCRecompilerX64Gen_imlInstruction_name_r(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); + } + else if( imlInstruction->type == PPCREC_IML_TYPE_R_R ) + { + if( PPCRecompilerX64Gen_imlInstruction_r_r(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction) == false ) + { + codeGenerationFailed = true; + } + } + else if (imlInstruction->type == PPCREC_IML_TYPE_R_S32) + { + if (PPCRecompilerX64Gen_imlInstruction_r_s32(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction) == false) + { + codeGenerationFailed = true; + } + } + else if (imlInstruction->type == PPCREC_IML_TYPE_CONDITIONAL_R_S32) + { + if (PPCRecompilerX64Gen_imlInstruction_conditional_r_s32(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction) == false) + { + codeGenerationFailed = true; + } + } + else if( imlInstruction->type == PPCREC_IML_TYPE_R_R_S32 ) + { + if( PPCRecompilerX64Gen_imlInstruction_r_r_s32(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction) == false ) + { + codeGenerationFailed = true; + } + } + else if( imlInstruction->type == PPCREC_IML_TYPE_R_R_R ) + { + if( PPCRecompilerX64Gen_imlInstruction_r_r_r(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction) == false ) + { + codeGenerationFailed = true; + } + } + else if( imlInstruction->type == PPCREC_IML_TYPE_CJUMP ) + { + if( PPCRecompilerX64Gen_imlInstruction_conditionalJump(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlSegment, imlInstruction) == false ) + { + codeGenerationFailed = true; + } + } + else if( imlInstruction->type == PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK ) + { + PPCRecompilerX64Gen_imlInstruction_conditionalJumpCycleCheck(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); + } + else if( imlInstruction->type == PPCREC_IML_TYPE_MACRO ) + { + if( PPCRecompilerX64Gen_imlInstruction_macro(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction) == false ) + { + codeGenerationFailed = true; + } + } + else if( imlInstruction->type == PPCREC_IML_TYPE_LOAD ) + { + if( PPCRecompilerX64Gen_imlInstruction_load(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction, false) == false ) + { + codeGenerationFailed = true; + } + } + else if( imlInstruction->type == PPCREC_IML_TYPE_LOAD_INDEXED ) + { + if( PPCRecompilerX64Gen_imlInstruction_load(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction, true) == false ) + { + codeGenerationFailed = true; + } + } + else if( imlInstruction->type == PPCREC_IML_TYPE_STORE ) + { + if( PPCRecompilerX64Gen_imlInstruction_store(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction, false) == false ) + { + codeGenerationFailed = true; + } + } + else if( imlInstruction->type == PPCREC_IML_TYPE_STORE_INDEXED ) + { + if( PPCRecompilerX64Gen_imlInstruction_store(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction, true) == false ) + { + codeGenerationFailed = true; + } + } + else if (imlInstruction->type == PPCREC_IML_TYPE_MEM2MEM) + { + PPCRecompilerX64Gen_imlInstruction_mem2mem(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); + } + else if( imlInstruction->type == PPCREC_IML_TYPE_CR ) + { + if( PPCRecompilerX64Gen_imlInstruction_cr(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction) == false ) + { + codeGenerationFailed = true; + } + } + else if( imlInstruction->type == PPCREC_IML_TYPE_JUMPMARK ) + { + // no op + } + else if( imlInstruction->type == PPCREC_IML_TYPE_NO_OP ) + { + // no op + } + else if( imlInstruction->type == PPCREC_IML_TYPE_PPC_ENTER ) + { + PPCRecompilerX64Gen_imlInstruction_ppcEnter(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); + } + else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_R_NAME ) + { + PPCRecompilerX64Gen_imlInstruction_fpr_r_name(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); + } + else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_NAME_R ) + { + PPCRecompilerX64Gen_imlInstruction_fpr_name_r(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); + } + else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD ) + { + if( PPCRecompilerX64Gen_imlInstruction_fpr_load(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction, false) == false ) + { + codeGenerationFailed = true; + } + } + else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD_INDEXED ) + { + if( PPCRecompilerX64Gen_imlInstruction_fpr_load(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction, true) == false ) + { + codeGenerationFailed = true; + } + } + else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE ) + { + if( PPCRecompilerX64Gen_imlInstruction_fpr_store(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction, false) == false ) + { + codeGenerationFailed = true; + } + } + else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE_INDEXED ) + { + if( PPCRecompilerX64Gen_imlInstruction_fpr_store(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction, true) == false ) + { + codeGenerationFailed = true; + } + } + else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R ) + { + PPCRecompilerX64Gen_imlInstruction_fpr_r_r(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); + } + else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R_R ) + { + PPCRecompilerX64Gen_imlInstruction_fpr_r_r_r(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); + } + else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R_R_R ) + { + PPCRecompilerX64Gen_imlInstruction_fpr_r_r_r_r(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); + } + else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_R ) + { + PPCRecompilerX64Gen_imlInstruction_fpr_r(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); + } + else + { + debug_printf("PPCRecompiler_generateX64Code(): Unsupported iml type 0x%x\n", imlInstruction->type); + assert_dbg(); + } + } + } + // handle failed code generation + if( codeGenerationFailed ) + { + free(x64GenContext.codeBuffer); + if (x64GenContext.relocateOffsetTable) + free(x64GenContext.relocateOffsetTable); + return false; + } + // allocate executable memory + uint8* executableMemory = PPCRecompilerX86_allocateExecutableMemory(x64GenContext.codeBufferIndex); + size_t baseAddress = (size_t)executableMemory; + // fix relocs + for(sint32 i=0; i<x64GenContext.relocateOffsetTableCount; i++) + { + if( x64GenContext.relocateOffsetTable[i].type == X86_RELOC_MAKE_RELATIVE ) + { + assert_dbg(); // deprecated + } + else if(x64GenContext.relocateOffsetTable[i].type == X64_RELOC_LINK_TO_PPC || x64GenContext.relocateOffsetTable[i].type == X64_RELOC_LINK_TO_SEGMENT) + { + // if link to PPC, search for segment that starts with this offset + uint32 ppcOffset = (uint32)(size_t)x64GenContext.relocateOffsetTable[i].extraInfo; + uint32 x64Offset = 0xFFFFFFFF; + if (x64GenContext.relocateOffsetTable[i].type == X64_RELOC_LINK_TO_PPC) + { + for (sint32 s = 0; s < ppcImlGenContext->segmentListCount; s++) + { + if (ppcImlGenContext->segmentList[s]->isJumpDestination && ppcImlGenContext->segmentList[s]->jumpDestinationPPCAddress == ppcOffset) + { + x64Offset = ppcImlGenContext->segmentList[s]->x64Offset; + break; + } + } + if (x64Offset == 0xFFFFFFFF) + { + debug_printf("Recompiler could not resolve jump (function at 0x%08x)\n", PPCRecFunction->ppcAddress); + // todo: Cleanup + return false; + } + } + else + { + PPCRecImlSegment_t* destSegment = (PPCRecImlSegment_t*)x64GenContext.relocateOffsetTable[i].extraInfo; + x64Offset = destSegment->x64Offset; + } + uint32 relocBase = x64GenContext.relocateOffsetTable[i].offset; + uint8* relocInstruction = x64GenContext.codeBuffer+relocBase; + if( relocInstruction[0] == 0x0F && (relocInstruction[1] >= 0x80 && relocInstruction[1] <= 0x8F) ) + { + // Jcc relativeImm32 + sint32 distanceNearJump = (sint32)((baseAddress + x64Offset) - (baseAddress + relocBase + 2)); + if (distanceNearJump >= -128 && distanceNearJump < 127) // disabled + { + // convert to near Jcc + *(uint8*)(relocInstruction + 0) = (uint8)(relocInstruction[1]-0x80 + 0x70); + // patch offset + *(uint8*)(relocInstruction + 1) = (uint8)distanceNearJump; + // replace unused 4 bytes with NOP instruction + relocInstruction[2] = 0x0F; + relocInstruction[3] = 0x1F; + relocInstruction[4] = 0x40; + relocInstruction[5] = 0x00; + } + else + { + // patch offset + *(uint32*)(relocInstruction + 2) = (uint32)((baseAddress + x64Offset) - (baseAddress + relocBase + 6)); + } + } + else if( relocInstruction[0] == 0xE9 ) + { + // JMP relativeImm32 + *(uint32*)(relocInstruction+1) = (uint32)((baseAddress+x64Offset)-(baseAddress+relocBase+5)); + } + else + assert_dbg(); + } + else + { + assert_dbg(); + } + } + + // copy code to executable memory + memcpy(executableMemory, x64GenContext.codeBuffer, x64GenContext.codeBufferIndex); + free(x64GenContext.codeBuffer); + x64GenContext.codeBuffer = nullptr; + if (x64GenContext.relocateOffsetTable) + free(x64GenContext.relocateOffsetTable); + // set code + PPCRecFunction->x86Code = executableMemory; + PPCRecFunction->x86Size = x64GenContext.codeBufferIndex; + return true; +} + +void PPCRecompilerX64Gen_generateEnterRecompilerCode() +{ + x64GenContext_t x64GenContext = {0}; + x64GenContext.codeBufferSize = 1024; + x64GenContext.codeBuffer = (uint8*)malloc(x64GenContext.codeBufferSize); + x64GenContext.codeBufferIndex = 0; + x64GenContext.activeCRRegister = PPC_REC_INVALID_REGISTER; + + // start of recompiler entry function + x64Gen_push_reg64(&x64GenContext, REG_RAX); + x64Gen_push_reg64(&x64GenContext, REG_RCX); + x64Gen_push_reg64(&x64GenContext, REG_RDX); + x64Gen_push_reg64(&x64GenContext, REG_RBX); + x64Gen_push_reg64(&x64GenContext, REG_RBP); + x64Gen_push_reg64(&x64GenContext, REG_RDI); + x64Gen_push_reg64(&x64GenContext, REG_RSI); + x64Gen_push_reg64(&x64GenContext, REG_R8); + x64Gen_push_reg64(&x64GenContext, REG_R9); + x64Gen_push_reg64(&x64GenContext, REG_R10); + x64Gen_push_reg64(&x64GenContext, REG_R11); + x64Gen_push_reg64(&x64GenContext, REG_R12); + x64Gen_push_reg64(&x64GenContext, REG_R13); + x64Gen_push_reg64(&x64GenContext, REG_R14); + x64Gen_push_reg64(&x64GenContext, REG_R15); + + // 000000007775EF04 | E8 00 00 00 00 call +0x00 + x64Gen_writeU8(&x64GenContext, 0xE8); + x64Gen_writeU8(&x64GenContext, 0x00); + x64Gen_writeU8(&x64GenContext, 0x00); + x64Gen_writeU8(&x64GenContext, 0x00); + x64Gen_writeU8(&x64GenContext, 0x00); + //000000007775EF09 | 48 83 04 24 05 add qword ptr ss:[rsp],5 + x64Gen_writeU8(&x64GenContext, 0x48); + x64Gen_writeU8(&x64GenContext, 0x83); + x64Gen_writeU8(&x64GenContext, 0x04); + x64Gen_writeU8(&x64GenContext, 0x24); + uint32 jmpPatchOffset = x64GenContext.codeBufferIndex; + x64Gen_writeU8(&x64GenContext, 0); // skip the distance until after the JMP + x64Emit_mov_mem64_reg64(&x64GenContext, REG_RDX, offsetof(PPCInterpreter_t, rspTemp), REG_RSP); + + + // MOV RSP, RDX (ppc interpreter instance) + x64Gen_mov_reg64_reg64(&x64GenContext, REG_RSP, REG_RDX); + // MOV R15, ppcRecompilerInstanceData + x64Gen_mov_reg64_imm64(&x64GenContext, REG_R15, (uint64)ppcRecompilerInstanceData); + // MOV R13, memory_base + x64Gen_mov_reg64_imm64(&x64GenContext, REG_R13, (uint64)memory_base); + + //JMP recFunc + x64Gen_jmp_reg64(&x64GenContext, REG_RCX); // call argument 1 + + x64GenContext.codeBuffer[jmpPatchOffset] = (x64GenContext.codeBufferIndex-(jmpPatchOffset-4)); + + //recompilerExit1: + x64Gen_pop_reg64(&x64GenContext, REG_R15); + x64Gen_pop_reg64(&x64GenContext, REG_R14); + x64Gen_pop_reg64(&x64GenContext, REG_R13); + x64Gen_pop_reg64(&x64GenContext, REG_R12); + x64Gen_pop_reg64(&x64GenContext, REG_R11); + x64Gen_pop_reg64(&x64GenContext, REG_R10); + x64Gen_pop_reg64(&x64GenContext, REG_R9); + x64Gen_pop_reg64(&x64GenContext, REG_R8); + x64Gen_pop_reg64(&x64GenContext, REG_RSI); + x64Gen_pop_reg64(&x64GenContext, REG_RDI); + x64Gen_pop_reg64(&x64GenContext, REG_RBP); + x64Gen_pop_reg64(&x64GenContext, REG_RBX); + x64Gen_pop_reg64(&x64GenContext, REG_RDX); + x64Gen_pop_reg64(&x64GenContext, REG_RCX); + x64Gen_pop_reg64(&x64GenContext, REG_RAX); + // RET + x64Gen_ret(&x64GenContext); + + uint8* executableMemory = PPCRecompilerX86_allocateExecutableMemory(x64GenContext.codeBufferIndex); + // copy code to executable memory + memcpy(executableMemory, x64GenContext.codeBuffer, x64GenContext.codeBufferIndex); + free(x64GenContext.codeBuffer); + PPCRecompiler_enterRecompilerCode = (void ATTR_MS_ABI (*)(uint64,uint64))executableMemory; +} + + +void* PPCRecompilerX64Gen_generateLeaveRecompilerCode() +{ + x64GenContext_t x64GenContext = {0}; + x64GenContext.codeBufferSize = 128; + x64GenContext.codeBuffer = (uint8*)malloc(x64GenContext.codeBufferSize); + x64GenContext.codeBufferIndex = 0; + x64GenContext.activeCRRegister = PPC_REC_INVALID_REGISTER; + + // update instruction pointer + // LR is in EDX + x64Emit_mov_mem32_reg32(&x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, instructionPointer), REG_EDX); + + // MOV RSP, [ppcRecompilerX64_rspTemp] + x64Emit_mov_reg64_mem64(&x64GenContext, REG_RSP, REG_RESV_HCPU, offsetof(PPCInterpreter_t, rspTemp)); + + // RET + x64Gen_ret(&x64GenContext); + + uint8* executableMemory = PPCRecompilerX86_allocateExecutableMemory(x64GenContext.codeBufferIndex); + // copy code to executable memory + memcpy(executableMemory, x64GenContext.codeBuffer, x64GenContext.codeBufferIndex); + free(x64GenContext.codeBuffer); + return executableMemory; +} + +void PPCRecompilerX64Gen_generateRecompilerInterfaceFunctions() +{ + PPCRecompilerX64Gen_generateEnterRecompilerCode(); + PPCRecompiler_leaveRecompilerCode_unvisited = (void ATTR_MS_ABI (*)())PPCRecompilerX64Gen_generateLeaveRecompilerCode(); + PPCRecompiler_leaveRecompilerCode_visited = (void ATTR_MS_ABI (*)())PPCRecompilerX64Gen_generateLeaveRecompilerCode(); + cemu_assert_debug(PPCRecompiler_leaveRecompilerCode_unvisited != PPCRecompiler_leaveRecompilerCode_visited); +} \ No newline at end of file diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64.h b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64.h new file mode 100644 index 00000000..1d37a77e --- /dev/null +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64.h @@ -0,0 +1,332 @@ + +typedef struct +{ + uint32 offset; + uint8 type; + void* extraInfo; +}x64RelocEntry_t; + +typedef struct +{ + uint8* codeBuffer; + sint32 codeBufferIndex; + sint32 codeBufferSize; + // cr state + sint32 activeCRRegister; // current x86 condition flags reflect this cr* register + sint32 activeCRState; // describes the way in which x86 flags map to the cr register (signed / unsigned) + // relocate offsets + x64RelocEntry_t* relocateOffsetTable; + sint32 relocateOffsetTableSize; + sint32 relocateOffsetTableCount; +}x64GenContext_t; + +// Some of these are defined by winnt.h and gnu headers +#undef REG_EAX +#undef REG_ECX +#undef REG_EDX +#undef REG_EBX +#undef REG_ESP +#undef REG_EBP +#undef REG_ESI +#undef REG_EDI +#undef REG_NONE +#undef REG_RAX +#undef REG_RCX +#undef REG_RDX +#undef REG_RBX +#undef REG_RSP +#undef REG_RBP +#undef REG_RSI +#undef REG_RDI +#undef REG_R8 +#undef REG_R9 +#undef REG_R10 +#undef REG_R11 +#undef REG_R12 +#undef REG_R13 +#undef REG_R14 +#undef REG_R15 + +#define REG_EAX 0 +#define REG_ECX 1 +#define REG_EDX 2 +#define REG_EBX 3 +#define REG_ESP 4 // reserved for low half of hCPU pointer +#define REG_EBP 5 +#define REG_ESI 6 +#define REG_EDI 7 +#define REG_NONE -1 + +#define REG_RAX 0 +#define REG_RCX 1 +#define REG_RDX 2 +#define REG_RBX 3 +#define REG_RSP 4 // reserved for hCPU pointer +#define REG_RBP 5 +#define REG_RSI 6 +#define REG_RDI 7 +#define REG_R8 8 +#define REG_R9 9 +#define REG_R10 10 +#define REG_R11 11 +#define REG_R12 12 +#define REG_R13 13 // reserved to hold pointer to memory base? (Not decided yet) +#define REG_R14 14 // reserved as temporary register +#define REG_R15 15 // reserved for pointer to ppcRecompilerInstanceData + +#define REG_AL 0 +#define REG_CL 1 +#define REG_DL 2 +#define REG_BL 3 +#define REG_AH 4 +#define REG_CH 5 +#define REG_DH 6 +#define REG_BH 7 + +// reserved registers +#define REG_RESV_TEMP (REG_R14) +#define REG_RESV_HCPU (REG_RSP) +#define REG_RESV_MEMBASE (REG_R13) +#define REG_RESV_RECDATA (REG_R15) + +// reserved floating-point registers +#define REG_RESV_FPR_TEMP (15) + + +extern sint32 x64Gen_registerMap[12]; + +#define tempToRealRegister(__x) (x64Gen_registerMap[__x]) +#define tempToRealFPRRegister(__x) (__x) +#define reg32ToReg16(__x) (__x) + +enum +{ + X86_CONDITION_EQUAL, // or zero + X86_CONDITION_NOT_EQUAL, // or not zero + X86_CONDITION_SIGNED_LESS, // or not greater/equal + X86_CONDITION_SIGNED_GREATER, // or not less/equal + X86_CONDITION_SIGNED_LESS_EQUAL, // or not greater + X86_CONDITION_SIGNED_GREATER_EQUAL, // or not less + X86_CONDITION_UNSIGNED_BELOW, // or not above/equal + X86_CONDITION_UNSIGNED_ABOVE, // or not below/equal + X86_CONDITION_UNSIGNED_BELOW_EQUAL, // or not above + X86_CONDITION_UNSIGNED_ABOVE_EQUAL, // or not below + X86_CONDITION_CARRY, // carry flag must be set + X86_CONDITION_NOT_CARRY, // carry flag must not be set + X86_CONDITION_SIGN, // sign flag must be set + X86_CONDITION_NOT_SIGN, // sign flag must not be set + X86_CONDITION_PARITY, // parity flag must be set + X86_CONDITION_NONE, // no condition, jump always +}; + +#define PPCREC_CR_TEMPORARY (8) // never stored +#define PPCREC_CR_STATE_TYPE_UNSIGNED_ARITHMETIC (0) // for signed arithmetic operations (ADD, CMPI) +#define PPCREC_CR_STATE_TYPE_SIGNED_ARITHMETIC (1) // for unsigned arithmetic operations (ADD, CMPI) +#define PPCREC_CR_STATE_TYPE_LOGICAL (2) // for unsigned operations (CMPLI) + +#define X86_RELOC_MAKE_RELATIVE (0) // make code imm relative to instruction +#define X64_RELOC_LINK_TO_PPC (1) // translate from ppc address to x86 offset +#define X64_RELOC_LINK_TO_SEGMENT (2) // link to beginning of segment + +#define PPC_X64_GPR_USABLE_REGISTERS (16-4) +#define PPC_X64_FPR_USABLE_REGISTERS (16-1) // Use XMM0 - XMM14, XMM15 is the temp register + + +bool PPCRecompiler_generateX64Code(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext); + +void PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext); + +void PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext_t* x64GenContext, sint32 jumpInstructionOffset, sint32 destinationOffset); + +void PPCRecompilerX64Gen_generateRecompilerInterfaceFunctions(); + +void PPCRecompilerX64Gen_imlInstruction_fpr_r_name(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction); +void PPCRecompilerX64Gen_imlInstruction_fpr_name_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction); +bool PPCRecompilerX64Gen_imlInstruction_fpr_load(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction, bool indexed); +bool PPCRecompilerX64Gen_imlInstruction_fpr_store(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction, bool indexed); + +void PPCRecompilerX64Gen_imlInstruction_fpr_r_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction); +void PPCRecompilerX64Gen_imlInstruction_fpr_r_r_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction); +void PPCRecompilerX64Gen_imlInstruction_fpr_r_r_r_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction); +void PPCRecompilerX64Gen_imlInstruction_fpr_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction); + +// ASM gen +void x64Gen_writeU8(x64GenContext_t* x64GenContext, uint8 v); +void x64Gen_writeU16(x64GenContext_t* x64GenContext, uint32 v); +void x64Gen_writeU32(x64GenContext_t* x64GenContext, uint32 v); + +void x64Emit_mov_reg32_mem32(x64GenContext_t* x64GenContext, sint32 destReg, sint32 memBaseReg64, sint32 memOffset); +void x64Emit_mov_mem32_reg32(x64GenContext_t* x64GenContext, sint32 memBaseReg64, sint32 memOffset, sint32 srcReg); +void x64Emit_mov_mem64_reg64(x64GenContext_t* x64GenContext, sint32 memBaseReg64, sint32 memOffset, sint32 srcReg); +void x64Emit_mov_reg64_mem64(x64GenContext_t* x64GenContext, sint32 destReg, sint32 memBaseReg64, sint32 memOffset); +void x64Emit_mov_reg64_mem32(x64GenContext_t* x64GenContext, sint32 destReg, sint32 memBaseReg64, sint32 memOffset); +void x64Emit_mov_mem32_reg64(x64GenContext_t* x64GenContext, sint32 memBaseReg64, sint32 memOffset, sint32 srcReg); +void x64Emit_mov_reg64_mem64(x64GenContext_t* x64GenContext, sint32 destReg, sint32 memBaseReg64, sint32 memIndexReg64, sint32 memOffset); +void x64Emit_mov_reg32_mem32(x64GenContext_t* x64GenContext, sint32 destReg, sint32 memBaseReg64, sint32 memIndexReg64, sint32 memOffset); +void x64Emit_mov_reg64b_mem8(x64GenContext_t* x64GenContext, sint32 destReg, sint32 memBaseReg64, sint32 memIndexReg64, sint32 memOffset); +void x64Emit_movZX_reg32_mem8(x64GenContext_t* x64GenContext, sint32 destReg, sint32 memBaseReg64, sint32 memIndexReg64, sint32 memOffset); +void x64Emit_movZX_reg64_mem8(x64GenContext_t* x64GenContext, sint32 destReg, sint32 memBaseReg64, sint32 memOffset); + +void x64Gen_movSignExtend_reg64Low32_mem8Reg64PlusReg64(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32); + +void x64Gen_movZeroExtend_reg64Low16_mem16Reg64PlusReg64(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32); +void x64Gen_mov_mem64Reg64PlusReg64_reg64(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32); +void x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext_t* x64GenContext, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32, sint32 srcRegister); +void x64Gen_movTruncate_mem16Reg64PlusReg64_reg64(x64GenContext_t* x64GenContext, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32, sint32 srcRegister); +void x64Gen_movTruncate_mem8Reg64PlusReg64_reg64(x64GenContext_t* x64GenContext, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32, sint32 srcRegister); +void x64Gen_mov_mem32Reg64_imm32(x64GenContext_t* x64GenContext, sint32 memRegister, uint32 memImmU32, uint32 dataImmU32); +void x64Gen_mov_mem64Reg64_imm32(x64GenContext_t* x64GenContext, sint32 memRegister, uint32 memImmU32, uint32 dataImmU32); +void x64Gen_mov_mem8Reg64_imm8(x64GenContext_t* x64GenContext, sint32 memRegister, uint32 memImmU32, uint8 dataImmU8); + +void x64Gen_mov_reg64_imm64(x64GenContext_t* x64GenContext, sint32 destRegister, uint64 immU64); +void x64Gen_mov_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 destRegister, uint64 immU32); +void x64Gen_mov_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister); + +void x64Gen_lea_reg64Low32_reg64Low32PlusReg64Low32(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 memRegisterA64, sint32 memRegisterB64); + +void x64Gen_cmovcc_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, uint32 conditionType, sint32 destRegister, sint32 srcRegister); +void x64Gen_mov_reg64_reg64(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister); +void x64Gen_xchg_reg64_reg64(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister); +void x64Gen_movSignExtend_reg64Low32_reg64Low16(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister); +void x64Gen_movZeroExtend_reg64Low32_reg64Low16(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister); +void x64Gen_movSignExtend_reg64Low32_reg64Low8(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister); +void x64Gen_movZeroExtend_reg64Low32_reg64Low8(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister); + +void x64Gen_or_reg64Low8_mem8Reg64(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 memRegister64, sint32 memImmS32); +void x64Gen_and_reg64Low8_mem8Reg64(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 memRegister64, sint32 memImmS32); +void x64Gen_mov_mem8Reg64_reg64Low8(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 memRegister64, sint32 memImmS32); + +void x64Gen_lock_cmpxchg_mem32Reg64PlusReg64_reg64(x64GenContext_t* x64GenContext, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32, sint32 srcRegister); +void x64Gen_lock_cmpxchg_mem32Reg64_reg64(x64GenContext_t* x64GenContext, sint32 memRegister64, sint32 memImmS32, sint32 srcRegister); + +void x64Gen_add_reg64_reg64(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister); +void x64Gen_add_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister); +void x64Gen_add_reg64_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, uint32 immU32); +void x64Gen_add_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, uint32 immU32); +void x64Gen_sub_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister); +void x64Gen_sub_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, uint32 immU32); +void x64Gen_sub_reg64_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, uint32 immU32); +void x64Gen_sub_mem32reg64_imm32(x64GenContext_t* x64GenContext, sint32 memRegister, sint32 memImmS32, uint64 immU32); +void x64Gen_sbb_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister); +void x64Gen_adc_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister); +void x64Gen_adc_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, uint32 immU32); +void x64Gen_dec_mem32(x64GenContext_t* x64GenContext, sint32 memoryRegister, uint32 memoryImmU32); +void x64Gen_imul_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 operandRegister); +void x64Gen_idiv_reg64Low32(x64GenContext_t* x64GenContext, sint32 operandRegister); +void x64Gen_div_reg64Low32(x64GenContext_t* x64GenContext, sint32 operandRegister); +void x64Gen_imul_reg64Low32(x64GenContext_t* x64GenContext, sint32 operandRegister); +void x64Gen_mul_reg64Low32(x64GenContext_t* x64GenContext, sint32 operandRegister); +void x64Gen_and_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, uint32 immU32); +void x64Gen_and_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister); +void x64Gen_test_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister); +void x64Gen_test_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, uint32 immU32); +void x64Gen_cmp_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, sint32 immS32); +void x64Gen_cmp_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister); +void x64Gen_cmp_reg64Low32_mem32reg64(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 memRegister, sint32 memImmS32); +void x64Gen_or_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, uint32 immU32); +void x64Gen_or_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister); +void x64Gen_xor_reg32_reg32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister); +void x64Gen_xor_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister); +void x64Gen_xor_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, uint32 immU32); + +void x64Gen_rol_reg64Low32_imm8(x64GenContext_t* x64GenContext, sint32 srcRegister, sint8 immS8); +void x64Gen_rol_reg64Low32_cl(x64GenContext_t* x64GenContext, sint32 srcRegister); +void x64Gen_rol_reg64Low16_imm8(x64GenContext_t* x64GenContext, sint32 srcRegister, sint8 immS8); +void x64Gen_rol_reg64_imm8(x64GenContext_t* x64GenContext, sint32 srcRegister, sint8 immS8); +void x64Gen_shl_reg64Low32_imm8(x64GenContext_t* x64GenContext, sint32 srcRegister, sint8 immS8); +void x64Gen_shr_reg64Low32_imm8(x64GenContext_t* x64GenContext, sint32 srcRegister, sint8 immS8); +void x64Gen_sar_reg64Low32_imm8(x64GenContext_t* x64GenContext, sint32 srcRegister, sint8 immS8); + +void x64Gen_not_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister); +void x64Gen_neg_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister); +void x64Gen_cdq(x64GenContext_t* x64GenContext); + +void x64Gen_bswap_reg64(x64GenContext_t* x64GenContext, sint32 destRegister); +void x64Gen_bswap_reg64Lower32bit(x64GenContext_t* x64GenContext, sint32 destRegister); +void x64Gen_bswap_reg64Lower16bit(x64GenContext_t* x64GenContext, sint32 destRegister); + +void x64Gen_lzcnt_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister); +void x64Gen_bsr_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister); +void x64Gen_cmp_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, sint32 immS32); +void x64Gen_setcc_mem8(x64GenContext_t* x64GenContext, sint32 conditionType, sint32 memoryRegister, uint32 memoryImmU32); +void x64Gen_setcc_reg64b(x64GenContext_t* x64GenContext, sint32 conditionType, sint32 dataRegister); +void x64Gen_bt_mem8(x64GenContext_t* x64GenContext, sint32 memoryRegister, uint32 memoryImmU32, uint8 bitIndex); +void x64Gen_cmc(x64GenContext_t* x64GenContext); + +void x64Gen_jmp_imm32(x64GenContext_t* x64GenContext, uint32 destImm32); +void x64Gen_jmp_memReg64(x64GenContext_t* x64GenContext, sint32 memRegister, uint32 immU32); +void x64Gen_jmpc_far(x64GenContext_t* x64GenContext, sint32 conditionType, sint32 relativeDest); +void x64Gen_jmpc_near(x64GenContext_t* x64GenContext, sint32 conditionType, sint32 relativeDest); + +void x64Gen_push_reg64(x64GenContext_t* x64GenContext, sint32 srcRegister); +void x64Gen_pop_reg64(x64GenContext_t* x64GenContext, sint32 destRegister); +void x64Gen_jmp_reg64(x64GenContext_t* x64GenContext, sint32 srcRegister); +void x64Gen_call_reg64(x64GenContext_t* x64GenContext, sint32 srcRegister); +void x64Gen_ret(x64GenContext_t* x64GenContext); +void x64Gen_int3(x64GenContext_t* x64GenContext); + +// floating-point (SIMD/SSE) gen +void x64Gen_movaps_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSource); +void x64Gen_movupd_xmmReg_memReg128(x64GenContext_t* x64GenContext, sint32 xmmRegister, sint32 memRegister, uint32 memImmU32); +void x64Gen_movupd_memReg128_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegister, sint32 memRegister, uint32 memImmU32); +void x64Gen_movddup_xmmReg_memReg64(x64GenContext_t* x64GenContext, sint32 xmmRegister, sint32 memRegister, uint32 memImmU32); +void x64Gen_movddup_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); +void x64Gen_movhlps_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); +void x64Gen_movsd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); +void x64Gen_movsd_memReg64_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegister, sint32 memRegister, uint32 memImmU32); +void x64Gen_movlpd_xmmReg_memReg64(x64GenContext_t* x64GenContext, sint32 xmmRegister, sint32 memRegister, uint32 memImmU32); +void x64Gen_unpcklpd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); +void x64Gen_unpckhpd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); +void x64Gen_shufpd_xmmReg_xmmReg_imm8(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc, uint8 imm8); +void x64Gen_addsd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); +void x64Gen_addpd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); +void x64Gen_subsd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); +void x64Gen_subpd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); +void x64Gen_mulsd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); +void x64Gen_mulpd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); +void x64Gen_mulpd_xmmReg_memReg128(x64GenContext_t* x64GenContext, sint32 xmmRegister, sint32 memRegister, uint32 memImmU32); +void x64Gen_divsd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); +void x64Gen_divpd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); +void x64Gen_comisd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); +void x64Gen_comisd_xmmReg_mem64Reg64(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 memoryReg, sint32 memImmS32); +void x64Gen_ucomisd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); +void x64Gen_comiss_xmmReg_mem64Reg64(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 memoryReg, sint32 memImmS32); +void x64Gen_orps_xmmReg_mem128Reg64(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, uint32 memReg, uint32 memImmS32); +void x64Gen_xorps_xmmReg_mem128Reg64(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, uint32 memReg, uint32 memImmS32); +void x64Gen_andps_xmmReg_mem128Reg64(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, uint32 memReg, uint32 memImmS32); +void x64Gen_andpd_xmmReg_memReg128(x64GenContext_t* x64GenContext, sint32 xmmRegister, sint32 memRegister, uint32 memImmU32); +void x64Gen_andps_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); +void x64Gen_pcmpeqd_xmmReg_mem128Reg64(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, uint32 memReg, uint32 memImmS32); +void x64Gen_cvttpd2dq_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); +void x64Gen_cvttsd2si_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 registerDest, sint32 xmmRegisterSrc); +void x64Gen_cvtsd2ss_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); +void x64Gen_cvtpd2ps_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); +void x64Gen_cvtss2sd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); +void x64Gen_cvtps2pd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); +void x64Gen_cvtpi2pd_xmmReg_mem64Reg64(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 memReg, sint32 memImmS32); +void x64Gen_cvtsd2si_reg64Low_xmmReg(x64GenContext_t* x64GenContext, sint32 registerDest, sint32 xmmRegisterSrc); +void x64Gen_cvttsd2si_reg64Low_xmmReg(x64GenContext_t* x64GenContext, sint32 registerDest, sint32 xmmRegisterSrc); +void x64Gen_sqrtsd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); +void x64Gen_sqrtpd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); +void x64Gen_rcpss_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); +void x64Gen_mulss_xmmReg_memReg64(x64GenContext_t* x64GenContext, sint32 xmmRegister, sint32 memRegister, uint32 memImmU32); + +void x64Gen_movd_xmmReg_reg64Low32(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 registerSrc); +void x64Gen_movd_reg64Low32_xmmReg(x64GenContext_t* x64GenContext, sint32 registerDest, sint32 xmmRegisterSrc); +void x64Gen_movq_xmmReg_reg64(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 registerSrc); +void x64Gen_movq_reg64_xmmReg(x64GenContext_t* x64GenContext, sint32 registerDst, sint32 xmmRegisterSrc); + +// AVX + +void x64Gen_avx_VPUNPCKHQDQ_xmm_xmm_xmm(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 srcRegisterA, sint32 srcRegisterB); +void x64Gen_avx_VUNPCKHPD_xmm_xmm_xmm(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 srcRegisterA, sint32 srcRegisterB); +void x64Gen_avx_VSUBPD_xmm_xmm_xmm(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 srcRegisterA, sint32 srcRegisterB); + +// BMI +void x64Gen_movBEZeroExtend_reg64_mem32Reg64PlusReg64(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32); +void x64Gen_movBEZeroExtend_reg64Low16_mem16Reg64PlusReg64(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32); + +void x64Gen_movBETruncate_mem32Reg64PlusReg64_reg64(x64GenContext_t* x64GenContext, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32, sint32 srcRegister); + +void x64Gen_shrx_reg64_reg64_reg64(x64GenContext_t* x64GenContext, sint32 registerDst, sint32 registerA, sint32 registerB); +void x64Gen_shlx_reg64_reg64_reg64(x64GenContext_t* x64GenContext, sint32 registerDst, sint32 registerA, sint32 registerB); \ No newline at end of file diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64AVX.cpp b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64AVX.cpp new file mode 100644 index 00000000..619c3985 --- /dev/null +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64AVX.cpp @@ -0,0 +1,49 @@ +#include "PPCRecompiler.h" +#include "PPCRecompilerX64.h" + +void _x64Gen_writeMODRMDeprecated(x64GenContext_t* x64GenContext, sint32 dataRegister, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32); + +void _x64Gen_vex128_nds(x64GenContext_t* x64GenContext, uint8 opcodeMap, uint8 additionalOperand, uint8 pp, uint8 vex_ext, uint8 vex_r, uint8 vex_b, uint8 opcode) +{ + if(vex_b != 0) + x64Gen_writeU8(x64GenContext, 0xC4); // three byte VEX + else + x64Gen_writeU8(x64GenContext, 0xC5); // two byte VEX + + if (vex_b != 0) + { + uint8 vex_x = 0; + x64Gen_writeU8(x64GenContext, (vex_r ? 0x00 : 0x80) | (vex_x ? 0x00 : 0x40) | (vex_b ? 0x00 : 0x20) | 1); + } + + x64Gen_writeU8(x64GenContext, (vex_ext<<7) | (((~additionalOperand)&0xF)<<3) | pp); + + x64Gen_writeU8(x64GenContext, opcode); +} + +#define VEX_PP_0F 0 // guessed +#define VEX_PP_66_0F 1 +#define VEX_PP_F3_0F 2 // guessed +#define VEX_PP_F2_0F 3 // guessed + + +void x64Gen_avx_VPUNPCKHQDQ_xmm_xmm_xmm(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 srcRegisterA, sint32 srcRegisterB) +{ + _x64Gen_vex128_nds(x64GenContext, 0, srcRegisterA, VEX_PP_66_0F, dstRegister < 8 ? 1 : 0, (dstRegister >= 8 && srcRegisterB >= 8) ? 1 : 0, srcRegisterB < 8 ? 0 : 1, 0x6D); + + x64Gen_writeU8(x64GenContext, 0xC0 + (srcRegisterB & 7) + (dstRegister & 7) * 8); +} + +void x64Gen_avx_VUNPCKHPD_xmm_xmm_xmm(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 srcRegisterA, sint32 srcRegisterB) +{ + _x64Gen_vex128_nds(x64GenContext, 0, srcRegisterA, VEX_PP_66_0F, dstRegister < 8 ? 1 : 0, (dstRegister >= 8 && srcRegisterB >= 8) ? 1 : 0, srcRegisterB < 8 ? 0 : 1, 0x15); + + x64Gen_writeU8(x64GenContext, 0xC0 + (srcRegisterB & 7) + (dstRegister & 7) * 8); +} + +void x64Gen_avx_VSUBPD_xmm_xmm_xmm(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 srcRegisterA, sint32 srcRegisterB) +{ + _x64Gen_vex128_nds(x64GenContext, 0, srcRegisterA, VEX_PP_66_0F, dstRegister < 8 ? 1 : 0, (dstRegister >= 8 && srcRegisterB >= 8) ? 1 : 0, srcRegisterB < 8 ? 0 : 1, 0x5C); + + x64Gen_writeU8(x64GenContext, 0xC0 + (srcRegisterB & 7) + (dstRegister & 7) * 8); +} \ No newline at end of file diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64BMI.cpp b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64BMI.cpp new file mode 100644 index 00000000..5a71e93d --- /dev/null +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64BMI.cpp @@ -0,0 +1,80 @@ +#include "PPCRecompiler.h" +#include "PPCRecompilerX64.h" + +void _x64Gen_writeMODRMDeprecated(x64GenContext_t* x64GenContext, sint32 dataRegister, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32); + +void x64Gen_movBEZeroExtend_reg64_mem32Reg64PlusReg64(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32) +{ + // MOVBE <dstReg64> (low dword), DWORD [<reg64> + <reg64> + <imm64>] + if( dstRegister >= 8 && memRegisterA64 >= 8 && memRegisterB64 >= 8 ) + x64Gen_writeU8(x64GenContext, 0x47); + else if( memRegisterA64 >= 8 && memRegisterB64 >= 8 ) + x64Gen_writeU8(x64GenContext, 0x43); + else if( dstRegister >= 8 && memRegisterB64 >= 8 ) + x64Gen_writeU8(x64GenContext, 0x42); + else if( dstRegister >= 8 && memRegisterA64 >= 8 ) + x64Gen_writeU8(x64GenContext, 0x45); + else if( dstRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x44); + else if( memRegisterA64 >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + else if( memRegisterB64 >= 8 ) + x64Gen_writeU8(x64GenContext, 0x42); + + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x38); + x64Gen_writeU8(x64GenContext, 0xF0); + _x64Gen_writeMODRMDeprecated(x64GenContext, dstRegister, memRegisterA64, memRegisterB64, memImmS32); +} + +void x64Gen_movBEZeroExtend_reg64Low16_mem16Reg64PlusReg64(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32) +{ + // MOVBE <dstReg64> (low word), WORD [<reg64> + <reg64> + <imm64>] + // note: Unlike the 32bit version this instruction does not set the upper 32bits of the 64bit register to 0 + x64Gen_writeU8(x64GenContext, 0x66); // 16bit prefix + x64Gen_movBEZeroExtend_reg64_mem32Reg64PlusReg64(x64GenContext, dstRegister, memRegisterA64, memRegisterB64, memImmS32); +} + +void x64Gen_movBETruncate_mem32Reg64PlusReg64_reg64(x64GenContext_t* x64GenContext, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32, sint32 srcRegister) +{ + // MOVBE DWORD [<reg64> + <reg64> + <imm64>], <srcReg64> (low dword) + if( srcRegister >= 8 && memRegisterA64 >= 8 && memRegisterB64 >= 8 ) + x64Gen_writeU8(x64GenContext, 0x47); + else if( memRegisterA64 >= 8 && memRegisterB64 >= 8 ) + x64Gen_writeU8(x64GenContext, 0x43); + else if( srcRegister >= 8 && memRegisterB64 >= 8 ) + x64Gen_writeU8(x64GenContext, 0x42); + else if( srcRegister >= 8 && memRegisterA64 >= 8 ) + x64Gen_writeU8(x64GenContext, 0x45); + else if( srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x44); + else if( memRegisterA64 >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + else if( memRegisterB64 >= 8 ) + x64Gen_writeU8(x64GenContext, 0x42); + + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x38); + x64Gen_writeU8(x64GenContext, 0xF1); + _x64Gen_writeMODRMDeprecated(x64GenContext, srcRegister, memRegisterA64, memRegisterB64, memImmS32); +} + +void x64Gen_shrx_reg64_reg64_reg64(x64GenContext_t* x64GenContext, sint32 registerDst, sint32 registerA, sint32 registerB) +{ + // SHRX reg64, reg64, reg64 + x64Gen_writeU8(x64GenContext, 0xC4); + x64Gen_writeU8(x64GenContext, 0xE2 - ((registerDst >= 8) ? 0x80 : 0) - ((registerA >= 8) ? 0x20 : 0)); + x64Gen_writeU8(x64GenContext, 0xFB - registerB * 8); + x64Gen_writeU8(x64GenContext, 0xF7); + x64Gen_writeU8(x64GenContext, 0xC0 + (registerDst & 7) * 8 + (registerA & 7)); +} + +void x64Gen_shlx_reg64_reg64_reg64(x64GenContext_t* x64GenContext, sint32 registerDst, sint32 registerA, sint32 registerB) +{ + // SHLX reg64, reg64, reg64 + x64Gen_writeU8(x64GenContext, 0xC4); + x64Gen_writeU8(x64GenContext, 0xE2 - ((registerDst >= 8) ? 0x80 : 0) - ((registerA >= 8) ? 0x20 : 0)); + x64Gen_writeU8(x64GenContext, 0xF9 - registerB * 8); + x64Gen_writeU8(x64GenContext, 0xF7); + x64Gen_writeU8(x64GenContext, 0xC0 + (registerDst & 7) * 8 + (registerA & 7)); +} \ No newline at end of file diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64FPU.cpp b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64FPU.cpp new file mode 100644 index 00000000..fbb95b2f --- /dev/null +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64FPU.cpp @@ -0,0 +1,1244 @@ +#include "PPCRecompiler.h" +#include "PPCRecompilerIml.h" +#include "PPCRecompilerX64.h" +#include "asm/x64util.h" + +void PPCRecompilerX64Gen_imlInstruction_fpr_r_name(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction) +{ + uint32 name = imlInstruction->op_r_name.name; + if( name >= PPCREC_NAME_FPR0 && name < (PPCREC_NAME_FPR0+32) ) + { + x64Gen_movupd_xmmReg_memReg128(x64GenContext, tempToRealFPRRegister(imlInstruction->op_r_name.registerIndex), REG_ESP, offsetof(PPCInterpreter_t, fpr)+sizeof(FPR_t)*(name-PPCREC_NAME_FPR0)); + } + else if( name >= PPCREC_NAME_TEMPORARY_FPR0 || name < (PPCREC_NAME_TEMPORARY_FPR0+8) ) + { + x64Gen_movupd_xmmReg_memReg128(x64GenContext, tempToRealFPRRegister(imlInstruction->op_r_name.registerIndex), REG_ESP, offsetof(PPCInterpreter_t, temporaryFPR)+sizeof(FPR_t)*(name-PPCREC_NAME_TEMPORARY_FPR0)); + } + else + { + cemu_assert_debug(false); + } +} + +void PPCRecompilerX64Gen_imlInstruction_fpr_name_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction) +{ + uint32 name = imlInstruction->op_r_name.name; + if( name >= PPCREC_NAME_FPR0 && name < (PPCREC_NAME_FPR0+32) ) + { + x64Gen_movupd_memReg128_xmmReg(x64GenContext, tempToRealFPRRegister(imlInstruction->op_r_name.registerIndex), REG_ESP, offsetof(PPCInterpreter_t, fpr)+sizeof(FPR_t)*(name-PPCREC_NAME_FPR0)); + } + else if( name >= PPCREC_NAME_TEMPORARY_FPR0 && name < (PPCREC_NAME_TEMPORARY_FPR0+8) ) + { + x64Gen_movupd_memReg128_xmmReg(x64GenContext, tempToRealFPRRegister(imlInstruction->op_r_name.registerIndex), REG_ESP, offsetof(PPCInterpreter_t, temporaryFPR)+sizeof(FPR_t)*(name-PPCREC_NAME_TEMPORARY_FPR0)); + } + else + { + cemu_assert_debug(false); + } +} + +void PPCRecompilerX64Gen_imlInstr_gqr_generateScaleCode(ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, sint32 registerXMM, bool isLoad, bool scalePS1, sint32 registerGQR) +{ + // load GQR + x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, registerGQR); + // extract scale field and multiply by 16 to get array offset + x64Gen_shr_reg64Low32_imm8(x64GenContext, REG_RESV_TEMP, (isLoad?16:0)+8-4); + x64Gen_and_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, (0x3F<<4)); + // multiply xmm by scale + x64Gen_add_reg64_reg64(x64GenContext, REG_RESV_TEMP, REG_RESV_RECDATA); + if (isLoad) + { + if(scalePS1) + x64Gen_mulpd_xmmReg_memReg128(x64GenContext, registerXMM, REG_RESV_TEMP, offsetof(PPCRecompilerInstanceData_t, _psq_ld_scale_ps0_ps1)); + else + x64Gen_mulpd_xmmReg_memReg128(x64GenContext, registerXMM, REG_RESV_TEMP, offsetof(PPCRecompilerInstanceData_t, _psq_ld_scale_ps0_1)); + } + else + { + if (scalePS1) + x64Gen_mulpd_xmmReg_memReg128(x64GenContext, registerXMM, REG_RESV_TEMP, offsetof(PPCRecompilerInstanceData_t, _psq_st_scale_ps0_ps1)); + else + x64Gen_mulpd_xmmReg_memReg128(x64GenContext, registerXMM, REG_RESV_TEMP, offsetof(PPCRecompilerInstanceData_t, _psq_st_scale_ps0_1)); + } +} + +// generate code for PSQ load for a particular type +// if scaleGQR is -1 then a scale of 1.0 is assumed (no scale) +void PPCRecompilerX64Gen_imlInstr_psq_load(ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, uint8 mode, sint32 registerXMM, sint32 memReg, sint32 memRegEx, sint32 memImmS32, bool indexed, sint32 registerGQR = -1) +{ + if (mode == PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0_PS1) + { + if (indexed) + { + assert_dbg(); + } + // optimized code for ps float load + x64Emit_mov_reg64_mem64(x64GenContext, REG_RESV_TEMP, REG_R13, memReg, memImmS32); + x64Gen_bswap_reg64(x64GenContext, REG_RESV_TEMP); + x64Gen_rol_reg64_imm8(x64GenContext, REG_RESV_TEMP, 32); // swap upper and lower DWORD + x64Gen_movq_xmmReg_reg64(x64GenContext, registerXMM, REG_RESV_TEMP); + x64Gen_cvtps2pd_xmmReg_xmmReg(x64GenContext, registerXMM, registerXMM); + // note: floats are not scaled + } + else if (mode == PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0) + { + if (indexed) + { + x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, memRegEx); + x64Gen_add_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, memReg); + if (hasMOVBESupport) + { + x64Gen_movBEZeroExtend_reg64_mem32Reg64PlusReg64(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, REG_RESV_TEMP, memImmS32); + } + else + { + x64Emit_mov_reg32_mem32(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, REG_RESV_TEMP, memImmS32); + x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP); + } + } + else + { + if (hasMOVBESupport) + { + x64Gen_movBEZeroExtend_reg64_mem32Reg64PlusReg64(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, memReg, memImmS32); + } + else + { + x64Emit_mov_reg32_mem32(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, memReg, memImmS32); + x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP); + } + } + if (hasAVXSupport) + { + x64Gen_movd_xmmReg_reg64Low32(x64GenContext, REG_RESV_FPR_TEMP, REG_RESV_TEMP); + } + else + { + x64Emit_mov_mem32_reg64(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR), REG_RESV_TEMP); + x64Gen_movddup_xmmReg_memReg64(x64GenContext, REG_RESV_FPR_TEMP, REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)); + } + x64Gen_cvtss2sd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, REG_RESV_FPR_TEMP); + // load constant 1.0 into lower half and upper half of temp register + x64Gen_movddup_xmmReg_memReg64(x64GenContext, registerXMM, REG_RESV_RECDATA, offsetof(PPCRecompilerInstanceData_t, _x64XMM_constDouble1_1)); + // overwrite lower half with single from memory + x64Gen_movsd_xmmReg_xmmReg(x64GenContext, registerXMM, REG_RESV_FPR_TEMP); + // note: floats are not scaled + } + else + { + sint32 readSize; + bool isSigned = false; + if (mode == PPCREC_FPR_LD_MODE_PSQ_S16_PS0 || + mode == PPCREC_FPR_LD_MODE_PSQ_S16_PS0_PS1) + { + readSize = 16; + isSigned = true; + } + else if (mode == PPCREC_FPR_LD_MODE_PSQ_U16_PS0 || + mode == PPCREC_FPR_LD_MODE_PSQ_U16_PS0_PS1) + { + readSize = 16; + isSigned = false; + } + else if (mode == PPCREC_FPR_LD_MODE_PSQ_S8_PS0 || + mode == PPCREC_FPR_LD_MODE_PSQ_S8_PS0_PS1) + { + readSize = 8; + isSigned = true; + } + else if (mode == PPCREC_FPR_LD_MODE_PSQ_U8_PS0 || + mode == PPCREC_FPR_LD_MODE_PSQ_U8_PS0_PS1) + { + readSize = 8; + isSigned = false; + } + else + assert_dbg(); + + bool loadPS1 = (mode == PPCREC_FPR_LD_MODE_PSQ_S16_PS0_PS1 || + mode == PPCREC_FPR_LD_MODE_PSQ_U16_PS0_PS1 || + mode == PPCREC_FPR_LD_MODE_PSQ_U8_PS0_PS1 || + mode == PPCREC_FPR_LD_MODE_PSQ_S8_PS0_PS1); + for (sint32 wordIndex = 0; wordIndex < 2; wordIndex++) + { + if (indexed) + { + assert_dbg(); + } + // read from memory + if (wordIndex == 1 && loadPS1 == false) + { + // store constant 1 + x64Gen_mov_mem32Reg64_imm32(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryGPR) + sizeof(uint32) * 1, 1); + } + else + { + uint32 memOffset = memImmS32 + wordIndex * (readSize / 8); + if (readSize == 16) + { + // half word + x64Gen_movZeroExtend_reg64Low16_mem16Reg64PlusReg64(x64GenContext, REG_RESV_TEMP, REG_R13, memReg, memOffset); + x64Gen_rol_reg64Low16_imm8(x64GenContext, REG_RESV_TEMP, 8); // endian swap + if (isSigned) + x64Gen_movSignExtend_reg64Low32_reg64Low16(x64GenContext, REG_RESV_TEMP, REG_RESV_TEMP); + else + x64Gen_movZeroExtend_reg64Low32_reg64Low16(x64GenContext, REG_RESV_TEMP, REG_RESV_TEMP); + } + else if (readSize == 8) + { + // byte + x64Emit_mov_reg64b_mem8(x64GenContext, REG_RESV_TEMP, REG_R13, memReg, memOffset); + if (isSigned) + x64Gen_movSignExtend_reg64Low32_reg64Low8(x64GenContext, REG_RESV_TEMP, REG_RESV_TEMP); + else + x64Gen_movZeroExtend_reg64Low32_reg64Low8(x64GenContext, REG_RESV_TEMP, REG_RESV_TEMP); + } + // store + x64Emit_mov_mem32_reg32(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryGPR) + sizeof(uint32) * wordIndex, REG_RESV_TEMP); + } + } + // convert the two integers to doubles + x64Gen_cvtpi2pd_xmmReg_mem64Reg64(x64GenContext, registerXMM, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryGPR)); + // scale + if (registerGQR >= 0) + PPCRecompilerX64Gen_imlInstr_gqr_generateScaleCode(ppcImlGenContext, x64GenContext, registerXMM, true, loadPS1, registerGQR); + } +} + +void PPCRecompilerX64Gen_imlInstr_psq_load_generic(ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, uint8 mode, sint32 registerXMM, sint32 memReg, sint32 memRegEx, sint32 memImmS32, bool indexed, sint32 registerGQR) +{ + bool loadPS1 = (mode == PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0_PS1); + // load GQR + x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, registerGQR); + // extract load type field + x64Gen_shr_reg64Low32_imm8(x64GenContext, REG_RESV_TEMP, 16); + x64Gen_and_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, 7); + // jump cases + x64Gen_cmp_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, 4); // type 4 -> u8 + sint32 jumpOffset_caseU8 = x64GenContext->codeBufferIndex; + x64Gen_jmpc_far(x64GenContext, X86_CONDITION_EQUAL, 0); + x64Gen_cmp_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, 5); // type 5 -> u16 + sint32 jumpOffset_caseU16 = x64GenContext->codeBufferIndex; + x64Gen_jmpc_far(x64GenContext, X86_CONDITION_EQUAL, 0); + x64Gen_cmp_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, 6); // type 4 -> s8 + sint32 jumpOffset_caseS8 = x64GenContext->codeBufferIndex; + x64Gen_jmpc_far(x64GenContext, X86_CONDITION_EQUAL, 0); + x64Gen_cmp_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, 7); // type 5 -> s16 + sint32 jumpOffset_caseS16 = x64GenContext->codeBufferIndex; + x64Gen_jmpc_far(x64GenContext, X86_CONDITION_EQUAL, 0); + // default case -> float + + // generate cases + uint32 jumpOffset_endOfFloat; + uint32 jumpOffset_endOfU8; + uint32 jumpOffset_endOfU16; + uint32 jumpOffset_endOfS8; + + PPCRecompilerX64Gen_imlInstr_psq_load(ppcImlGenContext, x64GenContext, loadPS1 ? PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0_PS1 : PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0, registerXMM, memReg, memRegEx, memImmS32, indexed, registerGQR); + jumpOffset_endOfFloat = x64GenContext->codeBufferIndex; + x64Gen_jmp_imm32(x64GenContext, 0); + + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_caseU16, x64GenContext->codeBufferIndex); + PPCRecompilerX64Gen_imlInstr_psq_load(ppcImlGenContext, x64GenContext, loadPS1 ? PPCREC_FPR_LD_MODE_PSQ_U16_PS0_PS1 : PPCREC_FPR_LD_MODE_PSQ_U16_PS0, registerXMM, memReg, memRegEx, memImmS32, indexed, registerGQR); + jumpOffset_endOfU8 = x64GenContext->codeBufferIndex; + x64Gen_jmp_imm32(x64GenContext, 0); + + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_caseS16, x64GenContext->codeBufferIndex); + PPCRecompilerX64Gen_imlInstr_psq_load(ppcImlGenContext, x64GenContext, loadPS1 ? PPCREC_FPR_LD_MODE_PSQ_S16_PS0_PS1 : PPCREC_FPR_LD_MODE_PSQ_S16_PS0, registerXMM, memReg, memRegEx, memImmS32, indexed, registerGQR); + jumpOffset_endOfU16 = x64GenContext->codeBufferIndex; + x64Gen_jmp_imm32(x64GenContext, 0); + + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_caseU8, x64GenContext->codeBufferIndex); + PPCRecompilerX64Gen_imlInstr_psq_load(ppcImlGenContext, x64GenContext, loadPS1 ? PPCREC_FPR_LD_MODE_PSQ_U8_PS0_PS1 : PPCREC_FPR_LD_MODE_PSQ_U8_PS0, registerXMM, memReg, memRegEx, memImmS32, indexed, registerGQR); + jumpOffset_endOfS8 = x64GenContext->codeBufferIndex; + x64Gen_jmp_imm32(x64GenContext, 0); + + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_caseS8, x64GenContext->codeBufferIndex); + PPCRecompilerX64Gen_imlInstr_psq_load(ppcImlGenContext, x64GenContext, loadPS1 ? PPCREC_FPR_LD_MODE_PSQ_S8_PS0_PS1 : PPCREC_FPR_LD_MODE_PSQ_S8_PS0, registerXMM, memReg, memRegEx, memImmS32, indexed, registerGQR); + + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_endOfFloat, x64GenContext->codeBufferIndex); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_endOfU8, x64GenContext->codeBufferIndex); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_endOfU16, x64GenContext->codeBufferIndex); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_endOfS8, x64GenContext->codeBufferIndex); +} + +// load from memory +bool PPCRecompilerX64Gen_imlInstruction_fpr_load(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction, bool indexed) +{ + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + sint32 realRegisterXMM = tempToRealFPRRegister(imlInstruction->op_storeLoad.registerData); + sint32 realRegisterMem = tempToRealRegister(imlInstruction->op_storeLoad.registerMem); + sint32 realRegisterMem2 = PPC_REC_INVALID_REGISTER; + if( indexed ) + realRegisterMem2 = tempToRealRegister(imlInstruction->op_storeLoad.registerMem2); + uint8 mode = imlInstruction->op_storeLoad.mode; + + if( mode == PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0_PS1 ) + { + // load byte swapped single into temporary FPR + if( indexed ) + { + x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, realRegisterMem2); + x64Gen_add_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, realRegisterMem); + if( hasMOVBESupport ) + x64Gen_movBEZeroExtend_reg64_mem32Reg64PlusReg64(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, REG_RESV_TEMP, imlInstruction->op_storeLoad.immS32); + else + x64Emit_mov_reg32_mem32(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, REG_RESV_TEMP, imlInstruction->op_storeLoad.immS32); + } + else + { + if( hasMOVBESupport ) + x64Gen_movBEZeroExtend_reg64_mem32Reg64PlusReg64(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32); + else + x64Emit_mov_reg32_mem32(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32); + } + if( hasMOVBESupport == false ) + x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP); + if( hasAVXSupport ) + { + x64Gen_movd_xmmReg_reg64Low32(x64GenContext, realRegisterXMM, REG_RESV_TEMP); + } + else + { + x64Emit_mov_mem32_reg64(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR), REG_RESV_TEMP); + x64Gen_movddup_xmmReg_memReg64(x64GenContext, realRegisterXMM, REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)); + } + + if (imlInstruction->op_storeLoad.flags2.notExpanded) + { + // leave value as single + } + else + { + x64Gen_cvtss2sd_xmmReg_xmmReg(x64GenContext, realRegisterXMM, realRegisterXMM); + x64Gen_movddup_xmmReg_xmmReg(x64GenContext, realRegisterXMM, realRegisterXMM); + } + } + else if( mode == PPCREC_FPR_LD_MODE_DOUBLE_INTO_PS0 ) + { + if( hasAVXSupport ) + { + if( indexed ) + { + // calculate offset + x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, realRegisterMem); + x64Gen_add_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, realRegisterMem2); + // load value + x64Emit_mov_reg64_mem64(x64GenContext, REG_RESV_TEMP, REG_R13, REG_RESV_TEMP, imlInstruction->op_storeLoad.immS32+0); + x64Gen_bswap_reg64(x64GenContext, REG_RESV_TEMP); + x64Gen_movq_xmmReg_reg64(x64GenContext, REG_RESV_FPR_TEMP, REG_RESV_TEMP); + x64Gen_movsd_xmmReg_xmmReg(x64GenContext, realRegisterXMM, REG_RESV_FPR_TEMP); + } + else + { + x64Emit_mov_reg64_mem64(x64GenContext, REG_RESV_TEMP, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32+0); + x64Gen_bswap_reg64(x64GenContext, REG_RESV_TEMP); + x64Gen_movq_xmmReg_reg64(x64GenContext, REG_RESV_FPR_TEMP, REG_RESV_TEMP); + x64Gen_movsd_xmmReg_xmmReg(x64GenContext, realRegisterXMM, REG_RESV_FPR_TEMP); + } + } + else + { + if( indexed ) + { + // calculate offset + x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, realRegisterMem); + x64Gen_add_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, realRegisterMem2); + // load double low part to temporaryFPR + x64Emit_mov_reg32_mem32(x64GenContext, REG_RESV_TEMP, REG_R13, REG_RESV_TEMP, imlInstruction->op_storeLoad.immS32+0); + x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP); + x64Emit_mov_mem32_reg64(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)+4, REG_RESV_TEMP); + // calculate offset again + x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, realRegisterMem); + x64Gen_add_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, realRegisterMem2); + // load double high part to temporaryFPR + x64Emit_mov_reg32_mem32(x64GenContext, REG_RESV_TEMP, REG_R13, REG_RESV_TEMP, imlInstruction->op_storeLoad.immS32+4); + x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP); + x64Emit_mov_mem32_reg64(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)+0, REG_RESV_TEMP); + // load double from temporaryFPR + x64Gen_movlpd_xmmReg_memReg64(x64GenContext, realRegisterXMM, REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)); + } + else + { + // load double low part to temporaryFPR + x64Emit_mov_reg32_mem32(x64GenContext, REG_RESV_TEMP, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32+0); + x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP); + x64Emit_mov_mem32_reg64(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)+4, REG_RESV_TEMP); + // load double high part to temporaryFPR + x64Emit_mov_reg32_mem32(x64GenContext, REG_RESV_TEMP, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32+4); + x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP); + x64Emit_mov_mem32_reg64(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)+0, REG_RESV_TEMP); + // load double from temporaryFPR + x64Gen_movlpd_xmmReg_memReg64(x64GenContext, realRegisterXMM, REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)); + } + } + } + else if (mode == PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0_PS1 || + mode == PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0 || + mode == PPCREC_FPR_LD_MODE_PSQ_S16_PS0 || + mode == PPCREC_FPR_LD_MODE_PSQ_S16_PS0_PS1 || + mode == PPCREC_FPR_LD_MODE_PSQ_S16_PS0 || + mode == PPCREC_FPR_LD_MODE_PSQ_U16_PS0 || + mode == PPCREC_FPR_LD_MODE_PSQ_U16_PS0_PS1 || + mode == PPCREC_FPR_LD_MODE_PSQ_S8_PS0 || + mode == PPCREC_FPR_LD_MODE_PSQ_S8_PS0_PS1 || + mode == PPCREC_FPR_LD_MODE_PSQ_S8_PS0 || + mode == PPCREC_FPR_LD_MODE_PSQ_U8_PS0_PS1 ) + { + PPCRecompilerX64Gen_imlInstr_psq_load(ppcImlGenContext, x64GenContext, mode, realRegisterXMM, realRegisterMem, realRegisterMem2, imlInstruction->op_storeLoad.immS32, indexed); + } + else if (mode == PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0_PS1 || + mode == PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0) + { + PPCRecompilerX64Gen_imlInstr_psq_load_generic(ppcImlGenContext, x64GenContext, mode, realRegisterXMM, realRegisterMem, realRegisterMem2, imlInstruction->op_storeLoad.immS32, indexed, tempToRealRegister(imlInstruction->op_storeLoad.registerGQR)); + } + else + { + return false; + } + return true; +} + +void PPCRecompilerX64Gen_imlInstr_psq_store(ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, uint8 mode, sint32 registerXMM, sint32 memReg, sint32 memRegEx, sint32 memImmS32, bool indexed, sint32 registerGQR = -1) +{ + bool storePS1 = (mode == PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0_PS1 || + mode == PPCREC_FPR_ST_MODE_PSQ_S8_PS0_PS1 || + mode == PPCREC_FPR_ST_MODE_PSQ_U8_PS0_PS1 || + mode == PPCREC_FPR_ST_MODE_PSQ_U16_PS0_PS1 || + mode == PPCREC_FPR_ST_MODE_PSQ_S16_PS0_PS1); + bool isFloat = mode == PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0 || mode == PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0_PS1; + if (registerGQR >= 0) + { + // move to temporary xmm and update registerXMM + x64Gen_movaps_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, registerXMM); + registerXMM = REG_RESV_FPR_TEMP; + // apply scale + if(isFloat == false) + PPCRecompilerX64Gen_imlInstr_gqr_generateScaleCode(ppcImlGenContext, x64GenContext, registerXMM, false, storePS1, registerGQR); + } + if (mode == PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0) + { + x64Gen_cvtsd2ss_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, registerXMM); + if (hasAVXSupport) + { + x64Gen_movd_reg64Low32_xmmReg(x64GenContext, REG_RESV_TEMP, REG_RESV_FPR_TEMP); + } + else + { + x64Gen_movsd_memReg64_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)); + x64Emit_mov_reg64_mem32(x64GenContext, REG_RESV_TEMP, REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)); + } + if (hasMOVBESupport == false) + x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP); + if (indexed) + { + cemu_assert_debug(memReg != memRegEx); + x64Gen_add_reg64Low32_reg64Low32(x64GenContext, memReg, memRegEx); + } + if (hasMOVBESupport) + x64Gen_movBETruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_R13, memReg, memImmS32, REG_RESV_TEMP); + else + x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_R13, memReg, memImmS32, REG_RESV_TEMP); + if (indexed) + { + x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, memReg, memRegEx); + } + return; + } + else if (mode == PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0_PS1) + { + if (indexed) + assert_dbg(); // todo + x64Gen_cvtpd2ps_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, registerXMM); + x64Gen_movq_reg64_xmmReg(x64GenContext, REG_RESV_TEMP, REG_RESV_FPR_TEMP); + x64Gen_rol_reg64_imm8(x64GenContext, REG_RESV_TEMP, 32); // swap upper and lower DWORD + x64Gen_bswap_reg64(x64GenContext, REG_RESV_TEMP); + x64Gen_mov_mem64Reg64PlusReg64_reg64(x64GenContext, REG_RESV_TEMP, REG_R13, memReg, memImmS32); + return; + } + // store as integer + // get limit from mode + sint32 clampMin, clampMax; + sint32 bitWriteSize; + if (mode == PPCREC_FPR_ST_MODE_PSQ_S8_PS0 || + mode == PPCREC_FPR_ST_MODE_PSQ_S8_PS0_PS1 ) + { + clampMin = -128; + clampMax = 127; + bitWriteSize = 8; + } + else if (mode == PPCREC_FPR_ST_MODE_PSQ_U8_PS0 || + mode == PPCREC_FPR_ST_MODE_PSQ_U8_PS0_PS1 ) + { + clampMin = 0; + clampMax = 255; + bitWriteSize = 8; + } + else if (mode == PPCREC_FPR_ST_MODE_PSQ_U16_PS0 || + mode == PPCREC_FPR_ST_MODE_PSQ_U16_PS0_PS1 ) + { + clampMin = 0; + clampMax = 0xFFFF; + bitWriteSize = 16; + } + else if (mode == PPCREC_FPR_ST_MODE_PSQ_S16_PS0 || + mode == PPCREC_FPR_ST_MODE_PSQ_S16_PS0_PS1 ) + { + clampMin = -32768; + clampMax = 32767; + bitWriteSize = 16; + } + else + { + cemu_assert(false); + } + for (sint32 valueIndex = 0; valueIndex < (storePS1?2:1); valueIndex++) + { + // todo - multiply by GQR scale + if (valueIndex == 0) + { + // convert low half (PS0) to integer + x64Gen_cvttsd2si_reg64Low_xmmReg(x64GenContext, REG_RESV_TEMP, registerXMM); + } + else + { + // load top half (PS1) into bottom half of temporary register + x64Gen_movhlps_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, registerXMM); + // convert low half to integer + x64Gen_cvttsd2si_reg64Low_xmmReg(x64GenContext, REG_RESV_TEMP, REG_RESV_FPR_TEMP); + } + // max(i, -clampMin) + x64Gen_cmp_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, clampMin); + sint32 jumpInstructionOffset1 = x64GenContext->codeBufferIndex; + x64Gen_jmpc_near(x64GenContext, X86_CONDITION_SIGNED_GREATER_EQUAL, 0); + x64Gen_mov_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, clampMin); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset1, x64GenContext->codeBufferIndex); + // min(i, clampMax) + x64Gen_cmp_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, clampMax); + sint32 jumpInstructionOffset2 = x64GenContext->codeBufferIndex; + x64Gen_jmpc_near(x64GenContext, X86_CONDITION_SIGNED_LESS_EQUAL, 0); + x64Gen_mov_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, clampMax); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset2, x64GenContext->codeBufferIndex); + // endian swap + if( bitWriteSize == 16) + x64Gen_rol_reg64Low16_imm8(x64GenContext, REG_RESV_TEMP, 8); + // write to memory + if (indexed) + assert_dbg(); // unsupported + sint32 memOffset = memImmS32 + valueIndex * (bitWriteSize/8); + if (bitWriteSize == 8) + x64Gen_movTruncate_mem8Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, memReg, memOffset, REG_RESV_TEMP); + else if (bitWriteSize == 16) + x64Gen_movTruncate_mem16Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, memReg, memOffset, REG_RESV_TEMP); + } +} + +void PPCRecompilerX64Gen_imlInstr_psq_store_generic(ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, uint8 mode, sint32 registerXMM, sint32 memReg, sint32 memRegEx, sint32 memImmS32, bool indexed, sint32 registerGQR) +{ + bool storePS1 = (mode == PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0_PS1); + // load GQR + x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, registerGQR); + // extract store type field + x64Gen_and_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, 7); + // jump cases + x64Gen_cmp_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, 4); // type 4 -> u8 + sint32 jumpOffset_caseU8 = x64GenContext->codeBufferIndex; + x64Gen_jmpc_far(x64GenContext, X86_CONDITION_EQUAL, 0); + x64Gen_cmp_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, 5); // type 5 -> u16 + sint32 jumpOffset_caseU16 = x64GenContext->codeBufferIndex; + x64Gen_jmpc_far(x64GenContext, X86_CONDITION_EQUAL, 0); + x64Gen_cmp_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, 6); // type 4 -> s8 + sint32 jumpOffset_caseS8 = x64GenContext->codeBufferIndex; + x64Gen_jmpc_far(x64GenContext, X86_CONDITION_EQUAL, 0); + x64Gen_cmp_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, 7); // type 5 -> s16 + sint32 jumpOffset_caseS16 = x64GenContext->codeBufferIndex; + x64Gen_jmpc_far(x64GenContext, X86_CONDITION_EQUAL, 0); + // default case -> float + + // generate cases + uint32 jumpOffset_endOfFloat; + uint32 jumpOffset_endOfU8; + uint32 jumpOffset_endOfU16; + uint32 jumpOffset_endOfS8; + + PPCRecompilerX64Gen_imlInstr_psq_store(ppcImlGenContext, x64GenContext, storePS1 ? PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0_PS1 : PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0, registerXMM, memReg, memRegEx, memImmS32, indexed, registerGQR); + jumpOffset_endOfFloat = x64GenContext->codeBufferIndex; + x64Gen_jmp_imm32(x64GenContext, 0); + + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_caseU16, x64GenContext->codeBufferIndex); + PPCRecompilerX64Gen_imlInstr_psq_store(ppcImlGenContext, x64GenContext, storePS1 ? PPCREC_FPR_ST_MODE_PSQ_U16_PS0_PS1 : PPCREC_FPR_ST_MODE_PSQ_U16_PS0, registerXMM, memReg, memRegEx, memImmS32, indexed, registerGQR); + jumpOffset_endOfU8 = x64GenContext->codeBufferIndex; + x64Gen_jmp_imm32(x64GenContext, 0); + + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_caseS16, x64GenContext->codeBufferIndex); + PPCRecompilerX64Gen_imlInstr_psq_store(ppcImlGenContext, x64GenContext, storePS1 ? PPCREC_FPR_ST_MODE_PSQ_S16_PS0_PS1 : PPCREC_FPR_ST_MODE_PSQ_S16_PS0, registerXMM, memReg, memRegEx, memImmS32, indexed, registerGQR); + jumpOffset_endOfU16 = x64GenContext->codeBufferIndex; + x64Gen_jmp_imm32(x64GenContext, 0); + + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_caseU8, x64GenContext->codeBufferIndex); + PPCRecompilerX64Gen_imlInstr_psq_store(ppcImlGenContext, x64GenContext, storePS1 ? PPCREC_FPR_ST_MODE_PSQ_U8_PS0_PS1 : PPCREC_FPR_ST_MODE_PSQ_U8_PS0, registerXMM, memReg, memRegEx, memImmS32, indexed, registerGQR); + jumpOffset_endOfS8 = x64GenContext->codeBufferIndex; + x64Gen_jmp_imm32(x64GenContext, 0); + + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_caseS8, x64GenContext->codeBufferIndex); + PPCRecompilerX64Gen_imlInstr_psq_store(ppcImlGenContext, x64GenContext, storePS1 ? PPCREC_FPR_ST_MODE_PSQ_S8_PS0_PS1 : PPCREC_FPR_ST_MODE_PSQ_S8_PS0, registerXMM, memReg, memRegEx, memImmS32, indexed, registerGQR); + + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_endOfFloat, x64GenContext->codeBufferIndex); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_endOfU8, x64GenContext->codeBufferIndex); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_endOfU16, x64GenContext->codeBufferIndex); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_endOfS8, x64GenContext->codeBufferIndex); +} + +// store to memory +bool PPCRecompilerX64Gen_imlInstruction_fpr_store(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction, bool indexed) +{ + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + sint32 realRegisterXMM = tempToRealFPRRegister(imlInstruction->op_storeLoad.registerData); + sint32 realRegisterMem = tempToRealRegister(imlInstruction->op_storeLoad.registerMem); + sint32 realRegisterMem2 = PPC_REC_INVALID_REGISTER; + if( indexed ) + realRegisterMem2 = tempToRealRegister(imlInstruction->op_storeLoad.registerMem2); + uint8 mode = imlInstruction->op_storeLoad.mode; + if( mode == PPCREC_FPR_ST_MODE_SINGLE_FROM_PS0 ) + { + if (imlInstruction->op_storeLoad.flags2.notExpanded) + { + // value is already in single format + if (hasAVXSupport) + { + x64Gen_movd_reg64Low32_xmmReg(x64GenContext, REG_RESV_TEMP, realRegisterXMM); + } + else + { + x64Gen_movsd_memReg64_xmmReg(x64GenContext, realRegisterXMM, REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)); + x64Emit_mov_reg64_mem32(x64GenContext, REG_RESV_TEMP, REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)); + } + } + else + { + x64Gen_cvtsd2ss_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, realRegisterXMM); + if (hasAVXSupport) + { + x64Gen_movd_reg64Low32_xmmReg(x64GenContext, REG_RESV_TEMP, REG_RESV_FPR_TEMP); + } + else + { + x64Gen_movsd_memReg64_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)); + x64Emit_mov_reg64_mem32(x64GenContext, REG_RESV_TEMP, REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)); + } + } + if( hasMOVBESupport == false ) + x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP); + if( indexed ) + { + if( realRegisterMem == realRegisterMem2 ) + assert_dbg(); + x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); + } + if( hasMOVBESupport ) + x64Gen_movBETruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32, REG_RESV_TEMP); + else + x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32, REG_RESV_TEMP); + if( indexed ) + { + x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); + } + } + else if( mode == PPCREC_FPR_ST_MODE_DOUBLE_FROM_PS0 ) + { + if( indexed ) + { + if( realRegisterMem == realRegisterMem2 ) + assert_dbg(); + x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); + } + x64Gen_movsd_memReg64_xmmReg(x64GenContext, realRegisterXMM, REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)); + // store double low part + x64Emit_mov_reg64_mem32(x64GenContext, REG_RESV_TEMP, REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)+0); + x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP); + x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32+4, REG_RESV_TEMP); + // store double high part + x64Emit_mov_reg64_mem32(x64GenContext, REG_RESV_TEMP, REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)+4); + x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP); + x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32+0, REG_RESV_TEMP); + if( indexed ) + { + x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); + } + } + else if( mode == PPCREC_FPR_ST_MODE_UI32_FROM_PS0 ) + { + if( hasAVXSupport ) + { + x64Gen_movd_reg64Low32_xmmReg(x64GenContext, REG_RESV_TEMP, realRegisterXMM); + } + else + { + x64Gen_movsd_memReg64_xmmReg(x64GenContext, realRegisterXMM, REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)); + x64Emit_mov_reg64_mem32(x64GenContext, REG_RESV_TEMP, REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)); + } + x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP); + if( indexed ) + { + if( realRegisterMem == realRegisterMem2 ) + assert_dbg(); + x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); + x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32, REG_RESV_TEMP); + x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); + } + else + { + x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32, REG_RESV_TEMP); + } + } + else if(mode == PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0_PS1 || + mode == PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0 || + mode == PPCREC_FPR_ST_MODE_PSQ_S8_PS0 || + mode == PPCREC_FPR_ST_MODE_PSQ_S8_PS0_PS1 || + mode == PPCREC_FPR_ST_MODE_PSQ_U8_PS0 || + mode == PPCREC_FPR_ST_MODE_PSQ_U8_PS0_PS1 || + mode == PPCREC_FPR_ST_MODE_PSQ_S16_PS0 || + mode == PPCREC_FPR_ST_MODE_PSQ_S16_PS0_PS1 || + mode == PPCREC_FPR_ST_MODE_PSQ_U16_PS0 || + mode == PPCREC_FPR_ST_MODE_PSQ_U16_PS0_PS1 ) + { + cemu_assert_debug(imlInstruction->op_storeLoad.flags2.notExpanded == false); + PPCRecompilerX64Gen_imlInstr_psq_store(ppcImlGenContext, x64GenContext, mode, realRegisterXMM, realRegisterMem, realRegisterMem2, imlInstruction->op_storeLoad.immS32, indexed); + } + else if (mode == PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0_PS1 || + mode == PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0) + { + PPCRecompilerX64Gen_imlInstr_psq_store_generic(ppcImlGenContext, x64GenContext, mode, realRegisterXMM, realRegisterMem, realRegisterMem2, imlInstruction->op_storeLoad.immS32, indexed, tempToRealRegister(imlInstruction->op_storeLoad.registerGQR)); + } + else + { + if( indexed ) + assert_dbg(); // todo + debug_printf("PPCRecompilerX64Gen_imlInstruction_fpr_store(): Unsupported mode %d\n", mode); + return false; + } + return true; +} + +void _swapPS0PS1(x64GenContext_t* x64GenContext, sint32 xmmReg) +{ + x64Gen_shufpd_xmmReg_xmmReg_imm8(x64GenContext, xmmReg, xmmReg, 1); +} + +// FPR op FPR +void PPCRecompilerX64Gen_imlInstruction_fpr_r_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction) +{ + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + if( imlInstruction->operation == PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM_AND_TOP ) + { + if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) + { + assert_dbg(); + } + x64Gen_movddup_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand); + } + else if( imlInstruction->operation == PPCREC_IML_OP_FPR_COPY_TOP_TO_BOTTOM_AND_TOP ) + { + if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) + { + assert_dbg(); + } + // VPUNPCKHQDQ + if (imlInstruction->op_fpr_r_r.registerResult == imlInstruction->op_fpr_r_r.registerOperand) + { + // unpack top to bottom and top + x64Gen_unpckhpd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand); + } + //else if ( hasAVXSupport ) + //{ + // // unpack top to bottom and top with non-destructive destination + // // update: On Ivy Bridge this causes weird stalls? + // x64Gen_avx_VUNPCKHPD_xmm_xmm_xmm(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand, imlInstruction->op_fpr_r_r.registerOperand); + //} + else + { + // move top to bottom + x64Gen_movhlps_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand); + // duplicate bottom + x64Gen_movddup_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerResult); + } + + } + else if( imlInstruction->operation == PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM ) + { + cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); + x64Gen_movsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand); + } + else if( imlInstruction->operation == PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_TOP ) + { + cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); + x64Gen_unpcklpd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand); + } + else if( imlInstruction->operation == PPCREC_IML_OP_FPR_COPY_BOTTOM_AND_TOP_SWAPPED ) + { + cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); + if( imlInstruction->op_fpr_r_r.registerResult != imlInstruction->op_fpr_r_r.registerOperand ) + x64Gen_movaps_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand); + _swapPS0PS1(x64GenContext, imlInstruction->op_fpr_r_r.registerResult); + } + else if( imlInstruction->operation == PPCREC_IML_OP_FPR_COPY_TOP_TO_TOP ) + { + cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); + x64Gen_shufpd_xmmReg_xmmReg_imm8(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand, 2); + } + else if( imlInstruction->operation == PPCREC_IML_OP_FPR_COPY_TOP_TO_BOTTOM ) + { + cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); + // use unpckhpd here? + x64Gen_shufpd_xmmReg_xmmReg_imm8(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand, 3); + _swapPS0PS1(x64GenContext, imlInstruction->op_fpr_r_r.registerResult); + } + else if( imlInstruction->operation == PPCREC_IML_OP_FPR_MULTIPLY_BOTTOM ) + { + if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) + { + assert_dbg(); + } + x64Gen_mulsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand); + } + else if( imlInstruction->operation == PPCREC_IML_OP_FPR_MULTIPLY_PAIR ) + { + if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) + { + assert_dbg(); + } + x64Gen_mulpd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand); + } + else if( imlInstruction->operation == PPCREC_IML_OP_FPR_DIVIDE_BOTTOM ) + { + if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) + { + assert_dbg(); + } + x64Gen_divsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand); + } + else if (imlInstruction->operation == PPCREC_IML_OP_FPR_DIVIDE_PAIR) + { + if (imlInstruction->crRegister != PPC_REC_INVALID_REGISTER) + { + assert_dbg(); + } + x64Gen_divpd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand); + } + else if( imlInstruction->operation == PPCREC_IML_OP_FPR_ADD_BOTTOM ) + { + if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) + { + assert_dbg(); + } + x64Gen_addsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand); + } + else if( imlInstruction->operation == PPCREC_IML_OP_FPR_ADD_PAIR ) + { + if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) + { + assert_dbg(); + } + x64Gen_addpd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand); + } + else if( imlInstruction->operation == PPCREC_IML_OP_FPR_SUB_PAIR ) + { + if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) + { + assert_dbg(); + } + x64Gen_subpd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand); + } + else if( imlInstruction->operation == PPCREC_IML_OP_FPR_SUB_BOTTOM ) + { + if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) + { + assert_dbg(); + } + x64Gen_subsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand); + } + else if( imlInstruction->operation == PPCREC_IML_OP_ASSIGN ) + { + if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) + { + assert_dbg(); + } + x64Gen_movaps_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand); + } + else if( imlInstruction->operation == PPCREC_IML_OP_FPR_BOTTOM_FCTIWZ ) + { + if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) + { + assert_dbg(); + } + x64Gen_cvttsd2si_xmmReg_xmmReg(x64GenContext, REG_RESV_TEMP, imlInstruction->op_fpr_r_r.registerOperand); + x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, REG_RESV_TEMP); + // move to FPR register + x64Gen_movq_xmmReg_reg64(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, REG_RESV_TEMP); + } + else if(imlInstruction->operation == PPCREC_IML_OP_FPR_FCMPU_BOTTOM || + imlInstruction->operation == PPCREC_IML_OP_FPR_FCMPU_TOP || + imlInstruction->operation == PPCREC_IML_OP_FPR_FCMPO_BOTTOM ) + { + if( imlInstruction->crRegister == PPC_REC_INVALID_REGISTER ) + { + assert_dbg(); + } + if (imlInstruction->operation == PPCREC_IML_OP_FPR_FCMPU_BOTTOM) + x64Gen_ucomisd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand); + else if (imlInstruction->operation == PPCREC_IML_OP_FPR_FCMPU_TOP) + { + // temporarily switch top/bottom of both operands and compare + if (imlInstruction->op_fpr_r_r.registerResult == imlInstruction->op_fpr_r_r.registerOperand) + { + _swapPS0PS1(x64GenContext, imlInstruction->op_fpr_r_r.registerResult); + x64Gen_ucomisd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand); + _swapPS0PS1(x64GenContext, imlInstruction->op_fpr_r_r.registerResult); + } + else + { + _swapPS0PS1(x64GenContext, imlInstruction->op_fpr_r_r.registerResult); + _swapPS0PS1(x64GenContext, imlInstruction->op_fpr_r_r.registerOperand); + x64Gen_ucomisd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand); + _swapPS0PS1(x64GenContext, imlInstruction->op_fpr_r_r.registerResult); + _swapPS0PS1(x64GenContext, imlInstruction->op_fpr_r_r.registerOperand); + } + } + else + x64Gen_comisd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand); + // todo: handle FPSCR updates + // update cr + sint32 crRegister = imlInstruction->crRegister; + // if the parity bit is set (NaN) we need to manually set CR LT, GT and EQ to 0 (comisd/ucomisd sets the respective flags to 1 in case of NaN) + x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_PARITY, REG_RSP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_SO)); // unordered + sint32 jumpInstructionOffset1 = x64GenContext->codeBufferIndex; + x64Gen_jmpc_near(x64GenContext, X86_CONDITION_PARITY, 0); + x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_UNSIGNED_BELOW, REG_RSP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_LT)); // same as X64_CONDITION_CARRY + x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_UNSIGNED_ABOVE, REG_RSP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_GT)); + x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_EQUAL, REG_RSP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_EQ)); + sint32 jumpInstructionOffset2 = x64GenContext->codeBufferIndex; + x64Gen_jmpc_near(x64GenContext, X86_CONDITION_NONE, 0); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset1, x64GenContext->codeBufferIndex); + x64Gen_mov_mem8Reg64_imm8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_LT), 0); + x64Gen_mov_mem8Reg64_imm8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_GT), 0); + x64Gen_mov_mem8Reg64_imm8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_EQ), 0); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset2, x64GenContext->codeBufferIndex); + } + else if( imlInstruction->operation == PPCREC_IML_OP_FPR_BOTTOM_FRES_TO_BOTTOM_AND_TOP ) + { + if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) + { + assert_dbg(); + } + // move register to XMM15 + x64Gen_movsd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, imlInstruction->op_fpr_r_r.registerOperand); + + // call assembly routine to calculate accurate FRES result in XMM15 + x64Gen_mov_reg64_imm64(x64GenContext, REG_RESV_TEMP, (uint64)recompiler_fres); + x64Gen_call_reg64(x64GenContext, REG_RESV_TEMP); + + // copy result to bottom and top half of result register + x64Gen_movddup_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, REG_RESV_FPR_TEMP); + } + else if (imlInstruction->operation == PPCREC_IML_OP_FPR_BOTTOM_RECIPROCAL_SQRT) + { + cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); + // move register to XMM15 + x64Gen_movsd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, imlInstruction->op_fpr_r_r.registerOperand); + + // call assembly routine to calculate accurate FRSQRTE result in XMM15 + x64Gen_mov_reg64_imm64(x64GenContext, REG_RESV_TEMP, (uint64)recompiler_frsqrte); + x64Gen_call_reg64(x64GenContext, REG_RESV_TEMP); + + // copy result to bottom of result register + x64Gen_movsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, REG_RESV_FPR_TEMP); + } + else if( imlInstruction->operation == PPCREC_IML_OP_FPR_NEGATE_PAIR ) + { + cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); + // copy register + if( imlInstruction->op_fpr_r_r.registerResult != imlInstruction->op_fpr_r_r.registerOperand ) + { + x64Gen_movaps_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand); + } + // toggle sign bits + x64Gen_xorps_xmmReg_mem128Reg64(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, REG_RESV_RECDATA, offsetof(PPCRecompilerInstanceData_t, _x64XMM_xorNegateMaskPair)); + } + else if( imlInstruction->operation == PPCREC_IML_OP_FPR_ABS_PAIR ) + { + cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); + // copy register + if( imlInstruction->op_fpr_r_r.registerResult != imlInstruction->op_fpr_r_r.registerOperand ) + { + x64Gen_movaps_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand); + } + // set sign bit to 0 + x64Gen_andps_xmmReg_mem128Reg64(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, REG_RESV_RECDATA, offsetof(PPCRecompilerInstanceData_t, _x64XMM_andAbsMaskPair)); + } + else if( imlInstruction->operation == PPCREC_IML_OP_FPR_FRES_PAIR || imlInstruction->operation == PPCREC_IML_OP_FPR_FRSQRTE_PAIR) + { + cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); + // calculate bottom half of result + x64Gen_movsd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, imlInstruction->op_fpr_r_r.registerOperand); + if(imlInstruction->operation == PPCREC_IML_OP_FPR_FRES_PAIR) + x64Gen_mov_reg64_imm64(x64GenContext, REG_RESV_TEMP, (uint64)recompiler_fres); + else + x64Gen_mov_reg64_imm64(x64GenContext, REG_RESV_TEMP, (uint64)recompiler_frsqrte); + x64Gen_call_reg64(x64GenContext, REG_RESV_TEMP); // calculate fres result in xmm15 + x64Gen_movsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, REG_RESV_FPR_TEMP); + + // calculate top half of result + // todo - this top to bottom copy can be optimized? + x64Gen_shufpd_xmmReg_xmmReg_imm8(x64GenContext, REG_RESV_FPR_TEMP, imlInstruction->op_fpr_r_r.registerOperand, 3); + x64Gen_shufpd_xmmReg_xmmReg_imm8(x64GenContext, REG_RESV_FPR_TEMP, REG_RESV_FPR_TEMP, 1); // swap top and bottom + + x64Gen_call_reg64(x64GenContext, REG_RESV_TEMP); // calculate fres result in xmm15 + + x64Gen_unpcklpd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, REG_RESV_FPR_TEMP); // copy bottom to top + } + else + { + assert_dbg(); + } +} + +/* + * FPR = op (fprA, fprB) + */ +void PPCRecompilerX64Gen_imlInstruction_fpr_r_r_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction) +{ + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + + if (imlInstruction->operation == PPCREC_IML_OP_FPR_MULTIPLY_BOTTOM) + { + if (imlInstruction->crRegister != PPC_REC_INVALID_REGISTER) + { + assert_dbg(); + } + if (imlInstruction->op_fpr_r_r_r.registerResult == imlInstruction->op_fpr_r_r_r.registerOperandA) + { + x64Gen_mulsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r.registerOperandB); + } + else if (imlInstruction->op_fpr_r_r_r.registerResult == imlInstruction->op_fpr_r_r_r.registerOperandB) + { + x64Gen_mulsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r.registerOperandA); + } + else + { + x64Gen_movsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r.registerOperandA); + x64Gen_mulsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r.registerOperandB); + } + } + else if (imlInstruction->operation == PPCREC_IML_OP_FPR_ADD_BOTTOM) + { + // registerResult(fp0) = registerOperandA(fp0) + registerOperandB(fp0) + cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); + // todo: Use AVX 3-operand VADDSD if available + if (imlInstruction->op_fpr_r_r_r.registerResult == imlInstruction->op_fpr_r_r_r.registerOperandA) + { + x64Gen_addsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r.registerOperandB); + } + else if (imlInstruction->op_fpr_r_r_r.registerResult == imlInstruction->op_fpr_r_r_r.registerOperandB) + { + x64Gen_addsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r.registerOperandA); + } + else + { + x64Gen_movaps_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r.registerOperandA); + x64Gen_addsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r.registerOperandB); + } + } + else if (imlInstruction->operation == PPCREC_IML_OP_FPR_SUB_PAIR) + { + // registerResult = registerOperandA - registerOperandB + cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); + if( imlInstruction->op_fpr_r_r_r.registerResult == imlInstruction->op_fpr_r_r_r.registerOperandA ) + { + x64Gen_subpd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r.registerOperandB); + } + else if (hasAVXSupport) + { + x64Gen_avx_VSUBPD_xmm_xmm_xmm(x64GenContext, imlInstruction->op_fpr_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r.registerOperandA, imlInstruction->op_fpr_r_r_r.registerOperandB); + } + else if( imlInstruction->op_fpr_r_r_r.registerResult == imlInstruction->op_fpr_r_r_r.registerOperandB ) + { + x64Gen_movaps_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, imlInstruction->op_fpr_r_r_r.registerOperandA); + x64Gen_subpd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, imlInstruction->op_fpr_r_r_r.registerOperandB); + x64Gen_movaps_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r.registerResult, REG_RESV_FPR_TEMP); + } + else + { + x64Gen_movaps_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r.registerOperandA); + x64Gen_subpd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r.registerOperandB); + } + } + else if( imlInstruction->operation == PPCREC_IML_OP_FPR_SUB_BOTTOM ) + { + cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); + if( imlInstruction->op_fpr_r_r_r.registerResult == imlInstruction->op_fpr_r_r_r.registerOperandA ) + { + x64Gen_subsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r.registerOperandB); + } + else if( imlInstruction->op_fpr_r_r_r.registerResult == imlInstruction->op_fpr_r_r_r.registerOperandB ) + { + x64Gen_movsd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, imlInstruction->op_fpr_r_r_r.registerOperandA); + x64Gen_subsd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, imlInstruction->op_fpr_r_r_r.registerOperandB); + x64Gen_movsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r.registerResult, REG_RESV_FPR_TEMP); + } + else + { + x64Gen_movsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r.registerOperandA); + x64Gen_subsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r.registerOperandB); + } + } + else + assert_dbg(); +} + +/* + * FPR = op (fprA, fprB, fprC) + */ +void PPCRecompilerX64Gen_imlInstruction_fpr_r_r_r_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction) +{ + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + if( imlInstruction->operation == PPCREC_IML_OP_FPR_SUM0 ) + { + cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); + + // todo: Investigate if there are other optimizations possible if the operand registers overlap + // generic case + // 1) move frA bottom to frTemp bottom and top + x64Gen_movddup_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, imlInstruction->op_fpr_r_r_r_r.registerOperandA); + // 2) add frB (both halfs, lower half is overwritten in the next step) + x64Gen_addpd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, imlInstruction->op_fpr_r_r_r_r.registerOperandB); + // 3) Interleave top of frTemp and frC + x64Gen_unpckhpd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, imlInstruction->op_fpr_r_r_r_r.registerOperandC); + // todo: We can optimize the REG_RESV_FPR_TEMP -> resultReg copy operation away when the result register does not overlap with any of the operand registers + x64Gen_movaps_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r_r.registerResult, REG_RESV_FPR_TEMP); + } + else if( imlInstruction->operation == PPCREC_IML_OP_FPR_SUM1 ) + { + cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); + // todo: Investigate if there are other optimizations possible if the operand registers overlap + // 1) move frA bottom to frTemp bottom and top + x64Gen_movddup_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, imlInstruction->op_fpr_r_r_r_r.registerOperandA); + // 2) add frB (both halfs, lower half is overwritten in the next step) + x64Gen_addpd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, imlInstruction->op_fpr_r_r_r_r.registerOperandB); + // 3) Copy bottom from frC + x64Gen_movsd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, imlInstruction->op_fpr_r_r_r_r.registerOperandC); + //// 4) Swap bottom and top half + //x64Gen_shufpd_xmmReg_xmmReg_imm8(x64GenContext, REG_RESV_FPR_TEMP, REG_RESV_FPR_TEMP, 1); + // todo: We can optimize the REG_RESV_FPR_TEMP -> resultReg copy operation away when the result register does not overlap with any of the operand registers + x64Gen_movaps_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r_r.registerResult, REG_RESV_FPR_TEMP); + + //float s0 = (float)hCPU->fpr[frC].fp0; + //float s1 = (float)(hCPU->fpr[frA].fp0 + hCPU->fpr[frB].fp1); + //hCPU->fpr[frD].fp0 = s0; + //hCPU->fpr[frD].fp1 = s1; + } + else if( imlInstruction->operation == PPCREC_IML_OP_FPR_SELECT_BOTTOM ) + { + cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); + x64Gen_comisd_xmmReg_mem64Reg64(x64GenContext, imlInstruction->op_fpr_r_r_r_r.registerOperandA, REG_RESV_RECDATA, offsetof(PPCRecompilerInstanceData_t, _x64XMM_constDouble0_0)); + sint32 jumpInstructionOffset1 = x64GenContext->codeBufferIndex; + x64Gen_jmpc_near(x64GenContext, X86_CONDITION_UNSIGNED_BELOW, 0); + // select C + x64Gen_movsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r_r.registerOperandC); + sint32 jumpInstructionOffset2 = x64GenContext->codeBufferIndex; + x64Gen_jmpc_near(x64GenContext, X86_CONDITION_NONE, 0); + // select B + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset1, x64GenContext->codeBufferIndex); + x64Gen_movsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r_r.registerOperandB); + // end + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset2, x64GenContext->codeBufferIndex); + } + else if( imlInstruction->operation == PPCREC_IML_OP_FPR_SELECT_PAIR ) + { + cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); + // select bottom + x64Gen_comisd_xmmReg_mem64Reg64(x64GenContext, imlInstruction->op_fpr_r_r_r_r.registerOperandA, REG_RESV_RECDATA, offsetof(PPCRecompilerInstanceData_t, _x64XMM_constDouble0_0)); + sint32 jumpInstructionOffset1_bottom = x64GenContext->codeBufferIndex; + x64Gen_jmpc_near(x64GenContext, X86_CONDITION_UNSIGNED_BELOW, 0); + // select C bottom + x64Gen_movsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r_r.registerOperandC); + sint32 jumpInstructionOffset2_bottom = x64GenContext->codeBufferIndex; + x64Gen_jmpc_near(x64GenContext, X86_CONDITION_NONE, 0); + // select B bottom + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset1_bottom, x64GenContext->codeBufferIndex); + x64Gen_movsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r_r.registerOperandB); + // end + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset2_bottom, x64GenContext->codeBufferIndex); + // select top + x64Gen_movhlps_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, imlInstruction->op_fpr_r_r_r_r.registerOperandA); // copy top to bottom (todo: May cause stall?) + x64Gen_comisd_xmmReg_mem64Reg64(x64GenContext, REG_RESV_FPR_TEMP, REG_RESV_RECDATA, offsetof(PPCRecompilerInstanceData_t, _x64XMM_constDouble0_0)); + sint32 jumpInstructionOffset1_top = x64GenContext->codeBufferIndex; + x64Gen_jmpc_near(x64GenContext, X86_CONDITION_UNSIGNED_BELOW, 0); + // select C top + //x64Gen_movsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r_r.registerOperandC); + x64Gen_shufpd_xmmReg_xmmReg_imm8(x64GenContext, imlInstruction->op_fpr_r_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r_r.registerOperandC, 2); + sint32 jumpInstructionOffset2_top = x64GenContext->codeBufferIndex; + x64Gen_jmpc_near(x64GenContext, X86_CONDITION_NONE, 0); + // select B top + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset1_top, x64GenContext->codeBufferIndex); + //x64Gen_movsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r_r.registerOperandB); + x64Gen_shufpd_xmmReg_xmmReg_imm8(x64GenContext, imlInstruction->op_fpr_r_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r_r.registerOperandB, 2); + // end + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset2_top, x64GenContext->codeBufferIndex); + } + else + assert_dbg(); +} + +/* + * Single FPR operation + */ +void PPCRecompilerX64Gen_imlInstruction_fpr_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction) +{ + PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + if( imlInstruction->operation == PPCREC_IML_OP_FPR_NEGATE_BOTTOM ) + { + cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); + // toggle sign bit + x64Gen_xorps_xmmReg_mem128Reg64(x64GenContext, imlInstruction->op_fpr_r.registerResult, REG_RESV_RECDATA, offsetof(PPCRecompilerInstanceData_t, _x64XMM_xorNegateMaskBottom)); + } + else if( imlInstruction->operation == PPCREC_IML_OP_FPR_ABS_BOTTOM ) + { + cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); + // mask out sign bit + x64Gen_andps_xmmReg_mem128Reg64(x64GenContext, imlInstruction->op_fpr_r.registerResult, REG_RESV_RECDATA, offsetof(PPCRecompilerInstanceData_t, _x64XMM_andAbsMaskBottom)); + } + else if( imlInstruction->operation == PPCREC_IML_OP_FPR_NEGATIVE_ABS_BOTTOM ) + { + cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); + // set sign bit + x64Gen_orps_xmmReg_mem128Reg64(x64GenContext, imlInstruction->op_fpr_r.registerResult, REG_RESV_RECDATA, offsetof(PPCRecompilerInstanceData_t, _x64XMM_xorNegateMaskBottom)); + } + else if( imlInstruction->operation == PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_BOTTOM ) + { + cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); + // convert to 32bit single + x64Gen_cvtsd2ss_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r.registerResult, imlInstruction->op_fpr_r.registerResult); + // convert back to 64bit double + x64Gen_cvtss2sd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r.registerResult, imlInstruction->op_fpr_r.registerResult); + } + else if( imlInstruction->operation == PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_PAIR ) + { + cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); + // convert to 32bit singles + x64Gen_cvtpd2ps_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r.registerResult, imlInstruction->op_fpr_r.registerResult); + // convert back to 64bit doubles + x64Gen_cvtps2pd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r.registerResult, imlInstruction->op_fpr_r.registerResult); + } + else if (imlInstruction->operation == PPCREC_IML_OP_FPR_EXPAND_BOTTOM32_TO_BOTTOM64_AND_TOP64) + { + cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); + // convert bottom to 64bit double + x64Gen_cvtss2sd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r.registerResult, imlInstruction->op_fpr_r.registerResult); + // copy to top half + x64Gen_movddup_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r.registerResult, imlInstruction->op_fpr_r.registerResult); + } + else + { + cemu_assert_unimplemented(); + } +} diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64Gen.cpp b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64Gen.cpp new file mode 100644 index 00000000..19327f46 --- /dev/null +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64Gen.cpp @@ -0,0 +1,1885 @@ +#include "PPCRecompiler.h" +#include "PPCRecompilerIml.h" +#include "PPCRecompilerX64.h" + +// x86/x64 extension opcodes that could be useful: +// ANDN +// mulx, rorx, sarx, shlx, shrx +// PDEP, PEXT + +void x64Gen_checkBuffer(x64GenContext_t* x64GenContext) +{ + // todo +} + +void x64Gen_writeU8(x64GenContext_t* x64GenContext, uint8 v) +{ + if( x64GenContext->codeBufferIndex+1 > x64GenContext->codeBufferSize ) + { + x64GenContext->codeBufferSize *= 2; + x64GenContext->codeBuffer = (uint8*)realloc(x64GenContext->codeBuffer, x64GenContext->codeBufferSize); + } + *(uint8*)(x64GenContext->codeBuffer+x64GenContext->codeBufferIndex) = v; + x64GenContext->codeBufferIndex++; +} + +void x64Gen_writeU16(x64GenContext_t* x64GenContext, uint32 v) +{ + if( x64GenContext->codeBufferIndex+2 > x64GenContext->codeBufferSize ) + { + x64GenContext->codeBufferSize *= 2; + x64GenContext->codeBuffer = (uint8*)realloc(x64GenContext->codeBuffer, x64GenContext->codeBufferSize); + } + *(uint16*)(x64GenContext->codeBuffer+x64GenContext->codeBufferIndex) = v; + x64GenContext->codeBufferIndex += 2; +} + +void x64Gen_writeU32(x64GenContext_t* x64GenContext, uint32 v) +{ + if( x64GenContext->codeBufferIndex+4 > x64GenContext->codeBufferSize ) + { + x64GenContext->codeBufferSize *= 2; + x64GenContext->codeBuffer = (uint8*)realloc(x64GenContext->codeBuffer, x64GenContext->codeBufferSize); + } + *(uint32*)(x64GenContext->codeBuffer+x64GenContext->codeBufferIndex) = v; + x64GenContext->codeBufferIndex += 4; +} + +void x64Gen_writeU64(x64GenContext_t* x64GenContext, uint64 v) +{ + if( x64GenContext->codeBufferIndex+8 > x64GenContext->codeBufferSize ) + { + x64GenContext->codeBufferSize *= 2; + x64GenContext->codeBuffer = (uint8*)realloc(x64GenContext->codeBuffer, x64GenContext->codeBufferSize); + } + *(uint64*)(x64GenContext->codeBuffer+x64GenContext->codeBufferIndex) = v; + x64GenContext->codeBufferIndex += 8; +} + +#include "x64Emit.hpp" + +void _x64Gen_writeMODRMDeprecated(x64GenContext_t* x64GenContext, sint32 dataRegister, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32) +{ + bool forceUseOffset = false; + if ((memRegisterA64 & 7) == 5) + { + // RBP and R13 have no memImmS32 == 0 encoding, therefore we need to use a 1 byte offset with the value 0 + forceUseOffset = true; + } + + if (memRegisterB64 == REG_NONE) + { + // memRegisterA64 + memImmS32 + uint8 modRM = (dataRegister & 7) * 8 + (memRegisterA64 & 7); + if (forceUseOffset && memImmS32 == 0) + { + // 1 byte offset + modRM |= (1 << 6); + } + if (memImmS32 == 0) + { + // no offset + modRM |= (0 << 6); + } + else if (memImmS32 >= -128 && memImmS32 <= 127) + { + // 1 byte offset + modRM |= (1 << 6); + } + else + { + // 4 byte offset + modRM |= (2 << 6); + } + x64Gen_writeU8(x64GenContext, modRM); + // SIB byte + if ((memRegisterA64 & 7) == 4) // RSP and R12 + { + x64Gen_writeU8(x64GenContext, 0x24); + } + // offset + if (((modRM >> 6) & 3) == 0) + ; // no offset + else if (((modRM >> 6) & 3) == 1) + x64Gen_writeU8(x64GenContext, (uint8)memImmS32); + else if (((modRM >> 6) & 3) == 2) + x64Gen_writeU32(x64GenContext, (uint32)memImmS32); + else + assert_dbg(); + return; + } + // note: Swapping mem register A and mem register B does not work because the instruction prefix defines the register group which might not match (e.g. regA in r0-r8 range and regB in RAX-RDI range) + if( (memRegisterA64&7) == 4 ) + { + assert_dbg(); + //sint32 temp = memRegisterA64; + //memRegisterA64 = memRegisterB64; + //memRegisterB64 = temp; + } + //if( (memRegisterA64&7) == 5 ) + //{ + // sint32 temp = memRegisterA64; + // memRegisterA64 = memRegisterB64; + // memRegisterB64 = temp; + //} + if( (memRegisterA64&7) == 4 ) + assert_dbg(); + uint8 modRM = (0x04<<0)+((dataRegister&7)<<3); + if( forceUseOffset && memImmS32 == 0 ) + { + // 1 byte offset + modRM |= (1<<6); + } + if( memImmS32 == 0 ) + { + // no offset + modRM |= (0<<6); + } + else if( memImmS32 >= -128 && memImmS32 <= 127 ) + { + // 1 byte offset + modRM |= (1<<6); + } + else + { + // 4 byte offset + modRM |= (2<<6); + } + x64Gen_writeU8(x64GenContext, modRM); + // sib byte + x64Gen_writeU8(x64GenContext, 0x00+(memRegisterA64&7)+(memRegisterB64&7)*8); + // offset + if( ((modRM>>6)&3) == 0 ) + ; // no offset + else if( ((modRM>>6)&3) == 1 ) + x64Gen_writeU8(x64GenContext, (uint8)memImmS32); + else if( ((modRM>>6)&3) == 2 ) + x64Gen_writeU32(x64GenContext, (uint32)memImmS32); + else + assert_dbg(); +} + +void x64Emit_mov_reg32_mem32(x64GenContext_t* x64GenContext, sint32 destReg, sint32 memBaseReg64, sint32 memOffset) +{ + x64Gen_writeMODRM_dyn<x64_opc_1byte<0x8B>>(x64GenContext, x64MODRM_opr_reg64(destReg), x64MODRM_opr_memReg64(memBaseReg64, memOffset)); +} + +void x64Emit_mov_mem32_reg32(x64GenContext_t* x64GenContext, sint32 memBaseReg64, sint32 memOffset, sint32 srcReg) +{ + x64Gen_writeMODRM_dyn<x64_opc_1byte_rev<0x89>>(x64GenContext, x64MODRM_opr_memReg64(memBaseReg64, memOffset), x64MODRM_opr_reg64(srcReg)); +} + +void x64Emit_mov_mem64_reg64(x64GenContext_t* x64GenContext, sint32 memBaseReg64, sint32 memOffset, sint32 srcReg) +{ + x64Gen_writeMODRM_dyn<x64_opc_1byte_rev<0x89, true>>(x64GenContext, x64MODRM_opr_memReg64(memBaseReg64, memOffset), x64MODRM_opr_reg64(srcReg)); +} + +void x64Emit_mov_reg64_mem64(x64GenContext_t* x64GenContext, sint32 destReg, sint32 memBaseReg64, sint32 memOffset) +{ + x64Gen_writeMODRM_dyn<x64_opc_1byte<0x8B, true>>(x64GenContext, x64MODRM_opr_reg64(destReg), x64MODRM_opr_memReg64(memBaseReg64, memOffset)); +} + +void x64Emit_mov_reg64_mem32(x64GenContext_t* x64GenContext, sint32 destReg, sint32 memBaseReg64, sint32 memOffset) +{ + x64Gen_writeMODRM_dyn<x64_opc_1byte<0x8B>>(x64GenContext, x64MODRM_opr_reg64(destReg), x64MODRM_opr_memReg64(memBaseReg64, memOffset)); +} + +void x64Emit_mov_mem32_reg64(x64GenContext_t* x64GenContext, sint32 memBaseReg64, sint32 memOffset, sint32 srcReg) +{ + x64Gen_writeMODRM_dyn<x64_opc_1byte_rev<0x89>>(x64GenContext, x64MODRM_opr_memReg64(memBaseReg64, memOffset), x64MODRM_opr_reg64(srcReg)); +} + +void x64Emit_mov_reg64_mem64(x64GenContext_t* x64GenContext, sint32 destReg, sint32 memBaseReg64, sint32 memIndexReg64, sint32 memOffset) +{ + x64Gen_writeMODRM_dyn<x64_opc_1byte<0x8B, true>>(x64GenContext, x64MODRM_opr_reg64(destReg), x64MODRM_opr_memRegPlusReg(memBaseReg64, memIndexReg64, memOffset)); +} + +void x64Emit_mov_reg32_mem32(x64GenContext_t* x64GenContext, sint32 destReg, sint32 memBaseReg64, sint32 memIndexReg64, sint32 memOffset) +{ + x64Gen_writeMODRM_dyn<x64_opc_1byte<0x8B>>(x64GenContext, x64MODRM_opr_reg64(destReg), x64MODRM_opr_memRegPlusReg(memBaseReg64, memIndexReg64, memOffset)); +} + +void x64Emit_mov_reg64b_mem8(x64GenContext_t* x64GenContext, sint32 destReg, sint32 memBaseReg64, sint32 memIndexReg64, sint32 memOffset) +{ + x64Gen_writeMODRM_dyn<x64_opc_1byte<0x8A>>(x64GenContext, x64MODRM_opr_reg64(destReg), x64MODRM_opr_memRegPlusReg(memBaseReg64, memIndexReg64, memOffset)); +} + +void x64Emit_movZX_reg32_mem8(x64GenContext_t* x64GenContext, sint32 destReg, sint32 memBaseReg64, sint32 memIndexReg64, sint32 memOffset) +{ + x64Gen_writeMODRM_dyn<x64_opc_2byte<0x0F,0xB6>>(x64GenContext, x64MODRM_opr_reg64(destReg), x64MODRM_opr_memRegPlusReg(memBaseReg64, memIndexReg64, memOffset)); +} + +void x64Emit_movZX_reg64_mem8(x64GenContext_t* x64GenContext, sint32 destReg, sint32 memBaseReg64, sint32 memOffset) +{ + x64Gen_writeMODRM_dyn<x64_opc_2byte<0x0F, 0xB6>>(x64GenContext, x64MODRM_opr_reg64(destReg), x64MODRM_opr_memReg64(memBaseReg64, memOffset)); +} + +void x64Gen_movSignExtend_reg64Low32_mem8Reg64PlusReg64(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32) +{ + // MOVSX <dstReg64> (low dword), BYTE [<reg64> + <reg64> + <imm64>] + if (dstRegister >= 8 && memRegisterA64 >= 8 && memRegisterB64 >= 8) + x64Gen_writeU8(x64GenContext, 0x47); + else if (memRegisterA64 >= 8 && memRegisterB64 >= 8) + x64Gen_writeU8(x64GenContext, 0x43); + else if (dstRegister >= 8 && memRegisterB64 >= 8) + x64Gen_writeU8(x64GenContext, 0x42); + else if (dstRegister >= 8 && memRegisterA64 >= 8) + x64Gen_writeU8(x64GenContext, 0x45); + else if (dstRegister >= 8) + x64Gen_writeU8(x64GenContext, 0x44); + else if (memRegisterA64 >= 8) + x64Gen_writeU8(x64GenContext, 0x41); + else if (memRegisterB64 >= 8) + x64Gen_writeU8(x64GenContext, 0x42); + else if (dstRegister >= 4) + x64Gen_writeU8(x64GenContext, 0x40); + + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0xBE); + _x64Gen_writeMODRMDeprecated(x64GenContext, dstRegister, memRegisterA64, memRegisterB64, memImmS32); +} + +void x64Gen_mov_mem64Reg64PlusReg64_reg64(x64GenContext_t* x64GenContext, sint32 srcRegister, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32) +{ + // MOV QWORD [<reg64> + <reg64> + <imm64>], <dstReg64> + if( srcRegister >= 8 && memRegisterA64 >= 8 && memRegisterB64 >= 8 ) + x64Gen_writeU8(x64GenContext, 0x47|8); + else if( memRegisterA64 >= 8 && memRegisterB64 >= 8 ) + x64Gen_writeU8(x64GenContext, 0x43|8); + else if( srcRegister >= 8 && memRegisterB64 >= 8 ) + x64Gen_writeU8(x64GenContext, 0x42|8); + else if( srcRegister >= 8 && memRegisterA64 >= 8 ) + x64Gen_writeU8(x64GenContext, 0x45|8); + else if( srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x44|8); + else if( memRegisterA64 >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41|8); + else if( memRegisterB64 >= 8 ) + x64Gen_writeU8(x64GenContext, 0x42|8); + else + x64Gen_writeU8(x64GenContext, 0x48); + x64Gen_writeU8(x64GenContext, 0x89); + _x64Gen_writeMODRMDeprecated(x64GenContext, srcRegister, memRegisterA64, memRegisterB64, memImmS32); +} + +void x64Gen_movZeroExtend_reg64Low16_mem16Reg64PlusReg64(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32) +{ + // MOV <dstReg64> (low word), WORD [<reg64> + <reg64> + <imm64>] + x64Gen_writeU8(x64GenContext, 0x66); // 16bit prefix + x64Emit_mov_reg32_mem32(x64GenContext, dstRegister, memRegisterA64, memRegisterB64, memImmS32); +} + +void x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext_t* x64GenContext, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32, sint32 srcRegister) +{ + // MOV DWORD [<reg64> + <reg64> + <imm64>], <srcReg64> (low dword) + if( srcRegister >= 8 && memRegisterA64 >= 8 && memRegisterB64 >= 8 ) + x64Gen_writeU8(x64GenContext, 0x47); + else if( memRegisterA64 >= 8 && memRegisterB64 >= 8 ) + x64Gen_writeU8(x64GenContext, 0x43); + else if( srcRegister >= 8 && memRegisterB64 >= 8 ) + x64Gen_writeU8(x64GenContext, 0x42); + else if( srcRegister >= 8 && memRegisterA64 >= 8 ) + x64Gen_writeU8(x64GenContext, 0x45); + else if( srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x44); + else if( memRegisterA64 >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + else if( memRegisterB64 >= 8 ) + x64Gen_writeU8(x64GenContext, 0x42); + + x64Gen_writeU8(x64GenContext, 0x89); + _x64Gen_writeMODRMDeprecated(x64GenContext, srcRegister, memRegisterA64, memRegisterB64, memImmS32); +} + +void x64Gen_movTruncate_mem16Reg64PlusReg64_reg64(x64GenContext_t* x64GenContext, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32, sint32 srcRegister) +{ + // MOV WORD [<reg64> + <reg64> + <imm64>], <srcReg64> (low dword) + x64Gen_writeU8(x64GenContext, 0x66); // 16bit prefix + x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, memRegisterA64, memRegisterB64, memImmS32, srcRegister); +} + +void x64Gen_movTruncate_mem8Reg64PlusReg64_reg64(x64GenContext_t* x64GenContext, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32, sint32 srcRegister) +{ + // MOV BYTE [<reg64> + <reg64> + <imm64>], <srcReg64> (low byte) + + // when no REX byte is present: Source register can range from AL to BH + // when a REX byte is present: Source register can range from AL to DIL or R8B to R15B + // todo: We don't need the REX byte when when the source register is AL,BL,CL or DL and neither memRegister A or B are within r8 - r15 + + uint8 rexByte = 0x40; + if( srcRegister >= 8 ) + rexByte |= (1<<2); + if( memRegisterA64 >= 8 ) + rexByte |= (1<<0); + if( memRegisterB64 >= 8 ) + rexByte |= (1<<1); + x64Gen_writeU8(x64GenContext, rexByte); + + x64Gen_writeU8(x64GenContext, 0x88); + _x64Gen_writeMODRMDeprecated(x64GenContext, srcRegister, memRegisterA64, memRegisterB64, memImmS32); +} + +void x64Gen_mov_mem32Reg64_imm32(x64GenContext_t* x64GenContext, sint32 memRegister, uint32 memImmU32, uint32 dataImmU32) +{ + // MOV DWORD [<memReg>+<memImmU32>], dataImmU32 + if( (memRegister&7) == 4 ) + { + if( memRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + sint32 memImmS32 = (sint32)memImmU32; + if( memImmS32 >= -128 && memImmS32 <= 127 ) + { + x64Gen_writeU8(x64GenContext, 0xC7); + x64Gen_writeU8(x64GenContext, 0x44); + x64Gen_writeU8(x64GenContext, 0x24); + x64Gen_writeU8(x64GenContext, (uint8)memImmU32); + } + else + { + x64Gen_writeU8(x64GenContext, 0xC7); + x64Gen_writeU8(x64GenContext, 0x84); + x64Gen_writeU8(x64GenContext, 0x24); + x64Gen_writeU32(x64GenContext, memImmU32); + } + x64Gen_writeU32(x64GenContext, dataImmU32); + } + else + { + assert_dbg(); + } +} + +void x64Gen_mov_mem64Reg64_imm32(x64GenContext_t* x64GenContext, sint32 memRegister, uint32 memImmU32, uint32 dataImmU32) +{ + // MOV QWORD [<memReg>+<memImmU32>], dataImmU32 + if( memRegister == REG_R14 ) + { + sint32 memImmS32 = (sint32)memImmU32; + if( memImmS32 == 0 ) + { + x64Gen_writeU8(x64GenContext, 0x49); + x64Gen_writeU8(x64GenContext, 0xC7); + x64Gen_writeU8(x64GenContext, 0x06); + x64Gen_writeU32(x64GenContext, dataImmU32); + } + else if( memImmS32 >= -128 && memImmS32 <= 127 ) + { + x64Gen_writeU8(x64GenContext, 0x49); + x64Gen_writeU8(x64GenContext, 0xC7); + x64Gen_writeU8(x64GenContext, 0x46); + x64Gen_writeU8(x64GenContext, (uint8)memImmS32); + x64Gen_writeU32(x64GenContext, dataImmU32); + } + else + { + assert_dbg(); + } + } + else + { + assert_dbg(); + } +} + +void x64Gen_mov_mem8Reg64_imm8(x64GenContext_t* x64GenContext, sint32 memRegister, uint32 memImmU32, uint8 dataImmU8) +{ + // MOV BYTE [<memReg64>+<memImmU32>], dataImmU8 + if( memRegister == REG_RSP ) + { + sint32 memImmS32 = (sint32)memImmU32; + if( memImmS32 >= -128 && memImmS32 <= 127 ) + { + x64Gen_writeU8(x64GenContext, 0xC6); + x64Gen_writeU8(x64GenContext, 0x44); + x64Gen_writeU8(x64GenContext, 0x24); + x64Gen_writeU8(x64GenContext, (uint8)memImmU32); + } + else + { + x64Gen_writeU8(x64GenContext, 0xC6); + x64Gen_writeU8(x64GenContext, 0x84); + x64Gen_writeU8(x64GenContext, 0x24); + x64Gen_writeU32(x64GenContext, memImmU32); + } + x64Gen_writeU8(x64GenContext, dataImmU8); + } + else + { + assert_dbg(); + } +} + +void x64Gen_mov_reg64_imm64(x64GenContext_t* x64GenContext, sint32 destRegister, uint64 immU64) +{ + // MOV <destReg64>, <imm64> + x64Gen_writeU8(x64GenContext, 0x48+(destRegister/8)); + x64Gen_writeU8(x64GenContext, 0xB8+(destRegister%8)); + x64Gen_writeU64(x64GenContext, immU64); +} + +void x64Gen_mov_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 destRegister, uint64 immU32) +{ + // todo: Emit shorter opcode if immU32 is 0 or falls in sint8 range? + // MOV <destReg64>, <imm64> + if( destRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + x64Gen_writeU8(x64GenContext, 0xB8+(destRegister&7)); + x64Gen_writeU32(x64GenContext, (uint32)immU32); +} + +void x64Gen_mov_reg64_reg64(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister) +{ + // MOV <destReg64>, <srcReg64> + if( destRegister >= 8 && srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x4D); + else if( destRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x49); + else if( srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x4C); + else + x64Gen_writeU8(x64GenContext, 0x48); + x64Gen_writeU8(x64GenContext, 0x89); + x64Gen_writeU8(x64GenContext, 0xC0+(destRegister&7)+(srcRegister&7)*8); +} + +void x64Gen_xchg_reg64_reg64(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister) +{ + // XCHG <destReg64>, <srcReg64> + if( destRegister >= 8 && srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x4D); + else if( destRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x49); + else if( srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x4C); + else + x64Gen_writeU8(x64GenContext, 0x48); + x64Gen_writeU8(x64GenContext, 0x87); + x64Gen_writeU8(x64GenContext, 0xC0+(destRegister&7)+(srcRegister&7)*8); +} + +void x64Gen_mov_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister) +{ + // MOV <destReg64_low32>, <srcReg64_low32> + if (destRegister >= 8 && srcRegister >= 8) + x64Gen_writeU8(x64GenContext, 0x45); + else if (destRegister >= 8) + x64Gen_writeU8(x64GenContext, 0x41); + else if (srcRegister >= 8) + x64Gen_writeU8(x64GenContext, 0x44); + x64Gen_writeU8(x64GenContext, 0x89); + x64Gen_writeU8(x64GenContext, 0xC0 + (destRegister & 7) + (srcRegister & 7) * 8); +} + +void x64Gen_cmovcc_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, uint32 conditionType, sint32 destRegister, sint32 srcRegister) +{ + // cMOVcc <destReg64_low32>, <srcReg64_low32> + if (destRegister >= 8 && srcRegister >= 8) + x64Gen_writeU8(x64GenContext, 0x45); + else if (srcRegister >= 8) + x64Gen_writeU8(x64GenContext, 0x41); + else if (destRegister >= 8) + x64Gen_writeU8(x64GenContext, 0x44); + x64Gen_writeU8(x64GenContext, 0x0F); + if (conditionType == X86_CONDITION_CARRY || conditionType == X86_CONDITION_UNSIGNED_BELOW) + x64Gen_writeU8(x64GenContext, 0x42); + else if (conditionType == X86_CONDITION_NOT_CARRY || conditionType == X86_CONDITION_UNSIGNED_ABOVE_EQUAL) + x64Gen_writeU8(x64GenContext, 0x43); + else if (conditionType == X86_CONDITION_EQUAL) + x64Gen_writeU8(x64GenContext, 0x44); + else if (conditionType == X86_CONDITION_NOT_EQUAL) + x64Gen_writeU8(x64GenContext, 0x45); + else if (conditionType == X86_CONDITION_UNSIGNED_BELOW_EQUAL) + x64Gen_writeU8(x64GenContext, 0x46); + else if (conditionType == X86_CONDITION_UNSIGNED_ABOVE) + x64Gen_writeU8(x64GenContext, 0x47); + else if (conditionType == X86_CONDITION_SIGN) + x64Gen_writeU8(x64GenContext, 0x48); + else if (conditionType == X86_CONDITION_NOT_SIGN) + x64Gen_writeU8(x64GenContext, 0x49); + else if (conditionType == X86_CONDITION_PARITY) + x64Gen_writeU8(x64GenContext, 0x4A); + else if (conditionType == X86_CONDITION_SIGNED_LESS) + x64Gen_writeU8(x64GenContext, 0x4C); + else if (conditionType == X86_CONDITION_SIGNED_GREATER_EQUAL) + x64Gen_writeU8(x64GenContext, 0x4D); + else if (conditionType == X86_CONDITION_SIGNED_LESS_EQUAL) + x64Gen_writeU8(x64GenContext, 0x4E); + else if (conditionType == X86_CONDITION_SIGNED_GREATER) + x64Gen_writeU8(x64GenContext, 0x4F); + else + { + assert_dbg(); + } + x64Gen_writeU8(x64GenContext, 0xC0 + (destRegister & 7) * 8 + (srcRegister & 7)); +} + +void x64Gen_movSignExtend_reg64Low32_reg64Low16(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister) +{ + // MOVSX <destReg64_lowDWORD>, <srcReg64_lowWORD> + if( destRegister >= 8 && srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x4D); + else if( srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + else if( destRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x4C); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0xBF); + x64Gen_writeU8(x64GenContext, 0xC0+(srcRegister&7)+(destRegister&7)*8); +} + +void x64Gen_movZeroExtend_reg64Low32_reg64Low16(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister) +{ + // MOVZX <destReg64_lowDWORD>, <srcReg64_lowWORD> + if( destRegister >= 8 && srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x4D); + else if( srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + else if( destRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x4C); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0xB7); + x64Gen_writeU8(x64GenContext, 0xC0+(srcRegister&7)+(destRegister&7)*8); +} + +void x64Gen_movSignExtend_reg64Low32_reg64Low8(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister) +{ + // MOVSX <destReg64_lowDWORD>, <srcReg64_lowBYTE> + if( destRegister >= 8 && srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x4D); + else if( srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + else if( destRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x4C); + else if( srcRegister >= 4 ) + x64Gen_writeU8(x64GenContext, 0x40); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0xBE); + x64Gen_writeU8(x64GenContext, 0xC0+(srcRegister&7)+(destRegister&7)*8); +} + +void x64Gen_movZeroExtend_reg64Low32_reg64Low8(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister) +{ + // MOVZX <destReg64_lowDWORD>, <srcReg64_lowBYTE> + if( destRegister >= 8 && srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x4D); + else if( srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + else if( destRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x4C); + else if( srcRegister >= 4 ) + x64Gen_writeU8(x64GenContext, 0x40); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0xB6); + x64Gen_writeU8(x64GenContext, 0xC0+(srcRegister&7)+(destRegister&7)*8); +} + +void x64Gen_lea_reg64Low32_reg64Low32PlusReg64Low32(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 memRegisterA64, sint32 memRegisterB64) +{ + // MOV <reg32>, DWORD [<reg32> + <reg32>] + if ((memRegisterA64 & 0x7) == 5) + { + // RBP + // swap mem registers to get the shorter instruction encoding + sint32 temp = memRegisterA64; + memRegisterA64 = memRegisterB64; + memRegisterB64 = temp; + } + if ((memRegisterA64 & 0x7) == 4) + { + // RSP + // swap mem registers + sint32 temp = memRegisterA64; + memRegisterA64 = memRegisterB64; + memRegisterB64 = temp; + if ((memRegisterA64 & 0x7) == 4) + assert_dbg(); // double RSP not supported + } + + x64Gen_writeU8(x64GenContext, 0x67); + if (dstRegister >= 8 && memRegisterA64 >= 8 && memRegisterB64 >= 8) + x64Gen_writeU8(x64GenContext, 0x47); + else if (dstRegister >= 8 && memRegisterA64 >= 8) + x64Gen_writeU8(x64GenContext, 0x45); + else if (dstRegister >= 8 && memRegisterB64 >= 8) + x64Gen_writeU8(x64GenContext, 0x46); + else if (dstRegister >= 8) + x64Gen_writeU8(x64GenContext, 0x44); + else if (memRegisterA64 >= 8 && memRegisterB64 >= 8) + x64Gen_writeU8(x64GenContext, 0x43); + else if (memRegisterB64 >= 8) + x64Gen_writeU8(x64GenContext, 0x42); + else if (memRegisterA64 >= 8) + x64Gen_writeU8(x64GenContext, 0x41); + + x64Gen_writeU8(x64GenContext, 0x8D); + _x64Gen_writeMODRMDeprecated(x64GenContext, dstRegister&0x7, memRegisterA64 & 0x7, memRegisterB64 & 0x7, 0); +} + +void _x64_op_reg64Low_mem8Reg64(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 memRegister64, sint32 memImmS32, uint8 opByte) +{ + // OR <dstReg64> (low byte), BYTE [<reg64> + <imm64>] + if (dstRegister >= 8 && memRegister64 >= 8) + x64Gen_writeU8(x64GenContext, 0x45); + if (dstRegister >= 8) + x64Gen_writeU8(x64GenContext, 0x44); + if (memRegister64 >= 8) + x64Gen_writeU8(x64GenContext, 0x41); + x64Gen_writeU8(x64GenContext, opByte); + _x64Gen_writeMODRMDeprecated(x64GenContext, dstRegister, memRegister64, REG_NONE, memImmS32); +} + +void x64Gen_or_reg64Low8_mem8Reg64(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 memRegister64, sint32 memImmS32) +{ + _x64_op_reg64Low_mem8Reg64(x64GenContext, dstRegister, memRegister64, memImmS32, 0x0A); +} + +void x64Gen_and_reg64Low8_mem8Reg64(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 memRegister64, sint32 memImmS32) +{ + _x64_op_reg64Low_mem8Reg64(x64GenContext, dstRegister, memRegister64, memImmS32, 0x22); +} + +void x64Gen_mov_mem8Reg64_reg64Low8(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 memRegister64, sint32 memImmS32) +{ + _x64_op_reg64Low_mem8Reg64(x64GenContext, dstRegister, memRegister64, memImmS32, 0x88); +} + +void x64Gen_lock_cmpxchg_mem32Reg64PlusReg64_reg64(x64GenContext_t* x64GenContext, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32, sint32 srcRegister) +{ + // LOCK CMPXCHG DWORD [<reg64> + <reg64> + <imm64>], <srcReg64> (low dword) + x64Gen_writeU8(x64GenContext, 0xF0); // LOCK prefix + + if( srcRegister >= 8 || memRegisterA64 >= 8|| memRegisterB64 >= 8 ) + x64Gen_writeU8(x64GenContext, 0x40+((srcRegister>=8)?4:0)+((memRegisterA64>=8)?1:0)+((memRegisterB64>=8)?2:0)); + + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0xB1); + + _x64Gen_writeMODRMDeprecated(x64GenContext, srcRegister, memRegisterA64, memRegisterB64, memImmS32); +} + +void x64Gen_lock_cmpxchg_mem32Reg64_reg64(x64GenContext_t* x64GenContext, sint32 memRegister64, sint32 memImmS32, sint32 srcRegister) +{ + // LOCK CMPXCHG DWORD [<reg64> + <imm64>], <srcReg64> (low dword) + x64Gen_writeU8(x64GenContext, 0xF0); // LOCK prefix + + if( srcRegister >= 8 || memRegister64 >= 8 ) + x64Gen_writeU8(x64GenContext, 0x40+((srcRegister>=8)?4:0)+((memRegister64>=8)?1:0)); + + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0xB1); + + if( memImmS32 == 0 ) + { + x64Gen_writeU8(x64GenContext, 0x45+(srcRegister&7)*8); + x64Gen_writeU8(x64GenContext, 0x00); + } + else + assert_dbg(); +} + +void x64Gen_add_reg64_reg64(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister) +{ + // ADD <destReg>, <srcReg> + x64Gen_writeU8(x64GenContext, 0x48+(destRegister/8)+(srcRegister/8)*4); + x64Gen_writeU8(x64GenContext, 0x01); + x64Gen_writeU8(x64GenContext, 0xC0+(srcRegister&7)*8+(destRegister&7)); +} + +void x64Gen_add_reg64_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, uint32 immU32) +{ + sint32 immS32 = (sint32)immU32; + if (srcRegister >= 8) + x64Gen_writeU8(x64GenContext, 0x49); + else + x64Gen_writeU8(x64GenContext, 0x48); + if (immS32 >= -128 && immS32 <= 127) + { + x64Gen_writeU8(x64GenContext, 0x83); + x64Gen_writeU8(x64GenContext, 0xC0 + (srcRegister & 7)); + x64Gen_writeU8(x64GenContext, (uint8)immS32); + } + else + { + x64Gen_writeU8(x64GenContext, 0x81); + x64Gen_writeU8(x64GenContext, 0xC0 + (srcRegister & 7)); + x64Gen_writeU32(x64GenContext, immU32); + } +} + +void x64Gen_add_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister) +{ + // ADD <destReg64_low32>, <srcReg64_low32> + if( destRegister >= 8 && srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x45); + else if( srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x44); + else if( destRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + x64Gen_writeU8(x64GenContext, 0x01); + x64Gen_writeU8(x64GenContext, 0xC0+(srcRegister&7)*8+(destRegister&7)); +} + +void x64Gen_add_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, uint32 immU32) +{ + sint32 immS32 = (sint32)immU32; + if( srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + if( immS32 >= -128 && immS32 <= 127 ) + { + x64Gen_writeU8(x64GenContext, 0x83); + x64Gen_writeU8(x64GenContext, 0xC0+(srcRegister&7)); + x64Gen_writeU8(x64GenContext, (uint8)immS32); + } + else + { + if( srcRegister == REG_RAX ) + { + // special EAX short form + x64Gen_writeU8(x64GenContext, 0x05); + } + else + { + x64Gen_writeU8(x64GenContext, 0x81); + x64Gen_writeU8(x64GenContext, 0xC0+(srcRegister&7)); + } + x64Gen_writeU32(x64GenContext, immU32); + } +} + +void x64Gen_sub_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister) +{ + // SUB <destReg64_low32>, <srcReg64_low32> + if( destRegister >= 8 && srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x45); + else if( srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x44); + else if( destRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + x64Gen_writeU8(x64GenContext, 0x29); + x64Gen_writeU8(x64GenContext, 0xC0+(srcRegister&7)*8+(destRegister&7)); +} + +void x64Gen_sub_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, uint32 immU32) +{ + sint32 immS32 = (sint32)immU32; + if( srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + if( immS32 >= -128 && immS32 <= 127 ) + { + x64Gen_writeU8(x64GenContext, 0x83); + x64Gen_writeU8(x64GenContext, 0xE8+(srcRegister&7)); + x64Gen_writeU8(x64GenContext, (uint8)immS32); + } + else + { + if( srcRegister == REG_RAX ) + { + // special EAX short form + x64Gen_writeU8(x64GenContext, 0x2D); + } + else + { + x64Gen_writeU8(x64GenContext, 0x81); + x64Gen_writeU8(x64GenContext, 0xE8+(srcRegister&7)); + } + x64Gen_writeU32(x64GenContext, immU32); + } +} + +void x64Gen_sub_reg64_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, uint32 immU32) +{ + sint32 immS32 = (sint32)immU32; + if( srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x49); + else + x64Gen_writeU8(x64GenContext, 0x48); + if( immS32 >= -128 && immS32 <= 127 ) + { + x64Gen_writeU8(x64GenContext, 0x83); + x64Gen_writeU8(x64GenContext, 0xE8+(srcRegister&7)); + x64Gen_writeU8(x64GenContext, (uint8)immS32); + } + else + { + x64Gen_writeU8(x64GenContext, 0x81); + x64Gen_writeU8(x64GenContext, 0xE8+(srcRegister&7)); + x64Gen_writeU32(x64GenContext, immU32); + } +} + +void x64Gen_sub_mem32reg64_imm32(x64GenContext_t* x64GenContext, sint32 memRegister, sint32 memImmS32, uint64 immU32) +{ + // SUB <mem32_memReg64>, <imm32> + sint32 immS32 = (sint32)immU32; + if( memRegister == REG_RSP ) + { + if( memImmS32 >= 128 ) + { + if( immS32 >= -128 && immS32 <= 127 ) + { + // 4 byte mem imm + 1 byte imm + x64Gen_writeU8(x64GenContext, 0x83); + x64Gen_writeU8(x64GenContext, 0xAC); + x64Gen_writeU8(x64GenContext, 0x24); + x64Gen_writeU32(x64GenContext, (uint32)memImmS32); + x64Gen_writeU8(x64GenContext, (uint8)immU32); + } + else + { + // 4 byte mem imm + 4 byte imm + x64Gen_writeU8(x64GenContext, 0x81); + x64Gen_writeU8(x64GenContext, 0xAC); + x64Gen_writeU8(x64GenContext, 0x24); + x64Gen_writeU32(x64GenContext, (uint32)memImmS32); + x64Gen_writeU32(x64GenContext, (uint32)immU32); + } + } + else + assert_dbg(); + } + else + { + assert_dbg(); + } +} + +void x64Gen_sbb_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister) +{ + // SBB <destReg64_low32>, <srcReg64_low32> + if( destRegister >= 8 && srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x45); + else if( srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x44); + else if( destRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + x64Gen_writeU8(x64GenContext, 0x19); + x64Gen_writeU8(x64GenContext, 0xC0+(srcRegister&7)*8+(destRegister&7)); +} + +void x64Gen_adc_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister) +{ + // ADC <destReg64_low32>, <srcReg64_low32> + if( destRegister >= 8 && srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x45); + else if( srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x44); + else if( destRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + x64Gen_writeU8(x64GenContext, 0x11); + x64Gen_writeU8(x64GenContext, 0xC0+(srcRegister&7)*8+(destRegister&7)); +} + +void x64Gen_adc_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, uint32 immU32) +{ + sint32 immS32 = (sint32)immU32; + if( srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + if( immS32 >= -128 && immS32 <= 127 ) + { + x64Gen_writeU8(x64GenContext, 0x83); + x64Gen_writeU8(x64GenContext, 0xD0+(srcRegister&7)); + x64Gen_writeU8(x64GenContext, (uint8)immS32); + } + else + { + if( srcRegister == REG_RAX ) + { + // special EAX short form + x64Gen_writeU8(x64GenContext, 0x15); + } + else + { + x64Gen_writeU8(x64GenContext, 0x81); + x64Gen_writeU8(x64GenContext, 0xD0+(srcRegister&7)); + } + x64Gen_writeU32(x64GenContext, immU32); + } +} + +void x64Gen_dec_mem32(x64GenContext_t* x64GenContext, sint32 memoryRegister, uint32 memoryImmU32) +{ + // DEC dword [<reg64>+imm] + sint32 memoryImmS32 = (sint32)memoryImmU32; + if (memoryRegister != REG_RSP) + assert_dbg(); // not supported yet + if (memoryImmS32 >= -128 && memoryImmS32 <= 127) + { + x64Gen_writeU8(x64GenContext, 0xFF); + x64Gen_writeU8(x64GenContext, 0x4C); + x64Gen_writeU8(x64GenContext, 0x24); + x64Gen_writeU8(x64GenContext, (uint8)memoryImmU32); + } + else + { + x64Gen_writeU8(x64GenContext, 0xFF); + x64Gen_writeU8(x64GenContext, 0x8C); + x64Gen_writeU8(x64GenContext, 0x24); + x64Gen_writeU32(x64GenContext, memoryImmU32); + } +} + +void x64Gen_imul_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 operandRegister) +{ + // IMUL <destReg64_low32>, <operandReg64_low32> + if( destRegister >= 8 && operandRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x45); + else if( operandRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + else if( destRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x44); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0xAF); + x64Gen_writeU8(x64GenContext, 0xC0+(operandRegister&7)+(destRegister&7)*8); +} + +void x64Gen_idiv_reg64Low32(x64GenContext_t* x64GenContext, sint32 operandRegister) +{ + // IDIV <destReg64_low32> + if( operandRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + x64Gen_writeU8(x64GenContext, 0xF7); + x64Gen_writeU8(x64GenContext, 0xF8+(operandRegister&7)); +} + +void x64Gen_div_reg64Low32(x64GenContext_t* x64GenContext, sint32 operandRegister) +{ + // DIV <destReg64_low32> + if( operandRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + x64Gen_writeU8(x64GenContext, 0xF7); + x64Gen_writeU8(x64GenContext, 0xF0+(operandRegister&7)); +} + +void x64Gen_imul_reg64Low32(x64GenContext_t* x64GenContext, sint32 operandRegister) +{ + // IMUL <destReg64_low32> + if( operandRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + x64Gen_writeU8(x64GenContext, 0xF7); + x64Gen_writeU8(x64GenContext, 0xE8+(operandRegister&7)); +} + +void x64Gen_mul_reg64Low32(x64GenContext_t* x64GenContext, sint32 operandRegister) +{ + // MUL <destReg64_low32> + if( operandRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + x64Gen_writeU8(x64GenContext, 0xF7); + x64Gen_writeU8(x64GenContext, 0xE0+(operandRegister&7)); +} + +void x64Gen_and_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, uint32 immU32) +{ + sint32 immS32 = (sint32)immU32; + if( srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + if( immS32 >= -128 && immS32 <= 127 ) + { + x64Gen_writeU8(x64GenContext, 0x83); + x64Gen_writeU8(x64GenContext, 0xE0+(srcRegister&7)); + x64Gen_writeU8(x64GenContext, (uint8)immS32); + } + else + { + if( srcRegister == REG_RAX ) + { + // special EAX short form + x64Gen_writeU8(x64GenContext, 0x25); + } + else + { + x64Gen_writeU8(x64GenContext, 0x81); + x64Gen_writeU8(x64GenContext, 0xE0+(srcRegister&7)); + } + x64Gen_writeU32(x64GenContext, immU32); + } +} + +void x64Gen_and_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister) +{ + // AND <destReg64_lowDWORD>, <srcReg64_lowDWORD> + if( destRegister >= 8 && srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x45); + else if( srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x44); + else if( destRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + x64Gen_writeU8(x64GenContext, 0x21); + x64Gen_writeU8(x64GenContext, 0xC0+(destRegister&7)+(srcRegister&7)*8); +} + +void x64Gen_test_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister) +{ + // TEST <destReg64_lowDWORD>, <srcReg64_lowDWORD> + if( destRegister >= 8 && srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x45); + else if( srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + else if( destRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x44); + x64Gen_writeU8(x64GenContext, 0x85); + x64Gen_writeU8(x64GenContext, 0xC0+(destRegister&7)*8+(srcRegister&7)); +} + +void x64Gen_test_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, uint32 immU32) +{ + sint32 immS32 = (sint32)immU32; + if( srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + if( srcRegister == REG_RAX ) + { + // special EAX short form + x64Gen_writeU8(x64GenContext, 0xA9); + } + else + { + x64Gen_writeU8(x64GenContext, 0xF7); + x64Gen_writeU8(x64GenContext, 0xC0+(srcRegister&7)); + } + x64Gen_writeU32(x64GenContext, immU32); +} + +void x64Gen_cmp_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, sint32 immS32) +{ + if( srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + if( immS32 >= -128 && immS32 <= 127 ) + { + // 83 F8 00 CMP EAX,0 + x64Gen_writeU8(x64GenContext, 0x83); + x64Gen_writeU8(x64GenContext, 0xF8+(srcRegister&7)); + x64Gen_writeU8(x64GenContext, (uint8)immS32); + } + else + { + if( srcRegister == REG_RAX ) + { + // special RAX short form + x64Gen_writeU8(x64GenContext, 0x3D); + } + else + { + x64Gen_writeU8(x64GenContext, 0x81); + x64Gen_writeU8(x64GenContext, 0xF8+(srcRegister&7)); + } + x64Gen_writeU32(x64GenContext, (uint32)immS32); + } +} + +void x64Gen_cmp_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister) +{ + // CMP <destReg64_lowDWORD>, <srcReg64_lowDWORD> + if( destRegister >= 8 && srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x45); + else if( srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x44); + else if( destRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + x64Gen_writeU8(x64GenContext, 0x39); + x64Gen_writeU8(x64GenContext, 0xC0+(destRegister&7)+(srcRegister&7)*8); +} + +void x64Gen_cmp_reg64Low32_mem32reg64(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 memRegister, sint32 memImmS32) +{ + // CMP <destReg64_lowDWORD>, DWORD [<memRegister>+<immS32>] + if( memRegister == REG_RSP ) + { + if( memImmS32 >= -128 && memImmS32 <= 127 ) + assert_dbg(); // todo -> Shorter instruction form + if( destRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x44); + x64Gen_writeU8(x64GenContext, 0x3B); + x64Gen_writeU8(x64GenContext, 0x84+(destRegister&7)*8); + x64Gen_writeU8(x64GenContext, 0x24); + x64Gen_writeU32(x64GenContext, (uint32)memImmS32); + } + else + { + assert_dbg(); + } +} + +void x64Gen_or_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, uint32 immU32) +{ + sint32 immS32 = (sint32)immU32; + if( srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + if( immS32 >= -128 && immS32 <= 127 ) + { + x64Gen_writeU8(x64GenContext, 0x83); + x64Gen_writeU8(x64GenContext, 0xC8+(srcRegister&7)); + x64Gen_writeU8(x64GenContext, (uint8)immS32); + } + else + { + if( srcRegister == REG_RAX ) + { + // special EAX short form + x64Gen_writeU8(x64GenContext, 0x0D); + } + else + { + x64Gen_writeU8(x64GenContext, 0x81); + x64Gen_writeU8(x64GenContext, 0xC8+(srcRegister&7)); + } + x64Gen_writeU32(x64GenContext, immU32); + } +} + +void x64Gen_or_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister) +{ + // OR <destReg64_lowDWORD>, <srcReg64_lowDWORD> + if( destRegister >= 8 && srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x45); + else if( srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x44); + else if( destRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + x64Gen_writeU8(x64GenContext, 0x09); + x64Gen_writeU8(x64GenContext, 0xC0+(destRegister&7)+(srcRegister&7)*8); +} + +void x64Gen_xor_reg32_reg32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister) +{ + // XOR <destReg>, <srcReg> + x64Gen_writeU8(x64GenContext, 0x33); + x64Gen_writeU8(x64GenContext, 0xC0+srcRegister+destRegister*8); +} + +void x64Gen_xor_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister) +{ + // XOR <destReg64_lowDWORD>, <srcReg64_lowDWORD> + if( destRegister >= 8 && srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x45); + else if( srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x44); + else if( destRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + x64Gen_writeU8(x64GenContext, 0x31); + x64Gen_writeU8(x64GenContext, 0xC0+(destRegister&7)+(srcRegister&7)*8); +} + +void x64Gen_xor_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, uint32 immU32) +{ + sint32 immS32 = (sint32)immU32; + if( srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + if( immS32 >= -128 && immS32 <= 127 ) + { + x64Gen_writeU8(x64GenContext, 0x83); + x64Gen_writeU8(x64GenContext, 0xF0+(srcRegister&7)); + x64Gen_writeU8(x64GenContext, (uint8)immS32); + } + else + { + if( srcRegister == REG_RAX ) + { + // special EAX short form + x64Gen_writeU8(x64GenContext, 0x35); + } + else + { + x64Gen_writeU8(x64GenContext, 0x81); + x64Gen_writeU8(x64GenContext, 0xF0+(srcRegister&7)); + } + x64Gen_writeU32(x64GenContext, immU32); + } +} + +void x64Gen_rol_reg64Low32_imm8(x64GenContext_t* x64GenContext, sint32 srcRegister, sint8 immS8) +{ + if( srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + if( immS8 == 1 ) + { + // short form for 1 bit ROL + x64Gen_writeU8(x64GenContext, 0xD1); + x64Gen_writeU8(x64GenContext, 0xC0+(srcRegister&7)); + } + else + { + x64Gen_writeU8(x64GenContext, 0xC1); + x64Gen_writeU8(x64GenContext, 0xC0+(srcRegister&7)); + x64Gen_writeU8(x64GenContext, (uint8)immS8); + } +} + +void x64Gen_rol_reg64Low32_cl(x64GenContext_t* x64GenContext, sint32 srcRegister) +{ + if( srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + x64Gen_writeU8(x64GenContext, 0xD3); + x64Gen_writeU8(x64GenContext, 0xC0+(srcRegister&7)); +} + +void x64Gen_rol_reg64Low16_imm8(x64GenContext_t* x64GenContext, sint32 srcRegister, sint8 immS8) +{ + x64Gen_writeU8(x64GenContext, 0x66); // 16bit prefix + if( srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + if( immS8 == 1 ) + { + // short form for 1 bit ROL + x64Gen_writeU8(x64GenContext, 0xD1); + x64Gen_writeU8(x64GenContext, 0xC0+(srcRegister&7)); + } + else + { + x64Gen_writeU8(x64GenContext, 0xC1); + x64Gen_writeU8(x64GenContext, 0xC0+(srcRegister&7)); + x64Gen_writeU8(x64GenContext, (uint8)immS8); + } +} + +void x64Gen_rol_reg64_imm8(x64GenContext_t* x64GenContext, sint32 srcRegister, sint8 immS8) +{ + if( srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x49); + else + x64Gen_writeU8(x64GenContext, 0x48); + if( immS8 == 1 ) + { + // short form for 1 bit ROL + x64Gen_writeU8(x64GenContext, 0xD1); + x64Gen_writeU8(x64GenContext, 0xC0+(srcRegister&7)); + } + else + { + x64Gen_writeU8(x64GenContext, 0xC1); + x64Gen_writeU8(x64GenContext, 0xC0+(srcRegister&7)); + x64Gen_writeU8(x64GenContext, (uint8)immS8); + } +} + +void x64Gen_shl_reg64Low32_imm8(x64GenContext_t* x64GenContext, sint32 srcRegister, sint8 immS8) +{ + if( srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + if( immS8 == 1 ) + { + // short form for 1 bit SHL + x64Gen_writeU8(x64GenContext, 0xD1); + x64Gen_writeU8(x64GenContext, 0xF0+(srcRegister&7)); + } + else + { + x64Gen_writeU8(x64GenContext, 0xC1); + x64Gen_writeU8(x64GenContext, 0xF0+(srcRegister&7)); + x64Gen_writeU8(x64GenContext, (uint8)immS8); + } +} + +void x64Gen_shr_reg64Low32_imm8(x64GenContext_t* x64GenContext, sint32 srcRegister, sint8 immS8) +{ + if( srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + if( immS8 == 1 ) + { + // short form for 1 bit SHR + x64Gen_writeU8(x64GenContext, 0xD1); + x64Gen_writeU8(x64GenContext, 0xE8+(srcRegister&7)); + } + else + { + x64Gen_writeU8(x64GenContext, 0xC1); + x64Gen_writeU8(x64GenContext, 0xE8+(srcRegister&7)); + x64Gen_writeU8(x64GenContext, (uint8)immS8); + } +} + +void x64Gen_sar_reg64Low32_imm8(x64GenContext_t* x64GenContext, sint32 srcRegister, sint8 immS8) +{ + if( srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + if( immS8 == 1 ) + { + // short form for 1 bit ROL + x64Gen_writeU8(x64GenContext, 0xD1); + x64Gen_writeU8(x64GenContext, 0xF8+(srcRegister&7)); + } + else + { + x64Gen_writeU8(x64GenContext, 0xC1); + x64Gen_writeU8(x64GenContext, 0xF8+(srcRegister&7)); + x64Gen_writeU8(x64GenContext, (uint8)immS8); + } +} + +void x64Gen_not_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister) +{ + if( destRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + x64Gen_writeU8(x64GenContext, 0xF7); + x64Gen_writeU8(x64GenContext, 0xD0+(destRegister&7)); +} + +void x64Gen_neg_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister) +{ + if( destRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + x64Gen_writeU8(x64GenContext, 0xF7); + x64Gen_writeU8(x64GenContext, 0xD8+(destRegister&7)); +} + +void x64Gen_cdq(x64GenContext_t* x64GenContext) +{ + x64Gen_writeU8(x64GenContext, 0x99); +} + +void x64Gen_bswap_reg64(x64GenContext_t* x64GenContext, sint32 destRegister) +{ + if( destRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41|8); + else + x64Gen_writeU8(x64GenContext, 0x40|8); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0xC8+(destRegister&7)); +} + +void x64Gen_bswap_reg64Lower32bit(x64GenContext_t* x64GenContext, sint32 destRegister) +{ + if( destRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0xC8+(destRegister&7)); +} + +void x64Gen_bswap_reg64Lower16bit(x64GenContext_t* x64GenContext, sint32 destRegister) +{ + assert_dbg(); // do not use this instruction, it's result is always undefined. Instead use ROL <reg16>, 8 + //x64Gen_writeU8(x64GenContext, 0x66); + //if( destRegister >= 8 ) + // x64Gen_writeU8(x64GenContext, 0x41); + //x64Gen_writeU8(x64GenContext, 0x0F); + //x64Gen_writeU8(x64GenContext, 0xC8+(destRegister&7)); +} + +void x64Gen_lzcnt_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister) +{ + // SSE4 + // LZCNT <destReg>, <srcReg> + x64Gen_writeU8(x64GenContext, 0xF3); + if( destRegister >= 8 && srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x45); + else if( destRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x44); + else if( srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0xBD); + x64Gen_writeU8(x64GenContext, 0xC0+(destRegister&7)*8+(srcRegister&7)); +} + +void x64Gen_bsr_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister) +{ + // BSR <destReg>, <srcReg> + if( destRegister >= 8 && srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x45); + else if( destRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x44); + else if( srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0xBD); + x64Gen_writeU8(x64GenContext, 0xC0+(destRegister&7)*8+(srcRegister&7)); +} + +void x64Gen_setcc_mem8(x64GenContext_t* x64GenContext, sint32 conditionType, sint32 memoryRegister, uint32 memoryImmU32) +{ + // SETcc [<reg64>+imm] + sint32 memoryImmS32 = (sint32)memoryImmU32; + if( memoryRegister != REG_RSP ) + assert_dbg(); // not supported + if( memoryRegister >= 8 ) + assert_dbg(); // not supported + if( memoryImmS32 >= -128 && memoryImmS32 <= 127 ) + { + if( conditionType == X86_CONDITION_EQUAL ) + { + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x94); + x64Gen_writeU8(x64GenContext, 0x44); + x64Gen_writeU8(x64GenContext, 0x24); + x64Gen_writeU8(x64GenContext, (uint32)memoryImmU32); + } + else if( conditionType == X86_CONDITION_NOT_EQUAL ) + { + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x95); + x64Gen_writeU8(x64GenContext, 0x44); + x64Gen_writeU8(x64GenContext, 0x24); + x64Gen_writeU8(x64GenContext, (uint32)memoryImmU32); + } + else if( conditionType == X86_CONDITION_UNSIGNED_ABOVE ) + { + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x97); + x64Gen_writeU8(x64GenContext, 0x44); + x64Gen_writeU8(x64GenContext, 0x24); + x64Gen_writeU8(x64GenContext, (uint32)memoryImmU32); + } + else if( conditionType == X86_CONDITION_UNSIGNED_ABOVE_EQUAL ) + { + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x93); + x64Gen_writeU8(x64GenContext, 0x44); + x64Gen_writeU8(x64GenContext, 0x24); + x64Gen_writeU8(x64GenContext, (uint32)memoryImmU32); + } + else if( conditionType == X86_CONDITION_UNSIGNED_BELOW ) + { + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x92); + x64Gen_writeU8(x64GenContext, 0x44); + x64Gen_writeU8(x64GenContext, 0x24); + x64Gen_writeU8(x64GenContext, (uint32)memoryImmU32); + } + else if( conditionType == X86_CONDITION_UNSIGNED_BELOW_EQUAL ) + { + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x96); + x64Gen_writeU8(x64GenContext, 0x44); + x64Gen_writeU8(x64GenContext, 0x24); + x64Gen_writeU8(x64GenContext, (uint32)memoryImmU32); + } + else if( conditionType == X86_CONDITION_SIGNED_GREATER ) + { + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x9F); + x64Gen_writeU8(x64GenContext, 0x44); + x64Gen_writeU8(x64GenContext, 0x24); + x64Gen_writeU8(x64GenContext, (uint32)memoryImmU32); + } + else if( conditionType == X86_CONDITION_SIGNED_GREATER_EQUAL ) + { + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x9D); + x64Gen_writeU8(x64GenContext, 0x44); + x64Gen_writeU8(x64GenContext, 0x24); + x64Gen_writeU8(x64GenContext, (uint32)memoryImmU32); + } + else if( conditionType == X86_CONDITION_SIGNED_LESS ) + { + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x9C); + x64Gen_writeU8(x64GenContext, 0x44); + x64Gen_writeU8(x64GenContext, 0x24); + x64Gen_writeU8(x64GenContext, (uint32)memoryImmU32); + } + else if( conditionType == X86_CONDITION_SIGNED_LESS_EQUAL ) + { + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x9E); + x64Gen_writeU8(x64GenContext, 0x44); + x64Gen_writeU8(x64GenContext, 0x24); + x64Gen_writeU8(x64GenContext, (uint32)memoryImmU32); + } + else if( conditionType == X86_CONDITION_PARITY ) + { + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x9A); + x64Gen_writeU8(x64GenContext, 0x44); + x64Gen_writeU8(x64GenContext, 0x24); + x64Gen_writeU8(x64GenContext, (uint32)memoryImmU32); + } + else + assert_dbg(); + } + else + { + if( conditionType == X86_CONDITION_EQUAL ) + { + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x94); + x64Gen_writeU8(x64GenContext, 0x84); + x64Gen_writeU8(x64GenContext, 0x24); + x64Gen_writeU32(x64GenContext, (uint32)memoryImmU32); + } + else if( conditionType == X86_CONDITION_NOT_EQUAL ) + { + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x95); + x64Gen_writeU8(x64GenContext, 0x84); + x64Gen_writeU8(x64GenContext, 0x24); + x64Gen_writeU32(x64GenContext, (uint32)memoryImmU32); + } + else if( conditionType == X86_CONDITION_UNSIGNED_ABOVE ) + { + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x97); + x64Gen_writeU8(x64GenContext, 0x84); + x64Gen_writeU8(x64GenContext, 0x24); + x64Gen_writeU32(x64GenContext, (uint32)memoryImmU32); + } + else if( conditionType == X86_CONDITION_UNSIGNED_ABOVE_EQUAL ) + { + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x93); + x64Gen_writeU8(x64GenContext, 0x84); + x64Gen_writeU8(x64GenContext, 0x24); + x64Gen_writeU32(x64GenContext, (uint32)memoryImmU32); + } + else if( conditionType == X86_CONDITION_UNSIGNED_BELOW || conditionType == X86_CONDITION_CARRY ) + { + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x92); + x64Gen_writeU8(x64GenContext, 0x84); + x64Gen_writeU8(x64GenContext, 0x24); + x64Gen_writeU32(x64GenContext, (uint32)memoryImmU32); + } + else if( conditionType == X86_CONDITION_NOT_CARRY ) + { + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x93); + x64Gen_writeU8(x64GenContext, 0x84); + x64Gen_writeU8(x64GenContext, 0x24); + x64Gen_writeU32(x64GenContext, (uint32)memoryImmU32); + } + else if( conditionType == X86_CONDITION_UNSIGNED_BELOW_EQUAL ) + { + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x96); + x64Gen_writeU8(x64GenContext, 0x84); + x64Gen_writeU8(x64GenContext, 0x24); + x64Gen_writeU32(x64GenContext, (uint32)memoryImmU32); + } + else if( conditionType == X86_CONDITION_SIGNED_GREATER ) + { + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x9F); + x64Gen_writeU8(x64GenContext, 0x84); + x64Gen_writeU8(x64GenContext, 0x24); + x64Gen_writeU32(x64GenContext, (uint32)memoryImmU32); + } + else if( conditionType == X86_CONDITION_SIGNED_GREATER_EQUAL ) + { + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x9D); + x64Gen_writeU8(x64GenContext, 0x84); + x64Gen_writeU8(x64GenContext, 0x24); + x64Gen_writeU32(x64GenContext, (uint32)memoryImmU32); + } + else if( conditionType == X86_CONDITION_SIGNED_LESS ) + { + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x9C); + x64Gen_writeU8(x64GenContext, 0x84); + x64Gen_writeU8(x64GenContext, 0x24); + x64Gen_writeU32(x64GenContext, (uint32)memoryImmU32); + } + else if( conditionType == X86_CONDITION_SIGNED_LESS_EQUAL ) + { + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x9E); + x64Gen_writeU8(x64GenContext, 0x84); + x64Gen_writeU8(x64GenContext, 0x24); + x64Gen_writeU32(x64GenContext, (uint32)memoryImmU32); + } + else if( conditionType == X86_CONDITION_SIGN ) + { + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x98); + x64Gen_writeU8(x64GenContext, 0x84); + x64Gen_writeU8(x64GenContext, 0x24); + x64Gen_writeU32(x64GenContext, (uint32)memoryImmU32); + } + else if( conditionType == X86_CONDITION_PARITY ) + { + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x9A); + x64Gen_writeU8(x64GenContext, 0x84); + x64Gen_writeU8(x64GenContext, 0x24); + x64Gen_writeU32(x64GenContext, (uint32)memoryImmU32); + } + else + assert_dbg(); + } +} + +void x64Gen_setcc_reg64b(x64GenContext_t* x64GenContext, sint32 conditionType, sint32 dataRegister) +{ + // SETcc <reg64_low8> + if (conditionType == X86_CONDITION_NOT_EQUAL) + { + if (dataRegister >= 8) + x64Gen_writeU8(x64GenContext, 0x41); + else if (dataRegister >= 4) + x64Gen_writeU8(x64GenContext, 0x40); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x95); + x64Gen_writeU8(x64GenContext, 0xC0 + (dataRegister & 7)); + } + else if (conditionType == X86_CONDITION_EQUAL) + { + if (dataRegister >= 8) + x64Gen_writeU8(x64GenContext, 0x41); + else if (dataRegister >= 4) + x64Gen_writeU8(x64GenContext, 0x40); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x94); + x64Gen_writeU8(x64GenContext, 0xC0 + (dataRegister & 7)); + } + else + assert_dbg(); +} + +void x64Gen_bt_mem8(x64GenContext_t* x64GenContext, sint32 memoryRegister, uint32 memoryImmU32, uint8 bitIndex) +{ + // BT [<reg64>+imm], bitIndex (bit test) + sint32 memoryImmS32 = (sint32)memoryImmU32; + if( memoryRegister != REG_RSP ) + assert_dbg(); // not supported yet + if( memoryImmS32 >= -128 && memoryImmS32 <= 127 ) + { + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0xBA); + x64Gen_writeU8(x64GenContext, 0x64); + x64Gen_writeU8(x64GenContext, 0x24); + x64Gen_writeU8(x64GenContext, (uint8)memoryImmU32); + x64Gen_writeU8(x64GenContext, bitIndex); + } + else + { + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0xBA); + x64Gen_writeU8(x64GenContext, 0xA4); + x64Gen_writeU8(x64GenContext, 0x24); + x64Gen_writeU32(x64GenContext, memoryImmU32); + x64Gen_writeU8(x64GenContext, bitIndex); + } +} + +void x64Gen_cmc(x64GenContext_t* x64GenContext) +{ + x64Gen_writeU8(x64GenContext, 0xF5); +} + +void x64Gen_jmp_imm32(x64GenContext_t* x64GenContext, uint32 destImm32) +{ + x64Gen_writeU8(x64GenContext, 0xE9); + x64Gen_writeU32(x64GenContext, destImm32); +} + +void x64Gen_jmp_memReg64(x64GenContext_t* x64GenContext, sint32 memRegister, uint32 immU32) +{ + if( memRegister == REG_NONE ) + { + assert_dbg(); + } + if( memRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + sint32 immS32 = (sint32)immU32; + if( immS32 == 0 ) + { + x64Gen_writeU8(x64GenContext, 0xFF); + x64Gen_writeU8(x64GenContext, 0x20+(memRegister&7)); + } + else if( immS32 >= -128 && immS32 <= 127 ) + { + x64Gen_writeU8(x64GenContext, 0xFF); + x64Gen_writeU8(x64GenContext, 0x60+(memRegister&7)); + x64Gen_writeU8(x64GenContext, (uint8)immU32); + } + else + { + x64Gen_writeU8(x64GenContext, 0xFF); + x64Gen_writeU8(x64GenContext, 0xA0+(memRegister&7)); + x64Gen_writeU32(x64GenContext, immU32); + } +} + +void x64Gen_jmpc_far(x64GenContext_t* x64GenContext, sint32 conditionType, sint32 relativeDest) +{ + // far JMPc #+relativeDest + if( conditionType == X86_CONDITION_NONE ) + { + // E9 FFFFFFFF + x64Gen_writeU8(x64GenContext, 0xE9); + } + else if( conditionType == X86_CONDITION_UNSIGNED_BELOW || conditionType == X86_CONDITION_CARRY ) + { + // 0F 82 FFFFFFFF + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x82); + } + else if( conditionType == X86_CONDITION_NOT_CARRY || conditionType == X86_CONDITION_UNSIGNED_ABOVE_EQUAL ) + { + // 0F 83 FFFFFFFF + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x83); + } + else if( conditionType == X86_CONDITION_EQUAL ) + { + // 0F 84 FFFFFFFF + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x84); + } + else if( conditionType == X86_CONDITION_NOT_EQUAL ) + { + // 0F 85 FFFFFFFF + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x85); + } + else if( conditionType == X86_CONDITION_UNSIGNED_BELOW_EQUAL ) + { + // 0F 86 FFFFFFFF + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x86); + } + else if( conditionType == X86_CONDITION_UNSIGNED_ABOVE ) + { + // 0F 87 FFFFFFFF + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x87); + } + else if( conditionType == X86_CONDITION_SIGN ) + { + // 0F 88 FFFFFFFF + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x88); + } + else if( conditionType == X86_CONDITION_NOT_SIGN ) + { + // 0F 89 FFFFFFFF + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x89); + } + else if( conditionType == X86_CONDITION_PARITY ) + { + // 0F 8A FFFFFFFF + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x8A); + } + else if( conditionType == X86_CONDITION_SIGNED_LESS ) + { + // 0F 8C FFFFFFFF + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x8C); + } + else if( conditionType == X86_CONDITION_SIGNED_GREATER_EQUAL ) + { + // 0F 8D FFFFFFFF + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x8D); + } + else if( conditionType == X86_CONDITION_SIGNED_LESS_EQUAL ) + { + // 0F 8E FFFFFFFF + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x8E); + } + else if( conditionType == X86_CONDITION_SIGNED_GREATER ) + { + // 0F 8F FFFFFFFF + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x8F); + } + else + assert_dbg(); + x64Gen_writeU32(x64GenContext, (uint32)relativeDest); +} + + +void x64Gen_jmpc_near(x64GenContext_t* x64GenContext, sint32 conditionType, sint32 relativeDest) +{ + // near JMPc #+relativeDest + if (conditionType == X86_CONDITION_NONE) + { + x64Gen_writeU8(x64GenContext, 0xEB); + } + else if (conditionType == X86_CONDITION_UNSIGNED_BELOW || conditionType == X86_CONDITION_CARRY) + { + x64Gen_writeU8(x64GenContext, 0x72); + } + else if (conditionType == X86_CONDITION_NOT_CARRY || conditionType == X86_CONDITION_UNSIGNED_ABOVE_EQUAL) + { + x64Gen_writeU8(x64GenContext, 0x73); + } + else if (conditionType == X86_CONDITION_EQUAL) + { + x64Gen_writeU8(x64GenContext, 0x74); + } + else if (conditionType == X86_CONDITION_NOT_EQUAL) + { + x64Gen_writeU8(x64GenContext, 0x75); + } + else if (conditionType == X86_CONDITION_UNSIGNED_BELOW_EQUAL) + { + x64Gen_writeU8(x64GenContext, 0x76); + } + else if (conditionType == X86_CONDITION_UNSIGNED_ABOVE) + { + x64Gen_writeU8(x64GenContext, 0x77); + } + else if (conditionType == X86_CONDITION_SIGN) + { + x64Gen_writeU8(x64GenContext, 0x78); + } + else if (conditionType == X86_CONDITION_NOT_SIGN) + { + x64Gen_writeU8(x64GenContext, 0x79); + } + else if (conditionType == X86_CONDITION_PARITY) + { + x64Gen_writeU8(x64GenContext, 0x7A); + } + else if (conditionType == X86_CONDITION_SIGNED_LESS) + { + x64Gen_writeU8(x64GenContext, 0x7C); + } + else if (conditionType == X86_CONDITION_SIGNED_GREATER_EQUAL) + { + x64Gen_writeU8(x64GenContext, 0x7D); + } + else if (conditionType == X86_CONDITION_SIGNED_LESS_EQUAL) + { + x64Gen_writeU8(x64GenContext, 0x7E); + } + else if (conditionType == X86_CONDITION_SIGNED_GREATER) + { + x64Gen_writeU8(x64GenContext, 0x7F); + } + else + assert_dbg(); + x64Gen_writeU8(x64GenContext, (uint8)relativeDest); +} + +void x64Gen_push_reg64(x64GenContext_t* x64GenContext, sint32 srcRegister) +{ + if( srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + x64Gen_writeU8(x64GenContext, 0x50+(srcRegister&7)); +} + +void x64Gen_pop_reg64(x64GenContext_t* x64GenContext, sint32 destRegister) +{ + if( destRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + x64Gen_writeU8(x64GenContext, 0x58+(destRegister&7)); +} + +void x64Gen_jmp_reg64(x64GenContext_t* x64GenContext, sint32 srcRegister) +{ + if( srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + x64Gen_writeU8(x64GenContext, 0xFF); + x64Gen_writeU8(x64GenContext, 0xE0+(srcRegister&7)); +} + +void x64Gen_call_reg64(x64GenContext_t* x64GenContext, sint32 srcRegister) +{ + if( srcRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x41); + x64Gen_writeU8(x64GenContext, 0xFF); + x64Gen_writeU8(x64GenContext, 0xD0+(srcRegister&7)); +} + +void x64Gen_ret(x64GenContext_t* x64GenContext) +{ + x64Gen_writeU8(x64GenContext, 0xC3); +} + +void x64Gen_int3(x64GenContext_t* x64GenContext) +{ + x64Gen_writeU8(x64GenContext, 0xCC); +} diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64GenFPU.cpp b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64GenFPU.cpp new file mode 100644 index 00000000..92289d68 --- /dev/null +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64GenFPU.cpp @@ -0,0 +1,752 @@ +#include "PPCRecompiler.h" +#include "PPCRecompilerIml.h" +#include "PPCRecompilerX64.h" + +void x64Gen_genSSEVEXPrefix2(x64GenContext_t* x64GenContext, sint32 xmmRegister1, sint32 xmmRegister2, bool use64BitMode) +{ + if( xmmRegister1 < 8 && xmmRegister2 < 8 && use64BitMode == false ) + return; + uint8 v = 0x40; + if( xmmRegister1 >= 8 ) + v |= 0x01; + if( xmmRegister2 >= 8 ) + v |= 0x04; + if( use64BitMode ) + v |= 0x08; + x64Gen_writeU8(x64GenContext, v); +} + +void x64Gen_genSSEVEXPrefix1(x64GenContext_t* x64GenContext, sint32 xmmRegister, bool use64BitMode) +{ + if( xmmRegister < 8 && use64BitMode == false ) + return; + uint8 v = 0x40; + if( use64BitMode ) + v |= 0x01; + if( xmmRegister >= 8 ) + v |= 0x04; + x64Gen_writeU8(x64GenContext, v); +} + +void x64Gen_movaps_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSource) +{ + // SSE + // copy xmm register + // MOVAPS <xmm>, <xmm> + x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSource, xmmRegisterDest, false); // tested + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x28); // alternative encoding: 0x29, source and destination register are exchanged + x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSource&7)); +} + +void x64Gen_movupd_xmmReg_memReg128(x64GenContext_t* x64GenContext, sint32 xmmRegister, sint32 memRegister, uint32 memImmU32) +{ + // SSE2 + // move two doubles from memory into xmm register + // MOVUPD <xmm>, [<reg>+<imm>] + if( memRegister == REG_ESP ) + { + // todo: Short form of instruction if memImmU32 is 0 or in -128 to 127 range + // 66 0F 10 84 E4 23 01 00 00 + x64Gen_writeU8(x64GenContext, 0x66); + x64Gen_genSSEVEXPrefix1(x64GenContext, xmmRegister, false); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x10); + x64Gen_writeU8(x64GenContext, 0x84+(xmmRegister&7)*8); + x64Gen_writeU8(x64GenContext, 0xE4); + x64Gen_writeU32(x64GenContext, memImmU32); + } + else if( memRegister == REG_NONE ) + { + assert_dbg(); + //x64Gen_writeU8(x64GenContext, 0x66); + //x64Gen_writeU8(x64GenContext, 0x0F); + //x64Gen_writeU8(x64GenContext, 0x10); + //x64Gen_writeU8(x64GenContext, 0x05+(xmmRegister&7)*8); + //x64Gen_writeU32(x64GenContext, memImmU32); + } + else + { + assert_dbg(); + } +} + +void x64Gen_movupd_memReg128_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegister, sint32 memRegister, uint32 memImmU32) +{ + // SSE2 + // move two doubles from memory into xmm register + // MOVUPD [<reg>+<imm>], <xmm> + if( memRegister == REG_ESP ) + { + // todo: Short form of instruction if memImmU32 is 0 or in -128 to 127 range + x64Gen_writeU8(x64GenContext, 0x66); + x64Gen_genSSEVEXPrefix1(x64GenContext, xmmRegister, false); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x11); + x64Gen_writeU8(x64GenContext, 0x84+(xmmRegister&7)*8); + x64Gen_writeU8(x64GenContext, 0xE4); + x64Gen_writeU32(x64GenContext, memImmU32); + } + else if( memRegister == REG_NONE ) + { + assert_dbg(); + //x64Gen_writeU8(x64GenContext, 0x66); + //x64Gen_writeU8(x64GenContext, 0x0F); + //x64Gen_writeU8(x64GenContext, 0x11); + //x64Gen_writeU8(x64GenContext, 0x05+(xmmRegister&7)*8); + //x64Gen_writeU32(x64GenContext, memImmU32); + } + else + { + assert_dbg(); + } +} + +void x64Gen_movddup_xmmReg_memReg64(x64GenContext_t* x64GenContext, sint32 xmmRegister, sint32 memRegister, uint32 memImmU32) +{ + // SSE3 + // move one double from memory into lower and upper half of a xmm register + if( memRegister == REG_RSP ) + { + // MOVDDUP <xmm>, [<reg>+<imm>] + // todo: Short form of instruction if memImmU32 is 0 or in -128 to 127 range + x64Gen_writeU8(x64GenContext, 0xF2); + if( xmmRegister >= 8 ) + x64Gen_writeU8(x64GenContext, 0x44); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x12); + x64Gen_writeU8(x64GenContext, 0x84+(xmmRegister&7)*8); + x64Gen_writeU8(x64GenContext, 0xE4); + x64Gen_writeU32(x64GenContext, memImmU32); + } + else if( memRegister == REG_R15 ) + { + // MOVDDUP <xmm>, [<reg>+<imm>] + // todo: Short form of instruction if memImmU32 is 0 or in -128 to 127 range + // F2 41 0F 12 87 - 44 33 22 11 + x64Gen_writeU8(x64GenContext, 0xF2); + x64Gen_genSSEVEXPrefix1(x64GenContext, xmmRegister, true); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x12); + x64Gen_writeU8(x64GenContext, 0x87+(xmmRegister&7)*8); + x64Gen_writeU32(x64GenContext, memImmU32); + } + else if( memRegister == REG_NONE ) + { + // MOVDDUP <xmm>, [<imm>] + // 36 F2 0F 12 05 - 00 00 00 00 + assert_dbg(); + //x64Gen_writeU8(x64GenContext, 0x36); + //x64Gen_writeU8(x64GenContext, 0xF2); + //x64Gen_writeU8(x64GenContext, 0x0F); + //x64Gen_writeU8(x64GenContext, 0x12); + //x64Gen_writeU8(x64GenContext, 0x05+(xmmRegister&7)*8); + //x64Gen_writeU32(x64GenContext, memImmU32); + } + else + { + assert_dbg(); + } +} + +void x64Gen_movddup_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) +{ + // SSE3 + // move low double from xmm register into lower and upper half of a different xmm register + x64Gen_writeU8(x64GenContext, 0xF2); + x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x12); + x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); +} + +void x64Gen_movhlps_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) +{ + // SSE1 + // move high double from xmm register into lower and upper half of a different xmm register + x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x12); + x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); +} + +void x64Gen_movsd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) +{ + // SSE2 + // move lower double from xmm register into lower half of a different xmm register, leave other half untouched + x64Gen_writeU8(x64GenContext, 0xF2); + x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x10); // alternative encoding: 0x11, src and dest exchanged + x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); +} + +void x64Gen_movsd_memReg64_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegister, sint32 memRegister, uint32 memImmU32) +{ + // SSE2 + // move lower 64bits (double) of xmm register to memory location + if( memRegister == REG_NONE ) + { + // MOVSD [<imm>], <xmm> + // F2 0F 11 05 - 45 23 01 00 + assert_dbg(); + //x64Gen_writeU8(x64GenContext, 0xF2); + //x64Gen_genSSEVEXPrefix(x64GenContext, xmmRegister, 0, false); + //x64Gen_writeU8(x64GenContext, 0x0F); + //x64Gen_writeU8(x64GenContext, 0x11); + //x64Gen_writeU8(x64GenContext, 0x05+xmmRegister*8); + //x64Gen_writeU32(x64GenContext, memImmU32); + } + else if( memRegister == REG_RSP ) + { + // MOVSD [RSP+<imm>], <xmm> + // F2 0F 11 84 24 - 33 22 11 00 + x64Gen_writeU8(x64GenContext, 0xF2); + x64Gen_genSSEVEXPrefix2(x64GenContext, 0, xmmRegister, false); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x11); + x64Gen_writeU8(x64GenContext, 0x84+(xmmRegister&7)*8); + x64Gen_writeU8(x64GenContext, 0x24); + x64Gen_writeU32(x64GenContext, memImmU32); + } + else + { + assert_dbg(); + } +} + +void x64Gen_movlpd_xmmReg_memReg64(x64GenContext_t* x64GenContext, sint32 xmmRegister, sint32 memRegister, uint32 memImmU32) +{ + // SSE3 + // move one double from memory into lower half of a xmm register, leave upper half unchanged(?) + if( memRegister == REG_NONE ) + { + // MOVLPD <xmm>, [<imm>] + //x64Gen_writeU8(x64GenContext, 0x66); + //x64Gen_writeU8(x64GenContext, 0x0F); + //x64Gen_writeU8(x64GenContext, 0x12); + //x64Gen_writeU8(x64GenContext, 0x05+(xmmRegister&7)*8); + //x64Gen_writeU32(x64GenContext, memImmU32); + assert_dbg(); + } + else if( memRegister == REG_RSP ) + { + // MOVLPD <xmm>, [<reg64>+<imm>] + // 66 0F 12 84 24 - 33 22 11 00 + x64Gen_writeU8(x64GenContext, 0x66); + x64Gen_genSSEVEXPrefix2(x64GenContext, 0, xmmRegister, false); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x12); + x64Gen_writeU8(x64GenContext, 0x84+(xmmRegister&7)*8); + x64Gen_writeU8(x64GenContext, 0x24); + x64Gen_writeU32(x64GenContext, memImmU32); + } + else + { + assert_dbg(); + } +} + +void x64Gen_unpcklpd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) +{ + // SSE2 + x64Gen_writeU8(x64GenContext, 0x66); + x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x14); + x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); +} + +void x64Gen_unpckhpd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) +{ + // SSE2 + x64Gen_writeU8(x64GenContext, 0x66); + x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x15); + x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); +} + +void x64Gen_shufpd_xmmReg_xmmReg_imm8(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc, uint8 imm8) +{ + // SSE2 + // shuffled copy source to destination + x64Gen_writeU8(x64GenContext, 0x66); + x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0xC6); + x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); + x64Gen_writeU8(x64GenContext, imm8); +} + +void x64Gen_addsd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) +{ + // SSE2 + // add bottom double of two xmm registers, leave upper quadword unchanged + x64Gen_writeU8(x64GenContext, 0xF2); + x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); // untested + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x58); + x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); +} + +void x64Gen_addpd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) +{ + // SSE2 + // add both doubles of two xmm registers + x64Gen_writeU8(x64GenContext, 0x66); + x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x58); + x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); +} + +void x64Gen_subsd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) +{ + // SSE2 + // subtract bottom double of two xmm registers, leave upper quadword unchanged + x64Gen_writeU8(x64GenContext, 0xF2); + x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x5C); + x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); +} + +void x64Gen_subpd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) +{ + // SSE2 + // subtract both doubles of two xmm registers + x64Gen_writeU8(x64GenContext, 0x66); + x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); // untested + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x5C); + x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); +} + +void x64Gen_mulsd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) +{ + // SSE2 + // multiply bottom double of two xmm registers, leave upper quadword unchanged + x64Gen_writeU8(x64GenContext, 0xF2); + x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x59); + x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); +} + +void x64Gen_mulpd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) +{ + // SSE2 + // multiply both doubles of two xmm registers + x64Gen_writeU8(x64GenContext, 0x66); + x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); // untested + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x59); + x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); +} + +void x64Gen_mulpd_xmmReg_memReg128(x64GenContext_t* x64GenContext, sint32 xmmRegister, sint32 memRegister, uint32 memImmU32) +{ + // SSE2 + if (memRegister == REG_NONE) + { + assert_dbg(); + } + else if (memRegister == REG_R14) + { + x64Gen_writeU8(x64GenContext, 0x66); + x64Gen_writeU8(x64GenContext, (xmmRegister < 8) ? 0x41 : 0x45); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x59); + x64Gen_writeU8(x64GenContext, 0x86 + (xmmRegister & 7) * 8); + x64Gen_writeU32(x64GenContext, memImmU32); + } + else + { + assert_dbg(); + } +} + +void x64Gen_divsd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) +{ + // SSE2 + // divide bottom double of two xmm registers, leave upper quadword unchanged + x64Gen_writeU8(x64GenContext, 0xF2); + x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x5E); + x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); +} + +void x64Gen_divpd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) +{ + // SSE2 + // divide bottom and top double of two xmm registers + x64Gen_writeU8(x64GenContext, 0x66); + x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x5E); + x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); +} + +void x64Gen_comisd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) +{ + // SSE2 + // compare bottom doubles + x64Gen_writeU8(x64GenContext, 0x66); + x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); // untested + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x2F); + x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); +} + +void x64Gen_comisd_xmmReg_mem64Reg64(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 memoryReg, sint32 memImmS32) +{ + // SSE2 + // compare bottom double with double from memory location + if( memoryReg == REG_R15 ) + { + x64Gen_writeU8(x64GenContext, 0x66); + x64Gen_genSSEVEXPrefix1(x64GenContext, xmmRegisterDest, true); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x2F); + x64Gen_writeU8(x64GenContext, 0x87+(xmmRegisterDest&7)*8); + x64Gen_writeU32(x64GenContext, (uint32)memImmS32); + } + else + assert_dbg(); +} + +void x64Gen_ucomisd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) +{ + // SSE2 + // compare bottom doubles + x64Gen_writeU8(x64GenContext, 0x66); + x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x2E); + x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); +} + +void x64Gen_comiss_xmmReg_mem64Reg64(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 memoryReg, sint32 memImmS32) +{ + // SSE2 + // compare bottom float with float from memory location + if (memoryReg == REG_R15) + { + x64Gen_genSSEVEXPrefix1(x64GenContext, xmmRegisterDest, true); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x2F); + x64Gen_writeU8(x64GenContext, 0x87 + (xmmRegisterDest & 7) * 8); + x64Gen_writeU32(x64GenContext, (uint32)memImmS32); + } + else + assert_dbg(); +} + +void x64Gen_orps_xmmReg_mem128Reg64(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, uint32 memReg, uint32 memImmS32) +{ + // SSE2 + // and xmm register with 128 bit value from memory + if( memReg == REG_R15 ) + { + x64Gen_genSSEVEXPrefix2(x64GenContext, memReg, xmmRegisterDest, false); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x56); + x64Gen_writeU8(x64GenContext, 0x87+(xmmRegisterDest&7)*8); + x64Gen_writeU32(x64GenContext, (uint32)memImmS32); + } + else + assert_dbg(); +} + +void x64Gen_xorps_xmmReg_mem128Reg64(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, uint32 memReg, uint32 memImmS32) +{ + // SSE2 + // xor xmm register with 128 bit value from memory + if( memReg == REG_R15 ) + { + x64Gen_genSSEVEXPrefix1(x64GenContext, xmmRegisterDest, true); // todo: should be x64Gen_genSSEVEXPrefix2() with memReg? + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x57); + x64Gen_writeU8(x64GenContext, 0x87+(xmmRegisterDest&7)*8); + x64Gen_writeU32(x64GenContext, (uint32)memImmS32); + } + else + assert_dbg(); +} + +void x64Gen_andpd_xmmReg_memReg128(x64GenContext_t* x64GenContext, sint32 xmmRegister, sint32 memRegister, uint32 memImmU32) +{ + // SSE2 + if (memRegister == REG_NONE) + { + assert_dbg(); + } + else if (memRegister == REG_R14) + { + x64Gen_writeU8(x64GenContext, 0x66); + x64Gen_writeU8(x64GenContext, (xmmRegister < 8) ? 0x41 : 0x45); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x54); + x64Gen_writeU8(x64GenContext, 0x86 + (xmmRegister & 7) * 8); + x64Gen_writeU32(x64GenContext, memImmU32); + } + else + { + assert_dbg(); + } +} + +void x64Gen_andps_xmmReg_mem128Reg64(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, uint32 memReg, uint32 memImmS32) +{ + // SSE2 + // and xmm register with 128 bit value from memory + if( memReg == REG_R15 ) + { + x64Gen_genSSEVEXPrefix1(x64GenContext, xmmRegisterDest, true); // todo: should be x64Gen_genSSEVEXPrefix2() with memReg? + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x54); + x64Gen_writeU8(x64GenContext, 0x87+(xmmRegisterDest&7)*8); + x64Gen_writeU32(x64GenContext, (uint32)memImmS32); + } + else + assert_dbg(); +} + +void x64Gen_andps_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) +{ + // SSE2 + // and xmm register with xmm register + x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x54); + x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); +} + +void x64Gen_pcmpeqd_xmmReg_mem128Reg64(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, uint32 memReg, uint32 memImmS32) +{ + // SSE2 + // doubleword integer compare + if( memReg == REG_R15 ) + { + x64Gen_writeU8(x64GenContext, 0x66); + x64Gen_genSSEVEXPrefix1(x64GenContext, xmmRegisterDest, true); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x76); + x64Gen_writeU8(x64GenContext, 0x87+(xmmRegisterDest&7)*8); + x64Gen_writeU32(x64GenContext, (uint32)memImmS32); + } + else + assert_dbg(); +} + +void x64Gen_cvttpd2dq_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) +{ + // SSE2 + // convert two doubles into two 32-bit integers in bottom part of xmm register, reset upper 64 bits of destination register + x64Gen_writeU8(x64GenContext, 0x66); + x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0xE6); + x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); +} + +void x64Gen_cvttsd2si_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 registerDest, sint32 xmmRegisterSrc) +{ + // SSE2 + // convert double to truncated integer in general purpose register + x64Gen_writeU8(x64GenContext, 0xF2); + x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, registerDest, false); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x2C); + x64Gen_writeU8(x64GenContext, 0xC0+(registerDest&7)*8+(xmmRegisterSrc&7)); +} + +void x64Gen_cvtsd2ss_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) +{ + // SSE2 + // converts bottom 64bit double to bottom 32bit single + x64Gen_writeU8(x64GenContext, 0xF2); + x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x5A); + x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); +} + +void x64Gen_cvtpd2ps_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) +{ + // SSE2 + // converts two 64bit doubles to two 32bit singles in bottom half of register + x64Gen_writeU8(x64GenContext, 0x66); + x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x5A); + x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); +} + +void x64Gen_cvtps2pd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) +{ + // SSE2 + // converts two 32bit singles to two 64bit doubles + x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x5A); + x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); +} + +void x64Gen_cvtss2sd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) +{ + // SSE2 + // converts bottom 32bit single to bottom 64bit double + x64Gen_writeU8(x64GenContext, 0xF3); + x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x5A); + x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); +} + +void x64Gen_cvtpi2pd_xmmReg_mem64Reg64(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 memReg, sint32 memImmS32) +{ + // SSE2 + // converts two signed 32bit integers to two doubles + if( memReg == REG_RSP ) + { + x64Gen_writeU8(x64GenContext, 0x66); + x64Gen_genSSEVEXPrefix1(x64GenContext, xmmRegisterDest, false); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x2A); + x64Gen_writeU8(x64GenContext, 0x84+(xmmRegisterDest&7)*8); + x64Gen_writeU8(x64GenContext, 0x24); + x64Gen_writeU32(x64GenContext, (uint32)memImmS32); + } + else + { + assert_dbg(); + } +} + +void x64Gen_cvtsd2si_reg64Low_xmmReg(x64GenContext_t* x64GenContext, sint32 registerDest, sint32 xmmRegisterSrc) +{ + // SSE2 + // converts bottom 64bit double to 32bit signed integer in general purpose register, round based on float-point control + x64Gen_writeU8(x64GenContext, 0xF2); + x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, registerDest, false); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x2D); + x64Gen_writeU8(x64GenContext, 0xC0+(registerDest&7)*8+(xmmRegisterSrc&7)); +} + +void x64Gen_cvttsd2si_reg64Low_xmmReg(x64GenContext_t* x64GenContext, sint32 registerDest, sint32 xmmRegisterSrc) +{ + // SSE2 + // converts bottom 64bit double to 32bit signed integer in general purpose register, always truncate + x64Gen_writeU8(x64GenContext, 0xF2); + x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, registerDest, false); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x2C); + x64Gen_writeU8(x64GenContext, 0xC0+(registerDest&7)*8+(xmmRegisterSrc&7)); +} + +void x64Gen_sqrtsd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) +{ + // SSE2 + // calculates square root of bottom double + x64Gen_writeU8(x64GenContext, 0xF2); + x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x51); + x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); +} + +void x64Gen_sqrtpd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) +{ + // SSE2 + // calculates square root of bottom and top double + x64Gen_writeU8(x64GenContext, 0x66); + x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x51); + x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); +} + +void x64Gen_rcpss_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) +{ + // SSE2 + // approximates reciprocal of bottom 32bit single + x64Gen_writeU8(x64GenContext, 0xF3); + x64Gen_genSSEVEXPrefix2(x64GenContext, xmmRegisterSrc, xmmRegisterDest, false); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x53); + x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(xmmRegisterSrc&7)); +} + +void x64Gen_mulss_xmmReg_memReg64(x64GenContext_t* x64GenContext, sint32 xmmRegister, sint32 memRegister, uint32 memImmU32) +{ + // SSE2 + if( memRegister == REG_NONE ) + { + assert_dbg(); + } + else if( memRegister == 15 ) + { + x64Gen_writeU8(x64GenContext, 0xF3); + x64Gen_writeU8(x64GenContext, (xmmRegister<8)?0x41:0x45); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x59); + x64Gen_writeU8(x64GenContext, 0x87+(xmmRegister&7)*8); + x64Gen_writeU32(x64GenContext, memImmU32); + } + else + { + assert_dbg(); + } +} + +void x64Gen_movd_xmmReg_reg64Low32(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 registerSrc) +{ + // SSE2 + // copy low 32bit of general purpose register into xmm register + // MOVD <xmm>, <reg32> + x64Gen_writeU8(x64GenContext, 0x66); + x64Gen_genSSEVEXPrefix2(x64GenContext, registerSrc, xmmRegisterDest, false); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x6E); // alternative encoding: 0x29, source and destination register are exchanged + x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(registerSrc&7)); +} + +void x64Gen_movd_reg64Low32_xmmReg(x64GenContext_t* x64GenContext, sint32 registerDest, sint32 xmmRegisterSrc) +{ + // SSE2 + // copy low 32bit of general purpose register into xmm register + // MOVD <reg32>, <xmm> + x64Gen_writeU8(x64GenContext, 0x66); + x64Gen_genSSEVEXPrefix2(x64GenContext, registerDest, xmmRegisterSrc, false); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x7E); // alternative encoding: 0x29, source and destination register are exchanged + x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterSrc&7)*8+(registerDest&7)); +} + +void x64Gen_movq_xmmReg_reg64(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 registerSrc) +{ + // SSE2 + // copy general purpose register into xmm register + // MOVD <xmm>, <reg64> + x64Gen_writeU8(x64GenContext, 0x66); + x64Gen_genSSEVEXPrefix2(x64GenContext, registerSrc, xmmRegisterDest, true); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x6E); // alternative encoding: 0x29, source and destination register are exchanged + x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(registerSrc&7)); +} + +void x64Gen_movq_reg64_xmmReg(x64GenContext_t* x64GenContext, sint32 registerDst, sint32 xmmRegisterSrc) +{ + // SSE2 + // copy general purpose register into xmm register + // MOVD <xmm>, <reg64> + x64Gen_writeU8(x64GenContext, 0x66); + x64Gen_genSSEVEXPrefix2(x64GenContext, registerDst, xmmRegisterSrc, true); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x7E); + x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterSrc&7)*8+(registerDst&7)); +} \ No newline at end of file diff --git a/src/Cafe/HW/Espresso/Recompiler/x64Emit.hpp b/src/Cafe/HW/Espresso/Recompiler/x64Emit.hpp new file mode 100644 index 00000000..e936f1d8 --- /dev/null +++ b/src/Cafe/HW/Espresso/Recompiler/x64Emit.hpp @@ -0,0 +1,360 @@ + + +template<uint8 op0, bool rex64Bit = false> +class x64_opc_1byte +{ +public: + static void emitBytes(x64GenContext_t* x64GenContext) + { + // write out op0 + x64Gen_writeU8(x64GenContext, op0); + } + + static constexpr bool isRevOrder() + { + return false; + } + + static constexpr bool hasRex64BitPrefix() + { + return rex64Bit; + } +}; + +template<uint8 op0, bool rex64Bit = false> +class x64_opc_1byte_rev +{ +public: + static void emitBytes(x64GenContext_t* x64GenContext) + { + // write out op0 + x64Gen_writeU8(x64GenContext, op0); + } + + static constexpr bool isRevOrder() + { + return true; + } + + static constexpr bool hasRex64BitPrefix() + { + return rex64Bit; + } +}; + +template<uint8 op0, uint8 op1, bool rex64Bit = false> +class x64_opc_2byte +{ +public: + static void emitBytes(x64GenContext_t* x64GenContext) + { + x64Gen_writeU8(x64GenContext, op0); + x64Gen_writeU8(x64GenContext, op1); + } + + static constexpr bool isRevOrder() + { + return false; + } + + static constexpr bool hasRex64BitPrefix() + { + return rex64Bit; + } +}; + +enum class MODRM_OPR_TYPE +{ + REG, + MEM +}; + +class x64MODRM_opr_reg64 +{ +public: + x64MODRM_opr_reg64(uint8 reg) + { + this->reg = reg; + } + + static constexpr MODRM_OPR_TYPE getType() + { + return MODRM_OPR_TYPE::REG; + } + + const uint8 getReg() const + { + return reg; + } + +private: + uint8 reg; +}; + +class x64MODRM_opr_memReg64 +{ +public: + x64MODRM_opr_memReg64(uint8 reg) + { + this->reg = reg; + this->offset = 0; + } + + x64MODRM_opr_memReg64(uint8 reg, sint32 offset) + { + this->reg = reg; + this->offset = offset; + } + + static constexpr MODRM_OPR_TYPE getType() + { + return MODRM_OPR_TYPE::MEM; + } + + const uint8 getBaseReg() const + { + return reg; + } + + const uint32 getOffset() const + { + return (uint32)offset; + } + + static constexpr bool hasBaseReg() + { + return true; + } + + static constexpr bool hasIndexReg() + { + return false; + } +private: + uint8 reg; + sint32 offset; +}; + +class x64MODRM_opr_memRegPlusReg +{ +public: + x64MODRM_opr_memRegPlusReg(uint8 regBase, uint8 regIndex) + { + if ((regIndex & 7) == 4) + { + // cant encode RSP/R12 in index register, switch with base register + // this only works if the scaler is 1 + std::swap(regBase, regIndex); + cemu_assert((regBase & 7) != 4); + } + this->regBase = regBase; + this->regIndex = regIndex; + this->offset = 0; + } + + x64MODRM_opr_memRegPlusReg(uint8 regBase, uint8 regIndex, sint32 offset) + { + if ((regIndex & 7) == 4) + { + std::swap(regBase, regIndex); + cemu_assert((regIndex & 7) != 4); + } + this->regBase = regBase; + this->regIndex = regIndex; + this->offset = offset; + } + + static constexpr MODRM_OPR_TYPE getType() + { +return MODRM_OPR_TYPE::MEM; + } + + const uint8 getBaseReg() const + { + return regBase; + } + + const uint8 getIndexReg() + { + return regIndex; + } + + const uint32 getOffset() const + { + return (uint32)offset; + } + + static constexpr bool hasBaseReg() + { + return true; + } + + static constexpr bool hasIndexReg() + { + return true; + } +private: + uint8 regBase; + uint8 regIndex; // multiplied by scaler which is fixed to 1 + sint32 offset; +}; + +template<class opcodeBytes, typename TA, typename TB> +void _x64Gen_writeMODRM_internal(x64GenContext_t* x64GenContext, TA opA, TB opB) +{ + static_assert(TA::getType() == MODRM_OPR_TYPE::REG); + x64Gen_checkBuffer(x64GenContext); + // REX prefix + // 0100 WRXB + if constexpr (TA::getType() == MODRM_OPR_TYPE::REG && TB::getType() == MODRM_OPR_TYPE::REG) + { + if (opA.getReg() & 8 || opB.getReg() & 8 || opcodeBytes::hasRex64BitPrefix()) + { + // opA -> REX.B + // baseReg -> REX.R + x64Gen_writeU8(x64GenContext, 0x40 | ((opA.getReg() & 8) ? (1 << 2) : 0) | ((opB.getReg() & 8) ? (1 << 0) : 0) | (opcodeBytes::hasRex64BitPrefix() ? (1 << 3) : 0)); + } + } + else if constexpr (TA::getType() == MODRM_OPR_TYPE::REG && TB::getType() == MODRM_OPR_TYPE::MEM) + { + if constexpr (opB.hasBaseReg() && opB.hasIndexReg()) + { + if (opA.getReg() & 8 || opB.getBaseReg() & 8 || opB.getIndexReg() & 8 || opcodeBytes::hasRex64BitPrefix()) + { + // opA -> REX.B + // baseReg -> REX.R + // indexReg -> REX.X + x64Gen_writeU8(x64GenContext, 0x40 | ((opA.getReg() & 8) ? (1 << 2) : 0) | ((opB.getBaseReg() & 8) ? (1 << 0) : 0) | ((opB.getIndexReg() & 8) ? (1 << 1) : 0) | (opcodeBytes::hasRex64BitPrefix() ? (1 << 3) : 0)); + } + } + else if constexpr (opB.hasBaseReg()) + { + if (opA.getReg() & 8 || opB.getBaseReg() & 8 || opcodeBytes::hasRex64BitPrefix()) + { + // opA -> REX.B + // baseReg -> REX.R + x64Gen_writeU8(x64GenContext, 0x40 | ((opA.getReg() & 8) ? (1 << 2) : 0) | ((opB.getBaseReg() & 8) ? (1 << 0) : 0) | (opcodeBytes::hasRex64BitPrefix() ? (1 << 3) : 0)); + } + } + else + { + if (opA.getReg() & 8 || opcodeBytes::hasRex64BitPrefix()) + { + // todo - verify + // opA -> REX.B + x64Gen_writeU8(x64GenContext, 0x40 | ((opA.getReg() & 8) ? (1 << 2) : 0) | (opcodeBytes::hasRex64BitPrefix() ? (1 << 3) : 0)); + } + } + } + // opcode + opcodeBytes::emitBytes(x64GenContext); + // modrm byte + if constexpr (TA::getType() == MODRM_OPR_TYPE::REG && TB::getType() == MODRM_OPR_TYPE::REG) + { + // reg, reg + x64Gen_writeU8(x64GenContext, 0xC0 + (opB.getReg() & 7) + ((opA.getReg() & 7) << 3)); + } + else if constexpr (TA::getType() == MODRM_OPR_TYPE::REG && TB::getType() == MODRM_OPR_TYPE::MEM) + { + if constexpr (TB::hasBaseReg() == false) // todo - also check for index reg and secondary sib reg + { + // form: [offset] + // instruction is just offset + cemu_assert(false); + } + else if constexpr (TB::hasIndexReg()) + { + // form: [base+index*scaler+offset], scaler is currently fixed to 1 + cemu_assert((opB.getIndexReg() & 7) != 4); // RSP not allowed as index register + const uint32 offset = opB.getOffset(); + if (offset == 0 && (opB.getBaseReg() & 7) != 5) // RBP/R13 has special meaning in no-offset encoding + { + // [form: index*1+base] + x64Gen_writeU8(x64GenContext, 0x00 + (4) + ((opA.getReg() & 7) << 3)); + // SIB byte + x64Gen_writeU8(x64GenContext, ((opB.getIndexReg()&7) << 3) + (opB.getBaseReg() & 7)); + } + else if (offset == (uint32)(sint32)(sint8)offset) + { + // [form: index*1+base+sbyte] + x64Gen_writeU8(x64GenContext, 0x40 + (4) + ((opA.getReg() & 7) << 3)); + // SIB byte + x64Gen_writeU8(x64GenContext, ((opB.getIndexReg() & 7) << 3) + (opB.getBaseReg() & 7)); + x64Gen_writeU8(x64GenContext, (uint8)offset); + } + else + { + // [form: index*1+base+sdword] + x64Gen_writeU8(x64GenContext, 0x80 + (4) + ((opA.getReg() & 7) << 3)); + // SIB byte + x64Gen_writeU8(x64GenContext, ((opB.getIndexReg() & 7) << 3) + (opB.getBaseReg() & 7)); + x64Gen_writeU32(x64GenContext, (uint32)offset); + } + } + else + { + // form: [baseReg + offset] + const uint32 offset = opB.getOffset(); + if (offset == 0 && (opB.getBaseReg() & 7) != 5) // RBP/R13 has special meaning in no-offset encoding + { + // form: [baseReg] + // if base reg is RSP/R12 we need to use SIB form of instruction + if ((opB.getBaseReg() & 7) == 4) + { + x64Gen_writeU8(x64GenContext, 0x00 + (4) + ((opA.getReg() & 7) << 3)); + // SIB byte [form: none*1+base] + x64Gen_writeU8(x64GenContext, (4 << 3) + (opB.getBaseReg() & 7)); + } + else + { + x64Gen_writeU8(x64GenContext, 0x00 + (opB.getBaseReg() & 7) + ((opA.getReg() & 7) << 3)); + } + } + else if (offset == (uint32)(sint32)(sint8)offset) + { + // form: [baseReg+sbyte] + // if base reg is RSP/R12 we need to use SIB form of instruction + if ((opB.getBaseReg() & 7) == 4) + { + x64Gen_writeU8(x64GenContext, 0x40 + (4) + ((opA.getReg() & 7) << 3)); + // SIB byte [form: none*1+base] + x64Gen_writeU8(x64GenContext, (4 << 3) + (opB.getBaseReg() & 7)); + } + else + { + x64Gen_writeU8(x64GenContext, 0x40 + (opB.getBaseReg() & 7) + ((opA.getReg() & 7) << 3)); + } + x64Gen_writeU8(x64GenContext, (uint8)offset); + } + else + { + // form: [baseReg+sdword] + // if base reg is RSP/R12 we need to use SIB form of instruction + if ((opB.getBaseReg() & 7) == 4) + { + x64Gen_writeU8(x64GenContext, 0x80 + (4) + ((opA.getReg() & 7) << 3)); + // SIB byte [form: none*1+base] + x64Gen_writeU8(x64GenContext, (4 << 3) + (opB.getBaseReg() & 7)); + } + else + { + x64Gen_writeU8(x64GenContext, 0x80 + (opB.getBaseReg() & 7) + ((opA.getReg() & 7) << 3)); + } + x64Gen_writeU32(x64GenContext, (uint32)offset); + } + } + } + else + { + assert_dbg(); + } +} + +template<class opcodeBytes, typename TA, typename TB> +void x64Gen_writeMODRM_dyn(x64GenContext_t* x64GenContext, TA opLeft, TB opRight) +{ + if constexpr (opcodeBytes::isRevOrder()) + _x64Gen_writeMODRM_internal<opcodeBytes, TB, TA>(x64GenContext, opRight, opLeft); + else + _x64Gen_writeMODRM_internal<opcodeBytes, TA, TB>(x64GenContext, opLeft, opRight); +} \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Common/RegisterSerializer.cpp b/src/Cafe/HW/Latte/Common/RegisterSerializer.cpp new file mode 100644 index 00000000..3810d354 --- /dev/null +++ b/src/Cafe/HW/Latte/Common/RegisterSerializer.cpp @@ -0,0 +1,2249 @@ +#include "Cafe/HW/Latte/ISA/RegDefines.h" +#include "Cafe/HW/Latte/Core/Latte.h" +#include "Cafe/HW/Latte/Common/RegisterSerializer.h" +#include "Common/filestream.h" + +#include <zstd.h> +#include <zlib.h> + +// compression dictionary for pipeline Latte register data, initialized on boot +ZSTD_CDict* s_c_regDict{}; +ZSTD_DDict* s_d_regDict{}; + +struct GPURegSerializerMapping +{ + uint32 regAddr; + uint32 regCount; +}; + +GPURegSerializerMapping g_gpuRegSerializerMapping_v1[] = +{ + // tied to GPUCompactedRegisterState - DO NOT MODIFY + // this list is based on the same list used internally by GX2ContextState, excluding ALU constants + // Config register + {0x2232, 0x2}, + {0x2235, 0x1}, + {0x223A, 0x1}, + {0x2256, 0x1}, + {0x22C8, 0x1}, + {0x2300, 0x6}, + {0x2310, 0xC}, + {0x2363, 0x1}, + {0x2404, 0x2}, + {0x2542, 0x1}, + {0x25C5, 0x1}, + {0x260C, 0x1}, + {0x2900, 0x48}, // sampler border color VS + {0x2980, 0x48}, // sampler border color PS + {0x2A00, 0x48}, // sampler border color GS + // Context register + {0xA000, 0x2}, + {0xA003, 0x3}, + {0xA00A, 0x4}, + {0xA010, 0x38}, // color buffer registers + others + {0xA050, 0x34}, // SQ_ALU_CONST_BUFFER_SIZE_PS_0 + {0xA08C, 0x1}, + {0xA08E, 0x4}, + {0xA094, 0x40}, + {0xA0D5, 0x1}, + {0xA0E0, 0x20}, + {0xA100, 0x9}, + {0xA10C, 0x3}, + {0xA10F, 0x60}, // mostly PA_CL_VPORT_* registers + {0xA185, 0xA}, + {0xA191, 0x27}, + {0xA1E0, 0x9}, + {0xA200, 0x1}, + {0xA202, 0x7}, + {0xA210, 0x29}, + {0xA250, 0x34}, + {0xA284, 0xC}, + {0xA290, 0x1}, + {0xA292, 0x2}, + {0xA29B, 0x1}, + {0xA2A1, 0x1}, + {0xA2A5, 0x1}, + {0xA2A8, 0x2}, + {0xA2AC, 0x3}, + {0xA2B4, 0x3}, + {0xA2B8, 0x3}, + {0xA2BC, 0x3}, + {0xA2C0, 0x3}, + {0xA2C8, 0x1}, + {0xA2CA, 0x1}, + {0xA2CC, 0x1}, + {0xA2CE, 0x1}, + {0xA300, 0x9}, + {0xA30C, 0x1}, + {0xA312, 0x1}, + {0xA316, 0x2}, + {0xA343, 0x2}, + {0xA349, 0x3}, + {0xA34C, 0x2}, + {0xA351, 0x1}, + {0xA37E, 0x6}, + // Resource registers + {0xE000, 0x70}, + {0xE380, 0x70}, + {0xE460, 0x70}, + {0xE7E0, 0x70}, + {0xE8B9, 0x7}, + {0xE8C0, 0x70}, + {0xE930, 0x70}, + {0xECB0, 0x70}, + {0xED89, 0x7}, + // Sampler registers + {0xF000, 0x36}, + {0xF036, 0x36}, + {0xF06C, 0x36}, + // Loop const registers + {0xF880, 0x60} +}; + +namespace Latte +{ + void StoreGPURegisterState(const LatteContextRegister& contextRegister, GPUCompactedRegisterState& registerStateOut) + { + uint32* regView = contextRegister.GetRawView(); + // read mapped registers into linear array + uint32* writePtr = registerStateOut.rawArray; + for (auto& itr : g_gpuRegSerializerMapping_v1) + { + uint32* readPtr = contextRegister.GetRawView() + itr.regAddr; + for (uint32 i = 0; i < itr.regCount; i++) + { + *writePtr = *readPtr; + readPtr++; + writePtr++; + } + } + uint32 numStoredRegs = (uint32)(writePtr - registerStateOut.rawArray); + cemu_assert(numStoredRegs * 4 == sizeof(registerStateOut.rawArray)); + } + + void LoadGPURegisterState(LatteContextRegister& contextRegisterOut, const GPUCompactedRegisterState& registerState) + { + uint32* regView = contextRegisterOut.GetRawView(); + // read mapped registers into linear array + const uint32* readPtr = registerState.rawArray; + for (auto& itr : g_gpuRegSerializerMapping_v1) + { + uint32* writePtr = contextRegisterOut.GetRawView() + itr.regAddr; + for (uint32 i = 0; i < itr.regCount; i++) + { + *writePtr = *readPtr; + writePtr++; + readPtr++; + } + } + uint32 numStoredRegs = (uint32)(readPtr - registerState.rawArray); + cemu_assert(numStoredRegs * 4 == sizeof(registerState.rawArray)); + } + + // register data is mostly zero words + // this very simple RLE compression will collapse all the zero-byte ranges giving a pretty substantial compression ratio on its own + // we can then further use zstd-dictionary compression on it to end up with ultra compact serialized data (50-300 bytes) + void _CompressZeros(uint32* regArray, uint32 regCount, MemStreamWriter& memWriter) + { + uint32 index = 0; + uint8 numZeroWords = 0; + while (index < regCount) + { + if (regArray[index] == 0) + { + numZeroWords++; + if (numZeroWords == 0x7F) + { + memWriter.writeBE<uint8>(0x80 | numZeroWords); + numZeroWords = 0; + } + index++; + continue; + } + if (numZeroWords != 0) + { + memWriter.writeBE<uint8>(0x80 | numZeroWords); + numZeroWords = 0; + } + // count how many set words follow + uint32 tempIndex = index + 1; + uint32 scanEnd = std::min(index + 0x7F, regCount); + while (tempIndex < scanEnd) + { + if (regArray[tempIndex] == 0) + break; + tempIndex++; + } + uint32 numSetWords = tempIndex - index; + memWriter.writeBE<uint8>((uint8)numSetWords); + // store word data + while (index < tempIndex) + { + memWriter.writeBE<uint32>(regArray[index]); + index++; + } + } + if (numZeroWords != 0) + memWriter.writeBE<uint8>(0x80 | numZeroWords); + } + + bool _UncompressZeros(uint32* regArray, uint32 regCount, MemStreamReader& memReader) + { + uint32 index = 0; + while (index < regCount) + { + uint8 pattern = memReader.readBE<uint8>(); + uint8 count = pattern & 0x7F; + if (count == 0) + return false; + if ((index + count) > regCount) + return false; + if (pattern & 0x80) + { + // zero words + while (count--) + { + regArray[index] = 0; + index++; + } + } + else + { + // data words + while (count--) + { + regArray[index] = memReader.readBE<uint32>(); + index++; + } + } + } + return !memReader.hasError() && memReader.isEndOfStream(); + } + + void SerializeRegisterState(GPUCompactedRegisterState& regState, MemStreamWriter& memWriter) + { + // convert regState into a platform independent representation in memory (we use a big-endian array) + uint32be regArray[GPUCompactedRegisterState::NUM_REGS]; + for (uint32 i = 0; i < GPUCompactedRegisterState::NUM_REGS; i++) + regArray[i] = regState.rawArray[i]; + // first, use simple RLE-style compression to get a more compact data representation + MemStreamWriter tmpBufferWriter(1024 * 4); + _CompressZeros(regState.rawArray, GPUCompactedRegisterState::NUM_REGS, tmpBufferWriter); + auto rleCompressedData = tmpBufferWriter.getResult(); + // second, compress using dictionary trained for RLE compressed register state + uint8 buffer[8 * 1024]; + ZSTD_CCtx* const cctx = ZSTD_createCCtx(); + size_t compressedSize = ZSTD_compress_usingCDict(cctx, buffer, sizeof(buffer), rleCompressedData.data(), rleCompressedData.size(), s_c_regDict); + ZSTD_freeCCtx(cctx); + cemu_assert(!ZSTD_isError(compressedSize)); + // serialize + memWriter.writeBE<uint8>(0x01); // version + memWriter.writeBE<uint16>((uint16)compressedSize); + memWriter.writeData(buffer, compressedSize); + } + + bool DeserializeRegisterState(GPUCompactedRegisterState& regState, MemStreamReader& memReader) + { + if (memReader.readBE<uint8>() != 1) + return false; // unknown version + // read compressed register data into buffer + uint8 buffer[8 * 1024]; + uint16 compressedSize = memReader.readBE<uint16>(); + if (compressedSize >= sizeof(buffer)) + return false; + memReader.readData(buffer, compressedSize); + if (memReader.hasError()) + return false; + // decompress using zstd with dictionary + uint8 rleDataBuffer[8 * 1024]; + ZSTD_DCtx* const dctx = ZSTD_createDCtx(); + size_t rleDecompressedSize = ZSTD_decompress_usingDDict(dctx, rleDataBuffer, sizeof(rleDataBuffer), buffer, compressedSize, s_d_regDict); + ZSTD_freeDCtx(dctx); + if (rleDecompressedSize == 0 || rleDecompressedSize > sizeof(rleDataBuffer)) + return false; + // decompress RLE + MemStreamReader rleReader(rleDataBuffer, (sint32)rleDecompressedSize); + if (!_UncompressZeros(regState.rawArray, GPUCompactedRegisterState::NUM_REGS, rleReader)) + return false; + return true; + } + + void UnitTestPipelineSerialization() + { + GPUCompactedRegisterState inputState{}; + GPUCompactedRegisterState outputState{}; + for (int i = 0; i < inputState.NUM_REGS; i++) + { + if ((0x1037778e >> ((i / 3) & 31)) & 1) + inputState.rawArray[i] = i + i * 17 + i * 23; + else + inputState.rawArray[i] = 0; + } + // serialize and deserialize + MemStreamWriter writer(1024 * 8); + SerializeRegisterState(inputState, writer); + auto writerRes = writer.getResult(); + MemStreamReader reader(writerRes.data(), (sint32)writerRes.size()); + bool r = DeserializeRegisterState(outputState, reader); + cemu_assert_debug(r); + cemu_assert_debug(memcmp(&inputState, &outputState, sizeof(GPUCompactedRegisterState)) == 0); + } +} + +extern const uint8 s_regDataDict[]; + +RunAtCemuBoot _loadPipelineCompressionDictionary([]() +{ + // decompress and load dict + static std::vector<uint8> s_dictData; + s_dictData.resize(0x1B800); + ZSTD_decompress(s_dictData.data(), s_dictData.size(), s_regDataDict, 0x5AD2); + s_c_regDict = ZSTD_createCDict(s_dictData.data(), s_dictData.size(), 9); + s_d_regDict = ZSTD_createDDict(s_dictData.data(), s_dictData.size()); + cemu_assert_debug(s_c_regDict); + cemu_assert_debug(s_d_regDict); + + Latte::UnitTestPipelineSerialization(); +}); + +const uint8 s_regDataDict[] = // 0x5AD2 Uncompressed: 0x1B800 +{ + 0x28, 0xB5, 0x2F, 0xFD, 0xA4, 0x00, 0xB8, 0x01, 0x00, 0x15, 0xD6, 0x02, + 0xFA, 0x09, 0x2B, 0xA7, 0x4C, 0x10, 0x10, 0x4A, 0xE9, 0xB4, 0x06, 0xCA, + 0xEB, 0xF5, 0x42, 0x3A, 0x7D, 0x5B, 0xF2, 0x57, 0x32, 0x13, 0x5C, 0x42, + 0x9E, 0xFD, 0xC4, 0x0B, 0x41, 0xFD, 0xB8, 0x0E, 0xBF, 0x59, 0x28, 0xC3, + 0x3A, 0xFA, 0x93, 0x0C, 0xC1, 0xA1, 0x65, 0x6E, 0x85, 0x63, 0x6E, 0xC6, + 0x1A, 0x1B, 0xB7, 0xDD, 0x75, 0xCC, 0x29, 0xFE, 0x4F, 0x86, 0xAC, 0x64, + 0x09, 0x23, 0xA1, 0xC4, 0x54, 0xD5, 0xCC, 0xA2, 0x7A, 0xA0, 0xCA, 0x6C, + 0x23, 0xE2, 0xA2, 0x26, 0xB4, 0xC9, 0xDE, 0x5B, 0xA6, 0xEB, 0x09, 0x53, + 0x0A, 0x9B, 0x0A, 0x60, 0xA6, 0x5E, 0xCB, 0xDE, 0x0F, 0x53, 0xEF, 0xE5, + 0x90, 0xC2, 0x18, 0x63, 0xB3, 0x4C, 0xCD, 0x5E, 0x99, 0x9A, 0x79, 0x0D, + 0x4C, 0x68, 0x82, 0x5C, 0xE8, 0x90, 0x49, 0x0B, 0xB7, 0x8C, 0x7B, 0x49, + 0x73, 0x65, 0xE1, 0x42, 0x43, 0xD5, 0xC4, 0x9F, 0x8B, 0xDA, 0xA7, 0x35, + 0x08, 0x83, 0x6E, 0xA8, 0x52, 0x17, 0xD3, 0x31, 0x07, 0xB9, 0x0C, 0x2E, + 0x24, 0x4F, 0x71, 0xD5, 0x7B, 0xA7, 0xA4, 0x5F, 0xD4, 0xC8, 0x0D, 0x21, + 0x1D, 0x98, 0xD7, 0x8E, 0xB9, 0xD1, 0x77, 0xFF, 0xE6, 0xFA, 0x4E, 0xEF, + 0x9D, 0x3D, 0xFE, 0xD6, 0xF2, 0xC4, 0x1F, 0xC3, 0xB8, 0x87, 0xE7, 0x08, + 0xC6, 0x8C, 0xDD, 0xE3, 0xE2, 0x83, 0x99, 0xE5, 0xFD, 0xEF, 0x96, 0xEA, + 0xCF, 0x65, 0xFC, 0x21, 0xFA, 0xA6, 0x4D, 0xE8, 0xC3, 0x78, 0xDA, 0x6A, + 0x9C, 0x48, 0x65, 0x5A, 0x96, 0x21, 0x51, 0xD2, 0x93, 0xE6, 0xFE, 0x7A, + 0x25, 0x28, 0xA1, 0x71, 0x35, 0xF0, 0xD8, 0x30, 0xE4, 0xC8, 0x43, 0x11, + 0xB7, 0x21, 0x56, 0x2C, 0xEF, 0x0D, 0x25, 0x28, 0xA5, 0x4A, 0x86, 0x20, + 0xD2, 0x01, 0x69, 0xDA, 0x4B, 0x82, 0xA2, 0xF9, 0x40, 0x13, 0xFA, 0x64, + 0x14, 0xEE, 0x2B, 0x6C, 0xB8, 0xA9, 0x30, 0x36, 0xC1, 0x38, 0x1B, 0x33, + 0x4C, 0xDC, 0x5D, 0x20, 0x1F, 0xF0, 0x23, 0x3B, 0x32, 0x13, 0xDB, 0xAD, + 0x30, 0x71, 0x9B, 0xA4, 0x32, 0x9A, 0xF8, 0x4C, 0xD1, 0xC4, 0x3E, 0xCE, + 0xA0, 0xF6, 0x11, 0x03, 0x4A, 0xEF, 0x9B, 0x38, 0x9E, 0x96, 0x7A, 0xCC, + 0xA7, 0xE4, 0xF2, 0x25, 0x72, 0x39, 0x96, 0xA6, 0xE5, 0x5B, 0x3C, 0x94, + 0xEF, 0x69, 0x2A, 0x5F, 0x4E, 0xB6, 0x43, 0xD3, 0x64, 0x6D, 0x9A, 0x56, + 0xD9, 0x35, 0x15, 0x79, 0xF1, 0x22, 0xDB, 0xAB, 0x30, 0x22, 0xEB, 0x32, + 0xBF, 0xAC, 0x97, 0x5A, 0x7F, 0x43, 0x21, 0x9F, 0xE2, 0x9F, 0x95, 0x12, + 0x35, 0xEC, 0x5F, 0xB9, 0x7F, 0x0D, 0x47, 0xB8, 0x92, 0xA8, 0x66, 0xCC, + 0x47, 0x1A, 0x3E, 0x53, 0x4C, 0x38, 0x95, 0xC0, 0x5D, 0x3B, 0xA6, 0x21, + 0x21, 0x0A, 0x5E, 0xF2, 0xE9, 0x04, 0x3D, 0xEB, 0x45, 0x4E, 0x8A, 0xC9, + 0xD7, 0x3F, 0xD3, 0xE4, 0xA4, 0x52, 0x14, 0x54, 0x23, 0x7A, 0x5C, 0x37, + 0xB4, 0x75, 0x7D, 0xFF, 0x13, 0x90, 0xE0, 0x42, 0x5F, 0x0C, 0x7F, 0xA8, + 0x03, 0xE1, 0xC4, 0xF2, 0x4E, 0x10, 0x03, 0xAD, 0xF2, 0xE8, 0x5A, 0x9C, + 0xF4, 0xDD, 0x2A, 0x2F, 0x22, 0x41, 0xE8, 0xDC, 0x4D, 0xF8, 0xC3, 0x76, + 0x59, 0xB2, 0x01, 0xB3, 0x44, 0xF9, 0xC2, 0x75, 0x50, 0xF0, 0xEF, 0x60, + 0x95, 0x3E, 0xF7, 0xD9, 0x3C, 0x60, 0x39, 0x32, 0xFD, 0xFB, 0xF3, 0xB4, + 0xB0, 0x26, 0xBB, 0xC2, 0xD2, 0xCB, 0xE2, 0xE6, 0xEA, 0x2C, 0x6A, 0xAE, + 0x6E, 0xD1, 0xB4, 0x3F, 0x24, 0x0B, 0x20, 0x38, 0xDA, 0x02, 0x1E, 0x47, + 0xFF, 0xEC, 0xE1, 0x61, 0xF4, 0x9E, 0x31, 0xA3, 0xDB, 0x60, 0xFB, 0xB7, + 0x7C, 0x5B, 0x22, 0xF5, 0x9B, 0x07, 0x52, 0x3F, 0x3D, 0x50, 0xFA, 0x99, + 0x44, 0x29, 0x55, 0x62, 0x75, 0x9A, 0xC4, 0x0A, 0x8B, 0x03, 0x4A, 0xCB, + 0x01, 0xA2, 0xF4, 0xDD, 0xA3, 0x61, 0x3A, 0x9B, 0x17, 0x1C, 0x6F, 0xD3, + 0xD1, 0xD8, 0x05, 0xA0, 0x68, 0x3B, 0xB6, 0xA2, 0x83, 0x1A, 0x30, 0x3A, + 0xE9, 0xE0, 0x01, 0xEC, 0x4A, 0x9E, 0x4E, 0x03, 0x06, 0x6F, 0x80, 0x39, + 0x19, 0xD9, 0xCC, 0x15, 0x64, 0xB7, 0xED, 0x8D, 0x06, 0x09, 0x8B, 0x0E, + 0xE3, 0x70, 0x84, 0x6B, 0x20, 0xA1, 0x2F, 0x30, 0x43, 0x43, 0xDD, 0x77, + 0x72, 0xA6, 0xE7, 0xB8, 0xFA, 0x22, 0x45, 0xA3, 0x85, 0x55, 0xA0, 0x10, + 0xD1, 0x28, 0x14, 0x14, 0x64, 0xBC, 0xFD, 0xA2, 0xED, 0xCB, 0xA0, 0xDB, + 0x75, 0xDA, 0x80, 0x06, 0xCD, 0x8B, 0x9F, 0x02, 0x64, 0x79, 0xA1, 0x42, + 0x01, 0xFA, 0x87, 0xCE, 0xBB, 0xA6, 0x78, 0x7C, 0x42, 0x3B, 0xB9, 0x30, + 0xB6, 0x9D, 0x33, 0x66, 0x73, 0x61, 0x23, 0x64, 0x63, 0x47, 0xDF, 0x6B, + 0x12, 0x33, 0xF9, 0x1C, 0x8F, 0xE8, 0xE8, 0x77, 0xEA, 0x33, 0xF3, 0x48, + 0x40, 0x91, 0xBE, 0x39, 0x75, 0x82, 0x47, 0xC6, 0x17, 0xCE, 0x02, 0x62, + 0x3F, 0x78, 0x64, 0xA0, 0x3A, 0xD2, 0x37, 0x31, 0x74, 0x93, 0x16, 0xBE, + 0x2A, 0x7C, 0xF4, 0x69, 0x07, 0xC5, 0xCF, 0x3A, 0x28, 0x06, 0x72, 0x7B, + 0x3B, 0xB2, 0x67, 0x92, 0xA1, 0x7A, 0xCA, 0xB6, 0x94, 0x69, 0x38, 0x9C, + 0xE8, 0xED, 0xEA, 0xF1, 0x02, 0xC5, 0xD2, 0x41, 0x36, 0x8C, 0x47, 0xE8, + 0xE8, 0x2B, 0x7A, 0x50, 0x4E, 0x05, 0xB6, 0xED, 0xB7, 0x6D, 0xCD, 0xFC, + 0x66, 0x40, 0x58, 0xD6, 0x9B, 0x97, 0xB2, 0xF9, 0x81, 0x2E, 0x11, 0xE3, + 0x01, 0x39, 0x7A, 0xE4, 0xB3, 0xA0, 0xBE, 0xD9, 0x58, 0x3F, 0x54, 0x8E, + 0xCE, 0xE9, 0x7B, 0x1B, 0x9F, 0xF6, 0x2C, 0x7F, 0x83, 0x4B, 0x8F, 0x2B, + 0x09, 0xB2, 0xBB, 0xB0, 0x71, 0xA2, 0xBB, 0x81, 0x84, 0x53, 0x09, 0xE4, + 0xE1, 0xF4, 0xB7, 0x62, 0x8E, 0xF8, 0x9A, 0x00, 0xDC, 0x08, 0x4D, 0xC3, + 0x18, 0x37, 0xC8, 0x53, 0xA0, 0x89, 0xA9, 0x0A, 0x6C, 0x2F, 0xFA, 0x9A, + 0x2A, 0x49, 0x09, 0x3A, 0x51, 0xE0, 0x2D, 0x73, 0x45, 0x02, 0xD6, 0x54, + 0xF2, 0x7B, 0x4F, 0x4B, 0xDF, 0x49, 0x39, 0xC2, 0xFB, 0xD6, 0xB4, 0x3A, + 0xA0, 0x1E, 0x5D, 0x95, 0x5C, 0x17, 0x74, 0x53, 0x0D, 0xC8, 0xE4, 0x92, + 0x12, 0x49, 0x2D, 0x03, 0x5E, 0x6F, 0x05, 0x22, 0xAF, 0x74, 0x07, 0x39, + 0x16, 0x51, 0xB7, 0xBD, 0x22, 0x13, 0x78, 0x60, 0x15, 0x66, 0x10, 0xAE, + 0xB9, 0x44, 0xC0, 0xB5, 0xC6, 0xC4, 0xBA, 0xF9, 0x02, 0x6B, 0x3A, 0x37, + 0x55, 0xEB, 0x4D, 0x60, 0xE3, 0x4D, 0xD5, 0xC6, 0x21, 0x5E, 0x0F, 0xF9, + 0x40, 0xD0, 0x9A, 0x4E, 0x4C, 0xD6, 0xC9, 0x35, 0x5B, 0x8F, 0xC3, 0xD6, + 0x42, 0x30, 0x20, 0xB0, 0x4E, 0xB2, 0x21, 0xCC, 0xB8, 0x82, 0xD3, 0x41, + 0xA3, 0x1D, 0xAC, 0xD7, 0xDA, 0x64, 0x01, 0x19, 0xB1, 0xFA, 0x80, 0x33, + 0x94, 0xDE, 0x16, 0x70, 0x4E, 0x74, 0x56, 0x6D, 0x28, 0xC6, 0xD2, 0xFD, + 0xA2, 0x35, 0x89, 0x7A, 0x9C, 0x50, 0x4E, 0x0E, 0x49, 0xDB, 0xC6, 0x9D, + 0xC9, 0x49, 0xE2, 0x45, 0xDB, 0xA7, 0xCF, 0x8B, 0x21, 0x5C, 0x90, 0x4A, + 0xD0, 0x17, 0x2F, 0xB8, 0x9A, 0x6A, 0x23, 0xCC, 0x06, 0x9D, 0x5F, 0x9C, + 0xF6, 0xDB, 0xDC, 0x72, 0xC0, 0xC9, 0xFB, 0x1B, 0x22, 0x8F, 0xA6, 0x81, + 0x1D, 0x14, 0x6D, 0x0D, 0xCC, 0x81, 0x1A, 0x53, 0x06, 0xAA, 0x81, 0x96, + 0x08, 0xF4, 0x51, 0x23, 0x6D, 0x17, 0x18, 0xCB, 0xBB, 0x98, 0xC9, 0x29, + 0x5B, 0x2E, 0x67, 0x8C, 0x8B, 0xEB, 0x47, 0x17, 0x07, 0xB2, 0xEE, 0x02, + 0x24, 0xEB, 0x22, 0x26, 0x8F, 0xE0, 0x50, 0x0A, 0xB6, 0x73, 0xC8, 0x1D, + 0x14, 0xEC, 0x40, 0xE7, 0xCD, 0x7B, 0x8D, 0xB7, 0xBF, 0x78, 0x83, 0x8C, + 0x99, 0xDE, 0xA6, 0x81, 0xD5, 0x43, 0x85, 0x85, 0x09, 0xF7, 0x80, 0x85, + 0x11, 0x91, 0x73, 0x11, 0x94, 0xB2, 0x5C, 0xB6, 0x33, 0x99, 0x61, 0xC5, + 0x9D, 0x02, 0xE0, 0x82, 0x69, 0xC4, 0xC5, 0x41, 0x62, 0x7F, 0x42, 0x77, + 0x68, 0x87, 0xA7, 0x99, 0x20, 0xD9, 0x54, 0xF4, 0x78, 0x85, 0xBC, 0x2D, + 0x45, 0x8F, 0x26, 0xA4, 0x6E, 0x83, 0x40, 0x2D, 0x47, 0xF8, 0x10, 0xDB, + 0xF8, 0xCC, 0x24, 0x46, 0x68, 0x01, 0xBC, 0x64, 0x48, 0x9C, 0xE9, 0x5D, + 0x4D, 0x8A, 0xB8, 0xD0, 0x13, 0x5C, 0x7B, 0xBD, 0x20, 0xC0, 0xBD, 0x39, + 0xDD, 0xE6, 0xE4, 0xEE, 0x07, 0xE5, 0xDC, 0xDE, 0xE0, 0x5A, 0xB9, 0x39, + 0xB7, 0x16, 0x0D, 0x06, 0xCD, 0xFC, 0x9A, 0x99, 0x93, 0x65, 0xC5, 0xD6, + 0x23, 0x42, 0x3F, 0x4A, 0x45, 0x4D, 0x83, 0x3F, 0xD0, 0x79, 0x51, 0x3C, + 0x36, 0x08, 0xA6, 0xCE, 0xE6, 0x47, 0xDC, 0x3D, 0x69, 0xE7, 0x74, 0x3C, + 0x7C, 0x96, 0xB8, 0x57, 0x4F, 0xAB, 0xAE, 0x40, 0xFC, 0xE3, 0xB0, 0xED, + 0x67, 0xD3, 0x41, 0x00, 0xB6, 0x5A, 0x74, 0x08, 0x25, 0x30, 0x84, 0x63, + 0xC7, 0xE5, 0x06, 0x58, 0x60, 0xAC, 0xE0, 0x60, 0x0B, 0x4D, 0x7B, 0xD9, + 0xD1, 0x8F, 0x1D, 0x35, 0x6B, 0x1C, 0xDD, 0xA4, 0x71, 0x74, 0x7B, 0xB7, + 0xEE, 0x2C, 0xD9, 0xFF, 0xF9, 0x2C, 0x9E, 0x12, 0x9E, 0x0A, 0xC5, 0xC1, + 0xD6, 0x0F, 0xBA, 0xBC, 0x9F, 0x74, 0xAE, 0x77, 0x0E, 0x56, 0x0F, 0x29, + 0x98, 0xE8, 0x6F, 0xAD, 0x0B, 0x4D, 0x09, 0xBE, 0xCA, 0x05, 0xBF, 0x40, + 0x54, 0x4D, 0xD7, 0x3C, 0x32, 0xE1, 0x7F, 0xCB, 0xB0, 0xB3, 0x69, 0x58, + 0x22, 0x13, 0x57, 0x37, 0x15, 0xEE, 0x02, 0xFD, 0x14, 0x1F, 0xA1, 0x8B, + 0x7C, 0xD2, 0xE5, 0x3C, 0x80, 0xB1, 0x91, 0x4A, 0xDF, 0x76, 0x5E, 0x7D, + 0x6B, 0x12, 0x16, 0x72, 0x89, 0xDD, 0x2C, 0xA1, 0x2B, 0x0E, 0xCF, 0xBB, + 0xD6, 0x8F, 0x67, 0x2E, 0x20, 0xC4, 0x54, 0x2A, 0xE6, 0xAA, 0x49, 0xB1, + 0x5A, 0x12, 0x39, 0x04, 0x7C, 0x43, 0xF3, 0x08, 0x87, 0x42, 0xA8, 0xA8, + 0xF8, 0x50, 0x83, 0x60, 0x88, 0x24, 0x32, 0xF4, 0xD1, 0xFC, 0xE5, 0xFE, + 0x6B, 0xDC, 0x17, 0x0E, 0x48, 0x05, 0x28, 0xA7, 0x8E, 0xA6, 0x6C, 0x8B, + 0x87, 0x79, 0x37, 0xC1, 0xEA, 0x7C, 0xF4, 0x76, 0xAD, 0xCE, 0xFF, 0xEC, + 0x51, 0x06, 0x33, 0xEE, 0x92, 0x10, 0xD0, 0x01, 0x6B, 0xDB, 0x80, 0x40, + 0x5A, 0x3A, 0x72, 0x10, 0xB9, 0x2B, 0x28, 0x1F, 0x72, 0xC5, 0x83, 0x73, + 0xE1, 0x6C, 0x73, 0x72, 0xF7, 0x11, 0x23, 0x33, 0xF0, 0xA8, 0x4F, 0x74, + 0x34, 0xD2, 0xEE, 0x5F, 0x80, 0xB5, 0x19, 0xA8, 0x9D, 0xF6, 0x0A, 0x7F, + 0x6E, 0xD1, 0x84, 0xE3, 0x77, 0x7B, 0xC7, 0x62, 0xCA, 0xE0, 0xEF, 0xC4, + 0x01, 0xCC, 0xB6, 0xEF, 0x2F, 0xF5, 0x1F, 0x4E, 0x78, 0xFE, 0xC4, 0xD6, + 0xE0, 0x5C, 0xBC, 0xBC, 0x4F, 0x9C, 0x84, 0x05, 0xC8, 0xFE, 0x41, 0x07, + 0xD9, 0x4A, 0xA0, 0x2A, 0x5A, 0x8A, 0xA9, 0x9D, 0xD9, 0x96, 0xF0, 0x3B, + 0x19, 0x6C, 0x73, 0xDB, 0xF0, 0x29, 0x82, 0x49, 0x85, 0xCC, 0xA9, 0x9A, + 0xF4, 0x26, 0x3E, 0xB9, 0x38, 0x93, 0x2F, 0x5F, 0x6F, 0x22, 0x2D, 0x6F, + 0x45, 0xC2, 0x36, 0x32, 0x79, 0x08, 0x3B, 0x6A, 0xA3, 0x22, 0x11, 0xDE, + 0x2A, 0x68, 0x9C, 0xE9, 0x3A, 0xB4, 0xA0, 0x31, 0x10, 0xEE, 0x85, 0x17, + 0x18, 0xA7, 0x02, 0x8F, 0xBA, 0x70, 0xE9, 0x17, 0x41, 0xA9, 0xFB, 0x80, + 0x8E, 0x49, 0x11, 0x68, 0x38, 0x31, 0xEE, 0xCD, 0x48, 0xDB, 0x5E, 0x62, + 0xDB, 0x0E, 0xBF, 0x6D, 0xFA, 0x26, 0xAE, 0xCC, 0xF3, 0x95, 0x01, 0x8D, + 0x6C, 0xA3, 0x8C, 0x8C, 0xEB, 0x04, 0x42, 0x9F, 0x04, 0x01, 0x01, 0x83, + 0xF2, 0x9E, 0xA9, 0x09, 0x95, 0x8D, 0x5A, 0x91, 0x51, 0x78, 0x99, 0xC4, + 0xFD, 0xEC, 0xC0, 0xEF, 0xB2, 0x64, 0xFC, 0x32, 0x28, 0xD2, 0xF6, 0x15, + 0x73, 0xF7, 0xC6, 0x94, 0x4B, 0x0E, 0xDD, 0x2A, 0x44, 0xFF, 0x85, 0xA3, + 0xA1, 0x3D, 0xA6, 0x68, 0x5A, 0x27, 0xAC, 0x0B, 0xFE, 0xC3, 0x87, 0x45, + 0xD7, 0x0F, 0xF6, 0x61, 0x8F, 0x40, 0x14, 0xCA, 0x4C, 0xDB, 0x16, 0x9B, + 0x5D, 0x76, 0x77, 0x15, 0x3F, 0x9B, 0x01, 0x78, 0x48, 0x7D, 0x6F, 0x91, + 0x7E, 0x87, 0x95, 0x24, 0x30, 0x52, 0x36, 0x48, 0x17, 0x58, 0x35, 0x9E, + 0x60, 0x10, 0xD5, 0xC6, 0x03, 0x08, 0xA8, 0xD1, 0x9F, 0x81, 0x39, 0x50, + 0x28, 0xDF, 0xB5, 0xE3, 0x03, 0x7A, 0xD0, 0xAE, 0x77, 0x69, 0xF9, 0x55, + 0x3A, 0xC8, 0x95, 0xB4, 0x9C, 0xEB, 0x68, 0x79, 0xF1, 0xD6, 0xCF, 0x6E, + 0x8D, 0xC8, 0xB2, 0xF1, 0x4B, 0x3A, 0x91, 0x49, 0x19, 0x8D, 0x8D, 0x8B, + 0xE5, 0xAC, 0x71, 0x81, 0xC6, 0x49, 0xCD, 0x0C, 0x2E, 0xCC, 0x34, 0xC1, + 0x56, 0x03, 0xD2, 0x0D, 0x21, 0xC0, 0xD8, 0x6D, 0xAB, 0x1C, 0x1E, 0x15, + 0x90, 0x99, 0x21, 0xD2, 0x82, 0xB7, 0xC6, 0x6F, 0xA0, 0x9F, 0x0D, 0xF3, + 0xE9, 0x47, 0x26, 0x2C, 0x4C, 0x01, 0x2D, 0x37, 0xE7, 0x54, 0x77, 0xB2, + 0x04, 0xEE, 0x05, 0xFA, 0x41, 0x34, 0xA7, 0x88, 0x5B, 0x13, 0xA6, 0xFF, + 0x6A, 0x44, 0xBD, 0xE9, 0x3A, 0x71, 0xF8, 0x0A, 0x1C, 0xBD, 0xC2, 0x61, + 0xDB, 0xDE, 0x14, 0xBD, 0xAA, 0x1A, 0x8F, 0x17, 0x1B, 0x54, 0x10, 0x43, + 0xDC, 0xA7, 0xAD, 0x19, 0x00, 0xDF, 0xF2, 0xD0, 0x5D, 0xEB, 0x3F, 0xEB, + 0x21, 0x66, 0x24, 0xFA, 0x2C, 0x04, 0x31, 0xBC, 0xBC, 0xBF, 0x47, 0xC1, + 0x9A, 0x30, 0x1A, 0x40, 0x24, 0xD3, 0xB8, 0x3B, 0xA3, 0x1D, 0x00, 0x88, + 0x24, 0x13, 0x89, 0x36, 0x8A, 0x7D, 0xAD, 0xA6, 0xFF, 0x4D, 0x1B, 0x95, + 0x2E, 0x49, 0xFB, 0x05, 0x21, 0x28, 0x98, 0x38, 0x57, 0x6A, 0x0C, 0x59, + 0x9B, 0x0B, 0x0A, 0xB0, 0x18, 0x53, 0x07, 0x79, 0x27, 0xB0, 0x7B, 0x31, + 0x4A, 0x56, 0x23, 0x78, 0x86, 0x95, 0xBC, 0x32, 0xB4, 0xC1, 0x9A, 0x16, + 0x4B, 0x10, 0xC8, 0x90, 0xB5, 0xE8, 0x90, 0x18, 0x48, 0x24, 0x06, 0x40, + 0x16, 0xE7, 0xBF, 0x08, 0x1E, 0x5A, 0xAC, 0x4E, 0xE4, 0xF0, 0xA7, 0xB1, + 0xA6, 0x05, 0xFA, 0xE4, 0xBE, 0x3A, 0x8B, 0x64, 0xEC, 0xA2, 0x28, 0x2D, + 0xB0, 0x9F, 0x74, 0xE2, 0x68, 0x39, 0xBC, 0x7C, 0x34, 0x65, 0xF0, 0xEF, + 0x12, 0xC5, 0x5D, 0xA7, 0x1F, 0x17, 0xDF, 0x7D, 0xD1, 0x10, 0xF9, 0x07, + 0x5C, 0xF7, 0x92, 0x68, 0x71, 0xFB, 0xCF, 0x4E, 0x34, 0xEA, 0x29, 0x37, + 0x9E, 0x51, 0xDE, 0xA6, 0xEB, 0x39, 0x05, 0xFD, 0x8B, 0x61, 0xB8, 0x60, + 0x31, 0x72, 0x2F, 0x55, 0x23, 0x06, 0xFF, 0x0E, 0x0A, 0x8E, 0xBA, 0x6D, + 0xDD, 0x2C, 0x53, 0xE1, 0xB6, 0xE4, 0x66, 0xA2, 0x8A, 0x7F, 0x6F, 0x95, + 0x90, 0xC2, 0xED, 0xDE, 0x45, 0xA2, 0x90, 0x7F, 0xC1, 0x22, 0x3E, 0x34, + 0x3B, 0xDC, 0x80, 0x9D, 0xBE, 0xDA, 0xD0, 0x49, 0x03, 0xF9, 0xEE, 0xDF, + 0x5C, 0xB1, 0x35, 0x97, 0xA4, 0x0A, 0x2F, 0xD7, 0xCD, 0x38, 0xB5, 0x6C, + 0x2F, 0xF8, 0x76, 0xCE, 0x0A, 0x7F, 0xA4, 0xA0, 0xF9, 0xAD, 0xC0, 0x9B, + 0x70, 0x42, 0xFA, 0x53, 0xE5, 0x44, 0x19, 0x0D, 0xF1, 0x11, 0x36, 0x2E, + 0xD9, 0xDA, 0x06, 0x0C, 0x15, 0x6E, 0xCB, 0x88, 0x66, 0xA2, 0xDA, 0xFE, + 0x90, 0x72, 0x72, 0x2B, 0x5D, 0x9D, 0x0A, 0x33, 0x5B, 0x72, 0xC4, 0xAE, + 0x21, 0x17, 0xFF, 0x68, 0x43, 0x17, 0xED, 0x34, 0xE8, 0x60, 0x58, 0xAE, + 0xFC, 0x4D, 0xAD, 0xC9, 0x1F, 0x26, 0xD0, 0xEF, 0x07, 0xEE, 0x60, 0x4E, + 0x35, 0xFE, 0x88, 0x86, 0xFF, 0x74, 0x66, 0x94, 0xD3, 0xE5, 0x29, 0x6F, + 0xEF, 0x7A, 0x4E, 0x2F, 0xF4, 0xAC, 0xDC, 0x6A, 0x1C, 0x01, 0x85, 0x5B, + 0x39, 0xAB, 0x45, 0x2D, 0xFF, 0x8A, 0x45, 0xCE, 0xDD, 0xE4, 0x56, 0x3B, + 0x16, 0x5A, 0x5C, 0x7C, 0xD6, 0x29, 0x65, 0x9B, 0xEF, 0xD9, 0xD5, 0x39, + 0x9B, 0xFC, 0xD1, 0xA2, 0x42, 0x8B, 0x91, 0xAF, 0x3A, 0x15, 0xFF, 0x52, + 0x20, 0x09, 0x33, 0x8E, 0x31, 0x26, 0x6C, 0xF5, 0x07, 0xFB, 0xD7, 0x29, + 0xC2, 0x85, 0x5F, 0x2E, 0xED, 0x65, 0x72, 0x27, 0xBE, 0x80, 0x4A, 0xAD, + 0x22, 0x59, 0x6F, 0x07, 0x41, 0x18, 0x70, 0x0E, 0xFF, 0x37, 0x5E, 0xC2, + 0x1B, 0x14, 0xC4, 0x06, 0xBE, 0x91, 0xA1, 0x54, 0x1B, 0xFF, 0x47, 0xF3, + 0x8A, 0x7F, 0x15, 0xA8, 0x46, 0x09, 0xC1, 0xA9, 0x5F, 0x74, 0x9D, 0xC2, + 0xFF, 0x3E, 0x31, 0x16, 0x85, 0x84, 0x41, 0x35, 0x05, 0x7E, 0xFF, 0x27, + 0xAE, 0x08, 0x6C, 0xDB, 0x76, 0x75, 0x4D, 0xD3, 0x3C, 0xB2, 0x7F, 0x59, + 0x74, 0x52, 0x14, 0x0A, 0xFA, 0x21, 0x1E, 0xFC, 0xD7, 0x7F, 0x28, 0x74, + 0x5E, 0x20, 0x3C, 0xE6, 0xCC, 0x8B, 0x27, 0x11, 0x11, 0x3A, 0x85, 0x47, + 0x4E, 0xBA, 0xD3, 0x77, 0x9D, 0x1B, 0xB6, 0x36, 0x6B, 0x5A, 0x04, 0xF9, + 0xFC, 0xF5, 0xA0, 0x35, 0xF3, 0x2F, 0x55, 0x10, 0xF2, 0xB4, 0x47, 0xB6, + 0xEE, 0xBF, 0xBF, 0xCA, 0xA1, 0x2A, 0x70, 0x45, 0xDD, 0xBC, 0x2C, 0x7B, + 0x17, 0xCA, 0x7C, 0xD2, 0x6C, 0x75, 0xFE, 0xEC, 0x52, 0xFD, 0x5B, 0xA1, + 0x1D, 0x7A, 0x7F, 0x34, 0x6F, 0xA0, 0x1E, 0xFA, 0xB3, 0xC5, 0xF6, 0x57, + 0x6D, 0xB0, 0xE0, 0x0A, 0x26, 0xA2, 0x11, 0x99, 0x18, 0xDC, 0xFB, 0xF5, + 0xF9, 0x40, 0xBB, 0x57, 0xD9, 0x4F, 0x9B, 0x8F, 0x32, 0xFF, 0x26, 0xBF, + 0x28, 0xB7, 0x13, 0x07, 0xCA, 0x66, 0x7B, 0x72, 0x69, 0x31, 0x18, 0x19, + 0xFC, 0x86, 0x6E, 0xCF, 0x2E, 0xAF, 0x53, 0x08, 0xD5, 0xF7, 0x3A, 0x3A, + 0xEA, 0x76, 0x85, 0xCF, 0xDE, 0x97, 0xAF, 0x75, 0xAA, 0xFF, 0x85, 0xCB, + 0xD2, 0x7E, 0x24, 0x43, 0xD3, 0x37, 0x18, 0x9A, 0xC0, 0x09, 0xAD, 0x54, + 0xD2, 0x23, 0x48, 0x67, 0xDD, 0x64, 0x07, 0xCD, 0x1E, 0x7D, 0x40, 0x1F, + 0xF2, 0x29, 0x7E, 0xAC, 0x43, 0x7F, 0xF6, 0x85, 0xAE, 0x3B, 0x9A, 0xA1, + 0x46, 0x3B, 0x6D, 0xF3, 0x5B, 0xAC, 0x67, 0xF2, 0x9D, 0x38, 0xA6, 0x4F, + 0xEE, 0x51, 0xDA, 0x47, 0xE0, 0x42, 0x52, 0xA4, 0x3B, 0x1E, 0x3B, 0x4D, + 0xC2, 0x22, 0x90, 0xDF, 0x09, 0x2A, 0x31, 0xBF, 0x05, 0xAA, 0x09, 0x1E, + 0xCD, 0x98, 0x9C, 0xD8, 0x9F, 0x67, 0x4F, 0x34, 0xDD, 0xFC, 0x38, 0xC2, + 0x67, 0xD6, 0x39, 0x71, 0x4C, 0x3E, 0x13, 0xE3, 0x8F, 0x67, 0x26, 0xA9, + 0xBE, 0xFD, 0x1D, 0xA9, 0xFE, 0x9A, 0x10, 0x19, 0x20, 0xA5, 0x4F, 0xFE, + 0x67, 0xD7, 0xD1, 0x86, 0x14, 0xAE, 0x93, 0xC9, 0x77, 0x84, 0xEA, 0x5D, + 0x89, 0x91, 0x05, 0xA6, 0x96, 0x0C, 0x50, 0x86, 0x08, 0x26, 0x3E, 0xFC, + 0x41, 0xC6, 0xEA, 0x7A, 0xC8, 0x25, 0x8D, 0xC5, 0x28, 0xDA, 0x32, 0x03, + 0xC4, 0xA4, 0xC1, 0xC4, 0x19, 0x5E, 0x19, 0xB2, 0x1F, 0x23, 0xD6, 0x59, + 0x17, 0x3A, 0x65, 0xDE, 0x66, 0x08, 0xC6, 0x25, 0x09, 0xA6, 0x6F, 0x32, + 0x98, 0xB1, 0xEA, 0x1C, 0xC0, 0x70, 0x80, 0x09, 0xA8, 0x33, 0xE6, 0x4E, + 0x38, 0x20, 0xE3, 0xAB, 0x97, 0x2A, 0x28, 0x47, 0x8C, 0xC1, 0x83, 0xC1, + 0x0B, 0x2D, 0x7C, 0x29, 0x51, 0xE2, 0x4B, 0x8F, 0x27, 0x15, 0x14, 0x30, + 0x3A, 0xE0, 0xE5, 0x83, 0x0D, 0xB6, 0x98, 0xD8, 0x50, 0xE4, 0x49, 0x65, + 0x22, 0xEB, 0x80, 0x0A, 0x8E, 0x72, 0x5E, 0x8C, 0x65, 0x68, 0x44, 0xC5, + 0x31, 0x91, 0x03, 0x95, 0xA1, 0x0D, 0x23, 0x5E, 0x38, 0x7A, 0x18, 0x50, + 0x36, 0xBC, 0x21, 0x6C, 0xCB, 0xC8, 0xF5, 0x36, 0x25, 0x01, 0x12, 0x21, + 0xB4, 0x29, 0xB5, 0xCC, 0xD8, 0x9C, 0xB8, 0x4E, 0xC8, 0x02, 0x33, 0x9E, + 0xF9, 0x48, 0xB2, 0x60, 0x87, 0x21, 0xB6, 0x46, 0xF8, 0x0A, 0x4A, 0xDB, + 0x05, 0x41, 0xBD, 0xD7, 0x26, 0xF5, 0x1E, 0x07, 0x52, 0xB7, 0x5F, 0xFD, + 0x78, 0x36, 0x1D, 0x54, 0xD9, 0xFD, 0xA0, 0x88, 0x6A, 0xC8, 0x60, 0x28, + 0xF9, 0x5A, 0x46, 0x9B, 0x18, 0x1A, 0x25, 0xF8, 0x1D, 0x06, 0x5B, 0xF4, + 0xBE, 0x2C, 0x5A, 0x11, 0x42, 0x5A, 0x83, 0xE6, 0xEC, 0x43, 0xF7, 0xF6, + 0x7B, 0x65, 0x06, 0x7D, 0x6F, 0xFF, 0xAD, 0xA7, 0x06, 0x43, 0xA8, 0x50, + 0x88, 0xAC, 0x0F, 0x22, 0xEB, 0x67, 0xD5, 0xA1, 0x38, 0xE8, 0x2A, 0x7E, + 0xA8, 0x0D, 0xA5, 0x51, 0x36, 0xEE, 0x5D, 0xBD, 0x53, 0x36, 0x12, 0x3C, + 0x72, 0x81, 0xFF, 0x90, 0x22, 0x7C, 0x6E, 0x9D, 0xF8, 0x0B, 0xD5, 0x74, + 0xF6, 0x56, 0xE8, 0xD9, 0xB4, 0xB0, 0xB1, 0x94, 0x00, 0xCB, 0x3D, 0x26, + 0x8E, 0xDA, 0x8E, 0xA2, 0xB7, 0xE7, 0x86, 0xEB, 0xA8, 0x31, 0x5A, 0xFD, + 0xE1, 0xD8, 0x18, 0xA1, 0x7C, 0x0F, 0x18, 0x21, 0xA2, 0xD2, 0xF6, 0xA8, + 0xC2, 0xE2, 0x1E, 0x54, 0x58, 0xBC, 0x97, 0x17, 0x85, 0x05, 0x1A, 0x11, + 0x9E, 0xB4, 0x58, 0x4C, 0xC5, 0x62, 0x3B, 0x25, 0x8B, 0xEB, 0xA4, 0x35, + 0xC0, 0x1C, 0x9F, 0x22, 0x84, 0x09, 0x13, 0x22, 0x88, 0xD1, 0x1F, 0xBC, + 0xBA, 0xC0, 0xC6, 0x2B, 0x3B, 0x1B, 0x8F, 0xEC, 0xB4, 0x8E, 0xCE, 0x81, + 0xB3, 0x83, 0x83, 0xF1, 0xF9, 0x41, 0xB6, 0x22, 0xD0, 0x42, 0xCD, 0x6C, + 0xAC, 0x25, 0xD3, 0xE1, 0x6C, 0x32, 0x36, 0x67, 0xEF, 0xE6, 0x6C, 0x3D, + 0x3B, 0x9C, 0x4D, 0x4C, 0xA2, 0x0A, 0x90, 0x9D, 0x07, 0x37, 0x76, 0x5E, + 0x91, 0xB1, 0xB7, 0xF4, 0x66, 0xF0, 0x2B, 0xC2, 0x82, 0xCA, 0xF9, 0x80, + 0xB4, 0x1A, 0x1D, 0xA4, 0xB5, 0x66, 0xBB, 0x41, 0x07, 0x18, 0x59, 0xB0, + 0x13, 0x5C, 0x1E, 0x35, 0x84, 0x21, 0x51, 0xD1, 0xD0, 0xAF, 0x20, 0x4E, + 0xC9, 0x75, 0xD5, 0x28, 0x20, 0xE1, 0x11, 0x61, 0x9B, 0xB6, 0x2D, 0xDA, + 0xAA, 0x3F, 0x45, 0x98, 0xA2, 0xA7, 0x7A, 0x68, 0x41, 0x31, 0x6B, 0xC1, + 0x83, 0x74, 0x13, 0xC5, 0x12, 0xED, 0xE8, 0x7B, 0xEF, 0xF1, 0x12, 0x96, + 0x82, 0x51, 0xB5, 0xF0, 0xC9, 0xA0, 0x07, 0xA4, 0xE6, 0xD2, 0x6A, 0x40, + 0x32, 0x35, 0xAC, 0x33, 0xF1, 0x6A, 0x30, 0x23, 0x40, 0x0D, 0x5D, 0x6F, + 0x59, 0x41, 0xE7, 0xBE, 0x42, 0xD6, 0xEF, 0x0C, 0x8C, 0xDF, 0x97, 0xA8, + 0xEE, 0xAF, 0x7B, 0x24, 0xB4, 0x3B, 0x1B, 0xF4, 0xA6, 0x62, 0xF6, 0x6E, + 0x34, 0xBE, 0x66, 0x01, 0x41, 0xC2, 0x0B, 0x5C, 0x3F, 0xE0, 0x08, 0x8B, + 0x23, 0x70, 0x22, 0x68, 0xD0, 0x8E, 0x6A, 0x90, 0x7A, 0x8A, 0x04, 0x89, + 0x18, 0xDA, 0xB6, 0xA9, 0xF3, 0xAE, 0x6F, 0x2A, 0xFE, 0x62, 0x98, 0x7A, + 0xBD, 0xED, 0x8A, 0x31, 0x89, 0xEC, 0xF0, 0x4E, 0x58, 0x83, 0x55, 0xF1, + 0x3F, 0x02, 0x1F, 0xB8, 0x9A, 0xAF, 0xF5, 0xB3, 0x10, 0xF8, 0xC0, 0xE5, + 0x02, 0x89, 0x3B, 0x1D, 0xE0, 0xC8, 0xF3, 0x0F, 0x11, 0xC0, 0x18, 0x3D, + 0x80, 0xCD, 0x13, 0xA6, 0x04, 0x58, 0x33, 0x7D, 0x94, 0x61, 0x00, 0xEB, + 0x32, 0x39, 0xEC, 0xE6, 0xEC, 0xA0, 0x64, 0x63, 0xB5, 0xB2, 0x06, 0xAB, + 0x9B, 0x7A, 0xAC, 0x6E, 0xBE, 0x45, 0x2D, 0xFD, 0x06, 0xB5, 0x34, 0x4C, + 0x06, 0x61, 0xB5, 0xDD, 0x1D, 0xAB, 0x85, 0x18, 0xCC, 0x9A, 0xE2, 0xEF, + 0xE6, 0x9E, 0xF4, 0x86, 0xD2, 0xA4, 0xE5, 0x92, 0x1C, 0xC0, 0x18, 0xA5, + 0x63, 0x93, 0x3C, 0x8D, 0x7C, 0x13, 0x49, 0x6F, 0x6C, 0x5D, 0xC5, 0xDD, + 0xD2, 0xF7, 0xE1, 0x64, 0xEB, 0x1A, 0x08, 0xB7, 0x75, 0xD5, 0x1B, 0x04, + 0x97, 0x12, 0xFF, 0x89, 0x53, 0x89, 0xB7, 0x57, 0x88, 0xA6, 0x44, 0xA1, + 0x8B, 0xA6, 0x6E, 0x42, 0xB4, 0x13, 0x30, 0x56, 0xA2, 0xB9, 0xB0, 0xCF, + 0xAA, 0xB5, 0x48, 0xE9, 0x9D, 0x3C, 0x27, 0xA1, 0x7C, 0x20, 0x8C, 0xE9, + 0x5B, 0xD3, 0x5A, 0x41, 0xDA, 0x2E, 0x04, 0x24, 0xA1, 0x18, 0x4E, 0xEA, + 0x61, 0xE4, 0x44, 0xEF, 0x62, 0x7A, 0x52, 0x44, 0xD6, 0xDE, 0xEB, 0xA4, + 0x5E, 0xD5, 0x49, 0x08, 0xCA, 0x0A, 0x7A, 0x87, 0x1E, 0x9D, 0xBB, 0xE8, + 0x6C, 0x0E, 0x65, 0x3F, 0x96, 0x9B, 0x87, 0x40, 0xBA, 0x77, 0x95, 0x54, + 0x30, 0x8A, 0xA6, 0x3A, 0xDD, 0x96, 0x33, 0x02, 0x69, 0x2B, 0x63, 0xFA, + 0x5E, 0xC1, 0x70, 0xD0, 0xF7, 0xAA, 0xA7, 0x5D, 0x81, 0x60, 0x4A, 0xAC, + 0x50, 0x82, 0x0A, 0x97, 0x30, 0xF6, 0x8B, 0xF7, 0x89, 0x74, 0xB9, 0xE0, + 0xAC, 0x38, 0x35, 0xE5, 0xC7, 0xEB, 0x59, 0x07, 0xC4, 0x2C, 0x6D, 0xA9, + 0x00, 0xCD, 0x71, 0xF2, 0x91, 0x0F, 0x39, 0x7A, 0x46, 0x19, 0x1F, 0x06, + 0xFA, 0x4C, 0xA5, 0xDC, 0xD4, 0x58, 0x04, 0x98, 0x5A, 0xE3, 0x43, 0xB1, + 0x08, 0x1F, 0x1F, 0x8E, 0xEA, 0x52, 0xC5, 0xC5, 0xAC, 0x85, 0x1E, 0xC6, + 0x06, 0x2A, 0x71, 0xE0, 0x48, 0x9B, 0x6C, 0xAD, 0x4E, 0x1A, 0x61, 0xF5, + 0xF6, 0x36, 0x1D, 0x0F, 0xA6, 0xA6, 0xA3, 0x64, 0x4A, 0xC9, 0x44, 0xB4, + 0xD2, 0x88, 0xE8, 0x16, 0xF5, 0xE1, 0xC2, 0x71, 0x3A, 0x26, 0x1C, 0x07, + 0x82, 0x5E, 0x69, 0x4A, 0xD1, 0x41, 0x72, 0x34, 0x1D, 0xB9, 0x14, 0x40, + 0x60, 0xD1, 0x6D, 0xB0, 0x6D, 0xB3, 0x6C, 0x16, 0x50, 0xB4, 0x0C, 0x4D, + 0x06, 0x8A, 0xA6, 0x72, 0x47, 0x13, 0xB5, 0x4C, 0x0A, 0x5A, 0xE3, 0x60, + 0x4B, 0x49, 0xDF, 0xD4, 0xBA, 0x5A, 0x93, 0x3D, 0x77, 0xA9, 0x28, 0x7B, + 0x8C, 0x4B, 0x19, 0xAE, 0xC0, 0xDB, 0x32, 0x65, 0x53, 0x84, 0xAB, 0xDB, + 0x7A, 0x4F, 0x6F, 0x50, 0x8D, 0x6E, 0x46, 0x35, 0x18, 0xA3, 0xA1, 0x19, + 0x03, 0xC1, 0x8F, 0x09, 0xDD, 0x51, 0x65, 0xDA, 0xFB, 0x2F, 0x59, 0x4F, + 0x17, 0x00, 0x1E, 0x50, 0x64, 0x49, 0x42, 0xE9, 0xDA, 0xE8, 0x44, 0xC8, + 0x25, 0x19, 0x23, 0xC5, 0x49, 0x0A, 0xB8, 0xE6, 0x9B, 0x80, 0xCB, 0x86, + 0x96, 0xAE, 0x99, 0x25, 0x74, 0x6D, 0x43, 0x0A, 0x6B, 0x1B, 0x41, 0x68, + 0x7D, 0xE5, 0xB4, 0x10, 0xCD, 0xCF, 0x46, 0x0F, 0x28, 0x7E, 0xF2, 0x80, + 0x42, 0x98, 0x81, 0xFA, 0x52, 0xC7, 0x06, 0x64, 0x08, 0x6C, 0x00, 0x43, + 0x4A, 0x12, 0x24, 0x46, 0xE2, 0xE2, 0x8C, 0x04, 0x8F, 0x54, 0x01, 0xA6, + 0x1D, 0x01, 0xD5, 0xEC, 0x83, 0x0A, 0x80, 0xEA, 0x64, 0xDC, 0x03, 0x85, + 0x44, 0x75, 0x42, 0x8D, 0x68, 0xDF, 0x22, 0x9A, 0x4B, 0xEA, 0x3D, 0xDD, + 0xCC, 0x90, 0xD3, 0x19, 0xE5, 0x92, 0x1F, 0x39, 0xF1, 0x96, 0x40, 0x61, + 0x46, 0x08, 0xF2, 0xB2, 0x09, 0x90, 0x97, 0x8D, 0x25, 0x2F, 0x5B, 0x26, + 0x47, 0xEF, 0x9A, 0x46, 0xC3, 0x34, 0xCD, 0xBE, 0xD0, 0x34, 0x73, 0x0B, + 0xFA, 0x34, 0x9D, 0x13, 0x7F, 0xB9, 0xCD, 0x89, 0xB3, 0xA0, 0x36, 0x86, + 0xD6, 0x27, 0x8E, 0x84, 0xE8, 0xA4, 0x89, 0x98, 0x08, 0xAD, 0x60, 0x04, + 0xB3, 0xD7, 0xE7, 0x06, 0x6A, 0x84, 0x3F, 0x0C, 0x85, 0xE2, 0x4D, 0x15, + 0x92, 0xFF, 0xA0, 0x2B, 0xA4, 0x08, 0x2F, 0xF0, 0x16, 0x4D, 0xD5, 0x95, + 0x2D, 0x53, 0x43, 0x50, 0xD1, 0x51, 0x60, 0xDA, 0xE2, 0xDB, 0xBC, 0x49, + 0xCE, 0x8C, 0x93, 0xDC, 0x08, 0x95, 0x64, 0xB6, 0xE4, 0xB3, 0x9C, 0x0B, + 0x72, 0xD6, 0x69, 0x89, 0xF9, 0x8B, 0x44, 0x5E, 0x3A, 0x4A, 0x17, 0x61, + 0x58, 0x78, 0xF6, 0xBA, 0x82, 0x23, 0x09, 0x05, 0x00, 0x84, 0x47, 0x53, + 0x88, 0xD3, 0x18, 0x28, 0xC0, 0x7D, 0xE6, 0x02, 0x55, 0x62, 0x69, 0xC1, + 0x7F, 0xE2, 0xA8, 0x12, 0x4B, 0xC1, 0xCA, 0x9B, 0x15, 0x25, 0x14, 0x06, + 0x19, 0x36, 0x4E, 0xF5, 0x0E, 0x14, 0xF2, 0x49, 0x0E, 0x4A, 0xA3, 0x51, + 0x08, 0x50, 0x4D, 0xEC, 0x32, 0x80, 0xF2, 0x21, 0xDF, 0x01, 0x54, 0xCF, + 0x23, 0x8A, 0x6E, 0xA7, 0x2E, 0x8A, 0xD2, 0x5B, 0x72, 0x3A, 0xC2, 0x8E, + 0x5C, 0x9B, 0xE4, 0x38, 0x84, 0x40, 0x5E, 0xE6, 0x0E, 0xF2, 0x2C, 0x4A, + 0x19, 0x9A, 0xDE, 0x13, 0x81, 0x83, 0x2B, 0x36, 0xCB, 0x19, 0xC3, 0xC4, + 0x26, 0x40, 0xD3, 0x6C, 0x0C, 0x4D, 0xB7, 0x3D, 0x40, 0xD3, 0xC3, 0x68, + 0xFA, 0x61, 0x00, 0xA2, 0xE2, 0xE2, 0x7F, 0xD7, 0xB9, 0xD1, 0xDB, 0xA1, + 0xEB, 0xF6, 0xF5, 0x33, 0xB4, 0xE9, 0x63, 0xF9, 0x96, 0x91, 0x14, 0xF7, + 0xD9, 0xBB, 0xBB, 0x33, 0x2C, 0x6D, 0x6B, 0x40, 0xA6, 0x09, 0x43, 0x17, + 0x93, 0xD0, 0x31, 0x92, 0x92, 0x08, 0x77, 0xA2, 0x4C, 0x92, 0xC0, 0xAF, + 0xA8, 0x93, 0xD8, 0xE7, 0x14, 0x78, 0xA1, 0x10, 0x85, 0x68, 0x8F, 0xFF, + 0x66, 0xF7, 0x69, 0x02, 0x50, 0x7F, 0x1A, 0x90, 0x65, 0x28, 0xAD, 0x6B, + 0xAD, 0x7C, 0xB9, 0xB2, 0x72, 0x4F, 0x08, 0x8C, 0x13, 0xEE, 0x4C, 0xCE, + 0x83, 0x4D, 0xDE, 0xFE, 0x19, 0x23, 0x51, 0x09, 0xAA, 0x6F, 0x1C, 0x41, + 0x25, 0x14, 0x17, 0x27, 0x08, 0x98, 0x25, 0xE3, 0xD0, 0x59, 0x41, 0xD5, + 0x3C, 0x86, 0x54, 0xFD, 0x57, 0xA7, 0x28, 0xB8, 0x0C, 0xAB, 0xD2, 0x28, + 0x45, 0x17, 0x2A, 0x23, 0x33, 0x2F, 0x46, 0x6C, 0xCC, 0xDE, 0x07, 0xCE, + 0xB1, 0x6E, 0x6D, 0x7B, 0xD7, 0xCA, 0xBC, 0x0E, 0x65, 0x4E, 0x96, 0x6D, + 0x21, 0x54, 0x2C, 0x3E, 0x84, 0x43, 0xAD, 0x08, 0xD5, 0x3C, 0xB4, 0x74, + 0xEF, 0x0A, 0xA7, 0x6B, 0x0B, 0xD2, 0x02, 0xC8, 0x1A, 0x5C, 0x98, 0x43, + 0x6B, 0x49, 0x77, 0x57, 0x42, 0x40, 0x08, 0x31, 0xB4, 0x76, 0xC0, 0x11, + 0x23, 0x96, 0x83, 0x87, 0x13, 0x81, 0x2E, 0x5C, 0x3B, 0x70, 0x75, 0xC9, + 0x61, 0x48, 0x9B, 0xE5, 0x43, 0x74, 0xB8, 0x79, 0xD8, 0x2C, 0xCF, 0x83, + 0x83, 0x16, 0x14, 0x8A, 0xBD, 0x57, 0x3A, 0xFD, 0x08, 0x5D, 0x6A, 0x98, + 0x15, 0xD3, 0x84, 0x89, 0x41, 0xAB, 0xC7, 0x10, 0x3C, 0x2A, 0xE6, 0x78, + 0x53, 0x5C, 0x7D, 0x96, 0xB7, 0x0F, 0x69, 0xC1, 0x7C, 0x88, 0xD8, 0x90, + 0x18, 0xF3, 0x21, 0x16, 0x6B, 0x97, 0x30, 0xE6, 0x5D, 0xC0, 0x98, 0xB7, + 0x17, 0x8B, 0x73, 0x8D, 0x79, 0x9B, 0x56, 0x12, 0xA1, 0x9C, 0x21, 0xC4, + 0x73, 0xF8, 0x30, 0x24, 0x0B, 0x9A, 0x1C, 0x55, 0x40, 0x01, 0x05, 0xEE, + 0xCA, 0xA2, 0x50, 0x35, 0xEC, 0xA5, 0xFB, 0x48, 0xFF, 0x19, 0x23, 0xCF, + 0x12, 0x0E, 0xA5, 0x3B, 0x11, 0x19, 0x24, 0x9C, 0xE1, 0xB1, 0x41, 0x2B, + 0xEA, 0xFF, 0x16, 0x82, 0xA2, 0x6E, 0x8A, 0x2D, 0x27, 0xD6, 0x72, 0xA7, + 0xD5, 0x75, 0xB1, 0xFC, 0x8A, 0xB1, 0x00, 0xE3, 0x41, 0xB5, 0x9C, 0x07, + 0x94, 0x0B, 0x2D, 0x5D, 0x74, 0x78, 0x32, 0xD0, 0x34, 0xE2, 0x54, 0x1A, + 0x86, 0xFB, 0x72, 0x74, 0x5C, 0x9C, 0x65, 0x15, 0x2E, 0x74, 0xD1, 0x54, + 0x18, 0xEC, 0xD1, 0x24, 0x85, 0x5D, 0x5D, 0x8A, 0xF5, 0x68, 0xC0, 0xB2, + 0xA9, 0x78, 0x3E, 0xE9, 0xBA, 0x4D, 0xD7, 0x8C, 0x2E, 0xB6, 0x23, 0xE4, + 0xE2, 0x48, 0x06, 0x35, 0x4F, 0xB0, 0x25, 0x74, 0xEE, 0x82, 0x60, 0x31, + 0xE0, 0x33, 0x31, 0x61, 0x9B, 0x15, 0x91, 0xA4, 0x04, 0x74, 0xD1, 0x13, + 0x01, 0x62, 0xEB, 0x70, 0x5E, 0x82, 0x88, 0x31, 0x51, 0xAA, 0x9F, 0xB7, + 0x65, 0x3C, 0x10, 0x0D, 0xF6, 0xF9, 0x07, 0x28, 0x6C, 0xD7, 0xD5, 0x54, + 0x28, 0x0A, 0x3A, 0xE4, 0xFB, 0x96, 0x8F, 0x6F, 0x13, 0xF7, 0xC8, 0x75, + 0x4E, 0xA5, 0x49, 0xD9, 0x42, 0x39, 0xB4, 0x59, 0xDA, 0xA6, 0x21, 0x11, + 0xDC, 0x1C, 0x93, 0x39, 0xC7, 0xCC, 0xF1, 0x0E, 0x47, 0x30, 0x7D, 0xE8, + 0x91, 0xE3, 0xE3, 0xD8, 0x58, 0x52, 0xCD, 0x68, 0x73, 0x7B, 0x7B, 0xC3, + 0xB3, 0xCE, 0xDD, 0x7D, 0x56, 0xB5, 0x73, 0x9B, 0xFA, 0xDF, 0xE9, 0xF1, + 0x02, 0x4F, 0x31, 0x8F, 0x86, 0xA3, 0x07, 0xAE, 0xFE, 0x05, 0x80, 0x40, + 0x62, 0x3E, 0x1E, 0x13, 0x2F, 0x80, 0x40, 0xBA, 0x51, 0x85, 0xE2, 0x8A, + 0x30, 0x29, 0x0C, 0x99, 0x07, 0x6E, 0xF8, 0x0A, 0x18, 0x41, 0x89, 0x03, + 0x20, 0xA6, 0x8C, 0x09, 0x62, 0x8C, 0x59, 0x5B, 0x04, 0xB4, 0x54, 0x76, + 0x81, 0x0F, 0x5E, 0xC0, 0x9E, 0x9C, 0x34, 0x50, 0xD8, 0x70, 0x3A, 0x53, + 0x1B, 0xE3, 0x00, 0x59, 0xB9, 0x77, 0x2E, 0x4E, 0xD5, 0x8A, 0x81, 0x43, + 0xB4, 0x44, 0x46, 0x8C, 0x47, 0x98, 0x6E, 0x2B, 0xF0, 0x5D, 0xA9, 0xAA, + 0x9F, 0xC9, 0xFC, 0xC9, 0x31, 0x06, 0x0C, 0xB4, 0x9B, 0x22, 0x22, 0x67, + 0xBA, 0x03, 0x56, 0x7B, 0x33, 0xBC, 0x8C, 0x02, 0x57, 0x22, 0xF0, 0xA6, + 0x4F, 0x51, 0x7F, 0xD5, 0x99, 0xAA, 0xC5, 0x97, 0xB4, 0x47, 0x52, 0x25, + 0xCF, 0xAD, 0x44, 0x1D, 0xE0, 0xC8, 0x44, 0x8F, 0x6F, 0xAF, 0xCE, 0x1D, + 0x55, 0xD4, 0x68, 0x4D, 0xCB, 0x06, 0xC4, 0xE8, 0xC7, 0x56, 0x96, 0x11, + 0x90, 0x00, 0xEA, 0xED, 0x51, 0x82, 0xA4, 0x68, 0x23, 0x40, 0x84, 0xE6, + 0x5F, 0x11, 0x9F, 0x29, 0x05, 0x82, 0xAE, 0x83, 0x9E, 0x89, 0x07, 0x82, + 0xAE, 0x34, 0xAE, 0xCD, 0x04, 0xE2, 0x6C, 0x08, 0x34, 0xBC, 0xD2, 0x25, + 0x38, 0xAC, 0x5C, 0x5B, 0xFE, 0x87, 0xD8, 0x49, 0x81, 0xC6, 0xB5, 0x4C, + 0x00, 0xC0, 0x62, 0x8F, 0x0A, 0x61, 0x76, 0x48, 0x71, 0x0C, 0x40, 0x16, + 0x1B, 0x53, 0x10, 0x02, 0x69, 0x88, 0x30, 0x20, 0xBC, 0x84, 0xA1, 0x71, + 0x17, 0xAA, 0x6F, 0xE1, 0x40, 0x60, 0x95, 0xE0, 0x93, 0xCB, 0xE6, 0x36, + 0x56, 0xC2, 0xFE, 0x95, 0x0C, 0xFD, 0xC0, 0x3E, 0x7C, 0x22, 0x78, 0xCD, + 0xF7, 0x64, 0xF1, 0xAC, 0xF8, 0xE8, 0x36, 0x16, 0x0E, 0xDB, 0x7B, 0x1A, + 0x84, 0xFD, 0x83, 0x09, 0x83, 0x7F, 0x17, 0x18, 0x47, 0xBC, 0x99, 0xE4, + 0xF5, 0xDE, 0x91, 0xD7, 0xCA, 0x85, 0xBF, 0x40, 0xF5, 0x9E, 0x0B, 0x54, + 0x17, 0xD7, 0xE6, 0x8E, 0x99, 0x79, 0x1B, 0x78, 0x9D, 0x17, 0xC4, 0x8B, + 0x7D, 0x2F, 0x26, 0x78, 0xED, 0x85, 0x04, 0x2F, 0xBD, 0xAD, 0x29, 0x5E, + 0x6F, 0x49, 0xF1, 0x5A, 0x8A, 0x61, 0xC9, 0x4C, 0x93, 0x85, 0xD2, 0x55, + 0xEE, 0x2F, 0x71, 0x06, 0xFD, 0xDC, 0xF3, 0x46, 0x0A, 0x2D, 0x12, 0x3F, + 0x3E, 0x54, 0xDB, 0x96, 0x78, 0xBA, 0xA2, 0xC1, 0x2B, 0x5C, 0x8B, 0x6A, + 0x90, 0x18, 0x04, 0x3A, 0x5D, 0x57, 0xD5, 0x30, 0xE7, 0x5A, 0x90, 0x22, + 0x67, 0x77, 0x35, 0x18, 0x92, 0xAB, 0xC1, 0xDC, 0xAE, 0xFE, 0xC9, 0xD5, + 0x7B, 0x70, 0x65, 0x86, 0xA8, 0x03, 0x86, 0xA2, 0xA0, 0x89, 0x16, 0x38, + 0xD7, 0x8F, 0x24, 0xE9, 0xA0, 0x68, 0xC6, 0x6A, 0x14, 0x05, 0xF8, 0x30, + 0x0E, 0xFD, 0xA6, 0xED, 0x8A, 0xAA, 0x40, 0xD5, 0x4A, 0x3A, 0xF0, 0x8B, + 0xB3, 0x3A, 0x8E, 0xE8, 0x31, 0x75, 0xFE, 0x7C, 0x26, 0x5F, 0xC2, 0x8C, + 0xDE, 0x6E, 0x91, 0x19, 0x70, 0x5F, 0xAE, 0x7F, 0x53, 0x96, 0x52, 0x1B, + 0xE2, 0x1C, 0x43, 0xCF, 0x95, 0x1E, 0xB6, 0xB8, 0xC9, 0x83, 0x95, 0xC6, + 0x4C, 0x0F, 0x16, 0xBA, 0xE5, 0x8D, 0x72, 0x36, 0xD3, 0xD3, 0x52, 0xA4, + 0xCD, 0x90, 0xA9, 0xDB, 0x40, 0x60, 0x57, 0x84, 0xC8, 0x94, 0xF0, 0x67, + 0xCB, 0x08, 0xF9, 0x16, 0x10, 0xB9, 0x64, 0xC4, 0x54, 0x96, 0x8C, 0xCF, + 0x70, 0x06, 0x51, 0x39, 0x65, 0xAB, 0x64, 0x00, 0x0B, 0x1B, 0x07, 0x83, + 0x4A, 0xC6, 0x87, 0xC4, 0x60, 0x0A, 0xBC, 0x55, 0xD4, 0xF4, 0x06, 0xDB, + 0xE2, 0xAB, 0x38, 0x14, 0x29, 0x32, 0xAE, 0xF6, 0xC3, 0x28, 0x4C, 0x3D, + 0x48, 0x8D, 0xAC, 0x67, 0x03, 0xEC, 0xBE, 0x54, 0x0A, 0x12, 0x57, 0xA7, + 0x42, 0xB4, 0x46, 0x32, 0xD4, 0x87, 0xE2, 0xEC, 0x71, 0x34, 0x89, 0xBD, + 0x33, 0x49, 0x9F, 0xB9, 0x42, 0xD9, 0x54, 0x70, 0x09, 0xE3, 0xB4, 0x86, + 0x29, 0x57, 0xCF, 0x93, 0x33, 0x1C, 0x9E, 0x9C, 0xA1, 0x58, 0xDA, 0xF6, + 0xF9, 0x43, 0xEA, 0x67, 0x8F, 0xEF, 0xD1, 0x94, 0x42, 0x57, 0xA5, 0x3E, + 0x95, 0x7A, 0x74, 0xCF, 0x0E, 0x09, 0xA9, 0xDB, 0xD2, 0xAD, 0xA8, 0x93, + 0x58, 0x51, 0x43, 0x8D, 0x02, 0x09, 0x82, 0xE9, 0x24, 0x9F, 0xA9, 0x29, + 0x19, 0x13, 0xA8, 0xD7, 0x02, 0xBF, 0xB1, 0x92, 0x10, 0x9B, 0x8A, 0x42, + 0x64, 0x00, 0x21, 0x29, 0x3F, 0x3F, 0xB7, 0x69, 0x08, 0x2B, 0x8A, 0xF4, + 0xAB, 0x6B, 0xCE, 0xF3, 0x63, 0x79, 0x9E, 0x1E, 0x5C, 0x3D, 0x96, 0x73, + 0x35, 0xC7, 0xB4, 0x96, 0x8F, 0x65, 0x39, 0xBF, 0x96, 0x3B, 0xBD, 0x82, + 0x78, 0x5D, 0xCB, 0xF1, 0x52, 0x2C, 0x57, 0x99, 0x17, 0x05, 0xE6, 0xED, + 0x28, 0x9F, 0x9C, 0x13, 0x98, 0x39, 0x4E, 0x85, 0x90, 0x04, 0x97, 0xC1, + 0xC4, 0x84, 0xDE, 0x15, 0x4D, 0x4C, 0x36, 0x50, 0x34, 0x7B, 0x41, 0x93, + 0xA3, 0xE9, 0x2A, 0x30, 0x78, 0x81, 0xC0, 0x55, 0xBE, 0xA4, 0xDB, 0xD4, + 0x67, 0xAD, 0x98, 0x0A, 0x20, 0xE4, 0x35, 0x43, 0x7F, 0x52, 0x92, 0x28, + 0x2A, 0xA5, 0xD0, 0xF5, 0x8B, 0x8D, 0x09, 0xDB, 0xA2, 0xAE, 0x50, 0xF4, + 0x64, 0xBA, 0x33, 0x7E, 0x5A, 0x10, 0xC8, 0x25, 0xEB, 0x59, 0x64, 0x99, + 0x13, 0x2C, 0x51, 0xB0, 0xD0, 0xC2, 0xDD, 0x0F, 0x56, 0x78, 0x60, 0x90, + 0xD4, 0x36, 0x77, 0x06, 0xD5, 0x43, 0x76, 0xD1, 0x31, 0xA7, 0x43, 0x03, + 0x1D, 0x22, 0xD8, 0x3D, 0x2D, 0x42, 0x95, 0x27, 0x32, 0xA8, 0xF7, 0x98, + 0x50, 0x7B, 0x54, 0xE5, 0x7B, 0x52, 0xE5, 0xED, 0xCA, 0x3C, 0xE5, 0xA9, + 0x18, 0xE5, 0x40, 0x52, 0x21, 0x54, 0x4C, 0xC4, 0x35, 0x3A, 0xA0, 0x5E, + 0x83, 0x00, 0xD4, 0x6D, 0x6A, 0xC1, 0x1E, 0x2E, 0x5B, 0x5C, 0xB2, 0x92, + 0x43, 0x30, 0x5C, 0xBA, 0x64, 0x2D, 0xF6, 0x7A, 0x43, 0x41, 0x30, 0xD7, + 0x00, 0x2A, 0x3A, 0xC4, 0x69, 0xA0, 0x78, 0x19, 0x28, 0x6E, 0x53, 0xB6, + 0x0D, 0x90, 0x9E, 0x7D, 0xB2, 0x20, 0xE3, 0xE2, 0x21, 0x25, 0x11, 0x3A, + 0x87, 0x21, 0xA5, 0xD5, 0x6B, 0x4C, 0x29, 0x9F, 0x8B, 0x9B, 0xE4, 0x49, + 0xCD, 0x1D, 0x77, 0x74, 0xD4, 0x79, 0x93, 0xDF, 0xF2, 0x69, 0x53, 0xB4, + 0x84, 0x96, 0xC9, 0x3A, 0x97, 0x2A, 0x09, 0xFA, 0x03, 0xF2, 0xA9, 0x5B, + 0xE2, 0x88, 0x38, 0x39, 0xB9, 0xBF, 0x8E, 0x9E, 0x39, 0xC3, 0x90, 0x83, + 0x44, 0x2B, 0x90, 0x1D, 0x0B, 0xCC, 0x7C, 0x19, 0x30, 0x60, 0x4F, 0xF0, + 0x21, 0xF6, 0x53, 0xA0, 0xE3, 0x38, 0x11, 0xCF, 0x64, 0x15, 0x38, 0x5E, + 0x21, 0xA1, 0x4C, 0xE2, 0x19, 0xF4, 0xCC, 0x9A, 0x7C, 0x60, 0x66, 0xC1, + 0xC5, 0x4A, 0x2E, 0xE5, 0xAC, 0xB0, 0xB6, 0x73, 0x1A, 0x8D, 0xD2, 0x04, + 0xD6, 0x4A, 0x22, 0xAC, 0x8F, 0x3D, 0x14, 0x7D, 0x7C, 0x53, 0xB4, 0x5D, + 0x0F, 0x25, 0x8E, 0x7E, 0xC2, 0xE1, 0x68, 0x11, 0xD4, 0x5C, 0xA6, 0xCF, + 0xF1, 0x32, 0x45, 0x1C, 0xF7, 0xF4, 0x8E, 0xDE, 0xBB, 0x21, 0x4C, 0x38, + 0xDC, 0x7B, 0x22, 0xBA, 0x2D, 0x22, 0xD1, 0xB6, 0xAA, 0xF9, 0xD1, 0xD3, + 0x3C, 0x8D, 0x16, 0x65, 0xC3, 0x80, 0xE5, 0x7C, 0x05, 0x34, 0x01, 0x7D, + 0x59, 0x22, 0x69, 0x78, 0x23, 0xE8, 0xC0, 0x29, 0xF4, 0xDF, 0x18, 0x35, + 0xA4, 0x9F, 0x6E, 0x9F, 0xD5, 0x15, 0x98, 0x12, 0x47, 0x7F, 0x2E, 0x12, + 0x79, 0x4B, 0x56, 0xF9, 0x0D, 0xFC, 0xF6, 0xE1, 0x71, 0x0A, 0x67, 0x5E, + 0xCC, 0x06, 0x79, 0xE0, 0xE8, 0x21, 0x93, 0xC1, 0x5A, 0x44, 0x5B, 0xB7, + 0x11, 0xF3, 0xEF, 0x70, 0x34, 0x49, 0x53, 0x75, 0xBF, 0x7B, 0x2A, 0x6D, + 0xA8, 0x4A, 0xEF, 0x2C, 0xD5, 0xDF, 0x04, 0x02, 0xA4, 0x50, 0x2D, 0x6D, + 0x7B, 0x75, 0x55, 0xD3, 0x71, 0xAB, 0xDA, 0x88, 0x82, 0x10, 0xF5, 0x0F, + 0x1B, 0xB4, 0x34, 0x6B, 0x83, 0x12, 0x9A, 0x53, 0x54, 0x91, 0x72, 0xCB, + 0x40, 0x31, 0xFA, 0xCD, 0x89, 0xD1, 0x6D, 0xC9, 0x26, 0x46, 0x27, 0x99, + 0x18, 0x6D, 0xC7, 0x84, 0x25, 0x2B, 0xA2, 0xCA, 0xB1, 0xFC, 0x0C, 0x0E, + 0xA4, 0xF1, 0x26, 0xE0, 0x9B, 0xD8, 0xA4, 0x07, 0x9C, 0x4A, 0x10, 0x95, + 0x89, 0x99, 0xBE, 0xFD, 0xF5, 0x2A, 0xEA, 0xE4, 0xA7, 0xCF, 0x40, 0x71, + 0xA9, 0x4A, 0xBC, 0xE8, 0x4F, 0x88, 0x40, 0xAD, 0x05, 0x44, 0x67, 0x0A, + 0x77, 0xDF, 0x88, 0x36, 0xB4, 0x75, 0x6E, 0x80, 0x56, 0x3C, 0x39, 0x73, + 0x19, 0x16, 0x77, 0x6D, 0x1B, 0x8A, 0xA7, 0x08, 0xAD, 0x61, 0x10, 0x3A, + 0xEB, 0x8F, 0x21, 0x6E, 0x69, 0x3F, 0x4F, 0x67, 0x77, 0x42, 0x19, 0x50, + 0x82, 0xB0, 0x30, 0x20, 0x17, 0x82, 0x21, 0x52, 0x18, 0xD1, 0xCA, 0xE3, + 0x44, 0x56, 0x40, 0x06, 0x70, 0xCE, 0x4C, 0x89, 0x20, 0x79, 0x91, 0xD4, + 0x44, 0x52, 0x40, 0xBD, 0x67, 0x91, 0xD4, 0xC8, 0x25, 0x2D, 0x47, 0x8A, + 0x88, 0x5D, 0x54, 0xF7, 0xA5, 0xB8, 0x14, 0xF5, 0x1D, 0xF8, 0x42, 0xF7, + 0xE5, 0xEE, 0xB4, 0x84, 0xE8, 0xFC, 0xE1, 0x1F, 0xD5, 0xD1, 0x27, 0x9C, + 0xBB, 0xA5, 0xA5, 0xE0, 0xB9, 0x23, 0xF4, 0x21, 0xC0, 0xAE, 0x0F, 0x01, + 0xE8, 0x1C, 0x39, 0x02, 0x72, 0xB4, 0x04, 0xFC, 0xAB, 0x29, 0xD5, 0x0B, + 0xA0, 0x08, 0xAD, 0x0C, 0x3C, 0xA4, 0x65, 0xB8, 0x80, 0x80, 0xA4, 0xFD, + 0x68, 0x24, 0x49, 0x3D, 0x91, 0x96, 0xBA, 0x43, 0x62, 0x23, 0xB7, 0x1D, + 0x0C, 0x2B, 0x4E, 0xBE, 0xB0, 0x62, 0xAF, 0x99, 0xB7, 0xE4, 0x06, 0xC3, + 0xD7, 0x6E, 0x2D, 0x7C, 0x95, 0x71, 0xFA, 0xBA, 0xCC, 0x89, 0xAF, 0x9F, + 0xC8, 0x2C, 0xFD, 0x34, 0x86, 0x6A, 0x21, 0xA3, 0xA6, 0x1A, 0x42, 0x6F, + 0x08, 0x20, 0xB9, 0x5B, 0x9A, 0x61, 0xA3, 0xBD, 0x20, 0x04, 0xE5, 0xD0, + 0x74, 0xE0, 0x65, 0xDB, 0x17, 0xA3, 0x70, 0xF7, 0x75, 0x8D, 0x1A, 0x7A, + 0x64, 0x39, 0x02, 0xFC, 0xD0, 0x33, 0x30, 0x26, 0x17, 0xDF, 0x1E, 0x45, + 0x81, 0xD6, 0xBA, 0xCE, 0x47, 0x29, 0x18, 0x1C, 0xCE, 0x10, 0xB8, 0xE0, + 0x66, 0x39, 0x0D, 0xD9, 0x45, 0x24, 0xA4, 0xB1, 0x1D, 0x15, 0x19, 0xBF, + 0xE4, 0x26, 0xD6, 0x08, 0x1A, 0x2D, 0x60, 0xC5, 0xD6, 0x27, 0x9F, 0x36, + 0xA0, 0x21, 0xE0, 0x39, 0xC4, 0x3B, 0x92, 0x1E, 0x01, 0xFF, 0xF1, 0x80, + 0xD3, 0x1B, 0x19, 0x2D, 0x7E, 0x4A, 0x3A, 0x57, 0x74, 0xE0, 0xFA, 0x13, + 0x65, 0xD4, 0xA5, 0x38, 0x7D, 0x75, 0x28, 0x05, 0x33, 0x98, 0x70, 0x4B, + 0x8B, 0x8B, 0xB3, 0xCD, 0x94, 0x24, 0xC2, 0x93, 0x91, 0x48, 0xB9, 0xE5, + 0x94, 0xFE, 0x70, 0x1C, 0x02, 0x34, 0x85, 0x92, 0x62, 0xA8, 0x7E, 0x2E, + 0xC1, 0x81, 0x75, 0x07, 0x16, 0x40, 0x7F, 0x84, 0x5E, 0x87, 0x2A, 0xD0, + 0x9A, 0xD6, 0x09, 0xA1, 0xB4, 0x04, 0x9B, 0xD2, 0x86, 0x30, 0x1D, 0xC6, + 0x04, 0xE4, 0xD6, 0xC4, 0x9C, 0x1F, 0xC7, 0x2B, 0xF8, 0xA7, 0x90, 0x1F, + 0x47, 0xA7, 0x14, 0x54, 0x58, 0x4E, 0x74, 0x66, 0x7B, 0xD0, 0xBE, 0x9D, + 0x40, 0xF4, 0x1B, 0x09, 0xF7, 0xCD, 0xE8, 0x68, 0x1C, 0x49, 0x18, 0x9D, + 0x22, 0x88, 0x87, 0x2E, 0x6C, 0xEB, 0x69, 0xEA, 0x26, 0x8C, 0xA2, 0xBF, + 0x13, 0x3E, 0x7C, 0x14, 0xED, 0xA3, 0x47, 0x91, 0xB2, 0x2E, 0xEA, 0x22, + 0x23, 0x1E, 0x91, 0x08, 0xB9, 0xB4, 0x9E, 0xF6, 0x5B, 0xA9, 0x92, 0x74, + 0x02, 0x21, 0x24, 0x0D, 0x49, 0xE0, 0xB5, 0x9A, 0xBB, 0x7C, 0x10, 0xC4, + 0xEB, 0x2A, 0xA1, 0x10, 0xD8, 0x9A, 0xCF, 0x1F, 0x2D, 0x64, 0x9D, 0x65, + 0x45, 0x0F, 0x1B, 0x05, 0x06, 0x57, 0x25, 0x4C, 0xB4, 0xC5, 0x69, 0x6D, + 0xDC, 0x80, 0x09, 0x41, 0x6B, 0x54, 0x69, 0x51, 0x98, 0xD3, 0x62, 0x79, + 0xFF, 0x02, 0x14, 0x60, 0x81, 0x12, 0xC1, 0x83, 0xB0, 0x07, 0x21, 0x60, + 0x5C, 0x95, 0x55, 0x0B, 0x15, 0x5B, 0x94, 0xD1, 0xFE, 0x2E, 0x70, 0x0A, + 0x52, 0xCF, 0x50, 0x36, 0x7A, 0x38, 0x28, 0xD9, 0xE8, 0xE1, 0x29, 0xD7, + 0xBB, 0x63, 0xFA, 0x0B, 0x0E, 0x18, 0x7A, 0x14, 0x94, 0x0E, 0xB0, 0xAF, + 0xB7, 0xAA, 0xA8, 0x29, 0x70, 0x6D, 0xBB, 0xCA, 0xBC, 0x5D, 0x19, 0x8B, + 0xAA, 0xDD, 0xD6, 0xE9, 0x5C, 0x2F, 0x02, 0xE3, 0x17, 0xDB, 0x4C, 0x46, + 0x3D, 0x7D, 0x00, 0xB5, 0x18, 0x29, 0xC2, 0x62, 0x13, 0x38, 0xDC, 0x1E, + 0xCC, 0x38, 0x55, 0x3D, 0x23, 0x66, 0xFC, 0xDD, 0xE9, 0x2E, 0xF1, 0x60, + 0x5D, 0xB2, 0xC4, 0xAD, 0x6D, 0x57, 0xE2, 0x17, 0x0A, 0xD0, 0x5F, 0x01, + 0x03, 0xA4, 0x51, 0x5F, 0xD4, 0xB8, 0xBF, 0x17, 0x23, 0xA8, 0xC4, 0xCE, + 0xE0, 0xC2, 0xC5, 0x60, 0x89, 0xAB, 0x38, 0x3F, 0x2F, 0xF0, 0x00, 0xFF, + 0x09, 0xB6, 0x26, 0xC5, 0x12, 0x00, 0x65, 0x1D, 0x4F, 0xA2, 0x7C, 0xE8, + 0x79, 0xF6, 0x01, 0x27, 0x51, 0x4C, 0x66, 0x48, 0x7F, 0x86, 0x40, 0x17, + 0xA5, 0x42, 0x09, 0x70, 0x25, 0xC8, 0x70, 0x2B, 0xA1, 0x02, 0x20, 0x54, + 0x31, 0x51, 0xC3, 0x19, 0xF7, 0x85, 0x89, 0x8A, 0x9B, 0x52, 0x9E, 0x4C, + 0xB1, 0x2E, 0x19, 0x01, 0xEC, 0x92, 0x61, 0xAE, 0xDE, 0x7B, 0x1E, 0x61, + 0xFD, 0x96, 0x1E, 0x41, 0xEE, 0xE6, 0x77, 0x62, 0x37, 0xE6, 0xED, 0x3B, + 0x31, 0xB3, 0x68, 0xE2, 0x8F, 0x1A, 0x5B, 0xCC, 0x36, 0x2B, 0x71, 0x4A, + 0xFF, 0x06, 0x63, 0x93, 0x9C, 0x02, 0x02, 0x08, 0x2F, 0xDE, 0x8E, 0x6C, + 0x58, 0x63, 0x1A, 0x7F, 0x55, 0x78, 0x18, 0x85, 0x89, 0x9A, 0xCE, 0x00, + 0xA2, 0x96, 0x0E, 0x3A, 0x72, 0xA2, 0x40, 0x7A, 0x1F, 0xA4, 0xD8, 0xAE, + 0x07, 0x2E, 0x33, 0xCD, 0xB6, 0x1E, 0xFB, 0xDB, 0x92, 0xD2, 0x95, 0xA9, + 0x98, 0x89, 0x42, 0x0B, 0x41, 0x44, 0x26, 0x5D, 0x52, 0x85, 0x74, 0xA1, + 0xB7, 0x81, 0xC2, 0x42, 0x1B, 0x27, 0xDC, 0xD2, 0xB9, 0xA8, 0x69, 0x66, + 0x10, 0x2D, 0x46, 0xEE, 0x5D, 0x96, 0x05, 0x15, 0x09, 0x2F, 0x6C, 0x82, + 0x0E, 0x67, 0x37, 0x28, 0x87, 0xD2, 0xF0, 0x23, 0x3C, 0x9C, 0x10, 0xC9, + 0x91, 0x36, 0x19, 0xAA, 0xEA, 0x6B, 0xAA, 0x0E, 0x7F, 0x9C, 0x29, 0x12, + 0x1F, 0xE1, 0x60, 0x90, 0x90, 0x9D, 0xD4, 0xA1, 0x85, 0xD0, 0xF1, 0xFE, + 0x54, 0x71, 0x40, 0x95, 0xBD, 0x57, 0x31, 0xC4, 0xBA, 0x89, 0xF7, 0x18, + 0x69, 0x87, 0xDC, 0x58, 0x3E, 0xA4, 0x46, 0x50, 0x93, 0x4E, 0xDF, 0x4C, + 0xC6, 0xE3, 0x53, 0x9E, 0x09, 0x52, 0xA3, 0x47, 0x79, 0xD0, 0xC9, 0x09, + 0x30, 0xCC, 0x9C, 0xE8, 0x66, 0xBF, 0x0C, 0x3B, 0xCA, 0xA3, 0x5C, 0x15, + 0x88, 0xB6, 0xB7, 0x2F, 0x07, 0x00, 0xBA, 0xA9, 0xA9, 0x1E, 0x9F, 0x39, + 0x39, 0x63, 0xDA, 0xEE, 0x6F, 0x42, 0x8F, 0x17, 0x65, 0x63, 0x96, 0x63, + 0x4E, 0xBB, 0x28, 0xC6, 0x9C, 0xF6, 0x6B, 0x09, 0x1B, 0x13, 0xC6, 0x04, + 0x53, 0x67, 0x26, 0xEC, 0x97, 0x3A, 0xC2, 0x0F, 0x41, 0xFD, 0xD3, 0xA8, + 0x98, 0xF0, 0xAC, 0xFF, 0x3C, 0xB6, 0x2D, 0x65, 0x90, 0x3E, 0xB3, 0xD8, + 0xB6, 0xC4, 0x05, 0x88, 0x11, 0x64, 0x9A, 0xD0, 0x40, 0x43, 0xCA, 0x83, + 0x22, 0x96, 0xE4, 0xC8, 0x90, 0x5A, 0x9B, 0x12, 0xA3, 0x8A, 0xEA, 0xEF, + 0x24, 0x2A, 0x2A, 0xE0, 0x09, 0xE4, 0x7F, 0xAB, 0xBB, 0xA0, 0xEB, 0xBA, + 0x38, 0xDB, 0x5C, 0x84, 0x7A, 0xD6, 0x54, 0xCC, 0x27, 0x84, 0xCA, 0x4D, + 0xB4, 0x00, 0x29, 0xD0, 0xB6, 0x24, 0x9C, 0x2E, 0xA9, 0x0C, 0x83, 0x2C, + 0x1D, 0x2C, 0xAE, 0x80, 0xD7, 0xA8, 0xB6, 0x2A, 0x30, 0x5D, 0x4D, 0x1F, + 0x16, 0xFD, 0xE5, 0xA0, 0xE6, 0x79, 0x45, 0x69, 0x0D, 0x8C, 0xD2, 0x09, + 0xA1, 0x12, 0x15, 0x25, 0xA2, 0x22, 0xAD, 0x16, 0xBD, 0xC3, 0x0C, 0x1A, + 0xBA, 0x4D, 0xFD, 0xFB, 0xEE, 0x84, 0x9E, 0x89, 0x67, 0x4B, 0x56, 0x0B, + 0xA4, 0x63, 0x3C, 0x96, 0xB1, 0x42, 0x66, 0xC8, 0x04, 0x2C, 0x6D, 0x05, + 0xE8, 0x11, 0x13, 0xB7, 0x96, 0x97, 0x97, 0x2C, 0x2A, 0x40, 0x9A, 0xEE, + 0x0A, 0xE8, 0x13, 0x3B, 0xCB, 0x1E, 0x31, 0x26, 0x0C, 0x14, 0x3F, 0xCD, + 0x8C, 0x2E, 0xC4, 0xE6, 0x6B, 0x21, 0x36, 0x0E, 0x0A, 0x96, 0xFB, 0x98, + 0x60, 0x29, 0x26, 0xC6, 0x62, 0x90, 0x35, 0x4B, 0x41, 0xD6, 0x5B, 0x3B, + 0x28, 0xDF, 0x33, 0xA3, 0x4C, 0xF3, 0xC7, 0x35, 0xCA, 0x32, 0x85, 0xCD, + 0x64, 0x8C, 0x09, 0x20, 0x00, 0x12, 0x29, 0xC0, 0x6B, 0x9E, 0x8C, 0x88, + 0x3B, 0x32, 0xA9, 0x3F, 0x24, 0x09, 0xEC, 0x9F, 0x91, 0x28, 0x00, 0xF6, + 0xCF, 0x1B, 0xFA, 0x75, 0x02, 0x04, 0xD4, 0xA4, 0x98, 0x97, 0xB6, 0x18, + 0xCD, 0x80, 0xA6, 0xF9, 0x5A, 0xA6, 0xD1, 0x90, 0x90, 0x71, 0x5A, 0x3E, + 0xE8, 0xDC, 0xEB, 0x5E, 0x55, 0x6D, 0x17, 0x30, 0xED, 0x43, 0x0C, 0x01, + 0x90, 0xD1, 0x42, 0xAB, 0x0C, 0xD6, 0x72, 0x48, 0x46, 0x70, 0x04, 0x95, + 0xCC, 0x0E, 0xD7, 0x3D, 0x7A, 0x60, 0xE1, 0x76, 0x8C, 0x68, 0x86, 0xA8, + 0x4A, 0x8A, 0x2D, 0xC8, 0x81, 0x8B, 0x8D, 0x39, 0x5D, 0x68, 0x6C, 0x30, + 0x48, 0xD8, 0x13, 0x86, 0x88, 0x30, 0x51, 0x4B, 0xA8, 0xE0, 0x6C, 0x88, + 0x58, 0x41, 0xEC, 0xEE, 0xD4, 0x00, 0x0B, 0x31, 0x16, 0x54, 0x1B, 0x7F, + 0x55, 0x7D, 0x0D, 0x3A, 0x32, 0x54, 0xD1, 0xB2, 0x9F, 0x20, 0x3C, 0x3D, + 0x54, 0x0F, 0x05, 0x23, 0x32, 0x7A, 0x26, 0x01, 0xBF, 0xD3, 0xF8, 0x1C, + 0x19, 0xFD, 0x63, 0x47, 0x05, 0x80, 0xE4, 0x1A, 0xBF, 0x80, 0x64, 0xF5, + 0xB6, 0xA4, 0x1D, 0x73, 0x7F, 0x79, 0xBE, 0x70, 0x9F, 0x75, 0x01, 0xC9, + 0xD5, 0xFD, 0x3D, 0x0D, 0xCE, 0x8C, 0xF5, 0xF9, 0x6B, 0x83, 0xC1, 0x25, + 0xCB, 0x33, 0x06, 0x63, 0xC7, 0x99, 0x31, 0xF6, 0x71, 0xD8, 0x62, 0x35, + 0x00, 0x52, 0xB0, 0xD2, 0x7B, 0x7B, 0x62, 0xF5, 0x9B, 0x12, 0xAB, 0x9F, + 0x40, 0xAE, 0x7E, 0xEE, 0xB8, 0x9A, 0x87, 0x19, 0xAA, 0x79, 0x94, 0xA1, + 0xDA, 0x46, 0x09, 0x56, 0xDB, 0xB0, 0x52, 0x56, 0xE7, 0x78, 0x19, 0x10, + 0xC7, 0xB7, 0xB6, 0x62, 0x35, 0x93, 0x62, 0x9B, 0x94, 0xCA, 0x06, 0x8C, + 0x9F, 0x35, 0xFE, 0x80, 0xFC, 0x92, 0xD2, 0x5F, 0x4D, 0x0A, 0x10, 0x09, + 0x61, 0xFE, 0x0B, 0x12, 0xAE, 0xD5, 0xBF, 0xE8, 0x39, 0x72, 0x50, 0x39, + 0x1D, 0x90, 0x56, 0xCB, 0xE1, 0x33, 0x6C, 0x90, 0xD6, 0xB2, 0xA6, 0x99, + 0x1C, 0x40, 0x18, 0xD8, 0xAF, 0x5D, 0x17, 0xC2, 0xE5, 0x94, 0xB8, 0x0B, + 0x0B, 0x5E, 0x28, 0x44, 0xCF, 0xB0, 0x6D, 0xA8, 0x03, 0xFD, 0x4C, 0x4C, + 0x55, 0xA0, 0x50, 0xFD, 0xAE, 0x41, 0x21, 0x9A, 0xAA, 0x43, 0xB8, 0x95, + 0x4A, 0x90, 0xEA, 0x02, 0xBD, 0x82, 0x9F, 0xF0, 0x05, 0xBF, 0xEF, 0x91, + 0x14, 0x95, 0xDB, 0xED, 0xAC, 0xB0, 0xBF, 0x32, 0xA1, 0xD2, 0x73, 0x09, + 0xE6, 0x23, 0x9F, 0x9E, 0x7D, 0x53, 0x1C, 0xA2, 0x41, 0x88, 0x14, 0x0B, + 0xCF, 0xB4, 0x57, 0x50, 0x9E, 0x68, 0xBB, 0x29, 0x28, 0x4F, 0xC0, 0x95, + 0xE0, 0x56, 0x42, 0x01, 0x45, 0xFC, 0xEA, 0xB0, 0x59, 0x44, 0x40, 0xB5, + 0xF8, 0x01, 0xAA, 0x84, 0x69, 0x98, 0x83, 0x01, 0x0D, 0xE5, 0x92, 0xCB, + 0x25, 0x4B, 0x17, 0xE4, 0x43, 0x0B, 0xE4, 0xA4, 0xB6, 0x20, 0xEE, 0x0E, + 0xF3, 0xF2, 0x36, 0xB8, 0xC6, 0x38, 0xA9, 0x8D, 0x47, 0xA0, 0x1C, 0x08, + 0xA2, 0x59, 0xD6, 0x23, 0xEA, 0x34, 0x14, 0x87, 0x53, 0xEA, 0x35, 0xAC, + 0xD4, 0x4F, 0x15, 0x48, 0xFD, 0x34, 0xC4, 0xEE, 0x89, 0xBC, 0x79, 0x87, + 0x1C, 0xCF, 0x9B, 0x69, 0xE2, 0x70, 0xA2, 0x59, 0x77, 0xE2, 0xE6, 0xD1, + 0xE9, 0xE6, 0xEA, 0xD4, 0x24, 0x16, 0xFC, 0x0A, 0x3E, 0x8F, 0xC8, 0x41, + 0x8A, 0x07, 0x5A, 0x88, 0xEF, 0xD5, 0x69, 0x49, 0xDE, 0xE3, 0x7E, 0x7E, + 0x8F, 0x8C, 0x04, 0x5E, 0xC6, 0x32, 0x5A, 0xB3, 0x79, 0xC5, 0x75, 0xAB, + 0x08, 0x8A, 0xED, 0x36, 0x40, 0x71, 0xF2, 0x8A, 0xD6, 0x49, 0x18, 0x5B, + 0x74, 0x6B, 0xC8, 0x75, 0x6F, 0x8D, 0x0D, 0xC5, 0xCB, 0xFB, 0x44, 0x54, + 0x82, 0x82, 0x3C, 0x5C, 0x44, 0x28, 0x0A, 0xF5, 0x85, 0x8A, 0x5C, 0x4F, + 0x43, 0x6B, 0x92, 0x24, 0x71, 0x46, 0x90, 0xBC, 0x04, 0x0E, 0x4C, 0xD1, + 0x8B, 0x27, 0xB4, 0xD7, 0xA5, 0x7F, 0x7F, 0x56, 0xC1, 0x07, 0xE8, 0x62, + 0x38, 0x2D, 0xC7, 0x39, 0xB0, 0x8C, 0xF7, 0x29, 0x27, 0xF9, 0x4B, 0x36, + 0x4E, 0x06, 0xD7, 0x60, 0xB5, 0xE7, 0x94, 0xE2, 0x82, 0x36, 0x10, 0x90, + 0x18, 0xD8, 0x42, 0x95, 0x2F, 0xFF, 0x3E, 0x44, 0x00, 0x03, 0xCD, 0x72, + 0x51, 0x58, 0x33, 0xDA, 0x30, 0x16, 0x53, 0x30, 0x4A, 0xA7, 0x6C, 0x4A, + 0x65, 0x53, 0x45, 0x97, 0x29, 0x8B, 0x0E, 0xB3, 0xBB, 0x72, 0xD9, 0x9A, + 0x2E, 0xD4, 0xD2, 0x57, 0xB9, 0x1A, 0x01, 0x62, 0x01, 0xED, 0xAA, 0x7D, + 0xAC, 0x6E, 0x32, 0x4A, 0x9F, 0xD5, 0x87, 0x80, 0xDF, 0x23, 0x2B, 0xAA, + 0x96, 0x44, 0x64, 0x62, 0xF6, 0xC8, 0x84, 0xD0, 0xF3, 0xA1, 0xBB, 0xC3, + 0x43, 0x0A, 0x2C, 0x74, 0xCD, 0x0E, 0x08, 0x40, 0x8A, 0x85, 0x12, 0xB6, + 0xA5, 0x3F, 0x18, 0x21, 0x94, 0xB6, 0xE4, 0x19, 0x82, 0x67, 0x88, 0x9D, + 0x91, 0xDF, 0x65, 0x65, 0x5C, 0x60, 0x98, 0x9A, 0xEF, 0x85, 0xA9, 0x7F, + 0xA8, 0x32, 0x70, 0x9A, 0xEA, 0xCB, 0x89, 0x8D, 0xD0, 0x0E, 0xBA, 0x99, + 0xE5, 0xCE, 0x52, 0xAA, 0x91, 0x4E, 0x40, 0x62, 0x84, 0x28, 0xCE, 0x02, + 0x88, 0xC8, 0x49, 0x1C, 0x4A, 0x54, 0x1C, 0xA5, 0xB3, 0xB7, 0x1F, 0xF8, + 0xA0, 0xD1, 0x45, 0xE8, 0x10, 0xA8, 0x0E, 0xD1, 0xA0, 0xFA, 0x4D, 0x3F, + 0xF8, 0x80, 0xCA, 0x6A, 0x96, 0x1A, 0x79, 0x0F, 0x49, 0xDF, 0x52, 0x76, + 0xF4, 0xA5, 0x67, 0x28, 0xC0, 0x1C, 0xC4, 0x67, 0xEE, 0xC8, 0xD6, 0x21, + 0xA1, 0xC8, 0x30, 0x01, 0x81, 0xCC, 0x74, 0xAE, 0xFC, 0x6C, 0x9E, 0x9D, + 0x64, 0xB0, 0xCC, 0x48, 0x20, 0x12, 0xAC, 0x5D, 0x5E, 0x1B, 0x39, 0x79, + 0x57, 0x2E, 0x73, 0xE7, 0x7C, 0xBC, 0x98, 0x88, 0x08, 0xA8, 0x9E, 0x5D, + 0xA8, 0x82, 0x50, 0x15, 0xC5, 0xFE, 0xD9, 0x25, 0x02, 0xAA, 0x3C, 0x20, + 0x49, 0x80, 0x40, 0xE1, 0x01, 0x21, 0x63, 0x41, 0x1E, 0x9C, 0xA4, 0x6B, + 0x84, 0x09, 0x08, 0x86, 0xBF, 0x15, 0xB5, 0x14, 0x69, 0x95, 0xE5, 0x1A, + 0x58, 0xA1, 0x86, 0x6F, 0x87, 0xD9, 0x8E, 0xC0, 0x23, 0xB2, 0x73, 0xD3, + 0x9E, 0x15, 0x7B, 0xEE, 0xD8, 0xCD, 0xF0, 0x84, 0x42, 0x35, 0xF3, 0x09, + 0x95, 0xD1, 0x92, 0xFB, 0x84, 0xBA, 0x74, 0x7C, 0x78, 0xA1, 0x52, 0x93, + 0xE0, 0x22, 0x56, 0x8C, 0x06, 0xB1, 0x82, 0x8D, 0x5E, 0x19, 0x3D, 0x2E, + 0x76, 0x3F, 0x56, 0x1C, 0x8B, 0x3D, 0x84, 0x51, 0x6D, 0x80, 0x16, 0x03, + 0x43, 0x9B, 0x75, 0xE2, 0xC3, 0xB8, 0xE0, 0x6B, 0xDB, 0x5E, 0x54, 0x05, + 0xAA, 0xC4, 0x55, 0xA5, 0x4B, 0xC2, 0x84, 0x74, 0xCE, 0x10, 0xA6, 0x19, + 0x1B, 0xF6, 0x44, 0xB1, 0xE3, 0x3B, 0x22, 0xD4, 0x8E, 0x5B, 0xAB, 0x05, + 0x6E, 0x93, 0xBC, 0x1A, 0x8C, 0xCE, 0x2B, 0x63, 0xA4, 0x7C, 0x52, 0xB4, + 0xB2, 0x49, 0x11, 0x8E, 0xA3, 0xF7, 0xA4, 0x71, 0x46, 0x75, 0xD8, 0xAA, + 0x29, 0x10, 0x10, 0x48, 0x65, 0x87, 0xB8, 0xDD, 0xE4, 0x5B, 0x8D, 0x22, + 0x61, 0x07, 0x85, 0x6B, 0xA1, 0xF2, 0xBB, 0x09, 0xB7, 0xA5, 0x80, 0xDF, + 0x14, 0x0C, 0xB9, 0x88, 0x22, 0x44, 0xFB, 0xEB, 0x00, 0x3A, 0x8E, 0x26, + 0xD9, 0x61, 0xEF, 0x6D, 0x47, 0xFF, 0x34, 0x22, 0xE3, 0x74, 0xE1, 0xDA, + 0x20, 0xC1, 0xE7, 0xC3, 0x77, 0x94, 0x16, 0x81, 0xBF, 0x08, 0x28, 0x90, + 0xDE, 0xAC, 0x68, 0x41, 0x18, 0x0F, 0xDA, 0xD0, 0xF4, 0x73, 0x86, 0xA6, + 0x67, 0x95, 0xFC, 0x6E, 0x83, 0x17, 0x61, 0x01, 0x3E, 0x99, 0x51, 0x82, + 0xBC, 0xB4, 0x30, 0x74, 0xF1, 0xD5, 0x86, 0x41, 0xB6, 0x99, 0x57, 0x90, + 0x80, 0x31, 0x14, 0xED, 0x89, 0x63, 0x24, 0x0E, 0x47, 0x02, 0x28, 0xA4, + 0xF3, 0xA2, 0xEE, 0xCC, 0xAF, 0x73, 0x46, 0x70, 0xED, 0x4C, 0x46, 0x0C, + 0x18, 0x66, 0x65, 0x64, 0x63, 0xCC, 0x18, 0x64, 0xCC, 0xB1, 0x17, 0x82, + 0x30, 0x54, 0xA3, 0x2A, 0x51, 0xE1, 0x5E, 0x9B, 0x71, 0x74, 0xF2, 0x78, + 0x34, 0x9F, 0x96, 0xBE, 0xC1, 0xE4, 0xF4, 0xA9, 0x60, 0x7D, 0xEB, 0x6B, + 0xFA, 0x36, 0x27, 0x3C, 0x92, 0x0D, 0x00, 0x43, 0x1D, 0x9C, 0x90, 0x0E, + 0xE2, 0xCC, 0x99, 0x1B, 0xA0, 0x02, 0x0C, 0xE5, 0xD2, 0xDE, 0x1B, 0xC5, + 0x4B, 0xC3, 0x0B, 0xAD, 0x4C, 0x4C, 0x58, 0x8A, 0xC2, 0x7F, 0xD1, 0xD2, + 0xE0, 0x3A, 0xC5, 0x7F, 0x86, 0xA0, 0x81, 0x52, 0xAA, 0x02, 0x39, 0x03, + 0x30, 0xA4, 0xED, 0x37, 0x00, 0x19, 0x76, 0xE0, 0x81, 0x06, 0x90, 0x15, + 0x4C, 0x4C, 0x80, 0x90, 0x33, 0x46, 0x0D, 0x8D, 0x04, 0x7A, 0x68, 0xAB, + 0x92, 0xF6, 0xB7, 0x21, 0xB3, 0xF6, 0x34, 0x70, 0xA3, 0x0B, 0x9E, 0xB2, + 0xC0, 0x74, 0x20, 0x86, 0x09, 0xC6, 0x15, 0x14, 0x4D, 0xE7, 0xD0, 0x76, + 0xEE, 0xEF, 0xCB, 0x9F, 0xDF, 0x87, 0xA7, 0x6F, 0x48, 0xD2, 0x13, 0x13, + 0xC2, 0x81, 0x67, 0x85, 0x30, 0xFE, 0x05, 0x2A, 0xC0, 0xA4, 0x60, 0x75, + 0x9D, 0xA7, 0x14, 0xA4, 0x01, 0x33, 0xF6, 0x54, 0xB5, 0x81, 0xDD, 0x3F, + 0xEA, 0xA9, 0x92, 0x81, 0x05, 0xA7, 0x95, 0x8B, 0xB8, 0xEB, 0xE0, 0x0A, + 0x9E, 0xB6, 0x1D, 0x6E, 0x48, 0x31, 0x07, 0x72, 0xB8, 0x21, 0x9F, 0x80, + 0x7D, 0x70, 0xD2, 0x67, 0xD4, 0x8C, 0xAF, 0xE2, 0xCD, 0xD5, 0x2A, 0xD6, + 0x5C, 0x5D, 0x06, 0x05, 0xD4, 0x65, 0xB0, 0x98, 0xCB, 0xAB, 0x59, 0x17, + 0x57, 0xDF, 0x05, 0xBD, 0xB9, 0xAD, 0xB5, 0x29, 0x5F, 0xCB, 0x40, 0xF9, + 0xA7, 0x7C, 0xCF, 0x11, 0xCA, 0xBC, 0x02, 0x94, 0xE7, 0x11, 0xF0, 0x14, + 0x62, 0xF5, 0x33, 0x88, 0x11, 0xCA, 0xB9, 0xF8, 0x29, 0x37, 0x00, 0x01, + 0xA0, 0xBF, 0xC9, 0xC7, 0xE9, 0x26, 0x95, 0xD3, 0xC1, 0xA0, 0xD9, 0x18, + 0x8F, 0x5F, 0xF3, 0x0D, 0x7D, 0xCD, 0x32, 0x81, 0x77, 0x7E, 0x50, 0xBF, + 0x46, 0x3E, 0x9A, 0x9F, 0x3F, 0x5E, 0x94, 0x9A, 0xDF, 0xC4, 0xBF, 0xD6, + 0x19, 0xE3, 0xF8, 0xB8, 0x21, 0x17, 0x68, 0x81, 0xFE, 0x50, 0xFF, 0x32, + 0x69, 0xF5, 0x0F, 0x0A, 0xA5, 0xCF, 0x77, 0xA0, 0x23, 0x40, 0xAB, 0x70, + 0xF8, 0xB3, 0x9B, 0x0F, 0x76, 0x91, 0xF0, 0x83, 0x42, 0x78, 0x91, 0x0E, + 0x28, 0x56, 0x20, 0x5F, 0xCD, 0x9E, 0xD7, 0x6A, 0xE2, 0xAC, 0x37, 0x24, + 0x8E, 0xD7, 0x43, 0xE0, 0xDC, 0x78, 0xE3, 0xF5, 0x0D, 0x37, 0x79, 0x76, + 0xF0, 0x3A, 0x4F, 0x1B, 0xAF, 0x9F, 0x80, 0xAC, 0x04, 0xB3, 0x9C, 0x43, + 0x1F, 0xDD, 0xE7, 0x62, 0xB1, 0x02, 0xB8, 0x10, 0x6D, 0xD5, 0xAB, 0x81, + 0x06, 0x1A, 0x04, 0x85, 0x4E, 0x35, 0xBD, 0x3F, 0xB4, 0x6F, 0xEC, 0xF1, + 0x1A, 0xBF, 0x99, 0xB8, 0x21, 0x63, 0x0A, 0x27, 0xFC, 0xD9, 0x9B, 0xDB, + 0x44, 0x5C, 0x52, 0xCE, 0x9E, 0x16, 0xB9, 0x33, 0x7D, 0x7D, 0x67, 0xFD, + 0xB0, 0xA1, 0x74, 0x7B, 0xFA, 0x56, 0x80, 0xE8, 0x4F, 0xDD, 0x2B, 0xA2, + 0x6F, 0x2F, 0x8F, 0xE6, 0x34, 0xF5, 0x37, 0x04, 0x5E, 0x70, 0xC2, 0x54, + 0x94, 0x19, 0x58, 0x0C, 0x3A, 0xCA, 0xD1, 0xBC, 0x86, 0x26, 0x07, 0x86, + 0x10, 0x44, 0x27, 0xCB, 0x2B, 0x43, 0x99, 0xD7, 0x85, 0xED, 0x6E, 0xD4, + 0x79, 0xF2, 0xC7, 0x67, 0x85, 0xB6, 0xCE, 0x69, 0x4B, 0x98, 0x1A, 0x1E, + 0x80, 0xF1, 0x81, 0x08, 0x00, 0x84, 0xE3, 0xEC, 0x4A, 0x75, 0x25, 0x45, + 0x01, 0x2B, 0xD5, 0xCC, 0x25, 0x80, 0x52, 0x7B, 0xFE, 0x3E, 0x48, 0xE3, + 0x65, 0x65, 0x8F, 0x29, 0x37, 0x4D, 0xD0, 0x7F, 0xBF, 0x07, 0xA8, 0x95, + 0x14, 0xC8, 0xFC, 0x8A, 0x73, 0xE3, 0x67, 0x36, 0xB6, 0xA3, 0x18, 0x42, + 0xDA, 0xF2, 0x90, 0xA9, 0x21, 0xE5, 0x00, 0x67, 0x9C, 0xE5, 0x17, 0x0F, + 0x9C, 0xE5, 0x79, 0x82, 0x58, 0xF1, 0x7A, 0x58, 0x2E, 0xE7, 0x0E, 0x0F, + 0x1E, 0x96, 0xAB, 0xF9, 0xE1, 0x2C, 0x88, 0x58, 0xD0, 0x55, 0xC4, 0x80, + 0x2B, 0x05, 0x99, 0x73, 0xA3, 0x76, 0xBF, 0x26, 0x05, 0x00, 0x1B, 0xA5, + 0xB8, 0xFB, 0xAC, 0x0D, 0x36, 0x4A, 0x77, 0xD6, 0xEA, 0x28, 0xA0, 0xC7, + 0x89, 0x95, 0x04, 0x65, 0x80, 0x92, 0xC8, 0x74, 0x03, 0xB5, 0xAF, 0xE7, + 0x8C, 0x21, 0x6D, 0x72, 0x70, 0xAB, 0x35, 0x9D, 0x64, 0x5F, 0xF9, 0x22, + 0x01, 0x68, 0x42, 0xA6, 0x36, 0xFC, 0x59, 0xF3, 0x80, 0xCC, 0x04, 0x00, + 0x72, 0x74, 0x4D, 0x77, 0xA3, 0x6B, 0x2B, 0x66, 0xD8, 0x2D, 0x5D, 0xA7, + 0xA1, 0x76, 0x05, 0x68, 0x11, 0x04, 0x0B, 0x8B, 0x18, 0xBA, 0x62, 0x58, + 0xC2, 0x46, 0x29, 0x4A, 0x0F, 0x1C, 0xC8, 0xF1, 0x01, 0xB5, 0x58, 0x05, + 0x5E, 0xA7, 0x9E, 0xA1, 0x61, 0x25, 0xEB, 0xAA, 0x4C, 0x99, 0x97, 0xAB, + 0xE5, 0x6E, 0x56, 0xCB, 0x45, 0x78, 0x32, 0xEE, 0x3D, 0x99, 0xB6, 0x98, + 0xAC, 0x66, 0x2E, 0x59, 0x8D, 0xFC, 0xC9, 0xDB, 0x73, 0xE2, 0xE2, 0x1C, + 0xAA, 0xAB, 0xE7, 0x4C, 0x5D, 0x6D, 0xB4, 0x21, 0x6A, 0x3A, 0x34, 0x51, + 0xF3, 0xCD, 0xA4, 0xCA, 0x0C, 0xD3, 0x49, 0x84, 0xA6, 0x3E, 0xE4, 0x4C, + 0x3D, 0xC8, 0x0F, 0x88, 0xD1, 0x63, 0xCB, 0x29, 0x45, 0x00, 0x14, 0x6C, + 0x01, 0x59, 0x57, 0xC6, 0x87, 0x50, 0x02, 0xEB, 0xEA, 0x34, 0xEB, 0x6A, + 0x6C, 0xED, 0xF3, 0x37, 0x45, 0xD5, 0xE9, 0xE8, 0x88, 0x2A, 0x93, 0x8F, + 0x4A, 0x82, 0x33, 0x26, 0x6E, 0x33, 0x37, 0xA8, 0x71, 0x06, 0xAB, 0xB7, + 0x29, 0x8E, 0x3B, 0xA3, 0x24, 0x9A, 0x9F, 0x06, 0x3D, 0xA2, 0x05, 0xAF, + 0xBB, 0xD4, 0xE8, 0xC5, 0xEB, 0xF4, 0xB6, 0xA8, 0x4F, 0x52, 0xA1, 0x19, + 0x5B, 0xED, 0xBE, 0x65, 0x87, 0x02, 0x18, 0x23, 0x38, 0x3D, 0xBD, 0x86, + 0x45, 0x44, 0xC0, 0x47, 0x48, 0x8C, 0x37, 0x14, 0xE0, 0x50, 0x83, 0x30, + 0x08, 0xE8, 0xEF, 0x69, 0x5B, 0xC2, 0x73, 0xE8, 0x59, 0xB3, 0x0B, 0x33, + 0x5A, 0x47, 0x60, 0x34, 0xB0, 0xF2, 0xB0, 0x62, 0x2E, 0xC7, 0xA9, 0x8A, + 0xB9, 0xD3, 0x0D, 0x1B, 0xCC, 0xD5, 0x7C, 0x30, 0x54, 0x83, 0x6A, 0x6D, + 0xD5, 0x88, 0x0E, 0x1C, 0x60, 0x80, 0xD9, 0x90, 0x5C, 0x4F, 0xA2, 0xA3, + 0x12, 0xF4, 0xC4, 0xF2, 0xB4, 0xB0, 0xC1, 0x99, 0xA2, 0x8D, 0x8F, 0xA7, + 0xC0, 0x86, 0x46, 0x40, 0x54, 0xCC, 0xB5, 0xDC, 0x55, 0xF9, 0xA0, 0x32, + 0xBC, 0x71, 0x0E, 0xD3, 0xBD, 0xFB, 0x43, 0x44, 0x71, 0x09, 0xBF, 0xC2, + 0x21, 0x11, 0xA5, 0x12, 0xFC, 0x11, 0xB3, 0xD0, 0xD9, 0xB7, 0x97, 0xBF, + 0x37, 0xA9, 0xA6, 0xCE, 0xFB, 0xBF, 0x9C, 0x3D, 0x44, 0x2C, 0x81, 0x9E, + 0xD9, 0x40, 0x9F, 0x15, 0xAD, 0xCF, 0xEB, 0x03, 0x2A, 0x00, 0xA0, 0x3E, + 0xB0, 0x87, 0x01, 0x11, 0x66, 0x4E, 0x94, 0x42, 0x98, 0x1B, 0x3B, 0xBB, + 0xF3, 0x01, 0x0F, 0x4D, 0x6B, 0x03, 0x27, 0x62, 0x38, 0x69, 0x8F, 0x42, + 0x46, 0x75, 0x4A, 0x8A, 0x46, 0x8D, 0x14, 0xDD, 0x02, 0xBA, 0x85, 0xEA, + 0x2A, 0x79, 0xAC, 0x2B, 0x52, 0x95, 0x77, 0xC7, 0x8B, 0xB3, 0xBD, 0x47, + 0x08, 0xD2, 0x7B, 0x26, 0xA8, 0x00, 0x69, 0x2E, 0x6B, 0x48, 0x7F, 0x86, + 0xEC, 0xD9, 0x31, 0x23, 0x9E, 0x50, 0xF5, 0x42, 0x55, 0x44, 0x20, 0x47, + 0x0B, 0x54, 0x03, 0xA7, 0x08, 0xD1, 0x55, 0x8F, 0x8D, 0xC8, 0x28, 0xD5, + 0x17, 0xB3, 0x95, 0x79, 0x9D, 0xF0, 0xBF, 0x2A, 0x8E, 0xA2, 0x2C, 0x69, + 0xAB, 0x6C, 0x79, 0x9F, 0x85, 0x2A, 0xDE, 0x22, 0x5B, 0xF8, 0x72, 0x5A, + 0xFE, 0xA8, 0x28, 0x7E, 0x77, 0xAB, 0x8A, 0xD3, 0xB9, 0xFF, 0x44, 0x23, + 0x42, 0x58, 0x1A, 0xFA, 0x3F, 0x15, 0x2F, 0x82, 0x03, 0x16, 0x5E, 0x28, + 0xB2, 0x94, 0x61, 0x03, 0x16, 0xDE, 0xD6, 0xF2, 0x63, 0xD1, 0xB6, 0x56, + 0x9C, 0x1B, 0x84, 0x6D, 0x29, 0xC8, 0x82, 0xB3, 0xAD, 0x71, 0xA0, 0x4D, + 0x66, 0x6C, 0xDD, 0x24, 0x78, 0xCF, 0xD0, 0x2C, 0x0D, 0x6C, 0x5D, 0x86, + 0xC5, 0xE5, 0x76, 0x1B, 0x4F, 0x97, 0xD5, 0x71, 0xE1, 0xFB, 0x80, 0xCB, + 0xC0, 0x33, 0x17, 0xB3, 0x31, 0x5C, 0xE8, 0xB1, 0x28, 0xFE, 0xC9, 0x28, + 0x42, 0xC2, 0xA2, 0xC4, 0x72, 0x0A, 0xD0, 0x06, 0xB5, 0x53, 0x07, 0x09, + 0x68, 0x02, 0xF8, 0x58, 0x16, 0x5D, 0x03, 0xD0, 0x0F, 0xF2, 0xF1, 0x14, + 0x5C, 0x68, 0x0D, 0x02, 0xD5, 0x51, 0xE1, 0x10, 0x3D, 0x08, 0x0D, 0x84, + 0xF5, 0xEF, 0x01, 0x34, 0x37, 0xD6, 0x1F, 0xA1, 0x4D, 0x11, 0x8A, 0x2D, + 0x3F, 0x62, 0x48, 0x93, 0x16, 0x43, 0xD4, 0xD8, 0x1D, 0x0B, 0x0F, 0x1F, + 0x8F, 0xF5, 0xF0, 0x7A, 0xAC, 0xE9, 0xB5, 0x3A, 0xD3, 0xBC, 0x76, 0x36, + 0xE0, 0xE5, 0x16, 0x65, 0x65, 0x3B, 0x61, 0x68, 0x37, 0x1F, 0x44, 0x13, + 0x14, 0x05, 0x53, 0xE8, 0xE7, 0xE3, 0x75, 0xDF, 0x7A, 0x11, 0x67, 0x93, + 0x6C, 0x70, 0x62, 0x87, 0x98, 0xD6, 0x66, 0xF0, 0xE9, 0xB5, 0x71, 0xBA, + 0xE3, 0x74, 0x72, 0x07, 0x3A, 0x23, 0x9C, 0xA6, 0x2B, 0xC2, 0x69, 0xF8, + 0x74, 0x13, 0x09, 0xA7, 0x9D, 0xF0, 0xC0, 0x31, 0x02, 0xFD, 0x00, 0xFA, + 0x65, 0x4E, 0x3B, 0x5B, 0x54, 0x38, 0xB2, 0xF1, 0x15, 0xB7, 0x06, 0xC2, + 0x11, 0x23, 0x41, 0x39, 0x33, 0x66, 0xDB, 0x32, 0xC9, 0xCA, 0xC6, 0x50, + 0x2C, 0x76, 0xC9, 0x17, 0x0B, 0xE1, 0xD4, 0x55, 0xCB, 0x5A, 0x9D, 0x5A, + 0x44, 0xA7, 0xF9, 0x6B, 0xC6, 0x34, 0x5D, 0x2C, 0x58, 0x13, 0x55, 0xB1, + 0xDE, 0x3D, 0x0A, 0x1E, 0x51, 0xF1, 0xD3, 0x88, 0x8A, 0xDB, 0x0E, 0x62, + 0x16, 0xA0, 0xEB, 0xAD, 0x1C, 0xB6, 0xA2, 0x54, 0x6C, 0xCC, 0xA1, 0xAB, + 0xC8, 0x4E, 0x4D, 0xD7, 0x74, 0x69, 0x80, 0x84, 0x38, 0x18, 0xBE, 0x01, + 0x8B, 0x65, 0xD1, 0x58, 0x4E, 0x39, 0x14, 0x41, 0x25, 0x22, 0x12, 0x23, + 0x73, 0x71, 0xAB, 0x6F, 0xC2, 0x99, 0x0F, 0x93, 0x78, 0x85, 0xB4, 0x16, + 0x72, 0x84, 0x78, 0x8D, 0x31, 0x6F, 0xA8, 0x55, 0xC4, 0x23, 0x72, 0x6B, + 0xCE, 0x74, 0x00, 0x5C, 0x90, 0xA2, 0x4A, 0x6E, 0xF4, 0xB6, 0x28, 0xBA, + 0xEA, 0x4C, 0xE7, 0x8E, 0xC3, 0xB7, 0x35, 0xCD, 0xFA, 0xDD, 0x6D, 0x9D, + 0x3C, 0x97, 0x4B, 0x81, 0xBB, 0xED, 0xA4, 0xD8, 0xE2, 0x76, 0xC2, 0xCE, + 0x46, 0xEA, 0x50, 0x07, 0x25, 0x38, 0x77, 0x82, 0x4F, 0xFA, 0xB0, 0x71, + 0x8C, 0xE2, 0x84, 0x4F, 0xB6, 0xBD, 0xA3, 0xD7, 0xCD, 0x21, 0xD6, 0xC7, + 0x16, 0x34, 0xF8, 0x64, 0x7A, 0x29, 0xE7, 0x8E, 0xF7, 0x29, 0xD8, 0xEC, + 0x88, 0x9B, 0x0A, 0xF4, 0x8D, 0x22, 0x24, 0x57, 0x62, 0x49, 0xEA, 0xD9, + 0xE9, 0x40, 0x9E, 0x87, 0x86, 0xFC, 0x2D, 0xA5, 0xE3, 0xC3, 0xCC, 0x0E, + 0x34, 0x68, 0x59, 0x92, 0x52, 0x8E, 0x62, 0x83, 0x43, 0xDC, 0xCB, 0xDB, + 0xC2, 0x10, 0x54, 0x48, 0x2E, 0x09, 0x05, 0xA7, 0x08, 0x83, 0x69, 0xDE, + 0x54, 0x21, 0xD4, 0xF1, 0x08, 0x64, 0x91, 0x5D, 0x4B, 0x54, 0xE8, 0x8B, + 0xD4, 0xC5, 0x67, 0xE1, 0x40, 0x79, 0xFE, 0xF3, 0x83, 0x19, 0xAA, 0xF0, + 0x6C, 0x80, 0x97, 0x2E, 0x5D, 0x08, 0xF0, 0xC2, 0x77, 0x47, 0x6A, 0x3E, + 0x20, 0x94, 0x6D, 0xBA, 0x54, 0xDD, 0x66, 0x4B, 0x15, 0xD1, 0xEA, 0x64, + 0x86, 0x2A, 0xE2, 0x53, 0xB2, 0x10, 0x39, 0x51, 0x34, 0x07, 0x0C, 0x67, + 0xAA, 0x36, 0x06, 0xD7, 0xBE, 0xA9, 0x33, 0xBD, 0x38, 0x98, 0x3C, 0x3F, + 0x60, 0x12, 0xC6, 0x3A, 0x14, 0x4B, 0x4E, 0x4A, 0x6E, 0xF5, 0xB6, 0xE7, + 0x3A, 0x43, 0x8F, 0xBE, 0x81, 0xCF, 0x0E, 0x4D, 0x25, 0x87, 0x16, 0x14, + 0x04, 0x44, 0x95, 0x5C, 0x72, 0x9C, 0x0E, 0x0E, 0x36, 0x10, 0x09, 0x51, + 0x66, 0x92, 0x5E, 0xAA, 0x0C, 0x52, 0xBC, 0x85, 0xF0, 0xF5, 0x10, 0x06, + 0x3F, 0xD0, 0x29, 0x8C, 0x62, 0x14, 0x2A, 0x1A, 0xD5, 0xE7, 0x33, 0xCA, + 0x4F, 0xDE, 0xF3, 0xE7, 0x3F, 0x8B, 0xA6, 0x31, 0xC1, 0x67, 0x02, 0x59, + 0x92, 0xD0, 0xC5, 0xF7, 0x3B, 0xF4, 0x4E, 0xC5, 0x89, 0x20, 0x20, 0x88, + 0x62, 0xCA, 0x41, 0xAE, 0x64, 0x8E, 0xF5, 0xA1, 0x39, 0x47, 0x79, 0xFC, + 0x7A, 0x73, 0x09, 0xC7, 0x30, 0x4D, 0x3E, 0x50, 0x82, 0x4E, 0x97, 0x34, + 0x1A, 0xAC, 0xB7, 0x3E, 0x8C, 0xCC, 0x48, 0x5B, 0xEB, 0x67, 0x92, 0x99, + 0x00, 0x07, 0xD0, 0x9D, 0x80, 0x21, 0x1E, 0xEE, 0xED, 0x0D, 0x73, 0x7E, + 0xCA, 0xC9, 0xE2, 0x97, 0x5C, 0xCF, 0x17, 0x47, 0x8F, 0xEC, 0x27, 0x89, + 0xD0, 0x8A, 0x05, 0x4E, 0xAB, 0x18, 0xA3, 0x78, 0x87, 0x69, 0xA3, 0x80, + 0x26, 0xB5, 0xEB, 0x7C, 0x76, 0xAC, 0x6D, 0xDB, 0xDA, 0xD0, 0x2C, 0x55, + 0x4E, 0x2F, 0x60, 0x42, 0x16, 0x5A, 0x30, 0xF1, 0x38, 0xEF, 0xE9, 0xEF, + 0xA7, 0x81, 0x0B, 0x32, 0x54, 0xB8, 0x11, 0x86, 0xE0, 0x5A, 0xF2, 0xBA, + 0xBD, 0xB9, 0xB5, 0x2D, 0x30, 0x2E, 0xF8, 0x4F, 0x74, 0x12, 0xD1, 0x1C, + 0x5E, 0xB5, 0xB3, 0x6A, 0xE7, 0x78, 0x44, 0xDB, 0xEE, 0x33, 0xFF, 0xD0, + 0x50, 0xA7, 0x8C, 0x17, 0xBF, 0x00, 0xE5, 0x8D, 0x0A, 0x05, 0xCA, 0x14, + 0xA9, 0x9F, 0x52, 0x96, 0x22, 0xF5, 0x50, 0x9A, 0x5B, 0x50, 0x3B, 0x1E, + 0x5C, 0xB4, 0x92, 0x42, 0xA0, 0xDE, 0xA3, 0x00, 0xEA, 0xB6, 0x69, 0xE3, + 0x4B, 0xD6, 0xC8, 0xAF, 0x18, 0x98, 0x3A, 0xB9, 0x63, 0x0A, 0x0D, 0x31, + 0x49, 0xC8, 0x12, 0x14, 0x1E, 0x40, 0x8F, 0x29, 0x66, 0x0A, 0xC7, 0xBA, + 0xAD, 0x2A, 0x8A, 0x87, 0x54, 0x37, 0x43, 0xD3, 0x70, 0x4E, 0xD6, 0xD1, + 0x73, 0xB0, 0xEE, 0xD0, 0x87, 0x9C, 0xE3, 0x3E, 0xE2, 0x5C, 0x16, 0xEB, + 0x68, 0xF6, 0x8A, 0x89, 0xB4, 0x00, 0xAA, 0xF4, 0x95, 0xA0, 0x81, 0xC8, + 0xF2, 0x8A, 0xE0, 0xF7, 0xCB, 0x1A, 0x6F, 0x9F, 0xBA, 0x3D, 0x22, 0x7B, + 0xDF, 0x06, 0xE5, 0xED, 0x90, 0x04, 0x61, 0xB4, 0x3D, 0x11, 0xC2, 0x35, + 0xC4, 0x3A, 0x1D, 0xB1, 0xDC, 0x69, 0x0F, 0x5E, 0x9E, 0xE5, 0x78, 0x44, + 0x8C, 0x0D, 0xB1, 0x7C, 0xCC, 0x4E, 0x55, 0x59, 0x09, 0x63, 0xC0, 0x53, + 0x21, 0x88, 0x14, 0x41, 0xE4, 0xC5, 0x40, 0x38, 0x20, 0x71, 0x4D, 0xD1, + 0x95, 0x0C, 0xCD, 0x6A, 0xD3, 0x67, 0x89, 0x10, 0xD5, 0x08, 0xD2, 0x08, + 0xAE, 0xB5, 0x2E, 0x11, 0xBE, 0xE5, 0x4A, 0x11, 0x8A, 0x11, 0x25, 0xD0, + 0xF3, 0x00, 0xC6, 0x4F, 0x00, 0x8D, 0x31, 0x97, 0xC5, 0xE4, 0x42, 0xAE, + 0x0C, 0x24, 0x11, 0xB1, 0xC3, 0xC7, 0xB3, 0x73, 0x3B, 0xC6, 0x62, 0x65, + 0xCE, 0x0A, 0x90, 0x3E, 0x70, 0xA8, 0xA0, 0x41, 0xAA, 0x10, 0xA6, 0x98, + 0x01, 0x79, 0x76, 0x06, 0xA9, 0xE4, 0xCD, 0x04, 0xE5, 0x6F, 0x57, 0xD2, + 0xB4, 0xA7, 0xD1, 0x1A, 0x4F, 0x6C, 0x9E, 0xA2, 0xFC, 0x3A, 0xA8, 0xAB, + 0xD9, 0x24, 0xDB, 0x3C, 0x2A, 0xED, 0xCC, 0x9C, 0x66, 0x02, 0x94, 0x11, + 0xAC, 0x53, 0x91, 0xE7, 0xF5, 0xD0, 0x74, 0xDE, 0x0E, 0x68, 0x43, 0x26, + 0xFE, 0x33, 0x9A, 0x28, 0x20, 0x38, 0x40, 0xD0, 0xCC, 0x1D, 0x03, 0x40, + 0x45, 0x17, 0xA0, 0x98, 0x17, 0xCA, 0x3A, 0x6C, 0x55, 0x27, 0x44, 0xE1, + 0xCD, 0xFB, 0x06, 0x2F, 0x2A, 0x98, 0x70, 0xD9, 0x3A, 0x42, 0x46, 0x93, + 0xA9, 0xF5, 0x95, 0xBC, 0xFA, 0xDA, 0xCE, 0xEA, 0x6B, 0xD4, 0x8E, 0x4D, + 0xB1, 0x84, 0xFC, 0x2E, 0x38, 0xA2, 0x79, 0xEA, 0xAB, 0xCD, 0xF0, 0xF5, + 0x44, 0x03, 0xEC, 0x9F, 0xBB, 0xD4, 0x85, 0x3F, 0x03, 0x55, 0xA1, 0x10, + 0x0D, 0x0C, 0x91, 0x89, 0x93, 0x48, 0x26, 0x88, 0xD8, 0x21, 0x1F, 0xA8, + 0xBD, 0xAF, 0xE1, 0x67, 0xCE, 0x52, 0xC8, 0x4E, 0x5A, 0x95, 0x14, 0x13, + 0x48, 0x05, 0x79, 0x10, 0x79, 0xCD, 0x63, 0xC8, 0xEB, 0x3C, 0x3F, 0x5E, + 0xE7, 0x51, 0xA2, 0x8B, 0x4D, 0x97, 0x25, 0xA8, 0x80, 0x44, 0x56, 0x0D, + 0x86, 0x33, 0xDD, 0x81, 0x01, 0xE6, 0x50, 0x45, 0x19, 0xC6, 0x09, 0xD3, + 0x15, 0xB5, 0x5B, 0x23, 0x82, 0x5B, 0x9F, 0x38, 0x14, 0x8B, 0xFE, 0x58, + 0xEC, 0xAC, 0xD6, 0xEC, 0x06, 0x7E, 0x07, 0x02, 0x70, 0xF6, 0x8A, 0x1A, + 0x19, 0x9E, 0xBB, 0x46, 0xB0, 0xB3, 0xD2, 0x5A, 0xEB, 0xAA, 0x06, 0xB2, + 0x69, 0xB5, 0x1E, 0x50, 0x0C, 0x07, 0xA3, 0x1B, 0x38, 0x18, 0x01, 0x9F, + 0xF4, 0x31, 0xC2, 0xD0, 0xF7, 0x07, 0x48, 0x07, 0x07, 0x9B, 0xCC, 0xE9, + 0xEC, 0xBC, 0xE4, 0x67, 0x71, 0x90, 0xA6, 0x82, 0xB3, 0xD7, 0x8C, 0xD2, + 0x52, 0xA5, 0xEB, 0xEC, 0x83, 0x6C, 0x77, 0x2B, 0xE8, 0x65, 0xD6, 0xDF, + 0x4D, 0x12, 0x9D, 0x9D, 0x2C, 0x9E, 0x2D, 0xE6, 0xEC, 0x5D, 0x38, 0xBB, + 0x4D, 0x85, 0x5C, 0x50, 0x20, 0x74, 0xF9, 0x68, 0xE0, 0x04, 0x5E, 0x41, + 0xDD, 0x8A, 0xB3, 0xEB, 0x74, 0x27, 0x22, 0x02, 0x83, 0xA8, 0xAA, 0xFE, + 0x56, 0xF0, 0x98, 0xF0, 0x82, 0x4C, 0x9F, 0x11, 0xCF, 0xE3, 0x11, 0x57, + 0x6D, 0x3C, 0x9E, 0x16, 0x68, 0xDE, 0x33, 0x28, 0x3E, 0x53, 0xF5, 0xBB, + 0xC2, 0x55, 0x41, 0x5B, 0x70, 0x88, 0xBE, 0x3E, 0x7D, 0xCE, 0xE5, 0x7E, + 0xF4, 0xB2, 0x9A, 0xD6, 0xB3, 0x01, 0x89, 0x34, 0xBA, 0x5B, 0x35, 0x1D, + 0x1F, 0xAA, 0xAF, 0x2E, 0xD1, 0x48, 0x7E, 0x23, 0x69, 0xE2, 0x44, 0xEF, + 0x08, 0x3D, 0x48, 0xF9, 0x21, 0xC6, 0xF0, 0xC3, 0xB0, 0xB2, 0x1E, 0x9D, + 0x28, 0x9E, 0xB1, 0x78, 0x91, 0x6A, 0x7C, 0xBD, 0x20, 0x5F, 0x69, 0x9B, + 0x20, 0x61, 0xDF, 0xDB, 0x25, 0xDA, 0x22, 0x99, 0x16, 0x86, 0xA5, 0x10, + 0x9C, 0x20, 0x46, 0x84, 0xE1, 0x0B, 0xD3, 0x27, 0x0C, 0x10, 0xDB, 0x82, + 0xEF, 0x2B, 0x05, 0x5F, 0x32, 0x8D, 0xEC, 0xD9, 0x18, 0x43, 0x30, 0x21, + 0x8F, 0x9E, 0xB9, 0x35, 0x04, 0xD3, 0x0C, 0x3A, 0x16, 0x28, 0x86, 0x91, + 0x41, 0x07, 0x0B, 0x22, 0x34, 0xE0, 0x80, 0x83, 0x41, 0x42, 0xCA, 0x04, + 0x41, 0xDB, 0x04, 0x65, 0x66, 0x10, 0x31, 0x66, 0x4C, 0xA2, 0x63, 0xE2, + 0x80, 0x08, 0xA6, 0xE0, 0x6C, 0xC2, 0xB2, 0x9F, 0xA1, 0x48, 0x25, 0xA0, + 0xAE, 0xE9, 0x63, 0x01, 0xA1, 0x0C, 0xBA, 0x2E, 0xF4, 0xE9, 0x62, 0xC5, + 0x04, 0xAE, 0x32, 0xC7, 0xC8, 0xA2, 0xFC, 0xAC, 0xDE, 0xE7, 0xCF, 0xA4, + 0xA2, 0x46, 0xCE, 0x95, 0x95, 0x13, 0x46, 0xD3, 0x43, 0xCC, 0x9A, 0xF0, + 0x54, 0x54, 0x8D, 0xE5, 0xAA, 0xEA, 0xF6, 0x7E, 0xC6, 0x4F, 0x1D, 0x00, + 0x66, 0x7F, 0x45, 0x47, 0x57, 0x29, 0xFE, 0x0E, 0xAB, 0xA0, 0xAB, 0x5B, + 0xF9, 0x39, 0x75, 0xA6, 0x6B, 0x12, 0x7F, 0x74, 0x47, 0x36, 0x5A, 0xEB, + 0x6D, 0x41, 0x0F, 0x93, 0x26, 0xA0, 0xFC, 0x9B, 0x90, 0xA0, 0xC4, 0x02, + 0xF3, 0x1F, 0x20, 0xFA, 0x60, 0xED, 0x34, 0x52, 0x7F, 0x02, 0x6B, 0x21, + 0x84, 0x79, 0x31, 0xCC, 0xC1, 0x08, 0xB9, 0x04, 0x6B, 0x93, 0xE3, 0x5B, + 0x50, 0x62, 0x74, 0x0B, 0x12, 0xAC, 0x67, 0xE4, 0xE8, 0xA7, 0xF3, 0xA8, + 0x2E, 0x68, 0x74, 0x5D, 0x5B, 0x34, 0x71, 0x8C, 0x8C, 0xD2, 0x63, 0x61, + 0xC2, 0x9E, 0x3C, 0x39, 0xAE, 0x44, 0x6B, 0xA0, 0x0D, 0xAD, 0xCE, 0x2A, + 0x4A, 0x3B, 0xA7, 0x28, 0xE9, 0xFD, 0x2C, 0x6D, 0x63, 0xB8, 0xB4, 0xD3, + 0xFA, 0xDD, 0xCA, 0x25, 0x6E, 0x46, 0xEA, 0x36, 0xFA, 0xCF, 0x80, 0xFA, + 0x1B, 0x45, 0x61, 0xE9, 0x93, 0x07, 0xE2, 0x54, 0x4C, 0x80, 0x05, 0xC3, + 0x88, 0xD4, 0xB2, 0x58, 0x36, 0x83, 0x8C, 0x06, 0xA3, 0x1A, 0x8C, 0xA2, + 0x0E, 0x50, 0xB3, 0xB3, 0x32, 0xD3, 0x29, 0x06, 0x50, 0x4B, 0x9F, 0x8F, + 0x30, 0xBA, 0x2E, 0x29, 0x1B, 0xB8, 0x78, 0xBD, 0x49, 0x0F, 0x8C, 0x10, + 0xE3, 0xE8, 0xDA, 0xB6, 0x35, 0x92, 0x0A, 0xE1, 0x50, 0x35, 0xF5, 0x93, + 0x76, 0x2E, 0xB3, 0xF5, 0x62, 0x31, 0xA7, 0x3D, 0xF1, 0xCC, 0x30, 0x3B, + 0xFE, 0x4A, 0x70, 0x86, 0x99, 0xE3, 0xE0, 0x90, 0xA1, 0xCC, 0x71, 0x8E, + 0x1E, 0x0C, 0x95, 0x99, 0xE7, 0x0F, 0x69, 0x6B, 0x1C, 0x19, 0x3F, 0xF3, + 0xF9, 0x86, 0x83, 0xA5, 0x25, 0xBA, 0x03, 0x07, 0x4B, 0x7E, 0x5E, 0x01, + 0xD8, 0xA3, 0x00, 0x8B, 0x38, 0xAA, 0x8E, 0x17, 0x6A, 0xFE, 0xDE, 0x88, + 0x3D, 0xC5, 0x94, 0xCA, 0x67, 0x93, 0xBE, 0xAE, 0x94, 0x26, 0x8C, 0x16, + 0xAA, 0x20, 0x39, 0xD3, 0xAF, 0x67, 0x48, 0x72, 0x84, 0x12, 0xAC, 0x94, + 0xB0, 0x1D, 0x2B, 0x32, 0x10, 0x79, 0x76, 0xD0, 0x9E, 0x64, 0x01, 0x72, + 0x04, 0x21, 0x5B, 0x44, 0x0A, 0x90, 0xAB, 0x31, 0x55, 0x60, 0x09, 0xE3, + 0xB3, 0x2B, 0xBB, 0x7A, 0x6F, 0xAC, 0x87, 0xD8, 0xD5, 0x3D, 0xBE, 0xAE, + 0xCE, 0xA2, 0xC4, 0x83, 0xCA, 0x50, 0x8E, 0xC6, 0x50, 0xD2, 0xF1, 0x41, + 0x39, 0x9D, 0x39, 0x2D, 0x94, 0xBB, 0x81, 0x29, 0x4F, 0xCE, 0x51, 0x9E, + 0xDC, 0x53, 0xCE, 0x94, 0x5B, 0xD1, 0x08, 0xA1, 0x46, 0xE2, 0x95, 0x0C, + 0x5F, 0xF8, 0x5D, 0xA2, 0x3E, 0xBB, 0x82, 0x7A, 0x79, 0x64, 0x13, 0x51, + 0x32, 0xFC, 0x71, 0x15, 0x95, 0x99, 0x30, 0x81, 0xD4, 0x3C, 0xC7, 0x0B, + 0xB0, 0xBB, 0xA7, 0x25, 0x02, 0xE4, 0xCF, 0x28, 0x0C, 0x70, 0xBC, 0x45, + 0xEE, 0xD3, 0xB0, 0x42, 0x09, 0x37, 0xF6, 0x19, 0xBD, 0xEB, 0xA9, 0x0A, + 0x0C, 0xAA, 0x4D, 0x84, 0xFE, 0xB4, 0xE8, 0xD0, 0xB4, 0xD1, 0x4D, 0x53, + 0xD0, 0x09, 0x74, 0x0A, 0x9D, 0x75, 0x47, 0x48, 0xE4, 0x8D, 0xF1, 0x26, + 0x3C, 0x47, 0xD6, 0x77, 0x24, 0x95, 0xA2, 0xF7, 0x0A, 0x50, 0x2E, 0x91, + 0x39, 0x86, 0xE4, 0x60, 0x3E, 0x04, 0x87, 0xCA, 0x05, 0x4B, 0x07, 0xCC, + 0xEF, 0x0D, 0xCB, 0xE5, 0xD8, 0xB0, 0xFC, 0xF2, 0xB0, 0xFC, 0xBE, 0xE1, + 0x53, 0x73, 0xFA, 0xC4, 0xD2, 0x66, 0xAD, 0xD3, 0xE1, 0x14, 0xC4, 0x01, + 0x03, 0xFA, 0x08, 0x1D, 0xB0, 0xB5, 0x6D, 0xEF, 0x40, 0x94, 0x38, 0x14, + 0x32, 0x43, 0xF9, 0x0C, 0x47, 0x5F, 0x02, 0xE1, 0x0D, 0xA5, 0x3D, 0xAE, + 0xA0, 0x6A, 0x16, 0x10, 0xEB, 0x40, 0xEE, 0x86, 0x9C, 0xB1, 0x4C, 0xD6, + 0xC6, 0x30, 0x59, 0xC7, 0xA9, 0x29, 0x35, 0xB7, 0xA9, 0xD1, 0xF7, 0x9F, + 0xD6, 0x6A, 0x40, 0x5C, 0xA2, 0x66, 0x6E, 0x45, 0xE9, 0x5D, 0x9D, 0xEC, + 0x4B, 0x72, 0x62, 0x5E, 0xF0, 0x04, 0x04, 0xAA, 0xE0, 0x07, 0xD0, 0x6D, + 0x92, 0x46, 0xC6, 0x59, 0x58, 0x1A, 0xA5, 0xF7, 0xAB, 0xAE, 0x2D, 0x10, + 0x8E, 0xBD, 0x2D, 0xD2, 0x2A, 0x1A, 0x9C, 0x9E, 0xF6, 0xAE, 0x2A, 0xE9, + 0xD2, 0xBC, 0x87, 0x49, 0xF2, 0x52, 0xA8, 0x78, 0x40, 0x55, 0xE3, 0xC8, + 0xAF, 0x88, 0x3A, 0x7B, 0x71, 0xB7, 0xEB, 0x0E, 0x71, 0xB7, 0x4C, 0xFF, + 0x1C, 0x13, 0x77, 0x22, 0xB5, 0xC1, 0x8E, 0x1D, 0x3F, 0x3A, 0x74, 0x90, + 0xF0, 0x9A, 0xA1, 0x7C, 0x83, 0x04, 0x14, 0x6A, 0x44, 0x7B, 0xA6, 0x08, + 0x2C, 0x35, 0xA7, 0xA6, 0x5C, 0xD4, 0x7B, 0x71, 0x51, 0x97, 0xD5, 0x41, + 0x5E, 0x06, 0x08, 0xB7, 0xB2, 0xA6, 0xDD, 0x96, 0xF7, 0x45, 0xD3, 0x97, + 0xC5, 0x60, 0x52, 0x33, 0xBF, 0xA4, 0xAE, 0x12, 0x1A, 0x73, 0xF4, 0xDD, + 0x3E, 0x8B, 0x94, 0x84, 0x2F, 0xAD, 0x07, 0xA8, 0xA0, 0x45, 0x10, 0x40, + 0x0A, 0xC9, 0x31, 0xD0, 0x92, 0xBC, 0x51, 0xD4, 0xFB, 0x63, 0xA6, 0x8A, + 0x2A, 0x5C, 0x88, 0xD6, 0x5C, 0x67, 0x32, 0x35, 0x00, 0xF3, 0x85, 0x60, + 0x99, 0xF8, 0x62, 0xD2, 0x00, 0xC7, 0x1B, 0x17, 0x00, 0x38, 0x4E, 0x08, + 0x37, 0x32, 0x40, 0x67, 0x52, 0xD5, 0x78, 0x49, 0x58, 0x50, 0x8A, 0x24, + 0x03, 0x48, 0x42, 0xF7, 0xA5, 0xEA, 0x24, 0x0B, 0x55, 0x01, 0x99, 0x30, + 0xA8, 0x84, 0x8A, 0x51, 0xEA, 0xF5, 0x2C, 0x50, 0x5A, 0x54, 0x44, 0x00, + 0x00, 0x14, 0x09, 0x63, 0xF6, 0x61, 0x18, 0x4D, 0x41, 0x0C, 0x83, 0x81, + 0x34, 0x4C, 0x24, 0xDF, 0x01, 0x64, 0x45, 0x0B, 0x02, 0xC2, 0x86, 0x02, + 0x83, 0x11, 0x60, 0x34, 0x40, 0x1C, 0x14, 0x06, 0x08, 0x60, 0x32, 0x10, + 0xC9, 0x52, 0x01, 0x03, 0x31, 0x2C, 0x60, 0x00, 0x50, 0x08, 0x38, 0xC3, + 0x98, 0xAA, 0xA2, 0x01, 0xB3, 0x94, 0x5C, 0x47, 0x1B, 0x3D, 0x1F, 0x18, + 0x64, 0x4C, 0x44, 0x22, 0x31, 0x32, 0x44, 0x61, 0x54, 0x1F, 0xC8, 0x56, + 0x86, 0x98, 0xBC, 0xC5, 0x53, 0x92, 0xF7, 0x36, 0x10, 0xE0, 0x2E, 0x91, + 0x48, 0x77, 0xE3, 0x0C, 0x7F, 0x8A, 0x7E, 0xE3, 0xE7, 0x69, 0x94, 0xAC, + 0x23, 0xB0, 0x3C, 0x45, 0x7A, 0x5A, 0x22, 0xC3, 0xA3, 0x5B, 0xC3, 0x93, + 0x3E, 0xEA, 0x3B, 0xEC, 0xB8, 0xD3, 0xEF, 0xD5, 0x8D, 0x19, 0xE6, 0x2E, + 0x52, 0x84, 0x2F, 0x73, 0x03, 0x39, 0x52, 0xFB, 0x0C, 0x20, 0x60, 0x85, + 0x54, 0xAF, 0x16, 0x46, 0xF0, 0x8F, 0xEC, 0xFD, 0x58, 0x18, 0xEA, 0x14, + 0x1D, 0x3E, 0xD2, 0x79, 0x61, 0xC5, 0x2E, 0x5B, 0x8A, 0x2A, 0xF6, 0x53, + 0xDD, 0x33, 0x6E, 0xF8, 0x87, 0x00, 0x09, 0x68, 0x4C, 0x19, 0x22, 0xC6, + 0x79, 0x8A, 0x2B, 0x8F, 0xEB, 0x45, 0x22, 0xC0, 0x01, 0x5F, 0x42, 0x5A, + 0x76, 0x24, 0xF8, 0x24, 0x95, 0x39, 0x81, 0xF0, 0x30, 0x1E, 0xD8, 0x48, + 0x6D, 0xE9, 0x0B, 0x15, 0x86, 0xAF, 0x6A, 0x5B, 0x44, 0x5E, 0xFE, 0x6F, + 0x03, 0x38, 0xB3, 0x0D, 0x56, 0xE3, 0x9E, 0xBB, 0xED, 0x0A, 0x8F, 0x3B, + 0xB1, 0x32, 0x4C, 0xAE, 0xC7, 0x2E, 0xA9, 0xE8, 0xD6, 0xC4, 0x61, 0x4D, + 0xD0, 0x0D, 0xA9, 0x96, 0xAF, 0x3F, 0xF3, 0x9C, 0xA9, 0xBD, 0x03, 0x72, + 0x38, 0x1E, 0x6B, 0x99, 0x41, 0xB0, 0xCB, 0x84, 0xD9, 0xBC, 0x43, 0x6E, + 0xBB, 0x34, 0xA4, 0xC2, 0x92, 0xEF, 0xBD, 0xEA, 0x9C, 0x52, 0x88, 0x3B, + 0x18, 0x25, 0xFF, 0x9C, 0x55, 0xAC, 0x9D, 0x36, 0x5B, 0xCD, 0x18, 0xE6, + 0x96, 0x52, 0x93, 0x87, 0xEA, 0x6D, 0xFB, 0xAE, 0x9F, 0xB5, 0xB8, 0xDE, + 0xF8, 0x50, 0x02, 0x44, 0xF0, 0x1F, 0x02, 0xC0, 0x09, 0xF5, 0x98, 0xCE, + 0xC7, 0x11, 0xC8, 0xD5, 0x8F, 0xEE, 0xA8, 0x50, 0x42, 0xAA, 0x60, 0x9D, + 0x70, 0x56, 0x0A, 0xF6, 0x54, 0x45, 0xCB, 0xF9, 0x71, 0x1D, 0xCA, 0xE7, + 0x80, 0xC7, 0xB0, 0xAE, 0x33, 0x3E, 0x11, 0x00, 0x5D, 0x6F, 0xA7, 0x43, + 0xDC, 0x7E, 0x33, 0xAD, 0xA4, 0xE2, 0x9B, 0xC0, 0x09, 0xD5, 0x6B, 0xA4, + 0x49, 0x1A, 0xD4, 0xF7, 0xFC, 0x3E, 0x00, 0x70, 0xD8, 0x3E, 0xFE, 0x6A, + 0x60, 0x5C, 0x58, 0xC3, 0xE9, 0xC6, 0x4E, 0x16, 0x6F, 0xB2, 0xC6, 0x79, + 0x4D, 0x14, 0x37, 0x79, 0x28, 0xD5, 0xE5, 0xE7, 0xF6, 0x66, 0xE5, 0x22, + 0x1D, 0x7D, 0xC1, 0xFD, 0x1B, 0x32, 0xB9, 0x45, 0x01, 0x00, 0x3D, 0x26, + 0x25, 0x35, 0x00, 0x33, 0xA0, 0x83, 0xCF, 0xCC, 0xCD, 0xA7, 0x8E, 0x48, + 0xC5, 0xFA, 0x67, 0x2D, 0x87, 0xB9, 0xA1, 0x0E, 0x01, 0xA4, 0x33, 0x8A, + 0x0D, 0x98, 0xDC, 0xA1, 0x99, 0xCA, 0x5C, 0x75, 0xEE, 0x3B, 0xB3, 0x9A, + 0x4E, 0xDB, 0x66, 0x48, 0xA2, 0x3B, 0x0E, 0x6C, 0x87, 0xF9, 0xA0, 0x64, + 0x86, 0xA0, 0x99, 0x50, 0xAC, 0xB3, 0xF3, 0xC9, 0xDE, 0x76, 0x59, 0xC6, + 0x3B, 0xB1, 0x78, 0x47, 0x9F, 0xFA, 0xCF, 0x86, 0x2B, 0xBB, 0x3C, 0x23, + 0x60, 0x5A, 0x7E, 0xD3, 0x7A, 0xB3, 0xE3, 0xEE, 0xE7, 0xEE, 0x2F, 0xD1, + 0x44, 0x6C, 0xD0, 0x1A, 0x61, 0x12, 0x17, 0x18, 0x18, 0x36, 0x5F, 0x98, + 0x40, 0x86, 0x59, 0xF3, 0x31, 0x6D, 0xB3, 0xCD, 0xDB, 0x16, 0x2C, 0x3E, + 0x5F, 0x39, 0x59, 0xC0, 0x51, 0xE5, 0x19, 0x41, 0xAD, 0x41, 0x83, 0x54, + 0x5F, 0xB6, 0x9F, 0xB4, 0xA4, 0x74, 0x79, 0xD6, 0xF8, 0x4B, 0x5C, 0x7F, + 0xFF, 0x4F, 0x32, 0x7E, 0xF1, 0xB8, 0xD1, 0x2F, 0x1A, 0x59, 0x7C, 0x2C, + 0x89, 0x39, 0xDF, 0xD6, 0x1A, 0x45, 0xDB, 0x16, 0x05, 0x24, 0x24, 0x3F, + 0x27, 0xC9, 0xEE, 0x2D, 0xCE, 0x82, 0x09, 0xD6, 0x4B, 0x0C, 0xC7, 0x4D, + 0xBF, 0x17, 0x9C, 0xFD, 0xFE, 0x88, 0x07, 0x3C, 0x4F, 0xE9, 0x3C, 0x42, + 0x64, 0xF5, 0x70, 0xD4, 0x00, 0x08, 0x51, 0x4D, 0x22, 0x4B, 0x90, 0xEA, + 0x93, 0x17, 0x87, 0x6B, 0x32, 0x93, 0x40, 0xC5, 0x8F, 0xFB, 0x16, 0x12, + 0xC7, 0x7F, 0x70, 0xD0, 0x92, 0xC7, 0xAE, 0x18, 0x10, 0xEB, 0xD0, 0x1C, + 0xAB, 0xD2, 0x28, 0x27, 0xFE, 0x65, 0x41, 0x8F, 0x8A, 0x93, 0x79, 0xC3, + 0xE4, 0xC3, 0xD8, 0xAA, 0x2F, 0x7C, 0x0B, 0x2B, 0x04, 0xFA, 0xC5, 0x3F, + 0x42, 0x47, 0x15, 0x96, 0x1C, 0x14, 0x57, 0x39, 0x30, 0x14, 0xD3, 0xD0, + 0x9C, 0x95, 0x91, 0x85, 0xE5, 0xEF, 0x98, 0xBC, 0xDF, 0xAC, 0x8A, 0x62, + 0x81, 0x50, 0x68, 0x50, 0xA2, 0x48, 0x41, 0xE6, 0x35, 0xEB, 0x7F, 0x31, + 0xDB, 0xD5, 0x16, 0xE1, 0x2E, 0x2F, 0x36, 0x1C, 0x7A, 0xF3, 0xFF, 0x9A, + 0x5F, 0x10, 0xA4, 0x7A, 0x8F, 0xF0, 0x4B, 0xCE, 0xFF, 0xDD, 0x67, 0x7D, + 0x1D, 0x6F, 0x1A, 0x5B, 0x06, 0x86, 0xC6, 0x3C, 0x47, 0x3B, 0xC3, 0x03, + 0x70, 0xBA, 0x3A, 0xBE, 0xF0, 0x4C, 0xA5, 0x32, 0xE0, 0x28, 0xB9, 0x3E, + 0x5B, 0xBD, 0xE1, 0x0D, 0xD7, 0xB3, 0xE7, 0xC8, 0xEB, 0x18, 0x59, 0x53, + 0xC7, 0x26, 0xEB, 0xEC, 0x23, 0xBE, 0x07, 0x29, 0x04, 0x88, 0x77, 0x27, + 0xBF, 0x62, 0x25, 0x24, 0xB4, 0x1B, 0x6E, 0x4B, 0xEF, 0x6E, 0xE5, 0x32, + 0xE9, 0x29, 0xEA, 0x88, 0xCF, 0x2D, 0x00, 0x8D, 0xCB, 0x6D, 0x34, 0x6E, + 0xE9, 0x28, 0x33, 0x68, 0xB1, 0x5B, 0x11, 0xDE, 0x60, 0x02, 0x45, 0x05, + 0x2B, 0x89, 0xE8, 0x9A, 0x01, 0x62, 0x17, 0x44, 0x1D, 0x1C, 0x34, 0xA4, + 0xD9, 0x92, 0x90, 0xE7, 0xAF, 0x3A, 0xFC, 0xA0, 0x57, 0x32, 0x4E, 0x2F, + 0x7A, 0x2C, 0xF1, 0xF2, 0x93, 0x15, 0xD8, 0xCB, 0x8B, 0x7B, 0x1A, 0x57, + 0x48, 0x58, 0xBA, 0x19, 0x34, 0x44, 0x9D, 0xD1, 0x34, 0x09, 0x36, 0xB3, + 0xFC, 0x76, 0x43, 0x57, 0x31, 0x19, 0x5E, 0xFA, 0xD6, 0x48, 0x31, 0xD3, + 0xCA, 0x95, 0x77, 0x8B, 0x99, 0xD4, 0x68, 0xB9, 0xBE, 0x4C, 0x5E, 0x8E, + 0x9E, 0xD1, 0x0A, 0xE3, 0x2F, 0xA9, 0x8E, 0xBE, 0x77, 0xC9, 0x5C, 0xB9, + 0xD9, 0xF5, 0xA1, 0x86, 0xBF, 0x75, 0x96, 0xF3, 0x8D, 0x26, 0x5D, 0x4D, + 0x7B, 0x96, 0x9D, 0x1F, 0x4D, 0x11, 0x3A, 0xC9, 0xF7, 0xA6, 0xBD, 0xE2, + 0xBC, 0xD4, 0x5B, 0xDB, 0x7D, 0xF3, 0xAA, 0xE8, 0x3B, 0xC9, 0xD9, 0x20, + 0x4A, 0x2A, 0x09, 0x1B, 0x36, 0x9B, 0xA0, 0x0C, 0x0A, 0x33, 0x4C, 0x82, + 0xB7, 0x6B, 0xD5, 0xC7, 0x30, 0x61, 0x12, 0xC9, 0xA7, 0x7D, 0x81, 0x95, + 0x87, 0x63, 0x3F, 0xCE, 0x7D, 0x9B, 0xD0, 0x4F, 0xE3, 0x42, 0x39, 0xFD, + 0xE4, 0x8D, 0x3C, 0x12, 0x5F, 0x6E, 0x24, 0xD3, 0xE6, 0x97, 0xA3, 0x68, + 0x4E, 0xFB, 0xDA, 0x4A, 0x56, 0x31, 0xB7, 0x88, 0xF1, 0x20, 0x51, 0x97, + 0x3A, 0x51, 0xB5, 0x3E, 0x52, 0x61, 0x36, 0x12, 0xC3, 0xFF, 0x07, 0x11, + 0xD2, 0x33, 0x8B, 0x82, 0xD6, 0x8C, 0x45, 0x02, 0x89, 0xB7, 0x7B, 0x13, + 0x3E, 0x50, 0xBB, 0x7E, 0xEC, 0x24, 0xC4, 0x77, 0x56, 0x7D, 0x74, 0x86, + 0x61, 0x0E, 0x46, 0xCF, 0x7B, 0x7B, 0xAA, 0x3B, 0x79, 0x8B, 0x7C, 0xDD, + 0xE7, 0xB0, 0x22, 0xCC, 0xCE, 0x3E, 0xA6, 0x63, 0xBA, 0x9F, 0xEA, 0xC7, + 0x65, 0x4B, 0xF7, 0xAB, 0x90, 0xFE, 0xE7, 0x34, 0x08, 0x8D, 0xE2, 0x81, + 0x91, 0x07, 0xB9, 0xE4, 0x34, 0x28, 0x6E, 0x83, 0xFC, 0xD0, 0x34, 0x8D, + 0xC3, 0x10, 0x7C, 0x1A, 0x3C, 0xF3, 0x44, 0x45, 0x2C, 0x23, 0xE5, 0xAF, + 0x43, 0xA7, 0x42, 0x61, 0x3F, 0xD7, 0xBB, 0xE5, 0x85, 0x9F, 0xF5, 0xE0, + 0xBB, 0x89, 0xF2, 0xD6, 0xF5, 0xFA, 0x4C, 0x8D, 0xC0, 0x30, 0x5E, 0x4F, + 0x15, 0x8E, 0x67, 0xF7, 0x45, 0xAD, 0xE4, 0xBD, 0x28, 0x8E, 0xE7, 0xDA, + 0x0E, 0x56, 0x84, 0xE8, 0xFA, 0x5D, 0xF7, 0x49, 0x3E, 0x9F, 0x6C, 0x96, + 0xDF, 0xFD, 0x23, 0x9F, 0xC5, 0x58, 0x0C, 0x44, 0xAD, 0x96, 0x54, 0xC4, + 0x4B, 0x0D, 0x76, 0x3E, 0x3E, 0xAD, 0xA3, 0x27, 0x66, 0x97, 0x9C, 0x41, + 0x0A, 0x61, 0x07, 0x14, 0x35, 0x26, 0xF4, 0xE8, 0xCF, 0xC6, 0x94, 0xDA, + 0x69, 0xB1, 0x93, 0x6C, 0xC7, 0x01, 0x17, 0x89, 0x7F, 0x8E, 0x9B, 0x6B, + 0xDA, 0xC7, 0xE5, 0x00, 0xBD, 0xD7, 0x01, 0x20, 0x6A, 0x0B, 0x7F, 0x6B, + 0x0E, 0x4E, 0x12, 0xB2, 0x6E, 0x16, 0x77, 0x86, 0x3E, 0x3D, 0xF7, 0x08, + 0x80, 0xA9, 0x71, 0xA3, 0xCE, 0x10, 0xA3, 0xEE, 0xBB, 0x19, 0xBA, 0x73, + 0x94, 0x98, 0x6D, 0x0A, 0x7A, 0x7F, 0xE0, 0x80, 0xE5, 0x6E, 0xC1, 0xE0, + 0x86, 0x31, 0xD9, 0xB3, 0xA5, 0x57, 0x6D, 0x62, 0xED, 0x7D, 0xF1, 0x19, + 0x7F, 0xD7, 0xCC, 0xA0, 0xDF, 0xC7, 0x0C, 0x43, 0x6C, 0x75, 0x99, 0x59, + 0x8F, 0xA6, 0xEE, 0x2C, 0x2E, 0xE7, 0x3E, 0x31, 0xC6, 0x36, 0xC1, 0x69, + 0x8E, 0xED, 0x42, 0xBB, 0x62, 0xFC, 0xA5, 0x25, 0x29, 0xAF, 0xBE, 0x4D, + 0x4B, 0x91, 0x5B, 0x94, 0xAD, 0x5B, 0x1C, 0xFD, 0x39, 0x8E, 0xA5, 0x94, + 0xD1, 0xD2, 0xE8, 0xE8, 0xB1, 0xB0, 0xA4, 0x29, 0x58, 0xBB, 0xDA, 0x7E, + 0xFC, 0x24, 0xA1, 0x21, 0x3A, 0x69, 0xEE, 0x5C, 0xE4, 0x08, 0x58, 0x3D, + 0xBC, 0xF9, 0xB8, 0xE4, 0xBF, 0x4A, 0xC5, 0x51, 0x70, 0xAE, 0x03, 0x81, + 0x93, 0xD5, 0x62, 0xB9, 0xB7, 0x96, 0x92, 0x17, 0x5E, 0xAF, 0xE8, 0xAA, + 0x82, 0x60, 0x30, 0xB0, 0xE3, 0xF8, 0xFB, 0xA5, 0xBE, 0x62, 0x4E, 0xB5, + 0x2B, 0xC2, 0x1D, 0x1F, 0xEA, 0xC3, 0x09, 0xB6, 0x6E, 0xAC, 0xE0, 0xCE, + 0x01, 0x9F, 0x97, 0xDE, 0xD0, 0x52, 0x92, 0xDC, 0xD4, 0xFC, 0x8A, 0xA2, + 0x7E, 0xDB, 0x10, 0x4E, 0x37, 0xD6, 0x83, 0xD9, 0x36, 0xFB, 0xA8, 0xC7, + 0xD6, 0xD3, 0x0F, 0xB4, 0x32, 0x05, 0x4E, 0x9B, 0x47, 0x89, 0x5E, 0x0D, + 0xF0, 0x9A, 0x49, 0x05, 0x38, 0xDF, 0xDD, 0x3E, 0x33, 0x35, 0x41, 0x64, + 0x17, 0xCD, 0xE6, 0xC1, 0x85, 0xA4, 0xBB, 0xC6, 0x43, 0x90, 0x71, 0x9A, + 0x5B, 0x5A, 0xE2, 0x97, 0x2D, 0x0F, 0xCC, 0xE0, 0xA6, 0x8E, 0x22, 0x51, + 0x38, 0xFB, 0x54, 0xBC, 0x92, 0x27, 0x4C, 0x2E, 0x44, 0x33, 0xE0, 0xE3, + 0xD5, 0xE0, 0xA8, 0xAB, 0x9B, 0xDC, 0xEF, 0x11, 0x3D, 0x86, 0x4E, 0xF4, + 0x83, 0xEB, 0x27, 0xF4, 0x55, 0x21, 0x0C, 0x49, 0x26, 0xB4, 0x42, 0x3B, + 0x81, 0x46, 0x8B, 0x6C, 0x3A, 0x29, 0xB3, 0x61, 0x34, 0x22, 0x65, 0xAB, + 0x56, 0x0D, 0xD9, 0xC3, 0x9E, 0x33, 0xFD, 0xD5, 0xFC, 0xF1, 0xDD, 0xDA, + 0xD6, 0xA9, 0x32, 0x21, 0x0E, 0x9E, 0x5C, 0x5A, 0xFF, 0x15, 0x07, 0x37, + 0x96, 0x74, 0x1F, 0x67, 0x89, 0x2D, 0x91, 0xD5, 0x50, 0x07, 0x65, 0x07, + 0x4F, 0x1E, 0x04, 0xDF, 0x56, 0xD8, 0x30, 0x98, 0x6F, 0xCD, 0xE0, 0x79, + 0xF3, 0xE4, 0x6A, 0xEA, 0x22, 0xD9, 0x7E, 0x7F, 0x6E, 0x87, 0x04, 0x6C, + 0x6F, 0x5A, 0x88, 0x62, 0x12, 0xB0, 0x3A, 0x68, 0xA3, 0xB5, 0x96, 0x2A, + 0x26, 0x98, 0x36, 0x54, 0x07, 0x83, 0x14, 0x3C, 0xC8, 0x30, 0x09, 0xE2, + 0xE0, 0xE2, 0x79, 0xFA, 0x7B, 0xC5, 0x85, 0x6E, 0xE9, 0x1C, 0xF9, 0xC6, + 0xC9, 0xDE, 0xCB, 0xA8, 0x02, 0x09, 0x24, 0x84, 0x8E, 0x45, 0xE4, 0x72, + 0x81, 0x13, 0x89, 0x24, 0xD2, 0x66, 0x6F, 0xD9, 0xF0, 0x9A, 0xF8, 0x82, + 0x02, 0x71, 0x6C, 0x83, 0x7F, 0x2F, 0xC9, 0x4A, 0x88, 0xA1, 0x2A, 0x8C, + 0x04, 0x38, 0xB4, 0xEF, 0x98, 0x4B, 0x54, 0x4A, 0xC1, 0xCE, 0x29, 0x43, + 0x73, 0xA1, 0x23, 0x70, 0xFC, 0xDA, 0x44, 0x15, 0x37, 0xFC, 0x53, 0x4D, + 0xB3, 0x52, 0xD1, 0x8A, 0x2B, 0x2D, 0x60, 0xE7, 0xDE, 0x29, 0x0A, 0xC6, + 0x67, 0x64, 0x8C, 0x2A, 0xD7, 0x79, 0x0B, 0x44, 0x95, 0x3A, 0xF7, 0x6E, + 0x02, 0x24, 0xAE, 0x6E, 0xB8, 0x64, 0x45, 0xD8, 0xD6, 0xE7, 0xCA, 0x1C, + 0x27, 0x0E, 0x48, 0xC0, 0x38, 0xC0, 0x9D, 0x6A, 0xFB, 0x91, 0x7B, 0x1D, + 0xF7, 0x90, 0xA0, 0x77, 0xA0, 0x9B, 0x4B, 0xE5, 0xDE, 0x60, 0xC4, 0x53, + 0xF0, 0x28, 0xC8, 0x9E, 0xB5, 0x77, 0x5B, 0x61, 0x84, 0xAC, 0xEA, 0x59, + 0x89, 0x3F, 0x6E, 0x03, 0x8A, 0xEB, 0x37, 0x69, 0xC8, 0x68, 0x74, 0x10, + 0xBD, 0xE9, 0xB1, 0x1F, 0x25, 0x38, 0xBD, 0x26, 0x05, 0x66, 0xB8, 0x44, + 0x70, 0x34, 0xBD, 0x2A, 0xEE, 0x5D, 0x81, 0xB1, 0x2D, 0x13, 0xE1, 0x04, + 0x7D, 0x82, 0xE3, 0x68, 0x67, 0x91, 0x60, 0xB9, 0x7B, 0x36, 0x89, 0x80, + 0x84, 0x3D, 0x85, 0x0A, 0xFE, 0xC3, 0xFF, 0x94, 0xAA, 0x7B, 0x86, 0x07, + 0x74, 0x22, 0xC1, 0x8B, 0x56, 0x67, 0xC7, 0xEC, 0x41, 0x4F, 0x17, 0xC1, + 0x02, 0x77, 0x42, 0x36, 0x4B, 0x0D, 0xF3, 0xC3, 0xB5, 0x71, 0x52, 0x86, + 0x54, 0x0A, 0xB6, 0x60, 0x00, 0x85, 0x76, 0xC3, 0x40, 0x42, 0xBA, 0x15, + 0x21, 0x08, 0x3D, 0xC8, 0xD5, 0x09, 0x53, 0xCC, 0x21, 0xFF, 0x19, 0x95, + 0xF0, 0x52, 0xBC, 0x4A, 0x5A, 0xEC, 0x4A, 0xB7, 0x21, 0x9E, 0x03, 0x4A, + 0x72, 0xF5, 0x90, 0xE4, 0x61, 0xC8, 0x8F, 0x3A, 0x3F, 0xE3, 0x5D, 0x2B, + 0xE9, 0x7A, 0x48, 0xA4, 0xDC, 0x0B, 0x78, 0xEC, 0x7E, 0x8B, 0xBF, 0x14, + 0x40, 0xFF, 0x26, 0x75, 0x07, 0x5E, 0x9A, 0xFC, 0xCD, 0xCD, 0x9C, 0xC5, + 0x02, 0xC6, 0x72, 0xB4, 0xA9, 0x4C, 0x06, 0x6E, 0x9B, 0xD4, 0xE2, 0x5F, + 0x22, 0x04, 0x13, 0x86, 0x6D, 0x39, 0xDB, 0x63, 0x4B, 0xF6, 0x07, 0x83, + 0xEA, 0x80, 0xC9, 0xAC, 0x83, 0xA9, 0x6D, 0x14, 0x30, 0x53, 0x73, 0x62, + 0x79, 0x40, 0xB8, 0x7D, 0x0D, 0x8D, 0x03, 0xA6, 0x21, 0xA0, 0x4F, 0x90, + 0x0D, 0xC4, 0xA0, 0x3C, 0xEB, 0x0E, 0xD1, 0xB0, 0x7A, 0xCD, 0x3A, 0x49, + 0xC9, 0x8D, 0x20, 0xE4, 0x94, 0x68, 0x8D, 0x94, 0xA7, 0x50, 0x67, 0xB6, + 0xF4, 0xC8, 0x40, 0x7E, 0x58, 0xAD, 0xB8, 0x8E, 0xBA, 0xCE, 0x08, 0x5D, + 0x54, 0xFE, 0xB3, 0xD1, 0x90, 0xB1, 0x5D, 0xDF, 0x21, 0xFA, 0x50, 0x46, + 0x7A, 0x88, 0xD3, 0x96, 0x17, 0x01, 0xE8, 0xC9, 0xD3, 0xCA, 0x1F, 0x23, + 0x2C, 0xD4, 0xF8, 0x62, 0xE5, 0xB3, 0x78, 0x59, 0xB8, 0xF6, 0x69, 0x85, + 0x48, 0xC5, 0xE4, 0xC1, 0x73, 0x47, 0xB4, 0x92, 0xAE, 0xDD, 0xD1, 0xEC, + 0xBE, 0x57, 0xCA, 0x51, 0xAB, 0xAF, 0xAD, 0xA8, 0x19, 0x84, 0xB5, 0xCF, + 0xA4, 0xD7, 0xAF, 0xE6, 0xF5, 0xFA, 0xBE, 0x3B, 0x6A, 0xE2, 0x37, 0x03, + 0x44, 0xDD, 0x02, 0x93, 0x05, 0x74, 0x9F, 0xD0, 0x98, 0xD9, 0x49, 0x3E, + 0x2D, 0x4E, 0xA6, 0x6F, 0x2C, 0x05, 0xDF, 0x45, 0xC8, 0xCA, 0xD1, 0x84, + 0xF6, 0x32, 0xA8, 0x1F, 0x7A, 0xF9, 0xF4, 0xBF, 0xC1, 0x03, 0x61, 0x47, + 0x65, 0xEA, 0xAE, 0x74, 0x1D, 0x91, 0xD2, 0x40, 0x9D, 0x54, 0x5F, 0x55, + 0xCA, 0x4F, 0xF0, 0x3A, 0x9A, 0x74, 0xD4, 0x92, 0x67, 0xA7, 0x82, 0x3B, + 0xAA, 0xBF, 0x49, 0xFF, 0x11, 0x4B, 0x3E, 0xF5, 0x59, 0x12, 0xB1, 0x19, + 0xEC, 0xC3, 0xCD, 0x9D, 0x28, 0x4D, 0x46, 0x37, 0x2A, 0x56, 0xC6, 0x27, + 0x21, 0x5D, 0x3F, 0x86, 0x0A, 0x1F, 0xEE, 0xFF, 0x0B, 0x28, 0x38, 0xD9, + 0x67, 0xA3, 0x80, 0x28, 0x6F, 0x71, 0x9E, 0xA6, 0x6B, 0x1E, 0x91, 0x72, + 0xEF, 0x0F, 0xB3, 0x60, 0x2D, 0x5B, 0xF4, 0x1C, 0xFC, 0x09, 0x8B, 0x72, + 0x3C, 0x41, 0x27, 0x19, 0x65, 0xAD, 0x32, 0x28, 0x6E, 0x8E, 0x46, 0xFA, + 0xA1, 0x48, 0x63, 0xD2, 0x64, 0x1B, 0xD4, 0x3B, 0xD3, 0xB9, 0x31, 0xE1, + 0x10, 0xAA, 0x3A, 0x46, 0x19, 0xF4, 0x8E, 0xD7, 0xF9, 0x40, 0x11, 0xE2, + 0x6A, 0x62, 0xCE, 0x83, 0xD3, 0x8A, 0xE4, 0xD0, 0x97, 0xC0, 0x50, 0x8A, + 0x3E, 0x92, 0x34, 0x26, 0x9C, 0x58, 0x54, 0x70, 0xAE, 0x13, 0x23, 0xFD, + 0x8E, 0x95, 0xBC, 0xF6, 0xA9, 0x4A, 0x59, 0xDC, 0xD6, 0x8C, 0x2C, 0xCF, + 0x97, 0x49, 0x1B, 0x13, 0xB9, 0x0E, 0x1B, 0xBC, 0x62, 0x34, 0xE4, 0x3E, + 0x84, 0xD8, 0x63, 0x92, 0x2F, 0x46, 0xB4, 0xDA, 0x5C, 0xB6, 0x63, 0xD0, + 0x86, 0x24, 0x8C, 0x50, 0xB3, 0x18, 0xE1, 0xFE, 0x70, 0x65, 0x54, 0xCD, + 0xC6, 0x0C, 0x14, 0x57, 0x74, 0xFA, 0x60, 0xB1, 0xE3, 0xE4, 0xBA, 0x0C, + 0x36, 0xA2, 0x8D, 0x48, 0x2F, 0x8F, 0x59, 0xAF, 0xD1, 0xC8, 0xC8, 0x60, + 0x2F, 0x4C, 0x42, 0xF4, 0x1F, 0xCD, 0x8C, 0x5C, 0xB2, 0xCA, 0xB3, 0xBE, + 0x1F, 0xF9, 0x76, 0xEC, 0x84, 0xA7, 0x3C, 0x36, 0xCB, 0x6F, 0x3D, 0x58, + 0x6A, 0x19, 0xE1, 0x2A, 0x34, 0xFA, 0xE0, 0xB0, 0x65, 0xDF, 0x90, 0x61, + 0xC1, 0x21, 0x3B, 0xE5, 0x84, 0xCD, 0x8A, 0x32, 0xFE, 0x0F, 0x68, 0x78, + 0x8F, 0x77, 0xCD, 0xE4, 0x3B, 0xBC, 0x38, 0xCE, 0x3E, 0x32, 0x97, 0x70, + 0x7F, 0xFA, 0x63, 0x6C, 0x2C, 0x50, 0xD7, 0x54, 0x4A, 0x83, 0x7A, 0xCA, + 0x51, 0x46, 0xFE, 0x8B, 0xB5, 0xCC, 0x6B, 0x64, 0x82, 0xF8, 0x17, 0xE1, + 0xE0, 0x48, 0x25, 0xD2, 0x0E, 0x0D, 0x29, 0xBD, 0x79, 0x50, 0x57, 0x11, + 0xD1, 0x48, 0x8D, 0x19, 0xF7, 0x9D, 0x56, 0xA4, 0x01, 0xCE, 0x3A, 0x2F, + 0x6B, 0x6F, 0x14, 0xF8, 0x59, 0x68, 0x4C, 0x16, 0xDE, 0x33, 0x09, 0x4D, + 0xB5, 0xD7, 0x14, 0xC0, 0xAB, 0x4D, 0x90, 0x19, 0x3E, 0x6C, 0x97, 0x85, + 0x2F, 0x5E, 0x81, 0xB0, 0x52, 0x2B, 0x7D, 0xCC, 0xF1, 0x3A, 0xC0, 0x95, + 0x9D, 0xF2, 0xB2, 0x97, 0xE2, 0x42, 0x26, 0xEA, 0xC2, 0xD7, 0x96, 0xA0, + 0x24, 0x47, 0xA3, 0xBB, 0x2C, 0x8F, 0xC6, 0x7B, 0x3F, 0xDF, 0x0C, 0x08, + 0x6D, 0x93, 0x2F, 0x26, 0xB8, 0x8C, 0xE5, 0x30, 0x72, 0x8D, 0x8A, 0x2D, + 0xE6, 0x29, 0x95, 0x2F, 0x43, 0x75, 0xFC, 0xD9, 0x6B, 0xCF, 0x1A, 0x29, + 0xBB, 0xF3, 0xE6, 0x8F, 0x43, 0x0E, 0x12, 0x07, 0x9F, 0xB1, 0x76, 0x45, + 0xB3, 0xEC, 0x6B, 0x5A, 0xD6, 0x77, 0x89, 0xCF, 0x7A, 0x9A, 0x26, 0x34, + 0xA6, 0x69, 0x51, 0x7C, 0x07, 0x3D, 0xA3, 0xA2, 0xAA, 0x57, 0xF5, 0x5A, + 0x9E, 0x85, 0x4F, 0x37, 0x53, 0x9D, 0x6A, 0x44, 0x26, 0x54, 0xE2, 0x7D, + 0x1D, 0xD5, 0xD7, 0x50, 0x00, 0xF9, 0x5B, 0x9D, 0x8D, 0x25, 0xA4, 0x78, + 0xF3, 0x14, 0x87, 0xBA, 0x89, 0xBB, 0x54, 0x72, 0xEC, 0xA7, 0xE9, 0xF3, + 0x07, 0xE1, 0x5E, 0x3A, 0xCC, 0x91, 0x9E, 0x8F, 0x61, 0x41, 0xCC, 0x28, + 0xBA, 0x98, 0xC8, 0x98, 0xFC, 0x13, 0x24, 0x44, 0x48, 0x7F, 0x5E, 0x46, + 0x45, 0x22, 0x44, 0x51, 0x1D, 0xF2, 0x02, 0x0C, 0x19, 0x47, 0x5B, 0xE4, + 0x7B, 0xE8, 0x7D, 0xE9, 0xB0, 0x9D, 0xB8, 0x6C, 0xF7, 0x7B, 0x04, 0x6F, + 0x24, 0xD7, 0x58, 0xF6, 0x09, 0x50, 0x0D, 0x2B, 0xCC, 0x85, 0x81, 0xD3, + 0xD4, 0x02, 0x8E, 0xFC, 0xFD, 0xC0, 0x04, 0x5C, 0x39, 0x48, 0x08, 0xB1, + 0x5D, 0xF1, 0xF0, 0x37, 0xCC, 0xD4, 0x0D, 0x78, 0xE9, 0x6F, 0xA2, 0x8B, + 0x02, 0xC7, 0xA0, 0x1D, 0xD8, 0x8F, 0xA6, 0xC3, 0x4E, 0x44, 0x0D, 0xF5, + 0x0E, 0xDE, 0x9E, 0x6A, 0xBE, 0xC9, 0xE1, 0x7B, 0x71, 0x42, 0x1F, 0x2D, + 0x37, 0x29, 0xCC, 0xCE, 0xE7, 0x38, 0x0C, 0xF2, 0x73, 0x1C, 0x5C, 0x45, + 0xA3, 0x2E, 0x94, 0xB1, 0x24, 0x94, 0x9D, 0x65, 0xAA, 0xB7, 0x87, 0x47, + 0x41, 0x3A, 0x85, 0xDE, 0x05, 0xB7, 0x4A, 0xAF, 0x9E, 0x15, 0x94, 0xA1, + 0x87, 0xE1, 0x91, 0x70, 0x40, 0x71, 0x56, 0x39, 0xCE, 0xC9, 0x47, 0x1D, + 0x8D, 0x80, 0x95, 0x7D, 0x3B, 0x9E, 0xA9, 0xA6, 0x93, 0x19, 0xEB, 0x23, + 0x1D, 0x3B, 0x72, 0xBA, 0x6F, 0x01, 0xFE, 0xC9, 0x56, 0x50, 0xE2, 0xFD, + 0x55, 0x04, 0x41, 0xA7, 0x75, 0x34, 0xC9, 0x58, 0xBE, 0x43, 0x12, 0x63, + 0x54, 0x20, 0xA8, 0x5E, 0x24, 0xE4, 0x92, 0x4D, 0x28, 0x55, 0x45, 0x32, + 0x65, 0xCF, 0x12, 0x1E, 0x50, 0x37, 0xE0, 0x86, 0xCD, 0xA7, 0xDD, 0x91, + 0x9E, 0xC6, 0xD1, 0xDE, 0xA9, 0xCA, 0x57, 0x70, 0x06, 0xEE, 0xD8, 0x90, + 0x61, 0xF0, 0x79, 0x84, 0x4E, 0xCC, 0x19, 0xCA, 0xDC, 0x1E, 0xB7, 0x78, + 0x90, 0x23, 0x0D, 0x83, 0xDB, 0x48, 0xA0, 0xD7, 0x86, 0x38, 0x6B, 0x6A, + 0x55, 0xFE, 0x89, 0x4F, 0xE5, 0xDA, 0x0C, 0x5B, 0xB3, 0x8C, 0x35, 0x00, + 0x0A, 0x17, 0xFD, 0x77, 0x00, 0x0E, 0x1F, 0x22, 0x67, 0xCB, 0x80, 0x21, + 0xC9, 0x4E, 0x57, 0xAB, 0x50, 0x0A, 0xBA, 0x9B, 0xF6, 0x15, 0x97, 0xEC, + 0x72, 0xDA, 0x5F, 0xF7, 0x5E, 0x28, 0x82, 0xD9, 0xDE, 0x86, 0x05, 0xB8, + 0x7A, 0xF6, 0x8D, 0x7B, 0x4F, 0x76, 0x35, 0xB9, 0x85, 0x6A, 0xF2, 0xDD, + 0x12, 0xC1, 0x44, 0xE5, 0xDC, 0xC7, 0x74, 0xF3, 0x55, 0x7D, 0x74, 0x18, + 0x5A, 0x41, 0x3D, 0xE9, 0xEA, 0x90, 0x37, 0x22, 0x77, 0x47, 0x61, 0xD2, + 0x02, 0x91, 0x4C, 0x8B, 0x24, 0x36, 0x00, 0xD7, 0xD1, 0x89, 0xF3, 0x4E, + 0x5D, 0x60, 0x7B, 0x36, 0xF7, 0x0F, 0x9A, 0x72, 0x51, 0x3D, 0xD7, 0x8B, + 0xE5, 0x8F, 0xEB, 0x09, 0x65, 0x96, 0xC5, 0x2B, 0x0D, 0x25, 0xC3, 0xF0, + 0x89, 0x86, 0xD4, 0xBA, 0xB6, 0x7B, 0x4F, 0x2A, 0x8A, 0xAB, 0x10, 0xC4, + 0x2E, 0xE5, 0x8C, 0xE2, 0x91, 0xB9, 0x9D, 0x09, 0x08, 0x45, 0x8B, 0xB3, + 0xFC, 0xED, 0x44, 0x38, 0xE8, 0xA5, 0x90, 0x00, 0x48, 0x76, 0xA7, 0x6C, + 0x44, 0x60, 0x10, 0xE2, 0x7F, 0x05, 0xD0, 0x91, 0x30, 0xDE, 0x3D, 0xE2, + 0xBC, 0xCC, 0xED, 0xEE, 0x53, 0xA3, 0xEF, 0xAA, 0x29, 0xE1, 0x1E, 0xFC, + 0x45, 0x7E, 0x98, 0x7C, 0x13, 0xB1, 0xF4, 0x92, 0x0A, 0x92, 0x4E, 0xDD, + 0x90, 0xD1, 0x07, 0x45, 0xA3, 0x2B, 0x5F, 0xDB, 0xBF, 0x53, 0x18, 0xDC, + 0x9F, 0x62, 0x64, 0xF1, 0x64, 0x6A, 0xF6, 0xCC, 0x8B, 0x10, 0x21, 0x72, + 0xD2, 0x12, 0x22, 0xDA, 0xA4, 0xAE, 0xE0, 0x14, 0x9D, 0x01, 0xDD, 0x5F, + 0x63, 0x71, 0x41, 0x21, 0x76, 0x43, 0x22, 0x63, 0xE0, 0xBF, 0x70, 0x2B, + 0xA2, 0x7D, 0x42, 0xEE, 0x2B, 0x23, 0x19, 0x7C, 0x7A, 0xA3, 0xCC, 0xFE, + 0xC6, 0x7E, 0x94, 0xC6, 0xF8, 0xD2, 0xE2, 0xB1, 0x54, 0xEE, 0xD7, 0xEA, + 0x9A, 0x20, 0x89, 0x2B, 0x8C, 0x70, 0x64, 0x39, 0x01, 0x64, 0x13, 0x72, + 0xE8, 0xCC, 0xC8, 0xC0, 0x89, 0x1A, 0x64, 0x30, 0xF7, 0x33, 0x62, 0xE4, + 0x09, 0x24, 0x0A, 0x83, 0x56, 0x18, 0x7A, 0x6C, 0x5A, 0x9E, 0x43, 0x3A, + 0x6E, 0xC6, 0xF3, 0x0B, 0x6B, 0xEE, 0x07, 0x6C, 0x08, 0x64, 0x28, 0xEF, + 0xA1, 0x72, 0xDA, 0x56, 0x36, 0x5E, 0xE7, 0xBD, 0x31, 0x0C, 0x58, 0x7F, + 0x46, 0x07, 0x9A, 0x9B, 0x74, 0x84, 0x7E, 0x7F, 0x11, 0x05, 0x04, 0x87, + 0x40, 0xB3, 0xA6, 0xB9, 0x18, 0x6B, 0x4F, 0xE5, 0xD9, 0x8E, 0xF2, 0x44, + 0x33, 0xBE, 0x14, 0x48, 0x30, 0x6A, 0x9D, 0x7A, 0x8B, 0xED, 0xFE, 0x6C, + 0xA1, 0x0C, 0x55, 0xC5, 0xB4, 0x43, 0xCD, 0xEC, 0x72, 0x88, 0x5E, 0xD5, + 0x6B, 0xCF, 0xB8, 0xBE, 0x6C, 0xC7, 0xFA, 0xFE, 0x6A, 0x97, 0xB9, 0x59, + 0x01, 0xE5, 0xD3, 0xDE, 0x82, 0xB8, 0x3E, 0x9B, 0xF6, 0xDC, 0x66, 0x62, + 0xA8, 0x29, 0xBA, 0x05, 0x0E, 0x67, 0x64, 0x0B, 0x5F, 0xAB, 0x29, 0xC9, + 0x18, 0x14, 0x23, 0x54, 0x52, 0x25, 0xF4, 0x69, 0xBB, 0xAB, 0xC3, 0x14, + 0x53, 0x99, 0x96, 0x94, 0x40, 0x03, 0x3C, 0x96, 0x42, 0x19, 0xAC, 0x2B, + 0xA6, 0x83, 0x60, 0xD1, 0xC8, 0xA7, 0x1C, 0x6C, 0x62, 0x4D, 0xD8, 0xA4, + 0x6E, 0x5E, 0xC1, 0x57, 0x7B, 0x17, 0x33, 0xB6, 0xCF, 0x91, 0x18, 0x94, + 0x8F, 0x2C, 0x14, 0xE5, 0x6F, 0x60, 0x5B, 0x41, 0xCB, 0x5F, 0xDA, 0x17, + 0x23, 0xB6, 0xE4, 0x4B, 0x6F, 0xD9, 0xFC, 0x16, 0xB3, 0x24, 0x1C, 0x98, + 0x40, 0x41, 0x2B, 0xBE, 0x15, 0x6D, 0xF3, 0x6A, 0x98, 0x3A, 0x7A, 0x99, + 0xE8, 0xB4, 0x62, 0x51, 0xBA, 0x01, 0x43, 0x19, 0x2E, 0xDB, 0xFB, 0x21, + 0x36, 0x70, 0x6C, 0xEE, 0xEC, 0x41, 0xD4, 0x4B, 0x9C, 0xE7, 0xE8, 0x82, + 0xB1, 0xFB, 0xAE, 0x17, 0x28, 0x48, 0xCE, 0xCF, 0xE3, 0xCF, 0x2A, 0xF2, + 0x9E, 0x9D, 0x24, 0xE8, 0x84, 0x1E, 0x05, 0xDD, 0x73, 0x57, 0x52, 0x79, + 0xC7, 0xD6, 0xA8, 0xBF, 0xB3, 0x03, 0x3E, 0xD4, 0x5E, 0xCE, 0x74, 0xC3, + 0x59, 0x69, 0x27, 0x5C, 0xE7, 0x5A, 0xE5, 0x82, 0x15, 0x5B, 0xD8, 0x18, + 0x98, 0x36, 0x47, 0xBF, 0x16, 0x8D, 0x0D, 0xD1, 0x43, 0x9F, 0xE4, 0x4E, + 0xAD, 0x42, 0x92, 0xE1, 0x0B, 0xA1, 0x81, 0x36, 0x38, 0xAA, 0x2F, 0x78, + 0xC7, 0x9B, 0x41, 0xB1, 0xF3, 0x30, 0xE1, 0x42, 0x61, 0x10, 0xF5, 0x8F, + 0xFB, 0xEC, 0xA9, 0xEC, 0xB8, 0x10, 0xAD, 0xC6, 0xC4, 0xA7, 0xA6, 0x4B, + 0x6A, 0x6C, 0xDC, 0x02, 0x20, 0x42, 0x08, 0xBD, 0x6F, 0xC5, 0xF6, 0x8F, + 0xC9, 0x2C, 0xA3, 0x18, 0x8C, 0x0B, 0xA1, 0x9D, 0x29, 0x25, 0x3E, 0xFE, + 0x7C, 0xF4, 0xDD, 0xEF, 0x39, 0xE1, 0x96, 0x85, 0x9C, 0xA1, 0xE6, 0x6E, + 0xC6, 0x0A, 0x31, 0xEC, 0xFB, 0x45, 0x85, 0xA0, 0x23, 0xFE, 0xCD, 0x1C, + 0x49, 0x4F, 0xA0, 0x10, 0xD7, 0xFC, 0x74, 0x5B, 0xF9, 0xB6, 0xB2, 0x11, + 0xD9, 0x9E, 0x8B, 0x26, 0xB1, 0x36, 0x8F, 0xA0, 0x6E, 0xC7, 0x39, 0xED, + 0x88, 0xF3, 0x21, 0x7B, 0xBB, 0x9A, 0xF9, 0x65, 0x76, 0x8F, 0x68, 0x72, + 0xB3, 0xEE, 0x0D, 0xAA, 0xCD, 0xB7, 0xFA, 0x0A, 0x47, 0x6F, 0xB0, 0x21, + 0x4A, 0xB9, 0xC8, 0x7A, 0xA1, 0x6D, 0x88, 0x50, 0xF7, 0xE9, 0x2E, 0xF5, + 0x64, 0x51, 0x44, 0xDE, 0x21, 0x3C, 0xC8, 0x31, 0x68, 0x42, 0x73, 0x41, + 0x32, 0xC5, 0xFD, 0xCE, 0xE7, 0x16, 0xD9, 0x34, 0xFF, 0xAC, 0x20, 0x1B, + 0x52, 0x2A, 0x9F, 0xD8, 0x71, 0xC3, 0x96, 0xB9, 0x88, 0x81, 0x28, 0x22, + 0x07, 0x76, 0xB8, 0x0E, 0x0A, 0x49, 0xA0, 0x15, 0x67, 0xF8, 0x75, 0x88, + 0xA2, 0xC3, 0x1F, 0xCB, 0xFD, 0x5F, 0x83, 0x28, 0x51, 0xA5, 0x77, 0xCB, + 0x21, 0x39, 0x43, 0x93, 0xB6, 0x9F, 0x00, 0x7D, 0x07, 0x1A, 0xF3, 0x98, + 0x23, 0x4B, 0x73, 0x96, 0xB1, 0xAA, 0x39, 0x24, 0x7C, 0x3D, 0xAD, 0x2C, + 0xFE, 0xB9, 0x91, 0x18, 0xE1, 0x4B, 0x4D, 0x83, 0x72, 0x5C, 0x7B, 0xCE, + 0xFC, 0x4F, 0xAC, 0x9C, 0x0F, 0xC5, 0x0B, 0x3B, 0x40, 0x0E, 0xB8, 0x6C, + 0x65, 0xF7, 0x16, 0xF2, 0x5D, 0x40, 0x1F, 0xE9, 0x4E, 0xFD, 0x7A, 0x9E, + 0x86, 0xDC, 0x6D, 0xFD, 0x0E, 0x4F, 0x72, 0xCB, 0x3E, 0x77, 0xCA, 0x33, + 0xB7, 0xB3, 0xA0, 0xF0, 0xE2, 0x5C, 0xA9, 0xF0, 0x7D, 0xC0, 0x53, 0x0A, + 0x0D, 0x27, 0x67, 0x62, 0x98, 0x7D, 0x9C, 0xEA, 0x76, 0xDF, 0x30, 0x1A, + 0x57, 0x8C, 0xEA, 0x04, 0x0F, 0x48, 0x35, 0x1F, 0x26, 0x82, 0x6A, 0x87, + 0x89, 0xCA, 0x49, 0xAF, 0xEC, 0x44, 0x1E, 0xD0, 0xDF, 0x33, 0x77, 0x03, + 0xA7, 0xEA, 0x7E, 0x63, 0x82, 0x8C, 0xC7, 0x57, 0x7A, 0x49, 0x52, 0x21, + 0xF5, 0x14, 0x98, 0x51, 0x8B, 0x6C, 0xBE, 0x3E, 0xC3, 0xF4, 0xE8, 0x86, + 0x6C, 0xDE, 0x28, 0xEB, 0x1A, 0xE2, 0x20, 0x80, 0xB0, 0xDA, 0x7A, 0x5E, + 0x9C, 0xDC, 0x95, 0x1C, 0x7B, 0xD7, 0xFA, 0x4B, 0xE9, 0x0C, 0xE6, 0x69, + 0x72, 0xA6, 0x61, 0x08, 0xCF, 0x89, 0xE6, 0x25, 0xEE, 0x4C, 0x2C, 0x0D, + 0x88, 0x71, 0x63, 0x72, 0x25, 0x2E, 0xB7, 0x6A, 0x55, 0x48, 0x22, 0x41, + 0xA6, 0x1D, 0x0F, 0x98, 0x8C, 0xA3, 0x04, 0x08, 0x73, 0x87, 0x77, 0x8F, + 0xA0, 0xA5, 0x33, 0xC9, 0xB6, 0x32, 0x3D, 0xE3, 0xD9, 0x41, 0x1D, 0x4B, + 0x98, 0x29, 0x59, 0xEF, 0x1F, 0xC6, 0x87, 0xC5, 0x9F, 0x40, 0xD4, 0xB2, + 0x47, 0x85, 0x03, 0x7F, 0x45, 0x1F, 0x9B, 0x65, 0x12, 0x19, 0xEB, 0xDD, + 0xFA, 0x85, 0x5F, 0x39, 0x44, 0x66, 0x00, 0x29, 0x98, 0x2C, 0x60, 0xD2, + 0x41, 0x38, 0x41, 0xA8, 0x17, 0xD3, 0x05, 0xC6, 0xB3, 0xBD, 0x8F, 0x38, + 0x82, 0x0F, 0x18, 0xDC, 0xF9, 0xB2, 0xF1, 0xE2, 0x81, 0xA8, 0x61, 0x07, + 0x37, 0x92, 0x5C, 0x85, 0xA8, 0xAE, 0x97, 0x76, 0xD5, 0xB8, 0x71, 0x3F, + 0xF4, 0x17, 0x8B, 0x78, 0x5B, 0xBE, 0xD9, 0x7D, 0x63, 0xFD, 0xFD, 0xD8, + 0xCA, 0x88, 0x00, 0x10, 0xA3, 0x9D, 0xF1, 0x42, 0xEE, 0x19, 0xFE, 0x2D, + 0xB8, 0x73, 0x48, 0x48, 0xC9, 0x77, 0xD2, 0x67, 0xA7, 0xDB, 0xD1, 0x7C, + 0xAC, 0x39, 0xD8, 0x44, 0xB6, 0x63, 0x19, 0x4C, 0xE6, 0x46, 0xEA, 0x36, + 0x78, 0xDC, 0x9F, 0x58, 0x21, 0xF3, 0xCB, 0x3E, 0xD8, 0xCB, 0x94, 0x10, + 0x3E, 0x51, 0xC9, 0x76, 0x71, 0x0D, 0xAF, 0xB5, 0x91, 0x42, 0x44, 0x99, + 0xE8, 0x68, 0x41, 0x70, 0x37, 0x47, 0x0A, 0xFE, 0x9C, 0x4F, 0x36, 0x2B, + 0x85, 0xAA, 0xFD, 0x49, 0xC5, 0xFB, 0x65, 0x2B, 0x31, 0x9A, 0x3D, 0x3E, + 0xE7, 0x9E, 0xF5, 0x09, 0x72, 0xB5, 0xD1, 0xC2, 0xE2, 0x1B, 0x21, 0x9A, + 0xDE, 0x5A, 0x2F, 0x96, 0xCC, 0x74, 0x10, 0xDE, 0x7C, 0x7D, 0xC9, 0xB8, + 0x4D, 0xF1, 0x1D, 0x6C, 0xB4, 0x56, 0x29, 0x3E, 0x58, 0x7E, 0x6D, 0xDF, + 0x7E, 0x7D, 0x53, 0xD2, 0xAD, 0x76, 0x40, 0x8D, 0xAC, 0xF4, 0x6B, 0x9F, + 0x82, 0x0E, 0xCD, 0xAE, 0x1E, 0x89, 0x94, 0x8B, 0x2F, 0xD2, 0x7B, 0x89, + 0xAE, 0x3C, 0xC7, 0x1B, 0x74, 0x85, 0xA7, 0x2E, 0x7B, 0x93, 0x17, 0xE9, + 0xF0, 0xCD, 0x6D, 0x93, 0x3D, 0x1C, 0xA0, 0x7B, 0xCA, 0xA5, 0x18, 0x0B, + 0x39, 0xE9, 0x39, 0x3D, 0x11, 0xFB, 0x03, 0xA5, 0xD1, 0x34, 0xBA, 0x25, + 0x2D, 0xF3, 0xE3, 0x23, 0x82, 0x5A, 0x9D, 0x0A, 0x57, 0x60, 0x76, 0x8D, + 0x7E, 0xA5, 0x5C, 0x9B, 0x64, 0xBD, 0x82, 0x97, 0x6A, 0x7C, 0x0B, 0xC5, + 0xA4, 0xF9, 0x87, 0x70, 0xCC, 0x3A, 0xFE, 0x6A, 0x2B, 0x11, 0x5D, 0x5E, + 0xF2, 0x7F, 0x85, 0xDC, 0x54, 0x37, 0xF7, 0x1E, 0x34, 0xC8, 0x0E, 0xC5, + 0x3C, 0x81, 0x82, 0xAE, 0x4B, 0xA7, 0xCB, 0x34, 0x19, 0xF0, 0xCD, 0xD5, + 0x29, 0x1C, 0x6F, 0xF6, 0x74, 0xE1, 0xF3, 0x51, 0x36, 0x6D, 0x26, 0xFF, + 0xB6, 0xC6, 0xCD, 0xCE, 0x71, 0x15, 0xC0, 0x8A, 0xCC, 0xD0, 0xAF, 0x61, + 0x97, 0x7B, 0xB0, 0xB8, 0x27, 0x56, 0x20, 0x16, 0x06, 0x94, 0xFF, 0xF1, + 0x99, 0x57, 0xB4, 0xCA, 0xB9, 0x9B, 0x83, 0x7C, 0x9B, 0x57, 0xDB, 0xB7, + 0x74, 0xC1, 0x66, 0x61, 0xD3, 0xCE, 0x3B, 0xBF, 0xD3, 0x0D, 0xF3, 0xB3, + 0x3B, 0xC8, 0x40, 0xE0, 0xCB, 0xED, 0x9D, 0xF0, 0x9B, 0x31, 0xC6, 0xCB, + 0x8F, 0xBB, 0xFB, 0xF3, 0x41, 0xF5, 0x35, 0xFA, 0xBC, 0xAA, 0xD6, 0x2B, + 0x68, 0x47, 0x82, 0x38, 0xBB, 0x4F, 0xC6, 0x79, 0xD9, 0x9D, 0x46, 0xE2, + 0x20, 0xF4, 0xA8, 0x6A, 0x0A, 0x46, 0xCA, 0x4F, 0x29, 0x68, 0xF3, 0x9E, + 0xDB, 0x7C, 0x01, 0x1A, 0xBA, 0xCB, 0x16, 0xC1, 0x6C, 0x7A, 0x83, 0xDE, + 0xE3, 0x20, 0xEA, 0xF8, 0xC7, 0x7B, 0x28, 0x39, 0x20, 0xFB, 0xEC, 0xED, + 0xAB, 0xB0, 0x1F, 0x06, 0xE2, 0x93, 0xFE, 0x7A, 0x9D, 0x2C, 0x79, 0xF6, + 0xE8, 0xAF, 0xFF, 0xD3, 0x3A, 0xAF, 0xA3, 0xE7, 0x47, 0xA4, 0x62, 0xAF, + 0xCD, 0x1E, 0xB0, 0x62, 0x4C, 0xBA, 0xB9, 0x0A, 0x98, 0xDC, 0x95, 0x94, + 0xEB, 0x5F, 0x55, 0xA6, 0xE9, 0xD2, 0xF1, 0xEA, 0xD5, 0x2F, 0x84, 0x95, + 0xAD, 0x50, 0xDF, 0x31, 0x16, 0x8A, 0xA3, 0x14, 0x4B, 0x9A, 0x49, 0x92, + 0xE0, 0x1A, 0x09, 0x64, 0x27, 0x4E, 0x56, 0xEF, 0x15, 0x12, 0x3F, 0x41, + 0x7E, 0xB7, 0x79, 0x26, 0xD0, 0xB3, 0x07, 0xF6, 0x5A, 0x36, 0x94, 0xDF, + 0x88, 0x0A, 0x4F, 0x54, 0x70, 0xCE, 0x30, 0x22, 0x01, 0x6D, 0x49, 0xCC, + 0x19, 0xB1, 0xFA, 0x1A, 0x9E, 0x78, 0xDA, 0x2E, 0x14, 0xC5, 0xED, 0xC9, + 0x05, 0x8C, 0x62, 0xD2, 0x3D, 0x99, 0x16, 0x2C, 0x5A, 0x59, 0x17, 0xDC, + 0x0B, 0xC7, 0x06, 0x1B, 0x89, 0xDA, 0x51, 0x0D, 0xAA, 0x95, 0x3D, 0x8E, + 0x38, 0xBC, 0x34, 0xA6, 0xCB, 0x6A, 0x9A, 0x71, 0x80, 0xD8, 0x8A, 0xE1, + 0x79, 0x03, 0x97, 0x9F, 0xCC, 0xFF, 0xE2, 0xB3, 0xC2, 0x87, 0x57, 0xDA, + 0x8A, 0xAD, 0x15, 0x2A, 0x8D, 0x05, 0xA9, 0xBA, 0x7A, 0x50, 0x48, 0x7E, + 0x02, 0x43, 0x34, 0xC6, 0xE1, 0xB3, 0x02, 0x8C, 0x8B, 0xB2, 0x18, 0x22, + 0xDD, 0xDC, 0x8C, 0xA4, 0x56, 0xAA, 0x7E, 0x20, 0xDA, 0x38, 0x49, 0xFC, + 0x2F, 0x91, 0x3F, 0x29, 0x48, 0xEA, 0x56, 0xEF, 0x26, 0x15, 0x5F, 0xFF, + 0x30, 0x49, 0x72, 0x8C, 0x8E, 0x39, 0x6D, 0x64, 0x5A, 0x83, 0x69, 0x46, + 0x90, 0x88, 0xBA, 0x06, 0x58, 0x02, 0xE5, 0x36, 0xF6, 0xE4, 0x58, 0x97, + 0xF2, 0xA8, 0xB0, 0x42, 0x7B, 0xFA, 0x80, 0xEE, 0x14, 0xC5, 0x83, 0x1A, + 0xE5, 0x9A, 0xB0, 0x64, 0x50, 0xB8, 0xE4, 0xBC, 0xAA, 0xA7, 0xBF, 0xD5, + 0x24, 0x44, 0x77, 0xAE, 0x4D, 0x09, 0x8D, 0xE0, 0x8E, 0xD2, 0xAD, 0x6B, + 0xC7, 0x4E, 0xBE, 0x40, 0xF1, 0xB6, 0xCA, 0x76, 0x57, 0xEF, 0x06, 0x2C, + 0x05, 0x0B, 0x01, 0x9A, 0x3F, 0x52, 0x9A, 0xA0, 0xB3, 0x9B, 0x0D, 0xAD, + 0x0C, 0xD2, 0x95, 0x3D, 0x5D, 0xAA, 0x7E, 0x40, 0x3B, 0xCA, 0x4D, 0x85, + 0x28, 0x75, 0x97, 0x53, 0x9D, 0x1D, 0x6C, 0xE2, 0xEA, 0x01, 0xE0, 0x61, + 0x52, 0xF0, 0xB6, 0xAC, 0x7D, 0x81, 0xE5, 0x78, 0x27, 0xC4, 0xC2, 0x9F, + 0xB4, 0x13, 0x21, 0x87, 0xC8, 0xEF, 0x88, 0xCB, 0xB5, 0x72, 0x6B, 0x11, + 0xFA, 0xA3, 0xA7, 0x96, 0x76, 0x92, 0x8B, 0x7E, 0x47, 0x6A, 0x7E, 0x64, + 0x70, 0x1A, 0xE5, 0x1D, 0x69, 0x76, 0x73, 0x7D, 0x58, 0x07, 0x27, 0x9C, + 0x33, 0x5A, 0xDA, 0xD5, 0xBA, 0xD5, 0xBA, 0x79, 0x45, 0x0E, 0x20, 0x27, + 0xD1, 0xEA, 0xE4, 0x24, 0x69, 0x8E, 0x52, 0xCF, 0xF4, 0x1C, 0xEF, 0xFE, + 0x54, 0x48, 0xC0, 0x01, 0x8D, 0xA3, 0xF5, 0x3B, 0x7C, 0x1D, 0xB2, 0x18, + 0x17, 0x15, 0x70, 0x38, 0xF4, 0x7D, 0xE9, 0xCE, 0xEB, 0xC0, 0xD0, 0x03, + 0x59, 0xE8, 0x65, 0x03, 0x8F, 0xB0, 0x43, 0xCF, 0xC3, 0x42, 0x59, 0xE7, + 0x24, 0x17, 0x42, 0x30, 0x55, 0xCB, 0xCA, 0xA6, 0x44, 0x10, 0xE5, 0x71, + 0x8E, 0x34, 0x22, 0xF7, 0x64, 0x23, 0x3C, 0xE0, 0xC8, 0x2D, 0xC5, 0xCA, + 0x21, 0x0A, 0x43, 0xD1, 0x8B, 0x40, 0x20, 0x3E, 0x6B, 0xBB, 0xC0, 0x1C, + 0xC1, 0xF0, 0x02, 0x13, 0x4E, 0xF8, 0xEF, 0x9A, 0xA3, 0x63, 0x8B, 0x0A, + 0xD6, 0xCB, 0x68, 0xB2, 0xC6, 0x13, 0xC2, 0x95, 0xF9, 0x80, 0x7A, 0x5C, + 0x3B, 0xDD, 0x50, 0xDE, 0xCD, 0x57, 0x14, 0x4E, 0xB0, 0x34, 0x83, 0x89, + 0xF5, 0xC5, 0xCF, 0x2A, 0x3F, 0x56, 0x4F, 0xE2, 0x8A, 0x54, 0x77, 0x55, + 0x6B, 0x27, 0xC7, 0xFA, 0x04, 0x95, 0x8F, 0xF9, 0x47, 0x0A, 0xA0, 0x88, + 0xD4, 0x46, 0x2A, 0x67, 0xAA, 0xC2, 0xEF, 0x2A, 0xC4, 0xCE, 0x7F, 0xA0, + 0xB1, 0x52, 0x33, 0x4B, 0x7C, 0x7E, 0x1C, 0x17, 0xFB, 0xCC, 0xE1, 0x92, + 0x53, 0xB3, 0x9F, 0x02, 0xA0, 0xF2, 0x7B, 0x89, 0x7C, 0x11, 0xC0, 0x93, + 0xBB, 0x8C, 0x3F, 0x80, 0x5E, 0x24, 0xCB, 0xDA, 0xD0, 0xAF, 0xBA, 0xB8, + 0x90, 0xFD, 0x8F, 0xAA, 0xF2, 0x36, 0x5A, 0x14, 0xD1, 0xEB, 0x7F, 0xA9, + 0x21, 0x7F, 0x6E, 0xAB, 0xB4, 0x70, 0xCF, 0x21, 0x10, 0x78, 0x04, 0xE7, + 0x32, 0xAA, 0x60, 0x1B, 0xEC, 0xF0, 0x1C, 0x07, 0xE6, 0xAF, 0x42, 0xBD, + 0x8B, 0x2A, 0xA7, 0x3C, 0xB2, 0xFD, 0xE7, 0xCF, 0x8F, 0x19, 0x3D, 0x09, + 0x8B, 0x9A, 0x98, 0xF7, 0x38, 0x42, 0x59, 0x98, 0x56, 0x3D, 0xD1, 0xCB, + 0x9C, 0x6C, 0x2F, 0x45, 0xF6, 0x0A, 0x4E, 0x92, 0x89, 0xD3, 0xAF, 0x6E, + 0xBF, 0x1C, 0xB4, 0xC1, 0x5B, 0x41, 0x21, 0x6D, 0xD4, 0x95, 0x17, 0x8B, + 0xDB, 0x20, 0xA1, 0x16, 0x76, 0xFA, 0xFF, 0xB7, 0xEC, 0x1D, 0x70, 0x04, + 0x45, 0xFD, 0x18, 0xD4, 0x7D, 0x9A, 0x17, 0x55, 0x01, 0x0A, 0xE3, 0x1B, + 0xDB, 0x8C, 0x5F, 0x3F, 0x48, 0x18, 0x99, 0x08, 0x93, 0xA6, 0xAD, 0xED, + 0x48, 0xDA, 0xFE, 0x8F, 0xF2, 0xEF, 0x4C, 0x8E, 0xFE, 0x6C, 0xC2, 0x46, + 0xE3, 0xEC, 0x73, 0xD9, 0x77, 0x83, 0x6F, 0xE9, 0x5C, 0xA6, 0xB6, 0x60, + 0x10, 0xD8, 0x03, 0x0A, 0xD0, 0xC7, 0xB4, 0x7D, 0xFE, 0x85, 0xDA, 0xE1, + 0xF3, 0x85, 0xB4, 0xC3, 0x67, 0xAA, 0xFB, 0x6C, 0x84, 0xB8, 0x7E, 0xDA, + 0x13, 0x95, 0x18, 0x2A, 0x0E, 0xF1, 0xAC, 0x3F, 0x93, 0x50, 0x20, 0xF6, + 0x84, 0x0C, 0xAE, 0x0A, 0x6C, 0x42, 0xDE, 0xA5, 0x97, 0x9C, 0x74, 0xEC, + 0xED, 0xBE, 0x11, 0xE2, 0xBE, 0x57, 0x02, 0x3E, 0xDB, 0xD1, 0x1C, 0xC4, + 0x4B, 0xB7, 0x4B, 0xA7, 0xB9, 0x74, 0xC8, 0xC2, 0x6C, 0x39, 0x79, 0x6B, + 0x17, 0x21, 0xCB, 0xDB, 0xB3, 0x71, 0xB1, 0xD2, 0xA4, 0x06, 0x11, 0x98, + 0x6D, 0xE8, 0x85, 0x30, 0x1A, 0x46, 0x47, 0x7D, 0x87, 0x7D, 0x03, 0xBD, + 0x64, 0xCD, 0x7C, 0x5C, 0x54, 0xBB, 0xE3, 0x2A, 0x3C, 0x4C, 0xB0, 0xAF, + 0x6A, 0xBF, 0x18, 0x40, 0xC4, 0x3A, 0x9E, 0x84, 0x01, 0x56, 0xB0, 0xF5, + 0xD4, 0xE0, 0xD2, 0x95, 0x7E, 0x85, 0x34, 0xDF, 0xC3, 0xAB, 0xFE, 0x5C, + 0xA6, 0x8C, 0xA5, 0x96, 0xAB, 0x51, 0x53, 0xEE, 0x80, 0x18, 0x8A, 0x87, + 0x61, 0x91, 0x3E, 0x93, 0x01, 0x85, 0xE8, 0x85, 0x84, 0x7A, 0x12, 0x29, + 0x58, 0xFF, 0xE7, 0xE0, 0x3D, 0x97, 0x1C, 0x94, 0x29, 0xFB, 0xB2, 0x1B, + 0xEB, 0xD1, 0x20, 0xAA, 0x52, 0x12, 0xA0, 0x0B, 0xC5, 0xB4, 0xBF, 0x4C, + 0xFB, 0xBF, 0x98, 0xCF, 0xD9, 0xE7, 0x96, 0xEE, 0xD2, 0x26, 0x0B, 0x04, + 0x0E, 0xE5, 0x37, 0xA5, 0xF7, 0x36, 0x35, 0x97, 0xFA, 0x35, 0xD2, 0xC0, + 0xA2, 0x5B, 0x2B, 0x25, 0x29, 0xF0, 0x18, 0x3B, 0xE6, 0x0C, 0xE4, 0x52, + 0x7F, 0x48, 0x15, 0xD0, 0x68, 0x3D, 0x6F, 0x68, 0x32, 0xB8, 0xF1, 0xCD, + 0x96, 0x6D, 0x48, 0x33, 0xEA, 0x57, 0x84, 0xCE, 0x1C, 0x93, 0x22, 0x8E, + 0xC0, 0x6E, 0x76, 0x0D, 0x87, 0x2E, 0x36, 0xCE, 0x2C, 0x65, 0xBD, 0xA9, + 0xBF, 0xB4, 0x88, 0xAB, 0x83, 0x74, 0x15, 0xC4, 0xDF, 0xA7, 0x4B, 0x7D, + 0xB3, 0xB8, 0x72, 0x8C, 0x57, 0x9C, 0xE8, 0xBB, 0xB3, 0x1D, 0xF1, 0xE4, + 0x11, 0x64, 0xEA, 0xB4, 0x17, 0xCF, 0x1F, 0xBF, 0x1D, 0xC0, 0xDE, 0x70, + 0xF8, 0xAF, 0x39, 0xFF, 0x30, 0x41, 0x7F, 0x5F, 0x21, 0x06, 0x09, 0x08, + 0x95, 0x1E, 0x6D, 0x47, 0x01, 0xF5, 0xA0, 0xD5, 0xF7, 0xA9, 0xF3, 0xB4, + 0x63, 0x8D, 0xB3, 0xBC, 0x0F, 0xC8, 0x83, 0xAD, 0x22, 0x31, 0x74, 0x1C, + 0xD7, 0xAB, 0x23, 0x74, 0x89, 0xBF, 0x3D, 0xBD, 0xF5, 0x47, 0xAB, 0x82, + 0x75, 0x81, 0x3D, 0x8C, 0xC9, 0x6E, 0x6F, 0x36, 0x45, 0x9E, 0x36, 0x1D, + 0xE2, 0xF8, 0x92, 0x27, 0xAB, 0x4B, 0x5B, 0xDD, 0xC2, 0x55, 0x3A, 0x1C, + 0x3E, 0xE4, 0xFA, 0xEF, 0xE5, 0xCB, 0xE6, 0x6A, 0xB3, 0xF8, 0x6E, 0x9C, + 0x65, 0x08, 0x92, 0x78, 0xF9, 0x27, 0x34, 0x40, 0xDE, 0xF3, 0xA3, 0xDC, + 0x2B, 0xD8, 0x68, 0x15, 0x61, 0xF1, 0x1E, 0xBA, 0xA4, 0x7A, 0x74, 0x13, + 0x48, 0x84, 0xA5, 0x79, 0x4F, 0xDE, 0xBD, 0xFE, 0xF1, 0xBE, 0xB1, 0x1B, + 0xE9, 0x85, 0x7B, 0x5D, 0xFA, 0x34, 0x2D, 0x53, 0xA7, 0xEE, 0x71, 0x9D, + 0xC8, 0xF6, 0xFA, 0xB1, 0x5D, 0xAD, 0x06, 0x76, 0x2C, 0xE5, 0x04, 0x3F, + 0x0F, 0x5F, 0x19, 0xD6, 0x56, 0x7B, 0xAB, 0xFB, 0x5E, 0xFB, 0x2B, 0x0A, + 0x52, 0xAB, 0x5B, 0xBB, 0x71, 0x69, 0x7C, 0xB4, 0x57, 0x29, 0x4D, 0x08, + 0x86, 0x2B, 0x9E, 0xFE, 0xAC, 0x9F, 0x73, 0xBE, 0x93, 0x23, 0x2A, 0xC4, + 0x7B, 0xEE, 0xF2, 0x9A, 0x71, 0xC1, 0x65, 0x14, 0xD6, 0xDC, 0x8A, 0x7E, + 0x32, 0x28, 0x68, 0xB7, 0xD0, 0x0E, 0xE7, 0x29, 0xBC, 0xD4, 0x54, 0x60, + 0xB2, 0xC4, 0x7B, 0x3A, 0x92, 0x1B, 0xF0, 0x61, 0xDB, 0x01, 0x13, 0xE7, + 0xF2, 0x9A, 0xFE, 0xB8, 0x8E, 0x3A, 0xC8, 0x09, 0x9A, 0x3E, 0x70, 0x05, + 0x60, 0x81, 0x64, 0xDF, 0x84, 0xEF, 0x29, 0x92, 0xEF, 0xA4, 0x32, 0x53, + 0x27, 0x52, 0xE2, 0x7B, 0x81, 0x0B, 0x0A, 0x83, 0xE8, 0x73, 0xF2, 0x36, + 0xC7, 0xA4, 0xDC, 0xBF, 0xB6, 0xBB, 0x60, 0x61, 0x7B, 0xD0, 0x3B, 0xA8, + 0xB1, 0x5A, 0x0C, 0x46, 0xEB, 0x68, 0x68, 0x1F, 0xA9, 0x70, 0xA9, 0x5A, + 0xBB, 0xAF, 0xDD, 0xFD, 0xEC, 0xEB, 0xB8, 0x75, 0x8D, 0x2A, 0x82, 0xD2, + 0xF0, 0xA6, 0x75, 0xB7, 0xEF, 0x33, 0x5D, 0x6B, 0xA2, 0x6A, 0x6A, 0xA1, + 0x22, 0x7A, 0x08, 0xA5, 0x7B, 0xAD, 0x52, 0x5E, 0xB6, 0x0E, 0x5F, 0xF6, + 0xD6, 0xB9, 0xD5, 0x30, 0x4C, 0xFB, 0xF1, 0x80, 0xA2, 0x24, 0x4A, 0xA0, + 0x9E, 0x61, 0xAB, 0xC2, 0xD3, 0x99, 0x4C, 0xB4, 0xBE, 0xC1, 0x64, 0xD1, + 0xF2, 0xF1, 0x61, 0xB8, 0x74, 0x45, 0x0C, 0xAD, 0xD0, 0x38, 0x9A, 0x7B, + 0xB7, 0x1F, 0x59, 0x20, 0x46, 0x85, 0x63, 0x8C, 0xA2, 0x25, 0xB4, 0x81, + 0x8C, 0x11, 0x13, 0xE7, 0x3A, 0x4A, 0x24, 0x94, 0x6F, 0x34, 0x0E, 0x13, + 0x8E, 0x5A, 0xED, 0xC5, 0x4E, 0x9E, 0x2C, 0x2E, 0xEF, 0x4C, 0x00, 0xD6, + 0xB4, 0x96, 0xE4, 0x1F, 0xCF, 0x66, 0x4B, 0xA0, 0xC2, 0xD3, 0xF3, 0x18, + 0x05, 0xBA, 0xAB, 0x7E, 0x32, 0xF7, 0x54, 0xBA, 0x64, 0xE7, 0xC3, 0x44, + 0x95, 0x4C, 0x62, 0xF9, 0x6F, 0x50, 0x27, 0xEB, 0xB1, 0xFE, 0xD8, 0xBD, + 0xD8, 0x44, 0x4D, 0x48, 0xC4, 0xBC, 0xB5, 0x4E, 0xF7, 0x2B, 0x3F, 0xC8, + 0x15, 0x34, 0xC7, 0x8E, 0x83, 0x91, 0xFD, 0x73, 0xBA, 0x27, 0x4D, 0xA3, + 0x4B, 0x4A, 0x3E, 0x13, 0xCE, 0x7B, 0xBF, 0xDD, 0x2F, 0x25, 0x22, 0xEA, + 0xC7, 0x24, 0x41, 0x0B, 0x23, 0xFD, 0x1F, 0x2E, 0x57, 0x19, 0xF8, 0xD6, + 0x87, 0x3E, 0x3A, 0xE8, 0x57, 0x00, 0xB7, 0xF2, 0xC2, 0xC5, 0x79, 0x5B, + 0x4D, 0x7F, 0xE5, 0xF8, 0x10, 0x8D, 0xFC, 0x02, 0xC8, 0x0D, 0xDA, 0x9A, + 0x85, 0xF0, 0xBD, 0xA2, 0x8D, 0x5A, 0x6A, 0x94, 0xB6, 0x96, 0x2B, 0xD4, + 0x4E, 0x77, 0x2F, 0x4F, 0x94, 0xAB, 0xD1, 0x4F, 0xDD, 0xA7, 0x53, 0x65, + 0xB1, 0x6B, 0x95, 0x4C, 0x3F, 0x41, 0x50, 0x62, 0x49, 0x54, 0x2D, 0x1D, + 0x4A, 0xE5, 0x45, 0x19, 0xBF, 0x37, 0xE8, 0x25, 0xE1, 0xF6, 0x77, 0x2B, + 0x59, 0x17, 0x72, 0x58, 0xE6, 0x42, 0x39, 0x9F, 0x8D, 0xB1, 0x62, 0x42, + 0xE1, 0x31, 0x4C, 0x2F, 0x00, 0xCB, 0x1F, 0xFB, 0x30, 0x1C, 0x8A, 0x4C, + 0x2A, 0xF9, 0x6E, 0x63, 0x90, 0x2B, 0x41, 0xC5, 0x3C, 0x87, 0xB6, 0x3F, + 0x83, 0xC8, 0x16, 0x9D, 0x9E, 0x15, 0x8D, 0x8F, 0xB7, 0x75, 0xB7, 0x1E, + 0x8B, 0xA5, 0x08, 0xBE, 0x26, 0x74, 0xF0, 0x8D, 0x10, 0x0C, 0x4C, 0x2E, + 0x33, 0x79, 0x85, 0x30, 0xF2, 0xC3, 0xD1, 0x3F, 0x11, 0x4A, 0x02, 0x35, + 0x33, 0x67, 0xAE, 0x38, 0x01, 0x9A, 0x88, 0xC9, 0x08, 0x50, 0x9C, 0x70, + 0x66, 0x4B, 0xB8, 0xF7, 0x0D, 0x83, 0xA8, 0x0C, 0x04, 0xD2, 0xFD, 0x7E, + 0x77, 0xAE, 0xCD, 0xAC, 0x47, 0xBD, 0x6E, 0x53, 0x5D, 0xC5, 0xB9, 0xB3, + 0x1A, 0x66, 0x7D, 0x27, 0xE4, 0x14, 0xA3, 0xF2, 0xF2, 0x98, 0xD3, 0x37, + 0x70, 0xD1, 0xDE, 0x27, 0x72, 0x2B, 0x2A, 0xA6, 0xB1, 0x1B, 0x50, 0x8F, + 0x82, 0xB1, 0x86, 0x26, 0x0A, 0x30, 0x76, 0xA3, 0x82, 0x89, 0x22, 0x48, + 0x5E, 0xA2, 0xCF, 0x3B, 0xC7, 0x1E, 0x0D, 0x41, 0x10, 0x6F, 0xF2, 0x53, + 0x17, 0x42, 0x7F, 0x83, 0xB3, 0xC9, 0x39, 0x6B, 0x69, 0x18, 0xF3, 0x41, + 0x5A, 0x97, 0xD9, 0x54, 0x1A, 0x70, 0x5F, 0x8C, 0x94, 0xB0, 0x83, 0x32, + 0xF4, 0x5B, 0x3A, 0x19, 0xD1, 0x8E, 0xF1, 0x3B, 0x4B, 0x07, 0x58, 0x16, + 0x0F, 0x2F, 0xB6, 0x55, 0xDC, 0xCD, 0x76, 0xDC, 0xF7, 0x0B, 0xE5, 0xAC, + 0x55, 0x03, 0x84, 0xAE, 0xD0, 0xEE, 0x9D, 0x74, 0x92, 0xA1, 0xA3, 0x97, + 0x3B, 0x22, 0x82, 0xDF, 0x93, 0x26, 0x2A, 0x2E, 0xD2, 0xF9, 0xF3, 0x24, + 0x5F, 0x6B, 0x13, 0x50, 0x54, 0x7C, 0x75, 0xF4, 0x34, 0x2C, 0x56, 0xA7, + 0x03, 0x8E, 0xAA, 0xBE, 0xA5, 0x0C, 0xD4, 0xAA, 0x15, 0x8C, 0xEC, 0x1E, + 0xB1, 0xCA, 0x67, 0x8D, 0x91, 0x0C, 0xB3, 0x09, 0xC4, 0xE5, 0xBE, 0x9F, + 0xFA, 0x35, 0x21, 0x0C, 0x10, 0xDD, 0x5A, 0xB1, 0x40, 0xE9, 0xC1, 0x53, + 0xD1, 0x84, 0x13, 0x7F, 0x36, 0x49, 0xEC, 0xAF, 0x63, 0x46, 0x72, 0x13, + 0xD0, 0x35, 0x90, 0xFB, 0xF9, 0x95, 0xCB, 0xF3, 0xB1, 0x24, 0x3C, 0x3A, + 0xC2, 0x20, 0x89, 0x27, 0xCB, 0x28, 0x92, 0xCB, 0x1F, 0x6F, 0x0F, 0x05, + 0x55, 0x8A, 0x50, 0xE9, 0x9E, 0x54, 0x69, 0x8A, 0x74, 0xC2, 0x98, 0x01, + 0x30, 0x7B, 0x25, 0x61, 0xF3, 0xF5, 0x60, 0xFF, 0x1A, 0xFA, 0x3E, 0x11, + 0xDB, 0xCC, 0x9D, 0x17, 0xCC, 0x42, 0xED, 0x10, 0xE2, 0x23, 0x0B, 0x7B, + 0x9E, 0x9B, 0x7A, 0x52, 0xD5, 0xFE, 0x51, 0xD3, 0x2E, 0x14, 0xF5, 0x58, + 0xF6, 0x9E, 0x0D, 0x2B, 0xA6, 0x4F, 0x51, 0xB2, 0xE4, 0xC7, 0x96, 0x83, + 0x26, 0xC4, 0xD9, 0x86, 0xF8, 0x7B, 0x65, 0xA2, 0x93, 0xF1, 0xEB, 0x00, + 0xBA, 0x20, 0x23, 0x9C, 0x59, 0xBC, 0x79, 0x95, 0xFF, 0xA7, 0xD9, 0xAF, + 0x3C, 0xF7, 0xBF, 0x63, 0x15, 0xA1, 0xDD, 0xFA, 0x41, 0x39, 0xE4, 0xAA, + 0xBA, 0xB5, 0x9E, 0x14, 0x86, 0x9D, 0x05, 0x56, 0x81, 0x6A, 0x22, 0x7C, + 0x3D, 0x76, 0x89, 0x06, 0xC4, 0x61, 0xE4, 0x39, 0xF0, 0x4E, 0x66, 0x95, + 0x1B, 0x09, 0x0D, 0x74, 0x5C, 0x6C, 0x03, 0x4E, 0xCA, 0xD5, 0x32, 0xD0, + 0x03, 0x2F, 0x09, 0xB9, 0x12, 0x32, 0x29, 0x44, 0x38, 0x97, 0x7E, 0xC0, + 0x5F, 0x66, 0x74, 0x3B, 0xB9, 0x98, 0x53, 0x94, 0x83, 0x7C, 0xAA, 0xC7, + 0xCE, 0x74, 0xC8, 0x80, 0x97, 0xB3, 0x80, 0x97, 0x7A, 0x66, 0xE5, 0xC9, + 0x50, 0x20, 0xC9, 0xD1, 0x84, 0xDC, 0xDC, 0x5C, 0x07, 0x43, 0x60, 0x97, + 0xD9, 0xA5, 0x6E, 0x8A, 0x7B, 0x98, 0xDB, 0x1C, 0x75, 0xE5, 0x58, 0x7F, + 0x61, 0x68, 0xFC, 0xEE, 0x32, 0x58, 0x6C, 0xF7, 0xA9, 0x0B, 0x56, 0x41, + 0x2C, 0x65, 0x16, 0xBD, 0xE1, 0xD6, 0xC1, 0xA0, 0xE2, 0x10, 0x61, 0xDB, + 0xC2, 0x8D, 0x5A, 0x9A, 0x70, 0x95, 0xD8, 0x72, 0xB0, 0x41, 0x88, 0x40, + 0xFD, 0x7D, 0xDE, 0x9E, 0x07, 0x79, 0xBA, 0xFD, 0x9E, 0xF9, 0x67, 0xB7, + 0x07, 0x13, 0x27, 0x13, 0xDC, 0xD9, 0x5A, 0xAD, 0x0D, 0x42, 0xA9, 0x7B, + 0x52, 0x17, 0x85, 0x49, 0x23, 0xE0, 0x10, 0x7D, 0xB3, 0xF5, 0x36, 0x21, + 0x2F, 0x01, 0xE9, 0x7B, 0x32, 0x75, 0x3C, 0xEC, 0x83, 0x4C, 0xA3, 0x56, + 0xD6, 0x4F, 0x84, 0xE2, 0xF5, 0xF9, 0x7D, 0x0D, 0x12, 0x0A, 0x7D, 0xA2, + 0xAA, 0x19, 0xF4, 0x53, 0x4C, 0x2F, 0x4A, 0xB4, 0x03, 0x22, 0x2E, 0xBE, + 0x6D, 0x1F, 0x2A, 0xD1, 0x98, 0x81, 0xC1, 0xEF, 0xEB, 0x0E, 0xD6, 0x8B, + 0x27, 0x47, 0x73, 0x61, 0x78, 0x3D, 0xBB, 0xE1, 0x92, 0xAA, 0x07, 0xF5, + 0x9F, 0x62, 0x51, 0xFB, 0xDC, 0x1C, 0x74, 0x6B, 0x9E, 0x0E, 0xDF, 0xCF, + 0xB6, 0x71, 0x8A, 0x5E, 0x8E, 0x83, 0x00, 0xEA, 0x43, 0xF8, 0x48, 0xCF, + 0xCA, 0x71, 0x3F, 0x85, 0xB8, 0x00, 0xAE, 0x5D, 0xC1, 0xD9, 0x3B, 0xAE, + 0x60, 0xB8, 0x0F, 0xE3, 0xE6, 0xDB, 0x23, 0x10, 0x93, 0x6C, 0x04, 0xE4, + 0xFB, 0x36, 0x24, 0xF4, 0xB8, 0xE0, 0xA0, 0xBF, 0x8D, 0xA6, 0x34, 0x46, + 0x0B, 0x37, 0x9C, 0x67, 0xE7, 0x5A, 0x65, 0xE7, 0x2D, 0x64, 0x4B, 0x5D, + 0x83, 0x86, 0xEC, 0xA2, 0x64, 0x3C, 0xB7, 0x78, 0xEE, 0xD3, 0x29, 0xAF, + 0x86, 0x0C, 0xB4, 0xDF, 0x69, 0x8F, 0xAF, 0x33, 0x4B, 0x52, 0x82, 0xCF, + 0x2A, 0xB2, 0x5B, 0x5F, 0x18, 0x26, 0xA7, 0x23, 0x1F, 0x8B, 0x3C, 0x90, + 0xCD, 0x75, 0x6B, 0x07, 0x4A, 0x1F, 0xF3, 0x60, 0xB6, 0x7D, 0xE0, 0x0A, + 0xBD, 0x99, 0x8B, 0xEA, 0x3F, 0xF9, 0x03, 0x10, 0x0B, 0x36, 0xBD, 0xBC, + 0x5A, 0xA3, 0x86, 0x37, 0x8F, 0x8D, 0x74, 0x44, 0x39, 0xAB, 0xC1, 0x35, + 0x30, 0x92, 0xB3, 0xB5, 0xDA, 0xD4, 0xC6, 0x05, 0x3E, 0xB6, 0xAD, 0x09, + 0x19, 0x27, 0x89, 0x25, 0xFB, 0x17, 0x0C, 0xE4, 0x8A, 0xE1, 0x0A, 0xF1, + 0xBA, 0x3A, 0x88, 0x6D, 0x0F, 0xF7, 0x8A, 0x9F, 0xE0, 0x96, 0x74, 0x19, + 0xFF, 0xC4, 0x09, 0xFB, 0xA3, 0x4F, 0x2B, 0x50, 0xCD, 0x7F, 0x1A, 0x71, + 0x17, 0x59, 0x81, 0xC1, 0x1A, 0xA4, 0x0A, 0x50, 0xB0, 0x82, 0x5F, 0x7C, + 0x26, 0xD7, 0xEB, 0xBC, 0x91, 0xF2, 0x05, 0xEF, 0xCC, 0x04, 0xED, 0x61, + 0xC5, 0x46, 0xF7, 0x0E, 0x0D, 0xBB, 0x3B, 0x60, 0x27, 0x95, 0x40, 0xCD, + 0xA8, 0xE1, 0xCA, 0xEA, 0x9F, 0x70, 0x06, 0x23, 0xDA, 0x1D, 0xDB, 0x93, + 0x3C, 0x9F, 0x7A, 0x95, 0x34, 0x63, 0xB1, 0xEF, 0x8C, 0xB5, 0x24, 0xD5, + 0x0B, 0xA3, 0xBA, 0x64, 0x76, 0x7B, 0x17, 0xD7, 0xE9, 0x19, 0xF4, 0xBE, + 0xCA, 0x65, 0x7D, 0x39, 0x54, 0x07, 0x2E, 0xE4, 0xD3, 0x2E, 0x95, 0x32, + 0x9F, 0x76, 0x1B, 0x89, 0x69, 0xD3, 0x4C, 0x1E, 0x44, 0xBC, 0x81, 0xCA, + 0xB8, 0xD2, 0xDC, 0x6D, 0x46, 0xD3, 0xC9, 0xF8, 0xA2, 0xD4, 0x30, 0xD9, + 0xF4, 0x30, 0xA0, 0x36, 0xBE, 0x92, 0x2C, 0x65, 0x6B, 0x7F, 0x7A, 0x6B, + 0xB7, 0x4E, 0xF1, 0x69, 0xDD, 0x02, 0x44, 0xCB, 0xB5, 0x36, 0x20, 0x23, + 0x21, 0xA4, 0x48, 0x15, 0xA0, 0x53, 0xAE, 0x98, 0x93, 0x6C, 0x74, 0xFA, + 0xCD, 0x9C, 0x42, 0x6B, 0x59, 0xC8, 0x3B, 0x2F, 0xDA, 0xFF, 0xEA, 0x6D, + 0xBD, 0xF2, 0xBA, 0xA4, 0xFA, 0x8E, 0xCC, 0x23, 0x34, 0x9E, 0x49, 0x78, + 0x83, 0x8E, 0x02, 0xB4, 0xEB, 0x13, 0x27, 0x06, 0xB3, 0x27, 0xD3, 0x14, + 0x44, 0x2E, 0x55, 0x16, 0x73, 0x7C, 0x03, 0x47, 0x1F, 0x77, 0xC7, 0x6A, + 0xF6, 0x79, 0xA9, 0xE9, 0x29, 0xEA, 0x84, 0x9D, 0x57, 0x16, 0xF3, 0x76, + 0x9F, 0x6B, 0xD6, 0x5D, 0xB2, 0xC8, 0x46, 0x0C, 0x56, 0x4B, 0x5E, 0x04, + 0xA3, 0xA6, 0xBB, 0x37, 0x81, 0x7D, 0xC8, 0x49, 0xF4, 0xC1, 0x0A, 0x64, + 0x13, 0x67, 0x1A, 0x5D, 0xB5, 0x16, 0x74, 0xB1, 0x28, 0x12, 0xE1, 0xF6, + 0xED, 0x62, 0x23, 0x4B, 0x08, 0x77, 0x7C, 0x30, 0xAF, 0x8D, 0xAA, 0x91, + 0xEF, 0xE0, 0x88, 0x4F, 0xA1, 0xB9, 0x44, 0xA8, 0x15, 0x7B, 0xC3, 0xE9, + 0xA5, 0x60, 0x11, 0x53, 0x54, 0x18, 0xFF, 0xB2, 0x48, 0x8C, 0xC2, 0x03, + 0x9D, 0x0C, 0x82, 0xB7, 0x3D, 0x0B, 0x7D, 0xAA, 0xEA, 0x50, 0x00, 0x53, + 0x41, 0xF5, 0xF7, 0xDA, 0xF4, 0x61, 0x5B, 0x8C, 0x89, 0x63, 0x89, 0xFB, + 0x97, 0xCA, 0xCE, 0xAE, 0x80, 0xB9, 0x6C, 0x46, 0xE1, 0x31, 0x8A, 0xF9, + 0xA9, 0xFE, 0x3C, 0xC5, 0xB0, 0xE3, 0x04, 0xA8, 0x14, 0x06, 0x54, 0x52, + 0x3E, 0x9E, 0x5A, 0x47, 0x67, 0x43, 0x2F, 0x09, 0xAF, 0xB0, 0x67, 0x69, + 0x87, 0x1F, 0xEF, 0xDF, 0x91, 0xC4, 0x48, 0x21, 0x41, 0xB0, 0xC5, 0x91, + 0x8F, 0xE3, 0x48, 0xA7, 0x4E, 0x3A, 0xBF, 0x22, 0x3F, 0xCC, 0xAE, 0x70, + 0x94, 0x8A, 0xAB, 0x34, 0xFC, 0x1A, 0x83, 0x8B, 0xAE, 0x1B, 0xA7, 0xC7, + 0x99, 0x7C, 0x45, 0x60, 0x6F, 0x81, 0x07, 0xE2, 0x95, 0x7D, 0xE1, 0x55, + 0x79, 0xC3, 0xA0, 0x0D, 0xBA, 0x9F, 0x22, 0x5A, 0x44, 0x79, 0x6F, 0x06, + 0x57, 0x89, 0x06, 0x97, 0x7F, 0x67, 0x71, 0xBB, 0xEF, 0xFF, 0xE4, 0x3E, + 0x9F, 0x63, 0x36, 0xFD, 0x74, 0xDB, 0x90, 0x49, 0x0D, 0xA5, 0xF8, 0xAD, + 0x60, 0x78, 0x08, 0x05, 0x8C, 0xFF, 0xF5, 0xD6, 0x5C, 0xEA, 0x1E, 0x7E, + 0x3A, 0x7B, 0xDE, 0xD7, 0x7F, 0xE8, 0x66, 0xFD, 0x53, 0x3F, 0x98, 0x2C, + 0x2B, 0xBC, 0x5D, 0x36, 0x40, 0xAF, 0xCC, 0xFC, 0x08, 0x00, 0x2D, 0x39, + 0xA6, 0x8B, 0x03, 0xFC, 0x3F, 0x94, 0x72, 0x34, 0xC0, 0x7E, 0xB8, 0x68, + 0x83, 0xB6, 0x5B, 0x90, 0x15, 0x32, 0x65, 0x7E, 0x36, 0x71, 0x4B, 0x15, + 0x54, 0x7E, 0x0D, 0x71, 0x92, 0xB7, 0xF9, 0x35, 0x90, 0x28, 0x89, 0x8D, + 0x29, 0x47, 0x78, 0xBF, 0xDC, 0x4F, 0x15, 0x51, 0x23, 0xBF, 0x7F, 0xBB, + 0x0A, 0xAB, 0xD6, 0xC3, 0xAF, 0x50, 0xE7, 0x58, 0x8E, 0x31, 0x93, 0x47, + 0xAF, 0xF8, 0xCE, 0xC6, 0xA8, 0x8F, 0xC2, 0x89, 0xE9, 0x69, 0x23, 0x5A, + 0xDA, 0x11, 0x6F, 0x2A, 0x74, 0x17, 0xE2, 0x9D, 0x53, 0x36, 0x14, 0xE4, + 0x6E, 0xCF, 0xC5, 0x26, 0x06, 0x96, 0x3A, 0xC4, 0xED, 0x1A, 0x8B, 0xF6, + 0xF4, 0x7E, 0x9D, 0x6D, 0x24, 0x81, 0x23, 0xCA, 0xAB, 0x07, 0xB4, 0x11, + 0x8E, 0x14, 0xCE, 0x63, 0x82, 0x6C, 0xF4, 0x64, 0x02, 0xA3, 0x32, 0x81, + 0x07, 0x82, 0xBC, 0x1D, 0xB0, 0x04, 0xB4, 0x4C, 0xD8, 0x83, 0x48, 0xFE, + 0xA5, 0x89, 0xD4, 0xF6, 0xBD, 0xB6, 0xBF, 0xB6, 0x0D, 0xFE, 0x7B, 0x2B, + 0x29, 0x62, 0x91, 0xE6, 0x13, 0xA8, 0x4C, 0xC2, 0xBB, 0x01, 0x27, 0x0C, + 0xF4, 0x91, 0xC8, 0xD2, 0x25, 0xF8, 0x2C, 0xE9, 0xE9, 0x25, 0x59, 0x92, + 0xB0, 0xEB, 0x19, 0xBF, 0xD1, 0x02, 0x9E, 0x08, 0x44, 0xAB, 0x54, 0xBE, + 0x54, 0x1E, 0xBB, 0x17, 0x29, 0xD8, 0xDF, 0x65, 0x79, 0x01, 0x0B, 0x5D, + 0x32, 0xB2, 0xA5, 0x32, 0x6E, 0x7B, 0x0F, 0x7E, 0xB5, 0x45, 0x52, 0xD3, + 0x12, 0x11, 0xE0, 0xDD, 0x82, 0x24, 0x70, 0xD1, 0x9C, 0x46, 0xBB, 0x42, + 0xB0, 0x35, 0x09, 0x5B, 0xAC, 0xC9, 0x99, 0x21, 0x64, 0xF9, 0xD1, 0x0E, + 0xCA, 0xB0, 0xB8, 0x21, 0x5B, 0xF0, 0xA0, 0x4D, 0x64, 0x42, 0xCD, 0x00, + 0x40, 0xB6, 0xD7, 0x32, 0x0B, 0x49, 0x8C, 0xB1, 0x34, 0x81, 0x97, 0x32, + 0xA9, 0xB8, 0x69, 0xC4, 0x45, 0x5E, 0x06, 0xD1, 0x76, 0x6F, 0xB2, 0x81, + 0x8F, 0x35, 0xD6, 0xDD, 0x64, 0x3D, 0x38, 0xA3, 0x17, 0x05, 0xAF, 0x57, + 0x1C, 0xDD, 0xA1, 0x73, 0x39, 0x6E, 0x1E, 0xFA, 0x8A, 0x5C, 0xEC, 0x92, + 0x83, 0xB7, 0xC0, 0x51, 0xA4, 0x13, 0xE2, 0xCA, 0x2F, 0x38, 0x11, 0xFE, + 0x81, 0x88, 0x01, 0x06, 0x98, 0x5E, 0xCF, 0xFD, 0xC7, 0x69, 0x1D, 0xE5, + 0xEF, 0x86, 0x52, 0x46, 0x74, 0x3F, 0xCF, 0x09, 0xBD, 0xED, 0x03, 0x45, + 0x7C, 0x8A, 0xBB, 0x25, 0x2B, 0x4C, 0x0F, 0xE7, 0xC9, 0x04, 0x24, 0x6C, + 0xDE, 0xF2, 0x79, 0xDA, 0xC9, 0xAD, 0x2A, 0xF9, 0xDC, 0x6B, 0xAF, 0x54, + 0xDC, 0x60, 0x7F, 0x9F, 0x20, 0x65, 0x03, 0x26, 0xAD, 0xE7, 0x94, 0xCB, + 0xDA, 0x7E, 0x9A, 0xC5, 0x48, 0xD2, 0x9E, 0x4D, 0x66, 0x0D, 0x0C, 0xEA, + 0x90, 0x7A, 0xCB, 0x3F, 0x98, 0x18, 0x51, 0xFD, 0xC8, 0x29, 0x35, 0xF6, + 0xCD, 0x7D, 0xDB, 0x28, 0xD2, 0x6D, 0x7F, 0xC9, 0x31, 0x0A, 0x5D, 0x78, + 0xE9, 0xFE, 0x2D, 0x66, 0x52, 0x23, 0xC4, 0xD2, 0x1B, 0xFC, 0x6B, 0x50, + 0xD1, 0xF3, 0xE5, 0x50, 0xB0, 0x14, 0xDD, 0xFF, 0x8A, 0xAE, 0x09, 0x4D, + 0x13, 0x6D, 0xEF, 0xBA, 0xB1, 0x79, 0xD9, 0x0B, 0x21, 0x6D, 0x2C, 0xA0, + 0x85, 0x6B, 0x49, 0x6B, 0x87, 0x13, 0x20, 0x03, 0x5F, 0xAC, 0xAF, 0xF1, + 0xFC, 0x28, 0x0D, 0x37, 0x97, 0x37, 0xC7, 0x7A, 0x97, 0xBE, 0x50, 0x6E, + 0x41, 0xB0, 0xEA, 0xA7, 0xD7, 0x44, 0x23, 0x62, 0xD9, 0xA8, 0x23, 0x12, + 0x5D, 0x37, 0xDF, 0xC7, 0x6B, 0x43, 0xE6, 0xA8, 0x50, 0xF5, 0x46, 0x03, + 0x53, 0x24, 0x4E, 0x82, 0x84, 0xB5, 0x25, 0xD6, 0x9F, 0x51, 0x96, 0x1C, + 0xCE, 0xA1, 0x93, 0x04, 0xAA, 0x25, 0x54, 0xA9, 0x66, 0xF4, 0xBE, 0xBD, + 0x9D, 0xF5, 0xEA, 0xE5, 0x24, 0x52, 0x26, 0xDC, 0xBF, 0x50, 0x4B, 0x2C, + 0xA2, 0x65, 0x1B, 0x5B, 0x53, 0x9F, 0xFD, 0xA2, 0x81, 0x0C, 0xDD, 0xAF, + 0xD1, 0xF6, 0xB8, 0x7D, 0x88, 0xD2, 0xBB, 0x85, 0xD6, 0x58, 0x11, 0x42, + 0x57, 0x1E, 0x25, 0x50, 0xD9, 0xD1, 0x0F, 0x9A, 0x54, 0xB8, 0x29, 0x1F, + 0x23, 0x99, 0x15, 0xBF, 0xF6, 0xED, 0x47, 0xC8, 0x5F, 0x72, 0xC0, 0x89, + 0xD5, 0xD3, 0x00, 0xFD, 0x60, 0x4D, 0x42, 0xFB, 0x61, 0xE6, 0x4D, 0xA1, + 0xF8, 0x78, 0x9D, 0xDF, 0x9D, 0x06, 0x36, 0xC4, 0x0A, 0x27, 0x45, 0xB1, + 0x6F, 0xA8, 0xE3, 0x94, 0x0E, 0x33, 0x5A, 0x1F, 0xE8, 0x3A, 0x7F, 0xB7, + 0x09, 0xC6, 0x91, 0xDF, 0xF3, 0x6E, 0x12, 0x69, 0xE3, 0xCF, 0xDA, 0xF1, + 0x12, 0x5A, 0x6B, 0xDE, 0x65, 0x45, 0x0A, 0xD9, 0xE7, 0x24, 0x1C, 0xD4, + 0x09, 0x7D, 0x1E, 0x15, 0xBD, 0xB8, 0x44, 0x79, 0xF3, 0x84, 0x5E, 0x51, + 0xBF, 0x2C, 0x15, 0xA4, 0x02, 0x23, 0x21, 0x22, 0x0D, 0x2B, 0xA5, 0xB7, + 0xFC, 0x3B, 0xE4, 0x7A, 0xAA, 0x0C, 0x8E, 0x5C, 0x55, 0xE2, 0x34, 0xE6, + 0x02, 0xD1, 0x7F, 0x3D, 0x25, 0x35, 0x99, 0xC0, 0xDD, 0x57, 0x0C, 0x62, + 0x5C, 0xDB, 0x80, 0x06, 0x18, 0x31, 0x3A, 0x0E, 0xCC, 0x4A, 0x3B, 0x4C, + 0x8D, 0xD9, 0x0E, 0x98, 0x1C, 0xE8, 0x27, 0x75, 0x53, 0xBC, 0x44, 0xAC, + 0x26, 0xCC, 0x62, 0x21, 0x45, 0x5D, 0x24, 0x70, 0xB3, 0x70, 0x82, 0xFA, + 0x54, 0xB5, 0x1A, 0x08, 0x82, 0x30, 0x25, 0x01, 0x49, 0x00, 0x76, 0xC6, + 0xFB, 0xED, 0x14, 0x23, 0x15, 0x3D, 0x5A, 0x85, 0xB7, 0x88, 0x91, 0x2E, + 0x04, 0xB7, 0xCC, 0x62, 0x0A, 0x89, 0x6B, 0xD6, 0x43, 0xBD, 0x82, 0x6F, + 0xEB, 0xB1, 0x2E, 0x25, 0x48, 0xC6, 0x3A, 0x47, 0xA2, 0xA1, 0xBA, 0xCC, + 0x71, 0x85, 0xD6, 0xBC, 0x4C, 0x51, 0x11, 0x24, 0x32, 0xF3, 0x08, 0x16, + 0xD7, 0x67, 0xC9, 0x1E, 0x64, 0xCA, 0x7D, 0x39, 0xAD, 0xF9, 0x05, 0x70, + 0x4C, 0x7B, 0x85, 0x50, 0x98, 0x0C, 0x51, 0x1B, 0xB9, 0xA2, 0xD0, 0x1D, + 0x6E, 0xA8, 0xF3, 0x8E, 0x28, 0x68, 0x3A, 0xC5, 0x57, 0x9A, 0x88, 0x1B, + 0xC5, 0xF9, 0x12, 0x85, 0x52, 0xDA, 0x92, 0x79, 0x05, 0x3F, 0x9E, 0xCD, + 0x03, 0xC1, 0xCC, 0xB4, 0x71, 0xE9, 0x41, 0xFD, 0xB1, 0x16, 0xF1, 0xB6, + 0x7C, 0x18, 0x38, 0x91, 0x50, 0xF0, 0xA9, 0xA5, 0x6F, 0xA9, 0xF3, 0x09, + 0x57, 0x2E, 0xDC, 0xF6, 0x08, 0x6C, 0xC7, 0xF0, 0x7E, 0x68, 0x48, 0x68, + 0x3C, 0x66, 0x4A, 0xDA, 0x76, 0xC6, 0xE9, 0x72, 0x61, 0xDF, 0x4D, 0x53, + 0x70, 0x17, 0xEC, 0x46, 0xC2, 0x64, 0x04, 0xB1, 0x68, 0x8D, 0x42, 0x0C, + 0xFE, 0x6D, 0xCC, 0xFE, 0x2D, 0xA7, 0x19, 0x48, 0xAC, 0x96, 0x97, 0x1D, + 0x09, 0x38, 0x7E, 0x7A, 0xE2, 0x84, 0x63, 0xA3, 0xD0, 0xD4, 0x7E, 0x9C, + 0x92, 0xE7, 0x3F, 0xFD, 0x01, 0xF7, 0x86, 0x4C, 0xDF, 0x4B, 0x01, 0x5F, + 0x93, 0xEA, 0x5D, 0xFB, 0xB2, 0x8E, 0xEB, 0xEA, 0x72, 0x2A, 0xB2, 0xA7, + 0x19, 0xD8, 0x8E, 0x80, 0xD4, 0x84, 0x05, 0x8C, 0xD9, 0x2B, 0xCF, 0xCC, + 0x36, 0x6C, 0xBD, 0xCD, 0xEF, 0x55, 0x90, 0x38, 0x66, 0xDF, 0x4F, 0x0C, + 0x5E, 0x66, 0x75, 0x44, 0x6C, 0xD3, 0xD7, 0x8F, 0xE7, 0xF9, 0x66, 0x13, + 0xDE, 0xA0, 0x5F, 0xAE, 0x57, 0x2B, 0x13, 0x8E, 0xAE, 0x56, 0xBB, 0x90, + 0xB9, 0xFB, 0x98, 0x11, 0x83, 0x33, 0x2B, 0xF1, 0x65, 0x91, 0x7A, 0x15, + 0xFB, 0x74, 0x3C, 0xEF, 0x1F, 0x25, 0x34, 0xB0, 0x07, 0x05, 0x37, 0xD1, + 0xF7, 0xF2, 0xC0, 0x8D, 0x47, 0x82, 0xD6, 0x5E, 0x13, 0x08, 0x58, 0x90, + 0xF2, 0x3A, 0x88, 0xC0, 0x8F, 0x27, 0xAA, 0x84, 0x03, 0x20, 0x71, 0xE1, + 0xF3, 0x34, 0x25, 0xBE, 0xC5, 0x3F, 0x6F, 0x0C, 0x95, 0x36, 0xDB, 0x77, + 0x3A, 0xF7, 0xA9, 0x51, 0x24, 0x7E, 0x04, 0xB0, 0x13, 0xD7, 0xC4, 0x29, + 0x20, 0x38, 0x42, 0x5B, 0x1E, 0x9A, 0xEB, 0x3C, 0xB1, 0xC9, 0xCF, 0x48, + 0x1F, 0xF1, 0x86, 0xD2, 0x6B, 0x83, 0xF9, 0x63, 0x6D, 0xEE, 0xB1, 0x8A, + 0x5C, 0x28, 0x69, 0x96, 0x9A, 0x38, 0x53, 0x25, 0xF5, 0xAC, 0x61, 0xD8, + 0x0A, 0x60, 0xC8, 0xD5, 0xA6, 0xFB, 0xA6, 0x10, 0x27, 0x55, 0x6C, 0xAE, + 0xEA, 0x8E, 0x65, 0x7A, 0x11, 0x94, 0x17, 0x5F, 0x90, 0x3E, 0x28, 0x48, + 0x08, 0xC0, 0x35, 0x9B, 0x3E, 0xE8, 0x42, 0xC0, 0x94, 0x9D, 0xDD, 0x83, + 0xCD, 0xED, 0xB3, 0x52, 0xD3, 0xE9, 0xD8, 0x89, 0x98, 0xA0, 0xC3, 0xFE, + 0x49, 0x17, 0x80, 0x99, 0x56, 0x84, 0xB9, 0x4E, 0x02, 0x4B, 0x27, 0xC6, + 0xA8, 0xD8, 0x11, 0xFB, 0x89, 0xE0, 0xB0, 0x5A, 0xA8, 0xC1, 0xC9, 0x0B, + 0x04, 0xC5, 0x23, 0xA4, 0x35, 0x5A, 0x5E, 0xB7, 0x24, 0xA6, 0x4E, 0x92, + 0x15, 0x00, 0x2B, 0xF9, 0x9F, 0xBC, 0x6F, 0x16, 0xF4, 0xFE, 0xBF, 0xDD, + 0x09, 0xB1, 0x36, 0x9D, 0xFF, 0x1F, 0x91, 0x8B, 0xAC, 0xAD, 0x44, 0x19, + 0x19, 0xEF, 0xAD, 0x2A, 0xE8, 0xF3, 0x85, 0x5C, 0xDB, 0xF8, 0x27, 0xFD, + 0xAC, 0x71, 0xB0, 0x5E, 0x20, 0x53, 0xAE, 0xA4, 0x3C, 0xFE, 0x8A, 0xC0, + 0xEB, 0xDB, 0x45, 0xBA, 0x09, 0xCC, 0xA5, 0x0B, 0x02, 0xBE, 0xEC, 0x60, + 0x55, 0x76, 0x0D, 0x6F, 0xB7, 0x07, 0xBF, 0xA7, 0xCD, 0x8D, 0xB4, 0x6A, + 0x33, 0xF7, 0xCB, 0x2C, 0x9D, 0xA0, 0x14, 0x13, 0xC4, 0x6C, 0xD7, 0xE5, + 0x8D, 0xE4, 0x0F, 0x76, 0x22, 0x99, 0x6B, 0x9D, 0xF6, 0xFC, 0x7E, 0x51, + 0x90, 0xBF, 0x9D, 0xD2, 0xF1, 0xC8, 0x5B, 0xFE, 0x59, 0x52, 0x5C, 0x41, + 0x7F, 0x81, 0xB3, 0xE6, 0xBC, 0x46, 0x0B, 0xB4, 0xFA, 0xC0, 0xA2, 0x1D, + 0x4B, 0xE8, 0x2F, 0x01, 0x16, 0x01, 0x72, 0xB4, 0x43, 0x30, 0x22, 0xD5, + 0xF2, 0xD8, 0x1B, 0x36, 0xD9, 0x09, 0x49, 0x46, 0x6E, 0xBF, 0x09, 0x93, + 0x8C, 0xAB, 0x68, 0xF5, 0xF8, 0xC1, 0xAD, 0xA8, 0x52, 0xD9, 0x11, 0x00, + 0x23, 0x1F, 0x25, 0x26, 0xC5, 0x65, 0x06, 0x11, 0xA5, 0xB3, 0xF7, 0x61, + 0xCF, 0x71, 0xFA, 0xFD, 0xEC, 0xD6, 0x5A, 0x06, 0xB4, 0xF6, 0xD5, 0x0A, + 0xE5, 0x3E, 0xE1, 0xC1, 0xC7, 0xD9, 0xF0, 0xB2, 0xD9, 0xB5, 0xF4, 0x82, + 0x09, 0xD1, 0xED, 0x29, 0xA9, 0xF2, 0xD0, 0x52, 0xD5, 0x5C, 0x0A, 0x20, + 0xB5, 0x9E, 0xEF, 0xDF, 0x40, 0xB4, 0x91, 0x97, 0xAB, 0x31, 0x3F, 0x72, + 0x88, 0xF3, 0xFD, 0x11, 0xD2, 0xDF, 0x29, 0xBC, 0x41, 0x71, 0xF5, 0x1E, + 0x55, 0xB8, 0xED, 0xEC, 0x74, 0x0A, 0x22, 0x6E, 0x4B, 0xD9, 0x53, 0xE5, + 0x2F, 0xCF, 0x8A, 0xD7, 0xA0, 0x0F, 0x3B, 0x36, 0x17, 0x68, 0xF3, 0x9C, + 0x98, 0xA7, 0x4D, 0x21, 0xCE, 0x9A, 0xA0, 0xB2, 0xCA, 0x3F, 0x0D, 0xF5, + 0x7F, 0x6B, 0x8C, 0x9E, 0xCC, 0x20, 0xB0, 0x7C, 0xAE, 0x16, 0xC0, 0xBA, + 0x09, 0x1B, 0xDD, 0x07, 0x07, 0xF6, 0xC3, 0x8A, 0x0B, 0x38, 0x08, 0xD3, + 0xE0, 0x30, 0x46, 0x6E, 0x7C, 0x03, 0x5E, 0x62, 0xFB, 0xCE, 0x1F, 0x85, + 0x84, 0xF7, 0xFF, 0xEB, 0xAE, 0x9A, 0xDC, 0x6E, 0xAC, 0xAC, 0x38, 0x9A, + 0x5B, 0xCA, 0x7D, 0x6E, 0x40, 0x27, 0x7D, 0x9F, 0x72, 0x38, 0xEE, 0xC6, + 0x3D, 0xE7, 0x4F, 0x8A, 0x45, 0xBE, 0x38, 0xE6, 0x06, 0x87, 0x61, 0x0A, + 0x1D, 0x0A, 0x4C, 0x57, 0x82, 0x75, 0x40, 0x1A, 0x44, 0x7E, 0x79, 0xF2, + 0x13, 0x90, 0xAB, 0xF6, 0xDE, 0x4C, 0xBA, 0x48, 0x34, 0xF1, 0x92, 0x1F, + 0xA0, 0xE8, 0x57, 0x68, 0x33, 0xB5, 0x07, 0xCA, 0x95, 0x00, 0xEC, 0xD8, + 0xE1, 0x94, 0x44, 0x85, 0xD6, 0x61, 0x6C, 0x32, 0xBB, 0x37, 0x9C, 0x79, + 0x6D, 0xBF, 0x8E, 0x54, 0x31, 0xDB, 0x4C, 0xB7, 0x6D, 0x34, 0xC0, 0xEA, + 0x94, 0xF0, 0x50, 0xC5, 0xE5, 0xA6, 0x24, 0xDB, 0x72, 0x55, 0x9A, 0x50, + 0x23, 0xE9, 0x71, 0xC7, 0x7C, 0x05, 0xAA, 0x03, 0x86, 0x5E, 0x37, 0x65, + 0xF9, 0x07, 0x4F, 0x6E, 0x09, 0x9D, 0x11, 0x44, 0x19, 0xAD, 0xAC, 0xC6, + 0x66, 0x23, 0x60, 0x3A, 0xBD, 0x12, 0xEA, 0x47, 0x26, 0x11, 0x20, 0xAA, + 0x7B, 0xF3, 0xF2, 0xFE, 0xDA, 0x42, 0x77, 0x14, 0xFD, 0x90, 0xC7, 0xD5, + 0xF4, 0xB2, 0x9A, 0xD5, 0x15, 0x4E, 0x0C, 0x1E, 0x9B, 0x08, 0x3C, 0xA8, + 0x3D, 0xB3, 0x05, 0x8C, 0xA6, 0x90, 0x36, 0x22, 0xCE, 0x3D, 0x48, 0x30, + 0xCE, 0xDF, 0xD4, 0xE7, 0x5D, 0x78, 0xA8, 0x0B, 0x7D, 0x4A, 0xFE, 0x0F, + 0x48, 0xCE, 0x35, 0xE3, 0xB1, 0x71, 0x9F, 0x06, 0x82, 0xA5, 0xCD, 0xC2, + 0x85, 0xF0, 0xBC, 0x82, 0x5C, 0xE0, 0x54, 0x4E, 0x3B, 0x0B, 0x0D, 0x20, + 0x1F, 0xD3, 0x1F, 0x3A, 0x7A, 0xD3, 0x11, 0xBE, 0x37, 0x99, 0x5C, 0x93, + 0x79, 0x85, 0xC5, 0x69, 0xAC, 0x58, 0x1D, 0x4E, 0x61, 0xAB, 0xB8, 0xDF, + 0x5B, 0xB8, 0x81, 0x3A, 0x1A, 0x88, 0xFC, 0x1A, 0x73, 0x67, 0xB5, 0x68, + 0x5F, 0xEB, 0x7F, 0x4D, 0xAD, 0xB1, 0xEC, 0x88, 0x2F, 0xB3, 0xC0, 0xB4, + 0x5B, 0xBF, 0x01, 0x51, 0x06, 0x0B, 0xF6, 0x1A, 0x80, 0x7A, 0xF1, 0xB9, + 0xFB, 0x3B, 0xDE, 0xAA, 0xCA, 0x07, 0x51, 0x04, 0x74, 0x95, 0x08, 0xED, + 0x1B, 0xA1, 0xD6, 0x87, 0xBC, 0x4D, 0xCC, 0x71, 0x49, 0x7B, 0x68, 0x39, + 0x0B, 0x3D, 0xFF, 0x4C, 0x10, 0xD1, 0x77, 0x1A, 0x6F, 0xAE, 0xA2, 0xA4, + 0x6B, 0x83, 0x55, 0xC9, 0x94, 0x2F, 0xC5, 0x5C, 0x74, 0xF5, 0xB1, 0x31, + 0x5C, 0x8F, 0xD0, 0x77, 0xCC, 0xCF, 0x62, 0xDD, 0x68, 0xB6, 0xCD, 0x95, + 0x1B, 0xA6, 0x3E, 0x61, 0xCC, 0x1F, 0xC0, 0xC7, 0xFC, 0x24, 0xD6, 0x1E, + 0xF3, 0x2E, 0x98, 0x79, 0x3D, 0xDF, 0x15, 0x58, 0x62, 0xD1, 0xE6, 0x1F, + 0x7F, 0x39, 0x86, 0x1A, 0x39, 0x17, 0xCE, 0x53, 0x27, 0x5A, 0x43, 0x39, + 0xDD, 0x88, 0x03, 0xDB, 0xDA, 0x55, 0x5B, 0xB6, 0x9C, 0x20, 0xF5, 0xBD, + 0xEF, 0xD6, 0xB2, 0xCC, 0x98, 0xF9, 0xC1, 0xB6, 0x53, 0xCB, 0xE7, 0xDA, + 0x0D, 0x1C, 0x58, 0x07, 0xFB, 0x1A, 0xF1, 0x4C, 0x1E, 0x0C, 0xA6, 0x2A, + 0x1B, 0x73, 0xE2, 0xAC, 0xE3, 0x16, 0x2D, 0x54, 0x3E, 0x29, 0x6B, 0x8C, + 0xB3, 0x78, 0x9A, 0xD6, 0xCE, 0x66, 0xBB, 0x80, 0x5E, 0x48, 0x1B, 0x24, + 0xC1, 0x9E, 0x92, 0x9C, 0xAB, 0xB2, 0x08, 0x0F, 0xD8, 0xAD, 0xDA, 0xC5, + 0xB9, 0x1E, 0x4A, 0x19, 0xB4, 0xCB, 0x40, 0xB1, 0x7B, 0x10, 0x5B, 0x80, + 0x46, 0x06, 0x8A, 0xED, 0xFB, 0xB6, 0x6D, 0xDA, 0xF1, 0x03, 0xAA, 0xDD, + 0x9A, 0xC6, 0xFB, 0x26, 0xDE, 0x82, 0x4F, 0x55, 0x54, 0xF8, 0x38, 0xDF, + 0xC9, 0xDB, 0x8A, 0x1B, 0x81, 0x15, 0x51, 0x6F, 0x0B, 0xFC, 0xF9, 0x72, + 0x71, 0x82, 0xC3, 0x37, 0xC2, 0xFB, 0xDD, 0x61, 0x01, 0xBC, 0xBB, 0x85, + 0xAF, 0xC3, 0x36, 0x34, 0xE5, 0xA2, 0xF8, 0x9F, 0xEF, 0x1E, 0xAA, 0xE1, + 0xE6, 0x2A, 0xE7, 0xBA, 0x87, 0xB1, 0x8E, 0xE5, 0x4F, 0x36, 0x0D, 0xF9, + 0x85, 0x34, 0x9A, 0x1D, 0xDE, 0xFD, 0xC5, 0x8C, 0xB5, 0xCC, 0x7E, 0x4E, + 0xEE, 0x84, 0x5E, 0x03, 0x18, 0x01, 0xB5, 0x2F, 0xF1, 0xD9, 0x84, 0xBB, + 0x2F, 0x10, 0x85, 0xFF, 0xA6, 0x01, 0x9B, 0xF8, 0xB0, 0x22, 0x09, 0x3D, + 0x9D, 0x78, 0x02, 0x14, 0x52, 0xDB, 0x7D, 0xAB, 0x7C, 0x22, 0xC8, 0x16, + 0x3C, 0x1B, 0x2F, 0xB7, 0xC7, 0x9B, 0xE4, 0x27, 0xB2, 0xD6, 0xB5, 0x2D, + 0xB4, 0x3E, 0xE2, 0xE5, 0x65, 0x93, 0x78, 0x1B, 0x1D, 0xB5, 0x2F, 0xDF, + 0x8E, 0xAA, 0xBF, 0xD8, 0x4A, 0xF0, 0x54, 0x0A, 0x1A, 0x96, 0x37, 0xE3, + 0xFF, 0x3B, 0x2B, 0xD4, 0xE2, 0x81, 0x85, 0xE8, 0xD6, 0xFE, 0x90, 0xFE, + 0x0D, 0xB9, 0x18, 0x86, 0x40, 0x93, 0xB3, 0x8C, 0xBD, 0xCF, 0xB4, 0x5A, + 0x99, 0x14, 0xBD, 0x85, 0xE8, 0x22, 0x9C, 0x3E, 0x25, 0x3A, 0xE1, 0x82, + 0x59, 0x27, 0x16, 0x4C, 0x01, 0x7A, 0x8A, 0x04, 0xED, 0xEA, 0x19, 0xF9, + 0x11, 0xC9, 0x11, 0x65, 0x0C, 0x9F, 0xEA, 0xF0, 0xE2, 0x9F, 0xD1, 0x43, + 0xF2, 0xC0, 0x83, 0xAF, 0x46, 0x9A, 0x47, 0xD8, 0xE5, 0x35, 0xC1, 0xC7, + 0x74, 0x7F, 0x2A, 0xE5, 0x46, 0x80, 0x43, 0x67, 0x83, 0xE0, 0xFB, 0x54, + 0x44, 0x76, 0xEB, 0x55, 0xE3, 0xD7, 0xAE, 0x9F, 0x11, 0xF6, 0xB8, 0x51, + 0x87, 0x66, 0x2B, 0x32, 0x6D, 0x0E, 0x72, 0xC0, 0xAD, 0x63, 0x08, 0x1C, + 0x78, 0xFF, 0x1B, 0xD9, 0xBF, 0x0D, 0x9B, 0x05, 0xA3, 0xCA, 0x47, 0x69, + 0x93, 0x9B, 0x2C, 0x41, 0x25, 0x19, 0x5E, 0xC1, 0x22, 0x34, 0x04, 0x4F, + 0xB3, 0x6C, 0x1F, 0x40, 0x26, 0x54, 0xED, 0x4B, 0x77, 0xD6, 0x11, 0xCE, + 0xC3, 0x9E, 0x8B, 0x75, 0xDD, 0x73, 0xDD, 0x65, 0x38, 0xFC, 0xA8, 0x34, + 0x48, 0x3E, 0x5A, 0x08, 0xED, 0xA1, 0x17, 0x45, 0x30, 0x99, 0xEA, 0x99, + 0x4A, 0xDF, 0x3C, 0x53, 0x78, 0xE4, 0x39, 0xF7, 0xEA, 0xBF, 0x97, 0xB0, + 0x34, 0x70, 0x00, 0x03, 0x23, 0xC5, 0xA6, 0x6F, 0xCF, 0xB4, 0x00, 0xA2, + 0xFA, 0xDA, 0xEF, 0x7F, 0x80, 0xF1, 0xCD, 0x7B, 0x30, 0x7A, 0xEE, 0xB4, + 0x88, 0x37, 0x26, 0x33, 0xCD, 0x5B, 0x9E, 0x38, 0x42, 0x09, 0xC5, 0x64, + 0x87, 0x34, 0x40, 0x80, 0x3F, 0x79, 0xE9, 0xA2, 0x4A, 0x51, 0x5F, 0x5D, + 0x86, 0x6C, 0x9E, 0x56, 0x5B, 0x07, 0x07, 0x93, 0x3F, 0x60, 0x27, 0xA5, + 0xA8, 0x00, 0x0B, 0x44, 0xB7, 0x19, 0x1D, 0xE9, 0x70, 0x72, 0x7B, 0x61, + 0x90, 0xA6, 0xCD, 0xB1, 0xFF, 0x8B, 0x0B, 0x9A, 0x60, 0x66, 0xBE, 0xB8, + 0x19, 0x95, 0x40, 0x31, 0x65, 0x3B, 0x37, 0x03, 0x8A, 0x9F, 0x86, 0x27, + 0x38, 0xB1, 0x08, 0xDF, 0x09, 0xF8, 0xE5, 0x82, 0x4E, 0x9C, 0x93, 0x90, + 0xB1, 0x44, 0x7B, 0xA1, 0xB1, 0x38, 0x64, 0xC0, 0x61, 0xF9, 0x85, 0xE2, + 0xAC, 0x94, 0xE5, 0xA8, 0xA1, 0x4E, 0x18, 0xFC, 0xA5, 0x5F, 0xF2, 0x38, + 0xE3, 0x1D, 0xDD, 0x0B, 0x5B, 0x61, 0x92, 0x46, 0xB5, 0xA1, 0x46, 0x1D, + 0xE3, 0x89, 0xB6, 0xD4, 0x8B, 0x2E, 0x43, 0x0A, 0x68, 0xA9, 0xF4, 0x5A, + 0xC1, 0xEE, 0x5A, 0xDC, 0xC1, 0x93, 0x50, 0x9A, 0x11, 0x12, 0x91, 0x6C, + 0xD4, 0x6F, 0x19, 0x72, 0x33, 0xA7, 0x56, 0xAD, 0x5E, 0x16, 0xAC, 0xAA, + 0xBE, 0x33, 0x1C, 0xBC, 0x92, 0x64, 0x31, 0xA6, 0xF3, 0xE4, 0x2C, 0x55, + 0xA7, 0x84, 0x59, 0x9A, 0xD6, 0x22, 0xAF, 0x55, 0x5F, 0x64, 0x69, 0x3D, + 0xC7, 0xED, 0x70, 0x0A, 0x76, 0xE9, 0xE8, 0x10, 0xB2, 0x84, 0x6F, 0x19, + 0x2A, 0xC4, 0x2D, 0xA7, 0x2C, 0x5D, 0xE0, 0xA0, 0x4C, 0x6B, 0x51, 0x9F, + 0x5A, 0xC0, 0xF4, 0xC5, 0x63, 0x0B, 0x41, 0xA0, 0x6C, 0xCD, 0x2D, 0x99, + 0xD7, 0xBA, 0x7E, 0x87, 0xC5, 0x09, 0x0B, 0x6E, 0x16, 0x10, 0x31, 0x73, + 0x09, 0x8F, 0x8F, 0x15, 0xC1, 0x3F, 0x54, 0x6D, 0x78, 0xCF, 0xAE, 0x42, + 0xFC, 0x11, 0x6E, 0x2E, 0xE6, 0x44, 0xE3, 0xC6, 0x96, 0xAD, 0xC9, 0x08, + 0x90, 0x37, 0xF0, 0xFA, 0xF7, 0x11, 0xB8, 0x2B, 0x60, 0xAB, 0x24, 0x3D, + 0xC3, 0x38, 0x72, 0x5F, 0x48, 0x6C, 0x6F, 0x72, 0x1E, 0xB6, 0x3F, 0xE1, + 0x75, 0xF9, 0xE2, 0x53, 0x9E, 0xE3, 0x63, 0x62, 0xF7, 0x22, 0x7A, 0xB6, + 0xF4, 0xB8, 0x59, 0xDA, 0x3A, 0x3E, 0x8E, 0x01, 0xC6, 0xB2, 0x65, 0xBC, + 0xAF, 0x5B, 0x2C, 0xCE, 0x9F, 0x66, 0x70, 0xFA, 0x83, 0x1E, 0xBB, 0x5E, + 0x33, 0xCD, 0xDF, 0xFB, 0xEB, 0xDE, 0x58, 0x70, 0xEB, 0x2A, 0x09, 0x50, + 0xC3, 0xF1, 0x8E, 0x6A, 0xAA, 0x31, 0x17, 0x37, 0x92, 0x01, 0xA2, 0x58, + 0x68, 0xF4, 0xF4, 0x48, 0xE1, 0xFA, 0xC7, 0xD6, 0x73, 0xEC, 0x8E, 0x24, + 0x69, 0xF6, 0x34, 0x49, 0x2A, 0x25, 0x28, 0xE2, 0x1D, 0x03, 0x10, 0xC2, + 0xF2, 0xB1, 0x67, 0x0A, 0xE1, 0x07, 0xF0, 0xAC, 0x0A, 0x65, 0x71, 0x1D, + 0x2F, 0x40, 0x3E, 0x96, 0x47, 0xF0, 0x8F, 0x17, 0x28, 0x6B, 0xB0, 0x8F, + 0xF2, 0xE8, 0xD0, 0x54, 0x7E, 0x38, 0x26, 0xF3, 0xD8, 0x30, 0xCD, 0xC0, + 0x47, 0x66, 0x45, 0x1C, 0x3F, 0xC0, 0xCD, 0xCC, 0x94, 0x9C, 0xE7, 0x55, + 0x1D, 0x6F, 0x80, 0xEE, 0x23, 0x69, 0xE2, 0xCC, 0xDB, 0x27, 0x76, 0x80, + 0x52, 0xAD, 0x27, 0xF6, 0xB3, 0xFB, 0x43, 0x26, 0x1D, 0x1F, 0x68, 0xBB, + 0x20, 0x4D, 0x02, 0x4C, 0xF9, 0xFE, 0x7C, 0xE5, 0xDD, 0xC7, 0xF9, 0x5B, + 0xB6, 0x3D, 0xF2, 0xB6, 0x2F, 0xBF, 0xE1, 0xFB, 0x58, 0xEF, 0x6E, 0x82, + 0x95, 0xA7, 0x03, 0x47, 0xD1, 0x72, 0x2D, 0xDE, 0x2E, 0x4C, 0xC0, 0x3D, + 0x4B, 0xEE, 0xF5, 0xAB, 0x7A, 0x5E, 0xCD, 0xEC, 0x19, 0x46, 0xCF, 0x72, + 0xD9, 0x47, 0x3A, 0x28, 0x2A, 0xEA, 0x1F, 0x84, 0x36, 0x00, 0x95, 0xF0, + 0x22, 0xD7, 0x11, 0x31, 0x9C, 0xDE, 0xDB, 0x5E, 0x6C, 0x98, 0x53, 0x1F, + 0xC5, 0x14, 0x03, 0x59, 0xFE, 0xD0, 0x91, 0x42, 0xA3, 0x45, 0xBB, 0x37, + 0xFB, 0x55, 0xC3, 0x97, 0x84, 0x5F, 0x4E, 0x42, 0x17, 0x3B, 0x51, 0xCF, + 0xAD, 0x41, 0xC6, 0x57, 0x25, 0xFC, 0xB7, 0xF2, 0xA6, 0xEE, 0x23, 0xB7, + 0x73, 0x77, 0x44, 0x5B, 0xFC, 0xDD, 0x41, 0x71, 0x7C, 0x3E, 0xDB, 0x73, + 0x71, 0x21, 0x89, 0x62, 0x11, 0x49, 0xCD, 0xFC, 0x34, 0x82, 0x5C, 0x1D, + 0xD4, 0xAD, 0x20, 0xBF, 0x5E, 0xE2, 0xA8, 0xB2, 0x96, 0xB2, 0xAB, 0xC3, + 0x5A, 0x7F, 0xBB, 0x86, 0x22, 0xD2, 0xF9, 0x69, 0xA2, 0x95, 0x14, 0x64, + 0x86, 0x87, 0x37, 0x1F, 0x81, 0x64, 0xC2, 0x95, 0x87, 0x43, 0x5C, 0xE0, + 0xDD, 0x7B, 0x9E, 0xE4, 0xCE, 0x73, 0xF5, 0xF0, 0x5A, 0xF5, 0x7E, 0xC3, + 0x66, 0x27, 0x40, 0xF5, 0xCC, 0xDB, 0xD3, 0x43, 0x69, 0x49, 0x6D, 0x30, + 0xF6, 0xAD, 0xB8, 0x5F, 0xA8, 0xDB, 0x34, 0x47, 0x0A, 0x86, 0x4D, 0x05, + 0x4C, 0x11, 0x80, 0x51, 0x96, 0x5B, 0xF9, 0x9E, 0x9B, 0x40, 0x49, 0x2D, + 0xE1, 0x83, 0x9C, 0xD3, 0xCA, 0xE3, 0x7E, 0x1F, 0x58, 0x52, 0x34, 0x10, + 0xF5, 0x00, 0x08, 0xF0, 0xF8, 0xF5, 0x53, 0x71, 0xFA, 0x67, 0xC3, 0xB1, + 0xFF, 0x91, 0xFC, 0xE8, 0x52, 0x31, 0xA9, 0x09, 0x76, 0xF8, 0x62, 0x8E, + 0x61, 0x94, 0x3C, 0x21, 0x4C, 0xD0, 0xB5, 0x84, 0xE9, 0x25, 0x6C, 0xD3, + 0x57, 0x2D, 0xD0, 0x66, 0xC0, 0x76, 0x45, 0x78, 0x25, 0xDA, 0xC6, 0xAD, + 0x02, 0x6F, 0xD5, 0x65, 0x7A, 0x27, 0x47, 0x6B, 0xB6, 0x9C, 0xF6, 0x64, + 0x61, 0x89, 0x95, 0x58, 0xBD, 0x13, 0x7C, 0xD8, 0x6A, 0xB0, 0x17, 0x91, + 0xD7, 0x08, 0x8C, 0x69, 0x44, 0x40, 0x29, 0xDF, 0x51, 0x9D, 0x1D, 0x59, + 0xF0, 0x28, 0x74, 0x58, 0x7A, 0x97, 0xE2, 0x88, 0x04, 0x87, 0x18, 0xA7, + 0x1C, 0xEB, 0xB3, 0x72, 0x3A, 0x3A, 0x4F, 0x8F, 0x66, 0x53, 0xFB, 0xCA, + 0xC8, 0x5D, 0x7F, 0x98, 0x98, 0x6F, 0x36, 0x0A, 0x6D, 0x3D, 0x49, 0xA0, + 0xD2, 0x47, 0x81, 0x84, 0x3C, 0x24, 0x7E, 0xC8, 0xD8, 0x87, 0x62, 0xA0, + 0x3F, 0x34, 0xA7, 0x95, 0xAD, 0x18, 0x7C, 0x05, 0xAF, 0xD2, 0x6C, 0x11, + 0x0F, 0x4F, 0x54, 0xD4, 0xC8, 0x6E, 0x11, 0xA9, 0x19, 0x9F, 0x20, 0xB8, + 0xED, 0xB6, 0x11, 0xFF, 0x67, 0xF5, 0xF6, 0x93, 0x2D, 0xED, 0x9C, 0xEC, + 0x6D, 0xAF, 0xEF, 0xBC, 0xC2, 0x3F, 0xDF, 0xB1, 0xBB, 0x34, 0xB3, 0xF5, + 0xC1, 0x2A, 0xBC, 0x8F, 0x74, 0xC1, 0x57, 0xD5, 0xA3, 0xB8, 0x6C, 0x1F, + 0x7E, 0x9F, 0x69, 0x15, 0xF0, 0x6B, 0x76, 0xEE, 0x5E, 0xCE, 0x4D, 0x63, + 0x19, 0xC8, 0xD3, 0x03, 0x76, 0x62, 0x72, 0xCF, 0xF7, 0x44, 0xF4, 0x0D, + 0x1B, 0x39, 0x25, 0x02, 0x9D, 0x0A, 0x98, 0xD6, 0x84, 0x44, 0x15, 0x18, + 0x1D, 0x27, 0x37, 0x99, 0x97, 0xE3, 0x89, 0xD8, 0x57, 0x1D, 0xF8, 0xC9, + 0x1C, 0xD5, 0x48, 0x2C, 0x73, 0xD9, 0x6E, 0x33, 0xDD, 0xAB, 0x6A, 0x97, + 0x53, 0x2E, 0xDF, 0x5D, 0xEE, 0xF4, 0x55, 0x34, 0x88, 0x17, 0x41, 0x2C, + 0x85, 0xE3, 0xFE, 0x02, 0xFE, 0xD1, 0xF6, 0xBF, 0x63, 0xA1, 0xEE, 0xAF, + 0xF9, 0x7E, 0xDE, 0xD4, 0x75, 0xA1, 0x23, 0x23, 0x76, 0x78, 0x71, 0x02, + 0xBD, 0x43, 0xA3, 0xC2, 0x10, 0x84, 0xFE, 0x1A, 0x7D, 0x98, 0x10, 0x8D, + 0x30, 0x69, 0x50, 0x68, 0x9B, 0x8E, 0x8A, 0x75, 0x18, 0x6D, 0x35, 0x9E, + 0xF0, 0xB9, 0x10, 0xF1, 0x42, 0x24, 0x4A, 0x9A, 0xA8, 0xC0, 0x91, 0xE5, + 0xC6, 0x7F, 0xE0, 0x13, 0x6E, 0x81, 0xBD, 0xA5, 0x9B, 0x3D, 0xA5, 0xEE, + 0x88, 0x60, 0x32, 0x2E, 0xF5, 0xA2, 0x06, 0x3E, 0x62, 0xCC, 0x0A, 0x48, + 0xB4, 0x70, 0xE8, 0xC5, 0xA6, 0xF5, 0x7E, 0x99, 0x74, 0xDB, 0x6E, 0xFD, + 0x88, 0x8C, 0x03, 0x5D, 0x19, 0x28, 0xB8, 0xCF, 0x0A, 0xA4, 0xEE, 0xCD, + 0x02, 0xD5, 0x62, 0xCE, 0x0C, 0x7B, 0xE5, 0x8B, 0xD8, 0x43, 0xEE, 0xF6, + 0x00, 0xDB, 0xFF, 0x2A, 0xB9, 0x8E, 0x5D, 0x71, 0x58, 0x70, 0xAA, 0x13, + 0x84, 0xC7, 0xC8, 0x76, 0x22, 0xD0, 0x3B, 0xD7, 0xB8, 0x90, 0xAD, 0xC0, + 0x2D, 0xC1, 0xDD, 0x6F, 0x90, 0xAC, 0xB0, 0x77, 0xD6, 0xA9, 0xAC, 0x06, + 0x92, 0x5E, 0x7B, 0x2E, 0x1D, 0x96, 0x74, 0xD9, 0x20, 0x96, 0xD6, 0x2F, + 0xFD, 0xCD, 0x3F, 0xE2, 0x3A, 0xCD, 0xD6, 0x1C, 0x17, 0x61, 0xCE, 0xC3, + 0xF2, 0xBC, 0xAB, 0xA8, 0xE7, 0xE9, 0xC2, 0xD5, 0x4F, 0x9D, 0x3E, 0xD3, + 0x08, 0xC4, 0xDD, 0x45, 0x61, 0xEB, 0xB8, 0xDE, 0x07, 0xD5, 0x7D, 0x25, + 0xAF, 0x2C, 0x7C, 0x82, 0x9D, 0xD8, 0x33, 0x52, 0xD8, 0x51, 0x7F, 0x7E, + 0x2C, 0xDF, 0xDD, 0xCE, 0x59, 0x30, 0x9C, 0x7C, 0x52, 0x39, 0xCD, 0x19, + 0x2A, 0x31, 0x5C, 0xDA, 0x35, 0xC8, 0x05, 0x2A, 0x28, 0xCD, 0x1E, 0x86, + 0xCC, 0xD8, 0x65, 0x04, 0x65, 0xFE, 0x76, 0x4F, 0xA8, 0x31, 0xBB, 0x65, + 0x1C, 0xD0, 0xB7, 0x00, 0x99, 0xFC, 0x1B, 0x62, 0xC6, 0xE9, 0x3C, 0xAE, + 0xC4, 0xBA, 0x84, 0x22, 0xC9, 0x0D, 0x06, 0x10, 0xE6, 0x02, 0x3C, 0x51, + 0x55, 0x75, 0xB3, 0x0F, 0x45, 0x9E, 0x4A, 0xCB, 0xA5, 0x8C, 0x1A, 0x7D, + 0x78, 0x58, 0x5C, 0x33, 0x17, 0x38, 0x1D, 0xA3, 0x33, 0x58, 0x31, 0x55, + 0xE6, 0x08, 0xB0, 0x89, 0xF8, 0xAD, 0xF6, 0x11, 0x79, 0xC3, 0xB7, 0x2E, + 0x15, 0x78, 0xFE, 0x93, 0x45, 0x5F, 0xDC, 0x15, 0xFC, 0xF7, 0x2B, 0xA4, + 0x43, 0xB4, 0x62, 0xDF, 0xC4, 0xDE, 0xB5, 0xFA, 0xA1, 0x99, 0xD6, 0x82, + 0xE1, 0xE8, 0x71, 0xB9, 0xCF, 0x6B, 0x22, 0xAA, 0x22, 0xA0, 0x7E, 0x62, + 0x4B, 0x8E, 0x4A, 0x3D, 0x37, 0x86, 0xA2, 0x65, 0xB2, 0x73, 0x49, 0x22, + 0xBC, 0x27, 0xAD, 0x0D, 0xC5, 0x69, 0xC3, 0x3C, 0x00, 0xAF, 0x8E, 0x44, + 0xE4, 0xCA, 0x8B, 0xB2, 0x33, 0x7D, 0x33, 0xA3, 0x2D, 0xE6, 0xB2, 0x1C, + 0xD8, 0x8C, 0xF1, 0x6E, 0x30, 0x0E, 0x13, 0x5C, 0x60, 0xC8, 0x99, 0xBC, + 0x8B, 0xED, 0x61, 0xEA, 0x35, 0xF4, 0xA9, 0x59, 0xAF, 0x69, 0x9B, 0x81, + 0x79, 0x03, 0xC4, 0x36, 0x46, 0x3F, 0x92, 0x78, 0x99, 0x50, 0x09, 0xD4, + 0x90, 0xB0, 0x08, 0xAB, 0x22, 0x46, 0xB5, 0xFF, 0x14, 0xC1, 0x43, 0x00, + 0xAA, 0xE3, 0xBC, 0x30, 0x38, 0x39, 0x0E, 0x71, 0xAD, 0xFC, 0x01, 0x44, + 0x7F, 0x32, 0x0F, 0xEE, 0xB8, 0xAE, 0xB2, 0x73, 0x6F, 0xD4, 0x7E, 0xCB, + 0xA7, 0x04, 0x85, 0xEA, 0x36, 0xFD, 0x73, 0x60, 0xFC, 0x85, 0xE5, 0x9B, + 0xCE, 0x94, 0xFD, 0xF2, 0x12, 0x11, 0x2C, 0xCC, 0x4F, 0x4E, 0xA5, 0x95, + 0x32, 0x81, 0x76, 0xD8, 0xEC, 0x88, 0x50, 0x0F, 0x93, 0x76, 0xF0, 0x59, + 0x39, 0x8E, 0xB3, 0x96, 0x3F, 0x50, 0x13, 0x36, 0xD8, 0xB9, 0xD8, 0xBA, + 0x17, 0x59, 0xD1, 0xCF, 0x66, 0x83, 0x23, 0x38, 0xE7, 0xEB, 0x61, 0xAC, + 0x13, 0x3E, 0xD6, 0xB4, 0xAA, 0x5C, 0xD0, 0xB2, 0xEC, 0xCB, 0xE3, 0x6D, + 0x42, 0xEA, 0xD1, 0xE7, 0xBE, 0x8A, 0x59, 0x6E, 0x34, 0x7E, 0x44, 0x00, + 0x99, 0x47, 0xA2, 0x45, 0x4D, 0xBA, 0x5F, 0x99, 0x13, 0x31, 0x4D, 0x11, + 0x8D, 0x01, 0xBA, 0x6B, 0x88, 0x7C, 0xEE, 0x87, 0xF2, 0xE7, 0x0F, 0x73, + 0xEB, 0x57, 0x4B, 0x77, 0x40, 0x52, 0xD3, 0xAC, 0x81, 0x1A, 0x3B, 0xFD, + 0xA4, 0x2C, 0x83, 0x11, 0x2A, 0xF7, 0x53, 0xDF, 0x28, 0x15, 0xE2, 0xCA, + 0xAC, 0x2B, 0xAC, 0xDA, 0x68, 0xE0, 0x19, 0x42, 0x8E, 0xC8, 0x78, 0xD4, + 0x11, 0x8B, 0xEF, 0xF6, 0xDC, 0x26, 0xEA, 0x67, 0x93, 0xEC, 0xC7, 0xEA, + 0xEF, 0x8D, 0xF7, 0xE1, 0xA8, 0x94, 0xD3, 0x35, 0xF1, 0x14, 0x38, 0x8A, + 0x63, 0xD1, 0x42, 0xD1, 0x7B, 0x26, 0xBF, 0x3C, 0xB2, 0x8A, 0x6E, 0xBC, + 0xA2, 0x73, 0x63, 0x6A, 0x95, 0x2A, 0x9B, 0x5F, 0xC3, 0x85, 0x38, 0x29, + 0xDD, 0xF5, 0x68, 0x43, 0xBE, 0xC2, 0x6A, 0x56, 0x28, 0xC8, 0x20, 0xCA, + 0x84, 0x0E, 0x23, 0xE4, 0x3D, 0x5F, 0x80, 0xBE, 0xE4, 0xDA, 0x0E, 0xBC, + 0x76, 0x84, 0x48, 0x15, 0x05, 0xA3, 0x56, 0x8C, 0xAE, 0x5E, 0x41, 0xE7, + 0x57, 0x6D, 0xF8, 0xD1, 0x1C, 0x9A, 0xC7, 0x41, 0x1D, 0xE2, 0xA9, 0x03, + 0x45, 0xDA, 0x88, 0x69, 0x99, 0x1B, 0x45, 0xF8, 0x28, 0x72, 0xCC, 0xD8, + 0xCE, 0x79, 0x10, 0xA7, 0x33, 0xF1, 0xC9, 0xD5, 0x01, 0x5A, 0x7A, 0x4F, + 0xE7, 0xDC, 0x71, 0x47, 0x1C, 0xB3, 0x86, 0x62, 0x08, 0xA2, 0x6D, 0x19, + 0xD7, 0xD3, 0x14, 0xEF, 0x84, 0x86, 0x9C, 0xAC, 0xAE, 0x03, 0x9A, 0xA5, + 0xD1, 0x91, 0x8E, 0x02, 0x4A, 0x70, 0x94, 0x51, 0x92, 0xD9, 0xAF, 0x7A, + 0x32, 0x91, 0xD2, 0x1F, 0x0B, 0x16, 0xA0, 0x52, 0xF8, 0x3B, 0x98, 0xD2, + 0xC2, 0x4B, 0xC0, 0xC2, 0x6B, 0x2E, 0xDE, 0xD6, 0x98, 0xC0, 0x1F, 0x6E, + 0x13, 0x56, 0xCC, 0x37, 0x8B, 0x31, 0x26, 0x5E, 0xC8, 0x53, 0xC6, 0xD2, + 0x1F, 0xAB, 0x79, 0x40, 0xE8, 0xFD, 0x25, 0xE1, 0x7F, 0x9E, 0xE4, 0xC0, + 0x3F, 0xCF, 0xBF, 0x74, 0x7A, 0x80, 0x9D, 0x1F, 0x86, 0xD3, 0xEA, 0x9D, + 0xB9, 0x88, 0x0F, 0x34, 0x82, 0x28, 0xD4, 0xB1, 0xF2, 0x86, 0xE4, 0x8D, + 0xF0, 0xFE, 0xBD, 0xEF, 0x86, 0xD3, 0xE0, 0xB9, 0x60, 0xBE, 0xC4, 0x77, + 0x70, 0x04, 0x7B, 0xD4, 0xCB, 0xA6, 0x25, 0x5E, 0xF4, 0x50, 0x35, 0x27, + 0xF7, 0x57, 0xAA, 0xEA, 0x29, 0x04, 0x1E, 0xC1, 0xB1, 0xE4, 0xC3, 0x8C, + 0x85, 0xF8, 0x69, 0xEF, 0xC0, 0x04, 0x18, 0x32, 0x66, 0xDD, 0x9B, 0x60, + 0x8F, 0x9F, 0x2D, 0xC1, 0x00, 0xDD, 0x84, 0x39, 0x38, 0x51, 0xA4, 0xCC, + 0xC2, 0x0E, 0x3C, 0xC3, 0x7B, 0x70, 0xBF, 0xC1, 0x73, 0x2E, 0x3C, 0x98, + 0x15, 0x63, 0x20, 0x33, 0x61, 0x9C, 0x39, 0x85, 0x1D, 0x26, 0xFD, 0x45, + 0xC0, 0x16, 0x55, 0x29, 0x63, 0xA7, 0x07, 0x48, 0x5C, 0x0B, 0x54, 0xE7, + 0xBD, 0xF0, 0x1A, 0x53, 0x13, 0xA7, 0xDD, 0x08, 0xF1, 0x02, 0x1E, 0x19, + 0x4E, 0x3E, 0xE3, 0xF8, 0x10, 0x1F, 0x71, 0x4D, 0xB8, 0x80, 0x55, 0x09, + 0x7B, 0x20, 0xF3, 0x31, 0xD1, 0x66, 0x90, 0xFF, 0x72, 0x41, 0x04, 0xAA, + 0xEB, 0x72, 0xA7, 0x8F, 0x79, 0x4B, 0xFF, 0x9B, 0xF8, 0x61, 0x2A, 0x96, + 0xB4, 0xB1, 0x3D, 0x08, 0x52, 0x6F, 0x1F, 0xB3, 0xD8, 0x9E, 0xEC, 0x39, + 0xCF, 0x7A, 0xF9, 0x09, 0x02, 0xBF, 0xCA, 0x8B, 0x5C, 0xD0, 0xBB, 0xA7, + 0x64, 0xE3, 0x0D, 0x95, 0xE8, 0x74, 0xE4, 0xD5, 0xDD, 0x37, 0xC0, 0xCB, + 0xDD, 0x67, 0xD7, 0xEF, 0x41, 0x51, 0x26, 0x77, 0x60, 0x93, 0xB2, 0xE7, + 0x9E, 0x76, 0x0C, 0xDA, 0x75, 0x4D, 0x31, 0x5F, 0x64, 0xD4, 0xA7, 0x9A, + 0x0A, 0x36, 0x29, 0x0F, 0x66, 0xC2, 0x3E, 0x9D, 0x38, 0xFE, 0x36, 0xC3, + 0x46, 0x1D, 0xB0, 0x2B, 0xE4, 0x49, 0x17, 0xC6, 0x10, 0x19, 0x03, 0xEF, + 0x54, 0x72, 0xFD, 0x0A, 0x98, 0x88, 0x56, 0xE6, 0x5D, 0xC1, 0xB1, 0x99, + 0x76, 0xE4, 0xDE, 0x95, 0x6E, 0x22, 0xB1, 0xA6, 0xAB, 0xD0, 0x17, 0xA3, + 0x50, 0x39, 0x0F, 0x2F, 0x24, 0xBD, 0x0F, 0x88, 0xCE, 0xE0, 0x60, 0x09, + 0x62, 0xF2, 0x4F, 0x5B, 0x2E, 0x98, 0x15, 0x29, 0x7C, 0xE7, 0xAF, 0x38, + 0x71, 0xAC, 0xE2, 0xA4, 0x37, 0x17, 0x91, 0xF1, 0xEB, 0x90, 0xA7, 0xAE, + 0x6A, 0xA8, 0x27, 0x49, 0xB8, 0x30, 0x29, 0x56, 0x6F, 0x44, 0x4A, 0x92, + 0xE4, 0x85, 0xFA, 0x88, 0xFE, 0xAD, 0x0A, 0xC1, 0x2C, 0x04, 0x56, 0xD9, + 0x82, 0xC2, 0x78, 0x55, 0xDB, 0x62, 0xB3, 0xCE, 0xDA, 0xCE, 0x12, 0xB2, + 0xBD, 0x5A, 0xA0, 0x5F, 0xFE, 0x10, 0xA1, 0x06, 0x43, 0xB2, 0xF3, 0x1C, + 0x86, 0x75, 0xFF, 0xE8, 0x72, 0xFB, 0xCC, 0xEB, 0x89, 0xBE, 0x77, 0x0D, + 0xBE, 0x98, 0x04, 0x16, 0x36, 0x59, 0x22, 0x7E, 0x83, 0x94, 0xE3, 0xE8, + 0x32, 0x9D, 0x94, 0x1B, 0x3D, 0x62, 0x57, 0x41, 0x3F, 0x1C, 0x4F, 0x89, + 0xE9, 0x4D, 0x2F, 0x17, 0xC1, 0x2B, 0xA1, 0x62, 0xE3, 0x95, 0xEA, 0x5A, + 0x9D, 0x1C, 0xD7, 0x10, 0xE6, 0x26, 0xDF, 0xD5, 0x75, 0xB4, 0xF9, 0x21, + 0xFA, 0x44, 0x6B, 0x51, 0xD5, 0x53, 0x3F, 0x0C, 0x7F, 0xE1, 0x3F, 0xEB, + 0x5E, 0x52, 0x29, 0xD9, 0x6D, 0xF9, 0xC2, 0xD4, 0xDC, 0x0B, 0x22, 0xD3, + 0x28, 0xBC, 0xDD, 0xD4, 0x71, 0x1C, 0xB5, 0xFF, 0xC4, 0xEE, 0xBD, 0x1A, + 0x9F, 0xF9, 0x4D, 0x4D, 0x57, 0x07, 0xF8, 0xFD, 0x21, 0x32, 0x08, 0xE7, + 0xB2, 0x75, 0x1C, 0xEC, 0x1C, 0x63, 0x32, 0x4C, 0x7C, 0x75, 0xB0, 0x27, + 0xD5, 0xCB, 0x6C, 0xF3, 0x88, 0x21, 0x61, 0x77, 0xDC, 0x8A, 0x26, 0xD6, + 0x32, 0xF3, 0xAB, 0x69, 0xFB, 0xB6, 0x8C, 0xC2, 0x61, 0x8D, 0xC2, 0x78, + 0x91, 0xD3, 0x15, 0x16, 0x48, 0xFA, 0xF6, 0x4F, 0xBE, 0x81, 0xB8, 0x3D, + 0x52, 0xD8, 0xEE, 0x92, 0x42, 0xE1, 0xEC, 0xA8, 0x79, 0x96, 0x2F, 0x7F, + 0xFD, 0xC5, 0x2E, 0xC5, 0xBA, 0xC1, 0xD1, 0x0B, 0x39, 0x28, 0xBC, 0x5F, + 0x05, 0xE3, 0x73, 0xDF, 0x3D, 0xDF, 0x5B, 0xDB, 0x85, 0x8E, 0x4B, 0x52, + 0x98, 0x76, 0x68, 0xE9, 0xBE, 0xFE, 0x14, 0x28, 0x00, 0x67, 0x6E, 0xB2, + 0x7D, 0x67, 0xCB, 0xF6, 0xFF, 0xFF, 0xAB, 0x42, 0x83, 0x35, 0x61, 0x65, + 0x64, 0x79, 0x15, 0x93, 0xE1, 0x97, 0xF8, 0x3F, 0xC8, 0xAB, 0x1D, 0x7F, + 0xBF, 0xD0, 0x22, 0x8A, 0x94, 0x6E, 0xF3, 0xCF, 0x74, 0x93, 0xEE, 0xA1, + 0xFB, 0xDC, 0xAE, 0x9F, 0x6F, 0x84, 0x10, 0x42, 0xD6, 0xE7, 0x78, 0xC5, + 0x02, 0x68, 0xC9, 0x17, 0x66, 0x50, 0xD3, 0xE9, 0x2E, 0x98, 0x77, 0x20, + 0x50, 0x24, 0x4A, 0x71, 0x91, 0x2E, 0x11, 0x6E, 0x98, 0x7A, 0xB3, 0x55, + 0x7B, 0xF5, 0x5D, 0x81, 0x45, 0xEC, 0x79, 0x90, 0x54, 0x7C, 0x12, 0x41, + 0x23, 0xEF, 0xB8, 0x07, 0x6F, 0x0D, 0x40, 0x01, 0x7D, 0x45, 0x76, 0x80, + 0x50, 0x34, 0x16, 0x47, 0x50, 0x4C, 0xD1, 0x35, 0xC4, 0x86, 0x36, 0x2E, + 0xAC, 0xA0, 0xEF, 0x5E, 0x67, 0xD7, 0x91, 0xCE, 0x56, 0xA0, 0xE5, 0x53, + 0xBB, 0x5A, 0x8C, 0x63, 0x15, 0x06, 0xE0, 0x01, 0xA9, 0x1C, 0xDF, 0xAC, + 0x7A, 0xE7, 0x8D, 0x3B, 0x87, 0xE5, 0x1B, 0x47, 0x4C, 0x69, 0x96, 0xB1, + 0x52, 0xE8, 0xAE, 0x21, 0x45, 0x6E, 0x60, 0x67, 0x82, 0x41, 0x0F, 0x8C, + 0x5D, 0x77, 0xDB, 0x8B, 0xF4, 0x95, 0x16, 0xD1, 0x62, 0x12, 0xB6, 0x8D, + 0x7D, 0xF3, 0x05, 0xCF, 0xA1, 0xA4, 0xE0, 0xCA, 0x75, 0x11, 0x01, 0xA1, + 0x6F, 0x60, 0x31, 0xD6, 0x94, 0x54, 0xE3, 0x1B, 0xE6, 0x1C, 0xD6, 0x05, + 0xA6, 0xBF, 0x1F, 0xF1, 0xEC, 0x6B, 0x9F, 0x28, 0x89, 0x7B, 0x92, 0x59, + 0xD2, 0xF7, 0x69, 0x7C, 0xCE, 0xA3, 0x9B, 0x78, 0xDE, 0x15, 0x21, 0x1F, + 0x77, 0x0E, 0xF9, 0x33, 0x96, 0x44, 0x76, 0x90, 0x3E, 0x3E, 0xBF, 0x0C, + 0xBC, 0x01, 0x0D, 0x61, 0x0C, 0x8F, 0x55, 0xA4, 0xC4, 0x0E, 0xC2, 0xF7, + 0x23, 0x1A, 0x85, 0x33, 0x53, 0xAA, 0x54, 0x1D, 0xEC, 0xFA, 0x0F, 0xD4, + 0x89, 0x87, 0x15, 0xB5, 0x74, 0xB8, 0xA0, 0x74, 0x1D, 0xA0, 0x29, 0xE3, + 0x16, 0x0B, 0x6C, 0x3F, 0x2D, 0xA6, 0xFD, 0x90, 0x63, 0x27, 0x7E, 0x0C, + 0xC0, 0x2E, 0x84, 0x81, 0x9C, 0xBC, 0x06, 0x06, 0xA2, 0x11, 0x49, 0x3B, + 0xC5, 0x13, 0xF7, 0x33, 0x24, 0x54, 0x89, 0x1E, 0x16, 0x01, 0x0B, 0x30, + 0xBD, 0xBD, 0x9B, 0x7D, 0xB1, 0x1B, 0xFA, 0x3F, 0x59, 0x94, 0xA6, 0xF9, + 0x27, 0x84, 0x30, 0x4E, 0x3A, 0x72, 0x61, 0x73, 0x56, 0x1E, 0xFE, 0xA9, + 0x32, 0x7E, 0x34, 0xDF, 0xF2, 0xCF, 0x9F, 0x32, 0x59, 0x87, 0xC8, 0x93, + 0x7C, 0x3F, 0x26, 0x6B, 0x34, 0xE0, 0xB3, 0xC3, 0xEE, 0x38, 0xF3, 0x50, + 0x30, 0xB4, 0xC1, 0x60, 0x48, 0xA6, 0x37, 0xD3, 0xE1, 0x77, 0xB6, 0x54, + 0x79, 0x8F, 0x2A, 0xBF, 0xDA, 0x43, 0xC6, 0xCE, 0x03, 0xFE, 0xDE, 0x08, + 0x2B, 0x18, 0x6C, 0x94, 0x6E, 0x0E, 0x7D, 0xDD, 0xFD, 0xB2, 0xD8, 0x85, + 0xAD, 0x75, 0xC9, 0x2C, 0x6E, 0x40, 0xAA, 0xC1, 0x28, 0x9D, 0x52, 0x21, + 0xEA, 0x19, 0xB8, 0x56, 0x2E, 0x0B, 0xE9, 0x4A, 0xF8, 0xFD, 0x28, 0xD3, + 0xDF, 0xA7, 0x1E, 0x77, 0xB1, 0x76, 0x29, 0x9A, 0xA9, 0x7C, 0xD6, 0x99, + 0xF9, 0xCE, 0x7A, 0x38, 0x96, 0xFF, 0x90, 0xF4, 0xAE, 0x04, 0x59, 0x9D, + 0x57, 0x49, 0x63, 0x71, 0x7A, 0xA8, 0x3A, 0x68, 0xF5, 0x8D, 0xAC, 0x19, + 0x36, 0xD8, 0x12, 0xE6, 0x6A, 0x58, 0x96, 0x0F, 0xC1, 0x4C, 0xF0, 0x6B, + 0xBE, 0xF2, 0x92, 0xFA, 0x1A, 0xC5, 0x43, 0x72, 0x3E, 0x9A, 0x09, 0x0D, + 0x16, 0xAA, 0xDF, 0x43, 0x42, 0x95, 0x1F, 0x14, 0x25, 0xFA, 0x05, 0x45, + 0x41, 0xE1, 0x7C, 0x00, 0xDC, 0x9D, 0x34, 0x51, 0x27, 0x27, 0xBE, 0x7A, + 0xE2, 0x63, 0x2E, 0xAC, 0x4A, 0xDA, 0x0E, 0x80, 0x34, 0x9A, 0x43, 0x2D, + 0xC8, 0xCF, 0x52, 0x23, 0x10, 0xCC, 0x3D, 0x30, 0x76, 0x85, 0xA7, 0x36, + 0xE7, 0x22, 0x5C, 0xC1, 0x00, 0x31, 0x31, 0x30, 0x61, 0xB9, 0xA7, 0xE9, + 0xCB, 0x19, 0x0F, 0x68, 0x86, 0x1E, 0x9E, 0xAE, 0xB6, 0x63, 0xF2, 0x7E, + 0x50, 0xAC, 0x4B, 0x96, 0x07, 0x2A, 0x5A, 0x9F, 0xE7, 0x10, 0x2F, 0x1F, + 0xE0, 0xEA, 0xE4, 0xB0, 0x03, 0x31, 0xEB, 0xF4, 0x40, 0x16, 0x97, 0x4E, + 0x40, 0x85, 0xD7, 0x1B, 0xB7, 0x50, 0xF8, 0xD4, 0x04, 0x0E, 0xFA, 0x89, + 0x30, 0x85, 0x61, 0x69, 0xCD, 0xC1, 0x2A, 0xAE, 0xC2, 0x9A, 0xAF, 0xA6, + 0x55, 0x41, 0xCD, 0x51, 0xCB, 0xFD, 0x0E, 0x3F, 0xEA, 0x93, 0x7C, 0x9A, + 0x1D, 0x0B, 0xEF, 0x75, 0x92, 0xB9, 0xD6, 0x6C, 0x29, 0x53, 0xDF, 0x7D, + 0x69, 0xDB, 0x0D, 0x97, 0xF1, 0x66, 0x74, 0x22, 0x0B, 0x34, 0x12, 0x69, + 0x88, 0xB2, 0x0D, 0xD4, 0xD3, 0xFB, 0xC1, 0xAA, 0x2C, 0x74, 0xB2, 0x20, + 0x2C, 0x42, 0x55, 0x01, 0x4D, 0x46, 0xAA, 0xEB, 0x62, 0xF1, 0x71, 0x4C, + 0xA7, 0xA9, 0xCE, 0x23, 0x4C, 0x63, 0xB8, 0x13, 0xF8, 0x2F, 0xE6, 0x22, + 0x4D, 0x2A, 0x7D, 0x45, 0xFC, 0xC5, 0x6C, 0xA7, 0x2A, 0x75, 0xBB, 0x2D, + 0xA0, 0x85, 0x87, 0xCA, 0x47, 0xB3, 0x15, 0xFB, 0xC4, 0x6C, 0x0E, 0x1B, + 0x54, 0xEF, 0x03, 0x4C, 0x43, 0xD9, 0xD9, 0x73, 0xC8, 0x10, 0x46, 0xE5, + 0x19, 0x38, 0x32, 0xCD, 0xB2, 0xDB, 0x8F, 0xBD, 0x08, 0xA3, 0x33, 0x01, + 0x41, 0x1F, 0x6A, 0x02, 0x07, 0x88, 0xAE, 0x98, 0x6C, 0x26, 0x05, 0xF0, + 0xA6, 0x46, 0x95, 0xE0, 0xD8, 0xB6, 0xF9, 0x56, 0xFF, 0xB7, 0xBD, 0x5D, + 0x77, 0xFC, 0x16, 0xF2, 0xEF, 0xDE, 0xFF, 0x15, 0x14, 0x28, 0xDB, 0x67, + 0x69, 0xA6, 0x7B, 0xFA, 0x17, 0xAE, 0x35, 0x1A, 0x6E, 0x4C, 0x08, 0x16, + 0x7E, 0x31, 0x4E, 0xF5, 0x67, 0x9F, 0x35, 0x8C, 0x7A, 0x78, 0xFC, 0x4F, + 0x7B, 0x1B, 0x97, 0xED, 0x08, 0x68, 0xFB, 0xE6, 0x6D, 0x7E, 0xDE, 0xBC, + 0x30, 0xA1, 0xD1, 0xB9, 0x46, 0xEA, 0xC2, 0x4D, 0x1B, 0x9B, 0xDA, 0x35, + 0x9F, 0x84, 0x47, 0x0E, 0x3E, 0x3C, 0xC2, 0x47, 0x33, 0x84, 0xEA, 0x94, + 0x0A, 0x0D, 0xDA, 0x1F, 0xCB, 0xF3, 0x0E, 0xDC, 0x33, 0x82, 0xDF, 0xF9, + 0x6A, 0x06, 0x78, 0xDE, 0xFA, 0x0F, 0xB9, 0xD8, 0xF0, 0xE8, 0x62, 0x25, + 0x7A, 0x40, 0x4F, 0x5F, 0xC3, 0x5E, 0x45, 0x20, 0x9D, 0x45, 0x43, 0x46, + 0xC8, 0x3D, 0x2B, 0x06, 0x33, 0x02, 0x7E, 0x40, 0xFF, 0x63, 0x11, 0x7D, + 0xE4, 0x49, 0xFB, 0xB2, 0xEB, 0x45, 0xEA, 0x3E, 0x43, 0x92, 0x67, 0xBD, + 0xD1, 0xF0, 0x6C, 0xE0, 0x71, 0x07, 0x87, 0xD5, 0xFF, 0x72, 0x37, 0xD9, + 0x6C, 0x54, 0x64, 0x92, 0x25, 0xEE, 0x13, 0x3C, 0x78, 0xBE, 0x63, 0x47, + 0xA1, 0xE0, 0x54, 0x9E, 0x64, 0xE7, 0x81, 0x02, 0xD7, 0x6E, 0xC5, 0xA3, + 0xE7, 0xA4, 0x8E, 0x3C, 0xC4, 0x80, 0xAD, 0xDC, 0xE6, 0x60, 0x25, 0x4D, + 0xE9, 0xFD, 0xF9, 0xC2, 0x04, 0x46 +}; \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Common/RegisterSerializer.h b/src/Cafe/HW/Latte/Common/RegisterSerializer.h new file mode 100644 index 00000000..db224c7b --- /dev/null +++ b/src/Cafe/HW/Latte/Common/RegisterSerializer.h @@ -0,0 +1,20 @@ +#pragma once +#include "util/helpers/Serializer.h" + +namespace Latte +{ + struct GPUCompactedRegisterState + { + static constexpr int const NUM_REGS = 1854; + + // tied to g_gpuRegSerializerMapping_v1 + uint32 rawArray[NUM_REGS]; + }; + + // convert GPU register state into compacted representation. Stores almost all registers, excluding ALU consts + void StoreGPURegisterState(const LatteContextRegister& contextRegister, GPUCompactedRegisterState& registerStateOut); + void LoadGPURegisterState(LatteContextRegister& contextRegisterOut, const GPUCompactedRegisterState& registerState); + + void SerializeRegisterState(GPUCompactedRegisterState& regState, MemStreamWriter& memWriter); + bool DeserializeRegisterState(GPUCompactedRegisterState& regState, MemStreamReader& memReader); +} \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Common/ShaderSerializer.cpp b/src/Cafe/HW/Latte/Common/ShaderSerializer.cpp new file mode 100644 index 00000000..771783a2 --- /dev/null +++ b/src/Cafe/HW/Latte/Common/ShaderSerializer.cpp @@ -0,0 +1,3686 @@ +#include "Cafe/HW/Latte/Common/ShaderSerializer.h" +#include <boost/container/small_vector.hpp> +#include <zstd.h> +#include <zlib.h> + +// compression dictionary for Latte shader code, initialized on boot +ZSTD_CDict* s_c_shaderDict{}; +ZSTD_DDict* s_d_shaderDict{}; + +namespace Latte +{ + void SerializeShaderProgram(void* shaderProg, uint32 size, MemStreamWriter& memWriter) + { + memWriter.writeBE<uint8>(1); // version + // compress shader using zstd level 6 + boost::container::small_vector<uint8, 4096> compressedBuf; + compressedBuf.resize(ZSTD_compressBound(size)); + ZSTD_CCtx* const cctx = ZSTD_createCCtx(); + size_t compressedSize = ZSTD_compress_usingCDict(cctx, compressedBuf.data(), compressedBuf.size(), shaderProg, size, s_c_shaderDict); + ZSTD_freeCCtx(cctx); + cemu_assert(!ZSTD_isError(compressedSize)); + memWriter.writeBE<uint32>(size); + memWriter.writeBE<uint32>((uint32)compressedSize); + memWriter.writeData(compressedBuf.data(), compressedSize); + } + + bool DeserializeShaderProgram(std::vector<uint8>& progData, MemStreamReader& memReader) + { + if (memReader.readBE<uint8>() != 1) + return false; // unknown version + uint32 progSize = memReader.readBE<uint32>(); + uint32 compressedShaderSize = memReader.readBE<uint32>(); + if (memReader.hasError()) + return false; + if (progSize == 0 || progSize >= 1024 * 1024 * 128) + return false; + if (compressedShaderSize == 0 || compressedShaderSize >= 1024 * 1024 * 128) + return false; + progData.resize(progSize); + auto compressedShaderData = memReader.readDataNoCopy(compressedShaderSize); + if (memReader.hasError()) + return false; + // decompress + ZSTD_DCtx* const dctx = ZSTD_createDCtx(); + size_t decompressedSize = ZSTD_decompress_usingDDict(dctx, progData.data(), progData.size(), compressedShaderData.data(), compressedShaderData.size(), s_d_shaderDict); + ZSTD_freeDCtx(dctx); + if (decompressedSize != progSize) + return false; + return true; + } +}; + +extern const uint8 s_shaderDict[]; + +RunAtCemuBoot _loadShaderCompressionDictionary([]() +{ + // decompress and load dict + static std::vector<uint8> s_shaderDictData; + s_shaderDictData.resize(0x1B800); + ZSTD_decompress(s_shaderDictData.data(), s_shaderDictData.size(), s_shaderDict, 0xA985); + s_c_shaderDict = ZSTD_createCDict(s_shaderDictData.data(), s_shaderDictData.size(), 6); + s_d_shaderDict = ZSTD_createDDict(s_shaderDictData.data(), s_shaderDictData.size()); + cemu_assert_debug(s_c_shaderDict); + cemu_assert_debug(s_d_shaderDict); +}); + +const uint8 s_shaderDict[0xA985] = // 0xA985 Uncompressed: 0x1B800 +{ + 0x28, 0xB5, 0x2F, 0xFD, 0xA4, 0x00, 0xB8, 0x01, 0x00, 0x54, 0xD4, 0x02, + 0x9A, 0xE6, 0xF3, 0xBF, 0x54, 0x20, 0x9C, 0x11, 0x6D, 0x3A, 0x8F, 0x30, + 0xAF, 0xC4, 0x16, 0xAE, 0x9D, 0x11, 0xC3, 0x09, 0x13, 0xCD, 0xE1, 0x18, + 0xAB, 0xC1, 0x05, 0xD6, 0x62, 0x83, 0x83, 0xE3, 0x80, 0x9F, 0x58, 0x48, + 0x33, 0xD7, 0x72, 0xE6, 0x42, 0x40, 0x91, 0x79, 0x32, 0xFE, 0x24, 0xD4, + 0xDA, 0x80, 0x21, 0xFE, 0x5C, 0x06, 0xA3, 0x44, 0x05, 0x2A, 0x0C, 0x87, + 0x01, 0x8D, 0x01, 0xD0, 0x98, 0xD0, 0x33, 0x17, 0xC6, 0xCD, 0x33, 0x4C, + 0xC8, 0x8A, 0xBB, 0x0E, 0x18, 0x70, 0x25, 0x4C, 0x59, 0x51, 0xD4, 0xB6, + 0xFF, 0x7F, 0x5B, 0xDB, 0x29, 0xCB, 0x0B, 0x3A, 0x0C, 0xB1, 0x0B, 0xA7, + 0xCF, 0x4E, 0x5F, 0x7E, 0x07, 0x71, 0x26, 0xA6, 0x14, 0x31, 0x8F, 0xB3, + 0x2E, 0x78, 0x3A, 0xCD, 0xC7, 0xBF, 0x54, 0xB1, 0x4B, 0x66, 0x16, 0xA3, + 0x5D, 0x0B, 0x83, 0x55, 0xEE, 0x66, 0x63, 0x3C, 0x77, 0x52, 0xFA, 0x57, + 0xE6, 0x6A, 0x95, 0xDA, 0xBD, 0xB2, 0xB9, 0xB7, 0xD5, 0xBC, 0xB3, 0x7F, + 0xED, 0x6C, 0x89, 0x5C, 0xA8, 0x0F, 0xAE, 0x56, 0x49, 0xBF, 0xF3, 0x67, + 0x0D, 0x8F, 0x5A, 0x43, 0xA9, 0x0F, 0x20, 0xE5, 0x52, 0xAA, 0xAA, 0x82, + 0x83, 0x54, 0x53, 0x17, 0x73, 0x06, 0xC4, 0xF8, 0x9D, 0x96, 0x12, 0xAB, + 0xF7, 0xFD, 0x2E, 0x4B, 0x39, 0xA9, 0x0B, 0x0E, 0x5E, 0x22, 0x1F, 0xCC, + 0x8C, 0x83, 0x17, 0xF5, 0xC1, 0xA4, 0x2A, 0x22, 0x9C, 0x12, 0x43, 0xF1, + 0x46, 0x45, 0x77, 0xDB, 0x6E, 0xD8, 0x51, 0x94, 0xF3, 0xF6, 0xEC, 0x38, + 0x58, 0x34, 0xF8, 0x25, 0xAA, 0xF4, 0x98, 0x5D, 0x2E, 0x62, 0x4B, 0x50, + 0x80, 0x4B, 0xBF, 0xFB, 0x51, 0x23, 0xB6, 0x05, 0xE8, 0x93, 0xE9, 0x88, + 0xD7, 0x95, 0xFD, 0x95, 0xD1, 0xFE, 0x19, 0x75, 0xE8, 0x0F, 0x7E, 0xB4, + 0x53, 0x08, 0x91, 0xC9, 0xF7, 0x75, 0x8A, 0x29, 0xDE, 0xD0, 0xB3, 0x05, + 0xE9, 0x19, 0x9D, 0xFF, 0x25, 0x3F, 0xED, 0xD2, 0xCD, 0x2E, 0xB7, 0x46, + 0xCC, 0x4F, 0x04, 0xA6, 0x06, 0x36, 0xA9, 0x1C, 0xE0, 0x41, 0x5D, 0xE3, + 0x9A, 0x8E, 0xCF, 0x49, 0xC0, 0xE6, 0x26, 0xB2, 0x62, 0x32, 0x73, 0x80, + 0xE7, 0xD7, 0xB8, 0xC9, 0xA5, 0x4D, 0x2A, 0x6D, 0xA2, 0x7D, 0x12, 0xBF, + 0x1D, 0xF6, 0x44, 0x6B, 0xB0, 0x50, 0x95, 0x1F, 0x8D, 0x23, 0x52, 0x4D, + 0x28, 0xD8, 0xE3, 0x99, 0xD6, 0xB0, 0x14, 0xE3, 0xF1, 0xE2, 0xD3, 0xF1, + 0xF8, 0x2A, 0xA6, 0xC6, 0xAF, 0x24, 0x72, 0xEF, 0xF5, 0x10, 0xF9, 0xFF, + 0x6F, 0xDD, 0xA5, 0x4E, 0x04, 0x3F, 0x7A, 0x5F, 0x9E, 0xEE, 0x9D, 0xB4, + 0x3D, 0x93, 0x85, 0xF5, 0x55, 0xEB, 0xD4, 0xE7, 0xCE, 0xF1, 0x9C, 0x49, + 0x7D, 0xB4, 0x33, 0xCB, 0xB2, 0x87, 0x5A, 0xE6, 0x95, 0x59, 0xA6, 0x44, + 0x1F, 0x2C, 0xEB, 0x92, 0x2C, 0xE3, 0xCE, 0xEC, 0x2A, 0x39, 0x9B, 0x4D, + 0x0B, 0x49, 0x7D, 0x5D, 0x2F, 0xCD, 0xB1, 0x36, 0x3D, 0xA3, 0xAB, 0xD7, + 0xF5, 0xF4, 0x2E, 0xB5, 0x72, 0xE5, 0xCF, 0x8A, 0x4B, 0x71, 0x85, 0x62, + 0x1A, 0x2C, 0x2B, 0xFF, 0xA5, 0x33, 0xE5, 0xE8, 0x43, 0x63, 0xF7, 0xF9, + 0xC1, 0x93, 0xCE, 0x80, 0x43, 0xAA, 0xFD, 0x05, 0x87, 0xD4, 0xFF, 0xD4, + 0x65, 0x04, 0xC1, 0x21, 0x30, 0x94, 0x46, 0xCC, 0x87, 0xE0, 0x4C, 0x03, + 0x53, 0x17, 0x18, 0xBD, 0x2C, 0xDD, 0x6F, 0x56, 0x4C, 0x52, 0xAE, 0x09, + 0xFF, 0x96, 0x56, 0x93, 0xDF, 0x9B, 0x54, 0x50, 0x5D, 0x4D, 0x40, 0x7B, + 0x83, 0x9F, 0x82, 0xF3, 0x0D, 0x8C, 0x09, 0x67, 0xA6, 0x60, 0x13, 0x35, + 0xDF, 0xFD, 0xF8, 0x35, 0xB5, 0x50, 0xBC, 0xC2, 0xD2, 0x51, 0xCA, 0x42, + 0xAE, 0x5C, 0xCD, 0x06, 0x2D, 0xA4, 0x45, 0x6E, 0xD4, 0x6A, 0x2A, 0x98, + 0xAC, 0xDA, 0xE9, 0x35, 0xAF, 0xC7, 0xDA, 0xA0, 0x67, 0x94, 0x22, 0xDA, + 0x3F, 0xF4, 0x31, 0xAD, 0x7D, 0x34, 0xAE, 0x59, 0xBA, 0xFF, 0xD4, 0x3E, + 0xD3, 0x15, 0x3C, 0x0E, 0xC2, 0xA6, 0x0D, 0xEE, 0x6D, 0x7E, 0x7F, 0xB8, + 0xA6, 0x44, 0xFF, 0xF8, 0x0E, 0x28, 0x29, 0x88, 0xB7, 0x20, 0xF7, 0x7E, + 0xC9, 0xEF, 0xCB, 0x1B, 0xE1, 0x78, 0x54, 0xB2, 0xFC, 0x18, 0x1F, 0x08, + 0x41, 0x5E, 0x4D, 0xD1, 0x08, 0x87, 0x28, 0xAE, 0xA6, 0x26, 0xC2, 0x59, + 0xC8, 0xA4, 0x3D, 0xB4, 0xF9, 0x9B, 0x8B, 0x54, 0xA7, 0x65, 0x8E, 0x22, + 0xB1, 0x67, 0x16, 0xF8, 0x5B, 0xBA, 0xAD, 0x59, 0xB6, 0xFC, 0xD8, 0xFC, + 0x25, 0xF5, 0xFF, 0xFF, 0x46, 0x88, 0x39, 0x56, 0xA8, 0x41, 0x8C, 0x25, + 0xE4, 0x10, 0x6B, 0x82, 0x0E, 0x29, 0xE3, 0x2B, 0x03, 0x1D, 0x80, 0xB8, + 0xE8, 0x99, 0xB6, 0x82, 0x22, 0xD4, 0x67, 0xD9, 0xB5, 0x6B, 0x6F, 0xDA, + 0x9D, 0x00, 0x22, 0xD5, 0xB3, 0x7E, 0x91, 0xFA, 0x8A, 0xBA, 0x5D, 0xC1, + 0x08, 0x66, 0xA4, 0x41, 0x0A, 0x3A, 0xA8, 0xC0, 0x0F, 0xEC, 0xB0, 0x86, + 0x95, 0x1D, 0x19, 0x58, 0xE9, 0x11, 0x00, 0x12, 0x98, 0xDC, 0xE4, 0x3F, + 0x69, 0x3B, 0xE9, 0xC7, 0xED, 0x4F, 0x44, 0x0F, 0x1D, 0xA5, 0x92, 0x25, + 0x31, 0xB4, 0x96, 0xF1, 0x8F, 0xFA, 0x27, 0xA1, 0xFA, 0xA1, 0xD0, 0x6D, + 0x77, 0xFC, 0xB2, 0x8D, 0x64, 0xDF, 0x7C, 0x5F, 0x3E, 0xD2, 0x5E, 0xC9, + 0x7F, 0xB9, 0x48, 0x36, 0x4B, 0xBE, 0x2F, 0x7A, 0x0D, 0x07, 0x46, 0x5F, + 0x59, 0xC9, 0xA4, 0x9C, 0x78, 0x32, 0xBE, 0x04, 0x33, 0x79, 0x34, 0x2F, + 0x91, 0xCB, 0x1F, 0x67, 0x9E, 0x84, 0x4F, 0x5C, 0xC9, 0x39, 0x98, 0xF6, + 0x9D, 0x59, 0xF4, 0xD7, 0xAB, 0x86, 0x5F, 0x14, 0x55, 0x3C, 0x0E, 0x8C, + 0xE4, 0x99, 0x2C, 0x51, 0x70, 0xC7, 0x31, 0x49, 0xA1, 0xE9, 0x83, 0xAF, + 0x67, 0xBC, 0x94, 0x2B, 0x77, 0xB0, 0x32, 0x43, 0x40, 0x63, 0x48, 0x07, + 0x5A, 0x83, 0xF2, 0x4D, 0x81, 0x7B, 0x89, 0x1E, 0xE5, 0x81, 0xF2, 0xBF, + 0x29, 0x8B, 0x67, 0x8B, 0x0F, 0x75, 0x20, 0x92, 0x82, 0x28, 0xE1, 0xEB, + 0x1D, 0xA5, 0x58, 0xFC, 0x60, 0x32, 0x48, 0x98, 0x91, 0x5B, 0x75, 0xE1, + 0x83, 0xAF, 0x1C, 0x41, 0xFA, 0x99, 0x54, 0xA6, 0x64, 0xCA, 0xE0, 0x64, + 0xBC, 0x6F, 0x58, 0xAC, 0x08, 0x69, 0x0E, 0xFE, 0x29, 0x2C, 0x56, 0x78, + 0x3D, 0xB5, 0xE6, 0xDC, 0x8E, 0xCD, 0x94, 0xF2, 0x78, 0xB8, 0x83, 0x37, + 0xEC, 0x54, 0x11, 0xED, 0x21, 0x49, 0xC6, 0xB6, 0x56, 0x6B, 0x7F, 0x52, + 0xB5, 0xD6, 0x85, 0xAC, 0xD1, 0x45, 0x9E, 0xC1, 0x7D, 0x15, 0xF3, 0x65, + 0xBB, 0x74, 0xAF, 0xB8, 0x4D, 0x56, 0x5A, 0xB9, 0x02, 0xFC, 0x9B, 0xBC, + 0xD1, 0x21, 0x6F, 0x45, 0x41, 0x39, 0x31, 0xC9, 0x73, 0x7C, 0x7B, 0xA2, + 0x42, 0x7F, 0xC3, 0x1E, 0x22, 0x36, 0x5F, 0xE3, 0xED, 0x53, 0x2C, 0xDF, + 0x9D, 0xBE, 0x05, 0xF5, 0xC1, 0x9A, 0x1C, 0x96, 0xC3, 0x97, 0xE8, 0xA3, + 0x37, 0x45, 0x9E, 0x79, 0x98, 0x88, 0x7B, 0x35, 0x4C, 0xF4, 0xBF, 0x71, + 0x50, 0x4D, 0x66, 0xEE, 0xC6, 0xC5, 0x49, 0xE5, 0xDA, 0x4C, 0x6F, 0x12, + 0x3C, 0x3C, 0x58, 0x68, 0x1F, 0xFE, 0xFC, 0x11, 0x81, 0xE6, 0x5C, 0x90, + 0x5C, 0xAE, 0xB7, 0x73, 0x25, 0x2F, 0xA5, 0xB8, 0xE0, 0x68, 0x2A, 0x17, + 0x2F, 0x8D, 0xC1, 0x45, 0x1F, 0x87, 0xE9, 0xC3, 0xFD, 0xD0, 0xF1, 0x2B, + 0x9F, 0x83, 0x8B, 0x99, 0x63, 0x95, 0xF3, 0x58, 0x7A, 0xFB, 0x0F, 0x79, + 0x59, 0x4A, 0x03, 0x66, 0xFF, 0xF7, 0x69, 0x98, 0xD6, 0xEB, 0xA9, 0x62, + 0x87, 0x60, 0x6C, 0x66, 0xBF, 0xFB, 0x65, 0x4C, 0x94, 0xD3, 0x22, 0xF7, + 0xFF, 0xBB, 0x94, 0x4A, 0xC0, 0xEE, 0x3F, 0x36, 0x4F, 0x5E, 0x0F, 0x97, + 0xE9, 0xC7, 0x11, 0xEA, 0x97, 0xF8, 0x95, 0x1E, 0xF7, 0xF5, 0x0C, 0x99, + 0xD3, 0x1D, 0x57, 0xC6, 0xB3, 0x45, 0x9E, 0xF9, 0xBB, 0x46, 0xA7, 0x56, + 0xB9, 0xFA, 0xEC, 0xC1, 0x32, 0xCE, 0xEE, 0xAA, 0xF8, 0xD7, 0xE6, 0x47, + 0x95, 0x5E, 0xF9, 0x36, 0x8D, 0x09, 0xFD, 0x3F, 0x74, 0x9D, 0xC1, 0xFF, + 0xF4, 0x5D, 0x6A, 0x59, 0xAD, 0x4B, 0xAA, 0xFF, 0x07, 0x8E, 0x99, 0x41, + 0xF2, 0xA0, 0xD5, 0x7E, 0x95, 0xAD, 0x4B, 0x36, 0x84, 0xCB, 0x67, 0x4E, + 0xC7, 0x4A, 0x46, 0xE1, 0x78, 0x21, 0x8A, 0xE4, 0x7F, 0xF0, 0x43, 0x52, + 0xB3, 0xD9, 0xBF, 0x38, 0x21, 0x1D, 0xDC, 0xBF, 0xD8, 0xA5, 0x23, 0xDD, + 0xA5, 0xFE, 0x31, 0x38, 0x7E, 0xF7, 0x27, 0x43, 0xEE, 0xCC, 0x9E, 0x0E, + 0x9B, 0x49, 0x8E, 0x67, 0xFC, 0x74, 0xB4, 0xE4, 0x1F, 0x38, 0x61, 0x10, + 0x43, 0x1D, 0x2B, 0x63, 0x97, 0xBA, 0x18, 0xFF, 0xD3, 0xD1, 0xFB, 0x62, + 0xCB, 0x0D, 0xFE, 0x5E, 0x8C, 0xF8, 0xA1, 0x8C, 0x72, 0x3D, 0x75, 0x24, + 0xB9, 0x3B, 0x64, 0xF0, 0xF6, 0x3F, 0xD9, 0xA5, 0x98, 0x78, 0x3F, 0x7A, + 0x7F, 0x10, 0xAE, 0xF9, 0x3B, 0xC3, 0xB3, 0x01, 0xDD, 0x3D, 0xFD, 0x4B, + 0x3F, 0x19, 0x7C, 0x05, 0x4F, 0xBA, 0x15, 0xFD, 0x37, 0x6C, 0xC8, 0x1F, + 0xA8, 0xE2, 0xDB, 0xE8, 0xBA, 0x54, 0x96, 0x2E, 0xF7, 0x9D, 0xC6, 0x92, + 0x19, 0xDD, 0xD2, 0x96, 0xF8, 0xDC, 0xC4, 0x70, 0x29, 0x73, 0x20, 0x64, + 0xC6, 0xF4, 0xC1, 0xA3, 0x06, 0x94, 0x19, 0x5D, 0xE8, 0x8C, 0x18, 0x18, + 0xDC, 0x32, 0xB2, 0x98, 0x54, 0x84, 0xAC, 0x8C, 0xBF, 0xE2, 0xB3, 0x35, + 0xD5, 0x91, 0x9A, 0x2C, 0xDD, 0x67, 0x5C, 0x0C, 0x4F, 0x96, 0x8E, 0x14, + 0xF2, 0x13, 0xF3, 0x39, 0x4F, 0x2E, 0xAF, 0xE5, 0xB7, 0x2A, 0xB3, 0xCA, + 0x18, 0x42, 0x28, 0x97, 0x44, 0x88, 0xD7, 0x5C, 0xF3, 0xC0, 0x5E, 0xE6, + 0x89, 0x5C, 0x9D, 0x99, 0x0A, 0xD1, 0x71, 0xB5, 0xCA, 0x13, 0x48, 0x0A, + 0xB1, 0x11, 0x61, 0x3A, 0x43, 0x35, 0x5E, 0x21, 0x29, 0x96, 0x7F, 0x46, + 0x9F, 0xB0, 0x59, 0x44, 0x89, 0x63, 0x8E, 0x62, 0x23, 0xAE, 0xDF, 0x8D, + 0x4E, 0x96, 0xE8, 0x2A, 0x76, 0x1C, 0x98, 0x16, 0x5C, 0xCE, 0x4D, 0xF7, + 0x50, 0x35, 0x93, 0x25, 0xD8, 0xC1, 0xBB, 0x42, 0x38, 0x45, 0xE7, 0xB7, + 0x5B, 0xA6, 0x5F, 0x67, 0xCB, 0x8D, 0xC3, 0x73, 0x0A, 0xCF, 0x77, 0x94, + 0xE3, 0x81, 0x88, 0xFF, 0xD3, 0xD5, 0x69, 0xF2, 0x5A, 0x60, 0x33, 0xE5, + 0x7E, 0x4E, 0xDC, 0x0D, 0x9E, 0xBF, 0xFA, 0x39, 0x81, 0xE5, 0x09, 0xC4, + 0x25, 0x98, 0x76, 0xED, 0xFB, 0x2B, 0xD7, 0xE9, 0x7F, 0x6B, 0x5F, 0x8B, + 0x61, 0x18, 0x3D, 0x1B, 0xE2, 0x71, 0x5E, 0xA3, 0x57, 0x7E, 0xC4, 0x69, + 0x33, 0x88, 0xEB, 0xA0, 0x9B, 0x41, 0xDF, 0xC7, 0xEF, 0xBC, 0x4F, 0x1D, + 0x43, 0xD3, 0x47, 0x8C, 0xD5, 0xD1, 0x33, 0xB7, 0xF1, 0xFF, 0x57, 0x6E, + 0xAE, 0x50, 0x7F, 0x95, 0x32, 0x71, 0x25, 0x0B, 0x8B, 0xF6, 0xF6, 0x83, + 0x5D, 0xB2, 0x5A, 0x88, 0x7F, 0xCF, 0x95, 0x5B, 0xB8, 0x46, 0x6B, 0x61, + 0xF9, 0x4F, 0x27, 0x7B, 0x65, 0xF7, 0xFF, 0x4D, 0x24, 0xA5, 0x81, 0xCF, + 0x98, 0x87, 0x23, 0x46, 0xD7, 0xDA, 0xBF, 0x6F, 0x40, 0xC4, 0x08, 0xB4, + 0xEF, 0xCE, 0x03, 0x8E, 0xD1, 0x57, 0x86, 0x3C, 0x57, 0x88, 0x6B, 0x85, + 0x5E, 0x8A, 0x5A, 0xB0, 0x38, 0xFD, 0xD6, 0x37, 0xE5, 0x60, 0x95, 0xA3, + 0xD9, 0x4E, 0x94, 0xD3, 0x79, 0x0E, 0x21, 0xC0, 0x60, 0xE2, 0x81, 0xF1, + 0xDC, 0x25, 0x1D, 0x2D, 0x4C, 0xE7, 0x54, 0x26, 0x1F, 0xF7, 0xCA, 0xDE, + 0xCE, 0x26, 0x52, 0x1C, 0x58, 0xFE, 0x8F, 0xE8, 0x76, 0x04, 0x53, 0xDE, + 0xF4, 0xE6, 0xF8, 0xC5, 0x60, 0x23, 0x9D, 0x4B, 0x99, 0x8C, 0xE7, 0xDA, + 0xE4, 0xAF, 0x63, 0x98, 0x73, 0xA0, 0xAF, 0xCA, 0xB3, 0xBC, 0x30, 0x1A, + 0xDB, 0x1E, 0x3F, 0x73, 0x33, 0x85, 0x3B, 0xA3, 0x8F, 0x0A, 0x58, 0x96, + 0x12, 0x43, 0xE1, 0xBA, 0x0C, 0x32, 0x73, 0xE3, 0x40, 0xE4, 0xEA, 0x95, + 0x85, 0x3D, 0x07, 0xCA, 0xF0, 0xFF, 0xB7, 0xCB, 0xE4, 0xBD, 0x25, 0x72, + 0x3C, 0x23, 0x88, 0xDF, 0xF5, 0xCB, 0x1C, 0xA6, 0xCE, 0xFA, 0x61, 0x5A, + 0x7C, 0x1D, 0xA3, 0xC5, 0x63, 0x5A, 0x28, 0xB9, 0x90, 0x30, 0x8C, 0x61, + 0x89, 0x83, 0x45, 0x57, 0xC4, 0x0F, 0xB5, 0xBD, 0x36, 0x6F, 0xA7, 0xC2, + 0x36, 0xCE, 0xDC, 0x56, 0x3E, 0x41, 0xC0, 0x6A, 0xD2, 0x42, 0xEC, 0x5D, + 0x56, 0x32, 0xB5, 0x6B, 0xC7, 0xF7, 0x7F, 0x24, 0xC2, 0x70, 0x01, 0x0B, + 0x94, 0xA8, 0xD1, 0x22, 0x44, 0x12, 0x56, 0x98, 0x64, 0xCF, 0xFC, 0x01, + 0x02, 0xB8, 0x2D, 0xC1, 0xB4, 0xE4, 0xCA, 0x17, 0xA7, 0xC5, 0xF2, 0x97, + 0x4A, 0x1C, 0x3B, 0x57, 0x42, 0xBB, 0x24, 0xDA, 0x17, 0x13, 0x51, 0x9C, + 0x17, 0x89, 0x3B, 0x79, 0x48, 0x9E, 0xC9, 0x8A, 0x14, 0x2E, 0x3F, 0x8B, + 0x52, 0x98, 0x0C, 0xAF, 0xBC, 0x48, 0xC0, 0x44, 0x7C, 0x50, 0xC0, 0x46, + 0xC1, 0xA0, 0x4A, 0xC6, 0x2D, 0xCB, 0x13, 0x84, 0x95, 0x5E, 0x32, 0x28, + 0x8E, 0x29, 0x03, 0xFC, 0x1B, 0x97, 0x5E, 0xFF, 0x7F, 0x83, 0xF8, 0x63, + 0x78, 0x9E, 0xE7, 0x79, 0x33, 0x34, 0xA9, 0x16, 0x85, 0x7B, 0x15, 0x6A, + 0xE9, 0x0D, 0xBF, 0x7B, 0xDA, 0x27, 0x3D, 0x08, 0x72, 0x2E, 0xF8, 0x1C, + 0xE2, 0xC3, 0xD1, 0x73, 0x60, 0x99, 0x79, 0xE8, 0x35, 0x49, 0x2F, 0x49, + 0x29, 0x59, 0x65, 0xE5, 0x2C, 0xD2, 0xC1, 0x23, 0x6F, 0x30, 0xDF, 0x75, + 0x69, 0x65, 0xA4, 0xAB, 0x31, 0xB2, 0x8F, 0xEC, 0x8D, 0xB1, 0x89, 0x5F, + 0x75, 0x91, 0x39, 0x16, 0x9D, 0x70, 0x87, 0xCD, 0xB4, 0x61, 0x32, 0xC6, + 0x4A, 0x3C, 0x9A, 0xD7, 0x48, 0xAE, 0xE8, 0x5C, 0x9B, 0xBF, 0x52, 0x8D, + 0x33, 0x70, 0xD0, 0x47, 0xEE, 0x2C, 0x1D, 0x31, 0x0A, 0x4F, 0xCC, 0x93, + 0x3D, 0xAE, 0x65, 0xF4, 0xCE, 0x98, 0x28, 0x34, 0x4F, 0xD1, 0x27, 0x5A, + 0x61, 0xC9, 0x39, 0x8C, 0x9D, 0xB6, 0x14, 0xEB, 0xB4, 0xD7, 0x93, 0xE4, + 0xD5, 0xA1, 0xE7, 0xA1, 0xBD, 0x59, 0x8E, 0x31, 0xE9, 0x40, 0x33, 0xC7, + 0xA8, 0x1A, 0xAB, 0xB7, 0xC1, 0xD5, 0xFF, 0x37, 0x4E, 0x92, 0xD5, 0x49, + 0x31, 0x8D, 0xCD, 0x1E, 0xCD, 0x69, 0xB6, 0xE5, 0xF0, 0x2C, 0xA2, 0xA3, + 0xA9, 0x1F, 0xCD, 0x2F, 0x11, 0x27, 0xB7, 0x4D, 0x2B, 0x5D, 0xD1, 0x6B, + 0x1D, 0x9E, 0xA5, 0x1E, 0xF6, 0xEB, 0xB1, 0x9D, 0x56, 0x78, 0xBD, 0xF8, + 0x38, 0xF4, 0xF9, 0x17, 0xFB, 0xFD, 0xFF, 0xFF, 0x3F, 0xBE, 0x10, 0x3C, + 0x13, 0x68, 0xBB, 0x5F, 0xC0, 0x97, 0x73, 0x25, 0x34, 0xA5, 0x8B, 0x7A, + 0xFF, 0xA6, 0x2F, 0x87, 0xDC, 0x73, 0xFB, 0xF9, 0x6F, 0xAC, 0x2D, 0x3C, + 0xE1, 0x2B, 0xB9, 0x76, 0x3B, 0x1D, 0x0D, 0x2E, 0xD2, 0xC7, 0xF1, 0x49, + 0xC1, 0xCD, 0xA4, 0xC2, 0xB9, 0x4C, 0xC2, 0x94, 0xE9, 0x2B, 0xB9, 0x36, + 0x79, 0x2D, 0x95, 0x89, 0x2B, 0x55, 0x26, 0x95, 0x2B, 0xCE, 0xAC, 0x5A, + 0x26, 0x2E, 0xB7, 0x46, 0x8C, 0x74, 0x6B, 0xFE, 0xF3, 0xFF, 0x2B, 0x61, + 0xFE, 0x78, 0x13, 0x67, 0x6C, 0x8D, 0x93, 0x84, 0x55, 0x67, 0x9D, 0x88, + 0x0F, 0xC9, 0x3B, 0xC7, 0x92, 0xCF, 0xF8, 0x0F, 0x41, 0x57, 0xB8, 0xC0, + 0x78, 0x63, 0x7D, 0x65, 0x9A, 0x70, 0x25, 0x0B, 0x17, 0x20, 0xF8, 0x53, + 0xF8, 0x1A, 0x3F, 0x5A, 0x32, 0x47, 0x8F, 0x8B, 0x65, 0xEE, 0x34, 0x40, + 0xF7, 0x55, 0x2C, 0xE3, 0x72, 0x01, 0xA1, 0x8A, 0x88, 0xD0, 0x07, 0x44, + 0xF6, 0x70, 0x70, 0xB9, 0xE0, 0x4C, 0x2A, 0x0A, 0x4D, 0xD1, 0x78, 0x0E, + 0x88, 0x25, 0x7B, 0x3D, 0x17, 0x29, 0xD7, 0x63, 0x43, 0x94, 0x2B, 0x29, + 0xAB, 0xC1, 0x73, 0xFB, 0x68, 0x3E, 0x5E, 0x1A, 0xB1, 0x93, 0xEE, 0x77, + 0x2F, 0xE1, 0xD8, 0x02, 0x34, 0xCF, 0xEA, 0x21, 0x3E, 0x27, 0xA0, 0xEB, + 0xAC, 0xD3, 0x38, 0x4F, 0x5C, 0x9A, 0x74, 0xFB, 0xAC, 0x23, 0x1C, 0x4D, + 0x8B, 0x1F, 0x2A, 0x7C, 0xFE, 0xCC, 0x9A, 0x2F, 0x2C, 0xC4, 0x9D, 0xDB, + 0x91, 0x63, 0xF3, 0x07, 0x71, 0xB9, 0x45, 0xC8, 0x92, 0xB5, 0x40, 0x0F, + 0x93, 0xBD, 0x96, 0xCC, 0xF5, 0x6F, 0xCF, 0x88, 0xC9, 0xE0, 0xDF, 0xF8, + 0xEF, 0xD1, 0xFD, 0xDF, 0x27, 0xC2, 0x56, 0xF0, 0x19, 0xB6, 0xFF, 0x4B, + 0x47, 0xD0, 0xE6, 0x21, 0xA2, 0x5D, 0x23, 0x3A, 0x2E, 0x7F, 0x8A, 0x9A, + 0x05, 0x96, 0x2F, 0xCE, 0x2B, 0xDF, 0xCC, 0x20, 0x48, 0x04, 0x2B, 0x83, + 0xBD, 0xB1, 0x5E, 0x3A, 0x10, 0xEE, 0xD9, 0x62, 0xF5, 0xA2, 0xEE, 0x53, + 0x8F, 0xAA, 0xE3, 0x4B, 0x67, 0xED, 0xFF, 0xA5, 0x2F, 0xC1, 0xF4, 0x30, + 0xD1, 0xED, 0x97, 0xE8, 0x67, 0xE9, 0xDF, 0x14, 0x99, 0xF8, 0x1D, 0x39, + 0x23, 0xF6, 0xB0, 0xE7, 0x41, 0x27, 0x6D, 0x36, 0xFF, 0xBF, 0x94, 0x23, + 0x7B, 0x61, 0x7C, 0xE4, 0x7A, 0x6A, 0x3E, 0xB9, 0x89, 0x4D, 0xAD, 0x16, + 0x3E, 0xF1, 0xF1, 0x64, 0xB5, 0xF8, 0x56, 0xED, 0x7F, 0x9F, 0x5E, 0x0B, + 0xCD, 0xB3, 0x48, 0x1C, 0x1E, 0x2D, 0xA4, 0x25, 0x1E, 0xED, 0xEB, 0x51, + 0xC7, 0x37, 0x4B, 0xBB, 0xB1, 0xB3, 0x46, 0x3F, 0xCF, 0x1A, 0xDD, 0x8E, + 0xC2, 0x1E, 0x36, 0xFF, 0x39, 0xD0, 0xBE, 0x27, 0xB6, 0x4C, 0x7F, 0xA3, + 0xA9, 0xF8, 0x64, 0xB4, 0x69, 0xF7, 0x0F, 0xF1, 0x2E, 0x6D, 0x19, 0x68, + 0xDF, 0x6F, 0x95, 0x86, 0xBB, 0x37, 0x7D, 0x6B, 0xDE, 0x53, 0x1A, 0x47, + 0x6F, 0x7F, 0x73, 0x45, 0xAA, 0x8B, 0xFA, 0xE5, 0x11, 0xB7, 0xD8, 0xFC, + 0x14, 0xCA, 0x23, 0x5D, 0x48, 0x8A, 0xDF, 0x0A, 0x37, 0x14, 0x39, 0xE2, + 0xA2, 0x26, 0xFA, 0x4C, 0xB4, 0x59, 0xFF, 0x3F, 0x8C, 0xB2, 0xF5, 0x8A, + 0xCD, 0xDF, 0x36, 0xBB, 0xFF, 0x3F, 0xC1, 0x30, 0x7A, 0x00, 0x16, 0x04, + 0x25, 0x1F, 0x20, 0x8D, 0xE1, 0xD4, 0x80, 0x2B, 0xC6, 0xB0, 0xC3, 0x0A, + 0x6E, 0xA0, 0x1C, 0x25, 0x13, 0x5D, 0x44, 0x2D, 0xF9, 0x1F, 0x09, 0xA2, + 0x93, 0x71, 0x19, 0x19, 0x8F, 0x7E, 0x17, 0x3A, 0xE9, 0x33, 0x9E, 0xFF, + 0xD0, 0x65, 0x6C, 0xA8, 0xFE, 0x11, 0x94, 0x2E, 0x7B, 0x16, 0x22, 0x51, + 0x56, 0x60, 0x2F, 0x43, 0xEB, 0xD1, 0x47, 0xA3, 0x1F, 0xDB, 0x2F, 0xBA, + 0xC9, 0x47, 0x36, 0x88, 0xEE, 0xE4, 0x3F, 0x41, 0xA1, 0x3C, 0x54, 0xA1, + 0x38, 0xE9, 0x23, 0x93, 0x0E, 0x25, 0x8C, 0x33, 0xFC, 0x74, 0xA9, 0xBE, + 0x18, 0x25, 0x96, 0xFD, 0x49, 0xF9, 0xFE, 0x7F, 0x49, 0x35, 0xFE, 0x67, + 0xDD, 0x07, 0x9E, 0x2C, 0xEC, 0x77, 0xA8, 0x52, 0xE7, 0xB0, 0xC7, 0x9E, + 0x28, 0x4B, 0x86, 0x7E, 0x39, 0x9B, 0xE9, 0x6D, 0xC3, 0xE4, 0xED, 0x54, + 0xF1, 0xA5, 0x13, 0x0A, 0x0E, 0xB5, 0xE5, 0x73, 0x37, 0x50, 0xFC, 0x0D, + 0x38, 0x4D, 0x35, 0x9D, 0xD5, 0x42, 0x3F, 0x24, 0xBD, 0x32, 0x7A, 0x3D, + 0xCF, 0xB4, 0x93, 0xA1, 0xDC, 0xCE, 0xC5, 0xDF, 0x9A, 0x72, 0xA8, 0xE3, + 0x8E, 0x9F, 0x1A, 0x6C, 0xF2, 0xCF, 0x32, 0x7A, 0x3B, 0x40, 0x8C, 0xEF, + 0x72, 0xD0, 0x6B, 0x21, 0xFE, 0x9E, 0x6B, 0x1D, 0x2C, 0xFA, 0x5F, 0xD3, + 0x57, 0x6A, 0x31, 0x7A, 0x9C, 0x8E, 0x9A, 0xBF, 0xD2, 0x79, 0xF7, 0x5F, + 0x0C, 0xBB, 0xF4, 0x63, 0xFA, 0x4E, 0x1B, 0xED, 0xE9, 0x60, 0xC9, 0x5A, + 0xA9, 0x8B, 0x13, 0x67, 0x9E, 0xF5, 0xDE, 0x4C, 0xBF, 0x11, 0xAC, 0x31, + 0x64, 0xD5, 0xFC, 0xF8, 0x74, 0xDC, 0x13, 0xC5, 0x49, 0x72, 0xA2, 0xF7, + 0xF1, 0x83, 0x2B, 0xC1, 0x1F, 0xF7, 0x46, 0xD8, 0x0C, 0xDA, 0xD3, 0x97, + 0x78, 0xFE, 0xFD, 0x50, 0x99, 0xDE, 0x2D, 0x3F, 0x42, 0x6F, 0x7F, 0x70, + 0xA6, 0xAD, 0x5E, 0xBF, 0x23, 0xB9, 0x23, 0xA7, 0xD0, 0xE7, 0x1D, 0x5D, + 0x75, 0x31, 0x7E, 0xE7, 0x59, 0x2F, 0x95, 0xF9, 0x56, 0x7A, 0xCA, 0xFD, + 0x64, 0xCE, 0x88, 0xCD, 0x74, 0xE5, 0x93, 0x71, 0x15, 0x38, 0x32, 0x98, + 0x14, 0xE0, 0xC8, 0xC0, 0x9D, 0xA9, 0x2C, 0xA4, 0xB1, 0xE2, 0x4B, 0x9D, + 0xF1, 0x4F, 0x29, 0x8E, 0x17, 0xBF, 0x0F, 0x65, 0xB4, 0xB7, 0xCC, 0xD1, + 0x7C, 0xC9, 0x71, 0x5A, 0x99, 0x6B, 0x11, 0xAB, 0x4B, 0x9D, 0x85, 0x60, + 0x5C, 0xFC, 0x2A, 0x0C, 0xF9, 0x2B, 0xDF, 0x9D, 0x25, 0x43, 0xDE, 0xDB, + 0x67, 0x38, 0x66, 0xFE, 0xFF, 0x24, 0x6E, 0x28, 0xF9, 0xF1, 0x9D, 0x43, + 0x74, 0x34, 0xBF, 0xC3, 0x4B, 0x5A, 0x64, 0x7C, 0xBB, 0xE5, 0x7B, 0x02, + 0xFD, 0xB5, 0x3F, 0x73, 0x04, 0x7F, 0xF2, 0x25, 0x9F, 0x13, 0xD6, 0x3A, + 0x3B, 0xD7, 0x27, 0x8F, 0xC6, 0xE6, 0x77, 0x36, 0x94, 0x2B, 0x7F, 0x84, + 0x72, 0x2F, 0xD4, 0x3F, 0x04, 0x7D, 0x68, 0xBE, 0x7B, 0x17, 0x43, 0xE2, + 0xFF, 0xE7, 0x92, 0x64, 0xEF, 0xFC, 0x91, 0xAF, 0x4E, 0x14, 0xE7, 0x95, + 0x40, 0x52, 0xA7, 0x7F, 0x45, 0xC4, 0xDC, 0x79, 0xF2, 0xCC, 0x4D, 0xC8, + 0xFF, 0x97, 0x2F, 0xC1, 0x52, 0xE0, 0x1B, 0xD0, 0x75, 0xE9, 0x41, 0x21, + 0x1B, 0x1A, 0xE8, 0x38, 0xE6, 0xF8, 0x21, 0xF1, 0x4A, 0xE6, 0x89, 0xD3, + 0x91, 0xCA, 0x28, 0x8E, 0x79, 0xBA, 0x85, 0x22, 0x3C, 0x41, 0x67, 0xF4, + 0xFB, 0x42, 0x95, 0xBD, 0x63, 0xB2, 0x77, 0x82, 0x0E, 0xFA, 0x6B, 0x46, + 0xEE, 0x47, 0x2A, 0xFB, 0x63, 0xF2, 0xFF, 0x67, 0x39, 0x2C, 0x6D, 0x74, + 0xB4, 0x54, 0x36, 0x7E, 0x10, 0x7F, 0x09, 0x07, 0x79, 0xA6, 0x8D, 0xEE, + 0x38, 0x65, 0x73, 0x65, 0x10, 0xDD, 0x4B, 0x65, 0xD0, 0x5F, 0x44, 0x77, + 0x69, 0x4B, 0x81, 0x7F, 0xBF, 0x74, 0x66, 0xDF, 0x8F, 0x1B, 0xFA, 0x6E, + 0xC9, 0x9E, 0x11, 0x16, 0xFD, 0x59, 0x32, 0x67, 0xBE, 0xA3, 0x5D, 0x2A, + 0xF5, 0xFF, 0x76, 0x6F, 0xC0, 0xBB, 0x54, 0x35, 0x30, 0x95, 0xE1, 0x8B, + 0xFA, 0xA5, 0xFF, 0xF6, 0x7F, 0xFD, 0x4F, 0xCF, 0x80, 0x97, 0x21, 0x52, + 0x68, 0x00, 0xC0, 0xA2, 0xC0, 0x4A, 0xE7, 0x12, 0xFB, 0xB6, 0x90, 0x44, + 0x0D, 0x30, 0xFF, 0x0D, 0x20, 0x44, 0x48, 0x47, 0xFF, 0x0D, 0xFF, 0x74, + 0xA3, 0xE9, 0x5E, 0x0C, 0x90, 0xE1, 0xC4, 0x17, 0x54, 0xB8, 0x44, 0xD1, + 0x81, 0x1F, 0xB8, 0x5C, 0x48, 0x6E, 0xA5, 0x63, 0xB0, 0x61, 0xC8, 0x0F, + 0x2D, 0x47, 0xAC, 0x20, 0x0A, 0x2F, 0x51, 0x88, 0xB6, 0x2E, 0xA4, 0x81, + 0xCC, 0x8A, 0xD0, 0x04, 0x35, 0x48, 0xC1, 0x0C, 0xD0, 0x96, 0x05, 0x8D, + 0x17, 0x0B, 0xA0, 0xC1, 0x06, 0x56, 0x43, 0x19, 0x62, 0x31, 0xC8, 0xB1, + 0x01, 0x0A, 0xFC, 0x70, 0x09, 0x22, 0x0C, 0x20, 0xEC, 0xF8, 0x10, 0xA4, + 0x81, 0x19, 0x31, 0x28, 0xB9, 0xC3, 0x69, 0x0D, 0x01, 0x78, 0xA9, 0x80, + 0x8E, 0x07, 0x9C, 0x94, 0x80, 0x73, 0x85, 0x48, 0x48, 0xE0, 0xC3, 0x44, + 0x87, 0x95, 0x20, 0x48, 0x43, 0xC8, 0x31, 0x60, 0x02, 0x1B, 0x5A, 0x50, + 0x64, 0x0A, 0x27, 0x52, 0x30, 0x13, 0x00, 0x22, 0xC2, 0x1C, 0xCA, 0xA0, + 0x31, 0x41, 0x6D, 0xBC, 0x90, 0x40, 0x77, 0x4D, 0xA4, 0xE0, 0x28, 0x00, + 0x03, 0x3F, 0x38, 0x5A, 0x46, 0x04, 0x8F, 0xA9, 0xE0, 0xEB, 0xB7, 0xA3, + 0xE6, 0x37, 0x4C, 0xBD, 0x65, 0xED, 0x9A, 0x7A, 0xEB, 0xBB, 0x8F, 0xFB, + 0x07, 0xEB, 0xFF, 0x04, 0xFC, 0x47, 0xC0, 0xFF, 0x03, 0xFC, 0x6F, 0xC1, + 0x27, 0x09, 0xC4, 0xF4, 0xE2, 0xFC, 0xCF, 0x32, 0x78, 0x07, 0x1C, 0xFF, + 0x87, 0xB6, 0xF1, 0x2E, 0xC9, 0xE4, 0x07, 0xBC, 0x76, 0x70, 0x05, 0x78, + 0x51, 0xF7, 0x0C, 0x27, 0xEA, 0xDD, 0xF9, 0x4F, 0x92, 0xAD, 0x84, 0xEC, + 0x2E, 0xB2, 0xCB, 0x82, 0xCA, 0x33, 0x60, 0x8B, 0x5C, 0x72, 0x2C, 0x0A, + 0xC9, 0xBA, 0x59, 0x25, 0xA7, 0xAE, 0x1C, 0x85, 0xE2, 0xA3, 0xDC, 0x2E, + 0x50, 0x1E, 0x06, 0x94, 0x93, 0x2C, 0xF1, 0xE4, 0xF2, 0x95, 0x3B, 0xDC, + 0x11, 0x84, 0x63, 0x66, 0x8E, 0xC6, 0x8E, 0x49, 0x40, 0x21, 0x80, 0x2D, + 0x92, 0xE8, 0x3F, 0x89, 0x1F, 0xC7, 0xB9, 0x45, 0xC6, 0x5A, 0xE4, 0x97, + 0xB4, 0x18, 0x95, 0x00, 0x80, 0x15, 0x3A, 0x48, 0x42, 0x86, 0x00, 0x3B, + 0x76, 0x24, 0x71, 0x84, 0x21, 0x86, 0xD8, 0xF2, 0xD7, 0x63, 0x79, 0x96, + 0x78, 0x3B, 0x5F, 0x72, 0xEE, 0xB5, 0xE4, 0x57, 0xEC, 0xD8, 0x81, 0x04, + 0xBE, 0x3F, 0xF8, 0xB7, 0xD7, 0x53, 0x97, 0xBC, 0x64, 0xDE, 0x81, 0x3B, + 0x5F, 0x2A, 0x51, 0x03, 0x01, 0x29, 0x48, 0xC2, 0x0A, 0x92, 0x8F, 0x7F, + 0x01, 0xFF, 0x71, 0x5A, 0x28, 0xA5, 0x12, 0x25, 0x09, 0x24, 0xC2, 0x60, + 0xC2, 0x86, 0x3B, 0x6D, 0x5D, 0x72, 0x8F, 0x72, 0xFF, 0xCE, 0xA6, 0x4B, + 0x1B, 0xE9, 0x12, 0xDE, 0xD1, 0xE9, 0x5F, 0xF0, 0x9C, 0xC8, 0x15, 0xEC, + 0x84, 0x79, 0x68, 0x88, 0xC4, 0x1C, 0xFB, 0x3E, 0xE3, 0x47, 0xB9, 0xF3, + 0x33, 0x7F, 0xFF, 0x3B, 0xB2, 0xA3, 0x6E, 0x9F, 0x1C, 0x96, 0x9F, 0x4C, + 0xE6, 0x5C, 0x20, 0x93, 0x4F, 0xFF, 0xFF, 0x9E, 0x23, 0x7F, 0x7C, 0xF0, + 0xE7, 0x4F, 0xCC, 0xE7, 0xE4, 0xA1, 0xF8, 0x84, 0xBD, 0x4C, 0x1D, 0x17, + 0xBD, 0x67, 0xAB, 0xB4, 0x51, 0xA5, 0xFE, 0xB3, 0x5F, 0x82, 0x40, 0xA0, + 0x39, 0x5E, 0x33, 0xE8, 0x5E, 0x5A, 0x44, 0xC7, 0xD5, 0x12, 0xDD, 0x2C, + 0x45, 0xE0, 0x8B, 0xF0, 0x88, 0x54, 0x60, 0xA2, 0x22, 0x2F, 0x91, 0x8A, + 0x69, 0x33, 0x92, 0x2B, 0x1F, 0x91, 0x72, 0xAF, 0xF1, 0x9C, 0x8F, 0xD8, + 0xAB, 0x2F, 0x6C, 0x32, 0x89, 0x64, 0xBC, 0xDC, 0x17, 0xAA, 0x4C, 0x43, + 0xF2, 0xE3, 0xC9, 0xDE, 0x17, 0x29, 0xD1, 0x9B, 0x27, 0x73, 0x3C, 0x19, + 0x3D, 0xF9, 0x79, 0x57, 0x64, 0x22, 0x39, 0x95, 0x88, 0x91, 0xA8, 0x46, + 0xF2, 0x22, 0x69, 0xF1, 0x5C, 0x4C, 0x3C, 0x12, 0x98, 0xE8, 0xF5, 0xCA, + 0x75, 0xC2, 0xF5, 0xEF, 0xD5, 0xEB, 0xAB, 0x33, 0xDB, 0xCF, 0xF4, 0x8C, + 0xF0, 0x8C, 0xDC, 0x17, 0x9D, 0x5F, 0x82, 0x9D, 0xBE, 0x1F, 0x1D, 0xD4, + 0xA5, 0xD8, 0x29, 0xFF, 0xAF, 0xF1, 0x93, 0x83, 0x4D, 0xF6, 0x44, 0x4E, + 0x87, 0x2A, 0x73, 0x4B, 0x74, 0xEE, 0x50, 0x47, 0x2A, 0xE3, 0x92, 0x19, + 0xF4, 0x5F, 0xD1, 0xA7, 0x86, 0x94, 0xA8, 0x43, 0x1D, 0xAD, 0xFD, 0x38, + 0x7F, 0x5D, 0x48, 0x69, 0x6F, 0x9C, 0x3D, 0x1D, 0x2A, 0x11, 0xFD, 0x2B, + 0x73, 0x3A, 0x6C, 0x44, 0xB4, 0x5F, 0xB9, 0xFE, 0x21, 0x49, 0x71, 0x6C, + 0xFC, 0xDD, 0x17, 0xE8, 0x16, 0xC9, 0xEE, 0x92, 0x81, 0xA4, 0x11, 0x1F, + 0x3C, 0x6A, 0x40, 0x19, 0x8A, 0xA9, 0x10, 0x31, 0x30, 0xB8, 0x85, 0x85, + 0xDE, 0x1B, 0x11, 0xE2, 0x9E, 0x34, 0xB2, 0x51, 0x21, 0x8C, 0xD4, 0x5F, + 0x88, 0xFF, 0x82, 0x40, 0x33, 0x5C, 0x8A, 0xCB, 0x49, 0x8A, 0xCA, 0x28, + 0x85, 0x5E, 0x4B, 0x39, 0x97, 0xCD, 0x64, 0xCF, 0xED, 0x92, 0x89, 0x2A, + 0x99, 0x15, 0x29, 0xF7, 0x33, 0x8A, 0x77, 0xE5, 0x23, 0xFE, 0x9F, 0xC9, + 0xBD, 0xC4, 0x7F, 0x3D, 0xB1, 0xD1, 0xCB, 0x89, 0x8D, 0x5C, 0x4F, 0x6C, + 0x0C, 0x5F, 0xE2, 0xCB, 0xF1, 0xE5, 0xD8, 0xDF, 0x6F, 0xC0, 0x32, 0xCD, + 0x81, 0x8D, 0x60, 0xE3, 0xDF, 0x1B, 0xDD, 0x34, 0xA7, 0xB3, 0x97, 0x73, + 0xB8, 0x63, 0xC7, 0xC6, 0x31, 0xE9, 0xD7, 0x53, 0xC7, 0x97, 0x5E, 0x94, + 0xC4, 0xF1, 0x62, 0x7C, 0x92, 0x11, 0x3C, 0x11, 0x39, 0xEC, 0xF2, 0x06, + 0x7D, 0x27, 0xED, 0x7E, 0x6F, 0x90, 0xC7, 0x9D, 0x98, 0x8E, 0xC0, 0x35, + 0x50, 0xDC, 0x06, 0xFA, 0xD0, 0x08, 0xE5, 0xA0, 0x91, 0x3E, 0x91, 0xD8, + 0x48, 0x6A, 0xA4, 0xD7, 0x88, 0xEA, 0xF4, 0x96, 0xCD, 0x6E, 0xC4, 0xC6, + 0xF4, 0x15, 0x9B, 0x81, 0x5F, 0xA8, 0x46, 0xD2, 0x88, 0x2A, 0xC3, 0x54, + 0x08, 0x55, 0x76, 0xBF, 0x68, 0xF1, 0x46, 0x54, 0xFF, 0x59, 0x2C, 0xF9, + 0x37, 0x93, 0xAC, 0x5A, 0x39, 0x9A, 0x5A, 0x5F, 0xCD, 0x5B, 0xBE, 0x62, + 0xD1, 0x26, 0x92, 0x29, 0x98, 0x7E, 0x34, 0x37, 0xFD, 0x8E, 0xBF, 0x22, + 0x3B, 0x92, 0x29, 0x77, 0xBC, 0x17, 0x6D, 0x27, 0x32, 0xE5, 0xF4, 0xA5, + 0x2B, 0x7A, 0xA7, 0x15, 0xFF, 0x13, 0x77, 0xAE, 0x9C, 0xC2, 0x9D, 0xE7, + 0x0F, 0x8C, 0x2A, 0x72, 0xA8, 0x9A, 0x9F, 0x82, 0x4F, 0xDC, 0x4D, 0xCD, + 0x61, 0xCE, 0x96, 0x7F, 0x53, 0x47, 0x98, 0x7C, 0x99, 0x53, 0x4A, 0x4B, + 0x06, 0xB7, 0xFC, 0xC1, 0x4E, 0x5E, 0x29, 0x3E, 0x2C, 0x47, 0xEF, 0x1C, + 0x7F, 0x63, 0xFD, 0x3E, 0x67, 0x8A, 0x2F, 0x4F, 0xD9, 0xDB, 0xB1, 0x94, + 0xA3, 0xE9, 0xA0, 0xAF, 0x7C, 0xC6, 0x9F, 0x9E, 0x2C, 0xC1, 0xAA, 0x78, + 0xD6, 0xCE, 0x96, 0xA9, 0xD8, 0xAC, 0x4A, 0xBF, 0x30, 0x7E, 0xE2, 0x72, + 0xEC, 0x48, 0xBB, 0x9E, 0x2D, 0x93, 0x3D, 0x56, 0x0C, 0x69, 0x7E, 0xEA, + 0x2F, 0x8C, 0x8D, 0x7A, 0x97, 0x72, 0x39, 0x35, 0x77, 0x98, 0x7E, 0x3B, + 0xDB, 0xE9, 0x5A, 0x8D, 0x33, 0x29, 0x48, 0x0E, 0x91, 0x89, 0x81, 0xC1, + 0x2D, 0x22, 0xC6, 0xE2, 0x1D, 0xB2, 0x89, 0xE2, 0xC5, 0xE2, 0x8F, 0x78, + 0x66, 0xF1, 0x58, 0xFC, 0x4C, 0x11, 0x93, 0xA2, 0x67, 0x8E, 0x23, 0xF8, + 0x6D, 0xD3, 0xC6, 0x67, 0xAE, 0xBB, 0xDB, 0x5D, 0xFB, 0x4C, 0xAF, 0x04, + 0x13, 0xB9, 0x9F, 0x6C, 0xBE, 0x05, 0x75, 0xFF, 0x9F, 0x72, 0x67, 0x6E, + 0x21, 0xDF, 0x5E, 0xD0, 0x69, 0xC7, 0x02, 0xFE, 0xA7, 0x25, 0x33, 0xE3, + 0xF0, 0xE4, 0x0B, 0x93, 0x1D, 0x67, 0x07, 0x57, 0x32, 0xFC, 0xEE, 0x66, + 0x5D, 0xB3, 0xD2, 0xC6, 0x69, 0x13, 0xE6, 0x6F, 0x4C, 0xCA, 0x73, 0x1A, + 0x83, 0x2F, 0x05, 0xD4, 0x04, 0xDA, 0xA0, 0x82, 0x17, 0x0A, 0x64, 0x11, + 0xF3, 0x49, 0xC2, 0x72, 0xC4, 0x17, 0xB0, 0x00, 0x81, 0xC9, 0x56, 0x76, + 0xC9, 0x14, 0x71, 0x66, 0xCD, 0xF1, 0x66, 0xB5, 0x7C, 0xF6, 0xF2, 0x32, + 0x3C, 0x58, 0xF8, 0xE9, 0xFB, 0xE5, 0xFE, 0x9C, 0x59, 0x86, 0x38, 0xCA, + 0x8F, 0x88, 0xB2, 0x7B, 0xE9, 0x88, 0x12, 0xFF, 0x6F, 0x56, 0x0D, 0x09, + 0xDE, 0xC0, 0x37, 0x9E, 0xE9, 0xBD, 0x71, 0xFA, 0x1B, 0x5F, 0x59, 0x59, + 0x59, 0xA1, 0xE4, 0x85, 0xC4, 0xD0, 0x19, 0x31, 0xB4, 0x25, 0xC8, 0x0D, + 0x12, 0x24, 0xC8, 0x9F, 0x5D, 0xFC, 0x04, 0x2B, 0xAC, 0xD0, 0x81, 0x77, + 0x1A, 0x11, 0xD2, 0xD4, 0x25, 0xB4, 0xCF, 0x99, 0xD9, 0xEC, 0xF1, 0x5F, + 0x99, 0x3F, 0x9A, 0xD6, 0xE8, 0xB9, 0x52, 0x38, 0xEC, 0xF9, 0x1D, 0x48, + 0xAC, 0x95, 0x60, 0xFF, 0x37, 0xB3, 0xB4, 0xC9, 0x37, 0x2F, 0x11, 0x44, + 0x8A, 0x8E, 0xF3, 0x5E, 0x19, 0x0A, 0x7D, 0x3D, 0x7E, 0x07, 0x5A, 0x72, + 0x89, 0xFE, 0x77, 0x7D, 0x8E, 0x24, 0xB4, 0x28, 0xC6, 0x71, 0x55, 0xD0, + 0x75, 0x1C, 0x58, 0xCE, 0x2D, 0x73, 0x5E, 0x66, 0xD0, 0xCA, 0x54, 0xEC, + 0x45, 0x4E, 0x91, 0xD1, 0xBE, 0x02, 0x92, 0xD1, 0x01, 0xDC, 0x3A, 0x22, + 0x78, 0x69, 0xCA, 0xB9, 0xD8, 0x48, 0x57, 0x3F, 0x25, 0x59, 0xEA, 0x22, + 0x83, 0x2D, 0xA3, 0xBD, 0xC0, 0xFE, 0x50, 0xFC, 0xB8, 0x33, 0xE8, 0xE6, + 0xF9, 0xE6, 0x9A, 0xE0, 0x07, 0x65, 0xF8, 0xDF, 0xAC, 0x2D, 0x74, 0xAD, + 0xB3, 0x59, 0xE8, 0x72, 0x71, 0x78, 0xCA, 0xD2, 0x2C, 0xC1, 0x6F, 0xE7, + 0x8F, 0x63, 0xF6, 0x93, 0xE8, 0xA2, 0x07, 0x2C, 0x2C, 0x51, 0x85, 0x2F, + 0x50, 0xC1, 0xE5, 0x40, 0x76, 0x09, 0xA6, 0x17, 0x47, 0xA7, 0xB9, 0x52, + 0xC3, 0x5A, 0x56, 0x84, 0xE5, 0x34, 0x02, 0xFF, 0xAB, 0x48, 0x13, 0x4E, + 0x9A, 0xE9, 0x6F, 0xA0, 0x87, 0xCE, 0xEC, 0x33, 0x4C, 0x6F, 0xBE, 0xDB, + 0xD2, 0xAE, 0xE4, 0xB1, 0xF4, 0x03, 0xFF, 0x3D, 0x08, 0x86, 0x08, 0xC0, + 0x18, 0x58, 0xE0, 0xBD, 0x96, 0x0C, 0xD9, 0x81, 0x0D, 0x94, 0xD0, 0x83, + 0x16, 0x5B, 0x28, 0x72, 0x30, 0x86, 0xA5, 0x23, 0xAB, 0x78, 0xC1, 0x5E, + 0x66, 0xAB, 0xF6, 0xE0, 0x1A, 0x5B, 0x65, 0x4A, 0x35, 0xDE, 0x2A, 0x52, + 0x63, 0x0F, 0x3D, 0x56, 0x51, 0xD3, 0x03, 0x98, 0xEF, 0xAB, 0xA8, 0x19, + 0x8F, 0x7B, 0x00, 0x47, 0x5C, 0xBF, 0x13, 0x06, 0xDF, 0x9A, 0x79, 0xA7, + 0x76, 0x92, 0xF5, 0x9A, 0x69, 0xDC, 0x59, 0xE2, 0x69, 0x4C, 0xBB, 0xEC, + 0x7F, 0x77, 0x76, 0x3C, 0x37, 0x14, 0xFE, 0xBD, 0x63, 0x5B, 0xD4, 0x9F, + 0xE1, 0x95, 0xF1, 0x26, 0xE5, 0x26, 0xE5, 0xA4, 0x2C, 0x37, 0x29, 0x37, + 0x60, 0xCA, 0x89, 0x10, 0x9E, 0xE6, 0xB7, 0x46, 0xDD, 0x6E, 0xCD, 0xCC, + 0xEC, 0x35, 0x4C, 0x8F, 0x9F, 0x1D, 0x81, 0x04, 0x17, 0x4B, 0x87, 0x0D, + 0x6A, 0x82, 0xD4, 0xD4, 0x60, 0x04, 0xA1, 0x0E, 0x0E, 0x1A, 0x97, 0x08, + 0x32, 0x26, 0x3E, 0x90, 0x4D, 0x00, 0x04, 0x59, 0x04, 0x40, 0x50, 0xE6, + 0x28, 0xE2, 0x2D, 0x06, 0xD0, 0x89, 0xA9, 0xE1, 0x32, 0xD1, 0xE1, 0x6B, + 0x59, 0x39, 0x64, 0x15, 0x0E, 0x4D, 0x94, 0x39, 0xD0, 0xF0, 0x53, 0x02, + 0xAE, 0x94, 0x31, 0xF0, 0xC0, 0x08, 0x4B, 0xB4, 0x4C, 0x32, 0x02, 0xE5, + 0x4D, 0x08, 0x92, 0x40, 0x22, 0x07, 0x04, 0x80, 0xFC, 0x99, 0x0B, 0xC7, + 0x05, 0x28, 0x60, 0xC4, 0x04, 0x1C, 0xF0, 0x32, 0x04, 0xB6, 0x48, 0x78, + 0x10, 0x78, 0x80, 0x11, 0x08, 0x28, 0x62, 0x5B, 0x40, 0x01, 0x3C, 0x08, + 0x1C, 0x40, 0x08, 0x16, 0x21, 0x4A, 0x2A, 0x39, 0x80, 0x08, 0xC0, 0x0F, + 0x34, 0x6A, 0x0F, 0x47, 0xA2, 0xEC, 0x64, 0xF4, 0xD0, 0x3A, 0x8A, 0xA5, + 0x26, 0x5C, 0x0B, 0x2B, 0x51, 0xA2, 0x95, 0x22, 0x07, 0x09, 0x94, 0xDD, + 0xEF, 0x06, 0xD7, 0x16, 0x50, 0xB0, 0x65, 0x0E, 0x22, 0x88, 0x5A, 0xF6, + 0x3A, 0xA4, 0xFF, 0x53, 0x40, 0x51, 0x05, 0x6A, 0x07, 0x0C, 0x8E, 0x90, + 0xE1, 0x88, 0x4D, 0x55, 0xA0, 0x7A, 0x60, 0x0D, 0x6A, 0xF8, 0x51, 0xDE, + 0xFF, 0x6F, 0x07, 0xBD, 0xC7, 0x55, 0x4C, 0xEC, 0x68, 0xE9, 0x83, 0x47, + 0x0D, 0x48, 0x7F, 0x70, 0xE7, 0x28, 0xD3, 0xD1, 0x1B, 0x03, 0x83, 0xA9, + 0x77, 0xAE, 0x4E, 0xA0, 0x96, 0xF1, 0x96, 0xF7, 0xD2, 0xCF, 0xAC, 0x62, + 0x80, 0x2F, 0xB7, 0x7C, 0xCB, 0x4A, 0x7F, 0x96, 0xEE, 0xF7, 0x61, 0xFF, + 0x02, 0x79, 0x94, 0x3B, 0xAB, 0x18, 0x2F, 0x9D, 0x42, 0x20, 0xE5, 0xF5, + 0x3C, 0x26, 0xF0, 0xA0, 0x0E, 0x74, 0xA1, 0xC8, 0x8C, 0x1F, 0x10, 0xA1, + 0xC8, 0x07, 0x52, 0x5C, 0xA4, 0xE1, 0x0D, 0x59, 0xE8, 0xA6, 0x14, 0xB8, + 0x0F, 0xD5, 0xCB, 0xB0, 0xD9, 0x37, 0x93, 0x67, 0x59, 0x1C, 0x42, 0xCF, + 0xFE, 0xD6, 0x81, 0x81, 0x9E, 0xC2, 0xA7, 0x27, 0xDB, 0xCC, 0x42, 0xF0, + 0xFD, 0x8C, 0x85, 0xEA, 0x12, 0x11, 0x4D, 0x96, 0x4C, 0x14, 0xBE, 0x3D, + 0x7D, 0x73, 0x3E, 0xF9, 0x92, 0xDC, 0x4F, 0x36, 0xB3, 0xEF, 0x30, 0xA8, + 0x1F, 0xE8, 0xF8, 0x3F, 0x88, 0x23, 0xE3, 0x98, 0xA8, 0xFB, 0xAC, 0x51, + 0xA5, 0xD3, 0x88, 0x97, 0x38, 0xF6, 0x5D, 0xB2, 0x34, 0x3A, 0x19, 0xB6, + 0x3A, 0xF3, 0xBF, 0x13, 0xF5, 0x32, 0x28, 0x49, 0x45, 0x95, 0x99, 0x77, + 0x9E, 0xD4, 0x7D, 0xA0, 0xDF, 0xDB, 0x89, 0x40, 0x54, 0x3E, 0xE5, 0x66, + 0x55, 0x34, 0xA6, 0x65, 0x13, 0x3F, 0x36, 0x35, 0x3A, 0x98, 0xBF, 0x39, + 0x11, 0x56, 0x14, 0x83, 0xB5, 0x98, 0x17, 0x2C, 0x08, 0x03, 0xCF, 0x13, + 0x2F, 0x26, 0x28, 0x40, 0x00, 0x25, 0x7A, 0x20, 0x91, 0x63, 0x85, 0x01, + 0x15, 0x87, 0x77, 0x63, 0x00, 0xA0, 0xB6, 0xA2, 0x09, 0x11, 0x82, 0xAB, + 0x01, 0xE0, 0x03, 0x6D, 0xE8, 0x11, 0xC6, 0x1A, 0x04, 0x70, 0x06, 0x0D, + 0x94, 0xC1, 0x0B, 0x20, 0xB8, 0x70, 0x81, 0x24, 0x5A, 0xF8, 0x41, 0x0A, + 0x14, 0x78, 0x21, 0x81, 0xA9, 0x36, 0xB8, 0x78, 0xC1, 0x01, 0x90, 0x24, + 0xA2, 0xC8, 0x41, 0x6A, 0x40, 0x3C, 0x19, 0x61, 0xAA, 0xD1, 0x82, 0x22, + 0x56, 0x10, 0xA6, 0x98, 0x00, 0x87, 0x0E, 0x1A, 0x1E, 0x9C, 0x40, 0x80, + 0x07, 0x3A, 0x40, 0x80, 0xDA, 0xC9, 0xAC, 0x64, 0x03, 0x28, 0x22, 0xBB, + 0x91, 0x55, 0x38, 0x04, 0xD1, 0xD2, 0x91, 0x42, 0x91, 0x18, 0x13, 0xD4, + 0x03, 0x00, 0xBD, 0x3E, 0x50, 0xEE, 0x0E, 0x29, 0x6F, 0xE0, 0xE1, 0xC4, + 0x02, 0x67, 0xA2, 0xB4, 0x56, 0x92, 0xE0, 0xC2, 0x81, 0x85, 0xC9, 0x15, + 0xAE, 0x15, 0x1E, 0x8C, 0x6E, 0x85, 0x4B, 0xE6, 0x16, 0x0A, 0xDA, 0xFD, + 0xE4, 0x27, 0x59, 0xF6, 0xEC, 0x2B, 0x2C, 0x93, 0xA3, 0x94, 0x6E, 0x8E, + 0x90, 0x0C, 0xD6, 0x90, 0xB8, 0x52, 0x9C, 0x90, 0x82, 0x16, 0x11, 0x44, + 0x30, 0xC2, 0xA3, 0xB5, 0xDD, 0xD5, 0xA2, 0x55, 0x61, 0x82, 0x16, 0x10, + 0xB7, 0xDD, 0x5A, 0x90, 0x22, 0x3E, 0x36, 0x8B, 0x69, 0x6D, 0x46, 0x27, + 0xD2, 0x44, 0x33, 0x5A, 0xD5, 0xB4, 0xAA, 0x44, 0x04, 0x75, 0xA0, 0x43, + 0xF6, 0xE3, 0x01, 0x36, 0x84, 0x91, 0x81, 0xFA, 0xB3, 0x89, 0x96, 0x8E, + 0x16, 0x33, 0x45, 0xD0, 0xF9, 0x91, 0x7C, 0x45, 0xC6, 0x44, 0xCB, 0xC8, + 0x1D, 0xAC, 0x88, 0x00, 0x0E, 0x88, 0xD8, 0xC1, 0x06, 0x07, 0x99, 0x02, + 0x02, 0x40, 0xED, 0x80, 0x56, 0x8A, 0x04, 0xAE, 0x1C, 0x17, 0x28, 0xA2, + 0xBC, 0x05, 0x86, 0x68, 0x75, 0x31, 0x4C, 0xB4, 0xEC, 0x8F, 0x3B, 0x8B, + 0x00, 0x8C, 0x02, 0x96, 0xA0, 0x83, 0x03, 0x5A, 0x8C, 0x68, 0xC0, 0x18, + 0x2D, 0x05, 0x68, 0xD5, 0x03, 0x18, 0x20, 0x0E, 0xA5, 0x1B, 0xAD, 0x0B, + 0xC0, 0xF5, 0x20, 0x82, 0xE8, 0xD6, 0xC4, 0x28, 0x57, 0xB2, 0x1E, 0x02, + 0xD0, 0xCA, 0xC1, 0xFF, 0xD3, 0x9F, 0x96, 0x96, 0xBD, 0xC5, 0x4D, 0xD7, + 0x50, 0xBE, 0xE0, 0x34, 0xD5, 0xF4, 0x0A, 0xAE, 0x18, 0x4C, 0xB7, 0xA0, + 0xFF, 0x53, 0x5A, 0xAD, 0xCF, 0x90, 0x69, 0x4A, 0x45, 0x91, 0xF3, 0xA4, + 0x98, 0xE6, 0x61, 0x87, 0xAE, 0x3C, 0x82, 0x0D, 0x32, 0x5A, 0x78, 0x0C, + 0x54, 0x38, 0x59, 0x02, 0xA9, 0x75, 0x84, 0x10, 0x5C, 0x31, 0x39, 0x88, + 0xA2, 0x4F, 0xFF, 0x8B, 0x4A, 0x1C, 0xEF, 0x70, 0x3F, 0x08, 0x14, 0x7F, + 0x6D, 0xB1, 0x74, 0x34, 0xC6, 0x7C, 0x9F, 0xCC, 0x9E, 0x65, 0x2A, 0x38, + 0x22, 0xBA, 0x0C, 0x94, 0x6E, 0xF4, 0x6B, 0xD9, 0x7E, 0xA4, 0x1D, 0x01, + 0x42, 0xB4, 0xB7, 0xD4, 0x06, 0xEE, 0xB1, 0x23, 0xD9, 0x2D, 0xF9, 0xBE, + 0xE7, 0xE0, 0xFF, 0x1F, 0x25, 0x18, 0x35, 0x5F, 0x34, 0x04, 0xA3, 0x8A, + 0x5F, 0x4F, 0x3D, 0xBD, 0xC4, 0x23, 0x72, 0x3D, 0xDB, 0x68, 0x02, 0xB9, + 0x2F, 0x20, 0x30, 0x20, 0x30, 0xD2, 0xF7, 0x22, 0x79, 0x3D, 0xA4, 0x73, + 0x3E, 0x19, 0xFF, 0xA7, 0x10, 0x2E, 0x95, 0xC9, 0xF7, 0x3F, 0x40, 0xBC, + 0x9C, 0xE7, 0x30, 0x0C, 0x11, 0xB6, 0x19, 0x8E, 0x69, 0x5F, 0xB3, 0x67, + 0x97, 0xEB, 0xB9, 0x92, 0x0F, 0xF1, 0xC2, 0xF6, 0xE6, 0x33, 0x7A, 0xE7, + 0x88, 0xF1, 0xC9, 0xD7, 0x68, 0xF7, 0xA1, 0x35, 0x0F, 0xED, 0xF6, 0xF9, + 0xF3, 0xFC, 0xCF, 0x88, 0x8D, 0x40, 0xCC, 0x43, 0x57, 0x0F, 0x9A, 0x39, + 0xAB, 0xCB, 0xF4, 0x67, 0x5D, 0x88, 0xCF, 0xA8, 0xFF, 0x87, 0x3D, 0x5B, + 0x46, 0x39, 0x31, 0xB2, 0x16, 0x68, 0x6A, 0x3E, 0x0D, 0x99, 0xEC, 0xF5, + 0x3C, 0xA3, 0x2B, 0x31, 0x32, 0xF7, 0x25, 0x97, 0x87, 0x58, 0xD6, 0x9C, + 0xDE, 0xB1, 0xFC, 0x17, 0x34, 0x65, 0x31, 0x06, 0x7D, 0x22, 0x01, 0x62, + 0x90, 0x3C, 0x39, 0xA4, 0x46, 0xEF, 0x2F, 0x7A, 0xF4, 0x9B, 0x99, 0xBB, + 0xA2, 0xE6, 0xCE, 0x42, 0x45, 0x0A, 0x73, 0xAE, 0x22, 0x7D, 0x21, 0xAF, + 0x0C, 0x3D, 0xF4, 0x04, 0xD7, 0xE3, 0x7F, 0x44, 0xF6, 0x35, 0x8E, 0xEC, + 0x5E, 0x05, 0xBF, 0x0A, 0xB8, 0x13, 0x91, 0x81, 0xD9, 0x33, 0xC5, 0x59, + 0xCD, 0xF4, 0x66, 0x26, 0xE9, 0x99, 0x49, 0xF2, 0xFA, 0x2B, 0x2D, 0x3E, + 0x3A, 0x3E, 0xA4, 0xEF, 0x37, 0x0B, 0x3F, 0x1A, 0x9B, 0xEF, 0xD1, 0x24, + 0xC0, 0x92, 0xB2, 0xB0, 0xA4, 0x66, 0xC9, 0x40, 0x15, 0x14, 0xE0, 0x01, + 0xA0, 0xE0, 0x1A, 0x00, 0x05, 0x4F, 0x00, 0x0A, 0x70, 0xDF, 0xDF, 0x8B, + 0x75, 0xBC, 0x56, 0x3C, 0x16, 0xCF, 0x59, 0xEB, 0x9D, 0xDF, 0x3D, 0xF3, + 0xA1, 0x2D, 0x07, 0x5D, 0x04, 0x8F, 0xFF, 0x56, 0x09, 0xA2, 0x6F, 0x12, + 0xC9, 0xDA, 0x9B, 0x49, 0xDC, 0xE3, 0xFD, 0xCA, 0xFC, 0x07, 0x5D, 0xFB, + 0x4E, 0x1D, 0xED, 0xFE, 0x87, 0xBF, 0x24, 0xA3, 0xE1, 0x1B, 0xF5, 0x74, + 0x6F, 0xD8, 0xD3, 0x39, 0x9E, 0xFF, 0x37, 0x36, 0xB1, 0x57, 0x21, 0xFC, + 0xB2, 0x85, 0xEE, 0xCB, 0xC5, 0xDB, 0xFF, 0xA5, 0x8E, 0xA7, 0x96, 0xE5, + 0xBF, 0x67, 0x05, 0xCD, 0xC6, 0x05, 0xC7, 0x4B, 0xDC, 0xDE, 0x36, 0xF8, + 0xDF, 0x28, 0x5E, 0xCE, 0x43, 0xA1, 0x5A, 0x55, 0x1A, 0x07, 0x36, 0xDA, + 0xDB, 0x36, 0xEC, 0x39, 0xA5, 0x3F, 0x68, 0xD6, 0x4B, 0x61, 0x49, 0xD6, + 0x7E, 0x39, 0xBF, 0xB7, 0xE7, 0x6E, 0x50, 0x28, 0xF4, 0x37, 0x14, 0x35, + 0x04, 0xC2, 0xB4, 0x5A, 0xC9, 0x93, 0xBF, 0x64, 0xE9, 0xD7, 0xE3, 0x41, + 0x0D, 0x9A, 0x21, 0xAF, 0xE7, 0x40, 0xF4, 0x9E, 0x93, 0x40, 0x0D, 0x9D, + 0x03, 0x4D, 0xED, 0xE4, 0xA9, 0x28, 0x20, 0xAC, 0xDE, 0x73, 0x03, 0xCD, + 0xCA, 0x81, 0xF1, 0xB1, 0xCF, 0x97, 0x16, 0xC6, 0x6C, 0x07, 0xC2, 0x42, + 0x59, 0x9E, 0xE9, 0xAC, 0xBB, 0xA7, 0xFD, 0x53, 0x3C, 0x96, 0xAE, 0x09, + 0x77, 0x57, 0xBB, 0xE0, 0x8A, 0x76, 0xF0, 0x2C, 0xAE, 0xBC, 0x08, 0x76, + 0xE0, 0x7F, 0xBF, 0x42, 0x38, 0xE6, 0x2C, 0xDA, 0xD9, 0xEC, 0xBE, 0xD8, + 0xD1, 0x3B, 0x0C, 0xA2, 0xFF, 0xAC, 0xB3, 0x6A, 0x76, 0xFC, 0xB3, 0xBA, + 0x7B, 0x56, 0xB9, 0x23, 0x99, 0x72, 0x5D, 0x88, 0x2B, 0xD6, 0xE9, 0x33, + 0x42, 0xA8, 0xDE, 0xA4, 0x0D, 0xA9, 0x83, 0x85, 0xAE, 0x85, 0xBD, 0x40, + 0x24, 0x36, 0xA2, 0xA6, 0xA9, 0xBB, 0x42, 0x9E, 0x39, 0xC4, 0x45, 0xA2, + 0xD1, 0x22, 0xFD, 0x6B, 0x55, 0xB5, 0x34, 0x54, 0xA1, 0x21, 0x2A, 0x12, + 0x8D, 0x14, 0xE9, 0x76, 0x0D, 0x93, 0x6C, 0xD9, 0x67, 0x56, 0xAF, 0x43, + 0xAC, 0x16, 0xED, 0xBF, 0xDD, 0xE9, 0x9D, 0xB9, 0xF8, 0x7C, 0x85, 0xEE, + 0x7F, 0x16, 0xD8, 0x39, 0x29, 0xA6, 0x77, 0x39, 0x50, 0xB0, 0xD6, 0xA6, + 0xCB, 0xD2, 0x19, 0xBF, 0x63, 0xC5, 0xB3, 0x5A, 0x50, 0x2E, 0x78, 0x85, + 0x16, 0x71, 0xA9, 0x2A, 0x72, 0xCF, 0x26, 0x9C, 0x7E, 0xBE, 0xFB, 0x2A, + 0xC9, 0x88, 0x4D, 0x51, 0x68, 0x85, 0xEE, 0x0E, 0xEF, 0x5A, 0xA3, 0x4B, + 0xAA, 0x88, 0xCA, 0x2D, 0xC2, 0x75, 0x8A, 0xF4, 0x3E, 0x85, 0xA5, 0xCE, + 0x8F, 0xCF, 0x33, 0xCF, 0xFD, 0x45, 0x5C, 0x0C, 0xE5, 0x90, 0x12, 0x4F, + 0xDA, 0x8C, 0x1C, 0x6B, 0xF4, 0x28, 0xE7, 0x85, 0x48, 0x57, 0xAA, 0x1F, + 0x07, 0x9B, 0x08, 0x55, 0xED, 0x54, 0xFD, 0xCB, 0x90, 0xF9, 0xD2, 0x14, + 0x96, 0xA8, 0xCF, 0x43, 0x62, 0x63, 0x4F, 0xAA, 0x44, 0xEE, 0xFC, 0x11, + 0x14, 0xA6, 0xC2, 0x8A, 0xE8, 0x4D, 0x15, 0x2B, 0xBD, 0xB1, 0x26, 0x0E, + 0xC9, 0x47, 0xFA, 0x16, 0x7F, 0xC5, 0x45, 0xE4, 0x4D, 0xE8, 0x3B, 0x6D, + 0xEA, 0xED, 0x1F, 0xFE, 0xE8, 0xB0, 0x05, 0x9D, 0xE8, 0x9B, 0x22, 0xA5, + 0x7A, 0x3B, 0x36, 0x44, 0xDC, 0x17, 0x2D, 0x59, 0xC3, 0x09, 0x43, 0xC5, + 0x7F, 0x7E, 0x69, 0x8C, 0xF1, 0x73, 0x59, 0x6B, 0x73, 0x3A, 0x55, 0x9D, + 0x38, 0xD4, 0x47, 0x73, 0x65, 0xF3, 0x97, 0xF8, 0xDB, 0x87, 0x67, 0x88, + 0xA4, 0x49, 0xB4, 0xCD, 0xD3, 0xE7, 0x43, 0x3B, 0xBA, 0x7A, 0xE5, 0xDB, + 0xEE, 0xA5, 0x63, 0x23, 0x2A, 0x52, 0xAF, 0xCA, 0xD3, 0x4B, 0x49, 0xC4, + 0x73, 0x66, 0x2E, 0xA5, 0xF2, 0x16, 0xB9, 0x43, 0x21, 0xA4, 0x7E, 0xAA, + 0x12, 0x3F, 0x1B, 0x94, 0x6B, 0xA5, 0x57, 0xE6, 0xC0, 0xA8, 0x23, 0x0D, + 0xCD, 0x21, 0xBA, 0x67, 0x7C, 0xB9, 0x31, 0xEA, 0xC3, 0xB3, 0xFC, 0xCA, + 0xFB, 0x9F, 0xFC, 0x45, 0xD0, 0x16, 0xC1, 0x2F, 0xE2, 0x71, 0x3A, 0x4A, + 0xE6, 0x09, 0xF7, 0x41, 0x24, 0x2A, 0xD2, 0xC9, 0x16, 0xCF, 0x48, 0x8A, + 0x74, 0xEE, 0x04, 0x4D, 0xA2, 0xB4, 0x25, 0x9F, 0xAB, 0x23, 0xF8, 0x99, + 0xA2, 0x26, 0x79, 0x5D, 0x24, 0x6C, 0xBA, 0x82, 0x4C, 0xCE, 0x20, 0x7B, + 0xA6, 0xCC, 0x9D, 0xF7, 0x4E, 0x28, 0xC0, 0xAE, 0x62, 0x1B, 0xBA, 0xDF, + 0xFE, 0xAD, 0x5F, 0xDA, 0x44, 0x21, 0x17, 0x4D, 0x65, 0x49, 0x5A, 0x47, + 0x15, 0x2D, 0x65, 0x4F, 0xD6, 0x91, 0x6A, 0xFD, 0xFE, 0xF3, 0x9F, 0x6D, + 0x05, 0x3F, 0xA2, 0xFF, 0xFD, 0x10, 0x24, 0xAF, 0x0F, 0x74, 0xAD, 0x83, + 0xFA, 0x36, 0x63, 0x3E, 0xF8, 0x26, 0x1D, 0xDC, 0x4E, 0xC2, 0xFF, 0x29, + 0x60, 0xED, 0x1C, 0x56, 0x34, 0x46, 0xD9, 0x98, 0xED, 0xA2, 0x2A, 0x0E, + 0x7E, 0xCC, 0xA1, 0x8E, 0x83, 0x7E, 0x8C, 0x08, 0x67, 0xD2, 0xEB, 0x9B, + 0xF4, 0x2E, 0x06, 0x06, 0xB3, 0x68, 0x2A, 0x97, 0xD5, 0x5D, 0xAD, 0x63, + 0x55, 0x13, 0xDA, 0x5D, 0x03, 0x3B, 0x15, 0x2B, 0xD5, 0xF5, 0xA6, 0xF2, + 0xC4, 0xC7, 0xEE, 0xF0, 0xF6, 0x16, 0xCD, 0xDC, 0xB8, 0x92, 0xBE, 0x49, + 0x65, 0xDA, 0xDD, 0x8D, 0x03, 0xE3, 0x41, 0xCD, 0x82, 0xD9, 0x58, 0xCE, + 0x5A, 0xD9, 0x38, 0x09, 0x3C, 0x88, 0xEC, 0x62, 0x28, 0x7E, 0xF6, 0x8E, + 0x85, 0x9E, 0x85, 0x45, 0x60, 0x97, 0x5D, 0x4E, 0x15, 0x93, 0xE7, 0x84, + 0x75, 0x45, 0xA7, 0x54, 0xE6, 0x84, 0x96, 0x60, 0x19, 0xCE, 0xAB, 0xD6, + 0x7B, 0x8B, 0x98, 0x71, 0x4D, 0x51, 0xAF, 0x0F, 0xA5, 0x9F, 0x81, 0xCD, + 0xB0, 0x22, 0x86, 0x89, 0x65, 0x57, 0xE5, 0x53, 0xFF, 0x62, 0xD2, 0x55, + 0x55, 0x57, 0xE8, 0xEF, 0xEF, 0x72, 0xFF, 0xEF, 0x24, 0xE2, 0x65, 0x0B, + 0xF3, 0x38, 0x35, 0x15, 0x93, 0x83, 0x31, 0x9D, 0xAA, 0xEF, 0xFA, 0x19, + 0x5C, 0x7F, 0x50, 0x23, 0x4B, 0x66, 0x7E, 0x89, 0xF9, 0x5D, 0xF0, 0x66, + 0x12, 0xEE, 0x90, 0x3E, 0x73, 0x0A, 0xAF, 0x7C, 0x97, 0x09, 0x71, 0xA2, + 0x72, 0x3D, 0x56, 0x0B, 0xD5, 0x44, 0x6F, 0x6A, 0x55, 0xE1, 0x72, 0xB6, + 0x91, 0xA6, 0xD4, 0x3A, 0x73, 0x93, 0x10, 0x4E, 0x63, 0x13, 0xC2, 0x87, + 0xA4, 0xBC, 0xDE, 0xBC, 0xBE, 0x01, 0x55, 0xAD, 0x0D, 0xC2, 0xE7, 0x89, + 0x88, 0x8C, 0x09, 0xE8, 0x24, 0x52, 0xE3, 0x92, 0x35, 0x58, 0x4E, 0xEF, + 0x79, 0xD0, 0x6B, 0xFC, 0xC4, 0x49, 0x60, 0xCB, 0x4D, 0xA4, 0x9A, 0xAE, + 0x79, 0xBA, 0xF9, 0xED, 0x14, 0xF6, 0xD8, 0x4E, 0xF8, 0xFE, 0xDE, 0x9F, + 0x91, 0x12, 0xD9, 0x4C, 0xB1, 0xD3, 0xD5, 0x56, 0x2F, 0xD3, 0xC1, 0x77, + 0xD9, 0xCA, 0x3C, 0x3D, 0x8B, 0xB0, 0x64, 0x61, 0x9D, 0x72, 0x30, 0xB1, + 0x75, 0xBA, 0x29, 0x3E, 0x34, 0x75, 0x4E, 0x16, 0x56, 0x6C, 0x04, 0x3F, + 0x2B, 0x5C, 0xF3, 0x4C, 0x21, 0xDC, 0x67, 0x61, 0x18, 0xFF, 0xC2, 0xA8, + 0x5E, 0x4E, 0x15, 0xE9, 0x14, 0xB6, 0xCC, 0x63, 0x55, 0x3A, 0x82, 0x97, + 0x7E, 0x56, 0xA8, 0x2B, 0x1A, 0xD7, 0x73, 0xEF, 0x14, 0xCF, 0xAA, 0xEF, + 0x4E, 0xE9, 0xFF, 0x90, 0x3B, 0x5B, 0xC6, 0x95, 0x6B, 0x7F, 0xC6, 0x58, + 0xB5, 0x96, 0x1B, 0xB2, 0x8D, 0xD5, 0xFE, 0x76, 0xAC, 0xD6, 0x65, 0x34, + 0x9D, 0x54, 0x6E, 0x3A, 0xE1, 0x7E, 0x36, 0x9D, 0x4C, 0xF4, 0x27, 0xD0, + 0x94, 0x31, 0xC5, 0xE4, 0x9C, 0x3F, 0x33, 0x49, 0x79, 0xAF, 0xCF, 0x95, + 0xDC, 0x79, 0xA2, 0xFE, 0xE2, 0xE8, 0xE5, 0xFE, 0xEE, 0xFE, 0xDA, 0xCF, + 0xA9, 0x4C, 0x53, 0x24, 0x3B, 0x3B, 0xB6, 0x48, 0x02, 0x12, 0x6A, 0x84, + 0x40, 0xB1, 0x70, 0xD1, 0x50, 0x1F, 0x50, 0x80, 0xF6, 0x80, 0xF2, 0x00, + 0x06, 0xA4, 0x37, 0x78, 0xA1, 0x3B, 0x34, 0x40, 0x75, 0xD0, 0x90, 0xC0, + 0x89, 0x1C, 0x1D, 0xE0, 0x80, 0x08, 0x2A, 0x60, 0x04, 0xA5, 0x40, 0x0C, + 0x08, 0x92, 0x60, 0x00, 0x45, 0x82, 0xE6, 0x48, 0x40, 0xE6, 0x04, 0x65, + 0x00, 0x02, 0x68, 0x47, 0x5F, 0x80, 0x07, 0x0D, 0xE0, 0x76, 0xC8, 0xF1, + 0x3A, 0x9C, 0x0E, 0x77, 0x0E, 0x1D, 0x60, 0x83, 0x00, 0xD6, 0x10, 0xDB, + 0x80, 0x19, 0x00, 0x70, 0x2F, 0xC8, 0x90, 0x81, 0x2D, 0xA4, 0x3C, 0xC9, + 0xB5, 0xC0, 0x65, 0x4D, 0x5C, 0x0A, 0x28, 0xF7, 0x81, 0x00, 0x5A, 0x1B, + 0xB0, 0x34, 0x06, 0x5F, 0x55, 0xE2, 0x78, 0x16, 0x63, 0xBE, 0x06, 0x2E, + 0x3B, 0x83, 0x1C, 0x3C, 0x80, 0x38, 0x49, 0xA6, 0x0F, 0xBE, 0xC5, 0xA0, + 0xA7, 0xF4, 0x48, 0x89, 0x8A, 0xEC, 0x19, 0x10, 0xE2, 0xB5, 0xFC, 0x9F, + 0x76, 0x24, 0xE9, 0xD7, 0x46, 0xBF, 0xE8, 0x55, 0xF4, 0x87, 0xBA, 0x83, + 0x97, 0xE2, 0x4B, 0xEF, 0xA5, 0xBF, 0x14, 0xC4, 0x21, 0x8C, 0xB3, 0x8B, + 0xB3, 0x63, 0x11, 0x08, 0x1E, 0x83, 0xBF, 0xE0, 0x0F, 0x3E, 0xC3, 0xB6, + 0xD6, 0x5A, 0x73, 0xBE, 0xA4, 0xF1, 0xFF, 0x2E, 0xB9, 0xC4, 0x97, 0xFE, + 0xEF, 0xDF, 0x80, 0xED, 0x1A, 0xF8, 0x23, 0xF2, 0xF9, 0xDB, 0xF8, 0x49, + 0x10, 0x5C, 0x62, 0xC4, 0xC7, 0xFF, 0xEF, 0x0D, 0xFD, 0xFF, 0x4D, 0x69, + 0xA1, 0x8E, 0x0E, 0xE1, 0x9B, 0xE0, 0x8B, 0x6C, 0x56, 0xA2, 0xDF, 0x24, + 0x12, 0x38, 0x8E, 0x14, 0xE1, 0x65, 0xFC, 0x9F, 0xB5, 0xA6, 0xE4, 0x43, + 0x62, 0xB3, 0x44, 0x47, 0xEA, 0x7F, 0xFF, 0x3B, 0x7F, 0x80, 0x5D, 0x0D, + 0x61, 0x2B, 0xEA, 0x97, 0x32, 0x8D, 0x25, 0x93, 0x71, 0xE7, 0x93, 0xBC, + 0xD4, 0xB2, 0x18, 0x65, 0x2B, 0xE9, 0x72, 0x49, 0xCE, 0x67, 0x09, 0xEA, + 0xF5, 0x38, 0x66, 0x96, 0x05, 0x5C, 0x04, 0x8F, 0xC6, 0xFE, 0x2C, 0x59, + 0xF2, 0x29, 0x01, 0xC6, 0x8E, 0x1D, 0xFF, 0xFF, 0xD7, 0x68, 0xDA, 0x56, + 0xC3, 0xD9, 0xF6, 0x7E, 0x4B, 0xEA, 0xDA, 0xD0, 0x7F, 0x13, 0x43, 0x95, + 0xAF, 0x4D, 0x4A, 0xEA, 0x45, 0x31, 0x8C, 0xDE, 0x16, 0xBF, 0x9E, 0x67, + 0x72, 0xE5, 0xCA, 0xBE, 0x82, 0x65, 0xCA, 0xC5, 0x4E, 0xA4, 0x47, 0xF3, + 0x91, 0x72, 0xA6, 0xF8, 0xD5, 0x99, 0xAD, 0x90, 0x2F, 0x8D, 0x9A, 0x9F, + 0x18, 0x37, 0xE7, 0x99, 0xBC, 0x4B, 0xF3, 0x7A, 0x5E, 0x4F, 0x4B, 0xED, + 0x95, 0x85, 0x34, 0x1F, 0x79, 0x47, 0xDE, 0x2D, 0x8D, 0x6B, 0xFB, 0x32, + 0x28, 0x89, 0xC6, 0xD1, 0xDC, 0x04, 0x6A, 0x33, 0x2C, 0x55, 0xBC, 0x60, + 0x2F, 0x73, 0x6C, 0x89, 0x5C, 0xAE, 0x58, 0x8E, 0xA1, 0x4F, 0x96, 0x12, + 0x89, 0x2A, 0xB7, 0x87, 0xA6, 0xB7, 0x8A, 0x1A, 0x91, 0x48, 0x2F, 0xF7, + 0x42, 0xAE, 0x60, 0xFB, 0x99, 0xE2, 0xB5, 0x18, 0xB3, 0x68, 0x60, 0x77, + 0xAD, 0xE8, 0xC1, 0xAE, 0x78, 0x7A, 0x4F, 0xA5, 0xAC, 0xDB, 0x7C, 0x31, + 0x73, 0x28, 0x9B, 0xC3, 0x9E, 0x7F, 0xB0, 0xD3, 0xED, 0x4A, 0xF1, 0xA8, + 0x8B, 0x4D, 0xB6, 0xB4, 0x0C, 0x5B, 0xED, 0x3E, 0xE3, 0xCC, 0x31, 0xEB, + 0xBB, 0xBA, 0xF5, 0xB2, 0x7A, 0xE5, 0x39, 0xF2, 0x50, 0x5C, 0x61, 0x2F, + 0xB3, 0x7A, 0xE7, 0xF3, 0x46, 0xBF, 0x97, 0xAD, 0xFF, 0xCD, 0x5E, 0x99, + 0x67, 0x52, 0x2E, 0xE7, 0xA2, 0xB7, 0x0F, 0xA5, 0x94, 0xA3, 0x2E, 0x4D, + 0x87, 0xBA, 0x5A, 0x65, 0xC5, 0x37, 0xA3, 0x17, 0xAC, 0x37, 0x1F, 0xD3, + 0xE8, 0x6F, 0x09, 0xDE, 0x45, 0xF1, 0xBD, 0xB7, 0xA4, 0xF7, 0xE5, 0x4F, + 0xC1, 0x7B, 0x4D, 0x1A, 0x05, 0xF3, 0x7D, 0x23, 0x54, 0x2F, 0xFE, 0xEE, + 0x3F, 0xA0, 0xFB, 0x2C, 0x57, 0x0E, 0xC4, 0xFF, 0x27, 0x29, 0x73, 0x04, + 0x71, 0x01, 0x6B, 0xB2, 0xC0, 0xC9, 0x05, 0x0B, 0x10, 0x21, 0x45, 0x07, + 0x99, 0x17, 0xA6, 0x80, 0x42, 0xCA, 0x36, 0xFA, 0xA8, 0xBE, 0xD2, 0x96, + 0xAE, 0x43, 0x0F, 0xD1, 0x1E, 0x6A, 0x54, 0x32, 0x90, 0x66, 0x3C, 0x6A, + 0x40, 0x10, 0xAC, 0xA9, 0xE1, 0x11, 0xCA, 0xCE, 0xC3, 0x8A, 0x2C, 0x18, + 0xCA, 0x6A, 0xEC, 0xE8, 0x96, 0x07, 0x8F, 0x1A, 0xB0, 0x92, 0xAA, 0xB5, + 0x35, 0xA0, 0x25, 0x69, 0x40, 0xFA, 0xD2, 0xAB, 0x26, 0x94, 0x81, 0x27, + 0xAF, 0xD1, 0xC2, 0x7F, 0x62, 0xF8, 0x50, 0x1E, 0xA1, 0x7F, 0xA1, 0x5A, + 0x5D, 0x3E, 0xE2, 0x8B, 0x4F, 0xB7, 0x7A, 0x98, 0x7F, 0x2E, 0xEA, 0xC5, + 0x50, 0xAE, 0x75, 0x22, 0x57, 0x37, 0xAA, 0xF8, 0xDF, 0xB0, 0x27, 0xF7, + 0x68, 0x6E, 0x53, 0x61, 0xDB, 0xFF, 0xC5, 0x9A, 0xAC, 0xC9, 0x37, 0xF1, + 0x06, 0x85, 0x6C, 0xB0, 0x69, 0x9A, 0xF6, 0x4D, 0xE1, 0x62, 0x7B, 0x3B, + 0xA7, 0x39, 0x5B, 0x46, 0xE2, 0x84, 0x42, 0xA1, 0x94, 0xCB, 0xAA, 0xBC, + 0xCA, 0xFD, 0xED, 0x53, 0xFD, 0x50, 0x96, 0x36, 0x8A, 0x65, 0x39, 0xCA, + 0xB6, 0x9D, 0x83, 0x82, 0x47, 0x0D, 0xA8, 0x85, 0xE5, 0x59, 0x24, 0xA5, + 0xFF, 0xF1, 0x46, 0xEF, 0xED, 0xC9, 0xDB, 0x4D, 0x3B, 0x7D, 0xF9, 0xC9, + 0x76, 0xF4, 0x96, 0xE5, 0x17, 0x3D, 0x48, 0xE5, 0x81, 0x1A, 0x4C, 0x0C, + 0x59, 0x4E, 0x93, 0x02, 0xE4, 0xA1, 0xF9, 0xE0, 0x51, 0x03, 0x56, 0xD0, + 0x9E, 0xDC, 0x85, 0x26, 0xB3, 0x01, 0xC1, 0x10, 0x97, 0xF3, 0xD3, 0xFF, + 0xD7, 0x6C, 0x73, 0xD6, 0x6C, 0xA5, 0x87, 0x70, 0xFD, 0x29, 0xA7, 0x0A, + 0xE2, 0xE2, 0x52, 0x2F, 0xE2, 0xB1, 0xF2, 0xF4, 0xE9, 0xB0, 0xC9, 0x2F, + 0x99, 0x9A, 0x6B, 0x14, 0x5B, 0x48, 0x4D, 0x05, 0xCF, 0xC5, 0x6C, 0x3E, + 0xD5, 0x59, 0xFF, 0xC9, 0xDC, 0x09, 0x52, 0x23, 0x9D, 0x61, 0x23, 0x62, + 0x11, 0xD4, 0xC2, 0xE6, 0x33, 0x73, 0x4E, 0xE0, 0xA8, 0xA3, 0xEF, 0xFE, + 0xC7, 0x72, 0x7E, 0xA9, 0xB4, 0xAB, 0xD2, 0x74, 0x31, 0x34, 0x45, 0x2C, + 0xDA, 0x2D, 0xE2, 0x85, 0xE4, 0x20, 0xC8, 0xD8, 0x53, 0xE6, 0x50, 0xC3, + 0x6F, 0xD1, 0xCC, 0xF5, 0x3B, 0x8D, 0xE3, 0xA1, 0xFF, 0x1B, 0x3C, 0xDD, + 0xC2, 0xE6, 0xA5, 0xFD, 0xDA, 0x77, 0xFE, 0x4D, 0x71, 0x7D, 0x7B, 0x3A, + 0x4A, 0x3A, 0x83, 0x07, 0x41, 0x9E, 0xE9, 0x4B, 0xE6, 0xF8, 0xA5, 0xE0, + 0xE6, 0xF4, 0x95, 0xCE, 0xF8, 0x93, 0xFD, 0x9B, 0xA1, 0x93, 0xCD, 0xB0, + 0xC9, 0x3E, 0xBA, 0x32, 0x55, 0xF9, 0x2B, 0xD8, 0xF8, 0xD3, 0xFF, 0xF6, + 0x0F, 0xCF, 0x7F, 0x3A, 0x6E, 0x46, 0x4F, 0x07, 0x0F, 0xC4, 0x5C, 0xD6, + 0xFF, 0xAA, 0xAC, 0x84, 0x01, 0xE1, 0x4E, 0x51, 0x0B, 0x90, 0x1C, 0x9A, + 0x40, 0x5E, 0xFA, 0xA1, 0x26, 0x90, 0xD3, 0x19, 0x36, 0x03, 0x92, 0xCA, + 0x80, 0x5C, 0x21, 0xB5, 0x3F, 0xE3, 0xD9, 0x38, 0x8E, 0xE3, 0x38, 0xE6, + 0x9C, 0xBF, 0x11, 0x6C, 0xC4, 0xC1, 0x46, 0xA5, 0xD0, 0x7D, 0x42, 0xEF, + 0x56, 0xA8, 0xFF, 0x32, 0xD4, 0xC1, 0x5E, 0xA6, 0xEB, 0x97, 0xD5, 0x2A, + 0x47, 0xAA, 0xA7, 0x46, 0xEF, 0xD1, 0xFF, 0xD7, 0xB8, 0x6B, 0x84, 0x9F, + 0xF3, 0x0F, 0x1C, 0x58, 0xBD, 0x19, 0xCE, 0xEB, 0x3F, 0xDF, 0x69, 0xBF, + 0xC5, 0x1D, 0x0B, 0xDD, 0x33, 0xF4, 0x3F, 0xE4, 0xD2, 0xDC, 0x4F, 0x56, + 0xA1, 0xF4, 0x3C, 0xDD, 0x76, 0x04, 0x39, 0x0E, 0x46, 0xDE, 0x9F, 0xE4, + 0x68, 0x7E, 0x5F, 0x6C, 0x39, 0x03, 0xDF, 0x60, 0xBB, 0x3C, 0xD3, 0x3F, + 0xFF, 0x07, 0xFB, 0x7E, 0x15, 0xEB, 0xB3, 0x4B, 0x3E, 0xE4, 0xFF, 0xF3, + 0xA5, 0x93, 0xF4, 0x4E, 0x17, 0x29, 0xF0, 0xDF, 0xFD, 0xDB, 0xDF, 0x2F, + 0x6C, 0x44, 0x3F, 0x05, 0x9F, 0xF1, 0xBB, 0xAF, 0x46, 0x9F, 0xF9, 0x12, + 0xFF, 0xD4, 0x93, 0xDE, 0x3C, 0xE8, 0xBB, 0x26, 0xF2, 0xDD, 0x71, 0x9B, + 0x95, 0xFF, 0x4D, 0x3A, 0xCB, 0xAA, 0xA3, 0xBF, 0xBB, 0x0A, 0x67, 0x83, + 0x5A, 0xD9, 0xA0, 0xF0, 0x45, 0x15, 0xB9, 0x99, 0x66, 0x0F, 0xB2, 0x9C, + 0x04, 0x9E, 0x59, 0x46, 0x23, 0x70, 0x74, 0xAA, 0x3A, 0x09, 0x61, 0x27, + 0x2E, 0xA5, 0x97, 0x3A, 0x11, 0x5D, 0x96, 0x8F, 0x0C, 0xC6, 0xA8, 0x58, + 0x42, 0x97, 0xE2, 0xD0, 0x3F, 0xC5, 0x6B, 0xD3, 0xDA, 0x02, 0x1F, 0xC2, + 0xF6, 0xFF, 0x2A, 0x95, 0xA9, 0xD0, 0xD5, 0x8B, 0x74, 0xFC, 0x7A, 0x7A, + 0x83, 0x34, 0xD3, 0xFF, 0x29, 0xF9, 0x4C, 0xA5, 0x5F, 0x69, 0xAF, 0x62, + 0xF5, 0x33, 0x48, 0x33, 0x5B, 0xFC, 0x70, 0x01, 0x55, 0x40, 0xD0, 0x65, + 0xC6, 0xC6, 0xF4, 0xA9, 0x7C, 0x66, 0x5A, 0x27, 0x97, 0xCB, 0x95, 0x27, + 0x95, 0xEB, 0x3A, 0xD1, 0xEB, 0x3A, 0x7D, 0xFD, 0x56, 0x16, 0x9A, 0xE2, + 0x67, 0x9A, 0xA2, 0xC7, 0xC1, 0x32, 0x67, 0x92, 0xE4, 0x33, 0xF0, 0x7E, + 0xE6, 0xB5, 0x11, 0x7F, 0x7B, 0xF4, 0x5A, 0x0B, 0x5B, 0x60, 0x29, 0xF5, + 0x76, 0xAA, 0x49, 0xE6, 0x76, 0xAE, 0x9D, 0xDB, 0x22, 0x87, 0x4D, 0xDB, + 0x27, 0xC3, 0xFF, 0xEF, 0x55, 0x23, 0x77, 0xFE, 0x27, 0x6F, 0x23, 0x8A, + 0xA7, 0xD3, 0x57, 0x66, 0x30, 0xC9, 0x56, 0x35, 0x7C, 0x7F, 0x44, 0x2A, + 0x31, 0xDB, 0xC1, 0x13, 0x97, 0xF6, 0x99, 0x31, 0x91, 0x36, 0x8F, 0xE8, + 0xDA, 0xD4, 0x76, 0xFA, 0xF2, 0x7D, 0x42, 0x3B, 0xFB, 0x76, 0x2A, 0x2C, + 0xD3, 0x12, 0x0F, 0xF9, 0x95, 0xAC, 0x7B, 0xFF, 0x3C, 0xAC, 0xB7, 0xA3, + 0x8E, 0x17, 0x6F, 0x8E, 0x46, 0x5F, 0x92, 0xBF, 0xC6, 0x84, 0xAE, 0x68, + 0x2B, 0x22, 0x92, 0x45, 0xE4, 0x9D, 0xB4, 0x45, 0xA3, 0x97, 0xBC, 0xA2, + 0xD8, 0xE8, 0x1C, 0xA9, 0xDC, 0x18, 0xC9, 0x85, 0xE5, 0x2C, 0x93, 0x0B, + 0x9E, 0x24, 0x7A, 0x49, 0x3F, 0xF8, 0x9D, 0x77, 0x39, 0x0B, 0x89, 0x5C, + 0x21, 0xD1, 0x4B, 0xC2, 0x9D, 0x41, 0x1B, 0xF9, 0xD2, 0x7E, 0xC0, 0x7F, + 0x07, 0xF4, 0x4F, 0xC1, 0x77, 0x33, 0xCF, 0x30, 0x98, 0xDD, 0x9F, 0xEC, + 0x47, 0xE4, 0x83, 0xE6, 0x3B, 0x8D, 0x06, 0xEC, 0xF9, 0x2A, 0x7F, 0xA5, + 0xAF, 0x28, 0xF0, 0x29, 0xAC, 0xC2, 0xE3, 0x5D, 0x46, 0x83, 0x62, 0x93, + 0x5F, 0x88, 0x37, 0xD9, 0x3C, 0x1A, 0xC8, 0xAA, 0xB6, 0xCF, 0x7A, 0x9F, + 0x70, 0x0F, 0x29, 0xF1, 0x2B, 0x9E, 0xD3, 0xD5, 0x54, 0xE3, 0xDF, 0x40, + 0xE1, 0x99, 0x75, 0xCF, 0xEE, 0x95, 0x2B, 0x36, 0x85, 0x85, 0x5A, 0xDA, + 0xFF, 0x4D, 0x0D, 0xA5, 0xFC, 0x41, 0xBF, 0x41, 0xE0, 0x12, 0x36, 0x21, + 0xDA, 0x3A, 0x19, 0x2F, 0xA1, 0xCA, 0xF4, 0x2E, 0xF1, 0x93, 0xBF, 0x44, + 0x8F, 0xFE, 0x1F, 0x9B, 0x85, 0xAE, 0x51, 0x47, 0x5B, 0x86, 0x62, 0xCB, + 0x52, 0x8D, 0x52, 0xEC, 0xB9, 0xF3, 0x66, 0x9B, 0xA2, 0x57, 0x3E, 0xF9, + 0xDA, 0x8C, 0xED, 0x8F, 0xEA, 0x2D, 0xBB, 0x11, 0xC1, 0x14, 0xDF, 0x48, + 0x78, 0x89, 0xCE, 0xE4, 0x10, 0x3C, 0x72, 0x34, 0x36, 0x08, 0x7D, 0x63, + 0x43, 0x9C, 0x08, 0x02, 0x25, 0xFF, 0x9F, 0x95, 0xDD, 0xAC, 0x4A, 0x41, + 0x72, 0xB1, 0x7C, 0xD6, 0xFC, 0xC5, 0xDE, 0xA1, 0xBA, 0x25, 0x9B, 0xB1, + 0x01, 0x29, 0xBA, 0x5B, 0x48, 0x49, 0xC0, 0x37, 0x09, 0x2A, 0x35, 0x09, + 0x3F, 0xD7, 0xA3, 0x5B, 0xD6, 0x21, 0x7C, 0x08, 0x77, 0xD5, 0x1A, 0xFD, + 0x43, 0x00, 0x77, 0x6C, 0xA1, 0xCB, 0x22, 0xC3, 0x13, 0xC2, 0x3B, 0xAC, + 0xA8, 0x4B, 0x75, 0x94, 0x82, 0xC2, 0xAA, 0x92, 0x0D, 0x8A, 0xEB, 0x4D, + 0x8C, 0x51, 0x07, 0xF5, 0x10, 0x96, 0x0C, 0x76, 0x5F, 0xD4, 0x66, 0xA2, + 0x47, 0xD5, 0x91, 0x72, 0x47, 0x4C, 0xDC, 0xCB, 0xB1, 0x20, 0x81, 0x23, + 0x25, 0x59, 0x4C, 0x5D, 0x4C, 0xF1, 0xBF, 0x73, 0x88, 0x4A, 0xC9, 0x74, + 0x75, 0x03, 0x85, 0xCF, 0x58, 0x42, 0xF1, 0x1E, 0x4D, 0x1F, 0x4B, 0x66, + 0x0C, 0x28, 0xB8, 0xD4, 0xE9, 0xF6, 0x61, 0xBF, 0xFB, 0xCA, 0x52, 0x0F, + 0xC1, 0x81, 0xC5, 0x4B, 0x67, 0x4E, 0x1D, 0xE9, 0xAA, 0xC5, 0x6F, 0xE7, + 0xA7, 0xB3, 0x5A, 0xA8, 0xB7, 0xC3, 0x86, 0xF8, 0x2E, 0x07, 0xE5, 0x50, + 0xF6, 0xF4, 0x77, 0x62, 0xFE, 0x7C, 0x56, 0x47, 0x46, 0xCF, 0x88, 0x2A, + 0xFF, 0x2D, 0x23, 0xD7, 0x37, 0xFF, 0xEB, 0xB3, 0x48, 0x1A, 0x2D, 0x59, + 0xA8, 0xE3, 0xF9, 0x4B, 0x01, 0x8B, 0xFE, 0x2D, 0xA2, 0xA7, 0xE3, 0xBF, + 0x8D, 0x1F, 0xBF, 0x88, 0xF0, 0x2B, 0x6F, 0x4F, 0x19, 0x4E, 0x12, 0x13, + 0xC7, 0xF3, 0xC3, 0x3B, 0x41, 0xCB, 0x89, 0x96, 0xBC, 0x38, 0x71, 0x5D, + 0x48, 0xD5, 0x77, 0x4B, 0x67, 0x0F, 0x33, 0x95, 0xFD, 0x82, 0xBF, 0x43, + 0x20, 0xD4, 0x4C, 0x39, 0x9E, 0x2D, 0x74, 0xF2, 0x2B, 0xFD, 0x57, 0x31, + 0x0C, 0xFE, 0xFB, 0x29, 0x2D, 0x85, 0xC1, 0xF4, 0x87, 0x32, 0xCA, 0xA9, + 0xF2, 0x05, 0x65, 0xB4, 0x93, 0x57, 0xFE, 0x9B, 0x77, 0x5A, 0xD3, 0x11, + 0x6B, 0xF3, 0x3D, 0x53, 0x0C, 0x61, 0x9D, 0x91, 0x2F, 0x9D, 0xA6, 0x7B, + 0x1F, 0x54, 0x67, 0x43, 0x4D, 0x61, 0x69, 0x9E, 0xF3, 0x22, 0x73, 0x48, + 0x56, 0xE5, 0x89, 0xB3, 0x19, 0xFC, 0x8C, 0xD8, 0x88, 0x3F, 0x3A, 0xFB, + 0xEA, 0x3C, 0x71, 0x67, 0x7F, 0xD1, 0x40, 0x48, 0x70, 0x4A, 0x6F, 0x98, + 0xFC, 0xE9, 0x80, 0xC9, 0x9E, 0x1A, 0x52, 0xD9, 0x0F, 0x93, 0x39, 0x1D, + 0x1F, 0x6F, 0x73, 0xD8, 0xDF, 0x0D, 0x91, 0xB0, 0xE8, 0xBF, 0x92, 0xF1, + 0xBD, 0xA1, 0xF0, 0xBC, 0x9D, 0x71, 0x1B, 0xCB, 0x67, 0xC4, 0xA7, 0x38, + 0xB6, 0x8B, 0x1D, 0x67, 0x7A, 0xFC, 0xCE, 0x25, 0x31, 0x7D, 0x05, 0x34, + 0x72, 0x5E, 0x8C, 0xB4, 0xB7, 0xF7, 0x28, 0x8A, 0xBF, 0xF8, 0x9A, 0xC3, + 0xED, 0x72, 0x4D, 0x6F, 0x1F, 0x9A, 0x31, 0xEB, 0x8D, 0x71, 0x72, 0x1F, + 0x7B, 0x7C, 0x12, 0xC1, 0x62, 0xB5, 0xCF, 0xE8, 0x97, 0x52, 0x6B, 0xC9, + 0x9A, 0x13, 0x17, 0xD4, 0x26, 0xAA, 0xF0, 0x70, 0x7A, 0x4E, 0xF5, 0xDF, + 0xB8, 0x88, 0x72, 0xB1, 0xBB, 0x64, 0x3F, 0xB9, 0x8A, 0xFD, 0xD2, 0x8B, + 0xFD, 0x33, 0xF4, 0xF5, 0xFC, 0xD4, 0xB4, 0x43, 0x63, 0xD3, 0x6B, 0x2A, + 0x34, 0xB0, 0x73, 0xA8, 0x53, 0x33, 0x35, 0xED, 0x7F, 0x6E, 0x38, 0xEF, + 0x81, 0x51, 0xFE, 0xD9, 0xB9, 0x6E, 0x9F, 0x90, 0x47, 0xF3, 0xD3, 0x29, + 0x18, 0xE2, 0x8C, 0xF0, 0x7C, 0x32, 0x15, 0x0A, 0x85, 0x3B, 0xAF, 0xA7, + 0xA0, 0x16, 0xE2, 0x50, 0xCF, 0x4D, 0xA6, 0xB4, 0xB0, 0xE7, 0xD9, 0x29, + 0xD6, 0xBE, 0x2F, 0xD8, 0x90, 0x67, 0x84, 0x07, 0x59, 0xED, 0x31, 0x2D, + 0xCC, 0xB9, 0xE9, 0xF4, 0x86, 0x70, 0x0A, 0x59, 0xB5, 0x77, 0x6F, 0x4A, + 0x55, 0x1A, 0x97, 0xF3, 0xA0, 0x26, 0x6B, 0x42, 0x33, 0x42, 0x64, 0x62, + 0x60, 0xF0, 0x89, 0xA4, 0x3C, 0x42, 0x2D, 0x9A, 0x4B, 0x39, 0x83, 0x45, + 0xC8, 0xCA, 0x3D, 0x7D, 0xE7, 0xBC, 0x90, 0x6D, 0x86, 0x1D, 0x55, 0x84, + 0xD4, 0xD3, 0x3F, 0xE3, 0xA7, 0xDF, 0x38, 0x50, 0xAF, 0xCC, 0xFF, 0x68, + 0xA7, 0xCF, 0x89, 0x9E, 0xA7, 0xA6, 0x58, 0xE6, 0x23, 0xAB, 0xC3, 0x23, + 0x47, 0x63, 0x5A, 0x3E, 0x7A, 0xE5, 0x78, 0x3B, 0x92, 0x94, 0x25, 0xF4, + 0xCC, 0xAA, 0x63, 0xF7, 0x91, 0x84, 0x09, 0x81, 0x2B, 0xA1, 0x4F, 0x8C, + 0x20, 0x94, 0xC6, 0x91, 0x2B, 0x1A, 0xD8, 0xBD, 0xB5, 0xD8, 0x61, 0xB7, + 0xF5, 0xF5, 0x83, 0x3D, 0xAB, 0x36, 0xAA, 0x54, 0xD7, 0xBF, 0x7C, 0x6C, + 0x12, 0x86, 0xB2, 0xD8, 0x73, 0x3F, 0x40, 0x3E, 0x78, 0xD4, 0x80, 0xF5, + 0x2D, 0x1C, 0x8D, 0xD1, 0x28, 0x29, 0x93, 0x51, 0x92, 0xE2, 0x9A, 0xA9, + 0xFC, 0xC4, 0x37, 0x5B, 0x23, 0x78, 0xAF, 0x59, 0x2D, 0xFD, 0xFF, 0x6B, + 0xF8, 0x3A, 0x80, 0x05, 0xBB, 0xC5, 0x31, 0xA3, 0x7B, 0x45, 0xA3, 0x5F, + 0xD1, 0xDF, 0x79, 0xF4, 0x1D, 0xF8, 0xFF, 0xF0, 0xFF, 0x07, 0xFF, 0x1F, + 0x0E, 0x84, 0xFF, 0x03, 0xD5, 0x25, 0x61, 0x63, 0xA0, 0xAF, 0x2F, 0x90, + 0x77, 0x82, 0x0B, 0xC4, 0xE9, 0x68, 0xA0, 0x1F, 0xBF, 0xF1, 0xF9, 0x4E, + 0x74, 0x4B, 0xD1, 0x4D, 0x51, 0xD3, 0xD2, 0x4B, 0xF5, 0x3E, 0x07, 0xBE, + 0xEF, 0x8F, 0x7D, 0xF2, 0x78, 0x83, 0xFE, 0xA0, 0x31, 0x28, 0xE8, 0xDE, + 0x17, 0x36, 0xDB, 0x5C, 0x89, 0x05, 0xB4, 0xC0, 0x06, 0xFE, 0x02, 0xBE, + 0x00, 0xB2, 0x4B, 0xB7, 0xC1, 0x41, 0xEE, 0x2B, 0xC8, 0xCD, 0x20, 0xE0, + 0x83, 0xFC, 0x3F, 0xC9, 0xDB, 0x97, 0x1E, 0x02, 0x6F, 0xF3, 0x52, 0xD4, + 0xBD, 0x74, 0x6D, 0x58, 0x64, 0x4A, 0xCD, 0xF8, 0xF3, 0xE8, 0x9E, 0xFF, + 0x77, 0x21, 0xF0, 0x19, 0xFE, 0xBB, 0x26, 0x65, 0x72, 0xBC, 0x33, 0xF4, + 0xFA, 0x19, 0x1B, 0xF9, 0x92, 0xFE, 0x2A, 0x9B, 0x76, 0x10, 0xCC, 0x4E, + 0xDB, 0x05, 0x7C, 0x0B, 0xA5, 0x42, 0x41, 0x12, 0x03, 0x5D, 0x09, 0xA8, + 0x82, 0xB3, 0x94, 0x5B, 0xB1, 0x26, 0x1A, 0x85, 0xCA, 0x1C, 0x3A, 0xA6, + 0x0D, 0x4A, 0x82, 0x67, 0x60, 0x28, 0xC2, 0x93, 0x42, 0xEA, 0x81, 0x99, + 0xE9, 0xE8, 0x4A, 0x2D, 0xBE, 0xB9, 0x98, 0x59, 0x11, 0x45, 0x72, 0xF0, + 0x8C, 0x04, 0xA7, 0x59, 0x28, 0x6F, 0x52, 0x50, 0x7A, 0x60, 0x52, 0x57, + 0x59, 0x43, 0xA9, 0x28, 0xCD, 0xCB, 0xB0, 0xC2, 0xA4, 0x23, 0x70, 0x39, + 0xF1, 0x14, 0x17, 0x93, 0x4A, 0x00, 0x88, 0xD6, 0x16, 0xD4, 0x87, 0x22, + 0x3E, 0xA7, 0x3D, 0x08, 0x62, 0x23, 0x8A, 0x1A, 0x2D, 0x22, 0x66, 0xE0, + 0x82, 0xD9, 0xD1, 0x43, 0xD9, 0xCD, 0xCC, 0x70, 0x31, 0x4F, 0x82, 0x12, + 0x88, 0x03, 0x60, 0xC6, 0x7C, 0x07, 0x32, 0x0E, 0xB8, 0xBE, 0x13, 0xFA, + 0x0A, 0xB8, 0xCE, 0x44, 0xCA, 0xAB, 0x4B, 0x1F, 0x66, 0xFB, 0x5F, 0x7E, + 0x7C, 0x1F, 0x25, 0xC0, 0xA2, 0x08, 0x1B, 0x6D, 0xA0, 0x42, 0x11, 0x20, + 0x40, 0x39, 0xD0, 0x43, 0x1C, 0x58, 0x2D, 0x24, 0xFF, 0x49, 0xDD, 0xF1, + 0x4A, 0xFE, 0x0D, 0x96, 0x3C, 0xFA, 0x8C, 0xE7, 0xFB, 0xF3, 0x7C, 0xB2, + 0x0C, 0x7C, 0x11, 0xDF, 0xC0, 0x7B, 0xD5, 0xB9, 0x58, 0x1D, 0xDF, 0xF3, + 0xA0, 0x6E, 0xCE, 0x87, 0xBE, 0xC7, 0x0F, 0x0E, 0x0F, 0x35, 0x69, 0xB8, + 0x01, 0x6A, 0x06, 0xBD, 0x9B, 0xA0, 0x77, 0x82, 0xE2, 0x93, 0x99, 0x12, + 0x64, 0x56, 0x0F, 0x43, 0xC3, 0x30, 0x8E, 0xB7, 0xF7, 0x3B, 0x86, 0xDE, + 0xEE, 0x4D, 0x8F, 0x29, 0xD7, 0x03, 0x33, 0x02, 0xDA, 0x3F, 0x2B, 0xF1, + 0x88, 0x42, 0xDA, 0xFF, 0xCE, 0x5C, 0x60, 0x26, 0x76, 0x33, 0xFF, 0xD3, + 0x94, 0x9B, 0x35, 0x68, 0x03, 0xEE, 0x0C, 0xFA, 0x9F, 0xCB, 0xD0, 0xA5, + 0xED, 0x43, 0x3B, 0xE6, 0x7A, 0x3A, 0x25, 0x8D, 0xA3, 0xF3, 0x98, 0x0A, + 0x53, 0x8F, 0xAB, 0x02, 0xC8, 0x00, 0x46, 0xD0, 0x05, 0x55, 0xA7, 0xE6, + 0xCE, 0x46, 0xBE, 0xD3, 0x09, 0x9D, 0xE6, 0x4A, 0x34, 0x25, 0xAA, 0x80, + 0xD2, 0x52, 0xC2, 0x3C, 0x1D, 0xC7, 0x1C, 0xCB, 0xA0, 0x5B, 0x66, 0xDD, + 0xCF, 0x20, 0x1F, 0x20, 0x22, 0xF5, 0x17, 0x71, 0x17, 0xA7, 0xCA, 0xE6, + 0x48, 0xFF, 0x26, 0x58, 0xF1, 0x04, 0x0E, 0x24, 0x72, 0x50, 0x83, 0x00, + 0x27, 0xDB, 0xA6, 0x86, 0x1F, 0x8D, 0x8F, 0x20, 0xDC, 0xFF, 0xE7, 0x37, + 0xD0, 0xA4, 0xD8, 0x7C, 0x46, 0x0E, 0x2A, 0xA5, 0x2D, 0x94, 0x32, 0x88, + 0xB3, 0xC9, 0xA7, 0x15, 0xB4, 0x94, 0xB8, 0xB5, 0xE0, 0xE9, 0x49, 0x4B, + 0x47, 0x82, 0x10, 0x41, 0xC7, 0xE4, 0x57, 0xB8, 0x4C, 0xFE, 0x3F, 0x7C, + 0x06, 0x69, 0x52, 0x5A, 0x26, 0xF8, 0xA5, 0x13, 0x9D, 0x78, 0x41, 0x48, + 0xFF, 0x1F, 0xF0, 0x83, 0x87, 0xF0, 0xFF, 0x7F, 0xD0, 0x16, 0xCB, 0xE1, + 0xDB, 0x4B, 0xC7, 0x63, 0x1B, 0x55, 0xAA, 0xED, 0x7D, 0x9C, 0x47, 0x0D, + 0x68, 0xAD, 0x8E, 0x8B, 0xFB, 0x4D, 0x4B, 0xC6, 0x4B, 0xAB, 0x44, 0xFF, + 0x10, 0x30, 0x02, 0x06, 0x13, 0x07, 0x68, 0x3C, 0x84, 0x47, 0x6C, 0x41, + 0x09, 0x3E, 0x10, 0xA1, 0x27, 0x65, 0x0A, 0xD7, 0x16, 0xF5, 0xE2, 0xB8, + 0xB8, 0x67, 0x16, 0xDC, 0xA9, 0x23, 0x92, 0xEC, 0x3C, 0x3A, 0x3D, 0x1D, + 0xBD, 0xE4, 0x46, 0x14, 0x40, 0x0A, 0x3A, 0xBF, 0x68, 0x09, 0x4E, 0x17, + 0xA2, 0x7B, 0x8E, 0x3F, 0x73, 0xFE, 0x4F, 0xED, 0x9F, 0x7D, 0x3B, 0x36, + 0x5B, 0x3D, 0x57, 0xE5, 0xB4, 0x70, 0xC7, 0x96, 0xBB, 0x6B, 0xFD, 0x68, + 0x5A, 0xD5, 0xDB, 0xF1, 0x4C, 0xCF, 0xC3, 0x66, 0x1A, 0x03, 0x58, 0x59, + 0xF9, 0xC1, 0x02, 0x36, 0x7C, 0xB8, 0x81, 0x12, 0x93, 0x6D, 0xD9, 0xC9, + 0x12, 0x37, 0xA3, 0x2E, 0x5E, 0xC8, 0x00, 0x17, 0xB6, 0x20, 0x00, 0x2A, + 0x40, 0x40, 0x0A, 0x2F, 0x20, 0x96, 0xD0, 0x80, 0x2D, 0x94, 0xF0, 0x43, + 0x16, 0x0B, 0xD0, 0x11, 0x42, 0x8B, 0x66, 0xA3, 0xC8, 0x01, 0xC6, 0x90, + 0x00, 0x4F, 0xAC, 0xA0, 0x00, 0x02, 0x3B, 0x4C, 0x50, 0xAA, 0xA9, 0x01, + 0x82, 0x20, 0x9C, 0xF0, 0x40, 0x8A, 0x05, 0x6C, 0x54, 0x80, 0x09, 0x1E, + 0x70, 0x0F, 0x4A, 0x24, 0x21, 0x43, 0xEB, 0xCA, 0x29, 0xEE, 0xC1, 0x2A, + 0x52, 0xAE, 0x99, 0x21, 0x0D, 0xD7, 0xCC, 0xD4, 0x4A, 0xD3, 0xAB, 0xCB, + 0xEF, 0x82, 0x8C, 0xBA, 0x2A, 0xB5, 0xE9, 0xC7, 0xBC, 0xE4, 0xC1, 0x43, + 0x30, 0xE6, 0x45, 0x20, 0xE0, 0x21, 0x03, 0xA3, 0x08, 0x28, 0xC0, 0x09, + 0x29, 0xE4, 0x28, 0x00, 0x0A, 0x7A, 0xE0, 0xF4, 0x00, 0xC6, 0x49, 0x96, + 0x4C, 0x16, 0x23, 0x82, 0xC1, 0x26, 0x60, 0x37, 0x98, 0x7F, 0x73, 0x5B, + 0x4E, 0x2F, 0xCB, 0xB3, 0x68, 0xEC, 0x89, 0xDE, 0xC0, 0x88, 0x6E, 0x49, + 0x03, 0x23, 0xC2, 0x65, 0x4A, 0x0D, 0x89, 0x5C, 0xCE, 0x33, 0xBD, 0xA9, + 0xA1, 0x30, 0x26, 0xFA, 0x5C, 0x58, 0xF4, 0x86, 0x9D, 0xD0, 0xC8, 0xAE, + 0x8C, 0x2C, 0x22, 0x15, 0xD0, 0x9A, 0xE4, 0x1A, 0x44, 0xAA, 0xA5, 0x60, + 0x8D, 0x9B, 0x5C, 0x9B, 0xB9, 0x1D, 0xCB, 0xB2, 0xDB, 0xEF, 0x7E, 0xAC, + 0x5E, 0x16, 0xBC, 0x31, 0xC1, 0x3F, 0xDD, 0x8F, 0xFF, 0x79, 0x7A, 0x16, + 0x18, 0x63, 0x1C, 0x13, 0x39, 0xD7, 0xC9, 0x67, 0x7C, 0x79, 0x7A, 0x83, + 0x57, 0xFB, 0x82, 0x50, 0x4F, 0xD9, 0x2F, 0xC7, 0xA3, 0x85, 0x71, 0xB0, + 0x5C, 0xB8, 0x62, 0x1B, 0xB1, 0xA5, 0x60, 0xEC, 0xCB, 0xBC, 0x1D, 0x19, + 0xB7, 0x23, 0x7E, 0x3B, 0xA9, 0x31, 0xFB, 0x82, 0x90, 0x9D, 0x6A, 0x0D, + 0xCD, 0x6E, 0x0C, 0x7B, 0x6A, 0x96, 0x55, 0x8F, 0x04, 0x43, 0xA3, 0x73, + 0xA1, 0xD1, 0xC1, 0x2F, 0x74, 0xC1, 0xD1, 0x4F, 0x43, 0x1F, 0x33, 0xD1, + 0x3D, 0xAD, 0x09, 0x2E, 0x33, 0x91, 0x35, 0xA9, 0x19, 0x77, 0x86, 0x35, + 0x34, 0x2B, 0x56, 0xF9, 0x48, 0x71, 0xAC, 0x57, 0xA7, 0x56, 0xD2, 0x34, + 0x18, 0xAC, 0x1D, 0xC4, 0xD7, 0x33, 0x47, 0xA1, 0x8E, 0x3E, 0x89, 0xB8, + 0xDA, 0xDC, 0xCA, 0x95, 0xB1, 0x6C, 0x5E, 0x0C, 0x73, 0x8E, 0x97, 0x1C, + 0x27, 0xEC, 0x51, 0xFD, 0x43, 0x1F, 0xCE, 0xC7, 0xF4, 0x6D, 0xD9, 0x73, + 0xD5, 0xD1, 0xB6, 0xC4, 0x4F, 0x25, 0x5E, 0x9E, 0x20, 0xF7, 0xC0, 0xD7, + 0x4F, 0xE4, 0xA5, 0x74, 0xEA, 0x1B, 0xF4, 0xC9, 0x9E, 0x29, 0xFA, 0x45, + 0x1E, 0x98, 0x2F, 0xA7, 0x43, 0xA7, 0xFB, 0xDE, 0x60, 0x53, 0x29, 0x0E, + 0xEB, 0x14, 0xF2, 0xCA, 0x10, 0x58, 0xE9, 0xCF, 0xC5, 0xF2, 0x17, 0xF2, + 0xBE, 0xE8, 0x4C, 0x39, 0x9E, 0xCC, 0xBD, 0xC6, 0x7F, 0x21, 0x10, 0x7B, + 0x2E, 0x60, 0x89, 0xE7, 0x60, 0x23, 0xE7, 0x82, 0x1C, 0x88, 0x94, 0x83, + 0xE5, 0x9A, 0x43, 0xFC, 0x90, 0xC7, 0x95, 0x33, 0x6C, 0xE8, 0xA3, 0xF7, + 0x95, 0x3B, 0xF8, 0xD0, 0x17, 0xBA, 0x21, 0xEE, 0xE4, 0x62, 0x2B, 0x1F, + 0xEE, 0x2C, 0xC1, 0x70, 0x4A, 0x26, 0xCA, 0xEB, 0xAF, 0xBF, 0x6E, 0xAD, + 0x3D, 0x59, 0x6D, 0xDF, 0xE5, 0xE4, 0xE4, 0x64, 0x53, 0xF9, 0xF6, 0x6F, + 0xB7, 0x28, 0x28, 0xF4, 0x3F, 0x4A, 0xEB, 0xF7, 0x23, 0x15, 0xF3, 0x9D, + 0x56, 0xEA, 0x32, 0x1C, 0xD3, 0xE4, 0xF8, 0xBF, 0x07, 0x6F, 0x31, 0xB6, + 0x94, 0xC2, 0x58, 0x7C, 0x4F, 0x0A, 0xAA, 0x27, 0xE1, 0x5F, 0xD2, 0x7F, + 0x97, 0x25, 0x6C, 0x48, 0x5D, 0x32, 0x7B, 0x65, 0xA2, 0x82, 0x39, 0x1F, + 0x13, 0x8A, 0x4D, 0x3C, 0xF3, 0x2F, 0x67, 0x56, 0xDE, 0xD1, 0xE6, 0x01, + 0xEA, 0xAB, 0xD6, 0xB3, 0x7D, 0xEB, 0x82, 0x31, 0xAE, 0x2F, 0xFF, 0x4C, + 0x6F, 0xCF, 0x4D, 0xBF, 0x41, 0x1D, 0x3D, 0xAB, 0xA3, 0xF0, 0xCC, 0x61, + 0x45, 0x8A, 0x63, 0xAE, 0xB8, 0x28, 0xEC, 0xD8, 0x37, 0x68, 0x09, 0x99, + 0xCE, 0x43, 0x4B, 0x08, 0x9B, 0x1C, 0x07, 0xE0, 0xB3, 0x93, 0x42, 0x7C, + 0xF2, 0xB3, 0x7C, 0x03, 0xFC, 0xF3, 0x80, 0xFF, 0x2D, 0xE3, 0x21, 0x06, + 0x04, 0x15, 0x2D, 0xC4, 0x06, 0x15, 0x9F, 0x61, 0x8E, 0xB0, 0x79, 0x9A, + 0x54, 0x99, 0xEB, 0xE3, 0x29, 0x84, 0xE0, 0x3D, 0xEC, 0xAC, 0xB4, 0xC5, + 0x77, 0x36, 0x13, 0x1D, 0xE9, 0xCE, 0x4D, 0x3B, 0xD5, 0xF4, 0xEE, 0x3D, + 0xC4, 0xC7, 0x14, 0x21, 0xEA, 0xEB, 0xB9, 0x22, 0x0F, 0x75, 0x45, 0x1C, + 0xEA, 0x8A, 0x3E, 0x9A, 0x77, 0x31, 0xE7, 0x62, 0x0E, 0x92, 0xD4, 0xC7, + 0xCF, 0xF2, 0x71, 0x1C, 0x04, 0x3B, 0x92, 0x97, 0x09, 0x7B, 0xED, 0xD9, + 0x4F, 0xBB, 0x67, 0xE0, 0x10, 0xEA, 0xA8, 0x59, 0xCF, 0x3E, 0xED, 0x86, + 0x61, 0xB3, 0x77, 0xD7, 0xBA, 0x5B, 0xA9, 0xA2, 0xDF, 0x97, 0x5A, 0x4C, + 0x7D, 0x83, 0x38, 0xB3, 0x22, 0xA5, 0xE1, 0x52, 0xDA, 0x77, 0x9E, 0xDF, + 0xA5, 0x06, 0x9A, 0x1A, 0x4B, 0x7B, 0x9C, 0x4D, 0x69, 0xBD, 0xF2, 0xD1, + 0xC0, 0xFE, 0x9E, 0x78, 0xFE, 0xCA, 0xA5, 0xDD, 0x7E, 0xDF, 0xFF, 0xFE, + 0xE0, 0x62, 0xDA, 0x5D, 0xF9, 0x71, 0xC1, 0xB0, 0x62, 0x87, 0x4D, 0x66, + 0xAF, 0x49, 0xAE, 0xC9, 0x77, 0x2B, 0x19, 0xE5, 0x7C, 0xB0, 0xA0, 0x1D, + 0x0D, 0x16, 0x72, 0xBC, 0x31, 0x19, 0xEA, 0x8B, 0xC9, 0xBC, 0x8A, 0xE2, + 0x50, 0x20, 0x86, 0x65, 0x1F, 0x16, 0x9E, 0x46, 0x83, 0x05, 0xF5, 0x96, + 0x1F, 0xF8, 0x7F, 0x36, 0xE4, 0x07, 0xCD, 0x54, 0xA7, 0x37, 0xAF, 0xB9, + 0x98, 0xB4, 0x45, 0x67, 0x05, 0xE7, 0xE0, 0xD0, 0x43, 0x6B, 0x6B, 0x43, + 0x4C, 0x69, 0xC3, 0xC8, 0x54, 0x3F, 0x15, 0x5C, 0x2F, 0xC0, 0x62, 0x6B, + 0xBD, 0x94, 0x1D, 0x4E, 0x8E, 0xFF, 0x05, 0x7F, 0xD5, 0x14, 0x97, 0x3C, + 0x4E, 0x43, 0x48, 0xFF, 0x05, 0xE5, 0x0B, 0xCC, 0x77, 0xA0, 0xEC, 0xE2, + 0x54, 0x04, 0xDD, 0x2D, 0x39, 0x64, 0xD5, 0x7A, 0x6F, 0x22, 0x53, 0x64, + 0x4E, 0xA3, 0x48, 0xA6, 0xB4, 0x8C, 0x75, 0x24, 0x53, 0xB6, 0x93, 0x1D, + 0xC5, 0x70, 0x0A, 0x3C, 0x7E, 0x56, 0xD8, 0x4C, 0xBF, 0xB9, 0x16, 0xFF, + 0xE6, 0xE3, 0x61, 0xC6, 0xB0, 0x57, 0xCC, 0x8B, 0xA7, 0xCF, 0x96, 0xC3, + 0x1E, 0x9D, 0x9B, 0xFE, 0x5C, 0x79, 0xA2, 0xA7, 0x9B, 0xC8, 0xC4, 0xC0, + 0x60, 0x13, 0x95, 0xFE, 0x7C, 0x44, 0xCF, 0x3C, 0x4F, 0xB2, 0x1D, 0x99, + 0xD0, 0x90, 0x82, 0x27, 0x19, 0xAB, 0x35, 0x1A, 0x71, 0xA6, 0xBD, 0xA9, + 0xA3, 0x94, 0x9B, 0x1C, 0x26, 0x86, 0x30, 0xFB, 0xE5, 0x6C, 0xA3, 0x73, + 0x3D, 0xF5, 0x84, 0xFE, 0xA4, 0x90, 0xE6, 0x63, 0xD3, 0x16, 0xE1, 0xB3, + 0x92, 0xBC, 0x9D, 0xCD, 0x24, 0x74, 0x5C, 0xBE, 0x57, 0x44, 0xBF, 0xA2, + 0x6A, 0xC3, 0x9E, 0xFA, 0x4D, 0xA1, 0x66, 0xD7, 0xE6, 0xFE, 0xAD, 0x2D, + 0x2C, 0xBF, 0xA1, 0x99, 0xD2, 0x7E, 0x2D, 0xE8, 0x15, 0x15, 0x68, 0xDF, + 0xC7, 0x2C, 0x75, 0xF4, 0x8F, 0x54, 0x2E, 0xB0, 0x85, 0x28, 0xFE, 0x69, + 0x8B, 0x68, 0xE9, 0x27, 0x44, 0xBB, 0xD6, 0x10, 0x38, 0x9E, 0x4C, 0x55, + 0x32, 0x25, 0x9A, 0xC9, 0xC0, 0x81, 0x28, 0x16, 0x71, 0x2F, 0x55, 0x04, + 0xFE, 0xF7, 0x22, 0xF1, 0x7F, 0xBD, 0xAA, 0x86, 0x53, 0x38, 0xD0, 0x31, + 0x7E, 0xE2, 0x40, 0x94, 0x33, 0x4F, 0x38, 0xB2, 0x52, 0x12, 0x86, 0xE2, + 0x92, 0x79, 0x91, 0xE8, 0xB7, 0xA2, 0x99, 0x87, 0x51, 0xC7, 0x53, 0x5C, + 0x69, 0x8C, 0xE8, 0xDE, 0x79, 0xB2, 0xD9, 0xFA, 0xE3, 0x9A, 0x45, 0xDC, + 0x4E, 0x0D, 0xCF, 0xE3, 0x63, 0x91, 0x47, 0x63, 0x2A, 0xD7, 0x82, 0x07, + 0xD5, 0xEC, 0x31, 0x24, 0x4C, 0xD5, 0xA1, 0xF5, 0xE4, 0x5E, 0x19, 0xDA, + 0x8F, 0x81, 0xC1, 0xA1, 0x83, 0x17, 0xFF, 0x5E, 0x17, 0xF9, 0xA6, 0xEF, + 0xD2, 0x59, 0xFA, 0x0F, 0xDC, 0x04, 0x1A, 0xA8, 0x1B, 0x03, 0x81, 0xF8, + 0x02, 0xD1, 0x1F, 0x52, 0x2E, 0x82, 0x6D, 0xAC, 0x6B, 0xE6, 0x49, 0x90, + 0x8B, 0x4C, 0x93, 0x1E, 0xBA, 0x92, 0x32, 0xD3, 0x8E, 0x4A, 0x98, 0xD3, + 0x0F, 0x60, 0x6A, 0x6B, 0x76, 0x0D, 0x6B, 0x25, 0x11, 0x76, 0xC8, 0xE8, + 0x43, 0x62, 0x9C, 0x62, 0x00, 0xFF, 0xC4, 0x52, 0xA4, 0x3B, 0xA2, 0x7B, + 0xA3, 0xB7, 0x14, 0xC1, 0x0D, 0x1E, 0xE1, 0xD1, 0xCC, 0xB3, 0xCB, 0x53, + 0x1E, 0xFA, 0xB3, 0x68, 0x1A, 0xA5, 0x28, 0x8B, 0x0F, 0x69, 0x89, 0x40, + 0x1E, 0x27, 0x95, 0x4C, 0x44, 0x33, 0x11, 0x08, 0x93, 0x31, 0x78, 0xC1, + 0x83, 0x44, 0xC8, 0xD1, 0xB4, 0xEC, 0xD2, 0x27, 0xED, 0xB2, 0xFC, 0x32, + 0x22, 0x8D, 0xE2, 0x5A, 0x56, 0x41, 0xD9, 0xFC, 0x1A, 0x57, 0xFB, 0xCD, + 0x88, 0xD4, 0x13, 0x91, 0x95, 0xD3, 0xB5, 0x61, 0x33, 0x73, 0xCF, 0x2C, + 0xE3, 0x68, 0x7E, 0x6F, 0x8B, 0xF8, 0x8B, 0xF8, 0xC6, 0xC4, 0xC0, 0x60, + 0x4B, 0x0E, 0x59, 0x19, 0xB5, 0x95, 0x5B, 0xEB, 0xA8, 0x69, 0xBF, 0x75, + 0x01, 0x4F, 0x63, 0xD6, 0x8E, 0x4D, 0x13, 0x78, 0xAC, 0x52, 0xE3, 0x73, + 0xA2, 0xA5, 0x76, 0xFB, 0x32, 0x31, 0x27, 0xCF, 0x84, 0xC1, 0xDA, 0xFB, + 0x22, 0xBE, 0xE0, 0x49, 0xFB, 0xCC, 0xF0, 0xD4, 0xC1, 0xF6, 0x17, 0x4F, + 0xDA, 0xB9, 0x9C, 0x0E, 0x22, 0xB9, 0xD8, 0xC8, 0x9D, 0x63, 0x77, 0x5E, + 0xBD, 0xC4, 0xB0, 0xDC, 0xB1, 0xD9, 0xDB, 0x34, 0x43, 0xBF, 0x1E, 0x35, + 0x53, 0xC1, 0x95, 0x72, 0x3D, 0xCF, 0x5E, 0x41, 0x17, 0x6C, 0xA9, 0x77, + 0xD2, 0xD4, 0xEC, 0x61, 0x18, 0x5B, 0xF6, 0x9A, 0xBD, 0xA0, 0xEC, 0xA3, + 0xD9, 0x60, 0xAA, 0x57, 0x6F, 0x8A, 0x59, 0xFD, 0x7A, 0xAA, 0x57, 0xFE, + 0xFF, 0x8F, 0x6B, 0x48, 0x53, 0xCD, 0xF1, 0x1E, 0x3C, 0x0B, 0x26, 0x96, + 0xB1, 0x1D, 0x3D, 0x13, 0xC5, 0x99, 0x31, 0xDD, 0x5D, 0xCD, 0x23, 0x39, + 0x96, 0xEE, 0x35, 0xEC, 0xCE, 0xB2, 0xBA, 0x6B, 0x5C, 0x68, 0x26, 0xC9, + 0x8E, 0x42, 0x58, 0x54, 0x71, 0xA8, 0x56, 0x53, 0x2B, 0x57, 0x70, 0xDA, + 0x04, 0x3E, 0x0F, 0x4E, 0x33, 0x99, 0xBE, 0x0E, 0x2C, 0xC6, 0xF8, 0x02, + 0x07, 0x00, 0xA4, 0xE0, 0x62, 0x00, 0x4B, 0x14, 0x96, 0x10, 0x85, 0x32, + 0x5C, 0x80, 0x75, 0x8A, 0x2F, 0xBD, 0x97, 0xBE, 0x64, 0x52, 0x10, 0xB3, + 0xAC, 0xE4, 0x9F, 0xFF, 0xC9, 0xDF, 0x05, 0x45, 0x62, 0x4E, 0x51, 0xD0, + 0xBF, 0xAC, 0x0D, 0x95, 0xD3, 0x84, 0x8C, 0x4A, 0xE9, 0x48, 0x17, 0xBE, + 0x08, 0x80, 0x90, 0x17, 0xDE, 0x01, 0x0D, 0x2D, 0x09, 0x19, 0x28, 0x39, + 0xB4, 0xED, 0x24, 0x09, 0x6A, 0x92, 0xC4, 0x49, 0x48, 0x68, 0x16, 0x09, + 0x3A, 0x42, 0xE2, 0x22, 0x25, 0xB2, 0x90, 0x12, 0x27, 0xB1, 0xFD, 0x9F, + 0x80, 0xAE, 0xE6, 0x38, 0x89, 0xBF, 0xA3, 0x94, 0x02, 0xD3, 0xDC, 0x24, + 0xC5, 0xED, 0x61, 0x4A, 0x31, 0x13, 0x83, 0x2A, 0xAA, 0x5C, 0x7A, 0xD8, + 0x51, 0xA5, 0x12, 0x83, 0xCD, 0xA4, 0x3A, 0xCD, 0x36, 0xFE, 0xA0, 0x14, + 0xB4, 0xC7, 0xC5, 0x3E, 0xA8, 0x02, 0x33, 0x60, 0x4F, 0x40, 0xF0, 0x0B, + 0x90, 0xCB, 0x04, 0x90, 0xBF, 0x54, 0xD3, 0x0F, 0x02, 0xE1, 0xAE, 0xB8, + 0x89, 0x24, 0x5B, 0x52, 0x84, 0x71, 0xD8, 0x46, 0x29, 0x3E, 0x94, 0x35, + 0x49, 0xE1, 0xA9, 0xC8, 0x1A, 0x29, 0xBE, 0x30, 0xAA, 0x2A, 0x44, 0x6D, + 0x9A, 0xEA, 0x44, 0xC9, 0x16, 0x10, 0x8F, 0xC8, 0x33, 0x20, 0xDC, 0xCE, + 0xB9, 0xD3, 0x96, 0x2A, 0x9C, 0xC2, 0x66, 0x6A, 0xB2, 0x06, 0x08, 0x67, + 0x8E, 0x9E, 0x6B, 0xE4, 0x0E, 0x03, 0xE4, 0xE0, 0xBB, 0x0E, 0xAF, 0xE9, + 0x63, 0x31, 0x79, 0xE5, 0xCA, 0xC6, 0xA1, 0x6C, 0x27, 0x05, 0xA7, 0x02, + 0x41, 0x6B, 0xFA, 0x72, 0x2C, 0x07, 0xC4, 0xEB, 0xBA, 0xD1, 0xF3, 0xA6, + 0xB0, 0xF3, 0xD9, 0xC2, 0x7F, 0x66, 0xD7, 0xFC, 0x35, 0x5C, 0x17, 0x33, + 0xCD, 0x94, 0xA9, 0x13, 0x1C, 0x73, 0x52, 0x01, 0x5D, 0x62, 0x77, 0x9A, + 0x28, 0x0E, 0x8F, 0x78, 0xCE, 0x1C, 0xBF, 0xB3, 0xC4, 0x33, 0x7A, 0x2F, + 0x0E, 0xFC, 0xFB, 0x2F, 0xEE, 0xC5, 0xBD, 0xBE, 0x69, 0x2B, 0x65, 0xFA, + 0x54, 0xA5, 0x55, 0xE9, 0x77, 0x96, 0x73, 0x35, 0xE9, 0xCA, 0xC2, 0xD5, + 0x29, 0x7E, 0x49, 0x64, 0x4C, 0x2A, 0x3E, 0x78, 0x6C, 0x77, 0xEA, 0x63, + 0xC4, 0x7D, 0x3E, 0x26, 0xEF, 0xC5, 0xC5, 0x4E, 0x9C, 0x8E, 0x38, 0x33, + 0x43, 0x3F, 0xEA, 0x95, 0x60, 0x8A, 0xA3, 0xF8, 0x2C, 0x32, 0x24, 0x79, + 0x64, 0xD4, 0x13, 0x83, 0x47, 0xC6, 0xFD, 0x94, 0x59, 0xF8, 0xFA, 0xCA, + 0x9B, 0x79, 0x14, 0x26, 0xE3, 0x3A, 0x03, 0xCD, 0x93, 0xCB, 0x53, 0xA2, + 0xD7, 0x27, 0x7A, 0xD5, 0x69, 0xE6, 0xEF, 0xC2, 0xFE, 0x18, 0xEF, 0xFE, + 0xA6, 0xA7, 0x36, 0xC3, 0xDB, 0x9B, 0x5E, 0xFB, 0xA6, 0x1C, 0x8B, 0xB4, + 0x19, 0xE7, 0x93, 0x85, 0xED, 0xE3, 0x93, 0xFD, 0x37, 0xB3, 0x36, 0xB3, + 0xD0, 0x55, 0xD6, 0xF7, 0xDB, 0xE6, 0x5C, 0xEB, 0xDD, 0x5B, 0x45, 0xC1, + 0x12, 0xA4, 0xB8, 0xBB, 0x95, 0xDD, 0x3E, 0xEB, 0x6F, 0x65, 0x4F, 0x9D, + 0x45, 0xC1, 0x67, 0xAE, 0x77, 0x76, 0xC1, 0x77, 0x1F, 0x93, 0x25, 0x1D, + 0xBA, 0x04, 0x1F, 0x04, 0x5F, 0x2E, 0x05, 0x55, 0xE1, 0x63, 0x97, 0xF3, + 0xAC, 0x22, 0xF9, 0x2F, 0x2F, 0x58, 0x2A, 0x9D, 0x3C, 0xF8, 0x93, 0x2E, + 0x46, 0x54, 0x28, 0xA4, 0x8A, 0x91, 0x14, 0xCA, 0xE6, 0x12, 0xCB, 0xD7, + 0x48, 0xA3, 0xBC, 0xF4, 0xDA, 0x56, 0x3E, 0x5F, 0xC5, 0xFE, 0x97, 0xA0, + 0xFF, 0x0D, 0x26, 0x0E, 0x8C, 0x87, 0x92, 0x87, 0x3E, 0x0F, 0x60, 0x63, + 0x22, 0xE0, 0x6B, 0xAD, 0x0D, 0x9F, 0x9E, 0xAE, 0xCE, 0xA0, 0x17, 0xF1, + 0xFF, 0xF8, 0xAC, 0x7F, 0x2D, 0x99, 0xE2, 0x1B, 0x96, 0xDE, 0xA8, 0xF4, + 0xC6, 0xE9, 0x9F, 0x9D, 0xC4, 0xE1, 0x19, 0x8D, 0x70, 0x4A, 0x26, 0xA5, + 0xCF, 0x7A, 0x85, 0xE5, 0xD9, 0xEF, 0xE8, 0xFF, 0xFB, 0xC7, 0x59, 0x46, + 0x4D, 0xCF, 0x42, 0x57, 0x7E, 0xC3, 0x86, 0x34, 0x73, 0x47, 0xA3, 0x1F, + 0x51, 0x6F, 0x13, 0x41, 0x3A, 0x0E, 0x9B, 0xDD, 0xFE, 0x03, 0x58, 0x4D, + 0x7B, 0x09, 0xAE, 0x23, 0x10, 0x6C, 0x0D, 0x1F, 0xD1, 0x97, 0x88, 0x3E, + 0xD8, 0x35, 0x03, 0xCB, 0xF2, 0xB2, 0x6A, 0x44, 0x07, 0x45, 0x2F, 0x87, + 0xC8, 0xA0, 0x6E, 0x49, 0x23, 0x06, 0xE5, 0x32, 0x03, 0xFE, 0x34, 0x30, + 0x6A, 0x08, 0x0C, 0xAA, 0x64, 0x92, 0x48, 0x15, 0xB0, 0x86, 0x25, 0xF1, + 0xDF, 0x5D, 0xE3, 0xE4, 0xFD, 0x9B, 0xA4, 0x29, 0xC7, 0x97, 0xD6, 0x78, + 0xE3, 0xBA, 0xC6, 0xEC, 0xEA, 0x88, 0x2F, 0x17, 0xA8, 0x94, 0xDE, 0x60, + 0xA2, 0x9E, 0x6A, 0xC6, 0x44, 0xAD, 0x2F, 0xA2, 0x2A, 0x92, 0x34, 0x7F, + 0xA7, 0x35, 0x0F, 0xEA, 0xD2, 0xF3, 0xD3, 0x8D, 0xCC, 0x56, 0x2D, 0xCB, + 0xB6, 0xAA, 0xA9, 0x80, 0x51, 0xC7, 0x26, 0xF2, 0x93, 0x17, 0x87, 0x5B, + 0x5A, 0xE3, 0xA7, 0xB0, 0x1C, 0x9F, 0x72, 0x7B, 0xDB, 0x38, 0x08, 0x07, + 0xDF, 0xFD, 0x35, 0x41, 0xF0, 0xA6, 0xF0, 0x8C, 0xAC, 0x71, 0xC5, 0xD7, + 0x10, 0x4F, 0xE4, 0xD2, 0x7B, 0x80, 0x8B, 0x94, 0xDB, 0xD9, 0x46, 0x7A, + 0xC5, 0x4D, 0x4D, 0xA4, 0x85, 0x86, 0x54, 0x6C, 0x26, 0xFA, 0xE5, 0xD4, + 0xF1, 0x35, 0x5A, 0x4E, 0x5E, 0x8F, 0x3D, 0x9D, 0x48, 0xCB, 0x89, 0x03, + 0xE3, 0xF6, 0xEB, 0xB1, 0xE3, 0x57, 0x48, 0x47, 0xEC, 0x1F, 0x63, 0x84, + 0xF0, 0xC3, 0x46, 0xF0, 0xCC, 0xC3, 0x18, 0x81, 0x87, 0x11, 0x6A, 0x8C, + 0x10, 0x1E, 0x34, 0xC2, 0x57, 0x12, 0x89, 0xA9, 0x11, 0x23, 0xD2, 0x9B, + 0x15, 0x52, 0xDF, 0x43, 0xD5, 0xCA, 0x95, 0x1A, 0x4E, 0xB9, 0x8D, 0x3F, + 0xD9, 0x7D, 0xA8, 0x4A, 0xD8, 0x05, 0xC9, 0xE5, 0x1C, 0x18, 0x1F, 0xD1, + 0x9B, 0x98, 0x4A, 0x8D, 0x34, 0x90, 0x99, 0xE2, 0x43, 0xF4, 0xA5, 0xEE, + 0xD7, 0x5B, 0xD4, 0x90, 0xFD, 0x2D, 0xBB, 0x0C, 0xE2, 0x14, 0x36, 0x7F, + 0xE8, 0x52, 0x17, 0x3D, 0x47, 0xC7, 0xEA, 0x7E, 0x4C, 0x41, 0xE0, 0x39, + 0xFC, 0xAD, 0x8E, 0xB4, 0x97, 0x40, 0x1D, 0xEA, 0xAD, 0xFC, 0x3F, 0xE6, + 0xDE, 0x09, 0x83, 0x0F, 0xFE, 0xFF, 0xFF, 0x0B, 0x99, 0xF1, 0xDF, 0xD2, + 0x1E, 0xC1, 0xF5, 0x08, 0x08, 0x1E, 0xF9, 0xCB, 0xFF, 0x3F, 0x4B, 0x66, + 0x04, 0xDA, 0x8F, 0x46, 0x5A, 0x0D, 0x85, 0xC0, 0xC7, 0x7C, 0x4E, 0x9E, + 0x63, 0x10, 0xF6, 0x32, 0x75, 0xFE, 0xFF, 0x9F, 0xD1, 0xCF, 0xAC, 0xE9, + 0x66, 0xCF, 0x59, 0xE9, 0x34, 0xFD, 0x6F, 0x92, 0xD3, 0x39, 0x81, 0xF5, + 0x3F, 0x77, 0x07, 0xF1, 0x2B, 0xED, 0xA8, 0x3E, 0x26, 0xEE, 0x65, 0x5A, + 0x9D, 0x38, 0x5C, 0xEB, 0x74, 0xCF, 0x99, 0xB1, 0x2F, 0x96, 0x22, 0xDE, + 0xFF, 0xEF, 0x62, 0xFC, 0x70, 0xC9, 0x79, 0xE2, 0x29, 0x2C, 0x6D, 0xCC, + 0xC7, 0x8E, 0x9F, 0x77, 0x1A, 0x96, 0x2D, 0x17, 0x2E, 0x45, 0x2C, 0x32, + 0x05, 0x54, 0x02, 0x66, 0x57, 0xE2, 0xE9, 0x64, 0x8E, 0xEB, 0x8B, 0x6E, + 0xBB, 0x6B, 0xED, 0x67, 0xA5, 0x34, 0x57, 0x69, 0x9A, 0xB4, 0x4C, 0xF9, + 0xB1, 0x99, 0xA9, 0x00, 0x5F, 0x0A, 0x07, 0x31, 0x88, 0x80, 0x81, 0x01, + 0x9F, 0x2F, 0x6E, 0xC4, 0x20, 0x03, 0x86, 0x2F, 0xDC, 0x80, 0x67, 0x02, + 0x57, 0xB0, 0xC2, 0x0B, 0xCB, 0xC4, 0x37, 0x18, 0x10, 0xC2, 0xA2, 0x22, + 0x8A, 0x07, 0xA4, 0x7E, 0xD8, 0xD0, 0xA0, 0x08, 0x19, 0xC4, 0x60, 0x0A, + 0x1F, 0x38, 0x52, 0xD0, 0x43, 0x05, 0x1A, 0x20, 0x09, 0x05, 0x0C, 0xA0, + 0x7C, 0x02, 0x3A, 0x96, 0x07, 0x0C, 0x60, 0x45, 0x00, 0xAE, 0x0B, 0x11, + 0x05, 0x00, 0x2A, 0xF5, 0xC1, 0xB5, 0x75, 0x14, 0xE6, 0xA9, 0x49, 0x79, + 0x44, 0x03, 0x39, 0x65, 0xCB, 0x27, 0xD0, 0x26, 0x10, 0x37, 0x38, 0x5F, + 0x60, 0x00, 0x28, 0xB0, 0x6D, 0xDD, 0xDB, 0xAC, 0x6E, 0x07, 0x55, 0x74, + 0x2E, 0x45, 0x52, 0x90, 0xD9, 0x60, 0xE6, 0x88, 0x07, 0xCC, 0x10, 0xE1, + 0x03, 0xF0, 0xF7, 0x2C, 0xCF, 0x20, 0xF0, 0x28, 0xB6, 0xEB, 0xD2, 0x1C, + 0xF1, 0xCD, 0xA0, 0xFB, 0xF3, 0x1C, 0x88, 0xBC, 0x2A, 0x7A, 0xDE, 0xBC, + 0xA8, 0xBC, 0xA8, 0x2F, 0xE2, 0xBA, 0x4C, 0xE5, 0xDF, 0x3E, 0xBD, 0x0F, + 0xBA, 0x2C, 0xEF, 0x5E, 0x6E, 0xE2, 0xDD, 0xC6, 0x73, 0x2F, 0x24, 0x12, + 0xA9, 0x02, 0x23, 0x86, 0x3B, 0x21, 0x52, 0x17, 0xC1, 0x39, 0xEC, 0xA1, + 0xA4, 0xAB, 0xE0, 0xCC, 0x9D, 0x55, 0x38, 0x69, 0x29, 0x9C, 0xFC, 0x51, + 0x00, 0x9F, 0x80, 0xD0, 0x47, 0x2A, 0xC0, 0x06, 0xF5, 0x86, 0x90, 0x42, + 0x15, 0xD0, 0x51, 0x30, 0xC7, 0x66, 0x12, 0xE7, 0x34, 0xBD, 0x39, 0x4D, + 0x9F, 0xD3, 0x94, 0x6C, 0xB5, 0x5A, 0x34, 0x44, 0x69, 0x02, 0x60, 0xF4, + 0x59, 0xC0, 0xE8, 0x31, 0x68, 0x80, 0x10, 0x27, 0x20, 0xBC, 0x2A, 0xE1, + 0x1F, 0x62, 0xF3, 0xD7, 0xBC, 0x59, 0xC7, 0xFF, 0x6F, 0x5C, 0xF3, 0x9B, + 0xE6, 0xE3, 0xFD, 0xC6, 0x9D, 0x65, 0x0D, 0xCD, 0x9F, 0xDE, 0xBF, 0xF3, + 0x9D, 0x9F, 0xB4, 0xDB, 0xBC, 0xC3, 0x73, 0x8C, 0xE3, 0x5C, 0x8F, 0x8B, + 0xC7, 0xDB, 0x7D, 0x37, 0x86, 0x42, 0xB5, 0x82, 0xEF, 0xF9, 0xB8, 0x51, + 0xFA, 0xA3, 0x5C, 0xA5, 0x69, 0x9A, 0x88, 0xCB, 0xF9, 0x53, 0xB5, 0x56, + 0x28, 0x66, 0x54, 0xDA, 0x2F, 0x6C, 0xEC, 0x99, 0xD6, 0xCE, 0x73, 0xEF, + 0x7C, 0x46, 0xD9, 0xB6, 0x99, 0x46, 0x43, 0x4A, 0x7B, 0xD0, 0x36, 0xDE, + 0x34, 0x49, 0x68, 0xB0, 0xA9, 0xA4, 0x19, 0x8B, 0x06, 0x3B, 0xD3, 0x35, + 0x0C, 0xA1, 0xE1, 0x23, 0x99, 0x85, 0xF8, 0xB5, 0x5F, 0x95, 0x73, 0x0D, + 0x48, 0xA2, 0xC9, 0x61, 0x4F, 0xC6, 0xDF, 0xBA, 0x9C, 0x7D, 0x3D, 0xB6, + 0x2C, 0x7A, 0x48, 0x6B, 0x74, 0xCF, 0xFF, 0xBF, 0x8E, 0xED, 0x57, 0xA1, + 0x4B, 0xFC, 0x31, 0x30, 0xB8, 0x25, 0xBB, 0x24, 0x52, 0xC8, 0x46, 0xB3, + 0xFB, 0x8C, 0x84, 0xCD, 0x8B, 0x34, 0x96, 0xB3, 0x9F, 0xF4, 0x97, 0x73, + 0xC3, 0x37, 0x7A, 0x36, 0x9D, 0x0F, 0x49, 0x8D, 0xB2, 0x1E, 0xAB, 0x73, + 0x1A, 0x3D, 0xA2, 0x7F, 0x87, 0xD2, 0xFE, 0x83, 0x4C, 0x0C, 0x4C, 0x87, + 0x7F, 0x58, 0x42, 0x35, 0xB6, 0xB0, 0xB0, 0x56, 0x50, 0x7E, 0x7F, 0x10, + 0xA2, 0x57, 0x29, 0x1A, 0x86, 0x61, 0xB3, 0x38, 0x8D, 0x72, 0x84, 0x4D, + 0xF7, 0x9E, 0x7E, 0x7D, 0x78, 0xAD, 0x28, 0x3F, 0x5C, 0xBB, 0x3F, 0x49, + 0xD9, 0xE9, 0x83, 0x17, 0xF4, 0x93, 0x7D, 0xAD, 0x4A, 0x71, 0x2F, 0x4D, + 0xA2, 0xAE, 0x55, 0x8B, 0xBB, 0x40, 0x07, 0x75, 0x5F, 0xE3, 0x76, 0x9E, + 0x9F, 0x95, 0xDA, 0x4F, 0xDF, 0x59, 0xD7, 0x51, 0xE3, 0x62, 0x5C, 0x67, + 0x8D, 0x4A, 0xD5, 0x54, 0x87, 0xFE, 0xF5, 0x64, 0x60, 0xE8, 0x19, 0x98, + 0xF1, 0xD0, 0xBE, 0x24, 0x2A, 0xCD, 0x43, 0x3D, 0xE8, 0xD9, 0xE5, 0xE1, + 0xA8, 0xA3, 0x56, 0x3A, 0xCF, 0x44, 0xAD, 0x64, 0x37, 0x63, 0xE1, 0x5A, + 0x21, 0xAE, 0xCB, 0xFE, 0x4A, 0x66, 0xBE, 0xCC, 0x1C, 0x17, 0x4A, 0x88, + 0x4C, 0x25, 0xA1, 0x1D, 0x9A, 0xB1, 0xEF, 0x7E, 0x74, 0x0D, 0x8A, 0x4B, + 0xF7, 0x2E, 0x6B, 0x5B, 0xBD, 0x9C, 0xDB, 0xFF, 0x34, 0xB3, 0x37, 0x14, + 0xFA, 0x4D, 0xA8, 0x57, 0x88, 0xE2, 0xCC, 0x0C, 0x65, 0xDC, 0xEA, 0x95, + 0x65, 0x9C, 0x99, 0x3D, 0xDB, 0xC6, 0xCE, 0x4C, 0x5C, 0xB9, 0x58, 0x46, + 0xBF, 0x92, 0xF5, 0x7D, 0x6E, 0x8A, 0x43, 0x6F, 0x31, 0xCE, 0xA2, 0x19, + 0x4C, 0xFB, 0x76, 0x7E, 0xFF, 0x73, 0x2F, 0x35, 0x77, 0x57, 0xAD, 0xA3, + 0x57, 0x03, 0x2F, 0xFE, 0x0F, 0x59, 0x4D, 0x7B, 0x66, 0x9A, 0x9A, 0x13, + 0x46, 0x8A, 0xE4, 0xD0, 0x40, 0x7D, 0x09, 0xA3, 0xC2, 0x05, 0x2A, 0x50, + 0xF9, 0x08, 0xA9, 0xC1, 0x02, 0x68, 0x78, 0x22, 0x08, 0x0B, 0x9B, 0x0E, + 0x7C, 0x60, 0x02, 0xD8, 0xF8, 0xFE, 0xFF, 0x7F, 0xD1, 0x70, 0xFB, 0x86, + 0xE5, 0x44, 0xEF, 0x3B, 0xD5, 0xE9, 0xA2, 0xB6, 0x20, 0x7C, 0x08, 0x4C, + 0xE8, 0x28, 0x1B, 0x84, 0xD3, 0xC0, 0x21, 0xFA, 0x29, 0xC0, 0x2F, 0x27, + 0x96, 0xA9, 0xA8, 0x84, 0x84, 0x95, 0x13, 0x53, 0xAB, 0x5A, 0x50, 0x39, + 0x85, 0x48, 0x00, 0x86, 0x04, 0x01, 0x00, 0x73, 0xB5, 0x01, 0x1C, 0x10, + 0x8D, 0x46, 0x82, 0x28, 0xCB, 0x33, 0x51, 0x7E, 0xC4, 0x04, 0xCB, 0x5E, + 0x4B, 0x1F, 0x68, 0x46, 0x01, 0xA5, 0x98, 0x21, 0x00, 0x20, 0x20, 0x00, + 0x01, 0x04, 0x12, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x82, 0x81, 0xEA, + 0x39, 0x44, 0xC2, 0xF2, 0x59, 0xD3, 0x1C, 0xB0, 0xF7, 0x45, 0xF7, 0x8F, + 0x49, 0xE0, 0xF9, 0x60, 0xB4, 0x8C, 0x56, 0x81, 0x2F, 0xF9, 0xC4, 0x5E, + 0xD4, 0x1C, 0x36, 0xD4, 0xA1, 0xBB, 0x6A, 0x81, 0xDB, 0x65, 0x4A, 0xAA, + 0xAF, 0xB3, 0x94, 0x1D, 0x68, 0x74, 0x01, 0xDB, 0xE4, 0x91, 0x4F, 0x5A, + 0x5E, 0x1C, 0x19, 0xE4, 0x6C, 0xA4, 0x67, 0x13, 0x01, 0xF7, 0x09, 0x4D, + 0xB0, 0x61, 0x7D, 0xE8, 0xA7, 0xBE, 0x50, 0x99, 0x55, 0x97, 0x31, 0xC7, + 0x6C, 0xC9, 0xB4, 0xE7, 0xEC, 0x55, 0x3F, 0x39, 0x75, 0x53, 0x36, 0xDA, + 0xC8, 0x7E, 0x4E, 0xFB, 0xE2, 0x8E, 0x0D, 0xF8, 0x92, 0xB5, 0x69, 0x07, + 0x86, 0x00, 0x82, 0x75, 0x52, 0x1E, 0x9F, 0x7A, 0x70, 0xBA, 0xC4, 0x20, + 0xB5, 0xD5, 0xA9, 0xF5, 0x85, 0xD9, 0x60, 0x81, 0xD2, 0xB0, 0x76, 0x23, + 0x42, 0xDA, 0x82, 0x25, 0x8A, 0x7C, 0xE1, 0x02, 0xDB, 0xBA, 0xEE, 0x15, + 0x59, 0xC0, 0x29, 0x45, 0xEC, 0x6B, 0x37, 0xD8, 0xAF, 0x7D, 0x2C, 0xB0, + 0xEB, 0xF1, 0x89, 0xD0, 0x36, 0xBB, 0x87, 0x1D, 0xAB, 0xD8, 0xD4, 0x4D, + 0xE7, 0x3A, 0xB8, 0xCD, 0x84, 0x6E, 0x59, 0x5D, 0x94, 0x21, 0x4D, 0xEF, + 0x6C, 0x8E, 0x76, 0x10, 0x72, 0xAB, 0xDF, 0xEA, 0xD5, 0x14, 0x70, 0x36, + 0xBB, 0xA3, 0x82, 0x9D, 0xF3, 0x58, 0xA5, 0x7B, 0x0B, 0x14, 0x6A, 0x83, + 0xCD, 0xED, 0x00, 0x40, 0xEC, 0xF2, 0xDA, 0xD2, 0x7F, 0xCA, 0x04, 0xB7, + 0x92, 0x0B, 0xB7, 0x57, 0xA4, 0x3D, 0x36, 0x7D, 0xF5, 0xEF, 0x7B, 0xBA, + 0xFF, 0xED, 0x62, 0xFE, 0xC1, 0xE8, 0x7E, 0x32, 0xBE, 0x7F, 0x24, 0x5B, + 0x94, 0xCD, 0xBB, 0x4A, 0xF6, 0xEA, 0x67, 0x4E, 0xF6, 0x49, 0x25, 0x87, + 0x82, 0xB2, 0x27, 0x2C, 0x46, 0xD2, 0xEB, 0xD6, 0x81, 0xB4, 0x8B, 0xF1, + 0x0E, 0xF5, 0x70, 0xBA, 0x3A, 0xF5, 0xFF, 0x21, 0xB8, 0x51, 0x7F, 0x94, + 0x59, 0x88, 0xD5, 0xBA, 0x16, 0x1C, 0x17, 0x35, 0x65, 0xD2, 0x64, 0xC5, + 0x93, 0x56, 0x02, 0x3B, 0xDE, 0x4A, 0xB0, 0xE0, 0xEB, 0x55, 0x4B, 0x25, + 0x7C, 0x8E, 0xA6, 0x04, 0x68, 0xA3, 0x43, 0xDA, 0x12, 0xDB, 0x33, 0x1B, + 0xB2, 0xC8, 0x8D, 0xB3, 0xB2, 0x17, 0x16, 0xF6, 0x13, 0xF5, 0xE6, 0x09, + 0xD0, 0xED, 0x87, 0x06, 0x79, 0x9A, 0x02, 0x8D, 0x52, 0xC7, 0xD0, 0x2B, + 0x48, 0xD1, 0x94, 0x8D, 0x7C, 0x68, 0x38, 0x3E, 0x72, 0xD4, 0x1D, 0x34, + 0xCB, 0x0C, 0xF6, 0xA8, 0x21, 0xA5, 0x95, 0xD6, 0x34, 0xE6, 0x60, 0xA9, + 0x48, 0x19, 0x1C, 0xC6, 0x8B, 0x82, 0xF8, 0xA9, 0xAE, 0x23, 0x41, 0xCA, + 0x18, 0x34, 0x3D, 0x34, 0x16, 0x05, 0x96, 0x52, 0xA1, 0x07, 0x4D, 0xD4, + 0xDE, 0xB1, 0x24, 0x0F, 0x8D, 0xBA, 0xB7, 0x6C, 0xD0, 0x83, 0x86, 0x05, + 0xC9, 0x5D, 0x3B, 0xF7, 0xC1, 0xD0, 0x00, 0xB2, 0xF1, 0x7B, 0xD1, 0x50, + 0x50, 0xD4, 0x48, 0xC5, 0x28, 0xEA, 0xC7, 0x2B, 0x64, 0x58, 0x01, 0xB6, + 0x60, 0x4B, 0x99, 0xA4, 0x8B, 0x26, 0x33, 0xC7, 0x85, 0xA0, 0x60, 0x0D, + 0xB3, 0x79, 0x74, 0xD6, 0xC8, 0x15, 0x41, 0x5C, 0xA3, 0xB2, 0x16, 0x7A, + 0xD0, 0x1C, 0x7E, 0xCF, 0x4A, 0xDB, 0x9C, 0x51, 0x78, 0x2F, 0xE0, 0x87, + 0xA6, 0xF5, 0xA4, 0x81, 0xE5, 0xC4, 0x73, 0x3B, 0x5E, 0xD1, 0x0B, 0x33, + 0x33, 0x27, 0x88, 0xAD, 0x2D, 0x3E, 0x1A, 0x41, 0xDA, 0x83, 0xC6, 0xB1, + 0x53, 0x34, 0xE3, 0xC9, 0x2E, 0x4F, 0x2D, 0x68, 0xA5, 0x39, 0x0B, 0xA0, + 0xCE, 0xDF, 0xDA, 0xE1, 0xCC, 0x6B, 0x7F, 0xF2, 0x4A, 0x1E, 0x1A, 0xA9, + 0xCB, 0x38, 0xAD, 0x1D, 0xC2, 0xD0, 0x79, 0x3B, 0xD3, 0xDA, 0xE4, 0x97, + 0x72, 0xAD, 0x92, 0x7E, 0x9F, 0xFB, 0xF5, 0xF5, 0xD6, 0x7C, 0x6B, 0xE6, + 0x34, 0x73, 0x24, 0x65, 0xDD, 0xC7, 0xCF, 0x1E, 0x9A, 0x62, 0xED, 0x50, + 0xC3, 0x69, 0x5B, 0xCB, 0xC1, 0xFB, 0xEE, 0x5F, 0xD1, 0x0F, 0xC8, 0xCD, + 0x68, 0x45, 0x54, 0x5F, 0x67, 0x1E, 0xC9, 0xC0, 0x79, 0x43, 0x96, 0x24, + 0xF4, 0xD0, 0x08, 0xAB, 0x50, 0x4C, 0x99, 0x10, 0x54, 0xDC, 0x31, 0xB2, + 0xF3, 0xA9, 0x99, 0x99, 0x23, 0x43, 0xA9, 0xE6, 0xC7, 0xF7, 0x2E, 0x26, + 0xF5, 0x8C, 0xB9, 0x75, 0xB9, 0xB8, 0x6A, 0x62, 0x6F, 0xAB, 0x36, 0x9C, + 0x99, 0x33, 0xD4, 0x14, 0xC2, 0xA5, 0x06, 0xC8, 0x5C, 0xD8, 0x45, 0x3A, + 0x21, 0x94, 0x5B, 0x30, 0x74, 0xFA, 0xE7, 0x8B, 0x7E, 0x7B, 0x11, 0x06, + 0x86, 0x47, 0xE2, 0x00, 0xF5, 0x40, 0x86, 0x89, 0xD1, 0xD7, 0x79, 0xD4, + 0xE5, 0x5E, 0xAC, 0x65, 0x24, 0xBD, 0x81, 0x50, 0x79, 0xE1, 0xD4, 0xBA, + 0xC8, 0xA1, 0xFD, 0x84, 0xDA, 0x26, 0x56, 0x64, 0x02, 0x1A, 0xCA, 0xE4, + 0xC5, 0x55, 0x11, 0x5C, 0x60, 0xC1, 0xB5, 0xB2, 0xB9, 0xB8, 0x03, 0xAA, + 0x1E, 0x08, 0xD6, 0x1F, 0x08, 0x61, 0x47, 0x1D, 0xC3, 0xA1, 0xE2, 0x29, + 0x2C, 0x59, 0x8B, 0xDC, 0x76, 0xEB, 0x88, 0x87, 0x3A, 0xB6, 0x1E, 0x84, + 0x5C, 0xD4, 0xEF, 0xC0, 0xA7, 0xBE, 0xE8, 0x91, 0xC2, 0x83, 0xB0, 0x7A, + 0x03, 0xD6, 0x6B, 0xCC, 0x48, 0x88, 0xA5, 0xB5, 0x6F, 0xB3, 0x34, 0x8C, + 0x3B, 0x33, 0x8E, 0xC0, 0x2F, 0xE7, 0x43, 0xF4, 0xA5, 0xE7, 0xD0, 0x98, + 0xAB, 0xEF, 0xBF, 0xB5, 0xA5, 0x17, 0x6B, 0x9A, 0x8D, 0x98, 0xAD, 0x10, + 0x8E, 0x43, 0xB7, 0x22, 0x0E, 0x87, 0xFB, 0xBD, 0xAD, 0x98, 0xDD, 0xA2, + 0x0C, 0x9F, 0x00, 0xAC, 0xD8, 0x44, 0x96, 0x50, 0x43, 0xC4, 0x09, 0x1D, + 0xFC, 0xD0, 0x7A, 0xCA, 0x6E, 0x97, 0xCD, 0xBE, 0xBA, 0x3E, 0x31, 0x1B, + 0xB1, 0x1D, 0x83, 0xE5, 0x79, 0xEC, 0xA2, 0x41, 0xC9, 0x6C, 0x33, 0x0E, + 0x7B, 0x0C, 0x3F, 0x31, 0xCD, 0x80, 0x97, 0x5E, 0xD8, 0xE8, 0x6D, 0xA2, + 0xAE, 0xB4, 0x35, 0xF7, 0x79, 0x28, 0x6B, 0xF3, 0xB2, 0x00, 0x32, 0x97, + 0xB3, 0x8C, 0xA1, 0xC2, 0x6E, 0xAF, 0xB8, 0xDF, 0xE5, 0x3A, 0x0E, 0x80, + 0xA3, 0xB3, 0xBA, 0x1C, 0x67, 0xB0, 0x72, 0xDE, 0x70, 0x7A, 0x68, 0x1D, + 0x73, 0xE8, 0xBA, 0xA7, 0xC9, 0x3A, 0xD8, 0x90, 0x45, 0xFE, 0xE6, 0xE4, + 0x7A, 0xE1, 0x75, 0xF6, 0xB8, 0x5A, 0x38, 0x97, 0xF3, 0x40, 0xF4, 0x43, + 0x3F, 0x4B, 0x20, 0xB6, 0xEE, 0x65, 0x8D, 0x9F, 0x51, 0x19, 0xC1, 0xB9, + 0x66, 0x29, 0x91, 0x8D, 0x89, 0xFF, 0x3E, 0x86, 0x9F, 0xD9, 0xDD, 0x27, + 0xCB, 0x42, 0x52, 0x6D, 0x42, 0x68, 0x22, 0x5A, 0xAB, 0x79, 0x75, 0x94, + 0x53, 0xBD, 0xA0, 0xD8, 0x1D, 0xF3, 0x6A, 0x07, 0xB1, 0xDE, 0x29, 0x36, + 0xDE, 0x96, 0x22, 0x17, 0x63, 0x74, 0xAF, 0xFD, 0x46, 0x43, 0x95, 0xE2, + 0xD2, 0xC6, 0x74, 0xF8, 0x47, 0x8C, 0xA2, 0x86, 0x56, 0x7F, 0xEA, 0x1F, + 0xE5, 0xE6, 0x91, 0xB3, 0x95, 0x15, 0xA9, 0x5E, 0x55, 0xDB, 0x17, 0x6D, + 0xCB, 0x7A, 0xAB, 0xB0, 0xB8, 0x69, 0x0A, 0x43, 0x93, 0xA2, 0xDE, 0x6A, + 0x13, 0x78, 0xC9, 0x2A, 0xAE, 0xC9, 0x6C, 0xDD, 0xC8, 0x0D, 0x29, 0xD4, + 0xBA, 0x1B, 0x07, 0xEF, 0x9B, 0x49, 0x34, 0xA6, 0x0E, 0xF3, 0xC1, 0x54, + 0xC8, 0x7C, 0x26, 0x14, 0x4F, 0x2D, 0x17, 0xED, 0xCD, 0x05, 0x47, 0xBF, + 0x77, 0xF4, 0x8F, 0x80, 0x72, 0xCE, 0x2A, 0x83, 0xFC, 0x85, 0x78, 0xFB, + 0x40, 0xE9, 0x28, 0xF6, 0xAC, 0x47, 0x5F, 0x3D, 0x75, 0x19, 0xC9, 0xBE, + 0x15, 0x94, 0x03, 0x61, 0xD6, 0x46, 0xBC, 0xF1, 0xE2, 0x8D, 0xCC, 0x6B, + 0xF5, 0x2B, 0xFB, 0xA3, 0x25, 0xD2, 0x53, 0x1A, 0x8E, 0x28, 0x92, 0x32, + 0x3A, 0xC0, 0x6F, 0x53, 0xDB, 0x86, 0x3A, 0x36, 0x35, 0xFD, 0x5C, 0xD0, + 0x4A, 0xDC, 0x19, 0xEC, 0x50, 0xCE, 0x5D, 0x31, 0x6C, 0x24, 0xCE, 0x0D, + 0x57, 0x53, 0x58, 0xC1, 0x9F, 0xBD, 0xF9, 0x08, 0xC9, 0xBF, 0x42, 0x7C, + 0x04, 0xF4, 0xA6, 0xCF, 0xF3, 0xFB, 0x7C, 0xEC, 0xF2, 0x30, 0x2C, 0x2D, + 0x32, 0xD8, 0x02, 0x2E, 0x1D, 0xB6, 0x64, 0x7E, 0x0B, 0x98, 0x05, 0x3C, + 0x5C, 0x06, 0x51, 0x28, 0x9D, 0x31, 0x9A, 0x15, 0x79, 0x22, 0x74, 0xDC, + 0x48, 0x4A, 0x1D, 0x83, 0x19, 0xD8, 0x91, 0xF4, 0x05, 0xF6, 0xBC, 0x85, + 0x5E, 0x6C, 0x44, 0xAD, 0x3B, 0xE5, 0x12, 0xA7, 0xE2, 0x95, 0xD4, 0xB9, + 0xC0, 0xB0, 0x1C, 0x44, 0xBC, 0x2D, 0x24, 0xD6, 0xBB, 0xB6, 0x39, 0x8C, + 0x15, 0x91, 0xF0, 0xCB, 0x77, 0x82, 0x19, 0xB1, 0x66, 0x06, 0xD3, 0x85, + 0x38, 0xEE, 0x06, 0x8E, 0x37, 0x70, 0xA3, 0x14, 0xCF, 0x33, 0x73, 0x1A, + 0xF2, 0xDA, 0x0E, 0xA7, 0x68, 0xA3, 0x7D, 0xF6, 0xE9, 0xCC, 0xBE, 0xDB, + 0xC7, 0x4E, 0xDD, 0x36, 0xC5, 0x3B, 0x15, 0x2D, 0x31, 0x1F, 0x21, 0xBA, + 0xC1, 0xD8, 0x1C, 0x44, 0x21, 0x37, 0x3B, 0xC0, 0x26, 0x69, 0x5A, 0xE9, + 0xCC, 0xD7, 0x54, 0xA3, 0x26, 0x97, 0x61, 0x3D, 0x65, 0x07, 0x6B, 0xDE, + 0xC4, 0x70, 0x99, 0x69, 0x79, 0x5B, 0xFB, 0xC2, 0x2E, 0x77, 0xF2, 0xD1, + 0x13, 0xA7, 0x9E, 0x61, 0xDA, 0xB4, 0x31, 0x59, 0x7C, 0x80, 0x63, 0xE2, + 0xB7, 0x22, 0x65, 0x4E, 0x85, 0xB4, 0x5E, 0xE3, 0xC0, 0xB7, 0x62, 0x30, + 0x87, 0xDD, 0x53, 0x83, 0x36, 0xCE, 0x1D, 0xCF, 0x99, 0x72, 0x37, 0xCD, + 0x1C, 0x2F, 0x25, 0xF0, 0x0B, 0xC2, 0x30, 0x45, 0x0D, 0x9E, 0x4B, 0x82, + 0x56, 0xC8, 0xA3, 0xFE, 0xDD, 0xC5, 0x98, 0x16, 0x98, 0x3B, 0x68, 0xC0, + 0x38, 0xF2, 0x9F, 0x48, 0xEA, 0x2B, 0x3A, 0x2D, 0x25, 0x25, 0x81, 0xEA, + 0x10, 0xE8, 0x30, 0x73, 0x89, 0xA5, 0x8B, 0x6B, 0xDF, 0xAA, 0x37, 0xB8, + 0xD3, 0x52, 0x78, 0xCF, 0x2F, 0xF7, 0x76, 0x98, 0x97, 0xF8, 0xC9, 0x57, + 0x41, 0x91, 0xAA, 0x31, 0xE3, 0x2D, 0x28, 0x2E, 0x33, 0xD1, 0x39, 0x92, + 0x9D, 0xFC, 0xF0, 0xF1, 0x63, 0x50, 0x20, 0x0E, 0xC4, 0xA6, 0x76, 0x00, + 0xEE, 0xA3, 0x56, 0xC7, 0x5A, 0xEF, 0xA9, 0x7F, 0x6F, 0x08, 0x84, 0xB3, + 0x75, 0x01, 0x45, 0x7B, 0x89, 0x88, 0x7A, 0x32, 0x71, 0x35, 0x28, 0x7D, + 0x58, 0xF4, 0x6C, 0x9B, 0x96, 0x69, 0x9B, 0x3D, 0x48, 0xA8, 0x72, 0x5A, + 0x14, 0xE2, 0x11, 0x77, 0x71, 0x85, 0x41, 0xB3, 0x79, 0x69, 0x0A, 0x0D, + 0x91, 0xC1, 0xD4, 0x63, 0x20, 0x80, 0x01, 0x76, 0xE7, 0x0A, 0x52, 0x92, + 0x66, 0x68, 0x2F, 0xE5, 0x2E, 0xEC, 0xAE, 0x8F, 0xC0, 0x43, 0x02, 0x8A, + 0x5D, 0x52, 0x9B, 0x08, 0xB7, 0x6C, 0x90, 0x74, 0x57, 0x79, 0x59, 0x9D, + 0x6E, 0x6B, 0x32, 0x93, 0x5B, 0x5E, 0x58, 0x14, 0xDA, 0xD0, 0xF5, 0xC0, + 0x3F, 0x6A, 0x07, 0x84, 0x57, 0x2E, 0xAA, 0x48, 0x84, 0xF5, 0x96, 0x88, + 0xB9, 0x42, 0x78, 0xB7, 0xF2, 0x31, 0x4F, 0xF8, 0x81, 0x7E, 0xD5, 0x3E, + 0xD8, 0xB9, 0xAC, 0xF3, 0x7E, 0xFC, 0x7A, 0xBA, 0x09, 0xE7, 0x4B, 0x18, + 0x59, 0x58, 0x1F, 0x4E, 0xFC, 0xF5, 0x55, 0x77, 0xCA, 0x2C, 0x65, 0xB3, + 0x5C, 0xF4, 0x1D, 0x64, 0x98, 0xA1, 0x32, 0x31, 0x33, 0x88, 0x80, 0xCE, + 0xEC, 0x22, 0x3E, 0xDE, 0x56, 0x83, 0x2B, 0xAF, 0x49, 0xA9, 0x71, 0xB0, + 0x7A, 0x1E, 0x9D, 0x57, 0x0E, 0xC0, 0x23, 0xC5, 0x1E, 0x7A, 0xE3, 0xD0, + 0x5E, 0x23, 0x12, 0xED, 0x8E, 0x90, 0xBB, 0xFB, 0xF4, 0xDA, 0xAF, 0x1B, + 0x07, 0x22, 0x2D, 0x76, 0xE5, 0x70, 0x09, 0x30, 0x11, 0x5B, 0xED, 0x90, + 0xD3, 0xC2, 0x91, 0x14, 0x71, 0xE5, 0x5E, 0xFC, 0x08, 0xFA, 0x46, 0x31, + 0x1D, 0xA3, 0xDC, 0xEC, 0x0A, 0x5F, 0xFA, 0xDB, 0xB8, 0xAF, 0x52, 0xD6, + 0x5C, 0xAE, 0x44, 0x91, 0xA8, 0x32, 0x8E, 0x21, 0x49, 0x04, 0x06, 0x0E, + 0x10, 0x9E, 0x74, 0x25, 0x39, 0xD1, 0x27, 0xD4, 0xB1, 0x8D, 0x85, 0x36, + 0xC5, 0x49, 0x9F, 0x05, 0xF4, 0x4D, 0x80, 0xD6, 0x75, 0x8D, 0x87, 0xB7, + 0xA8, 0xD6, 0x30, 0xA5, 0x8B, 0x96, 0x8A, 0xA6, 0x47, 0xCC, 0x2D, 0x76, + 0x29, 0x13, 0x2E, 0xF5, 0xB3, 0x23, 0x70, 0x4B, 0xE9, 0xDC, 0x72, 0xA8, + 0x94, 0x98, 0x05, 0xAE, 0x69, 0x1A, 0x2D, 0xA1, 0x1F, 0x59, 0xBD, 0xD2, + 0x28, 0xA3, 0x5C, 0xCA, 0x0A, 0x25, 0x1E, 0x97, 0x95, 0x96, 0xC9, 0x05, + 0x59, 0x8C, 0xDF, 0x12, 0x11, 0x68, 0x81, 0x62, 0x02, 0xB7, 0x06, 0x78, + 0x11, 0x44, 0x9C, 0x04, 0x2D, 0xAE, 0x91, 0xD6, 0x87, 0xEE, 0x20, 0xF5, + 0xD0, 0x15, 0xB4, 0xD6, 0xFB, 0x96, 0x88, 0xF9, 0x00, 0x3B, 0xAE, 0xDE, + 0xD3, 0xA0, 0xD5, 0xAA, 0x7C, 0x60, 0xE0, 0x7B, 0x76, 0xE0, 0xE5, 0xDF, + 0x18, 0x3D, 0x83, 0xB9, 0x01, 0x35, 0xEC, 0x68, 0x01, 0x7E, 0x03, 0xB0, + 0xE9, 0xC3, 0x61, 0xAD, 0x93, 0xE4, 0xDB, 0x1B, 0xAF, 0xE9, 0xE0, 0xAD, + 0xF3, 0x90, 0x99, 0xDD, 0x0C, 0x9E, 0x32, 0xBB, 0x48, 0x7E, 0xBA, 0x67, + 0x1E, 0x21, 0x73, 0xB7, 0xC5, 0x60, 0x5E, 0x2F, 0x33, 0x1E, 0x63, 0x64, + 0xD8, 0x49, 0x73, 0x88, 0x9A, 0x8F, 0xE1, 0x74, 0xAA, 0x69, 0x08, 0xE8, + 0xAD, 0xB1, 0x41, 0xC4, 0x9A, 0x70, 0x3F, 0x37, 0x5F, 0x4B, 0x47, 0x7B, + 0xCC, 0xAE, 0x0D, 0x19, 0x5A, 0x17, 0x4D, 0xC9, 0x3B, 0xB4, 0x9C, 0x33, + 0x57, 0xC0, 0xDD, 0x96, 0x79, 0xCC, 0xA2, 0x53, 0xF8, 0xDD, 0x75, 0xE7, + 0xA5, 0x2A, 0x19, 0x9E, 0x4D, 0xBD, 0x5A, 0x85, 0x01, 0x19, 0x3E, 0x6C, + 0xE0, 0x58, 0x4B, 0x6F, 0xCD, 0x47, 0xB0, 0x2C, 0x5F, 0x0E, 0xA2, 0xBD, + 0xDF, 0xA8, 0xF1, 0xDA, 0x89, 0x43, 0x8E, 0xF9, 0x19, 0xA3, 0x17, 0xBD, + 0x5C, 0x2B, 0x23, 0x11, 0xD5, 0xB4, 0xCF, 0x95, 0x60, 0xDC, 0xEF, 0xB4, + 0x79, 0xA4, 0x3D, 0x01, 0xFE, 0x77, 0x73, 0x4B, 0x2F, 0xF6, 0x00, 0xBC, + 0x18, 0xD1, 0x24, 0x6F, 0xD5, 0x6E, 0x97, 0x3E, 0xDE, 0x9A, 0xE2, 0x5D, + 0x0C, 0x92, 0xA1, 0x7F, 0xF5, 0xFB, 0x75, 0x7D, 0x8E, 0x16, 0xB6, 0x49, + 0x70, 0x5A, 0x45, 0x77, 0x6E, 0xAC, 0x41, 0xC0, 0x67, 0x3C, 0xB1, 0x0F, + 0xC1, 0x5A, 0x7C, 0x6F, 0x66, 0x11, 0xE8, 0x3F, 0x70, 0xCC, 0x1E, 0x40, + 0xE2, 0x1C, 0x70, 0xF0, 0x06, 0x65, 0x33, 0x95, 0x00, 0x35, 0x23, 0x6A, + 0x23, 0xB7, 0xF8, 0x58, 0x0F, 0xAD, 0x2D, 0x6B, 0x61, 0x39, 0x4B, 0xAF, + 0x4F, 0x24, 0xAC, 0x74, 0x3E, 0x30, 0x70, 0xD2, 0x66, 0x0A, 0xC1, 0x98, + 0xB7, 0x22, 0xBB, 0x70, 0x05, 0x09, 0x2F, 0xB6, 0xCE, 0xC5, 0xB6, 0x3B, + 0x88, 0x7A, 0xAA, 0x77, 0x10, 0xE9, 0xE3, 0xA7, 0x13, 0x3E, 0x9E, 0x50, + 0xE0, 0x1D, 0x22, 0x61, 0x70, 0xAD, 0x1F, 0xC7, 0x8F, 0x85, 0xF1, 0x36, + 0x2C, 0xEA, 0xBC, 0x6D, 0x3D, 0xC4, 0xB3, 0x12, 0xD1, 0x92, 0xAF, 0xA7, + 0x74, 0x4A, 0x16, 0x7A, 0x4B, 0xE0, 0x07, 0x6A, 0x6D, 0x50, 0xFD, 0x99, + 0x83, 0x06, 0xCC, 0x17, 0x4E, 0x05, 0xBC, 0x97, 0x79, 0x63, 0x10, 0x8D, + 0x5D, 0xAA, 0x62, 0xE5, 0xBB, 0x1B, 0x4C, 0xFA, 0x5D, 0xF4, 0x89, 0x90, + 0xBD, 0xD1, 0xD5, 0xE1, 0x2A, 0xCC, 0x02, 0x47, 0x4E, 0x26, 0xC0, 0xBD, + 0x86, 0xA0, 0xCA, 0xFA, 0x92, 0x1C, 0xB9, 0xDE, 0x65, 0xF7, 0x8F, 0xAC, + 0xCE, 0x4B, 0x36, 0x6A, 0x05, 0x5D, 0x8E, 0xFA, 0x77, 0xB2, 0x89, 0x57, + 0x62, 0x66, 0xE7, 0x28, 0x3D, 0x7D, 0x72, 0xE0, 0x51, 0x20, 0x9D, 0xDB, + 0xD7, 0x39, 0x27, 0x89, 0x02, 0xAA, 0x5E, 0x64, 0x4F, 0xD4, 0x9E, 0xB8, + 0x1D, 0x0F, 0x58, 0xEC, 0x34, 0x00, 0xCD, 0x81, 0xA3, 0xD7, 0xDE, 0xE0, + 0xC2, 0x17, 0x61, 0xBB, 0x71, 0x38, 0xCB, 0x86, 0xCB, 0xBD, 0xFB, 0x3B, + 0x7F, 0x4E, 0xB7, 0x01, 0xDF, 0x84, 0x6E, 0x2A, 0xEE, 0x15, 0x3C, 0x53, + 0x20, 0x65, 0x2B, 0xC5, 0xDA, 0x40, 0x85, 0xE9, 0xC2, 0x53, 0x25, 0x95, + 0x15, 0xEB, 0xB7, 0x70, 0xA0, 0x85, 0x60, 0x06, 0xFE, 0x0F, 0x61, 0x30, + 0x00, 0x77, 0x19, 0x27, 0xED, 0xB7, 0xBA, 0x5C, 0x92, 0xD7, 0xEF, 0xE1, + 0xBE, 0x0D, 0xB4, 0x4C, 0x45, 0x17, 0x2C, 0xD5, 0x6C, 0x74, 0xC7, 0x9C, + 0xE9, 0x3C, 0x2B, 0x28, 0xD0, 0xBA, 0x03, 0xD0, 0x6F, 0x3C, 0x11, 0xBA, + 0xEB, 0x8B, 0xA7, 0x16, 0x1B, 0xFB, 0x71, 0x59, 0x42, 0x93, 0xB8, 0x86, + 0xEA, 0xE4, 0xE0, 0xB5, 0xBA, 0xD9, 0x08, 0xA9, 0x3B, 0xB4, 0x6D, 0xCE, + 0xD7, 0xD8, 0x8A, 0x35, 0x70, 0x7D, 0x63, 0x34, 0x57, 0x25, 0xD4, 0x6D, + 0x63, 0x26, 0x17, 0xF4, 0x66, 0x5B, 0x10, 0x0F, 0x97, 0x04, 0xCF, 0xD5, + 0x76, 0xB6, 0xF4, 0x32, 0x8F, 0xF7, 0x2B, 0x34, 0x26, 0xBA, 0xF1, 0xE0, + 0xF3, 0x9A, 0x9A, 0x38, 0xE4, 0xE9, 0xF5, 0xD1, 0xDC, 0x69, 0xDA, 0x78, + 0x27, 0xCC, 0x74, 0x4A, 0xA0, 0x7F, 0x5B, 0x16, 0x9B, 0xEF, 0x97, 0xEF, + 0xCE, 0xEC, 0x5C, 0xE5, 0x4D, 0xA4, 0x41, 0xBA, 0xD9, 0x99, 0xAE, 0x41, + 0x14, 0xA8, 0x91, 0x1E, 0xC4, 0x1C, 0xF1, 0x98, 0xA0, 0x49, 0x67, 0xFC, + 0x79, 0x0C, 0x1C, 0x2B, 0xAB, 0xF6, 0xF1, 0xBD, 0x46, 0x45, 0x80, 0xEF, + 0x58, 0x91, 0x71, 0x9A, 0x6A, 0x99, 0xEC, 0xA7, 0x91, 0xB1, 0x49, 0xAD, + 0x58, 0xE2, 0xA6, 0xE9, 0x3E, 0x36, 0x6F, 0x9D, 0x9B, 0x1F, 0x52, 0x54, + 0xED, 0x25, 0xEE, 0x6E, 0x7E, 0xEB, 0xEC, 0xD1, 0x97, 0x08, 0x7A, 0x5A, + 0x47, 0x57, 0xF0, 0x45, 0x0A, 0x79, 0xF8, 0x87, 0x9F, 0x6E, 0x1D, 0xF4, + 0x5B, 0xDE, 0x8C, 0x17, 0x34, 0x74, 0x12, 0x3B, 0x4C, 0xDB, 0x9B, 0xBE, + 0x5E, 0x2D, 0x2C, 0x74, 0x5A, 0x10, 0xB9, 0x72, 0x75, 0x7F, 0x1C, 0x2B, + 0xA2, 0x6B, 0x13, 0x40, 0x4C, 0xE1, 0xAC, 0x68, 0xA0, 0x35, 0x2E, 0xA4, + 0xEE, 0x71, 0x95, 0x19, 0x31, 0x50, 0xA3, 0xE3, 0x52, 0x38, 0x17, 0x96, + 0x26, 0xF0, 0x85, 0xEE, 0xFE, 0x69, 0x60, 0xF4, 0xE0, 0x6D, 0x6E, 0x00, + 0x0B, 0x36, 0x3B, 0x1E, 0x67, 0xF9, 0x0F, 0xED, 0x2B, 0x14, 0xF1, 0x64, + 0xAB, 0xB8, 0x83, 0x0D, 0xB2, 0x0C, 0x53, 0x73, 0xD9, 0x7E, 0x35, 0x4C, + 0x40, 0xA6, 0x90, 0x69, 0x68, 0xF6, 0x8E, 0xC7, 0xE5, 0xBB, 0x7E, 0x51, + 0x21, 0x66, 0xFD, 0xC3, 0x69, 0x35, 0xDC, 0x86, 0x76, 0x4B, 0x0D, 0x08, + 0x4C, 0xA2, 0xE9, 0xF1, 0x61, 0xAA, 0x9E, 0x0C, 0xAD, 0x8C, 0xAB, 0x2E, + 0x16, 0x04, 0x2F, 0xC4, 0xDA, 0x88, 0xE4, 0x30, 0x4F, 0x66, 0xB9, 0x13, + 0xFC, 0xA1, 0x26, 0xF8, 0x61, 0xFD, 0xB0, 0x62, 0x0A, 0x1F, 0xA1, 0x94, + 0x46, 0x4A, 0xAF, 0x3B, 0x06, 0x72, 0x23, 0x01, 0xAF, 0xAF, 0x75, 0x4A, + 0x1F, 0x97, 0x17, 0x3E, 0xAA, 0x6C, 0x21, 0x5C, 0xA9, 0xE5, 0x03, 0xBC, + 0xEF, 0x7C, 0x5C, 0xD6, 0xFA, 0xE8, 0x1C, 0x90, 0x8F, 0xDC, 0xE8, 0x52, + 0xA9, 0x48, 0x3C, 0x4F, 0x17, 0x49, 0x2C, 0x9B, 0xBC, 0x36, 0xF7, 0xCC, + 0x02, 0x7B, 0x45, 0xA7, 0x38, 0x46, 0xA7, 0xA4, 0x96, 0x3F, 0xBA, 0x6F, + 0x1F, 0xB1, 0x3E, 0xA5, 0x93, 0x54, 0x0B, 0x7E, 0xDA, 0xD5, 0x8F, 0x5D, + 0x5C, 0x61, 0x13, 0x4B, 0xA4, 0x39, 0xD0, 0x40, 0x8A, 0x8D, 0x09, 0x04, + 0x5D, 0x90, 0xE2, 0x37, 0xAD, 0x0E, 0x1C, 0xCD, 0x5C, 0xF9, 0x3A, 0x82, + 0xBA, 0x09, 0x3A, 0x54, 0xC6, 0xF7, 0x05, 0x2A, 0x62, 0xB3, 0xCC, 0xCA, + 0x28, 0xD0, 0x9E, 0x35, 0x73, 0xF8, 0xDE, 0xE4, 0x77, 0x8C, 0x2D, 0xA9, + 0xF0, 0xE6, 0x0A, 0x42, 0xCC, 0x93, 0xF6, 0x6A, 0xC7, 0x47, 0x0D, 0x7C, + 0xDE, 0xD5, 0x4A, 0x84, 0xFA, 0x36, 0xEF, 0xBF, 0x0C, 0x7E, 0xDF, 0x5D, + 0x61, 0xB9, 0x4F, 0x2F, 0x53, 0x28, 0x72, 0x0C, 0x73, 0x26, 0x5E, 0xD7, + 0xA2, 0x9E, 0xD5, 0x43, 0xDA, 0x0B, 0xC7, 0xB8, 0x4A, 0xF9, 0x71, 0x59, + 0xB5, 0x06, 0x19, 0xE3, 0xEE, 0xFD, 0xDE, 0x18, 0x5B, 0x19, 0x00, 0x27, + 0xB6, 0x31, 0x10, 0x6F, 0x05, 0xA0, 0x9C, 0xE0, 0xB4, 0x9F, 0x06, 0x51, + 0x4D, 0xD1, 0xBC, 0x3F, 0x92, 0x09, 0x20, 0x2F, 0x34, 0xEA, 0x2C, 0x6C, + 0x4F, 0x51, 0x47, 0x09, 0x32, 0xC1, 0x0A, 0xBB, 0x6D, 0x45, 0x41, 0x7F, + 0xCE, 0xC1, 0x08, 0xB3, 0x62, 0x7D, 0xF7, 0x2B, 0xFD, 0xA1, 0xD8, 0xD4, + 0x2D, 0x79, 0x64, 0x4E, 0xD0, 0xE1, 0xF6, 0x54, 0xAE, 0x2E, 0xB3, 0x8D, + 0x03, 0x40, 0x5C, 0xFA, 0x7B, 0xB3, 0xD1, 0x65, 0x0A, 0xF3, 0x13, 0x5B, + 0xFE, 0x21, 0x3A, 0xDB, 0x2A, 0xEA, 0x65, 0xF0, 0xB0, 0xC5, 0x46, 0xD3, + 0x41, 0xC1, 0x57, 0x80, 0x0B, 0x1B, 0xF7, 0xE9, 0x95, 0xF8, 0xC5, 0xCF, + 0x03, 0x18, 0xC1, 0x7A, 0xA1, 0x8F, 0x96, 0xB4, 0xA2, 0xAF, 0x56, 0x30, + 0x34, 0x6E, 0x6D, 0x81, 0xBA, 0x25, 0x30, 0xF7, 0xC8, 0x5D, 0xB9, 0xD5, + 0x42, 0x99, 0x40, 0x56, 0xA3, 0xCD, 0x11, 0x5B, 0x42, 0x80, 0x81, 0xDE, + 0xCB, 0x27, 0x9E, 0xA3, 0xEF, 0xB6, 0x6F, 0xD9, 0x5A, 0x5E, 0xBD, 0x94, + 0xAC, 0xA5, 0x01, 0x58, 0x07, 0x10, 0x4F, 0xC7, 0xBC, 0xDC, 0x6D, 0x87, + 0x31, 0x6A, 0x18, 0xC9, 0xA5, 0xBE, 0x20, 0x98, 0x97, 0xA5, 0xFA, 0xA5, + 0xD9, 0x4E, 0xBB, 0x5F, 0x66, 0xB3, 0x53, 0x5F, 0x35, 0xB9, 0x9B, 0xCF, + 0xAD, 0xB1, 0x8C, 0xDA, 0x60, 0x8B, 0x88, 0x69, 0x09, 0x4E, 0x75, 0x6A, + 0x2F, 0x92, 0x89, 0xBE, 0x7A, 0x6A, 0xFF, 0x1C, 0x70, 0xBB, 0x97, 0xD4, + 0x5E, 0x12, 0x7C, 0x8B, 0x4C, 0x65, 0xBB, 0x54, 0x55, 0xE8, 0x39, 0x94, + 0xEE, 0xDE, 0xEF, 0xD6, 0x0E, 0x8F, 0xEB, 0x73, 0x53, 0x83, 0xAD, 0xDF, + 0xB3, 0xB9, 0x1D, 0x49, 0xC1, 0x69, 0x89, 0xCF, 0x61, 0x05, 0x15, 0x3C, + 0x85, 0x48, 0x62, 0x0B, 0x9B, 0x12, 0x0D, 0x81, 0x41, 0x8B, 0xE1, 0x9F, + 0x2D, 0x5A, 0xF8, 0x1D, 0x58, 0xA6, 0x20, 0x38, 0xD5, 0x36, 0x1B, 0xB6, + 0xF4, 0x7E, 0x75, 0xEC, 0xBE, 0xAD, 0x88, 0x5D, 0xB5, 0x14, 0x65, 0x00, + 0xA1, 0x0C, 0x7C, 0x95, 0x49, 0xA1, 0xB1, 0x94, 0x20, 0x2E, 0x56, 0x5E, + 0x03, 0xE9, 0x45, 0x49, 0x1E, 0xA5, 0x08, 0xEE, 0xFC, 0x6A, 0x4B, 0x98, + 0xC5, 0x11, 0xC0, 0x65, 0xEF, 0x4D, 0x5D, 0xB3, 0xD4, 0xAB, 0x57, 0x7B, + 0x3B, 0x74, 0xD7, 0x16, 0x6E, 0x0F, 0x15, 0xDF, 0xB9, 0x70, 0xCE, 0x17, + 0xD6, 0x18, 0xE4, 0xC4, 0xC9, 0xD3, 0x74, 0x66, 0x8D, 0xCB, 0x5A, 0x63, + 0x38, 0xAC, 0x40, 0x85, 0x10, 0xD2, 0xA1, 0x6D, 0x42, 0xAD, 0xA2, 0xBE, + 0x97, 0x80, 0xE7, 0x7E, 0x3F, 0x88, 0x8F, 0xF4, 0xB2, 0x9B, 0xA7, 0xA6, + 0x2D, 0x46, 0xE2, 0x1A, 0x3E, 0x40, 0xBB, 0xDF, 0x18, 0xC0, 0xED, 0xA7, + 0x6E, 0xF0, 0x6D, 0x85, 0xE0, 0x6E, 0x53, 0xCB, 0xDF, 0x6D, 0xB8, 0xB9, + 0xA9, 0xDB, 0x3B, 0xBC, 0xD4, 0x54, 0x1E, 0xE3, 0x8E, 0xA3, 0xB9, 0x54, + 0x6F, 0xC3, 0x00, 0xEF, 0x39, 0x4C, 0x48, 0xD5, 0x35, 0x27, 0xA0, 0xDC, + 0xB3, 0x86, 0xDE, 0x04, 0x1E, 0x6A, 0xC7, 0xE2, 0x18, 0x7D, 0xA7, 0x89, + 0xFE, 0x1A, 0x15, 0xDF, 0x47, 0xC8, 0x30, 0x77, 0x5C, 0xBB, 0x3A, 0x82, + 0x23, 0x5E, 0xAD, 0x63, 0xBA, 0x5D, 0x56, 0x46, 0x86, 0x29, 0xC9, 0x40, + 0xFE, 0x2B, 0xB2, 0x28, 0x50, 0xF3, 0xE5, 0x59, 0x8A, 0xD6, 0xDB, 0x91, + 0xB6, 0x48, 0x4C, 0xD1, 0x54, 0x69, 0x00, 0x9A, 0xE5, 0x26, 0x92, 0xF5, + 0x73, 0xDF, 0x28, 0x3A, 0xB0, 0x2C, 0xDD, 0x6A, 0xC4, 0xA2, 0xC1, 0x7B, + 0x95, 0x5B, 0x99, 0x16, 0x6D, 0x29, 0xB9, 0x53, 0x76, 0x99, 0xCC, 0xB9, + 0xDB, 0x10, 0xE8, 0x4D, 0x23, 0x3D, 0x4F, 0xEC, 0xF7, 0x18, 0xEC, 0x09, + 0xD7, 0xDA, 0x85, 0x2C, 0x85, 0xE9, 0xAE, 0x25, 0xC3, 0x18, 0xFC, 0x46, + 0x32, 0xDE, 0xCC, 0x15, 0x68, 0x6A, 0x6F, 0xD0, 0x31, 0xCD, 0x5D, 0xD8, + 0xF8, 0x3B, 0x4E, 0x41, 0x9F, 0xC0, 0x5F, 0xBA, 0x11, 0xF0, 0xC1, 0xAF, + 0x68, 0x06, 0x98, 0x15, 0xB7, 0x75, 0x45, 0xCD, 0xE3, 0x8E, 0xBA, 0x1A, + 0x01, 0x43, 0xCE, 0x73, 0x90, 0x3E, 0x90, 0x0B, 0x48, 0x6E, 0xD6, 0x08, + 0x82, 0x0B, 0x51, 0x2C, 0x3A, 0xE2, 0x0C, 0xD6, 0xA2, 0x4D, 0x43, 0xE1, + 0xD9, 0x9F, 0x41, 0x5B, 0x93, 0x6C, 0xE1, 0xAE, 0x55, 0x0B, 0xBC, 0x25, + 0x86, 0x1F, 0xCA, 0x32, 0xDE, 0x1B, 0x16, 0x07, 0x91, 0x50, 0x12, 0x16, + 0xB5, 0x3C, 0x55, 0x1A, 0x73, 0x64, 0x11, 0x5C, 0xE4, 0x5D, 0x27, 0x69, + 0xAF, 0x74, 0x4A, 0xE2, 0xDF, 0x99, 0x0D, 0x10, 0x3B, 0x5C, 0x89, 0x31, + 0x9B, 0x7F, 0xA2, 0xB0, 0x38, 0x87, 0x69, 0xD0, 0x89, 0x30, 0x92, 0x3A, + 0x84, 0xC0, 0x56, 0x4C, 0xCD, 0x4C, 0x49, 0x06, 0x5E, 0x72, 0x1E, 0xC6, + 0x01, 0xCF, 0xCD, 0x43, 0xB9, 0x4B, 0x38, 0x42, 0xB6, 0x9D, 0x20, 0x9C, + 0x15, 0x88, 0x7C, 0xEA, 0x8A, 0xE3, 0x2A, 0xDC, 0x09, 0x20, 0x16, 0x63, + 0x58, 0x02, 0x7D, 0x02, 0x72, 0x97, 0x2D, 0x46, 0x59, 0xEB, 0xE9, 0x33, + 0x59, 0xA9, 0xCF, 0x7C, 0x73, 0xCD, 0xC6, 0xB3, 0x29, 0xD6, 0xB4, 0xD1, + 0x19, 0xF7, 0xF6, 0x4D, 0x8E, 0xDB, 0xD9, 0xFA, 0xDC, 0x7C, 0x7F, 0x26, + 0x9E, 0xFA, 0x53, 0xD0, 0xA3, 0x3B, 0xB6, 0x6F, 0xEC, 0x03, 0x40, 0x12, + 0xE3, 0xCE, 0x94, 0x6F, 0x38, 0xEC, 0x42, 0x7B, 0xC5, 0x71, 0x09, 0x33, + 0xC5, 0xF1, 0x66, 0xA4, 0x8D, 0xAB, 0xF5, 0xE5, 0x32, 0xDE, 0x64, 0x3B, + 0xFF, 0xA2, 0x8C, 0x58, 0xD0, 0x29, 0xF3, 0x33, 0xFF, 0x7D, 0xEF, 0x37, + 0x9B, 0xF8, 0xF8, 0x0D, 0xCC, 0xE5, 0x0D, 0x3B, 0x15, 0xAD, 0x25, 0xE7, + 0x14, 0x2A, 0xD8, 0xD8, 0x67, 0xD3, 0x91, 0xBE, 0xEB, 0x5F, 0x74, 0x4A, + 0x83, 0x60, 0x30, 0x61, 0x61, 0x20, 0x6A, 0x1E, 0xF7, 0xDA, 0x31, 0x4C, + 0xA9, 0xED, 0x2B, 0x1A, 0xFD, 0x7E, 0x03, 0xDB, 0xFE, 0x39, 0xED, 0x51, + 0x58, 0x4C, 0x3D, 0xCF, 0xFB, 0xFA, 0xAC, 0x76, 0xF2, 0xCA, 0x73, 0x4F, + 0x2A, 0x0B, 0x7C, 0xB0, 0xF0, 0xD1, 0xB2, 0x0D, 0xA6, 0xCE, 0x33, 0xEC, + 0x71, 0x1E, 0xF3, 0x31, 0xAF, 0x02, 0x32, 0xA8, 0x5B, 0x38, 0x47, 0x29, + 0xE9, 0x0E, 0xB2, 0x2B, 0x8A, 0xC4, 0xA4, 0x0B, 0x6F, 0xFB, 0xFC, 0x6C, + 0x6F, 0x5C, 0xE6, 0xB8, 0x8B, 0x5C, 0xF3, 0x23, 0x76, 0xB4, 0x99, 0xCF, + 0xE7, 0xA4, 0x0F, 0xBA, 0x88, 0x73, 0x18, 0xA5, 0xE7, 0xDF, 0x7D, 0x7C, + 0x9B, 0x5E, 0x1E, 0xCD, 0xFA, 0x61, 0x8D, 0x4C, 0x1D, 0xF6, 0xF1, 0xFD, + 0x59, 0xF5, 0xB8, 0xE4, 0x1B, 0x36, 0x2D, 0x7A, 0xD5, 0xC3, 0x7E, 0xD0, + 0x53, 0x76, 0x14, 0x0F, 0x0B, 0xE4, 0x43, 0x53, 0x4D, 0x3E, 0x2C, 0xD4, + 0x62, 0xF2, 0x8B, 0x38, 0x40, 0xF6, 0x81, 0xB8, 0x54, 0x93, 0x4A, 0x6B, + 0x1C, 0xA4, 0x82, 0xEF, 0xA3, 0xD1, 0x04, 0x4E, 0xDF, 0xB8, 0x1B, 0x01, + 0xB1, 0xFF, 0xFA, 0x55, 0xFB, 0x01, 0xD5, 0xC4, 0x34, 0x4A, 0xE3, 0xE7, + 0xE2, 0x25, 0xBD, 0x78, 0xA5, 0x84, 0x1A, 0x0E, 0xF5, 0xE3, 0x2B, 0xAA, + 0x70, 0x17, 0x14, 0x91, 0x1C, 0xE4, 0x92, 0xD5, 0xAF, 0x38, 0x13, 0xB8, + 0x85, 0xF5, 0x31, 0x59, 0xE9, 0x3B, 0x2C, 0xD8, 0xE9, 0x13, 0x53, 0x02, + 0xB5, 0x87, 0x4D, 0xE6, 0x8A, 0x35, 0x8D, 0xE3, 0xD0, 0x70, 0x0F, 0xA1, + 0xD9, 0x03, 0x60, 0x3E, 0x1A, 0x81, 0x4A, 0xFB, 0xDA, 0x32, 0x77, 0x3A, + 0xFF, 0x30, 0x11, 0xF4, 0x86, 0xBC, 0xBB, 0x85, 0x65, 0xA5, 0xB7, 0x15, + 0x35, 0x79, 0x8E, 0xF3, 0x41, 0x2F, 0xB9, 0xB6, 0xFF, 0x9C, 0x99, 0x86, + 0x02, 0x00, 0xA4, 0x64, 0x97, 0x52, 0x83, 0xC1, 0x4B, 0xDE, 0xD8, 0x6C, + 0xAC, 0x21, 0xF9, 0x47, 0xA5, 0xAB, 0xD5, 0xD3, 0x70, 0xBC, 0x49, 0xC6, + 0x4B, 0x5A, 0x01, 0x01, 0x71, 0xFD, 0x89, 0x8B, 0xDF, 0x6B, 0xB4, 0xEA, + 0x27, 0xDC, 0x2E, 0xEA, 0x47, 0x77, 0xCB, 0x73, 0x55, 0x93, 0x36, 0xA1, + 0xE5, 0xCE, 0xED, 0x92, 0x92, 0x29, 0x6A, 0x7B, 0x12, 0x62, 0x0B, 0xC1, + 0x48, 0x17, 0x14, 0x27, 0xCB, 0x07, 0x9C, 0x93, 0x2D, 0xC8, 0xE5, 0x03, + 0x5F, 0x55, 0x82, 0x04, 0x36, 0x06, 0x30, 0x66, 0xE8, 0xC9, 0x9A, 0xD5, + 0xBE, 0xD5, 0x1A, 0xF1, 0x86, 0x9E, 0xFD, 0x36, 0xE7, 0xC3, 0x87, 0x92, + 0x79, 0xD5, 0xF8, 0x3A, 0xAC, 0x5B, 0x98, 0x63, 0xDE, 0x87, 0x6C, 0xC2, + 0x84, 0x0E, 0xC0, 0x4D, 0x15, 0x8C, 0xC8, 0x09, 0xE9, 0xED, 0x24, 0x77, + 0x6D, 0x10, 0xEF, 0x2A, 0x63, 0x58, 0xD4, 0x91, 0x01, 0xA1, 0x0D, 0x91, + 0xC3, 0x60, 0x44, 0x08, 0x69, 0x07, 0x78, 0xAA, 0x0F, 0x50, 0x06, 0x0A, + 0xAF, 0xC1, 0xB8, 0xD4, 0xDE, 0x6C, 0x0D, 0x94, 0x47, 0x04, 0xB2, 0xC3, + 0x6A, 0x03, 0x2C, 0x75, 0x19, 0xDA, 0x31, 0x8B, 0x57, 0xD7, 0xA7, 0x53, + 0x7A, 0x2B, 0xCF, 0xD8, 0xC7, 0x14, 0xDF, 0xB6, 0xE2, 0x82, 0x3E, 0xF4, + 0x4A, 0x01, 0xCB, 0xD0, 0xF4, 0xE0, 0x91, 0x83, 0x57, 0x54, 0xB3, 0x77, + 0x5D, 0x74, 0xCC, 0x01, 0xCB, 0x2B, 0x43, 0x72, 0x3D, 0xAE, 0x63, 0x18, + 0x26, 0x02, 0x0F, 0xE5, 0x7E, 0xB1, 0xAA, 0x94, 0x8D, 0xE7, 0x3A, 0xC2, + 0x79, 0x19, 0xB2, 0xF1, 0xBC, 0x17, 0x58, 0xEB, 0x82, 0xC3, 0xD1, 0x00, + 0xF0, 0x01, 0xDC, 0xF6, 0x82, 0x04, 0xD1, 0xEC, 0xCE, 0xD3, 0x44, 0x6E, + 0x49, 0x70, 0xF0, 0x74, 0x5C, 0xA7, 0x95, 0xDF, 0xBB, 0x97, 0x7A, 0x76, + 0x56, 0xBC, 0x06, 0x39, 0xEB, 0x4E, 0xAC, 0xBB, 0x56, 0x57, 0xF6, 0x3F, + 0x47, 0xE6, 0xDC, 0x53, 0x59, 0x18, 0xBC, 0xA7, 0x0B, 0x27, 0x39, 0x59, + 0xFC, 0xDB, 0x97, 0xA5, 0x47, 0xCB, 0x7C, 0x7B, 0x51, 0xAF, 0xAE, 0x24, + 0xA1, 0xF0, 0x64, 0xDB, 0x0B, 0xDE, 0xBF, 0xC6, 0x99, 0x6D, 0x82, 0xC1, + 0xD2, 0xAC, 0x83, 0x10, 0xF8, 0x98, 0xE7, 0x31, 0x62, 0x80, 0x2A, 0x83, + 0x9D, 0x97, 0xA2, 0xF6, 0xF2, 0xF4, 0x03, 0xCB, 0x04, 0x02, 0x5C, 0xE5, + 0xAC, 0x94, 0x4C, 0x45, 0x6D, 0x16, 0x20, 0x2E, 0x95, 0xA8, 0x29, 0x65, + 0x13, 0x37, 0x17, 0xC9, 0xCB, 0x85, 0xE1, 0xA1, 0x00, 0x5D, 0x08, 0x36, + 0xE2, 0x1A, 0x3D, 0xD0, 0xD9, 0xB1, 0x3E, 0x90, 0xD8, 0x69, 0xD0, 0x2E, + 0xDD, 0x90, 0x14, 0x53, 0xC7, 0x78, 0x3A, 0xFD, 0x13, 0xB9, 0xA2, 0x11, + 0xA0, 0x0C, 0xFC, 0x29, 0x30, 0x1E, 0x97, 0x8F, 0xAB, 0x35, 0x6B, 0x69, + 0x87, 0x8B, 0x9F, 0xB9, 0x38, 0xD6, 0x31, 0x89, 0xEF, 0xE0, 0x36, 0xAC, + 0x17, 0x97, 0x22, 0xD5, 0xDC, 0x3B, 0x0C, 0x00, 0xFB, 0x63, 0xBA, 0x07, + 0x59, 0x9A, 0x2E, 0xE0, 0x52, 0x13, 0x4C, 0xCD, 0x10, 0x2C, 0x4E, 0x8D, + 0x14, 0x0F, 0x8B, 0x8F, 0x22, 0x4D, 0xAC, 0x24, 0x01, 0xA4, 0xE7, 0x01, + 0x35, 0x93, 0xDE, 0x07, 0x2A, 0xDF, 0x13, 0x59, 0x23, 0xEF, 0x51, 0xD9, + 0xF3, 0xF7, 0x41, 0x43, 0x7B, 0x10, 0xA5, 0xA0, 0x94, 0xE5, 0x36, 0x48, + 0xF1, 0x65, 0xB3, 0xAB, 0xE6, 0x01, 0x5C, 0x3A, 0x90, 0x65, 0xF7, 0xF3, + 0xEE, 0x8B, 0x89, 0xB9, 0xBB, 0x40, 0xBE, 0xEA, 0x9E, 0xB8, 0xFD, 0xEF, + 0xA1, 0xBF, 0x75, 0x55, 0x54, 0xB7, 0xB6, 0x58, 0x2D, 0xDA, 0x95, 0xA5, + 0xD8, 0xF7, 0x96, 0x53, 0x0B, 0xD9, 0xAF, 0xC0, 0x51, 0x9B, 0x51, 0x9D, + 0x57, 0x9A, 0xD0, 0x98, 0x4F, 0xDE, 0x07, 0x24, 0x79, 0x07, 0x14, 0xB4, + 0xE6, 0xC2, 0x1E, 0x59, 0x85, 0xE2, 0x33, 0xAB, 0xD7, 0x62, 0x46, 0xB4, + 0x76, 0x06, 0x6C, 0xC3, 0xB6, 0xC4, 0x7C, 0x26, 0x77, 0x46, 0x25, 0x17, + 0xC1, 0x22, 0x55, 0x68, 0x30, 0xB7, 0xC6, 0xBF, 0x24, 0x86, 0xE7, 0x3D, + 0xF8, 0x00, 0x60, 0xA6, 0x95, 0xA9, 0x89, 0xA1, 0xB8, 0xF4, 0x94, 0x9C, + 0x4B, 0xF7, 0x1D, 0x2C, 0x4C, 0x30, 0xE4, 0xD3, 0x36, 0x38, 0xBB, 0xB2, + 0x6E, 0xE9, 0x79, 0x03, 0x62, 0x3D, 0xB3, 0xBF, 0x76, 0xE8, 0x2E, 0x5C, + 0xC0, 0x70, 0xE8, 0xEA, 0x00, 0xC6, 0xCD, 0x8C, 0x34, 0xA6, 0x39, 0x16, + 0x1F, 0x85, 0xC9, 0x88, 0x81, 0x1F, 0x1F, 0x3D, 0x2C, 0x35, 0xC4, 0x62, + 0x64, 0x26, 0xF1, 0xDF, 0x45, 0x49, 0x1A, 0xBE, 0x68, 0x62, 0x83, 0xA1, + 0x34, 0x70, 0x68, 0x4E, 0x73, 0x6A, 0x0C, 0x0E, 0xDA, 0x8D, 0x61, 0x13, + 0x7C, 0x62, 0x8C, 0x5C, 0xC6, 0xFD, 0x47, 0x63, 0x54, 0x4A, 0xB8, 0x4F, + 0x33, 0xDC, 0xDC, 0x99, 0xE5, 0x0A, 0xFF, 0x0E, 0x42, 0x51, 0xC2, 0xC4, + 0xDD, 0x28, 0xEE, 0x2D, 0xC7, 0xFE, 0x5C, 0xA2, 0xDB, 0xB2, 0xCE, 0x10, + 0x66, 0x90, 0x23, 0x22, 0x89, 0xA4, 0x74, 0x00, 0x1A, 0x1F, 0x27, 0x8F, + 0xA8, 0x36, 0xD7, 0x05, 0x76, 0x51, 0xD9, 0xBC, 0xB0, 0x35, 0x3D, 0xD1, + 0x0D, 0xC8, 0xB1, 0x17, 0x96, 0x51, 0xDF, 0x7A, 0x68, 0x26, 0x04, 0x03, + 0xDD, 0x78, 0xCD, 0x13, 0x7C, 0x0D, 0xD8, 0x64, 0xB5, 0xD9, 0x9F, 0xC0, + 0xC0, 0xBE, 0x12, 0xD6, 0x12, 0xE9, 0xB7, 0x65, 0xA3, 0xB2, 0x9B, 0x14, + 0xFA, 0x77, 0x4B, 0x9D, 0xDD, 0xE9, 0x43, 0x8E, 0x8C, 0xBE, 0xAD, 0x32, + 0x03, 0x24, 0xC4, 0x33, 0x16, 0x12, 0x3C, 0xDD, 0x78, 0x63, 0xDD, 0x6E, + 0x2B, 0x32, 0x4D, 0x22, 0x19, 0x04, 0xF2, 0xD6, 0xC1, 0x46, 0x59, 0xF0, + 0x46, 0x7A, 0x38, 0x17, 0xD0, 0xE3, 0x40, 0x2E, 0xC7, 0x8A, 0xC6, 0x7D, + 0xDA, 0x4A, 0x41, 0x26, 0xC5, 0x1A, 0xF6, 0xC3, 0xC7, 0x9C, 0x23, 0xD7, + 0x64, 0x39, 0xC1, 0x50, 0x68, 0xB2, 0xC2, 0xF8, 0x27, 0xEA, 0x22, 0xFC, + 0x12, 0xA2, 0x27, 0x5F, 0x9B, 0xDE, 0x5C, 0x99, 0x8D, 0x72, 0x9C, 0x09, + 0x19, 0xEF, 0x7F, 0xDB, 0x35, 0xB6, 0x84, 0xCD, 0x2F, 0x80, 0xA2, 0x24, + 0xAF, 0x1A, 0x71, 0x0D, 0x09, 0xE0, 0xBC, 0x6C, 0x1D, 0x30, 0x22, 0xF6, + 0x50, 0xA4, 0x65, 0x3E, 0xB2, 0x6A, 0xCC, 0x45, 0xB3, 0x06, 0x4A, 0x67, + 0xA5, 0x6F, 0x21, 0x1E, 0x9B, 0x6C, 0x4D, 0x08, 0x32, 0x8D, 0x17, 0xED, + 0x78, 0x04, 0x98, 0x17, 0x6C, 0xB3, 0x51, 0x18, 0xDF, 0xF0, 0x79, 0xDA, + 0x3F, 0x13, 0xDF, 0x32, 0x38, 0x85, 0x98, 0x64, 0xBD, 0x5A, 0x37, 0x39, + 0x58, 0x56, 0x83, 0x3B, 0x84, 0xB8, 0x71, 0x42, 0x7F, 0xBC, 0xCB, 0x82, + 0xA9, 0xDB, 0x0E, 0x8A, 0xB4, 0x05, 0x24, 0x4B, 0xC4, 0x0F, 0x09, 0xC3, + 0x35, 0x0A, 0x96, 0x6B, 0x46, 0xF0, 0x0F, 0x18, 0xC3, 0xFB, 0x5C, 0xC6, + 0x22, 0x2A, 0xC1, 0x2C, 0xE6, 0x2E, 0xDB, 0xC5, 0x8A, 0x8F, 0x65, 0xD5, + 0x63, 0x97, 0x8F, 0x5B, 0x77, 0xED, 0x96, 0x78, 0x83, 0x0C, 0x0A, 0xCE, + 0xD1, 0xF2, 0x4C, 0xA4, 0xA4, 0xD0, 0x09, 0x33, 0x66, 0xE9, 0xD2, 0x98, + 0x59, 0x66, 0xBA, 0x76, 0x72, 0x3E, 0x89, 0xCB, 0xA4, 0x4A, 0xE6, 0x84, + 0x4D, 0x1A, 0x20, 0x8E, 0xDE, 0x0B, 0x04, 0x85, 0x33, 0x2C, 0xBE, 0xF4, + 0xDB, 0x3B, 0x2B, 0x64, 0xD9, 0x76, 0xD4, 0xF4, 0x6F, 0x0F, 0xF3, 0x83, + 0x29, 0x3F, 0xA5, 0xCC, 0x8C, 0xE7, 0x93, 0x46, 0x0E, 0xFD, 0x1F, 0xA6, + 0xD5, 0x04, 0xD1, 0x85, 0x17, 0xB2, 0xD5, 0xBE, 0x3D, 0xC0, 0x33, 0xC0, + 0x52, 0x8A, 0x5C, 0xF6, 0x73, 0xBD, 0x85, 0xE0, 0x02, 0xA7, 0x47, 0x4E, + 0x60, 0x2F, 0x76, 0x94, 0x02, 0x01, 0xC7, 0x27, 0x81, 0x0B, 0xEB, 0xEA, + 0x49, 0x29, 0xFA, 0x02, 0x73, 0x6E, 0xB1, 0xEC, 0x82, 0x29, 0x29, 0x6F, + 0x3E, 0xF2, 0xB2, 0x1F, 0xD5, 0x66, 0x54, 0x00, 0x52, 0xE1, 0x1C, 0xDA, + 0xF0, 0xCF, 0x90, 0x83, 0x6B, 0xFF, 0xED, 0x3D, 0x69, 0x91, 0xA4, 0x47, + 0x82, 0x88, 0x09, 0x5A, 0x63, 0x75, 0x44, 0x88, 0xF4, 0x51, 0x85, 0xE0, + 0x6F, 0x85, 0x2A, 0x48, 0x14, 0x3B, 0x09, 0x8F, 0x80, 0xE6, 0xF8, 0x09, + 0x5C, 0xC6, 0x7B, 0x45, 0x85, 0x4F, 0x57, 0x9E, 0x58, 0x9A, 0x35, 0x5F, + 0x94, 0x70, 0x3B, 0x11, 0x49, 0x1D, 0xB8, 0x46, 0xB7, 0x79, 0x8E, 0x2C, + 0x48, 0xDB, 0x3C, 0x34, 0x4E, 0xD2, 0x17, 0x19, 0xA1, 0xDA, 0x48, 0x40, + 0x08, 0x6E, 0xCE, 0x66, 0xC3, 0x70, 0xB0, 0x01, 0x21, 0xD5, 0x97, 0x9B, + 0xAB, 0x65, 0x96, 0x05, 0x75, 0x97, 0xFB, 0x2E, 0x4B, 0x0C, 0x31, 0xFD, + 0x8E, 0x67, 0x76, 0xF4, 0x5B, 0x06, 0xF2, 0xB3, 0xA6, 0xDF, 0x56, 0xF6, + 0x5D, 0xA5, 0xDF, 0xBE, 0x01, 0x6A, 0x71, 0xC6, 0x1D, 0xFE, 0xBB, 0x08, + 0xA8, 0x06, 0x59, 0x4F, 0x6D, 0x92, 0x13, 0xD3, 0xCA, 0xCA, 0xFF, 0xBA, + 0x3A, 0xF2, 0xEA, 0xFD, 0xEE, 0x37, 0xA8, 0xB5, 0x4C, 0x42, 0xBA, 0x48, + 0x4A, 0x98, 0xA6, 0x92, 0x75, 0xB7, 0xE6, 0x77, 0x2A, 0xA9, 0x6A, 0xE3, + 0x6B, 0xC4, 0x72, 0x98, 0xE4, 0xA1, 0x6F, 0x44, 0x0E, 0x42, 0x2C, 0xAD, + 0x80, 0xE8, 0xA7, 0x5B, 0x82, 0xA4, 0x2D, 0x7A, 0x09, 0xFD, 0xB8, 0xBE, + 0x9E, 0x64, 0xE7, 0x18, 0x21, 0xBB, 0x7D, 0x58, 0xE7, 0xC7, 0x02, 0x8A, + 0x54, 0xCA, 0xCA, 0xEF, 0xBC, 0xD0, 0x0D, 0xFE, 0xCB, 0x79, 0x06, 0x38, + 0xEE, 0xD2, 0xB8, 0xF9, 0x40, 0x08, 0x6C, 0x80, 0xCE, 0xDC, 0xDA, 0xA0, + 0x2E, 0x22, 0x4B, 0x10, 0xCC, 0xEE, 0x01, 0xDA, 0x9F, 0xD7, 0x31, 0x84, + 0x26, 0xDD, 0xE0, 0x92, 0x60, 0xA5, 0xB9, 0xA7, 0x23, 0xF7, 0xF2, 0x07, + 0x06, 0x07, 0x80, 0x9F, 0x20, 0x5A, 0x1C, 0x68, 0x81, 0xE8, 0x27, 0xC4, + 0xF0, 0x54, 0x03, 0x9B, 0x1A, 0xAB, 0xD6, 0x43, 0x6B, 0xDB, 0x23, 0x10, + 0x79, 0x94, 0x3F, 0xB6, 0xDF, 0xAA, 0x43, 0x53, 0xF0, 0x16, 0x8F, 0x31, + 0xDA, 0xB7, 0xB3, 0xC5, 0x1E, 0xE5, 0x7B, 0x1F, 0x12, 0x38, 0x4D, 0x6A, + 0x41, 0x06, 0x0E, 0x32, 0x1C, 0x12, 0x61, 0x91, 0x2C, 0x95, 0x87, 0xE2, + 0x04, 0x2E, 0x34, 0x7C, 0x81, 0x43, 0x82, 0x2D, 0xC6, 0xA1, 0x15, 0x83, + 0xEB, 0xC7, 0x29, 0x14, 0x8F, 0x03, 0xDB, 0xC8, 0x3A, 0x79, 0x38, 0x99, + 0x06, 0xF2, 0x4C, 0x8A, 0xF2, 0x1D, 0x51, 0x5A, 0x06, 0xE7, 0xDB, 0x6C, + 0x86, 0x54, 0xFE, 0x59, 0x97, 0x6A, 0x59, 0x54, 0xD4, 0x6D, 0xE5, 0xE8, + 0xCD, 0x25, 0xE2, 0xA5, 0xF8, 0x1D, 0x58, 0xC9, 0x97, 0xA1, 0x08, 0x2E, + 0xE7, 0xEE, 0x80, 0x1B, 0xE2, 0x75, 0x42, 0x6C, 0x6D, 0xEF, 0x4C, 0xA3, + 0x79, 0xB1, 0xB8, 0x41, 0x76, 0x0D, 0x62, 0xD7, 0x18, 0x00, 0xE0, 0x9B, + 0x42, 0xA7, 0x48, 0x43, 0x39, 0x84, 0x54, 0xB0, 0xCB, 0x43, 0x03, 0xB5, + 0xCB, 0x38, 0x87, 0x7C, 0xD9, 0xB2, 0x87, 0x4F, 0x46, 0xB4, 0x3F, 0x5F, + 0x32, 0xC1, 0xFA, 0x8D, 0x1C, 0x9C, 0x9D, 0xE1, 0x71, 0xF4, 0xE0, 0x90, + 0xE4, 0x44, 0x87, 0xA1, 0x38, 0x8E, 0x80, 0xA0, 0xED, 0xCC, 0x21, 0xC0, + 0x0F, 0x27, 0xE4, 0x60, 0xDA, 0xE7, 0xC4, 0x94, 0x85, 0x50, 0x5B, 0x48, + 0x9A, 0x03, 0x4B, 0x4E, 0x67, 0x2B, 0x09, 0xAE, 0xA2, 0xF7, 0xB8, 0x91, + 0xB0, 0x3A, 0x72, 0x98, 0xEC, 0xDD, 0x9D, 0x87, 0xA2, 0xA4, 0x43, 0x57, + 0xE4, 0xEE, 0xFB, 0xA2, 0x1E, 0x03, 0x38, 0x65, 0x0E, 0x66, 0xBC, 0x81, + 0xC8, 0x9D, 0x6F, 0x70, 0x30, 0xDF, 0xF5, 0x31, 0x95, 0x26, 0x85, 0xDB, + 0x64, 0x08, 0x5A, 0x1F, 0xCF, 0xF5, 0x4E, 0x1F, 0xD7, 0x6B, 0xCA, 0x76, + 0xCC, 0xEE, 0xF9, 0x8C, 0x6E, 0xB7, 0x40, 0xCA, 0xF2, 0xD6, 0x63, 0xBF, + 0x43, 0x88, 0x92, 0x99, 0x6B, 0xCB, 0x11, 0xCC, 0x8D, 0x63, 0xE3, 0x4D, + 0x30, 0x0C, 0x05, 0x2C, 0xAA, 0x0F, 0x92, 0x7E, 0xA5, 0xF1, 0xB4, 0x21, + 0x87, 0xE7, 0x9D, 0x41, 0x9F, 0xFD, 0x88, 0x7B, 0x8C, 0xFA, 0xD2, 0xCC, + 0xAE, 0xDD, 0x97, 0x38, 0xAD, 0x4C, 0xE7, 0x9C, 0x3D, 0xE4, 0x64, 0x40, + 0xF2, 0xD0, 0x0E, 0x48, 0xCB, 0xD7, 0x0E, 0xFD, 0x32, 0x54, 0x42, 0x2D, + 0x76, 0x87, 0xA2, 0xEC, 0x62, 0x7C, 0xB7, 0x0E, 0x4F, 0x39, 0x25, 0x9C, + 0x0F, 0x47, 0xCF, 0x22, 0x84, 0x35, 0x18, 0x5A, 0x6C, 0x85, 0x48, 0x15, + 0xD0, 0x7C, 0x7D, 0x56, 0xB0, 0xC9, 0x42, 0x94, 0x11, 0xDB, 0xB8, 0x59, + 0x36, 0xE6, 0x06, 0x38, 0x71, 0xF0, 0xE6, 0x62, 0x60, 0x11, 0xB4, 0xB3, + 0xEA, 0xB9, 0x62, 0x12, 0x6F, 0x8D, 0xAC, 0xD8, 0x6B, 0xAD, 0x9E, 0x65, + 0xA5, 0x4A, 0x33, 0x25, 0xFA, 0x33, 0xDD, 0x0D, 0x9A, 0x80, 0xCA, 0xBB, + 0x78, 0x54, 0xCF, 0xDA, 0x6C, 0xAF, 0xBC, 0x07, 0x43, 0x12, 0xCE, 0x60, + 0x56, 0xB0, 0x64, 0x52, 0x37, 0xD3, 0x39, 0xF7, 0xB5, 0x24, 0x66, 0x94, + 0x05, 0x4F, 0xEA, 0xA2, 0x95, 0x0C, 0x97, 0x15, 0xD0, 0x00, 0x80, 0x87, + 0xBE, 0x27, 0xAA, 0x2C, 0x80, 0x77, 0xDC, 0xFA, 0xBA, 0xDB, 0xA1, 0x1C, + 0xFB, 0x2B, 0xB4, 0xFA, 0x61, 0x53, 0xD5, 0xD3, 0xA7, 0x9D, 0x99, 0xAC, + 0xC9, 0xBE, 0x27, 0x0A, 0x9C, 0xB0, 0xF2, 0x1E, 0x96, 0xF3, 0xCD, 0xA4, + 0xE9, 0x36, 0x30, 0xB9, 0xF3, 0xE3, 0x7E, 0x80, 0x62, 0x20, 0xFF, 0xA5, + 0xC9, 0x26, 0x40, 0xB5, 0x9E, 0x04, 0x96, 0x4C, 0xDD, 0x98, 0xA1, 0x22, + 0x53, 0x7E, 0x5D, 0x32, 0x4D, 0x34, 0x57, 0xDD, 0xE1, 0x44, 0xF6, 0x8C, + 0x07, 0x04, 0x86, 0x75, 0xB2, 0x7E, 0x09, 0x35, 0x06, 0x50, 0x84, 0x72, + 0xE9, 0xA2, 0x56, 0x79, 0xBC, 0x11, 0x3E, 0x7D, 0x5D, 0x02, 0x18, 0x27, + 0x4F, 0x41, 0x58, 0x02, 0x21, 0x52, 0x22, 0x8D, 0x0E, 0xF1, 0xB6, 0x4F, + 0x82, 0xE6, 0x60, 0x0E, 0x04, 0xD1, 0x53, 0xF5, 0x1A, 0xF0, 0xA4, 0x5B, + 0xF5, 0x04, 0x99, 0xE4, 0x19, 0xA2, 0xE7, 0x8C, 0x98, 0x75, 0x0A, 0x8E, + 0x02, 0x70, 0x52, 0x33, 0x18, 0x9B, 0x55, 0x20, 0x0B, 0xD0, 0x49, 0x44, + 0x23, 0x89, 0x42, 0x45, 0x26, 0xA7, 0x2C, 0x5B, 0x6B, 0xEF, 0xD9, 0x6F, + 0xE2, 0x5A, 0x59, 0x78, 0xB3, 0xAF, 0x61, 0x79, 0x94, 0x57, 0xD7, 0x05, + 0x27, 0xB5, 0xD4, 0x95, 0xAE, 0x8F, 0xA0, 0x4C, 0xFA, 0xC1, 0x52, 0x6B, + 0xDC, 0xA0, 0x1D, 0xBD, 0x04, 0x06, 0x9D, 0xAF, 0xDC, 0xB1, 0x29, 0x2A, + 0x18, 0xE1, 0x15, 0x11, 0xC0, 0xEF, 0xA6, 0x94, 0xB6, 0x35, 0xDC, 0x8E, + 0xA6, 0x9B, 0x45, 0x20, 0xCF, 0x1C, 0x7A, 0xDE, 0x78, 0xF4, 0x9E, 0xB5, + 0x59, 0x3D, 0xFE, 0x91, 0xBA, 0xA9, 0x5A, 0x1C, 0x53, 0xBD, 0x05, 0xCA, + 0x9A, 0x1B, 0x53, 0x7B, 0x41, 0x99, 0x63, 0xD6, 0xD2, 0x0F, 0x81, 0x03, + 0xA6, 0xA6, 0x1E, 0x49, 0xF7, 0x88, 0x89, 0x4E, 0x83, 0xC8, 0x60, 0xE2, + 0x61, 0x56, 0xC2, 0x7A, 0xC3, 0xA6, 0xA9, 0x36, 0x95, 0x4F, 0x4F, 0x5B, + 0x08, 0x82, 0x0B, 0xDF, 0xA6, 0x14, 0x7B, 0x4A, 0xDA, 0x42, 0xB4, 0x9B, + 0x42, 0x77, 0xEF, 0xB6, 0x10, 0xB4, 0x00, 0x56, 0x3E, 0x63, 0xD6, 0xBA, + 0x06, 0xE4, 0x95, 0x60, 0xAB, 0xFF, 0x49, 0x8F, 0xFC, 0x12, 0xB2, 0x90, + 0x20, 0xA8, 0x26, 0x39, 0xBE, 0xA1, 0x1C, 0xB7, 0xCB, 0xD4, 0xEE, 0xA6, + 0x6C, 0xDB, 0x1A, 0x62, 0x0B, 0x65, 0x3D, 0xC0, 0xA4, 0xBD, 0x50, 0xA8, + 0x52, 0xEB, 0xB5, 0x4F, 0x16, 0xD6, 0x9F, 0x72, 0xBF, 0xA8, 0x5C, 0xC9, + 0xCF, 0xAA, 0x54, 0x8D, 0xC4, 0x84, 0x8C, 0x16, 0x2F, 0x8E, 0x6A, 0xC6, + 0x61, 0xE1, 0x2D, 0x08, 0x4C, 0xB4, 0x71, 0x6F, 0x97, 0xD4, 0xE9, 0xFD, + 0xA2, 0x89, 0xBE, 0xEC, 0xCE, 0x48, 0xD1, 0x16, 0xFD, 0xFA, 0x82, 0xA5, + 0x8D, 0xBA, 0xC0, 0xB4, 0x97, 0xB6, 0x80, 0xA6, 0x81, 0x01, 0x70, 0x2D, + 0xA1, 0xE1, 0x90, 0xA0, 0xDE, 0x3B, 0xA4, 0xA7, 0xCD, 0xAC, 0xF9, 0xAA, + 0x47, 0xE2, 0x84, 0x60, 0x37, 0x51, 0x9C, 0x19, 0xBE, 0x6F, 0x0C, 0x88, + 0x42, 0xC5, 0x1F, 0xA3, 0x68, 0x57, 0xB9, 0x7B, 0x5C, 0x10, 0x94, 0xA2, + 0x89, 0xF0, 0x31, 0xB9, 0x72, 0x52, 0x61, 0x62, 0x8E, 0x7D, 0xC5, 0xEF, + 0xB4, 0xA0, 0x72, 0xA5, 0x18, 0xA1, 0x1F, 0xA5, 0x0A, 0x8C, 0x0B, 0x7A, + 0x5B, 0xDA, 0xB2, 0x72, 0xE9, 0xB9, 0x5E, 0x5C, 0xA3, 0xF8, 0x4C, 0xD1, + 0x5D, 0xC9, 0x13, 0xC5, 0x8C, 0xB7, 0x2F, 0x43, 0x0F, 0x5B, 0x7B, 0xE5, + 0x56, 0x9A, 0xAF, 0x79, 0xFA, 0x00, 0x12, 0x3D, 0x21, 0x6F, 0xC7, 0x54, + 0xD5, 0x4C, 0x30, 0x94, 0xF7, 0x4C, 0x5C, 0xB8, 0xB8, 0x11, 0xF7, 0xBD, + 0x0E, 0xC3, 0x2D, 0x8A, 0x2A, 0xD6, 0xC5, 0x7C, 0xDE, 0x9E, 0x26, 0x86, + 0x4D, 0x50, 0x78, 0x66, 0xA8, 0xAF, 0x4C, 0xBB, 0x8A, 0x45, 0x5E, 0x90, + 0xFA, 0x42, 0xB6, 0xAD, 0x54, 0x36, 0x80, 0xD8, 0xBE, 0x06, 0xE3, 0x7E, + 0x3E, 0xC5, 0x10, 0xFE, 0xEC, 0x26, 0xFC, 0x0E, 0x75, 0x39, 0x6B, 0x16, + 0xE7, 0x52, 0xAD, 0xEB, 0x62, 0x35, 0x25, 0xEB, 0x26, 0x3C, 0x1F, 0xFE, + 0xD2, 0xFE, 0x7C, 0x0E, 0x39, 0x01, 0x6B, 0x25, 0x5D, 0xBA, 0xF5, 0xB2, + 0xDB, 0x73, 0x6A, 0x67, 0x8E, 0xEC, 0x9E, 0x09, 0xA5, 0xD1, 0xD2, 0xB6, + 0xA7, 0xC2, 0x34, 0x62, 0x2B, 0x1E, 0x17, 0x07, 0x65, 0x80, 0xCB, 0x60, + 0xA9, 0x8D, 0x8C, 0xBD, 0x0B, 0x58, 0x1B, 0x66, 0xF1, 0x16, 0xD5, 0x9A, + 0xF0, 0xAA, 0x54, 0xCE, 0xF6, 0x84, 0x8F, 0x45, 0xC6, 0xA3, 0xF6, 0x1C, + 0x16, 0xFD, 0xF4, 0xBE, 0x0E, 0x15, 0xF1, 0x7C, 0xFC, 0x90, 0xDE, 0x47, + 0xA7, 0xD0, 0xB3, 0x00, 0x59, 0x88, 0xCB, 0xBD, 0x65, 0x1B, 0xF2, 0x12, + 0xB4, 0x92, 0xDF, 0xC0, 0xA1, 0xCE, 0xE3, 0xB2, 0x92, 0x41, 0xE6, 0xFB, + 0x18, 0x2F, 0x60, 0x9B, 0xCD, 0x48, 0xBA, 0x65, 0x07, 0xFE, 0x3F, 0xB6, + 0x14, 0x37, 0x3D, 0x22, 0x9C, 0x9A, 0x7F, 0x48, 0xED, 0xAA, 0x25, 0x41, + 0xF3, 0x0C, 0xCD, 0xC5, 0x87, 0x99, 0x89, 0xF3, 0xEB, 0x87, 0xC9, 0x38, + 0x5D, 0x41, 0xF3, 0x3D, 0x32, 0x78, 0xAE, 0xB9, 0x4B, 0xC1, 0xC8, 0x4B, + 0x30, 0x5D, 0x24, 0x00, 0x15, 0x37, 0x45, 0x59, 0x16, 0xED, 0xAC, 0x0B, + 0x8F, 0x7D, 0x02, 0xFB, 0xD9, 0x97, 0xF3, 0xAD, 0x6A, 0x3B, 0x25, 0xB7, + 0x9E, 0xF9, 0x30, 0x34, 0x4A, 0xCF, 0xBE, 0x3E, 0x13, 0x4F, 0xA7, 0x5C, + 0xF3, 0x84, 0x68, 0x5B, 0xC2, 0xD5, 0xF7, 0x67, 0x80, 0x43, 0x44, 0xA6, + 0xC1, 0x96, 0xCB, 0x52, 0xB4, 0xD6, 0x9B, 0xCD, 0x37, 0x5B, 0xB6, 0x46, + 0x61, 0x07, 0x17, 0x4E, 0x35, 0xAC, 0x52, 0x18, 0x67, 0x63, 0xCC, 0x4C, + 0x0C, 0x3A, 0x5B, 0xFC, 0x8B, 0x20, 0x31, 0x58, 0xA7, 0xF2, 0xC5, 0x24, + 0x88, 0x00, 0x61, 0xC3, 0x27, 0x0D, 0x7A, 0x27, 0x7B, 0x4D, 0x69, 0x97, + 0xE3, 0xA6, 0xB8, 0x17, 0x6E, 0xEA, 0x4E, 0xBA, 0x3B, 0x71, 0xDB, 0x08, + 0x50, 0xD8, 0x11, 0xD7, 0x1A, 0x53, 0xAA, 0xBA, 0xFC, 0x03, 0x57, 0x9C, + 0xC5, 0x73, 0x26, 0xCB, 0xD7, 0xD6, 0xD9, 0x01, 0xD5, 0x9D, 0xEF, 0x97, + 0x1B, 0xE3, 0xB0, 0x7D, 0x7A, 0x3A, 0xD8, 0xAB, 0xD4, 0x17, 0x9F, 0x8D, + 0x18, 0x97, 0x65, 0xFD, 0x1A, 0x62, 0x56, 0x96, 0x9B, 0x4D, 0xFB, 0xDB, + 0xAD, 0x39, 0x8C, 0x11, 0x76, 0xEB, 0xB0, 0x67, 0x9D, 0xD1, 0x90, 0x43, + 0x02, 0x1E, 0xD8, 0xC8, 0x86, 0x40, 0x9C, 0x83, 0x0B, 0x6F, 0x2E, 0x36, + 0xE5, 0xD6, 0x26, 0x58, 0xC3, 0x77, 0x8C, 0x06, 0x20, 0xB3, 0xFA, 0xBF, + 0xA7, 0xB4, 0x9E, 0xEE, 0x86, 0x25, 0x9A, 0x95, 0xB0, 0x42, 0xC4, 0xEF, + 0xC1, 0xCC, 0xF1, 0x5F, 0xDC, 0xF4, 0x3D, 0x6C, 0x8B, 0x9C, 0xBA, 0x22, + 0x65, 0xB2, 0xC9, 0x46, 0x0C, 0x17, 0xB9, 0xF3, 0xC1, 0xBE, 0xA5, 0xC4, + 0x4C, 0xC1, 0x31, 0x52, 0xD4, 0xB5, 0x37, 0xFF, 0xD1, 0xC2, 0xAA, 0xD4, + 0xB5, 0xE0, 0x22, 0xAC, 0xBC, 0xE4, 0x77, 0x99, 0xB7, 0x20, 0x97, 0x8E, + 0x57, 0xA0, 0x60, 0x03, 0xDA, 0x39, 0xAD, 0x36, 0xC1, 0xF0, 0x81, 0x5C, + 0x53, 0x26, 0xC4, 0xA5, 0xC0, 0xE4, 0x7B, 0x17, 0xAC, 0x78, 0xF2, 0x48, + 0x95, 0x9A, 0xD0, 0x7A, 0x3B, 0xB5, 0x34, 0x49, 0x53, 0xD6, 0x59, 0x58, + 0xAE, 0x6A, 0x58, 0xB4, 0x99, 0x87, 0xC3, 0xE0, 0x00, 0x33, 0x1C, 0x00, + 0x53, 0xAF, 0x18, 0xC3, 0x43, 0x5E, 0xF8, 0x2A, 0xF8, 0x01, 0xD7, 0x38, + 0xB5, 0x69, 0x48, 0xB9, 0xE8, 0x76, 0x52, 0xD2, 0x8D, 0x70, 0x35, 0xA0, + 0x0C, 0x8E, 0x3C, 0xE0, 0xA8, 0x73, 0x5E, 0x2A, 0x20, 0xA2, 0xF3, 0x17, + 0x9B, 0xB0, 0xF7, 0x3F, 0x5A, 0x9F, 0x0A, 0x1F, 0x5E, 0x5E, 0x95, 0x3F, + 0x88, 0xEA, 0x56, 0xD6, 0xD9, 0xD9, 0x5B, 0x9E, 0x34, 0x82, 0x4B, 0xC6, + 0x72, 0xCB, 0x63, 0x4B, 0x22, 0xC0, 0x02, 0x10, 0x87, 0x91, 0xCB, 0x4D, + 0x85, 0x20, 0x4F, 0x81, 0xFE, 0x35, 0xD1, 0x74, 0xEE, 0x81, 0x6D, 0xB9, + 0x9A, 0xB6, 0x04, 0x4C, 0x39, 0xAE, 0x15, 0x5C, 0xF7, 0x05, 0x96, 0x28, + 0x6B, 0x4B, 0x6B, 0x0C, 0xA8, 0x9B, 0x6B, 0xD2, 0xF0, 0x11, 0xA4, 0x1A, + 0x87, 0xC6, 0x10, 0x15, 0x12, 0x4D, 0x10, 0x5E, 0xB3, 0xB1, 0x37, 0x73, + 0x0A, 0xFB, 0x9F, 0x6F, 0xAB, 0xE8, 0x93, 0x30, 0xA0, 0x9F, 0xF9, 0x05, + 0xFE, 0x28, 0x99, 0x8C, 0x1E, 0x0A, 0xD8, 0x01, 0x0B, 0xF7, 0xD7, 0xCE, + 0x7C, 0xA3, 0xC4, 0x55, 0x07, 0x1A, 0x94, 0xB4, 0xA2, 0x12, 0x72, 0x9E, + 0x9C, 0x80, 0x37, 0x68, 0xA8, 0xB7, 0x87, 0x65, 0x4C, 0x6D, 0x58, 0xAA, + 0x57, 0x34, 0x67, 0x5E, 0x9D, 0xAD, 0x25, 0x34, 0x86, 0xA5, 0xE1, 0x4E, + 0x0E, 0xA1, 0x81, 0x82, 0x66, 0xFE, 0x63, 0x76, 0xCE, 0x09, 0xDF, 0x65, + 0xD3, 0xDB, 0x03, 0xB5, 0x88, 0xA5, 0xD5, 0x44, 0x4C, 0xD1, 0xC5, 0x80, + 0x22, 0x76, 0xD1, 0x9F, 0x6E, 0x9E, 0xF3, 0x84, 0xB1, 0x21, 0x6F, 0x8A, + 0x35, 0x61, 0x3A, 0x27, 0x91, 0x82, 0xA6, 0xC3, 0x82, 0xE0, 0xD7, 0xC9, + 0xAA, 0x5A, 0x47, 0x9C, 0x7C, 0xD3, 0x68, 0x8E, 0x4A, 0x04, 0xF3, 0x0F, + 0x65, 0xC0, 0xFB, 0x89, 0x35, 0xFE, 0x51, 0x61, 0x69, 0xDF, 0xC0, 0x44, + 0x32, 0x26, 0x80, 0xF2, 0xD7, 0xF8, 0x36, 0xC1, 0x78, 0x53, 0x54, 0xD4, + 0xD0, 0x4E, 0xB3, 0x1B, 0xB3, 0x09, 0xBF, 0x2C, 0xF2, 0xC6, 0x43, 0x3C, + 0x0F, 0x68, 0x70, 0x15, 0x1F, 0xC6, 0x17, 0xB7, 0xA3, 0xA3, 0x9D, 0x8E, + 0xC7, 0x70, 0xB9, 0x91, 0xCC, 0x59, 0xA8, 0x57, 0x4E, 0xF6, 0x2E, 0xD7, + 0x69, 0x01, 0xBA, 0xAA, 0xEC, 0x42, 0x80, 0x39, 0xE5, 0x5F, 0x00, 0x75, + 0x0A, 0xF4, 0xA5, 0xB6, 0x7A, 0x37, 0x06, 0x6E, 0x3E, 0xFE, 0x6C, 0x7B, + 0x26, 0x4C, 0x0D, 0xFF, 0xA6, 0x31, 0x4F, 0x63, 0x7E, 0x4F, 0x61, 0x5E, + 0xB8, 0x26, 0xED, 0x70, 0xE2, 0x4F, 0x4E, 0xEF, 0x7E, 0x55, 0x97, 0x1B, + 0x0B, 0x08, 0xB3, 0x80, 0x18, 0xEE, 0xB5, 0x9E, 0x0F, 0x86, 0xB5, 0xDA, + 0x38, 0x56, 0x75, 0x56, 0x08, 0xB6, 0x8E, 0x1A, 0xCB, 0x60, 0xE5, 0x16, + 0xC3, 0x67, 0xB5, 0x2D, 0xF5, 0x10, 0x22, 0x18, 0x85, 0xBC, 0x6F, 0x65, + 0xE0, 0x07, 0x22, 0x82, 0xB1, 0xFC, 0x56, 0xF1, 0xEA, 0x1D, 0xE2, 0xE5, + 0x39, 0x9E, 0x8F, 0x23, 0x9F, 0x9A, 0x4B, 0x11, 0xB0, 0xA2, 0x77, 0x9E, + 0x79, 0x84, 0xBC, 0xC4, 0x7C, 0xC6, 0x32, 0x5D, 0x19, 0x15, 0xFB, 0x41, + 0x94, 0xFD, 0xCC, 0x48, 0xBC, 0x73, 0x29, 0x77, 0x64, 0x66, 0xA3, 0xDB, + 0xA3, 0x65, 0x6D, 0xDE, 0x4B, 0x60, 0xA1, 0xA1, 0xDF, 0x3A, 0x74, 0x99, + 0x0A, 0x04, 0x17, 0xAF, 0x30, 0x26, 0xB1, 0xC4, 0xC3, 0xC6, 0xA1, 0x99, + 0x5D, 0x7C, 0xF4, 0xCD, 0xEF, 0x27, 0x6E, 0xFB, 0x13, 0xBE, 0x5F, 0x59, + 0x9C, 0x71, 0x6F, 0x51, 0x49, 0x7A, 0x5F, 0x03, 0xE8, 0x83, 0x97, 0x33, + 0xFB, 0x0B, 0x27, 0xE4, 0x0A, 0xF3, 0xA8, 0x7A, 0x10, 0x4A, 0x0C, 0xF3, + 0x1F, 0xA6, 0x0E, 0xBF, 0x40, 0x84, 0x72, 0x8D, 0x57, 0xB2, 0xD9, 0xB5, + 0x88, 0xDB, 0x80, 0x1A, 0x2A, 0x34, 0xB8, 0x5E, 0xC2, 0x86, 0x7B, 0x94, + 0x1F, 0xE0, 0xCC, 0xA2, 0x16, 0x4D, 0x1A, 0xF7, 0xF6, 0x48, 0x1B, 0x40, + 0x5A, 0x2B, 0xEF, 0xA5, 0x31, 0x98, 0x61, 0xE5, 0x16, 0x32, 0xEB, 0x58, + 0x8F, 0x51, 0xF2, 0x6F, 0xE1, 0x27, 0x28, 0x43, 0x18, 0x5D, 0xD2, 0x30, + 0x3C, 0x25, 0xD6, 0x6E, 0x00, 0x3B, 0x7F, 0xED, 0x43, 0x1E, 0xD8, 0x1F, + 0x07, 0x91, 0xEE, 0x3B, 0x30, 0x89, 0x14, 0x9B, 0xDD, 0x62, 0xD0, 0xA1, + 0xE2, 0xF3, 0x9A, 0xC9, 0x52, 0xCF, 0x14, 0x13, 0xB3, 0x24, 0x75, 0x97, + 0xB7, 0xF6, 0x52, 0x36, 0xFC, 0x66, 0x82, 0x10, 0xBF, 0xB2, 0x22, 0x0D, + 0x64, 0x78, 0x1F, 0x4F, 0x61, 0x5E, 0x6C, 0xC6, 0x60, 0x5B, 0x6E, 0x38, + 0x5D, 0xCF, 0xA9, 0x6F, 0x2B, 0x8D, 0x73, 0x78, 0xB8, 0xE3, 0x0F, 0xDE, + 0xC1, 0xA5, 0x71, 0xD9, 0x15, 0x31, 0x3A, 0x4F, 0xA9, 0x5A, 0x4A, 0xB1, + 0x97, 0x7D, 0x87, 0x4B, 0x45, 0xA3, 0x45, 0x9B, 0xFA, 0xF6, 0x62, 0x5E, + 0x2C, 0xC3, 0xC8, 0x93, 0x42, 0x8A, 0xA8, 0x8F, 0x83, 0x57, 0xC4, 0x63, + 0xD8, 0x0F, 0xA9, 0x16, 0x6C, 0x06, 0xF9, 0xEA, 0x75, 0x99, 0x95, 0x12, + 0xB4, 0x46, 0x0D, 0x24, 0xF5, 0x8C, 0x67, 0x70, 0x22, 0x42, 0x35, 0x5E, + 0x6F, 0xC3, 0x3F, 0xA4, 0xD6, 0xBC, 0xA9, 0xB7, 0xB9, 0x6F, 0x34, 0xD5, + 0x5A, 0x88, 0x54, 0xDC, 0x03, 0x17, 0xF7, 0xDE, 0xE6, 0x65, 0x2E, 0xF7, + 0x5E, 0xFF, 0xFE, 0xEE, 0x58, 0x62, 0x16, 0xE6, 0x32, 0xC3, 0x62, 0x2E, + 0xD9, 0xF4, 0x34, 0x9D, 0x0E, 0x33, 0x07, 0x59, 0xFD, 0x75, 0x0C, 0x98, + 0x01, 0xC3, 0x65, 0xED, 0xEA, 0x1E, 0xB2, 0xA3, 0x74, 0x90, 0xFF, 0xE7, + 0x16, 0xC8, 0x3F, 0x75, 0xC3, 0xA5, 0x9D, 0xB8, 0x8F, 0x5B, 0x82, 0xDC, + 0x64, 0xA8, 0x49, 0x52, 0x4A, 0xD4, 0xCD, 0x8F, 0x4D, 0x33, 0x6E, 0x05, + 0x6F, 0x64, 0x5C, 0x0F, 0x30, 0x96, 0x74, 0xA5, 0xB6, 0x6F, 0x52, 0xD3, + 0x0C, 0xFA, 0x41, 0x9E, 0x4E, 0x45, 0x7B, 0xBD, 0x3E, 0x0F, 0x69, 0x84, + 0xA4, 0xF8, 0x11, 0xD2, 0x14, 0x43, 0xCC, 0xDA, 0xFE, 0xE1, 0xD0, 0x54, + 0x5A, 0x45, 0xC4, 0x31, 0xA9, 0xDF, 0x07, 0xB6, 0x69, 0x9C, 0x63, 0xDE, + 0x6A, 0xB4, 0xE5, 0xB9, 0x92, 0xDA, 0x1B, 0xD4, 0xD3, 0x2C, 0x06, 0xCD, + 0xD1, 0xEA, 0x01, 0x44, 0xB1, 0x15, 0x35, 0x64, 0x9F, 0x21, 0xF7, 0x6B, + 0xAA, 0x97, 0x99, 0x29, 0xC7, 0x91, 0x3D, 0x6F, 0x35, 0x77, 0x71, 0x9F, + 0x35, 0xC2, 0xBE, 0xFC, 0x48, 0xE0, 0xB2, 0xED, 0x0E, 0x6D, 0xE8, 0x3F, + 0xB8, 0x04, 0x73, 0xC5, 0x24, 0x3D, 0x26, 0xF3, 0x1E, 0x5C, 0x1C, 0xD4, + 0x39, 0x1A, 0x7F, 0x8F, 0xA8, 0x68, 0x1E, 0xDB, 0xF2, 0x92, 0x0F, 0x8F, + 0x34, 0x6B, 0x82, 0x3A, 0xEF, 0x94, 0x80, 0xCE, 0xCB, 0xEF, 0xF3, 0x22, + 0x6C, 0x99, 0x7B, 0x6C, 0xCC, 0x27, 0x0F, 0x84, 0xEC, 0x1C, 0x56, 0x14, + 0x3C, 0xD9, 0x84, 0x1B, 0x26, 0x5A, 0x88, 0xF1, 0xA6, 0x0C, 0x86, 0xF8, + 0x62, 0xED, 0x64, 0xB5, 0x8D, 0xD4, 0x06, 0x98, 0xD6, 0xF2, 0xB5, 0xEC, + 0xB7, 0x40, 0x96, 0xEC, 0xAA, 0xAA, 0x5B, 0x0E, 0xC9, 0x7F, 0x84, 0xDB, + 0xB2, 0x91, 0x15, 0x86, 0xA3, 0xFA, 0x1D, 0xE8, 0xEF, 0xBA, 0x87, 0xFD, + 0x70, 0xE3, 0x89, 0xFE, 0x2A, 0xA5, 0x41, 0x74, 0xB9, 0xF2, 0x70, 0xED, + 0x12, 0xB6, 0xA2, 0x42, 0x00, 0x9F, 0x17, 0xFB, 0x43, 0xB3, 0x6A, 0x59, + 0xEA, 0x20, 0xD9, 0xD1, 0x9C, 0x5C, 0x84, 0x71, 0x57, 0x01, 0x7C, 0x3C, + 0x20, 0x61, 0x08, 0xAC, 0xB7, 0xB0, 0xEE, 0x29, 0x45, 0xBF, 0x87, 0xDB, + 0xC8, 0x33, 0xB1, 0x8A, 0xCC, 0x96, 0xC8, 0xB2, 0xF8, 0x89, 0x0B, 0x81, + 0xEF, 0xAA, 0x67, 0xFB, 0xC9, 0xD5, 0xFC, 0xFE, 0x3D, 0xEC, 0x78, 0x83, + 0x3A, 0xF6, 0x13, 0x43, 0x6E, 0x47, 0xA2, 0xDE, 0x48, 0x5B, 0x79, 0xCF, + 0x1F, 0x1C, 0xAD, 0xA4, 0x2C, 0x15, 0x81, 0x12, 0xDC, 0x2F, 0x80, 0x6F, + 0x7A, 0x32, 0x03, 0x52, 0x3B, 0xB0, 0x97, 0x41, 0xD5, 0xAB, 0x55, 0x2E, + 0xEA, 0xE9, 0xBB, 0xEB, 0x5B, 0x3E, 0xD5, 0x2E, 0xBC, 0xCE, 0x7E, 0x2D, + 0x52, 0xF3, 0x37, 0xF2, 0x53, 0x89, 0x5E, 0x56, 0x66, 0x74, 0xBF, 0xE2, + 0x35, 0xAA, 0xEA, 0x45, 0xE9, 0x6F, 0xFC, 0x1E, 0x15, 0x8E, 0x13, 0x5A, + 0xC6, 0xA2, 0x0B, 0x72, 0x89, 0x2B, 0x5E, 0x14, 0xC6, 0x41, 0x36, 0x0B, + 0xA0, 0xA4, 0x2B, 0x7F, 0x7E, 0xD0, 0x90, 0xFE, 0x4F, 0x4E, 0x7B, 0xD8, + 0x5B, 0x06, 0xEE, 0x0F, 0x62, 0xB6, 0x54, 0xDB, 0x17, 0x9B, 0x16, 0xEA, + 0x72, 0xAC, 0x58, 0x9D, 0x88, 0xD0, 0xE6, 0x14, 0x82, 0x57, 0x89, 0xEA, + 0x3A, 0x3D, 0x23, 0xE9, 0x14, 0x14, 0x0D, 0x95, 0x24, 0x7E, 0xAF, 0x4F, + 0x39, 0x3E, 0x51, 0x74, 0x06, 0xCC, 0x58, 0xA6, 0x50, 0xF0, 0xF8, 0x1B, + 0xD6, 0x48, 0xEB, 0xBD, 0x68, 0x34, 0xD8, 0x19, 0xA6, 0x83, 0x41, 0x37, + 0xDD, 0x41, 0x42, 0x1E, 0x5F, 0xFF, 0x0D, 0xEA, 0x25, 0x42, 0x55, 0x39, + 0xAA, 0x75, 0x88, 0x0F, 0xC8, 0x04, 0x47, 0x7A, 0xCA, 0xD0, 0x88, 0x72, + 0x74, 0x95, 0x1B, 0xCA, 0xEB, 0xF6, 0x11, 0x03, 0xA1, 0x0C, 0x61, 0x9E, + 0x4F, 0x2E, 0x6A, 0x60, 0x64, 0x7E, 0x5B, 0x12, 0xC4, 0x38, 0xFD, 0x66, + 0xBA, 0xBB, 0xAC, 0x1B, 0xF4, 0xAB, 0x7D, 0x35, 0x4E, 0xAB, 0x47, 0x3F, + 0xDA, 0xD5, 0x0C, 0x0D, 0x61, 0x85, 0xEF, 0x78, 0x74, 0x27, 0x1D, 0x39, + 0x07, 0x2B, 0x8C, 0x45, 0x24, 0xC6, 0x3A, 0xD4, 0x29, 0x71, 0xEE, 0xF0, + 0xAA, 0xDB, 0xE0, 0x4A, 0x5E, 0xB2, 0xEC, 0x91, 0xA6, 0xA6, 0xC8, 0xBC, + 0x60, 0x24, 0x58, 0x77, 0xBB, 0x5C, 0x07, 0x14, 0x39, 0x52, 0xE0, 0xA0, + 0xCC, 0x15, 0xA2, 0xC9, 0xA7, 0x69, 0x6F, 0xBC, 0x61, 0x38, 0xD3, 0x36, + 0xBC, 0xFC, 0x22, 0x96, 0x07, 0xB5, 0xE7, 0xFA, 0xA8, 0x9E, 0x99, 0x8F, + 0xDB, 0x62, 0xB0, 0x49, 0xF4, 0xD3, 0xC9, 0xFF, 0x7D, 0xA3, 0x12, 0xEF, + 0x61, 0x13, 0xF3, 0x81, 0x80, 0xB3, 0xCD, 0x74, 0x21, 0x12, 0x0F, 0x36, + 0x67, 0x52, 0xCE, 0xD1, 0x1D, 0x23, 0xA5, 0x35, 0x6B, 0x5F, 0x74, 0x62, + 0x8A, 0xAD, 0xED, 0x36, 0x27, 0xF8, 0x7D, 0xE6, 0xED, 0x78, 0xE4, 0xD2, + 0x4D, 0x56, 0xC5, 0xE1, 0x29, 0x54, 0x2D, 0x3E, 0x80, 0x97, 0xD0, 0xDC, + 0x1F, 0x89, 0x05, 0xAE, 0xE8, 0xB3, 0x21, 0xD5, 0xF4, 0x0B, 0xBB, 0x73, + 0xF6, 0x1A, 0x6A, 0xD5, 0x2E, 0xD4, 0xE3, 0x37, 0x93, 0x04, 0x91, 0x52, + 0x5E, 0xCC, 0x8E, 0xC5, 0xD5, 0x0A, 0xB7, 0x11, 0xA7, 0xB3, 0xA1, 0xB1, + 0xE0, 0x06, 0x61, 0xF7, 0xD6, 0xB4, 0x0C, 0x07, 0x27, 0x55, 0x67, 0x51, + 0xC0, 0x17, 0xD5, 0x44, 0xFB, 0xF7, 0xE6, 0x40, 0xAD, 0x58, 0x78, 0x6D, + 0xA5, 0x81, 0x60, 0x84, 0x9A, 0xB0, 0xEC, 0x9A, 0x7A, 0xEF, 0x5F, 0xEF, + 0xD6, 0x86, 0x8C, 0x66, 0xC6, 0xFE, 0x1B, 0x12, 0x4F, 0x87, 0xC1, 0xB1, + 0x1A, 0x85, 0x65, 0xE0, 0x44, 0xC6, 0x7F, 0xD3, 0xA4, 0x2C, 0x84, 0xF6, + 0x77, 0x70, 0x98, 0x43, 0xB2, 0x83, 0xB8, 0xD4, 0x6B, 0xAD, 0xD1, 0xC8, + 0x38, 0x98, 0x06, 0x43, 0xA9, 0xAB, 0x01, 0x02, 0x7D, 0x69, 0x01, 0x12, + 0x65, 0x58, 0x5E, 0x3C, 0xE7, 0xCD, 0x57, 0xD9, 0x18, 0x24, 0xA1, 0x96, + 0x8B, 0x64, 0x92, 0x5E, 0xBF, 0x6D, 0x8D, 0x8C, 0xF4, 0xE2, 0x35, 0x28, + 0xBB, 0xE2, 0x9A, 0x26, 0x08, 0xA6, 0xBD, 0x66, 0x09, 0x06, 0x41, 0x7D, + 0x43, 0x52, 0xBC, 0x17, 0x97, 0x7D, 0x98, 0x34, 0x9D, 0x84, 0x62, 0x57, + 0xC8, 0xDE, 0x7F, 0x85, 0xEE, 0xE9, 0x54, 0xD7, 0x11, 0x99, 0xFB, 0xA6, + 0xB6, 0x4F, 0x4B, 0x06, 0x9D, 0xDC, 0x3C, 0xF6, 0x6E, 0xB6, 0xAC, 0x93, + 0x6F, 0x4E, 0x1E, 0x9E, 0x5A, 0xB2, 0xC3, 0x6F, 0xC6, 0x99, 0x9C, 0xDA, + 0x25, 0x7D, 0x2E, 0x98, 0xDE, 0x0A, 0x7D, 0x75, 0xFF, 0xEE, 0xC9, 0x1E, + 0xC2, 0x1E, 0x49, 0xE2, 0xCD, 0x06, 0x2D, 0x42, 0x27, 0x12, 0xC2, 0x73, + 0x2B, 0x4A, 0x72, 0x8A, 0x70, 0x92, 0x91, 0x26, 0xE1, 0xDD, 0x30, 0xCA, + 0x1D, 0x27, 0x88, 0x99, 0x18, 0x8B, 0xBA, 0x05, 0xC6, 0x8C, 0x46, 0x01, + 0xE2, 0x83, 0x84, 0xBE, 0xE7, 0x71, 0x56, 0xD2, 0xF9, 0xDF, 0x66, 0x45, + 0xAB, 0x05, 0x30, 0x78, 0xE7, 0xE9, 0xCB, 0xF4, 0x10, 0x80, 0xB6, 0x50, + 0x7F, 0xD7, 0x9E, 0x5D, 0x93, 0xD0, 0x6F, 0xD7, 0x1E, 0x99, 0xC3, 0xC7, + 0xA0, 0x96, 0x7F, 0xE3, 0xAF, 0xF3, 0x71, 0x8A, 0x3A, 0x7B, 0xE0, 0xA9, + 0x77, 0x0E, 0x9F, 0xB2, 0xD7, 0x31, 0x34, 0x25, 0xE9, 0x2B, 0x4A, 0x5C, + 0x44, 0xAC, 0x27, 0x49, 0xFF, 0xAE, 0x91, 0x17, 0xB3, 0xFA, 0xF6, 0x72, + 0x96, 0xC8, 0x5C, 0x4F, 0x78, 0x1C, 0x55, 0x74, 0xD3, 0x78, 0xD9, 0x2E, + 0xE6, 0xA7, 0xEA, 0x29, 0x0E, 0xAA, 0x71, 0xAC, 0x77, 0xF6, 0xA2, 0x5A, + 0x00, 0x4C, 0xF8, 0xAA, 0x2E, 0xC7, 0x48, 0xD1, 0xC7, 0x0C, 0xB2, 0x4F, + 0xC7, 0xCA, 0xCC, 0xC7, 0xDF, 0xDC, 0x18, 0x8C, 0x29, 0x5B, 0x22, 0xB1, + 0x01, 0x1B, 0x54, 0x47, 0x5B, 0x98, 0x88, 0x49, 0x65, 0xB6, 0x55, 0xF5, + 0x7B, 0xD3, 0x5D, 0x37, 0x1A, 0xD5, 0x23, 0xF0, 0x5C, 0x2F, 0x96, 0xAB, + 0xD9, 0xCE, 0x6A, 0xA5, 0x9A, 0xAE, 0xDE, 0xB2, 0x46, 0x55, 0xF6, 0x6F, + 0x4E, 0xA1, 0x4C, 0x4E, 0xF2, 0x32, 0xCC, 0x89, 0xA3, 0x0F, 0x16, 0xFD, + 0x6C, 0x73, 0x07, 0x84, 0x9B, 0x6D, 0xC7, 0x3A, 0x9F, 0x04, 0xFA, 0x31, + 0xB8, 0x31, 0xD2, 0x9E, 0x1A, 0xE8, 0x36, 0x05, 0xEA, 0xD5, 0x88, 0x83, + 0x56, 0xB4, 0x2B, 0x87, 0xFD, 0xDF, 0x52, 0xFB, 0xBA, 0x6E, 0x7B, 0x6C, + 0x18, 0xCE, 0xAD, 0xD1, 0xF0, 0xBD, 0xC5, 0xF7, 0x42, 0x96, 0x83, 0xD9, + 0x70, 0x24, 0xF9, 0x32, 0x26, 0x2F, 0x69, 0x94, 0x05, 0xB2, 0x92, 0xB9, + 0x00, 0x68, 0x8F, 0x25, 0xF2, 0x33, 0x3D, 0x6D, 0x3E, 0x58, 0xE0, 0x0B, + 0x33, 0xA8, 0x0A, 0x58, 0x2F, 0x46, 0x49, 0x52, 0x2D, 0x7C, 0x5B, 0xF8, + 0x51, 0x29, 0x6C, 0xE1, 0xB4, 0x80, 0xAF, 0x31, 0x03, 0xCC, 0xF5, 0xCD, + 0x80, 0x13, 0x07, 0x96, 0xB4, 0x92, 0x29, 0x60, 0xBB, 0x95, 0xF4, 0x21, + 0xA3, 0x82, 0xB2, 0x33, 0x70, 0x19, 0x24, 0x9E, 0xA0, 0x53, 0xBE, 0x0C, + 0xC7, 0x0F, 0x36, 0x7B, 0xC7, 0xFA, 0xB5, 0x2C, 0xC8, 0xA4, 0x61, 0xD6, + 0xA4, 0x79, 0x11, 0x98, 0x47, 0x43, 0xF0, 0xA5, 0x33, 0x3C, 0xF3, 0xFB, + 0x7D, 0xF2, 0x22, 0x22, 0x80, 0x48, 0xE3, 0xFA, 0x2D, 0x5B, 0x73, 0xCB, + 0x60, 0xE6, 0x2A, 0xCB, 0x1A, 0xF8, 0x66, 0xC0, 0x7D, 0xB0, 0xEE, 0x36, + 0x9C, 0xD5, 0x00, 0x10, 0x9A, 0x6A, 0xF1, 0xB9, 0x32, 0x99, 0xF7, 0xEA, + 0xB8, 0x29, 0xCB, 0xFA, 0x9E, 0xC6, 0xB1, 0x2C, 0x4F, 0x9A, 0x54, 0xC8, + 0x96, 0x86, 0x1E, 0xCF, 0x79, 0x32, 0x4E, 0x81, 0x6C, 0x70, 0x7C, 0x68, + 0x68, 0x91, 0xFC, 0x17, 0xF7, 0xBC, 0x12, 0x4C, 0x6F, 0xB3, 0x23, 0xBF, + 0xF1, 0xDE, 0xEE, 0x47, 0x0E, 0xD4, 0xC3, 0xD5, 0x2C, 0x56, 0xA2, 0x0E, + 0x96, 0xF2, 0x79, 0x34, 0x52, 0x53, 0xB8, 0x63, 0xD0, 0x6D, 0x53, 0x3E, + 0x82, 0xD9, 0x2A, 0xB8, 0xE6, 0xC0, 0x9E, 0x43, 0x36, 0xEA, 0xE2, 0xD3, + 0xD6, 0xB3, 0x81, 0x1C, 0x53, 0xD7, 0x9A, 0xEC, 0xDD, 0x19, 0x23, 0x5A, + 0x64, 0x17, 0xE9, 0x87, 0xF8, 0x2B, 0xCF, 0x77, 0xAB, 0x09, 0x8A, 0x3B, + 0xD2, 0xF1, 0x0B, 0x6D, 0xE0, 0x28, 0xA7, 0x04, 0x36, 0x2C, 0x66, 0xBB, + 0x6A, 0x9E, 0xAD, 0x6B, 0xA3, 0xE6, 0x9E, 0x14, 0xFA, 0x25, 0xAE, 0xA6, + 0x7E, 0x59, 0xFD, 0x2A, 0x2C, 0x6C, 0x6D, 0x42, 0xF7, 0xF7, 0x2C, 0x6B, + 0xEB, 0xC3, 0xA0, 0xB1, 0x07, 0x2C, 0x86, 0xA9, 0xD1, 0x2D, 0xEB, 0x3D, + 0x02, 0xF2, 0xD0, 0x33, 0x79, 0xE4, 0x72, 0x0A, 0x4A, 0xCC, 0xBC, 0x5A, + 0x22, 0xCD, 0xB5, 0x35, 0x61, 0xEA, 0xB6, 0x7D, 0xEE, 0xA5, 0xAD, 0xE2, + 0x49, 0x5B, 0x94, 0xE4, 0x67, 0x72, 0x87, 0x8A, 0xC9, 0xAC, 0xE2, 0x96, + 0xE0, 0x7B, 0x0A, 0x8D, 0xB5, 0xFF, 0x35, 0x99, 0xF1, 0x71, 0xFA, 0x77, + 0x00, 0x60, 0x1A, 0x39, 0x64, 0x3F, 0x86, 0x86, 0x5A, 0xEC, 0xD3, 0xD7, + 0xCF, 0xC1, 0xA0, 0x3E, 0xDB, 0xA7, 0xED, 0xC6, 0x24, 0x63, 0x96, 0x29, + 0xAF, 0xCB, 0x49, 0x24, 0x08, 0xC5, 0x0D, 0xE0, 0x52, 0x95, 0x89, 0xB3, + 0xA8, 0xDD, 0x4D, 0x66, 0xAC, 0x7C, 0x7A, 0x15, 0xA5, 0x81, 0xBE, 0xB9, + 0x7E, 0x1C, 0x82, 0xA5, 0xD5, 0x45, 0x86, 0xE6, 0x06, 0x5C, 0x60, 0xC7, + 0x0F, 0xD3, 0x51, 0xF1, 0x17, 0x34, 0x6D, 0x43, 0xB1, 0x98, 0x52, 0x11, + 0x24, 0x3B, 0x9E, 0x5A, 0xE9, 0xD1, 0xEE, 0x7C, 0x64, 0xF5, 0xBB, 0x4B, + 0xBC, 0xC3, 0x43, 0x5F, 0x80, 0x27, 0xF2, 0x1A, 0xFC, 0xEA, 0xA0, 0x5C, + 0x25, 0xA4, 0x63, 0x16, 0x0B, 0xAA, 0x30, 0x2E, 0x4E, 0x68, 0x9D, 0xE0, + 0xEE, 0x38, 0x28, 0x80, 0xC7, 0x22, 0x10, 0x19, 0x55, 0x7F, 0x4C, 0xFE, + 0xD0, 0xBA, 0x38, 0x4A, 0x1E, 0xD0, 0x42, 0xE3, 0x57, 0x57, 0xD5, 0x3B, + 0xC3, 0xE7, 0x58, 0xA4, 0x7E, 0x29, 0x83, 0x9B, 0xDF, 0x16, 0x45, 0x1D, + 0xE1, 0xDC, 0xE2, 0xE2, 0xAB, 0x0E, 0x45, 0x3F, 0x34, 0x0C, 0x47, 0x88, + 0xEB, 0x0A, 0xF0, 0x55, 0x38, 0x7D, 0xCD, 0x73, 0x73, 0xEA, 0x59, 0xAB, + 0xA2, 0xFA, 0xF5, 0x20, 0x9B, 0x64, 0xCE, 0xB5, 0x6B, 0x0C, 0x40, 0x25, + 0xC7, 0x3B, 0x03, 0xED, 0xA9, 0x4C, 0x8F, 0x47, 0x44, 0x2B, 0x18, 0x6B, + 0x96, 0xF0, 0x1B, 0xAD, 0x6B, 0x3C, 0x7E, 0xA7, 0x60, 0xB5, 0x1F, 0x51, + 0x17, 0x05, 0xF3, 0xD1, 0xF5, 0x2A, 0x5C, 0xE6, 0xC4, 0x81, 0xBF, 0x91, + 0xBB, 0x52, 0xA5, 0x7B, 0x4B, 0xB3, 0x19, 0x8D, 0x13, 0xDB, 0xA6, 0x31, + 0xF8, 0x9F, 0x36, 0x21, 0x83, 0xF3, 0x0E, 0xF5, 0x13, 0x3B, 0xD4, 0x87, + 0xE2, 0xE1, 0x83, 0x3D, 0x56, 0x1A, 0x75, 0x4B, 0x80, 0x33, 0x22, 0xF1, + 0x5F, 0xCA, 0x53, 0xD0, 0x36, 0x6E, 0x4D, 0x4B, 0x95, 0xA2, 0x15, 0xB5, + 0x73, 0x5F, 0x97, 0x6B, 0xCE, 0xE1, 0x16, 0x3B, 0x8E, 0xBD, 0xF3, 0x54, + 0x8B, 0xC4, 0x10, 0x47, 0x10, 0x3B, 0x5D, 0xA4, 0xC9, 0x2E, 0xCB, 0x32, + 0x21, 0x1D, 0x1E, 0xE2, 0x03, 0xA1, 0x5F, 0x8B, 0x0B, 0x65, 0xCC, 0x11, + 0x46, 0x73, 0xF8, 0x51, 0x4F, 0xBA, 0x27, 0xAD, 0x85, 0xC7, 0xAB, 0xD0, + 0xA2, 0x90, 0x79, 0x27, 0x23, 0xF8, 0x1A, 0x94, 0x79, 0x82, 0xC3, 0x17, + 0x2B, 0xA8, 0x3E, 0x59, 0x2A, 0x64, 0xCD, 0x16, 0xDD, 0xBB, 0xA3, 0x0C, + 0x34, 0xDE, 0xAE, 0xAF, 0x5D, 0xDE, 0x90, 0x31, 0x7F, 0x9A, 0x69, 0x14, + 0x65, 0xAC, 0xC0, 0xED, 0xE9, 0xB8, 0xDD, 0x59, 0x4F, 0x37, 0xA7, 0xEC, + 0x5A, 0xA8, 0x96, 0xC2, 0xF8, 0x2A, 0xE6, 0xB4, 0xDD, 0xE7, 0xF8, 0x44, + 0x5B, 0x39, 0x4B, 0x47, 0xCE, 0x0C, 0x33, 0xB1, 0x99, 0xB2, 0x32, 0x05, + 0x7E, 0xFD, 0x12, 0x5B, 0xF8, 0xD2, 0x9D, 0x20, 0x94, 0x8D, 0x19, 0xFA, + 0x82, 0x61, 0xB3, 0x99, 0x7C, 0x8C, 0x15, 0x0E, 0x0D, 0x9C, 0xFC, 0xC1, + 0xDC, 0x0C, 0x79, 0x69, 0x07, 0xD8, 0x13, 0x86, 0xDA, 0xA4, 0xF0, 0xCF, + 0x5B, 0xDE, 0xF7, 0xB9, 0x71, 0x7C, 0x13, 0xA6, 0xAF, 0x31, 0x88, 0x1E, + 0x3B, 0xFC, 0x96, 0xDF, 0xE5, 0x63, 0x80, 0x69, 0x87, 0x09, 0xD8, 0x46, + 0xBB, 0x28, 0x18, 0x34, 0x1A, 0x03, 0xE8, 0xF4, 0x46, 0x46, 0x8B, 0xD5, + 0x3C, 0xD6, 0x02, 0xE3, 0xE2, 0x1A, 0x9A, 0xC7, 0x97, 0x2A, 0x62, 0x60, + 0x6A, 0xBD, 0x60, 0xFD, 0x5D, 0x4A, 0x68, 0x92, 0x56, 0x8F, 0x86, 0xAD, + 0x57, 0x36, 0x1B, 0xD6, 0x6C, 0x57, 0xB8, 0x41, 0x13, 0x1D, 0x13, 0xB8, + 0xB1, 0x60, 0xE2, 0xB5, 0x9A, 0x8F, 0xE3, 0x56, 0xAC, 0xD2, 0xC6, 0x62, + 0xBA, 0xA8, 0x66, 0x4D, 0xDA, 0x58, 0x9A, 0xCE, 0x8F, 0xCD, 0x55, 0xF6, + 0x19, 0xF1, 0x2C, 0xED, 0x38, 0x66, 0xAD, 0x88, 0x56, 0x2F, 0xA3, 0x42, + 0x93, 0x55, 0x33, 0x06, 0x35, 0x2B, 0xDE, 0x1E, 0xD9, 0x1B, 0x07, 0x5C, + 0x98, 0xD0, 0x74, 0xE7, 0x56, 0x97, 0x4F, 0xD1, 0xA4, 0x17, 0xB2, 0xB3, + 0x32, 0x70, 0xDF, 0x91, 0xEC, 0xC2, 0xA6, 0x86, 0xBC, 0x4E, 0xFC, 0x17, + 0xA6, 0x13, 0x50, 0x0C, 0x1E, 0x3C, 0x50, 0xDF, 0x6C, 0x14, 0x74, 0xBE, + 0xF5, 0x20, 0xB8, 0xBF, 0xA0, 0xF7, 0x3E, 0x32, 0x5F, 0x22, 0x9B, 0x15, + 0x74, 0xBC, 0xD5, 0x42, 0xCE, 0x19, 0xAA, 0x5C, 0x01, 0xB1, 0x77, 0xC3, + 0x50, 0x56, 0xCD, 0x8C, 0x61, 0xBA, 0x20, 0xFB, 0xD5, 0x76, 0x1D, 0x57, + 0xA3, 0xBB, 0xFC, 0xFB, 0x07, 0x1F, 0x43, 0x07, 0x39, 0x70, 0xAC, 0x82, + 0x6A, 0x41, 0xDE, 0x41, 0x3B, 0xE4, 0xC4, 0x3A, 0xD6, 0x4D, 0xF0, 0x77, + 0x54, 0xC7, 0x35, 0x26, 0x98, 0x47, 0x71, 0xEC, 0x2F, 0x02, 0x5F, 0x89, + 0x4B, 0xB0, 0xF3, 0xA5, 0xC7, 0x99, 0xB7, 0x2D, 0x65, 0xE3, 0x42, 0x07, + 0x17, 0x22, 0xD8, 0x96, 0x0A, 0x01, 0xB3, 0x26, 0xA1, 0x64, 0xCF, 0x4D, + 0xFA, 0x59, 0x54, 0xD9, 0x52, 0x6D, 0xAE, 0x62, 0x3D, 0x96, 0x1B, 0x49, + 0x6C, 0x38, 0x30, 0x16, 0xE9, 0xAE, 0xB8, 0x8E, 0xDF, 0x2A, 0x11, 0x33, + 0x09, 0x79, 0x60, 0x0E, 0xCC, 0xB8, 0x7D, 0xDF, 0x80, 0x76, 0x05, 0x89, + 0xDF, 0x96, 0xD6, 0x0E, 0x89, 0x5E, 0x72, 0x1D, 0x31, 0x9B, 0xA2, 0xED, + 0x41, 0x7F, 0x41, 0x2F, 0xCB, 0x31, 0xD9, 0x8A, 0x36, 0x90, 0xDB, 0xD1, + 0x26, 0xE5, 0x29, 0x7C, 0x37, 0x99, 0xFA, 0xDF, 0x6C, 0xF1, 0x49, 0x0B, + 0xE2, 0xD8, 0xB7, 0x53, 0x34, 0xEF, 0xDF, 0x9A, 0xBC, 0xCF, 0x23, 0x32, + 0xD1, 0x3C, 0xA7, 0x4A, 0xF4, 0x48, 0x1A, 0xE6, 0x4C, 0x8D, 0x04, 0x3D, + 0x7B, 0x1D, 0x63, 0xAB, 0xB4, 0xB8, 0xA1, 0x23, 0x71, 0xAB, 0x15, 0x01, + 0x00, 0xC6, 0x02, 0xAD, 0x86, 0x31, 0xDA, 0x07, 0x1C, 0x17, 0xBD, 0xED, + 0xF0, 0x2E, 0x7C, 0xA2, 0x64, 0x7B, 0x45, 0x4E, 0x5E, 0x76, 0x8E, 0x07, + 0x26, 0x2B, 0x4E, 0x4B, 0x8B, 0x31, 0xE4, 0xB1, 0xD8, 0xCD, 0x5C, 0xE7, + 0x68, 0xEF, 0x87, 0x5D, 0x57, 0xA6, 0x02, 0xDE, 0x64, 0xE5, 0x04, 0xB2, + 0xC8, 0x6F, 0xE6, 0x4B, 0xE0, 0x30, 0x38, 0x94, 0xFE, 0xC0, 0x0D, 0xE7, + 0x10, 0xCB, 0x98, 0x59, 0x9F, 0xE8, 0xBC, 0x78, 0x3C, 0x67, 0xDE, 0x4B, + 0x72, 0x6A, 0x40, 0x85, 0xFF, 0xCE, 0x72, 0xC9, 0x74, 0x44, 0x6F, 0xC8, + 0x4D, 0xA5, 0xC9, 0x6B, 0xBB, 0x0E, 0xF5, 0x26, 0x89, 0x06, 0x23, 0xA2, + 0xF0, 0x30, 0x01, 0xD4, 0x0C, 0xE9, 0xE9, 0x8D, 0x71, 0x77, 0x41, 0x1D, + 0x7A, 0x71, 0x79, 0x63, 0x86, 0xC9, 0x0D, 0x17, 0x67, 0x62, 0x6A, 0x24, + 0x64, 0xC8, 0xBD, 0xA4, 0xED, 0x7B, 0xF2, 0x35, 0x59, 0x53, 0x09, 0x70, + 0x56, 0xF2, 0xBB, 0x9B, 0x33, 0xEF, 0xFE, 0x25, 0x31, 0x65, 0x74, 0x5A, + 0xC8, 0x54, 0xD7, 0xE9, 0x10, 0x88, 0x16, 0x60, 0xB7, 0x4C, 0x9E, 0x8F, + 0x9F, 0xFA, 0x02, 0x5E, 0x78, 0xF2, 0x4D, 0x07, 0x42, 0x35, 0xAE, 0x55, + 0x3E, 0xA2, 0xBA, 0x6C, 0xB6, 0xBD, 0x87, 0xD8, 0xD8, 0xC8, 0x1F, 0xCA, + 0xD5, 0xA8, 0xCD, 0x5D, 0x05, 0xF5, 0xFA, 0x69, 0xDE, 0x0E, 0x0C, 0x5F, + 0x24, 0x0C, 0x4C, 0x36, 0xB1, 0x00, 0x3B, 0x61, 0x74, 0xDB, 0x81, 0xFB, + 0x41, 0x8D, 0x16, 0xDB, 0x46, 0x8C, 0x75, 0x53, 0x31, 0x87, 0x48, 0x03, + 0x26, 0xE7, 0xDA, 0x56, 0xD5, 0x59, 0x13, 0xAC, 0x49, 0x25, 0x6E, 0xE7, + 0x9F, 0xCD, 0xDE, 0x17, 0xC7, 0xA0, 0x04, 0x3E, 0x80, 0x3B, 0x61, 0x28, + 0x1F, 0xDA, 0xA7, 0x97, 0xE2, 0x99, 0x0D, 0x9B, 0x34, 0xDB, 0x67, 0x41, + 0x0E, 0xC1, 0x65, 0x6F, 0xE8, 0xD4, 0x92, 0x02, 0xBF, 0x86, 0xC2, 0x99, + 0x31, 0x58, 0xB2, 0x16, 0xBE, 0x8D, 0xAA, 0x7C, 0xF8, 0xC2, 0xF1, 0x93, + 0xBB, 0x06, 0x2B, 0xE8, 0x59, 0x77, 0x49, 0xDD, 0x71, 0x62, 0xC3, 0xBC, + 0x9F, 0x3E, 0x6D, 0x7B, 0x04, 0x4D, 0x96, 0x60, 0x86, 0xDB, 0xDB, 0x70, + 0xCA, 0x04, 0x2A, 0x21, 0xBA, 0xFF, 0x9A, 0x6D, 0xC8, 0x31, 0x04, 0xA0, + 0x1D, 0xE7, 0x34, 0xE5, 0xD6, 0x21, 0xF7, 0x55, 0x2B, 0xCE, 0x01, 0x62, + 0x4E, 0xCF, 0x2E, 0xAC, 0x5F, 0x6A, 0xAD, 0xE3, 0x72, 0xC1, 0x65, 0x34, + 0xF1, 0xB5, 0xF3, 0x37, 0xDE, 0x52, 0xB2, 0x89, 0x0C, 0xA5, 0x12, 0x96, + 0x46, 0xCE, 0x2E, 0x2E, 0x0C, 0xAA, 0x2E, 0xA2, 0x96, 0xFF, 0xDA, 0xDE, + 0x1A, 0x2D, 0x02, 0x65, 0x5A, 0xA1, 0x87, 0x3B, 0x09, 0x3C, 0x7D, 0x44, + 0xCD, 0x4A, 0x36, 0xEC, 0xA5, 0x25, 0x4D, 0x17, 0x49, 0x03, 0xD4, 0xEE, + 0xB7, 0x80, 0x8D, 0x70, 0xD7, 0x2F, 0x42, 0xE7, 0x65, 0x16, 0x99, 0x02, + 0xAF, 0x85, 0xC5, 0x91, 0x82, 0xBE, 0x37, 0x93, 0x5B, 0x2B, 0x2C, 0x6E, + 0x49, 0x71, 0x17, 0x4E, 0xBA, 0xA7, 0x64, 0x63, 0x16, 0x53, 0xF5, 0x66, + 0xB9, 0xE6, 0xCC, 0x49, 0xC6, 0xDE, 0x8C, 0xC6, 0x26, 0x85, 0xDD, 0x49, + 0x4E, 0xF6, 0x67, 0x93, 0xEA, 0xC5, 0xF2, 0x5B, 0x61, 0xC7, 0x3B, 0xBC, + 0x36, 0x51, 0xB1, 0x79, 0x7C, 0x17, 0xE9, 0x80, 0x3D, 0xE2, 0xDC, 0x99, + 0xB8, 0xA7, 0x74, 0x0C, 0xEB, 0xE0, 0xE6, 0xC6, 0x32, 0xA6, 0xE3, 0x03, + 0xB3, 0xE5, 0xEF, 0x33, 0x48, 0xE7, 0x03, 0x74, 0xEC, 0x55, 0x6C, 0x3C, + 0x7A, 0x93, 0x3D, 0x8C, 0x00, 0xD3, 0xB0, 0x58, 0x31, 0x89, 0x26, 0xC3, + 0x66, 0x8B, 0xDC, 0xAC, 0xCF, 0x46, 0x38, 0xBE, 0xE2, 0xB7, 0x4B, 0xB5, + 0xCB, 0xF4, 0x48, 0x64, 0x24, 0x48, 0x3D, 0x73, 0x89, 0x2F, 0x36, 0xC8, + 0x49, 0x09, 0xEC, 0x8C, 0x9E, 0x39, 0xDC, 0xA7, 0x41, 0x50, 0x7E, 0x8C, + 0xA4, 0x08, 0xB7, 0x54, 0xCB, 0x50, 0x63, 0xD2, 0xB6, 0x52, 0x27, 0xEA, + 0x80, 0x54, 0x2F, 0xB9, 0xE5, 0x30, 0x3B, 0x03, 0x07, 0x1A, 0x90, 0x14, + 0x72, 0x98, 0xC7, 0x99, 0xF8, 0x54, 0xA8, 0x57, 0xDB, 0x0F, 0x1A, 0x47, + 0x63, 0xE2, 0xDD, 0xC7, 0x98, 0x6A, 0xEC, 0x6C, 0xDA, 0x01, 0x92, 0xA8, + 0x62, 0x12, 0xD9, 0xBA, 0x36, 0x4C, 0x3B, 0x83, 0x39, 0xEE, 0xD7, 0x9C, + 0x7E, 0x66, 0x1D, 0x7A, 0xBC, 0xFF, 0x4D, 0x2E, 0xC0, 0x1A, 0x95, 0x31, + 0x29, 0xC2, 0xA2, 0xEA, 0x1E, 0xFA, 0x16, 0x70, 0xE1, 0xB6, 0x18, 0xF7, + 0x65, 0x90, 0x0C, 0x6C, 0xB0, 0x47, 0xB1, 0x5C, 0xE3, 0xC7, 0x66, 0xF5, + 0xEC, 0xE5, 0x79, 0x68, 0xC1, 0x72, 0x4F, 0xFC, 0xEA, 0x1A, 0xC5, 0xB3, + 0x0A, 0xC4, 0x81, 0xF1, 0xA2, 0x44, 0xCC, 0xE8, 0x57, 0x4A, 0xD2, 0xB2, + 0x71, 0x04, 0xA4, 0x1E, 0x21, 0xEA, 0x50, 0xFF, 0x62, 0x1B, 0x7C, 0xA6, + 0x8F, 0xE8, 0x38, 0x02, 0xC6, 0xE0, 0xFE, 0x08, 0x5A, 0x9B, 0x9C, 0x32, + 0x08, 0x60, 0xD8, 0xBE, 0x2A, 0xBA, 0x71, 0x53, 0x03, 0x84, 0x36, 0x45, + 0x59, 0x6D, 0xE4, 0x6C, 0x06, 0xFC, 0x45, 0x6B, 0xDE, 0x47, 0x89, 0x72, + 0xD5, 0x66, 0x62, 0xD4, 0xDA, 0x74, 0x91, 0xE9, 0x93, 0x2F, 0xEA, 0x78, + 0x8F, 0x0A, 0x7C, 0xFC, 0x77, 0xFA, 0x17, 0x68, 0x96, 0xD0, 0xC6, 0xF5, + 0x32, 0x70, 0xD3, 0xFA, 0xE6, 0x4A, 0x2A, 0x52, 0x1F, 0xEF, 0x20, 0x2E, + 0xE9, 0xBC, 0x58, 0xCF, 0x17, 0xCB, 0xC0, 0xF1, 0x46, 0x32, 0x02, 0x6F, + 0xB3, 0xA1, 0x72, 0x55, 0x58, 0x5B, 0x75, 0x94, 0xBA, 0xD7, 0xCB, 0xA5, + 0xFD, 0x82, 0xDF, 0x4E, 0x29, 0xCB, 0x4B, 0xD7, 0x5C, 0xCE, 0xA7, 0x69, + 0x10, 0xFF, 0xDD, 0xD0, 0xB5, 0x8E, 0xC0, 0x27, 0xE4, 0x5A, 0xD3, 0x4A, + 0xBB, 0x43, 0xEB, 0x6D, 0xF7, 0xEF, 0x57, 0x86, 0x3D, 0xC7, 0x42, 0xB2, + 0x8D, 0x62, 0x65, 0xEB, 0x7C, 0xD1, 0x76, 0xFE, 0x47, 0x65, 0x7F, 0xC8, + 0xF7, 0x29, 0x10, 0xDC, 0xEF, 0x94, 0xFF, 0x75, 0x07, 0xFA, 0x75, 0x22, + 0xCF, 0xAC, 0x66, 0xBC, 0x7D, 0xBB, 0x6B, 0x57, 0x37, 0xEA, 0x8A, 0x20, + 0x27, 0xC8, 0x8E, 0x5E, 0xCA, 0x33, 0x33, 0xB9, 0x5F, 0x42, 0x18, 0x9A, + 0x1C, 0xEC, 0x6D, 0x8E, 0x63, 0xCF, 0xD7, 0x1F, 0x01, 0xD5, 0x1C, 0x0B, + 0x7A, 0x12, 0xE3, 0x26, 0xC4, 0x0B, 0xE7, 0x7C, 0x5B, 0xF3, 0xC9, 0x9B, + 0xD3, 0x97, 0xF1, 0x2C, 0x25, 0xF4, 0x29, 0xE0, 0xAE, 0x61, 0xC1, 0x24, + 0xE2, 0xDD, 0x26, 0xCC, 0xD4, 0x7D, 0xCA, 0x27, 0x07, 0xE5, 0x64, 0xE3, + 0xE9, 0x95, 0x8C, 0x1E, 0xE1, 0xF7, 0x32, 0x3D, 0x13, 0xF5, 0x3F, 0xC9, + 0x93, 0x57, 0xC7, 0x0E, 0xC2, 0x41, 0x37, 0x53, 0x41, 0x74, 0x90, 0xFF, + 0x7C, 0xD3, 0x07, 0x14, 0x6A, 0xC3, 0x66, 0xA7, 0x25, 0x20, 0x2C, 0xC9, + 0x5E, 0x69, 0x79, 0x38, 0xAF, 0xF0, 0xDE, 0x86, 0x2F, 0x89, 0xB8, 0xCA, + 0x2C, 0xFC, 0x2F, 0x66, 0x3A, 0xF2, 0x8D, 0x1B, 0x4C, 0x0E, 0xF7, 0x42, + 0x1A, 0xF2, 0x3D, 0xD6, 0xFF, 0x5F, 0xA6, 0x31, 0x70, 0xFC, 0x90, 0x14, + 0xDC, 0x79, 0xE6, 0xDC, 0x09, 0xC2, 0x45, 0x50, 0xF3, 0x6E, 0x25, 0xED, + 0x50, 0xDB, 0xDE, 0xBE, 0x3E, 0xF7, 0xBE, 0xF2, 0xA1, 0xCE, 0x08, 0xF6, + 0xB1, 0x49, 0xED, 0xC5, 0xB3, 0xF7, 0x4F, 0x6D, 0x1C, 0x70, 0x62, 0xFC, + 0xAF, 0x03, 0x0B, 0x33, 0x95, 0x53, 0x48, 0x78, 0x48, 0x7B, 0x0A, 0x3E, + 0xC3, 0xEC, 0xF8, 0x7F, 0xF6, 0x13, 0x29, 0x9E, 0xCE, 0x63, 0x25, 0xE9, + 0x68, 0x75, 0xCF, 0xBC, 0x3C, 0xFF, 0x4C, 0x6E, 0x6C, 0xD7, 0x7D, 0xF0, + 0xF5, 0xD1, 0xEF, 0x5F, 0x98, 0xE4, 0x53, 0x52, 0x34, 0x7E, 0xF6, 0x40, + 0xE6, 0x7D, 0xC9, 0x67, 0x8C, 0xE5, 0x3E, 0xC8, 0xF7, 0x10, 0x07, 0x04, + 0x76, 0xBC, 0xDB, 0xB1, 0xE9, 0xF5, 0xAF, 0xCE, 0xA7, 0x21, 0xBF, 0xE7, + 0xE8, 0x3F, 0xAA, 0xEA, 0x81, 0x54, 0x69, 0xBD, 0x0F, 0x36, 0x46, 0xB6, + 0xA5, 0xDD, 0xB2, 0x3E, 0x03, 0xFE, 0xDF, 0x2D, 0xCD, 0xE0, 0x3D, 0xEA, + 0xE8, 0x25, 0xA0, 0xCE, 0x0A, 0x9B, 0x1C, 0x99, 0xE7, 0x52, 0x46, 0xF2, + 0x46, 0x11, 0x1F, 0x47, 0x7B, 0xF4, 0xC0, 0x7B, 0x75, 0x68, 0x5E, 0xD1, + 0x32, 0x20, 0xEB, 0xFA, 0x21, 0xBD, 0x6A, 0xB1, 0x79, 0x2C, 0x02, 0x3B, + 0x98, 0x6B, 0x27, 0xD4, 0x49, 0x85, 0xD7, 0x19, 0x53, 0x2D, 0xF3, 0xAA, + 0x0B, 0xED, 0x6B, 0x5B, 0x5E, 0x6E, 0x7C, 0xC9, 0x3D, 0xCB, 0xC4, 0xCA, + 0x7E, 0xC9, 0x86, 0xA9, 0xF1, 0x9D, 0xB1, 0x7A, 0xE4, 0x49, 0xFB, 0xBE, + 0x9A, 0x28, 0x83, 0x1C, 0xF3, 0x80, 0x9E, 0x15, 0x33, 0xEB, 0xBE, 0x63, + 0xE2, 0x3C, 0xDC, 0xFE, 0x79, 0x8F, 0x04, 0xAB, 0xDA, 0x36, 0xA5, 0x27, + 0xBF, 0x90, 0x3A, 0xCB, 0xA7, 0xBA, 0xAC, 0x98, 0xA0, 0x36, 0x88, 0x7B, + 0xED, 0x2A, 0xF7, 0xDF, 0x78, 0xD0, 0x16, 0x86, 0x31, 0xEC, 0xA4, 0x29, + 0xCC, 0x72, 0xE9, 0x68, 0x50, 0x54, 0x43, 0x7E, 0xB7, 0xEE, 0x54, 0xBA, + 0xB8, 0xD0, 0x44, 0x08, 0x4B, 0xBB, 0xB6, 0x6C, 0x96, 0x62, 0xD7, 0xAE, + 0x61, 0xCE, 0x61, 0x9F, 0x89, 0xDD, 0x54, 0xB6, 0x69, 0xBC, 0xF6, 0xC5, + 0x72, 0x03, 0x32, 0x71, 0xB6, 0xD7, 0x7D, 0x48, 0x20, 0x18, 0x30, 0x68, + 0xD9, 0x58, 0x7B, 0x8B, 0xAE, 0x59, 0x5D, 0x98, 0xA6, 0x2F, 0x92, 0x47, + 0x2D, 0xDD, 0x94, 0x4E, 0x65, 0xC1, 0x57, 0x8D, 0xC7, 0xE9, 0x15, 0x13, + 0xB2, 0x58, 0x30, 0x5A, 0x6B, 0x87, 0x75, 0x9B, 0xA7, 0xD4, 0x0A, 0x88, + 0x02, 0x62, 0x22, 0xB6, 0x04, 0xB3, 0xC8, 0xC3, 0x1F, 0x82, 0x96, 0x79, + 0x08, 0xBF, 0xE9, 0xA6, 0xEB, 0x09, 0x0B, 0x18, 0x57, 0x7A, 0x7B, 0x49, + 0x7E, 0xCB, 0xF2, 0x5D, 0x2C, 0xC7, 0xD9, 0xE1, 0x26, 0x6F, 0x56, 0x87, + 0x15, 0x99, 0xC2, 0x76, 0xDF, 0x56, 0xA5, 0xB3, 0x2E, 0xAD, 0xFC, 0xDF, + 0xB4, 0xB3, 0x1B, 0xF3, 0xB9, 0xA1, 0xA8, 0x65, 0x14, 0xDE, 0x25, 0xE3, + 0x5C, 0x0B, 0x80, 0x69, 0x65, 0x77, 0xC6, 0x78, 0xDC, 0x29, 0x87, 0x2C, + 0x24, 0xBF, 0xEB, 0xD1, 0x24, 0x69, 0x56, 0x5C, 0xFC, 0x67, 0x3B, 0xB2, + 0x77, 0x27, 0x6F, 0xBF, 0x80, 0x8E, 0xFB, 0x2D, 0x96, 0xC1, 0xC2, 0xAF, + 0xEF, 0xF4, 0x3A, 0x8E, 0x2E, 0xCD, 0x59, 0x3E, 0x07, 0xF3, 0xED, 0xC4, + 0x2E, 0xAF, 0x4B, 0xD4, 0x27, 0x86, 0x38, 0xEB, 0xC2, 0x8B, 0xDF, 0x7B, + 0xBD, 0x9C, 0x91, 0xC1, 0xF7, 0x03, 0x45, 0x77, 0x02, 0x5A, 0xD8, 0x3E, + 0x89, 0x54, 0x20, 0xA0, 0x4D, 0x49, 0x3A, 0xA7, 0x58, 0x4E, 0x19, 0xE6, + 0xC0, 0x69, 0x69, 0x0B, 0x72, 0x28, 0xEE, 0x0C, 0xB0, 0xC0, 0x1B, 0xC6, + 0x80, 0x3F, 0x5E, 0x71, 0xCC, 0x85, 0xA1, 0x51, 0x16, 0xB7, 0x9D, 0x08, + 0x59, 0xFB, 0xE1, 0x87, 0x1E, 0xA0, 0x63, 0x93, 0x84, 0xFC, 0x0F, 0x83, + 0x54, 0xD1, 0x99, 0x03, 0x64, 0xCD, 0x51, 0x28, 0x28, 0x95, 0x9B, 0xD8, + 0x87, 0xA6, 0x86, 0xC4, 0x82, 0x62, 0x58, 0xBE, 0x03, 0x9B, 0x2E, 0xE4, + 0xBF, 0xB4, 0xB5, 0xEF, 0x1E, 0xA5, 0xD0, 0xDF, 0xAD, 0x91, 0x4D, 0xB6, + 0x4C, 0x01, 0x84, 0x08, 0x85, 0x08, 0xA4, 0x08, 0xE7, 0xA7, 0x58, 0x4A, + 0x6D, 0xE7, 0x23, 0xDC, 0x2A, 0x23, 0x4F, 0xB9, 0x50, 0x94, 0x73, 0xB4, + 0x44, 0x6A, 0x79, 0xFA, 0x92, 0x65, 0xBD, 0xE8, 0x68, 0x78, 0x82, 0x12, + 0x39, 0x9E, 0x9F, 0x2C, 0x70, 0x02, 0x3D, 0xDE, 0xFA, 0x77, 0x7E, 0x4A, + 0x35, 0x8F, 0x35, 0xFF, 0xF7, 0x68, 0x5C, 0x9C, 0x9B, 0x3F, 0x1F, 0x9A, + 0xA7, 0x97, 0xA5, 0x71, 0xA9, 0xA9, 0xDA, 0xDC, 0xA6, 0xFA, 0xA0, 0x25, + 0x71, 0xB4, 0xB7, 0xCD, 0x54, 0x81, 0x3E, 0xA0, 0xED, 0xFD, 0x6D, 0xA7, + 0x6B, 0x41, 0xA8, 0x17, 0x17, 0x94, 0x96, 0x52, 0xB9, 0xCE, 0xA4, 0x12, + 0x6A, 0x45, 0x52, 0x41, 0x99, 0xF8, 0xD7, 0x2F, 0xB9, 0x88, 0x6A, 0xB6, + 0x97, 0x89, 0xE7, 0xA5, 0xEE, 0x66, 0x70, 0xF8, 0xD0, 0x9B, 0xE7, 0x4A, + 0x45, 0xAE, 0xEF, 0x5D, 0xDD, 0x3D, 0x0E, 0x64, 0x7A, 0xC5, 0x5C, 0x5A, + 0x07, 0x69, 0xE4, 0x81, 0xBF, 0x34, 0xAF, 0x57, 0xE0, 0x4B, 0xB1, 0x55, + 0x4E, 0xE5, 0xE5, 0xC1, 0x99, 0x7A, 0xCF, 0xD5, 0x3E, 0xEF, 0x69, 0x9E, + 0x7B, 0x71, 0xF6, 0x65, 0xBB, 0xFE, 0x7C, 0xD5, 0xDB, 0x57, 0xD1, 0x5F, + 0xAD, 0xAE, 0x26, 0xF2, 0xCA, 0x7B, 0x08, 0x3C, 0xE6, 0xDA, 0xDD, 0xE8, + 0xBA, 0x28, 0x83, 0xCA, 0x0E, 0x08, 0x00, 0x06, 0x8E, 0x12, 0x1C, 0x0D, + 0x59, 0x17, 0xA4, 0x50, 0x86, 0x19, 0x80, 0xF0, 0xC4, 0xFF, 0xA3, 0xC7, + 0xD1, 0x8C, 0xAF, 0x22, 0x26, 0x23, 0xC6, 0xF5, 0xEB, 0xFA, 0xC8, 0x3B, + 0x9C, 0x9C, 0xAB, 0x65, 0x3F, 0x74, 0x86, 0xD0, 0x91, 0x5B, 0x77, 0xD0, + 0xE9, 0xFD, 0x61, 0xDB, 0x5B, 0x68, 0x88, 0xDB, 0x3F, 0x78, 0x5C, 0x83, + 0xB1, 0x71, 0xF9, 0xBE, 0x84, 0xC7, 0x4D, 0x3B, 0xB1, 0x74, 0xE2, 0x56, + 0xD9, 0xB8, 0x57, 0x3C, 0x86, 0x36, 0x3C, 0x3D, 0xAC, 0xF5, 0xCD, 0xBE, + 0x9A, 0x3B, 0x9B, 0xA4, 0x7B, 0x87, 0xE4, 0xB6, 0x37, 0x32, 0xD3, 0xF0, + 0x88, 0x68, 0x8F, 0x8F, 0xC8, 0x04, 0x83, 0xF8, 0x14, 0x29, 0x67, 0x46, + 0x06, 0x83, 0xE1, 0x8C, 0x98, 0x94, 0x13, 0x69, 0x6C, 0xD0, 0x3D, 0x91, + 0x89, 0xA7, 0xCF, 0xBE, 0x97, 0x79, 0x64, 0xBE, 0x14, 0x62, 0xC4, 0x5F, + 0x55, 0x85, 0x8D, 0x7E, 0x3A, 0x4D, 0xF8, 0x16, 0xA7, 0xEC, 0xB3, 0x50, + 0xF0, 0xC0, 0x15, 0x3C, 0x81, 0xDB, 0x6C, 0xB8, 0xB2, 0xCE, 0xD2, 0xF2, + 0x22, 0x3D, 0x3B, 0xDA, 0x18, 0xC9, 0xD2, 0x23, 0xD4, 0x5A, 0x13, 0x26, + 0x87, 0x7B, 0xCD, 0x4C, 0x74, 0x4C, 0x7A, 0x30, 0x6D, 0x87, 0x44, 0xC6, + 0x4D, 0x3A, 0x54, 0xD6, 0x49, 0xEE, 0xD3, 0x9B, 0x62, 0x7E, 0x23, 0xAB, + 0x1C, 0x54, 0xE5, 0xAC, 0xB1, 0x37, 0xC6, 0x02, 0x53, 0x72, 0xDB, 0xA4, + 0x27, 0xFE, 0x28, 0xF9, 0xA8, 0x32, 0x36, 0x78, 0x22, 0x95, 0x19, 0xE2, + 0xFE, 0x25, 0x25, 0x1F, 0xB6, 0x5B, 0xCE, 0xB0, 0xDE, 0xA8, 0xD2, 0xFF, + 0x3D, 0xB7, 0xC6, 0x19, 0x74, 0x67, 0x46, 0x14, 0xB7, 0xDA, 0x12, 0x92, + 0xE4, 0x3C, 0xB7, 0x7D, 0x6E, 0x34, 0x45, 0x92, 0xDA, 0x27, 0xB9, 0xCD, + 0x90, 0x8F, 0x4C, 0x5D, 0x88, 0x81, 0xDA, 0xCF, 0x4F, 0x45, 0x8F, 0xF5, + 0xEF, 0x18, 0xA1, 0x70, 0xC8, 0xB5, 0xB5, 0xFE, 0x84, 0xBE, 0x47, 0x56, + 0xA9, 0xDB, 0x1F, 0x3E, 0x59, 0xBC, 0x2B, 0x15, 0xE1, 0x7C, 0x45, 0x0F, + 0xF5, 0x13, 0x38, 0x2A, 0x4F, 0x7E, 0x99, 0x64, 0xB0, 0x69, 0xDF, 0x46, + 0x63, 0xCE, 0xD4, 0xAA, 0x57, 0x09, 0xE5, 0xD4, 0x31, 0x84, 0x00, 0x04, + 0x1E, 0x3C, 0x19, 0x9E, 0xED, 0xAB, 0x01, 0xCD, 0x9E, 0x3E, 0x3E, 0x14, + 0xD3, 0x62, 0x33, 0xC1, 0xB1, 0x94, 0x60, 0xB5, 0x78, 0xDF, 0x04, 0xE7, + 0xB6, 0xFE, 0xCD, 0x71, 0xFF, 0x55, 0x1B, 0x26, 0x6A, 0x71, 0x93, 0x31, + 0xB4, 0x30, 0x29, 0x6D, 0x18, 0x0A, 0x97, 0x52, 0xB8, 0x00, 0x29, 0xDD, + 0x8D, 0x20, 0xBE, 0x42, 0xA2, 0xF3, 0x62, 0x87, 0x9C, 0x53, 0x19, 0xA4, + 0x17, 0x30, 0x0C, 0x01, 0xE8, 0x6E, 0xEC, 0xD4, 0xC0, 0x79, 0x16, 0x78, + 0xF0, 0xAE, 0x07, 0x1F, 0x60, 0x57, 0xCC, 0x9C, 0xAE, 0x81, 0x0D, 0x18, + 0xB8, 0x40, 0x62, 0x61, 0x68, 0x0D, 0x23, 0x4C, 0x94, 0x40, 0x0C, 0x16, + 0x0C, 0x55, 0x80, 0x29, 0xC1, 0x04, 0x5A, 0xBE, 0x88, 0xF9, 0x69, 0xC4, + 0x26, 0xFD, 0xAE, 0xE2, 0x3D, 0x49, 0x5E, 0x4C, 0xC6, 0x7B, 0x7F, 0xF7, + 0xFF, 0xAB, 0x97, 0xD8, 0x4C, 0xCA, 0xC1, 0x3D, 0xB0, 0xA0, 0x53, 0x68, + 0x51, 0x5F, 0x29, 0x4A, 0x49, 0xF4, 0xB2, 0xD7, 0xD2, 0x7A, 0xDA, 0x56, + 0x00, 0xDD, 0x57, 0xA0, 0x54, 0xAF, 0x44, 0x1D, 0xBF, 0x55, 0x24, 0x55, + 0x98, 0xAE, 0x5C, 0xFE, 0x73, 0xC4, 0x1F, 0x1E, 0x0A, 0xFD, 0x92, 0x65, + 0x83, 0xA1, 0xA0, 0x30, 0x42, 0xE1, 0x5F, 0xF9, 0xF4, 0xE0, 0x89, 0x96, + 0x2E, 0x90, 0x87, 0x52, 0xF5, 0x6C, 0x55, 0xC5, 0x73, 0xE2, 0x3E, 0xBD, + 0x0F, 0x75, 0x6F, 0xF6, 0xFF, 0xA0, 0xB7, 0xBF, 0x47, 0x8D, 0xA0, 0x2C, + 0xBC, 0xFF, 0x16, 0x65, 0x46, 0xFB, 0xFE, 0x67, 0xDE, 0x87, 0xE0, 0xCE, + 0xDA, 0xF4, 0x70, 0xB6, 0xDA, 0xD0, 0xB4, 0xC5, 0x40, 0xEE, 0x3D, 0xB3, + 0x41, 0xDA, 0x2C, 0xBF, 0xA4, 0x20, 0x98, 0xAD, 0x80, 0xB8, 0xD9, 0x9D, + 0x04, 0x49, 0x1F, 0x57, 0xBD, 0x4B, 0xAB, 0xA8, 0x56, 0xEB, 0xBB, 0x54, + 0x74, 0xB1, 0x50, 0x5E, 0x5D, 0xF4, 0x7B, 0xBD, 0xFE, 0x1D, 0x84, 0x90, + 0x3E, 0x5A, 0x36, 0xFE, 0x3D, 0x79, 0xEE, 0xA7, 0x50, 0x10, 0x8E, 0x2D, + 0xA5, 0x75, 0x43, 0x5D, 0xAC, 0xDE, 0x4F, 0xCF, 0x42, 0xFA, 0xE5, 0xB8, + 0x29, 0x01, 0x17, 0x20, 0x14, 0x61, 0x02, 0x50, 0xE0, 0x88, 0x01, 0x0E, + 0x3A, 0xEC, 0xAC, 0x6A, 0xFD, 0xA7, 0x67, 0xD2, 0xC9, 0x2E, 0xCF, 0x5C, + 0xEC, 0x6D, 0x72, 0x7B, 0xEC, 0x76, 0x2D, 0xB1, 0xAB, 0xE2, 0x39, 0x77, + 0xE3, 0x65, 0x62, 0x26, 0xAA, 0x3E, 0xFB, 0x17, 0x00, 0xE3, 0x94, 0x49, + 0x37, 0x63, 0x83, 0xC3, 0xA9, 0xEE, 0xFF, 0x8C, 0xA5, 0xF4, 0xB5, 0x52, + 0x14, 0x2D, 0xCD, 0x8E, 0x7B, 0xFD, 0xBD, 0x09, 0x26, 0x87, 0xCB, 0x98, + 0xF8, 0x4D, 0xBC, 0xEF, 0x6D, 0xFA, 0xA0, 0x38, 0x0F, 0xA3, 0xC0, 0xCB, + 0x6F, 0xE3, 0xED, 0x97, 0x61, 0xEA, 0xB0, 0x25, 0x78, 0xF7, 0xFB, 0xEF, + 0xB1, 0xD8, 0xBF, 0x7A, 0x7F, 0x96, 0xA2, 0xBF, 0x82, 0x10, 0x17, 0x5C, + 0xEB, 0x6C, 0xCA, 0x66, 0xA2, 0x23, 0xE4, 0xE5, 0xA7, 0xC0, 0x15, 0x3F, + 0x12, 0xF5, 0x67, 0xF9, 0xAF, 0xA5, 0xFE, 0xDB, 0x74, 0x36, 0x3E, 0x1D, + 0xE7, 0x85, 0xFE, 0xA7, 0x27, 0xEA, 0x61, 0x2F, 0x6A, 0x5E, 0xD9, 0x22, + 0x1A, 0x4C, 0xA6, 0x83, 0xBA, 0x9E, 0x15, 0x70, 0xA8, 0x4E, 0x70, 0x80, + 0xCD, 0xB4, 0xE3, 0xDD, 0xA1, 0x79, 0x73, 0x2C, 0xD4, 0x1D, 0xF9, 0xB7, + 0xD0, 0x47, 0xCE, 0x04, 0x86, 0xD4, 0x29, 0x63, 0xCB, 0xC0, 0x38, 0x4E, + 0x19, 0x13, 0xE6, 0xFB, 0xFF, 0x07, 0xFC, 0x0A, 0x11, 0xE2, 0x43, 0x16, + 0xC6, 0x32, 0x58, 0x95, 0x53, 0x86, 0xF7, 0x8C, 0xD3, 0xE9, 0x5B, 0x16, + 0x02, 0x7F, 0x0A, 0xD0, 0xFF, 0x09, 0xD3, 0x17, 0x2C, 0x53, 0xE7, 0x00, + 0xEC, 0x1E, 0x76, 0x8F, 0x15, 0x8F, 0x1E, 0x26, 0xAF, 0xA7, 0x3E, 0x75, + 0x9B, 0x83, 0xC3, 0x54, 0x9E, 0x7F, 0x1F, 0x82, 0xBC, 0x84, 0x87, 0x08, + 0x56, 0x90, 0x34, 0x3E, 0xE8, 0x67, 0x58, 0x2E, 0x4F, 0xCE, 0x23, 0x99, + 0xA5, 0xFB, 0xBF, 0x94, 0x11, 0x46, 0x18, 0xD1, 0x49, 0x1C, 0x87, 0x43, + 0xE2, 0x08, 0x0F, 0x08, 0xD5, 0x25, 0x96, 0x58, 0x82, 0xDA, 0x7A, 0x2B, + 0xCE, 0xEA, 0x7D, 0x93, 0x20, 0x1C, 0xFE, 0x99, 0x4F, 0x5D, 0xF8, 0xB7, + 0x98, 0x4A, 0x7E, 0x64, 0x4A, 0x4B, 0xB7, 0x87, 0xC9, 0x17, 0x1B, 0x97, + 0x22, 0x22, 0x6B, 0x8C, 0x5C, 0xCD, 0x4C, 0x5E, 0x86, 0xDD, 0x94, 0xE9, + 0x0B, 0x6E, 0xDB, 0x84, 0x44, 0xA1, 0x20, 0x2F, 0xB8, 0x2D, 0xA5, 0xF2, + 0xE2, 0xD2, 0x4F, 0x6F, 0x68, 0x7A, 0x77, 0x5E, 0x89, 0xDE, 0xB2, 0x5F, + 0x08, 0x93, 0x67, 0xFA, 0xE2, 0xA5, 0xA7, 0xE7, 0x44, 0x0C, 0x67, 0x6D, + 0x12, 0x86, 0xD9, 0xFB, 0x58, 0xF2, 0xB2, 0xEA, 0xFC, 0x8F, 0x1C, 0xF9, + 0x4D, 0x73, 0xFC, 0xB7, 0x22, 0x77, 0xD3, 0xC8, 0xB7, 0x1F, 0xC2, 0xBD, + 0xEA, 0x5C, 0x13, 0x91, 0xE9, 0xE5, 0xE4, 0x68, 0x3B, 0x18, 0x47, 0xA9, + 0xE9, 0xD3, 0x78, 0x38, 0x6D, 0xC8, 0x96, 0xD9, 0x74, 0xC1, 0x59, 0x9E, + 0x78, 0x53, 0x17, 0x6C, 0x15, 0xB3, 0x29, 0x35, 0xC7, 0xB8, 0x14, 0xBB, + 0x36, 0x28, 0x6A, 0xF2, 0x5A, 0x56, 0xDB, 0x0B, 0xCA, 0xA5, 0x4C, 0xBA, + 0xE0, 0x6A, 0x6A, 0x02, 0xA5, 0x68, 0xD0, 0xD5, 0x54, 0x4F, 0x35, 0xC6, + 0xCD, 0x2C, 0xC3, 0xB8, 0xA6, 0x9A, 0xE2, 0xE1, 0x47, 0xC1, 0x52, 0xAD, + 0x72, 0x7A, 0x0E, 0x22, 0x19, 0x82, 0xFD, 0x4B, 0xFA, 0xFF, 0x58, 0x8C, + 0x37, 0x51, 0x41, 0x7D, 0x74, 0x2A, 0xB2, 0x6C, 0x94, 0x16, 0xED, 0x89, + 0x0C, 0x7F, 0x5F, 0xF2, 0xC6, 0x2E, 0x1D, 0x15, 0xBB, 0x5E, 0x73, 0x4D, + 0x98, 0xAB, 0xA3, 0xE4, 0xF2, 0xED, 0xA4, 0x5E, 0x0C, 0x19, 0x13, 0xAA, + 0x72, 0x16, 0xBA, 0x71, 0xE7, 0x8A, 0x89, 0x11, 0xBF, 0x9D, 0x50, 0xEE, + 0x40, 0xE7, 0x98, 0xB6, 0x68, 0x5E, 0xD6, 0xD1, 0x97, 0xC8, 0xFA, 0xE2, + 0x52, 0x45, 0x28, 0xD9, 0x4F, 0x4B, 0x2A, 0x54, 0x04, 0xC3, 0xD2, 0xF2, + 0xF3, 0x17, 0x7D, 0x5C, 0xE5, 0xF8, 0x27, 0x25, 0x54, 0xC4, 0x4B, 0x94, + 0x5C, 0x74, 0x8D, 0x8B, 0xAF, 0xAC, 0x94, 0x8D, 0xA6, 0x41, 0xFC, 0x11, + 0x79, 0x64, 0x95, 0x3F, 0xE8, 0x23, 0x2A, 0x55, 0x22, 0xAF, 0xA3, 0x24, + 0xE7, 0x56, 0xDB, 0xA8, 0x5B, 0x6B, 0x5F, 0xE5, 0x4F, 0xBE, 0xBD, 0xB7, + 0x97, 0xEE, 0xC7, 0xC5, 0x51, 0x3D, 0x2B, 0x81, 0x08, 0xDC, 0x3F, 0x7E, + 0xCD, 0x3B, 0x95, 0x3F, 0xFC, 0x81, 0x33, 0x9A, 0x30, 0x33, 0x5B, 0x77, + 0x5D, 0x97, 0x89, 0x5E, 0xD9, 0xC7, 0xCD, 0xAF, 0x54, 0xAE, 0x78, 0xFD, + 0x57, 0x3D, 0xF9, 0x3E, 0x2E, 0xC6, 0x67, 0xD8, 0x8B, 0xC5, 0xE2, 0x7D, + 0x67, 0x6F, 0x34, 0xBD, 0xAF, 0x70, 0x25, 0x15, 0x1A, 0x27, 0x18, 0x92, + 0x13, 0xD5, 0x20, 0xD1, 0x49, 0xB7, 0xFE, 0xDB, 0xAB, 0x68, 0xDD, 0x18, + 0xE3, 0xAF, 0xD1, 0x8B, 0x21, 0x8A, 0xCB, 0x4A, 0x63, 0xE4, 0x8D, 0xEA, + 0x0F, 0x6E, 0x79, 0x24, 0xA2, 0x28, 0x6E, 0xD5, 0xD1, 0xC5, 0xF7, 0xAC, + 0x5A, 0xCF, 0x05, 0xC8, 0x7F, 0xBF, 0xA3, 0x46, 0xB4, 0xF7, 0xBB, 0x74, + 0x2F, 0x27, 0xB5, 0xD4, 0xD3, 0xD7, 0x30, 0x76, 0xCA, 0xA8, 0x31, 0x06, + 0x13, 0x60, 0x18, 0xA0, 0x17, 0xA0, 0x50, 0xA2, 0x01, 0x1C, 0x50, 0x21, + 0x00, 0x92, 0x93, 0xFC, 0x9F, 0xF1, 0x5F, 0x42, 0x4B, 0x74, 0x91, 0x25, + 0x43, 0x83, 0x84, 0xFD, 0x97, 0xFC, 0x47, 0x26, 0xAB, 0xB3, 0xDC, 0x60, + 0x5B, 0xAA, 0x8F, 0xE2, 0x8E, 0x32, 0x36, 0x1E, 0x7B, 0x43, 0x1E, 0x6A, + 0x76, 0x77, 0xB6, 0xDC, 0x04, 0x03, 0x79, 0xAA, 0x41, 0xCD, 0x9B, 0x39, + 0x73, 0x2D, 0xD0, 0x27, 0x9F, 0xB8, 0xA9, 0x53, 0x79, 0xC2, 0x24, 0x73, + 0x4E, 0xB3, 0xE9, 0x21, 0x79, 0x62, 0xFE, 0xC4, 0xED, 0xD9, 0xCF, 0xEC, + 0x21, 0x68, 0xF3, 0x1B, 0x99, 0x0D, 0x04, 0x55, 0x4B, 0x9E, 0x68, 0xBA, + 0x82, 0xE6, 0xF5, 0x6F, 0x8C, 0x5E, 0x35, 0x73, 0xAD, 0xEF, 0x9B, 0xCF, + 0xE3, 0xDD, 0x0C, 0x08, 0x1A, 0x0F, 0xCC, 0x7A, 0xE0, 0x84, 0x39, 0x49, + 0xC6, 0x35, 0x86, 0xC8, 0x29, 0xD3, 0x83, 0x21, 0x92, 0x7A, 0xBF, 0xF7, + 0x4E, 0xF3, 0x3B, 0xD5, 0xEA, 0x99, 0x52, 0xDD, 0x2D, 0x2F, 0x47, 0xED, + 0x03, 0xB2, 0xAA, 0x9A, 0x1C, 0x20, 0x7B, 0x95, 0x4E, 0x8E, 0xBD, 0x71, + 0xE3, 0xE7, 0xF0, 0x79, 0xB0, 0xB3, 0xFC, 0xDE, 0x48, 0xA6, 0x2A, 0x72, + 0xE7, 0x2C, 0x72, 0x69, 0x37, 0x66, 0xEA, 0x15, 0x2B, 0xA2, 0xEC, 0xFF, + 0x9F, 0xDE, 0x05, 0xE2, 0x52, 0x45, 0xB3, 0x69, 0x47, 0x58, 0x57, 0xF9, + 0x6C, 0xB3, 0x5F, 0xE2, 0x28, 0xC7, 0x36, 0xDE, 0x55, 0x29, 0x71, 0xA6, + 0xAE, 0x74, 0x35, 0xEF, 0xEA, 0xAE, 0xFB, 0xF0, 0x72, 0x47, 0xA3, 0xCB, + 0xA0, 0x87, 0xFE, 0x4E, 0x94, 0x15, 0x61, 0xB5, 0xD4, 0xBC, 0x1B, 0xE7, + 0x92, 0xF1, 0xCD, 0x24, 0xAE, 0xDE, 0xAA, 0x24, 0x05, 0x52, 0x49, 0x0A, + 0xB0, 0x7E, 0x84, 0x8C, 0x35, 0xE0, 0xA4, 0x6A, 0x5E, 0x7B, 0x22, 0x64, + 0xFA, 0x43, 0x27, 0xD5, 0xFD, 0x3A, 0x7C, 0x4C, 0xA0, 0x51, 0xAD, 0x3F, + 0xF1, 0x3F, 0xD2, 0xE3, 0x6C, 0xD0, 0xBD, 0x09, 0xCF, 0x88, 0x19, 0x8B, + 0x03, 0xFD, 0xB3, 0xAE, 0x6A, 0x1B, 0x3C, 0x8B, 0x30, 0x5C, 0x63, 0x42, + 0x5E, 0xFA, 0x4B, 0x91, 0xA9, 0xCA, 0x77, 0xD3, 0x40, 0xEE, 0xDD, 0xC9, + 0x3E, 0x42, 0x9A, 0xC3, 0x9D, 0x26, 0x17, 0x67, 0xDB, 0x7D, 0x7F, 0x81, + 0x13, 0x8C, 0xB3, 0xD4, 0xC8, 0x9D, 0x4E, 0xEA, 0x48, 0xAD, 0x71, 0x24, + 0x72, 0x1D, 0x89, 0xD9, 0x37, 0x12, 0x53, 0x3F, 0xFE, 0x3B, 0x32, 0x3B, + 0xCE, 0xEE, 0x85, 0x5F, 0x1B, 0x42, 0x58, 0x62, 0x78, 0x42, 0xE1, 0x0F, + 0x49, 0x92, 0x21, 0x7C, 0x43, 0xFB, 0xFB, 0x6A, 0x6F, 0x63, 0xDB, 0xA6, + 0xF2, 0x5A, 0x1D, 0x95, 0x46, 0xEB, 0x51, 0xC6, 0x7E, 0x5F, 0xDD, 0xFA, + 0xDF, 0x6A, 0x79, 0xB9, 0x8C, 0xFD, 0x8F, 0x40, 0x9B, 0x81, 0x4A, 0xCE, + 0x55, 0x8E, 0xBA, 0x2A, 0x86, 0x1E, 0x0B, 0x72, 0x87, 0x03, 0x8C, 0x57, + 0xFD, 0x2D, 0x20, 0xD0, 0xE1, 0x4B, 0x7D, 0x04, 0xEA, 0xBB, 0x1D, 0x91, + 0x37, 0xB0, 0x06, 0xFB, 0xC8, 0x42, 0x2F, 0x49, 0x12, 0xAF, 0x13, 0x4A, + 0x47, 0xA8, 0x27, 0x14, 0x3F, 0x9F, 0x58, 0x3D, 0x1E, 0x4F, 0x27, 0x5E, + 0xD4, 0xEB, 0x6A, 0x68, 0x66, 0xE4, 0x9B, 0x14, 0xB3, 0xC3, 0x92, 0xB8, + 0x54, 0x12, 0x5D, 0x45, 0xD2, 0x49, 0x27, 0xA9, 0x82, 0x47, 0xCC, 0x0E, + 0x9B, 0x23, 0xD6, 0x78, 0x71, 0xA9, 0x2B, 0xA5, 0xAB, 0x8A, 0x1A, 0x94, + 0x24, 0x55, 0x05, 0x16, 0x6B, 0x54, 0x11, 0xE5, 0x6B, 0x9C, 0xE4, 0xC4, + 0x5E, 0xB6, 0x25, 0x3F, 0x62, 0x2C, 0x89, 0xD1, 0xD6, 0xF8, 0xE3, 0xB1, + 0xE4, 0x48, 0x68, 0xEB, 0xF9, 0x70, 0x14, 0xC2, 0x1C, 0xB7, 0x7A, 0x9E, + 0x17, 0x45, 0x22, 0x7B, 0x29, 0xB4, 0x1E, 0x15, 0xCF, 0x8C, 0x59, 0x63, + 0xEA, 0x29, 0xB4, 0x31, 0x48, 0xB2, 0x4F, 0x34, 0x45, 0x5A, 0x27, 0xD2, + 0xBC, 0x19, 0x99, 0x57, 0x33, 0xBB, 0x28, 0x85, 0xC9, 0x7E, 0x42, 0x41, + 0x40, 0x5F, 0x9C, 0x53, 0x8C, 0xA2, 0x17, 0x33, 0x29, 0x46, 0x91, 0x7E, + 0x31, 0xD6, 0x30, 0x15, 0x35, 0xA3, 0x18, 0xFF, 0xF8, 0xCA, 0xE3, 0x54, + 0xE6, 0xF7, 0x29, 0x1E, 0x3F, 0xF3, 0x78, 0x85, 0x27, 0x83, 0x33, 0x9D, + 0x27, 0xD2, 0xAE, 0xE3, 0x89, 0xEF, 0xEC, 0xCF, 0xC9, 0xB8, 0x47, 0x42, + 0x42, 0x22, 0x63, 0x52, 0xEA, 0x46, 0x38, 0x23, 0x7A, 0xD1, 0x8A, 0x49, + 0x96, 0x8C, 0xFF, 0xFD, 0x96, 0x92, 0xA5, 0x55, 0x33, 0xA8, 0xEA, 0x7D, + 0xA8, 0x28, 0x70, 0x5C, 0x37, 0xCA, 0xD2, 0xC4, 0xFC, 0x42, 0xC5, 0xC3, + 0xB1, 0xFD, 0x3C, 0xF7, 0x21, 0x93, 0xD7, 0xA9, 0xBA, 0xA1, 0xEC, 0x1B, + 0x9A, 0xDE, 0x59, 0xFA, 0x33, 0x7D, 0xA8, 0x6C, 0x9E, 0xF0, 0xDF, 0xE1, + 0x5C, 0x8D, 0x5A, 0x33, 0x33, 0xCE, 0x3B, 0x38, 0xA0, 0x19, 0x3A, 0x8E, + 0xE5, 0xDC, 0x29, 0x6F, 0xE3, 0xCD, 0x88, 0xBF, 0xD9, 0x7B, 0x35, 0x23, + 0x49, 0x2D, 0x49, 0x32, 0x1C, 0x92, 0x49, 0xC2, 0x5E, 0xB4, 0x7A, 0xD0, + 0xD1, 0x20, 0x0E, 0x9D, 0xCE, 0xD9, 0xC5, 0x83, 0x42, 0xE2, 0xD5, 0x81, + 0x86, 0x17, 0xC8, 0x59, 0x91, 0x40, 0x08, 0x70, 0x20, 0x03, 0x16, 0x50, + 0x00, 0x03, 0x7C, 0x20, 0x7C, 0x7F, 0x93, 0x57, 0x13, 0xC5, 0xFC, 0x66, + 0x00, 0xBD, 0xD1, 0x5F, 0x90, 0xC5, 0x63, 0xAC, 0x1E, 0x5B, 0x7F, 0x11, + 0xE7, 0x19, 0x1F, 0x20, 0x98, 0xA0, 0x8C, 0x20, 0x70, 0x31, 0x6A, 0x00, + 0x15, 0x04, 0xE0, 0x40, 0x0E, 0x7E, 0x20, 0x03, 0x1B, 0x36, 0xA0, 0x27, + 0xEE, 0xC4, 0x9D, 0x4E, 0x14, 0x85, 0xE2, 0x50, 0x9F, 0x8F, 0x0A, 0xA8, + 0x3F, 0xAF, 0xC9, 0xF4, 0x33, 0xBB, 0x54, 0xA6, 0xF3, 0xA6, 0x8E, 0x99, + 0x5F, 0x84, 0x12, 0xCF, 0xEA, 0x0A, 0xF4, 0xE0, 0x8F, 0xAB, 0x5B, 0x5F, + 0x42, 0xB7, 0x62, 0x42, 0x10, 0x70, 0x28, 0xA1, 0x04, 0x00, 0x68, 0xC2, + 0x02, 0xC2, 0x10, 0x01, 0x0E, 0xD2, 0x80, 0x06, 0x29, 0x18, 0x00, 0xC4, + 0x6F, 0x30, 0x0E, 0x8E, 0x13, 0x38, 0x12, 0x37, 0x18, 0xC7, 0x53, 0xF5, + 0xC5, 0x45, 0xAA, 0xC8, 0x5A, 0xA9, 0x88, 0x92, 0xA3, 0x75, 0xE3, 0x64, + 0xFD, 0xAA, 0xA4, 0x92, 0x4B, 0xAD, 0x2E, 0x7B, 0x50, 0x9D, 0xCD, 0x20, + 0xAD, 0xBB, 0x10, 0x9C, 0x1E, 0x26, 0xE3, 0x79, 0x3C, 0x6B, 0x7E, 0x7D, + 0x6A, 0xB5, 0x0E, 0x4C, 0x75, 0x0D, 0xB4, 0x2C, 0x8B, 0xFE, 0xA7, 0x2D, + 0xF8, 0xF1, 0x39, 0xF4, 0x4E, 0x8C, 0xBF, 0xF6, 0x57, 0xBA, 0x4B, 0xFE, + 0xBF, 0x89, 0x6E, 0xC5, 0x07, 0x3E, 0x78, 0x20, 0xE1, 0x07, 0x33, 0x90, + 0xC0, 0x1B, 0x34, 0x00, 0x44, 0xC3, 0x46, 0xB6, 0x01, 0x94, 0x22, 0x48, + 0x4E, 0xF2, 0x5F, 0x5E, 0xCE, 0xF8, 0x0C, 0x0B, 0xDE, 0xA2, 0xD1, 0xC4, + 0x2F, 0xAF, 0x5F, 0xA2, 0xAB, 0xDE, 0x60, 0x1C, 0xFD, 0x4D, 0x44, 0xFD, + 0x44, 0x2B, 0x95, 0xDD, 0x60, 0xDE, 0x9D, 0x37, 0x52, 0xC4, 0x26, 0x54, + 0x44, 0x41, 0x54, 0xE5, 0x68, 0x0B, 0xEB, 0x3E, 0x54, 0x41, 0x14, 0x07, + 0x18, 0x6C, 0x8A, 0xF6, 0x41, 0x48, 0x5A, 0xEF, 0x56, 0xAF, 0xE7, 0xDA, + 0x78, 0x28, 0x78, 0xEE, 0xDF, 0x2F, 0xFE, 0x7F, 0x06, 0xF3, 0x67, 0x4D, + 0xA2, 0x11, 0x88, 0x9B, 0x16, 0x28, 0xC5, 0x8D, 0xF4, 0x78, 0x19, 0xBF, + 0x68, 0x7B, 0x5C, 0x5E, 0x5C, 0xAA, 0x0A, 0x49, 0xF5, 0x46, 0x23, 0x2B, + 0x22, 0xC9, 0x88, 0x7A, 0x23, 0x20, 0x92, 0xEB, 0x8D, 0x6A, 0x06, 0xC6, + 0xB9, 0x9F, 0x6F, 0x2F, 0x2E, 0x2E, 0x23, 0xFF, 0x7E, 0x47, 0x57, 0x19, + 0x49, 0xFD, 0xC0, 0x46, 0x71, 0x44, 0x25, 0xAE, 0x62, 0xDC, 0x91, 0xA3, + 0xAF, 0x62, 0x09, 0x5A, 0xB6, 0x7A, 0x56, 0xFD, 0xED, 0x09, 0x15, 0x5D, + 0xD2, 0x9E, 0x7F, 0x33, 0xC1, 0xAC, 0x5C, 0x0D, 0x88, 0xE7, 0xD2, 0xA7, + 0x3E, 0x7B, 0x9A, 0x54, 0xBA, 0x2C, 0x26, 0xEF, 0xB5, 0x8B, 0xDB, 0xE7, + 0x49, 0x3D, 0xE7, 0xE5, 0xF2, 0x8C, 0xD4, 0xFF, 0xE7, 0x91, 0xC7, 0x80, + 0xB8, 0x53, 0xFA, 0x21, 0x1C, 0x4C, 0x4C, 0x31, 0xDC, 0xD8, 0xAC, 0x04, + 0x2A, 0x89, 0xBF, 0x9C, 0xC9, 0xB3, 0xC9, 0xE1, 0xBA, 0x6B, 0xDD, 0x50, + 0x36, 0x3D, 0xEE, 0xFF, 0xAF, 0xD7, 0x59, 0xCE, 0x35, 0x69, 0x3F, 0x2F, + 0xA3, 0xAD, 0x3C, 0xE6, 0xDD, 0x24, 0xFE, 0x12, 0x3B, 0xCD, 0xB5, 0x6E, + 0x94, 0x05, 0x89, 0x46, 0xFF, 0x7F, 0xEF, 0xF0, 0x79, 0xE0, 0xFA, 0x4B, + 0x3F, 0xEE, 0xB5, 0xFB, 0x86, 0xEC, 0x90, 0x4B, 0xBD, 0xFB, 0x3C, 0x98, + 0xBE, 0x1C, 0xEE, 0x54, 0xC8, 0xC7, 0xC6, 0x7E, 0x43, 0xDA, 0x4B, 0xF7, + 0x4A, 0xA4, 0x77, 0x51, 0x72, 0x29, 0x79, 0x44, 0xD6, 0x08, 0x3D, 0x0B, + 0x56, 0xBE, 0xA8, 0xBB, 0xB1, 0x02, 0x8F, 0x77, 0x88, 0xC0, 0x3C, 0x28, + 0xE1, 0x1E, 0x41, 0xDC, 0x40, 0x30, 0x12, 0x34, 0x38, 0x02, 0x3F, 0x1D, + 0x3B, 0x00, 0x08, 0x9C, 0xF8, 0xB7, 0x54, 0x36, 0xA2, 0xCB, 0x71, 0x46, + 0xC1, 0xD9, 0x04, 0x48, 0x89, 0x19, 0xBE, 0x6E, 0x8B, 0x12, 0x98, 0x84, + 0x77, 0x24, 0x00, 0xC9, 0x71, 0xC2, 0xBA, 0x35, 0xE3, 0x87, 0xFE, 0x83, + 0xF4, 0xA8, 0xA9, 0x55, 0x62, 0x06, 0x76, 0xE9, 0xDE, 0x07, 0xE1, 0x07, + 0xE9, 0x7B, 0x6E, 0xD1, 0x3F, 0x83, 0x1A, 0xD6, 0xDA, 0x20, 0xF9, 0x5C, + 0x55, 0xA2, 0xE6, 0x32, 0x35, 0xF1, 0xE1, 0x39, 0x83, 0xEE, 0x52, 0xA2, + 0xC6, 0xE2, 0xB6, 0x97, 0xA6, 0x84, 0x77, 0x65, 0x42, 0x3A, 0xB2, 0x8D, + 0x77, 0x4D, 0x22, 0x33, 0x7C, 0x13, 0xEF, 0xBA, 0x49, 0xC8, 0x0C, 0xD7, + 0xAA, 0xEF, 0xEA, 0x6E, 0xF6, 0xC5, 0xD5, 0xA1, 0x8E, 0xD6, 0xD1, 0xF3, + 0xD0, 0x44, 0xF6, 0xF0, 0xFF, 0x5F, 0xB8, 0xE4, 0x3D, 0xFC, 0xEF, 0xBF, + 0xB2, 0xE9, 0x27, 0x5A, 0xFD, 0x26, 0xAC, 0x4B, 0x44, 0x25, 0x49, 0x49, + 0x09, 0xDA, 0x12, 0xC9, 0xB5, 0xB4, 0x02, 0xBD, 0x7B, 0x6A, 0xDD, 0xFE, + 0x12, 0x91, 0xC8, 0xF8, 0x5F, 0xA2, 0x38, 0xAA, 0xD9, 0x52, 0x34, 0xCE, + 0x33, 0x68, 0xB6, 0x53, 0x8B, 0xE6, 0x87, 0x1A, 0x8E, 0x55, 0x74, 0xD4, + 0x95, 0x49, 0xA9, 0x49, 0xF2, 0x8A, 0x6C, 0x71, 0x2F, 0xB6, 0xB0, 0x7D, + 0x69, 0xFD, 0x9A, 0x48, 0xC3, 0xBF, 0x5E, 0x5E, 0x6C, 0x41, 0x7F, 0x93, + 0x10, 0xEF, 0xD2, 0xFA, 0x57, 0xD6, 0x16, 0xFF, 0xD7, 0x8F, 0x7D, 0xBF, + 0xCB, 0xC5, 0x3C, 0x2E, 0x10, 0x31, 0x17, 0x10, 0x96, 0x6F, 0xAD, 0x15, + 0x73, 0x05, 0xCD, 0xE9, 0xFF, 0xAF, 0xBA, 0x78, 0xE2, 0x5D, 0x4C, 0xF4, + 0xF1, 0x18, 0x2B, 0x15, 0xD4, 0x47, 0x6C, 0xCE, 0x56, 0xFA, 0xD5, 0x59, + 0x48, 0xAF, 0xBA, 0xA6, 0xD0, 0x5C, 0x2A, 0x64, 0xE6, 0x76, 0x4D, 0xCA, + 0x87, 0x15, 0xAB, 0x08, 0x1F, 0xF4, 0xA3, 0xF3, 0x85, 0xA2, 0x4A, 0xA5, + 0x7A, 0x0D, 0x1B, 0xAA, 0x1A, 0x24, 0x15, 0xFC, 0xF9, 0x63, 0xC8, 0x47, + 0xA6, 0x29, 0xDD, 0x29, 0x2C, 0x9E, 0x66, 0x04, 0x74, 0x12, 0xEC, 0x95, + 0x98, 0x12, 0xB5, 0x88, 0x53, 0xF0, 0x22, 0x48, 0xFA, 0x59, 0xE8, 0x75, + 0x42, 0x2F, 0xA6, 0xD7, 0xFF, 0xEB, 0x38, 0x64, 0x39, 0x3C, 0x39, 0xE4, + 0x43, 0x1D, 0x22, 0x16, 0xD1, 0x89, 0x88, 0x75, 0x48, 0xEB, 0x24, 0x5A, + 0x6C, 0x53, 0xA7, 0x69, 0xDA, 0x33, 0x6D, 0xA1, 0x2D, 0xC2, 0x5D, 0xD3, + 0xB5, 0x56, 0xBE, 0xC8, 0x16, 0xE9, 0x77, 0x9B, 0x60, 0xE4, 0xCB, 0xB3, + 0x25, 0x5E, 0x3F, 0x61, 0xE2, 0xE8, 0x24, 0x97, 0xDB, 0xDA, 0x90, 0xD4, + 0x7D, 0x13, 0x0C, 0x0D, 0xB6, 0x56, 0x55, 0xE1, 0x6A, 0x30, 0x4D, 0x9C, + 0x3E, 0xBF, 0xF8, 0x37, 0xD4, 0x7F, 0x3B, 0xE4, 0x74, 0x1B, 0x93, 0x2E, + 0x4E, 0xAF, 0xF6, 0x1D, 0x8A, 0xCB, 0x6C, 0x50, 0x8C, 0x31, 0x88, 0xA3, + 0x32, 0x33, 0xE7, 0xF4, 0x8E, 0xF7, 0x7E, 0xB0, 0xEE, 0x66, 0x4C, 0x88, + 0xDB, 0xDC, 0x91, 0xDC, 0x94, 0xA7, 0x4F, 0x9C, 0x4D, 0x5B, 0x62, 0x76, + 0xDB, 0xA7, 0xCE, 0xA4, 0x01, 0xFA, 0x76, 0xCA, 0xCD, 0x6E, 0x00, 0x90, + 0x85, 0x05, 0x59, 0xD3, 0xFB, 0x2C, 0xF9, 0xFF, 0x9C, 0xA9, 0xE7, 0xD2, + 0x3A, 0x07, 0x75, 0x5D, 0xFD, 0xCF, 0x6B, 0x1D, 0x89, 0x0D, 0x4B, 0x0E, + 0xAC, 0x19, 0x2C, 0x59, 0x34, 0x48, 0x15, 0x01, 0x48, 0xFF, 0x55, 0x76, + 0x5F, 0xB4, 0x8C, 0xA8, 0x40, 0x0B, 0x1A, 0x88, 0xC1, 0x11, 0x02, 0xF8, + 0xC1, 0x05, 0x65, 0x98, 0xEC, 0xC8, 0x46, 0x50, 0xC2, 0x82, 0xA7, 0x87, + 0xC7, 0xC3, 0x41, 0x55, 0xCA, 0xE1, 0x74, 0x33, 0x28, 0x8E, 0xD2, 0x0D, + 0x8B, 0x1D, 0xA8, 0x6A, 0xEF, 0x76, 0x8A, 0xAA, 0x9B, 0xF6, 0x71, 0x74, + 0xAB, 0x2F, 0xC9, 0x6E, 0xF4, 0x0F, 0xFD, 0x3A, 0x9F, 0x8B, 0x88, 0x30, + 0x78, 0xD1, 0x85, 0xC1, 0x28, 0xE3, 0x48, 0x5E, 0xD6, 0xE5, 0x6D, 0x1B, + 0xEC, 0xFE, 0xDD, 0x8B, 0x5C, 0xA3, 0xA7, 0x4C, 0x78, 0xF8, 0x37, 0x1F, + 0x61, 0x1B, 0x8F, 0xFF, 0x72, 0xDA, 0x4B, 0xF8, 0x3B, 0xBE, 0xAD, 0xA3, + 0xDA, 0x35, 0x60, 0xC4, 0xB4, 0x01, 0x57, 0xAD, 0xE7, 0x31, 0xAE, 0xD3, + 0xEF, 0x0D, 0x7C, 0xCF, 0x0D, 0xF4, 0xDD, 0xE9, 0xAD, 0xD7, 0xDD, 0x7D, + 0x76, 0x17, 0x4A, 0x7A, 0xC4, 0x75, 0x34, 0xCB, 0xC9, 0x4F, 0xA7, 0xD3, + 0xE9, 0x74, 0x3A, 0x9D, 0xC8, 0x0C, 0xEA, 0x29, 0x34, 0x27, 0x2B, 0xA4, + 0xB9, 0x54, 0x15, 0x32, 0xB3, 0x31, 0x15, 0x42, 0xB3, 0x3D, 0x88, 0x3C, + 0x5D, 0x48, 0x0D, 0xF7, 0x14, 0x79, 0x32, 0xF5, 0x48, 0x9D, 0xA4, 0x3A, + 0x9E, 0x4E, 0xF8, 0xE4, 0x2A, 0xA0, 0xD3, 0xC9, 0x27, 0xCE, 0x89, 0x88, + 0x14, 0x11, 0x3C, 0x85, 0x4E, 0x44, 0x5C, 0xA5, 0x73, 0x22, 0xE2, 0xD3, + 0xE7, 0x14, 0x80, 0x54, 0x00, 0x78, 0xFA, 0x6C, 0x01, 0x08, 0xC0, 0xA7, + 0xD0, 0x96, 0x11, 0xF1, 0x81, 0x92, 0x9A, 0x41, 0xA4, 0x40, 0x61, 0x6B, + 0xA5, 0x50, 0x5A, 0x65, 0x07, 0xA7, 0x05, 0x71, 0x02, 0xE5, 0x38, 0x3B, + 0x38, 0x53, 0x10, 0x1F, 0x03, 0x48, 0x69, 0x44, 0xA4, 0x18, 0x00, 0x07, + 0x49, 0x0D, 0xA0, 0x53, 0x82, 0xB8, 0x29, 0xC4, 0x89, 0x01, 0x3C, 0x48, + 0x10, 0x97, 0x09, 0xF1, 0x21, 0x44, 0x2A, 0xC5, 0x72, 0x93, 0x88, 0x14, + 0x42, 0x70, 0x10, 0xCB, 0x99, 0x88, 0xA4, 0x84, 0xE8, 0x14, 0x7B, 0x35, + 0x21, 0x4E, 0x08, 0xF1, 0x20, 0xF6, 0x66, 0x7D, 0xFA, 0x4F, 0x04, 0xA9, + 0x4D, 0xDE, 0xCB, 0xD5, 0xB2, 0xF9, 0x11, 0x91, 0x73, 0xB5, 0x16, 0xE2, + 0xE7, 0xD7, 0xC8, 0xB6, 0xEB, 0xBA, 0xEE, 0xFB, 0xBE, 0x5B, 0xE3, 0xF9, + 0xF5, 0xF4, 0xC4, 0x02, 0x49, 0xFD, 0x40, 0x2B, 0x10, 0x27, 0x7E, 0x38, + 0x49, 0xFD, 0x60, 0x1B, 0x08, 0x8F, 0x98, 0x1D, 0x36, 0x07, 0x77, 0x02, + 0xC4, 0xA5, 0xAE, 0x94, 0x80, 0x9C, 0x50, 0x20, 0x18, 0x77, 0xF2, 0x0B, + 0x24, 0x9E, 0x9C, 0xEB, 0x68, 0xC7, 0x73, 0xDA, 0xD1, 0x0E, 0xE7, 0xB4, + 0xA3, 0x34, 0xDC, 0xCD, 0xD0, 0xFE, 0x2D, 0xBD, 0xB9, 0xD9, 0xC1, 0x28, + 0x96, 0x8C, 0x97, 0xF0, 0xFF, 0x3E, 0x8A, 0xE5, 0xBF, 0x8A, 0x12, 0x55, + 0xF6, 0xFD, 0x6D, 0x63, 0xF4, 0xD4, 0x31, 0x11, 0xCF, 0x2B, 0x8C, 0x9C, + 0x82, 0xC2, 0x44, 0xA0, 0x7B, 0x98, 0x4F, 0x71, 0x3E, 0xF7, 0xBF, 0xC8, + 0x14, 0x3E, 0x88, 0x31, 0x05, 0x15, 0x63, 0x4C, 0x91, 0x05, 0x35, 0xF6, + 0x21, 0x7E, 0x9A, 0xBA, 0xE2, 0x8A, 0x2B, 0xA6, 0x98, 0x62, 0x8A, 0x5B, + 0x33, 0xF7, 0x6F, 0x93, 0x4F, 0x61, 0xE3, 0x59, 0xF4, 0xA2, 0xE6, 0xEE, + 0xB9, 0x89, 0xD0, 0x0F, 0x3E, 0xBF, 0x9C, 0xE9, 0x81, 0x0F, 0x6C, 0x2F, + 0x88, 0x50, 0x92, 0xD3, 0xB7, 0x7A, 0x71, 0x84, 0xBB, 0x6C, 0xFD, 0xD2, + 0xCC, 0xE5, 0xDE, 0x74, 0xA8, 0x4D, 0x2C, 0xA0, 0xE1, 0x7E, 0x7A, 0x6B, + 0xA6, 0x04, 0xCD, 0xFD, 0xEF, 0x4D, 0x44, 0x54, 0xB8, 0x6A, 0xD3, 0x75, + 0x2F, 0x77, 0xF9, 0x6D, 0xFD, 0x14, 0xB2, 0xC2, 0x59, 0x1E, 0x4C, 0xB2, + 0xD7, 0x84, 0xFC, 0xE0, 0x6E, 0xBA, 0x71, 0x07, 0x34, 0x22, 0x2B, 0xEE, + 0x53, 0x4E, 0xDE, 0x78, 0x5F, 0x7F, 0xB3, 0xCF, 0x14, 0xF4, 0x48, 0xBD, + 0x38, 0xE1, 0xAB, 0xBC, 0x45, 0x2F, 0x9A, 0x5F, 0x33, 0x6D, 0xEB, 0x7D, + 0x4B, 0x99, 0x2D, 0xAE, 0xCD, 0x16, 0x33, 0x6D, 0x8B, 0xD9, 0x7A, 0xFF, + 0x56, 0x4A, 0x9E, 0x2B, 0xDE, 0xE7, 0x56, 0x4A, 0x9C, 0x2B, 0x5E, 0xE7, + 0x72, 0x26, 0x92, 0x6A, 0x7E, 0xFB, 0xEB, 0xC4, 0x14, 0xBD, 0x7F, 0x4E, + 0xC7, 0xC8, 0x70, 0xE3, 0xC8, 0xA8, 0xBC, 0xFB, 0xAF, 0x66, 0x48, 0xCD, + 0xB6, 0x45, 0x40, 0x28, 0xCE, 0x2F, 0x15, 0xCE, 0x65, 0xAD, 0xFA, 0x9A, + 0xFB, 0xFB, 0x95, 0xC8, 0x73, 0x2B, 0xE9, 0xA7, 0x82, 0xE6, 0x4A, 0xCC, + 0x70, 0xFF, 0xEC, 0xA9, 0xA1, 0xE7, 0xFF, 0x51, 0xFC, 0x7D, 0x9A, 0xC8, + 0x59, 0x2A, 0x76, 0x58, 0xDE, 0xF6, 0xAB, 0xD2, 0xE7, 0x8C, 0x48, 0x9F, + 0x42, 0xCF, 0x11, 0x7F, 0x4D, 0x3D, 0x56, 0xCC, 0x35, 0xF1, 0x81, 0xCF, + 0x7C, 0xE2, 0x7D, 0xA2, 0x56, 0x33, 0xDB, 0x9B, 0x41, 0x8F, 0x85, 0x17, + 0x97, 0x51, 0x82, 0xF0, 0xA2, 0xFE, 0xCD, 0xAF, 0xB9, 0xD6, 0x67, 0xA3, + 0x3F, 0xBD, 0xFB, 0x55, 0x3A, 0xD7, 0x3E, 0xE7, 0x14, 0x68, 0x0A, 0xBF, + 0xAD, 0x2A, 0x72, 0x42, 0xA1, 0xEE, 0x17, 0x6C, 0xBC, 0xB8, 0x2A, 0x98, + 0x93, 0xC1, 0x26, 0x43, 0xAE, 0x76, 0xC5, 0xFC, 0xAE, 0xEC, 0x67, 0x7E, + 0x27, 0x15, 0xD0, 0xA5, 0x01, 0x6D, 0xD4, 0xDA, 0x93, 0x09, 0xD5, 0x23, + 0x6A, 0xEC, 0xA2, 0x3E, 0xBD, 0x08, 0x14, 0x64, 0x4D, 0xCB, 0xD4, 0xE9, + 0xA0, 0xB4, 0x2E, 0xEB, 0x66, 0x97, 0x83, 0xBA, 0xE9, 0xDB, 0xE8, 0xEF, + 0x6D, 0xE9, 0xDB, 0xB7, 0x56, 0x1C, 0x60, 0xAE, 0xC5, 0x6D, 0x72, 0x30, + 0x4E, 0xDB, 0x81, 0xE9, 0xED, 0xBD, 0xC3, 0x3A, 0x1F, 0xE7, 0xEA, 0xAE, + 0x3F, 0xE7, 0xCA, 0x79, 0xEA, 0x31, 0x96, 0x0E, 0x1A, 0xE3, 0x6F, 0xF2, + 0x46, 0x6A, 0x67, 0x03, 0x88, 0x8F, 0x30, 0x1D, 0xF1, 0x5A, 0x93, 0x91, + 0x22, 0x56, 0x75, 0x71, 0xE9, 0x4D, 0xDC, 0xFE, 0xD9, 0x45, 0x5B, 0xF9, + 0x6B, 0x1D, 0x77, 0x89, 0xB0, 0x0C, 0x67, 0x9B, 0xDE, 0x8F, 0x0B, 0x67, + 0x5A, 0x61, 0x13, 0x8F, 0x56, 0xAF, 0x44, 0x45, 0xB3, 0x45, 0x32, 0x0B, + 0xBA, 0x3C, 0xED, 0x6B, 0x45, 0x86, 0xAD, 0x32, 0xF2, 0x1E, 0xFA, 0x78, + 0xFB, 0xA8, 0x91, 0x08, 0x44, 0xD0, 0x98, 0x88, 0xF7, 0x13, 0x48, 0x9E, + 0x40, 0x8C, 0xE1, 0xAB, 0xF7, 0x39, 0xE0, 0x43, 0x3E, 0xE8, 0x94, 0xE7, + 0xCF, 0xF9, 0x85, 0x1B, 0x57, 0x06, 0xBF, 0x5D, 0x18, 0xE3, 0x2B, 0xED, + 0x7C, 0x19, 0xFF, 0xCE, 0xE5, 0x67, 0xF9, 0xD7, 0xD0, 0x05, 0x21, 0xF6, + 0x40, 0xE3, 0x5D, 0x0A, 0xED, 0x91, 0xFE, 0x33, 0xA9, 0xA3, 0x98, 0x28, + 0x47, 0x7F, 0x09, 0x40, 0x7E, 0x44, 0xD1, 0x8B, 0xF7, 0x0C, 0xC8, 0x0E, + 0xE7, 0x57, 0x07, 0xFA, 0xB4, 0x3D, 0xEC, 0x3F, 0x9F, 0x93, 0xE0, 0xC0, + 0x0F, 0x3E, 0x40, 0x83, 0x51, 0x10, 0x12, 0xAC, 0x68, 0xD0, 0x02, 0x31, + 0xD0, 0xA1, 0x88, 0xCD, 0x03, 0xDD, 0xFF, 0x93, 0x48, 0xD6, 0x0F, 0x47, + 0x70, 0x64, 0x64, 0x44, 0xFE, 0x23, 0x2C, 0x28, 0x72, 0xC4, 0x1A, 0xB1, + 0x18, 0xE5, 0x23, 0x96, 0x65, 0x59, 0xD6, 0xC8, 0xC8, 0x88, 0xC5, 0x25, + 0x4B, 0xEA, 0xB8, 0xC5, 0xC2, 0x47, 0x8C, 0x4E, 0x52, 0x0D, 0x80, 0xCC, + 0x45, 0x8C, 0x6E, 0xFD, 0x15, 0x72, 0xB6, 0x76, 0xF4, 0x3D, 0x2E, 0xDF, + 0xDD, 0xDD, 0xC9, 0x3A, 0xE4, 0xD2, 0x8F, 0xA9, 0x7E, 0xAA, 0x1F, 0x63, + 0xF5, 0x3B, 0xBE, 0xFF, 0x0A, 0xAB, 0x05, 0x62, 0xEC, 0x63, 0x9D, 0x0D, + 0xE2, 0x17, 0x97, 0xD0, 0xD7, 0xF7, 0x10, 0x7D, 0xE2, 0xF1, 0x97, 0xDD, + 0xC5, 0x3F, 0xE8, 0x61, 0x4E, 0xE1, 0xB7, 0xFF, 0xA6, 0xE6, 0xCE, 0x85, + 0xDC, 0x6E, 0xFA, 0x07, 0x3D, 0x68, 0xF2, 0xE0, 0x48, 0x68, 0x33, 0xC2, + 0x4D, 0xA7, 0xFF, 0x06, 0x05, 0x12, 0xA6, 0x4A, 0xF6, 0xD1, 0x7B, 0xBF, + 0x76, 0x27, 0x72, 0x50, 0x9E, 0x6E, 0x27, 0xFD, 0xFF, 0x8E, 0xB6, 0x93, + 0xFD, 0xAC, 0xC0, 0xB9, 0x43, 0x5A, 0x81, 0xCD, 0x2B, 0x52, 0xF3, 0x6A, + 0xC7, 0x88, 0xB4, 0x63, 0x54, 0xC3, 0xC0, 0xB6, 0x96, 0x6F, 0x24, 0xF2, + 0x46, 0x92, 0x0F, 0x5D, 0xD4, 0x30, 0x38, 0xFC, 0xFD, 0xCF, 0xB5, 0x60, + 0xF7, 0x7D, 0xAB, 0x42, 0xCF, 0xA2, 0x73, 0xC6, 0x6A, 0x15, 0xF7, 0xF9, + 0x13, 0x37, 0x7E, 0x6A, 0x3E, 0xC7, 0x8A, 0xDD, 0x59, 0x5B, 0x55, 0x90, + 0x95, 0xCD, 0xEF, 0x93, 0x8B, 0xB8, 0xA5, 0x48, 0x2A, 0xA6, 0x9D, 0x73, + 0x29, 0x8D, 0xCE, 0x9F, 0x88, 0x62, 0x91, 0xF9, 0x90, 0xCC, 0x89, 0x8C, + 0xE4, 0x3E, 0x7B, 0x52, 0xDB, 0xA7, 0x05, 0x7E, 0x92, 0xB9, 0x16, 0xC6, + 0x4C, 0xCA, 0x6A, 0x1D, 0xE9, 0xDC, 0xF9, 0x09, 0xEC, 0xF6, 0xEC, 0x69, + 0x26, 0xC9, 0x55, 0xEC, 0xAC, 0x5B, 0xB9, 0xA8, 0x3C, 0x99, 0xCC, 0x4B, + 0xD9, 0xF7, 0xC7, 0xF3, 0x33, 0x8D, 0x04, 0xC9, 0xAB, 0xF0, 0x7F, 0x4F, + 0xF4, 0x9C, 0x0A, 0xE2, 0x41, 0x5C, 0x00, 0x29, 0x01, 0x70, 0x90, 0x94, + 0x00, 0x3A, 0xA5, 0x39, 0x01, 0x3C, 0x48, 0x73, 0x26, 0x22, 0x3E, 0x00, + 0x90, 0x4A, 0x91, 0x1C, 0x00, 0x38, 0x88, 0x4C, 0x01, 0xA0, 0x53, 0xE4, + 0x05, 0xC0, 0x83, 0xC8, 0xFB, 0xD6, 0xB2, 0xAE, 0x76, 0x77, 0x76, 0x41, + 0xB7, 0xD6, 0xF7, 0xAD, 0xDD, 0xCD, 0x96, 0x92, 0x51, 0xA9, 0xA5, 0x36, + 0x48, 0x7D, 0xE3, 0x38, 0x58, 0x06, 0x89, 0xCB, 0xC8, 0xDC, 0xB8, 0x4C, + 0x95, 0x75, 0xB2, 0x58, 0x58, 0x57, 0x38, 0x78, 0x62, 0x49, 0x87, 0x24, + 0xB6, 0xFF, 0x67, 0x15, 0x74, 0xB6, 0x04, 0x61, 0x28, 0x40, 0x16, 0xAC, + 0x97, 0x1B, 0x4C, 0xE9, 0x2B, 0x4A, 0x3F, 0x85, 0x62, 0xB5, 0xB7, 0xD5, + 0xD2, 0x80, 0x97, 0x9B, 0xAA, 0x2F, 0x72, 0x1C, 0x0A, 0x28, 0x4E, 0x0E, + 0x15, 0x81, 0x26, 0xC7, 0xBD, 0x51, 0x96, 0x4E, 0x0E, 0xA5, 0x79, 0xA1, + 0x67, 0xD8, 0x76, 0x99, 0x1C, 0xAA, 0x7A, 0x1B, 0x9D, 0x24, 0x12, 0xBD, + 0xF6, 0x09, 0x49, 0x9F, 0x3F, 0x0D, 0xBB, 0x77, 0x58, 0x13, 0x92, 0x66, + 0xCF, 0x2B, 0x63, 0xB5, 0xE4, 0x71, 0x47, 0xC3, 0x5B, 0x02, 0xDB, 0x4E, + 0x97, 0x80, 0x01, 0x82, 0xAF, 0xD2, 0xB2, 0xF8, 0xE6, 0x9B, 0x65, 0xB1, + 0xC8, 0x48, 0x49, 0x63, 0xCD, 0x58, 0x6D, 0x9D, 0xA2, 0x60, 0x3A, 0x6D, + 0xC5, 0xCC, 0xA8, 0x87, 0xC7, 0x63, 0x98, 0xE7, 0xE0, 0x5C, 0x97, 0x9D, + 0xC9, 0x7B, 0xF6, 0xC5, 0xEC, 0xB0, 0xD3, 0x7B, 0x71, 0xA9, 0x5A, 0xD6, + 0xD5, 0x3E, 0x49, 0xA6, 0xE5, 0xBD, 0x68, 0x54, 0xA1, 0xD7, 0x51, 0xA4, + 0x4E, 0x03, 0xD1, 0x23, 0x58, 0x2B, 0x64, 0x92, 0xF5, 0x7A, 0x51, 0x28, + 0x14, 0xB6, 0x85, 0xA2, 0xC8, 0xED, 0x7F, 0xD6, 0xF1, 0xBC, 0x34, 0xC3, + 0x07, 0x94, 0x19, 0x2D, 0x96, 0x0F, 0xD0, 0xF2, 0xC1, 0xE4, 0x39, 0xA1, + 0x84, 0x93, 0x47, 0x27, 0x9C, 0x80, 0x82, 0x15, 0x4F, 0x00, 0x11, 0x74, + 0xB3, 0xF8, 0xC4, 0x9F, 0x08, 0x22, 0x28, 0x26, 0xA1, 0xAD, 0xE0, 0x99, + 0x31, 0x61, 0x6C, 0x24, 0xE1, 0x40, 0x78, 0x33, 0x74, 0xB0, 0x60, 0x3F, + 0xF9, 0xC1, 0xCE, 0xD8, 0xB3, 0x92, 0x68, 0x96, 0x44, 0x27, 0xB9, 0xC8, + 0xE9, 0x22, 0x6D, 0xD7, 0x53, 0x92, 0x1D, 0x8D, 0x52, 0x3F, 0x68, 0x70, + 0x50, 0x5A, 0x66, 0x70, 0x2E, 0xF3, 0x22, 0xFC, 0xA3, 0x71, 0xEC, 0xBB, + 0x22, 0xF4, 0x1B, 0xB2, 0xD6, 0x2B, 0x52, 0x69, 0x6C, 0xC1, 0x4E, 0x2F, + 0xF9, 0x8C, 0x1F, 0x9F, 0x71, 0x03, 0xE7, 0xF1, 0x31, 0x97, 0x6A, 0x76, + 0x17, 0x5D, 0x22, 0xDE, 0x96, 0x89, 0xDB, 0x4B, 0x54, 0x66, 0xFC, 0x87, + 0xCA, 0x0C, 0xCA, 0x1D, 0x01, 0x18, 0x7F, 0x1F, 0x26, 0xEF, 0x55, 0x09, + 0xD9, 0xD2, 0x03, 0xBD, 0xC6, 0x4C, 0x19, 0x79, 0xDF, 0x41, 0x82, 0x00, + 0xA1, 0x81, 0xCC, 0xF3, 0xE3, 0x52, 0x7D, 0xA8, 0xE3, 0xC4, 0x19, 0x17, + 0xB7, 0x0E, 0x23, 0xB9, 0x77, 0x11, 0xD6, 0x04, 0xE3, 0x5B, 0x93, 0x6E, + 0x59, 0x2E, 0xE4, 0x89, 0xAB, 0x8C, 0x44, 0xF1, 0xC9, 0x23, 0x3F, 0x66, + 0x32, 0x18, 0xF7, 0x8F, 0xB0, 0x4C, 0x53, 0xC4, 0x11, 0xBF, 0x2D, 0xDF, + 0x93, 0x54, 0x54, 0x56, 0x6C, 0x61, 0xD4, 0x76, 0xAC, 0x2D, 0xC6, 0x39, + 0x13, 0xDF, 0x8F, 0x27, 0x30, 0x72, 0xF1, 0xF3, 0x46, 0x6E, 0xA9, 0x37, + 0xC0, 0xDA, 0x3E, 0x73, 0xE2, 0x4C, 0xBC, 0x17, 0xA3, 0x8C, 0x5E, 0x9D, + 0x41, 0x91, 0x27, 0x52, 0xED, 0x09, 0x79, 0x53, 0x2B, 0x95, 0x68, 0xD2, + 0x6E, 0xB0, 0x98, 0xFD, 0x9C, 0x7B, 0xBC, 0x16, 0x26, 0xEB, 0x25, 0xD2, + 0x1F, 0x2C, 0x6A, 0x99, 0x16, 0x3F, 0xD6, 0xEF, 0xC7, 0xB0, 0xFD, 0x0E, + 0xA6, 0xD3, 0x67, 0xF3, 0x82, 0xDC, 0x3E, 0x0C, 0xE3, 0xB8, 0x4D, 0xBA, + 0x80, 0xC9, 0x9D, 0x1D, 0xC9, 0xB1, 0x06, 0xF3, 0xDE, 0xA6, 0x29, 0x8C, + 0xC8, 0xDA, 0x0E, 0x66, 0x65, 0x3F, 0xE5, 0xE9, 0x95, 0x6C, 0x56, 0x5A, + 0x9A, 0x45, 0x7F, 0xD2, 0x39, 0x95, 0x79, 0x5A, 0xBD, 0xB2, 0x36, 0xEA, + 0x84, 0xF2, 0x9A, 0x67, 0x9B, 0xDD, 0x14, 0xFA, 0x5A, 0x25, 0x74, 0x65, + 0x9E, 0x59, 0x6D, 0x08, 0x42, 0x48, 0xA2, 0x10, 0xC2, 0xFF, 0x67, 0xA9, + 0x57, 0x42, 0xAD, 0xCF, 0x22, 0x8A, 0x73, 0x16, 0xBF, 0xF7, 0x45, 0x06, + 0xD4, 0xC0, 0x86, 0x6A, 0x85, 0xEF, 0x08, 0x1F, 0x2C, 0xE1, 0x80, 0x14, + 0x94, 0x0C, 0x41, 0x04, 0x03, 0x22, 0x88, 0x37, 0xF8, 0x38, 0x6E, 0x3D, + 0x87, 0x37, 0xAE, 0xDD, 0xC6, 0xDD, 0xC6, 0x1D, 0xA9, 0x5E, 0xF4, 0x7A, + 0x3C, 0x57, 0xC3, 0x34, 0x33, 0x8D, 0xB1, 0x84, 0xD9, 0x17, 0x53, 0xCD, + 0x83, 0x1F, 0xD3, 0x3B, 0x6E, 0xE1, 0x29, 0x4D, 0xC2, 0x88, 0x11, 0x78, + 0x80, 0x03, 0x46, 0x6C, 0x20, 0x87, 0x05, 0x4A, 0xB2, 0x8B, 0x63, 0x3B, + 0x8E, 0x21, 0x7C, 0x02, 0xF7, 0x71, 0x24, 0xC4, 0x38, 0x8E, 0xFB, 0xA8, + 0x14, 0x96, 0x70, 0x46, 0xD5, 0x4A, 0x56, 0x81, 0xA7, 0x90, 0x5A, 0xB6, + 0xDD, 0x32, 0xE7, 0x50, 0xB7, 0x25, 0x67, 0x19, 0x8C, 0x6B, 0x6B, 0xE1, + 0x89, 0x7E, 0xBC, 0xEA, 0xAB, 0xC6, 0x80, 0xE2, 0x67, 0x54, 0x95, 0xDF, + 0xE6, 0x0F, 0xE9, 0xF2, 0x1C, 0xC3, 0x09, 0x13, 0x18, 0x9E, 0x13, 0xA6, + 0x6E, 0xAE, 0x66, 0xDF, 0x0C, 0x37, 0x61, 0x62, 0x34, 0xC1, 0x5C, 0x0B, + 0x42, 0x65, 0x37, 0x92, 0xE6, 0x72, 0x11, 0x4F, 0x4C, 0xCA, 0x31, 0xD2, + 0xC8, 0x10, 0x51, 0xAC, 0x5A, 0xA1, 0xB2, 0x1D, 0x6C, 0x1B, 0x4D, 0x98, + 0x92, 0xD1, 0xE5, 0x37, 0x6A, 0x0C, 0xEE, 0xCD, 0xE3, 0x31, 0xA7, 0x0F, + 0x02, 0xAA, 0x35, 0x25, 0xB0, 0xCB, 0x1D, 0x4F, 0xD6, 0x44, 0xFD, 0xA4, + 0xD3, 0x76, 0x00, 0xBD, 0xD0, 0xE4, 0x8B, 0xF7, 0x0C, 0xAA, 0x9E, 0x33, + 0x7D, 0x7D, 0xA1, 0x1E, 0x53, 0x89, 0xD2, 0xE9, 0x36, 0x43, 0x4E, 0xA6, + 0x5F, 0xD3, 0x2F, 0xE5, 0x79, 0x36, 0x0F, 0xD4, 0x7B, 0xAE, 0x50, 0x16, + 0xBB, 0x5A, 0x8F, 0xDF, 0xAB, 0x7C, 0x54, 0x5E, 0xD4, 0xEE, 0x17, 0xBC, + 0x6A, 0xE6, 0x6A, 0xDB, 0xCF, 0xA7, 0xD9, 0xB7, 0x76, 0xE4, 0xE9, 0x72, + 0xC6, 0x93, 0xD5, 0xAA, 0x70, 0xE9, 0x87, 0x1E, 0xEF, 0x8C, 0x1B, 0xA9, + 0x66, 0xA4, 0x79, 0x91, 0x0A, 0x7D, 0x76, 0x13, 0x48, 0xE6, 0xF1, 0x64, + 0x6A, 0xC3, 0x4C, 0xE4, 0x39, 0xC3, 0x69, 0x9D, 0xB1, 0xEA, 0x93, 0xF9, + 0x90, 0x6B, 0x1F, 0xAF, 0xA9, 0x66, 0x4A, 0xA0, 0x41, 0xFD, 0x31, 0xDB, + 0xAD, 0x8E, 0x9F, 0x40, 0x5C, 0xA6, 0x11, 0x31, 0xE1, 0xE9, 0x22, 0xD1, + 0xD4, 0x43, 0x3F, 0xC6, 0x69, 0x3D, 0x68, 0xE2, 0xBB, 0x23, 0xBF, 0xEF, + 0xE3, 0x35, 0x75, 0x5E, 0xF6, 0xF5, 0xF8, 0x61, 0x3D, 0x88, 0xBD, 0x71, + 0xD4, 0x4D, 0x1F, 0xFF, 0x68, 0x3D, 0xDB, 0xB9, 0x6C, 0xBE, 0xCD, 0xB7, + 0xAD, 0xB5, 0x36, 0xBB, 0xF5, 0xCC, 0x94, 0x0D, 0xCB, 0x66, 0x49, 0xEF, + 0x6B, 0x9F, 0x56, 0xC6, 0x52, 0x39, 0x8C, 0xB3, 0x94, 0xB3, 0x92, 0xAB, + 0xE9, 0x9C, 0x8E, 0x9F, 0xC4, 0x9D, 0x04, 0xA4, 0x37, 0x69, 0x2E, 0x37, + 0xF3, 0x46, 0x4C, 0x41, 0x99, 0x4D, 0x0E, 0xCE, 0x61, 0x45, 0xB9, 0xAE, + 0xFA, 0x73, 0x5D, 0xD7, 0x4F, 0xCE, 0x10, 0x66, 0x47, 0x81, 0xD6, 0xA3, + 0x27, 0xDD, 0xF3, 0xBA, 0xFB, 0x6B, 0x7D, 0xC7, 0x2C, 0xB2, 0x18, 0xD7, + 0x94, 0x4B, 0xA6, 0x39, 0xFF, 0xAD, 0x1E, 0xBA, 0x89, 0xBC, 0xB5, 0xF5, + 0x24, 0x08, 0xEB, 0x45, 0x0C, 0x77, 0xC4, 0x7B, 0x57, 0xB5, 0xE7, 0xC1, + 0x21, 0xC9, 0x04, 0x72, 0xC5, 0xD9, 0x0A, 0x92, 0x16, 0x3E, 0x7C, 0x32, + 0xD2, 0x09, 0xB1, 0x01, 0x08, 0xD0, 0x08, 0x47, 0x28, 0x3B, 0xA2, 0x87, + 0xCD, 0x84, 0x85, 0x34, 0x4C, 0x23, 0x50, 0xA1, 0x48, 0x6A, 0xC7, 0x04, + 0x52, 0xC0, 0x85, 0x94, 0x3A, 0xD4, 0x21, 0xFE, 0xD6, 0x2F, 0xFF, 0x4A, + 0x4F, 0xAB, 0xBC, 0xB5, 0x4B, 0xF7, 0x2D, 0x2B, 0xD7, 0x56, 0x1A, 0x6F, + 0xAD, 0x9A, 0x18, 0x74, 0xEB, 0xBD, 0x50, 0x7C, 0xFF, 0xCD, 0xD2, 0xB1, + 0xB0, 0x04, 0x59, 0x2C, 0x41, 0x90, 0x25, 0xE8, 0x27, 0x5D, 0x4F, 0x20, + 0xEB, 0x04, 0x08, 0x9E, 0x00, 0xF1, 0x24, 0xE1, 0xA6, 0xC1, 0xF6, 0x32, + 0x5B, 0x8B, 0x1B, 0x32, 0x3F, 0xCE, 0xA0, 0xAF, 0x3D, 0xDB, 0x4F, 0xAC, + 0x03, 0x48, 0xFF, 0xDF, 0xF1, 0xA7, 0x4C, 0x3A, 0xC1, 0x88, 0xD6, 0x85, + 0x95, 0x49, 0x38, 0x59, 0x8B, 0x96, 0x29, 0x28, 0xC2, 0x2A, 0x6F, 0xD1, + 0x1F, 0x46, 0x86, 0xB0, 0xB3, 0xB6, 0x90, 0x53, 0x92, 0x05, 0x19, 0xE0, + 0xD0, 0x28, 0x00, 0x09, 0x92, 0x8C, 0xE3, 0x86, 0x88, 0x01, 0x30, 0x28, + 0x22, 0x8B, 0x04, 0x68, 0x41, 0x0A, 0x0A, 0xB4, 0xB1, 0x03, 0x00, 0x41, + 0xA8, 0xE0, 0x18, 0x0A, 0x3E, 0x18, 0xA4, 0xE1, 0x8B, 0x1B, 0x86, 0x11, + 0x74, 0xF1, 0x85, 0x3C, 0x68, 0xE1, 0x03, 0x42, 0x76, 0xEE, 0x10, 0x85, + 0x0D, 0x40, 0xE1, 0x09, 0x2F, 0x56, 0x11, 0x1D, 0x0B, 0x40, 0xA1, 0x83, + 0x96, 0x1C, 0x54, 0x1A, 0x8C, 0x20, 0x06, 0x4D, 0x48, 0xC0, 0x54, 0x87, + 0x15, 0x40, 0x40, 0x09, 0x98, 0x31, 0x92, 0x68, 0x80, 0x08, 0x42, 0x30, + 0x07, 0x0D, 0x64, 0x19, 0xC0, 0x00, 0x02, 0x2C, 0x31, 0x90, 0x28, 0xD9, + 0x80, 0x00, 0x10, 0x5D, 0x0E, 0x87, 0xD1, 0xFD, 0xE0, 0x01, 0x22, 0xC6, + 0x05, 0x7E, 0x5E, 0x31, 0xF9, 0x99, 0x08, 0x22, 0x30, 0x32, 0x84, 0x10, + 0xB2, 0x91, 0x86, 0x2E, 0x00, 0x61, 0x85, 0x1F, 0x9A, 0xB0, 0x04, 0x92, + 0x4E, 0x0F, 0x48, 0xC8, 0x99, 0x81, 0x33, 0x82, 0xF3, 0x07, 0xE9, 0xB0, + 0x20, 0xF0, 0xF8, 0xAB, 0x77, 0xD8, 0xD6, 0x0F, 0xD9, 0x67, 0xDD, 0x87, + 0x86, 0x3F, 0x35, 0x7F, 0x96, 0x68, 0xC2, 0x01, 0x07, 0x25, 0x21, 0x10, + 0xF4, 0x20, 0xCC, 0xA5, 0x4C, 0xB4, 0x44, 0xD7, 0xBC, 0xA6, 0xF1, 0xBA, + 0xB0, 0xCF, 0x9B, 0xE1, 0xCD, 0x8E, 0x44, 0x89, 0x0B, 0x83, 0xF0, 0xE7, + 0x1E, 0xD2, 0xC8, 0x69, 0x2D, 0x82, 0xBF, 0xEB, 0x34, 0x23, 0x4A, 0xAF, + 0xD7, 0xEB, 0xF0, 0xB7, 0xE1, 0x6D, 0x7C, 0x2C, 0x68, 0x2D, 0x13, 0x77, + 0x17, 0x25, 0x05, 0xC9, 0xC4, 0x0B, 0xA2, 0x60, 0x9C, 0x55, 0x45, 0xCF, + 0x96, 0x38, 0xEA, 0xE2, 0x1A, 0x91, 0x63, 0xCE, 0xB1, 0xFE, 0xA1, 0x1B, + 0x2D, 0xFE, 0xA5, 0x25, 0x13, 0x2D, 0x2D, 0x85, 0x23, 0xAD, 0x2E, 0x91, + 0xDE, 0x96, 0xEC, 0x81, 0x59, 0xB2, 0x47, 0xB4, 0x1E, 0xB9, 0xA1, 0x0C, + 0x94, 0x81, 0x5F, 0x64, 0xDB, 0xE3, 0xD3, 0xC3, 0xE3, 0x45, 0xAE, 0x39, + 0x3A, 0x39, 0x38, 0x1D, 0x53, 0x9A, 0x1A, 0x9A, 0x99, 0x8E, 0xA7, 0x41, + 0x42, 0x1C, 0x04, 0xF4, 0xC5, 0x7F, 0x2F, 0x39, 0x64, 0x5C, 0x60, 0x85, + 0xF2, 0x92, 0x81, 0x32, 0xF0, 0xD1, 0x02, 0x41, 0xAB, 0xCE, 0x0F, 0xCE, + 0xEE, 0xB2, 0xE2, 0x65, 0x69, 0xDF, 0x2F, 0xAF, 0xB6, 0x2C, 0x7E, 0x84, + 0x65, 0xBC, 0x70, 0x2D, 0x80, 0x14, 0xFD, 0x5F, 0xE4, 0xF3, 0x73, 0xF8, + 0x95, 0xEA, 0xB0, 0xAD, 0x0F, 0xBA, 0x28, 0xCA, 0x75, 0xFA, 0xFA, 0xFA, + 0x53, 0x96, 0x7F, 0x77, 0x04, 0xE9, 0xCA, 0xF1, 0x22, 0x88, 0x58, 0xC2, + 0x0A, 0x4B, 0x05, 0x4F, 0xC0, 0x22, 0x00, 0x10, 0xCB, 0x40, 0x12, 0x65, + 0x9C, 0x21, 0x08, 0x97, 0x8F, 0xB8, 0x89, 0x25, 0x5C, 0xB7, 0x8E, 0xA1, + 0xFC, 0x1F, 0xCD, 0x91, 0xFC, 0xE1, 0x47, 0xA2, 0xD4, 0x08, 0xF2, 0x47, + 0x17, 0x49, 0x6E, 0x8D, 0x82, 0xA1, 0x6C, 0x0F, 0x47, 0x8F, 0x72, 0xEF, + 0xC3, 0xA1, 0x54, 0x2E, 0xF7, 0x4B, 0xEE, 0x5D, 0xDD, 0xE8, 0xA4, 0x75, + 0xFB, 0x89, 0xFC, 0xC6, 0x4E, 0xDA, 0x8F, 0xFC, 0x5B, 0xDE, 0x0C, 0x66, + 0x84, 0x99, 0x5A, 0xD4, 0xEC, 0xF8, 0xCC, 0xA7, 0xF6, 0xD9, 0xCF, 0x9D, + 0x3F, 0x17, 0xBF, 0xB3, 0x43, 0xB3, 0x33, 0x75, 0x17, 0x08, 0xE0, 0xE9, + 0xE2, 0xDE, 0x3F, 0x8F, 0xC0, 0xF3, 0x23, 0x80, 0xE7, 0x9E, 0xB3, 0xF4, + 0x59, 0x9F, 0xF4, 0x6B, 0x3D, 0x54, 0xA2, 0xB2, 0xF7, 0x10, 0x9D, 0xBC, + 0x1A, 0x95, 0x1C, 0x0A, 0xA5, 0xDD, 0x5C, 0x03, 0x78, 0x8A, 0xCF, 0xED, + 0xE0, 0x35, 0x82, 0x23, 0xA0, 0xC8, 0xA5, 0x9A, 0x39, 0xDD, 0x96, 0xBC, + 0xC1, 0x88, 0xF7, 0xB7, 0x23, 0xD9, 0x8E, 0x1C, 0xE1, 0xC7, 0x8C, 0xDC, + 0xA4, 0x47, 0x5A, 0xD2, 0x99, 0x78, 0x91, 0xCC, 0x93, 0xF3, 0x87, 0x86, + 0xC5, 0x9D, 0xCB, 0xC9, 0x24, 0x9F, 0x73, 0x92, 0xDC, 0xD3, 0xE3, 0x4C, + 0xB8, 0x00, 0x81, 0xCC, 0x46, 0x0B, 0x26, 0x59, 0x80, 0x80, 0xF6, 0x13, + 0xB9, 0x4D, 0x73, 0x78, 0xFD, 0x5B, 0x44, 0xE5, 0xFB, 0x22, 0x99, 0x5D, + 0x0F, 0x4A, 0x2B, 0x5D, 0x09, 0x3F, 0x34, 0x46, 0xC0, 0x08, 0x00, 0x12, + 0x09, 0x49, 0x13, 0x1E, 0xEB, 0x43, 0x44, 0xB0, 0xB7, 0xB8, 0xDE, 0x01, + 0x92, 0x5F, 0xE5, 0xF4, 0x41, 0x56, 0x4E, 0x0C, 0x71, 0x93, 0x2E, 0xA5, + 0xC6, 0x7A, 0x9F, 0x0C, 0x0C, 0x40, 0x80, 0x3E, 0xB5, 0x02, 0x08, 0xFC, + 0x2C, 0x10, 0xF8, 0xBD, 0xD0, 0x08, 0xFD, 0x65, 0x94, 0xC8, 0x28, 0xE7, + 0xD4, 0x57, 0x7C, 0xA2, 0xC3, 0x07, 0xA2, 0xB3, 0x9D, 0xE8, 0xDC, 0x86, + 0x07, 0x9A, 0x3E, 0x5E, 0x99, 0x3C, 0x48, 0xDA, 0xB6, 0xED, 0x26, 0xD6, + 0x0C, 0x22, 0x81, 0x70, 0x7E, 0xF0, 0xB4, 0x1B, 0xB2, 0x34, 0x6F, 0x94, + 0x79, 0x24, 0x1E, 0x16, 0x85, 0x23, 0x48, 0xC0, 0xFE, 0x5D, 0xE4, 0x63, + 0x5C, 0x0C, 0xEC, 0x5D, 0x15, 0xF8, 0x09, 0xB8, 0x52, 0x28, 0x51, 0x10, + 0xFD, 0x66, 0x0B, 0x94, 0x25, 0x90, 0x54, 0x18, 0x69, 0x51, 0x98, 0x68, + 0xC1, 0xDC, 0xCA, 0xBE, 0x8B, 0xF8, 0x63, 0xCE, 0xBA, 0xD9, 0x44, 0xC9, + 0x6F, 0xDC, 0x52, 0x48, 0x6A, 0x0A, 0x29, 0x85, 0x3F, 0x85, 0xF6, 0xE3, + 0xFF, 0x3B, 0xDB, 0xF4, 0x7C, 0x05, 0xDA, 0xCF, 0xD4, 0xEF, 0xC7, 0xD8, + 0x5A, 0x2B, 0x6D, 0xD7, 0x07, 0x22, 0xC7, 0x6D, 0x6E, 0x6F, 0x9A, 0x0B, + 0x6A, 0xB6, 0xF7, 0x8E, 0x99, 0x1E, 0x9A, 0x14, 0xA8, 0xE1, 0xFA, 0x3B, + 0x36, 0x2B, 0x6F, 0xD1, 0x36, 0x09, 0xF9, 0x61, 0x75, 0x5B, 0xEE, 0xD2, + 0xA2, 0xA4, 0xF8, 0xFF, 0x97, 0xCF, 0x3E, 0x13, 0x57, 0x7B, 0x07, 0xF7, + 0xAC, 0xEB, 0xEC, 0x35, 0x25, 0x7E, 0x58, 0xEF, 0x98, 0x8D, 0x3E, 0x23, + 0xB2, 0xB2, 0xFA, 0x31, 0x9C, 0xED, 0xBB, 0xC3, 0xDE, 0xF9, 0x99, 0xAA, + 0xBD, 0x6C, 0x78, 0xAE, 0x2B, 0x5A, 0x2D, 0x52, 0xD9, 0x9D, 0x76, 0x7F, + 0xF7, 0xEB, 0xBA, 0x7E, 0x5D, 0xD7, 0x45, 0x7F, 0x5D, 0xD7, 0xF5, 0x7A, + 0x23, 0xA2, 0x37, 0xF3, 0x46, 0x74, 0x9C, 0xCD, 0x9E, 0xF3, 0x11, 0xF2, + 0x39, 0x4D, 0xA1, 0xAD, 0x3B, 0x09, 0x71, 0x24, 0x54, 0x17, 0x5B, 0xC7, + 0x2A, 0xEF, 0xAA, 0xAF, 0xF3, 0xBC, 0x53, 0xE4, 0x66, 0x84, 0xB5, 0xC4, + 0x92, 0xD1, 0xE1, 0xDC, 0x7B, 0x37, 0x55, 0x8A, 0xDD, 0x47, 0x9C, 0xAB, + 0xFE, 0xD9, 0xFC, 0x13, 0x7E, 0xDC, 0x93, 0x1D, 0xC6, 0x04, 0x78, 0x75, + 0x49, 0x00, 0x21, 0x8A, 0xC3, 0x2C, 0x6E, 0xF1, 0x73, 0x40, 0xE0, 0x0C, + 0x54, 0xFC, 0xE0, 0x8A, 0x0F, 0x38, 0x61, 0x0D, 0x28, 0x2C, 0xBA, 0x70, + 0x42, 0x53, 0x01, 0x07, 0x31, 0x95, 0xDE, 0x60, 0x35, 0xA7, 0x62, 0xE6, + 0xFA, 0x8D, 0xAC, 0xB9, 0x0F, 0x7D, 0x65, 0x30, 0xD7, 0x84, 0xA1, 0x02, + 0xE7, 0xDA, 0x58, 0x28, 0x84, 0x50, 0x07, 0x8A, 0xBC, 0x5D, 0x83, 0x42, + 0x7A, 0x46, 0xF9, 0xD0, 0x6B, 0xBC, 0x8A, 0x08, 0xAB, 0x50, 0x81, 0x19, + 0xCD, 0xBD, 0xC7, 0x37, 0x55, 0xA3, 0x04, 0x4D, 0x30, 0xB3, 0x86, 0xAC, + 0x02, 0xDB, 0xAA, 0x58, 0xD9, 0x98, 0xD9, 0x9E, 0xE5, 0x27, 0x59, 0xF1, + 0x08, 0xBF, 0xC9, 0x8F, 0x20, 0xC5, 0x6F, 0xFB, 0xBE, 0x7A, 0xDB, 0x0E, + 0xBB, 0xD5, 0xA6, 0x5C, 0xA5, 0xDB, 0xC6, 0x1F, 0x17, 0xB4, 0xA1, 0x4E, + 0x4F, 0x0E, 0x74, 0x6E, 0xA3, 0x74, 0x2B, 0x55, 0x6E, 0xAB, 0xF7, 0xBC, + 0xD5, 0xAC, 0xD3, 0xB6, 0x18, 0xB7, 0x4C, 0x58, 0x0F, 0xEF, 0x0E, 0xDA, + 0xD8, 0x43, 0xAA, 0x75, 0x7A, 0xC8, 0x21, 0xBA, 0x8E, 0xC8, 0xA3, 0xB4, + 0x6D, 0xDF, 0x56, 0xFA, 0x8E, 0x75, 0x9C, 0xB0, 0x15, 0xEF, 0xFE, 0x11, + 0x4C, 0x58, 0x40, 0xF7, 0x12, 0xDE, 0x75, 0xD5, 0x87, 0x1B, 0xC8, 0xC1, + 0x88, 0x32, 0xA4, 0x40, 0x06, 0x07, 0x09, 0x36, 0xA0, 0x21, 0x02, 0x1D, + 0x06, 0x12, 0x20, 0x04, 0x24, 0xE0, 0x71, 0xD4, 0xB8, 0x01, 0x27, 0x70, + 0x28, 0x56, 0xD8, 0x04, 0x1E, 0x45, 0x9D, 0x24, 0x2C, 0x2D, 0x98, 0xC4, + 0x4E, 0x5A, 0x1E, 0x49, 0x6C, 0x81, 0xC8, 0x91, 0x58, 0xF9, 0xD5, 0x88, + 0x0E, 0x4F, 0xEF, 0x4D, 0xD2, 0x61, 0x13, 0x3B, 0x91, 0x7D, 0xCC, 0x83, + 0x53, 0x3C, 0x98, 0x05, 0x46, 0xF2, 0x09, 0x4C, 0x64, 0x98, 0x5B, 0x23, + 0x23, 0x23, 0x15, 0xD6, 0x57, 0x08, 0x6B, 0x8B, 0x84, 0x35, 0x68, 0x4B, + 0x84, 0x35, 0xDE, 0x72, 0x58, 0xA3, 0x46, 0x8D, 0x1A, 0x35, 0x20, 0x84, + 0x2E, 0x35, 0x5E, 0x6B, 0xBC, 0xC7, 0x5F, 0x6A, 0xBC, 0x40, 0x17, 0x78, + 0xAB, 0xD6, 0x24, 0x48, 0x68, 0x05, 0xC1, 0x7C, 0xED, 0xC0, 0x94, 0x3D, + 0xB0, 0xD4, 0xC3, 0x3B, 0x04, 0x51, 0xA0, 0xEA, 0x98, 0x03, 0x15, 0x60, + 0xAC, 0x21, 0x02, 0x66, 0x80, 0x81, 0x00, 0x0A, 0x60, 0x80, 0x00, 0xFC, + 0x94, 0xD6, 0x96, 0xA0, 0x1B, 0xDA, 0x12, 0x04, 0xE4, 0xF4, 0x84, 0xBA, + 0x29, 0xE1, 0x80, 0x94, 0x7C, 0x06, 0x75, 0xA6, 0x2E, 0x91, 0xDD, 0x8F, + 0xAE, 0x6F, 0xB2, 0x0F, 0x48, 0xE7, 0x79, 0xDD, 0x9F, 0xDF, 0xF6, 0x78, + 0x82, 0x89, 0xBC, 0xC5, 0xCB, 0xC9, 0xFC, 0x6A, 0x5D, 0x80, 0xC1, 0x4E, + 0x32, 0x5A, 0x59, 0x0F, 0x5B, 0xB6, 0x39, 0xAE, 0x36, 0xD7, 0x5C, 0xC7, + 0x3F, 0x72, 0x75, 0x33, 0xF5, 0x77, 0xCE, 0x83, 0x95, 0x3E, 0x8B, 0x57, + 0x98, 0x05, 0xC3, 0x2F, 0x54, 0x67, 0xD0, 0xE2, 0x94, 0x05, 0x11, 0x3B, + 0xF8, 0x01, 0x0F, 0x62, 0x70, 0x24, 0xC1, 0x08, 0xA8, 0x94, 0x15, 0xA7, + 0x2F, 0x2E, 0xF5, 0x74, 0xDB, 0x7E, 0xDF, 0xFF, 0xFF, 0xBB, 0xC0, 0xFF, + 0x87, 0xB4, 0x72, 0x1E, 0xB7, 0x71, 0x1C, 0x11, 0x31, 0x68, 0x3D, 0xA7, + 0xC9, 0x29, 0x31, 0x93, 0xE4, 0x7A, 0xFA, 0x95, 0xDB, 0xDF, 0x0B, 0x8D, + 0x44, 0x69, 0x6D, 0xF7, 0x5B, 0xEC, 0xD8, 0xB9, 0x5F, 0x7F, 0xCB, 0x3E, + 0x60, 0x4F, 0xF5, 0xB1, 0xFF, 0xEF, 0xEE, 0x04, 0xA2, 0xDF, 0xE2, 0x52, + 0xFB, 0xE9, 0x0D, 0xD9, 0xA6, 0xF5, 0x2E, 0xC4, 0x26, 0x31, 0xEC, 0x99, + 0x62, 0xD7, 0xAB, 0xF5, 0xDE, 0xBF, 0x71, 0x5F, 0x57, 0xD6, 0xE5, 0x7F, + 0xBD, 0xFD, 0x78, 0xC4, 0x7C, 0x87, 0x7D, 0xE9, 0xF4, 0xBF, 0xBC, 0xF1, + 0xFE, 0x37, 0x52, 0x8A, 0x45, 0x97, 0x56, 0xBF, 0xB5, 0x72, 0x22, 0x85, + 0x22, 0xE2, 0x44, 0x3E, 0xC9, 0x65, 0xBD, 0x30, 0xF1, 0xC6, 0x3B, 0x83, + 0xFE, 0x21, 0xA0, 0xF9, 0x59, 0x96, 0x95, 0xC1, 0xF4, 0xB3, 0xE0, 0x7C, + 0xB6, 0x93, 0x18, 0xB6, 0x41, 0x3B, 0xB2, 0x69, 0xAC, 0x4C, 0xE8, 0xD2, + 0xEA, 0x03, 0xC1, 0x86, 0xD0, 0x1E, 0xE9, 0x02, 0xC9, 0x71, 0x59, 0xA4, + 0xC3, 0xC1, 0xAE, 0x9A, 0x8B, 0x51, 0xE1, 0x6F, 0x97, 0xD4, 0x73, 0x25, + 0x14, 0x45, 0x7B, 0x43, 0xA1, 0xB8, 0x90, 0x37, 0x55, 0x52, 0xDA, 0x5E, + 0xB6, 0x5A, 0x42, 0xB7, 0x6F, 0x5B, 0x89, 0xBC, 0x25, 0xA5, 0xDB, 0x71, + 0xB3, 0xE3, 0x32, 0x6F, 0xDB, 0x3E, 0xCE, 0xA4, 0xE7, 0x7A, 0xB7, 0x49, + 0xF5, 0x43, 0x9D, 0x22, 0xBD, 0x44, 0x2E, 0xC6, 0xA8, 0x81, 0xF1, 0x53, + 0xEF, 0x1C, 0x65, 0x63, 0x36, 0x91, 0x48, 0xB4, 0x55, 0x2B, 0x12, 0x89, + 0x36, 0x11, 0xB5, 0x22, 0xD1, 0x76, 0x45, 0xA2, 0xF9, 0xC4, 0x4F, 0x6D, + 0x72, 0x89, 0xA8, 0x7A, 0x6F, 0xE3, 0x38, 0x6E, 0x3B, 0x37, 0x1B, 0x72, + 0xB3, 0xBB, 0x65, 0x59, 0xA2, 0xF3, 0x88, 0x59, 0xF3, 0xFB, 0xFF, 0xFD, + 0x0A, 0xBB, 0x26, 0x1C, 0xD1, 0xEA, 0x3D, 0x9F, 0x1D, 0xD7, 0x6B, 0x41, + 0xC7, 0x62, 0xD6, 0x75, 0x8B, 0x44, 0xA2, 0x18, 0xE3, 0xFB, 0xA2, 0xBE, + 0xE8, 0x6F, 0x91, 0xA8, 0x7A, 0x16, 0xBD, 0xE8, 0xDE, 0x68, 0x34, 0x4A, + 0xB5, 0x8C, 0xDC, 0x8F, 0xDC, 0x31, 0xD6, 0xEF, 0xFB, 0xAE, 0xAB, 0x7E, + 0xFF, 0x77, 0xFA, 0x5D, 0x8F, 0x57, 0x9B, 0xF0, 0xB4, 0xAA, 0xE7, 0xD6, + 0x5B, 0x11, 0xF5, 0x44, 0xA2, 0xCC, 0xFB, 0xFF, 0xEE, 0x24, 0xEE, 0x1B, + 0x32, 0x9A, 0x8C, 0x59, 0x8C, 0x8F, 0xD5, 0xF3, 0x3C, 0x96, 0xE7, 0x7B, + 0x9E, 0xC7, 0x8F, 0xBF, 0x55, 0xC3, 0x4D, 0x53, 0x53, 0x28, 0x5E, 0xED, + 0xA3, 0x9D, 0xBD, 0xEA, 0x3B, 0xFA, 0xCE, 0x83, 0x57, 0xEB, 0xB0, 0x80, + 0xBC, 0x2D, 0x18, 0xBC, 0xB4, 0xB8, 0x05, 0xDB, 0x74, 0xDC, 0x83, 0x0D, + 0x33, 0x1C, 0xD8, 0x31, 0xC4, 0x78, 0xAE, 0x54, 0x67, 0x45, 0xF8, 0x3D, + 0xDF, 0x62, 0x6A, 0xA5, 0x54, 0x2C, 0xDB, 0x73, 0x69, 0x37, 0xE7, 0xCE, + 0xF4, 0xA2, 0x41, 0xD7, 0x94, 0x41, 0xBC, 0x4F, 0xC5, 0x19, 0xA0, 0xAB, + 0xC6, 0xF7, 0xA7, 0xCC, 0x4F, 0xE7, 0x5F, 0xD7, 0x08, 0x85, 0x42, 0x79, + 0x70, 0x84, 0x42, 0xA1, 0xE6, 0x0E, 0x16, 0x33, 0x42, 0xA1, 0x50, 0xA6, + 0x9D, 0xEF, 0xA2, 0x3E, 0x5B, 0xE2, 0x4B, 0x1D, 0x69, 0x1D, 0x8A, 0xCA, + 0x8D, 0x34, 0x42, 0x65, 0xDD, 0x76, 0xB9, 0xC5, 0x6B, 0x74, 0xB7, 0x4D, + 0xE5, 0x32, 0x7D, 0xF6, 0xD2, 0x74, 0x6E, 0xE6, 0x62, 0xE5, 0xBB, 0xB5, + 0x30, 0xBE, 0xBD, 0xE3, 0x15, 0xF4, 0xD4, 0xF4, 0x6E, 0xE4, 0xFE, 0x2F, + 0x0D, 0xCE, 0x20, 0x1E, 0xA8, 0x3D, 0xB3, 0x2C, 0xB6, 0x4E, 0xA1, 0x55, + 0xA3, 0xA5, 0xB7, 0x2E, 0xEB, 0x61, 0x5E, 0x3D, 0x6E, 0x45, 0x2F, 0xFD, + 0x14, 0xDD, 0x14, 0x45, 0xBF, 0x25, 0x5D, 0x44, 0x0F, 0x65, 0x65, 0x20, + 0xD4, 0x0A, 0xF0, 0xB5, 0xCF, 0xD2, 0x83, 0x8E, 0x50, 0xDC, 0xAA, 0xE7, + 0x3F, 0xA0, 0x7B, 0x88, 0x27, 0x71, 0x85, 0x79, 0xE3, 0xE3, 0x3D, 0xD0, + 0x11, 0x71, 0xF9, 0xCF, 0xC1, 0x5F, 0x39, 0xC6, 0xE7, 0x2E, 0xC4, 0xDC, + 0xEF, 0x52, 0x57, 0xCE, 0x4C, 0xA2, 0xCC, 0x93, 0x74, 0xA6, 0xF5, 0xAF, + 0xFF, 0x50, 0xEA, 0x47, 0x84, 0x25, 0x11, 0xFE, 0x10, 0xD6, 0x53, 0xFA, + 0x53, 0x4B, 0x55, 0x5F, 0x8F, 0x89, 0xF7, 0x52, 0xF2, 0xB1, 0x3F, 0xFF, + 0x25, 0x94, 0xDE, 0xE8, 0x23, 0xD1, 0xE1, 0xE0, 0x9C, 0x63, 0xC8, 0x90, + 0x49, 0xCD, 0x06, 0x72, 0x30, 0x44, 0x88, 0x2D, 0x4C, 0x00, 0x93, 0x01, + 0x05, 0x04, 0x51, 0x03, 0x89, 0xFF, 0x92, 0xF4, 0xDA, 0xD2, 0xD2, 0xC2, + 0xD1, 0xC0, 0x09, 0xE9, 0x87, 0xD8, 0xE4, 0x19, 0xCC, 0x2F, 0x99, 0x2B, + 0xED, 0x3F, 0xB2, 0xC9, 0x06, 0x7F, 0x12, 0xDC, 0xF1, 0x1D, 0xE0, 0xEC, + 0xFB, 0xF9, 0x2E, 0x5C, 0x65, 0xC9, 0x9B, 0x5F, 0xB9, 0xDF, 0x2F, 0xCC, + 0x7F, 0x47, 0x43, 0xDB, 0xEB, 0x40, 0xD3, 0x8E, 0xEF, 0x08, 0x64, 0xD5, + 0xA0, 0x5F, 0x9E, 0x2F, 0xAB, 0x85, 0x5D, 0x11, 0x36, 0xE0, 0xC0, 0x13, + 0x8A, 0x94, 0x01, 0x08, 0x16, 0x73, 0x78, 0xC2, 0x90, 0x9D, 0xD6, 0x86, + 0x2A, 0x8C, 0xBE, 0x8C, 0xBC, 0xBC, 0xE6, 0x47, 0xF1, 0x30, 0xD5, 0x23, + 0x1F, 0xB1, 0x7C, 0xE8, 0x19, 0xA7, 0xC1, 0x13, 0x23, 0xDD, 0xB7, 0xA0, + 0xC0, 0x43, 0x18, 0xAF, 0x71, 0x19, 0x37, 0x79, 0xA6, 0x06, 0x23, 0x27, + 0x7F, 0xB2, 0x75, 0xA1, 0x5F, 0x8D, 0x79, 0x68, 0x52, 0x3C, 0x34, 0x50, + 0x83, 0xC9, 0x60, 0x0E, 0x6F, 0x02, 0x84, 0xA9, 0x52, 0xDD, 0xFD, 0x8C, + 0x11, 0x77, 0x8D, 0x3A, 0x9A, 0xE5, 0x84, 0xE4, 0x64, 0xA8, 0x9A, 0xF4, + 0xC4, 0x93, 0x9D, 0x48, 0x83, 0x77, 0x4E, 0x23, 0x8A, 0xDF, 0x53, 0x0F, + 0xC8, 0xB2, 0x2F, 0xF7, 0x0E, 0xBB, 0x8F, 0x3A, 0x92, 0x48, 0xA5, 0x9F, + 0x72, 0xC0, 0x5A, 0x56, 0xEA, 0xA0, 0xB6, 0x4C, 0xA8, 0x44, 0xFA, 0xD5, + 0x3D, 0x13, 0x4D, 0x1F, 0x90, 0x26, 0x4E, 0xE2, 0x33, 0x95, 0x5A, 0x0F, + 0x95, 0x10, 0x6B, 0x5D, 0x90, 0x8F, 0x68, 0x7E, 0x70, 0xC5, 0x69, 0xA2, + 0xCE, 0x21, 0xAA, 0x25, 0x37, 0xE3, 0xC7, 0x9B, 0xB2, 0x4F, 0x35, 0x41, + 0x42, 0xE3, 0x85, 0xCE, 0x4D, 0x90, 0xB0, 0xCC, 0x87, 0x6A, 0x0F, 0x77, + 0x62, 0xE2, 0x83, 0x04, 0xB6, 0xE9, 0x41, 0x42, 0x8C, 0x05, 0x73, 0x6C, + 0x85, 0x91, 0x9C, 0x54, 0x20, 0xEC, 0xD5, 0x7E, 0x2C, 0x19, 0x97, 0x76, + 0xD7, 0xBB, 0xEB, 0xB2, 0xAB, 0x31, 0x20, 0xA9, 0x08, 0x3C, 0xA1, 0x67, + 0x4A, 0x06, 0x8B, 0x37, 0x22, 0x06, 0x4C, 0x45, 0x94, 0xD8, 0xEF, 0x56, + 0x2B, 0xC8, 0xA2, 0xCB, 0x79, 0xAC, 0xDD, 0x2B, 0x17, 0xEF, 0x54, 0x06, + 0xE2, 0x78, 0xD7, 0xD7, 0xFF, 0x9C, 0x3B, 0xE9, 0xF0, 0x54, 0x5A, 0x8D, + 0x5E, 0xF4, 0xE7, 0x43, 0x97, 0xE7, 0x96, 0xF8, 0x76, 0xD2, 0x1F, 0x23, + 0x62, 0x66, 0x51, 0xB5, 0xCC, 0x2C, 0x62, 0x6A, 0x99, 0x45, 0x97, 0x6F, + 0x79, 0x7E, 0x37, 0xBD, 0x52, 0x9A, 0xAF, 0x2E, 0x6A, 0xFD, 0xA7, 0x2A, + 0xF4, 0xF3, 0xAF, 0x3F, 0xBD, 0x6E, 0x5C, 0xAD, 0x28, 0x8A, 0x7A, 0x77, + 0xBA, 0xBC, 0xC2, 0xB9, 0x2E, 0x13, 0x37, 0x1B, 0x9F, 0xF8, 0xAA, 0x8B, + 0xDC, 0xCE, 0x9F, 0x68, 0x52, 0x77, 0xDD, 0x99, 0xD3, 0xDC, 0x4C, 0x60, + 0xC8, 0xED, 0xD2, 0xE0, 0x70, 0x54, 0xCC, 0x7E, 0x80, 0xAE, 0x39, 0xFB, + 0x84, 0x83, 0x2A, 0xC5, 0xAC, 0x63, 0x9C, 0x45, 0xF8, 0x50, 0xA4, 0x25, + 0x8F, 0xB1, 0xA4, 0xC2, 0x1F, 0x31, 0xFE, 0x87, 0x0B, 0xCC, 0x3A, 0x08, + 0x3F, 0x3B, 0x19, 0x21, 0x84, 0xD3, 0x7B, 0x81, 0xD6, 0xA5, 0x5A, 0x25, + 0xC8, 0x59, 0xF4, 0x96, 0x15, 0xAF, 0x65, 0xFD, 0x34, 0xBD, 0xFA, 0xAE, + 0x9F, 0xD3, 0x9C, 0xF4, 0x5D, 0xD4, 0xFB, 0xAD, 0x6A, 0x41, 0xFE, 0xFE, + 0x5A, 0xBF, 0xE3, 0xB0, 0x1E, 0x08, 0x76, 0xFE, 0x40, 0x4D, 0x99, 0x41, + 0xB5, 0x4A, 0x67, 0xB3, 0x29, 0x7D, 0x37, 0x9A, 0xB2, 0xFD, 0xD6, 0x0C, + 0xD7, 0xD6, 0xEA, 0xA1, 0xAD, 0x5A, 0xCD, 0x7D, 0x83, 0x1E, 0xFC, 0xE1, + 0x9F, 0x2A, 0x91, 0x6C, 0x3F, 0xF4, 0x2C, 0x88, 0xFB, 0x87, 0x6C, 0x90, + 0x1A, 0xE6, 0x1A, 0x04, 0xBB, 0x34, 0xC8, 0x83, 0x34, 0xFF, 0x2B, 0x31, + 0x9A, 0x7A, 0x74, 0x64, 0x7D, 0x79, 0xD6, 0x95, 0xE0, 0xFC, 0x4E, 0x65, + 0x54, 0x4A, 0x2E, 0xC2, 0xB0, 0x63, 0xF0, 0x50, 0x65, 0x35, 0x3A, 0x66, + 0x5D, 0xEB, 0xD8, 0x10, 0x9B, 0x75, 0xFD, 0x23, 0x27, 0x9A, 0xC6, 0x3F, + 0xC3, 0x03, 0x41, 0x2E, 0x11, 0xC9, 0xAF, 0x27, 0x8E, 0x91, 0x5E, 0x21, + 0xC4, 0x36, 0xC8, 0x73, 0x69, 0x1F, 0xC9, 0xEC, 0xB9, 0x4C, 0xA9, 0xEE, + 0xB9, 0x1A, 0xCF, 0xC9, 0xE0, 0xF6, 0x68, 0x8A, 0xAA, 0xA4, 0xFE, 0x14, + 0xD7, 0x93, 0xBE, 0xD4, 0x15, 0x2A, 0x57, 0xCD, 0x39, 0x89, 0x9A, 0x4E, + 0x17, 0x87, 0x3A, 0x7D, 0x24, 0x75, 0xC6, 0x08, 0x94, 0xE6, 0x7D, 0x55, + 0x1B, 0x81, 0x33, 0x91, 0x81, 0x27, 0x6A, 0x57, 0x01, 0xF5, 0x30, 0x7D, + 0xA2, 0x49, 0x46, 0x5E, 0xD3, 0x97, 0x2A, 0x6D, 0x91, 0xDD, 0xFC, 0x24, + 0x83, 0xCD, 0x48, 0xC9, 0x2D, 0x4C, 0x40, 0x18, 0x96, 0xB5, 0xAA, 0xA3, + 0x98, 0x32, 0x1F, 0x66, 0xEA, 0x97, 0xAB, 0x7D, 0xB1, 0x91, 0xF8, 0x2E, + 0xCF, 0x71, 0x4D, 0x0D, 0xF3, 0x6F, 0xA4, 0x01, 0x8D, 0x82, 0x46, 0xA7, + 0xE1, 0x1E, 0x07, 0xEF, 0x55, 0x9E, 0xE2, 0xE9, 0xDA, 0x17, 0xC6, 0x17, + 0x96, 0xD6, 0x23, 0xB7, 0xF1, 0xB9, 0x44, 0x33, 0xBE, 0xA7, 0x74, 0x4E, + 0xFF, 0xF7, 0x53, 0xD4, 0x09, 0xB4, 0x3B, 0x67, 0xBD, 0xAB, 0x76, 0x3A, + 0x28, 0x77, 0xAA, 0x25, 0x29, 0x64, 0x4C, 0x9C, 0xF8, 0xF2, 0x26, 0xC9, + 0x3D, 0xC9, 0x35, 0xAC, 0xEA, 0x93, 0xC7, 0x18, 0x4F, 0xAC, 0x3D, 0xF9, + 0x09, 0x8D, 0xFF, 0x1F, 0x0D, 0x16, 0x4A, 0x83, 0x69, 0xCE, 0xC8, 0xB5, + 0x13, 0x67, 0xC4, 0x5E, 0x95, 0x37, 0xA2, 0xF2, 0x9E, 0x75, 0x33, 0x0F, + 0xD7, 0xC3, 0x7E, 0xEE, 0xEE, 0x66, 0x60, 0xE4, 0xE1, 0x1B, 0xCF, 0x2F, + 0xAE, 0xA6, 0xA2, 0x63, 0xEA, 0xE1, 0x58, 0x9A, 0xEA, 0xCC, 0x25, 0x30, + 0x95, 0x6B, 0x35, 0x26, 0x8E, 0x8A, 0xF1, 0x5A, 0xA6, 0xEF, 0x43, 0x5A, + 0xD6, 0xA5, 0x6B, 0xEA, 0x95, 0xAB, 0x89, 0xB2, 0xCE, 0x13, 0xFD, 0x37, + 0xF9, 0xA4, 0xB4, 0x50, 0xF4, 0xFB, 0x2A, 0x53, 0x81, 0x83, 0xE2, 0x1B, + 0x25, 0xEC, 0xD4, 0xC8, 0xB1, 0x9D, 0xDE, 0xA6, 0x4C, 0x7C, 0xA4, 0xAC, + 0x5F, 0x47, 0xB3, 0x7D, 0x34, 0x8A, 0x23, 0x2E, 0xC0, 0xCB, 0x25, 0xAF, + 0x1C, 0x28, 0xD4, 0xCA, 0xB6, 0xDD, 0x4B, 0x42, 0xD4, 0x05, 0x24, 0xFF, + 0xC9, 0xCF, 0xCF, 0x2B, 0xAD, 0xB8, 0x94, 0xDC, 0x6C, 0x5E, 0x04, 0x8C, + 0xBB, 0xF9, 0x12, 0x9C, 0x4D, 0x98, 0x68, 0xFA, 0xD1, 0xBA, 0x70, 0xFA, + 0x9F, 0xE8, 0xB1, 0x65, 0xF1, 0x68, 0x34, 0x1A, 0x8D, 0x46, 0x23, 0x96, + 0x8A, 0xBA, 0x24, 0xEA, 0x92, 0xFF, 0x24, 0xAA, 0x7A, 0x1B, 0xDF, 0x44, + 0x66, 0xD2, 0xC3, 0x49, 0xAA, 0xF0, 0xFE, 0x5F, 0xA9, 0x5C, 0x1E, 0xE9, + 0x17, 0x08, 0xBC, 0x5B, 0xE1, 0x07, 0xF6, 0xAE, 0xD8, 0x03, 0xAB, 0xAB, + 0xF1, 0x01, 0xCD, 0xD7, 0xCD, 0x7C, 0xFE, 0x88, 0x7E, 0xBF, 0xBC, 0xDA, + 0x27, 0x63, 0xF2, 0x05, 0xB7, 0x51, 0xEF, 0xB7, 0x55, 0x13, 0xA8, 0x0B, + 0x2E, 0xFB, 0x91, 0x5B, 0xAA, 0x9B, 0xA7, 0x17, 0xAF, 0xD6, 0xB3, 0x4D, + 0xAB, 0xFE, 0x9A, 0xFE, 0xD1, 0x25, 0x77, 0xA0, 0x78, 0xCD, 0xEB, 0xF3, + 0xA9, 0x10, 0xA3, 0xD1, 0x3F, 0x29, 0xC4, 0xE8, 0x8B, 0xA3, 0xD2, 0x90, + 0x9C, 0xAB, 0xA9, 0x76, 0xE4, 0x27, 0x39, 0xCD, 0xA4, 0xF1, 0x70, 0x2A, + 0x21, 0x13, 0x76, 0x93, 0x65, 0x7B, 0xBC, 0xD8, 0xB4, 0x1E, 0x79, 0xAB, + 0xCD, 0x10, 0x53, 0x91, 0x5B, 0xAD, 0xBC, 0x66, 0x59, 0x6B, 0xFF, 0x78, + 0xE9, 0x37, 0x92, 0xD6, 0xFB, 0xBF, 0x14, 0xE2, 0xFF, 0xFF, 0x8F, 0xDE, + 0x09, 0x71, 0x02, 0xE4, 0xE4, 0xE3, 0xDE, 0x43, 0x68, 0x0B, 0x6A, 0x93, + 0x1B, 0x44, 0xE6, 0x0C, 0xC3, 0x9F, 0x99, 0xFF, 0x48, 0x7B, 0xF4, 0x98, + 0x79, 0x8C, 0xDD, 0x33, 0x97, 0x99, 0x99, 0xFB, 0xF6, 0xCC, 0xB6, 0x6B, + 0x4E, 0xF3, 0x99, 0xFE, 0x5F, 0x4E, 0xDB, 0xA5, 0xAE, 0xBC, 0x74, 0x95, + 0xBF, 0x29, 0xA5, 0x35, 0x4C, 0xC3, 0x33, 0x7C, 0xDA, 0xAC, 0xC3, 0x39, + 0x8C, 0xC3, 0x5D, 0x4A, 0x4A, 0x0A, 0xA7, 0x7A, 0x65, 0x85, 0xDF, 0x2B, + 0xCC, 0x5F, 0x61, 0xE6, 0x7B, 0x6C, 0x53, 0x52, 0x52, 0x52, 0x58, 0xC5, + 0x63, 0xEE, 0x56, 0x61, 0xBE, 0x0A, 0x73, 0xAB, 0x30, 0xF3, 0x6F, 0xE3, + 0xBF, 0xAD, 0xFC, 0xCA, 0x36, 0x15, 0x04, 0x7B, 0x8A, 0x7C, 0x90, 0xF8, + 0x94, 0x0B, 0xBD, 0x32, 0x4A, 0x10, 0xD0, 0xE7, 0xD3, 0x73, 0x7A, 0x83, + 0x72, 0xA3, 0x73, 0x94, 0x9C, 0xDF, 0xE0, 0x1C, 0x85, 0xD6, 0x22, 0xB6, + 0x47, 0xED, 0x41, 0x7B, 0x50, 0xD4, 0x7B, 0xD4, 0x50, 0x94, 0x4D, 0xA1, + 0xE1, 0x52, 0x35, 0xA5, 0x86, 0x63, 0x9A, 0xD2, 0xDB, 0x6F, 0x3A, 0xD4, + 0x53, 0x66, 0xB8, 0xA3, 0x74, 0x28, 0x21, 0x52, 0x2A, 0x1C, 0x14, 0x4F, + 0x1E, 0xD4, 0x55, 0x3C, 0xA8, 0x4F, 0x20, 0x54, 0x10, 0xA9, 0x20, 0x02, + 0x96, 0xAA, 0xA8, 0x64, 0x26, 0xD6, 0x54, 0xB1, 0x8C, 0x90, 0xA1, 0x91, + 0x01, 0x80, 0x09, 0x10, 0x00, 0xF3, 0x85, 0x01, 0x14, 0x0A, 0x89, 0x83, + 0x51, 0x18, 0x05, 0xE2, 0x44, 0x9A, 0x7E, 0x94, 0xE3, 0x87, 0xDB, 0x09, + 0x26, 0x50, 0x0D, 0xC6, 0x90, 0x24, 0x0F, 0x21, 0x03, 0x8C, 0x01, 0x8A, + 0x08, 0x40, 0x30, 0x00, 0x03, 0x00, 0x08, 0x80, 0x00, 0x0C, 0x44, 0x3B, + 0x95, 0xDE, 0x98, 0x71, 0xE3, 0xE0, 0x3E, 0x45, 0x4C, 0x2B, 0x47, 0x4C, + 0x4E, 0x64, 0xEA, 0xDB, 0xCA, 0x12, 0x15, 0xCB, 0xB4, 0x1B, 0xF7, 0x96, + 0x90, 0xA3, 0xC5, 0xD1, 0x0C, 0x66, 0x2C, 0x10, 0xBB, 0x18, 0xB4, 0x2D, + 0x0F, 0x06, 0x48, 0x7B, 0x95, 0xE1, 0x1A, 0x05, 0x7B, 0x95, 0x53, 0x71, + 0xC1, 0x34, 0xE9, 0xA9, 0x2B, 0x31, 0x8D, 0x97, 0xC9, 0x33, 0xE0, 0x4C, + 0x71, 0x03, 0x79, 0x55, 0xF0, 0x80, 0x90, 0x8F, 0x0A, 0x65, 0xC5, 0xCC, + 0x7B, 0xCD, 0xB1, 0x03, 0x43, 0xE3, 0x8D, 0x36, 0x99, 0x6D, 0x72, 0xB7, + 0x90, 0x50, 0xEE, 0x71, 0x27, 0x6A, 0xB1, 0x99, 0x53, 0x77, 0x1D, 0xC3, + 0xAB, 0xA3, 0x9D, 0xAC, 0x72, 0xD5, 0x39, 0x7B, 0x8E, 0xB4, 0xD8, 0xC2, + 0x71, 0x25, 0x8B, 0xBB, 0x9E, 0xA2, 0x71, 0x91, 0x67, 0xD3, 0x3A, 0xDB, + 0xAF, 0xE8, 0x50, 0x09, 0x7C, 0x67, 0x27, 0x9B, 0xB4, 0x4E, 0x1C, 0x18, + 0x2A, 0xD8, 0x8F, 0x05, 0x21, 0x07, 0xAD, 0x44, 0x0B, 0x68, 0xDE, 0xC5, + 0xF6, 0xC8, 0x52, 0x3A, 0x8C, 0xE7, 0xC3, 0x42, 0xD1, 0xBC, 0x78, 0xC9, + 0x0B, 0xF7, 0xC3, 0x82, 0x52, 0xFF, 0x13, 0xD3, 0x83, 0xA1, 0x8D, 0x23, + 0x9A, 0xA3, 0x94, 0x46, 0x88, 0xE4, 0x83, 0x77, 0xE1, 0xC2, 0x60, 0x32, + 0x8E, 0x25, 0x4D, 0xEC, 0x9D, 0x50, 0xE5, 0xE5, 0x4E, 0x98, 0x5C, 0x8D, + 0xCD, 0x70, 0x81, 0x95, 0x58, 0xA5, 0xD6, 0x22, 0xAC, 0x96, 0x70, 0x5B, + 0x44, 0x25, 0xD4, 0x2E, 0x43, 0x33, 0xDD, 0x6D, 0x7C, 0x62, 0x38, 0x03, + 0xA8, 0x02, 0x32, 0xF7, 0xD9, 0xB0, 0xBC, 0x59, 0x89, 0x59, 0x74, 0x14, + 0xEE, 0x8D, 0x84, 0x58, 0xE4, 0xD4, 0x2A, 0xEC, 0x6C, 0xCA, 0xAB, 0x72, + 0x50, 0x58, 0x7C, 0x04, 0x48, 0xF8, 0x7F, 0x6D, 0x02, 0x6F, 0x29, 0x0E, + 0xA0, 0x61, 0x85, 0x4A, 0x88, 0x31, 0x54, 0x9F, 0xE7, 0x30, 0x91, 0xF9, + 0x28, 0xDF, 0x86, 0xCD, 0x17, 0x0A, 0xA2, 0xA1, 0xDF, 0x27, 0xF0, 0xDF, + 0x04, 0xA0, 0x02, 0x22, 0xF7, 0xA4, 0xA5, 0xDD, 0x12, 0x52, 0xBF, 0x34, + 0xF9, 0x56, 0x80, 0x09, 0xFB, 0x7F, 0xAA, 0x93, 0x06, 0xA0, 0xAB, 0x9B, + 0xB6, 0x02, 0x3D, 0x70, 0x2F, 0x20, 0x76, 0x07, 0xB8, 0xF6, 0x22, 0x4C, + 0x76, 0xC5, 0x33, 0x66, 0xA1, 0xFC, 0x8A, 0x0C, 0xAF, 0x97, 0x29, 0xD4, + 0xAB, 0x02, 0x9C, 0x4E, 0x6A, 0xF6, 0x0E, 0x7F, 0xB8, 0x26, 0x71, 0x49, + 0xD9, 0x64, 0x88, 0x20, 0xA4, 0xC6, 0x21, 0xEA, 0x6E, 0xED, 0x68, 0x92, + 0xB5, 0x08, 0x08, 0x23, 0x31, 0x18, 0x33, 0x7D, 0x62, 0x6D, 0x13, 0xE5, + 0xF0, 0x98, 0x05, 0x7C, 0x5A, 0xBE, 0xF6, 0x8D, 0xFD, 0x9F, 0x44, 0x2B, + 0xFC, 0x47, 0xD6, 0x20, 0xE1, 0x2D, 0xAA, 0x3F, 0x99, 0x12, 0xF0, 0x75, + 0x9D, 0xD4, 0x65, 0xF6, 0xEE, 0xFB, 0xA7, 0xD4, 0x53, 0x34, 0xE6, 0xC8, + 0x5C, 0x8C, 0xDC, 0x01, 0x5A, 0xC5, 0x27, 0x31, 0xC9, 0xD2, 0x3B, 0x2D, + 0x1B, 0x68, 0x92, 0x22, 0x4A, 0xBF, 0x77, 0x78, 0x51, 0xAD, 0x20, 0xBC, + 0x97, 0xAB, 0x0B, 0xFA, 0xF1, 0xA6, 0xDC, 0x91, 0x47, 0x5B, 0x27, 0xCC, + 0x22, 0xAA, 0xF1, 0xCF, 0xCD, 0x5D, 0x18, 0xFA, 0x73, 0xEB, 0xF4, 0xCE, + 0xF4, 0x50, 0x30, 0x6C, 0x2D, 0xD4, 0xF6, 0xA6, 0x0A, 0xB6, 0xB3, 0x13, + 0xDF, 0xD6, 0x6F, 0x82, 0x0A, 0xBC, 0x41, 0x87, 0x71, 0x7E, 0x4D, 0xF4, + 0x29, 0x85, 0xDC, 0x24, 0xD4, 0x58, 0xF8, 0x80, 0x0A, 0x35, 0x11, 0x84, + 0xDC, 0xFE, 0xF8, 0x41, 0x62, 0x9D, 0x10, 0x65, 0x84, 0xB4, 0x55, 0x74, + 0xCF, 0x52, 0xBE, 0x2E, 0xB7, 0x2A, 0xA6, 0x8A, 0xB1, 0xDD, 0xE2, 0xEB, + 0x95, 0x3E, 0xDA, 0x19, 0xC8, 0x09, 0xD4, 0x9A, 0xDA, 0xE0, 0x94, 0x97, + 0xDB, 0x80, 0x63, 0x19, 0xEC, 0xB3, 0xCA, 0x71, 0x6E, 0xBA, 0x28, 0xA6, + 0xA4, 0x18, 0x19, 0x46, 0xB6, 0x84, 0x38, 0x4B, 0xD3, 0x84, 0xBF, 0x08, + 0x20, 0x86, 0x1E, 0xEC, 0xEC, 0x54, 0x05, 0xBC, 0x49, 0x2E, 0x51, 0xE4, + 0x7F, 0x44, 0x46, 0xFB, 0xB9, 0xD5, 0xCA, 0xCC, 0x18, 0x55, 0xBC, 0x8A, + 0x34, 0x39, 0xED, 0x92, 0x2C, 0x39, 0x0A, 0x36, 0xB7, 0x05, 0xCD, 0xF6, + 0x47, 0x92, 0x03, 0xE4, 0x3A, 0x3C, 0x1F, 0x5A, 0x85, 0xD5, 0x32, 0x1D, + 0x89, 0xCA, 0xCD, 0xDB, 0xEB, 0xCE, 0x87, 0xE0, 0x41, 0x9B, 0xB9, 0xA0, + 0xEC, 0x53, 0x96, 0x3D, 0xC5, 0x86, 0xD8, 0x22, 0x47, 0xA8, 0x99, 0x7D, + 0xEF, 0x47, 0xC3, 0xB6, 0xDD, 0xA0, 0xD1, 0x44, 0x7B, 0xAD, 0xB2, 0x31, + 0xA1, 0xE7, 0x91, 0x54, 0xD3, 0x93, 0xB9, 0x32, 0x07, 0xDC, 0xF2, 0xC2, + 0x9C, 0x69, 0x02, 0x1C, 0x47, 0x21, 0x59, 0x0E, 0x5E, 0x16, 0x49, 0x7D, + 0x52, 0x95, 0xE7, 0xB4, 0x5A, 0x97, 0x13, 0xA5, 0x20, 0xB3, 0x0A, 0x82, + 0x58, 0x98, 0xD3, 0x5E, 0x25, 0x45, 0xF6, 0xF1, 0x07, 0x0A, 0x6B, 0xF6, + 0xDA, 0xE4, 0x6C, 0x4F, 0xA3, 0x84, 0x9B, 0xE9, 0x90, 0xED, 0x79, 0x22, + 0xB9, 0x26, 0x0D, 0x10, 0xB9, 0xA3, 0x9B, 0x4B, 0x9B, 0x10, 0xE9, 0x15, + 0xEE, 0xC2, 0xE6, 0xE2, 0x14, 0x8D, 0x43, 0xA7, 0x96, 0xDD, 0xA0, 0x41, + 0x9F, 0xE2, 0x1D, 0x03, 0xB0, 0x43, 0x80, 0x37, 0x5E, 0x97, 0xEB, 0x16, + 0xE4, 0x22, 0xA3, 0xA8, 0x77, 0x16, 0xBC, 0xF1, 0xD1, 0xA9, 0x9B, 0x11, + 0xEB, 0xA3, 0x81, 0x02, 0x5E, 0xDD, 0x0C, 0xA4, 0xF5, 0x85, 0x65, 0x06, + 0x11, 0x76, 0xF0, 0x6A, 0x37, 0x6F, 0x28, 0x6F, 0x94, 0xC5, 0x25, 0x9A, + 0x63, 0xD5, 0xB2, 0xBD, 0xC6, 0xCB, 0x51, 0xEF, 0x0C, 0x61, 0x45, 0x11, + 0xC1, 0x9D, 0x3C, 0x9D, 0xEB, 0x12, 0x25, 0x83, 0xED, 0xD2, 0xC5, 0xAE, + 0xDC, 0x6F, 0x44, 0xA8, 0xE0, 0xBC, 0xA2, 0x77, 0xB8, 0x0F, 0x18, 0x22, + 0x67, 0x7B, 0xCD, 0xE2, 0x89, 0x67, 0x67, 0x17, 0xFD, 0x63, 0x64, 0x80, + 0xF6, 0xBE, 0xB2, 0xCA, 0xF8, 0xD1, 0x1B, 0x39, 0xD7, 0x66, 0xB8, 0x7E, + 0x25, 0x9C, 0x2C, 0x0B, 0xC3, 0xCE, 0xE5, 0xA1, 0x74, 0x79, 0x7C, 0xDF, + 0x07, 0xC1, 0x84, 0x82, 0x56, 0xB7, 0x34, 0x80, 0xDF, 0xBB, 0x85, 0x81, + 0x0C, 0x04, 0x30, 0x78, 0x1F, 0x44, 0x64, 0x03, 0xB9, 0x89, 0x48, 0x92, + 0x1E, 0xC8, 0x9F, 0xC5, 0x6D, 0x63, 0x0D, 0x8E, 0xF7, 0x71, 0x09, 0x41, + 0xD1, 0x25, 0x58, 0x35, 0xDE, 0x4E, 0x8E, 0xBA, 0x8D, 0x38, 0x54, 0xAF, + 0x80, 0x75, 0x54, 0x69, 0x6E, 0x4E, 0x4B, 0xE4, 0xCB, 0x89, 0xFE, 0x05, + 0x7A, 0x59, 0xB2, 0xB9, 0xA7, 0xD3, 0x61, 0x89, 0xDB, 0x7D, 0x08, 0x79, + 0xFE, 0xE6, 0x0F, 0x1E, 0x80, 0x95, 0xCD, 0x02, 0x4A, 0xA0, 0xC6, 0xCC, + 0x55, 0x93, 0x96, 0xA2, 0xFA, 0x29, 0x45, 0xC7, 0xCF, 0x43, 0x2C, 0x2D, + 0x9F, 0x33, 0xC8, 0x54, 0x72, 0xAC, 0x69, 0xAF, 0xF7, 0xCA, 0xFF, 0x43, + 0x94, 0x78, 0xDE, 0xAF, 0xF3, 0x84, 0xFB, 0xBB, 0x9F, 0x65, 0x8E, 0x52, + 0x14, 0x44, 0xF1, 0x30, 0x16, 0x42, 0xFA, 0x89, 0x8D, 0x8D, 0x0D, 0x3A, + 0x66, 0x93, 0x27, 0x5C, 0x4D, 0x32, 0x3D, 0x7C, 0xD2, 0x7B, 0xDA, 0xFC, + 0xD1, 0xE0, 0xB0, 0xB6, 0x40, 0xD1, 0xB9, 0xAD, 0x8D, 0xC4, 0xBF, 0x09, + 0x9E, 0xE8, 0x25, 0xC2, 0xEA, 0x36, 0xC2, 0xAD, 0x07, 0x7F, 0x51, 0xBD, + 0xD9, 0x4F, 0x8B, 0x2D, 0x60, 0xB6, 0xA7, 0x0A, 0x1E, 0x8E, 0xFD, 0x79, + 0x80, 0x77, 0x39, 0x39, 0x3F, 0x92, 0xED, 0x95, 0x6A, 0xFB, 0x11, 0x71, + 0x18, 0xA4, 0x3C, 0x37, 0x3D, 0xBB, 0xAD, 0x12, 0x81, 0x81, 0xA2, 0xDF, + 0x6C, 0x3C, 0xE4, 0xAE, 0x74, 0x3B, 0x7F, 0x4F, 0x3B, 0x2A, 0x16, 0xB9, + 0xA8, 0xA7, 0xB4, 0xD1, 0xF1, 0x5F, 0x4D, 0xA0, 0x97, 0xF2, 0x6B, 0x00, + 0x4D, 0x8E, 0xFF, 0xDE, 0x97, 0xD9, 0x2E, 0x49, 0x8A, 0x99, 0x07, 0xDB, + 0xEE, 0x26, 0x7B, 0xAC, 0x1A, 0xFB, 0x04, 0xD1, 0x44, 0xA9, 0xAC, 0x1A, + 0xCA, 0x1F, 0x66, 0x41, 0xD5, 0x51, 0xEB, 0x3C, 0xD8, 0xCC, 0x66, 0x14, + 0x0D, 0xCF, 0xAD, 0x1A, 0x8C, 0x4E, 0xE4, 0x3A, 0xF5, 0xD7, 0xC6, 0x2E, + 0x8F, 0xEB, 0xE7, 0x43, 0xB7, 0x46, 0x30, 0xDD, 0x44, 0x8F, 0xB2, 0x90, + 0x2B, 0xCE, 0xC3, 0x0F, 0x4E, 0xD0, 0x8E, 0xA9, 0xC0, 0xCD, 0x96, 0x69, + 0xB1, 0xA4, 0x8F, 0x1F, 0x6E, 0x43, 0x1F, 0x17, 0x13, 0xDE, 0xEB, 0xD2, + 0xB1, 0xED, 0x01, 0xD5, 0x58, 0x22, 0xD7, 0x93, 0x77, 0x37, 0x25, 0x29, + 0x0D, 0x07, 0x1E, 0x28, 0xA6, 0x96, 0x03, 0xD4, 0x4A, 0xCD, 0x5A, 0x4B, + 0x3D, 0x28, 0x5A, 0xF2, 0xB3, 0xC1, 0x6E, 0x99, 0xAA, 0x78, 0x08, 0x8D, + 0x77, 0xB3, 0x0A, 0x86, 0xBF, 0x41, 0x42, 0x96, 0x4E, 0x35, 0xA1, 0xC5, + 0x43, 0xB5, 0xAE, 0x38, 0xE7, 0xBB, 0x46, 0xB7, 0x79, 0x19, 0x56, 0x7C, + 0x0B, 0x4F, 0xFC, 0x97, 0xFD, 0xD4, 0xCC, 0x5E, 0xFD, 0x51, 0xB5, 0x7A, + 0xD7, 0x6C, 0x5C, 0x45, 0x7E, 0x20, 0x3A, 0xA7, 0x6F, 0xF8, 0xD4, 0x30, + 0xBB, 0x78, 0xB5, 0xA2, 0x25, 0x9E, 0x75, 0x93, 0x3B, 0xB0, 0x05, 0x90, + 0x55, 0xCA, 0x57, 0xDB, 0x97, 0xC5, 0x87, 0xF8, 0x6C, 0xA1, 0x20, 0x9F, + 0xBB, 0xD5, 0x54, 0xB1, 0xD4, 0xD6, 0xD6, 0xCA, 0xF3, 0xBA, 0xE6, 0x0B, + 0x66, 0xFA, 0x40, 0xFD, 0xAB, 0xE6, 0x87, 0xC2, 0x80, 0x0F, 0xAE, 0xA4, + 0x33, 0xC0, 0xC3, 0x58, 0x42, 0x51, 0xC8, 0x23, 0xA0, 0x4C, 0x2A, 0x62, + 0x05, 0x1B, 0x97, 0x26, 0x66, 0x1D, 0x81, 0x2B, 0x1B, 0xA8, 0x4D, 0x69, + 0x9E, 0x16, 0xB0, 0x4A, 0xDA, 0x78, 0xF2, 0x00, 0x78, 0x71, 0x9E, 0xB4, + 0x9E, 0x9C, 0xE8, 0xE7, 0xDC, 0x28, 0xE1, 0x92, 0xC6, 0xDB, 0xBB, 0xAE, + 0x36, 0x81, 0x51, 0x65, 0x3E, 0xFF, 0xD4, 0x5E, 0x22, 0x07, 0x16, 0x6A, + 0xA8, 0x01, 0x20, 0x0B, 0x14, 0xB7, 0x50, 0xAF, 0xE7, 0x89, 0x6C, 0x0D, + 0x6D, 0xA8, 0x5F, 0x32, 0x41, 0xA2, 0xD6, 0x0A, 0x8B, 0x1E, 0xDB, 0xEB, + 0x1C, 0xC0, 0x12, 0x5B, 0xBF, 0x19, 0xAF, 0x4E, 0x7D, 0x34, 0x33, 0x8E, + 0x33, 0x71, 0x1F, 0xF1, 0x45, 0x69, 0x68, 0x7D, 0xC1, 0x66, 0x07, 0xC5, + 0x58, 0xEF, 0x78, 0x58, 0x0A, 0x87, 0x9B, 0x6C, 0xA6, 0xF2, 0xF7, 0x9A, + 0x70, 0x93, 0xFE, 0x2D, 0x6E, 0xAE, 0x0F, 0x43, 0x7F, 0x70, 0xCF, 0x86, + 0x95, 0x41, 0x26, 0xAC, 0xA1, 0xC1, 0x87, 0xBE, 0x74, 0x68, 0xC2, 0x94, + 0xEC, 0x51, 0x5D, 0xFA, 0xB0, 0x32, 0x34, 0xA7, 0x3F, 0x17, 0x1C, 0xA4, + 0xB3, 0x42, 0x6C, 0x76, 0xF2, 0x3D, 0x11, 0x51, 0x57, 0x9F, 0xEF, 0x36, + 0x73, 0x0A, 0xD3, 0x1E, 0x09, 0xD1, 0x74, 0x62, 0xC4, 0xB8, 0xF4, 0x1C, + 0x14, 0x7B, 0xAC, 0x12, 0x4D, 0x8D, 0x1C, 0xCD, 0x67, 0xF6, 0x2D, 0x0F, + 0x2F, 0x0C, 0x54, 0x78, 0x0C, 0x39, 0xCD, 0xBC, 0x50, 0x2C, 0xCB, 0x1F, + 0xA8, 0x93, 0xFF, 0x77, 0x38, 0x8B, 0x19, 0xEF, 0x56, 0x7A, 0xD8, 0xB8, + 0xB4, 0x9F, 0x4D, 0xDD, 0xC9, 0x1F, 0x09, 0x2B, 0x0E, 0xC0, 0x84, 0x95, + 0x0B, 0xCF, 0xB3, 0x6C, 0x83, 0xCD, 0x1E, 0x4E, 0x1B, 0x3A, 0x55, 0x81, + 0xA7, 0x48, 0x9B, 0x74, 0x93, 0xEC, 0x9B, 0x65, 0x62, 0x11, 0x2C, 0x92, + 0x01, 0x54, 0xB6, 0x9E, 0x63, 0x56, 0xA4, 0x0F, 0xCB, 0x82, 0xE0, 0x9B, + 0x3A, 0x33, 0x6E, 0x86, 0x0C, 0x4C, 0xB3, 0x02, 0xF8, 0x0F, 0x43, 0xC5, + 0x81, 0xF0, 0x50, 0x39, 0xB6, 0x1D, 0xD7, 0xEF, 0xEF, 0xB1, 0xF6, 0x00, + 0x53, 0x56, 0x48, 0xE7, 0x44, 0x6A, 0x1F, 0xCB, 0xC9, 0x98, 0x57, 0xA4, + 0xE5, 0xBE, 0xD4, 0x64, 0xD8, 0x6B, 0xBF, 0x4D, 0x7A, 0x74, 0xE1, 0x36, + 0x08, 0xC8, 0x19, 0xF4, 0xC0, 0x28, 0xCA, 0xB3, 0x9E, 0x87, 0xFA, 0x78, + 0x83, 0x49, 0xED, 0x3E, 0x84, 0xF9, 0x54, 0xD6, 0x8D, 0x4F, 0x8C, 0xC8, + 0xC4, 0x77, 0xCB, 0x56, 0xE9, 0x1E, 0x73, 0x26, 0x81, 0xFE, 0xE0, 0x9E, + 0xF0, 0x3E, 0x32, 0x42, 0x06, 0x29, 0x20, 0xC7, 0x88, 0xE6, 0x7F, 0x75, + 0x1F, 0xED, 0x4F, 0x87, 0x82, 0x2C, 0x35, 0x06, 0x20, 0x12, 0x80, 0x27, + 0x5A, 0x44, 0x1C, 0x34, 0x66, 0xE2, 0x2E, 0x2D, 0xD5, 0xAB, 0x33, 0xC0, + 0xDD, 0x4A, 0xE3, 0x30, 0x75, 0x19, 0xF8, 0x7F, 0xF8, 0x16, 0x04, 0xD6, + 0xBE, 0xFA, 0xF6, 0x37, 0xF1, 0xFB, 0x55, 0x76, 0x84, 0xBE, 0x90, 0x7F, + 0xDF, 0x49, 0x9A, 0x84, 0xD6, 0x4D, 0x0D, 0x2B, 0x1F, 0x5D, 0x94, 0xD4, + 0x4A, 0x66, 0x26, 0x66, 0x52, 0x36, 0x15, 0xF8, 0x69, 0x4E, 0x9B, 0x5E, + 0xAE, 0x35, 0x60, 0x43, 0x92, 0xF8, 0x8A, 0xF3, 0x19, 0xED, 0xCA, 0x48, + 0xFE, 0x60, 0x15, 0x55, 0x68, 0x37, 0x7A, 0xF7, 0xA1, 0xE5, 0x12, 0x5A, + 0xDC, 0x32, 0xFE, 0x65, 0x43, 0x7D, 0x9F, 0xE8, 0x6C, 0x92, 0xDD, 0xD3, + 0x3F, 0x1A, 0x0B, 0x95, 0xAD, 0x74, 0xDE, 0x16, 0x77, 0xA5, 0xBA, 0x88, + 0x67, 0x35, 0x1C, 0xA3, 0xBE, 0xB9, 0x0C, 0xC1, 0x38, 0x6E, 0x31, 0x0A, + 0x16, 0xD0, 0x43, 0x03, 0xE3, 0x3B, 0xF0, 0xFA, 0x1E, 0xCE, 0xB2, 0x0A, + 0x92, 0x60, 0x85, 0x1F, 0xBB, 0xD7, 0xBB, 0x73, 0xD7, 0xA8, 0x29, 0x09, + 0x4E, 0x19, 0x58, 0x35, 0x7C, 0x4F, 0x52, 0x2C, 0x1F, 0xA6, 0xE1, 0x13, + 0x5A, 0x9F, 0x34, 0x6A, 0xB5, 0x44, 0x7D, 0xC0, 0xE5, 0x47, 0xF8, 0x79, + 0x4D, 0xBC, 0xBF, 0xB9, 0xD0, 0x7D, 0xD9, 0xA9, 0x83, 0x6F, 0xAB, 0xBB, + 0xF9, 0xAE, 0xFE, 0x0D, 0x85, 0xE2, 0x98, 0x22, 0x84, 0x58, 0xC1, 0xD8, + 0xF3, 0x7F, 0xC0, 0x40, 0xA8, 0xE3, 0xFB, 0x1C, 0x9D, 0xE7, 0xA5, 0xE7, + 0xEC, 0x33, 0x75, 0x25, 0x52, 0xC4, 0xC6, 0x5B, 0x71, 0xFD, 0x69, 0x20, + 0xA5, 0x42, 0x1C, 0x54, 0xCF, 0x5B, 0x21, 0x30, 0x4E, 0xE3, 0x71, 0xAA, + 0x76, 0x34, 0x1D, 0x2F, 0x80, 0xCF, 0xCB, 0x83, 0xEF, 0x04, 0xF4, 0xC5, + 0x0F, 0x53, 0x42, 0x03, 0xFD, 0xF2, 0x47, 0xD0, 0xB9, 0x0B, 0x53, 0xEB, + 0xC1, 0x11, 0xBA, 0xCB, 0x73, 0x60, 0x75, 0xE5, 0xF8, 0x43, 0x50, 0xBE, + 0xCB, 0xF6, 0xC7, 0xA5, 0x18, 0xD4, 0xD9, 0x0F, 0xA8, 0x2B, 0xC7, 0x2E, + 0x5F, 0xF8, 0x6F, 0xCD, 0x2D, 0xD2, 0x78, 0x6F, 0xCD, 0x78, 0x0B, 0x70, + 0x22, 0xFA, 0x85, 0x93, 0xB5, 0x44, 0xB6, 0x4B, 0x65, 0x1F, 0x49, 0x07, + 0x2E, 0x77, 0xE9, 0xF0, 0x5A, 0xBC, 0xE6, 0x76, 0xDD, 0x03, 0xF1, 0x86, + 0x66, 0x4B, 0x34, 0x72, 0x75, 0x89, 0x02, 0x31, 0x27, 0xC7, 0x9A, 0x39, + 0x91, 0xBE, 0xBC, 0xC9, 0x5F, 0x83, 0xE1, 0x29, 0x43, 0x73, 0x6C, 0x4E, + 0xB9, 0x4B, 0x0F, 0xD6, 0xD5, 0x5C, 0x55, 0xA1, 0x26, 0x95, 0xAA, 0x29, + 0xD0, 0xD4, 0x2C, 0xBC, 0x94, 0x9C, 0xEA, 0xED, 0x02, 0x68, 0x7D, 0x67, + 0xB0, 0xC0, 0xF4, 0x9D, 0x19, 0xA7, 0x6D, 0x6B, 0x5C, 0xEC, 0x5F, 0x44, + 0xCF, 0xC9, 0x07, 0x66, 0x70, 0xD6, 0x78, 0x86, 0x36, 0xA7, 0x19, 0x09, + 0xC6, 0x83, 0x61, 0xF2, 0xAD, 0x24, 0x52, 0x02, 0x6B, 0x2C, 0x33, 0xEE, + 0x39, 0x2F, 0x41, 0xD8, 0x15, 0x28, 0xA1, 0x33, 0x8A, 0x58, 0x9D, 0x01, + 0x7C, 0xB2, 0x28, 0xB1, 0x2D, 0x93, 0x4F, 0xCD, 0x46, 0xA2, 0x59, 0xB4, + 0x18, 0xBB, 0xE5, 0x08, 0xC1, 0xC9, 0x23, 0x00, 0x73, 0xB3, 0x1A, 0xF9, + 0x01, 0xC9, 0x22, 0xF2, 0xDB, 0xEA, 0x01, 0xD2, 0x35, 0x7C, 0x2C, 0x69, + 0x12, 0xB8, 0x0C, 0x2C, 0x3C, 0x4E, 0xA7, 0x04, 0xD4, 0x80, 0x8C, 0xF2, + 0x2A, 0x79, 0x88, 0x6E, 0x86, 0x64, 0x21, 0xF2, 0x56, 0x13, 0xC2, 0x6C, + 0x41, 0xB6, 0xB8, 0xC5, 0xFA, 0xC1, 0x78, 0xB3, 0x28, 0x1E, 0x1B, 0xA8, + 0x17, 0xC8, 0xDC, 0x8F, 0x5B, 0x83, 0x41, 0xED, 0x81, 0x32, 0x6C, 0x03, + 0x91, 0x3B, 0xCA, 0x3A, 0xD3, 0x3A, 0x9D, 0x87, 0x74, 0x33, 0x6D, 0xAD, + 0x4C, 0x3E, 0x39, 0x65, 0xDF, 0x00, 0x50, 0x4D, 0x5D, 0x18, 0x25, 0x8E, + 0xAF, 0xE7, 0xEE, 0x22, 0x43, 0xEA, 0x30, 0x20, 0x98, 0x58, 0xEB, 0x21, + 0x5E, 0xE2, 0x54, 0xB8, 0x1A, 0x51, 0xA6, 0x79, 0x83, 0xD4, 0xE6, 0xD0, + 0x73, 0xDA, 0xC9, 0xD1, 0x53, 0xFC, 0x0A, 0x0C, 0xDA, 0x35, 0x0F, 0x0A, + 0xC4, 0xDF, 0x65, 0x60, 0xD2, 0x96, 0x4B, 0x16, 0xE5, 0x99, 0xAB, 0x71, + 0x27, 0x43, 0xDF, 0x48, 0xB6, 0x2F, 0xFB, 0x3F, 0x7E, 0x0C, 0x46, 0xE9, + 0x3E, 0x1C, 0x07, 0xBA, 0xB2, 0x6E, 0x2A, 0x77, 0xB4, 0x46, 0x3A, 0x6E, + 0x22, 0x8C, 0x29, 0xA6, 0x55, 0x0C, 0xC6, 0x3E, 0x42, 0x26, 0x5B, 0xF5, + 0x14, 0x38, 0xF5, 0x26, 0x1E, 0xA4, 0xC1, 0x9A, 0x87, 0xA5, 0xCB, 0x4C, + 0x8E, 0x22, 0x72, 0x43, 0x8A, 0xF8, 0x65, 0x25, 0x17, 0xBD, 0xAB, 0x07, + 0xAF, 0x55, 0x9C, 0x7D, 0x24, 0xA2, 0x16, 0x82, 0xA7, 0xBA, 0xB2, 0x20, + 0x8D, 0xA8, 0x4D, 0x99, 0x87, 0x85, 0xE1, 0x2E, 0x2E, 0x2F, 0xA7, 0xC8, + 0x23, 0xDA, 0x4A, 0xEE, 0x24, 0xFE, 0x53, 0x27, 0xB9, 0x3F, 0x4C, 0x4F, + 0x1C, 0x92, 0xF7, 0xA1, 0x2A, 0x0A, 0xB3, 0xE9, 0x58, 0xA0, 0xD9, 0x38, + 0x13, 0x9E, 0x6A, 0xC2, 0x70, 0x72, 0xDA, 0x0A, 0xDC, 0xD1, 0x75, 0x92, + 0x7B, 0xE2, 0x58, 0xBC, 0xB8, 0xA9, 0xBD, 0xE8, 0xA3, 0x50, 0x6D, 0x0F, + 0x28, 0x34, 0xEA, 0x2D, 0x90, 0x56, 0x48, 0x25, 0xFE, 0xC8, 0xF4, 0x92, + 0xD3, 0xB7, 0x22, 0x77, 0xAD, 0x7E, 0xDD, 0xEF, 0x1F, 0x82, 0x03, 0x9E, + 0x17, 0x94, 0xFC, 0x84, 0xCC, 0x42, 0xDE, 0x4B, 0x3D, 0x06, 0xAA, 0x7B, + 0x8D, 0x63, 0xA5, 0x63, 0x27, 0x77, 0xD3, 0xB2, 0xD3, 0x69, 0x15, 0xC2, + 0x6E, 0x05, 0xBC, 0x34, 0x94, 0x6B, 0xAC, 0xEF, 0xDA, 0x9D, 0xBA, 0x45, + 0xC4, 0xDC, 0x94, 0xF0, 0x86, 0xDB, 0x38, 0x35, 0x28, 0x23, 0xB9, 0x90, + 0x3F, 0xDE, 0x4C, 0xEE, 0xB2, 0x9D, 0x43, 0x72, 0x58, 0xBD, 0xE4, 0xA2, + 0xBA, 0x62, 0x98, 0x59, 0xB5, 0x59, 0x19, 0xD2, 0x66, 0x7A, 0x27, 0x90, + 0xD9, 0x02, 0xD8, 0xAC, 0xC6, 0x59, 0x39, 0x4F, 0x7A, 0x31, 0x4E, 0x23, + 0xC2, 0x83, 0x8E, 0x72, 0xA4, 0xAD, 0xAD, 0x76, 0x95, 0xE4, 0xD0, 0x3A, + 0xF1, 0xBC, 0x54, 0x6E, 0xC2, 0x6F, 0x65, 0xAA, 0x31, 0xC4, 0x6D, 0x73, + 0xAB, 0x42, 0x15, 0x74, 0x90, 0x68, 0xDC, 0x6D, 0xEF, 0x72, 0xB1, 0xB4, + 0xA5, 0x30, 0xF4, 0xEA, 0x8B, 0x18, 0x1D, 0xCE, 0xD8, 0x66, 0xA1, 0x88, + 0x74, 0x8F, 0x47, 0x80, 0x2A, 0x8D, 0x95, 0xF0, 0x86, 0x23, 0x89, 0xB1, + 0xA3, 0x34, 0x83, 0xF8, 0x89, 0x8C, 0xF9, 0xEB, 0x26, 0xCA, 0x3E, 0x64, + 0x03, 0x61, 0xFB, 0x85, 0xCC, 0x0B, 0x1C, 0xA6, 0x05, 0xEA, 0x09, 0x9C, + 0xA8, 0x50, 0xB6, 0x8B, 0x5E, 0x9E, 0x3E, 0x3B, 0x8E, 0xAD, 0x7D, 0x0D, + 0x5A, 0x6E, 0x27, 0xC9, 0x4E, 0xFF, 0x53, 0x06, 0xAB, 0x40, 0x1F, 0x32, + 0x57, 0xCA, 0xD6, 0x3E, 0x1F, 0xF1, 0xED, 0x21, 0xF6, 0xAF, 0x6D, 0x65, + 0x14, 0xF5, 0x47, 0x26, 0xDC, 0x9B, 0x90, 0x66, 0xE5, 0x32, 0x4D, 0x92, + 0xB5, 0xD9, 0x85, 0x48, 0x18, 0x17, 0x96, 0x5F, 0xC2, 0xD5, 0xF7, 0xD0, + 0x11, 0xBE, 0xE5, 0x55, 0x00, 0x94, 0xBD, 0x69, 0x7B, 0x17, 0x7B, 0xF1, + 0xB9, 0xA8, 0x46, 0x5E, 0xD1, 0x6D, 0x2F, 0x36, 0xB1, 0x60, 0x0C, 0x96, + 0x3C, 0x0D, 0x35, 0x17, 0x70, 0x74, 0x5B, 0xA5, 0x1B, 0x99, 0x8E, 0x74, + 0xED, 0x89, 0x25, 0x29, 0x97, 0xBF, 0x7A, 0x91, 0x45, 0x69, 0xA1, 0x6B, + 0x61, 0xB4, 0x47, 0x50, 0xED, 0x15, 0xC0, 0xB6, 0x06, 0x36, 0x33, 0x12, + 0x16, 0xDA, 0x0A, 0x46, 0x4F, 0xCC, 0x7B, 0xC7, 0x8A, 0x45, 0x96, 0x27, + 0x1C, 0x46, 0x85, 0x08, 0xD7, 0x36, 0xB7, 0xC7, 0xA0, 0xD7, 0xC0, 0x60, + 0x2B, 0x6C, 0x6B, 0x6F, 0xD1, 0xBF, 0x5B, 0xF7, 0xA5, 0xC0, 0x48, 0xE2, + 0x88, 0xA3, 0x4C, 0x06, 0xDC, 0x97, 0xD2, 0x91, 0xF8, 0x85, 0x1C, 0x72, + 0x7F, 0x37, 0x05, 0x70, 0x4E, 0xEA, 0x0A, 0x89, 0x04, 0xA7, 0x64, 0x7D, + 0x95, 0x85, 0x35, 0x81, 0x0F, 0xBC, 0xA0, 0x04, 0x80, 0xD9, 0x58, 0x43, + 0x8F, 0x51, 0xEB, 0x29, 0x72, 0xA3, 0xEB, 0xB8, 0x0E, 0x2C, 0x94, 0x0E, + 0xAD, 0xCF, 0x29, 0xA5, 0xF0, 0xDF, 0x6D, 0xCA, 0x88, 0x7B, 0x2E, 0xDD, + 0x3C, 0x26, 0x79, 0xED, 0x67, 0x71, 0xAC, 0x44, 0xA5, 0xFA, 0xA8, 0x79, + 0x7C, 0x79, 0x35, 0x5D, 0xCB, 0x79, 0xA4, 0x56, 0xEB, 0x76, 0xD8, 0x4D, + 0x98, 0xFF, 0x71, 0xA8, 0x46, 0x7F, 0x26, 0x98, 0x4F, 0xA0, 0xC8, 0x47, + 0x00, 0xFB, 0x21, 0xE6, 0xD1, 0x11, 0x3C, 0xD4, 0xCF, 0x89, 0xF4, 0x68, + 0xAA, 0x2A, 0xFA, 0x48, 0xE7, 0x48, 0x10, 0xC7, 0xB8, 0x90, 0x17, 0xA3, + 0x0C, 0x0F, 0x08, 0x6E, 0xE4, 0xC9, 0x75, 0xA0, 0x89, 0x4F, 0xE1, 0x0A, + 0x83, 0xE3, 0x6E, 0x10, 0x80, 0xE4, 0xC2, 0x05, 0xF5, 0x29, 0x31, 0x30, + 0x57, 0xA7, 0xDC, 0x5F, 0x99, 0xD5, 0x1E, 0x1E, 0x07, 0x8C, 0xD1, 0x61, + 0x1B, 0xB2, 0xB3, 0x2C, 0xA8, 0x33, 0xC9, 0x4C, 0x39, 0x56, 0xBA, 0xFC, + 0x65, 0xE2, 0xB9, 0xB6, 0x1C, 0x56, 0x24, 0x59, 0xA0, 0x0F, 0x62, 0xB4, + 0xE3, 0xE6, 0xF4, 0x24, 0x08, 0xA6, 0x4B, 0xC7, 0xA5, 0x09, 0xB2, 0x0E, + 0xD6, 0x41, 0x4F, 0x89, 0x97, 0x0D, 0xA1, 0xE4, 0x10, 0x3D, 0x2E, 0x36, + 0xC2, 0x7A, 0x73, 0xD6, 0x30, 0x05, 0xA1, 0xC7, 0x08, 0x27, 0x28, 0x79, + 0x8C, 0xC8, 0xE3, 0xE5, 0x1B, 0x7F, 0x22, 0xCD, 0x9C, 0x6A, 0x66, 0xE9, + 0x63, 0xB2, 0x42, 0x72, 0x23, 0x3B, 0xB6, 0xF5, 0x08, 0xF5, 0x26, 0xD4, + 0x22, 0x8E, 0x9B, 0xFA, 0x42, 0x5C, 0x47, 0x33, 0x0F, 0x7C, 0xDF, 0x0A, + 0x97, 0x80, 0xF0, 0x84, 0xC6, 0xBB, 0xDA, 0x09, 0xD7, 0xBA, 0x79, 0x17, + 0x06, 0xD2, 0xDA, 0x34, 0x0A, 0x95, 0x83, 0x65, 0xFA, 0x92, 0xC3, 0x52, + 0xF9, 0xFA, 0x96, 0xCB, 0xC0, 0xE7, 0x53, 0x7F, 0xFF, 0x3F, 0x7C, 0x8B, + 0x92, 0xA3, 0x69, 0xBC, 0x46, 0xBD, 0x0E, 0xC9, 0xE7, 0x1E, 0x28, 0x62, + 0xEB, 0xE4, 0xB8, 0x28, 0x0C, 0x82, 0x1C, 0xA4, 0xC9, 0x92, 0x4F, 0xDE, + 0x66, 0x87, 0x9C, 0x7A, 0x1A, 0x77, 0x16, 0xD1, 0x78, 0xCB, 0x98, 0x2F, + 0xFA, 0x2B, 0x06, 0xF0, 0x30, 0x73, 0xE7, 0xD8, 0xB3, 0x0D, 0xAE, 0x57, + 0xCB, 0xB6, 0xF1, 0x1B, 0xCB, 0xA5, 0xCB, 0x2C, 0xDC, 0x09, 0xEB, 0x9F, + 0xCB, 0xBF, 0x81, 0x8F, 0x8B, 0x2E, 0xD0, 0x3A, 0x01, 0x2E, 0xF8, 0xB5, + 0x3E, 0xC8, 0x19, 0xB6, 0x1B, 0x44, 0xF9, 0xB4, 0x10, 0x6E, 0xC6, 0x8D, + 0xDB, 0x9F, 0xB5, 0x38, 0xCF, 0x3D, 0x50, 0x8A, 0xC0, 0x93, 0xA5, 0x7A, + 0x71, 0x69, 0x2A, 0xFF, 0x83, 0xBE, 0xDC, 0xC7, 0xBE, 0xAD, 0xED, 0x15, + 0x0E, 0xB6, 0xEF, 0x13, 0xD8, 0x75, 0x72, 0xC1, 0xEF, 0x2F, 0x14, 0x96, + 0x1E, 0x03, 0x6F, 0x49, 0x60, 0x45, 0xA6, 0x47, 0x37, 0x31, 0xCE, 0x7F, + 0x7C, 0x60, 0x06, 0x3C, 0xB8, 0x8B, 0x14, 0x64, 0xF6, 0xFD, 0xA6, 0xA0, + 0xC5, 0x17, 0xCD, 0x5E, 0xF5, 0xFE, 0x25, 0x5E, 0xA7, 0xAA, 0xAB, 0x1F, + 0x1D, 0xC1, 0xEC, 0xEE, 0x63, 0x57, 0x5A, 0xFC, 0xA7, 0x33, 0x5D, 0x9E, + 0x04, 0xB3, 0xE0, 0x50, 0x2A, 0x7F, 0x2C, 0x0A, 0xB4, 0xAA, 0x12, 0x6A, + 0xD0, 0x54, 0xA7, 0x0A, 0xCE, 0x98, 0x5A, 0x1C, 0xA1, 0xF4, 0x51, 0x03, + 0xEA, 0xAD, 0x89, 0xCA, 0xB9, 0x0D, 0x19, 0x9F, 0x37, 0xD7, 0x67, 0x98, + 0xBF, 0xE8, 0xA2, 0x98, 0xC1, 0x9F, 0x7E, 0xF7, 0x0F, 0x9B, 0x5B, 0x91, + 0xD1, 0x3D, 0xC1, 0x06, 0x17, 0x81, 0xFA, 0x54, 0x95, 0xC4, 0xEB, 0xBA, + 0xEA, 0x46, 0x82, 0xEA, 0xDA, 0x2A, 0x7E, 0xD0, 0xE7, 0x8C, 0xDA, 0x78, + 0xA1, 0xB2, 0xF4, 0xE5, 0x4A, 0xD3, 0x75, 0x68, 0xBE, 0xEB, 0x7E, 0x98, + 0x58, 0x83, 0xC1, 0xD6, 0x68, 0x37, 0xF3, 0x09, 0xF6, 0x53, 0x25, 0x0A, + 0x71, 0xDA, 0x29, 0x5F, 0x4A, 0xCD, 0x14, 0x33, 0x06, 0x88, 0xD0, 0x83, + 0xC6, 0x4D, 0x09, 0x1D, 0xFC, 0xDF, 0x59, 0x4F, 0x94, 0xB5, 0xA3, 0xF7, + 0x02, 0x04, 0x86, 0x41, 0x3A, 0xE7, 0x74, 0xF4, 0xFA, 0xB7, 0xF5, 0xB3, + 0xD6, 0x9B, 0x9D, 0x58, 0xB8, 0xE7, 0xFF, 0x00, 0xD4, 0xB1, 0xAB, 0x96, + 0x62, 0x15, 0x1D, 0x26, 0xB0, 0x10, 0x42, 0x47, 0x29, 0xD3, 0x67, 0x93, + 0xC7, 0x34, 0xD4, 0xBB, 0x08, 0x2A, 0x54, 0x45, 0x39, 0x77, 0x92, 0x7B, + 0x7C, 0xEC, 0xF2, 0x55, 0xCF, 0x0F, 0xAA, 0x22, 0x87, 0xFF, 0xF3, 0x45, + 0xA7, 0xA5, 0x2F, 0xB6, 0xF4, 0x89, 0xE3, 0xE2, 0x38, 0xC4, 0x7A, 0xA6, + 0x38, 0x6D, 0xCB, 0x5E, 0x28, 0x58, 0x46, 0xBF, 0x58, 0xE0, 0xD7, 0xA6, + 0x23, 0x43, 0xCE, 0x3F, 0x0D, 0x7F, 0x26, 0xAF, 0x9A, 0x8B, 0x4C, 0x92, + 0x6E, 0xBB, 0xFD, 0x92, 0x56, 0x70, 0xAB, 0xB6, 0xF3, 0x8C, 0x08, 0xF2, + 0xA9, 0x03, 0x96, 0x03, 0x1F, 0xFB, 0xBB, 0xCB, 0xAE, 0x69, 0xA6, 0x0D, + 0x5A, 0x0A, 0x71, 0x05, 0xB0, 0xE6, 0x3F, 0x24, 0x1D, 0xC6, 0xCB, 0xCC, + 0x3F, 0x33, 0xF5, 0xE8, 0x3F, 0x10, 0x36, 0x0E, 0xB5, 0x11, 0x0D, 0xFD, + 0x27, 0xDC, 0x1A, 0xEB, 0x2D, 0x0E, 0x62, 0xCF, 0xD4, 0x59, 0x03, 0x57, + 0xB2, 0x71, 0x11, 0x39, 0x5A, 0xCD, 0x8A, 0xDD, 0xA8, 0x63, 0x1B, 0xE9, + 0xFF, 0x8A, 0x1D, 0x71, 0xC0, 0xAF, 0xC2, 0xB2, 0x69, 0x88, 0xE0, 0xB5, + 0xA7, 0x03, 0x10, 0x95, 0x64, 0xC5, 0x3C, 0xF2, 0xBC, 0xC9, 0x20, 0xF5, + 0x25, 0x5B, 0x1B, 0xAE, 0x5E, 0x4A, 0x5D, 0xB0, 0xA6, 0xCA, 0x68, 0x41, + 0x89, 0x2C, 0xE6, 0x2D, 0xAF, 0xD2, 0x04, 0xC9, 0x88, 0x89, 0xDC, 0x39, + 0xED, 0xC9, 0xB6, 0xDD, 0xEC, 0xE7, 0x6D, 0xA0, 0x83, 0x0F, 0xCE, 0xB4, + 0xC1, 0x80, 0x3D, 0x2B, 0x21, 0xC1, 0x4D, 0xC6, 0x27, 0x13, 0xA2, 0xB9, + 0x92, 0xDD, 0xB9, 0xBD, 0xE5, 0x65, 0xAF, 0xA1, 0xBF, 0x9F, 0x04, 0x7D, + 0x02, 0x33, 0xE8, 0x9F, 0x25, 0x8D, 0x7C, 0xA5, 0x35, 0x80, 0x58, 0xF1, + 0xF2, 0x1B, 0x9D, 0xDF, 0x48, 0xA3, 0x9B, 0x3F, 0x8E, 0xCF, 0xAB, 0x54, + 0x03, 0x78, 0xFC, 0x1B, 0x8F, 0xA3, 0xDD, 0x28, 0x99, 0x4B, 0xEB, 0x89, + 0x4E, 0xAF, 0x60, 0xB6, 0x75, 0x7D, 0x89, 0x78, 0x0D, 0x9D, 0x80, 0x82, + 0x95, 0x50, 0x4B, 0xBF, 0xBA, 0xF7, 0x58, 0x8B, 0x62, 0x88, 0x68, 0xE5, + 0x59, 0xE4, 0xDF, 0xE8, 0x6C, 0xA3, 0xF6, 0xA4, 0xEB, 0x15, 0x8F, 0xE6, + 0xCB, 0x3E, 0xD3, 0x1E, 0xEC, 0x59, 0x34, 0x7C, 0x8B, 0x16, 0xD7, 0x3C, + 0x16, 0x17, 0x4B, 0xAC, 0x8D, 0xEF, 0x38, 0x63, 0x9A, 0xF7, 0x42, 0x64, + 0xA0, 0x80, 0x1B, 0x60, 0xDA, 0x2B, 0x5E, 0x31, 0x20, 0x3F, 0x96, 0xFD, + 0x9B, 0xCD, 0x27, 0x7D, 0x44, 0x72, 0x12, 0xCF, 0xA2, 0xDB, 0x2F, 0x1F, + 0x20, 0xB9, 0x1F, 0x75, 0x68, 0x7E, 0xAB, 0x40, 0xD7, 0xCD, 0xB7, 0xC8, + 0x85, 0x73, 0x58, 0x3D, 0x16, 0x35, 0x6F, 0x45, 0x45, 0x9D, 0xCB, 0x98, + 0x5A, 0x94, 0x66, 0xA0, 0x4E, 0x39, 0x22, 0x2E, 0xAB, 0x35, 0x1F, 0x1D, + 0x5D, 0x32, 0xA8, 0xD6, 0x9B, 0x97, 0x9B, 0x4C, 0xF6, 0x20, 0x2A, 0x80, + 0xB0, 0xC8, 0x4F, 0x9B, 0x64, 0xAE, 0x96, 0x8C, 0xEA, 0xA3, 0x67, 0x38, + 0x20, 0xDA, 0xFF, 0xF5, 0xA1, 0xFD, 0xA8, 0xF4, 0x97, 0xB5, 0x02, 0xF8, + 0x37, 0xE8, 0xB7, 0xB2, 0xBA, 0x38, 0x1E, 0xD5, 0x6C, 0x0A, 0x67, 0x29, + 0x0B, 0x32, 0xB5, 0x90, 0x73, 0xDA, 0x0B, 0xD2, 0xC1, 0x59, 0x19, 0xBE, + 0x25, 0x99, 0x77, 0xE9, 0xD4, 0x10, 0x33, 0x13, 0xD2, 0x92, 0xB1, 0x09, + 0x47, 0x3A, 0xBD, 0x4A, 0x66, 0xEA, 0x07, 0x9F, 0xC4, 0xE5, 0x55, 0x8E, + 0x55, 0xB2, 0x55, 0x3A, 0xEB, 0x1F, 0xD1, 0x3A, 0x65, 0xAF, 0x3E, 0xF4, + 0x0B, 0x34, 0xA5, 0x02, 0xBB, 0xD3, 0x86, 0x4D, 0x54, 0xE1, 0xEA, 0x14, + 0x83, 0x40, 0x19, 0x80, 0x68, 0x17, 0x22, 0xF6, 0x52, 0x25, 0x46, 0xB5, + 0x0C, 0x7F, 0xE3, 0xC7, 0xF8, 0x42, 0x59, 0x95, 0x65, 0x4D, 0x1A, 0x7C, + 0x6B, 0x45, 0xB9, 0xC5, 0x7F, 0x9A, 0x96, 0xD7, 0xA3, 0x5B, 0xC5, 0x90, + 0x8B, 0xBC, 0x7E, 0x31, 0x07, 0x44, 0x4B, 0xE2, 0xEE, 0xB4, 0x33, 0xCD, + 0x0D, 0x0F, 0x6B, 0x88, 0x4E, 0x57, 0x24, 0x1C, 0x00, 0xAE, 0x7D, 0x28, + 0xEB, 0x0E, 0x4C, 0x8E, 0xAA, 0xA0, 0xB3, 0xA3, 0xAF, 0x9C, 0x86, 0x7F, + 0xE3, 0x7F, 0x2D, 0x25, 0xA4, 0x09, 0xD6, 0x73, 0x95, 0x49, 0x98, 0x37, + 0xF3, 0x58, 0xE6, 0x5C, 0x24, 0x34, 0x87, 0xEA, 0x87, 0xCE, 0x76, 0x2F, + 0x0A, 0xAC, 0x0F, 0x9D, 0x85, 0xAA, 0x09, 0x6E, 0x06, 0x9C, 0x28, 0x9A, + 0xCB, 0x91, 0xEF, 0x19, 0xF0, 0x7A, 0x0C, 0xC0, 0xCE, 0xAC, 0x6A, 0xA7, + 0x20, 0x51, 0x35, 0xD7, 0x7B, 0xFB, 0xD1, 0x1C, 0x0F, 0xC4, 0xCE, 0x65, + 0x21, 0x63, 0x41, 0x35, 0x55, 0x2E, 0x20, 0x07, 0x43, 0x39, 0xBB, 0x86, + 0x60, 0xB8, 0x87, 0x4C, 0x0B, 0x78, 0x35, 0xBA, 0x91, 0x96, 0xAF, 0x0D, + 0xAA, 0x79, 0x6B, 0x1B, 0x75, 0xC2, 0x5B, 0x2F, 0x85, 0x04, 0x40, 0xED, + 0x96, 0x63, 0xF2, 0x36, 0xF1, 0x0A, 0x94, 0x6D, 0x01, 0x90, 0x7D, 0x1F, + 0x8B, 0x7D, 0x08, 0xD7, 0x9F, 0xA1, 0x23, 0xB2, 0xBF, 0xA3, 0x80, 0x6C, + 0x90, 0x67, 0x70, 0xF0, 0xC7, 0x0A, 0x6D, 0xAD, 0x40, 0x8B, 0x33, 0xA6, + 0xAD, 0x6F, 0x2A, 0xBE, 0xE2, 0x70, 0x4A, 0xFA, 0x31, 0x6B, 0x3B, 0x62, + 0x47, 0xDE, 0x5B, 0x13, 0xD0, 0x85, 0x63, 0x18, 0xE0, 0x9C, 0x61, 0x29, + 0x2B, 0x13, 0x64, 0x44, 0x36, 0x01, 0x97, 0xED, 0xB6, 0xF0, 0xCC, 0x8E, + 0xAD, 0xDE, 0x6B, 0xC7, 0x7B, 0xF5, 0x84, 0x08, 0xA3, 0xB7, 0x96, 0xBA, + 0x0F, 0x20, 0xC1, 0xE2, 0x40, 0x31, 0x6A, 0x25, 0x06, 0xC4, 0x35, 0x4F, + 0x04, 0x19, 0xD6, 0x86, 0xDC, 0x07, 0x48, 0x5F, 0xC3, 0xEA, 0x3B, 0x20, + 0xBB, 0x59, 0xFE, 0x05, 0x45, 0xC3, 0xDE, 0x08, 0x44, 0xF6, 0x19, 0xE9, + 0x95, 0x8A, 0xDE, 0xA6, 0xD9, 0x96, 0x59, 0x7E, 0x7C, 0xBF, 0x00, 0x48, + 0x36, 0xBD, 0xB0, 0xB4, 0x22, 0xBE, 0xD0, 0xE5, 0x40, 0x8C, 0x3B, 0x6E, + 0x11, 0xDA, 0x24, 0xBA, 0x09, 0xEE, 0x6C, 0xB0, 0xD5, 0x87, 0x01, 0xD9, + 0xBE, 0x02, 0xE1, 0x80, 0x89, 0x3F, 0x57, 0xB1, 0xF6, 0x53, 0xF8, 0x59, + 0x6F, 0x99, 0x25, 0xF0, 0x10, 0xD9, 0x8F, 0x56, 0xC7, 0x05, 0x4B, 0xE8, + 0xE2, 0x3C, 0xE0, 0x6C, 0x42, 0xA2, 0x08, 0x92, 0xED, 0x06, 0x64, 0xBF, + 0x0C, 0x91, 0x7D, 0xAE, 0x64, 0x0B, 0xEE, 0x88, 0x54, 0xB2, 0xC8, 0xE6, + 0xF1, 0x42, 0x75, 0x40, 0x64, 0x7B, 0x23, 0x75, 0x81, 0x17, 0x90, 0x7D, + 0x21, 0x79, 0xF2, 0x39, 0xD1, 0xD6, 0x26, 0xDE, 0xFB, 0xF4, 0x7B, 0x2A, + 0xFC, 0x00, 0xD0, 0x16, 0x8B, 0x0B, 0x62, 0x95, 0xC9, 0x86, 0xE1, 0xB2, + 0x0D, 0x7B, 0x04, 0x2D, 0xE2, 0x33, 0x4F, 0x48, 0x7D, 0x06, 0x94, 0x67, + 0x0F, 0x01, 0x40, 0x7B, 0xFA, 0x50, 0x25, 0x2C, 0xBA, 0xB5, 0x65, 0x0E, + 0x00, 0xAA, 0x60, 0x08, 0xD6, 0xE1, 0xB8, 0x55, 0xF3, 0x41, 0x21, 0xF9, + 0x1F, 0xE3, 0x71, 0x00, 0x6B, 0x8D, 0x59, 0x7A, 0xA3, 0xD1, 0x48, 0x6B, + 0x29, 0x82, 0x55, 0x3F, 0x7A, 0x62, 0x3A, 0xC5, 0xFE, 0x23, 0x0E, 0xFC, + 0xAA, 0x1D, 0xC3, 0x7A, 0xD6, 0x2B, 0xB0, 0xDD, 0x36, 0x8D, 0x3B, 0x78, + 0x15, 0x08, 0xB1, 0x3F, 0xC4, 0xD2, 0x46, 0xCD, 0x18, 0x4F, 0xAC, 0x73, + 0x0D, 0xE3, 0x63, 0xB1, 0xF2, 0xB9, 0xDA, 0x5A, 0x4A, 0x09, 0xCD, 0x6E, + 0x8D, 0xBA, 0x14, 0x6A, 0x3A, 0xC5, 0xA3, 0x5A, 0x2F, 0x30, 0xE4, 0xD4, + 0xFE, 0xB0, 0xC4, 0x33, 0xF3, 0xDF, 0xA8, 0xD4, 0x40, 0xD2, 0xB8, 0x78, + 0x2B, 0xC8, 0x03, 0x45, 0x6A, 0x14, 0x7A, 0xCA, 0x77, 0x1B, 0xCE, 0x09, + 0x64, 0x74, 0x9E, 0xAB, 0x0A, 0x00, 0x9B, 0xCC, 0x2B, 0xF4, 0x6E, 0xD0, + 0xD3, 0xC0, 0xEA, 0xC6, 0x3F, 0x88, 0xDE, 0x39, 0xF8, 0xA9, 0x62, 0x3C, + 0xCD, 0x1C, 0x00, 0x75, 0x8B, 0xA7, 0xB3, 0x02, 0x7F, 0xEB, 0x34, 0xBA, + 0x46, 0xF0, 0xCC, 0xC9, 0xB3, 0xB3, 0xF8, 0xBD, 0xEB, 0x18, 0x9F, 0x35, + 0x1A, 0xFE, 0xFE, 0x6F, 0xB7, 0x38, 0xC4, 0x30, 0xA0, 0xB7, 0xA8, 0xE0, + 0x03, 0x58, 0xE4, 0x79, 0x93, 0x0F, 0xD6, 0xAD, 0x8B, 0x05, 0xDE, 0x6D, + 0xA1, 0x79, 0x34, 0xA8, 0x0F, 0x49, 0x23, 0x46, 0xE5, 0xFA, 0x24, 0x4F, + 0xD7, 0x1F, 0xAD, 0xCF, 0xA4, 0x81, 0x06, 0x3C, 0x27, 0x41, 0x8C, 0xFA, + 0x7C, 0x87, 0xAF, 0xBD, 0x76, 0xD1, 0x7C, 0x03, 0x8B, 0x46, 0x60, 0x62, + 0x72, 0xCB, 0xC6, 0xF0, 0xD2, 0x6C, 0xDA, 0x22, 0xED, 0xF0, 0xBF, 0xE8, + 0x90, 0x6F, 0x29, 0xB4, 0x87, 0x73, 0x4D, 0x64, 0x96, 0x06, 0x91, 0x7E, + 0x4F, 0xE4, 0xAF, 0x04, 0x0D, 0xEE, 0x6E, 0xD3, 0x86, 0x7E, 0x77, 0x83, + 0x1E, 0x6B, 0xCD, 0xA1, 0x7D, 0x01, 0x19, 0x9B, 0x31, 0x36, 0x8F, 0x71, + 0xEE, 0xFA, 0x7B, 0xB6, 0xDF, 0xA4, 0x3F, 0x4E, 0x25, 0xE9, 0x21, 0x37, + 0xFB, 0xBE, 0x8E, 0x7D, 0x05, 0x8A, 0x27, 0x38, 0x92, 0x11, 0xC9, 0xCC, + 0x7B, 0x4F, 0x13, 0x81, 0x86, 0xE1, 0xC2, 0x37, 0xBA, 0xA1, 0xBA, 0xF0, + 0x37, 0x95, 0x59, 0xD1, 0x67, 0xA2, 0xC7, 0x2B, 0xEB, 0xFE, 0xBA, 0x10, + 0x07, 0x02, 0x6E, 0x8D, 0x3E, 0xB4, 0x09, 0x69, 0xEA, 0x5B, 0xC4, 0x40, + 0x9E, 0xF6, 0xDB, 0x8D, 0xFE, 0xF2, 0xF6, 0x34, 0x8A, 0xFC, 0x33, 0x7E, + 0x8D, 0x95, 0x24, 0xD8, 0xA3, 0xE0, 0xC2, 0x5F, 0x04, 0xA0, 0x93, 0x1A, + 0x13, 0x2B, 0x58, 0xB2, 0x4A, 0x80, 0x75, 0x4A, 0x4F, 0x62, 0xB4, 0x9D, + 0x04, 0xA3, 0xF3, 0x9E, 0xF1, 0xBF, 0x19, 0x19, 0xE9, 0x56, 0x0B, 0x5C, + 0x32, 0x5A, 0xAA, 0x8A, 0xF0, 0xC9, 0x18, 0x15, 0x84, 0xDF, 0xF8, 0x0F, + 0x3A, 0x0C, 0x27, 0x8C, 0x19, 0xC7, 0xEF, 0x75, 0x35, 0x09, 0x27, 0x7E, + 0xD3, 0xF2, 0x21, 0x6A, 0xB5, 0x4E, 0x27, 0xC0, 0x48, 0x3D, 0x68, 0x1F, + 0x8C, 0x4F, 0x2B, 0xEA, 0xC8, 0x77, 0x32, 0x71, 0x7D, 0xAA, 0x3E, 0x0E, + 0xE9, 0x62, 0x0F, 0xA9, 0xFA, 0x4D, 0x98, 0xE5, 0x47, 0xEB, 0x04, 0x61, + 0xB3, 0x04, 0xD2, 0xE5, 0x83, 0xBF, 0x4D, 0x74, 0xF4, 0xCA, 0x37, 0xB7, + 0x0E, 0x3B, 0x4E, 0x1B, 0x6B, 0xD1, 0xB7, 0xD2, 0xAB, 0x3F, 0xC1, 0x71, + 0xCE, 0xB4, 0x0D, 0x1B, 0x06, 0x35, 0x6F, 0x43, 0x2A, 0xC9, 0x35, 0x2B, + 0x91, 0x0C, 0xDD, 0xB7, 0x35, 0xF6, 0x33, 0xB7, 0x1B, 0xFF, 0xE8, 0x06, + 0x35, 0x4B, 0x42, 0x28, 0x85, 0x35, 0x2D, 0x41, 0x99, 0x86, 0x8C, 0x1C, + 0xE6, 0xF7, 0xC0, 0x06, 0x43, 0x84, 0xC0, 0x9D, 0xBF, 0x5E, 0x2D, 0xE0, + 0x22, 0x9C, 0x55, 0xF1, 0x89, 0xCC, 0xAB, 0xBF, 0x43, 0x71, 0x8F, 0xEE, + 0xD9, 0x24, 0x65, 0x09, 0x28, 0xF8, 0x64, 0xAC, 0xCE, 0x3E, 0x0C, 0x44, + 0x9C, 0x33, 0xE7, 0xCF, 0x2D, 0x73, 0x97, 0x94, 0xE3, 0x90, 0x55, 0xD9, + 0x00, 0x72, 0x75, 0xA8, 0x54, 0x92, 0x81, 0xC6, 0x52, 0xCB, 0x63, 0x03, + 0xBA, 0x13, 0x94, 0xCB, 0x18, 0xAC, 0x38, 0xD3, 0x5B, 0x6D, 0x5E, 0xB3, + 0x29, 0xB6, 0x06, 0x8E, 0xED, 0xB4, 0xEF, 0x43, 0x6D, 0x8F, 0x84, 0xF6, + 0x44, 0xB4, 0xA2, 0xBE, 0xBE, 0x90, 0x96, 0xAB, 0x51, 0x69, 0xDB, 0x93, + 0x4E, 0xE5, 0xE0, 0xF1, 0x9F, 0x05, 0x40, 0x70, 0xDA, 0x7B, 0x96, 0x77, + 0x6E, 0xD3, 0xF4, 0x6F, 0x6B, 0x59, 0x93, 0x32, 0x1C, 0x0B, 0xB0, 0xDD, + 0x5F, 0x7A, 0x8A, 0x6C, 0x42, 0x31, 0x3C, 0xB2, 0x91, 0x73, 0x43, 0x5D, + 0xAC, 0x53, 0x05, 0x22, 0xD2, 0x63, 0xFB, 0x41, 0xFE, 0xFA, 0xC3, 0xBE, + 0xD0, 0x72, 0x53, 0xC6, 0x57, 0x34, 0xDB, 0xDB, 0xD2, 0x05, 0xB2, 0x07, + 0x23, 0x3D, 0xEB, 0x16, 0xBF, 0x28, 0x41, 0x50, 0x8C, 0x43, 0x22, 0x82, + 0x1C, 0x69, 0x89, 0x38, 0x09, 0xE4, 0x78, 0xBF, 0x5C, 0x72, 0xC0, 0xCA, + 0x1B, 0xDC, 0xE5, 0xDE, 0x89, 0xA2, 0x7F, 0x9F, 0x01, 0xAB, 0x5C, 0x29, + 0xE8, 0x95, 0xDA, 0x0D, 0xEB, 0xA1, 0x42, 0xFE, 0x2E, 0x47, 0x93, 0x14, + 0x1B, 0x99, 0x07, 0x64, 0xFA, 0xD3, 0xB9, 0x5D, 0x44, 0xC1, 0x74, 0x39, + 0x47, 0x46, 0x80, 0x31, 0x5D, 0x66, 0x35, 0x2B, 0x0B, 0xDE, 0x2F, 0x7F, + 0x51, 0x31, 0xF0, 0x6C, 0x9A, 0xD9, 0xE3, 0xE4, 0x99, 0x8F, 0x2F, 0xB3, + 0xBC, 0x87, 0x27, 0xF3, 0x78, 0x8C, 0xC9, 0xF5, 0x9A, 0x6B, 0x23, 0xF5, + 0xC1, 0x91, 0x74, 0x04, 0x04, 0x7C, 0xDD, 0x4D, 0x23, 0x6C, 0xE4, 0x1C, + 0x67, 0xA2, 0x6B, 0x90, 0x73, 0xE7, 0xD2, 0x85, 0x59, 0x92, 0xA3, 0xF6, + 0x1E, 0x5B, 0xB6, 0x73, 0x9B, 0xFB, 0xD6, 0x2B, 0x6C, 0x60, 0xB0, 0x7F, + 0xE6, 0x9C, 0xB6, 0x09, 0x2F, 0xD6, 0x91, 0x64, 0xE5, 0x14, 0x65, 0x98, + 0xE2, 0x87, 0x9E, 0x3E, 0x14, 0xD2, 0x69, 0x5B, 0x3E, 0x5C, 0x0E, 0xC1, + 0x3F, 0x6E, 0xF5, 0xE6, 0xB7, 0x3B, 0x02, 0x7D, 0xCC, 0xCD, 0x8B, 0x43, + 0xBD, 0x11, 0x37, 0xCB, 0x70, 0x23, 0xA6, 0xCA, 0x34, 0x0A, 0x30, 0x43, + 0x40, 0xB7, 0x63, 0x95, 0x7F, 0x11, 0xC5, 0x4F, 0x85, 0x1A, 0x7C, 0x39, + 0x71, 0xBC, 0xA2, 0x24, 0x20, 0xA6, 0x12, 0x6F, 0x5A, 0x0C, 0x69, 0x0C, + 0x9D, 0x37, 0x01, 0x35, 0xBD, 0x1E, 0x18, 0xE1, 0xE7, 0x18, 0x78, 0xFC, + 0xAE, 0xC8, 0x67, 0xF1, 0x1E, 0xB7, 0xB1, 0x4D, 0x79, 0xF1, 0x94, 0xDC, + 0x3D, 0x67, 0xA8, 0xB7, 0xDC, 0x26, 0xC4, 0xF8, 0x9A, 0x83, 0xBC, 0x24, + 0x3A, 0x0A, 0xB3, 0xF4, 0x46, 0x61, 0x04, 0x2E, 0x03, 0x55, 0x18, 0xC5, + 0x28, 0xB5, 0x68, 0x8D, 0x74, 0x51, 0x5A, 0xC6, 0x4D, 0x5F, 0xAA, 0x58, + 0x86, 0x99, 0xC9, 0x63, 0x6E, 0xB2, 0xC8, 0x87, 0xE5, 0x36, 0xC1, 0xA3, + 0x66, 0xAF, 0x79, 0x77, 0x2C, 0xB9, 0x11, 0xD2, 0x1B, 0xF5, 0x21, 0xFE, + 0x21, 0x89, 0x6A, 0xEC, 0x71, 0x83, 0x27, 0x0E, 0xD1, 0x31, 0x6E, 0x24, + 0x20, 0x9F, 0x6E, 0x28, 0x95, 0x17, 0x7F, 0x76, 0x15, 0x70, 0xE9, 0x44, + 0x22, 0xDD, 0x84, 0x61, 0x10, 0x3F, 0x5C, 0x5B, 0xF6, 0xC6, 0x4C, 0x4E, + 0xE3, 0xCF, 0x6C, 0xF5, 0x50, 0x44, 0x29, 0x89, 0x04, 0xED, 0xA9, 0xC1, + 0x2F, 0xE2, 0x65, 0x2B, 0x87, 0xCA, 0xD9, 0x9B, 0xFA, 0xAA, 0xE7, 0x5B, + 0x87, 0x2B, 0x9C, 0x88, 0x84, 0xA3, 0x27, 0x6B, 0xF9, 0xFE, 0x1E, 0xD3, + 0xC7, 0x41, 0x36, 0xFA, 0xC7, 0xDA, 0x46, 0x9E, 0x68, 0xD9, 0x64, 0xF8, + 0x11, 0x0D, 0xA3, 0x12, 0x6C, 0x38, 0x97, 0x4D, 0xDE, 0xF9, 0xEE, 0xE3, + 0xB5, 0x8F, 0x17, 0x6F, 0x82, 0x65, 0x0F, 0x17, 0x79, 0x3D, 0x97, 0x38, + 0x1C, 0x41, 0x73, 0xCA, 0xD9, 0x10, 0xCC, 0xFB, 0xD8, 0xB6, 0x97, 0x90, + 0x42, 0x36, 0xAA, 0x9B, 0x45, 0x56, 0x5B, 0x16, 0xF0, 0xA2, 0x3E, 0x36, + 0x8E, 0x84, 0x0B, 0x08, 0xB8, 0xC5, 0x9F, 0x53, 0xB6, 0x06, 0xA9, 0x12, + 0x75, 0x61, 0xDB, 0x95, 0x26, 0x8F, 0x7C, 0x04, 0x86, 0x1E, 0x6E, 0x27, + 0x96, 0xB0, 0x27, 0xA0, 0x1E, 0x20, 0xBD, 0x9F, 0x6E, 0x89, 0x31, 0x8F, + 0x87, 0xB1, 0x96, 0x49, 0xB4, 0x8D, 0x0D, 0x7D, 0x87, 0xDC, 0xAF, 0x15, + 0xC4, 0xEA, 0x28, 0x47, 0xBA, 0x7A, 0xDF, 0x5F, 0x49, 0xC4, 0xC2, 0xDC, + 0xDA, 0xFD, 0x7B, 0xF3, 0xC5, 0x8F, 0x41, 0xFF, 0xE5, 0x1D, 0x96, 0x25, + 0xC7, 0x9A, 0x54, 0xC8, 0x02, 0x40, 0x50, 0x7E, 0xCE, 0x37, 0x5C, 0x3C, + 0xD5, 0xB4, 0xB4, 0x4B, 0x9C, 0x50, 0x4D, 0xC3, 0xD2, 0x88, 0xE8, 0x6A, + 0xDF, 0x18, 0x81, 0xD4, 0x0D, 0x18, 0x6E, 0x1F, 0x69, 0x80, 0x22, 0x29, + 0x03, 0xBE, 0xFB, 0x0F, 0x61, 0x98, 0x3F, 0x37, 0x1C, 0x94, 0x87, 0xA8, + 0x5E, 0x89, 0x52, 0x2A, 0xBC, 0x88, 0xC7, 0xE6, 0xA9, 0x25, 0xF3, 0x96, + 0x72, 0x6E, 0xB6, 0x2F, 0x49, 0x50, 0xE4, 0xEE, 0x85, 0xC2, 0x7E, 0x87, + 0xBF, 0x2C, 0x7A, 0x40, 0xFE, 0xBB, 0x34, 0x85, 0xB4, 0xEF, 0x3B, 0xDD, + 0xB3, 0x50, 0x03, 0x7F, 0x0A, 0x1B, 0x25, 0x64, 0x23, 0x64, 0x63, 0x46, + 0xF0, 0xD8, 0xBB, 0x3F, 0xA3, 0x5C, 0x91, 0x6F, 0xAA, 0x2C, 0x42, 0xF2, + 0x7C, 0x43, 0xB7, 0x20, 0xAF, 0x00, 0xB7, 0xD8, 0xA8, 0x13, 0x69, 0x61, + 0x2B, 0xC5, 0x8C, 0xDB, 0xE5, 0x1F, 0x17, 0xE7, 0x52, 0xD0, 0xFD, 0x87, + 0x0B, 0xE8, 0x62, 0xEA, 0x11, 0x42, 0xDB, 0x14, 0x1F, 0xEA, 0xE2, 0x25, + 0x6A, 0xA6, 0x6F, 0x50, 0xDB, 0xB0, 0xC7, 0x7E, 0x5C, 0x58, 0x70, 0xE7, + 0x32, 0xA0, 0x04, 0x20, 0x7C, 0xBF, 0x29, 0x74, 0x4D, 0xA7, 0x72, 0x78, + 0xF8, 0x34, 0x9D, 0x25, 0xCA, 0x45, 0xE2, 0xF9, 0xDC, 0xD5, 0x6A, 0xD9, + 0x39, 0x98, 0x8B, 0x3F, 0xB6, 0x8E, 0x2B, 0x9F, 0xE0, 0x92, 0x75, 0xE2, + 0x51, 0xDC, 0xC9, 0x10, 0x73, 0x0A, 0x03, 0x06, 0x8C, 0x7B, 0xA8, 0x09, + 0x76, 0x92, 0x70, 0xCE, 0x10, 0xC9, 0x5E, 0xDB, 0xE4, 0x1C, 0x7A, 0x7E, + 0x8B, 0x33, 0xD7, 0xEA, 0x55, 0xA5, 0x32, 0xAF, 0xF1, 0x95, 0x1C, 0x23, + 0x21, 0xCE, 0x25, 0x06, 0x30, 0xA9, 0x7D, 0xC2, 0x30, 0x17, 0x0D, 0xCC, + 0xF0, 0x60, 0x81, 0x50, 0xC8, 0x66, 0xC0, 0x84, 0x35, 0x12, 0x6C, 0xC2, + 0xFE, 0x1B, 0xA0, 0xC1, 0x71, 0xFE, 0xA5, 0x65, 0x3B, 0x0D, 0xD5, 0x24, + 0xFE, 0x78, 0x36, 0x9A, 0xCD, 0xD5, 0x0A, 0x58, 0x07, 0xFF, 0x1A, 0x63, + 0x44, 0x94, 0x2B, 0x78, 0x81, 0x37, 0x23, 0x28, 0x96, 0x37, 0x83, 0x85, + 0xEC, 0x3B, 0xA0, 0xBE, 0xA0, 0xCF, 0xB1, 0xEB, 0xB1, 0x63, 0x93, 0xD0, + 0xA1, 0xBD, 0xE6, 0xD4, 0xD3, 0xDA, 0x90, 0x13, 0xBF, 0x55, 0x6A, 0x7A, + 0x87, 0x55, 0xF0, 0xE1, 0x0F, 0x21, 0x21, 0x5E, 0x8C, 0xFE, 0xA3, 0x35, + 0xA9, 0x17, 0x9A, 0x52, 0xE3, 0xEB, 0x30, 0x11, 0x07, 0xA6, 0x0C, 0xF2, + 0x5E, 0xE2, 0x91, 0x31, 0xFB, 0x68, 0xFC, 0x82, 0xA2, 0xCC, 0xC0, 0x92, + 0x2C, 0x01, 0xB8, 0x58, 0xF4, 0x45, 0xD0, 0x46, 0x58, 0xA6, 0x40, 0x5F, + 0x03, 0x3A, 0x28, 0x3E, 0x8D, 0x67, 0xE7, 0xEA, 0xB7, 0xED, 0xBE, 0xBF, + 0x21, 0xDA, 0xDB, 0xD2, 0xB7, 0x0A, 0x61, 0x43, 0x88, 0xC8, 0x3B, 0x69, + 0x0E, 0x3D, 0x8E, 0xD0, 0x6B, 0xC1, 0xAC, 0x33, 0x81, 0x6B, 0xC5, 0xB3, + 0x6E, 0x21, 0x3F, 0xC1, 0x03, 0x94, 0x9C, 0x55, 0x6B, 0x74, 0x33, 0x4E, + 0x41, 0x76, 0x7B, 0xB2, 0x47, 0x05, 0xC3, 0x9F, 0xD0, 0x82, 0xA2, 0xB7, + 0x9B, 0xE1, 0x86, 0xD0, 0x59, 0x57, 0x25, 0xB0, 0x0A, 0x77, 0x53, 0x2A, + 0x0E, 0x27, 0x11, 0xCD, 0xC9, 0xF7, 0x45, 0xFD, 0xDC, 0x49, 0x99, 0xC2, + 0xBB, 0xA2, 0x48, 0x5C, 0x01, 0xBE, 0x2F, 0xBE, 0x6C, 0x68, 0xC1, 0x8E, + 0x81, 0x10, 0x47, 0xB6, 0x80, 0x70, 0x1D, 0x18, 0x3C, 0xF6, 0x4B, 0xBA, + 0x98, 0xB5, 0x13, 0xD8, 0xEF, 0x2A, 0xA4, 0x89, 0xFA, 0xEA, 0xB3, 0x08, + 0x72, 0xE2, 0x26, 0x40, 0xD4, 0x19, 0xE4, 0x93, 0xB9, 0xA2, 0x21, 0x47, + 0x00, 0x02, 0xCD, 0xD0, 0xA2, 0x89, 0xD7, 0x2F, 0x8D, 0xDA, 0xCF, 0xB2, + 0x8B, 0x31, 0x91, 0xA5, 0x5F, 0x5E, 0x09, 0x2F, 0xAF, 0x13, 0x8B, 0x2A, + 0x23, 0x86, 0x82, 0xCD, 0x67, 0x9B, 0x46, 0x1D, 0x91, 0xA8, 0x6B, 0x41, + 0x32, 0x1A, 0x8F, 0x8A, 0x53, 0xD1, 0x05, 0xB2, 0x02, 0x09, 0xC8, 0x88, + 0xBB, 0xF6, 0x15, 0xF2, 0xF9, 0xD5, 0xC3, 0x64, 0x7F, 0x5D, 0x2F, 0xCC, + 0xA7, 0xD6, 0x03, 0xCD, 0x6A, 0x6B, 0xDA, 0xA3, 0xC5, 0x07, 0xA4, 0x8E, + 0x53, 0x04, 0xFC, 0x8A, 0xFD, 0x12, 0x13, 0x8A, 0x7D, 0x41, 0x6D, 0x4A, + 0xF1, 0x43, 0xC7, 0xC9, 0xF5, 0xDB, 0x8A, 0xE1, 0x43, 0xAD, 0xB8, 0xA4, + 0x51, 0xCA, 0x7A, 0x12, 0x8B, 0x02, 0x47, 0x1F, 0x54, 0x8B, 0x1A, 0x8C, + 0x27, 0x9F, 0xE4, 0x22, 0xE2, 0x1D, 0x8C, 0x04, 0x23, 0x21, 0x84, 0xFA, + 0x68, 0xB6, 0x8A, 0xE9, 0xC2, 0xB2, 0x98, 0xDE, 0x41, 0x0C, 0x17, 0x5A, + 0xEF, 0x2F, 0xB3, 0x74, 0x02, 0x0B, 0xDC, 0x4B, 0xC8, 0xE9, 0xDE, 0xE2, + 0x94, 0xE5, 0x62, 0x2D, 0x59, 0x63, 0x74, 0x10, 0xBE, 0xDA, 0x36, 0xD9, + 0x2A, 0x14, 0x22, 0xD0, 0x25, 0x1F, 0xEE, 0xC4, 0x45, 0x28, 0xD1, 0x34, + 0x02, 0xF8, 0xFA, 0xDC, 0xE0, 0xF2, 0xB5, 0xA3, 0xB1, 0xAC, 0xB6, 0xE1, + 0xA6, 0x4C, 0x90, 0x76, 0xF4, 0xFB, 0xEC, 0x83, 0x45, 0xEF, 0xE5, 0xEC, + 0x0C, 0xDE, 0xE6, 0xAD, 0xB5, 0x85, 0x68, 0xDB, 0x63, 0xE6, 0x32, 0x6E, + 0xE1, 0x2C, 0xE8, 0xF0, 0x52, 0xC4, 0xB9, 0xB7, 0x13, 0x7D, 0xE8, 0x5F, + 0x9D, 0x74, 0xE1, 0x1C, 0x06, 0x3B, 0x49, 0x58, 0x0C, 0x17, 0x19, 0x75, + 0xB1, 0x96, 0x9D, 0xCF, 0x09, 0x9A, 0xFC, 0xD8, 0x45, 0x70, 0x48, 0x96, + 0x15, 0x69, 0xBD, 0xAC, 0x7D, 0x79, 0xD3, 0x52, 0xE7, 0xED, 0xC4, 0x33, + 0x5E, 0x26, 0x82, 0x89, 0xAA, 0x71, 0x53, 0xFE, 0x0B, 0x12, 0x84, 0x81, + 0x94, 0x6B, 0x45, 0x47, 0x32, 0x8C, 0x7B, 0xFC, 0x56, 0xD1, 0xEC, 0xFD, + 0x1F, 0x9D, 0x16, 0x5A, 0x84, 0xDC, 0xE0, 0xA9, 0x3C, 0x9D, 0x34, 0x11, + 0xBE, 0x16, 0x02, 0x90, 0x38, 0x3F, 0x5B, 0x85, 0x1D, 0x4E, 0x99, 0x46, + 0x24, 0x51, 0x32, 0x5F, 0xF2, 0x35, 0xB3, 0xC9, 0x4A, 0x58, 0x5D, 0x3A, + 0x0E, 0xEA, 0xC8, 0x0C, 0xED, 0xF6, 0x67, 0x0C, 0xAF, 0xD8, 0x42, 0x0C, + 0xEC, 0xAF, 0x72, 0x86, 0x28, 0x2A, 0x41, 0xAE, 0xED, 0xA2, 0x0A, 0x53, + 0x7C, 0xE7, 0x04, 0xA5, 0x28, 0x6E, 0x90, 0xAF, 0xF4, 0x02, 0x79, 0x3D, + 0x3A, 0x58, 0xB9, 0x33, 0x3A, 0x56, 0x7E, 0x79, 0xDB, 0x17, 0x42, 0xB1, + 0x85, 0xBB, 0xD1, 0x1C, 0xC7, 0x92, 0x0B, 0x04, 0x5C, 0xF9, 0x4B, 0xF6, + 0x94, 0x2B, 0x53, 0x30, 0xD4, 0x33, 0x5E, 0x35, 0x7B, 0x1B, 0x26, 0x22, + 0xCD, 0x16, 0xAE, 0x2D, 0xC6, 0x4E, 0x7A, 0xC3, 0xC5, 0xF8, 0x46, 0x51, + 0x28, 0xB2, 0xA7, 0x39, 0x4E, 0x80, 0xBD, 0x66, 0x2B, 0xD8, 0xDB, 0xAC, + 0x41, 0x50, 0xED, 0xDE, 0xD8, 0xB5, 0xFF, 0xCC, 0xF4, 0x2C, 0x90, 0x99, + 0xD8, 0x90, 0x65, 0xD6, 0x46, 0xCC, 0x9C, 0xC5, 0x7B, 0x0A, 0x1B, 0x2D, + 0xF5, 0x08, 0xDC, 0xF7, 0xE3, 0x23, 0x47, 0x5F, 0x58, 0x56, 0x74, 0x6F, + 0xBD, 0x42, 0x92, 0x15, 0x81, 0x0F, 0x75, 0x2C, 0xD2, 0x73, 0xEB, 0xD6, + 0x49, 0xDF, 0xB4, 0x44, 0xF0, 0x19, 0xC1, 0x65, 0x6F, 0x3E, 0x85, 0x8B, + 0xD2, 0x50, 0xBC, 0xB4, 0xD6, 0xFC, 0x39, 0x83, 0xB8, 0xB2, 0x8D, 0xD1, + 0x71, 0x0F, 0x32, 0xE3, 0x22, 0x3C, 0xD8, 0xE6, 0x96, 0xBC, 0xB9, 0xCF, + 0xE4, 0xA6, 0x7A, 0x43, 0xCA, 0xA8, 0xB7, 0xB4, 0x3B, 0xB4, 0xAA, 0xA7, + 0x5D, 0xEE, 0xB4, 0xD5, 0xDF, 0x6C, 0xE0, 0x80, 0x93, 0x75, 0x12, 0xD5, + 0x25, 0xA6, 0x20, 0x49, 0x6E, 0xD2, 0x04, 0x3A, 0xFB, 0x21, 0x5B, 0x5B, + 0x90, 0xEB, 0xB8, 0xD3, 0xD0, 0xB2, 0x51, 0xB9, 0xDF, 0xA6, 0x9A, 0xD6, + 0x46, 0x48, 0x72, 0x8F, 0x9B, 0x8D, 0x05, 0x7F, 0x99, 0xB0, 0x2E, 0xD4, + 0x50, 0x72, 0x97, 0x0F, 0x80, 0x0D, 0x6C, 0x74, 0x6C, 0x41, 0x4C, 0x4D, + 0xEE, 0xF4, 0x1F, 0xB3, 0xA7, 0x94, 0xB9, 0x65, 0xF2, 0xA3, 0x90, 0xAB, + 0x99, 0xD5, 0xEF, 0x1B, 0xD5, 0xD7, 0xE4, 0xC6, 0x2A, 0x73, 0x67, 0x47, + 0xFA, 0x92, 0xD2, 0x56, 0x1A, 0xF8, 0x1B, 0xFF, 0xB5, 0x39, 0x70, 0x06, + 0xC7, 0xE9, 0xE4, 0x16, 0x62, 0xEC, 0x56, 0x29, 0x79, 0xC1, 0x38, 0x17, + 0x8B, 0x14, 0x6C, 0xE9, 0x99, 0x08, 0xF6, 0xBC, 0xFE, 0xEF, 0x61, 0x55, + 0x51, 0x99, 0x9B, 0xC4, 0x7B, 0x71, 0xC5, 0xF5, 0xE0, 0x2D, 0xDA, 0xB5, + 0xD1, 0xCD, 0x00, 0x2E, 0xFF, 0xA0, 0xDF, 0xBB, 0xC7, 0x0A, 0xBB, 0x37, + 0x36, 0xEF, 0xDD, 0xE5, 0x51, 0xC7, 0x73, 0xC0, 0x35, 0xD2, 0x05, 0x89, + 0x3D, 0x61, 0x79, 0xD9, 0xD5, 0x9C, 0xD8, 0xA1, 0xE8, 0x43, 0x4F, 0x91, + 0xD4, 0xF3, 0x8F, 0x32, 0x2F, 0x42, 0xF9, 0xA5, 0x84, 0x0B, 0x23, 0xEC, + 0xC3, 0x69, 0x1A, 0xE7, 0xCB, 0x9F, 0x44, 0xD9, 0x7B, 0xAD, 0x7B, 0x6F, + 0x48, 0x55, 0xCE, 0x21, 0x0E, 0x73, 0x59, 0xD6, 0x28, 0xA3, 0xE8, 0x67, + 0xA7, 0x55, 0xEC, 0xE3, 0x39, 0xAF, 0x8A, 0x0E, 0x9F, 0x2E, 0xDF, 0x9E, + 0xBC, 0x40, 0x0C, 0x4C, 0x92, 0x70, 0xB6, 0x68, 0xD5, 0x59, 0xAB, 0x5E, + 0xF4, 0x6F, 0x9D, 0xB5, 0xE7, 0x52, 0x73, 0xAC, 0xE9, 0x39, 0x3A, 0x9E, + 0xD4, 0x98, 0xB7, 0x22, 0x01, 0x46, 0x83, 0x9A, 0xA4, 0x62, 0x0C, 0xA8, + 0x80, 0x22, 0xF7, 0x93, 0x40, 0x10, 0x23, 0x53, 0x04, 0xB1, 0x00, 0x51, + 0xA7, 0xFF, 0x84, 0x1C, 0x47, 0x3C, 0xEF, 0xEE, 0x65, 0xB1, 0xBC, 0x04, + 0xEC, 0xB9, 0x20, 0x24, 0xD5, 0x1B, 0x9A, 0xAE, 0x36, 0x0F, 0x0D, 0x6A, + 0x4D, 0x0A, 0x31, 0x91, 0xD1, 0x1C, 0x05, 0x4C, 0x3A, 0x20, 0xA7, 0xC1, + 0x8C, 0xD3, 0xF6, 0xC6, 0xBB, 0x82, 0xD1, 0x48, 0xF2, 0x06, 0x8D, 0x28, + 0xC8, 0xE9, 0x6C, 0xAB, 0xB7, 0xD1, 0x9D, 0x72, 0x92, 0xA0, 0xC7, 0x09, + 0x81, 0x7B, 0x6A, 0x2A, 0xF7, 0xE5, 0x88, 0xE9, 0xEF, 0x82, 0xD3, 0xEC, + 0x0B, 0xAA, 0x77, 0x4E, 0x32, 0xFF, 0x50, 0x47, 0xEA, 0x81, 0x49, 0xAA, + 0x81, 0xD5, 0x7E, 0x7B, 0x41, 0xED, 0x99, 0x4B, 0xCB, 0x9A, 0x2F, 0x9C, + 0xFE, 0x86, 0x98, 0x5E, 0xB3, 0x4A, 0x81, 0xFE, 0x66, 0xFA, 0xCF, 0x40, + 0x77, 0xBE, 0xE2, 0x90, 0xD5, 0xB9, 0xA2, 0x0E, 0xDD, 0xB0, 0x3C, 0xB9, + 0xCE, 0xE0, 0xC0, 0xA3, 0xA4, 0x09, 0xA2, 0x06, 0xB5, 0x31, 0x46, 0xF6, + 0x9E, 0x3E, 0x33, 0xB0, 0x1A, 0x71, 0xDD, 0x6A, 0x36, 0x1D, 0x2A, 0x23, + 0x18, 0x9F, 0xAD, 0x67, 0x5A, 0x9F, 0x94, 0x0C, 0xA0, 0xB4, 0x19, 0xA7, + 0x68, 0xF6, 0xEE, 0x76, 0x5B, 0xBC, 0xC1, 0x9C, 0x4D, 0x02, 0xB9, 0x74, + 0xC5, 0x32, 0xC4, 0x82, 0xE7, 0x71, 0xA1, 0x73, 0x8A, 0xCD, 0xAC, 0x8C, + 0x86, 0xF6, 0x41, 0xB6, 0xAD, 0x89, 0xFB, 0x02, 0x29, 0xAB, 0xE3, 0xE0, + 0x90, 0x6B, 0x1A, 0xE1, 0xF4, 0xDA, 0xFD, 0x1C, 0xAE, 0x8B, 0x51, 0x23, + 0x8C, 0x10, 0x0B, 0xD7, 0x50, 0xFB, 0x95, 0x08, 0xC1, 0x3E, 0x70, 0x56, + 0xFE, 0x0D, 0x49, 0xC7, 0x93, 0x99, 0xC5, 0x9A, 0xF7, 0x55, 0xC8, 0xEB, + 0x28, 0xDF, 0x57, 0x3C, 0x8B, 0x23, 0x1B, 0x29, 0xA2, 0x98, 0x33, 0xEC, + 0xF9, 0x7B, 0xE4, 0x1B, 0xEA, 0x2E, 0xAB, 0x31, 0xC2, 0xBB, 0x8B, 0x1D, + 0x6C, 0x6D, 0x47, 0x2A, 0xBA, 0xFD, 0xF8, 0x9E, 0x48, 0x86, 0x24, 0x73, + 0x0D, 0x61, 0x14, 0xEE, 0x19, 0x56, 0x2F, 0xC5, 0xC2, 0x02, 0x2A, 0x5E, + 0xC5, 0xAC, 0x64, 0xBF, 0xF8, 0xAF, 0xB4, 0x43, 0xB6, 0x3D, 0xAC, 0xFB, + 0x29, 0x25, 0x56, 0x3A, 0x16, 0x5B, 0x7C, 0x70, 0x27, 0x2C, 0xA6, 0xB8, + 0x5C, 0x59, 0x3B, 0xDC, 0x89, 0x41, 0xE3, 0x32, 0x30, 0x9C, 0x50, 0xEB, + 0xF3, 0xC1, 0x11, 0xD6, 0x0B, 0xF3, 0x6C, 0xC0, 0x38, 0xE8, 0x57, 0x20, + 0x99, 0xC6, 0x66, 0x52, 0xA6, 0xEB, 0x70, 0xF2, 0xE5, 0xE9, 0x3A, 0xE4, + 0x8E, 0x99, 0xEA, 0xC4, 0x4D, 0x67, 0x69, 0xEE, 0xF1, 0xEE, 0x79, 0x02, + 0x6B, 0xBC, 0xEF, 0xA9, 0x54, 0x99, 0x43, 0xF2, 0x2F, 0xB7, 0xD5, 0x0E, + 0xF3, 0x18, 0x4A, 0x67, 0xCD, 0xEE, 0x86, 0x67, 0x18, 0xA1, 0x30, 0x5A, + 0xFB, 0x08, 0xA3, 0x99, 0x93, 0xD6, 0x70, 0x4C, 0x0A, 0x3C, 0xC0, 0x5B, + 0x45, 0x06, 0xD3, 0x88, 0x5F, 0x37, 0xC5, 0x43, 0x7D, 0x8A, 0x3B, 0xC1, + 0x50, 0xEA, 0x94, 0x45, 0xE8, 0x25, 0x9E, 0xE3, 0xDE, 0xC1, 0x82, 0xA9, + 0x69, 0xD2, 0x72, 0x16, 0xAD, 0x29, 0x3A, 0x8D, 0xF8, 0xAD, 0x48, 0x81, + 0x15, 0xF9, 0xE8, 0x28, 0xB9, 0x3D, 0x75, 0x7A, 0x89, 0x67, 0x62, 0x6A, + 0x13, 0x6E, 0x06, 0x64, 0x97, 0xBE, 0x50, 0x33, 0xCD, 0xAC, 0x87, 0xAD, + 0xA8, 0xE0, 0x06, 0xAC, 0x22, 0x50, 0x04, 0x9A, 0x72, 0xED, 0xBE, 0xE3, + 0x31, 0x3B, 0x43, 0x59, 0x33, 0xEF, 0xF7, 0x8B, 0x15, 0xDD, 0xE9, 0xAC, + 0x4D, 0xAE, 0xAC, 0x86, 0x19, 0x9C, 0xA4, 0xD9, 0xDE, 0x9B, 0x81, 0x44, + 0x25, 0xA1, 0x76, 0x2F, 0x80, 0xF6, 0x63, 0x71, 0xE0, 0x33, 0x41, 0x5D, + 0xE6, 0x02, 0x3F, 0x7A, 0x4B, 0xFE, 0xB6, 0x83, 0xB2, 0x19, 0x66, 0xAF, + 0x6D, 0x8D, 0x71, 0x63, 0xE2, 0x80, 0x8A, 0x68, 0xB5, 0x57, 0x14, 0x09, + 0x75, 0x80, 0x0D, 0x98, 0xD8, 0xB1, 0x32, 0xED, 0xCE, 0xE7, 0x62, 0x95, + 0x6B, 0x40, 0x69, 0x96, 0x99, 0x8F, 0xF8, 0x55, 0xE1, 0x5D, 0xDC, 0x82, + 0xB9, 0xEB, 0xB8, 0xB4, 0x29, 0x10, 0x44, 0x77, 0x93, 0xCD, 0x41, 0x6A, + 0xF3, 0x02, 0xC6, 0x94, 0x91, 0x9B, 0x79, 0x7B, 0x72, 0x6E, 0x9A, 0xC0, + 0xB9, 0xBD, 0xD1, 0x94, 0x20, 0xF2, 0x79, 0x13, 0x66, 0x1B, 0x68, 0x3B, + 0xF3, 0x67, 0xE6, 0x3B, 0xBE, 0xB8, 0xD9, 0x85, 0x1C, 0xB7, 0x2D, 0xB8, + 0x54, 0xAE, 0x9C, 0x6C, 0x42, 0xA1, 0xB1, 0x45, 0x03, 0x70, 0x15, 0x19, + 0x17, 0xDE, 0x42, 0x6D, 0x32, 0x7A, 0xEF, 0x4A, 0xE5, 0xEC, 0x6C, 0xA6, + 0xFC, 0x98, 0x15, 0xE1, 0xB4, 0x1D, 0xFA, 0x87, 0xA0, 0xC7, 0x49, 0xEC, + 0x97, 0x84, 0xC6, 0x6B, 0x85, 0xDD, 0xD6, 0xDA, 0x3E, 0xD9, 0x02, 0x63, + 0xE8, 0xF7, 0x7B, 0x54, 0xCC, 0x3C, 0x62, 0xED, 0x30, 0x77, 0x7A, 0x5C, + 0xE8, 0x26, 0xA9, 0xC5, 0x18, 0xF0, 0x28, 0xC0, 0x3E, 0xF1, 0x0C, 0x3D, + 0x66, 0x1C, 0x40, 0x79, 0xEE, 0x4F, 0x5E, 0xA7, 0xE1, 0xA0, 0x4E, 0xBF, + 0x81, 0xBE, 0x8F, 0x59, 0xB6, 0xDD, 0x2F, 0xD7, 0x66, 0x8D, 0x45, 0xE9, + 0x03, 0xDF, 0x41, 0x86, 0x46, 0x58, 0x26, 0xE4, 0xE8, 0x6D, 0xFD, 0xB9, + 0xD6, 0xB9, 0xD1, 0x31, 0xD8, 0xC1, 0xCC, 0x23, 0x45, 0xB1, 0x08, 0x9A, + 0xB0, 0xA6, 0x0C, 0xE7, 0xB6, 0x2F, 0x2B, 0xC9, 0x08, 0xCC, 0xC5, 0x9E, + 0x32, 0x92, 0xA2, 0xE7, 0x27, 0x86, 0xFD, 0x6E, 0x42, 0x80, 0xC0, 0xFD, + 0xC8, 0x87, 0xAA, 0x30, 0x30, 0x9E, 0x58, 0x46, 0x08, 0xA2, 0x40, 0xD7, + 0x7B, 0xE2, 0xCF, 0x00, 0x46, 0x02, 0x64, 0x42, 0x36, 0x72, 0x0E, 0x61, + 0xE0, 0xC5, 0x10, 0x04, 0x99, 0x9B, 0xB5, 0xED, 0x0C, 0x70, 0x8C, 0xEA, + 0x72, 0x38, 0x99, 0x37, 0xD8, 0xE0, 0x51, 0xF1, 0x30, 0xA3, 0x44, 0xED, + 0x4B, 0x7C, 0x9B, 0x9C, 0xA8, 0xAC, 0x6C, 0x63, 0xD8, 0xB9, 0x2C, 0x0F, + 0xA3, 0xBA, 0xA5, 0xDD, 0x54, 0x0C, 0x13, 0xA8, 0xBF, 0xF1, 0x38, 0x50, + 0xCF, 0xAE, 0xA1, 0xC5, 0xBD, 0x1E, 0x8E, 0xE1, 0xE5, 0xC0, 0xAA, 0x93, + 0x6C, 0xC5, 0xB1, 0xB9, 0xC9, 0x69, 0xBC, 0xA7, 0xC6, 0xAA, 0xE0, 0x95, + 0xC5, 0xCE, 0xAD, 0x36, 0x19, 0xAF, 0x39, 0x2F, 0xE7, 0xDA, 0xCF, 0x83, + 0x99, 0x22, 0x91, 0x73, 0xD1, 0x69, 0xDF, 0x28, 0x3E, 0x44, 0x6C, 0x3D, + 0x05, 0xB2, 0x3C, 0x1A, 0x69, 0x29, 0x54, 0x9A, 0x25, 0x3C, 0x7B, 0x81, + 0x19, 0xBF, 0x7F, 0xF7, 0xFC, 0x74, 0xD7, 0xFD, 0xBF, 0x7F, 0x9A, 0x6B, + 0x8A, 0x04, 0xE2, 0xE0, 0x9E, 0x23, 0x13, 0x63, 0xC6, 0x64, 0xFB, 0x85, + 0x0E, 0xDA, 0x2D, 0xC9, 0x32, 0x58, 0xD1, 0x30, 0x64, 0xFA, 0x49, 0xD7, + 0x54, 0x21, 0x1B, 0xDF, 0x25, 0x33, 0x00, 0xFB, 0xDC, 0x5B, 0x49, 0xC1, + 0x57, 0x12, 0xDB, 0xEE, 0xFA, 0xEB, 0x80, 0x0F, 0xBF, 0xD8, 0xF3, 0xB2, + 0x33, 0xE6, 0xF7, 0x1F, 0xD7, 0x3E, 0x97, 0x69, 0x8E, 0xF1, 0x07, 0xB1, + 0x0B, 0x37, 0xC1, 0xCD, 0x5D, 0xCE, 0x93, 0x43, 0x87, 0xE7, 0x75, 0x1B, + 0x77, 0x43, 0xED, 0x73, 0x0B, 0x5A, 0x17, 0x55, 0xB7, 0xA2, 0xD5, 0x56, + 0xE8, 0xFA, 0xC8, 0x96, 0xB1, 0x41, 0x6E, 0x8B, 0xB1, 0x39, 0x5F, 0xD6, + 0x6C, 0x89, 0x1D, 0x9E, 0x8F, 0x11, 0xBB, 0x3F, 0x0F, 0xD4, 0x27, 0xA7, + 0x8C, 0x08, 0xF2, 0xF0, 0x95, 0x14, 0xF8, 0x3D, 0xBC, 0x39, 0xF2, 0xBA, + 0xEE, 0x3C, 0x39, 0xB6, 0x7A, 0xE0, 0x7B, 0x0E, 0xF0, 0x4D, 0x24, 0x13, + 0x98, 0xFB, 0x76, 0x9D, 0x4C, 0x94, 0xB1, 0xE0, 0xAF, 0xC3, 0x15, 0x18, + 0xFA, 0x77, 0x26, 0x01, 0xB4, 0x64, 0x02, 0xE5, 0xCA, 0x80, 0x5A, 0x5F, + 0xC2, 0x59, 0x2C, 0x84, 0x6B, 0x20, 0x48, 0xA6, 0xB4, 0x08, 0xAB, 0x51, + 0x7A, 0xF4, 0x78, 0xEC, 0x37, 0xB7, 0x3E, 0xAC, 0x6A, 0x6B, 0x9B, 0x36, + 0x2F, 0x91, 0x98, 0xD4, 0xC9, 0xC1, 0x69, 0xC2, 0xB5, 0x4F, 0xB0, 0x88, + 0x08, 0xFB, 0x71, 0xD9, 0x51, 0x0C, 0x08, 0x7F, 0x1F, 0x66, 0x9E, 0xDA, + 0xC5, 0xA5, 0x26, 0x6A, 0x86, 0x6C, 0xBA, 0xA7, 0xC8, 0x5A, 0x4F, 0x8E, + 0x50, 0x3B, 0xE1, 0xB7, 0x68, 0xBA, 0x34, 0x36, 0x54, 0x56, 0x10, 0xEA, + 0x1A, 0x7C, 0x9E, 0xA9, 0xBD, 0x16, 0x3B, 0x39, 0xA0, 0x7B, 0x6E, 0xD3, + 0xA4, 0xF3, 0xC9, 0x87, 0x0D, 0xD0, 0x11, 0xE7, 0x9F, 0x95, 0xAA, 0x2E, + 0x66, 0xF7, 0x06, 0xF3, 0x43, 0xC0, 0x29, 0x15, 0xCF, 0xEA, 0xC8, 0x8D, + 0x21, 0x23, 0x8D, 0x5D, 0x64, 0x1C, 0xBF, 0xEA, 0x4B, 0x2C, 0xDC, 0x1A, + 0x44, 0x94, 0x10, 0x30, 0xE4, 0x6F, 0x20, 0xFA, 0x5E, 0x41, 0x8D, 0x49, + 0xE4, 0x8F, 0x25, 0xA0, 0x1B, 0x3E, 0x02, 0x98, 0x84, 0xC6, 0x62, 0xF7, + 0xD6, 0xC1, 0x16, 0x13, 0x39, 0xB3, 0xC4, 0x3A, 0xB4, 0xBC, 0xA6, 0x9B, + 0x27, 0x0A, 0x1C, 0x6A, 0x4F, 0x4F, 0x01, 0xEB, 0x38, 0x3C, 0xCD, 0xAD, + 0x6B, 0x53, 0xC4, 0xBE, 0xB4, 0xEE, 0x41, 0x8F, 0x69, 0xD7, 0x02, 0xB3, + 0x75, 0x08, 0x96, 0xC6, 0xB3, 0xB6, 0xC4, 0x88, 0xA9, 0x58, 0xD1, 0xE4, + 0x4C, 0x8A, 0x39, 0x83, 0x79, 0xEF, 0x02, 0x2C, 0x2F, 0xF5, 0x43, 0x2D, + 0x35, 0xAE, 0x27, 0x93, 0xF3, 0x6D, 0x46, 0x22, 0x66, 0xC5, 0x55, 0x71, + 0x8C, 0x68, 0xA8, 0xE6, 0x16, 0x94, 0xDB, 0xBA, 0x51, 0x90, 0x59, 0xBC, + 0x77, 0x45, 0xE4, 0xBE, 0x82, 0x21, 0x6C, 0x3A, 0x73, 0x93, 0xCF, 0x7E, + 0xC5, 0xC5, 0x64, 0x2C, 0x47, 0x93, 0x27, 0x1F, 0x7B, 0xED, 0x55, 0x16, + 0xC1, 0x6D, 0x89, 0x0F, 0xC5, 0x09, 0xD8, 0x44, 0xB4, 0x4F, 0x01, 0xA8, + 0x5A, 0x2B, 0xEF, 0xF5, 0x84, 0x11, 0x8E, 0x45, 0xD7, 0x96, 0x9A, 0x6F, + 0x60, 0x5A, 0x30, 0x42, 0x78, 0x66, 0xC3, 0x57, 0x88, 0x25, 0x8B, 0xF3, + 0xEA, 0xDA, 0xAA, 0xF4, 0xBD, 0x5C, 0x9D, 0xB2, 0x81, 0xE0, 0x88, 0x78, + 0xCB, 0x4D, 0xC2, 0xFB, 0x1B, 0xDD, 0x79, 0x5F, 0xBD, 0x08, 0x2F, 0x79, + 0x91, 0xAB, 0x62, 0xC3, 0xED, 0x72, 0xA5, 0xD2, 0x61, 0xE4, 0x08, 0xE2, + 0x6D, 0x48, 0xD7, 0xD7, 0x65, 0x0A, 0xB1, 0xB5, 0x06, 0xCD, 0x9A, 0x36, + 0x51, 0x92, 0x03, 0x46, 0x55, 0x2E, 0x24, 0x94, 0x0C, 0x44, 0x35, 0x28, + 0xC4, 0x0F, 0xC2, 0xF5, 0xB3, 0xB4, 0x2D, 0x5D, 0x16, 0xF0, 0x27, 0x87, + 0xC7, 0xA5, 0x48, 0x0B, 0x9A, 0x93, 0x69, 0x0B, 0x8E, 0xCF, 0xF6, 0xBF, + 0xE4, 0x67, 0x8F, 0x4A, 0x63, 0x9E, 0x6C, 0x74, 0x4A, 0x1F, 0xAD, 0x21, + 0xFE, 0x51, 0xD7, 0x6A, 0x65, 0x9F, 0x31, 0x6B, 0x34, 0x72, 0x18, 0xBF, + 0x09, 0x37, 0x09, 0x7F, 0x4C, 0x63, 0x79, 0xD4, 0x5C, 0x57, 0xF2, 0x71, + 0xD1, 0x8B, 0xBD, 0xC0, 0x35, 0xB8, 0xA4, 0x25, 0x80, 0xFF, 0x7D, 0x16, + 0x52, 0x0F, 0x0E, 0x14, 0xCE, 0x4F, 0xF8, 0xCB, 0x9C, 0x50, 0xD4, 0x17, + 0x8A, 0x7F, 0x4C, 0x9F, 0x82, 0x40, 0x61, 0x28, 0x95, 0xB2, 0x1A, 0x63, + 0x30, 0xC3, 0x5B, 0x6C, 0x3A, 0xDB, 0x5F, 0xEC, 0x5F, 0x30, 0xF0, 0x81, + 0x05, 0xFC, 0xFD, 0xE8, 0x7D, 0x3A, 0xFF, 0xBB, 0xF7, 0x53, 0x91, 0x44, + 0x27, 0x05, 0x1E, 0x4E, 0x34, 0xDF, 0x03, 0x25, 0x09, 0x71, 0x53, 0x9A, + 0x70, 0xE3, 0xAC, 0x34, 0x12, 0x7A, 0xC3, 0xB9, 0xC2, 0x1C, 0x48, 0x68, + 0x39, 0x88, 0x71, 0x26, 0x44, 0xE9, 0x98, 0x69, 0xE6, 0x6F, 0x5C, 0x56, + 0x97, 0xAA, 0xD0, 0x5D, 0xC0, 0xAA, 0x95, 0xEC, 0x0D, 0xC1, 0x65, 0xB1, + 0xEA, 0x69, 0x66, 0xC4, 0xBA, 0x0A, 0x9F, 0xEA, 0x64, 0xB9, 0xF3, 0xFB, + 0x1F, 0x26, 0xDB, 0x9F, 0xD7, 0x02, 0x9E, 0x42, 0x33, 0xEB, 0x56, 0xAB, + 0x1D, 0xB5, 0xFB, 0xC5, 0x6D, 0x20, 0x98, 0xA4, 0x3E, 0xE7, 0xE5, 0xC1, + 0x3C, 0xB1, 0x05, 0xA0, 0x25, 0x1A, 0x1E, 0x28, 0x6D, 0x08, 0xE1, 0xD5, + 0xAE, 0x90, 0xEF, 0x6F, 0xFA, 0xB5, 0x42, 0x58, 0xEE, 0xD4, 0xA2, 0x90, + 0xA0, 0x65, 0xFC, 0xCA, 0xA2, 0x64, 0x00, 0xB7, 0x69, 0x72, 0xEF, 0xFD, + 0x2A, 0x44, 0xF5, 0xAF, 0x64, 0x80, 0x0F, 0x79, 0x20, 0x9A, 0xB8, 0x6B, + 0x7F, 0x9E, 0x0F, 0xB3, 0x18, 0x1A, 0xCC, 0x19, 0x17, 0x2B, 0xB8, 0x2C, + 0xCA, 0xA5, 0x8E, 0xC5, 0x1B, 0xB1, 0xEC, 0xF6, 0x9C, 0x3C, 0x20, 0x10, + 0x04, 0x6F, 0x35, 0x26, 0x8D, 0x0C, 0x3B, 0xB3, 0x8C, 0x08, 0xE3, 0xDE, + 0x2C, 0x09, 0x1E, 0xEB, 0x33, 0x08, 0x38, 0x28, 0xA3, 0x4F, 0x32, 0xD1, + 0xDD, 0x1C, 0x51, 0x38, 0x94, 0x07, 0xC0, 0xF8, 0x4C, 0x0E, 0x88, 0x95, + 0x6B, 0x24, 0xC3, 0xE3, 0x24, 0xD1, 0xD8, 0xF8, 0xD0, 0xA0, 0x5A, 0xFE, + 0xF0, 0x1C, 0xDF, 0x03, 0x62, 0xE1, 0x61, 0xFF, 0x43, 0x19, 0xA6, 0x85, + 0x6E, 0xE3, 0xF6, 0xF7, 0x6A, 0xF9, 0x48, 0x42, 0xA2, 0xC5, 0x04, 0xB5, + 0x42, 0xC2, 0xFB, 0xC0, 0xF8, 0xC1, 0x60, 0x95, 0xFE, 0x87, 0xA9, 0xE1, + 0xBD, 0x05, 0x03, 0xB4, 0xB0, 0xC7, 0x60, 0x7C, 0x1C, 0x62, 0xBC, 0x0B, + 0x0C, 0x6D, 0xE3, 0x8E, 0x98, 0x93, 0x2B, 0xF4, 0x75, 0xF8, 0x3F, 0x18, + 0xF3, 0x07, 0x62, 0xEE, 0x7E, 0x38, 0x26, 0x76, 0x40, 0xEC, 0x17, 0x93, + 0xC2, 0xD1, 0xC5, 0x5A, 0xD9, 0xA3, 0x15, 0x74, 0x3F, 0xF0, 0xA8, 0x54, + 0xEA, 0x8C, 0x3A, 0xDF, 0x51, 0xC7, 0xFD, 0xF5, 0xFA, 0x0F, 0x83, 0x9C, + 0x58, 0x5D, 0x81, 0x64, 0xFA, 0xED, 0x82, 0x80, 0x1C, 0x5B, 0x36, 0xA5, + 0x44, 0x5B, 0x77, 0x91, 0x7E, 0x42, 0x05, 0xF5, 0x33, 0x95, 0xCC, 0xC8, + 0x09, 0x96, 0xC3, 0x4D, 0x72, 0x1D, 0xA3, 0x4C, 0x57, 0x40, 0xDA, 0xB8, + 0x7E, 0xF8, 0xCF, 0xFA, 0xF0, 0xB7, 0x13, 0xD0, 0xCF, 0x86, 0xAF, 0x60, + 0x6F, 0xCE, 0x74, 0x5E, 0xC3, 0xC5, 0x5F, 0xB6, 0x2D, 0x99, 0xBA, 0x2D, + 0x68, 0x5C, 0xFA, 0x89, 0x44, 0xFB, 0xDD, 0x58, 0x6D, 0x5F, 0xB9, 0xBE, + 0xC6, 0x83, 0xEC, 0xAA, 0x1B, 0x95, 0x66, 0x53, 0xA3, 0x6F, 0xCD, 0x08, + 0x58, 0x09, 0xB4, 0x2D, 0xF7, 0x14, 0x31, 0x03, 0xEA, 0xB5, 0x87, 0xE1, + 0x87, 0xE5, 0x1F, 0x06, 0x42, 0xAD, 0xE0, 0x1A, 0x76, 0x95, 0x8C, 0x72, + 0x8A, 0xDD, 0x41, 0xD5, 0xB3, 0x6D, 0x55, 0x6C, 0xAB, 0x38, 0x0A, 0x0F, + 0xE4, 0xC2, 0x08, 0xA6, 0x91, 0xE2, 0x2A, 0x49, 0x29, 0x1F, 0x24, 0x98, + 0x6F, 0x43, 0x81, 0xD5, 0x9C, 0x15, 0xE7, 0x7B, 0x14, 0x84, 0x74, 0xC9, + 0x92, 0x59, 0xE6, 0xA2, 0xB4, 0xF0, 0xE2, 0x8E, 0x56, 0xA6, 0x8F, 0xB1, + 0xAE, 0x8E, 0x83, 0x55, 0xB2, 0x56, 0xB9, 0xCA, 0x17, 0x05, 0x2D, 0x46, + 0x3A, 0x6B, 0x32, 0xC2, 0x89, 0x56, 0xFB, 0x65, 0x59, 0x2B, 0xA9, 0x38, + 0xD6, 0xE9, 0x51, 0xB8, 0x33, 0x4E, 0x3E, 0x30, 0x41, 0x30, 0x8D, 0x8A, + 0xF5, 0x2C, 0xEC, 0xC0, 0xDA, 0xA7, 0x30, 0x24, 0x23, 0x5F, 0x2B, 0x5D, + 0xE6, 0x9A, 0x27, 0xF6, 0x91, 0x0A, 0x78, 0x26, 0xFE, 0xA5, 0xA3, 0xFD, + 0x81, 0xDB, 0x8D, 0xA8, 0x24, 0xE6, 0xD2, 0xEA, 0xBA, 0xD0, 0xD3, 0x93, + 0x29, 0x7A, 0x16, 0xA8, 0x77, 0xD8, 0xE9, 0x56, 0x2C, 0xB8, 0x72, 0x69, + 0x65, 0x99, 0x8B, 0x9D, 0x00, 0xA0, 0x0D, 0x81, 0x6C, 0xC2, 0x3A, 0x2F, + 0x62, 0x13, 0xC6, 0xFA, 0x5A, 0x1E, 0x97, 0x5B, 0x66, 0xD3, 0xD5, 0xD4, + 0x33, 0x0B, 0x47, 0xC0, 0x62, 0x92, 0x7E, 0x8A, 0x31, 0x56, 0xCD, 0x61, + 0x5C, 0x98, 0xE8, 0x5F, 0x01, 0x6F, 0xA1, 0x10, 0xB0, 0x90, 0xC4, 0xF0, + 0xC5, 0x66, 0x69, 0x62, 0x51, 0x09, 0x57, 0x59, 0x3D, 0x66, 0xFF, 0x9F, + 0x7C, 0x0C, 0x48, 0x23, 0x19, 0xCC, 0x5A, 0x73, 0x66, 0x30, 0x95, 0xCB, + 0xE8, 0x88, 0xD3, 0xB2, 0x8D, 0xD6, 0x72, 0x8D, 0xDB, 0x33, 0x40, 0x91, + 0x77, 0xCD, 0x54, 0x51, 0xAF, 0x8C, 0xBE, 0x63, 0x86, 0x18, 0x01, 0x4D, + 0xAF, 0xF7, 0x09, 0xF0, 0xD8, 0x92, 0x33, 0x7A, 0x46, 0x6A, 0x68, 0x4E, + 0x4F, 0x64, 0x22, 0xB2, 0xC5, 0x0D, 0x58, 0x66, 0x29, 0xA9, 0x62, 0x14, + 0x58, 0x4F, 0x2D, 0xAC, 0xE3, 0x2B, 0xF6, 0x9F, 0x76, 0x26, 0x3F, 0x2C, + 0x3B, 0xE0, 0x42, 0x53, 0xED, 0x37, 0x48, 0x51, 0x87, 0x2C, 0x38, 0x3B, + 0x27, 0xA9, 0x98, 0xAE, 0xAD, 0xD6, 0xA7, 0x09, 0xCB, 0x14, 0x2A, 0x7A, + 0xB7, 0x27, 0x82, 0x6E, 0x0E, 0xA2, 0xA6, 0xB9, 0xEF, 0x61, 0x2D, 0x2A, + 0xC6, 0x35, 0xC2, 0x1B, 0xE4, 0xCE, 0x3F, 0x2E, 0x3F, 0x42, 0x18, 0x0D, + 0x63, 0xED, 0x9B, 0x60, 0x3E, 0xE8, 0x14, 0x16, 0x0B, 0x21, 0x4C, 0x8E, + 0x2E, 0x67, 0x23, 0x07, 0x43, 0xD7, 0xBD, 0xC1, 0x67, 0xD8, 0x65, 0xAE, + 0x8F, 0x60, 0xE8, 0x2E, 0x1F, 0x34, 0x92, 0xBA, 0x11, 0x9F, 0xF3, 0xDA, + 0x63, 0xC2, 0xD5, 0x28, 0xF0, 0x81, 0x52, 0x5D, 0x4D, 0x3A, 0xFC, 0x1B, + 0xED, 0xFD, 0x15, 0x6F, 0xC0, 0xBB, 0xED, 0xFF, 0xAF, 0x20, 0xBE, 0x80, + 0xA9, 0xDD, 0xED, 0x41, 0x92, 0x74, 0x32, 0x41, 0xC3, 0x1C, 0x4D, 0x36, + 0x36, 0xBC, 0x60, 0x77, 0x61, 0xB5, 0xE1, 0x61, 0x7D, 0xD1, 0x38, 0x20, + 0x63, 0x57, 0x1C, 0x9F, 0x86, 0xAC, 0x0E, 0x79, 0xAA, 0x4D, 0x88, 0xCD, + 0x2F, 0xCF, 0x4E, 0x03, 0x0E, 0xF3, 0x21, 0x1B, 0xAA, 0x01, 0x88, 0x97, + 0x71, 0x86, 0xD6, 0xAB, 0xD3, 0x15, 0x7F, 0xCF, 0x9E, 0xF5, 0xB6, 0x91, + 0x84, 0x61, 0xB5, 0xBF, 0xC2, 0x74, 0xC7, 0x33, 0x0F, 0x74, 0x1D, 0xDE, + 0xF9, 0x86, 0x69, 0x9C, 0x5B, 0xFB, 0x52, 0xA6, 0xBE, 0xD5, 0xBC, 0xD1, + 0xE3, 0x40, 0x05, 0xC6, 0x17, 0x11, 0x87, 0xBE, 0x08, 0xDA, 0xBD, 0x9D, + 0xFF, 0x89, 0x3B, 0x63, 0x77, 0xE5, 0xB8, 0x30, 0x41, 0x09, 0x70, 0x79, + 0xB1, 0x1E, 0x57, 0x58, 0xD7, 0xEF, 0xEF, 0xD0, 0x8C, 0x63, 0xD2, 0xEA, + 0xB1, 0xEA, 0x0A, 0x70, 0x56, 0x92, 0x14, 0xCF, 0xA5, 0x95, 0x5E, 0xFA, + 0xEA, 0x0B, 0xA3, 0x1B, 0x76, 0x0E, 0x8C, 0x0A, 0x69, 0x3B, 0x86, 0x78, + 0x32, 0xDF, 0x5D, 0x10, 0x23, 0x7E, 0x69, 0xD8, 0x3C, 0x1C, 0xD8, 0x8B, + 0x50, 0x95, 0x7B, 0x11, 0x76, 0xFC, 0xB7, 0x1B, 0xDB, 0xF8, 0x8A, 0xBF, + 0x76, 0x8E, 0x21, 0xCB, 0x58, 0x39, 0x73, 0x07, 0x78, 0x75, 0x85, 0xD4, + 0xEE, 0x60, 0x57, 0xDF, 0xE5, 0xFB, 0xEF, 0x66, 0x38, 0xC6, 0xA6, 0x01, + 0xAD, 0x13, 0x5F, 0x16, 0x04, 0x33, 0xDD, 0x9C, 0x21, 0xE1, 0xD4, 0xFD, + 0x72, 0x26, 0x0C, 0xA4, 0x2D, 0x22, 0x80, 0x56, 0x24, 0x69, 0x0E, 0xD4, + 0x5E, 0xB3, 0x2B, 0x79, 0x6A, 0xE4, 0x9D, 0x70, 0x66, 0xFA, 0x73, 0xA2, + 0xDD, 0x90, 0x51, 0x33, 0xB0, 0x0C, 0x4A, 0xAF, 0x0B, 0xDE, 0x62, 0x8B, + 0x45, 0x8F, 0xA1, 0x1E, 0xE4, 0xEA, 0xE2, 0x42, 0xAA, 0x43, 0x11, 0xA3, + 0x84, 0xE8, 0x7D, 0x91, 0x88, 0x15, 0x72, 0xDD, 0x50, 0x31, 0xA9, 0xA5, + 0x17, 0x1B, 0x8C, 0xC8, 0x68, 0x7D, 0xCF, 0x0F, 0xE4, 0x66, 0xFF, 0x63, + 0x30, 0xAA, 0x19, 0x4F, 0x50, 0xF6, 0xA4, 0xEC, 0x45, 0xD0, 0x69, 0x5A, + 0x22, 0x0A, 0x0F, 0xA6, 0x9C, 0x74, 0x0D, 0x4B, 0x12, 0x14, 0xC0, 0x5B, + 0xFB, 0xCE, 0x0D, 0xAD, 0xB1, 0xBD, 0x92, 0x1C, 0x98, 0x80, 0xDA, 0xD1, + 0xAE, 0x5D, 0x7C, 0xE6, 0x4B, 0x52, 0x37, 0xDE, 0x70, 0x67, 0x14, 0x04, + 0x5C, 0xEF, 0x9D, 0xD3, 0xAF, 0x05, 0x8E, 0xA8, 0x9D, 0x97, 0xDE, 0x9D, + 0xD9, 0x19, 0xBE, 0x34, 0x95, 0x4D, 0x02, 0x15, 0x95, 0x79, 0xDD, 0x59, + 0x0B, 0x89, 0x60, 0x71, 0xC3, 0x64, 0x40, 0x21, 0x26, 0x90, 0xA6, 0x80, + 0xDE, 0x06, 0x91, 0xC7, 0x94, 0x58, 0x11, 0x5C, 0x64, 0x5F, 0xB5, 0x0D, + 0x6A, 0x11, 0xE0, 0x54, 0xF0, 0x5C, 0xC8, 0x4D, 0xEA, 0x52, 0x2B, 0xCE, + 0x3F, 0x1E, 0xFF, 0x21, 0x1A, 0xAF, 0x28, 0x17, 0x69, 0xC8, 0x8F, 0x1C, + 0xC5, 0x72, 0x8A, 0x5C, 0x36, 0xCF, 0x40, 0x68, 0xDE, 0x3D, 0x9F, 0xED, + 0x4A, 0x16, 0xCD, 0x5B, 0x54, 0x03, 0xD8, 0x9B, 0x07, 0x36, 0xB2, 0x40, + 0xD0, 0x73, 0x6D, 0xCA, 0x6E, 0xEA, 0xAD, 0xA4, 0x90, 0x25, 0xFE, 0x7F, + 0xCA, 0x1E, 0xF2, 0xF2, 0xC1, 0xDA, 0xAE, 0x4A, 0x7F, 0x60, 0xE2, 0xE0, + 0xE3, 0xD5, 0x69, 0xD3, 0xF9, 0xE1, 0x8F, 0x4F, 0xBB, 0x5F, 0xF6, 0xAA, + 0x74, 0x32, 0x69, 0x42, 0x88, 0x6E, 0xB9, 0x22, 0x2D, 0xF6, 0xE2, 0x17, + 0xAA, 0xC5, 0x3E, 0x6E, 0xB5, 0x45, 0x05, 0xE5, 0x59, 0x26, 0xAC, 0x94, + 0x61, 0x70, 0xFC, 0x72, 0xA3, 0xCE, 0x68, 0x9F, 0xAA, 0x3E, 0xB1, 0x49, + 0x7F, 0x02, 0x4C, 0x9B, 0xCA, 0x72, 0xFF, 0x46, 0x46, 0x11, 0xF9, 0x3D, + 0x29, 0x43, 0x27, 0xD3, 0x7A, 0x40, 0x5D, 0xFC, 0x52, 0x91, 0xC2, 0xD9, + 0x94, 0x16, 0x13, 0xF3, 0x9C, 0xD3, 0x36, 0x1A, 0x0A, 0xD8, 0x5A, 0x50, + 0x06, 0xD4, 0xF9, 0xDC, 0x2B, 0x6A, 0xE7, 0x71, 0x33, 0x07, 0xBD, 0xDB, + 0x97, 0x6D, 0xB8, 0x23, 0x73, 0x80, 0xF1, 0x17, 0x6C, 0xF5, 0x47, 0x11, + 0x68, 0x66, 0xCD, 0x4A, 0x14, 0xDB, 0x76, 0x14, 0xE0, 0x52, 0x8A, 0xB2, + 0x6A, 0x18, 0x4E, 0xC7, 0xB5, 0x90, 0xC1, 0x2A, 0xB2, 0xD6, 0xA7, 0xAD, + 0x53, 0xBE, 0xA6, 0xDF, 0xCB, 0xC6, 0xC5, 0xF1, 0x4D, 0x76, 0x69, 0xC1, + 0x91, 0x0A, 0xA2, 0x86, 0x42, 0xAC, 0xFA, 0xD1, 0x79, 0x35, 0xDB, 0x5F, + 0xB8, 0x50, 0xC7, 0x4E, 0xB7, 0xD6, 0x22, 0xD5, 0x1D, 0x1B, 0xCB, 0xE7, + 0xCA, 0x21, 0x06, 0x25, 0xB7, 0x73, 0xCE, 0xA2, 0x0D, 0x33, 0xE5, 0xAA, + 0xDD, 0x59, 0xB7, 0x9F, 0x89, 0x42, 0xEE, 0xE7, 0xB6, 0x82, 0xDA, 0x36, + 0x18, 0x21, 0xC8, 0x89, 0x61, 0x30, 0x0F, 0xD1, 0x8D, 0xE0, 0xF1, 0x8E, + 0x9B, 0xE9, 0x16, 0x76, 0x49, 0x77, 0x30, 0x3D, 0x85, 0xE9, 0xDC, 0x52, + 0x28, 0x0D, 0x38, 0xEE, 0x2D, 0x7F, 0xBD, 0x08, 0x54, 0x87, 0x71, 0x91, + 0x73, 0x30, 0xCC, 0x58, 0xAD, 0x67, 0xDC, 0xF8, 0x12, 0x55, 0xC2, 0x14, + 0xBA, 0x15, 0xFA, 0xD2, 0x79, 0xC6, 0xB6, 0x3A, 0x5C, 0x52, 0x10, 0xCE, + 0xE9, 0xBC, 0xF6, 0xFA, 0xAA, 0x0E, 0xB3, 0x37, 0x23, 0x1E, 0x16, 0x75, + 0x57, 0x62, 0xF5, 0x67, 0x2E, 0xC6, 0x3E, 0x46, 0x17, 0x77, 0xE1, 0x33, + 0x85, 0x52, 0x08, 0x82, 0xF6, 0xA4, 0xE1, 0xD8, 0x8E, 0x83, 0xA7, 0x7B, + 0xB1, 0xBE, 0x7D, 0x4C, 0xE8, 0x2E, 0x67, 0x58, 0x4C, 0x3E, 0x82, 0x85, + 0x78, 0xBB, 0x7C, 0xDA, 0xC9, 0xB0, 0xBF, 0x6B, 0x17, 0x14, 0x8A, 0x0F, + 0xB3, 0x76, 0x21, 0xAF, 0x54, 0x9C, 0x35, 0xE1, 0x02, 0x35, 0x3B, 0x85, + 0xC6, 0x25, 0x67, 0x43, 0xA5, 0x07, 0x60, 0x81, 0x52, 0x66, 0x57, 0x4C, + 0xF2, 0x81, 0x55, 0x9E, 0xED, 0xCF, 0x12, 0x8D, 0x79, 0x02, 0x8A, 0x80, + 0xE4, 0xBC, 0x75, 0xCC, 0xE3, 0x61, 0x6B, 0xFA, 0x2F, 0xF0, 0x8A, 0xB7, + 0xDD, 0x9E, 0xBD, 0x48, 0xC5, 0x9F, 0x12, 0xCD, 0x32, 0x96, 0x35, 0x1D, + 0x87, 0xC1, 0x79, 0xFD, 0xE3, 0x5E, 0x2B, 0x70, 0x65, 0x1B, 0x89, 0x00, + 0xA6, 0x82, 0xE0, 0xB6, 0xD1, 0x2C, 0xC6, 0x42, 0xB0, 0x2E, 0x36, 0x79, + 0x01, 0x7F, 0xFA, 0xF1, 0x99, 0x33, 0x5D, 0x8B, 0xD6, 0x16, 0xE1, 0x75, + 0x70, 0x0A, 0x51, 0xC7, 0xED, 0x1C, 0x23, 0x85, 0xC9, 0xB0, 0x74, 0xFF, + 0x47, 0x15, 0x8E, 0xE9, 0x21, 0x80, 0x64, 0x17, 0x96, 0xA1, 0xC6, 0xA1, + 0xF2, 0xCD, 0x79, 0xF1, 0x00, 0x3C, 0x14, 0x45, 0xA1, 0x49, 0x12, 0x0E, + 0xFD, 0xD9, 0x56, 0x23, 0xA5, 0x58, 0xB2, 0x9B, 0x27, 0xBF, 0x75, 0xFA, + 0xC2, 0x14, 0xBB, 0xD9, 0x0E, 0x98, 0x28, 0x59, 0x55, 0xF1, 0xC0, 0xD5, + 0x27, 0x62, 0xE6, 0xFE, 0x82, 0x0E, 0xD8, 0xBF, 0xE4, 0xB7, 0x9E, 0x45, + 0x74, 0x89, 0x01, 0x35, 0x14, 0x6E, 0xBE, 0x14, 0x44, 0x0B, 0x3A, 0x60, + 0x1B, 0x18, 0xF0, 0x18, 0x88, 0xAD, 0x5A, 0xD4, 0xF1, 0xCC, 0x25, 0x74, + 0x8F, 0xF4, 0x9E, 0x47, 0x96, 0xB5, 0xC7, 0xFE, 0xCE, 0x62, 0xA3, 0x98, + 0xBD, 0x35, 0x55, 0xF5, 0x64, 0x2F, 0x62, 0x97, 0x6C, 0x25, 0x82, 0xC6, + 0x3C, 0xCE, 0xD9, 0x16, 0xB9, 0x4F, 0x4C, 0x3A, 0xFB, 0xF9, 0x7F, 0x6C, + 0x9A, 0x90, 0x3C, 0x80, 0xB2, 0x86, 0x5B, 0x0B, 0xC9, 0x7B, 0x2A, 0x06, + 0xA7, 0xCC, 0xBB, 0xD6, 0x31, 0x9F, 0xDD, 0x0D, 0x76, 0x53, 0xD2, 0x66, + 0x7C, 0xE3, 0x0E, 0xE7, 0x0B, 0x18, 0xFF, 0x59, 0x5C, 0xF2, 0xEC, 0x21, + 0xDE, 0x6B, 0xF0, 0x5A, 0x36, 0xF9, 0xA3, 0x9A, 0xE7, 0xFB, 0xE8, 0xC0, + 0x2D, 0x0B, 0x65, 0x47, 0xC4, 0x25, 0x81, 0x13, 0xFC, 0x0E, 0x41, 0x0E, + 0x1B, 0xE1, 0x19, 0x58, 0x06, 0x3E, 0x41, 0xF3, 0xF7, 0x82, 0x84, 0x70, + 0x30, 0x93, 0x7F, 0x52, 0x59, 0xC0, 0xF5, 0xDF, 0x06, 0x40, 0xF3, 0xB8, + 0x60, 0x60, 0x9F, 0x01, 0x58, 0xFF, 0x9F, 0x1B, 0x88, 0x7B, 0xF9, 0x68, + 0x6F, 0xC1, 0x12, 0x30, 0x9C, 0x4B, 0x52, 0x4B, 0x58, 0x82, 0x27, 0xCD, + 0x66, 0xE7, 0x58, 0x6C, 0xA1, 0xB9, 0x25, 0x6C, 0x06, 0x02, 0x39, 0xA4, + 0x4E, 0xA0, 0xCC, 0x4C, 0x07, 0x76, 0x62, 0x39, 0xB9, 0x28, 0xBD, 0xAE, + 0x64, 0x59, 0xEB, 0xCD, 0xFC, 0x64, 0x3A, 0x84, 0x56, 0xDE, 0x9A, 0xB3, + 0xE8, 0x06, 0xC3, 0xBA, 0x30, 0xED, 0x4A, 0x01, 0x5B, 0x26, 0xE4, 0xFB, + 0xAE, 0x81, 0x8E, 0xFF, 0xF9, 0x75, 0x4A, 0xDE, 0x77, 0xF4, 0xFD, 0x45, + 0x93, 0xBE, 0x43, 0x57, 0xE3, 0x87, 0x0F, 0x3A, 0xE9, 0x9A, 0x8A, 0x30, + 0x0C, 0xBC, 0x4E, 0x62, 0x89, 0x83, 0x89, 0x93, 0x5F, 0xE1, 0x39, 0x68, + 0x9A, 0x04, 0x9E, 0x87, 0xF5, 0xA1, 0x5A, 0x7C, 0x96, 0x22, 0x1F, 0x86, + 0x71, 0x9D, 0xCC, 0xB9, 0x4A, 0x7B, 0xF8, 0x19, 0xFF, 0x50, 0x66, 0xDB, + 0xE4, 0x26, 0x54, 0xD9, 0xEE, 0x78, 0x00, 0xA0, 0x73, 0x71, 0x10, 0xB5, + 0x1A, 0x31, 0x62, 0x22, 0xE7, 0xC6, 0xC2, 0x12, 0x09, 0xDC, 0xB5, 0x05, + 0x0A, 0x0F, 0xA1, 0x5D, 0x4A, 0x52, 0x50, 0xB0, 0xB8, 0x47, 0x4F, 0x3D, + 0x10, 0xC9, 0x05, 0xEE, 0x22, 0x86, 0xC6, 0x83, 0xD0, 0xAC, 0x28, 0x4D, + 0x30, 0xE9, 0xD9, 0xAD, 0x9F, 0xDA, 0xA3, 0x9C, 0xDB, 0xE2, 0x32, 0x64, + 0x4A, 0xD6, 0xE8, 0xD3, 0x99, 0x07, 0x8F, 0x17, 0x76, 0x6F, 0x69, 0x93, + 0xB4, 0x65, 0x37, 0xE2, 0x5E, 0xC4, 0xCD, 0xC1, 0x5E, 0xE7, 0xB9, 0xFD, + 0xD0, 0xEC, 0xCD, 0x9F, 0x99, 0x0B, 0xEE, 0x6B, 0xB3, 0x24, 0x06, 0x0F, + 0x99, 0x5E, 0x87, 0x73, 0xAE, 0xA7, 0x4E, 0x56, 0x35, 0x6B, 0xB5, 0x22, + 0x38, 0x10, 0x36, 0xEB, 0xCC, 0xF7, 0xDA, 0x84, 0x07, 0x6A, 0x0A, 0xE5, + 0xA8, 0x34, 0xB6, 0x5D, 0xDC, 0xB3, 0x0D, 0xBE, 0xC8, 0xB1, 0x3A, 0x7A, + 0xA7, 0x96, 0x8B, 0x19, 0xFB, 0x26, 0xD4, 0xE9, 0x43, 0x59, 0x0C, 0x31, + 0x92, 0xB7, 0x46, 0xD7, 0xB6, 0x03, 0x56, 0xA7, 0x25, 0x11, 0xA2, 0x37, + 0x9E, 0xBC, 0x2B, 0xB7, 0xE6, 0xC5, 0x64, 0xBD, 0x16, 0x56, 0x88, 0x3E, + 0xBB, 0x8C, 0x20, 0x63, 0x4F, 0x3C, 0x4C, 0x8A, 0x19, 0x63, 0x4B, 0xAB, + 0x0B, 0xF9, 0x6F, 0xDF, 0x62, 0x0B, 0x9D, 0x5B, 0x07, 0x33, 0xC6, 0xFC, + 0xAD, 0x8C, 0xAE, 0x03, 0x98, 0x44, 0xDC, 0x34, 0x26, 0x39, 0x99, 0xCB, + 0x8C, 0xCC, 0xE9, 0xCE, 0x77, 0x00, 0x09, 0x48, 0xEA, 0xE2, 0xDC, 0x11, + 0x1C, 0xE5, 0x20, 0x30, 0xA1, 0x3E, 0xB7, 0x96, 0x19, 0xFA, 0x78, 0x33, + 0x79, 0x2B, 0xA8, 0xCC, 0x69, 0x2C, 0x8D, 0x1B, 0x61, 0xF6, 0x3E, 0x68, + 0xBF, 0xCF, 0x95, 0xCC, 0x4A, 0x5C, 0x25, 0x92, 0xE9, 0x6F, 0x1D, 0xCB, + 0xE8, 0x9F, 0x37, 0x8E, 0x59, 0x1A, 0x72, 0xCC, 0x35, 0x53, 0xFE, 0x4F, + 0x50, 0xCA, 0x13, 0x22, 0x32, 0x1D, 0x77, 0x3A, 0xEA, 0x12, 0xE6, 0x8A, + 0xF3, 0x31, 0x5B, 0x27, 0x53, 0x32, 0x3F, 0xCB, 0x86, 0x5D, 0xC3, 0xEE, + 0xD3, 0x66, 0x19, 0xCF, 0xAE, 0x21, 0xE0, 0x21, 0xD4, 0x97, 0xD7, 0x52, + 0x93, 0xB7, 0xDD, 0x67, 0xEC, 0xFA, 0x99, 0x71, 0x07, 0x1C, 0x95, 0x07, + 0x70, 0xFA, 0xE5, 0x55, 0x1A, 0xF3, 0xF8, 0x53, 0x48, 0x43, 0x93, 0xAB, + 0x0B, 0xA2, 0x4B, 0x08, 0xB4, 0xE3, 0xFC, 0x4F, 0x94, 0xFB, 0x8C, 0xA3, + 0x1E, 0x49, 0xF3, 0x90, 0x31, 0xCE, 0x0E, 0x6C, 0x4E, 0x6C, 0x15, 0x74, + 0x57, 0x64, 0x35, 0xF6, 0x77, 0x83, 0x6E, 0xB2, 0x8F, 0xFE, 0xA2, 0x31, + 0x09, 0xBA, 0x77, 0xBB, 0xBF, 0x03, 0x4E, 0x8D, 0xC9, 0x7E, 0x25, 0xA2, + 0x80, 0xEA, 0xBA, 0xB2, 0x7C, 0x11, 0x93, 0xB3, 0x5C, 0xCC, 0x2C, 0xC3, + 0x24, 0xBC, 0x51, 0x4F, 0x08, 0xE7, 0x98, 0x87, 0x73, 0x64, 0x7D, 0xE9, + 0xA9, 0x18, 0x16, 0x9C, 0x48, 0x12, 0x6E, 0x18, 0x57, 0x20, 0xA9, 0xBD, + 0xD0, 0xEE, 0xDD, 0x22, 0xF9, 0xAC, 0xC7, 0x6C, 0xBA, 0x08, 0xAB, 0xA7, + 0xFB, 0x7F, 0x1B, 0x03, 0x24, 0x7F, 0xE4, 0x2E, 0x6C, 0x22, 0xBD, 0x5E, + 0x7A, 0x36, 0xD5, 0x41, 0x4D, 0x37, 0x4F, 0xDA, 0x86, 0xDA, 0x84, 0x72, + 0x45, 0x0D, 0x9E, 0xE4, 0x79, 0xA6, 0x8F, 0xF6, 0x01, 0x1A, 0x6E, 0xFE, + 0xC3, 0x3E, 0x9A, 0x02, 0xBD, 0x67, 0x91, 0x6B, 0xF0, 0x32, 0x81, 0xF7, + 0x7D, 0xED, 0x63, 0x0B, 0xA2, 0x18, 0x83, 0xE1, 0x7B, 0x08, 0x46, 0x6E, + 0xD4, 0x05, 0x77, 0xA1, 0xE1, 0xFF, 0xFE, 0x9D, 0x1A, 0x0E, 0x43, 0x6D, + 0x0E, 0xFE, 0x30, 0x75, 0x92, 0x91, 0x8A, 0xFD, 0xD5, 0x57, 0xAD, 0xD4, + 0x4E, 0x3A, 0x6B, 0xBF, 0x0F, 0x32, 0x2B, 0xAF, 0x65, 0x8E, 0xED, 0xC3, + 0xD0, 0xED, 0x9E, 0x9C, 0x16, 0xAF, 0x5B, 0x1D, 0xAB, 0xDD, 0x68, 0xA8, + 0x93, 0xFA, 0xA2, 0xC6, 0xE7, 0x67, 0x63, 0x27, 0xDB, 0x1C, 0x87, 0x2E, + 0x9A, 0xCE, 0x73, 0xB0, 0xF3, 0x85, 0xE1, 0x13, 0x4E, 0x62, 0xF0, 0x85, + 0xBD, 0x1D, 0x51, 0x9D, 0xD0, 0x09, 0x56, 0x47, 0x27, 0x04, 0x07, 0x3B, + 0xA8, 0x51, 0x26, 0x4F, 0xA0, 0xFB, 0xB2, 0x96, 0x96, 0xFF, 0xB3, 0xB9, + 0xC2, 0x24, 0xAD, 0x33, 0x68, 0xBF, 0x4D, 0xC7, 0x79, 0x2F, 0xDB, 0xF4, + 0x4E, 0x4D, 0x88, 0x0B, 0xF2, 0x46, 0x6A, 0x2F, 0x7A, 0xFD, 0x8C, 0x23, + 0x91, 0x07, 0x4B, 0x7A, 0x70, 0x59, 0x4C, 0x72, 0x7C, 0x94, 0x02, 0xC3, + 0xDB, 0x0F, 0xED, 0x83, 0x9D, 0x31, 0xDB, 0x38, 0x8E, 0xDF, 0x9A, 0x64, + 0x82, 0x22, 0x6E, 0x47, 0x6C, 0x98, 0x9E, 0x9C, 0xB3, 0x79, 0xE6, 0x11, + 0x19, 0x5E, 0x04, 0xE7, 0x0F, 0x03, 0x4E, 0xEC, 0x40, 0x86, 0x08, 0xF5, + 0xF5, 0x51, 0x9E, 0x1F, 0x08, 0xEE, 0x52, 0xB3, 0xBD, 0xD9, 0x3C, 0xAA, + 0x3C, 0xA2, 0x0A, 0x58, 0xB2, 0x75, 0xEF, 0xDB, 0x07, 0xCF, 0xE2, 0x68, + 0x3A, 0x58, 0x2A, 0xF1, 0xB6, 0xA8, 0xDB, 0x4C, 0xA7, 0x4F, 0xA8, 0xD3, + 0x6B, 0x97, 0xB1, 0xDC, 0xC9, 0x64, 0xAA, 0x4C, 0xF8, 0x44, 0x9C, 0x46, + 0x37, 0xB6, 0xC3, 0x26, 0x3C, 0x2A, 0x5D, 0x13, 0x70, 0xFD, 0x81, 0x0C, + 0x75, 0xA2, 0x91, 0x4E, 0x3A, 0x4E, 0xF1, 0xA0, 0x0C, 0xB2, 0x8D, 0xC9, + 0x81, 0x90, 0xEC, 0x3C, 0x2B, 0xD5, 0x79, 0x95, 0x8F, 0x66, 0xAC, 0x85, + 0x3D, 0xA1, 0x59, 0xDE, 0x86, 0x3A, 0x2F, 0xCE, 0x46, 0xB3, 0xF8, 0xDB, + 0x0E, 0xFE, 0x09, 0xD0, 0xA9, 0x68, 0x0D, 0xC0, 0x19, 0xB9, 0x2A, 0xB5, + 0x4A, 0xF6, 0x91, 0x7E, 0x04, 0x50, 0x72, 0x5D, 0x68, 0x70, 0x24, 0x47, + 0x49, 0xC9, 0x4E, 0xD6, 0xB1, 0xE3, 0x04, 0x09, 0x9A, 0xC2, 0x5A, 0x2C, + 0x36, 0x6D, 0xEA, 0x9A, 0x60, 0x16, 0x3E, 0x27, 0xC4, 0x1C, 0x39, 0x03, + 0xAB, 0x21, 0x5B, 0x0A, 0x5F, 0xBA, 0x9D, 0xDD, 0x80, 0xFC, 0x67, 0xFC, + 0xCE, 0x72, 0xCF, 0x42, 0xC9, 0x41, 0xA1, 0xF2, 0x4B, 0x77, 0x75, 0xFC, + 0x7A, 0xEA, 0x3C, 0xBF, 0x39, 0x8E, 0xB1, 0x4E, 0xCD, 0x34, 0x36, 0xF1, + 0x4D, 0xEE, 0x92, 0x55, 0xC8, 0xAA, 0xD6, 0x64, 0x60, 0x8B, 0x0B, 0xB5, + 0xBF, 0xBB, 0x76, 0x5D, 0xF2, 0x36, 0xC9, 0x88, 0x29, 0x89, 0x02, 0x1E, + 0xEB, 0xF6, 0x05, 0x63, 0x28, 0xED, 0x4B, 0x94, 0x49, 0xE8, 0x0D, 0xD9, + 0xAD, 0x1B, 0x26, 0x0A, 0x12, 0xDC, 0x37, 0xF8, 0xBB, 0x34, 0x16, 0xE4, + 0xF2, 0xD2, 0xFD, 0xAD, 0x59, 0x71, 0xD2, 0x02, 0xA5, 0x03, 0x80, 0x40, + 0x4B, 0xA3, 0xED, 0x89, 0x0F, 0x6A, 0x89, 0x22, 0x8B, 0x16, 0xB9, 0x24, + 0x0E, 0xAE, 0x3C, 0xDB, 0x4F, 0xB0, 0x37, 0x49, 0x73, 0xE3, 0x4F, 0x41, + 0xB5, 0x74, 0x40, 0xDD, 0xED, 0xC3, 0xA7, 0xE6, 0xB6, 0x75, 0x7A, 0xC8, + 0xDD, 0x60, 0x37, 0x75, 0x5D, 0x38, 0x6A, 0x8B, 0x64, 0x46, 0xF3, 0xAD, + 0x82, 0x0D, 0x50, 0x24, 0x63, 0x53, 0x3A, 0xDD, 0x53, 0x2D, 0x8E, 0x75, + 0xA2, 0x91, 0x6D, 0x45, 0x08, 0xE3, 0xE7, 0xE5, 0xEA, 0x39, 0x98, 0xEF, + 0x34, 0x44, 0x5F, 0x36, 0x7E, 0x4F, 0xE6, 0x07, 0xD4, 0x4A, 0x89, 0x98, + 0x19, 0xB8, 0x87, 0x3B, 0x52, 0x0C, 0x67, 0xD9, 0x0E, 0xC4, 0x6A, 0x47, + 0x25, 0xF9, 0xB4, 0x6E, 0x2E, 0x65, 0x06, 0x09, 0xB3, 0xA6, 0x80, 0x87, + 0x68, 0xCF, 0x80, 0x71, 0x27, 0x24, 0xAD, 0x26, 0xF5, 0xF9, 0x66, 0x2B, + 0x0D, 0x07, 0xC0, 0xDA, 0xE4, 0x85, 0xA6, 0xA6, 0x30, 0xBE, 0x7C, 0xA9, + 0xB7, 0x18, 0x41, 0x78, 0xFD, 0x54, 0xDF, 0xB8, 0xB7, 0x30, 0x2D, 0x43, + 0xB2, 0xEA, 0x90, 0x40, 0xB5, 0x40, 0x10, 0x12, 0x67, 0xDD, 0xA3, 0xDA, + 0x98, 0x3E, 0x74, 0xCE, 0xAC, 0x5E, 0x0C, 0xCF, 0x4E, 0x7A, 0x68, 0x75, + 0x7E, 0x3A, 0xB8, 0xAB, 0x78, 0x4D, 0x44, 0x78, 0x87, 0x63, 0x6D, 0x4E, + 0x53, 0xB4, 0x2B, 0x97, 0x42, 0xAE, 0xD5, 0x44, 0xDD, 0xB5, 0x74, 0x79, + 0xC9, 0x7E, 0x72, 0xA1, 0x6A, 0xC0, 0x36, 0xBA, 0xC2, 0x7A, 0xD8, 0xEA, + 0x12, 0xD0, 0x66, 0x14, 0x36, 0x82, 0xA7, 0xD8, 0x34, 0x1D, 0xB8, 0xD8, + 0xE2, 0x95, 0x48, 0x22, 0x90, 0x7E, 0xF6, 0x7E, 0x80, 0x51, 0xAF, 0x0C, + 0x65, 0xD6, 0xE1, 0x9F, 0xB2, 0x4D, 0xDB, 0xF6, 0x6C, 0xD2, 0x08, 0x13, + 0x47, 0xDF, 0x64, 0x14, 0xCA, 0xDE, 0x2C, 0x89, 0xBB, 0x9C, 0x07, 0xCE, + 0x99, 0xF7, 0x0E, 0x39, 0xCE, 0x9F, 0xF8, 0x9B, 0x87, 0xCA, 0xA2, 0xF0, + 0xDB, 0x91, 0x71, 0x1C, 0x32, 0x57, 0xAF, 0xEB, 0x2D, 0xB5, 0xA4, 0xF7, + 0x8C, 0xEB, 0xC7, 0xEA, 0xA9, 0xF2, 0x0E, 0x36, 0xD5, 0x57, 0x57, 0x79, + 0x30, 0x39, 0x8D, 0x60, 0x00, 0x94, 0xF3, 0x9E, 0xF0, 0x43, 0xD7, 0xE4, + 0x48, 0xE6, 0xF3, 0xC9, 0x89, 0x3B, 0x9E, 0x38, 0x59, 0x8F, 0xC8, 0x22, + 0x80, 0xBE, 0x9B, 0x4A, 0xDF, 0x52, 0xF5, 0x01, 0x50, 0x1B, 0x72, 0xE3, + 0xD2, 0xF7, 0x0B, 0x4C, 0xD7, 0x23, 0x62, 0x11, 0x9C, 0x74, 0x48, 0xF4, + 0xCD, 0xBA, 0xD4, 0x7C, 0x88, 0x9E, 0xF6, 0xFB, 0x34, 0xFA, 0x30, 0x73, + 0x35, 0x2D, 0xE3, 0x85, 0x09, 0x14, 0x7A, 0x14, 0x3E, 0xC2, 0xEF, 0xA2, + 0x89, 0x3D, 0x79, 0x01, 0x43, 0x4E, 0x1C, 0x84, 0x08, 0xB8, 0x65, 0xE6, + 0x21, 0x81, 0x33, 0xC3, 0x8A +}; \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Common/ShaderSerializer.h b/src/Cafe/HW/Latte/Common/ShaderSerializer.h new file mode 100644 index 00000000..e97a4835 --- /dev/null +++ b/src/Cafe/HW/Latte/Common/ShaderSerializer.h @@ -0,0 +1,8 @@ +#pragma once +#include "util/helpers/Serializer.h" + +namespace Latte +{ + void SerializeShaderProgram(void* shaderProg, uint32 size, MemStreamWriter& memWriter); + bool DeserializeShaderProgram(std::vector<uint8>& progData, MemStreamReader& memReader); +}; \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Core/FetchShader.cpp b/src/Cafe/HW/Latte/Core/FetchShader.cpp new file mode 100644 index 00000000..a029516c --- /dev/null +++ b/src/Cafe/HW/Latte/Core/FetchShader.cpp @@ -0,0 +1,540 @@ +#include "Cafe/HW/Latte/Core/LatteConst.h" +#include "Cafe/HW/Latte/Core/LatteShaderAssembly.h" +#include "Cafe/HW/Latte/ISA/RegDefines.h" +#include "Cafe/OS/libs/gx2/GX2.h" +#include "Cafe/HW/Latte/Core/Latte.h" +#include "Cafe/HW/Latte/Core/LatteDraw.h" +#include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h" +#include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInstructions.h" +#include "Cafe/HW/Latte/Core/FetchShader.h" +#include "Cafe/HW/Latte/ISA/LatteInstructions.h" +#include "util/containers/LookupTableL3.h" +#include "util/helpers/fspinlock.h" +#include <openssl/sha.h> + +uint32 LatteShaderRecompiler_getAttributeSize(LatteParsedFetchShaderAttribute_t* attrib) +{ + if (attrib->format == FMT_32_32_32_32 || attrib->format == FMT_32_32_32_32_FLOAT) + return 4 * 4; + else if (attrib->format == FMT_32_32_32 || attrib->format == FMT_32_32_32_FLOAT) + return 3 * 4; + else if (attrib->format == FMT_32_32 || attrib->format == FMT_32_32_FLOAT) + return 2 * 4; + else if (attrib->format == FMT_32 || attrib->format == FMT_32_FLOAT) + return 1 * 4; + else if (attrib->format == FMT_16_16_16_16 || attrib->format == FMT_16_16_16_16_FLOAT) + return 4 * 2; + else if (attrib->format == FMT_16_16 || attrib->format == FMT_16_16_FLOAT) + return 2 * 2; + else if (attrib->format == FMT_16 || attrib->format == FMT_16_FLOAT) + return 1 * 2; + else if (attrib->format == FMT_8_8_8_8) + return 4 * 1; + else if (attrib->format == FMT_8_8) + return 2 * 1; + else if (attrib->format == FMT_8) + return 1 * 1; + else if (attrib->format == FMT_2_10_10_10) + return 4; + else + cemu_assert_unimplemented(); + return 0; +} + +uint32 LatteShaderRecompiler_getAttributeAlignment(LatteParsedFetchShaderAttribute_t* attrib) +{ + if (attrib->format == FMT_32_32_32_32 || attrib->format == FMT_32_32_32_32_FLOAT) + return 4; + else if (attrib->format == FMT_32_32_32 || attrib->format == FMT_32_32_32_FLOAT) + return 4; + else if (attrib->format == FMT_32_32 || attrib->format == FMT_32_32_FLOAT) + return 4; + else if (attrib->format == FMT_32 || attrib->format == FMT_32_FLOAT) + return 4; + else if (attrib->format == FMT_16_16_16_16 || attrib->format == FMT_16_16_16_16_FLOAT) + return 2; + else if (attrib->format == FMT_16_16 || attrib->format == FMT_16_16_FLOAT) + return 2; + else if (attrib->format == FMT_16 || attrib->format == FMT_16_FLOAT) + return 2; + else if (attrib->format == FMT_8_8_8_8) + return 1; + else if (attrib->format == FMT_8_8) + return 1; + else if (attrib->format == FMT_8) + return 1; + else if (attrib->format == FMT_2_10_10_10) + return 4; + else + cemu_assert_unimplemented(); + return 4; +} + +void LatteShader_calculateFSKey(LatteFetchShader* fetchShader) +{ + uint64 key = 0; + for (sint32 g = 0; g < fetchShader->bufferGroups.size(); g++) + { + LatteParsedFetchShaderBufferGroup_t& group = fetchShader->bufferGroups[g]; + for (sint32 f = 0; f < group.attribCount; f++) + { + LatteParsedFetchShaderAttribute_t* attrib = group.attrib + f; + key += (uint64)attrib->endianSwap; + key = _rotl64(key, 3); + key += (uint64)attrib->nfa; + key = _rotl64(key, 3); + key += (uint64)(attrib->isSigned?1:0); + key = _rotl64(key, 1); + key += (uint64)attrib->format; + key = _rotl64(key, 7); + key += (uint64)attrib->fetchType; + key = _rotl64(key, 8); + key += (uint64)attrib->ds[0]; + key = _rotl64(key, 2); + key += (uint64)attrib->ds[1]; + key = _rotl64(key, 2); + key += (uint64)attrib->ds[2]; + key = _rotl64(key, 2); + key += (uint64)attrib->ds[3]; + key = _rotl64(key, 2); + key += (uint64)(attrib->aluDivisor+1); + key = _rotl64(key, 2); + key += (uint64)attrib->attributeBufferIndex; + key = _rotl64(key, 8); + key += (uint64)attrib->semanticId; + key = _rotl64(key, 8); + key += (uint64)(attrib->offset & 3); + key = _rotl64(key, 2); + } + } + // todo - also hash invalid buffer groups? + fetchShader->key = key; +} + +uint32 LatteParsedFetchShaderBufferGroup_t::getCurrentBufferStride(uint32* contextRegister) const +{ + uint32 bufferIndex = this->attributeBufferIndex; + uint32 bufferBaseRegisterIndex = mmSQ_VTX_ATTRIBUTE_BLOCK_START + bufferIndex * 7; + uint32 bufferStride = (contextRegister[bufferBaseRegisterIndex + 2] >> 11) & 0xFFFF; + return bufferStride; +} + +void LatteFetchShader::CalculateFetchShaderVkHash() +{ + // calculate SHA1 of all states that are part of the Vulkan graphics pipeline + SHA_CTX ctx; + SHA1_Init(&ctx); + for(auto& group : bufferGroups) + { + // offsets + for (sint32 t = 0; t < group.attribCount; t++) + { + uint32 offset = group.attrib[t].offset; + SHA1_Update(&ctx, &t, sizeof(t)); + SHA1_Update(&ctx, &offset, sizeof(offset)); + } + } + uint8 shaDigest[20]; + SHA1_Final(shaDigest, &ctx); + // fold SHA1 hash into a 64bit value + uint64 h = *(uint64*)(shaDigest + 0); + h += *(uint64*)(shaDigest + 8); + h += (uint64)*(uint32*)(shaDigest + 16); + this->vkPipelineHashFragment = h; +} + +void _fetchShaderDecompiler_parseInstruction_VTX_SEMANTIC(LatteFetchShader* parsedFetchShader, uint32* contextRegister, const LatteClauseInstruction_VTX* instr) +{ + uint32 semanticId = instr->getFieldSEM_SEMANTIC_ID(); // location (attribute index inside shader) + uint32 bufferId = instr->getField_BUFFER_ID(); // the index used for GX2SetAttribBuffer (+0xA0) + LatteConst::VertexFetchType2 fetchType = instr->getField_FETCH_TYPE(); + auto srcSelX = instr->getField_SRC_SEL_X(); + auto dsx = instr->getField_DST_SEL(0); + auto dsy = instr->getField_DST_SEL(1); + auto dsz = instr->getField_DST_SEL(2); + auto dsw = instr->getField_DST_SEL(3); + auto dataFormat = instr->getField_DATA_FORMAT(); + uint32 offset = instr->getField_OFFSET(); + auto nfa = instr->getField_NUM_FORMAT_ALL(); + bool isSigned = instr->getField_FORMAT_COMP_ALL() == LatteClauseInstruction_VTX::FORMAT_COMP::COMP_SIGNED; + auto endianSwap = instr->getField_ENDIAN_SWAP(); + + // get buffer + cemu_assert_debug(bufferId >= 0xA0 && bufferId < 0xB0); + uint32 bufferIndex = (bufferId - 0xA0); + + // get or add new attribute group (by buffer index) + LatteParsedFetchShaderBufferGroup_t* attribGroup = nullptr; + if (LatteFetchShader::isValidBufferIndex(bufferIndex)) + { + auto bufferGroupItr = std::find_if(parsedFetchShader->bufferGroups.begin(), parsedFetchShader->bufferGroups.end(), [bufferIndex](LatteParsedFetchShaderBufferGroup_t& bufferGroup) {return bufferGroup.attributeBufferIndex == bufferIndex; }); + if (bufferGroupItr != parsedFetchShader->bufferGroups.end()) + attribGroup = &(*bufferGroupItr); + } + else + { + auto bufferGroupItr = std::find_if(parsedFetchShader->bufferGroupsInvalid.begin(), parsedFetchShader->bufferGroupsInvalid.end(), [bufferIndex](LatteParsedFetchShaderBufferGroup_t& bufferGroup) {return bufferGroup.attributeBufferIndex == bufferIndex; }); + if (bufferGroupItr != parsedFetchShader->bufferGroupsInvalid.end()) + attribGroup = &(*bufferGroupItr); + } + // create new group if none found + if (attribGroup == nullptr) + { + if (LatteFetchShader::isValidBufferIndex(bufferIndex)) + attribGroup = &parsedFetchShader->bufferGroups.emplace_back(); + else + attribGroup = &parsedFetchShader->bufferGroupsInvalid.emplace_back(); + + attribGroup->attributeBufferIndex = bufferIndex; + attribGroup->minOffset = offset; + attribGroup->maxOffset = offset; + } + // add attribute + sint32 groupAttribIndex = attribGroup->attribCount; + if (attribGroup->attribCount < (groupAttribIndex + 1)) + { + attribGroup->attribCount = (groupAttribIndex + 1); + attribGroup->attrib = (LatteParsedFetchShaderAttribute_t*)realloc(attribGroup->attrib, sizeof(LatteParsedFetchShaderAttribute_t) * attribGroup->attribCount); + } + attribGroup->attrib[groupAttribIndex].semanticId = semanticId; + attribGroup->attrib[groupAttribIndex].format = (uint8)dataFormat; + attribGroup->attrib[groupAttribIndex].fetchType = fetchType; + attribGroup->attrib[groupAttribIndex].nfa = (uint8)nfa; + attribGroup->attrib[groupAttribIndex].isSigned = isSigned; + attribGroup->attrib[groupAttribIndex].offset = offset; + attribGroup->attrib[groupAttribIndex].ds[0] = (uint8)dsx; + attribGroup->attrib[groupAttribIndex].ds[1] = (uint8)dsy; + attribGroup->attrib[groupAttribIndex].ds[2] = (uint8)dsz; + attribGroup->attrib[groupAttribIndex].ds[3] = (uint8)dsw; + attribGroup->attrib[groupAttribIndex].attributeBufferIndex = bufferIndex; + attribGroup->attrib[groupAttribIndex].endianSwap = endianSwap; + attribGroup->minOffset = (std::min)(attribGroup->minOffset, offset); + attribGroup->maxOffset = (std::max)(attribGroup->maxOffset, offset); + // get alu divisor + if (srcSelX == LatteClauseInstruction_VTX::SRC_SEL::SEL_X) + { + cemu_assert_debug(fetchType != LatteConst::VertexFetchType2::INSTANCE_DATA); // aluDivisor 0 in combination with instanced data is not allowed? + attribGroup->attrib[groupAttribIndex].aluDivisor = -1; + } + else if (srcSelX == LatteClauseInstruction_VTX::SRC_SEL::SEL_W) + { + cemu_assert_debug(fetchType == LatteConst::VertexFetchType2::INSTANCE_DATA); // using constant divisor 1 with per-vertex data seems strange? (divisor is instance-only) + // aluDivisor is constant 1 + attribGroup->attrib[groupAttribIndex].aluDivisor = 1; + } + else if (srcSelX == LatteClauseInstruction_VTX::SRC_SEL::SEL_Y) + { + // use alu divisor 1 + attribGroup->attrib[groupAttribIndex].aluDivisor = (sint32)contextRegister[mmVGT_INSTANCE_STEP_RATE_0 + 0]; + cemu_assert_debug(attribGroup->attrib[groupAttribIndex].aluDivisor > 0); + } + else if (srcSelX == LatteClauseInstruction_VTX::SRC_SEL::SEL_Z) + { + // use alu divisor 2 + attribGroup->attrib[groupAttribIndex].aluDivisor = (sint32)contextRegister[mmVGT_INSTANCE_STEP_RATE_0 + 1]; + cemu_assert_debug(attribGroup->attrib[groupAttribIndex].aluDivisor > 0); + } +} + +void _fetchShaderDecompiler_parseVTXClause(LatteFetchShader* parsedFetchShader, uint32* contextRegister, std::span<uint8> clauseCode, size_t numInstructions) +{ + const LatteClauseInstruction_VTX* instr = (LatteClauseInstruction_VTX*)clauseCode.data(); + const LatteClauseInstruction_VTX* end = instr + numInstructions; + while (instr < end) + { + if (instr->getField_VTX_INST() == LatteClauseInstruction_VTX::VTX_INST::_VTX_INST_SEMANTIC) + { + _fetchShaderDecompiler_parseInstruction_VTX_SEMANTIC(parsedFetchShader, contextRegister, instr); + } + else + { + assert_dbg(); + } + instr++; + } +} + +void _fetchShaderDecompiler_parseCF(LatteFetchShader* parsedFetchShader, uint32* contextRegister, std::span<uint8> programCode) +{ + size_t maxCountCFInstructions = programCode.size_bytes() / sizeof(LatteCFInstruction); + const LatteCFInstruction* cfInstruction = (LatteCFInstruction*)programCode.data(); + const LatteCFInstruction* end = cfInstruction + maxCountCFInstructions; + while (cfInstruction < end) + { + if (cfInstruction->getField_Opcode() == LatteCFInstruction::INST_VTX_TC) + { + auto vtxInstruction = cfInstruction->getParserIfOpcodeMatch<LatteCFInstruction_DEFAULT>(); + cemu_assert_debug(vtxInstruction->getField_COND() == LatteCFInstruction::CF_COND::CF_COND_ACTIVE); + _fetchShaderDecompiler_parseVTXClause(parsedFetchShader, contextRegister, vtxInstruction->getClauseCode(programCode), vtxInstruction->getField_COUNT()); + } + else if (cfInstruction->getField_Opcode() == LatteCFInstruction::INST_RETURN) + { + cemu_assert_debug(!cfInstruction->getField_END_OF_PROGRAM()); + return; + } + else + { + cemu_assert_debug(false); // unhandled / unexpected CF instruction + } + if (cfInstruction->getField_END_OF_PROGRAM()) + { + cemu_assert_debug(false); // unusual for fetch shader? They should end with a return instruction + break; + } + cfInstruction++; + } + cemu_assert_debug(false); // program must be terminated with an instruction that has EOP set? +} + +// parse fetch shader and create LatteFetchShader object +// also registers the fs in the cache (s_fetchShaderByHash) +// can be assumed to be thread-safe, if called simultaneously on the same fetch shader only one shader will become registered. The others will be destroyed +LatteFetchShader* LatteShaderRecompiler_createFetchShader(LatteFetchShader::CacheHash fsHash, uint32* contextRegister, uint32* fsProgramCode, uint32 fsProgramSize) +{ + LatteFetchShader* newFetchShader = new LatteFetchShader(); + newFetchShader->m_cacheHash = fsHash; + if( (fsProgramSize&0xF) != 0 ) + debugBreakpoint(); + uint32 index = 0; + + // if the first instruction is a CF instruction then parse shader properly + // otherwise fall back to our broken legacy method (where we assumed fetch shaders had no CF program) + // this workaround is required to make sure old shader caches dont break + + // from old fetch shader gen (CF part missing): + // {0x0000a001, 0x27961000, 0x00020000, 0x00000000} + // {0x0000a001, 0x2c151002, 0x00020000, 0x00000000, 0x0000a001, 0x068d1000, 0x0000000c, ...} + // {0x0000a001, 0x2c151000, 0x00020000, 0x00000000} + // {0x0300aa21, 0x28cd1006, 0x00000000, 0x00000000, 0x0300ab21, 0x28cd1007, 0x00000000, ...} + + // shaders shipped with games (e.g. BotW): + // {0x00000002, 0x01800400, 0x00000000, 0x8a000000, 0x1c00a001, 0x280d1000, 0x00090000, ...} + // {0x00000002, 0x01800000, 0x00000000, 0x8a000000, 0x1c00a001, 0x27961000, 0x000a0000, ...} + // {0x00000002, 0x01800c00, 0x00000000, 0x8a000000, 0x2c00a001, 0x2c151000, 0x000a0000, ...} // size 0x50 + // {0x00000002, 0x01801000, 0x00000000, 0x8a000000, 0x1c00a001, 0x280d1000, 0x00090000, ...} // size 0x60 + // {0x00000002, 0x01801c00, 0x00000000, 0x8a000000, 0x1c00a001, 0x280d1000, 0x00090000, ...} // size 0x90 + + // our new implementation: + // {0x00000002, 0x01800400, 0x00000000, 0x8a000000, 0x0000a001, 0x2c151000, 0x00020000, ...} + + // for ALU instructions everything except the 01 is dynamic + newFetchShader->bufferGroups.reserve(16); + if (fsProgramSize == 0) + { + // empty fetch shader, seen in Minecraft + // these only make sense when vertex shader does not call FS? + LatteShader_calculateFSKey(newFetchShader); + newFetchShader->CalculateFetchShaderVkHash(); + return newFetchShader; + } + + if ((fsProgramCode[0] & 1) == 0 && fsProgramCode[0] <= 0x30 && (fsProgramCode[1]&~((3 << 10)| (1 << 19))) == 0x01800000) + { + // very likely a CF instruction + _fetchShaderDecompiler_parseCF(newFetchShader, contextRegister, { (uint8*)fsProgramCode, fsProgramSize }); + } + else + { + while (index < (fsProgramSize / 4)) + { + uint32 dword0 = fsProgramCode[index]; + uint32 opcode = dword0 & 0x1F; + index++; + if (opcode == VTX_INST_MEM) + { + // this might be the clause initialization instruction? (Seems to be the first instruction always) + // todo - upon further investigation, it seems like fetch shaders also start with a CF program. Our implementation doesnt emit one right now + uint32 opcode2 = (dword0 >> 8) & 7; + + index += 3; + } + else if (opcode == VTX_INST_SEMANTIC) + { + _fetchShaderDecompiler_parseInstruction_VTX_SEMANTIC(newFetchShader, contextRegister, (const LatteClauseInstruction_VTX*)(fsProgramCode + index - 1)); + index += 3; + } + } + } + newFetchShader->bufferGroups.shrink_to_fit(); + // calculate group information + // VBO offsets and stride + uint32 vboOffset = 0; + for (auto& bufferGroup : newFetchShader->bufferGroups) + { + for(sint32 i=0; i< bufferGroup.attribCount; i++) + { + uint32 attribSize = LatteShaderRecompiler_getAttributeSize(bufferGroup.attrib+i); + uint32 attribAlignment = LatteShaderRecompiler_getAttributeAlignment(bufferGroup.attrib+i); + // fix alignment + vboOffset = (vboOffset+attribAlignment-1)&~(attribAlignment-1); + vboOffset += attribSize; + // index type + if(bufferGroup.attrib[i].fetchType == LatteConst::VERTEX_DATA) + bufferGroup.hasVtxIndexAccess = true; + else if (bufferGroup.attrib[i].fetchType == LatteConst::INSTANCE_DATA) + bufferGroup.hasInstanceIndexAccess = true; + } + // fix alignment of whole vertex + if(bufferGroup.attribCount > 0 ) + { + uint32 attribAlignment = LatteShaderRecompiler_getAttributeAlignment(bufferGroup.attrib+0); + vboOffset = (vboOffset+attribAlignment-1)&~(attribAlignment-1); + } + bufferGroup.vboStride = vboOffset; + } + LatteShader_calculateFSKey(newFetchShader); + newFetchShader->CalculateFetchShaderVkHash(); + + // register in cache + // its possible that during multi-threaded shader cache loading, two identical (same hash) fetch shaders get created simultaneously + // we catch and handle this case here. RegisterInCache() is atomic and if another fetch shader is already registered, we abandon the local instance + LatteFetchShader* registeredFS = newFetchShader->RegisterInCache(fsHash); + if (registeredFS) + { + delete newFetchShader; + newFetchShader = registeredFS; + } + else + { + newFetchShader->m_isRegistered = true; + } + + + return newFetchShader; +} + +LatteFetchShader::~LatteFetchShader() +{ + UnregisterInCache(); +} + +struct FetchShaderLookupInfo +{ + LatteFetchShader* fetchShader; + uint32 programSize; + uint32 lastFrameAccessed; +}; + +LookupTableL3<8, 8, 8, FetchShaderLookupInfo*> g_fetchShaderLookupCache; + +LatteFetchShader::CacheHash LatteFetchShader::CalculateCacheHash(void* programCode, uint32 programSize) +{ + uint32* programCodeU32 = (uint32*)programCode; + uint64 progHash1 = 0; + uint64 progHash2 = 0; + for (uint32 i = 0; i < programSize / 4; i++) + { + uint32 temp = programCodeU32[i]; + progHash1 += (uint64)temp; + progHash2 ^= (uint64)temp; + progHash1 = (progHash1 << 3) | (progHash1 >> 61); + progHash2 = (progHash2 >> 7) | (progHash2 << 57); + } + + // todo - we should incorporate the value of VGT_INSTANCE_STEP_RATE_0/1 into the hash since it affects the generated LatteFetchShader object + // However, this would break compatibility with shader caches and gfx packs due to altering the shader base hashes + + return progHash1 + progHash2; +} + +LatteFetchShader* LatteFetchShader::FindInCacheByHash(LatteFetchShader::CacheHash fsHash) +{ + // does not hold s_fetchShaderCache for better performance. Be careful not to call this while another thread invokes RegisterInCache() + auto itr = s_fetchShaderByHash.find(fsHash); + if (itr == s_fetchShaderByHash.end()) + return nullptr; + return itr->second; +} + +void* _getFSProgramPtr() +{ + return memory_getPointerFromPhysicalOffset(LatteGPUState.contextRegister[mmSQ_PGM_START_FS + 0] << 8); +} + +uint32 _getFSProgramSize() +{ + return LatteGPUState.contextRegister[mmSQ_PGM_START_FS + 1] << 3; +} + +LatteFetchShader* LatteFetchShader::FindByGPUState() +{ + // retrieve fetch shader that matches the currently set GPU context registers + uint32 fsPhysAddr24 = LatteGPUState.contextRegister[mmSQ_PGM_START_FS + 0]; + cemu_assert_debug(fsPhysAddr24 < 0x1000000); // should only contain the upper 24 bit of the address in the lower 24 bit of the register + + FetchShaderLookupInfo* lookupInfo = g_fetchShaderLookupCache.lookup(fsPhysAddr24); + if (lookupInfo) + { + // return fetch shader if still the same + uint32 fsSize = _getFSProgramSize(); + uint32 framesSinceLastAccess = LatteGPUState.frameCounter - lookupInfo->lastFrameAccessed; + if (lookupInfo->programSize == fsSize && framesSinceLastAccess == 0) + { + lookupInfo->lastFrameAccessed = LatteGPUState.frameCounter; + return lookupInfo->fetchShader; + } + // update lookup info + CacheHash fsHash = CalculateCacheHash(_getFSProgramPtr(), _getFSProgramSize()); + LatteFetchShader* fetchShader = FindInCacheByHash(fsHash); + if (!fetchShader) + { + fetchShader = LatteShaderRecompiler_createFetchShader(fsHash, LatteGPUState.contextNew.GetRawView(), (uint32*)_getFSProgramPtr(), _getFSProgramSize()); + cemu_assert(fetchShader); + } + lookupInfo->fetchShader = fetchShader; + lookupInfo->programSize = fsSize; + lookupInfo->lastFrameAccessed = LatteGPUState.frameCounter; + return fetchShader; + } + else + { + // try to find fetch shader by data hash + CacheHash fsHash = CalculateCacheHash(_getFSProgramPtr(), _getFSProgramSize()); + LatteFetchShader* fetchShader = FindInCacheByHash(fsHash); + if (!fetchShader) + { + fetchShader = LatteShaderRecompiler_createFetchShader(fsHash, LatteGPUState.contextNew.GetRawView(), (uint32*)_getFSProgramPtr(), _getFSProgramSize()); + cemu_assert(fetchShader); + } + // create new lookup entry + lookupInfo = new FetchShaderLookupInfo(); + lookupInfo->fetchShader = fetchShader; + lookupInfo->programSize = _getFSProgramSize(); + lookupInfo->lastFrameAccessed = LatteGPUState.frameCounter; + g_fetchShaderLookupCache.store(fsPhysAddr24, lookupInfo); +#ifndef PUBLIC_RELEASE + cemu_assert_debug(g_fetchShaderLookupCache.lookup(fsPhysAddr24) == lookupInfo); +#endif + } + return lookupInfo->fetchShader; +} + +FSpinlock s_spinlockFetchShaderCache; + +LatteFetchShader* LatteFetchShader::RegisterInCache(CacheHash fsHash) +{ + s_spinlockFetchShaderCache.acquire(); + auto itr = s_fetchShaderByHash.find(fsHash); + if (itr != s_fetchShaderByHash.end()) + { + LatteFetchShader* fs = itr->second; + s_spinlockFetchShaderCache.release(); + return fs; + } + s_fetchShaderByHash.emplace(fsHash, this); + s_spinlockFetchShaderCache.release(); + return nullptr; +} + +void LatteFetchShader::UnregisterInCache() +{ + if (!m_isRegistered) + return; + s_spinlockFetchShaderCache.acquire(); + auto itr = s_fetchShaderByHash.find(m_cacheHash); + cemu_assert(itr == s_fetchShaderByHash.end()); + s_fetchShaderByHash.erase(itr); + s_spinlockFetchShaderCache.release(); +} + +std::unordered_map<LatteFetchShader::CacheHash, LatteFetchShader*> LatteFetchShader::s_fetchShaderByHash; diff --git a/src/Cafe/HW/Latte/Core/FetchShader.h b/src/Cafe/HW/Latte/Core/FetchShader.h new file mode 100644 index 00000000..ac57714d --- /dev/null +++ b/src/Cafe/HW/Latte/Core/FetchShader.h @@ -0,0 +1,72 @@ +#pragma once +#include "Cafe/HW/Latte/Core/LatteConst.h" + +struct LatteParsedFetchShaderAttribute_t +{ + uint8 semanticId; + uint8 format; + LatteConst::VertexFetchType2 fetchType; + uint8 nfa; + uint8 isSigned; + LatteConst::VertexFetchEndianMode endianSwap; + uint8 ds[4]; // destination component select + sint32 aluDivisor; + uint32 offset; + uint32 attributeBufferIndex; +}; + +struct LatteParsedFetchShaderBufferGroup_t +{ + uint32 attributeBufferIndex{}; // index of buffer (0 to 15 are valid) + LatteParsedFetchShaderAttribute_t* attrib{}; // attributes for this buffer + sint32 attribCount{}; + // offset range of attributes + uint32 minOffset{}; + uint32 maxOffset{}; + // output + uint32 vboStride{}; + // calculated info + bool hasVtxIndexAccess{}; + bool hasInstanceIndexAccess{}; + + uint32 getCurrentBufferStride(uint32* contextRegister) const; +}; + +struct LatteFetchShader +{ + using CacheHash = uint64; + + ~LatteFetchShader(); + + std::vector<LatteParsedFetchShaderBufferGroup_t> bufferGroups; + std::vector<LatteParsedFetchShaderBufferGroup_t> bufferGroupsInvalid; // groups with buffer index not being a valid buffer (dst components of these can affect shader code, but no actual vertex imports are done) + + uint64 key{}; + + // Vulkan + uint64 vkPipelineHashFragment{}; // hash of all fetch shader state that influences the Vulkan graphics pipeline + + // cache info + CacheHash m_cacheHash{}; + bool m_isRegistered{}; // if true, fetch shader is referenced by cache (RegisterInCache() succeeded) + + + void CalculateFetchShaderVkHash(); + + uint64 getVkPipelineHashFragment() const { return vkPipelineHashFragment; }; + + static bool isValidBufferIndex(const uint32 index) { return index < 0x10; }; + + // cache + LatteFetchShader* RegisterInCache(CacheHash fsHash); // Fails if another fetch shader object is already registered with the same fsHash. Returns the previously registered fetch shader or null + void UnregisterInCache(); + + // fetch shader cache (move these to separate Cache class?) + static CacheHash CalculateCacheHash(void* programCode, uint32 programSize); + static LatteFetchShader* FindInCacheByHash(CacheHash fsHash); + static LatteFetchShader* FindByGPUState(); + + static std::unordered_map<CacheHash, LatteFetchShader*> s_fetchShaderByHash; +}; + +LatteFetchShader* LatteShaderRecompiler_createFetchShader(LatteFetchShader::CacheHash fsHash, uint32* contextRegister, uint32* fsProgramCode, uint32 fsProgramSize); \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Core/Latte.h b/src/Cafe/HW/Latte/Core/Latte.h new file mode 100644 index 00000000..ddff15ea --- /dev/null +++ b/src/Cafe/HW/Latte/Core/Latte.h @@ -0,0 +1,175 @@ +#pragma once +#include "Cafe/HW/Latte/Core/LatteConst.h" +#include "Cafe/HW/Latte/ISA/LatteReg.h" +#include "util/VirtualHeap/VirtualHeap.h" + +struct LatteTextureDefinition; +class LatteTexture; +class LatteTextureView; + +struct gx2GPUSharedArea_t +{ + volatile uint32 flipRequestCountBE; // counts how many buffer swaps were requested + volatile uint32 flipExecuteCountBE; // counts how many buffer swaps were executed + volatile uint32 swapInterval; // vsync swap interval (0 means vsync is deactivated) +}; + +struct LatteGPUState_t +{ + union + { + uint32 contextRegister[LATTE_MAX_REGISTER]; + LatteContextRegister contextNew; + }; + MPTR contextRegisterShadowAddr[LATTE_MAX_REGISTER]; + // context control + uint32 contextControl0; + uint32 contextControl1; + // draw context + struct + { + uint32 numInstances; + }drawContext; + // stats + uint32 frameCounter; + uint32 flipCounter; // increased by one everytime a vsync + flip happens + uint32 currentDrawCallTick; // set to current time at the beginning of a drawcall + uint32 drawCallCounter; // increased after every drawcall + uint32 textureBindCounter; // increased at the beginning of _updateTextures() + std::atomic<uint64> flipRequestCount; + // timer & vsync + uint64 timer_frequency; // contains frequency of HPC + uint64 timer_bootUp; // contains the timestamp of when the GPU thread timer was initialized + uint64 timer_nextVSync; + // shared + gx2GPUSharedArea_t* sharedArea; // quick reference to shared area + MPTR sharedAreaAddr; + // other + // todo: Currently we have the command buffer logic implemented as a FIFO ringbuffer. On real HW it's handled as a series of command buffers that are pushed individually. + std::atomic<uint64> lastSubmittedCommandBufferTimestamp; + uint32 gx2InitCalled; // incremented every time GX2Init() is called + // OpenGL control + uint32 glVendor; // GLVENDOR_* + // temporary (replace with proper solution later) + bool tvBufferUsesSRGB; + bool drcBufferUsesSRGB; + // draw state + bool activeShaderHasError; // if try, at least one currently bound shader stage has an error and cannot be used for drawing + bool repeatTextureInitialization; // if set during rendertarget or texture initialization, repeat the process (textures likely have been invalidated) + bool requiresTextureBarrier; // set if glTextureBarrier should be called + // OSScreen + struct + { + struct + { + bool isEnabled; + MPTR physPtr; + volatile uint32 flipRequestCount; + volatile uint32 flipExecuteCount; + }screen[2]; + }osScreen; +}; + +extern LatteGPUState_t LatteGPUState; + +extern uint8* gxRingBufferReadPtr; // currently active read pointer (gx2 ring buffer or display list) + +// texture + +#include "Cafe/HW/Latte/Core/LatteTexture.h" + +// texture loader + +void LatteTextureLoader_estimateAccessedDataRange(LatteTexture* texture, sint32 sliceIndex, sint32 mipIndex, uint32& addrStart, uint32& addrEnd); + +// render target +void LatteRenderTarget_updateScissorBox(); + +void LatteRenderTarget_trackUpdates(); + +void LatteRenderTarget_getScreenImageArea(sint32* x, sint32* y, sint32* width, sint32* height, sint32* fullWidth, sint32* fullHeight, bool padView = false); +void LatteRenderTarget_copyToBackbuffer(LatteTextureView* textureView, bool isPadView); + +void LatteRenderTarget_GetCurrentVirtualViewportSize(sint32* viewportWidth, sint32* viewportHeight); + +void LatteRenderTarget_itHLESwapScanBuffer(); +void LatteRenderTarget_itHLEClearColorDepthStencil(uint32 clearMask, MPTR colorBufferMPTR, MPTR colorBufferFormat, Latte::E_HWTILEMODE colorBufferTilemode, uint32 colorBufferWidth, uint32 colorBufferHeight, uint32 colorBufferPitch, uint32 colorBufferViewFirstSlice, uint32 colorBufferViewNumSlice, MPTR depthBufferMPTR, MPTR depthBufferFormat, Latte::E_HWTILEMODE depthBufferTileMode, sint32 depthBufferWidth, sint32 depthBufferHeight, sint32 depthBufferPitch, sint32 depthBufferViewFirstSlice, sint32 depthBufferViewNumSlice, float r, float g, float b, float a, float clearDepth, uint32 clearStencil); +void LatteRenderTarget_itHLECopyColorBufferToScanBuffer(MPTR colorBufferPtr, uint32 colorBufferWidth, uint32 colorBufferHeight, uint32 colorBufferSliceIndex, uint32 colorBufferFormat, uint32 colorBufferPitch, Latte::E_HWTILEMODE colorBufferTilemode, uint32 colorBufferSwizzle, uint32 renderTarget); + +void LatteRenderTarget_unloadAll(); + +// surface copy + +void LatteSurfaceCopy_copySurfaceNew(MPTR srcPhysAddr, MPTR srcMipAddr, uint32 srcSwizzle, Latte::E_GX2SURFFMT srcSurfaceFormat, sint32 srcWidth, sint32 srcHeight, sint32 srcDepth, uint32 srcPitch, sint32 srcSlice, Latte::E_DIM srcDim, Latte::E_HWTILEMODE srcTilemode, sint32 srcAA, sint32 srcLevel, MPTR dstPhysAddr, MPTR dstMipAddr, uint32 dstSwizzle, Latte::E_GX2SURFFMT dstSurfaceFormat, sint32 dstWidth, sint32 dstHeight, sint32 dstDepth, uint32 dstPitch, sint32 dstSlice, Latte::E_DIM dstDim, Latte::E_HWTILEMODE dstTilemode, sint32 dstAA, sint32 dstLevel); + +// texture cache + +void LatteTC_Init(); + +void LatteTC_RegisterTexture(LatteTexture* tex); +void LatteTC_UnregisterTexture(LatteTexture* tex); + +uint32 LatteTexture_CalculateTextureDataHash(LatteTexture* hostTexture); +void LatteTexture_ReloadData(LatteTexture* hostTexture, uint32 textureUnit); + +bool LatteTC_HasTextureChanged(LatteTexture* hostTexture, bool force = false); +void LatteTC_ResetTextureChangeTracker(LatteTexture* hostTexture, bool force = false); + +void LatteTC_MarkTextureStillInUse(LatteTexture* texture); // lets the texture garbage collector know the texture is still in use at the time of this function call +void LatteTC_CleanupUnusedTextures(); + +std::vector<LatteTexture*> LatteTC_GetDeleteableTextures(); + +void LatteTC_UnloadAllTextures(); + +// texture readback + +void LatteTextureReadback_Initate(LatteTextureView* textureView); +void LatteTextureReadback_StartTransfer(LatteTextureView* textureView); +bool LatteTextureReadback_Update(bool forceStart = false); +void LatteTextureReadback_NotifyTextureDeletion(LatteTexture* texture); +void LatteTextureReadback_UpdateFinishedTransfers(bool forceFinish); + +// query + +void LatteQuery_Init(); +void LatteQuery_BeginOcclusionQuery(MPTR queryMPTR); +void LatteQuery_EndOcclusionQuery(MPTR queryMPTR); +void LatteQuery_UpdateFinishedQueries(); +void LatteQuery_UpdateFinishedQueriesForceFinishAll(); +void LatteQuery_CancelActiveGPU7Queries(); + +// streamout + +void LatteStreamout_InitCache(); +sint32 LatteStreamout_GetRingBufferSize(); +void LatteStreamout_PrepareDrawcall(uint32 count, uint32 instanceCount); +void LatteStreamout_FinishDrawcall(bool useDirectMemoryMode); + +// timing + +void LatteTiming_Init(); +void LatteTiming_HandleTimedVsync(); + +// command processor + +void LatteCP_ProcessRingbuffer(); + +// buffer cache + +bool LatteBufferCache_Sync(uint32 minIndex, uint32 maxIndex, uint32 baseInstance, uint32 instanceCount); +void LatteBufferCache_LoadRemappedUniforms(struct LatteDecompilerShader* shader, float* uniformData); + +void LatteRenderTarget_updateViewport(); + +#define LATTE_GLSL_DYNAMIC_UNIFORM_BLOCK_SIZE (1024) // maximum size for uniform blocks (in vec4s). On Nvidia hardware 4096 is the maximum (64K / 16 = 4096) all other vendors have much higher limits + +//static uint32 glTempError; +//#define catchOpenGLError() glFinish(); if( (glTempError = glGetError()) != 0 ) { printf("OpenGL error 0x%x: %s : %d timestamp %08x\n", glTempError, __FILE__, __LINE__, GetTickCount()); __debugbreak(); } + +#define catchOpenGLError() + +// Latte emulation control +void Latte_Start(); +void Latte_Stop(); +bool Latte_IsActive(); \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Core/LatteAsyncCommands.cpp b/src/Cafe/HW/Latte/Core/LatteAsyncCommands.cpp new file mode 100644 index 00000000..a4ffa1c2 --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteAsyncCommands.cpp @@ -0,0 +1,138 @@ +#include "Cafe/HW/Latte/Core/Latte.h" +#include "Cafe/HW/Latte/Core/LatteAsyncCommands.h" +#include "Cafe/HW/Latte/Core/LatteShader.h" +#include "Cafe/HW/Latte/Core/LatteTexture.h" + +void LatteThread_Exit(); + +SlimRWLock swl_gpuAsyncCommands; + +typedef struct +{ + uint32 type; + union + { + struct + { + MPTR physAddr; + MPTR mipAddr; + uint32 swizzle; + sint32 format; + sint32 width; + sint32 height; + sint32 depth; + uint32 pitch; + uint32 slice; + sint32 dim; + Latte::E_HWTILEMODE tilemode; + sint32 aa; + sint32 level; + }forceTextureReadback; + + struct + { + uint64 shaderBaseHash; + uint64 shaderAuxHash; + LatteConst::ShaderType shaderType; + }deleteShader; + }; +}LatteAsyncCommand_t; + +#define ASYNC_CMD_FORCE_TEXTURE_READBACK 1 +#define ASYNC_CMD_DELETE_SHADER 2 + +std::queue<LatteAsyncCommand_t> LatteAsyncCommandQueue; + +void LatteAsyncCommands_queueForceTextureReadback(MPTR physAddr, MPTR mipAddr, uint32 swizzle, sint32 format, sint32 width, sint32 height, sint32 depth, uint32 pitch, uint32 slice, sint32 dim, Latte::E_HWTILEMODE tilemode, sint32 aa, sint32 level) +{ + LatteAsyncCommand_t asyncCommand = {}; + // setup command + asyncCommand.type = ASYNC_CMD_FORCE_TEXTURE_READBACK; + + asyncCommand.forceTextureReadback.physAddr = physAddr; + asyncCommand.forceTextureReadback.mipAddr = mipAddr; + asyncCommand.forceTextureReadback.swizzle = swizzle; + asyncCommand.forceTextureReadback.format = format; + asyncCommand.forceTextureReadback.width = width; + asyncCommand.forceTextureReadback.height = height; + asyncCommand.forceTextureReadback.depth = depth; + asyncCommand.forceTextureReadback.pitch = pitch; + asyncCommand.forceTextureReadback.slice = slice; + asyncCommand.forceTextureReadback.dim = dim; + asyncCommand.forceTextureReadback.tilemode = tilemode; + asyncCommand.forceTextureReadback.aa = aa; + asyncCommand.forceTextureReadback.level = level; + swl_gpuAsyncCommands.LockWrite(); + LatteAsyncCommandQueue.push(asyncCommand); + swl_gpuAsyncCommands.UnlockWrite(); +} + +void LatteAsyncCommands_queueDeleteShader(uint64 shaderBaseHash, uint64 shaderAuxHash, LatteConst::ShaderType shaderType) +{ + LatteAsyncCommand_t asyncCommand = {}; + // setup command + asyncCommand.type = ASYNC_CMD_DELETE_SHADER; + + asyncCommand.deleteShader.shaderBaseHash = shaderBaseHash; + asyncCommand.deleteShader.shaderAuxHash = shaderAuxHash; + asyncCommand.deleteShader.shaderType = shaderType; + + swl_gpuAsyncCommands.LockWrite(); + LatteAsyncCommandQueue.push(asyncCommand); + swl_gpuAsyncCommands.UnlockWrite(); +} + +void LatteAsyncCommands_waitUntilAllProcessed() +{ + while (LatteAsyncCommandQueue.empty() == false) + { + _mm_pause(); + } +} + +/* + * Called by the GPU command processor frequently + */ +void LatteAsyncCommands_checkAndExecute() +{ + // quick check if queue is empty (requires no lock) + if (!Latte_IsActive()) + LatteThread_Exit(); + if (LatteAsyncCommandQueue.empty()) + return; + swl_gpuAsyncCommands.LockWrite(); + while (LatteAsyncCommandQueue.empty() == false) + { + // get first command in queue + LatteAsyncCommand_t asyncCommand = LatteAsyncCommandQueue.front(); + swl_gpuAsyncCommands.UnlockWrite(); + if (asyncCommand.type == ASYNC_CMD_FORCE_TEXTURE_READBACK) + { + cemu_assert_debug(asyncCommand.forceTextureReadback.level == 0); // implement mip swizzle and verify + LatteTextureView* textureView = LatteTC_GetTextureSliceViewOrTryCreate(asyncCommand.forceTextureReadback.physAddr, asyncCommand.forceTextureReadback.mipAddr, (Latte::E_GX2SURFFMT)asyncCommand.forceTextureReadback.format, asyncCommand.forceTextureReadback.tilemode, asyncCommand.forceTextureReadback.width, asyncCommand.forceTextureReadback.height, asyncCommand.forceTextureReadback.depth, asyncCommand.forceTextureReadback.pitch, 0, asyncCommand.forceTextureReadback.slice, asyncCommand.forceTextureReadback.level); + if (textureView != nullptr) + { + LatteTexture_UpdateDataToLatest(textureView->baseTexture); + // start transfer + LatteTextureReadback_StartTransfer(textureView); + // wait until finished + LatteTextureReadback_UpdateFinishedTransfers(true); + } + else + { + forceLogDebug_printf("Texture not found for readback"); + } + } + else if (asyncCommand.type == ASYNC_CMD_DELETE_SHADER) + { + LatteSHRC_RemoveFromCacheByHash(asyncCommand.deleteShader.shaderBaseHash, asyncCommand.deleteShader.shaderAuxHash, asyncCommand.deleteShader.shaderType); + } + else + { + cemu_assert_unimplemented(); + } + swl_gpuAsyncCommands.LockWrite(); + LatteAsyncCommandQueue.pop(); + } + swl_gpuAsyncCommands.UnlockWrite(); +} \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Core/LatteAsyncCommands.h b/src/Cafe/HW/Latte/Core/LatteAsyncCommands.h new file mode 100644 index 00000000..f3ba80cf --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteAsyncCommands.h @@ -0,0 +1,7 @@ +#pragma once + +void LatteAsyncCommands_queueForceTextureReadback(MPTR physAddr, MPTR mipAddr, uint32 swizzle, sint32 format, sint32 width, sint32 height, sint32 depth, uint32 pitch, uint32 slice, sint32 dim, Latte::E_HWTILEMODE tilemode, sint32 aa, sint32 level); +void LatteAsyncCommands_queueDeleteShader(uint64 shaderBaseHash, uint64 shaderAuxHash, LatteConst::ShaderType shaderType); +void LatteAsyncCommands_waitUntilAllProcessed(); + +void LatteAsyncCommands_checkAndExecute(); \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Core/LatteBufferCache.cpp b/src/Cafe/HW/Latte/Core/LatteBufferCache.cpp new file mode 100644 index 00000000..93efdd62 --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteBufferCache.cpp @@ -0,0 +1,1103 @@ +#include "Cafe/HW/Latte/Renderer/Renderer.h" +#include "util/ChunkedHeap/ChunkedHeap.h" +#include "util/helpers/fspinlock.h" +#include "config/ActiveSettings.h" + +#define CACHE_PAGE_SIZE 0x400 +#define CACHE_PAGE_SIZE_M1 (CACHE_PAGE_SIZE-1) + +uint32 g_currentCacheChronon = 0; + +template<typename TRangeData, typename TNodeObject> +class IntervalTree2 +{ + // TNodeObject will be interfaced with via callbacks to static methods + + // static TNodeObject* Create(TRangeData rangeBegin, TRangeData rangeEnd, std::span<TNodeObject*> overlappingObjects) + // Create a new node with the given range. overlappingObjects contains all the nodes that are replaced by this operation. The callee has to delete all objects in overlappingObjects (Delete callback wont be invoked) + + // static void Delete(TNodeObject* nodeObject) + // Delete a node object. Replacement operations won't trigger this callback and instead pass the objects to Create() + + // static void Resize(TNodeObject* nodeObject, TRangeData rangeBegin, TRangeData rangeEnd) + // Shrink or extend an existing range + + // static TNodeObject* Split(TNodeObject* nodeObject, TRangeData firstRangeBegin, TRangeData firstRangeEnd, TRangeData secondRangeBegin, TRangeData secondRangeEnd) + // Cut a hole into an existing range and split it in two. Should return the newly created node object after the hole + + static_assert(std::is_pointer<TNodeObject>::value == false, "TNodeObject must be a non-pointer type"); + + struct InternalRange + { + InternalRange() {}; + InternalRange(TRangeData _rangeBegin, TRangeData _rangeEnd) : rangeBegin(_rangeBegin), rangeEnd(_rangeEnd) { cemu_assert_debug(_rangeBegin < _rangeEnd); }; + + TRangeData rangeBegin; + TRangeData rangeEnd; + + bool operator<(const InternalRange& rhs) const + { + // use <= instead of < because ranges are allowed to touch (e.g. 10-20 and 20-30 dont get merged) + return this->rangeEnd <= rhs.rangeBegin; + } + + }; + + std::map<InternalRange, TNodeObject*> m_map; + std::vector<TNodeObject*> m_tempObjectArray; + +public: + TNodeObject* getRange(TRangeData rangeBegin, TRangeData rangeEnd) + { + auto itr = m_map.find(InternalRange(rangeBegin, rangeEnd)); + if (itr == m_map.cend()) + return nullptr; + if (rangeBegin < (*itr).first.rangeBegin) + return nullptr; + if (rangeEnd > (*itr).first.rangeEnd) + return nullptr; + return (*itr).second; + } + + TNodeObject* getRangeByPoint(TRangeData rangeOffset) + { + auto itr = m_map.find(InternalRange(rangeOffset, rangeOffset+1)); // todo - better to use custom comparator instead of +1? + if (itr == m_map.cend()) + return nullptr; + cemu_assert_debug(rangeOffset >= (*itr).first.rangeBegin); + cemu_assert_debug(rangeOffset < (*itr).first.rangeEnd); + return (*itr).second; + } + + void addRange(TRangeData rangeBegin, TRangeData rangeEnd) + { + if (rangeEnd == rangeBegin) + return; + InternalRange range(rangeBegin, rangeEnd); + auto itr = m_map.find(range); + if (itr == m_map.cend()) + { + // new entry + m_map.emplace(range, TNodeObject::Create(rangeBegin, rangeEnd, std::span<TNodeObject*>())); + } + else + { + // overlap detected + if (rangeBegin >= (*itr).first.rangeBegin && rangeEnd <= (*itr).first.rangeEnd) + return; // do nothing if added range is already covered + rangeBegin = (std::min)(rangeBegin, (*itr).first.rangeBegin); + // DEBUG - make sure this is the start point of the merge process (the first entry that starts below minValue) +#ifndef PUBLIC_RELEASE + if (itr != m_map.cbegin()) + { + // check previous result + auto itrCopy = itr; + --itrCopy; + if ((*itrCopy).first.rangeEnd > rangeBegin) + { + assert_dbg(); // n-1 entry is also overlapping + rangeBegin = (std::min)(rangeBegin, (*itrCopy).first.rangeBegin); + } + } +#endif + // DEBUG - END + // collect and remove all overlapping ranges + size_t count = 0; + while (itr != m_map.cend() && (*itr).first.rangeBegin < rangeEnd) + { + rangeEnd = (std::max)(rangeEnd, (*itr).first.rangeEnd); + if (m_tempObjectArray.size() <= count) + m_tempObjectArray.resize(count + 8); + m_tempObjectArray[count] = (*itr).second; + count++; + auto tempItr = itr; + ++itr; + m_map.erase(tempItr); + } + + // create callback + TNodeObject* newObject = TNodeObject::Create(rangeBegin, rangeEnd, std::span<TNodeObject*>(m_tempObjectArray.data(), count)); + m_map.emplace(InternalRange(rangeBegin, rangeEnd), newObject); + } + } + + void removeRange(TRangeData rangeBegin, TRangeData rangeEnd) + { + InternalRange range(rangeBegin, rangeEnd); + auto itr = m_map.find(range); + if (itr == m_map.cend()) + return; + cemu_assert_debug(itr == m_map.lower_bound(range)); + while (itr != m_map.cend() && (*itr).first.rangeBegin < rangeEnd) + { + if ((*itr).first.rangeBegin >= rangeBegin && (*itr).first.rangeEnd <= rangeEnd) + { + // delete entire range + auto itrCopy = itr; + TNodeObject* t = (*itr).second; + ++itr; + m_map.erase(itrCopy); + TNodeObject::Delete(t); + continue; + } + if (rangeBegin > (*itr).first.rangeBegin && rangeEnd < (*itr).first.rangeEnd) + { + // cut hole into existing range + TRangeData firstRangeBegin = (*itr).first.rangeBegin; + TRangeData firstRangeEnd = rangeBegin; + TRangeData secondRangeBegin = rangeEnd; + TRangeData secondRangeEnd = (*itr).first.rangeEnd; + TNodeObject* newObject = TNodeObject::Split((*itr).second, firstRangeBegin, firstRangeEnd, secondRangeBegin, secondRangeEnd); + // modify key + auto nh = m_map.extract(itr); + nh.key().rangeBegin = firstRangeBegin; + nh.key().rangeEnd = firstRangeEnd; + m_map.insert(std::move(nh)); + // insert new object after hole + m_map.emplace(InternalRange(secondRangeBegin, secondRangeEnd), newObject); + return; // done + } + // shrink (trim either beginning or end) + TRangeData newRangeBegin; + TRangeData newRangeEnd; + if ((rangeBegin <= (*itr).first.rangeBegin && rangeEnd < (*itr).first.rangeEnd)) + { + // trim from beginning + newRangeBegin = (std::max)((*itr).first.rangeBegin, rangeEnd); + newRangeEnd = (*itr).first.rangeEnd; + } + else if ((rangeBegin > (*itr).first.rangeBegin && rangeEnd >= (*itr).first.rangeEnd)) + { + // trim from end + newRangeBegin = (*itr).first.rangeBegin; + newRangeEnd = (std::min)((*itr).first.rangeEnd, rangeBegin); + } + else + { + assert_dbg(); // should not happen + } + TNodeObject::Resize((*itr).second, newRangeBegin, newRangeEnd); + // modify key and increment iterator + auto itrCopy = itr; + ++itr; + auto nh = m_map.extract(itrCopy); + nh.key().rangeBegin = newRangeBegin; + nh.key().rangeEnd = newRangeEnd; + m_map.insert(std::move(nh)); + } + } + + // remove existing range that matches given begin and end + void removeRangeSingle(TRangeData rangeBegin, TRangeData rangeEnd) + { + InternalRange range(rangeBegin, rangeEnd); + auto itr = m_map.find(range); + cemu_assert_debug(itr != m_map.cend()); + if (itr == m_map.cend()) + return; + cemu_assert_debug((*itr).first.rangeBegin == rangeBegin && (*itr).first.rangeEnd == rangeEnd); + // delete entire range + TNodeObject* t = (*itr).second; + m_map.erase(itr); + TNodeObject::Delete(t); + } + + // remove existing range that matches given begin and end without calling delete callback + void removeRangeSingleWithoutCallback(TRangeData rangeBegin, TRangeData rangeEnd) + { + InternalRange range(rangeBegin, rangeEnd); + auto itr = m_map.find(range); + cemu_assert_debug(itr != m_map.cend()); + if (itr == m_map.cend()) + return; + cemu_assert_debug((*itr).first.rangeBegin == rangeBegin && (*itr).first.rangeEnd == rangeEnd); + // delete entire range + TNodeObject* t = (*itr).second; + m_map.erase(itr); + } + + void splitRange(TRangeData rangeOffset) + { + // not well tested + removeRange(rangeOffset, rangeOffset+1); + } + + template<typename TFunc> + void forEachOverlapping(TRangeData rangeBegin, TRangeData rangeEnd, TFunc f) + { + InternalRange range(rangeBegin, rangeEnd); + auto itr = m_map.find(range); + if (itr == m_map.cend()) + return; + cemu_assert_debug(itr == m_map.lower_bound(range)); + while (itr != m_map.cend() && (*itr).first.rangeBegin < rangeEnd) + { + f((*itr).second, rangeBegin, rangeEnd); + ++itr; + } + } + + void validate() + { + if (m_map.empty()) + return; + auto itr = m_map.begin(); + if ((*itr).first.rangeBegin > (*itr).first.rangeEnd) + assert_dbg(); + TRangeData currentLoc = (*itr).first.rangeEnd; + ++itr; + while (itr != m_map.end()) + { + if ((*itr).first.rangeBegin >= (*itr).first.rangeEnd) + assert_dbg(); // negative or zero size ranges are not allowed + if (currentLoc > (*itr).first.rangeBegin) + assert_dbg(); // stored ranges must not overlap + currentLoc = (*itr).first.rangeEnd; + ++itr; + } + } + + const std::map<InternalRange, TNodeObject*>& getAll() const { return m_map; }; +}; + +std::unique_ptr<VHeap> g_gpuBufferHeap = nullptr; +std::vector<uint8> s_pageUploadBuffer; +std::vector<class BufferCacheNode*> s_allCacheNodes; + +void LatteBufferCache_removeSingleNodeFromTree(BufferCacheNode* node); + +class BufferCacheNode +{ + static inline constexpr uint64 c_streamoutSig0 = 0xF0F0F0F0155C5B6Aull; + static inline constexpr uint64 c_streamoutSig1 = 0x8BE6336411814F4Full; + +public: + // returns false if not enough space is available + bool allocateCacheMemory() + { + cemu_assert_debug(m_hasCacheAlloc == false); + cemu_assert_debug(m_rangeEnd > m_rangeBegin); + m_hasCacheAlloc = g_gpuBufferHeap->allocOffset(m_rangeEnd - m_rangeBegin, CACHE_PAGE_SIZE, m_cacheOffset); + return m_hasCacheAlloc; + } + + void ReleaseCacheMemoryImmediately() + { + if (m_hasCacheAlloc) + { + cemu_assert_debug(isInUse() == false); + g_gpuBufferHeap->freeOffset(m_cacheOffset); + m_hasCacheAlloc = false; + } + } + + uint32 getBufferOffset(MPTR physAddr) const + { + cemu_assert_debug(m_hasCacheAlloc); + cemu_assert_debug(physAddr >= m_rangeBegin); + cemu_assert_debug(physAddr < m_rangeEnd); + uint32 relOffset = physAddr - m_rangeBegin; + return m_cacheOffset + relOffset; + } + + void writeStreamout(MPTR rangeBegin, MPTR rangeEnd) + { + if ((rangeBegin & 0xF)) + { + forceLogDebug_printf("writeStreamout(): RangeBegin not aligned to 16. Begin %08x End %08x", rangeBegin, rangeEnd); + rangeBegin = (rangeBegin + 0xF) & ~0xF; + rangeEnd = std::max(rangeBegin, rangeEnd); + } + if (rangeEnd & 0xF) + { + // todo - add support for 4 byte granularity for streamout writes and cache + // used by Affordable Space Adventures and YWW Level 1-8 + // also used by CoD Ghosts (8 byte granularity) + //forceLogDebug_printf("Streamout write size is not aligned to 16 bytes"); + rangeEnd &= ~0xF; + } + //cemu_assert_debug((rangeEnd & 0xF) == 0); + rangeBegin = std::max(rangeBegin, m_rangeBegin); + rangeEnd = std::min(rangeEnd, m_rangeEnd); + if (rangeBegin >= rangeEnd) + return; + sint32 numPages = getPageCountFromRange(rangeBegin, rangeEnd); + sint32 pageIndex = getPageIndexFromAddr(rangeBegin); + + cemu_assert_debug((m_rangeBegin + pageIndex * CACHE_PAGE_SIZE) <= rangeBegin); + cemu_assert_debug((m_rangeBegin + (pageIndex + numPages) * CACHE_PAGE_SIZE) >= rangeEnd); + + for (sint32 i = 0; i < numPages; i++) + { + pageWriteStreamoutSignatures(pageIndex, rangeBegin, rangeEnd); + pageIndex++; + //pageInfo->hasStreamoutData = true; + //pageInfo++; + } + if (numPages > 0) + m_hasStreamoutData = true; + } + + void checkAndSyncModifications(MPTR rangeBegin, MPTR rangeEnd, bool uploadData) + { + cemu_assert_debug(rangeBegin >= m_rangeBegin); + cemu_assert_debug(rangeEnd <= m_rangeEnd); + cemu_assert_debug(rangeBegin < m_rangeEnd); + cemu_assert_debug((rangeBegin % CACHE_PAGE_SIZE) == 0); + cemu_assert_debug((rangeEnd % CACHE_PAGE_SIZE) == 0); + + sint32 basePageIndex = getPageIndexFromAddrAligned(rangeBegin); + sint32 numPages = getPageCountFromRangeAligned(rangeBegin, rangeEnd); + uint8* pagePtr = memory_getPointerFromPhysicalOffset(rangeBegin); + sint32 uploadPageBegin = -1; + CachePageInfo* pageInfo = m_pageInfo.data() + basePageIndex; + for (sint32 i = 0; i < numPages; i++) + { + if (pageInfo->hasStreamoutData) + { + // first upload any pending sequence of pages + if (uploadPageBegin != -1) + { + // upload range + if (uploadData) + uploadPages(uploadPageBegin, basePageIndex + i); + uploadPageBegin = -1; + } + // check if hash changed + uint64 pageHash = hashPage(pagePtr); + if (pageInfo->hash != pageHash) + { + pageInfo->hash = pageHash; + // for pages that contain streamout data we do uploads with a much smaller granularity + // and skip uploading any data that is marked with streamout filler bytes + if (!uploadPageWithStreamoutFiltered(basePageIndex + i)) + pageInfo->hasStreamoutData = false; // all streamout data was replaced + } + pagePtr += CACHE_PAGE_SIZE; + pageInfo++; + continue; + } + + uint64 pageHash = hashPage(pagePtr); + pagePtr += CACHE_PAGE_SIZE; + if (pageInfo->hash != pageHash) + { + if (uploadPageBegin == -1) + uploadPageBegin = i + basePageIndex; + pageInfo->hash = pageHash; + } + else + { + if (uploadPageBegin != -1) + { + // upload range + if (uploadData) + uploadPages(uploadPageBegin, basePageIndex + i); + uploadPageBegin = -1; + } + } + pageInfo++; + } + if (uploadPageBegin != -1) + { + if (uploadData) + uploadPages(uploadPageBegin, basePageIndex + numPages); + } + } + + void checkAndSyncModifications(bool uploadData) + { + checkAndSyncModifications(m_rangeBegin, m_rangeEnd, uploadData); + m_lastModifyCheckCronon = g_currentCacheChronon; + m_hasInvalidation = false; + } + + void checkAndSyncModificationsIfChrononChanged(MPTR reservePhysAddress, uint32 reserveSize) + { + if (m_lastModifyCheckCronon != g_currentCacheChronon) + { + m_lastModifyCheckCronon = g_currentCacheChronon; + checkAndSyncModifications(m_rangeBegin, m_rangeEnd, true); + m_hasInvalidation = false; + } + if (m_hasInvalidation) + { + // ideally we would only upload the pages that intersect both the reserve range and the invalidation range + // but this would require complex per-page tracking of invalidation. Since this is on a hot path we do a cheap approximation + // where we only track one continous invalidation range + + // try to bound uploads to the reserve range within the invalidation + uint32 resRangeBegin = reservePhysAddress & ~CACHE_PAGE_SIZE_M1; + uint32 resRangeEnd = ((reservePhysAddress + reserveSize) + CACHE_PAGE_SIZE_M1) & ~CACHE_PAGE_SIZE_M1; + + uint32 uploadBegin = std::max(m_invalidationRangeBegin, resRangeBegin); + uint32 uploadEnd = std::min(resRangeEnd, m_invalidationRangeEnd); + + if (uploadBegin >= uploadEnd) + return; // reserve range not within invalidation or range is zero sized + + + if (uploadBegin == m_invalidationRangeBegin) + { + m_invalidationRangeBegin = uploadEnd; + checkAndSyncModifications(uploadBegin, uploadEnd, true); + } + if (uploadEnd == m_invalidationRangeEnd) + { + m_invalidationRangeEnd = uploadBegin; + checkAndSyncModifications(uploadBegin, uploadEnd, true); + } + else + { + // upload all of invalidation + checkAndSyncModifications(m_invalidationRangeBegin, m_invalidationRangeEnd, true); + m_invalidationRangeBegin = m_invalidationRangeEnd; + } + if(m_invalidationRangeEnd <= m_invalidationRangeBegin) + m_hasInvalidation = false; + + //if (resRangeBegin <= m_invalidationRangeBegin) + //{ + // // shrink/replace invalidation range from the bottom + // uint32 uploadBegin = m_invalidationRangeBegin;//std::max(m_invalidationRangeBegin, resRangeBegin); + // uint32 uploadEnd = std::min(resRangeEnd, m_invalidationRangeEnd); + // cemu_assert_debug(uploadEnd >= uploadBegin); + // if (uploadBegin != uploadEnd) + // checkAndSyncModifications(uploadBegin, uploadEnd, true); + // m_invalidationRangeBegin = uploadEnd; + // cemu_assert_debug(m_invalidationRangeBegin <= m_invalidationRangeEnd); + // if (m_invalidationRangeBegin >= m_invalidationRangeEnd) + // m_hasInvalidation = false; + //} + //else if (resRangeEnd >= m_invalidationRangeEnd) + //{ + // // shrink/replace invalidation range from the top + // uint32 uploadBegin = std::max(m_invalidationRangeBegin, resRangeBegin); + // uint32 uploadEnd = m_invalidationRangeEnd;// std::min(resRangeEnd, m_invalidationRangeEnd); + // cemu_assert_debug(uploadEnd >= uploadBegin); + // if (uploadBegin != uploadEnd) + // checkAndSyncModifications(uploadBegin, uploadEnd, true); + // m_invalidationRangeEnd = uploadBegin; + // cemu_assert_debug(m_invalidationRangeBegin <= m_invalidationRangeEnd); + // if (m_invalidationRangeBegin >= m_invalidationRangeEnd) + // m_hasInvalidation = false; + //} + //else + //{ + // // since we cant cut holes into the range upload it in it's entirety + // cemu_assert_debug(m_invalidationRangeEnd <= m_rangeEnd); + // cemu_assert_debug(m_invalidationRangeBegin >= m_rangeBegin); + // cemu_assert_debug(m_invalidationRangeBegin < m_invalidationRangeEnd); + // checkAndSyncModifications(m_invalidationRangeBegin, m_invalidationRangeEnd, true); + // m_hasInvalidation = false; + //} + + + + // todo - dont re-upload the whole range immediately + // under ideal circumstances we would only upload the data range requested for the current draw call + // but this is a hot path so we can't check + } + } + + void invalidate(MPTR rangeBegin, MPTR rangeEnd) + { + rangeBegin = std::max(rangeBegin, m_rangeBegin); + rangeEnd = std::min(rangeEnd, m_rangeEnd); + if (rangeBegin >= rangeEnd) + return; + if (m_hasInvalidation) + { + m_invalidationRangeBegin = std::min(m_invalidationRangeBegin, rangeBegin); + m_invalidationRangeEnd = std::max(m_invalidationRangeEnd, rangeEnd); + } + else + { + m_invalidationRangeBegin = rangeBegin; + m_invalidationRangeEnd = rangeEnd; + m_hasInvalidation = true; + } + cemu_assert_debug(m_invalidationRangeBegin >= m_rangeBegin); + cemu_assert_debug(m_invalidationRangeEnd <= m_rangeEnd); + cemu_assert_debug(m_invalidationRangeBegin < m_invalidationRangeEnd); + m_invalidationRangeBegin = m_invalidationRangeBegin & ~CACHE_PAGE_SIZE_M1; + m_invalidationRangeEnd = (m_invalidationRangeEnd + CACHE_PAGE_SIZE_M1) & ~CACHE_PAGE_SIZE_M1; + } + + void flagInUse() + { + m_lastDrawcall = LatteGPUState.drawCallCounter; + m_lastFrame = LatteGPUState.frameCounter; + } + + bool isInUse() const + { + return m_lastDrawcall == LatteGPUState.drawCallCounter; + } + + // returns true if the range does not contain any GPU-cache-only data and can be fully restored from RAM + bool isRAMOnly() const + { + return !m_hasStreamoutData; + } + + MPTR GetRangeBegin() const { return m_rangeBegin; } + MPTR GetRangeEnd() const { return m_rangeEnd; } + + uint32 GetDrawcallAge() const { return LatteGPUState.drawCallCounter - m_lastDrawcall; }; + uint32 GetFrameAge() const { return LatteGPUState.frameCounter - m_lastFrame; }; + + bool HasStreamoutData() const { return m_hasStreamoutData; }; + +private: + struct CachePageInfo + { + uint64 hash{ 0 }; + bool hasStreamoutData{ false }; + }; + + MPTR m_rangeBegin; + MPTR m_rangeEnd; // (exclusive) + bool m_hasCacheAlloc{ false }; + uint32 m_cacheOffset{ 0 }; + // usage + uint32 m_lastDrawcall; + uint32 m_lastFrame; + uint32 m_arrayIndex; + // state tracking + uint32 m_lastModifyCheckCronon{ g_currentCacheChronon - 1 }; + std::vector<CachePageInfo> m_pageInfo; + bool m_hasStreamoutData{ false }; + // invalidation + bool m_hasInvalidation{false}; + MPTR m_invalidationRangeBegin; + MPTR m_invalidationRangeEnd; + + BufferCacheNode(MPTR rangeBegin, MPTR rangeEnd): m_rangeBegin(rangeBegin), m_rangeEnd(rangeEnd) + { + flagInUse(); + cemu_assert_debug(rangeBegin < rangeEnd); + size_t numPages = getPageCountFromRangeAligned(rangeBegin, rangeEnd); + m_pageInfo.resize(numPages); + // append to array + m_arrayIndex = (uint32)s_allCacheNodes.size(); + s_allCacheNodes.emplace_back(this); + }; + + ~BufferCacheNode() + { + if (m_hasCacheAlloc) + g_deallocateQueue.emplace_back(m_cacheOffset); // release after current drawcall + // remove from array + auto temp = s_allCacheNodes.back(); + s_allCacheNodes.pop_back(); + if (this != temp) + { + s_allCacheNodes[m_arrayIndex] = temp; + temp->m_arrayIndex = m_arrayIndex; + } + } + + uint32 getPageIndexFromAddrAligned(uint32 offset) const + { + cemu_assert_debug((offset % CACHE_PAGE_SIZE) == 0); + return (offset - m_rangeBegin) / CACHE_PAGE_SIZE; + } + + uint32 getPageIndexFromAddr(uint32 offset) const + { + offset &= ~CACHE_PAGE_SIZE_M1; + return (offset - m_rangeBegin) / CACHE_PAGE_SIZE; + } + + uint32 getPageCountFromRangeAligned(MPTR rangeBegin, MPTR rangeEnd) const + { + cemu_assert_debug((rangeBegin % CACHE_PAGE_SIZE) == 0); + cemu_assert_debug((rangeEnd % CACHE_PAGE_SIZE) == 0); + cemu_assert_debug(rangeBegin <= rangeEnd); + return (rangeEnd - rangeBegin) / CACHE_PAGE_SIZE; + } + + uint32 getPageCountFromRange(MPTR rangeBegin, MPTR rangeEnd) const + { + rangeEnd = (rangeEnd + CACHE_PAGE_SIZE_M1) & ~CACHE_PAGE_SIZE_M1; + rangeBegin &= ~CACHE_PAGE_SIZE_M1; + cemu_assert_debug(rangeBegin <= rangeEnd); + return (rangeEnd - rangeBegin) / CACHE_PAGE_SIZE; + } + + void syncFromRAM(MPTR rangeBegin, MPTR rangeEnd) + { + cemu_assert_debug(rangeBegin >= m_rangeBegin); + cemu_assert_debug(rangeEnd <= m_rangeEnd); + cemu_assert_debug(rangeEnd > rangeBegin); + cemu_assert_debug(m_hasCacheAlloc); + + // reset write tracking + checkAndSyncModifications(rangeBegin, rangeEnd, false); + + g_renderer->bufferCache_upload(memory_getPointerFromPhysicalOffset(rangeBegin), rangeEnd - rangeBegin, getBufferOffset(rangeBegin)); + } + + void syncFromNode(BufferCacheNode* srcNode) + { + // get shared range + MPTR rangeBegin = std::max(m_rangeBegin, srcNode->m_rangeBegin); + MPTR rangeEnd = std::min(m_rangeEnd, srcNode->m_rangeEnd); + cemu_assert_debug(rangeBegin < rangeEnd); + g_renderer->bufferCache_copy(srcNode->getBufferOffset(rangeBegin), this->getBufferOffset(rangeBegin), rangeEnd - rangeBegin); + // copy page checksums and information + sint32 numPages = getPageCountFromRangeAligned(rangeBegin, rangeEnd); + CachePageInfo* pageInfoDst = this->m_pageInfo.data() + this->getPageIndexFromAddrAligned(rangeBegin); + CachePageInfo* pageInfoSrc = srcNode->m_pageInfo.data() + srcNode->getPageIndexFromAddrAligned(rangeBegin); + for (sint32 i = 0; i < numPages; i++) + { + pageInfoDst[i] = pageInfoSrc[i]; + if (pageInfoSrc[i].hasStreamoutData) + m_hasStreamoutData = true; + } + } + + void uploadPages(uint32 firstPage, uint32 lastPagePlusOne) + { + cemu_assert_debug(lastPagePlusOne > firstPage); + uint32 uploadRangeBegin = m_rangeBegin + firstPage * CACHE_PAGE_SIZE; + uint32 uploadRangeEnd = m_rangeBegin + lastPagePlusOne * CACHE_PAGE_SIZE; + cemu_assert_debug(uploadRangeEnd > uploadRangeBegin); + // make sure uploaded pages and hashes match + uint32 numPages = lastPagePlusOne - firstPage; + if (s_pageUploadBuffer.size() < (numPages * CACHE_PAGE_SIZE)) + s_pageUploadBuffer.resize(numPages * CACHE_PAGE_SIZE); + // todo - improve performance by merging memcpy + hashPage() ? + memcpy(s_pageUploadBuffer.data(), memory_getPointerFromPhysicalOffset(uploadRangeBegin), numPages * CACHE_PAGE_SIZE); + for (uint32 i = 0; i < numPages; i++) + { + m_pageInfo[firstPage + i].hash = hashPage(s_pageUploadBuffer.data() + i * CACHE_PAGE_SIZE); + } + g_renderer->bufferCache_upload(s_pageUploadBuffer.data(), uploadRangeEnd - uploadRangeBegin, getBufferOffset(uploadRangeBegin)); + } + + // upload only non-streamout data of a single page + // returns true if at least one streamout 16-byte block is present + // also updates the page hash to match the uploaded data (strict match) + sint32 uploadPageWithStreamoutFiltered(uint32 pageIndex) + { + uint8 pageCopy[CACHE_PAGE_SIZE]; + memcpy(pageCopy, memory_getPointerFromPhysicalOffset(m_rangeBegin + pageIndex * CACHE_PAGE_SIZE), CACHE_PAGE_SIZE); + + MPTR pageBase = m_rangeBegin + pageIndex * CACHE_PAGE_SIZE; + + sint32 blockBegin = -1; + uint64* pagePtrU64 = (uint64*)pageCopy; + m_pageInfo[pageIndex].hash = hashPage(pageCopy); + bool hasStreamoutBlocks = false; + for (sint32 i = 0; i < CACHE_PAGE_SIZE / 16; i++) + { + if (pagePtrU64[0] == c_streamoutSig0 && pagePtrU64[1] == c_streamoutSig1) + { + hasStreamoutBlocks = true; + if (blockBegin != -1) + { + uint32 uploadRelRangeBegin = blockBegin * 16; + uint32 uploadRelRangeEnd = i * 16; + cemu_assert_debug(uploadRelRangeEnd > uploadRelRangeBegin); + g_renderer->bufferCache_upload(pageCopy + uploadRelRangeBegin, uploadRelRangeEnd - uploadRelRangeBegin, getBufferOffset(pageBase + uploadRelRangeBegin)); + blockBegin = -1; + } + pagePtrU64 += 2; + continue; + } + else if (blockBegin == -1) + blockBegin = i; + pagePtrU64 += 2; + } + if (blockBegin != -1) + { + uint32 uploadRelRangeBegin = blockBegin * 16; + uint32 uploadRelRangeEnd = CACHE_PAGE_SIZE; + cemu_assert_debug(uploadRelRangeEnd > uploadRelRangeBegin); + g_renderer->bufferCache_upload(pageCopy + uploadRelRangeBegin, uploadRelRangeEnd - uploadRelRangeBegin, getBufferOffset(pageBase + uploadRelRangeBegin)); + blockBegin = -1; + } + return hasStreamoutBlocks; + } + + void shrink(MPTR newRangeBegin, MPTR newRangeEnd) + { + cemu_assert_debug(newRangeBegin >= m_rangeBegin); + cemu_assert_debug(newRangeEnd >= m_rangeEnd); + cemu_assert_debug(newRangeEnd > m_rangeBegin); + assert_dbg(); // todo (resize page array) + m_rangeBegin = newRangeBegin; + m_rangeEnd = newRangeEnd; + } + + static uint64 hashPage(uint8* mem) + { + // note - this algorithm is/was also baked into pageWriteStreamoutSignatures() + uint64 h = 0; + uint64* memU64 = (uint64*)mem; + for (uint32 i = 0; i < CACHE_PAGE_SIZE / 8; i++) + { + //h = _rotr64(h, 7); + //h ^= *memU64; + //memU64++; + + h = _rotr64(h, 7); + h += (*memU64 + (uint64)i); + memU64++; + } + return h; + } + + // flag page as having streamout data, also write streamout signatures to page memory + // also incrementally updates the page hash to include the written signatures, this prevents signature writes from triggering a data upload + void pageWriteStreamoutSignatures(uint32 pageIndex, MPTR rangeBegin, MPTR rangeEnd) + { + uint32 pageRangeBegin = m_rangeBegin + pageIndex * CACHE_PAGE_SIZE; + uint32 pageRangeEnd = pageRangeBegin + CACHE_PAGE_SIZE; + rangeBegin = std::max(pageRangeBegin, rangeBegin); + rangeEnd = std::min(pageRangeEnd, rangeEnd); + cemu_assert_debug(rangeEnd > rangeBegin); + cemu_assert_debug(rangeBegin >= pageRangeBegin); + cemu_assert_debug(rangeEnd <= pageRangeEnd); + cemu_assert_debug((rangeBegin & 0xF) == 0); + cemu_assert_debug((rangeEnd & 0xF) == 0); + + auto pageInfo = m_pageInfo.data() + pageIndex; + pageInfo->hasStreamoutData = true; + + // if the whole page is replaced we can use a cached hash + if (pageRangeBegin == rangeBegin && pageRangeEnd == rangeEnd) + { + uint64* pageMem = (uint64*)memory_getPointerFromPhysicalOffset(rangeBegin); + uint32 numBlocks = (rangeEnd - rangeBegin) / 16; + for (uint32 i = 0; i < numBlocks; i++) + { + pageMem[0] = c_streamoutSig0; + pageMem[1] = c_streamoutSig1; + pageMem += 2; + } + + pageInfo->hash = c_fullStreamoutPageHash; + return; + } + + uint64* pageMem = (uint64*)memory_getPointerFromPhysicalOffset(rangeBegin); + uint32 numBlocks = (rangeEnd - rangeBegin) / 16; + uint32 indexHashBlock = (rangeBegin - pageRangeBegin) / sizeof(uint64); + for (uint32 i = 0; i < numBlocks; i++) + { + pageMem[0] = c_streamoutSig0; + pageMem[1] = c_streamoutSig1; + pageMem += 2; + } + pageInfo->hash = 0; // reset hash + } + + static uint64 genStreamoutPageHash() + { + uint8 pageMem[CACHE_PAGE_SIZE]; + uint64* pageMemU64 = (uint64*)pageMem; + for (uint32 i = 0; i < sizeof(pageMem) / sizeof(uint64) / 2; i++) + { + pageMemU64[0] = c_streamoutSig0; + pageMemU64[1] = c_streamoutSig1; + pageMemU64 += 2; + } + + return hashPage(pageMem); + } + + static inline uint64 c_fullStreamoutPageHash = genStreamoutPageHash(); + static std::vector<uint32> g_deallocateQueue; + +public: + + static void ProcessDeallocations() + { + for(auto& itr : g_deallocateQueue) + g_gpuBufferHeap->freeOffset(itr); + g_deallocateQueue.clear(); + } + + // drops everything from the cache that isn't considered in use or unrestorable (ranges with streamout) + static void CleanupCacheAggressive(MPTR excludedRangeBegin, MPTR excludedRangeEnd) + { + size_t i = 0; + while (i < s_allCacheNodes.size()) + { + BufferCacheNode* node = s_allCacheNodes[i]; + if (node->isInUse()) + { + i++; + continue; + } + if(!node->isRAMOnly()) + { + i++; + continue; + } + if(node->GetRangeBegin() < excludedRangeEnd && node->GetRangeEnd() > excludedRangeBegin) + { + i++; + continue; + } + // delete range + node->ReleaseCacheMemoryImmediately(); + LatteBufferCache_removeSingleNodeFromTree(node); + delete node; + } + } + + /* callbacks from IntervalTree */ + + static BufferCacheNode* Create(MPTR rangeBegin, MPTR rangeEnd, std::span<BufferCacheNode*> overlappingObjects) + { + auto newRange = new BufferCacheNode(rangeBegin, rangeEnd); + if (!newRange->allocateCacheMemory()) + { + // not enough memory available, try to drop ram-only ranges from the ones we replace + for (size_t i = 0; i < overlappingObjects.size(); i++) + { + BufferCacheNode* nodeItr = overlappingObjects[i]; + if (!nodeItr->isInUse() && nodeItr->isRAMOnly()) + { + nodeItr->ReleaseCacheMemoryImmediately(); + delete nodeItr; + overlappingObjects[i] = nullptr; + } + } + // retry allocation + if (!newRange->allocateCacheMemory()) + { + forceLog_printf("Out-of-memory in GPU buffer (trying to allocate: %dKB) Cleaning up cache...", (rangeEnd - rangeBegin + 1023) / 1024); + CleanupCacheAggressive(rangeBegin, rangeEnd); + if (!newRange->allocateCacheMemory()) + { + forceLog_printf("Failed to free enough memory in GPU buffer"); + cemu_assert(false); + } + } + } + newRange->syncFromRAM(rangeBegin, rangeEnd); // possible small optimization: only load the ranges from RAM which are not overwritten by ->syncFromNode() + for (auto itr : overlappingObjects) + { + if(itr == nullptr) + continue; + newRange->syncFromNode(itr); + delete itr; + } + return newRange; + } + + static void Delete(BufferCacheNode* nodeObject) + { + delete nodeObject; + } + + static void Resize(BufferCacheNode* nodeObject, MPTR rangeBegin, MPTR rangeEnd) + { + nodeObject->shrink(rangeBegin, rangeEnd); + } + + static BufferCacheNode* Split(BufferCacheNode* nodeObject, MPTR firstRangeBegin, MPTR firstRangeEnd, MPTR secondRangeBegin, MPTR secondRangeEnd) + { + auto newRange = new BufferCacheNode(secondRangeBegin, secondRangeEnd); + // todo - add support for splitting BufferCacheNode memory allocations, then we dont need to do a separate allocation + if (!newRange->allocateCacheMemory()) + { + forceLog_printf("Out-of-memory in GPU buffer during split operation"); + cemu_assert(false); + } + newRange->syncFromNode(nodeObject); + nodeObject->shrink(firstRangeBegin, firstRangeEnd); + return newRange; + } +}; + +std::vector<uint32> BufferCacheNode::g_deallocateQueue; + +IntervalTree2<MPTR, BufferCacheNode> g_gpuBufferCache; + +void LatteBufferCache_removeSingleNodeFromTree(BufferCacheNode* node) +{ + g_gpuBufferCache.removeRangeSingleWithoutCallback(node->GetRangeBegin(), node->GetRangeEnd()); +} + +BufferCacheNode* LatteBufferCache_reserveRange(MPTR physAddress, uint32 size) +{ + MPTR rangeStart = physAddress - (physAddress % CACHE_PAGE_SIZE); + MPTR rangeEnd = (physAddress + size + CACHE_PAGE_SIZE_M1) & ~CACHE_PAGE_SIZE_M1; + + auto range = g_gpuBufferCache.getRange(rangeStart, rangeEnd); + if (!range) + { + g_gpuBufferCache.addRange(rangeStart, rangeEnd); + range = g_gpuBufferCache.getRange(rangeStart, rangeEnd); + cemu_assert_debug(range); + } + cemu_assert_debug(range->GetRangeBegin() <= physAddress); + cemu_assert_debug(range->GetRangeEnd() >= (physAddress + size)); + return range; +} + + +uint32 LatteBufferCache_retrieveDataInCache(MPTR physAddress, uint32 size) +{ + auto range = LatteBufferCache_reserveRange(physAddress, size); + range->flagInUse(); + + range->checkAndSyncModificationsIfChrononChanged(physAddress, size); + + return range->getBufferOffset(physAddress); +} + +void LatteBufferCache_copyStreamoutDataToCache(MPTR physAddress, uint32 size, uint32 streamoutBufferOffset) +{ + if (size == 0) + return; + cemu_assert_debug(size >= 16); + + auto range = LatteBufferCache_reserveRange(physAddress, size); + range->flagInUse(); + + g_renderer->bufferCache_copyStreamoutToMainBuffer(streamoutBufferOffset, range->getBufferOffset(physAddress), size); + + // write streamout signatures, flag affected pages + range->writeStreamout(physAddress, (physAddress + size)); +} + +void LatteBufferCache_invalidate(MPTR physAddress, uint32 size) +{ + if (size == 0) + return; + g_gpuBufferCache.forEachOverlapping(physAddress, physAddress + size, [](BufferCacheNode* node, MPTR invalidationRangeBegin, MPTR invalidationRangeEnd) + { + node->invalidate(invalidationRangeBegin, invalidationRangeEnd); + } + ); +} + +// optimized version of LatteBufferCache_invalidate() if physAddress points to the beginning of a page +void LatteBufferCache_invalidatePage(MPTR physAddress) +{ + cemu_assert_debug((physAddress & CACHE_PAGE_SIZE_M1) == 0); + BufferCacheNode* node = g_gpuBufferCache.getRangeByPoint(physAddress); + if (node) + node->invalidate(physAddress, physAddress+CACHE_PAGE_SIZE); +} + +void LatteBufferCache_processDeallocations() +{ + BufferCacheNode::ProcessDeallocations(); +} + +void LatteBufferCache_init(size_t bufferSize) +{ + g_gpuBufferHeap.reset(new VHeap(nullptr, (uint32)bufferSize)); + g_renderer->bufferCache_init((uint32)bufferSize); +} + +void LatteBufferCache_getStats(uint32& heapSize, uint32& allocationSize, uint32& allocNum) +{ + g_gpuBufferHeap->getStats(heapSize, allocationSize, allocNum); +} + +FSpinlock g_spinlockDCFlushQueue; +std::unordered_set<uint32>* g_DCFlushQueue = new std::unordered_set<uint32>(); // queued pages +std::unordered_set<uint32>* g_DCFlushQueueAlternate = new std::unordered_set<uint32>(); + +void LatteBufferCache_notifyDCFlush(MPTR address, uint32 size) +{ + if (address == 0 || size == 0xFFFFFFFF) + return; // global flushes are ignored for now + + uint32 firstPage = address / CACHE_PAGE_SIZE; + uint32 lastPage = (address + size - 1) / CACHE_PAGE_SIZE; + g_spinlockDCFlushQueue.acquire(); + for (uint32 i = firstPage; i <= lastPage; i++) + g_DCFlushQueue->emplace(i); + g_spinlockDCFlushQueue.release(); +} + +void LatteBufferCache_processDCFlushQueue() +{ + if (g_DCFlushQueue->empty()) // accessing this outside of the lock is technically undefined/unsafe behavior but on all known implementations this is fine and we can avoid the spinlock + return; + g_spinlockDCFlushQueue.acquire(); + std::swap(g_DCFlushQueue, g_DCFlushQueueAlternate); + g_spinlockDCFlushQueue.release(); + for (auto& itr : *g_DCFlushQueueAlternate) + LatteBufferCache_invalidatePage(itr * CACHE_PAGE_SIZE); + g_DCFlushQueueAlternate->clear(); +} + +void LatteBufferCache_notifyDrawDone() +{ + +} + +void LatteBufferCache_notifySwapTVScanBuffer() +{ + if( ActiveSettings::FlushGPUCacheOnSwap() ) + g_currentCacheChronon++; +} + +void LatteBufferCache_incrementalCleanup() +{ + static uint32 s_counter = 0; + + if (s_allCacheNodes.empty()) + return; + + s_counter++; + s_counter %= (uint32)s_allCacheNodes.size(); + + auto range = s_allCacheNodes[s_counter]; + + if (range->HasStreamoutData()) + { + // currently we never delete streamout ranges + // todo - check if streamout pages got overwritten + if the range would lose the hasStreamoutData flag + return; + } + + uint32 heapSize; + uint32 allocationSize; + uint32 allocNum; + g_gpuBufferHeap->getStats(heapSize, allocationSize, allocNum); + + if (allocationSize >= (heapSize * 4 / 5)) + { + // heap is 80% filled + if (range->GetFrameAge() >= 2) + { + g_gpuBufferCache.removeRangeSingle(range->GetRangeBegin(), range->GetRangeEnd()); + } + } + else if (allocationSize >= (heapSize * 3 / 4)) + { + // heap is 75-100% filled + if (range->GetFrameAge() >= 4) + { + g_gpuBufferCache.removeRangeSingle(range->GetRangeBegin(), range->GetRangeEnd()); + } + } + else if (allocationSize >= (heapSize / 2)) + { + // if heap is 50-75% filled + if (range->GetFrameAge() >= 20) + { + g_gpuBufferCache.removeRangeSingle(range->GetRangeBegin(), range->GetRangeEnd()); + } + } + else + { + // heap is under 50% capacity + if (range->GetFrameAge() >= 500) + { + g_gpuBufferCache.removeRangeSingle(range->GetRangeBegin(), range->GetRangeEnd()); + } + } +} diff --git a/src/Cafe/HW/Latte/Core/LatteBufferCache.h b/src/Cafe/HW/Latte/Core/LatteBufferCache.h new file mode 100644 index 00000000..da285192 --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteBufferCache.h @@ -0,0 +1,17 @@ +#pragma once + +void LatteBufferCache_init(size_t bufferSize); + +uint32 LatteBufferCache_retrieveDataInCache(MPTR physAddress, uint32 size); +void LatteBufferCache_copyStreamoutDataToCache(MPTR physAddress, uint32 size, uint32 streamoutBufferOffset); +void LatteBufferCache_invalidate(MPTR physAddress, uint32 size); + +void LatteBufferCache_notifyDCFlush(MPTR address, uint32 size); +void LatteBufferCache_processDCFlushQueue(); + +void LatteBufferCache_processDeallocations(); +void LatteBufferCache_incrementalCleanup(); + +void LatteBufferCache_getStats(uint32& heapSize, uint32& allocationSize, uint32& allocNum); + +void LatteBufferCache_notifySwapTVScanBuffer(); \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Core/LatteBufferData.cpp b/src/Cafe/HW/Latte/Core/LatteBufferData.cpp new file mode 100644 index 00000000..d864750a --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteBufferData.cpp @@ -0,0 +1,215 @@ +#include "Cafe/HW/Latte/ISA/RegDefines.h" +#include "Cafe/HW/Latte/Renderer/Renderer.h" +#include "Cafe/HW/Latte/Core/Latte.h" +#include "Cafe/HW/Latte/Core/LatteDraw.h" +#include "Cafe/HW/Latte/Core/LatteShader.h" +#include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h" +#include "Cafe/HW/Latte/Core/FetchShader.h" +#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" +#include "Cafe/GameProfile/GameProfile.h" + +#include "Cafe/HW/Latte/Core/LatteBufferCache.h" + +template<int vectorLen> +void rectGenerate4thVertex(uint32be* output, uint32be* input0, uint32be* input1, uint32be* input2) +{ + float* v = (float*)output; + + for (sint32 i = 0; i < vectorLen; i++) + output[vectorLen * 0 + i] = _swapEndianU32(input0[i]); + for (sint32 i = 0; i < vectorLen; i++) + output[vectorLen * 1 + i] = _swapEndianU32(input1[i]); + for (sint32 i = 0; i < vectorLen; i++) + output[vectorLen * 2 + i] = _swapEndianU32(input2[i]); + + float minX = std::min(v[vectorLen * 0 + 0], std::min(v[vectorLen * 1 + 0], v[vectorLen * 2 + 0])); + float maxX = std::max(v[vectorLen * 0 + 0], std::max(v[vectorLen * 1 + 0], v[vectorLen * 2 + 0])); + float minY = std::min(v[vectorLen * 0 + 1], std::min(v[vectorLen * 1 + 1], v[vectorLen * 2 + 1]));; + float maxY = std::max(v[vectorLen * 0 + 1], std::max(v[vectorLen * 1 + 1], v[vectorLen * 2 + 1]));; + + float totalX = minX; + totalX += maxY; + float halfX = totalX / 2.0f; + + float totalY = minY; + totalY += maxY; + float halfY = totalY / 2.0f; + + sint32 countX = + ((v[vectorLen * 0 + 0] < halfX) ? 1 : 0) + + ((v[vectorLen * 1 + 0] < halfX) ? 1 : 0) + + ((v[vectorLen * 2 + 0] < halfX) ? 1 : 0); + + sint32 countY = + ((v[vectorLen * 0 + 1] < halfY) ? 1 : 0) + + ((v[vectorLen * 1 + 1] < halfY) ? 1 : 0) + + ((v[vectorLen * 2 + 1] < halfY) ? 1 : 0); + + if (countX < 2) + v[vectorLen * 3 + 0] = minX; + else + v[vectorLen * 3 + 0] = maxX; + if (countY < 2) + v[vectorLen * 3 + 1] = minY; + else + v[vectorLen * 3 + 1] = maxY; + + if (vectorLen >= 3) + v[vectorLen * 3 + 2] = v[vectorLen * 0 + 2]; // z from v0 + if (vectorLen >= 4) + v[vectorLen * 3 + 3] = v[vectorLen * 0 + 3]; // w from v0 + + // order of rectangle vertices is + // v0 v1 + // v2 v3 + + for (sint32 f = 0; f < vectorLen*4; f++) + output[f] = _swapEndianU32(output[f]); +} + +#define ATTRIBUTE_CACHE_RING_SIZE (128) // up to 128 entries can be cached + +void LatteBufferCache_LoadRemappedUniforms(LatteDecompilerShader* shader, float* uniformData) +{ + uint32 shaderAluConst; + uint32 shaderUniformRegisterOffset; + + switch (shader->shaderType) + { + case LatteConst::ShaderType::Vertex: + shaderAluConst = 0x400; + shaderUniformRegisterOffset = mmSQ_VTX_UNIFORM_BLOCK_START; + break; + case LatteConst::ShaderType::Pixel: + shaderAluConst = 0; + shaderUniformRegisterOffset = mmSQ_PS_UNIFORM_BLOCK_START; + break; + case LatteConst::ShaderType::Geometry: + shaderAluConst = 0; // geometry shader has no ALU const + shaderUniformRegisterOffset = mmSQ_GS_UNIFORM_BLOCK_START; + break; + default: + cemu_assert_debug(false); + } + + // sourced from uniform registers + uint32* aluConstBase = LatteGPUState.contextRegister + mmSQ_ALU_CONSTANT0_0 + shaderAluConst; + for (auto it : shader->list_remappedUniformEntries_register) + { + uint64* uniformRegData = (uint64*)(aluConstBase + it.indexOffset / 4); + uint64* regDest = (uint64*)((uint8*)uniformData + it.mappedIndexOffset); + regDest[0] = uniformRegData[0]; + regDest[1] = uniformRegData[1]; + } + // sourced from uniform buffers + for (auto& bufferGroup : shader->list_remappedUniformEntries_bufferGroups) + { + MPTR physicalAddr = LatteGPUState.contextRegister[shaderUniformRegisterOffset + bufferGroup.kcacheBankIdOffset / 4]; + if (physicalAddr) + { + uint8* uniformBase = memory_base + physicalAddr; + for (auto& it : bufferGroup.entries) + { + uint64* regDest = (uint64*)((uint8*)uniformData + it.mappedIndexOffset); + uint64* uniformEntrySrc = (uint64*)(uniformBase + it.indexOffset); + memcpy(regDest, uniformEntrySrc, 16); + } + } + else + { + for (auto& it : bufferGroup.entries) + { + uint64* regDest = (uint64*)((uint8*)uniformData + it.mappedIndexOffset); + regDest[0] = 0; + regDest[1] = 0; + } + } + } +} + +void LatteBufferCache_syncGPUUniformBuffers(LatteDecompilerShader* shader, const uint32 uniformBufferRegOffset, LatteConst::ShaderType shaderType) +{ + if (shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK) + { + // use full uniform buffers + for (sint32 t = 0; t < shader->uniformBufferListCount; t++) + { + sint32 i = shader->uniformBufferList[t]; + MPTR physicalAddr = LatteGPUState.contextRegister[uniformBufferRegOffset + i * 7 + 0]; + uint32 uniformSize = LatteGPUState.contextRegister[uniformBufferRegOffset + i * 7 + 1] + 1; + + if (physicalAddr == MPTR_NULL) + { + // no data + g_renderer->buffer_bindUniformBuffer(shaderType, i, 0, 0); + continue; + } + + uint32 bindOffset = LatteBufferCache_retrieveDataInCache(physicalAddr, uniformSize); + + g_renderer->buffer_bindUniformBuffer(shaderType, i, bindOffset, uniformSize); + } + } +} + +// upload vertex and uniform buffers +bool LatteBufferCache_Sync(uint32 minIndex, uint32 maxIndex, uint32 baseInstance, uint32 instanceCount) +{ + static uint32 s_syncBufferCounter = 0; + + s_syncBufferCounter++; + if (s_syncBufferCounter >= 30) + { + LatteBufferCache_incrementalCleanup(); + s_syncBufferCounter = 0; + } + + LatteBufferCache_processDCFlushQueue(); + // process queued deallocations from previous drawcall + LatteBufferCache_processDeallocations(); + + // sync and bind vertex buffers + LatteFetchShader* parsedFetchShader = LatteSHRC_GetActiveFetchShader(); + if (!parsedFetchShader) + return false; + for (auto& bufferGroup : parsedFetchShader->bufferGroups) + { + uint32 bufferIndex = bufferGroup.attributeBufferIndex; + uint32 bufferBaseRegisterIndex = mmSQ_VTX_ATTRIBUTE_BLOCK_START + bufferIndex * 7; + MPTR bufferAddress = LatteGPUState.contextRegister[bufferBaseRegisterIndex + 0]; + uint32 bufferSize = LatteGPUState.contextRegister[bufferBaseRegisterIndex + 1] + 1; + uint32 bufferStride = (LatteGPUState.contextRegister[bufferBaseRegisterIndex + 2] >> 11) & 0xFFFF; + + if (bufferAddress == MPTR_NULL) + { + g_renderer->buffer_bindVertexBuffer(bufferIndex, 0, 0); + continue; + } + + // dont rely on buffer size given by game + uint32 fixedBufferSize = 0; + if (bufferGroup.hasVtxIndexAccess) + fixedBufferSize = bufferStride * (maxIndex + 1) + bufferGroup.maxOffset; + if (bufferGroup.hasInstanceIndexAccess) + { + uint32 fixedBufferSizeInstance = bufferStride * ((baseInstance + instanceCount) + 1) + bufferGroup.maxOffset; + fixedBufferSize = std::max(fixedBufferSize, fixedBufferSizeInstance); + } + if (fixedBufferSize == 0 || bufferStride == 0) + fixedBufferSize += 128; + + uint32 bindOffset = LatteBufferCache_retrieveDataInCache(bufferAddress, fixedBufferSize); + g_renderer->buffer_bindVertexBuffer(bufferIndex, bindOffset, fixedBufferSize); + } + // sync uniform buffers + LatteDecompilerShader* vertexShader = LatteSHRC_GetActiveVertexShader(); + if (vertexShader) + LatteBufferCache_syncGPUUniformBuffers(vertexShader, mmSQ_VTX_UNIFORM_BLOCK_START, LatteConst::ShaderType::Vertex); + LatteDecompilerShader* geometryShader = LatteSHRC_GetActiveGeometryShader(); + if (geometryShader) + LatteBufferCache_syncGPUUniformBuffers(geometryShader, mmSQ_GS_UNIFORM_BLOCK_START, LatteConst::ShaderType::Geometry); + LatteDecompilerShader* pixelShader = LatteSHRC_GetActivePixelShader(); + if (pixelShader) + LatteBufferCache_syncGPUUniformBuffers(pixelShader, mmSQ_PS_UNIFORM_BLOCK_START, LatteConst::ShaderType::Pixel); + return true; +} \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Core/LatteCachedFBO.h b/src/Cafe/HW/Latte/Core/LatteCachedFBO.h new file mode 100644 index 00000000..47fa9b42 --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteCachedFBO.h @@ -0,0 +1,148 @@ +#pragma once + +#include "Cafe/HW/Latte/Core/LatteTexture.h" +#include "util/math/vector2.h" + +class LatteCachedFBO +{ +public: + LatteCachedFBO(uint64 key); + + uint32 calculateNumColorBuffers() + { + uint32 n = 0; + for (sint32 i = 0; i < 8; i++) + if (colorBuffer[i].texture) + n++; + return n; + } + + bool hasDepthBuffer() + { + return depthBuffer.texture; + } + + std::vector<LatteTexture*>& GetTextures() + { + return m_referencedTextures; + } + + virtual ~LatteCachedFBO() {}; + +private: + void calculateEffectiveRenderAreaSize() + { + Vector2i rtEffectiveSize; + rtEffectiveSize.x = 0; + rtEffectiveSize.y = 0; + sint32 numViews = 0; + // derive extent from color buffers + for (sint32 i = 0; i < 8; i++) + { + if(colorBuffer[i].texture == nullptr) + continue; + sint32 effectiveWidth, effectiveHeight; + LatteTexture_getEffectiveSize(colorBuffer[i].texture->baseTexture, &effectiveWidth, &effectiveHeight, nullptr, colorBuffer[i].texture->firstMip); + if (rtEffectiveSize.x == 0 && rtEffectiveSize.y == 0) + { + rtEffectiveSize.x = effectiveWidth; + rtEffectiveSize.y = effectiveHeight; + } + if (effectiveWidth < rtEffectiveSize.x) + { + forceLogDebug_printf("Framebuffer has color texture with smaller effective width (%d -> %d)", rtEffectiveSize.x, effectiveWidth); + rtEffectiveSize.x = effectiveWidth; + } + if (effectiveHeight < rtEffectiveSize.y) + { + forceLogDebug_printf("Framebuffer has color texture with smaller effective height (%d -> %d)", rtEffectiveSize.y, effectiveHeight); + rtEffectiveSize.y = effectiveHeight; + } + numViews++; + } + // derive extent from depth buffer + if (depthBuffer.texture) + { + sint32 effectiveWidth, effectiveHeight; + LatteTexture_getEffectiveSize(depthBuffer.texture->baseTexture, &effectiveWidth, &effectiveHeight, nullptr, depthBuffer.texture->firstMip); + if (rtEffectiveSize.x == 0 && rtEffectiveSize.y == 0) + { + rtEffectiveSize.x = effectiveWidth; + rtEffectiveSize.y = effectiveHeight; + } + if (effectiveWidth < rtEffectiveSize.x) + { + forceLogDebug_printf("Framebuffer has depth texture with smaller effective width (%d -> %d)", rtEffectiveSize.x, effectiveWidth); + rtEffectiveSize.x = effectiveWidth; + } + if (effectiveHeight < rtEffectiveSize.y) + { + forceLogDebug_printf("Framebuffer has depth texture with smaller effective height (%d -> %d)", rtEffectiveSize.y, effectiveHeight); + rtEffectiveSize.y = effectiveHeight; + } + numViews++; + } + if (numViews == 0) + { + // empty FBO + m_size = Vector2i(32, 32); + return; + } + cemu_assert_debug(rtEffectiveSize.x != 0); + cemu_assert_debug(rtEffectiveSize.y != 0); + m_size = rtEffectiveSize; + } + +public: + uint64 key; + Vector2i m_size; + + struct + { + LatteTextureView* texture{}; + }colorBuffer[8]{}; + struct + { + LatteTextureView* texture{}; + bool hasStencil{}; + }depthBuffer{}; + uint32 drawBuffersMask{}; + + std::vector<LatteTexture*> m_referencedTextures; // color and depth views combined +}; + +class LatteMRT +{ +public: + static void NotifyTextureDeletion(LatteTexture* texture); + + // GPU state + static LatteTextureView* GetColorAttachmentTexture(uint32 index, bool createNew, bool checkForTextureChanges); + static uint8 GetActiveColorBufferMask(const LatteDecompilerShader* pixelShader, const struct LatteContextRegister& lcr); + static bool GetActiveDepthBufferMask(const struct LatteContextRegister& lcr); + static Latte::E_GX2SURFFMT GetColorBufferFormat(const uint32 index, const LatteContextRegister& lcr); + static Latte::E_GX2SURFFMT GetDepthBufferFormat(const struct LatteContextRegister& lcr); + + // FBO state management + static void SetColorAttachment(uint32 index, LatteTextureView* view); + static void SetDepthAndStencilAttachment(LatteTextureView* view, bool hasStencil); + static LatteTextureView* GetColorAttachment(uint32 index); + static LatteTextureView* GetDepthAttachment(); + + static void ApplyCurrentState(); + + static bool UpdateCurrentFBO(); // update FBO with info from current GPU state + + // helper functions + static void BindColorBufferOnly(LatteTextureView* view); + static void BindDepthBufferOnly(LatteTextureView* view); + + static void GetCurrentFragCoordScale(float* coordScale); + static void GetVirtualViewportDimensions(sint32& width, sint32& height); // returns the width and height of the current GPU viewport (unaffected by graphic pack rules) + + // todo - move this into FBO destructor (?) + static void DeleteCachedFBO(LatteCachedFBO* cfbo); + +private: + static LatteCachedFBO* CreateCachedFBO(uint64 key); +}; \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp b/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp new file mode 100644 index 00000000..b95b84d3 --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp @@ -0,0 +1,1671 @@ +#include "Cafe/HW/Latte/ISA/RegDefines.h" +#include "Cafe/OS/libs/gx2/GX2.h" // for write gatherer and special state. Get rid of dependency +#include "Cafe/OS/libs/gx2/GX2_Misc.h" // for GX2::sGX2MainCoreIndex. Legacy dependency +#include "Cafe/OS/libs/gx2/GX2_Event.h" // for notification callbacks +#include "Cafe/HW/Latte/Renderer/Renderer.h" +#include "Cafe/HW/Latte/Core/Latte.h" +#include "Cafe/HW/Latte/Core/LatteDraw.h" +#include "Cafe/HW/Latte/Core/LatteShader.h" +#include "Cafe/HW/Latte/Core/LatteAsyncCommands.h" +#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" +#include "Cafe/HW/Latte/Core/LatteIndices.h" +#include "Cafe/HW/Latte/Core/LatteBufferCache.h" +#include "Cafe/HW/Latte/Core/LattePM4.h" + +#include "Cafe/OS/libs/coreinit/coreinit_Time.h" + +#include "Cafe/CafeSystem.h" + +#define CP_TIMER_RECHECK 1024 + +//#define FAST_DRAW_LOGGING + +uint8* gxRingBufferReadPtr; // currently active read pointer (gx2 ring buffer or display list) +uint8* gx2CPParserDisplayListPtr; +uint8* gx2CPParserDisplayListStart; // used for debugging +uint8* gx2CPParserDisplayListEnd; + +void LatteThread_HandleOSScreen(); + +void LatteThread_Exit(); + +class DrawPassContext +{ +public: + bool isWithinDrawPass() const + { + return m_drawPassActive; + } + + void beginDrawPass() + { + m_drawPassActive = true; + m_isFirstDraw = true; + m_vertexBufferChanged = true; + m_uniformBufferChanged = true; + g_renderer->draw_beginSequence(); + } + + void executeDraw(uint32 count, bool isAutoIndex, MPTR physIndices) + { + uint32 baseVertex = LatteGPUState.contextRegister[mmSQ_VTX_BASE_VTX_LOC]; + uint32 baseInstance = LatteGPUState.contextRegister[mmSQ_VTX_START_INST_LOC]; + uint32 numInstances = LatteGPUState.drawContext.numInstances; + if (numInstances == 0) + return; + + if (!isAutoIndex) + { + cemu_assert_debug(physIndices != MPTR_NULL); + if (physIndices == MPTR_NULL) + return; + auto indexType = LatteGPUState.contextNew.VGT_DMA_INDEX_TYPE.get_INDEX_TYPE(); + g_renderer->draw_execute(baseVertex, baseInstance, numInstances, count, physIndices, indexType, m_isFirstDraw); + } + else + { + g_renderer->draw_execute(baseVertex, baseInstance, numInstances, count, MPTR_NULL, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE::AUTO, m_isFirstDraw); + } + m_isFirstDraw = false; + m_vertexBufferChanged = false; + m_uniformBufferChanged = false; + } + + void endDrawPass() + { + g_renderer->draw_endSequence(); + m_drawPassActive = false; + } + + void notifyModifiedVertexBuffer() + { + m_vertexBufferChanged = true; + } + + void notifyModifiedUniformBuffer() + { + m_uniformBufferChanged = true; + } + +private: + bool m_drawPassActive{ false }; + bool m_isFirstDraw{false}; + bool m_vertexBufferChanged{ false }; + bool m_uniformBufferChanged{ false }; +}; + +void LatteCP_processCommandBuffer(uint8* cmdBuffer, sint32 cmdSize, DrawPassContext& drawPassCtx); + +/* +* Read a U32 from the command buffer +* If no data is available then wait in a busy loop +*/ +uint32 LatteCP_readU32Deprc() +{ + uint32 v; + uint8* gxRingBufferWritePtr; + sint32 readDistance; + // no display list active + while (true) + { + gxRingBufferWritePtr = gx2WriteGatherPipe.writeGatherPtrGxBuffer[GX2::sGX2MainCoreIndex]; + readDistance = (sint32)(gxRingBufferWritePtr - gxRingBufferReadPtr); + if (readDistance != 0) + break; + + g_renderer->NotifyLatteCommandProcessorIdle(); // let the renderer know in case it wants to flush any commands + performanceMonitor.gpuTime_idleTime.beginMeasuring(); + // no command data available, spin in a busy loop for a bit then check again + for (sint32 busy = 0; busy < 80; busy++) + { + _mm_pause(); + } + LatteThread_HandleOSScreen(); // check if new frame was presented via OSScreen API + + readDistance = (sint32)(gxRingBufferWritePtr - gxRingBufferReadPtr); + if (readDistance != 0) + break; + if (!Latte_IsActive()) + LatteThread_Exit(); + + // still no command data available, do some other tasks + LatteTiming_HandleTimedVsync(); + LatteAsyncCommands_checkAndExecute(); + std::this_thread::yield(); + performanceMonitor.gpuTime_idleTime.endMeasuring(); + } + v = *(uint32*)gxRingBufferReadPtr; + gxRingBufferReadPtr += 4; +#ifndef PUBLIC_RELEASE + if (v == 0xcdcdcdcd) + assert_dbg(); +#endif + v = _swapEndianU32(v); + return v; +} + +void LatteCP_waitForNWords(uint32 numWords) +{ + uint8* gxRingBufferWritePtr; + sint32 readDistance; + bool isFlushed = false; + sint32 waitDistance = numWords * sizeof(uint32be); + // no display list active + while (true) + { + gxRingBufferWritePtr = gx2WriteGatherPipe.writeGatherPtrGxBuffer[GX2::sGX2MainCoreIndex]; + readDistance = (sint32)(gxRingBufferWritePtr - gxRingBufferReadPtr); + if (readDistance < 0) + return; // wrap around means there is at least one full command queued after this + if (readDistance >= waitDistance) + break; + g_renderer->NotifyLatteCommandProcessorIdle(); // let the renderer know in case it wants to flush any commands + performanceMonitor.gpuTime_idleTime.beginMeasuring(); + // no command data available, spin in a busy loop for a while then check again + for (sint32 busy = 0; busy < 80; busy++) + { + _mm_pause(); + } + readDistance = (sint32)(gxRingBufferWritePtr - gxRingBufferReadPtr); + if (readDistance < 0) + return; // wrap around means there is at least one full command queued after this + if (readDistance >= waitDistance) + break; + + if (!Latte_IsActive()) + LatteThread_Exit(); + + // still no command data available, do some other tasks + LatteTiming_HandleTimedVsync(); + LatteAsyncCommands_checkAndExecute(); + std::this_thread::yield(); + performanceMonitor.gpuTime_idleTime.endMeasuring(); + } +} + +template<uint32 readU32()> +void LatteCP_skipWords(uint32 wordsToSkip) +{ + while (wordsToSkip) + { + readU32(); + wordsToSkip--; + } +} + +typedef uint32be* LatteCMDPtr; +#define LatteReadCMD() ((uint32)*(cmd++)) +#define LatteSkipCMD(_nWords) cmd += (_nWords) + +LatteCMDPtr LatteCP_itSurfaceSync(LatteCMDPtr cmd) +{ + uint32 invalidationFlags = LatteReadCMD(); + uint32 size = LatteReadCMD() << 8; + MPTR addressPhys = LatteReadCMD() << 8; + uint32 pollInterval = LatteReadCMD(); + + if (addressPhys == MPTR_NULL || size == 0xFFFFFFFF) + return cmd; // block global invalidations because they are too expensive + + if (invalidationFlags & 0x800000) + { + // invalidate uniform or attribute buffer + LatteBufferCache_invalidate(addressPhys, size); + } + return cmd; +} + +template<uint32 readU32()> +void LatteCP_itIndirectBufferDepr(uint32 nWords) +{ + cemu_assert_debug(nWords == 3); + + uint32 physicalAddress = readU32(); + uint32 physicalAddressHigh = readU32(); // unused + uint32 sizeInDWords = readU32(); + uint32 displayListSize = sizeInDWords * 4; + cemu_assert_debug(displayListSize >= 4); + DrawPassContext drawPassCtx; + LatteCP_processCommandBuffer(memory_getPointerFromPhysicalOffset(physicalAddress), displayListSize, drawPassCtx); + if (drawPassCtx.isWithinDrawPass()) + drawPassCtx.endDrawPass(); +} + +LatteCMDPtr LatteCP_itIndirectBuffer(LatteCMDPtr cmd, uint32 nWords, DrawPassContext& drawPassCtx) +{ + cemu_assert_debug(nWords == 3); + uint32 physicalAddress = LatteReadCMD(); + uint32 physicalAddressHigh = LatteReadCMD(); // unused + uint32 sizeInDWords = LatteReadCMD(); + uint32 displayListSize = sizeInDWords * 4; + cemu_assert_debug(displayListSize >= 4); + + LatteCP_processCommandBuffer(memory_getPointerFromPhysicalOffset(physicalAddress), displayListSize, drawPassCtx); + return cmd; +} + +LatteCMDPtr LatteCP_itStreamoutBufferUpdate(LatteCMDPtr cmd, uint32 nWords) +{ + cemu_assert_debug(nWords == 5); + uint32 updateControl = LatteReadCMD(); + uint32 physicalAddressWrite = LatteReadCMD(); + uint32 ukn1 = LatteReadCMD(); + uint32 physicalAddressRead = LatteReadCMD(); + uint32 ukn3 = LatteReadCMD(); + + uint32 mode = (updateControl >> 1) & 3; + uint32 soIndex = (updateControl >> 8) & 3; + + if (mode == 0) + { + // reset pointer + MPTR virtualAddress = memory_physicalToVirtual(physicalAddressRead); + uint32 bufferOffset = 0; + LatteGPUState.contextRegister[mmVGT_STRMOUT_BUFFER_OFFSET_0 + 4 * soIndex] = bufferOffset; + } + else if (mode == 3) + { + // store current offset to memory + MPTR virtualAddress = memory_physicalToVirtual(physicalAddressWrite); + uint32 bufferOffset = LatteGPUState.contextRegister[mmVGT_STRMOUT_BUFFER_OFFSET_0 + 4 * soIndex]; + memory_writeU32(virtualAddress + 0x00, bufferOffset); + } + else + { + cemu_assert_unimplemented(); + } + return cmd; +} + +template<uint32 registerBaseMode> +void LatteCP_itSetRegistersGeneric_handleSpecialRanges(uint32 registerStartIndex, uint32 registerEndIndex) +{ + if constexpr (registerBaseMode == IT_SET_CONTEXT_REG) + { + if (registerStartIndex <= mmSQ_VTX_SEMANTIC_CLEAR && registerEndIndex >= mmSQ_VTX_SEMANTIC_CLEAR) + { + for (uint32 i = 0; i < 32; i++) + { + LatteGPUState.contextRegister[mmSQ_VTX_SEMANTIC_0 + i] = 0xFF; + } + } + } +} + +template<uint32 TRegisterBase> +LatteCMDPtr LatteCP_itSetRegistersGeneric(LatteCMDPtr cmd, uint32 nWords) +{ + uint32 registerOffset = LatteReadCMD(); + uint32 registerIndex = TRegisterBase + registerOffset; + uint32 registerStartIndex = registerIndex; + uint32 registerEndIndex = registerStartIndex + nWords; +#ifndef PUBLIC_RELEASE + cemu_assert_debug((registerIndex + nWords) <= LATTE_MAX_REGISTER); +#endif + uint32* outputReg = (uint32*)(LatteGPUState.contextRegister + registerIndex); + if (LatteGPUState.contextControl0 == 0x80000077) + { + // state shadowing enabled + uint32* shadowAddrs = LatteGPUState.contextRegisterShadowAddr + registerIndex; + sint32 indexCounter = 0; + while (--nWords) + { + uint32 dataWord = LatteReadCMD(); + MPTR regShadowAddr = shadowAddrs[indexCounter]; + if (regShadowAddr) + *(uint32*)(memory_base + regShadowAddr) = _swapEndianU32(dataWord); + outputReg[indexCounter] = dataWord; + indexCounter++; + } + } + else + { + // state shadowing disabled + sint32 indexCounter = 0; + while (--nWords) + { + *outputReg = LatteReadCMD(); + outputReg++; + } + } + // some register writes trigger special behavior + LatteCP_itSetRegistersGeneric_handleSpecialRanges<TRegisterBase>(registerStartIndex, registerEndIndex); + return cmd; +} + +template<uint32 TRegisterBase, typename TRegRangeCallback> +LatteCMDPtr LatteCP_itSetRegistersGeneric(LatteCMDPtr cmd, uint32 nWords, TRegRangeCallback cbRegRange) +{ + uint32 registerOffset = LatteReadCMD(); + uint32 registerIndex = TRegisterBase + registerOffset; + uint32 registerStartIndex = registerIndex; + uint32 registerEndIndex = registerStartIndex + nWords; +#ifndef PUBLIC_RELEASE + cemu_assert_debug((registerIndex + nWords) <= LATTE_MAX_REGISTER); +#endif + cbRegRange(registerStartIndex, registerEndIndex); + + uint32* outputReg = (uint32*)(LatteGPUState.contextRegister + registerIndex); + if (LatteGPUState.contextControl0 == 0x80000077) + { + // state shadowing enabled + uint32* shadowAddrs = LatteGPUState.contextRegisterShadowAddr + registerIndex; + sint32 indexCounter = 0; + while (--nWords) + { + uint32 dataWord = LatteReadCMD(); + MPTR regShadowAddr = shadowAddrs[indexCounter]; + if (regShadowAddr) + *(uint32*)(memory_base + regShadowAddr) = _swapEndianU32(dataWord); + outputReg[indexCounter] = dataWord; + indexCounter++; + } + } + else + { + // state shadowing disabled + sint32 indexCounter = 0; + while (--nWords) + { + *outputReg = LatteReadCMD(); + outputReg++; + } + } + // some register writes trigger special behavior + LatteCP_itSetRegistersGeneric_handleSpecialRanges<TRegisterBase>(registerStartIndex, registerEndIndex); + return cmd; +} + +LatteCMDPtr LatteCP_itIndexType(LatteCMDPtr cmd, uint32 nWords) +{ + cemu_assert_debug(nWords == 1); + LatteGPUState.contextNew.VGT_DMA_INDEX_TYPE.set_INDEX_TYPE((Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE)LatteReadCMD()); + return cmd; +} + +LatteCMDPtr LatteCP_itNumInstances(LatteCMDPtr cmd, uint32 nWords) +{ + cemu_assert_debug(nWords == 1); + LatteGPUState.drawContext.numInstances = LatteReadCMD(); + return cmd; +} + +LatteCMDPtr LatteCP_itWaitRegMem(LatteCMDPtr cmd, uint32 nWords) +{ + cemu_assert_debug(nWords == 6); + uint32 word0 = LatteReadCMD(); + uint32 word1 = LatteReadCMD(); + uint32 word2 = LatteReadCMD(); + uint32 word3 = LatteReadCMD(); + uint32 word4 = LatteReadCMD(); + uint32 word5 = LatteReadCMD(); + + uint32 compareOp = (word0) & 7; + uint32 physAddr = word1 & ~3; + cemu_assert_debug((physAddr&3) == 0); + uint32 fenceValue = word3; + uint32 fenceMask = word4; + + uint32* fencePtr = (uint32*)memory_getPointerFromPhysicalOffset(physAddr); + + const uint32 GPU7_WAIT_MEM_OP_ALWAYS = 0; + const uint32 GPU7_WAIT_MEM_OP_LESS = 1; + const uint32 GPU7_WAIT_MEM_OP_LEQUAL = 2; + const uint32 GPU7_WAIT_MEM_OP_EQUAL = 3; + const uint32 GPU7_WAIT_MEM_OP_NOTEQUAL = 4; + const uint32 GPU7_WAIT_MEM_OP_GEQUAL = 5; + const uint32 GPU7_WAIT_MEM_OP_GREATER = 6; + const uint32 GPU7_WAIT_MEM_OP_NEVER = 7; + + bool stalls = false; + if ((word0 & 0x10) != 0) + { + // wait for memory address + performanceMonitor.gpuTime_fenceTime.beginMeasuring(); + while (true) + { + uint32 fenceMemValue = _swapEndianU32(*fencePtr); + fenceMemValue &= fenceMask; + if (compareOp == GPU7_WAIT_MEM_OP_GEQUAL) + { + // greater or equal + if (fenceMemValue >= fenceValue) + break; + } + else if (compareOp == GPU7_WAIT_MEM_OP_EQUAL) + { + // equal + if (fenceMemValue == fenceValue) + break; + } + else + assert_dbg(); + if (!stalls) + { + g_renderer->NotifyLatteCommandProcessorIdle(); + stalls = true; + } + + // check if any GPU events happened + LatteTiming_HandleTimedVsync(); + LatteAsyncCommands_checkAndExecute(); + } + performanceMonitor.gpuTime_fenceTime.endMeasuring(); + } + else + { + // wait for register + debugBreakpoint(); + } + return cmd; +} + +LatteCMDPtr LatteCP_itMemWrite(LatteCMDPtr cmd, uint32 nWords) +{ + cemu_assert_debug(nWords == 4); + uint32 word0 = LatteReadCMD(); + uint32 word1 = LatteReadCMD(); + uint32 word2 = LatteReadCMD(); + uint32 word3 = LatteReadCMD(); + + MPTR valuePhysAddr = (word0 & ~3); + if (valuePhysAddr == 0) + { + cemuLog_force("GPU: Invalid itMemWrite to null pointer"); + return cmd; + } + uint32be* memPtr = (uint32be*)memory_getPointerFromPhysicalOffset(valuePhysAddr); + + if (word1 == 0x40000) + { + // write U32 + *memPtr = word2; + } + else if (word1 == 0x00000) + { + // write U64 (as two U32) + // note: The U32s are swapped + memPtr[0] = word2; + memPtr[1] = word3; + } + else if (word1 == 0x20000) + { + // write U64 (little endian) + memPtr[0] = _swapEndianU32(word2); + memPtr[1] = _swapEndianU32(word3); + } + else + cemu_assert_unimplemented(); + return cmd; +} + + +LatteCMDPtr LatteCP_itMemSemaphore(LatteCMDPtr cmd, uint32 nWords) +{ + cemu_assert_debug(nWords == 2); + MPTR semaphorePhysicalAddress = LatteReadCMD(); + uint32 semaphoreControl = LatteReadCMD(); + uint8 SEM_SIGNAL = (semaphoreControl >> 29) & 7; + + std::atomic<uint64le>* semaphoreData = _rawPtrToAtomic((uint64le*)memory_getPointerFromPhysicalOffset(semaphorePhysicalAddress)); + static_assert(sizeof(std::atomic<uint64le>) == sizeof(uint64le)); + + if (SEM_SIGNAL == 6) + { + // signal + semaphoreData->fetch_add(1); + } + else if(SEM_SIGNAL == 7) + { + // wait + size_t loopCount = 0; + while (true) + { + uint64le oldVal = semaphoreData->load(); + if (oldVal == 0) + { + loopCount++; + if (loopCount > 2000) + std::this_thread::yield(); + continue; + } + if (semaphoreData->compare_exchange_strong(oldVal, oldVal - 1)) + break; + } + } + else + { + cemu_assert_debug(false); + } + return cmd; +} + +LatteCMDPtr LatteCP_itContextControl(LatteCMDPtr cmd, uint32 nWords) +{ + cemu_assert_debug(nWords == 2); + uint32 word0 = LatteReadCMD(); + uint32 word1 = LatteReadCMD(); + LatteGPUState.contextControl0 = word0; + LatteGPUState.contextControl1 = word1; + return cmd; +} + +LatteCMDPtr LatteCP_itLoadReg(LatteCMDPtr cmd, uint32 nWords, uint32 regBase) +{ + if (nWords < 2 || (nWords & 1) != 0) + { + cemuLog_logDebug(LogType::Force, "itLoadReg: Invalid nWords value"); + return cmd; + } + MPTR physAddressRegArea = LatteReadCMD(); + uint32 waitForIdle = LatteReadCMD(); + uint32 loadEntries = (nWords - 2) / 2; + uint32 regIndex = 0; + for (uint32 i = 0; i < loadEntries; i++) + { + uint32 regOffset = LatteReadCMD(); + uint32 regCount = LatteReadCMD(); + cemu_assert_debug(regCount != 0); + for (uint32 f = 0; f < regCount; f++) + { + uint32 regAddr = regBase + regOffset + f; + uint32 regShadowMemAddr = physAddressRegArea + regIndex * 4; + LatteGPUState.contextRegisterShadowAddr[regAddr] = regShadowMemAddr; + LatteGPUState.contextRegister[regAddr] = memory_readU32Direct(regShadowMemAddr); + regIndex++; + } + } + return cmd; +} + +bool conditionalRenderActive = false; + +LatteCMDPtr LatteCP_itSetPredication(LatteCMDPtr cmd, uint32 nWords) +{ + cemu_assert_debug(nWords == 2); + MPTR physQueryInfo = LatteReadCMD(); + uint32 flags = LatteReadCMD(); + + uint32 queryTypeFlag = (flags >> 13) & 7; + uint32 pixelsMustPassFlag = (flags >> 31) & 1; + uint32 dontWaitFlag = (flags >> 1) & 19; + + if (queryTypeFlag == 0) + { + // disable conditional render + if (conditionalRenderActive == false) + debug_printf("conditionalRenderActive already inactive\n"); + conditionalRenderActive = false; + } + else + { + // enable conditonal render + if (conditionalRenderActive == true) + debug_printf("conditionalRenderActive already active\n"); + conditionalRenderActive = true; + } + return cmd; +} + +LatteCMDPtr LatteCP_itDrawIndex2(LatteCMDPtr cmd, uint32 nWords, DrawPassContext& drawPassCtx) +{ + cemu_assert_debug(nWords == 5); + uint32 ukn1 = LatteReadCMD(); + MPTR physIndices = LatteReadCMD(); + uint32 ukn2 = LatteReadCMD(); + uint32 count = LatteReadCMD(); + uint32 ukn3 = LatteReadCMD(); + + performanceMonitor.cycle[performanceMonitor.cycleIndex].drawCallCounter++; + + LatteGPUState.currentDrawCallTick = GetTickCount(); + drawPassCtx.executeDraw(count, false, physIndices); + return cmd; +} + +LatteCMDPtr LatteCP_itDrawIndexAuto(LatteCMDPtr cmd, uint32 nWords, DrawPassContext& drawPassCtx) +{ + cemu_assert_debug(nWords == 2); + uint32 count = LatteReadCMD(); + uint32 ukn = LatteReadCMD(); + + performanceMonitor.cycle[performanceMonitor.cycleIndex].drawCallCounter++; + + if (LatteGPUState.drawContext.numInstances == 0) + return cmd; + LatteGPUState.currentDrawCallTick = GetTickCount(); + // todo - better way to identify compute drawcalls + if ((LatteGPUState.contextRegister[mmSQ_CONFIG] >> 24) == 0xE4) + { + uint32 vsProgramCode = ((LatteGPUState.contextRegister[mmSQ_PGM_START_ES] & 0xFFFFFF) << 8); + uint32 vsProgramSize = LatteGPUState.contextRegister[mmSQ_PGM_START_ES + 1] << 3; + forceLogDebug_printf("Compute %d %08x %08x (unsupported)\n", count, vsProgramCode, vsProgramSize); + } + else + { + drawPassCtx.executeDraw(count, true, MPTR_NULL); + } + return cmd; +} + +MPTR _tempIndexArrayMPTR = MPTR_NULL; + +LatteCMDPtr LatteCP_itDrawImmediate(LatteCMDPtr cmd, uint32 nWords, DrawPassContext& drawPassCtx) +{ + uint32 count = LatteReadCMD(); + uint32 ukn1 = LatteReadCMD(); + // reserve array for index data + if (_tempIndexArrayMPTR == MPTR_NULL) + _tempIndexArrayMPTR = coreinit_allocFromSysArea(0x4000 * sizeof(uint32), 0x4); + + LatteGPUState.currentDrawCallTick = GetTickCount(); + // calculate size of index data in packet and read indices + uint32 numIndexU32s; + auto indexType = LatteGPUState.contextNew.VGT_DMA_INDEX_TYPE.get_INDEX_TYPE(); + if (indexType == Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE::U16_BE) + { + // 16bit indices + numIndexU32s = (count + 1) / 2; + memcpy(memory_getPointerFromVirtualOffset(_tempIndexArrayMPTR), cmd, numIndexU32s * sizeof(uint32)); + LatteSkipCMD(numIndexU32s); + // swap pairs + uint32* indexDataU32 = (uint32*)memory_getPointerFromVirtualOffset(_tempIndexArrayMPTR); + for (uint32 i = 0; i < numIndexU32s; i++) + { + indexDataU32[i] = (indexDataU32[i] >> 16) | (indexDataU32[i] << 16); + } + LatteIndices_invalidate(memory_getPointerFromVirtualOffset(_tempIndexArrayMPTR), numIndexU32s * sizeof(uint32)); + } + else if (indexType == Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE::U32_BE) + { + // 32bit indices + cemu_assert_debug(false); // testing needed + numIndexU32s = count; + memcpy(memory_getPointerFromVirtualOffset(_tempIndexArrayMPTR), cmd, numIndexU32s * sizeof(uint32)); + LatteSkipCMD(numIndexU32s); + LatteIndices_invalidate(memory_getPointerFromVirtualOffset(_tempIndexArrayMPTR), numIndexU32s * sizeof(uint32)); + } + else + { + cemuLog_log(LogType::Force, "itDrawImmediate - Unsupported index type"); + return cmd; + } + // verify packet size + if (nWords != (2 + numIndexU32s)) + debugBreakpoint(); + performanceMonitor.cycle[performanceMonitor.cycleIndex].drawCallCounter++; + + uint32 baseVertex = LatteGPUState.contextRegister[mmSQ_VTX_BASE_VTX_LOC]; + uint32 baseInstance = LatteGPUState.contextRegister[mmSQ_VTX_START_INST_LOC]; + uint32 numInstances = LatteGPUState.drawContext.numInstances; + + drawPassCtx.executeDraw(count, false, _tempIndexArrayMPTR); + return cmd; + +} + +LatteCMDPtr LatteCP_itHLEFifoWrapAround(LatteCMDPtr cmd, uint32 nWords) +{ + cemu_assert_debug(nWords == 1); + uint32 unused = LatteReadCMD(); + gxRingBufferReadPtr = gx2WriteGatherPipe.gxRingBuffer; + cmd = (LatteCMDPtr)gxRingBufferReadPtr; + return cmd; +} + +LatteCMDPtr LatteCP_itHLESampleTimer(LatteCMDPtr cmd, uint32 nWords) +{ + cemu_assert_debug(nWords == 1); + MPTR timerMPTR = (MPTR)LatteReadCMD(); + memory_writeU64Slow(timerMPTR, coreinit::coreinit_getTimerTick()); + return cmd; +} + +LatteCMDPtr LatteCP_itHLESpecialState(LatteCMDPtr cmd, uint32 nWords) +{ + cemu_assert_debug(nWords == 2); + uint32 stateId = LatteReadCMD(); + uint32 stateValue = LatteReadCMD(); + if (stateId > GX2_SPECIAL_STATE_COUNT) + { + cemu_assert_suspicious(); + } + else + { + LatteGPUState.contextNew.GetSpecialStateValues()[stateId] = stateValue; + } + return cmd; +} + +LatteCMDPtr LatteCP_itHLESetRetirementTimestamp(LatteCMDPtr cmd, uint32 nWords) +{ + cemu_assert_debug(nWords == 2); + uint32 timestampHigh = (uint32)LatteReadCMD(); + uint32 timestampLow = (uint32)LatteReadCMD(); + uint64 timestamp = ((uint64)timestampHigh << 32ULL) | (uint64)timestampLow; + GX2::__GX2NotifyNewRetirementTimestamp(timestamp); + return cmd; +} + +LatteCMDPtr LatteCP_itHLEBeginOcclusionQuery(LatteCMDPtr cmd, uint32 nWords) +{ + cemu_assert_debug(nWords == 1); + MPTR queryMPTR = (MPTR)LatteReadCMD(); + LatteQuery_BeginOcclusionQuery(queryMPTR); + return cmd; +} + +LatteCMDPtr LatteCP_itHLEEndOcclusionQuery(LatteCMDPtr cmd, uint32 nWords) +{ + cemu_assert_debug(nWords == 1); + MPTR queryMPTR = (MPTR)LatteReadCMD(); + LatteQuery_EndOcclusionQuery(queryMPTR); + return cmd; +} + +LatteCMDPtr LatteCP_itHLEBottomOfPipeCB(LatteCMDPtr cmd, uint32 nWords) +{ + cemu_assert_debug(nWords == 3); + MPTR timestampMPTR = (uint32)LatteReadCMD(); + uint32 timestampHigh = (uint32)LatteReadCMD(); + uint32 timestampLow = (uint32)LatteReadCMD(); + uint64 timestamp = ((uint64)timestampHigh << 32ULL) | (uint64)timestampLow; + // write timestamp + *(uint32*)memory_getPointerFromPhysicalOffset(timestampMPTR) = _swapEndianU32((uint32)(timestamp >> 32)); + *(uint32*)memory_getPointerFromPhysicalOffset(timestampMPTR + 4) = _swapEndianU32((uint32)timestamp); + // send event + GX2::__GX2NotifyEvent(GX2::GX2CallbackEventType::TIMESTAMP_BOTTOM); + return cmd; +} + +// GPU-side handler for GX2CopySurface/GX2CopySurfaceEx and similar +LatteCMDPtr LatteCP_itHLECopySurfaceNew(LatteCMDPtr cmd, uint32 nWords) +{ + cemu_assert_debug(nWords == 26); + // src + MPTR srcPhysAddr = LatteReadCMD(); + MPTR srcMipAddr = LatteReadCMD(); + uint32 srcSwizzle = LatteReadCMD(); + Latte::E_GX2SURFFMT srcSurfaceFormat = (Latte::E_GX2SURFFMT)LatteReadCMD(); + sint32 srcWidth = LatteReadCMD(); + sint32 srcHeight = LatteReadCMD(); + sint32 srcDepth = LatteReadCMD(); + uint32 srcPitch = LatteReadCMD(); + uint32 srcSlice = LatteReadCMD(); + Latte::E_DIM srcDim = (Latte::E_DIM)LatteReadCMD(); + Latte::E_HWTILEMODE srcTilemode = (Latte::E_HWTILEMODE)LatteReadCMD(); + sint32 srcAA = LatteReadCMD(); + sint32 srcLevel = LatteReadCMD(); + // dst + MPTR dstPhysAddr = LatteReadCMD(); + MPTR dstMipAddr = LatteReadCMD(); + uint32 dstSwizzle = LatteReadCMD(); + Latte::E_GX2SURFFMT dstSurfaceFormat = (Latte::E_GX2SURFFMT)LatteReadCMD(); + sint32 dstWidth = LatteReadCMD(); + sint32 dstHeight = LatteReadCMD(); + sint32 dstDepth = LatteReadCMD(); + uint32 dstPitch = LatteReadCMD(); + uint32 dstSlice = LatteReadCMD(); + Latte::E_DIM dstDim = (Latte::E_DIM)LatteReadCMD(); + Latte::E_HWTILEMODE dstTilemode = (Latte::E_HWTILEMODE)LatteReadCMD(); + sint32 dstAA = LatteReadCMD(); + sint32 dstLevel = LatteReadCMD(); + + LatteSurfaceCopy_copySurfaceNew(srcPhysAddr, srcMipAddr, srcSwizzle, srcSurfaceFormat, srcWidth, srcHeight, srcDepth, srcPitch, srcSlice, srcDim, srcTilemode, srcAA, srcLevel, dstPhysAddr, dstMipAddr, dstSwizzle, dstSurfaceFormat, dstWidth, dstHeight, dstDepth, dstPitch, dstSlice, dstDim, dstTilemode, dstAA, dstLevel); + return cmd; +} + +LatteCMDPtr LatteCP_itHLEClearColorDepthStencil(LatteCMDPtr cmd, uint32 nWords) +{ + cemu_assert_debug(nWords == 23); + uint32 clearMask = LatteReadCMD(); // color (1), depth (2), stencil (4) + // color buffer + MPTR colorBufferMPTR = LatteReadCMD(); // MPTR for color buffer (physical address) + MPTR colorBufferFormat = LatteReadCMD(); // format for color buffer + Latte::E_HWTILEMODE colorBufferTilemode = (Latte::E_HWTILEMODE)LatteReadCMD(); + uint32 colorBufferWidth = LatteReadCMD(); + uint32 colorBufferHeight = LatteReadCMD(); + uint32 colorBufferPitch = LatteReadCMD(); + uint32 colorBufferViewFirstSlice = LatteReadCMD(); + uint32 colorBufferViewNumSlice = LatteReadCMD(); + // depth buffer + MPTR depthBufferMPTR = LatteReadCMD(); // MPTR for depth buffer (physical address) + MPTR depthBufferFormat = LatteReadCMD(); // format for depth buffer + Latte::E_HWTILEMODE depthBufferTileMode = (Latte::E_HWTILEMODE)LatteReadCMD(); + uint32 depthBufferWidth = LatteReadCMD(); + uint32 depthBufferHeight = LatteReadCMD(); + uint32 depthBufferPitch = LatteReadCMD(); + uint32 depthBufferViewFirstSlice = LatteReadCMD(); + uint32 depthBufferViewNumSlice = LatteReadCMD(); + + float r = (float)LatteReadCMD() / 255.0f; + float g = (float)LatteReadCMD() / 255.0f; + float b = (float)LatteReadCMD() / 255.0f; + float a = (float)LatteReadCMD() / 255.0f; + + float clearDepth; + *(uint32*)&clearDepth = LatteReadCMD(); + uint32 clearStencil = LatteReadCMD(); + + LatteRenderTarget_itHLEClearColorDepthStencil( + clearMask, + colorBufferMPTR, colorBufferFormat, colorBufferTilemode, colorBufferWidth, colorBufferHeight, colorBufferPitch, colorBufferViewFirstSlice, colorBufferViewNumSlice, + depthBufferMPTR, depthBufferFormat, depthBufferTileMode, depthBufferWidth, depthBufferHeight, depthBufferPitch, depthBufferViewFirstSlice, depthBufferViewNumSlice, + r, g, b, a, + clearDepth, clearStencil); + return cmd; +} + +LatteCMDPtr LatteCP_itHLERequestSwapBuffers(LatteCMDPtr cmd, uint32 nWords) +{ + catchOpenGLError(); + cemu_assert_debug(nWords == 1); + MPTR reserved1 = LatteReadCMD(); + // request flip counter increase (will be increased on next flip) + LatteGPUState.flipRequestCount.fetch_add(1); + return cmd; +} + +LatteCMDPtr LatteCP_itHLESwapScanBuffer(LatteCMDPtr cmd, uint32 nWords) +{ + catchOpenGLError(); + cemu_assert_debug(nWords == 1); + MPTR reserved1 = LatteReadCMD(); // reserved + LatteRenderTarget_itHLESwapScanBuffer(); + return cmd; +} + +LatteCMDPtr LatteCP_itHLEWaitForFlip(LatteCMDPtr cmd, uint32 nWords) +{ + catchOpenGLError(); + cemu_assert_debug(nWords == 1); + MPTR reserved1 = LatteReadCMD(); // reserved + // wait for flip + uint32 currentFlipCount = LatteGPUState.flipCounter; + while (true) + { + _mm_pause(); + if (currentFlipCount != LatteGPUState.flipCounter) + { + break; + } + // check if any GPU events happened + LatteTiming_HandleTimedVsync(); + std::this_thread::yield(); + } + return cmd; +} + +LatteCMDPtr LatteCP_itHLECopyColorBufferToScanBuffer(LatteCMDPtr cmd, uint32 nWords) +{ + MPTR colorBufferPtr = LatteReadCMD(); // physical address + uint32 colorBufferWidth = LatteReadCMD(); + uint32 colorBufferHeight = LatteReadCMD(); + uint32 colorBufferPitch = LatteReadCMD(); + Latte::E_HWTILEMODE colorBufferTilemode = (Latte::E_HWTILEMODE)LatteReadCMD(); + uint32 colorBufferSwizzle = LatteReadCMD(); + uint32 colorBufferSliceIndex = LatteReadCMD(); + uint32 colorBufferFormat = LatteReadCMD(); + uint32 renderTarget = LatteReadCMD(); + + LatteRenderTarget_itHLECopyColorBufferToScanBuffer(colorBufferPtr, colorBufferWidth, colorBufferHeight, colorBufferSliceIndex, colorBufferFormat, colorBufferPitch, colorBufferTilemode, colorBufferSwizzle, renderTarget); + + return cmd; +} + +void LatteCP_dumpCommandBufferError(LatteCMDPtr cmdStart, LatteCMDPtr cmdEnd, LatteCMDPtr cmdError) +{ + cemuLog_log(LogType::Force, "Detected error in GPU command buffer"); + cemuLog_log(LogType::Force, "Dumping contents and info"); + cemuLog_log(LogType::Force, "Buffer 0x{0:08x} Size 0x{1:08x}", memory_getVirtualOffsetFromPointer(cmdStart), memory_getVirtualOffsetFromPointer(cmdEnd)); + cemuLog_log(LogType::Force, "Error at 0x{0:08x}", memory_getVirtualOffsetFromPointer(cmdError)); + for (LatteCMDPtr p = cmdStart; p < cmdEnd; p += 4) + { + if(cmdError >= p && cmdError < (p+4) ) + cemuLog_log(LogType::Force, "0x{0:08x}: {1:08x} {2:08x} {3:08x} {4:08x} <<<<<", memory_getVirtualOffsetFromPointer(p), p[0], p[1], p[2], p[3]); + else + cemuLog_log(LogType::Force, "0x{0:08x}: {1:08x} {2:08x} {3:08x} {4:08x}", memory_getVirtualOffsetFromPointer(p), p[0], p[1], p[2], p[3]); + } + cemuLog_waitForFlush(); + cemu_assert_debug(false); +} + +// any drawcalls issued without changing textures, framebuffers, shader or other complex states can be done quickly without having to reinitialize the entire pipeline state +// we implement this optimization by having an optimized version of LatteCP_processCommandBuffer, called right after drawcalls, which only implements commands that dont interfere with fast drawing. Other commands will cause this function to return to the complex parser +LatteCMDPtr LatteCP_processCommandBuffer_continuousDrawPass(LatteCMDPtr cmd, LatteCMDPtr cmdStart, LatteCMDPtr cmdEnd, DrawPassContext& drawPassCtx) +{ + cemu_assert_debug(drawPassCtx.isWithinDrawPass()); + // quit early if there are parameters set which are generally incompatible with fast drawing + if (LatteGPUState.contextRegister[mmVGT_STRMOUT_EN] != 0) + { + drawPassCtx.endDrawPass(); + return cmd; + } + // check for other special states? + + while (cmd < cmdEnd) + { + LatteCMDPtr cmdBeforeCommand = cmd; + uint32 itHeader = LatteReadCMD(); + uint32 itHeaderType = (itHeader >> 30) & 3; + if (itHeaderType == 3) + { + uint32 itCode = (itHeader >> 8) & 0xFF; + uint32 nWords = ((itHeader >> 16) & 0x3FFF) + 1; + switch (itCode) + { + case IT_SET_RESOURCE: // attribute buffers, uniform buffers or texture units + { + cmd = LatteCP_itSetRegistersGeneric<LATTE_REG_BASE_RESOURCE>(cmd, nWords, [&drawPassCtx](uint32 registerStart, uint32 registerEnd) + { + if (registerStart >= Latte::REGADDR::SQ_TEX_RESOURCE_WORD_FIRST && registerStart <= Latte::REGADDR::SQ_TEX_RESOURCE_WORD_LAST) + drawPassCtx.endDrawPass(); // texture updates end the current draw sequence + else if (registerStart >= mmSQ_VTX_ATTRIBUTE_BLOCK_START && registerEnd <= mmSQ_VTX_ATTRIBUTE_BLOCK_END) + drawPassCtx.notifyModifiedVertexBuffer(); + else + drawPassCtx.notifyModifiedUniformBuffer(); + }); + if (!drawPassCtx.isWithinDrawPass()) + return cmd; + break; + } + case IT_SET_ALU_CONST: // uniform register + { + cmd = LatteCP_itSetRegistersGeneric<LATTE_REG_BASE_ALU_CONST>(cmd, nWords); + break; + } + case IT_SET_CTL_CONST: + { + cmd = LatteCP_itSetRegistersGeneric<mmSQ_VTX_BASE_VTX_LOC>(cmd, nWords); + break; + } + case IT_SET_CONFIG_REG: + { + cmd = LatteCP_itSetRegistersGeneric<LATTE_REG_BASE_CONFIG>(cmd, nWords); + break; + } + case IT_INDEX_TYPE: + { + cmd = LatteCP_itIndexType(cmd, nWords); + break; + } + case IT_NUM_INSTANCES: + { + cmd = LatteCP_itNumInstances(cmd, nWords); + break; + } + case IT_DRAW_INDEX_2: + { +#ifdef FAST_DRAW_LOGGING + if(GetAsyncKeyState('A')) + forceLogRemoveMe_printf("Minimal draw"); +#endif + cmd = LatteCP_itDrawIndex2(cmd, nWords, drawPassCtx); + break; + } + case IT_SET_CONTEXT_REG: + { +#ifdef FAST_DRAW_LOGGING + if (GetAsyncKeyState('A')) + forceLogRemoveMe_printf("[FAST-DRAW] Quit due to command IT_SET_CONTEXT_REG Reg: %04x", (uint32)cmd[0] + 0xA000); +#endif + drawPassCtx.endDrawPass(); + return cmdBeforeCommand; + } + case IT_INDIRECT_BUFFER_PRIV: + { + cmd = LatteCP_itIndirectBuffer(cmd, nWords, drawPassCtx); + if (!drawPassCtx.isWithinDrawPass()) + return cmd; + break; + } + default: +#ifdef FAST_DRAW_LOGGING + if (GetAsyncKeyState('A')) + forceLogRemoveMe_printf("[FAST-DRAW] Quit due to command itCode 0x%02x", itCode); +#endif + drawPassCtx.endDrawPass(); + return cmdBeforeCommand; + } + } + else if (itHeaderType == 2) + { + // filler packet + } + else + { +#ifdef FAST_DRAW_LOGGING + if (GetAsyncKeyState('A')) + forceLogRemoveMe_printf("[FAST-DRAW] Quit due to unsupported headerType 0x%02x", itHeaderType); +#endif + drawPassCtx.endDrawPass(); + return cmdBeforeCommand; + } + } + cemu_assert_debug(drawPassCtx.isWithinDrawPass()); + return cmd; +} + +void LatteCP_processCommandBuffer(uint8* cmdBuffer, sint32 cmdSize, DrawPassContext& drawPassCtx) +{ + LatteCMDPtr cmd = (LatteCMDPtr)cmdBuffer; + LatteCMDPtr cmdStart = (LatteCMDPtr)cmdBuffer; + LatteCMDPtr cmdEnd = (LatteCMDPtr)(cmdBuffer + cmdSize); + + if (drawPassCtx.isWithinDrawPass()) + { + cmd = LatteCP_processCommandBuffer_continuousDrawPass(cmd, cmdStart, cmdEnd, drawPassCtx); + cemu_assert_debug(cmd <= cmdEnd); + if (cmd == cmdEnd) + return; + cemu_assert_debug(!drawPassCtx.isWithinDrawPass()); + } + + while (cmd < cmdEnd) + { + uint32 itHeader = LatteReadCMD(); + uint32 itHeaderType = (itHeader >> 30) & 3; + if (itHeaderType == 3) + { + uint32 itCode = (itHeader >> 8) & 0xFF; + uint32 nWords = ((itHeader >> 16) & 0x3FFF) + 1; +#ifndef PUBLIC_RELEASE + LatteCMDPtr expectedPostCmd = cmd + nWords; +#endif + switch (itCode) + { + case IT_SET_CONTEXT_REG: + { + cmd = LatteCP_itSetRegistersGeneric<LATTE_REG_BASE_CONTEXT>(cmd, nWords); + } + break; + case IT_SET_RESOURCE: + { + cmd = LatteCP_itSetRegistersGeneric<LATTE_REG_BASE_RESOURCE>(cmd, nWords); + } + break; + case IT_SET_ALU_CONST: + { + cmd = LatteCP_itSetRegistersGeneric<LATTE_REG_BASE_ALU_CONST>(cmd, nWords); + } + break; + case IT_SET_CTL_CONST: + { + cmd = LatteCP_itSetRegistersGeneric<mmSQ_VTX_BASE_VTX_LOC>(cmd, nWords); + } + break; + case IT_SET_SAMPLER: + { + cmd = LatteCP_itSetRegistersGeneric<LATTE_REG_BASE_SAMPLER>(cmd, nWords); + } + break; + case IT_SET_CONFIG_REG: + { + cmd = LatteCP_itSetRegistersGeneric<LATTE_REG_BASE_CONFIG>(cmd, nWords); + } + break; + case IT_SET_LOOP_CONST: + { + LatteSkipCMD(nWords); + // todo + } + break; + case IT_SURFACE_SYNC: + { + cmd = LatteCP_itSurfaceSync(cmd); + } + break; + case IT_INDIRECT_BUFFER_PRIV: + { + cmd = LatteCP_itIndirectBuffer(cmd, nWords, drawPassCtx); + if (drawPassCtx.isWithinDrawPass()) + { + cmd = LatteCP_processCommandBuffer_continuousDrawPass(cmd, cmdStart, cmdEnd, drawPassCtx); + cemu_assert_debug(cmd <= cmdEnd); + if (cmd == cmdEnd) + return; + cemu_assert_debug(!drawPassCtx.isWithinDrawPass()); + } +#ifndef PUBLIC_RELEASE + expectedPostCmd = cmd; +#endif + } + break; + case IT_STRMOUT_BUFFER_UPDATE: + { + cmd = LatteCP_itStreamoutBufferUpdate(cmd, nWords); + } + break; + case IT_INDEX_TYPE: + { + cmd = LatteCP_itIndexType(cmd, nWords); + } + break; + case IT_NUM_INSTANCES: + { + cmd = LatteCP_itNumInstances(cmd, nWords); + } + break; + case IT_DRAW_INDEX_2: + { + drawPassCtx.beginDrawPass(); +#ifdef FAST_DRAW_LOGGING + if (GetAsyncKeyState('A')) + forceLogRemoveMe_printf("[FAST-DRAW] Starting"); +#endif + cmd = LatteCP_itDrawIndex2(cmd, nWords, drawPassCtx); + cmd = LatteCP_processCommandBuffer_continuousDrawPass(cmd, cmdStart, cmdEnd, drawPassCtx); + cemu_assert_debug(cmd == cmdEnd || drawPassCtx.isWithinDrawPass() == false); // draw sequence should have ended if we didn't reach the end of the command buffer +#ifndef PUBLIC_RELEASE + expectedPostCmd = cmd; +#endif + } + break; + case IT_DRAW_INDEX_AUTO: + { + drawPassCtx.beginDrawPass(); + cmd = LatteCP_itDrawIndexAuto(cmd, nWords, drawPassCtx); + cmd = LatteCP_processCommandBuffer_continuousDrawPass(cmd, cmdStart, cmdEnd, drawPassCtx); + cemu_assert_debug(cmd == cmdEnd || drawPassCtx.isWithinDrawPass() == false); // draw sequence should have ended if we didn't reach the end of the command buffer +#ifndef PUBLIC_RELEASE + expectedPostCmd = cmd; +#endif +#ifdef FAST_DRAW_LOGGING + if (GetAsyncKeyState('A')) + forceLogRemoveMe_printf("[FAST-DRAW] Auto-draw"); +#endif + } + break; + case IT_DRAW_INDEX_IMMD: + { + DrawPassContext drawPassCtx; + drawPassCtx.beginDrawPass(); + cmd = LatteCP_itDrawImmediate(cmd, nWords, drawPassCtx); + drawPassCtx.endDrawPass(); + break; + } + case IT_WAIT_REG_MEM: + { + cmd = LatteCP_itWaitRegMem(cmd, nWords); + LatteTiming_HandleTimedVsync(); + LatteAsyncCommands_checkAndExecute(); + } + break; + case IT_MEM_WRITE: + { + cmd = LatteCP_itMemWrite(cmd, nWords); + } + break; + case IT_CONTEXT_CONTROL: + { + cmd = LatteCP_itContextControl(cmd, nWords); + } + break; + case IT_MEM_SEMAPHORE: + { + cmd = LatteCP_itMemSemaphore(cmd, nWords); + } + break; + case IT_LOAD_CONFIG_REG: + { + cmd = LatteCP_itLoadReg(cmd, nWords, LATTE_REG_BASE_CONFIG); + } + break; + case IT_LOAD_CONTEXT_REG: + { + cmd = LatteCP_itLoadReg(cmd, nWords, LATTE_REG_BASE_CONTEXT); + } + break; + case IT_LOAD_ALU_CONST: + { + cmd = LatteCP_itLoadReg(cmd, nWords, LATTE_REG_BASE_ALU_CONST); + } + break; + case IT_LOAD_LOOP_CONST: + { + cmd = LatteCP_itLoadReg(cmd, nWords, LATTE_REG_BASE_LOOP_CONST); + } + break; + case IT_LOAD_RESOURCE: + { + cmd = LatteCP_itLoadReg(cmd, nWords, LATTE_REG_BASE_RESOURCE); + } + break; + case IT_LOAD_SAMPLER: + { + cmd = LatteCP_itLoadReg(cmd, nWords, LATTE_REG_BASE_SAMPLER); + } + break; + case IT_SET_PREDICATION: + { + cmd = LatteCP_itSetPredication(cmd, nWords); + } + break; + case IT_HLE_COPY_COLORBUFFER_TO_SCANBUFFER: + { + cmd = LatteCP_itHLECopyColorBufferToScanBuffer(cmd, nWords); + } + break; + case IT_HLE_TRIGGER_SCANBUFFER_SWAP: + { + cmd = LatteCP_itHLESwapScanBuffer(cmd, nWords); + } + break; + case IT_HLE_WAIT_FOR_FLIP: + { + cmd = LatteCP_itHLEWaitForFlip(cmd, nWords); + } + break; + case IT_HLE_REQUEST_SWAP_BUFFERS: + { + cmd = LatteCP_itHLERequestSwapBuffers(cmd, nWords); + } + break; + case IT_HLE_CLEAR_COLOR_DEPTH_STENCIL: + { + cmd = LatteCP_itHLEClearColorDepthStencil(cmd, nWords); + } + break; + case IT_HLE_COPY_SURFACE_NEW: + { + cmd = LatteCP_itHLECopySurfaceNew(cmd, nWords); + } + break; + case IT_HLE_SAMPLE_TIMER: + { + cmd = LatteCP_itHLESampleTimer(cmd, nWords); + } + break; + case IT_HLE_SPECIAL_STATE: + { + cmd = LatteCP_itHLESpecialState(cmd, nWords); + } + break; + case IT_HLE_BEGIN_OCCLUSION_QUERY: + { + cmd = LatteCP_itHLEBeginOcclusionQuery(cmd, nWords); + } + break; + case IT_HLE_END_OCCLUSION_QUERY: + { + cmd = LatteCP_itHLEEndOcclusionQuery(cmd, nWords); + } + break; + case IT_HLE_SET_CB_RETIREMENT_TIMESTAMP: + { + cmd = LatteCP_itHLESetRetirementTimestamp(cmd, nWords); + } + break; + case IT_HLE_BOTTOM_OF_PIPE_CB: + { + cmd = LatteCP_itHLEBottomOfPipeCB(cmd, nWords); + } + break; + case IT_HLE_SYNC_ASYNC_OPERATIONS: + { + LatteSkipCMD(nWords); + LatteTextureReadback_UpdateFinishedTransfers(true); + LatteQuery_UpdateFinishedQueriesForceFinishAll(); + } + break; + default: + debug_printf("Unhandled IT %02x\n", itCode); + cemu_assert_debug(false); + LatteSkipCMD(nWords); + } +#ifndef PUBLIC_RELEASE + if(cmd != expectedPostCmd) + debug_printf("cmd %016p expectedPostCmd %016p\n", cmd, expectedPostCmd); + cemu_assert_debug(cmd == expectedPostCmd); +#endif + } + else if (itHeaderType == 2) + { + // filler packet + // has no body + } + else if (itHeaderType == 0) + { + uint32 registerBase = (itHeader & 0xFFFF); + uint32 registerCount = ((itHeader >> 16) & 0x3FFF) + 1; + if (registerBase == 0x304A) + { + GX2::__GX2NotifyEvent(GX2::GX2CallbackEventType::TIMESTAMP_TOP); + LatteSkipCMD(registerCount); + } + else if (registerBase == 0x304B) + { + LatteSkipCMD(registerCount); + } + else + { + LatteCP_dumpCommandBufferError(cmdStart, cmdEnd, cmd); + cemu_assert_debug(false); + } + } + else + { + debug_printf("invalid itHeaderType %08x\n", itHeaderType); + LatteCP_dumpCommandBufferError(cmdStart, cmdEnd, cmd); + cemu_assert_debug(false); + } + } + cemu_assert_debug(cmd == cmdEnd); +} + +void LatteCP_ProcessRingbuffer() +{ + sint32 timerRecheck = 0; // estimates how much CP processing time passed based on the executed commands, if the value exceeds CP_TIMER_RECHECK then _handleTimers() is called + while (true) + { + uint32 itHeader = LatteCP_readU32Deprc(); + uint32 itHeaderType = (itHeader >> 30) & 3; + if (itHeaderType == 3) + { + uint32 itCode = (itHeader >> 8) & 0xFF; + uint32 nWords = ((itHeader >> 16) & 0x3FFF) + 1; + LatteCP_waitForNWords(nWords); + LatteCMDPtr cmd = (LatteCMDPtr)gxRingBufferReadPtr; + uint8* expectedGxRingBufferReadPtr = gxRingBufferReadPtr + nWords*4; + switch (itCode) + { + case IT_SURFACE_SYNC: + { + gxRingBufferReadPtr = (uint8*)LatteCP_itSurfaceSync(cmd); + timerRecheck += CP_TIMER_RECHECK / 512; + } + break; + case IT_SET_CONTEXT_REG: + { + gxRingBufferReadPtr = (uint8*)LatteCP_itSetRegistersGeneric<LATTE_REG_BASE_CONTEXT>(cmd, nWords); + timerRecheck += CP_TIMER_RECHECK / 512; + } + break; + case IT_SET_RESOURCE: + { + gxRingBufferReadPtr = (uint8*)LatteCP_itSetRegistersGeneric<LATTE_REG_BASE_RESOURCE>(cmd, nWords); + timerRecheck += CP_TIMER_RECHECK / 512; + } + break; + case IT_SET_ALU_CONST: + { + gxRingBufferReadPtr = (uint8*)LatteCP_itSetRegistersGeneric<LATTE_REG_BASE_ALU_CONST>(cmd, nWords); + timerRecheck += CP_TIMER_RECHECK / 512; + break; + } + case IT_SET_CTL_CONST: + { + gxRingBufferReadPtr = (uint8*)LatteCP_itSetRegistersGeneric<mmSQ_VTX_BASE_VTX_LOC>(cmd, nWords); + timerRecheck += CP_TIMER_RECHECK / 512; + break; + } + case IT_SET_SAMPLER: + { + gxRingBufferReadPtr = (uint8*)LatteCP_itSetRegistersGeneric<LATTE_REG_BASE_SAMPLER>(cmd, nWords); + timerRecheck += CP_TIMER_RECHECK / 512; + break; + } + case IT_SET_CONFIG_REG: + { + gxRingBufferReadPtr = (uint8*)LatteCP_itSetRegistersGeneric<LATTE_REG_BASE_CONFIG>(cmd, nWords); + timerRecheck += CP_TIMER_RECHECK / 512; + break; + } + case IT_INDIRECT_BUFFER_PRIV: + { +#ifdef FAST_DRAW_LOGGING + if (GetAsyncKeyState('A')) + forceLogRemoveMe_printf("[FAST-DRAW] BEGIN CMD BUFFER"); +#endif + LatteCP_itIndirectBufferDepr<LatteCP_readU32Deprc>(nWords); + timerRecheck += CP_TIMER_RECHECK / 512; +#ifdef FAST_DRAW_LOGGING + if (GetAsyncKeyState('A')) + forceLogRemoveMe_printf("[FAST-DRAW] END CMD BUFFER"); +#endif + break; + } + case IT_STRMOUT_BUFFER_UPDATE: + { + gxRingBufferReadPtr = (uint8*)LatteCP_itStreamoutBufferUpdate(cmd, nWords); + timerRecheck += CP_TIMER_RECHECK / 512; + break; + } + case IT_INDEX_TYPE: + { + gxRingBufferReadPtr = (uint8*)LatteCP_itIndexType(cmd, nWords); + timerRecheck += CP_TIMER_RECHECK / 1024; + break; + } + case IT_NUM_INSTANCES: + { + gxRingBufferReadPtr = (uint8*)LatteCP_itNumInstances(cmd, nWords); + timerRecheck += CP_TIMER_RECHECK / 1024; + break; + } + case IT_DRAW_INDEX_2: + { + DrawPassContext drawPassCtx; + drawPassCtx.beginDrawPass(); + gxRingBufferReadPtr = (uint8*)LatteCP_itDrawIndex2(cmd, nWords, drawPassCtx); + drawPassCtx.endDrawPass(); + timerRecheck += CP_TIMER_RECHECK / 64; + break; + } + case IT_DRAW_INDEX_AUTO: + { + DrawPassContext drawPassCtx; + drawPassCtx.beginDrawPass(); + gxRingBufferReadPtr = (uint8*)LatteCP_itDrawIndexAuto(cmd, nWords, drawPassCtx); + drawPassCtx.endDrawPass(); + timerRecheck += CP_TIMER_RECHECK / 512; + break; + } + case IT_DRAW_INDEX_IMMD: + { + DrawPassContext drawPassCtx; + drawPassCtx.beginDrawPass(); + gxRingBufferReadPtr = (uint8*)LatteCP_itDrawImmediate(cmd, nWords, drawPassCtx); + drawPassCtx.endDrawPass(); + timerRecheck += CP_TIMER_RECHECK / 64; + break; + } + case IT_WAIT_REG_MEM: + { + gxRingBufferReadPtr = (uint8*)LatteCP_itWaitRegMem(cmd, nWords); + timerRecheck += CP_TIMER_RECHECK / 16; + break; + } + case IT_MEM_WRITE: + { + gxRingBufferReadPtr = (uint8*)LatteCP_itMemWrite(cmd, nWords); + timerRecheck += CP_TIMER_RECHECK / 128; + break; + } + case IT_CONTEXT_CONTROL: + { + gxRingBufferReadPtr = (uint8*)LatteCP_itContextControl(cmd, nWords); + timerRecheck += CP_TIMER_RECHECK / 128; + break; + } + case IT_MEM_SEMAPHORE: + { + gxRingBufferReadPtr = (uint8*)LatteCP_itMemSemaphore(cmd, nWords); + timerRecheck += CP_TIMER_RECHECK / 128; + break; + } + case IT_LOAD_CONFIG_REG: + { + gxRingBufferReadPtr = (uint8*)LatteCP_itLoadReg(cmd, nWords, LATTE_REG_BASE_CONFIG); + timerRecheck += CP_TIMER_RECHECK / 64; + break; + } + case IT_LOAD_CONTEXT_REG: + { + gxRingBufferReadPtr = (uint8*)LatteCP_itLoadReg(cmd, nWords, LATTE_REG_BASE_CONTEXT); + timerRecheck += CP_TIMER_RECHECK / 64; + break; + } + case IT_LOAD_ALU_CONST: + { + gxRingBufferReadPtr = (uint8*)LatteCP_itLoadReg(cmd, nWords, LATTE_REG_BASE_ALU_CONST); + timerRecheck += CP_TIMER_RECHECK / 64; + break; + } + case IT_LOAD_LOOP_CONST: + { + gxRingBufferReadPtr = (uint8*)LatteCP_itLoadReg(cmd, nWords, LATTE_REG_BASE_LOOP_CONST); + timerRecheck += CP_TIMER_RECHECK / 64; + break; + } + case IT_LOAD_RESOURCE: + { + gxRingBufferReadPtr = (uint8*)LatteCP_itLoadReg(cmd, nWords, LATTE_REG_BASE_RESOURCE); + timerRecheck += CP_TIMER_RECHECK / 64; + break; + } + case IT_LOAD_SAMPLER: + { + gxRingBufferReadPtr = (uint8*)LatteCP_itLoadReg(cmd, nWords, LATTE_REG_BASE_SAMPLER); + timerRecheck += CP_TIMER_RECHECK / 64; + break; + } + case IT_SET_LOOP_CONST: + { + LatteSkipCMD(nWords); + gxRingBufferReadPtr = (uint8*)cmd; + // todo + break; + } + case IT_SET_PREDICATION: + { + gxRingBufferReadPtr = (uint8*)LatteCP_itSetPredication(cmd, nWords); + timerRecheck += CP_TIMER_RECHECK / 512; + break; + } + case IT_HLE_COPY_COLORBUFFER_TO_SCANBUFFER: + { + gxRingBufferReadPtr = (uint8*)LatteCP_itHLECopyColorBufferToScanBuffer(cmd, nWords); + timerRecheck += CP_TIMER_RECHECK / 64; + break; + } + case IT_HLE_TRIGGER_SCANBUFFER_SWAP: + { + gxRingBufferReadPtr = (uint8*)LatteCP_itHLESwapScanBuffer(cmd, nWords); + timerRecheck += CP_TIMER_RECHECK / 64; + break; + } + case IT_HLE_WAIT_FOR_FLIP: + { + gxRingBufferReadPtr = (uint8*)LatteCP_itHLEWaitForFlip(cmd, nWords); + timerRecheck += CP_TIMER_RECHECK / 1; + break; + } + case IT_HLE_REQUEST_SWAP_BUFFERS: + { + gxRingBufferReadPtr = (uint8*)LatteCP_itHLERequestSwapBuffers(cmd, nWords); + timerRecheck += CP_TIMER_RECHECK / 32; + break; + } + case IT_HLE_CLEAR_COLOR_DEPTH_STENCIL: + { + gxRingBufferReadPtr = (uint8*)LatteCP_itHLEClearColorDepthStencil(cmd, nWords); + timerRecheck += CP_TIMER_RECHECK / 128; + break; + } + case IT_HLE_COPY_SURFACE_NEW: + { + gxRingBufferReadPtr = (uint8*)LatteCP_itHLECopySurfaceNew(cmd, nWords); + timerRecheck += CP_TIMER_RECHECK / 128; + break; + } + case IT_HLE_FIFO_WRAP_AROUND: + { + gxRingBufferReadPtr = (uint8*)LatteCP_itHLEFifoWrapAround(cmd, nWords); + expectedGxRingBufferReadPtr = gxRingBufferReadPtr; + timerRecheck += CP_TIMER_RECHECK / 512; + break; + } + case IT_HLE_SAMPLE_TIMER: + { + gxRingBufferReadPtr = (uint8*)LatteCP_itHLESampleTimer(cmd, nWords); + timerRecheck += CP_TIMER_RECHECK / 512; + break; + } + case IT_HLE_SPECIAL_STATE: + { + gxRingBufferReadPtr = (uint8*)LatteCP_itHLESpecialState(cmd, nWords); + timerRecheck += CP_TIMER_RECHECK / 512; + break; + } + case IT_HLE_BEGIN_OCCLUSION_QUERY: + { + gxRingBufferReadPtr = (uint8*)LatteCP_itHLEBeginOcclusionQuery(cmd, nWords); + timerRecheck += CP_TIMER_RECHECK / 512; + break; + } + case IT_HLE_END_OCCLUSION_QUERY: + { + gxRingBufferReadPtr = (uint8*)LatteCP_itHLEEndOcclusionQuery(cmd, nWords); + timerRecheck += CP_TIMER_RECHECK / 512; + break; + } + case IT_HLE_SET_CB_RETIREMENT_TIMESTAMP: + { + gxRingBufferReadPtr = (uint8*)LatteCP_itHLESetRetirementTimestamp(cmd, nWords); + timerRecheck += CP_TIMER_RECHECK / 512; + break; + } + case IT_HLE_BOTTOM_OF_PIPE_CB: + { + gxRingBufferReadPtr = (uint8*)LatteCP_itHLEBottomOfPipeCB(cmd, nWords); + break; + } + case IT_HLE_SYNC_ASYNC_OPERATIONS: + { + LatteCP_skipWords<LatteCP_readU32Deprc>(nWords); + LatteTextureReadback_UpdateFinishedTransfers(true); + LatteQuery_UpdateFinishedQueriesForceFinishAll(); + break; + } + default: + cemu_assert_debug(false); + } + cemu_assert_debug(expectedGxRingBufferReadPtr == gxRingBufferReadPtr); + } + else if (itHeaderType == 2) + { + // filler packet, skip this + cemu_assert_debug(itHeader == 0x80000000); + } + else if (itHeaderType == 0) + { + uint32 registerBase = (itHeader & 0xFFFF); + uint32 registerCount = ((itHeader >> 16) & 0x3FFF) + 1; + if (registerBase == 0x304A) + { + GX2::__GX2NotifyEvent(GX2::GX2CallbackEventType::TIMESTAMP_TOP); + LatteCP_skipWords<LatteCP_readU32Deprc>(registerCount); + } + else if (registerBase == 0x304B) + { + LatteCP_skipWords<LatteCP_readU32Deprc>(registerCount); + } + else + { + cemu_assert_debug(false); + } + } + else + { + debug_printf("invalid itHeaderType %08x\n", itHeaderType); + cemu_assert_debug(false); + } + if (timerRecheck >= CP_TIMER_RECHECK) + { + LatteTiming_HandleTimedVsync(); + LatteAsyncCommands_checkAndExecute(); + timerRecheck = 0; + } + } +} diff --git a/src/Cafe/HW/Latte/Core/LatteConst.h b/src/Cafe/HW/Latte/Core/LatteConst.h new file mode 100644 index 00000000..4cd92977 --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteConst.h @@ -0,0 +1,177 @@ +#pragma once +#include "Cafe/HW/Latte/ISA/LatteReg.h" + +// this file contains legacy C-style defines, modernize and merge into LatteReg.h + +// GPU7/Latte hardware info + +#define LATTE_NUM_GPR (128) +#define LATTE_NUM_STREAMOUT_BUFFER (4) +#define LATTE_NUM_COLOR_TARGET (8) + +#define LATTE_NUM_MAX_TEX_UNITS (18) // number of available texture units per shader stage (this might be higher than 18? BotW is the only game which uses more than 16?) +#define LATTE_NUM_MAX_UNIFORM_BUFFERS (16) // number of supported uniform buffer binding locations + +#define LATTE_VS_ATTRIBUTE_LIMIT (32) // todo: verify +#define LATTE_NUM_MAX_ATTRIBUTE_LOCATIONS (256) // should this be 128 since there are only 128 GPRs? + +#define LATTE_MAX_VERTEX_BUFFERS (16) + +// vertex formats + +#define FMT_INVALID 0x00 +#define FMT_8 0x01 +#define FMT_4_4 0x02 +#define FMT_3_3_2 0x03 +#define FMT_16 0x05 +#define FMT_16_FLOAT 0x06 +#define FMT_8_8 0x07 +#define FMT_5_6_5 0x08 +#define FMT_6_5_5 0x09 +#define FMT_1_5_5_5 0x0A +#define FMT_4_4_4_4 0x0B +#define FMT_5_5_5_1 0x0C +#define FMT_32 0x0D +#define FMT_32_FLOAT 0x0E +#define FMT_16_16 0x0F +#define FMT_16_16_FLOAT 0x10 +#define FMT_8_24 0x11 +#define FMT_8_24_FLOAT 0x12 +#define FMT_24_8 0x13 +#define FMT_24_8_FLOAT 0x14 +#define FMT_10_11_11 0x15 +#define FMT_10_11_11_FLOAT 0x16 +#define FMT_11_11_10 0x17 +#define FMT_11_11_10_FLOAT 0x18 +#define FMT_2_10_10_10 0x19 +#define FMT_8_8_8_8 0x1A +#define FMT_10_10_10_2 0x1B +#define FMT_X24_8_32_FLOAT 0x1C +#define FMT_32_32 0x1D +#define FMT_32_32_FLOAT 0x1E +#define FMT_16_16_16_16 0x1F +#define FMT_16_16_16_16_FLOAT 0x20 +#define FMT_32_32_32_32 0x22 +#define FMT_32_32_32_32_FLOAT 0x23 +#define FMT_1 0x25 +#define FMT_GB_GR 0x27 +#define FMT_BG_RG 0x28 +#define FMT_32_AS_8 0x29 +#define FMT_32_AS_8_8 0x2A +#define FMT_5_9_9_9_SHAREDEXP 0x2B +#define FMT_8_8_8 0x2C +#define FMT_16_16_16 0x2D +#define FMT_16_16_16_FLOAT 0x2E +#define FMT_32_32_32 0x2F +#define FMT_32_32_32_FLOAT 0x30 + +#define LATTE_NFA_2 2 +#define LATTE_NFA_3 3 + +#define LATTE_VTX_UNSIGNED 0 +#define LATTE_VTX_SIGNED 1 + +// OpenGL constants + +#define GLVENDOR_UNKNOWN (0) +#define GLVENDOR_AMD (1) // AMD/ATI +#define GLVENDOR_NVIDIA (2) +#define GLVENDOR_INTEL_LEGACY (3) +#define GLVENDOR_INTEL_NOLEGACY (4) +#define GLVENDOR_INTEL (5) + +// decompiler + +#define LATTE_DECOMPILER_DTYPE_UNDETERMINED (0) // data type is unknown +#define LATTE_DECOMPILER_DTYPE_UNSIGNED_INT (1) // 32bit unsigned integer +#define LATTE_DECOMPILER_DTYPE_SIGNED_INT (2) // 32bit signed integer +#define LATTE_DECOMPILER_DTYPE_FLOAT (3) // 32bit IEEE float + +#define LATTE_DECOMPILER_UNIFORM_MODE_NONE (0) // no uniform access at all +#define LATTE_DECOMPILER_UNIFORM_MODE_REMAPPED (1) // use remapped uniform array +#define LATTE_DECOMPILER_UNIFORM_MODE_FULL_CFILE (2) // load full cfile (uniform registers) +#define LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK (3) // load full uniform banks (uniform buffers) + +#define LATTE_ANALYZER_IMPORT_INDEX_PARAM_MAX (0xFF) +#define LATTE_ANALYZER_IMPORT_INDEX_SPIPOSITION (0x40000000) // gl_FragCoord + +#define LATTE_DECOMPILER_SAMPLER_NONE (0xFF) + +using LattePrimitiveMode = Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE; +using LatteIndexType = Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE; + +namespace LatteConst +{ + enum class ShaderType : uint32 + { + Reserved = 0, + // shaders for drawing + FirstRender = 1, + Vertex = 1, + Pixel = 2, + Geometry = 3, + LastRender = 3, + // compute shader + Compute = 4, + TotalCount = 5 + }; + + enum class VertexFetchNFA + { + NUM_FORMAT_NORMALIZED, + NUM_FORMAT_INT, + NUM_FORMAT_SCALED, + }; + + enum class VertexFetchEndianMode + { + SWAP_NONE = 0, // little endian + SWAP_U16 = 1, // U16 big endian + SWAP_U32 = 2, // U32 big endian + // helper for GX2 API + SWAP_DEFAULT = 3, + }; + + enum class VertexFetchFormat : uint32 + { + // some formats are for texture fetches only + + VTX_FMT_INVALID = 0x00, + + VTX_FMT_8 = 0x01, + VTX_FMT_8_8 = 0x07, + VTX_FMT_8_8_8 = 0x2C, + VTX_FMT_8_8_8_8 = 0x1A, + + VTX_FMT_32_32 = 0x1D, + VTX_FMT_32_32_FLOAT = 0x1E, + + VTX_FMT_16_16_16 = 0x2D, + VTX_FMT_16_16_16_FLOAT = 0x2E, + VTX_FMT_32_32_32 = 0x2F, + VTX_FMT_32_32_32_FLOAT = 0x30 + }; + + enum class VertexFetchDstSel : uint8 + { + X = 0, + Y = 1, + Z = 2, + W = 3, + CONST_0F = 4, + CONST_1F = 5, + UNUSED = 6, + MASKED = 7 + }; + + // used in VTX_WORD0 + enum VertexFetchType2 : uint8 + { + VERTEX_DATA = 0, + INSTANCE_DATA = 1, + NO_INDEX_OFFSET_DATA = 2, + }; + +}; + +#define LATTE_MAX_REGISTER (0x10000) \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Core/LatteDefaultShaders.cpp b/src/Cafe/HW/Latte/Core/LatteDefaultShaders.cpp new file mode 100644 index 00000000..7c590dbb --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteDefaultShaders.cpp @@ -0,0 +1,87 @@ +#include "Cafe/HW/Latte/Core/Latte.h" +#include "Cafe/HW/Latte/Core/LatteDraw.h" +#include "Cafe/HW/Latte/Core/LatteShader.h" +#include "Cafe/HW/Latte/Core/LatteDefaultShaders.h" +#include "util/helpers/StringBuf.h" + +LatteDefaultShader_t* _copyShader_depthToColor; +LatteDefaultShader_t* _copyShader_colorToDepth; + +void LatteDefaultShader_pixelCopyShader_generateVSBody(StringBuf* vs) +{ + vs->add("#version 420\r\n"); + vs->add("out vec2 passUV;\r\n"); + vs->add("uniform vec4 uf_vertexOffsets[4];\r\n"); + vs->add("\r\n"); + vs->add("void main(){\r\n"); + vs->add("int vID = gl_VertexID;\r\n"); + vs->add("passUV = uf_vertexOffsets[vID].zw;\r\n"); + vs->add("gl_Position = vec4(uf_vertexOffsets[vID].xy, 0.0, 1.0);\r\n"); + vs->add("}\r\n"); +} + +GLuint gxShaderDepr_compileRaw(StringBuf* strSourceVS, StringBuf* strSourceFS); +GLuint gxShaderDepr_compileRaw(const std::string& vertex_source, const std::string& fragment_source); + +LatteDefaultShader_t* LatteDefaultShader_getPixelCopyShader_depthToColor() +{ + if (_copyShader_depthToColor != 0) + return _copyShader_depthToColor; + catchOpenGLError(); + LatteDefaultShader_t* defaultShader = (LatteDefaultShader_t*)malloc(sizeof(LatteDefaultShader_t)); + memset(defaultShader, 0, sizeof(LatteDefaultShader_t)); + + StringBuf fCStr_vertexShader(1024 * 16); + LatteDefaultShader_pixelCopyShader_generateVSBody(&fCStr_vertexShader); + + StringBuf fCStr_defaultFragShader(1024 * 16); + fCStr_defaultFragShader.add("#version 420\r\n"); + fCStr_defaultFragShader.add("in vec2 passUV;\r\n"); + fCStr_defaultFragShader.add("uniform sampler2D textureSrc;\r\n"); + fCStr_defaultFragShader.add("layout(location = 0) out vec4 colorOut0;\r\n"); + fCStr_defaultFragShader.add("\r\n"); + fCStr_defaultFragShader.add("void main(){\r\n"); + fCStr_defaultFragShader.add("colorOut0 = vec4(texture(textureSrc, passUV).r,0.0,0.0,1.0);\r\n"); + fCStr_defaultFragShader.add("}\r\n"); + + defaultShader->glProgamId = gxShaderDepr_compileRaw(&fCStr_vertexShader, &fCStr_defaultFragShader); + catchOpenGLError(); + + defaultShader->copyShaderUniforms.uniformLoc_textureSrc = glGetUniformLocation(defaultShader->glProgamId, "textureSrc"); + defaultShader->copyShaderUniforms.uniformLoc_vertexOffsets = glGetUniformLocation(defaultShader->glProgamId, "uf_vertexOffsets"); + + _copyShader_depthToColor = defaultShader; + catchOpenGLError(); + return defaultShader; +} + +LatteDefaultShader_t* LatteDefaultShader_getPixelCopyShader_colorToDepth() +{ + if (_copyShader_colorToDepth != 0) + return _copyShader_colorToDepth; + catchOpenGLError(); + LatteDefaultShader_t* defaultShader = (LatteDefaultShader_t*)malloc(sizeof(LatteDefaultShader_t)); + memset(defaultShader, 0, sizeof(LatteDefaultShader_t)); + + StringBuf fCStr_vertexShader(1024 * 16); + LatteDefaultShader_pixelCopyShader_generateVSBody(&fCStr_vertexShader); + + StringBuf fCStr_defaultFragShader(1024 * 16); + fCStr_defaultFragShader.add("#version 420\r\n"); + fCStr_defaultFragShader.add("in vec2 passUV;\r\n"); + fCStr_defaultFragShader.add("uniform sampler2D textureSrc;\r\n"); + fCStr_defaultFragShader.add("layout(location = 0) out vec4 colorOut0;\r\n"); + fCStr_defaultFragShader.add("\r\n"); + fCStr_defaultFragShader.add("void main(){\r\n"); + fCStr_defaultFragShader.add("gl_FragDepth = texture(textureSrc, passUV).r;\r\n"); + fCStr_defaultFragShader.add("}\r\n"); + + + defaultShader->glProgamId = gxShaderDepr_compileRaw(&fCStr_vertexShader, &fCStr_defaultFragShader); + defaultShader->copyShaderUniforms.uniformLoc_textureSrc = glGetUniformLocation(defaultShader->glProgamId, "textureSrc"); + defaultShader->copyShaderUniforms.uniformLoc_vertexOffsets = glGetUniformLocation(defaultShader->glProgamId, "uf_vertexOffsets"); + + _copyShader_colorToDepth = defaultShader; + catchOpenGLError(); + return defaultShader; +} \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Core/LatteDefaultShaders.h b/src/Cafe/HW/Latte/Core/LatteDefaultShaders.h new file mode 100644 index 00000000..3776a3d2 --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteDefaultShaders.h @@ -0,0 +1,13 @@ + +typedef struct +{ + GLuint glProgamId; + struct + { + GLuint uniformLoc_textureSrc; + GLuint uniformLoc_vertexOffsets; + }copyShaderUniforms; +}LatteDefaultShader_t; + +LatteDefaultShader_t* LatteDefaultShader_getPixelCopyShader_depthToColor(); +LatteDefaultShader_t* LatteDefaultShader_getPixelCopyShader_colorToDepth(); \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Core/LatteDraw.h b/src/Cafe/HW/Latte/Core/LatteDraw.h new file mode 100644 index 00000000..89c34dc0 --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteDraw.h @@ -0,0 +1,4 @@ +#pragma once +#include "Common/GLInclude/GLInclude.h" + +void LatteDraw_cleanupAfterFrame(); \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Core/LatteGSCopyShaderParser.cpp b/src/Cafe/HW/Latte/Core/LatteGSCopyShaderParser.cpp new file mode 100644 index 00000000..58d3b635 --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteGSCopyShaderParser.cpp @@ -0,0 +1,249 @@ +#include "Cafe/HW/Latte/Core/LatteConst.h" +#include "Cafe/HW/Latte/Core/LatteShaderAssembly.h" +#include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h" + +void LatteGSCopyShaderParser_addFetchedParam(LatteParsedGSCopyShader* shaderContext, uint32 offset, uint32 gprIndex) +{ + if( shaderContext->numParam >= GPU7_COPY_SHADER_MAX_PARAMS ) + { + debug_printf("Copy shader: Too many fetched parameters\n"); + cemu_assert_suspicious(); + return; + } + shaderContext->paramMapping[shaderContext->numParam].exportParam = 0xFF; + shaderContext->paramMapping[shaderContext->numParam].offset = offset; + shaderContext->paramMapping[shaderContext->numParam].gprIndex = gprIndex; + shaderContext->numParam++; +} + +void LatteGSCopyShaderParser_assignRegisterParameterOutput(LatteParsedGSCopyShader* shaderContext, uint32 gprIndex, uint32 exportType, uint32 exportParam) +{ + // scan backwards to catch the most recently added entry in case a register has multiple entries + for(sint32 i=shaderContext->numParam-1; i>=0; i--) + { + if( shaderContext->paramMapping[i].gprIndex == gprIndex ) + { + if( shaderContext->paramMapping[i].exportParam != 0xFF ) + cemu_assert_debug(false); + if( exportParam >= 0x100 ) + cemu_assert_debug(false); + shaderContext->paramMapping[i].exportType = (uint8)exportType; + shaderContext->paramMapping[i].exportParam = (uint8)exportParam; + return; + } + } + cemu_assert_debug(false); // register is exported but never initialized? +} + +void LatteGSCopyShaderParser_addStreamWrite(LatteParsedGSCopyShader* shaderContext, uint32 bufferIndex, uint32 exportSourceGPR, uint32 exportArrayBase, uint32 memWriteArraySize, uint32 memWriteCompMask) +{ + // get info about current state of GPR + for (sint32 i = shaderContext->numParam - 1; i >= 0; i--) + { + if (shaderContext->paramMapping[i].gprIndex == exportSourceGPR) + { + LatteGSCopyShaderStreamWrite_t streamWrite; + streamWrite.bufferIndex = (uint8)bufferIndex; + streamWrite.offset = shaderContext->paramMapping[i].offset; + streamWrite.exportArrayBase = exportArrayBase; + streamWrite.memWriteArraySize = memWriteArraySize; + streamWrite.memWriteCompMask = memWriteCompMask; + shaderContext->list_streamWrites.push_back(streamWrite); + return; + } + } + cemu_assert_debug(false); // GPR not initialized? +} + +bool LatteGSCopyShaderParser_getExportTypeByOffset(LatteParsedGSCopyShader* shaderContext, uint32 offset, uint32* exportType, uint32* exportParam) +{ + for(sint32 i=0; i<shaderContext->numParam; i++) + { + if( shaderContext->paramMapping[i].offset == offset ) + { + *exportType = shaderContext->paramMapping[i].exportType; + *exportParam = shaderContext->paramMapping[i].exportParam; + return true; + } + } + return false; +} + +bool LatteGSCopyShaderParser_parseClauseVtx(LatteParsedGSCopyShader* shaderContext, uint8* programData, uint32 programSize, uint32 addr, uint32 count) +{ + for(uint32 i=0; i<count; i++) + { + uint32 instructionAddr = addr*2+i*4; + uint32 word0 = *(uint32*)(programData+instructionAddr*4+0); + uint32 word1 = *(uint32*)(programData+instructionAddr*4+4); + uint32 word2 = *(uint32*)(programData+instructionAddr*4+8); + uint32 word3 = *(uint32*)(programData+instructionAddr*4+12); + uint32 inst0_4 = (word0>>0)&0x1F; + if( inst0_4 == GPU7_TEX_INST_VFETCH ) + { + // data fetch + uint32 fetchType = (word0>>5)&3; + uint32 bufferId = (word0>>8)&0xFF; + uint32 offset = (word2>>0)&0xFFFF; + uint32 endianSwap = (word2>>16)&0x3; + uint32 constNoStride = (word2>>18)&0x1; + uint32 srcGpr = (word0>>16)&0x7F; + uint32 srcRel = (word0>>23)&1; + if( srcRel != 0 ) + debugBreakpoint(); + uint32 destGpr = (word1>>0)&0x7F; + uint32 destRel = (word1>>7)&1; + if( destRel != 0 ) + debugBreakpoint(); + uint32 dstSelX = (word1>>9)&0x7; + uint32 dstSelY = (word1>>12)&0x7; + uint32 dstSelZ = (word1>>15)&0x7; + uint32 dstSelW = (word1>>18)&0x7; + + uint32 srcSelX = (word0>>24)&0x3; + uint32 srcSelY = 0; + uint32 srcSelZ = 0; + uint32 srcSelW = 0; + + if( bufferId != 0x9F ) + { + debugBreakpoint(); // data not fetched from GS ring buffer + return false; + } + if( endianSwap != 0 ) + debugBreakpoint(); + if( fetchType != 2 ) + debugBreakpoint(); + if( srcSelX != 0 || srcGpr != 0 ) + debugBreakpoint(); + if( dstSelX != 0 || dstSelY != 1 || dstSelZ != 2 || dstSelW != 3 ) + debugBreakpoint(); + // remember imported parameter + LatteGSCopyShaderParser_addFetchedParam(shaderContext, offset, destGpr); + } + else + { + return false; + } + } + return true; +} + +LatteParsedGSCopyShader* LatteGSCopyShaderParser_parse(uint8* programData, uint32 programSize) +{ + cemu_assert_debug((programSize & 3) == 0); + LatteParsedGSCopyShader* shaderContext = new LatteParsedGSCopyShader(); + shaderContext->numParam = 0; + // parse control flow instructions + for(uint32 i=0; i<programSize/8; i++) + { + uint32 cfWord0 = *(uint32*)(programData+i*8+0); + uint32 cfWord1 = *(uint32*)(programData+i*8+4); + uint32 cf_inst23_7 = (cfWord1>>23)&0x7F; + // check the bigger opcode fields first + if( cf_inst23_7 < 0x40 ) // at 0x40 the bits overlap with the ALU instruction encoding + { + bool isEndOfProgram = ((cfWord1>>21)&1)!=0; + uint32 addr = cfWord0&0xFFFFFFFF; + uint32 count = (cfWord1>>10)&7; + if( ((cfWord1>>19)&1) != 0 ) + count |= 0x8; + count++; + if( cf_inst23_7 == GPU7_CF_INST_CALL_FS ) + { + // nop + } + else if( cf_inst23_7 == GPU7_CF_INST_NOP ) + { + // nop + if( ((cfWord1>>0)&7) != 0 ) + debugBreakpoint(); // pop count is not zero, + } + else if( cf_inst23_7 == GPU7_CF_INST_EXPORT || cf_inst23_7 == GPU7_CF_INST_EXPORT_DONE ) + { + // export + uint32 edType = (cfWord0>>13)&0x3; + uint32 edIndexGpr = (cfWord0>>23)&0x7F; + uint32 edRWRel = (cfWord0>>22)&1; + if( edRWRel != 0 || edIndexGpr != 0 ) + debugBreakpoint(); + // set export component selection + uint8 exportComponentSel[4]; + exportComponentSel[0] = (cfWord1>>0)&0x7; + exportComponentSel[1] = (cfWord1>>3)&0x7; + exportComponentSel[2] = (cfWord1>>6)&0x7; + exportComponentSel[3] = (cfWord1>>9)&0x7; + // set export array base, index and burstcount (export field) + uint32 exportArrayBase = (cfWord0>>0)&0x1FFF; + uint32 exportBurstCount = (cfWord1>>17)&0xF; + // set export source GPR and type + uint32 exportSourceGPR = (cfWord0>>15)&0x7F; + uint32 exportType = edType; + if (exportArrayBase == GPU7_DECOMPILER_CF_EXPORT_BASE_POSITION && exportComponentSel[0] == 4 && exportComponentSel[1] == 4 && exportComponentSel[2] == 4 && exportComponentSel[3] == 4) + { + // aka gl_Position = vec4(0.0) + // this instruction form is generated when the original shader doesn't assign gl_Position a value? + } + else if (exportComponentSel[0] != 0 || exportComponentSel[1] != 1 || exportComponentSel[2] != 2 || exportComponentSel[3] != 3) + { + cemu_assert_debug(false); + } + else + { + // register as param + for (uint32 f = 0; f < exportBurstCount + 1; f++) + { + LatteGSCopyShaderParser_assignRegisterParameterOutput(shaderContext, exportSourceGPR + f, exportType, exportArrayBase + f); + } + } + } + else if( cf_inst23_7 == GPU7_CF_INST_VTX ) + { + LatteGSCopyShaderParser_parseClauseVtx(shaderContext, programData, programSize, addr, count); + } + else if (cf_inst23_7 == GPU7_CF_INST_MEM_STREAM0_WRITE || + cf_inst23_7 == GPU7_CF_INST_MEM_STREAM1_WRITE ) + { + // streamout + uint32 bufferIndex; + if (cf_inst23_7 == GPU7_CF_INST_MEM_STREAM0_WRITE) + bufferIndex = 0; + else if (cf_inst23_7 == GPU7_CF_INST_MEM_STREAM0_WRITE) + bufferIndex = 1; + else + cemu_assert_debug(false); + + uint32 exportArrayBase = (cfWord0 >> 0) & 0x1FFF; + uint32 memWriteArraySize = (cfWord1 >> 0) & 0xFFF; + uint32 memWriteCompMask = (cfWord1 >> 12) & 0xF; + uint32 exportSourceGPR = (cfWord0 >> 15) & 0x7F; + + LatteGSCopyShaderParser_addStreamWrite(shaderContext, bufferIndex, exportSourceGPR, exportArrayBase, memWriteArraySize, memWriteCompMask); + } + else + { + forceLog_printf("Copyshader: Unknown 23_7 clause 0x%x found\n", cf_inst23_7); + cemu_assert_debug(false); + } + if( isEndOfProgram ) + { + break; + } + } + else + { + // ALU clauses not supported + debug_printf("Copyshader has ALU clause?\n"); + cemu_assert_debug(false); + delete shaderContext; + return nullptr; + } + } + // verify if all registers are exported + for(sint32 i=0; i<shaderContext->numParam; i++) + { + if( shaderContext->paramMapping[i].exportParam == 0xFFFF ) + debugBreakpoint(); + } + return shaderContext; +} diff --git a/src/Cafe/HW/Latte/Core/LatteIndices.cpp b/src/Cafe/HW/Latte/Core/LatteIndices.cpp new file mode 100644 index 00000000..2ffd1d0a --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteIndices.cpp @@ -0,0 +1,730 @@ +#include "Cafe/HW/Latte/Core/LatteConst.h" +#include "Cafe/HW/Latte/Renderer/Renderer.h" + +#include "Cafe/HW/Latte/ISA/RegDefines.h" + +#if BOOST_OS_LINUX +#include <immintrin.h> +#endif + +struct +{ + const void* lastPtr; + uint32 lastCount; + LattePrimitiveMode lastPrimitiveMode; + LatteIndexType lastIndexType; + // output + uint32 indexMin; + uint32 indexMax; + Renderer::INDEX_TYPE renderIndexType; + uint32 outputCount; + uint32 indexBufferOffset; + uint32 indexBufferIndex; +}LatteIndexCache{}; + +void LatteIndices_invalidate(const void* memPtr, uint32 size) +{ + if (LatteIndexCache.lastPtr >= memPtr && (LatteIndexCache.lastPtr < ((uint8*)memPtr + size)) ) + { + LatteIndexCache.lastPtr = nullptr; + LatteIndexCache.lastCount = 0; + } +} + +void LatteIndices_invalidateAll() +{ + LatteIndexCache.lastPtr = nullptr; + LatteIndexCache.lastCount = 0; +} + +uint32 LatteIndices_calculateIndexOutputSize(LattePrimitiveMode primitiveMode, LatteIndexType indexType, uint32 count) +{ + if (primitiveMode == LattePrimitiveMode::QUADS) + { + sint32 numQuads = count / 4; + if (indexType == LatteIndexType::AUTO) + { + if(count <= 0xFFFF) + return numQuads * 6 * sizeof(uint16); + return numQuads * 6 * sizeof(uint32); + } + if (indexType == LatteIndexType::U16_BE || indexType == LatteIndexType::U16_LE) + return numQuads * 6 * sizeof(uint16); + if (indexType == LatteIndexType::U32_BE || indexType == LatteIndexType::U32_LE) + return numQuads * 6 * sizeof(uint32); + cemu_assert_suspicious(); + return 0; + } + else if (primitiveMode == LattePrimitiveMode::QUAD_STRIP) + { + if (count <= 3) + { + return 0; + } + sint32 numQuads = (count-2) / 2; + if (indexType == LatteIndexType::AUTO) + { + if (count <= 0xFFFF) + return numQuads * 6 * sizeof(uint16); + return numQuads * 6 * sizeof(uint32); + } + if (indexType == LatteIndexType::U16_BE || indexType == LatteIndexType::U16_LE) + return numQuads * 6 * sizeof(uint16); + if (indexType == LatteIndexType::U32_BE || indexType == LatteIndexType::U32_LE) + return numQuads * 6 * sizeof(uint32); + cemu_assert_suspicious(); + return 0; + } + else if (primitiveMode == LattePrimitiveMode::LINE_LOOP) + { + count++; // one extra vertex to reconnect the LINE_STRIP to the beginning + if (indexType == LatteIndexType::AUTO) + { + if (count <= 0xFFFF) + return count * sizeof(uint16); + return count * sizeof(uint32); + } + if (indexType == LatteIndexType::U16_BE || indexType == LatteIndexType::U16_LE) + return count * sizeof(uint16); + if (indexType == LatteIndexType::U32_BE || indexType == LatteIndexType::U32_LE) + return count * sizeof(uint32); + cemu_assert_suspicious(); + return 0; + } + else if(indexType == LatteIndexType::AUTO) + return 0; + else if (indexType == LatteIndexType::U16_BE || indexType == LatteIndexType::U16_LE) + return count * sizeof(uint16); + else if (indexType == LatteIndexType::U32_BE || indexType == LatteIndexType::U32_LE) + return count * sizeof(uint32); + return 0; +} + +template<typename T> +void LatteIndices_convertBE(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax) +{ + const betype<T>* src = (betype<T>*)indexDataInput; + T* dst = (T*)indexDataOutput; + for (uint32 i = 0; i < count; i++) + { + T v = *src; + *dst = v; + indexMin = std::min(indexMin, (uint32)v); + indexMax = std::max(indexMax, (uint32)v); + dst++; + src++; + } +} + +template<typename T> +void LatteIndices_convertLE(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax) +{ + const T* src = (T*)indexDataInput; + T* dst = (T*)indexDataOutput; + for (uint32 i = 0; i < count; i++) + { + T v = *src; + *dst = v; + indexMin = std::min(indexMin, (uint32)v); + indexMax = std::max(indexMax, (uint32)v); + dst++; + src++; + } +} + +template<typename T> +void LatteIndices_unpackQuadsAndConvert(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax) +{ + sint32 numQuads = count / 4; + const betype<T>* src = (betype<T>*)indexDataInput; + T* dst = (T*)indexDataOutput; + for (sint32 i = 0; i < numQuads; i++) + { + T idx0 = src[0]; + T idx1 = src[1]; + T idx2 = src[2]; + T idx3 = src[3]; + indexMin = std::min(indexMin, (uint32)idx0); + indexMax = std::max(indexMax, (uint32)idx0); + indexMin = std::min(indexMin, (uint32)idx1); + indexMax = std::max(indexMax, (uint32)idx1); + indexMin = std::min(indexMin, (uint32)idx2); + indexMax = std::max(indexMax, (uint32)idx2); + indexMin = std::min(indexMin, (uint32)idx3); + indexMax = std::max(indexMax, (uint32)idx3); + dst[0] = idx0; + dst[1] = idx1; + dst[2] = idx2; + dst[3] = idx0; + dst[4] = idx2; + dst[5] = idx3; + src += 4; + dst += 6; + } +} + +template<typename T> +void LatteIndices_generateAutoQuadIndices(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax) +{ + sint32 numQuads = count / 4; + const betype<T>* src = (betype<T>*)indexDataInput; + T* dst = (T*)indexDataOutput; + for (sint32 i = 0; i < numQuads; i++) + { + T idx0 = i * 4 + 0; + T idx1 = i * 4 + 1; + T idx2 = i * 4 + 2; + T idx3 = i * 4 + 3; + dst[0] = idx0; + dst[1] = idx1; + dst[2] = idx2; + dst[3] = idx0; + dst[4] = idx2; + dst[5] = idx3; + src += 4; + dst += 6; + } + indexMin = 0; + indexMax = std::max(count, 1u) - 1; +} + +template<typename T> +void LatteIndices_unpackQuadStripAndConvert(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax) +{ + if (count <= 3) + return; + sint32 numQuads = (count - 2) / 2; + const betype<T>* src = (betype<T>*)indexDataInput; + T* dst = (T*)indexDataOutput; + for (sint32 i = 0; i < numQuads; i++) + { + T idx0 = src[0]; + T idx1 = src[1]; + T idx2 = src[2]; + T idx3 = src[3]; + indexMin = std::min(indexMin, (uint32)idx0); + indexMax = std::max(indexMax, (uint32)idx0); + indexMin = std::min(indexMin, (uint32)idx1); + indexMax = std::max(indexMax, (uint32)idx1); + indexMin = std::min(indexMin, (uint32)idx2); + indexMax = std::max(indexMax, (uint32)idx2); + indexMin = std::min(indexMin, (uint32)idx3); + indexMax = std::max(indexMax, (uint32)idx3); + dst[0] = idx0; + dst[1] = idx1; + dst[2] = idx2; + dst[3] = idx2; + dst[4] = idx1; + dst[5] = idx3; + src += 2; + dst += 6; + } +} + +template<typename T> +void LatteIndices_unpackLineLoopAndConvert(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax) +{ + if (count <= 0) + return; + const betype<T>* src = (betype<T>*)indexDataInput; + T firstIndex = *src; + T* dst = (T*)indexDataOutput; + for (sint32 i = 0; i < (sint32)count; i++) + { + T idx = *src; + indexMin = std::min(indexMin, (uint32)idx); + indexMax = std::max(indexMax, (uint32)idx); + *dst = idx; + src++; + dst++; + } + *dst = firstIndex; +} + +template<typename T> +void LatteIndices_generateAutoQuadStripIndices(void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax) +{ + if (count <= 3) + return; + sint32 numQuads = (count - 2) / 2; + T* dst = (T*)indexDataOutput; + for (sint32 i = 0; i < numQuads; i++) + { + T idx0 = i * 2 + 0; + T idx1 = i * 2 + 1; + T idx2 = i * 2 + 2; + T idx3 = i * 2 + 3; + dst[0] = idx0; + dst[1] = idx1; + dst[2] = idx2; + dst[3] = idx2; + dst[4] = idx1; + dst[5] = idx3; + dst += 6; + } + indexMin = 0; + indexMax = std::max(count, 1u) - 1; +} + + +template<typename T> +void LatteIndices_generateAutoLineLoopIndices(void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax) +{ + if (count == 0) + return; + T* dst = (T*)indexDataOutput; + for (sint32 i = 0; i < (sint32)count; i++) + { + *dst = (T)i; + dst++; + } + *dst = 0; + dst++; + indexMin = 0; + indexMax = std::max(count, 1u) - 1; +} + +#if BOOST_OS_LINUX +#pragma clang attribute push (__attribute__((target("avx2"))), apply_to=function) +#endif + +void LatteIndices_fastConvertU16_AVX2(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax) +{ + // using AVX + AVX2 we can process 16 indices at a time + const uint16* indicesU16BE = (const uint16*)indexDataInput; + uint16* indexOutput = (uint16*)indexDataOutput; + sint32 count16 = count >> 4; + sint32 countRemaining = count & 15; + if (count16) + { + __m256i mMin = _mm256_set_epi16((sint16)0xFFFF, (sint16)0xFFFF, (sint16)0xFFFF, (sint16)0xFFFF, (sint16)0xFFFF, (sint16)0xFFFF, (sint16)0xFFFF, (sint16)0xFFFF, + (sint16)0xFFFF, (sint16)0xFFFF, (sint16)0xFFFF, (sint16)0xFFFF, (sint16)0xFFFF, (sint16)0xFFFF, (sint16)0xFFFF, (sint16)0xFFFF); + __m256i mMax = _mm256_set_epi16(0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000); + __m256i mShuffle16Swap = _mm256_set_epi8(30, 31, 28, 29, 26, 27, 24, 25, 22, 23, 20, 21, 18, 19, 16, 17, 14, 15, 12, 13, 10, 11, 8, 9, 6, 7, 4, 5, 2, 3, 0, 1); + + do + { + __m256i mIndexData = _mm256_loadu_si256((const __m256i*)indicesU16BE); + indicesU16BE += 16; + _mm_prefetch((const char*)indicesU16BE, _MM_HINT_T0); + // endian swap + mIndexData = _mm256_shuffle_epi8(mIndexData, mShuffle16Swap); + _mm256_store_si256((__m256i*)indexOutput, mIndexData); + mMin = _mm256_min_epu16(mIndexData, mMin); + mMax = _mm256_max_epu16(mIndexData, mMax); + indexOutput += 16; + } while (--count16); + + // fold 32 to 16 byte + mMin = _mm256_min_epu16(mMin, _mm256_permute2x128_si256(mMin, mMin, 1)); + mMax = _mm256_max_epu16(mMax, _mm256_permute2x128_si256(mMax, mMax, 1)); + // fold 16 to 8 byte + mMin = _mm256_min_epu16(mMin, _mm256_shuffle_epi32(mMin, (2 << 0) | (3 << 2) | (2 << 4) | (3 << 6))); + mMax = _mm256_max_epu16(mMax, _mm256_shuffle_epi32(mMax, (2 << 0) | (3 << 2) | (2 << 4) | (3 << 6))); + + uint16* mMinU16 = (uint16*)&mMin; + uint16* mMaxU16 = (uint16*)&mMax; + + indexMin = std::min(indexMin, (uint32)mMinU16[0]); + indexMin = std::min(indexMin, (uint32)mMinU16[1]); + indexMin = std::min(indexMin, (uint32)mMinU16[2]); + indexMin = std::min(indexMin, (uint32)mMinU16[3]); + + indexMax = std::max(indexMax, (uint32)mMaxU16[0]); + indexMax = std::max(indexMax, (uint32)mMaxU16[1]); + indexMax = std::max(indexMax, (uint32)mMaxU16[2]); + indexMax = std::max(indexMax, (uint32)mMaxU16[3]); + } + // process remaining indices + uint32 _minIndex = 0xFFFFFFFF; + uint32 _maxIndex = 0; + for (sint32 i = countRemaining; (--i) >= 0;) + { + uint16 idx = _swapEndianU16(*indicesU16BE); + *indexOutput = idx; + indexOutput++; + indicesU16BE++; + _maxIndex = std::max(_maxIndex, (uint32)idx); + _minIndex = std::min(_minIndex, (uint32)idx); + } + // update min/max + indexMax = std::max(indexMax, _maxIndex); + indexMin = std::min(indexMin, _minIndex); +} + +#if BOOST_OS_LINUX +#pragma clang attribute pop +#endif + +#if BOOST_OS_LINUX +#pragma clang attribute push (__attribute__((target("avx2"))), apply_to=function) +#endif + +void LatteIndices_fastConvertU16_SSE41(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax) +{ + // SSSE3 & SSE4.1 optimized decoding + const uint16* indicesU16BE = (const uint16*)indexDataInput; + uint16* indexOutput = (uint16*)indexDataOutput; + sint32 count8 = count >> 3; + sint32 countRemaining = count & 7; + if (count8) + { + __m128i mMin = _mm_set_epi16((short)0xFFFF, (short)0xFFFF, (short)0xFFFF, (short)0xFFFF, (short)0xFFFF, (short)0xFFFF, (short)0xFFFF, (short)0xFFFF); + __m128i mMax = _mm_set_epi16(0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000); + __m128i mTemp; + __m128i* mRawIndices = (__m128i*)indicesU16BE; + indicesU16BE += count8 * 8; + __m128i* mOutputIndices = (__m128i*)indexOutput; + indexOutput += count8 * 8; + __m128i shufmask = _mm_set_epi8(14, 15, 12, 13, 10, 11, 8, 9, 6, 7, 4, 5, 2, 3, 0, 1); + while (count8--) + { + mTemp = _mm_loadu_si128(mRawIndices); + mRawIndices++; + mTemp = _mm_shuffle_epi8(mTemp, shufmask); + mMin = _mm_min_epu16(mMin, mTemp); + mMax = _mm_max_epu16(mMax, mTemp); + _mm_store_si128(mOutputIndices, mTemp); + mOutputIndices++; + } + + uint16* mMaxU16 = (uint16*)&mMax; + uint16* mMinU16 = (uint16*)&mMin; + + indexMax = std::max(indexMax, (uint32)mMaxU16[0]); + indexMax = std::max(indexMax, (uint32)mMaxU16[1]); + indexMax = std::max(indexMax, (uint32)mMaxU16[2]); + indexMax = std::max(indexMax, (uint32)mMaxU16[3]); + indexMax = std::max(indexMax, (uint32)mMaxU16[4]); + indexMax = std::max(indexMax, (uint32)mMaxU16[5]); + indexMax = std::max(indexMax, (uint32)mMaxU16[6]); + indexMax = std::max(indexMax, (uint32)mMaxU16[7]); + indexMin = std::min(indexMin, (uint32)mMinU16[0]); + indexMin = std::min(indexMin, (uint32)mMinU16[1]); + indexMin = std::min(indexMin, (uint32)mMinU16[2]); + indexMin = std::min(indexMin, (uint32)mMinU16[3]); + indexMin = std::min(indexMin, (uint32)mMinU16[4]); + indexMin = std::min(indexMin, (uint32)mMinU16[5]); + indexMin = std::min(indexMin, (uint32)mMinU16[6]); + indexMin = std::min(indexMin, (uint32)mMinU16[7]); + } + uint32 _minIndex = 0xFFFFFFFF; + uint32 _maxIndex = 0; + for (sint32 i = countRemaining; (--i) >= 0;) + { + uint16 idx = _swapEndianU16(*indicesU16BE); + *indexOutput = idx; + indexOutput++; + indicesU16BE++; + _maxIndex = std::max(_maxIndex, (uint32)idx); + _minIndex = std::min(_minIndex, (uint32)idx); + } + indexMax = std::max(indexMax, _maxIndex); + indexMin = std::min(indexMin, _minIndex); +} + +#if BOOST_OS_LINUX +#pragma clang attribute pop +#endif + +#if BOOST_OS_LINUX +#pragma clang attribute push (__attribute__((target("avx2"))), apply_to=function) +#endif + +void LatteIndices_fastConvertU32_AVX2(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax) +{ + // using AVX + AVX2 we can process 8 indices at a time + const uint32* indicesU32BE = (const uint32*)indexDataInput; + uint32* indexOutput = (uint32*)indexDataOutput; + sint32 count8 = count >> 3; + sint32 countRemaining = count & 7; + if (count8) + { + __m256i mMin = _mm256_set_epi32((sint32)0xFFFFFFFF, (sint32)0xFFFFFFFF, (sint32)0xFFFFFFFF, (sint32)0xFFFFFFFF, (sint32)0xFFFFFFFF, (sint32)0xFFFFFFFF, (sint32)0xFFFFFFFF, (sint32)0xFFFFFFFF); + __m256i mMax = _mm256_set_epi32(0, 0, 0, 0, 0, 0, 0, 0); + __m256i mShuffle32Swap = _mm256_set_epi8(28,29,30,31, + 24,25,26,27, + 20,21,22,23, + 16,17,18,19, + 12,13,14,15, + 8,9,10,11, + 4,5,6,7, + 0,1,2,3); + // unaligned + do + { + __m256i mIndexData = _mm256_loadu_si256((const __m256i*)indicesU32BE); + indicesU32BE += 8; + _mm_prefetch((const char*)indicesU32BE, _MM_HINT_T0); + // endian swap + mIndexData = _mm256_shuffle_epi8(mIndexData, mShuffle32Swap); + _mm256_store_si256((__m256i*)indexOutput, mIndexData); + mMin = _mm256_min_epu32(mIndexData, mMin); + mMax = _mm256_max_epu32(mIndexData, mMax); + indexOutput += 8; + } while (--count8); + + // fold 32 to 16 byte + mMin = _mm256_min_epu32(mMin, _mm256_permute2x128_si256(mMin, mMin, 1)); + mMax = _mm256_max_epu32(mMax, _mm256_permute2x128_si256(mMax, mMax, 1)); + // fold 16 to 8 byte + mMin = _mm256_min_epu32(mMin, _mm256_shuffle_epi32(mMin, (2 << 0) | (3 << 2) | (2 << 4) | (3 << 6))); + mMax = _mm256_max_epu32(mMax, _mm256_shuffle_epi32(mMax, (2 << 0) | (3 << 2) | (2 << 4) | (3 << 6))); + + uint32* mMinU32 = (uint32*)&mMin; + uint32* mMaxU32 = (uint32*)&mMax; + + indexMin = std::min(indexMin, (uint32)mMinU32[0]); + indexMin = std::min(indexMin, (uint32)mMinU32[1]); + + indexMax = std::max(indexMax, (uint32)mMaxU32[0]); + indexMax = std::max(indexMax, (uint32)mMaxU32[1]); + } + // process remaining indices + uint32 _minIndex = 0xFFFFFFFF; + uint32 _maxIndex = 0; + for (sint32 i = countRemaining; (--i) >= 0;) + { + uint32 idx = _swapEndianU32(*indicesU32BE); + *indexOutput = idx; + indexOutput++; + indicesU32BE++; + _maxIndex = std::max(_maxIndex, (uint32)idx); + _minIndex = std::min(_minIndex, (uint32)idx); + } + // update min/max + indexMax = std::max(indexMax, _maxIndex); + indexMin = std::min(indexMin, _minIndex); +} + +#if BOOST_OS_LINUX +#pragma clang attribute pop +#endif + +template<typename T> +void _LatteIndices_alternativeCalculateIndexMinMax(const void* indexData, uint32 count, uint32 primitiveRestartIndex, uint32& indexMin, uint32& indexMax) +{ + cemu_assert_debug(count != 0); + const betype<T>* idxPtrT = (betype<T>*)indexData; + T _indexMin = *idxPtrT; + T _indexMax = *idxPtrT; + cemu_assert_debug(primitiveRestartIndex <= std::numeric_limits<T>::max()); + T restartIndexT = (T)primitiveRestartIndex; + while (count) + { + T idx = *idxPtrT; + if (idx != restartIndexT) + { + _indexMin = std::min(_indexMin, idx); + _indexMax = std::max(_indexMax, idx); + } + idxPtrT++; + count--; + } + indexMin = _indexMin; + indexMax = _indexMax; +} + +// calculate min and max index while taking primitive restart into account +// fallback implementation in case the fast path gives us invalid results +void LatteIndices_alternativeCalculateIndexMinMax(const void* indexData, LatteIndexType indexType, uint32 count, uint32& indexMin, uint32& indexMax) +{ + if (count == 0) + { + indexMin = 0; + indexMax = 0; + return; + } + uint32 primitiveRestartIndex = LatteGPUState.contextNew.VGT_MULTI_PRIM_IB_RESET_INDX.get_RESTART_INDEX(); + + if (indexType == LatteIndexType::U16_BE) + { + _LatteIndices_alternativeCalculateIndexMinMax<uint16>(indexData, count, primitiveRestartIndex, indexMin, indexMax); + } + else if (indexType == LatteIndexType::U32_BE) + { + _LatteIndices_alternativeCalculateIndexMinMax<uint32>(indexData, count, primitiveRestartIndex, indexMin, indexMax); + } + else + { + cemu_assert_debug(false); + } +} + +void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32 count, LattePrimitiveMode primitiveMode, uint32& indexMin, uint32& indexMax, Renderer::INDEX_TYPE& renderIndexType, uint32& outputCount, uint32& indexBufferOffset, uint32& indexBufferIndex) +{ + // what this should do: + // [x] use fast SIMD-based index decoding + // [x] unpack QUAD indices to triangle indices + // [x] calculate min and max index, be careful about primitive restart index + // [x] decode data directly into coherent memory buffer? + // [ ] better cache implementation, allow to cache across frames + + // reuse from cache if data didn't change + if (LatteIndexCache.lastPtr == indexData && + LatteIndexCache.lastCount == count && + LatteIndexCache.lastPrimitiveMode == primitiveMode && + LatteIndexCache.lastIndexType == indexType) + { + indexMin = LatteIndexCache.indexMin; + indexMax = LatteIndexCache.indexMax; + renderIndexType = LatteIndexCache.renderIndexType; + outputCount = LatteIndexCache.outputCount; + indexBufferOffset = LatteIndexCache.indexBufferOffset; + indexBufferIndex = LatteIndexCache.indexBufferIndex; + return; + } + + outputCount = 0; + if (indexType == LatteIndexType::AUTO) + renderIndexType = Renderer::INDEX_TYPE::NONE; + else if (indexType == LatteIndexType::U16_BE || indexType == LatteIndexType::U16_LE) + renderIndexType = Renderer::INDEX_TYPE::U16; + else if (indexType == LatteIndexType::U32_BE) + renderIndexType = Renderer::INDEX_TYPE::U32; + else + cemu_assert_debug(false); + + uint32 primitiveRestartIndex = LatteGPUState.contextNew.VGT_MULTI_PRIM_IB_RESET_INDX.get_RESTART_INDEX(); + + // calculate index output size + uint32 indexOutputSize = LatteIndices_calculateIndexOutputSize(primitiveMode, indexType, count); + if (indexOutputSize == 0) + { + outputCount = count; + indexMin = 0; + indexMax = std::max(count, 1u)-1; + renderIndexType = Renderer::INDEX_TYPE::NONE; + return; // no indices + } + // query index buffer from renderer + void* indexOutputPtr = g_renderer->indexData_reserveIndexMemory(indexOutputSize, indexBufferOffset, indexBufferIndex); + + // decode indices + indexMin = std::numeric_limits<uint32>::max(); + indexMax = std::numeric_limits<uint32>::min(); + if (primitiveMode == LattePrimitiveMode::QUADS) + { + // unpack quads into triangles + if (indexType == LatteIndexType::AUTO) + { + if (count <= 0xFFFF) + { + LatteIndices_generateAutoQuadIndices<uint16>(indexData, indexOutputPtr, count, indexMin, indexMax); + renderIndexType = Renderer::INDEX_TYPE::U16; + } + else + { + LatteIndices_generateAutoQuadIndices<uint32>(indexData, indexOutputPtr, count, indexMin, indexMax); + renderIndexType = Renderer::INDEX_TYPE::U32; + } + } + else if (indexType == LatteIndexType::U16_BE) + LatteIndices_unpackQuadsAndConvert<uint16>(indexData, indexOutputPtr, count, indexMin, indexMax); + else if (indexType == LatteIndexType::U32_BE) + LatteIndices_unpackQuadsAndConvert<uint32>(indexData, indexOutputPtr, count, indexMin, indexMax); + else + cemu_assert_debug(false); + outputCount = count / 4 * 6; + } + else if (primitiveMode == LattePrimitiveMode::QUAD_STRIP) + { + // unpack quad strip into triangles + if (indexType == LatteIndexType::AUTO) + { + if (count <= 0xFFFF) + { + LatteIndices_generateAutoQuadStripIndices<uint16>(indexOutputPtr, count, indexMin, indexMax); + renderIndexType = Renderer::INDEX_TYPE::U16; + } + else + { + LatteIndices_generateAutoQuadStripIndices<uint32>(indexOutputPtr, count, indexMin, indexMax); + renderIndexType = Renderer::INDEX_TYPE::U32; + } + } + else if (indexType == LatteIndexType::U16_BE) + LatteIndices_unpackQuadStripAndConvert<uint16>(indexData, indexOutputPtr, count, indexMin, indexMax); + else if (indexType == LatteIndexType::U32_BE) + LatteIndices_unpackQuadStripAndConvert<uint32>(indexData, indexOutputPtr, count, indexMin, indexMax); + else + cemu_assert_debug(false); + if (count >= 2) + outputCount = (count - 2) / 2 * 6; + else + outputCount = 0; + } + else if (primitiveMode == LattePrimitiveMode::LINE_LOOP) + { + // unpack line loop into line strip with extra reconnecting vertex + if (indexType == LatteIndexType::AUTO) + { + if (count <= 0xFFFF) + { + LatteIndices_generateAutoLineLoopIndices<uint16>(indexOutputPtr, count, indexMin, indexMax); + renderIndexType = Renderer::INDEX_TYPE::U16; + } + else + { + LatteIndices_generateAutoLineLoopIndices<uint32>(indexOutputPtr, count, indexMin, indexMax); + renderIndexType = Renderer::INDEX_TYPE::U32; + } + } + else if (indexType == LatteIndexType::U16_BE) + LatteIndices_unpackLineLoopAndConvert<uint16>(indexData, indexOutputPtr, count, indexMin, indexMax); + else if (indexType == LatteIndexType::U32_BE) + LatteIndices_unpackLineLoopAndConvert<uint32>(indexData, indexOutputPtr, count, indexMin, indexMax); + else + cemu_assert_debug(false); + outputCount = count + 1; + } + else + { + if (indexType == LatteIndexType::U16_BE) + { + if (_cpuExtension_AVX2) + LatteIndices_fastConvertU16_AVX2(indexData, indexOutputPtr, count, indexMin, indexMax); + else if (_cpuExtension_SSE4_1 && _cpuExtension_SSSE3) + LatteIndices_fastConvertU16_SSE41(indexData, indexOutputPtr, count, indexMin, indexMax); + else + LatteIndices_convertBE<uint16>(indexData, indexOutputPtr, count, indexMin, indexMax); + } + else if (indexType == LatteIndexType::U32_BE) + { + if (_cpuExtension_AVX2) + LatteIndices_fastConvertU32_AVX2(indexData, indexOutputPtr, count, indexMin, indexMax); + else + LatteIndices_convertBE<uint32>(indexData, indexOutputPtr, count, indexMin, indexMax); + } + else if (indexType == LatteIndexType::U16_LE) + { + LatteIndices_convertLE<uint16>(indexData, indexOutputPtr, count, indexMin, indexMax); + } + else if (indexType == LatteIndexType::U32_LE) + { + LatteIndices_convertLE<uint32>(indexData, indexOutputPtr, count, indexMin, indexMax); + } + else + cemu_assert_debug(false); + outputCount = count; + } + // the above algorithms use a simplistic approach to get indexMin/indexMax + // here we make sure primitive restart indices dont influence the index range + if (primitiveRestartIndex == indexMin || primitiveRestartIndex == indexMax) + { + // recalculate index range but filter out primitive restart index + LatteIndices_alternativeCalculateIndexMinMax(indexData, indexType, count, indexMin, indexMax); + } + g_renderer->indexData_uploadIndexMemory(indexBufferOffset, indexOutputSize); + // update cache + LatteIndexCache.lastPtr = indexData; + LatteIndexCache.lastCount = count; + LatteIndexCache.lastPrimitiveMode = primitiveMode; + LatteIndexCache.lastIndexType = indexType; + LatteIndexCache.indexMin = indexMin; + LatteIndexCache.indexMax = indexMax; + LatteIndexCache.renderIndexType = renderIndexType; + LatteIndexCache.outputCount = outputCount; + LatteIndexCache.indexBufferOffset = indexBufferOffset; + LatteIndexCache.indexBufferIndex = indexBufferIndex; +} \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Core/LatteIndices.h b/src/Cafe/HW/Latte/Core/LatteIndices.h new file mode 100644 index 00000000..917d7991 --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteIndices.h @@ -0,0 +1,7 @@ +#pragma once +#include "Cafe/HW/Latte/Core/LatteConst.h" +#include "Cafe/HW/Latte/ISA/LatteReg.h" + +void LatteIndices_invalidate(const void* memPtr, uint32 size); +void LatteIndices_invalidateAll(); +void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32 count, LattePrimitiveMode primitiveMode, uint32& indexMin, uint32& indexMax, Renderer::INDEX_TYPE& renderIndexType, uint32& outputCount, uint32& indexBufferOffset, uint32& indexBufferIndex); \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Core/LatteOverlay.cpp b/src/Cafe/HW/Latte/Core/LatteOverlay.cpp new file mode 100644 index 00000000..ae483c32 --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteOverlay.cpp @@ -0,0 +1,685 @@ +#include "Cafe/HW/Latte/Core/LatteOverlay.h" +#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" +#include "gui/guiWrapper.h" + +#include "config/CemuConfig.h" + +#include "Cafe/HW/Latte/Renderer/Renderer.h" +#include "config/ActiveSettings.h" + +#include <imgui.h> +#include "resource/IconsFontAwesome5.h" +#include "imgui/imgui_extension.h" + +#include "input/InputManager.h" + +#if BOOST_OS_WINDOWS +#include <Psapi.h> +#include <winternl.h> +#pragma comment(lib, "ntdll.lib") +#endif + +struct OverlayStats +{ + OverlayStats() {}; + + int processor_count = 1; + + // cemu cpu stats + uint64_t last_cpu{}, kernel{}, user{}; + + // global cpu stats + struct ProcessorTime + { + uint64_t idle{}, kernel{}, user{}; + }; + + std::vector<ProcessorTime> processor_times; + + double fps{}; + uint32 draw_calls_per_frame{}; + float cpu_usage{}; // cemu cpu usage in % + std::vector<float> cpu_per_core; // global cpu usage in % per core + uint32 ram_usage{}; // ram usage in MB + + int vramUsage{}, vramTotal{}; // vram usage in mb +} g_state{}; + +extern std::atomic_int g_compiled_shaders_total; +extern std::atomic_int g_compiled_shaders_async; + +std::atomic_int g_compiling_pipelines; +std::atomic_int g_compiling_pipelines_async; +std::atomic_uint64_t g_compiling_pipelines_syncTimeSum; + +extern std::mutex g_friend_notification_mutex; +extern std::vector< std::pair<std::string, int> > g_friend_notifications; + +std::mutex g_notification_mutex; +std::vector< std::pair<std::string, int> > g_notifications; + +void LatteOverlay_pushNotification(const std::string& text, sint32 duration) +{ + std::unique_lock lock(g_notification_mutex); + g_notifications.emplace_back(text, duration); +} + +struct OverlayList +{ + std::wstring text; + float pos_x = 0; + float pos_y = 0; + float width; + + OverlayList(std::wstring text, float width) + : text(std::move(text)), width(width) {} +}; + +const auto kPopupFlags = ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav; + +const float kBackgroundAlpha = 0.65f; +void LatteOverlay_renderOverlay(ImVec2& position, ImVec2& pivot, sint32 direction) +{ + auto& config = GetConfig(); + + const auto font = ImGui_GetFont(14.0f * (float)config.overlay.text_scale / 100.0f); + ImGui::PushFont(font); + + const ImVec4 color = ImGui::ColorConvertU32ToFloat4(config.overlay.text_color); + ImGui::PushStyleColor(ImGuiCol_Text, color); + // stats overlay + if (config.overlay.fps || config.overlay.drawcalls || config.overlay.cpu_usage || config.overlay.cpu_per_core_usage || config.overlay.ram_usage) + { + ImGui::SetNextWindowPos(position, ImGuiCond_Always, pivot); + ImGui::SetNextWindowBgAlpha(kBackgroundAlpha); + if (ImGui::Begin("Stats overlay", nullptr, kPopupFlags)) + { + if (config.overlay.fps) + ImGui::Text("FPS: %.2lf", g_state.fps); + + if (config.overlay.drawcalls) + ImGui::Text("Draws/f: %d", g_state.draw_calls_per_frame); + + if (config.overlay.cpu_usage) + ImGui::Text("CPU: %.2lf%%", g_state.cpu_usage); + + if (config.overlay.cpu_per_core_usage) + { + for (sint32 i = 0; i < g_state.processor_count; ++i) + { + ImGui::Text("CPU #%d: %.2lf%%", i + 1, g_state.cpu_per_core[i]); + } + } + + if (config.overlay.ram_usage) + ImGui::Text("RAM: %dMB", g_state.ram_usage); + + if(config.overlay.vram_usage && g_state.vramUsage != -1 && g_state.vramTotal != -1) + ImGui::Text("VRAM: %dMB / %dMB", g_state.vramUsage, g_state.vramTotal); + + if (config.overlay.debug) + g_renderer->AppendOverlayDebugInfo(); + + position.y += (ImGui::GetWindowSize().y + 10.0f) * direction; + ImGui::End(); + } + } + + ImGui::PopStyleColor(); + ImGui::PopFont(); +} + +void LatteOverlay_RenderNotifications(ImVec2& position, ImVec2& pivot, sint32 direction) +{ + auto& config = GetConfig(); + + const auto font = ImGui_GetFont(14.0f * (float)config.notification.text_scale / 100.0f); + ImGui::PushFont(font); + + const ImVec4 color = ImGui::ColorConvertU32ToFloat4(config.notification.text_color); + ImGui::PushStyleColor(ImGuiCol_Text, color); + + // selected controller profiles in the beginning + if (config.notification.controller_profiles) + { + static bool s_init_overlay = false; + if (!s_init_overlay) + { + static std::chrono::steady_clock::time_point s_started = tick_cached(); + + const auto now = tick_cached(); + if (std::chrono::duration_cast<std::chrono::milliseconds>(now - s_started).count() <= 5000) + { + // active account + ImGui::SetNextWindowPos(position, ImGuiCond_Always, pivot); + ImGui::SetNextWindowBgAlpha(kBackgroundAlpha); + if (ImGui::Begin("Active account", nullptr, kPopupFlags)) + { + ImGui::TextUnformatted((const char*)ICON_FA_USER); + ImGui::SameLine(); + + static std::string s_mii_name; + if (s_mii_name.empty()) + { + auto tmp_view = Account::GetAccount(ActiveSettings::GetPersistentId()).GetMiiName(); + std::wstring tmp{ tmp_view }; + s_mii_name = boost::nowide::narrow(tmp); + } + ImGui::TextUnformatted(s_mii_name.c_str()); + + position.y += (ImGui::GetWindowSize().y + 10.0f) * direction; + ImGui::End(); + } + + // controller + std::vector<std::pair<int, std::string>> profiles; + auto& input_manager = InputManager::instance(); + for (int i = 0; i < InputManager::kMaxController; ++i) + { + const auto controller = input_manager.get_controller(i); + if (!controller) + continue; + + const auto& profile_name = controller->get_profile_name(); + if (profile_name.empty()) + continue; + + profiles.emplace_back(i, profile_name); + } + + if (!profiles.empty()) + { + ImGui::SetNextWindowPos(position, ImGuiCond_Always, pivot); + ImGui::SetNextWindowBgAlpha(kBackgroundAlpha); + if (ImGui::Begin("Controller profile names", nullptr, kPopupFlags)) + { + auto it = profiles.cbegin(); + ImGui::TextUnformatted((const char*)ICON_FA_GAMEPAD); + ImGui::SameLine(); + ImGui::Text("Player %d: %s", it->first + 1, it->second.c_str()); + + for (++it; it != profiles.cend(); ++it) + { + ImGui::Separator(); + ImGui::TextUnformatted((const char*)ICON_FA_GAMEPAD); + ImGui::SameLine(); + ImGui::Text("Player %d: %s", it->first + 1, it->second.c_str()); + } + + position.y += (ImGui::GetWindowSize().y + 10.0f) * direction; + ImGui::End(); + } + } + else + s_init_overlay = true; + } + else + s_init_overlay = true; + } + } + + if (config.notification.friends) + { + static std::vector< std::pair<std::string, std::chrono::steady_clock::time_point> > s_friend_list; + + std::unique_lock lock(g_friend_notification_mutex); + if (!g_friend_notifications.empty()) + { + const auto tick = tick_cached(); + + for (const auto& entry : g_friend_notifications) + { + s_friend_list.emplace_back(entry.first, tick + std::chrono::milliseconds(entry.second)); + } + + g_friend_notifications.clear(); + } + + if (!s_friend_list.empty()) + { + ImGui::SetNextWindowPos(position, ImGuiCond_Always, pivot); + ImGui::SetNextWindowBgAlpha(kBackgroundAlpha); + if (ImGui::Begin("Friends overlay", nullptr, kPopupFlags)) + { + const auto tick = tick_cached(); + for (auto it = s_friend_list.cbegin(); it != s_friend_list.cend();) + { + ImGui::TextUnformatted(it->first.c_str(), it->first.c_str() + it->first.size()); + if (tick >= it->second) + it = s_friend_list.erase(it); + else + ++it; + } + + position.y += (ImGui::GetWindowSize().y + 10.0f) * direction; + ImGui::End(); + } + + + } + } + + // low battery warning + if (config.notification.controller_battery) + { + std::vector<int> batteries; + auto& input_manager = InputManager::instance(); + for (int i = 0; i < InputManager::kMaxController; ++i) + { + const auto controller = input_manager.get_controller(i); + if (!controller) + continue; + + if (controller->is_battery_low()) + batteries.emplace_back(i); + } + + if (!batteries.empty()) + { + static std::chrono::steady_clock::time_point s_last_tick = tick_cached(); + static bool s_blink_state = false; + const auto now = tick_cached(); + + if (std::chrono::duration_cast<std::chrono::milliseconds>(now - s_last_tick).count() >= 750) + { + s_blink_state = !s_blink_state; + s_last_tick = now; + } + + ImGui::SetNextWindowPos(position, ImGuiCond_Always, pivot); + ImGui::SetNextWindowBgAlpha(kBackgroundAlpha); + if (ImGui::Begin("Low battery overlay", nullptr, kPopupFlags)) + { + auto it = batteries.cbegin(); + ImGui::TextUnformatted((const char*)(s_blink_state ? ICON_FA_BATTERY_EMPTY : ICON_FA_BATTERY_QUARTER)); + ImGui::SameLine(); + ImGui::Text("Player %d", *it + 1); + + for (++it; it != batteries.cend(); ++it) + { + ImGui::Separator(); + ImGui::TextUnformatted((const char*)(s_blink_state ? ICON_FA_BATTERY_EMPTY : ICON_FA_BATTERY_QUARTER)); + ImGui::SameLine(); + ImGui::Text("Player %d", *it + 1); + } + + position.y += (ImGui::GetWindowSize().y + 10.0f) * direction; + ImGui::End(); + } + } + } + + if (config.notification.shader_compiling) + { + static int32_t s_shader_count = 0; + static int32_t s_shader_count_async = 0; + if (s_shader_count > 0 || g_compiled_shaders_total > 0) + { + const int tmp = g_compiled_shaders_total.exchange(0); + const int tmpAsync = g_compiled_shaders_async.exchange(0); + s_shader_count += tmp; + s_shader_count_async += tmpAsync; + + static std::chrono::steady_clock::time_point s_last_tick = tick_cached(); + const auto now = tick_cached(); + + if (tmp > 0) + s_last_tick = now; + + if (std::chrono::duration_cast<std::chrono::milliseconds>(now - s_last_tick).count() >= 2500) + { + s_shader_count = 0; + s_shader_count_async = 0; + } + + if (s_shader_count > 0) + { + ImGui::SetNextWindowPos(position, ImGuiCond_Always, pivot); + ImGui::SetNextWindowBgAlpha(kBackgroundAlpha); + if (ImGui::Begin("Compiling shaders overlay", nullptr, kPopupFlags)) + { + ImRotateStart(); + ImGui::TextUnformatted((const char*)ICON_FA_SPINNER); + + const auto ticks = std::chrono::time_point_cast<std::chrono::milliseconds>(now); + ImRotateEnd(0.001f * ticks.time_since_epoch().count()); + ImGui::SameLine(); + + if (s_shader_count_async > 0 && GetConfig().async_compile) // the latter condition is to never show async count when async isn't enabled. Since it can be confusing to the user + { + if(s_shader_count > 1) + ImGui::Text("Compiled %d new shaders... (%d async)", s_shader_count, s_shader_count_async); + else + ImGui::Text("Compiled %d new shader... (%d async)", s_shader_count, s_shader_count_async); + } + else + { + if (s_shader_count > 1) + ImGui::Text("Compiled %d new shaders...", s_shader_count); + else + ImGui::Text("Compiled %d new shader...", s_shader_count); + } + + position.y += (ImGui::GetWindowSize().y + 10.0f) * direction; + ImGui::End(); + } + } + } + + static int32_t s_pipeline_count = 0; + static int32_t s_pipeline_count_async = 0; + if (s_pipeline_count > 0 || g_compiling_pipelines > 0) + { + const int tmp = g_compiling_pipelines.exchange(0); + const int tmpAsync = g_compiling_pipelines_async.exchange(0); + s_pipeline_count += tmp; + s_pipeline_count_async += tmpAsync; + + static std::chrono::steady_clock::time_point s_last_tick = tick_cached(); + const auto now = tick_cached(); + + if (tmp > 0) + s_last_tick = now; + + if (std::chrono::duration_cast<std::chrono::milliseconds>(now - s_last_tick).count() >= 2500) + { + s_pipeline_count = 0; + s_pipeline_count_async = 0; + } + + if (s_pipeline_count > 0) + { + ImGui::SetNextWindowPos(position, ImGuiCond_Always, pivot); + ImGui::SetNextWindowBgAlpha(kBackgroundAlpha); + if (ImGui::Begin("Compiling pipeline overlay", nullptr, kPopupFlags)) + { + ImRotateStart(); + ImGui::TextUnformatted((const char*)ICON_FA_SPINNER); + + const auto ticks = std::chrono::time_point_cast<std::chrono::milliseconds>(now); + ImRotateEnd(0.001f * ticks.time_since_epoch().count()); + ImGui::SameLine(); + +#ifndef PUBLIC_RELEASE + uint64 totalTime = g_compiling_pipelines_syncTimeSum / 1000000ull; + if (s_pipeline_count_async > 0) + { + if (s_pipeline_count > 1) + ImGui::Text("Compiled %d new pipelines... (%d async) TotalSync: %lldms", s_pipeline_count, s_pipeline_count_async, totalTime); + else + ImGui::Text("Compiled %d new pipeline... (%d async) TotalSync: %lldms", s_pipeline_count, s_pipeline_count_async, totalTime); + } + else + { + if (s_pipeline_count > 1) + ImGui::Text("Compiled %d new pipelines... TotalSync: %lldms", s_pipeline_count, totalTime); + else + ImGui::Text("Compiled %d new pipeline... TotalSync: %lldms", s_pipeline_count, totalTime); + } +#else + if (s_pipeline_count_async > 0) + { + if (s_pipeline_count > 1) + ImGui::Text("Compiled %d new pipelines... (%d async)", s_pipeline_count, s_pipeline_count_async); + else + ImGui::Text("Compiled %d new pipeline... (%d async)", s_pipeline_count, s_pipeline_count_async); + } + else + { + if (s_pipeline_count > 1) + ImGui::Text("Compiled %d new pipelines...", s_pipeline_count); + else + ImGui::Text("Compiled %d new pipeline...", s_pipeline_count); + } +#endif + position.y += (ImGui::GetWindowSize().y + 10.0f) * direction; + ImGui::End(); + } + } + } + } + + // misc notifications + static std::vector< std::pair<std::string, std::chrono::steady_clock::time_point> > s_misc_notifications; + + std::unique_lock misc_lock(g_notification_mutex); + if (!g_notifications.empty()) + { + const auto tick = tick_cached(); + + for (const auto& entry : g_notifications) + { + s_misc_notifications.emplace_back(entry.first, tick + std::chrono::milliseconds(entry.second)); + } + + g_notifications.clear(); + } + misc_lock.unlock(); + + if (!s_misc_notifications.empty()) + { + ImGui::SetNextWindowPos(position, ImGuiCond_Always, pivot); + ImGui::SetNextWindowBgAlpha(kBackgroundAlpha); + if (ImGui::Begin("Misc notifications", nullptr, kPopupFlags)) + { + const auto tick = tick_cached(); + for (auto it = s_misc_notifications.cbegin(); it != s_misc_notifications.cend();) + { + ImGui::TextUnformatted(it->first.c_str(), it->first.c_str() + it->first.size()); + if (tick >= it->second) + it = s_misc_notifications.erase(it); + else + ++it; + } + + position.y += (ImGui::GetWindowSize().y + 10.0f) * direction; + ImGui::End(); + } + } + + ImGui::PopStyleColor(); + ImGui::PopFont(); +} + +void LatteOverlay_translateScreenPosition(ScreenPosition pos, const Vector2f& window_size, ImVec2& position, ImVec2& pivot, sint32& direction) +{ + switch (pos) + { + case ScreenPosition::kTopLeft: + position = { 10, 10 }; + pivot = { 0, 0 }; + direction = 1; + break; + case ScreenPosition::kTopCenter: + position = { window_size.x / 2.0f, 10 }; + pivot = { 0.5f, 0 }; + direction = 1; + break; + case ScreenPosition::kTopRight: + position = { window_size.x - 10, 10 }; + pivot = { 1, 0 }; + direction = 1; + break; + case ScreenPosition::kBottomLeft: + position = { 10, window_size.y - 10 }; + pivot = { 0, 1 }; + direction = -1; + break; + case ScreenPosition::kBottomCenter: + position = { window_size.x / 2.0f, window_size.y - 10 }; + pivot = { 0.5f, 1 }; + direction = -1; + break; + case ScreenPosition::kBottomRight: + position = { window_size.x - 10, window_size.y - 10 }; + pivot = { 1, 1 }; + direction = -1; + break; + default: + __assume(false); + } +} + +void LatteOverlay_render(bool pad_view) +{ + const auto& config = GetConfig(); + if(config.overlay.position == ScreenPosition::kDisabled && config.notification.position == ScreenPosition::kDisabled) + return; + + sint32 w = 0, h = 0; + if (pad_view && gui_isPadWindowOpen()) + gui_getPadWindowSize(&w, &h); + else + gui_getWindowSize(&w, &h); + + if (w == 0 || h == 0) + return; + + const Vector2f window_size{ (float)w,(float)h }; + + // test if fonts are already precached + if (!ImGui_GetFont(14.0f * (float)config.overlay.text_scale / 100.0f)) + return; + + if (!ImGui_GetFont(14.0f * (float)config.notification.text_scale / 100.0f)) + return; + + ImVec2 position{}, pivot{}; + sint32 direction{}; + + if (config.overlay.position != ScreenPosition::kDisabled) + { + LatteOverlay_translateScreenPosition(config.overlay.position, window_size, position, pivot, direction); + LatteOverlay_renderOverlay(position, pivot, direction); + } + + + if (config.notification.position != ScreenPosition::kDisabled) + { + if(config.overlay.position != config.notification.position) + LatteOverlay_translateScreenPosition(config.notification.position, window_size, position, pivot, direction); + + LatteOverlay_RenderNotifications(position, pivot, direction); + } +} + + +void LatteOverlay_init() +{ +#if BOOST_OS_WINDOWS + SYSTEM_INFO sys_info; + GetSystemInfo(&sys_info); + g_state.processor_count = sys_info.dwNumberOfProcessors; + + g_state.processor_times.resize(g_state.processor_count); + g_state.cpu_per_core.resize(g_state.processor_count); +#else + g_state.processor_count = 1; +#endif +} + +void LatteOverlay_updateStats(double fps, sint32 drawcalls) +{ + if (GetConfig().overlay.position == ScreenPosition::kDisabled) + return; + + g_state.fps = fps; + g_state.draw_calls_per_frame = drawcalls; + +#if BOOST_OS_WINDOWS + // update cemu cpu + FILETIME ftime, fkernel, fuser; + LARGE_INTEGER now, kernel, user; + GetSystemTimeAsFileTime(&ftime); + now.LowPart = ftime.dwLowDateTime; + now.HighPart = ftime.dwHighDateTime; + + GetProcessTimes(GetCurrentProcess(), &ftime, &ftime, &fkernel, &fuser); + kernel.LowPart = fkernel.dwLowDateTime; + kernel.HighPart = fkernel.dwHighDateTime; + + user.LowPart = fuser.dwLowDateTime; + user.HighPart = fuser.dwHighDateTime; + + double percent = (kernel.QuadPart - g_state.kernel) + (user.QuadPart - g_state.user); + percent /= (now.QuadPart - g_state.last_cpu); + percent /= g_state.processor_count; + g_state.cpu_usage = percent * 100; + g_state.last_cpu = now.QuadPart; + g_state.user = user.QuadPart; + g_state.kernel = kernel.QuadPart; + + // update cpu per core + std::vector<SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION> sppi(g_state.processor_count); + if (NT_SUCCESS(NtQuerySystemInformation(SystemProcessorPerformanceInformation, sppi.data(), sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION) * g_state.processor_count, nullptr))) + { + for (sint32 i = 0; i < g_state.processor_count; ++i) + { + const uint64 kernel_diff = sppi[i].KernelTime.QuadPart - g_state.processor_times[i].kernel; + const uint64 user_diff = sppi[i].UserTime.QuadPart - g_state.processor_times[i].user; + const uint64 idle_diff = sppi[i].IdleTime.QuadPart - g_state.processor_times[i].idle; + + const auto total = kernel_diff + user_diff; // kernel time already includes idletime + const double cpu = total == 0 ? 0 : (1.0 - ((double)idle_diff / total)) * 100.0; + + g_state.cpu_per_core[i] = cpu; + //total_cpu += cpu; + + g_state.processor_times[i].idle = sppi[i].IdleTime.QuadPart; + g_state.processor_times[i].kernel = sppi[i].KernelTime.QuadPart; + g_state.processor_times[i].user = sppi[i].UserTime.QuadPart; + } + + //total_cpu /= g_state.processor_count; + //g_state.cpu_usage = total_cpu; + } + + // update ram + PROCESS_MEMORY_COUNTERS pmc{}; + pmc.cb = sizeof(pmc); + GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc)); + g_state.ram_usage = (pmc.WorkingSetSize / 1000) / 1000; +#endif + + // update vram + g_renderer->GetVRAMInfo(g_state.vramUsage, g_state.vramTotal); +} + +void LatteOverlay_updateStatsPerFrame() +{ + if (!ActiveSettings::FrameProfilerEnabled()) + return; + // update frametime graph + uint32 frameTime_total = (uint32)PPCTimer_tscToMicroseconds(performanceMonitor.gpuTime_frameTime.getPreviousFrameValue()); + uint32 frameTime_idle = (uint32)PPCTimer_tscToMicroseconds(performanceMonitor.gpuTime_idleTime.getPreviousFrameValue()); + uint32 frameTime_dcStageTextures = (uint32)PPCTimer_tscToMicroseconds(performanceMonitor.gpuTime_dcStageTextures.getPreviousFrameValue()); + uint32 frameTime_dcStageVertexMgr = (uint32)PPCTimer_tscToMicroseconds(performanceMonitor.gpuTime_dcStageVertexMgr.getPreviousFrameValue()); + uint32 frameTime_dcStageShaderAndUniformMgr = (uint32)PPCTimer_tscToMicroseconds(performanceMonitor.gpuTime_dcStageShaderAndUniformMgr.getPreviousFrameValue()); + uint32 frameTime_dcStageIndexMgr = (uint32)PPCTimer_tscToMicroseconds(performanceMonitor.gpuTime_dcStageIndexMgr.getPreviousFrameValue()); + uint32 frameTime_dcStageMRT = (uint32)PPCTimer_tscToMicroseconds(performanceMonitor.gpuTime_dcStageMRT.getPreviousFrameValue()); + uint32 frameTime_dcStageDrawcallAPI = (uint32)PPCTimer_tscToMicroseconds(performanceMonitor.gpuTime_dcStageDrawcallAPI.getPreviousFrameValue()); + uint32 frameTime_waitForAsync = (uint32)PPCTimer_tscToMicroseconds(performanceMonitor.gpuTime_waitForAsync.getPreviousFrameValue()); + + // make sure total frame time is not less than it's sums + uint32 minimumExpectedFrametime = + frameTime_idle + + frameTime_dcStageTextures + + frameTime_dcStageVertexMgr + + frameTime_dcStageShaderAndUniformMgr + + frameTime_dcStageIndexMgr + + frameTime_dcStageMRT + + frameTime_dcStageDrawcallAPI + + frameTime_waitForAsync; + frameTime_total = std::max(frameTime_total, minimumExpectedFrametime); + + //g_state.frametimeGraph.appendEntry(); + //g_state.frametimeGraph.setCurrentEntryValue(0xFF404040, frameTime_idle); + //g_state.frametimeGraph.setCurrentEntryValue(0xFFFFC0FF, frameTime_waitForAsync); + //g_state.frametimeGraph.setCurrentEntryValue(0xFF000040, frameTime_dcStageTextures); // dark red + //g_state.frametimeGraph.setCurrentEntryValue(0xFF004000, frameTime_dcStageVertexMgr); // dark green + //g_state.frametimeGraph.setCurrentEntryValue(0xFFFFFF80, frameTime_dcStageShaderAndUniformMgr); // blueish + //g_state.frametimeGraph.setCurrentEntryValue(0xFF800080, frameTime_dcStageIndexMgr); // purple + //g_state.frametimeGraph.setCurrentEntryValue(0xFF00FF00, frameTime_dcStageMRT); // green + //g_state.frametimeGraph.setCurrentEntryValue(0xFF00FFFF, frameTime_dcStageDrawcallAPI); // yellow + //g_state.frametimeGraph.setCurrentEntryValue(0xFFBBBBBB, frameTime_total - minimumExpectedFrametime); +} diff --git a/src/Cafe/HW/Latte/Core/LatteOverlay.h b/src/Cafe/HW/Latte/Core/LatteOverlay.h new file mode 100644 index 00000000..3df83d1d --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteOverlay.h @@ -0,0 +1,8 @@ +#pragma once + +void LatteOverlay_init(); +void LatteOverlay_render(bool pad_view); +void LatteOverlay_updateStats(double fps, sint32 drawcalls); +void LatteOverlay_updateStatsPerFrame(); + +void LatteOverlay_pushNotification(const std::string& text, sint32 duration); \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Core/LattePM4.h b/src/Cafe/HW/Latte/Core/LattePM4.h new file mode 100644 index 00000000..8079a89c --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LattePM4.h @@ -0,0 +1,60 @@ +#pragma once + +#define IT_SET_PREDICATION 0x20 +#define IT_DRAW_INDEX_2 0x27 +#define IT_CONTEXT_CONTROL 0x28 +#define IT_INDEX_TYPE 0x2A +#define IT_DRAW_INDEX_AUTO 0x2D +#define IT_DRAW_INDEX_IMMD 0x2E +#define IT_NUM_INSTANCES 0x2F +#define IT_INDIRECT_BUFFER_PRIV 0x32 +#define IT_STRMOUT_BUFFER_UPDATE 0x34 +#define IT_MEM_SEMAPHORE 0x39 +#define IT_WAIT_REG_MEM 0x3C +#define IT_MEM_WRITE 0x3D +#define IT_SURFACE_SYNC 0x43 +#define IT_EVENT_WRITE 0x46 + +#define IT_LOAD_CONFIG_REG 0x60 +#define IT_LOAD_CONTEXT_REG 0x61 +#define IT_LOAD_ALU_CONST 0x62 +#define IT_LOAD_BOOL_CONST 0x63 // not used? +#define IT_LOAD_LOOP_CONST 0x64 +#define IT_LOAD_RESOURCE 0x65 +#define IT_LOAD_SAMPLER 0x66 + +#define IT_SET_CONFIG_REG 0x68 +#define IT_SET_CONTEXT_REG 0x69 +#define IT_SET_ALU_CONST 0x6A +#define IT_SET_LOOP_CONST 0x6C +#define IT_SET_RESOURCE 0x6D +#define IT_SET_SAMPLER 0x6E +#define IT_SET_CTL_CONST 0x6F +#define IT_STRMOUT_BASE_UPDATE 0x72 +#define IT_SET_ALL_CONTEXTS 0x74 + +#define LATTE_REG_BASE_CONFIG 0x2000 +#define LATTE_REG_BASE_CONTEXT 0xA000 +#define LATTE_REG_BASE_ALU_CONST 0xC000 +#define LATTE_REG_BASE_LOOP_CONST 0xF880 +#define LATTE_REG_BASE_RESOURCE 0xE000 +#define LATTE_REG_BASE_SAMPLER 0xF000 + +// emulator only +#define IT_HLE_COPY_SURFACE_NEW 0xEE +#define IT_HLE_SYNC_ASYNC_OPERATIONS 0xEF +#define IT_HLE_REQUEST_SWAP_BUFFERS 0xF0 +#define IT_HLE_WAIT_FOR_FLIP 0xF1 +#define IT_HLE_BOTTOM_OF_PIPE_CB 0xF2 +#define IT_HLE_COPY_COLORBUFFER_TO_SCANBUFFER 0xF3 +#define IT_HLE_FIFO_WRAP_AROUND 0xF4 +#define IT_HLE_CLEAR_COLOR_DEPTH_STENCIL 0xF5 +#define IT_HLE_SAMPLE_TIMER 0xF7 +#define IT_HLE_TRIGGER_SCANBUFFER_SWAP 0xF8 +#define IT_HLE_SPECIAL_STATE 0xF9 +#define IT_HLE_BEGIN_OCCLUSION_QUERY 0xFA +#define IT_HLE_END_OCCLUSION_QUERY 0xFB +#define IT_HLE_SET_CB_RETIREMENT_TIMESTAMP 0xFD + +#define pm4HeaderType3(__itCode, __dataDWordCount) (0xC0000000|((uint32)(__itCode)<<8)|((uint32)((__dataDWordCount)-1)<<16)) +#define pm4HeaderType2Filler() (0x80000000) diff --git a/src/Cafe/HW/Latte/Core/LattePerformanceMonitor.cpp b/src/Cafe/HW/Latte/Core/LattePerformanceMonitor.cpp new file mode 100644 index 00000000..7104dbd9 --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LattePerformanceMonitor.cpp @@ -0,0 +1,127 @@ +#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" +#include "Cafe/HW/Latte/Core/LatteOverlay.h" +#include "gui/guiWrapper.h" + +performanceMonitor_t performanceMonitor{}; + +void LattePerformanceMonitor_frameEnd() +{ + // per-frame stats + performanceMonitor.gpuTime_shaderCreate.frameFinished(); + performanceMonitor.gpuTime_frameTime.frameFinished(); + performanceMonitor.gpuTime_idleTime.frameFinished(); + performanceMonitor.gpuTime_fenceTime.frameFinished(); + + performanceMonitor.gpuTime_dcStageTextures.frameFinished(); + performanceMonitor.gpuTime_dcStageVertexMgr.frameFinished(); + performanceMonitor.gpuTime_dcStageShaderAndUniformMgr.frameFinished(); + performanceMonitor.gpuTime_dcStageIndexMgr.frameFinished(); + performanceMonitor.gpuTime_dcStageMRT.frameFinished(); + performanceMonitor.gpuTime_dcStageDrawcallAPI.frameFinished(); + performanceMonitor.gpuTime_waitForAsync.frameFinished(); + + uint32 elapsedTime = GetTickCount() - performanceMonitor.cycle[performanceMonitor.cycleIndex].lastUpdate; + if (elapsedTime >= 1000) + { + bool isFirstUpdate = performanceMonitor.cycle[performanceMonitor.cycleIndex].lastUpdate == 0; + // sum up raw stats + uint32 totalElapsedTime = GetTickCount() - performanceMonitor.cycle[(performanceMonitor.cycleIndex + 1) % PERFORMANCE_MONITOR_TRACK_CYCLES].lastUpdate; + uint32 totalElapsedTimeFPS = GetTickCount() - performanceMonitor.cycle[(performanceMonitor.cycleIndex + PERFORMANCE_MONITOR_TRACK_CYCLES - 1) % PERFORMANCE_MONITOR_TRACK_CYCLES].lastUpdate; + uint32 elapsedFrames = 0; + uint32 elapsedFrames2S = 0; // elapsed frames for last two entries (seconds) + uint64 skippedCycles = 0; + uint64 vertexDataUploaded = 0; + uint64 vertexDataCached = 0; + uint64 uniformBankUploadedData = 0; + uint64 uniformBankUploadedCount = 0; + uint64 indexDataUploaded = 0; + uint64 indexDataCached = 0; + uint32 frameCounter = 0; + uint32 drawCallCounter = 0; + uint32 shaderBindCounter = 0; + uint32 recompilerLeaveCount = 0; + uint32 threadLeaveCount = 0; + for (sint32 i = 0; i < PERFORMANCE_MONITOR_TRACK_CYCLES; i++) + { + elapsedFrames += performanceMonitor.cycle[i].frameCounter; + skippedCycles += performanceMonitor.cycle[i].skippedCycles; + vertexDataUploaded += performanceMonitor.cycle[i].vertexDataUploaded; + vertexDataCached += performanceMonitor.cycle[i].vertexDataCached; + uniformBankUploadedData += performanceMonitor.cycle[i].uniformBankUploadedData; + uniformBankUploadedCount += performanceMonitor.cycle[i].uniformBankUploadedCount; + indexDataUploaded += performanceMonitor.cycle[i].indexDataUploaded; + indexDataCached += performanceMonitor.cycle[i].indexDataCached; + frameCounter += performanceMonitor.cycle[i].frameCounter; + drawCallCounter += performanceMonitor.cycle[i].drawCallCounter; + shaderBindCounter += performanceMonitor.cycle[i].shaderBindCount; + recompilerLeaveCount += performanceMonitor.cycle[i].recompilerLeaveCount; + threadLeaveCount += performanceMonitor.cycle[i].threadLeaveCount; + } + elapsedFrames = std::max<uint32>(elapsedFrames, 1); + elapsedFrames2S = performanceMonitor.cycle[(performanceMonitor.cycleIndex + PERFORMANCE_MONITOR_TRACK_CYCLES - 0) % PERFORMANCE_MONITOR_TRACK_CYCLES].frameCounter; + elapsedFrames2S += performanceMonitor.cycle[(performanceMonitor.cycleIndex + PERFORMANCE_MONITOR_TRACK_CYCLES - 1) % PERFORMANCE_MONITOR_TRACK_CYCLES].frameCounter; + elapsedFrames2S = std::max<uint32>(elapsedFrames2S, 1); + // calculate stats + uint64 passedCycles = PPCInterpreter_getMainCoreCycleCounter() - performanceMonitor.cycle[(performanceMonitor.cycleIndex + 1) % PERFORMANCE_MONITOR_TRACK_CYCLES].lastCycleCount; + passedCycles -= skippedCycles; + uint64 vertexDataUploadPerFrame = (vertexDataUploaded / (uint64)elapsedFrames); + vertexDataUploadPerFrame /= 1024ULL; + uint64 vertexDataCachedPerFrame = (vertexDataCached / (uint64)elapsedFrames); + vertexDataCachedPerFrame /= 1024ULL; + uint64 uniformBankDataUploadedPerFrame = (uniformBankUploadedData / (uint64)elapsedFrames); + uniformBankDataUploadedPerFrame /= 1024ULL; + uint32 uniformBankCountUploadedPerFrame = (uint32)(uniformBankUploadedCount / (uint64)elapsedFrames); + uint64 indexDataUploadPerFrame = (indexDataUploaded / (uint64)elapsedFrames); + indexDataUploadPerFrame /= 1024ULL; + + double fps = (double)elapsedFrames2S * 1000.0 / (double)totalElapsedTimeFPS; + uint32 drawCallsPerFrame = drawCallCounter / elapsedFrames; + uint32 shaderBindsPerFrame = shaderBindCounter / elapsedFrames; + passedCycles = passedCycles * 1000ULL / totalElapsedTime; + uint32 rlps = (uint32)((uint64)recompilerLeaveCount * 1000ULL / (uint64)totalElapsedTime); + uint32 tlps = (uint32)((uint64)threadLeaveCount * 1000ULL / (uint64)totalElapsedTime); + // set stats + + // next counter cycle + sint32 nextCycleIndex = (performanceMonitor.cycleIndex + 1) % PERFORMANCE_MONITOR_TRACK_CYCLES; + performanceMonitor.cycle[nextCycleIndex].drawCallCounter = 0; + performanceMonitor.cycle[nextCycleIndex].frameCounter = 0; + performanceMonitor.cycle[nextCycleIndex].shaderBindCount = 0; + performanceMonitor.cycle[nextCycleIndex].lastCycleCount = PPCInterpreter_getMainCoreCycleCounter(); + performanceMonitor.cycle[nextCycleIndex].skippedCycles = 0; + performanceMonitor.cycle[nextCycleIndex].vertexDataUploaded = 0; + performanceMonitor.cycle[nextCycleIndex].vertexDataCached = 0; + performanceMonitor.cycle[nextCycleIndex].uniformBankUploadedData = 0; + performanceMonitor.cycle[nextCycleIndex].uniformBankUploadedCount = 0; + performanceMonitor.cycle[nextCycleIndex].indexDataUploaded = 0; + performanceMonitor.cycle[nextCycleIndex].indexDataCached = 0; + performanceMonitor.cycle[nextCycleIndex].recompilerLeaveCount = 0; + performanceMonitor.cycle[nextCycleIndex].threadLeaveCount = 0; + performanceMonitor.cycleIndex = nextCycleIndex; + + if (isFirstUpdate) + { + LatteOverlay_updateStats(0.0, 0); + gui_updateWindowTitles(false, false, 0.0); + } + else + { + LatteOverlay_updateStats(fps, drawCallCounter / elapsedFrames); + gui_updateWindowTitles(false, false, fps); + } + // next update in 1 second + performanceMonitor.cycle[performanceMonitor.cycleIndex].lastUpdate = GetTickCount(); + + // prevent hibernation and screen saver/monitor off + #if BOOST_OS_WINDOWS + SetThreadExecutionState(ES_CONTINUOUS | ES_DISPLAY_REQUIRED | ES_SYSTEM_REQUIRED); + #endif + } + LatteOverlay_updateStatsPerFrame(); +} + +void LattePerformanceMonitor_frameBegin() +{ + performanceMonitor.vk.numDrawBarriersPerFrame.reset(); + performanceMonitor.vk.numBeginRenderpassPerFrame.reset(); +} \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Core/LattePerformanceMonitor.h b/src/Cafe/HW/Latte/Core/LattePerformanceMonitor.h new file mode 100644 index 00000000..6cd07ee2 --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LattePerformanceMonitor.h @@ -0,0 +1,141 @@ +#pragma once + +#define PERFORMANCE_MONITOR_TRACK_CYCLES (5) // one cycle lasts one second + +// todo - replace PPCTimer with HighResolutionTimer.h +uint64 PPCTimer_getRawTsc(); +uint64 PPCTimer_tscToMicroseconds(uint64 us); + +class LattePerfStatTimer +{ +public: + void beginMeasuring() + { + timerStart = PPCTimer_getRawTsc(); + } + + void endMeasuring() + { + uint64 dif = PPCTimer_getRawTsc() - timerStart; + currentSum += dif; + } + + void frameFinished() + { + previousFrame = currentSum; + currentSum = 0; + } + + uint64 getPreviousFrameValue() + { + return previousFrame; + } + +private: + uint64 currentSum{}; + uint64 previousFrame{}; + uint64 timerStart{}; +}; + +class LattePerfStatCounter +{ +public: + void increment() + { + m_value++; + } + + void decrement() + { + cemu_assert_debug(m_value > 0); + m_value--; + } + + void decrement(uint32 count) + { + cemu_assert_debug(count <= m_value); + m_value -= count; + } + + uint32 get() + { + return m_value; + } + + void reset() + { + m_value = 0; + } + +private: + std::atomic_uint32_t m_value{}; +}; + +typedef struct +{ + struct + { + // CPU + volatile uint64 lastCycleCount; + volatile uint64 skippedCycles; + volatile uint32 recompilerLeaveCount; // increased everytime the recompiler switches back to interpreter + volatile uint32 threadLeaveCount; // increased everytime a thread gives up it's timeslice + // GPU + volatile uint32 lastUpdate; + volatile uint32 frameCounter; + volatile uint32 drawCallCounter; + volatile uint32 shaderBindCount; + volatile uint64 vertexDataUploaded; // amount of vertex data uploaded to GPU (bytes) + volatile uint64 vertexDataCached; // amount of vertex data reused from GPU cache (bytes) + volatile uint64 uniformBankUploadedData; // amount of uniform buffer data (excluding remapped uniforms) uploaded to GPU + volatile uint64 uniformBankUploadedCount; // number of separate uploads for uniformBankDataUploaded + volatile uint64 indexDataUploaded; + volatile uint64 indexDataCached; + }cycle[PERFORMANCE_MONITOR_TRACK_CYCLES]; + volatile sint32 cycleIndex; + // new stats + LattePerfStatTimer gpuTime_frameTime; + LattePerfStatTimer gpuTime_shaderCreate; + LattePerfStatTimer gpuTime_idleTime; // time spent waiting for new commands from CPU + LattePerfStatTimer gpuTime_fenceTime; // time spent waiting for fence condition + + LattePerfStatTimer gpuTime_dcStageTextures; // drawcall texture/mrt setup + LattePerfStatTimer gpuTime_dcStageVertexMgr; // drawcall vertex setup and upload + LattePerfStatTimer gpuTime_dcStageShaderAndUniformMgr; // drawcall shader setup and uniform management/upload + LattePerfStatTimer gpuTime_dcStageIndexMgr; // drawcall index data setup and upload + LattePerfStatTimer gpuTime_dcStageMRT; // drawcall render target API + + LattePerfStatTimer gpuTime_dcStageDrawcallAPI; // drawcall api call + LattePerfStatTimer gpuTime_waitForAsync; // waiting for operations to complete (e.g. GX2DrawDone or force texture readback) Also includes texture readback and occlusion query polling logic + + // generic + uint32 numCompiledVS; // number of compiled vertex shader programs + uint32 numCompiledGS; // number of compiled geometry shader programs + uint32 numCompiledPS; // number of compiled pixel shader programs + + // Vulkan + struct + { + LattePerfStatCounter numDescriptorSets; + LattePerfStatCounter numDescriptorDynUniformBuffers; + LattePerfStatCounter numDescriptorStorageBuffers; + LattePerfStatCounter numDescriptorSamplerTextures; + LattePerfStatCounter numGraphicPipelines; + LattePerfStatCounter numImages; + LattePerfStatCounter numImageViews; + LattePerfStatCounter numRenderPass; + LattePerfStatCounter numFramebuffer; + + // per frame + LattePerfStatCounter numDrawBarriersPerFrame; + LattePerfStatCounter numBeginRenderpassPerFrame; + }vk; +}performanceMonitor_t; + +extern performanceMonitor_t performanceMonitor; + +void LattePerformanceMonitor_frameEnd(); +void LattePerformanceMonitor_frameBegin(); + +#define beginPerfMonProfiling(__obj) if( THasProfiling ) __obj.beginMeasuring() +#define endPerfMonProfiling(__obj) if( THasProfiling ) __obj.endMeasuring() \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Core/LatteQuery.cpp b/src/Cafe/HW/Latte/Core/LatteQuery.cpp new file mode 100644 index 00000000..a8329e63 --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteQuery.cpp @@ -0,0 +1,212 @@ +#include "Cafe/HW/Latte/ISA/RegDefines.h" +#include "Cafe/HW/Latte/Core/Latte.h" +#include "Cafe/HW/Latte/Core/LatteDraw.h" + +#include "Cafe/HW/Latte/Core/LatteQueryObject.h" +#include "Cafe/HW/Latte/Renderer/Renderer.h" + +#define GPU7_QUERY_TYPE_OCCLUSION (1) + +uint64 queryEventCounter = 1; + +struct LatteGX2QueryInformation +{ + MPTR queryMPTR; + uint64 queryEventStart; + uint64 queryEventEnd; + uint64 sampleSum; + bool queryEnded; +}; + +std::vector<LatteGX2QueryInformation*> list_activeGX2Queries2; + +std::vector<LatteQueryObject*> list_queriesInFlight; + +uint64 latestQueryFinishedEventId = 0; + +LatteQueryObject* _currentlyActiveRendererQuery = {0}; + +uint64 LatteQuery_getNextEventId() +{ + uint64 ev = queryEventCounter; + queryEventCounter++; + return ev; +} + +void LatteQuery_begin(LatteQueryObject* queryObject, uint64 eventId) +{ + queryObject->queryEventStart = eventId; + queryObject->begin(); +} + +void LatteQuery_end(LatteQueryObject* queryObject, uint64 eventId) +{ + cemu_assert_debug(!queryObject->queryEnded); + queryObject->queryEnded = true; + queryObject->queryEventEnd = eventId; + queryObject->end(); +} + +LatteQueryObject* LatteQuery_createSamplePassedQuery() +{ + return g_renderer->occlusionQuery_create(); +} + +void LatteQuery_finishGX2Query(LatteGX2QueryInformation* gx2Query) +{ + uint32* queryObjectData = (uint32*)memory_getPointerFromVirtualOffset(gx2Query->queryMPTR); + *(uint64*)(queryObjectData + 0) = 0; + *(uint64*)(queryObjectData + 2) = gx2Query->sampleSum; + *(uint64*)(queryObjectData + 4) = 0; + *(uint64*)(queryObjectData + 6) = 0; + + *(uint64*)(queryObjectData + 8) = 0; // overwrites the 'OCPU' magic constant letting GX2QueryGetOcclusionResult know that the query is finished (for CPU queries) +} + +void LatteQuery_UpdateFinishedQueries() +{ + g_renderer->occlusionQuery_updateState(); + for(uint32 i=0; i<list_queriesInFlight.size(); i++) + { + LatteQueryObject* queryObject = list_queriesInFlight[i]; + cemu_assert_debug(queryObject->queryEnded); + if( queryObject->queryEnded == false ) + continue; + // check if result is available + uint64 numSamplesPassed; + if (!queryObject->getResult(numSamplesPassed)) + break; + + cemu_assert_debug(latestQueryFinishedEventId < queryObject->queryEventEnd); + latestQueryFinishedEventId = queryObject->queryEventEnd; + + // add number of passed samples to all gx2 queries that were active at the time + for (auto& it : list_activeGX2Queries2) + { + if (queryObject->queryEventStart >= it->queryEventStart && queryObject->queryEventEnd <= it->queryEventEnd) + it->sampleSum += numSamplesPassed; + } + + list_queriesInFlight.erase(list_queriesInFlight.begin() + i); + i--; + g_renderer->occlusionQuery_destroy(queryObject); + } + // check for finished GX2 queries + for (sint32 i = 0; i < list_activeGX2Queries2.size(); i++) + { + auto gx2Query = list_activeGX2Queries2[i]; + if (gx2Query->queryEnded && latestQueryFinishedEventId >= gx2Query->queryEventEnd) + { + LatteQuery_finishGX2Query(gx2Query); + free(gx2Query); + list_activeGX2Queries2.erase(list_activeGX2Queries2.begin() + i); + i--; + } + } +} + +void LatteQuery_UpdateFinishedQueriesForceFinishAll() +{ + cemu_assert_debug(_currentlyActiveRendererQuery == nullptr); + g_renderer->occlusionQuery_flush(); // guarantees that all query commands have been submitted and finished processing + while (true) + { + LatteQuery_UpdateFinishedQueries(); + if (list_queriesInFlight.empty()) + break; + } +} + +sint32 checkQueriesCounter = 0; + +void LatteQuery_endActiveRendererQuery(uint64 currentEventId) +{ + if (_currentlyActiveRendererQuery != nullptr) + { + LatteQuery_end(_currentlyActiveRendererQuery, currentEventId); + list_queriesInFlight.emplace_back(_currentlyActiveRendererQuery); + _currentlyActiveRendererQuery = nullptr; + } +} + +void LatteQuery_BeginOcclusionQuery(MPTR queryMPTR) +{ + if (checkQueriesCounter < 7) + { + checkQueriesCounter++; + } + else + { + LatteQuery_UpdateFinishedQueries(); + checkQueriesCounter = 0; + } + + for(auto& it : list_activeGX2Queries2) + { + if (it->queryMPTR == queryMPTR) + { + debug_printf("itHLEBeginOcclusionQuery: Query 0x%08x is already active\n", queryMPTR); + return; + } + } + uint64 currentEventId = LatteQuery_getNextEventId(); + // end any currently active query + LatteQuery_endActiveRendererQuery(currentEventId); + // create GX2 query binding + LatteGX2QueryInformation* queryBinding = (LatteGX2QueryInformation*)malloc(sizeof(LatteGX2QueryInformation)); + memset(queryBinding, 0x00, sizeof(LatteGX2QueryInformation)); + queryBinding->queryEventStart = currentEventId; + queryBinding->queryMPTR = queryMPTR; + list_activeGX2Queries2.emplace_back(queryBinding); + // start renderer query + LatteQueryObject* queryObject = LatteQuery_createSamplePassedQuery(); + LatteQuery_begin(queryObject, currentEventId); + _currentlyActiveRendererQuery = queryObject; +} + +void LatteQuery_EndOcclusionQuery(MPTR queryMPTR) +{ + if (queryMPTR == MPTR_NULL) + return; + uint64 currentEventId = LatteQuery_getNextEventId(); + // mark query binding as ended + for(auto& it : list_activeGX2Queries2) + { + if (it->queryMPTR == queryMPTR) + { + it->queryEventEnd = currentEventId; + it->queryEnded = true; + break; + } + } + // end currently active renderer query + LatteQuery_endActiveRendererQuery(currentEventId); + // check if there are still active GX2 queries + bool hasActiveGX2Query = false; + for (auto& it : list_activeGX2Queries2) + { + if (!it->queryEnded) + { + hasActiveGX2Query = true; + break; + } + } + // start a new renderer query if there are still active GX2 queries + if (hasActiveGX2Query) + { + LatteQueryObject* queryObject = LatteQuery_createSamplePassedQuery(); + LatteQuery_begin(queryObject, currentEventId); + list_queriesInFlight.emplace_back(queryObject); + _currentlyActiveRendererQuery = queryObject; + catchOpenGLError(); + } +} + +void LatteQuery_CancelActiveGPU7Queries() +{ + cemu_assert_debug(_currentlyActiveRendererQuery == nullptr); +} + +void LatteQuery_Init() +{ +} \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Core/LatteQueryObject.h b/src/Cafe/HW/Latte/Core/LatteQueryObject.h new file mode 100644 index 00000000..a160dbde --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteQueryObject.h @@ -0,0 +1,14 @@ +#pragma once + +class LatteQueryObject +{ +public: + virtual bool getResult(uint64& numSamplesPassed) = 0; + virtual void begin() = 0; + virtual void end() = 0; + + uint32 index{}; + bool queryEnded{}; // set to true once the query is ended, but the result may not be available for some time after this + uint64 queryEventStart{}; + uint64 queryEventEnd{}; +}; \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp new file mode 100644 index 00000000..52a6f040 --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp @@ -0,0 +1,1133 @@ +#include "Cafe/HW/Latte/ISA/RegDefines.h" + +#include "Cafe/HW/Latte/Core/Latte.h" +#include "Cafe/HW/Latte/Core/LatteDraw.h" +#include "Cafe/HW/Latte/Core/LatteShader.h" +#include "Cafe/HW/Latte/Core/LatteOverlay.h" +#include "Cafe/HW/Latte/Core/LatteBufferCache.h" +#include "Cafe/HW/Latte/Core/LatteTexture.h" +#include "Cafe/HW/Latte/Core/LatteCachedFBO.h" +#include "Cafe/HW/Latte/Renderer/Renderer.h" +#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" +#include "Cafe/GraphicPack/GraphicPack2.h" +#include "config/ActiveSettings.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" +#include "gui/guiWrapper.h" +#include "Cafe/OS/libs/erreula/erreula.h" +#include "input/InputManager.h" +#include "Cafe/OS/libs/swkbd/swkbd.h" + +uint32 prevScissorX = 0; +uint32 prevScissorY = 0; +uint32 prevScissorWidth = 0; +uint32 prevScissorHeight = 0; + +bool hasValidFramebufferAttached = false; + +struct LatteMRTQuad +{ + sint32 width; + sint32 height; +}; + +struct +{ + LatteMRTQuad currentRenderSize; + LatteMRTQuad currentEffectiveSize; + struct + { + sint32 width; + sint32 height; + }currentGuestViewport; + bool renderTargetIsResized; + // tracking + sint32 rtUpdateListCount; + LatteTextureView* rtUpdateList[64]; + sint32 rtUpdateListSlice[64]; + sint32 rtUpdateListMip[64]; +}sLatteRenderTargetState; + +struct +{ + struct + { + LatteTextureView* view{}; + }colorBuffer[8]; + struct + { + LatteTextureView* view{}; + bool hasStencil{false}; + }depthBuffer; +}sLatteCurrentRendertargets{}; + +LatteCachedFBO::LatteCachedFBO(uint64 key) : key(key) +{ + for (sint32 i = 0; i < 8; i++) + { + LatteTextureView* colorTexView = sLatteCurrentRendertargets.colorBuffer[i].view; + colorBuffer[i].texture = colorTexView; + if (colorTexView) + { + vectorAppendUnique(colorTexView->list_associatedFbo, this); + m_referencedTextures.emplace_back(colorTexView->baseTexture); + } + } + + if (sLatteCurrentRendertargets.depthBuffer.view) + { + LatteTextureView* depthTexView = sLatteCurrentRendertargets.depthBuffer.view; + depthBuffer.texture = depthTexView; + depthBuffer.hasStencil = sLatteCurrentRendertargets.depthBuffer.hasStencil; + if (depthTexView) + { + vectorAppendUnique(depthTexView->list_associatedFbo, this); + m_referencedTextures.emplace_back(depthTexView->baseTexture); + } + } + + calculateEffectiveRenderAreaSize(); +} + +void LatteMRT::NotifyTextureDeletion(LatteTexture* texture) +{ + for (sint32 i = 0; i < Latte::GPU_LIMITS::NUM_COLOR_ATTACHMENTS; i++) + { + if (sLatteCurrentRendertargets.colorBuffer[i].view && sLatteCurrentRendertargets.colorBuffer[i].view->baseTexture == texture) + { + sLatteCurrentRendertargets.colorBuffer[i].view = nullptr; + } + } +} + +LatteCachedFBO* LatteMRT::CreateCachedFBO(uint64 key) +{ + return g_renderer->rendertarget_createCachedFBO(key); +} + +void LatteMRT::DeleteCachedFBO(LatteCachedFBO* cfbo) +{ + // color textures + for (sint32 i = 0; i < 8; i++) + { + if (cfbo->colorBuffer[i].texture == NULL) + continue; + cfbo->colorBuffer[i].texture->list_fboLookup.erase(std::remove(cfbo->colorBuffer[i].texture->list_fboLookup.begin(), cfbo->colorBuffer[i].texture->list_fboLookup.end(), cfbo), cfbo->colorBuffer[i].texture->list_fboLookup.end()); + cfbo->colorBuffer[i].texture->list_associatedFbo.erase(std::remove(cfbo->colorBuffer[i].texture->list_associatedFbo.begin(), cfbo->colorBuffer[i].texture->list_associatedFbo.end(), cfbo), cfbo->colorBuffer[i].texture->list_associatedFbo.end()); + } + // depth texture + if (cfbo->depthBuffer.texture) + { + cfbo->depthBuffer.texture->list_fboLookup.erase(std::remove(cfbo->depthBuffer.texture->list_fboLookup.begin(), cfbo->depthBuffer.texture->list_fboLookup.end(), cfbo), cfbo->depthBuffer.texture->list_fboLookup.end()); + cfbo->depthBuffer.texture->list_associatedFbo.erase(std::remove(cfbo->depthBuffer.texture->list_associatedFbo.begin(), cfbo->depthBuffer.texture->list_associatedFbo.end(), cfbo), cfbo->depthBuffer.texture->list_associatedFbo.end()); + } + g_renderer->rendertarget_deleteCachedFBO(cfbo); + delete cfbo; +} + +LatteCachedFBO* g_emptyFBO = nullptr; + +void LatteMRT::SetColorAttachment(uint32 index, LatteTextureView* view) +{ + sLatteCurrentRendertargets.colorBuffer[index].view = view; +} + +void LatteMRT::SetDepthAndStencilAttachment(LatteTextureView* view, bool hasStencil) +{ + sLatteCurrentRendertargets.depthBuffer.view = view; + sLatteCurrentRendertargets.depthBuffer.hasStencil = hasStencil; +} + +LatteTextureView* LatteMRT::GetColorAttachment(uint32 index) +{ + cemu_assert_debug(index < 8); + return sLatteCurrentRendertargets.colorBuffer[index].view; +} + +LatteTextureView* LatteMRT::GetDepthAttachment() +{ + return sLatteCurrentRendertargets.depthBuffer.view; +} + +void LatteMRT::ApplyCurrentState() +{ + uint64 key = 0; + LatteTextureView* fboLookupView = NULL; + for (sint32 i = 0; i < 8; i++) + { + LatteTextureView* colorView = sLatteCurrentRendertargets.colorBuffer[i].view; + if (colorView) + { + key += ((uint64)colorView); + key = _rotl64(key, 5); + fboLookupView = colorView; + } + key = _rotl64(key, 7); + } + if (sLatteCurrentRendertargets.depthBuffer.view) + { + key += ((uint64)sLatteCurrentRendertargets.depthBuffer.view); + key = _rotl64(key, 5); + key += (sLatteCurrentRendertargets.depthBuffer.hasStencil); + if (fboLookupView == NULL) + { + fboLookupView = sLatteCurrentRendertargets.depthBuffer.view; + } + } + // use fboLookupTexture to find cached FBO + if (fboLookupView == nullptr) + { + if (!g_emptyFBO) + g_emptyFBO = CreateCachedFBO(key); + g_renderer->rendertarget_bindFramebufferObject(g_emptyFBO); + return; + } + // look for FBO + for (auto& fbo : fboLookupView->list_fboLookup) + { + if (fbo->key == key) + { + // found matching FBO + g_renderer->rendertarget_bindFramebufferObject(fbo); + return; + } + } + // create new cached FBO + LatteCachedFBO* cfbo = CreateCachedFBO(key); + g_renderer->rendertarget_bindFramebufferObject(cfbo); + fboLookupView->list_fboLookup.push_back(cfbo); + // some extra checks to verify that looked up fbo matches active buffers + cemu_assert_debug(cfbo->colorBuffer[0].texture == sLatteCurrentRendertargets.colorBuffer[0].view); + cemu_assert_debug(cfbo->colorBuffer[1].texture == sLatteCurrentRendertargets.colorBuffer[1].view); + cemu_assert_debug(cfbo->colorBuffer[2].texture == sLatteCurrentRendertargets.colorBuffer[2].view); + cemu_assert_debug(cfbo->depthBuffer.texture == sLatteCurrentRendertargets.depthBuffer.view); +} + +void LatteMRT::BindColorBufferOnly(LatteTextureView* view) +{ + cemu_assert_debug(!view->baseTexture->isDepth); + SetColorAttachment(0, view); + for (sint32 i = 1; i < 8; i++) + SetColorAttachment(i, nullptr); + SetDepthAndStencilAttachment(nullptr, false); + ApplyCurrentState(); +} + +void LatteMRT::BindDepthBufferOnly(LatteTextureView* view) +{ + cemu_assert_debug(view->baseTexture->isDepth); + for (sint32 i = 0; i < 8; i++) + SetColorAttachment(i, nullptr); + SetDepthAndStencilAttachment(view, view->baseTexture->hasStencil); + ApplyCurrentState(); +} + +/***************************************************/ + +LatteTextureView* LatteMRT_FindColorBufferForClearing(MPTR colorBufferPtr, sint32 colorBufferWidth, sint32 colorBufferHeight, sint32 colorBufferPitch, uint32 format, sint32 sliceIndex, sint32* searchIndex) +{ + LatteTextureView* view = LatteTC_LookupTextureByData(colorBufferPtr, colorBufferWidth, colorBufferHeight, colorBufferPitch, 0, 1, sliceIndex, 1, searchIndex); + if (view == nullptr) + return nullptr; + return view; +} + +LatteTextureView* LatteMRT_CreateColorBuffer(MPTR colorBufferPhysMem, uint32 width, uint32 height, uint32 pitch, Latte::E_GX2SURFFMT format, Latte::E_HWTILEMODE tileMode, uint32 swizzle, uint32 viewSlice) +{ + cemu_assert_debug(colorBufferPhysMem != MPTR_NULL); + LatteTextureView* textureView; + if(viewSlice != 0) + textureView = LatteTexture_CreateMapping(colorBufferPhysMem, MPTR_NULL, width, height, viewSlice+1, pitch, tileMode, swizzle, 0, 1, viewSlice, 1, format, Latte::E_DIM::DIM_2D_ARRAY, Latte::E_DIM::DIM_2D, false); + else + textureView = LatteTexture_CreateMapping(colorBufferPhysMem, MPTR_NULL, width, height, 1, pitch, tileMode, swizzle, 0, 1, viewSlice, 1, format, Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, false); + // unbind texture + g_renderer->texture_bindAndActivate(nullptr, 0); + return textureView; +} + +LatteTextureView* LatteMRT_CreateDepthBuffer(MPTR depthBufferPhysMem, uint32 width, uint32 height, uint32 pitch, Latte::E_HWTILEMODE tileMode, Latte::E_GX2SURFFMT format, uint32 swizzle, sint32 viewSlice) +{ + LatteTextureView* textureView; + if(viewSlice == 0) + textureView = LatteTexture_CreateMapping(depthBufferPhysMem, MPTR_NULL, width, height, 1, pitch, tileMode, swizzle, 0, 1, viewSlice, 1, format, Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, true); + else + textureView = LatteTexture_CreateMapping(depthBufferPhysMem, MPTR_NULL, width, height, viewSlice+1, pitch, tileMode, swizzle, 0, 1, viewSlice, 1, format, Latte::E_DIM::DIM_2D_ARRAY, Latte::E_DIM::DIM_2D, true); + + LatteMRT::SetDepthAndStencilAttachment(textureView, textureView->baseTexture->hasStencil); + // unbind texture + g_renderer->texture_bindAndActivate(nullptr, 0); + return textureView; +} + +sint32 _depthBufferSizeWarningCount = 0; + +LatteTextureView* LatteMRT::GetColorAttachmentTexture(uint32 index, bool createNew, bool checkForTextureChanges) +{ + uint32* colorBufferRegBase = LatteGPUState.contextRegister+(mmCB_COLOR0_BASE + index); + uint32 regColorBufferBase = colorBufferRegBase[mmCB_COLOR0_BASE - mmCB_COLOR0_BASE] & 0xFFFFFF00; // the low 8 bits are ignored? How to Survive seems to rely on this + uint32 regColorSize = colorBufferRegBase[mmCB_COLOR0_SIZE - mmCB_COLOR0_BASE]; + uint32 regColorInfo = colorBufferRegBase[mmCB_COLOR0_INFO - mmCB_COLOR0_BASE]; + uint32 regColorView = colorBufferRegBase[mmCB_COLOR0_VIEW - mmCB_COLOR0_BASE]; + // decode color buffer reg info + Latte::E_HWTILEMODE colorBufferTileMode = (Latte::E_HWTILEMODE)((regColorInfo >> 8) & 0xF); + uint32 numberType = (regColorInfo >> 12) & 7; + Latte::E_GX2SURFFMT colorBufferFormat = GetColorBufferFormat(index, LatteGPUState.contextNew); + + MPTR colorBufferPhysMem = regColorBufferBase; + + uint32 colorBufferSwizzle = 0; + if ( Latte::TM_IsMacroTiled(colorBufferTileMode) ) + { + colorBufferSwizzle = colorBufferPhysMem & 0x700; + colorBufferPhysMem = colorBufferPhysMem & ~(7 << 8); + } + + // get view slice and view slice num + uint32 viewFirstSlice = (regColorView & 0x7FF); + uint32 viewNumSlices = ((regColorView >> 13) & 0x7FF) - viewFirstSlice + 1; + if (viewNumSlices != 1) + { + debug_printf("viewNumSlices is not 1! (%d)\n", viewNumSlices); + } + uint32 colorBufferPitch = (((regColorSize >> 0) & 0x3FF) + 1); + colorBufferPitch <<= 3; + uint32 pitchHeight = (((regColorSize >> 10) & 0xFFFFF) + 1); + pitchHeight <<= 6; + uint32 colorBufferHeight = pitchHeight / colorBufferPitch; + uint32 colorBufferWidth = colorBufferPitch; + + bool colorBufferWasFound = false; + sint32 viewFirstMip = 0; // todo + + cemu_assert_debug(viewNumSlices == 1); + + LatteTextureView* colorBufferView = LatteTextureViewLookupCache::lookupSliceEx(colorBufferPhysMem, colorBufferWidth, colorBufferHeight, colorBufferPitch, viewFirstMip, viewFirstSlice, colorBufferFormat, false); + + if (colorBufferView == nullptr) + { + // create color buffer view + colorBufferView = LatteTexture_CreateMapping(colorBufferPhysMem, 0, colorBufferWidth, colorBufferHeight, (viewFirstSlice + viewNumSlices), colorBufferPitch, colorBufferTileMode, colorBufferSwizzle>>8, viewFirstMip, 1, viewFirstSlice, viewNumSlices, (Latte::E_GX2SURFFMT)colorBufferFormat, (viewFirstSlice + viewNumSlices)>1? Latte::E_DIM::DIM_2D_ARRAY: Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, false); + LatteGPUState.repeatTextureInitialization = true; + checkForTextureChanges = false; + } + + if (colorBufferView->baseTexture->swizzle != colorBufferSwizzle) + { + colorBufferView->baseTexture->lastRenderTargetSwizzle = colorBufferSwizzle; + } + + // check for texture changes + if (checkForTextureChanges) + LatteTexture_UpdateDataToLatest(colorBufferView->baseTexture); + // mark as used + LatteTC_MarkTextureStillInUse(colorBufferView->baseTexture); + return colorBufferView; +} + +// get mask of all used color buffers +uint8 LatteMRT::GetActiveColorBufferMask(const LatteDecompilerShader* pixelShader, const LatteContextRegister& lcr) +{ + const uint32* regView = lcr.GetRawView(); + + uint8 colorBufferMask = 0; + for (uint32 i = 0; i < 8; i++) + { + if (regView[mmCB_COLOR0_BASE + i] != MPTR_NULL) + colorBufferMask |= (1 << i); + } + // check if color buffer output is active + const Latte::LATTE_CB_COLOR_CONTROL& colorControlReg = lcr.CB_COLOR_CONTROL; + uint32 colorBufferDisable = colorControlReg.get_SPECIAL_OP() == Latte::LATTE_CB_COLOR_CONTROL::E_SPECIALOP::DISABLE; + if (colorBufferDisable) + return 0; + cemu_assert_debug(colorControlReg.get_DEGAMMA_ENABLE() == false); // not supported + // combine color buffer mask with pixel output mask from pixel shader + colorBufferMask &= pixelShader->pixelColorOutputMask; + // combine color buffer mask with color channel mask from mmCB_TARGET_MASK (disable render buffer if all colors are blocked) + uint32 channelTargetMask = lcr.CB_TARGET_MASK.get_MASK(); + for (uint32 i = 0; i < 8; i++) + { + if (((channelTargetMask >> (i * 4)) & 0xF) == 0) + colorBufferMask &= ~(1 << i); + } + + // render targets smaller than the scissor size are not allowed + // this fixes a few render issues in Cemu but we dont know if this matches HW behavior + cemu_assert_debug(lcr.PA_SC_GENERIC_SCISSOR_TL.get_WINDOW_OFFSET_DISABLE() == true); // todo (not exposed by GX2 API) + uint32 scissorAccessWidth = lcr.PA_SC_GENERIC_SCISSOR_BR.get_BR_X(); + uint32 scissorAccessHeight = lcr.PA_SC_GENERIC_SCISSOR_BR.get_BR_Y(); + for (uint32 i = 0; i < 8; i++) + { + if( (colorBufferMask&(1<<i)) == 0 ) + continue; + // get width/height + uint32 regColorSize = regView[mmCB_COLOR0_SIZE + i]; + uint32 regColorInfo = regView[mmCB_COLOR0_INFO + i]; + // decode color buffer reg info + uint32 colorBufferPitch = (((regColorSize >> 0) & 0x3FF) + 1); + colorBufferPitch <<= 3; + uint32 pitchHeight = (((regColorSize >> 10) & 0xFFFFF) + 1); + pitchHeight <<= 6; + uint32 colorBufferHeight = pitchHeight / colorBufferPitch; + uint32 colorBufferWidth = colorBufferPitch; + + if ((colorBufferWidth < (sint32)scissorAccessWidth) || + (colorBufferHeight < (sint32)scissorAccessHeight)) + { + colorBufferMask &= ~(1<<i); + } + + } + + return colorBufferMask; +} + +// returns true if depth/stencil buffer is used +bool LatteMRT::GetActiveDepthBufferMask(const LatteContextRegister& lcr) +{ + bool depthBufferMask = true; + // if depth test is not used then detach the depth buffer + bool depthEnable = lcr.DB_DEPTH_CONTROL.get_Z_ENABLE(); + bool stencilTestEnable = lcr.DB_DEPTH_CONTROL.get_STENCIL_ENABLE(); + bool backStencilEnable = lcr.DB_DEPTH_CONTROL.get_BACK_STENCIL_ENABLE(); + + if (!depthEnable && !stencilTestEnable && !backStencilEnable) + depthBufferMask = false; + + return depthBufferMask; +} + +const uint32 _colorBufferFormatBits[] = +{ + 0, // 0 + 0x200, // 1 + 0, // 2 + 0, // 3 + 0x100, // 4 + 0x300, // 5 + 0x400, // 6 + 0x800, // 7 +}; + +Latte::E_GX2SURFFMT LatteMRT::GetColorBufferFormat(const uint32 index, const LatteContextRegister& lcr) +{ + cemu_assert_debug(index < Latte::GPU_LIMITS::NUM_COLOR_ATTACHMENTS); + uint32 regColorInfo = lcr.GetRawView()[mmCB_COLOR0_INFO + index]; + uint32 colorBufferFormat = (regColorInfo >> 2) & 0x3F; // base HW format + uint32 numberType = (regColorInfo >> 12) & 7; + colorBufferFormat |= _colorBufferFormatBits[numberType]; + return (Latte::E_GX2SURFFMT)colorBufferFormat; +} + +// return GX2 format of current depth buffer +Latte::E_GX2SURFFMT LatteMRT::GetDepthBufferFormat(const LatteContextRegister& lcr) +{ + uint32 regDepthBufferInfo = lcr.GetRawView()[mmDB_DEPTH_INFO]; + switch (regDepthBufferInfo & 7) + { + case 1: + return Latte::E_GX2SURFFMT::D16_UNORM; + case 3: + return Latte::E_GX2SURFFMT::D24_S8_UNORM; + case 5: + return Latte::E_GX2SURFFMT::D24_S8_FLOAT; + case 6: + return Latte::E_GX2SURFFMT::D32_FLOAT; + case 7: + return Latte::E_GX2SURFFMT::D32_S8_FLOAT; + default: + debug_printf("Invalid DB_DEPTH_INFO depthbuffer format (%d)\n", (regDepthBufferInfo & 7)); + break; + } + return Latte::E_GX2SURFFMT::D16_UNORM; +} + +bool LatteMRT::UpdateCurrentFBO() +{ + catchOpenGLError(); + sLatteRenderTargetState.rtUpdateListCount = 0; + // combine color buffer mask with pixel output mask from pixel shader + LatteDecompilerShader* pixelShader = LatteSHRC_GetActivePixelShader(); + uint8 colorBufferMask = GetActiveColorBufferMask(pixelShader, LatteGPUState.contextNew); + bool depthBufferMask = GetActiveDepthBufferMask(LatteGPUState.contextNew); + + // if depth test is not used then detach the depth buffer + bool depthEnable = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_Z_ENABLE(); + bool stencilTestEnable = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_STENCIL_ENABLE(); + bool backStencilEnable = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_BACK_STENCIL_ENABLE(); + + if (!depthEnable && !stencilTestEnable && !backStencilEnable) + depthBufferMask = false; + + bool hasResizedTexture = false; // set to true if any of the color buffers or the depth buffer reference a resized texture (via graphic pack texture rules) + sLatteRenderTargetState.renderTargetIsResized = false; + // real size + LatteMRTQuad* rtRealSize = &sLatteRenderTargetState.currentRenderSize; + rtRealSize->width = 0; + rtRealSize->height = 0; + // effective size (differs from real size only if graphic pack rules overwrite texture sizes) + LatteMRTQuad* rtEffectiveSize = &sLatteRenderTargetState.currentEffectiveSize; + rtEffectiveSize->width = 0; + rtEffectiveSize->height = 0; + // get scissor box + cemu_assert_debug(LatteGPUState.contextNew.PA_SC_GENERIC_SCISSOR_TL.get_WINDOW_OFFSET_DISABLE() == true); // todo (not exposed by GX2 API?) + uint32 scissorX = LatteGPUState.contextNew.PA_SC_GENERIC_SCISSOR_TL.get_TL_X(); + uint32 scissorY = LatteGPUState.contextNew.PA_SC_GENERIC_SCISSOR_TL.get_TL_Y(); + uint32 scissorWidth = LatteGPUState.contextNew.PA_SC_GENERIC_SCISSOR_BR.get_BR_X() - scissorX; + uint32 scissorHeight = LatteGPUState.contextNew.PA_SC_GENERIC_SCISSOR_BR.get_BR_Y() - scissorY; + uint32 scissorAccessWidth = scissorX + scissorWidth; + uint32 scissorAccessHeight = scissorY + scissorHeight; + // color buffers + for (uint32 i = 0; i < Latte::GPU_LIMITS::NUM_COLOR_ATTACHMENTS; i++) + { + if (((colorBufferMask)&(1 << i)) == 0) + { + // unbind + SetColorAttachment(i, nullptr); + continue; + } + LatteTextureView* colorAttachmentView = GetColorAttachmentTexture(i, true, true); + SetColorAttachment(i, colorAttachmentView); + // after the drawcall mark the texture as updated + sLatteRenderTargetState.rtUpdateList[sLatteRenderTargetState.rtUpdateListCount] = colorAttachmentView; + sLatteRenderTargetState.rtUpdateListCount++; + + sint32 colorAttachmentWidth; + sint32 colorAttachmentHeight; + LatteTexture_getSize(colorAttachmentView->baseTexture, &colorAttachmentWidth, &colorAttachmentHeight, nullptr, colorAttachmentView->firstMip); + + // set effective size + sint32 effectiveWidth, effectiveHeight; + LatteTexture_getEffectiveSize(colorAttachmentView->baseTexture, &effectiveWidth, &effectiveHeight, nullptr, colorAttachmentView->firstMip); + if( rtEffectiveSize->width == 0 && rtEffectiveSize->height == 0 ) + { + rtEffectiveSize->width = effectiveWidth; + rtEffectiveSize->height = effectiveHeight; + } + else if( rtEffectiveSize->width != effectiveWidth && rtEffectiveSize->height != effectiveHeight ) + { +#ifndef PUBLIC_RELEASE + forceLog_printf("Color buffer size mismatch (%dx%d). Effective size: %dx%d Real size: %dx%d Mismatching texture: %08x %dx%d fmt %04x", rtEffectiveSize->width, rtEffectiveSize->height, effectiveWidth, effectiveHeight, colorAttachmentView->baseTexture->width, colorAttachmentView->baseTexture->height, colorAttachmentView->baseTexture->physAddress, colorAttachmentView->baseTexture->width, colorAttachmentView->baseTexture->height, (uint32)colorAttachmentView->baseTexture->format); +#endif + } + // currently the first color attachment defines the size of the current render target + if (rtRealSize->width == 0 && rtRealSize->height == 0) + { + rtRealSize->width = colorAttachmentWidth; + rtRealSize->height = colorAttachmentHeight; + } + + if (colorAttachmentView) + continue; + } + // depth buffer + if (depthBufferMask) + { + uint32 regDepthBuffer = LatteGPUState.contextRegister[mmDB_HTILE_DATA_BASE]; + uint32 regDepthSize = LatteGPUState.contextRegister[mmDB_DEPTH_SIZE]; + uint32 regDepthBufferInfo = LatteGPUState.contextRegister[mmDB_DEPTH_INFO]; + + // get format and tileMode from info reg + Latte::E_GX2SURFFMT depthBufferFormat = GetDepthBufferFormat(LatteGPUState.contextNew); + Latte::E_HWTILEMODE depthBufferTileMode = (Latte::E_HWTILEMODE)((regDepthBufferInfo >> 15) & 0xF); + MPTR depthBufferPhysMem = regDepthBuffer << 8; + + uint32 depthBufferPitch = (((regDepthSize >> 0) & 0x3FF) + 1); + uint32 depthBufferHeight = ((((regDepthSize >> 10) & 0xFFFFF) + 1) / depthBufferPitch); + depthBufferPitch <<= 3; + depthBufferHeight <<= 3; + uint32 depthBufferWidth = depthBufferPitch; + + if (depthBufferWidth < 2) + { + debug_printf("depthBufferWidth has invalid value %d\n", depthBufferWidth); + depthBufferWidth = 2; + } + if (depthBufferHeight < 2) + { + debug_printf("depthBufferHeight has invalid value %d\n", depthBufferHeight); + depthBufferHeight = 2; + } + + bool blockDepthBuffer = false; + if (scissorAccessWidth > depthBufferPitch || scissorAccessHeight > depthBufferHeight) + { + SetDepthAndStencilAttachment(nullptr, false); + blockDepthBuffer = true; + // set effective size + if( rtEffectiveSize->width == 0 && rtEffectiveSize->height == 0 ) + { + rtEffectiveSize->width = rtRealSize->width; + rtEffectiveSize->height = rtRealSize->height; + } + } + + if (blockDepthBuffer == false) + { + if (rtRealSize->width == 0) + { + rtRealSize->width = depthBufferWidth; + rtRealSize->height = depthBufferHeight; + } + uint32 regDepthView = LatteGPUState.contextRegister[mmDB_DEPTH_VIEW]; + uint32 depthBufferViewFirstSlice = (regDepthView & 0x7FF); + uint32 depthBufferViewNumSlices = ((regDepthView >> 13) & 0x7FF) - depthBufferViewFirstSlice + 1; + cemu_assert_debug(depthBufferViewNumSlices == 1); // binding multiple layers makes no sense? + + uint32 depthBufferSwizzle = 0; + if (Latte::TM_IsMacroTiled(depthBufferTileMode)) + { + depthBufferSwizzle = (depthBufferPhysMem >> 8) & 7; + depthBufferPhysMem = depthBufferPhysMem & ~(7 << 8); + } + + if (depthBufferPhysMem != MPTR_NULL) + { + bool depthBufferWasFound = false; + LatteTextureView* depthBufferView = LatteTextureViewLookupCache::lookupSliceEx(depthBufferPhysMem, depthBufferWidth, depthBufferHeight, depthBufferPitch, 0, depthBufferViewFirstSlice, depthBufferFormat, true); + if (depthBufferView == nullptr) + { + // create depth buffer view + forceLogDebug_printf("Creating depth buffer tex %08x %dx%d slice %d", depthBufferPhysMem, depthBufferHeight, depthBufferWidth, depthBufferViewFirstSlice); + if(depthBufferViewFirstSlice == 0) + depthBufferView = LatteTexture_CreateMapping(depthBufferPhysMem, 0, depthBufferWidth, depthBufferHeight, 1, depthBufferPitch, depthBufferTileMode, depthBufferSwizzle, 0, 1, 0, 1, depthBufferFormat, Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, true); + else + depthBufferView = LatteTexture_CreateMapping(depthBufferPhysMem, 0, depthBufferWidth, depthBufferHeight, depthBufferViewFirstSlice+1, depthBufferPitch, depthBufferTileMode, depthBufferSwizzle, 0, 1, depthBufferViewFirstSlice, 1, depthBufferFormat, Latte::E_DIM::DIM_2D_ARRAY, Latte::E_DIM::DIM_2D, true); + LatteGPUState.repeatTextureInitialization = true; + } + else + { + // check for texture changes + LatteTexture_UpdateDataToLatest(depthBufferView->baseTexture); + } + // set effective size + sint32 effectiveWidth, effectiveHeight; + LatteTexture_getEffectiveSize(depthBufferView->baseTexture, &effectiveWidth, &effectiveHeight, NULL); + if (rtEffectiveSize->width == 0 && rtEffectiveSize->height == 0) + { + rtEffectiveSize->width = effectiveWidth; + rtEffectiveSize->height = effectiveHeight; + } + else if (rtEffectiveSize->width > effectiveWidth && rtEffectiveSize->height > effectiveHeight) + { + if (_depthBufferSizeWarningCount < 100) + { + forceLogDebug_printf("Depth buffer size too small. Effective size: %dx%d Real size: %dx%d Mismatching texture: %08x %dx%d fmt %04x", effectiveWidth, effectiveHeight, depthBufferView->baseTexture->width, depthBufferView->baseTexture->height, depthBufferView->baseTexture->physAddress, depthBufferView->baseTexture->width, depthBufferView->baseTexture->height, (uint32)depthBufferView->baseTexture->format); + _depthBufferSizeWarningCount++; + } + } + LatteTC_MarkTextureStillInUse(depthBufferView->baseTexture); + // after the drawcall mark the texture as updated + sLatteRenderTargetState.rtUpdateList[sLatteRenderTargetState.rtUpdateListCount] = depthBufferView; + sLatteRenderTargetState.rtUpdateListCount++; + SetDepthAndStencilAttachment(depthBufferView, depthBufferView->baseTexture->hasStencil); + } + } + else + { + SetDepthAndStencilAttachment(nullptr, false); + } + } + else + { + SetDepthAndStencilAttachment(nullptr, false); + } + catchOpenGLError(); + if (colorBufferMask || depthBufferMask) + { + hasValidFramebufferAttached = true; + } + else + { + hasValidFramebufferAttached = false; + return true; + } + if( rtEffectiveSize->width != rtRealSize->width || rtEffectiveSize->height != rtRealSize->height ) + { + //debug_printf("RenderTarget rescaled. Real: %dx%d Resized: %dx%d\n", rtRealSize->width, rtRealSize->height, rtEffectiveSize->width, rtEffectiveSize->height); + sLatteRenderTargetState.renderTargetIsResized = true; + } + if (sLatteRenderTargetState.currentEffectiveSize.width == 0) + { + debug_printf("Render target effective size is 0. May indicate a bug in Cemu or invalid color/depth buffers\n"); + return false; + } + return true; +} + +// return a vec4 with xy being the scale ratio for render target extend and zw being the virtual viewport dimensions +void LatteMRT::GetCurrentFragCoordScale(float* coordScale) +{ + if (sLatteRenderTargetState.renderTargetIsResized) + { + coordScale[0] = (float)sLatteRenderTargetState.currentRenderSize.width / (float)sLatteRenderTargetState.currentEffectiveSize.width; + coordScale[1] = (float)sLatteRenderTargetState.currentRenderSize.height / (float)sLatteRenderTargetState.currentEffectiveSize.height; + coordScale[2] = sLatteRenderTargetState.currentGuestViewport.width; + coordScale[3] = sLatteRenderTargetState.currentGuestViewport.height; + } + else + { + coordScale[0] = 1.0f; + coordScale[1] = 1.0f; + coordScale[2] = sLatteRenderTargetState.currentGuestViewport.width; + coordScale[3] = sLatteRenderTargetState.currentGuestViewport.height; + } +} + +void LatteMRT::GetVirtualViewportDimensions(sint32& width, sint32& height) +{ + width = sLatteRenderTargetState.currentGuestViewport.width; + height = sLatteRenderTargetState.currentGuestViewport.height; +} + +// flag all FBO textures as updated via GPU +// also handle texture readback +void LatteRenderTarget_trackUpdates() +{ + // after the drawcall, mark the render target textures as dynamically updated + uint64 eventCounter = LatteTexture_getNextUpdateEventCounter(); + for (sint32 i = 0; i < sLatteRenderTargetState.rtUpdateListCount; i++) + { + LatteTextureView* texView = sLatteRenderTargetState.rtUpdateList[i]; + LatteTexture* baseTexture = texView->baseTexture; + LatteTexture_TrackTextureGPUWrite(baseTexture, texView->firstSlice, texView->firstMip, eventCounter); + // texture readback + if (baseTexture->enableReadback) + { + LatteTextureReadback_Initate(texView); + } + } +} + +void LatteRenderTarget_itHLESwapScanBuffer() +{ + performanceMonitor.cycle[performanceMonitor.cycleIndex].frameCounter++; + if(LatteGPUState.frameCounter > 5) + performanceMonitor.gpuTime_frameTime.endMeasuring(); + LattePerformanceMonitor_frameEnd(); + LatteGPUState.frameCounter++; + g_renderer->SwapBuffers(true, true); + + catchOpenGLError(); + performanceMonitor.gpuTime_frameTime.beginMeasuring(); + + LatteTC_CleanupUnusedTextures(); + LatteDraw_cleanupAfterFrame(); + LatteQuery_CancelActiveGPU7Queries(); + LatteBufferCache_notifySwapTVScanBuffer(); + LattePerformanceMonitor_frameBegin(); +} + +void LatteRenderTarget_applyTextureColorClear(LatteTexture* texture, uint32 sliceIndex, uint32 mipIndex, float r, float g, float b, float a, uint64 eventCounter) +{ + if (texture->isDepth) + { + forceLogDebug_printf("Unsupported clear depth as color"); + } + else + { + g_renderer->texture_clearColorSlice(texture, sliceIndex, mipIndex, r, g, b, a); + LatteTexture_MarkDynamicTextureAsChanged(texture->baseView, sliceIndex, mipIndex, eventCounter); + } +} + +void LatteRenderTarget_applyTextureDepthClear(LatteTexture* texture, uint32 sliceIndex, uint32 mipIndex, bool hasDepthClear, bool hasStencilClear, float depthValue, uint8 stencilValue, uint64 eventCounter) +{ + if(texture->isDepth) + { + g_renderer->texture_clearDepthSlice(texture, sliceIndex, mipIndex, hasDepthClear, hasStencilClear, depthValue, stencilValue); + } + else + { + // clearing a color texture using depth clear + if (hasStencilClear) + return; // operation likely not intended as a color clear + //cemu_assert_debug(!hasStencilClear); + if (hasDepthClear) + { + g_renderer->texture_clearColorSlice(texture, sliceIndex, mipIndex, depthValue, depthValue, depthValue, depthValue); + } + + } + LatteTexture_MarkDynamicTextureAsChanged(texture->baseView, sliceIndex, mipIndex, eventCounter); +} + +void LatteRenderTarget_itHLEClearColorDepthStencil(uint32 clearMask, MPTR colorBufferMPTR, MPTR colorBufferFormat, Latte::E_HWTILEMODE colorBufferTilemode, uint32 colorBufferWidth, uint32 colorBufferHeight, uint32 colorBufferPitch, uint32 colorBufferViewFirstSlice, uint32 colorBufferViewNumSlice, MPTR depthBufferMPTR, MPTR depthBufferFormat, Latte::E_HWTILEMODE depthBufferTileMode, sint32 depthBufferWidth, sint32 depthBufferHeight, sint32 depthBufferPitch, sint32 depthBufferViewFirstSlice, sint32 depthBufferViewNumSlice, float r, float g, float b, float a, float clearDepth, uint32 clearStencil) +{ + uint32 depthBufferMipIndex = 0; // todo + uint32 colorBufferMipIndex = 0; // todo + + bool hasColorClear = (clearMask & 1); + bool hasDepthClear = (clearMask & 2); + bool hasStencilClear = (clearMask & 4); + + // extract swizzle offset from pointer + uint32 colorBufferSwizzle = 0; + uint32 depthBufferSwizzle = 0; + if (Latte::TM_IsMacroTiled(colorBufferTilemode)) + { + colorBufferSwizzle = (colorBufferMPTR >> 8) & 7; + colorBufferMPTR = colorBufferMPTR & ~(7 << 8); + } + if (Latte::TM_IsMacroTiled(depthBufferTileMode)) + { + depthBufferSwizzle = (depthBufferMPTR >> 8) & 7; + depthBufferMPTR = depthBufferMPTR & ~(7 << 8); + } + + cemu_assert_debug(colorBufferViewNumSlice <= 1); + cemu_assert_debug(depthBufferViewNumSlice <= 1); + + // clear color buffer (if flag set) + uint64 eventCounter = LatteTexture_getNextUpdateEventCounter(); + if ((clearMask & 1) != 0 && colorBufferMPTR != MPTR_NULL && colorBufferWidth > 0 && colorBufferHeight > 0) + { + // clear color + sint32 searchIndex = 0; + bool targetFound = false; + while (true) + { + LatteTextureView* colorView = LatteMRT_FindColorBufferForClearing(colorBufferMPTR, colorBufferWidth, colorBufferHeight, colorBufferPitch, colorBufferFormat, colorBufferViewFirstSlice, &searchIndex); + if (!colorView) + break; + if (Latte::GetFormatBits((Latte::E_GX2SURFFMT)colorBufferFormat) != Latte::GetFormatBits(colorView->baseTexture->format)) + { + continue; + } + + if (colorView->baseTexture->pitch == colorBufferPitch && colorView->baseTexture->height == colorBufferHeight) + targetFound = true; + + LatteRenderTarget_applyTextureColorClear(colorView->baseTexture, colorBufferViewFirstSlice, colorBufferMipIndex, r, g, b, a, eventCounter); + + } + if (targetFound == false) + { + // create new texture with matching format + cemu_assert_debug(colorBufferViewNumSlice <= 1); + LatteTextureView* newColorView = LatteMRT_CreateColorBuffer(colorBufferMPTR, colorBufferWidth, colorBufferHeight, colorBufferPitch, (Latte::E_GX2SURFFMT)colorBufferFormat, colorBufferTilemode, colorBufferSwizzle, colorBufferViewFirstSlice); + LatteRenderTarget_applyTextureColorClear(newColorView->baseTexture, colorBufferViewFirstSlice, colorBufferMipIndex, r, g, b, a, eventCounter); + } + } + // clear depth or stencil buffer (if flag set) + if ((hasDepthClear || hasStencilClear) && depthBufferMPTR != MPTR_NULL) + { + std::vector<LatteTexture*> list_depthClearTextures; + LatteTC_LookupTexturesByPhysAddr(depthBufferMPTR, list_depthClearTextures); + bool foundMatchingDepthBuffer = false; + // todo - support for clearing depth mips? + cemu_assert_debug(depthBufferViewNumSlice == 1); + + for (auto& texItr : list_depthClearTextures) + { + // only clear depth buffers if they are smaller + if (texItr->pitch > depthBufferPitch) + continue; + if (depthBufferViewFirstSlice >= texItr->depth) + continue; // slice out of range + + if (texItr->pitch == depthBufferPitch && texItr->height == depthBufferHeight) + foundMatchingDepthBuffer = true; + + // todo - calculate actual sliceIndex and mipIndex since the textures in list_depthClearTextures dont necessarily share the same base + LatteRenderTarget_applyTextureDepthClear(texItr, depthBufferViewFirstSlice, depthBufferMipIndex, hasDepthClear, hasStencilClear, clearDepth, clearStencil, eventCounter); + } + + if (foundMatchingDepthBuffer == false) + { + LatteTextureView* newDepthBufferView = LatteMRT_CreateDepthBuffer(depthBufferMPTR, depthBufferWidth, depthBufferHeight, depthBufferPitch, depthBufferTileMode, (Latte::E_GX2SURFFMT)depthBufferFormat, depthBufferSwizzle, depthBufferViewFirstSlice); + LatteRenderTarget_applyTextureDepthClear(newDepthBufferView->baseTexture, depthBufferViewFirstSlice, depthBufferMipIndex, hasDepthClear, hasStencilClear, clearDepth, clearStencil, eventCounter); + } + } +} + +sint32 _currentOutputImageWidth = 0; +sint32 _currentOutputImageHeight = 0; + +void LatteRenderTarget_getScreenImageArea(sint32* x, sint32* y, sint32* width, sint32* height, sint32* fullWidth, sint32* fullHeight, bool padView) +{ + int w, h; + if(padView && gui_isPadWindowOpen()) + gui_getPadWindowSize(&w, &h); + else + gui_getWindowSize(&w, &h); + + sint32 scaledOutputX; + sint32 scaledOutputY; + if (GetConfig().fullscreen_scaling == kKeepAspectRatio) + { + // calculate maximum possible resolution with intact aspect ratio + scaledOutputX = w; + scaledOutputY = _currentOutputImageHeight * w / std::max(_currentOutputImageWidth, 1); + if (scaledOutputY > h) + { + scaledOutputX = _currentOutputImageWidth * h / std::max(_currentOutputImageHeight, 1); + scaledOutputY = h; + } + } + else + { + scaledOutputX = w; + scaledOutputY = h; + } + + *x = (w - scaledOutputX) / 2; + *y = (h - scaledOutputY) / 2; + *width = scaledOutputX; + *height = scaledOutputY; + if (fullWidth) + *fullWidth = w; + if (fullHeight) + *fullHeight = h; +} + +void LatteRenderTarget_copyToBackbuffer(LatteTextureView* textureView, bool isPadView) +{ + if (g_renderer->GetType() == RendererAPI::Vulkan) + { + ((VulkanRenderer*)g_renderer.get())->PreparePresentationFrame(!isPadView); + } + + // make sure texture is updated to latest data in cache + LatteTexture_UpdateDataToLatest(textureView->baseTexture); + // mark source texture as still in use + LatteTC_MarkTextureStillInUse(textureView->baseTexture); + + sint32 effectiveWidth; + sint32 effectiveHeight; + sint32 effectiveDepth; + LatteTexture_getEffectiveSize(textureView->baseTexture, &effectiveWidth, &effectiveHeight, &effectiveDepth, 0); + _currentOutputImageWidth = effectiveWidth; + _currentOutputImageHeight = effectiveHeight; + + sint32 imageX, imageY; + sint32 imageWidth, imageHeight; + sint32 fullscreenWidth, fullscreenHeight; + + LatteRenderTarget_getScreenImageArea(&imageX, &imageY, &imageWidth, &imageHeight, &fullscreenWidth, &fullscreenHeight, isPadView); + + bool clearBackground = false; + if (imageWidth != fullscreenWidth || imageHeight != fullscreenHeight) + clearBackground = true; + + const bool renderUpsideDown = ActiveSettings::RenderUpsideDownEnabled(); + // force disable bicubic scaling if output resolution is equal/smaller than input resolution + const bool downscaling = (imageWidth <= effectiveWidth || imageHeight <= effectiveHeight); + // check for graphic pack shaders + RendererOutputShader* shader = nullptr; + LatteTextureView::MagFilter filter = LatteTextureView::MagFilter::kLinear; + for(const auto& gp : GraphicPack2::GetActiveGraphicPacks()) + { + if(downscaling) + { + shader = gp->GetDownscalingShader(renderUpsideDown); + if (shader) + { + filter = gp->GetDownscalingMagFilter(); + break; + } + } + else + { + shader = gp->GetUpscalingShader(renderUpsideDown); + if (shader) + { + filter = gp->GetUpscalingMagFilter(); + break; + } + } + + shader = gp->GetOuputShader(renderUpsideDown); + if (shader) + { + filter = downscaling ? gp->GetDownscalingMagFilter() : gp->GetUpscalingMagFilter(); + break; + } + } + + if (shader == nullptr) + { + sint32 scaling_filter = downscaling ? GetConfig().downscale_filter : GetConfig().upscale_filter; + + if (g_renderer->GetType() == RendererAPI::Vulkan) + { + // force linear or nearest neighbor filter + if(scaling_filter != kLinearFilter && scaling_filter != kNearestNeighborFilter) + scaling_filter = kLinearFilter; + } + + if (scaling_filter == kLinearFilter) + { + if(renderUpsideDown) + shader = RendererOutputShader::s_copy_shader_ud; + else + shader = RendererOutputShader::s_copy_shader; + + filter = LatteTextureView::MagFilter::kLinear; + } + else if (scaling_filter == kBicubicFilter) + { + if (renderUpsideDown) + shader = RendererOutputShader::s_bicubic_shader_ud; + else + shader = RendererOutputShader::s_bicubic_shader; + + filter = LatteTextureView::MagFilter::kNearestNeighbor; + } + else if (scaling_filter == kBicubicHermiteFilter) + { + if (renderUpsideDown) + shader = RendererOutputShader::s_hermit_shader_ud; + else + shader = RendererOutputShader::s_hermit_shader; + + filter = LatteTextureView::MagFilter::kLinear; + } + else if (scaling_filter == kNearestNeighborFilter) + { + if (renderUpsideDown) + shader = RendererOutputShader::s_copy_shader_ud; + else + shader = RendererOutputShader::s_copy_shader; + + filter = LatteTextureView::MagFilter::kNearestNeighbor; + } + } + cemu_assert(shader); + g_renderer->DrawBackbufferQuad(textureView, shader, filter==LatteTextureView::MagFilter::kLinear, imageX, imageY, imageWidth, imageHeight, isPadView, clearBackground); + g_renderer->HandleScreenshotRequest(textureView, isPadView); + if (!g_renderer->ImguiBegin(!isPadView)) + return; + swkbd_render(!isPadView); + nn::erreula::render(!isPadView); + LatteOverlay_render(isPadView); + g_renderer->ImguiEnd(); +} + +bool DLLEXPORT alwaysDisplayDRC = false; +bool ctrlTabHotkeyPressed = false; + +void LatteRenderTarget_itHLECopyColorBufferToScanBuffer(MPTR colorBufferPtr, uint32 colorBufferWidth, uint32 colorBufferHeight, uint32 colorBufferSliceIndex, uint32 colorBufferFormat, uint32 colorBufferPitch, Latte::E_HWTILEMODE colorBufferTilemode, uint32 colorBufferSwizzle, uint32 renderTarget) +{ + cemu_assert_debug(colorBufferSliceIndex == 0); // todo - support for non-zero slice + LatteTextureView* texView = LatteTC_GetTextureSliceViewOrTryCreate(colorBufferPtr, MPTR_NULL, (Latte::E_GX2SURFFMT)colorBufferFormat, colorBufferTilemode, colorBufferWidth, colorBufferHeight, 1, colorBufferPitch, colorBufferSwizzle, 0, 0, true); + if (!texView) + { + return; + } + + const bool tabPressed = gui_isKeyDown(WXK_TAB); + const bool ctrlPressed = gui_isKeyDown(0xA2); // VK_LCONTROL + bool showDRC = swkbd_hasKeyboardInputHook() == false && tabPressed; + + if (ctrlPressed && tabPressed) + { + if (ctrlTabHotkeyPressed == false) + { + alwaysDisplayDRC = !alwaysDisplayDRC; + ctrlTabHotkeyPressed = true; + } + } + else + ctrlTabHotkeyPressed = false; + + if (alwaysDisplayDRC) + showDRC = !tabPressed; + + if (!showDRC) + { + auto controller = InputManager::instance().get_vpad_controller(0); + if (controller && controller->is_screen_active()) + showDRC = true; + if (!showDRC) + { + controller = InputManager::instance().get_vpad_controller(1); + if (controller && controller->is_screen_active()) + showDRC = true; + } + } + + if (renderTarget == 4 && g_renderer->IsPadWindowActive()) + LatteRenderTarget_copyToBackbuffer(texView, true); + if ((renderTarget == 1 && !showDRC) || (renderTarget == 4 && showDRC)) + LatteRenderTarget_copyToBackbuffer(texView, false); +} + + + +// returns the current size of the virtual viewport (not the same as effective size, which can be influenced by texture rules) +void LatteRenderTarget_GetCurrentVirtualViewportSize(sint32* viewportWidth, sint32* viewportHeight) +{ + *viewportWidth = sLatteRenderTargetState.currentGuestViewport.width; + *viewportHeight = sLatteRenderTargetState.currentGuestViewport.height; +} + +void LatteRenderTarget_updateViewport() +{ + float vpWidth = LatteGPUState.contextNew.PA_CL_VPORT_XSCALE.get_SCALE() / 0.5f; + float vpX = LatteGPUState.contextNew.PA_CL_VPORT_XOFFSET.get_OFFSET() - LatteGPUState.contextNew.PA_CL_VPORT_XSCALE.get_SCALE(); + float vpHeight = LatteGPUState.contextNew.PA_CL_VPORT_YSCALE.get_SCALE() / -0.5f; + float vpY = LatteGPUState.contextNew.PA_CL_VPORT_YOFFSET.get_OFFSET() + LatteGPUState.contextNew.PA_CL_VPORT_YSCALE.get_SCALE(); + + bool halfZ = LatteGPUState.contextNew.PA_CL_CLIP_CNTL.get_DX_CLIP_SPACE_DEF(); + + // calculate near/far + float farZ; + float nearZ; + float s = LatteGPUState.contextNew.PA_CL_VPORT_ZSCALE.get_SCALE(); + float b = LatteGPUState.contextNew.PA_CL_VPORT_ZOFFSET.get_OFFSET(); + if (halfZ == false) + { + farZ = s + b; + nearZ = b - s; + } + else + { + farZ = s + b; + nearZ = b; + } + + sLatteRenderTargetState.currentGuestViewport.width = vpWidth; + sLatteRenderTargetState.currentGuestViewport.height = vpHeight; + if (sLatteRenderTargetState.renderTargetIsResized) + { + vpX *= ((float)sLatteRenderTargetState.currentEffectiveSize.width / (float)sLatteRenderTargetState.currentRenderSize.width); + vpY *= ((float)sLatteRenderTargetState.currentEffectiveSize.height / (float)sLatteRenderTargetState.currentRenderSize.height); + vpWidth *= ((float)sLatteRenderTargetState.currentEffectiveSize.width / (float)sLatteRenderTargetState.currentRenderSize.width); + vpHeight *= ((float)sLatteRenderTargetState.currentEffectiveSize.height / (float)sLatteRenderTargetState.currentRenderSize.height); + } + g_renderer->renderTarget_setViewport(vpX, vpY, vpWidth, vpHeight, nearZ, farZ, halfZ); +} + +void LatteRenderTarget_updateScissorBox() +{ + // update scissor box + uint32 scissorX = LatteGPUState.contextNew.PA_SC_GENERIC_SCISSOR_TL.get_TL_X(); + uint32 scissorY = LatteGPUState.contextNew.PA_SC_GENERIC_SCISSOR_TL.get_TL_Y(); + uint32 scissorWidth = LatteGPUState.contextNew.PA_SC_GENERIC_SCISSOR_BR.get_BR_X() - scissorX; + uint32 scissorHeight = LatteGPUState.contextNew.PA_SC_GENERIC_SCISSOR_BR.get_BR_Y() - scissorY; + if( sLatteRenderTargetState.renderTargetIsResized ) + { + scissorX = (sint32)((float)scissorX * ((float)sLatteRenderTargetState.currentEffectiveSize.width / (float)sLatteRenderTargetState.currentRenderSize.width)); + scissorY = (sint32)((float)scissorY * ((float)sLatteRenderTargetState.currentEffectiveSize.height / (float)sLatteRenderTargetState.currentRenderSize.height)); + scissorWidth = (sint32)((float)scissorWidth * ((float)sLatteRenderTargetState.currentEffectiveSize.width / (float)sLatteRenderTargetState.currentRenderSize.width)); + scissorHeight = (sint32)((float)scissorHeight * ((float)sLatteRenderTargetState.currentEffectiveSize.height / (float)sLatteRenderTargetState.currentRenderSize.height)); + } + + if( scissorX != prevScissorX || scissorY != prevScissorY || scissorWidth != prevScissorWidth || scissorHeight != prevScissorHeight ) + { + g_renderer->renderTarget_setScissor(scissorX, scissorY, scissorWidth, scissorHeight); + prevScissorX = scissorX; + prevScissorY = scissorY; + prevScissorWidth = scissorWidth; + prevScissorHeight = scissorHeight; + } +} + +void LatteRenderTarget_unloadAll() +{ + if (g_emptyFBO) + { + LatteMRT::DeleteCachedFBO(g_emptyFBO); + g_emptyFBO = nullptr; + } +} \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Core/LatteRingBuffer.cpp b/src/Cafe/HW/Latte/Core/LatteRingBuffer.cpp new file mode 100644 index 00000000..15c2b24c --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteRingBuffer.cpp @@ -0,0 +1,26 @@ +#include "Cafe/HW/Latte/Core/LatteRingBuffer.h" + +LatteRingBuffer_t* LatteRingBuffer_create(uint8* data, uint32 size) +{ + LatteRingBuffer_t* rb = (LatteRingBuffer_t*)malloc(sizeof(LatteRingBuffer_t)); + rb->data = data; + rb->size = size; + rb->writeIndex = 0; + return rb; +} + +uint8* LatteRingBuffer_allocate(LatteRingBuffer_t* rb, sint32 size, sint32 alignment) +{ +#ifndef PUBLIC_RELEASE + cemu_assert_debug(size < rb->size); +#endif + // align + rb->writeIndex = (rb->writeIndex + alignment - 1)&~(alignment-1); + // handle wrap-around + if ((rb->writeIndex + size) >= rb->size) + rb->writeIndex = 0; + // allocate range + uint8* data = rb->data + rb->writeIndex; + rb->writeIndex += size; + return data; +} \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Core/LatteRingBuffer.h b/src/Cafe/HW/Latte/Core/LatteRingBuffer.h new file mode 100644 index 00000000..ce28c483 --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteRingBuffer.h @@ -0,0 +1,10 @@ + +typedef struct +{ + uint8* data; + sint32 size; + sint32 writeIndex; +}LatteRingBuffer_t; + +LatteRingBuffer_t* LatteRingBuffer_create(uint8* data, uint32 size); +uint8* LatteRingBuffer_allocate(LatteRingBuffer_t* rb, sint32 size, sint32 alignment); \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Core/LatteShader.cpp b/src/Cafe/HW/Latte/Core/LatteShader.cpp new file mode 100644 index 00000000..ded1c20f --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteShader.cpp @@ -0,0 +1,988 @@ +#include "Cafe/HW/Latte/Core/LatteConst.h" +#include "Cafe/HW/Latte/Core/LatteShaderAssembly.h" +#include "Cafe/HW/Latte/ISA/RegDefines.h" +#include "Cafe/OS/libs/gx2/GX2.h" // todo - remove dependency +#include "Cafe/HW/Latte/ISA/LatteReg.h" +#include "Cafe/HW/Latte/Core/LatteShader.h" +#include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h" +#include "Cafe/HW/Latte/Core/FetchShader.h" +#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" +#include "Cafe/GraphicPack/GraphicPack2.h" +#include "util/helpers/StringParser.h" +#include "config/ActiveSettings.h" +#include "util/Zir/EmitterGLSL/ZpIREmitGLSL.h" +#include "util/Zir/Core/ZpIRDebug.h" +#include "util/containers/flat_hash_map.hpp" + +struct _ShaderHashCache +{ + uint64 prevHash1; + uint64 prevHash2; + uint32* prevProgramCode; + uint32 prevProgramSize; +}; + +_ShaderHashCache hashCacheVS = { 0 }; +_ShaderHashCache hashCacheGS = { 0 }; +_ShaderHashCache hashCachePS = { 0 }; + +LatteFetchShader* _activeFetchShader = nullptr; +LatteDecompilerShader* _activeVertexShader = nullptr; +LatteDecompilerShader* _activeGeometryShader = nullptr; +LatteDecompilerShader* _activePixelShader = nullptr; + +// runtime shader cache +using SHRC_CACHE_TYPE = ska::flat_hash_map<uint64, LatteDecompilerShader*>; + +SHRC_CACHE_TYPE sVertexShaders(512); +SHRC_CACHE_TYPE sGeometryShaders(512); +SHRC_CACHE_TYPE sPixelShaders(512); + +uint64 _shaderBaseHash_vs; +uint64 _shaderBaseHash_gs; +uint64 _shaderBaseHash_ps; + +std::atomic_int g_compiled_shaders_total = 0; +std::atomic_int g_compiled_shaders_async = 0; + +LatteFetchShader* LatteSHRC_GetActiveFetchShader() +{ + return _activeFetchShader; +} + +LatteDecompilerShader* LatteSHRC_GetActiveVertexShader() +{ + return _activeVertexShader; +} + +LatteDecompilerShader* LatteSHRC_GetActiveGeometryShader() +{ + return _activeGeometryShader; +} + +LatteDecompilerShader* LatteSHRC_GetActivePixelShader() +{ + return _activePixelShader; +} + +inline ska::flat_hash_map<uint64, LatteDecompilerShader*>& LatteSHRC_GetCacheByType(LatteConst::ShaderType shaderType) +{ + if (shaderType == LatteConst::ShaderType::Vertex) + return sVertexShaders; + else if (shaderType == LatteConst::ShaderType::Geometry) + return sGeometryShaders; + cemu_assert_debug(shaderType == LatteConst::ShaderType::Pixel); + return sPixelShaders; +} + +// calculate hash from shader binary +// this algorithm could be more efficient since we could leverage the fact that the size is always aligned to 8 byte +// but since this is baked into the shader names used for gfx packs and shader caches we can't really change this +void _calcShaderHashGeneric(uint32* programCode, uint32 programSize, uint64& outputHash1, uint64& outputHash2) +{ + outputHash1 = 0; + outputHash2 = 0; + for (uint32 i = 0; i < programSize / 4; i++) + { + uint32 temp = programCode[i]; + outputHash1 += (uint64)temp; + outputHash2 ^= (uint64)temp; + outputHash1 = (outputHash1 << 3) | (outputHash1 >> 61); + outputHash2 = (outputHash2 >> 7) | (outputHash2 << 57); + } +} + +void _calculateShaderProgramHash(uint32* programCode, uint32 programSize, _ShaderHashCache* hashCache, uint64* outputHash1, uint64* outputHash2) +{ + uint64 progHash1 = 0; + uint64 progHash2 = 0; + if (!programCode) + { + hashCache->prevProgramCode = NULL; + hashCache->prevProgramSize = 0; + hashCache->prevHash1 = 0; + hashCache->prevHash2 = 0; + } + else if (hashCache->prevProgramCode != programCode || hashCache->prevProgramSize != programSize) + { + _calcShaderHashGeneric(programCode, programSize, progHash1, progHash2); + hashCache->prevProgramCode = programCode; + hashCache->prevProgramSize = programSize; + hashCache->prevHash1 = progHash1; + hashCache->prevHash2 = progHash2; + } + else + { + progHash1 = hashCache->prevHash1; + progHash2 = hashCache->prevHash2; + } + *outputHash1 = progHash1; + *outputHash2 = progHash2; +} + +void LatteSHRC_ResetCachedShaderHash() +{ + hashCacheVS.prevProgramCode = 0; + hashCacheVS.prevProgramSize = 0; + hashCacheGS.prevProgramCode = 0; + hashCacheGS.prevProgramSize = 0; + hashCachePS.prevProgramCode = 0; + hashCachePS.prevProgramSize = 0; +} + +LatteShaderPSInputTable _activePSImportTable; + +LatteShaderPSInputTable* LatteSHRC_GetPSInputTable() +{ + return &_activePSImportTable; +} + +bool LatteSHRC_RemoveFromCache(LatteDecompilerShader* shader) +{ + bool removed = false; + auto& cache = LatteSHRC_GetCacheByType(shader->shaderType); + // remove from hashtable + auto baseIt = cache.find(shader->baseHash); + if (baseIt == cache.end()) + { + cemu_assert_suspicious(); // deleting from runtime cache but shader is not present? + } + else if (baseIt->second == shader) + { + if (baseIt->second->next) + cache.emplace(shader->baseHash, baseIt->second->next); + else + cache.erase(baseIt); + removed = true; + } + else + { + // remove from chain + LatteDecompilerShader* shaderChain = baseIt->second; + while (shaderChain->next) + { + if (shaderChain->next == shader) + { + shaderChain->next = shaderChain->next->next; + removed = true; + break; + } + } + } + return removed; +} + +void LatteSHRC_RemoveFromCacheByHash(uint64 shader_base_hash, uint64 shader_aux_hash, LatteConst::ShaderType type) +{ + LatteDecompilerShader* shader = nullptr; + if (type == LatteConst::ShaderType::Vertex) + shader = LatteSHRC_FindVertexShader(shader_base_hash, shader_aux_hash); + else if (type == LatteConst::ShaderType::Geometry) + shader = LatteSHRC_FindGeometryShader(shader_base_hash, shader_aux_hash); + else if (type == LatteConst::ShaderType::Pixel) + shader = LatteSHRC_FindPixelShader(shader_base_hash, shader_aux_hash); + if (shader) + LatteSHRC_RemoveFromCache(shader); +} + +void LatteShader_free(LatteDecompilerShader* shader) +{ + LatteSHRC_RemoveFromCache(shader); + if (shader->shader) + delete shader->shader; + shader->shader = nullptr; + delete shader; +} + +// both vertex and geometry/pixel shader depend on PS inputs +// we prepare the PS import info in advance +void LatteShader_UpdatePSInputs(uint32* contextRegisters) +{ + // PS control + uint32 psControl0 = contextRegisters[mmSPI_PS_IN_CONTROL_0]; + uint32 spi0_positionEnable = (psControl0 >> 8) & 1; + uint32 spi0_positionCentroid = (psControl0 >> 9) & 1; + cemu_assert_debug(spi0_positionCentroid == 0); // controls gl_FragCoord + uint32 spi0_positionAddr = (psControl0 >> 10) & 0x1F; // controls gl_FragCoord + uint32 spi0_paramGen = (psControl0 >> 15) & 0xF; // used for gl_PointCoords + uint32 spi0_paramGenAddr = (psControl0 >> 19) & 0x7F; + sint32 importIndex = 0; + + //cemu_assert_debug(((psControl0>>26)&3) == 1); // BARYC_SAMPLE_CNTL + //cemu_assert_debug((psControl0&(1 << 28)) == 0); // PERSP_GRADIENT_ENA + //cemu_assert_debug((psControl0&(1 << 29)) == 0); // LINEAR_GRADIENT_ENA + // if LINEAR_GRADIENT_ENA_bit is enabled, the pixel shader accesses gl_ClipSize? + + // VS/GS parameters + uint32 numPSInputs = contextRegisters[mmSPI_PS_IN_CONTROL_0] & 0x3F; + uint64 key = 0; + + if (spi0_positionEnable) + { + key += (uint64)spi0_positionAddr + 1; + } + + // parameter gen + if (spi0_paramGen != 0) + { + key += _rotr64(spi0_paramGen, 7); + key += _rotr64(spi0_paramGenAddr, 3); + _activePSImportTable.paramGen = spi0_paramGen; + _activePSImportTable.paramGenGPR = spi0_paramGenAddr; + } + else + { + _activePSImportTable.paramGen = 0; + } + + // semantic imports from vertex shader +#ifndef PUBLIC_RELEASE + uint8 semanticMask[256 / 8] = { 0 }; +#endif + cemu_assert_debug(numPSInputs <= GPU7_PS_MAX_INPUTS); + numPSInputs = std::min<uint32>(numPSInputs, GPU7_PS_MAX_INPUTS); + + for (uint32 f = 0; f < numPSInputs; f++) + { + uint32 psInputControl = contextRegisters[mmSPI_PS_INPUT_CNTL_0 + f]; + uint32 psSemanticId = (psInputControl & 0xFF); + + uint8 defaultValue = (psInputControl>>8)&3; + // default: + // 0 -> 0.0 0.0 0.0 0.0 + // 1 -> 0.0 0.0 0.0 1.0 + // 2 -> 1.0 1.0 1.0 0.0 + // 3 -> 1.0 1.0 1.0 1.0 + cemu_assert_debug(defaultValue <= 1); + + uint32 uknBits = psInputControl & ~((0xFF)|(0x3<<8) | (1 << 10) | (1 << 12)); + uknBits &= ~0x800; // FLAT_SHADE + //cemu_assert_debug(uknBits == 0); + //cemu_assert_debug(((psInputControl >> 11) & 1) == 0); // centroid + //cemu_assert_debug(((psInputControl >> 17) & 1) == 0); // point sprite coord + cemu_assert_debug(psSemanticId != 0xFF); + + key += (uint64)psInputControl; + key = _rotl64(key, 7); + if (spi0_positionEnable && f == spi0_positionAddr) + { + _activePSImportTable.import[f].semanticId = LATTE_ANALYZER_IMPORT_INDEX_SPIPOSITION; + _activePSImportTable.import[f].isFlat = false; + _activePSImportTable.import[f].isNoPerspective = false; + key += (uint64)0x33; + } + else + { +#ifndef PUBLIC_RELEASE + if (semanticMask[psSemanticId >> 3] & (1 << (psSemanticId & 7))) + { + forceLogDebug_printf("SemanticId already used"); + } + semanticMask[psSemanticId >> 3] |= (1 << (psSemanticId & 7)); +#endif + + _activePSImportTable.import[f].semanticId = psSemanticId; + _activePSImportTable.import[f].isFlat = (psInputControl&(1 << 10)) != 0; + _activePSImportTable.import[f].isNoPerspective = (psInputControl&(1 << 12)) != 0; + } + } + _activePSImportTable.key = key; + _activePSImportTable.count = numPSInputs; +} + +void LatteShader_CreateRendererShader(LatteDecompilerShader* shader, bool compileAsync) +{ + if (shader->hasError ) + { + forceLog_printf("Unable to compile shader %I64x", shader->baseHash); + return; + } + + GraphicPack2::GP_SHADER_TYPE gpShaderType; + RendererShader::ShaderType shaderType; + if (shader->shaderType == LatteConst::ShaderType::Vertex) + { + shaderType = RendererShader::ShaderType::kVertex; + gpShaderType = GraphicPack2::GP_SHADER_TYPE::VERTEX; + } + else if (shader->shaderType == LatteConst::ShaderType::Geometry) + { + shaderType = RendererShader::ShaderType::kGeometry; + gpShaderType = GraphicPack2::GP_SHADER_TYPE::GEOMETRY; + } + else if (shader->shaderType == LatteConst::ShaderType::Pixel) + { + shaderType = RendererShader::ShaderType::kFragment; + gpShaderType = GraphicPack2::GP_SHADER_TYPE::PIXEL; + } + + // check if a custom shader is present + std::string shaderSrc; + + const std::string* customShaderSrc = GraphicPack2::FindCustomShaderSource(shader->baseHash, shader->auxHash, gpShaderType, g_renderer->GetType() == RendererAPI::Vulkan); + if (customShaderSrc) + { + shaderSrc.assign(*customShaderSrc); + shader->isCustomShader = true; + } + else + shaderSrc.assign(shader->strBuf_shaderSource->c_str()); + + if (shaderType == RendererShader::ShaderType::kVertex && + (shader->baseHash == 0x15bc7edf9de2ed30 || shader->baseHash == 0x83a697d61a3b9202 || + shader->baseHash == 0x97bc44a5028381c6 || shader->baseHash == 0x24838b84d15a1da1)) + { + forceLogDebug_printf("Filtered shader to avoid AMD crash"); + shader->shader = nullptr; + shader->hasError = true; + return; + } + + // create shader + shader->shader = g_renderer->shader_create(shaderType, shader->baseHash, shader->auxHash, shaderSrc, true, shader->isCustomShader); + if (shader->shader == nullptr) + shader->hasError = true; + // after renderer shader creation we can throw away any intermediate info + LatteShader_CleanupAfterCompile(shader); +} + +void LatteShader_FinishCompilation(LatteDecompilerShader* shader) +{ + if (shader->hasError) + { + forceLogDebug_printf("LatteShader_finishCompilation(): Skipped because of error in shader %llx", shader->baseHash); + return; + } + shader->shader->WaitForCompiled(); + + LatteShader_prepareSeparableUniforms(shader); + LatteShader_CleanupAfterCompile(shader); +} + +void LatteSHRC_RegisterShader(LatteDecompilerShader* shader, uint64 baseHash, uint64 auxHash) +{ + auto& cache = LatteSHRC_GetCacheByType(shader->shaderType); + shader->baseHash = baseHash; + shader->auxHash = auxHash; + + auto it = cache.find(baseHash); + if (it == cache.end()) + { + shader->next = nullptr; + cache.emplace(shader->baseHash, shader); + } + else + { + shader->next = it->second->next; + it->second->next = shader; + } +} + +LatteDecompilerShader* LatteSHRC_GetFromChain(LatteDecompilerShader* baseShader, uint64 baseHash, uint64 auxHash) +{ + while (baseShader && baseShader->auxHash != auxHash) + baseShader = baseShader->next; + return baseShader; +} + +LatteDecompilerShader* LatteSHRC_Get(SHRC_CACHE_TYPE& cache, uint64 baseHash, uint64 auxHash) +{ + auto it = cache.find(baseHash); + if (it == cache.end()) + return nullptr; + LatteDecompilerShader* baseShader = it->second; + if (!baseShader) + return nullptr; + while (baseShader && baseShader->auxHash != auxHash) + baseShader = baseShader->next; + return baseShader; +} + +LatteDecompilerShader* LatteSHRC_FindVertexShader(uint64 baseHash, uint64 auxHash) +{ + return LatteSHRC_Get(sVertexShaders, baseHash, auxHash); +} + +LatteDecompilerShader* LatteSHRC_FindGeometryShader(uint64 baseHash, uint64 auxHash) +{ + return LatteSHRC_Get(sGeometryShaders, baseHash, auxHash); +} + +LatteDecompilerShader* LatteSHRC_FindPixelShader(uint64 baseHash, uint64 auxHash) +{ + return LatteSHRC_Get(sPixelShaders, baseHash, auxHash); +} + +// update the currently active fetch shader +void LatteShaderSHRC_UpdateFetchShader() +{ + _activeFetchShader = LatteFetchShader::FindByGPUState(); +} + +void LatteShader_CleanupAfterCompile(LatteDecompilerShader* shader) +{ + if (shader->strBuf_shaderSource) + { + delete shader->strBuf_shaderSource; + shader->strBuf_shaderSource = nullptr; + } +} + +void LatteShader_DumpShader(uint64 baseHash, uint64 auxHash, LatteDecompilerShader* shader) +{ + if (!ActiveSettings::DumpShadersEnabled()) + return; + + const char* suffix = ""; + if (shader->shaderType == LatteConst::ShaderType::Vertex) + suffix = "vs"; + else if (shader->shaderType == LatteConst::ShaderType::Geometry) + suffix = "gs"; + else if (shader->shaderType == LatteConst::ShaderType::Pixel) + suffix = "ps"; + fs::path dumpPath = "dump/shaders"; + dumpPath /= fmt::format("{:016x}_{:016x}_{}.txt", baseHash, auxHash, suffix); + FileStream* fs = FileStream::createFile2(dumpPath); + if (fs) + { + if (shader->strBuf_shaderSource) + fs->writeData(shader->strBuf_shaderSource->c_str(), shader->strBuf_shaderSource->getLen()); + delete fs; + } +} + +void LatteShader_DumpRawShader(uint64 baseHash, uint64 auxHash, uint32 type, uint8* programCode, uint32 programLen) +{ + if (!ActiveSettings::DumpShadersEnabled()) + return; + const char* suffix = ""; + if (type == SHADER_DUMP_TYPE_FETCH) + suffix = "fs"; + else if (type == SHADER_DUMP_TYPE_VERTEX) + suffix = "vs"; + else if (type == SHADER_DUMP_TYPE_GEOMETRY) + suffix = "gs"; + else if (type == SHADER_DUMP_TYPE_PIXEL) + suffix = "ps"; + else if (type == SHADER_DUMP_TYPE_COPY) + suffix = "copy"; + else if (type == SHADER_DUMP_TYPE_COMPUTE) + suffix = "compute"; + fs::path dumpPath = "dump/shaders"; + dumpPath /= fmt::format("{:016x}_{:016x}_{}.bin", baseHash, auxHash, suffix); + FileStream* fs = FileStream::createFile2(dumpPath); + if (fs) + { + fs->writeData(programCode, programLen); + delete fs; + } +} + +void LatteSHRC_UpdateVSBaseHash(uint8* vertexShaderPtr, uint32 vertexShaderSize, bool usesGeometryShader) +{ + uint32* vsProgramCode = (uint32*)vertexShaderPtr; + // update hash from vertex shader data + uint64 vsHash1 = 0; + uint64 vsHash2 = 0; + _calculateShaderProgramHash(vsProgramCode, vertexShaderSize, &hashCacheVS, &vsHash1, &vsHash2); + uint64 vsHash = vsHash1 + vsHash2 + _activeFetchShader->key + _activePSImportTable.key + (usesGeometryShader ? 0x1111ULL : 0ULL); + + uint32 tmp = LatteGPUState.contextNew.PA_CL_VTE_CNTL.getRawValue() ^ 0x43F; + vsHash += tmp; + + auto primitiveType = LatteGPUState.contextNew.VGT_PRIMITIVE_TYPE.get_PRIMITIVE_MODE(); + if (primitiveType == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::RECTS) + { + vsHash += 13ULL; + } + else if (primitiveType == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::POINTS) + { + // required for Vulkan since we have to write the pointsize in the shader + vsHash += 71ULL; + } + vsHash += (LatteGPUState.contextRegister[mmVGT_STRMOUT_EN] ? 21 : 0); + // halfZ + if (LatteGPUState.contextNew.PA_CL_CLIP_CNTL.get_DX_CLIP_SPACE_DEF()) + vsHash += 0x1537; + + _shaderBaseHash_vs = vsHash; +} + +void LatteSHRC_UpdateGSBaseHash(uint8* geometryShaderPtr, uint32 geometryShaderSize, uint8* geometryCopyShader, uint32 geometryCopyShaderSize) +{ + // update hash from geometry shader data + uint64 gsHash1 = 0; + uint64 gsHash2 = 0; + _calculateShaderProgramHash((uint32*)geometryShaderPtr, geometryShaderSize, &hashCacheVS, &gsHash1, &gsHash2); + // get geometry shader + uint64 gsHash = gsHash1 + gsHash2; + gsHash += (uint64)_activeVertexShader->ringParameterCount; + gsHash += (LatteGPUState.contextRegister[mmVGT_STRMOUT_EN] ? 21 : 0); + _shaderBaseHash_gs = gsHash; +} + +void LatteSHRC_UpdatePSBaseHash(uint8* pixelShaderPtr, uint32 pixelShaderSize, bool usesGeometryShader) +{ + uint32* psProgramCode = (uint32*)pixelShaderPtr; + // update hash from pixel shader data + uint64 psHash1 = 0; + uint64 psHash2 = 0; + _calculateShaderProgramHash(psProgramCode, pixelShaderSize, &hashCachePS, &psHash1, &psHash2); + // get vertex shader + uint64 psHash = psHash1 + psHash2 + _activePSImportTable.key + (usesGeometryShader ? hashCacheGS.prevHash1 : 0ULL); + _shaderBaseHash_ps = psHash; +} + +uint64 LatteSHRC_CalcVSAuxHash(LatteDecompilerShader* vertexShader, uint32* contextRegisters) +{ + // todo - include texture types in aux hash similar to how it is already done in pixel shader + // or maybe there is a way to figure out the proper texture types? + uint64 auxHash = 0; + if(vertexShader->hasStreamoutBufferWrite) + { + // hash stride for streamout buffers + for (uint32 i = 0; i < LATTE_NUM_STREAMOUT_BUFFER; i++) + { + if(!vertexShader->streamoutBufferWriteMask2[i]) + continue; + uint32 bufferStride = contextRegisters[mmVGT_STRMOUT_VTX_STRIDE_0 + i * 4]; + auxHash = _rotl64(auxHash, 7); + auxHash += (uint64)bufferStride; + } + } + // textures can affect the shader. Either by their type (2D, 3D, cubemap) or by their format (float vs integer) + uint64 auxHashTex = 0; + for (uint8 i = 0; i < vertexShader->textureUnitListCount; i++) + { + uint8 t = vertexShader->textureUnitList[i]; + uint32 word4 = contextRegisters[Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_VS + t * 7 + 4]; + if ((word4 & 0x300) == 0x100) + { + // integer format + auxHashTex = _rotl64(auxHashTex, 7); + auxHashTex += 0x333; + } + } + return auxHash + auxHashTex; +} + +uint64 LatteSHRC_CalcGSAuxHash(LatteDecompilerShader* geometryShader) +{ + // todo - include texture types in aux hash similar to how it is already done in pixel shader + return 0; +} + +uint64 LatteSHRC_CalcPSAuxHash(LatteDecompilerShader* pixelShader, uint32* contextRegisters) +{ + uint64 auxHash = 0; + // CB_SHADER_MASK can remap pixel shader outputs + auxHash = (auxHash >> 3) | (auxHash << 61); + auxHash += (uint64)contextRegisters[mmCB_SHADER_MASK]; + // alpha test + uint8 alphaTestFunc = contextRegisters[Latte::REGADDR::SX_ALPHA_TEST_CONTROL] & 0x7; + uint8 alphaTestEnable = (contextRegisters[Latte::REGADDR::SX_ALPHA_TEST_CONTROL] >> 3) & 1; + if (alphaTestEnable) + { + auxHash += (uint64)alphaTestFunc; + auxHash = (auxHash >> 3) | (auxHash << 61); + auxHash += 1; + } + // texture types (2D, 3D, cubemap etc.) affect the shader too + for (uint8 i = 0; i < pixelShader->textureUnitListCount; i++) + { + uint8 t = pixelShader->textureUnitList[i]; + uint32 word0 = contextRegisters[Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_PS + t * 7 + 0]; + uint32 dim = (word0 & 7); + auxHash = (auxHash << 3) | (auxHash >> 61); + auxHash += (uint64)dim; + } + return auxHash; +} + +LatteDecompilerShader* LatteShader_CreateShaderFromDecompilerOutput(LatteDecompilerOutput_t& decompilerOutput, uint64 baseHash, bool calculateAuxHash, uint64 optionalAuxHash, uint32* contextRegister) +{ + LatteDecompilerShader* shader = decompilerOutput.shader; + shader->baseHash = baseHash; + // copy resource mapping + if(g_renderer->GetType() == RendererAPI::Vulkan) + shader->resourceMapping = decompilerOutput.resourceMappingVK; + else + shader->resourceMapping = decompilerOutput.resourceMappingGL; + // copy texture info + shader->textureUnitMask2 = decompilerOutput.textureUnitMask; + // copy streamout info + shader->streamoutBufferWriteMask2 = decompilerOutput.streamoutBufferWriteMask; + shader->hasStreamoutBufferWrite = decompilerOutput.streamoutBufferWriteMask.any(); + // copy uniform offsets + // for OpenGL these are retrieved in _prepareSeparableUniforms() + if (g_renderer->GetType() == RendererAPI::Vulkan) + { + shader->uniform.loc_remapped = decompilerOutput.uniformOffsetsVK.offset_remapped; + shader->uniform.loc_uniformRegister = decompilerOutput.uniformOffsetsVK.offset_uniformRegister; + shader->uniform.count_uniformRegister = decompilerOutput.uniformOffsetsVK.count_uniformRegister; + shader->uniform.loc_windowSpaceToClipSpaceTransform = decompilerOutput.uniformOffsetsVK.offset_windowSpaceToClipSpaceTransform; + shader->uniform.loc_alphaTestRef = decompilerOutput.uniformOffsetsVK.offset_alphaTestRef; + shader->uniform.loc_pointSize = decompilerOutput.uniformOffsetsVK.offset_pointSize; + shader->uniform.loc_fragCoordScale = decompilerOutput.uniformOffsetsVK.offset_fragCoordScale; + for (sint32 t = 0; t < LATTE_NUM_MAX_TEX_UNITS; t++) + { + if (decompilerOutput.uniformOffsetsVK.offset_texScale[t] >= 0) + { + LatteUniformTextureScaleEntry_t entry = { 0 }; + entry.texUnit = t; + entry.uniformLocation = decompilerOutput.uniformOffsetsVK.offset_texScale[t]; + shader->uniform.list_ufTexRescale.push_back(entry); + } + } + shader->uniform.loc_verticesPerInstance = decompilerOutput.uniformOffsetsVK.offset_verticesPerInstance; + for (sint32 t = 0; t < LATTE_NUM_STREAMOUT_BUFFER; t++) + shader->uniform.loc_streamoutBufferBase[t] = decompilerOutput.uniformOffsetsVK.offset_streamoutBufferBase[t]; + shader->uniform.uniformRangeSize = decompilerOutput.uniformOffsetsVK.offset_endOfBlock; + } + else + { + shader->uniform.count_uniformRegister = decompilerOutput.uniformOffsetsVK.count_uniformRegister; + } + // calculate aux hash + if (calculateAuxHash) + { + if (decompilerOutput.shaderType == LatteConst::ShaderType::Vertex) + { + uint64 vsAuxHash = LatteSHRC_CalcVSAuxHash(shader, contextRegister); + shader->auxHash = vsAuxHash; + } + else if (decompilerOutput.shaderType == LatteConst::ShaderType::Geometry) + { + uint64 gsAuxHash = LatteSHRC_CalcGSAuxHash(shader); + shader->auxHash = gsAuxHash; + } + else if (decompilerOutput.shaderType == LatteConst::ShaderType::Pixel) + { + uint64 psAuxHash = LatteSHRC_CalcPSAuxHash(shader, contextRegister); + shader->auxHash = psAuxHash; + } + else + cemu_assert_debug(false); + } + else + { + shader->auxHash = optionalAuxHash; + } + return shader; +} + +#include "Cafe/HW/Latte/Transcompiler/LatteTC.h" +#include "Cafe/HW/Latte/ShaderInfo/ShaderInfo.h" + +LatteDecompilerShader* LatteShader_compileSeparableVertexShader(uint64 baseHash, uint64& vsAuxHash, uint8* vertexShaderPtr, uint32 vertexShaderSize, bool usesGeometryShader, LatteFetchShader* fetchShader) +{ + /* Analyze shader to gather general information about inputs/outputs */ + Latte::ShaderDescription shaderDescription; + if (!shaderDescription.analyzeShaderCode(vertexShaderPtr, vertexShaderSize, LatteConst::ShaderType::Vertex)) + { + assert_dbg(); + return nullptr; + } + /* Create context dependent IO info for this shader */ + //Latte::ShaderInstanceInfo + assert_dbg(); + + // todo - Use ShaderInstanceInfo when generating the GLSL (GLSL::Emit() should take a 'GLSLInfoSource' class which has a bunch of virtual methods for retrieving uniform names etc. We then override this class and plug in logic using ShaderInstanceInfo + + /* Translate R600Plus to GLSL */ + ZpIR::DebugPrinter irDebugPrinter; + LatteTCGenIR genIR; + genIR.setVertexShaderContext(fetchShader, LatteGPUState.contextRegister + mmSQ_VTX_SEMANTIC_0); + auto irObj = genIR.transcompileLatteToIR(vertexShaderPtr, vertexShaderSize, LatteTCGenIR::VERTEX); + // debug output (before register allocation) + irDebugPrinter.setShowPhysicalRegisters(false); + irDebugPrinter.debugPrint(irObj); + // register allocation + ZirPass::RegisterAllocatorForGLSL ra(irObj); + ra.applyPass(); + // debug output (after register allocation) + irDebugPrinter.setShowPhysicalRegisters(true); + irDebugPrinter.setPhysicalRegisterNameSource(ZirPass::RegisterAllocatorForGLSL::DebugPrintHelper_getPhysRegisterName); + irDebugPrinter.debugPrint(irObj); + // gen GLSL + StringBuf glslSourceBuffer(64 * 1024); + // emit GLSL header + assert_dbg(); // todo + // emit main + ZirEmitter::GLSL emitter; + emitter.Emit(irObj, &glslSourceBuffer); + + // debug copy to string + std::string dbg; + dbg.insert(0, glslSourceBuffer.c_str(), glslSourceBuffer.getLen()); + assert_dbg(); + + + return nullptr; +} + +// compile new vertex shader (relies partially on current state) +LatteDecompilerShader* LatteShader_CompileSeparableVertexShader(uint64 baseHash, uint64& vsAuxHash, uint8* vertexShaderPtr, uint32 vertexShaderSize, bool usesGeometryShader, LatteFetchShader* fetchShader) +{ + // new decompiler + //LatteShader_compileSeparableVertexShader(baseHash, vsAuxHash, vertexShaderPtr, vertexShaderSize, usesGeometryShader, fetchShader); + + // legacy decompiler + LatteDecompilerOutput_t decompilerOutput{}; + LatteFetchShader* fetchShaderList[1]; + fetchShaderList[0] = fetchShader; + LatteDecompiler_DecompileVertexShader(_shaderBaseHash_vs, LatteGPUState.contextRegister, vertexShaderPtr, vertexShaderSize, fetchShaderList, 1, LatteGPUState.contextNew.GetSpecialStateValues(), usesGeometryShader, &decompilerOutput); + LatteDecompilerShader* vertexShader = LatteShader_CreateShaderFromDecompilerOutput(decompilerOutput, baseHash, true, 0, LatteGPUState.contextRegister); + vsAuxHash = vertexShader->auxHash; + if (vertexShader->hasError == false) + { + uint8* fsProgramCode = (uint8*)memory_getPointerFromPhysicalOffset(LatteGPUState.contextRegister[mmSQ_PGM_START_FS + 0] << 8); + uint32 fsProgramSize = LatteGPUState.contextRegister[mmSQ_PGM_START_FS + 1] << 3; + LatteShaderCache_writeSeparableVertexShader(vertexShader->baseHash, vertexShader->auxHash, fsProgramCode, fsProgramSize, vertexShaderPtr, vertexShaderSize, LatteGPUState.contextRegister, usesGeometryShader); + } + LatteShader_DumpShader(vertexShader->baseHash, vertexShader->auxHash, vertexShader); + LatteShader_DumpRawShader(vertexShader->baseHash, vertexShader->auxHash, SHADER_DUMP_TYPE_VERTEX, vertexShaderPtr, vertexShaderSize); + LatteShader_CreateRendererShader(vertexShader, false); + performanceMonitor.numCompiledVS++; + + if (g_renderer->GetType() == RendererAPI::OpenGL) + { + if (vertexShader->shader) + vertexShader->shader->PreponeCompilation(true); + LatteShader_FinishCompilation(vertexShader); + } + + LatteSHRC_RegisterShader(vertexShader, vertexShader->baseHash, vertexShader->auxHash); + return vertexShader; +} + +LatteDecompilerShader* LatteShader_CompileSeparableGeometryShader(uint64 baseHash, uint8* geometryShaderPtr, uint32 geometryShaderSize, uint8* geometryCopyShader, uint32 geometryCopyShaderSize) +{ + LatteDecompilerOutput_t decompilerOutput{}; + LatteFetchShader* fetchShaderList[1]; + fetchShaderList[0] = _activeFetchShader; + LatteDecompiler_DecompileGeometryShader(_shaderBaseHash_gs, LatteGPUState.contextRegister, geometryShaderPtr, geometryShaderSize, geometryCopyShader, geometryCopyShaderSize, LatteGPUState.contextNew.GetSpecialStateValues(), _activeVertexShader->ringParameterCount, &decompilerOutput); + LatteDecompilerShader* geometryShader = LatteShader_CreateShaderFromDecompilerOutput(decompilerOutput, baseHash, true, 0, LatteGPUState.contextRegister); + if (geometryShader->hasError == false) + { + LatteShaderCache_writeSeparableGeometryShader(geometryShader->baseHash, geometryShader->auxHash, geometryShaderPtr, geometryShaderSize, geometryCopyShader, geometryCopyShaderSize, LatteGPUState.contextRegister, LatteGPUState.contextNew.GetSpecialStateValues(), _activeVertexShader->ringParameterCount); + } + LatteShader_DumpShader(geometryShader->baseHash, geometryShader->auxHash, geometryShader); + LatteShader_DumpRawShader(geometryShader->baseHash, geometryShader->auxHash, SHADER_DUMP_TYPE_GEOMETRY, geometryShaderPtr, geometryShaderSize); + LatteShader_DumpRawShader(geometryShader->baseHash, geometryShader->auxHash, SHADER_DUMP_TYPE_COPY, geometryCopyShader, geometryCopyShaderSize); + LatteShader_CreateRendererShader(geometryShader, false); + performanceMonitor.numCompiledGS++; + + if (g_renderer->GetType() == RendererAPI::OpenGL) + { + if (geometryShader->shader) + geometryShader->shader->PreponeCompilation(true); + LatteShader_FinishCompilation(geometryShader); + } + + LatteSHRC_RegisterShader(geometryShader, geometryShader->baseHash, geometryShader->auxHash); + return geometryShader; +} + +LatteDecompilerShader* LatteShader_CompileSeparablePixelShader(uint64 baseHash, uint64& psAuxHash, uint8* pixelShaderPtr, uint32 pixelShaderSize, bool usesGeometryShader) +{ + LatteDecompilerOutput_t decompilerOutput{}; + LatteDecompiler_DecompilePixelShader(baseHash, LatteGPUState.contextRegister, pixelShaderPtr, pixelShaderSize, LatteGPUState.contextNew.GetSpecialStateValues(), usesGeometryShader, &decompilerOutput); + LatteDecompilerShader* pixelShader = LatteShader_CreateShaderFromDecompilerOutput(decompilerOutput, baseHash, true, 0, LatteGPUState.contextRegister); + psAuxHash = pixelShader->auxHash; + LatteShader_DumpShader(_shaderBaseHash_ps, psAuxHash, pixelShader); + LatteShader_DumpRawShader(_shaderBaseHash_ps, psAuxHash, SHADER_DUMP_TYPE_PIXEL, pixelShaderPtr, pixelShaderSize); + LatteShader_CreateRendererShader(pixelShader, false); + performanceMonitor.numCompiledPS++; + if (pixelShader->hasError == false) + { + LatteShaderCache_writeSeparablePixelShader(_shaderBaseHash_ps, psAuxHash, pixelShaderPtr, pixelShaderSize, LatteGPUState.contextRegister, usesGeometryShader); + } + + if (g_renderer->GetType() == RendererAPI::OpenGL) + { + if (pixelShader->shader) + pixelShader->shader->PreponeCompilation(true); + LatteShader_FinishCompilation(pixelShader); + } + + LatteSHRC_RegisterShader(pixelShader, _shaderBaseHash_ps, psAuxHash); + return pixelShader; +} + +void LatteSHRC_UpdateVertexShader(uint8* vertexShaderPtr, uint32 vertexShaderSize, bool usesGeometryShader) +{ + LatteSHRC_UpdateVSBaseHash(vertexShaderPtr, vertexShaderSize, usesGeometryShader); + uint64 vsAuxHash = 0; + auto itBaseShader = sVertexShaders.find(_shaderBaseHash_vs); + LatteDecompilerShader* vertexShader = nullptr; + if (itBaseShader != sVertexShaders.end()) + { + vsAuxHash = LatteSHRC_CalcVSAuxHash(itBaseShader->second, LatteGPUState.contextRegister); + vertexShader = LatteSHRC_GetFromChain(itBaseShader->second, _shaderBaseHash_vs, vsAuxHash); + } + if (!vertexShader) + vertexShader = LatteShader_CompileSeparableVertexShader(_shaderBaseHash_vs, vsAuxHash, vertexShaderPtr, vertexShaderSize, usesGeometryShader, _activeFetchShader); + if (vertexShader->hasError) + { + LatteGPUState.activeShaderHasError = true; + return; + } + g_renderer->shader_bind(vertexShader->shader); + _activeVertexShader = vertexShader; +} + +void LatteSHRC_UpdateGeometryShader(bool usesGeometryShader, uint8* geometryShaderPtr, uint32 geometryShaderSize, uint8* geometryCopyShader, uint32 geometryCopyShaderSize) +{ + if (usesGeometryShader == false || _activeVertexShader == nullptr) + { + g_renderer->shader_unbind(RendererShader::ShaderType::kGeometry); + _shaderBaseHash_gs = 0; + _activeGeometryShader = nullptr; + return; + } + LatteSHRC_UpdateGSBaseHash(geometryShaderPtr, geometryShaderSize, geometryCopyShader, geometryCopyShaderSize); + auto itBaseShader = sGeometryShaders.find(_shaderBaseHash_gs); + LatteDecompilerShader* geometryShader; + if (itBaseShader != sGeometryShaders.end()) + { + // geometry shader already known + geometryShader = itBaseShader->second; + cemu_assert_debug(LatteSHRC_CalcGSAuxHash(geometryShader) == 0); + } + else + { + // decompile geometry shader + geometryShader = LatteShader_CompileSeparableGeometryShader(_shaderBaseHash_gs, geometryShaderPtr, geometryShaderSize, geometryCopyShader, geometryCopyShaderSize); + } + if (geometryShader->hasError) + { + LatteGPUState.activeShaderHasError = true; + return; + } + g_renderer->shader_bind(geometryShader->shader); + _activeGeometryShader = geometryShader; +} + +void LatteSHRC_UpdatePixelShader(uint8* pixelShaderPtr, uint32 pixelShaderSize, bool usesGeometryShader) +{ + if (LatteGPUState.contextRegister[mmVGT_STRMOUT_EN] != 0 && g_renderer->GetType() == RendererAPI::OpenGL) + { + if (_activePixelShader) + { + g_renderer->shader_unbind(RendererShader::ShaderType::kFragment); + _activePixelShader = nullptr; + } + return; + } + LatteSHRC_UpdatePSBaseHash(pixelShaderPtr, pixelShaderSize, usesGeometryShader); + uint64 psAuxHash = 0; + auto itBaseShader = sPixelShaders.find(_shaderBaseHash_ps); + LatteDecompilerShader* pixelShader = nullptr; + if (itBaseShader != sPixelShaders.end()) + { + psAuxHash = LatteSHRC_CalcPSAuxHash(itBaseShader->second, LatteGPUState.contextRegister); + pixelShader = LatteSHRC_GetFromChain(itBaseShader->second, _shaderBaseHash_ps, psAuxHash); + } + if (!pixelShader) + pixelShader = LatteShader_CompileSeparablePixelShader(_shaderBaseHash_ps, psAuxHash, pixelShaderPtr, pixelShaderSize, usesGeometryShader); + if (pixelShader->hasError) + { + LatteGPUState.activeShaderHasError = true; + return; + } + g_renderer->shader_bind(pixelShader->shader); + _activePixelShader = pixelShader; +} + +void LatteSHRC_UpdateActiveShaders() +{ + // check if geometry shader is used + auto gsMode = LatteGPUState.contextNew.VGT_GS_MODE.get_MODE(); + + cemu_assert_debug(LatteGPUState.contextNew.VGT_GS_MODE.get_ES_PASSTHRU() == false); + // todo: Support for ES passthrough and cut mode in mmVGT_GS_MODE + + bool geometryShaderUsed = false; + if (gsMode == Latte::LATTE_VGT_GS_MODE::E_MODE::OFF) + { + geometryShaderUsed = false; + } + else if (gsMode == Latte::LATTE_VGT_GS_MODE::E_MODE::SCENARIO_G) + { + // could also be compute shader? + geometryShaderUsed = true; + } + else + { + cemu_assert_debug(false); + } + // get shader programs + uint8* psProgramCode = (uint8*)memory_getPointerFromPhysicalOffset((LatteGPUState.contextRegister[mmSQ_PGM_START_PS] & 0xFFFFFF) << 8); + uint32 psProgramSize = LatteGPUState.contextRegister[mmSQ_PGM_START_PS + 1] << 3; + uint8* gsProgramCode = (uint8*)memory_getPointerFromPhysicalOffset((LatteGPUState.contextRegister[mmSQ_PGM_START_GS] & 0xFFFFFF) << 8); + uint32 gsProgramSize = LatteGPUState.contextRegister[mmSQ_PGM_START_GS + 1] << 3; + + uint8* vsProgramCode; + uint32 vsProgramSize; + uint8* copyProgramCode = NULL; + uint32 copyProgramSize = 0; + if (geometryShaderUsed) + { + vsProgramCode = (uint8*)memory_getPointerFromPhysicalOffset((LatteGPUState.contextRegister[mmSQ_PGM_START_ES] & 0xFFFFFF) << 8); + vsProgramSize = LatteGPUState.contextRegister[mmSQ_PGM_START_ES + 1] << 3; + copyProgramCode = (uint8*)memory_getPointerFromPhysicalOffset((LatteGPUState.contextRegister[mmSQ_PGM_START_VS] & 0xFFFFFF) << 8); + if (LatteGPUState.contextRegister[mmSQ_PGM_START_VS] == 0) + { + copyProgramCode = NULL; + debug_printf("copyProgram is NULL but used. Might be because of unsupported vertex/geometry mode?"); + } + copyProgramSize = LatteGPUState.contextRegister[mmSQ_PGM_START_VS + 1] << 3; + } + else + { + if (LatteGPUState.contextRegister[mmSQ_PGM_START_VS] == 0) + { + debug_printf("No vertex shader program set\n"); + LatteGPUState.activeShaderHasError = true; + return; + } + vsProgramCode = (uint8*)memory_getPointerFromPhysicalOffset((LatteGPUState.contextRegister[mmSQ_PGM_START_VS] & 0xFFFFFF) << 8); + vsProgramSize = LatteGPUState.contextRegister[mmSQ_PGM_START_VS + 1] << 3; + } + // set new shaders + LatteGPUState.activeShaderHasError = false; + LatteShader_UpdatePSInputs(LatteGPUState.contextRegister); + LatteShaderSHRC_UpdateFetchShader(); + LatteSHRC_UpdateVertexShader(vsProgramCode, vsProgramSize, geometryShaderUsed); + if (LatteGPUState.activeShaderHasError) + return; + LatteSHRC_UpdateGeometryShader(geometryShaderUsed, gsProgramCode, gsProgramSize, copyProgramCode, copyProgramSize); + if (LatteGPUState.activeShaderHasError) + return; + LatteSHRC_UpdatePixelShader(psProgramCode, psProgramSize, geometryShaderUsed); + if (LatteGPUState.activeShaderHasError) + return; +} + +// returns the sampler base index for the given shader type +sint32 LatteDecompiler_getTextureSamplerBaseIndex(LatteConst::ShaderType shaderType) +{ + uint32 samplerId = LATTE_DECOMPILER_SAMPLER_NONE; + if (shaderType == LatteConst::ShaderType::Vertex) + return Latte::SAMPLER_BASE_INDEX_VERTEX; + else if (shaderType == LatteConst::ShaderType::Pixel) + return Latte::SAMPLER_BASE_INDEX_PIXEL; + else if (shaderType == LatteConst::ShaderType::Geometry) + return Latte::SAMPLER_BASE_INDEX_GEOMETRY; + else + cemu_assert_suspicious(); + return 0; +} + +void LatteSHRC_Init() +{ + cemu_assert_debug(sVertexShaders.empty()); + cemu_assert_debug(sGeometryShaders.empty()); + cemu_assert_debug(sPixelShaders.empty()); +} diff --git a/src/Cafe/HW/Latte/Core/LatteShader.h b/src/Cafe/HW/Latte/Core/LatteShader.h new file mode 100644 index 00000000..0fba0322 --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteShader.h @@ -0,0 +1,126 @@ +#pragma once +#include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h" +#include "Cafe/HW/Latte/ISA/RegDefines.h" + +void LatteSHRC_Init(); + +void LatteSHRC_ResetCachedShaderHash(); +void LatteShaderSHRC_UpdateFetchShader(); + +void LatteSHRC_UpdateActiveShaders(); + +struct LatteFetchShader* LatteSHRC_GetActiveFetchShader(); +LatteDecompilerShader* LatteSHRC_GetActiveVertexShader(); +LatteDecompilerShader* LatteSHRC_GetActiveGeometryShader(); +LatteDecompilerShader* LatteSHRC_GetActivePixelShader(); + +LatteDecompilerShader* LatteSHRC_FindVertexShader(uint64 baseHash, uint64 auxHash); +LatteDecompilerShader* LatteSHRC_FindGeometryShader(uint64 baseHash, uint64 auxHash); +LatteDecompilerShader* LatteSHRC_FindPixelShader(uint64 baseHash, uint64 auxHash); + + +#define GPU7_PS_MAX_INPUTS 32 + +struct LatteShaderPSInputTable +{ + struct psImport_t + { + uint32 semanticId; + bool isFlat; + bool isNoPerspective; + }; + psImport_t import[GPU7_PS_MAX_INPUTS]{}; // GPR is index + sint32 count; + uint64 key; + uint8 paramGen; + uint8 paramGenGPR; + + // returns semantic id of vertex shader output by index + // the returned semanticId may not have a match in the pixel shader (use hasPSImportForSemanticId to determine matched exports) + static sint32 getVertexShaderOutParamSemanticId(uint32* contextRegisters, sint32 index) + { + cemu_assert_debug(index >= 0 && index < 32); + uint32 vsSemanticId = (contextRegisters[mmSPI_VS_OUT_ID_0 + (index / 4)] >> (8 * (index % 4))) & 0xFF; + return (sint32)vsSemanticId; + } + + bool hasPSImportForSemanticId(sint32 semanticId) + { + for (sint32 i = 0; i < this->count; i++) + { + if (this->import[i].semanticId == semanticId) + return true; + } + return false; + } + + psImport_t* getPSImportBySemanticId(sint32 semanticId) + { + if (semanticId > LATTE_ANALYZER_IMPORT_INDEX_PARAM_MAX) + return nullptr; + // get import based on semanticId + sint32 psInputIndex = -1; + for (sint32 f = 0; f < this->count; f++) + { + if (this->import[f].semanticId == semanticId) + return this->import + f; + } + return nullptr; + } + + sint32 getPSImportLocationBySemanticId(sint32 semanticId) + { + if (semanticId > LATTE_ANALYZER_IMPORT_INDEX_PARAM_MAX) + return -1; + // get import based on semanticId + sint32 psInputIndex = -1; + for (sint32 f = 0; f < this->count; f++) + { + if (this->import[f].semanticId == semanticId) + return f; + } + return -1; + } +}; + +void LatteShader_UpdatePSInputs(uint32* contextRegisters); +LatteShaderPSInputTable* LatteSHRC_GetPSInputTable(); + +void LatteShader_free(LatteDecompilerShader* shader); +void LatteSHRC_RemoveFromCacheByHash(uint64 shader_base_hash, uint64 shader_aux_hash, LatteConst::ShaderType type); + +extern uint64 _shaderBaseHash_fs; +extern uint64 _shaderBaseHash_vs; +extern uint64 _shaderBaseHash_gs; +extern uint64 _shaderBaseHash_ps; + +LatteDecompilerShader* LatteShader_CreateShaderFromDecompilerOutput(LatteDecompilerOutput_t& decompilerOutput, uint64 baseHash, bool calculateAuxHash, uint64 optionalAuxHash, uint32* contextRegister); + +void LatteShader_CreateRendererShader(LatteDecompilerShader* shader, bool compileAsync); +void LatteShader_FinishCompilation(LatteDecompilerShader* shader); + +void LatteShader_prepareSeparableUniforms(LatteDecompilerShader* shader); + +void LatteSHRC_RegisterShader(LatteDecompilerShader* shader, uint64 baseHash, uint64 auxHash); + +void LatteShader_CleanupAfterCompile(LatteDecompilerShader* shader); + +#define SHADER_DUMP_TYPE_FETCH 0 +#define SHADER_DUMP_TYPE_VERTEX 1 +#define SHADER_DUMP_TYPE_GEOMETRY 2 +#define SHADER_DUMP_TYPE_PIXEL 3 +#define SHADER_DUMP_TYPE_COPY 4 +#define SHADER_DUMP_TYPE_COMPUTE 5 + +void LatteShader_DumpShader(uint64 baseHash, uint64 auxHash, LatteDecompilerShader* shader); +void LatteShader_DumpRawShader(uint64 baseHash, uint64 auxHash, uint32 type, uint8* programCode, uint32 programLen); + +// shader cache file +void LatteShaderCache_load(); + +void LatteShaderCache_writeSeparableVertexShader(uint64 shaderBaseHash, uint64 shaderAuxHash, uint8* fetchShader, uint32 fetchShaderSize, uint8* vertexShader, uint32 vertexShaderSize, uint32* contextRegisters, bool usesGeometryShader); +void LatteShaderCache_writeSeparableGeometryShader(uint64 shaderBaseHash, uint64 shaderAuxHash, uint8* geometryShader, uint32 geometryShaderSize, uint8* gsCopyShader, uint32 gsCopyShaderSize, uint32* contextRegisters, uint32* hleSpecialState, uint32 vsRingParameterCount); +void LatteShaderCache_writeSeparablePixelShader(uint64 shaderBaseHash, uint64 shaderAuxHash, uint8* pixelShader, uint32 pixelShaderSize, uint32* contextRegisters, bool usesGeometryShader); + +// todo - sort this +sint32 LatteDecompiler_getTextureSamplerBaseIndex(LatteConst::ShaderType shaderType); \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Core/LatteShaderAssembly.h b/src/Cafe/HW/Latte/Core/LatteShaderAssembly.h new file mode 100644 index 00000000..df636689 --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteShaderAssembly.h @@ -0,0 +1,95 @@ +#pragma once + +// CF export + +#define GPU7_DECOMPILER_CF_EXPORT_BASE_POSITION (60) +#define GPU7_DECOMPILER_CF_EXPORT_POINT_SIZE (61) + +// CF instruction set + +#define GPU7_CF_INST_NOP (0x00) +#define GPU7_CF_INST_TEX (0x01) +#define GPU7_CF_INST_VTX (0x02) // used only in GS copy program? +#define GPU7_CF_INST_LOOP_END (0x05) +#define GPU7_CF_INST_LOOP_START_DX10 (0x06) +#define GPU7_CF_INST_LOOP_START_NO_AL (0x07) // (Seen in Project Zero) + +#define GPU7_CF_INST_LOOP_BREAK (0x09) +#define GPU7_CF_INST_JUMP (0x0A) +#define GPU7_CF_INST_ELSE (0x0D) +#define GPU7_CF_INST_POP (0x0E) +#define GPU7_CF_INST_ELSE_AFTER (0x0F) +#define GPU7_CF_INST_CALL (0x12) +#define GPU7_CF_INST_CALL_FS (0x13) +#define GPU7_CF_INST_RETURN (0x14) +#define GPU7_CF_INST_EMIT_VERTEX (0x15) // only available in geometry shader +#define GPU7_CF_INST_MEM_STREAM0_WRITE (0x20) // for stream out +#define GPU7_CF_INST_MEM_STREAM1_WRITE (0x21) // for stream out +#define GPU7_CF_INST_MEM_RING_WRITE (0x26) // used to pass data to/from geometry shader +#define GPU7_CF_INST_EXPORT (0x27) +#define GPU7_CF_INST_EXPORT_DONE (0x28) + +#define GPU7_CF_INST_ALU_MASK (0x10000) // this mask is used to make sure that there is no collision between any GPU7_CF_INST_* values (e.g. GPU7_CF_INST_ALU_BREAK and GPU7_CF_INST_POP would collide due to different encoding but identical value) +#define GPU7_CF_INST_ALU (0x08 | GPU7_CF_INST_ALU_MASK) +#define GPU7_CF_INST_ALU_PUSH_BEFORE (0x09 | GPU7_CF_INST_ALU_MASK) +#define GPU7_CF_INST_ALU_POP_AFTER (0x0A | GPU7_CF_INST_ALU_MASK) +#define GPU7_CF_INST_ALU_POP2_AFTER (0x0B | GPU7_CF_INST_ALU_MASK) +#define GPU7_CF_INST_ALU_BREAK (0x0E | GPU7_CF_INST_ALU_MASK) +#define GPU7_CF_INST_ALU_ELSE_AFTER (0x0F | GPU7_CF_INST_ALU_MASK) + + +//#define INDEX_NONE (-1) // used to indicate absolute access +#define GPU7_INDEX_AR_X (0) +#define GPU7_INDEX_AR_Y (1) +#define GPU7_INDEX_AR_Z (2) +#define GPU7_INDEX_AR_W (3) +#define GPU7_INDEX_LOOP (4) +#define GPU7_INDEX_GLOBAL (5) +#define GPU7_INDEX_GLOBAL_AR_X (6) + +#define GPU7_TEX_INST_VFETCH (0x00) +#define GPU7_TEX_INST_MEM (0x02) +#define GPU7_TEX_INST_LD (0x03) +#define GPU7_TEX_INST_GET_TEXTURE_RESINFO (0x04) +#define GPU7_TEX_INST_GET_COMP_TEX_LOD (0x06) +#define GPU7_TEX_INST_GET_GRADIENTS_H (0x07) +#define GPU7_TEX_INST_GET_GRADIENTS_V (0x08) +#define GPU7_TEX_INST_SET_GRADIENTS_H (0x0B) +#define GPU7_TEX_INST_SET_GRADIENTS_V (0x0C) +#define GPU7_TEX_INST_SET_CUBEMAP_INDEX (0x0E) +#define GPU7_TEX_INST_FETCH4 (0x0F) +#define GPU7_TEX_INST_SAMPLE (0x10) // LOD from hw +#define GPU7_TEX_INST_SAMPLE_L (0x11) // LOD from srcGpr.w +#define GPU7_TEX_INST_SAMPLE_LB (0x12) // todo: Accesses LOD_BIAS field of instruction +#define GPU7_TEX_INST_SAMPLE_LZ (0x13) // LOD is 0.0 +#define GPU7_TEX_INST_SAMPLE_G (0x14) +#define GPU7_TEX_INST_SAMPLE_C (0x18) // sample & compare, LOD from hw +#define GPU7_TEX_INST_SAMPLE_C_L (0x19) // sample & compare, LOD from srcGpr.w +#define GPU7_TEX_INST_SAMPLE_C_LZ (0x1B) // sample & compare, LOD is 0.0 + +#define GPU7_TEX_UNNORMALIZED (0) +#define GPU7_TEX_NORMALIZED (1) + + +#define GPU7_ALU_SRC_UNUSED (-1) // special value which indicates that the ALU operand is not used +#define GPU7_ALU_SRC_IS_GPR(__v) ((__v)>=0 && (__v)<128) // 0-127 +#define GPU7_ALU_SRC_IS_CBANK0(__v) ((__v)>=128 && (__v)<160) // 128-159 +#define GPU7_ALU_SRC_IS_CBANK1(__v) ((__v)>=160 && (__v)<192) // 160-191 +#define GPU7_ALU_SRC_IS_CONST_0F(__v) ((__v)==248) // 248 +#define GPU7_ALU_SRC_IS_CONST_1F(__v) ((__v)==249) // 249 +#define GPU7_ALU_SRC_IS_CONST_1I(__v) ((__v)==250) // 250 +#define GPU7_ALU_SRC_IS_CONST_M1I(__v) ((__v)==251) // 251 (0xFB) +#define GPU7_ALU_SRC_IS_CONST_0_5F(__v) ((__v)==252) // 252 (0xFC) +#define GPU7_ALU_SRC_IS_LITERAL(__v) ((__v)==253) // 253 (0xFD) +#define GPU7_ALU_SRC_IS_PV(__v) ((__v)==254) // 254 (0xFE) +#define GPU7_ALU_SRC_IS_PS(__v) ((__v)==255) // 255 (0xFF) +#define GPU7_ALU_SRC_IS_CFILE(__v) ((__v)>=256 && (__v)<512) // 256-511 (uniform register) + +#define GPU7_ALU_SRC_IS_ANY_CONST(__v) ((__v)>=248 && (__v)<253) // special macro to handle all GPU7_ALU_SRC_IS_CONST_* + +#define GPU7_ALU_SRC_GET_GPR_INDEX(__v) ((__v)) +#define GPU7_ALU_SRC_GET_CFILE_INDEX(__v) ((__v)-256) +#define GPU7_ALU_SRC_GET_CBANK0_INDEX(__v) ((__v)-128) +#define GPU7_ALU_SRC_GET_CBANK1_INDEX(__v) ((__v)-160) + +#define GPU7_ALU_SRC_LITERAL (0xFD) diff --git a/src/Cafe/HW/Latte/Core/LatteShaderCache.cpp b/src/Cafe/HW/Latte/Core/LatteShaderCache.cpp new file mode 100644 index 00000000..3bc6699b --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteShaderCache.cpp @@ -0,0 +1,778 @@ +#include "Cafe/CafeSystem.h" +#include "Cafe/HW/Latte/Core/LatteConst.h" +#include "Cafe/HW/Latte/Core/Latte.h" +#include "Cafe/HW/Latte/Core/LatteShader.h" +#include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h" +#include "Cafe/HW/Latte/Core/FetchShader.h" +#include "Cemu/FileCache/FileCache.h" +#include "Cafe/GraphicPack/GraphicPack.h" +#include "Cafe/GameProfile/GameProfile.h" +#include "gui/guiWrapper.h" + +#include "Cafe/HW/Latte/Renderer/Renderer.h" +#include "Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.h" + +#include <imgui.h> +#include "imgui/imgui_extension.h" + +#include "config/ActiveSettings.h" +#include "Cafe/TitleList/GameInfo.h" + +#include "util/helpers/SystemException.h" +#include "Cafe/HW/Latte/Common/RegisterSerializer.h" +#include "Cafe/HW/Latte/Common/ShaderSerializer.h" +#include "util/helpers/Serializer.h" + +#include <wx/msgdlg.h> + +#if BOOST_OS_WINDOWS > 0 +#include <psapi.h> +#endif + +#define SHADER_CACHE_COMPILE_QUEUE_SIZE (32) + +struct +{ + sint32 compiledShaderCount; + // number of loaded shaders + sint32 vertexShaderCount; + sint32 geometryShaderCount; + sint32 pixelShaderCount; +}shaderCacheScreenStats; + +struct +{ + ImTextureID textureId; + // shader loading + sint32 loadedShaderFiles; + sint32 shaderFileCount; + // pipeline loading + uint32 loadedPipelines; + sint32 pipelineFileCount; +}g_shaderCacheLoaderState; + +FileCache* fc_shaderCacheGeneric = nullptr; // contains hardware and Cemu version independent shader information + +#define SHADER_CACHE_GENERIC_EXTRA_VERSION 2 // changing this constant will invalidate all hardware-independent cache files + +#define SHADER_CACHE_TYPE_VERTEX (0) +#define SHADER_CACHE_TYPE_GEOMETRY (1) +#define SHADER_CACHE_TYPE_PIXEL (2) + +bool LatteShaderCache_readSeparableShader(uint8* shaderInfoData, sint32 shaderInfoSize); +void LatteShaderCache_loadVulkanPipelineCache(uint64 cacheTitleId); +bool LatteShaderCache_updatePipelineLoadingProgress(); +void LatteShaderCache_ShowProgress(const std::function <bool(void)>& loadUpdateFunc, bool isPipelines); + +void LatteShaderCache_handleDeprecatedCacheFiles(fs::path pathGeneric, fs::path pathGenericPre1_25_0, fs::path pathGenericPre1_16_0); + +struct +{ + struct + { + LatteDecompilerShader* shader; + }entry[SHADER_CACHE_COMPILE_QUEUE_SIZE]; + sint32 count; +}shaderCompileQueue; + +void LatteShaderCache_initCompileQueue() +{ + shaderCompileQueue.count = 0; +} + +void LatteShaderCache_addToCompileQueue(LatteDecompilerShader* shader) +{ + cemu_assert(shaderCompileQueue.count < SHADER_CACHE_COMPILE_QUEUE_SIZE); + shaderCompileQueue.entry[shaderCompileQueue.count].shader = shader; + shaderCompileQueue.count++; +} + +void LatteShaderCache_removeFromCompileQueue(sint32 index) +{ + for (sint32 i = index; i<shaderCompileQueue.count-1; i++) + shaderCompileQueue.entry[i].shader = shaderCompileQueue.entry[i + 1].shader; + shaderCompileQueue.count--; +} + +/* + * Process entries from compile queue until there are equal or less entries + * left than specified by maxRemainingEntries + */ +void LatteShaderCache_updateCompileQueue(sint32 maxRemainingEntries) +{ + while (true) + { + if (shaderCompileQueue.count <= maxRemainingEntries) + break; + auto shader = shaderCompileQueue.entry[0].shader; + if (shader) + LatteShader_FinishCompilation(shader); + LatteShaderCache_removeFromCompileQueue(0); + } +} + +typedef struct +{ + unsigned char imageTypeCode; + short int imageWidth; + short int imageHeight; + unsigned char bitCount; + std::vector<uint8> imageData; +} TGAFILE; + +bool LoadTGAFile(const std::vector<uint8>& buffer, TGAFILE *tgaFile) +{ + if (buffer.size() <= 18) + return false; + + tgaFile->imageTypeCode = buffer[2]; + if (tgaFile->imageTypeCode != 2 && tgaFile->imageTypeCode != 3) + return false; + + tgaFile->imageWidth = *(uint16*)(buffer.data() + 12); + tgaFile->imageHeight = *(uint16*)(buffer.data() + 14); + tgaFile->bitCount = buffer[16]; + + // Color mode -> 3 = BGR, 4 = BGRA. + const uint8 colorMode = tgaFile->bitCount / 8; + if (colorMode != 3) + return false; + + const uint32 imageSize = tgaFile->imageWidth * tgaFile->imageHeight * colorMode; + if (imageSize + 18 >= buffer.size()) + return false; + + tgaFile->imageData.resize(imageSize); + std::copy(buffer.data() + 18, buffer.data() + 18 + imageSize, tgaFile->imageData.begin()); + // Change from BGR to RGB so OpenGL can read the image data. + for (uint32 imageIdx = 0; imageIdx < imageSize; imageIdx += colorMode) + { + std::swap(tgaFile->imageData[imageIdx], tgaFile->imageData[imageIdx + 2]); + } + + return true; +} + +void LatteShaderCache_finish() +{ + if (g_renderer->GetType() == RendererAPI::Vulkan) + RendererShaderVk::ShaderCacheLoading_end(); + else if (g_renderer->GetType() == RendererAPI::OpenGL) + RendererShaderGL::ShaderCacheLoading_end(); +} + +uint32 LatteShaderCache_getShaderCacheExtraVersion(uint64 titleId) +{ + // encode the titleId in the version to prevent users from swapping caches between titles + const uint32 cacheFileVersion = 1; + uint32 extraVersion = ((uint32)(titleId >> 32) + ((uint32)titleId) * 3) + cacheFileVersion + 0xe97af1ad; + return extraVersion; +} + +uint32 LatteShaderCache_getPipelineCacheExtraVersion(uint64 titleId) +{ + const uint32 cacheFileVersion = 1; + uint32 extraVersion = ((uint32)(titleId >> 32) + ((uint32)titleId) * 3) + cacheFileVersion; + return extraVersion; +} + +void LatteShaderCache_load() +{ + shaderCacheScreenStats.compiledShaderCount = 0; + shaderCacheScreenStats.vertexShaderCount = 0; + shaderCacheScreenStats.geometryShaderCount = 0; + shaderCacheScreenStats.pixelShaderCount = 0; + + uint64 cacheTitleId = CafeSystem::GetForegroundTitleId(); + + const auto timeLoadStart = now_cached(); + // remember current amount of committed memory +#if BOOST_OS_WINDOWS > 0 + PROCESS_MEMORY_COUNTERS pmc1; + GetProcessMemoryInfo(GetCurrentProcess(), &pmc1, sizeof(PROCESS_MEMORY_COUNTERS)); + LONGLONG totalMem1 = pmc1.PagefileUsage; +#endif + // init shader parallel compile queue + LatteShaderCache_initCompileQueue(); + // create directories + std::error_code ec; + fs::create_directories(ActiveSettings::GetPath("shaderCache/transferable"), ec); + fs::create_directories(ActiveSettings::GetPath("shaderCache/precompiled"), ec); + // initialize renderer specific caches + if (g_renderer->GetType() == RendererAPI::Vulkan) + RendererShaderVk::ShaderCacheLoading_begin(cacheTitleId); + else if (g_renderer->GetType() == RendererAPI::OpenGL) + RendererShaderGL::ShaderCacheLoading_begin(cacheTitleId); + // get cache file name + const auto pathGeneric = ActiveSettings::GetPath("shaderCache/transferable/{:016x}_shaders.bin", cacheTitleId); + const auto pathGenericPre1_25_0 = ActiveSettings::GetPath("shaderCache/transferable/{:016x}.bin", cacheTitleId); // before 1.25.0 + const auto pathGenericPre1_16_0 = ActiveSettings::GetPath("shaderCache/transferable/{:08x}.bin", CafeSystem::GetRPXHashBase()); // before 1.16.0 + + LatteShaderCache_handleDeprecatedCacheFiles(pathGeneric, pathGenericPre1_25_0, pathGenericPre1_16_0); + // calculate extraVersion for transferable and precompiled shader cache + uint32 transferableExtraVersion = SHADER_CACHE_GENERIC_EXTRA_VERSION; + fc_shaderCacheGeneric = FileCache::Open(pathGeneric.generic_wstring(), false, transferableExtraVersion); // legacy extra version (1.25.0 - 1.25.1b) + if(!fc_shaderCacheGeneric) + fc_shaderCacheGeneric = FileCache::Open(pathGeneric.generic_wstring(), true, LatteShaderCache_getShaderCacheExtraVersion(cacheTitleId)); + if(!fc_shaderCacheGeneric) + { + // no shader cache available yet + forceLog_printfW(L"Unable to open or create shader cache file \"%s\"", pathGeneric.c_str()); + LatteShaderCache_finish(); + return; + } + fc_shaderCacheGeneric->UseCompression(false); + + // load/compile cached shaders + sint32 entryCount = fc_shaderCacheGeneric->GetMaximumFileIndex(); + g_shaderCacheLoaderState.shaderFileCount = fc_shaderCacheGeneric->GetFileCount(); + g_shaderCacheLoaderState.loadedShaderFiles = 0; + + // get game background loading image + TGAFILE file{}; + g_shaderCacheLoaderState.textureId = nullptr; + + std::string tvTexPath = fmt::format("{}/meta/bootTvTex.tga", CafeSystem::GetMlcStoragePath(CafeSystem::GetForegroundTitleId())); + sint32 status; + auto fscfile = fsc_open(tvTexPath.c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &status); + if (fscfile) + { + uint32 size = fsc_getFileSize(fscfile); + if (size > 0) + { + std::vector<uint8> tmpData(size); + fsc_readFile(fscfile, tmpData.data(), size); + const bool backgroundLoaded = LoadTGAFile(tmpData, &file); + + if (backgroundLoaded) + g_shaderCacheLoaderState.textureId = g_renderer->GenerateTexture(file.imageData, { file.imageWidth, file.imageHeight }); + } + + fsc_close(fscfile); + } + + sint32 numLoadedShaders = 0; + uint32 loadIndex = 0; + + auto LoadShadersUpdate = [&]() -> bool + { + if (loadIndex >= (uint32)fc_shaderCacheGeneric->GetMaximumFileIndex()) + return false; + LatteShaderCache_updateCompileQueue(SHADER_CACHE_COMPILE_QUEUE_SIZE - 2); + uint64 name1; + uint64 name2; + std::vector<uint8> fileData; + if (!fc_shaderCacheGeneric->GetFileByIndex(loadIndex, &name1, &name2, fileData)) + { + loadIndex++; + return true; + } + g_shaderCacheLoaderState.loadedShaderFiles++; + if (LatteShaderCache_readSeparableShader(fileData.data(), fileData.size()) == false) + { + // something is wrong with the stored shader, remove entry from shader cache files + forceLog_printf("Shader cache entry %d invalid, deleting...", loadIndex); + fc_shaderCacheGeneric->DeleteFile({ name1, name2 }); + } + numLoadedShaders++; + loadIndex++; + return true; + }; + + LatteShaderCache_ShowProgress(LoadShadersUpdate, false); + + LatteShaderCache_updateCompileQueue(0); + // write load time and RAM usage to log file (in dev build) +#if BOOST_OS_WINDOWS > 0 + const auto timeLoadEnd = now_cached(); + const auto timeLoad = std::chrono::duration_cast<std::chrono::milliseconds>(timeLoadEnd - timeLoadStart).count(); + PROCESS_MEMORY_COUNTERS pmc2; + GetProcessMemoryInfo(GetCurrentProcess(), &pmc2, sizeof(PROCESS_MEMORY_COUNTERS)); + LONGLONG totalMem2 = pmc2.PagefileUsage; + LONGLONG memCommited = totalMem2 - totalMem1; + forceLog_printf("Shader cache loaded with %d shaders. Commited mem %dMB. Took %dms", numLoadedShaders, (sint32)(memCommited/1024/1024), timeLoad); +#endif + LatteShaderCache_finish(); + // if Vulkan then also load pipeline cache + if (g_renderer->GetType() == RendererAPI::Vulkan) + LatteShaderCache_loadVulkanPipelineCache(cacheTitleId); + + // clear framebuffers and clean up + auto& io = ImGui::GetIO(); + const auto kPopupFlags = ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_AlwaysAutoResize; + for (int i = 0; i < 2; ++i) + { + g_renderer->BeginFrame(true); + if (g_renderer->ImguiBegin(true)) + { + ImGui::SetNextWindowPos({ 0,0 }, ImGuiCond_Always); + ImGui::SetNextWindowSize(io.DisplaySize, ImGuiCond_Always); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, { 0,0 }); + if (ImGui::Begin("Background texture", nullptr, kPopupFlags)) + { + if (g_shaderCacheLoaderState.textureId) + { + float imageDisplayWidth = io.DisplaySize.x; + float imageDisplayHeight = 720 * imageDisplayWidth / 1280; + + float paddingLeftAndRight = 0.0f; + float paddingTopAndBottom = (io.DisplaySize.y - imageDisplayHeight)/2.0f; + if (imageDisplayHeight > io.DisplaySize.y) + { + imageDisplayHeight = io.DisplaySize.y; + imageDisplayWidth = 1280 * imageDisplayHeight / 720; + paddingLeftAndRight = (io.DisplaySize.x - imageDisplayWidth)/2.0f; + paddingTopAndBottom = 0.0f; + } + + ImGui::GetWindowDrawList()->AddImage(g_shaderCacheLoaderState.textureId, ImVec2(paddingLeftAndRight, paddingTopAndBottom), ImVec2(io.DisplaySize.x-paddingLeftAndRight, io.DisplaySize.y-paddingTopAndBottom), { 0,1 }, { 1,0 }); + } + ImGui::End(); + } + ImGui::PopStyleVar(2); + + g_renderer->ImguiEnd(); + } + g_renderer->SwapBuffers(true, true); + } + + if (g_shaderCacheLoaderState.textureId) + g_renderer->DeleteTexture(g_shaderCacheLoaderState.textureId); +} + +void LatteShaderCache_ShowProgress(const std::function <bool(void)>& loadUpdateFunc, bool isPipelines) +{ + auto& io = ImGui::GetIO(); + const auto kPopupFlags = ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_AlwaysAutoResize; + const auto textColor = 0xFF888888; + + auto lastFrameUpdate = tick_cached(); + + while (true) + { + bool r = loadUpdateFunc(); + if (!r) + break; + + // in order to slightly speed up shader loading, we don't update the display if little time passed + // this also avoids delayed loading in case third party software caps the framerate at 30 + if ((tick_cached() - lastFrameUpdate) < std::chrono::milliseconds(1000 / 20)) // -> aim for 20 FPS + continue; + + int w, h; + gui_getWindowSize(&w, &h); + const Vector2f window_size{ (float)w,(float)h }; + + ImGui_GetFont(window_size.y / 32.0f); // = 24 by default + ImGui_GetFont(window_size.y / 48.0f); // = 16 + + g_renderer->BeginFrame(true); + if (g_renderer->ImguiBegin(true)) + { + const auto progress_font = ImGui_GetFont(window_size.y / 32.0f); // = 24 by default + const auto shader_count_font = ImGui_GetFont(window_size.y / 48.0f); // = 16 + // render background texture + if (g_shaderCacheLoaderState.textureId) + { + ImGui::SetNextWindowPos({ 0, 0 }, ImGuiCond_Always); + ImGui::SetNextWindowSize(io.DisplaySize, ImGuiCond_Always); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, { 0, 0 }); + if (ImGui::Begin("Background texture", nullptr, kPopupFlags | ImGuiWindowFlags_NoBringToFrontOnFocus)) + { + float imageDisplayWidth = io.DisplaySize.x; + float imageDisplayHeight = 720 * imageDisplayWidth / 1280; + + float paddingLeftAndRight = 0.0f; + float paddingTopAndBottom = (io.DisplaySize.y - imageDisplayHeight) / 2.0f; + if (imageDisplayHeight > io.DisplaySize.y) + { + imageDisplayHeight = io.DisplaySize.y; + imageDisplayWidth = 1280 * imageDisplayHeight / 720; + paddingLeftAndRight = (io.DisplaySize.x - imageDisplayWidth) / 2.0f; + paddingTopAndBottom = 0.0f; + } + + ImGui::GetWindowDrawList()->AddImage(g_shaderCacheLoaderState.textureId, ImVec2(paddingLeftAndRight, paddingTopAndBottom), ImVec2(io.DisplaySize.x-paddingLeftAndRight, io.DisplaySize.y-paddingTopAndBottom), { 0,1 }, { 1,0 }); + ImGui::End(); + } + ImGui::PopStyleVar(2); + } + + ImVec2 position = { window_size.x / 2.0f, window_size.y / 2.0f }; + ImVec2 pivot = { 0.5f, 0.5f }; + ImVec2 progress_size = { io.DisplaySize.x * 0.5f, 0 }; + ImGui::SetNextWindowPos(position, ImGuiCond_Always, pivot); + ImGui::SetNextWindowSize(progress_size, ImGuiCond_Always); + ImGui::SetNextWindowBgAlpha(0.8f); + ImGui::PushStyleColor(ImGuiCol_PlotHistogram, textColor); + ImGui::PushStyleColor(ImGuiCol_WindowBg, 0); + ImGui::PushFont(progress_font); + + std::string titleText = "Shader progress"; + + if (ImGui::Begin(titleText.c_str(), nullptr, kPopupFlags)) + { + const float width = ImGui::GetWindowSize().x / 2.0f; + + std::string text; + if (isPipelines) + { + text = "Loading cached Vulkan pipelines..."; + } + else + { + if (shaderCacheScreenStats.compiledShaderCount >= 3) + text = "Compiling cached shaders..."; + else + text = "Loading cached shaders..."; + } + + ImGui::SetCursorPosX(width - ImGui::CalcTextSize(text.c_str()).x / 2); + ImGui::Text(text.c_str()); + + float percentLoaded; + if(isPipelines) + percentLoaded = (float)g_shaderCacheLoaderState.loadedPipelines / (float)g_shaderCacheLoaderState.pipelineFileCount; + else + percentLoaded = (float)g_shaderCacheLoaderState.loadedShaderFiles / (float)g_shaderCacheLoaderState.shaderFileCount; + ImGui::ProgressBar(percentLoaded, { -1, 0 }, ""); + + if (isPipelines) + text = fmt::format("{}/{} ({}%%)", g_shaderCacheLoaderState.loadedPipelines, g_shaderCacheLoaderState.pipelineFileCount, (int)(percentLoaded * 100)); + else + text = fmt::format("{}/{} ({}%%)", g_shaderCacheLoaderState.loadedShaderFiles, g_shaderCacheLoaderState.shaderFileCount, (int)(percentLoaded * 100)); + ImGui::SetCursorPosX(width - ImGui::CalcTextSize(text.c_str()).x / 2); + ImGui::Text(text.c_str()); + ImGui::End(); + } + ImGui::PopFont(); + ImGui::PopStyleColor(2); + + if (!isPipelines) + { + position = { 10, window_size.y - 10 }; + pivot = { 0, 1 }; + ImGui::SetNextWindowPos(position, ImGuiCond_Always, pivot); + ImGui::SetNextWindowBgAlpha(0.8f); + ImGui::PushStyleColor(ImGuiCol_WindowBg, 0); + ImGui::PushFont(shader_count_font); + if (ImGui::Begin("Shader count", nullptr, kPopupFlags)) + { + const float offset = shader_count_font->FallbackAdvanceX * 25.f; + ImGui::Text("Vertex shaders"); + ImGui::SameLine(offset); + ImGui::Text("%d", shaderCacheScreenStats.vertexShaderCount); + + ImGui::Text("Pixel shaders"); + ImGui::SameLine(offset); + ImGui::Text("%d", shaderCacheScreenStats.pixelShaderCount); + + ImGui::Text("Geometry shaders"); + ImGui::SameLine(offset); + ImGui::Text("%d", shaderCacheScreenStats.geometryShaderCount); + + ImGui::End(); + } + ImGui::PopStyleColor(); + ImGui::PopFont(); + } + + g_renderer->ImguiEnd(); + lastFrameUpdate = tick_cached(); + } + + // finish frame + g_renderer->SwapBuffers(true, true); + } +} + +void LatteShaderCache_loadVulkanPipelineCache(uint64 cacheTitleId) +{ + auto& pipelineCache = VulkanPipelineStableCache::GetInstance(); + g_shaderCacheLoaderState.pipelineFileCount = pipelineCache.BeginLoading(cacheTitleId); + g_shaderCacheLoaderState.loadedPipelines = 0; + LatteShaderCache_ShowProgress(LatteShaderCache_updatePipelineLoadingProgress, true); + pipelineCache.EndLoading(); +} + +bool LatteShaderCache_updatePipelineLoadingProgress() +{ + uint32 pipelinesMissingShaders = 0; + return VulkanPipelineStableCache::GetInstance().UpdateLoading(g_shaderCacheLoaderState.loadedPipelines, pipelinesMissingShaders); +} + +uint64 LatteShaderCache_getShaderNameInTransferableCache(uint64 baseHash, uint32 shaderType) +{ + baseHash &= ~(7ULL << 61ULL); + baseHash |= ((uint64)shaderType << 61ULL); + return baseHash; +} + +void LatteShaderCache_writeSeparableVertexShader(uint64 shaderBaseHash, uint64 shaderAuxHash, uint8* fetchShader, uint32 fetchShaderSize, uint8* vertexShader, uint32 vertexShaderSize, uint32* contextRegisters, bool usesGeometryShader) +{ + if (!fc_shaderCacheGeneric) + return; + MemStreamWriter streamWriter(128 * 1024); + // header + streamWriter.writeBE<uint8>(1 | (SHADER_CACHE_TYPE_VERTEX << 4)); // version and type (shared field) + streamWriter.writeBE<uint64>(shaderBaseHash); + streamWriter.writeBE<uint64>(shaderAuxHash); + streamWriter.writeBE<uint8>(usesGeometryShader ? 1 : 0); + // register state + Latte::GPUCompactedRegisterState compactRegState; + Latte::StoreGPURegisterState(*(LatteContextRegister*)contextRegisters, compactRegState); + Latte::SerializeRegisterState(compactRegState, streamWriter); + // fetch shader + Latte::SerializeShaderProgram(fetchShader, fetchShaderSize, streamWriter); + // vertex shader + Latte::SerializeShaderProgram(vertexShader, vertexShaderSize, streamWriter); + // write to cache + uint64 shaderCacheName = LatteShaderCache_getShaderNameInTransferableCache(shaderBaseHash, SHADER_CACHE_TYPE_VERTEX); + std::span<uint8> dataBlob = streamWriter.getResult(); + fc_shaderCacheGeneric->AddFileAsync({ shaderCacheName, shaderAuxHash }, dataBlob.data(), dataBlob.size()); +} + +void LatteShaderCache_writeSeparableGeometryShader(uint64 shaderBaseHash, uint64 shaderAuxHash, uint8* geometryShader, uint32 geometryShaderSize, uint8* gsCopyShader, uint32 gsCopyShaderSize, uint32* contextRegisters, uint32* hleSpecialState, uint32 vsRingParameterCount) +{ + if (!fc_shaderCacheGeneric) + return; + MemStreamWriter streamWriter(128 * 1024); + // header + streamWriter.writeBE<uint8>(1 | (SHADER_CACHE_TYPE_GEOMETRY << 4)); // version and type (shared field) + streamWriter.writeBE<uint64>(shaderBaseHash); + streamWriter.writeBE<uint64>(shaderAuxHash); + cemu_assert_debug(vsRingParameterCount < 0x10000); + streamWriter.writeBE<uint16>(vsRingParameterCount); + // register state + Latte::GPUCompactedRegisterState compactRegState; + Latte::StoreGPURegisterState(*(LatteContextRegister*)contextRegisters, compactRegState); + Latte::SerializeRegisterState(compactRegState, streamWriter); + // geometry copy shader + Latte::SerializeShaderProgram(gsCopyShader, gsCopyShaderSize, streamWriter); + // geometry shader + Latte::SerializeShaderProgram(geometryShader, geometryShaderSize, streamWriter); + // write to cache + uint64 shaderCacheName = LatteShaderCache_getShaderNameInTransferableCache(shaderBaseHash, SHADER_CACHE_TYPE_GEOMETRY); + std::span<uint8> dataBlob = streamWriter.getResult(); + fc_shaderCacheGeneric->AddFileAsync({ shaderCacheName, shaderAuxHash }, dataBlob.data(), dataBlob.size()); +} + +void LatteShaderCache_writeSeparablePixelShader(uint64 shaderBaseHash, uint64 shaderAuxHash, uint8* pixelShader, uint32 pixelShaderSize, uint32* contextRegisters, bool usesGeometryShader) +{ + if (!fc_shaderCacheGeneric) + return; + MemStreamWriter streamWriter(128 * 1024); + streamWriter.writeBE<uint8>(1 | (SHADER_CACHE_TYPE_PIXEL << 4)); // version and type (shared field) + streamWriter.writeBE<uint64>(shaderBaseHash); + streamWriter.writeBE<uint64>(shaderAuxHash); + streamWriter.writeBE<uint8>(usesGeometryShader ? 1 : 0); + // register state + Latte::GPUCompactedRegisterState compactRegState; + Latte::StoreGPURegisterState(*(LatteContextRegister*)contextRegisters, compactRegState); + Latte::SerializeRegisterState(compactRegState, streamWriter); + // pixel shader + Latte::SerializeShaderProgram(pixelShader, pixelShaderSize, streamWriter); + // write to cache + uint64 shaderCacheName = LatteShaderCache_getShaderNameInTransferableCache(shaderBaseHash, SHADER_CACHE_TYPE_PIXEL); + std::span<uint8> dataBlob = streamWriter.getResult(); + fc_shaderCacheGeneric->AddFileAsync({ shaderCacheName, shaderAuxHash }, dataBlob.data(), dataBlob.size()); +} + +void LatteShaderCache_loadOrCompileSeparableShader(LatteDecompilerShader* shader, uint64 shaderBaseHash, uint64 shaderAuxHash) +{ + RendererShader::ShaderType shaderType; + if (shader->shaderType == LatteConst::ShaderType::Vertex) + { + shaderType = RendererShader::ShaderType::kVertex; + shaderCacheScreenStats.vertexShaderCount++; + } + else if (shader->shaderType == LatteConst::ShaderType::Geometry) + { + shaderType = RendererShader::ShaderType::kGeometry; + shaderCacheScreenStats.geometryShaderCount++; + } + else if (shader->shaderType == LatteConst::ShaderType::Pixel) + { + shaderType = RendererShader::ShaderType::kFragment; + shaderCacheScreenStats.pixelShaderCount++; + } + // compile shader + shaderCacheScreenStats.compiledShaderCount++; + LatteShader_CreateRendererShader(shader, true); + if (shader->shader == nullptr) + return; + LatteShaderCache_addToCompileQueue(shader); +} + +bool LatteShaderCache_readSeparableVertexShader(MemStreamReader& streamReader, uint8 version) +{ + std::unique_ptr<LatteContextRegister> lcr(new LatteContextRegister()); + if (version != 1) + return false; + uint64 shaderBaseHash = streamReader.readBE<uint64>(); + uint64 shaderAuxHash = streamReader.readBE<uint64>(); + bool usesGeometryShader = streamReader.readBE<uint8>() != 0; + // context registers + Latte::GPUCompactedRegisterState regState; + if (!Latte::DeserializeRegisterState(regState, streamReader)) + return false; + Latte::LoadGPURegisterState(*lcr, regState); + if (streamReader.hasError()) + return false; + // fetch shader + std::vector<uint8> fetchShaderData; + if (!Latte::DeserializeShaderProgram(fetchShaderData, streamReader)) + return false; + if (streamReader.hasError()) + return false; + // vertex shader + std::vector<uint8> vertexShaderData; + if (!Latte::DeserializeShaderProgram(vertexShaderData, streamReader)) + return false; + if (streamReader.hasError() || !streamReader.isEndOfStream()) + return false; + // update PS inputs (influence VS shader outputs) + LatteShader_UpdatePSInputs(lcr->GetRawView()); + // get fetch shader + LatteFetchShader::CacheHash fsHash = LatteFetchShader::CalculateCacheHash((uint32*)fetchShaderData.data(), fetchShaderData.size()); + LatteFetchShader* fetchShader = LatteShaderRecompiler_createFetchShader(fsHash, lcr->GetRawView(), (uint32*)fetchShaderData.data(), fetchShaderData.size()); + // decompile vertex shader + LatteDecompilerOutput_t decompilerOutput{}; + LatteFetchShader* fetchShaderList[1]; + fetchShaderList[0] = fetchShader; + LatteDecompiler_DecompileVertexShader(shaderBaseHash, lcr->GetRawView(), vertexShaderData.data(), vertexShaderData.size(), fetchShaderList, 1, lcr->GetSpecialStateValues(), usesGeometryShader, &decompilerOutput); + LatteDecompilerShader* vertexShader = LatteShader_CreateShaderFromDecompilerOutput(decompilerOutput, shaderBaseHash, false, shaderAuxHash, lcr->GetRawView()); + // compile + LatteShader_DumpShader(shaderBaseHash, shaderAuxHash, vertexShader); + LatteShader_DumpRawShader(shaderBaseHash, shaderAuxHash, SHADER_DUMP_TYPE_VERTEX, vertexShaderData.data(), vertexShaderData.size()); + LatteShaderCache_loadOrCompileSeparableShader(vertexShader, shaderBaseHash, shaderAuxHash); + catchOpenGLError(); + LatteSHRC_RegisterShader(vertexShader, shaderBaseHash, shaderAuxHash); + return true; +} + +bool LatteShaderCache_readSeparableGeometryShader(MemStreamReader& streamReader, uint8 version) +{ + if (version != 1) + return false; + std::unique_ptr<LatteContextRegister> lcr(new LatteContextRegister()); + uint64 shaderBaseHash = streamReader.readBE<uint64>(); + uint64 shaderAuxHash = streamReader.readBE<uint64>(); + uint32 vsRingParameterCount = streamReader.readBE<uint16>(); + // context registers + Latte::GPUCompactedRegisterState regState; + if (!Latte::DeserializeRegisterState(regState, streamReader)) + return false; + Latte::LoadGPURegisterState(*lcr, regState); + if (streamReader.hasError()) + return false; + // geometry copy shader + std::vector<uint8> geometryCopyShaderData; + if (!Latte::DeserializeShaderProgram(geometryCopyShaderData, streamReader)) + return false; + // geometry shader + std::vector<uint8> geometryShaderData; + if (!Latte::DeserializeShaderProgram(geometryShaderData, streamReader)) + return false; + if (streamReader.hasError() || !streamReader.isEndOfStream()) + return false; + // update PS inputs + LatteShader_UpdatePSInputs(lcr->GetRawView()); + // decompile geometry shader + LatteDecompilerOutput_t decompilerOutput{}; + LatteDecompiler_DecompileGeometryShader(shaderBaseHash, lcr->GetRawView(), geometryShaderData.data(), geometryShaderData.size(), geometryCopyShaderData.data(), geometryCopyShaderData.size(), lcr->GetSpecialStateValues(), vsRingParameterCount, &decompilerOutput); + LatteDecompilerShader* geometryShader = LatteShader_CreateShaderFromDecompilerOutput(decompilerOutput, shaderBaseHash, false, shaderAuxHash, lcr->GetRawView()); + // compile + LatteShader_DumpShader(shaderBaseHash, shaderAuxHash, geometryShader); + LatteShader_DumpRawShader(shaderBaseHash, shaderAuxHash, SHADER_DUMP_TYPE_GEOMETRY, geometryShaderData.data(), geometryShaderData.size()); + LatteShaderCache_loadOrCompileSeparableShader(geometryShader, shaderBaseHash, shaderAuxHash); + catchOpenGLError(); + LatteSHRC_RegisterShader(geometryShader, shaderBaseHash, shaderAuxHash); + return true; +} + +bool LatteShaderCache_readSeparablePixelShader(MemStreamReader& streamReader, uint8 version) +{ + if (version != 1) + return false; + std::unique_ptr<LatteContextRegister> lcr(new LatteContextRegister()); + uint64 shaderBaseHash = streamReader.readBE<uint64>(); + uint64 shaderAuxHash = streamReader.readBE<uint64>(); + bool usesGeometryShader = streamReader.readBE<uint8>() != 0; + // context registers + Latte::GPUCompactedRegisterState regState; + if (!Latte::DeserializeRegisterState(regState, streamReader)) + return false; + Latte::LoadGPURegisterState(*lcr, regState); + if (streamReader.hasError()) + return false; + // pixel shader + std::vector<uint8> pixelShaderData; + if (!Latte::DeserializeShaderProgram(pixelShaderData, streamReader)) + return false; + if (streamReader.hasError() || !streamReader.isEndOfStream()) + return false; + // update PS inputs + LatteShader_UpdatePSInputs(lcr->GetRawView()); + // decompile pixel shader + LatteDecompilerOutput_t decompilerOutput{}; + LatteDecompiler_DecompilePixelShader(shaderBaseHash, lcr->GetRawView(), pixelShaderData.data(), pixelShaderData.size(), lcr->GetSpecialStateValues(), usesGeometryShader, &decompilerOutput); + LatteDecompilerShader* pixelShader = LatteShader_CreateShaderFromDecompilerOutput(decompilerOutput, shaderBaseHash, false, shaderAuxHash, lcr->GetRawView()); + // compile + LatteShader_DumpShader(shaderBaseHash, shaderAuxHash, pixelShader); + LatteShader_DumpRawShader(shaderBaseHash, shaderAuxHash, SHADER_DUMP_TYPE_PIXEL, pixelShaderData.data(), pixelShaderData.size()); + LatteShaderCache_loadOrCompileSeparableShader(pixelShader, shaderBaseHash, shaderAuxHash); + catchOpenGLError(); + LatteSHRC_RegisterShader(pixelShader, shaderBaseHash, shaderAuxHash); + return true; +} + +// read shader info from shader cache +bool LatteShaderCache_readSeparableShader(uint8* shaderInfoData, sint32 shaderInfoSize) +{ + if (shaderInfoSize < 8) + return false; + MemStreamReader streamReader(shaderInfoData, shaderInfoSize); + uint8 versionAndType = streamReader.readBE<uint8>(); + uint8 version = versionAndType & 0xF; + uint8 type = (versionAndType >> 4) & 0xF; + if (type == SHADER_CACHE_TYPE_VERTEX) + return LatteShaderCache_readSeparableVertexShader(streamReader, version); + else if (type == SHADER_CACHE_TYPE_GEOMETRY) + return LatteShaderCache_readSeparableGeometryShader(streamReader, version); + else if (type == SHADER_CACHE_TYPE_PIXEL) + return LatteShaderCache_readSeparablePixelShader(streamReader, version); + return false; +} + +#include <wx/msgdlg.h> + +void LatteShaderCache_handleDeprecatedCacheFiles(fs::path pathGeneric, fs::path pathGenericPre1_25_0, fs::path pathGenericPre1_16_0) +{ + std::error_code ec; + + bool hasOldCacheFiles = fs::exists(pathGenericPre1_25_0, ec) || fs::exists(pathGenericPre1_16_0, ec); + bool hasNewCacheFiles = fs::exists(pathGeneric, ec); + + if (hasOldCacheFiles && !hasNewCacheFiles) + { + // ask user if they want to delete or keep the old cache file + const auto infoMsg = L"Outdated shader cache\n\nCemu detected that the shader cache for this game is outdated\nOnly shader caches generated with Cemu 1.25.0 or above are supported\n\n" + "We recommend deleting the outdated cache file as it will no longer be used by Cemu"; + + wxMessageDialog dialog(nullptr, _(infoMsg), _("Outdated shader cache"), + wxYES_NO | wxCENTRE | wxICON_EXCLAMATION); + + dialog.SetYesNoLabels(_("Delete outdated cache file [recommended]"), _("Keep outdated cache file")); + const auto result = dialog.ShowModal(); + if (result == wxID_YES) + { + fs::remove(pathGenericPre1_16_0, ec); + fs::remove(pathGenericPre1_25_0, ec); + } + } +} \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Core/LatteShaderCache.h b/src/Cafe/HW/Latte/Core/LatteShaderCache.h new file mode 100644 index 00000000..f065b2b8 --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteShaderCache.h @@ -0,0 +1,4 @@ +#pragma once + +uint32 LatteShaderCache_getShaderCacheExtraVersion(uint64 titleId); +uint32 LatteShaderCache_getPipelineCacheExtraVersion(uint64 titleId); \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Core/LatteShaderGL.cpp b/src/Cafe/HW/Latte/Core/LatteShaderGL.cpp new file mode 100644 index 00000000..e709e46d --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteShaderGL.cpp @@ -0,0 +1,168 @@ +#include "Common/GLInclude/GLInclude.h" +#include "Cafe/HW/Latte/Core/Latte.h" +#include "Cafe/HW/Latte/Core/LatteDraw.h" +#include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h" +#include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h" +#include "Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.h" +#include "util/helpers/StringBuf.h" + +bool gxShader_checkIfSuccessfullyLinked(GLuint glProgram) +{ + int status = -1; + glGetProgramiv(glProgram, GL_LINK_STATUS, &status); + if( status == GL_TRUE ) + return true; + // in debug mode, get and print shader error log + char infoLog[48*1024]; + uint32 infoLogLength, tempLength; + glGetProgramiv(glProgram, GL_INFO_LOG_LENGTH, (GLint *)&infoLogLength); + tempLength = sizeof(infoLog)-1; + glGetProgramInfoLog(glProgram, std::min(tempLength, infoLogLength), (GLsizei*)&tempLength, (GLcharARB*)infoLog); + infoLog[tempLength] = '\0'; + forceLog_printf("Link error in raw shader"); + cafeLog_logLine(LOG_TYPE_FORCE, infoLog); + return false; +} + +void LatteShader_prepareSeparableUniforms(LatteDecompilerShader* shader) +{ + if (g_renderer->GetType() == RendererAPI::Vulkan) + return; + + auto shaderGL = (RendererShaderGL*)shader->shader; + // setup uniform info + if (shader->shaderType == LatteConst::ShaderType::Vertex) + { + shader->uniform.loc_remapped = glGetUniformLocation(shaderGL->GetProgram(), "uf_remappedVS"); + shader->uniform.loc_uniformRegister = glGetUniformLocation(shaderGL->GetProgram(), "uf_uniformRegisterVS"); + } + else if (shader->shaderType == LatteConst::ShaderType::Geometry) + { + shader->uniform.loc_remapped = glGetUniformLocation(shaderGL->GetProgram(), "uf_remappedGS"); + shader->uniform.loc_uniformRegister = glGetUniformLocation(shaderGL->GetProgram(), "uf_uniformRegisterGS"); + } + else if (shader->shaderType == LatteConst::ShaderType::Pixel) + { + shader->uniform.loc_remapped = glGetUniformLocation(shaderGL->GetProgram(), "uf_remappedPS"); + shader->uniform.loc_uniformRegister = glGetUniformLocation(shaderGL->GetProgram(), "uf_uniformRegisterPS"); + } + catchOpenGLError(); + shader->uniform.loc_windowSpaceToClipSpaceTransform = glGetUniformLocation(shaderGL->GetProgram(), "uf_windowSpaceToClipSpaceTransform"); + shader->uniform.loc_alphaTestRef = glGetUniformLocation(shaderGL->GetProgram(), "uf_alphaTestRef"); + shader->uniform.loc_pointSize = glGetUniformLocation(shaderGL->GetProgram(), "uf_pointSize"); + shader->uniform.loc_fragCoordScale = glGetUniformLocation(shaderGL->GetProgram(), "uf_fragCoordScale"); + cemu_assert_debug(shader->uniform.list_ufTexRescale.empty()); + for (sint32 t = 0; t < LATTE_NUM_MAX_TEX_UNITS; t++) + { + char ufName[64]; + sprintf(ufName, "uf_tex%dScale", t); + GLint uniformLocation = glGetUniformLocation(shaderGL->GetProgram(), ufName); + if (uniformLocation >= 0) + { + LatteUniformTextureScaleEntry_t entry = { 0 }; + entry.texUnit = t; + entry.uniformLocation = uniformLocation; + shader->uniform.list_ufTexRescale.push_back(entry); + } + } +} +GLuint gpu7ShaderGLDepr_compileShader(const std::string& source, uint32_t type) +{ + cemu_assert(type == GL_VERTEX_SHADER || type == GL_FRAGMENT_SHADER); + const GLuint shader_object = glCreateShader(type); + + const char *c_str = source.c_str(); + const GLint size = (GLint)source.size(); + glShaderSource(shader_object, 1, &c_str, &size); + glCompileShader(shader_object); + + GLint log_length; + glGetShaderiv(shader_object, GL_INFO_LOG_LENGTH, &log_length); + if (log_length > 0) + { + char log[2048]{}; + GLsizei log_size; + glGetShaderInfoLog(shader_object, std::min(log_length, (GLint)sizeof(log) - 1), &log_size, log); + forceLog_printf("Error/Warning in vertex shader:"); + forceLog_printf("%s", log); + } + + return shader_object; +} +GLuint gpu7ShaderGLDepr_compileVertexShader(const std::string& source) +{ + return gpu7ShaderGLDepr_compileShader(source, GL_VERTEX_SHADER); +} + +GLuint gpu7ShaderGLDepr_compileFragmentShader(const std::string& source) +{ + return gpu7ShaderGLDepr_compileShader(source, GL_FRAGMENT_SHADER); +} + +GLuint gpu7ShaderGLDepr_compileVertexShader(const char* shaderSource, sint32 shaderSourceLength) +{ + uint32 shaderObject = glCreateShader(GL_VERTEX_SHADER); + GLchar* srcPtr = (GLchar*)shaderSource; + GLint srcLen = shaderSourceLength; + glShaderSource(shaderObject, 1, &srcPtr, &srcLen); + glCompileShader(shaderObject); + uint32 shaderLogLengthInfo, shaderLogLen; + glGetShaderiv(shaderObject, GL_INFO_LOG_LENGTH, (GLint *)&shaderLogLengthInfo); + if (shaderLogLengthInfo > 0) + { + char messageLog[2048]{}; + glGetShaderInfoLog(shaderObject, std::min<uint32>(shaderLogLengthInfo, sizeof(messageLog) - 1), (GLsizei*)&shaderLogLen, (GLcharARB*)messageLog); + forceLog_printf("Error/Warning in vertex shader:"); + forceLog_printf("%s", messageLog); + } + return shaderObject; +} + +GLuint gpu7ShaderGLDepr_compileFragmentShader(const char* shaderSource, sint32 shaderSourceLength) +{ + uint32 shaderObject = glCreateShader(GL_FRAGMENT_SHADER); + GLchar* srcPtr = (GLchar*)shaderSource; + GLint srcLen = shaderSourceLength; + glShaderSource(shaderObject, 1, &srcPtr, &srcLen); + glCompileShader(shaderObject); + uint32 shaderLogLengthInfo, shaderLogLen; + char messageLog[2048]; + glGetShaderiv(shaderObject, GL_INFO_LOG_LENGTH, (GLint *)&shaderLogLengthInfo); + if (shaderLogLengthInfo > 0) + { + memset(messageLog, 0, sizeof(messageLog)); + glGetShaderInfoLog(shaderObject, std::min<uint32>(shaderLogLengthInfo, sizeof(messageLog) - 1), (GLsizei*)&shaderLogLen, (GLcharARB*)messageLog); + forceLog_printf("Error/Warning in fragment shader:"); + forceLog_printf("%s", messageLog); + } + return shaderObject; +} + +GLuint gxShaderDepr_compileRaw(StringBuf* strSourceVS, StringBuf* strSourceFS) +{ + GLuint glShaderProgram = glCreateProgram(); + GLuint vertexShader = gpu7ShaderGLDepr_compileVertexShader(strSourceVS->c_str(), strSourceVS->getLen()); + glAttachShader(glShaderProgram, vertexShader); + GLuint fragmentShader = gpu7ShaderGLDepr_compileFragmentShader(strSourceFS->c_str(), strSourceFS->getLen()); + glAttachShader(glShaderProgram, fragmentShader); + glLinkProgram(glShaderProgram); + if( gxShader_checkIfSuccessfullyLinked(glShaderProgram) == false ) + { + return 0; + } + return glShaderProgram; +} + +GLuint gxShaderDepr_compileRaw(const std::string& vertex_source, const std::string& fragment_source) +{ + const GLuint programm = glCreateProgram(); + + auto vertex_shader = std::async(std::launch::deferred, gpu7ShaderGLDepr_compileShader, vertex_source, GL_VERTEX_SHADER); + auto fragment_shader = std::async(std::launch::deferred, gpu7ShaderGLDepr_compileShader, fragment_source, GL_FRAGMENT_SHADER); + + glAttachShader(programm, vertex_shader.get()); + glAttachShader(programm, fragment_shader.get()); + + glLinkProgram(programm); + return gxShader_checkIfSuccessfullyLinked(programm) ? programm : 0; +} diff --git a/src/Cafe/HW/Latte/Core/LatteSoftware.cpp b/src/Cafe/HW/Latte/Core/LatteSoftware.cpp new file mode 100644 index 00000000..8e5ec29b --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteSoftware.cpp @@ -0,0 +1,890 @@ +#include "Cafe/HW/Latte/ISA/RegDefines.h" +#include "Cafe/HW/Latte/Core/Latte.h" +#include "Cafe/HW/Latte/Core/LatteConst.h" +#include "Cafe/HW/Latte/Core/LatteShaderAssembly.h" +#include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInstructions.h" +#include "Cafe/HW/Latte/Core/FetchShader.h" + +#include "Cafe/HW/Latte/Core/LattePM4.h" + +#define GPU7_ENDIAN_8IN32 2 + +typedef struct +{ + union + { + float f[4]; + uint32 u32[4]; + sint32 s32[4]; + }; +}LatteReg_t; + +#define REG_AR (128) + +typedef struct +{ + LatteReg_t reg[128+1]; + union + { + uint32 u32[5]; + sint32 s32[5]; + float f[5]; + }pvps; + union + { + uint32 u32[5]; + sint32 s32[5]; + float f[5]; + }pvpsUpdate; + void* cfilePtr; + LatteReg_t* literalPtr; + // cbank + LatteReg_t* cbank0; + LatteReg_t* cbank1; + // vertex shader exports + LatteReg_t export_pos; + + uint32* shaderBase; + sint32 shaderSize; + // shaders + LatteFetchShader* fetchShader; +}LatteSoftwareExecContext_t; + +LatteSoftwareExecContext_t LatteSWCtx; + +char _tempStringBuf[2048]; +sint32 tempStringIndex = 0; + +char* getTempString() +{ + tempStringIndex = (tempStringIndex + 1) % 10; + return _tempStringBuf + tempStringIndex * (sizeof(_tempStringBuf) / 10); +} + +const char* _getSrcName(uint32 srcSel, uint32 srcChan) +{ + if (GPU7_ALU_SRC_IS_GPR(srcSel)) + { + char* tempStr = getTempString(); + sprintf(tempStr, "R%d", srcSel & 0x7F); + if (srcChan == 0) + strcat(tempStr, ".x"); + else if (srcChan == 1) + strcat(tempStr, ".y"); + else if (srcChan == 2) + strcat(tempStr, ".z"); + else + strcat(tempStr, ".w"); + return tempStr; + } + else if (GPU7_ALU_SRC_IS_CFILE(srcSel)) + { + return "CFILE"; + } + else if (GPU7_ALU_SRC_IS_PV(srcSel)) + { + return "PV"; + } + else if (GPU7_ALU_SRC_IS_PS(srcSel)) + { + return "PS"; + } + else if (GPU7_ALU_SRC_IS_CBANK0(srcSel)) + { + return "CBANK0"; + } + else if (GPU7_ALU_SRC_IS_CBANK1(srcSel)) + { + return "CBANK1"; + } + else if (GPU7_ALU_SRC_IS_CONST_0F(srcSel)) + { + return "0.0"; + } + else if (GPU7_ALU_SRC_IS_CONST_1F(srcSel)) + { + return "1.0"; + } + else if (GPU7_ALU_SRC_IS_CONST_0_5F(srcSel)) + { + return "0.5"; + } + else if (GPU7_ALU_SRC_IS_LITERAL(srcSel)) + { + return "LITERAL"; + } + else + { + cemu_assert_unimplemented(); + } + return "UKN"; +} + +sint32 _getSrc_genericS32(uint32 srcSel, uint32 srcChan, uint32 srcRel, uint32 indexMode) +{ + sint32 v = 0; + if (GPU7_ALU_SRC_IS_GPR(srcSel)) + { + cemu_assert_debug(srcRel == 0); + v = LatteSWCtx.reg[GPU7_ALU_SRC_GET_GPR_INDEX(srcSel)].s32[srcChan]; + } + else if (GPU7_ALU_SRC_IS_CFILE(srcSel)) + { + if (srcRel) + { + if (indexMode <= GPU7_INDEX_AR_W) + { + v = ((sint32*)LatteSWCtx.cfilePtr)[LatteSWCtx.reg[REG_AR].s32[indexMode] * 4 + GPU7_ALU_SRC_GET_CFILE_INDEX(srcSel) * 4 + srcChan]; + } + else + cemu_assert_debug(false); + } + else + { + v = ((sint32*)LatteSWCtx.cfilePtr)[GPU7_ALU_SRC_GET_CFILE_INDEX(srcSel) * 4 + srcChan]; + } + } + else if (GPU7_ALU_SRC_IS_PV(srcSel)) + { + cemu_assert_debug(srcRel == 0); + v = LatteSWCtx.pvps.s32[srcChan]; + } + else if (GPU7_ALU_SRC_IS_PS(srcSel)) + { + cemu_assert_debug(srcRel == 0); + v = LatteSWCtx.pvps.s32[4]; + } + else if (GPU7_ALU_SRC_IS_CBANK0(srcSel)) + { + if (srcRel) + { + if (indexMode <= GPU7_INDEX_AR_W) + { + v = LatteSWCtx.cbank0[LatteSWCtx.reg[REG_AR].s32[indexMode] + GPU7_ALU_SRC_GET_CBANK0_INDEX(srcSel)].s32[srcChan]; + } + else + assert_dbg(); + } + else + { + v = LatteSWCtx.cbank0[GPU7_ALU_SRC_GET_CBANK0_INDEX(srcSel)].s32[srcChan]; + } + } + else if (GPU7_ALU_SRC_IS_CBANK1(srcSel)) + { + if (srcRel) + { + if (indexMode <= GPU7_INDEX_AR_W) + { + v = LatteSWCtx.cbank1[LatteSWCtx.reg[REG_AR].s32[indexMode] + GPU7_ALU_SRC_GET_CBANK1_INDEX(srcSel)].s32[srcChan]; + } + else + assert_dbg(); + } + else + { + v = LatteSWCtx.cbank1[GPU7_ALU_SRC_GET_CBANK1_INDEX(srcSel)].s32[srcChan]; + } + } + else if (GPU7_ALU_SRC_IS_CONST_0F(srcSel)) + { + cemu_assert_debug(srcRel == 0); + v = 0; // 0.0f + } + else if (GPU7_ALU_SRC_IS_CONST_1F(srcSel)) + { + cemu_assert_debug(srcRel == 0); + v = 0x3f800000; // 1.0f + } + else if (GPU7_ALU_SRC_IS_CONST_0_5F(srcSel)) + { + cemu_assert_debug(srcRel == 0); + v = 0x3f000000; // 0.5f + } + else if (GPU7_ALU_SRC_IS_LITERAL(srcSel)) + { + v = LatteSWCtx.literalPtr->s32[srcChan]; + } + else + assert_dbg(); + return v; +} + +sint32 _getSrc_s32(uint32 srcSel, uint32 srcChan, uint32 srcNeg, uint32 srcAbs, uint32 srcRel, uint32 indexMode) +{ + sint32 v = _getSrc_genericS32(srcSel, srcChan, srcRel, indexMode); + cemu_assert_debug(srcNeg == 0); + cemu_assert_debug(srcAbs == 0); + return v; +} + +float _getSrc_f(uint32 srcSel, uint32 srcChan, uint32 srcNeg, uint32 srcAbs, uint32 srcRel, uint32 indexMode) +{ + float v = 0; + *(sint32*)&v = _getSrc_genericS32(srcSel, srcChan, srcRel, indexMode); + if (srcAbs) // todo - how does this interact with srcNeg + v = fabs(v); + if (srcNeg) + v = -v; + return v; +} + +#define _updateGPR_S32(__gprIdx,__channel,__v) {gprUpdate[updateQueueLength].gprIndex = __gprIdx; gprUpdate[updateQueueLength].channel = __channel; gprUpdate[updateQueueLength].s32 = __v; updateQueueLength++;} +#define _updateGPR_F(__gprIdx,__channel,__v) {gprUpdate[updateQueueLength].gprIndex = __gprIdx; gprUpdate[updateQueueLength].channel = __channel; gprUpdate[updateQueueLength].f = __v; updateQueueLength++;} + +#define _updatePVPS_S32(__pvIndex, __v) {LatteSWCtx.pvpsUpdate.s32[__pvIndex] = __v;} +#define _updatePVPS_F(__pvIndex, __v) {LatteSWCtx.pvpsUpdate.f[__pvIndex] = __v;} + +float LatteSoftware_omod(uint32 omod, float f) +{ + switch (omod) + { + case ALU_OMOD_NONE: + return f; + case ALU_OMOD_MUL2: + return f * 2.0f; + case ALU_OMOD_MUL4: + return f * 4.0f; + case ALU_OMOD_DIV2: + return f / 2.0f; + } + cemu_assert_debug(false); + return 0.0f; +} + +#ifndef PUBLIC_RELEASE +#define _clamp(__v) if(destClamp != 0) cemu_assert_unimplemented() +#else +#define _clamp(__v) // todo +#endif + +#define _omod(__v) __v = LatteSoftware_omod(omod, __v) + +bool LatteDecompiler_IsALUTransInstruction(bool isOP3, uint32 opcode); + +void LatteSoftware_setupCBankPointers(uint32 cBank0Index, uint32 cBank1Index, uint32 cBank0AddrBase, uint32 cBank1AddrBase) +{ + MPTR cBank0Ptr = LatteGPUState.contextRegister[mmSQ_VTX_UNIFORM_BLOCK_START + cBank0Index * 7 + 0]; + MPTR cBank1Ptr = LatteGPUState.contextRegister[mmSQ_VTX_UNIFORM_BLOCK_START + cBank1Index * 7 + 0]; + LatteSWCtx.cbank0 = (LatteReg_t*)memory_getPointerFromPhysicalOffset(cBank0Ptr + cBank0AddrBase); + LatteSWCtx.cbank1 = (LatteReg_t*)memory_getPointerFromPhysicalOffset(cBank1Ptr + cBank1AddrBase); +} + +void LatteSoftware_executeALUClause(uint32 cfType, uint32 addr, uint32 count, uint32 cBank0Index, uint32 cBank1Index, uint32 cBank0AddrBase, uint32 cBank1AddrBase) +{ + cemu_assert_debug(cfType == GPU7_CF_INST_ALU); // todo - handle other ALU clauses + uint32* aluWordPtr = LatteSWCtx.shaderBase + addr * 2; + uint32* aluWordPtrEnd = aluWordPtr + count * 2; + + LatteSoftware_setupCBankPointers(cBank0Index, cBank1Index, cBank0AddrBase, cBank1AddrBase); + + struct + { + sint16 gprIndex; + sint16 channel; + union + { + float f; + uint32 u32; + sint32 s32; + }; + }gprUpdate[16]; + + sint32 updateQueueLength = 0; + uint32 aluUnitWriteMask = 0; + uint8 literalAccessMask = 0; + while (aluWordPtr < aluWordPtrEnd) + { + // calculate number of instructions in group + sint32 groupInstructionCount; + for (sint32 i = 0; i < 6; i++) + { + if (aluWordPtr[i * 2] & 0x80000000) + { + groupInstructionCount = i + 1; + break; + } + cemu_assert_debug(i < 5); + } + LatteSWCtx.literalPtr = (LatteReg_t*)(aluWordPtr + groupInstructionCount*2); + // process group + bool hasReductionInstruction = false; + float reductionResult = 0.0f; + for (sint32 s = 0; s < groupInstructionCount; s++) + { + uint32 aluWord0 = aluWordPtr[0]; + uint32 aluWord1 = aluWordPtr[1]; + aluWordPtr += 2; + uint32 alu_inst13_5 = (aluWord1 >> 13) & 0x1F; + // parameters from ALU word 0 (shared for ALU OP2 and OP3) + uint32 src0Sel = (aluWord0 >> 0) & 0x1FF; // source selection + uint32 src1Sel = (aluWord0 >> 13) & 0x1FF; + uint32 src0Rel = (aluWord0 >> 9) & 0x1; // relative addressing mode + uint32 src1Rel = (aluWord0 >> 22) & 0x1; + uint32 src0Chan = (aluWord0 >> 10) & 0x3; // component selection x/y/z/w + uint32 src1Chan = (aluWord0 >> 23) & 0x3; + uint32 src0Neg = (aluWord0 >> 12) & 0x1; // negate input + uint32 src1Neg = (aluWord0 >> 25) & 0x1; + uint32 indexMode = (aluWord0 >> 26) & 7; + uint32 predSel = (aluWord0 >> 29) & 3; + if (GPU7_ALU_SRC_IS_LITERAL(src0Sel)) + literalAccessMask |= (1 << src0Chan); + if (GPU7_ALU_SRC_IS_LITERAL(src1Sel)) + literalAccessMask |= (1 << src1Chan); + + if (alu_inst13_5 >= 0x8) + { + // op3 + // parameters from ALU word 1 + uint32 src2Sel = (aluWord1 >> 0) & 0x1FF; // source selection + uint32 src2Rel = (aluWord1 >> 9) & 0x1; // relative addressing mode + uint32 src2Chan = (aluWord1 >> 10) & 0x3; // component selection x/y/z/w + uint32 src2Neg = (aluWord1 >> 12) & 0x1; // negate input + if (GPU7_ALU_SRC_IS_LITERAL(src2Sel)) + literalAccessMask |= (1 << src2Chan); + + uint32 destGpr = (aluWord1 >> 21) & 0x7F; + uint32 destRel = (aluWord1 >> 28) & 1; + uint32 destElem = (aluWord1 >> 29) & 3; + uint32 destClamp = (aluWord1 >> 31) & 1; + + uint32 aluUnit = destElem; + if (aluUnitWriteMask&(1 << destElem)) + { + aluUnit = 4; // PV + } + else + aluUnitWriteMask |= (1 << destElem); + + switch (alu_inst13_5) + { + case ALU_OP3_INST_CMOVE: + { + float f = _getSrc_f(src0Sel, src0Chan, src0Neg, 0, src0Rel, indexMode); + sint32 result; + if (f == 0.0f) + result = _getSrc_s32(src1Sel, src1Chan, src1Neg, 0, src1Rel, indexMode); + else + result = _getSrc_s32(src2Sel, src2Chan, src2Neg, 0, src2Rel, indexMode); + cemu_assert_debug(destClamp == 0); + _updateGPR_S32(destGpr, destElem, result); + _updatePVPS_S32(aluUnit, result); + break; + } + case ALU_OP3_INST_CMOVGT: + { + float f = _getSrc_f(src0Sel, src0Chan, src0Neg, 0, src0Rel, indexMode); + sint32 result; + if (f > 0.0f) + result = _getSrc_s32(src1Sel, src1Chan, src1Neg, 0, src1Rel, indexMode); + else + result = _getSrc_s32(src2Sel, src2Chan, src2Neg, 0, src2Rel, indexMode); + cemu_assert_debug(destClamp == 0); + _updateGPR_S32(destGpr, destElem, result); + _updatePVPS_S32(aluUnit, result); + break; + } + case ALU_OP3_INST_MULADD: + { + float f0 = _getSrc_f(src0Sel, src0Chan, src0Neg, 0, src0Rel, indexMode); + float f1 = _getSrc_f(src1Sel, src1Chan, src1Neg, 0, src1Rel, indexMode); + float f2 = _getSrc_f(src2Sel, src2Chan, src2Neg, 0, src2Rel, indexMode); + float f = f0*f1+f2; + _updateGPR_F(destGpr, destElem, f); + _updatePVPS_F(aluUnit, f); + break; + } + default: + cemu_assert_debug(false); + } + } + else + { + uint32 alu_inst7_11 = (aluWord1 >> 7) & 0x7FF; + + uint32 src0Abs = (aluWord1 >> 0) & 1; + uint32 src1Abs = (aluWord1 >> 1) & 1; + uint32 updateExecuteMask = (aluWord1 >> 2) & 1; + uint32 updatePredicate = (aluWord1 >> 3) & 1; + uint32 writeMask = (aluWord1 >> 4) & 1; + uint32 omod = (aluWord1 >> 5) & 3; + + uint32 destGpr = (aluWord1 >> 21) & 0x7F; + uint32 destRel = (aluWord1 >> 28) & 1; + uint32 destElem = (aluWord1 >> 29) & 3; + uint32 destClamp = (aluWord1 >> 31) & 1; + + uint32 aluUnit = destElem; + if (LatteDecompiler_IsALUTransInstruction(false, alu_inst7_11)) + aluUnit = 4; + + if (aluUnitWriteMask&(1 << destElem)) + { + aluUnit = 4; // PV + } + else + aluUnitWriteMask |= (1 << destElem); + + switch (alu_inst7_11) + { + case ALU_OP2_INST_NOP: + { + break; + } + case ALU_OP2_INST_MOV: + { + if (src0Neg || src0Abs || omod != 0) + { + float v = _getSrc_f(src0Sel, src0Chan, src0Neg, src0Abs, src0Rel, indexMode); + _omod(v); + _clamp(f); + if (writeMask) + _updateGPR_F(destGpr, destElem, v); + _updatePVPS_F(aluUnit, v); + } + else + { + sint32 v = _getSrc_s32(src0Sel, src0Chan, src0Neg, src0Abs, src0Rel, indexMode); + // todo - omod/clamp for float moves + if (writeMask) + _updateGPR_S32(destGpr, destElem, v); + _updatePVPS_S32(aluUnit, v); + } + break; + } + case ALU_OP2_INST_ADD: + { + float f0 = _getSrc_f(src0Sel, src0Chan, src0Neg, src0Abs, src0Rel, indexMode); + float f1 = _getSrc_f(src1Sel, src1Chan, src1Neg, src1Abs, src1Rel, indexMode); + float f = f0 + f1; + _omod(f); + _clamp(f); + if (writeMask) + _updateGPR_F(destGpr, destElem, f); + _updatePVPS_F(aluUnit, f); + break; + } + case ALU_OP2_INST_MUL: + case ALU_OP2_INST_MUL_IEEE: + { + float f0 = _getSrc_f(src0Sel, src0Chan, src0Neg, src0Abs, src0Rel, indexMode); + float f1 = _getSrc_f(src1Sel, src1Chan, src1Neg, src1Abs, src1Rel, indexMode); + float f = f0 * f1; + if (f0 == 0.0f || f1 == 0.0f) + f = 0.0f; + _omod(f); + _clamp(f); + if (writeMask) + _updateGPR_F(destGpr, destElem, f); + _updatePVPS_F(aluUnit, f); + break; + } + case ALU_OP2_INST_TRUNC: + { + float f0 = _getSrc_f(src0Sel, src0Chan, src0Neg, src0Abs, src0Rel, indexMode); + float f = truncf(f0); + _omod(f); + _clamp(f); + if (writeMask) + _updateGPR_F(destGpr, destElem, f); + _updatePVPS_F(aluUnit, f); + break; + } + case ALU_OP2_INST_RECIP_IEEE: + { + float f0 = _getSrc_f(src0Sel, src0Chan, src0Neg, src0Abs, src0Rel, indexMode); + float f = 1.0f / f0; + _omod(f); + _clamp(f); + if (writeMask) + _updateGPR_F(destGpr, destElem, f); + _updatePVPS_F(aluUnit, f); + break; + } + case ALU_OP2_INST_SETGT: + { + float f0 = _getSrc_f(src0Sel, src0Chan, src0Neg, src0Abs, src0Rel, indexMode); + float f1 = _getSrc_f(src1Sel, src1Chan, src1Neg, src1Abs, src1Rel, indexMode); + float f = (f0 > f1) ? 1.0f : 0.0f; + _omod(f); + _clamp(f); + if (writeMask) + _updateGPR_F(destGpr, destElem, f); + _updatePVPS_F(aluUnit, f); + break; + } + case ALU_OP2_INST_SETGE: + { + float f0 = _getSrc_f(src0Sel, src0Chan, src0Neg, src0Abs, src0Rel, indexMode); + float f1 = _getSrc_f(src1Sel, src1Chan, src1Neg, src1Abs, src1Rel, indexMode); + float f = (f0 >= f1) ? 1.0f : 0.0f; + _omod(f); + _clamp(f); + if (writeMask) + _updateGPR_F(destGpr, destElem, f); + _updatePVPS_F(aluUnit, f); + break; + } + case ALU_OP2_INST_INT_TO_FLOAT: + { + sint32 v = _getSrc_s32(src0Sel, src0Chan, src0Neg, src0Abs, src0Rel, indexMode); + float f = (float)v; + _omod(f); + _clamp(f); + if (writeMask) + _updateGPR_F(destGpr, destElem, f); + _updatePVPS_F(aluUnit, f); + break; + } + case ALU_OP2_INST_FLT_TO_INT: + { + float v = _getSrc_f(src0Sel, src0Chan, src0Neg, src0Abs, src0Rel, indexMode); + sint32 f = (sint32)v; + if (writeMask) + _updateGPR_S32(destGpr, destElem, f); + _updatePVPS_S32(aluUnit, f); + break; + } + case ALU_OP2_INST_MOVA_FLOOR: + { + float f = _getSrc_f(src0Sel, src0Chan, src0Neg, src0Abs, src0Rel, indexMode); + f = floor(f); + f = std::min(std::max(f, -256.0f), 255.0f); + // omod, clamp? + _updateGPR_S32(REG_AR, destElem, (sint32)f); + if (writeMask) + _updateGPR_F(destGpr, destElem, f); + _updatePVPS_F(aluUnit, f); + break; + } + case ALU_OP2_INST_DOT4: + { + float f0 = _getSrc_f(src0Sel, src0Chan, src0Neg, src0Abs, src0Rel, indexMode); + float f1 = _getSrc_f(src1Sel, src1Chan, src1Neg, src1Abs, src1Rel, indexMode); + float f = f0 * f1; + reductionResult += f; + _omod(f); + _clamp(f); + if (writeMask) + { + _updateGPR_F(destGpr, destElem, f); + } + _updatePVPS_F(aluUnit, f); + hasReductionInstruction = true; + break; + } + default: + cemu_assert_debug(false); + } + } + } + // apply updates + if (hasReductionInstruction == false) + { + for (sint32 i = 0; i < updateQueueLength; i++) + { + LatteSWCtx.reg[gprUpdate[i].gprIndex].s32[gprUpdate[i].channel] = gprUpdate[i].s32; + } + LatteSWCtx.pvps.s32[0] = LatteSWCtx.pvpsUpdate.s32[0]; + LatteSWCtx.pvps.s32[1] = LatteSWCtx.pvpsUpdate.s32[1]; + LatteSWCtx.pvps.s32[2] = LatteSWCtx.pvpsUpdate.s32[2]; + LatteSWCtx.pvps.s32[3] = LatteSWCtx.pvpsUpdate.s32[3]; + LatteSWCtx.pvps.s32[4] = LatteSWCtx.pvpsUpdate.s32[4]; + } + else + { + for (sint32 i = 0; i < updateQueueLength; i++) + { + LatteSWCtx.reg[gprUpdate[i].gprIndex].f[gprUpdate[i].channel] = reductionResult; + } + LatteSWCtx.pvps.f[0] = reductionResult; + LatteSWCtx.pvps.f[1] = reductionResult; + LatteSWCtx.pvps.f[2] = reductionResult; + LatteSWCtx.pvps.f[3] = reductionResult; + } + updateQueueLength = 0; + // skip literals + if (literalAccessMask&(3 << 0)) + aluWordPtr += 2; + if (literalAccessMask&(3 << 2)) + { + cemu_assert_debug((literalAccessMask &3) != 0); + aluWordPtr += 2; + } + // reset group state tracking variables + aluUnitWriteMask = 0; + literalAccessMask = 0; + } +} + +sint32 _getRegValueByCompSel(uint32 gprIndex, uint32 compSel) +{ + if (compSel < 4) + return LatteSWCtx.reg[gprIndex].s32[compSel]; + cemu_assert_unimplemented(); + return 0; +} + +void LatteSoftware_singleRun() +{ + uint32* cfWords = LatteSWCtx.shaderBase; + sint32 instructionIndex = 0; + while (true) + { + uint32 cfWord0 = cfWords[instructionIndex + 0]; + uint32 cfWord1 = cfWords[instructionIndex + 1]; + instructionIndex += 2; + + uint32 cf_inst23_7 = (cfWord1 >> 23) & 0x7F; + if (cf_inst23_7 < 0x40) // starting at 0x40 the bits overlap with the ALU instruction encoding + { + bool isEndOfProgram = ((cfWord1 >> 21) & 1) != 0; + uint32 addr = cfWord0 & 0xFFFFFFFF; + uint32 count = (cfWord1 >> 10) & 7; + if (((cfWord1 >> 19) & 1) != 0) + count |= 0x8; + count++; + if (cf_inst23_7 == GPU7_CF_INST_CALL_FS) + { + + } + else if (cf_inst23_7 == GPU7_CF_INST_NOP) + { + // nop + if (((cfWord1 >> 0) & 7) != 0) + cemu_assert_debug(false); // pop count is not zero + } + else if (cf_inst23_7 == GPU7_CF_INST_EXPORT || cf_inst23_7 == GPU7_CF_INST_EXPORT_DONE) + { + // export + uint32 edType = (cfWord0 >> 13) & 0x3; + uint32 edIndexGpr = (cfWord0 >> 23) & 0x7F; + uint32 edRWRel = (cfWord0 >> 22) & 1; + if (edRWRel != 0 || edIndexGpr != 0) + cemu_assert_debug(false); + + uint8 exportComponentSel[4]; + exportComponentSel[0] = (cfWord1 >> 0) & 0x7; + exportComponentSel[1] = (cfWord1 >> 3) & 0x7; + exportComponentSel[2] = (cfWord1 >> 6) & 0x7; + exportComponentSel[3] = (cfWord1 >> 9) & 0x7; + uint32 exportArrayBase = (cfWord0 >> 0) & 0x1FFF; + uint32 exportBurstCount = (cfWord1 >> 17) & 0xF; + uint32 exportSourceGPR = (cfWord0 >> 15) & 0x7F; + uint32 memWriteElemSize = (cfWord0>>29)&3; // unused + + cemu_assert_debug(exportBurstCount == 0); + if (edType == 1 && exportArrayBase == GPU7_DECOMPILER_CF_EXPORT_BASE_POSITION) + { + LatteSWCtx.export_pos.s32[0] = _getRegValueByCompSel(exportSourceGPR, exportComponentSel[0]); + LatteSWCtx.export_pos.s32[1] = _getRegValueByCompSel(exportSourceGPR, exportComponentSel[1]); + LatteSWCtx.export_pos.s32[2] = _getRegValueByCompSel(exportSourceGPR, exportComponentSel[2]); + LatteSWCtx.export_pos.s32[3] = _getRegValueByCompSel(exportSourceGPR, exportComponentSel[3]); + } + else + { + // unhandled export + cemu_assert_unimplemented(); + } + } + else if (cf_inst23_7 == GPU7_CF_INST_TEX) + { + cemu_assert_unimplemented(); + } + else if (cf_inst23_7 == GPU7_CF_INST_ELSE || + cf_inst23_7 == GPU7_CF_INST_POP) + { + cemu_assert_unimplemented(); + } + else if (cf_inst23_7 == GPU7_CF_INST_JUMP) + { + cemu_assert_unimplemented(); + } + else if (cf_inst23_7 == GPU7_CF_INST_LOOP_START_DX10 || cf_inst23_7 == GPU7_CF_INST_LOOP_END) + { + cemu_assert_unimplemented(); + } + else if (cf_inst23_7 == GPU7_CF_INST_LOOP_BREAK) + { + cemu_assert_unimplemented(); + } + else if (cf_inst23_7 == GPU7_CF_INST_MEM_STREAM0_WRITE || + cf_inst23_7 == GPU7_CF_INST_MEM_STREAM1_WRITE) + { + cemu_assert_unimplemented(); + } + else if (cf_inst23_7 == GPU7_CF_INST_MEM_RING_WRITE) + { + cemu_assert_unimplemented(); + } + else if (cf_inst23_7 == GPU7_CF_INST_EMIT_VERTEX) + { + cemu_assert_unimplemented(); + } + else + { + cemu_assert_unimplemented(); + } + if (isEndOfProgram) + { + return; + } + } + else + { + // ALU instruction + uint32 cf_inst26_4 = ((cfWord1 >> 26) & 0xF) | GPU7_CF_INST_ALU_MASK; + if (cf_inst26_4 == GPU7_CF_INST_ALU || cf_inst26_4 == GPU7_CF_INST_ALU_PUSH_BEFORE || cf_inst26_4 == GPU7_CF_INST_ALU_POP_AFTER || cf_inst26_4 == GPU7_CF_INST_ALU_POP2_AFTER || cf_inst26_4 == GPU7_CF_INST_ALU_BREAK || cf_inst26_4 == GPU7_CF_INST_ALU_ELSE_AFTER) + { + uint32 addr = (cfWord0 >> 0) & 0x3FFFFF; + uint32 count = ((cfWord1 >> 18) & 0x7F) + 1; + uint32 cBank0Index = (cfWord0 >> 22) & 0xF; + uint32 cBank1Index = (cfWord0 >> 26) & 0xF; + uint32 cBank0AddrBase = ((cfWord1 >> 2) & 0xFF) * 16; + uint32 cBank1AddrBase = ((cfWord1 >> 10) & 0xFF) * 16; + LatteSoftware_executeALUClause(cf_inst26_4, addr, count, cBank0Index, cBank1Index, cBank0AddrBase, cBank1AddrBase); + } + else + { + cemu_assert_unimplemented(); + } + } + } + cemu_assert_debug(false); +} + +template<int endianMode> +uint32 _readVtxU32(void* ptr) +{ + uint32 v = *(uint32*)ptr; + if constexpr (endianMode == GPU7_ENDIAN_8IN32) + v = _swapEndianU32(v); + return v; +} + +template<int endianMode, int nfa> +void _readAttr_FLOAT_32_32(void* ptr, LatteReg_t& output) +{ + output.s32[0] = _readVtxU32<endianMode>((uint32*)ptr); + output.s32[1] = _readVtxU32<endianMode>((uint32*)ptr + 1); + output.s32[2] = 0; + output.s32[3] = 0; +} + +template<int endianMode, int nfa> +void _readAttr_FLOAT_32_32_32(void* ptr, LatteReg_t& output) +{ + output.s32[0] = _readVtxU32<endianMode>((uint32*)ptr); + output.s32[1] = _readVtxU32<endianMode>((uint32*)ptr + 1); + output.s32[2] = _readVtxU32<endianMode>((uint32*)ptr + 2); + output.s32[3] = 0; +} + +template<int endianMode, int nfa> +void _readAttr_FLOAT_32_32_32_32(void* ptr, LatteReg_t& output) +{ + output.s32[0] = _readVtxU32<endianMode>((uint32*)ptr); + output.s32[1] = _readVtxU32<endianMode>((uint32*)ptr + 1); + output.s32[2] = _readVtxU32<endianMode>((uint32*)ptr + 2); + output.s32[3] = _readVtxU32<endianMode>((uint32*)ptr + 3); +} + +#define _fmtKey(__fmt, __endianSwap, __nfa, __isSigned) ((__endianSwap)|((__nfa)<<2)|((__isSigned)<<4)|((__fmt)<<5)) + +void LatteSoftware_loadVertexAttributes(sint32 index) +{ + LatteSWCtx.reg[0].s32[0] = index; + for (auto& bufferGroup : LatteSWCtx.fetchShader->bufferGroups) + { + for (sint32 f = 0; f < bufferGroup.attribCount; f++) + { + auto attrib = bufferGroup.attrib + f; + // calculate element index + sint32 elementIndex = index; + // todo - handle instance index and attr divisor + + // get buffer + uint32 bufferIndex = attrib->attributeBufferIndex; + if (bufferIndex >= 0x10) + { + continue; + } + uint32 bufferBaseRegisterIndex = mmSQ_VTX_ATTRIBUTE_BLOCK_START + bufferIndex * 7; + MPTR bufferAddress = LatteGPUState.contextRegister[bufferBaseRegisterIndex + 0]; + uint32 bufferSize = LatteGPUState.contextRegister[bufferBaseRegisterIndex + 1] + 1; + uint32 bufferStride = (LatteGPUState.contextRegister[bufferBaseRegisterIndex + 2] >> 11) & 0xFFFF; + if (bufferAddress == MPTR_NULL) + { + debug_printf("Warning: Attribute uses NULL buffer during software emulation\n"); + continue; + } + + // translate semanticId to gpr index + uint32 gprIndex = 0xFFFFFFFF; + for (sint32 f = 0; f < 32; f++) + { + if (LatteGPUState.contextRegister[mmSQ_VTX_SEMANTIC_0 + f] == attrib->semanticId) + { + gprIndex = f; + break; + } + } + if (gprIndex == 0xFFFFFFFF) + continue; // attribute is not mapped to VS input + gprIndex = gprIndex + 1; + + sint32 formatKey = _fmtKey((sint32)attrib->format, (sint32)attrib->endianSwap, (sint32)attrib->nfa, (sint32)attrib->isSigned); + + void* inputData = memory_getPointerFromPhysicalOffset(bufferAddress + elementIndex * bufferStride); + + LatteReg_t attrData; + + switch (formatKey) + { + case _fmtKey(FMT_32_32_FLOAT, GPU7_ENDIAN_8IN32, LATTE_NFA_2, LATTE_VTX_UNSIGNED): + _readAttr_FLOAT_32_32<GPU7_ENDIAN_8IN32, LATTE_NFA_2>(inputData, attrData); + break; + case _fmtKey(FMT_32_32_32_FLOAT, GPU7_ENDIAN_8IN32, LATTE_NFA_2, LATTE_VTX_UNSIGNED): + _readAttr_FLOAT_32_32_32<GPU7_ENDIAN_8IN32, LATTE_NFA_2>(inputData, attrData); + break; + case _fmtKey(FMT_32_32_32_32_FLOAT, GPU7_ENDIAN_8IN32, LATTE_NFA_2, LATTE_VTX_UNSIGNED): + _readAttr_FLOAT_32_32_32_32<GPU7_ENDIAN_8IN32, LATTE_NFA_2>(inputData, attrData); + break; + default: + cemu_assert_debug(false); + } + + LatteReg_t* gprOutput = LatteSWCtx.reg+gprIndex; + for (uint32 f = 0; f < 4; f++) + { + if (attrib->ds[f] < 4) + gprOutput->s32[f] = attrData.s32[f]; + else if (attrib->ds[f] == 4) + gprOutput->s32[f] = 0; + else if (attrib->ds[f] == 5) + gprOutput->f[f] = 1.0; + else + cemu_assert_debug(false); + } + } + } +} + +float* LatteSoftware_getPositionExport() +{ + return LatteSWCtx.export_pos.f; +} + +void LatteSoftware_executeVertex(sint32 index) +{ + LatteSoftware_loadVertexAttributes(index); + LatteSoftware_singleRun(); +} + +void LatteSoftware_setupVertexShader(LatteFetchShader* fetchShader, void* shaderPtr, sint32 size) +{ + LatteSWCtx.fetchShader = fetchShader; + LatteSWCtx.shaderBase = (uint32*)shaderPtr; + LatteSWCtx.shaderSize = size; + LatteSWCtx.cfilePtr = (void*)(LatteGPUState.contextRegister + LATTE_REG_BASE_ALU_CONST + 0x400); +} diff --git a/src/Cafe/HW/Latte/Core/LatteSoftware.h b/src/Cafe/HW/Latte/Core/LatteSoftware.h new file mode 100644 index 00000000..7a2215e1 --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteSoftware.h @@ -0,0 +1,6 @@ +#pragma once + +void LatteSoftware_setupVertexShader(struct LatteFetchShader* fetchShader, void* shaderPtr, sint32 size); +void LatteSoftware_executeVertex(sint32 index); + +float* LatteSoftware_getPositionExport(); \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Core/LatteStreamoutGPU.cpp b/src/Cafe/HW/Latte/Core/LatteStreamoutGPU.cpp new file mode 100644 index 00000000..07e7106d --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteStreamoutGPU.cpp @@ -0,0 +1,168 @@ +#include "Cafe/HW/Latte/Core/LatteConst.h" +#include "Cafe/HW/Latte/ISA/RegDefines.h" +#include "Cafe/HW/Latte/Core/Latte.h" +#include "Cafe/HW/Latte/Core/LatteDraw.h" +#include "Cafe/HW/Latte/Core/LatteShader.h" +#include "Cafe/GameProfile/GameProfile.h" +#include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h" + +#include "util/containers/IntervalBucketContainer.h" +#include "Cafe/HW/Latte/Renderer/Renderer.h" +#include "Cafe/HW/Latte/Core/LatteRingBuffer.h" +#include "Cafe/HW/Latte/Core/LatteBufferCache.h" + +struct +{ + sint32 currentRingbufferOffset; + VirtualBufferHeap_t* mainBufferHeap; +}streamoutManager; + +sint32 LatteStreamout_GetRingBufferSize() +{ + return 8 * 1024 * 1024; // 8MB +} + +sint32 LatteStreamout_allocateGPURingbufferMem(sint32 size) +{ + // pad size to 256 byte alignment + size = (size + 255)&~255; + // get next offset + if ((streamoutManager.currentRingbufferOffset + size) > LatteStreamout_GetRingBufferSize()) + { + streamoutManager.currentRingbufferOffset = 0; + } + sint32 allocOffset = streamoutManager.currentRingbufferOffset; + streamoutManager.currentRingbufferOffset += size; + return allocOffset; +} + +void LatteStreamout_InitCache() +{ + streamoutManager.currentRingbufferOffset = 0; + streamoutManager.mainBufferHeap = nullptr; +} + +bool _transformFeedbackIsActive = false; +struct +{ + uint32 vertexCount; + uint32 instanceCount; + uint32 streamoutWriteMask; + struct + { + bool isActive; + sint32 ringBufferOffset; + uint32 rangeAddr; + uint32 rangeSize; // size of written streamout data, bounded by buffer size + }streamoutBufferWrite[LATTE_NUM_STREAMOUT_BUFFER]; +}activeStreamoutOperation; + +uint32 LatteStreamout_getNumberOfWrittenVertices() +{ + // todo: Currently we only handle GX2_POINTS + return activeStreamoutOperation.vertexCount * activeStreamoutOperation.instanceCount; +} + +// returns the number of bytes that are written into the buffer by the current draw operation (ignoring buffer maximum size) +uint32 LatteStreamout_getBufferWriteRangeSize(uint32 streamoutBufferIndex) +{ + uint32 bufferStride = LatteGPUState.contextRegister[mmVGT_STRMOUT_VTX_STRIDE_0 + streamoutBufferIndex * 4] << 2; + uint32 bufferSize = LatteGPUState.contextRegister[mmVGT_STRMOUT_BUFFER_SIZE_0 + streamoutBufferIndex * 4] << 2; + uint32 writeSize = LatteStreamout_getNumberOfWrittenVertices() * bufferStride; + if (bufferSize < writeSize) + writeSize = bufferSize; + return writeSize; +} + +void LatteStreamout_PrepareDrawcall(uint32 count, uint32 instanceCount) +{ + if (LatteGPUState.contextRegister[mmVGT_STRMOUT_EN] == 0) + { + _transformFeedbackIsActive = false; + return; // streamout inactive + } + // get active vertex shader + LatteDecompilerShader* vertexShader = LatteSHRC_GetActiveVertexShader(); + // if a geometry shader is used calculate how many vertices it outputs + LatteDecompilerShader* geometryShader = LatteSHRC_GetActiveGeometryShader(); + sint32 maxVerticesInGS = 1; + if (geometryShader) + { + uint32 gsOutPrimType = LatteGPUState.contextRegister[mmVGT_GS_OUT_PRIM_TYPE]; + uint32 bytesPerVertex = LatteGPUState.contextRegister[mmSQ_GS_VERT_ITEMSIZE] * 4; + maxVerticesInGS = ((LatteGPUState.contextRegister[mmSQ_GSVS_RING_ITEMSIZE] & 0x7FFF) * 4) / bytesPerVertex; + cemu_assert_debug(maxVerticesInGS > 0); + } + // setup active streamout operation struct + activeStreamoutOperation.vertexCount = count * maxVerticesInGS; + activeStreamoutOperation.instanceCount = instanceCount; + // get mask of all written streamout buffers + uint32 streamoutWriteMask = 0; + if (geometryShader) + { +#ifndef PUBLIC_RELEASE + cemu_assert_debug(vertexShader->streamoutBufferWriteMask2.any() == false); +#endif + for (sint32 i = 0; i < LATTE_NUM_STREAMOUT_BUFFER; i++) + if (geometryShader->streamoutBufferWriteMask2[i]) + streamoutWriteMask |= (1 << i); + } + else + { + for (sint32 i = 0; i < LATTE_NUM_STREAMOUT_BUFFER; i++) + if (vertexShader->streamoutBufferWriteMask2[i]) + streamoutWriteMask |= (1 << i); + } + activeStreamoutOperation.streamoutWriteMask = streamoutWriteMask; + // bind streamout buffers + for (uint32 i = 0; i < LATTE_NUM_STREAMOUT_BUFFER; i++) + { + if ((streamoutWriteMask&(1 << i)) == 0) + { + activeStreamoutOperation.streamoutBufferWrite[i].isActive = false; + continue; + } + uint32 bufferBaseMPTR = LatteGPUState.contextRegister[mmVGT_STRMOUT_BUFFER_BASE_0 + i * 4] << 8; + uint32 bufferSize = LatteGPUState.contextRegister[mmVGT_STRMOUT_BUFFER_SIZE_0 + i * 4] << 2; + uint32 bufferOffset = LatteGPUState.contextRegister[mmVGT_STRMOUT_BUFFER_OFFSET_0 + i * 4]; + uint32 streamoutWriteSize = LatteStreamout_getBufferWriteRangeSize(i); + uint32 rangeAddr = bufferBaseMPTR + bufferOffset; + sint32 ringBufferOffset = LatteStreamout_allocateGPURingbufferMem(streamoutWriteSize); // allocate memory for the entire streamout write + // calculate write size after bounding it to the buffer + uint32 remainingBytesToWrite = bufferOffset > bufferSize ? 0 : (bufferSize - bufferOffset); + uint32 rangeSize = std::min(streamoutWriteSize, remainingBytesToWrite); + + activeStreamoutOperation.streamoutBufferWrite[i].isActive = true; + activeStreamoutOperation.streamoutBufferWrite[i].ringBufferOffset = ringBufferOffset; + activeStreamoutOperation.streamoutBufferWrite[i].rangeAddr = rangeAddr; + activeStreamoutOperation.streamoutBufferWrite[i].rangeSize = rangeSize; + + g_renderer->streamout_setupXfbBuffer(i, ringBufferOffset, rangeAddr, rangeSize); + } + g_renderer->streamout_begin(); + _transformFeedbackIsActive = true; +} + +void LatteStreamout_FinishDrawcall(bool useDirectMemoryMode) +{ + if (_transformFeedbackIsActive) + { + _transformFeedbackIsActive = false; + for (uint32 i = 0; i < LATTE_NUM_STREAMOUT_BUFFER; i++) + { + if ((activeStreamoutOperation.streamoutWriteMask&(1 << i)) == 0) + continue; + if (activeStreamoutOperation.streamoutBufferWrite[i].rangeSize > 0) + { + if(useDirectMemoryMode) + g_renderer->bufferCache_copyStreamoutToMainBuffer(activeStreamoutOperation.streamoutBufferWrite[i].ringBufferOffset, activeStreamoutOperation.streamoutBufferWrite[i].rangeAddr, activeStreamoutOperation.streamoutBufferWrite[i].rangeSize); + else + LatteBufferCache_copyStreamoutDataToCache(activeStreamoutOperation.streamoutBufferWrite[i].rangeAddr, activeStreamoutOperation.streamoutBufferWrite[i].rangeSize, activeStreamoutOperation.streamoutBufferWrite[i].ringBufferOffset); + } + // advance streamout offset + uint32 newOffset = LatteGPUState.contextRegister[mmVGT_STRMOUT_BUFFER_OFFSET_0 + i * 4] + activeStreamoutOperation.streamoutBufferWrite[i].rangeSize; + LatteGPUState.contextRegister[mmVGT_STRMOUT_BUFFER_OFFSET_0 + i * 4] = newOffset; + } + g_renderer->streamout_rendererFinishDrawcall(); + } +} diff --git a/src/Cafe/HW/Latte/Core/LatteSurfaceCopy.cpp b/src/Cafe/HW/Latte/Core/LatteSurfaceCopy.cpp new file mode 100644 index 00000000..1c84145a --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteSurfaceCopy.cpp @@ -0,0 +1,107 @@ +#include "Cafe/HW/Latte/Core/Latte.h" +#include "Cafe/HW/Latte/Core/LatteDraw.h" +#include "Cafe/HW/Latte/Core/LatteShader.h" +#include "Cafe/HW/Latte/Core/LatteDefaultShaders.h" +#include "Cafe/HW/Latte/Core/LatteTexture.h" + +#include "Cafe/HW/Latte/Renderer/Renderer.h" + +void LatteSurfaceCopy_copySurfaceNew(MPTR srcPhysAddr, MPTR srcMipAddr, uint32 srcSwizzle, Latte::E_GX2SURFFMT srcSurfaceFormat, sint32 srcWidth, sint32 srcHeight, sint32 srcDepth, uint32 srcPitch, sint32 srcSlice, Latte::E_DIM srcDim, Latte::E_HWTILEMODE srcTilemode, sint32 srcAA, sint32 srcLevel, MPTR dstPhysAddr, MPTR dstMipAddr, uint32 dstSwizzle, Latte::E_GX2SURFFMT dstSurfaceFormat, sint32 dstWidth, sint32 dstHeight, sint32 dstDepth, uint32 dstPitch, sint32 dstSlice, Latte::E_DIM dstDim, Latte::E_HWTILEMODE dstTilemode, sint32 dstAA, sint32 dstLevel) +{ + // check if source is within valid mip range + if (srcDim == Latte::E_DIM::DIM_3D && (srcDepth >> srcLevel) == 0 && (srcWidth >> srcLevel) == 0 && (srcHeight >> srcLevel) == 0) + return; + else if ((srcWidth >> srcLevel) == 0 && (srcHeight >> srcLevel) == 0) + return; + // look up source texture + LatteTexture* sourceTexture = nullptr; + LatteTextureView* sourceView = LatteTC_GetTextureSliceViewOrTryCreate(srcPhysAddr, srcMipAddr, srcSurfaceFormat, srcTilemode, srcWidth, srcHeight, srcDepth, srcPitch, srcSwizzle, srcSlice, srcLevel); + if (sourceView == nullptr) + { + debug_printf("HLECopySurface(): Source texture is not in list of dynamic textures\n"); + return; + } + sourceTexture = sourceView->baseTexture; + if (sourceTexture->reloadFromDynamicTextures) + { + LatteTexture_UpdateCacheFromDynamicTextures(sourceTexture); + sourceTexture->reloadFromDynamicTextures = false; + } + // look up destination texture + LatteTexture* destinationTexture = nullptr; + LatteTextureView* destinationView = LatteTextureViewLookupCache::lookupSlice(dstPhysAddr, dstWidth, dstHeight, dstPitch, dstLevel, dstSlice, dstSurfaceFormat); + if (destinationView) + destinationTexture = destinationView->baseTexture; + + // create destination texture if it doesnt exist + if (!destinationTexture) + { + LatteTexture* renderTargetConf = nullptr; + destinationView = LatteTexture_CreateMapping(dstPhysAddr, dstMipAddr, dstWidth, dstHeight, dstDepth, dstPitch, dstTilemode, dstSwizzle, dstLevel, 1, dstSlice, 1, dstSurfaceFormat, dstDim, Latte::E_DIM::DIM_2D, false); + destinationTexture = destinationView->baseTexture; + } + // copy texture + if (sourceTexture && destinationTexture) + { + // mark source and destination texture as still in use + LatteTC_MarkTextureStillInUse(destinationTexture); + LatteTC_MarkTextureStillInUse(sourceTexture); + // determine GL slice indices + sint32 realSrcSlice = srcSlice; + sint32 realDstSlice = dstSlice; + if (LatteTexture_doesEffectiveRescaleRatioMatch(sourceTexture, sourceView->firstMip, destinationTexture, destinationView->firstMip)) + { + // adjust copy size + sint32 copyWidth = std::max(srcWidth >> srcLevel, 1); + sint32 copyHeight = std::max(srcHeight >> srcLevel, 1); + // use the smaller width/height as copy size + copyWidth = std::min(copyWidth, std::max(dstWidth >> dstLevel, 1)); + copyHeight = std::min(copyHeight, std::max(dstHeight >> dstLevel, 1)); + sint32 effectiveCopyWidth = copyWidth; + sint32 effectiveCopyHeight = copyHeight; + LatteTexture_scaleToEffectiveSize(sourceTexture, &effectiveCopyWidth, &effectiveCopyHeight, 0); + // copy slice + if (sourceView->baseTexture->isDepth != destinationView->baseTexture->isDepth) + { + g_renderer->surfaceCopy_copySurfaceWithFormatConversion(sourceTexture, sourceView->firstMip, sourceView->firstSlice, destinationTexture, destinationView->firstMip, destinationView->firstSlice, copyWidth, copyHeight); + uint64 eventCounter = LatteTexture_getNextUpdateEventCounter(); + LatteTexture_MarkDynamicTextureAsChanged(destinationTexture->baseView, destinationView->firstSlice, destinationView->firstMip, eventCounter); + } + else + { + // calculate mip levels relative to texture base + sint32 texDstMipLevel; + if (destinationTexture->physAddress == dstPhysAddr) + { + texDstMipLevel = dstLevel; + } + else + { + // todo - handle mip addresses properly + texDstMipLevel = dstLevel - destinationView->firstMip; + } + + g_renderer->texture_copyImageSubData(sourceTexture, sourceView->firstMip, 0, 0, realSrcSlice, destinationTexture, texDstMipLevel, 0, 0, realDstSlice, effectiveCopyWidth, effectiveCopyHeight, 1); + uint64 eventCounter = LatteTexture_getNextUpdateEventCounter(); + LatteTexture_MarkDynamicTextureAsChanged(destinationTexture->baseView, destinationView->firstSlice, texDstMipLevel, eventCounter); + } + } + else + { + debug_printf("gx2CP_itHLECopySurface(): Copy texture with non-matching effective size\n"); + } + LatteTC_ResetTextureChangeTracker(destinationTexture); + // flag texture as updated + destinationTexture->lastUpdateEventCounter = LatteTexture_getNextUpdateEventCounter(); + destinationTexture->isUpdatedOnGPU = true; // todo - also track update flag per-slice + } + else + debug_printf("Source or destination texture does not exist\n"); + + // download destination texture if it matches known accessed formats + if (destinationTexture->width == 8 && destinationTexture->height == 8 && destinationTexture->tileMode == Latte::E_HWTILEMODE::TM_1D_TILED_THIN1) + { + forceLogDebug_printf("Texture readback after copy for Bayonetta 2 (phys: 0x%08x)", destinationTexture->physAddress); + LatteTextureReadback_Initate(destinationView); + } +} \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Core/LatteTexture.cpp b/src/Cafe/HW/Latte/Core/LatteTexture.cpp new file mode 100644 index 00000000..899888de --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteTexture.cpp @@ -0,0 +1,1361 @@ +#include "Cafe/HW/Latte/Core/Latte.h" +#include "Cafe/HW/Latte/Core/LatteDraw.h" +#include "Cafe/HW/Latte/Core/LatteShader.h" +#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" +#include "Cafe/HW/Latte/Core/LatteTexture.h" + +#include "Cafe/HW/Latte/Renderer/Renderer.h" +#include "Cafe/HW/Latte/LatteAddrLib/LatteAddrLib.h" + +#include "Cafe/GraphicPack/GraphicPack2.h" + +struct TexMemOccupancyEntry +{ + uint32 addrStart; + uint32 addrEnd; + LatteTextureSliceMipInfo* sliceMipInfo; +}; + +#define TEX_OCCUPANCY_BUCKET_COUNT (0x800) // each bucket covers a range of 2MB +#define TEX_OCCUPANCY_BUCKET_SIZE (0x100000000/TEX_OCCUPANCY_BUCKET_COUNT) + +#define loopItrMemOccupancyBuckets(__startAddr, __endAddr) for(sint32 startBucketIndex = ((__startAddr)/TEX_OCCUPANCY_BUCKET_SIZE), bucketIndex=startBucketIndex; bucketIndex<=((__endAddr-1)/TEX_OCCUPANCY_BUCKET_SIZE); bucketIndex++) + +std::vector<TexMemOccupancyEntry> list_texMemOccupancyBucket[TEX_OCCUPANCY_BUCKET_COUNT]; + +std::atomic_bool s_refreshTextureQueryList; +std::vector<LatteTextureInformation> s_cacheInfoList; + +std::vector<LatteTextureInformation> LatteTexture_QueryCacheInfo() +{ + // raise request flag to refresh cache + s_refreshTextureQueryList.store(true); + // wait until cleared or until timeout occurred + auto begin = std::chrono::high_resolution_clock::now(); + while (true) + { + if (!s_refreshTextureQueryList) + break; + auto dur = std::chrono::high_resolution_clock::now() - begin; + auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(dur).count(); + if (ms >= 1000) // dont stall more than one second + return std::vector<LatteTextureInformation>(); + } + return s_cacheInfoList; +} + +void LatteTexture_RefreshInfoCache() +{ + if (!s_refreshTextureQueryList) + return; + std::vector<LatteTextureInformation> infoCache; + std::set<LatteTexture*> visitedTextures; + + std::unordered_set<LatteTextureView*> allViews = LatteTextureViewLookupCache::GetAllViews(); + for (auto& it : allViews) + { + LatteTexture* baseTexture = it->baseTexture; + if(visitedTextures.find(baseTexture) != visitedTextures.end()) + continue; + visitedTextures.emplace(baseTexture); + // add cache info + auto& entry = infoCache.emplace_back(); + entry.physAddress = baseTexture->physAddress; + entry.physMipAddress = baseTexture->physMipAddress; + entry.width = baseTexture->width; + entry.height = baseTexture->height; + entry.depth = baseTexture->depth; + entry.pitch = baseTexture->pitch; + entry.mipLevels = baseTexture->mipLevels; + entry.format = baseTexture->format; + entry.isDepth = baseTexture->isDepth; + entry.dim = baseTexture->dim; + entry.tileMode = baseTexture->tileMode; + entry.lastAccessTick = baseTexture->lastAccessTick; + entry.lastAccessFrameCount = baseTexture->lastAccessFrameCount; + entry.isUpdatedOnGPU = baseTexture->isUpdatedOnGPU; + // overwrite info + entry.overwriteInfo.hasResolutionOverwrite = baseTexture->overwriteInfo.hasResolutionOverwrite; + entry.overwriteInfo.width = baseTexture->overwriteInfo.width; + entry.overwriteInfo.height = baseTexture->overwriteInfo.height; + entry.overwriteInfo.depth = baseTexture->overwriteInfo.depth; + // count number of alternative views + entry.alternativeViewCount = 0; + // views + for (auto& viewItr : baseTexture->views) + { + if(viewItr == baseTexture->baseView) + continue; + auto& viewEntry = entry.views.emplace_back(); + viewEntry.physAddress = viewItr->baseTexture->physAddress; + viewEntry.physMipAddress = viewItr->baseTexture->physMipAddress; + viewEntry.width = viewItr->baseTexture->width; + viewEntry.height = viewItr->baseTexture->height; + viewEntry.pitch = viewItr->baseTexture->pitch; + viewEntry.firstMip = viewItr->firstMip; + viewEntry.numMip = viewItr->numMip; + viewEntry.firstSlice = viewItr->firstSlice; + viewEntry.numSlice = viewItr->numSlice; + viewEntry.format = viewItr->format; + viewEntry.dim = viewItr->dim; + } + } + std::swap(s_cacheInfoList, infoCache); + s_refreshTextureQueryList.store(false); +} + +void LatteTexture_AddTexMemOccupancyInterval(LatteTextureSliceMipInfo* sliceMipInfo) +{ + TexMemOccupancyEntry entry; + entry.addrStart = sliceMipInfo->addrStart; + entry.addrEnd = sliceMipInfo->addrEnd; + entry.sliceMipInfo = sliceMipInfo; + loopItrMemOccupancyBuckets(entry.addrStart, entry.addrEnd) + list_texMemOccupancyBucket[bucketIndex].push_back(entry); +} + +void LatteTexture_RegisterTextureMemoryOccupancy(LatteTexture* texture) +{ + sint32 mipLevels = texture->mipLevels; + sint32 sliceCount = texture->depth; + for (sint32 mipIndex = 0; mipIndex < mipLevels; mipIndex++) + { + sint32 mipSliceCount; + if (texture->Is3DTexture()) + mipSliceCount = std::max(1, sliceCount >> mipIndex); + else + mipSliceCount = sliceCount; + for (sint32 sliceIndex = 0; sliceIndex < mipSliceCount; sliceIndex++) + { + LatteTextureSliceMipInfo* sliceMipInfo = texture->sliceMipInfo + texture->GetSliceMipArrayIndex(sliceIndex, mipIndex); + LatteTexture_AddTexMemOccupancyInterval(sliceMipInfo); + } + } +} + +void LatteTexture_RemoveTexMemOccupancyInterval(LatteTexture* texture, LatteTextureSliceMipInfo* sliceMipInfo) +{ + loopItrMemOccupancyBuckets(sliceMipInfo->addrStart, sliceMipInfo->addrEnd) + { + for (sint32 i = 0; i < list_texMemOccupancyBucket[bucketIndex].size(); i++) + { + if (list_texMemOccupancyBucket[bucketIndex][i].sliceMipInfo->texture == texture) + { + list_texMemOccupancyBucket[bucketIndex].erase(list_texMemOccupancyBucket[bucketIndex].begin() + i); + i--; + continue; + } + } + } +} + +void LatteTexture_UnregisterTextureMemoryOccupancy(LatteTexture* texture) +{ + sint32 mipLevels = texture->mipLevels; + sint32 sliceCount = texture->depth; + for (sint32 mipIndex = 0; mipIndex < mipLevels; mipIndex++) + { + sint32 mipSliceCount; + if (texture->Is3DTexture()) + mipSliceCount = std::max(1, sliceCount >> mipIndex); + else + mipSliceCount = sliceCount; + for (sint32 sliceIndex = 0; sliceIndex < mipSliceCount; sliceIndex++) + { + LatteTextureSliceMipInfo* sliceMipInfo = texture->sliceMipInfo + texture->GetSliceMipArrayIndex(sliceIndex, mipIndex); + LatteTexture_RemoveTexMemOccupancyInterval(texture, sliceMipInfo); + } + } +} + +// calculate the actually accessed data range +// the resulting range is an estimate and may be smaller than the actual slice size (but not larger) +void LatteTexture_EstimateMipSliceAccessedDataRange(LatteTexture* texture, sint32 sliceIndex, sint32 mipIndex, LatteTextureSliceMipInfo* sliceMipInfo) +{ + uint32 estAddrStart; + uint32 estAddrEnd; + LatteTextureLoader_estimateAccessedDataRange(texture, sliceIndex, mipIndex, estAddrStart, estAddrEnd); + + cemu_assert_debug(estAddrStart >= sliceMipInfo->addrStart); + cemu_assert_debug(estAddrEnd <= sliceMipInfo->addrEnd); + cemu_assert_debug(estAddrStart <= estAddrEnd); + + sliceMipInfo->estDataAddrStart = estAddrStart; + sliceMipInfo->estDataAddrEnd = estAddrEnd; +} + +void LatteTexture_InitSliceAndMipInfo(LatteTexture* texture) +{ + cemu_assert_debug(texture->mipLevels > 0); + cemu_assert_debug(texture->depth > 0); + sint32 mipSliceCount = texture->GetSliceMipArraySize(); + texture->sliceMipInfo = new LatteTextureSliceMipInfo[mipSliceCount](); + // todo - mipLevels can be greater than maximum possible mip count. How to handle this? Probably should differentiate between mipLevels and effective mip levels + sint32 mipLevels = texture->mipLevels; + sint32 sliceCount = texture->depth; + + for (sint32 mipIndex = 0; mipIndex < mipLevels; mipIndex++) + { + sint32 mipSliceCount; + if (texture->Is3DTexture()) + { + mipSliceCount = std::max(1, sliceCount >> mipIndex); + } + else + mipSliceCount = sliceCount; + for (sint32 sliceIndex = 0; sliceIndex < mipSliceCount; sliceIndex++) + { + uint32 calcSliceAddr; + uint32 calcSliceSize; + sint32 calcSubSliceIndex; + LatteAddrLib::CalculateMipAndSliceAddr(texture->physAddress, texture->physMipAddress, texture->format, texture->width, texture->height, texture->depth, texture->dim, texture->tileMode, texture->swizzle, 0, mipIndex, sliceIndex, &calcSliceAddr, &calcSliceSize, &calcSubSliceIndex); + LatteTextureSliceMipInfo* sliceMipInfo = texture->sliceMipInfo + texture->GetSliceMipArrayIndex(sliceIndex, mipIndex); + sliceMipInfo->addrStart = calcSliceAddr; + sliceMipInfo->addrEnd = calcSliceAddr + calcSliceSize; + sliceMipInfo->subIndex = calcSubSliceIndex; + sliceMipInfo->dataChecksum = 0; + sliceMipInfo->sliceIndex = sliceIndex; + sliceMipInfo->mipIndex = mipIndex; + sliceMipInfo->texture = texture; + // get additional slice/mip info + LatteAddrLib::AddrSurfaceInfo_OUT surfaceInfo; + LatteAddrLib::GX2CalculateSurfaceInfo(texture->format, texture->width, texture->height, texture->depth, texture->dim, Latte::MakeGX2TileMode(texture->tileMode), 0, mipIndex, &surfaceInfo); + sliceMipInfo->tileMode = surfaceInfo.hwTileMode; + + if (mipIndex == 0) + sliceMipInfo->pitch = texture->pitch; // for the base level, use the pitch value configured in hardware + else + sliceMipInfo->pitch = surfaceInfo.pitch; + LatteTexture_EstimateMipSliceAccessedDataRange(texture, sliceIndex, mipIndex, sliceMipInfo); + } + } +} + +// if this function returns false, textures will not be synchronized even if their data overlaps +bool LatteTexture_IsFormatViewCompatible(Latte::E_GX2SURFFMT formatA, Latte::E_GX2SURFFMT formatB) +{ + // todo - find a better way to handle this + for (sint32 swap = 0; swap < 2; swap++) + { + // other formats + // seems like format 0x19 (RGB10_A2) has issues on OpenGL Intel and AMD when copying texture data + Latte::E_HWSURFFMT hwFormatA = Latte::GetHWFormat(formatA); + Latte::E_HWSURFFMT hwFormatB = Latte::GetHWFormat(formatB); + if (hwFormatA == Latte::E_HWSURFFMT::HWFMT_2_10_10_10 && formatB == Latte::E_GX2SURFFMT::R11_G11_B10_FLOAT) + return false; + if (formatA == Latte::E_GX2SURFFMT::R11_G11_B10_FLOAT && hwFormatB == Latte::E_HWSURFFMT::HWFMT_2_10_10_10) + return false; + + if (hwFormatA == Latte::E_HWSURFFMT::HWFMT_2_10_10_10 && formatB == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM) + return false; + if (formatA == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM && hwFormatB == Latte::E_HWSURFFMT::HWFMT_2_10_10_10) + return false; + + // format A1B5G5R5 views are not compatible with other 16-bit formats in OpenGL + if (formatA == Latte::E_GX2SURFFMT::A1_B5_G5_R5_UNORM || formatB == Latte::E_GX2SURFFMT::A1_B5_G5_R5_UNORM) + return false; + // used in N64 VC (E.g. Super Mario 64) + + // used in Smash + if (formatA == Latte::E_GX2SURFFMT::D24_S8_UNORM && formatB == Latte::E_GX2SURFFMT::R10_G10_B10_A2_SNORM) + return false; + if (formatA == Latte::E_GX2SURFFMT::R32_FLOAT && formatB == Latte::E_GX2SURFFMT::R10_G10_B10_A2_SNORM) + return false; + + // loop again with swapped vars + Latte::E_GX2SURFFMT temp = formatA; + formatA = formatB; + formatB = temp; + } + return true; +} + +bool LatteTexture_IsTexelSizeCompatibleFormat(Latte::E_GX2SURFFMT formatA, Latte::E_GX2SURFFMT formatB) +{ + // handle some special cases where formats are incompatible regardless of equal bpp + if (formatA == Latte::E_GX2SURFFMT::D24_S8_UNORM && formatB == Latte::E_GX2SURFFMT::D32_FLOAT) + return false; + if (Latte::IsCompressedFormat(formatA) && Latte::IsCompressedFormat(formatB)) + { + if (Latte::GetHWFormat(formatA) != Latte::GetHWFormat(formatB)) + return false; // compressed formats with different encodings are considered incompatible + } + return Latte::GetFormatBits((Latte::E_GX2SURFFMT)formatA) == Latte::GetFormatBits((Latte::E_GX2SURFFMT)formatB); +} + +void LatteTexture_copyData(LatteTexture* srcTexture, LatteTexture* dstTexture, sint32 mipCount, sint32 sliceCount) +{ + cemu_assert_debug(mipCount != 0); + cemu_assert_debug(sliceCount != 0); + sint32 effectiveCopyWidth = srcTexture->width; + sint32 effectiveCopyHeight = srcTexture->height; + if (LatteTexture_doesEffectiveRescaleRatioMatch(dstTexture, 0, srcTexture, 0)) + { + // adjust copy size + LatteTexture_scaleToEffectiveSize(dstTexture, &effectiveCopyWidth, &effectiveCopyHeight, 0); + } + else + { + sint32 effectiveWidth_dst, effectiveHeight_dst; + LatteTexture_getEffectiveSize(srcTexture, &effectiveWidth_dst, &effectiveHeight_dst, NULL, 0); + sint32 effectiveWidth_src, effectiveHeight_src; + LatteTexture_getEffectiveSize(dstTexture, &effectiveWidth_src, &effectiveHeight_src, NULL, 0); + + debug_printf("texture_copyData(): Effective size mismatch\n"); + forceLogDebug_printf("texture_copyData(): Effective size mismatch (due to texture rule)"); + forceLogDebug_printf("Destination: origResolution %04dx%04d effectiveResolution %04dx%04d fmt %04x mipIndex %d", srcTexture->width, srcTexture->height, effectiveWidth_dst, effectiveHeight_dst, (uint32)dstTexture->format, 0); + forceLogDebug_printf("Source: origResolution %04dx%04d effectiveResolution %04dx%04d fmt %04x mipIndex %d", srcTexture->width, srcTexture->height, effectiveWidth_src, effectiveHeight_src, (uint32)srcTexture->format, 0); + return; + } + catchOpenGLError(); + + for (sint32 mipIndex = 0; mipIndex < mipCount; mipIndex++) + { + sint32 sliceCopyWidth = std::max(effectiveCopyWidth >> mipIndex, 1); + sint32 sliceCopyHeight = std::max(effectiveCopyHeight >> mipIndex, 1); + g_renderer->texture_copyImageSubData(srcTexture, mipIndex, 0, 0, 0, dstTexture, mipIndex, 0, 0, 0, sliceCopyWidth, sliceCopyHeight, sliceCount); + sint32 mipSliceCount = sliceCount; + if (dstTexture->Is3DTexture()) + mipSliceCount >>= mipIndex; + for (sint32 sliceIndex = 0; sliceIndex < mipSliceCount; sliceIndex++) + { + LatteTextureSliceMipInfo* srcTexSliceInfo = srcTexture->sliceMipInfo + srcTexture->GetSliceMipArrayIndex(sliceIndex, mipIndex); + LatteTextureSliceMipInfo* dstTexSliceInfo = dstTexture->sliceMipInfo + dstTexture->GetSliceMipArrayIndex(sliceIndex, mipIndex); + dstTexSliceInfo->lastDynamicUpdate = srcTexSliceInfo->lastDynamicUpdate; + } + catchOpenGLError(); + } + catchOpenGLError(); +} + +template<bool bothMustMatch> +bool LatteTexture_DoesWidthHeightMatch(Latte::E_GX2SURFFMT format1, uint32 width1, uint32 height1, Latte::E_GX2SURFFMT format2, uint32 width2, uint32 height2) +{ + if (Latte::IsCompressedFormat(format1)) + { + width1 <<= 2; + height1 <<= 2; + } + if (Latte::IsCompressedFormat(format2)) + { + width2 <<= 2; + height2 <<= 2; + } + if constexpr(bothMustMatch) + return width1 == width2 && height1 == height2; + else + return width1 == width2 || height1 == height2; +} + +void LatteTexture_CopySlice(LatteTexture* srcTexture, sint32 srcSlice, sint32 srcMip, LatteTexture* dstTexture, sint32 dstSlice, sint32 dstMip, sint32 srcX, sint32 srcY, sint32 dstX, sint32 dstY, sint32 width, sint32 height) +{ + if (srcTexture->isDepth != dstTexture->isDepth) + { + g_renderer->surfaceCopy_copySurfaceWithFormatConversion(srcTexture, srcMip, srcSlice, dstTexture, dstMip, dstSlice, width, height); + return; + } + // rescale copy size + sint32 effectiveCopyWidth = width; + sint32 effectiveCopyHeight = height; + LatteTexture_scaleToEffectiveSize(srcTexture, &effectiveCopyWidth, &effectiveCopyHeight, 0); + + sint32 effectiveSrcX = srcX; + sint32 effectiveSrcY = srcY; + LatteTexture_scaleToEffectiveSize(srcTexture, &effectiveSrcX, &effectiveSrcY, 0); + + sint32 effectiveDstX = dstX; + sint32 effectiveDstY = dstY; + LatteTexture_scaleToEffectiveSize(dstTexture, &effectiveDstX, &effectiveDstY, 0); + + // check if rescale is compatible + if (LatteTexture_doesEffectiveRescaleRatioMatch(dstTexture, 0, srcTexture, 0) == false) + { + sint32 effectiveWidth_src = srcTexture->overwriteInfo.hasResolutionOverwrite ? srcTexture->overwriteInfo.width : srcTexture->width; + sint32 effectiveHeight_src = srcTexture->overwriteInfo.hasResolutionOverwrite ? srcTexture->overwriteInfo.height : srcTexture->height; + sint32 effectiveWidth_dst = dstTexture->overwriteInfo.hasResolutionOverwrite ? dstTexture->overwriteInfo.width : dstTexture->width; + sint32 effectiveHeight_dst = dstTexture->overwriteInfo.hasResolutionOverwrite ? dstTexture->overwriteInfo.height : dstTexture->height; + if (cafeLog_isLoggingFlagEnabled(LOG_TYPE_TEXTURE_CACHE)) + { + forceLog_printf("_copySlice(): Unable to sync textures with mismatching scale ratio (due to texture rule)"); + float ratioWidth_src = (float)effectiveWidth_src / (float)srcTexture->width; + float ratioHeight_src = (float)effectiveHeight_src / (float)srcTexture->height; + float ratioWidth_dst = (float)effectiveWidth_dst / (float)dstTexture->width; + float ratioHeight_dst = (float)effectiveHeight_dst / (float)dstTexture->height; + forceLog_printf("Source: %08x origResolution %4d/%4d effectiveResolution %4d/%4d fmt %04x mipIndex %d ratioW/H: %.4f/%.4f", srcTexture->physAddress, srcTexture->width, srcTexture->height, effectiveWidth_src, effectiveHeight_src, (uint32)srcTexture->format, srcMip, ratioWidth_src, ratioHeight_src); + forceLog_printf("Destination: %08x origResolution %4d/%4d effectiveResolution %4d/%4d fmt %04x mipIndex %d ratioW/H: %.4f/%.4f", dstTexture->physAddress, dstTexture->width, dstTexture->height, effectiveWidth_dst, effectiveHeight_dst, (uint32)dstTexture->format, dstMip, ratioWidth_dst, ratioHeight_dst); + } + //forceLogDebug_printf("If these textures are not meant to share data you can ignore this"); + return; + } + // todo - store 'lastUpdated' value per slice/mip and copy it's value when copying the slice data + g_renderer->texture_copyImageSubData(srcTexture, srcMip, effectiveSrcX, effectiveSrcY, srcSlice, dstTexture, dstMip, effectiveDstX, effectiveDstY, dstSlice, effectiveCopyWidth, effectiveCopyHeight, 1); +} + +bool LatteTexture_GetSubtextureSliceAndMip(LatteTexture* baseTexture, LatteTexture* mipTexture, sint32* baseSliceIndex, sint32* baseMipIndex) +{ + LatteTextureSliceMipInfo* mipTextureSliceInfo = mipTexture->sliceMipInfo + mipTexture->GetSliceMipArrayIndex(0, 0); + // todo - this can be optimized by first determining the mip level from pitch + for (sint32 mipIndex = 0; mipIndex < baseTexture->mipLevels; mipIndex++) + { + sint32 sliceCount; + if (baseTexture->Is3DTexture()) + sliceCount = std::max(baseTexture->depth >> mipIndex, 1); + else + sliceCount = baseTexture->depth; + for (sint32 sliceIndex = 0; sliceIndex < sliceCount; sliceIndex++) + { + LatteTextureSliceMipInfo* sliceMipInfo = baseTexture->sliceMipInfo + baseTexture->GetSliceMipArrayIndex(sliceIndex, mipIndex); + if (sliceMipInfo->addrStart == mipTextureSliceInfo->addrStart && sliceMipInfo->subIndex == mipTextureSliceInfo->subIndex) + { + *baseSliceIndex = sliceIndex; + *baseMipIndex = mipIndex; + return true; + } + // todo - support overlapping textures with a non-zero y-offset + } + } + return false; +} + +// if a texture shares memory with another texture then flag those textures as invalidated (on next use, synchronize data) +void LatteTexture_MarkDynamicTextureAsChanged(LatteTextureView* textureView, sint32 sliceIndex, sint32 mipIndex, uint64 eventCounter) +{ + LatteTexture* baseTexture = textureView->baseTexture; + baseTexture->lastWriteEventCounter = eventCounter; + + sint32 aSliceIndex = textureView->firstSlice + sliceIndex; + sint32 aMipIndex = textureView->firstMip + mipIndex; + LatteTextureSliceMipInfo* baseSliceMipInfo = baseTexture->sliceMipInfo + baseTexture->GetSliceMipArrayIndex(aSliceIndex, aMipIndex); + baseSliceMipInfo->lastDynamicUpdate = eventCounter; + + LatteTexture_MarkConnectedTexturesForReloadFromDynamicTextures(textureView->baseTexture); +} + +void LatteTexture_SyncSlice(LatteTexture* srcTexture, sint32 srcSliceIndex, sint32 srcMipIndex, LatteTexture* dstTexture, sint32 dstSliceIndex, sint32 dstMipIndex) +{ + sint32 srcWidth = srcTexture->width; + sint32 srcHeight = srcTexture->height; + sint32 dstWidth = dstTexture->width; + sint32 dstHeight = dstTexture->height; + + if (srcMipIndex == 0 && dstMipIndex == 0 && (srcTexture->tileMode == Latte::E_HWTILEMODE::TM_LINEAR_ALIGNED || srcTexture->tileMode == Latte::E_HWTILEMODE::TM_1D_TILED_THIN1) && srcTexture->height > dstTexture->height && (srcTexture->height % dstTexture->height) == 0) + { + bool isMatch = srcTexture->tileMode == Latte::E_HWTILEMODE::TM_LINEAR_ALIGNED; + if (srcTexture->tileMode == Latte::E_HWTILEMODE::TM_1D_TILED_THIN1 && srcTexture->width == 32) + { + // special case for CoD BO2, where 1024x32 and 32x32x32 textures share memory + isMatch = true; + } + + if (isMatch && srcTexture->IsCompressedFormat() == false && dstTexture->IsCompressedFormat() == false) + { + sint32 virtualSlices = srcTexture->height / dstTexture->height; + if (dstTexture->depth == virtualSlices) + { + // special case for Ninja Gaiden + // it initializes a 24x24x24 texture array as a 24x576x1 2D texture (using tilemode 1) + sint32 copyWidth = std::min(srcWidth, dstWidth); + sint32 copyHeight = std::min(srcHeight, dstHeight); + for (sint32 slice = 0; slice < virtualSlices; slice++) + LatteTexture_CopySlice(srcTexture, srcSliceIndex, srcMipIndex, dstTexture, dstSliceIndex + slice, dstMipIndex, 0, slice * dstTexture->height, 0, 0, copyWidth, copyHeight); + } + return; + } + } + + bool srcIsCompressed = srcTexture->IsCompressedFormat(); + bool dstIsCompressed = dstTexture->IsCompressedFormat(); + + if (srcIsCompressed != dstIsCompressed) + { + // convert into unit of source texture + if (srcIsCompressed == false) + { + // destination compressed, source uncompressed (integer format) + dstWidth >>= 2; + dstHeight >>= 2; + } + else + { + // destination uncompressed (integer format), source compressed + dstWidth <<= 2; + dstHeight <<= 2; + } + } + + srcWidth = std::max(srcWidth >> srcMipIndex, 1); + srcHeight = std::max(srcHeight >> srcMipIndex, 1); + dstWidth = std::max(dstWidth >> dstMipIndex, 1); + dstHeight = std::max(dstHeight >> dstMipIndex, 1); + + sint32 copyWidth = std::min(srcWidth, dstWidth); + sint32 copyHeight = std::min(srcHeight, dstHeight); + + LatteTexture_CopySlice(srcTexture, srcSliceIndex, srcMipIndex, dstTexture, dstSliceIndex, dstMipIndex, 0, 0, 0, 0, copyWidth, copyHeight); + +} + +void LatteTexture_UpdateTextureFromDynamicChanges(LatteTexture* texture) +{ + // note: Currently this function assumes that only one other texture is updated per slice/mip (if multiple overlap, we should merge the one with the latest timestamp the latest of each individually) + for (auto& texRel : texture->list_compatibleRelations) + { + LatteTexture* baseTexture = texRel->baseTexture; + LatteTexture* subTexture = texRel->subTexture; + for (sint32 cMipIndex = 0; cMipIndex < texRel->mipCount; cMipIndex++) + { + sint32 mipSliceCount = texRel->sliceCount; + if (texRel->baseTexture->Is3DTexture()) + { + cemu_assert_debug(cMipIndex == 0); // values above 0 need testing + mipSliceCount >>= cMipIndex; + } + + for (sint32 cSliceIndex = 0; cSliceIndex < mipSliceCount; cSliceIndex++) + { + LatteTextureSliceMipInfo* baseSliceMipInfo = baseTexture->sliceMipInfo + baseTexture->GetSliceMipArrayIndex(texRel->baseSliceIndex + cSliceIndex, texRel->baseMipIndex + cMipIndex); + LatteTextureSliceMipInfo* subSliceMipInfo = subTexture->sliceMipInfo + subTexture->GetSliceMipArrayIndex(cSliceIndex, cMipIndex); + if (texture == baseTexture) + { + // baseTexture is target texture + if (baseSliceMipInfo->lastDynamicUpdate < subSliceMipInfo->lastDynamicUpdate) + { + LatteTexture_SyncSlice(subTexture, cSliceIndex, cMipIndex, baseTexture, texRel->baseSliceIndex + cSliceIndex, texRel->baseMipIndex + cMipIndex); + baseSliceMipInfo->lastDynamicUpdate = subSliceMipInfo->lastDynamicUpdate; + if(subTexture->isUpdatedOnGPU) + texture->isUpdatedOnGPU = true; + } + } + else + { + // subTexture is target texture + if (subSliceMipInfo->lastDynamicUpdate < baseSliceMipInfo->lastDynamicUpdate) + { + LatteTexture_SyncSlice(baseTexture, texRel->baseSliceIndex + cSliceIndex, texRel->baseMipIndex + cMipIndex, subTexture, cSliceIndex, cMipIndex); + subSliceMipInfo->lastDynamicUpdate = baseSliceMipInfo->lastDynamicUpdate; + if (baseTexture->isUpdatedOnGPU) + texture->isUpdatedOnGPU = true; + } + } + } + } + } +} + +bool _LatteTexture_IsTileModeCompatible(LatteTexture* texture1, sint32 mipIndex1, LatteTexture* texture2, sint32 mipIndex2) +{ + if (mipIndex1 == 0 && mipIndex2 == 0) + return texture1->tileMode == texture2->tileMode; + + LatteTextureSliceMipInfo* texture1SliceInfo = texture1->sliceMipInfo + texture1->GetSliceMipArrayIndex(0, mipIndex1); + LatteTextureSliceMipInfo* texture2SliceInfo = texture2->sliceMipInfo + texture2->GetSliceMipArrayIndex(0, mipIndex2); + if (texture1SliceInfo->tileMode == texture2SliceInfo->tileMode) + return true; + return false; +} + +bool __LatteTexture_IsBlockedFormatRelation(LatteTexture* texture1, LatteTexture* texture2) +{ + if (texture1->isDepth && texture2->isDepth == false) + { + // necessary for Smash? (currently our depth to color copy always converts and the depth ends up in R only) + if (texture1->format == Latte::E_GX2SURFFMT::D32_FLOAT && Latte::GetHWFormat(texture2->format) == Latte::E_HWSURFFMT::HWFMT_8_8_8_8) + return true; + } + // Vulkan has stricter rules + if (g_renderer->GetType() == RendererAPI::Vulkan) + { + // found in Smash (Wii Fit Stage) + if (texture1->format == Latte::E_GX2SURFFMT::D32_FLOAT && Latte::GetHWFormat(texture2->format) == Latte::E_HWSURFFMT::HWFMT_8_24) + return true; + } + + return false; +} + +bool LatteTexture_IsBlockedFormatRelation(LatteTexture* texture1, LatteTexture* texture2) +{ + if (__LatteTexture_IsBlockedFormatRelation(texture1, texture2)) + return true; + return __LatteTexture_IsBlockedFormatRelation(texture2, texture1); +} + +// called if two textures are known to overlap in memory +// this function then tries to figure out the details and registers the relation in texture*->list_compatibleRelations +void LatteTexture_TrackTextureRelation(LatteTexture* texture1, LatteTexture* texture2) +{ + // make sure texture 2 is always at texture 1 mip level 0 or beyond + if (texture1->physAddress > texture2->physAddress) + return LatteTexture_TrackTextureRelation(texture2, texture1); + // check if this texture relation is already tracked + cemu_assert_debug(texture1->physAddress != 0); + cemu_assert_debug(texture2->physAddress != 0); + for (auto& it : texture1->list_compatibleRelations) + { + if (it->baseTexture == texture1 && it->subTexture == texture2) + return; // association already known + } + // check for blocked format combination + if (LatteTexture_IsBlockedFormatRelation(texture1, texture2)) + return; + + if (texture1->physAddress == texture2->physAddress && false) + { + // both textures overlap at mip level 0 + cemu_assert_debug(texture1->swizzle == texture2->swizzle); + cemu_assert_debug(texture1->tileMode == texture2->tileMode); + if (LatteTexture_DoesWidthHeightMatch<false>(texture1->format, texture1->width, texture1->height, texture2->format, texture2->width, texture2->height)) + { + cemu_assert_unimplemented(); + } + } + else + { + sint32 baseSliceIndex; + sint32 baseMipIndex; + if (texture1->physAddress == texture2->physAddress) + { + baseSliceIndex = 0; + baseMipIndex = 0; + } + else + { + if (LatteTexture_GetSubtextureSliceAndMip(texture1, texture2, &baseSliceIndex, &baseMipIndex) == false) + { + return; + } + } + sint32 sharedMipLevels = 1; + // todo - support for multiple shared mip levels + // check if pitch is compatible + LatteTextureSliceMipInfo* texture1SliceInfo = texture1->sliceMipInfo + texture1->GetSliceMipArrayIndex(baseSliceIndex, baseMipIndex); + LatteTextureSliceMipInfo* texture2SliceInfo = texture2->sliceMipInfo + texture2->GetSliceMipArrayIndex(0, 0); + if (_LatteTexture_IsTileModeCompatible(texture1, baseMipIndex, texture2, 0) == false) + return; // not compatible + if (texture1SliceInfo->pitch != texture2SliceInfo->pitch) + return; // not compatible + // calculate compatible depth range + sint32 baseRemainingDepth = texture1->GetMipDepth(baseMipIndex) - baseSliceIndex; + cemu_assert_debug(baseRemainingDepth >= 0); + sint32 compatibleDepthRange = std::min(baseRemainingDepth, texture2->depth); + cemu_assert_debug(compatibleDepthRange > 0); + + // create association + LatteTextureRelation* rel = (LatteTextureRelation*)malloc(sizeof(LatteTextureRelation)); + memset(rel, 0, sizeof(LatteTextureRelation)); + rel->baseTexture = texture1; + rel->subTexture = texture2; + rel->baseMipIndex = baseMipIndex; + rel->baseSliceIndex = baseSliceIndex; + rel->mipCount = sharedMipLevels; + rel->sliceCount = compatibleDepthRange; + rel->yOffset = 0; // todo + texture1->list_compatibleRelations.push_back(rel); + texture2->list_compatibleRelations.push_back(rel); + } +} + +void LatteTexture_TrackDataOverlap(LatteTexture* texture, LatteTextureSliceMipInfo* sliceMipInfo, TexMemOccupancyEntry& occupancy) +{ + // todo - handle tile thickness and z offset + + // todo - check address range overlap + auto& occMipSliceInfo = occupancy.sliceMipInfo; + + if ((sliceMipInfo->addrEnd > occMipSliceInfo->addrStart && sliceMipInfo->addrStart < occMipSliceInfo->addrEnd) == false) + return; + + // check if this overlap is already tracked + for (auto& it : sliceMipInfo->list_dataOverlap) + { + if (it.destMipSliceInfo == occupancy.sliceMipInfo) + return; + } + // register texture->dest + LatteTextureSliceMipDataOverlap_t overlapEntry; + overlapEntry.destMipSliceInfo = occupancy.sliceMipInfo; + overlapEntry.destTexture = occupancy.sliceMipInfo->texture; + sliceMipInfo->list_dataOverlap.push_back(overlapEntry); + // register dest->texture + LatteTextureSliceMipDataOverlap_t overlapEntry2; + overlapEntry2.destMipSliceInfo = sliceMipInfo; + overlapEntry2.destTexture = sliceMipInfo->texture; + occupancy.sliceMipInfo->list_dataOverlap.push_back(overlapEntry2); +} + +void _LatteTexture_RemoveDataOverlapTracking(LatteTexture* texture, LatteTextureSliceMipInfo* sliceMipInfo, LatteTextureSliceMipDataOverlap_t& dataOverlap) +{ + LatteTexture* destTexture = dataOverlap.destTexture; + LatteTextureSliceMipInfo* destSliceMipInfo = dataOverlap.destMipSliceInfo; + // delete from dest + for (auto it = destSliceMipInfo->list_dataOverlap.begin(); it != destSliceMipInfo->list_dataOverlap.end();) + { + if (it->destTexture == texture) + it = destSliceMipInfo->list_dataOverlap.erase(it); + else if (it->destTexture == destTexture) + cemu_assert_unimplemented(); + else + it++; + } +} + +void LatteTexture_DeleteDataOverlapTracking(LatteTexture* texture, LatteTextureSliceMipInfo* sliceMipInfo) +{ + for(auto& it : sliceMipInfo->list_dataOverlap) + _LatteTexture_RemoveDataOverlapTracking(texture, sliceMipInfo, it); + sliceMipInfo->list_dataOverlap.resize(0); +} + +void LatteTexture_DeleteDataOverlapTracking(LatteTexture* texture) +{ + sint32 mipLevels = texture->mipLevels; + sint32 sliceCount = texture->depth; + for (sint32 mipIndex = 0; mipIndex < mipLevels; mipIndex++) + { + sint32 mipSliceCount; + if (texture->Is3DTexture()) + mipSliceCount = std::max(1, sliceCount >> mipIndex); + else + mipSliceCount = sliceCount; + for (sint32 sliceIndex = 0; sliceIndex < mipSliceCount; sliceIndex++) + { + LatteTextureSliceMipInfo* sliceMipInfo = texture->sliceMipInfo + texture->GetSliceMipArrayIndex(sliceIndex, mipIndex); + LatteTexture_DeleteDataOverlapTracking(texture, sliceMipInfo); + } + } +} + +void LatteTexture_GatherTextureRelations(LatteTexture* texture) +{ + for (sint32 mipIndex = 0; mipIndex < texture->mipLevels; mipIndex++) + { + sint32 mipSliceCount; + if (texture->Is3DTexture()) + mipSliceCount = std::max(1, texture->depth >> mipIndex); + else + mipSliceCount = texture->depth; + for (sint32 sliceIndex = 0; sliceIndex < mipSliceCount; sliceIndex++) + { + LatteTextureSliceMipInfo* sliceMipInfo = texture->sliceMipInfo + texture->GetSliceMipArrayIndex(sliceIndex, mipIndex); + loopItrMemOccupancyBuckets(sliceMipInfo->addrStart, sliceMipInfo->addrEnd) + { + for (auto& occupancy : list_texMemOccupancyBucket[bucketIndex]) + { + LatteTexture* itrTexture = occupancy.sliceMipInfo->texture; + if (itrTexture == texture) + continue; // ignore self + if (sliceMipInfo->addrEnd >= occupancy.addrStart && sliceMipInfo->addrStart < occupancy.addrEnd) + { + if (sliceMipInfo->addrStart == occupancy.addrStart && sliceMipInfo->subIndex == occupancy.sliceMipInfo->subIndex) + { + // overlapping with zero x/y offset + if (sliceMipInfo->pitch == occupancy.sliceMipInfo->pitch && LatteTexture_IsTexelSizeCompatibleFormat(texture->format, itrTexture->format) + && sliceMipInfo->tileMode == occupancy.sliceMipInfo->tileMode && + LatteTexture_IsFormatViewCompatible(texture->format, itrTexture->format)) + { + LatteTexture_TrackTextureRelation(texture, itrTexture); + } + else + { + // pitch not compatible or format not compatible + } + } + else + { + LatteTexture_TrackDataOverlap(texture, sliceMipInfo, occupancy); + } + } + } + } + } + } +} + +void LatteTexture_DeleteTextureRelations(LatteTexture* texture) +{ + while (texture->list_compatibleRelations.empty() == false) + { + LatteTextureRelation* rel = texture->list_compatibleRelations[0]; + rel->baseTexture->list_compatibleRelations.erase(std::find(rel->baseTexture->list_compatibleRelations.begin(), rel->baseTexture->list_compatibleRelations.end(), rel)); + rel->subTexture->list_compatibleRelations.erase(std::find(rel->subTexture->list_compatibleRelations.begin(), rel->subTexture->list_compatibleRelations.end(), rel)); + free(rel); + } + texture->list_compatibleRelations.clear(); +} + +enum VIEWCOMPATIBILITY +{ + VIEW_COMPATIBLE, // subtexture can be represented as view into base texture + VIEW_BASE_TOO_SMALL, // base texture must be extended (depth or mip levels) to fit sub texture + VIEW_NOT_COMPATIBLE, +}; + +bool IsDimensionCompatibleForView(Latte::E_DIM baseDim, Latte::E_DIM viewDim) +{ + bool incompatibleDim = false; + if (baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_2D) + ; + else if (baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_2D_ARRAY) + ; + else if (baseDim == Latte::E_DIM::DIM_CUBEMAP && viewDim == Latte::E_DIM::DIM_CUBEMAP) + ; + else if (baseDim == Latte::E_DIM::DIM_CUBEMAP && viewDim == Latte::E_DIM::DIM_2D_ARRAY) + ; + else if (baseDim == Latte::E_DIM::DIM_2D_ARRAY && viewDim == Latte::E_DIM::DIM_2D_ARRAY) + ; + else if (baseDim == Latte::E_DIM::DIM_2D_ARRAY && viewDim == Latte::E_DIM::DIM_2D) + ; + else if (baseDim == Latte::E_DIM::DIM_2D_ARRAY && viewDim == Latte::E_DIM::DIM_CUBEMAP) + ; + else if (baseDim == Latte::E_DIM::DIM_3D && viewDim == Latte::E_DIM::DIM_2D_ARRAY) + ; + else if (baseDim == Latte::E_DIM::DIM_2D_MSAA && viewDim == Latte::E_DIM::DIM_2D_MSAA) + ; + else if (baseDim == Latte::E_DIM::DIM_2D_ARRAY && viewDim == Latte::E_DIM::DIM_3D) + { + // not compatible on OpenGL + incompatibleDim = true; + } + else if (baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_2D_MSAA) + { + // not compatible + incompatibleDim = true; + } + else if (baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_1D) + { + // not compatible + incompatibleDim = true; + } + else if (baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_3D) + { + // not compatible + incompatibleDim = true; + } + else if (baseDim == Latte::E_DIM::DIM_3D && viewDim == Latte::E_DIM::DIM_2D) + { + // not compatible + incompatibleDim = true; + } + else if ((baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_CUBEMAP) || + (baseDim == Latte::E_DIM::DIM_CUBEMAP && viewDim == Latte::E_DIM::DIM_2D)) + { + // not compatible + incompatibleDim = true; + } + else if (baseDim == Latte::E_DIM::DIM_2D_MSAA && viewDim == Latte::E_DIM::DIM_2D) + { + // not compatible + incompatibleDim = true; + } + else if (baseDim == Latte::E_DIM::DIM_1D && viewDim == Latte::E_DIM::DIM_2D) + { + // not compatible (probably?) + incompatibleDim = true; + } + else + { + cemu_assert_debug(false); + incompatibleDim = true; + } + return !incompatibleDim; +} + +VIEWCOMPATIBILITY LatteTexture_CanTextureBeRepresentedAsView(LatteTexture* baseTexture, uint32 physAddr, sint32 width, sint32 height, sint32 pitch, Latte::E_DIM dimView, Latte::E_GX2SURFFMT format, bool isDepth, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, sint32& relativeMipIndex, sint32& relativeSliceIndex) +{ + relativeMipIndex = 0; + relativeSliceIndex = 0; + if (LatteTexture_IsFormatViewCompatible(baseTexture->format, format) == false) + return VIEW_NOT_COMPATIBLE; + if (baseTexture->physAddress == physAddr && baseTexture->pitch == pitch) + { + if (baseTexture->isDepth != isDepth) + return VIEW_NOT_COMPATIBLE; // depth and non-depth formats are never compatible (on OpenGL) + if (!LatteTexture_IsTexelSizeCompatibleFormat(baseTexture->format, format) || baseTexture->width != width || baseTexture->height != height) + return VIEW_NOT_COMPATIBLE; + if (!IsDimensionCompatibleForView(baseTexture->dim, dimView)) + return VIEW_NOT_COMPATIBLE; + if (baseTexture->isDepth && baseTexture->format != format) + { + // depth view with different format + forceLogDebug_printf("_createMapping(): Incompatible depth view format"); + return VIEW_NOT_COMPATIBLE; + } + + // AMD has a bug on OpenGL where it ignores the internal format of texture views when they are bound as render targets, + // as a result we cant use texture views when they have a different format + if (baseTexture->format != format) + return VIEW_NOT_COMPATIBLE; + + if ((firstMip + numMip) > baseTexture->mipLevels || (firstSlice + numSlice) > baseTexture->depth) + { + // view has more slices or mips than existing texture + return VIEW_BASE_TOO_SMALL; + + } + return VIEW_COMPATIBLE; + } + else + { + if (numMip > 1) + return VIEW_NOT_COMPATIBLE; + if (baseTexture->Is3DTexture()) + return VIEW_NOT_COMPATIBLE; // todo - add support for mapping views into 3D textures + + // if phys address or pitch differs then it might be pointing to a mip + for (sint32 m = 0; m < baseTexture->mipLevels; m++) + { + auto sliceMipInfo = baseTexture->sliceMipInfo + baseTexture->GetSliceMipArrayIndex(0, m); + // check pitch + if(sliceMipInfo->pitch != pitch) + continue; + // check all slices + if(LatteAddrLib::TM_IsThickAndMacroTiled(baseTexture->tileMode)) + continue; // todo - check only every 4th slice? + for (sint32 s=0; s<baseTexture->GetMipDepth(m); s++) + { + sliceMipInfo = baseTexture->sliceMipInfo + baseTexture->GetSliceMipArrayIndex(s, m); + if (sliceMipInfo->addrStart != physAddr || sliceMipInfo->pitch != pitch) + continue; + + if (baseTexture->isDepth != isDepth) + return VIEW_NOT_COMPATIBLE; + if (baseTexture->GetMipWidth(m) != width || baseTexture->GetMipHeight(m) != height) + return VIEW_NOT_COMPATIBLE; + if (!LatteTexture_IsTexelSizeCompatibleFormat(baseTexture->format, format) ) + return VIEW_NOT_COMPATIBLE; + + if (!IsDimensionCompatibleForView(baseTexture->dim, dimView)) + return VIEW_NOT_COMPATIBLE; + if (baseTexture->isDepth && baseTexture->format != format) + { + // depth view with different format + forceLogDebug_printf("_createMapping(): Incompatible depth view format"); + return VIEW_NOT_COMPATIBLE; + } + + // AMD has a bug on OpenGL where it ignores the internal format of texture views when they are bound as render targets, + // as a result we cant use texture views when they have a different format + if (baseTexture->format != format) + return VIEW_NOT_COMPATIBLE; + + if ((m + firstMip + numMip) > baseTexture->mipLevels || (s + firstSlice + numSlice) > baseTexture->depth) + { + relativeMipIndex = m; + relativeSliceIndex = s; + return VIEW_BASE_TOO_SMALL; + } + relativeMipIndex = m; + relativeSliceIndex = s; + return VIEW_COMPATIBLE; + } + } + } + return VIEW_NOT_COMPATIBLE; +} + +// deletes any related textures that have become redundant (aka textures that can also be represented entirely as a view into the new texture) +void LatteTexture_DeleteAbsorbedSubtextures(LatteTexture* texture) +{ + for(size_t i=0; i<texture->list_compatibleRelations.size(); i++) + { + LatteTextureRelation* textureRelation = texture->list_compatibleRelations[i]; + LatteTexture* relatedTexture = (textureRelation->baseTexture!=texture)? textureRelation->baseTexture:textureRelation->subTexture; + + sint32 relativeMipIndex; + sint32 relativeSliceIndex; + if (LatteTexture_CanTextureBeRepresentedAsView(texture, relatedTexture->physAddress, relatedTexture->width, relatedTexture->height, relatedTexture->pitch, relatedTexture->dim, relatedTexture->format, relatedTexture->isDepth, 0, relatedTexture->mipLevels, 0, relatedTexture->depth, relativeMipIndex, relativeSliceIndex) == VIEW_COMPATIBLE) + { + LatteTexture_Delete(relatedTexture); + LatteGPUState.repeatTextureInitialization = true; + } + } +} + +void LatteTexture_RecreateTextureWithDifferentMipSliceCount(LatteTexture* texture, MPTR physMipAddr, sint32 newMipCount, sint32 newDepth) +{ + Latte::E_DIM newDim = texture->dim; + if (newDim == Latte::E_DIM::DIM_2D && newDepth > 1) + newDim = Latte::E_DIM::DIM_2D_ARRAY; + else if (newDim == Latte::E_DIM::DIM_1D && newDepth > 1) + newDim = Latte::E_DIM::DIM_1D_ARRAY; + LatteTextureView* view = LatteTexture_CreateTexture(0, newDim, texture->physAddress, physMipAddr, texture->format, texture->width, texture->height, newDepth, texture->pitch, newMipCount, texture->swizzle, texture->tileMode, texture->isDepth); + cemu_assert(!(view->baseTexture->mipLevels <= 1 && physMipAddr == MPTR_NULL && newMipCount > 1)); + // copy data from old texture if its dynamically updated + if (texture->isUpdatedOnGPU) + { + LatteTexture_copyData(texture, view->baseTexture, texture->mipLevels, texture->depth); + view->baseTexture->isUpdatedOnGPU = true; + } + // remove old texture + LatteTexture_Delete(texture); + // gather texture relations for new texture + LatteTexture_GatherTextureRelations(view->baseTexture); + LatteTexture_UpdateTextureFromDynamicChanges(view->baseTexture); + // todo - inherit 'isUpdatedOnGPU' flag for each mip/slice + + // delete any individual smaller slices/mips that have become redundant + LatteTexture_DeleteAbsorbedSubtextures(view->baseTexture); +} + +// create new texture representation +// if allowCreateNewDataTexture is true, a new texture will be created if necessary. If it is false, only existing textures may be used, except if a data-compatible version of the requested texture already exists and it's not view compatible +LatteTextureView* LatteTexture_CreateMapping(MPTR physAddr, MPTR physMipAddr, sint32 width, sint32 height, sint32 depth, sint32 pitch, Latte::E_HWTILEMODE tileMode, uint32 swizzle, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, Latte::E_GX2SURFFMT format, Latte::E_DIM dimBase, Latte::E_DIM dimView, bool isDepth, bool allowCreateNewDataTexture) +{ + if (format == Latte::E_GX2SURFFMT::INVALID_FORMAT) + { + forceLogDebug_printf("LatteTexture_CreateMapping(): Invalid format"); + return nullptr; + } + // note: When creating an existing texture, we only allow mip and slice expansion at the end + cemu_assert_debug(depth); + + cemu_assert_debug(!(depth > 1 && dimBase == Latte::E_DIM::DIM_2D)); + cemu_assert_debug(!(numSlice > 1 && dimView == Latte::E_DIM::DIM_2D)); + // todo, depth and numSlice are redundant + + sint32 sliceCount = firstSlice + numSlice; + std::vector<LatteTexture*> list_overlappingTextures; + for (sint32 sliceIndex = 0; sliceIndex < sliceCount; sliceIndex++) + { + sint32 mipIndex = 0; + uint32 calcSliceAddrStart; + uint32 calcSliceSize; + sint32 calcSubSliceIndex; + LatteAddrLib::CalculateMipAndSliceAddr(physAddr, physMipAddr, format, width, height, depth, dimBase, tileMode, swizzle, 0, mipIndex, sliceIndex, &calcSliceAddrStart, &calcSliceSize, &calcSubSliceIndex); + uint32 calcSliceAddrEnd = calcSliceAddrStart + calcSliceSize; + // attempt to create view in already existing texture first (we may have to recreate the texture with new specifications) + loopItrMemOccupancyBuckets(calcSliceAddrStart, calcSliceAddrEnd) + { + for (auto& occupancy : list_texMemOccupancyBucket[bucketIndex]) + { + if (calcSliceAddrEnd >= occupancy.addrStart && calcSliceAddrStart < occupancy.addrEnd) + { + if (calcSliceAddrStart == occupancy.addrStart) + { + // overlapping with zero x/y offset + if (std::find(list_overlappingTextures.begin(), list_overlappingTextures.end(), occupancy.sliceMipInfo->texture) == list_overlappingTextures.end()) + { + list_overlappingTextures.push_back(occupancy.sliceMipInfo->texture); + } + } + else + { + // overlapping but not matching directly + // todo - check if they match with a y offset + } + } + } + } + } + // try to merge textures if possible + for (auto& tex : list_overlappingTextures) + { + sint32 relativeMipIndex; + sint32 relativeSliceIndex; + VIEWCOMPATIBILITY viewCompatibility = LatteTexture_CanTextureBeRepresentedAsView(tex, physAddr, width, height, pitch, dimView, format, isDepth, firstMip, numMip, firstSlice, numSlice, relativeMipIndex, relativeSliceIndex); + if (viewCompatibility == VIEW_NOT_COMPATIBLE) + { + allowCreateNewDataTexture = true; + continue; + } + if (viewCompatibility == VIEW_BASE_TOO_SMALL) + { + if (relativeMipIndex != 0 || relativeSliceIndex != 0) + { + // not yet supported + allowCreateNewDataTexture = true; + continue; + } + // new mapping has more slices/mips than known texture -> expand texture + sint32 newDepth = std::max(relativeSliceIndex + firstSlice + numSlice, std::max(depth, tex->depth)); + sint32 newMipCount = std::max(relativeMipIndex + firstMip + numMip, tex->mipLevels); + uint32 newPhysMipAddr; + if ((relativeMipIndex + firstMip + numMip) > 1) + { + newPhysMipAddr = physMipAddr; + } + else + { + newPhysMipAddr = tex->physMipAddress; + } + LatteTexture_RecreateTextureWithDifferentMipSliceCount(tex, newPhysMipAddr, newMipCount, newDepth); + return LatteTexture_CreateMapping(physAddr, physMipAddr, width, height, depth, pitch, tileMode, swizzle, firstMip, numMip, firstSlice, numSlice, format, dimBase, dimView, isDepth); + } + else if(viewCompatibility == VIEW_COMPATIBLE) + { + LatteTextureView* view = tex->GetOrCreateView(dimView, format, relativeMipIndex + firstMip, numMip, relativeSliceIndex + firstSlice, numSlice); + if (relativeMipIndex != 0 || relativeSliceIndex != 0) + { + // for accesses to mips/slices using a physAddress offset we manually need to create a new view lookup + // by default views only create a lookup for the base texture physAddress + view->CreateLookupForSubTexture(relativeMipIndex, relativeSliceIndex); +#ifndef PUBLIC_RELEASE + LatteTextureView* testView = LatteTextureViewLookupCache::lookup(physAddr, width, height, depth, pitch, firstMip, numMip, firstSlice, numSlice, format, dimView); + cemu_assert(testView); +#endif + } + return view; + } + else + { + cemu_assert_debug(false); + } + } + // create new texture + if (allowCreateNewDataTexture == false) + return nullptr; + LatteTextureView* view = LatteTexture_CreateTexture(0, dimBase, physAddr, physMipAddr, format, width, height, depth, pitch, firstMip + numMip, swizzle, tileMode, isDepth); + LatteTexture_GatherTextureRelations(view->baseTexture); + LatteTexture_UpdateTextureFromDynamicChanges(view->baseTexture); + // delete any individual smaller slices/mips that have become redundant + LatteTexture_DeleteAbsorbedSubtextures(view->baseTexture); + return view; +} + +LatteTextureView* LatteTC_LookupTextureByData(MPTR physAddr, sint32 width, sint32 height, sint32 pitch, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, sint32* searchIndex) +{ + cemu_assert_debug(firstMip == 0); + sint32 cSearchIndex = 0; + loopItrMemOccupancyBuckets(physAddr, physAddr+1) + { + auto& bucket = list_texMemOccupancyBucket[bucketIndex]; + for (sint32 i = 0; i < bucket.size(); i++) + { + if (bucket[i].addrStart == physAddr) + { + LatteTexture* tex = bucket[i].sliceMipInfo->texture; + if (tex->physAddress == physAddr && tex->pitch == pitch) + { + if (firstSlice >= 0 && firstSlice < (tex->depth)) + { + if (cSearchIndex >= *searchIndex) + { + (*searchIndex)++; + return tex->baseView; + } + cSearchIndex++; + } + } + } + } + } + + return nullptr; +} + +void LatteTC_LookupTexturesByPhysAddr(MPTR physAddr, std::vector<LatteTexture*>& list_textures) +{ + sint32 cSearchIndex = 0; + loopItrMemOccupancyBuckets(physAddr, physAddr + 1) + { + for (sint32 i = 0; i < list_texMemOccupancyBucket[bucketIndex].size(); i++) + { + if (list_texMemOccupancyBucket[bucketIndex][i].addrStart == physAddr) + { + LatteTexture* tex = list_texMemOccupancyBucket[bucketIndex][i].sliceMipInfo->texture; + if (tex->physAddress == physAddr) + { + vectorAppendUnique(list_textures, tex); + } + } + } + } +} + +LatteTextureView* LatteTC_GetTextureSliceViewOrTryCreate(MPTR srcImagePtr, MPTR srcMipPtr, Latte::E_GX2SURFFMT srcFormat, Latte::E_HWTILEMODE srcTileMode, uint32 srcWidth, uint32 srcHeight, uint32 srcDepth, uint32 srcPitch, uint32 srcSwizzle, uint32 srcSlice, uint32 srcMip, const bool requireExactResolution) +{ + LatteTextureView* sourceView; + if(requireExactResolution == false) + sourceView = LatteTextureViewLookupCache::lookupSliceMinSize(srcImagePtr, srcWidth, srcHeight, srcPitch, srcMip, srcSlice, srcFormat); + else + sourceView = LatteTextureViewLookupCache::lookupSlice(srcImagePtr, srcWidth, srcHeight, srcPitch, srcMip, srcSlice, srcFormat); + if (sourceView) + return sourceView; + return LatteTexture_CreateMapping(srcImagePtr, srcMipPtr, srcWidth, srcHeight, srcDepth, srcPitch, srcTileMode, srcSwizzle, srcMip, 1, srcSlice, 1, srcFormat, srcDepth > 1 ? Latte::E_DIM::DIM_2D_ARRAY : Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, false, false); +} + +void LatteTexture_UpdateDataToLatest(LatteTexture* texture) +{ + if (LatteTC_HasTextureChanged(texture)) + { + g_renderer->texture_rememberBoundTexture(0); + g_renderer->texture_bindAndActivateRawTex(texture, 0); + LatteTexture_ReloadData(texture, 0); + g_renderer->texture_restoreBoundTexture(0); + } + if (texture->reloadFromDynamicTextures) + { + LatteTexture_UpdateCacheFromDynamicTextures(texture); + texture->reloadFromDynamicTextures = false; + } +} + +LatteTextureSliceMipInfo* LatteTexture::GetSliceMipArrayEntry(sint32 sliceIndex, sint32 mipIndex) +{ + return sliceMipInfo + GetSliceMipArrayIndex(sliceIndex, mipIndex); +} + +std::vector<LatteTexture*> sAllTextures; // entries can be nullptr +std::vector<size_t> sAllTextureFreeIndices; + +void _AddTextureToGlobalList(LatteTexture* tex) +{ + if (sAllTextureFreeIndices.empty()) + { + tex->globalListIndex = sAllTextures.size(); + sAllTextures.emplace_back(tex); + return; + } + size_t index = sAllTextureFreeIndices.back(); + sAllTextureFreeIndices.pop_back(); + sAllTextures[index] = tex; + tex->globalListIndex = index; +} + +void _RemoveTextureFromGlobalList(LatteTexture* tex) +{ + cemu_assert_debug(tex->globalListIndex >= 0 && tex->globalListIndex < sAllTextures.size()); + cemu_assert_debug(sAllTextures[tex->globalListIndex] == tex); + if (tex->globalListIndex + 1 == sAllTextures.size()) + { + // if the index is at the end, make the list smaller instead of freeing the index + sAllTextures.pop_back(); + return; + } + sAllTextures[tex->globalListIndex] = nullptr; + sAllTextureFreeIndices.emplace_back(tex->globalListIndex); +} + +std::vector<LatteTexture*>& LatteTexture::GetAllTextures() +{ + return sAllTextures; +} + +LatteTexture::LatteTexture(uint32 textureUnit, Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, uint32 swizzle, + Latte::E_HWTILEMODE tileMode, bool isDepth) +{ + _AddTextureToGlobalList(this); + if (depth < 1) + depth = 1; + // setup texture object + this->physAddress = physAddress; + this->dim = dim; + this->format = format; + this->width = width; + this->height = height; + this->depth = depth; + this->swizzle = swizzle; + this->pitch = pitch; + this->mipLevels = mipLevels; + this->tileMode = tileMode; + this->isDepth = isDepth; + this->physMipAddress = physMipAddress; + this->lastUpdateEventCounter = LatteTexture_getNextUpdateEventCounter(); + this->lastWriteEventCounter = LatteTexture_getNextUpdateEventCounter(); + + // handle graphic pack overwrite rules + for (const auto& gp : GraphicPack2::GetActiveGraphicPacks()) + { + for (const auto& rule : gp->GetTextureRules()) + { + if (!rule.filter_settings.format_whitelist.empty() && std::find(rule.filter_settings.format_whitelist.begin(), rule.filter_settings.format_whitelist.end(), (uint32)format) == rule.filter_settings.format_whitelist.end()) + continue; + + if (!rule.filter_settings.format_blacklist.empty() && std::find(rule.filter_settings.format_blacklist.begin(), rule.filter_settings.format_blacklist.end(), (uint32)format) != rule.filter_settings.format_blacklist.end()) + continue; + + if (!rule.filter_settings.tilemode_whitelist.empty() && std::find(rule.filter_settings.tilemode_whitelist.begin(), rule.filter_settings.tilemode_whitelist.end(), (int)tileMode) == rule.filter_settings.tilemode_whitelist.end()) + continue; + + if (!rule.filter_settings.tilemode_blacklist.empty() && std::find(rule.filter_settings.tilemode_blacklist.begin(), rule.filter_settings.tilemode_blacklist.end(), (int)tileMode) != rule.filter_settings.tilemode_blacklist.end()) + continue; + + if (rule.filter_settings.width != -1 && rule.filter_settings.width != width) + continue; + if (rule.filter_settings.height != -1 && rule.filter_settings.height != height) + continue; + if (rule.filter_settings.depth != -1 && rule.filter_settings.depth != depth) + continue; + if (rule.filter_settings.inMEM1 == GraphicPack2::TextureRule::FILTER_SETTINGS::MEM1_FILTER::OUTSIDE && mmuRange_MEM1.containsAddress(this->physAddress)) + continue; + if (rule.filter_settings.inMEM1 == GraphicPack2::TextureRule::FILTER_SETTINGS::MEM1_FILTER::INSIDE && !mmuRange_MEM1.containsAddress(this->physAddress)) + continue; + + this->overwriteInfo.width = width; + this->overwriteInfo.height = height; + this->overwriteInfo.depth = depth; + + if (rule.overwrite_settings.width != -1) + { + this->overwriteInfo.hasResolutionOverwrite = true; + this->overwriteInfo.width = rule.overwrite_settings.width; + } + if (rule.overwrite_settings.height != -1) + { + this->overwriteInfo.hasResolutionOverwrite = true; + this->overwriteInfo.height = rule.overwrite_settings.height; + } + if (rule.overwrite_settings.depth != -1) + { + this->overwriteInfo.hasResolutionOverwrite = true; + this->overwriteInfo.depth = rule.overwrite_settings.depth; + } + if (rule.overwrite_settings.format != -1) + { + this->overwriteInfo.hasFormatOverwrite = true; + this->overwriteInfo.format = rule.overwrite_settings.format; + } + if (rule.overwrite_settings.lod_bias != -1) + { + this->overwriteInfo.hasLodBias = true; + this->overwriteInfo.lodBias = rule.overwrite_settings.lod_bias; + } + if (rule.overwrite_settings.relative_lod_bias != -1) + { + this->overwriteInfo.hasRelativeLodBias = true; + this->overwriteInfo.relativeLodBias = rule.overwrite_settings.relative_lod_bias; + } + if (rule.overwrite_settings.anistropic_value != -1) + { + this->overwriteInfo.anisotropicLevel = rule.overwrite_settings.anistropic_value; + } + } + } + // determine if this texture should ever be mirrored to CPU RAM + if (this->tileMode == Latte::E_HWTILEMODE::TM_LINEAR_ALIGNED) + { + this->enableReadback = true; + } +} + +LatteTexture::~LatteTexture() +{ + _RemoveTextureFromGlobalList(this); + cemu_assert_debug(baseView == nullptr); + cemu_assert_debug(views.empty()); +}; + +// sync texture data between overlapping textures +void LatteTexture_UpdateCacheFromDynamicTextures(LatteTexture* textureDest) +{ + LatteTexture_UpdateTextureFromDynamicChanges(textureDest); +} + +void LatteTexture_MarkConnectedTexturesForReloadFromDynamicTextures(LatteTexture* texture) +{ + for (auto& it : texture->list_compatibleRelations) + { + if (texture == it->baseTexture) + it->subTexture->reloadFromDynamicTextures = true; + else + it->baseTexture->reloadFromDynamicTextures = true; + } +} + +void LatteTexture_TrackTextureGPUWrite(LatteTexture* texture, uint32 slice, uint32 mip, uint64 eventCounter) +{ + LatteTexture_MarkDynamicTextureAsChanged(texture->baseView, slice, mip, eventCounter); + LatteTC_ResetTextureChangeTracker(texture); + texture->isUpdatedOnGPU = true; + texture->lastUnflushedRTDrawcallIndex = LatteGPUState.drawCallCounter; +} diff --git a/src/Cafe/HW/Latte/Core/LatteTexture.h b/src/Cafe/HW/Latte/Core/LatteTexture.h new file mode 100644 index 00000000..6cdc528e --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteTexture.h @@ -0,0 +1,343 @@ +#pragma once +#include "Cafe/HW/Latte/Core/LatteConst.h" + +struct LatteSamplerState +{ + uint8 clampS; + uint8 clampT; + uint8 clampR; + uint32 filterMag; // openGL constant + uint32 filterMin; // openGL constant + uint8 maxAniso; + uint8 maxMipLevels; + uint8 depthCompareMode; + uint8 depthCompareFunc; + uint16 minLod; + uint16 maxLod; + sint16 lodBias; + uint8 borderType; + float borderColor[4]; +}; + +#include "Cafe/HW/Latte/Core/LatteTextureView.h" + +class LatteTexture +{ +public: + LatteTexture(uint32 textureUnit, Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, uint32 swizzle, Latte::E_HWTILEMODE tileMode, bool isDepth); + virtual ~LatteTexture(); + + virtual void InitTextureState() {}; + + LatteTextureView* GetOrCreateView(Latte::E_DIM dim, Latte::E_GX2SURFFMT format, sint32 firstMip, sint32 mipCount, sint32 firstSlice, sint32 sliceCount) + { + for (auto& itr : views) + { + if (itr->firstMip == firstMip && itr->numMip == mipCount && itr->firstSlice == firstSlice && itr->numSlice == sliceCount && + itr->dim == dim && itr->format == format) + return itr; + } + return CreateView(dim, format, firstMip, mipCount, firstSlice, sliceCount); + } + + LatteTextureView* GetOrCreateView(sint32 firstMip, sint32 mipCount, sint32 firstSlice, sint32 sliceCount) + { + return GetOrCreateView(this->dim, this->format, firstMip, mipCount, firstSlice, sliceCount); + } + + bool IsCompressedFormat() const + { + return Latte::IsCompressedFormat(format); + } + + uint32 GetBPP() const + { + return Latte::GetFormatBits(format); + } + + bool Is3DTexture() const { return dim == Latte::E_DIM::DIM_3D; }; + + sint32 GetMipDepth(sint32 mipIndex) + { + cemu_assert_debug(mipIndex >= 0 && mipIndex < this->mipLevels); + if (Is3DTexture()) + return std::max(depth >> mipIndex, 1); + return depth; + } + + sint32 GetMipWidth(sint32 mipIndex) + { + cemu_assert_debug(mipIndex >= 0 && mipIndex < this->mipLevels); + return std::max(width >> mipIndex, 1); + } + + sint32 GetMipHeight(sint32 mipIndex) + { + cemu_assert_debug(mipIndex >= 0 && mipIndex < this->mipLevels); + return std::max(height >> mipIndex, 1); + } + + // return the size necessary for a 1D array to store one entry per slice/mip combo (using getSliceMipArrayIndex) + sint32 GetSliceMipArraySize() + { + cemu_assert_debug(this->depth > 0); + cemu_assert_debug(this->mipLevels > 0); + return this->depth * this->mipLevels; + } + + // calculate index within slice/mip array + sint32 GetSliceMipArrayIndex(sint32 sliceIndex, sint32 mipIndex) + { + cemu_assert_debug(sliceIndex < depth); + cemu_assert_debug(mipIndex < mipLevels); + // to keep the computation fast, we ignore that 3D textures have decreasing slice count per mip and leave some indices empty + return this->depth * mipIndex + sliceIndex; + } + + struct LatteTextureSliceMipInfo* GetSliceMipArrayEntry(sint32 sliceIndex, sint32 mipIndex); + static std::vector<LatteTexture*>& GetAllTextures(); // note: can contain nullptr entries + +protected: + virtual LatteTextureView* CreateView(Latte::E_DIM dim, Latte::E_GX2SURFFMT format, sint32 firstMip, sint32 mipCount, sint32 firstSlice, sint32 sliceCount) = 0; + +public: + + // Latte texture info + MPTR physAddress; + MPTR physMipAddress; + Latte::E_DIM dim; + Latte::E_GX2SURFFMT format; + Latte::E_HWTILEMODE tileMode; + sint32 width; + sint32 height; + sint32 depth; + sint32 pitch; + sint32 mipLevels; // number of used mip levels (must be at least 1) + sint32 maxPossibleMipLevels{}; // number of mips that can potentially be used (uses resolution from texture rules) + uint32 swizzle; + uint32 lastRenderTargetSwizzle{}; // set to rt swizzle bits whenever the texture is being rendered to + // data info + bool isDataDefined{}; + bool isDepth; + bool hasStencil{}; // for depth textures + // info per mip/slice + struct LatteTextureSliceMipInfo* sliceMipInfo{}; + // physical offsets for start and end of data (calculated on texture load) + MPTR texDataPtrLow{}; + MPTR texDataPtrHigh{}; + uint32 texDataHash2{}; + // state + bool isUpdatedOnGPU{ false }; // set if any GPU-side operation modified this texture and strict one-way RAM->VRAM memory mirroring no longer applies + bool enableReadback{ false }; // if true, texture will be mirrored back to CPU RAM under specific circumstances + // invalidation + bool forceInvalidate{}; + // cache control + bool reloadFromDynamicTextures{}; + // last update (dynamic) + uint64 lastWriteEventCounter; + uint64 lastUpdateEventCounter; + uint32 lastUpdateFrameCounter{}; + uint32 reloadCount{}; + // last update (from RAM data) + uint32 lastDataUpdateFrameCounter{}; + // optimization + bool useLightHash{}; + // overwrite info + struct + { + // resolution + bool hasResolutionOverwrite; + sint32 width; + sint32 height; + sint32 depth; + // format + bool hasFormatOverwrite; + sint32 format; + // lod bias + sint16 lodBias; // in 1/64th steps + bool hasLodBias; + // relative lod bias + sint16 relativeLodBias; // in 1/64th steps + bool hasRelativeLodBias; + // anisotropic + sint8 anisotropicLevel{ -1 }; // 1<<n, 0 is disabled + }overwriteInfo{}; + // for detecting multiple references during the same drawcall + uint32 lockTextureUpdateId{}; // set to current texture update id whenever texture is bound and used + uint32 lockCount{}; + // usage + uint32 lastAccessTick{}; + uint32 lastAccessFrameCount{}; + // detection of render feedback loops (see OpenGL 4.5 spec, 9.3) + uint32 lastUnflushedRTDrawcallIndex{}; + // views + LatteTextureView* baseView{}; + std::vector<LatteTextureView*> views; // list of all views created for this texture, including baseView + // texture relations (overlapping memory) + std::vector<struct LatteTextureRelation*> list_compatibleRelations; // list of other data-compatible textures that share the same memory ranges + // index in global texture list + size_t globalListIndex; +}; + +struct LatteTextureDefinition // todo - actually use this in LatteTexture class +{ + MPTR physAddress; + MPTR physMipAddress; + Latte::E_DIM dim; + Latte::E_GX2SURFFMT format; + uint32 width; + uint32 height; + uint32 depth; + uint32 pitch; + uint32 mipLevels; + uint32 swizzle; + Latte::E_HWTILEMODE tileMode; + bool isDepth; + + LatteTextureDefinition() = default; + LatteTextureDefinition(const LatteTexture* tex) + { + physAddress = tex->physAddress; + physMipAddress = tex->physMipAddress; + dim = tex->dim; + format = tex->format; + width = tex->width; + height = tex->height; + depth = tex->depth; + pitch = tex->pitch; + mipLevels = tex->mipLevels; + swizzle = tex->swizzle; + tileMode = tex->tileMode; + isDepth = tex->isDepth; + } +}; + +class LatteTextureView; + +struct LatteTextureSliceMipDataOverlap_t +{ + LatteTexture* destTexture; + struct LatteTextureSliceMipInfo* destMipSliceInfo; +}; + +struct LatteTextureSliceMipInfo +{ + uint32 addrStart; // same as physAddr if this mip were it's own texture + uint32 addrEnd; + uint32 subIndex; // for thick tiles, multiple (4) slices can be interleaved into the same data range. This stores the index + LatteTexture* texture; + sint32 sliceIndex; + sint32 mipIndex; + // change tracking + uint32 dataChecksum; + uint64 lastDynamicUpdate; + // format info + Latte::E_HWTILEMODE tileMode; + sint32 pitch; + // data overlap tracking + uint32 estDataAddrStart; + uint32 estDataAddrEnd; + std::vector<LatteTextureSliceMipDataOverlap_t> list_dataOverlap; +}; + +struct LatteTextureRelation +{ + LatteTexture* baseTexture; + LatteTexture* subTexture; // texture which is contained within baseTexture + sint32 baseSliceIndex; + sint32 baseMipIndex; + sint32 sliceCount; + sint32 mipCount; + //sint32 xOffset; + sint32 yOffset; +}; + +// used if external modules want to retrieve texture cache information +struct LatteTextureViewInformation +{ + MPTR physAddress; + MPTR physMipAddress; + sint32 width; + sint32 height; + sint32 pitch; + sint32 firstMip; + sint32 numMip; + sint32 firstSlice; + sint32 numSlice; + Latte::E_GX2SURFFMT format; + Latte::E_DIM dim; +}; + +struct LatteTextureInformation +{ + MPTR physAddress; + MPTR physMipAddress; + sint32 width; + sint32 height; + sint32 depth; + sint32 pitch; + sint32 mipLevels; + Latte::E_GX2SURFFMT format; + bool isDepth; + Latte::E_DIM dim; + Latte::E_HWTILEMODE tileMode; + // access + uint32 lastAccessTick; + uint32 lastAccessFrameCount; + bool isUpdatedOnGPU; + // misc + uint32 alternativeViewCount; + // overwrite info + struct + { + // resolution + bool hasResolutionOverwrite; + sint32 width; + sint32 height; + sint32 depth; + }overwriteInfo{}; + + std::vector<LatteTextureViewInformation> views; +}; + +void LatteTexture_init(); +void LatteTexture_updateTextures(); + +std::vector<LatteTextureInformation> LatteTexture_QueryCacheInfo(); + +float* LatteTexture_getEffectiveTextureScale(LatteConst::ShaderType shaderType, sint32 texUnit); + +LatteTextureView* LatteTexture_CreateTexture(uint32 textureUnit, Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, uint32 swizzle, Latte::E_HWTILEMODE tileMode, bool isDepth); +void LatteTexture_Delete(LatteTexture* texture); + +void LatteTextureLoader_writeReadbackTextureToMemory(LatteTextureDefinition* textureData, uint32 sliceIndex, uint32 mipIndex, uint8* linearPixelData); + +void LatteTexture_getSize(LatteTexture* texture, sint32* width, sint32* height, sint32* depth, sint32 mipLevel); +void LatteTexture_getEffectiveSize(LatteTexture* texture, sint32* effectiveWidth, sint32* effectiveHeight, sint32* effectiveDepth, sint32 mipLevel = 0); +sint32 LatteTexture_getEffectiveWidth(LatteTexture* texture); +bool LatteTexture_doesEffectiveRescaleRatioMatch(LatteTexture* texture1, sint32 mipLevel1, LatteTexture* texture2, sint32 mipLevel2); +void LatteTexture_scaleToEffectiveSize(LatteTexture* texture, sint32* x, sint32* y, sint32 mipLevel); +uint64 LatteTexture_getNextUpdateEventCounter(); + +void LatteTexture_UpdateCacheFromDynamicTextures(LatteTexture* texture); +void LatteTexture_MarkConnectedTexturesForReloadFromDynamicTextures(LatteTexture* texture); +void LatteTexture_TrackTextureGPUWrite(LatteTexture* texture, uint32 slice, uint32 mip, uint64 eventCounter); + +void LatteTexture_InitSliceAndMipInfo(LatteTexture* texture); +void LatteTexture_RegisterTextureMemoryOccupancy(LatteTexture* texture); +void LatteTexture_UnregisterTextureMemoryOccupancy(LatteTexture* texture); + +void LatteTexture_DeleteTextureRelations(LatteTexture* texture); +void LatteTexture_DeleteDataOverlapTracking(LatteTexture* texture); + +LatteTextureView* LatteTexture_CreateMapping(MPTR physAddr, MPTR physMipAddr, sint32 width, sint32 height, sint32 depth, sint32 pitch, Latte::E_HWTILEMODE tileMode, uint32 swizzle, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, Latte::E_GX2SURFFMT format, Latte::E_DIM dimBase, Latte::E_DIM dimView, bool isDepth, bool allowCreateNewDataTexture = true); + +LatteTextureView* LatteTC_LookupTextureByData(MPTR physAddr, sint32 width, sint32 height, sint32 pitch, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, sint32* searchIndex); +void LatteTC_LookupTexturesByPhysAddr(MPTR physAddr, std::vector<LatteTexture*>& list_textures); + +LatteTextureView* LatteTC_GetTextureSliceViewOrTryCreate(MPTR srcImagePtr, MPTR srcMipPtr, Latte::E_GX2SURFFMT srcFormat, Latte::E_HWTILEMODE srcTileMode, uint32 srcWidth, uint32 srcHeight, uint32 srcDepth, uint32 srcPitch, uint32 srcSwizzle, uint32 srcSlice, uint32 srcMip, const bool requireExactResolution = false); + +void LatteTexture_MarkDynamicTextureAsChanged(LatteTextureView* textureView, sint32 sliceIndex, sint32 mipIndex, uint64 eventCounter); +void LatteTexture_UpdateTextureFromDynamicChanges(LatteTexture* texture); + +void LatteTexture_UpdateDataToLatest(LatteTexture* texture); \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Core/LatteTextureCache.cpp b/src/Cafe/HW/Latte/Core/LatteTextureCache.cpp new file mode 100644 index 00000000..ae7ac4d0 --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteTextureCache.cpp @@ -0,0 +1,436 @@ +#include "Cafe/HW/Latte/Core/Latte.h" +#include "Cafe/HW/Latte/Core/LatteDraw.h" +#include "Cafe/HW/Latte/Core/LatteTexture.h" +#include "Cafe/HW/Latte/Renderer/Renderer.h" + +std::unordered_set<LatteTexture*> g_allTextures; + +void LatteTC_Init() +{ + cemu_assert_debug(g_allTextures.empty()); +} + +void LatteTC_RegisterTexture(LatteTexture* tex) +{ + g_allTextures.emplace(tex); +} + +void LatteTC_UnregisterTexture(LatteTexture* tex) +{ + g_allTextures.erase(tex); +} + +// sample few uint64s uniformly over memory range +uint32 _quickStochasticHash(void* texData, uint32 memRange) +{ + uint64* texDataU64 = (uint64*)texData; + + uint64 hashVal = 0; + memRange /= sizeof(uint64); + + uint32 memStep = memRange / 37; // use prime here to avoid memStep aligning nicely with pitch of texture, leading to sampling only along the border of a texture + for (sint32 i = 0; i < 37; i++) + { + hashVal += *texDataU64; + hashVal = (hashVal << 3) | (hashVal >> 61); + texDataU64 += memStep; + } + return (uint32)hashVal ^ (uint32)(hashVal >> 32); +} + +uint32 LatteTexture_CalculateTextureDataHash(LatteTexture* hostTexture) +{ + if( hostTexture->texDataPtrHigh == hostTexture->texDataPtrLow ) + { + return 0; + } + if (hostTexture->format == Latte::E_GX2SURFFMT::R11_G11_B10_FLOAT) + { + // this is an exotic format that usually isn't generated or updated CPU-side + // therefore as an optimization we can risk to only check a minimal amount of bytes at the beginning of the texture data + // updates which change the entire texture should still be detected this way + // this also helps with a bug in BotW which seems to fill the empty areas of the textures with other data which causes unnecessary invalidations and texture reloads + + // Wonderful 101 generates this format in a 8x8x8 3D texture using tiling aperture + if (hostTexture->tileMode == Latte::E_HWTILEMODE::TM_1D_TILED_THICK && hostTexture->depth == 8 && hostTexture->width == 8 && hostTexture->height == 8) + { + // special case for Wonderful 101 + uint32* texDataU32 = (uint32*)memory_getPointerFromPhysicalOffset(hostTexture->texDataPtrLow); + return texDataU32[0] ^ texDataU32[0x100/4] ^ texDataU32[0x200/4] ^ texDataU32[0x300/4]; // check the first thick slice (each slice has 0x400 bytes, with 0x100 bytes between layers) + } + uint32* texDataU32 = (uint32*)memory_getPointerFromPhysicalOffset(hostTexture->texDataPtrLow); + return texDataU32[0] ^ texDataU32[1] ^ texDataU32[2] ^ texDataU32[3]; + } + + uint32 memRange = hostTexture->texDataPtrHigh - hostTexture->texDataPtrLow; + uint32* texDataU32 = (uint32*)memory_getPointerFromPhysicalOffset(hostTexture->texDataPtrLow); + uint32 hashVal = 0; + uint32 pixelCount = hostTexture->width*hostTexture->height; + + bool isCompressedFormat = hostTexture->IsCompressedFormat(); + if (isCompressedFormat || hostTexture->useLightHash) + { + // check only 32 samples of the texture + if (memRange < 256) + { + memRange /= sizeof(uint32); + while (memRange--) + { + hashVal += *texDataU32; + hashVal = (hashVal << 3) | (hashVal >> 29); + texDataU32++; + } + } + else + { + hashVal = _quickStochasticHash(texDataU32, memRange); + } + return hashVal; + } + + + if( pixelCount <= (700*700) ) + { + // small texture size + bool isCompressedFormat = hostTexture->IsCompressedFormat(); + if( isCompressedFormat == false || memRange < 0x200 ) + { + memRange /= (4*sizeof(uint32)); + while( memRange-- ) + { + hashVal += *texDataU32; + hashVal = (hashVal<<3)|(hashVal>>29); + texDataU32 += 4; + } + } + else + { + memRange /= (32*sizeof(uint32)); + while( memRange-- ) + { + hashVal += *texDataU32; + hashVal = (hashVal<<3)|(hashVal>>29); + texDataU32 += 32; + } + } + } + else if( pixelCount <= (1200*1200) ) + { + // medium texture size + bool isCompressedFormat = hostTexture->IsCompressedFormat(); + if( isCompressedFormat == false ) + { + memRange /= (12*sizeof(uint32)); + while( memRange-- ) + { + hashVal += *texDataU32; + hashVal = (hashVal<<3)|(hashVal>>29); + texDataU32 += 12; + } + } + else + { + memRange /= (96*sizeof(uint32)); + while( memRange-- ) + { + hashVal += *texDataU32; + hashVal = (hashVal<<3)|(hashVal>>29); + texDataU32 += 96; + } + } + } + else + { + // huge texture size + bool isCompressedFormat = hostTexture->IsCompressedFormat(); + if( isCompressedFormat == false ) + { + #if BOOST_OS_WINDOWS > 0 + if (_cpuExtension_AVX2) + { + __m256i h256 = { 0 }; + __m256i* readPtr = (__m256i*)texDataU32; + memRange /= (288); + while (memRange--) + { + __m256i temp = _mm256_load_si256(readPtr); + readPtr += (288 / 32); + h256 = _mm256_xor_si256(h256, temp); + } + hashVal = h256.m256i_u32[0] + h256.m256i_u32[1] + h256.m256i_u32[2] + h256.m256i_u32[3] + h256.m256i_u32[4] + h256.m256i_u32[5] + h256.m256i_u32[6] + h256.m256i_u32[7]; + } +#else + if( false ) {} +#endif + else + { + memRange /= (32 * sizeof(uint64)); + uint64 h64 = 0; + uint64* texDataU64 = (uint64*)texDataU32; + while (memRange--) + { + h64 += *texDataU64; + h64 = (h64 << 3) | (h64 >> 61); + texDataU64 += 32; + } + hashVal = (h64 & 0xFFFFFFFF) + (h64 >> 32); + } + } + else + { + memRange /= (512*sizeof(uint32)); + while( memRange-- ) + { + hashVal += *texDataU32; + hashVal = (hashVal<<3)|(hashVal>>29); + texDataU32 += 512; + } + } + } + + return hashVal; +} + +uint64 _botwLargeTexHax = 0; + +bool LatteTC_HasTextureChanged(LatteTexture* hostTexture, bool force) +{ + if (hostTexture->forceInvalidate) + { + force = true; + debug_printf("Force invalidate 0x%08x\n", hostTexture->physAddress); + hostTexture->forceInvalidate = false; + } + // if texture is written by GPU operations we switch to a faster hash implementation + if (hostTexture->isUpdatedOnGPU && hostTexture->useLightHash == false) + { + hostTexture->useLightHash = true; + // update hash + hostTexture->texDataHash2 = LatteTexture_CalculateTextureDataHash(hostTexture); + } + // only check each texture for updates once a frame + // todo: Instead of relying on frames, it would be better to recheck only after any GPU wait operation occurred. + if( hostTexture->lastDataUpdateFrameCounter == LatteGPUState.frameCounter && force == false) + return false; + hostTexture->lastDataUpdateFrameCounter = LatteGPUState.frameCounter; + // we assume that certain texture properties indicate that the texture will never be written by the CPU + if (hostTexture->width == 1280 && hostTexture->format != Latte::E_GX2SURFFMT::R8_UNORM && force == false) + { + // todo - remove this or find a better way to handle excluded texture invalidation checks (maybe via game profile?) + return false; + } + // workaround for corrupted terrain texture in BotW after video playback + // probably would be fixed if we added support for invalidating individual slices/mips of a texture + uint32 texDataHash = LatteTexture_CalculateTextureDataHash(hostTexture); + if( texDataHash != hostTexture->texDataHash2 ) + { + hostTexture->texDataHash2 = texDataHash; + if (hostTexture->depth == 83 && hostTexture->width == 1024 && hostTexture->height == 1024) + { + _botwLargeTexHax = LatteGPUState.frameCounter; + } + return true; + } + if (_botwLargeTexHax != 0 && hostTexture->depth == 83 && hostTexture->width == 1024 && hostTexture->height == 1024 && _botwLargeTexHax != LatteGPUState.frameCounter) + { + _botwLargeTexHax = 0; + return true; + } + return false; +} + +void LatteTC_ResetTextureChangeTracker(LatteTexture* hostTexture, bool force) +{ + if( hostTexture->lastDataUpdateFrameCounter == LatteGPUState.frameCounter && force == false) + return; + hostTexture->lastDataUpdateFrameCounter = LatteGPUState.frameCounter; + LatteTC_HasTextureChanged(hostTexture, true); +} + +/* + * This function should be called whenever the texture is still used in some form (any kind of access counts) + * The purpose of this function is to prevent garbage collection of textures that are still actively used + */ +void LatteTC_MarkTextureStillInUse(LatteTexture* texture) +{ + texture->lastAccessTick = LatteGPUState.currentDrawCallTick; + texture->lastAccessFrameCount = LatteGPUState.frameCounter; +} + +// check if a texture has been overwritten by another texture using GPU-writes +bool LatteTC_IsTextureDataOverwritten(LatteTexture* texture) +{ + // check overlaps + sint32 mipLevels = texture->mipLevels; + sint32 sliceCount = texture->depth; + mipLevels = std::min(mipLevels, 3); // only check first 3 mip levels + for (sint32 mipIndex = 0; mipIndex < mipLevels; mipIndex++) + { + sint32 mipSliceCount; + if (texture->Is3DTexture()) + mipSliceCount = std::max(1, sliceCount >> mipIndex); + else + mipSliceCount = sliceCount; + for (sint32 sliceIndex = 0; sliceIndex < mipSliceCount; sliceIndex++) + { + LatteTextureSliceMipInfo* sliceMipInfo = texture->sliceMipInfo + texture->GetSliceMipArrayIndex(sliceIndex, mipIndex); + bool isSliceMipOutdated = false; + for (auto& overlapData : sliceMipInfo->list_dataOverlap) + { + if (sliceMipInfo->lastDynamicUpdate < overlapData.destMipSliceInfo->lastDynamicUpdate) + { + isSliceMipOutdated = true; + break; + } + } + if (isSliceMipOutdated == false) + return false; + } + } + return true; +} + +void LatteTexture_Delete(LatteTexture* texture) +{ + LatteTC_UnregisterTexture(texture); + LatteMRT::NotifyTextureDeletion(texture); + LatteTextureReadback_NotifyTextureDeletion(texture); + LatteTexture_DeleteTextureRelations(texture); + // delete views + while (!texture->views.empty()) + delete texture->views[0]; + cemu_assert_debug(texture->views.empty()); + cemu_assert_debug(texture->baseView == nullptr); + // free data overlap tracking + LatteTexture_DeleteDataOverlapTracking(texture); + // remove from lists + LatteTexture_UnregisterTextureMemoryOccupancy(texture); + // free memory + if (texture->sliceMipInfo) + { + delete[] texture->sliceMipInfo; + texture->sliceMipInfo = nullptr; + } + g_renderer->texture_destroy(texture); +} + +/* + * Checks if the texture can be dropped from the cache and if yes, delete it + * Returns true if the texture was deleted + */ +bool LatteTC_CleanupCheckTexture(LatteTexture* texture, uint32 currentTick) +{ + uint32 currentFrameCount = LatteGPUState.frameCounter; + uint32 ticksSinceLastAccess = currentTick - texture->lastAccessTick; + uint32 framesSinceLastAccess = currentFrameCount - texture->lastAccessFrameCount; + if( !texture->isUpdatedOnGPU ) + { + // RAM-only textures are safe to be deleted since we can always restore them from RAM + if( ticksSinceLastAccess >= (120*1000) && framesSinceLastAccess >= 2000 ) + { + LatteTexture_Delete(texture); + return true; + } + } + + if ((LatteGPUState.currentDrawCallTick - texture->lastAccessTick) >= 100 && + LatteTC_IsTextureDataOverwritten(texture)) + { + LatteTexture_Delete(texture); + return true; + } + // if unused for more than 5 seconds, start deleting views since they are cheap to recreate + if (ticksSinceLastAccess >= 5 * 1000 && framesSinceLastAccess >= 30) + { + for (sint32 i = 0; i < 3; i++) + { + if (texture->views.size() <= 1) + break; + LatteTextureView* view = texture->views[0]; + if (view == texture->baseView) + view = texture->views[1]; + delete view; + } + } + return false; +} + +void LatteTexture_RefreshInfoCache(); + +/* + * Scans for unused textures and deletes them + * Called at the end of every frame + */ +void LatteTC_CleanupUnusedTextures() +{ + static size_t currentScanIndex = 0; + uint32 currentTick = GetTickCount(); + sint32 maxDelete = 10; + std::vector<LatteTexture*>& allTextures = LatteTexture::GetAllTextures(); + if (!allTextures.empty()) + { + for (sint32 c = 0; c < 25; c++) + { + if (currentScanIndex >= allTextures.size()) + currentScanIndex = 0; + LatteTexture* texItr = allTextures[currentScanIndex]; + currentScanIndex++; + if (!texItr) + continue; + if (LatteTC_CleanupCheckTexture(texItr, currentTick)) + { + maxDelete--; + if (maxDelete <= 0) + break; // deleting can be an expensive operation, dont delete too many at once to avoid micro stutter + if (allTextures.empty()) + break; + } + } + } + LatteTexture_RefreshInfoCache(); // find a better place to call this from? +} + +std::vector<LatteTexture*> LatteTC_GetDeleteableTextures() +{ + std::vector<LatteTexture*> texList; + uint32 currentFrameCount = LatteGPUState.frameCounter; + + for (auto& itr : g_allTextures) + { + if(itr->lastAccessFrameCount == 0) + continue; // not initialized + uint32 framesSinceLastAccess = currentFrameCount - itr->lastAccessFrameCount; + if(framesSinceLastAccess < 3) + continue; + if (itr->isUpdatedOnGPU) + { + if (LatteTC_IsTextureDataOverwritten(itr)) + texList.emplace_back(itr); + } + else + { + texList.emplace_back(itr); + } + } + + return texList; +} + +void LatteTC_UnloadAllTextures() +{ + std::vector<LatteTexture*> allTexturesCopy = LatteTexture::GetAllTextures(); + for (auto& itr : allTexturesCopy) + { + if(itr) + LatteTexture_Delete(itr); + } + LatteRenderTarget_unloadAll(); +} + +/* + * Asynchronous way to invalidate textures + */ +__declspec(dllexport) void gpu7Texture_forceInvalidateByImagePtr(MPTR imagePtr) +{ + // deprecated. Texture cache heuristics are now good enough to detect moving frames +} diff --git a/src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp b/src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp new file mode 100644 index 00000000..8d4c7efb --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp @@ -0,0 +1,422 @@ +#include "Cafe/HW/Latte/ISA/RegDefines.h" +#include "Cafe/OS/libs/gx2/GX2.h" // todo - remove this dependency +#include "Cafe/HW/Latte/Core/Latte.h" +#include "Cafe/HW/Latte/Core/LatteShader.h" + +#include "Cafe/HW/Latte/Renderer/Renderer.h" +#include "Cafe/GraphicPack/GraphicPack2.h" + +#include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h" +#include "Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.h" +#include "Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.h" + +struct TexScaleXY +{ + float xy[2]; +}; + +struct +{ + TexScaleXY perUnit[Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE]; // stores actualResolution/effectiveResolution ratio for each texture +}LatteTextureScale[static_cast<size_t>(LatteConst::ShaderType::TotalCount)] = { }; + +float* LatteTexture_getEffectiveTextureScale(LatteConst::ShaderType shaderType, sint32 texUnit) +{ + cemu_assert_debug(texUnit >= 0 && texUnit < Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE); + return LatteTextureScale[static_cast<size_t>(shaderType)].perUnit[texUnit].xy; +} + +void LatteTexture_setEffectiveTextureScale(LatteConst::ShaderType shaderType, sint32 texUnit, float u, float v) +{ + cemu_assert_debug(texUnit >= 0 && texUnit < Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE); + float* t = LatteTextureScale[static_cast<size_t>(shaderType)].perUnit[texUnit].xy; + t[0] = u; + t[1] = v; +} + +void LatteTextureLoader_UpdateTextureSliceData(LatteTexture* tex, sint32 textureUnit, uint32 sliceIndex, uint32 mipIndex, MPTR physImagePtr, MPTR physMipPtr, Latte::E_DIM dim, uint32 width, uint32 height, uint32 depth, uint32 mipLevels, uint32 pitch, Latte::E_HWTILEMODE tileMode, uint32 swizzle, bool dumpTex); + +void LatteTexture_ReloadData(LatteTexture* tex, uint32 textureUnit) +{ + tex->reloadCount++; + for(sint32 mip=0; mip<tex->mipLevels; mip++) + { + if(tex->dim == Latte::E_DIM::DIM_2D_ARRAY || + tex->dim == Latte::E_DIM::DIM_2D_ARRAY_MSAA ) + { + sint32 numSlices = std::max(tex->depth, 1); + for(sint32 s=0; s<numSlices; s++) + LatteTextureLoader_UpdateTextureSliceData(tex, textureUnit, s, mip, tex->physAddress, tex->physMipAddress, tex->dim, tex->width, tex->height, tex->depth, tex->mipLevels, tex->pitch, tex->tileMode, tex->swizzle, true); + } + else if( tex->dim == Latte::E_DIM::DIM_CUBEMAP ) + { + cemu_assert_debug((tex->depth % 6) == 0); + sint32 numFullCubeMaps = tex->depth/6; // number of cubemaps (if numFullCubeMaps is >1 then this texture is a cubemap array) + for(sint32 s=0; s<numFullCubeMaps*6; s++) + LatteTextureLoader_UpdateTextureSliceData(tex, textureUnit, s, mip, tex->physAddress, tex->physMipAddress, tex->dim, tex->width, tex->height, tex->depth, tex->mipLevels, tex->pitch, tex->tileMode, tex->swizzle, true); + } + else if( tex->dim == Latte::E_DIM::DIM_3D ) + { + sint32 mipDepth = std::max(tex->depth>>mip, 1); + for(sint32 s=0; s<mipDepth; s++) + { + LatteTextureLoader_UpdateTextureSliceData(tex, textureUnit, s, mip, tex->physAddress, tex->physMipAddress, tex->dim, tex->width, tex->height, tex->depth, tex->mipLevels, tex->pitch, tex->tileMode, tex->swizzle, true); + } + } + else + { + // load slice 0 + LatteTextureLoader_UpdateTextureSliceData(tex, textureUnit, 0, mip, tex->physAddress, tex->physMipAddress, tex->dim, tex->width, tex->height, tex->depth, tex->mipLevels, tex->pitch, tex->tileMode, tex->swizzle, true); + } + } + tex->lastUpdateEventCounter = LatteTexture_getNextUpdateEventCounter(); +} + +LatteTextureView* LatteTexture_CreateTexture(uint32 textureUnit, Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, uint32 swizzle, Latte::E_HWTILEMODE tileMode, bool isDepth) +{ + const auto tex = g_renderer->texture_createTextureEx(textureUnit, dim, physAddress, physMipAddress, format, width, height, depth, pitch, mipLevels, swizzle, tileMode, isDepth); + // init slice/mip info array + LatteTexture_InitSliceAndMipInfo(tex); + LatteTexture_RegisterTextureMemoryOccupancy(tex); + cemu_assert_debug(mipLevels != 0); + // calculate number of potential mip levels (from effective size) + sint32 effectiveWidth = width; + sint32 effectiveHeight = height; + sint32 effectiveDepth = depth; + if (tex->overwriteInfo.hasResolutionOverwrite) + { + effectiveWidth = tex->overwriteInfo.width; + effectiveHeight = tex->overwriteInfo.height; + effectiveDepth = tex->overwriteInfo.depth; + } + tex->maxPossibleMipLevels = 1; + if (dim != Latte::E_DIM::DIM_3D) + { + for (sint32 i = 0; i < 20; i++) + { + if ((effectiveWidth >> i) <= 1 && (effectiveHeight >> i) <= 1) + { + tex->maxPossibleMipLevels = i + 1; + break; + } + } + } + else + { + for (sint32 i = 0; i < 20; i++) + { + if ((effectiveWidth >> i) <= 1 && (effectiveHeight >> i) <= 1 && (effectiveDepth >> i) <= 1) + { + tex->maxPossibleMipLevels = i + 1; + break; + } + } + } + LatteTexture_ReloadData(tex, textureUnit); + LatteTC_MarkTextureStillInUse(tex); + LatteTC_RegisterTexture(tex); + // create initial view that maps to the whole texture + tex->baseView = tex->GetOrCreateView(0, tex->mipLevels, 0, tex->depth); + return tex->baseView; +} + +Latte::E_GX2SURFFMT LatteTexture_ReconstructGX2Format(const Latte::LATTE_SQ_TEX_RESOURCE_WORD1_N& texUnitWord1, const Latte::LATTE_SQ_TEX_RESOURCE_WORD4_N& texUnitWord4) +{ + Latte::E_GX2SURFFMT gx2Format = (Latte::E_GX2SURFFMT)texUnitWord1.get_DATA_FORMAT(); + auto nfa = texUnitWord4.get_NUM_FORM_ALL(); + if (nfa == Latte::LATTE_SQ_TEX_RESOURCE_WORD4_N::E_NUM_FORMAT_ALL::NUM_FORMAT_SCALED) + gx2Format |= Latte::E_GX2SURFFMT::FMT_BIT_FLOAT; + else if (nfa == Latte::LATTE_SQ_TEX_RESOURCE_WORD4_N::E_NUM_FORMAT_ALL::NUM_FORMAT_INT) + gx2Format |= Latte::E_GX2SURFFMT::FMT_BIT_INT; + + if(texUnitWord4.get_FORCE_DEGAMMA()) + gx2Format |= Latte::E_GX2SURFFMT::FMT_BIT_SRGB; + + if (texUnitWord4.get_FORMAT_COMP_X() == Latte::LATTE_SQ_TEX_RESOURCE_WORD4_N::E_FORMAT_COMP::COMP_SIGNED) + gx2Format |= Latte::E_GX2SURFFMT::FMT_BIT_SIGNED; + + return gx2Format; +} + +void LatteTexture_updateTexturesForStage(LatteDecompilerShader* shaderContext, uint32 glBackendBaseTexUnit, _LatteRegisterSetTextureUnit* texRegBase) +{ + for (sint32 z = 0; z < shaderContext->textureUnitListCount; z++) + { + sint32 textureIndex = shaderContext->textureUnitList[z]; + const auto& texRegister = texRegBase[textureIndex]; + + // get physical address of texture data + MPTR physAddr = (texRegister.word2.get_BASE_ADDRESS() << 8); + if (physAddr == MPTR_NULL) + continue; // invalid data + MPTR physMipAddr = (texRegister.word3.get_MIP_ADDRESS() << 8); + + // word0 + const auto word0 = texRegister.word0; + auto dim = word0.get_DIM(); + uint32 pitch = (word0.get_PITCH() + 1) << 3; + uint32 width = word0.get_WIDTH() + 1; + auto tileMode = word0.get_TILE_MODE(); + // word1 + const auto word1 = texRegister.word1; + uint32 depth = word1.get_DEPTH(); + if (dim == Latte::E_DIM::DIM_2D_ARRAY || dim == Latte::E_DIM::DIM_3D || dim == Latte::E_DIM::DIM_2D_ARRAY_MSAA || dim == Latte::E_DIM::DIM_1D_ARRAY) + { + depth = depth + 1; + } + else + { + if (dim == Latte::E_DIM::DIM_CUBEMAP) + depth = 6 * (depth + 1); + if (depth == 0) + depth = 1; + } + uint32 height = word1.get_HEIGHT() + 1; + if (Latte::IsCompressedFormat(word1.get_DATA_FORMAT())) + pitch /= 4; + // view slice + const auto word4 = texRegister.word4; + const auto word5 = texRegister.word5; + + uint32 viewFirstSlice = word5.get_BASE_ARRAY(); + uint32 viewNumSlices = word5.get_LAST_ARRAY() + 1 - viewFirstSlice; + + uint32 viewFirstMip = word4.get_BASE_LEVEL(); + uint32 viewNumMips = word5.get_LAST_LEVEL() + 1 - viewFirstMip; + + cemu_assert_debug(viewNumMips != 0); + + Latte::E_GX2SURFFMT format = LatteTexture_ReconstructGX2Format(word1, word4); + + // todo - AA + if (dim == Latte::E_DIM::DIM_2D_MSAA) + { + // MSAA only supports one mip level? + // without this we encounter a crash in The Mysterious Cities of Gold: Secret Paths due to it setting mip count to 2 and leaving mip pointer on an invalid uninitialized value + viewFirstMip = 0; + viewNumMips = 1; + } + + // swizzle + uint32 swizzle = 0; + if (Latte::TM_IsMacroTiled(tileMode)) + { + // extract swizzle bits from pointer if macro-tiled + swizzle = (physAddr & 0x700); + physAddr &= ~0x700; + } + + bool isDepthSampler = shaderContext->textureUsesDepthCompare[textureIndex]; + // look for already existing texture + LatteTextureView* textureView; + if (isDepthSampler == false) + textureView = LatteTextureViewLookupCache::lookup(physAddr, width, height, depth, pitch, viewFirstMip, viewNumMips, viewFirstSlice, viewNumSlices, format, dim); + else + textureView = LatteTextureViewLookupCache::lookup(physAddr, width, height, depth, pitch, viewFirstMip, viewNumMips, viewFirstSlice, viewNumSlices, format, dim, true); + if (textureView == nullptr) + { + // create new mapping + textureView = LatteTexture_CreateMapping(physAddr, physMipAddr, width, height, depth, pitch, tileMode, swizzle, viewFirstMip, viewNumMips, viewFirstSlice, viewNumSlices, format, dim, dim, isDepthSampler); + if (textureView == nullptr) + continue; + LatteGPUState.repeatTextureInitialization = true; + } + + if (g_renderer->GetType() == RendererAPI::OpenGL) + { + // on OpenGL, texture views and sampler parameters are tied together (we are avoiding sampler objects due to driver bugs) + // in order to emulate different sampler parameters when a texture is bound multiple times we create extra views + OpenGLRenderer* rendererGL = static_cast<OpenGLRenderer*>(g_renderer.get()); + + // if this texture is bound multiple times then use alternative views + if (textureView->lastTextureBindIndex == LatteGPUState.textureBindCounter) + { + // Intel driver has issues with textures that have multiple views bound and used by a shader, causes a softlock in BotW + // therefore we disable this on Intel + if (LatteGPUState.glVendor != GLVENDOR_INTEL_NOLEGACY) + { + LatteTextureViewGL* textureViewGL = (LatteTextureViewGL*)textureView; + // get next unused alternative texture view + while (true) + { + textureViewGL = textureViewGL->GetAlternativeView(); + if (textureViewGL->lastTextureBindIndex != LatteGPUState.textureBindCounter) + break; + } + textureView = textureViewGL; + } + } + textureView->lastTextureBindIndex = LatteGPUState.textureBindCounter; + rendererGL->renderstate_updateTextureSettingsGL(shaderContext, textureView, textureIndex + glBackendBaseTexUnit, word4, textureIndex, isDepthSampler); + } + g_renderer->texture_bindOnly(textureView, textureIndex + glBackendBaseTexUnit); + // update if data changed + bool swizzleChanged = false; + if (textureView->baseTexture->swizzle != swizzle) + { + debug_printf("BaseSwizzle diff prev %08x new %08x rt %08x tm %d\n", textureView->baseTexture->swizzle, swizzle, textureView->baseTexture->lastRenderTargetSwizzle, textureView->baseTexture->tileMode); + if (swizzle == textureView->baseTexture->lastRenderTargetSwizzle) + { + // last render to texture updated the swizzle and we can assume the texture data is still valid + textureView->baseTexture->swizzle = textureView->baseTexture->lastRenderTargetSwizzle; + } + else + { + // reload texture + swizzleChanged = true; + } + } + else if ((viewFirstMip + viewNumMips) > 1 && (textureView->baseTexture->physMipAddress != physMipAddr)) + { + debug_printf("MipPhys/Swizzle change diff prev %08x new %08x tm %d\n", textureView->baseTexture->physMipAddress, physMipAddr, textureView->baseTexture->tileMode); + swizzleChanged = true; + cemu_assert_debug(physMipAddr != MPTR_NULL); + } + // check for changes + if (LatteTC_HasTextureChanged(textureView->baseTexture) || swizzleChanged) + { +#ifndef PUBLIC_RELEASE + debug_printf("Reload texture 0x%08x res %dx%d memRange %08x-%08x SwizzleChange: %s\n", textureView->baseTexture->physAddress, textureView->baseTexture->width, textureView->baseTexture->height, textureView->baseTexture->texDataPtrLow, textureView->baseTexture->texDataPtrHigh, swizzleChanged ? "yes" : "no"); +#endif + // update swizzle / changed mip address + if (swizzleChanged) + { + textureView->baseTexture->swizzle = swizzle; + if ((viewFirstMip + viewNumMips) > 1) + { + textureView->baseTexture->physMipAddress = physMipAddr; + } + } + g_renderer->texture_bindAndActivateRawTex(textureView->baseTexture, textureIndex + glBackendBaseTexUnit); + debug_printf("Reload reason: Data-change when bound as texture (new hash 0x%08x)\n", textureView->baseTexture->texDataHash2); + LatteTexture_ReloadData(textureView->baseTexture, textureIndex + glBackendBaseTexUnit); + } + LatteTexture* baseTexture = textureView->baseTexture; + if (baseTexture->reloadFromDynamicTextures) + { + LatteTexture_UpdateCacheFromDynamicTextures(baseTexture); + baseTexture->reloadFromDynamicTextures = false; + } + LatteTC_MarkTextureStillInUse(baseTexture); + + // check if barrier is necessary + if ((sint32)(LatteGPUState.drawCallCounter - baseTexture->lastUnflushedRTDrawcallIndex) < 2) + { + LatteGPUState.requiresTextureBarrier = true; + baseTexture->lastUnflushedRTDrawcallIndex = 0; + } + // update scale + float texScaleU, texScaleV; + if (baseTexture->overwriteInfo.hasResolutionOverwrite == false) + { + texScaleU = 1.0f; + texScaleV = 1.0f; + } + else + { + texScaleU = (float)baseTexture->overwriteInfo.width / (float)baseTexture->width; + texScaleV = (float)baseTexture->overwriteInfo.height / (float)baseTexture->height; + } + LatteTexture_setEffectiveTextureScale(shaderContext->shaderType, textureIndex, texScaleU, texScaleV); + } +} + +// initialize textures used by the current drawcall +// Sets LatteGPUState.repeatTextureInitialization to true if a new texture mapping was created (indicating that this function must be called again) +// also sets LatteGPUState.requiresTextureBarrier to true if texture barrier is required +void LatteTexture_updateTextures() +{ + LatteGPUState.textureBindCounter++; + // pixel shader + LatteDecompilerShader* pixelShader = LatteSHRC_GetActivePixelShader(); + if (pixelShader) + LatteTexture_updateTexturesForStage(pixelShader, CEMU_PS_TEX_UNIT_BASE, LatteGPUState.contextNew.SQ_TEX_START_PS); + // vertex shader + LatteDecompilerShader* vertexShader = LatteSHRC_GetActiveVertexShader(); + cemu_assert_debug(vertexShader != nullptr); + LatteTexture_updateTexturesForStage(vertexShader, CEMU_VS_TEX_UNIT_BASE, LatteGPUState.contextNew.SQ_TEX_START_VS); + // geometry shader + LatteDecompilerShader* geometryShader = LatteSHRC_GetActiveGeometryShader(); + if (geometryShader) + LatteTexture_updateTexturesForStage(geometryShader, CEMU_GS_TEX_UNIT_BASE, LatteGPUState.contextNew.SQ_TEX_START_GS); +} + +// returns the width, height, depth of the texture +void LatteTexture_getSize(LatteTexture* texture, sint32* width, sint32* height, sint32* depth, sint32 mipLevel) +{ + *width = texture->width; + *height = texture->height; + if (depth != NULL) + *depth = texture->depth; + // handle mip level + *width = std::max(1, *width >> mipLevel); + *height = std::max(1, *height >> mipLevel); + if(texture->Is3DTexture() && depth) + *depth = std::max(1, *depth >> mipLevel); +} + +/* + * Returns the internally used width/height/depth of the texture + * Usually this is the width/height/depth specified by the game, + * unless the texture resolution was redefined via graphic pack texture rules + */ +void LatteTexture_getEffectiveSize(LatteTexture* texture, sint32* effectiveWidth, sint32* effectiveHeight, sint32* effectiveDepth, sint32 mipLevel) +{ + *effectiveWidth = texture->width; + *effectiveHeight = texture->height; + if( effectiveDepth != NULL ) + *effectiveDepth = texture->depth; + if( texture->overwriteInfo.hasResolutionOverwrite ) + { + *effectiveWidth = texture->overwriteInfo.width; + *effectiveHeight = texture->overwriteInfo.height; + if( effectiveDepth != NULL ) + *effectiveDepth = texture->overwriteInfo.depth; + } + // handle mipLevel + // todo: Mip-mapped 3D textures decrease in depth also? + *effectiveWidth = std::max(1, *effectiveWidth >> mipLevel); + *effectiveHeight = std::max(1, *effectiveHeight >> mipLevel); +} + +sint32 LatteTexture_getEffectiveWidth(LatteTexture* texture) +{ + if (texture->overwriteInfo.hasResolutionOverwrite) + return texture->overwriteInfo.width; + return texture->width; +} + +// returns true if the two textures have the same rescale factor +bool LatteTexture_doesEffectiveRescaleRatioMatch(LatteTexture* texture1, sint32 mipLevel1, LatteTexture* texture2, sint32 mipLevel2) +{ + double widthRatio1 = (double)LatteTexture_getEffectiveWidth(texture1) / (double)texture1->width; + double widthRatio2 = (double)LatteTexture_getEffectiveWidth(texture2) / (double)texture2->width; + // the difference between the factors must be less than 5% + double diff = widthRatio1 / widthRatio2; + if (abs(1.0 - diff) > 0.05) + { + return false; + } + return true; +} + +void LatteTexture_scaleToEffectiveSize(LatteTexture* texture, sint32* x, sint32* y, sint32 mipLevel) +{ + if( texture->overwriteInfo.hasResolutionOverwrite == false ) + return; + *x = *x * std::max(1,texture->overwriteInfo.width>>mipLevel) / std::max(1,texture->width>>mipLevel); + *y = *y * std::max(1,texture->overwriteInfo.height>>mipLevel) / std::max(1, texture->height>>mipLevel); +} + +uint64 _textureUpdateEventCounter = 1; + +uint64 LatteTexture_getNextUpdateEventCounter() +{ + uint64 counter = _textureUpdateEventCounter; + _textureUpdateEventCounter++; + return counter; +} + +void LatteTexture_init() +{ +} \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Core/LatteTextureLoader.cpp b/src/Cafe/HW/Latte/Core/LatteTextureLoader.cpp new file mode 100644 index 00000000..5bd71e81 --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteTextureLoader.cpp @@ -0,0 +1,890 @@ +#include "Cafe/HW/Latte/Renderer/Renderer.h" +#include "Cafe/HW/Latte/LatteAddrLib/LatteAddrLib.h" +#include "config/ActiveSettings.h" +#include "Cafe/CafeSystem.h" + +//#define BENCHMARK_TEXTURE_DECODING // if defined, time it takes to decode textures will be measured and logged to log.txt + +#ifdef BENCHMARK_TEXTURE_DECODING +uint64 textureDecodeBenchmark_perFormatSum[0x40] = { 0 }; // duration sum per texture format (hw format) - in microseconds +uint64 textureDecodeBenchmark_totalSum = 0; +#endif + +void LatteTextureLoader_begin(LatteTextureLoaderCtx* textureLoader, uint32 sliceIndex, uint32 mipIndex, MPTR physImagePtr, MPTR physMipPtr, Latte::E_GX2SURFFMT format, Latte::E_DIM dim, uint32 width, uint32 height, uint32 depth, uint32 mipLevels, uint32 pitch, Latte::E_HWTILEMODE tileMode, uint32 swizzle) +{ + textureLoader->physAddress = physImagePtr; + textureLoader->physMipAddress = physMipPtr; + textureLoader->sliceIndex = sliceIndex; + cemu_assert_debug(mipLevels != 0); + textureLoader->mipLevels = std::max<uint32>(1, mipLevels); + textureLoader->tileMode = tileMode; + textureLoader->bpp = Latte::GetFormatBits(format); + textureLoader->stepX = 1; + textureLoader->stepY = 1; + if (Latte::IsCompressedFormat(format)) + { + textureLoader->stepX = 4; + textureLoader->stepY = 4; + } + + textureLoader->pipeSwizzle = (swizzle >> 8) & 1; + textureLoader->bankSwizzle = ((swizzle >> 9) & 3); + + uint32 surfaceAA = 0; // todo + + if (mipIndex > 0 && Latte::TM_IsMacroTiled(tileMode)) + { + // separate swizzle from mip pointer if mip chain is not macro-tiled (and thus not swizzled) + LatteAddrLib::AddrSurfaceInfo_OUT surfaceInfo; + LatteAddrLib::GX2CalculateSurfaceInfo(format, width, height, depth, dim, Latte::MakeGX2TileMode(tileMode), surfaceAA, 1, &surfaceInfo); + if (Latte::TM_IsMacroTiled(surfaceInfo.hwTileMode)) + { + uint32 mipSwizzle = physMipPtr&0x700; + physMipPtr &= ~0x700; + textureLoader->physMipAddress = physMipPtr; + textureLoader->pipeSwizzle = (mipSwizzle >> 8) & 1; + textureLoader->bankSwizzle = ((mipSwizzle >> 9) & 3); + } + } + + // calculate surface info + uint32 level = mipIndex; + LatteAddrLib::AddrSurfaceInfo_OUT surfaceInfo; + LatteAddrLib::GX2CalculateSurfaceInfo(format, width, height, depth, dim, Latte::MakeGX2TileMode(tileMode), surfaceAA, level, &surfaceInfo); + textureLoader->levelOffset = LatteAddrLib::CalculateMipOffset(format, width, height, depth, dim, (Latte::E_HWTILEMODE)tileMode, swizzle, surfaceAA, level); + textureLoader->tileMode = surfaceInfo.hwTileMode; + + textureLoader->minOffsetOutdated = 0; + textureLoader->maxOffsetOutdated = (sint32)surfaceInfo.surfSize; + + textureLoader->surfaceInfoHeight = surfaceInfo.height; + textureLoader->surfaceInfoDepth = surfaceInfo.depth; + + // correct handling for LINEAR_ALIGNED pitch alignment is still not fully understood: + //seems like sometimes there is a conditional pitch alignment to 0x40 OR there is no pitch alignment at all and we have a bug somewhere else + + uint64 titleId = CafeSystem::GetForegroundTitleId(); + titleId &= ~0x300ULL; + + if (tileMode == Latte::E_HWTILEMODE::TM_LINEAR_ALIGNED && titleId == (0x000500301001200aULL)) + { + // examples of titles that use linear textures: + // Minecraft - Uses sprite atlases with mips and linear tilemode. Expects padding of pitch for smaller mips to be 0x40 + // Browser - Linear pitch must be used as-is, padding/alignment will break textures (uses a weird way to calculate pitch by using GX2CalcSurface on a texture with tileMode 0/4) + // BotW - uses linear textures as render targets. With the smallest resolution being 3x3 with no pitch alignment expected at all (pitch = 3)? -> Not possible because both textures and rendertargets require a minimum alignment of 8 for pitch? + surfaceInfo.pitch = std::max<uint32>(1, pitch >> mipIndex); + } + + + textureLoader->width = width >> (mipIndex); + textureLoader->width = std::max(textureLoader->width, 1); + textureLoader->height = height >> (mipIndex); + textureLoader->height = std::max(textureLoader->height, 1); + + textureLoader->pitch = surfaceInfo.pitch; + // calculate start address + if (level == 0) + textureLoader->inputData = (uint8*)memory_getPointerFromPhysicalOffset(physImagePtr); + else + textureLoader->inputData = (uint8*)memory_getPointerFromPhysicalOffset(physMipPtr) + textureLoader->levelOffset; + + SetupCachedSurfaceAddrInfo(&textureLoader->computeAddrInfo, textureLoader->sliceIndex, 0, textureLoader->bpp, textureLoader->pitch, surfaceInfo.height, depth, 1 * 1, textureLoader->tileMode, false, textureLoader->pipeSwizzle, textureLoader->bankSwizzle); +} + +uint8* LatteTextureLoader_GetInput(LatteTextureLoaderCtx* textureLoader, sint32 x, sint32 y) +{ + // calculate address of input tile + uint32 offset = 0; + if (textureLoader->tileMode == Latte::E_HWTILEMODE::TM_LINEAR_GENERAL || textureLoader->tileMode == Latte::E_HWTILEMODE::TM_LINEAR_ALIGNED) + offset = LatteAddrLib::ComputeSurfaceAddrFromCoordLinear(x / textureLoader->stepX, y / textureLoader->stepY, textureLoader->sliceIndex, 0, textureLoader->bpp, textureLoader->pitch, textureLoader->surfaceInfoHeight, textureLoader->surfaceInfoDepth); + else if (textureLoader->tileMode == Latte::E_HWTILEMODE::TM_1D_TILED_THIN1 || textureLoader->tileMode == Latte::E_HWTILEMODE::TM_1D_TILED_THICK) + offset = LatteAddrLib::ComputeSurfaceAddrFromCoordMicroTiled(x / textureLoader->stepX, y / textureLoader->stepY, textureLoader->sliceIndex, textureLoader->bpp, textureLoader->pitch, textureLoader->surfaceInfoHeight, (Latte::E_HWTILEMODE)textureLoader->tileMode, false); + else + offset = LatteAddrLib::ComputeSurfaceAddrFromCoordMacroTiledCached(x / textureLoader->stepX, y / textureLoader->stepY, &textureLoader->computeAddrInfo); + uint8* blockData = textureLoader->inputData + offset; + return blockData; +} + +/* + * Optimized version which assumes tileMode == 1 + * Also does not do any min/max offset tracking + */ +uint8* LatteTextureLoader_getInputLinearOptimized(LatteTextureLoaderCtx* textureLoader, sint32 x, sint32 y) +{ + // calculate address of input tile + uint32 bitPos = 0; + uint32 offset = 0; + offset = LatteAddrLib::ComputeSurfaceAddrFromCoordLinear(x / textureLoader->stepX, y / textureLoader->stepY, textureLoader->sliceIndex, 0, textureLoader->bpp, textureLoader->pitch, textureLoader->surfaceInfoHeight, textureLoader->surfaceInfoDepth); + return textureLoader->inputData + offset; +} + +#define LatteTextureLoader_getInputLinearOptimized_(__textureLoader,__x,__y,__stepX,__stepY,__bpp,__sliceIndex,__numSlices,__sample,__pitch,__height) (textureLoader->inputData+((__x/__stepX) + __pitch * (__y/__stepY) + (__sliceIndex + __numSlices * __sample) * __height * __pitch)*(__bpp/8)) + +float SRGB_to_RGB(float cs) +{ + float cl; + if (cs <= 0.04045f) + cl = cs / 12.92f; + else + cl = powf(((cs + 0.055f) / 1.055f), 2.4f); + return cl; +} + +void decodeBC1Block(uint8* inputData, float* output4x4RGBA) +{ + // read colors + uint16 c0 = *(uint16*)(inputData + 0); + uint16 c1 = *(uint16*)(inputData + 2); + // decode colors (RGB565 -> RGB888) + float r[4]; + float g[4]; + float b[4]; + float a[4]; + b[0] = (float)((c0 >> 0) & 0x1F) / 31.0f; + b[1] = (float)((c1 >> 0) & 0x1F) / 31.0f; + g[0] = (float)((c0 >> 5) & 0x3F) / 63.0f; + g[1] = (float)((c1 >> 5) & 0x3F) / 63.0f; + r[0] = (float)((c0 >> 11) & 0x1F) / 31.0f; + r[1] = (float)((c1 >> 11) & 0x1F) / 31.0f; + a[0] = 1.0f; + a[1] = 1.0f; + a[2] = 1.0f; + + if (c0 > c1) + { + r[2] = (r[0] * 2.0f + r[1]) / 3.0f; + r[3] = (r[0] * 1.0f + r[1] * 2.0f) / 3.0f; + g[2] = (g[0] * 2.0f + g[1]) / 3.0f; + g[3] = (g[0] * 1.0f + g[1] * 2.0f) / 3.0f; + b[2] = (b[0] * 2.0f + b[1]) / 3.0f; + b[3] = (b[0] * 1.0f + b[1] * 2.0f) / 3.0f; + a[3] = 1.0f; + } + else + { + r[2] = (r[0] + r[1]) / 2.0f; + r[3] = 0.0f; + g[2] = (g[0] + g[1]) / 2.0f; + g[3] = 0.0f; + b[2] = (b[0] + b[1]) / 2.0f; + b[3] = 0.0f; + a[3] = 0.0f; + } + + uint8* indexData = inputData + 4; + float* colorOutputRGBA = output4x4RGBA; + for (sint32 row = 0; row < 4; row++) + { + uint8 i0 = ((*indexData) >> 0) & 3; + uint8 i1 = ((*indexData) >> 2) & 3; + uint8 i2 = ((*indexData) >> 4) & 3; + uint8 i3 = ((*indexData) >> 6) & 3; + colorOutputRGBA[0] = r[i0]; + colorOutputRGBA[1] = g[i0]; + colorOutputRGBA[2] = b[i0]; + colorOutputRGBA[3] = a[i0]; + colorOutputRGBA += 4; + colorOutputRGBA[0] = r[i1]; + colorOutputRGBA[1] = g[i1]; + colorOutputRGBA[2] = b[i1]; + colorOutputRGBA[3] = a[i1]; + colorOutputRGBA += 4; + colorOutputRGBA[0] = r[i2]; + colorOutputRGBA[1] = g[i2]; + colorOutputRGBA[2] = b[i2]; + colorOutputRGBA[3] = a[i2]; + colorOutputRGBA += 4; + colorOutputRGBA[0] = r[i3]; + colorOutputRGBA[1] = g[i3]; + colorOutputRGBA[2] = b[i3]; + colorOutputRGBA[3] = a[i3]; + colorOutputRGBA += 4; + indexData++; + } +} + +void decodeBC2Block_UNORM(uint8* inputData, float* imageRGBA) +{ + uint32 color0 = *(uint16*)(inputData + 8); + uint32 color1 = *(uint16*)(inputData + 10); + uint32 colorIndices = *(uint32*)(inputData + 12); + + uint8 r0 = (color0 >> 11) & 0x1F; + uint8 g0 = (color0 >> 5) & 0x3F; + uint8 b0 = (color0 >> 0) & 0x1F; + + uint8 r1 = (color1 >> 11) & 0x1F; + uint8 g1 = (color1 >> 5) & 0x3F; + uint8 b1 = (color1 >> 0) & 0x1F; + + float r[4]; + float g[4]; + float b[4]; + r[0] = (float)r0 / 31.0f; + r[1] = (float)r1 / 31.0f; + r[2] = (r[0] * 2.0f + r[1]) / 3.0f; + r[3] = (r[0] + r[1] * 2.0f) / 3.0f; + g[0] = (float)g0 / 63.0f; + g[1] = (float)g1 / 63.0f; + g[2] = (g[0] * 2.0f + g[1]) / 3.0f; + g[3] = (g[0] + g[1] * 2.0f) / 3.0f; + b[0] = (float)b0 / 31.0f; + b[1] = (float)b1 / 31.0f; + b[2] = (b[0] * 2.0f + b[1]) / 3.0f; + b[3] = (b[0] + b[1] * 2.0f) / 3.0f; + + for (sint32 py = 0; py < 4; py++) + { + for (sint32 px = 0; px < 4; px++) + { + uint8 colorIndex = (colorIndices >> (2 * (px + 4 * py))) & 0x03; + sint32 pixelOffset = (px + py * 4) * 4; + imageRGBA[pixelOffset + 0] = r[colorIndex]; + imageRGBA[pixelOffset + 1] = g[colorIndex]; + imageRGBA[pixelOffset + 2] = b[colorIndex]; + } + } + + // decode alpha + uint8* alphaData = (uint8*)(inputData + 0); + for (sint32 py = 0; py < 4; py++) + { + for (sint32 px = 0; px < 4; px++) + { + uint32 alphaIndex = (px + py * 4); + uint8 alphaCode = (alphaData[alphaIndex / 2] >> ((alphaIndex & 1) * 4)) & 0xF; + alphaCode |= (alphaCode << 4); + sint32 pixelOffset = (px + py * 4) * 4; + imageRGBA[pixelOffset + 3] = (float)alphaCode / 255.0f; // alpha + } + } +} + +void decodeBC3Block_UNORM(uint8* inputData, float* imageRGBA) +{ + uint32 color0 = *(uint16*)(inputData + 8); + uint32 color1 = *(uint16*)(inputData + 10); + uint32 colorIndices = *(uint32*)(inputData + 12); + + uint8 r0 = (color0 >> 11) & 0x1F; + uint8 g0 = (color0 >> 5) & 0x3F; + uint8 b0 = (color0 >> 0) & 0x1F; + + uint8 r1 = (color1 >> 11) & 0x1F; + uint8 g1 = (color1 >> 5) & 0x3F; + uint8 b1 = (color1 >> 0) & 0x1F; + + float r[4]; + float g[4]; + float b[4]; + r[0] = (float)r0 / 31.0f; + r[1] = (float)r1 / 31.0f; + r[2] = (r[0] * 2.0f + r[1]) / 3.0f; + r[3] = (r[0] + r[1] * 2.0f) / 3.0f; + g[0] = (float)g0 / 63.0f; + g[1] = (float)g1 / 63.0f; + g[2] = (g[0] * 2.0f + g[1]) / 3.0f; + g[3] = (g[0] + g[1] * 2.0f) / 3.0f; + b[0] = (float)b0 / 31.0f; + b[1] = (float)b1 / 31.0f; + b[2] = (b[0] * 2.0f + b[1]) / 3.0f; + b[3] = (b[0] + b[1] * 2.0f) / 3.0f; + + for (sint32 py = 0; py < 4; py++) + { + for (sint32 px = 0; px < 4; px++) + { + uint8 colorIndex = (colorIndices >> (2 * (px + 4 * py))) & 0x03; + sint32 pixelOffset = (px + py * 4) * 4; + imageRGBA[pixelOffset + 0] = r[colorIndex]; + imageRGBA[pixelOffset + 1] = g[colorIndex]; + imageRGBA[pixelOffset + 2] = b[colorIndex]; + //imageRGBA[pixelOffset+3] = 1.0f; // alpha + } + } + + // decode alpha + uint8 alpha0 = *(uint8*)(inputData + 0); + uint8 alpha1 = *(uint8*)(inputData + 1); + uint32 alphaCodeRow[2] = { 0 }; + alphaCodeRow[0] |= ((*(uint8*)(inputData + 2)) << 0); + alphaCodeRow[0] |= ((*(uint8*)(inputData + 3)) << 8); + alphaCodeRow[0] |= ((*(uint8*)(inputData + 4)) << 16); + alphaCodeRow[1] |= ((*(uint8*)(inputData + 5)) << 0); + alphaCodeRow[1] |= ((*(uint8*)(inputData + 6)) << 8); + alphaCodeRow[1] |= ((*(uint8*)(inputData + 7)) << 16); + + float a[8]; + a[0] = (float)alpha0 / 255.0f; + a[1] = (float)alpha1 / 255.0f; + + if (alpha0 > alpha1) + { + // 6 interpolated alpha values. + a[2] = (a[0] * 6.0f + a[1] * 1.0f) / 7.0f; + a[3] = (a[0] * 5.0f + a[1] * 2.0f) / 7.0f; + a[4] = (a[0] * 4.0f + a[1] * 3.0f) / 7.0f; + a[5] = (a[0] * 3.0f + a[1] * 4.0f) / 7.0f; + a[6] = (a[0] * 2.0f + a[1] * 5.0f) / 7.0f; + a[7] = (a[0] * 1.0f + a[1] * 6.0f) / 7.0f; + } + else + { + // 4 interpolated alpha values. + a[2] = (a[0] * 4.0f + a[1] * 1.0f) / 5.0f; + a[3] = (a[0] * 3.0f + a[1] * 2.0f) / 5.0f; + a[4] = (a[0] * 2.0f + a[1] * 3.0f) / 5.0f; + a[5] = (a[0] * 1.0f + a[1] * 4.0f) / 5.0f; + a[6] = 0.0f; + a[7] = 1.0f; + } + + for (sint32 py = 0; py < 4; py++) + { + for (sint32 px = 0; px < 4; px++) + { + uint8 alphaCode = (alphaCodeRow[py / 2] >> 3 * (px + 4 * (py & 1))) & 0x07; + sint32 pixelOffset = (px + py * 4) * 4; + imageRGBA[pixelOffset + 3] = a[alphaCode]; // alpha + } + } +} + +void decodeBC4Block_UNORM(uint8* blockStorage, float* rOutput) +{ + uint8* blockInput = (uint8*)blockStorage; + float red[8]; + + red[0] = ((float)(*(uint8*)(blockInput + 0))) / 255.0f; + red[1] = ((float)(*(uint8*)(blockInput + 1))) / 255.0f; + + if (blockInput[0] > blockInput[1]) + { + // 6 interpolated color values + red[2] = (6 * red[0] + 1 * red[1]) / 7.0f; // bit code 010 + red[3] = (5 * red[0] + 2 * red[1]) / 7.0f; // bit code 011 + red[4] = (4 * red[0] + 3 * red[1]) / 7.0f; // bit code 100 + red[5] = (3 * red[0] + 4 * red[1]) / 7.0f; // bit code 101 + red[6] = (2 * red[0] + 5 * red[1]) / 7.0f; // bit code 110 + red[7] = (1 * red[0] + 6 * red[1]) / 7.0f; // bit code 111 + } + else + { + // 4 interpolated color values + red[2] = (4 * red[0] + 1 * red[1]) / 5.0f; // bit code 010 + red[3] = (3 * red[0] + 2 * red[1]) / 5.0f; // bit code 011 + red[4] = (2 * red[0] + 3 * red[1]) / 5.0f; // bit code 100 + red[5] = (1 * red[0] + 4 * red[1]) / 5.0f; // bit code 101 + red[6] = 0.0f; // bit code 110 + red[7] = 1.0f; // bit code 111 + } + + uint8* bitIndices = blockInput + 2; + uint32 redRow0 = (((uint32)bitIndices[2]) << 16) | (((uint32)bitIndices[1]) << 8) | (((uint32)bitIndices[0]) << 0); + uint32 redRow1 = (((uint32)bitIndices[5]) << 16) | (((uint32)bitIndices[4]) << 8) | (((uint32)bitIndices[3]) << 0); + + uint8 pRed[16]; + for (sint32 i = 0; i < 8; i++) + { + pRed[i] = (redRow0 >> (i * 3)) & 7; + pRed[i + 8] = (redRow1 >> (i * 3)) & 7; + } + + float* pixelOutput = rOutput; + for (sint32 py = 0; py < 4; py++) + { + for (sint32 px = 0; px < 4; px++) + { + float c = red[pRed[px + py * 4]]; + *pixelOutput = c; + pixelOutput++; + } + } +} + +void decodeBC5Block_UNORM(uint8* blockStorage, float* rgOutput) +{ + uint8* blockInput = (uint8*)blockStorage; + float red[8]; + float green[8]; + + red[0] = ((float)(*(uint8*)(blockInput + 0))) / 255.0f; + red[1] = ((float)(*(uint8*)(blockInput + 1))) / 255.0f; + + if (red[0] > red[1]) + { + // 6 interpolated color values + red[2] = (6 * red[0] + 1 * red[1]) / 7.0f; // bit code 010 + red[3] = (5 * red[0] + 2 * red[1]) / 7.0f; // bit code 011 + red[4] = (4 * red[0] + 3 * red[1]) / 7.0f; // bit code 100 + red[5] = (3 * red[0] + 4 * red[1]) / 7.0f; // bit code 101 + red[6] = (2 * red[0] + 5 * red[1]) / 7.0f; // bit code 110 + red[7] = (1 * red[0] + 6 * red[1]) / 7.0f; // bit code 111 + } + else + { + // 4 interpolated color values + red[2] = (4 * red[0] + 1 * red[1]) / 5.0f; // bit code 010 + red[3] = (3 * red[0] + 2 * red[1]) / 5.0f; // bit code 011 + red[4] = (2 * red[0] + 3 * red[1]) / 5.0f; // bit code 100 + red[5] = (1 * red[0] + 4 * red[1]) / 5.0f; // bit code 101 + red[6] = 0.0f; // bit code 110 + red[7] = 1.0f; // bit code 111 + } + + green[0] = ((float)(*(uint8*)(blockInput + 8))) / 255.0f; + green[1] = ((float)(*(uint8*)(blockInput + 9))) / 255.0f; + + if (green[0] > green[1]) + { + // 6 interpolated color values + green[2] = (6 * green[0] + 1 * green[1]) / 7.0f; // bit code 010 + green[3] = (5 * green[0] + 2 * green[1]) / 7.0f; // bit code 011 + green[4] = (4 * green[0] + 3 * green[1]) / 7.0f; // bit code 100 + green[5] = (3 * green[0] + 4 * green[1]) / 7.0f; // bit code 101 + green[6] = (2 * green[0] + 5 * green[1]) / 7.0f; // bit code 110 + green[7] = (1 * green[0] + 6 * green[1]) / 7.0f; // bit code 111 + } + else + { + // 4 interpolated color values + green[2] = (4 * green[0] + 1 * green[1]) / 5.0f; // bit code 010 + green[3] = (3 * green[0] + 2 * green[1]) / 5.0f; // bit code 011 + green[4] = (2 * green[0] + 3 * green[1]) / 5.0f; // bit code 100 + green[5] = (1 * green[0] + 4 * green[1]) / 5.0f; // bit code 101 + green[6] = 0.0f; // bit code 110 + green[7] = 1.0f; // bit code 111 + } + + + uint8* bitIndices = blockInput + 2; + uint32 redRow0 = (((uint32)bitIndices[2]) << 16) | (((uint32)bitIndices[1]) << 8) | (((uint32)bitIndices[0]) << 0); + uint32 redRow1 = (((uint32)bitIndices[5]) << 16) | (((uint32)bitIndices[4]) << 8) | (((uint32)bitIndices[3]) << 0); + bitIndices = blockInput + 8 + 2; + uint32 greenRow0 = (((uint32)bitIndices[2]) << 16) | (((uint32)bitIndices[1]) << 8) | (((uint32)bitIndices[0]) << 0); + uint32 greenRow1 = (((uint32)bitIndices[5]) << 16) | (((uint32)bitIndices[4]) << 8) | (((uint32)bitIndices[3]) << 0); + + uint8 pRed[16]; + uint8 pGreen[16]; + for (sint32 i = 0; i < 8; i++) + { + pRed[i] = (redRow0 >> (i * 3)) & 7; + pRed[i + 8] = (redRow1 >> (i * 3)) & 7; + pGreen[i] = (greenRow0 >> (i * 3)) & 7; + pGreen[i + 8] = (greenRow1 >> (i * 3)) & 7; + } + + float* pixelOutput = rgOutput; + for (sint32 py = 0; py < 4; py++) + { + for (sint32 px = 0; px < 4; px++) + { + float c = red[pRed[px + py * 4]]; + *pixelOutput = c; + pixelOutput++; + c = green[pGreen[px + py * 4]]; + *pixelOutput = c; + pixelOutput++; + } + } +} + +void decodeBC5Block_SNORM(uint8* blockStorage, float* rgOutput) // todo - can merge this with the UNORM implementation by using a template? +{ + uint8* blockInput = (uint8*)blockStorage; + float red[8]; + float green[8]; + + red[0] = ((float)(*(sint8*)(blockInput + 0)) + 128.0f) / 255.0f; + red[1] = ((float)(*(sint8*)(blockInput + 1)) + 128.0f) / 255.0f; + red[0] = (red[0] * 2.0f - 1.0f); + red[1] = (red[1] * 2.0f - 1.0f); + + if (red[0] > red[1]) + { + // 6 interpolated color values + red[2] = (6 * red[0] + 1 * red[1]) / 7.0f; // bit code 010 + red[3] = (5 * red[0] + 2 * red[1]) / 7.0f; // bit code 011 + red[4] = (4 * red[0] + 3 * red[1]) / 7.0f; // bit code 100 + red[5] = (3 * red[0] + 4 * red[1]) / 7.0f; // bit code 101 + red[6] = (2 * red[0] + 5 * red[1]) / 7.0f; // bit code 110 + red[7] = (1 * red[0] + 6 * red[1]) / 7.0f; // bit code 111 + } + else + { + // 4 interpolated color values + red[2] = (4 * red[0] + 1 * red[1]) / 5.0f; // bit code 010 + red[3] = (3 * red[0] + 2 * red[1]) / 5.0f; // bit code 011 + red[4] = (2 * red[0] + 3 * red[1]) / 5.0f; // bit code 100 + red[5] = (1 * red[0] + 4 * red[1]) / 5.0f; // bit code 101 + red[6] = -1.0f; // bit code 110 + red[7] = 1.0f; // bit code 111 + } + + green[0] = ((float)(*(sint8*)(blockInput + 8)) + 128.0f) / 255.0f; + green[1] = ((float)(*(sint8*)(blockInput + 9)) + 128.0f) / 255.0f; + green[0] = (green[0] * 2.0f - 1.0f); + green[1] = (green[1] * 2.0f - 1.0f); + + if (green[0] > green[1]) + { + // 6 interpolated color values + green[2] = (6 * green[0] + 1 * green[1]) / 7.0f; // bit code 010 + green[3] = (5 * green[0] + 2 * green[1]) / 7.0f; // bit code 011 + green[4] = (4 * green[0] + 3 * green[1]) / 7.0f; // bit code 100 + green[5] = (3 * green[0] + 4 * green[1]) / 7.0f; // bit code 101 + green[6] = (2 * green[0] + 5 * green[1]) / 7.0f; // bit code 110 + green[7] = (1 * green[0] + 6 * green[1]) / 7.0f; // bit code 111 + } + else + { + // 4 interpolated color values + green[2] = (4 * green[0] + 1 * green[1]) / 5.0f; // bit code 010 + green[3] = (3 * green[0] + 2 * green[1]) / 5.0f; // bit code 011 + green[4] = (2 * green[0] + 3 * green[1]) / 5.0f; // bit code 100 + green[5] = (1 * green[0] + 4 * green[1]) / 5.0f; // bit code 101 + green[6] = -1.0f; // bit code 110 + green[7] = 1.0f; // bit code 111 + } + + + uint8* bitIndices = blockInput + 2; + uint32 redRow0 = (((uint32)bitIndices[2]) << 16) | (((uint32)bitIndices[1]) << 8) | (((uint32)bitIndices[0]) << 0); + uint32 redRow1 = (((uint32)bitIndices[5]) << 16) | (((uint32)bitIndices[4]) << 8) | (((uint32)bitIndices[3]) << 0); + bitIndices = blockInput + 8 + 2; + uint32 greenRow0 = (((uint32)bitIndices[2]) << 16) | (((uint32)bitIndices[1]) << 8) | (((uint32)bitIndices[0]) << 0); + uint32 greenRow1 = (((uint32)bitIndices[5]) << 16) | (((uint32)bitIndices[4]) << 8) | (((uint32)bitIndices[3]) << 0); + + uint8 pRed[16]; + uint8 pGreen[16]; + for (sint32 i = 0; i < 8; i++) + { + pRed[i] = (redRow0 >> (i * 3)) & 7; + pRed[i + 8] = (redRow1 >> (i * 3)) & 7; + pGreen[i] = (greenRow0 >> (i * 3)) & 7; + pGreen[i + 8] = (greenRow1 >> (i * 3)) & 7; + } + + for (sint32 py = 0; py < 4; py++) + { + float* pixelOutput = rgOutput + (py * 4) * 2; + for (sint32 px = 0; px < 4; px++) + { + float c = red[pRed[px + py * 4]]; + pixelOutput[0] = c; + c = green[pGreen[px + py * 4]]; + pixelOutput[1] = c; + pixelOutput += 2; + } + } +} + +void LatteTextureLoader_loadTextureDataIntoSlice(LatteTexture* hostTexture, sint32 width, sint32 height, sint32 depth, sint32 mipLevels, void* pixelData, sint32 sliceIndex, sint32 mipIndex, uint32 compressedImageSize) +{ + if (mipIndex == 0) + { + cemu_assert_debug(width == hostTexture->width); + cemu_assert_debug(height == hostTexture->height); + cemu_assert_debug(depth == hostTexture->depth); + } + cemu_assert_debug(mipLevels == hostTexture->mipLevels); + if (hostTexture->overwriteInfo.hasResolutionOverwrite || hostTexture->overwriteInfo.hasFormatOverwrite) + { + // todo - ideally, we should scale/convert the data to the new format and resolution + g_renderer->texture_clearSlice(hostTexture, sliceIndex, mipIndex); + } + else + { + g_renderer->texture_loadSlice(hostTexture, width, height, depth, pixelData, sliceIndex, mipIndex, compressedImageSize); + } +} + +void LatteTextureLoader_UpdateTextureSliceData(LatteTexture* tex, sint32 textureUnit, uint32 sliceIndex, uint32 mipIndex, MPTR physImagePtr, MPTR physMipPtr, Latte::E_DIM dim, uint32 width, uint32 height, uint32 depth, uint32 mipLevels, uint32 pitch, Latte::E_HWTILEMODE tileMode, uint32 swizzle, bool dumpTex) +{ + LatteTextureLoaderCtx textureLoader = { 0 }; + + Latte::E_GX2SURFFMT format = tex->format; + LatteTextureLoader_begin(&textureLoader, sliceIndex, mipIndex, physImagePtr, physMipPtr, format, dim, width, height, depth, mipLevels, pitch, tileMode, swizzle); + + // enable texture dumping + textureLoader.dump = ActiveSettings::DumpTexturesEnabled(); + if (textureLoader.dump) + { + uint32 dumpSize = (((textureLoader.width + 4)&~4) * ((textureLoader.height + 4)&~4)) * 4; + textureLoader.dumpRGBA = (uint8*)malloc(dumpSize); + memset(textureLoader.dumpRGBA, 0x00, dumpSize); + } + + // query texture decoder from renderer + TextureDecoder* texDecoder = nullptr; + texDecoder = g_renderer->texture_chooseDecodedFormat(format, tex->isDepth, dim, width, height); + + if (tex->isDataDefined == false) + { + g_renderer->texture_reserveTextureOnGPU(tex); + tex->isDataDefined = true; + // if decoder is not set then clear texture + // on Vulkan this is used to make sure the texture is no longer in UNDEFINED layout + if (!texDecoder) + { + if(tex->isDepth) + g_renderer->texture_clearDepthSlice(tex, 0, 0, true, tex->hasStencil, 0.0f, 0); + else + g_renderer->texture_clearColorSlice(tex, 0, 0, 0.0f, 0.0f, 0.0f, 0.0f); + } + } + + if (texDecoder == nullptr) + return; + + textureLoader.decodedTexelCountX = texDecoder->getTexelCountX(&textureLoader); + textureLoader.decodedTexelCountY = texDecoder->getTexelCountY(&textureLoader); + + // allocate memory for decoded texture + uint32 imageSize = texDecoder->calculateImageSize(&textureLoader); + + uint8* pixelData = (uint8*)g_renderer->texture_acquireTextureUploadBuffer(imageSize); + // decode texture (if data is required) +#ifdef BENCHMARK_TEXTURE_DECODING + LARGE_INTEGER benchmark_begin; + LARGE_INTEGER benchmark_end; + LARGE_INTEGER benchmark_freq; + QueryPerformanceCounter(&benchmark_begin); +#endif + if (tex->overwriteInfo.hasFormatOverwrite == false && tex->overwriteInfo.hasResolutionOverwrite == false) + { + texDecoder->decode(&textureLoader, pixelData); + } +#ifdef BENCHMARK_TEXTURE_DECODING + QueryPerformanceCounter(&benchmark_end); + QueryPerformanceFrequency(&benchmark_freq); + uint64 benchmarkResultMicroSeconds = (benchmark_end.QuadPart - benchmark_begin.QuadPart) * 1000000ULL / benchmark_freq.QuadPart; + textureDecodeBenchmark_perFormatSum[(int)tex->format & 0x3F] += benchmarkResultMicroSeconds; + textureDecodeBenchmark_totalSum += benchmarkResultMicroSeconds; + forceLog_printf("TexDecode %04dx%04dx%04d Fmt %04x Dim %d TileMode %02x Took %03d.%03dms Sum(format) %06dms Sum(total) %06dms", textureLoader.width, textureLoader.height, textureLoader.surfaceInfoDepth, (int)tex->format, (int)tex->dim, textureLoader.tileMode, (uint32)(benchmarkResultMicroSeconds / 1000ULL), (uint32)(benchmarkResultMicroSeconds % 1000ULL), (uint32)(textureDecodeBenchmark_perFormatSum[tex->gx2Format & 0x3F] / 1000ULL), (uint32)(textureDecodeBenchmark_totalSum / 1000ULL)); +#endif + + // convert texture to RGBA when dumping is enabled + if (textureLoader.dump) + { + for (sint32 y = 0; y < textureLoader.height; y++) + { + sint32 pixelOffset = (y * textureLoader.width) * 4; + uint8* pixelOutput = textureLoader.dumpRGBA + pixelOffset; + for (sint32 x = 0; x < textureLoader.width; x++) + { + uint8* blockData = LatteTextureLoader_GetInput(&textureLoader, x, y); + texDecoder->decodePixelToRGBA(blockData, pixelOutput, x % textureLoader.stepX, y % textureLoader.stepY); + pixelOutput += 4; + } + } + } + + // update texture data offsets and hashes + // this has to be done before the texture data is decoded & uploaded to prevent a race condition where updates during upload are missed + if (mipIndex == 0 || (tex->texDataPtrLow == 0 && tex->texDataPtrHigh == 0)) + { + tex->texDataPtrLow = physImagePtr + textureLoader.minOffsetOutdated; // always zero + tex->texDataPtrHigh = physImagePtr + textureLoader.maxOffsetOutdated; // currently set to surface size + LatteTC_ResetTextureChangeTracker(tex, true); + } + // load slice + //debug_printf("[Load Slice] Addr: %08x MIP: %02d Slice: %02d Res %04x/%04x Texel Res %04x/%04x Fmt %04x Tm %d\n", textureLoader.physAddress, mipIndex, sliceIndex, textureLoader.width, textureLoader.height, textureLoader.texelCountX, textureLoader.texelCountY, (int)format, tileMode); + LatteTextureLoader_loadTextureDataIntoSlice(tex, textureLoader.width, textureLoader.height, depth, mipLevels, pixelData, sliceIndex, mipIndex, imageSize); + // write texture dump + if (textureLoader.dump) + { + wchar_t path[1024]; + swprintf(path, 1024, L"dump\\textures\\%08x_fmt%04x_slice%d_mip%02d_%dx%d_tm%02d.tga", physImagePtr, (uint32)tex->format, sliceIndex, mipIndex, tex->width, tex->height, tileMode); + tga_write_rgba(path, textureLoader.width, textureLoader.height, textureLoader.dumpRGBA); + free(textureLoader.dumpRGBA); + } + // clean up + g_renderer->texture_releaseTextureUploadBuffer(pixelData); + catchOpenGLError(); +} + +template<typename copyType> +void optimizedLinearReadbackWriteLoop(LatteTextureLoaderCtx* textureLoader, uint8* linearPixelData) +{ + uint32 pitch = textureLoader->width; + // optimized for linear + for (sint32 y = 0; y < textureLoader->height; y++) + { + sint32 yc = y; + sint32 pixelOffset = yc * pitch; + copyType* rowPixelData = (copyType*)(linearPixelData + pixelOffset * sizeof(copyType)); + copyType* blockData = (copyType*)LatteTextureLoader_getInputLinearOptimized_(textureLoader, 0, y, 1, 1, sizeof(copyType) * 8, 0, 1, 0, textureLoader->pitch, textureLoader->height); + if constexpr (sizeof(copyType) == 4) + { + memcpy_dwords(blockData, rowPixelData, textureLoader->width); + } + else + { + for (sint32 x = 0; x < textureLoader->width; x++) + { + *blockData = *rowPixelData; + rowPixelData++; + blockData++; + } + } + } +} + +void LatteTextureLoader_writeReadbackTextureToMemory(LatteTextureDefinition* textureData, uint32 sliceIndex, uint32 mipIndex, uint8* linearPixelData) +{ + LatteTextureLoaderCtx textureLoader = { 0 }; + LatteTextureLoader_begin(&textureLoader, sliceIndex, mipIndex, textureData->physAddress, textureData->physMipAddress, textureData->format, textureData->dim, textureData->width, textureData->height, textureData->depth, textureData->mipLevels, textureData->pitch, textureData->tileMode, textureData->swizzle); + +#ifndef PUBLIC_RELEASE + if (textureData->depth != 1) + forceLog_printf("_writeReadbackTextureToMemory(): Texture has multiple slices (not supported)"); +#endif + if (textureLoader.physAddress == MPTR_NULL) + { + forceLog_printf("_writeReadbackTextureToMemory(): Texture has invalid address"); + return; + } + + if (textureData->tileMode == Latte::E_HWTILEMODE::TM_LINEAR_ALIGNED) + { + uint32 pitch = textureLoader.width; + if (textureData->format == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM || + textureData->format == Latte::E_GX2SURFFMT::R8_G8_B8_A8_SRGB) + { + optimizedLinearReadbackWriteLoop<uint32>(&textureLoader, linearPixelData); + } + else if (textureData->format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_UNORM) + { + optimizedLinearReadbackWriteLoop<uint64>(&textureLoader, linearPixelData); + } + else if (textureData->format == Latte::E_GX2SURFFMT::R32_G32_B32_A32_FLOAT) + { + for (sint32 y = 0; y < textureLoader.height; y += textureLoader.stepY) + { + sint32 yc = y; + sint32 pixelOffset = (0 + yc * pitch) * 16; + for (sint32 x = 0; x < textureLoader.width; x += textureLoader.stepX) + { + uint8* blockData = LatteTextureLoader_getInputLinearOptimized(&textureLoader, x, y); + (*(uint32*)(blockData + 0)) = *(uint32*)(linearPixelData + pixelOffset + 0); + (*(uint32*)(blockData + 4)) = *(uint32*)(linearPixelData + pixelOffset + 4); + (*(uint32*)(blockData + 8)) = *(uint32*)(linearPixelData + pixelOffset + 8); + (*(uint32*)(blockData + 12)) = *(uint32*)(linearPixelData + pixelOffset + 12); + pixelOffset += 16; + } + } + } + else if (textureData->format == Latte::E_GX2SURFFMT::R32_FLOAT) + { + for (sint32 y = 0; y < textureLoader.height; y += textureLoader.stepY) + { + sint32 yc = y; + for (sint32 x = 0; x < textureLoader.width; x += textureLoader.stepX) + { + uint8* blockData = LatteTextureLoader_getInputLinearOptimized(&textureLoader, x, y); + sint32 pixelOffset = (x + yc * pitch) * 4; + (*(uint32*)(blockData + 0)) = *(uint32*)(linearPixelData + pixelOffset + 0); + } + } + } + else if (textureData->format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT) + { + for (sint32 y = 0; y < textureLoader.height; y += textureLoader.stepY) + { + sint32 yc = y; + for (sint32 x = 0; x < textureLoader.width; x += textureLoader.stepX) + { + uint8* blockData = LatteTextureLoader_getInputLinearOptimized(&textureLoader, x, y); + sint32 pixelOffset = (x + yc * pitch) * 8; + (*(uint32*)(blockData + 0)) = *(uint32*)(linearPixelData + pixelOffset + 0); + (*(uint32*)(blockData + 4)) = *(uint32*)(linearPixelData + pixelOffset + 4); + } + } + } + else if (textureData->format == Latte::E_GX2SURFFMT::R8_G8_UNORM) + { + optimizedLinearReadbackWriteLoop<uint16>(&textureLoader, linearPixelData); + } + else if (textureData->format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_UNORM) + { + cemu_assert_unimplemented(); + } + else if (textureData->format == Latte::E_GX2SURFFMT::R16_UNORM) + { + optimizedLinearReadbackWriteLoop<uint16>(&textureLoader, linearPixelData); + } + else + { + forceLogDebug_printf("Linear texture readback unsupported for format 0x%04x", (uint32)textureData->format); + debugBreakpoint(); + } + return; + } + // generic and slow decode loops + Latte::E_HWSURFFMT hwFormat = Latte::GetHWFormat(textureData->format); + if (hwFormat == Latte::E_HWSURFFMT::HWFMT_8_8_8_8) + { + // used in Bayonetta 2 + for (sint32 y = 0; y < textureLoader.height; y++) + { + uint8* pixelInput = linearPixelData + (y * textureLoader.width) * 4; + for (sint32 x = 0; x < textureLoader.width; x++) + { + uint8* outputData = LatteTextureLoader_GetInput(&textureLoader, x, y); + *(uint32*)(outputData + 0) = *(uint32*)pixelInput; + pixelInput += 4; + } + } + } + else if (hwFormat == Latte::E_HWSURFFMT::HWFMT_32_FLOAT) + { + // required by Wind Waker for direct access to depth buffer + // Bayonetta 2 also uses this but it converts the depth buffer to a color texture first + for (sint32 y = 0; y < textureLoader.height; y++) + { + uint8* pixelInput = linearPixelData + (y * textureLoader.width) * 4; + for (sint32 x = 0; x < textureLoader.width; x++) + { + uint8* outputData = LatteTextureLoader_GetInput(&textureLoader, x, y); + *(uint32*)(outputData + 0) = *(uint32*)pixelInput; + pixelInput += 4; + } + } + } + else + { + forceLogDebug_printf("Texture readback unsupported format %04x for tileMode 0x%02x", (uint32)textureData->format, textureData->tileMode); + } + +} + +void LatteTextureLoader_estimateAccessedDataRange(LatteTexture* texture, sint32 sliceIndex, sint32 mipIndex, uint32& addrStart, uint32& addrEnd) +{ + LatteTextureLoaderCtx textureLoader = { 0 }; + LatteTextureLoader_begin(&textureLoader, sliceIndex, mipIndex, texture->physAddress, texture->physMipAddress, texture->format, texture->dim, texture->width, texture->height, texture->depth, texture->mipLevels, texture->pitch, texture->tileMode, texture->swizzle); + + cemu_assert_debug(textureLoader.width > 0); + cemu_assert_debug(textureLoader.height > 0); + + // estimate data range by checking addresses of corner pixels + // this isn't very reliable, find a better solution + uint32 estimatedMinAddr = 0xFFFFFFFF; + uint32 estimatedMaxAddr = 0x00000000; + uint32 tempAddr; + tempAddr = memory_getVirtualOffsetFromPointer(LatteTextureLoader_GetInput(&textureLoader, 0, 0)); + estimatedMinAddr = std::min(estimatedMinAddr, tempAddr); + estimatedMaxAddr = std::max(estimatedMaxAddr, tempAddr); + tempAddr = memory_getVirtualOffsetFromPointer(LatteTextureLoader_GetInput(&textureLoader, textureLoader.width - 1, 0)); + estimatedMinAddr = std::min(estimatedMinAddr, tempAddr); + estimatedMaxAddr = std::max(estimatedMaxAddr, tempAddr); + tempAddr = memory_getVirtualOffsetFromPointer(LatteTextureLoader_GetInput(&textureLoader, 0, textureLoader.height - 1)); + estimatedMinAddr = std::min(estimatedMinAddr, tempAddr); + estimatedMaxAddr = std::max(estimatedMaxAddr, tempAddr); + tempAddr = memory_getVirtualOffsetFromPointer(LatteTextureLoader_GetInput(&textureLoader, textureLoader.width - 1, textureLoader.height - 1)); + estimatedMinAddr = std::min(estimatedMinAddr, tempAddr); + estimatedMaxAddr = std::max(estimatedMaxAddr, tempAddr); + + addrStart = estimatedMinAddr; + addrEnd = estimatedMaxAddr; +} diff --git a/src/Cafe/HW/Latte/Core/LatteTextureLoader.h b/src/Cafe/HW/Latte/Core/LatteTextureLoader.h new file mode 100644 index 00000000..7c89a09a --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteTextureLoader.h @@ -0,0 +1,1864 @@ +#pragma once +#include "util/helpers/ClassWrapper.h" +#include "Cafe/HW/Latte/LatteAddrLib/LatteAddrLib.h" +#include "util/ImageWriter/tga.h" + +struct LatteTextureLoaderCtx +{ + uint32 physAddress; + uint32 physMipAddress; + sint32 width; + sint32 height; + sint32 pitch; // stored elements per row + uint32 mipLevels; + uint32 sliceIndex; + sint32 stepX; + sint32 stepY; + uint32 pipeSwizzle; + uint32 bankSwizzle; + Latte::E_HWTILEMODE tileMode; + uint32 bpp; + uint8* inputData; + sint32 minOffsetOutdated; + sint32 maxOffsetOutdated; + // calculated info + uint32 surfaceInfoHeight; + uint32 surfaceInfoDepth; + // mip + uint32 levelOffset; // relative to physMipAddress + // info for decoded texture + sint32 decodedTexelCountX; + sint32 decodedTexelCountY; + // decoder + LatteAddrLib::CachedSurfaceAddrInfo computeAddrInfo; + // debug dump texture + bool dump; + uint8* dumpRGBA; +}; + +uint8* LatteTextureLoader_GetInput(LatteTextureLoaderCtx* textureLoader, sint32 x, sint32 y); + +#include "Cafe/HW/Latte/LatteAddrLib/AddrLibFastDecode.h" + +void decodeBC1Block(uint8* inputData, float* output4x4RGBA); +void decodeBC2Block_UNORM(uint8* inputData, float* imageRGBA); +void decodeBC3Block_UNORM(uint8* inputData, float* imageRGBA); +void decodeBC4Block_UNORM(uint8* blockStorage, float* rOutput); +void decodeBC5Block_UNORM(uint8* blockStorage, float* rgOutput); +void decodeBC5Block_SNORM(uint8* blockStorage, float* rgOutput); + +inline void BC1_GetPixel(uint8* inputData, sint32 x, sint32 y, uint8 rgba[4]) +{ + // read colors + uint16 c0 = *(uint16*)(inputData + 0); + uint16 c1 = *(uint16*)(inputData + 2); + // decode colors (RGB565 -> RGB888) + float r[4]; + float g[4]; + float b[4]; + float a[4]; + b[0] = (float)((c0 >> 0) & 0x1F) / 31.0f; + b[1] = (float)((c1 >> 0) & 0x1F) / 31.0f; + g[0] = (float)((c0 >> 5) & 0x3F) / 63.0f; + g[1] = (float)((c1 >> 5) & 0x3F) / 63.0f; + r[0] = (float)((c0 >> 11) & 0x1F) / 31.0f; + r[1] = (float)((c1 >> 11) & 0x1F) / 31.0f; + a[0] = 1.0f; + a[1] = 1.0f; + a[2] = 1.0f; + + if (c0 > c1) + { + r[2] = (r[0] * 2.0f + r[1]) / 3.0f; + r[3] = (r[0] * 1.0f + r[1] * 2.0f) / 3.0f; + g[2] = (g[0] * 2.0f + g[1]) / 3.0f; + g[3] = (g[0] * 1.0f + g[1] * 2.0f) / 3.0f; + b[2] = (b[0] * 2.0f + b[1]) / 3.0f; + b[3] = (b[0] * 1.0f + b[1] * 2.0f) / 3.0f; + a[3] = 1.0f; + } + else + { + r[2] = (r[0] + r[1]) / 2.0f; + r[3] = 0.0f; + g[2] = (g[0] + g[1]) / 2.0f; + g[3] = 0.0f; + b[2] = (b[0] + b[1]) / 2.0f; + b[3] = 0.0f; + a[3] = 0.0f; + } + + uint8* indexData = inputData + 4; + + uint8 i = (indexData[y] >> (x * 2)) & 3; + rgba[0] = (uint8)(r[i] * 255.0f); + rgba[1] = (uint8)(g[i] * 255.0f); + rgba[2] = (uint8)(b[i] * 255.0f); + rgba[3] = (uint8)(a[i] * 255.0f); +} + +// decodes a specific GPU7 texture format into a native linear format that can be used by the render API +class TextureDecoder +{ +public: + // properties + virtual sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) = 0; + virtual sint32 getTexelCountX(LatteTextureLoaderCtx* textureLoader) + { + return textureLoader->width; + } + + virtual sint32 getTexelCountY(LatteTextureLoaderCtx* textureLoader) + { + return textureLoader->height; + } + + // image size + virtual sint32 calculateImageSize(LatteTextureLoaderCtx* textureLoader) + { + return getTexelCountX(textureLoader) * getTexelCountY(textureLoader) * getBytesPerTexel(textureLoader); + } + + // decode loop + virtual void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) = 0; + + virtual void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) = 0; +}; + +class TextureDecoder_R16_G16_B16_A16_FLOAT : public TextureDecoder, public SingletonClass<TextureDecoder_R16_G16_B16_A16_FLOAT> +{ +public: + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 2 * 4; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + optimizedDecodeLoops<uint64, 1, false, false>(textureLoader, outputData); + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + // todo: dump 16 bit float formats properly + float r16f = (float)(*((uint16*)blockData + 0)); + float g16f = (float)(*((uint16*)blockData + 1)); + float b16f = (float)(*((uint16*)blockData + 2)); + float a16f = (float)(*((uint16*)blockData + 3)); + *(outputPixel + 0) = (uint8)(r16f * 255.0f); + *(outputPixel + 1) = (uint8)(g16f * 255.0f); + *(outputPixel + 2) = (uint8)(b16f * 255.0f); + *(outputPixel + 3) = (uint8)(a16f * 255.0f); + } +}; + +class TextureDecoder_R16_G16_FLOAT : public TextureDecoder, public SingletonClass<TextureDecoder_R16_G16_FLOAT> +{ +public: + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 2 * 2; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + optimizedDecodeLoops<uint32, 1, false, false>(textureLoader, outputData); + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + // todo: dump 16 bit float formats properly + float r16f = (float)(*((uint16*)blockData + 0)); + float g16f = (float)(*((uint16*)blockData + 1)); + *(outputPixel + 0) = (uint8)(r16f * 255.0f); + *(outputPixel + 1) = (uint8)(g16f * 255.0f); + *(outputPixel + 2) = 0; + *(outputPixel + 3) = 255; + } +}; + +class TextureDecoder_R16_SNORM : public TextureDecoder, public SingletonClass<TextureDecoder_R16_SNORM> +{ +public: + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 1 * 2; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + optimizedDecodeLoops<uint16, 1, false, false>(textureLoader, outputData); + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + *(outputPixel + 0) = (uint8)(*(((uint16*)blockData) + 0) >> 8) / 2 + 128; + *(outputPixel + 1) = 0; + *(outputPixel + 2) = 0; + *(outputPixel + 3) = 255; + } +}; + +class TextureDecoder_R16_FLOAT : public TextureDecoder, public SingletonClass<TextureDecoder_R16_FLOAT> +{ +public: + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 1 * 2; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + optimizedDecodeLoops<uint16, 1, false, false>(textureLoader, outputData); + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + // todo: dump 16 bit float formats properly + float r16f = (float)(*((uint16*)blockData + 0)); + *(outputPixel + 0) = (uint8)(r16f * 255.0f); + *(outputPixel + 1) = 0; + *(outputPixel + 2) = 0; + *(outputPixel + 3) = 255; + } +}; + +class TextureDecoder_R32_FLOAT : public TextureDecoder, public SingletonClass<TextureDecoder_R32_FLOAT> +{ +public: + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 1 * 4; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + optimizedDecodeLoops<uint32, 1, false, false>(textureLoader, outputData); + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + float r32f = *((float*)blockData + 0); + *(outputPixel + 0) = (uint8)(r32f * 255.0f); + *(outputPixel + 1) = 0; + *(outputPixel + 2) = 0; + *(outputPixel + 3) = 255; + } +}; + +class TextureDecoder_R32_G32_FLOAT : public TextureDecoder, public SingletonClass<TextureDecoder_R32_G32_FLOAT> +{ +public: + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 2 * 4; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + optimizedDecodeLoops<uint64, 1, false, false>(textureLoader, outputData); + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + float r32f = *((float*)blockData + 0); + float g32f = *((float*)blockData + 1); + *(outputPixel + 0) = (uint8)(r32f * 255.0f); + *(outputPixel + 1) = (uint8)(g32f * 255.0f); + *(outputPixel + 2) = 0; + *(outputPixel + 3) = 255; + } +}; + +class TextureDecoder_R32_G32_UINT : public TextureDecoder, public SingletonClass<TextureDecoder_R32_G32_UINT> +{ +public: + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 2 * 4; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + optimizedDecodeLoops<uint64, 1, false, false>(textureLoader, outputData); + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + *(outputPixel + 0) = (uint8)(*(((uint32*)blockData) + 0) >> 24); + *(outputPixel + 1) = (uint8)(*(((uint32*)blockData) + 1) >> 24); + *(outputPixel + 2) = 0; + *(outputPixel + 3) = 255; + } +}; + + +class TextureDecoder_R32_UINT : public TextureDecoder, public SingletonClass<TextureDecoder_R32_UINT> +{ +public: + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 1 * 4; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + optimizedDecodeLoops<uint32, 1, false, false>(textureLoader, outputData); + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + *(outputPixel + 0) = (uint8)(*(((uint32*)blockData) + 0) >> 24); + *(outputPixel + 1) = 0; + *(outputPixel + 2) = 0; + *(outputPixel + 3) = 255; + } +}; + +class TextureDecoder_R16_UINT : public TextureDecoder, public SingletonClass<TextureDecoder_R16_UINT> +{ +public: + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 1 * 2; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + optimizedDecodeLoops<uint16, 1, false, false>(textureLoader, outputData); + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + *(outputPixel + 0) = (uint8)(*(((uint16*)blockData) + 0) >> 8); + *(outputPixel + 1) = 0; + *(outputPixel + 2) = 0; + *(outputPixel + 3) = 255; + } +}; + +class TextureDecoder_R8_UINT : public TextureDecoder, public SingletonClass<TextureDecoder_R8_UINT> +{ +public: + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 1; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + optimizedDecodeLoops<uint8, 1, false, false>(textureLoader, outputData); + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + *(outputPixel + 0) = *(blockData + 0); + *(outputPixel + 1) = 0; + *(outputPixel + 2) = 0; + *(outputPixel + 3) = 255; + } +}; + +class TextureDecoder_R32_G32_B32_A32_FLOAT : public TextureDecoder, public SingletonClass<TextureDecoder_R32_G32_B32_A32_FLOAT> +{ +public: + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 4 * 4; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + optimizedDecodeLoops<uint64, 2, false, false>(textureLoader, outputData); + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + float r32f = *((float*)blockData + 0); + float g32f = *((float*)blockData + 1); + float b32f = *((float*)blockData + 2); + float a32f = *((float*)blockData + 3); + *(outputPixel + 0) = (uint8)(r32f * 255.0f); + *(outputPixel + 1) = (uint8)(g32f * 255.0f); + *(outputPixel + 2) = (uint8)(b32f * 255.0f); + *(outputPixel + 3) = (uint8)(a32f * 255.0f); + } +}; + +class TextureDecoder_R32_G32_B32_A32_UINT : public TextureDecoder, public SingletonClass<TextureDecoder_R32_G32_B32_A32_UINT> +{ +public: + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 4 * 4; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + // note - before 1.15.4 this format was implemented as big-endian + //optimizedDecodeLoops<uint64, 2, false>(textureLoader, outputData); + + for (sint32 y = 0; y < textureLoader->height; y += textureLoader->stepY) + { + sint32 yc = y; + for (sint32 x = 0; x < textureLoader->width; x += textureLoader->stepX) + { + uint8* blockData = LatteTextureLoader_GetInput(textureLoader, x, y); + sint32 pixelOffset = (x + yc * textureLoader->width) * 4 * sizeof(uint32); // write to target buffer + *(uint32*)(outputData + pixelOffset + 0) = _swapEndianU32(*(uint32*)(blockData + 0)); + *(uint32*)(outputData + pixelOffset + 4) = _swapEndianU32(*(uint32*)(blockData + 4)); + *(uint32*)(outputData + pixelOffset + 8) = _swapEndianU32(*(uint32*)(blockData + 8)); + *(uint32*)(outputData + pixelOffset + 12) = _swapEndianU32(*(uint32*)(blockData + 12)); + // todo: Verify if this format is big-endian + } + } + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + *(outputPixel + 0) = (uint8)(*(((uint32*)blockData) + 0) >> 24); + *(outputPixel + 1) = (uint8)(*(((uint32*)blockData) + 1) >> 24); + *(outputPixel + 2) = (uint8)(*(((uint32*)blockData) + 2) >> 24); + *(outputPixel + 3) = (uint8)(*(((uint32*)blockData) + 3) >> 24); + } +}; + +class TextureDecoder_R16_G16_B16_A16_UINT : public TextureDecoder, public SingletonClass<TextureDecoder_R16_G16_B16_A16_UINT> +{ +public: + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 4 * 2; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + // note - before 1.15.4 this format was implemented as big-endian + //optimizedDecodeLoops<uint64, 1, false>(textureLoader, outputData); + + for (sint32 y = 0; y < textureLoader->height; y += textureLoader->stepY) + { + sint32 yc = y; + for (sint32 x = 0; x < textureLoader->width; x += textureLoader->stepX) + { + uint8* blockData = LatteTextureLoader_GetInput(textureLoader, x, y); + sint32 pixelOffset = (x + yc * textureLoader->width) * 4 * sizeof(uint16); // write to target buffer + *(uint16*)(outputData + pixelOffset + 0) = _swapEndianU16(*(uint16*)(blockData + 0)); + *(uint16*)(outputData + pixelOffset + 2) = _swapEndianU16(*(uint16*)(blockData + 2)); + *(uint16*)(outputData + pixelOffset + 4) = _swapEndianU16(*(uint16*)(blockData + 4)); + *(uint16*)(outputData + pixelOffset + 6) = _swapEndianU16(*(uint16*)(blockData + 6)); + // todo: Verify if this format is big-endian + } + } + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + *(outputPixel + 0) = (uint8)(*(((uint16*)blockData) + 0) >> 8); + *(outputPixel + 1) = (uint8)(*(((uint16*)blockData) + 1) >> 8); + *(outputPixel + 2) = (uint8)(*(((uint16*)blockData) + 2) >> 8); + *(outputPixel + 3) = (uint8)(*(((uint16*)blockData) + 3) >> 8); + } +}; + + +class TextureDecoder_R8_G8_B8_A8_UINT : public TextureDecoder, public SingletonClass<TextureDecoder_R8_G8_B8_A8_UINT> +{ +public: + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 4 * 1; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + optimizedDecodeLoops<uint32, 1, false, false>(textureLoader, outputData); + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + *(outputPixel + 0) = *(blockData + 0); + *(outputPixel + 1) = *(blockData + 1); + *(outputPixel + 2) = *(blockData + 2); + *(outputPixel + 3) = *(blockData + 3); + } +}; + +class TextureDecoder_R24_X8 : public TextureDecoder, public SingletonClass<TextureDecoder_R24_X8> +{ +public: + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 4; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + for (sint32 y = 0; y < textureLoader->height; y += textureLoader->stepY) + { + sint32 yc = y; + for (sint32 x = 0; x < textureLoader->width; x += textureLoader->stepX) + { + uint8* blockData = LatteTextureLoader_GetInput(textureLoader, x, y); + sint32 pixelOffset = (x + yc * textureLoader->width) * 1 * sizeof(uint32); + *(uint32*)(outputData + pixelOffset + 0) = 0; + } + } + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + } +}; + +class TextureDecoder_X24_G8_UINT : public TextureDecoder, public SingletonClass<TextureDecoder_X24_G8_UINT> +{ +public: + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 4; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + for (sint32 y = 0; y < textureLoader->height; y += textureLoader->stepY) + { + sint32 yc = y; + for (sint32 x = 0; x < textureLoader->width; x += textureLoader->stepX) + { + uint8* blockData = LatteTextureLoader_GetInput(textureLoader, x, y); + sint32 pixelOffset = (x + yc * textureLoader->width) * 1 * sizeof(uint32); + *(uint32*)(outputData + pixelOffset + 0) = 0; + } + } + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + } +}; + +class TextureDecoder_D32_S8_UINT_X24 : public TextureDecoder, public SingletonClass<TextureDecoder_D32_S8_UINT_X24> +{ +public: + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 8; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + optimizedDecodeLoops<uint64, 1, false, false>(textureLoader, outputData); + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + } +}; + +class TextureDecoder_R4_G4_UNORM_toRGBA4444 : public TextureDecoder, public SingletonClass<TextureDecoder_R4_G4_UNORM_toRGBA4444> +{ +public: + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 2; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + for (sint32 y = 0; y < textureLoader->height; y += textureLoader->stepY) + { + sint32 yc = y; + for (sint32 x = 0; x < textureLoader->width; x += textureLoader->stepX) + { + uint8* blockData = LatteTextureLoader_GetInput(textureLoader, x, y); + sint32 pixelOffset = (x + yc * textureLoader->width) * 2; + uint8 v = (*(uint8*)(blockData + 0)); + *(uint8*)(outputData + pixelOffset + 0) = 0; // OpenGL has no RG4 format so we use RGBA4 instead and these two values (blue + alpha) are always set to zero + *(uint8*)(outputData + pixelOffset + 1) = ((v >> 4) & 0xF) | ((v << 4) & 0xF0); // todo: Is this nibble swap correct? + } + } + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + uint8 v0 = *(blockData + 0); + uint8 c0 = (v0 & 0xF); + uint8 c1 = (v0 >> 4) & 0xF; + c0 = (c0 << 4) | c0; + c1 = (c1 << 4) | c1; + *(outputPixel + 0) = c0; + *(outputPixel + 1) = c1; + *(outputPixel + 2) = 0; + *(outputPixel + 3) = 255; + } +}; + +class TextureDecoder_R4_G4_UNORM_toRGBA4444_vk : public TextureDecoder, public SingletonClass<TextureDecoder_R4_G4_UNORM_toRGBA4444_vk> +{ +public: + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 2; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + for (sint32 y = 0; y < textureLoader->height; y += textureLoader->stepY) + { + sint32 yc = y; + for (sint32 x = 0; x < textureLoader->width; x += textureLoader->stepX) + { + uint8* blockData = LatteTextureLoader_GetInput(textureLoader, x, y); + sint32 pixelOffset = (x + yc * textureLoader->width) * 2; + uint8 v = (*(uint8*)(blockData + 0)); + *(uint8*)(outputData + pixelOffset + 1) = 0; + *(uint8*)(outputData + pixelOffset + 0) = ((v >> 4) & 0xF) | ((v << 4) & 0xF0); // todo: Is this nibble swap correct? + } + } + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + uint8 v0 = *(blockData + 0); + uint8 c0 = (v0 & 0xF); + uint8 c1 = (v0 >> 4) & 0xF; + c0 = (c0 << 4) | c0; + c1 = (c1 << 4) | c1; + *(outputPixel + 0) = c0; + *(outputPixel + 1) = c1; + *(outputPixel + 2) = 0; + *(outputPixel + 3) = 255; + } +}; + + +class TextureDecoder_R4_G4_B4_A4_UNORM : public TextureDecoder, public SingletonClass<TextureDecoder_R4_G4_B4_A4_UNORM> +{ +public: + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 2; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + for (sint32 y = 0; y < textureLoader->height; y += textureLoader->stepY) + { + sint32 yc = y; + for (sint32 x = 0; x < textureLoader->width; x += textureLoader->stepX) + { + uint8* blockData = LatteTextureLoader_GetInput(textureLoader, x, y); + sint32 pixelOffset = (x + yc * textureLoader->width) * 2; + uint8 v0 = (*(uint8*)(blockData + 0)); + uint8 v1 = (*(uint8*)(blockData + 1)); + *(uint8*)(outputData + pixelOffset + 0) = ((v1 >> 4) & 0xF) | ((v1 << 4) & 0xF0); // todo: Verify + *(uint8*)(outputData + pixelOffset + 1) = ((v0 >> 4) & 0xF) | ((v0 << 4) & 0xF0); // todo: Verify + } + } + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + uint8 v0 = *(blockData + 0); + uint8 v1 = *(blockData + 1); + uint8 c0 = (v0 & 0xF); + uint8 c1 = (v0 >> 4) & 0xF; + uint8 c2 = (v1 & 0xF); + uint8 c3 = (v1 >> 4) & 0xF; + c0 = (c0 << 4) | c0; + c1 = (c1 << 4) | c1; + c2 = (c2 << 4) | c2; + c3 = (c3 << 4) | c3; + *(outputPixel + 0) = c0; + *(outputPixel + 1) = c1; + *(outputPixel + 2) = c2; + *(outputPixel + 3) = c3; + } +}; + +class TextureDecoder_R8_G8_B8_A8 : public TextureDecoder, public SingletonClass<TextureDecoder_R8_G8_B8_A8> +{ +public: + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 4; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + optimizedDecodeLoops<uint32, 1, false, false>(textureLoader, outputData); + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + *(outputPixel + 0) = *(blockData + 0); + *(outputPixel + 1) = *(blockData + 1); + *(outputPixel + 2) = *(blockData + 2); + *(outputPixel + 3) = *(blockData + 3); + } +}; + +class TextureDecoder_D24_S8 : public TextureDecoder, public SingletonClass<TextureDecoder_D24_S8> +{ +public: + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 4; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + optimizedDecodeLoops<uint32, 1, false, false>(textureLoader, outputData); + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + uint32 d24 = (*(uint32*)blockData) & 0xFFFFFF; + uint8 s8 = (*(uint32*)blockData >> 24) & 0xFF; + *(outputPixel + 0) = (uint8)(d24 >> 16); + *(outputPixel + 1) = (uint8)(d24 >> 16); + *(outputPixel + 2) = (uint8)(d24 >> 16); + *(outputPixel + 3) = s8; + } +}; + +class TextureDecoder_NullData32 : public TextureDecoder, public SingletonClass<TextureDecoder_NullData32> +{ +public: + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 4; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + memset(outputData, 0, sizeof(uint32) * getTexelCountX(textureLoader) * getTexelCountY(textureLoader)); + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + } +}; + +class TextureDecoder_NullData64 : public TextureDecoder, public SingletonClass<TextureDecoder_NullData64> +{ +public: + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 8; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + memset(outputData, 0, sizeof(uint64) * getTexelCountX(textureLoader) * getTexelCountY(textureLoader)); + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + } +}; + +class TextureDecoder_R8 : public TextureDecoder, public SingletonClass<TextureDecoder_R8> +{ +public: + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 1; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + optimizedDecodeLoops<uint8, 1, false, false>(textureLoader, outputData); + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + *(outputPixel + 0) = *(blockData + 0); + *(outputPixel + 1) = 0; + *(outputPixel + 2) = 0; + *(outputPixel + 3) = 255; + } +}; + +class TextureDecoder_R8_G8 : public TextureDecoder, public SingletonClass<TextureDecoder_R8_G8> +{ +public: + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 2; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + optimizedDecodeLoops<uint16, 1, false, false>(textureLoader, outputData); + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + *(outputPixel + 0) = *(blockData + 0); + *(outputPixel + 1) = *(blockData + 1); + *(outputPixel + 2) = 0; + *(outputPixel + 3) = 255; + } +}; + +class TextureDecoder_R4_G4 : public TextureDecoder, public SingletonClass<TextureDecoder_R4_G4> +{ +public: + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 1; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + optimizedDecodeLoops<uint8, 1, false, false>(textureLoader, outputData); + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + uint8 v0 = (*(uint8*)(blockData + 0)); + uint8 c0 = (v0 & 0xF); + uint8 c1 = (v0 >> 4) & 0xF; + c0 = (c0 << 4) | c0; + c1 = (c1 << 4) | c1; + *(outputPixel + 0) = c0; + *(outputPixel + 1) = c1; + *(outputPixel + 2) = 0; + *(outputPixel + 3) = 255; + } +}; + +class TextureDecoder_R16_UNORM : public TextureDecoder, public SingletonClass<TextureDecoder_R16_UNORM> +{ +public: + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 2; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + optimizedDecodeLoops<uint16, 1, false, false>(textureLoader, outputData); + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + *(outputPixel + 0) = (uint8)(*(((uint16*)blockData) + 0) >> 8); + *(outputPixel + 1) = 0; + *(outputPixel + 2) = 0; + *(outputPixel + 3) = 255; + } +}; + +class TextureDecoder_R16_G16_B16_A16 : public TextureDecoder, public SingletonClass<TextureDecoder_R16_G16_B16_A16> +{ +public: + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 4 * 2; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + optimizedDecodeLoops<uint64, 1, false, false>(textureLoader, outputData); + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + *(outputPixel + 0) = (uint8)(*(((uint16*)blockData) + 0) >> 8); + *(outputPixel + 1) = (uint8)(*(((uint16*)blockData) + 1) >> 8); + *(outputPixel + 2) = (uint8)(*(((uint16*)blockData) + 2) >> 8); + *(outputPixel + 3) = (uint8)(*(((uint16*)blockData) + 3) >> 8); + } +}; + +class TextureDecoder_R16_G16 : public TextureDecoder, public SingletonClass<TextureDecoder_R16_G16> +{ +public: + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 2 * 2; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + optimizedDecodeLoops<uint32, 1, false, false>(textureLoader, outputData); + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + *(outputPixel + 0) = (uint8)(*(((uint16*)blockData) + 0) >> 8); + *(outputPixel + 1) = (uint8)(*(((uint16*)blockData) + 1) >> 8); + *(outputPixel + 2) = 0; + *(outputPixel + 3) = 255; + } +}; + +class TextureDecoder_R5_G6_B5 : public TextureDecoder, public SingletonClass<TextureDecoder_R5_G6_B5> +{ +public: + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 2; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + optimizedDecodeLoops<uint16, 1, false, false>(textureLoader, outputData); + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + uint16 colorData = (*(uint16*)blockData); + uint8 red5 = (colorData >> 0) & 0x1F; + uint8 green6 = (colorData >> 5) & 0x3F; + uint8 blue5 = (colorData >> 11) & 0x1F; + *(outputPixel + 0) = (red5 << 3) | (red5 >> 2); + *(outputPixel + 1) = (green6 << 2) | (green6 >> 4); + *(outputPixel + 2) = (blue5 << 3) | (blue5 >> 2); + *(outputPixel + 3) = 255; + } +}; + +class TextureDecoder_R5_G6_B5_swappedRB : public TextureDecoder, public SingletonClass<TextureDecoder_R5_G6_B5_swappedRB> +{ +public: + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 2; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + for (sint32 y = 0; y < textureLoader->height; y += textureLoader->stepY) + { + sint32 yc = y; + for (sint32 x = 0; x < textureLoader->width; x += textureLoader->stepX) + { + uint8* blockData = LatteTextureLoader_GetInput(textureLoader, x, y); + sint32 pixelOffset = (x + yc * textureLoader->width) * 2; + uint16 colorData = (*(uint16*)(blockData + 0)); + + // colorData >> 0 -> red + // colorData >> 5 -> green + // colorData >> 11 -> blue + + colorData = ((colorData >> 11) & 0x1F) | (colorData & (0x3F << 5)) | ((colorData << 11) & (0x1F << 11)); + + //// swap order of components + ////uint8 red5 = (colorData>>0)&0x1F; + ////uint8 green5 = (colorData>>5)&0x1F; + ////uint8 blue5 = (colorData>>10)&0x1F; + ////uint8 alpha1 = (colorData>>15)&0x1; + ////colorData = blue5|(green5<<5)|(red5<<10)|(alpha1<<15); + ////*(uint16*)(outputData+pixelOffset+0) = colorData; + //uint8 red5 = (colorData >> 11) & 0x1F; + //uint8 green5 = (colorData >> 6) & 0x1F; + //uint8 blue5 = (colorData >> 1) & 0x1F; + //uint8 alpha1 = (colorData >> 0) & 0x1; + ////colorData = blue5|(green5<<5)|(red5<<10)|(alpha1<<15); + ////colorData = (blue5<<11)|(green5<<6)|(red5<<1)|(alpha1<<0); + * (uint16*)(outputData + pixelOffset + 0) = colorData; + } + } + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + uint16 colorData = (*(uint16*)blockData); + uint8 red5 = (colorData >> 0) & 0x1F; + uint8 green6 = (colorData >> 5) & 0x3F; + uint8 blue5 = (colorData >> 11) & 0x1F; + *(outputPixel + 0) = (red5 << 3) | (red5 >> 2); + *(outputPixel + 1) = (green6 << 2) | (green6 >> 4); + *(outputPixel + 2) = (blue5 << 3) | (blue5 >> 2); + *(outputPixel + 3) = 255; + } +}; + +class TextureDecoder_R5_G5_B5_A1_UNORM : public TextureDecoder, public SingletonClass<TextureDecoder_R5_G5_B5_A1_UNORM> +{ +public: + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 2; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + optimizedDecodeLoops<uint16, 1, false, false>(textureLoader, outputData); + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + uint16 colorData = (*(uint16*)blockData); + uint8 red5 = (colorData >> 0) & 0x1F; + uint8 green5 = (colorData >> 5) & 0x1F; + uint8 blue5 = (colorData >> 10) & 0x1F; + uint8 alpha1 = (colorData >> 11) & 0x1; + *(outputPixel + 0) = (red5 << 3) | (red5 >> 2); + *(outputPixel + 1) = (green5 << 3) | (green5 >> 2); + *(outputPixel + 2) = (blue5 << 3) | (blue5 >> 2); + *(outputPixel + 3) = (alpha1 << 3); + } +}; + +class uint16_R5_G5_B5_A1_swapRB +{ +public: + void operator=(const uint16_R5_G5_B5_A1_swapRB& v) + { + internalVal = ((v.internalVal >> 10) & (0x1F << 0)) | ((v.internalVal << 10) & (0x1F << 10)) | (v.internalVal & ((0x1F << 5) | 0x8000)); + } + + uint16 internalVal; +}; + +static_assert(sizeof(uint16_R5_G5_B5_A1_swapRB) == 2); + +class TextureDecoder_R5_G5_B5_A1_UNORM_swappedRB : public TextureDecoder, public SingletonClass<TextureDecoder_R5_G5_B5_A1_UNORM_swappedRB> +{ +public: + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 2; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + optimizedDecodeLoops<uint16_R5_G5_B5_A1_swapRB, 1, false, false>(textureLoader, outputData); + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + uint16 colorData = (*(uint16*)blockData); + uint8 red5 = (colorData >> 0) & 0x1F; + uint8 green5 = (colorData >> 5) & 0x1F; + uint8 blue5 = (colorData >> 10) & 0x1F; + uint8 alpha1 = (colorData >> 11) & 0x1; + *(outputPixel + 0) = (red5 << 3) | (red5 >> 2); + *(outputPixel + 1) = (green5 << 3) | (green5 >> 2); + *(outputPixel + 2) = (blue5 << 3) | (blue5 >> 2); + *(outputPixel + 3) = (alpha1 << 3); + } +}; + +class uint16_R5_G5_B5_A1_swapOpenGL +{ +public: + void operator=(const uint16_R5_G5_B5_A1_swapOpenGL& v) + { + uint16 red = (v.internalVal >> 0) & 0x1F; + uint16 green = (v.internalVal >> 5) & 0x1F; + uint16 blue = (v.internalVal >> 10) & 0x1F; + uint16 alpha = (v.internalVal >> 15) & 0x1; + internalVal = (red << 11) | (green << 6) | (blue << 1) | alpha; + } + uint16 internalVal; +}; + +static_assert(sizeof(uint16_R5_G5_B5_A1_swapOpenGL) == 2); + +class TextureDecoder_R5_G5_B5_A1_UNORM_swappedOpenGL : public TextureDecoder, public SingletonClass<TextureDecoder_R5_G5_B5_A1_UNORM_swappedOpenGL> +{ +public: + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 2; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + optimizedDecodeLoops<uint16_R5_G5_B5_A1_swapOpenGL, 1, false, false>(textureLoader, outputData); + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + uint16 colorData = (*(uint16*)blockData); + uint8 red5 = (colorData >> 0) & 0x1F; + uint8 green5 = (colorData >> 5) & 0x1F; + uint8 blue5 = (colorData >> 10) & 0x1F; + uint8 alpha1 = (colorData >> 11) & 0x1; + *(outputPixel + 0) = (red5 << 3) | (red5 >> 2); + *(outputPixel + 1) = (green5 << 3) | (green5 >> 2); + *(outputPixel + 2) = (blue5 << 3) | (blue5 >> 2); + *(outputPixel + 3) = (alpha1 << 3); + } +}; + +class TextureDecoder_A1_B5_G5_R5_UNORM : public TextureDecoder, public SingletonClass<TextureDecoder_A1_B5_G5_R5_UNORM> +{ +public: + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 2; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + optimizedDecodeLoops<uint16, 1, false, false>(textureLoader, outputData); + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + uint16 colorData = (*(uint16*)blockData); + uint8 red5 = (colorData >> 11) & 0x1F; + uint8 green5 = (colorData >> 6) & 0x1F; + uint8 blue5 = (colorData >> 1) & 0x1F; + uint8 alpha1 = (colorData >> 0) & 0x1; + *(outputPixel + 0) = (red5 << 3) | (red5 >> 2); + *(outputPixel + 1) = (green5 << 3) | (green5 >> 2); + *(outputPixel + 2) = (blue5 << 3) | (blue5 >> 2); + *(outputPixel + 3) = (alpha1 << 3); + } +}; + +class TextureDecoder_A1_B5_G5_R5_UNORM_vulkan : public TextureDecoder, public SingletonClass<TextureDecoder_A1_B5_G5_R5_UNORM_vulkan> +{ +public: + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 2; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + for (sint32 y = 0; y < textureLoader->height; y += textureLoader->stepY) + { + sint32 yc = y; + for (sint32 x = 0; x < textureLoader->width; x += textureLoader->stepX) + { + uint8* blockData = LatteTextureLoader_GetInput(textureLoader, x, y); + sint32 pixelOffset = (x + yc * textureLoader->width) * 2; + uint16 colorData = (*(uint16*)(blockData + 0)); + // swap order of components + uint8 red5 = (colorData >> 11) & 0x1F; + uint8 green5 = (colorData >> 6) & 0x1F; + uint8 blue5 = (colorData >> 1) & 0x1F; + uint8 alpha1 = (colorData >> 0) & 0x1; + colorData = blue5 | (green5 << 5) | (red5 << 10) | (alpha1 << 15); + *(uint16*)(outputData + pixelOffset + 0) = colorData; + } + } + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + uint16 colorData = (*(uint16*)blockData); + uint8 red5 = (colorData >> 11) & 0x1F; + uint8 green5 = (colorData >> 6) & 0x1F; + uint8 blue5 = (colorData >> 1) & 0x1F; + uint8 alpha1 = (colorData >> 0) & 0x1; + *(outputPixel + 0) = (red5 << 3) | (red5 >> 2); + *(outputPixel + 1) = (green5 << 3) | (green5 >> 2); + *(outputPixel + 2) = (blue5 << 3) | (blue5 >> 2); + *(outputPixel + 3) = (alpha1 << 3); + } +}; + + +class TextureDecoder_R10_G10_B10_A2_UNORM : public TextureDecoder, public SingletonClass<TextureDecoder_R10_G10_B10_A2_UNORM> +{ +public: + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 4; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + optimizedDecodeLoops<uint32, 1, false, false>(textureLoader, outputData); + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + uint16 r10 = ((*(uint32*)blockData) >> 0) & 0x3FF; + uint16 g10 = ((*(uint32*)blockData) >> 10) & 0x3FF; + uint16 b10 = ((*(uint32*)blockData) >> 20) & 0x3FF; + uint8 a2 = ((*(uint32*)blockData) >> 30) & 0x3; + *(outputPixel + 0) = (uint8)(r10 >> 6); + *(outputPixel + 1) = (uint8)(g10 >> 6); + *(outputPixel + 2) = (uint8)(b10 >> 6); + *(outputPixel + 3) = a2; + } +}; + +class TextureDecoder_R10_G10_B10_A2_SNORM_toRGBA16 : public TextureDecoder, public SingletonClass<TextureDecoder_R10_G10_B10_A2_SNORM_toRGBA16> +{ +public: + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 4 * 2; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + // todo - implement + for (sint32 y = 0; y < textureLoader->height; y += textureLoader->stepY) + { + sint32 yc = y; + sint32 pixelOffset = (yc * textureLoader->width) * (2 * 4); + sint16* pixelOutput = (sint16*)(outputData + pixelOffset); + for (sint32 x = 0; x < textureLoader->width; x += textureLoader->stepX) + { + uint8* blockData = LatteTextureLoader_GetInput(textureLoader, x, y); + uint32 v = (*(uint32*)(blockData + 0)); + *pixelOutput = 0; + pixelOffset++; + *pixelOutput = 0; + pixelOffset++; + *pixelOutput = 0; + pixelOffset++; + *pixelOutput = 0; + pixelOffset++; + } + } + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + uint16 r10 = ((*(uint32*)blockData) >> 0) & 0x3FF; + uint16 g10 = ((*(uint32*)blockData) >> 10) & 0x3FF; + uint16 b10 = ((*(uint32*)blockData) >> 20) & 0x3FF; + uint8 a2 = ((*(uint32*)blockData) >> 30) & 0x3; + *(outputPixel + 0) = (uint8)(r10 >> 6) / 2 + 128; + *(outputPixel + 1) = (uint8)(g10 >> 6) / 2 + 128; + *(outputPixel + 2) = (uint8)(b10 >> 6) / 2 + 128; + *(outputPixel + 3) = a2 / 2 + 128; + } +}; + +class TextureDecoder_A2_B10_G10_R10_UNORM_toRGBA16 : public TextureDecoder, public SingletonClass<TextureDecoder_A2_B10_G10_R10_UNORM_toRGBA16> +{ +public: + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 4 * 2; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + // todo - implement + for (sint32 y = 0; y < textureLoader->height; y += textureLoader->stepY) + { + sint32 yc = y; + sint32 pixelOffset = (yc * textureLoader->width) * (2 * 4); + sint16* pixelOutput = (sint16*)(outputData + pixelOffset); + for (sint32 x = 0; x < textureLoader->width; x += textureLoader->stepX) + { + uint8* blockData = LatteTextureLoader_GetInput(textureLoader, x, y); + uint32 v = (*(uint32*)(blockData + 0)); + *pixelOutput = 0; + pixelOffset++; + *pixelOutput = 0; + pixelOffset++; + *pixelOutput = 0; + pixelOffset++; + *pixelOutput = 0; + pixelOffset++; + } + } + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + uint8 a2 = ((*(uint32*)blockData) >> 0) & 0x3; + uint16 r10 = ((*(uint32*)blockData) >> 2) & 0x3FF; + uint16 g10 = ((*(uint32*)blockData) >> 12) & 0x3FF; + uint16 b10 = ((*(uint32*)blockData) >> 22) & 0x3FF; + *(outputPixel + 0) = (uint8)(r10 >> 6); + *(outputPixel + 1) = (uint8)(g10 >> 6); + *(outputPixel + 2) = (uint8)(b10 >> 6); + *(outputPixel + 3) = a2; + } +}; + +class TextureDecoder_R11_G11_B10_FLOAT : public TextureDecoder, public SingletonClass<TextureDecoder_R11_G11_B10_FLOAT> +{ +public: + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 4; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + optimizedDecodeLoops<uint32, 1, false, false>(textureLoader, outputData); + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + // todo: add dumping support + } +}; + +class TextureDecoder_BC1_UNORM_uncompress_generic : public TextureDecoder +{ +public: + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 4 * 4; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + for (sint32 y = 0; y < textureLoader->height; y += textureLoader->stepY) + { + for (sint32 x = 0; x < textureLoader->width; x += textureLoader->stepX) + { + uint8* blockData = LatteTextureLoader_GetInput(textureLoader, x, y); + sint32 blockSizeX = (std::min)(4, textureLoader->width - x); + sint32 blockSizeY = (std::min)(4, textureLoader->height - y); + // decode 4x4 pixels at once + float rgbaBlock[4 * 4 * 4]; + decodeBC1Block(blockData, rgbaBlock); + for (sint32 py = 0; py < blockSizeY; py++) + { + sint32 yc = y + py; + for (sint32 px = 0; px < blockSizeX; px++) + { + sint32 pixelOffset = (x + px + yc * textureLoader->width) * 16; // write to target buffer + float red = rgbaBlock[(px + py * 4) * 4 + 0]; + float green = rgbaBlock[(px + py * 4) * 4 + 1]; + float blue = rgbaBlock[(px + py * 4) * 4 + 2]; + float alpha = rgbaBlock[(px + py * 4) * 4 + 3]; + *(float*)(outputData + pixelOffset + 0) = red; + *(float*)(outputData + pixelOffset + 4) = green; + *(float*)(outputData + pixelOffset + 8) = blue; + *(float*)(outputData + pixelOffset + 12) = alpha; + } + } + } + } + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + BC1_GetPixel(blockData, blockOffsetX, blockOffsetY, outputPixel); + } +}; + +class TextureDecoder_BC1_UNORM_uncompress : public TextureDecoder_BC1_UNORM_uncompress_generic, public SingletonClass<TextureDecoder_BC1_UNORM_uncompress> +{ + // reuse TextureDecoder_BC1_UNORM_uncompress_generic +}; + +class TextureDecoder_BC1_SRGB_uncompress : public TextureDecoder_BC1_UNORM_uncompress_generic, public SingletonClass<TextureDecoder_BC1_SRGB_uncompress> +{ + // reuse TextureDecoder_BC1_UNORM_uncompress_generic +}; + +class TextureDecoder_BC1 : public TextureDecoder, public SingletonClass<TextureDecoder_BC1> +{ +public: + + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 8; + } + + sint32 getTexelCountX(LatteTextureLoaderCtx* textureLoader) override + { + return (textureLoader->width + 3) / 4; + } + + sint32 getTexelCountY(LatteTextureLoaderCtx* textureLoader) override + { + return (textureLoader->height + 3) / 4; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + optimizedDecodeLoops<uint64, 1, false, true>(textureLoader, outputData); + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + BC1_GetPixel(blockData, blockOffsetX, blockOffsetY, outputPixel); + } +}; + +class TextureDecoder_BC2 : public TextureDecoder, public SingletonClass<TextureDecoder_BC2> +{ +public: + + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 4 * 4; + } + + sint32 getTexelCountX(LatteTextureLoaderCtx* textureLoader) override + { + return (textureLoader->width + 3) / 4; + } + + sint32 getTexelCountY(LatteTextureLoaderCtx* textureLoader) override + { + return (textureLoader->height + 3) / 4; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + optimizedDecodeLoops<uint64, 2, false, true>(textureLoader, outputData); + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + float rgbaBlock[4 * 4 * 4]; + decodeBC2Block_UNORM(blockData, rgbaBlock); + float red = rgbaBlock[(blockOffsetX + blockOffsetY * 4) * 4 + 0]; + float green = rgbaBlock[(blockOffsetX + blockOffsetY * 4) * 4 + 1]; + float blue = rgbaBlock[(blockOffsetX + blockOffsetY * 4) * 4 + 2]; + float alpha = rgbaBlock[(blockOffsetX + blockOffsetY * 4) * 4 + 3]; + *(outputPixel + 0) = (uint8)(red * 255.0f); + *(outputPixel + 1) = (uint8)(green * 255.0f); + *(outputPixel + 2) = (uint8)(blue * 255.0f); + *(outputPixel + 3) = (uint8)(alpha * 255.0f); + } +}; + +class TextureDecoder_BC2_UNORM_uncompress : public TextureDecoder, public SingletonClass<TextureDecoder_BC2_UNORM_uncompress> +{ +public: + + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 4 * 4; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + for (sint32 y = 0; y < textureLoader->height; y += textureLoader->stepY) + { + for (sint32 x = 0; x < textureLoader->width; x += textureLoader->stepX) + { + uint8* blockData = LatteTextureLoader_GetInput(textureLoader, x, y); + sint32 blockSizeX = (std::min)(4, textureLoader->width - x); + sint32 blockSizeY = (std::min)(4, textureLoader->height - y); + // decode 4x4 pixels at once + float rgbaBlock[4 * 4 * 4]; + decodeBC2Block_UNORM(blockData, rgbaBlock); + for (sint32 py = 0; py < blockSizeY; py++) + { + sint32 yc = y + py; + for (sint32 px = 0; px < blockSizeX; px++) + { + sint32 pixelOffset = (x + px + yc * textureLoader->width) * 16; // write to target buffer + float red = rgbaBlock[(px + py * 4) * 4 + 0]; + float green = rgbaBlock[(px + py * 4) * 4 + 1]; + float blue = rgbaBlock[(px + py * 4) * 4 + 2]; + float alpha = rgbaBlock[(px + py * 4) * 4 + 3]; + *(float*)(outputData + pixelOffset + 0) = red; + *(float*)(outputData + pixelOffset + 4) = green; + *(float*)(outputData + pixelOffset + 8) = blue; + *(float*)(outputData + pixelOffset + 12) = alpha; + } + } + } + } + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + float rgbaBlock[4 * 4 * 4]; + decodeBC2Block_UNORM(blockData, rgbaBlock); + float red = rgbaBlock[(blockOffsetX + blockOffsetY * 4) * 4 + 0]; + float green = rgbaBlock[(blockOffsetX + blockOffsetY * 4) * 4 + 1]; + float blue = rgbaBlock[(blockOffsetX + blockOffsetY * 4) * 4 + 2]; + float alpha = rgbaBlock[(blockOffsetX + blockOffsetY * 4) * 4 + 3]; + *(outputPixel + 0) = (uint8)(red * 255.0f); + *(outputPixel + 1) = (uint8)(green * 255.0f); + *(outputPixel + 2) = (uint8)(blue * 255.0f); + *(outputPixel + 3) = (uint8)(alpha * 255.0f); + } +}; + +class TextureDecoder_BC2_SRGB_uncompress : public TextureDecoder, public SingletonClass<TextureDecoder_BC2_SRGB_uncompress> +{ +public: + + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 4 * 4; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + // todo - apply srgb conversion + for (sint32 y = 0; y < textureLoader->height; y += textureLoader->stepY) + { + for (sint32 x = 0; x < textureLoader->width; x += textureLoader->stepX) + { + uint8* blockData = LatteTextureLoader_GetInput(textureLoader, x, y); + sint32 blockSizeX = (std::min)(4, textureLoader->width - x); + sint32 blockSizeY = (std::min)(4, textureLoader->height - y); + // decode 4x4 pixels at once + float rgbaBlock[4 * 4 * 4]; + decodeBC2Block_UNORM(blockData, rgbaBlock); + for (sint32 py = 0; py < blockSizeY; py++) + { + sint32 yc = y + py; + for (sint32 px = 0; px < blockSizeX; px++) + { + sint32 pixelOffset = (x + px + yc * textureLoader->width) * 16; // write to target buffer + float red = rgbaBlock[(px + py * 4) * 4 + 0]; + float green = rgbaBlock[(px + py * 4) * 4 + 1]; + float blue = rgbaBlock[(px + py * 4) * 4 + 2]; + float alpha = rgbaBlock[(px + py * 4) * 4 + 3]; + *(float*)(outputData + pixelOffset + 0) = red; + *(float*)(outputData + pixelOffset + 4) = green; + *(float*)(outputData + pixelOffset + 8) = blue; + *(float*)(outputData + pixelOffset + 12) = alpha; + } + } + } + } + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + // todo - apply srgb conversion + float rgbaBlock[4 * 4 * 4]; + decodeBC2Block_UNORM(blockData, rgbaBlock); + float red = rgbaBlock[(blockOffsetX + blockOffsetY * 4) * 4 + 0]; + float green = rgbaBlock[(blockOffsetX + blockOffsetY * 4) * 4 + 1]; + float blue = rgbaBlock[(blockOffsetX + blockOffsetY * 4) * 4 + 2]; + float alpha = rgbaBlock[(blockOffsetX + blockOffsetY * 4) * 4 + 3]; + *(outputPixel + 0) = (uint8)(red * 255.0f); + *(outputPixel + 1) = (uint8)(green * 255.0f); + *(outputPixel + 2) = (uint8)(blue * 255.0f); + *(outputPixel + 3) = (uint8)(alpha * 255.0f); + } +}; + +class TextureDecoder_BC3_uncompress_generic : public TextureDecoder +{ +public: + + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 4 * 4; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + for (sint32 y = 0; y < textureLoader->height; y += textureLoader->stepY) + { + for (sint32 x = 0; x < textureLoader->width; x += textureLoader->stepX) + { + uint8* blockData = LatteTextureLoader_GetInput(textureLoader, x, y); + sint32 blockSizeX = (std::min)(4, textureLoader->width - x); + sint32 blockSizeY = (std::min)(4, textureLoader->height - y); + // decode 4x4 pixels at once + float rgbaBlock[4 * 4 * 4]; + decodeBC3Block_UNORM(blockData, rgbaBlock); + for (sint32 py = 0; py < blockSizeY; py++) + { + sint32 yc = y + py; + for (sint32 px = 0; px < blockSizeX; px++) + { + sint32 pixelOffset = (x + px + yc * textureLoader->width) * 16; // write to target buffer + float red = rgbaBlock[(px + py * 4) * 4 + 0]; + float green = rgbaBlock[(px + py * 4) * 4 + 1]; + float blue = rgbaBlock[(px + py * 4) * 4 + 2]; + float alpha = rgbaBlock[(px + py * 4) * 4 + 3]; + *(float*)(outputData + pixelOffset + 0) = red; + *(float*)(outputData + pixelOffset + 4) = green; + *(float*)(outputData + pixelOffset + 8) = blue; + *(float*)(outputData + pixelOffset + 12) = alpha; + } + } + } + } + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + float rgbaBlock[4 * 4 * 4]; + decodeBC3Block_UNORM(blockData, rgbaBlock); + float red = rgbaBlock[(blockOffsetX + blockOffsetY * 4) * 4 + 0]; + float green = rgbaBlock[(blockOffsetX + blockOffsetY * 4) * 4 + 1]; + float blue = rgbaBlock[(blockOffsetX + blockOffsetY * 4) * 4 + 2]; + float alpha = rgbaBlock[(blockOffsetX + blockOffsetY * 4) * 4 + 3]; + *(outputPixel + 0) = (uint8)(red * 255.0f); + *(outputPixel + 1) = (uint8)(green * 255.0f); + *(outputPixel + 2) = (uint8)(blue * 255.0f); + *(outputPixel + 3) = (uint8)(alpha * 255.0f); + } +}; + +class TextureDecoder_BC3_UNORM_uncompress : public TextureDecoder_BC3_uncompress_generic, public SingletonClass<TextureDecoder_BC3_UNORM_uncompress> +{ + // reuse TextureDecoder_BC3_uncompress_generic +}; + +class TextureDecoder_BC3_SRGB_uncompress : public TextureDecoder_BC3_uncompress_generic, public SingletonClass<TextureDecoder_BC3_SRGB_uncompress> +{ + // reuse TextureDecoder_BC3_uncompress_generic +}; + +class TextureDecoder_BC3 : public TextureDecoder, public SingletonClass<TextureDecoder_BC3> +{ +public: + + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 16; + } + + sint32 getTexelCountX(LatteTextureLoaderCtx* textureLoader) override + { + return (textureLoader->width + 3) / 4; + } + + sint32 getTexelCountY(LatteTextureLoaderCtx* textureLoader) override + { + return (textureLoader->height + 3) / 4; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + optimizedDecodeLoops<uint64, 2, false, true>(textureLoader, outputData); + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + float rgbaBlock[4 * 4 * 4]; + decodeBC3Block_UNORM(blockData, rgbaBlock); + float red = rgbaBlock[(blockOffsetX + blockOffsetY * 4) * 4 + 0]; + float green = rgbaBlock[(blockOffsetX + blockOffsetY * 4) * 4 + 1]; + float blue = rgbaBlock[(blockOffsetX + blockOffsetY * 4) * 4 + 2]; + float alpha = rgbaBlock[(blockOffsetX + blockOffsetY * 4) * 4 + 3]; + *(outputPixel + 0) = (uint8)(red * 255.0f); + *(outputPixel + 1) = (uint8)(green * 255.0f); + *(outputPixel + 2) = (uint8)(blue * 255.0f); + *(outputPixel + 3) = (uint8)(alpha * 255.0f); + } +}; + +class TextureDecoder_BC4_UNORM_uncompress : public TextureDecoder, public SingletonClass<TextureDecoder_BC4_UNORM_uncompress> +{ +public: + + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 2 * 4; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + for (sint32 y = 0; y < textureLoader->height; y += textureLoader->stepY) + { + for (sint32 x = 0; x < textureLoader->width; x += textureLoader->stepX) + { + uint8* blockData = LatteTextureLoader_GetInput(textureLoader, x, y); + sint32 blockSizeX = (std::min)(4, textureLoader->width - x); + sint32 blockSizeY = (std::min)(4, textureLoader->height - y); + // decode 4x4 pixels at once + float rBlock[4 * 4 * 1]; + decodeBC4Block_UNORM(blockData, rBlock); + + for (sint32 py = 0; py < blockSizeY; py++) + { + sint32 yc = y + py; + for (sint32 px = 0; px < blockSizeX; px++) + { + sint32 pixelOffset = (x + px + yc * textureLoader->width) * 8; // write to target buffer + float red = rBlock[(px + py * 4) * 1 + 0]; + *(float*)(outputData + pixelOffset + 0) = red; + *(float*)(outputData + pixelOffset + 4) = 0.0f; + } + } + } + } + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + float rBlock[4 * 4 * 1]; + decodeBC4Block_UNORM(blockData, rBlock); + float red = rBlock[(blockOffsetX + blockOffsetY * 4) * 1 + 0]; + *(outputPixel + 0) = (uint8)(red * 255.0f); + *(outputPixel + 1) = 0; + *(outputPixel + 2) = 0; + *(outputPixel + 3) = 255; + } +}; + +class TextureDecoder_BC4 : public TextureDecoder, public SingletonClass<TextureDecoder_BC4> +{ +public: + + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 8; + } + + sint32 getTexelCountX(LatteTextureLoaderCtx* textureLoader) override + { + return (textureLoader->width + 3) / 4; + } + + sint32 getTexelCountY(LatteTextureLoaderCtx* textureLoader) override + { + return (textureLoader->height + 3) / 4; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + optimizedDecodeLoops<uint64, 1, false, true>(textureLoader, outputData); + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + float rBlock[4 * 4 * 1]; + decodeBC4Block_UNORM(blockData, rBlock); + float red = rBlock[(blockOffsetX + blockOffsetY * 4) * 1 + 0]; + *(outputPixel + 0) = (uint8)(red * 255.0f); + *(outputPixel + 1) = 0; + *(outputPixel + 2) = 0; + *(outputPixel + 3) = 255; + } +}; + +class TextureDecoder_BC5_UNORM_uncompress : public TextureDecoder, public SingletonClass<TextureDecoder_BC5_UNORM_uncompress> +{ +public: + + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 2 * 4; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + for (sint32 y = 0; y < textureLoader->height; y += textureLoader->stepY) + { + for (sint32 x = 0; x < textureLoader->width; x += textureLoader->stepX) + { + uint8* blockData = LatteTextureLoader_GetInput(textureLoader, x, y); + sint32 blockSizeX = (std::min)(4, textureLoader->width - x); + sint32 blockSizeY = (std::min)(4, textureLoader->height - y); + // decode 4x4 pixels at once + float rgBlock[4 * 4 * 2]; + decodeBC5Block_UNORM(blockData, rgBlock); + + for (sint32 py = 0; py < blockSizeY; py++) + { + sint32 yc = y + py; + for (sint32 px = 0; px < blockSizeX; px++) + { + sint32 pixelOffset = (x + px + yc * textureLoader->width) * 8; // write to target buffer + float red = rgBlock[(px + py * 4) * 2 + 0]; + float green = rgBlock[(px + py * 4) * 2 + 1]; + *(float*)(outputData + pixelOffset + 0) = red; + *(float*)(outputData + pixelOffset + 4) = green; + } + } + } + } + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + float rgBlock[4 * 4 * 2]; + decodeBC5Block_UNORM(blockData, rgBlock); + float red = rgBlock[(blockOffsetX + blockOffsetY * 4) * 2 + 0]; + float green = rgBlock[(blockOffsetX + blockOffsetY * 4) * 2 + 1]; + *(outputPixel + 0) = (uint8)(red * 255.0f); + *(outputPixel + 1) = (uint8)(green * 255.0f); + *(outputPixel + 2) = 0; + *(outputPixel + 3) = 255; + } +}; + +class TextureDecoder_BC5_SNORM_uncompress : public TextureDecoder, public SingletonClass<TextureDecoder_BC5_SNORM_uncompress> +{ +public: + + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 2 * 4; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + for (sint32 y = 0; y < textureLoader->height; y += textureLoader->stepY) + { + for (sint32 x = 0; x < textureLoader->width; x += textureLoader->stepX) + { + uint8* blockData = LatteTextureLoader_GetInput(textureLoader, x, y); + sint32 blockSizeX = (std::min)(4, textureLoader->width - x); + sint32 blockSizeY = (std::min)(4, textureLoader->height - y); + // decode 4x4 pixels at once + float rgBlock[4 * 4 * 2]; + decodeBC5Block_SNORM(blockData, rgBlock); + + for (sint32 py = 0; py < blockSizeY; py++) + { + sint32 yc = y + py; + for (sint32 px = 0; px < blockSizeX; px++) + { + sint32 pixelOffset = (x + px + yc * textureLoader->width) * 8; // write to target buffer + float red = rgBlock[(px + py * 4) * 2 + 0]; + float green = rgBlock[(px + py * 4) * 2 + 1]; + *(float*)(outputData + pixelOffset + 0) = red; + *(float*)(outputData + pixelOffset + 4) = green; + } + } + } + } + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + // todo: fix BC5 SNORM dumping + float rgBlock[4 * 4 * 2]; + decodeBC5Block_SNORM(blockData, rgBlock); + float red = rgBlock[(blockOffsetX + blockOffsetY * 4) * 2 + 0]; + float green = rgBlock[(blockOffsetX + blockOffsetY * 4) * 2 + 1]; + *(outputPixel + 0) = (uint8)((0.5f + red * 0.5f) * 255.0f); + *(outputPixel + 1) = (uint8)((0.5f + green * 0.5f) * 255.0f); + *(outputPixel + 2) = 0; + *(outputPixel + 3) = 255; + } +}; + +class TextureDecoder_BC5 : public TextureDecoder, public SingletonClass<TextureDecoder_BC5> +{ +public: + + sint32 getBytesPerTexel(LatteTextureLoaderCtx* textureLoader) override + { + return 16; + } + + sint32 getTexelCountX(LatteTextureLoaderCtx* textureLoader) override + { + return (textureLoader->width + 3) / 4; + } + + sint32 getTexelCountY(LatteTextureLoaderCtx* textureLoader) override + { + return (textureLoader->height + 3) / 4; + } + + void decode(LatteTextureLoaderCtx* textureLoader, uint8* outputData) override + { + optimizedDecodeLoops<uint64, 2, false, true>(textureLoader, outputData); + } + + void decodePixelToRGBA(uint8* blockData, uint8* outputPixel, uint8 blockOffsetX, uint8 blockOffsetY) override + { + float rgBlock[4 * 4 * 2]; + decodeBC5Block_UNORM(blockData, rgBlock); + float red = rgBlock[(blockOffsetX + blockOffsetY * 4) * 2 + 0]; + float green = rgBlock[(blockOffsetX + blockOffsetY * 4) * 2 + 1]; + *(outputPixel + 0) = (uint8)(red * 255.0f); + *(outputPixel + 1) = (uint8)(green * 255.0f); + *(outputPixel + 2) = 0; + *(outputPixel + 3) = 255; + } +}; \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Core/LatteTextureReadback.cpp b/src/Cafe/HW/Latte/Core/LatteTextureReadback.cpp new file mode 100644 index 00000000..bd579b11 --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteTextureReadback.cpp @@ -0,0 +1,144 @@ +#include "Cafe/HW/Latte/Core/Latte.h" +#include "Cafe/HW/Latte/Core/LatteDraw.h" +#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" + +#include "Common/GLInclude/GLInclude.h" + +#include "Cafe/HW/Latte/Renderer/Renderer.h" +#include "Cafe/HW/Latte/Core/LatteTexture.h" +#include "Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.h" + +// #define LOG_READBACK_TIME + +struct LatteTextureReadbackQueueEntry +{ + uint32 lastUpdateDrawcallIndex; + LatteTextureView* textureView; +}; + +std::vector<LatteTextureReadbackQueueEntry> sTextureScheduledReadbacks; // readbacks that have been queued but the actual transfer has not yet been started +std::queue<LatteTextureReadbackInfo*> sTextureActiveReadbackQueue; // readbacks in flight + +void LatteTextureReadback_StartTransfer(LatteTextureView* textureView) +{ + // create info entry and store in ordered linked list + LatteTextureReadbackInfo* readbackInfo = g_renderer->texture_createReadback(textureView); + sTextureActiveReadbackQueue.push(readbackInfo); + readbackInfo->StartTransfer(); + //debug_printf("[Tex-Readback] %08x %dx%d TM %d FMT %04x\n", textureView->baseTexture->physAddress, textureView->baseTexture->width, textureView->baseTexture->height, textureView->baseTexture->tileMode, textureView->baseTexture->format); + readbackInfo->transferStartTime = HighResolutionTimer().now().getTick(); +} + +/* + * Checks for queued transfers and starts them if at least one drawcall has passed since the last write + * Called after a draw operation has finished + * Returns true if at least one transfer was started + */ +bool LatteTextureReadback_Update(bool forceStart) +{ + bool hasStartedTransfer = false; + for (size_t i = 0; i < sTextureScheduledReadbacks.size(); i++) + { + LatteTextureReadbackQueueEntry& entry = sTextureScheduledReadbacks[i]; + uint32 numPassedDrawcalls = LatteGPUState.drawCallCounter - entry.lastUpdateDrawcallIndex; + // initiate transfer + LatteTextureReadback_StartTransfer(entry.textureView); + // remove element + vectorRemoveByIndex(sTextureScheduledReadbacks, i); + i--; + hasStartedTransfer = true; + } + return hasStartedTransfer; +} + +/* + * Called when a texture is deleted + */ +void LatteTextureReadback_NotifyTextureDeletion(LatteTexture* texture) +{ + // delete from queue + for (size_t i = 0; i < sTextureScheduledReadbacks.size(); i++) + { + LatteTextureReadbackQueueEntry& entry = sTextureScheduledReadbacks[i]; + if (entry.textureView->baseTexture == texture) + { + vectorRemoveByIndex(sTextureScheduledReadbacks, i); + break; + } + } +} + +void LatteTextureReadback_Initate(LatteTextureView* textureView) +{ + // currently we don't support readback for resized textures + if (textureView->baseTexture->overwriteInfo.hasResolutionOverwrite) + { + forceLog_printf("_initate(): Readback is not supported for textures with modified resolution"); + return; + } + // check if texture isn't already queued for transfer + for (size_t i = 0; i < sTextureScheduledReadbacks.size(); i++) + { + LatteTextureReadbackQueueEntry& entry = sTextureScheduledReadbacks[i]; + if (entry.textureView == textureView) + { + entry.lastUpdateDrawcallIndex = LatteGPUState.drawCallCounter; + return; + } + } + // queue + LatteTextureReadbackQueueEntry queueEntry; + queueEntry.textureView = textureView; + queueEntry.lastUpdateDrawcallIndex = LatteGPUState.drawCallCounter; + sTextureScheduledReadbacks.emplace_back(queueEntry); +} + +void LatteTextureReadback_UpdateFinishedTransfers(bool forceFinish) +{ + if (forceFinish) + { + // start any delayed transfers + LatteTextureReadback_Update(true); + } + performanceMonitor.gpuTime_waitForAsync.beginMeasuring(); + while (!sTextureActiveReadbackQueue.empty()) + { + LatteTextureReadbackInfo* readbackInfo = sTextureActiveReadbackQueue.front(); + if (forceFinish) + { + if (!readbackInfo->IsFinished()) + { + readbackInfo->waitStartTime = HighResolutionTimer().now().getTick(); + readbackInfo->ForceFinish(); + // rerun logic since ->ForceFinish() can recurively call this function and thus modify the queue + continue; + } + } + else + { + if (!readbackInfo->IsFinished()) + break; + readbackInfo->waitStartTime = HighResolutionTimer().now().getTick(); + } + // performance testing +#ifdef LOG_READBACK_TIME + HRTick currentTick = HighResolutionTimer().now().getTick(); + double elapsedSecondsTransfer = HighResolutionTimer::getTimeDiff(readbackInfo->transferStartTime, currentTick); + double elapsedSecondsWaiting = HighResolutionTimer::getTimeDiff(readbackInfo->waitStartTime, currentTick); + forceLog_printf("[Texture-Readback] %08x Res %4d/%4d TM %d FMT %04x ReadbackLatency: %6.3lfms WaitTime: %6.3lfms ForcedWait %s", readbackInfo->hostTextureCopy.physAddress, readbackInfo->hostTextureCopy.width, readbackInfo->hostTextureCopy.height, readbackInfo->hostTextureCopy.tileMode, (uint32)readbackInfo->hostTextureCopy.format, elapsedSecondsTransfer * 1000.0, elapsedSecondsWaiting * 1000.0, forceFinish?"yes":"no"); +#endif + uint8* pixelData = readbackInfo->GetData(); + LatteTextureLoader_writeReadbackTextureToMemory(&readbackInfo->hostTextureCopy, 0, 0, pixelData); + readbackInfo->ReleaseData(); + // get the original texture if it still exists and invalidate the current data hash + LatteTextureView* origTexView = LatteTextureViewLookupCache::lookupSlice(readbackInfo->hostTextureCopy.physAddress, readbackInfo->hostTextureCopy.width, readbackInfo->hostTextureCopy.height, readbackInfo->hostTextureCopy.pitch, 0, 0, readbackInfo->hostTextureCopy.format); + if (origTexView) + LatteTC_ResetTextureChangeTracker(origTexView->baseTexture, true); + delete readbackInfo; + // remove from queue + cemu_assert_debug(!sTextureActiveReadbackQueue.empty()); + cemu_assert_debug(readbackInfo == sTextureActiveReadbackQueue.front()); + sTextureActiveReadbackQueue.pop(); + } + performanceMonitor.gpuTime_waitForAsync.endMeasuring(); +} diff --git a/src/Cafe/HW/Latte/Core/LatteTextureReadbackInfo.h b/src/Cafe/HW/Latte/Core/LatteTextureReadbackInfo.h new file mode 100644 index 00000000..4f3a3069 --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteTextureReadbackInfo.h @@ -0,0 +1,30 @@ +#pragma once + +#include "Cafe/HW/Latte/Core/LatteTexture.h" +#include "util/highresolutiontimer/HighResolutionTimer.h" + +class LatteTextureReadbackInfo +{ +public: + LatteTextureReadbackInfo(LatteTextureView* textureView) + : hostTextureCopy(textureView->baseTexture), m_textureView(textureView) + {} + + virtual ~LatteTextureReadbackInfo() = default; + + virtual void StartTransfer() = 0; + virtual bool IsFinished() = 0; + virtual void ForceFinish() {}; + + virtual uint8* GetData() = 0; + virtual void ReleaseData() {}; + + HRTick transferStartTime; + HRTick waitStartTime; + // texture info + LatteTextureDefinition hostTextureCopy{}; + +protected: + LatteTextureView* m_textureView; + uint32 m_image_size = 0; +}; \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Core/LatteTextureView.cpp b/src/Cafe/HW/Latte/Core/LatteTextureView.cpp new file mode 100644 index 00000000..8cfa11e9 --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteTextureView.cpp @@ -0,0 +1,244 @@ +#include "Cafe/HW/Latte/Core/LatteTexture.h" +#include "Cafe/HW/Latte/Core/LatteTextureView.h" +#include "Cafe/HW/Latte/Core/Latte.h" +#include "Cafe/GraphicPack/GraphicPack.h" +#include "Cafe/GraphicPack/GraphicPack2.h" + +LatteTextureView::LatteTextureView(LatteTexture* texture, sint32 firstMip, sint32 mipCount, sint32 firstSlice, sint32 sliceCount, Latte::E_DIM dim, Latte::E_GX2SURFFMT format, bool registerView) +{ + this->baseTexture = texture; + this->firstMip = firstMip; + this->numMip = mipCount; + this->firstSlice = firstSlice; + this->numSlice = sliceCount; + this->dim = dim; + this->format = format; + if (registerView) + { + texture->views.emplace_back(this); + LatteTextureViewLookupCache::Add(this); + } +} + +LatteTextureView::~LatteTextureView() +{ + // unregister view + LatteTextureViewLookupCache::RemoveAll(this); + // remove from texture + vectorRemoveByValue(baseTexture->views, this); + if (baseTexture->baseView == this) + baseTexture->baseView = nullptr; + // delete all associated FBOs + while (!list_associatedFbo.empty()) + LatteMRT::DeleteCachedFBO(list_associatedFbo[0]); +} + +void LatteTextureView::CreateLookupForSubTexture(uint32 mipStart, uint32 sliceStart) +{ + cemu_assert_debug(mipStart != 0 || sliceStart != 0); // This function should never be called with both parameters zero. Every view creates a base lookup on construction + LatteTextureViewLookupCache::Add(this, mipStart, sliceStart); +} + +/* View lookup cache */ + +struct LatteTexViewLookupDesc +{ + LatteTexViewLookupDesc(LatteTextureView* view) : view(view) + { + this->physAddr = view->baseTexture->physAddress; + this->physMipAddr = view->baseTexture->physMipAddress; + this->width = view->baseTexture->width; + this->height = view->baseTexture->height; + this->pitch = view->baseTexture->pitch; + this->firstMip = view->firstMip; + this->numMip = view->numMip; + this->firstSlice = view->firstSlice; + this->numSlice = view->numSlice; + this->format = view->format; + this->dim = view->dim; + this->isDepth = view->baseTexture->isDepth; + } + + void SetParametersForSubTexture(sint32 baseMip, sint32 baseSlice) + { + cemu_assert_debug(baseMip >= 0); + cemu_assert_debug(baseSlice >= 0); + LatteTextureSliceMipInfo* sliceMipInfo = view->baseTexture->GetSliceMipArrayEntry(baseSlice, baseMip); + physAddr = sliceMipInfo->addrStart; + pitch = sliceMipInfo->pitch; + cemu_assert_debug(format == view->baseTexture->format); // if the format is different then width/height calculation might differ. This only affects the case where an integer format is mapped onto a compressed format or vice versa. + width = view->baseTexture->GetMipWidth(baseMip); + height = view->baseTexture->GetMipHeight(baseMip); + // adjust firstMip and firstSlice to be relative to base of subtexture + cemu_assert(firstMip >= baseMip); + cemu_assert(firstSlice >= baseSlice); + firstMip -= baseMip; + firstSlice -= baseSlice; + } + + // key data for looking up views + MPTR physAddr; + MPTR physMipAddr; + sint32 width; + sint32 height; + sint32 pitch; + sint32 firstMip; + sint32 numMip; + sint32 firstSlice; + sint32 numSlice; + Latte::E_GX2SURFFMT format; + Latte::E_DIM dim; + bool isDepth; + // associated view + LatteTextureView* view; +}; + +struct LatteTexViewBucket +{ + std::vector<LatteTexViewLookupDesc> list; +}; + +#define TEXTURE_VIEW_BUCKETS (1061) + +inline uint32 _getViewBucketKey(MPTR physAddress, uint32 width, uint32 height, uint32 pitch) +{ + return (physAddress + width * 7 + height * 11 + pitch * 13) % TEXTURE_VIEW_BUCKETS; +} + +inline uint32 _getViewBucketKeyNoRes(MPTR physAddress, uint32 pitch) +{ + return (physAddress + pitch * 13) % TEXTURE_VIEW_BUCKETS; +} + +LatteTexViewBucket texViewBucket[TEXTURE_VIEW_BUCKETS] = { }; +LatteTexViewBucket texViewBucket_nores[TEXTURE_VIEW_BUCKETS] = { }; + +void LatteTextureViewLookupCache::Add(LatteTextureView* view, uint32 baseMip, uint32 baseSlice) +{ + LatteTexViewLookupDesc desc(view); + if (baseMip != 0 || baseSlice != 0) + desc.SetParametersForSubTexture(baseMip, baseSlice); + // generic bucket + uint32 key = _getViewBucketKey(desc.physAddr, desc.width, desc.height, desc.pitch); + texViewBucket[key].list.emplace_back(desc); + vectorAppendUnique(view->viewLookUpCacheKeys, key); + // resolution-independent bucket + key = _getViewBucketKeyNoRes(desc.physAddr, desc.pitch); + texViewBucket_nores[key].list.push_back(desc); + vectorAppendUnique(view->viewLookUpCacheKeysNoRes, key); +} + +void LatteTextureViewLookupCache::RemoveAll(LatteTextureView* view) +{ + for (auto& key : view->viewLookUpCacheKeys) + { + auto& bucket = texViewBucket[key].list; + bucket.erase(std::remove_if(bucket.begin(), bucket.end(), [view](const LatteTexViewLookupDesc& v) { + return v.view == view; }), bucket.end()); + } + for (auto& key : view->viewLookUpCacheKeysNoRes) + { + auto& bucket = texViewBucket_nores[key].list; + bucket.erase(std::remove_if(bucket.begin(), bucket.end(), [view](const LatteTexViewLookupDesc& v) { + return v.view == view; }), bucket.end()); + } +} + + +LatteTextureView* LatteTextureViewLookupCache::lookup(MPTR physAddr, sint32 width, sint32 height, sint32 depth, sint32 pitch, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, Latte::E_GX2SURFFMT format, Latte::E_DIM dim) +{ + // todo - add tileMode param to this and the other lookup functions? + uint32 key = _getViewBucketKey(physAddr, width, height, pitch); + key %= TEXTURE_VIEW_BUCKETS; + for (auto& it : texViewBucket[key].list) + { + if (it.format == format && it.dim == dim && + it.width == width && it.height == height && it.pitch == pitch && it.physAddr == physAddr + && it.firstMip == firstMip && it.numMip == numMip + && it.firstSlice == firstSlice && it.numSlice == numSlice + ) + { + return it.view; + } + } + return nullptr; +} + +LatteTextureView* LatteTextureViewLookupCache::lookup(MPTR physAddr, sint32 width, sint32 height, sint32 depth, sint32 pitch, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, Latte::E_GX2SURFFMT format, Latte::E_DIM dim, bool isDepth) +{ + cemu_assert_debug(firstSlice == 0); + uint32 key = _getViewBucketKey(physAddr, width, height, pitch); + key %= TEXTURE_VIEW_BUCKETS; + for (auto& it : texViewBucket[key].list) + { + if (it.format == format && it.dim == dim && it.width == width && it.height == height && it.pitch == pitch && it.physAddr == physAddr + && it.firstMip == firstMip && it.numMip == numMip + && it.firstSlice == firstSlice && it.numSlice == numSlice && + it.isDepth == isDepth + ) + { + return it.view; + } + } + return nullptr; +} + +// look up view with unspecified mipCount and sliceCount +LatteTextureView* LatteTextureViewLookupCache::lookupSlice(MPTR physAddr, sint32 width, sint32 height, sint32 pitch, sint32 firstMip, sint32 firstSlice, Latte::E_GX2SURFFMT format) +{ + uint32 key = _getViewBucketKey(physAddr, width, height, pitch); + key %= TEXTURE_VIEW_BUCKETS; + for (auto& it : texViewBucket[key].list) + { + if (it.width == width && it.height == height && it.pitch == pitch && it.physAddr == physAddr && it.format == format) + { + if (firstSlice == it.firstSlice && firstMip == it.firstMip) + return it.view; + } + } + return nullptr; +} + +// look up view with unspecified mipCount/sliceCount and only minimum width and height given +LatteTextureView* LatteTextureViewLookupCache::lookupSliceMinSize(MPTR physAddr, sint32 minWidth, sint32 minHeight, sint32 pitch, sint32 firstMip, sint32 firstSlice, Latte::E_GX2SURFFMT format) +{ + uint32 key = _getViewBucketKeyNoRes(physAddr, pitch); + key %= TEXTURE_VIEW_BUCKETS; + for (auto& it : texViewBucket_nores[key].list) + { + if (it.width >= minWidth && it.height >= minHeight && it.pitch == pitch && it.physAddr == physAddr && it.format == format) + { + if (firstSlice == it.firstSlice && firstMip == it.firstMip) + return it.view; + } + } + return nullptr; +} + +// similar to lookupSlice but also compares isDepth +LatteTextureView* LatteTextureViewLookupCache::lookupSliceEx(MPTR physAddr, sint32 width, sint32 height, sint32 pitch, sint32 firstMip, sint32 firstSlice, Latte::E_GX2SURFFMT format, bool isDepth) +{ + cemu_assert_debug(firstMip == 0); + uint32 key = _getViewBucketKey(physAddr, width, height, pitch); + key %= TEXTURE_VIEW_BUCKETS; + for (auto& it : texViewBucket[key].list) + { + if (it.width == width && it.height == height && it.pitch == pitch && it.physAddr == physAddr && it.format == format && it.isDepth == isDepth) + { + if (firstSlice == it.firstSlice && firstMip == it.firstMip) + return it.view; + } + } + return nullptr; +} + +std::unordered_set<LatteTextureView*> LatteTextureViewLookupCache::GetAllViews() +{ + std::unordered_set<LatteTextureView*> viewSet; + for (uint32 i = 0; i < TEXTURE_VIEW_BUCKETS; i++) + { + for (auto& it : texViewBucket[i].list) + viewSet.emplace(it.view); + } + return viewSet; +} \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Core/LatteTextureView.h b/src/Cafe/HW/Latte/Core/LatteTextureView.h new file mode 100644 index 00000000..a6d2e16c --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteTextureView.h @@ -0,0 +1,52 @@ +#pragma once + +class LatteTextureView +{ +public: + enum class MagFilter + { + kLinear, + kNearestNeighbor, + }; + + LatteTextureView(class LatteTexture* texture, sint32 firstMip, sint32 mipCount, sint32 firstSlice, sint32 sliceCount, Latte::E_DIM dim, Latte::E_GX2SURFFMT format, bool registerView = true); + virtual ~LatteTextureView(); + + void CreateLookupForSubTexture(uint32 mipStart, uint32 sliceStart); + + class LatteTexture* baseTexture; + // view definition + // note that a view can be addressed by more than one combination of physAddress/firstMip/firstSlice + // thus the firstMip and firstSlice members of this class may not match the ones that were set in the GPU registers when this view was looked up + sint32 firstMip; + sint32 numMip; + sint32 firstSlice; + sint32 numSlice; + Latte::E_GX2SURFFMT format; // format of view, can differ from base texture + Latte::E_DIM dim; // dimension of view + // state + uint32 lastTextureBindIndex = 0; + // FBO association + std::vector<class LatteCachedFBO*> list_fboLookup; // only set for the first color texture of each FBO, or the depth texture if no color textures are present + std::vector<class LatteCachedFBO*> list_associatedFbo; // list of cached fbos that reference this texture view + // view lookup cache + std::vector<uint32> viewLookUpCacheKeys; + std::vector<uint32> viewLookUpCacheKeysNoRes; +}; + +class LatteTextureViewLookupCache +{ +public: + static void Add(LatteTextureView* view, uint32 baseMip = 0, uint32 baseSlice = 0); + static void RemoveAll(LatteTextureView* view); + + static LatteTextureView* lookup(MPTR physAddr, sint32 width, sint32 height, sint32 depth, sint32 pitch, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, Latte::E_GX2SURFFMT format, Latte::E_DIM dim); + static LatteTextureView* lookup(MPTR physAddr, sint32 width, sint32 height, sint32 depth, sint32 pitch, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, Latte::E_GX2SURFFMT format, Latte::E_DIM dim, bool isDepth); + static LatteTextureView* lookupSlice(MPTR physAddr, sint32 width, sint32 height, sint32 pitch, sint32 firstMip, sint32 firstSlice, Latte::E_GX2SURFFMT format); + static LatteTextureView* lookupSliceMinSize(MPTR physAddr, sint32 minWidth, sint32 minHeight, sint32 pitch, sint32 firstMip, sint32 firstSlice, Latte::E_GX2SURFFMT format); + static LatteTextureView* lookupSliceEx(MPTR physAddr, sint32 width, sint32 height, sint32 pitch, sint32 firstMip, sint32 firstSlice, Latte::E_GX2SURFFMT format, bool isDepth); + + static std::unordered_set<LatteTextureView*> GetAllViews(); + + +}; \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Core/LatteThread.cpp b/src/Cafe/HW/Latte/Core/LatteThread.cpp new file mode 100644 index 00000000..ce28c759 --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteThread.cpp @@ -0,0 +1,266 @@ +#include "Cafe/HW/Latte/ISA/RegDefines.h" +#include "Cafe/OS/libs/gx2/GX2.h" // todo - remove dependency +#include "Cafe/HW/Latte/Core/Latte.h" +#include "Cafe/HW/Latte/Core/LatteDraw.h" +#include "Cafe/HW/Latte/Core/LatteShader.h" +#include "Cafe/HW/Latte/Core/LatteAsyncCommands.h" +#include "Cafe/GameProfile/GameProfile.h" +#include "gui/guiWrapper.h" + +#include "Cafe/HW/Latte/Core/LatteBufferCache.h" + +#include "Cafe/HW/Latte/Renderer/Renderer.h" +#include "Cafe/HW/Latte/Core/LatteTexture.h" +#include "util/helpers/helpers.h" + +#include <imgui.h> +#include "config/ActiveSettings.h" + +#include "Cafe/CafeSystem.h" + +LatteGPUState_t LatteGPUState = {}; + +std::atomic_bool sLatteThreadRunning = false; +std::atomic_bool sLatteThreadFinishedInit = false; + +void LatteThread_Exit(); + +void Latte_LoadInitialRegisters() +{ + LatteGPUState.contextNew.CB_TARGET_MASK.set_MASK(0xFFFFFFFF); + LatteGPUState.contextNew.VGT_MULTI_PRIM_IB_RESET_INDX.set_RESTART_INDEX(0xFFFFFFFF); + LatteGPUState.contextRegister[Latte::REGADDR::PA_CL_CLIP_CNTL] = 0; + *(float*)&LatteGPUState.contextRegister[mmDB_DEPTH_CLEAR] = 1.0f; +} + +extern bool gx2WriteGatherInited; + +LatteTextureView* osScreenTVTex[2] = { nullptr }; +LatteTextureView* osScreenDRCTex[2] = { nullptr }; + +LatteTextureView* LatteHandleOSScreen_getOrCreateScreenTex(MPTR physAddress, uint32 width, uint32 height, uint32 pitch) +{ + LatteTextureView* texView = LatteTextureViewLookupCache::lookup(physAddress, width, height, 1, pitch, 0, 1, 0, 1, Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM, Latte::E_DIM::DIM_2D); + if (texView) + return texView; + return LatteTexture_CreateTexture(0, Latte::E_DIM::DIM_2D, physAddress, 0, Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM, width, height, 1, pitch, 1, 0, Latte::E_HWTILEMODE::TM_LINEAR_ALIGNED, false); +} + +void LatteHandleOSScreen_prepareTextures() +{ + osScreenTVTex[0] = LatteHandleOSScreen_getOrCreateScreenTex(LatteGPUState.osScreen.screen[0].physPtr, 1280, 720, 1280); + osScreenTVTex[1] = LatteHandleOSScreen_getOrCreateScreenTex(LatteGPUState.osScreen.screen[0].physPtr + 1280 * 720 * 4, 1280, 720, 1280); + osScreenDRCTex[0] = LatteHandleOSScreen_getOrCreateScreenTex(LatteGPUState.osScreen.screen[1].physPtr, 854, 480, 0x380); + osScreenDRCTex[1] = LatteHandleOSScreen_getOrCreateScreenTex(LatteGPUState.osScreen.screen[1].physPtr + 896 * 480 * 4, 854, 480, 0x380); +} + +void LatteRenderTarget_copyToBackbuffer(LatteTextureView* textureView, bool isPadView); + +bool LatteHandleOSScreen_TV() +{ + if (!LatteGPUState.osScreen.screen[0].isEnabled) + return false; + if (LatteGPUState.osScreen.screen[0].flipExecuteCount == LatteGPUState.osScreen.screen[0].flipRequestCount) + return false; + LatteHandleOSScreen_prepareTextures(); + + sint32 bufferDisplayTV = (LatteGPUState.osScreen.screen[0].flipRequestCount & 1) ^ 1; + sint32 bufferDisplayDRC = (LatteGPUState.osScreen.screen[1].flipRequestCount & 1) ^ 1; + + const uint32 bufferIndexTV = (bufferDisplayTV); + const uint32 bufferIndexDRC = bufferDisplayDRC; + + g_renderer->texture_bindAndActivate(osScreenTVTex[bufferIndexTV], 0); + LatteTexture_ReloadData(osScreenTVTex[bufferIndexTV]->baseTexture, 0); + + // TV screen + LatteRenderTarget_copyToBackbuffer(osScreenTVTex[bufferIndexTV]->baseTexture->baseView, false); + + if (LatteGPUState.osScreen.screen[0].flipExecuteCount != LatteGPUState.osScreen.screen[0].flipRequestCount) + LatteGPUState.osScreen.screen[0].flipExecuteCount = LatteGPUState.osScreen.screen[0].flipRequestCount; + return true; +} + +bool LatteHandleOSScreen_DRC() +{ + if (!LatteGPUState.osScreen.screen[1].isEnabled) + return false; + if (LatteGPUState.osScreen.screen[1].flipExecuteCount == LatteGPUState.osScreen.screen[1].flipRequestCount) + return false; + LatteHandleOSScreen_prepareTextures(); + + sint32 bufferDisplayDRC = (LatteGPUState.osScreen.screen[1].flipRequestCount & 1) ^ 1; + + const uint32 bufferIndexDRC = bufferDisplayDRC; + + g_renderer->texture_bindAndActivate(osScreenDRCTex[bufferIndexDRC], 0); + LatteTexture_ReloadData(osScreenDRCTex[bufferIndexDRC]->baseTexture, 0); + + // GamePad screen + LatteRenderTarget_copyToBackbuffer(osScreenDRCTex[bufferIndexDRC]->baseTexture->baseView, true); + + if (LatteGPUState.osScreen.screen[1].flipExecuteCount != LatteGPUState.osScreen.screen[1].flipRequestCount) + LatteGPUState.osScreen.screen[1].flipExecuteCount = LatteGPUState.osScreen.screen[1].flipRequestCount; + return true; +} + +void LatteThread_HandleOSScreen() +{ + bool swapTV = LatteHandleOSScreen_TV(); + bool swapDRC = LatteHandleOSScreen_DRC(); + if(swapTV || swapDRC) + g_renderer->SwapBuffers(swapTV, swapDRC); +} + +int Latte_ThreadEntry() +{ + SetThreadName("LatteThread"); + sint32 w,h; + gui_getWindowSize(&w,&h); + + // imgui + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); + io.WantSaveIniSettings = false; + io.IniFilename = nullptr; + io.Fonts->AddFontDefault(); + + // renderer + g_renderer->Initialize(); + RendererOutputShader::InitializeStatic(); + io.DisplaySize = ImVec2((float)w, (float)h); + + LatteTiming_Init(); + LatteTexture_init(); + LatteTC_Init(); + LatteBufferCache_init(164 * 1024 * 1024); + LatteQuery_Init(); + LatteSHRC_Init(); + LatteStreamout_InitCache(); + + g_renderer->renderTarget_setViewport(0, 0, w, h, 0.0f, 1.0f); + + // enable GLSL gl_PointSize support + // glEnable(GL_PROGRAM_POINT_SIZE); // breaks shader caching on AMD (as of 2018) + + LatteGPUState.glVendor = GLVENDOR_UNKNOWN; + switch(g_renderer->GetVendor()) + { + case GfxVendor::AMD: + LatteGPUState.glVendor = GLVENDOR_AMD; + break; + case GfxVendor::IntelLegacy: + LatteGPUState.glVendor = GLVENDOR_INTEL_LEGACY; + break; + case GfxVendor::IntelNoLegacy: + LatteGPUState.glVendor = GLVENDOR_INTEL_NOLEGACY; + break; + case GfxVendor::Intel: + LatteGPUState.glVendor = GLVENDOR_INTEL; + break; + case GfxVendor::Nvidia: + LatteGPUState.glVendor = GLVENDOR_NVIDIA; + break; + default: + break; + } + + sLatteThreadFinishedInit = true; + + // register debug handler + if (cafeLog_isLoggingFlagEnabled(LOG_TYPE_OPENGL)) + g_renderer->EnableDebugMode(); + + // wait till a game is started + while( true ) + { + if( CafeSystem::IsTitleRunning() ) + break; + + g_renderer->DrawEmptyFrame(true); + g_renderer->DrawEmptyFrame(false); + + gui_hasScreenshotRequest(); // keep the screenshot request queue empty + std::this_thread::sleep_for(std::chrono::milliseconds(1000/60)); + } + + g_renderer->DrawEmptyFrame(true); + + // load/init shader cache file + LatteShaderCache_load(); + + // init registers + Latte_LoadInitialRegisters(); + // let CPU thread know the GPU is done initializing + g_isGPUInitFinished = true; + // wait until CPU has called GX2Init() + while (LatteGPUState.gx2InitCalled == 0) + { + std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + LatteThread_HandleOSScreen(); + if (!Latte_IsActive()) + LatteThread_Exit(); + } + gxRingBufferReadPtr = gx2WriteGatherPipe.gxRingBuffer; + LatteCP_ProcessRingbuffer(); + cemu_assert_debug(false); // should never reach + return 0; +} + +std::thread sLatteThread; +std::mutex sLatteThreadStateMutex; + +// initializes GPU thread which in turn also activates graphic packs +// does not return until the thread finished initialization +void Latte_Start() +{ + std::unique_lock _lock(sLatteThreadStateMutex); + cemu_assert_debug(!sLatteThreadRunning); + sLatteThreadRunning = true; + sLatteThreadFinishedInit = false; + sLatteThread = std::thread(Latte_ThreadEntry); + // wait until initialized + while (!sLatteThreadFinishedInit) + { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } +} + +void Latte_Stop() +{ + std::unique_lock _lock(sLatteThreadStateMutex); + sLatteThreadRunning = false; + _lock.unlock(); + sLatteThread.join(); +} + +bool Latte_IsActive() +{ + return sLatteThreadRunning; +} + +void LatteThread_Exit() +{ + if (g_renderer) + g_renderer->Shutdown(); + // clean up texture cache + LatteTC_UnloadAllTextures(); + // clean up runtime shader cache + // todo + // destroy renderer but make sure that g_renderer remains valid until the destructor has finished + if (g_renderer) + { + Renderer* renderer = g_renderer.get(); + delete renderer; + g_renderer.release(); + } + // reset GPU7 state + std::memset(&LatteGPUState, 0, sizeof(LatteGPUState)); + #if BOOST_OS_WINDOWS + ExitThread(0); + #else + pthread_exit(nullptr); + #endif + cemu_assert_unimplemented(); +} diff --git a/src/Cafe/HW/Latte/Core/LatteTiming.cpp b/src/Cafe/HW/Latte/Core/LatteTiming.cpp new file mode 100644 index 00000000..115b60d9 --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteTiming.cpp @@ -0,0 +1,173 @@ +#include "Cafe/HW/Latte/Core/Latte.h" +#include "Cafe/OS/libs/gx2/GX2_Event.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/VsyncDriver.h" +#include "util/highresolutiontimer/HighResolutionTimer.h" +#include "config/CemuConfig.h" +#include "Cafe/CafeSystem.h" + +sint32 s_customVsyncFrequency = -1; + +void LatteTiming_NotifyHostVSync(); + +// calculate time between vsync events in timer units +// standard rate on Wii U is 59.94, however to prevent tearing and microstutter on ~60Hz displays it is better if we slightly overshoot 60 Hz +// can be modified by graphic packs +HRTick LatteTime_CalculateTimeBetweenVSync() +{ + // 59.94 -> 60 * 0.999 + + HRTick tick = HighResolutionTimer::getFrequency(); + if (s_customVsyncFrequency > 0) + { + tick /= (uint64)s_customVsyncFrequency; + } + else + { + tick *= 1000ull; + tick /= 1002ull; + tick /= 60ull; + } + return tick; +} + +void LatteTiming_setCustomVsyncFrequency(sint32 frequency) +{ + s_customVsyncFrequency = frequency; +} + +void LatteTiming_disableCustomVsyncFrequency() +{ + s_customVsyncFrequency = -1; +} + +bool LatteTiming_getCustomVsyncFrequency(sint32& customFrequency) +{ + sint32 t = s_customVsyncFrequency; + if (t <= 0) + return false; + customFrequency = t; + return true; +} + +bool s_usingHostDrivenVSync = false; + +void LatteTiming_EnableHostDrivenVSync() +{ + if (s_usingHostDrivenVSync) + return; + VsyncDriver_startThread(LatteTiming_NotifyHostVSync); + s_usingHostDrivenVSync = true; +} + +bool LatteTiming_IsUsingHostDrivenVSync() +{ + return s_usingHostDrivenVSync; +} + +void LatteTiming_Init() +{ + LatteGPUState.timer_frequency = HighResolutionTimer::getFrequency(); + LatteGPUState.timer_bootUp = HighResolutionTimer::now().getTick(); + LatteGPUState.timer_nextVSync = LatteGPUState.timer_bootUp + LatteTime_CalculateTimeBetweenVSync(); +} + +void LatteTiming_signalVsync() +{ + static uint32 s_vsyncIntervalCounter = 0; + + if (!LatteGPUState.gx2InitCalled) + return; + s_vsyncIntervalCounter++; + uint32 swapInterval = 1; + if (LatteGPUState.sharedArea) + swapInterval = LatteGPUState.sharedArea->swapInterval; + + // flip + if (s_vsyncIntervalCounter >= swapInterval) + { + if (LatteGPUState.sharedArea) + { + // hack/workaround - only execute flip if GX2SwapScanBuffers() isn't lagging behind + uint64 currentTitleId = CafeSystem::GetForegroundTitleId(); + if (currentTitleId == 0x00050000101c9500 || currentTitleId == 0x00050000101c9400 || currentTitleId == 0x0005000e101c9300) + { + uint32 currentFlipRequestCount = _swapEndianU32(LatteGPUState.sharedArea->flipRequestCountBE); + uint32 currentFlipExecuteCount = _swapEndianU32(LatteGPUState.sharedArea->flipExecuteCountBE); + + if ((currentFlipRequestCount >= currentFlipExecuteCount) || (currentFlipExecuteCount - currentFlipRequestCount < 4)) + { + LatteGPUState.sharedArea->flipExecuteCountBE = _swapEndianU32(_swapEndianU32(LatteGPUState.sharedArea->flipExecuteCountBE) + 1); + } + + LatteGPUState.flipCounter++; + + } + else + { + // old code for all other games + if (LatteGPUState.flipRequestCount > 0) + { + LatteGPUState.flipRequestCount.fetch_sub(1); + LatteGPUState.sharedArea->flipExecuteCountBE = _swapEndianU32(_swapEndianU32(LatteGPUState.sharedArea->flipExecuteCountBE) + 1); + } + + } + } + GX2::__GX2NotifyEvent(GX2::GX2CallbackEventType::FLIP); + s_vsyncIntervalCounter = 0; + } + // vsync + GX2::__GX2NotifyEvent(GX2::GX2CallbackEventType::VSYNC); +} + +HRTick s_lastHostVsync = 0; + +// notify when host vsync event is triggered (on renderer canvas) +void LatteTiming_NotifyHostVSync() +{ + if (!LatteTiming_IsUsingHostDrivenVSync()) + return; + auto nowTimePoint = HighResolutionTimer::now().getTick(); + auto dif = nowTimePoint - s_lastHostVsync; + auto vsyncPeriod = LatteTime_CalculateTimeBetweenVSync(); + + + if (dif < vsyncPeriod) + { + // skip + return; + } + uint64 elapsedPeriods = dif / vsyncPeriod; + if (elapsedPeriods >= 10) + { + s_lastHostVsync = nowTimePoint; + } + else + s_lastHostVsync += vsyncPeriod; + + LatteTiming_signalVsync(); +} + +// handle timed vsync event +void LatteTiming_HandleTimedVsync() +{ + // simulate VSync + uint64 currentTimer = HighResolutionTimer::now().getTick(); + if( currentTimer >= LatteGPUState.timer_nextVSync ) + { + if(!LatteTiming_IsUsingHostDrivenVSync()) + LatteTiming_signalVsync(); + // even if vsync is delegated to the host device, we still use this virtual vsync timer to check finished states + LatteQuery_UpdateFinishedQueries(); + LatteTextureReadback_UpdateFinishedTransfers(false); + // update vsync timer + uint64 vsyncTime = LatteTime_CalculateTimeBetweenVSync(); + uint64 missedVsyncCount = (currentTimer - LatteGPUState.timer_nextVSync) / vsyncTime; + if (missedVsyncCount >= 2) + { + LatteGPUState.timer_nextVSync += vsyncTime*(missedVsyncCount+1ULL); + } + else + LatteGPUState.timer_nextVSync += vsyncTime; + } +} \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Core/LatteTiming.h b/src/Cafe/HW/Latte/Core/LatteTiming.h new file mode 100644 index 00000000..88f4578a --- /dev/null +++ b/src/Cafe/HW/Latte/Core/LatteTiming.h @@ -0,0 +1,7 @@ +#pragma once + +void LatteTiming_setCustomVsyncFrequency(sint32 frequency); +void LatteTiming_disableCustomVsyncFrequency(); +bool LatteTiming_getCustomVsyncFrequency(sint32& customFrequency); + +void LatteTiming_EnableHostDrivenVSync(); \ No newline at end of file diff --git a/src/Cafe/HW/Latte/ISA/LatteInstructions.h b/src/Cafe/HW/Latte/ISA/LatteInstructions.h new file mode 100644 index 00000000..70291104 --- /dev/null +++ b/src/Cafe/HW/Latte/ISA/LatteInstructions.h @@ -0,0 +1,924 @@ +#pragma once +#include "Cafe/HW/Latte/Core/LatteConst.h" + +namespace Latte +{ + using GPRType = uint8; +}; + +class LatteCFInstruction +{ +public: + enum class CF_COND + { + CF_COND_ACTIVE = 0, + CF_COND_FALSE = 1, + CF_COND_BOOL = 2, + CF_COND_NOT_BOOL = 3, + }; + + enum OPCODE + { + // SQ_CF_INST_* + INST_NOP = 0x00, + INST_TEX = 0x01, + INST_VTX = 0x02, // vertex fetch clause, used only in GS copy program? + INST_VTX_TC = 0x03, // vertex fetch clause, through texture cache + INST_LOOP_START = 0x04, // DX9 style loop + INST_LOOP_END = 0x05, + INST_LOOP_START_DX10 = 0x06, + INST_LOOP_BREAK = 0x09, + INST_JUMP = 0x0A, + INST_ELSE = 0x0D, + INST_POP = 0x0E, + INST_ELSE_AFTER = 0x0F, + INST_CALL = 0x12, + INST_CALL_FS = 0x13, + INST_RETURN = 0x14, + INST_EMIT_VERTEX = 0x15, // only available in geometry shader + INST_MEM_STREAM0_WRITE = 0x20, // for stream out (index selects buffer) + INST_MEM_STREAM1_WRITE = 0x21, + INST_MEM_STREAM2_WRITE = 0x22, + INST_MEM_STREAM3_WRITE = 0x23, + INST_MEM_RING_WRITE = 0x26, // used to pass data to/from geometry shader + INST_EXPORT = 0x27, + INST_EXPORT_DONE = 0x28, // last export + + // ALU instructions + MASK_ALU = 0x40, // mask to differentiate ALU instructions + INST_ALU = (0x08 | MASK_ALU), + INST_ALU_PUSH_BEFORE = (0x09 | MASK_ALU), + INST_ALU_POP_AFTER = (0x0A | MASK_ALU), + INST_ALU_POP2_AFTER = (0x0B | MASK_ALU), + // reserved + INST_ALU_CONTINUE = (0x0D | MASK_ALU), + INST_ALU_BREAK = (0x0E | MASK_ALU), + INST_ALU_ELSE_AFTER = (0x0F | MASK_ALU), + }; + + OPCODE getField_Opcode() const + { + uint32 cf_inst23_7 = (word1 >> 23) & 0x7F; + // check the bigger opcode fields first + if (cf_inst23_7 < 0x40) // starting at 0x40 the bits overlap with the ALU instruction encoding + { + // cf_inst23_7 is opcode + return (OPCODE)cf_inst23_7; + } + uint32 cf_inst26_4 = ((word1 >> 26) & 0xF); + // cf_inst26_4 is ALU opcode + return (OPCODE)(cf_inst26_4 | OPCODE::MASK_ALU); + } + + bool getField_END_OF_PROGRAM() const + { + // shared by all CF instruction types except ALU + cemu_assert_debug((getField_Opcode() & OPCODE::MASK_ALU) != OPCODE::MASK_ALU); + return ((word1 >> 21) & 1) != 0; + } + + const class LatteCFInstruction_ALU* getParser_ALU() const + { + cemu_assert_debug((getField_Opcode() & OPCODE::MASK_ALU) == OPCODE::MASK_ALU); + return (const LatteCFInstruction_ALU*)this; + } + + // EXPORT is for: + // SQ_CF_INST_MEM_STREAM0 - SQ_CF_INST_MEM_STREAM3 + // SQ_CF_INST_MEM_SCRATCH + // SQ_CF_INST_MEM_REDUCTION + // SQ_CF_INST_MEM_RING + // SQ_CF_INST_EXPORT + // SQ_CF_INST_EXPORT_DONE + const class LatteCFInstruction_EXPORT_IMPORT* getParser_EXPORT() const + { + return (const LatteCFInstruction_EXPORT_IMPORT*)this; + } + + template<typename TCFEncoding> + const TCFEncoding* getParserIfOpcodeMatch() const + { + auto opcode = getField_Opcode(); + if (TCFEncoding::MatchesOpcode(opcode)) + return (const TCFEncoding*)this; + return nullptr; + } + + // writing + void setField_Opcode(OPCODE opcode) + { + cemu_assert_debug(((uint32)opcode & (uint32)OPCODE::MASK_ALU) == 0); + word1 &= ~(0xF << 23); + word1 |= ((uint32)opcode << 23); + } + +protected: + uint32 word0; + uint32 word1; +}; + +// default encoding, CF_DWORD0 + CF_DWORD1 +// used for opcodes: See list in MatchesOpcode() +class LatteCFInstruction_DEFAULT : public LatteCFInstruction +{ +public: + LatteCFInstruction_DEFAULT() + { + word0 = 0; + word1 = 0; + } + + static bool MatchesOpcode(const OPCODE opcode) + { + return opcode == OPCODE::INST_NOP || + opcode == OPCODE::INST_VTX || + opcode == OPCODE::INST_VTX_TC || + opcode == OPCODE::INST_TEX || + opcode == OPCODE::INST_CALL_FS || + opcode == OPCODE::INST_CALL || + opcode == OPCODE::INST_RETURN || + opcode == OPCODE::INST_LOOP_START || + opcode == OPCODE::INST_LOOP_END || + opcode == OPCODE::INST_LOOP_START_DX10 || + //opcode == OPCODE::INST_LOOP_CONTINUE || + //opcode == OPCODE::INST_LOOP_BREAK || + opcode == OPCODE::INST_JUMP || + //opcode == OPCODE::INST_PUSH || + //opcode == OPCODE::INST_PUSH_ELSE || + opcode == OPCODE::INST_ELSE || + opcode == OPCODE::INST_POP || + //opcode == OPCODE::INST_POP_JUMP || + opcode == OPCODE::INST_JUMP || + //opcode == OPCODE::INST_POP_PUSH || + //opcode == OPCODE::INST_PUSH || + //opcode == OPCODE::INST_POP_PUSH_ELSE || + //opcode == OPCODE::INST_PUSH_ELSE || + opcode == OPCODE::INST_EMIT_VERTEX + //opcode == OPCODE::INST_EMIT_CUT_VERTEX || + //opcode == OPCODE::INST_CUT_VERTEX || + //opcode == OPCODE::INST_KILL + ; + } + + // returns offset in bytes + uint32 getField_ADDR() const // returns offset in bytes + { + return word0 << 3; + } + + uint32 getField_POP_COUNT() const + { + return (word1 >> 0) & 7; + } + + uint32 getField_CF_CONST() const + { + return (word1 >> 3) & 0x1F; + } + + CF_COND getField_COND() const + { + return (CF_COND)((word1 >> 8) & 0x3); + } + + uint32 getField_COUNT() const + { + uint32 count = (word1 >> 10) & 0x7; // R600 field + count |= ((word1 >> 16)&0x8); // R700 has an extra bit at 19 + return count + 1; + } + + uint32 getField_CALL_COUNT() const + { + return (word1 >> 13) & 0x3F; + } + + uint32 getField_VALID_PIXEL_MODE() const + { + return (word1 >> 22) & 1; + } + + uint32 getField_WHOLE_QUAD_MODE() const + { + return (word1 >> 30) & 1; + } + + uint32 getField_BARRIER() const + { + return (word1 >> 31) & 1; + } + + std::span<uint8> getClauseCode(std::span<uint8> programCode) const + { + cemu_assert_debug(getField_Opcode() == LatteCFInstruction::INST_VTX || getField_Opcode() == LatteCFInstruction::INST_VTX_TC); + cemu_assert_debug(getField_ADDR() <= programCode.size()); + cemu_assert_debug((programCode.size() - getField_ADDR()) >= getField_COUNT() * 16); + return programCode.subspan(getField_ADDR(), getField_COUNT() * 16); + } + + // writing + void setField_ADDR(uint32 addrInBytes) // in bytes + { + word0 = addrInBytes >> 3; + } + + void setField_COUNT(uint32 count) + { + cemu_assert_debug(count > 0 && count <= 16); + count--; + word1 &= ~((0x7 << 10) | (1 << 19)); + word1 |= ((count & 0x7) << 10); + word1 |= ((count << 16) & (1<<19)); + } + + void setField_BARRIER(bool isEnabled) + { + if (isEnabled) + word1 |= (1 << 31); + else + word1 &= ~(1 << 31); + } + +}; + +// CF_ALLOC_EXPORT_DWORD0 + CF_ALLOC_EXPORT_DWORD1_BUF / CF_ALLOC_EXPORT_DWORD1_SWIZ +// this has two different encoding. Use isEncodingBUF() to determine which fields are valid +class LatteCFInstruction_EXPORT_IMPORT : public LatteCFInstruction // CF_ALLOC_EXPORT_DWORD1_SWIZ +{ +public: + static bool MatchesOpcode(const OPCODE opcode) + { + return opcode == OPCODE::INST_MEM_STREAM0_WRITE || + opcode == OPCODE::INST_MEM_STREAM1_WRITE || + opcode == OPCODE::INST_MEM_STREAM2_WRITE || + opcode == OPCODE::INST_MEM_STREAM3_WRITE || + //opcode == OPCODE::INST_MEM_SCRATCH || + //opcode == OPCODE::INST_MEM_REDUCTION || + opcode == OPCODE::INST_MEM_RING_WRITE || + opcode == OPCODE::INST_EXPORT || + opcode == OPCODE::INST_EXPORT_DONE; + } + + enum EXPORT_TYPE : uint8 + { + PIXEL = 0, + POSITION = 1, + PARAMETER = 2, + UNUSED_VAL = 3 + }; + + enum COMPSEL : uint8 + { + X, + Y, + Z, + W, + CONST_0F, + CONST_1F, + RESERVED, + MASKED + }; + + EXPORT_TYPE getField_TYPE() const + { + return (EXPORT_TYPE)((word0 >> 13) & 0x3); + } + + uint32 getField_ARRAY_BASE() const + { + return (word0 >> 0) & 0x1FFF; + } + + uint32 getField_INDEX_GPR() const + { + return (word0 >> 23) & 0x7F; + } + + // read/write GPR (source/destination) + uint32 getField_RW_GPR() const + { + return (word0 >> 15) & 0x7F; + } + + // if true, RW_GPR is indexed + bool getField_RW_REL() const + { + return ((word0 >> 22) & 0x1) != 0; + } + + uint8 getField_BURST_COUNT() const + { + return ((word1 >> 17) & 0xF) + 1; + } + + uint8 getField_ELEM_SIZE() const + { + return ((word0 >> 30) & 0x3) + 1; + } + + // word1 bits 0-15 differ depending on BUF/SWIZ encoding + // returns true if BUF encoding is used (BUF fields valid, SWIZ invalid). Otherwise SWIZ encoding is used (BUF fields invalid, SWIZ fields valid) + bool isEncodingBUF() const + { + return ((word1 >> 12) & 0xF) != 0; + } + + // fields specific to SWIZ encoding + + COMPSEL getSwizField_SEL_X() const { cemu_assert_debug(!isEncodingBUF()); return (COMPSEL)((word1 >> 0) & 0x7); } + COMPSEL getSwizField_SEL_Y() const { cemu_assert_debug(!isEncodingBUF()); return (COMPSEL)((word1 >> 3) & 0x7); } + COMPSEL getSwizField_SEL_Z() const { cemu_assert_debug(!isEncodingBUF()); return (COMPSEL)((word1 >> 6) & 0x7); } + COMPSEL getSwizField_SEL_W() const { cemu_assert_debug(!isEncodingBUF()); return (COMPSEL)((word1 >> 9) & 0x7); } + + // fields specific to BUF encoding (word1 bits 0-15) + + uint32 getBufField_ARRAY_SIZE() const + { + cemu_assert_debug(isEncodingBUF()); + return (word1 >> 0) & 0xFFF; + } + + // applies only to writes + uint32 getBufField_COMP_MASK() const + { + cemu_assert_debug(isEncodingBUF()); + return (word1 >> 12) & 0xF; + } + + // these are not specific to EXPORT instr? Move to LatteCFInstruction? + bool getValidPixelMode() const + { + return ((word1 >> 22) & 0x1) != 0; + } + + bool getWholeQuadMode() const + { + return ((word1 >> 30) & 0x1) != 0; + + } +}; + +// encoding for CF_ALU_DWORD0 + CF_ALU_DWORD1 +// used for: +// CF_INST_ALU +// CF_INST_ALU_PUSH_BEFORE +// CF_INST_ALU_POP_AFTER +// CF_INST_ALU_POP2_AFTER +// CF_INST_ALU_CONTINUE +// CF_INST_ALU_BREAK +// CF_INST_ALU_ELSE_AFTER +class LatteCFInstruction_ALU : public LatteCFInstruction +{ +public: + static bool MatchesOpcode(const OPCODE opcode) + { + return opcode == OPCODE::INST_ALU || + opcode == OPCODE::INST_ALU_PUSH_BEFORE || + opcode == OPCODE::INST_ALU_POP_AFTER || + opcode == OPCODE::INST_ALU_POP2_AFTER || + opcode == OPCODE::INST_ALU_CONTINUE || + opcode == OPCODE::INST_ALU_BREAK || + opcode == OPCODE::INST_ALU_ELSE_AFTER; + } + + uint32 getField_ADDR() const + { + return (word0 >> 0) & 0x3FFFFF; + } + + uint32 getField_COUNT() const + { + return ((word1 >> 18) & 0x7F) + 1; + } + + uint32 getField_KCACHE_BANK0() const + { + return (word0 >> 22) & 0xF; + } + + uint32 getField_KCACHE_BANK1() const + { + return (word0 >> 26) & 0xF; + } + + uint32 getField_KCACHE_ADDR0() const + { + return (word1 >> 2) & 0xFF; + } + + uint32 getField_KCACHE_ADDR1() const + { + return (word1 >> 10) & 0xFF; + } + + bool getField_USES_WATERFALL() const + { + return ((word1 >> 25)&1) !=0; + } + + // todo - KCACHE_MODE0, KCACHE_MODE1, WHOLE_QUAD_MODE, BARRIER +}; + +static_assert(sizeof(LatteCFInstruction) == 8); +static_assert(sizeof(LatteCFInstruction_DEFAULT) == 8); +static_assert(sizeof(LatteCFInstruction_EXPORT_IMPORT) == 8); +static_assert(sizeof(LatteCFInstruction_ALU) == 8); + +/* Latte instructions */ + +class LatteClauseInstruction_VTX // used by CF VTX and VTX_TC clauses +{ +public: + LatteClauseInstruction_VTX() + { + word0 = 0; + word1 = 0; + word2 = 0; + word3 = 0; + } + + // VTX_DWORD0 + enum class VTX_INST + { + _VTX_INST_FETCH = 0, + _VTX_INST_SEMANTIC = 1, + _VTX_INST_MEM = 2 + }; + + enum class SRC_SEL + { + SEL_X = 0, + SEL_Y = 1, + SEL_Z = 2, + SEL_W = 3, + }; + + enum class DST_SEL + { + SEL_X = 0, + SEL_Y = 1, + SEL_Z = 2, + SEL_W = 3, + SEL_0 = 4, // constant 0.0 + SEL_1 = 5, // constant 1.0 + SEL_RESERVED = 6, + SEL_MASK = 7, + }; + + enum class NUM_FORMAT_ALL // NFA + { + NUM_FORMAT_NORM = 0, // normalized to float (-1.0 to 1.0 for signed, 0.0 to 1.0 for unsigned) + NUM_FORMAT_INT = 1, // interpreted as integer + NUM_FORMAT_SCALED = 2, + }; + + enum class FORMAT_COMP + { + COMP_UNSIGNED = 0, + COMP_SIGNED = 1, + }; + + enum class SRF_MODE + { + SRF_MODE_ZERO_CLAMP_MINUS_ONE = 0, + SRF_MODE_NO_ZERO = 1, + }; + + // fields todo: + // FETCH_WHOLE_QUAD + // MEGA_FETCH_COUNT + // MEGA_FETCH (word2) + // ALT_CONST (word2) + + VTX_INST getField_VTX_INST() const // alias opcode + { + return (VTX_INST)((word0 >> 0) & 0x1F); + } + + LatteConst::VertexFetchType2 getField_FETCH_TYPE() const + { + return (LatteConst::VertexFetchType2)((word0 >> 5) & 0x3); + } + + uint32 getField_BUFFER_ID() const + { + return (word0 >> 8) & 0xFF; + } + + uint32 getField_SRC_GPR() const + { + return (word0 >> 16) & 0x7F; + } + + bool getField_SRC_REL() const + { + return ((word0 >> 23) & 1) != 0; + } + + SRC_SEL getField_SRC_SEL_X() const + { + return (SRC_SEL)((word0 >> 24) & 0x3); + } + + // WORD1 depends on instruction type but some fields are shared + // VTX_DWORD1 / VTX_DWORD1_GPR / VTX_DWORD1_SEM + + DST_SEL getField_DST_SEL(uint32 index) const // shared field + { + cemu_assert_debug(index <= 3); + return (DST_SEL)((word1 >> (9 + index*3)) & 0x7); + } + + bool getField_USE_CONST_FIELDS() const // shared field + { + return ((word1 >> 21) & 1) != 0; + } + + LatteConst::VertexFetchFormat getField_DATA_FORMAT() const // shared field + { + return (LatteConst::VertexFetchFormat)((word1 >> 22) & 0x3F); + } + + NUM_FORMAT_ALL getField_NUM_FORMAT_ALL() const // shared field + { + return (NUM_FORMAT_ALL)((word1 >> 28) & 3); + } + + FORMAT_COMP getField_FORMAT_COMP_ALL() const // shared field + { + return (FORMAT_COMP)((word1 >> 30) & 1); + } + + SRF_MODE getField_SRF_MODE_ALL() const // shared field + { + return (SRF_MODE)((word1 >> 30) & 1); + } + + // VTX_DWORD1_SEM specific fields (VTX_INST_SEMANTIC) + uint32 getFieldSEM_SEMANTIC_ID() const + { + return ((word1 >> 0) & 0xFF); + } + + // VTX_DWORD1_GPR specific fields (VTX_INST_FETCH?) + // todo + + // VTX_DWORD2 + uint32 getField_OFFSET() const // shared field + { + return ((word2 >> 0) & 0xFFFF); + } + + LatteConst::VertexFetchEndianMode getField_ENDIAN_SWAP() const // shared field + { + return (LatteConst::VertexFetchEndianMode)((word2 >> 16) & 0x3); + } + + bool getField_CONST_BUF_NO_STRIDE() const // shared field + { + return ((word2 >> 18) & 0x1) != 0; + } + + // writing + LatteClauseInstruction_VTX& setField_VTX_INST(VTX_INST inst) + { + word0 &= ~(0x1F << 0); + word0 |= ((uint32)inst << 0); + return *this; + } + + LatteClauseInstruction_VTX& setField_FETCH_TYPE(LatteConst::VertexFetchType2 fetchType) + { + word0 &= ~(0x3 << 5); + word0 |= ((uint32)fetchType << 5); + return *this; + } + + LatteClauseInstruction_VTX& setField_BUFFER_ID(uint32 bufferId) + { + word0 &= ~(0xFF << 8); + word0 |= (bufferId << 8); + return *this; + } + + LatteClauseInstruction_VTX& setField_SRC_GPR(uint32 srcGPR) + { + word0 &= ~(0x7F << 16); + word0 |= (srcGPR << 16); + return *this; + } + + LatteClauseInstruction_VTX& setField_SRC_REL(bool isRel) + { + if(isRel) + word0 |= (1 << 23); + else + word0 &= ~(0x1 << 23); + return *this; + } + + LatteClauseInstruction_VTX& setField_SRC_SEL_X(SRC_SEL srcSel) + { + word0 &= ~(0x3 << 24); + word0 |= ((uint32)srcSel << 24); + return *this; + } + + LatteClauseInstruction_VTX& setFieldSEM_SEMANTIC_ID(uint32 semanticId) + { + cemu_assert_debug(semanticId <= 0xFF); + word1 &= ~(0xFF); + word1 |= ((uint32)semanticId); + return *this; + } + + LatteClauseInstruction_VTX& setField_OFFSET(uint32 offset) + { + cemu_assert_debug(offset < 0x10000); + word2 &= ~(0xFFFF << 0); + word2 |= ((uint32)offset); + return *this; + } + + LatteClauseInstruction_VTX& setField_DATA_FORMAT(LatteConst::VertexFetchFormat fetchFormat) + { + word1 &= ~(0x3F << 22); + word1 |= ((uint32)fetchFormat << 22); + return *this; + } + + LatteClauseInstruction_VTX& setField_NUM_FORMAT_ALL(NUM_FORMAT_ALL nfa) + { + word1 &= ~(0x3 << 28); + word1 |= ((uint32)nfa << 28); + return *this; + } + + LatteClauseInstruction_VTX& setField_FORMAT_COMP_ALL(FORMAT_COMP componentFormat) + { + word1 &= ~(0x1 << 30); + word1 |= ((uint32)componentFormat << 30); + return *this; + } + + LatteClauseInstruction_VTX& setField_DST_SEL(uint32 index, DST_SEL dstSel) + { + cemu_assert_debug(index <= 3); + word1 &= ~(0x7 << (9 + index * 3)); + word1 |= ((uint32)dstSel << (9 + index * 3)); + return *this; + } + + LatteClauseInstruction_VTX& setField_ENDIAN_SWAP(LatteConst::VertexFetchEndianMode endianSwap) + { + word2 &= ~(0x3 << 16); + word2 |= ((uint32)endianSwap << 16); + return *this; + } + + +protected: + uint32 word0; + uint32 word1; + uint32 word2; + uint32 word3; // not used +}; + +static_assert(sizeof(LatteClauseInstruction_VTX) == 16); + +class LatteClauseInstruction_ALU +{ +public: + enum OPCODE_OP2 + { + ADD = 0x00, + MUL = 0x01, + MUL_IEEE = 0x02, + //#define ALU_OP2_INST_MAX (0x003) + //#define ALU_OP2_INST_MIN (0x004) + //#define ALU_OP2_INST_MAX_DX10 (0x005) + //#define ALU_OP2_INST_SETE (0x008) + //#define ALU_OP2_INST_SETGT (0x009) + //#define ALU_OP2_INST_SETGE (0x00A) + //#define ALU_OP2_INST_SETNE (0x00B) + //#define ALU_OP2_INST_SETE_DX10 (0x00C) + //#define ALU_OP2_INST_SETGT_DX10 (0x00D) + //#define ALU_OP2_INST_SETGE_DX10 (0x00E) + //#define ALU_OP2_INST_SETNE_DX10 (0x00F) + //#define ALU_OP2_INST_FLOOR (0x014) + //#define ALU_OP2_INST_FRACT (0x010) + //#define ALU_OP2_INST_TRUNC (0x011) + //#define ALU_OP2_INST_RNDNE (0x013) + //#define ALU_OP2_INST_MOVA_FLOOR (0x016) // changes address register + //#define ALU_OP2_INST_MOVA_INT (0x018) // changes address register + MOV = 0x19, + //#define ALU_OP2_INST_NOP (0x01A) + //#define ALU_OP2_INST_PRED_SETE (0x020) + //#define ALU_OP2_INST_PRED_SETGT (0x021) + //#define ALU_OP2_INST_PRED_SETGE (0x022) + //#define ALU_OP2_INST_PRED_SETNE (0x023) + //#define ALU_OP2_INST_AND_INT (0x030) // integer instruction + //#define ALU_OP2_INST_OR_INT (0x031) // integer instruction + //#define ALU_OP2_INST_XOR_INT (0x032) // integer instruction + //#define ALU_OP2_INST_NOT_INT (0x033) // integer instruction + //#define ALU_OP2_INST_ADD_INT (0x034) // integer instruction + //#define ALU_OP2_INST_SUB_INT (0x035) // integer instruction + //#define ALU_OP2_INST_MAX_INT (0x036) // integer instruction + //#define ALU_OP2_INST_MIN_INT (0x037) // integer instruction + //#define ALU_OP2_INST_SETE_INT (0x03A) // integer instruction + //#define ALU_OP2_INST_SETGT_INT (0x03B) // integer instruction + //#define ALU_OP2_INST_SETGE_INT (0x03C) // integer instruction + //#define ALU_OP2_INST_SETNE_INT (0x03D) // integer instruction + //#define ALU_OP2_INST_SETGT_UINT (0x03E) // integer instruction + //#define ALU_OP2_INST_SETGE_UINT (0x03F) // integer instruction + //#define ALU_OP2_INST_PRED_SETE_INT (0x042) // integer instruction + //#define ALU_OP2_INST_PRED_SETGT_INT (0x043) // integer instruction + //#define ALU_OP2_INST_PRED_SETGE_INT (0x044) // integer instruction + //#define ALU_OP2_INST_PRED_SETNE_INT (0x045) // integer instruction + //#define ALU_OP2_INST_KILLE (0x02C) + //#define ALU_OP2_INST_KILLGT (0x02D) + //#define ALU_OP2_INST_KILLGE (0x02E) + //#define ALU_OP2_INST_KILLE_INT (0x046) + //#define ALU_OP2_INST_KILLGT_INT (0x047) + //#define ALU_OP2_INST_KILLNE_INT (0x049) + DOT4 = 0x50, + //#define ALU_OP2_INST_DOT4_IEEE (0x051) + CUBE = 0x52, + EXP_IEEE = 0x61, + LOG_CLAMPED = 0x62, + LOG_IEEE = 0x63, + SQRT_IEEE = 0x6A, + SIN = 0x06E, + COS = 0x06F, + RECIP_FF = 0x65, + RECIP_IEEE = 0x66, + RECIPSQRT_CLAMPED = 0x67, + RECIPSQRT_FF = 0x68, + RECIPSQRT_IEEE = 0x69, + FLT_TO_INT = 0x6B, + INT_TO_FLOAT = 0x6C, + UINT_TO_FLOAT = 0x6D, + ASHR_INT = 0x70, + LSHR_INT = 0x71, + LSHL_INT = 0x72, + MULLO_INT = 0x73, + MULLO_UINT = 0x75, + FLT_TO_UINT = 0x79, + }; + + bool isOP3() const + { + uint32 alu_inst13_5 = (word1 >> 13) & 0x1F; + return alu_inst13_5 >= 0x8; + } + + OPCODE_OP2 getOP2Code() const + { + uint32 alu_inst7_11 = (word1 >> 7) & 0x7FF; + return (OPCODE_OP2)alu_inst7_11; + } + + bool isLastInGroup() const + { + return (word0 & 0x80000000) != 0; + } + + const class LatteClauseInstruction_ALU_OP2* getOP2Instruction() const + { + return (const class LatteClauseInstruction_ALU_OP2*)this; + } + +protected: + const uint32 word0 = 0; + const uint32 word1 = 0; +}; + +class LatteALUSrcSel +{ +public: + LatteALUSrcSel(const uint16 op) : m_op(op) {}; + + bool isGPR() const { return m_op < 128; }; + bool isAnyConst() const { return m_op >= 248 && m_op <= 252; }; + bool isConst_0F() const { return m_op == 248; }; + bool isLiteral() const { return m_op == 253; }; + bool isCFile() const { return m_op >= 256; }; + + uint32 getGPR() const { return m_op; }; + uint32 getCFile() const { return (m_op & 0xFF); }; + +private: + const uint16 m_op; // 0 - 511 +}; + +class LatteClauseInstruction_ALU_OP2 : public LatteClauseInstruction_ALU +{ +public: + uint32 getDestGpr() const + { + return (word1 >> 21) & 0x7F; + } + + uint32 getDestElem() const + { + return (word1 >> 29) & 3; + } + + bool getDestRel() const + { + return ((word1 >> 28) & 1) != 0; + } + + bool getDestClamp() const + { + return ((word1 >> 31) & 1) != 0; + } + + bool getWriteMask() const + { + return ((word1 >> 4) & 1) != 0; + } + + uint8 getOMod() const // use enum? + { + return (word1 >> 5) & 3; + } + + const LatteALUSrcSel getSrc0Sel() const + { + return LatteALUSrcSel((word0 >> 0) & 0x1FF); + } + + const uint8 getSrc0Chan() const + { + return (word0 >> 10) & 0x3; + } + + const LatteALUSrcSel getSrc1Sel() const + { + return LatteALUSrcSel((word0 >> 13) & 0x1FF); + } + + const uint8 getSrc1Chan() const + { + return (word0 >> 23) & 0x3; + } + + bool isSrc0Neg() const + { + return ((word0 >> 12) & 0x1) != 0; + } + + bool isSrc1Neg() const + { + return ((word0 >> 25) & 0x1) != 0; + } + + bool isSrc0Rel() const + { + return ((word0 >> 9) & 0x1) != 0; + } + + bool isSrc1Rel() const + { + return ((word0 >> 22) & 0x1) != 0; + } + + uint8 getIndexMode() const + { + return (word0 >> 26) & 7;; + } + + bool isTranscedentalUnit() const + { + const uint32 op2 = this->getOP2Code(); + switch (op2) + { + case COS: + case SIN: + case RECIP_FF: // todo: verify + case RECIP_IEEE: // todo: verify + case RECIPSQRT_IEEE: // todo: verify + case RECIPSQRT_CLAMPED: // todo: verify + case RECIPSQRT_FF: // todo: verify + case MULLO_INT: + case MULLO_UINT: + case FLT_TO_INT: + case FLT_TO_UINT: + case INT_TO_FLOAT: + case UINT_TO_FLOAT: + case LOG_CLAMPED: + case LOG_IEEE: + case EXP_IEEE: + case SQRT_IEEE: + return true; + default: + break; + } + return false; + } + +}; + +static_assert(sizeof(LatteClauseInstruction_ALU) == 8); + diff --git a/src/Cafe/HW/Latte/ISA/LatteReg.h b/src/Cafe/HW/Latte/ISA/LatteReg.h new file mode 100644 index 00000000..9d7934f8 --- /dev/null +++ b/src/Cafe/HW/Latte/ISA/LatteReg.h @@ -0,0 +1,1369 @@ +#pragma once + +namespace Latte +{ + // common enums + enum class E_DIM : uint32 // shared between Latte backend and GX2 code + { + DIM_1D = 0, + DIM_2D = 1, + DIM_3D = 2, + DIM_CUBEMAP = 3, + DIM_1D_ARRAY = 4, + DIM_2D_ARRAY = 5, + DIM_2D_MSAA = 6, + DIM_2D_ARRAY_MSAA = 7 + }; + + enum class E_AAMODE : uint32 // shared between Latte backend and GX2 code + { + AA_1X = 0, + AA_2X = 1, + AA_4X = 2, + AA_8X = 3, + }; + + enum class E_HWTILEMODE + { + TM_LINEAR_GENERAL = 0, // linear (pitch must be aligned to 8?) + TM_LINEAR_ALIGNED = 1, // pitch must be multiple of 64 pixels? + + TM_1D_TILED_THIN1 = 2, // no macro tiling, 8x8 micro tiles + TM_1D_TILED_THICK = 3, // no macro tiling, 8x8x4 micro tiles + + TM_2D_TILED_THIN1 = 4, // 4x2 + TM_2D_TILED_THIN2 = 5, // 2x4 + TM_2D_TILED_THIN4 = 6, // 1x8 + TM_2D_TILED_THICK = 7, // 4x2x1 + + TM_2B_TILED_THIN1 = 8, // 4x2 + TM_2B_TILED_THIN2 = 9, // 2x4 + TM_2B_TILED_THIN4 = 10, // 1x8 + TM_2B_TILED_THICK = 11, // 4x2x1 + + TM_3D_TILED_THIN1 = 12, // 4x2 + TM_3D_TILED_THICK = 13, // 4x2x1 + + TM_3B_TILED_THIN1 = 14, // 4x2 + TM_3B_TILED_THICK = 15, // 4x2x1 + }; + + enum class E_GX2TILEMODE : uint32 + { + // same as E_TILEMODE but contains additional options with special meaning + TM_LINEAR_GENERAL = 0, + TM_LINEAR_ALIGNED = 1, + + // micro-tiled + TM_1D_TILED_THIN1 = 2, + TM_1D_TILED_THICK = 3, + + // macro-tiled + TM_2D_TILED_THIN1 = 4, + TM_2D_TILED_THIN2 = 5, + TM_2D_TILED_THIN4 = 6, + TM_2D_TILED_THICK = 7, + + TM_2B_TILED_THIN1 = 8, + TM_2B_TILED_THIN2 = 9, + TM_2B_TILED_THIN4 = 10, + TM_2B_TILED_THICK = 11, + + TM_3D_TILED_THIN1 = 12, + TM_3D_TILED_THICK = 13, + + TM_3B_TILED_THIN1 = 14, + TM_3B_TILED_THICK = 15, + + // special + TM_LINEAR_SPECIAL = 16, + TM_32_SPECIAL = 32, + }; + + inline E_HWTILEMODE MakeHWTileMode(const E_GX2TILEMODE gx2Tilemode) + { + return (E_HWTILEMODE)((uint32)gx2Tilemode & 0xF); + } + + inline E_GX2TILEMODE MakeGX2TileMode(const E_HWTILEMODE hwTilemode) + { + return (E_GX2TILEMODE)hwTilemode; + } + + inline bool TM_IsMacroTiled(const E_HWTILEMODE tm) + { + return (uint32)tm >= 4; + } + + inline bool TM_IsMacroTiled(const E_GX2TILEMODE tm) + { + return (uint32)tm >= 4 && (uint32)tm != 16; + } + + inline bool TM_IsBankSwapped(const E_HWTILEMODE tileMode) + { + return + tileMode == E_HWTILEMODE::TM_2B_TILED_THIN1 || + tileMode == E_HWTILEMODE::TM_2B_TILED_THIN2 || + tileMode == E_HWTILEMODE::TM_2B_TILED_THIN4 || + tileMode == E_HWTILEMODE::TM_2B_TILED_THICK || + tileMode == E_HWTILEMODE::TM_3B_TILED_THIN1 || + tileMode == E_HWTILEMODE::TM_3B_TILED_THICK; + } + + enum class E_HWSURFFMT + { + INVALID_FORMAT = 0, + // hardware formats only + HWFMT_8 = 0x1, + HWFMT_4_4 = 0x2, + HWFMT_3_3_2 = 0x3, + HWFMT_16 = 0x5, + HWFMT_16_FLOAT = 0x6, + HWFMT_8_8 = 0x7, + HWFMT_5_6_5 = 0x8, + HWFMT_6_5_5 = 0x9, + HWFMT_1_5_5_5 = 0xA, + HWFMT_4_4_4_4 = 0xB, + HWFMT_5_5_5_1 = 0xC, + HWFMT_32 = 0xD, + HWFMT_32_FLOAT = 0xE, + HWFMT_16_16 = 0xF, + HWFMT_16_16_FLOAT = 0x10, + HWFMT_8_24 = 0x11, + HWFMT_8_24_FLOAT = 0x12, + HWFMT_24_8 = 0x13, + HWFMT_24_8_FLOAT = 0x14, + HWFMT_10_11_11 = 0x15, + HWFMT_10_11_11_FLOAT = 0x16, + HWFMT_11_11_10 = 0x17, + HWFMT_11_11_10_FLOAT = 0x18, + HWFMT_2_10_10_10 = 0x19, + HWFMT_8_8_8_8 = 0x1A, + HWFMT_10_10_10_2 = 0x1B, + HWFMT_X24_8_32_FLOAT = 0x1C, + HWFMT_32_32 = 0x1D, + HWFMT_32_32_FLOAT = 0x1E, + HWFMT_16_16_16_16 = 0x1F, + HWFMT_16_16_16_16_FLOAT = 0x20, + HWFMT_32_32_32_32 = 0x22, + HWFMT_32_32_32_32_FLOAT = 0x23, + HWFMT_BC1 = 0x31, + HWFMT_BC2 = 0x32, + HWFMT_BC3 = 0x33, + HWFMT_BC4 = 0x34, + HWFMT_BC5 = 0x35, + + // these formats exist in R600/R700 documentation, but GX2 doesn't seem to handle them. Are they supported? + U_HWFMT_BC6 = 0x36, + U_HWFMT_BC7 = 0x37, + U_HWFMT_32_32_32 = 0x2F, + U_HWFMT_32_32_32_FLOAT = 0x30, + + + }; + + enum class E_GX2SURFFMT // GX2 surface format + { + INVALID_FORMAT = 0, + // base hardware formats (shared with E_HWSURFFMT) + HWFMT_8 = 0x1, + HWFMT_4_4 = 0x2, + HWFMT_3_3_2 = 0x3, + HWFMT_16 = 0x5, + HWFMT_16_FLOAT = 0x6, + HWFMT_8_8 = 0x7, + HWFMT_5_6_5 = 0x8, + HWFMT_6_5_5 = 0x9, + HWFMT_1_5_5_5 = 0xA, + HWFMT_4_4_4_4 = 0xB, + HWFMT_5_5_5_1 = 0xC, + HWFMT_32 = 0xD, + HWFMT_32_FLOAT = 0xE, + HWFMT_16_16 = 0xF, + HWFMT_16_16_FLOAT = 0x10, + HWFMT_8_24 = 0x11, + HWFMT_8_24_FLOAT = 0x12, + HWFMT_24_8 = 0x13, + HWFMT_24_8_FLOAT = 0x14, + HWFMT_10_11_11 = 0x15, + HWFMT_10_11_11_FLOAT = 0x16, + HWFMT_11_11_10 = 0x17, + HWFMT_11_11_10_FLOAT = 0x18, + HWFMT_2_10_10_10 = 0x19, + HWFMT_8_8_8_8 = 0x1A, + HWFMT_10_10_10_2 = 0x1B, + HWFMT_X24_8_32_FLOAT = 0x1C, + HWFMT_32_32 = 0x1D, + HWFMT_32_32_FLOAT = 0x1E, + HWFMT_16_16_16_16 = 0x1F, + HWFMT_16_16_16_16_FLOAT = 0x20, + HWFMT_32_32_32_32 = 0x22, + HWFMT_32_32_32_32_FLOAT = 0x23, + HWFMT_BC1 = 0x31, + HWFMT_BC2 = 0x32, + HWFMT_BC3 = 0x33, + HWFMT_BC4 = 0x34, + HWFMT_BC5 = 0x35, + + // GX2 extra format bits + FMT_BIT_INT = 0x100, + FMT_BIT_SIGNED = 0x200, + FMT_BIT_SRGB = 0x400, + FMT_BIT_FLOAT = 0x800, + + // GX2 formats + R4_G4_UNORM = HWFMT_4_4, + + R5_G6_B5_UNORM = HWFMT_5_6_5, + R5_G5_B5_A1_UNORM = HWFMT_1_5_5_5, + R4_G4_B4_A4_UNORM = HWFMT_4_4_4_4, + A1_B5_G5_R5_UNORM = HWFMT_5_5_5_1, + + R8_UNORM = HWFMT_8, + R8_SNORM = (HWFMT_8 | FMT_BIT_SIGNED), + R8_UINT = (HWFMT_8 | FMT_BIT_INT), + R8_SINT = (HWFMT_8 | FMT_BIT_INT | FMT_BIT_SIGNED), + + R8_G8_UNORM = HWFMT_8_8, + R8_G8_SNORM = (HWFMT_8_8 | FMT_BIT_SIGNED), + R8_G8_UINT = (HWFMT_8_8 | FMT_BIT_INT), + R8_G8_SINT = (HWFMT_8_8 | FMT_BIT_INT | FMT_BIT_SIGNED), + + R8_G8_B8_A8_UNORM = HWFMT_8_8_8_8, + R8_G8_B8_A8_SNORM = (HWFMT_8_8_8_8 | FMT_BIT_SIGNED), + R8_G8_B8_A8_UINT = (HWFMT_8_8_8_8 | FMT_BIT_INT), + R8_G8_B8_A8_SINT = (HWFMT_8_8_8_8 | FMT_BIT_INT | FMT_BIT_SIGNED), + R8_G8_B8_A8_SRGB = (HWFMT_8_8_8_8 | FMT_BIT_SRGB), + + R10_G10_B10_A2_UNORM = HWFMT_2_10_10_10, + R10_G10_B10_A2_SNORM = (HWFMT_2_10_10_10 | FMT_BIT_SIGNED), + R10_G10_B10_A2_UINT = (HWFMT_2_10_10_10 | FMT_BIT_INT), + R10_G10_B10_A2_SINT = (HWFMT_2_10_10_10 | FMT_BIT_INT | FMT_BIT_SIGNED), + R10_G10_B10_A2_SRGB = (HWFMT_2_10_10_10 | FMT_BIT_SRGB), + + A2_B10_G10_R10_UNORM = HWFMT_10_10_10_2, + A2_B10_G10_R10_UINT = (HWFMT_10_10_10_2 | FMT_BIT_INT), + + R16_UNORM = HWFMT_16, + R16_SNORM = (HWFMT_16 | FMT_BIT_SIGNED), + R16_UINT = (HWFMT_16 | FMT_BIT_INT), + R16_SINT = (HWFMT_16 | FMT_BIT_INT | FMT_BIT_SIGNED), + R16_FLOAT = (HWFMT_16_FLOAT | FMT_BIT_FLOAT), + + R16_G16_UNORM = HWFMT_16_16, + R16_G16_SNORM = (HWFMT_16_16 | FMT_BIT_SIGNED), + R16_G16_UINT = (HWFMT_16_16 | FMT_BIT_INT), + R16_G16_SINT = (HWFMT_16_16 | FMT_BIT_INT | FMT_BIT_SIGNED), + R16_G16_FLOAT = (HWFMT_16_16_FLOAT | FMT_BIT_FLOAT), + + R16_G16_B16_A16_UNORM = HWFMT_16_16_16_16, + R16_G16_B16_A16_SNORM = (HWFMT_16_16_16_16 | FMT_BIT_SIGNED), + R16_G16_B16_A16_UINT = (HWFMT_16_16_16_16 | FMT_BIT_INT), + R16_G16_B16_A16_SINT = (HWFMT_16_16_16_16 | FMT_BIT_INT | FMT_BIT_SIGNED), + R16_G16_B16_A16_FLOAT = (HWFMT_16_16_16_16_FLOAT | FMT_BIT_FLOAT), + + + R24_X8_UNORM = (HWFMT_8_24), + R24_X8_FLOAT = (HWFMT_8_24 | FMT_BIT_FLOAT), + X24_G8_UINT = (HWFMT_8_24 | FMT_BIT_INT), + R32_X8_FLOAT = (HWFMT_X24_8_32_FLOAT | FMT_BIT_FLOAT), // R32_X8_FLOAT + X32_G8_UINT_X24 = (HWFMT_X24_8_32_FLOAT | FMT_BIT_INT), // X32_G8_UINT + + R11_G11_B10_FLOAT = (HWFMT_10_11_11_FLOAT | FMT_BIT_FLOAT), + + // 32bit component formats do not support SNORM/UNORM (at least GX2 doesnt expose it) + R32_UINT = (HWFMT_32 | FMT_BIT_INT), + R32_SINT = (HWFMT_32 | FMT_BIT_INT | FMT_BIT_SIGNED), + R32_FLOAT = (HWFMT_32_FLOAT | FMT_BIT_FLOAT), + + R32_G32_UINT = (HWFMT_32_32 | FMT_BIT_INT), + R32_G32_SINT = (HWFMT_32_32 | FMT_BIT_INT | FMT_BIT_SIGNED), + R32_G32_FLOAT = (HWFMT_32_32_FLOAT | FMT_BIT_FLOAT), + + R32_G32_B32_A32_UINT = (HWFMT_32_32_32_32 | FMT_BIT_INT), + R32_G32_B32_A32_SINT = (HWFMT_32_32_32_32 | FMT_BIT_INT | FMT_BIT_SIGNED), + R32_G32_B32_A32_FLOAT = (HWFMT_32_32_32_32_FLOAT | FMT_BIT_FLOAT), + + // depth + D24_S8_UNORM = (HWFMT_8_24), + D24_S8_FLOAT = (HWFMT_8_24 | FMT_BIT_FLOAT), + D32_S8_FLOAT = (HWFMT_X24_8_32_FLOAT | FMT_BIT_FLOAT), + D16_UNORM = HWFMT_16, + D32_FLOAT = (HWFMT_32_FLOAT | FMT_BIT_FLOAT), + + // compressed formats + BC1_UNORM = (HWFMT_BC1), + BC1_SRGB = (HWFMT_BC1 | FMT_BIT_SRGB), + BC2_UNORM = (HWFMT_BC2), + BC2_SRGB = (HWFMT_BC2 | FMT_BIT_SRGB), + BC3_UNORM = (HWFMT_BC3), + BC3_SRGB = (HWFMT_BC3 | FMT_BIT_SRGB), + BC4_UNORM = (HWFMT_BC4), + BC4_SNORM = (HWFMT_BC4 | FMT_BIT_SIGNED), + BC5_UNORM = (HWFMT_BC5), + BC5_SNORM = (HWFMT_BC5 | FMT_BIT_SIGNED), + + // special + NV12_UNORM = 0x81, + }; + DEFINE_ENUM_FLAG_OPERATORS(E_GX2SURFFMT); + + inline uint32 GetFormatBits(const Latte::E_HWSURFFMT hwFmt) + { + const uint8 sBitsTable[0x40] = { + 0x00,0x08,0x08,0x00,0x00,0x10,0x10,0x10, + 0x10,0x10,0x10,0x10,0x10,0x20,0x20,0x20, + 0x20,0x20,0x00,0x20,0x00,0x00,0x20,0x00, + 0x00,0x20,0x20,0x20,0x40,0x40,0x40,0x40, + 0x40,0x00,0x80,0x80,0x00,0x00,0x00,0x10, + 0x10,0x20,0x20,0x20,0x00,0x00,0x00,0x60, + 0x60,0x40,0x80,0x80,0x40,0x80,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + cemu_assert((uint32)hwFmt < 0x40); + return sBitsTable[(uint32)hwFmt]; + } + + inline uint32 GetFormatBits(const Latte::E_GX2SURFFMT gx2Fmt) + { + return GetFormatBits((Latte::E_HWSURFFMT)((uint32)gx2Fmt & 0x3F)); + } + + inline E_HWSURFFMT GetHWFormat(E_GX2SURFFMT format) + { + return (E_HWSURFFMT)((uint32)format & 0x3F); + } + + inline bool IsCompressedFormat(Latte::E_HWSURFFMT format) + { + return (uint32)format >= 0x31 && (uint32)format <= 0x35; + } + + inline bool IsCompressedFormat(Latte::E_GX2SURFFMT format) + { + return IsCompressedFormat((Latte::E_HWSURFFMT)((uint32)format & 0x3F)); + } + + enum GPU_LIMITS + { + NUM_VERTEX_BUFFERS = 16, + NUM_TEXTURES_PER_STAGE = 18, + NUM_SAMPLERS_PER_STAGE = 18, // is this 16 or 18? + NUM_COLOR_ATTACHMENTS = 8, + }; + + enum REGADDR + { + VGT_PRIMITIVE_TYPE = 0x2256, + + // each stage has 12 sets of 4 border color registers + TD_PS_SAMPLER0_BORDER_RED = 0x2900, + TD_PS_SAMPLER0_BORDER_GREEN = 0x2901, + TD_PS_SAMPLER0_BORDER_BLUE = 0x2902, + TD_PS_SAMPLER0_BORDER_ALPHA = 0x2903, + + TD_VS_SAMPLER0_BORDER_RED = 0x2980, + TD_VS_SAMPLER0_BORDER_GREEN = 0x2981, + TD_VS_SAMPLER0_BORDER_BLUE = 0x2982, + TD_VS_SAMPLER0_BORDER_ALPHA = 0x2983, + + TD_GS_SAMPLER0_BORDER_RED = 0x2A00, + TD_GS_SAMPLER0_BORDER_GREEN = 0x2A01, + TD_GS_SAMPLER0_BORDER_BLUE = 0x2A02, + TD_GS_SAMPLER0_BORDER_ALPHA = 0x2A03, + + DB_STENCIL_CLEAR = 0xA00A, + DB_DEPTH_CLEAR = 0xA00B, + + CB_TARGET_MASK = 0xA08E, + + PA_SC_GENERIC_SCISSOR_TL = 0xA090, + PA_SC_GENERIC_SCISSOR_BR = 0xA091, + + VGT_MULTI_PRIM_IB_RESET_INDX = 0xA103, + SX_ALPHA_TEST_CONTROL = 0xA104, + CB_BLEND_RED = 0xA105, + CB_BLEND_GREEN = 0xA106, + CB_BLEND_BLUE = 0xA107, + CB_BLEND_ALPHA = 0xA108, + + DB_STENCILREFMASK = 0xA10C, + DB_STENCILREFMASK_BF = 0xA10D, + SX_ALPHA_REF = 0xA10E, + PA_CL_VPORT_XSCALE = 0xA10F, + PA_CL_VPORT_XOFFSET = 0xA110, + PA_CL_VPORT_YSCALE = 0xA111, + PA_CL_VPORT_YOFFSET = 0xA112, + PA_CL_VPORT_ZSCALE = 0xA113, + PA_CL_VPORT_ZOFFSET = 0xA114, + + CB_BLEND0_CONTROL = 0xA1E0, // first + CB_BLEND7_CONTROL = 0xA1E7, // last + + DB_DEPTH_CONTROL = 0xA200, + + CB_COLOR_CONTROL = 0xA202, + + PA_CL_CLIP_CNTL = 0xA204, + PA_SU_SC_MODE_CNTL = 0xA205, + PA_CL_VTE_CNTL = 0xA206, + + PA_SU_POINT_SIZE = 0xA280, + PA_SU_POINT_MINMAX = 0xA281, + + VGT_GS_MODE = 0xA290, + + VGT_DMA_INDEX_TYPE = 0xA29F, // todo - verify offset + + // HiZ early stencil test? + DB_SRESULTS_COMPARE_STATE0 = 0xA34A, + DB_SRESULTS_COMPARE_STATE1 = 0xA34B, + + PA_SU_POLY_OFFSET_CLAMP = 0xA37F, + PA_SU_POLY_OFFSET_FRONT_SCALE = 0xA380, + PA_SU_POLY_OFFSET_FRONT_OFFSET = 0xA381, + PA_SU_POLY_OFFSET_BACK_SCALE = 0xA382, + PA_SU_POLY_OFFSET_BACK_OFFSET = 0xA383, + + // texture units + SQ_TEX_RESOURCE_WORD0_N_PS = 0xE000, + SQ_TEX_RESOURCE_WORD0_N_VS = 0xE460, + SQ_TEX_RESOURCE_WORD0_N_GS = 0xE930, + SQ_TEX_RESOURCE_WORD_FIRST = SQ_TEX_RESOURCE_WORD0_N_PS, + SQ_TEX_RESOURCE_WORD_LAST = (SQ_TEX_RESOURCE_WORD0_N_GS + GPU_LIMITS::NUM_TEXTURES_PER_STAGE * 7 - 1), + // there are 54 samplers with 3 registers each. 18 per stage. For stage indices see SAMPLER_BASE_INDEX_* + SQ_TEX_SAMPLER_WORD0_0 = 0xF000, + SQ_TEX_SAMPLER_WORD1_0 = 0xF001, + SQ_TEX_SAMPLER_WORD2_0 = 0xF002, + + + }; + + inline constexpr int SAMPLER_BASE_INDEX_PIXEL = 0; + inline constexpr int SAMPLER_BASE_INDEX_VERTEX = 18; + inline constexpr int SAMPLER_BASE_INDEX_GEOMETRY = 36; + +#define LATTE_BITFIELD(__regname, __bitIndex, __bitWidth) \ +auto& set_##__regname(uint32 newValue) \ +{ \ + cemu_assert_debug(newValue < (1u << (__bitWidth))); \ + v &= ~((((1u << (__bitWidth)) - 1u) << (__bitIndex))); \ + v |= (newValue << (__bitIndex)); \ + return *this; \ +} \ +uint32 get_##__regname() const \ +{ \ + return (v >> (__bitIndex))&((1u << (__bitWidth)) - 1u); \ +} + +#define LATTE_BITFIELD_SIGNED(__regname, __bitIndex, __bitWidth) \ +auto& set_##__regname(sint32 newValue) \ +{ \ + cemu_assert_debug(newValue < (1 << ((__bitWidth)-1))); \ + cemu_assert_debug(newValue >= -(1 << ((__bitWidth)-1))); \ + v &= ~((((1u << (__bitWidth)) - 1u) << (__bitIndex))); \ + v |= (((uint32)newValue & ((1u << (__bitWidth)) - 1u)) << (__bitIndex)); \ + return *this; \ +} \ +sint32 get_##__regname() const \ +{ \ + sint32 r = (v >> (__bitIndex))&((1u << (__bitWidth)) - 1u); \ + r = (r << (32 - (__bitWidth))); \ + r = (r >> (32 - (__bitWidth))); \ + return r; \ +} + +#define LATTE_BITFIELD_BOOL(__regname, __bitIndex) \ +auto& set_##__regname(bool newValue) \ +{ \ + if(newValue) \ + v |= (1u << (__bitIndex)); \ + else \ + v &= ~(1u << (__bitIndex)); \ + return *this; \ +} \ +bool get_##__regname() const \ +{ \ + return (v&(1u << (__bitIndex))) != 0; \ +} + +#define LATTE_BITFIELD_TYPED(__regname, __bitIndex, __bitWidth, __typename) \ +auto& set_##__regname(__typename newValue) \ +{ \ + cemu_assert_debug(static_cast<uint32>(newValue) < (1u << (__bitWidth))); \ + v &= ~((((1u << (__bitWidth)) - 1u) << (__bitIndex))); \ + v |= (static_cast<uint32>(newValue) << (__bitIndex)); \ + return *this; \ +} \ +__typename get_##__regname() const \ +{ \ + return static_cast<__typename>((v >> (__bitIndex))&((1u << (__bitWidth)) - 1u)); \ +} + +#define LATTE_BITFIELD_FULL_TYPED(__regname, __typename) \ +auto& set_##__regname(__typename newValue) \ +{ \ + v = (static_cast<uint32>(newValue)); \ + return *this; \ +} \ +__typename get_##__regname() const \ +{ \ + return static_cast<__typename>(v); \ +} + +#define LATTE_BITFIELD_FLOAT(__regname) \ +auto& set_##__regname(float newValue) \ +{ \ + *(float*)&v = newValue; \ + return *this; \ +} \ +float get_##__regname() const \ +{ \ + return *(float*)&v; \ +} + + class LATTEREG + { + public: + uint32 getRawValue() const + { + return v; + } + + uint32 getRawValueBE() const + { + return _swapEndianU32(v); + } + + protected: + uint32 v{}; + }; + + // shared enums + enum class E_COMPAREFUNC // used by depth test func and alpha test func + { + NEVER, + LESS, + EQUAL, + LEQUAL, + GREATER, + NOTEQUAL, + GEQUAL, + ALWAYS + }; + + enum class E_ENDIAN_SWAP + { + SWAP_NONE = 0, + }; + + struct LATTE_VGT_PRIMITIVE_TYPE : LATTEREG // 0x2256 + { + enum class E_PRIMITIVE_TYPE + { + NONE = 0x0, + POINTS = 0x1, + LINES = 0x2, + LINE_STRIP = 0x3, + TRIANGLES = 0x4, + TRIANGLE_FAN = 0x5, + TRIANGLE_STRIP = 0x6, + + LINES_ADJACENT = 0xA, + LINE_STRIP_ADJACENT = 0xB, + TRIANGLES_ADJACENT = 0xC, + TRIANGLE_STRIP_ADJACENT = 0xD, + + RECTS = 0x11, + LINE_LOOP = 0x12, + QUADS = 0x13, + QUAD_STRIP = 0x14, + }; + + LATTE_BITFIELD_FULL_TYPED(PRIMITIVE_MODE, E_PRIMITIVE_TYPE); + }; + + struct LATTE_TD_BORDER_COLOR : LATTEREG // 0x2900 - 0x2A47 + { + LATTE_BITFIELD_FLOAT(channelValue); + }; + + struct LATTE_DB_STENCIL_CLEAR : LATTEREG // 0xA00A + { + LATTE_BITFIELD(clearValue, 0, 8); + }; + + struct LATTE_DB_DEPTH_CLEAR : LATTEREG // 0xA00B + { + LATTE_BITFIELD_FLOAT(clearValue); + }; + + struct LATTE_CB_TARGET_MASK : LATTEREG // 0xA08E + { + LATTE_BITFIELD_FULL_TYPED(MASK, uint32); + }; + + struct LATTE_PA_SC_GENERIC_SCISSOR_TL : LATTEREG // 0xA090 + { + LATTE_BITFIELD(TL_X, 0, 15); + LATTE_BITFIELD(TL_Y, 16, 15); + LATTE_BITFIELD_BOOL(WINDOW_OFFSET_DISABLE, 31); + }; + + struct LATTE_PA_SC_GENERIC_SCISSOR_BR : LATTEREG // 0xA091 + { + LATTE_BITFIELD(BR_X, 0, 15); + LATTE_BITFIELD(BR_Y, 16, 15); + }; + + struct LATTE_VGT_MULTI_PRIM_IB_RESET_INDX : LATTEREG // 0xA103 + { + LATTE_BITFIELD_FULL_TYPED(RESTART_INDEX, uint32); + }; + + struct LATTE_SX_ALPHA_TEST_CONTROL : LATTEREG // 0xA104 + { + using E_ALPHA_FUNC = E_COMPAREFUNC; + + LATTE_BITFIELD_TYPED(ALPHA_FUNC, 0, 3, E_ALPHA_FUNC); + LATTE_BITFIELD_BOOL(ALPHA_TEST_ENABLE, 3); + LATTE_BITFIELD_BOOL(ALPHA_TEST_BYPASS, 8); + }; + + struct LATTE_CB_BLEND_RED : LATTEREG // 0xA105 + { + LATTE_BITFIELD_FLOAT(RED); + }; + + struct LATTE_CB_BLEND_GREEN : LATTEREG // 0xA106 + { + LATTE_BITFIELD_FLOAT(GREEN); + }; + + struct LATTE_CB_BLEND_BLUE : LATTEREG // 0xA107 + { + LATTE_BITFIELD_FLOAT(BLUE); + }; + + struct LATTE_CB_BLEND_ALPHA : LATTEREG // 0xA108 + { + LATTE_BITFIELD_FLOAT(ALPHA); + }; + + struct LATTE_DB_STENCILREFMASK : LATTEREG // 0xA10C + { + LATTE_BITFIELD(STENCILREF_F, 0, 8); + LATTE_BITFIELD(STENCILMASK_F, 8, 8); + LATTE_BITFIELD(STENCILWRITEMASK_F, 16, 8); + }; + + struct LATTE_DB_STENCILREFMASK_BF : LATTEREG // 0xA10D + { + LATTE_BITFIELD(STENCILREF_B, 0, 8); + LATTE_BITFIELD(STENCILMASK_B, 8, 8); + LATTE_BITFIELD(STENCILWRITEMASK_B, 16, 8); + }; + + struct LATTE_SX_ALPHA_REF : LATTEREG // 0xA10E + { + LATTE_BITFIELD_FLOAT(ALPHA_TEST_REF); + }; + + struct LATTE_PA_CL_VPORT_XSCALE : LATTEREG // 0xA10F + { + LATTE_BITFIELD_FLOAT(SCALE); + }; + + struct LATTE_PA_CL_VPORT_XOFFSET : LATTEREG // 0xA110 + { + LATTE_BITFIELD_FLOAT(OFFSET); + }; + + struct LATTE_PA_CL_VPORT_YSCALE : LATTEREG // 0xA111 + { + LATTE_BITFIELD_FLOAT(SCALE); + }; + + struct LATTE_PA_CL_VPORT_YOFFSET : LATTEREG // 0xA112 + { + LATTE_BITFIELD_FLOAT(OFFSET); + }; + + struct LATTE_PA_CL_VPORT_ZSCALE : LATTEREG // 0xA113 + { + LATTE_BITFIELD_FLOAT(SCALE); + }; + + struct LATTE_PA_CL_VPORT_ZOFFSET : LATTEREG // 0xA114 + { + LATTE_BITFIELD_FLOAT(OFFSET); + }; + + struct LATTE_CB_BLENDN_CONTROL : LATTEREG // 0xA1E0 - 0xA1E7 + { + enum class E_BLENDFACTOR + { + BLEND_ZERO = 0x00, + BLEND_ONE = 0x01, + BLEND_SRC_COLOR = 0x02, + BLEND_ONE_MINUS_SRC_COLOR = 0x03, + BLEND_SRC_ALPHA = 0x04, + BLEND_ONE_MINUS_SRC_ALPHA = 0x05, + BLEND_DST_ALPHA = 0x06, + BLEND_ONE_MINUS_DST_ALPHA = 0x07, + BLEND_DST_COLOR = 0x08, + BLEND_ONE_MINUS_DST_COLOR = 0x09, + BLEND_SRC_ALPHA_SATURATE = 0x0A, + BLEND_BOTH_SRC_ALPHA = 0x0B, + BLEND_BOTH_INV_SRC_ALPHA = 0x0C, + BLEND_CONST_COLOR = 0x0D, + BLEND_ONE_MINUS_CONST_COLOR = 0x0E, + BLEND_SRC1_COLOR = 0x0F, + BLEND_INV_SRC1_COLOR = 0x10, + BLEND_SRC1_ALPHA = 0x11, + BLEND_INV_SRC1_ALPHA = 0x12, + BLEND_CONST_ALPHA = 0x13, + BLEND_ONE_MINUS_CONST_ALPHA = 0x14 + }; + + enum class E_COMBINEFUNC + { + DST_PLUS_SRC = 0, + SRC_MINUS_DST = 1, + MIN_DST_SRC = 2, + MAX_DST_SRC = 3, + DST_MINUS_SRC = 4 + }; + + LATTE_BITFIELD_TYPED(COLOR_SRCBLEND, 0, 5, E_BLENDFACTOR); + LATTE_BITFIELD_TYPED(COLOR_COMB_FCN, 5, 3, E_COMBINEFUNC); + LATTE_BITFIELD_TYPED(COLOR_DSTBLEND, 8, 5, E_BLENDFACTOR); + LATTE_BITFIELD_BOOL(OPACITY_WEIGHT, 13); + LATTE_BITFIELD_TYPED(ALPHA_SRCBLEND, 16, 5, E_BLENDFACTOR); + LATTE_BITFIELD_TYPED(ALPHA_COMB_FCN, 21, 3, E_COMBINEFUNC); + LATTE_BITFIELD_TYPED(ALPHA_DSTBLEND, 24, 5, E_BLENDFACTOR); + LATTE_BITFIELD_BOOL(SEPARATE_ALPHA_BLEND, 29); + }; + + struct LATTE_DB_DEPTH_CONTROL : LATTEREG // 0xA200 + { + using E_ZFUNC = E_COMPAREFUNC; + using E_STENCILFUNC = E_COMPAREFUNC; + + enum class E_STENCILACTION + { + KEEP, + ZERO, + REPLACE, + INCR, + DECR, + INVERT, + INCR_WRAP, + DECR_WRAP, + }; + + LATTE_BITFIELD_BOOL(STENCIL_ENABLE, 0); + LATTE_BITFIELD_BOOL(Z_ENABLE, 1); + LATTE_BITFIELD_BOOL(Z_WRITE_ENABLE, 2); + // bit 3 is unused? + LATTE_BITFIELD_TYPED(Z_FUNC, 4, 3, E_ZFUNC); + LATTE_BITFIELD_BOOL(BACK_STENCIL_ENABLE, 7); + + LATTE_BITFIELD_TYPED(STENCIL_FUNC_F, 8, 3, E_STENCILFUNC); + LATTE_BITFIELD_TYPED(STENCIL_FAIL_F, 11, 3, E_STENCILACTION); + LATTE_BITFIELD_TYPED(STENCIL_ZPASS_F, 14, 3, E_STENCILACTION); + LATTE_BITFIELD_TYPED(STENCIL_ZFAIL_F, 17, 3, E_STENCILACTION); + + LATTE_BITFIELD_TYPED(STENCIL_FUNC_B, 20, 3, E_STENCILFUNC); + LATTE_BITFIELD_TYPED(STENCIL_FAIL_B, 23, 3, E_STENCILACTION); + LATTE_BITFIELD_TYPED(STENCIL_ZPASS_B, 26, 3, E_STENCILACTION); + LATTE_BITFIELD_TYPED(STENCIL_ZFAIL_B, 29, 3, E_STENCILACTION); + }; + + struct LATTE_CB_COLOR_CONTROL : LATTEREG // 0xA202 + { + enum class E_SPECIALOP + { + NORMAL = 0, // use state to render + DISABLE = 1, // dont write color results + }; + + enum class E_LOGICOP + { + CLEAR = 0x00, + COPY = 0xCC, + OR = 0xEE, + SET = 0xFF, + }; + + LATTE_BITFIELD_BOOL(FOG_ENABLE, 0); + LATTE_BITFIELD_BOOL(MULTIWRITE_ENABLE, 1); + LATTE_BITFIELD_BOOL(DITHER_ENABLE, 2); + LATTE_BITFIELD_BOOL(DEGAMMA_ENABLE, 3); + LATTE_BITFIELD_TYPED(SPECIAL_OP, 4, 3, E_SPECIALOP); + LATTE_BITFIELD_BOOL(PER_MRT_BLEND, 7); + LATTE_BITFIELD(BLEND_MASK, 8, 8); + LATTE_BITFIELD_TYPED(ROP, 16, 8, E_LOGICOP); // aka logic op + }; + + struct LATTE_PA_CL_CLIP_CNTL : LATTEREG // 0xA204 + { + // todo - other fields + // see R6xx_3D_Registers.pdf + + LATTE_BITFIELD_BOOL(CLIP_DISABLE, 16); + + LATTE_BITFIELD_BOOL(DX_CLIP_SPACE_DEF, 19); // GX2 calls this flag HalfZ + + + LATTE_BITFIELD_BOOL(DX_RASTERIZATION_KILL, 22); + + LATTE_BITFIELD_BOOL(DX_LINEAR_ATTR_CLIP_ENA, 24); // what does this do? + + LATTE_BITFIELD_BOOL(ZCLIP_NEAR_DISABLE, 26); + LATTE_BITFIELD_BOOL(ZCLIP_FAR_DISABLE, 27); + + }; + + struct LATTE_PA_CL_VTE_CNTL : LATTEREG // 0xA206 + { + LATTE_BITFIELD_BOOL(VPORT_X_SCALE_ENA, 0); + LATTE_BITFIELD_BOOL(VPORT_X_OFFSET_ENA, 1); + + LATTE_BITFIELD_BOOL(VPORT_Y_SCALE_ENA, 2); + LATTE_BITFIELD_BOOL(VPORT_Y_OFFSET_ENA, 3); + + LATTE_BITFIELD_BOOL(VPORT_Z_SCALE_ENA, 4); + LATTE_BITFIELD_BOOL(VPORT_Z_OFFSET_ENA, 5); + + LATTE_BITFIELD_BOOL(VTX_XY_FMT, 8); + LATTE_BITFIELD_BOOL(VTX_Z_FMT, 9); + LATTE_BITFIELD_BOOL(VTX_W0_FMT, 10); + }; + + struct LATTE_PA_SU_POINT_SIZE : LATTEREG // 0xA280 + { + LATTE_BITFIELD(HEIGHT, 0, 16); + LATTE_BITFIELD(WIDTH, 16, 16); + }; + + struct LATTE_PA_SU_POINT_MINMAX : LATTEREG // 0xA281 + { + LATTE_BITFIELD(MIN_SIZE, 0, 16); + LATTE_BITFIELD(MAX_SIZE, 16, 16); + }; + + struct LATTE_VGT_GS_MODE : LATTEREG // 0xA290 + { + enum class E_MODE + { + OFF = 0, + SCENARIO_A = 1, + SCENARIO_B = 2, + SCENARIO_G = 3 + }; + + enum class E_CUT_MODE + { + CUT_1024 = 0, + CUT_512 = 1, + CUT_256 = 2, + CUT_128 = 3, + }; + + enum class E_COMPUTE_MODE + { + OFF = 0, + UKN_1 = 1, + UKN_2 = 2, + ON = 3, + }; + + LATTE_BITFIELD_TYPED(MODE, 0, 2, E_MODE); + LATTE_BITFIELD_BOOL(ES_PASSTHRU, 2); + LATTE_BITFIELD_TYPED(CUT_MODE, 3, 2, E_CUT_MODE); + LATTE_BITFIELD_TYPED(COMPUTE_MODE, 14, 2, E_COMPUTE_MODE); + LATTE_BITFIELD_BOOL(PARTIAL_THD_AT_EOI, 17); + }; + + struct LATTE_VGT_DMA_INDEX_TYPE : LATTEREG // 0xA29F + { + enum class E_INDEX_TYPE + { + U16_LE = 0, // U16 + U32_LE = 1, // U32 + + U16_BE = 4, // U16 + SwapU16 + U32_BE = 9, // U32 + SwapU32 + + // index 0 -> U16 + // index 1 -> U32 + // bit 0x2 -> swap U16 + // bit 0x4 -> swap U32 + // bit 0x8 -> swap U32 (machine word?) + + AUTO = 0xFFFF, // helper value for tracking auto-generated indices. Not part of the actual enum + }; + + LATTE_BITFIELD_FULL_TYPED(INDEX_TYPE, E_INDEX_TYPE); + }; + + struct LATTE_PA_SU_POLY_OFFSET_CLAMP : LATTEREG // 0xA37F + { + LATTE_BITFIELD_FLOAT(CLAMP); + }; + + struct LATTE_PA_SU_POLY_OFFSET_FRONT_SCALE : LATTEREG // 0xA380 + { + LATTE_BITFIELD_FLOAT(SCALE); + }; + + struct LATTE_PA_SU_POLY_OFFSET_FRONT_OFFSET : LATTEREG // 0xA381 + { + LATTE_BITFIELD_FLOAT(OFFSET); + }; + + struct LATTE_PA_SU_POLY_OFFSET_BACK_SCALE : LATTEREG // 0xA382 + { + LATTE_BITFIELD_FLOAT(SCALE); + }; + + struct LATTE_PA_SU_POLY_OFFSET_BACK_OFFSET : LATTEREG // 0xA383 + { + LATTE_BITFIELD_FLOAT(OFFSET); + }; + + struct LATTE_SQ_TEX_RESOURCE_WORD0_N : LATTEREG // 0xE000 + index * 7 + { + LATTE_BITFIELD_TYPED(DIM, 0, 3, E_DIM); + LATTE_BITFIELD_TYPED(TILE_MODE, 3, 4, E_HWTILEMODE); + + LATTE_BITFIELD_BOOL(TILE_TYPE, 7); + + LATTE_BITFIELD(PITCH, 8, 11); + LATTE_BITFIELD(WIDTH, 19, 13); + }; + + struct LATTE_SQ_TEX_RESOURCE_WORD1_N : LATTEREG // 0xE001 + index * 7 + { + LATTE_BITFIELD(HEIGHT, 0, 13); + LATTE_BITFIELD(DEPTH, 13, 13); + LATTE_BITFIELD_TYPED(DATA_FORMAT, 26, 6, E_HWSURFFMT); + }; + + struct LATTE_SQ_TEX_RESOURCE_WORD2_N : LATTEREG // 0xE002 + index * 7 + { + LATTE_BITFIELD_FULL_TYPED(BASE_ADDRESS, uint32); + }; + + struct LATTE_SQ_TEX_RESOURCE_WORD3_N : LATTEREG // 0xE003 + index * 7 + { + LATTE_BITFIELD_FULL_TYPED(MIP_ADDRESS, uint32); + }; + + struct LATTE_SQ_TEX_RESOURCE_WORD4_N : LATTEREG // 0xE004 + index * 7 + { + enum class E_FORMAT_COMP + { + COMP_UNSIGNED = 0, + COMP_SIGNED = 1, + COMP_UNSIGNED_BIASED = 2, + }; + + enum class E_NUM_FORMAT_ALL + { + NUM_FORMAT_NORM = 0, + NUM_FORMAT_INT = 1, + NUM_FORMAT_SCALED = 2, + }; + + enum class E_SRF_MODE_ALL + { + SRF_MODE_ZERO_CLAMP_MINUS_ONE = 0, + SRF_MODE_NO_ZERO = 1, + }; + + using E_ENDIAN_SWAP = E_ENDIAN_SWAP; + + enum class E_SEL + { + SEL_X = 0, + SEL_Y = 1, + SEL_Z = 2, + SEL_W = 3, + SEL_0 = 4, + SEL_1 = 5 + }; + + LATTE_BITFIELD_TYPED(FORMAT_COMP_X, 0, 2, E_FORMAT_COMP); + LATTE_BITFIELD_TYPED(FORMAT_COMP_Y, 2, 2, E_FORMAT_COMP); + LATTE_BITFIELD_TYPED(FORMAT_COMP_Z, 4, 2, E_FORMAT_COMP); + LATTE_BITFIELD_TYPED(FORMAT_COMP_W, 6, 2, E_FORMAT_COMP); + LATTE_BITFIELD_TYPED(NUM_FORM_ALL, 8, 2, E_NUM_FORMAT_ALL); + LATTE_BITFIELD_TYPED(SRF_MODE_ALL, 10, 1, E_SRF_MODE_ALL); + LATTE_BITFIELD_BOOL(FORCE_DEGAMMA, 11); + LATTE_BITFIELD_TYPED(ENDIAN_SWAP, 12, 2, E_ENDIAN_SWAP); + LATTE_BITFIELD(REQUEST_SIZE, 14, 2); + LATTE_BITFIELD_TYPED(DST_SEL_X, 16, 3, E_SEL); + LATTE_BITFIELD_TYPED(DST_SEL_Y, 19, 3, E_SEL); + LATTE_BITFIELD_TYPED(DST_SEL_Z, 22, 3, E_SEL); + LATTE_BITFIELD_TYPED(DST_SEL_W, 25, 3, E_SEL); + LATTE_BITFIELD(BASE_LEVEL, 28, 4); + }; + + struct LATTE_SQ_TEX_RESOURCE_WORD5_N : LATTEREG // 0xE005 + index * 7 + { + LATTE_BITFIELD(LAST_LEVEL, 0, 4); // for MSAA textures, this stores the AA level + LATTE_BITFIELD(BASE_ARRAY, 4, 13); + LATTE_BITFIELD(LAST_ARRAY, 17, 13); + LATTE_BITFIELD_BOOL(UKN_BIT_30, 30); // may be a 2 bit value? + }; + + struct LATTE_SQ_TEX_RESOURCE_WORD6_N : LATTEREG // 0xE006 + index * 7 + { + enum class E_MPEG_CLAMP + { + UKN = 0, + }; + + enum class E_TYPE + { + VTX_INVALID_TEXTURE = 0, + VTX_INVALID_BUFFER = 1, + VTX_VALID_TEXTURE = 2, + VTX_VALID_BUFFER = 3, + }; + + // unsure if these are correct + + LATTE_BITFIELD_TYPED(MPEG_CLAMP, 0, 2, E_MPEG_CLAMP); + LATTE_BITFIELD(MAX_ANISO, 2, 3); + LATTE_BITFIELD(PERF_MODULATION, 5, 3); + LATTE_BITFIELD_BOOL(INTERLACED, 8); + LATTE_BITFIELD_TYPED(TYPE, 30, 2, E_TYPE); + }; + + struct LATTE_SQ_TEX_SAMPLER_WORD0_0 : LATTEREG // 0xF000+n*3 - 0xF??? + { + enum class E_CLAMP + { + WRAP = 0, + MIRROR = 1, + CLAMP_LAST_TEXEL = 2, + MIRROR_ONCE_LAST_TEXEL = 3, + CLAMP_HALF_BORDER = 4, + MIRROR_ONCE_HALF_BORDER = 5, + CLAMP_BORDER = 6, + MIRROR_ONCE_BORDER = 7, + }; + + enum class E_XY_FILTER + { + POINT = 0, + BILINEAR = 1, + BICUBIC = 2, + // 3 unused ? + ANISO_POINT = 4, + ANISO_BILINEAR = 5, + // 6, 7 unused ? + }; + + enum class E_Z_FILTER + { + NONE = 0, + POINT = 1, + LINEAR = 2, + // 3 is unused ? + }; + + enum class E_BORDER_COLOR_TYPE + { + TRANSPARENT_BLACK = 0, + OPAQUE_BLACK = 1, + OPAQUE_WHITE = 2, + REGISTER = 3, + }; + + enum class E_DEPTH_COMPARE + { + NEVER = 0, + LESS = 1, + EQUAL = 2, + LEQUAL = 3, + GREATER = 4, + NOTEQUAL = 5, + GEQUAL = 6, + ALWAYS = 7 + }; + + + enum class E_CHROMA_KEY + { + DISABLE = 0, + KILL = 1, + BLEND = 2, + // 3 is unused + }; + + LATTE_BITFIELD_TYPED(CLAMP_X, 0, 3, E_CLAMP); + LATTE_BITFIELD_TYPED(CLAMP_Y, 3, 3, E_CLAMP); + LATTE_BITFIELD_TYPED(CLAMP_Z, 6, 3, E_CLAMP); + LATTE_BITFIELD_TYPED(XY_MAG_FILTER, 9, 3, E_XY_FILTER); + LATTE_BITFIELD_TYPED(XY_MIN_FILTER, 12, 3, E_XY_FILTER); + LATTE_BITFIELD_TYPED(Z_FILTER, 15, 2, E_Z_FILTER); + LATTE_BITFIELD_TYPED(MIP_FILTER, 17, 2, E_Z_FILTER); + LATTE_BITFIELD(MAX_ANISO_RATIO, 19, 3); + LATTE_BITFIELD_TYPED(BORDER_COLOR_TYPE, 22, 2, E_BORDER_COLOR_TYPE); + LATTE_BITFIELD_BOOL(POINT_SAMPLING_CLAMP, 24); + LATTE_BITFIELD_BOOL(TEX_ARRAY_OVERRIDE, 25); + LATTE_BITFIELD_TYPED(DEPTH_COMPARE_FUNCTION, 26, 3, E_DEPTH_COMPARE); + LATTE_BITFIELD_TYPED(CHROMA_KEY, 28, 2, E_CHROMA_KEY); + LATTE_BITFIELD_BOOL(LOD_USES_MINOR_AXIS, 25); + }; + + struct LATTE_SQ_TEX_SAMPLER_WORD1_0 : LATTEREG // 0xF001+n*3 - 0xF??? + { + LATTE_BITFIELD(MIN_LOD, 0, 10); + LATTE_BITFIELD(MAX_LOD, 10, 10); + LATTE_BITFIELD_SIGNED(LOD_BIAS, 20, 12); + }; + + struct LATTE_SQ_TEX_SAMPLER_WORD2_0 : LATTEREG // 0xF002+n*3 - 0xF??? + { + enum E_SAMPLER_TYPE + { + UKN0 = 0, + UKN1 = 1, + }; + + LATTE_BITFIELD(LOD_BIAS_SECONDARY, 0, 12); + LATTE_BITFIELD_BOOL(MC_COORD_TRUNCATE, 12); + LATTE_BITFIELD_BOOL(FORCE_DEGAMMA, 13); + LATTE_BITFIELD_BOOL(HIGH_PRECISION_FILTER, 14); + + // PERF_MIP at 15, 3 bits + // PERF_Z at 18, 3 bits + // bit 21 is unused? + + LATTE_BITFIELD(ANISO_BIAS, 22, 6); // is size correct? + + //LATTE_BITFIELD_BOOL(FETCH_4, 26); overlaps with ANISO_BIAS + //LATTE_BITFIELD_BOOL(SAMPLE_IS_PCF, 27); overlaps with ANISO_BIAS + + LATTE_BITFIELD_TYPED(TYPE, 31, 1, E_SAMPLER_TYPE); + }; + + struct LATTE_PA_SU_SC_MODE_CNTL : LATTEREG // 0xA205 + { + enum class E_FRONTFACE + { + CCW = 0, + CW = 1 + }; + + enum class E_POLYGONMODE + { + UKN0 = 0, // default - render triangles + }; + + enum class E_PTYPE + { + POINTS = 0, + LINES = 1, + TRIANGLES = 2, + }; + + LATTE_BITFIELD_BOOL(CULL_FRONT, 0); + LATTE_BITFIELD_BOOL(CULL_BACK, 1); + LATTE_BITFIELD_TYPED(FRONT_FACE, 2, 1, E_FRONTFACE); + LATTE_BITFIELD_TYPED(POLYGON_MODE, 3, 2, E_POLYGONMODE); + LATTE_BITFIELD_TYPED(FRONT_POLY_MODE, 5, 3, E_PTYPE); + LATTE_BITFIELD_TYPED(BACK_POLY_MODE, 8, 3, E_PTYPE); + LATTE_BITFIELD_BOOL(OFFSET_FRONT_ENABLED, 11); + LATTE_BITFIELD_BOOL(OFFSET_BACK_ENABLED, 12); + LATTE_BITFIELD_BOOL(OFFSET_PARA_ENABLED, 13); // offset enable for lines and points? + // additional fields? + }; +} + +struct _LatteRegisterSetTextureUnit +{ + Latte::LATTE_SQ_TEX_RESOURCE_WORD0_N word0; + Latte::LATTE_SQ_TEX_RESOURCE_WORD1_N word1; + Latte::LATTE_SQ_TEX_RESOURCE_WORD2_N word2; + Latte::LATTE_SQ_TEX_RESOURCE_WORD3_N word3; + Latte::LATTE_SQ_TEX_RESOURCE_WORD4_N word4; + Latte::LATTE_SQ_TEX_RESOURCE_WORD5_N word5; + Latte::LATTE_SQ_TEX_RESOURCE_WORD6_N word6; +}; + +static_assert(sizeof(_LatteRegisterSetTextureUnit) == 28); + +struct _LatteRegisterSetSampler +{ + Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0 WORD0; + Latte::LATTE_SQ_TEX_SAMPLER_WORD1_0 WORD1; + Latte::LATTE_SQ_TEX_SAMPLER_WORD2_0 WORD2; +}; + +static_assert(sizeof(_LatteRegisterSetSampler) == 12); + +struct _LatteRegisterSetSamplerBorderColor +{ + Latte::LATTE_TD_BORDER_COLOR red; + Latte::LATTE_TD_BORDER_COLOR green; + Latte::LATTE_TD_BORDER_COLOR blue; + Latte::LATTE_TD_BORDER_COLOR alpha; +}; + +static_assert(sizeof(_LatteRegisterSetSamplerBorderColor) == 16); + +struct LatteContextRegister +{ + uint8 padding0[0x08958]; + + /* +0x08958 */ Latte::LATTE_VGT_PRIMITIVE_TYPE VGT_PRIMITIVE_TYPE; + uint8 padding5[0x0A400 - 0x0895C]; + /* +0x0A400 */ _LatteRegisterSetSamplerBorderColor TD_PS_SAMPLER_BORDER_COLOR[Latte::GPU_LIMITS::NUM_SAMPLERS_PER_STAGE]; + uint8 padding6[0x0A600 - 0x0A520]; + /* +0x0A600 */ _LatteRegisterSetSamplerBorderColor TD_VS_SAMPLER_BORDER_COLOR[Latte::GPU_LIMITS::NUM_SAMPLERS_PER_STAGE]; + uint8 padding7[0x0A800 - 0x0A720]; + /* +0x0A800 */ _LatteRegisterSetSamplerBorderColor TD_GS_SAMPLER_BORDER_COLOR[Latte::GPU_LIMITS::NUM_SAMPLERS_PER_STAGE]; + uint8 padding8[0x28238 - 0x0A920]; + /* +0x28238 */ Latte::LATTE_CB_TARGET_MASK CB_TARGET_MASK; + uint8 padding_2823C[4]; + /* +0x28240 */ Latte::LATTE_PA_SC_GENERIC_SCISSOR_TL PA_SC_GENERIC_SCISSOR_TL; + /* +0x28244 */ Latte::LATTE_PA_SC_GENERIC_SCISSOR_BR PA_SC_GENERIC_SCISSOR_BR; + uint8 padding_28248[0x2840C - 0x28248]; + /* +0x2840C */ Latte::LATTE_VGT_MULTI_PRIM_IB_RESET_INDX VGT_MULTI_PRIM_IB_RESET_INDX; + /* +0x28410 */ Latte::LATTE_SX_ALPHA_TEST_CONTROL SX_ALPHA_TEST_CONTROL; + /* +0x28414 */ Latte::LATTE_CB_BLEND_RED CB_BLEND_RED; + /* +0x28418 */ Latte::LATTE_CB_BLEND_GREEN CB_BLEND_GREEN; + /* +0x2841C */ Latte::LATTE_CB_BLEND_BLUE CB_BLEND_BLUE; + /* +0x28420 */ Latte::LATTE_CB_BLEND_ALPHA CB_BLEND_ALPHA; + uint8 padding_28424[0x28430 - 0x28424]; + /* +0x28430 */ Latte::LATTE_DB_STENCILREFMASK DB_STENCILREFMASK; + /* +0x28434 */ Latte::LATTE_DB_STENCILREFMASK_BF DB_STENCILREFMASK_BF; + /* +0x28438 */ Latte::LATTE_SX_ALPHA_REF SX_ALPHA_REF; + /* +0x2843C */ Latte::LATTE_PA_CL_VPORT_XSCALE PA_CL_VPORT_XSCALE; + /* +0x28440 */ Latte::LATTE_PA_CL_VPORT_XOFFSET PA_CL_VPORT_XOFFSET; + /* +0x28444 */ Latte::LATTE_PA_CL_VPORT_YSCALE PA_CL_VPORT_YSCALE; + /* +0x28448 */ Latte::LATTE_PA_CL_VPORT_YOFFSET PA_CL_VPORT_YOFFSET; + /* +0x2844C */ Latte::LATTE_PA_CL_VPORT_ZSCALE PA_CL_VPORT_ZSCALE; + /* +0x28450 */ Latte::LATTE_PA_CL_VPORT_ZOFFSET PA_CL_VPORT_ZOFFSET; + + uint8 padding_28450[0x28780 - 0x28454]; + + /* +0x28780 */ Latte::LATTE_CB_BLENDN_CONTROL CB_BLENDN_CONTROL[8]; + + uint8 padding_287A0[0x28800 - 0x287A0]; + + /* +0x28800 */ Latte::LATTE_DB_DEPTH_CONTROL DB_DEPTH_CONTROL; + uint8 padding_28804[4]; + /* +0x28808 */ Latte::LATTE_CB_COLOR_CONTROL CB_COLOR_CONTROL; + uint8 padding_2880C[4]; + /* +0x28810 */ Latte::LATTE_PA_CL_CLIP_CNTL PA_CL_CLIP_CNTL; + /* +0x28814 */ Latte::LATTE_PA_SU_SC_MODE_CNTL PA_SU_SC_MODE_CNTL; + /* +0x28818 */ Latte::LATTE_PA_CL_VTE_CNTL PA_CL_VTE_CNTL; + + uint8 padding_2881C[0x28A00 - 0x2881C]; + + /* +0x28A00 */ Latte::LATTE_PA_SU_POINT_SIZE PA_SU_POINT_SIZE; + /* +0x28A04 */ Latte::LATTE_PA_SU_POINT_MINMAX PA_SU_POINT_MINMAX; + + uint8 padding_28A08[0x28A40 - 0x28A08]; + + /* +0x28A40 */ Latte::LATTE_VGT_GS_MODE VGT_GS_MODE; + + uint8 padding_28A44[0x28A7C - 0x28A44]; + + /* +0x28A7C */ Latte::LATTE_VGT_DMA_INDEX_TYPE VGT_DMA_INDEX_TYPE; + + uint8 padding_28A80[0x28DFC - 0x28A80]; + + /* +0x28DFC */ Latte::LATTE_PA_SU_POLY_OFFSET_CLAMP PA_SU_POLY_OFFSET_CLAMP; + /* +0x28E00 */ Latte::LATTE_PA_SU_POLY_OFFSET_FRONT_SCALE PA_SU_POLY_OFFSET_FRONT_SCALE; + /* +0x28E04 */ Latte::LATTE_PA_SU_POLY_OFFSET_FRONT_OFFSET PA_SU_POLY_OFFSET_FRONT_OFFSET; + /* +0x28E08 */ Latte::LATTE_PA_SU_POLY_OFFSET_BACK_SCALE PA_SU_POLY_OFFSET_BACK_SCALE; + /* +0x28E0C */ Latte::LATTE_PA_SU_POLY_OFFSET_BACK_OFFSET PA_SU_POLY_OFFSET_BACK_OFFSET; + + uint8 padding_28E10[0x38000 - 0x28E10]; + + // texture units are mapped as following (18 sets each): + // 0xE000 0x38000 -> pixel shader + // 0xE460 0x39180 -> vertex shader / compute shader + // 0xE930 0x3A4C0 -> geometry shader + // there is register space for exactly 160 ps tex units and 176 vs tex units. It's unknown how many GS units there are. + + /* +0x38000 */ _LatteRegisterSetTextureUnit SQ_TEX_START_PS[Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE]; + _LatteRegisterSetTextureUnit _DUMMY_TEX_UNITS_PS[160 - Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE]; + /* +0x39180 */ _LatteRegisterSetTextureUnit SQ_TEX_START_VS[Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE]; + _LatteRegisterSetTextureUnit _DUMMY_TEX_UNITS_VS[176 - Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE]; + /* +0x3A4C0 */ _LatteRegisterSetTextureUnit SQ_TEX_START_GS[Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE]; + + uint8 padding_3A6B8[0x3C000 - 0x3A6B8]; + + /* +0x3C000 */ _LatteRegisterSetSampler SQ_TEX_SAMPLER[18 * 3]; + + /* +0x3C288 */ + uint8 padding_3C288[0x40000 - 0x3C288]; + + /* +0x40000 */ + // special state registers + uint32 hleSpecialState[9]; // deprecated + + uint32* GetRawView() const + { + return (uint32*)padding0; + } + + uint32* GetSpecialStateValues() const + { + return (uint32*)hleSpecialState; + } +}; + +static_assert(sizeof(LatteContextRegister) == 0x10000 * 4 + 9 * 4); + +static_assert(offsetof(LatteContextRegister, VGT_PRIMITIVE_TYPE) == Latte::REGADDR::VGT_PRIMITIVE_TYPE * 4); +static_assert(offsetof(LatteContextRegister, TD_PS_SAMPLER_BORDER_COLOR) == Latte::REGADDR::TD_PS_SAMPLER0_BORDER_RED * 4); +static_assert(offsetof(LatteContextRegister, TD_VS_SAMPLER_BORDER_COLOR) == Latte::REGADDR::TD_VS_SAMPLER0_BORDER_RED * 4); +static_assert(offsetof(LatteContextRegister, TD_GS_SAMPLER_BORDER_COLOR) == Latte::REGADDR::TD_GS_SAMPLER0_BORDER_RED * 4); +static_assert(offsetof(LatteContextRegister, CB_TARGET_MASK) == Latte::REGADDR::CB_TARGET_MASK * 4); +static_assert(offsetof(LatteContextRegister, PA_SC_GENERIC_SCISSOR_TL) == Latte::REGADDR::PA_SC_GENERIC_SCISSOR_TL * 4); +static_assert(offsetof(LatteContextRegister, PA_SC_GENERIC_SCISSOR_BR) == Latte::REGADDR::PA_SC_GENERIC_SCISSOR_BR * 4); +static_assert(offsetof(LatteContextRegister, VGT_MULTI_PRIM_IB_RESET_INDX) == Latte::REGADDR::VGT_MULTI_PRIM_IB_RESET_INDX * 4); +static_assert(offsetof(LatteContextRegister, SX_ALPHA_TEST_CONTROL) == Latte::REGADDR::SX_ALPHA_TEST_CONTROL * 4); +static_assert(offsetof(LatteContextRegister, DB_STENCILREFMASK) == Latte::REGADDR::DB_STENCILREFMASK * 4); +static_assert(offsetof(LatteContextRegister, DB_STENCILREFMASK_BF) == Latte::REGADDR::DB_STENCILREFMASK_BF * 4); +static_assert(offsetof(LatteContextRegister, SX_ALPHA_REF) == Latte::REGADDR::SX_ALPHA_REF * 4); +static_assert(offsetof(LatteContextRegister, CB_BLEND_RED) == Latte::REGADDR::CB_BLEND_RED * 4); +static_assert(offsetof(LatteContextRegister, CB_BLEND_GREEN) == Latte::REGADDR::CB_BLEND_GREEN * 4); +static_assert(offsetof(LatteContextRegister, CB_BLEND_BLUE) == Latte::REGADDR::CB_BLEND_BLUE * 4); +static_assert(offsetof(LatteContextRegister, CB_BLEND_ALPHA) == Latte::REGADDR::CB_BLEND_ALPHA * 4); +static_assert(offsetof(LatteContextRegister, PA_CL_VPORT_XSCALE) == Latte::REGADDR::PA_CL_VPORT_XSCALE * 4); +static_assert(offsetof(LatteContextRegister, PA_CL_VPORT_XOFFSET) == Latte::REGADDR::PA_CL_VPORT_XOFFSET * 4); +static_assert(offsetof(LatteContextRegister, PA_CL_VPORT_YSCALE) == Latte::REGADDR::PA_CL_VPORT_YSCALE * 4); +static_assert(offsetof(LatteContextRegister, PA_CL_VPORT_YOFFSET) == Latte::REGADDR::PA_CL_VPORT_YOFFSET * 4); +static_assert(offsetof(LatteContextRegister, PA_CL_VPORT_ZSCALE) == Latte::REGADDR::PA_CL_VPORT_ZSCALE * 4); +static_assert(offsetof(LatteContextRegister, PA_CL_VPORT_ZOFFSET) == Latte::REGADDR::PA_CL_VPORT_ZOFFSET * 4); +static_assert(offsetof(LatteContextRegister, PA_CL_CLIP_CNTL) == Latte::REGADDR::PA_CL_CLIP_CNTL * 4); +static_assert(offsetof(LatteContextRegister, PA_SU_SC_MODE_CNTL) == Latte::REGADDR::PA_SU_SC_MODE_CNTL * 4); +static_assert(offsetof(LatteContextRegister, PA_CL_VTE_CNTL) == Latte::REGADDR::PA_CL_VTE_CNTL * 4); +static_assert(offsetof(LatteContextRegister, PA_SU_POINT_SIZE) == Latte::REGADDR::PA_SU_POINT_SIZE * 4); +static_assert(offsetof(LatteContextRegister, PA_SU_POINT_MINMAX) == Latte::REGADDR::PA_SU_POINT_MINMAX * 4); +static_assert(offsetof(LatteContextRegister, CB_BLENDN_CONTROL) == Latte::REGADDR::CB_BLEND0_CONTROL * 4); +static_assert(offsetof(LatteContextRegister, DB_DEPTH_CONTROL) == Latte::REGADDR::DB_DEPTH_CONTROL * 4); +static_assert(offsetof(LatteContextRegister, CB_COLOR_CONTROL) == Latte::REGADDR::CB_COLOR_CONTROL * 4); +static_assert(offsetof(LatteContextRegister, VGT_GS_MODE) == Latte::REGADDR::VGT_GS_MODE * 4); +static_assert(offsetof(LatteContextRegister, VGT_DMA_INDEX_TYPE) == Latte::REGADDR::VGT_DMA_INDEX_TYPE * 4); +static_assert(offsetof(LatteContextRegister, PA_SU_POLY_OFFSET_CLAMP) == Latte::REGADDR::PA_SU_POLY_OFFSET_CLAMP * 4); +static_assert(offsetof(LatteContextRegister, PA_SU_POLY_OFFSET_FRONT_SCALE) == Latte::REGADDR::PA_SU_POLY_OFFSET_FRONT_SCALE * 4); +static_assert(offsetof(LatteContextRegister, PA_SU_POLY_OFFSET_FRONT_OFFSET) == Latte::REGADDR::PA_SU_POLY_OFFSET_FRONT_OFFSET * 4); +static_assert(offsetof(LatteContextRegister, PA_SU_POLY_OFFSET_BACK_SCALE) == Latte::REGADDR::PA_SU_POLY_OFFSET_BACK_SCALE * 4); +static_assert(offsetof(LatteContextRegister, PA_SU_POLY_OFFSET_BACK_OFFSET) == Latte::REGADDR::PA_SU_POLY_OFFSET_BACK_OFFSET * 4); +static_assert(offsetof(LatteContextRegister, SQ_TEX_START_PS) == Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_PS * 4); +static_assert(offsetof(LatteContextRegister, SQ_TEX_START_VS) == Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_VS * 4); +static_assert(offsetof(LatteContextRegister, SQ_TEX_START_GS) == Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_GS * 4); +static_assert(offsetof(LatteContextRegister, SQ_TEX_SAMPLER) == Latte::REGADDR::SQ_TEX_SAMPLER_WORD0_0 * 4); diff --git a/src/Cafe/HW/Latte/ISA/RegDefines.h b/src/Cafe/HW/Latte/ISA/RegDefines.h new file mode 100644 index 00000000..d28739c0 --- /dev/null +++ b/src/Cafe/HW/Latte/ISA/RegDefines.h @@ -0,0 +1,331 @@ +#pragma once +// todo - this file is superseded by LatteReg.h + +#include "Cafe/HW/Latte/ISA/LatteReg.h" + +#define mmPA_CL_VS_OUT_CNTL 0xA207 + +#define mmPA_SU_LINE_CNTL 0xA282 +#define mmPA_SU_POLY_OFFSET_DB_FMT_CNTL 0xA37E + +#define mmPA_SC_WINDOW_OFFSET 0xA080 +#define mmPA_SC_AA_CONFIG 0xA301 +#define mmPA_SC_AA_MASK 0xA312 +#define mmPA_SC_AA_SAMPLE_LOCS_MCTX 0xA307 +#define mmPA_SC_AA_SAMPLE_LOCS_8S_WD1_MCTX 0xA308 +#define mmPA_SC_LINE_STIPPLE 0xA283 +#define mmPA_SC_LINE_CNTL 0xA300 +#define mmPA_SC_SCREEN_SCISSOR_TL 0xA00C +#define mmPA_SC_SCREEN_SCISSOR_BR 0xA00D +#define mmPA_SC_WINDOW_SCISSOR_TL 0xA081 +#define mmPA_SC_WINDOW_SCISSOR_BR 0xA082 +#define mmPA_SC_CLIPRECT_RULE 0xA083 +#define mmPA_SC_CLIPRECT_0_TL 0xA084 +#define mmPA_SC_CLIPRECT_0_BR 0xA085 +#define mmPA_SC_CLIPRECT_1_TL 0xA086 +#define mmPA_SC_CLIPRECT_1_BR 0xA087 +#define mmPA_SC_CLIPRECT_2_TL 0xA088 +#define mmPA_SC_CLIPRECT_2_BR 0xA089 +#define mmPA_SC_CLIPRECT_3_TL 0xA08A +#define mmPA_SC_CLIPRECT_3_BR 0xA08B +#define mmPA_SC_EDGERULE 0xA08C + +#define mmPA_SC_MODE_CNTL 0xA293 +#define mmPA_SC_MPASS_PS_CNTL 0xA292 + +#define mmVGT_DRAW_INITIATOR 0xA1FC +#define mmVGT_EVENT_INITIATOR 0xA2A4 +#define mmVGT_EVENT_ADDRESS_REG 0xA1FE +#define mmVGT_DMA_BASE_HI 0xA1F9 +#define mmVGT_DMA_BASE 0xA1FA +#define mmVGT_DMA_INDEX_TYPE 0xA29F +#define mmVGT_DMA_NUM_INSTANCES 0xA2A2 +#define mmVGT_DMA_SIZE 0xA29D + +#define mmVGT_IMMED_DATA 0xA1FD +#define mmVGT_INDEX_TYPE 0x2257 +#define mmVGT_NUM_INDICES 0x225C +#define mmVGT_NUM_INSTANCES 0x225D +#define mmVGT_PRIMITIVE_TYPE 0x2256 +#define mmVGT_PRIMITIVEID_EN 0xA2A1 +#define mmVGT_VTX_CNT_EN 0xA2AE +#define mmVGT_REUSE_OFF 0xA2AD +#define mmVGT_INSTANCE_STEP_RATE_0 0xA2A8 +#define mmVGT_INSTANCE_STEP_RATE_1 0xA2A9 +#define mmVGT_MAX_VTX_INDX 0xA100 +#define mmVGT_MIN_VTX_INDX 0xA101 +#define mmVGT_INDX_OFFSET 0xA102 +#define mmVGT_VERTEX_REUSE_BLOCK_CNTL 0xA316 +#define mmVGT_OUT_DEALLOC_CNTL 0xA317 +#define mmVGT_MULTI_PRIM_IB_RESET_EN 0xA2A5 +#define mmVGT_ENHANCE 0xA294 +#define mmVGT_OUTPUT_PATH_CNTL 0xA284 +#define mmVGT_HOS_CNTL 0xA285 +#define mmVGT_HOS_MAX_TESS_LEVEL 0xA286 +#define mmVGT_HOS_MIN_TESS_LEVEL 0xA287 +#define mmVGT_HOS_REUSE_DEPTH 0xA288 +#define mmVGT_GROUP_PRIM_TYPE 0xA289 +#define mmVGT_GROUP_FIRST_DECR 0xA28A +#define mmVGT_GROUP_DECR 0xA28B +#define mmVGT_GROUP_VECT_0_CNTL 0xA28C +#define mmVGT_GROUP_VECT_1_CNTL 0xA28D +#define mmVGT_GROUP_VECT_0_FMT_CNTL 0xA28E +#define mmVGT_GROUP_VECT_1_FMT_CNTL 0xA28F +#define mmVGT_GS_OUT_PRIM_TYPE 0xA29B + +#define mmVGT_STRMOUT_EN 0xA2AC +#define mmVGT_STRMOUT_BUFFER_SIZE_0 0xA2B4 +#define mmVGT_STRMOUT_BUFFER_SIZE_1 0xA2B8 +#define mmVGT_STRMOUT_BUFFER_SIZE_2 0xA2BC +#define mmVGT_STRMOUT_BUFFER_SIZE_3 0xA2C0 +#define mmVGT_STRMOUT_BUFFER_OFFSET_0 0xA2B7 +#define mmVGT_STRMOUT_BUFFER_OFFSET_1 0xA2BB +#define mmVGT_STRMOUT_BUFFER_OFFSET_2 0xA2BF +#define mmVGT_STRMOUT_BUFFER_OFFSET_3 0xA2C3 +#define mmVGT_STRMOUT_VTX_STRIDE_0 0xA2B5 +#define mmVGT_STRMOUT_VTX_STRIDE_1 0xA2B9 +#define mmVGT_STRMOUT_VTX_STRIDE_2 0xA2BD +#define mmVGT_STRMOUT_VTX_STRIDE_3 0xA2C1 +#define mmVGT_STRMOUT_BUFFER_BASE_0 0xA2B6 +#define mmVGT_STRMOUT_BUFFER_BASE_1 0xA2BA +#define mmVGT_STRMOUT_BUFFER_BASE_2 0xA2BE +#define mmVGT_STRMOUT_BUFFER_BASE_3 0xA2C2 +#define mmVGT_STRMOUT_BUFFER_EN 0xA2C8 +#define mmVGT_STRMOUT_BASE_OFFSET_0 0xA2C4 +#define mmVGT_STRMOUT_BASE_OFFSET_1 0xA2C5 +#define mmVGT_STRMOUT_BASE_OFFSET_2 0xA2C6 +#define mmVGT_STRMOUT_BASE_OFFSET_3 0xA2C7 +#define mmVGT_STRMOUT_BASE_OFFSET_HI_0 0xA2D1 +#define mmVGT_STRMOUT_BASE_OFFSET_HI_1 0xA2D2 +#define mmVGT_STRMOUT_BASE_OFFSET_HI_2 0xA2D3 +#define mmVGT_STRMOUT_BASE_OFFSET_HI_3 0xA2D4 +#define mmVGT_STRMOUT_DRAW_OPAQUE_OFFSET 0xA2CA +#define mmVGT_STRMOUT_DRAW_OPAQUE_BUFFER_FILLED_SIZE 0xA2CB +#define mmVGT_STRMOUT_DRAW_OPAQUE_VERTEX_STRIDE 0xA2CC + +#define mmSQ_PGM_START_PS 0xA210 +#define mmSQ_PGM_CF_OFFSET_PS 0xA233 +#define mmSQ_PGM_RESOURCES_PS 0xA214 +#define mmSQ_PGM_EXPORTS_PS 0xA215 +#define mmSQ_PGM_START_VS 0xA216 +#define mmSQ_PGM_CF_OFFSET_VS 0xA234 +#define mmSQ_PGM_RESOURCES_VS 0xA21A +#define mmSQ_PGM_START_GS 0xA21B +#define mmSQ_PGM_CF_OFFSET_GS 0xA235 +#define mmSQ_PGM_RESOURCES_GS 0xA21F +#define mmSQ_PGM_START_ES 0xA220 +#define mmSQ_PGM_CF_OFFSET_ES 0xA236 +#define mmSQ_PGM_RESOURCES_ES 0xA224 +#define mmSQ_PGM_START_FS 0xA225 +#define mmSQ_PGM_CF_OFFSET_FS 0xA237 +#define mmSQ_PGM_RESOURCES_FS 0xA229 +#define mmSQ_ESGS_RING_ITEMSIZE 0xA22A +#define mmSQ_GSVS_RING_ITEMSIZE 0xA22B +#define mmSQ_ESTMP_RING_ITEMSIZE 0xA22C +#define mmSQ_GSTMP_RING_ITEMSIZE 0xA22D +#define mmSQ_VSTMP_RING_ITEMSIZE 0xA22E +#define mmSQ_PSTMP_RING_ITEMSIZE 0xA22F +#define mmSQ_FBUF_RING_ITEMSIZE 0xA230 +#define mmSQ_REDUC_RING_ITEMSIZE 0xA231 +#define mmSQ_GS_VERT_ITEMSIZE 0xA232 +#define mmSQ_VTX_SEMANTIC_CLEAR 0xA238 + +#define mmSQ_VTX_SEMANTIC_0 0xA0E0 +#define mmSQ_VTX_SEMANTIC_1 0xA0E1 +#define mmSQ_VTX_SEMANTIC_2 0xA0E2 +#define mmSQ_VTX_SEMANTIC_3 0xA0E3 +#define mmSQ_VTX_SEMANTIC_4 0xA0E4 +#define mmSQ_VTX_SEMANTIC_5 0xA0E5 +#define mmSQ_VTX_SEMANTIC_6 0xA0E6 +#define mmSQ_VTX_SEMANTIC_7 0xA0E7 +#define mmSQ_VTX_SEMANTIC_8 0xA0E8 +#define mmSQ_VTX_SEMANTIC_9 0xA0E9 +#define mmSQ_VTX_SEMANTIC_10 0xA0EA +#define mmSQ_VTX_SEMANTIC_11 0xA0EB +#define mmSQ_VTX_SEMANTIC_12 0xA0EC +#define mmSQ_VTX_SEMANTIC_13 0xA0ED +#define mmSQ_VTX_SEMANTIC_14 0xA0EE +#define mmSQ_VTX_SEMANTIC_15 0xA0EF +#define mmSQ_VTX_SEMANTIC_16 0xA0F0 +#define mmSQ_VTX_SEMANTIC_17 0xA0F1 +#define mmSQ_VTX_SEMANTIC_18 0xA0F2 +#define mmSQ_VTX_SEMANTIC_19 0xA0F3 +#define mmSQ_VTX_SEMANTIC_20 0xA0F4 +#define mmSQ_VTX_SEMANTIC_21 0xA0F5 +#define mmSQ_VTX_SEMANTIC_22 0xA0F6 +#define mmSQ_VTX_SEMANTIC_23 0xA0F7 +#define mmSQ_VTX_SEMANTIC_24 0xA0F8 +#define mmSQ_VTX_SEMANTIC_25 0xA0F9 +#define mmSQ_VTX_SEMANTIC_26 0xA0FA +#define mmSQ_VTX_SEMANTIC_27 0xA0FB +#define mmSQ_VTX_SEMANTIC_28 0xA0FC +#define mmSQ_VTX_SEMANTIC_29 0xA0FD +#define mmSQ_VTX_SEMANTIC_30 0xA0FE +#define mmSQ_VTX_SEMANTIC_31 0xA0FF + +#define mmSPI_VS_OUT_ID_0 0xA185 +#define mmSPI_VS_OUT_ID_1 0xA186 +#define mmSPI_VS_OUT_ID_2 0xA187 +#define mmSPI_VS_OUT_ID_3 0xA188 +#define mmSPI_VS_OUT_ID_4 0xA189 +#define mmSPI_VS_OUT_ID_5 0xA18A +#define mmSPI_VS_OUT_ID_6 0xA18B +#define mmSPI_VS_OUT_ID_7 0xA18C +#define mmSPI_VS_OUT_ID_8 0xA18D +#define mmSPI_VS_OUT_ID_9 0xA18E +#define mmSPI_PS_INPUT_CNTL_0 0xA191 +#define mmSPI_PS_INPUT_CNTL_1 0xA192 +#define mmSPI_PS_INPUT_CNTL_2 0xA193 +#define mmSPI_PS_INPUT_CNTL_3 0xA194 +#define mmSPI_PS_INPUT_CNTL_4 0xA195 +#define mmSPI_PS_INPUT_CNTL_5 0xA196 +#define mmSPI_PS_INPUT_CNTL_6 0xA197 +#define mmSPI_PS_INPUT_CNTL_7 0xA198 +#define mmSPI_PS_INPUT_CNTL_8 0xA199 +#define mmSPI_PS_INPUT_CNTL_9 0xA19A +#define mmSPI_PS_INPUT_CNTL_10 0xA19B +#define mmSPI_PS_INPUT_CNTL_11 0xA19C +#define mmSPI_PS_INPUT_CNTL_12 0xA19D +#define mmSPI_PS_INPUT_CNTL_13 0xA19E +#define mmSPI_PS_INPUT_CNTL_14 0xA19F +#define mmSPI_PS_INPUT_CNTL_15 0xA1A0 +#define mmSPI_PS_INPUT_CNTL_16 0xA1A1 +#define mmSPI_PS_INPUT_CNTL_17 0xA1A2 +#define mmSPI_PS_INPUT_CNTL_18 0xA1A3 +#define mmSPI_PS_INPUT_CNTL_19 0xA1A4 +#define mmSPI_PS_INPUT_CNTL_20 0xA1A5 +#define mmSPI_PS_INPUT_CNTL_21 0xA1A6 +#define mmSPI_PS_INPUT_CNTL_22 0xA1A7 +#define mmSPI_PS_INPUT_CNTL_23 0xA1A8 +#define mmSPI_PS_INPUT_CNTL_24 0xA1A9 +#define mmSPI_PS_INPUT_CNTL_25 0xA1AA +#define mmSPI_PS_INPUT_CNTL_26 0xA1AB +#define mmSPI_PS_INPUT_CNTL_27 0xA1AC +#define mmSPI_PS_INPUT_CNTL_28 0xA1AD +#define mmSPI_PS_INPUT_CNTL_29 0xA1AE +#define mmSPI_PS_INPUT_CNTL_30 0xA1AF +#define mmSPI_PS_INPUT_CNTL_31 0xA1B0 +#define mmSPI_VS_OUT_CONFIG 0xA1B1 +#define mmSPI_THREAD_GROUPING 0xA1B2 +#define mmSPI_PS_IN_CONTROL_0 0xA1B3 +#define mmSPI_PS_IN_CONTROL_1 0xA1B4 +#define mmSPI_INTERP_CONTROL_0 0xA1B5 +#define mmSPI_INPUT_Z 0xA1B6 + +#define mmDB_DEPTH_BASE 0xA003 +#define mmDB_DEPTH_INFO 0xA004 +#define mmDB_HTILE_DATA_BASE 0xA005 +#define mmDB_DEPTH_SIZE 0xA000 +#define mmDB_DEPTH_VIEW 0xA001 +#define mmDB_RENDER_CONTROL 0xA343 +#define mmDB_RENDER_OVERRIDE 0xA344 +#define mmDB_SHADER_CONTROL 0xA203 +#define mmDB_STENCIL_CLEAR 0xA00A +#define mmDB_DEPTH_CLEAR 0xA00B +#define mmDB_HTILE_SURFACE 0xA349 +#define mmDB_PRELOAD_CONTROL 0xA34C +#define mmDB_PREFETCH_LIMIT 0xA34D +#define mmDB_STENCILREFMASK 0xA10C +#define mmDB_STENCILREFMASK_BF 0xA10D +#define mmDB_SRESULTS_COMPARE_STATE0 0xA34A +#define mmDB_SRESULTS_COMPARE_STATE1 0xA34B + +#define mmDB_ALPHA_TO_MASK 0xA351 + +#define mmCB_CLRCMP_CONTROL 0xA30C +#define mmCB_CLRCMP_SRC 0xA30D +#define mmCB_CLRCMP_DST 0xA30E +#define mmCB_CLRCMP_MSK 0xA30F +#define mmCB_COLOR0_BASE 0xA010 +#define mmCB_COLOR1_BASE 0xA011 +#define mmCB_COLOR2_BASE 0xA012 +#define mmCB_COLOR3_BASE 0xA013 +#define mmCB_COLOR4_BASE 0xA014 +#define mmCB_COLOR5_BASE 0xA015 +#define mmCB_COLOR6_BASE 0xA016 +#define mmCB_COLOR7_BASE 0xA017 +#define mmCB_COLOR0_SIZE 0xA018 +#define mmCB_COLOR1_SIZE 0xA019 +#define mmCB_COLOR2_SIZE 0xA01A +#define mmCB_COLOR3_SIZE 0xA01B +#define mmCB_COLOR4_SIZE 0xA01C +#define mmCB_COLOR5_SIZE 0xA01D +#define mmCB_COLOR6_SIZE 0xA01E +#define mmCB_COLOR7_SIZE 0xA01F +#define mmCB_COLOR0_VIEW 0xA020 +#define mmCB_COLOR1_VIEW 0xA021 +#define mmCB_COLOR2_VIEW 0xA022 +#define mmCB_COLOR3_VIEW 0xA023 +#define mmCB_COLOR4_VIEW 0xA024 +#define mmCB_COLOR5_VIEW 0xA025 +#define mmCB_COLOR6_VIEW 0xA026 +#define mmCB_COLOR7_VIEW 0xA027 +#define mmCB_COLOR0_INFO 0xA028 +#define mmCB_COLOR1_INFO 0xA029 +#define mmCB_COLOR2_INFO 0xA02A +#define mmCB_COLOR3_INFO 0xA02B +#define mmCB_COLOR4_INFO 0xA02C +#define mmCB_COLOR5_INFO 0xA02D +#define mmCB_COLOR6_INFO 0xA02E +#define mmCB_COLOR7_INFO 0xA02F +#define mmCB_COLOR0_TILE 0xA030 +#define mmCB_COLOR1_TILE 0xA031 +#define mmCB_COLOR2_TILE 0xA032 +#define mmCB_COLOR3_TILE 0xA033 +#define mmCB_COLOR4_TILE 0xA034 +#define mmCB_COLOR5_TILE 0xA035 +#define mmCB_COLOR6_TILE 0xA036 +#define mmCB_COLOR7_TILE 0xA037 +#define mmCB_COLOR0_FRAG 0xA038 +#define mmCB_COLOR1_FRAG 0xA039 +#define mmCB_COLOR2_FRAG 0xA03A +#define mmCB_COLOR3_FRAG 0xA03B +#define mmCB_COLOR4_FRAG 0xA03C +#define mmCB_COLOR5_FRAG 0xA03D +#define mmCB_COLOR6_FRAG 0xA03E +#define mmCB_COLOR7_FRAG 0xA03F +#define mmCB_COLOR0_MASK 0xA040 +#define mmCB_COLOR1_MASK 0xA041 +#define mmCB_COLOR2_MASK 0xA042 +#define mmCB_COLOR3_MASK 0xA043 +#define mmCB_COLOR4_MASK 0xA044 +#define mmCB_COLOR5_MASK 0xA045 +#define mmCB_COLOR6_MASK 0xA046 +#define mmCB_COLOR7_MASK 0xA047 +#define mmCB_SHADER_MASK 0xA08F +#define mmCB_SHADER_CONTROL 0xA1E8 + +#define mmSQ_VTX_BASE_VTX_LOC 0xF3FC // baseVertex +#define mmSQ_VTX_START_INST_LOC 0xF3FD // baseInstance + +#define mmSQ_CONFIG 0x2300 // used by GX2SetShaderModeEx +#define mmSQ_GPR_RESOURCE_MGMT_1 0x2301 +#define mmSQ_THREAD_RESOURCE_MGMT 0x2303 +#define mmSQ_STACK_RESOURCE_MGMT_1 0x2304 +#define mmSQ_STACK_RESOURCE_MGMT_2 0x2305 +#define mmSQ_ESGS_RING_BASE 0x2310 +#define mmSQ_ESGS_RING_SIZE 0x2311 +#define mmSQ_GSVS_RING_BASE 0x2312 +#define mmSQ_GSVS_RING_SIZE 0x2313 +#define mmSQ_ESTMP_RING_BASE 0x2314 +#define mmSQ_ESTMP_RING_SIZE 0x2315 +#define mmSQ_GSTMP_RING_BASE 0x2316 +#define mmSQ_GSTMP_RING_SIZE 0x2317 + + +#define mmSQ_TEX_RESOURCE_WORD0 0xE000 +#define mmSQ_ALU_CONSTANT0_0 0xC000 + +#define mmSQ_VTX_ATTRIBUTE_BLOCK_START (mmSQ_TEX_RESOURCE_WORD0+0x8C0) +#define mmSQ_VTX_ATTRIBUTE_BLOCK_END (mmSQ_VTX_ATTRIBUTE_BLOCK_START + 7*16) + +#define mmSQ_VTX_UNIFORM_BLOCK_START (mmSQ_TEX_RESOURCE_WORD0+0x7E0) // 7 dwords for each uniform block +#define mmSQ_VTX_UNIFORM_BLOCK_END (mmSQ_VTX_UNIFORM_BLOCK_START + 7*16 - 1) + +#define mmSQ_PS_UNIFORM_BLOCK_START (mmSQ_TEX_RESOURCE_WORD0+0x250) // 7 dwords for each uniform block +#define mmSQ_PS_UNIFORM_BLOCK_END (mmSQ_PS_UNIFORM_BLOCK_START + 7*16 - 1) + +#define mmSQ_GS_UNIFORM_BLOCK_START (mmSQ_TEX_RESOURCE_WORD0+0xCB0) // 7 dwords for each uniform block +#define mmSQ_GS_UNIFORM_BLOCK_END (mmSQ_GS_UNIFORM_BLOCK_START + 7*16 - 1) + +#define mmSQ_CS_DISPATCH_PARAMS (mmSQ_TEX_RESOURCE_WORD0+0x865) \ No newline at end of file diff --git a/src/Cafe/HW/Latte/LatteAddrLib/AddrLibFastDecode.h b/src/Cafe/HW/Latte/LatteAddrLib/AddrLibFastDecode.h new file mode 100644 index 00000000..b0e2cfb3 --- /dev/null +++ b/src/Cafe/HW/Latte/LatteAddrLib/AddrLibFastDecode.h @@ -0,0 +1,384 @@ +#pragma once +#include "Cafe/HW/Latte/LatteAddrLib/LatteAddrLib.h" + +template<typename texelBaseType, int texelBaseTypeCount, bool isEncodeDirection, bool isCompressed> +void optimizedDecodeLoop_tm04_numSamples1_8x8(LatteTextureLoaderCtx* textureLoader, uint8* outputData, sint32 texelCountX, sint32 texelCountY) +{ + uint16* tableBase = textureLoader->computeAddrInfo.microTilePixelIndexTable + ((textureLoader->computeAddrInfo.slice & 7) << 6); + for (sint32 yt = 0; yt < texelCountY; yt += 8) + { + for (sint32 xt = 0; xt < texelCountX; xt += 8) + { + sint32 baseOffset = LatteAddrLib::ComputeSurfaceAddrFromCoordMacroTiledCached_tm04_sample1(xt, yt, &textureLoader->computeAddrInfo); // this is only 10-20% of execution time + for (sint32 ry = 0; ry < 8; ry++) + { + sint32 pixelOffset = ((yt + ry)*textureLoader->decodedTexelCountX + (xt)) * (sizeof(texelBaseType)*texelBaseTypeCount); + texelBaseType* blockOutput = (texelBaseType*)(outputData + pixelOffset); + + uint16* pixelOffsets = tableBase + (ry << 3); + + for (sint32 rx = 0; rx < 8; rx++) + { + uint32 pixelIndex = *pixelOffsets; + pixelOffsets++; + uint32 pixelOffset = pixelIndex * sizeof(texelBaseType)*texelBaseTypeCount; + uint32 elemOffset = pixelOffset; + if ((sizeof(texelBaseType)*texelBaseTypeCount * 8 * 8) > 256) + { + // separate group bytes, for small formats this step is not necessary since elemOffset is never over 0xFF (maximum is 8*8*bpp) + elemOffset = (elemOffset & 0xFF) | ((elemOffset&~0xFF) << 3); + } + + sint32 offset = baseOffset + elemOffset; + + uint8* blockData = textureLoader->inputData + offset; + // copy as-is + if (texelBaseTypeCount == 1) + { + if (isEncodeDirection) + *(texelBaseType*)blockData = *blockOutput; + else + *blockOutput = *(texelBaseType*)blockData; + blockOutput++; + } + else if (texelBaseTypeCount == 2) + { + if (isEncodeDirection) + { + ((texelBaseType*)blockData)[0] = blockOutput[0]; + ((texelBaseType*)blockData)[1] = blockOutput[1]; + } + else + { + blockOutput[0] = ((texelBaseType*)blockData)[0]; + blockOutput[1] = ((texelBaseType*)blockData)[1]; + } + blockOutput += 2; + } + else + assert_dbg(); + } + } + } + } +} + +template<typename texelBaseType, int texelBaseTypeCount, bool isEncodeDirection, bool isCompressed> +void optimizedDecodeLoop_tm04_numSamples1_8x8_optimizedRowCopy(LatteTextureLoaderCtx* textureLoader, uint8* outputData, sint32 texelCountX, sint32 texelCountY) +{ + uint16* tableBase = textureLoader->computeAddrInfo.microTilePixelIndexTable + ((textureLoader->computeAddrInfo.slice & 7) << 6); + for (sint32 yt = 0; yt < texelCountY; yt += 8) + { + for (sint32 xt = 0; xt < texelCountX; xt += 8) + { + sint32 baseOffset = ComputeSurfaceAddrFromCoordMacroTiledCached_tm04_sample1(xt, yt, &textureLoader->computeAddrInfo); // this is only 10-20% of execution time + for (sint32 ry = 0; ry < 8; ry++) + { + sint32 pixelOffset = ((yt + ry)*textureLoader->decodedTexelCountX + (xt)) * (sizeof(texelBaseType)*texelBaseTypeCount); + texelBaseType* blockOutput = (texelBaseType*)(outputData + pixelOffset); + + uint16* pixelOffsets = tableBase + (ry << 3); + + uint32 pixelIndex = *pixelOffsets; + pixelOffsets++; + uint32 elemOffset = pixelIndex * sizeof(texelBaseType)*texelBaseTypeCount; + if ((sizeof(texelBaseType)*texelBaseTypeCount * 8 * 8) > 256) + { + // separate group bytes, for small formats this step is not necessary since elemOffset is never over 0xFF (maximum is 8*8*bpp) + elemOffset = (elemOffset & 0xFF) | ((elemOffset&~0xFF) << 3); + } + + sint32 offset = baseOffset + elemOffset; + + texelBaseType* blockData = (texelBaseType*)(textureLoader->inputData + offset); + // x-to-offset translation table (for bpp = 64) + // 0 -> 0 + // 1 -> 1 + // 2 -> 4 + // 3 -> 5 + // 4 -> 8 + // 5 -> 9 + // 6 -> 12 + // 7 -> 13 + + // x-to-offset translation table (for bpp = 32) + // 0 -> 0 + // 1 -> 1 + // 2 -> 2 + // 3 -> 3 + // 4 -> 8 + // 5 -> 9 + // 6 -> 10 + // 7 -> 11 + + + if ((sizeof(texelBaseType)*texelBaseTypeCount) == 8) + { + // bpp = 64 + if (texelBaseTypeCount == 1) + { + if (isEncodeDirection) + { + blockData[0] = blockOutput[0]; + blockData[1] = blockOutput[1]; + blockData[4] = blockOutput[2]; + blockData[5] = blockOutput[3]; + blockData[8] = blockOutput[4]; + blockData[9] = blockOutput[5]; + blockData[12] = blockOutput[6]; + blockData[13] = blockOutput[7]; + } + else + { + blockOutput[0] = blockData[0]; + blockOutput[1] = blockData[1]; + blockOutput[2] = blockData[4]; + blockOutput[3] = blockData[5]; + blockOutput[4] = blockData[8]; + blockOutput[5] = blockData[9]; + blockOutput[6] = blockData[12]; + blockOutput[7] = blockData[13]; + } + blockOutput += 8; + } + else + assert_dbg(); + } + else if ((sizeof(texelBaseType)*texelBaseTypeCount) == 4) + { + // bpp = 32 + if (texelBaseTypeCount == 1) + { + uint64* blockOutput64 = (uint64*)blockOutput; + uint64* blockData64 = (uint64*)blockData; + if (isEncodeDirection) + { + blockData64[0] = blockOutput64[0]; + blockData64[1] = blockOutput64[1]; + blockData64[4] = blockOutput64[2]; + blockData64[5] = blockOutput64[3]; + } + else + { + blockOutput64[0] = blockData64[0]; + blockOutput64[1] = blockData64[1]; + blockOutput64[2] = blockData64[4]; + blockOutput64[3] = blockData64[5]; + } + blockOutput += 8; + } + else + cemu_assert_unimplemented(); + } + else if ((sizeof(texelBaseType)*texelBaseTypeCount) == 1) + { + // bpp = 8 + if (texelBaseTypeCount == 1) + { + uint64* blockOutput64 = (uint64*)blockOutput; + uint64* blockData64 = (uint64*)blockData; + if (isEncodeDirection) + blockData64[0] = blockOutput64[0]; + else + blockOutput64[0] = blockData64[0]; + blockOutput += 8; + } + else + cemu_assert_unimplemented(); + } + else + cemu_assert_unimplemented(); + } + } + } +} + +template<typename texelBaseType, int texelBaseTypeCount, bool isEncodeDirection, bool isCompressed> +void optimizedDecodeLoops(LatteTextureLoaderCtx* textureLoader, uint8* outputData) +{ + sint32 texelCountX; + sint32 texelCountY; + if (isCompressed) + { + texelCountX = (textureLoader->width + 3) / 4; + texelCountY = (textureLoader->height + 3) / 4; + } + else + { + texelCountX = textureLoader->width; + texelCountY = textureLoader->height; + } + + if (textureLoader->tileMode == Latte::E_HWTILEMODE::TM_2D_TILED_THIN1 && textureLoader->computeAddrInfo.numSamples == 1) + { + sint32 texelCountOrigX = texelCountX; + sint32 texelCountOrigY = texelCountY; + texelCountX &= ~7; + texelCountY &= ~7; + // full tiles (assuming tileMode=4 and numSamples=1) + // only recalculate tile related offset at the beginning of each block + // calculate offsets in loop + + // unsure if this variant is faster: + if (textureLoader->computeAddrInfo.microTileType == 0 && (sizeof(texelBaseType)*texelBaseTypeCount) == 8) + { + optimizedDecodeLoop_tm04_numSamples1_8x8_optimizedRowCopy<texelBaseType, texelBaseTypeCount, isEncodeDirection, isCompressed>(textureLoader, outputData, texelCountX, texelCountY); + } + else if (textureLoader->computeAddrInfo.microTileType == 0 && (sizeof(texelBaseType)*texelBaseTypeCount) == 4) + { + optimizedDecodeLoop_tm04_numSamples1_8x8_optimizedRowCopy<texelBaseType, texelBaseTypeCount, isEncodeDirection, isCompressed>(textureLoader, outputData, texelCountX, texelCountY); + } + else if (textureLoader->computeAddrInfo.microTileType == 0 && (sizeof(texelBaseType)*texelBaseTypeCount) == 1) + { + optimizedDecodeLoop_tm04_numSamples1_8x8_optimizedRowCopy<texelBaseType, texelBaseTypeCount, isEncodeDirection, isCompressed>(textureLoader, outputData, texelCountX, texelCountY); + } + else + { + optimizedDecodeLoop_tm04_numSamples1_8x8<texelBaseType, texelBaseTypeCount, isEncodeDirection, isCompressed>(textureLoader, outputData, texelCountX, texelCountY); + } + // the above code only handles full 8x8 pixel blocks, for uneven sizes we need to process the remaining pixels here + // right border + for (sint32 yt = 0; yt < texelCountY; yt++) + { + sint32 pixelOffset = (yt*textureLoader->decodedTexelCountX + texelCountX) * (sizeof(texelBaseType)*texelBaseTypeCount); + texelBaseType* blockOutput = (texelBaseType*)(outputData + pixelOffset); + for (sint32 xt = texelCountX; xt < texelCountOrigX; xt++) + { + sint32 offset = ComputeSurfaceAddrFromCoordMacroTiledCached_tm04_sample1(xt, yt, &textureLoader->computeAddrInfo); + uint8* blockData = textureLoader->inputData + offset; + // copy as-is + if (texelBaseTypeCount == 1) + { + if (isEncodeDirection) + *(texelBaseType*)blockData = *blockOutput; + else + *blockOutput = *(texelBaseType*)blockData; + blockOutput++; + } + else if (texelBaseTypeCount == 2) + { + if (isEncodeDirection) + { + ((texelBaseType*)blockData)[0] = blockOutput[0]; + ((texelBaseType*)blockData)[1] = blockOutput[1]; + } + else + { + blockOutput[0] = ((texelBaseType*)blockData)[0]; + blockOutput[1] = ((texelBaseType*)blockData)[1]; + } + blockOutput += 2; + } + } + } + // bottom border (with bottom right corner) + for (sint32 yt = texelCountY; yt < texelCountOrigY; yt++) + { + sint32 pixelOffset = (yt*textureLoader->decodedTexelCountX) * (sizeof(texelBaseType)*texelBaseTypeCount); + texelBaseType* blockOutput = (texelBaseType*)(outputData + pixelOffset); + for (sint32 xt = 0; xt < texelCountOrigX; xt++) + { + sint32 offset = ComputeSurfaceAddrFromCoordMacroTiledCached_tm04_sample1(xt, yt, &textureLoader->computeAddrInfo); + uint8* blockData = textureLoader->inputData + offset; + // copy as-is + if (texelBaseTypeCount == 1) + { + if (isEncodeDirection) + *(texelBaseType*)blockData = *blockOutput; + else + *blockOutput = *(texelBaseType*)blockData; + blockOutput++; + } + else if (texelBaseTypeCount == 2) + { + if (isEncodeDirection) + { + ((texelBaseType*)blockData)[0] = blockOutput[0]; + ((texelBaseType*)blockData)[1] = blockOutput[1]; + } + else + { + blockOutput[0] = ((texelBaseType*)blockData)[0]; + blockOutput[1] = ((texelBaseType*)blockData)[1]; + } + blockOutput += 2; + } + } + } + } + else if (textureLoader->tileMode == Latte::E_HWTILEMODE::TM_LINEAR_ALIGNED) + { + // optimized handler for linear textures + uint32 sliceOffset = textureLoader->sliceIndex * textureLoader->height * textureLoader->pitch; + for (sint32 y = 0; y < texelCountY; y++) + { + sint32 pixelOffset = (y*textureLoader->decodedTexelCountX) * (sizeof(texelBaseType)*texelBaseTypeCount); + texelBaseType* blockOutput = (texelBaseType*)(outputData + pixelOffset); + texelBaseType* blockData = (texelBaseType*)(textureLoader->inputData + (textureLoader->pitch * y + sliceOffset) * (sizeof(texelBaseType)*texelBaseTypeCount)); + for (sint32 x = 0; x < texelCountX; x++) + { + // copy as-is + if (texelBaseTypeCount == 1) + { + if(isEncodeDirection) + *(texelBaseType*)blockData = *blockOutput; + else + *blockOutput = *(texelBaseType*)blockData; + blockData++; + blockOutput++; + } + else if (texelBaseTypeCount == 2) + { + if (isEncodeDirection) + { + ((texelBaseType*)blockData)[0] = blockOutput[0]; + ((texelBaseType*)blockData)[1] = blockOutput[1]; + } + else + { + blockOutput[0] = ((texelBaseType*)blockData)[0]; + blockOutput[1] = ((texelBaseType*)blockData)[1]; + } + blockData += 2; + blockOutput += 2; + } + } + } + } + else + { + // generic handler + for (sint32 y = 0; y < textureLoader->height; y += textureLoader->stepY) + { + sint32 pixelOffset = ((y / textureLoader->stepY)*textureLoader->decodedTexelCountX) * (sizeof(texelBaseType)*texelBaseTypeCount); + texelBaseType* blockOutput = (texelBaseType*)(outputData + pixelOffset); + for (sint32 x = 0; x < textureLoader->width; x += textureLoader->stepX) + { + uint8* blockData = LatteTextureLoader_GetInput(textureLoader, x, y); + // copy as-is + if (texelBaseTypeCount == 1) + { + if (isEncodeDirection) + *(texelBaseType*)blockData = *blockOutput; + else + *blockOutput = *(texelBaseType*)blockData; + blockOutput++; + } + else if (texelBaseTypeCount == 2) + { + if (isEncodeDirection) + { + ((texelBaseType*)blockData)[0] = blockOutput[0]; + ((texelBaseType*)blockData)[1] = blockOutput[1]; + } + else + { + blockOutput[0] = ((texelBaseType*)blockData)[0]; + blockOutput[1] = ((texelBaseType*)blockData)[1]; + } + blockOutput += 2; + } + } + } + } +} \ No newline at end of file diff --git a/src/Cafe/HW/Latte/LatteAddrLib/LatteAddrLib.cpp b/src/Cafe/HW/Latte/LatteAddrLib/LatteAddrLib.cpp new file mode 100644 index 00000000..60a6e2c4 --- /dev/null +++ b/src/Cafe/HW/Latte/LatteAddrLib/LatteAddrLib.cpp @@ -0,0 +1,1238 @@ +#include "Cafe/HW/Latte/Core/Latte.h" +#include "Cafe/HW/Latte/LatteAddrLib/LatteAddrLib.h" +#include "Cafe/OS/libs/gx2/GX2_Surface.h" +#include <bit> + +/* + Info: + + - Extra samples for AA are stored in their own micro-tiles + + Macro-Tiling: + + - Contains one micro-tile for every combination of bank/channel select + - Since there are 4 bank and 2 pipe bits this means 4*2 = 8 micro tiles (or 8*4 for thick?). But the arrangement varies per tilemode (aspect ratio) + Allowed layouts: 1x8, 2x4, 4x2 + + - Address format: .... aaaaabbc aaaaaaaa A = offset, b = bank, c = channel + - Channel/Bank bits are determined by: + channel0 = x[3] ^ y[3] + bank0 = x[3] ^ y[5] + bank1 = x[4] ^ y[4] + +*/ + +using namespace Latte; + +namespace LatteAddrLib +{ + + enum class COMPUTE_SURFACE_RESULT + { + RESULT_OK = 0, + UNKNOWN_FORMAT = 3, + BAD_SIZE_FIELD = 6, + }; + + const uint32 m_configFlags = (1 << 29); + + uint32 GetSliceComputingFlags() + { + return (m_configFlags >> 26) & 3; + } + + uint32 GetFillSizeFieldsFlags() + { + return (m_configFlags >> 25) & 1; + } + + bool GetFlagUseTileIndex() + { + return ((m_configFlags >> 24) & 1) != 0; + } + + bool GetFlagNoCubeMipSlicesPad() + { + return ((m_configFlags >> 28) & 1) != 0; + } + + bool GetFlagNo1DTiledMSAA() + { + return ((m_configFlags >> 29) & 1) != 0; + } + + bool IsPow2(uint32 dim) + { + return (dim & (dim - 1)) == 0; + } + + uint32 PowTwoAlign(uint32 x, uint32 align) + { + return (x + align - 1) & ~(align - 1); + } + + uint32 NextPow2(uint32 dim) + { + return std::bit_ceil<uint32>(dim); + } + + uint32 GetBitsPerPixel(E_HWSURFFMT format, uint32* pElemMode, uint32* pExpandX, uint32* pExpandY) + { + uint32 bpp; + uint32 elemMode = 3; + switch (format) + { + case E_HWSURFFMT::INVALID_FORMAT: + bpp = 0; + *pExpandX = 1; + *pExpandY = 1; + break; + case E_HWSURFFMT::HWFMT_8: + case E_HWSURFFMT::HWFMT_4_4: + case E_HWSURFFMT::HWFMT_3_3_2: + bpp = 8; + *pExpandX = 1; + *pExpandY = 1; + break; + case E_HWSURFFMT::HWFMT_16: + case E_HWSURFFMT::HWFMT_16_FLOAT: + case E_HWSURFFMT::HWFMT_8_8: + case E_HWSURFFMT::HWFMT_5_6_5: + case E_HWSURFFMT::HWFMT_6_5_5: + case E_HWSURFFMT::HWFMT_1_5_5_5: + case E_HWSURFFMT::HWFMT_4_4_4_4: + bpp = 16; + *pExpandX = 1; + *pExpandY = 1; + break; + case E_HWSURFFMT::HWFMT_5_5_5_1: + bpp = 16; + *pExpandX = 1; + *pExpandY = 1; + break; + case E_HWSURFFMT::HWFMT_32: + case E_HWSURFFMT::HWFMT_32_FLOAT: + case E_HWSURFFMT::HWFMT_16_16: + case E_HWSURFFMT::HWFMT_16_16_FLOAT: + case E_HWSURFFMT::HWFMT_24_8: + case E_HWSURFFMT::HWFMT_24_8_FLOAT: + case E_HWSURFFMT::HWFMT_10_11_11: + case E_HWSURFFMT::HWFMT_11_11_10: + case E_HWSURFFMT::HWFMT_2_10_10_10: + case E_HWSURFFMT::HWFMT_8_8_8_8: + case E_HWSURFFMT::HWFMT_8_24: + case E_HWSURFFMT::HWFMT_8_24_FLOAT: + case E_HWSURFFMT::HWFMT_10_11_11_FLOAT: + case E_HWSURFFMT::HWFMT_11_11_10_FLOAT: + case E_HWSURFFMT::HWFMT_10_10_10_2: + bpp = 32; + *pExpandX = 1; + *pExpandY = 1; + break; + case E_HWSURFFMT::HWFMT_32_32: + case E_HWSURFFMT::HWFMT_32_32_FLOAT: + case E_HWSURFFMT::HWFMT_16_16_16_16: + case E_HWSURFFMT::HWFMT_16_16_16_16_FLOAT: + case E_HWSURFFMT::HWFMT_X24_8_32_FLOAT: + bpp = 64; + *pExpandX = 1; + *pExpandY = 1; + break; + case E_HWSURFFMT::HWFMT_32_32_32_32: + case E_HWSURFFMT::HWFMT_32_32_32_32_FLOAT: + bpp = 128; + *pExpandX = 1; + *pExpandY = 1; + break; + case E_HWSURFFMT::HWFMT_BC1: + elemMode = 9; + bpp = 64; + *pExpandX = 4; + *pExpandY = 4; + break; + case E_HWSURFFMT::HWFMT_BC2: + elemMode = 10; + bpp = 128; + *pExpandX = 4; + *pExpandY = 4; + break; + case E_HWSURFFMT::HWFMT_BC3: + elemMode = 11; + bpp = 128; + *pExpandX = 4; + *pExpandY = 4; + break; + case E_HWSURFFMT::HWFMT_BC4: + elemMode = 12; + bpp = 64; + *pExpandX = 4; + *pExpandY = 4; + break; + case E_HWSURFFMT::HWFMT_BC5: + case E_HWSURFFMT::U_HWFMT_BC6: + case E_HWSURFFMT::U_HWFMT_BC7: + elemMode = 13; + bpp = 128; + *pExpandX = 4; + *pExpandY = 4; + break; + default: + cemu_assert_suspicious(); + bpp = 0; + *pExpandX = 1; + *pExpandY = 1; + break; + } + *pElemMode = elemMode; + return bpp; + } + + void AdjustSurfaceInfo(uint32 elemMode, uint32 expandX, uint32 expandY, uint32* pBpp, uint32* pWidth, uint32* pHeight) + { + bool isBCFormat = false; + if (pBpp) + { + uint32 bpp = *pBpp; + uint32 packedBits; + switch (elemMode) + { + case 4: + packedBits = bpp / expandX / expandY; + break; + case 5: + case 6: + packedBits = expandY * expandX * bpp; + break; + case 7: + case 8: + packedBits = *pBpp; + break; + case 9: + case 12: + packedBits = 64; + isBCFormat = true; + break; + case 10: + case 11: + case 13: + packedBits = 128; + isBCFormat = true; + break; + case 0: + case 1: + case 2: + case 3: + packedBits = *pBpp; + break; + default: + packedBits = *pBpp; + break; + } + *pBpp = packedBits; + } + if (pWidth) + { + if (pHeight) + { + uint32 width = *pWidth; + uint32 height = *pHeight; + if (expandX > 1 || expandY > 1) + { + uint32 widthAligned; + uint32 heightAligned; + if (elemMode == 4) + { + widthAligned = expandX * width; + heightAligned = expandY * height; + } + else if (isBCFormat) + { + widthAligned = width / expandX; + heightAligned = height / expandY; + } + else + { + widthAligned = (width + expandX - 1) / expandX; + heightAligned = (height + expandY - 1) / expandY; + } + *pWidth = std::max<uint32>(widthAligned, 1); + *pHeight = std::max<uint32>(heightAligned, 1); + } + } + } + } + + void ComputeMipLevelDimensions(uint32* pWidth, uint32* pHeight, uint32* pNumSlices, AddrSurfaceFlags flags, Latte::E_HWSURFFMT format, uint32 mipLevel) + { + bool isBCn = (uint32)format >= (uint32)Latte::E_HWSURFFMT::HWFMT_BC1 && (uint32)format <= (uint32)Latte::E_HWSURFFMT::U_HWFMT_BC7; + if (isBCn && (mipLevel == 0 || flags.inputIsBase)) + { + *pWidth = PowTwoAlign(*pWidth, 4); + *pHeight = PowTwoAlign(*pHeight, 4); + } + if (isBCn) + { + if (mipLevel != 0) + { + uint32 width = *pWidth; + uint32 height = *pHeight; + uint32 slices = *pNumSlices; + if (flags.inputIsBase) + { + if (!flags.dimCube) + slices >>= mipLevel; + width = std::max<uint32>(width >> mipLevel, 1); + height = std::max<uint32>(height >> mipLevel, 1); + slices = std::max<uint32>(slices, 1); + } + *pWidth = NextPow2(width); + *pHeight = NextPow2(height); + *pNumSlices = slices; + } + } + else if (mipLevel && flags.inputIsBase) + { + uint32 width = *pWidth; + uint32 height = *pHeight; + uint32 slices = *pNumSlices; + width >>= mipLevel; + height >>= mipLevel; + if (!flags.dimCube) // dim 3D + slices >>= mipLevel; + width = std::max<uint32>(1, width); + height = std::max<uint32>(1, height); + slices = std::max<uint32>(1, slices); + if (format != E_HWSURFFMT::U_HWFMT_32_32_32 && format != E_HWSURFFMT::U_HWFMT_32_32_32_FLOAT) + { + width = NextPow2(width); + height = NextPow2(height); + slices = NextPow2(slices); + } + *pWidth = width; + *pHeight = height; + *pNumSlices = slices; + } + } + + E_HWTILEMODE ConvertTileModeToNonBankSwappedMode(E_HWTILEMODE tileMode) + { + switch (tileMode) + { + case E_HWTILEMODE::TM_2B_TILED_THIN1: + return E_HWTILEMODE::TM_2D_TILED_THIN1; + case E_HWTILEMODE::TM_2B_TILED_THIN2: + return E_HWTILEMODE::TM_2D_TILED_THIN2; + case E_HWTILEMODE::TM_2B_TILED_THIN4: + return E_HWTILEMODE::TM_2D_TILED_THIN4; + case E_HWTILEMODE::TM_2B_TILED_THICK: + return E_HWTILEMODE::TM_2D_TILED_THICK; + case E_HWTILEMODE::TM_3B_TILED_THIN1: + return E_HWTILEMODE::TM_3D_TILED_THIN1; + case E_HWTILEMODE::TM_3B_TILED_THICK: + return E_HWTILEMODE::TM_3D_TILED_THICK; + default: + break; + } + return tileMode; + } + + uint32 _CalculateSurfaceTileSlices(E_HWTILEMODE tileMode, uint32 bpp, uint32 numSamples) + { + uint32 bytePerSample = ((bpp << 6) + 7) >> 3; + uint32 tileSlices = 1; + if (TM_GetThickness(tileMode) > 1) + numSamples = 4; + if (bytePerSample) + { + uint32 samplePerTile = m_splitSize / bytePerSample; + if (samplePerTile) + { + tileSlices = numSamples / samplePerTile; + if (!(numSamples / samplePerTile)) + tileSlices = 1; + } + } + return tileSlices; + } + + uint32 ComputeSurfaceRotationFromTileMode(E_HWTILEMODE tileMode) + { + switch (tileMode) + { + case E_HWTILEMODE::TM_2D_TILED_THIN1: + case E_HWTILEMODE::TM_2D_TILED_THIN2: + case E_HWTILEMODE::TM_2D_TILED_THIN4: + case E_HWTILEMODE::TM_2D_TILED_THICK: + case E_HWTILEMODE::TM_2B_TILED_THIN1: + case E_HWTILEMODE::TM_2B_TILED_THIN2: + case E_HWTILEMODE::TM_2B_TILED_THIN4: + case E_HWTILEMODE::TM_2B_TILED_THICK: + return m_pipes * ((m_banks >> 1) - 1); + case E_HWTILEMODE::TM_3D_TILED_THIN1: + case E_HWTILEMODE::TM_3D_TILED_THICK: + case E_HWTILEMODE::TM_3B_TILED_THIN1: + case E_HWTILEMODE::TM_3B_TILED_THICK: + if (m_pipes >= 4) + return (m_pipes >> 1) - 1; + return 1; + default: + break; + } + return 0; + } + + E_HWTILEMODE _ComputeSurfaceMipLevelTileMode(E_HWTILEMODE baseTileMode, uint32 bpp, uint32 level, uint32 width, uint32 height, uint32 numSlices, uint32 numSamples, bool isDepth, bool noRecursive) + { + E_HWTILEMODE result; + E_HWTILEMODE expTileMode = baseTileMode; + uint32 tileSlices = _CalculateSurfaceTileSlices(baseTileMode, bpp, numSamples); + switch (baseTileMode) + { + case E_HWTILEMODE::TM_1D_TILED_THIN1: + if (numSamples > 1 && GetFlagNo1DTiledMSAA()) + expTileMode = E_HWTILEMODE::TM_2D_TILED_THIN1; + break; + case E_HWTILEMODE::TM_1D_TILED_THICK: + if (numSamples > 1 || isDepth) + expTileMode = E_HWTILEMODE::TM_1D_TILED_THIN1; + if (numSamples == 2 || numSamples == 4) + expTileMode = E_HWTILEMODE::TM_2D_TILED_THICK; + break; + case E_HWTILEMODE::TM_2D_TILED_THIN2: + if (2 * m_pipeInterleaveBytes > m_splitSize) + expTileMode = E_HWTILEMODE::TM_2D_TILED_THIN1; + break; + case E_HWTILEMODE::TM_2D_TILED_THIN4: + if (4 * m_pipeInterleaveBytes > m_splitSize) + expTileMode = E_HWTILEMODE::TM_2D_TILED_THIN2; + break; + case E_HWTILEMODE::TM_2D_TILED_THICK: + if (numSamples > 1 || tileSlices > 1 || isDepth) + expTileMode = E_HWTILEMODE::TM_2D_TILED_THIN1; + break; + case E_HWTILEMODE::TM_2B_TILED_THIN2: + if (2 * m_pipeInterleaveBytes > m_splitSize) + expTileMode = E_HWTILEMODE::TM_2B_TILED_THIN1; + break; + case E_HWTILEMODE::TM_2B_TILED_THIN4: + if (4 * m_pipeInterleaveBytes > m_splitSize) + expTileMode = E_HWTILEMODE::TM_2B_TILED_THIN2; + break; + case E_HWTILEMODE::TM_2B_TILED_THICK: + if (numSamples > 1 || tileSlices > 1 || isDepth) + expTileMode = E_HWTILEMODE::TM_2B_TILED_THIN1; + break; + case E_HWTILEMODE::TM_3D_TILED_THICK: + if (numSamples > 1 || tileSlices > 1 || isDepth) + expTileMode = E_HWTILEMODE::TM_3D_TILED_THIN1; + break; + case E_HWTILEMODE::TM_3B_TILED_THICK: + if (numSamples > 1 || tileSlices > 1 || isDepth) + expTileMode = E_HWTILEMODE::TM_3B_TILED_THIN1; + break; + default: + expTileMode = baseTileMode; + break; + } + uint32 rotation = ComputeSurfaceRotationFromTileMode(expTileMode); + if (!(rotation % m_pipes)) + { + switch (expTileMode) + { + case E_HWTILEMODE::TM_3D_TILED_THIN1: + expTileMode = E_HWTILEMODE::TM_2D_TILED_THIN1; + break; + case E_HWTILEMODE::TM_3D_TILED_THICK: + expTileMode = E_HWTILEMODE::TM_2D_TILED_THICK; + break; + case E_HWTILEMODE::TM_3B_TILED_THIN1: + expTileMode = E_HWTILEMODE::TM_2B_TILED_THIN1; + break; + case E_HWTILEMODE::TM_3B_TILED_THICK: + expTileMode = E_HWTILEMODE::TM_2B_TILED_THICK; + break; + default: + break; + } + } + if (noRecursive) + { + result = expTileMode; + } + else + { + if (bpp == 96 || bpp == 48 || bpp == 24) + bpp /= 3u; + uint32 widthAligned = NextPow2(width); + uint32 heightAligned = NextPow2(height); + uint32 numSlicesAligned = NextPow2(numSlices); + if (level) + { + expTileMode = ConvertTileModeToNonBankSwappedMode(expTileMode); + uint32 thickness = TM_GetThickness(expTileMode); + uint32 microTileBytes = (numSamples * bpp * (thickness << 6) + 7) >> 3; + uint32 widthAlignFactor; + if (microTileBytes >= m_pipeInterleaveBytes) + widthAlignFactor = 1; + else + widthAlignFactor = m_pipeInterleaveBytes / microTileBytes; + uint32 macroTileWidth = 8 * m_banks; + uint32 macroTileHeight = 8 * m_pipes; + switch (expTileMode) + { + case E_HWTILEMODE::TM_2D_TILED_THIN1: + case E_HWTILEMODE::TM_3D_TILED_THIN1: + if (widthAligned < widthAlignFactor * macroTileWidth || heightAligned < macroTileHeight) + expTileMode = E_HWTILEMODE::TM_1D_TILED_THIN1; + break; + case E_HWTILEMODE::TM_2D_TILED_THIN2: + macroTileWidth >>= 1; + macroTileHeight *= 2; + if (widthAligned < widthAlignFactor * macroTileWidth || heightAligned < macroTileHeight) + expTileMode = E_HWTILEMODE::TM_1D_TILED_THIN1; + break; + case E_HWTILEMODE::TM_2D_TILED_THIN4: + macroTileWidth >>= 2; + macroTileHeight *= 4; + if (widthAligned < widthAlignFactor * macroTileWidth || heightAligned < macroTileHeight) + expTileMode = E_HWTILEMODE::TM_1D_TILED_THIN1; + break; + case E_HWTILEMODE::TM_2D_TILED_THICK: + case E_HWTILEMODE::TM_3D_TILED_THICK: + if (widthAligned < widthAlignFactor * macroTileWidth || heightAligned < macroTileHeight) + expTileMode = E_HWTILEMODE::TM_1D_TILED_THICK; + break; + default: + break; + } + if (expTileMode == E_HWTILEMODE::TM_1D_TILED_THICK) + { + if (numSlicesAligned < 4) + expTileMode = E_HWTILEMODE::TM_1D_TILED_THIN1; + } + else if (expTileMode == E_HWTILEMODE::TM_2D_TILED_THICK) + { + if (numSlicesAligned < 4) + expTileMode = E_HWTILEMODE::TM_2D_TILED_THIN1; + } + else if (expTileMode == E_HWTILEMODE::TM_3D_TILED_THICK && numSlicesAligned < 4) + { + expTileMode = E_HWTILEMODE::TM_3D_TILED_THIN1; + } + result = _ComputeSurfaceMipLevelTileMode(expTileMode, bpp, level, widthAligned, heightAligned, numSlicesAligned, numSamples, isDepth, true); + } + else + { + result = expTileMode; + } + } + return result; + } + + uint32 _ComputeMacroTileAspectRatio(E_HWTILEMODE tileMode) + { + switch (tileMode) + { + case E_HWTILEMODE::TM_2B_TILED_THIN1: + case E_HWTILEMODE::TM_3D_TILED_THIN1: + case E_HWTILEMODE::TM_3B_TILED_THIN1: + return 1; + case E_HWTILEMODE::TM_2D_TILED_THIN2: + case E_HWTILEMODE::TM_2B_TILED_THIN2: + return 2; + case E_HWTILEMODE::TM_2D_TILED_THIN4: + case E_HWTILEMODE::TM_2B_TILED_THIN4: + return 4; + default: + break; + } + return 1; + } + + void _AdjustPitchAlignment(AddrSurfaceFlags flags, uint32& pitchAlign) + { + if (flags.display) + pitchAlign = PowTwoAlign(pitchAlign, 32); + } + + void _ComputeSurfaceAlignmentsMacroTiled(E_HWTILEMODE tileMode, uint32 bpp, AddrSurfaceFlags flags, uint32 numSamples, uint32* pBaseAlign, uint32* pPitchAlign, uint32* pHeightAlign, uint32* pMacroWidth, uint32* pMacroHeight) + { + uint32 aspectRatio = _ComputeMacroTileAspectRatio(tileMode); + uint32 thickness = TM_GetThickness(tileMode); + if (bpp == 96 || bpp == 48 || bpp == 24) + bpp /= 3; + if (bpp == 3) + bpp = 1; + uint32 macroTileWidth = (8 * m_banks) / aspectRatio; + uint32 macroTileHeight = aspectRatio * 8 * m_pipes; + uint32 pitchAlign = std::max(macroTileWidth, macroTileWidth * (m_pipeInterleaveBytes / bpp / (8 * thickness) / numSamples)); + _AdjustPitchAlignment(flags, *pPitchAlign); + uint32 heightAlign = macroTileHeight; + uint32 macroTileBytes = numSamples * ((bpp * macroTileHeight * macroTileWidth + 7) >> 3); + if (m_chipFamily == 1 && numSamples == 1) + macroTileBytes *= 2; + uint32 baseAlign; + if (thickness == 1) + baseAlign = std::max(macroTileBytes, (numSamples * heightAlign * bpp * pitchAlign + 7) >> 3); + else + baseAlign = std::max(m_pipeInterleaveBytes, (4 * heightAlign * bpp * pitchAlign + 7) >> 3); + uint32 microTileBytes = (thickness * numSamples * (bpp << 6) + 7) >> 3; + uint32 splits; + if (microTileBytes < m_splitSize) + splits = 1; + else + splits = microTileBytes / m_splitSize; + baseAlign /= splits; + *pBaseAlign = baseAlign; + *pPitchAlign = pitchAlign; + *pHeightAlign = heightAlign; + *pMacroWidth = macroTileWidth; + *pMacroHeight = macroTileHeight; + } + + uint32 ComputeSurfaceBankSwappedWidth(E_HWTILEMODE tileMode, uint32 bpp, uint32 numSamples, uint32 pitch) + { + uint32 bankSwapWidth = 0; + uint32 slicesPerTile = 1; + uint32 bytesPerSample = 8 * bpp; + uint32 samplesPerTile = m_splitSize / bytesPerSample; + if (m_splitSize / bytesPerSample) + { + slicesPerTile = numSamples / samplesPerTile; + if (!(numSamples / samplesPerTile)) + slicesPerTile = 1; + } + if (TM_IsThickAndMacroTiled(tileMode) == 1) + numSamples = 4; + if (TM_IsBankSwapped(tileMode)) + { + uint32 bytesPerTileSlice = numSamples * bytesPerSample / slicesPerTile; + uint32 aspectRatioFactor = _ComputeMacroTileAspectRatio(tileMode); + uint32 swapTiles = std::max<uint32>((m_swapSize >> 1) / bpp, 1); + uint32 swapWidth = swapTiles * 8 * m_banks; + uint32 heightBytes = numSamples * aspectRatioFactor * m_pipes * bpp / slicesPerTile; + uint32 swapMax = m_pipes * m_banks * m_rowSize / heightBytes; + uint32 swapMin = m_pipeInterleaveBytes * 8 * m_banks / bytesPerTileSlice; + uint32 swapVal; + if (swapMax >= swapWidth) + swapVal = std::max(swapWidth, swapMin); + else + swapVal = swapMax; + for (bankSwapWidth = swapVal; bankSwapWidth >= 2 * pitch; bankSwapWidth >>= 1); + } + return bankSwapWidth; + } + + void PadDimensions(E_HWTILEMODE tileMode, uint32 padDims, int isCube, int cubeAsArray, uint32* pPitch, uint32 pitchAlign, uint32* pHeight, uint32 heightAlign, uint32* pSlices, uint32 sliceAlign) + { + uint32 thickness = TM_GetThickness(tileMode); + if (!padDims) + padDims = 3; + if (IsPow2(pitchAlign)) + { + *pPitch = PowTwoAlign(*pPitch, pitchAlign); + } + else + { + *pPitch = pitchAlign + *pPitch - 1; + *pPitch /= pitchAlign; + *pPitch *= pitchAlign; + } + if (padDims > 1) + *pHeight = PowTwoAlign(*pHeight, heightAlign); + if (padDims > 2 || thickness > 1) + { + if (isCube && (!GetFlagNoCubeMipSlicesPad() || cubeAsArray)) + *pSlices = NextPow2(*pSlices); + if (thickness > 1) + *pSlices = PowTwoAlign(*pSlices, sliceAlign); + } + } + + void _ComputeSurfaceAlignmentsMicroTiled(E_HWTILEMODE tileMode, uint32 bpp, AddrSurfaceFlags flags, uint32 numSamples, uint32& baseAlignOut, uint32& pitchAlignOut, uint32& heightAlignOut) + { + if (bpp == 96 || bpp == 48 || bpp == 24) + bpp /= 3u; + uint32 thickness = TM_GetThickness(tileMode); + baseAlignOut = m_pipeInterleaveBytes; + pitchAlignOut = std::max(8u, m_pipeInterleaveBytes / bpp / numSamples / thickness); + heightAlignOut = 8; + _AdjustPitchAlignment(flags, pitchAlignOut); + } + + void _ComputeSurfaceAlignmentsLinear(E_HWTILEMODE tileMode, uint32 bpp, AddrSurfaceFlags flags, uint32* pBaseAlign, uint32* pPitchAlign, uint32* pHeightAlign) + { + cemu_assert_debug(tileMode == E_HWTILEMODE::TM_LINEAR_GENERAL || tileMode == E_HWTILEMODE::TM_LINEAR_ALIGNED); + if (tileMode == E_HWTILEMODE::TM_LINEAR_ALIGNED) + { + uint32 pixelsPerPipeInterleave = 8 * m_pipeInterleaveBytes / bpp; + *pBaseAlign = m_pipeInterleaveBytes; + *pPitchAlign = std::max<uint32>(64, pixelsPerPipeInterleave); + *pHeightAlign = 1; + } + else if (tileMode == E_HWTILEMODE::TM_LINEAR_GENERAL) + { + *pBaseAlign = 1; + *pPitchAlign = 1; + *pHeightAlign = 1; + } + _AdjustPitchAlignment(flags, *pPitchAlign); + } + + void _ComputeSurfaceInfoLinear(E_HWTILEMODE tileMode, uint32 bpp, uint32 numSamples, uint32 pitch, uint32 height, uint32 numSlices, uint32 mipLevel, uint32 padDims, AddrSurfaceFlags flags, AddrSurfaceInfo_OUT* pOut) + { + uint32 heightAlign; + uint32 pitchAlign; + uint32 baseAlign; + uint32 expPitch = pitch; + uint32 expHeight = height; + uint32 expNumSlices = numSlices; + _ComputeSurfaceAlignmentsLinear(tileMode, bpp, flags, &baseAlign, &pitchAlign, &heightAlign); + if (flags.linearWA && mipLevel == 0) + { + expPitch /= 3u; + expPitch = NextPow2(expPitch); + } + if (mipLevel) + { + expPitch = NextPow2(expPitch); + expHeight = NextPow2(expHeight); + if (flags.dimCube) + { + expNumSlices = numSlices; + if (numSlices <= 1) + padDims = 2; + else + padDims = 0; + } + else + { + expNumSlices = NextPow2(numSlices); + } + } + uint32 microTileThickness = TM_GetThickness(tileMode); + PadDimensions(tileMode, padDims, flags.dimCube, flags.cubeAsArray, &expPitch, pitchAlign, &expHeight, heightAlign, &expNumSlices, microTileThickness); + if (flags.linearWA && mipLevel == 0) + expPitch *= 3; + uint32 slices = expNumSlices * numSamples / microTileThickness; + pOut->pitch = expPitch; + pOut->height = expHeight; + pOut->depth = expNumSlices; + pOut->surfSize = (((uint64)expHeight * expPitch * slices * bpp * numSamples + 7) / 8); + pOut->baseAlign = baseAlign; + pOut->pitchAlign = pitchAlign; + pOut->heightAlign = heightAlign; + pOut->depthAlign = microTileThickness; + } + + void _ComputeSurfaceInfoMicroTiled(E_HWTILEMODE tileMode, uint32 bpp, uint32 numSamples, uint32 pitch, uint32 height, uint32 numSlices, uint32 mipLevel, uint32 padDims, AddrSurfaceFlags flags, AddrSurfaceInfo_OUT* pOut) + { + E_HWTILEMODE expTileMode = tileMode; + uint32 expPitch = pitch; + uint32 expHeight = height; + uint32 expNumSlices = numSlices; + uint32 microTileThickness = TM_GetThickness(tileMode); + if (mipLevel) + { + expPitch = NextPow2(pitch); + expHeight = NextPow2(height); + if (flags.dimCube) + { + expNumSlices = numSlices; + if (numSlices <= 1) + padDims = 2; + else + padDims = 0; + } + else + { + expNumSlices = NextPow2(numSlices); + } + if (expTileMode == E_HWTILEMODE::TM_1D_TILED_THICK && expNumSlices < 4) + { + expTileMode = E_HWTILEMODE::TM_1D_TILED_THIN1; + microTileThickness = 1; + } + } + uint32 heightAlign; + uint32 pitchAlign; + uint32 baseAlign; + _ComputeSurfaceAlignmentsMicroTiled(expTileMode, bpp, flags, numSamples, /* outputs: */ baseAlign, pitchAlign, heightAlign); + PadDimensions(expTileMode, padDims, flags.dimCube, flags.cubeAsArray, &expPitch, pitchAlign, &expHeight, heightAlign, &expNumSlices, microTileThickness); + pOut->pitch = expPitch; + pOut->height = expHeight; + pOut->depth = expNumSlices; + pOut->surfSize = (((uint64)expHeight * expPitch * expNumSlices * bpp * numSamples + 7) / 8); + pOut->hwTileMode = expTileMode; + pOut->baseAlign = baseAlign; + pOut->pitchAlign = pitchAlign; + pOut->heightAlign = heightAlign; + pOut->depthAlign = microTileThickness; + } + + void _ComputeSurfaceInfoMacroTiled(E_HWTILEMODE tileMode, E_HWTILEMODE baseTileMode, uint32 bpp, uint32 numSamples, uint32 pitch, uint32 height, uint32 numSlices, uint32 mipLevel, uint32 padDims, AddrSurfaceFlags flags, AddrSurfaceInfo_OUT* pOut) + { + uint32 macroWidth, macroHeight; + uint32 baseAlign, heightAlign, pitchAlign; + + uint32 expPitch = pitch; + uint32 expHeight = height; + uint32 expNumSlices = numSlices; + E_HWTILEMODE expTileMode = tileMode; + uint32 microTileThickness = TM_GetThickness(tileMode); + if (mipLevel) + { + expPitch = NextPow2(pitch); + expHeight = NextPow2(height); + expNumSlices = NextPow2(numSlices); + if (flags.dimCube) + { + // cubemap + expNumSlices = numSlices; + padDims = numSlices <= 1 ? 2 : 0; + } + if (expTileMode == E_HWTILEMODE::TM_2D_TILED_THICK && expNumSlices < 4) + { + expTileMode = E_HWTILEMODE::TM_2D_TILED_THIN1; + microTileThickness = 1; + } + } + uint32 pitchAlignFactor = std::max<uint32>((m_pipeInterleaveBytes >> 3) / bpp, 1); + if (tileMode != baseTileMode && mipLevel != 0 && TM_IsThickAndMacroTiled(baseTileMode) && !TM_IsThickAndMacroTiled(tileMode)) + { + _ComputeSurfaceAlignmentsMacroTiled(baseTileMode, bpp, flags, numSamples, &baseAlign, &pitchAlign, &heightAlign, ¯oWidth, ¯oHeight); + if (expPitch < pitchAlign * pitchAlignFactor || expHeight < heightAlign) + { + _ComputeSurfaceInfoMicroTiled(E_HWTILEMODE::TM_1D_TILED_THIN1, bpp, numSamples, pitch, height, numSlices, mipLevel, padDims, flags, pOut); + return; + } + } + _ComputeSurfaceAlignmentsMacroTiled(tileMode, bpp, flags, numSamples, &baseAlign, &pitchAlign, &heightAlign, ¯oWidth, ¯oHeight); + uint32 bankSwappedWidth = ComputeSurfaceBankSwappedWidth(tileMode, bpp, numSamples, pitch); + if (bankSwappedWidth > pitchAlign) + pitchAlign = bankSwappedWidth; + PadDimensions(tileMode, padDims, flags.dimCube, flags.cubeAsArray, &expPitch, pitchAlign, &expHeight, heightAlign, &expNumSlices, microTileThickness); + pOut->pitch = expPitch; + pOut->height = expHeight; + pOut->depth = expNumSlices; + pOut->surfSize = (((uint64)expHeight * expPitch * expNumSlices * bpp * numSamples + 7) / 8); + pOut->hwTileMode = expTileMode; + pOut->baseAlign = baseAlign; + pOut->pitchAlign = pitchAlign; + pOut->heightAlign = heightAlign; + pOut->depthAlign = microTileThickness; + } + + COMPUTE_SURFACE_RESULT ComputeSurfaceInfoEx(const AddrSurfaceInfo_IN* pIn, AddrSurfaceInfo_OUT* pOut) + { + Latte::E_HWTILEMODE tileMode = pIn->tileMode; + Latte::E_HWTILEMODE baseTileMode = tileMode; + uint32 bpp = pIn->bpp; + uint32 numSamples = std::max<uint32>(pIn->numSamples, 1); + uint32 pitch = pIn->width; + uint32 height = pIn->height; + uint32 numSlices = pIn->numSlices; + uint32 mipLevel = pIn->mipLevel; + AddrSurfaceFlags flags = pIn->flags; + uint32 padDims = 0; + if (flags.dimCube && mipLevel == 0) + padDims = 2; + if (flags.fmask) + tileMode = ConvertTileModeToNonBankSwappedMode(tileMode); + else + tileMode = _ComputeSurfaceMipLevelTileMode(tileMode, bpp, mipLevel, pitch, height, numSlices, numSamples, flags.depth, false); + switch (tileMode) + { + case E_HWTILEMODE::TM_LINEAR_GENERAL: + case E_HWTILEMODE::TM_LINEAR_ALIGNED: + _ComputeSurfaceInfoLinear(tileMode, bpp, numSamples, pitch, height, numSlices, mipLevel, padDims, flags, pOut); + pOut->hwTileMode = tileMode; + break; + case E_HWTILEMODE::TM_1D_TILED_THIN1: + case E_HWTILEMODE::TM_1D_TILED_THICK: + _ComputeSurfaceInfoMicroTiled(tileMode, bpp, numSamples, pitch, height, numSlices, mipLevel, padDims, flags, pOut); + break; + case E_HWTILEMODE::TM_2D_TILED_THIN1: + case E_HWTILEMODE::TM_2D_TILED_THIN2: + case E_HWTILEMODE::TM_2D_TILED_THIN4: + case E_HWTILEMODE::TM_2D_TILED_THICK: + case E_HWTILEMODE::TM_2B_TILED_THIN1: + case E_HWTILEMODE::TM_2B_TILED_THIN2: + case E_HWTILEMODE::TM_2B_TILED_THIN4: + case E_HWTILEMODE::TM_2B_TILED_THICK: + case E_HWTILEMODE::TM_3D_TILED_THIN1: + case E_HWTILEMODE::TM_3D_TILED_THICK: + case E_HWTILEMODE::TM_3B_TILED_THIN1: + case E_HWTILEMODE::TM_3B_TILED_THICK: + _ComputeSurfaceInfoMacroTiled(tileMode, baseTileMode, bpp, numSamples, pitch, height, numSlices, mipLevel, padDims, flags, pOut); + break; + default: + return COMPUTE_SURFACE_RESULT::UNKNOWN_FORMAT; + } + return COMPUTE_SURFACE_RESULT::RESULT_OK; + } + + void RestoreSurfaceInfo(uint32 elemMode, uint32 expandX, uint32 expandY, uint32*pBpp, uint32 *pWidth, uint32*pHeight) + { + if (pBpp) + { + uint32 bpp = *pBpp; + uint32 originalBits; + switch (elemMode) + { + case 4: + originalBits = expandY * expandX * bpp; + break; + case 5: + case 6: + originalBits = bpp / expandX / expandY; + break; + case 7: + case 8: + originalBits = *pBpp; + break; + case 9: + case 12: + originalBits = 64; + break; + case 10: + case 11: + case 13: + originalBits = 128; + break; + case 0: + case 1: + case 2: + case 3: + originalBits = *pBpp; + break; + default: + originalBits = *pBpp; + break; + } + *pBpp = originalBits; + } + if (pWidth && pHeight) + { + uint32 width = *pWidth; + uint32 height = *pHeight; + if (expandX > 1 || expandY > 1) + { + if (elemMode == 4) + { + width /= expandX; + height /= expandY; + } + else + { + width *= expandX; + height *= expandY; + } + } + *pWidth = std::max<uint32>(width, 1); + *pHeight = std::max<uint32>(height, 1); + } + } + + COMPUTE_SURFACE_RESULT ComputeSurfaceInfo(AddrSurfaceInfo_IN* pIn, AddrSurfaceInfo_OUT* pOut) + { + if (GetFillSizeFieldsFlags() == 1 && (pIn->size != sizeof(AddrSurfaceInfo_IN) || pOut->size != sizeof(AddrSurfaceInfo_OUT))) + return COMPUTE_SURFACE_RESULT::BAD_SIZE_FIELD; + cemu_assert_debug(pIn->bpp <= 128); + ComputeMipLevelDimensions(&pIn->width, &pIn->height, &pIn->numSlices, pIn->flags, pIn->format, pIn->mipLevel); + uint32 width = pIn->width; + uint32 height = pIn->height; + uint32 bpp = pIn->bpp; + uint32 elemMode; + uint32 expandX = 1; + uint32 expandY = 1; + cemu_assert_debug(pIn->tileIndex == 0 && pIn->pTileInfo == nullptr); + pOut->pixelBits = pIn->bpp; + if (pIn->format != E_HWSURFFMT::INVALID_FORMAT) + { + bpp = GetBitsPerPixel(pIn->format, &elemMode, &expandX, &expandY); + if (pIn->tileMode == E_HWTILEMODE::TM_LINEAR_ALIGNED && elemMode == 4 && expandX == 3) + pIn->flags.linearWA = true; + AdjustSurfaceInfo(elemMode, expandX, expandY, &bpp, &width, &height); + pIn->width = width; + pIn->height = height; + pIn->bpp = bpp; + } + else if (pIn->bpp != 0) + { + pIn->width = std::max<uint32>(pIn->width, 1); + pIn->height = std::max<uint32>(pIn->height, 1); + } + else + return COMPUTE_SURFACE_RESULT::UNKNOWN_FORMAT; + COMPUTE_SURFACE_RESULT r = ComputeSurfaceInfoEx(pIn, pOut); + if (r != COMPUTE_SURFACE_RESULT::RESULT_OK) + return r; + pOut->bpp = pIn->bpp; + pOut->pixelPitch = pOut->pitch; + pOut->pixelHeight = pOut->height; + if (pIn->format != E_HWSURFFMT::INVALID_FORMAT && (!pIn->flags.linearWA || pIn->mipLevel == 0)) + { + RestoreSurfaceInfo(elemMode, expandX, expandY, &bpp, &pOut->pixelPitch, &pOut->pixelHeight); + } + uint32 sliceFlags = GetSliceComputingFlags(); + if (sliceFlags) + { + if (sliceFlags == 1) + pOut->sliceSize = (pOut->height * pOut->pitch * pOut->bpp * pIn->numSamples + 7) / 8; + } + else if (pIn->flags.dim3D) + { + pOut->sliceSize = (uint32)(pOut->surfSize); + } + else + { + if(pOut->surfSize == 0 && pOut->depth == 0) + pOut->sliceSize = 0; // edge case for (1D)_ARRAY textures with res 0/0/0 + else + pOut->sliceSize = (uint32)(pOut->surfSize / pOut->depth); + if (pIn->slice == pIn->numSlices - 1 && pIn->numSlices > 1) + pOut->sliceSize += pOut->sliceSize * (pOut->depth - pIn->numSlices); + } + pOut->pitchTileMax = (pOut->pitch >> 3) - 1; + pOut->heightTileMax = (pOut->height >> 3) - 1; + pOut->sliceTileMax = ((pOut->height * pOut->pitch >> 6) - 1); + return COMPUTE_SURFACE_RESULT::RESULT_OK; + } + + void GX2CalculateSurfaceInfo(Latte::E_GX2SURFFMT surfaceFormat, uint32 surfaceWidth, uint32 surfaceHeight, uint32 surfaceDepth, E_DIM surfaceDim, E_GX2TILEMODE surfaceTileMode, uint32 surfaceAA, uint32 level, AddrSurfaceInfo_OUT* pSurfOut, bool optimizeForDepthBuffer, bool optimizeForScanBuffer) + { + AddrSurfaceInfo_IN surfInfoIn = { 0 }; + Latte::E_HWSURFFMT hwFormat = Latte::GetHWFormat(surfaceFormat); + memset(pSurfOut, 0, sizeof(AddrSurfaceInfo_OUT)); + pSurfOut->size = sizeof(AddrSurfaceInfo_OUT); + if (surfaceTileMode == E_GX2TILEMODE::TM_LINEAR_SPECIAL) + { + uint32 numSamples = 1 << surfaceAA; + uint32 blockSize = IsCompressedFormat(hwFormat) ? 4 : 1; + uint32 width = ((surfaceWidth >> level) + blockSize - 1) & ~(blockSize - 1); + pSurfOut->bpp = Latte::GetFormatBits(hwFormat); + pSurfOut->pitch = width / blockSize; + pSurfOut->pixelBits = pSurfOut->bpp; + pSurfOut->baseAlign = 1; + pSurfOut->pitchAlign = 1; + pSurfOut->heightAlign = 1; + pSurfOut->depthAlign = 1; + switch (surfaceDim) + { + case E_DIM::DIM_1D: + pSurfOut->height = 1; + pSurfOut->depth = 1; + break; + case E_DIM::DIM_2D: + pSurfOut->height = std::max<uint32>(surfaceHeight >> level, 1); + pSurfOut->depth = 1; + break; + case E_DIM::DIM_3D: + pSurfOut->height = std::max<uint32>(surfaceHeight >> level, 1); + pSurfOut->depth = std::max<uint32>(surfaceDepth >> level, 1); + break; + case E_DIM::DIM_CUBEMAP: + pSurfOut->height = std::max<uint32>(surfaceHeight >> level, 1); + pSurfOut->depth = std::max<uint32>(surfaceDepth, 6); + break; + case E_DIM::DIM_1D_ARRAY: + pSurfOut->height = 1; + pSurfOut->depth = surfaceDepth; + break; + case E_DIM::DIM_2D_ARRAY: + pSurfOut->height = std::max<uint32>(surfaceHeight >> level, 1); + pSurfOut->depth = surfaceDepth; + break; + default: + break; + } + pSurfOut->height = (~(blockSize - 1) & (pSurfOut->height + blockSize - 1)) / (uint64)blockSize; + pSurfOut->pixelPitch = ~(blockSize - 1) & ((surfaceWidth >> level) + blockSize - 1); + pSurfOut->pixelPitch = std::max<uint32>(pSurfOut->pixelPitch, blockSize); + pSurfOut->pixelHeight = ~(blockSize - 1) & ((surfaceHeight >> level) + blockSize - 1); + pSurfOut->pixelHeight = std::max<uint32>(pSurfOut->pixelHeight, blockSize); + pSurfOut->pitch = std::max<uint32>(pSurfOut->pitch, 1); + pSurfOut->height = std::max<uint32>(pSurfOut->height, 1); + pSurfOut->surfSize = pSurfOut->bpp * numSamples * pSurfOut->depth * pSurfOut->height * pSurfOut->pitch >> 3; + if (surfaceDim == E_DIM::DIM_3D) + pSurfOut->sliceSize = (uint32)pSurfOut->surfSize; + else + { + if(pSurfOut->surfSize == 0 && pSurfOut->depth == 0) + pSurfOut->sliceSize = 0; + else + pSurfOut->sliceSize = (uint32)(pSurfOut->surfSize / pSurfOut->depth); + } + pSurfOut->pitchTileMax = (pSurfOut->pitch >> 3) - 1; + pSurfOut->heightTileMax = (pSurfOut->height >> 3) - 1; + pSurfOut->sliceTileMax = (pSurfOut->height * pSurfOut->pitch >> 6) - 1; + } + else + { + memset(&surfInfoIn, 0, sizeof(AddrSurfaceInfo_IN)); + surfInfoIn.size = sizeof(AddrSurfaceInfo_IN); + if (!IsValidHWTileMode((E_HWTILEMODE)surfaceTileMode)) + { + // cemuLog_log(LogType::Force, "Unexpected TileMode {} in AddrLib", (uint32)surfaceTileMode); + surfaceTileMode = (E_GX2TILEMODE)((uint32)surfaceTileMode & 0xF); + } + surfInfoIn.tileMode = MakeHWTileMode(surfaceTileMode); + surfInfoIn.format = hwFormat; + surfInfoIn.bpp = Latte::GetFormatBits(hwFormat); + surfInfoIn.numSamples = 1 << surfaceAA; + surfInfoIn.numFrags = surfInfoIn.numSamples; + surfInfoIn.width = std::max<uint32>(surfaceWidth >> level, 1); + switch (surfaceDim) + { + case E_DIM::DIM_1D: + surfInfoIn.height = 1; + surfInfoIn.numSlices = 1; + break; + case E_DIM::DIM_2D: + surfInfoIn.height = std::max<uint32>(surfaceHeight >> level, 1); + surfInfoIn.numSlices = 1; + break; + case E_DIM::DIM_3D: + surfInfoIn.height = std::max<uint32>(surfaceHeight >> level, 1); + surfInfoIn.numSlices = std::max<uint32>(surfaceDepth >> level, 1); + surfInfoIn.flags.dim3D = true; + break; + case E_DIM::DIM_CUBEMAP: + surfInfoIn.height = std::max<uint32>(surfaceHeight >> level, 1); + surfInfoIn.numSlices = std::max<uint32>(surfaceDepth, 6); + surfInfoIn.flags.dimCube = true; + break; + case E_DIM::DIM_1D_ARRAY: + surfInfoIn.height = 1; + surfInfoIn.numSlices = surfaceDepth; + break; + case E_DIM::DIM_2D_ARRAY: + surfInfoIn.height = std::max<uint32>(surfaceHeight >> level, 1); + surfInfoIn.numSlices = surfaceDepth; + break; + case E_DIM::DIM_2D_MSAA: + surfInfoIn.height = std::max<uint32>(surfaceHeight >> level, 1); + surfInfoIn.numSlices = 1; + break; + case E_DIM::DIM_2D_ARRAY_MSAA: + surfInfoIn.height = std::max<uint32>(surfaceHeight >> level, 1); + surfInfoIn.numSlices = surfaceDepth; + break; + default: + break; + } + surfInfoIn.slice = 0; + surfInfoIn.mipLevel = level; + surfInfoIn.flags.inputIsBase = (level == 0); + surfInfoIn.flags.depth = optimizeForDepthBuffer; + surfInfoIn.flags.display = optimizeForScanBuffer; + ComputeSurfaceInfo(&surfInfoIn, pSurfOut); + } + } + + uint32 CalculateMipOffset(Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, E_DIM dim, E_HWTILEMODE tileMode, uint32 swizzle, uint32 surfaceAA, sint32 mipIndex) + { + cemu_assert_debug(IsValidHWTileMode(tileMode)); + AddrSurfaceInfo_OUT surfaceInfo; + uint32 currentMipOffset = 0; + E_HWTILEMODE lastTileMode = tileMode; + uint32 prevSize = 0; + for (sint32 level = 1; level <= mipIndex; level++) + { + GX2CalculateSurfaceInfo(format, width, height, depth, dim, MakeGX2TileMode(tileMode), surfaceAA, level, &surfaceInfo); + if (level) + { + uint32 pad = 0; + if (TM_IsMacroTiled(lastTileMode) && !TM_IsMacroTiled(surfaceInfo.hwTileMode)) + { + if (level > 1) + pad = swizzle & 0xFFFF; + } + pad += (surfaceInfo.baseAlign - (currentMipOffset % surfaceInfo.baseAlign)) % surfaceInfo.baseAlign; + currentMipOffset = currentMipOffset + pad + prevSize; + } + else + { + currentMipOffset = prevSize; + } + lastTileMode = surfaceInfo.hwTileMode; + prevSize = (uint32)surfaceInfo.surfSize; + } + return currentMipOffset; + } + + // Calculate aligned address and size of a given slice and mip level + // For thick-tiled surfaces this returns the area of the whole thick tile (4 slices per thick tile) and the relative slice index within the tile is returned in subSliceIndex + void CalculateMipAndSliceAddr(uint32 physAddr, uint32 physMipAddr, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, Latte::E_DIM dim, Latte::E_HWTILEMODE tileMode, uint32 swizzle, uint32 surfaceAA, sint32 mipIndex, sint32 sliceIndex, uint32* outputSliceOffset, uint32* outputSliceSize, sint32* subSliceIndex) + { + cemu_assert_debug((uint32)tileMode < 16); // only hardware tilemodes allowed + + AddrSurfaceInfo_OUT surfaceInfo; + uint32 currentMipOffset = 0; + Latte::E_HWTILEMODE lastTileMode = tileMode; + uint32 prevSize = 0; + for (sint32 level = 1; level <= mipIndex; level++) + { + GX2CalculateSurfaceInfo(format, width, height, depth, dim, MakeGX2TileMode(tileMode), surfaceAA, level, &surfaceInfo); + // extract swizzle from mip-pointer if macro tiled + if (level == 1 && TM_IsMacroTiled(surfaceInfo.hwTileMode)) + { + swizzle = physMipAddr & 0x700; + physMipAddr &= ~0x700; + } + cemu_assert_debug(IsValidHWTileMode(surfaceInfo.hwTileMode)); + if (level) + { + uint32 pad = 0; + if (TM_IsMacroTiled(lastTileMode) && !TM_IsMacroTiled(surfaceInfo.hwTileMode)) + { + if (level > 1) + pad = swizzle & 0xFFFF; + } + pad += (surfaceInfo.baseAlign - (currentMipOffset % surfaceInfo.baseAlign)) % surfaceInfo.baseAlign; + currentMipOffset = currentMipOffset + pad + prevSize; + } + else + { + currentMipOffset = prevSize; + } + lastTileMode = surfaceInfo.hwTileMode; + prevSize = (uint32)surfaceInfo.surfSize; + } + // calculate slice offset + if( mipIndex == 0 ) // make sure surfaceInfo is initialized + GX2CalculateSurfaceInfo(format, width, height, depth, dim, MakeGX2TileMode(tileMode), surfaceAA, 0, &surfaceInfo); + uint32 sliceOffset = 0; + uint32 sliceSize = 0; + + // surfaceInfo.sliceSize isn't always correct (especially when depth is misaligned with 4 for THICK tile modes?) so we calculate it manually + // this formula only works because both pitch and height are aligned to micro/macro blocks by GX2CalculateSurfaceInfo, normally we would have to use the tile dimensions to calculate the size + uint32 correctedSliceSize = surfaceInfo.pitch*surfaceInfo.height*surfaceInfo.bpp / 8; + + if (TM_IsThick(surfaceInfo.hwTileMode)) + { + // 4 slices are interleaved + sliceOffset = (sliceIndex&~3) * correctedSliceSize; + sliceSize = correctedSliceSize * 4; + *subSliceIndex = sliceIndex & 3; + } + else + { + sliceOffset = sliceIndex * correctedSliceSize; + sliceSize = correctedSliceSize; + *subSliceIndex = 0; + } + if (mipIndex) + { + sliceOffset += physMipAddr; + } + else + { + sliceOffset += physAddr; + } + *outputSliceOffset = currentMipOffset + sliceOffset; + *outputSliceSize = sliceSize; + } + +}; diff --git a/src/Cafe/HW/Latte/LatteAddrLib/LatteAddrLib.h b/src/Cafe/HW/Latte/LatteAddrLib/LatteAddrLib.h new file mode 100644 index 00000000..58d2a9dc --- /dev/null +++ b/src/Cafe/HW/Latte/LatteAddrLib/LatteAddrLib.h @@ -0,0 +1,181 @@ +#pragma once +#include "Cafe/HW/Latte/ISA/LatteReg.h" + +namespace LatteAddrLib +{ + static const uint32 m_banks = 4; + static const uint32 m_banksBitcount = 2; + static const uint32 m_pipes = 2; + static const uint32 m_pipesBitcount = 1; + static const uint32 m_pipeInterleaveBytes = 256; + static const uint32 m_pipeInterleaveBytesBitcount = 8; + static const uint32 m_rowSize = 2048; + static const uint32 m_swapSize = 256; + static const uint32 m_splitSize = 2048; + static const uint32 m_chipFamily = 2; + + union AddrSurfaceFlags + { + uint32 rawValue; + struct + { + bool color : 1; + bool depth : 1; + bool stencil : 1; + bool uknFlag4 : 1; + bool dimCube : 1; + bool dim3D : 1; + bool fmask : 1; + bool cubeAsArray : 1; + bool uknFlag8 : 1; + bool linearWA : 1; + bool uknFlag10 : 1; + bool uknFlag11 : 1; + bool inputIsBase : 1; + bool display : 1; + }; + }; + + struct AddrTileInfo + { + uint32 banks; + uint32 bankWidth; + uint32 bankHeight; + uint32 macroAspectRatio; + uint32 tileSplitBytes; + uint32 pipeConfig; + }; + + struct AddrSurfaceInfo_IN + { + uint32 size; + Latte::E_HWTILEMODE tileMode; + Latte::E_HWSURFFMT format; + uint32 bpp; + uint32 numSamples; + uint32 width; + uint32 height; + uint32 numSlices; + uint32 slice; + uint32 mipLevel; + AddrSurfaceFlags flags; + uint32 numFrags; + AddrTileInfo* pTileInfo; + sint32 tileIndex; + }; + + struct AddrSurfaceInfo_OUT + { + uint32 size; + uint32 pitch; + uint32 height; + uint32 depth; + uint64 surfSize; + Latte::E_HWTILEMODE hwTileMode; + uint32 baseAlign; + uint32 pitchAlign; + uint32 heightAlign; + uint32 depthAlign; + uint32 bpp; + uint32 pixelPitch; + uint32 pixelHeight; + uint32 pixelBits; + uint32 sliceSize; + uint32 pitchTileMax; + uint32 heightTileMax; + uint32 sliceTileMax; + AddrTileInfo* pTileInfo; + uint32 tileType; + sint32 tileIndex; + }; + + inline bool IsHWTileMode(Latte::E_GX2TILEMODE tileMode) + { + return (uint32)tileMode < 16; + } + + inline bool IsValidHWTileMode(Latte::E_HWTILEMODE tileMode) + { + return (uint32)tileMode < 16; + } + + inline bool TM_IsThick(Latte::E_HWTILEMODE tileMode) + { + return + tileMode == Latte::E_HWTILEMODE::TM_1D_TILED_THICK || + tileMode == Latte::E_HWTILEMODE::TM_2D_TILED_THICK || + tileMode == Latte::E_HWTILEMODE::TM_2B_TILED_THICK || + tileMode == Latte::E_HWTILEMODE::TM_3D_TILED_THICK || + tileMode == Latte::E_HWTILEMODE::TM_3B_TILED_THICK; + } + + inline uint32 TM_GetThickness(Latte::E_HWTILEMODE tileMode) + { + return TM_IsThick(tileMode) ? 4 : 1; + } + + inline bool TM_IsThickAndMacroTiled(Latte::E_HWTILEMODE tileMode) + { + return + tileMode == Latte::E_HWTILEMODE::TM_2D_TILED_THICK || + tileMode == Latte::E_HWTILEMODE::TM_2B_TILED_THICK || + tileMode == Latte::E_HWTILEMODE::TM_3D_TILED_THICK || + tileMode == Latte::E_HWTILEMODE::TM_3B_TILED_THICK; + } + + uint32 ComputeSurfaceRotationFromTileMode(Latte::E_HWTILEMODE tileMode); + uint32 ComputeSurfaceBankSwappedWidth(Latte::E_HWTILEMODE tileMode, uint32 bpp, uint32 numSamples, uint32 pitch); + + void GX2CalculateSurfaceInfo(Latte::E_GX2SURFFMT surfaceFormat, uint32 surfaceWidth, uint32 surfaceHeight, uint32 surfaceDepth, Latte::E_DIM surfaceDim, Latte::E_GX2TILEMODE surfaceTileMode, uint32 surfaceAA, uint32 level, AddrSurfaceInfo_OUT* pSurfOut, bool optimizeForDepthBuffer = false, bool optimizeForScanBuffer = false); + uint32 CalculateMipOffset(Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, Latte::E_DIM dim, Latte::E_HWTILEMODE tileMode, uint32 swizzle, uint32 surfaceAA, sint32 mipIndex); + void CalculateMipAndSliceAddr(uint32 physAddr, uint32 physMipAddr, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, Latte::E_DIM dim, Latte::E_HWTILEMODE tileMode, uint32 swizzle, uint32 surfaceAA, sint32 mipIndex, sint32 sliceIndex, uint32* outputSliceOffset, uint32* outputSliceSize, sint32* subSliceIndex); + + /* Pixel access */ + + uint32 ComputeSurfaceAddrFromCoordLinear(uint32 x, uint32 y, uint32 slice, uint32 sample, uint32 bpp, uint32 pitch, uint32 height, uint32 numSlices); + uint32 ComputeSurfaceAddrFromCoordMicroTiled(uint32 x, uint32 y, uint32 slice, uint32 bpp, uint32 pitch, uint32 height, Latte::E_HWTILEMODE tileMode, bool isDepth); + uint32 ComputeSurfaceAddrFromCoordMacroTiled(uint32 x, uint32 y, uint32 slice, uint32 sample, uint32 bpp, uint32 pitch, uint32 height, uint32 numSamples, Latte::E_HWTILEMODE tileMode, bool isDepth, uint32 pipeSwizzle, uint32 bankSwizzle); + + uint32 _ComputePixelIndexWithinMicroTile(uint32 x, uint32 y, uint32 z, uint32 bpp, Latte::E_HWTILEMODE tileMode, uint32 microTileType); + + // addr lib (optimized access) + struct CachedSurfaceAddrInfo + { + uint32 slice; + uint32 sample; + uint32 bpp; + uint32 pitch; + uint32 height; + uint32 depth; + uint32 numSamples; // for AA + Latte::E_HWTILEMODE tileMode; + int isDepth; + uint32 pipeSwizzle; + uint32 bankSwizzle; + uint32 pixelOffsetMul; + uint32 bytesPerPixel; + // calculated data + uint32 microTileThickness; + uint32 microTileBits; + uint32 microTileBytes; + uint32 microTileType; + uint32 rotation; // bank rotation + uint32 macroTilePitch; + uint32 macroTileHeight; + uint32 macroTilePitchBits; + uint32 macroTileHeightBits; + uint32 macroTilesPerRow; + uint32 macroTileBytes; + uint32 bankSwapWidth; + uint32 sliceBytes; + uint32 sliceIn; + // const + uint32 c0; + // micro tile pixel index table + uint16 microTilePixelIndexTable[8 * 8 * 8]; + }; + + void SetupCachedSurfaceAddrInfo(CachedSurfaceAddrInfo* info, uint32 slice, uint32 sample, uint32 bpp, uint32 pitch, uint32 height, uint32 depth, uint32 numSamples, Latte::E_HWTILEMODE tileMode, int isDepth, uint32 pipeSwizzle, uint32 bankSwizzle); + uint32 ComputeSurfaceAddrFromCoordMacroTiledCached(uint32 x, uint32 y, CachedSurfaceAddrInfo* info); + uint32 ComputeSurfaceAddrFromCoordMacroTiledCached_tm04_sample1(uint32 x, uint32 y, CachedSurfaceAddrInfo* info); +}; diff --git a/src/Cafe/HW/Latte/LatteAddrLib/LatteAddrLib_Coord.cpp b/src/Cafe/HW/Latte/LatteAddrLib/LatteAddrLib_Coord.cpp new file mode 100644 index 00000000..8dc69b57 --- /dev/null +++ b/src/Cafe/HW/Latte/LatteAddrLib/LatteAddrLib_Coord.cpp @@ -0,0 +1,405 @@ +#include "Cafe/HW/Latte/Core/Latte.h" +#include "Cafe/HW/Latte/LatteAddrLib/LatteAddrLib.h" +#include "Cafe/OS/libs/gx2/GX2_Surface.h" + +using namespace Latte; + +namespace LatteAddrLib +{ + +#if BOOST_OS_LINUX + unsigned char _BitScanReverse(uint32* _Index, uint32 _Mask) + { + if (!_Mask) + return 0; + *_Index = 31 - __builtin_clzl(_Mask); + return 1; + } +#endif + + static const uint32 bankSwapOrder[] = { 0, 1, 3, 2 }; + + uint32 _GetMicroTileType(bool isDepth) + { + return isDepth ? 1 : 0; + } + + uint32 _ComputePixelIndexWithinMicroTile(uint32 x, uint32 y, uint32 z, uint32 bpp, E_HWTILEMODE tileMode, uint32 microTileType) + { + cemu_assert_debug(microTileType == 0 || microTileType == 1); + uint32 pixelBit0, pixelBit1, pixelBit2, pixelBit3, pixelBit4, pixelBit5, pixelBit6, pixelBit7, pixelBit8; + pixelBit6 = 0; + pixelBit7 = 0; + pixelBit8 = 0; + uint32 thickness = LatteAddrLib::TM_GetThickness(tileMode); + if (microTileType) + { + pixelBit0 = x & 1; + pixelBit1 = y & 1; + pixelBit2 = (x & 2) >> 1; + pixelBit3 = (y & 2) >> 1; + pixelBit4 = (x & 4) >> 2; + pixelBit5 = (y & 4) >> 2; + } + else + { + switch (bpp) + { + case 8: + pixelBit0 = x & 1; + pixelBit1 = (x & 2) >> 1; + pixelBit2 = (x & 4) >> 2; + pixelBit3 = (y & 2) >> 1; + pixelBit4 = y & 1; + pixelBit5 = (y & 4) >> 2; + break; + case 0x10: + pixelBit0 = x & 1; + pixelBit1 = (x & 2) >> 1; + pixelBit2 = (x & 4) >> 2; + pixelBit3 = y & 1; + pixelBit4 = (y & 2) >> 1; + pixelBit5 = (y & 4) >> 2; + break; + case 0x20: + case 0x60: + pixelBit0 = x & 1; + pixelBit1 = (x & 2) >> 1; + pixelBit2 = y & 1; + pixelBit3 = (x & 4) >> 2; + pixelBit4 = (y & 2) >> 1; + pixelBit5 = (y & 4) >> 2; + break; + case 0x40: + pixelBit0 = x & 1; + pixelBit1 = y & 1; + pixelBit2 = (x & 2) >> 1; + pixelBit3 = (x & 4) >> 2; + pixelBit4 = (y & 2) >> 1; + pixelBit5 = (y & 4) >> 2; + break; + case 0x80: + pixelBit0 = y & 1; + pixelBit1 = x & 1; + pixelBit2 = (x & 2) >> 1; + pixelBit3 = (x & 4) >> 2; + pixelBit4 = (y & 2) >> 1; + pixelBit5 = (y & 4) >> 2; + break; + default: + pixelBit0 = x & 1; + pixelBit1 = (x & 2) >> 1; + pixelBit2 = y & 1; + pixelBit3 = (x & 4) >> 2; + pixelBit4 = (y & 2) >> 1; + pixelBit5 = (y & 4) >> 2; + break; + } + } + if (thickness > 1) + { + pixelBit6 = z & 1; + pixelBit7 = (z & 2) >> 1; + } + return (pixelBit8 << 8) | (pixelBit7 << 7) | (pixelBit6 << 6) | (pixelBit5 << 5) | (pixelBit4 << 4) | (pixelBit3 << 3) | (pixelBit2 << 2) | (pixelBit1 << 1) | pixelBit0; + } + + uint32 _ComputePipeFromCoordWoRotation(uint32 x, uint32 y) + { + // hardcoded to assume 2 pipes + uint32 pipe; + pipe = ((y >> 3) ^ (x >> 3)) & 1; + return pipe; + } + + uint32 _ComputeBankFromCoordWoRotation(uint32 x, uint32 y) + { + uint32 bank; + if (m_banks == 4) + { + uint32 bankNew = (y >> 4) & 3; + bankNew = ((bankNew >> 1) | (bankNew << 1)); // swap lowest two bits + bankNew ^= (x >> 3); + bankNew &= 3; + bank = bankNew; + } + else if (m_banks == 8) + { + cemu_assert_unimplemented(); + bank = 0; + } + else + { + bank = 0; + } + return bank; + } + + uint32 ComputeSurfaceAddrFromCoordLinear(uint32 x, uint32 y, uint32 slice, uint32 sample, uint32 bpp, uint32 pitch, uint32 height, uint32 numSlices) + { + uint32 pixelIndex = x + pitch * y + (slice + numSlices * sample) * height * pitch; + return (pixelIndex * bpp) / 8; + } + + uint32 ComputeSurfaceAddrFromCoordMicroTiled(uint32 x, uint32 y, uint32 slice, uint32 bpp, uint32 pitch, uint32 height, Latte::E_HWTILEMODE tileMode, bool isDepth) + { + uint32 microTileThickness = (tileMode == Latte::E_HWTILEMODE::TM_1D_TILED_THICK) ? 4 : 1; + uint32 microTilesPerRow = pitch >> 3; + uint32 microTileIndexX = x >> 3; + uint32 microTileIndexY = y >> 3; + uint32 microTileBytes = microTileThickness * (((bpp << 6) + 7) >> 3); // each tile is 8x8 or 8x8x4 + uint32 microTileOffset = microTileBytes * (uint64)((x >> 3) + (pitch >> 3) * (y >> 3)); + uint32 sliceBytes = (height * (uint64)pitch * microTileThickness * bpp + 7) / 8; + uint32 sliceOffset = sliceBytes * (slice / microTileThickness); + uint32 pixelIndex = _ComputePixelIndexWithinMicroTile(x, y, slice, bpp, tileMode, _GetMicroTileType(isDepth)); + uint32 pixelOffset = bpp * pixelIndex; + pixelOffset >>= 3; + return pixelOffset + microTileOffset + sliceOffset; + } + + uint32 ComputeSurfaceAddrFromCoordMacroTiled(uint32 x, uint32 y, uint32 slice, uint32 sample, uint32 bpp, uint32 pitch, uint32 height, uint32 numSamples, Latte::E_HWTILEMODE tileMode, bool isDepth, uint32 pipeSwizzle, uint32 bankSwizzle) + { + uint32 microTileThickness = LatteAddrLib::TM_GetThickness((E_HWTILEMODE)tileMode); + uint32 microTileBits = numSamples * bpp * (microTileThickness * (8 * 8)); + uint32 microTileBytes = microTileBits >> 3; + uint32 pixelIndex = _ComputePixelIndexWithinMicroTile(x, y, slice, bpp, tileMode, _GetMicroTileType(isDepth)); + uint32 sampleOffset, pixelOffset; + if (isDepth) + { + sampleOffset = bpp * sample; + pixelOffset = numSamples * bpp * pixelIndex; + } + else + { + sampleOffset = sample * (microTileBits / numSamples); + pixelOffset = bpp * pixelIndex; + } + uint32 elemOffset = pixelOffset + sampleOffset; + uint32 bytesPerSample = microTileBytes / numSamples; + uint32 sampleSlice, numSampleSplits; + if (numSamples <= 1 || microTileBytes <= m_splitSize) + { + numSampleSplits = 1; + sampleSlice = 0; + } + else + { + uint32 samplesPerSlice = m_splitSize / bytesPerSample; + numSampleSplits = numSamples / samplesPerSlice; + numSamples = samplesPerSlice; + sampleSlice = elemOffset / (microTileBits / numSampleSplits); + elemOffset %= microTileBits / numSampleSplits; + } + elemOffset >>= 3; + uint32 pipe = _ComputePipeFromCoordWoRotation(x, y); + uint32 bank = _ComputeBankFromCoordWoRotation(x, y); + uint32 bankPipe = pipe + m_pipes * bank; + uint32 rotation = ComputeSurfaceRotationFromTileMode(tileMode); + uint32 swizzle = pipeSwizzle + m_pipes * bankSwizzle; + uint32 sliceIn = slice; + if (TM_IsThickAndMacroTiled(tileMode)) + sliceIn >>= 2; + bankPipe ^= m_pipes * sampleSlice * ((m_banks >> 1) + 1) ^ (swizzle + sliceIn * rotation); + bankPipe %= m_pipes * m_banks; + pipe = bankPipe % m_pipes; + bank = bankPipe / m_pipes; + uint64 sliceBytes = (((uint64)height * pitch * microTileThickness * bpp * numSamples + 7) / 8); + uint64 sliceOffset = sliceBytes * ((sampleSlice + numSampleSplits * slice) / microTileThickness); + uint32 macroTilePitch = 8 * m_banks; + uint32 macroTileHeight = 8 * m_pipes; + switch (tileMode) + { + case Latte::E_HWTILEMODE::TM_2D_TILED_THIN2: + case Latte::E_HWTILEMODE::TM_2B_TILED_THIN2: + macroTilePitch >>= 1; + macroTileHeight <<= 1; + break; + case Latte::E_HWTILEMODE::TM_2D_TILED_THIN4: + case Latte::E_HWTILEMODE::TM_2B_TILED_THIN4: + macroTilePitch >>= 2; + macroTileHeight <<= 2; + break; + default: + break; + } + uint32 macroTilesPerRow = pitch / macroTilePitch; + uint32 macroTileBytes = (numSamples * microTileThickness * bpp * macroTileHeight * macroTilePitch + 7) >> 3; + uint32 macroTileIndexX = x / macroTilePitch; + uint32 macroTileIndexY = y / macroTileHeight; + uint32 macroTileOffset = (x / macroTilePitch + pitch / macroTilePitch * (y / macroTileHeight)) * macroTileBytes; + if (TM_IsBankSwapped(tileMode)) + { + uint32 bankSwapWidth = ComputeSurfaceBankSwappedWidth(tileMode, bpp, numSamples, pitch); + uint32 swapIndex = macroTilePitch * macroTileIndexX / bankSwapWidth; + uint32 bankMask = m_banks - 1; + bank ^= bankSwapOrder[swapIndex & bankMask]; + } + uint32 pipeOffset = (pipe << m_pipeInterleaveBytesBitcount); + uint32 bankOffset = (bank << (m_pipesBitcount + m_pipeInterleaveBytesBitcount)); + uint32 numSwizzleBits = (m_banksBitcount + m_pipesBitcount); + uint32 macroSliceOffset = (uint32)((macroTileOffset + sliceOffset) >> numSwizzleBits); + macroSliceOffset += elemOffset; + + uint32 macroSliceOffsetHigh = macroSliceOffset & ~((1 << m_pipeInterleaveBytesBitcount) - 1); + uint32 macroSliceOffsetLow = macroSliceOffset & ((1 << m_pipeInterleaveBytesBitcount) - 1); + + uint32 finalMacroTileOffset = (macroSliceOffsetHigh << numSwizzleBits) | macroSliceOffsetLow; + return finalMacroTileOffset | pipeOffset | bankOffset; + } + + void SetupCachedSurfaceAddrInfo(CachedSurfaceAddrInfo* info, uint32 slice, uint32 sample, uint32 bpp, uint32 pitch, uint32 height, uint32 depth, uint32 numSamples, E_HWTILEMODE tileMode, int isDepth, uint32 pipeSwizzle, uint32 bankSwizzle) + { + info->slice = slice; + info->sample = sample; + info->bpp = bpp; + info->pitch = pitch; + info->height = height; + info->depth = depth; + info->numSamples = numSamples; + info->tileMode = tileMode; + info->isDepth = isDepth; + info->pipeSwizzle = pipeSwizzle; + info->bankSwizzle = bankSwizzle; + // calculate static info + info->microTileThickness = LatteAddrLib::TM_GetThickness((E_HWTILEMODE)tileMode); + info->microTileBits = info->numSamples * info->bpp * (info->microTileThickness * (8 * 8)); + info->microTileBytes = info->microTileBits >> 3; + info->microTileType = (info->isDepth != 0) ? 1 : 0; + cemu_assert_debug(sample == 0); // non-zero not supported + info->rotation = ComputeSurfaceRotationFromTileMode((E_HWTILEMODE)tileMode); + // macro tile + info->macroTilePitch = 8 * m_banks; + info->macroTileHeight = 8 * m_pipes; + switch (info->tileMode) + { + case E_HWTILEMODE::TM_2D_TILED_THIN2: + case E_HWTILEMODE::TM_2B_TILED_THIN2: + info->macroTilePitch >>= 1; + info->macroTileHeight <<= 1; + break; + case E_HWTILEMODE::TM_2D_TILED_THIN4: + case E_HWTILEMODE::TM_2B_TILED_THIN4: + info->macroTilePitch >>= 2; + info->macroTileHeight <<= 2; + break; + default: + break; + } + _BitScanReverse((DWORD*)&info->macroTilePitchBits, info->macroTilePitch); + _BitScanReverse((DWORD*)&info->macroTileHeightBits, info->macroTileHeight); + info->macroTilesPerRow = info->pitch / info->macroTilePitch; + info->macroTileBytes = (info->numSamples * info->microTileThickness * info->bpp * info->macroTileHeight * info->macroTilePitch + 7) >> 3; + // slice + info->sliceBytes = (info->height * (uint64)info->pitch * info->microTileThickness * info->bpp * info->numSamples + 7) / 8; + info->sliceIn = info->slice; + if (TM_IsThickAndMacroTiled(tileMode)) + info->sliceIn >>= 2; + // bank swap + if (TM_IsBankSwapped(tileMode)) + info->bankSwapWidth = ComputeSurfaceBankSwappedWidth(tileMode, info->bpp, info->numSamples, info->pitch); + // pixel offset multiplier + if (info->isDepth) + { + info->pixelOffsetMul = info->numSamples * info->bpp; + } + else + { + info->pixelOffsetMul = info->bpp; + } + info->bytesPerPixel = info->pixelOffsetMul >> 3; + // table for micro tile offset calculation (we could pre-generate these) + for (sint32 z = 0; z < 8; z++) + { + for (sint32 y = 0; y < 8; y++) + { + for (sint32 x = 0; x < 8; x++) + { + uint16 v = _ComputePixelIndexWithinMicroTile(x, y, z, info->bpp, info->tileMode, info->microTileType); + info->microTilePixelIndexTable[x + y * 8 + z * 8 * 8] = v; + } + } + } + // other constant values + uint32 swizzle = info->pipeSwizzle + m_pipes * info->bankSwizzle; + info->c0 = (swizzle + info->sliceIn * info->rotation); + } + + uint32 ComputeSurfaceAddrFromCoordMacroTiledCached(uint32 x, uint32 y, CachedSurfaceAddrInfo* info) + { + uint32 numSamples = info->numSamples; + uint32 pixelIndex = (uint32)info->microTilePixelIndexTable[(x & 7) + ((y & 7) << 3) + ((info->slice & 7) << 6)]; + uint32 pixelOffset = pixelIndex * info->pixelOffsetMul; + uint32 bytesPerSample = info->microTileBytes / numSamples; + uint32 sampleSlice, numSampleSplits, samplesPerSlice; + if (numSamples <= 1 || info->microTileBytes <= m_splitSize) + { + samplesPerSlice = numSamples; + numSampleSplits = 1; + sampleSlice = 0; + } + else + { + samplesPerSlice = m_splitSize / bytesPerSample; + numSampleSplits = numSamples / samplesPerSlice; + numSamples = samplesPerSlice; + sampleSlice = pixelOffset / (info->microTileBits / numSampleSplits); + pixelOffset %= info->microTileBits / numSampleSplits; + } + pixelOffset >>= 3; + uint32 pipe = _ComputePipeFromCoordWoRotation(x, y); + uint32 bank = _ComputeBankFromCoordWoRotation(x, y); + uint32 bankPipe = pipe + m_pipes * bank; + bankPipe ^= m_pipes * sampleSlice * ((m_banks >> 1) + 1) ^ info->c0; + bankPipe %= m_pipes * m_banks; + pipe = bankPipe % m_pipes; + bank = bankPipe / m_pipes; + uint32 sliceOffset = info->sliceBytes * ((sampleSlice + numSampleSplits * info->slice) / info->microTileThickness); + uint32 macroTileIndexX = x >> info->macroTilePitchBits; + uint32 macroTileIndexY = y >> info->macroTileHeightBits; + uint32 macroTileOffset = (macroTileIndexX + (info->pitch >> info->macroTilePitchBits) * macroTileIndexY) * info->macroTileBytes; + if (TM_IsBankSwapped(info->tileMode)) + { + uint32 swapIndex = info->macroTilePitch * macroTileIndexX / info->bankSwapWidth; + bank ^= bankSwapOrder[swapIndex & (m_banks - 1)]; + } + uint32 pipeOffset = (pipe << m_pipeInterleaveBytesBitcount); + uint32 bankOffset = (bank << (m_pipesBitcount + m_pipeInterleaveBytesBitcount)); + uint32 numSwizzleBits = (m_banksBitcount + m_pipesBitcount); + uint32 macroSliceOffset = (uint32)((macroTileOffset + sliceOffset) >> numSwizzleBits); + macroSliceOffset += pixelOffset; + uint32 macroSliceOffsetHigh = macroSliceOffset & ~((1 << m_pipeInterleaveBytesBitcount) - 1); + uint32 macroSliceOffsetLow = macroSliceOffset & ((1 << m_pipeInterleaveBytesBitcount) - 1); + uint32 finalMacroTileOffset = (macroSliceOffsetHigh << numSwizzleBits) | macroSliceOffsetLow; + return finalMacroTileOffset | pipeOffset | bankOffset; + } + + /* + * Optimized routine with following assumptions: + * tileMode is 4 + * samples is 1 + */ + uint32 ComputeSurfaceAddrFromCoordMacroTiledCached_tm04_sample1(uint32 x, uint32 y, CachedSurfaceAddrInfo* info) + { + uint32 pixelIndex = (uint32)info->microTilePixelIndexTable[(x & 7) + ((y & 7) << 3) + ((info->slice & 7) << 6)]; + uint32 pixelOffset = pixelIndex * info->pixelOffsetMul; + pixelOffset >>= 3; // bits to bytes + uint32 pipe = _ComputePipeFromCoordWoRotation(x, y); // pipe = ((y >> 3) ^ (x >> 3)) & 1; + uint32 bank = _ComputeBankFromCoordWoRotation(x, y); // based on (x>>3)&3 and (y>>4)&3 + pipe ^= (info->c0 >> 0) & 1; + bank ^= (info->c0 >> 1) & 3; + uint32 sliceOffset = info->sliceBytes * (info->slice / info->microTileThickness); + uint32 macroTileIndexX = x >> info->macroTilePitchBits; + uint32 macroTileIndexY = y >> info->macroTileHeightBits; + uint32 macroTileOffset = (macroTileIndexX + (info->pitch >> info->macroTilePitchBits) * macroTileIndexY) * info->macroTileBytes; + uint32 pipeOffset = (pipe << m_pipeInterleaveBytesBitcount); + uint32 bankOffset = (bank << (m_pipesBitcount + m_pipeInterleaveBytesBitcount)); + uint32 numSwizzleBits = (m_banksBitcount + m_pipesBitcount); + uint32 macroSliceOffset = (uint32)((macroTileOffset + sliceOffset) >> numSwizzleBits); + macroSliceOffset += pixelOffset; + uint32 macroSliceOffsetHigh = macroSliceOffset & ~((1 << m_pipeInterleaveBytesBitcount) - 1); + uint32 macroSliceOffsetLow = macroSliceOffset & ((1 << m_pipeInterleaveBytesBitcount) - 1); + uint32 finalMacroTileOffset = (macroSliceOffsetHigh << numSwizzleBits) | macroSliceOffsetLow; + return finalMacroTileOffset | pipeOffset | bankOffset; + } + +}; \ No newline at end of file diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.cpp b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.cpp new file mode 100644 index 00000000..8bad99e0 --- /dev/null +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.cpp @@ -0,0 +1,1188 @@ +#include "Cafe/HW/Latte/Core/LatteConst.h" +#include "Cafe/HW/Latte/Core/LatteShaderAssembly.h" +#include "Cafe/HW/Latte/ISA/RegDefines.h" +#include "Cafe/HW/Latte/Core/Latte.h" +#include "Cafe/HW/Latte/Core/LatteDraw.h" +#include "Cafe/HW/Latte/Core/LatteShader.h" +#include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h" +#include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInternal.h" +#include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInstructions.h" +#include "Cafe/HW/Latte/Core/FetchShader.h" +#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" +#include "Cafe/HW/Latte/Renderer/Renderer.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" +#include "util/helpers/helpers.h" + +// parse instruction and if valid append it to instructionList +bool LatteDecompiler_ParseCFInstruction(LatteDecompilerShaderContext* shaderContext, uint32 cfIndex, uint32 cfWord0, uint32 cfWord1, bool* endOfProgram, std::vector<LatteDecompilerCFInstruction>& instructionList) +{ + LatteDecompilerShader* shaderObj = shaderContext->shader; + uint32 cf_inst23_7 = (cfWord1 >> 23) & 0x7F; + if (cf_inst23_7 < 0x40) // starting at 0x40 the bits overlap with the ALU instruction encoding + { + *endOfProgram = ((cfWord1 >> 21) & 1) != 0; + uint32 addr = cfWord0 & 0xFFFFFFFF; + uint32 count = (cfWord1 >> 10) & 7; + if (((cfWord1 >> 19) & 1) != 0) + count |= 0x8; + count++; + if (cf_inst23_7 == GPU7_CF_INST_CALL_FS) + { + // nop + return true; + } + else if (cf_inst23_7 == GPU7_CF_INST_NOP) + { + // nop + if (((cfWord1 >> 0) & 7) != 0) + debugBreakpoint(); // pop count is not zero + return true; + } + else if (cf_inst23_7 == GPU7_CF_INST_EXPORT || cf_inst23_7 == GPU7_CF_INST_EXPORT_DONE) + { + // export + uint32 edType = (cfWord0 >> 13) & 0x3; + uint32 edIndexGpr = (cfWord0 >> 23) & 0x7F; + uint32 edRWRel = (cfWord0 >> 22) & 1; + if (edRWRel != 0 || edIndexGpr != 0) + debugBreakpoint(); + LatteDecompilerCFInstruction& cfInstruction = instructionList.emplace_back(); + // set type and address + cfInstruction.type = cf_inst23_7; + cfInstruction.cfAddr = cfIndex; + // set cond + cfInstruction.cfCond = (cfWord1 >> 8) & 3; + // set export component selection + cfInstruction.exportComponentSel[0] = (cfWord1 >> 0) & 0x7; + cfInstruction.exportComponentSel[1] = (cfWord1 >> 3) & 0x7; + cfInstruction.exportComponentSel[2] = (cfWord1 >> 6) & 0x7; + cfInstruction.exportComponentSel[3] = (cfWord1 >> 9) & 0x7; + // set export array base, index and burstcount + cfInstruction.exportArrayBase = (cfWord0 >> 0) & 0x1FFF; + cfInstruction.exportBurstCount = (cfWord1 >> 17) & 0xF; + // set export source GPR and type + cfInstruction.exportSourceGPR = (cfWord0 >> 15) & 0x7F; + cfInstruction.exportType = edType; + //cfInstruction->memWriteElemSize = (cfWord0>>29)&3; // unused + return true; + } + else if (cf_inst23_7 == GPU7_CF_INST_TEX) + { + LatteDecompilerCFInstruction& cfInstruction = instructionList.emplace_back(); + // set type and address + cfInstruction.type = cf_inst23_7; + cfInstruction.cfAddr = cfIndex; + // set cond + cfInstruction.cfCond = (cfWord1 >> 8) & 3; + // set TEX clause related values + cfInstruction.addr = addr; // index of first instruction in 64bit words + cfInstruction.count = count; // number of instructions (each instruction is 128bit) + // todo: CF_CONST and COND field and maybe other fields? + return true; + } + else if (cf_inst23_7 == GPU7_CF_INST_ELSE || + cf_inst23_7 == GPU7_CF_INST_POP) + { + LatteDecompilerCFInstruction& cfInstruction = instructionList.emplace_back(); + // set type and address + cfInstruction.type = cf_inst23_7; + cfInstruction.cfAddr = cfIndex; + // set cond and popCount + cfInstruction.cfCond = (cfWord1 >> 8) & 3; + cfInstruction.popCount = (cfWord1 >> 0) & 7; + // set TEX clause related values + cfInstruction.addr = addr; // index of first instruction in 64bit words + cfInstruction.count = count; // number of instructions (each instruction is 128bit) + // todo: CF_CONST + return true; + } + else if (cf_inst23_7 == GPU7_CF_INST_JUMP) + { + // ignored (we use ALU/IF/ELSE/PUSH/POP clauses to determine code flow) + return true; + } + else if (cf_inst23_7 == GPU7_CF_INST_LOOP_START_DX10 || cf_inst23_7 == GPU7_CF_INST_LOOP_END) + { + LatteDecompilerCFInstruction& cfInstruction = instructionList.emplace_back(); + // set type and address + cfInstruction.type = cf_inst23_7; + cfInstruction.cfAddr = cfIndex; + // set cond and popCount + cfInstruction.cfCond = (cfWord1 >> 8) & 3; + cfInstruction.popCount = (cfWord1 >> 0) & 7; + // set TEX clause related values + cfInstruction.addr = addr; // index of first instruction in 64bit words + cfInstruction.count = count; // number of instructions (each instruction is 128bit) + return true; + } + else if (cf_inst23_7 == GPU7_CF_INST_LOOP_BREAK) + { + LatteDecompilerCFInstruction& cfInstruction = instructionList.emplace_back(); + // set type and address + cfInstruction.type = cf_inst23_7; + cfInstruction.cfAddr = cfIndex; + // set cond and popCount + cfInstruction.cfCond = (cfWord1 >> 8) & 3; + cfInstruction.popCount = (cfWord1 >> 0) & 7; + // set clause related values + cfInstruction.addr = addr; // index of first instruction in 64bit words + cfInstruction.count = count; // number of instructions (each instruction is 128bit) + return true; + } + else if (cf_inst23_7 == GPU7_CF_INST_MEM_STREAM0_WRITE || + cf_inst23_7 == GPU7_CF_INST_MEM_STREAM1_WRITE) + { + LatteDecompilerCFInstruction& cfInstruction = instructionList.emplace_back(); + // todo: Correctly read all the STREAM0_WRITE specific fields + // set type and address + cfInstruction.type = cf_inst23_7; + cfInstruction.cfAddr = cfIndex; + // set export array base + cfInstruction.exportArrayBase = (cfWord0 >> 0) & 0x1FFF; + cfInstruction.memWriteArraySize = (cfWord1 >> 0) & 0xFFF; + cfInstruction.memWriteCompMask = (cfWord1 >> 12) & 0xF; + // set export source GPR and type + cfInstruction.exportSourceGPR = (cfWord0 >> 15) & 0x7F; + return true; + } + else if (cf_inst23_7 == GPU7_CF_INST_MEM_RING_WRITE) + { + // this CF instruction is only available when the geometry shader stage is active + LatteDecompilerCFInstruction& cfInstruction = instructionList.emplace_back(); + // set type and address + cfInstruction.type = cf_inst23_7; + cfInstruction.cfAddr = cfIndex; + // set export array base + cfInstruction.exportArrayBase = (cfWord0 >> 0) & 0x1FFF; + cfInstruction.memWriteArraySize = (cfWord1 >> 0) & 0xFFF; + cfInstruction.memWriteCompMask = (cfWord1 >> 12) & 0xF; + cfInstruction.memWriteElemSize = ((cfWord0 >> 30) & 0x3); + cfInstruction.exportBurstCount = (cfWord1 >> 17) & 0xF; + // set export source GPR and type + cfInstruction.exportSourceGPR = (cfWord0 >> 15) & 0x7F; + return true; + } + else if (cf_inst23_7 == GPU7_CF_INST_EMIT_VERTEX) + { + // this CF instruction is only available when the geometry shader stage is active + LatteDecompilerCFInstruction& cfInstruction = instructionList.emplace_back(); + // set type and address + cfInstruction.type = cf_inst23_7; + cfInstruction.cfAddr = cfIndex; + return true; + } + else if (cf_inst23_7 == GPU7_CF_INST_CALL) + { + // CALL subroutine + LatteDecompilerCFInstruction& cfInstruction = instructionList.emplace_back(); + uint32 callCount = (cfWord1 >> 13) & 0x3F; + cfInstruction.addr = addr; // index of call destination in 64bit words + cfInstruction.count = callCount; // store callCount in count + cfInstruction.type = cf_inst23_7; + cfInstruction.cfAddr = cfIndex; + // remember subroutine + bool subroutineIsKnown = false; + for (auto& it : shaderContext->list_subroutines) + { + if (it.cfAddr == addr) + { + subroutineIsKnown = true; + break; + } + } + if (subroutineIsKnown == false) + { + LatteDecompilerSubroutineInfo subroutineInfo = { 0 }; + subroutineInfo.cfAddr = addr; + shaderContext->list_subroutines.push_back(subroutineInfo); + } + return true; + } + else if (cf_inst23_7 == GPU7_CF_INST_RETURN) + { + LatteDecompilerCFInstruction& cfInstruction = instructionList.emplace_back(); + // set type and address + cfInstruction.type = cf_inst23_7; + cfInstruction.cfAddr = cfIndex; + // set cond and popCount + cfInstruction.cfCond = (cfWord1 >> 8) & 3; + cfInstruction.popCount = (cfWord1 >> 0) & 7; + // todo - other fields? + return true; + } + else + { + debug_printf("Unknown 23_7 clause 0x%x found\n", cf_inst23_7); + shaderObj->hasError = true; + return false; + } + } + else + { + // ALU instruction + uint32 cf_inst26_4 = ((cfWord1 >> 26) & 0xF) | GPU7_CF_INST_ALU_MASK; + if (cf_inst26_4 == GPU7_CF_INST_ALU || cf_inst26_4 == GPU7_CF_INST_ALU_PUSH_BEFORE || cf_inst26_4 == GPU7_CF_INST_ALU_POP_AFTER || cf_inst26_4 == GPU7_CF_INST_ALU_POP2_AFTER || cf_inst26_4 == GPU7_CF_INST_ALU_BREAK || cf_inst26_4 == GPU7_CF_INST_ALU_ELSE_AFTER) + { + LatteDecompilerCFInstruction& cfInstruction = instructionList.emplace_back(); + // set type and address + cfInstruction.type = cf_inst26_4; + cfInstruction.cfAddr = cfIndex; + // CF_ALU_* has no cond field + cfInstruction.cfCond = 0; + // set ALU clause related values + cfInstruction.addr = (cfWord0 >> 0) & 0x3FFFFF; // index of first instruction in 64bit words + cfInstruction.count = ((cfWord1 >> 18) & 0x7F) + 1; // number of instructions (each instruction is 64bit) + // set constant file/bank values + cfInstruction.cBank0Index = (cfWord0 >> 22) & 0xF; + cfInstruction.cBank1Index = (cfWord0 >> 26) & 0xF; + cfInstruction.cBank0AddrBase = ((cfWord1 >> 2) & 0xFF) * 16; + cfInstruction.cBank1AddrBase = ((cfWord1 >> 10) & 0xFF) * 16; + return true; + } + else + { + debug_printf("Unknown 26_4 clause 0x%x found\n", cf_inst26_4); + shaderObj->hasError = true; + return false; + } + } + cemu_assert_unimplemented(); // should not reach + return false; +} + +void LatteDecompiler_ParseCFSubroutine(LatteDecompilerShaderContext* shaderContext, uint8* programData, uint32 programSize, LatteDecompilerSubroutineInfo* subroutineInfo) +{ + LatteDecompilerShader* shaderObj = shaderContext->shader; + // parse control flow instructions + for (uint32 i = subroutineInfo->cfAddr; i < programSize / 8; i++) + { + uint32 cfWord0 = *(uint32*)(programData + i * 8 + 0); + uint32 cfWord1 = *(uint32*)(programData + i * 8 + 4); + bool isEndOfProgram = false; + if( !LatteDecompiler_ParseCFInstruction(shaderContext, i, cfWord0, cfWord1, &isEndOfProgram, subroutineInfo->instructions) ) + continue; + cemu_assert_debug(!isEndOfProgram); // should never be encountered in a subroutine? + if (shaderObj->hasError) + return; + auto& cfInstruction = subroutineInfo->instructions.back(); + if (cfInstruction.type == GPU7_CF_INST_RETURN) + return; // todo - should check if this return statement is conditional + } + cemu_assert_debug(false); // should not reach (subroutines have to end with RETURN) +} + +void LatteDecompiler_ParseCF(LatteDecompilerShaderContext* shaderContext, uint8* programData, uint32 programSize) +{ + LatteDecompilerShader* shaderObj = shaderContext->shader; + // parse control flow instructions for main entry point + bool endOfProgram = false; + for (uint32 i = 0; i < programSize / 8; i++) + { + uint32 cfWord0 = *(uint32*)(programData + i * 8 + 0); + uint32 cfWord1 = *(uint32*)(programData + i * 8 + 4); + LatteDecompiler_ParseCFInstruction(shaderContext, i, cfWord0, cfWord1, &endOfProgram, shaderContext->cfInstructions); + if (endOfProgram) + break; + } + // parse CF instructions for subroutines + for (auto& subroutineInfo : shaderContext->list_subroutines) + { + LatteDecompiler_ParseCFSubroutine(shaderContext, programData, programSize, &subroutineInfo); + } +} + +// returns true if the given op2/op3 ALU instruction is always executed on the transcendental unit +bool LatteDecompiler_IsALUTransInstruction(bool isOP3, uint32 opcode) +{ + if( isOP3 == true ) + return false; // OP3 has no transcendental instructions? + + if( opcode == ALU_OP2_INST_COS || + opcode == ALU_OP2_INST_SIN || + opcode == ALU_OP2_INST_RECIP_FF || + opcode == ALU_OP2_INST_RECIP_IEEE || + opcode == ALU_OP2_INST_RECIPSQRT_IEEE || + opcode == ALU_OP2_INST_RECIPSQRT_CLAMPED || + opcode == ALU_OP2_INST_RECIPSQRT_FF || + opcode == ALU_OP2_INST_MULLO_INT || + opcode == ALU_OP2_INST_MULLO_UINT || + opcode == ALU_OP2_INST_FLT_TO_INT || + opcode == ALU_OP2_INST_FLT_TO_UINT || + opcode == ALU_OP2_INST_INT_TO_FLOAT || + opcode == ALU_OP2_INST_UINT_TO_FLOAT || + opcode == ALU_OP2_INST_LOG_CLAMPED || + opcode == ALU_OP2_INST_LOG_IEEE || + opcode == ALU_OP2_INST_EXP_IEEE || + opcode == ALU_OP2_INST_UINT_TO_FLOAT || + opcode == ALU_OP2_INST_SQRT_IEEE + ) + { + // transcendental + return true; + } + else if( opcode == ALU_OP2_INST_MOV || + opcode == ALU_OP2_INST_ADD || + opcode == ALU_OP2_INST_NOP || + opcode == ALU_OP2_INST_MUL || + opcode == ALU_OP2_INST_DOT4 || + opcode == ALU_OP2_INST_DOT4_IEEE || + opcode == ALU_OP2_INST_MAX || // Not sure if MIN/MAX are non-transcendental? + opcode == ALU_OP2_INST_MIN || + opcode == ALU_OP2_INST_AND_INT || + opcode == ALU_OP2_INST_OR_INT || + opcode == ALU_OP2_INST_XOR_INT || + opcode == ALU_OP2_INST_NOT_INT || + opcode == ALU_OP2_INST_ADD_INT || + opcode == ALU_OP2_INST_SUB_INT || + opcode == ALU_OP2_INST_SETGT || + opcode == ALU_OP2_INST_SETGE || + opcode == ALU_OP2_INST_SETNE || + opcode == ALU_OP2_INST_SETE || + opcode == ALU_OP2_INST_SETE_INT || + opcode == ALU_OP2_INST_SETNE_INT || + opcode == ALU_OP2_INST_SETGT_INT || + opcode == ALU_OP2_INST_SETGE_INT || + opcode == ALU_OP2_INST_SETGE_UINT || + opcode == ALU_OP2_INST_SETGT_UINT || + opcode == ALU_OP2_INST_MAX_DX10 || + opcode == ALU_OP2_INST_PRED_SETE || + opcode == ALU_OP2_INST_PRED_SETNE || + opcode == ALU_OP2_INST_PRED_SETGE || + opcode == ALU_OP2_INST_PRED_SETGT || + opcode == ALU_OP2_INST_PRED_SETE_INT || + opcode == ALU_OP2_INST_PRED_SETNE_INT || + opcode == ALU_OP2_INST_PRED_SETGT_INT || + opcode == ALU_OP2_INST_PRED_SETGE_INT || + opcode == ALU_OP2_INST_KILLE_INT || + opcode == ALU_OP2_INST_KILLGT_INT || + opcode == ALU_OP2_INST_KILLNE_INT || + opcode == ALU_OP2_INST_KILLGT || + opcode == ALU_OP2_INST_KILLGE || + opcode == ALU_OP2_INST_KILLE || + opcode == ALU_OP2_INST_MUL_IEEE || + opcode == ALU_OP2_INST_FLOOR || + opcode == ALU_OP2_INST_FRACT || + opcode == ALU_OP2_INST_TRUNC || + opcode == ALU_OP2_INST_LSHL_INT || + opcode == ALU_OP2_INST_ASHR_INT || + opcode == ALU_OP2_INST_LSHR_INT || + opcode == ALU_OP2_INST_MAX_INT || + opcode == ALU_OP2_INST_MIN_INT || + opcode == ALU_OP2_INST_MOVA_FLOOR || + opcode == ALU_OP2_INST_MOVA_INT || + opcode == ALU_OP2_INST_SETE_DX10 || + opcode == ALU_OP2_INST_SETNE_DX10 || + opcode == ALU_OP2_INST_SETGT_DX10 || + opcode == ALU_OP2_INST_SETGE_DX10 || + opcode == ALU_OP2_INST_RNDNE || + opcode == ALU_OP2_INST_CUBE // reduction instruction + ) + { + // not transcendental + return false; + } + else + { + debug_printf("_isALUTransInstruction(): Unknown instruction 0x%x (%s)\n", opcode, isOP3?"op3":"op2"); + } + + // ALU.Trans instructions: + // [x] FLT_TO_INT + // [x] FLT_TO_UINT + // [x] INT_TO_FLT + // MULHI_INT + // MULHI_UINT + // [x] MULLO_INT + // [x] MULLO_UINT + // RECIP_INT + // RECIP_UINT + // [x] UINT_TO_FLT + // [x] COS + // [x] EXP_IEEE + // [x] LOG_CLAMPED + // [x] LOG_IEEE + // MUL_LIT + // MUL_LIT_D2 + // MUL_LIT_M2 + // MUL_LIT_M4 + // RECIP_CLAMPED + // [x] RECIP_FF + // [x] RECIP_IEEE + // [x] RECIPSQRT_CLAMPED + // [x] RECIPSQRT_FF + // [x] RECIPSQRT_IEEE + // [x] SIN + // [x] SQRT_IEEE + + return false; +} + +void LatteDecompiler_ParseALUClause(LatteDecompilerShader* shaderContext, LatteDecompilerCFInstruction* cfInstruction, uint8* programData, uint32 programSize) +{ + sint32 instructionGroupIndex = 0; + sint32 indexInGroup = 0; // index of instruction within instruction group + uint32 elementsWrittenMask = 0; // used to determine ALU/Trans unit for instructions + uint8 literalMask = 0; // mask of used literals for current instruction group + sint32 parserIndex = 0; + while( parserIndex < cfInstruction->count ) + { + uint32 aluWord0 = *(uint32*)(programData+(cfInstruction->addr+parserIndex)*8+0); + uint32 aluWord1 = *(uint32*)(programData+(cfInstruction->addr+parserIndex)*8+4); + parserIndex++; + bool isLastInGroup = (aluWord0&0x80000000) != 0; + uint32 alu_inst13_5 = (aluWord1>>13)&0x1F; + // parameters from ALU word 0 (shared for ALU OP2 and OP3) + uint32 src0Sel = (aluWord0>>0)&0x1FF; // source selection + uint32 src1Sel = (aluWord0>>13)&0x1FF; + uint32 src0Rel = (aluWord0>>9)&0x1; // relative addressing mode + uint32 src1Rel = (aluWord0>>22)&0x1; + uint32 src0Chan = (aluWord0>>10)&0x3; // component selection x/y/z/w + uint32 src1Chan = (aluWord0>>23)&0x3; + uint32 src0Neg = (aluWord0>>12)&0x1; // negate input + uint32 src1Neg = (aluWord0>>25)&0x1; + uint32 indexMode = (aluWord0>>26)&7; + uint32 predSel = (aluWord0>>29)&3; + if( predSel != 0 ) + debugBreakpoint(); + if( alu_inst13_5 >= 0x8 ) + { + // op3 + // parameters from ALU word 1 + uint32 src2Sel = (aluWord1>>0)&0x1FF; // source selection + uint32 src2Rel = (aluWord1>>9)&0x1; // relative addressing mode + uint32 src2Chan = (aluWord1>>10)&0x3; // component selection x/y/z/w + uint32 src2Neg = (aluWord1>>12)&0x1; // negate input + + uint32 destGpr = (aluWord1>>21)&0x7F; + uint32 destRel = (aluWord1>>28)&1; + uint32 destElem = (aluWord1>>29)&3; + uint32 destClamp = (aluWord1>>31)&1; + + LatteDecompilerALUInstruction aluInstruction; + aluInstruction.cfInstruction = cfInstruction; + aluInstruction.isOP3 = true; + aluInstruction.opcode = alu_inst13_5; + aluInstruction.instructionGroupIndex = instructionGroupIndex; + aluInstruction.indexMode = indexMode; + aluInstruction.destGpr = destGpr; + aluInstruction.destRel = destRel; + aluInstruction.destElem = destElem; + aluInstruction.destClamp = destClamp; + aluInstruction.writeMask = 1; + aluInstruction.omod = 0; // op3 has no omod + aluInstruction.sourceOperand[0].sel = src0Sel; + aluInstruction.sourceOperand[0].rel = src0Rel; + aluInstruction.sourceOperand[0].abs = 0; + aluInstruction.sourceOperand[0].neg = src0Neg; + aluInstruction.sourceOperand[0].chan = src0Chan; + aluInstruction.sourceOperand[1].sel = src1Sel; + aluInstruction.sourceOperand[1].rel = src1Rel; + aluInstruction.sourceOperand[1].abs = 0; + aluInstruction.sourceOperand[1].neg = src1Neg; + aluInstruction.sourceOperand[1].chan = src1Chan; + aluInstruction.sourceOperand[2].sel = src2Sel; + aluInstruction.sourceOperand[2].rel = src2Rel; + aluInstruction.sourceOperand[2].abs = 0; + aluInstruction.sourceOperand[2].neg = src2Neg; + aluInstruction.sourceOperand[2].chan = src2Chan; + // check for literal access + if( GPU7_ALU_SRC_IS_LITERAL(src0Sel) ) + literalMask |= (1<<src0Chan); + if( GPU7_ALU_SRC_IS_LITERAL(src1Sel) ) + literalMask |= (1<<src1Chan); + if( GPU7_ALU_SRC_IS_LITERAL(src2Sel) ) + literalMask |= (1<<src2Chan); + // determine used ALU unit (x,y,z,w,t) + uint32 aluUnit = destElem; + if( aluUnit < 4 && (elementsWrittenMask & (1<<aluUnit)) != 0 ) + { + aluUnit = 4; // ALU unit already used, this instruction uses the transcendental unit + } + elementsWrittenMask |= (1<<aluUnit); + aluInstruction.aluUnit = aluUnit; + aluInstruction.indexInGroup = indexInGroup; + aluInstruction.isLastInstructionOfGroup = isLastInGroup; + // add instruction to list of sub-instructions + cfInstruction->instructionsALU.emplace_back(aluInstruction); + } + else + { + uint32 alu_inst7_11 = (aluWord1>>7)&0x7FF; + + uint32 src0Abs = (aluWord1>>0)&1; + uint32 src1Abs = (aluWord1>>1)&1; + uint32 updateExecuteMask = (aluWord1>>2)&1; + uint32 updatePredicate = (aluWord1>>3)&1; + uint32 writeMask = (aluWord1>>4)&1; + uint32 omod = (aluWord1>>5)&3; + + uint32 destGpr = (aluWord1>>21)&0x7F; + uint32 destRel = (aluWord1>>28)&1; + uint32 destElem = (aluWord1>>29)&3; + uint32 destClamp = (aluWord1>>31)&1; + + LatteDecompilerALUInstruction aluInstruction; + aluInstruction.cfInstruction = cfInstruction; + aluInstruction.isOP3 = false; + aluInstruction.opcode = alu_inst7_11; + aluInstruction.instructionGroupIndex = instructionGroupIndex; + aluInstruction.indexMode = indexMode; + aluInstruction.destGpr = destGpr; + aluInstruction.destRel = destRel; + aluInstruction.destElem = destElem; + aluInstruction.destClamp = destClamp; + aluInstruction.writeMask = writeMask; + aluInstruction.updateExecuteMask = updateExecuteMask; + aluInstruction.updatePredicate = updatePredicate; + aluInstruction.omod = omod; + aluInstruction.sourceOperand[0].sel = src0Sel; + aluInstruction.sourceOperand[0].rel = src0Rel; + aluInstruction.sourceOperand[0].abs = src0Abs; + aluInstruction.sourceOperand[0].neg = src0Neg; + aluInstruction.sourceOperand[0].chan = src0Chan; + aluInstruction.sourceOperand[1].sel = src1Sel; + aluInstruction.sourceOperand[1].rel = src1Rel; + aluInstruction.sourceOperand[1].abs = src1Abs; + aluInstruction.sourceOperand[1].neg = src1Neg; + aluInstruction.sourceOperand[1].chan = src1Chan; + aluInstruction.sourceOperand[2].sel = 0xFFFFFFFF; + // check for literal access + if( GPU7_ALU_SRC_IS_LITERAL(src0Sel) ) + literalMask |= (1<<src0Chan); + if( GPU7_ALU_SRC_IS_LITERAL(src1Sel) ) + literalMask |= (1<<src1Chan); + // determine ALU unit (x,y,z,w,t) + uint32 aluUnit = destElem; + // some instructions always use the transcendental unit + bool isTranscendentalOperation = LatteDecompiler_IsALUTransInstruction(false, alu_inst7_11); + if( isTranscendentalOperation ) + aluUnit = 4; + if( aluUnit < 4 && (elementsWrittenMask & (1<<aluUnit)) != 0 ) + { + aluUnit = 4; // ALU unit already used, this instruction uses the transcendental unit + } + elementsWrittenMask |= (1<<aluUnit); + aluInstruction.aluUnit = aluUnit; + aluInstruction.indexInGroup = indexInGroup; + aluInstruction.isLastInstructionOfGroup = isLastInGroup; + // add instruction to list of sub-instructions + cfInstruction->instructionsALU.emplace_back(aluInstruction); + } + indexInGroup++; + if( isLastInGroup ) + { + // load literal data + if( literalMask ) + { + bool useLiteralDataXY = false; + bool useLiteralDataZW = false; + if( (literalMask&(1|2)) ) + { + useLiteralDataXY = true; + } + if( (literalMask&(4|8)) ) + { + useLiteralDataXY = true; + useLiteralDataZW = true; + } + uint32 literalWords[4] = {0}; + literalWords[0] = *(uint32*)(programData+(cfInstruction->addr+parserIndex)*8+0); + literalWords[1] = *(uint32*)(programData+(cfInstruction->addr+parserIndex)*8+4); + if( useLiteralDataZW ) + { + literalWords[2] = *(uint32*)(programData+(cfInstruction->addr+parserIndex+1)*8+0); + literalWords[3] = *(uint32*)(programData+(cfInstruction->addr+parserIndex+1)*8+4); + } + if( useLiteralDataZW ) + parserIndex += 2; + else + parserIndex += 1; + // set literal data for all instructions of the current instruction group + for(auto& aluInstructionItr : reverse_itr(cfInstruction->instructionsALU) ) + { + if( aluInstructionItr.instructionGroupIndex != instructionGroupIndex ) + break; + aluInstructionItr.literalData.w[0] = literalWords[0]; + aluInstructionItr.literalData.w[1] = literalWords[1]; + aluInstructionItr.literalData.w[2] = literalWords[2]; + aluInstructionItr.literalData.w[3] = literalWords[3]; + } + } + // reset instruction group related tracking variables + literalMask = 0; + elementsWrittenMask = 0; + indexInGroup = 0; + // start next group + instructionGroupIndex++; + } + } +} + +/* + * Parse TEX clause + */ +void LatteDecompiler_ParseTEXClause(LatteDecompilerShader* shaderContext, LatteDecompilerCFInstruction* cfInstruction, uint8* programData, uint32 programSize) +{ + for(sint32 i=0; i<cfInstruction->count; i++) + { + // each instruction is 128bit + uint32 instructionAddr = cfInstruction->addr*2+i*4; + uint32 word0 = *(uint32*)(programData+instructionAddr*4+0); + uint32 word1 = *(uint32*)(programData+instructionAddr*4+4); + uint32 word2 = *(uint32*)(programData+instructionAddr*4+8); + uint32 word3 = *(uint32*)(programData+instructionAddr*4+12); + uint32 inst0_4 = (word0>>0)&0x1F; + if (inst0_4 == GPU7_TEX_INST_SAMPLE || inst0_4 == GPU7_TEX_INST_SAMPLE_L || inst0_4 == GPU7_TEX_INST_SAMPLE_LZ || inst0_4 == GPU7_TEX_INST_SAMPLE_LB || inst0_4 == GPU7_TEX_INST_SAMPLE_C || inst0_4 == GPU7_TEX_INST_SAMPLE_C_L || inst0_4 == GPU7_TEX_INST_SAMPLE_C_LZ || inst0_4 == GPU7_TEX_INST_FETCH4 || inst0_4 == GPU7_TEX_INST_SAMPLE_G || inst0_4 == GPU7_TEX_INST_LD + || inst0_4 == GPU7_TEX_INST_GET_TEXTURE_RESINFO || inst0_4 == GPU7_TEX_INST_GET_COMP_TEX_LOD) + { + uint32 fetchType = (word0 >> 5) & 3; + uint32 bufferId = (word0 >> 8) & 0xFF; + uint32 samplerId = (word2 >> 15) & 0x1F; + uint32 srcGpr = (word0 >> 16) & 0x7F; + uint32 srcRel = (word0 >> 23) & 1; + if (srcRel != 0) + debugBreakpoint(); + uint32 destGpr = (word1 >> 0) & 0x7F; + uint32 destRel = (word1 >> 7) & 1; + if (destRel != 0) + debugBreakpoint(); + uint32 dstSelX = (word1 >> 9) & 0x7; + uint32 dstSelY = (word1 >> 12) & 0x7; + uint32 dstSelZ = (word1 >> 15) & 0x7; + uint32 dstSelW = (word1 >> 18) & 0x7; + + uint32 coordTypeX = (word1 >> 28) & 1; + uint32 coordTypeY = (word1 >> 29) & 1; + uint32 coordTypeZ = (word1 >> 30) & 1; + uint32 coordTypeW = (word1 >> 31) & 1; + + uint32 srcSelX = (word2 >> 20) & 0x7; + uint32 srcSelY = (word2 >> 23) & 0x7; + uint32 srcSelZ = (word2 >> 26) & 0x7; + uint32 srcSelW = (word2 >> 29) & 0x7; + + uint32 offsetX = (word2 >> 0) & 0x1F; + uint32 offsetY = (word2 >> 5) & 0x1F; + uint32 offsetZ = (word2 >> 10) & 0x1F; + + // bufferID -> Texture index + // samplerId -> Sampler index + sint32 textureIndex = bufferId - 0x00; + + // create new tex instruction + LatteDecompilerTEXInstruction texInstruction; + texInstruction.cfInstruction = cfInstruction; + texInstruction.opcode = inst0_4; + texInstruction.textureFetch.textureIndex = textureIndex; + texInstruction.textureFetch.samplerIndex = samplerId; + texInstruction.dstSel[0] = dstSelX; + texInstruction.dstSel[1] = dstSelY; + texInstruction.dstSel[2] = dstSelZ; + texInstruction.dstSel[3] = dstSelW; + texInstruction.textureFetch.srcSel[0] = srcSelX; + texInstruction.textureFetch.srcSel[1] = srcSelY; + texInstruction.textureFetch.srcSel[2] = srcSelZ; + texInstruction.textureFetch.srcSel[3] = srcSelW; + texInstruction.textureFetch.offsetX = (sint8)((offsetX & 0x10) ? (offsetX | 0xE0) : (offsetX)); + texInstruction.textureFetch.offsetY = (sint8)((offsetY & 0x10) ? (offsetY | 0xE0) : (offsetY)); + texInstruction.textureFetch.offsetZ = (sint8)((offsetZ & 0x10) ? (offsetZ | 0xE0) : (offsetZ)); + texInstruction.dstGpr = destGpr; + texInstruction.srcGpr = srcGpr; + texInstruction.textureFetch.unnormalized[0] = coordTypeX == 0; + texInstruction.textureFetch.unnormalized[1] = coordTypeY == 0; + texInstruction.textureFetch.unnormalized[2] = coordTypeZ == 0; + texInstruction.textureFetch.unnormalized[3] = coordTypeW == 0; + cfInstruction->instructionsTEX.emplace_back(texInstruction); + } + else if( inst0_4 == GPU7_TEX_INST_SET_CUBEMAP_INDEX ) + { + // todo: check if the encoding of fields matches with that of GPU7_TEX_INST_SAMPLE* (it should, according to AMD doc) + uint32 fetchType = (word0>>5)&3; + uint32 bufferId = (word0>>8)&0xFF; + uint32 samplerId = (word2>>15)&0x1F; + uint32 srcGpr = (word0>>16)&0x7F; + uint32 srcRel = (word0>>23)&1; + if( srcRel != 0 ) + debugBreakpoint(); + uint32 destGpr = (word1>>0)&0x7F; + uint32 destRel = (word1>>7)&1; + if( destRel != 0 ) + debugBreakpoint(); + uint32 dstSelX = (word1>>9)&0x7; + uint32 dstSelY = (word1>>12)&0x7; + uint32 dstSelZ = (word1>>15)&0x7; + uint32 dstSelW = (word1>>18)&0x7; + + uint32 srcSelX = (word2>>20)&0x7; + uint32 srcSelY = (word2>>23)&0x7; + uint32 srcSelZ = (word2>>26)&0x7; + uint32 srcSelW = (word2>>29)&0x7; + + sint32 textureIndex = bufferId-0x00; + + // create new tex instruction + LatteDecompilerTEXInstruction texInstruction; + texInstruction.cfInstruction = cfInstruction; + texInstruction.opcode = inst0_4; + texInstruction.textureFetch.textureIndex = textureIndex; + texInstruction.textureFetch.samplerIndex = samplerId; + texInstruction.dstSel[0] = dstSelX; + texInstruction.dstSel[1] = dstSelY; + texInstruction.dstSel[2] = dstSelZ; + texInstruction.dstSel[3] = dstSelW; + texInstruction.textureFetch.srcSel[0] = srcSelX; + texInstruction.textureFetch.srcSel[1] = srcSelY; + texInstruction.textureFetch.srcSel[2] = srcSelZ; + texInstruction.textureFetch.srcSel[3] = srcSelW; + texInstruction.dstGpr = destGpr; + texInstruction.srcGpr = srcGpr; + cfInstruction->instructionsTEX.emplace_back(texInstruction); + } + else if (inst0_4 == GPU7_TEX_INST_GET_GRADIENTS_H || inst0_4 == GPU7_TEX_INST_GET_GRADIENTS_V) + { + uint32 fetchType = (word0 >> 5) & 3; + uint32 bufferId = (word0 >> 8) & 0xFF; + uint32 samplerId = (word2 >> 15) & 0x1F; + uint32 srcGpr = (word0 >> 16) & 0x7F; + uint32 srcRel = (word0 >> 23) & 1; + if (srcRel != 0) + debugBreakpoint(); + uint32 destGpr = (word1 >> 0) & 0x7F; + uint32 destRel = (word1 >> 7) & 1; + if (destRel != 0) + debugBreakpoint(); + uint32 dstSelX = (word1 >> 9) & 0x7; + uint32 dstSelY = (word1 >> 12) & 0x7; + uint32 dstSelZ = (word1 >> 15) & 0x7; + uint32 dstSelW = (word1 >> 18) & 0x7; + + uint32 coordTypeX = (word1 >> 28) & 1; + uint32 coordTypeY = (word1 >> 29) & 1; + uint32 coordTypeZ = (word1 >> 30) & 1; + uint32 coordTypeW = (word1 >> 31) & 1; + cemu_assert_debug(coordTypeX != GPU7_TEX_UNNORMALIZED); + cemu_assert_debug(coordTypeY != GPU7_TEX_UNNORMALIZED); + cemu_assert_debug(coordTypeZ != GPU7_TEX_UNNORMALIZED); + cemu_assert_debug(coordTypeW != GPU7_TEX_UNNORMALIZED); + + uint32 srcSelX = (word2 >> 20) & 0x7; + uint32 srcSelY = (word2 >> 23) & 0x7; + uint32 srcSelZ = (word2 >> 26) & 0x7; + uint32 srcSelW = (word2 >> 29) & 0x7; + + uint32 offsetX = (word2 >> 0) & 0x1F; + uint32 offsetY = (word2 >> 5) & 0x1F; + uint32 offsetZ = (word2 >> 10) & 0x1F; + cemu_assert_debug(offsetX == 0); + cemu_assert_debug(offsetY == 0); + cemu_assert_debug(offsetZ == 0); + + // create new tex instruction + LatteDecompilerTEXInstruction texInstruction; + texInstruction.cfInstruction = cfInstruction; + texInstruction.opcode = inst0_4; + texInstruction.dstSel[0] = dstSelX; + texInstruction.dstSel[1] = dstSelY; + texInstruction.dstSel[2] = dstSelZ; + texInstruction.dstSel[3] = dstSelW; + texInstruction.textureFetch.srcSel[0] = srcSelX; + texInstruction.textureFetch.srcSel[1] = srcSelY; + texInstruction.textureFetch.srcSel[2] = srcSelZ; + texInstruction.textureFetch.srcSel[3] = srcSelW; + texInstruction.dstGpr = destGpr; + texInstruction.srcGpr = srcGpr; + cfInstruction->instructionsTEX.emplace_back(texInstruction); + } + else if (inst0_4 == GPU7_TEX_INST_SET_GRADIENTS_H || inst0_4 == GPU7_TEX_INST_SET_GRADIENTS_V) + { + uint32 bufferId = (word0 >> 8) & 0xFF; + uint32 samplerId = (word2 >> 15) & 0x1F; + uint32 srcGpr = (word0 >> 16) & 0x7F; + uint32 srcRel = (word0 >> 23) & 1; + if (srcRel != 0) + debugBreakpoint(); + uint32 coordTypeX = (word1 >> 28) & 1; + uint32 coordTypeY = (word1 >> 29) & 1; + uint32 coordTypeZ = (word1 >> 30) & 1; + uint32 coordTypeW = (word1 >> 31) & 1; + cemu_assert_debug(coordTypeX != GPU7_TEX_UNNORMALIZED); + cemu_assert_debug(coordTypeY != GPU7_TEX_UNNORMALIZED); + cemu_assert_debug(coordTypeZ != GPU7_TEX_UNNORMALIZED); + cemu_assert_debug(coordTypeW != GPU7_TEX_UNNORMALIZED); + + uint32 srcSelX = (word2 >> 20) & 0x7; + uint32 srcSelY = (word2 >> 23) & 0x7; + uint32 srcSelZ = (word2 >> 26) & 0x7; + uint32 srcSelW = (word2 >> 29) & 0x7; + + sint32 textureIndex = bufferId - 0x00; + // create new tex instruction + LatteDecompilerTEXInstruction texInstruction; + texInstruction.cfInstruction = cfInstruction; + texInstruction.opcode = inst0_4; + texInstruction.textureFetch.textureIndex = textureIndex; + texInstruction.textureFetch.samplerIndex = samplerId; + texInstruction.textureFetch.srcSel[0] = srcSelX; + texInstruction.textureFetch.srcSel[1] = srcSelY; + texInstruction.textureFetch.srcSel[2] = srcSelZ; + texInstruction.textureFetch.srcSel[3] = srcSelW; + texInstruction.srcGpr = srcGpr; + texInstruction.dstGpr = 0xFFFFFFFF; + cfInstruction->instructionsTEX.emplace_back(texInstruction); + } + else if( inst0_4 == GPU7_TEX_INST_VFETCH ) + { + // this uses the VTX_WORD* encoding + uint32 fetchType = (word0>>5)&3; + uint32 bufferId = (word0>>8)&0xFF; + uint32 offset = (word2>>0)&0xFFFF; + uint32 endianSwap = (word2>>16)&0x3; + uint32 constNoStride = (word2>>18)&0x1; + uint32 srcGpr = (word0>>16)&0x7F; + uint32 srcRel = (word0>>23)&1; + if( srcRel != 0 ) + debugBreakpoint(); + uint32 destGpr = (word1>>0)&0x7F; + uint32 destRel = (word1>>7)&1; + if( destRel != 0 ) + debugBreakpoint(); + uint32 dstSelX = (word1>>9)&0x7; + uint32 dstSelY = (word1>>12)&0x7; + uint32 dstSelZ = (word1>>15)&0x7; + uint32 dstSelW = (word1>>18)&0x7; + + uint32 srcSelX = (word0>>24)&0x3; + uint32 srcSelY = 0; + uint32 srcSelZ = 0; + uint32 srcSelW = 0; + + // create new tex instruction + LatteDecompilerTEXInstruction texInstruction; + texInstruction.cfInstruction = cfInstruction; + texInstruction.opcode = inst0_4; + texInstruction.textureFetch.textureIndex = bufferId; + texInstruction.textureFetch.samplerIndex = 0; + texInstruction.textureFetch.offset = offset; + texInstruction.dstSel[0] = dstSelX; + texInstruction.dstSel[1] = dstSelY; + texInstruction.dstSel[2] = dstSelZ; + texInstruction.dstSel[3] = dstSelW; + texInstruction.textureFetch.srcSel[0] = srcSelX; + texInstruction.textureFetch.srcSel[1] = srcSelY; + texInstruction.textureFetch.srcSel[2] = srcSelZ; + texInstruction.textureFetch.srcSel[3] = srcSelW; + texInstruction.dstGpr = destGpr; + texInstruction.srcGpr = srcGpr; + cfInstruction->instructionsTEX.emplace_back(texInstruction); + } + else if (inst0_4 == GPU7_TEX_INST_MEM) + { + // memory access + // MEM_RD_WORD0 + uint32 elementSize = (word0 >> 5) & 3; + uint32 memOp = (word0 >> 8) & 7; + uint8 indexed = (word0 >> 12) & 1; + uint32 srcGPR = (word0 >> 16) & 0x7F; + uint8 srcREL = (word0 >> 23) & 1; + uint8 srcSelX = (word0 >> 24) & 3; + // MEM_RD_WORD1 + uint32 dstGPR = (word1 >> 0) & 0x7F; + uint8 dstREL = (word1 >> 7) & 1; + uint8 dstSelX = (word1 >> 9) & 7; + uint8 dstSelY = (word1 >> 12) & 7; + uint8 dstSelZ = (word1 >> 15) & 7; + uint8 dstSelW = (word1 >> 18) & 7; + uint8 dataFormat = (word1 >> 22) & 0x3F; + uint8 nfa = (word1 >> 28) & 3; + uint8 isSigned = (word1 >> 30) & 1; + uint8 srfMode = (word1 >> 31) & 1; + // MEM_RD_WORD2 + uint32 arrayBase = (word2 & 0x1FFF); + uint8 endianSwap = (word2 >> 16) & 3; + uint32 arraySize = (word2 >> 20) & 0xFFF; + if (memOp == 2) + { + // read from scatter buffer (SSBO) + LatteDecompilerTEXInstruction texInstruction; + texInstruction.cfInstruction = cfInstruction; + texInstruction.opcode = inst0_4; + + cemu_assert_debug(srcREL == 0 || dstREL == 0); // unsupported relative access + + texInstruction.memRead.arrayBase = arrayBase; + texInstruction.srcGpr = srcGPR; + texInstruction.dstGpr = dstGPR; + texInstruction.memRead.srcSelX = srcSelX; + texInstruction.dstSel[0] = dstSelX; + texInstruction.dstSel[1] = dstSelY; + texInstruction.dstSel[2] = dstSelZ; + texInstruction.dstSel[3] = dstSelW; + + texInstruction.memRead.format = dataFormat; + texInstruction.memRead.nfa = nfa; + texInstruction.memRead.isSigned = isSigned; + + cfInstruction->instructionsTEX.emplace_back(texInstruction); + } + else + { + cemu_assert_unimplemented(); + } + } + else + { + forceLogDebug_printf("Unsupported tex instruction %d\n", inst0_4); + shaderContext->hasError = true; + break; + } + } + cemu_assert_debug(cfInstruction->instructionsALU.empty()); // clause may only contain texture instructions +} + +// iterate all CF instructions and parse clause sub-instructions (if present) +void LatteDecompiler_ParseClauses(LatteDecompilerShaderContext* decompilerContext, uint8* programData, uint32 programSize, std::vector<LatteDecompilerCFInstruction> &list_instructions) +{ + LatteDecompilerShader* shader = decompilerContext->shader; + for (auto& cfInstruction : list_instructions) + { + if (cfInstruction.type == GPU7_CF_INST_ALU || cfInstruction.type == GPU7_CF_INST_ALU_PUSH_BEFORE || cfInstruction.type == GPU7_CF_INST_ALU_POP_AFTER || cfInstruction.type == GPU7_CF_INST_ALU_POP2_AFTER || cfInstruction.type == GPU7_CF_INST_ALU_BREAK || cfInstruction.type == GPU7_CF_INST_ALU_ELSE_AFTER) + { + LatteDecompiler_ParseALUClause(shader, &cfInstruction, programData, programSize); + } + else if (cfInstruction.type == GPU7_CF_INST_TEX) + { + LatteDecompiler_ParseTEXClause(shader, &cfInstruction, programData, programSize); + } + else if (cfInstruction.type == GPU7_CF_INST_EXPORT || cfInstruction.type == GPU7_CF_INST_EXPORT_DONE) + { + // no sub-instructions + } + else if (cfInstruction.type == GPU7_CF_INST_ELSE || cfInstruction.type == GPU7_CF_INST_POP) + { + // no sub-instructions + } + else if (cfInstruction.type == GPU7_CF_INST_LOOP_START_DX10 || cfInstruction.type == GPU7_CF_INST_LOOP_END) + { + // no sub-instructions + } + else if (cfInstruction.type == GPU7_CF_INST_LOOP_BREAK) + { + // no sub-instructions + } + else if (cfInstruction.type == GPU7_CF_INST_MEM_STREAM0_WRITE || + cfInstruction.type == GPU7_CF_INST_MEM_STREAM1_WRITE) + { + // no sub-instructions + } + else if (cfInstruction.type == GPU7_CF_INST_MEM_RING_WRITE) + { + // no sub-instructions + } + else if (cfInstruction.type == GPU7_CF_INST_CALL) + { + // no sub-instructions + } + else if (cfInstruction.type == GPU7_CF_INST_RETURN) + { + // no sub-instructions + } + else if (cfInstruction.type == GPU7_CF_INST_EMIT_VERTEX) + { + // no sub-instructions + } + else + { + debug_printf("_parseClauses(): Unsupported clause 0x%x\n", cfInstruction.type); + cemu_assert_unimplemented(); + } + } +} + +// iterate all CF instructions and parse sub-instructions +void LatteDecompiler_ParseClauses(LatteDecompilerShaderContext* shaderContext, uint8* programData, uint32 programSize) +{ + LatteDecompilerShader* shader = shaderContext->shader; + LatteDecompiler_ParseClauses(shaderContext, programData, programSize, shaderContext->cfInstructions); + // parse subroutines + for (auto& subroutineInfo : shaderContext->list_subroutines) + { + LatteDecompiler_ParseClauses(shaderContext, programData, programSize, subroutineInfo.instructions); + } +} + +void _LatteDecompiler_GenerateDataForFastAccess(LatteDecompilerShader* shader) +{ + if (shader->hasError) + return; + for (size_t i = 0; i < shader->list_remappedUniformEntries.size(); i++) + { + LatteDecompilerRemappedUniformEntry_t* entry = shader->list_remappedUniformEntries.data() + i; + if (entry->isRegister) + { + LatteFastAccessRemappedUniformEntry_register_t entryReg; + entryReg.indexOffset = entry->index * 16; + entryReg.mappedIndexOffset = entry->mappedIndex * 16; + shader->list_remappedUniformEntries_register.push_back(entryReg); + } + else + { + LatteFastAccessRemappedUniformEntry_buffer_t entryBuf; + uint32 kcacheBankIdOffset = entry->kcacheBankId* (7 * 4); + entryBuf.indexOffset = entry->index * 16; + entryBuf.mappedIndexOffset = entry->mappedIndex * 16; + // find or create buffer group + auto bufferGroup = std::find_if(shader->list_remappedUniformEntries_bufferGroups.begin(), shader->list_remappedUniformEntries_bufferGroups.end(), [kcacheBankIdOffset](const LatteDecompilerShader::_RemappedUniformBufferGroup& v) { return v.kcacheBankIdOffset == kcacheBankIdOffset; }); + if (bufferGroup != shader->list_remappedUniformEntries_bufferGroups.end()) + { + (*bufferGroup).entries.emplace_back(entryBuf); + } + else + { + shader->list_remappedUniformEntries_bufferGroups.emplace_back(kcacheBankIdOffset).entries.emplace_back(entryBuf); + } + } + } +} + +void _LatteDecompiler_Process(LatteDecompilerShaderContext* shaderContext, uint8* programData, uint32 programSize) +{ + // parse control flow instructions + if (shaderContext->shader->hasError == false) + LatteDecompiler_ParseCF(shaderContext, programData, programSize); + // parse individual clauses + if (shaderContext->shader->hasError == false) + LatteDecompiler_ParseClauses(shaderContext, programData, programSize); + // analyze + if (shaderContext->shader->hasError == false) + LatteDecompiler_analyze(shaderContext, shaderContext->shader); + if (shaderContext->shader->hasError == false) + LatteDecompiler_analyzeDataTypes(shaderContext); + // emit code + if (shaderContext->shader->hasError == false) + LatteDecompiler_emitGLSLShader(shaderContext, shaderContext->shader); + LatteDecompiler_cleanup(shaderContext); + // fast access + _LatteDecompiler_GenerateDataForFastAccess(shaderContext->shader); +} + +void LatteDecompiler_InitContext(LatteDecompilerShaderContext& dCtx, LatteDecompilerOutput_t* output, LatteConst::ShaderType shaderType, uint64 shaderBaseHash, uint32* contextRegisters) +{ + dCtx.output = output; + dCtx.shaderType = shaderType; + output->shaderType = shaderType; + dCtx.shaderBaseHash = shaderBaseHash; + dCtx.contextRegisters = contextRegisters; + dCtx.contextRegistersNew = (LatteContextRegister*)contextRegisters; +} + +void LatteDecompiler_DecompileVertexShader(uint64 shaderBaseHash, uint32* contextRegisters, uint8* programData, uint32 programSize, LatteFetchShader** fetchShaderList, sint32 fetchShaderCount, uint32* hleSpecialState, bool usesGeometryShader, LatteDecompilerOutput_t* output, bool useTFViaSSBO) +{ + cemu_assert_debug((programSize & 3) == 0); + performanceMonitor.gpuTime_shaderCreate.beginMeasuring(); + // prepare decompiler context + LatteDecompilerShaderContext shaderContext = { 0 }; + LatteDecompiler_InitContext(shaderContext, output, LatteConst::ShaderType::Vertex, shaderBaseHash, contextRegisters); + cemu_assert_debug(fetchShaderCount == 1); + for (sint32 i = 0; i < fetchShaderCount; i++) + { + shaderContext.fetchShaderList[i] = fetchShaderList[i]; + } + shaderContext.fetchShaderCount = fetchShaderCount; + // ugly hack to get tf mode from Vulkan renderer + shaderContext.useTFViaSSBO = useTFViaSSBO; + if (g_renderer->GetType() == RendererAPI::Vulkan) + { + shaderContext.useTFViaSSBO = VulkanRenderer::GetInstance()->useTFViaSSBO(); + } + // prepare shader (deprecated) + LatteDecompilerShader* shader = new LatteDecompilerShader(); + shader->shaderType = LatteConst::ShaderType::Vertex; + shader->compatibleFetchShader = shaderContext.fetchShaderList[0]; + shaderContext.shaderType = LatteConst::ShaderType::Vertex; + output->shaderType = LatteConst::ShaderType::Vertex; + shaderContext.shader = shader; + output->shader = shader; + shaderContext.usesGeometryShader = usesGeometryShader; + for (sint32 i = 0; i < LATTE_NUM_MAX_TEX_UNITS; i++) + { + shader->textureUnitSamplerAssignment[i] = LATTE_DECOMPILER_SAMPLER_NONE; + shader->textureUsesDepthCompare[i] = false; + } + // parse & compile + _LatteDecompiler_Process(&shaderContext, programData, programSize); + performanceMonitor.gpuTime_shaderCreate.endMeasuring(); +} + +void LatteDecompiler_DecompileGeometryShader(uint64 shaderBaseHash, uint32* contextRegisters, uint8* programData, uint32 programSize, uint8* gsCopyProgramData, uint32 gsCopyProgramSize, uint32* hleSpecialState, uint32 vsRingParameterCount, LatteDecompilerOutput_t* output, bool useTFViaSSBO) +{ + cemu_assert_debug((programSize & 3) == 0); + performanceMonitor.gpuTime_shaderCreate.beginMeasuring(); + // prepare decompiler context + LatteDecompilerShaderContext shaderContext = { 0 }; + shaderContext.fetchShaderCount = 0; + LatteDecompiler_InitContext(shaderContext, output, LatteConst::ShaderType::Geometry, shaderBaseHash, contextRegisters); + // prepare shader + LatteDecompilerShader* shader = new LatteDecompilerShader(); + shaderContext.output = output; + shader->shaderType = LatteConst::ShaderType::Geometry; + shader->ringParameterCountFromPrevStage = vsRingParameterCount; + shaderContext.shaderType = LatteConst::ShaderType::Geometry; + output->shaderType = LatteConst::ShaderType::Geometry; + shaderContext.shader = shader; + output->shader = shader; + shaderContext.usesGeometryShader = true; + if (gsCopyProgramData == NULL) + { + shader->hasError = true; + } + else + { + shaderContext.parsedGSCopyShader = LatteGSCopyShaderParser_parse(gsCopyProgramData, gsCopyProgramSize); + } + for (sint32 i = 0; i < LATTE_NUM_MAX_TEX_UNITS; i++) + { + shader->textureUnitSamplerAssignment[i] = LATTE_DECOMPILER_SAMPLER_NONE; + shader->textureUsesDepthCompare[i] = false; + } + // ugly hack to get tf mode from Vulkan renderer + shaderContext.useTFViaSSBO = useTFViaSSBO; + if (g_renderer->GetType() == RendererAPI::Vulkan) + { + shaderContext.useTFViaSSBO = VulkanRenderer::GetInstance()->useTFViaSSBO(); + } + // parse & compile + _LatteDecompiler_Process(&shaderContext, programData, programSize); + performanceMonitor.gpuTime_shaderCreate.endMeasuring(); +} + +void LatteDecompiler_DecompilePixelShader(uint64 shaderBaseHash, uint32* contextRegisters, uint8* programData, uint32 programSize, uint32* hleSpecialState, bool usesGeometryShader, LatteDecompilerOutput_t* output) +{ + cemu_assert_debug((programSize & 3) == 0); + performanceMonitor.gpuTime_shaderCreate.beginMeasuring(); + // prepare decompiler context + LatteDecompilerShaderContext shaderContext = { 0 }; + LatteDecompiler_InitContext(shaderContext, output, LatteConst::ShaderType::Pixel, shaderBaseHash, contextRegisters); + shaderContext.contextRegisters = contextRegisters; + // prepare shader + LatteDecompilerShader* shader = new LatteDecompilerShader(); + shaderContext.output = output; + shader->shaderType = LatteConst::ShaderType::Pixel; + shaderContext.shaderType = LatteConst::ShaderType::Pixel; + output->shaderType = LatteConst::ShaderType::Pixel; + shaderContext.shader = shader; + output->shader = shader; + shaderContext.usesGeometryShader = usesGeometryShader; + for (sint32 i = 0; i < LATTE_NUM_MAX_TEX_UNITS; i++) + { + shader->textureUnitSamplerAssignment[i] = LATTE_DECOMPILER_SAMPLER_NONE; + shader->textureUsesDepthCompare[i] = false; + } + // parse & compile + _LatteDecompiler_Process(&shaderContext, programData, programSize); + performanceMonitor.gpuTime_shaderCreate.endMeasuring(); +} + +void LatteDecompiler_cleanup(LatteDecompilerShaderContext* shaderContext) +{ + shaderContext->cfInstructions.clear(); +} diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h new file mode 100644 index 00000000..4f0d3ff2 --- /dev/null +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h @@ -0,0 +1,307 @@ +#pragma once +#include "Cafe/HW/Latte/Core/LatteConst.h" +#include "Cafe/HW/Latte/Renderer/RendererShader.h" + +namespace LatteDecompiler +{ + enum class DataType + { + UNDEFINED = 0, + U32 = 0, + S32 = 0, + FLOAT = 0 + }; +}; + +// decompiler shader types + +typedef struct +{ + bool isRegister; // if true -> Uniform register, if false -> Uniform buffer + uint8 kcacheBankId; // uniform buffer id (if uniform buffer) + uint32 index; // uniform address (in 4-DWORD tuples) + uint32 mappedIndex; // index in remapped uniform array +}LatteDecompilerRemappedUniformEntry_t; + +typedef struct +{ + uint32 indexOffset; // uniform address (in 4-DWORD tuples) + uint32 mappedIndexOffset; // index in remapped uniform array +}LatteFastAccessRemappedUniformEntry_register_t; + +typedef struct +{ + uint16 indexOffset; // uniform address (in 4-DWORD tuples) + uint16 mappedIndexOffset; // index in remapped uniform array +}LatteFastAccessRemappedUniformEntry_buffer_t; + +typedef struct +{ + uint32 texUnit; + sint32 uniformLocation; + float currentValue[2]; +}LatteUniformTextureScaleEntry_t; + +struct LatteDecompilerShaderResourceMapping +{ + LatteDecompilerShaderResourceMapping() + { + std::fill(textureUnitToBindingPoint, textureUnitToBindingPoint + LATTE_NUM_MAX_TEX_UNITS, -1); + std::fill(uniformBuffersBindingPoint, uniformBuffersBindingPoint + LATTE_NUM_MAX_UNIFORM_BUFFERS, -1); + std::fill(attributeMapping, attributeMapping + LATTE_NUM_MAX_ATTRIBUTE_LOCATIONS, -1); + } + static const sint8 UNUSED_BINDING = -1; + // most of this is for Vulkan + sint8 setIndex{}; + // texture + sint8 textureUnitToBindingPoint[LATTE_NUM_MAX_TEX_UNITS]; + // uniform buffer + sint8 uniformVarsBufferBindingPoint{}; // special block for uniform registers/remapped array/custom variables + sint8 uniformBuffersBindingPoint[LATTE_NUM_MAX_UNIFORM_BUFFERS]; + // shader storage buffer for transform feedback (if alternative mode is used) + sint8 tfStorageBindingPoint{-1}; + // attributes (vertex shader only) + sint8 attributeMapping[LATTE_NUM_MAX_ATTRIBUTE_LOCATIONS]; + + sint32 getTextureCount() + { + sint32 count = 0; + for (sint32 i = 0; i < LATTE_NUM_MAX_TEX_UNITS; i++) + { + if (textureUnitToBindingPoint[i] >= 0) + count++; + } + return count; + } + + sint32 getTextureUnitFromBindingPoint(sint8 bindingPoint) + { + for (sint32 i = 0; i < LATTE_NUM_MAX_TEX_UNITS; i++) + { + if (textureUnitToBindingPoint[i] == bindingPoint) + return i; + } + + cemu_assert_debug(false); + return -1; + } + + // returns -1 if no there is no texture binding point + sint32 getTextureBaseBindingPoint() + { + sint32 bindingPoint = 9999; + for (sint32 i = 0; i < LATTE_NUM_MAX_TEX_UNITS; i++) + { + if (textureUnitToBindingPoint[i] >= 0) + bindingPoint = std::min(bindingPoint, (sint32)textureUnitToBindingPoint[i]); + } + if (bindingPoint == 9999) + return -1; + return bindingPoint; + } + + bool getUniformBufferBindingRange(sint32& minBinding, sint32& maxBinding) + { + sint32 minBindingPoint = 9999; + sint32 maxBindingPoint = -9999; + if (uniformVarsBufferBindingPoint >= 0) + { + minBindingPoint = std::min(minBindingPoint, (sint32)uniformVarsBufferBindingPoint); + maxBindingPoint = std::max(maxBindingPoint, (sint32)uniformVarsBufferBindingPoint); + } + for (sint32 i = 0; i < LATTE_NUM_MAX_UNIFORM_BUFFERS; i++) + { + if (uniformBuffersBindingPoint[i] >= 0) + { + minBindingPoint = std::min(minBindingPoint, (sint32)uniformBuffersBindingPoint[i]); + maxBindingPoint = std::max(maxBindingPoint, (sint32)uniformBuffersBindingPoint[i]); + } + } + if (minBindingPoint == 9999) + return false; + minBinding = minBindingPoint; + maxBinding = maxBindingPoint; + return true; + } + + sint32 getTFStorageBufferBindingPoint() + { + return tfStorageBindingPoint; + } + + bool hasUniformBuffers() + { + for (sint32 i = 0; i < LATTE_NUM_MAX_UNIFORM_BUFFERS; i++) + { + if (uniformBuffersBindingPoint[i] >= 0) + return true; + } + return false; + } + + sint32 getAttribHostShaderIndex(uint32 semanticId) + { + cemu_assert_debug(semanticId < 0x100); + return attributeMapping[semanticId]; + } +}; + +struct LatteDecompilerShader +{ + LatteDecompilerShader* next; + LatteConst::ShaderType shaderType; + uint64 baseHash; + uint64 auxHash; + // vertex shader + struct LatteFetchShader* compatibleFetchShader{}; + // error tracking + bool hasError; // if set, the shader cannot be used + // optimized access / iteration + // list of uniform buffers used + uint8 uniformBufferList[LATTE_NUM_MAX_UNIFORM_BUFFERS]; + uint8 uniformBufferListCount; + // list of used texture units (faster access than iterating textureUnitMask) + uint8 textureUnitList[LATTE_NUM_MAX_TEX_UNITS]; + uint8 textureUnitListCount; + // input + Latte::E_DIM textureUnitDim[LATTE_NUM_MAX_TEX_UNITS]; // dimension of texture unit, from the currently set texture + bool textureIsIntegerFormat[LATTE_NUM_MAX_TEX_UNITS]{}; + // analyzer stage (uniforms) + uint8 uniformMode; // determines how uniforms are managed within the shader (see GPU7_DECOMPILER_UNIFORM_MODE_* constants) + uint64 uniformDataHash64[2]; // used to avoid redundant calls to glUniform* + std::vector<LatteDecompilerRemappedUniformEntry_t> list_remappedUniformEntries; + // analyzer stage (textures) + std::bitset<LATTE_NUM_MAX_TEX_UNITS> textureUnitMask2; + uint16 textureUnitSamplerAssignment[LATTE_NUM_MAX_TEX_UNITS]; // GPU7_SAMPLER_NONE means undefined + bool textureUsesDepthCompare[LATTE_NUM_MAX_TEX_UNITS]; + + // analyzer stage (pixel outputs) + uint32 pixelColorOutputMask; // from LSB to MSB, 1 bit per written output. 1 if written (indices of color attachments, may differ from export index inside the pixel shader) + // analyzer stage (geometry shader parameters/inputs) + uint32 ringParameterCount; + uint32 ringParameterCountFromPrevStage; // used in geometry shader to hold VS ringParameterCount + // analyzer stage (misc) + std::bitset<LATTE_NUM_STREAMOUT_BUFFER> streamoutBufferWriteMask2; + bool hasStreamoutBufferWrite; + // output code + class StringBuf* strBuf_shaderSource{nullptr}; + // separable shaders + RendererShader* shader; + bool isCustomShader; + + uint32 outputParameterMask; + // resource mapping (binding points) + LatteDecompilerShaderResourceMapping resourceMapping; + // uniforms + struct + { + sint32 loc_remapped; // uf_remappedVS/uf_remappedGS/uf_remappedPS + sint32 loc_uniformRegister; // uf_uniformRegisterVS/uf_uniformRegisterGS/uf_uniformRegisterPS + sint32 count_uniformRegister; + sint32 loc_windowSpaceToClipSpaceTransform; // uf_windowSpaceToClipSpaceTransform + sint32 loc_alphaTestRef; // uf_alphaTestRef + sint32 loc_pointSize; // uf_pointSize + sint32 loc_fragCoordScale; + std::vector<LatteUniformTextureScaleEntry_t> list_ufTexRescale; // list of mappings for uf_tex*Scale <-> uniform location + float ufCurrentValueAlphaTestRef; + float ufCurrentValueFragCoordScale[2]; + sint32 loc_verticesPerInstance; + sint32 loc_streamoutBufferBase[LATTE_NUM_STREAMOUT_BUFFER]; + sint32 uniformRangeSize; // entire size of uniform variable block + }uniform; + // fast access + struct _RemappedUniformBufferGroup + { + _RemappedUniformBufferGroup(uint32 _kcacheBankIdOffset) : kcacheBankIdOffset(_kcacheBankIdOffset) {}; + + uint32 kcacheBankIdOffset; + std::vector<LatteFastAccessRemappedUniformEntry_buffer_t> entries; + }; + std::vector<LatteFastAccessRemappedUniformEntry_register_t> list_remappedUniformEntries_register; + std::vector<_RemappedUniformBufferGroup> list_remappedUniformEntries_bufferGroups; +}; + +struct LatteDecompilerOutputUniformOffsets +{ + sint32 offset_remapped; + sint32 offset_uniformRegister; + sint32 count_uniformRegister; // in vec4 + sint32 offset_alphaTestRef; + sint32 offset_pointSize; + sint32 offset_fragCoordScale; + sint32 offset_windowSpaceToClipSpaceTransform; + sint32 offset_texScale[LATTE_NUM_MAX_TEX_UNITS]; + sint32 offset_verticesPerInstance{-1}; + sint32 offset_streamoutBufferBase[LATTE_NUM_STREAMOUT_BUFFER]{ -1, -1, -1, -1 }; + sint32 offset_endOfBlock; // stores size of uniform variable block + + LatteDecompilerOutputUniformOffsets() + { + offset_remapped = -1; + offset_uniformRegister = -1; + count_uniformRegister = 0; + offset_alphaTestRef = -1; + offset_pointSize = -1; + offset_fragCoordScale = -1; + offset_windowSpaceToClipSpaceTransform = -1; + for (sint32 i = 0; i < LATTE_NUM_MAX_TEX_UNITS; i++) + offset_texScale[i] = -1; + offset_endOfBlock = 0; + } +}; + +struct LatteDecompilerOutput_t +{ + LatteDecompilerShader* shader; + LatteConst::ShaderType shaderType; + + // texture info + std::bitset<LATTE_NUM_MAX_TEX_UNITS> textureUnitMask; + + // streamout info + std::bitset<LATTE_NUM_STREAMOUT_BUFFER> streamoutBufferWriteMask; + uint32 streamoutBufferStride[LATTE_NUM_STREAMOUT_BUFFER]{}; + + // uniform locations + LatteDecompilerOutputUniformOffsets uniformOffsetsGL; + LatteDecompilerOutputUniformOffsets uniformOffsetsVK; + // mapping and binding information + LatteDecompilerShaderResourceMapping resourceMappingGL; + LatteDecompilerShaderResourceMapping resourceMappingVK; +}; + +struct LatteDecompilerSubroutineInfo; + +void LatteDecompiler_DecompileVertexShader(uint64 shaderBaseHash, uint32* contextRegisters, uint8* programData, uint32 programSize, struct LatteFetchShader** fetchShaderList, sint32 fetchShaderCount, uint32* hleSpecialState, bool usesGeometryShader, LatteDecompilerOutput_t* output, bool useTFViaSSBO = false); +void LatteDecompiler_DecompileGeometryShader(uint64 shaderBaseHash, uint32* contextRegisters, uint8* programData, uint32 programSize, uint8* gsCopyProgramData, uint32 gsCopyProgramSize, uint32* hleSpecialState, uint32 vsRingParameterCount, LatteDecompilerOutput_t* output, bool useTFViaSSBO = false); +void LatteDecompiler_DecompilePixelShader(uint64 shaderBaseHash, uint32* contextRegisters, uint8* programData, uint32 programSize, uint32* hleSpecialState, bool usesGeometryShader, LatteDecompilerOutput_t* output); + +// specialized shader parsers + +#define GPU7_COPY_SHADER_MAX_PARAMS (32) + +struct LatteGSCopyShaderStreamWrite_t +{ + uint8 bufferIndex; + uint16 offset; // offset in ring buffer from GS + uint32 exportArrayBase; + uint32 memWriteArraySize; + uint32 memWriteCompMask; +}; + +struct LatteParsedGSCopyShader +{ + struct + { + uint16 offset; + uint16 gprIndex; + uint8 exportType; + uint8 exportParam; + }paramMapping[GPU7_COPY_SHADER_MAX_PARAMS]; + sint32 numParam; + // streamout writes + std::vector<LatteGSCopyShaderStreamWrite_t> list_streamWrites; +}; + +LatteParsedGSCopyShader* LatteGSCopyShaderParser_parse(uint8* programData, uint32 programSize); +bool LatteGSCopyShaderParser_getExportTypeByOffset(LatteParsedGSCopyShader* shaderContext, uint32 offset, uint32* exportType, uint32* exportParam); \ No newline at end of file diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp new file mode 100644 index 00000000..0131b34e --- /dev/null +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp @@ -0,0 +1,1094 @@ +#include "Cafe/HW/Latte/Core/LatteConst.h" +#include "Cafe/HW/Latte/Core/LatteShaderAssembly.h" +#include "Cafe/HW/Latte/ISA/RegDefines.h" +#include "Cafe/OS/libs/gx2/GX2.h" // todo - remove this dependency +#include "Cafe/HW/Latte/Core/Latte.h" +#include "Cafe/HW/Latte/Core/LatteDraw.h" +#include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h" +#include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInternal.h" +#include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInstructions.h" +#include "Cafe/HW/Latte/Core/FetchShader.h" +#include "Cafe/HW/Latte/Core/LatteShader.h" +#include "Cafe/HW/Latte/Renderer/Renderer.h" + +/* + * Return index of used color attachment based on shader pixel export index (0-7) + */ +sint32 LatteDecompiler_getColorOutputIndexFromExportIndex(LatteDecompilerShaderContext* shaderContext, sint32 exportIndex) +{ + sint32 colorOutputIndex = -1; + sint32 outputCounter = 0; + uint32 cbShaderMask = shaderContext->contextRegisters[mmCB_SHADER_MASK]; + uint32 cbShaderControl = shaderContext->contextRegisters[mmCB_SHADER_CONTROL]; + for(sint32 m=0; m<8; m++) + { + uint32 outputMask = (cbShaderMask>>(m*4))&0xF; + if( outputMask == 0 ) + continue; + cemu_assert_debug(outputMask == 0xF); // mask is unsupported + if( outputCounter == exportIndex ) + { + colorOutputIndex = m; + break; + } + outputCounter++; + } + cemu_assert_debug(colorOutputIndex != -1); // real outputs and outputs defined via mask do not match up + return colorOutputIndex; +} + +void _remapUniformAccess(LatteDecompilerShaderContext* shaderContext, bool isRegisterUniform, uint32 kcacheBankId, uint32 uniformIndex) +{ + auto& list_uniformMapping = shaderContext->shader->list_remappedUniformEntries; + for(uint32 i=0; i<list_uniformMapping.size(); i++) + { + LatteDecompilerRemappedUniformEntry_t* ufMapping = list_uniformMapping.data()+i; + if( isRegisterUniform ) + { + if( ufMapping->isRegister == true && ufMapping->index == uniformIndex ) + { + return; + } + } + else + { + if( ufMapping->isRegister == false && ufMapping->kcacheBankId == kcacheBankId && ufMapping->index == uniformIndex ) + { + return; + } + } + } + // add new mapping + LatteDecompilerRemappedUniformEntry_t newMapping = {0}; + if( isRegisterUniform ) + { + newMapping.isRegister = true; + newMapping.index = uniformIndex; + newMapping.mappedIndex = (uint32)list_uniformMapping.size(); + } + else + { + newMapping.isRegister = false; + newMapping.kcacheBankId = kcacheBankId; + newMapping.index = uniformIndex; + newMapping.mappedIndex = (uint32)list_uniformMapping.size(); + } + list_uniformMapping.emplace_back(newMapping); +} + +/* + * Checks for register collisions and marks the instructions accordingly + * startIndex is the first instruction of the group + * endIndex is inclusive the last instruction of the same group + */ +void _analyzeALUInstructionGroupForRegisterCollision(LatteDecompilerShaderContext* shaderContext, LatteDecompilerCFInstruction* cfInstruction, sint32 startIndex, sint32 endIndex) +{ + uint8 registerChannelWriteMask[(LATTE_NUM_GPR *4+7)/8] = {0}; + + struct + { + uint8 gprIndex; + uint8 channel; + }registerBackupEntries[5]; + sint32 registerBackupCount = 0; + + for(sint32 i=startIndex; i<=endIndex; i++) + { + LatteDecompilerALUInstruction& aluInstruction = cfInstruction->instructionsALU[i]; + // ignore NOP instruction + if( aluInstruction.isOP3 == false && aluInstruction.opcode == ALU_OP2_INST_NOP ) + continue; + if( aluInstruction.destElem > 3 ) + debugBreakpoint(); + registerChannelWriteMask[(aluInstruction.destGpr * 4 + aluInstruction.destElem) / 8] |= (1 << ((aluInstruction.destGpr * 4 + aluInstruction.destElem) % 8)); + // check if any previously written register is read + for(sint32 f=0; f<3; f++) + { + if( GPU7_ALU_SRC_IS_GPR(aluInstruction.sourceOperand[f].sel) == false ) + continue; + sint32 gprIndex = GPU7_ALU_SRC_GET_GPR_INDEX(aluInstruction.sourceOperand[f].sel); + if( aluInstruction.sourceOperand[f].chan > 3 ) + debugBreakpoint(); + if( (registerChannelWriteMask[(gprIndex*4+aluInstruction.sourceOperand[f].chan)/8]&(1<<((gprIndex*4+aluInstruction.sourceOperand[f].chan)%8))) != 0 ) + { + // register is overwritten by same or previous instruction, mark register backup for this instruction + // check if this register already has a backup + bool hasBackup = false; + for(sint32 t=0; t<registerBackupCount; t++) + { + if( (sint32)registerBackupEntries[t].gprIndex == gprIndex && registerBackupEntries[t].channel == aluInstruction.sourceOperand[f].chan ) + { + aluInstruction.sourceOperand[f].requiredRegisterBackup = true; + aluInstruction.sourceOperand[f].registerBackupIndex = t; + hasBackup = true; + break; + } + } + if( hasBackup == false ) + { + // add new entry + if( registerBackupCount < sizeof(registerBackupEntries)/sizeof(registerBackupEntries[0]) ) + { + // add entry + registerBackupEntries[registerBackupCount].gprIndex = gprIndex; + registerBackupEntries[registerBackupCount].channel = aluInstruction.sourceOperand[f].chan; + registerBackupCount++; + // mark operand for backup + aluInstruction.sourceOperand[f].requiredRegisterBackup = true; + aluInstruction.sourceOperand[f].registerBackupIndex = registerBackupCount-1; + } + else + debugBreakpoint(); + } + } + } + } +} + +/* + * Returns true if the instruction takes integer operands or returns a integer value + */ +bool _isIntegerInstruction(const LatteDecompilerALUInstruction& aluInstruction) +{ + if (aluInstruction.isOP3 == false) + { + // OP2 + switch (aluInstruction.opcode) + { + case ALU_OP2_INST_ADD: + case ALU_OP2_INST_MUL: + case ALU_OP2_INST_MUL_IEEE: + case ALU_OP2_INST_MAX: + case ALU_OP2_INST_MIN: + case ALU_OP2_INST_FLOOR: + case ALU_OP2_INST_FRACT: + case ALU_OP2_INST_TRUNC: + case ALU_OP2_INST_MOV: + case ALU_OP2_INST_NOP: + case ALU_OP2_INST_DOT4: + case ALU_OP2_INST_DOT4_IEEE: + case ALU_OP2_INST_CUBE: + case ALU_OP2_INST_EXP_IEEE: + case ALU_OP2_INST_LOG_CLAMPED: + case ALU_OP2_INST_LOG_IEEE: + case ALU_OP2_INST_SQRT_IEEE: + case ALU_OP2_INST_SIN: + case ALU_OP2_INST_COS: + case ALU_OP2_INST_RNDNE: + case ALU_OP2_INST_MAX_DX10: + case ALU_OP2_INST_SETGT: + case ALU_OP2_INST_SETGE: + case ALU_OP2_INST_SETNE: + case ALU_OP2_INST_SETE: + case ALU_OP2_INST_PRED_SETE: + case ALU_OP2_INST_PRED_SETGT: + case ALU_OP2_INST_PRED_SETGE: + case ALU_OP2_INST_PRED_SETNE: + case ALU_OP2_INST_KILLE: + case ALU_OP2_INST_KILLGT: + case ALU_OP2_INST_KILLGE: + case ALU_OP2_INST_RECIP_FF: + case ALU_OP2_INST_RECIP_IEEE: + case ALU_OP2_INST_RECIPSQRT_CLAMPED: + case ALU_OP2_INST_RECIPSQRT_FF: + case ALU_OP2_INST_RECIPSQRT_IEEE: + return false; + case ALU_OP2_INST_FLT_TO_INT: + case ALU_OP2_INST_INT_TO_FLOAT: + case ALU_OP2_INST_UINT_TO_FLOAT: + case ALU_OP2_INST_ASHR_INT: + case ALU_OP2_INST_LSHR_INT: + case ALU_OP2_INST_LSHL_INT: + case ALU_OP2_INST_MULLO_INT: + case ALU_OP2_INST_MULLO_UINT: + case ALU_OP2_INST_FLT_TO_UINT: + case ALU_OP2_INST_AND_INT: + case ALU_OP2_INST_OR_INT: + case ALU_OP2_INST_XOR_INT: + case ALU_OP2_INST_NOT_INT: + case ALU_OP2_INST_ADD_INT: + case ALU_OP2_INST_SUB_INT: + case ALU_OP2_INST_MAX_INT: + case ALU_OP2_INST_MIN_INT: + case ALU_OP2_INST_SETE_INT: + case ALU_OP2_INST_SETGT_INT: + case ALU_OP2_INST_SETGE_INT: + case ALU_OP2_INST_SETNE_INT: + case ALU_OP2_INST_SETGT_UINT: + case ALU_OP2_INST_SETGE_UINT: + case ALU_OP2_INST_PRED_SETE_INT: + case ALU_OP2_INST_PRED_SETGT_INT: + case ALU_OP2_INST_PRED_SETGE_INT: + case ALU_OP2_INST_PRED_SETNE_INT: + case ALU_OP2_INST_KILLE_INT: + case ALU_OP2_INST_KILLGT_INT: + case ALU_OP2_INST_KILLNE_INT: + case ALU_OP2_INST_MOVA_FLOOR: + case ALU_OP2_INST_MOVA_INT: + return true; + // these return an integer result but are usually used only for conditionals + case ALU_OP2_INST_SETE_DX10: + case ALU_OP2_INST_SETGT_DX10: + case ALU_OP2_INST_SETGE_DX10: + case ALU_OP2_INST_SETNE_DX10: + return true; + default: +#ifndef PUBLIC_RELEASE + debug_printf("_isIntegerInstruction(): OP3=%s opcode=%02x\n", aluInstruction.isOP3 ? "true" : "false", aluInstruction.opcode); + cemu_assert_debug(false); +#endif + break; + } + } + else + { + // OP3 + switch (aluInstruction.opcode) + { + case ALU_OP3_INST_MULADD: + case ALU_OP3_INST_MULADD_D2: + case ALU_OP3_INST_MULADD_M2: + case ALU_OP3_INST_MULADD_M4: + case ALU_OP3_INST_MULADD_IEEE: + case ALU_OP3_INST_CMOVE: + case ALU_OP3_INST_CMOVGT: + case ALU_OP3_INST_CMOVGE: + return false; + case ALU_OP3_INST_CNDE_INT: + case ALU_OP3_INST_CNDGT_INT: + case ALU_OP3_INST_CMOVGE_INT: + return true; + default: +#ifndef PUBLIC_RELEASE + debug_printf("_isIntegerInstruction(): OP3=%s opcode=%02x\n", aluInstruction.isOP3?"true":"false", aluInstruction.opcode); +#endif + break; + } + } + return false; +} + +/* + * Analyze ALU CF instruction and all instructions within the ALU clause + */ +void LatteDecompiler_analyzeALUClause(LatteDecompilerShaderContext* shaderContext, LatteDecompilerCFInstruction* cfInstruction) +{ + // check if this shader has any clause that potentially modifies the pixel execution state + if( cfInstruction->type == GPU7_CF_INST_ALU_PUSH_BEFORE || cfInstruction->type == GPU7_CF_INST_ALU_POP_AFTER || cfInstruction->type == GPU7_CF_INST_ALU_POP2_AFTER || cfInstruction->type == GPU7_CF_INST_ALU_BREAK || cfInstruction->type == GPU7_CF_INST_ALU_ELSE_AFTER ) + { + shaderContext->analyzer.modifiesPixelActiveState = true; + } + // analyze ALU instructions + for(auto& aluInstruction : cfInstruction->instructionsALU) + { + // ignore NOP instruction + if( aluInstruction.isOP3 == false && aluInstruction.opcode == ALU_OP2_INST_NOP ) + continue; + // check for CUBE instruction + if( aluInstruction.isOP3 == false && aluInstruction.opcode == ALU_OP2_INST_CUBE ) + { + shaderContext->analyzer.hasRedcCUBE = true; + } + // check for integer instruction + if (_isIntegerInstruction(aluInstruction)) + shaderContext->analyzer.usesIntegerValues = true; + // process all available operands (inputs) + for(sint32 f=0; f<3; f++) + { + // check input for uniform access + if( aluInstruction.sourceOperand[f].sel == 0xFFFFFFFF ) + continue; // source operand not set/used + if( GPU7_ALU_SRC_IS_CFILE(aluInstruction.sourceOperand[f].sel) ) + { + // uniform register access + + // relative register file accesses are tricky because the range of possible indices is unknown + // worst case we have to load the full file (256 * 16 byte entries) + // but here we track all access indices so the analyzer can make guesstimates about the actual size when there are relative accesses + + shaderContext->analyzer.uniformRegisterAccess = true; + if (aluInstruction.sourceOperand[f].rel) + { + shaderContext->analyzer.uniformRegisterDynamicAccess = true; + shaderContext->analyzer.uniformRegisterAccessIndices.emplace_back(GPU7_ALU_SRC_GET_CFILE_INDEX(aluInstruction.sourceOperand[f].sel), true); + } + else + { + _remapUniformAccess(shaderContext, true, 0, GPU7_ALU_SRC_GET_CFILE_INDEX(aluInstruction.sourceOperand[f].sel)); + shaderContext->analyzer.uniformRegisterAccessIndices.emplace_back(GPU7_ALU_SRC_GET_CFILE_INDEX(aluInstruction.sourceOperand[f].sel), false); + } + } + else if( GPU7_ALU_SRC_IS_CBANK0(aluInstruction.sourceOperand[f].sel) ) + { + // uniform bank 0 (uniform buffer with index cfInstruction->cBank0Index) + uint32 uniformBufferIndex = cfInstruction->cBank0Index; + if( uniformBufferIndex >= LATTE_NUM_MAX_UNIFORM_BUFFERS) + debugBreakpoint(); + shaderContext->analyzer.uniformBufferAccessMask |= (1<<uniformBufferIndex); + if( aluInstruction.sourceOperand[f].rel ) + shaderContext->analyzer.uniformBufferDynamicAccessMask |= (1<<uniformBufferIndex); + _remapUniformAccess(shaderContext, false, uniformBufferIndex, GPU7_ALU_SRC_GET_CBANK0_INDEX(aluInstruction.sourceOperand[f].sel)+cfInstruction->cBank0AddrBase); + } + else if( GPU7_ALU_SRC_IS_CBANK1(aluInstruction.sourceOperand[f].sel) ) + { + // uniform bank 1 (uniform buffer with index cfInstruction->cBank1Index) + uint32 uniformBufferIndex = cfInstruction->cBank1Index; + if( uniformBufferIndex >= LATTE_NUM_MAX_UNIFORM_BUFFERS) + debugBreakpoint(); + shaderContext->analyzer.uniformBufferAccessMask |= (1<<uniformBufferIndex); + if( aluInstruction.sourceOperand[f].rel ) + shaderContext->analyzer.uniformBufferDynamicAccessMask |= (1<<uniformBufferIndex); + _remapUniformAccess(shaderContext, false, uniformBufferIndex, GPU7_ALU_SRC_GET_CBANK1_INDEX(aluInstruction.sourceOperand[f].sel)+cfInstruction->cBank1AddrBase); + } + else if( GPU7_ALU_SRC_IS_GPR(aluInstruction.sourceOperand[f].sel) ) + { + sint32 gprIndex = GPU7_ALU_SRC_GET_GPR_INDEX(aluInstruction.sourceOperand[f].sel); + shaderContext->analyzer.gprUseMask[gprIndex/8] |= (1<<(gprIndex%8)); + if( aluInstruction.sourceOperand[f].rel != 0 ) + { + // if indexed register access is used, all possibly referenced registers are stored to a separate array at the beginning of the group + shaderContext->analyzer.usesRelativeGPRRead = true; + continue; + } + + } + } + if( aluInstruction.destRel != 0 ) + { + shaderContext->analyzer.usesRelativeGPRWrite = true; + } + shaderContext->analyzer.gprUseMask[aluInstruction.destGpr/8] |= (1<<(aluInstruction.destGpr%8)); + } + // check for register collisions inside instruction groups (registers that are overwritten while being read) + sint32 currentGroupIndex = 0; + sint32 currentGroupStartIndex = 0; + for(uint32 i=0; i<cfInstruction->instructionsALU.size(); i++) + { + LatteDecompilerALUInstruction& aluInstruction = cfInstruction->instructionsALU[i]; + if( aluInstruction.instructionGroupIndex != currentGroupIndex ) + { + cemu_assert_debug(i != 0); // first group cant end at first instruction + _analyzeALUInstructionGroupForRegisterCollision(shaderContext, cfInstruction, currentGroupStartIndex, i-1); + // start next group + currentGroupIndex = aluInstruction.instructionGroupIndex; + currentGroupStartIndex = i; + } + } + if( currentGroupStartIndex < (sint32)cfInstruction->instructionsALU.size() ) + { + _analyzeALUInstructionGroupForRegisterCollision(shaderContext, cfInstruction, currentGroupStartIndex, (uint32)cfInstruction->instructionsALU.size()-1); + } +} + +// analyze TEX CF instruction and all instructions within the TEX clause +void LatteDecompiler_analyzeTEXClause(LatteDecompilerShaderContext* shaderContext, LatteDecompilerCFInstruction* cfInstruction) +{ + LatteDecompilerShader* shader = shaderContext->shader; + for(auto& texInstruction : cfInstruction->instructionsTEX) + { + if( texInstruction.opcode == GPU7_TEX_INST_SAMPLE || + texInstruction.opcode == GPU7_TEX_INST_SAMPLE_L || + texInstruction.opcode == GPU7_TEX_INST_SAMPLE_LB || + texInstruction.opcode == GPU7_TEX_INST_SAMPLE_LZ || + texInstruction.opcode == GPU7_TEX_INST_SAMPLE_C || + texInstruction.opcode == GPU7_TEX_INST_SAMPLE_C_L || + texInstruction.opcode == GPU7_TEX_INST_SAMPLE_C_LZ || + texInstruction.opcode == GPU7_TEX_INST_FETCH4 || + texInstruction.opcode == GPU7_TEX_INST_SAMPLE_G || + texInstruction.opcode == GPU7_TEX_INST_LD ) + { + if (texInstruction.textureFetch.textureIndex < 0 || texInstruction.textureFetch.textureIndex >= LATTE_NUM_MAX_TEX_UNITS) + { + forceLogDebug_printf("Shader %llx has out of bounds texture access (texture %d)", shaderContext->shader->baseHash, (sint32)texInstruction.textureFetch.textureIndex); + continue; + } + if( texInstruction.textureFetch.samplerIndex < 0 || texInstruction.textureFetch.samplerIndex >= 0x12 ) + cemu_assert_debug(false); + if(shaderContext->output->textureUnitMask[texInstruction.textureFetch.textureIndex] && shader->textureUnitSamplerAssignment[texInstruction.textureFetch.textureIndex] != texInstruction.textureFetch.samplerIndex && shader->textureUnitSamplerAssignment[texInstruction.textureFetch.textureIndex] != LATTE_DECOMPILER_SAMPLER_NONE ) + { + cemu_assert_debug(false); + } + shaderContext->output->textureUnitMask[texInstruction.textureFetch.textureIndex] = true; + shader->textureUnitSamplerAssignment[texInstruction.textureFetch.textureIndex] = texInstruction.textureFetch.samplerIndex; + if( texInstruction.opcode == GPU7_TEX_INST_SAMPLE_C || texInstruction.opcode == GPU7_TEX_INST_SAMPLE_C_L || texInstruction.opcode == GPU7_TEX_INST_SAMPLE_C_LZ) + shader->textureUsesDepthCompare[texInstruction.textureFetch.textureIndex] = true; + + bool useTexelCoords = false; + if (texInstruction.opcode == GPU7_TEX_INST_SAMPLE && (texInstruction.textureFetch.unnormalized[0] && texInstruction.textureFetch.unnormalized[1] && texInstruction.textureFetch.unnormalized[2] && texInstruction.textureFetch.unnormalized[3])) + useTexelCoords = true; + else if (texInstruction.opcode == GPU7_TEX_INST_LD) + useTexelCoords = true; + if (useTexelCoords) + { + shaderContext->analyzer.texUnitUsesTexelCoordinates.set(texInstruction.textureFetch.textureIndex); + } + } + else if( texInstruction.opcode == GPU7_TEX_INST_GET_COMP_TEX_LOD || texInstruction.opcode == GPU7_TEX_INST_GET_TEXTURE_RESINFO ) + { + if( texInstruction.textureFetch.textureIndex < 0 || texInstruction.textureFetch.textureIndex >= LATTE_NUM_MAX_TEX_UNITS ) + debugBreakpoint(); + if( texInstruction.textureFetch.samplerIndex != 0 ) + debugBreakpoint(); // sampler is ignored and should be 0 + shaderContext->output->textureUnitMask[texInstruction.textureFetch.textureIndex] = true; + } + else if( texInstruction.opcode == GPU7_TEX_INST_SET_CUBEMAP_INDEX ) + { + // no analysis required + } + else if (texInstruction.opcode == GPU7_TEX_INST_GET_GRADIENTS_H || texInstruction.opcode == GPU7_TEX_INST_GET_GRADIENTS_V) + { + // no analysis required + } + else if (texInstruction.opcode == GPU7_TEX_INST_SET_GRADIENTS_H || texInstruction.opcode == GPU7_TEX_INST_SET_GRADIENTS_V) + { + shaderContext->analyzer.hasGradientLookup = true; + } + else if( texInstruction.opcode == GPU7_TEX_INST_VFETCH ) + { + // VFETCH is used to access uniform buffers dynamically + if( texInstruction.textureFetch.textureIndex >= 0x80 && texInstruction.textureFetch.textureIndex <= 0x8F ) + { + uint32 uniformBufferIndex = texInstruction.textureFetch.textureIndex - 0x80; + shaderContext->analyzer.uniformBufferAccessMask |= (1<<uniformBufferIndex); + shaderContext->analyzer.uniformBufferDynamicAccessMask |= (1<<uniformBufferIndex); + } + else if( texInstruction.textureFetch.textureIndex == 0x9F && shader->shaderType == LatteConst::ShaderType::Geometry ) + { + // instruction to read geometry shader input from ringbuffer + } + else + debugBreakpoint(); + } + else if (texInstruction.opcode == GPU7_TEX_INST_MEM) + { + // SSBO access + shaderContext->analyzer.hasSSBORead = true; + } + else + debugBreakpoint(); + // mark read and written registers as used + if(texInstruction.dstGpr < LATTE_NUM_GPR) + shaderContext->analyzer.gprUseMask[texInstruction.dstGpr/8] |= (1<<(texInstruction.dstGpr%8)); + if(texInstruction.srcGpr < LATTE_NUM_GPR) + shaderContext->analyzer.gprUseMask[texInstruction.srcGpr/8] |= (1<<(texInstruction.srcGpr%8)); + } +} + +/* + * Analyze export CF instruction + */ +void LatteDecompiler_analyzeExport(LatteDecompilerShaderContext* shaderContext, LatteDecompilerCFInstruction* cfInstruction) +{ + LatteDecompilerShader* shader = shaderContext->shader; + if( shader->shaderType == LatteConst::ShaderType::Pixel ) + { + if( cfInstruction->exportType == 0 && cfInstruction->exportArrayBase < 8 ) + { + // remember color outputs that are written + for(uint32 i=0; i<(cfInstruction->exportBurstCount+1); i++) + { + sint32 colorOutputIndex = LatteDecompiler_getColorOutputIndexFromExportIndex(shaderContext, cfInstruction->exportArrayBase+i); + shader->pixelColorOutputMask |= (1<<colorOutputIndex); + } + } + else if( cfInstruction->exportType == 0 && cfInstruction->exportArrayBase == 61 ) + { + // writes pixel depth + } + else + debugBreakpoint(); + } + else if (shader->shaderType == LatteConst::ShaderType::Vertex) + { + if (cfInstruction->exportType == 2 && cfInstruction->exportArrayBase < 32) + { + shaderContext->shader->outputParameterMask |= (1<<cfInstruction->exportArrayBase); + } + else if (cfInstruction->exportType == 1 && cfInstruction->exportArrayBase == GPU7_DECOMPILER_CF_EXPORT_POINT_SIZE) + { + shaderContext->analyzer.writesPointSize = true; + } + } + // mark input GPRs as used + for(uint32 i=0; i<(cfInstruction->exportBurstCount+1); i++) + { + shaderContext->analyzer.gprUseMask[(cfInstruction->exportSourceGPR+i)/8] |= (1<<((cfInstruction->exportSourceGPR+i)%8)); + } +} + +void LatteDecompiler_analyzeSubroutine(LatteDecompilerShaderContext* shaderContext, uint32 cfAddr) +{ + // analyze CF and clauses up to RET statement + + // todo - find cfInstruction index from cfAddr + cemu_assert_debug(false); + + for(auto& cfInstruction : shaderContext->cfInstructions) + { + if (cfInstruction.type == GPU7_CF_INST_ALU || cfInstruction.type == GPU7_CF_INST_ALU_PUSH_BEFORE || cfInstruction.type == GPU7_CF_INST_ALU_POP_AFTER || cfInstruction.type == GPU7_CF_INST_ALU_POP2_AFTER || cfInstruction.type == GPU7_CF_INST_ALU_BREAK || cfInstruction.type == GPU7_CF_INST_ALU_ELSE_AFTER) + { + LatteDecompiler_analyzeALUClause(shaderContext, &cfInstruction); + } + else if (cfInstruction.type == GPU7_CF_INST_TEX) + { + LatteDecompiler_analyzeTEXClause(shaderContext, &cfInstruction); + } + else if (cfInstruction.type == GPU7_CF_INST_EXPORT || cfInstruction.type == GPU7_CF_INST_EXPORT_DONE) + { + LatteDecompiler_analyzeExport(shaderContext, &cfInstruction); + } + else if (cfInstruction.type == GPU7_CF_INST_ELSE || cfInstruction.type == GPU7_CF_INST_POP) + { + shaderContext->analyzer.modifiesPixelActiveState = true; + } + else if (cfInstruction.type == GPU7_CF_INST_LOOP_START_DX10 || cfInstruction.type == GPU7_CF_INST_LOOP_END) + { + shaderContext->analyzer.modifiesPixelActiveState = true; + } + else if (cfInstruction.type == GPU7_CF_INST_LOOP_BREAK) + { + shaderContext->analyzer.modifiesPixelActiveState = true; + } + else if (cfInstruction.type == GPU7_CF_INST_EMIT_VERTEX) + { + // nothing to analyze + } + else if (cfInstruction.type == GPU7_CF_INST_CALL) + { + cemu_assert_debug(false); // CALLs inside subroutines are still todo + } + else + { + cemu_assert_unimplemented(); + } + } +} + +namespace LatteDecompiler +{ + void _initTextureBindingPointsGL(LatteDecompilerShaderContext* decompilerContext) + { + // for OpenGL we use the relative texture unit index + for (sint32 i = 0; i < LATTE_NUM_MAX_TEX_UNITS; i++) + { + if (!decompilerContext->output->textureUnitMask[i]) + continue; + sint32 textureBindingPoint; + if (decompilerContext->shaderType == LatteConst::ShaderType::Vertex) + textureBindingPoint = i + CEMU_VS_TEX_UNIT_BASE; + else if (decompilerContext->shaderType == LatteConst::ShaderType::Geometry) + textureBindingPoint = i + CEMU_GS_TEX_UNIT_BASE; + else if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel) + textureBindingPoint = i + CEMU_PS_TEX_UNIT_BASE; + + decompilerContext->output->resourceMappingGL.textureUnitToBindingPoint[i] = textureBindingPoint; + } + } + + void _initTextureBindingPointsVK(LatteDecompilerShaderContext* decompilerContext) + { + // for Vulkan we use consecutive indices + for (sint32 i = 0; i < LATTE_NUM_MAX_TEX_UNITS; i++) + { + if (!decompilerContext->output->textureUnitMask[i]) + continue; + decompilerContext->output->resourceMappingVK.textureUnitToBindingPoint[i] = decompilerContext->currentBindingPointVK; + decompilerContext->currentBindingPointVK++; + } + } + + void _initHasUniformVarBlock(LatteDecompilerShaderContext* decompilerContext) + { + decompilerContext->hasUniformVarBlock = false; + if (decompilerContext->shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_REMAPPED) + decompilerContext->hasUniformVarBlock = true; + else if (decompilerContext->shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CFILE) + decompilerContext->hasUniformVarBlock = true; + + bool hasAnyViewportScaleDisabled = + !decompilerContext->contextRegistersNew->PA_CL_VTE_CNTL.get_VPORT_X_SCALE_ENA() || + !decompilerContext->contextRegistersNew->PA_CL_VTE_CNTL.get_VPORT_Y_SCALE_ENA() || + !decompilerContext->contextRegistersNew->PA_CL_VTE_CNTL.get_VPORT_Z_SCALE_ENA(); + // we currently only support all on/off. Individual component scaling is not supported + cemu_assert_debug(decompilerContext->contextRegistersNew->PA_CL_VTE_CNTL.get_VPORT_X_SCALE_ENA() == !hasAnyViewportScaleDisabled); + cemu_assert_debug(decompilerContext->contextRegistersNew->PA_CL_VTE_CNTL.get_VPORT_Y_SCALE_ENA() == !hasAnyViewportScaleDisabled); + cemu_assert_debug(decompilerContext->contextRegistersNew->PA_CL_VTE_CNTL.get_VPORT_Z_SCALE_ENA() == !hasAnyViewportScaleDisabled); + cemu_assert_debug(decompilerContext->contextRegistersNew->PA_CL_VTE_CNTL.get_VPORT_X_OFFSET_ENA() == !hasAnyViewportScaleDisabled); + cemu_assert_debug(decompilerContext->contextRegistersNew->PA_CL_VTE_CNTL.get_VPORT_Y_OFFSET_ENA() == !hasAnyViewportScaleDisabled); + cemu_assert_debug(decompilerContext->contextRegistersNew->PA_CL_VTE_CNTL.get_VPORT_Z_OFFSET_ENA() == !hasAnyViewportScaleDisabled); + + if (decompilerContext->shaderType == LatteConst::ShaderType::Vertex && hasAnyViewportScaleDisabled) + decompilerContext->hasUniformVarBlock = true; // uf_windowSpaceToClipSpaceTransform + bool alphaTestEnable = decompilerContext->contextRegistersNew->SX_ALPHA_TEST_CONTROL.get_ALPHA_TEST_ENABLE(); + if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel && alphaTestEnable != 0) + decompilerContext->hasUniformVarBlock = true; // uf_alphaTestRef + if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel) + decompilerContext->hasUniformVarBlock = true; // uf_fragCoordScale + if (decompilerContext->shaderType == LatteConst::ShaderType::Vertex && decompilerContext->analyzer.outputPointSize && decompilerContext->analyzer.writesPointSize == false) + decompilerContext->hasUniformVarBlock = true; // uf_pointSize + if (decompilerContext->shaderType == LatteConst::ShaderType::Geometry && decompilerContext->analyzer.outputPointSize && decompilerContext->analyzer.writesPointSize == false) + decompilerContext->hasUniformVarBlock = true; // uf_pointSize + if (decompilerContext->analyzer.useSSBOForStreamout && + (decompilerContext->shaderType == LatteConst::ShaderType::Vertex && decompilerContext->usesGeometryShader == false) || + (decompilerContext->shaderType == LatteConst::ShaderType::Geometry)) + { + decompilerContext->hasUniformVarBlock = true; // uf_verticesPerInstance and uf_streamoutBufferBase* + } + } + + void _initUniformBindingPoints(LatteDecompilerShaderContext* decompilerContext) + { + // check if uniform vars block has at least one variable + _initHasUniformVarBlock(decompilerContext); + + if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel) + { + for (sint32 t = 0; t < LATTE_NUM_MAX_TEX_UNITS; t++) + { + if (decompilerContext->analyzer.texUnitUsesTexelCoordinates.test(t) == false) + continue; + decompilerContext->hasUniformVarBlock = true; // uf_tex%dScale + } + } + // assign binding point to uniform var block + decompilerContext->output->resourceMappingGL.uniformVarsBufferBindingPoint = -1; // OpenGL currently doesnt use a uniform block + if (decompilerContext->hasUniformVarBlock) + { + decompilerContext->output->resourceMappingVK.uniformVarsBufferBindingPoint = decompilerContext->currentBindingPointVK; + decompilerContext->currentBindingPointVK++; + } + else + decompilerContext->output->resourceMappingVK.uniformVarsBufferBindingPoint = -1; + // assign binding points to uniform buffers + if (decompilerContext->shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK) + { + // for Vulkan we use consecutive indices + for (uint32 i = 0; i < LATTE_NUM_MAX_UNIFORM_BUFFERS; i++) + { + if ((decompilerContext->analyzer.uniformBufferAccessMask&(1 << i)) == 0) + continue; + sint32 uniformBindingPoint = i; + if (decompilerContext->shaderType == LatteConst::ShaderType::Geometry) + uniformBindingPoint += 64; + else if (decompilerContext->shaderType == LatteConst::ShaderType::Vertex) + uniformBindingPoint += 0; + else if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel) + uniformBindingPoint += 32; + + decompilerContext->output->resourceMappingVK.uniformBuffersBindingPoint[i] = decompilerContext->currentBindingPointVK; + decompilerContext->currentBindingPointVK++; + } + // for OpenGL we use the relative buffer index + for (uint32 i = 0; i < LATTE_NUM_MAX_UNIFORM_BUFFERS; i++) + { + if ((decompilerContext->analyzer.uniformBufferAccessMask&(1 << i)) == 0) + continue; + sint32 uniformBindingPoint = i; + if (decompilerContext->shaderType == LatteConst::ShaderType::Geometry) + uniformBindingPoint += 64; + else if (decompilerContext->shaderType == LatteConst::ShaderType::Vertex) + uniformBindingPoint += 0; + else if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel) + uniformBindingPoint += 32; + decompilerContext->output->resourceMappingGL.uniformBuffersBindingPoint[i] = uniformBindingPoint; + } + } + // shader storage buffer for alternative transform feedback path + if (decompilerContext->analyzer.useSSBOForStreamout) + { + decompilerContext->output->resourceMappingVK.tfStorageBindingPoint = decompilerContext->currentBindingPointVK; + decompilerContext->currentBindingPointVK++; + } + } + + void _initAttributeBindingPoints(LatteDecompilerShaderContext* decompilerContext) + { + if (decompilerContext->shaderType != LatteConst::ShaderType::Vertex) + return; + // create input attribute binding mapping + // OpenGL and Vulkan use consecutive indices starting at 0 + sint8 bindingIndex = 0; + for (sint32 i = 0; i < LATTE_NUM_MAX_ATTRIBUTE_LOCATIONS; i++) + { + if (decompilerContext->analyzer.inputAttributSemanticMask[i]) + { + decompilerContext->output->resourceMappingGL.attributeMapping[i] = bindingIndex; + decompilerContext->output->resourceMappingVK.attributeMapping[i] = bindingIndex; + bindingIndex++; + } + } + } + +} + +/* + * Analyze the shader program + * This will help to determine: + * 1) Uniform usage + * 2) Texture usage + * 3) Data types + * 4) CF stack and execution flow + */ +void LatteDecompiler_analyze(LatteDecompilerShaderContext* shaderContext, LatteDecompilerShader* shader) +{ + // analyze render state + shaderContext->analyzer.isPointsPrimitive = shaderContext->contextRegistersNew->VGT_PRIMITIVE_TYPE.get_PRIMITIVE_MODE() == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::POINTS; + shaderContext->analyzer.hasStreamoutEnable = shaderContext->contextRegisters[mmVGT_STRMOUT_EN] != 0; // set if the shader is used for transform feedback operations + if (shaderContext->shaderType == LatteConst::ShaderType::Vertex && shaderContext->usesGeometryShader == false) + shaderContext->analyzer.outputPointSize = shaderContext->analyzer.isPointsPrimitive; + else if (shaderContext->shaderType == LatteConst::ShaderType::Geometry) + { + uint32 gsOutPrimType = shaderContext->contextRegisters[mmVGT_GS_OUT_PRIM_TYPE]; + if (gsOutPrimType == 0) // points + shaderContext->analyzer.outputPointSize = true; + } + // analyze input attributes for vertex/geometry shader + if (shader->shaderType == LatteConst::ShaderType::Vertex || shader->shaderType == LatteConst::ShaderType::Geometry) + { + for (sint32 f = 0; f < shaderContext->fetchShaderCount; f++) + { + LatteFetchShader* parsedFetchShader = (LatteFetchShader*)shaderContext->fetchShaderList[f]; + + for(auto& bufferGroup : parsedFetchShader->bufferGroups) + { + for (sint32 i = 0; i < bufferGroup.attribCount; i++) + { + uint8 semanticId = bufferGroup.attrib[i].semanticId; + if (semanticId == 0xFF) + { + // unused attribute? Found in Hot Wheels: World's best driver + continue; + } + cemu_assert_debug(semanticId < 0x80); + shaderContext->analyzer.inputAttributSemanticMask[semanticId] = true; + } + } + } + } + // list of subroutines (call destinations) + std::vector<uint32> list_subroutineAddrs; + // analyze CF and clauses + for(auto& cfInstruction : shaderContext->cfInstructions) + { + if (cfInstruction.type == GPU7_CF_INST_ALU || cfInstruction.type == GPU7_CF_INST_ALU_PUSH_BEFORE || cfInstruction.type == GPU7_CF_INST_ALU_POP_AFTER || cfInstruction.type == GPU7_CF_INST_ALU_POP2_AFTER || cfInstruction.type == GPU7_CF_INST_ALU_BREAK || cfInstruction.type == GPU7_CF_INST_ALU_ELSE_AFTER) + { + LatteDecompiler_analyzeALUClause(shaderContext, &cfInstruction); + } + else if (cfInstruction.type == GPU7_CF_INST_TEX) + { + LatteDecompiler_analyzeTEXClause(shaderContext, &cfInstruction); + } + else if (cfInstruction.type == GPU7_CF_INST_EXPORT || cfInstruction.type == GPU7_CF_INST_EXPORT_DONE) + { + LatteDecompiler_analyzeExport(shaderContext, &cfInstruction); + } + else if (cfInstruction.type == GPU7_CF_INST_ELSE || cfInstruction.type == GPU7_CF_INST_POP) + { + shaderContext->analyzer.modifiesPixelActiveState = true; + } + else if (cfInstruction.type == GPU7_CF_INST_LOOP_START_DX10 || cfInstruction.type == GPU7_CF_INST_LOOP_END) + { + shaderContext->analyzer.modifiesPixelActiveState = true; + shaderContext->analyzer.hasLoops = true; + } + else if (cfInstruction.type == GPU7_CF_INST_LOOP_BREAK) + { + shaderContext->analyzer.modifiesPixelActiveState = true; + shaderContext->analyzer.hasLoops = true; + } + else if (cfInstruction.type == GPU7_CF_INST_MEM_STREAM0_WRITE || + cfInstruction.type == GPU7_CF_INST_MEM_STREAM1_WRITE) + { + uint32 streamoutBufferIndex; + if (cfInstruction.type == GPU7_CF_INST_MEM_STREAM0_WRITE) + streamoutBufferIndex = 0; + else if (cfInstruction.type == GPU7_CF_INST_MEM_STREAM1_WRITE) + streamoutBufferIndex = 1; + else + cemu_assert_debug(false); + shaderContext->analyzer.hasStreamoutWrite = true; + cemu_assert(streamoutBufferIndex < shaderContext->output->streamoutBufferWriteMask.size()); + shaderContext->output->streamoutBufferWriteMask[streamoutBufferIndex] = true; + uint32 vectorWriteSize = 0; + for (sint32 f = 0; f < 4; f++) + { + if ((cfInstruction.memWriteCompMask & (1 << f)) != 0) + vectorWriteSize = (f + 1) * 4; + shaderContext->output->streamoutBufferStride[f] = shaderContext->contextRegisters[mmVGT_STRMOUT_VTX_STRIDE_0 + f * 4] << 2; + } + + cemu_assert_debug((cfInstruction.exportArrayBase * 4 + vectorWriteSize) <= shaderContext->output->streamoutBufferStride[streamoutBufferIndex]); + } + else if (cfInstruction.type == GPU7_CF_INST_MEM_RING_WRITE) + { + // track number of parameters that are output (simplified by just tracking the offset of the last one) + if (cfInstruction.memWriteElemSize != 3) + debugBreakpoint(); + if (cfInstruction.exportBurstCount != 0 && cfInstruction.memWriteElemSize != 3) + { + debugBreakpoint(); + } + uint32 dwordWriteCount = (cfInstruction.exportBurstCount + 1) * 4; + uint32 numRingParameter = (cfInstruction.exportArrayBase + dwordWriteCount) / 4; + shader->ringParameterCount = std::max(shader->ringParameterCount, numRingParameter); + // mark input GPRs as used + for (uint32 i = 0; i < (cfInstruction.exportBurstCount + 1); i++) + { + shaderContext->analyzer.gprUseMask[(cfInstruction.exportSourceGPR + i) / 8] |= (1 << ((cfInstruction.exportSourceGPR + i) % 8)); + } + } + else if (cfInstruction.type == GPU7_CF_INST_EMIT_VERTEX) + { + shaderContext->analyzer.numEmitVertex++; + } + else if (cfInstruction.type == GPU7_CF_INST_CALL) + { + // CALL instruction does not need analyzing + // and subroutines are analyzed separately + } + else + cemu_assert_unimplemented(); + } + // analyze subroutines + for (auto subroutineAddr : list_subroutineAddrs) + { + LatteDecompiler_analyzeSubroutine(shaderContext, subroutineAddr); + } + // decide which uniform mode to use + if(shaderContext->analyzer.uniformBufferAccessMask != 0 && shaderContext->analyzer.uniformRegisterAccess ) + debugBreakpoint(); // not allowed + if(shaderContext->analyzer.uniformBufferDynamicAccessMask != 0 ) + { + shader->uniformMode = LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK; + } + else if(shaderContext->analyzer.uniformRegisterDynamicAccess ) + { + shader->uniformMode = LATTE_DECOMPILER_UNIFORM_MODE_FULL_CFILE; + } + else if(shaderContext->analyzer.uniformBufferAccessMask != 0 || shaderContext->analyzer.uniformRegisterAccess != 0 ) + { + shader->uniformMode = LATTE_DECOMPILER_UNIFORM_MODE_REMAPPED; + } + else + { + shader->uniformMode = LATTE_DECOMPILER_UNIFORM_MODE_NONE; + } + // generate list of uniform buffers based on uniformBufferAccessMask (for faster access) + shader->uniformBufferListCount = 0; + for (uint32 i = 0; i < LATTE_NUM_MAX_UNIFORM_BUFFERS; i++) + { + if( !HAS_FLAG(shaderContext->analyzer.uniformBufferAccessMask, (1<<i)) ) + continue; + shader->uniformBufferList[shader->uniformBufferListCount] = i; + shader->uniformBufferListCount++; + } + // get dimension of each used textures + _LatteRegisterSetTextureUnit* texRegs = nullptr; + if( shader->shaderType == LatteConst::ShaderType::Vertex ) + texRegs = shaderContext->contextRegistersNew->SQ_TEX_START_VS; + else if( shader->shaderType == LatteConst::ShaderType::Pixel ) + texRegs = shaderContext->contextRegistersNew->SQ_TEX_START_PS; + else if( shader->shaderType == LatteConst::ShaderType::Geometry ) + texRegs = shaderContext->contextRegistersNew->SQ_TEX_START_GS; + + for(sint32 i=0; i<LATTE_NUM_MAX_TEX_UNITS; i++) + { + if (!shaderContext->output->textureUnitMask[i]) + { + // texture unit not used + shader->textureUnitDim[i] = (Latte::E_DIM)0xFF; + continue; + } + auto& texUnit = texRegs[i]; + auto dim = texUnit.word0.get_DIM(); + shader->textureUnitDim[i] = dim; + if(dim == Latte::E_DIM::DIM_CUBEMAP) + shaderContext->analyzer.hasCubeMapTexture = true; + shader->textureIsIntegerFormat[i] = texUnit.word4.get_NUM_FORM_ALL() == Latte::LATTE_SQ_TEX_RESOURCE_WORD4_N::E_NUM_FORMAT_ALL::NUM_FORMAT_INT; + } + // generate list of used texture units + shader->textureUnitListCount = 0; + for (sint32 i = 0; i < LATTE_NUM_MAX_TEX_UNITS; i++) + { + if (shaderContext->output->textureUnitMask[i]) + { + shader->textureUnitList[shader->textureUnitListCount] = i; + shader->textureUnitListCount++; + } + } + // for geometry shaders check the copy shader for stream writes + if (shader->shaderType == LatteConst::ShaderType::Geometry && shaderContext->parsedGSCopyShader->list_streamWrites.empty() == false) + { + shaderContext->analyzer.hasStreamoutWrite = true; + if (shaderContext->contextRegisters[mmVGT_STRMOUT_EN] != 0) + shaderContext->analyzer.hasStreamoutEnable = true; + for (auto& it : shaderContext->parsedGSCopyShader->list_streamWrites) + { + shaderContext->output->streamoutBufferWriteMask[it.bufferIndex] = true; + uint32 vectorWriteSize = 0; + for (sint32 f = 0; f < 4; f++) + { + if ((it.memWriteCompMask&(1 << f)) != 0) + vectorWriteSize = (f + 1) * 4; + } + shaderContext->output->streamoutBufferStride[it.bufferIndex] = std::max(shaderContext->output->streamoutBufferStride[it.bufferIndex], it.exportArrayBase * 4 + vectorWriteSize); + } + } + // analyze input attributes again (if shader has relative GPR read) + if(shaderContext->analyzer.usesRelativeGPRRead && (shader->shaderType == LatteConst::ShaderType::Vertex || shader->shaderType == LatteConst::ShaderType::Geometry) ) + { + for (sint32 f = 0; f < shaderContext->fetchShaderCount; f++) + { + LatteFetchShader* parsedFetchShader = (LatteFetchShader*)shaderContext->fetchShaderList[f]; + for(auto& bufferGroup : parsedFetchShader->bufferGroups) + { + for (sint32 i = 0; i < bufferGroup.attribCount; i++) + { + uint32 registerIndex; + // get register index based on vtx semantic table + uint32 attributeShaderLoc = 0xFFFFFFFF; + for (sint32 f = 0; f < 32; f++) + { + if (shaderContext->contextRegisters[mmSQ_VTX_SEMANTIC_0 + f] == bufferGroup.attrib[i].semanticId) + { + attributeShaderLoc = f; + break; + } + } + if (attributeShaderLoc == 0xFFFFFFFF) + continue; // attribute is not mapped to VS input + registerIndex = attributeShaderLoc + 1; + shaderContext->analyzer.gprUseMask[registerIndex / 8] |= (1 << (registerIndex % 8)); + } + } + } + } + else if (shaderContext->analyzer.usesRelativeGPRRead && shader->shaderType == LatteConst::ShaderType::Pixel) + { + // mark pixel shader inputs as used if there is any relative GPR access + LatteShaderPSInputTable* psInputTable = LatteSHRC_GetPSInputTable(); + for (sint32 i = 0; i < psInputTable->count; i++) + { + shaderContext->analyzer.gprUseMask[i / 8] |= (1 << (i % 8)); + } + } + // analyze CF stack + sint32 cfCurrentStackDepth = 0; + sint32 cfCurrentMaxStackDepth = 0; + for(auto& cfInstruction : shaderContext->cfInstructions) + { + if (cfInstruction.type == GPU7_CF_INST_ALU) + { + // no effect on stack depth + cfInstruction.activeStackDepth = cfCurrentStackDepth; + } + else if (cfInstruction.type == GPU7_CF_INST_ALU_PUSH_BEFORE ) + { + cfCurrentStackDepth++; + cfCurrentMaxStackDepth = std::max(cfCurrentMaxStackDepth, cfCurrentStackDepth); + cfInstruction.activeStackDepth = cfCurrentStackDepth; + } + else if (cfInstruction.type == GPU7_CF_INST_ALU_POP_AFTER) + { + cfInstruction.activeStackDepth = cfCurrentStackDepth; + cfCurrentStackDepth--; + } + else if (cfInstruction.type == GPU7_CF_INST_ALU_POP2_AFTER) + { + cfInstruction.activeStackDepth = cfCurrentStackDepth; + cfCurrentStackDepth -= 2; + } + else if (cfInstruction.type == GPU7_CF_INST_ALU_BREAK ) + { + cfInstruction.activeStackDepth = cfCurrentStackDepth; + } + else if (cfInstruction.type == GPU7_CF_INST_ALU_ELSE_AFTER) + { + if (cfInstruction.popCount != 0) + debugBreakpoint(); + cfInstruction.activeStackDepth = cfCurrentStackDepth; + } + else if (cfInstruction.type == GPU7_CF_INST_ELSE ) + { + //if (cfInstruction.popCount != 0) + // debugBreakpoint(); -> Only relevant when ELSE jump is taken + cfInstruction.activeStackDepth = cfCurrentStackDepth; + } + else if (cfInstruction.type == GPU7_CF_INST_POP) + { + cfInstruction.activeStackDepth = cfCurrentStackDepth; + cfCurrentStackDepth -= cfInstruction.popCount; + if (cfCurrentStackDepth < 0) + debugBreakpoint(); + } + else if (cfInstruction.type == GPU7_CF_INST_LOOP_START_DX10 || cfInstruction.type == GPU7_CF_INST_LOOP_END) + { + // no effect on stack depth + cfInstruction.activeStackDepth = cfCurrentStackDepth; + } + else if (cfInstruction.type == GPU7_CF_INST_LOOP_BREAK) + { + // since we assume that the break is not taken (for all pixels), we also don't need to worry about the stack depth adjustment + cfInstruction.activeStackDepth = cfCurrentStackDepth; + } + else if (cfInstruction.type == GPU7_CF_INST_TEX) + { + // no effect on stack depth + cfInstruction.activeStackDepth = cfCurrentStackDepth; + } + else if (cfInstruction.type == GPU7_CF_INST_EXPORT || cfInstruction.type == GPU7_CF_INST_EXPORT_DONE) + { + // no effect on stack depth + cfInstruction.activeStackDepth = cfCurrentStackDepth; + } + else if (cfInstruction.type == GPU7_CF_INST_MEM_STREAM0_WRITE || + cfInstruction.type == GPU7_CF_INST_MEM_STREAM1_WRITE) + { + // no effect on stack depth + cfInstruction.activeStackDepth = cfCurrentStackDepth; + } + else if (cfInstruction.type == GPU7_CF_INST_MEM_RING_WRITE) + { + // no effect on stack depth + cfInstruction.activeStackDepth = cfCurrentStackDepth; + } + else if (cfInstruction.type == GPU7_CF_INST_EMIT_VERTEX) + { + // no effect on stack depth + cfInstruction.activeStackDepth = cfCurrentStackDepth; + } + else if (cfInstruction.type == GPU7_CF_INST_CALL) + { + // no effect on stack depth + cfInstruction.activeStackDepth = cfCurrentStackDepth; + } + else + { + cemu_assert_debug(false); + } + } + shaderContext->analyzer.activeStackMaxDepth = cfCurrentMaxStackDepth; + if (cfCurrentStackDepth != 0) + { + debug_printf("cfCurrentStackDepth is not zero after all CF instructions. depth is %d\n", cfCurrentStackDepth); + cemu_assert_debug(false); + } + if(list_subroutineAddrs.empty() == false) + forceLogDebug_printf("Todo - analyze shader subroutine CF stack"); + // TF mode + if (shaderContext->useTFViaSSBO && shaderContext->output->streamoutBufferWriteMask.any()) + { + shaderContext->analyzer.useSSBOForStreamout = true; + } + // assign binding points + if (shaderContext->shaderType == LatteConst::ShaderType::Vertex) + shaderContext->output->resourceMappingVK.setIndex = 0; + else if (shaderContext->shaderType == LatteConst::ShaderType::Pixel) + shaderContext->output->resourceMappingVK.setIndex = 1; + else if (shaderContext->shaderType == LatteConst::ShaderType::Geometry) + shaderContext->output->resourceMappingVK.setIndex = 2; + LatteDecompiler::_initTextureBindingPointsGL(shaderContext); + LatteDecompiler::_initTextureBindingPointsVK(shaderContext); + LatteDecompiler::_initUniformBindingPoints(shaderContext); + LatteDecompiler::_initAttributeBindingPoints(shaderContext); +} diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSL.cpp b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSL.cpp new file mode 100644 index 00000000..9a5107d0 --- /dev/null +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSL.cpp @@ -0,0 +1,4225 @@ +#include "Cafe/HW/Latte/Core/LatteConst.h" +#include "Cafe/HW/Latte/Core/LatteShaderAssembly.h" +#include "Cafe/HW/Latte/ISA/RegDefines.h" +#include "Cafe/OS/libs/gx2/GX2.h" // todo - remove dependency +#include "Cafe/HW/Latte/Core/Latte.h" +#include "Cafe/HW/Latte/Core/LatteDraw.h" +#include "Cafe/HW/Latte/Core/LatteShader.h" +#include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h" +#include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInternal.h" +#include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInstructions.h" +#include "Cafe/HW/Latte/Core/FetchShader.h" + +#include "Cafe/GameProfile/GameProfile.h" + +#include "Cafe/HW/Latte/Renderer/Renderer.h" +#include "config/ActiveSettings.h" +#include "util/helpers/StringBuf.h" + +#include <bitset> + +#define _CRLF "\r\n" + +void LatteDecompiler_emitAttributeDecodeGLSL(LatteDecompilerShader* shaderContext, StringBuf* src, LatteParsedFetchShaderAttribute_t* attrib); + +/* + * Variable names: + * R0-R127 temp + * Most variables are multi-typed and the respective type is appended to the name + * Type suffixes are: f (float), i (32bit int), ui (unsigned 32bit int) + * Examples: R13ui.x, tempf.z + */ + +// local prototypes +void _emitTypeConversionPrefix(LatteDecompilerShaderContext* shaderContext, sint32 sourceType, sint32 destinationType); +void _emitTypeConversionSuffix(LatteDecompilerShaderContext* shaderContext, sint32 sourceType, sint32 destinationType); +void LatteDecompiler_emitClauseCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerCFInstruction* cfInstruction, bool isSubroutine); + +const char* _getShaderUniformBlockInterfaceName(LatteConst::ShaderType mode) +{ + switch (mode) + { + case LatteConst::ShaderType::Vertex: + return "uniformBlockVS"; + case LatteConst::ShaderType::Pixel: + return "uniformBlockPS"; + case LatteConst::ShaderType::Geometry: + return "uniformBlockGS"; + default: + break; + } + cemu_assert_unimplemented(); + return nullptr; +} + +const char* _getShaderUniformBlockVariableName(LatteConst::ShaderType mode) +{ + switch (mode) + { + case LatteConst::ShaderType::Vertex: + return "uf_blockVS"; + case LatteConst::ShaderType::Pixel: + return "uf_blockPS"; + case LatteConst::ShaderType::Geometry: + return "uf_blockGS"; + default: + break; + } + cemu_assert_unimplemented(); + return nullptr; +} + +const char* _getTextureUnitVariablePrefixName(LatteConst::ShaderType mode) +{ + switch (mode) + { + case LatteConst::ShaderType::Vertex: + return "textureUnitVS"; + case LatteConst::ShaderType::Pixel: + return "textureUnitPS"; + case LatteConst::ShaderType::Geometry: + return "textureUnitGS"; + } + cemu_assert_unimplemented(); + return nullptr; +} + +const char* _getElementStrByIndex(uint32 channel) +{ + switch (channel) + { + case 0: + return "x"; + case 1: + return "y"; + case 2: + return "z"; + case 3: + return "w"; + } + return "UNDEFINED"; +} + +char _tempGenString[64][256]; +uint32 _tempGenStringIndex = 0; + +char* _getTempString() +{ + char* str = _tempGenString[_tempGenStringIndex]; + _tempGenStringIndex = (_tempGenStringIndex+1)%64; + return str; +} + +static char* _getActiveMaskVarName(LatteDecompilerShaderContext* shaderContext, sint32 index) +{ + char* varName = _getTempString(); + if (shaderContext->isSubroutine) + sprintf(varName, "activeMaskStackSub%04x[%d]", shaderContext->subroutineInfo->cfAddr, index); + else + sprintf(varName, "activeMaskStack[%d]", index); + return varName; +} + +static char* _getActiveMaskCVarName(LatteDecompilerShaderContext* shaderContext, sint32 index) +{ + char* varName = _getTempString(); + if (shaderContext->isSubroutine) + sprintf(varName, "activeMaskStackCSub%04x[%d]", shaderContext->subroutineInfo->cfAddr, index); + else + sprintf(varName, "activeMaskStackC[%d]", index); + return varName; +} + +static char* _getRegisterVarName(LatteDecompilerShaderContext* shaderContext, uint32 index, sint32 destRelIndexMode=-1) +{ + auto type = shaderContext->typeTracker.defaultDataType; + char* tempStr = _getTempString(); + if (shaderContext->typeTracker.useArrayGPRs == false) + { + if (type == LATTE_DECOMPILER_DTYPE_SIGNED_INT) + sprintf(tempStr, "R%di", index); + else if (type == LATTE_DECOMPILER_DTYPE_FLOAT) + sprintf(tempStr, "R%df", index); + } + else + { + char destRelOffset[32]; + if (destRelIndexMode >= 0) + { + if (destRelIndexMode == GPU7_INDEX_AR_X) + strcpy(destRelOffset, "ARi.x"); + else if (destRelIndexMode == GPU7_INDEX_AR_Y) + strcpy(destRelOffset, "ARi.y"); + else if (destRelIndexMode == GPU7_INDEX_AR_Z) + strcpy(destRelOffset, "ARi.z"); + else if (destRelIndexMode == GPU7_INDEX_AR_W) + strcpy(destRelOffset, "ARi.w"); + else + debugBreakpoint(); + if (type == LATTE_DECOMPILER_DTYPE_SIGNED_INT) + { + sprintf(tempStr, "Ri[%d+%s]", index, destRelOffset); + } + else if (type == LATTE_DECOMPILER_DTYPE_FLOAT) + { + sprintf(tempStr, "Rf[%d+%s]", index, destRelOffset); + } + } + else + { + if (type == LATTE_DECOMPILER_DTYPE_SIGNED_INT) + { + sprintf(tempStr, "Ri[%d]", index); + } + else if (type == LATTE_DECOMPILER_DTYPE_FLOAT) + { + sprintf(tempStr, "Rf[%d]", index); + } + } + } + return tempStr; +} + +sint32 _getVertexShaderOutParamSemanticId(uint32* contextRegisters, sint32 index) // deprecated - moved to psInputTable +{ + uint32 vsSemanticId = (contextRegisters[mmSPI_VS_OUT_ID_0 + (index / 4)] >> (8 * (index % 4))) & 0xFF; + // check if export exists since exports are generated based on PS inputs + LatteShaderPSInputTable* psInputTable = LatteSHRC_GetPSInputTable(); + for (sint32 i = 0; i < psInputTable->count; i++) + { + if(psInputTable->import[i].semanticId == vsSemanticId) + return vsSemanticId; + } + return 0xFF; +} + +sint32 _getInputRegisterDataType(LatteDecompilerShaderContext* shaderContext, LatteDecompilerALUInstruction* aluInstruction, sint32 operandIndex) +{ + return shaderContext->typeTracker.defaultDataType; +} + +sint32 _getALUInstructionOutputDataType(LatteDecompilerShaderContext* shaderContext, LatteDecompilerALUInstruction* aluInstruction) +{ + return shaderContext->typeTracker.defaultDataType; +} + +// returns true if the ALU instruction is a OP2 reduction instruction +bool _isReductionInstruction(LatteDecompilerALUInstruction* aluInstruction) +{ + return aluInstruction->isOP3 == false && (aluInstruction->opcode == ALU_OP2_INST_DOT4 || aluInstruction->opcode == ALU_OP2_INST_DOT4_IEEE || aluInstruction->opcode == ALU_OP2_INST_CUBE); +} + +void _appendRegisterTypeSuffix(StringBuf* src, sint32 dataType) +{ + if (dataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) + src->add("i"); + else if (dataType == LATTE_DECOMPILER_DTYPE_UNSIGNED_INT) + src->add("ui"); + else if (dataType == LATTE_DECOMPILER_DTYPE_FLOAT) + src->add("f"); + else + cemu_assert_unimplemented(); +} + +void _appendChannelAccess(StringBuf* src, sint32 channelIndex) +{ + cemu_assert_debug(channelIndex >= 0 && channelIndex <= 3); + switch (channelIndex) + { + case 0: + src->add(".x"); + return; + case 1: + src->add(".y"); + return; + case 2: + src->add(".z"); + return; + case 3: + src->add(".w"); + return; + } +} + +/* + * Writes the name of the output variable and channel + * E.g. R5f.x or tempf.x if writeMask is 0 + */ +void _emitInstructionOutputVariableName(LatteDecompilerShaderContext* shaderContext, LatteDecompilerALUInstruction* aluInstruction) +{ + auto src = shaderContext->shaderSource; + sint32 outputDataType = _getALUInstructionOutputDataType(shaderContext, aluInstruction); + if( aluInstruction->writeMask == 0 ) + { + // does not output to GPR + if( aluInstruction->aluUnit < 4 && _isReductionInstruction(aluInstruction) == false ) + { + // output to PV + src->addFmt("PV{}", (aluInstruction->instructionGroupIndex&1)); + _appendRegisterTypeSuffix(src, outputDataType); + } + else if( aluInstruction->aluUnit == 4 && _isReductionInstruction(aluInstruction) == false ) + { + // output to PS + src->addFmt("PS{}", (aluInstruction->instructionGroupIndex&1)); + _appendRegisterTypeSuffix(src, outputDataType); + return; + } + else + { + // output to temp + src->add("temp"); + _appendRegisterTypeSuffix(src, outputDataType); + } + _appendChannelAccess(src, aluInstruction->aluUnit); + } + else + { + // output to GPR + src->add(_getRegisterVarName(shaderContext, aluInstruction->destGpr, aluInstruction->destRel==0?-1:aluInstruction->indexMode)); + _appendChannelAccess(src, aluInstruction->destElem); + } +} + +void _emitInstructionPVPSOutputVariableName(LatteDecompilerShaderContext* shaderContext, LatteDecompilerALUInstruction* aluInstruction) +{ + StringBuf* src = shaderContext->shaderSource; + if( aluInstruction->aluUnit == 4 ) + { + // output to PS + src->addFmt("PS{}", aluInstruction->instructionGroupIndex&1); + } + else + { + // output to PV + src->addFmt("PV{}", aluInstruction->instructionGroupIndex&1); + } + sint32 outputDataType = _getALUInstructionOutputDataType(shaderContext, aluInstruction); + _appendRegisterTypeSuffix(src, outputDataType); + if( aluInstruction->aluUnit < 4 ) + { + _appendChannelAccess(src, aluInstruction->aluUnit); + } +} + +void _emitRegisterAccessCode(LatteDecompilerShaderContext* shaderContext, sint32 gprIndex, sint32 channel0, sint32 channel1, sint32 channel2, sint32 channel3, sint32 dataType = -1) +{ + StringBuf* src = shaderContext->shaderSource; + sint32 registerElementDataType = shaderContext->typeTracker.defaultDataType; + cemu_assert_debug(gprIndex >= 0 && gprIndex <= 127); + if (dataType >= 0) + { + _emitTypeConversionPrefix(shaderContext, registerElementDataType, dataType); + } + if(shaderContext->typeTracker.useArrayGPRs ) + src->add("R"); + else + src->addFmt("R{}", gprIndex); + _appendRegisterTypeSuffix(src, registerElementDataType); + if (shaderContext->typeTracker.useArrayGPRs) + src->addFmt("[{}]", gprIndex); + + src->add("."); + + sint32 channelArray[4]; + channelArray[0] = channel0; + channelArray[1] = channel1; + channelArray[2] = channel2; + channelArray[3] = channel3; + + for (sint32 i = 0; i < 4; i++) + { + if (channelArray[i] >= 0 && channelArray[i] <= 3) + src->add(_getElementStrByIndex(channelArray[i])); + else if (channelArray[i] == -1) + { + // channel not used + } + else + { + cemu_assert_unimplemented(); + } + } + if (dataType >= 0) + _emitTypeConversionSuffix(shaderContext, registerElementDataType, dataType); +} + +void _emitALURegisterInputAccessCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerALUInstruction* aluInstruction, sint32 operandIndex) +{ + StringBuf* src = shaderContext->shaderSource; + sint32 currentRegisterElementType = _getInputRegisterDataType(shaderContext, aluInstruction, operandIndex); + if( GPU7_ALU_SRC_IS_GPR(aluInstruction->sourceOperand[operandIndex].sel) == false ) + debugBreakpoint(); + sint32 gprIndex = GPU7_ALU_SRC_GET_GPR_INDEX(aluInstruction->sourceOperand[operandIndex].sel); + + if( aluInstruction->sourceOperand[operandIndex].requiredRegisterBackup ) + { + // access via backup variable + src->addFmt("backupReg{}", aluInstruction->sourceOperand[operandIndex].registerBackupIndex); + _appendRegisterTypeSuffix(src, currentRegisterElementType); + } + else + { + // access via register variable + _emitRegisterAccessCode(shaderContext, gprIndex, aluInstruction->sourceOperand[operandIndex].chan, -1, -1, -1); + } +} + +void _emitPVAccessCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerALUInstruction* aluInstruction, sint32 operandIndex) +{ + StringBuf* src = shaderContext->shaderSource; + sint32 currentRegisterElementType = _getInputRegisterDataType(shaderContext, aluInstruction, operandIndex); + src->addFmt("PV{}", (aluInstruction->instructionGroupIndex&1)^1); + _appendRegisterTypeSuffix(src, currentRegisterElementType); + _appendChannelAccess(src, aluInstruction->sourceOperand[operandIndex].chan); +} + +void _emitPSAccessCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerALUInstruction* aluInstruction, sint32 operandIndex) +{ + StringBuf* src = shaderContext->shaderSource; + sint32 currentRegisterElementType = _getInputRegisterDataType(shaderContext, aluInstruction, operandIndex); + src->addFmt("PS{}", (aluInstruction->instructionGroupIndex&1)^1); + _appendRegisterTypeSuffix(src, currentRegisterElementType); +} + +/* + * Emits the expression used for calculating the index for uniform access + * For static access, this is a number + * For dynamic access, this is AR.* + base + */ +void _emitUniformAccessIndexCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerALUInstruction* aluInstruction, sint32 operandIndex) +{ + StringBuf* src = shaderContext->shaderSource; + bool isUniformRegister = GPU7_ALU_SRC_IS_CFILE(aluInstruction->sourceOperand[operandIndex].sel); + sint32 uniformOffset = 0; // index into array, for relative accesses this is the base offset + if( isUniformRegister ) + { + uniformOffset = GPU7_ALU_SRC_GET_CFILE_INDEX(aluInstruction->sourceOperand[operandIndex].sel); + } + else + { + if( GPU7_ALU_SRC_IS_CBANK0(aluInstruction->sourceOperand[operandIndex].sel) ) + { + uniformOffset = GPU7_ALU_SRC_GET_CBANK0_INDEX(aluInstruction->sourceOperand[operandIndex].sel) + aluInstruction->cfInstruction->cBank0AddrBase; + } + else + { + uniformOffset = GPU7_ALU_SRC_GET_CBANK1_INDEX(aluInstruction->sourceOperand[operandIndex].sel) + aluInstruction->cfInstruction->cBank1AddrBase; + } + } + if( aluInstruction->sourceOperand[operandIndex].rel != 0 ) + { + if (aluInstruction->indexMode == GPU7_INDEX_AR_X) + src->addFmt("ARi.x+{}", uniformOffset); + else if (aluInstruction->indexMode == GPU7_INDEX_AR_Y) + src->addFmt("ARi.y+{}", uniformOffset); + else if (aluInstruction->indexMode == GPU7_INDEX_AR_Z) + src->addFmt("ARi.z+{}", uniformOffset); + else if (aluInstruction->indexMode == GPU7_INDEX_AR_W) + src->addFmt("ARi.w+{}", uniformOffset); + else + cemu_assert_unimplemented(); + } + else + { + src->addFmt("{}", uniformOffset); + } +} + +void _emitUniformAccessCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerALUInstruction* aluInstruction, sint32 operandIndex, sint32 requiredType) +{ + StringBuf* src = shaderContext->shaderSource; + if(shaderContext->shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_REMAPPED ) + { + // uniform registers or buffers are accessed statically with predictable offsets + // find entry in remapped uniform + if( aluInstruction->sourceOperand[operandIndex].rel != 0 ) + debugBreakpoint(); + bool isUniformRegister = GPU7_ALU_SRC_IS_CFILE(aluInstruction->sourceOperand[operandIndex].sel); + sint32 uniformOffset = 0; // index into array + sint32 uniformBufferIndex = 0; + if( isUniformRegister ) + { + uniformOffset = GPU7_ALU_SRC_GET_CFILE_INDEX(aluInstruction->sourceOperand[operandIndex].sel); + uniformBufferIndex = 0; + } + else + { + if( GPU7_ALU_SRC_IS_CBANK0(aluInstruction->sourceOperand[operandIndex].sel) ) + { + uniformOffset = GPU7_ALU_SRC_GET_CBANK0_INDEX(aluInstruction->sourceOperand[operandIndex].sel) + aluInstruction->cfInstruction->cBank0AddrBase; + uniformBufferIndex = aluInstruction->cfInstruction->cBank0Index; + } + else + { + uniformOffset = GPU7_ALU_SRC_GET_CBANK1_INDEX(aluInstruction->sourceOperand[operandIndex].sel) + aluInstruction->cfInstruction->cBank1AddrBase; + uniformBufferIndex = aluInstruction->cfInstruction->cBank1Index; + } + } + LatteDecompilerRemappedUniformEntry_t* remappedUniformEntry = NULL; + for(size_t i=0; i< shaderContext->shader->list_remappedUniformEntries.size(); i++) + { + LatteDecompilerRemappedUniformEntry_t* remappedUniformEntryItr = shaderContext->shader->list_remappedUniformEntries.data() + i; + if( remappedUniformEntryItr->isRegister && isUniformRegister ) + { + if( remappedUniformEntryItr->index == uniformOffset ) + { + remappedUniformEntry = remappedUniformEntryItr; + break; + } + } + else + { + if( remappedUniformEntryItr->kcacheBankId == uniformBufferIndex && remappedUniformEntryItr->index == uniformOffset ) + { + remappedUniformEntry = remappedUniformEntryItr; + break; + } + } + } + cemu_assert_debug(remappedUniformEntry); + _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, requiredType); + if(shaderContext->shader->shaderType == LatteConst::ShaderType::Vertex ) + src->addFmt("uf_remappedVS[{}]", remappedUniformEntry->mappedIndex); + else if(shaderContext->shader->shaderType == LatteConst::ShaderType::Pixel ) + src->addFmt("uf_remappedPS[{}]", remappedUniformEntry->mappedIndex); + else if(shaderContext->shader->shaderType == LatteConst::ShaderType::Geometry ) + src->addFmt("uf_remappedGS[{}]", remappedUniformEntry->mappedIndex); + else + debugBreakpoint(); + _appendChannelAccess(src, aluInstruction->sourceOperand[operandIndex].chan); + _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, requiredType); + } + else if( shaderContext->shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CFILE ) + { + // uniform registers are accessed with unpredictable (dynamic) offset + _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, requiredType); + if(shaderContext->shader->shaderType == LatteConst::ShaderType::Vertex ) + src->add("uf_uniformRegisterVS["); + else if (shaderContext->shader->shaderType == LatteConst::ShaderType::Pixel) + src->add("uf_uniformRegisterPS["); + else if(shaderContext->shader->shaderType == LatteConst::ShaderType::Geometry ) + src->add("uf_uniformRegisterGS["); + else + debugBreakpoint(); + _emitUniformAccessIndexCode(shaderContext, aluInstruction, operandIndex); + src->add("]"); + + _appendChannelAccess(src, aluInstruction->sourceOperand[operandIndex].chan); + _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, requiredType); + } + else if( shaderContext->shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK ) + { + // uniform buffers are available as a whole + bool isUniformRegister = GPU7_ALU_SRC_IS_CFILE(aluInstruction->sourceOperand[operandIndex].sel); + if( isUniformRegister ) + debugBreakpoint(); + sint32 uniformBufferIndex = 0; + if( GPU7_ALU_SRC_IS_CBANK0(aluInstruction->sourceOperand[operandIndex].sel) ) + { + uniformBufferIndex = aluInstruction->cfInstruction->cBank0Index; + } + else + { + uniformBufferIndex = aluInstruction->cfInstruction->cBank1Index; + } + _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, requiredType); + src->addFmt("{}{}[", _getShaderUniformBlockVariableName(shaderContext->shader->shaderType), uniformBufferIndex); + _emitUniformAccessIndexCode(shaderContext, aluInstruction, operandIndex); + src->addFmt("]"); + + _appendChannelAccess(src, aluInstruction->sourceOperand[operandIndex].chan); + _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, requiredType); + } + else + debugBreakpoint(); +} + +// Generates (slow) code to read an indexed GPR +void _emitCodeToReadRelativeGPR(LatteDecompilerShaderContext* shaderContext, LatteDecompilerALUInstruction* aluInstruction, sint32 operandIndex, sint32 requiredType) +{ + StringBuf* src = shaderContext->shaderSource; + uint32 gprBaseIndex = GPU7_ALU_SRC_GET_GPR_INDEX(aluInstruction->sourceOperand[operandIndex].sel); + cemu_assert_debug(aluInstruction->sourceOperand[operandIndex].rel != 0); + + if( shaderContext->typeTracker.useArrayGPRs ) + { + _emitTypeConversionPrefix(shaderContext, shaderContext->typeTracker.defaultDataType, requiredType); + src->add(_getRegisterVarName(shaderContext, gprBaseIndex, aluInstruction->indexMode)); + _appendChannelAccess(src, aluInstruction->sourceOperand[operandIndex].chan); + _emitTypeConversionSuffix(shaderContext, shaderContext->typeTracker.defaultDataType, requiredType); + return; + } + + char indexAccessCode[64]; + if (aluInstruction->indexMode == GPU7_INDEX_AR_X) + sprintf(indexAccessCode, "ARi.x"); + else if (aluInstruction->indexMode == GPU7_INDEX_AR_Y) + sprintf(indexAccessCode, "ARi.y"); + else if (aluInstruction->indexMode == GPU7_INDEX_AR_Z) + sprintf(indexAccessCode, "ARi.z"); + else if (aluInstruction->indexMode == GPU7_INDEX_AR_W) + sprintf(indexAccessCode, "ARi.w"); + else + cemu_assert_unimplemented(); + + if( LATTE_DECOMPILER_DTYPE_SIGNED_INT != requiredType ) + _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, requiredType); + + // generated code looks like this: + // result = ((lookupIndex==0)?GPR5:(lookupIndex==1)?GPR6:(lookupIndex==2)?GPR7:...:(lookupIndex==122)?GPR127:0) + src->add("("); + for(sint32 i=gprBaseIndex; i<LATTE_NUM_GPR; i++) + { + // only emit access code for registers which are potentially written + if((shaderContext->analyzer.gprUseMask[i / 8] & (1 << (i % 8))) == 0 ) + continue; + src->addFmt("({}=={})?", indexAccessCode, i-gprBaseIndex); + // code to access gpr + uint32 gprIndex = i; + src->add(_getRegisterVarName(shaderContext, i)); + _appendChannelAccess(src, aluInstruction->sourceOperand[operandIndex].chan); + src->add(":"); + } + src->add("0)"); + if( LATTE_DECOMPILER_DTYPE_SIGNED_INT != requiredType ) + _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, requiredType); +} + +void _emitOperandInputCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerALUInstruction* aluInstruction, sint32 operandIndex, sint32 requiredType) +{ + StringBuf* src = shaderContext->shaderSource; + if( operandIndex < 0 || operandIndex >= 3 ) + debugBreakpoint(); + sint32 requiredTypeOut = requiredType; + if( requiredType != LATTE_DECOMPILER_DTYPE_FLOAT && (aluInstruction->sourceOperand[operandIndex].abs != 0 || aluInstruction->sourceOperand[operandIndex].neg != 0) ) + { + // we need to apply float operations on the input but it's not read as a float + // force internal required type to float and then cast it back to whatever type is actually required + requiredType = LATTE_DECOMPILER_DTYPE_FLOAT; + } + + if( requiredTypeOut != requiredType ) + _emitTypeConversionPrefix(shaderContext, requiredType, requiredTypeOut); + + if( aluInstruction->sourceOperand[operandIndex].neg != 0 ) + src->add("-("); + if( aluInstruction->sourceOperand[operandIndex].abs != 0 ) + src->add("abs("); + + if( GPU7_ALU_SRC_IS_GPR(aluInstruction->sourceOperand[operandIndex].sel) ) + { + if( aluInstruction->sourceOperand[operandIndex].rel != 0 ) + { + _emitCodeToReadRelativeGPR(shaderContext, aluInstruction, operandIndex, requiredType); + } + else + { + uint32 gprIndex = GPU7_ALU_SRC_GET_GPR_INDEX(aluInstruction->sourceOperand[operandIndex].sel); + if( requiredType == LATTE_DECOMPILER_DTYPE_SIGNED_INT ) + { + // signed int 32bit + sint32 currentRegisterElementType = _getInputRegisterDataType(shaderContext, aluInstruction, operandIndex); + // write code for register input + _emitTypeConversionPrefix(shaderContext, currentRegisterElementType, requiredType); + _emitALURegisterInputAccessCode(shaderContext, aluInstruction, operandIndex); + _emitTypeConversionSuffix(shaderContext, currentRegisterElementType, requiredType); + } + else if( requiredType == LATTE_DECOMPILER_DTYPE_UNSIGNED_INT ) + { + // unsigned int 32bit + sint32 currentRegisterElementType = _getInputRegisterDataType(shaderContext, aluInstruction, operandIndex); + if( currentRegisterElementType == LATTE_DECOMPILER_DTYPE_SIGNED_INT ) + { + // need to convert from int to uint + src->add("uint("); + } + else if( currentRegisterElementType == LATTE_DECOMPILER_DTYPE_UNSIGNED_INT ) + { + // no extra work necessary + } + else + debugBreakpoint(); + // write code for register input + _emitALURegisterInputAccessCode(shaderContext, aluInstruction, operandIndex); + if( currentRegisterElementType == LATTE_DECOMPILER_DTYPE_SIGNED_INT ) + { + src->add(")"); + } + } + else if( requiredType == LATTE_DECOMPILER_DTYPE_FLOAT ) + { + // float 32bit + sint32 currentRegisterElementType = _getInputRegisterDataType(shaderContext, aluInstruction, operandIndex); + if( currentRegisterElementType == LATTE_DECOMPILER_DTYPE_SIGNED_INT ) + { + // need to convert (not cast) from int bits to float + src->add("intBitsToFloat("); + } + else if( currentRegisterElementType == LATTE_DECOMPILER_DTYPE_FLOAT ) + { + // no extra work necessary + } + else + debugBreakpoint(); + // write code for register input + _emitALURegisterInputAccessCode(shaderContext, aluInstruction, operandIndex); + if( currentRegisterElementType == LATTE_DECOMPILER_DTYPE_SIGNED_INT ) + { + src->add(")"); + } + } + else + debugBreakpoint(); + } + } + else if( GPU7_ALU_SRC_IS_CONST_0F(aluInstruction->sourceOperand[operandIndex].sel) ) + { + if(requiredType == LATTE_DECOMPILER_DTYPE_SIGNED_INT || requiredType == LATTE_DECOMPILER_DTYPE_UNSIGNED_INT) + src->add("0"); + else if( requiredType == LATTE_DECOMPILER_DTYPE_FLOAT ) + src->add("0.0"); + else + debugBreakpoint(); + } + else if( GPU7_ALU_SRC_IS_CONST_1F(aluInstruction->sourceOperand[operandIndex].sel) ) + { + if( requiredType == LATTE_DECOMPILER_DTYPE_SIGNED_INT ) + src->add("floatBitsToInt(1.0)"); + else if( requiredType == LATTE_DECOMPILER_DTYPE_FLOAT ) + src->add("1.0"); + else + debugBreakpoint(); + } + else if( GPU7_ALU_SRC_IS_CONST_0_5F(aluInstruction->sourceOperand[operandIndex].sel) ) + { + if( requiredType == LATTE_DECOMPILER_DTYPE_SIGNED_INT ) + src->add("floatBitsToInt(0.5)"); + else if( requiredType == LATTE_DECOMPILER_DTYPE_FLOAT ) + src->add("0.5"); + else + debugBreakpoint(); + } + else if( GPU7_ALU_SRC_IS_CONST_1I(aluInstruction->sourceOperand[operandIndex].sel) ) + { + if (requiredType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) + src->add("int(1)"); + else if (requiredType == LATTE_DECOMPILER_DTYPE_UNSIGNED_INT) + src->add("uint(1)"); + else + debugBreakpoint(); + } + else if( GPU7_ALU_SRC_IS_CONST_M1I(aluInstruction->sourceOperand[operandIndex].sel) ) + { + if( requiredType == LATTE_DECOMPILER_DTYPE_SIGNED_INT ) + src->add("int(-1)"); + else + debugBreakpoint(); + } + else if( GPU7_ALU_SRC_IS_LITERAL(aluInstruction->sourceOperand[operandIndex].sel) ) + { + if( requiredType == LATTE_DECOMPILER_DTYPE_SIGNED_INT ) + src->addFmt("0x{:08x}", aluInstruction->literalData.w[aluInstruction->sourceOperand[operandIndex].chan]); + else if( requiredType == LATTE_DECOMPILER_DTYPE_UNSIGNED_INT ) + src->addFmt("uint(0x{:08x})", aluInstruction->literalData.w[aluInstruction->sourceOperand[operandIndex].chan]); + else if (requiredType == LATTE_DECOMPILER_DTYPE_FLOAT) + { + uint32 constVal = aluInstruction->literalData.w[aluInstruction->sourceOperand[operandIndex].chan]; + if (constVal == 0x3f800000) + src->add("1.0"); + else if (constVal == 0x3fc00000) + src->add("1.5"); + else if (constVal == 0x3e800000) + src->add("0.25"); + else if (constVal == 0x80000000) + src->add("-0.0"); + else if (constVal == 0x40000000) + src->add("2.0"); + else if (constVal == 0x40800000) + src->add("4.0"); + else + src->addFmt("intBitsToFloat(0x{:08x})", aluInstruction->literalData.w[aluInstruction->sourceOperand[operandIndex].chan]); + } + else + debugBreakpoint(); + } + else if( GPU7_ALU_SRC_IS_CFILE(aluInstruction->sourceOperand[operandIndex].sel) ) + { + _emitUniformAccessCode(shaderContext, aluInstruction, operandIndex, requiredType); + } + else if( GPU7_ALU_SRC_IS_CBANK0(aluInstruction->sourceOperand[operandIndex].sel) || + GPU7_ALU_SRC_IS_CBANK1(aluInstruction->sourceOperand[operandIndex].sel) ) + { + _emitUniformAccessCode(shaderContext, aluInstruction, operandIndex, requiredType); + } + else if( GPU7_ALU_SRC_IS_PV(aluInstruction->sourceOperand[operandIndex].sel) ) + { + sint32 currentPVDataType = _getInputRegisterDataType(shaderContext, aluInstruction, operandIndex); + if( requiredType == currentPVDataType ) + { + _emitPVAccessCode(shaderContext, aluInstruction, operandIndex); + } + else + { + _emitTypeConversionPrefix(shaderContext, currentPVDataType, requiredType); + _emitPVAccessCode(shaderContext, aluInstruction, operandIndex); + _emitTypeConversionSuffix(shaderContext, currentPVDataType, requiredType); + } + } + else if( GPU7_ALU_SRC_IS_PS(aluInstruction->sourceOperand[operandIndex].sel) ) + { + sint32 currentPSDataType = _getInputRegisterDataType(shaderContext, aluInstruction, operandIndex); + if( requiredType == currentPSDataType ) + { + _emitPSAccessCode(shaderContext, aluInstruction, operandIndex); + } + else + { + _emitTypeConversionPrefix(shaderContext, currentPSDataType, requiredType); + _emitPSAccessCode(shaderContext, aluInstruction, operandIndex); + _emitTypeConversionSuffix(shaderContext, currentPSDataType, requiredType); + } + } + else + { + debug_printf("Unsupported operand sel 0x%x\n", aluInstruction->sourceOperand[operandIndex].sel); + debugBreakpoint(); + } + + if( aluInstruction->sourceOperand[operandIndex].abs != 0 ) + src->add(")"); + if( aluInstruction->sourceOperand[operandIndex].neg != 0 ) + src->add(")"); + + if( requiredTypeOut != requiredType ) + _emitTypeConversionSuffix(shaderContext, requiredType, requiredTypeOut); + +} + +void _emitTypeConversionPrefix(LatteDecompilerShaderContext* shaderContext, sint32 sourceType, sint32 destinationType) +{ + if( sourceType == destinationType ) + return; + StringBuf* src = shaderContext->shaderSource; + if (sourceType == LATTE_DECOMPILER_DTYPE_FLOAT && destinationType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) + src->add("floatBitsToInt("); + else if (sourceType == LATTE_DECOMPILER_DTYPE_FLOAT && destinationType == LATTE_DECOMPILER_DTYPE_UNSIGNED_INT) + src->add("floatBitsToUint("); + else if( sourceType == LATTE_DECOMPILER_DTYPE_SIGNED_INT && destinationType == LATTE_DECOMPILER_DTYPE_FLOAT ) + src->add("intBitsToFloat("); + else if( sourceType == LATTE_DECOMPILER_DTYPE_UNSIGNED_INT && destinationType == LATTE_DECOMPILER_DTYPE_SIGNED_INT ) + src->add("int("); + else if( sourceType == LATTE_DECOMPILER_DTYPE_SIGNED_INT && destinationType == LATTE_DECOMPILER_DTYPE_UNSIGNED_INT ) + src->add("uint("); + else + cemu_assert_debug(false); +} + +void _emitTypeConversionSuffix(LatteDecompilerShaderContext* shaderContext, sint32 sourceType, sint32 destinationType) +{ + if( sourceType == destinationType ) + return; + StringBuf* src = shaderContext->shaderSource; + src->add(")"); +} + +template<int TDataType> +void _emitALUOperationBinary(LatteDecompilerShaderContext* shaderContext, LatteDecompilerALUInstruction* aluInstruction, const char* operandStr) +{ + StringBuf* src = shaderContext->shaderSource; + sint32 outputType = _getALUInstructionOutputDataType(shaderContext, aluInstruction); + _emitInstructionOutputVariableName(shaderContext, aluInstruction); + src->add(" = "); + _emitTypeConversionPrefix(shaderContext, TDataType, outputType); + _emitOperandInputCode(shaderContext, aluInstruction, 0, TDataType); + src->add((char*)operandStr); + _emitOperandInputCode(shaderContext, aluInstruction, 1, TDataType); + _emitTypeConversionSuffix(shaderContext, TDataType, outputType); + src->add(";" _CRLF); +} + +void _emitALUOP2InstructionCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerCFInstruction* cfInstruction, LatteDecompilerALUInstruction* aluInstruction) +{ + StringBuf* src = shaderContext->shaderSource; + sint32 outputType; // data type of output + if( aluInstruction->opcode == ALU_OP2_INST_MOV ) + { + outputType = _getALUInstructionOutputDataType(shaderContext, aluInstruction); + bool requiresFloatMove = false; + requiresFloatMove = aluInstruction->sourceOperand[0].abs != 0 || aluInstruction->sourceOperand[0].neg != 0; + if( requiresFloatMove ) + { + // abs/neg operations are applied to source operand, do float based move + _emitInstructionOutputVariableName(shaderContext, aluInstruction); + src->add(" = "); + _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); + _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); + _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); + src->add(";" _CRLF); + } + else + { + _emitInstructionOutputVariableName(shaderContext, aluInstruction); + src->add(" = "); + _emitOperandInputCode(shaderContext, aluInstruction, 0, outputType); + src->add(";" _CRLF); + } + } + else if( aluInstruction->opcode == ALU_OP2_INST_MOVA_FLOOR ) + { + if( aluInstruction->writeMask != 0 ) + debugBreakpoint(); + if( aluInstruction->omod != 0 ) + debugBreakpoint(); // omod is handled after instruction code, check what affect it has on AR + outputType = _getALUInstructionOutputDataType(shaderContext, aluInstruction); + + src->add("tempResultf = "); + _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(";" _CRLF); + src->add("tempResultf = floor(tempResultf);" _CRLF); + src->add("tempResultf = clamp(tempResultf, -256.0, 255.0);" _CRLF); + // set AR + if( aluInstruction->destElem == 0 ) + src->add("ARi.x = int(tempResultf);" _CRLF); + else if( aluInstruction->destElem == 1 ) + src->add("ARi.y = int(tempResultf);" _CRLF); + else if( aluInstruction->destElem == 2 ) + src->add("ARi.z = int(tempResultf);" _CRLF); + else + src->add("ARi.w = int(tempResultf);" _CRLF); + // set output + _emitInstructionOutputVariableName(shaderContext, aluInstruction); + src->add(" = "); + if( outputType != LATTE_DECOMPILER_DTYPE_SIGNED_INT ) + debugBreakpoint(); // todo + src->add("floatBitsToInt(tempResultf)"); + src->add(";" _CRLF); + } + else if( aluInstruction->opcode == ALU_OP2_INST_MOVA_INT ) + { + if( aluInstruction->writeMask != 0 ) + debugBreakpoint(); + if( aluInstruction->omod != 0 ) + debugBreakpoint(); // omod is handled after instruction code, check what affect it has on AR + outputType = _getALUInstructionOutputDataType(shaderContext, aluInstruction); + + src->add("tempResulti = "); + _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_SIGNED_INT); + src->add(";" _CRLF); + src->add("tempResulti = clamp(tempResulti, -256, 255);" _CRLF); + // set AR + if( aluInstruction->destElem == 0 ) + src->add("ARi.x = tempResulti;" _CRLF); + else if( aluInstruction->destElem == 1 ) + src->add("ARi.y = tempResulti;" _CRLF); + else if( aluInstruction->destElem == 2 ) + src->add("ARi.z = tempResulti;" _CRLF); + else + src->add("ARi.w = tempResulti;" _CRLF); + // set output + _emitInstructionOutputVariableName(shaderContext, aluInstruction); + src->add(" = "); + if( outputType != LATTE_DECOMPILER_DTYPE_SIGNED_INT ) + debugBreakpoint(); // todo + src->add("tempResulti"); + src->add(";" _CRLF); + + } + else if( aluInstruction->opcode == ALU_OP2_INST_ADD ) + { + _emitALUOperationBinary<LATTE_DECOMPILER_DTYPE_FLOAT>(shaderContext, aluInstruction, " + "); + } + else if( aluInstruction->opcode == ALU_OP2_INST_MUL ) + { + // 0*anything is always 0 + outputType = _getALUInstructionOutputDataType(shaderContext, aluInstruction); + _emitInstructionOutputVariableName(shaderContext, aluInstruction); + src->add(" = "); + _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); + + // if any operand is a non-zero literal or constant we can use standard multiplication + bool useDefaultMul = false; + if (GPU7_ALU_SRC_IS_CONST_0F(aluInstruction->sourceOperand[0].sel) || GPU7_ALU_SRC_IS_CONST_0F(aluInstruction->sourceOperand[1].sel)) + { + // result is always zero + src->add("0.0"); + } + else + { + // multiply + if (GPU7_ALU_SRC_IS_LITERAL(aluInstruction->sourceOperand[0].sel) || GPU7_ALU_SRC_IS_LITERAL(aluInstruction->sourceOperand[1].sel) || + GPU7_ALU_SRC_IS_ANY_CONST(aluInstruction->sourceOperand[0].sel) || GPU7_ALU_SRC_IS_ANY_CONST(aluInstruction->sourceOperand[1].sel)) + { + useDefaultMul = true; + } + if (g_current_game_profile->GetAccurateShaderMul() != AccurateShaderMulOption::False && useDefaultMul == false) + { + src->add("mul_nonIEEE("); + _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(", "); + _emitOperandInputCode(shaderContext, aluInstruction, 1, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(")"); + } + else + { + _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(" * "); + _emitOperandInputCode(shaderContext, aluInstruction, 1, LATTE_DECOMPILER_DTYPE_FLOAT); + } + } + + _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); + src->add(";" _CRLF); + } + else if( aluInstruction->opcode == ALU_OP2_INST_MUL_IEEE ) + { + // 0*anything according to IEEE rules + _emitALUOperationBinary<LATTE_DECOMPILER_DTYPE_FLOAT>(shaderContext, aluInstruction, " * "); + } + else if (aluInstruction->opcode == ALU_OP2_INST_RECIP_IEEE) + { + outputType = _getALUInstructionOutputDataType(shaderContext, aluInstruction); + _emitInstructionOutputVariableName(shaderContext, aluInstruction); + src->add(" = "); + _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); + src->add("1.0"); + src->add(" / "); + _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); + _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); + src->add(";" _CRLF); + } + else if (aluInstruction->opcode == ALU_OP2_INST_RECIP_FF) + { + // untested (BotW bombs) + src->add("tempResultf = 1.0 / ("); + _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(");" _CRLF); + // INF becomes 0.0 + src->add("if( isinf(tempResultf) == true && (floatBitsToInt(tempResultf)&0x80000000) == 0 ) tempResultf = 0.0;" _CRLF); + // -INF becomes -0.0 + src->add("else if( isinf(tempResultf) == true && (floatBitsToInt(tempResultf)&0x80000000) != 0 ) tempResultf = -0.0;" _CRLF); + // assign result to output + outputType = _getALUInstructionOutputDataType(shaderContext, aluInstruction); + _emitInstructionOutputVariableName(shaderContext, aluInstruction); + src->add(" = "); + _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); + src->add("tempResultf"); + _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); + src->add(";" _CRLF); + } + else if( aluInstruction->opcode == ALU_OP2_INST_RECIPSQRT_IEEE || + aluInstruction->opcode == ALU_OP2_INST_RECIPSQRT_CLAMPED || + aluInstruction->opcode == ALU_OP2_INST_RECIPSQRT_FF ) + { + // todo: This should be correct but testing is needed + src->add("tempResultf = 1.0 / sqrt("); + _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(");" _CRLF); + if (aluInstruction->opcode == ALU_OP2_INST_RECIPSQRT_CLAMPED) + { + // note: if( -INF < 0.0 ) does not resolve to true + src->add("if( isinf(tempResultf) == true && (floatBitsToInt(tempResultf)&0x80000000) != 0 ) tempResultf = -3.40282347E+38F;" _CRLF); + src->add("else if( isinf(tempResultf) == true && (floatBitsToInt(tempResultf)&0x80000000) == 0 ) tempResultf = 3.40282347E+38F;" _CRLF); + } + else if (aluInstruction->opcode == ALU_OP2_INST_RECIPSQRT_FF) + { + // untested (BotW bombs) + src->add("if( isinf(tempResultf) == true && (floatBitsToInt(tempResultf)&0x80000000) != 0 ) tempResultf = -0.0;" _CRLF); + src->add("else if( isinf(tempResultf) == true && (floatBitsToInt(tempResultf)&0x80000000) == 0 ) tempResultf = 0.0;" _CRLF); + } + // assign result to output + outputType = _getALUInstructionOutputDataType(shaderContext, aluInstruction); + _emitInstructionOutputVariableName(shaderContext, aluInstruction); + src->add(" = "); + _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); + src->add("tempResultf"); + _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); + src->add(";" _CRLF); + } + else if( aluInstruction->opcode == ALU_OP2_INST_MAX || + aluInstruction->opcode == ALU_OP2_INST_MIN || + aluInstruction->opcode == ALU_OP2_INST_MAX_DX10 ) + { + outputType = _getALUInstructionOutputDataType(shaderContext, aluInstruction); + _emitInstructionOutputVariableName(shaderContext, aluInstruction); + src->add(" = "); + _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); + if( aluInstruction->opcode == ALU_OP2_INST_MAX ) + src->add("max"); + else if( aluInstruction->opcode == ALU_OP2_INST_MIN ) + src->add("min"); + else if( aluInstruction->opcode == ALU_OP2_INST_MAX_DX10 ) + { + src->add("max"); + } + src->add("("); + _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(", "); + _emitOperandInputCode(shaderContext, aluInstruction, 1, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(")"); + _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); + src->add(";" _CRLF); + } + else if( aluInstruction->opcode == ALU_OP2_INST_FLOOR || + aluInstruction->opcode == ALU_OP2_INST_FRACT || + aluInstruction->opcode == ALU_OP2_INST_TRUNC ) + { + outputType = _getALUInstructionOutputDataType(shaderContext, aluInstruction); + _emitInstructionOutputVariableName(shaderContext, aluInstruction); + src->add(" = "); + _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); + if( aluInstruction->opcode == ALU_OP2_INST_FLOOR ) + src->add("floor"); + else if( aluInstruction->opcode == ALU_OP2_INST_FRACT ) + src->add("fract"); + else + src->add("trunc"); + src->add("("); + _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(")"); + _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); + src->add(";" _CRLF); + } + else if( aluInstruction->opcode == ALU_OP2_INST_LOG_CLAMPED || + aluInstruction->opcode == ALU_OP2_INST_LOG_IEEE ) + { + src->add("tempResultf = max(0.0, "); + _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(");" _CRLF); + + src->add("tempResultf = log2(tempResultf);" _CRLF); + if( aluInstruction->opcode == ALU_OP2_INST_LOG_CLAMPED ) + { + // note: apparently -INF < 0.0 does not resolve to true + src->add("if( isinf(tempResultf) == true ) tempResultf = -3.40282347E+38F;" _CRLF); + } + // assign result to output + outputType = _getALUInstructionOutputDataType(shaderContext, aluInstruction); + _emitInstructionOutputVariableName(shaderContext, aluInstruction); + src->add(" = "); + _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); + src->add("tempResultf"); + _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); + src->add(";" _CRLF); + } + else if( aluInstruction->opcode == ALU_OP2_INST_RNDNE ) + { + outputType = _getALUInstructionOutputDataType(shaderContext, aluInstruction); + _emitInstructionOutputVariableName(shaderContext, aluInstruction); + src->add(" = "); + _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); + src->add("roundEven"); + src->add("("); + _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(")"); + _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); + src->add(";" _CRLF); + } + else if( aluInstruction->opcode == ALU_OP2_INST_EXP_IEEE ) + { + outputType = _getALUInstructionOutputDataType(shaderContext, aluInstruction); + _emitInstructionOutputVariableName(shaderContext, aluInstruction); + src->add(" = "); + _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); + src->add("exp2"); + src->add("("); + _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(")"); + _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); + src->add(";" _CRLF); + } + else if( aluInstruction->opcode == ALU_OP2_INST_SQRT_IEEE ) + { + outputType = _getALUInstructionOutputDataType(shaderContext, aluInstruction); + _emitInstructionOutputVariableName(shaderContext, aluInstruction); + src->add(" = "); + _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); + src->add("sqrt"); + src->add("("); + _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(")"); + _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); + src->add(";" _CRLF); + } + else if( aluInstruction->opcode == ALU_OP2_INST_SIN || + aluInstruction->opcode == ALU_OP2_INST_COS ) + { + outputType = _getALUInstructionOutputDataType(shaderContext, aluInstruction); + _emitInstructionOutputVariableName(shaderContext, aluInstruction); + src->add(" = "); + _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); + if( aluInstruction->opcode == ALU_OP2_INST_SIN ) + src->add("sin"); + else + src->add("cos"); + src->add("(("); + _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(")/0.1591549367)"); + _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); + src->add(";" _CRLF); + } + else if( aluInstruction->opcode == ALU_OP2_INST_FLT_TO_INT ) + { + outputType = _getALUInstructionOutputDataType(shaderContext, aluInstruction); + _emitInstructionOutputVariableName(shaderContext, aluInstruction); + src->add(" = "); + _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); + src->add("int"); + src->add("("); + _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(")"); + _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); + src->add(";" _CRLF); + } + else if( aluInstruction->opcode == ALU_OP2_INST_FLT_TO_UINT ) + { + outputType = _getALUInstructionOutputDataType(shaderContext, aluInstruction); + _emitInstructionOutputVariableName(shaderContext, aluInstruction); + src->add(" = "); + _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_UNSIGNED_INT, outputType); + src->add("uint"); + src->add("("); + _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(")"); + _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_UNSIGNED_INT, outputType); + src->add(";" _CRLF); + } + else if( aluInstruction->opcode == ALU_OP2_INST_INT_TO_FLOAT ) + { + outputType = _getALUInstructionOutputDataType(shaderContext, aluInstruction); + _emitInstructionOutputVariableName(shaderContext, aluInstruction); + src->add(" = "); + _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); + src->add("float"); + src->add("("); + _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_SIGNED_INT); + src->add(")"); + _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); + src->add(";" _CRLF); + } + else if( aluInstruction->opcode == ALU_OP2_INST_UINT_TO_FLOAT ) + { + outputType = _getALUInstructionOutputDataType(shaderContext, aluInstruction); + _emitInstructionOutputVariableName(shaderContext, aluInstruction); + src->add(" = "); + _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); + src->add("float"); + src->add("("); + _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_UNSIGNED_INT); + src->add(")"); + _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); + src->add(";" _CRLF); + } + else if (aluInstruction->opcode == ALU_OP2_INST_AND_INT) + _emitALUOperationBinary<LATTE_DECOMPILER_DTYPE_SIGNED_INT>(shaderContext, aluInstruction, " & "); + else if (aluInstruction->opcode == ALU_OP2_INST_OR_INT) + _emitALUOperationBinary<LATTE_DECOMPILER_DTYPE_SIGNED_INT>(shaderContext, aluInstruction, " | "); + else if (aluInstruction->opcode == ALU_OP2_INST_XOR_INT) + _emitALUOperationBinary<LATTE_DECOMPILER_DTYPE_SIGNED_INT>(shaderContext, aluInstruction, " ^ "); + else if( aluInstruction->opcode == ALU_OP2_INST_NOT_INT ) + { + outputType = _getALUInstructionOutputDataType(shaderContext, aluInstruction); + _emitInstructionOutputVariableName(shaderContext, aluInstruction); + src->add(" = "); + _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); + src->add("~("); + _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_SIGNED_INT); + src->add(")"); + _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); + src->add(";" _CRLF); + } + else if( aluInstruction->opcode == ALU_OP2_INST_ADD_INT ) + _emitALUOperationBinary<LATTE_DECOMPILER_DTYPE_SIGNED_INT>(shaderContext, aluInstruction, " + "); + else if( aluInstruction->opcode == ALU_OP2_INST_MAX_INT || aluInstruction->opcode == ALU_OP2_INST_MIN_INT ) + { + // not verified + outputType = _getALUInstructionOutputDataType(shaderContext, aluInstruction); + _emitInstructionOutputVariableName(shaderContext, aluInstruction); + if( aluInstruction->opcode == ALU_OP2_INST_MAX_INT ) + src->add(" = max("); + else + src->add(" = min("); + _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); + _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_SIGNED_INT); + src->add(", "); + _emitOperandInputCode(shaderContext, aluInstruction, 1, LATTE_DECOMPILER_DTYPE_SIGNED_INT); + _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); + src->add(");" _CRLF); + } + else if( aluInstruction->opcode == ALU_OP2_INST_SUB_INT ) + { + // note: The AMD doc says src1 is on the left side but tests indicate otherwise. It's src0 - src1. + _emitALUOperationBinary<LATTE_DECOMPILER_DTYPE_SIGNED_INT>(shaderContext, aluInstruction, " - "); + } + else if (aluInstruction->opcode == ALU_OP2_INST_MULLO_INT) + _emitALUOperationBinary<LATTE_DECOMPILER_DTYPE_SIGNED_INT>(shaderContext, aluInstruction, " * "); + else if (aluInstruction->opcode == ALU_OP2_INST_MULLO_UINT) + _emitALUOperationBinary<LATTE_DECOMPILER_DTYPE_UNSIGNED_INT>(shaderContext, aluInstruction, " * "); + else if( aluInstruction->opcode == ALU_OP2_INST_LSHL_INT ) + _emitALUOperationBinary<LATTE_DECOMPILER_DTYPE_SIGNED_INT>(shaderContext, aluInstruction, " << "); + else if( aluInstruction->opcode == ALU_OP2_INST_LSHR_INT ) + _emitALUOperationBinary<LATTE_DECOMPILER_DTYPE_SIGNED_INT>(shaderContext, aluInstruction, " >> "); + else if( aluInstruction->opcode == ALU_OP2_INST_ASHR_INT ) + { + outputType = _getALUInstructionOutputDataType(shaderContext, aluInstruction); + _emitInstructionOutputVariableName(shaderContext, aluInstruction); + src->add(" = "); + _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); + _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_SIGNED_INT); + src->add(" >> "); + _emitOperandInputCode(shaderContext, aluInstruction, 1, LATTE_DECOMPILER_DTYPE_SIGNED_INT); + _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); + src->add(";" _CRLF); + } + else if( aluInstruction->opcode == ALU_OP2_INST_SETGT || + aluInstruction->opcode == ALU_OP2_INST_SETGE || + aluInstruction->opcode == ALU_OP2_INST_SETNE || + aluInstruction->opcode == ALU_OP2_INST_SETE ) + { + outputType = _getALUInstructionOutputDataType(shaderContext, aluInstruction); + _emitInstructionOutputVariableName(shaderContext, aluInstruction); + src->add(" = "); + _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); + src->add("("); + _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); + if( aluInstruction->opcode == ALU_OP2_INST_SETGT ) + src->add(" > "); + else if( aluInstruction->opcode == ALU_OP2_INST_SETGE ) + src->add(" >= "); + else if (aluInstruction->opcode == ALU_OP2_INST_SETNE) + src->add(" != "); + else if (aluInstruction->opcode == ALU_OP2_INST_SETE) + src->add(" == "); + _emitOperandInputCode(shaderContext, aluInstruction, 1, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(")?1.0:0.0"); + _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); + src->add(";" _CRLF); + } + else if( aluInstruction->opcode == ALU_OP2_INST_SETGT_DX10 || + aluInstruction->opcode == ALU_OP2_INST_SETE_DX10 || + aluInstruction->opcode == ALU_OP2_INST_SETNE_DX10 || + aluInstruction->opcode == ALU_OP2_INST_SETGE_DX10 ) + { + if( aluInstruction->omod != 0 ) + debugBreakpoint(); + outputType = _getALUInstructionOutputDataType(shaderContext, aluInstruction); + _emitInstructionOutputVariableName(shaderContext, aluInstruction); + src->add(" = "); + _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); + src->add("(("); + _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); + if( aluInstruction->opcode == ALU_OP2_INST_SETE_DX10 ) + src->add(" == "); + else if( aluInstruction->opcode == ALU_OP2_INST_SETNE_DX10 ) + src->add(" != "); + else if( aluInstruction->opcode == ALU_OP2_INST_SETGT_DX10 ) + src->add(" > "); + else if( aluInstruction->opcode == ALU_OP2_INST_SETGE_DX10 ) + src->add(" >= "); + _emitOperandInputCode(shaderContext, aluInstruction, 1, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(")?int(0xFFFFFFFF):int(0x0))"); + _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); + src->add(";"); + src->add(_CRLF); + } + else if( aluInstruction->opcode == ALU_OP2_INST_SETE_INT || + aluInstruction->opcode == ALU_OP2_INST_SETNE_INT || + aluInstruction->opcode == ALU_OP2_INST_SETGT_INT || + aluInstruction->opcode == ALU_OP2_INST_SETGE_INT ) + { + outputType = _getALUInstructionOutputDataType(shaderContext, aluInstruction); + _emitInstructionOutputVariableName(shaderContext, aluInstruction); + src->add(" = "); + _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); + src->add("("); + _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_SIGNED_INT); + if( aluInstruction->opcode == ALU_OP2_INST_SETE_INT ) + src->add(" == "); + else if( aluInstruction->opcode == ALU_OP2_INST_SETNE_INT ) + src->add(" != "); + else if( aluInstruction->opcode == ALU_OP2_INST_SETGT_INT ) + src->add(" > "); + else if( aluInstruction->opcode == ALU_OP2_INST_SETGE_INT ) + src->add(" >= "); + _emitOperandInputCode(shaderContext, aluInstruction, 1, LATTE_DECOMPILER_DTYPE_SIGNED_INT); + src->add(")?int(0xFFFFFFFF):int(0x0)"); + _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); + src->add(";" _CRLF); + } + else if( aluInstruction->opcode == ALU_OP2_INST_SETGE_UINT || + aluInstruction->opcode == ALU_OP2_INST_SETGT_UINT ) + { + // todo: Unsure if the result is unsigned or signed + outputType = _getALUInstructionOutputDataType(shaderContext, aluInstruction); + _emitInstructionOutputVariableName(shaderContext, aluInstruction); + src->add(" = "); + _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); + src->add("("); + _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_UNSIGNED_INT); + if( aluInstruction->opcode == ALU_OP2_INST_SETGE_UINT ) + src->add(" >= "); + else if( aluInstruction->opcode == ALU_OP2_INST_SETGT_UINT ) + src->add(" > "); + _emitOperandInputCode(shaderContext, aluInstruction, 1, LATTE_DECOMPILER_DTYPE_UNSIGNED_INT); + src->add(")?int(0xFFFFFFFF):int(0x0)"); + _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); + src->add(";" _CRLF); + } + else if( aluInstruction->opcode == ALU_OP2_INST_PRED_SETGT || + aluInstruction->opcode == ALU_OP2_INST_PRED_SETGE || + aluInstruction->opcode == ALU_OP2_INST_PRED_SETE || + aluInstruction->opcode == ALU_OP2_INST_PRED_SETNE || + aluInstruction->opcode == ALU_OP2_INST_PRED_SETNE_INT || + aluInstruction->opcode == ALU_OP2_INST_PRED_SETE_INT || + aluInstruction->opcode == ALU_OP2_INST_PRED_SETGE_INT || + aluInstruction->opcode == ALU_OP2_INST_PRED_SETGT_INT ) + { + if( aluInstruction->writeMask != 0 ) + debugBreakpoint(); + + bool isIntPred = (aluInstruction->opcode == ALU_OP2_INST_PRED_SETNE_INT) || (aluInstruction->opcode == ALU_OP2_INST_PRED_SETE_INT) || (aluInstruction->opcode == ALU_OP2_INST_PRED_SETGE_INT) || (aluInstruction->opcode == ALU_OP2_INST_PRED_SETGT_INT); + + src->add("predResult"); + src->add(" = ("); + _emitOperandInputCode(shaderContext, aluInstruction, 0, isIntPred?LATTE_DECOMPILER_DTYPE_SIGNED_INT:LATTE_DECOMPILER_DTYPE_FLOAT); + + if (aluInstruction->opcode == ALU_OP2_INST_PRED_SETE || aluInstruction->opcode == ALU_OP2_INST_PRED_SETE_INT) + src->add(" == "); + else if (aluInstruction->opcode == ALU_OP2_INST_PRED_SETGT || aluInstruction->opcode == ALU_OP2_INST_PRED_SETGT_INT) + src->add(" > "); + else if (aluInstruction->opcode == ALU_OP2_INST_PRED_SETGE || aluInstruction->opcode == ALU_OP2_INST_PRED_SETGE_INT) + src->add(" >= "); + else if (aluInstruction->opcode == ALU_OP2_INST_PRED_SETNE || aluInstruction->opcode == ALU_OP2_INST_PRED_SETNE_INT) + src->add(" != "); + else + cemu_assert_debug(false); + + _emitOperandInputCode(shaderContext, aluInstruction, 1, isIntPred?LATTE_DECOMPILER_DTYPE_SIGNED_INT:LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(");" _CRLF); + // handle result of predicate instruction based on current ALU clause type + if( cfInstruction->type == GPU7_CF_INST_ALU_PUSH_BEFORE ) + { + src->addFmt("{} = predResult;" _CRLF, _getActiveMaskVarName(shaderContext, cfInstruction->activeStackDepth)); + src->addFmt("{} = predResult == true && {} == true;" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1), _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth)); + } + else if( cfInstruction->type == GPU7_CF_INST_ALU_BREAK ) + { + // leave current loop + src->add("if( predResult == false ) break;" _CRLF); + } + else + cemu_assert_debug(false); + } + else if( aluInstruction->opcode == ALU_OP2_INST_KILLE_INT || + aluInstruction->opcode == ALU_OP2_INST_KILLNE_INT || + aluInstruction->opcode == ALU_OP2_INST_KILLGT_INT) + { + src->add("if( "); + src->add(" ("); + _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_SIGNED_INT); + if( aluInstruction->opcode == ALU_OP2_INST_KILLE_INT ) + src->add(" == "); + else if (aluInstruction->opcode == ALU_OP2_INST_KILLNE_INT) + src->add(" != "); + else if (aluInstruction->opcode == ALU_OP2_INST_KILLGT_INT) + src->add(" > "); + else + debugBreakpoint(); + _emitOperandInputCode(shaderContext, aluInstruction, 1, LATTE_DECOMPILER_DTYPE_SIGNED_INT); + src->add(")"); + src->add(") discard;"); + src->add(_CRLF); + } + else if( aluInstruction->opcode == ALU_OP2_INST_KILLGT || + aluInstruction->opcode == ALU_OP2_INST_KILLGE || + aluInstruction->opcode == ALU_OP2_INST_KILLE ) + { + src->add("if( "); + src->add(" ("); + _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); + if( aluInstruction->opcode == ALU_OP2_INST_KILLGT ) + src->add(" > "); + else if( aluInstruction->opcode == ALU_OP2_INST_KILLGE ) + src->add(" >= "); + else if( aluInstruction->opcode == ALU_OP2_INST_KILLE ) + src->add(" == "); + else + debugBreakpoint(); + _emitOperandInputCode(shaderContext, aluInstruction, 1, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(")"); + src->add(") discard;"); + src->add(_CRLF); + } + else + { + src->add("Unsupported instruction;" _CRLF); + debug_printf("Unsupported ALU op2 instruction 0x%x\n", aluInstruction->opcode); + shaderContext->shader->hasError = true; + } +} + +void _emitALUOP3InstructionCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerCFInstruction* cfInstruction, LatteDecompilerALUInstruction* aluInstruction) +{ + StringBuf* src = shaderContext->shaderSource; + cemu_assert_debug(aluInstruction->destRel == 0); // todo + sint32 outputType; // data type of output + if( aluInstruction->opcode == ALU_OP3_INST_MULADD || + aluInstruction->opcode == ALU_OP3_INST_MULADD_D2 || + aluInstruction->opcode == ALU_OP3_INST_MULADD_M2 || + aluInstruction->opcode == ALU_OP3_INST_MULADD_M4 || + aluInstruction->opcode == ALU_OP3_INST_MULADD_IEEE ) + { + // todo: Figure out the exact difference between normal MULADD and MULADD_IEEE + outputType = _getALUInstructionOutputDataType(shaderContext, aluInstruction); + _emitInstructionOutputVariableName(shaderContext, aluInstruction); + src->add(" = "); + _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); + src->add("("); + + bool useDefaultMul = false; + if (GPU7_ALU_SRC_IS_LITERAL(aluInstruction->sourceOperand[0].sel) || GPU7_ALU_SRC_IS_LITERAL(aluInstruction->sourceOperand[1].sel) || + GPU7_ALU_SRC_IS_ANY_CONST(aluInstruction->sourceOperand[0].sel) || GPU7_ALU_SRC_IS_ANY_CONST(aluInstruction->sourceOperand[1].sel)) + { + useDefaultMul = true; + } + if (aluInstruction->opcode == ALU_OP3_INST_MULADD_IEEE) + useDefaultMul = true; + + if (g_current_game_profile->GetAccurateShaderMul() != AccurateShaderMulOption::False && useDefaultMul == false) + { + src->add("mul_nonIEEE("); + _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(","); + _emitOperandInputCode(shaderContext, aluInstruction, 1, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(")"); + } + else + { + _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(" * "); + _emitOperandInputCode(shaderContext, aluInstruction, 1, LATTE_DECOMPILER_DTYPE_FLOAT); + } + + src->add(" + "); + _emitOperandInputCode(shaderContext, aluInstruction, 2, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(")"); + if( aluInstruction->opcode == ALU_OP3_INST_MULADD_D2 ) + src->add("/2.0"); + else if( aluInstruction->opcode == ALU_OP3_INST_MULADD_M2 ) + src->add("*2.0"); + else if( aluInstruction->opcode == ALU_OP3_INST_MULADD_M4 ) + src->add("*4.0"); + _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); + src->add(";" _CRLF); + } + else if(aluInstruction->opcode == ALU_OP3_INST_CNDE_INT || aluInstruction->opcode == ALU_OP3_INST_CNDGT_INT || aluInstruction->opcode == ALU_OP3_INST_CMOVGE_INT) + { + bool requiresFloatResult = (aluInstruction->sourceOperand[1].neg != 0) || (aluInstruction->sourceOperand[2].neg != 0); + outputType = _getALUInstructionOutputDataType(shaderContext, aluInstruction); + _emitInstructionOutputVariableName(shaderContext, aluInstruction); + src->add(" = "); + _emitTypeConversionPrefix(shaderContext, requiresFloatResult?LATTE_DECOMPILER_DTYPE_FLOAT:LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); + src->add("(("); + _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_SIGNED_INT); + //src->add(" == 0)?("); + if (aluInstruction->opcode == ALU_OP3_INST_CNDE_INT) + src->add(" == "); + else if (aluInstruction->opcode == ALU_OP3_INST_CNDGT_INT) + src->add(" > "); + else if (aluInstruction->opcode == ALU_OP3_INST_CMOVGE_INT) + src->add(" >= "); + src->add("0)?("); + + _emitOperandInputCode(shaderContext, aluInstruction, 1, requiresFloatResult?LATTE_DECOMPILER_DTYPE_FLOAT:LATTE_DECOMPILER_DTYPE_SIGNED_INT); + src->add("):("); + _emitOperandInputCode(shaderContext, aluInstruction, 2, requiresFloatResult?LATTE_DECOMPILER_DTYPE_FLOAT:LATTE_DECOMPILER_DTYPE_SIGNED_INT); + src->add("))"); + _emitTypeConversionSuffix(shaderContext, requiresFloatResult?LATTE_DECOMPILER_DTYPE_FLOAT:LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); + src->add(";" _CRLF); + } + else if( aluInstruction->opcode == ALU_OP3_INST_CMOVGE || + aluInstruction->opcode == ALU_OP3_INST_CMOVE || + aluInstruction->opcode == ALU_OP3_INST_CMOVGT ) + { + outputType = _getALUInstructionOutputDataType(shaderContext, aluInstruction); + _emitInstructionOutputVariableName(shaderContext, aluInstruction); + src->add(" = "); + _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); + src->add("(("); + _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); + if( aluInstruction->opcode == ALU_OP3_INST_CMOVE ) + src->add(" == "); + else if( aluInstruction->opcode == ALU_OP3_INST_CMOVGE ) + src->add(" >= "); + else if( aluInstruction->opcode == ALU_OP3_INST_CMOVGT ) + src->add(" > "); + src->add("0.0)?("); + _emitOperandInputCode(shaderContext, aluInstruction, 1, LATTE_DECOMPILER_DTYPE_SIGNED_INT); + src->add("):("); + _emitOperandInputCode(shaderContext, aluInstruction, 2, LATTE_DECOMPILER_DTYPE_SIGNED_INT); + src->add("))"); + _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); + src->add(";" _CRLF); + } + else + { + src->add("Unsupported instruction;" _CRLF); + debug_printf("Unsupported ALU op3 instruction 0x%x\n", aluInstruction->opcode); + shaderContext->shader->hasError = true; + } +} + +void _emitALUReductionInstructionCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerALUInstruction* aluRedcInstruction[4]) +{ + StringBuf* src = shaderContext->shaderSource; + if( aluRedcInstruction[0]->isOP3 == false && (aluRedcInstruction[0]->opcode == ALU_OP2_INST_DOT4 || aluRedcInstruction[0]->opcode == ALU_OP2_INST_DOT4_IEEE) ) + { + // todo: Figure out and implement the difference between normal DOT4 and DOT4_IEEE + sint32 outputType = _getALUInstructionOutputDataType(shaderContext, aluRedcInstruction[0]); + _emitInstructionOutputVariableName(shaderContext, aluRedcInstruction[0]); + src->add(" = "); + _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); + + // dot(vec4(op0),vec4(op1)) + src->add("dot(vec4("); + _emitOperandInputCode(shaderContext, aluRedcInstruction[0], 0, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(","); + _emitOperandInputCode(shaderContext, aluRedcInstruction[1], 0, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(","); + _emitOperandInputCode(shaderContext, aluRedcInstruction[2], 0, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(","); + _emitOperandInputCode(shaderContext, aluRedcInstruction[3], 0, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add("),vec4("); + _emitOperandInputCode(shaderContext, aluRedcInstruction[0], 1, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(","); + _emitOperandInputCode(shaderContext, aluRedcInstruction[1], 1, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(","); + _emitOperandInputCode(shaderContext, aluRedcInstruction[2], 1, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(","); + _emitOperandInputCode(shaderContext, aluRedcInstruction[3], 1, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add("))"); + + _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); + src->add(";" _CRLF); + } + else if( aluRedcInstruction[0]->isOP3 == false && (aluRedcInstruction[0]->opcode == ALU_OP2_INST_CUBE) ) + { + /* + * How the CUBE instruction works (guessed mostly, based on DirectX/OpenGL spec): + Input: vec4, 3d direction vector (can be unnormalized) + w component (which can be ignored, since it only scales the vector but does not affect the direction) + + First we figure out the major axis (closest axis-aligned vector). There are six possible vectors: + +rx 0 + -rx 1 + +ry 2 + -ry 3 + +rz 4 + -rz 5 + The major axis vector is calculated by looking at the largest (absolute) 3d vector component and then setting the other components to 0.0 + The value that remains in the axis vector is referred to as 'MajorAxis' by the AMD documentation. + The S,T coordinates are taken from the other two components. + Example: -0.5,0.2,0.4 -> -rx -> -0.5,0.0,0.0 MajorAxis: -0.5, S: 0.2 T: 0.4 + + The CUBE reduction instruction requires a specific mapping for the input vector: + src0 = Rn.zzxy + src1 = Rn.yxzz + It's probably related to the way the instruction works internally? + If we look at the individual components per ALU unit: + z y -> Compare y/z + z x -> Compare x/z + x z -> Compare x/z + y z -> Compare y/z + */ + + sint32 outputType; + + src->add("redcCUBE("); + src->add("vec4("); + _emitOperandInputCode(shaderContext, aluRedcInstruction[0], 0, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(","); + _emitOperandInputCode(shaderContext, aluRedcInstruction[1], 0, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(","); + _emitOperandInputCode(shaderContext, aluRedcInstruction[2], 0, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(","); + _emitOperandInputCode(shaderContext, aluRedcInstruction[3], 0, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add("),"); + src->add("vec4("); + _emitOperandInputCode(shaderContext, aluRedcInstruction[0], 1, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(","); + _emitOperandInputCode(shaderContext, aluRedcInstruction[1], 1, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(","); + _emitOperandInputCode(shaderContext, aluRedcInstruction[2], 1, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(","); + _emitOperandInputCode(shaderContext, aluRedcInstruction[3], 1, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add("),"); + src->add("cubeMapSTM,cubeMapFaceId);" _CRLF); + + // dst.X (S) + outputType = _getALUInstructionOutputDataType(shaderContext, aluRedcInstruction[0]); + _emitInstructionOutputVariableName(shaderContext, aluRedcInstruction[0]); + src->add(" = "); + _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); + src->add("cubeMapSTM.x"); + _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); + src->add(";" _CRLF); + // dst.Y (T) + outputType = _getALUInstructionOutputDataType(shaderContext, aluRedcInstruction[1]); + _emitInstructionOutputVariableName(shaderContext, aluRedcInstruction[1]); + src->add(" = "); + _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); + src->add("cubeMapSTM.y"); + _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); + src->add(";" _CRLF); + // dst.Z (MajorAxis) + outputType = _getALUInstructionOutputDataType(shaderContext, aluRedcInstruction[2]); + _emitInstructionOutputVariableName(shaderContext, aluRedcInstruction[2]); + src->add(" = "); + _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); + src->add("cubeMapSTM.z"); + _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); + src->add(";" _CRLF); + // dst.W (FaceId) + outputType = _getALUInstructionOutputDataType(shaderContext, aluRedcInstruction[3]); + _emitInstructionOutputVariableName(shaderContext, aluRedcInstruction[3]); + src->add(" = "); + _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); + src->add("cubeMapFaceId"); + _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); + src->add(";" _CRLF); + } + else + cemu_assert_unimplemented(); +} + +void _emitALUClauseRegisterBackupCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerCFInstruction* cfInstruction, sint32 startIndex, sint32 instructionGroupIndex) +{ + StringBuf* src = shaderContext->shaderSource; + for (size_t i = (uint32)startIndex; i < cfInstruction->instructionsALU.size(); i++) + { + LatteDecompilerALUInstruction& aluInstruction = cfInstruction->instructionsALU[i]; + if( aluInstruction.instructionGroupIndex != instructionGroupIndex ) + break; // end of current group + // ignore NOP instruction + if( aluInstruction.isOP3 == false && aluInstruction.opcode == ALU_OP2_INST_NOP ) + continue; + // check if there are any operand input registers that need to be kept intact + for(sint32 f=0; f<3; f++) + { + if( GPU7_ALU_SRC_IS_GPR(aluInstruction.sourceOperand[f].sel) == false ) + continue; + if( aluInstruction.sourceOperand[f].requiredRegisterBackup == false ) + continue; + sint32 gprIndex = GPU7_ALU_SRC_GET_GPR_INDEX(aluInstruction.sourceOperand[f].sel); + if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) + src->addFmt("backupReg{}i = ", aluInstruction.sourceOperand[f].registerBackupIndex); + else if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_FLOAT) + src->addFmt("backupReg{}f = ", aluInstruction.sourceOperand[f].registerBackupIndex); + src->add(_getRegisterVarName(shaderContext, gprIndex)); + _appendChannelAccess(src, aluInstruction.sourceOperand[f].chan); + src->add(";" _CRLF); + } + } +} + +bool _isPVUsedInNextGroup(LatteDecompilerCFInstruction* cfInstruction, sint32 startIndex, sint32 pvUnit) +{ + sint32 currentGroupIndex = cfInstruction->instructionsALU[startIndex].instructionGroupIndex; + for (sint32 i = startIndex + 1; i < (sint32)cfInstruction->instructionsALU.size(); i++) + { + LatteDecompilerALUInstruction& aluInstructionItr = cfInstruction->instructionsALU[i]; + if(aluInstructionItr.instructionGroupIndex == currentGroupIndex ) + continue; + if ((sint32)aluInstructionItr.instructionGroupIndex > currentGroupIndex + 1) + return false; + // check OP code type + if (aluInstructionItr.isOP3) + { + // op0 + if (GPU7_ALU_SRC_IS_PV(aluInstructionItr.sourceOperand[0].sel)) + { + uint32 chan = aluInstructionItr.sourceOperand[0].chan; + if (pvUnit == chan) + return true; + } + // op1 + if (GPU7_ALU_SRC_IS_PV(aluInstructionItr.sourceOperand[1].sel)) + { + uint32 chan = aluInstructionItr.sourceOperand[1].chan; + if (pvUnit == chan) + return true; + } + // op2 + if (GPU7_ALU_SRC_IS_PV(aluInstructionItr.sourceOperand[2].sel)) + { + uint32 chan = aluInstructionItr.sourceOperand[2].chan; + if (pvUnit == chan) + return true; + } + } + else + { + // op0 + if (GPU7_ALU_SRC_IS_PV(aluInstructionItr.sourceOperand[0].sel)) + { + uint32 chan = aluInstructionItr.sourceOperand[0].chan; + if (pvUnit == chan) + return true; + } + // op1 + if (GPU7_ALU_SRC_IS_PV(aluInstructionItr.sourceOperand[1].sel)) + { + uint32 chan = aluInstructionItr.sourceOperand[1].chan; + if (pvUnit == chan) + return true; + } + // todo: Not all operations use both operands + } + } + return false; +} + +void _emitVec3(LatteDecompilerShaderContext* shaderContext, uint32 dataType, LatteDecompilerALUInstruction* aluInst0, sint32 opIdx0, LatteDecompilerALUInstruction* aluInst1, sint32 opIdx1, LatteDecompilerALUInstruction* aluInst2, sint32 opIdx2) +{ + StringBuf* src = shaderContext->shaderSource; + if (dataType == LATTE_DECOMPILER_DTYPE_FLOAT) + { + src->add("vec3("); + _emitOperandInputCode(shaderContext, aluInst0, opIdx0, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(","); + _emitOperandInputCode(shaderContext, aluInst1, opIdx1, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(","); + _emitOperandInputCode(shaderContext, aluInst2, opIdx2, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(")"); + } + else if (dataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) + { + src->add("ivec3("); + _emitOperandInputCode(shaderContext, aluInst0, opIdx0, LATTE_DECOMPILER_DTYPE_SIGNED_INT); + src->add(","); + _emitOperandInputCode(shaderContext, aluInst1, opIdx1, LATTE_DECOMPILER_DTYPE_SIGNED_INT); + src->add(","); + _emitOperandInputCode(shaderContext, aluInst2, opIdx2, LATTE_DECOMPILER_DTYPE_SIGNED_INT); + src->add(")"); + } + else + cemu_assert_unimplemented(); +} + +void _emitGPRVectorAssignment(LatteDecompilerShaderContext* shaderContext, LatteDecompilerALUInstruction** aluInstructions, sint32 count) +{ + StringBuf* src = shaderContext->shaderSource; + // output var name (GPR) + src->add(_getRegisterVarName(shaderContext, aluInstructions[0]->destGpr, -1)); + src->add("."); + for (sint32 f = 0; f < count; f++) + { + src->add(_getElementStrByIndex(aluInstructions[f]->destElem)); + } + src->add(" = "); +} + +/* + * Analyze 3 instructions from the same group and generate vectorized/optimized code if possible + * Return true if code was generated + */ +bool _handleInstructionPattern3(LatteDecompilerShaderContext* shaderContext, LatteDecompilerCFInstruction* cfInstruction, sint32 startIndex, LatteDecompilerALUInstruction** instructionBlock) +{ + // check if omod and clamp is zero + if (instructionBlock[0]->omod != 0 || instructionBlock[0]->destClamp != 0) + return false; + if (instructionBlock[1]->omod != 0 || instructionBlock[1]->destClamp != 0) + return false; + if (instructionBlock[2]->omod != 0 || instructionBlock[2]->destClamp != 0) + return false; + // check if destination operand is the same register + // todo: Also check for PV + if (instructionBlock[0]->writeMask == 0 || + instructionBlock[1]->writeMask == 0 || + instructionBlock[2]->writeMask == 0 + ) + return false; + if (instructionBlock[0]->destGpr != instructionBlock[1]->destGpr || instructionBlock[1]->destGpr != instructionBlock[2]->destGpr) + return false; + if (instructionBlock[0]->destRel != 0 || instructionBlock[1]->destRel != 0 || instructionBlock[2]->destRel != 0) + return false; + // make sure all instructions are of type OP2 + //if (instructionBlock[0]->isOP3 || instructionBlock[1]->isOP3 || instructionBlock[2]->isOP3) + // return false; + if (instructionBlock[0]->isOP3 != instructionBlock[1]->isOP3 || instructionBlock[1]->isOP3 != instructionBlock[2]->isOP3) + return false; + // check if operation is the same + if (instructionBlock[0]->opcode != instructionBlock[1]->opcode || instructionBlock[1]->opcode != instructionBlock[2]->opcode) + return false; + // at this point we know the following conditions are true for the instruction block: + // All output to the same GPR + // All share the same OP2 operation + // omod and destClamp is 0 + StringBuf* src = shaderContext->shaderSource; + if (instructionBlock[0]->isOP3 == false && instructionBlock[0]->opcode == ALU_OP2_INST_MOV) + { + uint32 outputType = _getALUInstructionOutputDataType(shaderContext, instructionBlock[0]); + _emitGPRVectorAssignment(shaderContext, instructionBlock, 3); + _emitVec3(shaderContext, outputType, instructionBlock[0], 0, instructionBlock[1], 0, instructionBlock[2], 0); + src->add(";" _CRLF); + } + else if (instructionBlock[0]->isOP3 == false && instructionBlock[0]->opcode == ALU_OP2_INST_ADD) + { + uint32 outputType = _getALUInstructionOutputDataType(shaderContext, instructionBlock[0]); + _emitGPRVectorAssignment(shaderContext, instructionBlock, 3); + _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); + _emitVec3(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, instructionBlock[0], 0, instructionBlock[1], 0, instructionBlock[2], 0); + src->add(" + "); + _emitVec3(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, instructionBlock[0], 1, instructionBlock[1], 1, instructionBlock[2], 1); + _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, outputType); + src->add(";" _CRLF); + } + else + return false; + // handle PV/PS + for (sint32 i = 0; i < 3; i++) + { + LatteDecompilerALUInstruction* aluInstruction = instructionBlock[i]; + bool skipPV = false; + if (aluInstruction->writeMask == 0) + skipPV = true; // no write -> Value already written to PV/PS + if (aluInstruction->aluUnit < 4 && _isPVUsedInNextGroup(cfInstruction, startIndex, aluInstruction->aluUnit) == false) + skipPV = true; + if (skipPV == false) + { + _emitInstructionPVPSOutputVariableName(shaderContext, aluInstruction); + src->add(" = "); + _emitInstructionOutputVariableName(shaderContext, aluInstruction); + src->add(";" _CRLF); + } + // todo: We can vectorize this part as well + } + return true; +} + +void _emitALUClauseCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerCFInstruction* cfInstruction) +{ + StringBuf* src = shaderContext->shaderSource; + LatteDecompilerALUInstruction* aluRedcInstruction[4]; + for(size_t i=0; i<cfInstruction->instructionsALU.size(); i++) + { + LatteDecompilerALUInstruction& aluInstruction = cfInstruction->instructionsALU[i]; + if( aluInstruction.indexInGroup == 0 ) + { + src->addFmt("// {}" _CRLF, aluInstruction.instructionGroupIndex); + // create register backup code for this group (if required) + _emitALUClauseRegisterBackupCode(shaderContext, cfInstruction, i, aluInstruction.instructionGroupIndex); + } + // detect reduction instructions and use a special handler + bool isReductionOperation = _isReductionInstruction(&aluInstruction); + if( isReductionOperation ) + { + if( (i+4)> cfInstruction->instructionsALU.size()) + debugBreakpoint(); + aluRedcInstruction[0] = &aluInstruction; + aluRedcInstruction[1] = &cfInstruction->instructionsALU[i + 1]; + aluRedcInstruction[2] = &cfInstruction->instructionsALU[i + 2]; + aluRedcInstruction[3] = &cfInstruction->instructionsALU[i + 3]; + if( aluRedcInstruction[0]->isOP3 != aluRedcInstruction[1]->isOP3 || aluRedcInstruction[1]->isOP3 != aluRedcInstruction[2]->isOP3 || aluRedcInstruction[2]->isOP3 != aluRedcInstruction[3]->isOP3 ) + debugBreakpoint(); + if( aluRedcInstruction[0]->opcode != aluRedcInstruction[1]->opcode || aluRedcInstruction[1]->opcode != aluRedcInstruction[2]->opcode || aluRedcInstruction[2]->opcode != aluRedcInstruction[3]->opcode ) + debugBreakpoint(); + if( aluRedcInstruction[0]->omod != aluRedcInstruction[1]->omod || aluRedcInstruction[1]->omod != aluRedcInstruction[2]->omod || aluRedcInstruction[2]->omod != aluRedcInstruction[3]->omod ) + debugBreakpoint(); + if( aluRedcInstruction[0]->destClamp != aluRedcInstruction[1]->destClamp || aluRedcInstruction[1]->destClamp != aluRedcInstruction[2]->destClamp || aluRedcInstruction[2]->destClamp != aluRedcInstruction[3]->destClamp ) + debugBreakpoint(); + _emitALUReductionInstructionCode(shaderContext, aluRedcInstruction); + i += 3; // skip the three following instructions that are part of the reduction operation + } + // detect and handle common instruction patterns (for example, vectorizable operations) + if (isReductionOperation == false) + { + sint32 remainingInstructions = cfInstruction->instructionsALU.size() - i; + LatteDecompilerALUInstruction* instructionBlock[4]; + // scan for instruction patterns of length 4 + // todo + // scan for instruction patterns of length 3 + if (remainingInstructions >= 3) + { + instructionBlock[0] = &aluInstruction; + instructionBlock[1] = &cfInstruction->instructionsALU[i + 1]; + instructionBlock[2] = &cfInstruction->instructionsALU[i + 2]; + if (instructionBlock[0]->instructionGroupIndex == instructionBlock[1]->instructionGroupIndex && instructionBlock[1]->instructionGroupIndex == instructionBlock[2]->instructionGroupIndex) + { + if (_handleInstructionPattern3(shaderContext, cfInstruction, i, instructionBlock)) + { + i += 2; + continue; + } + } + } + // scan for instruction patterns of length 2 + // todo + } + // handle instruction (if not a reduction operation, else it would already have been handled above) + if( isReductionOperation == false ) + { + if( aluInstruction.isOP3 ) + { + // op3 + _emitALUOP3InstructionCode(shaderContext, cfInstruction, &aluInstruction); + } + else + { + // op2 + if( aluInstruction.opcode == ALU_OP2_INST_NOP ) + continue; // skip NOP instruction + _emitALUOP2InstructionCode(shaderContext, cfInstruction, &aluInstruction); + } + } + // handle omod + sint32 outputDataType = _getALUInstructionOutputDataType(shaderContext, &aluInstruction); + if( aluInstruction.omod != ALU_OMOD_NONE ) + { + if( outputDataType == LATTE_DECOMPILER_DTYPE_FLOAT ) + { + _emitInstructionOutputVariableName(shaderContext, &aluInstruction); + if( aluInstruction.omod == ALU_OMOD_MUL2 ) + src->add(" *= 2.0;" _CRLF); + else if( aluInstruction.omod == ALU_OMOD_MUL4 ) + src->add(" *= 4.0;" _CRLF); + else if( aluInstruction.omod == ALU_OMOD_DIV2 ) + src->add(" /= 2.0;" _CRLF); + } + else if( outputDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT ) + { + _emitInstructionOutputVariableName(shaderContext, &aluInstruction); + src->add(" = "); + src->add("floatBitsToInt(intBitsToFloat("); + _emitInstructionOutputVariableName(shaderContext, &aluInstruction); + src->add(")"); + if( aluInstruction.omod == 1 ) + src->add(" * 2.0"); + else if( aluInstruction.omod == 2 ) + src->add(" * 4.0"); + else if( aluInstruction.omod == 3 ) + src->add(" / 2.0"); + src->add(");" _CRLF); + } + else + { + debugBreakpoint(); + } + } + // handle clamp + if( aluInstruction.destClamp != 0 ) + { + if( outputDataType == LATTE_DECOMPILER_DTYPE_FLOAT ) + { + _emitInstructionOutputVariableName(shaderContext, &aluInstruction); + src->add(" = clamp("); + _emitInstructionOutputVariableName(shaderContext, &aluInstruction); + src->add(", 0.0, 1.0);" _CRLF); + } + else if( outputDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT ) + { + _emitInstructionOutputVariableName(shaderContext, &aluInstruction); + src->add(" = clampFI32("); + _emitInstructionOutputVariableName(shaderContext, &aluInstruction); + src->add(");" _CRLF); + } + else + { + debugBreakpoint(); + } + } + // set PV or PS + if( isReductionOperation == false ) + { + // make sure the result wasn't already written to PV/PS + bool skipPV = false; + if (aluInstruction.writeMask == 0) + skipPV = true; // no write -> Value already written to PV/PS + if (aluInstruction.aluUnit < 4 && _isPVUsedInNextGroup(cfInstruction, i, aluInstruction.aluUnit) == false) + skipPV = true; + if(skipPV == false) + { + _emitInstructionPVPSOutputVariableName(shaderContext, &aluInstruction); + src->add(" = "); + _emitInstructionOutputVariableName(shaderContext, &aluInstruction); + src->add(";" _CRLF); + } + } + else + { + // reduction operations set all four PV components (todo: Needs further research. According to AMD docs, dot4 only sets PV.x? update: Unlike DOT4, CUBE sets all PV elements accordingly to their GPR output?) + if( aluRedcInstruction[0]->opcode == ALU_OP2_INST_CUBE ) + { + // CUBE + for(sint32 f=0; f<4; f++) + { + _emitInstructionPVPSOutputVariableName(shaderContext, aluRedcInstruction[f]); + src->add(" = "); + _emitInstructionOutputVariableName(shaderContext, aluRedcInstruction[f]); + src->add(";" _CRLF); + } + } + else + { + // DOT4, DOT4_IEEE, etc. + for(sint32 f=0; f<4; f++) + { + _emitInstructionPVPSOutputVariableName(shaderContext, aluRedcInstruction[f]); + src->add(" = "); + _emitInstructionOutputVariableName(shaderContext, aluRedcInstruction[0]); + src->add(";" _CRLF); + } + // reduction operations are only set for output in redc[0], we also need to update redc[1] to redc[3] + for(sint32 f=1; f<4; f++) + { + if( aluRedcInstruction[f]->writeMask == 0 ) + continue; + _emitInstructionOutputVariableName(shaderContext, aluRedcInstruction[f]); + src->add(" = "); + _emitInstructionOutputVariableName(shaderContext, aluRedcInstruction[0]); + src->add(";" _CRLF); + } + } + } + } +} + +/* + * Emits code to access one component (xyzw) of the texture coordinate input vector + */ +void _emitTEXSampleCoordInputComponent(LatteDecompilerShaderContext* shaderContext, LatteDecompilerTEXInstruction* texInstruction, sint32 componentIndex, sint32 varType) +{ + StringBuf* src = shaderContext->shaderSource; + if( componentIndex >= 4 ) + { + debugBreakpoint(); + return; + } + sint32 elementSel = texInstruction->textureFetch.srcSel[componentIndex]; + const char* resultElemTable[4] = {"x","y","z","w"}; + if( varType == LATTE_DECOMPILER_DTYPE_SIGNED_INT ) + { + if (elementSel < 4) + { + if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) + src->addFmt("{}.{}", _getRegisterVarName(shaderContext, texInstruction->srcGpr), resultElemTable[elementSel]); + else if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_FLOAT) + src->addFmt("floatBitsToInt({}.{})", _getRegisterVarName(shaderContext, texInstruction->srcGpr), resultElemTable[elementSel]); + else + { + cemu_assert_unimplemented(); + } + } + else if( elementSel == 4 ) + src->add("floatBitsToInt(0.0)"); + else if( elementSel == 5 ) + src->add("floatBitsToInt(1.0)"); + else + { + cemu_assert_unimplemented(); + } + } + else if( varType == LATTE_DECOMPILER_DTYPE_FLOAT ) + { + if (elementSel < 4) + { + if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) + src->addFmt("intBitsToFloat({}.{})", _getRegisterVarName(shaderContext, texInstruction->srcGpr), resultElemTable[elementSel]); + else if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_FLOAT) + src->addFmt("{}.{}", _getRegisterVarName(shaderContext, texInstruction->srcGpr), resultElemTable[elementSel]); + else + { + cemu_assert_unimplemented(); + } + } + else if( elementSel == 4 ) + src->addFmt("0.0"); + else if( elementSel == 5 ) + src->addFmt("1.0"); + else + { + cemu_assert_unimplemented(); + } + } + else + { + cemu_assert_unimplemented(); + } +} + +const char* _texGprAccessElemTable[8] = {"x","y","z","w","_","_","_","_"}; + +char* _getTexGPRAccess(LatteDecompilerShaderContext* shaderContext, sint32 gprIndex, uint32 dataType, sint8 selX, sint8 selY, sint8 selZ, sint8 selW, char* tempBuffer) +{ + // intBitsToFloat(R{}i.w) + *tempBuffer = '\0'; + uint8 elemCount = (selX > 0 ? 1 : 0) + (selY > 0 ? 1 : 0) + (selZ > 0 ? 1 : 0) + (selW > 0 ? 1 : 0); + if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) + { + if (dataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) + ; // no conversion + else if (dataType == LATTE_DECOMPILER_DTYPE_FLOAT) + strcat(tempBuffer, "intBitsToFloat("); + else + cemu_assert_unimplemented(); + strcat(tempBuffer, _getRegisterVarName(shaderContext, gprIndex)); + // _texGprAccessElemTable + strcat(tempBuffer, "."); + if (selX >= 0) + strcat(tempBuffer, _texGprAccessElemTable[selX]); + if (selY >= 0) + strcat(tempBuffer, _texGprAccessElemTable[selY]); + if (selZ >= 0) + strcat(tempBuffer, _texGprAccessElemTable[selZ]); + if (selW >= 0) + strcat(tempBuffer, _texGprAccessElemTable[selW]); + if (dataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) + ; // no conversion + else if (dataType == LATTE_DECOMPILER_DTYPE_FLOAT) + strcat(tempBuffer, ")"); + else + cemu_assert_unimplemented(); + } + else if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_FLOAT) + { + if (dataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) + cemu_assert_unimplemented(); + else if (dataType == LATTE_DECOMPILER_DTYPE_FLOAT) + ; // no conversion + else + cemu_assert_unimplemented(); + strcat(tempBuffer, _getRegisterVarName(shaderContext, gprIndex)); + // _texGprAccessElemTable + strcat(tempBuffer, "."); + if (selX >= 0) + strcat(tempBuffer, _texGprAccessElemTable[selX]); + if (selY >= 0) + strcat(tempBuffer, _texGprAccessElemTable[selY]); + if (selZ >= 0) + strcat(tempBuffer, _texGprAccessElemTable[selZ]); + if (selW >= 0) + strcat(tempBuffer, _texGprAccessElemTable[selW]); + if (dataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) + cemu_assert_unimplemented(); + else if (dataType == LATTE_DECOMPILER_DTYPE_FLOAT) + ; // no conversion + else + cemu_assert_unimplemented(); + } + else + cemu_assert_unimplemented(); + return tempBuffer; +} + +void _emitTEXSampleTextureCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerTEXInstruction* texInstruction) +{ + StringBuf* src = shaderContext->shaderSource; + if (texInstruction->textureFetch.textureIndex < 0 || texInstruction->textureFetch.textureIndex >= LATTE_NUM_MAX_TEX_UNITS) + { + // skip out of bounds texture unit access + return; + } + + auto texDim = shaderContext->shader->textureUnitDim[texInstruction->textureFetch.textureIndex]; + + char tempBuffer0[32]; + char tempBuffer1[32]; + src->add(_getRegisterVarName(shaderContext, texInstruction->dstGpr)); + src->add("."); + const char* resultElemTable[4] = {"x","y","z","w"}; + sint32 numWrittenElements = 0; + for(sint32 f=0; f<4; f++) + { + if( texInstruction->dstSel[f] < 4 ) + { + src->add(resultElemTable[f]); + numWrittenElements++; + } + else if( texInstruction->dstSel[f] == 7 ) + { + // masked and not written + } + else + { + debugBreakpoint(); + } + } + // texture sampler opcode + uint32 texOpcode = texInstruction->opcode; + if (shaderContext->shaderType == LatteConst::ShaderType::Vertex) + { + // vertex shader forces LOD to zero, but certain sampler types don't support textureLod(...) API + if (texOpcode == GPU7_TEX_INST_SAMPLE_C_LZ) + texOpcode = GPU7_TEX_INST_SAMPLE_C; + } + // check if offset is used + bool hasOffset = false; + if( texInstruction->textureFetch.offsetX != 0 || texInstruction->textureFetch.offsetY != 0 || texInstruction->textureFetch.offsetZ != 0 ) + hasOffset = true; + // emit sample code + if (shaderContext->shader->textureIsIntegerFormat[texInstruction->textureFetch.textureIndex]) + { + // integer samplers + if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) // uint to int + { + if(numWrittenElements == 1) + src->add(" = int("); + else + shaderContext->shaderSource->addFmt(" = ivec{}(", numWrittenElements); + } + else if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_FLOAT) + src->add(" = uintBitsToFloat("); + } + else + { + // float samplers + if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) + src->add(" = floatBitsToInt("); + else if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_FLOAT) + src->add(" = ("); + } + + bool unnormalizationHandled = false; + bool useTexelCoordinates = false; + + // handle illegal combinations + if (texOpcode == GPU7_TEX_INST_FETCH4 && (texDim == Latte::E_DIM::DIM_1D || texDim == Latte::E_DIM::DIM_1D_ARRAY)) + { + // fetch4 is not allowed on 1D textures + // seen in YWW during boss fight of Level 1-4 + // todo - investigate what this returns on actual HW + if (numWrittenElements == 1) + shaderContext->shaderSource->add("0.0"); + else + shaderContext->shaderSource->addFmt("vec{}(0.0)", numWrittenElements); + shaderContext->shaderSource->add(");" _CRLF); + return; + } + + + if (texOpcode == GPU7_TEX_INST_SAMPLE && (texInstruction->textureFetch.unnormalized[0] && texInstruction->textureFetch.unnormalized[1] && texInstruction->textureFetch.unnormalized[2] && texInstruction->textureFetch.unnormalized[3]) ) + { + // texture is likely a RECT + if (hasOffset) + cemu_assert_unimplemented(); + src->add("texelFetch("); + unnormalizationHandled = true; + useTexelCoordinates = true; + } + else if( texOpcode == GPU7_TEX_INST_FETCH4 ) + { + if( hasOffset ) + cemu_assert_unimplemented(); + src->add("textureGather("); + } + else if( texOpcode == GPU7_TEX_INST_LD ) + { + if( hasOffset ) + cemu_assert_unimplemented(); + src->add("texelFetch("); + unnormalizationHandled = true; + useTexelCoordinates = true; + } + else if( texOpcode == GPU7_TEX_INST_SAMPLE_L ) + { + // sample with LOD value set in gpr.w (replaces computed LOD value) + if( hasOffset ) + src->add("textureLodOffset("); + else + src->add("textureLod("); + } + else if (texOpcode == GPU7_TEX_INST_SAMPLE_LZ) + { + // sample with LOD set to 0.0 (replaces computed LOD value) + if (hasOffset) + src->add("textureLodOffset("); + else + src->add("textureLod("); + } + else if (texOpcode == GPU7_TEX_INST_SAMPLE_LB) + { + // sample with LOD biased + // note: AMD doc says LOD bias is calculated from instruction LOD_BIAS field. But it appears that LOD bias is taken from input register. Might actually be both? + if (hasOffset) + src->add("textureOffset("); + else + src->add("texture("); + } + else if (texOpcode == GPU7_TEX_INST_SAMPLE) + { + if (hasOffset) + src->add("textureOffset("); + else + src->add("texture("); + } + else if (texOpcode == GPU7_TEX_INST_SAMPLE_C_L) + { + // sample with LOD value set in gpr.w (replaces computed LOD value) + if (hasOffset) + src->add("textureLodOffset("); + else + src->add("textureLod("); + } + else if (texOpcode == GPU7_TEX_INST_SAMPLE_C_LZ) + { + // sample with LOD set to 0.0 (replaces computed LOD value) + if (hasOffset) + src->add("textureLodOffset("); + else + src->add("textureLod("); + } + else if (texOpcode == GPU7_TEX_INST_SAMPLE_C) + { + if (hasOffset) + src->add("textureOffset("); + else + src->add("texture("); + } + else if (texOpcode == GPU7_TEX_INST_SAMPLE_G) + { + if (hasOffset) + cemu_assert_unimplemented(); + src->add("textureGrad("); + } + else + { + if( hasOffset ) + cemu_assert_unimplemented(); + cemu_assert_unimplemented(); + src->add("texture("); + } + if( texInstruction->textureFetch.srcSel[0] >= 4 ) + cemu_assert_unimplemented(); + if( texInstruction->textureFetch.srcSel[1] >= 4 ) + cemu_assert_unimplemented(); + src->addFmt("{}{}, ", _getTextureUnitVariablePrefixName(shaderContext->shader->shaderType), texInstruction->textureFetch.textureIndex); + + // for textureGather() add shift (todo: depends on rounding mode set in sampler registers?) + if (texOpcode == GPU7_TEX_INST_FETCH4) + { + if (texDim == Latte::E_DIM::DIM_2D) + { + //src->addFmt2("(vec2(-0.1) / vec2(textureSize({}{},0).xy)) + ", gpu7Decompiler_getTextureUnitVariablePrefixName(shaderContext->shader->shaderType), texInstruction->textureIndex); + + // vec2(-0.00001) is minimum to break Nvidia + // vec2(0.0001) is minimum to fix shadows on Intel, also fixes it on AMD (Windows and Linux) + + // todo - emulating coordinate rounding mode correctly is tricky + // GX2 supports two modes: Truncate or rounding according to DX9 rules + // Vulkan uses truncate mode when point sampling (min and mag is both nearest) otherwise it uses rounding + + // adding a small fixed bias is enough to avoid vendor-specific cases where small inaccuracies cause the number to get rounded down due to truncation + src->addFmt("vec2(0.0001) + "); + } + } + + + if(useTexelCoordinates) + { + // handle integer coordinates for texelFetch + if (texDim == Latte::E_DIM::DIM_2D || texDim == Latte::E_DIM::DIM_2D_MSAA) + { + src->add("ivec2("); + src->add("vec2("); + _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 0, (texOpcode == GPU7_TEX_INST_LD) ? LATTE_DECOMPILER_DTYPE_SIGNED_INT : LATTE_DECOMPILER_DTYPE_FLOAT); + src->addFmt(", "); + _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 1, (texOpcode == GPU7_TEX_INST_LD) ? LATTE_DECOMPILER_DTYPE_SIGNED_INT : LATTE_DECOMPILER_DTYPE_FLOAT); + + src->addFmt(")*uf_tex{}Scale", texInstruction->textureFetch.textureIndex); // close vec2 and scale + + src->add("), 0"); // close ivec2 and lod param + // todo - lod + } + else if (texDim == Latte::E_DIM::DIM_1D) + { + // VC DS games forget to initialize textures and use texel fetch on an uninitialized texture (a dim of 0 maps to 1D) + src->add("int("); + src->add("float("); + _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 0, (texOpcode == GPU7_TEX_INST_LD) ? LATTE_DECOMPILER_DTYPE_SIGNED_INT : LATTE_DECOMPILER_DTYPE_FLOAT); + src->addFmt(")*uf_tex{}Scale.x", texInstruction->textureFetch.textureIndex); + src->add("), 0"); + // todo - lod + } + else + cemu_assert_debug(false); + } + else + { + // float coordinates + if ( (texOpcode == GPU7_TEX_INST_SAMPLE_C || texOpcode == GPU7_TEX_INST_SAMPLE_C_L || texOpcode == GPU7_TEX_INST_SAMPLE_C_LZ) ) + { + // shadow sampler + if (texDim == Latte::E_DIM::DIM_2D_ARRAY) + { + // 3 coords + compare value (as vec4) + src->add("vec4("); + _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(","); + _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 1, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(","); + _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 2, LATTE_DECOMPILER_DTYPE_FLOAT); + + src->addFmt(",{})", _getTexGPRAccess(shaderContext, texInstruction->srcGpr, LATTE_DECOMPILER_DTYPE_FLOAT, texInstruction->textureFetch.srcSel[3], -1, -1, -1, tempBuffer0)); + } + else if (texDim == Latte::E_DIM::DIM_CUBEMAP) + { + // 2 coords + faceId + if (texInstruction->textureFetch.srcSel[0] >= 4 || texInstruction->textureFetch.srcSel[1] >= 4) + { + debugBreakpoint(); + } + src->add("vec4("); + src->addFmt("redcCUBEReverse({},", _getTexGPRAccess(shaderContext, texInstruction->srcGpr, LATTE_DECOMPILER_DTYPE_FLOAT, texInstruction->textureFetch.srcSel[0], texInstruction->textureFetch.srcSel[1], -1, -1, tempBuffer0)); + _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 2, LATTE_DECOMPILER_DTYPE_SIGNED_INT); + src->addFmt(")"); + src->addFmt(",cubeMapArrayIndex{})", texInstruction->textureFetch.textureIndex); // cubemap index + } + else if (texDim == Latte::E_DIM::DIM_1D) + { + // 1 coord + 1 unused coord (per GLSL spec) + compare value + if (texInstruction->textureFetch.srcSel[0] >= 4) + { + debugBreakpoint(); + } + src->addFmt("vec3({},0.0,{})", _getTexGPRAccess(shaderContext, texInstruction->srcGpr, LATTE_DECOMPILER_DTYPE_FLOAT, texInstruction->textureFetch.srcSel[0], -1, -1, -1, tempBuffer0), _getTexGPRAccess(shaderContext, texInstruction->srcGpr, LATTE_DECOMPILER_DTYPE_FLOAT, texInstruction->textureFetch.srcSel[3], -1, -1, -1, tempBuffer1)); + } + else + { + // 2 coords + compare value (as vec3) + if (texInstruction->textureFetch.srcSel[0] >= 4 && texInstruction->textureFetch.srcSel[1] >= 4) + { + debugBreakpoint(); + } + src->addFmt("vec3({}, {})", _getTexGPRAccess(shaderContext, texInstruction->srcGpr, LATTE_DECOMPILER_DTYPE_FLOAT, texInstruction->textureFetch.srcSel[0], texInstruction->textureFetch.srcSel[1], -1, -1, tempBuffer0), _getTexGPRAccess(shaderContext, texInstruction->srcGpr, LATTE_DECOMPILER_DTYPE_FLOAT, texInstruction->textureFetch.srcSel[3], -1, -1, -1, tempBuffer1)); + } + } + else if( texDim == Latte::E_DIM::DIM_3D || texDim == Latte::E_DIM::DIM_2D_ARRAY ) + { + // 3 coords + src->add("vec3("); + _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 0, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(","); + _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 1, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(","); + _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 2, LATTE_DECOMPILER_DTYPE_FLOAT); + src->add(")"); + } + else if( texDim == Latte::E_DIM::DIM_CUBEMAP ) + { + // 2 coords + faceId + if( texInstruction->textureFetch.srcSel[0] >= 4 || texInstruction->textureFetch.srcSel[1] >= 4 ) + { + debugBreakpoint(); + } + src->add("vec4("); + src->addFmt("redcCUBEReverse({},", _getTexGPRAccess(shaderContext, texInstruction->srcGpr, LATTE_DECOMPILER_DTYPE_FLOAT, texInstruction->textureFetch.srcSel[0], texInstruction->textureFetch.srcSel[1], -1, -1, tempBuffer0)); + _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 2, LATTE_DECOMPILER_DTYPE_SIGNED_INT); + src->add(")"); + src->addFmt(",cubeMapArrayIndex{})", texInstruction->textureFetch.textureIndex); // cubemap index + } + else if( texDim == Latte::E_DIM::DIM_1D ) + { + // 1 coord + src->add(_getTexGPRAccess(shaderContext, texInstruction->srcGpr, LATTE_DECOMPILER_DTYPE_FLOAT, texInstruction->textureFetch.srcSel[0], -1, -1, -1, tempBuffer0)); + } + else + { + // 2 coords + src->add(_getTexGPRAccess(shaderContext, texInstruction->srcGpr, LATTE_DECOMPILER_DTYPE_FLOAT, texInstruction->textureFetch.srcSel[0], texInstruction->textureFetch.srcSel[1], -1, -1, tempBuffer0)); + + // avoid truncate to effectively round downwards on texel edges + if (ActiveSettings::ForceSamplerRoundToPrecision()) + src->addFmt("+ vec2(1.0)/vec2(textureSize({}{}, 0))/512.0", _getTextureUnitVariablePrefixName(shaderContext->shader->shaderType), texInstruction->textureFetch.textureIndex); + } + // lod or lod bias parameter + if( texOpcode == GPU7_TEX_INST_SAMPLE_L || texOpcode == GPU7_TEX_INST_SAMPLE_LB || texOpcode == GPU7_TEX_INST_SAMPLE_C_L) + { + if( texInstruction->textureFetch.srcSel[3] >= 4 ) + debugBreakpoint(); + src->addFmt(",{}", _getTexGPRAccess(shaderContext, texInstruction->srcGpr, LATTE_DECOMPILER_DTYPE_FLOAT, texInstruction->textureFetch.srcSel[3], -1, -1, -1, tempBuffer0)); + } + else if( texOpcode == GPU7_TEX_INST_SAMPLE_LZ || texOpcode == GPU7_TEX_INST_SAMPLE_C_LZ ) + { + src->add(",0.0"); + } + } + // gradient parameters + if (texOpcode == GPU7_TEX_INST_SAMPLE_G) + { + if (texDim == Latte::E_DIM::DIM_2D || + texDim == Latte::E_DIM::DIM_1D ) + { + src->add(",gradH.xy,gradV.xy"); + } + else + { + cemu_assert_unimplemented(); + } + } + // offset + if( texOpcode == GPU7_TEX_INST_SAMPLE_L || texOpcode == GPU7_TEX_INST_SAMPLE_LZ || texOpcode == GPU7_TEX_INST_SAMPLE_C_LZ || texOpcode == GPU7_TEX_INST_SAMPLE || texOpcode == GPU7_TEX_INST_SAMPLE_C ) + { + if( hasOffset ) + { + uint8 offsetComponentCount = 0; + if( texDim == Latte::E_DIM::DIM_1D ) + offsetComponentCount = 1; + else if( texDim == Latte::E_DIM::DIM_2D ) + offsetComponentCount = 2; + else if( texDim == Latte::E_DIM::DIM_3D ) + offsetComponentCount = 3; + else if( texDim == Latte::E_DIM::DIM_2D_ARRAY ) + offsetComponentCount = 2; + else + cemu_assert_unimplemented(); + + if( (texInstruction->textureFetch.offsetX&1) ) + cemu_assert_unimplemented(); + if( (texInstruction->textureFetch.offsetY&1) ) + cemu_assert_unimplemented(); + if ((texInstruction->textureFetch.offsetZ & 1)) + cemu_assert_unimplemented(); + + if( offsetComponentCount == 1 ) + src->addFmt(",{}", texInstruction->textureFetch.offsetX/2); + else if( offsetComponentCount == 2 ) + src->addFmt(",ivec2({},{})", texInstruction->textureFetch.offsetX/2, texInstruction->textureFetch.offsetY/2, texInstruction->textureFetch.offsetZ/2); + else if( offsetComponentCount == 3 ) + src->addFmt(",ivec3({},{},{})", texInstruction->textureFetch.offsetX/2, texInstruction->textureFetch.offsetY/2, texInstruction->textureFetch.offsetZ/2); + } + } + // lod bias + if( texOpcode == GPU7_TEX_INST_SAMPLE_C || texOpcode == GPU7_TEX_INST_SAMPLE_C_LZ ) + { + src->add(")"); + + if (numWrittenElements > 1) + { + // result is copied into multiple channels + src->add("."); + for (sint32 f = 0; f < numWrittenElements; f++) + { + cemu_assert_debug(texInstruction->dstSel[f] == 0); // only x component is defined + src->add("x"); + } + } + } + else + { + src->add(")."); + for (sint32 f = 0; f < 4; f++) + { + if( texInstruction->dstSel[f] < 4 ) + { + uint8 elemIndex = texInstruction->dstSel[f]; + if (texOpcode == GPU7_TEX_INST_FETCH4) + { + // GLSL's textureGather() and GPU7's FETCH4 instruction have a different order of elements + // xyzw: top-left, top-right, bottom-right, bottom-left + // textureGather xyzw + // fetch4 yzxw + // translate index from fetch4 to textureGather order + static uint8 fetchToGather[4] = + { + 2, // x -> z + 0, // y -> x + 1, // z -> y + 3, // w -> w + }; + elemIndex = fetchToGather[elemIndex]; + } + src->add(resultElemTable[elemIndex]); + numWrittenElements++; + } + else if( texInstruction->dstSel[f] == 7 ) + { + // masked and not written + } + else + { + cemu_assert_unimplemented(); + } + } + } + src->add(");"); + + // debug +#ifndef PUBLIC_RELEASE + if(texInstruction->opcode == GPU7_TEX_INST_LD ) + src->add(" // TEX_INST_LD"); + else if(texInstruction->opcode == GPU7_TEX_INST_SAMPLE ) + src->add(" // TEX_INST_SAMPLE"); + else if(texInstruction->opcode == GPU7_TEX_INST_SAMPLE_L ) + src->add(" // TEX_INST_SAMPLE_L"); + else if(texInstruction->opcode == GPU7_TEX_INST_SAMPLE_LZ ) + src->add(" // TEX_INST_SAMPLE_LZ"); + else if(texInstruction->opcode == GPU7_TEX_INST_SAMPLE_C ) + src->add(" // TEX_INST_SAMPLE_C"); + else if(texInstruction->opcode == GPU7_TEX_INST_SAMPLE_G ) + src->add(" // TEX_INST_SAMPLE_G"); + else + src->addFmt(" // 0x{:02x}", texInstruction->opcode); + if (texInstruction->opcode != texOpcode) + src->addFmt(" (applied as 0x{:02x})", texOpcode); + src->addFmt(" OffsetXYZ {:02x} {:02x} {:02x}", (uint8)texInstruction->textureFetch.offsetX&0xFF, (uint8)texInstruction->textureFetch.offsetY&0xFF, (uint8)texInstruction->textureFetch.offsetZ&0xFF); +#endif + src->add("" _CRLF); +} + +void _emitTEXGetTextureResInfoCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerTEXInstruction* texInstruction) +{ + StringBuf* src = shaderContext->shaderSource; + src->addFmt("R{}", texInstruction->dstGpr); + src->add("i"); + src->add("."); + + const char* resultElemTable[4] = {"x","y","z","w"}; + sint32 numWrittenElements = 0; + for(sint32 f=0; f<4; f++) + { + if( texInstruction->dstSel[f] < 4 ) + { + src->add(resultElemTable[f]); + numWrittenElements++; + } + else if( texInstruction->dstSel[f] == 7 ) + { + // masked and not written + } + else + { + cemu_assert_unimplemented(); + } + } + + // todo - mip index parameter? + + auto texDim = shaderContext->shader->textureUnitDim[texInstruction->textureFetch.textureIndex]; + + if (texDim == Latte::E_DIM::DIM_1D) + src->addFmt(" = ivec4(textureSize({}{}, 0),1,1,1).", _getTextureUnitVariablePrefixName(shaderContext->shader->shaderType), texInstruction->textureFetch.textureIndex); + else if (texDim == Latte::E_DIM::DIM_1D_ARRAY) + src->addFmt(" = ivec4(textureSize({}{}, 0),1,1).", _getTextureUnitVariablePrefixName(shaderContext->shader->shaderType), texInstruction->textureFetch.textureIndex); + else if (texDim == Latte::E_DIM::DIM_2D || texDim == Latte::E_DIM::DIM_2D_MSAA) + src->addFmt(" = ivec4(textureSize({}{}, 0),1,1).", _getTextureUnitVariablePrefixName(shaderContext->shader->shaderType), texInstruction->textureFetch.textureIndex); + else if (texDim == Latte::E_DIM::DIM_2D_ARRAY) + src->addFmt(" = ivec4(textureSize({}{}, 0),1).", _getTextureUnitVariablePrefixName(shaderContext->shader->shaderType), texInstruction->textureFetch.textureIndex); + else + { + cemu_assert_debug(false); + src->addFmt(" = ivec4(textureSize({}{}, 0),1,1).", _getTextureUnitVariablePrefixName(shaderContext->shader->shaderType), texInstruction->textureFetch.textureIndex); + } + + for(sint32 f=0; f<4; f++) + { + if( texInstruction->dstSel[f] < 4 ) + { + src->add(resultElemTable[texInstruction->dstSel[f]]); + numWrittenElements++; + } + else if( texInstruction->dstSel[f] == 7 ) + { + // masked and not written + } + else + { + debugBreakpoint(); + } + } + src->add(";" _CRLF); +} + +void _emitTEXGetCompTexLodCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerTEXInstruction* texInstruction) +{ + StringBuf* src = shaderContext->shaderSource; + src->add(_getRegisterVarName(shaderContext, texInstruction->dstGpr)); + src->add("."); + + const char* resultElemTable[4] = {"x","y","z","w"}; + sint32 numWrittenElements = 0; + for(sint32 f=0; f<4; f++) + { + if( texInstruction->dstSel[f] < 4 ) + { + src->add(resultElemTable[f]); + numWrittenElements++; + } + else if( texInstruction->dstSel[f] == 7 ) + { + // masked and not written + } + else + { + debugBreakpoint(); + } + } + + src->add(" = "); + _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, shaderContext->typeTracker.defaultDataType); + + if( shaderContext->shader->textureUnitDim[texInstruction->textureFetch.textureIndex] == Latte::E_DIM::DIM_CUBEMAP ) + { + // 3 coordinates + if(shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_FLOAT) + src->addFmt("vec4(textureQueryLod({}{}, {}.{}{}{}),0.0,0.0)", _getTextureUnitVariablePrefixName(shaderContext->shader->shaderType), texInstruction->textureFetch.textureIndex, _getRegisterVarName(shaderContext, texInstruction->srcGpr), resultElemTable[texInstruction->textureFetch.srcSel[0]], resultElemTable[texInstruction->textureFetch.srcSel[1]], resultElemTable[texInstruction->textureFetch.srcSel[2]]); + else + src->addFmt("vec4(textureQueryLod({}{}, intBitsToFloat({}.{}{}{})),0.0,0.0)", _getTextureUnitVariablePrefixName(shaderContext->shader->shaderType), texInstruction->textureFetch.textureIndex, _getRegisterVarName(shaderContext, texInstruction->srcGpr), resultElemTable[texInstruction->textureFetch.srcSel[0]], resultElemTable[texInstruction->textureFetch.srcSel[1]], resultElemTable[texInstruction->textureFetch.srcSel[2]]); + } + else + { + if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_FLOAT) + src->addFmt("vec4(textureQueryLod({}{}, {}.{}{}),0.0,0.0)", _getTextureUnitVariablePrefixName(shaderContext->shader->shaderType), texInstruction->textureFetch.textureIndex, _getRegisterVarName(shaderContext, texInstruction->srcGpr), resultElemTable[texInstruction->textureFetch.srcSel[0]], resultElemTable[texInstruction->textureFetch.srcSel[1]]); + else + src->addFmt("vec4(textureQueryLod({}{}, intBitsToFloat({}.{}{})),0.0,0.0)", _getTextureUnitVariablePrefixName(shaderContext->shader->shaderType), texInstruction->textureFetch.textureIndex, _getRegisterVarName(shaderContext, texInstruction->srcGpr), resultElemTable[texInstruction->textureFetch.srcSel[0]], resultElemTable[texInstruction->textureFetch.srcSel[1]]); + debugBreakpoint(); + } + + + _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, shaderContext->typeTracker.defaultDataType); + src->add("."); + + for(sint32 f=0; f<4; f++) + { + if( texInstruction->dstSel[f] < 4 ) + { + src->add(resultElemTable[texInstruction->dstSel[f]]); + numWrittenElements++; + } + else if( texInstruction->dstSel[f] == 7 ) + { + // masked and not written + } + else + { + debugBreakpoint(); + } + } + src->add(";" _CRLF); +} + +void _emitTEXSetCubemapIndexCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerTEXInstruction* texInstruction) +{ + StringBuf* src = shaderContext->shaderSource; + src->addFmt("cubeMapArrayIndex{}", texInstruction->textureFetch.textureIndex); + const char* resultElemTable[4] = {"x","y","z","w"}; + + if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) + src->addFmt(" = intBitsToFloat(R{}i.{});" _CRLF, texInstruction->srcGpr, resultElemTable[texInstruction->textureFetch.srcSel[0]]); + else if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_FLOAT) + src->addFmt(" = R{}f.{};" _CRLF, texInstruction->srcGpr, resultElemTable[texInstruction->textureFetch.srcSel[0]]); + else + cemu_assert_unimplemented(); +} + +void _emitTEXGetGradientsHV(LatteDecompilerShaderContext* shaderContext, LatteDecompilerTEXInstruction* texInstruction) +{ + StringBuf* src = shaderContext->shaderSource; + sint32 componentCount = 0; + for (sint32 i = 0; i < 4; i++) + { + if(texInstruction->dstSel[i] == 7) + continue; + componentCount++; + } + src->add(_getRegisterVarName(shaderContext, texInstruction->dstGpr)); + src->add("."); + const char* resultElemTable[4] = { "x","y","z","w" }; + sint32 numWrittenElements = 0; + for (sint32 f = 0; f < 4; f++) + { + if (texInstruction->dstSel[f] < 4) + { + src->add(resultElemTable[f]); + numWrittenElements++; + } + else if (texInstruction->dstSel[f] == 7) + { + // masked and not written + } + else + { + debugBreakpoint(); + } + } + + const char* funcName; + if (texInstruction->opcode == GPU7_TEX_INST_GET_GRADIENTS_H) + funcName = "dFdx"; + else + funcName = "dFdy"; + + src->add(" = "); + + _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, shaderContext->typeTracker.defaultDataType); + + src->addFmt("{}(", funcName); + _emitRegisterAccessCode(shaderContext, texInstruction->srcGpr, (componentCount >= 1) ? texInstruction->textureFetch.srcSel[0] : -1, (componentCount >= 2) ? texInstruction->textureFetch.srcSel[1] : -1, (componentCount >= 3) ? texInstruction->textureFetch.srcSel[2] : -1, (componentCount >= 4)?texInstruction->textureFetch.srcSel[3]:-1, LATTE_DECOMPILER_DTYPE_FLOAT); + + src->add(")"); + + _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_FLOAT, shaderContext->typeTracker.defaultDataType); + + src->add(";" _CRLF); + +} + +void _emitTEXSetGradientsHV(LatteDecompilerShaderContext* shaderContext, LatteDecompilerTEXInstruction* texInstruction) +{ + StringBuf* src = shaderContext->shaderSource; + if (texInstruction->opcode == GPU7_TEX_INST_SET_GRADIENTS_H) + src->add("gradH = "); + else + src->add("gradV = "); + + _emitRegisterAccessCode(shaderContext, texInstruction->srcGpr, texInstruction->textureFetch.srcSel[0], texInstruction->textureFetch.srcSel[1], texInstruction->textureFetch.srcSel[2], texInstruction->textureFetch.srcSel[3], LATTE_DECOMPILER_DTYPE_FLOAT); + + src->add(";" _CRLF); +} + +void _emitGSReadInputVFetchCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerTEXInstruction* texInstruction) +{ + StringBuf* src = shaderContext->shaderSource; + src->add(_getRegisterVarName(shaderContext, texInstruction->dstGpr)); + + src->add("."); + + const char* resultElemTable[4] = {"x","y","z","w"}; + sint32 numWrittenElements = 0; + for(sint32 f=0; f<4; f++) + { + if( texInstruction->dstSel[f] < 4 ) + { + src->add(resultElemTable[f]); + numWrittenElements++; + } + else if( texInstruction->dstSel[f] == 7 ) + { + // masked and not written + } + else + { + cemu_assert_unimplemented(); + } + } + + src->add(" = "); + _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, shaderContext->typeTracker.defaultDataType); + src->add("(v2g["); + if (texInstruction->textureFetch.srcSel[0] >= 4) + cemu_assert_unimplemented(); + if (texInstruction->textureFetch.srcSel[1] >= 4) + cemu_assert_unimplemented(); + // todo: Index type + src->add("0"); + src->addFmt("].passV2GParameter{}.", texInstruction->textureFetch.offset/16); + + + for(sint32 f=0; f<4; f++) + { + if( texInstruction->dstSel[f] < 4 ) + { + src->add(resultElemTable[texInstruction->dstSel[f]]); + numWrittenElements++; + } + else if( texInstruction->dstSel[f] == 7 ) + { + // masked and not written + } + else + { + cemu_assert_unimplemented(); + } + } + src->add(")"); + _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, shaderContext->typeTracker.defaultDataType); + src->add(";" _CRLF); +} + +sint32 _writeDestMaskXYZW(LatteDecompilerShaderContext* shaderContext, sint8* dstSel) +{ + StringBuf* src = shaderContext->shaderSource; + const char* resultElemTable[4] = { "x","y","z","w" }; + sint32 numWrittenElements = 0; + for (sint32 f = 0; f < 4; f++) + { + if (dstSel[f] < 4) + { + src->add(resultElemTable[f]); + numWrittenElements++; + } + else if (dstSel[f] == 7) + { + // masked and not written + } + else + { + cemu_assert_unimplemented(); + } + } + return numWrittenElements; +} + +void _emitTEXVFetchCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerTEXInstruction* texInstruction) +{ + // handle special case where geometry shader reads input attributes from vertex shader via ringbuffer + StringBuf* src = shaderContext->shaderSource; + if( texInstruction->textureFetch.textureIndex == 0x9F && shaderContext->shaderType == LatteConst::ShaderType::Geometry ) + { + _emitGSReadInputVFetchCode(shaderContext, texInstruction); + return; + } + + src->add(_getRegisterVarName(shaderContext, texInstruction->dstGpr)); + src->add("."); + + _writeDestMaskXYZW(shaderContext, texInstruction->dstSel); + const char* resultElemTable[4] = {"x","y","z","w"}; + + src->add(" = "); + + if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) + src->add("floatBitsToInt("); + else + src->add("("); + + src->addFmt("{}{}[", _getShaderUniformBlockVariableName(shaderContext->shader->shaderType), texInstruction->textureFetch.textureIndex - 0x80); + + if( shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT ) + src->addFmt("{}.{}", _getRegisterVarName(shaderContext, texInstruction->srcGpr), resultElemTable[texInstruction->textureFetch.srcSel[0]]); + else + src->addFmt("floatBitsToInt({}.{})", _getRegisterVarName(shaderContext, texInstruction->srcGpr), resultElemTable[texInstruction->textureFetch.srcSel[0]]); + src->add("]."); + + + for(sint32 f=0; f<4; f++) + { + if( texInstruction->dstSel[f] < 4 ) + { + src->add(resultElemTable[texInstruction->dstSel[f]]); + } + else if( texInstruction->dstSel[f] == 7 ) + { + // masked and not written + } + else + { + debugBreakpoint(); + } + } + src->add(");" _CRLF); +} + +void _emitTEXReadMemCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerTEXInstruction* texInstruction) +{ + StringBuf* src = shaderContext->shaderSource; + src->add(_getRegisterVarName(shaderContext, texInstruction->dstGpr)); + src->add("."); + sint32 count = _writeDestMaskXYZW(shaderContext, texInstruction->dstSel); + + src->add(" = "); + + if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) + src->add("floatBitsToInt("); + else + src->add("("); + + sint32 readCount; + + if (texInstruction->memRead.format == FMT_32_FLOAT) + { + readCount = 1; + // todo + src->add("0.0"); + } + else if (texInstruction->memRead.format == FMT_32_32_FLOAT) + { + readCount = 2; + // todo + src->add("vec2(0.0,0.0)"); + } + else if (texInstruction->memRead.format == FMT_32_32_32_FLOAT) + { + readCount = 3; + // todo + src->add("vec3(0.0,0.0,0.0)"); + } + else + { + cemu_assert_unimplemented(); + } + if (count < readCount) + { + if (count == 1) + src->add(".x"); + else if (count == 2) + src->add(".xy"); + else if (count == 3) + src->add(".xyz"); + } + src->add(");" _CRLF); +} + +void _emitTEXClauseCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerCFInstruction* cfInstruction) +{ + cemu_assert_debug(cfInstruction->instructionsALU.empty()); + for(auto& texInstruction : cfInstruction->instructionsTEX) + { + if( texInstruction.opcode == GPU7_TEX_INST_SAMPLE || texInstruction.opcode == GPU7_TEX_INST_SAMPLE_L || texInstruction.opcode == GPU7_TEX_INST_SAMPLE_LB || texInstruction.opcode == GPU7_TEX_INST_SAMPLE_LZ || texInstruction.opcode == GPU7_TEX_INST_SAMPLE_C || texInstruction.opcode == GPU7_TEX_INST_SAMPLE_C_L || texInstruction.opcode == GPU7_TEX_INST_SAMPLE_C_LZ || texInstruction.opcode == GPU7_TEX_INST_FETCH4 || texInstruction.opcode == GPU7_TEX_INST_SAMPLE_G || texInstruction.opcode == GPU7_TEX_INST_LD ) + _emitTEXSampleTextureCode(shaderContext, &texInstruction); + else if( texInstruction.opcode == GPU7_TEX_INST_GET_TEXTURE_RESINFO ) + _emitTEXGetTextureResInfoCode(shaderContext, &texInstruction); + else if( texInstruction.opcode == GPU7_TEX_INST_GET_COMP_TEX_LOD ) + _emitTEXGetCompTexLodCode(shaderContext, &texInstruction); + else if( texInstruction.opcode == GPU7_TEX_INST_SET_CUBEMAP_INDEX ) + _emitTEXSetCubemapIndexCode(shaderContext, &texInstruction); + else if (texInstruction.opcode == GPU7_TEX_INST_GET_GRADIENTS_H || + texInstruction.opcode == GPU7_TEX_INST_GET_GRADIENTS_V) + _emitTEXGetGradientsHV(shaderContext, &texInstruction); + else if (texInstruction.opcode == GPU7_TEX_INST_SET_GRADIENTS_H || + texInstruction.opcode == GPU7_TEX_INST_SET_GRADIENTS_V) + _emitTEXSetGradientsHV(shaderContext, &texInstruction); + else if (texInstruction.opcode == GPU7_TEX_INST_VFETCH) + _emitTEXVFetchCode(shaderContext, &texInstruction); + else if (texInstruction.opcode == GPU7_TEX_INST_MEM) + _emitTEXReadMemCode(shaderContext, &texInstruction); + else + cemu_assert_unimplemented(); + } +} + +// generate the code for reading the source input GPR (or constants) for exports +void _emitExportGPRReadCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerCFInstruction* cfInstruction, sint32 requiredType, uint32 burstIndex) +{ + StringBuf* src = shaderContext->shaderSource; + uint32 numOutputs = 4; + if( cfInstruction->type == GPU7_CF_INST_MEM_RING_WRITE ) + { + numOutputs = (cfInstruction->memWriteCompMask&1)?1:0; + numOutputs += (cfInstruction->memWriteCompMask&2)?1:0; + numOutputs += (cfInstruction->memWriteCompMask&4)?1:0; + numOutputs += (cfInstruction->memWriteCompMask&8)?1:0; + } + if (requiredType == LATTE_DECOMPILER_DTYPE_FLOAT) + { + if(numOutputs == 1) + src->add("float("); + else + src->addFmt("vec{}(", numOutputs); + } + else if (requiredType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) + { + if (numOutputs == 1) + src->add("int("); + else + src->addFmt("ivec{}(", numOutputs); + } + else + cemu_assert_unimplemented(); + sint32 actualOutputs = 0; + for(sint32 i=0; i<4; i++) + { + // todo: Use type of register element based on information from type tracker (currently we assume it's always a signed integer) + uint32 exportSel = 0; + if( cfInstruction->type == GPU7_CF_INST_MEM_RING_WRITE ) + { + exportSel = i; + if( (cfInstruction->memWriteCompMask&(1<<i)) == 0 ) + continue; // dont output + } + else + { + exportSel = cfInstruction->exportComponentSel[i]; + } + if( actualOutputs > 0 ) + src->add(", "); + actualOutputs++; + if( exportSel < 4 ) + { + _emitRegisterAccessCode(shaderContext, cfInstruction->exportSourceGPR+burstIndex, exportSel, -1, -1, -1, requiredType); + } + else if (exportSel == 4) + { + // constant zero + src->add("0"); + } + else if (exportSel == 5) + { + // constant one + src->add("1.0"); + } + else if( exportSel == 7 ) + { + // element masked (which means 0 is exported?) + src->add("0"); + } + else + { + cemu_assert_debug(false); + src->add("0"); + } + } + if( requiredType == LATTE_DECOMPILER_DTYPE_FLOAT ) + src->add(")"); + else if( requiredType == LATTE_DECOMPILER_DTYPE_SIGNED_INT ) + src->add(")"); + else + cemu_assert_unimplemented(); +} + +void _emitExportCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerCFInstruction* cfInstruction) +{ + StringBuf* src = shaderContext->shaderSource; + src->add("// export" _CRLF); + if(shaderContext->shaderType == LatteConst::ShaderType::Vertex ) + { + if( cfInstruction->exportBurstCount != 0 ) + debugBreakpoint(); + if (cfInstruction->exportType == 1 && cfInstruction->exportArrayBase == GPU7_DECOMPILER_CF_EXPORT_BASE_POSITION) + { + // export position + // GX2 special state 0 disables rasterizer viewport offset and scaling (probably, exact mechanism is not known). Handle this here + bool hasAnyViewportScaleDisabled = + !shaderContext->contextRegistersNew->PA_CL_VTE_CNTL.get_VPORT_X_SCALE_ENA() || + !shaderContext->contextRegistersNew->PA_CL_VTE_CNTL.get_VPORT_Y_SCALE_ENA() || + !shaderContext->contextRegistersNew->PA_CL_VTE_CNTL.get_VPORT_Z_SCALE_ENA(); + + if (hasAnyViewportScaleDisabled) + { + src->add("vec4 finalPos = "); + _emitExportGPRReadCode(shaderContext, cfInstruction, LATTE_DECOMPILER_DTYPE_FLOAT, 0); + src->add(";" _CRLF); + src->add("finalPos.xy = finalPos.xy * uf_windowSpaceToClipSpaceTransform - vec2(1.0,1.0);"); + src->add("SET_POSITION(finalPos);"); + } + else + { + src->add("SET_POSITION("); + _emitExportGPRReadCode(shaderContext, cfInstruction, LATTE_DECOMPILER_DTYPE_FLOAT, 0); + src->add(");" _CRLF); + } + } + else if (cfInstruction->exportType == 1 && cfInstruction->exportArrayBase == GPU7_DECOMPILER_CF_EXPORT_POINT_SIZE ) + { + // export gl_PointSize + if (shaderContext->analyzer.outputPointSize) + { + cemu_assert_debug(shaderContext->analyzer.writesPointSize); + src->add("gl_PointSize = ("); + _emitExportGPRReadCode(shaderContext, cfInstruction, LATTE_DECOMPILER_DTYPE_FLOAT, 0); + src->add(").x"); + src->add(";" _CRLF); + } + } + else if( cfInstruction->exportType == 2 && cfInstruction->exportArrayBase < 32 ) + { + // export parameter + sint32 paramIndex = cfInstruction->exportArrayBase; + uint32 vsSemanticId = _getVertexShaderOutParamSemanticId(shaderContext->contextRegisters, paramIndex); + if (vsSemanticId != 0xFF) + { + src->addFmt("passParameterSem{} = ", vsSemanticId); + _emitExportGPRReadCode(shaderContext, cfInstruction, LATTE_DECOMPILER_DTYPE_FLOAT, 0); + src->add(";" _CRLF); + } + else + { + src->add("// skipped export to semanticId 255" _CRLF); + } + } + else + cemu_assert_unimplemented(); + } + else if(shaderContext->shaderType == LatteConst::ShaderType::Pixel ) + { + if( cfInstruction->exportType == 0 && cfInstruction->exportArrayBase < 8 ) + { + for(uint32 i=0; i<(cfInstruction->exportBurstCount+1); i++) + { + sint32 pixelColorOutputIndex = LatteDecompiler_getColorOutputIndexFromExportIndex(shaderContext, cfInstruction->exportArrayBase+i); + // if color output is for target 0, then also handle alpha test + bool alphaTestEnable = shaderContext->contextRegistersNew->SX_ALPHA_TEST_CONTROL.get_ALPHA_TEST_ENABLE(); + auto alphaTestFunc = shaderContext->contextRegistersNew->SX_ALPHA_TEST_CONTROL.get_ALPHA_FUNC(); + if( pixelColorOutputIndex == 0 && alphaTestEnable && alphaTestFunc == Latte::E_COMPAREFUNC::NEVER ) + { + // never pass alpha test + src->add("discard;" _CRLF); + } + else if( pixelColorOutputIndex == 0 && alphaTestEnable && alphaTestFunc != Latte::E_COMPAREFUNC::ALWAYS) + { + src->add("if( (("); + _emitExportGPRReadCode(shaderContext, cfInstruction, LATTE_DECOMPILER_DTYPE_FLOAT, i); + src->add(").a "); + + switch( alphaTestFunc ) + { + case Latte::E_COMPAREFUNC::LESS: + src->add("<"); + break; + case Latte::E_COMPAREFUNC::EQUAL: + src->add("=="); + break; + case Latte::E_COMPAREFUNC::LEQUAL: + src->add("<="); + break; + case Latte::E_COMPAREFUNC::GREATER: + src->add(">"); + break; + case Latte::E_COMPAREFUNC::NOTEQUAL: + src->add("!="); + break; + case Latte::E_COMPAREFUNC::GEQUAL: + src->add(">="); + break; + } + src->add(" uf_alphaTestRef"); + src->add(") == false) discard;" _CRLF); + } + // pixel color output + src->addFmt("passPixelColor{} = ", pixelColorOutputIndex); + _emitExportGPRReadCode(shaderContext, cfInstruction, LATTE_DECOMPILER_DTYPE_FLOAT, i); + src->add(";" _CRLF); + + if( cfInstruction->exportArrayBase+i >= 8 ) + cemu_assert_unimplemented(); + } + } + else if( cfInstruction->exportType == 0 && cfInstruction->exportArrayBase == 61 ) + { + // pixel depth or gl_FragStencilRefARB + if( cfInstruction->exportBurstCount > 0 ) + cemu_assert_unimplemented(); + + if (cfInstruction->exportComponentSel[0] == 7) + { + cemu_assert_unimplemented(); // gl_FragDepth ? + } + if (cfInstruction->exportComponentSel[1] != 7) + { + cemu_assert_unimplemented(); // exporting to gl_FragStencilRefARB + } + if (cfInstruction->exportComponentSel[2] != 7) + { + cemu_assert_unimplemented(); // ukn + } + if (cfInstruction->exportComponentSel[3] != 7) + { + cemu_assert_unimplemented(); // ukn + } + + src->add("gl_FragDepth = "); + _emitExportGPRReadCode(shaderContext, cfInstruction, LATTE_DECOMPILER_DTYPE_FLOAT, 0); + src->add(".x"); + src->add(";" _CRLF); + } + else + cemu_assert_unimplemented(); + } +} + +void _emitXYZWByMask(StringBuf* src, uint32 mask) +{ + if( (mask&(1<<0)) != 0 ) + src->add("x"); + if( (mask&(1<<1)) != 0 ) + src->add("y"); + if( (mask&(1<<2)) != 0 ) + src->add("z"); + if( (mask&(1<<3)) != 0 ) + src->add("w"); +} + +void _emitCFRingWriteCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerCFInstruction* cfInstruction) +{ + StringBuf* src = shaderContext->shaderSource; + // calculate parameter output (based on ring buffer output offset relative to GS unit) + uint32 bytesPerVertex = shaderContext->contextRegisters[mmSQ_GS_VERT_ITEMSIZE] * 4; + bytesPerVertex = std::max(bytesPerVertex, (uint32)1); // avoid division by zero + uint32 parameterOffset = ((cfInstruction->exportArrayBase * 4) % bytesPerVertex); + // for geometry shaders with streamout, MEM_RING_WRITE is used to pass the data to the copy shader, which then uses STREAM*_WRITE + if (shaderContext->shaderType == LatteConst::ShaderType::Geometry && shaderContext->analyzer.hasStreamoutEnable) + { + // if streamout is enabled, we generate transform feedback output code instead of the normal gs output + for (uint32 burstIndex = 0; burstIndex < (cfInstruction->exportBurstCount + 1); burstIndex++) + { + parameterOffset = ((cfInstruction->exportArrayBase * 4 + burstIndex*0x10) % bytesPerVertex); + // find matching stream write in copy shader + LatteGSCopyShaderStreamWrite_t* streamWrite = nullptr; + for (auto& it : shaderContext->parsedGSCopyShader->list_streamWrites) + { + if (it.offset == parameterOffset) + { + streamWrite = ⁢ + break; + } + } + if (streamWrite == nullptr) + { + cemu_assert_suspicious(); + return; + } + + for (sint32 i = 0; i < 4; i++) + { + if ((cfInstruction->memWriteCompMask&(1 << i)) == 0) + continue; + + if (shaderContext->useTFViaSSBO) + { + uint32 u32Offset = streamWrite->exportArrayBase + i; + src->addFmt("sb_buffer[sbBase{} + {}]", streamWrite->bufferIndex, u32Offset); + } + else + { + src->addFmt("sb{}[{}]", streamWrite->bufferIndex, streamWrite->exportArrayBase + i); + } + + src->add(" = "); + + _emitTypeConversionPrefix(shaderContext, shaderContext->typeTracker.defaultDataType, LATTE_DECOMPILER_DTYPE_SIGNED_INT); + + src->addFmt("{}.", _getRegisterVarName(shaderContext, cfInstruction->exportSourceGPR+burstIndex)); + if (i == 0) + src->add("x"); + else if (i == 1) + src->add("y"); + else if (i == 2) + src->add("z"); + else if (i == 3) + src->add("w"); + + _emitTypeConversionSuffix(shaderContext, shaderContext->typeTracker.defaultDataType, LATTE_DECOMPILER_DTYPE_SIGNED_INT); + + src->add(";" _CRLF); + } + } + return; + } + + if (shaderContext->shaderType == LatteConst::ShaderType::Vertex) + { + if (cfInstruction->memWriteElemSize != 3) + cemu_assert_unimplemented(); + if ((cfInstruction->exportArrayBase & 3) != 0) + cemu_assert_unimplemented(); + for (sint32 burstIndex = 0; burstIndex < (sint32)(cfInstruction->exportBurstCount + 1); burstIndex++) + { + src->addFmt("v2g.passV2GParameter{}.", (cfInstruction->exportArrayBase) / 4 + burstIndex); + _emitXYZWByMask(src, cfInstruction->memWriteCompMask); + src->addFmt(" = "); + _emitExportGPRReadCode(shaderContext, cfInstruction, LATTE_DECOMPILER_DTYPE_SIGNED_INT, burstIndex); + src->add(";" _CRLF); + } + } + else if (shaderContext->shaderType == LatteConst::ShaderType::Geometry) + { + cemu_assert_debug(cfInstruction->memWriteElemSize == 3); + //if (cfInstruction->memWriteElemSize != 3) + // debugBreakpoint(); + cemu_assert_debug((cfInstruction->exportArrayBase & 3) == 0); + + for (uint32 burstIndex = 0; burstIndex < (cfInstruction->exportBurstCount + 1); burstIndex++) + { + uint32 parameterExportType = 0; + uint32 parameterExportBase = 0; + if (LatteGSCopyShaderParser_getExportTypeByOffset(shaderContext->parsedGSCopyShader, parameterOffset + burstIndex * (cfInstruction->memWriteElemSize+1)*4, ¶meterExportType, ¶meterExportBase) == false) + { + cemu_assert_debug(false); + shaderContext->hasError = true; + return; + } + + if (parameterExportType == 1 && parameterExportBase == GPU7_DECOMPILER_CF_EXPORT_BASE_POSITION) + { + src->add("{" _CRLF); + src->addFmt("vec4 pos = vec4(0.0,0.0,0.0,1.0);" _CRLF); + src->addFmt("pos."); + _emitXYZWByMask(src, cfInstruction->memWriteCompMask); + src->addFmt(" = "); + _emitExportGPRReadCode(shaderContext, cfInstruction, LATTE_DECOMPILER_DTYPE_FLOAT, burstIndex); + src->add(";" _CRLF); + src->add("SET_POSITION(pos);" _CRLF); + src->add("}" _CRLF); + } + else if (parameterExportType == 2 && parameterExportBase < 16) + { + src->addFmt("passG2PParameter{}.", parameterExportBase); + _emitXYZWByMask(src, cfInstruction->memWriteCompMask); + src->addFmt(" = "); + _emitExportGPRReadCode(shaderContext, cfInstruction, LATTE_DECOMPILER_DTYPE_FLOAT, burstIndex); + src->add(";" _CRLF); + } + else + cemu_assert_debug(false); + } + } + else + debugBreakpoint(); // todo +} + +void _emitStreamWriteCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerCFInstruction* cfInstruction) +{ + StringBuf* src = shaderContext->shaderSource; + if (shaderContext->analyzer.hasStreamoutEnable == false) + { +#ifndef PUBLIC_RELEASE + src->add("// omitted streamout write" _CRLF); +#endif + return; + } + uint32 streamoutBufferIndex; + if (cfInstruction->type == GPU7_CF_INST_MEM_STREAM0_WRITE) + streamoutBufferIndex = 0; + else if (cfInstruction->type == GPU7_CF_INST_MEM_STREAM1_WRITE) + streamoutBufferIndex = 1; + else + cemu_assert_unimplemented(); + + if (shaderContext->shaderType == LatteConst::ShaderType::Vertex) + { + uint32 arraySize = cfInstruction->memWriteArraySize + 1; + + for (sint32 i = 0; i < (sint32)arraySize; i++) + { + if ((cfInstruction->memWriteCompMask&(1 << i)) == 0) + continue; + + if (shaderContext->useTFViaSSBO) + { + uint32 u32Offset = cfInstruction->exportArrayBase + i; + src->addFmt("sb_buffer[sbBase{} + {}]", streamoutBufferIndex, u32Offset); + } + else + { + src->addFmt("sb{}[{}]", streamoutBufferIndex, cfInstruction->exportArrayBase + i); + } + + src->add(" = "); + + _emitTypeConversionPrefix(shaderContext, shaderContext->typeTracker.defaultDataType, LATTE_DECOMPILER_DTYPE_SIGNED_INT); + + src->add(_getRegisterVarName(shaderContext, cfInstruction->exportSourceGPR)); + _appendChannelAccess(src, i); + _emitTypeConversionSuffix(shaderContext, shaderContext->typeTracker.defaultDataType, LATTE_DECOMPILER_DTYPE_SIGNED_INT); + + src->add(";" _CRLF); + } + } + else + cemu_assert_debug(false); +} + +void _emitCFCall(LatteDecompilerShaderContext* shaderContext, LatteDecompilerCFInstruction* cfInstruction) +{ + StringBuf* src = shaderContext->shaderSource; + uint32 subroutineAddr = cfInstruction->addr; + LatteDecompilerSubroutineInfo* subroutineInfo = nullptr; + // find subroutine + for (auto& subroutineItr : shaderContext->list_subroutines) + { + if (subroutineItr.cfAddr == subroutineAddr) + { + subroutineInfo = &subroutineItr; + break; + } + } + if (subroutineInfo == nullptr) + { + cemu_assert_debug(false); + return; + } + // inline function + if (shaderContext->isSubroutine) + { + cemu_assert_debug(false); // inlining with cascaded function calls not supported + return; + } + // init CF stack variables + src->addFmt("activeMaskStackSub{:04x}[0] = true;" _CRLF, subroutineInfo->cfAddr); + src->addFmt("activeMaskStackCSub{:04x}[0] = true;" _CRLF, subroutineInfo->cfAddr); + src->addFmt("activeMaskStackCSub{:04x}[1] = true;" _CRLF, subroutineInfo->cfAddr); + + shaderContext->isSubroutine = true; + shaderContext->subroutineInfo = subroutineInfo; + for(auto& cfInstruction : subroutineInfo->instructions) + LatteDecompiler_emitClauseCode(shaderContext, &cfInstruction, true); + shaderContext->isSubroutine = false; + shaderContext->subroutineInfo = nullptr; +} + +void LatteDecompiler_emitClauseCode(LatteDecompilerShaderContext* shaderContext, LatteDecompilerCFInstruction* cfInstruction, bool isSubroutine) +{ + StringBuf* src = shaderContext->shaderSource; + + if( cfInstruction->type == GPU7_CF_INST_ALU || cfInstruction->type == GPU7_CF_INST_ALU_PUSH_BEFORE || cfInstruction->type == GPU7_CF_INST_ALU_POP_AFTER || cfInstruction->type == GPU7_CF_INST_ALU_POP2_AFTER || cfInstruction->type == GPU7_CF_INST_ALU_BREAK || cfInstruction->type == GPU7_CF_INST_ALU_ELSE_AFTER ) + { + // emit ALU code + if (shaderContext->analyzer.modifiesPixelActiveState) + { + if(cfInstruction->type == GPU7_CF_INST_ALU_PUSH_BEFORE) + src->addFmt("if( {} == true ) {{" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1 - 1)); + else + src->addFmt("if( {} == true ) {{" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1)); + } + if (cfInstruction->type == GPU7_CF_INST_ALU_PUSH_BEFORE) + { + src->addFmt("{} = {};" _CRLF, _getActiveMaskVarName(shaderContext, cfInstruction->activeStackDepth), _getActiveMaskVarName(shaderContext, cfInstruction->activeStackDepth-1)); + src->addFmt("{} = {};" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1), _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth)); + } + _emitALUClauseCode(shaderContext, cfInstruction); + if( shaderContext->analyzer.modifiesPixelActiveState ) + src->add("}" _CRLF); + cemu_assert_debug(!(shaderContext->analyzer.modifiesPixelActiveState == false && cfInstruction->type != GPU7_CF_INST_ALU)); + // handle ELSE case of PUSH_BEFORE + if( cfInstruction->type == GPU7_CF_INST_ALU_PUSH_BEFORE ) + { + src->add("else {" _CRLF); + src->addFmt("{} = false;" _CRLF, _getActiveMaskVarName(shaderContext, cfInstruction->activeStackDepth)); + src->addFmt("{} = false;" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1)); + src->add("}" _CRLF); + } + // post clause handler + if( cfInstruction->type == GPU7_CF_INST_ALU_POP_AFTER ) + { + src->addFmt("{} = {} == true && {} == true;" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1 - 1), _getActiveMaskVarName(shaderContext, cfInstruction->activeStackDepth - 1), _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth - 1)); + } + else if( cfInstruction->type == GPU7_CF_INST_ALU_POP2_AFTER ) + { + src->addFmt("{} = {} == true && {} == true;" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1 - 2), _getActiveMaskVarName(shaderContext, cfInstruction->activeStackDepth - 2), _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth - 2)); + } + else if( cfInstruction->type == GPU7_CF_INST_ALU_ELSE_AFTER ) + { + // no condition test + // pop stack + if( cfInstruction->popCount != 0 ) + debugBreakpoint(); + // else operation + src->addFmt("{} = {} == false;" _CRLF, _getActiveMaskVarName(shaderContext, cfInstruction->activeStackDepth), _getActiveMaskVarName(shaderContext, cfInstruction->activeStackDepth)); + src->addFmt("{} = {} == true && {} == true;" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1), _getActiveMaskVarName(shaderContext, cfInstruction->activeStackDepth), _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth)); + } + } + else if( cfInstruction->type == GPU7_CF_INST_TEX ) + { + // emit TEX code + if (shaderContext->analyzer.modifiesPixelActiveState) + { + src->addFmt("if( {} == true ) {{" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth+1)); + } + _emitTEXClauseCode(shaderContext, cfInstruction); + if (shaderContext->analyzer.modifiesPixelActiveState) + { + src->add("}" _CRLF); + } + } + else if( cfInstruction->type == GPU7_CF_INST_EXPORT || cfInstruction->type == GPU7_CF_INST_EXPORT_DONE ) + { + // emit export code + _emitExportCode(shaderContext, cfInstruction); + } + else if( cfInstruction->type == GPU7_CF_INST_ELSE ) + { + // todo: Condition test, popCount? + src->addFmt("{} = {} == false;" _CRLF, _getActiveMaskVarName(shaderContext, cfInstruction->activeStackDepth), _getActiveMaskVarName(shaderContext, cfInstruction->activeStackDepth)); + src->addFmt("{} = {} == true && {} == true;" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1), _getActiveMaskVarName(shaderContext, cfInstruction->activeStackDepth), _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth)); + } + else if( cfInstruction->type == GPU7_CF_INST_POP ) + { + src->addFmt("{} = {} == true && {} == true;" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1 - cfInstruction->popCount), _getActiveMaskVarName(shaderContext, cfInstruction->activeStackDepth - cfInstruction->popCount), _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth - cfInstruction->popCount)); + } + else if( cfInstruction->type == GPU7_CF_INST_LOOP_START_DX10 ) + { + // start of loop + // if pixel is disabled, then skip loop + if (ActiveSettings::ShaderPreventInfiniteLoopsEnabled()) + { + // with iteration limit to prevent infinite loops + src->addFmt("int loopCounter{} = 0;" _CRLF, (sint32)cfInstruction->cfAddr); + src->addFmt("while( {} == true && loopCounter{} < 500 )" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1), (sint32)cfInstruction->cfAddr); + src->add("{" _CRLF); + src->addFmt("loopCounter{}++;" _CRLF, (sint32)cfInstruction->cfAddr); + } + else + { + src->addFmt("while( {} == true )" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1)); + src->add("{" _CRLF); + } + } + else if( cfInstruction->type == GPU7_CF_INST_LOOP_END ) + { + // this might not always work + if( cfInstruction->popCount != 0 ) + debugBreakpoint(); + src->add("}" _CRLF); + } + else if( cfInstruction->type == GPU7_CF_INST_LOOP_BREAK ) + { + if( cfInstruction->popCount != 0 ) + debugBreakpoint(); + if (shaderContext->analyzer.modifiesPixelActiveState) + { + src->addFmt("if( {} == true ) {{" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1)); + } + // note: active stack level is set to the same level as the loop begin. popCount is ignored + src->add("break;" _CRLF); + + if (shaderContext->analyzer.modifiesPixelActiveState) + src->add("}" _CRLF); + + } + else if( cfInstruction->type == GPU7_CF_INST_MEM_STREAM0_WRITE || + cfInstruction->type == GPU7_CF_INST_MEM_STREAM1_WRITE ) + { + _emitStreamWriteCode(shaderContext, cfInstruction); + } + else if( cfInstruction->type == GPU7_CF_INST_MEM_RING_WRITE ) + { + _emitCFRingWriteCode(shaderContext, cfInstruction); + } + else if( cfInstruction->type == GPU7_CF_INST_EMIT_VERTEX ) + { + if( shaderContext->analyzer.modifiesPixelActiveState ) + src->addFmt("if( {} == true ) {{" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1)); + // write point size + if (shaderContext->analyzer.outputPointSize && shaderContext->analyzer.writesPointSize == false) + src->add("gl_PointSize = uf_pointSize;" _CRLF); + // emit vertex + src->add("EmitVertex();" _CRLF); + // increment transform feedback pointer + if (shaderContext->analyzer.useSSBOForStreamout) + { + for (sint32 i = 0; i < LATTE_NUM_STREAMOUT_BUFFER; i++) + { + if (!shaderContext->output->streamoutBufferWriteMask[i]) + continue; + cemu_assert_debug((shaderContext->output->streamoutBufferStride[i] & 3) == 0); + src->addFmt("sbBase{} += {};" _CRLF, i, shaderContext->output->streamoutBufferStride[i] / 4); + } + } + + if( shaderContext->analyzer.modifiesPixelActiveState ) + src->add("}" _CRLF); + } + else if (cfInstruction->type == GPU7_CF_INST_CALL) + { + _emitCFCall(shaderContext, cfInstruction); + } + else if (cfInstruction->type == GPU7_CF_INST_RETURN) + { + // todo (handle properly) + } + else + { + cemu_assert_debug(false); + } +} + +void LatteDecompiler_emitGLSLHelperFunctions(LatteDecompilerShaderContext* shaderContext, StringBuf* fCStr_shaderSource) +{ + if( shaderContext->analyzer.hasRedcCUBE ) + { + fCStr_shaderSource->add("void redcCUBE(vec4 src0, vec4 src1, out vec3 stm, out int faceId)\r\n" + "{\r\n" + "// stm -> x .. s, y .. t, z .. MajorAxis*2.0\r\n" + + "vec3 inputCoord = normalize(vec3(src1.y, src1.x, src0.x));\r\n" + + "float rx = inputCoord.x;\r\n" + "float ry = inputCoord.y;\r\n" + "float rz = inputCoord.z;\r\n" + "if( abs(rx) > abs(ry) && abs(rx) > abs(rz) )\r\n" + "{\r\n" + "stm.z = rx*2.0;\r\n" + "stm.xy = vec2(ry,rz); \r\n" + "if( rx >= 0.0 )\r\n" + "{\r\n" + "faceId = 0;\r\n" + "}\r\n" + "else\r\n" + "{\r\n" + "faceId = 1;\r\n" + "}\r\n" + "}\r\n" + "else if( abs(ry) > abs(rx) && abs(ry) > abs(rz) )\r\n" + "{\r\n" + "stm.z = ry*2.0;\r\n" + "stm.xy = vec2(rx,rz); \r\n" + "if( ry >= 0.0 )\r\n" + "{\r\n" + "faceId = 2;\r\n" + "}\r\n" + "else\r\n" + "{\r\n" + "faceId = 3;\r\n" + "}\r\n" + "}\r\n" + "else //if( abs(rz) > abs(ry) && abs(rz) > abs(rx) )\r\n" + "{\r\n" + "stm.z = rz*2.0;\r\n" + "stm.xy = vec2(rx,ry); \r\n" + "if( rz >= 0.0 )\r\n" + "{\r\n" + "faceId = 4;\r\n" + "}\r\n" + "else\r\n" + "{\r\n" + "faceId = 5;\r\n" + "}\r\n" + "}\r\n" + "}\r\n"); + } + + if( shaderContext->analyzer.hasCubeMapTexture ) + { + fCStr_shaderSource->add("vec3 redcCUBEReverse(vec2 st, int faceId)\r\n" + "{\r\n" + "st.yx = st.xy;\r\n" + "vec3 v;\r\n" + "float majorAxis = 1.0;\r\n" + "if( faceId == 0 )\r\n" + "{\r\n" + "v.yz = (st-vec2(1.5))*(majorAxis*2.0);\r\n" + "v.x = 1.0;\r\n" + "}\r\n" + "else if( faceId == 1 )\r\n" + "{\r\n" + "v.yz = (st-vec2(1.5))*(majorAxis*2.0);\r\n" + "v.x = -1.0;\r\n" + "}\r\n" + "else if( faceId == 2 )\r\n" + "{\r\n" + "v.xz = (st-vec2(1.5))*(majorAxis*2.0);\r\n" + "v.y = 1.0;\r\n" + "}\r\n" + "else if( faceId == 3 )\r\n" + "{\r\n" + "v.xz = (st-vec2(1.5))*(majorAxis*2.0);\r\n" + "v.y = -1.0;\r\n" + "}\r\n" + "else if( faceId == 4 )\r\n" + "{\r\n" + "v.xy = (st-vec2(1.5))*(majorAxis*2.0);\r\n" + "v.z = 1.0;\r\n" + "}\r\n" + "else\r\n" + "{\r\n" + "v.xy = (st-vec2(1.5))*(majorAxis*2.0);\r\n" + "v.z = -1.0;\r\n" + "}\r\n" + + + + "return v;\r\n" + "}\r\n"); + } + + // clamp + fCStr_shaderSource->add("" + "int clampFI32(int v)\r\n" + "{\r\n" + "if( v == 0x7FFFFFFF )\r\n" + " return floatBitsToInt(1.0);\r\n" + "else if( v == 0xFFFFFFFF )\r\n" + " return floatBitsToInt(0.0);\r\n" + "return floatBitsToInt(clamp(intBitsToFloat(v), 0.0, 1.0));\r\n" + "}\r\n"); + // mul non-ieee way (0*NaN/INF => 0.0) + if (g_current_game_profile->GetAccurateShaderMul() == AccurateShaderMulOption::True) + { + // things we tried: + //fCStr_shaderSource->add("float mul_nonIEEE(float a, float b){ return mix(a*b,0.0,a==0.0||b==0.0); }" STR_LINEBREAK); + //fCStr_shaderSource->add("float mul_nonIEEE(float a, float b){ return mix(vec2(a*b,0.0),vec2(0.0,0.0),(equal(vec2(a),vec2(0.0,0.0))||equal(vec2(b),vec2(0.0,0.0)))).x; }" STR_LINEBREAK); + //fCStr_shaderSource->add("float mul_nonIEEE(float a, float b){ if( a == 0.0 || b == 0.0 ) return 0.0; return a*b; }" STR_LINEBREAK); + //fCStr_shaderSource->add("float mul_nonIEEE(float a, float b){float r = a*b;r = intBitsToFloat(floatBitsToInt(r)&(((floatBitsToInt(a) != 0) && (floatBitsToInt(b) != 0))?0xFFFFFFFF:0));return r;}" STR_LINEBREAK); works + + if( LatteGPUState.glVendor == GLVENDOR_NVIDIA && !ActiveSettings::DumpShadersEnabled()) + fCStr_shaderSource->add("float mul_nonIEEE(float a, float b){return mix(0.0, a*b, (a != 0.0) && (b != 0.0));}" _CRLF); // compiles faster on Nvidia and also results in lower RAM usage + else + fCStr_shaderSource->add("float mul_nonIEEE(float a, float b){ if( a == 0.0 || b == 0.0 ) return 0.0; return a*b; }" _CRLF); + } + else + { + fCStr_shaderSource->add("float mul_nonIEEE(float a, float b){ return min(a*b,min(abs(a)*3.40282347E+38F,abs(b)*3.40282347E+38F)); }" _CRLF); + } +} + +void _addPixelShaderExtraDebugInfo(LatteDecompilerShaderContext* shaderContext, StringBuf* fCStr_shaderSource) +{ +#ifndef PUBLIC_RELEASE + fCStr_shaderSource->add("// Color buffers:" _CRLF); + for(uint32 i=0; i<8; i++) + { + uint32 regColorBuffer = shaderContext->contextRegisters[mmCB_COLOR0_BASE+i]; + uint32 regColorSize = shaderContext->contextRegisters[mmCB_COLOR0_SIZE+i]; + uint32 regColorInfo = shaderContext->contextRegisters[mmCB_COLOR0_INFO+i]; + uint32 regColorView = shaderContext->contextRegisters[mmCB_COLOR0_VIEW+i]; + MPTR colorBufferPhysMem = regColorBuffer; + if( regColorBuffer == MPTR_NULL ) + continue; + + uint32 colorBufferFormat = (regColorInfo>>2)&0x3F; // format + uint32 colorBufferTileMode = 0; + colorBufferTileMode = (regColorInfo >> 8) & 0xF; + switch ( (regColorInfo >> 12) & 7 ) + { + case 4: + colorBufferFormat |= 0x100; + break; + case 1: + colorBufferFormat |= 0x200; + break; + case 5: + colorBufferFormat |= 0x300; + break; + case 6: + colorBufferFormat |= 0x400; + break; + case 7: + colorBufferFormat |= 0x800; + break; + default: + break; + } + + uint32 colorBufferWidth = (regColorSize>>0)&0xFFFF; + uint32 colorBufferHeight = (regColorSize>>16)&0xFFFF; + fCStr_shaderSource->addFmt("// Color{}: {}x{} at 0x{:08x} fmt {:04x} tm {}" _CRLF, i, colorBufferWidth, colorBufferHeight, colorBufferPhysMem, colorBufferFormat, colorBufferTileMode); + } +#endif +} + +#include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLHeader.hpp" + +void LatteDecompiler_emitAttributeImport(LatteDecompilerShaderContext* shaderContext, LatteParsedFetchShaderAttribute_t& attrib) +{ + auto src = shaderContext->shaderSource; + + static const char* dsMappingTableFloat[6] = { "int(attrDecoder.x)", "int(attrDecoder.y)", "int(attrDecoder.z)", "int(attrDecoder.w)", "floatBitsToInt(0.0)", "floatBitsToInt(1.0)" }; + static const char* dsMappingTableInt[6] = { "int(attrDecoder.x)", "int(attrDecoder.y)", "int(attrDecoder.z)", "int(attrDecoder.w)", "0", "1" }; + + // get register index based on vtx semantic table + uint32 attributeShaderLoc = 0xFFFFFFFF; + for (sint32 f = 0; f < 32; f++) + { + if (shaderContext->contextRegisters[mmSQ_VTX_SEMANTIC_0 + f] == attrib.semanticId) + { + attributeShaderLoc = f; + break; + } + } + if (attributeShaderLoc == 0xFFFFFFFF) + return; // attribute is not mapped to VS input + uint32 registerIndex = attributeShaderLoc + 1; // R0 is skipped + // is register used? + if ((shaderContext->analyzer.gprUseMask[registerIndex / 8] & (1 << (registerIndex % 8))) == 0) + { + src->addFmt("// skipped unused attribute for r{}" _CRLF, registerIndex); + return; + } + + LatteDecompiler_emitAttributeDecodeGLSL(shaderContext->shader, src, &attrib); + + if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) + src->addFmt("{} = ivec4(", _getRegisterVarName(shaderContext, registerIndex)); + else + src->addFmt("{} = vec4(", _getRegisterVarName(shaderContext, registerIndex)); + for (sint32 f = 0; f < 4; f++) + { + uint8 ds = attrib.ds[f]; + if (f > 0) + src->add(", "); + _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, shaderContext->typeTracker.defaultDataType); + if (ds >= 6) + { + cemu_assert_unimplemented(); + ds = 4; // read as 0.0 + } + if (attrib.nfa != 1) + { + src->add(dsMappingTableFloat[ds]); + } + else + { + src->add(dsMappingTableInt[ds]); + } + _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, shaderContext->typeTracker.defaultDataType); + } + src->add(");" _CRLF); +} + +void LatteDecompiler_emitGLSLShader(LatteDecompilerShaderContext* shaderContext, LatteDecompilerShader* shader) +{ + StringBuf* src = new StringBuf(1024*1024*12); // reserve 12MB for generated source (we resize-to-fit at the end) + shaderContext->shaderSource = src; + // GLSL shader header + src->add("#version 430" _CRLF); // 430 is required for shader storage (Vulkan alternative TF path) + src->add("#extension GL_ARB_texture_gather : enable" _CRLF); + src->add("#extension GL_ARB_separate_shader_objects : enable" _CRLF); + + if (shaderContext->analyzer.hasStreamoutWrite || shaderContext->usesGeometryShader ) + src->add("#extension GL_ARB_enhanced_layouts : enable" _CRLF); + + // debug info + src->addFmt("// shader %08x%08x" _CRLF, (uint32)(shaderContext->shaderBaseHash >> 32), (uint32)(shaderContext->shaderBaseHash & 0xFFFFFFFF)); +#ifndef PUBLIC_RELEASE + src->addFmt("// usesIntegerValues: {}" _CRLF, shaderContext->analyzer.usesIntegerValues?"true":"false"); + src->addFmt(_CRLF); + + if( shader->shaderType == LatteConst::ShaderType::Pixel ) + _addPixelShaderExtraDebugInfo(shaderContext, src); + +#endif + // header part (definitions for inputs and outputs) + LatteDecompiler::emitHeader(shaderContext); + // helper functions + LatteDecompiler_emitGLSLHelperFunctions(shaderContext, src); + // start of main + src->add("void main()" _CRLF); + src->add("{" _CRLF); + // variable definition + if (shaderContext->typeTracker.useArrayGPRs == false) + { + // each register is a separate variable + for (sint32 i = 0; i < 128; i++) + { + if (shaderContext->analyzer.usesRelativeGPRRead || (shaderContext->analyzer.gprUseMask[i / 8] & (1 << (i & 7))) != 0) + { + //fCStr_appendFormatted(fCStr_shaderSource, "ivec4 R{}i, R{}i, R{}i, R{}i;" STR_LINEBREAK, i*4+0, i*4+1, i*4+2, i*4+3); + if (shaderContext->typeTracker.genIntReg) + src->addFmt("ivec4 R{}i = ivec4(0);" _CRLF, i); + else if (shaderContext->typeTracker.genFloatReg) + src->addFmt("vec4 R{}f = vec4(0.0);" _CRLF, i); + } + } + } + else + { + // registers are represented using a single large array + if (shaderContext->typeTracker.genIntReg) + src->addFmt("ivec4 Ri[128];" _CRLF); + else if (shaderContext->typeTracker.genFloatReg) + src->addFmt("vec4 Rf[128];" _CRLF); + for (sint32 i = 0; i < 128; i++) + { + if (shaderContext->typeTracker.genIntReg) + src->addFmt("Ri[{}] = ivec4(0);" _CRLF, i); + else if (shaderContext->typeTracker.genFloatReg) + src->addFmt("Rf[{}] = vec4(0.0);" _CRLF, i); + } + } + + if( shader->shaderType == LatteConst::ShaderType::Vertex ) + src->addFmt("uvec4 attrDecoder;" _CRLF); + if (shaderContext->typeTracker.genIntReg) + src->addFmt("int backupReg0i, backupReg1i, backupReg2i, backupReg3i, backupReg4i;" _CRLF); + if (shaderContext->typeTracker.genFloatReg) + src->addFmt("float backupReg0f, backupReg1f, backupReg2f, backupReg3f, backupReg4f;" _CRLF); + if (shaderContext->typeTracker.genIntReg) + { + src->addFmt("ivec4 PV0i = ivec4(0), PV1i = ivec4(0);" _CRLF); + src->addFmt("int PS0i = 0, PS1i = 0;" _CRLF); + src->addFmt("ivec4 tempi = ivec4(0);" _CRLF); + } + if (shaderContext->typeTracker.genFloatReg) + { + src->addFmt("vec4 PV0f = vec4(0.0), PV1f = vec4(0.0);" _CRLF); + src->addFmt("float PS0f = 0.0, PS1f = 0.0;" _CRLF); + src->addFmt("vec4 tempf = vec4(0.0);" _CRLF); + } + if (shaderContext->analyzer.hasGradientLookup) + { + src->add("vec4 gradH;" _CRLF); + src->add("vec4 gradV;" _CRLF); + } + src->add("float tempResultf;" _CRLF); + src->add("int tempResulti;" _CRLF); + src->add("ivec4 ARi = ivec4(0);" _CRLF); + src->add("bool predResult = true;" _CRLF); + if(shaderContext->analyzer.modifiesPixelActiveState ) + { + // cemu_assert_debug(shaderContext->analyzer.activeStackMaxDepth == 0); + src->addFmt("bool activeMaskStack[{}];" _CRLF, shaderContext->analyzer.activeStackMaxDepth+1); + src->addFmt("bool activeMaskStackC[{}];" _CRLF, shaderContext->analyzer.activeStackMaxDepth+2); + for (sint32 i = 0; i < shaderContext->analyzer.activeStackMaxDepth; i++) + { + src->addFmt("activeMaskStack[{}] = false;" _CRLF, i); + } + for (sint32 i = 0; i < shaderContext->analyzer.activeStackMaxDepth+1; i++) + { + src->addFmt("activeMaskStackC[{}] = false;" _CRLF, i); + } + src->addFmt("activeMaskStack[0] = true;" _CRLF); + src->addFmt("activeMaskStackC[0] = true;" _CRLF); + src->addFmt("activeMaskStackC[1] = true;" _CRLF); + // generate vars for each subroutine + for (auto& subroutineInfo : shaderContext->list_subroutines) + { + sint32 subroutineMaxStackDepth = 0; + src->addFmt("bool activeMaskStackSub%04x[{}];" _CRLF, subroutineInfo.cfAddr, subroutineMaxStackDepth + 1); + src->addFmt("bool activeMaskStackCSub%04x[{}];" _CRLF, subroutineInfo.cfAddr, subroutineMaxStackDepth + 2); + } + } + // helper variables for cube maps (todo: Only emit when used) + src->addFmt("vec3 cubeMapSTM;" _CRLF); + src->addFmt("int cubeMapFaceId;" _CRLF); + for(sint32 i=0; i<LATTE_NUM_MAX_TEX_UNITS; i++) + { + if(!shaderContext->output->textureUnitMask[i]) + continue; + if( shader->textureUnitDim[i] != Latte::E_DIM::DIM_CUBEMAP ) + continue; + src->addFmt("float cubeMapArrayIndex{} = 0.0;" _CRLF, i); + } + // init base offset for streamout buffer writes + if (shaderContext->analyzer.useSSBOForStreamout && (shader->shaderType == LatteConst::ShaderType::Vertex || shader->shaderType == LatteConst::ShaderType::Geometry)) + { + for (sint32 i = 0; i < LATTE_NUM_STREAMOUT_BUFFER; i++) + { + if(!shaderContext->output->streamoutBufferWriteMask[i]) + continue; + + cemu_assert_debug((shaderContext->output->streamoutBufferStride[i]&3) == 0); + + if (shader->shaderType == LatteConst::ShaderType::Vertex) // vertex shader + src->addFmt("int sbBase{} = uf_streamoutBufferBase{}/4 + (gl_VertexID + uf_verticesPerInstance * gl_InstanceID)*{};" _CRLF, i, i, shaderContext->output->streamoutBufferStride[i] / 4); + else // geometry shader + { + uint32 gsOutPrimType = shaderContext->contextRegisters[mmVGT_GS_OUT_PRIM_TYPE]; + uint32 bytesPerVertex = shaderContext->contextRegisters[mmSQ_GS_VERT_ITEMSIZE] * 4; + uint32 maxVerticesInGS = ((shaderContext->contextRegisters[mmSQ_GSVS_RING_ITEMSIZE] & 0x7FFF) * 4) / bytesPerVertex; + + cemu_assert_debug(gsOutPrimType == 0); // currently we only properly handle GS output primitive points + + src->addFmt("int sbBase{} = uf_streamoutBufferBase{}/4 + (gl_PrimitiveIDIn * {})*{};" _CRLF, i, i, maxVerticesInGS, shaderContext->output->streamoutBufferStride[i] / 4); + } + } + + } + // code to load inputs from previous stage + if( shader->shaderType == LatteConst::ShaderType::Vertex ) + { + if( (shaderContext->analyzer.gprUseMask[0/8]&(1<<(0%8))) != 0 ) + { + if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) + src->addFmt("{} = ivec4(gl_VertexID, 0, 0, gl_InstanceID);" _CRLF, _getRegisterVarName(shaderContext, 0)); + else if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_FLOAT) + src->addFmt("{} = floatBitsToInt(ivec4(gl_VertexID, 0, 0, gl_InstanceID));" _CRLF, _getRegisterVarName(shaderContext, 0)); + else + cemu_assert_unimplemented(); + } + + if (shaderContext->fetchShaderCount == 1) + { + LatteFetchShader* parsedFetchShader = shaderContext->fetchShaderList[0]; + for(auto& bufferGroup : parsedFetchShader->bufferGroups) + { + for(sint32 i=0; i<bufferGroup.attribCount; i++) + LatteDecompiler_emitAttributeImport(shaderContext, bufferGroup.attrib[i]); + } + for (auto& bufferGroup : parsedFetchShader->bufferGroupsInvalid) + { + // these attributes point to non-existent buffers + // todo - figure out how the hardware actually handles this, currently we assume the input values are zero + for (sint32 i = 0; i < bufferGroup.attribCount; i++) + LatteDecompiler_emitAttributeImport(shaderContext, bufferGroup.attrib[i]); + } + } + else + { + cemu_assert_unimplemented(); + } + } + else if (shader->shaderType == LatteConst::ShaderType::Pixel) + { + LatteShaderPSInputTable* psInputTable = LatteSHRC_GetPSInputTable(); + + uint32 psControl0 = shaderContext->contextRegisters[mmSPI_PS_IN_CONTROL_0]; + uint32 psControl1 = shaderContext->contextRegisters[mmSPI_PS_IN_CONTROL_1]; + + uint32 spiInterpControl = shaderContext->contextRegisters[mmSPI_INTERP_CONTROL_0]; + uint8 spriteEnable = (spiInterpControl >> 1) & 1; + cemu_assert_debug(spriteEnable == 0); + + uint8 frontFace_enabled = (psControl1 >> 8) & 1; + uint8 frontFace_chan = (psControl1 >> 9) & 3; + uint8 frontFace_allBits = (psControl1 >> 11) & 1; + uint8 frontFace_regIndex = (psControl1 >> 12) & 0x1F; + + // handle param_gen + if (psInputTable->paramGen != 0) + { + cemu_assert_debug((psInputTable->paramGen) == 1); // handle the other bits (the same set of coordinates with different perspective/projection settings?) + uint32 paramGenGPRIndex = psInputTable->paramGenGPR; + if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_FLOAT) + src->addFmt("{} = gl_PointCoord.xyxy;" _CRLF, _getRegisterVarName(shaderContext, paramGenGPRIndex)); + else + src->addFmt("{} = floatBitsToInt(gl_PointCoord.xyxy);" _CRLF, _getRegisterVarName(shaderContext, paramGenGPRIndex)); + } + + for (sint32 i = 0; i < psInputTable->count; i++) + { + uint32 psControl0 = shaderContext->contextRegisters[mmSPI_PS_IN_CONTROL_0]; + uint32 spi0_paramGen = (psControl0 >> 15) & 0xF; + + sint32 gprIndex = i;// +spi0_paramGen + paramRegOffset; + if ((shaderContext->analyzer.gprUseMask[gprIndex / 8] & (1 << (gprIndex % 8))) == 0 && shaderContext->analyzer.usesRelativeGPRRead == false) + continue; + uint32 psInputSemanticId = psInputTable->import[i].semanticId; + if (psInputSemanticId == LATTE_ANALYZER_IMPORT_INDEX_SPIPOSITION) + { + if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_FLOAT) + src->addFmt("{} = GET_FRAGCOORD();" _CRLF, _getRegisterVarName(shaderContext, gprIndex)); + else + src->addFmt("{} = floatBitsToInt(GET_FRAGCOORD());" _CRLF, _getRegisterVarName(shaderContext, gprIndex)); + continue; + } + + if (shaderContext->usesGeometryShader) + { + // import from geometry shader + if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) + src->addFmt("{} = floatBitsToInt(passG2PParameter{});" _CRLF, _getRegisterVarName(shaderContext, gprIndex), psInputSemanticId & 0x7F); + else if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_FLOAT) + src->addFmt("{} = passG2PParameter{};" _CRLF, _getRegisterVarName(shaderContext, gprIndex), psInputSemanticId & 0x7F); + else + cemu_assert_unimplemented(); + } + else + { + // import from vertex shader + if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) + src->addFmt("{} = floatBitsToInt(passParameterSem{});" _CRLF, _getRegisterVarName(shaderContext, gprIndex), psInputSemanticId); + else if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_FLOAT) + src->addFmt("{} = passParameterSem{};" _CRLF, _getRegisterVarName(shaderContext, gprIndex), psInputSemanticId); + else + cemu_assert_unimplemented(); + } + } + // front facing attribute + if (frontFace_enabled) + { + if ((shaderContext->analyzer.gprUseMask[0 / 8] & (1 << (0 % 8))) != 0) + { + if (frontFace_allBits) + cemu_assert_debug(false); + if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_SIGNED_INT) + src->addFmt("{}.{} = floatBitsToInt(gl_FrontFacing?1.0:0.0);" _CRLF, _getRegisterVarName(shaderContext, frontFace_regIndex), _getElementStrByIndex(frontFace_chan)); + else if (shaderContext->typeTracker.defaultDataType == LATTE_DECOMPILER_DTYPE_FLOAT) + src->addFmt("{}.{} = gl_FrontFacing?1.0:0.0;" _CRLF, _getRegisterVarName(shaderContext, frontFace_regIndex), _getElementStrByIndex(frontFace_chan)); + else + cemu_assert_debug(false); + } + } + } + for(auto& cfInstruction : shaderContext->cfInstructions) + LatteDecompiler_emitClauseCode(shaderContext, &cfInstruction, false); + if( shader->shaderType == LatteConst::ShaderType::Geometry ) + src->add("EndPrimitive();" _CRLF); + // vertex shader should write renderstate point size at the end if required but not modified by shader + if (shaderContext->analyzer.outputPointSize && shaderContext->analyzer.writesPointSize == false) + { + if (shader->shaderType == LatteConst::ShaderType::Vertex && shaderContext->usesGeometryShader == false) + src->add("gl_PointSize = uf_pointSize;" _CRLF); + } + // end of shader main + src->add("}" _CRLF); + src->shrink_to_fit(); + shader->strBuf_shaderSource = src; +} diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLAttrDecoder.cpp b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLAttrDecoder.cpp new file mode 100644 index 00000000..1b6b0d16 --- /dev/null +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLAttrDecoder.cpp @@ -0,0 +1,498 @@ +#include "Cafe/HW/Latte/Core/LatteConst.h" +#include "Cafe/HW/Latte/Core/LatteShaderAssembly.h" +#include "Cafe/HW/Latte/ISA/RegDefines.h" +#include "Cafe/HW/Latte/Core/Latte.h" +#include "Cafe/HW/Latte/Core/LatteDraw.h" +#include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h" +#include "Cafe/HW/Latte/Core/FetchShader.h" +#include "Cafe/HW/Latte/Renderer/Renderer.h" +#include "util/helpers/StringBuf.h" + +#define _CRLF "\r\n" + +void _readLittleEndianAttributeU32x4(LatteDecompilerShader* shaderContext, StringBuf* src, uint32 attributeInputIndex) +{ + src->addFmt("attrDecoder = attrDataSem{};" _CRLF, attributeInputIndex); +} + +void _readLittleEndianAttributeU32x3(LatteDecompilerShader* shaderContext, StringBuf* src, uint32 attributeInputIndex) +{ + src->addFmt("attrDecoder = uvec4(attrDataSem{}.xyz,0);" _CRLF, attributeInputIndex); +} + +void _readLittleEndianAttributeU32x2(LatteDecompilerShader* shaderContext, StringBuf* src, uint32 attributeInputIndex) +{ + src->addFmt("attrDecoder = uvec4(attrDataSem{}.xy,0,0);" _CRLF, attributeInputIndex); +} + +void _readLittleEndianAttributeU32x1(LatteDecompilerShader* shaderContext, StringBuf* src, uint32 attributeInputIndex) +{ + src->addFmt("attrDecoder = uvec4(attrDataSem{}.x,0,0,0);" _CRLF, attributeInputIndex); +} + +void _readLittleEndianAttributeU16x2(LatteDecompilerShader* shaderContext, StringBuf* src, uint32 attributeInputIndex) +{ + src->addFmt("attrDecoder = uvec4(attrDataSem{}.xy,0,0);" _CRLF, attributeInputIndex); +} + +void _readLittleEndianAttributeU16x4(LatteDecompilerShader* shaderContext, StringBuf* src, uint32 attributeInputIndex) +{ + src->addFmt("attrDecoder = attrDataSem{};" _CRLF, attributeInputIndex); +} + +void _readBigEndianAttributeU32x4(LatteDecompilerShader* shaderContext, StringBuf* src, uint32 attributeInputIndex) +{ + src->addFmt("attrDecoder = attrDataSem{};" _CRLF, attributeInputIndex); + src->add("attrDecoder = (attrDecoder>>24)|((attrDecoder>>8)&0xFF00)|((attrDecoder<<8)&0xFF0000)|((attrDecoder<<24));" _CRLF); +} + +void _readBigEndianAttributeU32x3(LatteDecompilerShader* shaderContext, StringBuf* src, uint32 attributeInputIndex) +{ + src->addFmt("attrDecoder.xyz = attrDataSem{}.xyz;" _CRLF, attributeInputIndex); + src->add("attrDecoder.xyz = (attrDecoder.xyz>>24)|((attrDecoder.xyz>>8)&0xFF00)|((attrDecoder.xyz<<8)&0xFF0000)|((attrDecoder.xyz<<24));" _CRLF); + src->add("attrDecoder.w = 0;" _CRLF); +} + +void _readBigEndianAttributeU32x2(LatteDecompilerShader* shaderContext, StringBuf* src, uint32 attributeInputIndex) +{ + src->addFmt("attrDecoder.xy = attrDataSem{}.xy;" _CRLF, attributeInputIndex); + src->add("attrDecoder.xy = (attrDecoder.xy>>24)|((attrDecoder.xy>>8)&0xFF00)|((attrDecoder.xy<<8)&0xFF0000)|((attrDecoder.xy<<24));" _CRLF); + src->add("attrDecoder.z = 0;" _CRLF); + src->add("attrDecoder.w = 0;" _CRLF); +} + +void _readBigEndianAttributeU32x1(LatteDecompilerShader* shaderContext, StringBuf* src, uint32 attributeInputIndex) +{ + src->addFmt("attrDecoder.x = attrDataSem{}.x;" _CRLF, attributeInputIndex); + src->add("attrDecoder.x = (attrDecoder.x>>24)|((attrDecoder.x>>8)&0xFF00)|((attrDecoder.x<<8)&0xFF0000)|((attrDecoder.x<<24));" _CRLF); + src->add("attrDecoder.y = 0;" _CRLF); + src->add("attrDecoder.z = 0;" _CRLF); + src->add("attrDecoder.w = 0;" _CRLF); +} + +void _readBigEndianAttributeU16x1(LatteDecompilerShader* shaderContext, StringBuf* src, uint32 attributeInputIndex) +{ + src->addFmt("attrDecoder.xy = attrDataSem{}.xy;" _CRLF, attributeInputIndex); + src->add("attrDecoder.x = ((attrDecoder.x>>8)&0xFF)|((attrDecoder.x<<8)&0xFF00);" _CRLF); + src->add("attrDecoder.y = 0;" _CRLF); + src->add("attrDecoder.z = 0;" _CRLF); + src->add("attrDecoder.w = 0;" _CRLF); +} + +void _readBigEndianAttributeU16x2(LatteDecompilerShader* shaderContext, StringBuf* src, uint32 attributeInputIndex) +{ + src->addFmt("attrDecoder.xy = attrDataSem{}.xy;" _CRLF, attributeInputIndex); + src->add("attrDecoder.xy = ((attrDecoder.xy>>8)&0xFF)|((attrDecoder.xy<<8)&0xFF00);" _CRLF); + src->add("attrDecoder.z = 0;" _CRLF); + src->add("attrDecoder.w = 0;" _CRLF); +} + +void _readBigEndianAttributeU16x4(LatteDecompilerShader* shaderContext, StringBuf* src, uint32 attributeInputIndex) +{ + src->addFmt("attrDecoder.xyzw = attrDataSem{}.xyzw;" _CRLF, attributeInputIndex); + src->add("attrDecoder = ((attrDecoder>>8)&0xFF)|((attrDecoder<<8)&0xFF00);" _CRLF); +} + +void LatteDecompiler_emitAttributeDecodeGLSL(LatteDecompilerShader* shaderContext, StringBuf* src, LatteParsedFetchShaderAttribute_t* attrib) +{ + if (attrib->attributeBufferIndex >= Latte::GPU_LIMITS::NUM_VERTEX_BUFFERS) + { + src->add("attrDecoder = ivec4(0);" _CRLF); + return; + } + + uint32 attributeInputIndex = attrib->semanticId; + if( attrib->endianSwap == LatteConst::VertexFetchEndianMode::SWAP_U32 ) + { + if( attrib->format == FMT_32_32_32_32_FLOAT && attrib->nfa == 2 ) + { + _readBigEndianAttributeU32x4(shaderContext, src, attributeInputIndex); + } + else if( attrib->format == FMT_32_32_32_FLOAT && attrib->nfa == 2 ) + { + _readBigEndianAttributeU32x3(shaderContext, src, attributeInputIndex); + } + else if( attrib->format == FMT_32_32_FLOAT && attrib->nfa == 2 ) + { + _readBigEndianAttributeU32x2(shaderContext, src, attributeInputIndex); + } + else if( attrib->format == FMT_32_FLOAT && attrib->nfa == 2 ) + { + _readBigEndianAttributeU32x1(shaderContext, src, attributeInputIndex); + } + else if( attrib->format == FMT_2_10_10_10 && attrib->nfa == 0 ) + { + _readBigEndianAttributeU32x1(shaderContext, src, attributeInputIndex); + // Bayonetta 2 uses this format to store normals + src->add("attrDecoder.xyzw = uvec4((attrDecoder.x>>0)&0x3FF,(attrDecoder.x>>10)&0x3FF,(attrDecoder.x>>20)&0x3FF,(attrDecoder.x>>30)&0x3);" _CRLF); + if (attrib->isSigned != 0) + { + src->add("if( (attrDecoder.x&0x200) != 0 ) attrDecoder.x |= 0xFFFFFC00;" _CRLF); + src->add("if( (attrDecoder.y&0x200) != 0 ) attrDecoder.y |= 0xFFFFFC00;" _CRLF); + src->add("if( (attrDecoder.z&0x200) != 0 ) attrDecoder.z |= 0xFFFFFC00;" _CRLF); + src->add("attrDecoder.x = floatBitsToUint(max(float(int(attrDecoder.x))/511.0,-1.0));" _CRLF); + src->add("attrDecoder.y = floatBitsToUint(max(float(int(attrDecoder.y))/511.0,-1.0));" _CRLF); + src->add("attrDecoder.z = floatBitsToUint(max(float(int(attrDecoder.z))/511.0,-1.0));" _CRLF); + } + else + { + src->add("attrDecoder.x = floatBitsToUint(max(float(int(attrDecoder.x))/1023.0,-1.0));" _CRLF); + src->add("attrDecoder.y = floatBitsToUint(max(float(int(attrDecoder.y))/1023.0,-1.0));" _CRLF); + src->add("attrDecoder.z = floatBitsToUint(max(float(int(attrDecoder.z))/1023.0,-1.0));" _CRLF); + } + src->add("attrDecoder.w = floatBitsToUint(float(attrDecoder.w));" _CRLF); // unsure? + + } + else if( attrib->format == FMT_32_32_32_32 && attrib->nfa == 1 && attrib->isSigned == 0 ) + { + _readBigEndianAttributeU32x4(shaderContext, src, attributeInputIndex); + } + else if( attrib->format == FMT_32_32_32 && attrib->nfa == 1 && attrib->isSigned == 0 ) + { + _readBigEndianAttributeU32x3(shaderContext, src, attributeInputIndex); + } + else if( attrib->format == FMT_32_32 && attrib->nfa == 1 && attrib->isSigned == 0 ) + { + _readBigEndianAttributeU32x2(shaderContext, src, attributeInputIndex); + } + else if (attrib->format == FMT_32 && attrib->nfa == 1 && attrib->isSigned == 0) + { + _readBigEndianAttributeU32x1(shaderContext, src, attributeInputIndex); + } + else if (attrib->format == FMT_32 && attrib->nfa == 1 && attrib->isSigned == 1) + { + // we can just read the signed s32 as a u32 since no sign-extension is necessary + _readBigEndianAttributeU32x1(shaderContext, src, attributeInputIndex); + } + else if( attrib->format == FMT_8_8_8_8 && attrib->nfa == 0 && attrib->isSigned == 0 ) + { + // seen in Minecraft Wii U Edition + src->addFmt("attrDecoder.xyzw = floatBitsToUint(vec4(attrDataSem{}.wzyx)/255.0);" _CRLF, attributeInputIndex); + } + else if( attrib->format == FMT_8_8_8_8 && attrib->nfa == 0 && attrib->isSigned != 0 ) + { + // seen in Minecraft Wii U Edition + src->addFmt("attrDecoder.xyzw = attrDataSem{}.wzyx;" _CRLF, attributeInputIndex); + src->add("if( (attrDecoder.x&0x80) != 0 ) attrDecoder.x |= 0xFFFFFF00;" _CRLF); + src->add("if( (attrDecoder.y&0x80) != 0 ) attrDecoder.y |= 0xFFFFFF00;" _CRLF); + src->add("if( (attrDecoder.z&0x80) != 0 ) attrDecoder.z |= 0xFFFFFF00;" _CRLF); + src->add("if( (attrDecoder.w&0x80) != 0 ) attrDecoder.w |= 0xFFFFFF00;" _CRLF); + src->add("attrDecoder.x = floatBitsToUint(max(float(int(attrDecoder.x))/127.0,-1.0));" _CRLF); + src->add("attrDecoder.y = floatBitsToUint(max(float(int(attrDecoder.y))/127.0,-1.0));" _CRLF); + src->add("attrDecoder.z = floatBitsToUint(max(float(int(attrDecoder.z))/127.0,-1.0));" _CRLF); + src->add("attrDecoder.w = floatBitsToUint(max(float(int(attrDecoder.w))/127.0,-1.0));" _CRLF); + } + else if( attrib->format == FMT_8_8_8_8 && attrib->nfa == 1 && attrib->isSigned == 0 ) + { + // seen in Minecraft Wii U Edition + src->addFmt("attrDecoder.xyzw = attrDataSem{}.wzyx;" _CRLF, attributeInputIndex); + } + else if (attrib->format == FMT_8_8_8_8 && attrib->nfa == 2 && attrib->isSigned == 0) + { + // seen in Ben 10 Omniverse + src->addFmt("attrDecoder.xyzw = floatBitsToUint(vec4(attrDataSem{}.wzyx));" _CRLF, attributeInputIndex); + } + else + { + cemuLog_log(LogType::Force, "_emitAttributeDecodeGLSL(): Unsupported fmt {:02x} nfa {} signed {} endian {}\n", attrib->format, attrib->nfa, attrib->isSigned, attrib->endianSwap); + cemu_assert_unimplemented(); + } + } + else if( attrib->endianSwap == LatteConst::VertexFetchEndianMode::SWAP_NONE ) + { + if( attrib->format == FMT_32_32_32_32_FLOAT && attrib->nfa == 2 ) + { + _readLittleEndianAttributeU32x4(shaderContext, src, attributeInputIndex); + } + else if (attrib->format == FMT_32_32_32_FLOAT && attrib->nfa == 2) + { + _readLittleEndianAttributeU32x3(shaderContext, src, attributeInputIndex); + } + else if (attrib->format == FMT_32_32_FLOAT && attrib->nfa == 2) + { + // seen in Cities of Gold + _readLittleEndianAttributeU32x2(shaderContext, src, attributeInputIndex); + } + else if (attrib->format == FMT_32 && attrib->nfa == 1 && attrib->isSigned == 0) + { + // seen in Nano Assault Neo + _readLittleEndianAttributeU32x1(shaderContext, src, attributeInputIndex); + } + else if (attrib->format == FMT_2_10_10_10 && attrib->nfa == 0 && attrib->isSigned == 0) + { + // seen in Fast Racing Neo + _readLittleEndianAttributeU32x1(shaderContext, src, attributeInputIndex); + src->add("attrDecoder.xyzw = uvec4((attrDecoder.x>>0)&0x3FF,(attrDecoder.x>>10)&0x3FF,(attrDecoder.x>>20)&0x3FF,(attrDecoder.x>>30)&0x3);" _CRLF); + src->add("attrDecoder.x = floatBitsToUint(max(float(int(attrDecoder.x))/1023.0,-1.0));" _CRLF); + src->add("attrDecoder.y = floatBitsToUint(max(float(int(attrDecoder.y))/1023.0,-1.0));" _CRLF); + src->add("attrDecoder.z = floatBitsToUint(max(float(int(attrDecoder.z))/1023.0,-1.0));" _CRLF); + src->add("attrDecoder.w = floatBitsToUint(float(attrDecoder.w));" _CRLF); // todo - is this correct? + } + else if (attrib->format == FMT_16_16_16_16 && attrib->nfa == 0 && attrib->isSigned != 0) + { + // seen in CoD ghosts + _readLittleEndianAttributeU16x4(shaderContext, src, attributeInputIndex); + src->add("if( (attrDecoder.x&0x8000) != 0 ) attrDecoder.x |= 0xFFFF0000;" _CRLF); + src->add("if( (attrDecoder.y&0x8000) != 0 ) attrDecoder.y |= 0xFFFF0000;" _CRLF); + src->add("if( (attrDecoder.z&0x8000) != 0 ) attrDecoder.z |= 0xFFFF0000;" _CRLF); + src->add("if( (attrDecoder.w&0x8000) != 0 ) attrDecoder.w |= 0xFFFF0000;" _CRLF); + src->add("attrDecoder.x = floatBitsToUint(max(float(int(attrDecoder.x))/32767.0,-1.0));" _CRLF); + src->add("attrDecoder.y = floatBitsToUint(max(float(int(attrDecoder.y))/32767.0,-1.0));" _CRLF); + src->add("attrDecoder.z = floatBitsToUint(max(float(int(attrDecoder.z))/32767.0,-1.0));" _CRLF); + src->add("attrDecoder.w = floatBitsToUint(max(float(int(attrDecoder.w))/32767.0,-1.0));" _CRLF); + } + else if (attrib->format == FMT_16_16_16_16_FLOAT && attrib->nfa == 2) + { + // seen in Giana Sisters: Twisted Dreams + _readLittleEndianAttributeU16x4(shaderContext, src, attributeInputIndex); + src->add("attrDecoder.xyzw = floatBitsToInt(vec4(unpackHalf2x16(attrDecoder.x|(attrDecoder.y<<16)),unpackHalf2x16(attrDecoder.z|(attrDecoder.w<<16))));" _CRLF); + } + else if (attrib->format == FMT_16_16 && attrib->nfa == 0 && attrib->isSigned != 0) + { + // seen in Nano Assault Neo + _readLittleEndianAttributeU16x2(shaderContext, src, attributeInputIndex); + src->add("if( (attrDecoder.x&0x8000) != 0 ) attrDecoder.x |= 0xFFFF0000;" _CRLF); + src->add("if( (attrDecoder.y&0x8000) != 0 ) attrDecoder.y |= 0xFFFF0000;" _CRLF); + src->add("attrDecoder.x = floatBitsToUint(max(float(int(attrDecoder.x))/32767.0,-1.0));" _CRLF); + src->add("attrDecoder.y = floatBitsToUint(max(float(int(attrDecoder.y))/32767.0,-1.0));" _CRLF); + } + else if (attrib->format == FMT_16_16_FLOAT && attrib->nfa == 2) + { + // seen in Giana Sisters: Twisted Dreams + _readLittleEndianAttributeU16x2(shaderContext, src, attributeInputIndex); + src->add("attrDecoder.xy = floatBitsToUint(unpackHalf2x16(attrDecoder.x|(attrDecoder.y<<16)));" _CRLF); + src->add("attrDecoder.zw = uvec2(0);" _CRLF); + } + else if( attrib->format == FMT_8_8_8_8 && attrib->nfa == 0 && attrib->isSigned == 0 ) + { + src->addFmt("attrDecoder.xyzw = floatBitsToUint(vec4(attrDataSem{}.xyzw)/255.0);" _CRLF, attributeInputIndex); + } + else if( attrib->format == FMT_8_8_8_8 && attrib->nfa == 0 && attrib->isSigned != 0 ) + { + src->addFmt("attrDecoder.xyzw = attrDataSem{}.xyzw;" _CRLF, attributeInputIndex); + src->add("if( (attrDecoder.x&0x80) != 0 ) attrDecoder.x |= 0xFFFFFF00;" _CRLF); + src->add("if( (attrDecoder.y&0x80) != 0 ) attrDecoder.y |= 0xFFFFFF00;" _CRLF); + src->add("if( (attrDecoder.z&0x80) != 0 ) attrDecoder.z |= 0xFFFFFF00;" _CRLF); + src->add("if( (attrDecoder.w&0x80) != 0 ) attrDecoder.w |= 0xFFFFFF00;" _CRLF); + src->add("attrDecoder.x = floatBitsToUint(max(float(int(attrDecoder.x))/127.0,-1.0));" _CRLF); + src->add("attrDecoder.y = floatBitsToUint(max(float(int(attrDecoder.y))/127.0,-1.0));" _CRLF); + src->add("attrDecoder.z = floatBitsToUint(max(float(int(attrDecoder.z))/127.0,-1.0));" _CRLF); + src->add("attrDecoder.w = floatBitsToUint(max(float(int(attrDecoder.w))/127.0,-1.0));" _CRLF); + } + else if (attrib->format == FMT_8_8_8_8 && attrib->nfa == 1 && attrib->isSigned == 0) + { + src->addFmt("attrDecoder.xyzw = attrDataSem{}.xyzw;" _CRLF, attributeInputIndex); + } + else if (attrib->format == FMT_8_8_8_8 && attrib->nfa == 1 && attrib->isSigned != 0) + { + // seen in Sonic Lost World + src->addFmt("attrDecoder.xyzw = attrDataSem{}.xyzw;" _CRLF, attributeInputIndex); + src->add("if( (attrDecoder.x&0x80) != 0 ) attrDecoder.x |= 0xFFFFFF00;" _CRLF); + src->add("if( (attrDecoder.y&0x80) != 0 ) attrDecoder.y |= 0xFFFFFF00;" _CRLF); + src->add("if( (attrDecoder.z&0x80) != 0 ) attrDecoder.z |= 0xFFFFFF00;" _CRLF); + src->add("if( (attrDecoder.w&0x80) != 0 ) attrDecoder.w |= 0xFFFFFF00;" _CRLF); + } + else if( attrib->format == FMT_8_8_8_8 && attrib->nfa == 2 && attrib->isSigned == 0 ) + { + // seen in One Piece + src->addFmt("attrDecoder.xyzw = floatBitsToInt(vec4(attrDataSem{}.xyzw));" _CRLF, attributeInputIndex); + } + else if (attrib->format == FMT_8_8 && attrib->nfa == 0 && attrib->isSigned == 0) + { + if( (attrib->offset&3) == 2 && LatteGPUState.glVendor == GLVENDOR_AMD && g_renderer->GetType() == RendererAPI::OpenGL ) + { + // AMD workaround + src->addFmt("attrDecoder.xy = floatBitsToUint(vec2(attrDataSem{}.zw)/255.0);" _CRLF, attributeInputIndex); + src->add("attrDecoder.zw = uvec2(0);" _CRLF); + } + else + { + src->addFmt("attrDecoder.xy = floatBitsToUint(vec2(attrDataSem{}.xy)/255.0);" _CRLF, attributeInputIndex); + src->add("attrDecoder.zw = uvec2(0);" _CRLF); + } + } + else if (attrib->format == FMT_8_8 && attrib->nfa == 2 && attrib->isSigned == 0) + { + // seen in BotW + if ((attrib->offset & 3) == 2 && LatteGPUState.glVendor == GLVENDOR_AMD && g_renderer->GetType() == RendererAPI::OpenGL) + { + // AMD workaround + src->addFmt("attrDecoder.xy = floatBitsToUint(vec2(attrDataSem{}.zw));" _CRLF, attributeInputIndex); + src->add("attrDecoder.zw = uvec2(0);" _CRLF); + } + else + { + src->addFmt("attrDecoder.xy = floatBitsToUint(vec2(attrDataSem{}.xy));" _CRLF, attributeInputIndex); + src->add("attrDecoder.zw = uvec2(0);" _CRLF); + } + } + else if (attrib->format == FMT_8_8 && attrib->nfa == 0 && attrib->isSigned != 0) + { + if ((attrib->offset & 3) == 2 && LatteGPUState.glVendor == GLVENDOR_AMD && g_renderer->GetType() == RendererAPI::OpenGL) + { + // AMD workaround + src->addFmt("attrDecoder.xy = attrDataSem{}.zw;" _CRLF, attributeInputIndex); + src->add("if( (attrDecoder.x&0x80) != 0 ) attrDecoder.x |= 0xFFFFFF00;" _CRLF); + src->add("if( (attrDecoder.y&0x80) != 0 ) attrDecoder.y |= 0xFFFFFF00;" _CRLF); + src->add("attrDecoder.x = floatBitsToUint(max(float(int(attrDecoder.x))/127.0,-1.0));" _CRLF); + src->add("attrDecoder.y = floatBitsToUint(max(float(int(attrDecoder.y))/127.0,-1.0));" _CRLF); + src->add("attrDecoder.zw = uvec2(0);" _CRLF); + } + else + { + src->addFmt("attrDecoder.xy = attrDataSem{}.xy;" _CRLF, attributeInputIndex); + src->add("if( (attrDecoder.x&0x80) != 0 ) attrDecoder.x |= 0xFFFFFF00;" _CRLF); + src->add("if( (attrDecoder.y&0x80) != 0 ) attrDecoder.y |= 0xFFFFFF00;" _CRLF); + src->add("attrDecoder.x = floatBitsToUint(max(float(int(attrDecoder.x))/127.0,-1.0));" _CRLF); + src->add("attrDecoder.y = floatBitsToUint(max(float(int(attrDecoder.y))/127.0,-1.0));" _CRLF); + src->add("attrDecoder.zw = uvec2(0);" _CRLF); + } + } + else if (attrib->format == FMT_8_8 && attrib->nfa == 1 && attrib->isSigned == 0) + { + if ((attrib->offset & 3) == 2 && LatteGPUState.glVendor == GLVENDOR_AMD && g_renderer->GetType() == RendererAPI::OpenGL) + { + // AMD workaround + src->addFmt("attrDecoder.xyzw = uvec4(attrDataSem{}.zw,0,0);" _CRLF, attributeInputIndex); + } + else + { + src->addFmt("attrDecoder.xyzw = uvec4(attrDataSem{}.xy,0,0);" _CRLF, attributeInputIndex); + } + } + else if( attrib->format == FMT_8 && attrib->nfa == 0 && attrib->isSigned == 0 ) + { + // seen in Pikmin 3 + src->addFmt("attrDecoder.x = floatBitsToUint(float(attrDataSem{}.x)/255.0);" _CRLF, attributeInputIndex); + src->add("attrDecoder.yzw = uvec3(0);" _CRLF); + } + else if( attrib->format == FMT_8 && attrib->nfa == 1 && attrib->isSigned == 0 ) + { + src->addFmt("attrDecoder.xyzw = uvec4(attrDataSem{}.x,0,0,0);" _CRLF, attributeInputIndex); + } + else + { + cemuLog_log(LogType::Force, "_emitAttributeDecodeGLSL(): Unsupported fmt {:02x} nfa {} signed {} endian {}\n", attrib->format, attrib->nfa, attrib->isSigned, attrib->endianSwap); + cemu_assert_debug(false); + } + } + else if( attrib->endianSwap == LatteConst::VertexFetchEndianMode::SWAP_U16 ) + { + if( attrib->format == FMT_16_16_16_16_FLOAT && attrib->nfa == 2 ) + { + _readBigEndianAttributeU16x4(shaderContext, src, attributeInputIndex); + src->add("attrDecoder.xyzw = floatBitsToInt(vec4(unpackHalf2x16(attrDecoder.x|(attrDecoder.y<<16)),unpackHalf2x16(attrDecoder.z|(attrDecoder.w<<16))));" _CRLF); + } + else if (attrib->format == FMT_16_16_16_16 && attrib->nfa == 0 && attrib->isSigned != 0) + { + _readBigEndianAttributeU16x4(shaderContext, src, attributeInputIndex); + src->add("if( (attrDecoder.x&0x8000) != 0 ) attrDecoder.x |= 0xFFFF0000;" _CRLF); + src->add("if( (attrDecoder.y&0x8000) != 0 ) attrDecoder.y |= 0xFFFF0000;" _CRLF); + src->add("if( (attrDecoder.z&0x8000) != 0 ) attrDecoder.z |= 0xFFFF0000;" _CRLF); + src->add("if( (attrDecoder.w&0x8000) != 0 ) attrDecoder.w |= 0xFFFF0000;" _CRLF); + src->add("attrDecoder.x = floatBitsToUint(max(float(int(attrDecoder.x))/32767.0,-1.0));" _CRLF); + src->add("attrDecoder.y = floatBitsToUint(max(float(int(attrDecoder.y))/32767.0,-1.0));" _CRLF); + src->add("attrDecoder.z = floatBitsToUint(max(float(int(attrDecoder.z))/32767.0,-1.0));" _CRLF); + src->add("attrDecoder.w = floatBitsToUint(max(float(int(attrDecoder.w))/32767.0,-1.0));" _CRLF); + } + else if (attrib->format == FMT_16_16_16_16 && attrib->nfa == 0 && attrib->isSigned == 0) + { + // seen in BotW + _readBigEndianAttributeU16x4(shaderContext, src, attributeInputIndex); + src->add("attrDecoder.x = floatBitsToUint(float(int(attrDecoder.x))/65535.0);" _CRLF); + src->add("attrDecoder.y = floatBitsToUint(float(int(attrDecoder.y))/65535.0);" _CRLF); + src->add("attrDecoder.z = floatBitsToUint(float(int(attrDecoder.z))/65535.0);" _CRLF); + src->add("attrDecoder.w = floatBitsToUint(float(int(attrDecoder.w))/65535.0);" _CRLF); + } + else if( attrib->format == FMT_16_16_16_16 && attrib->nfa == 2 && attrib->isSigned != 0 ) + { + // seen in Minecraft Wii U Edition + _readBigEndianAttributeU16x4(shaderContext, src, attributeInputIndex); + src->add("if( (attrDecoder.x&0x8000) != 0 ) attrDecoder.x |= 0xFFFF0000;" _CRLF); + src->add("if( (attrDecoder.y&0x8000) != 0 ) attrDecoder.y |= 0xFFFF0000;" _CRLF); + src->add("if( (attrDecoder.z&0x8000) != 0 ) attrDecoder.z |= 0xFFFF0000;" _CRLF); + src->add("if( (attrDecoder.w&0x8000) != 0 ) attrDecoder.w |= 0xFFFF0000;" _CRLF); + src->add("attrDecoder.x = floatBitsToUint(float(int(attrDecoder.x)));" _CRLF); + src->add("attrDecoder.y = floatBitsToUint(float(int(attrDecoder.y)));" _CRLF); + src->add("attrDecoder.z = floatBitsToUint(float(int(attrDecoder.z)));" _CRLF); + src->add("attrDecoder.w = floatBitsToUint(float(int(attrDecoder.w)));" _CRLF); + } + else if( attrib->format == FMT_16_16_16_16 && attrib->nfa == 1 && attrib->isSigned != 0 ) + { + // seen in Minecraft Wii U Edition + _readBigEndianAttributeU16x4(shaderContext, src, attributeInputIndex); + src->add("if( (attrDecoder.x&0x8000) != 0 ) attrDecoder.x |= 0xFFFF0000;" _CRLF); + src->add("if( (attrDecoder.y&0x8000) != 0 ) attrDecoder.y |= 0xFFFF0000;" _CRLF); + src->add("if( (attrDecoder.z&0x8000) != 0 ) attrDecoder.z |= 0xFFFF0000;" _CRLF); + src->add("if( (attrDecoder.w&0x8000) != 0 ) attrDecoder.w |= 0xFFFF0000;" _CRLF); + } + else if( attrib->format == FMT_16_16_16_16 && attrib->nfa == 1 && attrib->isSigned == 0 ) + { + _readBigEndianAttributeU16x4(shaderContext, src, attributeInputIndex); + } + else if( attrib->format == FMT_16_16_FLOAT && attrib->nfa == 2 ) + { + _readBigEndianAttributeU16x2(shaderContext, src, attributeInputIndex); + src->add("attrDecoder.xy = floatBitsToUint(unpackHalf2x16(attrDecoder.x|(attrDecoder.y<<16)));" _CRLF); + src->add("attrDecoder.zw = uvec2(0);" _CRLF); + } + else if( attrib->format == FMT_16_16 && attrib->nfa == 0 && attrib->isSigned == 0 ) + { + _readBigEndianAttributeU16x2(shaderContext, src, attributeInputIndex); + src->add("attrDecoder.xy = floatBitsToUint(vec2(float(attrDecoder.x), float(attrDecoder.y))/65535.0);" _CRLF); + src->add("attrDecoder.zw = uvec2(0);" _CRLF); + } + else if( attrib->format == FMT_16_16 && attrib->nfa == 0 && attrib->isSigned != 0 ) + { + _readBigEndianAttributeU16x2(shaderContext, src, attributeInputIndex); + src->add("if( (attrDecoder.x&0x8000) != 0 ) attrDecoder.x |= 0xFFFF0000;" _CRLF); + src->add("if( (attrDecoder.y&0x8000) != 0 ) attrDecoder.y |= 0xFFFF0000;" _CRLF); + src->add("attrDecoder.x = floatBitsToUint(max(float(int(attrDecoder.x))/32767.0,-1.0));" _CRLF); + src->add("attrDecoder.y = floatBitsToUint(max(float(int(attrDecoder.y))/32767.0,-1.0));" _CRLF); + src->add("attrDecoder.zw = uvec2(0);" _CRLF); + } + else if( attrib->format == FMT_16_16 && attrib->nfa == 1 && attrib->isSigned == 0 ) + { + _readBigEndianAttributeU16x2(shaderContext, src, attributeInputIndex); + } + else if( attrib->format == FMT_16_16 && attrib->nfa == 1 && attrib->isSigned != 0 ) + { + _readBigEndianAttributeU16x2(shaderContext, src, attributeInputIndex); + src->add("if( (attrDecoder.x&0x8000) != 0 ) attrDecoder.x |= 0xFFFF0000;" _CRLF); + src->add("if( (attrDecoder.y&0x8000) != 0 ) attrDecoder.y |= 0xFFFF0000;" _CRLF); + src->add("attrDecoder.zw = uvec2(0);" _CRLF); + } + else if( attrib->format == FMT_16_16 && attrib->nfa == 2 && attrib->isSigned == 0 ) + { + _readBigEndianAttributeU16x2(shaderContext, src, attributeInputIndex); + src->add("attrDecoder.xy = floatBitsToUint(vec2(float(attrDecoder.x), float(attrDecoder.y)));" _CRLF); + src->add("attrDecoder.zw = uvec2(0);" _CRLF); + } + else if( attrib->format == FMT_16_16 && attrib->nfa == 2 && attrib->isSigned != 0 ) + { + _readBigEndianAttributeU16x2(shaderContext, src, attributeInputIndex); + src->add("if( (attrDecoder.x&0x8000) != 0 ) attrDecoder.x |= 0xFFFF0000;" _CRLF); + src->add("if( (attrDecoder.y&0x8000) != 0 ) attrDecoder.y |= 0xFFFF0000;" _CRLF); + src->add("attrDecoder.xy = floatBitsToUint(vec2(float(int(attrDecoder.x)), float(int(attrDecoder.y))));" _CRLF); + src->add("attrDecoder.zw = uvec2(0);" _CRLF); + } + else if (attrib->format == FMT_16 && attrib->nfa == 1 && attrib->isSigned == 0) + { + _readBigEndianAttributeU16x1(shaderContext, src, attributeInputIndex); + } + else if (attrib->format == FMT_16 && attrib->nfa == 0 && attrib->isSigned == 0) + { + // seen in CoD ghosts + _readBigEndianAttributeU16x1(shaderContext, src, attributeInputIndex); + src->add("attrDecoder.x = floatBitsToUint(float(int(attrDecoder.x))/65535.0);" _CRLF); + } + else + { + forceLogDebug_printf("_emitAttributeDecodeGLSL(): Unsupported fmt {:02x} nfa {} signed {} endian {}\n", attrib->format, attrib->nfa, attrib->isSigned, attrib->endianSwap); + } + } + else + { + cemu_assert_debug(false); + } +} diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLHeader.hpp b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLHeader.hpp new file mode 100644 index 00000000..c680ee43 --- /dev/null +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLHeader.hpp @@ -0,0 +1,589 @@ +#pragma once + +namespace LatteDecompiler +{ + void _emitUniformVariables(LatteDecompilerShaderContext* decompilerContext, RendererAPI rendererType, LatteDecompilerOutputUniformOffsets& uniformOffsets) + { + LatteDecompilerShaderResourceMapping& resourceMapping = (rendererType == RendererAPI::Vulkan) ? decompilerContext->output->resourceMappingVK : decompilerContext->output->resourceMappingGL; + + if (rendererType == RendererAPI::Vulkan) + { + // for Vulkan uniform vars are in a uniform buffer + if (decompilerContext->hasUniformVarBlock) + { + cemu_assert_debug(resourceMapping.uniformVarsBufferBindingPoint >= 0); + decompilerContext->shaderSource->addFmt("layout(set = {}, binding = {}) uniform ufBlock" _CRLF "{{" _CRLF, (sint32)resourceMapping.setIndex, (sint32)resourceMapping.uniformVarsBufferBindingPoint); + } + } + + sint32 uniformCurrentOffset = 0; + auto shader = decompilerContext->shader; + auto shaderType = decompilerContext->shader->shaderType; + auto shaderSrc = decompilerContext->shaderSource; + if (decompilerContext->shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_REMAPPED) + { + // uniform registers or buffers are accessed statically with predictable offsets + // this allows us to remap the used entries into a more compact array + if (shaderType == LatteConst::ShaderType::Vertex) + shaderSrc->addFmt("uniform ivec4 uf_remappedVS[{}];" _CRLF, (sint32)shader->list_remappedUniformEntries.size()); + else if (shaderType == LatteConst::ShaderType::Pixel) + shaderSrc->addFmt("uniform ivec4 uf_remappedPS[{}];" _CRLF, (sint32)shader->list_remappedUniformEntries.size()); + else if (shaderType == LatteConst::ShaderType::Geometry) + shaderSrc->addFmt("uniform ivec4 uf_remappedGS[{}];" _CRLF, (sint32)shader->list_remappedUniformEntries.size()); + else + debugBreakpoint(); + uniformOffsets.offset_remapped = uniformCurrentOffset; + uniformCurrentOffset += 16 * shader->list_remappedUniformEntries.size(); + } + else if (decompilerContext->shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CFILE) + { + // here we try to predict the accessed range so we dont have to upload the whole register file + // we assume that if there is a fixed-index access on an index higher than a relative access, it bounds the prior relative access + sint16 highestAccessIndex = -1; + bool highestAccessIndexIsRel = false; + for(auto& accessItr : decompilerContext->analyzer.uniformRegisterAccessIndices) + { + if (accessItr.index > highestAccessIndex || (accessItr.index == highestAccessIndex && accessItr.isRelative && !highestAccessIndexIsRel)) + { + highestAccessIndex = accessItr.index; + highestAccessIndexIsRel = accessItr.isRelative; + } + } + if (highestAccessIndex < 0) + highestAccessIndex = 0; + + uint32 cfileSize; + if (highestAccessIndexIsRel) + cfileSize = 256; + else + cfileSize = highestAccessIndex + 1; + + // full uniform register file has to be present + if (shaderType == LatteConst::ShaderType::Vertex) + shaderSrc->addFmt("uniform ivec4 uf_uniformRegisterVS[{}];" _CRLF, cfileSize); + else if (shaderType == LatteConst::ShaderType::Pixel) + shaderSrc->addFmt("uniform ivec4 uf_uniformRegisterPS[{}];" _CRLF, cfileSize); + else if (shaderType == LatteConst::ShaderType::Geometry) + shaderSrc->addFmt("uniform ivec4 uf_uniformRegisterGS[{}];" _CRLF, cfileSize); + else + debugBreakpoint(); + uniformOffsets.offset_uniformRegister = uniformCurrentOffset; + uniformOffsets.count_uniformRegister = cfileSize; + uniformCurrentOffset += 16 * cfileSize; + } + // special uniforms + bool hasAnyViewportScaleDisabled = + !decompilerContext->contextRegistersNew->PA_CL_VTE_CNTL.get_VPORT_X_SCALE_ENA() || + !decompilerContext->contextRegistersNew->PA_CL_VTE_CNTL.get_VPORT_Y_SCALE_ENA() || + !decompilerContext->contextRegistersNew->PA_CL_VTE_CNTL.get_VPORT_Z_SCALE_ENA(); + + if (decompilerContext->shaderType == LatteConst::ShaderType::Vertex && hasAnyViewportScaleDisabled) + { + // aka GX2 special state 0 + uniformCurrentOffset = (uniformCurrentOffset + 7)&~7; + shaderSrc->add("uniform vec2 uf_windowSpaceToClipSpaceTransform;" _CRLF); + uniformOffsets.offset_windowSpaceToClipSpaceTransform = uniformCurrentOffset; + uniformCurrentOffset += 8; + } + bool alphaTestEnable = decompilerContext->contextRegistersNew->SX_ALPHA_TEST_CONTROL.get_ALPHA_TEST_ENABLE(); + if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel && alphaTestEnable) + { + uniformCurrentOffset = (uniformCurrentOffset + 3)&~3; + shaderSrc->add("uniform float uf_alphaTestRef;" _CRLF); + uniformOffsets.offset_alphaTestRef = uniformCurrentOffset; + uniformCurrentOffset += 4; + } + if (decompilerContext->analyzer.outputPointSize && decompilerContext->analyzer.writesPointSize == false) + { + if ((decompilerContext->shaderType == LatteConst::ShaderType::Vertex && !decompilerContext->usesGeometryShader) || + decompilerContext->shaderType == LatteConst::ShaderType::Geometry) + { + uniformCurrentOffset = (uniformCurrentOffset + 3)&~3; + shaderSrc->add("uniform float uf_pointSize;" _CRLF); + uniformOffsets.offset_pointSize = uniformCurrentOffset; + uniformCurrentOffset += 4; + } + } + // define uf_fragCoordScale which holds the xy scale for render target resolution vs effective resolution + if (shader->shaderType == LatteConst::ShaderType::Pixel) + { + if (rendererType == RendererAPI::OpenGL) + { + uniformCurrentOffset = (uniformCurrentOffset + 7)&~7; + shaderSrc->add("uniform vec2 uf_fragCoordScale;" _CRLF); + uniformOffsets.offset_fragCoordScale = uniformCurrentOffset; + uniformCurrentOffset += 8; + } + else + { + // in Vulkan uf_fragCoordScale stores the origin in zw + uniformCurrentOffset = (uniformCurrentOffset + 15)&~15; + shaderSrc->add("uniform vec4 uf_fragCoordScale;" _CRLF); + uniformOffsets.offset_fragCoordScale = uniformCurrentOffset; + uniformCurrentOffset += 16; + } + } + // provide scale factor for every texture that is accessed via texel coordinates (texelFetch) + for (sint32 t = 0; t < LATTE_NUM_MAX_TEX_UNITS; t++) + { + if (decompilerContext->analyzer.texUnitUsesTexelCoordinates.test(t) == false) + continue; + uniformCurrentOffset = (uniformCurrentOffset + 7) & ~7; + shaderSrc->addFmt("uniform vec2 uf_tex{}Scale;" _CRLF, t); + uniformOffsets.offset_texScale[t] = uniformCurrentOffset; + uniformCurrentOffset += 8; + } + // define uf_verticesPerInstance + uf_streamoutBufferBaseX + if (decompilerContext->analyzer.useSSBOForStreamout && + (shader->shaderType == LatteConst::ShaderType::Vertex && decompilerContext->usesGeometryShader == false) || + (shader->shaderType == LatteConst::ShaderType::Geometry) ) + { + shaderSrc->add("uniform int uf_verticesPerInstance;" _CRLF); + uniformOffsets.offset_verticesPerInstance = uniformCurrentOffset; + uniformCurrentOffset += 4; + for (uint32 i = 0; i < LATTE_NUM_STREAMOUT_BUFFER; i++) + { + if (decompilerContext->output->streamoutBufferWriteMask[i]) + { + shaderSrc->addFmt("uniform int uf_streamoutBufferBase{};" _CRLF, i); + uniformOffsets.offset_streamoutBufferBase[i] = uniformCurrentOffset; + uniformCurrentOffset += 4; + } + } + } + + uniformOffsets.offset_endOfBlock = uniformCurrentOffset; + if (rendererType == RendererAPI::Vulkan) + { + if (decompilerContext->hasUniformVarBlock) + shaderSrc->add("};" _CRLF); // end of push-constant block + } + } + + void _emitUniformBuffers(LatteDecompilerShaderContext* decompilerContext) + { + auto shaderSrc = decompilerContext->shaderSource; + // uniform buffer definition + if (decompilerContext->shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK) + { + for (uint32 i = 0; i < LATTE_NUM_MAX_UNIFORM_BUFFERS; i++) + { + if ((decompilerContext->analyzer.uniformBufferAccessMask&(1 << i)) == 0) + continue; + + cemu_assert_debug(decompilerContext->output->resourceMappingGL.uniformBuffersBindingPoint[i] >= 0); + cemu_assert_debug(decompilerContext->output->resourceMappingVK.uniformBuffersBindingPoint[i] >= 0); + + shaderSrc->addFmt("UNIFORM_BUFFER_LAYOUT({}, {}, {}) ", (sint32)decompilerContext->output->resourceMappingGL.uniformBuffersBindingPoint[i], (sint32)decompilerContext->output->resourceMappingVK.setIndex, (sint32)decompilerContext->output->resourceMappingVK.uniformBuffersBindingPoint[i]); + + shaderSrc->addFmt("uniform {}{}" _CRLF, _getShaderUniformBlockInterfaceName(decompilerContext->shaderType), i); + shaderSrc->add("{" _CRLF); + shaderSrc->addFmt("vec4 {}{}[{}];" _CRLF, _getShaderUniformBlockVariableName(decompilerContext->shaderType), i, LATTE_GLSL_DYNAMIC_UNIFORM_BLOCK_SIZE); + shaderSrc->add("};" _CRLF _CRLF); + shaderSrc->add(_CRLF); + } + } + else if (decompilerContext->shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_REMAPPED) + { + // already generated in _emitUniformVariables + } + else if (decompilerContext->shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CFILE) + { + // already generated in _emitUniformVariables + } + else if (decompilerContext->shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_NONE) + { + // no uniforms used + } + else + { + cemu_assert_debug(false); + } + } + + void _emitTextureDefinitions(LatteDecompilerShaderContext* shaderContext) + { + auto src = shaderContext->shaderSource; + // texture sampler definition + for (sint32 i = 0; i < LATTE_NUM_MAX_TEX_UNITS; i++) + { + if (!shaderContext->output->textureUnitMask[i]) + continue; + + src->addFmt("TEXTURE_LAYOUT({}, {}, {}) ", (sint32)shaderContext->output->resourceMappingGL.textureUnitToBindingPoint[i], (sint32)shaderContext->output->resourceMappingVK.setIndex, (sint32)shaderContext->output->resourceMappingVK.textureUnitToBindingPoint[i]); + + src->add("uniform "); + if (shaderContext->shader->textureIsIntegerFormat[i]) + { + // integer samplers + if (shaderContext->shader->textureUnitDim[i] == Latte::E_DIM::DIM_1D) + src->add("usampler1D"); + else if (shaderContext->shader->textureUnitDim[i] == Latte::E_DIM::DIM_2D || shaderContext->shader->textureUnitDim[i] == Latte::E_DIM::DIM_2D_MSAA) + src->add("usampler2D"); + else + cemu_assert_unimplemented(); + } + else if (shaderContext->shader->textureUnitDim[i] == Latte::E_DIM::DIM_2D || shaderContext->shader->textureUnitDim[i] == Latte::E_DIM::DIM_2D_MSAA) + src->add("sampler2D"); + else if (shaderContext->shader->textureUnitDim[i] == Latte::E_DIM::DIM_1D) + src->add("sampler1D"); + else if (shaderContext->shader->textureUnitDim[i] == Latte::E_DIM::DIM_2D_ARRAY) + src->add("sampler2DArray"); + else if (shaderContext->shader->textureUnitDim[i] == Latte::E_DIM::DIM_CUBEMAP) + src->add("samplerCubeArray"); + else if (shaderContext->shader->textureUnitDim[i] == Latte::E_DIM::DIM_3D) + src->add("sampler3D"); + else if (shaderContext->shader->textureUnitDim[i] == Latte::E_DIM::DIM_1D) + { + cemu_assert_unimplemented(); + src->add("sampler2D"); + } + else + { + cemu_assert_unimplemented(); + } + if (shaderContext->shader->textureUsesDepthCompare[i]) + src->add("Shadow"); // shadow sampler + + src->addFmt(" {}{};", _getTextureUnitVariablePrefixName(shaderContext->shaderType), i); + + src->add(_CRLF); + } + } + + void _emitAttributes(LatteDecompilerShaderContext* decompilerContext) + { + auto shaderSrc = decompilerContext->shaderSource; + if (decompilerContext->shader->shaderType == LatteConst::ShaderType::Vertex) + { + // attribute inputs + for (uint32 i = 0; i < LATTE_NUM_MAX_ATTRIBUTE_LOCATIONS; i++) + { + if (decompilerContext->analyzer.inputAttributSemanticMask[i]) + { + cemu_assert_debug(decompilerContext->output->resourceMappingGL.attributeMapping[i] >= 0); + cemu_assert_debug(decompilerContext->output->resourceMappingVK.attributeMapping[i] >= 0); + cemu_assert_debug(decompilerContext->output->resourceMappingGL.attributeMapping[i] == decompilerContext->output->resourceMappingVK.attributeMapping[i]); + + shaderSrc->addFmt("ATTR_LAYOUT({}, {}) in uvec4 attrDataSem{};" _CRLF, (sint32)decompilerContext->output->resourceMappingVK.setIndex, (sint32)decompilerContext->output->resourceMappingVK.attributeMapping[i], i); + } + } + } + } + + void _emitHeaderMacros(LatteDecompilerShaderContext* decompilerContext) + { + auto src = decompilerContext->shaderSource; + // OpenGL/Vulkan ifdefs + src->add("#ifdef VULKAN" _CRLF); + // Vulkan defines + src->add("#define ATTR_LAYOUT(__vkSet, __location) layout(set = __vkSet, location = __location)" _CRLF); + src->add("#define UNIFORM_BUFFER_LAYOUT(__glLocation, __vkSet, __vkLocation) layout(set = __vkSet, binding = __vkLocation, std140)" _CRLF); + src->add("#define TEXTURE_LAYOUT(__glLocation, __vkSet, __vkLocation) layout(set = __vkSet, binding = __vkLocation)" _CRLF); + if (decompilerContext->shaderType == LatteConst::ShaderType::Vertex || decompilerContext->shaderType == LatteConst::ShaderType::Geometry) + { + src->add("#define gl_VertexID gl_VertexIndex" _CRLF); + src->add("#define gl_InstanceID gl_InstanceIndex" _CRLF); + if (decompilerContext->analyzer.hasStreamoutWrite) + src->add("#define XFB_BLOCK_LAYOUT(__bufferIndex, __stride, __location) layout(location = __location, xfb_buffer = __bufferIndex, xfb_stride = __stride, xfb_offset = 0)" _CRLF); + + if (decompilerContext->contextRegistersNew->PA_CL_CLIP_CNTL.get_DX_CLIP_SPACE_DEF()) + { + src->add("#define SET_POSITION(_v) gl_Position = _v" _CRLF); + } + else + { + src->add("#define SET_POSITION(_v) gl_Position = _v; gl_Position.z = (gl_Position.z + gl_Position.w) / 2.0" _CRLF); + } + + if (decompilerContext->shaderType == LatteConst::ShaderType::Vertex || decompilerContext->shaderType == LatteConst::ShaderType::Geometry) + { + if (decompilerContext->usesGeometryShader) + src->add("#define V2G_LAYOUT layout(location = 0)" _CRLF); + } + } + else if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel) + { + //fCStr_shaderSource->append("#define GET_FRAGCOORD() vec4(gl_FragCoord.xy*uf_fragCoordScale.xy+uf_fragCoordScale.zw,gl_FragCoord.zw)" STR_LINEBREAK); + src->add("#define GET_FRAGCOORD() vec4(gl_FragCoord.xy*uf_fragCoordScale.xy,gl_FragCoord.zw)" _CRLF); + } + src->add("#else" _CRLF); + // OpenGL defines + src->add("#define ATTR_LAYOUT(__vkSet, __location) layout(location = __location)" _CRLF); + src->add("#define UNIFORM_BUFFER_LAYOUT(__glLocation, __vkSet, __vkLocation) layout(binding = __glLocation, std140) " _CRLF); + src->add("#define TEXTURE_LAYOUT(__glLocation, __vkSet, __vkLocation) layout(binding = __glLocation)" _CRLF); + if (decompilerContext->shaderType == LatteConst::ShaderType::Vertex || decompilerContext->shaderType == LatteConst::ShaderType::Geometry) + { + if (decompilerContext->analyzer.hasStreamoutWrite) + src->add("#define XFB_BLOCK_LAYOUT(__bufferIndex, __stride, __location) layout(xfb_buffer = __bufferIndex, xfb_stride = __stride)" _CRLF); + + src->add("#define SET_POSITION(_v) gl_Position = _v\r\n"); + if (decompilerContext->usesGeometryShader) + src->add("#define V2G_LAYOUT" _CRLF); + } + else if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel) + { + src->add("#define GET_FRAGCOORD() vec4(gl_FragCoord.xy*uf_fragCoordScale,gl_FragCoord.zw)" _CRLF); + } + src->add("#endif" _CRLF); + + // geometry shader input/output primitive info + if (decompilerContext->shaderType == LatteConst::ShaderType::Geometry) + { + auto drawPrimitiveMode = decompilerContext->contextRegistersNew->VGT_PRIMITIVE_TYPE.get_PRIMITIVE_MODE(); + const char* gsInputPrimitive = ""; + if (drawPrimitiveMode == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::POINTS) + gsInputPrimitive = "points"; + else if (drawPrimitiveMode == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::TRIANGLES) + gsInputPrimitive = "triangles"; + else if (drawPrimitiveMode == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::LINE_STRIP) + gsInputPrimitive = "lines_adjacency"; + else + { + debug_printf("drawPrimitiveMode %d\n", drawPrimitiveMode); + cemu_assert_debug(false); + } + // note: The input primitive type is not stored with the geometry shader object (and registers). Therefore we have to rely on whatever primitive mode was set for the draw call. + src->addFmt("layout({}) in;" _CRLF, gsInputPrimitive); + + uint32 gsOutPrimType = decompilerContext->contextRegisters[mmVGT_GS_OUT_PRIM_TYPE]; + uint32 bytesPerVertex = decompilerContext->contextRegisters[mmSQ_GS_VERT_ITEMSIZE] * 4; + + uint32 maxVerticesInGS = ((decompilerContext->contextRegisters[mmSQ_GSVS_RING_ITEMSIZE] & 0x7FFF) * 4) / bytesPerVertex; + + if (!decompilerContext->analyzer.hasLoops && decompilerContext->analyzer.numEmitVertex < maxVerticesInGS) + maxVerticesInGS = decompilerContext->analyzer.numEmitVertex; + + src->add("layout ("); + if (gsOutPrimType == 0) + src->add("points"); + else if (gsOutPrimType == 1) + src->add("line_strip"); + else if (gsOutPrimType == 2) + src->add("triangle_strip"); + else + { + cemu_assert_debug(false); + } + src->addFmt(", max_vertices={}) out;" _CRLF, (sint32)maxVerticesInGS); + } + } + + void _emitVSExports(LatteDecompilerShaderContext* shaderContext) + { + auto* src = shaderContext->shaderSource; + LatteShaderPSInputTable* psInputTable = LatteSHRC_GetPSInputTable(); + auto parameterMask = shaderContext->shader->outputParameterMask; + for (uint32 i = 0; i < 32; i++) + { + if ((parameterMask&(1 << i)) == 0) + continue; + uint32 vsSemanticId = _getVertexShaderOutParamSemanticId(shaderContext->contextRegisters, i); + if (vsSemanticId > LATTE_ANALYZER_IMPORT_INDEX_PARAM_MAX) + continue; + // get import based on semanticId + sint32 psInputIndex = -1; + for (sint32 f = 0; f < psInputTable->count; f++) + { + if (psInputTable->import[f].semanticId == vsSemanticId) + { + psInputIndex = f; + break; + } + } + if (psInputIndex == -1) + continue; // no ps input + + src->addFmt("layout(location = {}) ", psInputIndex); + if (psInputTable->import[psInputIndex].isFlat) + src->add("flat "); + if (psInputTable->import[psInputIndex].isNoPerspective) + src->add("noperspective "); + src->add("out"); + src->addFmt(" vec4 passParameterSem{};" _CRLF, psInputTable->import[psInputIndex].semanticId); + } + } + + void _emitPSImports(LatteDecompilerShaderContext* shaderContext) + { + auto* src = shaderContext->shaderSource; + LatteShaderPSInputTable* psInputTable = LatteSHRC_GetPSInputTable(); + for (sint32 i = 0; i < psInputTable->count; i++) + { + if (psInputTable->import[i].semanticId > LATTE_ANALYZER_IMPORT_INDEX_PARAM_MAX) + continue; + src->addFmt("layout(location = {}) ", i); + if (psInputTable->import[i].isFlat) + src->add("flat "); + if (psInputTable->import[i].isNoPerspective) + src->add("noperspective "); + src->add("in"); + src->addFmt(" vec4 passParameterSem{};" _CRLF, psInputTable->import[i].semanticId); + } + } + + void _emitMisc(LatteDecompilerShaderContext* decompilerContext) + { + auto src = decompilerContext->shaderSource; + // per-vertex output (VS or GS) + if ((decompilerContext->shaderType == LatteConst::ShaderType::Vertex && decompilerContext->usesGeometryShader == false) || + (decompilerContext->shaderType == LatteConst::ShaderType::Geometry)) + { + src->add("out gl_PerVertex" _CRLF); + src->add("{" _CRLF); + src->add(" vec4 gl_Position;" _CRLF); + if (decompilerContext->analyzer.outputPointSize) + src->add(" float gl_PointSize;" _CRLF); + src->add("};" _CRLF); + } + // varyings (variables passed from vertex to pixel shader, only if geometry stage is disabled + if (decompilerContext->usesGeometryShader == false) + { + if (decompilerContext->shaderType == LatteConst::ShaderType::Vertex) + { + _emitVSExports(decompilerContext); + } + else if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel) + { + _emitPSImports(decompilerContext); + } + } + else + { + if (decompilerContext->shaderType == LatteConst::ShaderType::Vertex || decompilerContext->shaderType == LatteConst::ShaderType::Geometry) + { + // parameters shared between vertex shader and geometry shader + src->add("V2G_LAYOUT "); + + if (decompilerContext->shaderType == LatteConst::ShaderType::Vertex) + src->add("out Vertex" _CRLF); + else if (decompilerContext->shaderType == LatteConst::ShaderType::Geometry) + src->add("in Vertex" _CRLF); + src->add("{" _CRLF); + uint32 ringParameterCountVS2GS = 0; + if (decompilerContext->shaderType == LatteConst::ShaderType::Vertex) + { + ringParameterCountVS2GS = decompilerContext->shader->ringParameterCount; + } + else + { + ringParameterCountVS2GS = decompilerContext->shader->ringParameterCountFromPrevStage; + } + for (uint32 f = 0; f < ringParameterCountVS2GS; f++) + src->addFmt(" ivec4 passV2GParameter{};" _CRLF, f); + if (decompilerContext->shaderType == LatteConst::ShaderType::Vertex) + src->add("}v2g;" _CRLF); + else if (decompilerContext->shaderType == LatteConst::ShaderType::Geometry) + src->add("}v2g[];" _CRLF); + } + if (decompilerContext->shaderType == LatteConst::ShaderType::Geometry) + { + // parameters shared between geometry and pixel shader + uint32 ringItemSize = decompilerContext->contextRegisters[mmSQ_GSVS_RING_ITEMSIZE] & 0x7FFF; + if ((ringItemSize & 0xF) != 0) + debugBreakpoint(); + if (((decompilerContext->contextRegisters[mmSQ_GSVS_RING_ITEMSIZE] & 0x7FFF) & 0xF) != 0) + debugBreakpoint(); + + for (sint32 p = 0; p < decompilerContext->parsedGSCopyShader->numParam; p++) + { + if (decompilerContext->parsedGSCopyShader->paramMapping[p].exportType != 2) + continue; + src->addFmt("layout(location = {}) out vec4 passG2PParameter{};" _CRLF, decompilerContext->parsedGSCopyShader->paramMapping[p].exportParam & 0x7F, (sint32)decompilerContext->parsedGSCopyShader->paramMapping[p].exportParam); + } + } + else if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel) + { + // pixel shader with geometry shader + LatteShaderPSInputTable* psInputTable = LatteSHRC_GetPSInputTable(); + for (sint32 i = 0; i < psInputTable->count; i++) + { + if (psInputTable->import[i].semanticId > LATTE_ANALYZER_IMPORT_INDEX_PARAM_MAX) + continue; + uint32 location = psInputTable->import[i].semanticId & 0x7F; // todo - the range above 128 has special meaning? + + src->addFmt("layout(location = {}) ", location); + if (psInputTable->import[i].isFlat) + src->add("flat "); + if (psInputTable->import[i].isNoPerspective) + src->add("noperspective "); + if (decompilerContext->shaderType == LatteConst::ShaderType::Vertex) + src->add("out"); + else if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel) + src->add("in"); + else + debugBreakpoint(); + + src->addFmt(" vec4 passG2PParameter{};" _CRLF, (sint32)location); + } + } + } + // output defines + if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel) + { + // generate pixel outputs for pixel shader + for (uint32 i = 0; i < LATTE_NUM_COLOR_TARGET; i++) + { + if ((decompilerContext->shader->pixelColorOutputMask&(1 << i)) != 0) + { + src->addFmt("layout(location = {}) out vec4 passPixelColor{};" _CRLF, i, i); + } + } + } + // streamout buffer (transform feedback) + if ((decompilerContext->shaderType == LatteConst::ShaderType::Vertex || decompilerContext->shaderType == LatteConst::ShaderType::Geometry) && decompilerContext->analyzer.hasStreamoutEnable) + { + if (decompilerContext->useTFViaSSBO) + { + if (decompilerContext->analyzer.useSSBOForStreamout && decompilerContext->analyzer.hasStreamoutWrite) + { + src->addFmt("layout(set = {}, binding = {}) buffer StreamoutBuffer" _CRLF, decompilerContext->output->resourceMappingVK.setIndex, decompilerContext->output->resourceMappingVK.getTFStorageBufferBindingPoint()); + src->add("{" _CRLF); + src->add("int sb_buffer[];" _CRLF); + src->add("};" _CRLF); + } + } + else + { + sint32 locationOffset = 0; // glslang wants a location for xfb outputs + for (uint32 i = 0; i < LATTE_NUM_STREAMOUT_BUFFER; i++) + { + if (!decompilerContext->output->streamoutBufferWriteMask[i]) + continue; + uint32 bufferStride = decompilerContext->output->streamoutBufferStride[i]; + src->addFmt("XFB_BLOCK_LAYOUT({}, {}, {}) out XfbBlock{} " _CRLF, i, bufferStride, locationOffset, i); + src->add("{" _CRLF); + src->addFmt("layout(xfb_buffer = {}, xfb_offset = 0) int sb{}[{}];" _CRLF, i, i, decompilerContext->output->streamoutBufferStride[i] / 4); + src->add("};" _CRLF); + locationOffset += (decompilerContext->output->streamoutBufferStride[i] / 4); + } + } + } + } + + void emitHeader(LatteDecompilerShaderContext* decompilerContext) + { + const bool dump_shaders_enabled = ActiveSettings::DumpShadersEnabled(); + if(dump_shaders_enabled) + decompilerContext->shaderSource->add("// start of shader inputs/outputs, predetermined by Cemu. Do not touch" _CRLF); + // macros + _emitHeaderMacros(decompilerContext); + // uniform variables + decompilerContext->shaderSource->add("#ifdef VULKAN" _CRLF); + _emitUniformVariables(decompilerContext, RendererAPI::Vulkan, decompilerContext->output->uniformOffsetsVK); + decompilerContext->shaderSource->add("#else" _CRLF); + _emitUniformVariables(decompilerContext, RendererAPI::OpenGL, decompilerContext->output->uniformOffsetsGL); + decompilerContext->shaderSource->add("#endif" _CRLF); + // uniform buffers + _emitUniformBuffers(decompilerContext); + // textures + _emitTextureDefinitions(decompilerContext); + // attributes + _emitAttributes(decompilerContext); + // misc stuff + _emitMisc(decompilerContext); + + if (dump_shaders_enabled) + decompilerContext->shaderSource->add("// end of shader inputs/outputs" _CRLF); + } +} diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInstructions.h b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInstructions.h new file mode 100644 index 00000000..7c07155d --- /dev/null +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInstructions.h @@ -0,0 +1,117 @@ +// GPU7 instruction set is a hybrid between R600 and R700 + +// CF instructions +#define CF_INST_NOP (0x00) +#define CF_INST_TEX (0x01) +#define CF_INST_LOOP_END (0x05) +#define CF_INST_LOOP_START_DX10 (0x06) +#define CF_INST_JUMP (0x0A) +#define CF_INST_ELSE (0x0D) +#define CF_INST_POP (0x0E) +#define CF_INST_ELSE_AFTER (0x0F) +#define CF_INST_CALL_FS (0x13) +#define CF_INST_EXPORT (0x27) +#define CF_INST_EXPORT_DONE (0x28) + +#define CF_INST_ALU (0x08) +#define CF_INST_ALU_PUSH_BEFORE (0x09) +#define CF_INST_ALU_POP_AFTER (0x0A) +#define CF_INST_ALU_POP2_AFTER (0x0B) +#define CF_INST_ALU_BREAK (0x0E) // leave loop if pred is modified + +#define ALU_OMOD_NONE (0) +#define ALU_OMOD_MUL2 (1) +#define ALU_OMOD_MUL4 (2) +#define ALU_OMOD_DIV2 (3) + +// ALU op2 instructions +#define ALU_OP2_INST_ADD (0x000) +#define ALU_OP2_INST_MUL (0x001) +#define ALU_OP2_INST_MUL_IEEE (0x002) +#define ALU_OP2_INST_MAX (0x003) +#define ALU_OP2_INST_MIN (0x004) +#define ALU_OP2_INST_MAX_DX10 (0x005) +#define ALU_OP2_INST_SETE (0x008) +#define ALU_OP2_INST_SETGT (0x009) +#define ALU_OP2_INST_SETGE (0x00A) +#define ALU_OP2_INST_SETNE (0x00B) +#define ALU_OP2_INST_SETE_DX10 (0x00C) +#define ALU_OP2_INST_SETGT_DX10 (0x00D) +#define ALU_OP2_INST_SETGE_DX10 (0x00E) +#define ALU_OP2_INST_SETNE_DX10 (0x00F) +#define ALU_OP2_INST_FLOOR (0x014) +#define ALU_OP2_INST_FRACT (0x010) +#define ALU_OP2_INST_TRUNC (0x011) +#define ALU_OP2_INST_RNDNE (0x013) +#define ALU_OP2_INST_MOVA_FLOOR (0x016) // changes address register +#define ALU_OP2_INST_MOVA_INT (0x018) // changes address register +#define ALU_OP2_INST_MOV (0x019) +#define ALU_OP2_INST_NOP (0x01A) +#define ALU_OP2_INST_PRED_SETE (0x020) +#define ALU_OP2_INST_PRED_SETGT (0x021) +#define ALU_OP2_INST_PRED_SETGE (0x022) +#define ALU_OP2_INST_PRED_SETNE (0x023) +#define ALU_OP2_INST_AND_INT (0x030) // integer instruction +#define ALU_OP2_INST_OR_INT (0x031) // integer instruction +#define ALU_OP2_INST_XOR_INT (0x032) // integer instruction +#define ALU_OP2_INST_NOT_INT (0x033) // integer instruction +#define ALU_OP2_INST_ADD_INT (0x034) // integer instruction +#define ALU_OP2_INST_SUB_INT (0x035) // integer instruction +#define ALU_OP2_INST_MAX_INT (0x036) // integer instruction +#define ALU_OP2_INST_MIN_INT (0x037) // integer instruction +#define ALU_OP2_INST_SETE_INT (0x03A) // integer instruction +#define ALU_OP2_INST_SETGT_INT (0x03B) // integer instruction +#define ALU_OP2_INST_SETGE_INT (0x03C) // integer instruction +#define ALU_OP2_INST_SETNE_INT (0x03D) // integer instruction +#define ALU_OP2_INST_SETGT_UINT (0x03E) // integer instruction +#define ALU_OP2_INST_SETGE_UINT (0x03F) // integer instruction +#define ALU_OP2_INST_PRED_SETE_INT (0x042) // integer instruction +#define ALU_OP2_INST_PRED_SETGT_INT (0x043) // integer instruction +#define ALU_OP2_INST_PRED_SETGE_INT (0x044) // integer instruction +#define ALU_OP2_INST_PRED_SETNE_INT (0x045) // integer instruction +#define ALU_OP2_INST_KILLE (0x02C) +#define ALU_OP2_INST_KILLGT (0x02D) +#define ALU_OP2_INST_KILLGE (0x02E) +#define ALU_OP2_INST_KILLE_INT (0x046) +#define ALU_OP2_INST_KILLGT_INT (0x047) +#define ALU_OP2_INST_KILLNE_INT (0x049) +#define ALU_OP2_INST_DOT4 (0x050) +#define ALU_OP2_INST_DOT4_IEEE (0x051) +#define ALU_OP2_INST_CUBE (0x052) +#define ALU_OP2_INST_EXP_IEEE (0x061) +#define ALU_OP2_INST_LOG_CLAMPED (0x062) +#define ALU_OP2_INST_LOG_IEEE (0x063) +#define ALU_OP2_INST_SQRT_IEEE (0x06A) +#define ALU_OP2_INST_SIN (0x06E) +#define ALU_OP2_INST_COS (0x06F) +#define ALU_OP2_INST_RECIP_FF (0x065) +#define ALU_OP2_INST_RECIP_IEEE (0x066) +#define ALU_OP2_INST_RECIPSQRT_CLAMPED (0x067) +#define ALU_OP2_INST_RECIPSQRT_FF (0x068) +#define ALU_OP2_INST_RECIPSQRT_IEEE (0x069) +#define ALU_OP2_INST_FLT_TO_INT (0x06B) // conversion instruction +#define ALU_OP2_INST_INT_TO_FLOAT (0x06C) // conversion instruction +#define ALU_OP2_INST_UINT_TO_FLOAT (0x06D) // conversion instruction +#define ALU_OP2_INST_ASHR_INT (0x070) // integer instruction +#define ALU_OP2_INST_LSHR_INT (0x071) // integer instruction +#define ALU_OP2_INST_LSHL_INT (0x072) // integer instruction +#define ALU_OP2_INST_MULLO_INT (0x073) // integer instruction +#define ALU_OP2_INST_MULLO_UINT (0x075) // integer instruction +#define ALU_OP2_INST_FLT_TO_UINT (0x079) // conversion instruction + +// ALU op3 instructions +#define ALU_OP3_INST_MULADD (0x10) +#define ALU_OP3_INST_MULADD_M2 (0x11) +#define ALU_OP3_INST_MULADD_M4 (0x12) +#define ALU_OP3_INST_MULADD_D2 (0x13) +#define ALU_OP3_INST_MULADD_IEEE (0x14) +#define ALU_OP3_INST_CMOVE (0x18) +#define ALU_OP3_INST_CMOVGT (0x19) +#define ALU_OP3_INST_CMOVGE (0x1A) +#define ALU_OP3_INST_CNDE_INT (0x1C) // integer instruction +#define ALU_OP3_INST_CNDGT_INT (0x1D) // integer instruction +#define ALU_OP3_INST_CMOVGE_INT (0x1E) // integer instruction + +// fetch shader +#define VTX_INST_SEMANTIC (0x01) +#define VTX_INST_MEM (0x02) \ No newline at end of file diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInternal.h b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInternal.h new file mode 100644 index 00000000..4c2c2efd --- /dev/null +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInternal.h @@ -0,0 +1,234 @@ +#pragma once + +struct LatteDecompilerALUInstruction +{ + struct LatteDecompilerCFInstruction* cfInstruction{}; + bool isOP3{}; + uint32 opcode{}; + uint32 instructionGroupIndex{}; + uint8 indexMode{}; + uint8 omod{}; + // destination + uint32 destGpr{}; + uint8 destRel{}; + uint8 destElem{}; + uint8 destClamp{}; + uint8 writeMask{}; + // flags + uint8 updateExecuteMask{}; + uint8 updatePredicate{}; + // source operands + struct + { + uint32 sel{}; + uint8 rel{}; + uint8 abs{}; + uint8 neg{}; + uint8 chan{}; + // register backup information (used for instruction groups where the same register is read and written) + bool requiredRegisterBackup{}; + uint8 registerBackupIndex{}; // index of the used register backup variable (at the beginning of the group the register value is copied to the temporary register with this index) + }sourceOperand[3]; + union + { + uint32 w[4]; + float f[4]; + }literalData; + // information from analyzer stage + uint8 aluUnit{}; // 0-3 -> ALU.x/y/u/w (PV), 4 -> Trans unit (PS) + uint8 indexInGroup{}; // index of instruction within instruction group + bool isLastInstructionOfGroup{}; +}; + +struct LatteDecompilerTEXInstruction +{ + struct LatteDecompilerCFInstruction* cfInstruction{}; + uint32 opcode{}; + // texture or vertex fetch + // shared + sint32 srcGpr; + sint32 dstGpr; + sint8 dstSel[4]; + // texture fetch + struct + { + sint32 textureIndex{}; + sint32 samplerIndex{}; + uint32 offset{}; + sint8 srcSel[4]{}; + sint8 offsetX{}; + sint8 offsetY{}; + sint8 offsetZ{}; + bool unnormalized[4]{}; // set if texture coordinates are in [0,dim] range instead of [0,1] + }textureFetch; + // memRead + struct + { + uint32 arrayBase{}; + sint8 srcSelX{}; + uint32 format{}; + uint8 nfa{}; + uint8 isSigned{}; + }memRead; + // custom shadow function + sint32 shadowFunctionIndex{}; +}; + +struct LatteDecompilerCFInstruction +{ + uint32 type{}; + uint32 cfAddr{}; + // for clauses with instructions + uint32 addr{}; + sint32 count{}; + // clause contains either ALU or TEX instructions + std::vector<LatteDecompilerALUInstruction> instructionsALU; + std::vector<LatteDecompilerTEXInstruction> instructionsTEX; + // for clauses that access uniform buffers + uint32 cBank0Index{}; + uint32 cBank1Index{}; + uint32 cBank0AddrBase{}; + uint32 cBank1AddrBase{}; + // for exports + uint32 exportType{}; + uint8 exportComponentSel[4]{}; + uint32 exportBurstCount{}; + // for mem write + uint32 memWriteArraySize{}; + uint8 memWriteCompMask{}; + uint8 memWriteElemSize{}; // 0-3 + // for exports and mem write + uint32 exportArrayBase{}; + uint32 exportSourceGPR{}; + // misc + uint32 cfCond{}; + uint32 popCount{}; + // information from analyzer stage + bool modifiesPredicate{}; + bool modifiesActiveMask{}; + uint32 numPredInstructions{}; + sint32 activeStackDepth{}; // stack depth during the clause/CF instruction + + LatteDecompilerCFInstruction() + { + + } + + ~LatteDecompilerCFInstruction() + { + cemu_assert_debug(!(instructionsALU.size() != 0 && instructionsTEX.size() != 0)); // make sure we dont accidentally added the wrong instruction type + } + +#if BOOST_OS_WINDOWS + LatteDecompilerCFInstruction(LatteDecompilerCFInstruction& mE) = default; + LatteDecompilerCFInstruction(LatteDecompilerCFInstruction&& mE) = default; +#else + LatteDecompilerCFInstruction(const LatteDecompilerCFInstruction& mE) = default; + LatteDecompilerCFInstruction(const LatteDecompilerCFInstruction&& mE) = default; +#endif + LatteDecompilerCFInstruction& operator=(LatteDecompilerCFInstruction&& mE) = default; +}; + +struct LatteDecompilerCFileAccess +{ + LatteDecompilerCFileAccess(uint8 index, bool isRelative) : index(index), isRelative(isRelative) {}; + uint8 index; + bool isRelative; +}; + +struct LatteDecompilerSubroutineInfo +{ + uint32 cfAddr; + std::vector<LatteDecompilerCFInstruction> instructions; +}; + +struct LatteDecompilerShaderContext +{ + LatteDecompilerOutput_t* output; + LatteDecompilerShader* shader; + LatteConst::ShaderType shaderType; + uint32* contextRegisters; // deprecated + struct LatteContextRegister* contextRegistersNew; + uint64 shaderBaseHash; + StringBuf* shaderSource; // move to output struct + std::vector<LatteDecompilerCFInstruction> cfInstructions; + // fetch shader (required for vertex shader) + LatteFetchShader* fetchShaderList[32]; + sint32 fetchShaderCount; + // geometry copy shader (only present when geometry shader is active) + LatteParsedGSCopyShader* parsedGSCopyShader; + // state + bool hasError; + // type tracker + struct + { + // data type tracker + uint8 defaultDataType; + bool genFloatReg; // if set, generate R*f register variables + bool genIntReg; // if set, generate R*i register variables + bool useArrayGPRs; // if set, an array is used to represent GPRs instead of individual variables + }typeTracker; + // analyzer + struct + { + // general + bool hasStreamoutEnable{}; // set if streamout is enabled + bool hasLoops{}; // loop directives present in shader + // vertex shader + bool isPointsPrimitive{}; // set if current render primitive is points + bool outputPointSize{}; // set if the current shader should output the point size + std::bitset<256> inputAttributSemanticMask; // one set bit for every used semanticId - todo: there are only 128 bit available semantic locations? The MSB has special meaning? + // uniform + bool uniformRegisterAccess; // set to true if cfile (uniform register) is accessed + bool uniformRegisterDynamicAccess; // set to true if cfile (uniform register) is accessed with a dynamic index + uint32 uniformBufferAccessMask; // 1 bit per buffer, set if the uniform buffer is accessed + uint32 uniformBufferDynamicAccessMask; // 1 bit per buffer, set if the uniform buffer is accessed by dynamic index + std::vector<LatteDecompilerCFileAccess> uniformRegisterAccessIndices; + // ssbo + bool hasSSBORead; // shader has instructions that read from SSBO + bool hasSSBOWrite; // shader has instructions that write to SSBO + // textures + std::bitset<LATTE_NUM_MAX_TEX_UNITS> texUnitUsesTexelCoordinates; + bool hasCubeMapTexture; // set to true if a cubemap texture is used + bool hasGradientLookup; // set to true if texture lookup with custom gradients is used + // misc + bool usesRelativeGPRRead; // set if indexed GPR reads are used + bool usesRelativeGPRWrite; // set if indexed GPR writes are used + uint8 gprUseMask[(LATTE_NUM_GPR + 7) / 8]; // 1 bit per GPR, set if GPR is read/written anywhere in the program (ignores GPR accesses with relative index) + bool hasStreamoutWrite; // stream-out CF instructions are used + bool hasRedcCUBE; // has cube reduction instruction + bool modifiesPixelActiveState; // set if the active mask is changed anywhere in the shader (If false, we can skip active mask checks) + bool usesIntegerValues; // set if the shader uses any kind of integer instruction or integer-based GPR/AR access + sint32 activeStackMaxDepth; // maximum depth of pixel state stack + // analyzer stage (vs) + bool writesPointSize{}; + // streamout (vs and gs) + bool useSSBOForStreamout{}; + // geometry shader + uint32 numEmitVertex{}; // counts how often emit vertex instruction is found + }analyzer; + + // set while generating code for subroutine + bool isSubroutine; + LatteDecompilerSubroutineInfo* subroutineInfo; + + // emitter + bool hasUniformVarBlock; + sint32 currentBindingPointVK{}; + + // unsorted + bool usesGeometryShader; // for VS + bool useTFViaSSBO; + sint32 currentShadowFunctionIndex; + std::vector<LatteDecompilerSubroutineInfo> list_subroutines; +}; + +void LatteDecompiler_analyze(LatteDecompilerShaderContext* shaderContext, LatteDecompilerShader* shader); +void LatteDecompiler_analyzeDataTypes(LatteDecompilerShaderContext* shaderContext); +void LatteDecompiler_emitGLSLShader(LatteDecompilerShaderContext* shaderContext, LatteDecompilerShader* shader); + +void LatteDecompiler_cleanup(LatteDecompilerShaderContext* shaderContext); + +// helper functions + +sint32 LatteDecompiler_getColorOutputIndexFromExportIndex(LatteDecompilerShaderContext* shaderContext, sint32 exportIndex); \ No newline at end of file diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerRegisterDataTypeTracker.cpp b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerRegisterDataTypeTracker.cpp new file mode 100644 index 00000000..b9fd3618 --- /dev/null +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerRegisterDataTypeTracker.cpp @@ -0,0 +1,25 @@ +#include "Cafe/HW/Latte/Core/LatteConst.h" +#include "Cafe/HW/Latte/Core/LatteShaderAssembly.h" +#include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInstructions.h" +#include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h" +#include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInternal.h" + +void LatteDecompiler_analyzeDataTypes(LatteDecompilerShaderContext* shaderContext) +{ + // determine default type + if (shaderContext->analyzer.usesIntegerValues) + { + shaderContext->typeTracker.defaultDataType = LATTE_DECOMPILER_DTYPE_SIGNED_INT; + shaderContext->typeTracker.genIntReg = true; + } + else + { + shaderContext->typeTracker.defaultDataType = LATTE_DECOMPILER_DTYPE_FLOAT; + shaderContext->typeTracker.genFloatReg = true; + } + // determine register representation + if (shaderContext->analyzer.usesRelativeGPRWrite) + { + shaderContext->typeTracker.useArrayGPRs = true; + } +} \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/CachedFBOGL.h b/src/Cafe/HW/Latte/Renderer/OpenGL/CachedFBOGL.h new file mode 100644 index 00000000..c0c91cba --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/CachedFBOGL.h @@ -0,0 +1,101 @@ +#pragma once +#include "Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.h" +#include "Cafe/HW/Latte/Core/LatteCachedFBO.h" +#include "Common/GLInclude/GLInclude.h" + +class CachedFBOGL : public LatteCachedFBO +{ +public: + CachedFBOGL(uint64 key) + : LatteCachedFBO(key) + { + // generate framebuffer + if (glCreateFramebuffers && false) + glCreateFramebuffers(1, &glId_fbo); + else + glGenFramebuffers(1, &glId_fbo); + // bind framebuffer + ((OpenGLRenderer*)g_renderer.get())->rendertarget_bindFramebufferObject(this); + // setup framebuffer + for (sint32 i = 0; i < 8; i++) + { + LatteTextureViewGL* colorTexViewGL = (LatteTextureViewGL*)colorBuffer[i].texture; + if (!colorTexViewGL) + glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT + i, GL_TEXTURE_2D, 0, 0); + else if (colorTexViewGL->dim == Latte::E_DIM::DIM_2D) + glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT + i, GL_TEXTURE_2D, colorTexViewGL->glTexId, 0); + else if (colorTexViewGL->dim == Latte::E_DIM::DIM_3D) + glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0_EXT + i, colorTexViewGL->glTexId, 0, 0); + else if (colorTexViewGL->dim == Latte::E_DIM::DIM_2D_ARRAY) + glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0_EXT + i, colorTexViewGL->glTexId, 0, 0); + else if (colorTexViewGL->dim == Latte::E_DIM::DIM_CUBEMAP) + glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0_EXT + i, colorTexViewGL->glTexId, 0, 0); + else + { + cemu_assert_suspicious(); + } + } + + if (depthBuffer.texture) + { + LatteTextureViewGL* depthTexViewGL = (LatteTextureViewGL*)depthBuffer.texture; + if (depthTexViewGL->dim == Latte::E_DIM::DIM_2D) + glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTexViewGL->glTexId, 0); + else if (depthTexViewGL->dim == Latte::E_DIM::DIM_2D_ARRAY) + glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthTexViewGL->glTexId, 0, 0); + else + cemu_assert_suspicious(); + if (depthBuffer.hasStencil) + { + if (depthTexViewGL->dim == Latte::E_DIM::DIM_2D) + glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, depthTexViewGL->glTexId, 0); + else if (depthTexViewGL->dim == Latte::E_DIM::DIM_2D_ARRAY) + glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, depthTexViewGL->glTexId, 0, 0); + else + cemu_assert_suspicious(); + } + else + { + glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + } + } + else + { + glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + } + SetDrawBuffers(); + } + +private: + void SetDrawBuffers() + { + GLenum buffers[8]; + uint32 bufferCount = 0; + for (uint32 i = 0; i < 8; i++) + { + if (colorBuffer[i].texture) + { + buffers[bufferCount] = GL_COLOR_ATTACHMENT0_EXT + i; + bufferCount++; + } + else + { + // pad with GL_NONE entries to make sure that draw buffer indices match up with color attachment indices + buffers[bufferCount] = GL_NONE; + bufferCount++; + } + } + // trim trailing GL_NONE + while (bufferCount > 0 && buffers[bufferCount - 1] == GL_NONE) + bufferCount--; + + if (bufferCount == 0) + glDrawBuffer(GL_NONE); + else + glDrawBuffers(bufferCount, buffers); + } + +public: + GLuint glId_fbo{}; +}; diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.cpp new file mode 100644 index 00000000..dac3c3a4 --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.cpp @@ -0,0 +1,510 @@ +#include "Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.h" +#include "Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.h" +#include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h" +#include "Cafe/HW/Latte/Core/Latte.h" + +#include "config/LaunchSettings.h" + +GLuint texIdPool[64]; +sint32 texIdPoolIndex = 64; + +static GLuint _genTextureHandleGL() +{ + if (texIdPoolIndex == 64) + { + glGenTextures(64, texIdPool); + texIdPoolIndex = 0; + } + texIdPoolIndex++; + return texIdPool[texIdPoolIndex - 1]; +} + +LatteTextureGL::LatteTextureGL(uint32 textureUnit, Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, uint32 swizzle, + Latte::E_HWTILEMODE tileMode, bool isDepth) + : LatteTexture(textureUnit, dim, physAddress, physMipAddress, format, width, height, depth, pitch, mipLevels, swizzle, tileMode, isDepth) +{ + GenerateEmptyTextureFromGX2Dim(dim, this->glId_texture, this->glTexTarget); + // set format info + FormatInfoGL glFormatInfo; + GetOpenGLFormatInfo(isDepth, format, dim, &glFormatInfo); + this->glInternalFormat = glFormatInfo.glInternalFormat; + this->isAlternativeFormat = glFormatInfo.isUsingAlternativeFormat; + this->hasStencil = glFormatInfo.hasStencil; // todo - should get this from the GX2 format? + // bind texture + g_renderer->texture_bindAndActivateRawTex(this, textureUnit); + LatteTextureGL::InitTextureState(); + // set debug name + bool useGLDebugNames = false; +#ifndef PUBLIC_RELEASE + useGLDebugNames = true; +#endif + if (LaunchSettings::NSightModeEnabled()) + useGLDebugNames = true; + if (useGLDebugNames) + { + char textureDebugLabel[512]; + sprintf(textureDebugLabel, "%08x_f%04x%s_p%04x_%dx%d", physAddress, (uint32)format, this->isDepth ? "_d" : "", pitch, width, height); + glObjectLabel(GL_TEXTURE, this->glId_texture, -1, textureDebugLabel); + } +} + +LatteTextureGL::~LatteTextureGL() +{ + glDeleteTextures(1, &glId_texture); + catchOpenGLError(); +} + +void LatteTextureGL::GenerateEmptyTextureFromGX2Dim(Latte::E_DIM dim, GLuint& texId, GLint& texTarget) +{ + texId = _genTextureHandleGL(); + if (dim == Latte::E_DIM::DIM_2D) + texTarget = GL_TEXTURE_2D; + else if (dim == Latte::E_DIM::DIM_1D) + texTarget = GL_TEXTURE_1D; + else if (dim == Latte::E_DIM::DIM_3D) + texTarget = GL_TEXTURE_3D; + else if (dim == Latte::E_DIM::DIM_2D_ARRAY) + texTarget = GL_TEXTURE_2D_ARRAY; + else if (dim == Latte::E_DIM::DIM_CUBEMAP) + texTarget = GL_TEXTURE_CUBE_MAP_ARRAY; + else if (dim == Latte::E_DIM::DIM_2D_MSAA) + texTarget = GL_TEXTURE_2D; // todo, GL_TEXTURE_2D_MULTISAMPLE ? + else + { + cemu_assert_unimplemented(); + } +} + +LatteTextureView* LatteTextureGL::CreateView(Latte::E_DIM dim, Latte::E_GX2SURFFMT format, sint32 firstMip, sint32 mipCount, sint32 firstSlice, sint32 sliceCount) +{ + return new LatteTextureViewGL(this, dim, format, firstMip, mipCount, firstSlice, sliceCount); +} + +void LatteTextureGL::InitTextureState() +{ + // init texture with some default parameters (todo - this shouldn't be necessary if we properly set parameters when a texture is used) + catchOpenGLError(); + glTexParameteri(glTexTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(glTexTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(glTexTarget, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(glTexTarget, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameteri(glTexTarget, GL_TEXTURE_COMPARE_MODE, GL_NONE); + catchOpenGLError(); +} + +void LatteTextureGL::GetOpenGLFormatInfo(bool isDepth, Latte::E_GX2SURFFMT format, Latte::E_DIM dim, FormatInfoGL* formatInfoOut) +{ + formatInfoOut->isUsingAlternativeFormat = false; + + if (isDepth) + { + if (format == Latte::E_GX2SURFFMT::D24_S8_UNORM) + { + formatInfoOut->setDepthFormat(GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, true); + return; + } + else if (format == Latte::E_GX2SURFFMT::D24_S8_FLOAT) + { + formatInfoOut->setDepthFormat(GL_DEPTH32F_STENCIL8, GL_DEPTH_STENCIL, GL_FLOAT_32_UNSIGNED_INT_24_8_REV, true); + formatInfoOut->markAsAlternativeFormat(); + return; + } + else if (format == Latte::E_GX2SURFFMT::D32_S8_FLOAT) + { + formatInfoOut->setDepthFormat(GL_DEPTH32F_STENCIL8, GL_DEPTH_STENCIL, GL_FLOAT_32_UNSIGNED_INT_24_8_REV, true); + return; + } + else if (format == Latte::E_GX2SURFFMT::D32_FLOAT) + { + formatInfoOut->setDepthFormat(GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT, false); + return; + } + else if (format == Latte::E_GX2SURFFMT::D16_UNORM) + { + formatInfoOut->setDepthFormat(GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, false); + return; + } + // unsupported depth format + forceLog_printf("OpenGL: Unsupported texture depth format 0x%04x", (uint32)format); + // use placeholder format + formatInfoOut->setDepthFormat(GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, false); + formatInfoOut->markAsAlternativeFormat(); + return; + } + + bool glIsCompressed = false; + bool isUsingAlternativeFormat = false; // set to true if there is no bit-perfect matching OpenGL format + sint32 glInternalFormat; + sint32 glSuppliedFormat; + sint32 glSuppliedFormatType; + // check if compressed textures should be used + bool allowCompressedGLFormat = true; + if (LatteGPUState.glVendor == GLVENDOR_INTEL_LEGACY) + allowCompressedGLFormat = false; // compressed formats seem to cause more harm than good on Intel + // get format information + if (format == Latte::E_GX2SURFFMT::R4_G4_UNORM) + { + formatInfoOut->setFormat(GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4); + formatInfoOut->markAsAlternativeFormat(); + return; + } + else if (format == Latte::E_GX2SURFFMT::R4_G4_B4_A4_UNORM) + { + formatInfoOut->setFormat(GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4); + return; + } + else if (format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT) + { + formatInfoOut->setFormat(GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT); + return; + } + else if (format == Latte::E_GX2SURFFMT::R16_G16_FLOAT) + { + formatInfoOut->setFormat(GL_RG16F, GL_RG, GL_HALF_FLOAT); + return; + } + else if (format == Latte::E_GX2SURFFMT::R16_SNORM) + { + formatInfoOut->setFormat(GL_R16_SNORM, GL_RED, GL_SHORT); + return; + } + else if (format == Latte::E_GX2SURFFMT::R16_FLOAT) + { + formatInfoOut->setFormat(GL_R16F, GL_RED, GL_HALF_FLOAT); + return; + } + else if (format == Latte::E_GX2SURFFMT::BC1_UNORM || + format == Latte::E_GX2SURFFMT::BC1_SRGB) + { + if (allowCompressedGLFormat) + { + if (format == Latte::E_GX2SURFFMT::BC1_SRGB) + formatInfoOut->setCompressed(GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT, -1, -1); + else + formatInfoOut->setCompressed(GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, -1, -1); + return; + } + else + { + formatInfoOut->setFormat(GL_RGBA16F, GL_RGBA, GL_FLOAT); + formatInfoOut->markAsAlternativeFormat(); + return; + } + } + else if (format == Latte::E_GX2SURFFMT::BC2_UNORM || format == Latte::E_GX2SURFFMT::BC2_SRGB) + { + // todo - use OpenGL BC2 format if available + formatInfoOut->setFormat(GL_RGBA16F, GL_RGBA, GL_FLOAT); + formatInfoOut->markAsAlternativeFormat(); + return; + } + else if (format == Latte::E_GX2SURFFMT::BC3_UNORM || format == Latte::E_GX2SURFFMT::BC3_SRGB) + { + if (allowCompressedGLFormat) + { + if (format == Latte::E_GX2SURFFMT::BC3_SRGB) + formatInfoOut->setCompressed(GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT, -1, -1); + else + formatInfoOut->setCompressed(GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, -1, -1); + return; + } + else + { + // todo: SRGB support + formatInfoOut->setFormat(GL_RGBA16F, GL_RGBA, GL_FLOAT); + formatInfoOut->markAsAlternativeFormat(); + return; + } + } + else if (format == Latte::E_GX2SURFFMT::BC4_UNORM || format == Latte::E_GX2SURFFMT::BC4_SNORM) + { + if (dim != Latte::E_DIM::DIM_2D && dim != Latte::E_DIM::DIM_2D_ARRAY) + allowCompressedGLFormat = false; // RGTC1 does not support non-2D textures + + if (allowCompressedGLFormat) + { + if (format == Latte::E_GX2SURFFMT::BC4_UNORM) + formatInfoOut->setCompressed(GL_COMPRESSED_RED_RGTC1, -1, -1); + else + formatInfoOut->setCompressed(GL_COMPRESSED_SIGNED_RED_RGTC1, -1, -1); + return; + } + else + { + formatInfoOut->setFormat(GL_RG16F, GL_RG, GL_FLOAT); + formatInfoOut->markAsAlternativeFormat(); + return; + } + } + else if (format == Latte::E_GX2SURFFMT::BC5_UNORM || format == Latte::E_GX2SURFFMT::BC5_SNORM) + { + if (allowCompressedGLFormat) + { + if (format == Latte::E_GX2SURFFMT::BC5_SNORM) + formatInfoOut->setCompressed(GL_COMPRESSED_SIGNED_RG_RGTC2, -1, -1); + else + formatInfoOut->setCompressed(GL_COMPRESSED_RG_RGTC2, -1, -1); + return; + } + else + { + formatInfoOut->setFormat(GL_RG16F, GL_RG, GL_FLOAT); + formatInfoOut->markAsAlternativeFormat(); + return; + } + } + else if (format == Latte::E_GX2SURFFMT::R32_FLOAT) + { + formatInfoOut->setFormat(GL_R32F, GL_RED, GL_FLOAT); + return; + } + else if (format == Latte::E_GX2SURFFMT::R32_G32_FLOAT) + { + formatInfoOut->setFormat(GL_RG32F, GL_RG, GL_FLOAT); + return; + } + else if (format == Latte::E_GX2SURFFMT::R32_G32_UINT) + { + formatInfoOut->setFormat(GL_RG32UI, GL_RG_INTEGER, GL_UNSIGNED_INT); + return; + } + else if (format == Latte::E_GX2SURFFMT::R32_UINT) + { + formatInfoOut->setFormat(GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT); + return; + } + else if (format == Latte::E_GX2SURFFMT::R16_UINT) + { + // used by VC DS (New Super Mario Bros) + formatInfoOut->setFormat(GL_R16UI, GL_RED_INTEGER, GL_UNSIGNED_SHORT); + return; + } + else if (format == Latte::E_GX2SURFFMT::R8_UINT) + { + // used by VC DS (New Super Mario Bros) + formatInfoOut->setFormat(GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE); + return; + } + else if (format == Latte::E_GX2SURFFMT::R32_G32_B32_A32_FLOAT) + { + formatInfoOut->setFormat(GL_RGBA32F, GL_RGBA, GL_FLOAT); + return; + } + else if (format == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM || format == Latte::E_GX2SURFFMT::R8_G8_B8_A8_SRGB) + { + if (format == Latte::E_GX2SURFFMT::R8_G8_B8_A8_SRGB) + glInternalFormat = GL_SRGB8_ALPHA8; + else + glInternalFormat = GL_RGBA8; + // supplied format + glSuppliedFormat = GL_RGBA; + glSuppliedFormatType = GL_UNSIGNED_BYTE; + glIsCompressed = false; + } + else if (format == Latte::E_GX2SURFFMT::R8_G8_B8_A8_SNORM) + { + glInternalFormat = GL_RGBA8_SNORM; + // supplied format + glSuppliedFormat = GL_RGBA; + glSuppliedFormatType = GL_BYTE; + glIsCompressed = false; + } + else if (format == Latte::E_GX2SURFFMT::R8_UNORM) + { + glInternalFormat = GL_R8; + // supplied format + glSuppliedFormat = GL_RED; + glSuppliedFormatType = GL_UNSIGNED_BYTE; + glIsCompressed = false; + } + else if (format == Latte::E_GX2SURFFMT::R8_SNORM) + { + glInternalFormat = GL_R8_SNORM; + // supplied format + glSuppliedFormat = GL_RED; + glSuppliedFormatType = GL_BYTE; + glIsCompressed = false; + } + else if (format == Latte::E_GX2SURFFMT::R8_G8_UNORM) + { + glInternalFormat = GL_RG8; + // supplied format + glSuppliedFormat = GL_RG; + glSuppliedFormatType = GL_UNSIGNED_BYTE; + glIsCompressed = false; + } + else if (format == Latte::E_GX2SURFFMT::R8_G8_SNORM) + { + glInternalFormat = GL_RG8_SNORM; + // supplied format + glSuppliedFormat = GL_RG; + glSuppliedFormatType = GL_BYTE; + glIsCompressed = false; + } + else if (format == Latte::E_GX2SURFFMT::R16_UNORM) + { + glInternalFormat = GL_R16; + // supplied format + glSuppliedFormat = GL_RED; + glSuppliedFormatType = GL_UNSIGNED_SHORT; + glIsCompressed = false; + } + else if (format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_UNORM) + { + glInternalFormat = GL_RGBA16; + // supplied format + glSuppliedFormat = GL_RGBA; + glSuppliedFormatType = GL_UNSIGNED_SHORT; + glIsCompressed = false; + } + else if (format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_SNORM) + { + glInternalFormat = GL_RGBA16_SNORM; + // supplied format + glSuppliedFormat = GL_RGBA; + glSuppliedFormatType = GL_SHORT; + glIsCompressed = false; + } + else if (format == Latte::E_GX2SURFFMT::R16_G16_UNORM) + { + glInternalFormat = GL_RG16; + // supplied format + glSuppliedFormat = GL_RG; + glSuppliedFormatType = GL_UNSIGNED_SHORT; + glIsCompressed = false; + } + else if (format == Latte::E_GX2SURFFMT::R5_G6_B5_UNORM) + { + glInternalFormat = GL_RGB565; + // supplied format + glSuppliedFormat = GL_RGB; + glSuppliedFormatType = GL_UNSIGNED_SHORT_5_6_5_REV; + glIsCompressed = false; + } + else if (format == Latte::E_GX2SURFFMT::R5_G5_B5_A1_UNORM) + { + glInternalFormat = GL_RGB5_A1; + // supplied format + glSuppliedFormat = GL_RGBA; + glSuppliedFormatType = GL_UNSIGNED_SHORT_5_5_5_1; + glIsCompressed = false; + } + else if (format == Latte::E_GX2SURFFMT::A1_B5_G5_R5_UNORM) + { + glInternalFormat = GL_RGB5_A1; + // supplied format + glSuppliedFormat = GL_RGBA; + glSuppliedFormatType = GL_UNSIGNED_SHORT_5_5_5_1; + glIsCompressed = false; + } + else if (format == Latte::E_GX2SURFFMT::R10_G10_B10_A2_UNORM) + { + glInternalFormat = GL_RGB10_A2; + // supplied format + glSuppliedFormat = GL_RGBA; + glSuppliedFormatType = GL_UNSIGNED_INT_2_10_10_10_REV; + glIsCompressed = false; + } + else if (format == Latte::E_GX2SURFFMT::R10_G10_B10_A2_SRGB) // used by Super Mario Maker + { + glInternalFormat = GL_RGB10_A2; // todo - how to handle SRGB for this format? + // supplied format + glSuppliedFormat = GL_RGBA; + glSuppliedFormatType = GL_UNSIGNED_INT_2_10_10_10_REV; + glIsCompressed = false; + } + else if (format == Latte::E_GX2SURFFMT::A2_B10_G10_R10_UNORM) + { + glInternalFormat = GL_RGB10_A2; + // supplied format + glSuppliedFormat = GL_RGBA; + glSuppliedFormatType = GL_UNSIGNED_INT_10_10_10_2; + glIsCompressed = false; + } + else if (format == Latte::E_GX2SURFFMT::R10_G10_B10_A2_SNORM) + { + glInternalFormat = GL_RGBA16_SNORM; // OpenGL has no signed version of GL_RGB10_A2 + isUsingAlternativeFormat = true; + // supplied format + glSuppliedFormat = GL_RGBA; + glSuppliedFormatType = GL_SHORT; + glIsCompressed = false; + } + else if (format == Latte::E_GX2SURFFMT::R11_G11_B10_FLOAT) + { + glInternalFormat = GL_R11F_G11F_B10F; + // supplied format + glSuppliedFormat = GL_RGB; + glSuppliedFormatType = GL_UNSIGNED_INT_10F_11F_11F_REV; + glIsCompressed = false; + } + else if (format == Latte::E_GX2SURFFMT::R32_G32_B32_A32_UINT) + { + glInternalFormat = GL_RGBA32UI; + // supplied format + glSuppliedFormat = GL_RGBA_INTEGER; + glSuppliedFormatType = GL_UNSIGNED_INT; + glIsCompressed = false; + } + else if (format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_UINT) + { + glInternalFormat = GL_RGBA16UI; + // supplied format + glSuppliedFormat = GL_RGBA_INTEGER; + glSuppliedFormatType = GL_UNSIGNED_SHORT; + glIsCompressed = false; + } + else if (format == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UINT) + { + glInternalFormat = GL_RGBA8UI; + // supplied format + glSuppliedFormat = GL_RGBA_INTEGER; + glSuppliedFormatType = GL_UNSIGNED_BYTE; + glIsCompressed = false; + } + else if (format == Latte::E_GX2SURFFMT::R24_X8_UNORM) + { + // OpenGL has no color version of GL_DEPTH24_STENCIL8, therefore we use a 32-bit floating-point format instead + glInternalFormat = GL_R32F; + isUsingAlternativeFormat = true; + // supplied format + glSuppliedFormat = GL_RED; + glSuppliedFormatType = GL_FLOAT; + glIsCompressed = false; + } + else if (format == Latte::E_GX2SURFFMT::X24_G8_UINT) + { + // OpenGL has no X24_G8 format, we use RGBA8UI instead and manually swizzle the channels + // this format is used in Resident Evil Revelations when scanning with the Genesis. It's also used in Cars 3: Driven to Win? + + glInternalFormat = GL_RGBA8UI; + isUsingAlternativeFormat = true; + // supplied format + glSuppliedFormat = GL_RGBA; + glSuppliedFormatType = GL_FLOAT; + glIsCompressed = false; + } + else if (format == Latte::E_GX2SURFFMT::R32_X8_FLOAT) + { + // only available as depth format in OpenGL + // used by Cars 3: Driven to Win + // find a way to emulate this using a color format + glInternalFormat = GL_DEPTH32F_STENCIL8; + isUsingAlternativeFormat = false; + // supplied format + glSuppliedFormat = GL_DEPTH_STENCIL; + glSuppliedFormatType = GL_FLOAT_32_UNSIGNED_INT_24_8_REV; + glIsCompressed = false; + cemu_assert_debug(false); + } + else + { + forceLog_printf("OpenGL: Unsupported texture format 0x%04x", (uint32)format); + cemu_assert_unimplemented(); + } + formatInfoOut->glInternalFormat = glInternalFormat; + formatInfoOut->glSuppliedFormat = glSuppliedFormat; + formatInfoOut->glSuppliedFormatType = glSuppliedFormatType; + formatInfoOut->glIsCompressed = glIsCompressed; + formatInfoOut->isUsingAlternativeFormat = isUsingAlternativeFormat; +} diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.h b/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.h new file mode 100644 index 00000000..fabd1bac --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.h @@ -0,0 +1,67 @@ +#pragma once + +#include "Cafe/HW/Latte/Core/LatteTexture.h" +#include "Common/GLInclude/GLInclude.h" + +class LatteTextureGL : public LatteTexture +{ +public: + LatteTextureGL(uint32 textureUnit, Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, + uint32 swizzle, Latte::E_HWTILEMODE tileMode, bool isDepth); + + ~LatteTextureGL(); + + static void GenerateEmptyTextureFromGX2Dim(Latte::E_DIM dim, GLuint& texId, GLint& texTarget); + + void InitTextureState() override; + +protected: + LatteTextureView* CreateView(Latte::E_DIM dim, Latte::E_GX2SURFFMT format, sint32 firstMip, sint32 mipCount, sint32 firstSlice, sint32 sliceCount) override; + +public: + struct FormatInfoGL + { + sint32 glInternalFormat; + sint32 glSuppliedFormat; + sint32 glSuppliedFormatType; + bool glIsCompressed; + bool hasStencil{}; + bool isUsingAlternativeFormat{}; + + void setFormat(sint32 glInternalFormat, sint32 glSuppliedFormat, sint32 glSuppliedFormatType) + { + this->glInternalFormat = glInternalFormat; + this->glSuppliedFormat = glSuppliedFormat; + this->glSuppliedFormatType = glSuppliedFormatType; + this->glIsCompressed = false; + } + + void setDepthFormat(sint32 glInternalFormat, sint32 glSuppliedFormat, sint32 glSuppliedFormatType, bool hasStencil) + { + this->glInternalFormat = glInternalFormat; + this->glSuppliedFormat = glSuppliedFormat; + this->glSuppliedFormatType = glSuppliedFormatType; + this->glIsCompressed = false; + this->hasStencil = hasStencil; + } + + void setCompressed(sint32 glInternalFormat, sint32 glSuppliedFormat, sint32 glSuppliedFormatType) + { + setFormat(glInternalFormat, glSuppliedFormat, glSuppliedFormatType); + this->glIsCompressed = true; + } + + void markAsAlternativeFormat() + { + this->isUsingAlternativeFormat = true; + } + }; + + static void GetOpenGLFormatInfo(bool isDepth, Latte::E_GX2SURFFMT format, Latte::E_DIM dim, FormatInfoGL* formatInfoOut); + + // OpenGL stuff + GLuint glId_texture; + GLint glTexTarget; // GL_TEXTURE_2D, GL_TEXTURE_3D, GL_TEXTURE_2D_ARRAY etc. + GLint glInternalFormat = 0; // internal format of OpenGL texture (0 if not initialized) + bool isAlternativeFormat{}; // if set to true, the OpenGL format is not a bit-perfect match for the GX2 format +}; diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.cpp new file mode 100644 index 00000000..fc5b5dc4 --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.cpp @@ -0,0 +1,118 @@ +#include "Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.h" +#include "Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.h" +#include "Cafe/HW/Latte/Renderer/Renderer.h" +#include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h" +#include "config/LaunchSettings.h" + +LatteTextureViewGL::LatteTextureViewGL(LatteTextureGL* texture, Latte::E_DIM dim, Latte::E_GX2SURFFMT format, sint32 firstMip, sint32 mipCount, sint32 firstSlice, sint32 sliceCount, bool registerView, bool forceCreateNewTexId) + : LatteTextureView(texture, firstMip, mipCount, firstSlice, sliceCount, dim, format, registerView) +{ + if (dim != texture->dim || format != texture->format || + firstSlice != 0 || firstMip != 0 || mipCount != texture->mipLevels || sliceCount != texture->depth || + forceCreateNewTexId) + { + LatteTextureGL::GenerateEmptyTextureFromGX2Dim(dim, glTexId, glTexTarget); + this->glInternalFormat = 0; + InitAliasView(); + } + else + { + glTexId = texture->glId_texture; + glTexTarget = texture->glTexTarget; + glInternalFormat = texture->glInternalFormat; + } + // mark all sampler properties as undefined + samplerState.maxAniso = 0xFF; + samplerState.filterMin = 0xFFFFFFFF; + samplerState.filterMag = 0xFFFFFFFF; + samplerState.maxMipLevels = 0xFF; + samplerState.borderType = 0xFF; + samplerState.borderColor[0] = 9999.0f; + samplerState.borderColor[1] = 9999.0f; + samplerState.borderColor[2] = 9999.0f; + samplerState.borderColor[3] = 9999.0f; + samplerState.clampS = 0xFF; + samplerState.clampT = 0xFF; + samplerState.clampR = 0xFF; + samplerState.minLod = 0xFFFF; + samplerState.maxLod = 0xFFFF; + samplerState.lodBias = 0x7FFF; + samplerState.depthCompareMode = 0xFF; + samplerState.depthCompareFunc = 0xFF; + swizzleR = 0xFF; + swizzleG = 0xFF; + swizzleB = 0xFF; + swizzleA = 0xFF; +} + +LatteTextureViewGL::~LatteTextureViewGL() +{ + delete m_alternativeView; + ((OpenGLRenderer*)g_renderer.get())->texture_notifyDelete(this); + glDeleteTextures(1, &glTexId); +} + +void LatteTextureViewGL::InitAliasView() +{ + const auto texture = (LatteTextureGL*)baseTexture; + // get internal format + if (baseTexture->isDepth) + { + // depth is handled differently + forceLogDebug_printf("Creating depth view"); + cemu_assert(format == texture->format); // todo + glInternalFormat = texture->glInternalFormat; + } + else + { + LatteTextureGL::FormatInfoGL glFormatInfo; + LatteTextureGL::GetOpenGLFormatInfo(baseTexture->isDepth, format, dim, &glFormatInfo); + glInternalFormat = glFormatInfo.glInternalFormat; + } + + catchOpenGLError(); + if (firstMip >= texture->maxPossibleMipLevels) + { + forceLogDebug_printf("_createNewView: Out of bounds mip level requested"); + glTextureView(glTexId, glTexTarget, texture->glId_texture, glInternalFormat, texture->maxPossibleMipLevels - 1, numMip, firstSlice, this->numSlice); + } + else + glTextureView(glTexId, glTexTarget, texture->glId_texture, glInternalFormat, firstMip, numMip, firstSlice, numSlice); + + catchOpenGLError(); + + if (glTextureParameteri) + { + glTextureParameteri(glTexId, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTextureParameteri(glTexId, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTextureParameteri(glTexId, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTextureParameteri(glTexId, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTextureParameteri(glTexId, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + glTextureParameteri(glTexId, GL_TEXTURE_COMPARE_MODE, GL_NONE); + } + else + { + // todo - fallback for when DSA isn't supported + } + + // set debug name + bool useGLDebugNames = false; +#ifndef PUBLIC_RELEASE + useGLDebugNames = true; +#endif + if (LaunchSettings::NSightModeEnabled()) + useGLDebugNames = true; + if (useGLDebugNames) + { + char textureDebugLabel[512]; + sprintf(textureDebugLabel, "%08x_f%04x_p%04x_viewFMT%04x%s_org%d", baseTexture->physAddress, (uint32)baseTexture->format, baseTexture->pitch, (uint32)this->format, baseTexture->isDepth?"_d":"", texture->glId_texture); + glObjectLabel(GL_TEXTURE, glTexId, -1, textureDebugLabel); + } +} + +LatteTextureViewGL* LatteTextureViewGL::GetAlternativeView() +{ + if (!m_alternativeView) + m_alternativeView = new LatteTextureViewGL((LatteTextureGL*)baseTexture, dim, format, firstMip, numMip, firstSlice, numSlice, false, true); + return m_alternativeView; +} \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.h b/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.h new file mode 100644 index 00000000..df74e73b --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.h @@ -0,0 +1,28 @@ +#pragma once + +#include "Cafe/HW/Latte/Core/LatteTexture.h" +#include "Common/GLInclude/GLInclude.h" + +class LatteTextureViewGL : public LatteTextureView +{ +public: + LatteTextureViewGL(class LatteTextureGL* texture, Latte::E_DIM dim, Latte::E_GX2SURFFMT format, sint32 firstMip, sint32 mipCount, sint32 firstSlice, sint32 sliceCount, bool registerView = true, bool forceCreateNewTexId = false); + ~LatteTextureViewGL(); + + LatteTextureViewGL* GetAlternativeView(); + + // GL specific states + LatteSamplerState samplerState{}; + uint8 swizzleR; + uint8 swizzleG; + uint8 swizzleB; + uint8 swizzleA; + + GLuint glTexId; + GLint glTexTarget; + sint32 glInternalFormat; + + LatteTextureViewGL* m_alternativeView{ nullptr }; +private: + void InitAliasView(); +}; diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLQuery.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLQuery.cpp new file mode 100644 index 00000000..55e27991 --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLQuery.cpp @@ -0,0 +1,76 @@ +#include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h" +#include "Common/GLInclude/GLInclude.h" + +class LatteQueryObjectGL : public LatteQueryObject +{ + friend class OpenGLRenderer; + + bool getResult(uint64& numSamplesPassed) override; + void begin() override; + void end() override; + +private: + GLuint m_queryId{}; + GLenum m_glTarget{}; +}; + +LatteQueryObject* OpenGLRenderer::occlusionQuery_create() +{ + if (!list_queryCacheOcclusion.empty()) + { + LatteQueryObjectGL* queryObject = list_queryCacheOcclusion.front(); + list_queryCacheOcclusion.erase(list_queryCacheOcclusion.begin() + 0); + queryObject->m_glTarget = GL_SAMPLES_PASSED; + queryObject->queryEnded = false; + queryObject->queryEventStart = 0; + queryObject->queryEventEnd = 0; + return queryObject; + } + // no query object available in cache, create new query + LatteQueryObjectGL* queryObject = new LatteQueryObjectGL(); + glGenQueries(1, &queryObject->m_queryId); + queryObject->m_glTarget = GL_SAMPLES_PASSED; + queryObject->queryEnded = false; + queryObject->index = 0; + queryObject->queryEventStart = 0; + queryObject->queryEventEnd = 0; + catchOpenGLError(); + return queryObject; +} + +void OpenGLRenderer::occlusionQuery_destroy(LatteQueryObject* queryObj) +{ + list_queryCacheOcclusion.emplace_back(static_cast<LatteQueryObjectGL*>(queryObj)); +} + +void OpenGLRenderer::occlusionQuery_flush() +{ + glFlush(); +} + +bool LatteQueryObjectGL::getResult(uint64& numSamplesPassed) +{ + GLint resultAvailable = 0; + catchOpenGLError(); + glGetQueryObjectiv(this->m_queryId, GL_QUERY_RESULT_AVAILABLE, &resultAvailable); + if (resultAvailable == 0) + return false; + catchOpenGLError(); + GLint64 queryResult = 0; + glGetQueryObjecti64v(this->m_queryId, GL_QUERY_RESULT, &queryResult); + numSamplesPassed = queryResult; + return true; +} + +void LatteQueryObjectGL::begin() +{ + catchOpenGLError(); + glBeginQueryIndexed(this->m_glTarget, this->index, this->m_queryId); + catchOpenGLError(); +} + +void LatteQueryObjectGL::end() +{ + glEndQueryIndexed(this->m_glTarget, this->index); + catchOpenGLError(); +} \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp new file mode 100644 index 00000000..d89cbcac --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp @@ -0,0 +1,2036 @@ +#include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h" +#include "gui/guiWrapper.h" + +#include "Cafe/HW/Latte/Core/LatteRingBuffer.h" +#include "Cafe/HW/Latte/Core/LatteDraw.h" +#include "Cafe/HW/Latte/Core/LatteOverlay.h" + +#include "Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.h" +#include "Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.h" +#include "Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.h" +#include "Cafe/HW/Latte/Renderer/OpenGL/CachedFBOGL.h" +#include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLTextureReadback.h" + +#include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h" + +#include "imgui/imgui_impl_opengl3.h" +#include "imgui/imgui_extension.h" + +#include "Cafe/HW/Latte/ISA/RegDefines.h" +#include "Cafe/OS/libs/gx2/GX2.h" + +#include "gui/canvas/OpenGLCanvas.h" + +#define STRINGIFY2(X) #X +#define STRINGIFY(X) STRINGIFY2(X) + +#define GLFUNC(__type, __name) __type __name; +#include "Common/GLInclude/glFunctions.h" +#undef GLFUNC + +#include "config/ActiveSettings.h" +#include "config/LaunchSettings.h" + +static const int TEXBUFFER_SIZE = 1024 * 1024 * 32; // 32MB + +struct +{ + // options + bool useTextureUploadBuffer; + // texture upload + GLuint uploadBuffer; + sint32 uploadIndex; + void* uploadBufferPtr; + LatteRingBuffer_t* uploadRingBuffer; + // current texture work buffer (subrange of uploadRingBuffer) + uint8* texWorkBuffer; + sint32 texWorkBufferSize; + // texture upload buffer (when not using persistent buffer) + std::vector<uint8> texUploadBuffer; + // FBO for fast clearing (on Nvidia or if glClearTexSubImage is not supported) + GLuint clearFBO; +}glRendererState; + +static const GLenum glDepthFuncTable[] = +{ + GL_NEVER, + GL_LESS, + GL_EQUAL, + GL_LEQUAL, + GL_GREATER, + GL_NOTEQUAL, + GL_GEQUAL, + GL_ALWAYS +}; + +static const GLenum glAlphaTestFunc[] = +{ + GL_NEVER, + GL_LESS, + GL_EQUAL, + GL_LEQUAL, + GL_GREATER, + GL_NOTEQUAL, + GL_GEQUAL, + GL_ALWAYS +}; + + + +OpenGLRenderer::OpenGLRenderer() +{ + glRendererState.useTextureUploadBuffer = false; + if (glRendererState.useTextureUploadBuffer) + { + glCreateBuffers(1, &glRendererState.uploadBuffer); + glNamedBufferStorage(glRendererState.uploadBuffer, TEXBUFFER_SIZE, nullptr, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT); + void* buffer = glMapNamedBufferRange(glRendererState.uploadBuffer, 0, TEXBUFFER_SIZE, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_FLUSH_EXPLICIT_BIT); + if (buffer == nullptr) + { + forceLog_printf("Failed to allocate GL texture upload buffer. Using traditional API instead"); + cemu_assert_debug(false); + } + glRendererState.uploadBufferPtr = buffer; + glRendererState.uploadRingBuffer = LatteRingBuffer_create((uint8*)buffer, TEXBUFFER_SIZE); + glRendererState.uploadIndex = 0; + } + +#if BOOST_OS_WINDOWS > 0 + try + { + m_dxgi_wrapper = std::make_unique<DXGIWrapper>(); + } + catch (const std::exception& ex) + { + forceLog_printf("Unable to create dxgi wrapper: %s (VRAM overlay stat won't be available)", ex.what()); + } +#endif +} + +OpenGLRenderer::~OpenGLRenderer() +{ + if(m_pipeline != 0) + glDeleteProgramPipelines(1, &m_pipeline); +} + +OpenGLRenderer* OpenGLRenderer::GetInstance() +{ + cemu_assert_debug(g_renderer && dynamic_cast<OpenGLRenderer*>(g_renderer.get())); + return (OpenGLRenderer*)g_renderer.get(); +} + +bool OpenGLRenderer::ImguiBegin(bool mainWindow) +{ + if (!mainWindow) + { + GLCanvas_MakeCurrent(true); + m_isPadViewContext = true; + } + + if(!Renderer::ImguiBegin(mainWindow)) + return false; + + renderstate_resetColorControl(); + renderstate_resetDepthControl(); + renderstate_resetStencilMask(); + + if (glClipControl) + glClipControl(GL_LOWER_LEFT, GL_NEGATIVE_ONE_TO_ONE); + + ImGui_ImplOpenGL3_NewFrame(); + ImGui_UpdateWindowInformation(mainWindow); + ImGui::NewFrame(); + return true; +} + +void OpenGLRenderer::ImguiEnd() +{ + ImGui::Render(); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + + if (m_isPadViewContext) + { + GLCanvas_MakeCurrent(false); + m_isPadViewContext = false; + } + + if (glClipControl) + glClipControl(GL_UPPER_LEFT, GL_NEGATIVE_ONE_TO_ONE); +} + +ImTextureID OpenGLRenderer::GenerateTexture(const std::vector<uint8>& data, const Vector2i& size) +{ + GLuint textureId; + glGenTextures(1, &textureId); + + glBindTexture(GL_TEXTURE_2D, textureId); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + + glActiveTexture(GL_TEXTURE0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB, size.x, size.y, 0, GL_RGB, GL_UNSIGNED_BYTE, data.data()); + return (ImTextureID)(uintptr_t)textureId; +} + +void OpenGLRenderer::DeleteTexture(ImTextureID id) +{ + if (id) + { + GLuint textureId = (GLuint)(uintptr_t)id; + glDeleteTextures(1, &textureId); + } +} + +void OpenGLRenderer::DeleteFontTextures() +{ + ImGui_ImplOpenGL3_DestroyFontsTexture(); +} + +typedef void(*GL_IMPORT)(); + +#if BOOST_OS_WINDOWS > 0 +GL_IMPORT _GetOpenGLFunction(HMODULE hLib, const char* name) +{ + GL_IMPORT r = (GL_IMPORT)wglGetProcAddress(name); + if (r == nullptr) + r = (GL_IMPORT)GetProcAddress(hLib, name); + return r; +} + +void LoadOpenGLImports() +{ + HMODULE hLib = LoadLibraryA("opengl32.dll"); +#define GLFUNC(__type, __name) __name = (__type)_GetOpenGLFunction(hLib, STRINGIFY(__name)); +#include "Common/GLInclude/glFunctions.h" +#undef GLFUNC +} +#else +GL_IMPORT _GetOpenGLFunction(void* hLib, PFNGLXGETPROCADDRESSPROC func, const char* name) +{ + GL_IMPORT r = (GL_IMPORT)func((const GLubyte*)name); + return r; +} + +#include <dlfcn.h> +// #define RTLD_NOW 0x00002 +// #define RTLD_GLOBAL 0x00100 + +void LoadOpenGLImports() +{ + PFNGLXGETPROCADDRESSPROC _glXGetProcAddress = nullptr; + void* libGL = dlopen("libGL.so.1", RTLD_NOW | RTLD_GLOBAL); + _glXGetProcAddress = (PFNGLXGETPROCADDRESSPROC)dlsym(libGL, "glXGetProcAddressARB"); + if(!_glXGetProcAddress) + { + libGL = dlopen("libGL.so", RTLD_NOW | RTLD_GLOBAL); + _glXGetProcAddress = (PFNGLXGETPROCADDRESSPROC)dlsym(libGL, "glXGetProcAddressARB"); + } + +#define GLFUNC(__type, __name) __name = (__type)_GetOpenGLFunction(libGL, _glXGetProcAddress, STRINGIFY(__name)); +#include "Common/GLInclude/glFunctions.h" +#undef GLFUNC +} +#endif + +void OpenGLRenderer::Initialize() +{ + auto lock = cafeLog_acquire(); + forceLog_printf("------- Init OpenGL graphics backend -------"); + + GLCanvas_MakeCurrent(false); + LoadOpenGLImports(); + GetVendorInformation(); + +#if BOOST_OS_WINDOWS > 0 + if (wglSwapIntervalEXT) + wglSwapIntervalEXT(0); // disable V-Sync per default +#endif + + if (glMaxShaderCompilerThreadsARB) + glMaxShaderCompilerThreadsARB(0xFFFFFFFF); + + forceLog_printf("OpenGL extensions:"); + forceLog_printf("ARB_clip_control: %s", glClipControl ? "available" : "not supported"); + forceLog_printf("ARB_get_program_binary: %s", (glGetProgramBinary != NULL && glProgramBinary != NULL) ? "available" : "not supported"); + forceLog_printf("ARB_clear_texture: %s", (glClearTexImage != NULL) ? "available" : "not supported"); + forceLog_printf("ARB_copy_image: %s", (glCopyImageSubData != NULL) ? "available" : "not supported"); + forceLog_printf("NV_depth_buffer_float: %s", (glDepthRangedNV != NULL) ? "available" : "not supported"); + + // generate default frame buffer + glGenFramebuffers(1, &m_defaultFramebufferId); + catchOpenGLError(); + + // enable framebuffer SRGB support + glEnable(GL_FRAMEBUFFER_SRGB); + + if (this->m_vendor != GfxVendor::AMD) + { + // don't enable this for AMD because GL_PROGRAM_POINT_SIZE breaks shader binaries for some reason + // it also seems like AMD ignores GL_POINT_SPRITE and gl_PointCoord is always 0.0/0.0 + + // point size is always defined via shader + glEnable(GL_PROGRAM_POINT_SIZE); + + // since we are using compatibility profile point sprites are disabled by default (e.g. gl_PointCoord wont work) + glEnable(GL_POINT_SPRITE); + } + + // check if clip control is available + if (glClipControl) + { + glClipControl(GL_UPPER_LEFT, GL_NEGATIVE_ONE_TO_ONE); + } + else + { + cemuLog_log(LogType::Force, "ARB_CLIP_CONTROL not supported by graphics driver. This will lead to crashes or graphical artifacts."); + } + + // set pixel unpack alignment to 1 byte + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + // on NVIDIA rendering to SNORM textures is clamped to [0.0,1.0] range for non-core profiles + // we can still disable the clamping using an older function (note that AMD and Intel don't need this, which is technically incorrect according to the compatibility profile spec) + if (m_vendor == GfxVendor::Nvidia) + glClampColor(GL_CLAMP_FRAGMENT_COLOR, GL_FALSE); + + + glEnable(GL_PRIMITIVE_RESTART); + glPrimitiveRestartIndex(0xFFFFFFFF); + + glGenProgramPipelines(1, &m_pipeline); + glBindProgramPipeline(m_pipeline); + + lock.unlock(); + + // create framebuffer for fast clearing (avoid glClearTexSubImage on Nvidia) + if (this->m_vendor == GfxVendor::Nvidia || glClearTexSubImage == nullptr) + { + // generate framebuffer + if (glCreateFramebuffers && false) + glCreateFramebuffers(1, &glRendererState.clearFBO); + else + glGenFramebuffers(1, &glRendererState.clearFBO); + } + + draw_init(); + + catchOpenGLError(); + + glGenBuffers(1, &glStreamoutCacheRingBuffer); + glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, glStreamoutCacheRingBuffer); + glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, LatteStreamout_GetRingBufferSize(), NULL, GL_DYNAMIC_DRAW); + glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, 0); + + catchOpenGLError(); + + // imgui + ImGui_ImplOpenGL3_Init("#version 130"); +} + +bool OpenGLRenderer::IsPadWindowActive() +{ + return GLCanvas_HasPadViewOpen(); +} + +void OpenGLRenderer::Flush(bool waitIdle) +{ + glFlush(); + if (waitIdle) + glFinish(); +} + +void OpenGLRenderer::NotifyLatteCommandProcessorIdle() +{ + glFlush(); +} + +void OpenGLRenderer::EnableVSync(int state) +{ +#if BOOST_OS_WINDOWS > 0 + if(wglSwapIntervalEXT) + wglSwapIntervalEXT(state); // 1 = enabled, 0 = disabled +#else + cemuLog_log(LogType::Force, "OpenGL vsync not implemented"); +#endif +} + +bool IsRunningInWine(); + +void OpenGLRenderer::GetVendorInformation() +{ + // example vendor strings: + // ATI Technologies Inc. + // NVIDIA Corporation + // Intel + char* glVendorString = (char*)glGetString(GL_VENDOR); + char* glRendererString = (char*)glGetString(GL_RENDERER); + char* glVersionString = (char*)glGetString(GL_VERSION); + + forceLog_printf("GL_VENDOR: %s", glVendorString ? glVendorString : "unknown"); + forceLog_printf("GL_RENDERER: %s", glRendererString ? glRendererString : "unknown"); + forceLog_printf("GL_VERSION: %s", glVersionString ? glVersionString : "unknown"); + + if(boost::icontains(glVersionString, "Mesa") || IsRunningInWine()) + { + m_vendor = GfxVendor::Mesa; + return; + } + + if (glVendorString) + { + if ((toupper(glVendorString[0]) == 'A' && toupper(glVendorString[1]) == 'T' && toupper(glVendorString[2]) == 'I') || + (toupper(glVendorString[0]) == 'A' && toupper(glVendorString[1]) == 'M' && toupper(glVendorString[2]) == 'D')) + { + m_vendor = GfxVendor::AMD; + return; + } + else if (memcmp(glVendorString, "NVIDIA", 6) == 0) + { + m_vendor = GfxVendor::Nvidia; + return; + } + else if (memcmp(glVendorString, "Intel", 5) == 0) + { + if (LaunchSettings::ForceIntelLegacyEnabled()) + m_vendor = GfxVendor::IntelLegacy; + else + m_vendor = GfxVendor::IntelNoLegacy; + return; + } + } + + m_vendor = GfxVendor::Generic; +} + +void _glDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam) +{ + if (LatteGPUState.glVendor == GLVENDOR_NVIDIA && strstr(message, "Buffer")) + return; + if (LatteGPUState.glVendor == GLVENDOR_NVIDIA && strstr(message, "performance warning")) + return; + if (LatteGPUState.glVendor == GLVENDOR_NVIDIA && strstr(message, "Dithering is enabled")) + return; + + if (LatteGPUState.glVendor == GLVENDOR_NVIDIA && strstr(message, "does not have a defined base level")) + return; + + forceLog_printf("GLDEBUG: %s", message); + + cemu_assert_debug(false); +} + +void OpenGLRenderer::EnableDebugMode() +{ + glEnable(GL_DEBUG_OUTPUT); + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); + glDebugMessageCallback(_glDebugCallback, NULL); + glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, NULL, true); +} + +void OpenGLRenderer::SwapBuffers(bool swapTV, bool swapDRC) +{ + GLCanvas_SwapBuffers(swapTV, swapDRC); + + if (swapTV) + cleanupAfterFrame(); +} + +bool OpenGLRenderer::BeginFrame(bool mainWindow) +{ + if (!mainWindow && !IsPadWindowActive()) + return false; + + GLCanvas_MakeCurrent(!mainWindow); + + ClearColorbuffer(!mainWindow); + return true; +} + +void OpenGLRenderer::DrawEmptyFrame(bool mainWindow) +{ + if (!BeginFrame(mainWindow)) + return; + + SwapBuffers(mainWindow, !mainWindow); +} + +void OpenGLRenderer::ClearColorbuffer(bool padView) +{ + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + glClear(GL_COLOR_BUFFER_BIT); +} + + +void OpenGLRenderer::HandleScreenshotRequest(LatteTextureView* texView, bool padView) +{ + const bool hasScreenshotRequest = gui_hasScreenshotRequest(); + if(!hasScreenshotRequest && m_screenshot_state == ScreenshotState::None) + return; + + if (IsPadWindowActive()) + { + // we already took a pad view screenshow and want a main window screenshot + if (m_screenshot_state == ScreenshotState::Main && padView) + return; + + if (m_screenshot_state == ScreenshotState::Pad && !padView) + return; + + // remember which screenshot is left to take + if (m_screenshot_state == ScreenshotState::None) + m_screenshot_state = padView ? ScreenshotState::Main : ScreenshotState::Pad; + else + m_screenshot_state = ScreenshotState::None; + } + else + m_screenshot_state = ScreenshotState::None; + + int screenshotWidth, screenshotHeight; + glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); + texture_bindAndActivate(texView, 0); + glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &screenshotWidth); + glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &screenshotHeight); + glPixelStorei(GL_PACK_ALIGNMENT, 1); // set alignment to 1 + + const sint32 pixelDataSize = screenshotWidth * screenshotHeight * 3; + std::vector<uint8> rgb_data(pixelDataSize); + + glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_UNSIGNED_BYTE, rgb_data.data()); + texture_bindAndActivate(nullptr, 0); + + const bool srcUsesSRGB = HAS_FLAG(texView->format, Latte::E_GX2SURFFMT::FMT_BIT_SRGB); + const bool dstUsesSRGB = (!padView && LatteGPUState.tvBufferUsesSRGB) || (padView && LatteGPUState.drcBufferUsesSRGB); + if((srcUsesSRGB && !dstUsesSRGB) || (!srcUsesSRGB && dstUsesSRGB)) + { + for (sint32 iy = 0; iy < screenshotHeight; ++iy) + { + for (sint32 ix = 0; ix < screenshotWidth; ++ix) + { + uint8* pData = rgb_data.data() + (ix + iy * screenshotWidth) * 3; + if (srcUsesSRGB && !dstUsesSRGB) + { + // SRGB -> RGB + pData[0] = SRGBComponentToRGB(pData[0]); + pData[1] = SRGBComponentToRGB(pData[1]); + pData[2] = SRGBComponentToRGB(pData[2]); + } + else if (!srcUsesSRGB && dstUsesSRGB) + { + // RGB -> SRGB + pData[0] = RGBComponentToSRGB(pData[0]); + pData[1] = RGBComponentToSRGB(pData[1]); + pData[2] = RGBComponentToSRGB(pData[2]); + } + } + } + } + + SaveScreenshot(rgb_data, screenshotWidth, screenshotHeight, !padView); +} + +void OpenGLRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutputShader* shader, bool useLinearTexFilter, sint32 imageX, sint32 imageY, sint32 imageWidth, sint32 imageHeight, bool padView, bool clearBackground) +{ + if (padView && !IsPadWindowActive()) + return; + + catchOpenGLError(); + GLCanvas_MakeCurrent(padView); + + renderstate_resetColorControl(); + renderstate_resetDepthControl(); + attributeStream_reset(); + + if (clearBackground) + { + int windowWidth, windowHeight; + if (padView) + gui_getPadWindowSize(&windowWidth, &windowHeight); + else + gui_getWindowSize(&windowWidth, &windowHeight); + g_renderer->renderTarget_setViewport(0, 0, windowWidth, windowHeight, 0.0f, 1.0f); + g_renderer->ClearColorbuffer(padView); + } + + // bind back buffer + rendertarget_bindFramebufferObject(nullptr); + + // calculate effective size + sint32 effectiveWidth; + sint32 effectiveHeight; + LatteTexture_getEffectiveSize(texView->baseTexture, &effectiveWidth, &effectiveHeight, nullptr, 0); + + g_renderer->shader_unbind(RendererShader::ShaderType::kVertex); + g_renderer->shader_unbind(RendererShader::ShaderType::kGeometry); + g_renderer->shader_unbind(RendererShader::ShaderType::kFragment); + shader->Bind(); + shader->SetUniformParameters(*texView, { effectiveWidth, effectiveHeight }, { imageWidth, imageHeight }); + + // set viewport + glViewportIndexedf(0, imageX, imageY, imageWidth, imageHeight); + + LatteTextureViewGL* texViewGL = (LatteTextureViewGL*)texView; + g_renderer->texture_bindAndActivate(texView, 0); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, useLinearTexFilter ? GL_LINEAR : GL_NEAREST); + texViewGL->samplerState.filterMag = 0xFFFFFFFF; + + if ((!padView && !LatteGPUState.tvBufferUsesSRGB) || (padView && !LatteGPUState.drcBufferUsesSRGB)) + glDisable(GL_FRAMEBUFFER_SRGB); + + uint16 indexData[6] = { 0,1,2,3,4,5 }; + glDrawRangeElements(GL_TRIANGLES, 0, 5, 6, GL_UNSIGNED_SHORT, indexData); + + if ((!padView && !LatteGPUState.tvBufferUsesSRGB) || (padView && !LatteGPUState.drcBufferUsesSRGB)) + glEnable(GL_FRAMEBUFFER_SRGB); + + // unbind texture + g_renderer->texture_bindAndActivate(nullptr, 0); + + catchOpenGLError(); + + // restore viewport + glViewportIndexedf(0, prevViewportX, prevViewportY, prevViewportWidth, prevViewportHeight); + + // switch back to TV context + if (padView) + GLCanvas_MakeCurrent(false); +} + +void OpenGLRenderer::renderTarget_setViewport(float x, float y, float width, float height, float nearZ, float farZ, bool halfZ /*= false*/) +{ + if (prevNearZ != nearZ || prevFarZ != farZ || _prevHalfZ != halfZ) + { + if (*(uint32*)&farZ == 0x3f7fffff) + *(uint32*)&farZ = 0x3f800000; + if (glDepthRangedNV) + glDepthRangedNV(nearZ, farZ); + else + glDepthRange(nearZ, farZ); + prevNearZ = nearZ; + prevFarZ = farZ; + } + + bool invertY = false; + if (height < 0.0) + { + invertY = true; + y += height; + height = -height; + } + + if (glClipControl && (_prevInvertY != invertY || _prevHalfZ != halfZ)) + { + GLenum clipDepth = halfZ ? GL_ZERO_TO_ONE : GL_NEGATIVE_ONE_TO_ONE; + if (invertY) + glClipControl(GL_LOWER_LEFT, clipDepth); // OpenGL style + else + glClipControl(GL_UPPER_LEFT, clipDepth); // DX style (default for GX2) + _prevInvertY = invertY; + _prevHalfZ = halfZ; + } + + if (prevViewportX == x && prevViewportY == y && prevViewportWidth == width && prevViewportHeight == height) + return; // viewport did not change + glViewportIndexedf(0, x, y, width, height); + prevViewportX = x; + prevViewportY = y; + prevViewportWidth = width; + prevViewportHeight = height; +} + +void OpenGLRenderer::renderTarget_setScissor(sint32 scissorX, sint32 scissorY, sint32 scissorWidth, sint32 scissorHeight) +{ + if (prevScissorEnable != true) + { + // enable scissor box + glEnable(GL_SCISSOR_TEST); + prevScissorEnable = true; + } + glScissor(scissorX, scissorY, scissorWidth, scissorHeight); +} + +LatteCachedFBO* OpenGLRenderer::rendertarget_createCachedFBO(uint64 key) +{ + return new CachedFBOGL(key); +} + +void OpenGLRenderer::rendertarget_deleteCachedFBO(LatteCachedFBO* cfbo) +{ + auto cfboGL = (CachedFBOGL*)cfbo; + if (prevBoundFBO == cfboGL->glId_fbo) + prevBoundFBO = -1; + glDeleteFramebuffers(1, &cfboGL->glId_fbo); +} + +// set active FBO +void OpenGLRenderer::rendertarget_bindFramebufferObject(LatteCachedFBO* cfbo) +{ + GLuint fboid; + if (cfbo) + { + const auto cfboGL = (CachedFBOGL*)cfbo; + fboid = cfboGL->glId_fbo; + } + else + fboid = 0; + + if (prevBoundFBO != fboid) + { + glBindFramebuffer(GL_FRAMEBUFFER_EXT, fboid); + prevBoundFBO = fboid; + } +} + +void OpenGLRenderer::renderstate_setChannelTargetMask(uint32 renderTargetMask) +{ + if (renderTargetMask != prevTargetColorMask) + { + for (sint32 i = 0; i < 8; i++) + { + uint32 targetRGBAMask = ((renderTargetMask >> (i * 4)) & 0xF); + if (targetRGBAMask != ((prevTargetColorMask >> (i * 4)) & 0xF)) + { + // update color mask + glColorMaski(i, (targetRGBAMask & 1) ? GL_TRUE : GL_FALSE, (targetRGBAMask & 2) ? GL_TRUE : GL_FALSE, (targetRGBAMask & 4) ? GL_TRUE : GL_FALSE, (targetRGBAMask & 8) ? GL_TRUE : GL_FALSE); + } + } + prevTargetColorMask = renderTargetMask; + } +} + +void OpenGLRenderer::renderstate_setAlwaysWriteDepth() +{ + if (prevDepthEnable == 0) + { + glEnable(GL_DEPTH_TEST); + prevDepthEnable = 1; + } + glDepthFunc(GL_ALWAYS); + prevDepthFunc = Latte::LATTE_DB_DEPTH_CONTROL::E_ZFUNC::ALWAYS; +} + +static const GLuint table_glBlendSrcDst[] = +{ + /* 0x00 */ GL_ZERO, + /* 0x01 */ GL_ONE, + /* 0x02 */ GL_SRC_COLOR, + /* 0x03 */ GL_ONE_MINUS_SRC_COLOR, + /* 0x04 */ GL_SRC_ALPHA, + /* 0x05 */ GL_ONE_MINUS_SRC_ALPHA, + /* 0x06 */ GL_DST_ALPHA, + /* 0x07 */ GL_ONE_MINUS_DST_ALPHA, + /* 0x08 */ GL_DST_COLOR, + /* 0x09 */ GL_ONE_MINUS_DST_COLOR, + /* 0x0A */ GL_SRC_ALPHA_SATURATE, + /* 0x0B */ 0xFFFFFFFF, + /* 0x0C */ 0xFFFFFFFF, + /* 0x0D */ GL_CONSTANT_COLOR, + /* 0x0E */ GL_ONE_MINUS_CONSTANT_COLOR, + /* 0x0F */ GL_SRC1_COLOR, + /* 0x10 */ GL_ONE_MINUS_SRC1_COLOR, + /* 0x11 */ GL_SRC1_ALPHA, + /* 0x12 */ GL_ONE_MINUS_SRC1_ALPHA, + /* 0x13 */ GL_CONSTANT_ALPHA, + /* 0x14 */ GL_ONE_MINUS_CONSTANT_ALPHA +}; + +static GLuint GetGLBlendFactor(Latte::LATTE_CB_BLENDN_CONTROL::E_BLENDFACTOR blendFactor) +{ + uint32 blendFactorU = (uint32)blendFactor; + if (blendFactorU >= 0xF && blendFactorU <= 0x12) + { + debug_printf("Unsupported dual-source blending used\n"); + cemu_assert_debug(false); // dual-source blending + return GL_ZERO; + } + if (blendFactorU >= (sizeof(table_glBlendSrcDst) / sizeof(table_glBlendSrcDst[0]))) + { + debug_printf("GetGLBlendFactor: Constant 0x%x out of range\n", blendFactor); + return GL_ZERO; + } + if (table_glBlendSrcDst[blendFactorU] == -1) + { + debug_printf("GetGLBlendFactor: Constant 0x%x is invalid\n", blendFactor); + cemu_assert_debug(false); + return GL_ZERO; + } + return table_glBlendSrcDst[blendFactorU]; +} + +static const GLuint table_glBlendCombine[] = +{ + GL_FUNC_ADD, + GL_FUNC_SUBTRACT, + GL_MIN, + GL_MAX, + GL_FUNC_REVERSE_SUBTRACT +}; + +GLuint GetGLBlendCombineFunc(Latte::LATTE_CB_BLENDN_CONTROL::E_COMBINEFUNC combineFunc) +{ + uint32 combineFuncU = (uint32)combineFunc; + if (combineFuncU >= (sizeof(table_glBlendCombine) / sizeof(table_glBlendCombine[0]))) + { + cemu_assert_suspicious(); + return GL_FUNC_ADD; + } + return table_glBlendCombine[combineFuncU]; +} + + +void* OpenGLRenderer::texture_acquireTextureUploadBuffer(uint32 size) +{ + if (glRendererState.useTextureUploadBuffer) + { + glRendererState.texWorkBuffer = LatteRingBuffer_allocate(glRendererState.uploadRingBuffer, size, 1024); + glRendererState.texWorkBufferSize = size; + return glRendererState.texWorkBuffer; + } + // static memory buffer + if (glRendererState.texUploadBuffer.size() < size) + { + glRendererState.texUploadBuffer.resize(size); + } + return glRendererState.texUploadBuffer.data(); +} + +void OpenGLRenderer::texture_releaseTextureUploadBuffer(uint8* mem) +{ + // do nothing +} + +TextureDecoder* OpenGLRenderer::texture_chooseDecodedFormat(Latte::E_GX2SURFFMT format, bool isDepth, Latte::E_DIM dim, uint32 width, uint32 height) +{ + TextureDecoder* texDecoder = nullptr; + if (isDepth) + { + if (format == Latte::E_GX2SURFFMT::R32_FLOAT) + { + return TextureDecoder_R32_FLOAT::getInstance(); + } + if (format == Latte::E_GX2SURFFMT::D24_S8_UNORM) + { + return TextureDecoder_D24_S8::getInstance(); + } + else if (format == Latte::E_GX2SURFFMT::D24_S8_FLOAT) + { + return TextureDecoder_NullData64::getInstance(); + } + else if (format == Latte::E_GX2SURFFMT::D32_S8_FLOAT) + { + return TextureDecoder_D32_S8_UINT_X24::getInstance(); + } + else if (format == Latte::E_GX2SURFFMT::R32_FLOAT) + { + return TextureDecoder_R32_FLOAT::getInstance(); + } + else if (format == Latte::E_GX2SURFFMT::R16_UNORM) + { + return TextureDecoder_R16_FLOAT::getInstance(); + } + return nullptr; + } + + if (LatteGPUState.glVendor == GLVENDOR_INTEL_LEGACY) + { + if (format == Latte::E_GX2SURFFMT::BC1_UNORM) + { + texDecoder = TextureDecoder_BC1_UNORM_uncompress::getInstance(); + } + else if (format == Latte::E_GX2SURFFMT::BC1_SRGB) + { + texDecoder = TextureDecoder_BC1_SRGB_uncompress::getInstance(); + } + else if (format == Latte::E_GX2SURFFMT::BC3_UNORM) + { + texDecoder = TextureDecoder_BC3_UNORM_uncompress::getInstance(); + } + else if (format == Latte::E_GX2SURFFMT::BC3_SRGB) + { + texDecoder = TextureDecoder_BC3_SRGB_uncompress::getInstance(); + } + else if (format == Latte::E_GX2SURFFMT::BC4_UNORM) + { + texDecoder = TextureDecoder_BC4_UNORM_uncompress::getInstance(); + } + else if (format == Latte::E_GX2SURFFMT::BC4_SNORM) + { + cemu_assert_debug(false); // todo + } + else if (format == Latte::E_GX2SURFFMT::BC5_UNORM) + { + texDecoder = TextureDecoder_BC5_UNORM_uncompress::getInstance(); + } + else if (format == Latte::E_GX2SURFFMT::BC5_SNORM) + { + texDecoder = TextureDecoder_BC5_SNORM_uncompress::getInstance(); + } + if (texDecoder) + return texDecoder; + } + + if (format == Latte::E_GX2SURFFMT::R4_G4_UNORM) + texDecoder = TextureDecoder_R4_G4_UNORM_toRGBA4444::getInstance(); + else if (format == Latte::E_GX2SURFFMT::R4_G4_B4_A4_UNORM) + texDecoder = TextureDecoder_R4_G4_B4_A4_UNORM::getInstance(); + else if (format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT) + texDecoder = TextureDecoder_R16_G16_B16_A16_FLOAT::getInstance(); + else if (format == Latte::E_GX2SURFFMT::R16_G16_FLOAT) + texDecoder = TextureDecoder_R16_G16_FLOAT::getInstance(); + else if (format == Latte::E_GX2SURFFMT::R16_SNORM) + texDecoder = TextureDecoder_R16_SNORM::getInstance(); + else if (format == Latte::E_GX2SURFFMT::R16_FLOAT) + texDecoder = TextureDecoder_R16_FLOAT::getInstance(); + else if (format == Latte::E_GX2SURFFMT::R32_FLOAT) + texDecoder = TextureDecoder_R32_FLOAT::getInstance(); + else if (format == Latte::E_GX2SURFFMT::BC1_UNORM) + texDecoder = TextureDecoder_BC1::getInstance(); + else if (format == Latte::E_GX2SURFFMT::BC1_SRGB) + texDecoder = TextureDecoder_BC1::getInstance(); + else if (format == Latte::E_GX2SURFFMT::BC2_UNORM) + texDecoder = TextureDecoder_BC2_UNORM_uncompress::getInstance(); + else if (format == Latte::E_GX2SURFFMT::BC2_SRGB) + texDecoder = TextureDecoder_BC2_SRGB_uncompress::getInstance(); + else if (format == Latte::E_GX2SURFFMT::BC3_UNORM) + texDecoder = TextureDecoder_BC3::getInstance(); + else if (format == Latte::E_GX2SURFFMT::BC3_SRGB) + texDecoder = TextureDecoder_BC3::getInstance(); + else if (format == Latte::E_GX2SURFFMT::BC4_UNORM) + { + if (dim != Latte::E_DIM::DIM_2D && dim != Latte::E_DIM::DIM_2D_ARRAY) + texDecoder = TextureDecoder_BC4_UNORM_uncompress::getInstance(); + else + texDecoder = TextureDecoder_BC4::getInstance(); + } + else if (format == Latte::E_GX2SURFFMT::BC4_SNORM) + { + if (dim != Latte::E_DIM::DIM_2D && dim != Latte::E_DIM::DIM_2D_ARRAY) + texDecoder = TextureDecoder_BC4::getInstance(); + else + texDecoder = TextureDecoder_BC4_UNORM_uncompress::getInstance(); + } + else if (format == Latte::E_GX2SURFFMT::BC5_UNORM) + texDecoder = TextureDecoder_BC5::getInstance(); + else if (format == Latte::E_GX2SURFFMT::BC5_SNORM) + texDecoder = TextureDecoder_BC5::getInstance(); + else if (format == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM) + texDecoder = TextureDecoder_R8_G8_B8_A8::getInstance(); + else if (format == Latte::E_GX2SURFFMT::R8_G8_B8_A8_SNORM) + texDecoder = TextureDecoder_R8_G8_B8_A8::getInstance(); + else if (format == Latte::E_GX2SURFFMT::R8_G8_B8_A8_SRGB) + texDecoder = TextureDecoder_R8_G8_B8_A8::getInstance(); + else if (format == Latte::E_GX2SURFFMT::R8_UNORM) + texDecoder = TextureDecoder_R8::getInstance(); + else if (format == Latte::E_GX2SURFFMT::R8_SNORM) + texDecoder = TextureDecoder_R8::getInstance(); + else if (format == Latte::E_GX2SURFFMT::R8_G8_UNORM) + texDecoder = TextureDecoder_R8_G8::getInstance(); + else if (format == Latte::E_GX2SURFFMT::R8_G8_SNORM) + texDecoder = TextureDecoder_R8_G8::getInstance(); + else if (format == Latte::E_GX2SURFFMT::R16_UNORM) + texDecoder = TextureDecoder_R16_UNORM::getInstance(); + else if (format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_UNORM) + texDecoder = TextureDecoder_R16_G16_B16_A16::getInstance(); + else if (format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_SNORM) + texDecoder = TextureDecoder_R16_G16_B16_A16::getInstance(); + else if (format == Latte::E_GX2SURFFMT::R16_G16_UNORM) + texDecoder = TextureDecoder_R16_G16::getInstance(); + else if (format == Latte::E_GX2SURFFMT::R5_G6_B5_UNORM) + texDecoder = TextureDecoder_R5_G6_B5::getInstance(); + else if (format == Latte::E_GX2SURFFMT::R5_G5_B5_A1_UNORM) + texDecoder = TextureDecoder_R5_G5_B5_A1_UNORM_swappedOpenGL::getInstance(); + else if (format == Latte::E_GX2SURFFMT::A1_B5_G5_R5_UNORM) + texDecoder = TextureDecoder_A1_B5_G5_R5_UNORM::getInstance(); + else if (format == Latte::E_GX2SURFFMT::R32_G32_FLOAT) + texDecoder = TextureDecoder_R32_G32_FLOAT::getInstance(); + else if (format == Latte::E_GX2SURFFMT::R32_G32_UINT) + texDecoder = TextureDecoder_R32_G32_UINT::getInstance(); + else if (format == Latte::E_GX2SURFFMT::R32_UINT) + texDecoder = TextureDecoder_R32_UINT::getInstance(); + else if (format == Latte::E_GX2SURFFMT::R16_UINT) + texDecoder = TextureDecoder_R16_UINT::getInstance(); + else if (format == Latte::E_GX2SURFFMT::R8_UINT) + texDecoder = TextureDecoder_R8_UINT::getInstance(); + else if (format == Latte::E_GX2SURFFMT::R32_G32_B32_A32_FLOAT) + texDecoder = TextureDecoder_R32_G32_B32_A32_FLOAT::getInstance(); + else if (format == Latte::E_GX2SURFFMT::R10_G10_B10_A2_UNORM) + texDecoder = TextureDecoder_R10_G10_B10_A2_UNORM::getInstance(); + else if (format == Latte::E_GX2SURFFMT::A2_B10_G10_R10_UNORM) + texDecoder = TextureDecoder_A2_B10_G10_R10_UNORM_toRGBA16::getInstance(); + else if (format == Latte::E_GX2SURFFMT::R10_G10_B10_A2_SNORM) + texDecoder = TextureDecoder_R10_G10_B10_A2_SNORM_toRGBA16::getInstance(); + else if (format == Latte::E_GX2SURFFMT::R10_G10_B10_A2_SRGB) + texDecoder = TextureDecoder_R10_G10_B10_A2_UNORM::getInstance(); + else if (format == Latte::E_GX2SURFFMT::R11_G11_B10_FLOAT) + texDecoder = TextureDecoder_R11_G11_B10_FLOAT::getInstance(); + else if (format == Latte::E_GX2SURFFMT::R32_G32_B32_A32_UINT) + texDecoder = TextureDecoder_R32_G32_B32_A32_UINT::getInstance(); + else if (format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_UINT) + texDecoder = TextureDecoder_R16_G16_B16_A16_UINT::getInstance(); + else if (format == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UINT) + texDecoder = TextureDecoder_R8_G8_B8_A8_UINT::getInstance(); + else if (format == Latte::E_GX2SURFFMT::R24_X8_UNORM) + texDecoder = TextureDecoder_R24_X8::getInstance(); + else if (format == Latte::E_GX2SURFFMT::X24_G8_UINT) + texDecoder = TextureDecoder_X24_G8_UINT::getInstance(); + else if (format == Latte::E_GX2SURFFMT::D32_S8_FLOAT) + texDecoder = TextureDecoder_D32_S8_UINT_X24::getInstance(); + else + cemu_assert_debug(false); + + cemu_assert_debug(!isDepth); + + return texDecoder; +} + +void OpenGLRenderer::texture_destroy(LatteTexture* hostTexture) +{ + delete hostTexture; +} + +void OpenGLRenderer::texture_reserveTextureOnGPU(LatteTexture* hostTexture) +{ + cemu_assert_debug(hostTexture->isDataDefined == false); + sint32 effectiveBaseWidth = hostTexture->width; + sint32 effectiveBaseHeight = hostTexture->height; + sint32 effectiveBaseDepth = hostTexture->depth; + if (hostTexture->overwriteInfo.hasResolutionOverwrite) + { + effectiveBaseWidth = hostTexture->overwriteInfo.width; + effectiveBaseHeight = hostTexture->overwriteInfo.height; + effectiveBaseDepth = hostTexture->overwriteInfo.depth; + } + // get format info + LatteTextureGL::FormatInfoGL glFormatInfo; + LatteTextureGL::GetOpenGLFormatInfo(hostTexture->isDepth, hostTexture->overwriteInfo.hasFormatOverwrite ? (Latte::E_GX2SURFFMT)hostTexture->overwriteInfo.format : hostTexture->format, hostTexture->dim, &glFormatInfo); + // calculate mip count + sint32 mipLevels = std::min(hostTexture->mipLevels, hostTexture->maxPossibleMipLevels); + mipLevels = std::max(mipLevels, 1); + // create immutable storage + if (hostTexture->dim == Latte::E_DIM::DIM_2D || hostTexture->dim == Latte::E_DIM::DIM_2D_MSAA) + { + cemu_assert_debug(effectiveBaseDepth == 1); + glTexStorage2D(GL_TEXTURE_2D, mipLevels, glFormatInfo.glInternalFormat, effectiveBaseWidth, effectiveBaseHeight); + } + else if (hostTexture->dim == Latte::E_DIM::DIM_1D) + { + cemu_assert_debug(effectiveBaseHeight == 1); + cemu_assert_debug(effectiveBaseDepth == 1); + glTexStorage1D(GL_TEXTURE_1D, mipLevels, glFormatInfo.glInternalFormat, effectiveBaseWidth); + } + else if (hostTexture->dim == Latte::E_DIM::DIM_2D_ARRAY || hostTexture->dim == Latte::E_DIM::DIM_2D_ARRAY_MSAA) + { + glTexStorage3D(GL_TEXTURE_2D_ARRAY, mipLevels, glFormatInfo.glInternalFormat, effectiveBaseWidth, effectiveBaseHeight, std::max(1, effectiveBaseDepth)); + } + else if (hostTexture->dim == Latte::E_DIM::DIM_3D) + { + glTexStorage3D(GL_TEXTURE_3D, mipLevels, glFormatInfo.glInternalFormat, effectiveBaseWidth, effectiveBaseHeight, std::max(1, effectiveBaseDepth)); + } + else if (hostTexture->dim == Latte::E_DIM::DIM_CUBEMAP) + { + glTexStorage3D(GL_TEXTURE_CUBE_MAP_ARRAY, mipLevels, glFormatInfo.glInternalFormat, effectiveBaseWidth, effectiveBaseHeight, effectiveBaseDepth); + } + else + { + cemu_assert_unimplemented(); + } +} + +// use standard API to upload texture data +void OpenGLRenderer_texture_loadSlice_normal(LatteTexture* hostTextureGeneric, sint32 width, sint32 height, sint32 depth, void* pixelData, sint32 sliceIndex, sint32 mipIndex, uint32 imageSize) +{ + auto hostTexture = (LatteTextureGL*)hostTextureGeneric; + + sint32 effectiveWidth = width; + sint32 effectiveHeight = height; + sint32 effectiveDepth = depth; + cemu_assert_debug(hostTexture->overwriteInfo.hasResolutionOverwrite == false); // not supported in _loadSlice + cemu_assert_debug(hostTexture->overwriteInfo.hasFormatOverwrite == false); // not supported in _loadSlice + // get format info + LatteTextureGL::FormatInfoGL glFormatInfo; + LatteTextureGL::GetOpenGLFormatInfo(hostTexture->isDepth, hostTexture->overwriteInfo.hasFormatOverwrite ? (Latte::E_GX2SURFFMT)hostTexture->overwriteInfo.format : hostTexture->format, hostTexture->dim, &glFormatInfo); + // upload slice + catchOpenGLError(); + if (hostTexture->dim == Latte::E_DIM::DIM_2D || hostTexture->dim == Latte::E_DIM::DIM_2D_MSAA) + { + if (glFormatInfo.glIsCompressed) + { + if (glCompressedTextureSubImage2D) + glCompressedTextureSubImage2D(hostTexture->glId_texture, mipIndex, 0, 0, effectiveWidth, effectiveHeight, glFormatInfo.glInternalFormat, imageSize, pixelData); + else + glCompressedTexSubImage2D(GL_TEXTURE_2D, mipIndex, 0, 0, effectiveWidth, effectiveHeight, glFormatInfo.glInternalFormat, imageSize, pixelData); + } + else + { + if (mipIndex < hostTexture->maxPossibleMipLevels) + glTexSubImage2D(GL_TEXTURE_2D, mipIndex, 0, 0, effectiveWidth, effectiveHeight, glFormatInfo.glSuppliedFormat, glFormatInfo.glSuppliedFormatType, pixelData); + else + forceLogDebug_printf("2D texture mip level allocated out of range"); + } + } + else if (hostTexture->dim == Latte::E_DIM::DIM_1D) + { + if (glFormatInfo.glIsCompressed == true) + cemu_assert_unimplemented(); + glTexSubImage1D(GL_TEXTURE_1D, mipIndex, 0, width, glFormatInfo.glSuppliedFormat, glFormatInfo.glSuppliedFormatType, pixelData); + } + else if (hostTexture->dim == Latte::E_DIM::DIM_2D_ARRAY || hostTexture->dim == Latte::E_DIM::DIM_2D_ARRAY_MSAA) + { + if (glFormatInfo.glIsCompressed) + glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, mipIndex, 0, 0, sliceIndex, effectiveWidth, effectiveHeight, 1, glFormatInfo.glInternalFormat, imageSize, pixelData); + else + glTexSubImage3D(GL_TEXTURE_2D_ARRAY, mipIndex, 0, 0, sliceIndex, effectiveWidth, effectiveHeight, 1, glFormatInfo.glSuppliedFormat, glFormatInfo.glSuppliedFormatType, pixelData); + } + else if (hostTexture->dim == Latte::E_DIM::DIM_3D) + { + if (glFormatInfo.glIsCompressed) + glCompressedTexSubImage3D(GL_TEXTURE_3D, mipIndex, 0, 0, sliceIndex, effectiveWidth, effectiveHeight, 1, glFormatInfo.glInternalFormat, imageSize, pixelData); + else + glTexSubImage3D(GL_TEXTURE_3D, mipIndex, 0, 0, sliceIndex, effectiveWidth, effectiveHeight, 1, glFormatInfo.glSuppliedFormat, glFormatInfo.glSuppliedFormatType, pixelData); + } + else if (hostTexture->dim == Latte::E_DIM::DIM_CUBEMAP) + { + if (glFormatInfo.glIsCompressed) + glCompressedTexSubImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, mipIndex, 0, 0, sliceIndex, width, height, 1, glFormatInfo.glInternalFormat, imageSize, pixelData); + else + glTexSubImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, mipIndex, 0, 0, sliceIndex, width, height, 1, glFormatInfo.glSuppliedFormat, glFormatInfo.glSuppliedFormatType, pixelData); + } + else + { + cemu_assert_debug(false); + } + catchOpenGLError(); +} + + +// use persistent buffers to upload data +void OpenGLRenderer_texture_loadSlice_viaBuffers(LatteTexture* hostTexture, sint32 width, sint32 height, sint32 depth, void* pixelData, sint32 sliceIndex, sint32 mipIndex, uint32 imageSize) +{ + // bind buffer + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, glRendererState.uploadBuffer); + catchOpenGLError(); + // upload data to buffer + cemu_assert(imageSize <= TEXBUFFER_SIZE); + + cemu_assert_debug(glRendererState.texWorkBufferSize == imageSize); + glFlushMappedBufferRange(GL_PIXEL_UNPACK_BUFFER, (GLintptr)(glRendererState.texWorkBuffer - (uint8*)glRendererState.uploadBufferPtr), imageSize); + catchOpenGLError(); + OpenGLRenderer_texture_loadSlice_normal(hostTexture, width, height, depth, (void*)(glRendererState.texWorkBuffer - (uint8*)glRendererState.uploadBufferPtr), sliceIndex, mipIndex, imageSize); + // unbind buffer and sync + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + catchOpenGLError(); +} + +void OpenGLRenderer::texture_loadSlice(LatteTexture* hostTexture, sint32 width, sint32 height, sint32 depth, void* pixelData, sint32 sliceIndex, sint32 mipIndex, uint32 imageSize) +{ + if (glRendererState.useTextureUploadBuffer) + OpenGLRenderer_texture_loadSlice_viaBuffers(hostTexture, width, height, depth, pixelData, sliceIndex, mipIndex, imageSize); + else + OpenGLRenderer_texture_loadSlice_normal(hostTexture, width, height, depth, pixelData, sliceIndex, mipIndex, imageSize); +} + +void OpenGLRenderer::texture_clearColorSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex, float r, float g, float b, float a) +{ + LatteTextureGL* texGL = (LatteTextureGL*)hostTexture; + cemu_assert_debug(!texGL->isDepth); + + sint32 eWidth, eHeight, eDepth; + LatteTexture_getEffectiveSize(hostTexture, &eWidth, &eHeight, &eDepth, mipIndex); + renderstate_resetColorControl(); + renderTarget_setViewport(0, 0, eWidth, eHeight, 0.0f, 1.0f); + LatteMRT::BindColorBufferOnly(hostTexture->GetOrCreateView(mipIndex, 1, sliceIndex, 1)); + glClearColor(r, g, b, a); + glClear(GL_COLOR_BUFFER_BIT); +} + +void OpenGLRenderer::texture_clearDepthSlice(LatteTexture* hostTexture, uint32 sliceIndex, sint32 mipIndex, bool clearDepth, bool clearStencil, float depthValue, uint32 stencilValue) +{ + LatteTextureGL* texGL = (LatteTextureGL*)hostTexture; + cemu_assert_debug(texGL->isDepth); + + sint32 eWidth, eHeight, eDepth; + LatteTexture_getEffectiveSize(hostTexture, &eWidth, &eHeight, &eDepth, mipIndex); + renderstate_resetColorControl(); + renderstate_resetDepthControl(); + renderTarget_setViewport(0, 0, eWidth, eHeight, 0.0f, 1.0f); + LatteMRT::BindDepthBufferOnly(hostTexture->GetOrCreateView(mipIndex, 1, sliceIndex, 1)); + + if (!hostTexture->hasStencil) + clearStencil = false; + + if (clearDepth) + glClearDepth(clearDepth); + if (clearStencil) + { + renderstate_resetStencilMask(); + glClearStencil((GLint)stencilValue); + } + glClear((clearDepth ? GL_DEPTH_BUFFER_BIT : 0) | (clearStencil ? GL_STENCIL_BUFFER_BIT : 0)); + catchOpenGLError(); +} + + +void OpenGLRenderer::texture_clearSlice(LatteTexture* hostTextureGeneric, sint32 sliceIndex, sint32 mipIndex) +{ + auto hostTexture = (LatteTextureGL*)hostTextureGeneric; + // get OpenGL format info + LatteTextureGL::FormatInfoGL formatInfoGL; + LatteTextureGL::GetOpenGLFormatInfo(hostTexture->isDepth, hostTexture->format, hostTexture->dim, &formatInfoGL); + // get effective size of mip + sint32 effectiveWidth; + sint32 effectiveHeight; + LatteTexture_getEffectiveSize(hostTexture, &effectiveWidth, &effectiveHeight, nullptr, mipIndex); + + // on Nvidia glClearTexImage and glClearTexSubImage has bad performance (clearing a 4K texture takes up to 50ms) + // clearing with glTextureSubImage2D from a CPU RAM buffer is only slightly slower + // clearing with glTextureSubImage2D from a OpenGL buffer is 10-20% faster than glClearTexImage) + // clearing with FBO and glClear is orders of magnitude faster than the other methods + // (these are results from 2018, may be different now) + + if (this->m_vendor == GfxVendor::Nvidia || glClearTexSubImage == nullptr) + { + if (formatInfoGL.glIsCompressed) + { + forceLogDebug_printf("Unsupported clear for compressed texture"); + return; // todo - create integer texture view to clear compressed textures + } + if (hostTextureGeneric->isDepth) + { + forceLogDebug_printf("Unsupported clear for depth texture"); + return; // todo - use depth clear + } + + glBindFramebuffer(GL_FRAMEBUFFER_EXT, glRendererState.clearFBO); + // set attachment + if (hostTexture->dim == Latte::E_DIM::DIM_2D) + glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, hostTexture->glId_texture, mipIndex); + else + glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, hostTexture->glId_texture, mipIndex, sliceIndex); + + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + glClear(GL_COLOR_BUFFER_BIT); + glBindFramebuffer(GL_FRAMEBUFFER_EXT, prevBoundFBO); + return; + } + if (glClearTexSubImage == nullptr) + return; + // clear + glClearTexSubImage(hostTexture->glId_texture, mipIndex, 0, 0, sliceIndex, effectiveWidth, effectiveHeight, 1, formatInfoGL.glSuppliedFormat, formatInfoGL.glSuppliedFormatType, NULL); +} + +LatteTexture* OpenGLRenderer::texture_createTextureEx(uint32 textureUnit, Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, + uint32 swizzle, Latte::E_HWTILEMODE tileMode, bool isDepth) +{ + return new LatteTextureGL(textureUnit, dim, physAddress, physMipAddress, format, width, height, depth, pitch, mipLevels, swizzle, tileMode, isDepth); + +} + +void OpenGLRenderer::texture_setActiveTextureUnit(sint32 index) +{ + if (activeTextureUnit != index) + { + glActiveTexture(GL_TEXTURE0 + index); + activeTextureUnit = index; + } +} + +void OpenGLRenderer::texture_bindAndActivate(LatteTextureView* textureView, uint32 textureUnit) +{ + const auto textureViewGL = (LatteTextureViewGL*)textureView; + cemu_assert_debug(textureUnit < (sizeof(LatteBoundTexturesBackup) / sizeof(LatteBoundTexturesBackup[0]))); + // don't call glBindTexture if the texture is already bound + if (LatteBoundTextures[textureUnit] == textureViewGL) + { + texture_setActiveTextureUnit(textureUnit); + return; // already bound + } + // bind + LatteBoundTextures[textureUnit] = textureViewGL; + texture_setActiveTextureUnit(textureUnit); + if (textureViewGL) + { + glBindTexture(textureViewGL->glTexTarget, textureViewGL->glTexId); + texUnitTexId[textureUnit] = textureViewGL->glTexId; + texUnitTexTarget[textureUnit] = textureViewGL->glTexTarget; + } +} + +void OpenGLRenderer::texture_bindAndActivateRawTex(LatteTexture* texture, uint32 textureUnit) +{ + cemu_assert_debug(textureUnit < (sizeof(LatteBoundTexturesBackup) / sizeof(LatteBoundTexturesBackup[0]))); + // don't call glBindTexture if the texture is already bound + if (LatteBoundTextures[textureUnit] == texture) + { + texture_setActiveTextureUnit(textureUnit); + return; // already bound + } + // bind + LatteBoundTextures[textureUnit] = texture; + texture_setActiveTextureUnit(textureUnit); + if (texture) + { + auto textureGL = (LatteTextureGL*)texture; + glBindTexture(textureGL->glTexTarget, textureGL->glId_texture); + texUnitTexId[textureUnit] = textureGL->glId_texture; + texUnitTexTarget[textureUnit] = textureGL->glTexTarget; + } +} + +void OpenGLRenderer::texture_notifyDelete(LatteTextureView* textureView) +{ + for (uint32 i = 0; i < Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE * 3; i++) + { + if (LatteBoundTextures[i] == textureView) + LatteBoundTextures[i] = nullptr; + } +} + +// similar to _bindAndActivate() but doesn't call _setActiveTextureUnit() if texture is already bound +void OpenGLRenderer::texture_bindOnly(LatteTextureView* textureView1, uint32 textureUnit) +{ + auto textureView = ((LatteTextureViewGL*)textureView1); + + cemu_assert_debug(textureUnit < Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE * 3); + if (LatteBoundTextures[textureUnit] == textureView) + return; + if (textureView == nullptr) + return; + // bind + if (glBindTextureUnit) + { + glBindTextureUnit(textureUnit, textureView->glTexId); + LatteBoundTextures[textureUnit] = textureView; + texUnitTexId[textureUnit] = textureView->glTexId; + texUnitTexTarget[textureUnit] = textureView->glTexTarget; + activeTextureUnit = -1; + } + else + { + texture_setActiveTextureUnit(textureUnit); + glBindTexture(textureView->glTexTarget, textureView->glTexId); + LatteBoundTextures[textureUnit] = textureView; + texUnitTexId[textureUnit] = textureView->glTexId; + texUnitTexTarget[textureUnit] = textureView->glTexTarget; + } +} + +void OpenGLRenderer::texture_rememberBoundTexture(uint32 textureUnit) +{ + cemu_assert_debug(texUnitBackupSlotUsed[textureUnit] == false); + texUnitBackupSlotUsed[textureUnit] = true; + LatteBoundTexturesBackup[textureUnit] = LatteBoundTextures[textureUnit]; + texUnitTexIdBackup[textureUnit] = texUnitTexId[textureUnit]; + texUnitTexTargetBackup[textureUnit] = texUnitTexTarget[textureUnit]; +} + +void OpenGLRenderer::texture_restoreBoundTexture(uint32 textureUnit) +{ + cemu_assert_debug(texUnitBackupSlotUsed[textureUnit] == true); + texUnitBackupSlotUsed[textureUnit] = false; + if (LatteBoundTextures[textureUnit] == LatteBoundTexturesBackup[textureUnit]) + { + return; // already bound + } + LatteBoundTextures[textureUnit] = LatteBoundTexturesBackup[textureUnit]; + texUnitTexId[textureUnit] = texUnitTexIdBackup[textureUnit]; + texUnitTexTarget[textureUnit] = texUnitTexTargetBackup[textureUnit]; + texture_setActiveTextureUnit(textureUnit); + glBindTexture(texUnitTexTargetBackup[textureUnit], texUnitTexIdBackup[textureUnit]); +} + +void OpenGLRenderer::texture_copyImageSubData(LatteTexture* src, sint32 srcMip, sint32 effectiveSrcX, sint32 effectiveSrcY, sint32 srcSlice, LatteTexture* dst, sint32 dstMip, sint32 effectiveDstX, sint32 effectiveDstY, + sint32 dstSlice, sint32 effectiveCopyWidth, sint32 effectiveCopyHeight, sint32 srcDepth) +{ + auto srcGL = (LatteTextureGL*)src; + auto dstGL = (LatteTextureGL*)dst; + + if ((srcGL->isAlternativeFormat || dstGL->isAlternativeFormat) && (srcGL->glInternalFormat != dstGL->glInternalFormat)) + { + if (srcGL->format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_UINT && dstGL->format == Latte::E_GX2SURFFMT::BC4_UNORM) + { + cemu_assert_debug(dstGL->dim != Latte::E_DIM::DIM_2D); + // special case where BC4 format is replaced with R16F for array/3d-textures (since OpenGL's BC4 compression only supports 2D textures) + texture_syncSliceSpecialBC4(srcGL, srcSlice, srcMip, dstGL, dstSlice, dstMip); + return; + } + else + { + forceLogDebug_printf("_syncSlice() called with unhandled alternative format"); + return; + } + } + + if (srcGL->format == Latte::E_GX2SURFFMT::R32_G32_B32_A32_UINT && dstGL->format == Latte::E_GX2SURFFMT::BC3_UNORM) + { + if ((dstGL->width >> dstMip) < 4 || (dstGL->height >> dstMip) < 4) + { + texture_syncSliceSpecialIntegerToBC3(srcGL, srcSlice, srcMip, dstGL, dstSlice, dstMip); + return; + + } + } + catchOpenGLError(); + glCopyImageSubData(srcGL->glId_texture, srcGL->glTexTarget, srcMip, effectiveSrcX, effectiveSrcY, srcSlice, dstGL->glId_texture, dstGL->glTexTarget, dstMip, effectiveDstX, effectiveDstY, dstSlice, effectiveCopyWidth, effectiveCopyHeight, srcDepth); + catchOpenGLError(); +} + +LatteTextureReadbackInfo* OpenGLRenderer::texture_createReadback(LatteTextureView* textureView) +{ + return new LatteTextureReadbackInfoGL(textureView); +} + +void LatteDraw_resetAttributePointerCache(); + +void OpenGLRenderer::attributeStream_reset() +{ + // reset attribute state + attributeStream_unbindVertexBuffer(); + // setup vertices + SetArrayElementBuffer(0); + LatteDraw_resetAttributePointerCache(); + SetAttributeArrayState(0, true, -1); + SetAttributeArrayState(1, true, -1); + for (uint32 i = 0; i < GPU_GL_MAX_NUM_ATTRIBUTE; i++) + SetAttributeArrayState(i, false, -1); +} + +void OpenGLRenderer::bufferCache_init(const sint32 bufferSize) +{ + glGenBuffers(1, &glAttributeCacheAB); + glBindBuffer(GL_ARRAY_BUFFER, glAttributeCacheAB); + glBufferData(GL_ARRAY_BUFFER, bufferSize, NULL, GL_STREAM_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); +} + +void OpenGLRenderer::attributeStream_bindVertexCacheBuffer() +{ + if (_boundArrayBuffer == glAttributeCacheAB) + return; + _boundArrayBuffer = glAttributeCacheAB; + glBindBuffer(GL_ARRAY_BUFFER, glAttributeCacheAB); +} + +void OpenGLRenderer::attributeStream_unbindVertexBuffer() +{ + if (_boundArrayBuffer == 0) + return; + _boundArrayBuffer = 0; + glBindBuffer(GL_ARRAY_BUFFER, 0); +} + +RendererShader* OpenGLRenderer::shader_create(RendererShader::ShaderType type, uint64 baseHash, uint64 auxHash, const std::string& source, bool isGameShader, bool isGfxPackShader) +{ + return new RendererShaderGL(type, baseHash, auxHash, isGameShader, isGfxPackShader, source); +} + +void OpenGLRenderer::shader_bind(RendererShader* shader) +{ + auto shaderGL = (RendererShaderGL*)shader; + + GLbitfield shaderBit; + + const auto program = shaderGL->GetProgram(); + + switch(shader->GetType()) + { + case RendererShader::ShaderType::kVertex: + if (program == prevVertexShaderProgram) + return; + + shaderBit = GL_VERTEX_SHADER_BIT; + prevVertexShaderProgram = program; + break; + case RendererShader::ShaderType::kFragment: + if (program == prevPixelShaderProgram) + return; + + shaderBit = GL_FRAGMENT_SHADER_BIT; + prevPixelShaderProgram = program; + break; + case RendererShader::ShaderType::kGeometry: + if (program == prevGeometryShaderProgram) + return; + + shaderBit = GL_GEOMETRY_SHADER_BIT; + prevGeometryShaderProgram = program; + break; + default: + __assume(false); + } + + catchOpenGLError(); + glUseProgramStages(m_pipeline, shaderBit, program); + catchOpenGLError(); +} + +void OpenGLRenderer::shader_bind(GLuint program, GLbitfield shaderType) +{ + catchOpenGLError(); + glUseProgramStages(m_pipeline, shaderType, program); + catchOpenGLError(); +} + +void OpenGLRenderer::shader_unbind(RendererShader::ShaderType shaderType) +{ + switch (shaderType) { + case RendererShader::ShaderType::kVertex: + glUseProgramStages(m_pipeline, GL_VERTEX_SHADER_BIT, 0); + prevVertexShaderProgram = -1; + break; + case RendererShader::ShaderType::kFragment: + glUseProgramStages(m_pipeline, GL_FRAGMENT_SHADER_BIT, 0); + prevPixelShaderProgram = -1; + break; + case RendererShader::ShaderType::kGeometry: + glUseProgramStages(m_pipeline, GL_GEOMETRY_SHADER_BIT, 0); + prevGeometryShaderProgram = -1; + break; + default: + __assume(false); + } +} + +void decodeBC4Block_UNORM(uint8* blockStorage, float* rOutput); + +void OpenGLRenderer::texture_syncSliceSpecialBC4(LatteTexture* srcTexture, sint32 srcSliceIndex, sint32 srcMipIndex, LatteTexture* dstTexture, sint32 dstSliceIndex, sint32 dstMipIndex) +{ + auto srcTextureGL = (LatteTextureGL*)srcTexture; + auto dstTextureGL = (LatteTextureGL*)dstTexture; + + sint32 sourceTexWidth = std::max(srcTexture->width >> srcMipIndex, 1); + sint32 sourceTexHeight = std::max(srcTexture->height >> srcMipIndex, 1); + sint32 destTexWidth = std::max(dstTexture->width >> dstMipIndex, 1); + sint32 destTexHeight = std::max(dstTexture->height >> dstMipIndex, 1); + + sint32 compressedCopyWidth = std::min(sourceTexWidth, std::max(1, destTexWidth / 4)); + sint32 compressedCopyHeight = std::min(sourceTexHeight, std::max(1, destTexHeight / 4)); + + uint8* texelData = (uint8*)malloc(compressedCopyWidth*compressedCopyHeight * 8); + float* pixelRGBA16fData = (float*)malloc(destTexWidth*destTexHeight * sizeof(float) * 2); + glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); + if (glGetTextureSubImage) + glGetTextureSubImage(srcTextureGL->glId_texture, 0, 0, 0, srcSliceIndex, compressedCopyWidth, compressedCopyHeight, 1, GL_RGBA_INTEGER, GL_UNSIGNED_SHORT, compressedCopyWidth * compressedCopyHeight * 8, texelData); + for (sint32 bx = 0; bx < compressedCopyWidth; bx++) + { + for (sint32 by = 0; by < compressedCopyHeight; by++) + { + float rBlock[4 * 4]; + decodeBC4Block_UNORM(texelData + (bx + by * compressedCopyWidth) * 8, rBlock); + for (sint32 sy = 0; sy < std::min(4, destTexHeight - by * 4); sy++) + { + for (sint32 sx = 0; sx < std::min(4, destTexWidth - bx * 4); sx++) + { + sint32 pixelIndex = (bx * 4 + sx) + (by * 4 + sy)*destTexWidth; + pixelRGBA16fData[pixelIndex * 2] = rBlock[sx + sy * 4]; + pixelRGBA16fData[pixelIndex * 2 + 1] = rBlock[sx + sy * 4]; + } + } + } + } + // upload mip + if (glGetTextureSubImage && glTextureSubImage3D) + glTextureSubImage3D(dstTextureGL->glId_texture, dstMipIndex, 0, 0, dstSliceIndex, destTexWidth, destTexHeight, 1, GL_RG, GL_FLOAT, pixelRGBA16fData); + free(pixelRGBA16fData); + free(texelData); + catchOpenGLError(); +} + +void OpenGLRenderer::texture_syncSliceSpecialIntegerToBC3(LatteTexture* srcTexture, sint32 srcSliceIndex, sint32 srcMipIndex, LatteTexture* dstTexture, sint32 dstSliceIndex, sint32 dstMipIndex) +{ + auto srcTextureGL = (LatteTextureGL*)srcTexture; + auto dstTextureGL = (LatteTextureGL*)dstTexture; + + sint32 sourceTexWidth = std::max(srcTexture->width >> srcMipIndex, 1); + sint32 sourceTexHeight = std::max(srcTexture->height >> srcMipIndex, 1); + sint32 destTexWidth = std::max(dstTexture->width >> dstMipIndex, 1); + sint32 destTexHeight = std::max(dstTexture->height >> dstMipIndex, 1); + + sint32 compressedCopyWidth = std::min(sourceTexWidth, std::max(1, destTexWidth / 4)); + sint32 compressedCopyHeight = std::min(sourceTexHeight, std::max(1, destTexHeight / 4)); + + uint8* texelData = (uint8*)malloc(compressedCopyWidth*compressedCopyHeight * 16); + + catchOpenGLError(); + glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); + catchOpenGLError(); + if (glGetTextureSubImage) + glGetTextureSubImage(srcTextureGL->glId_texture, 0, 0, 0, srcSliceIndex, compressedCopyWidth, compressedCopyHeight, 1, GL_RGBA_INTEGER, GL_UNSIGNED_INT, compressedCopyWidth * compressedCopyHeight * 16, texelData); + //float* pixelRGBA16fData = (float*)malloc(destTexWidth*destTexHeight * sizeof(float) * 2); + //for (sint32 bx = 0; bx < compressedCopyWidth; bx++) + //{ + // for (sint32 by = 0; by < compressedCopyHeight; by++) + // { + // float rBlock[4 * 4]; + // decodeBC4Block_UNORM(texelData + (bx + by * compressedCopyWidth) * 8, rBlock); + // for (sint32 sy = 0; sy < min(4, destTexHeight - by * 4); sy++) + // { + // for (sint32 sx = 0; sx < min(4, destTexWidth - bx * 4); sx++) + // { + // sint32 pixelIndex = (bx * 4 + sx) + (by * 4 + sy)*destTexWidth; + // pixelRGBA16fData[pixelIndex * 2] = rBlock[sx + sy * 4]; + // pixelRGBA16fData[pixelIndex * 2 + 1] = rBlock[sx + sy * 4]; + // } + // } + // } + //} + // upload mip + catchOpenGLError(); + if (glGetTextureSubImage && glCompressedTextureSubImage3D) + glCompressedTextureSubImage3D(dstTextureGL->glId_texture, dstMipIndex, 0, 0, dstSliceIndex, destTexWidth, destTexHeight, 1, dstTextureGL->glInternalFormat, compressedCopyWidth * compressedCopyHeight * 16, texelData); + free(texelData); + catchOpenGLError(); +} + +void OpenGLRenderer::renderstate_updateBlendingAndColorControl() +{ + catchOpenGLError(); + const auto& colorControlReg = LatteGPUState.contextNew.CB_COLOR_CONTROL; + + const auto specialOp = colorControlReg.get_SPECIAL_OP(); + const uint32 blendEnableMask = colorControlReg.get_BLEND_MASK(); + const auto logicOp = colorControlReg.get_ROP(); + + cemu_assert_debug(!colorControlReg.get_MULTIWRITE_ENABLE()); // not supported + + uint32 renderTargetMask = LatteGPUState.contextNew.CB_TARGET_MASK.get_MASK(); + if (specialOp == Latte::LATTE_CB_COLOR_CONTROL::E_SPECIALOP::DISABLE) + { + renderTargetMask = 0; + } + OpenGLRenderer::renderstate_setChannelTargetMask(renderTargetMask); + catchOpenGLError(); + + // handle depth and stencil control + // get depth control parameters + bool depthEnable = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_Z_ENABLE(); + auto depthFunc = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_Z_FUNC(); + bool depthWriteEnable = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_Z_WRITE_ENABLE(); + + // get stencil control parameters + bool stencilEnable = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_STENCIL_ENABLE(); + bool backStencilEnable = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_BACK_STENCIL_ENABLE(); + auto frontStencilFunc = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_STENCIL_FUNC_F(); + auto frontStencilZPass = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_STENCIL_ZPASS_F(); + auto frontStencilZFail = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_STENCIL_ZFAIL_F(); + auto frontStencilFail = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_STENCIL_FAIL_F(); + auto backStencilFunc = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_STENCIL_FUNC_B(); + auto backStencilZPass = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_STENCIL_ZPASS_B(); + auto backStencilZFail = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_STENCIL_ZFAIL_B(); + auto backStencilFail = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_STENCIL_FAIL_B(); + + // get stencil control parameters + uint32 stencilCompareMaskFront = LatteGPUState.contextNew.DB_STENCILREFMASK.get_STENCILMASK_F(); + uint32 stencilWriteMaskFront = LatteGPUState.contextNew.DB_STENCILREFMASK.get_STENCILWRITEMASK_F(); + uint32 stencilRefFront = LatteGPUState.contextNew.DB_STENCILREFMASK.get_STENCILREF_F(); + uint32 stencilCompareMaskBack = LatteGPUState.contextNew.DB_STENCILREFMASK_BF.get_STENCILMASK_B(); + uint32 stencilWriteMaskBack = LatteGPUState.contextNew.DB_STENCILREFMASK_BF.get_STENCILWRITEMASK_B(); + uint32 stencilRefBack = LatteGPUState.contextNew.DB_STENCILREFMASK_BF.get_STENCILREF_B(); + + const static GLenum stencilActionGX2ToGL[] = + { + GL_KEEP, + GL_ZERO, + GL_REPLACE, + GL_INCR, + GL_DECR, + GL_INVERT, + GL_INCR_WRAP, + GL_DECR_WRAP + }; + + if (prevStencilEnable != stencilEnable) + { + if (stencilEnable) + glEnable(GL_STENCIL_TEST); + else + glDisable(GL_STENCIL_TEST); + prevStencilEnable = stencilEnable; + } + + // update stencil parameters only if stencil is enabled + if (stencilEnable) + { + if (!backStencilEnable) + { + // if back face stencil is disabled then use front parameters + backStencilFunc = frontStencilFunc; + backStencilZPass = frontStencilZPass; + backStencilZFail = frontStencilZFail; + backStencilFail = frontStencilFail; + stencilRefBack = stencilRefFront; + stencilCompareMaskBack = stencilCompareMaskFront; + stencilWriteMaskBack = stencilWriteMaskFront; + } + // update stencil configuration for front side + if (prevFrontStencilFail != frontStencilFail || prevFrontStencilZFail != frontStencilZFail || prevFrontStencilZPass != frontStencilZPass) + { + glStencilOpSeparate(GL_FRONT, stencilActionGX2ToGL[(size_t)frontStencilFail], stencilActionGX2ToGL[(size_t)frontStencilZFail], stencilActionGX2ToGL[(size_t)frontStencilZPass]); + prevFrontStencilFail = frontStencilFail; + prevFrontStencilZFail = frontStencilZFail; + prevFrontStencilZPass = frontStencilZPass; + } + if (prevFrontStencilFunc != frontStencilFunc || prevStencilRefFront != stencilRefFront || prevStencilCompareMaskFront != stencilCompareMaskFront) + { + glStencilFuncSeparate(GL_FRONT, glDepthFuncTable[(size_t)frontStencilFunc], stencilRefFront, stencilCompareMaskFront); + prevFrontStencilFunc = frontStencilFunc; + prevStencilRefFront = stencilRefFront; + prevStencilCompareMaskFront = stencilCompareMaskFront; + } + if (prevStencilWriteMaskFront != stencilWriteMaskFront) + { + glStencilMaskSeparate(GL_FRONT, stencilWriteMaskFront); + prevStencilWriteMaskFront = stencilWriteMaskFront; + } + // update stencil configuration for back side + if (prevBackStencilFail != backStencilFail || prevBackStencilZFail != backStencilZFail || prevBackStencilZPass != backStencilZPass) + { + glStencilOpSeparate(GL_BACK, stencilActionGX2ToGL[(size_t)backStencilFail], stencilActionGX2ToGL[(size_t)backStencilZFail], stencilActionGX2ToGL[(size_t)backStencilZPass]); + prevBackStencilFail = backStencilFail; + prevBackStencilZFail = backStencilZFail; + prevBackStencilZPass = backStencilZPass; + } + if (prevBackStencilFunc != backStencilFunc || prevStencilRefBack != stencilRefBack || prevStencilCompareMaskBack != stencilCompareMaskBack) + { + glStencilFuncSeparate(GL_BACK, glDepthFuncTable[(size_t)backStencilFunc], stencilRefBack, stencilCompareMaskBack); + prevBackStencilFunc = backStencilFunc; + prevStencilRefBack = stencilRefBack; + prevStencilCompareMaskBack = stencilCompareMaskBack; + } + if (prevStencilWriteMaskBack != stencilWriteMaskBack) + { + glStencilMaskSeparate(GL_BACK, stencilWriteMaskBack); + prevStencilWriteMaskBack = stencilWriteMaskBack; + } + } + + if (depthEnable != prevDepthEnable) + { + if (depthEnable) + glEnable(GL_DEPTH_TEST); + else + glDisable(GL_DEPTH_TEST); + prevDepthEnable = depthEnable; + } + if (depthWriteEnable != prevDepthWriteEnable) + { + if (depthWriteEnable) + glDepthMask(GL_TRUE); + else + glDepthMask(GL_FALSE); + prevDepthWriteEnable = depthWriteEnable; + } + if (depthFunc != prevDepthFunc) + { + glDepthFunc(glDepthFuncTable[(size_t)depthFunc]); + prevDepthFunc = depthFunc; + } + + catchOpenGLError(); + uint32 blendChangeMask = blendEnableMask ^ prevBlendMask; + if (blendChangeMask) + { + for (uint32 i = 0; i < 8; i++) + { + if ((blendChangeMask & (1 << i)) != 0) + { + // bit changed -> blend mode was toggled + if ((blendEnableMask & (1 << i)) != 0) + glEnablei(GL_BLEND, i); + else + glDisablei(GL_BLEND, i); + } + } + prevBlendMask = blendEnableMask; + } + catchOpenGLError(); + + uint32* blendColorConstant = LatteGPUState.contextRegister + Latte::REGADDR::CB_BLEND_RED; + if (blendColorConstant[0] != prevBlendColorConstant[0] || + blendColorConstant[1] != prevBlendColorConstant[1] || + blendColorConstant[2] != prevBlendColorConstant[2] || + blendColorConstant[3] != prevBlendColorConstant[3]) + { + glBlendColor(*(float*)(blendColorConstant + 0), *(float*)(blendColorConstant + 1), *(float*)(blendColorConstant + 2), *(float*)(blendColorConstant + 3)); + prevBlendColorConstant[0] = blendColorConstant[0]; + prevBlendColorConstant[1] = blendColorConstant[1]; + prevBlendColorConstant[2] = blendColorConstant[2]; + prevBlendColorConstant[3] = blendColorConstant[3]; + } + + for (uint32 i = 0; i < 8; i++) + { + const auto& blendControlReg = LatteGPUState.contextNew.CB_BLENDN_CONTROL[i]; + if (blendControlReg.getRawValue() != prevBlendControlReg[i]) + { + if (blendControlReg.get_SEPARATE_ALPHA_BLEND()) + { + glBlendFuncSeparatei(i, GetGLBlendFactor(blendControlReg.get_COLOR_SRCBLEND()), GetGLBlendFactor(blendControlReg.get_COLOR_DSTBLEND()), GetGLBlendFactor(blendControlReg.get_ALPHA_SRCBLEND()), GetGLBlendFactor(blendControlReg.get_ALPHA_DSTBLEND())); + glBlendEquationSeparatei(i, GetGLBlendCombineFunc(blendControlReg.get_COLOR_COMB_FCN()), GetGLBlendCombineFunc(blendControlReg.get_ALPHA_COMB_FCN())); + } + else + { + auto colorSrc = GetGLBlendFactor(blendControlReg.get_COLOR_SRCBLEND()); + auto colorDst = GetGLBlendFactor(blendControlReg.get_COLOR_DSTBLEND()); + glBlendFuncSeparatei(i, colorSrc, colorDst, colorSrc, colorDst); + auto combineFunc = GetGLBlendCombineFunc(blendControlReg.get_COLOR_COMB_FCN()); + glBlendEquationSeparatei(i, combineFunc, combineFunc); + } + prevBlendControlReg[i] = blendControlReg.getRawValue(); + } + } + // set logic op + uint32 logicOpGL = GL_COPY; + if (logicOp == Latte::LATTE_CB_COLOR_CONTROL::E_LOGICOP::COPY) + logicOpGL = GL_COPY; + else if (logicOp == Latte::LATTE_CB_COLOR_CONTROL::E_LOGICOP::SET) + logicOpGL = GL_SET; + else if (logicOp == Latte::LATTE_CB_COLOR_CONTROL::E_LOGICOP::CLEAR) + logicOpGL = GL_CLEAR; + else if (logicOp == Latte::LATTE_CB_COLOR_CONTROL::E_LOGICOP::OR) + logicOpGL = GL_OR; + else + cemu_assert_unimplemented(); + if (prevLogicOp != logicOpGL) + { + if (logicOpGL != GL_COPY) + glEnable(GL_COLOR_LOGIC_OP); + else + glDisable(GL_COLOR_LOGIC_OP); + glLogicOp(logicOpGL); + prevLogicOp = logicOpGL; + } + + // polygon control + const auto& polygonControlReg = LatteGPUState.contextNew.PA_SU_SC_MODE_CNTL; + const auto frontFace = polygonControlReg.get_FRONT_FACE(); + uint32 cullFront = polygonControlReg.get_CULL_FRONT(); + uint32 cullBack = polygonControlReg.get_CULL_BACK(); + // todo - polygon modes + uint32 lineAndPointOffsetEnabled = polygonControlReg.get_OFFSET_PARA_ENABLED(); + uint32 polyOffsetFrontEnabled = polygonControlReg.get_OFFSET_FRONT_ENABLED(); + uint32 polyOffsetBackEnabled = polygonControlReg.get_OFFSET_BACK_ENABLED(); + + uint32 cullEnable = (cullFront || cullBack) ? 1 : 0; + if (prevCullEnable != cullEnable) + { + if (cullEnable) + glEnable(GL_CULL_FACE); + else + glDisable(GL_CULL_FACE); + prevCullEnable = cullEnable; + } + if (prevCullFrontFace != frontFace) + { + if (frontFace == Latte::LATTE_PA_SU_SC_MODE_CNTL::E_FRONTFACE::CCW) + glFrontFace(GL_CCW); + else if (frontFace == Latte::LATTE_PA_SU_SC_MODE_CNTL::E_FRONTFACE::CW) + glFrontFace(GL_CW); + else + cemu_assert_unimplemented(); + prevCullFrontFace = frontFace; + } + if (prevCullFront != cullFront || prevCullBack != cullBack) + { + if (cullFront && cullBack) + glCullFace(GL_FRONT_AND_BACK); + else if (cullFront == 0 && cullBack) + glCullFace(GL_BACK); + else if (cullFront && cullBack == 0) + glCullFace(GL_FRONT); + else + ; // front and back disabled, do nothing here since we force disable culling via glDisable(GL_CULL_FACE) above + prevCullFront = cullFront; + prevCullBack = cullBack; + } + + if (polyOffsetFrontEnabled != prevPolygonOffsetFrontEnabled) + { + if (polyOffsetFrontEnabled) + glEnable(GL_POLYGON_OFFSET_FILL); + else + glDisable(GL_POLYGON_OFFSET_FILL); + prevPolygonOffsetFrontEnabled = polyOffsetFrontEnabled; + + } + + if (polyOffsetFrontEnabled) + { + // if polygon offset is enabled check if offset/scale register changed + if (LatteGPUState.contextNew.PA_SU_POLY_OFFSET_FRONT_OFFSET.getRawValue() != prevPolygonFrontOffsetU32 || LatteGPUState.contextNew.PA_SU_POLY_OFFSET_FRONT_SCALE.getRawValue() != prevPolygonFrontScaleU32 || LatteGPUState.contextNew.PA_SU_POLY_OFFSET_CLAMP.getRawValue() != prevPolygonClampU32) + { + float frontScale = LatteGPUState.contextNew.PA_SU_POLY_OFFSET_FRONT_SCALE.get_SCALE(); + float frontOffset = LatteGPUState.contextNew.PA_SU_POLY_OFFSET_FRONT_OFFSET.get_OFFSET(); + float offsetClamp = LatteGPUState.contextNew.PA_SU_POLY_OFFSET_CLAMP.get_CLAMP(); + + frontScale /= 16.0f; + + //if( glPolygonOffsetClampEXT ) + // glPolygonOffsetClampEXT(frontOffset, frontScale, offsetClamp); + //else + glPolygonOffset(frontScale, frontOffset); + + prevPolygonFrontOffsetU32 = LatteGPUState.contextNew.PA_SU_POLY_OFFSET_FRONT_SCALE.getRawValue(); + prevPolygonFrontScaleU32 = LatteGPUState.contextNew.PA_SU_POLY_OFFSET_FRONT_SCALE.getRawValue(); + prevPolygonClampU32 = LatteGPUState.contextNew.PA_SU_POLY_OFFSET_CLAMP.getRawValue(); + } + } + // clip control + catchOpenGLError(); + cemu_assert_debug(LatteGPUState.contextNew.PA_CL_CLIP_CNTL.get_ZCLIP_NEAR_DISABLE() == LatteGPUState.contextNew.PA_CL_CLIP_CNTL.get_ZCLIP_FAR_DISABLE()); // near/far clipping disabled individually + uint32 zClipEnable = LatteGPUState.contextNew.PA_CL_CLIP_CNTL.get_ZCLIP_NEAR_DISABLE() == false; + // todo: Mass Effect 3 uses precompiled display lists which seem to write values to PA_CL_CLIP_CNTL which aren't available via the traditional GX2 API + if (prevZClipEnable != zClipEnable) + { + if (zClipEnable) + { + // disable depth clamping and enable clipping + glDisable(GL_DEPTH_CLAMP); + } + else + { + // enable depth clamping and disable clipping + glEnable(GL_DEPTH_CLAMP); + } + prevZClipEnable = zClipEnable; + } + catchOpenGLError(); + // point size + const auto& pointSizeReg = LatteGPUState.contextNew.PA_SU_POINT_SIZE; + if (pointSizeReg.getRawValue() != prevPointSizeReg) + { + float pointWidth = (float)pointSizeReg.get_WIDTH() / 8.0f; + float pointHeight = (float)pointSizeReg.get_HEIGHT() / 8.0f; + if (pointWidth == 0.0f) + glPointSize(1.0f / 8.0f); // minimum size + else + glPointSize(pointWidth); + prevPointSizeReg = pointSizeReg.getRawValue(); + catchOpenGLError(); + } + // primitive restart index + uint32 primitiveRestartIndex = LatteGPUState.contextNew.VGT_MULTI_PRIM_IB_RESET_INDX.get_RESTART_INDEX(); + if (prevPrimitiveRestartIndex != primitiveRestartIndex) + { + glPrimitiveRestartIndex(primitiveRestartIndex); + prevPrimitiveRestartIndex = primitiveRestartIndex; + } +} + +void OpenGLRenderer::renderstate_resetColorControl() +{ + renderstate_setChannelTargetMask(0xF); + // disable blending + uint32 blendEnableMask = 0; + for (uint32 i = 0; i < 8; i++) + { + if (((blendEnableMask^prevBlendMask)&(1 << i)) != 0) + { + // bit changed -> blend mode was toggled + if ((blendEnableMask&(1 << i)) != 0) + glEnablei(GL_BLEND, i); + else + glDisablei(GL_BLEND, i); + } + } + prevBlendMask = blendEnableMask; + // "forget" blend states + for (uint32 i = 0; i < 8; i++) + { + prevBlendControlReg[i] = 0xFFFFFFFF; + } + // disable alpha test + if (prevAlphaTestEnable != 0) + { + glDisable(GL_ALPHA_TEST); + prevAlphaTestEnable = 0; + } + if (prevLogicOp != GL_COPY) + { + glDisable(GL_COLOR_LOGIC_OP); + glLogicOp(GL_COPY); + prevLogicOp = GL_COPY; + } + // disable culling + uint32 cullEnable = 0; + if (prevCullEnable != cullEnable) + { + glDisable(GL_CULL_FACE); + prevCullEnable = 0; + } + // disable polygon offset + if (prevPolygonOffsetFrontEnabled != 0) + { + glDisable(GL_POLYGON_OFFSET_FILL); + prevPolygonOffsetFrontEnabled = 0; + } + // disable scissor box + if (prevScissorEnable != false) + { + glDisable(GL_SCISSOR_TEST); + prevScissorEnable = false; + } +} + +void OpenGLRenderer::renderstate_resetDepthControl() +{ + if (prevDepthEnable) + { + glDisable(GL_DEPTH_TEST); + prevDepthEnable = false; + } + if (!prevDepthWriteEnable) + { + glDepthMask(GL_TRUE); + prevDepthWriteEnable = true; + } + if (prevStencilEnable) + { + glDisable(GL_STENCIL_TEST); + prevStencilEnable = false; + } + //if (prevZClipEnable == 0) + //{ + // glDisable(GL_DEPTH_CLAMP); + // prevZClipEnable = 1; + //} + + glDisable(GL_DEPTH_CLAMP); + prevZClipEnable = 1; + + if (prevPrimitiveRestartIndex != 0xFFFFFFFF) + { + glPrimitiveRestartIndex(0xFFFFFFFF); + prevPrimitiveRestartIndex = 0xFFFFFFFF; + } +} + +void OpenGLRenderer::renderstate_resetStencilMask() +{ + uint32 stencilWriteMaskFront = 0xFFFFFFFF; // enable front mask + uint32 stencilWriteMaskBack = 0xFFFFFFFF; // enable back mask + if (prevStencilWriteMaskFront != stencilWriteMaskFront) + { + glStencilMaskSeparate(GL_FRONT, stencilWriteMaskFront); + prevStencilWriteMaskFront = stencilWriteMaskFront; + } + if (prevStencilWriteMaskBack != stencilWriteMaskBack) + { + glStencilMaskSeparate(GL_BACK, stencilWriteMaskBack); + prevStencilWriteMaskBack = stencilWriteMaskBack; + } +} + +void OpenGLRenderer::cleanupAfterFrame() +{ + ReleaseBufferCacheEntries(); +} + +void OpenGLRenderer::ReleaseBufferCacheEntries() +{ + for (auto& itr : m_destructionQueues.bufferCacheEntries) + itr.free(); + m_destructionQueues.bufferCacheEntries.clear(); +} diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h new file mode 100644 index 00000000..129ada03 --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h @@ -0,0 +1,285 @@ +#pragma once +#include "Common/GLInclude/GLInclude.h" + +#include "Cafe/HW/Latte/Renderer/Renderer.h" +#include "Cafe/HW/Latte/Renderer/RendererOuputShader.h" + +#define GPU_GL_MAX_NUM_ATTRIBUTE (16) // Wii U GPU supports more than 16 but not all desktop GPUs do. Have to keep this at 16 until we find a better solution + +class OpenGLRenderer : public Renderer +{ + friend class OpenGLCanvas; +public: + OpenGLRenderer(); + ~OpenGLRenderer(); + + RendererAPI GetType() override { return RendererAPI::OpenGL; } + + static OpenGLRenderer* GetInstance(); + + // imgui + bool ImguiBegin(bool mainWindow) override; + void ImguiEnd() override; + ImTextureID GenerateTexture(const std::vector<uint8>& data, const Vector2i& size) override; + void DeleteTexture(ImTextureID id) override; + void DeleteFontTextures() override; + + void Initialize() override; + bool IsPadWindowActive() override; + + void Flush(bool waitIdle = false) override; + void NotifyLatteCommandProcessorIdle() override; + + void EnableVSync(int state) override; + + void EnableDebugMode() override; + void SwapBuffers(bool swapTV = true, bool swapDRC = true) override; + + bool BeginFrame(bool mainWindow) override; + void DrawEmptyFrame(bool mainWindow) override; + void ClearColorbuffer(bool padView) override; + void HandleScreenshotRequest(LatteTextureView* texView, bool padView) override; + + void DrawBackbufferQuad(LatteTextureView* texView, RendererOutputShader* shader, bool useLinearTexFilter, sint32 imageX, sint32 imageY, sint32 imageWidth, sint32 imageHeight, bool padView, bool clearBackground) override; + + void AppendOverlayDebugInfo() override + { + // does nothing currently + } + + // rendertarget + void renderTarget_setViewport(float x, float y, float width, float height, float nearZ, float farZ, bool halfZ = false) override; + void renderTarget_setScissor(sint32 scissorX, sint32 scissorY, sint32 scissorWidth, sint32 scissorHeight) override; + + LatteCachedFBO* rendertarget_createCachedFBO(uint64 key) override; + void rendertarget_deleteCachedFBO(LatteCachedFBO* cfbo) override; + void rendertarget_bindFramebufferObject(LatteCachedFBO* cfbo) override; + + // renderstate functions + void renderstate_setChannelTargetMask(uint32 renderTargetMask); + void renderstate_setAlwaysWriteDepth(); + void renderstate_updateBlendingAndColorControl(); + void renderstate_resetColorControl(); + void renderstate_resetDepthControl(); + void renderstate_resetStencilMask(); + + void renderstate_updateTextureSettingsGL(LatteDecompilerShader* shaderContext, LatteTextureView* _hostTextureView, uint32 hostTextureUnit, const Latte::LATTE_SQ_TEX_RESOURCE_WORD4_N texUnitWord4, uint32 texUnitIndex, bool isDepthSampler); + + // texture functions + void texture_destroy(LatteTexture* hostTexture) override; + + void* texture_acquireTextureUploadBuffer(uint32 size) override; + void texture_releaseTextureUploadBuffer(uint8* mem) override; + + TextureDecoder* texture_chooseDecodedFormat(Latte::E_GX2SURFFMT format, bool isDepth, Latte::E_DIM dim, uint32 width, uint32 height) override; + + void texture_reserveTextureOnGPU(LatteTexture* hostTexture) override; + void texture_clearSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex) override; + void texture_loadSlice(LatteTexture* hostTexture, sint32 width, sint32 height, sint32 depth, void* pixelData, sint32 sliceIndex, sint32 mipIndex, uint32 compressedImageSize) override; + void texture_clearColorSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex, float r, float g, float b, float a) override; + void texture_clearDepthSlice(LatteTexture* hostTexture, uint32 sliceIndex, sint32 mipIndex, bool clearDepth, bool clearStencil, float depthValue, uint32 stencilValue) override; + + LatteTexture* texture_createTextureEx(uint32 textureUnit, Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, uint32 swizzle, Latte::E_HWTILEMODE tileMode, bool isDepth) override; + + void texture_bindAndActivate(LatteTextureView* textureView, uint32 textureUnit) override; + void texture_bindAndActivateRawTex(LatteTexture* texture, uint32 textureUnit) override; + void texture_bindOnly(LatteTextureView* textureView, uint32 textureUnit) override; + void texture_rememberBoundTexture(uint32 textureUnit) override; + void texture_restoreBoundTexture(uint32 textureUnit) override; + void texture_copyImageSubData(LatteTexture* src, sint32 srcMip, sint32 effectiveSrcX, sint32 effectiveSrcY, sint32 srcSlice, LatteTexture* dst, sint32 dstMip, sint32 effectiveDstX, sint32 effectiveDstY, sint32 dstSlice, sint32 effectiveCopyWidth, sint32 effectiveCopyHeight, sint32 srcDepth) override; + + void texture_notifyDelete(LatteTextureView* textureView); + + LatteTextureReadbackInfo* texture_createReadback(LatteTextureView* textureView) override; + + void surfaceCopy_copySurfaceWithFormatConversion(LatteTexture* sourceTexture, sint32 srcMip, sint32 srcSlice, LatteTexture* destinationTexture, sint32 dstMip, sint32 dstSlice, sint32 width, sint32 height) override; + + void attributeStream_reset(); + void bufferCache_init(const sint32 bufferSize) override; + void bufferCache_upload(uint8* buffer, sint32 size, uint32 bufferOffset) override; + void bufferCache_copy(uint32 srcOffset, uint32 dstOffset, uint32 size) override; + + void buffer_bindVertexBuffer(uint32 bufferIndex, uint32 offset, uint32 size) override; + void buffer_bindUniformBuffer(LatteConst::ShaderType shaderType, uint32 bufferIndex, uint32 offset, uint32 size) override; + + void _setupVertexAttributes(); + + void attributeStream_bindVertexCacheBuffer(); + void attributeStream_unbindVertexBuffer(); + + static void SetAttributeArrayState(uint32 index, bool isEnabled, sint32 aluDivisor); + static void SetArrayElementBuffer(GLuint arrayElementBuffer); + + // index + void* indexData_reserveIndexMemory(uint32 size, uint32& offset, uint32& bufferIndex) override + { + assert_dbg(); + return nullptr; + } + + void indexData_uploadIndexMemory(uint32 offset, uint32 size) override + { + assert_dbg(); + } + + // uniform + void uniformData_update(); + + // shader + RendererShader* shader_create(RendererShader::ShaderType type, uint64 baseHash, uint64 auxHash, const std::string& source, bool isGameShader, bool isGfxPackShader) override; + void shader_bind(RendererShader* shader) override; + void shader_bind(GLuint program, GLbitfield shaderType); + void shader_unbind(RendererShader::ShaderType shaderType) override; + + // streamout + void streamout_setupXfbBuffer(uint32 bufferIndex, sint32 ringBufferOffset, uint32 rangeAddr, uint32 rangeSize) override; + void streamout_begin() override; + void bufferCache_copyStreamoutToMainBuffer(uint32 srcOffset, uint32 dstOffset, uint32 size) override; + void streamout_rendererFinishDrawcall() override; + + // draw + void draw_init(); + + void draw_beginSequence() override; + void draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, bool isFirst) override; + void draw_endSequence() override; + + template<bool TIsMinimal, bool THasProfiling> + void draw_genericDrawHandler(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType); + + // resource garbage collection + void cleanupAfterFrame(); + void ReleaseBufferCacheEntries(); + + // occlusion queries + LatteQueryObject* occlusionQuery_create() override; + void occlusionQuery_destroy(LatteQueryObject* queryObj) override; + void occlusionQuery_flush() override; + void occlusionQuery_updateState() override {}; + +private: + void GetVendorInformation() override; + + void texture_setActiveTextureUnit(sint32 index); + + void texture_syncSliceSpecialBC4(LatteTexture* srcTexture, sint32 srcSliceIndex, sint32 srcMipIndex, LatteTexture* dstTexture, sint32 dstSliceIndex, sint32 dstMipIndex); + void texture_syncSliceSpecialIntegerToBC3(LatteTexture* srcTexture, sint32 srcSliceIndex, sint32 srcMipIndex, LatteTexture* dstTexture, sint32 dstSliceIndex, sint32 dstMipIndex); + + GLuint m_defaultFramebufferId; + GLuint m_pipeline = 0; + + bool m_isPadViewContext{}; + + // rendertarget viewport + float prevViewportX = 0; + float prevViewportY = 0; + float prevViewportWidth = 0; + float prevViewportHeight = 0; + + float prevNearZ = -999999.0f; + float prevFarZ = -9999999.0f; + bool _prevInvertY = false; + bool _prevHalfZ = false; + + uint32 prevScissorX = 0; + uint32 prevScissorY = 0; + uint32 prevScissorWidth = 0; + uint32 prevScissorHeight = 0; + bool prevScissorEnable = false; + + bool m_isXfbActive = false; + + sint32 activeTextureUnit = 0; + void* LatteBoundTextures[Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE * 3]{}; + GLuint texUnitTexId[Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE * 3]{}; + GLenum texUnitTexTarget[Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE * 3]{}; + + void* LatteBoundTexturesBackup[Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE * 3]{}; + GLuint texUnitTexIdBackup[Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE * 3]{}; + GLenum texUnitTexTargetBackup[Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE * 3]{}; + bool texUnitBackupSlotUsed[Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE * 3]{}; + + // attribute stream + GLuint glAttributeCacheAB{}; + GLuint _boundArrayBuffer{}; + + // streamout + GLuint glStreamoutCacheRingBuffer; + + // cfbo + GLuint prevBoundFBO = -1; + GLuint glId_fbo = 0; + + // renderstate + uint32 prevBlendControlReg[8] = { 0 }; + uint32 prevBlendMask = 0; + uint32 prevLogicOp = 0; + uint32 prevBlendColorConstant[4] = { 0 }; + uint8 prevAlphaTestEnable = 0; + uint8 prevAlphaTestFunc = 0; + uint32 prevAlphaTestRefU32 = 0; + bool prevDepthEnable = 0; + bool prevDepthWriteEnable = 0; + Latte::LATTE_DB_DEPTH_CONTROL::E_ZFUNC prevDepthFunc = (Latte::LATTE_DB_DEPTH_CONTROL::E_ZFUNC)-1; + bool prevStencilEnable = false; + Latte::LATTE_DB_DEPTH_CONTROL::E_STENCILFUNC prevFrontStencilFunc = (Latte::LATTE_DB_DEPTH_CONTROL::E_STENCILFUNC)-1; + Latte::LATTE_DB_DEPTH_CONTROL::E_STENCILFUNC prevBackStencilFunc = (Latte::LATTE_DB_DEPTH_CONTROL::E_STENCILFUNC)-1; + Latte::LATTE_DB_DEPTH_CONTROL::E_STENCILACTION prevFrontStencilZPass = (Latte::LATTE_DB_DEPTH_CONTROL::E_STENCILACTION)-1; + Latte::LATTE_DB_DEPTH_CONTROL::E_STENCILACTION prevFrontStencilZFail = (Latte::LATTE_DB_DEPTH_CONTROL::E_STENCILACTION)-1; + Latte::LATTE_DB_DEPTH_CONTROL::E_STENCILACTION prevFrontStencilFail = (Latte::LATTE_DB_DEPTH_CONTROL::E_STENCILACTION)-1; + Latte::LATTE_DB_DEPTH_CONTROL::E_STENCILACTION prevBackStencilZPass = (Latte::LATTE_DB_DEPTH_CONTROL::E_STENCILACTION)-1; + Latte::LATTE_DB_DEPTH_CONTROL::E_STENCILACTION prevBackStencilZFail = (Latte::LATTE_DB_DEPTH_CONTROL::E_STENCILACTION)-1; + Latte::LATTE_DB_DEPTH_CONTROL::E_STENCILACTION prevBackStencilFail = (Latte::LATTE_DB_DEPTH_CONTROL::E_STENCILACTION)-1; + uint32 prevStencilRefFront = -1; + uint32 prevStencilCompareMaskFront = -1; + uint32 prevStencilWriteMaskFront = -1; + uint32 prevStencilRefBack = -1; + uint32 prevStencilCompareMaskBack = -1; + uint32 prevStencilWriteMaskBack = -1; + + uint32 prevTargetColorMask = 0; // RGBA color mask for each render target (4 bit per target, starting from LSB) + uint32 prevCullEnable = 0; + uint32 prevCullFront = 0; + uint32 prevCullBack = 0; + Latte::LATTE_PA_SU_SC_MODE_CNTL::E_FRONTFACE prevCullFrontFace = Latte::LATTE_PA_SU_SC_MODE_CNTL::E_FRONTFACE::CCW; + + uint32 prevPolygonFrontScaleU32 = 0; + uint32 prevPolygonFrontOffsetU32 = 0; + uint32 prevPolygonClampU32 = 0; + uint32 prevPolygonOffsetFrontEnabled = 0; + uint32 prevPolygonOffsetBackEnabled = 0; + + uint32 prevZClipEnable = 0; + + uint32 prevPointSizeReg = 0xFFFFFFFF; + + uint32 prevPrimitiveRestartIndex = 0xFFFFFFFF; + + GLuint prevVertexShaderProgram = -1; + GLuint prevGeometryShaderProgram = -1; + GLuint prevPixelShaderProgram = -1; + + // occlusion queries + std::vector<class LatteQueryObjectGL*> list_queryCacheOcclusion; // cache for unused queries + + // resource garbage collection + struct bufferCacheReleaseQueueEntry_t + { + bufferCacheReleaseQueueEntry_t(VirtualBufferHeap_t* heap, VirtualBufferHeapEntry_t* entry) : m_heap(heap), m_entry(entry) {}; + + void free() + { + virtualBufferHeap_free(m_heap, m_entry); + } + + VirtualBufferHeap_t* m_heap{}; + VirtualBufferHeapEntry_t* m_entry{}; + }; + + struct + { + sint32 index; + std::vector<bufferCacheReleaseQueueEntry_t> bufferCacheEntries; + }m_destructionQueues; + +}; \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererCore.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererCore.cpp new file mode 100644 index 00000000..151f54f9 --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererCore.cpp @@ -0,0 +1,1599 @@ +#include "Common/GLInclude/GLInclude.h" + +#include "Cafe/HW/Latte/Core/LatteRingBuffer.h" +#include "Cafe/HW/Latte/Core/LatteDraw.h" +#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" +#include "Cafe/HW/Latte/Core/LatteShader.h" +#include "Cafe/HW/Latte/Core/LatteSoftware.h" +#include "Cafe/HW/Latte/Core/FetchShader.h" + +#include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h" +#include "Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.h" +#include "Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.h" +#include "Cafe/HW/Latte/Renderer/OpenGL/CachedFBOGL.h" +#include "Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.h" + +#include "Cafe/HW/Latte/ISA/RegDefines.h" +#include "Cafe/OS/libs/gx2/GX2.h" + +#include "Cafe/GameProfile/GameProfile.h" +#include "config/ActiveSettings.h" + + +using _INDEX_TYPE = Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE; + +GLenum sGLActiveDrawMode = 0; + +extern bool hasValidFramebufferAttached; + +#define INDEX_CACHE_ENTRIES (8) + +typedef struct +{ + MPTR prevIndexDataMPTR; + sint32 prevIndexType; + sint32 prevCount; + // index data + uint8* indexData; + uint8* indexData2; + uint32 indexBufferOffset; + sint32 indexDataSize; // current size + sint32 indexDataLimit; // maximum size + // info + uint32 maxIndex; + uint32 minIndex; +}indexDataCacheEntry_t; + +struct +{ + indexDataCacheEntry_t indexCacheEntry[INDEX_CACHE_ENTRIES]; + sint32 nextCacheEntryIndex; + // info about currently used index data + uint32 maxIndex; + uint32 minIndex; + uint8* indexData; + // buffer + GLuint glIndexCacheBuffer; + VirtualBufferHeap_t* indexBufferVirtualHeap; + uint8* mappedIndexBuffer; + LatteRingBuffer_t* indexRingBuffer; + uint8* tempIndexStorage; + // misc + bool initialized; + GLuint glActiveElementArrayBuffer; +}indexState = { 0 }; + +struct +{ + uint8* vboOutput; + uint32 vboStride; + uint8 dataFormat; + uint8 nfa; + bool isSigned; +}activeAttributePointer[LATTE_VS_ATTRIBUTE_LIMIT] = { 0 }; + +void LatteDraw_resetAttributePointerCache() +{ + for (sint32 i = 0; i < LATTE_VS_ATTRIBUTE_LIMIT; i++) + { + activeAttributePointer[i].vboOutput = (uint8*)-1; + activeAttributePointer[i].vboStride = (uint32)-1; + } +} + +void _setAttributeBufferPointerRaw(uint32 attributeShaderLoc, uint8* buffer, uint32 bufferSize, uint32 stride, LatteParsedFetchShaderAttribute_t* attrib, uint8* vboOutput, uint32 vboStride) +{ + uint32 dataFormat = attrib->format; + bool isSigned = attrib->isSigned != 0; + uint8 nfa = attrib->nfa; + // don't call glVertexAttribIPointer if parameters have not changed + if (activeAttributePointer[attributeShaderLoc].vboOutput == vboOutput && activeAttributePointer[attributeShaderLoc].vboStride == vboStride && activeAttributePointer[attributeShaderLoc].dataFormat == dataFormat && activeAttributePointer[attributeShaderLoc].nfa == nfa && activeAttributePointer[attributeShaderLoc].isSigned == isSigned) + { + return; + } + activeAttributePointer[attributeShaderLoc].vboOutput = vboOutput; + activeAttributePointer[attributeShaderLoc].vboStride = vboStride; + activeAttributePointer[attributeShaderLoc].dataFormat = dataFormat; + activeAttributePointer[attributeShaderLoc].nfa = nfa; + activeAttributePointer[attributeShaderLoc].isSigned = isSigned; + // setup attribute pointer + if (dataFormat == FMT_32_32_32_32_FLOAT || dataFormat == FMT_32_32_32_32) + { + glVertexAttribIPointer(attributeShaderLoc, 4, GL_UNSIGNED_INT, vboStride, vboOutput); + } + else if (dataFormat == FMT_32_32_32_FLOAT || dataFormat == FMT_32_32_32) + { + glVertexAttribIPointer(attributeShaderLoc, 3, GL_UNSIGNED_INT, vboStride, vboOutput); + } + else if (dataFormat == FMT_32_32_FLOAT || dataFormat == FMT_32_32) + { + glVertexAttribIPointer(attributeShaderLoc, 2, GL_UNSIGNED_INT, vboStride, vboOutput); + } + else if (dataFormat == FMT_32_FLOAT || dataFormat == FMT_32) + { + glVertexAttribIPointer(attributeShaderLoc, 1, GL_UNSIGNED_INT, vboStride, vboOutput); + } + else if (dataFormat == FMT_8_8_8_8) + { + glVertexAttribIPointer(attributeShaderLoc, 4, GL_UNSIGNED_BYTE, vboStride, vboOutput); + } + else if (dataFormat == FMT_8_8) + { + // workaround for AMD (alignment must be 4 for 2xbyte) + if (((uint32)(size_t)vboOutput & 0x3) == 2 && LatteGPUState.glVendor == GLVENDOR_AMD) + { + glVertexAttribIPointer(attributeShaderLoc, 4, GL_UNSIGNED_BYTE, vboStride, vboOutput - 2); + } + else + { + glVertexAttribIPointer(attributeShaderLoc, 2, GL_UNSIGNED_BYTE, vboStride, vboOutput); + } + } + else if (dataFormat == FMT_8) + { + glVertexAttribIPointer(attributeShaderLoc, 1, GL_UNSIGNED_BYTE, vboStride, vboOutput); + } + else if (dataFormat == FMT_16_16_16_16_FLOAT || dataFormat == FMT_16_16_16_16) + { + glVertexAttribIPointer(attributeShaderLoc, 4, GL_UNSIGNED_SHORT, vboStride, vboOutput); + } + else if (dataFormat == FMT_16_16_FLOAT || dataFormat == FMT_16_16) + { + glVertexAttribIPointer(attributeShaderLoc, 2, GL_UNSIGNED_SHORT, vboStride, vboOutput); + } + else if (dataFormat == FMT_16_FLOAT || dataFormat == FMT_16) + { + glVertexAttribIPointer(attributeShaderLoc, 1, GL_UNSIGNED_SHORT, vboStride, vboOutput); + } + else if (dataFormat == FMT_2_10_10_10) + { + glVertexAttribIPointer(attributeShaderLoc, 1, GL_UNSIGNED_INT, vboStride, vboOutput); + } + else + { + debug_printf("_setAttributeBufferPointerRaw(): Unsupported format %d\n", dataFormat); + cemu_assert_unimplemented(); + } +} + +bool glAttributeArrayIsEnabled[GPU_GL_MAX_NUM_ATTRIBUTE] = { 0 }; +sint32 glAttributeArrayAluDivisor[GPU_GL_MAX_NUM_ATTRIBUTE] = { 0 }; + +void OpenGLRenderer::SetAttributeArrayState(uint32 index, bool isEnabled, sint32 aluDivisor) +{ + cemu_assert_debug(index < GPU_GL_MAX_NUM_ATTRIBUTE); + catchOpenGLError(); + if (glAttributeArrayIsEnabled[index] != isEnabled) + { + if (isEnabled) + { + // enable + glEnableVertexAttribArray(index); + glAttributeArrayIsEnabled[index] = true; + } + else + { + // disable + glDisableVertexAttribArray(index); + glAttributeArrayIsEnabled[index] = false; + } + catchOpenGLError(); + } + // set divisor state + if (glAttributeArrayAluDivisor[index] != aluDivisor) + { + if (aluDivisor <= 0) + glVertexAttribDivisor(index, 0); + else + glVertexAttribDivisor(index, aluDivisor); + glAttributeArrayAluDivisor[index] = aluDivisor; + catchOpenGLError(); + } +} + +// Sets the currently active element array buffer and binds it +void OpenGLRenderer::SetArrayElementBuffer(GLuint arrayElementBuffer) +{ + if (arrayElementBuffer == indexState.glActiveElementArrayBuffer) + return; + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, arrayElementBuffer); + indexState.glActiveElementArrayBuffer = arrayElementBuffer; +} + +typedef struct +{ + MPTR physAddr; + sint32 count; + uint32 primitiveRestartIndex; + uint32 primitiveMode; +}indexDataCacheKey_t; + +typedef struct _indexDataCacheEntry_t +{ + indexDataCacheKey_t key; + _indexDataCacheEntry_t* nextInBucket; // points to next element in same bucket + uint32 physSize; + uint32 hash; + _INDEX_TYPE indexType; + //sint32 indexType; + uint32 minIndex; + uint32 maxIndex; + uint32 lastAccessFrameCount; + VirtualBufferHeapEntry_t* heapEntry; + _indexDataCacheEntry_t* nextInMostRecentUsage; // points to element which was used more recently + _indexDataCacheEntry_t* prevInMostRecentUsage; // points to element which was used less recently +}indexDataCacheEntry2_t; + +#define INDEX_DATA_CACHE_BUCKETS (1783) + +indexDataCacheEntry2_t* indexDataCacheBucket[INDEX_DATA_CACHE_BUCKETS] = { 0 }; +indexDataCacheEntry2_t* indexDataCacheFirst = nullptr; // points to least recently used item +indexDataCacheEntry2_t* indexDataCacheLast = nullptr; // points to most recently used item +sint32 indexDataCacheEntryCount = 0; + +void _appendToUsageLinkedList(indexDataCacheEntry2_t* entry) +{ + if (indexDataCacheLast == nullptr) + { + indexDataCacheLast = entry; + indexDataCacheFirst = entry; + entry->nextInMostRecentUsage = nullptr; + entry->prevInMostRecentUsage = nullptr; + } + else + { + indexDataCacheLast->nextInMostRecentUsage = entry; + entry->prevInMostRecentUsage = indexDataCacheLast; + entry->nextInMostRecentUsage = nullptr; + indexDataCacheLast = entry; + } +} + +void _removeFromUsageLinkedList(indexDataCacheEntry2_t* entry) +{ + if (entry->prevInMostRecentUsage) + { + entry->prevInMostRecentUsage->nextInMostRecentUsage = entry->nextInMostRecentUsage; + } + else + indexDataCacheFirst = entry->nextInMostRecentUsage; + if (entry->nextInMostRecentUsage) + { + entry->nextInMostRecentUsage->prevInMostRecentUsage = entry->prevInMostRecentUsage; + } + else + indexDataCacheLast = entry->prevInMostRecentUsage; + entry->prevInMostRecentUsage = nullptr; + entry->nextInMostRecentUsage = nullptr; +} + +void _removeFromBucket(indexDataCacheEntry2_t* entry) +{ + uint32 indexDataBucketIdx = (uint32)((entry->key.physAddr + entry->key.count) ^ (entry->key.physAddr >> 16)) % INDEX_DATA_CACHE_BUCKETS; + if (indexDataCacheBucket[indexDataBucketIdx] == entry) + { + indexDataCacheBucket[indexDataBucketIdx] = entry->nextInBucket; + entry->nextInBucket = nullptr; + return; + } + indexDataCacheEntry2_t* cacheEntryItr = indexDataCacheBucket[indexDataBucketIdx]; + while (cacheEntryItr) + { + if (cacheEntryItr->nextInBucket == entry) + { + cacheEntryItr->nextInBucket = entry->nextInBucket; + entry->nextInBucket = nullptr; + return; + } + // next + cacheEntryItr = cacheEntryItr->nextInBucket; + } +} + +void _decodeAndUploadIndexData(indexDataCacheEntry2_t* cacheEntry) +{ + uint32 count = cacheEntry->key.count; + uint32 primitiveRestartIndex = cacheEntry->key.primitiveRestartIndex; + if (cacheEntry->indexType == _INDEX_TYPE::U16_BE) + { + // 16bit indices + uint16* indexInputU16 = (uint16*)memory_getPointerFromPhysicalOffset(cacheEntry->key.physAddr); + uint16* indexOutputU16 = (uint16*)indexState.tempIndexStorage; + cemu_assert_debug(count != 0); + uint16 indexMinU16 = 0xFFFF; + uint16 indexMaxU16 = 0; + if (primitiveRestartIndex < 0x10000) + { + // with primitive restart index + uint16 primitiveRestartIndexU16 = (uint16)primitiveRestartIndex; + for (uint32 i = 0; i < count; i++) + { + uint16 idxU16 = _swapEndianU16(*indexInputU16); + indexInputU16++; + if (primitiveRestartIndexU16 != idxU16) + { + indexMinU16 = std::min(indexMinU16, idxU16); + indexMaxU16 = std::max(indexMaxU16, idxU16); + } + *indexOutputU16 = idxU16; + indexOutputU16++; + } + } + else + { + // without primitive restart index + for (uint32 i = 0; i < count; i++) + { + uint16 idxU16 = _swapEndianU16(*indexInputU16); + indexInputU16++; + indexMinU16 = std::min(indexMinU16, idxU16); + indexMaxU16 = std::max(indexMaxU16, idxU16); + *indexOutputU16 = idxU16; + indexOutputU16++; + } + } + cacheEntry->minIndex = indexMinU16; + cacheEntry->maxIndex = indexMaxU16; + glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, cacheEntry->heapEntry->startOffset, count * sizeof(uint16), indexState.tempIndexStorage); + performanceMonitor.cycle[performanceMonitor.cycleIndex].indexDataUploaded += (count * sizeof(uint16)); + } + else if(cacheEntry->indexType == _INDEX_TYPE::U32_BE) + { + // 32bit indices + uint32* indexInputU32 = (uint32*)memory_getPointerFromPhysicalOffset(cacheEntry->key.physAddr); + uint32* indexOutputU32 = (uint32*)indexState.tempIndexStorage; + cemu_assert_debug(count != 0); + uint32 indexMinU32 = _swapEndianU32(*indexInputU32); + uint32 indexMaxU32 = _swapEndianU32(*indexInputU32); + for (uint32 i = 0; i < count; i++) + { + uint32 idxU32 = _swapEndianU32(*indexInputU32); + indexInputU32++; + if (idxU32 != primitiveRestartIndex) + { + indexMinU32 = std::min(indexMinU32, idxU32); + indexMaxU32 = std::max(indexMaxU32, idxU32); + } + *indexOutputU32 = idxU32; + indexOutputU32++; + } + cacheEntry->minIndex = indexMinU32; + cacheEntry->maxIndex = indexMaxU32; + glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, cacheEntry->heapEntry->startOffset, count * sizeof(uint32), indexState.tempIndexStorage); + performanceMonitor.cycle[performanceMonitor.cycleIndex].indexDataUploaded += (count * sizeof(uint32)); + } + else + { + cemu_assert_debug(false); + } +} + + +void LatteDraw_cleanupAfterFrame() +{ + // drop everything from cache that is older than 30 frames + uint32 frameCounter = LatteGPUState.frameCounter; + while (indexDataCacheFirst) + { + indexDataCacheEntry2_t* entry = indexDataCacheFirst; + if ((frameCounter - entry->lastAccessFrameCount) < 30) + break; + // remove entry + virtualBufferHeap_free(indexState.indexBufferVirtualHeap, entry->heapEntry); + _removeFromUsageLinkedList(entry); + _removeFromBucket(entry); + free(entry); + } +} + +void LatteDrawGL_removeLeastRecentlyUsedIndexCacheEntries(sint32 count) +{ + while (indexDataCacheFirst && count > 0) + { + indexDataCacheEntry2_t* entry = indexDataCacheFirst; + // remove entry + virtualBufferHeap_free(indexState.indexBufferVirtualHeap, entry->heapEntry); + _removeFromUsageLinkedList(entry); + _removeFromBucket(entry); + free(entry); + count--; + } +} + +uint32 LatteDrawGL_calculateIndexDataHash(uint8* data, uint32 size) +{ + uint32 h = 0; + if (size < 16) + { + // hash the bytes individually + while (size != 0) + { + h += (uint32)*data; + data++; + size--; + } + return h; + } + // first 16 bytes + h += *(uint32*)(data + 0); + h += *(uint32*)(data + 4); + h += *(uint32*)(data + 8); + h += *(uint32*)(data + 12); + // last 16 bytes + data = data + ((size - 16)&~3); + h += *(uint32*)(data + 0); + h += *(uint32*)(data + 4); + h += *(uint32*)(data + 8); + h += *(uint32*)(data + 12); + return h; +} + +// index handling with cache +// todo - Outdated cache implementation. Update OpenGL renderer to use the generic implementation that is also used by the Vulkan renderer +void LatteDrawGL_prepareIndicesWithGPUCache(MPTR indexDataMPTR, _INDEX_TYPE indexType, sint32 count, sint32 primitiveMode) +{ + if (indexType == _INDEX_TYPE::AUTO) + { + indexState.minIndex = 0; + indexState.maxIndex = count - 1; + // since no indices are used we don't need to unbind the element array buffer + return; // automatic indices + } + + OpenGLRenderer::SetArrayElementBuffer(indexState.glIndexCacheBuffer); + uint32 indexDataBucketIdx = (uint32)((indexDataMPTR + count) ^ (indexDataMPTR >> 16)) % INDEX_DATA_CACHE_BUCKETS; + // find matching entry + uint32 primitiveRestartIndex = LatteGPUState.contextNew.VGT_MULTI_PRIM_IB_RESET_INDX.get_RESTART_INDEX(); + indexDataCacheEntry2_t* cacheEntryItr = indexDataCacheBucket[indexDataBucketIdx]; + indexDataCacheKey_t compareKey; + compareKey.physAddr = indexDataMPTR; + compareKey.count = count; + compareKey.primitiveMode = primitiveMode; + compareKey.primitiveRestartIndex = primitiveRestartIndex; + while (cacheEntryItr) + { + if (memcmp(&(cacheEntryItr->key), &compareKey, sizeof(compareKey)) != 0) + { + // next + cacheEntryItr = cacheEntryItr->nextInBucket; + continue; + } + // entry found + indexState.minIndex = cacheEntryItr->minIndex; + indexState.maxIndex = cacheEntryItr->maxIndex; + indexState.indexData = (uint8*)(size_t)cacheEntryItr->heapEntry->startOffset; + cacheEntryItr->lastAccessFrameCount = LatteGPUState.frameCounter; + // check if the data changed + uint32 h = LatteDrawGL_calculateIndexDataHash(memory_getPointerFromPhysicalOffset(indexDataMPTR), cacheEntryItr->physSize); + if (cacheEntryItr->hash != h) + { + forceLogDebug_printf("IndexData hash changed"); + _decodeAndUploadIndexData(cacheEntryItr); + cacheEntryItr->hash = h; + } + // move entry to the front + _removeFromUsageLinkedList(cacheEntryItr); + _appendToUsageLinkedList(cacheEntryItr); + return; + } + // calculate size of index data in cache + sint32 cacheIndexDataSize = 0; + if (indexType == _INDEX_TYPE::U16_BE || indexType == _INDEX_TYPE::U16_LE) + cacheIndexDataSize = count * sizeof(uint16); + else + cacheIndexDataSize = count * sizeof(uint32); + // no matching entry, create new one + VirtualBufferHeapEntry_t* heapEntry = virtualBufferHeap_allocate(indexState.indexBufferVirtualHeap, cacheIndexDataSize); + if (heapEntry == nullptr) + { + while (true) + { + LatteDrawGL_removeLeastRecentlyUsedIndexCacheEntries(10); + heapEntry = virtualBufferHeap_allocate(indexState.indexBufferVirtualHeap, cacheIndexDataSize); + if (heapEntry != nullptr) + break; + if (indexDataCacheFirst == nullptr) + { + forceLog_printf("Unable to allocate entry in index cache"); + assert_dbg(); + } + } + } + indexDataCacheEntry2_t* cacheEntry = (indexDataCacheEntry2_t*)malloc(sizeof(indexDataCacheEntry2_t)); + memset(cacheEntry, 0, sizeof(indexDataCacheEntry2_t)); + cacheEntry->key.physAddr = indexDataMPTR; + cacheEntry->physSize = (indexType == _INDEX_TYPE::U16_BE || indexType == _INDEX_TYPE::U16_LE) ? (count * sizeof(uint16)) : (count * sizeof(uint32)); + cacheEntry->hash = LatteDrawGL_calculateIndexDataHash(memory_getPointerFromPhysicalOffset(indexDataMPTR), cacheEntry->physSize); + cacheEntry->key.count = count; + cacheEntry->key.primitiveRestartIndex = primitiveRestartIndex; + cacheEntry->indexType = indexType; + cacheEntry->key.primitiveMode = primitiveMode; + cacheEntry->heapEntry = heapEntry; + cacheEntry->lastAccessFrameCount = LatteGPUState.frameCounter; + // append entry in bucket list + cacheEntry->nextInBucket = indexDataCacheBucket[indexDataBucketIdx]; + indexDataCacheBucket[indexDataBucketIdx] = cacheEntry; + // append as most recently used entry + _appendToUsageLinkedList(cacheEntry); + // decode and upload the data + _decodeAndUploadIndexData(cacheEntry); + + indexDataCacheEntryCount++; + indexState.minIndex = cacheEntry->minIndex; + indexState.maxIndex = cacheEntry->maxIndex; + indexState.indexData = (uint8*)(size_t)cacheEntry->heapEntry->startOffset; +} + +void LatteDraw_handleSpecialState8_clearAsDepth() +{ + if (LatteGPUState.contextNew.GetSpecialStateValues()[0] == 0) + forceLogDebug_printf("Special state 8 requires special state 0 but it is not set?"); + // get depth buffer information + uint32 regDepthBuffer = LatteGPUState.contextRegister[mmDB_HTILE_DATA_BASE]; + uint32 regDepthSize = LatteGPUState.contextRegister[mmDB_DEPTH_SIZE]; + uint32 regDepthBufferInfo = LatteGPUState.contextRegister[mmDB_DEPTH_INFO]; + // get format and tileMode from info reg + uint32 depthBufferTileMode = (regDepthBufferInfo >> 15) & 0xF; + + MPTR depthBufferPhysMem = regDepthBuffer << 8; + uint32 depthBufferPitch = (((regDepthSize >> 0) & 0x3FF) + 1); + uint32 depthBufferHeight = ((((regDepthSize >> 10) & 0xFFFFF) + 1) / depthBufferPitch); + depthBufferPitch <<= 3; + depthBufferHeight <<= 3; + uint32 depthBufferWidth = depthBufferPitch; + + sint32 sliceIndex = 0; // todo + sint32 mipIndex = 0; + + // clear all color buffers that match the format of the depth buffer + sint32 searchIndex = 0; + bool targetFound = false; + while (true) + { + LatteTextureView* view = LatteTC_LookupTextureByData(depthBufferPhysMem, depthBufferWidth, depthBufferHeight, depthBufferPitch, 0, 1, sliceIndex, 1, &searchIndex); + if (view != nullptr) + { + sint32 effectiveClearWidth = view->baseTexture->width; + sint32 effectiveClearHeight = view->baseTexture->height; + LatteTexture_scaleToEffectiveSize(view->baseTexture, &effectiveClearWidth, &effectiveClearHeight, 0); + + // hacky way to get clear color + float* regClearColor = (float*)(LatteGPUState.contextRegister + 0xC000 + 0); // REG_BASE_ALU_CONST + + uint8 clearColor[4] = { 0 }; + clearColor[0] = (uint8)(regClearColor[0] * 255.0f); + clearColor[1] = (uint8)(regClearColor[1] * 255.0f); + clearColor[2] = (uint8)(regClearColor[2] * 255.0f); + clearColor[3] = (uint8)(regClearColor[3] * 255.0f); + + // todo - use fragment shader software emulation (evoke for one pixel) to determine clear color + // todo - dont clear entire slice, use effectiveClearWidth, effectiveClearHeight + + if (g_renderer->GetType() == RendererAPI::OpenGL) + { + //cemu_assert_debug(false); // implement g_renderer->texture_clearColorSlice properly for OpenGL renderer + if (glClearTexSubImage) + glClearTexSubImage(((LatteTextureViewGL*)view)->glTexId, mipIndex, 0, 0, 0, effectiveClearWidth, effectiveClearHeight, 1, GL_RGBA, GL_UNSIGNED_BYTE, clearColor); + } + else + { + g_renderer->texture_clearColorSlice(view->baseTexture, sliceIndex + view->firstSlice, mipIndex + view->firstMip, clearColor[0], clearColor[1], clearColor[2], clearColor[3]); + } + } + } +} + +void LatteDrawGL_doDraw(_INDEX_TYPE indexType, uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count) +{ + if (indexType == _INDEX_TYPE::U16_BE) + { + // 16bit index, big endian + if (instanceCount > 1 || baseInstance != 0) + { + glDrawElementsInstancedBaseVertexBaseInstance(sGLActiveDrawMode, count, GL_UNSIGNED_SHORT, indexState.indexData, instanceCount, baseVertex, baseInstance); + } + else + { + if (baseVertex != 0) + glDrawRangeElementsBaseVertex(sGLActiveDrawMode, indexState.minIndex, indexState.maxIndex, count, GL_UNSIGNED_SHORT, indexState.indexData, baseVertex); + else + glDrawRangeElements(sGLActiveDrawMode, indexState.minIndex, indexState.maxIndex, count, GL_UNSIGNED_SHORT, indexState.indexData); + } + } + else if (indexType == _INDEX_TYPE::U32_BE) + { + // 32bit index, big endian + if (instanceCount > 1 || baseInstance != 0) + { + //debug_printf("Render instanced\n"); + glDrawElementsInstancedBaseVertexBaseInstance(sGLActiveDrawMode, count, GL_UNSIGNED_INT, indexState.indexData, instanceCount, baseVertex, baseInstance); + } + else + { + glDrawRangeElementsBaseVertex(sGLActiveDrawMode, indexState.minIndex, indexState.maxIndex, count, GL_UNSIGNED_INT, indexState.indexData, baseVertex); + } + } + else if (indexType == _INDEX_TYPE::AUTO) + { + // render without index (automatic index generation) + cemu_assert_debug(baseInstance == 0); + if (instanceCount > 1) + glDrawArraysInstanced(sGLActiveDrawMode, baseVertex, count, instanceCount); + else + { + glDrawArrays(sGLActiveDrawMode, baseVertex, count); + } + } + else + { + cemu_assert_debug(false); + } +} + +uint32 _glVertexBufferOffset[32] = { 0 }; + +void OpenGLRenderer::buffer_bindVertexBuffer(uint32 bufferIndex, uint32 offset, uint32 size) +{ + _glVertexBufferOffset[bufferIndex] = offset; +} + +void OpenGLRenderer::buffer_bindUniformBuffer(LatteConst::ShaderType shaderType, uint32 bufferIndex, uint32 offset, uint32 size) +{ + switch (shaderType) + { + case LatteConst::ShaderType::Vertex: + bufferIndex += 0; + break; + case LatteConst::ShaderType::Pixel: + bufferIndex += 32; + break; + case LatteConst::ShaderType::Geometry: + bufferIndex += 64; + break; + } + + if (offset == 0 && size == 0) + { + // when binding NULL we just bind some arbitrary undefined data so the OpenGL driver is happy since a size of 0 is not allowed (should we bind a buffer filled with zeroes instead?) + glBindBufferRange(GL_UNIFORM_BUFFER, bufferIndex, glAttributeCacheAB, 0, 32); + return; + } + glBindBufferRange(GL_UNIFORM_BUFFER, bufferIndex, glAttributeCacheAB, offset, size); +} + +void LatteDraw_resetAttributePointerCache(); + +void _resetAttributes(LatteParsedFetchShaderBufferGroup_t* attribGroup, bool* attributeArrayUsed) +{ + for (sint32 i = 0; i < attribGroup->attribCount; i++) + { + LatteParsedFetchShaderAttribute_t* attrib = attribGroup->attrib + i; + sint32 attributeShaderLocation = attrib->semanticId; // we now bind to the semanticId instead + attributeArrayUsed[attributeShaderLocation] = false; + } +} + +void OpenGLRenderer::_setupVertexAttributes() +{ + LatteFetchShader* fetchShader = LatteSHRC_GetActiveFetchShader(); + LatteDecompilerShader* vertexShader = LatteSHRC_GetActiveVertexShader(); + + catchOpenGLError(); + + // bind buffer + attributeStream_bindVertexCacheBuffer(); + + catchOpenGLError(); + LatteFetchShader* parsedFetchShader = LatteSHRC_GetActiveFetchShader(); + bool attributeArrayUsed[32] = { 0 }; // used to keep track of enabled vertex attributes for this shader + sint32 attributeDataIndex = 0; + uint32 vboDataOffset = 0; + + bool tfBufferIsBound = false; + sint32 maxReallocAttemptLimit = 1; + + for(auto& bufferGroup : parsedFetchShader->bufferGroups) + { + uint32 bufferIndex = bufferGroup.attributeBufferIndex; + uint32 bufferBaseRegisterIndex = mmSQ_VTX_ATTRIBUTE_BLOCK_START + bufferIndex * 7; + MPTR bufferAddress = LatteGPUState.contextRegister[bufferBaseRegisterIndex + 0]; + uint32 bufferSize = LatteGPUState.contextRegister[bufferBaseRegisterIndex + 1] + 1; + uint32 bufferStride = (LatteGPUState.contextRegister[bufferBaseRegisterIndex + 2] >> 11) & 0xFFFF; + + if (bufferAddress == MPTR_NULL) + { + _resetAttributes(&bufferGroup, attributeArrayUsed); + continue; + } + + vboDataOffset = _glVertexBufferOffset[bufferIndex]; + + for (sint32 i = 0; i < bufferGroup.attribCount; i++) + { + LatteParsedFetchShaderAttribute_t* attrib = bufferGroup.attrib + i; + sint32 attributeShaderLocation = attrib->semanticId; // we now bind to the semanticId instead + + attributeShaderLocation = vertexShader->resourceMapping.getAttribHostShaderIndex(attrib->semanticId); + if (attributeShaderLocation == -1) + continue; // attribute not used + if (attributeShaderLocation >= GPU_GL_MAX_NUM_ATTRIBUTE) + continue; + if (attributeArrayUsed[attributeShaderLocation] == true) + { + debug_printf("Fetch shader attribute is bound multiple times\n"); + } + + // get buffer + uint32 bufferIndex = attrib->attributeBufferIndex; + cemu_assert_debug(bufferIndex < 0x10); + + cemu_assert_debug(attrib->fetchType == LatteConst::VERTEX_DATA || attrib->fetchType == LatteConst::INSTANCE_DATA); // unsupported fetch type + + SetAttributeArrayState(attributeShaderLocation, true, (bufferStride == 0) ? 99999999 : attrib->aluDivisor); + + uint8* bufferInput = memory_getPointerFromPhysicalOffset(bufferAddress) + attrib->offset; + uint32 bufferSizeInput = bufferSize - attrib->offset; + + uint8* vboGLPtr; + vboGLPtr = (uint8*)(size_t)(vboDataOffset + attrib->offset); + _setAttributeBufferPointerRaw(attributeShaderLocation, NULL, 0, bufferStride, attrib, vboGLPtr, bufferStride); + + attributeArrayUsed[attributeShaderLocation] = true; + attributeDataIndex++; + catchOpenGLError(); + } + } + for (uint32 i = 0; i < GPU_GL_MAX_NUM_ATTRIBUTE; i++) + { + if (attributeArrayUsed[i] == false && glAttributeArrayIsEnabled[i] == true) + SetAttributeArrayState(i, false, -1); + } +} + +void rectsEmulationGS_outputSingleVertex(std::string& gsSrc, LatteDecompilerShader* vertexShader, LatteShaderPSInputTable* psInputTable, sint32 vIdx); +void rectsEmulationGS_outputGeneratedVertex(std::string& gsSrc, LatteDecompilerShader* vertexShader, LatteShaderPSInputTable* psInputTable, const char* variant); +void rectsEmulationGS_outputVerticesCode(std::string& gsSrc, LatteDecompilerShader* vertexShader, LatteShaderPSInputTable* psInputTable, sint32 p0, sint32 p1, sint32 p2, sint32 p3, const char* variant, const LatteContextRegister& latteRegister); + +std::map<uint64, RendererShaderGL*> g_mapGLRectEmulationGS; + +RendererShaderGL* rectsEmulationGS_generateShaderGL(LatteDecompilerShader* vertexShader) +{ + LatteShaderPSInputTable* psInputTable = LatteSHRC_GetPSInputTable(); + + std::string gsSrc; + gsSrc.append("#version 450\r\n"); + + // layout + gsSrc.append("layout(triangles) in;\r\n"); + gsSrc.append("layout(triangle_strip) out;\r\n"); + gsSrc.append("layout(max_vertices = 4) out;\r\n"); + + // gl_PerVertex input + + gsSrc.append("in gl_PerVertex {\r\n"); + gsSrc.append("vec4 gl_Position;\r\n"); + gsSrc.append("} gl_in[];\r\n"); + + // gl_PerVertex output + + gsSrc.append("out gl_PerVertex {\r\n"); + gsSrc.append("vec4 gl_Position;\r\n"); + gsSrc.append("};\r\n"); + + // inputs & outputs + auto parameterMask = vertexShader->outputParameterMask; + for (sint32 f = 0; f < 2; f++) + { + for (uint32 i = 0; i < 32; i++) + { + if ((parameterMask & (1 << i)) == 0) + continue; + sint32 vsSemanticId = psInputTable->getVertexShaderOutParamSemanticId(LatteGPUState.contextRegister, i); + if (vsSemanticId < 0) + continue; + auto psImport = psInputTable->getPSImportBySemanticId(vsSemanticId); + if (psImport == nullptr) + continue; + + gsSrc.append(fmt::format("layout(location = {}) ", psInputTable->getPSImportLocationBySemanticId(vsSemanticId))); + if (psImport->isFlat) + gsSrc.append("flat "); + if (psImport->isNoPerspective) + gsSrc.append("noperspective "); + + if (f == 0) + gsSrc.append("in"); + else + gsSrc.append("out"); + + if (f == 0) + gsSrc.append(fmt::format(" vec4 passParameterSem{}In[];\r\n", vsSemanticId)); + else + gsSrc.append(fmt::format(" vec4 passParameterSem{}Out;\r\n", vsSemanticId)); + } + } + + // gen function + gsSrc.append("vec4 gen4thVertexA(vec4 a, vec4 b, vec4 c)\r\n"); + gsSrc.append("{\r\n"); + gsSrc.append("return b - (c - a);\r\n"); + gsSrc.append("}\r\n"); + + gsSrc.append("vec4 gen4thVertexB(vec4 a, vec4 b, vec4 c)\r\n"); + gsSrc.append("{\r\n"); + gsSrc.append("return c - (b - a);\r\n"); + gsSrc.append("}\r\n"); + + gsSrc.append("vec4 gen4thVertexC(vec4 a, vec4 b, vec4 c)\r\n"); + gsSrc.append("{\r\n"); + gsSrc.append("return c + (b - a);\r\n"); + gsSrc.append("}\r\n"); + + // main + gsSrc.append("void main()\r\n"); + gsSrc.append("{\r\n"); + + // there are two possible winding orders that need different triangle generation: + // 0 1 + // 2 3 + // and + // 0 1 + // 3 2 + // all others are just symmetries of these cases + + // we can determine the case by comparing the distance 0<->1 and 0<->2 + + gsSrc.append("float dist0_1 = length(gl_in[1].gl_Position.xy - gl_in[0].gl_Position.xy);\r\n"); + gsSrc.append("float dist0_2 = length(gl_in[2].gl_Position.xy - gl_in[0].gl_Position.xy);\r\n"); + gsSrc.append("float dist1_2 = length(gl_in[2].gl_Position.xy - gl_in[1].gl_Position.xy);\r\n"); + + // emit vertices + gsSrc.append("if(dist0_1 > dist0_2 && dist0_1 > dist1_2)\r\n"); + gsSrc.append("{\r\n"); + // p0 to p1 is diagonal + rectsEmulationGS_outputVerticesCode(gsSrc, vertexShader, psInputTable, 2, 1, 0, 3, "A", LatteGPUState.contextNew); + gsSrc.append("} else if ( dist0_2 > dist0_1 && dist0_2 > dist1_2 ) {\r\n"); + // p0 to p2 is diagonal + rectsEmulationGS_outputVerticesCode(gsSrc, vertexShader, psInputTable, 1, 2, 0, 3, "B", LatteGPUState.contextNew); + gsSrc.append("} else {\r\n"); + // p1 to p2 is diagonal + rectsEmulationGS_outputVerticesCode(gsSrc, vertexShader, psInputTable, 0, 1, 2, 3, "C", LatteGPUState.contextNew); + gsSrc.append("}\r\n"); + + gsSrc.append("}\r\n"); + + auto glShader = new RendererShaderGL(RendererShader::ShaderType::kGeometry, 0, 0, false, false, gsSrc); + glShader->PreponeCompilation(true); + + return glShader; +} + +RendererShaderGL* rectsEmulationGS_getShaderGL(LatteDecompilerShader* vertexShader) +{ + LatteShaderPSInputTable* psInputTable = LatteSHRC_GetPSInputTable(); + + uint64 h = vertexShader->baseHash + psInputTable->key; + + auto itr = g_mapGLRectEmulationGS.find(h); + if (itr != g_mapGLRectEmulationGS.end()) + return (*itr).second; + + auto glShader = rectsEmulationGS_generateShaderGL(vertexShader); + + g_mapGLRectEmulationGS.emplace(h, glShader); + return glShader; +} + +uint32 sPrevTextureReadbackDrawcallUpdate = 0; + +template<bool TIsMinimal, bool THasProfiling> +void OpenGLRenderer::draw_genericDrawHandler(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType) +{ + ReleaseBufferCacheEntries(); + + catchOpenGLError(); + void* indexData = indexDataMPTR != MPTR_NULL ? memory_getPointerFromPhysicalOffset(indexDataMPTR) : NULL; + auto primitiveMode = LatteGPUState.contextNew.VGT_PRIMITIVE_TYPE.get_PRIMITIVE_MODE(); + // handle special state 8 (clear as depth) + if (LatteGPUState.contextNew.GetSpecialStateValues()[8] != 0) + { + LatteDraw_handleSpecialState8_clearAsDepth(); + LatteGPUState.drawCallCounter++; + return; + } + + // update shaders and uniforms + if constexpr (!TIsMinimal) + { + beginPerfMonProfiling(performanceMonitor.gpuTime_dcStageShaderAndUniformMgr); + LatteSHRC_UpdateActiveShaders(); + endPerfMonProfiling(performanceMonitor.gpuTime_dcStageShaderAndUniformMgr); + } + if (LatteGPUState.activeShaderHasError) + { + debug_printf("Skipped drawcall due to shader error\n"); + return; + } + // check for blacklisted shaders + uint64 vsShaderHash = 0; + if (LatteSHRC_GetActiveVertexShader()) + vsShaderHash = LatteSHRC_GetActiveVertexShader()->baseHash; + uint64 psShaderHash = 0; + if (LatteSHRC_GetActivePixelShader()) + psShaderHash = LatteSHRC_GetActivePixelShader()->baseHash; + + // setup streamout (if enabled) + bool rasterizerEnable = LatteGPUState.contextNew.PA_CL_CLIP_CNTL.get_DX_RASTERIZATION_KILL() == false; + if (!LatteGPUState.contextNew.PA_CL_VTE_CNTL.get_VPORT_X_OFFSET_ENA()) + rasterizerEnable = true; + + bool streamoutEnable = LatteGPUState.contextRegister[mmVGT_STRMOUT_EN] != 0; + if (streamoutEnable) + { + if (glBeginTransformFeedback == nullptr || LatteGPUState.glVendor == GLVENDOR_INTEL_NOLEGACY) + { + cemu_assert_debug(false); + return; // transform feedback not supported + } + } + // skip draw if output is not used + if (rasterizerEnable == false && streamoutEnable == false) + { + // rasterizer and streamout disabled + LatteGPUState.drawCallCounter++; + return; + } + // get primitive + if (primitiveMode == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::TRIANGLES) + sGLActiveDrawMode = GL_TRIANGLES; + else if (primitiveMode == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::TRIANGLE_STRIP) + sGLActiveDrawMode = GL_TRIANGLE_STRIP; + else if (primitiveMode == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::QUADS) + sGLActiveDrawMode = GL_QUADS; + else if (primitiveMode == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::TRIANGLE_FAN) + sGLActiveDrawMode = GL_TRIANGLE_FAN; + else if (primitiveMode == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::RECTS) + sGLActiveDrawMode = GL_TRIANGLES; + else if (primitiveMode == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::POINTS) + sGLActiveDrawMode = GL_POINTS; + else if (primitiveMode == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::LINES) + sGLActiveDrawMode = GL_LINES; + else if (primitiveMode == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::LINE_STRIP) + sGLActiveDrawMode = GL_LINE_STRIP; + else if (primitiveMode == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::LINE_LOOP) + sGLActiveDrawMode = GL_LINE_LOOP; + else if (primitiveMode == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::QUAD_STRIP) + sGLActiveDrawMode = GL_QUAD_STRIP; + else + { + cemu_assert_debug(false); // unsupported primitive type + LatteGPUState.drawCallCounter++; + return; + } + + if constexpr (!TIsMinimal) + { + // update render targets and textures + LatteGPUState.requiresTextureBarrier = false; + beginPerfMonProfiling(performanceMonitor.gpuTime_dcStageTextures); + while (true) + { + LatteGPUState.repeatTextureInitialization = false; + if (streamoutEnable == false) + { + // only handle rendertargets if streamout is inactive + if (LatteMRT::UpdateCurrentFBO() == false) + return; // no render target + if (hasValidFramebufferAttached == false) + return; + } + LatteTexture_updateTextures(); // caution: Do not call any functions that potentially modify texture bindings after this line + if (LatteGPUState.repeatTextureInitialization == false) + break; + catchOpenGLError(); + } + endPerfMonProfiling(performanceMonitor.gpuTime_dcStageTextures); + + beginPerfMonProfiling(performanceMonitor.gpuTime_dcStageMRT); + LatteMRT::ApplyCurrentState(); + endPerfMonProfiling(performanceMonitor.gpuTime_dcStageMRT); + + // texture barrier for write-read patterns + if (LatteGPUState.requiresTextureBarrier && glTextureBarrier) + { + glTextureBarrier(); + } + + } + + catchOpenGLError(); + // handle RECT primitive + if (primitiveMode == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::RECTS) + { + RendererShaderGL* rectsEmulationShader = rectsEmulationGS_getShaderGL(LatteSHRC_GetActiveVertexShader()); + shader_bind(rectsEmulationShader); + } + + // prepare index cache + beginPerfMonProfiling(performanceMonitor.gpuTime_dcStageIndexMgr); + LatteDrawGL_prepareIndicesWithGPUCache(indexDataMPTR, indexType, count, (sint32)primitiveMode); + endPerfMonProfiling(performanceMonitor.gpuTime_dcStageIndexMgr); + + // synchronize vertex and uniform buffers + LatteBufferCache_Sync(indexState.minIndex + baseVertex, indexState.maxIndex + baseVertex, baseInstance, instanceCount); + + _setupVertexAttributes(); + + // update renderstate + LatteRenderTarget_updateViewport(); + LatteRenderTarget_updateScissorBox(); + renderstate_updateBlendingAndColorControl(); + catchOpenGLError(); + // handle special state 5 (convert depth to color) + if (LatteGPUState.contextNew.GetSpecialStateValues()[5] != 0) + { + debug_printf("GPU7 special state 5 used\n"); + LatteTextureView* rt_color = LatteMRT::GetColorAttachment(0); + LatteTextureView* rt_depth = LatteMRT::GetDepthAttachment(); + if (!rt_depth || !rt_color) + { + cemuLog_force("GPU7 special state 5 used but render target not setup correctly"); + return; + } + surfaceCopy_copySurfaceWithFormatConversion(rt_depth->baseTexture, rt_depth->firstMip, rt_depth->firstSlice, rt_color->baseTexture, rt_color->firstMip, rt_color->firstSlice, rt_depth->baseTexture->width, rt_depth->baseTexture->height); + LatteGPUState.drawCallCounter++; + return; + } + + beginPerfMonProfiling(performanceMonitor.gpuTime_dcStageShaderAndUniformMgr); + // update uniform values + uniformData_update(); + endPerfMonProfiling(performanceMonitor.gpuTime_dcStageShaderAndUniformMgr); + catchOpenGLError(); + // upload special uniforms + LatteDecompilerShader* vertexShader = LatteSHRC_GetActiveVertexShader(); + LatteDecompilerShader* pixelShader = LatteSHRC_GetActivePixelShader(); + LatteDecompilerShader* geometryShader = LatteSHRC_GetActiveGeometryShader(); + if (vertexShader) + { + auto vertexShaderGL = (RendererShaderGL*)vertexShader->shader; + if (vertexShader->uniform.loc_windowSpaceToClipSpaceTransform >= 0) + { + sint32 viewportWidth; + sint32 viewportHeight; + LatteRenderTarget_GetCurrentVirtualViewportSize(&viewportWidth, &viewportHeight); // always call after _updateViewport() + float t[2]; + t[0] = 2.0f / (float)viewportWidth; + t[1] = 2.0f / (float)viewportHeight; + glProgramUniform2fv(vertexShaderGL->GetProgram(), vertexShader->uniform.loc_windowSpaceToClipSpaceTransform, 1, t); + } + // update uf_texRescaleFactors + for (auto& entry : vertexShader->uniform.list_ufTexRescale) + { + float* xyScale = LatteTexture_getEffectiveTextureScale(LatteConst::ShaderType::Vertex, entry.texUnit); + if (memcmp(entry.currentValue, xyScale, sizeof(float) * 2) == 0) + continue; // value unchanged + memcpy(entry.currentValue, xyScale, sizeof(float) * 2); + glProgramUniform2fv(vertexShaderGL->GetProgram(), entry.uniformLocation, 1, xyScale); + } + // update uf_pointSize + if (vertexShader->uniform.loc_pointSize >= 0) + { + float t[1]; + float pointWidth = (float)LatteGPUState.contextNew.PA_SU_POINT_SIZE.get_WIDTH() / 8.0f; + if (pointWidth == 0.0f) + pointWidth = 1.0f / 8.0f; // minimum size + t[0] = pointWidth; + glProgramUniform1fv(vertexShaderGL->GetProgram(), vertexShader->uniform.loc_pointSize, 1, t); + } + } + if (geometryShader) + { + auto geometryShaderGL = (RendererShaderGL*)geometryShader->shader; + // update uf_texRescaleFactors + for (auto& entry : geometryShader->uniform.list_ufTexRescale) + { + float* xyScale = LatteTexture_getEffectiveTextureScale(LatteConst::ShaderType::Geometry, entry.texUnit); + if (memcmp(entry.currentValue, xyScale, sizeof(float) * 2) == 0) + continue; // value unchanged + memcpy(entry.currentValue, xyScale, sizeof(float) * 2); + glProgramUniform2fv(geometryShaderGL->GetProgram(), entry.uniformLocation, 1, xyScale); + } + // update uf_pointSize + if (geometryShader->uniform.loc_pointSize >= 0) + { + float t[1]; + float pointWidth = (float)LatteGPUState.contextNew.PA_SU_POINT_SIZE.get_WIDTH() / 8.0f; + if (pointWidth == 0.0f) + pointWidth = 1.0f / 8.0f; // minimum size + t[0] = pointWidth; + glProgramUniform1fv(geometryShaderGL->GetProgram(), geometryShader->uniform.loc_pointSize, 1, t); + } + } + if (pixelShader) + { + auto pixelShaderGL = (RendererShaderGL*)pixelShader->shader; + if (pixelShader->uniform.loc_alphaTestRef >= 0) + { + float t[1]; + t[0] = LatteGPUState.contextNew.SX_ALPHA_REF.get_ALPHA_TEST_REF(); + if (pixelShader->uniform.ufCurrentValueAlphaTestRef != t[0]) + { + glProgramUniform1fv(pixelShaderGL->GetProgram(), pixelShader->uniform.loc_alphaTestRef, 1, t); + pixelShader->uniform.ufCurrentValueAlphaTestRef = t[0]; + } + } + // update uf_fragCoordScale + if (pixelShader->uniform.loc_fragCoordScale >= 0) + { + float coordScale[4]; + LatteMRT::GetCurrentFragCoordScale(coordScale); + if (pixelShader->uniform.ufCurrentValueFragCoordScale[0] != coordScale[0] || pixelShader->uniform.ufCurrentValueFragCoordScale[1] != coordScale[1]) + { + glProgramUniform2fv(pixelShaderGL->GetProgram(), pixelShader->uniform.loc_fragCoordScale, 1, coordScale); + pixelShader->uniform.ufCurrentValueFragCoordScale[0] = coordScale[0]; + pixelShader->uniform.ufCurrentValueFragCoordScale[1] = coordScale[1]; + } + } + // update uf_texRescaleFactors + for (auto& entry : pixelShader->uniform.list_ufTexRescale) + { + float* xyScale = LatteTexture_getEffectiveTextureScale(LatteConst::ShaderType::Pixel, entry.texUnit); + if (memcmp(entry.currentValue, xyScale, sizeof(float) * 2) == 0) + continue; // value unchanged + memcpy(entry.currentValue, xyScale, sizeof(float) * 2); + glProgramUniform2fv(pixelShaderGL->GetProgram(), entry.uniformLocation, 1, xyScale); + } + } + catchOpenGLError(); + // prepare streamout + LatteStreamout_PrepareDrawcall(count, instanceCount); + + if (streamoutEnable && rasterizerEnable) + { + if (primitiveMode == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::QUADS) + sGLActiveDrawMode = GL_POINTS; + } + catchOpenGLError(); + // render + beginPerfMonProfiling(performanceMonitor.gpuTime_dcStageDrawcallAPI); + LatteDrawGL_doDraw(indexType, baseVertex, baseInstance, instanceCount, count); + endPerfMonProfiling(performanceMonitor.gpuTime_dcStageDrawcallAPI); + // post-drawcall logic + if(pixelShader) + LatteRenderTarget_trackUpdates(); + LatteStreamout_FinishDrawcall(false); + catchOpenGLError(); + + if (primitiveMode == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::RECTS) + shader_unbind(RendererShader::ShaderType::kGeometry); + + LatteGPUState.drawCallCounter++; + if (streamoutEnable && rasterizerEnable) + { + // streamout and rasterizer enabled, repeat drawcall with streamout disabled + uint32 strmOutEnOrg = LatteGPUState.contextRegister[mmVGT_STRMOUT_EN]; + LatteGPUState.contextRegister[mmVGT_STRMOUT_EN] = 0; + draw_genericDrawHandler<false, THasProfiling>(baseVertex, baseInstance, instanceCount, count, indexDataMPTR, indexType); + LatteGPUState.contextRegister[mmVGT_STRMOUT_EN] = strmOutEnOrg; + return; + } + LatteTextureReadback_Update(); + uint32 dcSinceLastReadbackCheck = LatteGPUState.drawCallCounter - sPrevTextureReadbackDrawcallUpdate; + if (dcSinceLastReadbackCheck >= 150) + { + LatteTextureReadback_UpdateFinishedTransfers(false); + sPrevTextureReadbackDrawcallUpdate = LatteGPUState.drawCallCounter; + } + catchOpenGLError(); +} + +void OpenGLRenderer::draw_beginSequence() +{ + // no-op +} + +void OpenGLRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, bool isFirst) +{ + bool isMinimal = !isFirst; + if (ActiveSettings::FrameProfilerEnabled()) + { + if (isMinimal) + draw_genericDrawHandler<true, true>(baseVertex, baseInstance, instanceCount, count, indexDataMPTR, indexType); + else + draw_genericDrawHandler<false, true>(baseVertex, baseInstance, instanceCount, count, indexDataMPTR, indexType); + } + else + { + if (isMinimal) + draw_genericDrawHandler<true, false>(baseVertex, baseInstance, instanceCount, count, indexDataMPTR, indexType); + else + draw_genericDrawHandler<false, false>(baseVertex, baseInstance, instanceCount, count, indexDataMPTR, indexType); + } +} + +void OpenGLRenderer::draw_endSequence() +{ + // no-op +} + +#define GPU7_INDEX_BUFFER_CACHE_SIZE_DEPR (18*1024*1024) // 18MB + +void OpenGLRenderer::draw_init() +{ + if (indexState.initialized) + return; + indexState.initialized = true; + // create index buffer + glGenBuffers(1, &indexState.glIndexCacheBuffer); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexState.glIndexCacheBuffer); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, GPU7_INDEX_BUFFER_CACHE_SIZE_DEPR, NULL, GL_DYNAMIC_DRAW); +#if BOOST_OS_WINDOWS + indexState.mappedIndexBuffer = (uint8*)_aligned_malloc(GPU7_INDEX_BUFFER_CACHE_SIZE_DEPR, 256); +#else + indexState.mappedIndexBuffer = (uint8*)aligned_alloc(256, GPU7_INDEX_BUFFER_CACHE_SIZE_DEPR); +#endif + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + indexState.indexRingBuffer = LatteRingBuffer_create(indexState.mappedIndexBuffer, GPU7_INDEX_BUFFER_CACHE_SIZE_DEPR); + indexState.tempIndexStorage = (uint8*)malloc(1024 * 1024 * 8); + // create virtual heap for index buffer + indexState.indexBufferVirtualHeap = virtualBufferHeap_create(GPU7_INDEX_BUFFER_CACHE_SIZE_DEPR); +} + +void OpenGLRenderer::bufferCache_upload(uint8* buffer, sint32 size, uint32 bufferOffset) +{ + attributeStream_bindVertexCacheBuffer(); + glBufferSubData(GL_ARRAY_BUFFER, bufferOffset, size, buffer); +} + +void OpenGLRenderer::bufferCache_copy(uint32 srcOffset, uint32 dstOffset, uint32 size) +{ + attributeStream_bindVertexCacheBuffer(); + glCopyBufferSubData(GL_ARRAY_BUFFER, GL_ARRAY_BUFFER, srcOffset, dstOffset, size); +} + + +GLint glClampTable[] = +{ + GL_REPEAT, + GL_MIRRORED_REPEAT, + GL_CLAMP_TO_EDGE, + GL_MIRROR_CLAMP_TO_EDGE, + GL_CLAMP_TO_EDGE, + GL_MIRROR_CLAMP_TO_BORDER_EXT, + GL_CLAMP_TO_BORDER, + GL_MIRROR_CLAMP_TO_BORDER_EXT +}; + +GLint glCompSelTable[8] = +{ + GL_RED, + GL_GREEN, + GL_BLUE, + GL_ALPHA, + GL_ZERO, + GL_ONE, + 0, + 0 +}; + +GLint glDepthCompareTable[8] = { + GL_NEVER, + GL_LESS, + GL_EQUAL, + GL_LEQUAL, + GL_GREATER, + GL_NOTEQUAL, + GL_GEQUAL, + GL_ALWAYS +}; + +// Remaps component selection if the underlying OpenGL texture format would behave differently than it's GPU7 counterpart +uint32 _correctTextureCompSelGL(Latte::E_GX2SURFFMT format, uint32 compSel) +{ + switch (format) + { + case Latte::E_GX2SURFFMT::R8_UNORM: // R8 is replicated on all channels (while OpenGL would return 1.0 for BGA instead) + case Latte::E_GX2SURFFMT::R8_SNORM: // probably the same as _UNORM, but needs testing + if (compSel >= 1 && compSel <= 3) + compSel = 0; + break; + case Latte::E_GX2SURFFMT::A1_B5_G5_R5_UNORM: // order of components is reversed (RGBA -> ABGR) + if (compSel >= 0 && compSel <= 3) + compSel = 3 - compSel; + break; + case Latte::E_GX2SURFFMT::BC4_UNORM: + case Latte::E_GX2SURFFMT::BC4_SNORM: + if (compSel >= 1 && compSel <= 3) + compSel = 0; + break; + case Latte::E_GX2SURFFMT::BC5_UNORM: + case Latte::E_GX2SURFFMT::BC5_SNORM: + // RG maps to RG + // B maps to ? + // A maps to G (guessed) + if (compSel == 3) + compSel = 1; // read Alpha as Green + break; + case Latte::E_GX2SURFFMT::A2_B10_G10_R10_UNORM: + // reverse components (Wii U: ABGR, OpenGL: RGBA) + // used in Resident Evil Revelations + if (compSel >= 0 && compSel <= 3) + compSel = 3 - compSel; + break; + case Latte::E_GX2SURFFMT::X24_G8_UINT: + // map everything to alpha? + if (compSel >= 0 && compSel <= 3) + compSel = 3; + break; + default: + break; + } + return compSel; +} + +#define quickBindTexture() if( textureIsActive == false ) { g_renderer->texture_bindAndActivate(hostTextureView, hostTextureUnit); textureIsActive = true; } + +uint32 _getGLMinFilter(Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER filterMin, Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_Z_FILTER filterMip) +{ + bool isMinPointFilter = (filterMin == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER::POINT) || (filterMin == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER::ANISO_POINT); + + if (filterMip == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_Z_FILTER::NONE) + { + // no mip + return isMinPointFilter ? GL_NEAREST : GL_LINEAR; + } + else if (filterMip == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_Z_FILTER::POINT) + { + // nearest neighbor + return isMinPointFilter ? GL_NEAREST_MIPMAP_NEAREST : GL_LINEAR_MIPMAP_NEAREST; + } + // else -> filterMip == LINEAR + return isMinPointFilter ? GL_NEAREST_MIPMAP_LINEAR : GL_LINEAR_MIPMAP_LINEAR; +} + +/* +* Update channel swizzling and other texture settings for a texture unit +* hostTextureView is the texture unit view used on the host side +* The baseGX2TexUnit parameter is used to identify the shader stage in which this texture is accessed +*/ +void OpenGLRenderer::renderstate_updateTextureSettingsGL(LatteDecompilerShader* shaderContext, LatteTextureView* _hostTextureView, uint32 hostTextureUnit, const Latte::LATTE_SQ_TEX_RESOURCE_WORD4_N texUnitWord4, uint32 texUnitIndex, bool isDepthSampler) +{ + // todo - this is OpenGL-specific, decouple this from the renderer-neutral backend + auto hostTextureView = (LatteTextureViewGL*)_hostTextureView; + + LatteTexture* baseTexture = hostTextureView->baseTexture; + // get texture register word 0 + catchOpenGLError(); + bool textureIsActive = false; + + uint32 compSelR = (uint32)texUnitWord4.get_DST_SEL_X(); + uint32 compSelG = (uint32)texUnitWord4.get_DST_SEL_Y(); + uint32 compSelB = (uint32)texUnitWord4.get_DST_SEL_Z(); + uint32 compSelA = (uint32)texUnitWord4.get_DST_SEL_W(); + // on OpenGL some channels might be mapped differently + compSelR = _correctTextureCompSelGL(hostTextureView->format, compSelR); + compSelG = _correctTextureCompSelGL(hostTextureView->format, compSelG); + compSelB = _correctTextureCompSelGL(hostTextureView->format, compSelB); + compSelA = _correctTextureCompSelGL(hostTextureView->format, compSelA); + // update swizzle parameters + if (hostTextureView->swizzleR != compSelR) + { + quickBindTexture(); + glTexParameteri(hostTextureView->glTexTarget, GL_TEXTURE_SWIZZLE_R, glCompSelTable[compSelR]); + hostTextureView->swizzleR = compSelR; + } + if (hostTextureView->swizzleG != compSelG) + { + quickBindTexture(); + glTexParameteri(hostTextureView->glTexTarget, GL_TEXTURE_SWIZZLE_G, glCompSelTable[compSelG]); + hostTextureView->swizzleG = compSelG; + } + if (hostTextureView->swizzleB != compSelB) + { + quickBindTexture(); + glTexParameteri(hostTextureView->glTexTarget, GL_TEXTURE_SWIZZLE_B, glCompSelTable[compSelB]); + hostTextureView->swizzleB = compSelB; + } + if (hostTextureView->swizzleA != compSelA) + { + quickBindTexture(); + glTexParameteri(hostTextureView->glTexTarget, GL_TEXTURE_SWIZZLE_A, glCompSelTable[compSelA]); + hostTextureView->swizzleA = compSelA; + } + catchOpenGLError(); + + uint32 stageSamplerIndex = shaderContext->textureUnitSamplerAssignment[texUnitIndex]; + if (stageSamplerIndex != LATTE_DECOMPILER_SAMPLER_NONE) + { + uint32 samplerIndex = stageSamplerIndex; + samplerIndex += LatteDecompiler_getTextureSamplerBaseIndex(shaderContext->shaderType); + + const _LatteRegisterSetSampler* samplerWords = LatteGPUState.contextNew.SQ_TEX_SAMPLER + samplerIndex; + + auto filterMag = samplerWords->WORD0.get_XY_MAG_FILTER(); + auto filterMin = samplerWords->WORD0.get_XY_MAG_FILTER(); + //auto filterZ = samplerWords->WORD0.get_Z_FILTER(); + auto filterMip = samplerWords->WORD0.get_MIP_FILTER(); + + // get OpenGL constant for min filter + uint32 filterMinGL = _getGLMinFilter(filterMin, filterMip); + uint32 filterMagGL = (filterMag == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER::POINT || filterMag == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER::ANISO_POINT) ? GL_NEAREST : GL_LINEAR; + + // todo: z-filter is customizable for GPU7 but OpenGL doesn't offer the same functionality? + + LatteSamplerState* samplerState = &hostTextureView->samplerState; + + catchOpenGLError(); + + uint32 clampX = (uint32)samplerWords->WORD0.get_CLAMP_X(); + uint32 clampY = (uint32)samplerWords->WORD0.get_CLAMP_Y(); + uint32 clampZ = (uint32)samplerWords->WORD0.get_CLAMP_Z(); + if (samplerState->clampS != clampX) + { + quickBindTexture(); + glTexParameteri(hostTextureView->glTexTarget, GL_TEXTURE_WRAP_S, glClampTable[clampX]); + samplerState->clampS = clampX; + } + if (samplerState->clampT != clampY) + { + quickBindTexture(); + glTexParameteri(hostTextureView->glTexTarget, GL_TEXTURE_WRAP_T, glClampTable[clampY]); + samplerState->clampT = clampY; + } + if (samplerState->clampR != clampZ) + { + quickBindTexture(); + glTexParameteri(hostTextureView->glTexTarget, GL_TEXTURE_WRAP_R, glClampTable[clampZ]); + samplerState->clampR = clampZ; + } + catchOpenGLError(); + + uint32 maxAniso = (uint32)samplerWords->WORD0.get_MAX_ANISO_RATIO(); + + if (baseTexture->overwriteInfo.anisotropicLevel >= 0) + maxAniso = baseTexture->overwriteInfo.anisotropicLevel; + + if (samplerState->maxAniso != maxAniso) + { + quickBindTexture(); + glTexParameterf(hostTextureView->glTexTarget, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)(1 << maxAniso)); + samplerState->maxAniso = maxAniso; + catchOpenGLError(); + } + if (samplerState->filterMin != filterMinGL) + { + quickBindTexture(); + glTexParameteri(hostTextureView->glTexTarget, GL_TEXTURE_MIN_FILTER, filterMinGL); + samplerState->filterMin = filterMinGL; + catchOpenGLError(); + } + + if (samplerState->filterMag != filterMagGL) + { + quickBindTexture(); + glTexParameteri(hostTextureView->glTexTarget, GL_TEXTURE_MAG_FILTER, filterMagGL); + samplerState->filterMag = filterMagGL; + catchOpenGLError(); + } + if (samplerState->maxMipLevels != hostTextureView->numMip) + { + quickBindTexture(); + glTexParameteri(hostTextureView->glTexTarget, GL_TEXTURE_MAX_LEVEL, std::max(hostTextureView->numMip, 1) - 1); + samplerState->maxMipLevels = hostTextureView->numMip; + catchOpenGLError(); + } + // lod + uint32 iMinLOD = samplerWords->WORD1.get_MIN_LOD(); + uint32 iMaxLOD = samplerWords->WORD1.get_MAX_LOD(); + sint32 iLodBias = samplerWords->WORD1.get_LOD_BIAS(); + + // apply relative lod bias from graphic pack + if (baseTexture->overwriteInfo.hasRelativeLodBias) + { + iLodBias += baseTexture->overwriteInfo.relativeLodBias; + } + // apply absolute lod bias from graphic pack + if (baseTexture->overwriteInfo.hasLodBias) + { + iLodBias = baseTexture->overwriteInfo.lodBias; + } + + if (samplerState->minLod != iMinLOD) + { + quickBindTexture(); + glTexParameterf(hostTextureView->glTexTarget, GL_TEXTURE_MIN_LOD, (float)iMinLOD / 64.0f); + samplerState->minLod = iMinLOD; + } + if (samplerState->maxLod != iMaxLOD) + { + quickBindTexture(); + glTexParameterf(hostTextureView->glTexTarget, GL_TEXTURE_MAX_LOD, (float)iMaxLOD / 64.0f); + samplerState->maxLod = iMaxLOD; + } + if (samplerState->lodBias != iLodBias) + { + quickBindTexture(); + glTexParameterf(hostTextureView->glTexTarget, GL_TEXTURE_LOD_BIAS, (float)iLodBias / 64.0f); + samplerState->lodBias = iLodBias; + } + // depth compare + uint32 samplerDepthCompare = (uint32)samplerWords->WORD0.get_DEPTH_COMPARE_FUNCTION(); + uint8 depthCompareMode = isDepthSampler ? 1 : 0; + + if (samplerDepthCompare != samplerState->depthCompareFunc) + { + quickBindTexture(); + glTexParameteri(hostTextureView->glTexTarget, GL_TEXTURE_COMPARE_FUNC, glDepthCompareTable[samplerDepthCompare]); + samplerState->depthCompareFunc = samplerDepthCompare; + } + + if (depthCompareMode != samplerState->depthCompareMode) + { + quickBindTexture(); + if (depthCompareMode != 0) + glTexParameteri(hostTextureView->glTexTarget, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); + else + glTexParameteri(hostTextureView->glTexTarget, GL_TEXTURE_COMPARE_MODE, GL_NONE); + samplerState->depthCompareMode = depthCompareMode; + } + + catchOpenGLError(); + // border + auto borderType = samplerWords->WORD0.get_BORDER_COLOR_TYPE(); + if (samplerState->borderType != (uint8)borderType || borderType == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_BORDER_COLOR_TYPE::REGISTER) + { + // todo: Should we use integer border color (glTexParameteriv) for integer texture formats? + GLfloat borderColor[4]; + if (borderType == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_BORDER_COLOR_TYPE::TRANSPARENT_BLACK) + { + borderColor[0] = 0.0f; + borderColor[1] = 0.0f; + borderColor[2] = 0.0f; + borderColor[3] = 0.0f; + } + else if (borderType == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_BORDER_COLOR_TYPE::OPAQUE_BLACK) + { + borderColor[0] = 0.0f; + borderColor[1] = 0.0f; + borderColor[2] = 0.0f; + borderColor[3] = 1.0f; + } + else if (borderType == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_BORDER_COLOR_TYPE::OPAQUE_WHITE) + { + borderColor[0] = 1.0f; + borderColor[1] = 1.0f; + borderColor[2] = 1.0f; + borderColor[3] = 1.0f; + } + else + { + // border color from register + _LatteRegisterSetSamplerBorderColor* borderColorReg; + if (shaderContext->shaderType == LatteConst::ShaderType::Vertex || shaderContext->shaderType == LatteConst::ShaderType::Compute) + borderColorReg = LatteGPUState.contextNew.TD_VS_SAMPLER_BORDER_COLOR + stageSamplerIndex; + else if (shaderContext->shaderType == LatteConst::ShaderType::Pixel) + borderColorReg = LatteGPUState.contextNew.TD_PS_SAMPLER_BORDER_COLOR + stageSamplerIndex; + else // geometry + borderColorReg = LatteGPUState.contextNew.TD_GS_SAMPLER_BORDER_COLOR + stageSamplerIndex; + + borderColor[0] = borderColorReg->red.get_channelValue(); + borderColor[1] = borderColorReg->green.get_channelValue(); + borderColor[2] = borderColorReg->blue.get_channelValue(); + borderColor[3] = borderColorReg->alpha.get_channelValue(); + } + + if (samplerState->borderColor[0] != borderColor[0] || samplerState->borderColor[1] != borderColor[1] || samplerState->borderColor[2] != borderColor[2] || samplerState->borderColor[3] != borderColor[3]) + { + quickBindTexture(); + glTexParameterfv(hostTextureView->glTexTarget, GL_TEXTURE_BORDER_COLOR, borderColor); + samplerState->borderColor[0] = borderColor[0]; + samplerState->borderColor[1] = borderColor[1]; + samplerState->borderColor[2] = borderColor[2]; + samplerState->borderColor[3] = borderColor[3]; + } + samplerState->borderType = (uint8)borderType; + } + catchOpenGLError(); + } +} \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererStreamout.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererStreamout.cpp new file mode 100644 index 00000000..ef23a770 --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererStreamout.cpp @@ -0,0 +1,58 @@ +#include "Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.h" +#include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h" + +#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" +#include "Cafe/HW/Latte/Core/LatteShader.h" + +#include "Cafe/OS/libs/gx2/GX2.h" // todo - remove dependency + +void OpenGLRenderer::streamout_setupXfbBuffer(uint32 bufferIndex, sint32 ringBufferOffset, uint32 rangeAddr, uint32 rangeSize) +{ + catchOpenGLError(); + glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, bufferIndex, glStreamoutCacheRingBuffer, ringBufferOffset, rangeSize); + catchOpenGLError(); +} + +void OpenGLRenderer::streamout_begin() +{ + // get primitive mode + GLenum glTransformFeedbackPrimitiveMode; + auto primitiveMode = LatteGPUState.contextNew.VGT_PRIMITIVE_TYPE.get_PRIMITIVE_MODE(); + if (primitiveMode == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::POINTS) + glTransformFeedbackPrimitiveMode = GL_POINTS; + else if(primitiveMode == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::TRIANGLES) + glTransformFeedbackPrimitiveMode = GL_POINTS; + else if (primitiveMode == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::QUADS) + glTransformFeedbackPrimitiveMode = GL_POINTS; + else + { + debug_printf("Unsupported streamout primitive mode 0x%02x\n", primitiveMode); + cemu_assert_debug(false); + } + cemu_assert_debug(m_isXfbActive == false); + glEnable(GL_RASTERIZER_DISCARD_EXT); + glBeginTransformFeedback(glTransformFeedbackPrimitiveMode); + catchOpenGLError(); + m_isXfbActive = true; +} + +void OpenGLRenderer::bufferCache_copyStreamoutToMainBuffer(uint32 srcOffset, uint32 dstOffset, uint32 size) +{ + if (glCopyNamedBufferSubData) + glCopyNamedBufferSubData(glStreamoutCacheRingBuffer, glAttributeCacheAB, srcOffset, dstOffset, size); + else + forceLog_printf("glCopyNamedBufferSubData() not supported"); +} + +void OpenGLRenderer::streamout_rendererFinishDrawcall() +{ + if (m_isXfbActive) + { + glEndTransformFeedback(); + glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, 0); + glDisable(GL_RASTERIZER_DISCARD_EXT); + + m_isXfbActive = false; + } + +} \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererUniformData.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererUniformData.cpp new file mode 100644 index 00000000..6ffcccbb --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererUniformData.cpp @@ -0,0 +1,76 @@ +#include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h" +#include "Cafe/HW/Latte/Core/LatteShader.h" + +GLint _gl_remappedUniformData[4 * 256]; + +void OpenGLRenderer::uniformData_update() +{ + // update uniforms + LatteDecompilerShader* shaderArray[3]; + shaderArray[0] = LatteSHRC_GetActiveVertexShader(); + shaderArray[1] = LatteSHRC_GetActivePixelShader(); + shaderArray[2] = LatteSHRC_GetActiveGeometryShader();; + + uint32 shaderBlockUniformRegisterOffset[3]; + shaderBlockUniformRegisterOffset[0] = mmSQ_VTX_UNIFORM_BLOCK_START; + shaderBlockUniformRegisterOffset[1] = mmSQ_PS_UNIFORM_BLOCK_START; + shaderBlockUniformRegisterOffset[2] = mmSQ_GS_UNIFORM_BLOCK_START; + + uint32 shaderALUConstOffset[3]; + shaderALUConstOffset[0] = 0x400; + shaderALUConstOffset[1] = 0; + shaderALUConstOffset[2] = 0xFFFFFFFF; // GS has no uniform registers + + for (sint32 s = 0; s < 3; s++) + { + // update block uniforms + LatteDecompilerShader* shader = shaderArray[s]; + if (!shader) + continue; + + auto hostShader = shader->shader; + + if (shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_REMAPPED) + { + auto& list_uniformMapping = shader->list_remappedUniformEntries; + cemu_assert_debug(list_uniformMapping.size() <= 256); + sint32 remappedArraySize = (sint32)list_uniformMapping.size(); + LatteBufferCache_LoadRemappedUniforms(shader, (float*)(_gl_remappedUniformData)); + // update values only when the hash changed + if (remappedArraySize > 0) + { + uint64 uniformDataHash[2] = { 0 }; + uint64* remappedUniformData64 = (uint64*)_gl_remappedUniformData; + for (sint32 f = 0; f < remappedArraySize; f++) + { + uniformDataHash[0] ^= remappedUniformData64[0]; + uniformDataHash[0] = _rotl64(uniformDataHash[0], 11); + uniformDataHash[1] ^= remappedUniformData64[1]; + uniformDataHash[1] = _rotl64(uniformDataHash[1], 11); + remappedUniformData64 += 2; + } + if (shader->uniformDataHash64[0] != uniformDataHash[0] || shader->uniformDataHash64[1] != uniformDataHash[1]) + { + shader->uniformDataHash64[0] = uniformDataHash[0]; + shader->uniformDataHash64[1] = uniformDataHash[1]; + hostShader->SetUniform4iv(shader->uniform.loc_remapped, _gl_remappedUniformData, remappedArraySize); + } + } + } + else if (shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CFILE) + { + if (shaderALUConstOffset[s] == 0xFFFFFFFF) + assert_dbg(); + GLint* uniformRegData = (GLint*)(LatteGPUState.contextRegister + mmSQ_ALU_CONSTANT0_0 + shaderALUConstOffset[s]); + hostShader->SetUniform4iv(shader->uniform.loc_uniformRegister, uniformRegData, shader->uniform.count_uniformRegister); + } + else if (shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK) + { + // handled by _syncGPUUniformBuffers() + } + else if (shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_NONE) + { + // no uniforms used + } + } +} \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLSurfaceCopy.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLSurfaceCopy.cpp new file mode 100644 index 00000000..8c8c36d7 --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLSurfaceCopy.cpp @@ -0,0 +1,117 @@ +#include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h" +#include "Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.h" +#include "Cafe/HW/Latte/Renderer/OpenGL/CachedFBOGL.h" + +#include "Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.h" +#include "Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.h" +#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" +#include "Cafe/HW/Latte/Core/LatteShader.h" +#include "Cafe/HW/Latte/Core/LatteDraw.h" + +#include "Cafe/HW/Latte/Core/LatteDefaultShaders.h" + +void LatteDraw_resetAttributePointerCache(); + +void _setDepthCompareMode(LatteTextureViewGL* textureView, uint8 depthCompareMode) +{ + if (depthCompareMode != textureView->samplerState.depthCompareMode) + { + if (depthCompareMode != 0) + glTexParameteri(textureView->glTexTarget, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); + else + glTexParameteri(textureView->glTexTarget, GL_TEXTURE_COMPARE_MODE, GL_NONE); + textureView->samplerState.depthCompareMode = depthCompareMode; + } +} + +void OpenGLRenderer::surfaceCopy_copySurfaceWithFormatConversion(LatteTexture* sourceTexture, sint32 srcMip, sint32 srcSlice, LatteTexture* destinationTexture, sint32 dstMip, sint32 dstSlice, sint32 width, sint32 height) +{ + // scale copy size to effective size + sint32 effectiveCopyWidth = width; + sint32 effectiveCopyHeight = height; + LatteTexture_scaleToEffectiveSize(sourceTexture, &effectiveCopyWidth, &effectiveCopyHeight, 0); + sint32 sourceEffectiveWidth; + sint32 sourceEffectiveHeight; + LatteTexture_getEffectiveSize(sourceTexture, &sourceEffectiveWidth, &sourceEffectiveHeight, nullptr, srcMip); + // reset everything + renderstate_resetColorControl(); + renderstate_resetDepthControl(); + attributeStream_unbindVertexBuffer(); + + SetArrayElementBuffer(0); + LatteDraw_resetAttributePointerCache(); + SetAttributeArrayState(0, true, -1); + SetAttributeArrayState(1, true, -1); + for (uint32 i = 2; i < GPU_GL_MAX_NUM_ATTRIBUTE; i++) + SetAttributeArrayState(i, false, -1); + catchOpenGLError(); + // set viewport + g_renderer->renderTarget_setViewport(0, 0, (float)effectiveCopyWidth, (float)effectiveCopyHeight, 0.0f, 1.0f); + catchOpenGLError(); + // get a view of the copied slice/mip in the source and destination texture + LatteTextureView* sourceView = sourceTexture->GetOrCreateView(srcMip, 1, srcSlice, 1); + LatteTextureView* destinationView = destinationTexture->GetOrCreateView(dstMip, 1, dstSlice, 1); + + g_renderer->texture_bindAndActivate(sourceView, 0); + catchOpenGLError(); + // setup texture attributes + _setDepthCompareMode((LatteTextureViewGL*)sourceView, 0); + catchOpenGLError(); + // bind framebuffer + if (destinationTexture->isDepth) + LatteMRT::BindDepthBufferOnly(destinationView); + else + LatteMRT::BindColorBufferOnly(destinationView); + catchOpenGLError(); + // enable depth writes if the destination is a depth texture + if (destinationTexture->isDepth) + renderstate_setAlwaysWriteDepth(); + // bind format specific copy shader + LatteDefaultShader_t* copyShader = LatteDefaultShader_getPixelCopyShader_depthToColor(); + if (destinationTexture->isDepth) + copyShader = LatteDefaultShader_getPixelCopyShader_colorToDepth(); + glUseProgram(copyShader->glProgamId); + catchOpenGLError(); + // setup uniforms + glUniform1i(copyShader->copyShaderUniforms.uniformLoc_textureSrc, 0); + catchOpenGLError(); + + float vertexOffsets[4 * 4]; + float srcCopyWidth = (float)width / (float)sourceTexture->width; + float srcCopyHeight = (float)height / (float)sourceTexture->height; + // q0 vertex + vertexOffsets[0] = -1.0f; + vertexOffsets[1] = 1.0f; + // q0 uv + vertexOffsets[2] = 0.0f; + vertexOffsets[3] = 0.0f; + // q1 + vertexOffsets[4] = 1.0f; + vertexOffsets[5] = 1.0f; + // q1 uv + vertexOffsets[6] = srcCopyWidth; + vertexOffsets[7] = 0.0f; + // q2 + vertexOffsets[8] = -1.0f; + vertexOffsets[9] = -1.0f; + // q2 uv + vertexOffsets[10] = 0.0f; + vertexOffsets[11] = srcCopyHeight; + // q3 + vertexOffsets[12] = 1.0f; + vertexOffsets[13] = -1.0f; + // q3 uv + vertexOffsets[14] = srcCopyWidth; + vertexOffsets[15] = srcCopyHeight; + + glUniform4fv(copyShader->copyShaderUniforms.uniformLoc_vertexOffsets, 4, vertexOffsets); + catchOpenGLError(); + + // draw + uint16 indexData[6] = { 0,1,3,0,2,3 }; + glDrawRangeElements(GL_TRIANGLES, 0, 5, 6, GL_UNSIGNED_SHORT, indexData); + catchOpenGLError(); + + LatteGPUState.repeatTextureInitialization = true; + glUseProgram(0); +} \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLTextureReadback.h b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLTextureReadback.h new file mode 100644 index 00000000..a1ba49ef --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLTextureReadback.h @@ -0,0 +1,27 @@ +#pragma once +#include "Cafe/HW/Latte/Core/LatteTextureReadbackInfo.h" +#include "Common/GLInclude/GLInclude.h" + +class LatteTextureReadbackInfoGL : public LatteTextureReadbackInfo +{ +public: + LatteTextureReadbackInfoGL(LatteTextureView* textureView); + + ~LatteTextureReadbackInfoGL(); + + void StartTransfer() override; + bool IsFinished() override; + + uint8* GetData() override; + void ReleaseData() override; + +private: + GLuint m_texFormatGL; + GLuint m_texDataTypeGL; + + // buffer + GLuint texImageBufferGL = 0; + // sync + GLsync imageCopyFinSync = nullptr; +}; + diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.cpp new file mode 100644 index 00000000..60dd99ea --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.cpp @@ -0,0 +1,295 @@ +#include "Cafe/HW/Latte/Renderer/Renderer.h" +#include "Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.h" + +#include "Cemu/FileCache/FileCache.h" + +#include "config/ActiveSettings.h" +#include "config/LaunchSettings.h" + +extern std::atomic_int g_compiled_shaders_total; +extern std::atomic_int g_compiled_shaders_async; + +bool s_isLoadingShaders{false}; + +bool RendererShaderGL::loadBinary() +{ + if (!g_programBinaryCache) + return false; + if (m_isGameShader == false || m_isGfxPackShader) + return false; // only non-custom + if (!glProgramBinary) + return false; // OpenGL program binaries not supported + + cemu_assert_debug(m_baseHash != 0); + uint64 h1, h2; + GenerateShaderPrecompiledCacheFilename(m_type, m_baseHash, m_auxHash, h1, h2); + sint32 fileSize = 0; + std::vector<uint8> cacheFileData; + if (!g_programBinaryCache->GetFile({ h1, h2 }, cacheFileData)) + return false; + if (fileSize < sizeof(uint32)) + { + return false; + } + + uint32 shaderBinFormat = *(uint32*)(cacheFileData.data() + 0); + + m_program = glCreateProgram(); + glProgramBinary(m_program, shaderBinFormat, cacheFileData.data()+4, cacheFileData.size()-4); + + int status = -1; + glGetProgramiv(m_program, GL_LINK_STATUS, &status); + if (status != GL_TRUE) + { + glDeleteProgram(m_program); + m_program = 0; + return false; + } + m_isCompiled = true; + return true; +} + +void RendererShaderGL::storeBinary() +{ + if (!g_programBinaryCache) + return; + if (!glGetProgramBinary) + return; + if (m_program == 0) + return; + if (!m_isGameShader || m_isGfxPackShader) + return; + + GLint binaryLength = 0; + glGetProgramiv(m_program, GL_PROGRAM_BINARY_LENGTH, &binaryLength); + if (binaryLength > 0) + { + uint64 h1, h2; + GenerateShaderPrecompiledCacheFilename(m_type, m_baseHash, m_auxHash, h1, h2); + // build stored shader data (4 byte format + binary data) + std::vector<uint8> storedBinary(binaryLength+sizeof(uint32), 0); + GLenum binaryFormat = 0; + glGetProgramBinary(m_program, binaryLength, NULL, &binaryFormat, storedBinary.data()+sizeof(uint32)); + *(uint32*)(storedBinary.data() + 0) = binaryFormat; + // store + g_programBinaryCache->AddFileAsync({ h1, h2 }, storedBinary.data(), storedBinary.size()); + } +} + +RendererShaderGL::RendererShaderGL(ShaderType type, uint64 baseHash, uint64 auxHash, bool isGameShader, bool isGfxPackShader, const std::string& glslSource) + : RendererShader(type, baseHash, auxHash, isGameShader, isGfxPackShader), m_glslSource(glslSource) +{ + GLenum glShaderType; + switch (type) + { + case ShaderType::kVertex: + glShaderType = GL_VERTEX_SHADER; + break; + case ShaderType::kFragment: + glShaderType = GL_FRAGMENT_SHADER; + break; + case ShaderType::kGeometry: + glShaderType = GL_GEOMETRY_SHADER; + break; + default: + cemu_assert_debug(false); + } + + if (s_isLoadingShaders) + { + if (loadBinary()) + { + m_glslSource.clear(); + m_glslSource.shrink_to_fit(); + return; + } + } + + m_shader_object = glCreateShader(glShaderType); + + const char *c_str = m_glslSource.c_str(); + const GLint size = (GLint)m_glslSource.size(); + glShaderSource(m_shader_object, 1, &c_str, &size); + glCompileShader(m_shader_object); + + GLint log_length; + glGetShaderiv(m_shader_object, GL_INFO_LOG_LENGTH, &log_length); + if (log_length > 0) + { + char log[2048]{}; + GLsizei log_size; + glGetShaderInfoLog(m_shader_object, std::min<uint32>(log_length, sizeof(log) - 1), &log_size, log); + forceLog_printf("Error/Warning in shader:"); + forceLog_printf("%s", log); + } + + // set debug name + if (LaunchSettings::NSightModeEnabled()) + { + auto objNameStr = fmt::format("shader_{:016x}_{:016x}", m_baseHash, m_auxHash); + glObjectLabel(GL_SHADER, m_shader_object, objNameStr.size(), objNameStr.c_str()); + } + + m_program = glCreateProgram(); + glProgramParameteri(m_program, GL_PROGRAM_SEPARABLE, GL_TRUE); + glProgramParameteri(m_program, GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL_TRUE); + glAttachShader(m_program, m_shader_object); + m_shader_attached = true; + glLinkProgram(m_program); + + storeBinary(); + + // count shader compilation + if (!s_isLoadingShaders) + ++g_compiled_shaders_total; + + // we can throw away the GLSL code to conserve RAM + m_glslSource.clear(); + m_glslSource.shrink_to_fit(); +} + +RendererShaderGL::~RendererShaderGL() +{ + if (m_shader_object != 0 && m_shader_attached) + glDetachShader(m_program, m_shader_object); + + if (m_shader_object != 0) + glDeleteShader(m_shader_object); + + if (m_program != 0) + glDeleteProgram(m_program); +} + +void RendererShaderGL::PreponeCompilation(bool isRenderThread) +{ + // the logic for initiating compilation is currently in the constructor + // here we only guarantee that it is finished before we return + if (m_isCompiled) + return; + WaitForCompiled(); +} + +bool RendererShaderGL::IsCompiled() +{ + cemu_assert_debug(false); + return true; +} + +bool RendererShaderGL::WaitForCompiled() +{ + char infoLog[8 * 1024]; + if (m_isCompiled) + return true; + // check if compilation was successful + GLint compileStatus = GL_FALSE; + glGetShaderiv(m_shader_object, GL_COMPILE_STATUS, &compileStatus); + if (compileStatus == 0) + { + uint32 infoLogLength, tempLength; + glGetShaderiv(m_shader_object, GL_INFO_LOG_LENGTH, (GLint *)&infoLogLength); + if (infoLogLength != 0) + { + tempLength = sizeof(infoLog) - 1; + glGetShaderInfoLog(m_shader_object, std::min(infoLogLength, tempLength), (GLsizei*)&tempLength, (GLcharARB*)infoLog); + infoLog[tempLength] = '\0'; + forceLog_printf("Compile error in shader. Log:", infoLog); + cafeLog_logLine(LOG_TYPE_FORCE, infoLog); + } + if (m_shader_object != 0) + glDeleteShader(m_shader_object); + m_isCompiled = true; + return false; + } + // get shader binary + GLint linkStatus = GL_FALSE; + glGetProgramiv(m_program, GL_LINK_STATUS, &linkStatus); + if (linkStatus == 0) + { + uint32 infoLogLength, tempLength; + glGetProgramiv(m_program, GL_INFO_LOG_LENGTH, (GLint *)&infoLogLength); + if (infoLogLength != 0) + { + tempLength = sizeof(infoLog) - 1; + glGetProgramInfoLog(m_program, std::min(infoLogLength, tempLength), (GLsizei*)&tempLength, (GLcharARB*)infoLog); + infoLog[tempLength] = '\0'; + forceLog_printf("Link error in shader. Log:", infoLog); + cafeLog_logLine(LOG_TYPE_FORCE, infoLog); + } + m_isCompiled = true; + return false; + } + + /*glDetachShader(m_program, m_shader_object); + m_shader_attached = false;*/ + m_isCompiled = true; + return true; +} + +sint32 RendererShaderGL::GetUniformLocation(const char* name) +{ + return glGetUniformLocation(m_program, name); +} + +void RendererShaderGL::SetUniform1iv(sint32 location, void* data, sint32 count) +{ + glProgramUniform1iv(m_program, location, count, (const GLint*)data); +} + +void RendererShaderGL::SetUniform2fv(sint32 location, void* data, sint32 count) +{ + glProgramUniform2fv(m_program, location, count, (const GLfloat*)data); +} + +void RendererShaderGL::SetUniform4iv(sint32 location, void* data, sint32 count) +{ + glProgramUniform4iv(m_program, location, count, (const GLint*)data); +} + +void RendererShaderGL::ShaderCacheLoading_begin(uint64 cacheTitleId) +{ + if (g_programBinaryCache) + { + delete g_programBinaryCache; + g_programBinaryCache = nullptr; + } + + // determine if cache is enabled + bool usePrecompiled = false; + switch (ActiveSettings::GetPrecompiledShadersOption()) + { + case PrecompiledShaderOption::Auto: + if (g_renderer->GetVendor() == GfxVendor::Nvidia) + usePrecompiled = false; + else + usePrecompiled = true; + break; + case PrecompiledShaderOption::Enable: + usePrecompiled = true; + break; + case PrecompiledShaderOption::Disable: + usePrecompiled = false; + break; + default: + __assume(false); + } + + forceLog_printf("Using precompiled shaders: %s", usePrecompiled ? "true" : "false"); + + if (usePrecompiled) + { + const uint32 cacheMagic = GeneratePrecompiledCacheId(); + const std::string cacheFilename = fmt::format("{:016x}_gl.bin", cacheTitleId); + const std::wstring cachePath = ActiveSettings::GetPath("shaderCache/precompiled/{}", cacheFilename).generic_wstring(); + g_programBinaryCache = FileCache::Open(cachePath, true, cacheMagic); + if (g_programBinaryCache == nullptr) + cemuLog_log(LogType::Force, "Unable to open OpenGL precompiled cache {}", cacheFilename); + } + s_isLoadingShaders = true; +} + +void RendererShaderGL::ShaderCacheLoading_end() +{ + s_isLoadingShaders = false; +} + +FileCache* RendererShaderGL::g_programBinaryCache{}; \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.h b/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.h new file mode 100644 index 00000000..5c929998 --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.h @@ -0,0 +1,42 @@ +#pragma once + +#include "Cafe/HW/Latte/Renderer/RendererShader.h" +#include "Common\GLInclude\GLInclude.h" + +class RendererShaderGL : public RendererShader +{ +public: + RendererShaderGL(ShaderType type, uint64 baseHash, uint64 auxHash, bool isGameShader, bool isGfxPackShader, const std::string& glslSource); + + virtual ~RendererShaderGL(); + + void PreponeCompilation(bool isRenderThread) override; + bool IsCompiled() override; + bool WaitForCompiled() override; + + GLuint GetProgram() const { cemu_assert_debug(m_isCompiled); return m_program; } + GLuint GetShaderObject() const { cemu_assert_debug(m_isCompiled); return m_shader_object; } + + sint32 GetUniformLocation(const char* name) override; + void SetUniform1iv(sint32 location, void* data, sint32 count) override; + void SetUniform2fv(sint32 location, void* data, sint32 count) override; + void SetUniform4iv(sint32 location, void* data, sint32 count) override; + + static void ShaderCacheLoading_begin(uint64 cacheTitleId); + static void ShaderCacheLoading_end(); + +private: + GLuint m_program; + GLuint m_shader_object; + + bool loadBinary(); + void storeBinary(); + + std::string m_glslSource; + + bool m_shader_attached{ false }; + bool m_isCompiled{ false }; + + static class FileCache* g_programBinaryCache; +}; + diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/TextureReadbackGL.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/TextureReadbackGL.cpp new file mode 100644 index 00000000..45fa9273 --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/TextureReadbackGL.cpp @@ -0,0 +1,131 @@ +#include "Cafe/HW/Latte/Renderer/Renderer.h" +#include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLTextureReadback.h" +#include "Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.h" + +LatteTextureReadbackInfoGL::LatteTextureReadbackInfoGL(LatteTextureView* textureView) + : LatteTextureReadbackInfo(textureView) +{ + LatteTexture* baseTexture = textureView->baseTexture; + // handle format + if (textureView->format == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM) + { + m_image_size = baseTexture->width*baseTexture->height * 4; + m_texFormatGL = GL_RGBA; + m_texDataTypeGL = GL_UNSIGNED_BYTE; + } + else if (textureView->format == Latte::E_GX2SURFFMT::R8_G8_B8_A8_SRGB) + { + m_image_size = baseTexture->width*baseTexture->height * 4; + m_texFormatGL = GL_RGBA; + m_texDataTypeGL = GL_UNSIGNED_BYTE; + } + else if (textureView->format == Latte::E_GX2SURFFMT::R32_G32_B32_A32_FLOAT) + { + m_image_size = baseTexture->width*baseTexture->height * 16; + m_texFormatGL = GL_RGBA; + m_texDataTypeGL = GL_FLOAT; + } + else if (textureView->format == Latte::E_GX2SURFFMT::R32_FLOAT) + { + if (baseTexture->isDepth) + { + m_image_size = baseTexture->width*baseTexture->height * 4; + m_texFormatGL = GL_DEPTH_COMPONENT; + m_texDataTypeGL = GL_FLOAT; + } + else + { + m_image_size = baseTexture->width*baseTexture->height * 4; + m_texFormatGL = GL_RED; + m_texDataTypeGL = GL_FLOAT; + } + } + else if (textureView->format == Latte::E_GX2SURFFMT::R16_UNORM) + { + if (baseTexture->isDepth) + { + m_image_size = baseTexture->width*baseTexture->height * 2; + m_texFormatGL = GL_DEPTH_COMPONENT; + m_texDataTypeGL = GL_UNSIGNED_SHORT; + cemu_assert_unimplemented(); + } + else + { + m_image_size = baseTexture->width*baseTexture->height * 2; + m_texFormatGL = GL_RED; + m_texDataTypeGL = GL_UNSIGNED_SHORT; + } + } + else if (textureView->format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT) + { + m_image_size = baseTexture->width*baseTexture->height * 8; + m_texFormatGL = GL_RGBA; + m_texDataTypeGL = GL_HALF_FLOAT; + } + else if (textureView->format == Latte::E_GX2SURFFMT::R8_G8_UNORM) + { + m_image_size = baseTexture->width*baseTexture->height * 2; + m_texFormatGL = GL_RG; + m_texDataTypeGL = GL_UNSIGNED_BYTE; + } + else if (textureView->format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_UNORM) + { + m_image_size = baseTexture->width*baseTexture->height * 8; + m_texFormatGL = GL_RGBA; + m_texDataTypeGL = GL_UNSIGNED_SHORT; + } + else + { + forceLogDebug_printf("Unsupported texture readback format %04x\n", (uint32)textureView->format); + return; + } +} + +LatteTextureReadbackInfoGL::~LatteTextureReadbackInfoGL() +{ + if(imageCopyFinSync != 0) + glDeleteSync(imageCopyFinSync); + + if(texImageBufferGL) + glDeleteBuffers(1, &texImageBufferGL); +} + +void LatteTextureReadbackInfoGL::StartTransfer() +{ + cemu_assert(m_textureView); + + g_renderer->texture_bindAndActivate(m_textureView, 0); + // create unsynchronized buffer + glGenBuffers(1, &texImageBufferGL); + glBindBuffer(GL_PIXEL_PACK_BUFFER, texImageBufferGL); + glBufferData(GL_PIXEL_PACK_BUFFER, m_image_size, NULL, GL_DYNAMIC_READ); + // request texture read into buffer + glGetTexImage(((LatteTextureViewGL*)m_textureView)->glTexTarget, 0, m_texFormatGL, m_texDataTypeGL, NULL); + glFlush(); + // create fence sync (so we can check if the image copy operation finished) + imageCopyFinSync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + m_textureView = nullptr; +} + +bool LatteTextureReadbackInfoGL::IsFinished() +{ + GLenum status = glClientWaitSync(imageCopyFinSync, 0, 0); + if (status == GL_TIMEOUT_EXPIRED) + return false; + else if (status == GL_ALREADY_SIGNALED || status == GL_SIGNALED) + return true; + else + throw std::runtime_error("_updateFinishedTransfers(): Error during readback sync check\n"); +} + +uint8* LatteTextureReadbackInfoGL::GetData() +{ + glBindBuffer(GL_PIXEL_PACK_BUFFER, texImageBufferGL); + return (uint8*)glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY); +} + + +void LatteTextureReadbackInfoGL::ReleaseData() +{ + glUnmapBuffer(GL_PIXEL_PACK_BUFFER); +} diff --git a/src/Cafe/HW/Latte/Renderer/Renderer.cpp b/src/Cafe/HW/Latte/Renderer/Renderer.cpp new file mode 100644 index 00000000..c7f7b814 --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/Renderer.cpp @@ -0,0 +1,202 @@ +#include "Cafe/HW/Latte/Renderer/Renderer.h" +#include "gui/guiWrapper.h" + +#include "config/CemuConfig.h" +#include "Cafe/HW/Latte/Core/LatteOverlay.h" + +#include <imgui.h> +#include "imgui/imgui_extension.h" +#include <png.h> + +#include "config/ActiveSettings.h" + +std::unique_ptr<Renderer> g_renderer; + +bool Renderer::GetVRAMInfo(int& usageInMB, int& totalInMB) const +{ + usageInMB = totalInMB = -1; + +#if BOOST_OS_WINDOWS + if (m_dxgi_wrapper) + { + DXGI_QUERY_VIDEO_MEMORY_INFO info{}; + if (m_dxgi_wrapper->QueryVideoMemoryInfo(info)) + { + totalInMB = (info.Budget / 1000) / 1000; + usageInMB = (info.CurrentUsage / 1000) / 1000; + return true; + } + } +#endif + + return false; +} + +bool Renderer::ImguiBegin(bool mainWindow) +{ + sint32 w = 0, h = 0; + if(mainWindow) + gui_getWindowSize(&w, &h); + else if(gui_isPadWindowOpen()) + gui_getPadWindowSize(&w, &h); + else + return false; + + if (w == 0 || h == 0) + return false; + + const Vector2f window_size{ (float)w,(float)h }; + auto& io = ImGui::GetIO(); + io.DisplaySize = { window_size.x, window_size.y }; // should be only updated in the renderer and only when needed + + ImGui_PrecacheFonts(); + return true; +} + +uint8 Renderer::SRGBComponentToRGB(uint8 ci) +{ + const float cs = (float)ci / 255.0f; + float cl; + if (cs <= 0.04045) + cl = cs / 12.92f; + else + cl = std::pow((cs + 0.055f) / 1.055f, 2.4f); + cl = std::min(cl, 1.0f); + return (uint8)(cl * 255.0f); +} + +uint8 Renderer::RGBComponentToSRGB(uint8 cli) +{ + const float cl = (float)cli / 255.0f; + float cs; + if (cl < 0.0031308) + cs = 12.92f * cl; + else + cs = 1.055f * std::pow(cl, 0.41666f) - 0.055f; + cs = std::max(std::min(cs, 1.0f), 0.0f); + return (uint8)(cs * 255.0f); +} + +void Renderer::SaveScreenshot(const std::vector<uint8>& rgb_data, int width, int height, bool mainWindow) const +{ +#if BOOST_OS_WINDOWS + const bool save_screenshot = GetConfig().save_screenshot; + std::thread([](std::vector<uint8> data, bool save_screenshot, int width, int height, bool mainWindow) + { + if (mainWindow) + { + // copy to clipboard + std::vector<uint8> buffer(sizeof(BITMAPINFO) + data.size()); + auto* bmpInfo = (BITMAPINFO*)buffer.data(); + bmpInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmpInfo->bmiHeader.biWidth = width; + bmpInfo->bmiHeader.biHeight = height; + bmpInfo->bmiHeader.biPlanes = 1; + bmpInfo->bmiHeader.biBitCount = 24; + bmpInfo->bmiHeader.biCompression = BI_RGB; + + uint8* clipboard_image = buffer.data() + sizeof(BITMAPINFOHEADER); + // RGB -> BGR + for (sint32 iy = 0; iy < height; ++iy) + { + for (sint32 ix = 0; ix < width; ++ix) + { + uint8* pIn = data.data() + (ix + iy * width) * 3; + uint8* pOut = clipboard_image + (ix + (height - iy - 1) * width) * 3; + + pOut[0] = pIn[2]; + pOut[1] = pIn[1]; + pOut[2] = pIn[0]; + } + } + + if (OpenClipboard(nullptr)) + { + EmptyClipboard(); + + const HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, buffer.size()); + if (hGlobal) + { + memcpy(GlobalLock(hGlobal), buffer.data(), buffer.size()); + GlobalUnlock(hGlobal); + + SetClipboardData(CF_DIB, hGlobal); + GlobalFree(hGlobal); + } + + CloseClipboard(); + } + + LatteOverlay_pushNotification("Screenshot saved", 2500); + } + + // save to png file + if (save_screenshot) + { + fs::path screendir = ActiveSettings::GetPath("screenshots"); + if (!fs::exists(screendir)) + fs::create_directory(screendir); + + auto counter = 0; + for (const auto& it : fs::directory_iterator(screendir)) + { + int tmp; + if (swscanf_s(it.path().filename().c_str(), L"screenshot_%d", &tmp) == 1) + counter = std::max(counter, tmp); + } + + screendir /= fmt::format(L"screenshot_{}.png", ++counter); + FileStream* fs = FileStream::createFile2(screendir); + if (fs) + { + bool success = true; + auto png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (png_ptr) + { + auto info_ptr = png_create_info_struct(png_ptr); + if (info_ptr) + { + if (!setjmp(png_jmpbuf(png_ptr))) + { + auto pngWriter = [](png_structp png_ptr, png_bytep data, png_size_t length) -> void + { + FileStream* fs = (FileStream*)png_get_io_ptr(png_ptr); + fs->writeData(data, length); + }; + + //png_init_io(png_ptr, file); + png_set_write_fn(png_ptr, (void*)fs, pngWriter, nullptr); + + png_set_IHDR(png_ptr, info_ptr, width, height, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); + png_write_info(png_ptr, info_ptr); + for (int i = 0; i < height; ++i) + { + uint8* pData = data.data() + (i * width) * 3; + png_write_row(png_ptr, pData); + } + + png_write_end(png_ptr, nullptr); + } + else + success = false; + + png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1); + } + + png_destroy_write_struct(&png_ptr, nullptr); + } + delete fs; + if (!success) + { + std::error_code ec; + fs::remove(screendir, ec); + } + } + } + }, rgb_data, save_screenshot, width, height, mainWindow).detach(); + +#else +cemuLog_log(LogType::Force, "Screenshot feature not implemented"); +#endif +} diff --git a/src/Cafe/HW/Latte/Renderer/Renderer.h b/src/Cafe/HW/Latte/Renderer/Renderer.h new file mode 100644 index 00000000..bd57cee1 --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/Renderer.h @@ -0,0 +1,178 @@ +#pragma once + +#include "Cafe/HW/Latte/Core/Latte.h" +#include "Cafe/HW/Latte/Core/LatteCachedFBO.h" +#include "Cafe/HW/Latte/Renderer/RendererShader.h" +#include "Cafe/HW/Latte/Core/LatteTextureLoader.h" +#include "Cafe/HW/Latte/Core/LatteTextureReadbackInfo.h" +#include "Cafe/HW/Latte/Core/LatteQueryObject.h" +#include "Cafe/HW/Latte/Renderer/RendererOuputShader.h" + +#if BOOST_OS_WINDOWS +#include "util/DXGIWrapper/DXGIWrapper.h" +#endif + +enum class GfxVendor +{ + Generic, + + AMD, + IntelLegacy, + IntelNoLegacy, + Intel, + Nvidia, + Mesa, + + MAX +}; + +enum class RendererAPI +{ + OpenGL, + Vulkan, + + MAX +}; + +using ImTextureID = void*; + +class Renderer +{ +public: + enum class INDEX_TYPE + { + NONE, + U16, + U32 + }; + + virtual ~Renderer() = default; + + virtual RendererAPI GetType() = 0; + + virtual void Initialize() {} + virtual void Shutdown() {} + virtual bool IsPadWindowActive() = 0; + + virtual bool GetVRAMInfo(int& usageInMB, int& totalInMB) const; + + virtual void EnableDebugMode() {} + virtual void EnableVSync(int state) = 0; + + virtual void ClearColorbuffer(bool padView) = 0; + virtual void DrawEmptyFrame(bool mainWindow) = 0; + virtual void SwapBuffers(bool swapTV, bool swapDRC) = 0; + + virtual void HandleScreenshotRequest(LatteTextureView* texView, bool padView){} + + virtual void DrawBackbufferQuad(LatteTextureView* texView, RendererOutputShader* shader, bool useLinearTexFilter, + sint32 imageX, sint32 imageY, sint32 imageWidth, sint32 imageHeight, + bool padView, bool clearBackground) = 0; + virtual bool BeginFrame(bool mainWindow) = 0; + + // flush control + virtual void Flush(bool waitIdle = false) = 0; // called when explicit flush is required (e.g. by imgui) + virtual void NotifyLatteCommandProcessorIdle() = 0; // called when command processor has no more commands available or when stalled + + // imgui + virtual bool ImguiBegin(bool mainWindow); + virtual void ImguiEnd() = 0; + virtual ImTextureID GenerateTexture(const std::vector<uint8>& data, const Vector2i& size) = 0; + virtual void DeleteTexture(ImTextureID id) = 0; + virtual void DeleteFontTextures() = 0; + + GfxVendor GetVendor() const { return m_vendor; } + virtual void AppendOverlayDebugInfo() = 0; + + // rendertarget + virtual void renderTarget_setViewport(float x, float y, float width, float height, float nearZ, float farZ, bool halfZ = false) = 0; + virtual void renderTarget_setScissor(sint32 scissorX, sint32 scissorY, sint32 scissorWidth, sint32 scissorHeight) = 0; + + virtual LatteCachedFBO* rendertarget_createCachedFBO(uint64 key) = 0; + virtual void rendertarget_deleteCachedFBO(LatteCachedFBO* fbo) = 0; + virtual void rendertarget_bindFramebufferObject(LatteCachedFBO* cfbo) = 0; + + // texture functions + virtual void texture_destroy(LatteTexture* hostTexture) = 0; + + virtual void* texture_acquireTextureUploadBuffer(uint32 size) = 0; + virtual void texture_releaseTextureUploadBuffer(uint8* mem) = 0; + + virtual TextureDecoder* texture_chooseDecodedFormat(Latte::E_GX2SURFFMT format, bool isDepth, Latte::E_DIM dim, uint32 width, uint32 height) = 0; + + virtual void texture_reserveTextureOnGPU(LatteTexture* hostTexture) = 0; + virtual void texture_clearSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex) = 0; + virtual void texture_loadSlice(LatteTexture* hostTexture, sint32 width, sint32 height, sint32 depth, void* pixelData, sint32 sliceIndex, sint32 mipIndex, uint32 compressedImageSize) = 0; + virtual void texture_clearColorSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex, float r, float g, float b, float a) = 0; + virtual void texture_clearDepthSlice(LatteTexture* hostTexture, uint32 sliceIndex, sint32 mipIndex, bool clearDepth, bool clearStencil, float depthValue, uint32 stencilValue) = 0; + + virtual LatteTexture* texture_createTextureEx(uint32 textureUnit, Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, uint32 swizzle, Latte::E_HWTILEMODE tileMode, bool isDepth) = 0; + + virtual void texture_bindAndActivate(LatteTextureView* textureView, uint32 textureUnit) = 0; + virtual void texture_bindAndActivateRawTex(LatteTexture* texture, uint32 textureUnit) = 0; + virtual void texture_bindOnly(LatteTextureView* textureView, uint32 textureUnit) = 0; + virtual void texture_rememberBoundTexture(uint32 textureUnit) = 0; + virtual void texture_restoreBoundTexture(uint32 textureUnit) = 0; + virtual void texture_copyImageSubData(LatteTexture* src, sint32 srcMip, sint32 effectiveSrcX, sint32 effectiveSrcY, sint32 srcSlice, LatteTexture* dst, sint32 dstMip, sint32 effectiveDstX, sint32 effectiveDstY, sint32 dstSlice, sint32 effectiveCopyWidth, sint32 effectiveCopyHeight, sint32 srcDepth) = 0; + + virtual LatteTextureReadbackInfo* texture_createReadback(LatteTextureView* textureView) = 0; + + // surface copy + virtual void surfaceCopy_copySurfaceWithFormatConversion(LatteTexture* sourceTexture, sint32 srcMip, sint32 srcSlice, LatteTexture* destinationTexture, sint32 dstMip, sint32 dstSlice, sint32 width, sint32 height) = 0; + + // buffer cache + virtual void bufferCache_init(const sint32 bufferSize) = 0; + virtual void bufferCache_upload(uint8* buffer, sint32 size, uint32 bufferOffset) = 0; + virtual void bufferCache_copy(uint32 srcOffset, uint32 dstOffset, uint32 size) = 0; + virtual void bufferCache_copyStreamoutToMainBuffer(uint32 srcOffset, uint32 dstOffset, uint32 size) = 0; + + virtual void buffer_bindVertexBuffer(uint32 bufferIndex, uint32 offset, uint32 size) = 0; + virtual void buffer_bindUniformBuffer(LatteConst::ShaderType shaderType, uint32 bufferIndex, uint32 offset, uint32 size) = 0; + + // shader + virtual RendererShader* shader_create(RendererShader::ShaderType type, uint64 baseHash, uint64 auxHash, const std::string& source, bool compileAsync, bool isGfxPackSource) = 0; + virtual void shader_bind(RendererShader* shader) = 0; + virtual void shader_unbind(RendererShader::ShaderType shaderType) = 0; + + // streamout + virtual void streamout_setupXfbBuffer(uint32 bufferIndex, sint32 ringBufferOffset, uint32 rangeAddr, uint32 rangeSize) = 0; + virtual void streamout_begin() = 0; + virtual void streamout_rendererFinishDrawcall() = 0; + + // core drawing logic + virtual void draw_beginSequence() = 0; + virtual void draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, bool isFirst) = 0; + virtual void draw_endSequence() = 0; + + // index + virtual void* indexData_reserveIndexMemory(uint32 size, uint32& offset, uint32& bufferIndex) = 0; + virtual void indexData_uploadIndexMemory(uint32 offset, uint32 size) = 0; + + // occlusion queries + virtual LatteQueryObject* occlusionQuery_create() = 0; + virtual void occlusionQuery_destroy(LatteQueryObject* queryObj) = 0; + virtual void occlusionQuery_flush() = 0; + virtual void occlusionQuery_updateState() = 0; + +protected: + virtual void GetVendorInformation() { } + GfxVendor m_vendor = GfxVendor::Generic; + + static uint8 SRGBComponentToRGB(uint8 ci); + static uint8 RGBComponentToSRGB(uint8 cli); + + enum class ScreenshotState + { + None, + Main, + Pad, + }; + ScreenshotState m_screenshot_state = ScreenshotState::None; + void SaveScreenshot(const std::vector<uint8>& rgb_data, int width, int height, bool mainWindow) const; + +#if BOOST_OS_WINDOWS + std::unique_ptr<DXGIWrapper> m_dxgi_wrapper{}; +#endif +}; + +extern std::unique_ptr<Renderer> g_renderer; \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Renderer/RendererOuputShader.cpp b/src/Cafe/HW/Latte/Renderer/RendererOuputShader.cpp new file mode 100644 index 00000000..1f214802 --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/RendererOuputShader.cpp @@ -0,0 +1,382 @@ +#include "Cafe/HW/Latte/Renderer/RendererOuputShader.h" +#include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h" + +const std::string RendererOutputShader::s_copy_shader_source = +R"(#version 420 + +#ifdef VULKAN +layout(location = 0) in vec2 passUV; +layout(binding = 0) uniform sampler2D textureSrc; +layout(location = 0) out vec4 colorOut0; +#else +in vec2 passUV; +layout(binding=0) uniform sampler2D textureSrc; +layout(location = 0) out vec4 colorOut0; +#endif + +void main() +{ + colorOut0 = vec4(texture(textureSrc, passUV).rgb,1.0); +} +)"; + +const std::string RendererOutputShader::s_bicubic_shader_source = +R"( +#version 420 + +#ifdef VULKAN +layout(location = 0) in vec2 passUV; +layout(binding = 0) uniform sampler2D textureSrc; +layout(binding = 1) uniform vec2 textureSrcResolution; +layout(location = 0) out vec4 colorOut0; +#else +in vec2 passUV; +layout(binding=0) uniform sampler2D textureSrc; +uniform vec2 textureSrcResolution; +layout(location = 0) out vec4 colorOut0; +#endif + +vec4 cubic(float x) +{ + float x2 = x * x; + float x3 = x2 * x; + vec4 w; + w.x = -x3 + 3 * x2 - 3 * x + 1; + w.y = 3 * x3 - 6 * x2 + 4; + w.z = -3 * x3 + 3 * x2 + 3 * x + 1; + w.w = x3; + return w / 6.0; +} + +vec4 bcFilter(vec2 texcoord, vec2 texscale) +{ + float fx = fract(texcoord.x); + float fy = fract(texcoord.y); + texcoord.x -= fx; + texcoord.y -= fy; + + vec4 xcubic = cubic(fx); + vec4 ycubic = cubic(fy); + + vec4 c = vec4(texcoord.x - 0.5, texcoord.x + 1.5, texcoord.y - 0.5, texcoord.y + 1.5); + vec4 s = vec4(xcubic.x + xcubic.y, xcubic.z + xcubic.w, ycubic.x + ycubic.y, ycubic.z + ycubic.w); + vec4 offset = c + vec4(xcubic.y, xcubic.w, ycubic.y, ycubic.w) / s; + + vec4 sample0 = texture(textureSrc, vec2(offset.x, offset.z) * texscale); + vec4 sample1 = texture(textureSrc, vec2(offset.y, offset.z) * texscale); + vec4 sample2 = texture(textureSrc, vec2(offset.x, offset.w) * texscale); + vec4 sample3 = texture(textureSrc, vec2(offset.y, offset.w) * texscale); + + float sx = s.x / (s.x + s.y); + float sy = s.z / (s.z + s.w); + + return mix( + mix(sample3, sample2, sx), + mix(sample1, sample0, sx), sy); +} + +void main(){ + colorOut0 = vec4(bcFilter(passUV*textureSrcResolution, vec2(1.0,1.0)/textureSrcResolution).rgb,1.0); +} +)"; + +const std::string RendererOutputShader::s_hermite_shader_source = +R"(#version 420 + +in vec4 gl_FragCoord; +in vec2 passUV; +layout(binding=0) uniform sampler2D textureSrc; +uniform vec2 textureSrcResolution; +uniform vec2 outputResolution; +layout(location = 0) out vec4 colorOut0; + +// https://www.shadertoy.com/view/MllSzX + +vec3 CubicHermite (vec3 A, vec3 B, vec3 C, vec3 D, float t) +{ + float t2 = t*t; + float t3 = t*t*t; + vec3 a = -A/2.0 + (3.0*B)/2.0 - (3.0*C)/2.0 + D/2.0; + vec3 b = A - (5.0*B)/2.0 + 2.0*C - D / 2.0; + vec3 c = -A/2.0 + C/2.0; + vec3 d = B; + + return a*t3 + b*t2 + c*t + d; +} + + +vec3 BicubicHermiteTexture(vec2 uv, vec4 texelSize) +{ + vec2 pixel = uv*texelSize.zw + 0.5; + vec2 frac = fract(pixel); + pixel = floor(pixel) / texelSize.zw - vec2(texelSize.xy/2.0); + + vec4 doubleSize = texelSize*texelSize; + + vec3 C00 = texture(textureSrc, pixel + vec2(-texelSize.x ,-texelSize.y)).rgb; + vec3 C10 = texture(textureSrc, pixel + vec2( 0.0 ,-texelSize.y)).rgb; + vec3 C20 = texture(textureSrc, pixel + vec2( texelSize.x ,-texelSize.y)).rgb; + vec3 C30 = texture(textureSrc, pixel + vec2( doubleSize.x,-texelSize.y)).rgb; + + vec3 C01 = texture(textureSrc, pixel + vec2(-texelSize.x , 0.0)).rgb; + vec3 C11 = texture(textureSrc, pixel + vec2( 0.0 , 0.0)).rgb; + vec3 C21 = texture(textureSrc, pixel + vec2( texelSize.x , 0.0)).rgb; + vec3 C31 = texture(textureSrc, pixel + vec2( doubleSize.x, 0.0)).rgb; + + vec3 C02 = texture(textureSrc, pixel + vec2(-texelSize.x , texelSize.y)).rgb; + vec3 C12 = texture(textureSrc, pixel + vec2( 0.0 , texelSize.y)).rgb; + vec3 C22 = texture(textureSrc, pixel + vec2( texelSize.x , texelSize.y)).rgb; + vec3 C32 = texture(textureSrc, pixel + vec2( doubleSize.x, texelSize.y)).rgb; + + vec3 C03 = texture(textureSrc, pixel + vec2(-texelSize.x , doubleSize.y)).rgb; + vec3 C13 = texture(textureSrc, pixel + vec2( 0.0 , doubleSize.y)).rgb; + vec3 C23 = texture(textureSrc, pixel + vec2( texelSize.x , doubleSize.y)).rgb; + vec3 C33 = texture(textureSrc, pixel + vec2( doubleSize.x, doubleSize.y)).rgb; + + vec3 CP0X = CubicHermite(C00, C10, C20, C30, frac.x); + vec3 CP1X = CubicHermite(C01, C11, C21, C31, frac.x); + vec3 CP2X = CubicHermite(C02, C12, C22, C32, frac.x); + vec3 CP3X = CubicHermite(C03, C13, C23, C33, frac.x); + + return CubicHermite(CP0X, CP1X, CP2X, CP3X, frac.y); +} + +void main(){ + vec4 texelSize = vec4( 1.0 / outputResolution.xy, outputResolution.xy); + colorOut0 = vec4(BicubicHermiteTexture(passUV, texelSize), 1.0); +} +)"; + +RendererOutputShader::RendererOutputShader(const std::string& vertex_source, const std::string& fragment_source) +{ + m_vertex_shader = g_renderer->shader_create(RendererShader::ShaderType::kVertex, 0, 0, vertex_source, false, false); + m_fragment_shader = g_renderer->shader_create(RendererShader::ShaderType::kFragment, 0, 0, fragment_source, false, false); + + m_vertex_shader->PreponeCompilation(true); + m_fragment_shader->PreponeCompilation(true); + + if (!m_vertex_shader->WaitForCompiled()) + throw std::exception(); + + if(!m_fragment_shader->WaitForCompiled()) + throw std::exception(); + + if (g_renderer->GetType() == RendererAPI::OpenGL) + { + m_attributes[0].m_loc_texture_src_resolution = m_vertex_shader->GetUniformLocation("textureSrcResolution"); + m_attributes[0].m_loc_input_resolution = m_vertex_shader->GetUniformLocation("inputResolution"); + m_attributes[0].m_loc_output_resolution = m_vertex_shader->GetUniformLocation("outputResolution"); + + m_attributes[1].m_loc_texture_src_resolution = m_fragment_shader->GetUniformLocation("textureSrcResolution"); + m_attributes[1].m_loc_input_resolution = m_fragment_shader->GetUniformLocation("inputResolution"); + m_attributes[1].m_loc_output_resolution = m_fragment_shader->GetUniformLocation("outputResolution"); + } + else + { + forceLogDebug_printf("RendererOutputShader() - todo for Vulkan"); + m_attributes[0].m_loc_texture_src_resolution = -1; + m_attributes[0].m_loc_input_resolution = -1; + m_attributes[0].m_loc_output_resolution = -1; + + m_attributes[1].m_loc_texture_src_resolution = -1; + m_attributes[1].m_loc_input_resolution = -1; + m_attributes[1].m_loc_output_resolution = -1; + } + +} + +void RendererOutputShader::SetUniformParameters(const LatteTextureView& texture_view, const Vector2i& input_res, const Vector2i& output_res) const +{ + float res[2]; + // vertex shader + if (m_attributes[0].m_loc_texture_src_resolution != -1) + { + res[0] = (float)texture_view.baseTexture->width; + res[1] = (float)texture_view.baseTexture->height; + m_vertex_shader->SetUniform2fv(m_attributes[0].m_loc_texture_src_resolution, res, 1); + } + + if (m_attributes[0].m_loc_input_resolution != -1) + { + res[0] = (float)input_res.x; + res[1] = (float)input_res.y; + m_vertex_shader->SetUniform2fv(m_attributes[0].m_loc_input_resolution, res, 1); + } + + if (m_attributes[0].m_loc_output_resolution != -1) + { + res[0] = (float)output_res.x; + res[1] = (float)output_res.y; + m_vertex_shader->SetUniform2fv(m_attributes[0].m_loc_output_resolution, res, 1); + } + + // fragment shader + if (m_attributes[1].m_loc_texture_src_resolution != -1) + { + res[0] = (float)texture_view.baseTexture->width; + res[1] = (float)texture_view.baseTexture->height; + m_fragment_shader->SetUniform2fv(m_attributes[1].m_loc_texture_src_resolution, res, 1); + } + + if (m_attributes[1].m_loc_input_resolution != -1) + { + res[0] = (float)input_res.x; + res[1] = (float)input_res.y; + m_fragment_shader->SetUniform2fv(m_attributes[1].m_loc_input_resolution, res, 1); + } + + if (m_attributes[1].m_loc_output_resolution != -1) + { + res[0] = (float)output_res.x; + res[1] = (float)output_res.y; + m_fragment_shader->SetUniform2fv(m_attributes[1].m_loc_output_resolution, res, 1); + } +} + +void RendererOutputShader::Bind() const +{ + g_renderer->shader_bind(m_vertex_shader); + g_renderer->shader_bind(m_fragment_shader); +} + +RendererOutputShader* RendererOutputShader::s_copy_shader; +RendererOutputShader* RendererOutputShader::s_copy_shader_ud; + +RendererOutputShader* RendererOutputShader::s_bicubic_shader; +RendererOutputShader* RendererOutputShader::s_bicubic_shader_ud; + +RendererOutputShader* RendererOutputShader::s_hermit_shader; +RendererOutputShader* RendererOutputShader::s_hermit_shader_ud; + +std::string RendererOutputShader::GetOpenGlVertexSource(bool render_upside_down) +{ + // vertex shader + std::ostringstream vertex_source; + vertex_source << + R"(#version 400 +out vec2 passUV; + +out gl_PerVertex +{ + vec4 gl_Position; +}; + +void main(){ + vec2 vPos; + vec2 vUV; + int vID = gl_VertexID; +)"; + + if (render_upside_down) + { + vertex_source << + R"( if( vID == 0 ) { vPos = vec2(1.0,1.0); vUV = vec2(1.0,0.0); } + else if( vID == 1 ) { vPos = vec2(-1.0,1.0); vUV = vec2(0.0,0.0); } + else if( vID == 2 ) { vPos = vec2(-1.0,-1.0); vUV = vec2(0.0,1.0); } + else if( vID == 3 ) { vPos = vec2(-1.0,-1.0); vUV = vec2(0.0,1.0); } + else if( vID == 4 ) { vPos = vec2(1.0,-1.0); vUV = vec2(1.0,1.0); } + else if( vID == 5 ) { vPos = vec2(1.0,1.0); vUV = vec2(1.0,0.0); } + )"; + } + else + { + vertex_source << + R"( if( vID == 0 ) { vPos = vec2(1.0,1.0); vUV = vec2(1.0,1.0); } + else if( vID == 1 ) { vPos = vec2(-1.0,1.0); vUV = vec2(0.0,1.0); } + else if( vID == 2 ) { vPos = vec2(-1.0,-1.0); vUV = vec2(0.0,0.0); } + else if( vID == 3 ) { vPos = vec2(-1.0,-1.0); vUV = vec2(0.0,0.0); } + else if( vID == 4 ) { vPos = vec2(1.0,-1.0); vUV = vec2(1.0,0.0); } + else if( vID == 5 ) { vPos = vec2(1.0,1.0); vUV = vec2(1.0,1.0); } + )"; + } + + vertex_source << + R"( passUV = vUV; + gl_Position = vec4(vPos, 0.0, 1.0); +} +)"; + return vertex_source.str(); +} + +std::string RendererOutputShader::GetVulkanVertexSource(bool render_upside_down) +{ + // vertex shader + std::ostringstream vertex_source; + vertex_source << + R"(#version 450 +layout(location = 0) out vec2 passUV; + +out gl_PerVertex +{ + vec4 gl_Position; +}; + +void main(){ + vec2 vPos; + vec2 vUV; + int vID = gl_VertexIndex; +)"; + + if (render_upside_down) + { + vertex_source << + R"( if( vID == 0 ) { vPos = vec2(1.0,1.0); vUV = vec2(1.0,0.0); } + else if( vID == 1 ) { vPos = vec2(-1.0,1.0); vUV = vec2(0.0,0.0); } + else if( vID == 2 ) { vPos = vec2(-1.0,-1.0); vUV = vec2(0.0,1.0); } + else if( vID == 3 ) { vPos = vec2(-1.0,-1.0); vUV = vec2(0.0,1.0); } + else if( vID == 4 ) { vPos = vec2(1.0,-1.0); vUV = vec2(1.0,1.0); } + else if( vID == 5 ) { vPos = vec2(1.0,1.0); vUV = vec2(1.0,0.0); } + )"; + } + else + { + vertex_source << + R"( if( vID == 0 ) { vPos = vec2(1.0,1.0); vUV = vec2(1.0,1.0); } + else if( vID == 1 ) { vPos = vec2(-1.0,1.0); vUV = vec2(0.0,1.0); } + else if( vID == 2 ) { vPos = vec2(-1.0,-1.0); vUV = vec2(0.0,0.0); } + else if( vID == 3 ) { vPos = vec2(-1.0,-1.0); vUV = vec2(0.0,0.0); } + else if( vID == 4 ) { vPos = vec2(1.0,-1.0); vUV = vec2(1.0,0.0); } + else if( vID == 5 ) { vPos = vec2(1.0,1.0); vUV = vec2(1.0,1.0); } + )"; + } + + vertex_source << + R"( passUV = vUV; + gl_Position = vec4(vPos, 0.0, 1.0); +} +)"; + return vertex_source.str(); +} +void RendererOutputShader::InitializeStatic() +{ + std::string vertex_source, vertex_source_ud; + // vertex shader + if (g_renderer->GetType() == RendererAPI::OpenGL) + { + vertex_source = GetOpenGlVertexSource(false); + vertex_source_ud = GetOpenGlVertexSource(true); + + s_copy_shader = new RendererOutputShader(vertex_source, s_copy_shader_source); + s_copy_shader_ud = new RendererOutputShader(vertex_source_ud, s_copy_shader_source); + + s_bicubic_shader = new RendererOutputShader(vertex_source, s_bicubic_shader_source); + s_bicubic_shader_ud = new RendererOutputShader(vertex_source_ud, s_bicubic_shader_source); + + s_hermit_shader = new RendererOutputShader(vertex_source, s_hermite_shader_source); + s_hermit_shader_ud = new RendererOutputShader(vertex_source_ud, s_hermite_shader_source); + } + else + { + vertex_source = GetVulkanVertexSource(false); + vertex_source_ud = GetVulkanVertexSource(true); + + s_copy_shader = new RendererOutputShader(vertex_source, s_copy_shader_source); + s_copy_shader_ud = new RendererOutputShader(vertex_source_ud, s_copy_shader_source); + + /* s_bicubic_shader = new RendererOutputShader(vertex_source, s_bicubic_shader_source); TODO + s_bicubic_shader_ud = new RendererOutputShader(vertex_source_ud, s_bicubic_shader_source); + + s_hermit_shader = new RendererOutputShader(vertex_source, s_hermite_shader_source); + s_hermit_shader_ud = new RendererOutputShader(vertex_source_ud, s_hermite_shader_source);*/ + } +} diff --git a/src/Cafe/HW/Latte/Renderer/RendererOuputShader.h b/src/Cafe/HW/Latte/Renderer/RendererOuputShader.h new file mode 100644 index 00000000..253990e2 --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/RendererOuputShader.h @@ -0,0 +1,65 @@ +#pragma once + +#include "Cafe/HW/Latte/Renderer/RendererShader.h" +#include "util/math/vector2.h" + +#include "Cafe/HW/Latte/Core/LatteTexture.h" + +class RendererOutputShader +{ +public: + enum Shader + { + kCopy, + kBicubic, + kHermit, + }; + RendererOutputShader(const std::string& vertex_source, const std::string& fragment_source); + virtual ~RendererOutputShader() = default; + + void SetUniformParameters(const LatteTextureView& texture_view, const Vector2i& input_res, const Vector2i& output_res) const; + void Bind() const; + + RendererShader* GetVertexShader() const + { + return m_vertex_shader; + } + + RendererShader* GetFragmentShader() const + { + return m_fragment_shader; + } + + static void InitializeStatic(); + + static RendererOutputShader* s_copy_shader; + static RendererOutputShader* s_copy_shader_ud; + + static RendererOutputShader* s_bicubic_shader; + static RendererOutputShader* s_bicubic_shader_ud; + + static RendererOutputShader* s_hermit_shader; + static RendererOutputShader* s_hermit_shader_ud; + + static std::string GetVulkanVertexSource(bool render_upside_down); + static std::string GetOpenGlVertexSource(bool render_upside_down); + +protected: + RendererShader* m_vertex_shader; + RendererShader* m_fragment_shader; + + struct + { + sint32 m_loc_texture_src_resolution = -1; + sint32 m_loc_input_resolution = -1; + sint32 m_loc_output_resolution = -1; + } m_attributes[2]{}; + +private: + static const std::string s_copy_shader_source; + static const std::string s_bicubic_shader_source; + static const std::string s_hermite_shader_source; + + static const std::string s_bicubic_shader_source_vk; + static const std::string s_hermite_shader_source_vk; +}; diff --git a/src/Cafe/HW/Latte/Renderer/RendererShader.cpp b/src/Cafe/HW/Latte/Renderer/RendererShader.cpp new file mode 100644 index 00000000..b72a5659 --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/RendererShader.cpp @@ -0,0 +1,36 @@ +#include "Cafe/HW/Latte/Renderer/RendererShader.h" +#include "Cafe/GameProfile/GameProfile.h" + +// generate a Cemu version and setting dependent id +uint32 RendererShader::GeneratePrecompiledCacheId() +{ + uint32 v = 0; + const char* s = EMULATOR_VERSION_SUFFIX; + while (*s) + { + v = _rotl(v, 7); + v += (uint32)(*s); + s++; + } + v += (EMULATOR_VERSION_LEAD * 1000000u); + v += (EMULATOR_VERSION_MAJOR * 10000u); + v += (EMULATOR_VERSION_MINOR * 100u); + + // settings that can influence shaders + v += (uint32)g_current_game_profile->GetAccurateShaderMul() * 133; // this option modifies shaders + + return v; +} + +void RendererShader::GenerateShaderPrecompiledCacheFilename(RendererShader::ShaderType type, uint64 baseHash, uint64 auxHash, uint64& h1, uint64& h2) +{ + h1 = baseHash; + h2 = auxHash; + + if (type == RendererShader::ShaderType::kVertex) + h2 += 0xA16374cull; + else if (type == RendererShader::ShaderType::kFragment) + h2 += 0x8752deull; + else if (type == RendererShader::ShaderType::kGeometry) + h2 += 0x65a035ull; +} \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Renderer/RendererShader.h b/src/Cafe/HW/Latte/Renderer/RendererShader.h new file mode 100644 index 00000000..1c15211f --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/RendererShader.h @@ -0,0 +1,42 @@ +#pragma once + +class RendererShader +{ +public: + enum class ShaderType + { + kVertex, + kFragment, + kGeometry + }; + + virtual ~RendererShader() = default; + + ShaderType GetType() const { return m_type; } + + virtual void PreponeCompilation(bool isRenderThread) = 0; // if shader not yet compiled, compile it synchronously (if possible) or alternatively wait for compilation. After this function IsCompiled() is guaranteed to be true + virtual bool IsCompiled() = 0; + virtual bool WaitForCompiled() = 0; + + virtual sint32 GetUniformLocation(const char* name) = 0; + + virtual void SetUniform1iv(sint32 location, void* data, sint32 count) = 0; + virtual void SetUniform2fv(sint32 location, void* data, sint32 count) = 0; + virtual void SetUniform4iv(sint32 location, void* data, sint32 count) = 0; + +protected: + // if isGameShader is true, then baseHash and auxHash are valid + RendererShader(ShaderType type, uint64 baseHash, uint64 auxHash, bool isGameShader, bool isGfxPackShader) + : m_type(type), m_baseHash(baseHash), m_auxHash(auxHash), m_isGameShader(isGameShader), m_isGfxPackShader(isGfxPackShader) {} + + static uint32 GeneratePrecompiledCacheId(); + static void GenerateShaderPrecompiledCacheFilename(ShaderType type, uint64 baseHash, uint64 auxHash, uint64& h1, uint64& h2); + +protected: + ShaderType m_type; + uint64 m_baseHash; + uint64 m_auxHash; + bool m_isGameShader; + bool m_isGfxPackShader; +}; + diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/CachedFBOVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/CachedFBOVk.cpp new file mode 100644 index 00000000..66f7ba95 --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/CachedFBOVk.cpp @@ -0,0 +1,229 @@ +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/CachedFBOVk.h" + +void CachedFBOVk::CreateRenderPass() +{ + VKRObjectRenderPass::AttachmentInfo_t attachmentInfo; + + for (int i = 0; i < 8; ++i) + { + auto& buffer = colorBuffer[i]; + auto textureViewVk = (LatteTextureViewVk*)buffer.texture; + if (!textureViewVk) + { + attachmentInfo.colorAttachment[i].viewObj = nullptr; + continue; + } + // setup color attachment + auto viewObj = textureViewVk->GetViewRGBA(); + attachmentInfo.colorAttachment[i].viewObj = viewObj; + attachmentInfo.colorAttachment[i].format = textureViewVk->GetFormat(); + } + + // setup depth attachment + if (depthBuffer.texture) + { + LatteTextureViewVk* depthTexVk = static_cast<LatteTextureViewVk*>(depthBuffer.texture); + auto depthBufferViewObj = depthTexVk->GetViewRGBA(); + attachmentInfo.depthAttachment.viewObj = depthBufferViewObj; + attachmentInfo.depthAttachment.format = depthTexVk->GetFormat(); + attachmentInfo.depthAttachment.hasStencil = depthTexVk->baseTexture->hasStencil; + } + else + { + // no depth attachment + attachmentInfo.depthAttachment.viewObj = nullptr; + } + + m_vkrObjRenderPass = new VKRObjectRenderPass(attachmentInfo); +} + +CachedFBOVk::~CachedFBOVk() +{ + while (!m_usedByPipelines.empty()) + delete m_usedByPipelines[0]; + auto vkr = VulkanRenderer::GetInstance(); + vkr->releaseDestructibleObject(m_vkrObjFramebuffer); + m_vkrObjFramebuffer = nullptr; + vkr->releaseDestructibleObject(m_vkrObjRenderPass); + m_vkrObjRenderPass = nullptr; +} + +VKRObjectTextureView* CachedFBOVk::GetColorBufferImageView(uint32 index) +{ + cemu_assert(index < 8); + auto& cb = colorBuffer[index]; + auto textureViewVk = (LatteTextureViewVk*)cb.texture; + if (!textureViewVk) + return nullptr; + auto viewDim = textureViewVk->dim; + if (viewDim == Latte::E_DIM::DIM_3D) + viewDim = Latte::E_DIM::DIM_2D; // bind 3D texture slices as 2D images + return textureViewVk->GetViewRGBA(); +} + +VKRObjectTextureView* CachedFBOVk::GetDepthStencilBufferImageView(bool& hasStencil) +{ + hasStencil = false; + auto textureViewVk = (LatteTextureViewVk*)depthBuffer.texture; + if (!textureViewVk) + return nullptr; + cemu_assert_debug(textureViewVk->numMip == 1); + hasStencil = textureViewVk->baseTexture->hasStencil; + return textureViewVk->GetViewRGBA(); +} + +void CachedFBOVk::CreateFramebuffer() +{ + std::array<VKRObjectTextureView*, 9> imageViews{}; + int imageViewIndex = 0; + for (uint32 i = 0; i < 8; ++i) + { + VKRObjectTextureView* cbView = GetColorBufferImageView(i); + if (!cbView) + continue; + imageViews[imageViewIndex++] = cbView; + } + + bool hasStencil = false; + VKRObjectTextureView* depthStencilView = GetDepthStencilBufferImageView(hasStencil); + if (depthStencilView) + imageViews[imageViewIndex++] = depthStencilView; + + cemu_assert_debug(imageViewIndex < 9); + + m_vkrObjFramebuffer = new VKRObjectFramebuffer(m_vkrObjRenderPass, std::span<VKRObjectTextureView*>(imageViews.data(), imageViewIndex), m_size); + + m_extend = { (uint32)m_size.x, (uint32)m_size.y }; +} + + +void CachedFBOVk::InitDynamicRenderingData() +{ + // init struct for KHR_dynamic_rendering + for (int i = 0; i < 8; ++i) + { + m_vkColorAttachments[i].sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR; + m_vkColorAttachments[i].pNext = nullptr; + m_vkColorAttachments[i].imageLayout = VK_IMAGE_LAYOUT_GENERAL; + + m_vkColorAttachments[i].resolveMode = VK_RESOLVE_MODE_NONE; + m_vkColorAttachments[i].resolveImageView = VK_NULL_HANDLE; + m_vkColorAttachments[i].resolveImageLayout = VK_IMAGE_LAYOUT_UNDEFINED; + + m_vkColorAttachments[i].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + m_vkColorAttachments[i].storeOp = VK_ATTACHMENT_STORE_OP_STORE; + + // ignore clearValue + + VKRObjectTextureView* cbView = GetColorBufferImageView(i); + + auto& buffer = colorBuffer[i]; + if (!cbView) + { + m_vkColorAttachments[i].imageView = VK_NULL_HANDLE; + continue; + } + else + m_vkColorAttachments[i].imageView = cbView->m_textureImageView; + } + + m_vkRenderingInfo.pColorAttachments = m_vkColorAttachments; + m_vkRenderingInfo.colorAttachmentCount = 8; + // trim the color attachment list if tail entries are not set + while (m_vkRenderingInfo.colorAttachmentCount > 0) + { + if (m_vkColorAttachments[m_vkRenderingInfo.colorAttachmentCount - 1].imageView) + break; + m_vkRenderingInfo.colorAttachmentCount--; + } + + // initially set both stencil and depth attachment to an empty default + m_vkDepthAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR; + m_vkDepthAttachment.pNext = nullptr; + m_vkDepthAttachment.imageLayout = VK_IMAGE_LAYOUT_GENERAL; + m_vkDepthAttachment.resolveMode = VK_RESOLVE_MODE_NONE; + m_vkDepthAttachment.resolveImageView = VK_NULL_HANDLE; + m_vkDepthAttachment.resolveImageLayout = VK_IMAGE_LAYOUT_UNDEFINED; + m_vkDepthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + m_vkDepthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + + m_vkStencilAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR; + m_vkStencilAttachment.pNext = nullptr; + m_vkStencilAttachment.imageLayout = VK_IMAGE_LAYOUT_GENERAL; + m_vkStencilAttachment.resolveMode = VK_RESOLVE_MODE_NONE; + m_vkStencilAttachment.resolveImageView = VK_NULL_HANDLE; + m_vkStencilAttachment.resolveImageLayout = VK_IMAGE_LAYOUT_UNDEFINED; + m_vkStencilAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + m_vkStencilAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + + m_vkRenderingInfo.pDepthAttachment = nullptr; + m_vkRenderingInfo.pStencilAttachment = nullptr; + + bool hasStencil = false; + VKRObjectTextureView* depthStencilView = GetDepthStencilBufferImageView(hasStencil); + + // setup depth and stencil attachment + if (depthStencilView) + { + m_vkDepthAttachment.imageView = depthStencilView->m_textureImageView; + m_vkDepthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + m_vkDepthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + m_vkRenderingInfo.pDepthAttachment = &m_vkDepthAttachment; + if (hasStencil) + { + m_vkStencilAttachment.imageView = depthStencilView->m_textureImageView; + m_vkStencilAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + m_vkStencilAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + m_vkRenderingInfo.pStencilAttachment = &m_vkStencilAttachment; + } + } + + m_vkRenderingInfo.sType = VK_STRUCTURE_TYPE_RENDERING_INFO_KHR; + m_vkRenderingInfo.pNext = nullptr; + m_vkRenderingInfo.flags = 0; + m_vkRenderingInfo.renderArea.offset = { 0, 0 }; + m_vkRenderingInfo.renderArea.extent = m_extend; + m_vkRenderingInfo.viewMask = 0; // multiview disabled + m_vkRenderingInfo.layerCount = 1; +} + + +uint32 s_currentCollisionCheckIndex = 1; + +bool CachedFBOVk::CheckForCollision(VkDescriptorSetInfo* vsDS, VkDescriptorSetInfo* gsDS, VkDescriptorSetInfo* psDS) const +{ + s_currentCollisionCheckIndex++; + const uint32 curColIndex = s_currentCollisionCheckIndex; + for (auto& itr : m_referencedTextures) + { + LatteTextureVk* vkTex = (LatteTextureVk*)itr; + vkTex->m_collisionCheckIndex = curColIndex; + } + if (vsDS) + { + for (auto& itr : vsDS->list_fboCandidates) + { + if (itr->m_collisionCheckIndex == curColIndex) + return true; + } + } + if (gsDS) + { + for (auto& itr : gsDS->list_fboCandidates) + { + if (itr->m_collisionCheckIndex == curColIndex) + return true; + } + } + if (psDS) + { + for (auto& itr : psDS->list_fboCandidates) + { + if (itr->m_collisionCheckIndex == curColIndex) + return true; + } + } + return false; +} \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/CachedFBOVk.h b/src/Cafe/HW/Latte/Renderer/Vulkan/CachedFBOVk.h new file mode 100644 index 00000000..b83bd96c --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/CachedFBOVk.h @@ -0,0 +1,84 @@ +#pragma once + +#include "Cafe/HW/Latte/Core/LatteCachedFBO.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/VKRBase.h" + +class CachedFBOVk : public LatteCachedFBO +{ +public: + CachedFBOVk(uint64 key, VkDevice device) + : LatteCachedFBO(key), m_device(device) + { + CreateRenderPass(); + CreateFramebuffer(); + InitDynamicRenderingData(); + } + + ~CachedFBOVk(); + + static inline FSpinlock s_spinlockDependency; + + VKRObjectFramebuffer* GetFramebufferObj() const + { + return m_vkrObjFramebuffer; + } + + VKRObjectRenderPass* GetRenderPassObj() const + { + return m_vkrObjRenderPass; + } + + // for KHR_dynamic_rendering + VkRenderingInfoKHR* GetRenderingInfo() + { + return &m_vkRenderingInfo; + } + + + void TrackDependency(class PipelineInfo* pipelineInfo) + { + s_spinlockDependency.acquire(); + m_usedByPipelines.emplace_back(pipelineInfo); + s_spinlockDependency.release(); + } + + void RemoveDependency(class PipelineInfo* pipelineInfo) + { + s_spinlockDependency.acquire(); + vectorRemoveByValue(m_usedByPipelines, pipelineInfo); + s_spinlockDependency.release(); + } + + [[nodiscard]] const VkExtent2D& GetExtend() const { return m_extend;} + + // checks if any of the sampled textures are output by the FBO + bool CheckForCollision(VkDescriptorSetInfo* vsDS, VkDescriptorSetInfo* gsDS, VkDescriptorSetInfo* psDS) const; + +private: + + void CreateRenderPass(); + void CreateFramebuffer(); + + void InitDynamicRenderingData(); + + VKRObjectTextureView* GetColorBufferImageView(uint32 index); + VKRObjectTextureView* GetDepthStencilBufferImageView(bool& hasStencil); + + VkDevice m_device; + VKRObjectRenderPass* m_vkrObjRenderPass{}; + VKRObjectFramebuffer* m_vkrObjFramebuffer{}; + + VkExtent2D m_extend; + + // for KHR_dynamic_rendering + VkRenderingInfoKHR m_vkRenderingInfo; + VkRenderingAttachmentInfoKHR m_vkColorAttachments[8]; + VkRenderingAttachmentInfoKHR m_vkDepthAttachment; + VkRenderingAttachmentInfoKHR m_vkStencilAttachment; + //uint8 m_vkColorAttachmentsCount{0}; + bool m_vkHasDepthAttachment{ false }; + bool m_vkHasStencilAttachment{ false }; + + + std::vector<class PipelineInfo*> m_usedByPipelines; // PipelineInfo objects which use this renderpass/framebuffer +}; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureViewVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureViewVk.cpp new file mode 100644 index 00000000..62382290 --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureViewVk.cpp @@ -0,0 +1,239 @@ +#include "Cafe/HW/Latte/Renderer/Vulkan/LatteTextureViewVk.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" +#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" + +uint32 LatteTextureVk_AdjustTextureCompSel(Latte::E_GX2SURFFMT format, uint32 compSel) +{ + switch (format) + { + case Latte::E_GX2SURFFMT::R8_UNORM: // R8 is replicated on all channels (while OpenGL would return 1.0 for BGA instead) + case Latte::E_GX2SURFFMT::R8_SNORM: // probably the same as _UNORM, but needs testing + if (compSel >= 1 && compSel <= 3) + compSel = 0; + break; + case Latte::E_GX2SURFFMT::A1_B5_G5_R5_UNORM: // order of components is reversed (RGBA -> ABGR) + if (compSel >= 0 && compSel <= 3) + compSel = 3 - compSel; + break; + case Latte::E_GX2SURFFMT::BC4_UNORM: + case Latte::E_GX2SURFFMT::BC4_SNORM: + if (compSel >= 1 && compSel <= 3) + compSel = 0; + break; + case Latte::E_GX2SURFFMT::BC5_UNORM: + case Latte::E_GX2SURFFMT::BC5_SNORM: + // RG maps to RG + // B maps to ? + // A maps to G (guessed) + if (compSel == 3) + compSel = 1; // read Alpha as Green + break; + case Latte::E_GX2SURFFMT::A2_B10_G10_R10_UNORM: + // reverse components (Wii U: ABGR, OpenGL: RGBA) + // used in Resident Evil Revelations + if (compSel >= 0 && compSel <= 3) + compSel = 3 - compSel; + break; + case Latte::E_GX2SURFFMT::X24_G8_UINT: + // map everything to alpha? + if (compSel >= 0 && compSel <= 3) + compSel = 3; + break; + case Latte::E_GX2SURFFMT::R4_G4_UNORM: + // red and green swapped + if (compSel == 0) + compSel = 1; + else if (compSel == 1) + compSel = 0; + break; + default: + break; + } + return compSel; +} + +LatteTextureViewVk::LatteTextureViewVk(VkDevice device, LatteTextureVk* texture, Latte::E_DIM dim, Latte::E_GX2SURFFMT format, sint32 firstMip, sint32 mipCount, sint32 firstSlice, sint32 sliceCount) + : LatteTextureView(texture, firstMip, mipCount, firstSlice, sliceCount, dim, format), m_device(device) +{ + if (dim != texture->dim || format != texture->format) + { + VulkanRenderer::FormatInfoVK texFormatInfo; + VulkanRenderer::GetInstance()->GetTextureFormatInfoVK(format, texture->isDepth, dim, 0, 0, &texFormatInfo); + m_format = texFormatInfo.vkImageFormat; + } + else + m_format = texture->GetFormat(); + m_uniqueId = VulkanRenderer::GetInstance()->GenUniqueId(); +} + +LatteTextureViewVk::~LatteTextureViewVk() +{ + while (!list_descriptorSets.empty()) + delete list_descriptorSets[0]; + + if (m_smallCacheView0) + VulkanRenderer::GetInstance()->releaseDestructibleObject(m_smallCacheView0); + if (m_smallCacheView1) + VulkanRenderer::GetInstance()->releaseDestructibleObject(m_smallCacheView1); + + if (m_fallbackCache) + { + for (auto& itr : *m_fallbackCache) + VulkanRenderer::GetInstance()->releaseDestructibleObject(itr.second); + delete m_fallbackCache; + m_fallbackCache = nullptr; + } +} + +VKRObjectTextureView* LatteTextureViewVk::CreateView(uint32 gpuSamplerSwizzle) +{ + uint32 compSelR = (gpuSamplerSwizzle >> 16) & 0x7; + uint32 compSelG = (gpuSamplerSwizzle >> 19) & 0x7; + uint32 compSelB = (gpuSamplerSwizzle >> 22) & 0x7; + uint32 compSelA = (gpuSamplerSwizzle >> 25) & 0x7; + compSelR = LatteTextureVk_AdjustTextureCompSel(format, compSelR); + compSelG = LatteTextureVk_AdjustTextureCompSel(format, compSelG); + compSelB = LatteTextureVk_AdjustTextureCompSel(format, compSelB); + compSelA = LatteTextureVk_AdjustTextureCompSel(format, compSelA); + + VkImageViewCreateInfo viewInfo{}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.image = GetBaseImage()->GetImageObj()->m_image; + viewInfo.viewType = GetImageViewTypeFromGX2Dim(dim); + viewInfo.format = m_format; + viewInfo.subresourceRange.aspectMask = GetBaseImage()->GetImageAspect(); + if (viewInfo.subresourceRange.aspectMask & VK_IMAGE_ASPECT_DEPTH_BIT) + viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; // make sure stencil is never set, we only support sampling depth for now + viewInfo.subresourceRange.baseMipLevel = firstMip; + viewInfo.subresourceRange.levelCount = this->numMip; + if (viewInfo.viewType == VK_IMAGE_VIEW_TYPE_3D && baseTexture->Is3DTexture()) + { + cemu_assert_debug(firstMip == 0); + cemu_assert_debug(this->numSlice == baseTexture->depth); + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = 1; + } + else + { + viewInfo.subresourceRange.baseArrayLayer = firstSlice; + viewInfo.subresourceRange.layerCount = this->numSlice; + } + + static const VkComponentSwizzle swizzle[] = + { + VK_COMPONENT_SWIZZLE_R, + VK_COMPONENT_SWIZZLE_G, + VK_COMPONENT_SWIZZLE_B, + VK_COMPONENT_SWIZZLE_A, + VK_COMPONENT_SWIZZLE_ZERO, + VK_COMPONENT_SWIZZLE_ONE, + VK_COMPONENT_SWIZZLE_ZERO, + VK_COMPONENT_SWIZZLE_ZERO + }; + + viewInfo.components.r = swizzle[compSelR]; + viewInfo.components.g = swizzle[compSelG]; + viewInfo.components.b = swizzle[compSelB]; + viewInfo.components.a = swizzle[compSelA]; + + VkImageView view; + if (vkCreateImageView(m_device, &viewInfo, nullptr, &view) != VK_SUCCESS) + throw std::runtime_error("failed to create texture image view!"); + + return new VKRObjectTextureView(GetBaseImage()->GetImageObj(), view); +} + +VKRObjectTextureView* LatteTextureViewVk::GetViewRGBA() +{ + return GetSamplerView(0x06880000); // RGBA swizzle +} + +VKRObjectTextureView* LatteTextureViewVk::GetSamplerView(uint32 gpuSamplerSwizzle) +{ + gpuSamplerSwizzle &= 0x0FFF0000; + // look up view in cache + // intentionally using unrolled code here instead of a loop for a small performance gain + if (m_smallCacheSwizzle0 == gpuSamplerSwizzle) + return m_smallCacheView0; + if (m_smallCacheSwizzle1 == gpuSamplerSwizzle) + return m_smallCacheView1; + auto fallbackCache = m_fallbackCache; + if (m_fallbackCache) + { + const auto it = fallbackCache->find(gpuSamplerSwizzle); + if (it != fallbackCache->cend()) + return it->second; + } + // not cached, create new view and store in cache + auto viewObj = CreateView(gpuSamplerSwizzle); + if (m_smallCacheSwizzle0 == CACHE_EMPTY_ENTRY) + { + m_smallCacheSwizzle0 = gpuSamplerSwizzle; + m_smallCacheView0 = viewObj; + } + else if (m_smallCacheSwizzle1 == CACHE_EMPTY_ENTRY) + { + m_smallCacheSwizzle1 = gpuSamplerSwizzle; + m_smallCacheView1 = viewObj; + } + else + { + if (!m_fallbackCache) + m_fallbackCache = new std::unordered_map<uint32, VKRObjectTextureView*>(); + m_fallbackCache->insert_or_assign(gpuSamplerSwizzle, viewObj); + } + return viewObj; +} + +VkSampler LatteTextureViewVk::GetDefaultTextureSampler(bool useLinearTexFilter) +{ + VkSampler& sampler = GetViewRGBA()->m_textureDefaultSampler[useLinearTexFilter ? 1 : 0]; + + if (sampler != VK_NULL_HANDLE) + return sampler; + + VkSamplerCreateInfo samplerInfo{}; + samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + + if (useLinearTexFilter) + { + samplerInfo.magFilter = VK_FILTER_LINEAR; + samplerInfo.minFilter = VK_FILTER_LINEAR; + } + else + { + samplerInfo.magFilter = VK_FILTER_NEAREST; + samplerInfo.minFilter = VK_FILTER_NEAREST; + } + + if (vkCreateSampler(m_device, &samplerInfo, nullptr, &sampler) != VK_SUCCESS) + { + forceLog_printf("Failed to create default sampler"); + throw std::runtime_error("failed to create texture sampler!"); + } + + return sampler; +} + +VkImageViewType LatteTextureViewVk::GetImageViewTypeFromGX2Dim(Latte::E_DIM dim) +{ + switch (dim) + { + case Latte::E_DIM::DIM_1D: + return VK_IMAGE_VIEW_TYPE_1D; + case Latte::E_DIM::DIM_2D: + case Latte::E_DIM::DIM_2D_MSAA: + return VK_IMAGE_VIEW_TYPE_2D; + case Latte::E_DIM::DIM_2D_ARRAY: + return VK_IMAGE_VIEW_TYPE_2D_ARRAY; + case Latte::E_DIM::DIM_3D: + return VK_IMAGE_VIEW_TYPE_3D; + case Latte::E_DIM::DIM_CUBEMAP: + return VK_IMAGE_VIEW_TYPE_CUBE_ARRAY; + default: + cemu_assert_unimplemented(); + } + return VK_IMAGE_VIEW_TYPE_2D; +} \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureViewVk.h b/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureViewVk.h new file mode 100644 index 00000000..06802068 --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureViewVk.h @@ -0,0 +1,44 @@ +#pragma once + +#include "Cafe/HW/Latte/Core/LatteTexture.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/VKRBase.h" + +class LatteTextureViewVk : public LatteTextureView +{ +public: + LatteTextureViewVk(VkDevice device, class LatteTextureVk* texture, Latte::E_DIM dim, Latte::E_GX2SURFFMT format, sint32 firstMip, sint32 mipCount, sint32 firstSlice, sint32 sliceCount); + ~LatteTextureViewVk(); + + uint64 GetUniqueId() const { return m_uniqueId; }; + VKRObjectTextureView* GetViewRGBA(); + VKRObjectTextureView* GetSamplerView(uint32 gpuSamplerSwizzle); + VkSampler GetDefaultTextureSampler(bool useLinearTexFilter); + VkFormat GetFormat() const { return m_format; } + + LatteTextureVk* GetBaseImage() const { return (LatteTextureVk*)baseTexture; } + + void AddDescriptorSetReference(struct VkDescriptorSetInfo* dsInfo) { if (std::find(list_descriptorSets.begin(), list_descriptorSets.end(), dsInfo) == list_descriptorSets.end()) list_descriptorSets.emplace_back(dsInfo); }; + void RemoveDescriptorSetReference(struct VkDescriptorSetInfo* dsInfo) { list_descriptorSets.erase(std::remove(list_descriptorSets.begin(), list_descriptorSets.end(), dsInfo), list_descriptorSets.end()); }; + +private: + VkImageViewType GetImageViewTypeFromGX2Dim(Latte::E_DIM dim); + VKRObjectTextureView* CreateView(uint32 gpuSamplerSwizzle); + + // each texture view holds one Vulkan image view per swizzle mask. Image views are only instantiated when requested via GetViewRGBA/GetSamplerView + // since a large majority of texture views will only have 1 or 2 instantiated image views, we use a small fixed-size cache + // and only allocate the larger map (m_fallbackCache) if necessary + inline static const uint32 CACHE_EMPTY_ENTRY = 0xFFFFFFFF; + + uint32 m_smallCacheSwizzle0 = { CACHE_EMPTY_ENTRY }; + uint32 m_smallCacheSwizzle1 = { CACHE_EMPTY_ENTRY }; + VKRObjectTextureView* m_smallCacheView0 = {}; + VKRObjectTextureView* m_smallCacheView1 = {}; + std::unordered_map<uint32, VKRObjectTextureView*>* m_fallbackCache{}; + + VkDevice m_device; + VkFormat m_format; + std::vector<struct VkDescriptorSetInfo*> list_descriptorSets; // list of descriptors sets referencing this view + + uint64 m_uniqueId; +}; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.cpp new file mode 100644 index 00000000..b41760bc --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.cpp @@ -0,0 +1,141 @@ +#include "Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/LatteTextureViewVk.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" + +LatteTextureVk::LatteTextureVk(class VulkanRenderer* vkRenderer, uint32 textureUnit, Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, uint32 swizzle, + Latte::E_HWTILEMODE tileMode, bool isDepth) + : LatteTexture(textureUnit, dim, physAddress, physMipAddress, format, width, height, depth, pitch, mipLevels, swizzle, tileMode, isDepth), m_vkr(vkRenderer) +{ + vkObjTex = new VKRObjectTexture(); + + VkImageCreateInfo imageInfo{}; + imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + + sint32 effectiveBaseWidth = width; + sint32 effectiveBaseHeight = height; + sint32 effectiveBaseDepth = depth; + if (overwriteInfo.hasResolutionOverwrite) + { + effectiveBaseWidth = overwriteInfo.width; + effectiveBaseHeight = overwriteInfo.height; + effectiveBaseDepth = overwriteInfo.depth; + } + effectiveBaseDepth = std::max(1, effectiveBaseDepth); + + imageInfo.extent.width = effectiveBaseWidth; + imageInfo.extent.height = effectiveBaseHeight; + imageInfo.mipLevels = mipLevels; + imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + + if (dim == Latte::E_DIM::DIM_3D) + { + imageInfo.extent.depth = effectiveBaseDepth; + imageInfo.arrayLayers = 1; + imageInfo.flags |= VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT; + } + else + { + imageInfo.extent.depth = 1; + imageInfo.arrayLayers = effectiveBaseDepth; + if (dim != Latte::E_DIM::DIM_1D && (effectiveBaseDepth % 6) == 0 && effectiveBaseWidth == effectiveBaseHeight) + imageInfo.flags |= VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; + } + + VulkanRenderer::FormatInfoVK texFormatInfo; + vkRenderer->GetTextureFormatInfoVK(format, isDepth, dim, effectiveBaseWidth, effectiveBaseHeight, &texFormatInfo); + hasStencil = (texFormatInfo.vkImageAspect & VK_IMAGE_ASPECT_STENCIL_BIT) != 0; + imageInfo.format = texFormatInfo.vkImageFormat; + vkObjTex->m_imageAspect = texFormatInfo.vkImageAspect; + + if (isDepth == false && texFormatInfo.isCompressed) + { + imageInfo.flags |= VK_IMAGE_CREATE_BLOCK_TEXEL_VIEW_COMPATIBLE_BIT; + } + if (isDepth == false) + imageInfo.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; + + if (isDepth) + { + imageInfo.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + } + else + { + if(Latte::IsCompressedFormat(format) == false && texFormatInfo.vkImageFormat != VK_FORMAT_R4G4_UNORM_PACK8) // Vulkan's R4G4 cant be used as a color attachment + imageInfo.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + } + + if (dim == Latte::E_DIM::DIM_2D) + imageInfo.imageType = VK_IMAGE_TYPE_2D; + else if (dim == Latte::E_DIM::DIM_1D) + imageInfo.imageType = VK_IMAGE_TYPE_1D; + else if (dim == Latte::E_DIM::DIM_3D) + imageInfo.imageType = VK_IMAGE_TYPE_3D; + else if (dim == Latte::E_DIM::DIM_2D_ARRAY) + imageInfo.imageType = VK_IMAGE_TYPE_2D; + else if (dim == Latte::E_DIM::DIM_CUBEMAP) + imageInfo.imageType = VK_IMAGE_TYPE_2D; + else if (dim == Latte::E_DIM::DIM_2D_MSAA) + imageInfo.imageType = VK_IMAGE_TYPE_2D; + else + { + cemu_assert_unimplemented(); + } + + if (vkCreateImage(m_vkr->GetLogicalDevice(), &imageInfo, nullptr, &vkObjTex->m_image) != VK_SUCCESS) + m_vkr->UnrecoverableError("Failed to create texture image"); + + if (m_vkr->IsDebugUtilsEnabled() && vkSetDebugUtilsObjectNameEXT) + { + VkDebugUtilsObjectNameInfoEXT objName{}; + objName.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT; + objName.objectType = VK_OBJECT_TYPE_IMAGE; + objName.pNext = nullptr; + objName.objectHandle = (uint64_t)vkObjTex->m_image; + auto objNameStr = fmt::format("tex_{:08x}_fmt{:04x}", physAddress, (uint32)format); + objName.pObjectName = objNameStr.c_str(); + vkSetDebugUtilsObjectNameEXT(m_vkr->GetLogicalDevice(), &objName); + } + + vkObjTex->m_flags = imageInfo.flags; + vkObjTex->m_format = imageInfo.format; + + // init layout array + m_layoutsMips = std::max(mipLevels, 1u); // todo - use effective mip count + m_layoutsDepth = std::max(depth, 1u); + if (Is3DTexture()) + m_layouts.resize(m_layoutsMips, VK_IMAGE_LAYOUT_UNDEFINED); // one per mip + else + m_layouts.resize(m_layoutsMips * m_layoutsDepth, VK_IMAGE_LAYOUT_UNDEFINED); // one per layer per mip +} + +LatteTextureVk::~LatteTextureVk() +{ + cemu_assert_debug(views.empty()); + + m_vkr->surfaceCopy_notifyTextureRelease(this); + + VulkanRenderer::GetInstance()->releaseDestructibleObject(vkObjTex); + vkObjTex = nullptr; +} + +LatteTextureView* LatteTextureVk::CreateView(Latte::E_DIM dim, Latte::E_GX2SURFFMT format, sint32 firstMip, sint32 mipCount, sint32 firstSlice, sint32 sliceCount) +{ + cemu_assert_debug(mipCount > 0); + cemu_assert_debug(sliceCount > 0); + cemu_assert_debug((firstMip + mipCount) <= this->mipLevels); + cemu_assert_debug((firstSlice + sliceCount) <= this->depth); + return new LatteTextureViewVk(m_vkr->GetLogicalDevice(), this, dim, format, firstMip, mipCount, firstSlice, sliceCount); +} + +void LatteTextureVk::setAllocation(struct VkImageMemAllocation* memAllocation) +{ + vkObjTex->m_allocation = memAllocation; +} + +struct VkImageMemAllocation* LatteTextureVk::getAllocation() const +{ + return vkObjTex->m_allocation; +} diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.h b/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.h new file mode 100644 index 00000000..2131ed9c --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.h @@ -0,0 +1,96 @@ +#pragma once + +#include "Cafe/HW/Latte/Core/LatteTexture.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" +#include "util/ChunkedHeap/ChunkedHeap.h" + +#include "Cafe/HW/Latte/Renderer/Vulkan/VKRBase.h" + +class LatteTextureVk : public LatteTexture +{ +public: + LatteTextureVk(class VulkanRenderer* vkRenderer, uint32 textureUnit, Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, + uint32 swizzle, Latte::E_HWTILEMODE tileMode, bool isDepth); + + ~LatteTextureVk(); + + VKRObjectTexture* GetImageObj() const { return vkObjTex; }; + + VkFormat GetFormat() const { return vkObjTex->m_format; } + VkImageAspectFlags GetImageAspect() const { return vkObjTex->m_imageAspect; } + + void setAllocation(struct VkImageMemAllocation* memAllocation); + struct VkImageMemAllocation* getAllocation() const; + + VkImageLayout GetImageLayout(VkImageSubresource& subresource) + { + cemu_assert_debug(subresource.mipLevel < m_layoutsMips); + cemu_assert_debug(subresource.arrayLayer < m_layoutsDepth); + if (Is3DTexture()) + return m_layouts[subresource.mipLevel]; + return m_layouts[subresource.mipLevel * m_layoutsDepth + subresource.arrayLayer]; + } + + VkImageLayout GetImageLayout(VkImageSubresourceRange& subresource) + { + cemu_assert_debug(subresource.baseMipLevel < m_layoutsMips); + cemu_assert_debug(subresource.baseArrayLayer < m_layoutsDepth); + cemu_assert_debug(subresource.levelCount == 1); + if (Is3DTexture()) + return m_layouts[subresource.baseMipLevel]; + cemu_assert_debug(subresource.layerCount > 0); + if (subresource.layerCount > 1) + { + VkImageLayout imgLayout = m_layouts[subresource.baseMipLevel * m_layoutsDepth + subresource.baseArrayLayer]; + for (uint32 i = 1; i < subresource.layerCount; i++) + { + cemu_assert_debug(m_layouts[subresource.baseMipLevel * m_layoutsDepth + subresource.baseArrayLayer + i] == imgLayout); + } + return imgLayout; + } + return m_layouts[subresource.baseMipLevel * m_layoutsDepth + subresource.baseArrayLayer]; + } + + void SetImageLayout(VkImageSubresource& subresource, VkImageLayout newLayout) + { + cemu_assert_debug(subresource.mipLevel < m_layoutsMips); + cemu_assert_debug(subresource.arrayLayer < m_layoutsDepth); + if (Is3DTexture()) + m_layouts[subresource.mipLevel] = newLayout; + else + m_layouts[subresource.mipLevel * m_layoutsDepth + subresource.arrayLayer] = newLayout; + } + + void SetImageLayout(VkImageSubresourceRange& subresource, VkImageLayout newLayout) + { + cemu_assert_debug(subresource.baseMipLevel < m_layoutsMips); + cemu_assert_debug(subresource.baseArrayLayer < m_layoutsDepth); + cemu_assert_debug(subresource.levelCount == 1); + if (Is3DTexture()) + m_layouts[subresource.baseMipLevel] = newLayout; + else + { + for(uint32 i=0; i<subresource.layerCount; i++) + m_layouts[subresource.baseMipLevel * m_layoutsDepth + subresource.baseArrayLayer + i] = newLayout; + } + } + +protected: + LatteTextureView* CreateView(Latte::E_DIM dim, Latte::E_GX2SURFFMT format, sint32 firstMip, sint32 mipCount, sint32 firstSlice, sint32 sliceCount) override; + +public: + uint64 m_vkFlushIndex{}; // used to track read-write dependencies within the same renderpass + + uint64 m_vkFlushIndex_read{}; + uint64 m_vkFlushIndex_write{}; + + uint32 m_collisionCheckIndex{}; // used to track if texture is being both sampled and output to during drawcall + +private: + class VulkanRenderer* m_vkr; + + VKRObjectTexture* vkObjTex{}; + std::vector<VkImageLayout> m_layouts; + uint32 m_layoutsMips; + uint32 m_layoutsDepth; +}; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp new file mode 100644 index 00000000..b107e56d --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp @@ -0,0 +1,473 @@ +#include "Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.h" + +#if __has_include(<glslang/build_info.h>) +#include <glslang/build_info.h> +#else +#define GLSLANG_VERSION_LESS_OR_EQUAL_TO (false) +#endif + +#include <glslang/Public/ShaderLang.h> +#if GLSLANG_VERSION_LESS_OR_EQUAL_TO(11, 0, 0) +#include <glslang/SPIRV/GlslangToSpv.h> +#else +#include <SPIRV/Logger.h> +#include <SPIRV/GlslangToSpv.h> +#endif + + +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" + +#include <thread> + +#include "config/ActiveSettings.h" +#include "config/CemuConfig.h" +#include "util/helpers/ConcurrentQueue.h" + +#include "Cemu/FileCache/FileCache.h" + +bool s_isLoadingShadersVk{ false }; +class FileCache* s_spirvCache{nullptr}; + +extern std::atomic_int g_compiled_shaders_total; +extern std::atomic_int g_compiled_shaders_async; + +const TBuiltInResource DefaultTBuiltInResource = { + /* .MaxLights = */ 32, + /* .MaxClipPlanes = */ 6, + /* .MaxTextureUnits = */ 32, + /* .MaxTextureCoords = */ 32, + /* .MaxVertexAttribs = */ 64, + /* .MaxVertexUniformComponents = */ 4096, + /* .MaxVaryingFloats = */ 64, + /* .MaxVertexTextureImageUnits = */ 32, + /* .MaxCombinedTextureImageUnits = */ 80, + /* .MaxTextureImageUnits = */ 32, + /* .MaxFragmentUniformComponents = */ 4096, + /* .MaxDrawBuffers = */ 32, + /* .MaxVertexUniformVectors = */ 128, + /* .MaxVaryingVectors = */ 8, + /* .MaxFragmentUniformVectors = */ 16, + /* .MaxVertexOutputVectors = */ 16, + /* .MaxFragmentInputVectors = */ 15, + /* .MinProgramTexelOffset = */ -8, + /* .MaxProgramTexelOffset = */ 7, + /* .MaxClipDistances = */ 8, + /* .MaxComputeWorkGroupCountX = */ 65535, + /* .MaxComputeWorkGroupCountY = */ 65535, + /* .MaxComputeWorkGroupCountZ = */ 65535, + /* .MaxComputeWorkGroupSizeX = */ 1024, + /* .MaxComputeWorkGroupSizeY = */ 1024, + /* .MaxComputeWorkGroupSizeZ = */ 64, + /* .MaxComputeUniformComponents = */ 1024, + /* .MaxComputeTextureImageUnits = */ 16, + /* .MaxComputeImageUniforms = */ 8, + /* .MaxComputeAtomicCounters = */ 8, + /* .MaxComputeAtomicCounterBuffers = */ 1, + /* .MaxVaryingComponents = */ 60, + /* .MaxVertexOutputComponents = */ 64, + /* .MaxGeometryInputComponents = */ 64, + /* .MaxGeometryOutputComponents = */ 128, + /* .MaxFragmentInputComponents = */ 128, + /* .MaxImageUnits = */ 8, + /* .MaxCombinedImageUnitsAndFragmentOutputs = */ 8, + /* .MaxCombinedShaderOutputResources = */ 8, + /* .MaxImageSamples = */ 0, + /* .MaxVertexImageUniforms = */ 0, + /* .MaxTessControlImageUniforms = */ 0, + /* .MaxTessEvaluationImageUniforms = */ 0, + /* .MaxGeometryImageUniforms = */ 0, + /* .MaxFragmentImageUniforms = */ 8, + /* .MaxCombinedImageUniforms = */ 8, + /* .MaxGeometryTextureImageUnits = */ 16, + /* .MaxGeometryOutputVertices = */ 256, + /* .MaxGeometryTotalOutputComponents = */ 1024, + /* .MaxGeometryUniformComponents = */ 1024, + /* .MaxGeometryVaryingComponents = */ 64, + /* .MaxTessControlInputComponents = */ 128, + /* .MaxTessControlOutputComponents = */ 128, + /* .MaxTessControlTextureImageUnits = */ 16, + /* .MaxTessControlUniformComponents = */ 1024, + /* .MaxTessControlTotalOutputComponents = */ 4096, + /* .MaxTessEvaluationInputComponents = */ 128, + /* .MaxTessEvaluationOutputComponents = */ 128, + /* .MaxTessEvaluationTextureImageUnits = */ 16, + /* .MaxTessEvaluationUniformComponents = */ 1024, + /* .MaxTessPatchComponents = */ 120, + /* .MaxPatchVertices = */ 32, + /* .MaxTessGenLevel = */ 64, + /* .MaxViewports = */ 16, + /* .MaxVertexAtomicCounters = */ 0, + /* .MaxTessControlAtomicCounters = */ 0, + /* .MaxTessEvaluationAtomicCounters = */ 0, + /* .MaxGeometryAtomicCounters = */ 0, + /* .MaxFragmentAtomicCounters = */ 8, + /* .MaxCombinedAtomicCounters = */ 8, + /* .MaxAtomicCounterBindings = */ 1, + /* .MaxVertexAtomicCounterBuffers = */ 0, + /* .MaxTessControlAtomicCounterBuffers = */ 0, + /* .MaxTessEvaluationAtomicCounterBuffers = */ 0, + /* .MaxGeometryAtomicCounterBuffers = */ 0, + /* .MaxFragmentAtomicCounterBuffers = */ 1, + /* .MaxCombinedAtomicCounterBuffers = */ 1, + /* .MaxAtomicCounterBufferSize = */ 16384, + /* .MaxTransformFeedbackBuffers = */ 4, + /* .MaxTransformFeedbackInterleavedComponents = */ 64, + /* .MaxCullDistances = */ 8, + /* .MaxCombinedClipAndCullDistances = */ 8, + /* .MaxSamples = */ 4, + + /* .maxMeshOutputVerticesNV = */ 256, + /* .maxMeshOutputPrimitivesNV = */ 512, + /* .maxMeshWorkGroupSizeX_NV = */ 32, + /* .maxMeshWorkGroupSizeY_NV = */ 1, + /* .maxMeshWorkGroupSizeZ_NV = */ 1, + /* .maxTaskWorkGroupSizeX_NV = */ 32, + /* .maxTaskWorkGroupSizeY_NV = */ 1, + /* .maxTaskWorkGroupSizeZ_NV = */ 1, + /* .maxMeshViewCountNV = */ 4, + +#if GLSLANG_VERSION_LESS_OR_EQUAL_TO(11, 0, 0) + /* .maxDualSourceDrawBuffersEXT = */ 1, +#endif + + /* .limits = */ { + /* .nonInductiveForLoops = */ 1, + /* .whileLoops = */ 1, + /* .doWhileLoops = */ 1, + /* .generalUniformIndexing = */ 1, + /* .generalAttributeMatrixVectorIndexing = */ 1, + /* .generalVaryingIndexing = */ 1, + /* .generalSamplerIndexing = */ 1, + /* .generalVariableIndexing = */ 1, + /* .generalConstantMatrixVectorIndexing = */ 1, +} +}; + +class _ShaderVkThreadPool +{ +public: + void StartThreads() + { + if (s_threads.empty()) + { + // create thread pool + m_shutdownThread.store(false); + const uint32 threadCount = 2; + for (uint32 i = 0; i < threadCount; ++i) + s_threads.emplace_back(&_ShaderVkThreadPool::CompilerThreadFunc, this); + } + } + + ~_ShaderVkThreadPool() + { + m_shutdownThread.store(true); + for (uint32 i = 0; i < s_threads.size(); ++i) + s_compilationQueueCount.increment(); + for (auto& it : s_threads) + it.join(); + s_threads.clear(); + } + + void CompilerThreadFunc() + { + while (!m_shutdownThread.load(std::memory_order::relaxed)) + { + s_compilationQueueCount.decrementWithWait(); + s_compilationQueueMutex.lock(); + if (s_compilationQueue.empty()) + { + // queue empty again, shaders compiled synchronously via PreponeCompilation() + s_compilationQueueMutex.unlock(); + continue; + } + RendererShaderVk* job = s_compilationQueue.front(); + s_compilationQueue.pop_front(); + // set compilation state + cemu_assert_debug(job->m_compilationState.getValue() == RendererShaderVk::COMPILATION_STATE::QUEUED); + job->m_compilationState.setValue(RendererShaderVk::COMPILATION_STATE::COMPILING); + s_compilationQueueMutex.unlock(); + // compile + job->CompileInternal(false); + ++g_compiled_shaders_async; + // mark as compiled + cemu_assert_debug(job->m_compilationState.getValue() == RendererShaderVk::COMPILATION_STATE::COMPILING); + job->m_compilationState.setValue(RendererShaderVk::COMPILATION_STATE::DONE); + } + } + +public: + std::vector<std::thread> s_threads; + + std::deque<RendererShaderVk*> s_compilationQueue; + CounterSemaphore s_compilationQueueCount; + std::mutex s_compilationQueueMutex; + +private: + std::atomic<bool> m_shutdownThread; +}ShaderVkThreadPool; + +RendererShaderVk::RendererShaderVk(ShaderType type, uint64 baseHash, uint64 auxHash, bool isGameShader, bool isGfxPackShader, const std::string& glslCode) + : RendererShader(type, baseHash, auxHash, isGameShader, isGfxPackShader), m_glslCode(glslCode) +{ + // start async compilation + ShaderVkThreadPool.s_compilationQueueMutex.lock(); + m_compilationState.setValue(COMPILATION_STATE::QUEUED); + ShaderVkThreadPool.s_compilationQueue.push_back(this); + ShaderVkThreadPool.s_compilationQueueCount.increment(); + ShaderVkThreadPool.StartThreads(); + ShaderVkThreadPool.s_compilationQueueMutex.unlock(); +} + +RendererShaderVk::~RendererShaderVk() +{ + VulkanRenderer::GetInstance()->destroyShader(this); +} + +sint32 RendererShaderVk::GetUniformLocation(const char* name) +{ + cemu_assert_suspicious(); + return 0; +} + +void RendererShaderVk::SetUniform1iv(sint32 location, void* data, sint32 count) +{ + cemu_assert_suspicious(); +} + +void RendererShaderVk::SetUniform2fv(sint32 location, void* data, sint32 count) +{ + cemu_assert_suspicious(); +} + +void RendererShaderVk::SetUniform4iv(sint32 location, void* data, sint32 count) +{ + cemu_assert_suspicious(); +} + +void RendererShaderVk::CreateVkShaderModule(std::span<uint32> spirvBuffer) +{ + VkShaderModuleCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = spirvBuffer.size_bytes(); + createInfo.pCode = spirvBuffer.data(); + + VulkanRenderer* vkr = (VulkanRenderer*)g_renderer.get(); + + VkDevice m_device = vkr->GetLogicalDevice(); + + VkResult result = vkCreateShaderModule(m_device, &createInfo, nullptr, &m_shader_module); + if (result != VK_SUCCESS) + { + forceLog_printf("Vulkan: Shader error"); + throw std::runtime_error(fmt::format("Failed to create shader module: {}", result)); + } + + // set debug name + if (vkr->IsDebugUtilsEnabled() && vkSetDebugUtilsObjectNameEXT) + { + VkDebugUtilsObjectNameInfoEXT objName{}; + objName.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT; + objName.objectType = VK_OBJECT_TYPE_SHADER_MODULE; + objName.pNext = nullptr; + objName.objectHandle = (uint64_t)m_shader_module; + auto objNameStr = fmt::format("shader_{:016x}_{:016x}", m_baseHash, m_auxHash); + objName.pObjectName = objNameStr.c_str(); + vkSetDebugUtilsObjectNameEXT(vkr->GetLogicalDevice(), &objName); + } +} + +void RendererShaderVk::FinishCompilation() +{ + m_glslCode.clear(); + m_glslCode.shrink_to_fit(); +} + +void RendererShaderVk::CompileInternal(bool isRenderThread) +{ + // try to retrieve SPIR-V module from cache + if (s_isLoadingShadersVk && (m_isGameShader && !m_isGfxPackShader) && s_spirvCache) + { + cemu_assert_debug(m_baseHash != 0); + uint64 h1, h2; + GenerateShaderPrecompiledCacheFilename(m_type, m_baseHash, m_auxHash, h1, h2); + std::vector<uint8> cacheFileData; + if (s_spirvCache->GetFile({ h1, h2 }, cacheFileData)) + { + // generate shader from cached SPIR-V buffer + CreateVkShaderModule(std::span<uint32>((uint32*)cacheFileData.data(), cacheFileData.size() / sizeof(uint32))); + FinishCompilation(); + return; + } + } + + EShLanguage state; + switch (GetType()) + { + case ShaderType::kVertex: + state = EShLangVertex; + break; + case ShaderType::kFragment: + state = EShLangFragment; + break; + case ShaderType::kGeometry: + state = EShLangGeometry; + break; + default: + cemu_assert_debug(false); + } + + glslang::TShader Shader(state); + const char* cstr = m_glslCode.c_str(); + Shader.setStrings(&cstr, 1); + Shader.setEnvInput(glslang::EShSourceGlsl, state, glslang::EShClientVulkan, 100); + Shader.setEnvClient(glslang::EShClientVulkan, glslang::EShTargetClientVersion::EShTargetVulkan_1_1); + + Shader.setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetLanguageVersion::EShTargetSpv_1_3); + + TBuiltInResource Resources = DefaultTBuiltInResource; + std::string PreprocessedGLSL; + + VulkanRenderer* vkr = (VulkanRenderer*)g_renderer.get(); + + EShMessages messagesPreprocess; + if (vkr->IsDebugUtilsEnabled() && vkSetDebugUtilsObjectNameEXT) + messagesPreprocess = (EShMessages)(EShMsgSpvRules | EShMsgVulkanRules | EShMsgDebugInfo); + else + messagesPreprocess = (EShMessages)(EShMsgSpvRules | EShMsgVulkanRules); + + glslang::TShader::ForbidIncluder Includer; + if (!Shader.preprocess(&Resources, 450, ENoProfile, false, false, messagesPreprocess, &PreprocessedGLSL, Includer)) + { + cemuLog_log(LogType::Force, fmt::format("GLSL Preprocessing Failed For {:016x}_{:016x}: \"{}\"", m_baseHash, m_auxHash, Shader.getInfoLog())); + FinishCompilation(); + return; + } + + EShMessages messagesParseLink; + if (vkr->IsDebugUtilsEnabled() && vkSetDebugUtilsObjectNameEXT) + messagesParseLink = (EShMessages)(EShMsgSpvRules | EShMsgVulkanRules | EShMsgDebugInfo); + else + messagesParseLink = (EShMessages)(EShMsgSpvRules | EShMsgVulkanRules); + + const char* PreprocessedCStr = PreprocessedGLSL.c_str(); + Shader.setStrings(&PreprocessedCStr, 1); + if (!Shader.parse(&Resources, 100, false, messagesParseLink)) + { + cemuLog_log(LogType::Force, fmt::format("GLSL parsing failed for {:016x}_{:016x}: \"{}\"", m_baseHash, m_auxHash, Shader.getInfoLog())); + cemu_assert_debug(false); + FinishCompilation(); + return; + } + + glslang::TProgram Program; + Program.addShader(&Shader); + + if (!Program.link(messagesParseLink)) + { + cemuLog_log(LogType::Force, fmt::format("GLSL linking failed for {:016x}_{:016x}: \"{}\"", m_baseHash, m_auxHash, Program.getInfoLog())); + cemu_assert_debug(false); + FinishCompilation(); + return; + } + + if (!Program.mapIO()) + { + cemuLog_log(LogType::Force, fmt::format("GLSL linking failed for {:016x}_{:016x}: \"{}\"", m_baseHash, m_auxHash, Program.getInfoLog())); + FinishCompilation(); + return; + } + + // temp storage for SPIR-V after translation + std::vector<uint32> spirvBuffer; + spv::SpvBuildLogger logger; + + glslang::SpvOptions spvOptions; + spvOptions.disableOptimizer = false; + spvOptions.generateDebugInfo = (vkr->IsDebugUtilsEnabled() && vkSetDebugUtilsObjectNameEXT); + spvOptions.validate = false; + spvOptions.optimizeSize = true; + + //auto beginTime = benchmarkTimer_start(); + + GlslangToSpv(*Program.getIntermediate(state), spirvBuffer, &logger, &spvOptions); + + //double timeDur = benchmarkTimer_stop(beginTime); + //forceLogRemoveMe_printf("Shader GLSL-to-SPIRV compilation took %lfms Size %08x", timeDur, spirvBuffer.size()*4); + + if (s_spirvCache && m_isGameShader && m_isGfxPackShader == false) + { + uint64 h1, h2; + GenerateShaderPrecompiledCacheFilename(m_type, m_baseHash, m_auxHash, h1, h2); + s_spirvCache->AddFile({ h1, h2 }, (const uint8*)spirvBuffer.data(), spirvBuffer.size() * sizeof(uint32)); + } + + CreateVkShaderModule(spirvBuffer); + + // count compiled shader + if (!s_isLoadingShadersVk) + { + if( m_isGameShader ) + ++g_compiled_shaders_total; + } + + FinishCompilation(); +} + +void RendererShaderVk::PreponeCompilation(bool isRenderThread) +{ + ShaderVkThreadPool.s_compilationQueueMutex.lock(); + bool isStillQueued = m_compilationState.hasState(COMPILATION_STATE::QUEUED); + if (isStillQueued) + { + // remove from queue + ShaderVkThreadPool.s_compilationQueue.erase(std::remove(ShaderVkThreadPool.s_compilationQueue.begin(), ShaderVkThreadPool.s_compilationQueue.end(), this), ShaderVkThreadPool.s_compilationQueue.end()); + m_compilationState.setValue(COMPILATION_STATE::COMPILING); + } + ShaderVkThreadPool.s_compilationQueueMutex.unlock(); + if (!isStillQueued) + { + m_compilationState.waitUntilValue(COMPILATION_STATE::DONE); + --g_compiled_shaders_async; // compilation caused a stall so we don't consider this one async + return; + } + else + { + // compile synchronously + CompileInternal(isRenderThread); + m_compilationState.setValue(COMPILATION_STATE::DONE); + } +} + +bool RendererShaderVk::IsCompiled() +{ + return m_compilationState.hasState(COMPILATION_STATE::DONE); +}; + +bool RendererShaderVk::WaitForCompiled() +{ + m_compilationState.waitUntilValue(COMPILATION_STATE::DONE); + return true; +} + +void RendererShaderVk::ShaderCacheLoading_begin(uint64 cacheTitleId) +{ + if (s_spirvCache) + { + delete s_spirvCache; + s_spirvCache = nullptr; + } + uint32 spirvCacheMagic = GeneratePrecompiledCacheId(); + const std::string cacheFilename = fmt::format("{:016x}_spirv.bin", cacheTitleId); + const std::wstring cachePath = ActiveSettings::GetPath("shaderCache/precompiled/{}", cacheFilename).generic_wstring(); + s_spirvCache = FileCache::Open(cachePath, true, spirvCacheMagic); + if (s_spirvCache == nullptr) + cemuLog_log(LogType::Force, "Unable to open SPIR-V cache {}", cacheFilename); + s_isLoadingShadersVk = true; +} + +void RendererShaderVk::ShaderCacheLoading_end() +{ + // keep g_spirvCache open since we will write to it while the game is running + s_isLoadingShadersVk = false; +} \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.h b/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.h new file mode 100644 index 00000000..5be9ce1f --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.h @@ -0,0 +1,71 @@ +#pragma once + +#include "Cafe/HW/Latte/Renderer/RendererShader.h" +#include "util/helpers/ConcurrentQueue.h" + +#include <vulkan/vulkan_core.h> +#include "util/helpers/Semaphore.h" +#include "util/helpers/fspinlock.h" + +class RendererShaderVk : public RendererShader +{ + friend class VulkanRenderer; + friend class _ShaderVkThreadPool; + + enum class COMPILATION_STATE : uint32 + { + NONE, + QUEUED, + COMPILING, + DONE + }; + +public: + static void ShaderCacheLoading_begin(uint64 cacheTitleId); + static void ShaderCacheLoading_end(); + + RendererShaderVk(ShaderType type, uint64 baseHash, uint64 auxHash, bool isGameShader, bool isGfxPackShader, const std::string& glslCode); + virtual ~RendererShaderVk(); + + sint32 GetUniformLocation(const char* name) override; + void SetUniform1iv(sint32 location, void* data, sint32 count) override; + void SetUniform2fv(sint32 location, void* data, sint32 count) override; + void SetUniform4iv(sint32 location, void* data, sint32 count) override; + VkShaderModule& GetShaderModule() { return m_shader_module; } + + static inline FSpinlock s_dependencyLock; + + void TrackDependency(class PipelineInfo* p) + { + s_dependencyLock.acquire(); + list_pipelineInfo.emplace_back(p); + s_dependencyLock.release(); + } + + void RemoveDependency(class PipelineInfo* p) + { + s_dependencyLock.acquire(); + vectorRemoveByValue(list_pipelineInfo, p); + s_dependencyLock.release(); + } + + void PreponeCompilation(bool isRenderThread) override; + bool IsCompiled() override; + bool WaitForCompiled() override; + +private: + void CompileInternal(bool isRenderThread); + + void FinishCompilation(); + + VkShaderModule m_shader_module = nullptr; + + StateSemaphore<COMPILATION_STATE> m_compilationState{ COMPILATION_STATE::NONE }; + + std::string m_glslCode; + + void CreateVkShaderModule(std::span<uint32> spirvBuffer); + + // pipeline infos + std::vector<class PipelineInfo*> list_pipelineInfo; +}; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/TextureReadbackVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/TextureReadbackVk.cpp new file mode 100644 index 00000000..6acca716 --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/TextureReadbackVk.cpp @@ -0,0 +1,148 @@ +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanTextureReadback.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.h" + +LatteTextureReadbackInfoVk::LatteTextureReadbackInfoVk(VkDevice device, LatteTextureView* textureView) + : LatteTextureReadbackInfo(textureView), m_device(device) +{ + m_image_size = GetImageSize(textureView); +} + +LatteTextureReadbackInfoVk::~LatteTextureReadbackInfoVk() +{ +} + +uint32 LatteTextureReadbackInfoVk::GetImageSize(LatteTextureView* textureView) +{ + const auto* baseTexture = (LatteTextureVk*)textureView->baseTexture; + // handle format + const auto textureFormat = baseTexture->GetFormat(); + if (textureView->format == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM) + { + cemu_assert(textureFormat == VK_FORMAT_R8G8B8A8_UNORM); + return baseTexture->width * baseTexture->height * 4; + } + else if (textureView->format == Latte::E_GX2SURFFMT::R8_UNORM) + { + cemu_assert(textureFormat == VK_FORMAT_R8_UNORM); + return baseTexture->width * baseTexture->height * 1; + } + else if (textureView->format == Latte::E_GX2SURFFMT::R8_G8_B8_A8_SRGB) + { + cemu_assert(textureFormat == VK_FORMAT_R8G8B8A8_SRGB); + return baseTexture->width * baseTexture->height * 4; + } + else if (textureView->format == Latte::E_GX2SURFFMT::R32_G32_B32_A32_FLOAT) + { + cemu_assert(textureFormat == VK_FORMAT_R32G32B32A32_SFLOAT); + return baseTexture->width * baseTexture->height * 16; + } + else if (textureView->format == Latte::E_GX2SURFFMT::R32_FLOAT) + { + cemu_assert(textureFormat == VK_FORMAT_R32_SFLOAT || textureFormat == VK_FORMAT_D32_SFLOAT); + if (baseTexture->isDepth) + return baseTexture->width * baseTexture->height * 4; + else + return baseTexture->width * baseTexture->height * 4; + } + else if (textureView->format == Latte::E_GX2SURFFMT::R16_UNORM) + { + cemu_assert(textureFormat == VK_FORMAT_R16_UNORM); + if (baseTexture->isDepth) + { + cemu_assert_debug(false); + return baseTexture->width * baseTexture->height * 2; + } + else + { + return baseTexture->width * baseTexture->height * 2; + } + } + else if (textureView->format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT) + { + cemu_assert(textureFormat == VK_FORMAT_R16G16B16A16_SFLOAT); + return baseTexture->width * baseTexture->height * 8; + } + else if (textureView->format == Latte::E_GX2SURFFMT::R8_G8_UNORM) + { + cemu_assert(textureFormat == VK_FORMAT_R8G8_UNORM); + return baseTexture->width * baseTexture->height * 2; + } + else if (textureView->format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_UNORM) + { + cemu_assert(textureFormat == VK_FORMAT_R16G16B16A16_UNORM); + return baseTexture->width * baseTexture->height * 8; + } + else if (textureView->format == Latte::E_GX2SURFFMT::D24_S8_UNORM) + { + cemu_assert(textureFormat == VK_FORMAT_D24_UNORM_S8_UINT); + // todo - if driver does not support VK_FORMAT_D24_UNORM_S8_UINT this is represented as VK_FORMAT_D32_SFLOAT_S8_UINT which is 8 bytes + return baseTexture->width * baseTexture->height * 4; + } + else + { + forceLog_printf("Unsupported texture readback format %04x\n", (uint32)textureView->format); + cemu_assert_debug(false); + return 0; + } +} + + +void LatteTextureReadbackInfoVk::StartTransfer() +{ + cemu_assert(m_textureView); + + auto* baseTexture = (LatteTextureVk*)m_textureView->baseTexture; + baseTexture->GetImageObj()->flagForCurrentCommandBuffer(); + + cemu_assert_debug(m_textureView->firstSlice == 0); + cemu_assert_debug(m_textureView->firstMip == 0); + cemu_assert_debug(m_textureView->baseTexture->dim != Latte::E_DIM::DIM_3D); + + VkBufferImageCopy region{}; + region.bufferOffset = m_buffer_offset; + region.bufferRowLength = baseTexture->width; + region.bufferImageHeight = baseTexture->height; + + region.imageSubresource.aspectMask = baseTexture->GetImageAspect(); + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + region.imageSubresource.mipLevel = 0; + + region.imageOffset = {0,0,0}; + region.imageExtent = {(uint32)baseTexture->width,(uint32)baseTexture->height,1}; + + const auto renderer = VulkanRenderer::GetInstance(); + renderer->draw_endRenderPass(); + + renderer->barrier_image<VulkanRenderer::ANY_TRANSFER | VulkanRenderer::IMAGE_WRITE, VulkanRenderer::TRANSFER_READ>(baseTexture, region.imageSubresource, VK_IMAGE_LAYOUT_GENERAL); + + renderer->barrier_sequentializeTransfer(); + + vkCmdCopyImageToBuffer(renderer->getCurrentCommandBuffer(), baseTexture->GetImageObj()->m_image, VK_IMAGE_LAYOUT_GENERAL, m_buffer, 1, ®ion); + + renderer->barrier_sequentializeTransfer(); + + renderer->barrier_image<VulkanRenderer::TRANSFER_READ, VulkanRenderer::ANY_TRANSFER | VulkanRenderer::IMAGE_WRITE>(baseTexture, region.imageSubresource, VK_IMAGE_LAYOUT_GENERAL); // make sure transfer is finished before image is modified + renderer->barrier_bufferRange<VulkanRenderer::TRANSFER_WRITE, VulkanRenderer::HOST_READ>(m_buffer, m_buffer_offset, m_image_size); // make sure transfer is finished before result is read + + m_associatedCommandBufferId = renderer->GetCurrentCommandBufferId(); + m_textureView = nullptr; + + // to decrease latency of readbacks make sure that the current command buffer is submitted soon + renderer->RequestSubmitSoon(); + renderer->RequestSubmitOnIdle(); +} + +bool LatteTextureReadbackInfoVk::IsFinished() +{ + const auto renderer = VulkanRenderer::GetInstance(); + return renderer->HasCommandBufferFinished(m_associatedCommandBufferId); +} + +void LatteTextureReadbackInfoVk::ForceFinish() +{ + const auto renderer = VulkanRenderer::GetInstance(); + renderer->WaitCommandBufferFinished(m_associatedCommandBufferId); +} + diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VKRBase.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VKRBase.h new file mode 100644 index 00000000..89eabdc1 --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VKRBase.h @@ -0,0 +1,192 @@ +#pragma once +#include "util/math/vector2.h" + +class VKRMoveableRefCounterRef +{ +public: + class VKRMoveableRefCounter* ref; +}; + +class VKRMoveableRefCounter +{ +public: + VKRMoveableRefCounter() + { + selfRef = new VKRMoveableRefCounterRef(); + selfRef->ref = this; + } + + virtual ~VKRMoveableRefCounter() + { + cemu_assert_debug(refCount == 0); + + // remove references +#ifndef PUBLIC_RELEASE + for (auto itr : refs) + { + auto& rev = itr->ref->reverseRefs; + rev.erase(std::remove(rev.begin(), rev.end(), this->selfRef), rev.end()); + } +#endif + for (auto itr : refs) + itr->ref->refCount--; + refs.clear(); + delete selfRef; + selfRef = nullptr; + } + + VKRMoveableRefCounter(const VKRMoveableRefCounter&) = delete; + VKRMoveableRefCounter& operator=(const VKRMoveableRefCounter&) = delete; + VKRMoveableRefCounter(VKRMoveableRefCounter&& rhs) noexcept + { + this->refs = std::move(rhs.refs); + this->refCount = rhs.refCount; + rhs.refCount = 0; + this->selfRef = rhs.selfRef; + rhs.selfRef = nullptr; + this->selfRef->ref = this; + } + + VKRMoveableRefCounter& operator=(VKRMoveableRefCounter&& rhs) noexcept + { + cemu_assert(false); + } + + void addRef(VKRMoveableRefCounter* refTarget) + { + this->refs.emplace_back(refTarget->selfRef); + refTarget->refCount++; + +#ifndef PUBLIC_RELEASE + // add reverse ref + refTarget->reverseRefs.emplace_back(this->selfRef); +#endif + } + + // methods to directly increment/decrement ref counter (for situations where no external object is available) + void incRef() + { + this->refCount++; + } + + void decRef() + { + this->refCount--; + } + +protected: + int refCount{}; +private: + VKRMoveableRefCounterRef* selfRef; + std::vector<VKRMoveableRefCounterRef*> refs; +#ifndef PUBLIC_RELEASE + std::vector<VKRMoveableRefCounterRef*> reverseRefs; +#endif + + void moveObj(VKRMoveableRefCounter&& rhs) + { + this->refs = std::move(rhs.refs); + this->refCount = rhs.refCount; + this->selfRef = rhs.selfRef; + this->selfRef->ref = this; + } +}; + +class VKRDestructibleObject : public VKRMoveableRefCounter +{ +public: + void flagForCurrentCommandBuffer(); + bool canDestroy(); + + virtual ~VKRDestructibleObject() {}; +private: + uint64 m_lastCmdBufferId{}; +}; + +/* Vulkan objects */ + +class VKRObjectTexture : public VKRDestructibleObject +{ +public: + VKRObjectTexture(); + ~VKRObjectTexture() override; + + VkImage m_image{ VK_NULL_HANDLE }; + VkFormat m_format = VK_FORMAT_UNDEFINED; + VkImageCreateFlags m_flags; + VkImageAspectFlags m_imageAspect; + struct VkImageMemAllocation* m_allocation{}; + +}; + +class VKRObjectTextureView : public VKRDestructibleObject +{ +public: + VKRObjectTextureView(VKRObjectTexture* tex, VkImageView view); + ~VKRObjectTextureView() override; + + VkImageView m_textureImageView{ VK_NULL_HANDLE }; + VkSampler m_textureDefaultSampler[2] = { VK_NULL_HANDLE, VK_NULL_HANDLE }; // relict from LatteTextureViewVk, get rid of it eventually +}; + +class VKRObjectRenderPass : public VKRDestructibleObject +{ +public: + struct AttachmentEntryColor_t + { + VKRObjectTextureView* viewObj{}; + bool isPresent{}; + VkFormat format; // todo - we dont need to track isPresent or viewObj, we can just compare this with VK_FORMAT_UNDEFINED + }; + + struct AttachmentEntryDepth_t + { + VKRObjectTextureView* viewObj{}; + bool isPresent{}; + VkFormat format; // todo - we dont need to track isPresent or viewObj, we can just compare this with VK_FORMAT_UNDEFINED + bool hasStencil; + }; + + struct AttachmentInfo_t + { + AttachmentEntryColor_t colorAttachment[8]; + AttachmentEntryDepth_t depthAttachment; + }; + +public: + VKRObjectRenderPass(AttachmentInfo_t& attachmentInfo, sint32 colorAttachmentCount = 8); + ~VKRObjectRenderPass() override; + VkRenderPass m_renderPass{ VK_NULL_HANDLE }; + uint64 m_hashForPipeline; // helper var. Holds hash of all the renderpass creation parameters (mainly the formats) that affect the pipeline state +}; + +class VKRObjectFramebuffer : public VKRDestructibleObject +{ +public: + VKRObjectFramebuffer(VKRObjectRenderPass* renderPass, std::span<VKRObjectTextureView*> attachments, Vector2i size); + ~VKRObjectFramebuffer() override; + + VkFramebuffer m_frameBuffer{ VK_NULL_HANDLE }; +}; + +class VKRObjectPipeline : public VKRDestructibleObject +{ +public: + VKRObjectPipeline(); + ~VKRObjectPipeline() override; + + void setPipeline(VkPipeline newPipeline); + + VkPipeline pipeline = VK_NULL_HANDLE; + VkDescriptorSetLayout vertexDSL = VK_NULL_HANDLE, pixelDSL = VK_NULL_HANDLE, geometryDSL = VK_NULL_HANDLE; + VkPipelineLayout pipeline_layout = VK_NULL_HANDLE; +}; + +class VKRObjectDescriptorSet : public VKRDestructibleObject +{ +public: + VKRObjectDescriptorSet(); + ~VKRObjectDescriptorSet() override; + + VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; +}; \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VKRMemoryManager.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VKRMemoryManager.cpp new file mode 100644 index 00000000..93021fd8 --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VKRMemoryManager.cpp @@ -0,0 +1,536 @@ +#include "Cafe/HW/Latte/Renderer/Vulkan/VKRMemoryManager.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" +#include <imgui.h> + +/* VKRSynchronizedMemoryBuffer */ + +void VKRSynchronizedRingAllocator::addUploadBufferSyncPoint(AllocatorBuffer_t& buffer, uint32 offset) +{ + auto cmdBufferId = m_vkr->GetCurrentCommandBufferId(); + if (cmdBufferId == buffer.lastSyncpointCmdBufferId) + return; + buffer.lastSyncpointCmdBufferId = cmdBufferId; + buffer.queue_syncPoints.emplace(cmdBufferId, offset); +} + +void VKRSynchronizedRingAllocator::allocateAdditionalUploadBuffer(uint32 sizeRequiredForAlloc) +{ + // calculate buffer size, should be a multiple of bufferAllocSize that is at least as large as sizeRequiredForAlloc + uint32 bufferAllocSize = m_minimumBufferAllocSize; + while (bufferAllocSize < sizeRequiredForAlloc) + bufferAllocSize += m_minimumBufferAllocSize; + + AllocatorBuffer_t newBuffer{}; + newBuffer.writeIndex = 0; + newBuffer.basePtr = nullptr; + if (m_bufferType == BUFFER_TYPE::STAGING) + m_vkrMemMgr->CreateBuffer(bufferAllocSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, newBuffer.vk_buffer, newBuffer.vk_mem); + else if (m_bufferType == BUFFER_TYPE::INDEX) + m_vkrMemMgr->CreateBuffer(bufferAllocSize, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, newBuffer.vk_buffer, newBuffer.vk_mem); + else + cemu_assert_debug(false); + + void* bufferPtr = nullptr; + vkMapMemory(m_vkr->GetLogicalDevice(), newBuffer.vk_mem, 0, VK_WHOLE_SIZE, 0, &bufferPtr); + newBuffer.basePtr = (uint8*)bufferPtr; + newBuffer.size = bufferAllocSize; + newBuffer.index = (uint32)m_buffers.size(); + m_buffers.push_back(newBuffer); +} + +VKRSynchronizedRingAllocator::AllocatorReservation_t VKRSynchronizedRingAllocator::AllocateBufferMemory(uint32 size, uint32 alignment) +{ + if (alignment < 128) + alignment = 128; + size = (size + 127) & ~127; + + for (auto& itr : m_buffers) + { + // align pointer + uint32 alignmentPadding = (alignment - (itr.writeIndex % alignment)) % alignment; + uint32 distanceToSyncPoint; + if (!itr.queue_syncPoints.empty()) + { + if(itr.queue_syncPoints.front().offset < itr.writeIndex) + distanceToSyncPoint = 0xFFFFFFFF; + else + distanceToSyncPoint = itr.queue_syncPoints.front().offset - itr.writeIndex; + } + else + distanceToSyncPoint = 0xFFFFFFFF; + uint32 spaceNeeded = alignmentPadding + size; + if (spaceNeeded > distanceToSyncPoint) + continue; // not enough space in current buffer + if ((itr.writeIndex + spaceNeeded) > itr.size) + { + // wrap-around + spaceNeeded = size; + alignmentPadding = 0; + // check if there is enough space in current buffer after wrap-around + if (!itr.queue_syncPoints.empty()) + { + distanceToSyncPoint = itr.queue_syncPoints.front().offset - 0; + if (spaceNeeded > distanceToSyncPoint) + continue; + } + else if (spaceNeeded > itr.size) + continue; + itr.writeIndex = 0; + } + addUploadBufferSyncPoint(itr, itr.writeIndex); + itr.writeIndex += alignmentPadding; + uint32 offset = itr.writeIndex; + itr.writeIndex += size; + itr.cleanupCounter = 0; + VKRSynchronizedRingAllocator::AllocatorReservation_t res; + res.vkBuffer = itr.vk_buffer; + res.vkMem = itr.vk_mem; + res.memPtr = itr.basePtr + offset; + res.bufferOffset = offset; + res.size = size; + res.bufferIndex = itr.index; + return res; + } + // allocate new buffer + allocateAdditionalUploadBuffer(size); + return AllocateBufferMemory(size, alignment); +} + +void VKRSynchronizedRingAllocator::FlushReservation(AllocatorReservation_t& uploadReservation) +{ + cemu_assert_debug(m_bufferType == BUFFER_TYPE::STAGING); // only the staging buffer isn't coherent + // todo - use nonCoherentAtomSize for flush size (instead of hardcoded constant) + VkMappedMemoryRange flushedRange{}; + flushedRange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; + flushedRange.memory = uploadReservation.vkMem; + flushedRange.offset = uploadReservation.bufferOffset; + flushedRange.size = uploadReservation.size; + vkFlushMappedMemoryRanges(m_vkr->GetLogicalDevice(), 1, &flushedRange); +} + +void VKRSynchronizedRingAllocator::CleanupBuffer(uint64 latestFinishedCommandBufferId) +{ + if (latestFinishedCommandBufferId > 1) + latestFinishedCommandBufferId -= 1; + + for (auto& itr : m_buffers) + { + while (!itr.queue_syncPoints.empty() && latestFinishedCommandBufferId > itr.queue_syncPoints.front().commandBufferId) + { + itr.queue_syncPoints.pop(); + } + if (itr.queue_syncPoints.empty()) + itr.cleanupCounter++; + } + + // check if last buffer is available for deletion + if (m_buffers.size() >= 2) + { + auto& lastBuffer = m_buffers.back(); + if (lastBuffer.cleanupCounter >= 1000) + { + // release buffer + vkUnmapMemory(m_vkr->GetLogicalDevice(), lastBuffer.vk_mem); + m_vkrMemMgr->DeleteBuffer(lastBuffer.vk_buffer, lastBuffer.vk_mem); + m_buffers.pop_back(); + } + } +} + +VkBuffer VKRSynchronizedRingAllocator::GetBufferByIndex(uint32 index) const +{ + return m_buffers[index].vk_buffer; +} + +void VKRSynchronizedRingAllocator::GetStats(uint32& numBuffers, size_t& totalBufferSize, size_t& freeBufferSize) const +{ + numBuffers = (uint32)m_buffers.size(); + totalBufferSize = 0; + freeBufferSize = 0; + for (auto& itr : m_buffers) + { + totalBufferSize += itr.size; + // calculate free space in buffer + uint32 distanceToSyncPoint; + if (!itr.queue_syncPoints.empty()) + { + if (itr.queue_syncPoints.front().offset < itr.writeIndex) + distanceToSyncPoint = (itr.size - itr.writeIndex) + itr.queue_syncPoints.front().offset; // size with wrap-around + else + distanceToSyncPoint = itr.queue_syncPoints.front().offset - itr.writeIndex; + } + else + distanceToSyncPoint = itr.size; + freeBufferSize += distanceToSyncPoint; + } +} + +/* VkTextureChunkedHeap */ + +uint32 VkTextureChunkedHeap::allocateNewChunk(uint32 chunkIndex, uint32 minimumAllocationSize) +{ + cemu_assert_debug(m_list_chunkInfo.size() == chunkIndex); + m_list_chunkInfo.resize(m_list_chunkInfo.size() + 1); + + // pad minimumAllocationSize to 32KB alignment + minimumAllocationSize = (minimumAllocationSize + (32*1024-1)) & ~(32 * 1024 - 1); + + uint32 allocationSize = 1024 * 1024 * 128; + if (chunkIndex == 0) + { + // make the first allocation smaller, this decreases wasted memory when there are textures that require specific flags (and thus separate heaps) + allocationSize = 1024 * 1024 * 16; + } + if (allocationSize < minimumAllocationSize) + allocationSize = minimumAllocationSize; + // get available memory types/heaps + std::vector<uint32> deviceLocalMemoryTypeIndices = m_vkrMemoryManager->FindMemoryTypes(m_typeFilter, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + std::vector<uint32> hostLocalMemoryTypeIndices = m_vkrMemoryManager->FindMemoryTypes(m_typeFilter, 0); + // remove device local memory types from host local vector + auto pred = [&deviceLocalMemoryTypeIndices](const uint32& v) ->bool + { + return std::find(deviceLocalMemoryTypeIndices.begin(), deviceLocalMemoryTypeIndices.end(), v) != deviceLocalMemoryTypeIndices.end(); + }; + hostLocalMemoryTypeIndices.erase(std::remove_if(hostLocalMemoryTypeIndices.begin(), hostLocalMemoryTypeIndices.end(), pred), hostLocalMemoryTypeIndices.end()); + // allocate chunk memory + for (sint32 t = 0; t < 3; t++) + { + // attempt to allocate from device local memory first + for (auto memType : deviceLocalMemoryTypeIndices) + { + VkMemoryAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = allocationSize; + allocInfo.memoryTypeIndex = memType; + + VkDeviceMemory imageMemory; + VkResult r = vkAllocateMemory(m_device, &allocInfo, nullptr, &imageMemory); + if (r != VK_SUCCESS) + continue; + forceLog_printf("Vulkan-Info: Allocated additional memory for textures from device-local memory"); + m_list_chunkInfo[chunkIndex].mem = imageMemory; + return allocationSize; + } + // attempt to allocate from host-local memory + for (auto memType : hostLocalMemoryTypeIndices) + { + VkMemoryAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = allocationSize; + allocInfo.memoryTypeIndex = memType; + + VkDeviceMemory imageMemory; + VkResult r = vkAllocateMemory(m_device, &allocInfo, nullptr, &imageMemory); + if (r != VK_SUCCESS) + continue; + forceLog_printf("Vulkan-Info: Allocated additional memory for textures from host-local memory"); + m_list_chunkInfo[chunkIndex].mem = imageMemory; + return allocationSize; + } + // retry with smaller size if possible + allocationSize /= 2; + if (allocationSize < minimumAllocationSize) + break; + forceLog_printf("Failed to allocate texture memory chunk with size %dMB. Trying again with smaller allocation size\n", allocationSize / 1024 / 1024); + } + forceLog_printf("Unable to allocate image memory chunk (%d heaps)", deviceLocalMemoryTypeIndices.size()); + throw std::runtime_error("failed to allocate image memory!"); + return 0; +} + +uint32_t VKRMemoryManager::FindMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) const +{ + VkPhysicalDeviceMemoryProperties memProperties; + vkGetPhysicalDeviceMemoryProperties(m_vkr->GetPhysicalDevice(), &memProperties); + + for (uint32 i = 0; i < memProperties.memoryTypeCount; i++) + { + if ((typeFilter & (1 << i)) != 0 && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + return i; + } + m_vkr->UnrecoverableError(fmt::format("failed to find suitable memory type ({0:#08x} {1:#08x})", typeFilter, properties).c_str()); + return 0; +} + +bool VKRMemoryManager::FindMemoryType2(uint32 typeFilter, VkMemoryPropertyFlags properties, uint32& memoryIndex) const +{ + VkPhysicalDeviceMemoryProperties memProperties; + vkGetPhysicalDeviceMemoryProperties(m_vkr->GetPhysicalDevice(), &memProperties); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) + { + if (typeFilter & (1 << i) && memProperties.memoryTypes[i].propertyFlags == properties) + { + memoryIndex = i; + return true; + } + } + return false; +} + +std::vector<uint32> VKRMemoryManager::FindMemoryTypes(uint32_t typeFilter, VkMemoryPropertyFlags properties) const +{ + std::vector<uint32> memoryTypes; + memoryTypes.clear(); + VkPhysicalDeviceMemoryProperties memProperties; + vkGetPhysicalDeviceMemoryProperties(m_vkr->GetPhysicalDevice(), &memProperties); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) + { + if (typeFilter & (1 << i) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + memoryTypes.emplace_back(i); + } + + if (memoryTypes.empty()) + m_vkr->UnrecoverableError(fmt::format("Failed to find suitable memory type ({0:#08x} {1:#08x})", typeFilter, properties).c_str()); + + return memoryTypes; +} + +size_t VKRMemoryManager::GetTotalMemoryForBufferType(VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, size_t minimumBufferSize) +{ + VkDevice logicalDevice = m_vkr->GetLogicalDevice(); + // create temporary buffer object to get memory type + VkBuffer temporaryBuffer; + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.usage = usage; + bufferInfo.size = minimumBufferSize; // the buffer size can theoretically influence the memory type, is there a better way to handle this? + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + if (vkCreateBuffer(logicalDevice, &bufferInfo, nullptr, &temporaryBuffer) != VK_SUCCESS) + { + forceLog_printf("Vulkan: GetTotalMemoryForBufferType() failed to create temporary buffer"); + return 0; + } + + // get memory requirements for buffer + VkMemoryRequirements memRequirements; + vkGetBufferMemoryRequirements(logicalDevice, temporaryBuffer, &memRequirements); + uint32 typeFilter = memRequirements.memoryTypeBits; + // destroy temporary buffer + vkDestroyBuffer(logicalDevice, temporaryBuffer, nullptr); + // get list of all suitable heaps + std::unordered_set<uint32> list_heapIndices; + VkPhysicalDeviceMemoryProperties memProperties{}; + vkGetPhysicalDeviceMemoryProperties(m_vkr->GetPhysicalDevice(), &memProperties); + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) + { + if (typeFilter & (1 << i) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) + list_heapIndices.emplace(memProperties.memoryTypes[i].heapIndex); + } + // sum up size of heaps + size_t total = 0; + for (auto heapIndex : list_heapIndices) + { + if (heapIndex > memProperties.memoryHeapCount) + continue; + total += memProperties.memoryHeaps[heapIndex].size; + } + + return total; +} + +void VKRMemoryManager::CreateBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) const +{ + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.usage = usage; + bufferInfo.size = size; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + if (vkCreateBuffer(m_vkr->GetLogicalDevice(), &bufferInfo, nullptr, &buffer) != VK_SUCCESS) + m_vkr->UnrecoverableError("Failed to create buffer"); + + VkMemoryRequirements memRequirements; + vkGetBufferMemoryRequirements(m_vkr->GetLogicalDevice(), buffer, &memRequirements); + + VkMemoryAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memRequirements.size; + allocInfo.memoryTypeIndex = FindMemoryType(memRequirements.memoryTypeBits, properties); + + if (vkAllocateMemory(m_vkr->GetLogicalDevice(), &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) + m_vkr->UnrecoverableError("Failed to allocate buffer memory"); + if (vkBindBufferMemory(m_vkr->GetLogicalDevice(), buffer, bufferMemory, 0) != VK_SUCCESS) + m_vkr->UnrecoverableError("Failed to bind buffer memory"); +} + +bool VKRMemoryManager::CreateBuffer2(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) const +{ + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.usage = usage; + bufferInfo.size = size; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + if (vkCreateBuffer(m_vkr->GetLogicalDevice(), &bufferInfo, nullptr, &buffer) != VK_SUCCESS) + { + cemuLog_log(LogType::Force, "Failed to create buffer (CreateBuffer2)"); + return false; + } + + VkMemoryRequirements memRequirements; + vkGetBufferMemoryRequirements(m_vkr->GetLogicalDevice(), buffer, &memRequirements); + + VkMemoryAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memRequirements.size; + if (!FindMemoryType2(memRequirements.memoryTypeBits, properties, allocInfo.memoryTypeIndex)) + { + vkDestroyBuffer(m_vkr->GetLogicalDevice(), buffer, nullptr); + return false; + } + if (vkAllocateMemory(m_vkr->GetLogicalDevice(), &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) + { + vkDestroyBuffer(m_vkr->GetLogicalDevice(), buffer, nullptr); + return false; + } + if (vkBindBufferMemory(m_vkr->GetLogicalDevice(), buffer, bufferMemory, 0) != VK_SUCCESS) + { + vkDestroyBuffer(m_vkr->GetLogicalDevice(), buffer, nullptr); + cemuLog_log(LogType::Force, "Failed to bind buffer (CreateBuffer2)"); + return false; + } + return true; +} + +bool VKRMemoryManager::CreateBufferFromHostMemory(void* hostPointer, VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) const +{ + VkBufferCreateInfo bufferInfo{}; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferInfo.usage = usage; + bufferInfo.size = size; + bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + VkExternalMemoryBufferCreateInfo emb{}; + emb.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO; + emb.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT; + + bufferInfo.pNext = &emb; + + if (vkCreateBuffer(m_vkr->GetLogicalDevice(), &bufferInfo, nullptr, &buffer) != VK_SUCCESS) + { + cemuLog_log(LogType::Force, "Failed to create buffer (CreateBuffer2)"); + return false; + } + + VkMemoryRequirements memRequirements; + vkGetBufferMemoryRequirements(m_vkr->GetLogicalDevice(), buffer, &memRequirements); + + VkMemoryAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memRequirements.size; + + VkImportMemoryHostPointerInfoEXT importHostMem{}; + importHostMem.sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_HOST_POINTER_INFO_EXT; + importHostMem.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT; + importHostMem.pHostPointer = hostPointer; + // VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT or + // VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_MAPPED_FOREIGN_MEMORY_BIT_EXT + // whats the difference ? + + allocInfo.pNext = &importHostMem; + + if (!FindMemoryType2(memRequirements.memoryTypeBits, properties, allocInfo.memoryTypeIndex)) + { + vkDestroyBuffer(m_vkr->GetLogicalDevice(), buffer, nullptr); + return false; + } + if (vkAllocateMemory(m_vkr->GetLogicalDevice(), &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) + { + vkDestroyBuffer(m_vkr->GetLogicalDevice(), buffer, nullptr); + return false; + } + if (vkBindBufferMemory(m_vkr->GetLogicalDevice(), buffer, bufferMemory, 0) != VK_SUCCESS) + { + vkDestroyBuffer(m_vkr->GetLogicalDevice(), buffer, nullptr); + cemuLog_log(LogType::Force, "Failed to bind buffer (CreateBufferFromHostMemory)"); + return false; + } + return true; +} + +void VKRMemoryManager::DeleteBuffer(VkBuffer& buffer, VkDeviceMemory& deviceMem) const +{ + if (buffer != VK_NULL_HANDLE) + vkDestroyBuffer(m_vkr->GetLogicalDevice(), buffer, nullptr); + if (deviceMem != VK_NULL_HANDLE) + vkFreeMemory(m_vkr->GetLogicalDevice(), deviceMem, nullptr); + buffer = VK_NULL_HANDLE; + deviceMem = VK_NULL_HANDLE; +} + +VkImageMemAllocation* VKRMemoryManager::imageMemoryAllocate(VkImage image) +{ + VkMemoryRequirements memRequirements; + vkGetImageMemoryRequirements(m_vkr->GetLogicalDevice(), image, &memRequirements); + uint32 typeFilter = memRequirements.memoryTypeBits; + + // get or create heap for this type filter + VkTextureChunkedHeap* texHeap; + auto it = map_textureHeap.find(typeFilter); + if (it == map_textureHeap.end()) + { + texHeap = new VkTextureChunkedHeap(this, typeFilter, m_vkr->GetLogicalDevice()); + map_textureHeap.emplace(typeFilter, texHeap); + } + else + texHeap = it->second; + + // alloc mem from heap + uint32 allocationSize = (uint32)memRequirements.size; + + CHAddr mem = texHeap->allocMem(allocationSize, (uint32)memRequirements.alignment); + if (!mem.isValid()) + { + // allocation failed, try to make space by deleting textures + // todo - improve this algorithm + std::vector<LatteTexture*> deleteableTextures = LatteTC_GetDeleteableTextures(); + // delete up to 20 textures from the deletable textures list, then retry allocation + while (!deleteableTextures.empty()) + { + size_t numDelete = deleteableTextures.size(); + if (numDelete > 20) + numDelete = 20; + for (size_t i = 0; i < numDelete; i++) + LatteTexture_Delete(deleteableTextures[i]); + deleteableTextures.erase(deleteableTextures.begin(), deleteableTextures.begin() + numDelete); + mem = texHeap->allocMem(allocationSize, (uint32)memRequirements.alignment); + if (mem.isValid()) + break; + } + if (!mem.isValid()) + { + m_vkr->UnrecoverableError("Ran out of VRAM for textures"); + } + } + + vkBindImageMemory(m_vkr->GetLogicalDevice(), image, texHeap->getChunkMem(mem.chunkIndex), mem.offset); + + return new VkImageMemAllocation(typeFilter, mem, allocationSize); +} + +void VKRMemoryManager::imageMemoryFree(VkImageMemAllocation* imageMemAllocation) +{ + auto heapItr = map_textureHeap.find(imageMemAllocation->typeFilter); + if (heapItr == map_textureHeap.end()) + { + forceLog_printf("Internal texture heap error"); + return; + } + heapItr->second->freeMem(imageMemAllocation->mem); + delete imageMemAllocation; +} + +void VKRMemoryManager::appendOverlayHeapDebugInfo() +{ + for (auto& itr : map_textureHeap) + { + uint32 heapSize; + uint32 allocatedBytes; + itr.second->getStatistics(heapSize, allocatedBytes); + + uint32 heapSizeMB = (heapSize / 1024 / 1024); + uint32 allocatedBytesMB = (allocatedBytes / 1024 / 1024); + + ImGui::Text(fmt::format("{0:#08x} Size: {1}MB/{2}MB", itr.first, allocatedBytesMB, heapSizeMB).c_str()); + } +} \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VKRMemoryManager.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VKRMemoryManager.h new file mode 100644 index 00000000..47561159 --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VKRMemoryManager.h @@ -0,0 +1,200 @@ +#pragma once +#include "Cafe/HW/Latte/Renderer/Renderer.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" +#include "util/ChunkedHeap/ChunkedHeap.h" + +struct VkImageMemAllocation +{ + VkImageMemAllocation(uint32 _typeFilter, CHAddr _mem, uint32 _allocationSize) : typeFilter(_typeFilter), mem(_mem), allocationSize(_allocationSize) { }; + + uint32 typeFilter; + CHAddr mem; + uint32 allocationSize; + + uint32 getAllocationSize() { return allocationSize; } +}; + +class VkTextureChunkedHeap : private ChunkedHeap +{ +public: + VkTextureChunkedHeap(class VKRMemoryManager* memoryManager, uint32 typeFilter, VkDevice device) : m_vkrMemoryManager(memoryManager), m_typeFilter(typeFilter), m_device(device) { }; + + struct ChunkInfo + { + VkDeviceMemory mem; + }; + + uint32 allocateNewChunk(uint32 chunkIndex, uint32 minimumAllocationSize) override; + + CHAddr allocMem(uint32 size, uint32 alignment) + { + if (alignment < 4) + alignment = 4; + if ((alignment & (alignment - 1)) != 0) + { + // not a power of two + forceLog_printf("VkTextureChunkedHeap: Invalid alignment %d", alignment); + } + return this->alloc(size, alignment); + } + + void freeMem(CHAddr addr) + { + this->free(addr); + } + + void setDevice(VkDevice dev) + { + m_device = dev; + } + + VkDeviceMemory getChunkMem(uint32 index) + { + if (index >= m_list_chunkInfo.size()) + return VK_NULL_HANDLE; + return m_list_chunkInfo[index].mem; + } + + void getStatistics(uint32& totalHeapSize, uint32& allocatedBytes) const + { + totalHeapSize = numHeapBytes; + allocatedBytes = numAllocatedBytes; + } + + VkDevice m_device; + uint32 m_typeFilter{ 0xFFFFFFFF }; + class VKRMemoryManager* m_vkrMemoryManager; + std::vector<ChunkInfo> m_list_chunkInfo; +}; + +// a circular ring-buffer which tracks and releases memory per command-buffer +class VKRSynchronizedRingAllocator +{ +public: + enum class BUFFER_TYPE + { + STAGING, // staging upload buffer + INDEX, // buffer for index data + }; + + VKRSynchronizedRingAllocator(class VulkanRenderer* vkRenderer, class VKRMemoryManager* vkMemoryManager, BUFFER_TYPE bufferType, uint32 minimumBufferAllocSize) : m_vkr(vkRenderer), m_vkrMemMgr(vkMemoryManager), m_bufferType(bufferType), m_minimumBufferAllocSize(minimumBufferAllocSize) {}; + VKRSynchronizedRingAllocator(const VKRSynchronizedRingAllocator&) = delete; // disallow copy + + struct BufferSyncPoint_t + { + // todo - modularize sync point + uint64 commandBufferId; + uint32 offset; + + BufferSyncPoint_t(uint64 _commandBufferId, uint32 _offset) : commandBufferId(_commandBufferId), offset(_offset) {}; + }; + + struct AllocatorBuffer_t + { + VkBuffer vk_buffer; + VkDeviceMemory vk_mem; + uint8* basePtr; + uint32 size; + uint32 writeIndex; + std::queue<BufferSyncPoint_t> queue_syncPoints; + uint64 lastSyncpointCmdBufferId{ 0xFFFFFFFFFFFFFFFFull }; + uint32 index; + uint32 cleanupCounter{ 0 }; // increased by one every time CleanupBuffer() is called if there is no sync point. If it reaches 300 then the buffer is released + }; + + struct AllocatorReservation_t + { + VkBuffer vkBuffer; + VkDeviceMemory vkMem; + uint8* memPtr; + uint32 bufferOffset; + uint32 size; + uint32 bufferIndex; + }; + + AllocatorReservation_t AllocateBufferMemory(uint32 size, uint32 alignment); + void FlushReservation(AllocatorReservation_t& uploadReservation); + void CleanupBuffer(uint64 latestFinishedCommandBufferId); + VkBuffer GetBufferByIndex(uint32 index) const; + + void GetStats(uint32& numBuffers, size_t& totalBufferSize, size_t& freeBufferSize) const; + +private: + void allocateAdditionalUploadBuffer(uint32 sizeRequiredForAlloc); + void addUploadBufferSyncPoint(AllocatorBuffer_t& buffer, uint32 offset); + + const class VulkanRenderer* m_vkr; + const class VKRMemoryManager* m_vkrMemMgr; + const BUFFER_TYPE m_bufferType; + const uint32 m_minimumBufferAllocSize; + + std::vector<AllocatorBuffer_t> m_buffers; + +}; + +void LatteIndices_invalidateAll(); + +class VKRMemoryManager +{ + friend class VKRSynchronizedRingAllocator; +public: + VKRMemoryManager(class VulkanRenderer* renderer) : m_stagingBuffer(renderer, this, VKRSynchronizedRingAllocator::BUFFER_TYPE::STAGING, 32u * 1024 * 1024), m_indexBuffer(renderer, this, VKRSynchronizedRingAllocator::BUFFER_TYPE::INDEX, 4u * 1024 * 1024) + { + m_vkr = renderer; + } + + // texture memory management + std::unordered_map<uint32, VkTextureChunkedHeap*> map_textureHeap; // one heap per memory type + std::vector<uint8> m_textureUploadBuffer; + + // texture upload buffer + void* TextureUploadBufferAcquire(uint32 size) + { + if (m_textureUploadBuffer.size() < size) + m_textureUploadBuffer.resize(size); + + return m_textureUploadBuffer.data(); + } + + void TextureUploadBufferRelease(uint8* mem) + { + cemu_assert_debug(m_textureUploadBuffer.data() == mem); + m_textureUploadBuffer.clear(); + } + + VKRSynchronizedRingAllocator& getStagingAllocator() { return m_stagingBuffer; }; // allocator for texture/attribute/uniform uploads + VKRSynchronizedRingAllocator& getIndexAllocator() { return m_indexBuffer; }; // allocator for index data + + void cleanupBuffers(uint64 latestFinishedCommandBufferId) + { + LatteIndices_invalidateAll(); + m_stagingBuffer.CleanupBuffer(latestFinishedCommandBufferId); + m_indexBuffer.CleanupBuffer(latestFinishedCommandBufferId); + } + + // memory helpers + uint32_t FindMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) const; + bool FindMemoryType2(uint32 typeFilter, VkMemoryPropertyFlags properties, uint32& memoryIndex) const; // searches for exact properties. Can gracefully fail without throwing exception (returns false) + std::vector<uint32> FindMemoryTypes(uint32_t typeFilter, VkMemoryPropertyFlags properties) const; + + // image memory allocation + void imageMemoryFree(VkImageMemAllocation* imageMemAllocation); + VkImageMemAllocation* imageMemoryAllocate(VkImage image); + + // buffer management + size_t GetTotalMemoryForBufferType(VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, size_t minimumBufferSize = 16 * 1024 * 1024); + + void CreateBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) const; + bool CreateBuffer2(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) const; // same as CreateBuffer but doesn't throw exception on failure + bool CreateBufferFromHostMemory(void* hostPointer, VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) const; + + void DeleteBuffer(VkBuffer& buffer, VkDeviceMemory& deviceMem) const; + + // overlay info + void appendOverlayHeapDebugInfo(); + + private: + class VulkanRenderer* m_vkr; + VKRSynchronizedRingAllocator m_stagingBuffer; + VKRSynchronizedRingAllocator m_indexBuffer; +}; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VKRPipelineInfo.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VKRPipelineInfo.cpp new file mode 100644 index 00000000..e9936c43 --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VKRPipelineInfo.cpp @@ -0,0 +1,94 @@ +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.h" + +#include <glslang/Include/Types.h> +#include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h" +#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" + +#include "imgui/imgui_impl_vulkan.h" +#include "imgui/imgui_extension.h" +#include "config/CemuConfig.h" + +PipelineInfo::PipelineInfo(uint64 minimalStateHash, uint64 pipelineHash, LatteFetchShader* fetchShader, LatteDecompilerShader* vertexShader, LatteDecompilerShader* pixelShader, LatteDecompilerShader* geometryShader) +{ + this->minimalStateHash = minimalStateHash; + this->stateHash = pipelineHash; + + this->fetchShader = fetchShader; + this->vertexShader = vertexShader; + this->geometryShader = geometryShader; + this->pixelShader = pixelShader; + + this->vertexShaderVk = vertexShader ? (RendererShaderVk*)vertexShader->shader : nullptr; + this->geometryShaderVk = geometryShader ? (RendererShaderVk*)geometryShader->shader : nullptr; + this->pixelShaderVk = pixelShader ? (RendererShaderVk*)pixelShader->shader : nullptr; + + // init VKRObjPipeline + m_vkrObjPipeline = new VKRObjectPipeline(); + m_vkrObjPipeline->pipeline = VK_NULL_HANDLE; + + // track dependency with shaders + if (vertexShaderVk) + vertexShaderVk->TrackDependency(this); + if (geometryShaderVk) + geometryShaderVk->TrackDependency(this); + if (pixelShaderVk) + pixelShaderVk->TrackDependency(this); + + // "Accurate barriers" is usually enabled globally but since the CPU cost is substantial we allow users to disable it (debug -> 'Accurate barriers' option) + // We always force accurate barriers for known problematic shaders + if (pixelShader) + { + if (pixelShader->baseHash == 0x6f6f6e7b9aae57af && pixelShader->auxHash == 0x00078787f9249249) // BotW lava + neverSkipAccurateBarrier = true; + if (pixelShader->baseHash == 0x4c0bd596e3aef4a6 && pixelShader->auxHash == 0x003c3c3fc9269249) // BotW foam layer for water on the bottom of waterfalls + neverSkipAccurateBarrier = true; + } +} + + +PipelineInfo::~PipelineInfo() +{ + if (rectEmulationGS) + { + delete rectEmulationGS; + rectEmulationGS = nullptr; + } + + // delete descriptor sets + while (!pixel_ds_cache.empty()) + { + VkDescriptorSetInfo* dsInfo = pixel_ds_cache.begin()->second; + delete dsInfo; + } + while (!geometry_ds_cache.empty()) + { + VkDescriptorSetInfo* dsInfo = geometry_ds_cache.begin()->second; + delete dsInfo; + } + while (!vertex_ds_cache.empty()) + { + VkDescriptorSetInfo* dsInfo = vertex_ds_cache.begin()->second; + delete dsInfo; + } + + // disassociate from shaders + if (vertexShaderVk) + vertexShaderVk->RemoveDependency(this); + if (geometryShaderVk) + geometryShaderVk->RemoveDependency(this); + if (pixelShaderVk) + pixelShaderVk->RemoveDependency(this); + + // queue pipeline for destruction + if (m_vkrObjPipeline) + { + VulkanRenderer::GetInstance()->releaseDestructibleObject(m_vkrObjPipeline); + m_vkrObjPipeline = nullptr; + } + + // remove from cache + VulkanRenderer::GetInstance()->unregisterGraphicsPipeline(this); +} \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VsyncDriver.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VsyncDriver.cpp new file mode 100644 index 00000000..910ceff2 --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VsyncDriver.cpp @@ -0,0 +1,217 @@ +#include "gui/MainWindow.h" + +#if BOOST_OS_WINDOWS + +#include <Windows.h> + +typedef LONG NTSTATUS; + +typedef UINT32 D3DKMT_HANDLE; +typedef UINT D3DDDI_VIDEO_PRESENT_SOURCE_ID; + +typedef struct _D3DKMT_OPENADAPTERFROMHDC +{ + HDC hDc; + D3DKMT_HANDLE hAdapter; + LUID AdapterLuid; + D3DDDI_VIDEO_PRESENT_SOURCE_ID VidPnSourceId; +}D3DKMT_OPENADAPTERFROMHDC; + +typedef struct _D3DKMT_WAITFORVERTICALBLANKEVENT { + D3DKMT_HANDLE hAdapter; + D3DKMT_HANDLE hDevice; + D3DDDI_VIDEO_PRESENT_SOURCE_ID VidPnSourceId; +} D3DKMT_WAITFORVERTICALBLANKEVENT; + +class DeviceVsyncHandler +{ +public: + DeviceVsyncHandler(void(*cbVSync)()) : m_vsyncDriverVSyncCb(cbVSync) + { + m_shutdownThread = false; + if (!pfnD3DKMTOpenAdapterFromHdc) + { + HMODULE hModuleGDI = LoadLibraryA("gdi32.dll"); + *(void**)&pfnD3DKMTOpenAdapterFromHdc = GetProcAddress(hModuleGDI, "D3DKMTOpenAdapterFromHdc"); + *(void**)&pfnD3DKMTWaitForVerticalBlankEvent = GetProcAddress(hModuleGDI, "D3DKMTWaitForVerticalBlankEvent"); + } + m_thd = std::thread(&DeviceVsyncHandler::vsyncThread, this); + } + + ~DeviceVsyncHandler() + { + m_shutdownThread = true; + cemu_assert_debug(m_thd.joinable()); + m_thd.join(); + } + + void notifyWindowPosChanged() + { + m_checkMonitorChange = true; + } + +private: + bool HasMonitorChanged() + { + HWND hWnd = (HWND)g_mainFrame->GetRenderCanvasHWND(); + if (hWnd == 0) + return true; + HMONITOR hMonitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST); + + MONITORINFOEXW monitorInfo{}; + monitorInfo.cbSize = sizeof(monitorInfo); + if (GetMonitorInfoW(hMonitor, &monitorInfo) == 0) + return true; + + if (wcscmp(monitorInfo.szDevice, m_activeMonitorDevice) == 0) + return false; + + return true; + } + + HRESULT GetAdapterHandleFromHwnd(D3DKMT_HANDLE* phAdapter, UINT* pOutput) + { + if (!g_mainFrame) + return E_FAIL; + HWND hWnd = (HWND)g_mainFrame->GetRenderCanvasHWND(); + if (hWnd == 0) + return E_FAIL; + + wcsncpy(m_activeMonitorDevice, L"", 32); // reset remembered monitor device + m_checkMonitorChange = false; + + D3DKMT_OPENADAPTERFROMHDC OpenAdapterData; + + *phAdapter = NULL; + *pOutput = 0; + + HMONITOR hMonitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST); + + MONITORINFOEXW monitorInfo{}; + monitorInfo.cbSize = sizeof(monitorInfo); + if (GetMonitorInfoW(hMonitor, &monitorInfo) == 0) + return E_FAIL; + + + HDC hdc = CreateDCW(NULL, monitorInfo.szDevice, NULL, NULL); + if (hdc == NULL) { + return E_FAIL; + } + + OpenAdapterData.hDc = hdc; + if (pfnD3DKMTOpenAdapterFromHdc(&OpenAdapterData) == 0) + { + DeleteDC(hdc); + *phAdapter = OpenAdapterData.hAdapter; + *pOutput = OpenAdapterData.VidPnSourceId; + // remember monitor device + wcsncpy(m_activeMonitorDevice, monitorInfo.szDevice, 32); + return S_OK; + } + DeleteDC(hdc); + + return E_FAIL; + } + + void vsyncThread() + { + D3DKMT_HANDLE hAdapter; + UINT hOutput; + GetAdapterHandleFromHwnd(&hAdapter, &hOutput); + + int failCount = 0; + while (!m_shutdownThread) + { + D3DKMT_WAITFORVERTICALBLANKEVENT arg; + arg.hDevice = 0; + arg.hAdapter = hAdapter; + arg.VidPnSourceId = hOutput; + NTSTATUS r = pfnD3DKMTWaitForVerticalBlankEvent(&arg); + if (r != 0) + { + //forceLog_printf("Wait for VerticalBlank failed\n"); + Sleep(1000 / 60); + failCount++; + if (failCount >= 10) + { + while (GetAdapterHandleFromHwnd(&hAdapter, &hOutput) != S_OK) + { + Sleep(1000 / 60); + if (m_shutdownThread) + return; + } + failCount = 0; + } + } + else + signalVsync(); + if (m_checkMonitorChange) + { + m_checkMonitorChange = false; + if (HasMonitorChanged()) + { + while (GetAdapterHandleFromHwnd(&hAdapter, &hOutput) != S_OK) + { + Sleep(1000 / 60); + if (m_shutdownThread) + return; + } + } + } + } + } + + void signalVsync() + { + if(m_vsyncDriverVSyncCb) + m_vsyncDriverVSyncCb(); + } + + void setCallback(void(*cbVSync)()) + { + m_vsyncDriverVSyncCb = cbVSync; + } + +private: + NTSTATUS(__stdcall* pfnD3DKMTOpenAdapterFromHdc)(D3DKMT_OPENADAPTERFROMHDC* Arg1) = nullptr; + NTSTATUS(__stdcall* pfnD3DKMTWaitForVerticalBlankEvent)(const D3DKMT_WAITFORVERTICALBLANKEVENT* Arg1) = nullptr; + + std::thread m_thd; + bool m_shutdownThread; + bool m_checkMonitorChange{}; + WCHAR m_activeMonitorDevice[32]; + + void (*m_vsyncDriverVSyncCb)() = nullptr; +}; + +DeviceVsyncHandler* s_vsyncDriver = nullptr; + +std::mutex s_driverAccess; + +void VsyncDriver_startThread(void(*cbVSync)()) +{ + std::unique_lock<std::mutex> ul(s_driverAccess); + if (!s_vsyncDriver) + s_vsyncDriver = new DeviceVsyncHandler(cbVSync); +} + +void VsyncDriver_notifyWindowPosChanged() +{ + std::unique_lock<std::mutex> ul(s_driverAccess); + if (s_vsyncDriver) + s_vsyncDriver->notifyWindowPosChanged(); +} + +#else + +void VsyncDriver_startThread(void(*cbVSync)()) +{ + cemu_assert_unimplemented(); +} + +void VsyncDriver_notifyWindowPosChanged() +{ + +} + +#endif \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VsyncDriver.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VsyncDriver.h new file mode 100644 index 00000000..16add61d --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VsyncDriver.h @@ -0,0 +1,4 @@ +#pragma once + +void VsyncDriver_startThread(void(*cbVSync)()); +void VsyncDriver_notifyWindowPosChanged(); \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.cpp new file mode 100644 index 00000000..c657cba7 --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.cpp @@ -0,0 +1,124 @@ +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" +#define VKFUNC_DEFINE +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" + +#if BOOST_OS_LINUX +#include <dlfcn.h> +#endif + +bool g_vulkan_available = false; + +#if BOOST_OS_WINDOWS + +bool InitializeGlobalVulkan() +{ + const auto hmodule = LoadLibraryA("vulkan-1.dll"); + + if(g_vulkan_available) + return true; + + if (hmodule == nullptr) + { + forceLog_printf("Vulkan loader not available. Outdated graphics driver or Vulkan runtime not installed?"); + return false; + } + + #define VKFUNC_INIT + #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" + + if(!vkEnumerateInstanceVersion) + { + forceLog_printf("vkEnumerateInstanceVersion not available. Outdated graphics driver or Vulkan runtime?"); + FreeLibrary(hmodule); + return false; + } + + g_vulkan_available = true; + return true; +} + +bool InitializeInstanceVulkan(VkInstance instance) +{ + const auto hmodule = GetModuleHandleA("vulkan-1.dll"); + if (hmodule == nullptr) + return false; + + #define VKFUNC_INSTANCE_INIT + #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" + + return true; +} + +bool InitializeDeviceVulkan(VkDevice device) +{ + const auto hmodule = GetModuleHandleA("vulkan-1.dll"); + if (hmodule == nullptr) + return false; + + #define VKFUNC_DEVICE_INIT + #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" + + return true; +} + +#else + +void* dlopen_vulkan_loader() +{ + void* vulkan_so = dlopen("libvulkan.so", RTLD_NOW); + if(!vulkan_so) + vulkan_so = dlopen("libvulkan.so.1", RTLD_NOW); + return vulkan_so; +} + +bool InitializeGlobalVulkan() +{ + void* vulkan_so = dlopen_vulkan_loader(); + + if(g_vulkan_available) + return true; + + if (!vulkan_so) + { + forceLog_printf("Vulkan loader not available."); + return false; + } + + #define VKFUNC_INIT + #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" + + if(!vkEnumerateInstanceVersion) + { + forceLog_printf("vkEnumerateInstanceVersion not available. Outdated graphics driver or Vulkan runtime?"); + return false; + } + + g_vulkan_available = true; + return true; +} + +bool InitializeInstanceVulkan(VkInstance instance) +{ + void* vulkan_so = dlopen_vulkan_loader(); + if (!vulkan_so) + return false; + + #define VKFUNC_INSTANCE_INIT + #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" + + return true; +} + +bool InitializeDeviceVulkan(VkDevice device) +{ + void* vulkan_so = dlopen_vulkan_loader(); + if (!vulkan_so) + return false; + + #define VKFUNC_DEVICE_INIT + #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" + + return true; +} + +#endif \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h new file mode 100644 index 00000000..bf4e58d0 --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h @@ -0,0 +1,234 @@ +#ifndef _VULKAN_API_ +#define _VULKAN_API_ + +#if BOOST_OS_WINDOWS +#define VK_USE_PLATFORM_WIN32_KHR // todo - define in CMakeLists.txt +#endif + +#include <vulkan/vulkan.h> + +bool InitializeGlobalVulkan(); +bool InitializeInstanceVulkan(VkInstance instance); +bool InitializeDeviceVulkan(VkDevice device); +extern bool g_vulkan_available; + +#endif + +#ifdef VKFUNC_DEFINE + #define VKFUNC(__FUNC__) PFN_##__FUNC__ __FUNC__ = nullptr + #define VKFUNC_INSTANCE(__FUNC__) PFN_##__FUNC__ __FUNC__ = nullptr + #define VKFUNC_DEVICE(__FUNC__) PFN_##__FUNC__ __FUNC__ = nullptr +#else + #if defined(VKFUNC_INIT) + #if BOOST_OS_WINDOWS + #define VKFUNC(__FUNC__) __FUNC__ = (PFN_##__FUNC__)GetProcAddress(hmodule, #__FUNC__) + #else + #define VKFUNC(__FUNC__) __FUNC__ = (PFN_##__FUNC__)dlsym(vulkan_so, #__FUNC__) + #endif + #define VKFUNC_INSTANCE(__FUNC__) + #define VKFUNC_DEVICE(__FUNC__) + #elif defined(VKFUNC_INSTANCE_INIT) + #define VKFUNC(__FUNC__) + #define VKFUNC_INSTANCE(__FUNC__) __FUNC__ = (PFN_##__FUNC__)vkGetInstanceProcAddr(instance, #__FUNC__) + #define VKFUNC_DEVICE(__FUNC__) + #elif defined(VKFUNC_DEVICE_INIT) + #define VKFUNC(__FUNC__) + #define VKFUNC_INSTANCE(__FUNC__) + #define VKFUNC_DEVICE(__FUNC__) __FUNC__ = (PFN_##__FUNC__)vkGetDeviceProcAddr(device, #__FUNC__) + #else + #define VKFUNC(__FUNC__) extern PFN_##__FUNC__ __FUNC__ + #define VKFUNC_INSTANCE(__FUNC__) extern PFN_##__FUNC__ __FUNC__ + #define VKFUNC_DEVICE(__FUNC__) extern PFN_##__FUNC__ __FUNC__ + #endif +#endif + + + +// global functions +VKFUNC(vkGetInstanceProcAddr); +VKFUNC(vkCreateInstance); +VKFUNC(vkGetDeviceProcAddr); +VKFUNC(vkEnumerateInstanceExtensionProperties); +VKFUNC(vkEnumerateDeviceExtensionProperties); +VKFUNC(vkEnumerateInstanceVersion); + +// instance functions +VKFUNC_INSTANCE(vkDestroyInstance); +VKFUNC_INSTANCE(vkEnumeratePhysicalDevices); +VKFUNC_INSTANCE(vkCreateDevice); +VKFUNC_INSTANCE(vkDestroyDevice); +VKFUNC_INSTANCE(vkDeviceWaitIdle); + +// device debug functions + +// instance debug functions +VKFUNC_INSTANCE(vkCreateDebugReportCallbackEXT); + +VKFUNC_INSTANCE(vkGetPhysicalDeviceToolPropertiesEXT); +VKFUNC_INSTANCE(vkSetDebugUtilsObjectNameEXT); + +//VKFUNC_INSTANCE(vkCreateDebugReportCallbackEXT); + +// getters +VKFUNC_DEVICE(vkGetDeviceQueue); +VKFUNC_INSTANCE(vkGetPhysicalDeviceQueueFamilyProperties); +VKFUNC_INSTANCE(vkGetPhysicalDeviceSurfaceSupportKHR); +VKFUNC_INSTANCE(vkGetPhysicalDeviceSurfaceCapabilitiesKHR); +VKFUNC_INSTANCE(vkGetPhysicalDeviceSurfaceFormatsKHR); +VKFUNC_INSTANCE(vkGetPhysicalDeviceSurfacePresentModesKHR); +VKFUNC_INSTANCE(vkGetPhysicalDeviceMemoryProperties); +VKFUNC_INSTANCE(vkGetPhysicalDeviceProperties); +VKFUNC_INSTANCE(vkGetPhysicalDeviceProperties2); +VKFUNC_INSTANCE(vkGetPhysicalDeviceFeatures2); +VKFUNC_INSTANCE(vkGetPhysicalDeviceFormatProperties); + +// semaphore +VKFUNC_DEVICE(vkCreateSemaphore); +VKFUNC_DEVICE(vkDestroySemaphore); + +// imageview +VKFUNC_DEVICE(vkCreateImageView); +VKFUNC_DEVICE(vkDestroyImageView); + +// shader +VKFUNC_DEVICE(vkCreateShaderModule); +VKFUNC_DEVICE(vkDestroyShaderModule); + +// framebuffer +VKFUNC_DEVICE(vkCreateFramebuffer); +VKFUNC_DEVICE(vkDestroyFramebuffer); + +// renderpass +VKFUNC_DEVICE(vkCreateRenderPass); +VKFUNC_DEVICE(vkDestroyRenderPass); +VKFUNC_DEVICE(vkCmdBeginRenderPass); +VKFUNC_DEVICE(vkCmdEndRenderPass); + +// command buffers +VKFUNC_DEVICE(vkCreateCommandPool); +VKFUNC_DEVICE(vkDestroyCommandPool); +VKFUNC_DEVICE(vkAllocateCommandBuffers); +VKFUNC_DEVICE(vkFreeCommandBuffers); +VKFUNC_DEVICE(vkBeginCommandBuffer); +VKFUNC_DEVICE(vkResetCommandBuffer); +VKFUNC_DEVICE(vkEndCommandBuffer); +VKFUNC_DEVICE(vkQueueSubmit); + +// pipeline +VKFUNC_DEVICE(vkCreatePipelineCache); +VKFUNC_DEVICE(vkMergePipelineCaches); +VKFUNC_DEVICE(vkGetPipelineCacheData); +VKFUNC_DEVICE(vkDestroyPipelineCache); +VKFUNC_DEVICE(vkCreatePipelineLayout); +VKFUNC_DEVICE(vkDestroyPipelineLayout); +VKFUNC_DEVICE(vkCreateGraphicsPipelines); +VKFUNC_DEVICE(vkDestroyPipeline); +VKFUNC_DEVICE(vkCmdBindPipeline); + +// swapchain +#if BOOST_OS_LINUX +VKFUNC_INSTANCE(vkCreateXlibSurfaceKHR); +VKFUNC_INSTANCE(vkCreateXcbSurfaceKHR); +#endif + +#if BOOST_OS_WINDOWS +VKFUNC_INSTANCE(vkCreateWin32SurfaceKHR); +#endif + +VKFUNC_INSTANCE(vkDestroySurfaceKHR); +VKFUNC_DEVICE(vkCreateSwapchainKHR); +VKFUNC_DEVICE(vkDestroySwapchainKHR); +VKFUNC_DEVICE(vkGetSwapchainImagesKHR); +VKFUNC_DEVICE(vkAcquireNextImageKHR); +VKFUNC_INSTANCE(vkQueuePresentKHR); + +// fences +VKFUNC_DEVICE(vkCreateFence); +VKFUNC_DEVICE(vkWaitForFences); +VKFUNC_DEVICE(vkGetFenceStatus); +VKFUNC_DEVICE(vkResetFences); +VKFUNC_DEVICE(vkDestroyFence); + +// cmd +VKFUNC_DEVICE(vkCmdDraw); +VKFUNC_DEVICE(vkCmdCopyBufferToImage); +VKFUNC_DEVICE(vkCmdCopyImageToBuffer); +VKFUNC_DEVICE(vkCmdClearColorImage); +VKFUNC_DEVICE(vkCmdBindIndexBuffer); +VKFUNC_DEVICE(vkCmdBindVertexBuffers); +VKFUNC_DEVICE(vkCmdDrawIndexed); +VKFUNC_DEVICE(vkCmdSetViewport); +VKFUNC_DEVICE(vkCmdSetScissor); +VKFUNC_DEVICE(vkCmdBindDescriptorSets); +VKFUNC_DEVICE(vkCmdPipelineBarrier); +VKFUNC_DEVICE(vkCmdClearDepthStencilImage); +VKFUNC_DEVICE(vkCmdCopyBuffer); +VKFUNC_DEVICE(vkCmdCopyImage); +VKFUNC_DEVICE(vkCmdBlitImage); +VKFUNC_DEVICE(vkCmdPushConstants); +VKFUNC_DEVICE(vkCmdSetBlendConstants); +VKFUNC_DEVICE(vkCmdSetDepthBias); + +// synchronization2 + +VKFUNC_DEVICE(vkCmdPipelineBarrier2KHR); + +// khr_dynamic_rendering +VKFUNC_DEVICE(vkCmdBeginRenderingKHR); +VKFUNC_DEVICE(vkCmdEndRenderingKHR); + +// transform feedback extension +VKFUNC_DEVICE(vkCmdBindTransformFeedbackBuffersEXT); +VKFUNC_DEVICE(vkCmdBeginTransformFeedbackEXT); +VKFUNC_DEVICE(vkCmdEndTransformFeedbackEXT); + +// query +VKFUNC_DEVICE(vkCreateQueryPool); +VKFUNC_DEVICE(vkCmdResetQueryPool); +VKFUNC_DEVICE(vkCmdBeginQuery); +VKFUNC_DEVICE(vkCmdEndQuery); +VKFUNC_DEVICE(vkCmdCopyQueryPoolResults); + +// event +VKFUNC_DEVICE(vkCreateEvent); +VKFUNC_DEVICE(vkCmdSetEvent); +VKFUNC_DEVICE(vkCmdWaitEvents); +VKFUNC_DEVICE(vkGetEventStatus); +VKFUNC_DEVICE(vkDestroyEvent); + +// memory +VKFUNC_DEVICE(vkAllocateMemory); +VKFUNC_DEVICE(vkFreeMemory); +VKFUNC_DEVICE(vkCreateBuffer); +VKFUNC_DEVICE(vkDestroyBuffer); +VKFUNC_DEVICE(vkBindBufferMemory); +VKFUNC_DEVICE(vkMapMemory); +VKFUNC_DEVICE(vkUnmapMemory); +VKFUNC_DEVICE(vkGetBufferMemoryRequirements); +VKFUNC_DEVICE(vkFlushMappedMemoryRanges); +VKFUNC_DEVICE(vkInvalidateMappedMemoryRanges); + +// image +VKFUNC_DEVICE(vkCreateImage); +VKFUNC_DEVICE(vkDestroyImage); +VKFUNC_DEVICE(vkGetImageMemoryRequirements); +VKFUNC_DEVICE(vkBindImageMemory); + +VKFUNC_DEVICE(vkCreateSampler); +VKFUNC_DEVICE(vkDestroySampler); + +VKFUNC_DEVICE(vkCreateDescriptorSetLayout); +VKFUNC_DEVICE(vkAllocateDescriptorSets); +VKFUNC_DEVICE(vkFreeDescriptorSets); +VKFUNC_DEVICE(vkUpdateDescriptorSets); +VKFUNC_DEVICE(vkCreateDescriptorPool); +VKFUNC_DEVICE(vkDestroyDescriptorSetLayout); + +#undef VKFUNC_INIT +#undef VKFUNC_INSTANCE_INIT +#undef VKFUNC_DEVICE_INIT +#undef VKFUNC_DEFINE + +#undef VKFUNC +#undef VKFUNC_INSTANCE +#undef VKFUNC_DEVICE \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.cpp new file mode 100644 index 00000000..7fb506b7 --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.cpp @@ -0,0 +1,1044 @@ +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" +#include "Cafe/HW/Latte/Core/FetchShader.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.h" +#include "Cafe/HW/Latte/Core/LatteShader.h" +#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" +#include "Cafe/OS/libs/gx2/GX2.h" +#include "config/ActiveSettings.h" +#include "util/helpers/Serializer.h" +#include "Cafe/HW/Latte/Common/RegisterSerializer.h" + +std::mutex s_nvidiaWorkaround; + +/* rects emulation */ + +void rectsEmulationGS_outputSingleVertex(std::string& gsSrc, LatteDecompilerShader* vertexShader, LatteShaderPSInputTable* psInputTable, sint32 vIdx, const LatteContextRegister& latteRegister) +{ + auto parameterMask = vertexShader->outputParameterMask; + for (uint32 i = 0; i < 32; i++) + { + if ((parameterMask & (1 << i)) == 0) + continue; + sint32 vsSemanticId = psInputTable->getVertexShaderOutParamSemanticId(latteRegister.GetRawView(), i); + if (vsSemanticId < 0) + continue; + // make sure PS has matching input + if (!psInputTable->hasPSImportForSemanticId(vsSemanticId)) + continue; + gsSrc.append(fmt::format("passParameterSem{}Out = passParameterSem{}In[{}];\r\n", vsSemanticId, vsSemanticId, vIdx)); + } + gsSrc.append(fmt::format("gl_Position = gl_in[{}].gl_Position;\r\n", vIdx)); + gsSrc.append("EmitVertex();\r\n"); +} + +void rectsEmulationGS_outputGeneratedVertex(std::string& gsSrc, LatteDecompilerShader* vertexShader, LatteShaderPSInputTable* psInputTable, const char* variant, const LatteContextRegister& latteRegister) +{ + auto parameterMask = vertexShader->outputParameterMask; + for (uint32 i = 0; i < 32; i++) + { + if ((parameterMask & (1 << i)) == 0) + continue; + sint32 vsSemanticId = psInputTable->getVertexShaderOutParamSemanticId(latteRegister.GetRawView(), i); + if (vsSemanticId < 0) + continue; + // make sure PS has matching input + if (!psInputTable->hasPSImportForSemanticId(vsSemanticId)) + continue; + gsSrc.append(fmt::format("passParameterSem{}Out = gen4thVertex{}(passParameterSem{}In[0], passParameterSem{}In[1], passParameterSem{}In[2]);\r\n", vsSemanticId, variant, vsSemanticId, vsSemanticId, vsSemanticId)); + } + gsSrc.append(fmt::format("gl_Position = gen4thVertex{}(gl_in[0].gl_Position, gl_in[1].gl_Position, gl_in[2].gl_Position);\r\n", variant)); + gsSrc.append("EmitVertex();\r\n"); +} + +void rectsEmulationGS_outputVerticesCode(std::string& gsSrc, LatteDecompilerShader* vertexShader, LatteShaderPSInputTable* psInputTable, sint32 p0, sint32 p1, sint32 p2, sint32 p3, const char* variant, const LatteContextRegister& latteRegister) +{ + sint32 pList[4] = { p0, p1, p2, p3 }; + for (sint32 i = 0; i < 4; i++) + { + if (pList[i] == 3) + rectsEmulationGS_outputGeneratedVertex(gsSrc, vertexShader, psInputTable, variant, latteRegister); + else + rectsEmulationGS_outputSingleVertex(gsSrc, vertexShader, psInputTable, pList[i], latteRegister); + } +} + +RendererShaderVk* rectsEmulationGS_generate(LatteDecompilerShader* vertexShader, const LatteContextRegister& latteRegister) +{ + std::string gsSrc; + + gsSrc.append("#version 450\r\n"); + + LatteShaderPSInputTable* psInputTable = LatteSHRC_GetPSInputTable(); + + // layout + gsSrc.append("layout(triangles) in;\r\n"); + gsSrc.append("layout(triangle_strip) out;\r\n"); + gsSrc.append("layout(max_vertices = 4) out;\r\n"); + + // inputs & outputs + auto parameterMask = vertexShader->outputParameterMask; + for (sint32 f = 0; f < 2; f++) + { + for (uint32 i = 0; i < 32; i++) + { + if ((parameterMask & (1 << i)) == 0) + continue; + sint32 vsSemanticId = psInputTable->getVertexShaderOutParamSemanticId(latteRegister.GetRawView(), i); + if (vsSemanticId < 0) + continue; + auto psImport = psInputTable->getPSImportBySemanticId(vsSemanticId); + if (psImport == nullptr) + continue; + + gsSrc.append(fmt::format("layout(location = {}) ", psInputTable->getPSImportLocationBySemanticId(vsSemanticId))); + if (psImport->isFlat) + gsSrc.append("flat "); + if (psImport->isNoPerspective) + gsSrc.append("noperspective "); + + if (f == 0) + gsSrc.append("in"); + else + gsSrc.append("out"); + + if (f == 0) + gsSrc.append(fmt::format(" vec4 passParameterSem{}In[];\r\n", vsSemanticId)); + else + gsSrc.append(fmt::format(" vec4 passParameterSem{}Out;\r\n", vsSemanticId)); + } + } + + // gen function + gsSrc.append("vec4 gen4thVertexA(vec4 a, vec4 b, vec4 c)\r\n"); + gsSrc.append("{\r\n"); + gsSrc.append("return b - (c - a);\r\n"); + gsSrc.append("}\r\n"); + + gsSrc.append("vec4 gen4thVertexB(vec4 a, vec4 b, vec4 c)\r\n"); + gsSrc.append("{\r\n"); + gsSrc.append("return c - (b - a);\r\n"); + gsSrc.append("}\r\n"); + + gsSrc.append("vec4 gen4thVertexC(vec4 a, vec4 b, vec4 c)\r\n"); + gsSrc.append("{\r\n"); + gsSrc.append("return c + (b - a);\r\n"); + gsSrc.append("}\r\n"); + + // main + gsSrc.append("void main()\r\n"); + gsSrc.append("{\r\n"); + + // there are two possible winding orders that need different triangle generation: + // 0 1 + // 2 3 + // and + // 0 1 + // 3 2 + // all others are just symmetries of these cases + + // we can determine the case by comparing the distance 0<->1 and 0<->2 + + gsSrc.append("float dist0_1 = length(gl_in[1].gl_Position.xy - gl_in[0].gl_Position.xy);\r\n"); + gsSrc.append("float dist0_2 = length(gl_in[2].gl_Position.xy - gl_in[0].gl_Position.xy);\r\n"); + gsSrc.append("float dist1_2 = length(gl_in[2].gl_Position.xy - gl_in[1].gl_Position.xy);\r\n"); + + // emit vertices + gsSrc.append("if(dist0_1 > dist0_2 && dist0_1 > dist1_2)\r\n"); + gsSrc.append("{\r\n"); + // p0 to p1 is diagonal + rectsEmulationGS_outputVerticesCode(gsSrc, vertexShader, psInputTable, 2, 1, 0, 3, "A", latteRegister); + gsSrc.append("} else if ( dist0_2 > dist0_1 && dist0_2 > dist1_2 ) {\r\n"); + // p0 to p2 is diagonal + rectsEmulationGS_outputVerticesCode(gsSrc, vertexShader, psInputTable, 1, 2, 0, 3, "B", latteRegister); + gsSrc.append("} else {\r\n"); + // p1 to p2 is diagonal + rectsEmulationGS_outputVerticesCode(gsSrc, vertexShader, psInputTable, 0, 1, 2, 3, "C", latteRegister); + gsSrc.append("}\r\n"); + + gsSrc.append("}\r\n"); + + auto vkShader = new RendererShaderVk(RendererShader::ShaderType::kGeometry, 0, 0, false, false, gsSrc); + vkShader->PreponeCompilation(true); + return vkShader; +} + +/* pipeline compiler and cache helper */ + +extern std::atomic_int g_compiling_pipelines; +extern std::atomic_int g_compiling_pipelines_async; +extern std::atomic_uint64_t g_compiling_pipelines_syncTimeSum; + +PipelineCompiler::PipelineCompiler() {}; +PipelineCompiler::~PipelineCompiler() +{ + if (m_vkrObjPipeline) + m_vkrObjPipeline->decRef(); + if (m_renderPassObj) + m_renderPassObj->decRef(); +}; + +VkFormat PipelineCompiler::GetVertexFormat(uint8 format) +{ + switch (format) + { + case FMT_32_32_32_32_FLOAT: + return VK_FORMAT_R32G32B32A32_UINT; + case FMT_32_32_32_FLOAT: + return VK_FORMAT_R32G32B32_UINT; + case FMT_32_32_FLOAT: + return VK_FORMAT_R32G32_UINT; + case FMT_32_FLOAT: + return VK_FORMAT_R32_UINT; + case FMT_8_8_8_8: + return VK_FORMAT_R8G8B8A8_UINT; + case FMT_8_8_8: + return VK_FORMAT_R8G8B8_UINT; + case FMT_8_8: + return VK_FORMAT_R8G8_UINT; + case FMT_8: + return VK_FORMAT_R8_UINT; + case FMT_32_32_32_32: + return VK_FORMAT_R32G32B32A32_UINT; + case FMT_32_32_32: + return VK_FORMAT_R32G32B32_UINT; + case FMT_32_32: + return VK_FORMAT_R32G32_UINT; + case FMT_32: + return VK_FORMAT_R32_UINT; + case FMT_16_16_16_16: + return VK_FORMAT_R16G16B16A16_UINT; // verified to match OpenGL + case FMT_16_16_16: + return VK_FORMAT_R16G16B16_UINT; + case FMT_16_16: + return VK_FORMAT_R16G16_UINT; + case FMT_16: + return VK_FORMAT_R16_UINT; + case FMT_16_16_16_16_FLOAT: + return VK_FORMAT_R16G16B16A16_UINT; // verified to match OpenGL + case FMT_16_16_16_FLOAT: + return VK_FORMAT_R16G16B16_UINT; + case FMT_16_16_FLOAT: + return VK_FORMAT_R16G16_UINT; + case FMT_16_FLOAT: + return VK_FORMAT_R16_UINT; + case FMT_2_10_10_10: + return VK_FORMAT_R32_UINT; // verified to match OpenGL + default: + forceLog_printf("Unsupported vertex format: %02x", format); + assert_dbg(); + return VK_FORMAT_UNDEFINED; + } +} + +static VkBlendOp GetVkBlendOp(Latte::LATTE_CB_BLENDN_CONTROL::E_COMBINEFUNC combineFunc) +{ + switch (combineFunc) + { + case Latte::LATTE_CB_BLENDN_CONTROL::E_COMBINEFUNC::DST_PLUS_SRC: + return VK_BLEND_OP_ADD; + case Latte::LATTE_CB_BLENDN_CONTROL::E_COMBINEFUNC::SRC_MINUS_DST: + return VK_BLEND_OP_SUBTRACT; + case Latte::LATTE_CB_BLENDN_CONTROL::E_COMBINEFUNC::MIN_DST_SRC: + return VK_BLEND_OP_MIN; + case Latte::LATTE_CB_BLENDN_CONTROL::E_COMBINEFUNC::MAX_DST_SRC: + return VK_BLEND_OP_MAX; + case Latte::LATTE_CB_BLENDN_CONTROL::E_COMBINEFUNC::DST_MINUS_SRC: + return VK_BLEND_OP_REVERSE_SUBTRACT; + default: + cemu_assert_suspicious(); + return VK_BLEND_OP_ADD; + } +} + +static VkBlendFactor GetVkBlendFactor(Latte::LATTE_CB_BLENDN_CONTROL::E_BLENDFACTOR factor) +{ + const VkBlendFactor factors[] = + { + /* 0x00 */ VK_BLEND_FACTOR_ZERO, + /* 0x01 */ VK_BLEND_FACTOR_ONE, + /* 0x02 */ VK_BLEND_FACTOR_SRC_COLOR, + /* 0x03 */ VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR, + /* 0x04 */ VK_BLEND_FACTOR_SRC_ALPHA, + /* 0x05 */ VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, + /* 0x06 */ VK_BLEND_FACTOR_DST_ALPHA, + /* 0x07 */ VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA, + /* 0x08 */ VK_BLEND_FACTOR_DST_COLOR, + /* 0x09 */ VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR, + /* 0x0A */ VK_BLEND_FACTOR_SRC_ALPHA_SATURATE, + /* 0x0B */ VK_BLEND_FACTOR_MAX_ENUM, // todo + /* 0x0C */ VK_BLEND_FACTOR_MAX_ENUM, // todo + /* 0x0D */ VK_BLEND_FACTOR_CONSTANT_COLOR, + /* 0x0E */ VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR, + /* 0x0F */ VK_BLEND_FACTOR_SRC1_COLOR, + /* 0x10 */ VK_BLEND_FACTOR_ONE_MINUS_SRC1_COLOR, + /* 0x11 */ VK_BLEND_FACTOR_SRC1_ALPHA, + /* 0x12 */ VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA, + /* 0x13 */ VK_BLEND_FACTOR_CONSTANT_ALPHA, + /* 0x14 */ VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA + }; + cemu_assert_debug((uint32)factor < std::size(factors)); + return factors[(uint32)factor]; +} + +bool PipelineCompiler::ConsumesBlendConstants(VkBlendFactor blendFactor) +{ + if (blendFactor == VK_BLEND_FACTOR_CONSTANT_COLOR || + blendFactor == VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR || + blendFactor == VK_BLEND_FACTOR_CONSTANT_ALPHA || + blendFactor == VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA) + return true; + return false; +} + +void PipelineCompiler::CreateDescriptorSetLayout(VulkanRenderer* vkRenderer, LatteDecompilerShader* shader, VkDescriptorSetLayout& layout, PipelineInfo* vkrPipelineInfo) +{ + // create vertex shader descriptor set + std::vector<VkDescriptorSetLayoutBinding> descriptorSetLayoutBindings; + + VkShaderStageFlags stageFlags = 0; + uint32 stageIndex = 0; + if (shader->shaderType == LatteConst::ShaderType::Vertex) + { + stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + stageIndex = VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX; + } + else if (shader->shaderType == LatteConst::ShaderType::Pixel) + { + stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + stageIndex = VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT; + } + else if (shader->shaderType == LatteConst::ShaderType::Geometry) + { + stageFlags = VK_SHADER_STAGE_GEOMETRY_BIT; + stageIndex = VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY; + } + // attributes + // -> not part of descriptor + + // textures + sint32 textureBindingBase = shader->resourceMapping.getTextureBaseBindingPoint(); + if (textureBindingBase >= 0) + { + sint32 textureCount = shader->resourceMapping.getTextureCount(); + for (sint32 i = 0; i < textureCount; i++) + { + VkDescriptorSetLayoutBinding entry{}; + entry.binding = (uint32)textureBindingBase + i; + entry.descriptorCount = 1; + entry.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + entry.pImmutableSamplers = nullptr; + entry.stageFlags = stageFlags; + descriptorSetLayoutBindings.emplace_back(entry); + } + } + + // uniform buffers + if (shader->resourceMapping.uniformVarsBufferBindingPoint >= 0) + { + VkDescriptorSetLayoutBinding entry{}; + entry.binding = shader->resourceMapping.uniformVarsBufferBindingPoint; + entry.descriptorCount = 1; + entry.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; + entry.pImmutableSamplers = nullptr; + entry.stageFlags = stageFlags; + descriptorSetLayoutBindings.emplace_back(entry); + } + + for (sint32 i = 0; i < LATTE_NUM_MAX_UNIFORM_BUFFERS; i++) + { + if (shader->resourceMapping.uniformBuffersBindingPoint[i] >= 0) + { + VkDescriptorSetLayoutBinding entry{}; + entry.binding = shader->resourceMapping.uniformBuffersBindingPoint[i]; + entry.descriptorCount = 1; + entry.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; + entry.pImmutableSamplers = nullptr; + entry.stageFlags = stageFlags; + descriptorSetLayoutBindings.emplace_back(entry); + + vkrPipelineInfo->dynamicOffsetInfo.list_uniformBuffers[stageIndex].emplace_back((uint8)i); + } + } + + // storage buffer for TF + if (shader->resourceMapping.tfStorageBindingPoint >= 0) + { + VkDescriptorSetLayoutBinding entry{}; + entry.binding = shader->resourceMapping.tfStorageBindingPoint; + entry.descriptorCount = 1; + entry.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + entry.pImmutableSamplers = nullptr; + entry.stageFlags = stageFlags; + descriptorSetLayoutBindings.emplace_back(entry); + } + + if (shader->resourceMapping.uniformVarsBufferBindingPoint >= 0) + vkrPipelineInfo->dynamicOffsetInfo.hasUniformVar[stageIndex] = true; + if (shader->resourceMapping.hasUniformBuffers()) + vkrPipelineInfo->dynamicOffsetInfo.hasUniformBuffers[stageIndex] = true; + + VkDescriptorSetLayoutCreateInfo layoutInfo = {}; + layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layoutInfo.bindingCount = descriptorSetLayoutBindings.size(); + layoutInfo.pBindings = descriptorSetLayoutBindings.data(); + + if (vkCreateDescriptorSetLayout(vkRenderer->m_logicalDevice, &layoutInfo, nullptr, &layout) != VK_SUCCESS) + vkRenderer->UnrecoverableError(fmt::format("Failed to create descriptor set layout for shader {0:#x}", shader->baseHash).c_str()); +} + +bool PipelineCompiler::InitShaderStages(VulkanRenderer* vkRenderer, RendererShaderVk* vkVertexShader, RendererShaderVk* vkPixelShader, RendererShaderVk* vkGeometryShader) +{ + // prepare shader stages + cemu_assert_debug(vkVertexShader == nullptr || vkVertexShader->IsCompiled()); + cemu_assert_debug(vkPixelShader == nullptr || vkPixelShader->IsCompiled()); + cemu_assert_debug(vkGeometryShader == nullptr || vkGeometryShader->IsCompiled()); + + if ((vkVertexShader && vkVertexShader->GetShaderModule() == VK_NULL_HANDLE) || + (vkGeometryShader && vkGeometryShader->GetShaderModule() == VK_NULL_HANDLE) || + (vkPixelShader && vkPixelShader->GetShaderModule() == VK_NULL_HANDLE)) + { + forceLog_printf("Vulkan-Info: Pipeline creation failed due to invalid shader(s)"); + return false; + } + + if (vkVertexShader) + shaderStages.emplace_back(vkRenderer->CreatePipelineShaderStageCreateInfo(VK_SHADER_STAGE_VERTEX_BIT, vkVertexShader->GetShaderModule(), "main")); + + if (vkGeometryShader) + shaderStages.emplace_back(vkRenderer->CreatePipelineShaderStageCreateInfo(VK_SHADER_STAGE_GEOMETRY_BIT, vkGeometryShader->GetShaderModule(), "main")); + else if (m_rectEmulationGS) + shaderStages.emplace_back(vkRenderer->CreatePipelineShaderStageCreateInfo(VK_SHADER_STAGE_GEOMETRY_BIT, m_rectEmulationGS->GetShaderModule(), "main")); + + if (vkPixelShader) + shaderStages.emplace_back(vkRenderer->CreatePipelineShaderStageCreateInfo(VK_SHADER_STAGE_FRAGMENT_BIT, vkPixelShader->GetShaderModule(), "main")); + + return true; +} + +void PipelineCompiler::InitVertexInputState(const LatteContextRegister& latteRegister, LatteDecompilerShader* vertexShader, LatteFetchShader* fetchShader) +{ + vertexInputAttributeDescription.reserve(16); + vertexInputBindingDescription.reserve(fetchShader->bufferGroups.size()); + + for (auto& bufferGroup : fetchShader->bufferGroups) + { + std::optional<LatteConst::VertexFetchType2> fetchType; + + for (sint32 j = 0; j < bufferGroup.attribCount; ++j) + { + auto& attr = bufferGroup.attrib[j]; + + uint32 semanticId = vertexShader->resourceMapping.attributeMapping[attr.semanticId]; + if (semanticId == (uint32)-1) + continue; // attribute not used? + + VkVertexInputAttributeDescription entry{}; + entry.location = semanticId; + entry.offset = attr.offset; + entry.binding = attr.attributeBufferIndex; + entry.format = GetVertexFormat(attr.format); + vertexInputAttributeDescription.emplace_back(entry); + + if (fetchType.has_value()) + cemu_assert_debug(fetchType == attr.fetchType); + else + fetchType = attr.fetchType; + + if (attr.fetchType == LatteConst::INSTANCE_DATA) + { + cemu_assert_debug(attr.aluDivisor == 1); // other divisor not yet supported + // use VK_EXT_vertex_attribute_divisor + } + } + + uint32 bufferIndex = bufferGroup.attributeBufferIndex; + uint32 bufferBaseRegisterIndex = mmSQ_VTX_ATTRIBUTE_BLOCK_START + bufferIndex * 7; + uint32 bufferStride = (latteRegister.GetRawView()[bufferBaseRegisterIndex + 2] >> 11) & 0xFFFF; + + VkVertexInputBindingDescription entry{}; + entry.stride = bufferStride; + if (!fetchType.has_value() || fetchType == LatteConst::VertexFetchType2::VERTEX_DATA) + entry.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + else if (fetchType == LatteConst::VertexFetchType2::INSTANCE_DATA) + entry.inputRate = VK_VERTEX_INPUT_RATE_INSTANCE; + else + { + cemu_assert(false); + } + entry.binding = bufferIndex; + vertexInputBindingDescription.emplace_back(entry); + } + + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertexInputInfo.vertexBindingDescriptionCount = vertexInputBindingDescription.size(); + vertexInputInfo.pVertexBindingDescriptions = vertexInputBindingDescription.data(); + vertexInputInfo.vertexAttributeDescriptionCount = vertexInputAttributeDescription.size(); + vertexInputInfo.pVertexAttributeDescriptions = vertexInputAttributeDescription.data(); +} + +void PipelineCompiler::InitInputAssemblyState(const Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE primitiveMode) +{ + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.primitiveRestartEnable = VK_TRUE; + switch (primitiveMode) + { + case Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::POINTS: + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST; + inputAssembly.primitiveRestartEnable = false; + break; + case Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::LINES: + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_LINE_LIST; + inputAssembly.primitiveRestartEnable = false; + break; + case Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::LINE_STRIP: + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_LINE_STRIP; + break; + case Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::LINE_LOOP: + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_LINE_STRIP; // line loops are emulated as line strips with an extra connecting strip at the end + break; + case Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::LINE_STRIP_ADJACENT: // Tropical Freeze level 3-6 + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY; + break; + case Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::TRIANGLES: + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssembly.primitiveRestartEnable = false; + break; + case Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::TRIANGLE_FAN: + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN; + break; + case Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::TRIANGLE_STRIP: + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; + break; + case Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::QUADS: + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; // quads are emulated as 2 triangles + inputAssembly.primitiveRestartEnable = false; + break; + case Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::QUAD_STRIP: + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; // quad strips are emulated as (count-2)/2 triangles + inputAssembly.primitiveRestartEnable = false; + break; + case Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::RECTS: + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; // rects are emulated as 2 triangles + inputAssembly.primitiveRestartEnable = false; + break; + default: + forceLogDebug_printf("Vulkan-Unsupported: Graphics pipeline with primitive mode %d created", primitiveMode); + cemu_assert_debug(false); + } +} + +void PipelineCompiler::InitViewportState() +{ + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.scissorCount = 1; +} + +void PipelineCompiler::InitRasterizerState(const LatteContextRegister& latteRegister, VulkanRenderer* vkRenderer, bool isPrimitiveRect, bool& usesDepthBias) +{ + // polygon control + const auto& polygonControlReg = latteRegister.PA_SU_SC_MODE_CNTL; + const auto frontFace = polygonControlReg.get_FRONT_FACE(); + uint32 cullFront = polygonControlReg.get_CULL_FRONT(); + uint32 cullBack = polygonControlReg.get_CULL_BACK(); + uint32 polyOffsetFrontEnable = polygonControlReg.get_OFFSET_FRONT_ENABLED(); + + cemu_assert_debug(LatteGPUState.contextNew.PA_CL_CLIP_CNTL.get_ZCLIP_NEAR_DISABLE() == LatteGPUState.contextNew.PA_CL_CLIP_CNTL.get_ZCLIP_FAR_DISABLE()); // near or far clipping can be disabled individually + bool zClipEnable = LatteGPUState.contextNew.PA_CL_CLIP_CNTL.get_ZCLIP_FAR_DISABLE() == false; + + // z-clipping + rasterizerExt.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_DEPTH_CLIP_STATE_CREATE_INFO_EXT; + rasterizerExt.depthClipEnable = zClipEnable; + rasterizerExt.flags = 0; + + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.pNext = &rasterizerExt; + rasterizer.rasterizerDiscardEnable = LatteGPUState.contextNew.PA_CL_CLIP_CNTL.get_DX_RASTERIZATION_KILL(); + // GX2SetSpecialState(0, true) workaround + if (!LatteGPUState.contextNew.PA_CL_VTE_CNTL.get_VPORT_X_OFFSET_ENA()) + rasterizer.rasterizerDiscardEnable = false; + + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + if (vkRenderer->m_featureControl.deviceExtensions.nv_fill_rectangle && isPrimitiveRect) + rasterizer.polygonMode = VK_POLYGON_MODE_FILL_RECTANGLE_NV; + + rasterizer.depthClampEnable = VK_TRUE; // depth clamping is always enabled + + rasterizer.lineWidth = 1.0f; // TODO -> mmPA_SU_LINE_CNTL + + usesDepthBias = polyOffsetFrontEnable; + if (polyOffsetFrontEnable) + { + rasterizer.depthBiasEnable = VK_TRUE; + // initialize to zero, set dynamically via vkCmdSetDepthBias + rasterizer.depthBiasConstantFactor = 0.0f; + rasterizer.depthBiasSlopeFactor = 0.0f; + rasterizer.depthBiasClamp = 0.0f; + } + else + rasterizer.depthBiasEnable = VK_FALSE; + + // todo - how does culling behave with rects? + // right now we just assume that their winding is always CW + if (isPrimitiveRect) + { + if (frontFace == Latte::LATTE_PA_SU_SC_MODE_CNTL::E_FRONTFACE::CW) + cullFront = cullBack; + else + cullBack = cullFront; + } + + if (cullFront && cullBack) + rasterizer.cullMode = VK_CULL_MODE_FRONT_AND_BACK; + else if (cullFront) + rasterizer.cullMode = VK_CULL_MODE_FRONT_BIT; + else if (cullBack) + rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; + else + rasterizer.cullMode = VK_CULL_MODE_NONE; + + if (frontFace == Latte::LATTE_PA_SU_SC_MODE_CNTL::E_FRONTFACE::CCW) + rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + else + rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; + + // multisampling + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; +} + +void PipelineCompiler::InitBlendState(const LatteContextRegister& latteRegister, PipelineInfo* pipelineInfo, bool& usesBlendConstants) +{ + const Latte::LATTE_CB_COLOR_CONTROL& colorControlReg = latteRegister.CB_COLOR_CONTROL; + uint32 blendEnableMask = colorControlReg.get_BLEND_MASK(); + uint32 renderTargetMask = latteRegister.CB_TARGET_MASK.get_MASK(); + + usesBlendConstants = false; + + for (size_t i = 0; i < colorBlendAttachments.size(); i++) + { + auto& entry = colorBlendAttachments[i]; + if (((blendEnableMask & (1 << i))) != 0) + entry.blendEnable = VK_TRUE; + else + entry.blendEnable = VK_FALSE; + + const auto& blendControlReg = latteRegister.CB_BLENDN_CONTROL[i]; + + entry.colorWriteMask = (renderTargetMask >> (i * 4)) & 0xF; + entry.colorBlendOp = GetVkBlendOp(blendControlReg.get_COLOR_COMB_FCN()); + entry.srcColorBlendFactor = GetVkBlendFactor(blendControlReg.get_COLOR_SRCBLEND()); + entry.dstColorBlendFactor = GetVkBlendFactor(blendControlReg.get_COLOR_DSTBLEND()); + if (blendControlReg.get_SEPARATE_ALPHA_BLEND()) + { + entry.alphaBlendOp = GetVkBlendOp(blendControlReg.get_ALPHA_COMB_FCN()); + entry.srcAlphaBlendFactor = GetVkBlendFactor(blendControlReg.get_ALPHA_SRCBLEND()); + entry.dstAlphaBlendFactor = GetVkBlendFactor(blendControlReg.get_ALPHA_DSTBLEND()); + } + else + { + entry.alphaBlendOp = entry.colorBlendOp; + entry.srcAlphaBlendFactor = entry.srcColorBlendFactor; + entry.dstAlphaBlendFactor = entry.dstColorBlendFactor; + } + + usesBlendConstants |= ConsumesBlendConstants(entry.srcColorBlendFactor); + usesBlendConstants |= ConsumesBlendConstants(entry.dstColorBlendFactor); + usesBlendConstants |= ConsumesBlendConstants(entry.srcAlphaBlendFactor); + usesBlendConstants |= ConsumesBlendConstants(entry.dstAlphaBlendFactor); + } + + // setup VkPipelineColorBlendStateCreateInfo + colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + + const auto logicOp = colorControlReg.get_ROP(); + if (logicOp == Latte::LATTE_CB_COLOR_CONTROL::E_LOGICOP::COPY) + { + colorBlending.logicOpEnable = VK_FALSE; + colorBlending.logicOp = VK_LOGIC_OP_COPY; + } + else + { + colorBlending.logicOpEnable = VK_TRUE; + switch (logicOp) + { + case Latte::LATTE_CB_COLOR_CONTROL::E_LOGICOP::SET: + colorBlending.logicOp = VK_LOGIC_OP_SET; + break; + case Latte::LATTE_CB_COLOR_CONTROL::E_LOGICOP::CLEAR: + colorBlending.logicOp = VK_LOGIC_OP_CLEAR; + break; + case Latte::LATTE_CB_COLOR_CONTROL::E_LOGICOP::OR: + colorBlending.logicOp = VK_LOGIC_OP_OR; + break; + default: + colorBlending.logicOp = VK_LOGIC_OP_COPY; + cemu_assert_unimplemented(); + } + } + + colorBlending.attachmentCount = colorBlendAttachments.size(); + colorBlending.pAttachments = colorBlendAttachments.data(); + + // we use VK_DYNAMIC_STATE_BLEND_CONSTANTS, the blend constants here don't matter + colorBlending.blendConstants[0] = 0; + colorBlending.blendConstants[1] = 0; + colorBlending.blendConstants[2] = 0; + colorBlending.blendConstants[3] = 0; +} + +void PipelineCompiler::InitDescriptorSetLayouts(VulkanRenderer* vkRenderer, PipelineInfo* vkrPipelineInfo, LatteDecompilerShader* vertexShader, LatteDecompilerShader* pixelShader, LatteDecompilerShader* geometryShader) +{ + auto vkObjPipeline = vkrPipelineInfo->m_vkrObjPipeline; + + if (vertexShader) + { + cemu_assert_debug(descriptorSetLayoutCount == 0); + CreateDescriptorSetLayout(vkRenderer, vertexShader, descriptorSetLayout[descriptorSetLayoutCount], vkrPipelineInfo); + vkObjPipeline->vertexDSL = descriptorSetLayout[descriptorSetLayoutCount]; + descriptorSetLayoutCount++; + } + + if (pixelShader) + { + cemu_assert_debug(descriptorSetLayoutCount == 1); + CreateDescriptorSetLayout(vkRenderer, pixelShader, descriptorSetLayout[descriptorSetLayoutCount], vkrPipelineInfo); + vkObjPipeline->pixelDSL = descriptorSetLayout[descriptorSetLayoutCount]; + descriptorSetLayoutCount++; + } + else if (geometryShader) + { + // if no pixel shader is present, create empty placeholder descriptor set layout (geometry shader set must be at index 2) + VkDescriptorSetLayoutCreateInfo layoutInfo = {}; + layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layoutInfo.bindingCount = 0; + layoutInfo.pBindings = nullptr; + if (vkCreateDescriptorSetLayout(vkRenderer->m_logicalDevice, &layoutInfo, nullptr, &descriptorSetLayout[descriptorSetLayoutCount]) != VK_SUCCESS) + vkRenderer->UnrecoverableError(fmt::format("Failed to create placeholder descriptor set layout for shader {0:#x}", geometryShader->baseHash).c_str()); + descriptorSetLayoutCount++; + } + + if (geometryShader) + { + cemu_assert_debug(descriptorSetLayoutCount == 2); + CreateDescriptorSetLayout(vkRenderer, geometryShader, descriptorSetLayout[descriptorSetLayoutCount], vkrPipelineInfo); + vkObjPipeline->geometryDSL = descriptorSetLayout[descriptorSetLayoutCount]; + descriptorSetLayoutCount++; + } +} + +void PipelineCompiler::InitDepthStencilState() +{ + // get depth control parameters + bool depthEnable = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_Z_ENABLE(); + auto depthFunc = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_Z_FUNC(); + bool depthWriteEnable = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_Z_WRITE_ENABLE(); + + // setup VkPipelineDepthStencilStateCreateInfo + depthStencilState.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + depthStencilState.depthTestEnable = depthEnable ? VK_TRUE : VK_FALSE; + depthStencilState.depthWriteEnable = depthWriteEnable ? VK_TRUE : VK_FALSE; + + static const VkCompareOp vkDepthCompareTable[8] = + { + VK_COMPARE_OP_NEVER, + VK_COMPARE_OP_LESS, + VK_COMPARE_OP_EQUAL, + VK_COMPARE_OP_LESS_OR_EQUAL, + VK_COMPARE_OP_GREATER, + VK_COMPARE_OP_NOT_EQUAL, + VK_COMPARE_OP_GREATER_OR_EQUAL, + VK_COMPARE_OP_ALWAYS + }; + + depthStencilState.depthCompareOp = vkDepthCompareTable[(size_t)depthFunc]; + + depthStencilState.depthBoundsTestEnable = false; // todo + depthStencilState.minDepthBounds = 0.0f; + depthStencilState.maxDepthBounds = 1.0f; + + // get stencil control parameters + bool stencilEnable = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_STENCIL_ENABLE(); + bool backStencilEnable = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_BACK_STENCIL_ENABLE(); + auto frontStencilFunc = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_STENCIL_FUNC_F(); + auto frontStencilZPass = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_STENCIL_ZPASS_F(); + auto frontStencilZFail = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_STENCIL_ZFAIL_F(); + auto frontStencilFail = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_STENCIL_FAIL_F(); + auto backStencilFunc = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_STENCIL_FUNC_B(); + auto backStencilZPass = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_STENCIL_ZPASS_B(); + auto backStencilZFail = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_STENCIL_ZFAIL_B(); + auto backStencilFail = LatteGPUState.contextNew.DB_DEPTH_CONTROL.get_STENCIL_FAIL_B(); + // get stencil control parameters + uint32 stencilCompareMaskFront = LatteGPUState.contextNew.DB_STENCILREFMASK.get_STENCILMASK_F(); + uint32 stencilWriteMaskFront = LatteGPUState.contextNew.DB_STENCILREFMASK.get_STENCILWRITEMASK_F(); + uint32 stencilRefFront = LatteGPUState.contextNew.DB_STENCILREFMASK.get_STENCILREF_F(); + uint32 stencilCompareMaskBack = LatteGPUState.contextNew.DB_STENCILREFMASK_BF.get_STENCILMASK_B(); + uint32 stencilWriteMaskBack = LatteGPUState.contextNew.DB_STENCILREFMASK_BF.get_STENCILWRITEMASK_B(); + uint32 stencilRefBack = LatteGPUState.contextNew.DB_STENCILREFMASK_BF.get_STENCILREF_B(); + + static const VkStencilOp stencilOpTable[8] = { + VK_STENCIL_OP_KEEP, + VK_STENCIL_OP_ZERO, + VK_STENCIL_OP_REPLACE, + VK_STENCIL_OP_INCREMENT_AND_CLAMP, + VK_STENCIL_OP_DECREMENT_AND_CLAMP, + VK_STENCIL_OP_INVERT, + VK_STENCIL_OP_INCREMENT_AND_WRAP, + VK_STENCIL_OP_DECREMENT_AND_WRAP + }; + + depthStencilState.stencilTestEnable = stencilEnable ? VK_TRUE : VK_FALSE; + + depthStencilState.front.reference = stencilRefFront; + depthStencilState.front.compareMask = stencilCompareMaskFront; + depthStencilState.front.writeMask = stencilWriteMaskBack; + depthStencilState.front.compareOp = vkDepthCompareTable[(size_t)frontStencilFunc]; + depthStencilState.front.depthFailOp = stencilOpTable[(size_t)frontStencilZFail]; + depthStencilState.front.failOp = stencilOpTable[(size_t)frontStencilFail]; + depthStencilState.front.passOp = stencilOpTable[(size_t)frontStencilZPass]; + + if (backStencilEnable) + { + depthStencilState.back.reference = stencilRefBack; + depthStencilState.back.compareMask = stencilCompareMaskBack; + depthStencilState.back.writeMask = stencilWriteMaskBack; + depthStencilState.back.compareOp = vkDepthCompareTable[(size_t)backStencilFunc]; + depthStencilState.back.depthFailOp = stencilOpTable[(size_t)backStencilZFail]; + depthStencilState.back.failOp = stencilOpTable[(size_t)backStencilFail]; + depthStencilState.back.passOp = stencilOpTable[(size_t)backStencilZPass]; + } + else + { + depthStencilState.back.reference = stencilRefFront; + depthStencilState.back.compareMask = stencilCompareMaskFront; + depthStencilState.back.writeMask = stencilWriteMaskFront; + depthStencilState.back.compareOp = vkDepthCompareTable[(size_t)frontStencilFunc]; + depthStencilState.back.depthFailOp = stencilOpTable[(size_t)frontStencilZFail]; + depthStencilState.back.failOp = stencilOpTable[(size_t)frontStencilFail]; + depthStencilState.back.passOp = stencilOpTable[(size_t)frontStencilZPass]; + } +} + +void PipelineCompiler::InitDynamicState(PipelineInfo* pipelineInfo, bool usesBlendConstants, bool usesDepthBias) +{ + + if (usesBlendConstants) + { + dynamicStates.emplace_back(VK_DYNAMIC_STATE_BLEND_CONSTANTS); + pipelineInfo->usesBlendConstants = true; + } + if (usesDepthBias) + { + dynamicStates.emplace_back(VK_DYNAMIC_STATE_DEPTH_BIAS); + pipelineInfo->usesDepthBias = true; + } + + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = dynamicStates.size(); + dynamicState.pDynamicStates = dynamicStates.data(); +} + +bool PipelineCompiler::InitFromCurrentGPUState(PipelineInfo* pipelineInfo, const LatteContextRegister& latteRegister, VKRObjectRenderPass* renderPassObj) +{ + VulkanRenderer* vkRenderer = VulkanRenderer::GetInstance(); + + // ########################################################################################################################################## + bool isPrimitiveRect = false; + const auto primitiveMode = latteRegister.VGT_PRIMITIVE_TYPE.get_PRIMITIVE_MODE(); + isPrimitiveRect = (primitiveMode == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::RECTS); + + m_fetchShader = pipelineInfo->fetchShader; + m_vkVertexShader = pipelineInfo->vertexShaderVk; + m_vkPixelShader = pipelineInfo->pixelShaderVk; + m_vkGeometryShader = pipelineInfo->geometryShaderVk; + m_vkrObjPipeline = pipelineInfo->m_vkrObjPipeline; + m_renderPassObj = renderPassObj; + + // if required generate RECT emulation geometry shader + if (!vkRenderer->m_featureControl.deviceExtensions.nv_fill_rectangle && isPrimitiveRect) + { + cemu_assert(m_vkGeometryShader == nullptr); // todo - handle cases where the game already provides a GS + m_rectEmulationGS = rectsEmulationGS_generate(pipelineInfo->vertexShader, latteRegister); + pipelineInfo->rectEmulationGS = m_rectEmulationGS; + } + + // ########################################################################################################################################## + + pipelineInfo->primitiveMode = primitiveMode; + InitVertexInputState(latteRegister, pipelineInfo->vertexShader, pipelineInfo->fetchShader); + InitInputAssemblyState(primitiveMode); + InitViewportState(); + bool usesDepthBias = false; + InitRasterizerState(latteRegister, vkRenderer, isPrimitiveRect, usesDepthBias); + bool usesBlendConstants = false; + InitBlendState(latteRegister, pipelineInfo, usesBlendConstants); + InitDescriptorSetLayouts(vkRenderer, pipelineInfo, pipelineInfo->vertexShader, pipelineInfo->pixelShader, pipelineInfo->geometryShader); + + // ########################################################################################################################################## + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = descriptorSetLayoutCount; + pipelineLayoutInfo.pSetLayouts = descriptorSetLayout; + pipelineLayoutInfo.pPushConstantRanges = nullptr; + pipelineLayoutInfo.pushConstantRangeCount = 0; + + VkResult result = vkCreatePipelineLayout(vkRenderer->m_logicalDevice, &pipelineLayoutInfo, nullptr, &m_pipeline_layout); + if (result != VK_SUCCESS) + { + forceLog_printf("%s", fmt::format("Failed to create pipeline layout: {}", result).c_str()); + s_nvidiaWorkaround.unlock(); + return false; + } + + // ################################################### + + InitDepthStencilState(); + + // ########################################################################################################################################## + + InitDynamicState(pipelineInfo, usesBlendConstants, usesDepthBias); + + // ########################################################################################################################################## + + pipelineInfo->m_vkrObjPipeline->pipeline_layout = m_pipeline_layout; + + // increment ref counter for vkrObjPipeline and renderpass object to make sure they dont get released while we are using them + m_vkrObjPipeline->incRef(); + renderPassObj->incRef(); + return true; +} + +bool PipelineCompiler::Compile(bool forceCompile, bool isRenderThread, bool showInOverlay) +{ + VulkanRenderer* vkRenderer = VulkanRenderer::GetInstance(); + + if (!vkRenderer->m_featureControl.deviceExtensions.pipeline_creation_cache_control) + forceCompile = true; // if VK_EXT_pipeline_creation_cache_control is not supported we always force synchronous compilation + + if (!forceCompile) + { + // fail early if some shader stages are not compiled + if (m_vkVertexShader && m_vkVertexShader->IsCompiled() == false) + return false; + if (m_vkPixelShader && m_vkPixelShader->IsCompiled() == false) + return false; + if (m_vkGeometryShader && m_vkGeometryShader->IsCompiled() == false) + return false; + } + else + { + // if some shader stages are not compiled yet, compile them now + if (m_vkVertexShader && m_vkVertexShader->IsCompiled() == false) + m_vkVertexShader->PreponeCompilation(isRenderThread); + if (m_vkPixelShader && m_vkPixelShader->IsCompiled() == false) + m_vkPixelShader->PreponeCompilation(isRenderThread); + if (m_vkGeometryShader && m_vkGeometryShader->IsCompiled() == false) + m_vkGeometryShader->PreponeCompilation(isRenderThread); + } + + if (shaderStages.empty()) + { + if (!InitShaderStages(vkRenderer, m_vkVertexShader, m_vkPixelShader, m_vkGeometryShader)) + return true; // invalid shaders, cannot compile + } + + VkGraphicsPipelineCreateInfo pipelineInfo{}; + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineInfo.stageCount = shaderStages.size(); + pipelineInfo.pStages = shaderStages.data(); + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pDynamicState = &dynamicState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.layout = m_pipeline_layout; + pipelineInfo.renderPass = m_renderPassObj->m_renderPass; + pipelineInfo.pDepthStencilState = &depthStencilState; + pipelineInfo.subpass = 0; + pipelineInfo.basePipelineHandle = nullptr; + pipelineInfo.flags = 0; + if (!forceCompile) + pipelineInfo.flags |= VK_PIPELINE_CREATE_FAIL_ON_PIPELINE_COMPILE_REQUIRED_BIT_EXT; + + VkPipelineCreationFeedbackCreateInfoEXT creationFeedbackInfo; + VkPipelineCreationFeedbackEXT creationFeedback; + std::vector<VkPipelineCreationFeedbackEXT> creationStageFeedback(0); + if (vkRenderer->m_featureControl.deviceExtensions.pipeline_feedback) + { + creationFeedback = {}; + creationFeedback.flags = VK_PIPELINE_CREATION_FEEDBACK_VALID_BIT_EXT; + + creationStageFeedback.reserve(pipelineInfo.stageCount); + for (uint32_t i = 0; i < pipelineInfo.stageCount; ++i) + creationStageFeedback.data()[i] = { VK_PIPELINE_CREATION_FEEDBACK_VALID_BIT_EXT, 0 }; + + creationFeedbackInfo = {}; + creationFeedbackInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CREATION_FEEDBACK_CREATE_INFO_EXT; + creationFeedbackInfo.pPipelineCreationFeedback = &creationFeedback; + creationFeedbackInfo.pPipelineStageCreationFeedbacks = creationStageFeedback.data(); + creationFeedbackInfo.pipelineStageCreationFeedbackCount = pipelineInfo.stageCount; + pipelineInfo.pNext = &creationFeedbackInfo; + } + + VkPipeline pipeline = VK_NULL_HANDLE; + VkResult result; + uint8 retryCount = 0; + while (retryCount < 3) + { + std::shared_lock lock(vkRenderer->m_pipeline_cache_save_mutex); + result = vkCreateGraphicsPipelines(vkRenderer->m_logicalDevice, vkRenderer->m_pipeline_cache, 1, &pipelineInfo, nullptr, &pipeline); + lock.unlock(); + if (result != VK_ERROR_OUT_OF_DEVICE_MEMORY) + break; + retryCount++; + } + + if (result == VK_ERROR_PIPELINE_COMPILE_REQUIRED_EXT) + { + return false; + } + else if (result == VK_SUCCESS) + { + m_vkrObjPipeline->setPipeline(pipeline); + } + else + { + forceLog_printf("Failed to create graphics pipeline. Error %d", (sint32)result); + cemu_assert_debug(false); + return true; // true indicates that caller should no longer attempt to compile this pipeline again + } + vkRenderer->m_pipeline_cache_semaphore.notify(); + + if (vkRenderer->m_featureControl.deviceExtensions.pipeline_feedback) + { + if (HAS_FLAG(creationFeedback.flags, VK_PIPELINE_CREATION_FEEDBACK_VALID_BIT_EXT)) + { + bool hasCacheHit = HAS_FLAG(creationFeedback.flags, VK_PIPELINE_CREATION_FEEDBACK_APPLICATION_PIPELINE_CACHE_HIT_BIT_EXT); + if (!hasCacheHit) + { + if (showInOverlay) + { + if (isRenderThread) + g_compiling_pipelines_syncTimeSum += creationFeedback.duration; + else + g_compiling_pipelines_async++; + g_compiling_pipelines++; + } + } + } + } + return true; +} + +void PipelineCompiler::TrackAsCached(uint64 baseHash, uint64 pipelineStateHash) +{ + auto& pipelineCache = VulkanPipelineStableCache::GetInstance(); + if (pipelineCache.HasPipelineCached(baseHash, pipelineStateHash)) + return; + pipelineCache.AddCurrentStateToCache(baseHash, pipelineStateHash); +} diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.h new file mode 100644 index 00000000..4020b27e --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.h @@ -0,0 +1,84 @@ +#pragma once + +class PipelineCompiler : public VKRMoveableRefCounter +{ +private: + // helper functions + VkFormat GetVertexFormat(uint8 format); + bool ConsumesBlendConstants(VkBlendFactor blendFactor); + + void CreateDescriptorSetLayout(VulkanRenderer* vkRenderer, LatteDecompilerShader* shader, VkDescriptorSetLayout& layout, PipelineInfo* vkrPipelineInfo); + +private: + + /* shader stages (requires compiled shader) */ + + RendererShaderVk* m_rectEmulationGS{}; + + bool InitShaderStages(VulkanRenderer* vkRenderer, RendererShaderVk* vkVertexShader, RendererShaderVk* vkPixelShader, RendererShaderVk* vkGeometryShader); + + /* vertex input state */ + + void InitVertexInputState(const LatteContextRegister& latteRegister, LatteDecompilerShader* vertexShader, LatteFetchShader* fetchShader); + void InitInputAssemblyState(const Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE primitiveMode); + void InitViewportState(); + void InitRasterizerState(const LatteContextRegister& latteRegister, VulkanRenderer* vkRenderer, bool isPrimitiveRect, bool& usesDepthBias); + void InitBlendState(const LatteContextRegister& latteRegister, PipelineInfo* pipelineInfo, bool& usesBlendConstants); + void InitDescriptorSetLayouts(VulkanRenderer* vkRenderer, PipelineInfo* vkrPipelineInfo, LatteDecompilerShader* vertexShader, LatteDecompilerShader* pixelShader, LatteDecompilerShader* geometryShader); + void InitDepthStencilState(); + void InitDynamicState(PipelineInfo* pipelineInfo, bool usesBlendConstants, bool usesDepthBias); + +public: + PipelineCompiler(); + ~PipelineCompiler(); + + VKRObjectPipeline* m_vkrObjPipeline{}; + LatteFetchShader* m_fetchShader{}; + RendererShaderVk* m_vkVertexShader{}; + RendererShaderVk* m_vkPixelShader{}; + RendererShaderVk* m_vkGeometryShader{}; + + bool InitFromCurrentGPUState(PipelineInfo* pipelineInfo, const LatteContextRegister& latteRegister, VKRObjectRenderPass* renderPassObj); + void TrackAsCached(uint64 baseHash, uint64 pipelineStateHash); // stores pipeline to permanent cache if not yet cached. Must be called synchronously from render thread due to dependency on GPU state + + VkPipelineLayout m_pipeline_layout; + VKRObjectRenderPass* m_renderPassObj{}; + + /* shader stages */ + std::vector<VkPipelineShaderStageCreateInfo> shaderStages; + + /* vertex attribute description */ + std::vector<VkVertexInputAttributeDescription> vertexInputAttributeDescription; + std::vector<VkVertexInputBindingDescription> vertexInputBindingDescription; + VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; + + /* input assembly state */ + VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; + + /* viewport state */ + VkPipelineViewportStateCreateInfo viewportState{}; + + /* rasterizer state */ + VkPipelineRasterizationStateCreateInfo rasterizer{}; + VkPipelineRasterizationDepthClipStateCreateInfoEXT rasterizerExt{}; + VkPipelineMultisampleStateCreateInfo multisampling{}; + + /* blend state */ + std::array<VkPipelineColorBlendAttachmentState, 8> colorBlendAttachments{}; + VkPipelineColorBlendStateCreateInfo colorBlending{}; + + /* descriptor set layouts */ + VkDescriptorSetLayout descriptorSetLayout[3]; + sint32 descriptorSetLayoutCount = 0; + + /* depth stencil state */ + VkPipelineDepthStencilStateCreateInfo depthStencilState{}; + + /* dynamic state */ + std::vector<VkDynamicState> dynamicStates = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; + VkPipelineDynamicStateCreateInfo dynamicState = {}; + + // returns true if the shader was compiled (even if errors occurred) + bool Compile(bool forceCompile, bool isRenderThread, bool showInOverlay); + +}; \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.cpp new file mode 100644 index 00000000..ddf5e273 --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.cpp @@ -0,0 +1,433 @@ +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.h" +#include "Cafe/HW/Latte/Core/LatteShader.h" +#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" +#include "Cafe/HW/Latte/Core/LatteCachedFBO.h" +#include "Cafe/OS/libs/gx2/GX2.h" +#include "config/ActiveSettings.h" +#include "util/helpers/Serializer.h" +#include "Cafe/HW/Latte/Common/RegisterSerializer.h" +#include "Cemu/FileCache/FileCache.h" +#include "Cafe/HW/Latte/Core/LatteShaderCache.h" +#include "util/helpers/helpers.h" +#include <openssl/sha.h> + +struct +{ + uint32 pipelineLoadIndex; + uint32 pipelineMaxFileIndex; + + std::atomic_uint32_t pipelinesQueued; + std::atomic_uint32_t pipelinesLoaded; +}g_vkCacheState; + +VulkanPipelineStableCache g_vkPipelineStableCacheInstance; + +VulkanPipelineStableCache& VulkanPipelineStableCache::GetInstance() +{ + return g_vkPipelineStableCacheInstance; +} + +uint32 VulkanPipelineStableCache::BeginLoading(uint64 cacheTitleId) +{ + std::error_code ec; + fs::create_directories(ActiveSettings::GetPath("shaderCache/transferable"), ec); + const auto pathCacheFile = ActiveSettings::GetPath("shaderCache/transferable/{:016x}_vkpipeline.bin", cacheTitleId); + // init cache loader state + g_vkCacheState.pipelineLoadIndex = 0; + g_vkCacheState.pipelineMaxFileIndex = 0; + g_vkCacheState.pipelinesLoaded = 0; + g_vkCacheState.pipelinesQueued = 0; + // start async compilation threads + m_compilationCount.store(0); + m_compilationQueue.clear(); + uint32 cpuCoreCount = GetPhysicalCoreCount(); + m_numCompilationThreads = std::clamp(cpuCoreCount, 1u, 8u); + if (g_renderer->GetVendor() == GfxVendor::Nvidia) + { + forceLog_printf("Disable multi-threaded pipeline loading due to an issue with Nvidia drivers"); + m_numCompilationThreads = 1; + } + for (uint32 i = 0; i < m_numCompilationThreads; i++) + { + std::thread compileThread(&VulkanPipelineStableCache::CompilerThread, this); + compileThread.detach(); + } + // open cache file or create it + cemu_assert_debug(s_cache == nullptr); + const uint32 cacheFileVersion = 1; + s_cache = FileCache::Open(pathCacheFile.generic_wstring(), true, LatteShaderCache_getPipelineCacheExtraVersion(cacheTitleId)); + if (!s_cache) + { + cemuLog_log(LogType::Force, "Failed to open or create Vulkan pipeline cache file: {}", pathCacheFile.generic_string()); + return 0; + } + else + { + s_cache->UseCompression(false); + g_vkCacheState.pipelineMaxFileIndex = s_cache->GetMaximumFileIndex(); + } + return s_cache->GetFileCount(); +} + +bool VulkanPipelineStableCache::UpdateLoading(uint32& pipelinesLoadedTotal, uint32& pipelinesMissingShaders) +{ + pipelinesLoadedTotal = g_vkCacheState.pipelinesLoaded; + pipelinesMissingShaders = 0; + while (g_vkCacheState.pipelineLoadIndex <= g_vkCacheState.pipelineMaxFileIndex) + { + if (m_compilationQueue.size() >= 50) + { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + return true; // queue up to 50 entries at a time + } + + uint64 fileNameA, fileNameB; + std::vector<uint8> fileData; + if (s_cache->GetFileByIndex(g_vkCacheState.pipelineLoadIndex, &fileNameA, &fileNameB, fileData)) + { + // queue for async compilation + g_vkCacheState.pipelinesQueued++; + m_compilationQueue.push(std::move(fileData)); + g_vkCacheState.pipelineLoadIndex++; + return true; + } + g_vkCacheState.pipelineLoadIndex++; + } + if (g_vkCacheState.pipelinesLoaded != g_vkCacheState.pipelinesQueued) + { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + return true; // pipelines still compiling + } + return false; // done +} + +void VulkanPipelineStableCache::EndLoading() +{ + // shut down compilation threads + uint32 threadCount = m_numCompilationThreads; + m_numCompilationThreads = 0; // signal thread shutdown + for (uint32 i = 0; i < threadCount; i++) + { + m_compilationQueue.push({}); // push empty workload for every thread. Threads then will shutdown after checking for m_numCompilationThreads == 0 + } + // keep cache file open for writing of new pipelines +} + +struct CachedPipeline +{ + struct ShaderHash + { + uint64 baseHash; + uint64 auxHash; + bool isPresent{}; + + void set(uint64 baseHash, uint64 auxHash) + { + this->baseHash = baseHash; + this->auxHash = auxHash; + this->isPresent = true; + } + }; + + ShaderHash vsHash; // includes fetch shader + ShaderHash gsHash; + ShaderHash psHash; + + Latte::GPUCompactedRegisterState gpuState; +}; + +VkFormat __getColorBufferVkFormat(const uint32 index, const LatteContextRegister& lcr) +{ + Latte::E_GX2SURFFMT colorBufferFormat = LatteMRT::GetColorBufferFormat(index, lcr); + VulkanRenderer::FormatInfoVK texFormatInfo; + VulkanRenderer::GetInstance()->GetTextureFormatInfoVK(colorBufferFormat, false, Latte::E_DIM::DIM_2D, 1280, 720, &texFormatInfo); + return texFormatInfo.vkImageFormat; +} + +void __getDepthBufferVkFormat(const LatteContextRegister& lcr, VkFormat& dbFormat, bool& hasStencil) +{ + Latte::E_GX2SURFFMT format = LatteMRT::GetDepthBufferFormat(lcr); + VulkanRenderer::FormatInfoVK texFormatInfo; + VulkanRenderer::GetInstance()->GetTextureFormatInfoVK(format, true, Latte::E_DIM::DIM_2D, 1280, 720, &texFormatInfo); + dbFormat = texFormatInfo.vkImageFormat; + hasStencil = (texFormatInfo.vkImageAspect & VK_IMAGE_ASPECT_STENCIL_BIT) != 0; +} + +// create placeholder renderpass for cached pipeline +VKRObjectRenderPass* __CreateTemporaryRenderPass(const LatteDecompilerShader* pixelShader, const LatteContextRegister& lcr) +{ + VKRObjectRenderPass::AttachmentInfo_t attachmentInfo; + + uint8 cbMask = LatteMRT::GetActiveColorBufferMask(pixelShader, lcr); + bool dbMask = LatteMRT::GetActiveDepthBufferMask(lcr); + + for (int i = 0; i < 8; ++i) + { + if ((cbMask & (1 << i)) == 0) + { + attachmentInfo.colorAttachment[i].viewObj = nullptr; + continue; + } + // setup color attachment + attachmentInfo.colorAttachment[i].viewObj = nullptr; + attachmentInfo.colorAttachment[i].isPresent = true; + attachmentInfo.colorAttachment[i].format = __getColorBufferVkFormat(i, lcr); + } + + // setup depth attachment + if (dbMask) + { + attachmentInfo.depthAttachment.viewObj = nullptr; + attachmentInfo.depthAttachment.isPresent = true; + VkFormat dbFormat; + bool hasStencil; + __getDepthBufferVkFormat(lcr, dbFormat, hasStencil); + attachmentInfo.depthAttachment.format = dbFormat; + attachmentInfo.depthAttachment.hasStencil = hasStencil; + } + else + { + // no depth attachment + attachmentInfo.depthAttachment.viewObj = nullptr; + attachmentInfo.depthAttachment.isPresent = false; + } + + return new VKRObjectRenderPass(attachmentInfo); +} + +void VulkanPipelineStableCache::LoadPipelineFromCache(std::span<uint8> fileData) +{ + static FSpinlock s_spinlockSharedInternal; + + // deserialize file + LatteContextRegister* lcr = new LatteContextRegister(); + s_spinlockSharedInternal.acquire(); + CachedPipeline* cachedPipeline = new CachedPipeline(); + s_spinlockSharedInternal.release(); + + MemStreamReader streamReader(fileData.data(), fileData.size()); + if (!DeserializePipeline(streamReader, *cachedPipeline)) + { + // failed to deserialize + return; + } + // restored register view from compacted state + Latte::LoadGPURegisterState(*lcr, cachedPipeline->gpuState); + + LatteDecompilerShader* vertexShader = nullptr; + LatteDecompilerShader* geometryShader = nullptr; + LatteDecompilerShader* pixelShader = nullptr; + // find vertex shader + if (cachedPipeline->vsHash.isPresent) + { + vertexShader = LatteSHRC_FindVertexShader(cachedPipeline->vsHash.baseHash, cachedPipeline->vsHash.auxHash); + if (!vertexShader) + { + forceLogDebug_printf("Vertex shader not found in cache"); + return; + } + } + // find geometry shader + if (cachedPipeline->gsHash.isPresent) + { + geometryShader = LatteSHRC_FindGeometryShader(cachedPipeline->gsHash.baseHash, cachedPipeline->gsHash.auxHash); + if (!geometryShader) + { + forceLogDebug_printf("Geometry shader not found in cache"); + return; + } + } + // find pixel shader + if (cachedPipeline->psHash.isPresent) + { + pixelShader = LatteSHRC_FindPixelShader(cachedPipeline->psHash.baseHash, cachedPipeline->psHash.auxHash); + if (!pixelShader) + { + forceLogDebug_printf("Pixel shader not found in cache"); + return; + } + } + // create temporary renderpass + if (!pixelShader) + { + cemu_assert_debug(false); + return; + } + auto renderPass = __CreateTemporaryRenderPass(pixelShader, *lcr); + // create pipeline info + m_pipelineIsCachedLock.acquire(); + PipelineInfo* pipelineInfo = new PipelineInfo(0, 0, vertexShader->compatibleFetchShader, vertexShader, pixelShader, geometryShader); + m_pipelineIsCachedLock.release(); + // compile + { + PipelineCompiler pp; + if (!pp.InitFromCurrentGPUState(pipelineInfo, *lcr, renderPass)) + { + s_spinlockSharedInternal.acquire(); + delete lcr; + delete cachedPipeline; + s_spinlockSharedInternal.release(); + return; + } + pp.Compile(true, true, false); + // destroy pp early + } + // on success, calculate pipeline hash and flag as present in cache + uint64 pipelineBaseHash = vertexShader->baseHash; + uint64 pipelineStateHash = VulkanRenderer::draw_calculateGraphicsPipelineHash(vertexShader->compatibleFetchShader, vertexShader, geometryShader, pixelShader, renderPass, *lcr); + m_pipelineIsCachedLock.acquire(); + m_pipelineIsCached.emplace(pipelineBaseHash, pipelineStateHash); + m_pipelineIsCachedLock.release(); + // clean up + s_spinlockSharedInternal.acquire(); + delete pipelineInfo; + delete lcr; + delete cachedPipeline; + VulkanRenderer::GetInstance()->releaseDestructibleObject(renderPass); + s_spinlockSharedInternal.release(); +} + +bool VulkanPipelineStableCache::HasPipelineCached(uint64 baseHash, uint64 pipelineStateHash) +{ + PipelineHash ph(baseHash, pipelineStateHash); + return m_pipelineIsCached.find(ph) != m_pipelineIsCached.end(); +} + +ConcurrentQueue<CachedPipeline*> g_pipelineCachingQueue; + +void VulkanPipelineStableCache::AddCurrentStateToCache(uint64 baseHash, uint64 pipelineStateHash) +{ + m_pipelineIsCached.emplace(baseHash, pipelineStateHash); + if (!m_pipelineCacheStoreThread) + { + m_pipelineCacheStoreThread = new std::thread(&VulkanPipelineStableCache::WorkerThread, this); + m_pipelineCacheStoreThread->detach(); + } + // fill job structure with cached GPU state + // for each cached pipeline we store: + // - Active shaders (referenced by hash) + // - An almost-complete register state of the GPU (minus some ALU uniform constants which aren't relevant) + CachedPipeline* job = new CachedPipeline(); + auto vs = LatteSHRC_GetActiveVertexShader(); + auto gs = LatteSHRC_GetActiveGeometryShader(); + auto ps = LatteSHRC_GetActivePixelShader(); + if (vs) + job->vsHash.set(vs->baseHash, vs->auxHash); + if (gs) + job->gsHash.set(gs->baseHash, gs->auxHash); + if (ps) + job->psHash.set(ps->baseHash, ps->auxHash); + Latte::StoreGPURegisterState(LatteGPUState.contextNew, job->gpuState); + // queue job + g_pipelineCachingQueue.push(job); +} + +bool VulkanPipelineStableCache::SerializePipeline(MemStreamWriter& memWriter, CachedPipeline& cachedPipeline) +{ + memWriter.writeBE<uint8>(0x01); // version + uint8 presentMask = 0; + if (cachedPipeline.vsHash.isPresent) + presentMask |= 1; + if (cachedPipeline.gsHash.isPresent) + presentMask |= 2; + if (cachedPipeline.psHash.isPresent) + presentMask |= 4; + memWriter.writeBE<uint8>(presentMask); + if (cachedPipeline.vsHash.isPresent) + { + memWriter.writeBE<uint64>(cachedPipeline.vsHash.baseHash); + memWriter.writeBE<uint64>(cachedPipeline.vsHash.auxHash); + } + if (cachedPipeline.gsHash.isPresent) + { + memWriter.writeBE<uint64>(cachedPipeline.gsHash.baseHash); + memWriter.writeBE<uint64>(cachedPipeline.gsHash.auxHash); + } + if (cachedPipeline.psHash.isPresent) + { + memWriter.writeBE<uint64>(cachedPipeline.psHash.baseHash); + memWriter.writeBE<uint64>(cachedPipeline.psHash.auxHash); + } + Latte::SerializeRegisterState(cachedPipeline.gpuState, memWriter); + return true; +} + +bool VulkanPipelineStableCache::DeserializePipeline(MemStreamReader& memReader, CachedPipeline& cachedPipeline) +{ + // version + if (memReader.readBE<uint8>() != 1) + { + cemuLog_log(LogType::Force, "Cached Vulkan pipeline corrupted or has unknown version"); + return false; + } + // shader hashes + uint8 presentMask = memReader.readBE<uint8>(); + if (presentMask & 1) + { + uint64 baseHash = memReader.readBE<uint64>(); + uint64 auxHash = memReader.readBE<uint64>(); + cachedPipeline.vsHash.set(baseHash, auxHash); + } + if (presentMask & 2) + { + uint64 baseHash = memReader.readBE<uint64>(); + uint64 auxHash = memReader.readBE<uint64>(); + cachedPipeline.gsHash.set(baseHash, auxHash); + } + if (presentMask & 4) + { + uint64 baseHash = memReader.readBE<uint64>(); + uint64 auxHash = memReader.readBE<uint64>(); + cachedPipeline.psHash.set(baseHash, auxHash); + } + // deserialize GPU state + if (!Latte::DeserializeRegisterState(cachedPipeline.gpuState, memReader)) + { + return false; + } + cemu_assert_debug(!memReader.hasError()); + return true; +} + +int VulkanPipelineStableCache::CompilerThread() +{ + while (m_numCompilationThreads != 0) + { + std::vector<uint8> pipelineData = m_compilationQueue.pop(); + if(pipelineData.empty()) + continue; + LoadPipelineFromCache(pipelineData); + ++g_vkCacheState.pipelinesLoaded; + } + return 0; +} + +void VulkanPipelineStableCache::WorkerThread() +{ + while (true) + { + CachedPipeline* job; + g_pipelineCachingQueue.pop(job); + if (!s_cache) + { + delete job; + continue; + } + // serialize + MemStreamWriter memWriter(1024 * 4); + SerializePipeline(memWriter, *job); + auto blob = memWriter.getResult(); + // file name is derived from data hash + uint8 hash[256 / 8]; + SHA256_CTX sha256; + SHA256_Init(&sha256); + SHA256_Update(&sha256, blob.data(), blob.size()); + SHA256_Final(hash, &sha256); + uint64 nameA = *(uint64be*)(hash + 0); + uint64 nameB = *(uint64be*)(hash + 8); + s_cache->AddFileAsync({ nameA, nameB }, blob.data(), blob.size()); + delete job; + } +} \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.h new file mode 100644 index 00000000..dbf64614 --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.h @@ -0,0 +1,65 @@ +#pragma once +#include "util/helpers/fspinlock.h" + +struct VulkanPipelineHash +{ + VulkanPipelineHash() {}; + VulkanPipelineHash(uint64 h0, uint64 h1) : h0(h0), h1(h1) {}; + + uint64 h0; + uint64 h1; +}; + +class VulkanPipelineStableCache +{ + struct PipelineHash + { + PipelineHash(uint64 h0, uint64 h1) : h0(h0), h1(h1) {}; + + uint64 h0; + uint64 h1; + + bool operator==(const PipelineHash& r) const + { + return h0 == r.h0 && h1 == r.h1; + } + + struct HashFunc + { + size_t operator()(const PipelineHash& v) const + { + static_assert(sizeof(uint64) == sizeof(size_t)); + return v.h0 ^ v.h1; + } + }; + }; + +public: + static VulkanPipelineStableCache& GetInstance(); + + uint32 BeginLoading(uint64 cacheTitleId); // returns count of pipelines stored in cache + bool UpdateLoading(uint32& pipelinesLoadedTotal, uint32& pipelinesMissingShaders); + void EndLoading(); + void LoadPipelineFromCache(std::span<uint8> fileData); + + bool HasPipelineCached(uint64 baseHash, uint64 pipelineStateHash); + void AddCurrentStateToCache(uint64 baseHash, uint64 pipelineStateHash); + + // pipeline serialization for file + bool SerializePipeline(class MemStreamWriter& memWriter, struct CachedPipeline& cachedPipeline); + bool DeserializePipeline(class MemStreamReader& memReader, struct CachedPipeline& cachedPipeline); + +private: + int CompilerThread(); + void WorkerThread(); + + std::thread* m_pipelineCacheStoreThread; + + std::unordered_set<PipelineHash, PipelineHash::HashFunc> m_pipelineIsCached; + FSpinlock m_pipelineIsCachedLock; + class FileCache* s_cache; + + std::atomic_uint32_t m_numCompilationThreads{ 0 }; + ConcurrentQueue<std::vector<uint8>> m_compilationQueue; + std::atomic_uint32_t m_compilationCount; +}; \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanQuery.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanQuery.cpp new file mode 100644 index 00000000..18e5e407 --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanQuery.cpp @@ -0,0 +1,212 @@ +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" + +class LatteQueryObjectVk : public LatteQueryObject +{ + friend class VulkanRenderer; + + LatteQueryObjectVk(VulkanRenderer* rendererVk) : m_rendererVk(rendererVk) + { + + }; + + bool getResult(uint64& numSamplesPassed) override; + void begin() override; + void end() override; + void beginFragment(); + void endFragment(); + void handleFinishedFragments(); + + uint32 acquireQueryIndex(); + void releaseQueryIndex(uint32 queryIndex); + +private: + struct queryFragment + { + uint32 queryIndex; + uint64 m_finishCommandBuffer; + bool isFinished; + }; + + VulkanRenderer* m_rendererVk; + //sint32 m_queryIndex; + std::vector<queryFragment> list_queryFragments; + bool m_vkQueryEnded{}; + bool m_hasActiveQuery{}; + bool m_hasActiveFragment{}; + uint64 m_finishCommandBuffer; + uint64 m_acccumulatedSum; +}; + +bool LatteQueryObjectVk::getResult(uint64& numSamplesPassed) +{ + if (!m_vkQueryEnded) + return false; + if (!m_rendererVk->HasCommandBufferFinished(m_finishCommandBuffer)) + return false; + handleFinishedFragments(); + cemu_assert_debug(list_queryFragments.empty()); + numSamplesPassed = m_acccumulatedSum; + //numSamplesPassed = m_rendererVk->m_occlusionQueries.ptrQueryResults[m_queryIndex]; + return true; +} + +void LatteQueryObjectVk::beginFragment() +{ + m_rendererVk->draw_endRenderPass(); + + handleFinishedFragments(); + uint32 newQueryIndex = acquireQueryIndex(); + + queryFragment qf{}; + qf.queryIndex = newQueryIndex; + qf.isFinished = false; + qf.m_finishCommandBuffer = 0; + list_queryFragments.emplace_back(qf); + + + vkCmdResetQueryPool(m_rendererVk->m_state.currentCommandBuffer, m_rendererVk->m_occlusionQueries.queryPool, newQueryIndex, 1); + vkCmdBeginQuery(m_rendererVk->m_state.currentCommandBuffer, m_rendererVk->m_occlusionQueries.queryPool, newQueryIndex, VK_QUERY_CONTROL_PRECISE_BIT); + // todo - we already synchronize with command buffers, should we also set wait bits? + + m_hasActiveFragment = true; +} + +void LatteQueryObjectVk::begin() +{ + m_vkQueryEnded = false; + m_hasActiveQuery = true; + beginFragment(); +} + +void LatteQueryObjectVk::endFragment() +{ + m_rendererVk->draw_endRenderPass(); + + cemu_assert_debug(m_hasActiveFragment); + uint32 queryIndex = list_queryFragments.back().queryIndex; + vkCmdEndQuery(m_rendererVk->m_state.currentCommandBuffer, m_rendererVk->m_occlusionQueries.queryPool, queryIndex); + + vkCmdCopyQueryPoolResults(m_rendererVk->m_state.currentCommandBuffer, m_rendererVk->m_occlusionQueries.queryPool, queryIndex, 1, m_rendererVk->m_occlusionQueries.bufferQueryResults, queryIndex * sizeof(uint64), 8, VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT); + list_queryFragments.back().m_finishCommandBuffer = m_rendererVk->GetCurrentCommandBufferId(); + list_queryFragments.back().isFinished = true; + m_hasActiveFragment = false; +} + +void LatteQueryObjectVk::handleFinishedFragments() +{ + // remove finished fragments and add to m_acccumulatedSum + while (!list_queryFragments.empty()) + { + auto& it = list_queryFragments.front(); + if (!it.isFinished) + break; + if (!m_rendererVk->HasCommandBufferFinished(it.m_finishCommandBuffer)) + break; + m_acccumulatedSum += m_rendererVk->m_occlusionQueries.ptrQueryResults[it.queryIndex]; + releaseQueryIndex(it.queryIndex); + list_queryFragments.erase(list_queryFragments.begin()); + } +} + +uint32 LatteQueryObjectVk::acquireQueryIndex() +{ + if (m_rendererVk->m_occlusionQueries.list_availableQueryIndices.empty()) + { + forceLog_printf("Vulkan-Error: Exhausted query pool"); + assert_dbg(); + } + uint32 queryIndex = m_rendererVk->m_occlusionQueries.list_availableQueryIndices.back(); + m_rendererVk->m_occlusionQueries.list_availableQueryIndices.pop_back(); + return queryIndex; +} + +void LatteQueryObjectVk::releaseQueryIndex(uint32 queryIndex) +{ + m_rendererVk->m_occlusionQueries.list_availableQueryIndices.emplace_back(queryIndex); +} + +void LatteQueryObjectVk::end() +{ + cemu_assert_debug(!list_queryFragments.empty()); + if(m_hasActiveFragment) + endFragment(); + m_vkQueryEnded = true; + m_hasActiveQuery = false; + m_finishCommandBuffer = m_rendererVk->GetCurrentCommandBufferId(); + m_rendererVk->m_occlusionQueries.m_lastCommandBuffer = m_finishCommandBuffer; + m_rendererVk->RequestSubmitSoon(); // make sure the current command buffer gets submitted soon + m_rendererVk->RequestSubmitOnIdle(); +} + +LatteQueryObject* VulkanRenderer::occlusionQuery_create() +{ + // create query pool if it doesn't already exist + if(m_occlusionQueries.queryPool == VK_NULL_HANDLE) + { + VkQueryPoolCreateInfo queryPoolCreateInfo{}; + queryPoolCreateInfo.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO; + queryPoolCreateInfo.flags = 0; + queryPoolCreateInfo.queryType = VK_QUERY_TYPE_OCCLUSION; + queryPoolCreateInfo.queryCount = OCCLUSION_QUERY_POOL_SIZE; + queryPoolCreateInfo.pipelineStatistics = 0; + auto r = vkCreateQueryPool(m_logicalDevice, &queryPoolCreateInfo, nullptr, &m_occlusionQueries.queryPool); + if (r != VK_SUCCESS) + { + forceLog_printf("Vulkan-Error: Failed to create query pool with error %d", (sint32)r); + return nullptr; + } + } + LatteQueryObjectVk* queryObjVk = nullptr; + if (m_occlusionQueries.list_cachedQueries.empty()) + { + queryObjVk = new LatteQueryObjectVk(this); + } + else + { + queryObjVk = m_occlusionQueries.list_cachedQueries.front(); + m_occlusionQueries.list_cachedQueries.erase(m_occlusionQueries.list_cachedQueries.begin()+0); + } + queryObjVk->queryEnded = false; + queryObjVk->queryEventStart = 0; + queryObjVk->queryEventEnd = 0; + queryObjVk->m_vkQueryEnded = false; + queryObjVk->m_acccumulatedSum = 0; + cemu_assert_debug(queryObjVk->list_queryFragments.empty()); // query fragment list should always be cleared in _destroy() + m_occlusionQueries.list_currentlyActiveQueries.emplace_back(queryObjVk); + return queryObjVk; +} + +void VulkanRenderer::occlusionQuery_destroy(LatteQueryObject* queryObj) +{ + LatteQueryObjectVk* queryObjVk = static_cast<LatteQueryObjectVk*>(queryObj); + m_occlusionQueries.list_currentlyActiveQueries.erase(std::remove(m_occlusionQueries.list_currentlyActiveQueries.begin(), m_occlusionQueries.list_currentlyActiveQueries.end(), queryObj), m_occlusionQueries.list_currentlyActiveQueries.end()); + m_occlusionQueries.list_cachedQueries.emplace_back(queryObjVk); + for (auto& it : queryObjVk->list_queryFragments) + queryObjVk->releaseQueryIndex(it.queryIndex); + queryObjVk->list_queryFragments.clear(); +} + +void VulkanRenderer::occlusionQuery_flush() +{ + WaitCommandBufferFinished(m_occlusionQueries.m_lastCommandBuffer); +} + +void VulkanRenderer::occlusionQuery_updateState() +{ + // check for finished command buffers here since query states are tied to buffers + ProcessFinishedCommandBuffers(); +} + +void VulkanRenderer::occlusionQuery_notifyEndCommandBuffer() +{ + for (auto& it : m_occlusionQueries.list_currentlyActiveQueries) + if(it->m_hasActiveQuery) + it->endFragment(); +} + +void VulkanRenderer::occlusionQuery_notifyBeginCommandBuffer() +{ + for (auto& it : m_occlusionQueries.list_currentlyActiveQueries) + if (it->m_hasActiveQuery) + it->beginFragment(); +} \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp new file mode 100644 index 00000000..a792fc69 --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -0,0 +1,4214 @@ +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanTextureReadback.h" + +#include "Cafe/HW/Latte/Core/LatteBufferCache.h" +#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" + +#include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h" + +#include "Cafe/CafeSystem.h" + +#include "util/helpers/helpers.h" + +#include "config/ActiveSettings.h" +#include "config/CemuConfig.h" +#include "gui/guiWrapper.h" + +#include "imgui/imgui_extension.h" +#include "imgui/imgui_impl_vulkan.h" + +#include "Cafe/TitleList/GameInfo.h" + +#include "Cafe/HW/Latte/Core/LatteTiming.h" // vsync control + +#include <glslang/Include/Types.h> + +#include <wx/msgdlg.h> + +#define VK_API_VERSION_MAJOR(version) (((uint32_t)(version) >> 22) & 0x7FU) +#define VK_API_VERSION_MINOR(version) (((uint32_t)(version) >> 12) & 0x3FFU) + +extern std::atomic_int g_compiling_pipelines; + +const std::vector<const char*> kOptionalDeviceExtensions = +{ + //VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME, + VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME, + VK_NV_FILL_RECTANGLE_EXTENSION_NAME, + VK_EXT_PIPELINE_CREATION_FEEDBACK_EXTENSION_NAME, + VK_EXT_FILTER_CUBIC_EXTENSION_NAME, // not supported by any device yet + VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME, + VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME, +}; + +const std::vector<const char*> kRequiredDeviceExtensions = +{ + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + VK_KHR_SAMPLER_MIRROR_CLAMP_TO_EDGE_EXTENSION_NAME +}; // Intel doesnt support VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME + +VKAPI_ATTR VkBool32 VKAPI_CALL DebugUtilsCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageTypes, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) +{ +#ifndef PUBLIC_RELEASE + + if (strstr(pCallbackData->pMessage, "consumes input location")) + return VK_FALSE; // false means we dont care + if (strstr(pCallbackData->pMessage, "blend")) + return VK_FALSE; // + + // note: Check if previously used location in VK_EXT_debug_report callback is the same as messageIdNumber under the new extension + // validation errors which are difficult to fix + if (pCallbackData->messageIdNumber == 0x6c3b517c || pCallbackData->messageIdNumber == 0xffffffffa6b17cdf || pCallbackData->messageIdNumber == 0xffffffffc406fcb7) + return VK_FALSE; // its illegal to render to and sample from same texture + if (pCallbackData->messageIdNumber == 0x6e633069) + return VK_FALSE; // framebuffer attachments should have identity swizzle + if (pCallbackData->messageIdNumber == 0xffffffffb408bc0b) + return VK_FALSE; // too many samplers + + if (pCallbackData->messageIdNumber == 0x6bbb14) + return VK_FALSE; // SPIR-V inconsistency + + if (strstr(pCallbackData->pMessage, "Number of currently valid sampler objects is not less than the maximum allowed")) + return VK_FALSE; + + assert_dbg(); + +#endif + + cafeLog_logLine(LOG_TYPE_FORCE, (char*)pCallbackData->pMessage); + + return VK_FALSE; +} + +std::vector<VulkanRenderer::DeviceInfo> VulkanRenderer::GetDevices() +{ + if(!vkEnumerateInstanceVersion) + { + cemuLog_log(LogType::Force, "Vulkan cant list devices because Vulkan loader failed"); + return {}; + } + uint32 apiVersion = VK_API_VERSION_1_1; + if (vkEnumerateInstanceVersion(&apiVersion) != VK_SUCCESS) + { + if (VK_API_VERSION_MAJOR(apiVersion) < 1 || VK_API_VERSION_MINOR(apiVersion) < 2) + apiVersion = VK_API_VERSION_1_1; + } + + std::vector<DeviceInfo> result; + + std::vector<const char*> requiredExtensions; + requiredExtensions.clear(); + requiredExtensions.emplace_back(VK_KHR_SURFACE_EXTENSION_NAME); + #if BOOST_OS_WINDOWS + requiredExtensions.emplace_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME); + #else + requiredExtensions.emplace_back(VK_KHR_XLIB_SURFACE_EXTENSION_NAME); + #endif + + VkApplicationInfo app_info{}; + app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + app_info.pApplicationName = EMULATOR_NAME; + app_info.applicationVersion = VK_MAKE_VERSION(EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR); + app_info.pEngineName = EMULATOR_NAME; + app_info.engineVersion = app_info.applicationVersion; + app_info.apiVersion = apiVersion; + + VkInstanceCreateInfo create_info{}; + create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + create_info.pApplicationInfo = &app_info; + create_info.ppEnabledExtensionNames = requiredExtensions.data(); + create_info.enabledExtensionCount = requiredExtensions.size(); + create_info.ppEnabledLayerNames = nullptr; + create_info.enabledLayerCount = 0; + + VkInstance instance = nullptr; + try + { + VkResult err; + if ((err = vkCreateInstance(&create_info, nullptr, &instance)) != VK_SUCCESS) + throw std::runtime_error(fmt::format("Unable to create a Vulkan instance: {}", err)); + + if (!InitializeInstanceVulkan(instance)) + throw std::runtime_error("can't initialize instanced vulkan functions"); + + uint32_t device_count = 0; + vkEnumeratePhysicalDevices(instance, &device_count, nullptr); + if (device_count == 0) + throw std::runtime_error("Failed to find a GPU with Vulkan support."); + + // create tmp surface to create a logical device + auto surface = CreateFramebufferSurface(instance, gui_getWindowInfo().window_main); + std::vector<VkPhysicalDevice> devices(device_count); + vkEnumeratePhysicalDevices(instance, &device_count, devices.data()); + for (const auto& device : devices) + { + if (IsDeviceSuitable(surface, device)) + { + VkPhysicalDeviceIDProperties physDeviceIDProps = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES }; + VkPhysicalDeviceProperties2 physDeviceProps = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2 }; + physDeviceProps.pNext = &physDeviceIDProps; + vkGetPhysicalDeviceProperties2(device, &physDeviceProps); + + result.emplace_back(physDeviceProps.properties.deviceName, physDeviceIDProps.deviceUUID); + } + } + } + catch (...) + { + } + + if (instance) + vkDestroyInstance(instance, nullptr); + + return result; + +} + +bool IsRunningInWine(); +void VulkanRenderer::DetermineVendor() +{ + VkPhysicalDeviceProperties2 properties{}; + VkPhysicalDeviceDriverProperties driverProperties{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES }; + properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + if (m_featureControl.deviceExtensions.driver_properties) + properties.pNext = &driverProperties; + + vkGetPhysicalDeviceProperties2(m_physical_device, &properties); + switch (properties.properties.vendorID) + { + case 0x10DE: + m_vendor = GfxVendor::Nvidia; + break; + case 0x8086: // iGPU + m_vendor = GfxVendor::Intel; + break; + case 0x1002: + m_vendor = GfxVendor::AMD; + break; + } + + if (IsRunningInWine()) + m_vendor = GfxVendor::Mesa; + + forceLog_printf("Using GPU: %s", properties.properties.deviceName); + if (m_featureControl.deviceExtensions.driver_properties) + forceLog_printf("Driver version: %s", driverProperties.driverInfo) + else + forceLog_printf("Driver version (as stored in device info): %08X", properties.properties.driverVersion); +} + +void VulkanRenderer::GetDeviceFeatures() +{ + VkPhysicalDeviceCustomBorderColorFeaturesEXT bcf{}; + bcf.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUSTOM_BORDER_COLOR_FEATURES_EXT; + + VkPhysicalDevicePipelineCreationCacheControlFeaturesEXT pcc{}; + pcc.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_CREATION_CACHE_CONTROL_FEATURES_EXT; + pcc.pNext = &bcf; + + VkPhysicalDeviceFeatures2 physicalDeviceFeatures2{}; + physicalDeviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + physicalDeviceFeatures2.pNext = &pcc; + + vkGetPhysicalDeviceFeatures2(m_physical_device, &physicalDeviceFeatures2); + + m_featureControl.deviceExtensions.pipeline_creation_cache_control = pcc.pipelineCreationCacheControl; + m_featureControl.deviceExtensions.custom_border_color_without_format = m_featureControl.deviceExtensions.custom_border_color && bcf.customBorderColorWithoutFormat; + + if (!m_featureControl.deviceExtensions.pipeline_creation_cache_control) + { + forceLogDebug_printf("VK_EXT_pipeline_creation_cache_control not supported. Cannot use asynchronous shader and pipeline compilation"); + // if async shader compilation is enabled show warning message + if (GetConfig().async_compile) + wxMessageBox(_("The currently installed graphics driver does not support the Vulkan extension necessary for asynchronous shader compilation. Asynchronous compilation cannot be used.\n \nRequired extension: VK_EXT_pipeline_creation_cache_control\n\nInstalling the latest graphics driver may solve this error."), _("Information"), wxOK | wxCENTRE); + } + if (!m_featureControl.deviceExtensions.custom_border_color_without_format) + { + if (m_featureControl.deviceExtensions.custom_border_color) + { + forceLog_printf("VK_EXT_custom_border_color is present but only with limited support. Cannot emulate arbitrary border color"); + } + else + { + forceLog_printf("VK_EXT_custom_border_color not supported. Cannot emulate arbitrary border color"); + } + } + // retrieve limits + VkPhysicalDeviceProperties2 p2{}; + p2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + vkGetPhysicalDeviceProperties2(m_physical_device, &p2); + m_featureControl.limits.minUniformBufferOffsetAlignment = std::max(p2.properties.limits.minUniformBufferOffsetAlignment, (VkDeviceSize)4); + m_featureControl.limits.nonCoherentAtomSize = std::max(p2.properties.limits.nonCoherentAtomSize, (VkDeviceSize)4); + cemuLog_log(LogType::Force, fmt::format("VulkanLimits: UBAlignment {0} nonCoherentAtomSize {1}", p2.properties.limits.minUniformBufferOffsetAlignment, p2.properties.limits.nonCoherentAtomSize)); +} + +VulkanRenderer::VulkanRenderer() +{ + glslang::InitializeProcess(); + + forceLog_printf("------- Init Vulkan graphics backend -------"); + + const bool useValidationLayer = cafeLog_isLoggingFlagEnabled(LOG_TYPE_VULKAN_VALIDATION); + if (useValidationLayer) + forceLog_printf("Validation layer is enabled"); + + VkResult err; + + // build list of layers + m_layerNames.clear(); + if (useValidationLayer) + m_layerNames.emplace_back("VK_LAYER_KHRONOS_validation"); + + // check available instance extensions + std::vector<const char*> enabledInstanceExtensions = CheckInstanceExtensionSupport(m_featureControl); + + uint32 apiVersion = VK_API_VERSION_1_1; + if (vkEnumerateInstanceVersion(&apiVersion) != VK_SUCCESS) + { + if (VK_API_VERSION_MAJOR(apiVersion) < 1 || VK_API_VERSION_MINOR(apiVersion) < 2) + apiVersion = VK_API_VERSION_1_1; + } + + cemuLog_log(LogType::Force, fmt::format("Vulkan instance version: {}.{}", VK_API_VERSION_MAJOR(apiVersion), VK_API_VERSION_MINOR(apiVersion))); + + VkApplicationInfo app_info{}; + app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + app_info.pApplicationName = EMULATOR_NAME; + app_info.applicationVersion = VK_MAKE_VERSION(EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR); + app_info.pEngineName = EMULATOR_NAME; + app_info.engineVersion = app_info.applicationVersion; + app_info.apiVersion = apiVersion; + + VkInstanceCreateInfo create_info{}; + create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + create_info.pApplicationInfo = &app_info; + create_info.ppEnabledExtensionNames = enabledInstanceExtensions.data(); + create_info.enabledExtensionCount = enabledInstanceExtensions.size(); + create_info.ppEnabledLayerNames = m_layerNames.data(); + create_info.enabledLayerCount = m_layerNames.size(); + + if ((err = vkCreateInstance(&create_info, nullptr, &m_instance)) != VK_SUCCESS) + throw std::runtime_error(fmt::format("Unable to create a Vulkan instance: {}", err)); + + if (!InitializeInstanceVulkan(m_instance)) + throw std::runtime_error("Unable to load instanced Vulkan functions"); + + uint32_t device_count = 0; + vkEnumeratePhysicalDevices(m_instance, &device_count, nullptr); + if (device_count == 0) + throw std::runtime_error("Failed to find a GPU with Vulkan support."); + + // create tmp surface to create a logical device + auto surface = CreateFramebufferSurface(m_instance, gui_getWindowInfo().window_main); + + auto& config = GetConfig(); + decltype(config.graphic_device_uuid) zero{}; + const bool has_device_set = config.graphic_device_uuid != zero; + + VkPhysicalDevice fallbackDevice = VK_NULL_HANDLE; + + std::vector<VkPhysicalDevice> devices(device_count); + vkEnumeratePhysicalDevices(m_instance, &device_count, devices.data()); + for (const auto& device : devices) + { + if (IsDeviceSuitable(surface, device)) + { + if (fallbackDevice == VK_NULL_HANDLE) + fallbackDevice = device; + + if (has_device_set) + { + VkPhysicalDeviceIDProperties physDeviceIDProps = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES }; + VkPhysicalDeviceProperties2 physDeviceProps = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2 }; + physDeviceProps.pNext = &physDeviceIDProps; + vkGetPhysicalDeviceProperties2(device, &physDeviceProps); + + if (memcmp(config.graphic_device_uuid.data(), physDeviceIDProps.deviceUUID, VK_UUID_SIZE) != 0) + continue; + } + + m_physical_device = device; + break; + } + } + + if (m_physical_device == VK_NULL_HANDLE && fallbackDevice != VK_NULL_HANDLE) + { + forceLog_printf("The selected GPU could not be found or is not suitable. Falling back to first available device instead"); + m_physical_device = fallbackDevice; + config.graphic_device_uuid = {}; // resetting device selection + } + else if (m_physical_device == VK_NULL_HANDLE) + { + forceLog_printf("No physical GPU could be found with the required extensions and swap chain support."); + throw std::runtime_error("No physical GPU could be found with the required extensions and swap chain support."); + } + + CheckDeviceExtensionSupport(m_physical_device, m_featureControl); // todo - merge this with GetDeviceFeatures and separate from IsDeviceSuitable? + if (m_featureControl.debugMarkersSupported) + forceLog_printf("Debug: Frame debugger attached, will use vkDebugMarkerSetObjectNameEXT"); + + DetermineVendor(); + GetDeviceFeatures(); + + // init memory manager + memoryManager = new VKRMemoryManager(this); + + try + { + VkPhysicalDeviceIDProperties physDeviceIDProps = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES }; + VkPhysicalDeviceProperties2 physDeviceProps = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2 }; + physDeviceProps.pNext = &physDeviceIDProps; + vkGetPhysicalDeviceProperties2(m_physical_device, &physDeviceProps); + + #if BOOST_OS_WINDOWS + m_dxgi_wrapper = std::make_unique<DXGIWrapper>(physDeviceIDProps.deviceLUID); + #endif + } + catch (const std::exception& ex) + { + forceLog_printf("can't create dxgi wrapper: %s", ex.what()); + } + + // create logical device + m_indices = FindQueueFamilies(surface, m_physical_device); + std::set<int> uniqueQueueFamilies = { m_indices.graphicsFamily, m_indices.presentFamily }; + std::vector<VkDeviceQueueCreateInfo> queueCreateInfos = CreateQueueCreateInfos(uniqueQueueFamilies); + VkPhysicalDeviceFeatures deviceFeatures = {}; + + deviceFeatures.independentBlend = VK_TRUE; + deviceFeatures.samplerAnisotropy = VK_TRUE; + deviceFeatures.imageCubeArray = VK_TRUE; + deviceFeatures.geometryShader = VK_TRUE; + deviceFeatures.logicOp = VK_TRUE; + deviceFeatures.occlusionQueryPrecise = VK_TRUE; + deviceFeatures.depthClamp = VK_TRUE; + deviceFeatures.depthBiasClamp = VK_TRUE; + if (m_vendor == GfxVendor::AMD) + { + deviceFeatures.robustBufferAccess = VK_TRUE; + forceLog_printf("Enable robust buffer access"); + } + if (m_featureControl.mode.useTFEmulationViaSSBO) + { + deviceFeatures.vertexPipelineStoresAndAtomics = true; + } + + void* deviceExtensionFeatures = nullptr; + + // enable VK_EXT_pipeline_creation_cache_control + VkPhysicalDevicePipelineCreationCacheControlFeaturesEXT cacheControlFeature{}; + if (m_featureControl.deviceExtensions.pipeline_creation_cache_control) + { + cacheControlFeature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_CREATION_CACHE_CONTROL_FEATURES_EXT; + cacheControlFeature.pNext = deviceExtensionFeatures; + deviceExtensionFeatures = &cacheControlFeature; + cacheControlFeature.pipelineCreationCacheControl = VK_TRUE; + } + // enable VK_EXT_custom_border_color + VkPhysicalDeviceCustomBorderColorFeaturesEXT customBorderColorFeature{}; + if (m_featureControl.deviceExtensions.custom_border_color_without_format) + { + customBorderColorFeature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUSTOM_BORDER_COLOR_FEATURES_EXT; + customBorderColorFeature.pNext = deviceExtensionFeatures; + deviceExtensionFeatures = &customBorderColorFeature; + customBorderColorFeature.customBorderColors = VK_TRUE; + customBorderColorFeature.customBorderColorWithoutFormat = VK_TRUE; + } + + std::vector<const char*> used_extensions; + VkDeviceCreateInfo createInfo = CreateDeviceCreateInfo(queueCreateInfos, deviceFeatures, deviceExtensionFeatures, used_extensions); + + VkResult result = vkCreateDevice(m_physical_device, &createInfo, nullptr, &m_logicalDevice); + if (result != VK_SUCCESS) + { + forceLog_printf("Vulkan: Unable to create a logical device. Error %d", (sint32)result); + throw std::runtime_error(fmt::format("Unable to create a logical device: {}", result)); + } + + InitializeDeviceVulkan(m_logicalDevice); + + vkGetDeviceQueue(m_logicalDevice, m_indices.graphicsFamily, 0, &m_graphicsQueue); + vkGetDeviceQueue(m_logicalDevice, m_indices.graphicsFamily, 0, &m_presentQueue); + + vkDestroySurfaceKHR(m_instance, surface, nullptr); + + if (useValidationLayer && m_featureControl.instanceExtensions.debug_utils) + { + PFN_vkCreateDebugUtilsMessengerEXT vkCreateDebugUtilsMessengerEXT = reinterpret_cast<PFN_vkCreateDebugUtilsMessengerEXT>(vkGetInstanceProcAddr(m_instance, "vkCreateDebugUtilsMessengerEXT")); + + VkDebugUtilsMessengerCreateInfoEXT debugCallback{}; + debugCallback.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT; + debugCallback.pNext = nullptr; + debugCallback.flags = 0; + debugCallback.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT; + debugCallback.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + debugCallback.pfnUserCallback = &DebugUtilsCallback; + + vkCreateDebugUtilsMessengerEXT(m_instance, &debugCallback, nullptr, &m_debugCallback); + } + + if (m_featureControl.instanceExtensions.debug_utils) + forceLog_printf("Using available debug function: vkCreateDebugUtilsMessengerEXT()"); + + // set initial viewport and scissor box size + m_state.currentViewport.width = 4; + m_state.currentViewport.height = 4; + m_state.currentScissorRect.extent.width = 4; + m_state.currentScissorRect.extent.height = 4; + + QueryMemoryInfo(); + QueryAvailableFormats(); + CreateBackbufferIndexBuffer(); + CreateCommandPool(); + CreateCommandBuffers(); + CreateDescriptorPool(); + swapchain_createDescriptorSetLayout(); + + // extension info + // cemuLog_force("VK_KHR_dynamic_rendering: {}", m_featureControl.deviceExtensions.dynamic_rendering?"supported":"not supported"); + + void* bufferPtr; + // init ringbuffer for uniform vars + m_uniformVarBufferMemoryIsCoherent = false; + if (memoryManager->CreateBuffer2(UNIFORMVAR_RINGBUFFER_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT, m_uniformVarBuffer, m_uniformVarBufferMemory)) + m_uniformVarBufferMemoryIsCoherent = true; + else if (memoryManager->CreateBuffer2(UNIFORMVAR_RINGBUFFER_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT | VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, m_uniformVarBuffer, m_uniformVarBufferMemory)) + m_uniformVarBufferMemoryIsCoherent = true; // unified memory + else if (memoryManager->CreateBuffer2(UNIFORMVAR_RINGBUFFER_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, m_uniformVarBuffer, m_uniformVarBufferMemory)) + m_uniformVarBufferMemoryIsCoherent = true; + else + { + memoryManager->CreateBuffer2(UNIFORMVAR_RINGBUFFER_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, m_uniformVarBuffer, m_uniformVarBufferMemory); + } + + if (!m_uniformVarBufferMemoryIsCoherent) + cemuLog_log(LogType::Force, "[Vulkan-Info] Using non-coherent memory for uniform data"); + bufferPtr = nullptr; + vkMapMemory(m_logicalDevice, m_uniformVarBufferMemory, 0, VK_WHOLE_SIZE, 0, &bufferPtr); + m_uniformVarBufferPtr = (uint8*)bufferPtr; + + // texture readback buffer + memoryManager->CreateBuffer(TEXTURE_READBACK_SIZE, VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT, m_textureReadbackBuffer, m_textureReadbackBufferMemory); + bufferPtr = nullptr; + vkMapMemory(m_logicalDevice, m_textureReadbackBufferMemory, 0, VK_WHOLE_SIZE, 0, &bufferPtr); + m_textureReadbackBufferPtr = (uint8*)bufferPtr; + + // transform feedback ringbuffer + memoryManager->CreateBuffer(LatteStreamout_GetRingBufferSize(), VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | (m_featureControl.mode.useTFEmulationViaSSBO ? VK_BUFFER_USAGE_STORAGE_BUFFER_BIT : 0), 0, m_xfbRingBuffer, m_xfbRingBufferMemory); + + // occlusion query result buffer + memoryManager->CreateBuffer(OCCLUSION_QUERY_POOL_SIZE * sizeof(uint64), VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT, m_occlusionQueries.bufferQueryResults, m_occlusionQueries.memoryQueryResults); + bufferPtr = nullptr; + vkMapMemory(m_logicalDevice, m_occlusionQueries.memoryQueryResults, 0, VK_WHOLE_SIZE, 0, &bufferPtr); + m_occlusionQueries.ptrQueryResults = (uint64*)bufferPtr; + + for (sint32 i = 0; i < OCCLUSION_QUERY_POOL_SIZE; i++) + m_occlusionQueries.list_availableQueryIndices.emplace_back(i); + + // enable surface copies via buffer if we have plenty of memory available (otherwise use drawcalls) + size_t availableSurfaceCopyBufferMem = memoryManager->GetTotalMemoryForBufferType(VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + //m_featureControl.mode.useBufferSurfaceCopies = availableSurfaceCopyBufferMem >= 2000ull * 1024ull * 1024ull; // enable if at least 2000MB VRAM + m_featureControl.mode.useBufferSurfaceCopies = false; + + if (m_featureControl.mode.useBufferSurfaceCopies) + { + //forceLog_printf("Enable surface copies via buffer"); + } + else + { + //forceLog_printf("Disable surface copies via buffer (Requires 2GB. Has only %lluMB available)", availableSurfaceCopyBufferMem / 1024ull / 1024ull); + } +} + +VulkanRenderer::~VulkanRenderer() +{ + SubmitCommandBuffer(); + vkDeviceWaitIdle(m_logicalDevice); + WaitCommandBufferFinished(GetCurrentCommandBufferId()); + // shut down pipeline save thread + m_destructionRequested = true; + m_pipeline_cache_semaphore.notify(); + m_pipeline_cache_save_thread.join(); + + // shut down imgui + ImGui_ImplVulkan_Shutdown(); + + // delete null objects + DeleteNullObjects(); + + // delete buffers + memoryManager->DeleteBuffer(m_indexBuffer, m_indexBufferMemory); + memoryManager->DeleteBuffer(m_uniformVarBuffer, m_uniformVarBufferMemory); + memoryManager->DeleteBuffer(m_textureReadbackBuffer, m_textureReadbackBufferMemory); + memoryManager->DeleteBuffer(m_xfbRingBuffer, m_xfbRingBufferMemory); + memoryManager->DeleteBuffer(m_occlusionQueries.bufferQueryResults, m_occlusionQueries.memoryQueryResults); + memoryManager->DeleteBuffer(m_bufferCache, m_bufferCacheMemory); + // texture memory + // todo + // upload buffers + // todo + + m_padSwapchainInfo = nullptr; + m_mainSwapchainInfo = nullptr; + + // clean up resources used for surface copy + surfaceCopy_cleanup(); + + // clean up default shaders + delete defaultShaders.copySurface_vs; + defaultShaders.copySurface_vs = nullptr; + delete defaultShaders.copySurface_psColor2Depth; + defaultShaders.copySurface_psColor2Depth = nullptr; + delete defaultShaders.copySurface_psDepth2Color; + defaultShaders.copySurface_psDepth2Color = nullptr; + + // destroy misc + for (auto& it : m_cmd_buffer_fences) + { + vkDestroyFence(m_logicalDevice, it, nullptr); + it = VK_NULL_HANDLE; + } + + if (m_pipelineLayout != VK_NULL_HANDLE) + vkDestroyPipelineLayout(m_logicalDevice, m_pipelineLayout, nullptr); + + if (m_commandPool != VK_NULL_HANDLE) + vkDestroyCommandPool(m_logicalDevice, m_commandPool, nullptr); + + // destroy debug callback + if (m_debugCallback) + { + PFN_vkDestroyDebugUtilsMessengerEXT vkDestroyDebugUtilsMessengerEXT = reinterpret_cast<PFN_vkDestroyDebugUtilsMessengerEXT>(vkGetInstanceProcAddr(m_instance, "vkDestroyDebugUtilsMessengerEXT")); + vkDestroyDebugUtilsMessengerEXT(m_instance, m_debugCallback, nullptr); + } + + // destroy instance, devices + if (m_instance != VK_NULL_HANDLE) + { + if (m_logicalDevice != VK_NULL_HANDLE) + { + vkDestroyDevice(m_logicalDevice, nullptr); + } + + vkDestroyInstance(m_instance, nullptr); + } + + // destroy memory manager + delete memoryManager; + + // crashes? + //glslang::FinalizeProcess(); +} + +VulkanRenderer* VulkanRenderer::GetInstance() +{ +#ifndef PUBLIC_RELEASE + cemu_assert_debug(g_renderer && dynamic_cast<VulkanRenderer*>(g_renderer.get())); + // Use #if here because dynamic_casts dont get optimized away even if the result is not stored as with cemu_assert_debug +#endif + return (VulkanRenderer*)g_renderer.get(); +} + +void VulkanRenderer::Initialize(const Vector2i& size, bool isMainWindow) +{ + auto& windowHandleInfo = isMainWindow ? gui_getWindowInfo().canvas_main : gui_getWindowInfo().canvas_pad; + + const auto surface = CreateFramebufferSurface(m_instance, windowHandleInfo); + if (isMainWindow) + { + m_mainSwapchainInfo = std::make_unique<SwapChainInfo>(m_logicalDevice, surface); + CreateSwapChain(*m_mainSwapchainInfo, size, isMainWindow); + + // aquire first command buffer + InitFirstCommandBuffer(); + } + else + { + m_padSwapchainInfo = std::make_unique<SwapChainInfo>(m_logicalDevice, surface); + CreateSwapChain(*m_padSwapchainInfo, size, isMainWindow); + } +} + +bool VulkanRenderer::IsPadWindowActive() +{ + return IsSwapchainInfoValid(false); +} + +void VulkanRenderer::HandleScreenshotRequest(LatteTextureView* texView, bool padView) +{ + const bool hasScreenshotRequest = gui_hasScreenshotRequest(); + if (!hasScreenshotRequest && m_screenshot_state == ScreenshotState::None) + return; + + if (IsSwapchainInfoValid(false)) + { + // we already took a pad view screenshow and want a main window screenshot + if (m_screenshot_state == ScreenshotState::Main && padView) + return; + + if (m_screenshot_state == ScreenshotState::Pad && !padView) + return; + + // remember which screenshot is left to take + if (m_screenshot_state == ScreenshotState::None) + m_screenshot_state = padView ? ScreenshotState::Main : ScreenshotState::Pad; + else + m_screenshot_state = ScreenshotState::None; + } + else + m_screenshot_state = ScreenshotState::None; + + auto texViewVk = (LatteTextureViewVk*)texView; + auto baseImageTex = texViewVk->GetBaseImage(); + baseImageTex->GetImageObj()->flagForCurrentCommandBuffer(); + auto baseImageTexVkImage = baseImageTex->GetImageObj()->m_image; + + //auto baseImageObj = baseImage->GetTextureImageView(); + + auto dumpImage = baseImageTex->GetImageObj()->m_image; + //dumpImage->flagForCurrentCommandBuffer(); + + int width, height; + LatteTexture_getEffectiveSize(baseImageTex, &width, &height, nullptr, 0); + + VkImage image = nullptr; + VkDeviceMemory imageMemory = nullptr;; + + auto format = baseImageTex->GetFormat(); + if (format != VK_FORMAT_R8G8B8A8_UNORM && format != VK_FORMAT_R8G8B8A8_SRGB && format != VK_FORMAT_R8G8B8_UNORM && format != VK_FORMAT_R8G8B8_SNORM) + { + VkFormatProperties formatProps; + vkGetPhysicalDeviceFormatProperties(m_physical_device, format, &formatProps); + bool supportsBlit = (formatProps.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT) != 0; + + const bool dstUsesSRGB = (!padView && LatteGPUState.tvBufferUsesSRGB) || (padView && LatteGPUState.drcBufferUsesSRGB); + const auto blitFormat = dstUsesSRGB ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_R8G8B8A8_UNORM; + + vkGetPhysicalDeviceFormatProperties(m_physical_device, blitFormat, &formatProps); + supportsBlit &= (formatProps.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT) != 0; + + // convert texture using blitting + if (supportsBlit) + { + VkImageCreateInfo imageInfo{}; + imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + imageInfo.format = blitFormat; + imageInfo.extent = { (uint32)width, (uint32)height, 1 }; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.arrayLayers = 1; + imageInfo.mipLevels = 1; + imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + imageInfo.imageType = VK_IMAGE_TYPE_2D; + imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + + if (vkCreateImage(m_logicalDevice, &imageInfo, nullptr, &image) != VK_SUCCESS) + return; + + VkMemoryRequirements memRequirements; + vkGetImageMemoryRequirements(m_logicalDevice, image, &memRequirements); + + VkMemoryAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocInfo.allocationSize = memRequirements.size; + allocInfo.memoryTypeIndex = memoryManager->FindMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + + if (vkAllocateMemory(m_logicalDevice, &allocInfo, nullptr, &imageMemory) != VK_SUCCESS) + { + vkDestroyImage(m_logicalDevice, image, nullptr); + return; + } + + vkBindImageMemory(m_logicalDevice, image, imageMemory, 0); + + // prepare dest image + { + VkImageMemoryBarrier barrier = {}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + vkCmdPipelineBarrier(getCurrentCommandBuffer(), VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier); + } + // prepare src image for blitting + { + VkImageMemoryBarrier barrier = {}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = baseImageTexVkImage; + barrier.subresourceRange.aspectMask = baseImageTex->GetImageAspect(); + barrier.subresourceRange.baseMipLevel = texViewVk->firstMip; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = texViewVk->firstSlice; + barrier.subresourceRange.layerCount = 1; + barrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL; + barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + barrier.srcAccessMask = VK_ACCESS_MEMORY_READ_BIT; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + vkCmdPipelineBarrier(getCurrentCommandBuffer(), VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier); + } + + VkOffset3D blitSize{ width, height, 1 }; + VkImageBlit imageBlitRegion{}; + imageBlitRegion.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + imageBlitRegion.srcSubresource.layerCount = 1; + imageBlitRegion.srcOffsets[1] = blitSize; + imageBlitRegion.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + imageBlitRegion.dstSubresource.layerCount = 1; + imageBlitRegion.dstOffsets[1] = blitSize; + + // Issue the blit command + vkCmdBlitImage(getCurrentCommandBuffer(), baseImageTexVkImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &imageBlitRegion, VK_FILTER_NEAREST); + + // dest image to general layout + { + VkImageMemoryBarrier barrier = {}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT; + vkCmdPipelineBarrier(getCurrentCommandBuffer(), VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier); + } + // transition image back + { + VkImageMemoryBarrier barrier = {}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = baseImageTexVkImage; + barrier.subresourceRange.aspectMask = baseImageTex->GetImageAspect(); + barrier.subresourceRange.baseMipLevel = texViewVk->firstMip; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = texViewVk->firstSlice; + barrier.subresourceRange.layerCount = 1; + barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; + barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT; + vkCmdPipelineBarrier(getCurrentCommandBuffer(), VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier); + } + + format = VK_FORMAT_R8G8B8A8_UNORM; + dumpImage = image; + } + } + + uint32 size; + switch (format) + { + case VK_FORMAT_R8G8B8A8_UNORM: + case VK_FORMAT_R8G8B8A8_SRGB: + size = 4 * width * height; + break; + case VK_FORMAT_R8G8B8_UNORM: + case VK_FORMAT_R8G8B8_SRGB: + size = 3 * width * height; + break; + default: + size = 0; + } + + if (size == 0) + { + cemu_assert_debug(false); + return; + } + + VkBufferImageCopy region{}; + region.bufferOffset = 0; + region.bufferRowLength = width; + region.bufferImageHeight = height; + + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + region.imageSubresource.mipLevel = 0; + + region.imageOffset = { 0,0,0 }; + region.imageExtent = { (uint32)width,(uint32)height,1 }; + + void* bufferPtr = nullptr; + + VkBuffer buffer = nullptr; + VkDeviceMemory bufferMemory = nullptr; + memoryManager->CreateBuffer(size, VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT, buffer, bufferMemory); + vkMapMemory(m_logicalDevice, bufferMemory, 0, VK_WHOLE_SIZE, 0, &bufferPtr); + + { + VkImageMemoryBarrier barrier = {}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = dumpImage; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + barrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL; + barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; + barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + vkCmdPipelineBarrier(getCurrentCommandBuffer(), VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier); + } + + vkCmdCopyImageToBuffer(getCurrentCommandBuffer(), dumpImage, VK_IMAGE_LAYOUT_GENERAL, buffer, 1, ®ion); + + SubmitCommandBuffer(); + WaitCommandBufferFinished(GetCurrentCommandBufferId()); + + bool formatValid = true; + + std::vector<uint8> rgb_data; + rgb_data.reserve(3 * width * height); + + switch (format) + { + case VK_FORMAT_R8G8B8A8_UNORM: + for (auto ptr = (uint8*)bufferPtr; ptr < (uint8*)bufferPtr + size; ptr += 4) + { + rgb_data.emplace_back(*ptr); + rgb_data.emplace_back(*(ptr + 1)); + rgb_data.emplace_back(*(ptr + 2)); + } + break; + case VK_FORMAT_R8G8B8A8_SRGB: + for (auto ptr = (uint8*)bufferPtr; ptr < (uint8*)bufferPtr + size; ptr += 4) + { + rgb_data.emplace_back(SRGBComponentToRGB(*ptr)); + rgb_data.emplace_back(SRGBComponentToRGB(*(ptr + 1))); + rgb_data.emplace_back(SRGBComponentToRGB(*(ptr + 2))); + } + break; + case VK_FORMAT_R8G8B8_UNORM: + std::copy((uint8*)bufferPtr, (uint8*)bufferPtr + size, rgb_data.begin()); + break; + case VK_FORMAT_R8G8B8_SRGB: + std::transform((uint8*)bufferPtr, (uint8*)bufferPtr + size, rgb_data.begin(), SRGBComponentToRGB); + break; + default: + formatValid = false; + cemu_assert_debug(false); + } + + vkUnmapMemory(m_logicalDevice, bufferMemory); + vkFreeMemory(m_logicalDevice, bufferMemory, nullptr); + vkDestroyBuffer(m_logicalDevice, buffer, nullptr); + + if (image) + vkDestroyImage(m_logicalDevice, image, nullptr); + if (imageMemory) + vkFreeMemory(m_logicalDevice, imageMemory, nullptr); + + if (formatValid) + SaveScreenshot(rgb_data, width, height, !padView); +} + +void VulkanRenderer::ResizeSwapchain(const Vector2i& size, bool isMainWindow) +{ + if (isMainWindow) + { + m_swapchainState.newExtentMainWindow = size; + m_swapchainState.resizeRequestedMainWindow = true; + } + else + { + m_swapchainState.newExtentPadWindow = size; + m_swapchainState.resizeRequestedPadWindow = true; + } +} + +static const float kQueuePriority = 1.0f; + +std::vector<VkDeviceQueueCreateInfo> VulkanRenderer::CreateQueueCreateInfos(const std::set<sint32>& uniqueQueueFamilies) const +{ + std::vector<VkDeviceQueueCreateInfo> queueCreateInfos; + + for (int queueFamily : uniqueQueueFamilies) + { + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &kQueuePriority; + queueCreateInfos.emplace_back(queueCreateInfo); + } + + return queueCreateInfos; +} + +VkDeviceCreateInfo VulkanRenderer::CreateDeviceCreateInfo(const std::vector<VkDeviceQueueCreateInfo>& queueCreateInfos, const VkPhysicalDeviceFeatures& deviceFeatures, const void* deviceExtensionStructs, std::vector<const char*>& used_extensions) const +{ + used_extensions = kRequiredDeviceExtensions; + if (m_featureControl.deviceExtensions.tooling_info) + used_extensions.emplace_back(VK_EXT_TOOLING_INFO_EXTENSION_NAME); + if (m_featureControl.deviceExtensions.depth_range_unrestricted) + used_extensions.emplace_back(VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME); + if (m_featureControl.deviceExtensions.nv_fill_rectangle) + used_extensions.emplace_back(VK_NV_FILL_RECTANGLE_EXTENSION_NAME); + if (m_featureControl.deviceExtensions.pipeline_feedback) + used_extensions.emplace_back(VK_EXT_PIPELINE_CREATION_FEEDBACK_EXTENSION_NAME); + if (m_featureControl.deviceExtensions.cubic_filter) + used_extensions.emplace_back(VK_EXT_FILTER_CUBIC_EXTENSION_NAME); + if (m_featureControl.deviceExtensions.custom_border_color) + used_extensions.emplace_back(VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); + if (m_featureControl.deviceExtensions.driver_properties) + used_extensions.emplace_back(VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME); + if (m_featureControl.deviceExtensions.external_memory_host) + used_extensions.emplace_back(VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME); + if (m_featureControl.deviceExtensions.synchronization2) + used_extensions.emplace_back(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME); + if (m_featureControl.deviceExtensions.dynamic_rendering) + used_extensions.emplace_back(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME); + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + createInfo.queueCreateInfoCount = (uint32_t)queueCreateInfos.size(); + createInfo.pEnabledFeatures = &deviceFeatures; + createInfo.enabledExtensionCount = used_extensions.size(); + createInfo.ppEnabledExtensionNames = used_extensions.data(); + + createInfo.pNext = deviceExtensionStructs; + + if (!m_layerNames.empty()) + { + createInfo.enabledLayerCount = m_layerNames.size(); + createInfo.ppEnabledLayerNames = m_layerNames.data(); + } + + return createInfo; +} + +RendererShader* VulkanRenderer::shader_create(RendererShader::ShaderType type, uint64 baseHash, uint64 auxHash, const std::string& source, bool isGameShader, bool isGfxPackShader) +{ + return new RendererShaderVk(type, baseHash, auxHash, isGameShader, isGfxPackShader, source); +} + +void VulkanRenderer::shader_bind(RendererShader* shader) +{ + // does nothing on Vulkan + // remove from main render backend and internalize into GL backend +} + +void VulkanRenderer::shader_unbind(RendererShader::ShaderType shaderType) +{ + // does nothing on Vulkan +} + +VulkanRenderer::QueueFamilyIndices VulkanRenderer::FindQueueFamilies(VkSurfaceKHR surface, const VkPhysicalDevice& device) +{ + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + QueueFamilyIndices indices; + for (int i = 0; i < (int)queueFamilies.size(); ++i) + { + const auto& queueFamily = queueFamilies[i]; + if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) + indices.graphicsFamily = i; + + VkBool32 presentSupport = false; + const VkResult result = vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + if (result != VK_SUCCESS) + throw std::runtime_error(fmt::format("Error while attempting to check if a surface supports presentation: {}", result)); + + if (queueFamily.queueCount > 0 && presentSupport) + indices.presentFamily = i; + + if (indices.IsComplete()) + break; + } + + return indices; +} + +bool VulkanRenderer::CheckDeviceExtensionSupport(const VkPhysicalDevice device, FeatureControl& info) +{ + std::vector<VkExtensionProperties> availableDeviceExtensions; + + auto isExtensionAvailable = [&availableDeviceExtensions](const char* extensionName) -> bool + { + return std::find_if(availableDeviceExtensions.begin(), availableDeviceExtensions.end(), + [&extensionName](const VkExtensionProperties& prop) -> bool + { + return strcmp(prop.extensionName, extensionName) == 0; + }) != availableDeviceExtensions.cend(); + }; + + uint32_t extensionCount; + VkResult result = vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); + if (result != VK_SUCCESS) + throw std::runtime_error(fmt::format("Cannot retrieve count of properties for a physical device: {}", result)); + + availableDeviceExtensions.resize(extensionCount); + result = vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableDeviceExtensions.data()); + if (result != VK_SUCCESS) + throw std::runtime_error(fmt::format("Cannot retrieve properties for a physical device: {}", result)); + + std::set<std::string> requiredExtensions(kRequiredDeviceExtensions.begin(), kRequiredDeviceExtensions.end()); + for (const auto& extension : availableDeviceExtensions) + { + requiredExtensions.erase(extension.extensionName); + } + + info.deviceExtensions.tooling_info = isExtensionAvailable(VK_EXT_TOOLING_INFO_EXTENSION_NAME); + info.deviceExtensions.transform_feedback = isExtensionAvailable(VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME); + info.deviceExtensions.depth_range_unrestricted = isExtensionAvailable(VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME); + info.deviceExtensions.nv_fill_rectangle = isExtensionAvailable(VK_NV_FILL_RECTANGLE_EXTENSION_NAME); + info.deviceExtensions.pipeline_feedback = isExtensionAvailable(VK_EXT_PIPELINE_CREATION_FEEDBACK_EXTENSION_NAME); + info.deviceExtensions.cubic_filter = isExtensionAvailable(VK_EXT_FILTER_CUBIC_EXTENSION_NAME); + info.deviceExtensions.custom_border_color = isExtensionAvailable(VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); + info.deviceExtensions.driver_properties = isExtensionAvailable(VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME); + info.deviceExtensions.external_memory_host = isExtensionAvailable(VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME); + info.deviceExtensions.synchronization2 = isExtensionAvailable(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME); + info.deviceExtensions.dynamic_rendering = false; // isExtensionAvailable(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME); + // dynamic rendering doesn't provide any benefits for us right now. Driver implementations are very unoptimized as of Feb 2022 + + // check for framedebuggers + info.debugMarkersSupported = false; + if (info.deviceExtensions.tooling_info && vkGetPhysicalDeviceToolPropertiesEXT) + { + uint32_t toolCount = 0; + if (vkGetPhysicalDeviceToolPropertiesEXT(device, &toolCount, nullptr) == VK_SUCCESS) + { + std::vector<VkPhysicalDeviceToolPropertiesEXT> toolProperties(toolCount); + if (toolCount > 0 && vkGetPhysicalDeviceToolPropertiesEXT(device, &toolCount, toolProperties.data()) == VK_SUCCESS) + { + for (auto& itr : toolProperties) + { + if ((itr.purposes & VK_TOOL_PURPOSE_DEBUG_MARKERS_BIT_EXT) != 0) + info.debugMarkersSupported = true; + } + } + } + } + + return requiredExtensions.empty(); +} + +std::vector<const char*> VulkanRenderer::CheckInstanceExtensionSupport(FeatureControl& info) +{ + std::vector<VkExtensionProperties> availableInstanceExtensions; + std::vector<const char*> enabledInstanceExtensions; + VkResult err; + + auto isExtensionAvailable = [&availableInstanceExtensions](const char* extensionName) -> bool + { + return std::find_if(availableInstanceExtensions.begin(), availableInstanceExtensions.end(), + [&extensionName](const VkExtensionProperties& prop) -> bool + { + return strcmp(prop.extensionName, extensionName) == 0; + }) != availableInstanceExtensions.cend(); + }; + + // get list of available instance extensions + uint32_t count; + if ((err = vkEnumerateInstanceExtensionProperties(nullptr, &count, nullptr)) != VK_SUCCESS) + throw std::runtime_error(fmt::format("Failed to retrieve the instance extension properties : {}", err)); + + availableInstanceExtensions.resize(count); + if ((err = vkEnumerateInstanceExtensionProperties(nullptr, &count, availableInstanceExtensions.data())) != VK_SUCCESS) + throw std::runtime_error(fmt::format("Failed to retrieve the instance extension properties: {}", err)); + + // build list of required extensions + std::vector<const char*> requiredInstanceExtensions; + requiredInstanceExtensions.emplace_back(VK_KHR_SURFACE_EXTENSION_NAME); + #if BOOST_OS_WINDOWS + requiredInstanceExtensions.emplace_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME); + #else + requiredInstanceExtensions.emplace_back(VK_KHR_XLIB_SURFACE_EXTENSION_NAME); + #endif + if (cafeLog_isLoggingFlagEnabled(LOG_TYPE_VULKAN_VALIDATION)) + requiredInstanceExtensions.emplace_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); + + // make sure all required extensions are supported + for (const auto& extension : availableInstanceExtensions) + { + for (auto it = requiredInstanceExtensions.begin(); it < requiredInstanceExtensions.end(); ++it) + { + if (strcmp(*it, extension.extensionName) == 0) + { + enabledInstanceExtensions.emplace_back(*it); + requiredInstanceExtensions.erase(it); + break; + } + } + } + if (!requiredInstanceExtensions.empty()) + { + forceLog_printf("The following required Vulkan instance extensions are not supported:"); + + std::stringstream ss; + for (const auto& extension : requiredInstanceExtensions) + forceLog_printf("%s", extension); + cemuLog_waitForFlush(); + throw std::runtime_error(ss.str()); + } + + // check for optional extensions + info.instanceExtensions.debug_utils = isExtensionAvailable(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + if (info.instanceExtensions.debug_utils) + enabledInstanceExtensions.emplace_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + return enabledInstanceExtensions; +} + +VulkanRenderer::SwapChainSupportDetails VulkanRenderer::QuerySwapChainSupport(VkSurfaceKHR surface, const VkPhysicalDevice& device) +{ + SwapChainSupportDetails details; + + VkResult result = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + if (result != VK_SUCCESS) + { + if (result != VK_ERROR_SURFACE_LOST_KHR) + forceLog_printf("vkGetPhysicalDeviceSurfaceCapabilitiesKHR failed. Error %d", (sint32)result); + throw std::runtime_error(fmt::format("Unable to retrieve physical device surface capabilities: {}", result)); + } + + uint32_t formatCount = 0; + result = vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); + if (result != VK_SUCCESS) + { + forceLog_printf("vkGetPhysicalDeviceSurfaceFormatsKHR failed. Error %d", (sint32)result); + throw std::runtime_error(fmt::format("Unable to retrieve the number of formats for a surface on a physical device: {}", result)); + } + + if (formatCount != 0) + { + details.formats.resize(formatCount); + result = vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); + if (result != VK_SUCCESS) + { + forceLog_printf("vkGetPhysicalDeviceSurfaceFormatsKHR failed. Error %d", (sint32)result); + throw std::runtime_error(fmt::format("Unable to retrieve the formats for a surface on a physical device: {}", result)); + } + } + + uint32_t presentModeCount = 0; + result = vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); + if (result != VK_SUCCESS) + { + forceLog_printf("vkGetPhysicalDeviceSurfacePresentModesKHR failed. Error %d", (sint32)result); + throw std::runtime_error(fmt::format("Unable to retrieve the count of present modes for a surface on a physical device: {}", result)); + } + + if (presentModeCount != 0) + { + details.presentModes.resize(presentModeCount); + result = vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); + if (result != VK_SUCCESS) + { + forceLog_printf("vkGetPhysicalDeviceSurfacePresentModesKHR failed. Error %d", (sint32)result); + throw std::runtime_error(fmt::format("Unable to retrieve the present modes for a surface on a physical device: {}", result)); + } + } + + return details; +} + +bool VulkanRenderer::IsDeviceSuitable(VkSurfaceKHR surface, const VkPhysicalDevice& device) +{ + if (!FindQueueFamilies(surface, device).IsComplete()) + return false; + + // check API version (using Vulkan 1.0 way of querying properties) + VkPhysicalDeviceProperties properties{}; + vkGetPhysicalDeviceProperties(device, &properties); + uint32 vkVersionMajor = VK_API_VERSION_MAJOR(properties.apiVersion); + uint32 vkVersionMinor = VK_API_VERSION_MINOR(properties.apiVersion); + if (vkVersionMajor < 1 || vkVersionMinor < 1) + return false; // minimum required version is Vulkan 1.1 + + FeatureControl info; + if (!CheckDeviceExtensionSupport(device, info)) + return false; + + const SwapChainSupportDetails swapChainSupport = QuerySwapChainSupport(surface, device); + + return !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); +} + +#if BOOST_OS_WINDOWS +VkSurfaceKHR VulkanRenderer::CreateWinSurface(VkInstance instance, HWND hwindow) +{ + VkWin32SurfaceCreateInfoKHR sci{}; + sci.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR; + sci.hwnd = hwindow; + sci.hinstance = GetModuleHandle(nullptr); + + VkSurfaceKHR result; + VkResult err; + if ((err = vkCreateWin32SurfaceKHR(instance, &sci, nullptr, &result)) != VK_SUCCESS) + { + forceLog_printf("Cannot create a Win32 Vulkan surface: %d", (sint32)err); + throw std::runtime_error(fmt::format("Cannot create a Win32 Vulkan surface: {}", err)); + } + + return result; +} +#endif + +#if BOOST_OS_LINUX +VkSurfaceKHR VulkanRenderer::CreateXlibSurface(VkInstance instance, Display* dpy, Window window) +{ + VkXlibSurfaceCreateInfoKHR sci{}; + sci.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR; + sci.flags = 0; + sci.dpy = dpy; + sci.window = window; + + VkSurfaceKHR result; + VkResult err; + if ((err = vkCreateXlibSurfaceKHR(instance, &sci, nullptr, &result)) != VK_SUCCESS) + { + forceLog_printf("Cannot create a X11 Vulkan surface: %d", (sint32)err); + throw std::runtime_error(fmt::format("Cannot create a X11 Vulkan surface: {}", err)); + } + + return result; +} + +VkSurfaceKHR VulkanRenderer::CreateXcbSurface(VkInstance instance, xcb_connection_t* connection, xcb_window_t window) +{ + VkXcbSurfaceCreateInfoKHR sci{}; + sci.sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR; + sci.flags = 0; + sci.connection = connection; + sci.window = window; + + VkSurfaceKHR result; + VkResult err; + if ((err = vkCreateXcbSurfaceKHR(instance, &sci, nullptr, &result)) != VK_SUCCESS) + { + forceLog_printf("Cannot create a XCB Vulkan surface: %d", (sint32)err); + throw std::runtime_error(fmt::format("Cannot create a XCB Vulkan surface: {}", err)); + } + + return result; +} +#endif + +VkSurfaceKHR VulkanRenderer::CreateFramebufferSurface(VkInstance instance, struct WindowHandleInfo& windowInfo) +{ +#if BOOST_OS_WINDOWS + return CreateWinSurface(instance, windowInfo.hwnd); +#else + return CreateXlibSurface(instance, windowInfo.xlib_display, windowInfo.xlib_window); +#endif +} + +VkSurfaceFormatKHR VulkanRenderer::ChooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& formats, bool mainWindow) const +{ + if (formats.size() == 1 && formats[0].format == VK_FORMAT_UNDEFINED) + return{ VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR }; + + for (const auto& format : formats) + { + if ((mainWindow && LatteGPUState.tvBufferUsesSRGB) || (!mainWindow && LatteGPUState.drcBufferUsesSRGB)) + { + if (format.format == VK_FORMAT_B8G8R8A8_SRGB && format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) + return format; + } + else + { + if (format.format == VK_FORMAT_B8G8R8A8_UNORM && format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) + return format; + } + } + + return formats[0]; +} + +VkPresentModeKHR VulkanRenderer::ChooseSwapPresentMode(const std::vector<VkPresentModeKHR>& modes) +{ + m_vsync_state = (VSync)GetConfig().vsync.GetValue(); + if (m_vsync_state == VSync::MAILBOX) + { + if (std::find(modes.cbegin(), modes.cend(), VK_PRESENT_MODE_MAILBOX_KHR) != modes.cend()) + return VK_PRESENT_MODE_MAILBOX_KHR; + + forceLog_printf("Vulkan: Can't find mailbox present mode"); + } + else if (m_vsync_state == VSync::Immediate) + { + if (std::find(modes.cbegin(), modes.cend(), VK_PRESENT_MODE_IMMEDIATE_KHR) != modes.cend()) + return VK_PRESENT_MODE_IMMEDIATE_KHR; + + forceLog_printf("Vulkan: Can't find immediate present mode"); + } + else if (m_vsync_state == VSync::SYNC_AND_LIMIT) + { + LatteTiming_EnableHostDrivenVSync(); + // use immediate mode if available, other wise fall back to + //if (std::find(modes.cbegin(), modes.cend(), VK_PRESENT_MODE_IMMEDIATE_KHR) != modes.cend()) + // return VK_PRESENT_MODE_IMMEDIATE_KHR; + //else + // forceLog_printf("Vulkan: Present mode 'immediate' not available. Vsync might not behave as intended"); + return VK_PRESENT_MODE_FIFO_KHR; + } + + return VK_PRESENT_MODE_FIFO_KHR; +} + +VkExtent2D VulkanRenderer::ChooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities, const Vector2i& size) const +{ + if (capabilities.currentExtent.width != std::numeric_limits<uint32>::max()) + return capabilities.currentExtent; + + VkExtent2D actualExtent = { (uint32)size.x, (uint32)size.y }; + actualExtent.width = std::max(capabilities.minImageExtent.width, std::min(capabilities.maxImageExtent.width, actualExtent.width)); + actualExtent.height = std::max(capabilities.minImageExtent.height, std::min(capabilities.maxImageExtent.height, actualExtent.height)); + return actualExtent; +} + +void VulkanRenderer::CreateCommandPool() +{ + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.queueFamilyIndex = m_indices.graphicsFamily; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + + VkResult result = vkCreateCommandPool(m_logicalDevice, &poolInfo, nullptr, &m_commandPool); + if (result != VK_SUCCESS) + throw std::runtime_error(fmt::format("Failed to create command pool: {}", result)); +} + +void VulkanRenderer::CreateCommandBuffers() +{ + auto it = m_cmd_buffer_fences.begin(); + VkFenceCreateInfo fenceInfo{}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + vkCreateFence(m_logicalDevice, &fenceInfo, nullptr, &*it); + + ++it; + fenceInfo.flags = 0; + for (; it != m_cmd_buffer_fences.end(); ++it) + { + vkCreateFence(m_logicalDevice, &fenceInfo, nullptr, &*it); + } + + VkCommandBufferAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = m_commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = (uint32_t)m_commandBuffers.size(); + + const VkResult result = vkAllocateCommandBuffers(m_logicalDevice, &allocInfo, m_commandBuffers.data()); + if (result != VK_SUCCESS) + { + forceLog_printf("Failed to allocate command buffers: %d", result); + throw std::runtime_error(fmt::format("Failed to allocate command buffers: {}", result)); + } + + for (auto& semItr : m_commandBufferSemaphores) + { + VkSemaphoreCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + if (vkCreateSemaphore(m_logicalDevice, &info, nullptr, &semItr) != VK_SUCCESS) + UnrecoverableError("Failed to create semaphore for command buffer"); + } +} + +VkSwapchainCreateInfoKHR VulkanRenderer::CreateSwapchainCreateInfo(VkSurfaceKHR surface, const SwapChainSupportDetails& swapChainSupport, const VkSurfaceFormatKHR& surfaceFormat, uint32 imageCount, const VkExtent2D& extent) +{ + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + const QueueFamilyIndices indices = FindQueueFamilies(surface, m_physical_device); + uint32_t queueFamilyIndices[] = { (uint32)indices.graphicsFamily, (uint32)indices.presentFamily }; + if (indices.graphicsFamily != indices.presentFamily) + { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } + else + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = ChooseSwapPresentMode(swapChainSupport.presentModes); + createInfo.clipped = VK_TRUE; + + forceLogDebug_printf("vulkan presentation mode: %d", createInfo.presentMode); + return createInfo; +} + +bool VulkanRenderer::IsSwapchainInfoValid(bool mainWindow) const +{ + if (mainWindow) + return m_mainSwapchainInfo && m_mainSwapchainInfo->swapChain && m_mainSwapchainInfo->m_imageAvailableFence; + + return m_padSwapchainInfo && m_padSwapchainInfo->swapChain && m_padSwapchainInfo->m_imageAvailableFence; +} + +VkSwapchainKHR VulkanRenderer::CreateSwapChain(SwapChainInfo& swap_chain_info, const Vector2i& size, bool mainwindow) +{ + const SwapChainSupportDetails details = QuerySwapChainSupport(swap_chain_info.surface, m_physical_device); + m_swapchainFormat = ChooseSwapSurfaceFormat(details.formats, mainwindow); + swap_chain_info.swapchainExtend = ChooseSwapExtent(details.capabilities, size); + // calculate number of swapchain presentation images + uint32_t image_count = details.capabilities.minImageCount + 1; + if (details.capabilities.maxImageCount > 0 && image_count > details.capabilities.maxImageCount) + image_count = details.capabilities.maxImageCount; + + VkSwapchainCreateInfoKHR create_info = CreateSwapchainCreateInfo(swap_chain_info.surface, details, m_swapchainFormat, image_count, swap_chain_info.swapchainExtend); + create_info.oldSwapchain = swap_chain_info.swapChain; + swap_chain_info.swapChain = nullptr; + create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + + VkResult result = vkCreateSwapchainKHR(m_logicalDevice, &create_info, nullptr, &swap_chain_info.swapChain); + if (result != VK_SUCCESS) + UnrecoverableError("Error attempting to create a swapchain"); + + result = vkGetSwapchainImagesKHR(m_logicalDevice, swap_chain_info.swapChain, &image_count, nullptr); + if (result != VK_SUCCESS) + UnrecoverableError("Error attempting to retrieve the count of swapchain images"); + + // clean up previously initialized swapchain + for (const auto& image : swap_chain_info.m_swapchainImages) + vkDestroyImage(m_logicalDevice, image, nullptr); + swap_chain_info.m_swapchainImages.clear(); + for (auto& sem : swap_chain_info.m_swapchainPresentSemaphores) + vkDestroySemaphore(m_logicalDevice, sem, nullptr); + swap_chain_info.m_swapchainPresentSemaphores.clear(); + + + swap_chain_info.m_swapchainImages.resize(image_count); + result = vkGetSwapchainImagesKHR(m_logicalDevice, swap_chain_info.swapChain, &image_count, swap_chain_info.m_swapchainImages.data()); + if (result != VK_SUCCESS) + UnrecoverableError("Error attempting to retrieve swapchain images"); + // create default renderpass + VkAttachmentDescription colorAttachment = {}; + colorAttachment.format = m_swapchainFormat.format; + colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentReference colorAttachmentRef = {}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + VkSubpassDescription subpass = {}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + + if (swap_chain_info.m_swapChainRenderPass) + { + vkDestroyRenderPass(m_logicalDevice, swap_chain_info.m_swapChainRenderPass, nullptr); + swap_chain_info.m_swapChainRenderPass = nullptr; + } + + VkRenderPassCreateInfo renderPassInfo = {}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = 1; + renderPassInfo.pAttachments = &colorAttachment; + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + result = vkCreateRenderPass(m_logicalDevice, &renderPassInfo, nullptr, &swap_chain_info.m_swapChainRenderPass); + if (result != VK_SUCCESS) + UnrecoverableError("Failed to create renderpass for swapchain"); + + // create swapchain image views + for (const auto& image_view : swap_chain_info.m_swapchainImageViews) + { + vkDestroyImageView(m_logicalDevice, image_view, nullptr); + } + swap_chain_info.m_swapchainImageViews.clear(); + + swap_chain_info.m_swapchainImageViews.resize(swap_chain_info.m_swapchainImages.size()); + for (sint32 i = 0; i < swap_chain_info.m_swapchainImages.size(); i++) + { + VkImageViewCreateInfo createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = swap_chain_info.m_swapchainImages[i]; + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = m_swapchainFormat.format; + createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + result = vkCreateImageView(m_logicalDevice, &createInfo, nullptr, &swap_chain_info.m_swapchainImageViews[i]); + if (result != VK_SUCCESS) + UnrecoverableError("Failed to create imageviews for swapchain"); + } + + // create swapchain framebuffers + for (const auto& framebuffer : swap_chain_info.m_swapchainFramebuffers) + { + vkDestroyFramebuffer(m_logicalDevice, framebuffer, nullptr); + } + swap_chain_info.m_swapchainFramebuffers.clear(); + + swap_chain_info.m_swapchainFramebuffers.resize(swap_chain_info.m_swapchainImages.size()); + swap_chain_info.m_swapchainPresentSemaphores.resize(swap_chain_info.m_swapchainImages.size()); + for (size_t i = 0; i < swap_chain_info.m_swapchainImages.size(); i++) + { + VkImageView attachments[1]; + attachments[0] = swap_chain_info.m_swapchainImageViews[i]; + // create framebuffer + VkFramebufferCreateInfo framebufferInfo = {}; + framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferInfo.renderPass = swap_chain_info.m_swapChainRenderPass; + framebufferInfo.attachmentCount = 1; + framebufferInfo.pAttachments = attachments; + framebufferInfo.width = swap_chain_info.swapchainExtend.width; + framebufferInfo.height = swap_chain_info.swapchainExtend.height; + framebufferInfo.layers = 1; + result = vkCreateFramebuffer(m_logicalDevice, &framebufferInfo, nullptr, &swap_chain_info.m_swapchainFramebuffers[i]); + if (result != VK_SUCCESS) + UnrecoverableError("Failed to create framebuffer for swapchain"); + // create present semaphore + VkSemaphoreCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + if (vkCreateSemaphore(m_logicalDevice, &info, nullptr, &swap_chain_info.m_swapchainPresentSemaphores[i]) != VK_SUCCESS) + UnrecoverableError("Failed to create semaphore for swapchain present"); + } + + // init m_acquireInfo + for (auto& itr : swap_chain_info.m_acquireInfo) + { + vkDestroySemaphore(m_logicalDevice, itr.acquireSemaphore, nullptr); + } + swap_chain_info.m_acquireInfo.clear(); + swap_chain_info.m_acquireInfo.resize(swap_chain_info.m_swapchainImages.size()); + for (auto& itr : swap_chain_info.m_acquireInfo) + { + VkSemaphoreCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + if (vkCreateSemaphore(m_logicalDevice, &info, nullptr, &itr.acquireSemaphore) != VK_SUCCESS) + UnrecoverableError("Failed to create semaphore for swapchain acquire"); + } + swap_chain_info.m_acquireIndex = 0; + + + if (swap_chain_info.m_imageAvailableFence) + { + vkDestroyFence(m_logicalDevice, swap_chain_info.m_imageAvailableFence, nullptr); + swap_chain_info.m_imageAvailableFence = nullptr; + } + + VkFenceCreateInfo fenceInfo = {}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + result = vkCreateFence(m_logicalDevice, &fenceInfo, nullptr, &swap_chain_info.m_imageAvailableFence); + if (result != VK_SUCCESS) + UnrecoverableError("Failed to create fence for swapchain"); + + return swap_chain_info.swapChain; +} + +void VulkanRenderer::CreateNullTexture(NullTexture& nullTex, VkImageType imageType) +{ + // these are used when the game requests NULL ptr textures or buffers + // texture + VkImageCreateInfo imageInfo{}; + imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + if (imageType == VK_IMAGE_TYPE_1D) + { + imageInfo.extent.width = 4; + imageInfo.extent.height = 1; + } + else if (imageType == VK_IMAGE_TYPE_2D) + { + imageInfo.extent.width = 4; + imageInfo.extent.height = 1; + } + else + { + cemu_assert(false); + } + imageInfo.mipLevels = 1; + imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageInfo.extent.depth = 1; + imageInfo.arrayLayers = 1; + imageInfo.imageType = imageType; + imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM; + if (vkCreateImage(m_logicalDevice, &imageInfo, nullptr, &nullTex.image) != VK_SUCCESS) + UnrecoverableError("Failed to create nullTex image"); + nullTex.allocation = memoryManager->imageMemoryAllocate(nullTex.image); + + VkClearColorValue clrColor{}; + ClearColorImageRaw(nullTex.image, 0, 0, clrColor, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL); + // texture view + VkImageViewCreateInfo viewInfo{}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.image = nullTex.image; + if (imageType == VK_IMAGE_TYPE_1D) + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_1D; + else if (imageType == VK_IMAGE_TYPE_2D) + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + else + { + cemu_assert(false); + } + viewInfo.format = VK_FORMAT_R8G8B8A8_UNORM; + viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = 1; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = 1; + if (vkCreateImageView(m_logicalDevice, &viewInfo, nullptr, &nullTex.view) != VK_SUCCESS) + UnrecoverableError("Failed to create nullTex image view"); + // sampler + VkSamplerCreateInfo samplerInfo{}; + samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + samplerInfo.magFilter = VK_FILTER_LINEAR; + samplerInfo.minFilter = VK_FILTER_LINEAR; + samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + samplerInfo.mipLodBias = 0.0f; + samplerInfo.compareOp = VK_COMPARE_OP_NEVER; + samplerInfo.minLod = 0.0f; + samplerInfo.maxLod = 0.0f; + samplerInfo.maxAnisotropy = 1.0; + samplerInfo.anisotropyEnable = VK_FALSE; + samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; + vkCreateSampler(m_logicalDevice, &samplerInfo, nullptr, &nullTex.sampler); +} + +void VulkanRenderer::CreateNullObjects() +{ + CreateNullTexture(nullTexture1D, VK_IMAGE_TYPE_1D); + CreateNullTexture(nullTexture2D, VK_IMAGE_TYPE_2D); +} + +void VulkanRenderer::DeleteNullTexture(NullTexture& nullTex) +{ + vkDestroySampler(m_logicalDevice, nullTex.sampler, nullptr); + nullTex.sampler = VK_NULL_HANDLE; + vkDestroyImageView(m_logicalDevice, nullTex.view, nullptr); + nullTex.view = VK_NULL_HANDLE; + vkDestroyImage(m_logicalDevice, nullTex.image, nullptr); + nullTex.image = VK_NULL_HANDLE; + memoryManager->imageMemoryFree(nullTex.allocation); + nullTex.allocation = nullptr; +} + +void VulkanRenderer::DeleteNullObjects() +{ + DeleteNullTexture(nullTexture1D); + DeleteNullTexture(nullTexture2D); +} + +void VulkanRenderer::ImguiInit() +{ + // TODO: renderpass swapchain format may change between srgb and rgb -> need reinit + VkAttachmentDescription colorAttachment = {}; + colorAttachment.format = m_swapchainFormat.format; + colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.initialLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentReference colorAttachmentRef = {}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + VkSubpassDescription subpass = {}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + + VkRenderPassCreateInfo renderPassInfo = {}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = 1; + renderPassInfo.pAttachments = &colorAttachment; + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + const auto result = vkCreateRenderPass(m_logicalDevice, &renderPassInfo, nullptr, &m_mainSwapchainInfo->m_imguiRenderPass); + if (result != VK_SUCCESS) + throw VkException(result, "can't create imgui renderpass"); + + ImGui_ImplVulkan_InitInfo info{}; + info.Instance = m_instance; + info.PhysicalDevice = m_physical_device; + info.Device = m_logicalDevice; + info.QueueFamily = m_indices.presentFamily; + info.Queue = m_presentQueue; + info.PipelineCache = m_pipeline_cache; + info.DescriptorPool = m_descriptorPool; + info.MinImageCount = m_mainSwapchainInfo->m_swapchainImages.size(); + info.ImageCount = info.MinImageCount; + + ImGui_ImplVulkan_Init(&info, m_mainSwapchainInfo->m_imguiRenderPass); +} + +void VulkanRenderer::Initialize() +{ + CreatePipelineCache(); + ImguiInit(); + CreateNullObjects(); +} + +void VulkanRenderer::Shutdown() +{ + SubmitCommandBuffer(); + vkDeviceWaitIdle(m_logicalDevice); +} + +void VulkanRenderer::UnrecoverableError(const char* errMsg) const +{ + forceLog_printf("Unrecoverable error in Vulkan renderer"); + forceLog_printf("Msg: %s", errMsg); + throw std::runtime_error(errMsg); +} + +struct VulkanRequestedFormat_t +{ + VkFormat fmt; + const char* name; + bool isDepth; + bool mustSupportAttachment; + bool mustSupportBlending; +}; + +#define reqColorFormat(__name, __reqAttachment, __reqBlend) {__name, ""#__name, false, __reqAttachment, __reqBlend} +#define reqDepthFormat(__name) {__name, ""#__name, true, true, false} + +VulkanRequestedFormat_t requestedFormatList[] = +{ + reqDepthFormat(VK_FORMAT_D32_SFLOAT_S8_UINT), + reqDepthFormat(VK_FORMAT_D24_UNORM_S8_UINT), + reqDepthFormat(VK_FORMAT_D32_SFLOAT), + reqDepthFormat(VK_FORMAT_D16_UNORM), + reqColorFormat(VK_FORMAT_R32G32B32A32_SFLOAT, true, true), + reqColorFormat(VK_FORMAT_R32G32B32A32_UINT, true, false), + reqColorFormat(VK_FORMAT_R16G16B16A16_SFLOAT, true, true), + reqColorFormat(VK_FORMAT_R16G16B16A16_UINT, true, false), + reqColorFormat(VK_FORMAT_R16G16B16A16_UNORM, true, true), + reqColorFormat(VK_FORMAT_R16G16B16A16_SNORM, true, true), + reqColorFormat(VK_FORMAT_R8G8B8A8_UNORM, true, true), + reqColorFormat(VK_FORMAT_R8G8B8A8_SNORM, true, true), + reqColorFormat(VK_FORMAT_R8G8B8A8_SRGB, true, true), + reqColorFormat(VK_FORMAT_R8G8B8A8_UINT, true, false), + reqColorFormat(VK_FORMAT_R8G8B8A8_SINT, true, false), + reqColorFormat(VK_FORMAT_R4G4B4A4_UNORM_PACK16, true, true), + reqColorFormat(VK_FORMAT_R32G32_SFLOAT, true, true), + reqColorFormat(VK_FORMAT_R32G32_UINT, true, false), + reqColorFormat(VK_FORMAT_R16G16_UNORM, true, true), + reqColorFormat(VK_FORMAT_R16G16_SFLOAT, true, true), + reqColorFormat(VK_FORMAT_R8G8_UNORM, true, true), + reqColorFormat(VK_FORMAT_R8G8_SNORM, true, true), + reqColorFormat(VK_FORMAT_R4G4_UNORM_PACK8, true, true), + reqColorFormat(VK_FORMAT_R32_SFLOAT, true, true), + reqColorFormat(VK_FORMAT_R32_UINT, true, false), + reqColorFormat(VK_FORMAT_R16_SFLOAT, true, true), + reqColorFormat(VK_FORMAT_R16_UNORM, true, true), + reqColorFormat(VK_FORMAT_R16_SNORM, true, true), + reqColorFormat(VK_FORMAT_R8_UNORM, true, true), + reqColorFormat(VK_FORMAT_R8_SNORM, true, true), + reqColorFormat(VK_FORMAT_R5G6B5_UNORM_PACK16, true, true), + reqColorFormat(VK_FORMAT_R5G5B5A1_UNORM_PACK16, true, true), + reqColorFormat(VK_FORMAT_B10G11R11_UFLOAT_PACK32, true, true), + reqColorFormat(VK_FORMAT_R16G16B16A16_SNORM, true, true), + reqColorFormat(VK_FORMAT_BC1_RGBA_SRGB_BLOCK, false, false), + reqColorFormat(VK_FORMAT_BC1_RGBA_UNORM_BLOCK, false, false), + reqColorFormat(VK_FORMAT_BC2_UNORM_BLOCK, false, false), + reqColorFormat(VK_FORMAT_BC2_SRGB_BLOCK, false, false), + reqColorFormat(VK_FORMAT_BC3_UNORM_BLOCK, false, false), + reqColorFormat(VK_FORMAT_BC3_SRGB_BLOCK, false, false), + reqColorFormat(VK_FORMAT_BC4_UNORM_BLOCK, false, false), + reqColorFormat(VK_FORMAT_BC4_SNORM_BLOCK, false, false), + reqColorFormat(VK_FORMAT_BC5_UNORM_BLOCK, false, false), + reqColorFormat(VK_FORMAT_BC5_SNORM_BLOCK, false, false), + reqColorFormat(VK_FORMAT_A2B10G10R10_UNORM_PACK32, true, true), + reqColorFormat(VK_FORMAT_R32_SFLOAT, true, true) +}; + +void VulkanRenderer::QueryMemoryInfo() +{ + VkPhysicalDeviceMemoryProperties memProperties; + vkGetPhysicalDeviceMemoryProperties(m_physical_device, &memProperties); + forceLog_printf("Vulkan device memory info:"); + for (uint32 i = 0; i < memProperties.memoryHeapCount; i++) + { + forceLog_printf("Heap %d - Size %dMB Flags 0x%08x", i, (sint32)(memProperties.memoryHeaps[i].size / 1024ll / 1024ll), (uint32)memProperties.memoryHeaps[i].flags); + } + for (uint32 i = 0; i < memProperties.memoryTypeCount; i++) + { + forceLog_printf("Memory %d - HeapIndex %d Flags 0x%08x", i, (sint32)memProperties.memoryTypes[i].heapIndex, (uint32)memProperties.memoryTypes[i].propertyFlags); + } +} + +void VulkanRenderer::QueryAvailableFormats() +{ + VkFormatProperties fmtProp{}; + vkGetPhysicalDeviceFormatProperties(m_physical_device, VK_FORMAT_D24_UNORM_S8_UINT, &fmtProp); + // D24S8 + if (fmtProp.optimalTilingFeatures != 0) // todo - more restrictive check + { + m_supportedFormatInfo.fmt_d24_unorm_s8_uint = true; + } + // R4G4 + fmtProp = {}; + vkGetPhysicalDeviceFormatProperties(m_physical_device, VK_FORMAT_R4G4_UNORM_PACK8, &fmtProp); + if (fmtProp.optimalTilingFeatures != 0) + { + m_supportedFormatInfo.fmt_r4g4_unorm_pack = true; + } + // print info about unsupported formats to log + for (auto& it : requestedFormatList) + { + fmtProp = {}; + vkGetPhysicalDeviceFormatProperties(m_physical_device, it.fmt, &fmtProp); + VkFormatFeatureFlags requestedBits = 0; + if (it.mustSupportAttachment) + { + if (it.isDepth) + requestedBits |= VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT; + else + requestedBits |= VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT; + if (!it.isDepth && it.mustSupportBlending) + requestedBits |= VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT; + } + requestedBits |= VK_FORMAT_FEATURE_TRANSFER_DST_BIT; + requestedBits |= VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT; + + if (fmtProp.optimalTilingFeatures == 0) + { + forceLog_printf("%s not supported", it.name); + } + else if ((fmtProp.optimalTilingFeatures & requestedBits) != requestedBits) + { + //std::string missingStr; + //missingStr.assign(fmt::format("{} missing features:", it.name)); + //if (!(fmtProp.optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT) && !it.isDepth && it.mustSupportAttachment) + // missingStr.append(" COLOR_ATTACHMENT"); + //if (!(fmtProp.optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT) && !it.isDepth && it.mustSupportBlending) + // missingStr.append(" COLOR_ATTACHMENT_BLEND"); + //if (!(fmtProp.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) && it.isDepth && it.mustSupportAttachment) + // missingStr.append(" DEPTH_ATTACHMENT"); + //if (!(fmtProp.optimalTilingFeatures & VK_FORMAT_FEATURE_TRANSFER_DST_BIT)) + // missingStr.append(" TRANSFER_DST"); + //if (!(fmtProp.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT)) + // missingStr.append(" SAMPLED_IMAGE"); + //forceLog_printf("%s", missingStr.c_str()); + } + } +} + +void VulkanRenderer::EnableVSync(int state) +{ + if (m_vsync_state == (VSync)state) + return; + + m_vsync_state = (VSync)state; + + // recreate spawn chains (vsync state is checked from config in ChooseSwapPresentMode) + RecreateSwapchain(true); + if (m_padSwapchainInfo) + RecreateSwapchain(false); +} + +bool VulkanRenderer::ImguiBegin(bool mainWindow) +{ + if (!Renderer::ImguiBegin(mainWindow)) + return false; + + if (!IsSwapchainInfoValid(mainWindow)) + return false; + + draw_endRenderPass(); + m_state.currentPipeline = VK_NULL_HANDLE; + + AcquireNextSwapchainImage(mainWindow); + + auto& swapchain_info = mainWindow ? *m_mainSwapchainInfo : *m_padSwapchainInfo; + + ImGui_ImplVulkan_CreateFontsTexture(m_state.currentCommandBuffer); + ImGui_ImplVulkan_NewFrame(m_state.currentCommandBuffer, swapchain_info.m_swapchainFramebuffers[swapchain_info.swapchainImageIndex], swapchain_info.swapchainExtend); + ImGui_UpdateWindowInformation(mainWindow); + ImGui::NewFrame(); + return true; +} + + +void VulkanRenderer::ImguiEnd() +{ + ImGui::Render(); + ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), m_state.currentCommandBuffer); + vkCmdEndRenderPass(m_state.currentCommandBuffer); +} + +std::vector<LatteTextureVk*> g_imgui_textures; // TODO manage better +ImTextureID VulkanRenderer::GenerateTexture(const std::vector<uint8>& data, const Vector2i& size) +{ + try + { + // g_imgui_textures.emplace_back(texture); + std::vector <uint8> tmp(size.x * size.y * 4); + for (size_t i = 0; i < data.size() / 3; ++i) + { + tmp[(i * 4) + 0] = data[(i * 3) + 0]; + tmp[(i * 4) + 1] = data[(i * 3) + 1]; + tmp[(i * 4) + 2] = data[(i * 3) + 2]; + tmp[(i * 4) + 3] = 0xFF; + } + return (ImTextureID)ImGui_ImplVulkan_GenerateTexture(m_state.currentCommandBuffer, tmp, size); + } + catch (const std::exception& ex) + { + forceLog_printf("can't generate imgui texture: %s", ex.what()); + return nullptr; + } +} + +void VulkanRenderer::DeleteTexture(ImTextureID id) +{ + vkDeviceWaitIdle(m_logicalDevice); + ImGui_ImplVulkan_DeleteTexture(id); +} + +void VulkanRenderer::DeleteFontTextures() +{ + ImGui_ImplVulkan_DestroyFontsTexture(); +} + + +bool VulkanRenderer::BeginFrame(bool mainWindow) +{ + if (!IsSwapchainInfoValid(mainWindow)) + return false; + + AcquireNextSwapchainImage(mainWindow); + + auto& swap_info = mainWindow ? *m_mainSwapchainInfo : *m_padSwapchainInfo; + + VkClearColorValue clearColor{ 0, 0, 0, 0 }; + ClearColorImageRaw(swap_info.m_swapchainImages[swap_info.swapchainImageIndex], 0, 0, clearColor, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); + + // mark current swapchain image as well defined + if (!mainWindow) + m_swapchainState.drcHasDefinedSwapchainImage = true; + else + m_swapchainState.tvHasDefinedSwapchainImage = true; + + return true; +} + +void VulkanRenderer::DrawEmptyFrame(bool mainWindow) +{ + if (!BeginFrame(mainWindow)) + return; + SwapBuffers(mainWindow, !mainWindow); +} + +void VulkanRenderer::PreparePresentationFrame(bool mainWindow) +{ + if (!IsSwapchainInfoValid(mainWindow)) + return; + + AcquireNextSwapchainImage(mainWindow); +} + +void VulkanRenderer::ProcessDestructionQueues(size_t commandBufferIndex) +{ + auto& current_descriptor_cache = m_destructionQueues.m_cmd_descriptor_set_objects[commandBufferIndex]; + if (!current_descriptor_cache.empty()) + { + assert_dbg(); + //for (const auto& descriptor : current_descriptor_cache) + //{ + // vkFreeDescriptorSets(m_logicalDevice, m_descriptorPool, 1, &descriptor); + // performanceMonitor.vk.numDescriptorSets.decrement(); + //} + + current_descriptor_cache.clear(); + } + + // destroy buffers + for (auto& itr : m_destructionQueues.m_buffers[commandBufferIndex]) + vkDestroyBuffer(m_logicalDevice, itr, nullptr); + m_destructionQueues.m_buffers[commandBufferIndex].clear(); + + // destroy device memory objects + for (auto& itr : m_destructionQueues.m_memory[commandBufferIndex]) + vkFreeMemory(m_logicalDevice, itr, nullptr); + m_destructionQueues.m_memory[commandBufferIndex].clear(); + + // destroy image views + for (auto& itr : m_destructionQueues.m_cmd_image_views[commandBufferIndex]) + vkDestroyImageView(m_logicalDevice, itr, nullptr); + m_destructionQueues.m_cmd_image_views[commandBufferIndex].clear(); + + // destroy host textures + for (auto itr : m_destructionQueues.m_host_textures[commandBufferIndex]) + delete itr; + m_destructionQueues.m_host_textures[commandBufferIndex].clear(); + + ProcessDestructionQueue2(); +} + +void VulkanRenderer::InitFirstCommandBuffer() +{ + cemu_assert_debug(m_state.currentCommandBuffer == nullptr); + // m_commandBufferIndex always points to the currently used command buffer, so we set it to 0 + m_commandBufferIndex = 0; + m_commandBufferSyncIndex = 0; + + m_state.currentCommandBuffer = m_commandBuffers[m_commandBufferIndex]; + vkResetFences(m_logicalDevice, 1, &m_cmd_buffer_fences[m_commandBufferIndex]); + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; + vkBeginCommandBuffer(m_state.currentCommandBuffer, &beginInfo); + + vkCmdSetViewport(m_state.currentCommandBuffer, 0, 1, &m_state.currentViewport); + vkCmdSetScissor(m_state.currentCommandBuffer, 0, 1, &m_state.currentScissorRect); + + m_state.resetCommandBufferState(); +} + +void VulkanRenderer::ProcessFinishedCommandBuffers() +{ + bool finishedCmdBuffers = false; + while (m_commandBufferSyncIndex != m_commandBufferIndex) + { + VkResult fenceStatus = vkGetFenceStatus(m_logicalDevice, m_cmd_buffer_fences[m_commandBufferSyncIndex]); + if (fenceStatus == VK_SUCCESS) + { + ProcessDestructionQueues(m_commandBufferSyncIndex); + m_commandBufferSyncIndex = (m_commandBufferSyncIndex + 1) % m_commandBuffers.size(); + memoryManager->cleanupBuffers(m_countCommandBufferFinished); + m_countCommandBufferFinished++; + finishedCmdBuffers = true; + continue; + } + else if (fenceStatus == VK_NOT_READY) + { + // not signaled + break; + } + forceLog_printf("vkGetFenceStatus returned unexpected error %d", (sint32)fenceStatus); + cemu_assert_debug(false); + } + if (finishedCmdBuffers) + { + LatteTextureReadback_UpdateFinishedTransfers(false); + } +} + +void VulkanRenderer::WaitForNextFinishedCommandBuffer() +{ + cemu_assert_debug(m_commandBufferSyncIndex != m_commandBufferIndex); + // wait on least recently submitted command buffer + VkResult result = vkWaitForFences(m_logicalDevice, 1, &m_cmd_buffer_fences[m_commandBufferSyncIndex], true, UINT64_MAX); + if (result == VK_TIMEOUT) + { + forceLog_printf("vkWaitForFences: Returned VK_TIMEOUT on infinite fence"); + } + else if (result != VK_SUCCESS) + { + forceLog_printf("vkWaitForFences: Returned unhandled error %d", (sint32)result); + } + // process + ProcessFinishedCommandBuffers(); +} + +void VulkanRenderer::SubmitCommandBuffer(VkSemaphore* signalSemaphore, VkSemaphore* waitSemaphore) +{ + draw_endRenderPass(); + + occlusionQuery_notifyEndCommandBuffer(); + + vkEndCommandBuffer(m_state.currentCommandBuffer); + + VkSubmitInfo submitInfo = {}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &m_state.currentCommandBuffer; + + // signal current command buffer semaphore + VkSemaphore signalSemArray[2]; + if (signalSemaphore) + { + submitInfo.signalSemaphoreCount = 2; + signalSemArray[0] = m_commandBufferSemaphores[m_commandBufferIndex]; // signal current + signalSemArray[1] = *signalSemaphore; // signal current + submitInfo.pSignalSemaphores = signalSemArray; + } + else + { + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = &m_commandBufferSemaphores[m_commandBufferIndex]; // signal current + } + + // wait for previous command buffer semaphore + VkSemaphore prevSem = GetLastSubmittedCmdBufferSemaphore(); + const VkPipelineStageFlags semWaitStageMask[2] = { VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT }; + VkSemaphore waitSemArray[2]; + submitInfo.waitSemaphoreCount = 0; + if (m_numSubmittedCmdBuffers > 0) + waitSemArray[submitInfo.waitSemaphoreCount++] = prevSem; // wait on semaphore from previous submit + if (waitSemaphore) + waitSemArray[submitInfo.waitSemaphoreCount++] = *waitSemaphore; + submitInfo.pWaitDstStageMask = semWaitStageMask; + submitInfo.pWaitSemaphores = waitSemArray; + + const VkResult result = vkQueueSubmit(m_graphicsQueue, 1, &submitInfo, m_cmd_buffer_fences[m_commandBufferIndex]); + if (result != VK_SUCCESS) + UnrecoverableError(fmt::format("failed to submit command buffer. Error {}", result).c_str()); + m_numSubmittedCmdBuffers++; + + // check if any previously submitted command buffers have finished execution + ProcessFinishedCommandBuffers(); + + // acquire next command buffer + auto nextCmdBufferIndex = (m_commandBufferIndex + 1) % m_commandBuffers.size(); + if (nextCmdBufferIndex == m_commandBufferSyncIndex) + { + // force wait for the next command buffer + forceLogDebug_printf("Vulkan: Waiting for available command buffer..."); + WaitForNextFinishedCommandBuffer(); + } + m_commandBufferIndex = nextCmdBufferIndex; + + + m_state.currentCommandBuffer = m_commandBuffers[m_commandBufferIndex]; + vkResetFences(m_logicalDevice, 1, &m_cmd_buffer_fences[m_commandBufferIndex]); + vkResetCommandBuffer(m_state.currentCommandBuffer, 0); + + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; + vkBeginCommandBuffer(m_state.currentCommandBuffer, &beginInfo); + + // make sure some states are set for this command buffer + vkCmdSetViewport(m_state.currentCommandBuffer, 0, 1, &m_state.currentViewport); + vkCmdSetScissor(m_state.currentCommandBuffer, 0, 1, &m_state.currentScissorRect); + + // DEBUG + //debug_genericBarrier(); + + // reset states which are bound to a command buffer + m_state.resetCommandBufferState(); + + occlusionQuery_notifyBeginCommandBuffer(); + + m_recordedDrawcalls = 0; + m_submitThreshold = 500; // this used to be 750 before 1.25.5, but more frequent submission is actually better for latency + m_submitOnIdle = false; +} + +// submit within next 10 drawcalls +void VulkanRenderer::RequestSubmitSoon() +{ + m_submitThreshold = std::min(m_submitThreshold, m_recordedDrawcalls + 10); +} + +// command buffer will be submitted when GPU has no more commands to process or when threshold is reached +void VulkanRenderer::RequestSubmitOnIdle() +{ + m_submitOnIdle = true; +} + +uint64 VulkanRenderer::GetCurrentCommandBufferId() const +{ + return m_numSubmittedCmdBuffers; +} + +bool VulkanRenderer::HasCommandBufferFinished(uint64 commandBufferId) const +{ + return m_countCommandBufferFinished > commandBufferId; +} + +void VulkanRenderer::WaitCommandBufferFinished(uint64 commandBufferId) +{ + if (commandBufferId == m_numSubmittedCmdBuffers) + SubmitCommandBuffer(); + while (HasCommandBufferFinished(commandBufferId) == false) + WaitForNextFinishedCommandBuffer(); +} + +void VulkanRenderer::PipelineCacheSaveThread(size_t cache_size) +{ + const auto dir = ActiveSettings::GetPath("shaderCache/driver/vk"); + if (!fs::exists(dir)) + { + try + { + fs::create_directories(dir); + } + catch (const std::exception& ex) + { + forceLog_printf("can't create vulkan pipeline cache directory \"%s\": %s", dir.generic_string().c_str(), ex.what()); + return; + } + } + + const auto filename = dir / fmt::format(L"{:016x}.bin", CafeSystem::GetForegroundTitleId()); + + while (true) + { + if (m_destructionRequested) + return; + m_pipeline_cache_semaphore.wait(); + if (m_destructionRequested) + return; + for (sint32 i = 0; i < 15 * 4; i++) + { + if (m_destructionRequested) + return; + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + } + + // always prioritize the compiler threads over this thread + // avoid calling stalling lock() since it will block other threads from entering even when the lock is currently held in shared mode + while (!m_pipeline_cache_save_mutex.try_lock()) + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + + size_t size = 0; + VkResult res = vkGetPipelineCacheData(m_logicalDevice, m_pipeline_cache, &size, nullptr); + if (res == VK_SUCCESS && size > 0 && size != cache_size) + { + std::vector<uint8_t> cacheData(size); + res = vkGetPipelineCacheData(m_logicalDevice, m_pipeline_cache, &size, cacheData.data()); + m_pipeline_cache_semaphore.reset(); + m_pipeline_cache_save_mutex.unlock(); + + if (res == VK_SUCCESS) + { + + auto file = std::ofstream(filename, std::ios::out | std::ios::binary); + if (file.is_open()) + { + file.write((char*)cacheData.data(), cacheData.size()); + file.close(); + + cache_size = size; + forceLogDebug_printf("pipeline cache saved"); + } + else + { + forceLog_printf("can't write pipeline cache to disk"); + } + } + else + { + forceLog_printf("can't retrieve pipeline cache data: 0x%x", res); + } + } + else + { + m_pipeline_cache_semaphore.reset(); + m_pipeline_cache_save_mutex.unlock(); + } + } +} + +void VulkanRenderer::CreatePipelineCache() +{ + std::vector<uint8_t> cacheData; + const auto dir = ActiveSettings::GetPath("shaderCache/driver/vk"); + if (fs::exists(dir)) + { + const auto filename = dir / fmt::format("{:016x}.bin", CafeSystem::GetForegroundTitleId()); + + auto file = std::ifstream(filename, std::ios::in | std::ios::binary | std::ios::ate); + if (file.is_open()) + { + const size_t fileSize = file.tellg(); + file.seekg(0, std::ifstream::beg); + + cacheData.resize(fileSize); + file.read((char*)cacheData.data(), cacheData.size()); + file.close(); + + forceLogDebug_printf("pipeline cache loaded"); + } + } + + VkPipelineCacheCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; + createInfo.initialDataSize = cacheData.size(); + createInfo.pInitialData = cacheData.data(); + VkResult result = vkCreatePipelineCache(m_logicalDevice, &createInfo, nullptr, &m_pipeline_cache); + if (result != VK_SUCCESS) + UnrecoverableError(fmt::format("Failed to create pipeline cache: {}", result).c_str()); + + size_t cache_size = 0; + vkGetPipelineCacheData(m_logicalDevice, m_pipeline_cache, &cache_size, nullptr); + + m_pipeline_cache_save_thread = std::thread(&VulkanRenderer::PipelineCacheSaveThread, this, cache_size); +} + +void VulkanRenderer::swapchain_createDescriptorSetLayout() +{ + VkDescriptorSetLayoutBinding samplerLayoutBinding = {}; + samplerLayoutBinding.binding = 0; + samplerLayoutBinding.descriptorCount = 1; + samplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + samplerLayoutBinding.pImmutableSamplers = nullptr; + samplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + + VkDescriptorSetLayoutBinding bindings[] = { samplerLayoutBinding }; + VkDescriptorSetLayoutCreateInfo layoutInfo = {}; + layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layoutInfo.bindingCount = std::size(bindings); + layoutInfo.pBindings = bindings; + + if (vkCreateDescriptorSetLayout(m_logicalDevice, &layoutInfo, nullptr, &m_swapchainDescriptorSetLayout) != VK_SUCCESS) + UnrecoverableError("failed to create descriptor set layout for swapchain"); +} + +void VulkanRenderer::GetTextureFormatInfoVK(Latte::E_GX2SURFFMT format, bool isDepth, Latte::E_DIM dim, sint32 width, sint32 height, FormatInfoVK* formatInfoOut) +{ + formatInfoOut->texelCountX = width; + formatInfoOut->texelCountY = height; + formatInfoOut->isCompressed = false; + if (isDepth) + { + switch (format) + { + case Latte::E_GX2SURFFMT::D24_S8_UNORM: + if (m_supportedFormatInfo.fmt_d24_unorm_s8_uint == false) + { + formatInfoOut->vkImageFormat = VK_FORMAT_D32_SFLOAT_S8_UINT; + formatInfoOut->vkImageAspect = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; + formatInfoOut->decoder = TextureDecoder_NullData64::getInstance(); + } + else + { + formatInfoOut->vkImageFormat = VK_FORMAT_D24_UNORM_S8_UINT; + formatInfoOut->vkImageAspect = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; + formatInfoOut->decoder = TextureDecoder_D24_S8::getInstance(); + } + break; + case Latte::E_GX2SURFFMT::D24_S8_FLOAT: + // alternative format + formatInfoOut->vkImageFormat = VK_FORMAT_D32_SFLOAT_S8_UINT; + formatInfoOut->vkImageAspect = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; + formatInfoOut->decoder = TextureDecoder_NullData64::getInstance(); + break; + case Latte::E_GX2SURFFMT::D32_FLOAT: + formatInfoOut->vkImageFormat = VK_FORMAT_D32_SFLOAT; + formatInfoOut->vkImageAspect = VK_IMAGE_ASPECT_DEPTH_BIT; + formatInfoOut->decoder = TextureDecoder_R32_FLOAT::getInstance(); + break; + case Latte::E_GX2SURFFMT::D16_UNORM: + formatInfoOut->vkImageFormat = VK_FORMAT_D16_UNORM; + formatInfoOut->vkImageAspect = VK_IMAGE_ASPECT_DEPTH_BIT; + formatInfoOut->decoder = TextureDecoder_R16_UNORM::getInstance(); + break; + case Latte::E_GX2SURFFMT::D32_S8_FLOAT: + formatInfoOut->vkImageFormat = VK_FORMAT_D32_SFLOAT_S8_UINT; + formatInfoOut->vkImageAspect = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; + formatInfoOut->decoder = TextureDecoder_D32_S8_UINT_X24::getInstance(); + break; + default: + forceLog_printf("Unsupported depth texture format %04x", (uint32)format); + // default to placeholder format + formatInfoOut->vkImageFormat = VK_FORMAT_D16_UNORM; + formatInfoOut->vkImageAspect = VK_IMAGE_ASPECT_DEPTH_BIT; + formatInfoOut->decoder = nullptr; + break; + } + } + else + { + formatInfoOut->vkImageAspect = VK_IMAGE_ASPECT_COLOR_BIT; + switch (format) + { + // RGBA formats + case Latte::E_GX2SURFFMT::R32_G32_B32_A32_FLOAT: + formatInfoOut->vkImageFormat = VK_FORMAT_R32G32B32A32_SFLOAT; + formatInfoOut->decoder = TextureDecoder_R32_G32_B32_A32_FLOAT::getInstance(); + break; + case Latte::E_GX2SURFFMT::R32_G32_B32_A32_UINT: + formatInfoOut->vkImageFormat = VK_FORMAT_R32G32B32A32_UINT; + formatInfoOut->decoder = TextureDecoder_R32_G32_B32_A32_UINT::getInstance(); + break; + case Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT: + formatInfoOut->vkImageFormat = VK_FORMAT_R16G16B16A16_SFLOAT; + formatInfoOut->decoder = TextureDecoder_R16_G16_B16_A16_FLOAT::getInstance(); + break; + case Latte::E_GX2SURFFMT::R16_G16_B16_A16_UINT: + formatInfoOut->vkImageFormat = VK_FORMAT_R16G16B16A16_UINT; + formatInfoOut->decoder = TextureDecoder_R16_G16_B16_A16_UINT::getInstance(); + break; + case Latte::E_GX2SURFFMT::R16_G16_B16_A16_UNORM: + formatInfoOut->vkImageFormat = VK_FORMAT_R16G16B16A16_UNORM; + formatInfoOut->decoder = TextureDecoder_R16_G16_B16_A16::getInstance(); + break; + case Latte::E_GX2SURFFMT::R16_G16_B16_A16_SNORM: + formatInfoOut->vkImageFormat = VK_FORMAT_R16G16B16A16_SNORM; + formatInfoOut->decoder = TextureDecoder_R16_G16_B16_A16::getInstance(); + break; + case Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM: + formatInfoOut->vkImageFormat = VK_FORMAT_R8G8B8A8_UNORM; + formatInfoOut->decoder = TextureDecoder_R8_G8_B8_A8::getInstance(); + break; + case Latte::E_GX2SURFFMT::R8_G8_B8_A8_SNORM: + formatInfoOut->vkImageFormat = VK_FORMAT_R8G8B8A8_SNORM; + formatInfoOut->decoder = TextureDecoder_R8_G8_B8_A8::getInstance(); + break; + case Latte::E_GX2SURFFMT::R8_G8_B8_A8_SRGB: + formatInfoOut->vkImageFormat = VK_FORMAT_R8G8B8A8_SRGB; + formatInfoOut->decoder = TextureDecoder_R8_G8_B8_A8::getInstance(); + break; + case Latte::E_GX2SURFFMT::R8_G8_B8_A8_UINT: + formatInfoOut->vkImageFormat = VK_FORMAT_R8G8B8A8_UINT; + formatInfoOut->decoder = TextureDecoder_R8_G8_B8_A8::getInstance(); + break; + case Latte::E_GX2SURFFMT::R8_G8_B8_A8_SINT: + formatInfoOut->vkImageFormat = VK_FORMAT_R8G8B8A8_SINT; + formatInfoOut->decoder = TextureDecoder_R8_G8_B8_A8::getInstance(); + break; + // RG formats + case Latte::E_GX2SURFFMT::R32_G32_FLOAT: + formatInfoOut->vkImageFormat = VK_FORMAT_R32G32_SFLOAT; + formatInfoOut->decoder = TextureDecoder_R32_G32_FLOAT::getInstance(); + break; + case Latte::E_GX2SURFFMT::R32_G32_UINT: + formatInfoOut->vkImageFormat = VK_FORMAT_R32G32_UINT; + formatInfoOut->decoder = TextureDecoder_R32_G32_UINT::getInstance(); + break; + case Latte::E_GX2SURFFMT::R16_G16_UNORM: + formatInfoOut->vkImageFormat = VK_FORMAT_R16G16_UNORM; + formatInfoOut->decoder = TextureDecoder_R16_G16::getInstance(); + break; + case Latte::E_GX2SURFFMT::R16_G16_FLOAT: + formatInfoOut->vkImageFormat = VK_FORMAT_R16G16_SFLOAT; + formatInfoOut->decoder = TextureDecoder_R16_G16_FLOAT::getInstance(); + break; + case Latte::E_GX2SURFFMT::R8_G8_UNORM: + formatInfoOut->vkImageFormat = VK_FORMAT_R8G8_UNORM; + formatInfoOut->decoder = TextureDecoder_R8_G8::getInstance(); + break; + case Latte::E_GX2SURFFMT::R8_G8_SNORM: + formatInfoOut->vkImageFormat = VK_FORMAT_R8G8_SNORM; + formatInfoOut->decoder = TextureDecoder_R8_G8::getInstance(); + break; + case Latte::E_GX2SURFFMT::R4_G4_UNORM: + if (m_supportedFormatInfo.fmt_r4g4_unorm_pack == false) + { + formatInfoOut->vkImageFormat = VK_FORMAT_R4G4B4A4_UNORM_PACK16; + formatInfoOut->decoder = TextureDecoder_R4_G4_UNORM_toRGBA4444_vk::getInstance(); + } + else + { + formatInfoOut->vkImageFormat = VK_FORMAT_R4G4_UNORM_PACK8; + formatInfoOut->decoder = TextureDecoder_R4_G4::getInstance(); // todo - verify if order of R/G matches between GX2/Vulkan + } + break; + // R formats + case Latte::E_GX2SURFFMT::R32_FLOAT: + formatInfoOut->vkImageFormat = VK_FORMAT_R32_SFLOAT; + formatInfoOut->decoder = TextureDecoder_R32_FLOAT::getInstance(); + break; + case Latte::E_GX2SURFFMT::R32_UINT: + formatInfoOut->vkImageFormat = VK_FORMAT_R32_UINT; + formatInfoOut->decoder = TextureDecoder_R32_UINT::getInstance(); + break; + case Latte::E_GX2SURFFMT::R16_FLOAT: + formatInfoOut->vkImageFormat = VK_FORMAT_R16_SFLOAT; + formatInfoOut->decoder = TextureDecoder_R16_FLOAT::getInstance(); + break; + case Latte::E_GX2SURFFMT::R16_UNORM: + formatInfoOut->vkImageFormat = VK_FORMAT_R16_UNORM; + formatInfoOut->decoder = TextureDecoder_R16_UNORM::getInstance(); + break; + case Latte::E_GX2SURFFMT::R16_SNORM: + formatInfoOut->vkImageFormat = VK_FORMAT_R16_SNORM; + formatInfoOut->decoder = TextureDecoder_R16_SNORM::getInstance(); + break; + case Latte::E_GX2SURFFMT::R16_UINT: + formatInfoOut->vkImageFormat = VK_FORMAT_R16_UINT; + formatInfoOut->decoder = TextureDecoder_R16_UINT::getInstance(); + break; + case Latte::E_GX2SURFFMT::R8_UNORM: + formatInfoOut->vkImageFormat = VK_FORMAT_R8_UNORM; + formatInfoOut->decoder = TextureDecoder_R8::getInstance(); + break; + case Latte::E_GX2SURFFMT::R8_SNORM: + formatInfoOut->vkImageFormat = VK_FORMAT_R8_SNORM; + formatInfoOut->decoder = TextureDecoder_R8::getInstance(); + break; + case Latte::E_GX2SURFFMT::R8_UINT: + formatInfoOut->vkImageFormat = VK_FORMAT_R8_UINT; + formatInfoOut->decoder = TextureDecoder_R8_UINT::getInstance(); + break; + // special formats + case Latte::E_GX2SURFFMT::R5_G6_B5_UNORM: + // Vulkan has R in MSB, GPU7 has it in LSB + formatInfoOut->vkImageFormat = VK_FORMAT_R5G6B5_UNORM_PACK16; + formatInfoOut->decoder = TextureDecoder_R5_G6_B5_swappedRB::getInstance(); + break; + case Latte::E_GX2SURFFMT::R5_G5_B5_A1_UNORM: + // used in Super Mario 3D World for the hidden Luigi sprites + // since order of channels is reversed in Vulkan compared to GX2 the format we need is A1B5G5R5 + formatInfoOut->vkImageFormat = VK_FORMAT_A1R5G5B5_UNORM_PACK16; + formatInfoOut->decoder = TextureDecoder_R5_G5_B5_A1_UNORM_swappedRB::getInstance(); + break; + case Latte::E_GX2SURFFMT::A1_B5_G5_R5_UNORM: + // used by VC64 (e.g. Ocarina of Time) + formatInfoOut->vkImageFormat = VK_FORMAT_A1R5G5B5_UNORM_PACK16; // A 15 R 10..14, G 5..9 B 0..4 + formatInfoOut->decoder = TextureDecoder_A1_B5_G5_R5_UNORM_vulkan::getInstance(); + break; + case Latte::E_GX2SURFFMT::R11_G11_B10_FLOAT: + formatInfoOut->vkImageFormat = VK_FORMAT_B10G11R11_UFLOAT_PACK32; // verify if order of channels is still the same as GX2 + formatInfoOut->decoder = TextureDecoder_R11_G11_B10_FLOAT::getInstance(); + break; + case Latte::E_GX2SURFFMT::R4_G4_B4_A4_UNORM: + formatInfoOut->vkImageFormat = VK_FORMAT_R4G4B4A4_UNORM_PACK16; // verify order of channels + formatInfoOut->decoder = TextureDecoder_R4_G4_B4_A4_UNORM::getInstance(); + break; + // special formats - R10G10B10_A2 + case Latte::E_GX2SURFFMT::R10_G10_B10_A2_UNORM: + formatInfoOut->vkImageFormat = VK_FORMAT_A2B10G10R10_UNORM_PACK32; // todo - verify + formatInfoOut->decoder = TextureDecoder_R10_G10_B10_A2_UNORM::getInstance(); + break; + case Latte::E_GX2SURFFMT::R10_G10_B10_A2_SNORM: + formatInfoOut->vkImageFormat = VK_FORMAT_R16G16B16A16_SNORM; // Vulkan has VK_FORMAT_A2R10G10B10_SNORM_PACK32 but it doesnt work? + formatInfoOut->decoder = TextureDecoder_R10_G10_B10_A2_SNORM_toRGBA16::getInstance(); + break; + case Latte::E_GX2SURFFMT::R10_G10_B10_A2_SRGB: + //formatInfoOut->vkImageFormat = VK_FORMAT_R16G16B16A16_SNORM; // Vulkan has no uncompressed SRGB format with more than 8 bits per channel + //formatInfoOut->decoder = TextureDecoder_R10_G10_B10_A2_SNORM_toRGBA16::getInstance(); + //break; + formatInfoOut->vkImageFormat = VK_FORMAT_A2B10G10R10_UNORM_PACK32; // todo - verify + formatInfoOut->decoder = TextureDecoder_R10_G10_B10_A2_UNORM::getInstance(); + break; + // compressed formats + case Latte::E_GX2SURFFMT::BC1_SRGB: + formatInfoOut->vkImageFormat = VK_FORMAT_BC1_RGBA_SRGB_BLOCK; // todo - verify + formatInfoOut->decoder = TextureDecoder_BC1::getInstance(); + break; + case Latte::E_GX2SURFFMT::BC1_UNORM: + formatInfoOut->vkImageFormat = VK_FORMAT_BC1_RGBA_UNORM_BLOCK; // todo - verify + formatInfoOut->decoder = TextureDecoder_BC1::getInstance(); + break; + case Latte::E_GX2SURFFMT::BC2_UNORM: + formatInfoOut->vkImageFormat = VK_FORMAT_BC2_UNORM_BLOCK; // todo - verify + formatInfoOut->decoder = TextureDecoder_BC2::getInstance(); + break; + case Latte::E_GX2SURFFMT::BC2_SRGB: + formatInfoOut->vkImageFormat = VK_FORMAT_BC2_SRGB_BLOCK; // todo - verify + formatInfoOut->decoder = TextureDecoder_BC2::getInstance(); + break; + case Latte::E_GX2SURFFMT::BC3_UNORM: + formatInfoOut->vkImageFormat = VK_FORMAT_BC3_UNORM_BLOCK; + formatInfoOut->decoder = TextureDecoder_BC3::getInstance(); + break; + case Latte::E_GX2SURFFMT::BC3_SRGB: + formatInfoOut->vkImageFormat = VK_FORMAT_BC3_SRGB_BLOCK; + formatInfoOut->decoder = TextureDecoder_BC3::getInstance(); + break; + case Latte::E_GX2SURFFMT::BC4_UNORM: + formatInfoOut->vkImageFormat = VK_FORMAT_BC4_UNORM_BLOCK; + formatInfoOut->decoder = TextureDecoder_BC4::getInstance(); + break; + case Latte::E_GX2SURFFMT::BC4_SNORM: + formatInfoOut->vkImageFormat = VK_FORMAT_BC4_SNORM_BLOCK; + formatInfoOut->decoder = TextureDecoder_BC4::getInstance(); + break; + case Latte::E_GX2SURFFMT::BC5_UNORM: + formatInfoOut->vkImageFormat = VK_FORMAT_BC5_UNORM_BLOCK; + formatInfoOut->decoder = TextureDecoder_BC5::getInstance(); + break; + case Latte::E_GX2SURFFMT::BC5_SNORM: + formatInfoOut->vkImageFormat = VK_FORMAT_BC5_SNORM_BLOCK; + formatInfoOut->decoder = TextureDecoder_BC5::getInstance(); + break; + case Latte::E_GX2SURFFMT::R24_X8_UNORM: + formatInfoOut->vkImageFormat = VK_FORMAT_R32_SFLOAT; + formatInfoOut->decoder = TextureDecoder_R24_X8::getInstance(); + break; + case Latte::E_GX2SURFFMT::X24_G8_UINT: + // used by Color Splash and Resident Evil + formatInfoOut->vkImageFormat = VK_FORMAT_R8G8B8A8_UINT; // todo - should we use ABGR format? + formatInfoOut->decoder = TextureDecoder_X24_G8_UINT::getInstance(); // todo - verify + default: + forceLog_printf("Unsupported color texture format %04x\n", (uint32)format); + cemu_assert_debug(false); + } + } +} + +VkPipelineShaderStageCreateInfo VulkanRenderer::CreatePipelineShaderStageCreateInfo(VkShaderStageFlagBits stage, VkShaderModule& module, const char* entryName) const +{ + VkPipelineShaderStageCreateInfo shaderStageInfo{}; + shaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + shaderStageInfo.stage = stage; + shaderStageInfo.module = module; + shaderStageInfo.pName = entryName; + return shaderStageInfo; +} + +VkPipeline VulkanRenderer::backbufferBlit_createGraphicsPipeline(VkDescriptorSetLayout descriptorLayout, bool padView, RendererOutputShader* shader) +{ + auto& swap_info = !padView ? *m_mainSwapchainInfo : *m_padSwapchainInfo; + + RendererShaderVk* vertexRendererShader = static_cast<RendererShaderVk*>(shader->GetVertexShader()); + RendererShaderVk* fragmentRendererShader = static_cast<RendererShaderVk*>(shader->GetFragmentShader()); + + uint64 hash = 0; + hash += (uint64)vertexRendererShader; + hash += (uint64)fragmentRendererShader; + hash += (uint64)(padView ? m_drvBufferUsesSRGB : m_tvBufferUsesSRGB); + hash += ((uint64)padView) << 1; + + static std::unordered_map<uint64, VkPipeline> s_pipeline_cache; + const auto it = s_pipeline_cache.find(hash); + if (it != s_pipeline_cache.cend()) + return it->second; + + std::vector<VkPipelineShaderStageCreateInfo> shaderStages; + if (vertexRendererShader) + shaderStages.emplace_back(CreatePipelineShaderStageCreateInfo(VK_SHADER_STAGE_VERTEX_BIT, vertexRendererShader->GetShaderModule(), "main")); + + if (fragmentRendererShader) + shaderStages.emplace_back(CreatePipelineShaderStageCreateInfo(VK_SHADER_STAGE_FRAGMENT_BIT, fragmentRendererShader->GetShaderModule(), "main")); + + VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertexInputInfo.vertexBindingDescriptionCount = 0; + vertexInputInfo.vertexAttributeDescriptionCount = 0; + + VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssembly.primitiveRestartEnable = VK_FALSE; + + VkPipelineViewportStateCreateInfo viewportState{}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.scissorCount = 1; + + VkDynamicState dynamicStates[] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; + + VkPipelineDynamicStateCreateInfo dynamicState = {}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = std::size(dynamicStates); + dynamicState.pDynamicStates = dynamicStates; + + VkPipelineRasterizationStateCreateInfo rasterizer{}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; + rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + + VkPipelineMultisampleStateCreateInfo multisampling{}; + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + VkPipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = VK_FALSE; + + VkPipelineColorBlendStateCreateInfo colorBlending{}; + colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlending.logicOpEnable = VK_FALSE; + colorBlending.logicOp = VK_LOGIC_OP_COPY; + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.blendConstants[0] = 0.0f; + colorBlending.blendConstants[1] = 0.0f; + colorBlending.blendConstants[2] = 0.0f; + colorBlending.blendConstants[3] = 0.0f; + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = 1; + pipelineLayoutInfo.pSetLayouts = &descriptorLayout; + + VkResult result = vkCreatePipelineLayout(m_logicalDevice, &pipelineLayoutInfo, nullptr, &m_pipelineLayout); + if (result != VK_SUCCESS) + throw std::runtime_error(fmt::format("Failed to create pipeline layout: {}", result)); + + VkGraphicsPipelineCreateInfo pipelineInfo = {}; + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineInfo.stageCount = shaderStages.size(); + pipelineInfo.pStages = shaderStages.data(); + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pDynamicState = &dynamicState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.layout = m_pipelineLayout; + pipelineInfo.renderPass = swap_info.m_swapChainRenderPass; + pipelineInfo.subpass = 0; + pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; + + VkPipeline pipeline = nullptr; + std::shared_lock lock(m_pipeline_cache_save_mutex); + result = vkCreateGraphicsPipelines(m_logicalDevice, m_pipeline_cache, 1, &pipelineInfo, nullptr, &pipeline); + if (result != VK_SUCCESS) + { + forceLog_printf("Failed to create graphics pipeline. Error %d", result); + throw std::runtime_error(fmt::format("Failed to create graphics pipeline: {}", result)); + } + + s_pipeline_cache[hash] = pipeline; + m_pipeline_cache_semaphore.notify(); + + return pipeline; +} + +void VulkanRenderer::AcquireNextSwapchainImage(bool main_window) +{ + auto& swapInfo = main_window ? *m_mainSwapchainInfo : *m_padSwapchainInfo; + if (swapInfo.swapchainImageIndex != -1) + return; // image already reserved + + + vkWaitForFences(m_logicalDevice, 1, &swapInfo.m_imageAvailableFence, VK_TRUE, std::numeric_limits<uint64_t>::max()); + vkResetFences(m_logicalDevice, 1, &swapInfo.m_imageAvailableFence); + + auto& acquireInfo = swapInfo.m_acquireInfo[swapInfo.m_acquireIndex]; + + VkResult result = vkAcquireNextImageKHR(m_logicalDevice, swapInfo.swapChain, std::numeric_limits<uint64_t>::max(), acquireInfo.acquireSemaphore, swapInfo.m_imageAvailableFence, &swapInfo.swapchainImageIndex); + if (result != VK_SUCCESS) + { + while (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) // todo: handle error state correctly. Looping doesnt always make sense? + { + try + { + RecreateSwapchain(main_window); + if (vkWaitForFences(m_logicalDevice, 1, &swapInfo.m_imageAvailableFence, VK_TRUE, 0) == VK_SUCCESS) + vkResetFences(m_logicalDevice, 1, &swapInfo.m_imageAvailableFence); + result = vkAcquireNextImageKHR(m_logicalDevice, swapInfo.swapChain, std::numeric_limits<uint64_t>::max(), acquireInfo.acquireSemaphore, swapInfo.m_imageAvailableFence, &swapInfo.swapchainImageIndex); + if (result == VK_SUCCESS) + return; + } + catch (std::exception&) {} + + std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + + throw std::runtime_error(fmt::format("Failed to acquire next image: {}", result)); + } + + swapInfo.m_acquireIndex = (swapInfo.m_acquireIndex + 1) % swapInfo.m_acquireInfo.size(); + + SubmitCommandBuffer(nullptr, &acquireInfo.acquireSemaphore); +} + +void VulkanRenderer::RecreateSwapchain(bool main_window) +{ + SubmitCommandBuffer(); + vkDeviceWaitIdle(m_logicalDevice); + auto& swapinfo = main_window ? *m_mainSwapchainInfo : *m_padSwapchainInfo; + vkWaitForFences(m_logicalDevice, 1, &swapinfo.m_imageAvailableFence, VK_TRUE, + std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::milliseconds(10)).count() + ); + + Vector2i size; + if (main_window) + { + ImGui_ImplVulkan_Shutdown(); + gui_getWindowSize(&size.x, &size.y); + } + else + { + gui_getPadWindowSize(&size.x, &size.y); + } + + if (swapinfo.swapChain != VK_NULL_HANDLE) + { + // todo - for some reason using the swapchain replacement method (old var being set) causes crashes and other issues + vkDestroySwapchainKHR(m_logicalDevice, swapinfo.swapChain, nullptr); + swapinfo.m_swapchainImages.clear(); // swapchain images are automatically destroyed + swapinfo.swapChain = VK_NULL_HANDLE; + } + + swapinfo.swapChain = nullptr; + swapinfo.swapChain = CreateSwapChain(swapinfo, size, main_window); + swapinfo.swapchainImageIndex = -1; + + if (main_window) + ImguiInit(); +} + +void VulkanRenderer::SwapBuffer(bool main_window) +{ + auto& swapInfo = main_window ? *m_mainSwapchainInfo : *m_padSwapchainInfo; + + if (main_window) + { + const bool resize = m_swapchainState.resizeRequestedMainWindow; + m_swapchainState.resizeRequestedMainWindow = false; + + if (resize || m_tvBufferUsesSRGB != LatteGPUState.tvBufferUsesSRGB) + { + try + { + RecreateSwapchain(main_window); + m_tvBufferUsesSRGB = LatteGPUState.tvBufferUsesSRGB; + } + catch (std::exception&) { cemu_assert_debug(false); } + return; + } + } + else + { + const bool resize = m_swapchainState.resizeRequestedPadWindow; + m_swapchainState.resizeRequestedPadWindow = false; + + if (resize || m_drvBufferUsesSRGB != LatteGPUState.drcBufferUsesSRGB) + { + try + { + RecreateSwapchain(main_window); + m_drvBufferUsesSRGB = LatteGPUState.drcBufferUsesSRGB; + } + catch (std::exception&) { cemu_assert_debug(false); } + return; + } + } + + auto& swapinfo = main_window ? *m_mainSwapchainInfo : *m_padSwapchainInfo; + AcquireNextSwapchainImage(main_window); + + if ((main_window && m_swapchainState.tvHasDefinedSwapchainImage == false) || + (!main_window && m_swapchainState.drcHasDefinedSwapchainImage == false)) + { + // set the swapchain image to a defined state + VkClearColorValue clearColor{ 0, 0, 0, 0 }; + ClearColorImageRaw(swapInfo.m_swapchainImages[swapInfo.swapchainImageIndex], 0, 0, clearColor, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); + } + + // make sure any writes to the image have finished (is this necessary? End of command buffer implicitly flushes everything?) + VkMemoryBarrier memoryBarrier{}; + memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER; + memoryBarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + memoryBarrier.dstAccessMask = 0; + VkPipelineStageFlags srcStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + VkPipelineStageFlags dstStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStage, dstStage, 0, 1, &memoryBarrier, 0, nullptr, 0, nullptr); + + + VkSemaphore presentSemaphore = swapInfo.m_swapchainPresentSemaphores[swapInfo.swapchainImageIndex]; + SubmitCommandBuffer(&presentSemaphore); // submit all command and signal semaphore + + cemu_assert_debug(m_numSubmittedCmdBuffers > 0); + + + VkPresentInfoKHR presentInfo = {}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = &swapinfo.swapChain; + presentInfo.pImageIndices = &swapinfo.swapchainImageIndex; + // wait on command buffer semaphore + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = &presentSemaphore; + + VkResult result = vkQueuePresentKHR(m_presentQueue, &presentInfo); + if (result != VK_SUCCESS) + { + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) // todo: dont loop but handle error state? + { + int counter = 0; + while (true) + { + try + { + RecreateSwapchain(main_window); + return; + } + catch (std::exception&) + { + // loop until successful + counter++; + if (counter > 25) + { + cemuLog_log(LogType::Force, "Failed to recreate swapchain during SwapBuffer"); + cemuLog_waitForFlush(); + exit(0); + } + } + + std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + } + + cemuLog_log(LogType::Force, fmt::format("vkQueuePresentKHR failed with error {}", result)); + cemuLog_waitForFlush(); + + throw std::runtime_error(fmt::format("Failed to present draw command buffer: {}", result)); + } + + if (main_window) + m_swapchainState.tvHasDefinedSwapchainImage = false; + else + m_swapchainState.drcHasDefinedSwapchainImage = false; + + swapinfo.swapchainImageIndex = -1; +} + +void VulkanRenderer::Flush(bool waitIdle) +{ + if (m_recordedDrawcalls > 0 || m_submitOnIdle) + SubmitCommandBuffer(); + if (waitIdle) + WaitCommandBufferFinished(GetCurrentCommandBufferId()); +} + +void VulkanRenderer::NotifyLatteCommandProcessorIdle() +{ + if (m_submitOnIdle) + SubmitCommandBuffer(); +} + +void VulkanRenderer::SwapBuffers(bool swapTV, bool swapDRC) +{ + SubmitCommandBuffer(); + + if (swapTV && IsSwapchainInfoValid(true)) + SwapBuffer(true); + + if (swapDRC && IsSwapchainInfoValid(false)) + SwapBuffer(false); +} + +void VulkanRenderer::ClearColorbuffer(bool padView) +{ + if (!IsSwapchainInfoValid(!padView)) + return; + + auto& swap_info = padView ? *m_padSwapchainInfo : *m_mainSwapchainInfo; + if (swap_info.swapchainImageIndex == -1) + return; + + VkClearColorValue clearColor{ 0, 0, 0, 0 }; + ClearColorImageRaw(swap_info.m_swapchainImages[swap_info.swapchainImageIndex], 0, 0, clearColor, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL); +} + +void VulkanRenderer::ClearColorImageRaw(VkImage image, uint32 sliceIndex, uint32 mipIndex, const VkClearColorValue& color, VkImageLayout inputLayout, VkImageLayout outputLayout) +{ + draw_endRenderPass(); + + VkImageMemoryBarrier barrier = {}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.oldLayout = inputLayout; + barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.baseMipLevel = mipIndex; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = sliceIndex; + barrier.subresourceRange.layerCount = 1; + + VkPipelineStageFlags srcStages = 0; + VkPipelineStageFlags dstStages = 0; + barrier.srcAccessMask = 0; + barrier.dstAccessMask = 0; + barrier_calcStageAndMask<SYNC_OP::ANY_TRANSFER | SYNC_OP::IMAGE_READ | SYNC_OP::IMAGE_WRITE>(srcStages, barrier.srcAccessMask); + barrier_calcStageAndMask<SYNC_OP::ANY_TRANSFER>(dstStages, barrier.dstAccessMask); + + vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStages, dstStages, 0, 0, nullptr, 0, nullptr, 1, &barrier); + + VkImageSubresourceRange imageRange{}; + imageRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + imageRange.baseArrayLayer = sliceIndex; + imageRange.layerCount = 1; + imageRange.baseMipLevel = mipIndex; + imageRange.levelCount = 1; + + vkCmdClearColorImage(m_state.currentCommandBuffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &color, 1, &imageRange); + + barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + barrier.newLayout = outputLayout; + + srcStages = 0; + dstStages = 0; + barrier.srcAccessMask = 0; + barrier.dstAccessMask = 0; + barrier_calcStageAndMask<SYNC_OP::ANY_TRANSFER>(srcStages, barrier.srcAccessMask); + barrier_calcStageAndMask<SYNC_OP::ANY_TRANSFER | SYNC_OP::IMAGE_READ | SYNC_OP::IMAGE_WRITE>(dstStages, barrier.dstAccessMask); + vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStages, dstStages, 0, 0, nullptr, 0, nullptr, 1, &barrier); +} + +void VulkanRenderer::ClearColorImage(LatteTextureVk* vkTexture, uint32 sliceIndex, uint32 mipIndex, const VkClearColorValue& color, VkImageLayout outputLayout) +{ + VkImageSubresourceRange subresourceRange; + + subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + subresourceRange.baseMipLevel = mipIndex; + subresourceRange.levelCount = 1; + subresourceRange.baseArrayLayer = sliceIndex; + subresourceRange.layerCount = 1; + + auto imageObj = vkTexture->GetImageObj(); + imageObj->flagForCurrentCommandBuffer(); + + VkImageLayout inputLayout = vkTexture->GetImageLayout(subresourceRange); + ClearColorImageRaw(imageObj->m_image, sliceIndex, mipIndex, color, inputLayout, outputLayout); + vkTexture->SetImageLayout(subresourceRange, outputLayout); +} + +void VulkanRenderer::CreateBackbufferIndexBuffer() +{ + const VkDeviceSize bufferSize = sizeof(uint16) * 6; + memoryManager->CreateBuffer(bufferSize, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, m_indexBuffer, m_indexBufferMemory); + + uint16* data; + vkMapMemory(m_logicalDevice, m_indexBufferMemory, 0, bufferSize, 0, (void**)&data); + const uint16 tmp[] = { 0, 1, 2, 3, 4, 5 }; + std::copy(std::begin(tmp), std::end(tmp), data); + vkUnmapMemory(m_logicalDevice, m_indexBufferMemory); +} + +void VulkanRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutputShader* shader, bool useLinearTexFilter, sint32 imageX, sint32 imageY, sint32 imageWidth, sint32 imageHeight, bool padView, bool clearBackground) +{ + if (!IsSwapchainInfoValid(!padView)) + return; + + auto& swapInfo = !padView ? *m_mainSwapchainInfo : *m_padSwapchainInfo; + LatteTextureViewVk* texViewVk = (LatteTextureViewVk*)texView; + draw_endRenderPass(); + + if (clearBackground) + ClearColorbuffer(padView); + + AcquireNextSwapchainImage(!padView); + + // barrier for input texture + VkMemoryBarrier memoryBarrier{}; + memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER; + VkPipelineStageFlags srcStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT; + VkPipelineStageFlags dstStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + memoryBarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; + memoryBarrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_SHADER_READ_BIT; + vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStage, dstStage, 0, 1, &memoryBarrier, 0, nullptr, 0, nullptr); + + auto pipeline = backbufferBlit_createGraphicsPipeline(m_swapchainDescriptorSetLayout, padView, shader); + + VkRenderPassBeginInfo renderPassInfo = {}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = swapInfo.m_swapChainRenderPass; + renderPassInfo.framebuffer = swapInfo.m_swapchainFramebuffers[swapInfo.swapchainImageIndex]; + renderPassInfo.renderArea.offset = { 0, 0 }; + renderPassInfo.renderArea.extent = swapInfo.swapchainExtend; + renderPassInfo.clearValueCount = 0; + + VkViewport viewport{}; + viewport.x = imageX; + viewport.y = imageY; + viewport.width = imageWidth; + viewport.height = imageHeight; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewport(m_state.currentCommandBuffer, 0, 1, &viewport); + + VkRect2D scissor{}; + scissor.extent = swapInfo.swapchainExtend; + vkCmdSetScissor(m_state.currentCommandBuffer, 0, 1, &scissor); + + auto descriptSet = backbufferBlit_createDescriptorSet(m_swapchainDescriptorSetLayout, texViewVk, useLinearTexFilter); + + vkCmdBeginRenderPass(m_state.currentCommandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + + vkCmdBindPipeline(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + m_state.currentPipeline = pipeline; + + vkCmdBindIndexBuffer(m_state.currentCommandBuffer, m_indexBuffer, 0, VK_INDEX_TYPE_UINT16); + + vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout, 0, 1, &descriptSet, 0, nullptr); + + vkCmdDrawIndexed(m_state.currentCommandBuffer, 6, 1, 0, 0, 0); + + vkCmdEndRenderPass(m_state.currentCommandBuffer); + + // restore viewport + vkCmdSetViewport(m_state.currentCommandBuffer, 0, 1, &m_state.currentViewport); + + // mark current swapchain image as well defined + if (padView) + m_swapchainState.drcHasDefinedSwapchainImage = true; + else + m_swapchainState.tvHasDefinedSwapchainImage = true; +} + +void VulkanRenderer::CreateDescriptorPool() +{ + std::array<VkDescriptorPoolSize, 4> poolSizes = {}; + poolSizes[0].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + poolSizes[0].descriptorCount = 1024 * 128; + poolSizes[1].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + poolSizes[1].descriptorCount = 1024 * 1; + poolSizes[2].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; + poolSizes[2].descriptorCount = 1024 * 128; + poolSizes[3].type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + poolSizes[3].descriptorCount = 1024 * 4; + + VkDescriptorPoolCreateInfo poolInfo = {}; + poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + poolInfo.poolSizeCount = poolSizes.size(); + poolInfo.pPoolSizes = poolSizes.data(); + poolInfo.maxSets = 1024 * 256; + poolInfo.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT; + + if (vkCreateDescriptorPool(m_logicalDevice, &poolInfo, nullptr, &m_descriptorPool) != VK_SUCCESS) + UnrecoverableError("Failed to create descriptor pool!"); +} + +VkDescriptorSet VulkanRenderer::backbufferBlit_createDescriptorSet(VkDescriptorSetLayout descriptor_set_layout, LatteTextureViewVk* texViewVk, bool useLinearTexFilter) +{ + uint64 hash = 0; + hash += (uint64)texViewVk->GetViewRGBA(); + hash += (uint64)texViewVk->GetDefaultTextureSampler(useLinearTexFilter); + + static std::unordered_map<uint64, VkDescriptorSet> s_set_cache; + const auto it = s_set_cache.find(hash); + if (it != s_set_cache.cend()) + return it->second; + + VkDescriptorSetAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = m_descriptorPool; + allocInfo.descriptorSetCount = 1; + allocInfo.pSetLayouts = &descriptor_set_layout; + + VkDescriptorSet result; + if (vkAllocateDescriptorSets(m_logicalDevice, &allocInfo, &result) != VK_SUCCESS) + UnrecoverableError("Failed to allocate descriptor sets for backbuffer blit"); + performanceMonitor.vk.numDescriptorSets.increment(); + + VkDescriptorImageInfo imageInfo = {}; + imageInfo.imageLayout = VK_IMAGE_LAYOUT_GENERAL; + imageInfo.imageView = texViewVk->GetViewRGBA()->m_textureImageView; + imageInfo.sampler = texViewVk->GetDefaultTextureSampler(useLinearTexFilter); + + VkWriteDescriptorSet descriptorWrites = {}; + descriptorWrites.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites.dstSet = result; + descriptorWrites.dstBinding = 0; + descriptorWrites.dstArrayElement = 0; + descriptorWrites.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + descriptorWrites.descriptorCount = 1; + descriptorWrites.pImageInfo = &imageInfo; + + vkUpdateDescriptorSets(m_logicalDevice, 1, &descriptorWrites, 0, nullptr); + performanceMonitor.vk.numDescriptorSamplerTextures.increment(); + + s_set_cache[hash] = result; + return result; +} + +void VulkanRenderer::renderTarget_setViewport(float x, float y, float width, float height, float nearZ, float farZ, bool halfZ) +{ + // the Vulkan renderer handles halfZ in the vertex shader + + float vpNewX = x; + float vpNewY = y + height; + float vpNewWidth = width; + float vpNewHeight = -height; + + if (m_state.currentViewport.x == vpNewX && m_state.currentViewport.y == vpNewY && m_state.currentViewport.width == vpNewWidth && m_state.currentViewport.height == vpNewHeight && m_state.currentViewport.minDepth == nearZ && m_state.currentViewport.maxDepth == farZ) + return; // viewport did not change + + m_state.currentViewport.x = vpNewX; + m_state.currentViewport.y = vpNewY; + m_state.currentViewport.width = vpNewWidth; + m_state.currentViewport.height = vpNewHeight; + + m_state.currentViewport.minDepth = nearZ; + m_state.currentViewport.maxDepth = farZ; + + vkCmdSetViewport(m_state.currentCommandBuffer, 0, 1, &m_state.currentViewport); +} + + +void VulkanRenderer::renderTarget_setScissor(sint32 scissorX, sint32 scissorY, sint32 scissorWidth, sint32 scissorHeight) +{ + m_state.currentScissorRect.offset.x = scissorX; + m_state.currentScissorRect.offset.y = scissorY; + m_state.currentScissorRect.extent.width = scissorWidth; + m_state.currentScissorRect.extent.height = scissorHeight; + vkCmdSetScissor(m_state.currentCommandBuffer, 0, 1, &m_state.currentScissorRect); +} + +LatteCachedFBO* VulkanRenderer::rendertarget_createCachedFBO(uint64 key) +{ + return new CachedFBOVk(key, m_logicalDevice); +} + +void VulkanRenderer::rendertarget_deleteCachedFBO(LatteCachedFBO* cfbo) +{ + if (cfbo == m_state.activeFBO) + m_state.activeFBO = nullptr; +} + +void VulkanRenderer::rendertarget_bindFramebufferObject(LatteCachedFBO* cfbo) +{ + m_state.activeFBO = (CachedFBOVk*)cfbo; +} + +void* VulkanRenderer::texture_acquireTextureUploadBuffer(uint32 size) +{ + return memoryManager->TextureUploadBufferAcquire(size); +} + +void VulkanRenderer::texture_releaseTextureUploadBuffer(uint8* mem) +{ + memoryManager->TextureUploadBufferRelease(mem); +} + +TextureDecoder* VulkanRenderer::texture_chooseDecodedFormat(Latte::E_GX2SURFFMT format, bool isDepth, Latte::E_DIM dim, uint32 width, uint32 height) +{ + FormatInfoVK texFormatInfo{}; + GetTextureFormatInfoVK(format, isDepth, dim, width, height, &texFormatInfo); + return texFormatInfo.decoder; +} + +void VulkanRenderer::texture_reserveTextureOnGPU(LatteTexture* hostTexture) +{ + LatteTextureVk* vkTexture = (LatteTextureVk*)hostTexture; + auto allocationInfo = memoryManager->imageMemoryAllocate(vkTexture->GetImageObj()->m_image); + vkTexture->setAllocation(allocationInfo); +} + +void VulkanRenderer::texture_destroy(LatteTexture* hostTexture) +{ + LatteTextureVk* texVk = (LatteTextureVk*)hostTexture; + delete texVk; +} + +void VulkanRenderer::destroyViewDepr(VkImageView imageView) +{ + cemu_assert_debug(false); + + m_destructionQueues.m_cmd_image_views[m_commandBufferIndex].emplace_back(imageView); +} + +void VulkanRenderer::destroyBuffer(VkBuffer buffer) +{ + m_destructionQueues.m_buffers[m_commandBufferIndex].emplace_back(buffer); +} + +void VulkanRenderer::destroyDeviceMemory(VkDeviceMemory mem) +{ + m_destructionQueues.m_memory[m_commandBufferIndex].emplace_back(mem); +} + +void VulkanRenderer::destroyPipelineInfo(PipelineInfo* pipelineInfo) +{ + cemu_assert_debug(false); +} + +void VulkanRenderer::destroyShader(RendererShaderVk* shader) +{ + while (!shader->list_pipelineInfo.empty()) + delete shader->list_pipelineInfo[0]; +} + +void VulkanRenderer::releaseDestructibleObject(VKRDestructibleObject* destructibleObject) +{ + // destroy immediately if possible + if (destructibleObject->canDestroy()) + { + delete destructibleObject; + return; + } + // otherwise put on queue + m_spinlockDestructionQueue.acquire(); + m_destructionQueue.emplace_back(destructibleObject); + m_spinlockDestructionQueue.release(); +} + +void VulkanRenderer::ProcessDestructionQueue2() +{ + m_spinlockDestructionQueue.acquire(); + for (auto it = m_destructionQueue.begin(); it != m_destructionQueue.end();) + { + if ((*it)->canDestroy()) + { + delete (*it); + it = m_destructionQueue.erase(it); + continue; + } + ++it; + } + m_spinlockDestructionQueue.release(); +} + +VkDescriptorSetInfo::~VkDescriptorSetInfo() +{ + for (auto& it : list_referencedViews) + it->RemoveDescriptorSetReference(this); + // unregister + switch (shaderType) + { + case LatteConst::ShaderType::Vertex: + { + auto r = pipeline_info->vertex_ds_cache.erase(stateHash); + cemu_assert_debug(r == 1); + break; + } + case LatteConst::ShaderType::Pixel: + { + auto r = pipeline_info->pixel_ds_cache.erase(stateHash); + cemu_assert_debug(r == 1); + break; + } + case LatteConst::ShaderType::Geometry: + { + auto r = pipeline_info->geometry_ds_cache.erase(stateHash); + cemu_assert_debug(r == 1); + break; + } + default: + __assume(false); + } + // update global stats + performanceMonitor.vk.numDescriptorSamplerTextures.decrement(statsNumSamplerTextures); + performanceMonitor.vk.numDescriptorDynUniformBuffers.decrement(statsNumDynUniformBuffers); + performanceMonitor.vk.numDescriptorStorageBuffers.decrement(statsNumStorageBuffers); + + VulkanRenderer::GetInstance()->releaseDestructibleObject(m_vkObjDescriptorSet); + m_vkObjDescriptorSet = nullptr; +} + +void VulkanRenderer::texture_clearSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex) +{ + draw_endRenderPass(); + auto vkTexture = (LatteTextureVk*)hostTexture; + if (vkTexture->isDepth) + texture_clearDepthSlice(hostTexture, sliceIndex, mipIndex, true, vkTexture->hasStencil, 0.0f, 0); + else + { + cemu_assert_debug(vkTexture->dim != Latte::E_DIM::DIM_3D); + if (hostTexture->IsCompressedFormat()) + { + auto imageObj = vkTexture->GetImageObj(); + imageObj->flagForCurrentCommandBuffer(); + + forceLogDebug_printf("Compressed texture (%d/%d fmt %04x) unsupported clear", vkTexture->width, vkTexture->height, (uint32)vkTexture->format); + + VkImageSubresourceLayers subresourceRange{}; + subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + subresourceRange.mipLevel = mipIndex; + subresourceRange.baseArrayLayer = sliceIndex; + subresourceRange.layerCount = 1; + barrier_image<ANY_TRANSFER | IMAGE_READ, ANY_TRANSFER | IMAGE_READ | IMAGE_WRITE>(vkTexture, subresourceRange, VK_IMAGE_LAYOUT_GENERAL); + } + else + { + ClearColorImage(vkTexture, sliceIndex, mipIndex, { 0,0,0,0 }, VK_IMAGE_LAYOUT_GENERAL); + } + } +} + +void VulkanRenderer::texture_clearColorSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex, float r, float g, float b, float a) +{ + auto vkTexture = (LatteTextureVk*)hostTexture; + cemu_assert_debug(vkTexture->dim != Latte::E_DIM::DIM_3D); + ClearColorImage(vkTexture, sliceIndex, mipIndex, { r,g,b,a }, VK_IMAGE_LAYOUT_GENERAL); +} + +void VulkanRenderer::texture_clearDepthSlice(LatteTexture* hostTexture, uint32 sliceIndex, sint32 mipIndex, bool clearDepth, bool clearStencil, float depthValue, uint32 stencilValue) +{ + draw_endRenderPass(); // vkCmdClearDepthStencilImage must not be inside renderpass + + auto vkTexture = (LatteTextureVk*)hostTexture; + + VkImageAspectFlags imageAspect = vkTexture->GetImageAspect(); + + VkImageAspectFlags aspectMask = 0; + if (clearDepth && (imageAspect & VK_IMAGE_ASPECT_DEPTH_BIT) != 0) + aspectMask |= VK_IMAGE_ASPECT_DEPTH_BIT; + if (clearStencil && (imageAspect & VK_IMAGE_ASPECT_STENCIL_BIT) != 0) + aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; + + auto imageObj = vkTexture->GetImageObj(); + imageObj->flagForCurrentCommandBuffer(); + + VkImageSubresourceLayers subresourceRange{}; + subresourceRange.aspectMask = vkTexture->GetImageAspect(); + subresourceRange.mipLevel = mipIndex; + subresourceRange.baseArrayLayer = sliceIndex; + subresourceRange.layerCount = 1; + barrier_image<ANY_TRANSFER | IMAGE_READ | IMAGE_WRITE, ANY_TRANSFER>(vkTexture, subresourceRange, VK_IMAGE_LAYOUT_GENERAL); + + VkClearDepthStencilValue depthStencilValue{}; + depthStencilValue.depth = depthValue; + depthStencilValue.stencil = stencilValue; + + VkImageSubresourceRange range{}; + range.baseMipLevel = mipIndex; + range.levelCount = 1; + range.baseArrayLayer = sliceIndex; + range.layerCount = 1; + + range.aspectMask = aspectMask; + + vkCmdClearDepthStencilImage(m_state.currentCommandBuffer, imageObj->m_image, VK_IMAGE_LAYOUT_GENERAL, &depthStencilValue, 1, &range); + + barrier_image<ANY_TRANSFER, ANY_TRANSFER | IMAGE_READ | IMAGE_WRITE>(vkTexture, subresourceRange, VK_IMAGE_LAYOUT_GENERAL); +} + +void VulkanRenderer::texture_loadSlice(LatteTexture* hostTexture, sint32 width, sint32 height, sint32 depth, void* pixelData, sint32 sliceIndex, sint32 mipIndex, uint32 compressedImageSize) +{ + auto vkTexture = (LatteTextureVk*)hostTexture; + auto vkImageObj = vkTexture->GetImageObj(); + vkImageObj->flagForCurrentCommandBuffer(); + + draw_endRenderPass(); + + VkMemoryRequirements memRequirements; + vkGetImageMemoryRequirements(m_logicalDevice, vkImageObj->m_image, &memRequirements); + + uint32 uploadSize = compressedImageSize;// memRequirements.size; + uint32 uploadAlignment = memRequirements.alignment; + + VKRSynchronizedRingAllocator& vkMemAllocator = memoryManager->getStagingAllocator(); + + auto uploadResv = vkMemAllocator.AllocateBufferMemory(uploadSize, uploadAlignment); + memcpy(uploadResv.memPtr, pixelData, compressedImageSize); + vkMemAllocator.FlushReservation(uploadResv); + + FormatInfoVK texFormatInfo; + GetTextureFormatInfoVK(hostTexture->format, hostTexture->isDepth, hostTexture->dim, 0, 0, &texFormatInfo); + + bool is3DTexture = hostTexture->Is3DTexture(); + + VkImageSubresourceLayers barrierSubresourceRange{}; + barrierSubresourceRange.aspectMask = texFormatInfo.vkImageAspect; + barrierSubresourceRange.mipLevel = mipIndex; + barrierSubresourceRange.baseArrayLayer = is3DTexture ? 0 : sliceIndex; + barrierSubresourceRange.layerCount = 1; + barrier_image<ANY_TRANSFER | IMAGE_READ | IMAGE_WRITE | HOST_WRITE, ANY_TRANSFER>(vkTexture, barrierSubresourceRange, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + + VkBufferImageCopy imageRegion[2]{}; + sint32 imageRegionCount = 0; + if (texFormatInfo.vkImageAspect == VK_IMAGE_ASPECT_COLOR_BIT || texFormatInfo.vkImageAspect == VK_IMAGE_ASPECT_DEPTH_BIT) + { + imageRegion[0].bufferOffset = uploadResv.bufferOffset; + imageRegion[0].imageExtent.width = width; + imageRegion[0].imageExtent.height = height; + imageRegion[0].imageExtent.depth = 1; + imageRegion[0].imageOffset.z = is3DTexture ? sliceIndex : 0; + + imageRegion[0].imageSubresource.mipLevel = mipIndex; + imageRegion[0].imageSubresource.aspectMask = texFormatInfo.vkImageAspect; + imageRegion[0].imageSubresource.baseArrayLayer = is3DTexture ? 0 : sliceIndex; + imageRegion[0].imageSubresource.layerCount = 1; + imageRegionCount = 1; + } + else if (texFormatInfo.vkImageAspect == VK_IMAGE_ASPECT_DEPTH_BIT) + { + if (is3DTexture) + cemu_assert_debug(false); + + // depth only copy + imageRegion[0].bufferOffset = uploadResv.bufferOffset; + imageRegion[0].imageExtent.width = width; + imageRegion[0].imageExtent.height = height; + imageRegion[0].imageExtent.depth = 1; + + imageRegion[0].imageSubresource.mipLevel = mipIndex; + imageRegion[0].imageSubresource.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + imageRegion[0].imageSubresource.baseArrayLayer = sliceIndex; + imageRegion[0].imageSubresource.layerCount = 1; + + imageRegionCount = 1; + } + else if (texFormatInfo.vkImageAspect == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) + { + if (is3DTexture) + cemu_assert_debug(false); + + // depth copy + imageRegion[0].bufferOffset = uploadResv.bufferOffset; + imageRegion[0].imageExtent.width = width; + imageRegion[0].imageExtent.height = height; + imageRegion[0].imageExtent.depth = 1; + + imageRegion[0].imageSubresource.mipLevel = mipIndex; + imageRegion[0].imageSubresource.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + imageRegion[0].imageSubresource.baseArrayLayer = sliceIndex; + imageRegion[0].imageSubresource.layerCount = 1; + + // stencil copy + imageRegion[1].bufferOffset = uploadResv.bufferOffset; + imageRegion[1].imageExtent.width = width; + imageRegion[1].imageExtent.height = height; + imageRegion[1].imageExtent.depth = 1; + + imageRegion[1].imageSubresource.mipLevel = mipIndex; + imageRegion[1].imageSubresource.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT; + imageRegion[1].imageSubresource.baseArrayLayer = sliceIndex; + imageRegion[1].imageSubresource.layerCount = 1; + + imageRegionCount = 2; + } + else + cemu_assert_debug(false); + + vkCmdCopyBufferToImage(m_state.currentCommandBuffer, uploadResv.vkBuffer, vkImageObj->m_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, imageRegionCount, imageRegion); + + barrier_image<ANY_TRANSFER, ANY_TRANSFER | IMAGE_READ | IMAGE_WRITE>(vkTexture, barrierSubresourceRange, VK_IMAGE_LAYOUT_GENERAL); +} + +LatteTexture* VulkanRenderer::texture_createTextureEx(uint32 textureUnit, Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, + uint32 swizzle, Latte::E_HWTILEMODE tileMode, bool isDepth) +{ + return new LatteTextureVk(this, textureUnit, dim, physAddress, physMipAddress, format, width, height, depth, pitch, mipLevels, swizzle, tileMode, isDepth); +} + +void VulkanRenderer::texture_bindAndActivate(LatteTextureView* textureView, uint32 textureUnit) +{ + m_state.boundTexture[textureUnit] = static_cast<LatteTextureViewVk*>(textureView); +} + +void VulkanRenderer::texture_bindOnly(LatteTextureView* textureView, uint32 textureUnit) +{ + m_state.boundTexture[textureUnit] = static_cast<LatteTextureViewVk*>(textureView); +} + +void VulkanRenderer::texture_copyImageSubData(LatteTexture* src, sint32 srcMip, sint32 effectiveSrcX, sint32 effectiveSrcY, sint32 srcSlice, LatteTexture* dst, sint32 dstMip, sint32 effectiveDstX, sint32 effectiveDstY, sint32 dstSlice, sint32 effectiveCopyWidth, sint32 effectiveCopyHeight, sint32 srcDepth) +{ + LatteTextureVk* srcVk = static_cast<LatteTextureVk*>(src); + LatteTextureVk* dstVk = static_cast<LatteTextureVk*>(dst); + + draw_endRenderPass(); // vkCmdCopyImage must be called outside of a renderpass + + VKRObjectTexture* srcVkObj = srcVk->GetImageObj(); + VKRObjectTexture* dstVkObj = dstVk->GetImageObj(); + srcVkObj->flagForCurrentCommandBuffer(); + dstVkObj->flagForCurrentCommandBuffer(); + + VkImageCopy region{}; + region.srcOffset.x = effectiveSrcX; + region.srcOffset.y = effectiveSrcY; + region.dstOffset.x = effectiveDstX; + region.dstOffset.y = effectiveDstY; + region.extent.width = effectiveCopyWidth; + region.extent.height = effectiveCopyHeight; + region.extent.depth = 1; + + if (src->Is3DTexture()) + { + region.srcOffset.z = srcSlice; + region.extent.depth = srcDepth; + region.srcSubresource.baseArrayLayer = 0; + region.srcSubresource.layerCount = 1; + } + else + { + region.srcOffset.z = 0; + region.extent.depth = 1; + region.srcSubresource.baseArrayLayer = srcSlice; + region.srcSubresource.layerCount = srcDepth; + } + + if (dst->Is3DTexture()) + { + region.dstOffset.z = dstSlice; + region.dstSubresource.baseArrayLayer = 0; + region.dstSubresource.layerCount = 1; + } + else + { + region.dstOffset.z = 0; + region.dstSubresource.baseArrayLayer = dstSlice; + region.dstSubresource.layerCount = srcDepth; + } + + region.srcSubresource.mipLevel = srcMip; + region.srcSubresource.aspectMask = srcVk->GetImageAspect(); + + region.dstSubresource.mipLevel = dstMip; + region.dstSubresource.aspectMask = dstVk->GetImageAspect(); + + bool srcIsCompressed = Latte::IsCompressedFormat(srcVk->format); + bool dstIsCompressed = Latte::IsCompressedFormat(dstVk->format); + + if (!srcIsCompressed && dstIsCompressed) + { + // handle the special case where the destination is compressed and not a multiple of the texel size (4) + sint32 mipWidth = std::max(dst->width >> dstMip, 1); + sint32 mipHeight = std::max(dst->height >> dstMip, 1); + + + if (mipWidth < 4 || mipHeight < 4) + { + forceLogDebug_printf("vkCmdCopyImage - blocked copy for unsupported uncompressed->compressed copy with dst smaller than 4x4"); + return; + } + + } + + // make sure all write operations to the src image have finished + barrier_image<SYNC_OP::IMAGE_WRITE | SYNC_OP::ANY_TRANSFER, SYNC_OP::ANY_TRANSFER>(srcVk, region.srcSubresource, VK_IMAGE_LAYOUT_GENERAL); + // make sure all read and write operations to the dst image have finished + barrier_image<SYNC_OP::IMAGE_READ | SYNC_OP::IMAGE_WRITE | SYNC_OP::ANY_TRANSFER, SYNC_OP::ANY_TRANSFER>(dstVk, region.dstSubresource, VK_IMAGE_LAYOUT_GENERAL); + + vkCmdCopyImage(m_state.currentCommandBuffer, srcVkObj->m_image, VK_IMAGE_LAYOUT_GENERAL, dstVkObj->m_image, VK_IMAGE_LAYOUT_GENERAL, 1, ®ion); + + // make sure the transfer is finished before the image is read or written + barrier_image<SYNC_OP::ANY_TRANSFER, SYNC_OP::IMAGE_READ | SYNC_OP::IMAGE_WRITE | SYNC_OP::ANY_TRANSFER>(dstVk, region.dstSubresource, VK_IMAGE_LAYOUT_GENERAL); +} + +LatteTextureReadbackInfo* VulkanRenderer::texture_createReadback(LatteTextureView* textureView) +{ + auto* result = new LatteTextureReadbackInfoVk(m_logicalDevice, textureView); + + LatteTextureVk* vkTex = (LatteTextureVk*)textureView->baseTexture; + + VkMemoryRequirements memRequirements; + vkGetImageMemoryRequirements(m_logicalDevice, vkTex->GetImageObj()->m_image, &memRequirements); + + const uint32 linearImageSize = result->GetImageSize(); + const uint32 uploadSize = (linearImageSize == 0) ? memRequirements.size : linearImageSize; + const uint32 uploadAlignment = 256; // todo - use Vk optimalBufferCopyOffsetAlignment + m_textureReadbackBufferWriteIndex = (m_textureReadbackBufferWriteIndex + uploadAlignment - 1) & ~(uploadAlignment - 1); + + if ((m_textureReadbackBufferWriteIndex + uploadSize + 256) > TEXTURE_READBACK_SIZE) + { + m_textureReadbackBufferWriteIndex = 0; + } + + const uint32 uploadBufferOffset = m_textureReadbackBufferWriteIndex; + m_textureReadbackBufferWriteIndex += uploadSize; + + result->SetBuffer(m_textureReadbackBuffer, m_textureReadbackBufferPtr, uploadBufferOffset); + + return result; +} + +uint32 s_vkCurrentUniqueId = 0; + +uint64 VulkanRenderer::GenUniqueId() +{ + s_vkCurrentUniqueId++; + return s_vkCurrentUniqueId; +} + +void VulkanRenderer::streamout_setupXfbBuffer(uint32 bufferIndex, sint32 ringBufferOffset, uint32 rangeAddr, uint32 rangeSize) +{ + VkDeviceSize tfBufferOffset = ringBufferOffset; + m_streamoutState.buffer[bufferIndex].enabled = true; + m_streamoutState.buffer[bufferIndex].ringBufferOffset = ringBufferOffset; +} + +void VulkanRenderer::streamout_begin() +{ + if (m_featureControl.mode.useTFEmulationViaSSBO) + return; + if (m_state.hasActiveXfb == false) + m_state.hasActiveXfb = true; +} + +void VulkanRenderer::streamout_applyTransformFeedbackState() +{ + if (m_featureControl.mode.useTFEmulationViaSSBO) + return; + cemu_assert_debug(m_state.hasActiveXfb == false); + if (m_state.hasActiveXfb) + { + // set buffers + for (sint32 i = 0; i < LATTE_NUM_STREAMOUT_BUFFER; i++) + { + if (m_streamoutState.buffer[i].enabled) + { + VkBuffer tfBuffer = m_xfbRingBuffer; + VkDeviceSize tfBufferOffset = m_streamoutState.buffer[i].ringBufferOffset; + VkDeviceSize tfBufferSize = VK_WHOLE_SIZE; + vkCmdBindTransformFeedbackBuffersEXT(m_state.currentCommandBuffer, i, 1, &tfBuffer, &tfBufferOffset, &tfBufferSize); + } + } + // begin transform feedback + vkCmdBeginTransformFeedbackEXT(m_state.currentCommandBuffer, 0, 0, nullptr, nullptr); + } +} + +void VulkanRenderer::streamout_rendererFinishDrawcall() +{ + if (m_state.hasActiveXfb) + { + vkCmdEndTransformFeedbackEXT(m_state.currentCommandBuffer, 0, 0, nullptr, nullptr); + m_streamoutState.buffer[0].enabled = false; + m_streamoutState.buffer[1].enabled = false; + m_streamoutState.buffer[2].enabled = false; + m_streamoutState.buffer[3].enabled = false; + m_state.hasActiveXfb = false; + } +} + + +void VulkanRenderer::buffer_bindVertexBuffer(uint32 bufferIndex, uint32 offset, uint32 size) +{ + cemu_assert_debug(!m_useHostMemoryForCache); + if (m_state.currentVertexBinding[bufferIndex].offset == offset) + return; + cemu_assert_debug(bufferIndex < LATTE_MAX_VERTEX_BUFFERS); + m_state.currentVertexBinding[bufferIndex].offset = offset; + VkBuffer attrBuffer = m_bufferCache; + VkDeviceSize attrOffset = offset; + vkCmdBindVertexBuffers(m_state.currentCommandBuffer, bufferIndex, 1, &attrBuffer, &attrOffset); +} + +void VulkanRenderer::buffer_bindUniformBuffer(LatteConst::ShaderType shaderType, uint32 bufferIndex, uint32 offset, uint32 size) +{ + cemu_assert_debug(!m_useHostMemoryForCache); + cemu_assert_debug(bufferIndex < 16); + switch (shaderType) + { + case LatteConst::ShaderType::Vertex: + dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX].unformBufferOffset[bufferIndex] = offset; + break; + case LatteConst::ShaderType::Geometry: + dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY].unformBufferOffset[bufferIndex] = offset; + break; + case LatteConst::ShaderType::Pixel: + dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT].unformBufferOffset[bufferIndex] = offset; + break; + default: + cemu_assert_debug(false); + } +} + +void VulkanRenderer::bufferCache_init(const sint32 bufferSize) +{ + m_importedMemBaseAddress = 0x10000000; + size_t hostAllocationSize = 0x40000000ull; + // todo - get size of allocation + bool configUseHostMemory = false; // todo - replace this with a config option + m_useHostMemoryForCache = false; + if (m_featureControl.deviceExtensions.external_memory_host && configUseHostMemory) + { + m_useHostMemoryForCache = memoryManager->CreateBufferFromHostMemory(memory_getPointerFromVirtualOffset(m_importedMemBaseAddress), hostAllocationSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, 0, m_importedMem, m_importedMemMemory); + if (!m_useHostMemoryForCache) + { + cemuLog_force("Unable to import host memory to Vulkan buffer. Use default cache system instead"); + } + } + if(!m_useHostMemoryForCache) + memoryManager->CreateBuffer(bufferSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, 0, m_bufferCache, m_bufferCacheMemory); +} + +void VulkanRenderer::bufferCache_upload(uint8* buffer, sint32 size, uint32 bufferOffset) +{ + draw_endRenderPass(); + + VKRSynchronizedRingAllocator& vkMemAllocator = memoryManager->getStagingAllocator(); + + auto uploadResv = vkMemAllocator.AllocateBufferMemory(size, 256); + memcpy(uploadResv.memPtr, buffer, size); + + vkMemAllocator.FlushReservation(uploadResv); + + barrier_bufferRange<ANY_TRANSFER | HOST_WRITE, ANY_TRANSFER, + BUFFER_SHADER_READ, TRANSFER_WRITE>( + uploadResv.vkBuffer, uploadResv.bufferOffset, uploadResv.size, // make sure any in-flight transfers are completed + m_bufferCache, bufferOffset, size); // make sure all reads are completed before we overwrite the data + + VkBufferCopy region; + region.srcOffset = uploadResv.bufferOffset; + region.dstOffset = bufferOffset; + region.size = size; + vkCmdCopyBuffer(m_state.currentCommandBuffer, uploadResv.vkBuffer, m_bufferCache, 1, ®ion); + + barrier_sequentializeTransfer(); +} + +void VulkanRenderer::bufferCache_copy(uint32 srcOffset, uint32 dstOffset, uint32 size) +{ + cemu_assert_debug(!m_useHostMemoryForCache); + draw_endRenderPass(); + + barrier_sequentializeTransfer(); + + bool isOverlapping = (srcOffset + size) > dstOffset && (srcOffset) < (dstOffset + size); + cemu_assert_debug(!isOverlapping); + + VkBufferCopy bufferCopy{}; + bufferCopy.srcOffset = srcOffset; + bufferCopy.dstOffset = dstOffset; + bufferCopy.size = size; + vkCmdCopyBuffer(m_state.currentCommandBuffer, m_bufferCache, m_bufferCache, 1, &bufferCopy); + + barrier_sequentializeTransfer(); +} + +void VulkanRenderer::bufferCache_copyStreamoutToMainBuffer(uint32 srcOffset, uint32 dstOffset, uint32 size) +{ + draw_endRenderPass(); + + VkBuffer dstBuffer; + if (m_useHostMemoryForCache) + { + // in host memory mode, dstOffset is physical address instead of cache address + dstBuffer = m_importedMem; + dstOffset -= m_importedMemBaseAddress; + } + else + dstBuffer = m_bufferCache; + + barrier_bufferRange<BUFFER_SHADER_WRITE, TRANSFER_READ, + ANY_TRANSFER | BUFFER_SHADER_READ, TRANSFER_WRITE>( + m_xfbRingBuffer, srcOffset, size, // wait for all writes to finish + dstBuffer, dstOffset, size); // wait for all reads to finish + + barrier_sequentializeTransfer(); + + VkBufferCopy bufferCopy{}; + bufferCopy.srcOffset = srcOffset; + bufferCopy.dstOffset = dstOffset; + bufferCopy.size = size; + vkCmdCopyBuffer(m_state.currentCommandBuffer, m_xfbRingBuffer, dstBuffer, 1, &bufferCopy); + + barrier_sequentializeTransfer(); +} + +void VulkanRenderer::AppendOverlayDebugInfo() +{ + ImGui::Text("--- Vulkan info ---"); + ImGui::Text("GfxPipelines %u", performanceMonitor.vk.numGraphicPipelines.get()); + ImGui::Text("DescriptorSets %u", performanceMonitor.vk.numDescriptorSets.get()); + ImGui::Text("DS ImgSamplers %u", performanceMonitor.vk.numDescriptorSamplerTextures.get()); + ImGui::Text("DS DynUniform %u", performanceMonitor.vk.numDescriptorDynUniformBuffers.get()); + ImGui::Text("DS StorageBuf %u", performanceMonitor.vk.numDescriptorStorageBuffers.get()); + ImGui::Text("Images %u", performanceMonitor.vk.numImages.get()); + ImGui::Text("ImageView %u", performanceMonitor.vk.numImageViews.get()); + ImGui::Text("RenderPass %u", performanceMonitor.vk.numRenderPass.get()); + ImGui::Text("Framebuffer %u", performanceMonitor.vk.numFramebuffer.get()); + m_spinlockDestructionQueue.acquire(); + ImGui::Text("DestructionQ %u", (unsigned int)m_destructionQueue.size()); + m_spinlockDestructionQueue.release(); + + + ImGui::Text("BeginRP/f %u", performanceMonitor.vk.numBeginRenderpassPerFrame.get()); + ImGui::Text("Barriers/f %u", performanceMonitor.vk.numDrawBarriersPerFrame.get()); + ImGui::Text("--- Cache info ---"); + + uint32 bufferCacheHeapSize = 0; + uint32 bufferCacheAllocationSize = 0; + uint32 bufferCacheNumAllocations = 0; + + LatteBufferCache_getStats(bufferCacheHeapSize, bufferCacheAllocationSize, bufferCacheNumAllocations); + + ImGui::Text("Buffer"); + ImGui::SameLine(60.0f); + ImGui::Text("%06uKB / %06uKB Allocs: %u", (uint32)(bufferCacheAllocationSize + 1023) / 1024, ((uint32)bufferCacheHeapSize + 1023) / 1024, (uint32)bufferCacheNumAllocations); + + uint32 numBuffers; + size_t totalSize, freeSize; + + memoryManager->getStagingAllocator().GetStats(numBuffers, totalSize, freeSize); + ImGui::Text("Staging"); + ImGui::SameLine(60.0f); + ImGui::Text("%06uKB / %06uKB Buffers: %u", ((uint32)(totalSize - freeSize) + 1023) / 1024, ((uint32)totalSize + 1023) / 1024, (uint32)numBuffers); + + memoryManager->getIndexAllocator().GetStats(numBuffers, totalSize, freeSize); + ImGui::Text("Index"); + ImGui::SameLine(60.0f); + ImGui::Text("%06uKB / %06uKB Buffers: %u", ((uint32)(totalSize - freeSize) + 1023) / 1024, ((uint32)totalSize + 1023) / 1024, (uint32)numBuffers); + + ImGui::Text("--- Tex heaps ---"); + memoryManager->appendOverlayHeapDebugInfo(); +} + +void VKRDestructibleObject::flagForCurrentCommandBuffer() +{ + m_lastCmdBufferId = VulkanRenderer::GetInstance()->GetCurrentCommandBufferId(); +} + +bool VKRDestructibleObject::canDestroy() +{ + if (refCount > 0) + return false; + return VulkanRenderer::GetInstance()->HasCommandBufferFinished(m_lastCmdBufferId); +} + +VKRObjectTexture::VKRObjectTexture() +{ + performanceMonitor.vk.numImages.increment(); +} + +VKRObjectTexture::~VKRObjectTexture() +{ + auto vkr = VulkanRenderer::GetInstance(); + if (m_allocation) + { + vkr->GetMemoryManager()->imageMemoryFree(m_allocation); + m_allocation = nullptr; + } + if (m_image) + vkDestroyImage(vkr->GetLogicalDevice(), m_image, nullptr); + performanceMonitor.vk.numImages.decrement(); +} + +VKRObjectTextureView::VKRObjectTextureView(VKRObjectTexture* tex, VkImageView view) +{ + m_textureImageView = view; + this->addRef(tex); + performanceMonitor.vk.numImageViews.increment(); +} + +VKRObjectTextureView::~VKRObjectTextureView() +{ + auto logicalDevice = VulkanRenderer::GetInstance()->GetLogicalDevice(); + if (m_textureDefaultSampler[0] != VK_NULL_HANDLE) + vkDestroySampler(logicalDevice, m_textureDefaultSampler[0], nullptr); + if (m_textureDefaultSampler[1] != VK_NULL_HANDLE) + vkDestroySampler(logicalDevice, m_textureDefaultSampler[1], nullptr); + vkDestroyImageView(logicalDevice, m_textureImageView, nullptr); + performanceMonitor.vk.numImageViews.decrement(); +} + +VKRObjectRenderPass::VKRObjectRenderPass(AttachmentInfo_t& attachmentInfo, sint32 colorAttachmentCount) +{ + // generate helper hash for pipeline state + uint64 stateHash = 0; + for (int i = 0; i < 8; ++i) + { + if (attachmentInfo.colorAttachment[i].isPresent || attachmentInfo.colorAttachment[i].viewObj) + { + stateHash += attachmentInfo.colorAttachment[i].format + i * 31; + stateHash = _rotl64(stateHash, 7); + } + } + if (attachmentInfo.depthAttachment.isPresent || attachmentInfo.depthAttachment.viewObj) + { + stateHash += attachmentInfo.depthAttachment.format; + stateHash = _rotl64(stateHash, 7); + } + m_hashForPipeline = stateHash; + + // setup Vulkan renderpass + std::vector<VkAttachmentDescription> attachments_descriptions; + std::array<VkAttachmentReference, 8> color_attachments_references{}; + cemu_assert(colorAttachmentCount <= color_attachments_references.size()); + sint32 numColorAttachments = 0; + for (int i = 0; i < 8; ++i) + { + if (attachmentInfo.colorAttachment[i].viewObj == nullptr && attachmentInfo.colorAttachment[i].isPresent == false) + { + color_attachments_references[i].attachment = VK_ATTACHMENT_UNUSED; + continue; + } + + color_attachments_references[i].attachment = (uint32)attachments_descriptions.size(); + color_attachments_references[i].layout = VK_IMAGE_LAYOUT_GENERAL; + + VkAttachmentDescription entry{}; + entry.format = attachmentInfo.colorAttachment[i].format; + entry.samples = VK_SAMPLE_COUNT_1_BIT; + entry.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + entry.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + entry.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + entry.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + entry.initialLayout = VK_IMAGE_LAYOUT_GENERAL; + entry.finalLayout = VK_IMAGE_LAYOUT_GENERAL; + attachments_descriptions.emplace_back(entry); + + numColorAttachments = i + 1; + } + + VkAttachmentReference depth_stencil_attachments_references{}; + bool hasDepthStencilAttachment = false; + if (attachmentInfo.depthAttachment.viewObj == nullptr && attachmentInfo.depthAttachment.isPresent == false) + { + depth_stencil_attachments_references.attachment = VK_ATTACHMENT_UNUSED; + } + else + { + hasDepthStencilAttachment = true; + depth_stencil_attachments_references.attachment = (uint32)attachments_descriptions.size(); + depth_stencil_attachments_references.layout = VK_IMAGE_LAYOUT_GENERAL; + + VkAttachmentDescription entry{}; + entry.format = attachmentInfo.depthAttachment.format; + entry.samples = VK_SAMPLE_COUNT_1_BIT; + entry.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + entry.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + if (attachmentInfo.depthAttachment.hasStencil) + { + entry.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + entry.stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE; + } + else + { + entry.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + entry.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + } + entry.initialLayout = VK_IMAGE_LAYOUT_GENERAL; + entry.finalLayout = VK_IMAGE_LAYOUT_GENERAL; + attachments_descriptions.emplace_back(entry); + } + + // todo - use numColorAttachments instead of .size() or colorAttachmentCount (needs adjusting in many places) + + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = colorAttachmentCount; + subpass.pColorAttachments = color_attachments_references.data(); + subpass.inputAttachmentCount = 0; + subpass.pInputAttachments = nullptr; + subpass.pDepthStencilAttachment = &depth_stencil_attachments_references; + + VkRenderPassCreateInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = (uint32)attachments_descriptions.size(); + renderPassInfo.pAttachments = attachments_descriptions.data(); + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + + renderPassInfo.pDependencies = nullptr; + renderPassInfo.dependencyCount = 0; + // before Cemu 1.25.5 we used zero here, which means implicit synchronization. For 1.25.5 it was changed to 2 (using the subpass dependencies above) + // Reverted this again to zero for Cemu 1.25.5b as the performance cost is just too high. Manual synchronization is preferred + + if (vkCreateRenderPass(VulkanRenderer::GetInstance()->GetLogicalDevice(), &renderPassInfo, nullptr, &m_renderPass) != VK_SUCCESS) + { + forceLog_printf("Vulkan-Error: Failed to create render pass"); + throw std::runtime_error("failed to create render pass!"); + } + + // track references + for (int i = 0; i < 8; ++i) + { + if (attachmentInfo.colorAttachment[i].viewObj) + addRef(attachmentInfo.colorAttachment[i].viewObj); + } + if (attachmentInfo.depthAttachment.viewObj) + addRef(attachmentInfo.depthAttachment.viewObj); + performanceMonitor.vk.numRenderPass.increment(); +} + +VKRObjectRenderPass::~VKRObjectRenderPass() +{ + if (m_renderPass != VK_NULL_HANDLE) + vkDestroyRenderPass(VulkanRenderer::GetInstance()->GetLogicalDevice(), m_renderPass, nullptr); + performanceMonitor.vk.numRenderPass.decrement(); +} + +VKRObjectFramebuffer::VKRObjectFramebuffer(VKRObjectRenderPass* renderPass, std::span<VKRObjectTextureView*> attachments, Vector2i size) +{ + // convert VKRObjectTextureView* array to vkImageView array + std::array<VkImageView, 16> attachmentViews; + cemu_assert(attachments.size() < attachmentViews.size()); + for (size_t i = 0; i < attachments.size(); i++) + attachmentViews[i] = attachments[i]->m_textureImageView; + + VkFramebufferCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + createInfo.pAttachments = attachmentViews.data(); + createInfo.attachmentCount = attachments.size(); + createInfo.renderPass = renderPass->m_renderPass; + createInfo.layers = 1; + createInfo.width = size.x; + createInfo.height = size.y; + if (vkCreateFramebuffer(VulkanRenderer::GetInstance()->GetLogicalDevice(), &createInfo, nullptr, &m_frameBuffer) != VK_SUCCESS) + throw std::runtime_error("failed to create framebuffer!"); + + // track refs + this->addRef(renderPass); + for (auto& itr : attachments) + this->addRef(itr); + + performanceMonitor.vk.numFramebuffer.increment(); +} + +VKRObjectFramebuffer::~VKRObjectFramebuffer() +{ + if (m_frameBuffer != VK_NULL_HANDLE) + vkDestroyFramebuffer(VulkanRenderer::GetInstance()->GetLogicalDevice(), m_frameBuffer, nullptr); + performanceMonitor.vk.numFramebuffer.decrement(); +} + +VKRObjectPipeline::VKRObjectPipeline() +{ + // todo +} + +void VKRObjectPipeline::setPipeline(VkPipeline newPipeline) +{ + cemu_assert_debug(pipeline == VK_NULL_HANDLE); + pipeline = newPipeline; + if(newPipeline != VK_NULL_HANDLE) + performanceMonitor.vk.numGraphicPipelines.increment(); +} + +VKRObjectPipeline::~VKRObjectPipeline() +{ + auto vkr = VulkanRenderer::GetInstance(); + if (pipeline != VK_NULL_HANDLE) + { + vkDestroyPipeline(vkr->GetLogicalDevice(), pipeline, nullptr); + performanceMonitor.vk.numGraphicPipelines.decrement(); + } + if (vertexDSL != VK_NULL_HANDLE) + vkDestroyDescriptorSetLayout(vkr->GetLogicalDevice(), vertexDSL, nullptr); + if (pixelDSL != VK_NULL_HANDLE) + vkDestroyDescriptorSetLayout(vkr->GetLogicalDevice(), pixelDSL, nullptr); + if (geometryDSL != VK_NULL_HANDLE) + vkDestroyDescriptorSetLayout(vkr->GetLogicalDevice(), geometryDSL, nullptr); + if (pipeline_layout != VK_NULL_HANDLE) + vkDestroyPipelineLayout(vkr->GetLogicalDevice(), pipeline_layout, nullptr); +} + +VKRObjectDescriptorSet::VKRObjectDescriptorSet() +{ + performanceMonitor.vk.numDescriptorSets.increment(); +} + +VKRObjectDescriptorSet::~VKRObjectDescriptorSet() +{ + auto vkr = VulkanRenderer::GetInstance(); + vkFreeDescriptorSets(vkr->GetLogicalDevice(), vkr->GetDescriptorPool(), 1, &descriptorSet); + performanceMonitor.vk.numDescriptorSets.decrement(); +} diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h new file mode 100644 index 00000000..b77bfa53 --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h @@ -0,0 +1,1035 @@ +#pragma once +#include "Cafe/HW/Latte/Renderer/Renderer.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/LatteTextureViewVk.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/CachedFBOVk.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/VKRMemoryManager.h" +#include "util/math/vector2.h" +#include "util/helpers/Semaphore.h" +#include "util/containers/flat_hash_map.hpp" +#include "util/containers/robin_hood.h" + +struct VkSupportedFormatInfo_t +{ + bool fmt_d24_unorm_s8_uint{}; + bool fmt_r4g4_unorm_pack{}; +}; + +struct VkDescriptorSetInfo +{ + VKRObjectDescriptorSet* m_vkObjDescriptorSet{}; + + ~VkDescriptorSetInfo(); + + std::vector<LatteTextureViewVk*> list_referencedViews; + std::vector<LatteTextureVk*> list_fboCandidates; // prefiltered list of textures which may need a barrier + LatteConst::ShaderType shaderType{}; + uint64 stateHash{}; + class PipelineInfo* pipeline_info{}; + + // tracking for allocated descriptors + uint8 statsNumSamplerTextures{ 0 }; + uint8 statsNumDynUniformBuffers{ 0 }; + uint8 statsNumStorageBuffers{ 0 }; +}; + +class VkException : public std::runtime_error +{ +public: + VkException(VkResult result, const std::string& message) + : runtime_error(message), m_result(result) + {} + + VkResult GetResult() const { return m_result; } + +private: + VkResult m_result; +}; + +namespace VulkanRendererConst +{ + static const inline int SHADER_STAGE_INDEX_VERTEX = 0; + static const inline int SHADER_STAGE_INDEX_FRAGMENT = 1; + static const inline int SHADER_STAGE_INDEX_GEOMETRY = 2; + static const inline int SHADER_STAGE_INDEX_COUNT = 3; +}; + +class PipelineInfo +{ +public: + PipelineInfo(uint64 minimalStateHash, uint64 pipelineHash, struct LatteFetchShader* fetchShader, LatteDecompilerShader* vertexShader, LatteDecompilerShader* pixelShader, LatteDecompilerShader* geometryShader); + PipelineInfo(const PipelineInfo& info) = delete; + ~PipelineInfo(); + + bool operator==(const PipelineInfo& pipeline_info) const + { + return true; + } + + + template<typename T> + struct direct_hash + { + size_t operator()(const uint64& k) const noexcept + { + return k; + } + }; + + // std::unordered_map<uint64, VkDescriptorSetInfo*> 3.16% (total CPU time) + // robin_hood::unordered_flat_map<uint64, VkDescriptorSetInfo*> vertex_ds_cache, pixel_ds_cache, geometry_ds_cache; ~1.80% + // ska::bytell_hash_map<uint64, VkDescriptorSetInfo*, direct_hash<uint64>> vertex_ds_cache, pixel_ds_cache, geometry_ds_cache; -> 1.91% + ska::flat_hash_map<uint64, VkDescriptorSetInfo*, direct_hash<uint64>> vertex_ds_cache, pixel_ds_cache, geometry_ds_cache; // 1.71% + + VKRObjectPipeline* m_vkrObjPipeline; + + LatteDecompilerShader* vertexShader = nullptr; + LatteDecompilerShader* geometryShader = nullptr; + LatteDecompilerShader* pixelShader = nullptr; + LatteFetchShader* fetchShader = nullptr; + Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE primitiveMode{}; + + RendererShaderVk* vertexShaderVk = nullptr; + RendererShaderVk* geometryShaderVk = nullptr; + RendererShaderVk* pixelShaderVk = nullptr; + + uint64 minimalStateHash; + uint64 stateHash; + + bool usesBlendConstants{ false }; + bool usesDepthBias{ false }; + + struct + { + bool hasUniformVar[VulkanRendererConst::SHADER_STAGE_INDEX_COUNT]; + bool hasUniformBuffers[VulkanRendererConst::SHADER_STAGE_INDEX_COUNT]; + std::vector<uint8> list_uniformBuffers[VulkanRendererConst::SHADER_STAGE_INDEX_COUNT]; + }dynamicOffsetInfo{}; + + // primitive rects emulation + RendererShaderVk* rectEmulationGS = nullptr; + + // hack - accurate barrier needed for this pipeline + bool neverSkipAccurateBarrier{false}; +}; + +class VulkanRenderer : public Renderer +{ + friend class LatteQueryObjectVk; + friend class LatteTextureReadbackInfoVk; + friend class PipelineCompiler; + + static const inline int UNIFORMVAR_RINGBUFFER_SIZE = 1024 * 1024 * 16; // 16MB + static const inline int INDEX_STREAM_BUFFER_SIZE = 16 * 1024 * 1024; // 16 MB + + static const inline int TEXTURE_READBACK_SIZE = 32 * 1024 * 1024; // 32 MB + + static const inline int OCCLUSION_QUERY_POOL_SIZE = 1024; + +public: + enum class VSync + { + // values here must match GeneralSettings2::m_vsync + Immediate = 0, + FIFO = 1, + MAILBOX = 2, + SYNC_AND_LIMIT = 3, // synchronize emulated vsync events to monitor vsync. But skip events if rate higher than virtual vsync period + }; + + // memory management + VKRMemoryManager* memoryManager{}; + VKRMemoryManager* GetMemoryManager() const { return memoryManager; }; + + VkSupportedFormatInfo_t m_supportedFormatInfo; + + typedef struct + { + // Vulkan image info + VkFormat vkImageFormat; + VkImageAspectFlags vkImageAspect; + bool isCompressed; + + // texture decoder info + TextureDecoder* decoder; + + sint32 texelCountX; + sint32 texelCountY; + }FormatInfoVK; + + struct DeviceInfo + { + DeviceInfo(const std::string name, uint8* uuid) + : name(name) + { + std::copy(uuid, uuid + VK_UUID_SIZE, this->uuid.data()); + } + + std::string name; + std::array<uint8, VK_UUID_SIZE> uuid; + }; + + static std::vector<DeviceInfo> GetDevices(); + VulkanRenderer(); + virtual ~VulkanRenderer(); + + RendererAPI GetType() override { return RendererAPI::Vulkan; } + + static VulkanRenderer* GetInstance(); + + void UnrecoverableError(const char* errMsg) const; + + void GetDeviceFeatures(); + void DetermineVendor(); + void Initialize(const Vector2i& size, bool isMainWindow); + + bool IsPadWindowActive() override; + + void HandleScreenshotRequest(LatteTextureView* texView, bool padView) override; + void ResizeSwapchain(const Vector2i& size, bool isMainWindow); + + void QueryMemoryInfo(); + void QueryAvailableFormats(); + + void EnableVSync(int state) override; + +#if BOOST_OS_WINDOWS + static VkSurfaceKHR CreateWinSurface(VkInstance instance, HWND hwindow); +#endif +#if BOOST_OS_LINUX + static VkSurfaceKHR CreateXlibSurface(VkInstance instance, Display* dpy, Window window); + static VkSurfaceKHR CreateXcbSurface(VkInstance instance, xcb_connection_t* connection, xcb_window_t window); +#endif + + static VkSurfaceKHR CreateFramebufferSurface(VkInstance instance, struct WindowHandleInfo& windowInfo); + + void AppendOverlayDebugInfo() override; + + void ImguiInit(); + VkInstance GetVkInstance() const { return m_instance; } + VkDevice GetLogicalDevice() const { return m_logicalDevice; } + VkPhysicalDevice GetPhysicalDevice() const { return m_physical_device; } + + VkDescriptorPool GetDescriptorPool() const { return m_descriptorPool; } + + void WaitDeviceIdle() const { vkDeviceWaitIdle(m_logicalDevice); } + + void Initialize() override; + void Shutdown() override; + + void SwapBuffers(bool swapTV = true, bool swapDRC = true) override; + + void Flush(bool waitIdle = false) override; + void NotifyLatteCommandProcessorIdle() override; + + uint64 GenUniqueId(); // return unique id (uses incrementing counter) + + void DrawEmptyFrame(bool mainWindow) override; + void PreparePresentationFrame(bool mainWindow); + + void ProcessDestructionQueues(size_t commandBufferIndex); + void InitFirstCommandBuffer(); + void ProcessFinishedCommandBuffers(); + void WaitForNextFinishedCommandBuffer(); + void SubmitCommandBuffer(VkSemaphore* signalSemaphore = nullptr, VkSemaphore* waitSemaphore = nullptr); + void RequestSubmitSoon(); + void RequestSubmitOnIdle(); + + // command buffer synchronization + uint64 GetCurrentCommandBufferId() const; + bool HasCommandBufferFinished(uint64 commandBufferId) const; + void WaitCommandBufferFinished(uint64 commandBufferId); + + // clean up (deprecated) + void destroyViewDepr(VkImageView imageView); + void destroyBuffer(VkBuffer buffer); + void destroyDeviceMemory(VkDeviceMemory mem); + void destroyPipelineInfo(PipelineInfo* pipelineInfo); + void destroyShader(RendererShaderVk* shader); + // clean up (new) + void releaseDestructibleObject(VKRDestructibleObject* destructibleObject); + void ProcessDestructionQueue2(); + + FSpinlock m_spinlockDestructionQueue; + std::vector<VKRDestructibleObject*> m_destructionQueue; + + void PipelineCacheSaveThread(size_t cache_size); + + void ClearColorbuffer(bool padView) override; + void ClearColorImageRaw(VkImage image, uint32 sliceIndex, uint32 mipIndex, const VkClearColorValue& color, VkImageLayout inputLayout, VkImageLayout outputLayout); + void ClearColorImage(LatteTextureVk* vkTexture, uint32 sliceIndex, uint32 mipIndex, const VkClearColorValue& color, VkImageLayout outputLayout); + + void DrawBackbufferQuad(LatteTextureView* texView, RendererOutputShader* shader, bool useLinearTexFilter, sint32 imageX, sint32 imageY, sint32 imageWidth, sint32 imageHeight, bool padView, bool clearBackground) override; + void CreateDescriptorPool(); + VkDescriptorSet backbufferBlit_createDescriptorSet(VkDescriptorSetLayout descriptor_set_layout, LatteTextureViewVk* texViewVk, bool useLinearTexFilter); + + robin_hood::unordered_flat_map<uint64, robin_hood::unordered_flat_map<uint64, PipelineInfo*> > m_pipeline_info_cache; // using robin_hood::unordered_flat_map is twice as fast (1-2% overall CPU time reduction) + void draw_debugPipelineHashState(); + PipelineInfo* draw_getCachedPipeline(); + + // pipeline state hash + static uint64 draw_calculateMinimalGraphicsPipelineHash(const LatteFetchShader* fetchShader, const LatteContextRegister& lcr); + static uint64 draw_calculateGraphicsPipelineHash(const LatteFetchShader* fetchShader, const LatteDecompilerShader* vertexShader, const LatteDecompilerShader* geometryShader, const LatteDecompilerShader* pixelShader, const VKRObjectRenderPass* renderPassObj, const LatteContextRegister& lcr); + + // rendertarget + void renderTarget_setViewport(float x, float y, float width, float height, float nearZ, float farZ, bool halfZ = false) override; + void renderTarget_setScissor(sint32 scissorX, sint32 scissorY, sint32 scissorWidth, sint32 scissorHeight) override; + + LatteCachedFBO* rendertarget_createCachedFBO(uint64 key) override; + void rendertarget_deleteCachedFBO(LatteCachedFBO* cfbo) override; + void rendertarget_bindFramebufferObject(LatteCachedFBO* cfbo) override; + + // texture functions + void* texture_acquireTextureUploadBuffer(uint32 size) override; + void texture_releaseTextureUploadBuffer(uint8* mem) override; + + + TextureDecoder* texture_chooseDecodedFormat(Latte::E_GX2SURFFMT format, bool isDepth, Latte::E_DIM dim, uint32 width, uint32 height) override; + + void texture_reserveTextureOnGPU(LatteTexture* hostTexture) override; + void texture_destroy(LatteTexture* hostTexture) override; + + void texture_clearSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex) override; + void texture_clearColorSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex, float r, float g, float b, float a) override; + void texture_clearDepthSlice(LatteTexture* hostTexture, uint32 sliceIndex, sint32 mipIndex, bool clearDepth, bool clearStencil, float depthValue, uint32 stencilValue) override; + + void texture_loadSlice(LatteTexture* hostTexture, sint32 width, sint32 height, sint32 depth, void* pixelData, sint32 sliceIndex, sint32 mipIndex, uint32 compressedImageSize) override; + + LatteTexture* texture_createTextureEx(uint32 textureUnit, Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, uint32 swizzle, Latte::E_HWTILEMODE tileMode, bool isDepth) override; + + void texture_bindAndActivate(LatteTextureView* textureView, uint32 textureUnit) override; + void texture_bindOnly(LatteTextureView* textureView, uint32 textureUnit) override; + + void texture_bindAndActivateRawTex(LatteTexture* texture, uint32 textureUnit) override {}; + + void texture_rememberBoundTexture(uint32 textureUnit) override {}; + void texture_restoreBoundTexture(uint32 textureUnit) override {}; + void texture_copyImageSubData(LatteTexture* src, sint32 srcMip, sint32 effectiveSrcX, sint32 effectiveSrcY, sint32 srcSlice, LatteTexture* dst, sint32 dstMip, sint32 effectiveDstX, sint32 effectiveDstY, sint32 dstSlice, sint32 effectiveCopyWidth, sint32 effectiveCopyHeight, sint32 srcDepth) override; + LatteTextureReadbackInfo* texture_createReadback(LatteTextureView* textureView) override; + + // surface copy + void surfaceCopy_copySurfaceWithFormatConversion(LatteTexture* sourceTexture, sint32 srcMip, sint32 srcSlice, LatteTexture* destinationTexture, sint32 dstMip, sint32 dstSlice, sint32 width, sint32 height) override; + void surfaceCopy_notifyTextureRelease(LatteTextureVk* hostTexture); + + private: + void surfaceCopy_viaBuffer(LatteTextureVk* srcTextureVk, sint32 texSrcMip, sint32 texSrcLevel, LatteTextureVk* dstTextureVk, sint32 texDstMip, sint32 texDstLevel, sint32 effectiveCopyWidth, sint32 effectiveCopyHeight); + void surfaceCopy_viaDrawcall(LatteTextureVk* srcTextureVk, sint32 texSrcMip, sint32 texSrcSlice, LatteTextureVk* dstTextureVk, sint32 texDstMip, sint32 texDstSlice, sint32 effectiveCopyWidth, sint32 effectiveCopyHeight); + + void surfaceCopy_cleanup(); + +private: + uint64 copySurface_getPipelineStateHash(struct VkCopySurfaceState_t& state); + struct CopySurfacePipelineInfo* copySurface_getCachedPipeline(struct VkCopySurfaceState_t& state); + struct CopySurfacePipelineInfo* copySurface_getOrCreateGraphicsPipeline(struct VkCopySurfaceState_t& state); + VKRObjectTextureView* surfaceCopy_createImageView(LatteTextureVk* textureVk, uint32 sliceIndex, uint32 mipIndex); + VKRObjectFramebuffer* surfaceCopy_getOrCreateFramebuffer(struct VkCopySurfaceState_t& state, struct CopySurfacePipelineInfo* pipelineInfo); + VKRObjectDescriptorSet* surfaceCopy_getOrCreateDescriptorSet(struct VkCopySurfaceState_t& state, struct CopySurfacePipelineInfo* pipelineInfo); + + VKRObjectRenderPass* copySurface_createRenderpass(struct VkCopySurfaceState_t& state); + + std::unordered_map<uint64, struct CopySurfacePipelineInfo*> m_copySurfacePipelineCache; + + VkBuffer m_surfaceCopyBuffer = VK_NULL_HANDLE; + VkDeviceMemory m_surfaceCopyBufferMemory = VK_NULL_HANDLE; + size_t m_surfaceCopyBufferSize{}; + +public: + // renderer interface + void bufferCache_init(const sint32 bufferSize) override; + void bufferCache_upload(uint8* buffer, sint32 size, uint32 bufferOffset) override; + void bufferCache_copy(uint32 srcOffset, uint32 dstOffset, uint32 size) override; + + void buffer_bindVertexBuffer(uint32 bufferIndex, uint32 buffer, uint32 size) override; + void buffer_bindUniformBuffer(LatteConst::ShaderType shaderType, uint32 bufferIndex, uint32 offset, uint32 size) override; + + RendererShader* shader_create(RendererShader::ShaderType type, uint64 baseHash, uint64 auxHash, const std::string& source, bool isGameShader, bool isGfxPackShader) override; + void shader_bind(RendererShader* shader) override; + void shader_unbind(RendererShader::ShaderType shaderType) override; + + void* indexData_reserveIndexMemory(uint32 size, uint32& offset, uint32& bufferIndex) override; + void indexData_uploadIndexMemory(uint32 offset, uint32 size) override; + + // externally callable + void GetTextureFormatInfoVK(Latte::E_GX2SURFFMT format, bool isDepth, Latte::E_DIM dim, sint32 width, sint32 height, FormatInfoVK* formatInfoOut); + void unregisterGraphicsPipeline(PipelineInfo* pipelineInfo); + +private: + struct VkRendererState + { + VkRendererState() = default; + VkRendererState(const VkRendererState&) = delete; + VkRendererState(VkRendererState&&) noexcept = delete; + + // textures + LatteTextureViewVk* boundTexture[128]{}; + + // rendertarget + CachedFBOVk* activeFBO{}; // the FBO active for the emulated GPU + + // command buffer + VkCommandBuffer currentCommandBuffer{}; + + // pipeline + VkPipeline currentPipeline{ VK_NULL_HANDLE }; + + // renderpass + CachedFBOVk* activeRenderpassFBO{}; // the FBO of the currently active Vulkan renderpass + + // drawcall state + PipelineInfo* activePipelineInfo{ nullptr }; + VkDescriptorSetInfo* activeVertexDS{ nullptr }; + VkDescriptorSetInfo* activePixelDS{ nullptr }; + VkDescriptorSetInfo* activeGeometryDS{ nullptr }; + bool descriptorSetsChanged{ false }; + bool hasRenderSelfDependency{ false }; // set if current drawcall samples textures which are also output as a rendertarget + + // viewport and scissor box + VkViewport currentViewport{}; + VkRect2D currentScissorRect{}; + + // vertex bindings + struct + { + uint32 offset; + }currentVertexBinding[LATTE_MAX_VERTEX_BUFFERS]{}; + + // transform feedback + bool hasActiveXfb{}; + + // index buffer + Renderer::INDEX_TYPE activeIndexType{}; + uint32 activeIndexBufferIndex{}; + uint32 activeIndexBufferOffset{}; + + // polygon offset + uint32 prevPolygonFrontOffsetU32{ 0xFFFFFFFF }; + uint32 prevPolygonFrontScaleU32{ 0xFFFFFFFF }; + uint32 prevPolygonFrontClampU32{ 0xFFFFFFFF }; + + void resetCommandBufferState() + { + prevPolygonFrontOffsetU32 = 0xFFFFFFFF; + prevPolygonFrontScaleU32 = 0xFFFFFFFF; + prevPolygonFrontClampU32 = 0xFFFFFFFF; + currentPipeline = VK_NULL_HANDLE; + for (auto& itr : currentVertexBinding) + { + itr.offset = 0xFFFFFFFF; + } + activeIndexType = Renderer::INDEX_TYPE::NONE; + activeIndexBufferIndex = std::numeric_limits<uint32>::max(); + activeIndexBufferOffset = std::numeric_limits<uint32>::max(); + } + + // invalidation / flushing + uint64 currentFlushIndex{0}; + bool requestFlush{ false }; // flush after every draw operation. The renderpass dependencies dont handle dependencies across multiple drawcalls inside a single renderpass + + // draw sequence + bool drawSequenceSkip; // if true, skip draw_execute() + }m_state; + + // swapchain - todo move this to m_swapchainState + struct SwapChainInfo + { + SwapChainInfo(VkDevice device, VkSurfaceKHR surface) : m_device(device), surface(surface) {} + SwapChainInfo(const SwapChainInfo&) = delete; + SwapChainInfo(SwapChainInfo&&) noexcept = default; + ~SwapChainInfo() + { + if (m_swapChainRenderPass) + { + vkDestroyRenderPass(m_device, m_swapChainRenderPass, nullptr); + m_swapChainRenderPass = VK_NULL_HANDLE; + } + if (swapChain) + { + vkDestroySwapchainKHR(m_device, swapChain, nullptr); + swapChain = VK_NULL_HANDLE; + } + for (auto& imageView : m_swapchainImageViews) + vkDestroyImageView(m_device, imageView, nullptr); + m_swapchainImageViews.clear(); + for (auto& framebuffer : m_swapchainFramebuffers) + vkDestroyFramebuffer(m_device, framebuffer, nullptr); + m_swapchainFramebuffers.clear(); + } + + VkDevice m_device; + + VkSurfaceKHR surface{}; + VkSwapchainKHR swapChain{}; + VkExtent2D swapchainExtend{}; + uint32 swapchainImageIndex = (uint32)-1; + uint32 m_acquireIndex = 0; // increases with every successful vkAcquireNextImageKHR + + struct AcquireInfo + { + // move fence here? + VkSemaphore acquireSemaphore; + }; + std::vector<AcquireInfo> m_acquireInfo; // indexed by acquireIndex + + // swapchain image ringbuffer (indexed by swapchainImageIndex) + std::vector<VkImage> m_swapchainImages; + std::vector<VkImageView> m_swapchainImageViews; + std::vector<VkFramebuffer> m_swapchainFramebuffers; + std::vector<VkSemaphore> m_swapchainPresentSemaphores; + + VkFence m_imageAvailableFence = nullptr; + VkRenderPass m_swapChainRenderPass = nullptr; + VkRenderPass m_imguiRenderPass = nullptr; + }; + std::unique_ptr<SwapChainInfo> m_mainSwapchainInfo{}, m_padSwapchainInfo{}; + bool IsSwapchainInfoValid(bool mainWindow) const; + VkSwapchainKHR CreateSwapChain(SwapChainInfo& swap_chain_info, const Vector2i& size, bool mainwindow); + + struct + { + bool resizeRequestedMainWindow{}; + Vector2i newExtentMainWindow; + bool resizeRequestedPadWindow{}; + Vector2i newExtentPadWindow; + bool tvHasDefinedSwapchainImage{}; // indicates if the swapchain image is in a defined state + bool drcHasDefinedSwapchainImage{}; + }m_swapchainState; + + VkDescriptorPool m_descriptorPool; + VkSurfaceFormatKHR m_swapchainFormat; + + bool m_tvBufferUsesSRGB = false; + bool m_drvBufferUsesSRGB = false; + + struct QueueFamilyIndices + { + int32_t graphicsFamily = -1; + int32_t presentFamily = -1; + + bool IsComplete() const { return graphicsFamily >= 0 && presentFamily >= 0; } + }; + static QueueFamilyIndices FindQueueFamilies(VkSurfaceKHR surface, const VkPhysicalDevice& device); + + struct FeatureControl + { + struct + { + // if using new optional extensions add to CheckDeviceExtensionSupport and CreateDeviceCreateInfo + bool tooling_info = false; // VK_EXT_tooling_info + bool transform_feedback = false; + bool depth_range_unrestricted = false; + bool nv_fill_rectangle = false; // NV_fill_rectangle + bool pipeline_feedback = false; + bool pipeline_creation_cache_control = false; // VK_EXT_pipeline_creation_cache_control + bool custom_border_color = false; // VK_EXT_custom_border_color + bool custom_border_color_without_format = false; // VK_EXT_custom_border_color (specifically customBorderColorWithoutFormat) + bool cubic_filter = false; // VK_EXT_FILTER_CUBIC_EXTENSION_NAME + bool driver_properties = false; // VK_KHR_driver_properties + bool external_memory_host = false; // VK_EXT_external_memory_host + bool synchronization2 = false; // VK_KHR_synchronization2 + bool dynamic_rendering = false; // VK_KHR_dynamic_rendering + }deviceExtensions; + + struct + { + bool debug_utils = false; // VK_EXT_DEBUG_UTILS + }instanceExtensions; + + struct + { + bool useBufferSurfaceCopies; // if GPU has enough VRAM to spare, allow to use a buffer to copy surfaces (instead of drawcalls) + bool useTFEmulationViaSSBO = true; // emulate transform feedback via shader writes to a storage buffer + }mode; + + struct + { + uint32 minUniformBufferOffsetAlignment = 256; + uint32 nonCoherentAtomSize = 256; + }limits; + + bool debugMarkersSupported = false; // frame debugger is attached + + }m_featureControl{}; + static bool CheckDeviceExtensionSupport(const VkPhysicalDevice device, FeatureControl& info); + static std::vector<const char*> CheckInstanceExtensionSupport(FeatureControl& info); + + void SwapBuffer(bool main_window); + VSync m_vsync_state = VSync::Immediate; + + struct SwapChainSupportDetails + { + VkSurfaceCapabilitiesKHR capabilities; + std::vector<VkSurfaceFormatKHR> formats; + std::vector<VkPresentModeKHR> presentModes; + }; + + VkDescriptorSetLayout m_swapchainDescriptorSetLayout; + + VkQueue m_graphicsQueue, m_presentQueue; + + // swapchain + + static SwapChainSupportDetails QuerySwapChainSupport(VkSurfaceKHR surface, const VkPhysicalDevice& device); + std::vector<VkDeviceQueueCreateInfo> CreateQueueCreateInfos(const std::set<int>& uniqueQueueFamilies) const; + VkDeviceCreateInfo CreateDeviceCreateInfo(const std::vector<VkDeviceQueueCreateInfo>& queueCreateInfos, const VkPhysicalDeviceFeatures& deviceFeatures, const void* deviceExtensionStructs, std::vector<const char*>& used_extensions) const; + static bool IsDeviceSuitable(VkSurfaceKHR surface, const VkPhysicalDevice& device); + VkSurfaceFormatKHR ChooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& formats, bool mainWindow) const; + VkPresentModeKHR ChooseSwapPresentMode(const std::vector<VkPresentModeKHR>& modes); + VkExtent2D ChooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities, const Vector2i& size) const; + VkSwapchainCreateInfoKHR CreateSwapchainCreateInfo(VkSurfaceKHR surface, const SwapChainSupportDetails& swapChainSupport, const VkSurfaceFormatKHR& surfaceFormat, uint32 imageCount, const VkExtent2D& extent); + + void CreateCommandPool(); + void CreateCommandBuffers(); + + void swapchain_createDescriptorSetLayout(); + + // shader + + bool IsAsyncPipelineAllowed(uint32 numIndices); + + uint64 GetDescriptorSetStateHash(LatteDecompilerShader* shader); + + // imgui + bool ImguiBegin(bool mainWindow) override; + void ImguiEnd() override; + ImTextureID GenerateTexture(const std::vector<uint8>& data, const Vector2i& size) override; + void DeleteTexture(ImTextureID id) override; + void DeleteFontTextures() override; + bool BeginFrame(bool mainWindow) override; + + // drawcall emulation + PipelineInfo* draw_createGraphicsPipeline(uint32 indexCount); + PipelineInfo* draw_getOrCreateGraphicsPipeline(uint32 indexCount); + + void draw_updateVkBlendConstants(); + void draw_updateDepthBias(bool forceUpdate); + + void draw_setRenderPass(); + void draw_endRenderPass(); + + void draw_beginSequence() override; + void draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, bool isFirst) override; + void draw_endSequence() override; + + void draw_updateVertexBuffersDirectAccess(); + void draw_updateUniformBuffersDirectAccess(LatteDecompilerShader* shader, const uint32 uniformBufferRegOffset, LatteConst::ShaderType shaderType); + + void draw_prepareDynamicOffsetsForDescriptorSet(uint32 shaderStageIndex, uint32* dynamicOffsets, sint32& numDynOffsets, const PipelineInfo* pipeline_info); + VkDescriptorSetInfo* draw_getOrCreateDescriptorSet(PipelineInfo* pipeline_info, LatteDecompilerShader* shader); + void draw_prepareDescriptorSets(PipelineInfo* pipeline_info, VkDescriptorSetInfo*& vertexDS, VkDescriptorSetInfo*& pixelDS, VkDescriptorSetInfo*& geometryDS); + void draw_handleSpecialState5(); + + // draw synchronization helper + void sync_inputTexturesChanged(); + void sync_RenderPassLoadTextures(CachedFBOVk* fboVk); + void sync_RenderPassStoreTextures(CachedFBOVk* fboVk); + + // command buffer + + VkCommandBuffer getCurrentCommandBuffer() const { return m_state.currentCommandBuffer; } + + // uniform + void uniformData_updateUniformVars(uint32 shaderStageIndex, LatteDecompilerShader* shader); + + // indices + void CreateBackbufferIndexBuffer(); + + // misc + void CreatePipelineCache(); + VkPipelineShaderStageCreateInfo CreatePipelineShaderStageCreateInfo(VkShaderStageFlagBits stage, VkShaderModule& module, const char* entryName) const; + VkPipeline backbufferBlit_createGraphicsPipeline(VkDescriptorSetLayout descriptorLayout, bool padView, RendererOutputShader* shader); + void AcquireNextSwapchainImage(bool main_window); + void RecreateSwapchain(bool mainwindow); + + // streamout + void streamout_setupXfbBuffer(uint32 bufferIndex, sint32 ringBufferOffset, uint32 rangeAddr, uint32 rangeSize) override; + void streamout_begin() override; + void streamout_applyTransformFeedbackState(); + void bufferCache_copyStreamoutToMainBuffer(uint32 srcOffset, uint32 dstOffset, uint32 size) override; + void streamout_rendererFinishDrawcall() override; + + // occlusion queries + LatteQueryObject* occlusionQuery_create() override; + void occlusionQuery_destroy(LatteQueryObject* queryObj) override; + void occlusionQuery_flush() override; + void occlusionQuery_updateState() override; + void occlusionQuery_notifyEndCommandBuffer(); + void occlusionQuery_notifyBeginCommandBuffer(); + +private: + VkBuffer m_indexBuffer = VK_NULL_HANDLE; + VkDeviceMemory m_indexBufferMemory = VK_NULL_HANDLE; + + std::vector<const char*> m_layerNames; + VkInstance m_instance = VK_NULL_HANDLE; + VkPhysicalDevice m_physical_device = VK_NULL_HANDLE; + VkDevice m_logicalDevice = VK_NULL_HANDLE; + VkDebugUtilsMessengerEXT m_debugCallback = nullptr; + volatile bool m_destructionRequested = false; + + QueueFamilyIndices m_indices{}; + + Semaphore m_pipeline_cache_semaphore; + std::shared_mutex m_pipeline_cache_save_mutex; + std::thread m_pipeline_cache_save_thread; + VkPipelineCache m_pipeline_cache{ nullptr }; + VkPipelineLayout m_pipelineLayout{nullptr}; + VkCommandPool m_commandPool{ nullptr }; + + // buffer to cache uniform vars + VkBuffer m_uniformVarBuffer = VK_NULL_HANDLE; + VkDeviceMemory m_uniformVarBufferMemory = VK_NULL_HANDLE; + bool m_uniformVarBufferMemoryIsCoherent{false}; + uint8* m_uniformVarBufferPtr = nullptr; + uint32 m_uniformVarBufferWriteIndex = 0; + + // transform feedback ringbuffer + VkBuffer m_xfbRingBuffer = VK_NULL_HANDLE; + VkDeviceMemory m_xfbRingBufferMemory = VK_NULL_HANDLE; + + // buffer cache (attributes, uniforms and streamout) + VkBuffer m_bufferCache = VK_NULL_HANDLE; + VkDeviceMemory m_bufferCacheMemory = VK_NULL_HANDLE; + + // texture readback + VkBuffer m_textureReadbackBuffer = VK_NULL_HANDLE; + VkDeviceMemory m_textureReadbackBufferMemory = VK_NULL_HANDLE; + uint8* m_textureReadbackBufferPtr = nullptr; + uint32 m_textureReadbackBufferWriteIndex = 0; + + // placeholder objects to simulate NULL buffers and textures + struct NullTexture + { + VkImage image; + VkImageView view; + VkSampler sampler; + VkImageMemAllocation* allocation; + }; + + NullTexture nullTexture1D{}; + NullTexture nullTexture2D{}; + + void CreateNullTexture(NullTexture& nullTex, VkImageType imageType); + void CreateNullObjects(); + void DeleteNullTexture(NullTexture& nullTex); + void DeleteNullObjects(); + + // if VK_EXT_external_memory_host is supported we can (optionally) import all of the Wii U memory into a Vulkan memory object + // this allows us to skip any vertex/uniform caching logic and let the GPU directly read the memory from main RAM + // Wii U memory imported into a buffer + bool m_useHostMemoryForCache{ false }; + VkBuffer m_importedMem = VK_NULL_HANDLE; + VkDeviceMemory m_importedMemMemory = VK_NULL_HANDLE; + MPTR m_importedMemBaseAddress = 0; + + // command buffer, garbage collection, synchronization + static constexpr uint32 kCommandBufferPoolSize = 128; + + struct + { + std::array<std::vector<VkDescriptorSet>, kCommandBufferPoolSize> m_cmd_descriptor_set_objects; + std::array<std::vector<VkImageView>, kCommandBufferPoolSize> m_cmd_image_views; + std::array<std::vector<LatteTextureVk*>, kCommandBufferPoolSize> m_host_textures; + std::array<std::vector<VkBuffer>, kCommandBufferPoolSize> m_buffers; + std::array<std::vector<VkDeviceMemory>, kCommandBufferPoolSize> m_memory; + }m_destructionQueues; + + size_t m_commandBufferIndex = 0; // current buffer being filled + size_t m_commandBufferSyncIndex = 0; // latest buffer that finished execution (updated on submit) + std::array<VkFence, kCommandBufferPoolSize> m_cmd_buffer_fences; + std::array<VkCommandBuffer, kCommandBufferPoolSize> m_commandBuffers; + std::array<VkSemaphore, kCommandBufferPoolSize> m_commandBufferSemaphores; + + VkSemaphore GetLastSubmittedCmdBufferSemaphore() + { + return m_commandBufferSemaphores[(m_commandBufferIndex + m_commandBufferSemaphores.size() - 1) % m_commandBufferSemaphores.size()]; + } + + uint64 m_numSubmittedCmdBuffers{}; + uint64 m_countCommandBufferFinished{}; + + uint32 m_recordedDrawcalls{}; // number of drawcalls recorded into current command buffer + uint32 m_submitThreshold{}; // submit current buffer if recordedDrawcalls exceeds this number + bool m_submitOnIdle{}; // submit current buffer if Latte command processor goes into idle state (no more commands or waiting for externally signaled condition) + + // tracking for dynamic offsets + struct + { + uint32 uniformVarBufferOffset[VulkanRendererConst::SHADER_STAGE_INDEX_COUNT]; + struct + { + uint32 unformBufferOffset[LATTE_NUM_MAX_UNIFORM_BUFFERS]; + }shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_COUNT]; + }dynamicOffsetInfo{}; + + // streamout + struct + { + struct + { + bool enabled; + uint32 ringBufferOffset; + }buffer[LATTE_NUM_STREAMOUT_BUFFER]; + sint32 verticesPerInstance; + }m_streamoutState{}; + + struct + { + VkQueryPool queryPool{VK_NULL_HANDLE}; + sint32 currentQueryIndex{}; + std::vector<class LatteQueryObjectVk*> list_cachedQueries; + std::vector<class LatteQueryObjectVk*> list_currentlyActiveQueries; + uint64 m_lastCommandBuffer{}; + // query result buffer + VkBuffer bufferQueryResults; + VkDeviceMemory memoryQueryResults; + uint64* ptrQueryResults; + std::vector<uint16> list_availableQueryIndices; + }m_occlusionQueries; + + // barrier + + enum SYNC_OP : uint32 + { + /* name */ /* operations */ + HOST_WRITE = 0x01, + HOST_READ = 0x02, + + // BUFFER_INDEX_READ (should be separated?) + BUFFER_SHADER_READ = 0x04, // any form of shader read access + BUFFER_SHADER_WRITE = 0x08, // any form of shader write access + ANY_TRANSFER = 0x10, // previous transfer to/from buffer or image + TRANSFER_READ = 0x80, // transfer from image/buffer + TRANSFER_WRITE = 0x100, // transfer to image/buffer + + + IMAGE_READ = 0x20, + IMAGE_WRITE = 0x40, + + }; + + template<uint32 TSyncOp> + void barrier_calcStageAndMask(VkPipelineStageFlags& stages, VkAccessFlags& accessFlags) + { + stages = 0; + accessFlags = 0; + if constexpr ((TSyncOp & BUFFER_SHADER_READ) != 0) + { + // in theory: VK_ACCESS_INDEX_READ_BIT should be set here too but indices are currently separated + stages |= VK_PIPELINE_STAGE_VERTEX_INPUT_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + accessFlags |= VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_UNIFORM_READ_BIT | VK_ACCESS_SHADER_READ_BIT; + } + + if constexpr ((TSyncOp & BUFFER_SHADER_WRITE) != 0) + { + stages |= VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + accessFlags |= VK_ACCESS_SHADER_WRITE_BIT; + } + + if constexpr ((TSyncOp & ANY_TRANSFER) != 0) + { + //stages |= VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_HOST_BIT; + //accessFlags |= VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_HOST_READ_BIT | VK_ACCESS_HOST_WRITE_BIT; + stages |= VK_PIPELINE_STAGE_TRANSFER_BIT; + accessFlags |= VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; + + //accessFlags |= VK_ACCESS_MEMORY_READ_BIT; + //accessFlags |= VK_ACCESS_MEMORY_WRITE_BIT; + } + + if constexpr ((TSyncOp & TRANSFER_READ) != 0) + { + stages |= VK_PIPELINE_STAGE_TRANSFER_BIT; + accessFlags |= VK_ACCESS_TRANSFER_READ_BIT; + + //accessFlags |= VK_ACCESS_MEMORY_READ_BIT; + } + + if constexpr ((TSyncOp & TRANSFER_WRITE) != 0) + { + stages |= VK_PIPELINE_STAGE_TRANSFER_BIT; + accessFlags |= VK_ACCESS_TRANSFER_WRITE_BIT; + + //accessFlags |= VK_ACCESS_MEMORY_WRITE_BIT; + } + + if constexpr ((TSyncOp & HOST_WRITE) != 0) + { + stages |= VK_PIPELINE_STAGE_HOST_BIT; + accessFlags |= VK_ACCESS_HOST_WRITE_BIT; + } + + if constexpr ((TSyncOp & HOST_READ) != 0) + { + stages |= VK_PIPELINE_STAGE_HOST_BIT; + accessFlags |= VK_ACCESS_HOST_READ_BIT; + } + + if constexpr ((TSyncOp & IMAGE_READ) != 0) + { + stages |= VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + accessFlags |= VK_ACCESS_SHADER_READ_BIT; + + stages |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + accessFlags |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT; + + stages |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + accessFlags |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT; + } + + if constexpr ((TSyncOp & IMAGE_WRITE) != 0) + { + stages |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + accessFlags |= VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + + stages |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + accessFlags |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + } + } + + template<uint32 TSrcSyncOp, uint32 TDstSyncOp> + void barrier_bufferRange(VkBuffer buffer, VkDeviceSize offset, VkDeviceSize size) + { + VkBufferMemoryBarrier bufMemBarrier{}; + bufMemBarrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; + bufMemBarrier.pNext = nullptr; + bufMemBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + bufMemBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + + VkPipelineStageFlags srcStages = 0; + VkPipelineStageFlags dstStages = 0; + + bufMemBarrier.srcAccessMask = 0; + bufMemBarrier.dstAccessMask = 0; + + barrier_calcStageAndMask<TSrcSyncOp>(srcStages, bufMemBarrier.srcAccessMask); + barrier_calcStageAndMask<TDstSyncOp>(dstStages, bufMemBarrier.dstAccessMask); + + bufMemBarrier.buffer = buffer; + bufMemBarrier.offset = offset; + bufMemBarrier.size = size; + vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStages, dstStages, 0, 0, nullptr, 1, &bufMemBarrier, 0, nullptr); + } + + template<uint32 TSrcSyncOpA, uint32 TDstSyncOpA, uint32 TSrcSyncOpB, uint32 TDstSyncOpB> + void barrier_bufferRange(VkBuffer bufferA, VkDeviceSize offsetA, VkDeviceSize sizeA, + VkBuffer bufferB, VkDeviceSize offsetB, VkDeviceSize sizeB) + { + VkPipelineStageFlags srcStagesA = 0; + VkPipelineStageFlags dstStagesA = 0; + VkPipelineStageFlags srcStagesB = 0; + VkPipelineStageFlags dstStagesB = 0; + + VkBufferMemoryBarrier bufMemBarrier[2]; + + bufMemBarrier[0].sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; + bufMemBarrier[0].pNext = nullptr; + bufMemBarrier[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + bufMemBarrier[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + bufMemBarrier[0].srcAccessMask = 0; + bufMemBarrier[0].dstAccessMask = 0; + barrier_calcStageAndMask<TSrcSyncOpA>(srcStagesA, bufMemBarrier[0].srcAccessMask); + barrier_calcStageAndMask<TDstSyncOpA>(dstStagesA, bufMemBarrier[0].dstAccessMask); + bufMemBarrier[0].buffer = bufferA; + bufMemBarrier[0].offset = offsetA; + bufMemBarrier[0].size = sizeA; + + bufMemBarrier[1].sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; + bufMemBarrier[1].pNext = nullptr; + bufMemBarrier[1].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + bufMemBarrier[1].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + bufMemBarrier[1].srcAccessMask = 0; + bufMemBarrier[1].dstAccessMask = 0; + barrier_calcStageAndMask<TSrcSyncOpB>(srcStagesB, bufMemBarrier[1].srcAccessMask); + barrier_calcStageAndMask<TDstSyncOpB>(dstStagesB, bufMemBarrier[1].dstAccessMask); + bufMemBarrier[1].buffer = bufferB; + bufMemBarrier[1].offset = offsetB; + bufMemBarrier[1].size = sizeB; + + vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStagesA|srcStagesB, dstStagesA|dstStagesB, 0, 0, nullptr, 2, bufMemBarrier, 0, nullptr); + } + + void barrier_sequentializeTransfer() + { + VkMemoryBarrier memBarrier{}; + memBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER; + memBarrier.pNext = nullptr; + + VkPipelineStageFlags srcStages = VK_PIPELINE_STAGE_TRANSFER_BIT; + VkPipelineStageFlags dstStages = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + + memBarrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; + memBarrier.dstAccessMask = 0; + + memBarrier.srcAccessMask |= (VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT); + memBarrier.dstAccessMask |= (VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT); + + vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStages, dstStages, 0, 1, &memBarrier, 0, nullptr, 0, nullptr); + } + + void barrier_sequentializeCommand() + { + VkPipelineStageFlags srcStages = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; + VkPipelineStageFlags dstStages = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + + vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStages, dstStages, 0, 0, nullptr, 0, nullptr, 0, nullptr); + } + + template<uint32 TSrcSyncOp, uint32 TDstSyncOp> + void barrier_image(LatteTextureVk* vkTexture, VkImageSubresourceLayers& subresourceLayers, VkImageLayout newLayout) + { + VkImage imageVk = vkTexture->GetImageObj()->m_image; + + VkPipelineStageFlags srcStages = 0; + VkPipelineStageFlags dstStages = 0; + + VkImageMemoryBarrier imageMemBarrier{}; + imageMemBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + imageMemBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + imageMemBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + imageMemBarrier.srcAccessMask = 0; + imageMemBarrier.dstAccessMask = 0; + barrier_calcStageAndMask<TSrcSyncOp>(srcStages, imageMemBarrier.srcAccessMask); + barrier_calcStageAndMask<TDstSyncOp>(dstStages, imageMemBarrier.dstAccessMask); + imageMemBarrier.image = imageVk; + imageMemBarrier.subresourceRange.aspectMask = subresourceLayers.aspectMask; + imageMemBarrier.subresourceRange.baseArrayLayer = subresourceLayers.baseArrayLayer; + imageMemBarrier.subresourceRange.layerCount = subresourceLayers.layerCount; + imageMemBarrier.subresourceRange.baseMipLevel = subresourceLayers.mipLevel; + imageMemBarrier.subresourceRange.levelCount = 1; + imageMemBarrier.oldLayout = vkTexture->GetImageLayout(imageMemBarrier.subresourceRange); + imageMemBarrier.newLayout = newLayout; + + vkCmdPipelineBarrier(m_state.currentCommandBuffer, + srcStages, dstStages, + 0, + 0, NULL, + 0, NULL, + 1, &imageMemBarrier); + + vkTexture->SetImageLayout(imageMemBarrier.subresourceRange, newLayout); + } + + +public: + bool useTFViaSSBO() { return m_featureControl.mode.useTFEmulationViaSSBO; }; + bool IsDebugUtilsEnabled() const + { + return m_featureControl.debugMarkersSupported && m_featureControl.instanceExtensions.debug_utils; + } + +private: + + // debug + void debug_genericBarrier(); + + // shaders + struct + { + RendererShaderVk* copySurface_vs{}; + RendererShaderVk* copySurface_psDepth2Color{}; + RendererShaderVk* copySurface_psColor2Depth{}; + }defaultShaders; + + +}; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp new file mode 100644 index 00000000..e47b5bf0 --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp @@ -0,0 +1,1705 @@ +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.h" + +#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" +#include "Cafe/HW/Latte/Core/LatteShader.h" +#include "Cafe/HW/Latte/Core/FetchShader.h" +#include "Cafe/HW/Latte/Core/LatteIndices.h" +#include "Cafe/OS/libs/gx2/GX2.h" +#include "imgui/imgui_impl_vulkan.h" +#include "Cafe/GameProfile/GameProfile.h" +#include "util/helpers/helpers.h" + +extern bool hasValidFramebufferAttached; + +// includes only states that may change during minimal drawcalls +uint64 VulkanRenderer::draw_calculateMinimalGraphicsPipelineHash(const LatteFetchShader* fetchShader, const LatteContextRegister& lcr) +{ + uint64 stateHash = 0; + + // fetch shader + for (auto& group : fetchShader->bufferGroups) + { + uint32 bufferStride = group.getCurrentBufferStride(lcr.GetRawView()); + stateHash = _rotl64(stateHash, 7); + stateHash += bufferStride * 3; + } + + stateHash += fetchShader->getVkPipelineHashFragment(); + stateHash = _rotl64(stateHash, 7); + + stateHash += lcr.GetRawView()[mmVGT_PRIMITIVE_TYPE]; + stateHash = _rotl64(stateHash, 7); + + stateHash += lcr.GetRawView()[mmVGT_STRMOUT_EN]; + stateHash = _rotl64(stateHash, 7); + + if(lcr.PA_CL_CLIP_CNTL.get_DX_RASTERIZATION_KILL()) + stateHash += 0x333333; + + return stateHash; +} + +uint64 VulkanRenderer::draw_calculateGraphicsPipelineHash(const LatteFetchShader* fetchShader, const LatteDecompilerShader* vertexShader, const LatteDecompilerShader* geometryShader, const LatteDecompilerShader* pixelShader, const VKRObjectRenderPass* renderPassObj, const LatteContextRegister& lcr) +{ + // note: vertexShader references a fetchShader (vertexShader->fetchShader) but it's not necessarily the one that is currently active + // this is because we try to separate dynamic state (mainly attribute offsets) from the actual attribute data layout and mapping (types and slots) + // on Vulkan this causes issues because we bake the attribute offsets, which may not match vertexShader->compatibleFetchShader, into the pipeline + // To avoid issues always use the active fetch shader. Not the one associated with the vertexShader object + // note 2: + // there is a secondary issue where we dont store all fetch shaders into the pipeline cache (only a single fetch shader is tied to each stored vertex shader) + // but we can probably trust drivers to not require pipeline recompilation if only the offsets differ + // An alternative would be to use VK_EXT_vertex_input_dynamic_state but it comes with minor overhead + // Regardless, the extension is not well supported as of writing this (July 2021, only 10% of GPUs support it on Windows. Nvidia only) + + cemu_assert_debug(fetchShader->key == fetchShader->key); // fetch shaders must be layout compatible, but may have different offsets + + uint64 stateHash; + stateHash = draw_calculateMinimalGraphicsPipelineHash(fetchShader, lcr); + stateHash = (stateHash >> 8) + (stateHash * 0x370531ull) % 0x7F980D3BF9B4639Dull; + + uint32* ctxRegister = lcr.GetRawView(); + + if (vertexShader) + stateHash += vertexShader->baseHash; + + stateHash = _rotl64(stateHash, 13); + + if (geometryShader) + stateHash += geometryShader->baseHash; + + stateHash = _rotl64(stateHash, 13); + + if (pixelShader) + stateHash += pixelShader->baseHash + pixelShader->auxHash; + + stateHash = _rotl64(stateHash, 13); + + uint32 polygonCtrl = lcr.PA_SU_SC_MODE_CNTL.getRawValue(); + stateHash += polygonCtrl; + stateHash = _rotl64(stateHash, 7); + + stateHash += ctxRegister[Latte::REGADDR::PA_CL_CLIP_CNTL]; + stateHash = _rotl64(stateHash, 7); + + const auto colorControlReg = ctxRegister[Latte::REGADDR::CB_COLOR_CONTROL]; + stateHash += colorControlReg; + + stateHash += ctxRegister[Latte::REGADDR::CB_TARGET_MASK]; + + const uint32 blendEnableMask = (colorControlReg >> 8) & 0xFF; + if (blendEnableMask) + { + for (auto i = 0; i < 8; ++i) + { + if (((blendEnableMask & (1 << i))) == 0) + continue; + stateHash = _rotl64(stateHash, 7); + stateHash += ctxRegister[Latte::REGADDR::CB_BLEND0_CONTROL + i]; + } + } + + stateHash += renderPassObj->m_hashForPipeline; + + uint32 depthControl = ctxRegister[Latte::REGADDR::DB_DEPTH_CONTROL]; + bool stencilTestEnable = depthControl & 1; + if (stencilTestEnable) + { + stateHash += ctxRegister[mmDB_STENCILREFMASK]; + stateHash = _rotl64(stateHash, 17); + if(depthControl & (1<<7)) // back stencil enable + { + stateHash += ctxRegister[mmDB_STENCILREFMASK_BF]; + stateHash = _rotl64(stateHash, 13); + } + } + else + { + // zero out stencil related bits (8-31) + depthControl &= 0xFF; + } + + stateHash = _rotl64(stateHash, 17); + stateHash += depthControl; + + // polygon offset + if (polygonCtrl & (1 << 11)) + { + // front offset enabled + stateHash += 0x1111; + } + + return stateHash; +} + +void VulkanRenderer::draw_debugPipelineHashState() +{ + cemu_assert_debug(false); +} + +PipelineInfo* VulkanRenderer::draw_getCachedPipeline() +{ + // todo - optimize m_pipeline_info_cache away and store directly in vk vertex shader + const auto fetchShader = LatteSHRC_GetActiveFetchShader(); + const auto vertexShader = LatteSHRC_GetActiveVertexShader(); + const auto it = m_pipeline_info_cache.find(vertexShader->baseHash); + if (it == m_pipeline_info_cache.cend()) + return nullptr; + + const auto geometryShader = LatteSHRC_GetActiveGeometryShader(); + const auto pixelShader = LatteSHRC_GetActivePixelShader(); + auto cachedFboVk = (CachedFBOVk*)m_state.activeFBO; + + const uint64 stateHash = draw_calculateGraphicsPipelineHash(fetchShader, vertexShader, geometryShader, pixelShader, cachedFboVk->GetRenderPassObj(), LatteGPUState.contextNew); + + const auto innerit = it->second.find(stateHash); + if (innerit == it->second.cend()) + return nullptr; + + return innerit->second; +} + +void VulkanRenderer::unregisterGraphicsPipeline(PipelineInfo* pipelineInfo) +{ + bool removedFromCache = false; + for (auto& topMapItr : m_pipeline_info_cache) + { + auto& subMap = topMapItr.second; + for (auto it = subMap.cbegin(); it != subMap.cend();) + { + if (it->second == pipelineInfo) + { + subMap.erase(it); + removedFromCache = true; + break; + } + ++it; + } + if (removedFromCache) + break; + } +} + +bool g_compilePipelineThreadInit{false}; +std::mutex g_compilePipelineMutex; +std::condition_variable g_compilePipelineCondVar; +std::queue<PipelineCompiler*> g_compilePipelineRequests; + +void compilePipeline_thread(sint32 threadIndex) +{ +#ifdef _WIN32 + // one thread runs at normal priority while the others run at lower priority + if(threadIndex != 0) + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL); +#endif + while (true) + { + std::unique_lock lock(g_compilePipelineMutex); + while (g_compilePipelineRequests.empty()) + g_compilePipelineCondVar.wait(lock); + + PipelineCompiler* request = g_compilePipelineRequests.front(); + + g_compilePipelineRequests.pop(); + + lock.unlock(); + + request->Compile(true, false, true); + delete request; + } +} + +void compilePipelineThread_init() +{ + uint32 numCompileThreads; + + uint32 cpuCoreCount = GetPhysicalCoreCount(); + if (cpuCoreCount <= 2) + numCompileThreads = 1; + else + numCompileThreads = 2 + (cpuCoreCount - 3); // 2 plus one additionally for every extra core above 3 + + numCompileThreads = std::min(numCompileThreads, 8u); // cap at 8 + + for (uint32_t i = 0; i < numCompileThreads; i++) + { + std::thread compileThread(compilePipeline_thread, i); + compileThread.detach(); + } +} + +void compilePipelineThread_queue(PipelineCompiler* v) +{ + std::unique_lock lock(g_compilePipelineMutex); + g_compilePipelineRequests.push(std::move(v)); + lock.unlock(); + g_compilePipelineCondVar.notify_one(); +} + +// make a guess if a pipeline is not essential +// non-essential means that skipping these drawcalls shouldn't lead to permanently corrupted graphics +bool VulkanRenderer::IsAsyncPipelineAllowed(uint32 numIndices) +{ + // frame debuggers dont handle async well (as of 2020) + if (IsDebugUtilsEnabled() && vkSetDebugUtilsObjectNameEXT) + return false; + + CachedFBOVk* currentFBO = m_state.activeFBO; + auto fboExtend = currentFBO->GetExtend(); + + if (fboExtend.width == 1600 && fboExtend.height == 1600) + return false; // Splatoon ink mechanics use 1600x1600 R8 and R8G8 framebuffers, this resolution is rare enough that we can just blacklist it globally + + if (currentFBO->hasDepthBuffer()) + return true; // aggressive filter but seems to work well so far + + // small index count (3,4,5,6) is often associated with full-viewport quads (which are considered essential due to often being used to generate persistent textures) + if (numIndices <= 6) + { + + return false; + } + + return true; +} + +// create graphics pipeline for current state +PipelineInfo* VulkanRenderer::draw_createGraphicsPipeline(uint32 indexCount) +{ + if (!g_compilePipelineThreadInit) + { + compilePipelineThread_init(); + g_compilePipelineThreadInit = true; + } + + const auto fetchShader = LatteSHRC_GetActiveFetchShader(); + const auto vertexShader = LatteSHRC_GetActiveVertexShader(); + const auto geometryShader = LatteSHRC_GetActiveGeometryShader(); + const auto pixelShader = LatteSHRC_GetActivePixelShader(); + auto cachedFboVk = (CachedFBOVk*)m_state.activeFBO; + + uint64 minimalStateHash = draw_calculateMinimalGraphicsPipelineHash(fetchShader, LatteGPUState.contextNew); + uint64 pipelineHash = draw_calculateGraphicsPipelineHash(fetchShader, vertexShader, geometryShader, pixelShader, cachedFboVk->GetRenderPassObj(), LatteGPUState.contextNew); + + // create PipelineInfo + auto vkFBO = (CachedFBOVk*)(VulkanRenderer::GetInstance()->m_state.activeFBO); + PipelineInfo* pipelineInfo = new PipelineInfo(minimalStateHash, pipelineHash, fetchShader, vertexShader, pixelShader, geometryShader); + + // register pipeline + uint64 vsBaseHash = vertexShader->baseHash; + auto it = m_pipeline_info_cache.emplace(vsBaseHash, robin_hood::unordered_flat_map<uint64, PipelineInfo*>()); + auto& cache_map = it.first->second; + cache_map.emplace(pipelineHash, pipelineInfo); + + // init pipeline compiler + PipelineCompiler* pipelineCompiler = new PipelineCompiler(); + + pipelineCompiler->InitFromCurrentGPUState(pipelineInfo, LatteGPUState.contextNew, vkFBO->GetRenderPassObj()); + pipelineCompiler->TrackAsCached(vsBaseHash, pipelineHash); + + // use heuristics based on parameter patterns to determine if the current drawcall is essential (non-skipable) + bool allowAsyncCompile = false; + if (GetConfig().async_compile) + allowAsyncCompile = IsAsyncPipelineAllowed(indexCount); + + if (allowAsyncCompile) + { + // even when async is allowed, attempt synchronous creation first (which will immediately fail if the pipeline is not cached) + if (pipelineCompiler->Compile(false, true, true) == false) + { + // shaders or pipeline not cached -> asynchronous compilation + compilePipelineThread_queue(pipelineCompiler); + } + else + { + delete pipelineCompiler; + } + } + else + { + // synchronous compilation + pipelineCompiler->Compile(true, true, true); + delete pipelineCompiler; + } + + return pipelineInfo; +} + +PipelineInfo* VulkanRenderer::draw_getOrCreateGraphicsPipeline(uint32 indexCount) +{ + auto cache_object = draw_getCachedPipeline(); + if (cache_object != nullptr) + { + +#ifndef PUBLIC_RELEASE + cemu_assert_debug(cache_object->vertexShader == LatteSHRC_GetActiveVertexShader()); + cemu_assert_debug(cache_object->geometryShader == LatteSHRC_GetActiveGeometryShader()); + cemu_assert_debug(cache_object->pixelShader == LatteSHRC_GetActivePixelShader()); + if (cache_object->fetchShader->key != LatteSHRC_GetActiveFetchShader()->key || + cache_object->fetchShader->vkPipelineHashFragment != LatteSHRC_GetActiveFetchShader()->vkPipelineHashFragment) + { + debug_printf("Incompatible fetch shader %p %p\n", cache_object->fetchShader, LatteSHRC_GetActiveFetchShader()); + assert_dbg(); + } + uint64 calcMinimalHash = draw_calculateMinimalGraphicsPipelineHash(LatteSHRC_GetActiveFetchShader(), LatteGPUState.contextNew); + auto currentPrimitiveMode = LatteGPUState.contextNew.VGT_PRIMITIVE_TYPE.get_PRIMITIVE_MODE(); + cemu_assert_debug(cache_object->primitiveMode == currentPrimitiveMode); + cemu_assert_debug(cache_object->minimalStateHash == calcMinimalHash); +#endif + return cache_object; + } + //draw_debugPipelineHashState(); + + return draw_createGraphicsPipeline(indexCount); +} + +void* VulkanRenderer::indexData_reserveIndexMemory(uint32 size, uint32& offset, uint32& bufferIndex) +{ + auto& indexAllocator = this->memoryManager->getIndexAllocator(); + auto resv = indexAllocator.AllocateBufferMemory(size, 32); + offset = resv.bufferOffset; + bufferIndex = resv.bufferIndex; + return resv.memPtr; +} + +void VulkanRenderer::indexData_uploadIndexMemory(uint32 offset, uint32 size) +{ + // does nothing since the index buffer memory is coherent +} + +void VulkanRenderer::uniformData_updateUniformVars(uint32 shaderStageIndex, LatteDecompilerShader* shader) +{ + sint32 shaderAluConst; + sint32 shaderUniformRegisterOffset; + + switch (shader->shaderType) + { + case LatteConst::ShaderType::Vertex: + shaderAluConst = 0x400; + shaderUniformRegisterOffset = mmSQ_VTX_UNIFORM_BLOCK_START; + break; + case LatteConst::ShaderType::Pixel: + shaderAluConst = 0; + shaderUniformRegisterOffset = mmSQ_PS_UNIFORM_BLOCK_START; + break; + case LatteConst::ShaderType::Geometry: + shaderAluConst = 0; // geometry shader has no ALU const + shaderUniformRegisterOffset = mmSQ_GS_UNIFORM_BLOCK_START; + break; + default: + cemu_assert_debug(false); + } + + if (shader->resourceMapping.uniformVarsBufferBindingPoint >= 0) + { + float uniformData[512 * 4]; + + if (shader->uniform.list_ufTexRescale.empty() == false) + { + for (auto& entry : shader->uniform.list_ufTexRescale) + { + float* xyScale = LatteTexture_getEffectiveTextureScale(shader->shaderType, entry.texUnit); + float* v = uniformData + (entry.uniformLocation / 4); + memcpy(entry.currentValue, xyScale, sizeof(float) * 2); + memcpy(v, xyScale, sizeof(float) * 2); + } + } + if (shader->uniform.loc_alphaTestRef >= 0) + { + float* v = uniformData + (shader->uniform.loc_alphaTestRef / 4); + v[0] = LatteGPUState.contextNew.SX_ALPHA_REF.get_ALPHA_TEST_REF(); + } + if (shader->uniform.loc_pointSize >= 0) + { + const auto& pointSizeReg = LatteGPUState.contextNew.PA_SU_POINT_SIZE; + float pointWidth = (float)pointSizeReg.get_WIDTH() / 8.0f; + if (pointWidth == 0.0f) + pointWidth = 1.0f / 8.0f; // minimum size + float* v = uniformData + (shader->uniform.loc_pointSize / 4); + v[0] = pointWidth; + } + if (shader->uniform.loc_remapped >= 0) + { + LatteBufferCache_LoadRemappedUniforms(shader, uniformData + (shader->uniform.loc_remapped / 4)); + } + if (shader->uniform.loc_uniformRegister >= 0) + { + uint32* uniformRegData = (uint32*)(LatteGPUState.contextRegister + mmSQ_ALU_CONSTANT0_0 + shaderAluConst); + float* v = uniformData + (shader->uniform.loc_uniformRegister / 4); + memcpy(v, uniformRegData, shader->uniform.count_uniformRegister * 16); + } + if (shader->uniform.loc_windowSpaceToClipSpaceTransform >= 0) + { + sint32 viewportWidth; + sint32 viewportHeight; + LatteRenderTarget_GetCurrentVirtualViewportSize(&viewportWidth, &viewportHeight); // always call after _updateViewport() + float* v = uniformData + (shader->uniform.loc_windowSpaceToClipSpaceTransform / 4); + v[0] = 2.0f / (float)viewportWidth; + v[1] = 2.0f / (float)viewportHeight; + } + if (shader->uniform.loc_fragCoordScale >= 0) + { + float* coordScale = uniformData + (shader->uniform.loc_fragCoordScale / 4); + LatteMRT::GetCurrentFragCoordScale(coordScale); + } + if (shader->uniform.loc_verticesPerInstance >= 0) + { + *(int*)(uniformData + (shader->uniform.loc_verticesPerInstance / 4)) = m_streamoutState.verticesPerInstance; + for (sint32 b = 0; b < LATTE_NUM_STREAMOUT_BUFFER; b++) + { + if (shader->uniform.loc_streamoutBufferBase[b] >= 0) + { + *(int*)(uniformData + (shader->uniform.loc_streamoutBufferBase[b] / 4)) = m_streamoutState.buffer[b].ringBufferOffset; + } + } + } + // upload + if ((m_uniformVarBufferWriteIndex + shader->uniform.uniformRangeSize + 1024) > UNIFORMVAR_RINGBUFFER_SIZE) + { + m_uniformVarBufferWriteIndex = 0; + } + uint32 bufferAlignmentM1 = std::max(m_featureControl.limits.minUniformBufferOffsetAlignment, m_featureControl.limits.nonCoherentAtomSize) - 1; + const uint32 uniformOffset = m_uniformVarBufferWriteIndex; + memcpy(m_uniformVarBufferPtr + uniformOffset, uniformData, shader->uniform.uniformRangeSize); + m_uniformVarBufferWriteIndex += shader->uniform.uniformRangeSize; + m_uniformVarBufferWriteIndex = (m_uniformVarBufferWriteIndex + bufferAlignmentM1) & ~bufferAlignmentM1; + // update dynamic offset + dynamicOffsetInfo.uniformVarBufferOffset[shaderStageIndex] = uniformOffset; + // flush if not coherent + if (!m_uniformVarBufferMemoryIsCoherent) + { + uint32 nonCoherentAtomSizeM1 = m_featureControl.limits.nonCoherentAtomSize - 1; + VkMappedMemoryRange flushedRange{}; + flushedRange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; + flushedRange.memory = m_uniformVarBufferMemory; + flushedRange.offset = uniformOffset; + flushedRange.size = (shader->uniform.uniformRangeSize + nonCoherentAtomSizeM1) & ~nonCoherentAtomSizeM1; + vkFlushMappedMemoryRanges(m_logicalDevice, 1, &flushedRange); + } + } +} + +void VulkanRenderer::draw_prepareDynamicOffsetsForDescriptorSet(uint32 shaderStageIndex, uint32* dynamicOffsets, + sint32& numDynOffsets, + const PipelineInfo* pipeline_info) +{ + numDynOffsets = 0; + if (pipeline_info->dynamicOffsetInfo.hasUniformVar[shaderStageIndex]) + { + dynamicOffsets[0] = dynamicOffsetInfo.uniformVarBufferOffset[shaderStageIndex]; + numDynOffsets++; + } + if (pipeline_info->dynamicOffsetInfo.hasUniformBuffers[shaderStageIndex]) + { + for (auto& itr : pipeline_info->dynamicOffsetInfo.list_uniformBuffers[shaderStageIndex]) + { + dynamicOffsets[numDynOffsets] = dynamicOffsetInfo.shaderUB[shaderStageIndex].unformBufferOffset[itr]; + numDynOffsets++; + } + } +} + +uint64 VulkanRenderer::GetDescriptorSetStateHash(LatteDecompilerShader* shader) +{ + uint64 hash = 0; + + const sint32 textureCount = shader->resourceMapping.getTextureCount(); + for (int i = 0; i < textureCount; ++i) + { + const auto relative_textureUnit = shader->resourceMapping.getTextureUnitFromBindingPoint(i); + auto hostTextureUnit = relative_textureUnit; + auto textureDim = shader->textureUnitDim[relative_textureUnit]; + auto texUnitRegIndex = hostTextureUnit * 7; + switch (shader->shaderType) + { + case LatteConst::ShaderType::Vertex: + hostTextureUnit += CEMU_VS_TEX_UNIT_BASE; + texUnitRegIndex += Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_VS; + break; + case LatteConst::ShaderType::Pixel: + hostTextureUnit += CEMU_PS_TEX_UNIT_BASE; + texUnitRegIndex += Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_PS; + break; + case LatteConst::ShaderType::Geometry: + hostTextureUnit += CEMU_GS_TEX_UNIT_BASE; + texUnitRegIndex += Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_GS; + break; + default: + __assume(false); + } + + auto texture = m_state.boundTexture[hostTextureUnit]; + if (!texture) + continue; + + const uint32 word4 = LatteGPUState.contextRegister[texUnitRegIndex + 4]; + + uint32 samplerIndex = shader->textureUnitSamplerAssignment[relative_textureUnit]; + if (samplerIndex != LATTE_DECOMPILER_SAMPLER_NONE) + { + samplerIndex += LatteDecompiler_getTextureSamplerBaseIndex(shader->shaderType); + hash += LatteGPUState.contextRegister[Latte::REGADDR::SQ_TEX_SAMPLER_WORD0_0 + samplerIndex * 3 + 0]; + hash = _rotl64(hash, 7); + hash += LatteGPUState.contextRegister[Latte::REGADDR::SQ_TEX_SAMPLER_WORD0_0 + samplerIndex * 3 + 1]; + hash = _rotl64(hash, 7); + hash += LatteGPUState.contextRegister[Latte::REGADDR::SQ_TEX_SAMPLER_WORD0_0 + samplerIndex * 3 + 2]; + hash = _rotl64(hash, 7); + } + hash = _rotl64(hash, 7); + // hash view id + swizzle mask + hash += (uint64)texture->GetUniqueId(); + hash = _rotr64(hash, 21); + hash += (uint64)(word4 & 0x0FFF0000); + } + + return hash; +} + +VkDescriptorSetInfo* VulkanRenderer::draw_getOrCreateDescriptorSet(PipelineInfo* pipeline_info, LatteDecompilerShader* shader) +{ + const uint64 stateHash = GetDescriptorSetStateHash(shader); + + VkDescriptorSetLayout descriptor_set_layout; + switch (shader->shaderType) + { + case LatteConst::ShaderType::Vertex: + { + const auto it = pipeline_info->vertex_ds_cache.find(stateHash); + if (it != pipeline_info->vertex_ds_cache.cend()) + return it->second; + descriptor_set_layout = pipeline_info->m_vkrObjPipeline->vertexDSL; + break; + } + case LatteConst::ShaderType::Pixel: + { + const auto it = pipeline_info->pixel_ds_cache.find(stateHash); + if (it != pipeline_info->pixel_ds_cache.cend()) + return it->second; + descriptor_set_layout = pipeline_info->m_vkrObjPipeline->pixelDSL; + break; + } + case LatteConst::ShaderType::Geometry: + { + const auto it = pipeline_info->geometry_ds_cache.find(stateHash); + if (it != pipeline_info->geometry_ds_cache.cend()) + return it->second; + descriptor_set_layout = pipeline_info->m_vkrObjPipeline->geometryDSL; + break; + } + default: + __assume(false); + } + + // create new descriptor set + VkDescriptorSetInfo* dsInfo = new VkDescriptorSetInfo(); + dsInfo->stateHash = stateHash; + dsInfo->shaderType = shader->shaderType; + dsInfo->pipeline_info = pipeline_info; + + dsInfo->m_vkObjDescriptorSet = new VKRObjectDescriptorSet(); + auto vkObjDS = dsInfo->m_vkObjDescriptorSet; + + VkDescriptorSetAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = m_descriptorPool; + allocInfo.descriptorSetCount = 1; + allocInfo.pSetLayouts = &descriptor_set_layout; + + VkDescriptorSet result; + if (vkAllocateDescriptorSets(m_logicalDevice, &allocInfo, &result) != VK_SUCCESS) + { + UnrecoverableError(fmt::format("Failed to allocate descriptor sets. Currently allocated: Descriptors={} TextureSamplers={} DynUniformBuffers={} StorageBuffers={}", + performanceMonitor.vk.numDescriptorSets.get(), + performanceMonitor.vk.numDescriptorSamplerTextures.get(), + performanceMonitor.vk.numDescriptorDynUniformBuffers.get(), + performanceMonitor.vk.numDescriptorStorageBuffers.get() + ).c_str()); + } + vkObjDS->descriptorSet = result; + + sint32 textureCount = shader->resourceMapping.getTextureCount(); + + std::vector<VkWriteDescriptorSet> descriptorWrites; + std::vector<VkDescriptorImageInfo> textureArray; + for (int i = 0; i < textureCount; ++i) + { + VkDescriptorImageInfo info{}; + const auto relative_textureUnit = shader->resourceMapping.getTextureUnitFromBindingPoint(i); + auto hostTextureUnit = relative_textureUnit; + auto textureDim = shader->textureUnitDim[relative_textureUnit]; + auto texUnitRegIndex = hostTextureUnit * 7; + switch (shader->shaderType) + { + case LatteConst::ShaderType::Vertex: + hostTextureUnit += CEMU_VS_TEX_UNIT_BASE; + texUnitRegIndex += Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_VS; + break; + case LatteConst::ShaderType::Pixel: + hostTextureUnit += CEMU_PS_TEX_UNIT_BASE; + texUnitRegIndex += Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_PS; + break; + case LatteConst::ShaderType::Geometry: + hostTextureUnit += CEMU_GS_TEX_UNIT_BASE; + texUnitRegIndex += Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_GS; + break; + default: + __assume(false); + } + + auto textureView = m_state.boundTexture[hostTextureUnit]; + if (!textureView) + { + if (textureDim == Latte::E_DIM::DIM_1D) + { + info.imageLayout = VK_IMAGE_LAYOUT_GENERAL; + info.imageView = nullTexture1D.view; + info.sampler = nullTexture1D.sampler; + textureArray.emplace_back(info); + } + else + { + info.imageLayout = VK_IMAGE_LAYOUT_GENERAL; + info.imageView = nullTexture2D.view; + info.sampler = nullTexture2D.sampler; + textureArray.emplace_back(info); + } + continue; + } + // safeguard to avoid using mismatching texture dimensions + // if we properly support aux hash for vs/gs this should never trigger + if (textureDim == Latte::E_DIM::DIM_1D && (textureView->dim != Latte::E_DIM::DIM_1D)) + { + // should be 1D + info.imageLayout = VK_IMAGE_LAYOUT_GENERAL; + info.imageView = nullTexture1D.view; + info.sampler = nullTexture1D.sampler; + textureArray.emplace_back(info); + // forceLog_printf("Vulkan-Info: Shader 0x%016llx uses 1D texture but bound texture has mismatching type (dim: 0x%02x)", shader->baseHash, textureView->gx2Dim); + continue; + } + else if (textureDim == Latte::E_DIM::DIM_2D && (textureView->dim != Latte::E_DIM::DIM_2D && textureView->dim != Latte::E_DIM::DIM_2D_MSAA)) + { + // should be 2D + // is GPU7 fine with 2D access to a 2D_ARRAY texture? + info.imageLayout = VK_IMAGE_LAYOUT_GENERAL; + info.imageView = nullTexture2D.view; + info.sampler = nullTexture2D.sampler; + textureArray.emplace_back(info); + // forceLog_printf("Vulkan-Info: Shader 0x%016llx uses 2D texture but bound texture has mismatching type (dim: 0x%02x)", shader->baseHash, textureView->gx2Dim); + continue; + } + + info.imageLayout = VK_IMAGE_LAYOUT_GENERAL; + + VkSamplerCustomBorderColorCreateInfoEXT samplerCustomBorderColor{}; + + VkSampler sampler; + VkSamplerCreateInfo samplerInfo{}; + samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + + LatteTexture* baseTexture = textureView->baseTexture; + // get texture register word 0 + uint32 word4 = LatteGPUState.contextRegister[texUnitRegIndex + 4]; + + auto imageViewObj = textureView->GetSamplerView(word4); + info.imageView = imageViewObj->m_textureImageView; + vkObjDS->addRef(imageViewObj); + + // track relation between view and descriptor set + vectorAppendUnique(dsInfo->list_referencedViews, textureView); + textureView->AddDescriptorSetReference(dsInfo); + + if (!baseTexture->IsCompressedFormat()) + vectorAppendUnique(dsInfo->list_fboCandidates, (LatteTextureVk*)baseTexture); + + uint32 stageSamplerIndex = shader->textureUnitSamplerAssignment[relative_textureUnit]; + if (stageSamplerIndex != LATTE_DECOMPILER_SAMPLER_NONE) + { + uint32 samplerIndex = stageSamplerIndex + LatteDecompiler_getTextureSamplerBaseIndex(shader->shaderType); + const _LatteRegisterSetSampler* samplerWords = LatteGPUState.contextNew.SQ_TEX_SAMPLER + samplerIndex; + + // lod + uint32 iMinLOD = samplerWords->WORD1.get_MIN_LOD(); + uint32 iMaxLOD = samplerWords->WORD1.get_MAX_LOD(); + sint32 iLodBias = samplerWords->WORD1.get_LOD_BIAS(); + + // apply relative lod bias from graphic pack + if (baseTexture->overwriteInfo.hasRelativeLodBias) + iLodBias += baseTexture->overwriteInfo.relativeLodBias; + // apply absolute lod bias from graphic pack + if (baseTexture->overwriteInfo.hasLodBias) + iLodBias = baseTexture->overwriteInfo.lodBias; + + auto filterMip = samplerWords->WORD0.get_MIP_FILTER(); + if (filterMip == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_Z_FILTER::NONE) + { + samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; + samplerInfo.minLod = 0; + samplerInfo.maxLod = 0.25f; + } + else if (filterMip == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_Z_FILTER::POINT) + { + samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; + samplerInfo.minLod = (float)iMinLOD / 64.0f; + samplerInfo.maxLod = (float)iMaxLOD / 64.0f; + } + else if (filterMip == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_Z_FILTER::LINEAR) + { + samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + samplerInfo.minLod = (float)iMinLOD / 64.0f; + samplerInfo.maxLod = (float)iMaxLOD / 64.0f; + } + else + { + // fallback for invalid constants + samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + samplerInfo.minLod = (float)iMinLOD / 64.0f; + samplerInfo.maxLod = (float)iMaxLOD / 64.0f; + } + + auto filterMin = samplerWords->WORD0.get_XY_MIN_FILTER(); + cemu_assert_debug(filterMin != Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER::BICUBIC); // todo + samplerInfo.minFilter = (filterMin == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER::POINT || filterMin == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER::ANISO_POINT) ? VK_FILTER_NEAREST : VK_FILTER_LINEAR; + + auto filterMag = samplerWords->WORD0.get_XY_MAG_FILTER(); + samplerInfo.magFilter = (filterMag == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER::POINT || filterMin == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER::ANISO_POINT) ? VK_FILTER_NEAREST : VK_FILTER_LINEAR; + + auto filterZ = samplerWords->WORD0.get_Z_FILTER(); + // todo: z-filter for texture array samplers is customizable for GPU7 but OpenGL/Vulkan doesn't expose this functionality? + + static const VkSamplerAddressMode s_vkClampTable[] = { + VK_SAMPLER_ADDRESS_MODE_REPEAT, // WRAP + VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT, // MIRROR + VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, // CLAMP_LAST_TEXEL + VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE, // MIRROR_ONCE_LAST_TEXEL + VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, // unsupported HALF_BORDER + VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, // unsupported MIRROR_ONCE_HALF_BORDER + VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, // CLAMP_BORDER + VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER // MIRROR_ONCE_BORDER + }; + + auto clampX = samplerWords->WORD0.get_CLAMP_X(); + auto clampY = samplerWords->WORD0.get_CLAMP_Y(); + auto clampZ = samplerWords->WORD0.get_CLAMP_Z(); + + samplerInfo.addressModeU = s_vkClampTable[(size_t)clampX]; + samplerInfo.addressModeV = s_vkClampTable[(size_t)clampY]; + samplerInfo.addressModeW = s_vkClampTable[(size_t)clampZ]; + + auto maxAniso = samplerWords->WORD0.get_MAX_ANISO_RATIO(); + + if (baseTexture->overwriteInfo.anisotropicLevel >= 0) + maxAniso = baseTexture->overwriteInfo.anisotropicLevel; + + if (maxAniso > 0) + { + samplerInfo.anisotropyEnable = VK_TRUE; + samplerInfo.maxAnisotropy = (float)(1 << maxAniso); + } + else + { + samplerInfo.anisotropyEnable = VK_FALSE; + samplerInfo.maxAnisotropy = 1.0f; + } + + samplerInfo.mipLodBias = (float)iLodBias / 64.0f; + + // depth compare + uint8 depthCompareMode = shader->textureUsesDepthCompare[relative_textureUnit] ? 1 : 0; + static const VkCompareOp s_vkCompareOps[] + { + VK_COMPARE_OP_NEVER, + VK_COMPARE_OP_LESS, + VK_COMPARE_OP_EQUAL, + VK_COMPARE_OP_LESS_OR_EQUAL, + VK_COMPARE_OP_GREATER, + VK_COMPARE_OP_NOT_EQUAL, + VK_COMPARE_OP_GREATER_OR_EQUAL, + VK_COMPARE_OP_ALWAYS, + }; + if (depthCompareMode == 1) + { + samplerInfo.compareEnable = VK_TRUE; + samplerInfo.compareOp = s_vkCompareOps[(size_t)samplerWords->WORD0.get_DEPTH_COMPARE_FUNCTION()]; + } + else + { + samplerInfo.compareEnable = VK_FALSE; + } + + // border + auto borderType = samplerWords->WORD0.get_BORDER_COLOR_TYPE(); + + if (borderType == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_BORDER_COLOR_TYPE::TRANSPARENT_BLACK) + samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK; + else if (borderType == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_BORDER_COLOR_TYPE::OPAQUE_BLACK) + samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK; + else if (borderType == Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0::E_BORDER_COLOR_TYPE::OPAQUE_WHITE) + samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; + else + { + if (this->m_featureControl.deviceExtensions.custom_border_color_without_format) + { + samplerCustomBorderColor.sType = VK_STRUCTURE_TYPE_SAMPLER_CUSTOM_BORDER_COLOR_CREATE_INFO_EXT; + samplerCustomBorderColor.format = VK_FORMAT_UNDEFINED; + + _LatteRegisterSetSamplerBorderColor* borderColorReg; + if (shader->shaderType == LatteConst::ShaderType::Vertex) + borderColorReg = LatteGPUState.contextNew.TD_VS_SAMPLER_BORDER_COLOR + stageSamplerIndex; + else if (shader->shaderType == LatteConst::ShaderType::Pixel) + borderColorReg = LatteGPUState.contextNew.TD_PS_SAMPLER_BORDER_COLOR + stageSamplerIndex; + else // geometry + borderColorReg = LatteGPUState.contextNew.TD_GS_SAMPLER_BORDER_COLOR + stageSamplerIndex; + samplerCustomBorderColor.customBorderColor.float32[0] = borderColorReg->red.get_channelValue(); + samplerCustomBorderColor.customBorderColor.float32[1] = borderColorReg->green.get_channelValue(); + samplerCustomBorderColor.customBorderColor.float32[2] = borderColorReg->blue.get_channelValue(); + samplerCustomBorderColor.customBorderColor.float32[3] = borderColorReg->alpha.get_channelValue(); + + samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_CUSTOM_EXT; + samplerInfo.pNext = &samplerCustomBorderColor; + } + else + { + // default to transparent black if custom border color is not supported + samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK; + } + } + } + + if (vkCreateSampler(m_logicalDevice, &samplerInfo, nullptr, &sampler) != VK_SUCCESS) + UnrecoverableError("Failed to create texture sampler"); + info.sampler = sampler; + textureArray.emplace_back(info); + } + + if (textureCount > 0) + { + for (sint32 i = 0; i < textureCount; i++) + { + VkWriteDescriptorSet write_descriptor{}; + write_descriptor.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write_descriptor.dstSet = result; + write_descriptor.dstBinding = shader->resourceMapping.getTextureBaseBindingPoint() + i; + write_descriptor.dstArrayElement = 0; + write_descriptor.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + write_descriptor.descriptorCount = 1; + write_descriptor.pImageInfo = textureArray.data() + i; + descriptorWrites.emplace_back(write_descriptor); + performanceMonitor.vk.numDescriptorSamplerTextures.increment(); + dsInfo->statsNumSamplerTextures++; + } + } + + // descriptor for uniform vars (as buffer) + + VkDescriptorBufferInfo uniformVarsBufferInfo{}; + + if (shader->resourceMapping.uniformVarsBufferBindingPoint >= 0) + { + uniformVarsBufferInfo.buffer = m_uniformVarBuffer; + uniformVarsBufferInfo.offset = 0; // fixed offset is always zero since we only use dynamic offsets + uniformVarsBufferInfo.range = shader->uniform.uniformRangeSize; + + VkWriteDescriptorSet write_descriptor{}; + write_descriptor.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write_descriptor.dstSet = result; + write_descriptor.dstBinding = shader->resourceMapping.uniformVarsBufferBindingPoint; + write_descriptor.dstArrayElement = 0; + write_descriptor.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; + write_descriptor.descriptorCount = 1; + write_descriptor.pBufferInfo = &uniformVarsBufferInfo; + descriptorWrites.emplace_back(write_descriptor); + performanceMonitor.vk.numDescriptorDynUniformBuffers.increment(); + dsInfo->statsNumDynUniformBuffers++; + } + + // descriptor for uniform buffers + + VkDescriptorBufferInfo uniformBufferInfo{}; + uniformBufferInfo.buffer = m_useHostMemoryForCache ? m_importedMem : m_bufferCache; + uniformBufferInfo.offset = 0; // fixed offset is always zero since we only use dynamic offsets + + if (m_vendor == GfxVendor::AMD) + { + // on AMD we enable robust buffer access and map the remaining range of the buffer + uniformBufferInfo.range = VK_WHOLE_SIZE; + } + else + { + // on other vendors (which may not allow large range values) we disable robust buffer access and use a fixed size + // update: starting with their Vulkan 1.2 drivers Nvidia now also prevents out-of-bounds access. Unlike on AMD, we can't use VK_WHOLE_SIZE due to 64KB size limit of uniforms + // as a workaround we set the size to the allowed maximum. A proper solution would be to use SSBOs for large uniforms / uniforms with unknown size? + uniformBufferInfo.range = 1024 * 16 * 4; // XCX + } + + for (sint32 i = 0; i < LATTE_NUM_MAX_UNIFORM_BUFFERS; i++) + { + if (shader->resourceMapping.uniformBuffersBindingPoint[i] >= 0) + { + VkWriteDescriptorSet write_descriptor{}; + write_descriptor.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write_descriptor.dstSet = result; + write_descriptor.dstBinding = shader->resourceMapping.uniformBuffersBindingPoint[i]; + write_descriptor.dstArrayElement = 0; + write_descriptor.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; + write_descriptor.descriptorCount = 1; + write_descriptor.pBufferInfo = &uniformBufferInfo; + descriptorWrites.emplace_back(write_descriptor); + performanceMonitor.vk.numDescriptorDynUniformBuffers.increment(); + dsInfo->statsNumDynUniformBuffers++; + } + } + + + VkDescriptorBufferInfo tfStorageBufferInfo{}; + + if (shader->resourceMapping.tfStorageBindingPoint >= 0) + { + tfStorageBufferInfo.buffer = m_xfbRingBuffer; + tfStorageBufferInfo.offset = 0; // offset is calculated in shader + tfStorageBufferInfo.range = VK_WHOLE_SIZE; + + VkWriteDescriptorSet write_descriptor{}; + write_descriptor.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write_descriptor.dstSet = result; + write_descriptor.dstBinding = shader->resourceMapping.tfStorageBindingPoint; + write_descriptor.dstArrayElement = 0; + write_descriptor.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + write_descriptor.descriptorCount = 1; + write_descriptor.pBufferInfo = &tfStorageBufferInfo; + descriptorWrites.emplace_back(write_descriptor); + performanceMonitor.vk.numDescriptorStorageBuffers.increment(); + dsInfo->statsNumStorageBuffers++; + } + + if (!descriptorWrites.empty()) + vkUpdateDescriptorSets(m_logicalDevice, (uint32)descriptorWrites.size(), descriptorWrites.data(), 0, nullptr); + + switch (shader->shaderType) + { + case LatteConst::ShaderType::Vertex: + { + pipeline_info->vertex_ds_cache[stateHash] = dsInfo; + break; + } + case LatteConst::ShaderType::Pixel: + { + pipeline_info->pixel_ds_cache[stateHash] = dsInfo; + break; + } + case LatteConst::ShaderType::Geometry: + { + pipeline_info->geometry_ds_cache[stateHash] = dsInfo; + break; + } + default: + __assume(false); + } + + return dsInfo; +} + +void VulkanRenderer::sync_inputTexturesChanged() +{ + bool writeFlushRequired = false; + + if (m_state.activeVertexDS) + { + for (auto& tex : m_state.activeVertexDS->list_fboCandidates) + { + tex->m_vkFlushIndex_read = m_state.currentFlushIndex; + if (tex->m_vkFlushIndex_write == m_state.currentFlushIndex) + writeFlushRequired = true; + } + } + if (m_state.activeGeometryDS) + { + for (auto& tex : m_state.activeGeometryDS->list_fboCandidates) + { + tex->m_vkFlushIndex_read = m_state.currentFlushIndex; + if (tex->m_vkFlushIndex_write == m_state.currentFlushIndex) + writeFlushRequired = true; + } + } + if (m_state.activePixelDS) + { + for (auto& tex : m_state.activePixelDS->list_fboCandidates) + { + tex->m_vkFlushIndex_read = m_state.currentFlushIndex; + if (tex->m_vkFlushIndex_write == m_state.currentFlushIndex) + writeFlushRequired = true; + } + } + // barrier here + if (writeFlushRequired) + { + VkMemoryBarrier memoryBarrier{}; + memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER; + memoryBarrier.srcAccessMask = 0; + memoryBarrier.dstAccessMask = 0; + + VkPipelineStageFlags srcStage = 0; + VkPipelineStageFlags dstStage = 0; + + // src + srcStage |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + memoryBarrier.srcAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + + srcStage |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + memoryBarrier.srcAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + + // dst + dstStage |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + memoryBarrier.dstAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT; + + dstStage |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + memoryBarrier.dstAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT; + + vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStage, dstStage, 0, 1, &memoryBarrier, 0, nullptr, 0, nullptr); + + performanceMonitor.vk.numDrawBarriersPerFrame.increment(); + + m_state.currentFlushIndex++; + } +} + +void VulkanRenderer::sync_RenderPassLoadTextures(CachedFBOVk* fboVk) +{ + bool readFlushRequired = false; + // always called after draw_inputTexturesChanged() + for (auto& tex : fboVk->GetTextures()) + { + LatteTextureVk* texVk = (LatteTextureVk*)tex; + // write-before-write + if (texVk->m_vkFlushIndex_write == m_state.currentFlushIndex) + readFlushRequired = true; + + + texVk->m_vkFlushIndex_write = m_state.currentFlushIndex; + // todo - also check for write-before-write ? + if (texVk->m_vkFlushIndex_read == m_state.currentFlushIndex) + readFlushRequired = true; + } + // barrier here + if (readFlushRequired) + { + VkMemoryBarrier memoryBarrier{}; + memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER; + memoryBarrier.srcAccessMask = 0; + memoryBarrier.dstAccessMask = 0; + + VkPipelineStageFlags srcStage = 0; + VkPipelineStageFlags dstStage = 0; + + // src + srcStage |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + memoryBarrier.srcAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + + srcStage |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + memoryBarrier.srcAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + + // dst + dstStage |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + memoryBarrier.dstAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT; + + dstStage |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + memoryBarrier.dstAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT; + + vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStage, dstStage, 0, 1, &memoryBarrier, 0, nullptr, 0, nullptr); + + performanceMonitor.vk.numDrawBarriersPerFrame.increment(); + + m_state.currentFlushIndex++; + } +} + +void VulkanRenderer::sync_RenderPassStoreTextures(CachedFBOVk* fboVk) +{ + uint32 flushIndex = m_state.currentFlushIndex; + for (auto& tex : fboVk->GetTextures()) + { + LatteTextureVk* texVk = (LatteTextureVk*)tex; + texVk->m_vkFlushIndex_write = flushIndex; + } +} + +void VulkanRenderer::draw_prepareDescriptorSets(PipelineInfo* pipeline_info, VkDescriptorSetInfo*& vertexDS, VkDescriptorSetInfo*& pixelDS, VkDescriptorSetInfo*& geometryDS) +{ + const auto vertexShader = LatteSHRC_GetActiveVertexShader(); + const auto geometryShader = LatteSHRC_GetActiveGeometryShader(); + const auto pixelShader = LatteSHRC_GetActivePixelShader(); + + + if (vertexShader) + { + auto descriptorSetInfo = draw_getOrCreateDescriptorSet(pipeline_info, vertexShader); + descriptorSetInfo->m_vkObjDescriptorSet->flagForCurrentCommandBuffer(); + vertexDS = descriptorSetInfo; + } + + if (pixelShader) + { + auto descriptorSetInfo = draw_getOrCreateDescriptorSet(pipeline_info, pixelShader); + descriptorSetInfo->m_vkObjDescriptorSet->flagForCurrentCommandBuffer(); + pixelDS = descriptorSetInfo; + + } + + if (geometryShader) + { + auto descriptorSetInfo = draw_getOrCreateDescriptorSet(pipeline_info, geometryShader); + descriptorSetInfo->m_vkObjDescriptorSet->flagForCurrentCommandBuffer(); + geometryDS = descriptorSetInfo; + } +} + +void VulkanRenderer::draw_updateVkBlendConstants() +{ + uint32* blendColorConstant = LatteGPUState.contextRegister + Latte::REGADDR::CB_BLEND_RED; + vkCmdSetBlendConstants(m_state.currentCommandBuffer, (const float*)blendColorConstant); +} + +void VulkanRenderer::draw_updateDepthBias(bool forceUpdate) +{ + uint32 frontScaleU32 = LatteGPUState.contextNew.PA_SU_POLY_OFFSET_FRONT_SCALE.getRawValue(); + uint32 frontOffsetU32 = LatteGPUState.contextNew.PA_SU_POLY_OFFSET_FRONT_OFFSET.getRawValue(); + uint32 offsetClampU32 = LatteGPUState.contextNew.PA_SU_POLY_OFFSET_CLAMP.getRawValue(); + + if (forceUpdate == false && + m_state.prevPolygonFrontScaleU32 == frontScaleU32 && + m_state.prevPolygonFrontOffsetU32 == frontOffsetU32 && + m_state.prevPolygonFrontClampU32 == offsetClampU32) + return; + + m_state.prevPolygonFrontScaleU32 = frontScaleU32; + m_state.prevPolygonFrontOffsetU32 = frontOffsetU32; + m_state.prevPolygonFrontClampU32 = offsetClampU32; + + float frontScale = LatteGPUState.contextNew.PA_SU_POLY_OFFSET_FRONT_SCALE.get_SCALE(); + float frontOffset = LatteGPUState.contextNew.PA_SU_POLY_OFFSET_FRONT_OFFSET.get_OFFSET(); + float offsetClamp = LatteGPUState.contextNew.PA_SU_POLY_OFFSET_CLAMP.get_CLAMP(); + + frontScale /= 16.0f; + + vkCmdSetDepthBias(m_state.currentCommandBuffer, frontOffset, offsetClamp, frontScale); +} + +bool s_syncOnNextDraw = false; + +void VulkanRenderer::draw_setRenderPass() +{ + CachedFBOVk* fboVk = m_state.activeFBO; + + // update self-dependency flag + if (m_state.descriptorSetsChanged || m_state.activeRenderpassFBO != fboVk) + { + bool hadDep = m_state.hasRenderSelfDependency; + m_state.hasRenderSelfDependency = fboVk->CheckForCollision(m_state.activeVertexDS, m_state.activeGeometryDS, m_state.activePixelDS); + } + + auto vkObjRenderPass = fboVk->GetRenderPassObj(); + auto vkObjFramebuffer = fboVk->GetFramebufferObj(); + + if (m_state.hasRenderSelfDependency) + { + bool triggerBarrier = GetConfig().vk_accurate_barriers || m_state.activePipelineInfo->neverSkipAccurateBarrier; + if (triggerBarrier) + { + VkMemoryBarrier memoryBarrier{}; + memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER; + memoryBarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + memoryBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + VkPipelineStageFlags srcStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + VkPipelineStageFlags dstStage = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStage, dstStage, 0, 1, &memoryBarrier, 0, nullptr, 0, nullptr); + performanceMonitor.vk.numDrawBarriersPerFrame.increment(); + } + } + + if (m_state.activeRenderpassFBO == fboVk) + { + if (m_state.descriptorSetsChanged) + sync_inputTexturesChanged(); + return; + } + draw_endRenderPass(); + if (m_state.descriptorSetsChanged) + sync_inputTexturesChanged(); + + // assume that FBO changed, update self-dependency state + m_state.hasRenderSelfDependency = fboVk->CheckForCollision(m_state.activeVertexDS, m_state.activeGeometryDS, m_state.activePixelDS); + + sync_RenderPassLoadTextures(fboVk); + + if (m_featureControl.deviceExtensions.dynamic_rendering) + { + vkCmdBeginRenderingKHR(m_state.currentCommandBuffer, fboVk->GetRenderingInfo()); + } + else + { + VkRenderPass renderPass = vkObjRenderPass->m_renderPass; + VkFramebuffer framebuffer = vkObjFramebuffer->m_frameBuffer; + VkExtent2D extend = fboVk->GetExtend(); + + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = renderPass; + renderPassInfo.framebuffer = framebuffer; + renderPassInfo.renderArea.offset = { 0, 0 }; + renderPassInfo.renderArea.extent = extend; + renderPassInfo.clearValueCount = 0; + vkCmdBeginRenderPass(m_state.currentCommandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + } + + m_state.activeRenderpassFBO = fboVk; + + vkObjRenderPass->flagForCurrentCommandBuffer(); + vkObjFramebuffer->flagForCurrentCommandBuffer(); + + performanceMonitor.vk.numBeginRenderpassPerFrame.increment(); +} + +void VulkanRenderer::draw_endRenderPass() +{ + if (!m_state.activeRenderpassFBO) + return; + if (m_featureControl.deviceExtensions.dynamic_rendering) + vkCmdEndRenderingKHR(m_state.currentCommandBuffer); + else + vkCmdEndRenderPass(m_state.currentCommandBuffer); + sync_RenderPassStoreTextures(m_state.activeRenderpassFBO); + m_state.activeRenderpassFBO = nullptr; +} + +void LatteDraw_handleSpecialState8_clearAsDepth(); + +// transfer depth buffer data to color buffer +void VulkanRenderer::draw_handleSpecialState5() +{ + LatteMRT::UpdateCurrentFBO(); + LatteRenderTarget_updateViewport(); + + LatteTextureView* colorBuffer = LatteMRT::GetColorAttachment(0); + LatteTextureView* depthBuffer = LatteMRT::GetDepthAttachment(); + + sint32 vpWidth, vpHeight; + LatteMRT::GetVirtualViewportDimensions(vpWidth, vpHeight); + + surfaceCopy_copySurfaceWithFormatConversion( + depthBuffer->baseTexture, depthBuffer->firstMip, depthBuffer->firstSlice, + colorBuffer->baseTexture, colorBuffer->firstMip, colorBuffer->firstSlice, + vpWidth, vpHeight); +} + +void VulkanRenderer::draw_beginSequence() +{ + m_state.drawSequenceSkip = false; + + bool streamoutEnable = LatteGPUState.contextRegister[mmVGT_STRMOUT_EN] != 0; + + // update shader state + LatteSHRC_UpdateActiveShaders(); + if (m_state.drawSequenceSkip) + { + debug_printf("Skipping drawcalls due to shader error\n"); + m_state.drawSequenceSkip = true; + cemu_assert_debug(false); + return; + } + + // update render target and texture state + LatteGPUState.requiresTextureBarrier = false; + while (true) + { + LatteGPUState.repeatTextureInitialization = false; + if (!LatteMRT::UpdateCurrentFBO()) + { + debug_printf("Rendertarget invalid\n"); + m_state.drawSequenceSkip = true; + return; // no render target + } + + if (!hasValidFramebufferAttached && !streamoutEnable) + { + debug_printf("Drawcall with no color buffer or depth buffer attached\n"); + m_state.drawSequenceSkip = true; + return; // no render target + } + LatteTexture_updateTextures(); + if (!LatteGPUState.repeatTextureInitialization) + break; + } + + // apply render target + LatteMRT::ApplyCurrentState(); + + // viewport and scissor box + LatteRenderTarget_updateViewport(); + LatteRenderTarget_updateScissorBox(); + + // check for conditions which would turn the drawcalls into no-ops + bool rasterizerEnable = LatteGPUState.contextNew.PA_CL_CLIP_CNTL.get_DX_RASTERIZATION_KILL() == false; + + // GX2SetSpecialState(0, true) enables DX_RASTERIZATION_KILL, but still expects depth writes to happen? -> Research which stages are disabled by DX_RASTERIZATION_KILL exactly + // for now we use a workaround: + if (!LatteGPUState.contextNew.PA_CL_VTE_CNTL.get_VPORT_X_OFFSET_ENA()) + rasterizerEnable = true; + + if (rasterizerEnable == false && streamoutEnable == false) + m_state.drawSequenceSkip = true; +} + +void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, bool isFirst) +{ + if (m_state.drawSequenceSkip) + { + LatteGPUState.drawCallCounter++; + return; + } + + // fast clear color as depth + if (LatteGPUState.contextNew.GetSpecialStateValues()[8] != 0) + { + LatteDraw_handleSpecialState8_clearAsDepth(); + LatteGPUState.drawCallCounter++; + return; + } + else if (LatteGPUState.contextNew.GetSpecialStateValues()[5] != 0) + { + draw_handleSpecialState5(); + LatteGPUState.drawCallCounter++; + return; + } + + // process index data + const LattePrimitiveMode primitiveMode = static_cast<LattePrimitiveMode>(LatteGPUState.contextRegister[mmVGT_PRIMITIVE_TYPE]); + + Renderer::INDEX_TYPE hostIndexType; + uint32 hostIndexCount; + uint32 indexMin = 0; + uint32 indexMax = 0; + uint32 indexBufferOffset = 0; + uint32 indexBufferIndex = 0; + LatteIndices_decode(memory_getPointerFromVirtualOffset(indexDataMPTR), indexType, count, primitiveMode, indexMin, indexMax, hostIndexType, hostIndexCount, indexBufferOffset, indexBufferIndex); + + // update index binding + bool isPrevIndexData = false; + if (hostIndexType != INDEX_TYPE::NONE) + { + if (m_state.activeIndexBufferOffset != indexBufferOffset || m_state.activeIndexBufferIndex != indexBufferIndex || m_state.activeIndexType != hostIndexType) + { + m_state.activeIndexType = hostIndexType; + m_state.activeIndexBufferOffset = indexBufferOffset; + m_state.activeIndexBufferIndex = indexBufferIndex; + VkIndexType vkType; + if (hostIndexType == INDEX_TYPE::U16) + vkType = VK_INDEX_TYPE_UINT16; + else if (hostIndexType == INDEX_TYPE::U32) + vkType = VK_INDEX_TYPE_UINT32; + else + cemu_assert(false); + vkCmdBindIndexBuffer(m_state.currentCommandBuffer, memoryManager->getIndexAllocator().GetBufferByIndex(indexBufferIndex), indexBufferOffset, vkType); + } + else + isPrevIndexData = true; + } + + if (m_useHostMemoryForCache) + { + // direct memory access (Wii U memory space imported as a Vulkan buffer), update buffer bindings + draw_updateVertexBuffersDirectAccess(); + LatteDecompilerShader* vertexShader = LatteSHRC_GetActiveVertexShader(); + if (vertexShader) + draw_updateUniformBuffersDirectAccess(vertexShader, mmSQ_VTX_UNIFORM_BLOCK_START, LatteConst::ShaderType::Vertex); + LatteDecompilerShader* geometryShader = LatteSHRC_GetActiveGeometryShader(); + if (geometryShader) + draw_updateUniformBuffersDirectAccess(geometryShader, mmSQ_GS_UNIFORM_BLOCK_START, LatteConst::ShaderType::Geometry); + LatteDecompilerShader* pixelShader = LatteSHRC_GetActivePixelShader(); + if (pixelShader) + draw_updateUniformBuffersDirectAccess(pixelShader, mmSQ_PS_UNIFORM_BLOCK_START, LatteConst::ShaderType::Pixel); + } + else + { + // synchronize vertex and uniform cache and update buffer bindings + LatteBufferCache_Sync(indexMin + baseVertex, indexMax + baseVertex, baseInstance, instanceCount); + } + + // prepare streamout + m_streamoutState.verticesPerInstance = count; + LatteStreamout_PrepareDrawcall(count, instanceCount); + + // update uniform vars + LatteDecompilerShader* vertexShader = LatteSHRC_GetActiveVertexShader(); + LatteDecompilerShader* pixelShader = LatteSHRC_GetActivePixelShader(); + LatteDecompilerShader* geometryShader = LatteSHRC_GetActiveGeometryShader(); + + if (vertexShader) + uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX, vertexShader); + if (pixelShader) + uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT, pixelShader); + if (geometryShader) + uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY, geometryShader); + + PipelineInfo* pipeline_info; + + if (!isFirst) + { + if (m_state.activePipelineInfo->minimalStateHash != draw_calculateMinimalGraphicsPipelineHash(vertexShader->compatibleFetchShader, LatteGPUState.contextNew)) + { + // pipeline changed + pipeline_info = draw_getOrCreateGraphicsPipeline(count); + m_state.activePipelineInfo = pipeline_info; + } + else + { + pipeline_info = m_state.activePipelineInfo; +#ifndef PUBLIC_RELEASE + auto pipeline_info2 = draw_getOrCreateGraphicsPipeline(count); + if (pipeline_info != pipeline_info2) + { + cemu_assert_debug(false); + } +#endif + } + } + else + { + pipeline_info = draw_getOrCreateGraphicsPipeline(count); + m_state.activePipelineInfo = pipeline_info; + } + + auto vkObjPipeline = pipeline_info->m_vkrObjPipeline; + + if (vkObjPipeline->pipeline == VK_NULL_HANDLE) + { + // invalid/uninitialized pipeline + m_state.activeVertexDS = nullptr; + return; + } + + + VkDescriptorSetInfo *vertexDS = nullptr, *pixelDS = nullptr, *geometryDS = nullptr; + if (!isFirst && m_state.activeVertexDS) + { + vertexDS = m_state.activeVertexDS; + pixelDS = m_state.activePixelDS; + geometryDS = m_state.activeGeometryDS; + m_state.descriptorSetsChanged = false; + } + else + { + draw_prepareDescriptorSets(pipeline_info, vertexDS, pixelDS, geometryDS); + m_state.activeVertexDS = vertexDS; + m_state.activePixelDS = pixelDS; + m_state.activeGeometryDS = geometryDS; + m_state.descriptorSetsChanged = true; + } + + draw_setRenderPass(); + + if (m_state.currentPipeline != vkObjPipeline->pipeline) + { + vkCmdBindPipeline(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, vkObjPipeline->pipeline); + vkObjPipeline->flagForCurrentCommandBuffer(); + m_state.currentPipeline = vkObjPipeline->pipeline; + // depth bias + if (pipeline_info->usesDepthBias) + draw_updateDepthBias(true); + } + else + { + if (pipeline_info->usesDepthBias) + draw_updateDepthBias(false); + } + + // update blend constants + if (pipeline_info->usesBlendConstants) + draw_updateVkBlendConstants(); + + // update descriptor sets + uint32_t dynamicOffsets[17 * 2]; + if (vertexDS && pixelDS) + { + // update vertex and pixel descriptor set in a single call to vkCmdBindDescriptorSets + sint32 numDynOffsetsVS; + sint32 numDynOffsetsPS; + draw_prepareDynamicOffsetsForDescriptorSet(VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX, dynamicOffsets, numDynOffsetsVS, + pipeline_info); + draw_prepareDynamicOffsetsForDescriptorSet(VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT, dynamicOffsets + numDynOffsetsVS, numDynOffsetsPS, + pipeline_info); + + VkDescriptorSet dsArray[2]; + dsArray[0] = vertexDS->m_vkObjDescriptorSet->descriptorSet; + dsArray[1] = pixelDS->m_vkObjDescriptorSet->descriptorSet; + + vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, + vkObjPipeline->pipeline_layout, 0, 2, dsArray, numDynOffsetsVS + numDynOffsetsPS, + dynamicOffsets); + } + else if (vertexDS) + { + sint32 numDynOffsets; + draw_prepareDynamicOffsetsForDescriptorSet(VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX, dynamicOffsets, numDynOffsets, + pipeline_info); + vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, + vkObjPipeline->pipeline_layout, 0, 1, &vertexDS->m_vkObjDescriptorSet->descriptorSet, numDynOffsets, + dynamicOffsets); + } + else if (pixelDS) + { + sint32 numDynOffsets; + draw_prepareDynamicOffsetsForDescriptorSet(VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT, dynamicOffsets, numDynOffsets, + pipeline_info); + vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, + vkObjPipeline->pipeline_layout, 1, 1, &pixelDS->m_vkObjDescriptorSet->descriptorSet, numDynOffsets, + dynamicOffsets); + } + if (geometryDS) + { + sint32 numDynOffsets; + draw_prepareDynamicOffsetsForDescriptorSet(VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY, dynamicOffsets, numDynOffsets, + pipeline_info); + vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, + vkObjPipeline->pipeline_layout, 2, 1, &geometryDS->m_vkObjDescriptorSet->descriptorSet, numDynOffsets, + dynamicOffsets); + } + + // draw + if (hostIndexType != INDEX_TYPE::NONE) + vkCmdDrawIndexed(m_state.currentCommandBuffer, hostIndexCount, instanceCount, 0, baseVertex, baseInstance); + else + vkCmdDraw(m_state.currentCommandBuffer, count, instanceCount, baseVertex, baseInstance); + + LatteStreamout_FinishDrawcall(m_useHostMemoryForCache); + + LatteGPUState.drawCallCounter++; +} + +// used in place of vertex/uniform caching when direct memory access is possible +void VulkanRenderer::draw_updateVertexBuffersDirectAccess() +{ + LatteFetchShader* parsedFetchShader = LatteSHRC_GetActiveFetchShader(); + if (!parsedFetchShader) + return; + for (auto& bufferGroup : parsedFetchShader->bufferGroups) + { + uint32 bufferIndex = bufferGroup.attributeBufferIndex; + uint32 bufferBaseRegisterIndex = mmSQ_VTX_ATTRIBUTE_BLOCK_START + bufferIndex * 7; + MPTR bufferAddress = LatteGPUState.contextRegister[bufferBaseRegisterIndex + 0]; + uint32 bufferSize = LatteGPUState.contextRegister[bufferBaseRegisterIndex + 1] + 1; + uint32 bufferStride = (LatteGPUState.contextRegister[bufferBaseRegisterIndex + 2] >> 11) & 0xFFFF; + + if (bufferAddress == MPTR_NULL) + { + cemu_assert_unimplemented(); + } + if (m_state.currentVertexBinding[bufferIndex].offset == bufferAddress) + continue; + cemu_assert_debug(bufferAddress < 0x50000000); + VkBuffer attrBuffer = m_importedMem; + VkDeviceSize attrOffset = bufferAddress - m_importedMemBaseAddress; + vkCmdBindVertexBuffers(m_state.currentCommandBuffer, bufferIndex, 1, &attrBuffer, &attrOffset); + } +} + +void VulkanRenderer::draw_updateUniformBuffersDirectAccess(LatteDecompilerShader* shader, const uint32 uniformBufferRegOffset, LatteConst::ShaderType shaderType) +{ + if (shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK) + { + // use full uniform buffers + for (sint32 t = 0; t < shader->uniformBufferListCount; t++) + { + sint32 i = shader->uniformBufferList[t]; + MPTR physicalAddr = LatteGPUState.contextRegister[uniformBufferRegOffset + i * 7 + 0]; + uint32 uniformSize = LatteGPUState.contextRegister[uniformBufferRegOffset + i * 7 + 1] + 1; + + if (physicalAddr == MPTR_NULL) + { + cemu_assert_unimplemented(); + continue; + } + + cemu_assert_debug(physicalAddr < 0x50000000); + + uint32 bufferIndex = i; + cemu_assert_debug(bufferIndex < 16); + + switch (shaderType) + { + case LatteConst::ShaderType::Vertex: + dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX].unformBufferOffset[bufferIndex] = physicalAddr - m_importedMemBaseAddress; + break; + case LatteConst::ShaderType::Geometry: + dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY].unformBufferOffset[bufferIndex] = physicalAddr - m_importedMemBaseAddress; + break; + case LatteConst::ShaderType::Pixel: + dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT].unformBufferOffset[bufferIndex] = physicalAddr - m_importedMemBaseAddress; + break; + default: + cemu_assert_debug(false); + } + } + } +} + +void VulkanRenderer::draw_endSequence() +{ + LatteDecompilerShader* pixelShader = LatteSHRC_GetActivePixelShader(); + // post-drawcall logic + if (pixelShader) + LatteRenderTarget_trackUpdates(); + bool hasReadback = LatteTextureReadback_Update(); + m_recordedDrawcalls++; + if (m_recordedDrawcalls >= m_submitThreshold || hasReadback) + { + SubmitCommandBuffer(); + } +} + +void VulkanRenderer::debug_genericBarrier() +{ + draw_endRenderPass(); + + VkMemoryBarrier memoryBarrier{}; + memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER; + memoryBarrier.srcAccessMask = VK_ACCESS_INDIRECT_COMMAND_READ_BIT | + VK_ACCESS_INDEX_READ_BIT | + VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | + VK_ACCESS_UNIFORM_READ_BIT | + VK_ACCESS_INPUT_ATTACHMENT_READ_BIT | + VK_ACCESS_SHADER_READ_BIT | + VK_ACCESS_SHADER_WRITE_BIT | + VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | + VK_ACCESS_TRANSFER_READ_BIT | + VK_ACCESS_TRANSFER_WRITE_BIT | + VK_ACCESS_HOST_READ_BIT | + VK_ACCESS_HOST_WRITE_BIT | + VK_ACCESS_MEMORY_READ_BIT | + VK_ACCESS_MEMORY_WRITE_BIT; + + memoryBarrier.dstAccessMask = VK_ACCESS_INDIRECT_COMMAND_READ_BIT | + VK_ACCESS_INDEX_READ_BIT | + VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | + VK_ACCESS_UNIFORM_READ_BIT | + VK_ACCESS_INPUT_ATTACHMENT_READ_BIT | + VK_ACCESS_SHADER_READ_BIT | + VK_ACCESS_SHADER_WRITE_BIT | + VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | + VK_ACCESS_TRANSFER_READ_BIT | + VK_ACCESS_TRANSFER_WRITE_BIT | + VK_ACCESS_HOST_READ_BIT | + VK_ACCESS_HOST_WRITE_BIT | + VK_ACCESS_MEMORY_READ_BIT | + VK_ACCESS_MEMORY_WRITE_BIT; + + vkCmdPipelineBarrier(m_state.currentCommandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1, &memoryBarrier, 0, nullptr, 0, nullptr); +} diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp new file mode 100644 index 00000000..c1751dd5 --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp @@ -0,0 +1,977 @@ +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" + +struct CopyShaderPushConstantData_t +{ + float vertexOffsets[4 * 2]; + sint32 srcTexelOffset[2]; +}; + +struct CopySurfacePipelineInfo +{ + template<typename T> + struct TexSliceMipMapping + { + TexSliceMipMapping(LatteTextureVk* texture) : m_texture(texture) {}; + ~TexSliceMipMapping() + { + //delete vkObjPipeline; + //delete vkObjRenderPass; + for (auto itr : m_array) + { + if (itr != nullptr) + delete itr; + } + } + + T* create(sint32 sliceIndex, sint32 mipIndex) + { + sint32 idx = m_texture->GetSliceMipArrayIndex(sliceIndex, mipIndex); + if (idx >= m_array.size()) + m_array.resize(idx + 1); + T* v = new T(); + m_array[idx] = v; + return v; + } + + T* get(sint32 sliceIndex, sint32 mipIndex) const + { + sint32 idx = m_texture->GetSliceMipArrayIndex(sliceIndex, mipIndex); + if (idx >= m_array.size()) + return nullptr; + return m_array[idx]; + } + + TexSliceMipMapping(const TexSliceMipMapping&) = delete; + TexSliceMipMapping& operator=(const TexSliceMipMapping&) = delete; + TexSliceMipMapping(TexSliceMipMapping&& rhs) + { + m_texture = rhs.m_texture; + m_array = std::move(rhs.m_array); + } + + TexSliceMipMapping& operator=(TexSliceMipMapping&& rhs) + { + m_texture = rhs.m_texture; + m_array = std::move(rhs.m_array); + } + + LatteTextureVk* m_texture; + std::vector<T*> m_array; + }; + + struct FramebufferValue + { + VKRObjectFramebuffer* vkObjFramebuffer; + VKRObjectTextureView* vkObjImageView; + }; + + struct DescriptorValue + { + VKRObjectDescriptorSet* vkObjDescriptorSet; + VKRObjectTextureView* vkObjImageView; + //VKRObjectSampler* vkObjSampler; + }; + + CopySurfacePipelineInfo() = default; + CopySurfacePipelineInfo(VkDevice device) : m_device(device) {} + CopySurfacePipelineInfo(const CopySurfacePipelineInfo& info) = delete; + + VkDevice m_device = nullptr; + + VKRObjectPipeline* vkObjPipeline{}; + VKRObjectRenderPass* vkObjRenderPass{}; + + // map of framebuffers used with this pipeline + std::unordered_map<LatteTextureVk*, TexSliceMipMapping<FramebufferValue>> map_framebuffers; + + // map of descriptor sets used with this pipeline + std::unordered_map<LatteTextureVk*, TexSliceMipMapping<DescriptorValue>> map_descriptors; +}; + +struct VkCopySurfaceState_t +{ + LatteTextureVk* sourceTexture; + sint32 srcMip; + sint32 srcSlice; + LatteTextureVk* destinationTexture; + sint32 dstMip; + sint32 dstSlice; + sint32 width; + sint32 height; +}; + +extern std::atomic_int g_compiling_pipelines; + +uint64 VulkanRenderer::copySurface_getPipelineStateHash(VkCopySurfaceState_t& state) +{ + uint64 h = 0; + + h += (uintptr_t)state.destinationTexture->GetFormat(); + h = _rotr64(h, 7); + + h += state.sourceTexture->isDepth ? 0x1111ull : 0; + h = _rotr64(h, 7); + h += state.destinationTexture->isDepth ? 0x1112ull : 0; + h = _rotr64(h, 7); + + return h; +} + +CopySurfacePipelineInfo* VulkanRenderer::copySurface_getCachedPipeline(VkCopySurfaceState_t& state) +{ + const uint64 stateHash = copySurface_getPipelineStateHash(state); + + const auto it = m_copySurfacePipelineCache.find(stateHash); + if (it == m_copySurfacePipelineCache.cend()) + return nullptr; + return it->second; +} + +RendererShaderVk* _vkGenSurfaceCopyShader_vs() +{ + const char* vsShaderSrc = + "#version 450\r\n" + "layout(location = 0) out ivec2 passSrcTexelOffset;\r\n" + "layout(push_constant) uniform pushConstants {\r\n" + "vec2 vertexOffsets[4];\r\n" + "ivec2 srcTexelOffset;\r\n" + "}uf_pushConstants;\r\n" + "\r\n" + "void main(){\r\n" + //"ivec2 tUV;\r\n" + "vec2 tPOS;\r\n" + "switch(gl_VertexIndex)" + "{\r\n" + // AMD driver has issues with indexed push constant access, therefore use this workaround + "case 0: tPOS = uf_pushConstants.vertexOffsets[0].xy; break;\r\n" + "case 1: tPOS = uf_pushConstants.vertexOffsets[1].xy; break;\r\n" + "case 2: tPOS = uf_pushConstants.vertexOffsets[3].xy; break;\r\n" + "case 3: tPOS = uf_pushConstants.vertexOffsets[0].xy; break;\r\n" + "case 4: tPOS = uf_pushConstants.vertexOffsets[2].xy; break;\r\n" + "case 5: tPOS = uf_pushConstants.vertexOffsets[3].xy; break;\r\n" + "}" + "passSrcTexelOffset = uf_pushConstants.srcTexelOffset;\r\n" + "gl_Position = vec4(tPOS, 0, 1.0);\r\n" + "}\r\n"; + + std::string shaderStr(vsShaderSrc); + auto vkShader = new RendererShaderVk(RendererShader::ShaderType::kVertex, 0, 0, false, false, shaderStr); + vkShader->PreponeCompilation(true); + return vkShader; +} + +RendererShaderVk* _vkGenSurfaceCopyShader_ps_colorToDepth() +{ + const char* psShaderSrc = "" + "#version 450\r\n" + "layout(location = 0) in flat ivec2 passSrcTexelOffset;\r\n" + "layout(binding = 0) uniform sampler2D textureSrc;\r\n" + "in vec4 gl_FragCoord;\r\n" + "\r\n" + "void main(){\r\n" + "gl_FragDepth = texelFetch(textureSrc, passSrcTexelOffset + ivec2(gl_FragCoord.xy), 0).r;\r\n" + "}\r\n"; + + std::string shaderStr(psShaderSrc); + auto vkShader = new RendererShaderVk(RendererShader::ShaderType::kFragment, 0, 0, false, false, shaderStr); + vkShader->PreponeCompilation(true); + return vkShader; +} + +RendererShaderVk* _vkGenSurfaceCopyShader_ps_depthToColor() +{ + const char* psShaderSrc = "" + "#version 450\r\n" + "layout(location = 0) in flat ivec2 passSrcTexelOffset;\r\n" + "layout(binding = 0) uniform sampler2D textureSrc;\r\n" + "layout(location = 0) out vec4 colorOut0;\r\n" + "in vec4 gl_FragCoord;\r\n" + "\r\n" + "void main(){\r\n" + "colorOut0.r = texelFetch(textureSrc, passSrcTexelOffset + ivec2(gl_FragCoord.xy), 0).r;\r\n" + "}\r\n"; + + std::string shaderStr(psShaderSrc); + auto vkShader = new RendererShaderVk(RendererShader::ShaderType::kFragment, 0, 0, false, false, shaderStr); + vkShader->PreponeCompilation(true); + return vkShader; +} + +VKRObjectRenderPass* VulkanRenderer::copySurface_createRenderpass(VkCopySurfaceState_t& state) +{ + VKRObjectRenderPass::AttachmentInfo_t attachmentInfo{}; + + if (state.destinationTexture->isDepth) + { + attachmentInfo.depthAttachment.viewObj = ((LatteTextureViewVk*)state.destinationTexture->baseView)->GetViewRGBA(); + attachmentInfo.depthAttachment.format = state.destinationTexture->GetFormat(); + attachmentInfo.depthAttachment.hasStencil = state.destinationTexture->hasStencil; + } + else + { + attachmentInfo.colorAttachment[0].viewObj = ((LatteTextureViewVk*)state.destinationTexture->baseView)->GetViewRGBA(); + attachmentInfo.colorAttachment[0].format = state.destinationTexture->GetFormat(); + } + + VKRObjectRenderPass* vkObjRenderPass = new VKRObjectRenderPass(attachmentInfo, 1); + + return vkObjRenderPass; +} + +CopySurfacePipelineInfo* VulkanRenderer::copySurface_getOrCreateGraphicsPipeline(VkCopySurfaceState_t& state) +{ + auto cache_object = copySurface_getCachedPipeline(state); + if (cache_object != nullptr) + return cache_object; + + if (defaultShaders.copySurface_vs == nullptr) + { + // on first call generate shaders + defaultShaders.copySurface_vs = _vkGenSurfaceCopyShader_vs(); + defaultShaders.copySurface_psColor2Depth = _vkGenSurfaceCopyShader_ps_colorToDepth(); + defaultShaders.copySurface_psDepth2Color = _vkGenSurfaceCopyShader_ps_depthToColor(); + } + + RendererShaderVk* vertexShader = defaultShaders.copySurface_vs; + RendererShaderVk* pixelShader = nullptr; + if (state.sourceTexture->isDepth && !state.destinationTexture->isDepth) + pixelShader = defaultShaders.copySurface_psDepth2Color; + else if (!state.sourceTexture->isDepth && state.destinationTexture->isDepth) + pixelShader = defaultShaders.copySurface_psColor2Depth; + else + { + cemu_assert(false); + } + + std::vector<VkPipelineShaderStageCreateInfo> shaderStages; + + shaderStages.emplace_back(CreatePipelineShaderStageCreateInfo(VK_SHADER_STAGE_VERTEX_BIT, vertexShader->GetShaderModule(), "main")); + shaderStages.emplace_back(CreatePipelineShaderStageCreateInfo(VK_SHADER_STAGE_FRAGMENT_BIT, pixelShader->GetShaderModule(), "main")); + + // ########################################################################################################################################## + + const uint64 stateHash = copySurface_getPipelineStateHash(state); + + CopySurfacePipelineInfo* copyPipeline = new CopySurfacePipelineInfo(); + + m_copySurfacePipelineCache.try_emplace(stateHash, copyPipeline); + + VKRObjectPipeline* vkObjPipeline = new VKRObjectPipeline(); + + // ########################################################################################################################################## + + VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertexInputInfo.vertexBindingDescriptionCount = 0; + vertexInputInfo.pVertexBindingDescriptions = nullptr; + vertexInputInfo.vertexAttributeDescriptionCount = 0; + vertexInputInfo.pVertexAttributeDescriptions = nullptr; + + // ########################################################################################################################################## + + VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.primitiveRestartEnable = VK_FALSE; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + + // ########################################################################################################################################## + + VkPipelineViewportStateCreateInfo viewportState{}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.scissorCount = 1; + + // ########################################################################################################################################## + + VkPipelineRasterizationStateCreateInfo rasterizer{}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = VK_CULL_MODE_NONE; + rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + + // ########################################################################################################################################## + + VkPipelineMultisampleStateCreateInfo multisampling{}; + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + // ########################################################################################################################################## + + VkPipelineColorBlendStateCreateInfo colorBlending{}; + VkPipelineColorBlendAttachmentState blendAttachment{}; + + if (!state.destinationTexture->isDepth) + { + blendAttachment.blendEnable = VK_FALSE; + blendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT; + colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &blendAttachment; + colorBlending.logicOpEnable = VK_FALSE; + } + + + // ########################################################################################################################################## + + std::vector<VkDescriptorSetLayoutBinding> descriptorSetLayoutBindings; + + VkDescriptorSetLayoutBinding entry{}; + entry.binding = 0; + entry.descriptorCount = 1; + entry.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + entry.pImmutableSamplers = nullptr; + entry.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + descriptorSetLayoutBindings.emplace_back(entry); + + VkDescriptorSetLayoutCreateInfo layoutInfo = {}; + layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layoutInfo.bindingCount = (uint32_t)descriptorSetLayoutBindings.size(); + layoutInfo.pBindings = descriptorSetLayoutBindings.data(); + + if (vkCreateDescriptorSetLayout(m_logicalDevice, &layoutInfo, nullptr, &vkObjPipeline->pixelDSL) != VK_SUCCESS) + UnrecoverableError(fmt::format("Failed to create descriptor set layout for surface copy shader").c_str()); + + // ########################################################################################################################################## + + VkPushConstantRange pushConstantRange{}; + pushConstantRange.offset = 0; + pushConstantRange.size = sizeof(CopyShaderPushConstantData_t); + pushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = 1; + pipelineLayoutInfo.pSetLayouts = &vkObjPipeline->pixelDSL; + pipelineLayoutInfo.pPushConstantRanges = &pushConstantRange; + pipelineLayoutInfo.pushConstantRangeCount = 1; + + VkResult result = vkCreatePipelineLayout(m_logicalDevice, &pipelineLayoutInfo, nullptr, &vkObjPipeline->pipeline_layout); + if (result != VK_SUCCESS) + { + forceLog_printf("%s", fmt::format("Failed to create pipeline layout: {}", result).c_str()); + vkObjPipeline->pipeline = VK_NULL_HANDLE; + return copyPipeline; + } + + // ################################################### + + bool writeDepth = state.destinationTexture->isDepth; + + VkPipelineDepthStencilStateCreateInfo depthStencilState{}; + depthStencilState.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + depthStencilState.depthTestEnable = writeDepth ? VK_TRUE : VK_FALSE; + depthStencilState.depthWriteEnable = writeDepth ? VK_TRUE : VK_FALSE; + depthStencilState.depthCompareOp = VK_COMPARE_OP_ALWAYS; + + depthStencilState.depthBoundsTestEnable = false; + depthStencilState.minDepthBounds = 0.0f; + depthStencilState.maxDepthBounds = 1.0f; + + depthStencilState.stencilTestEnable = VK_FALSE; + + // ########################################################################################################################################## + + std::vector<VkDynamicState> dynamicStates = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; + + VkPipelineDynamicStateCreateInfo dynamicState = {}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = (uint32_t)dynamicStates.size(); + dynamicState.pDynamicStates = dynamicStates.data(); + + // ########################################################################################################################################## + + copyPipeline->vkObjRenderPass = copySurface_createRenderpass(state); + vkObjPipeline->addRef(copyPipeline->vkObjRenderPass); + + // ########################################################### + + VkGraphicsPipelineCreateInfo pipelineInfo{}; + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineInfo.stageCount = (uint32_t)shaderStages.size(); + pipelineInfo.pStages = shaderStages.data(); + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pDynamicState = &dynamicState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pColorBlendState = state.destinationTexture->isDepth?nullptr:&colorBlending; + pipelineInfo.layout = vkObjPipeline->pipeline_layout; + pipelineInfo.renderPass = copyPipeline->vkObjRenderPass->m_renderPass; + pipelineInfo.pDepthStencilState = &depthStencilState; + pipelineInfo.subpass = 0; + pipelineInfo.basePipelineHandle = nullptr; + pipelineInfo.flags = 0; + + copyPipeline->vkObjPipeline = vkObjPipeline; + + result = vkCreateGraphicsPipelines(m_logicalDevice, m_pipeline_cache, 1, &pipelineInfo, nullptr, ©Pipeline->vkObjPipeline->pipeline); + if (result != VK_SUCCESS) + { + forceLog_printf("Failed to create graphics pipeline for surface copy. Error %d Info:", (sint32)result); + cemu_assert_debug(false); + copyPipeline->vkObjPipeline->pipeline = VK_NULL_HANDLE; + } + //performanceMonitor.vk.numGraphicPipelines.increment(); + + //m_pipeline_cache_semaphore.notify(); + + return copyPipeline; +} + +VKRObjectTextureView* VulkanRenderer::surfaceCopy_createImageView(LatteTextureVk* textureVk, uint32 sliceIndex, uint32 mipIndex) +{ + + VkImageViewCreateInfo viewCreateInfo = {}; + viewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewCreateInfo.image = textureVk->GetImageObj()->m_image; + viewCreateInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewCreateInfo.format = textureVk->GetFormat(); + viewCreateInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + viewCreateInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + viewCreateInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + viewCreateInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + if (textureVk->isDepth) + viewCreateInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + else + viewCreateInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + viewCreateInfo.subresourceRange.baseMipLevel = mipIndex; + viewCreateInfo.subresourceRange.levelCount = 1; + viewCreateInfo.subresourceRange.baseArrayLayer = sliceIndex; + viewCreateInfo.subresourceRange.layerCount = 1; + VkImageView imageView; + if (vkCreateImageView(m_logicalDevice, &viewCreateInfo, nullptr, &imageView) != VK_SUCCESS) + UnrecoverableError("Failed to create framebuffer image view for copy surface operation"); + return new VKRObjectTextureView(textureVk->GetImageObj(), imageView); +} + +VKRObjectFramebuffer* VulkanRenderer::surfaceCopy_getOrCreateFramebuffer(VkCopySurfaceState_t& state, CopySurfacePipelineInfo* pipelineInfo) +{ + auto itr = pipelineInfo->map_framebuffers.find(state.destinationTexture); + if (itr != pipelineInfo->map_framebuffers.end()) + { + auto p = itr->second.get(state.dstSlice, state.dstMip); + if (p != nullptr) + return p->vkObjFramebuffer; + } + + // create view + VKRObjectTextureView* vkObjTextureView = surfaceCopy_createImageView(state.destinationTexture, state.dstSlice, state.dstMip); + + // create new framebuffer + sint32 effectiveWidth = 0; + sint32 effectiveHeight = 0; + LatteTexture_getEffectiveSize(state.destinationTexture, &effectiveWidth, &effectiveHeight, nullptr, state.dstMip); + + std::array<VKRObjectTextureView*, 1> fbAttachments; + fbAttachments[0] = vkObjTextureView; + + VKRObjectFramebuffer* vkObjFramebuffer = new VKRObjectFramebuffer(pipelineInfo->vkObjRenderPass, fbAttachments, Vector2i(effectiveWidth, effectiveHeight)); + + // register + auto insertResult = pipelineInfo->map_framebuffers.try_emplace(state.destinationTexture, state.destinationTexture); + CopySurfacePipelineInfo::FramebufferValue* framebufferVal = insertResult.first->second.create(state.dstSlice, state.dstMip); + framebufferVal->vkObjFramebuffer = vkObjFramebuffer; + framebufferVal->vkObjImageView = vkObjTextureView; + + return vkObjFramebuffer; +} + +VKRObjectDescriptorSet* VulkanRenderer::surfaceCopy_getOrCreateDescriptorSet(VkCopySurfaceState_t& state, CopySurfacePipelineInfo* pipelineInfo) +{ + auto itr = pipelineInfo->map_descriptors.find(state.sourceTexture); + if (itr != pipelineInfo->map_descriptors.end()) + { + auto p = itr->second.get(state.srcSlice, state.srcMip); + if (p != nullptr) + return p->vkObjDescriptorSet; + } + + VKRObjectDescriptorSet* vkObjDescriptorSet = new VKRObjectDescriptorSet(); + + // allocate new descriptor set + VkDescriptorSetAllocateInfo allocInfo = {}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = m_descriptorPool; + allocInfo.descriptorSetCount = 1; + allocInfo.pSetLayouts = &(pipelineInfo->vkObjPipeline->pixelDSL); + + if (vkAllocateDescriptorSets(m_logicalDevice, &allocInfo, &vkObjDescriptorSet->descriptorSet) != VK_SUCCESS) + { + UnrecoverableError("failed to allocate descriptor set for surface copy operation"); + } + + // create view + VKRObjectTextureView* vkObjImageView = surfaceCopy_createImageView(state.sourceTexture, state.srcSlice, state.srcMip); + vkObjDescriptorSet->addRef(vkObjImageView); + + // create sampler + VkSamplerCreateInfo samplerInfo{}; + samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; + samplerInfo.minLod = 0; + samplerInfo.maxLod = 0; + samplerInfo.minFilter = VK_FILTER_NEAREST; + samplerInfo.magFilter = VK_FILTER_NEAREST; + samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + samplerInfo.anisotropyEnable = VK_FALSE; + samplerInfo.maxAnisotropy = 1.0f; + samplerInfo.mipLodBias = 0; + samplerInfo.compareEnable = VK_FALSE; + samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK; + + if (vkCreateSampler(m_logicalDevice, &samplerInfo, nullptr, &vkObjImageView->m_textureDefaultSampler[0]) != VK_SUCCESS) + UnrecoverableError("Failed to create texture sampler for surface copy operation"); + + // create descriptor image info + VkDescriptorImageInfo descriptorImageInfo{}; + + descriptorImageInfo.sampler = vkObjImageView->m_textureDefaultSampler[0]; + descriptorImageInfo.imageView = vkObjImageView->m_textureImageView; + descriptorImageInfo.imageLayout = VK_IMAGE_LAYOUT_GENERAL; + + VkWriteDescriptorSet write_descriptor{}; + write_descriptor.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write_descriptor.dstSet = vkObjDescriptorSet->descriptorSet; + write_descriptor.dstBinding = 0; + write_descriptor.dstArrayElement = 0; + write_descriptor.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + write_descriptor.descriptorCount = 1; + write_descriptor.pImageInfo = &descriptorImageInfo; + + vkUpdateDescriptorSets(m_logicalDevice, 1, &write_descriptor, 0, nullptr); + + // register + auto insertResult = pipelineInfo->map_descriptors.try_emplace(state.sourceTexture, state.sourceTexture); + CopySurfacePipelineInfo::DescriptorValue* descriptorValue = insertResult.first->second.create(state.srcSlice, state.srcMip); + descriptorValue->vkObjDescriptorSet = vkObjDescriptorSet; + descriptorValue->vkObjImageView = vkObjImageView; + + return vkObjDescriptorSet; +} + +void VulkanRenderer::surfaceCopy_viaDrawcall(LatteTextureVk* srcTextureVk, sint32 texSrcMip, sint32 texSrcSlice, LatteTextureVk* dstTextureVk, sint32 texDstMip, sint32 texDstSlice, sint32 effectiveCopyWidth, sint32 effectiveCopyHeight) +{ + draw_endRenderPass(); + + //debug_printf("surfaceCopy_viaDrawcall Src %04d %04d Dst %04d %04d CopySize %04d %04d\n", srcTextureVk->width, srcTextureVk->height, dstTextureVk->width, dstTextureVk->height, effectiveCopyWidth, effectiveCopyHeight); + + + VkImageSubresourceLayers srcImageSubresource; + srcImageSubresource.aspectMask = srcTextureVk->GetImageAspect(); + srcImageSubresource.baseArrayLayer = texSrcSlice; + srcImageSubresource.mipLevel = texSrcMip; + srcImageSubresource.layerCount = 1; + + VkImageSubresourceLayers dstImageSubresource; + dstImageSubresource.aspectMask = dstTextureVk->GetImageAspect(); + dstImageSubresource.baseArrayLayer = texDstSlice; + dstImageSubresource.mipLevel = texDstMip; + dstImageSubresource.layerCount = 1; + + VkCopySurfaceState_t copySurfaceState; + copySurfaceState.sourceTexture = srcTextureVk; + copySurfaceState.srcSlice = texSrcSlice; + copySurfaceState.srcMip = texSrcMip; + copySurfaceState.destinationTexture = dstTextureVk; + copySurfaceState.dstSlice = texDstSlice; + copySurfaceState.dstMip = texDstMip; + copySurfaceState.width = effectiveCopyWidth; + copySurfaceState.height = effectiveCopyHeight; + + CopySurfacePipelineInfo* copySurfacePipelineInfo = copySurface_getOrCreateGraphicsPipeline(copySurfaceState); + + // get framebuffer + VKRObjectFramebuffer* vkObjFramebuffer = surfaceCopy_getOrCreateFramebuffer(copySurfaceState, copySurfacePipelineInfo); + vkObjFramebuffer->flagForCurrentCommandBuffer(); + + // get descriptor set + VKRObjectDescriptorSet* vkObjDescriptorSet = surfaceCopy_getOrCreateDescriptorSet(copySurfaceState, copySurfacePipelineInfo); + + // get extend + sint32 effectiveWidth = 0; + sint32 effectiveHeight = 0; + LatteTexture_getEffectiveSize(dstTextureVk, &effectiveWidth, &effectiveHeight, nullptr, texDstMip); + + // get extend + sint32 srcEffectiveWidth = 0; + sint32 srcEffectiveHeight = 0; + LatteTexture_getEffectiveSize(srcTextureVk, &srcEffectiveWidth, &srcEffectiveHeight, nullptr, texSrcMip); + + CopyShaderPushConstantData_t pushConstantData; + + float srcCopyWidth = (float)1.0f; + float srcCopyHeight = (float)1.0f; + // q0 vertex + pushConstantData.vertexOffsets[0] = -1.0f; + pushConstantData.vertexOffsets[1] = 1.0f; + // q1 + pushConstantData.vertexOffsets[2] = 1.0f; + pushConstantData.vertexOffsets[3] = 1.0f; + // q2 + pushConstantData.vertexOffsets[4] = -1.0f; + pushConstantData.vertexOffsets[5] = -1.0f; + // q3 + pushConstantData.vertexOffsets[6] = 1.0f; + pushConstantData.vertexOffsets[7] = -1.0f; + + pushConstantData.srcTexelOffset[0] = 0; + pushConstantData.srcTexelOffset[1] = 0; + + vkCmdPushConstants(m_state.currentCommandBuffer, copySurfacePipelineInfo->vkObjPipeline->pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(pushConstantData), &pushConstantData); + + // draw + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = copySurfacePipelineInfo->vkObjRenderPass->m_renderPass; + renderPassInfo.framebuffer = vkObjFramebuffer->m_frameBuffer; + renderPassInfo.renderArea.offset = { 0, 0 }; + renderPassInfo.renderArea.extent = { (uint32_t)effectiveCopyWidth, (uint32_t)effectiveCopyHeight }; + renderPassInfo.clearValueCount = 0; + + VkViewport viewport{}; + viewport.x = 0; + viewport.y = (float)effectiveCopyHeight; + viewport.width = (float)effectiveCopyWidth; + viewport.height = (float)-effectiveCopyHeight; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + + VkRect2D scissor; + scissor.offset.x = 0; + scissor.offset.y = 0; + scissor.extent.width = effectiveCopyWidth; + scissor.extent.height = effectiveCopyHeight; + + vkCmdSetViewport(m_state.currentCommandBuffer, 0, 1, &viewport); + vkCmdSetScissor(m_state.currentCommandBuffer, 0, 1, &scissor); + + cemu_assert_debug(srcTextureVk->GetImageObj()->m_image != dstTextureVk->GetImageObj()->m_image); + + barrier_image<SYNC_OP::IMAGE_WRITE | SYNC_OP::ANY_TRANSFER, SYNC_OP::IMAGE_READ>(srcTextureVk, srcImageSubresource, VK_IMAGE_LAYOUT_GENERAL); // wait for any modifying operations on source image to complete + barrier_image<SYNC_OP::IMAGE_READ | SYNC_OP::IMAGE_WRITE | SYNC_OP::ANY_TRANSFER, SYNC_OP::IMAGE_WRITE>(dstTextureVk, dstImageSubresource, VK_IMAGE_LAYOUT_GENERAL); // wait for any operations on destination image to complete + + + vkCmdBeginRenderPass(m_state.currentCommandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + + vkCmdBindPipeline(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, copySurfacePipelineInfo->vkObjPipeline->pipeline); + copySurfacePipelineInfo->vkObjPipeline->flagForCurrentCommandBuffer(); + + m_state.currentPipeline = copySurfacePipelineInfo->vkObjPipeline->pipeline; + + vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, + copySurfacePipelineInfo->vkObjPipeline->pipeline_layout, 0, 1, &vkObjDescriptorSet->descriptorSet, 0, nullptr); + vkObjDescriptorSet->flagForCurrentCommandBuffer(); + + vkCmdDraw(m_state.currentCommandBuffer, 6, 1, 0, 0); + + vkCmdEndRenderPass(m_state.currentCommandBuffer); + + barrier_image<SYNC_OP::IMAGE_READ, SYNC_OP::IMAGE_READ | SYNC_OP::IMAGE_WRITE | SYNC_OP::ANY_TRANSFER>(srcTextureVk, srcImageSubresource, VK_IMAGE_LAYOUT_GENERAL); // wait for drawcall to complete before any other operations on the source image + barrier_image<SYNC_OP::IMAGE_WRITE, SYNC_OP::IMAGE_READ | SYNC_OP::IMAGE_WRITE | SYNC_OP::ANY_TRANSFER>(dstTextureVk, dstImageSubresource, VK_IMAGE_LAYOUT_GENERAL); // wait for drawcall to complete before any other operations on the destination image + + // restore viewport and scissor box + vkCmdSetViewport(m_state.currentCommandBuffer, 0, 1, &m_state.currentViewport); + vkCmdSetScissor(m_state.currentCommandBuffer, 0, 1, &m_state.currentScissorRect); + + LatteTexture_TrackTextureGPUWrite(dstTextureVk, texDstSlice, texDstMip, LatteTexture_getNextUpdateEventCounter()); +} + +struct vkComponentDesc_t +{ + enum class TYPE : uint8 + { + NONE, + UNORM, + SNORM, + FLOAT + }; + uint8 bits; + TYPE type; + + vkComponentDesc_t(uint8 b, TYPE t) : bits(b), type(t) {}; + friend bool operator==(const vkComponentDesc_t& lhs, const vkComponentDesc_t& rhs) + { + return lhs.bits == rhs.bits && lhs.type == rhs.type; + } +}; + +bool vkIsDepthFormat(VkFormat imageFormat) +{ + switch (imageFormat) + { + case VK_FORMAT_D32_SFLOAT_S8_UINT: + case VK_FORMAT_D24_UNORM_S8_UINT: + case VK_FORMAT_D32_SFLOAT: + case VK_FORMAT_D16_UNORM: + return true; + default: + break; + } + return false; +} + +vkComponentDesc_t vkGetFormatDepthBits(VkFormat imageFormat) +{ + switch (imageFormat) + { + case VK_FORMAT_D32_SFLOAT_S8_UINT: + return vkComponentDesc_t(32, vkComponentDesc_t::TYPE::FLOAT); + case VK_FORMAT_D24_UNORM_S8_UINT: + return vkComponentDesc_t(24, vkComponentDesc_t::TYPE::UNORM); + case VK_FORMAT_D32_SFLOAT: + return vkComponentDesc_t(32, vkComponentDesc_t::TYPE::FLOAT); + case VK_FORMAT_D16_UNORM: + return vkComponentDesc_t(16, vkComponentDesc_t::TYPE::UNORM); + default: + break; + } + return vkComponentDesc_t(0, vkComponentDesc_t::TYPE::NONE); +} + +bool vkIsBitCompatibleColorDepthFormat(VkFormat format1, VkFormat format2) +{ + cemu_assert_debug(vkIsDepthFormat(format1) != vkIsDepthFormat(format2)); + + VkFormat depthFormat, colorFormat; + if (vkIsDepthFormat(format1)) + { + depthFormat = format1; + colorFormat = format2; + } + else + { + depthFormat = format2; + colorFormat = format1; + } + + switch (depthFormat) + { + case VK_FORMAT_D32_SFLOAT_S8_UINT: + return colorFormat == VK_FORMAT_R32_SFLOAT; + case VK_FORMAT_D24_UNORM_S8_UINT: + return false; // there is no 24-bit color format + case VK_FORMAT_D32_SFLOAT: + return colorFormat == VK_FORMAT_R32_SFLOAT; + case VK_FORMAT_D16_UNORM: + return colorFormat == VK_FORMAT_R16_UNORM; + default: + break; + } + return false; +} + +void VulkanRenderer::surfaceCopy_viaBuffer(LatteTextureVk* srcTextureVk, sint32 texSrcMip, sint32 texSrcSlice, LatteTextureVk* dstTextureVk, sint32 texDstMip, sint32 texDstSlice, sint32 effectiveCopyWidth, sint32 effectiveCopyHeight) +{ + cemu_assert_debug(false); // not used currently + + cemu_assert_debug(m_featureControl.mode.useBufferSurfaceCopies); + + if (srcTextureVk->dim == Latte::E_DIM::DIM_3D) + { + cemu_assert_debug(false); + return; + } + if (dstTextureVk->dim == Latte::E_DIM::DIM_3D) + { + cemu_assert_debug(false); + return; + } + + draw_endRenderPass(); + + // calculate buffer size required for copy + VkDeviceSize copySize = std::max(srcTextureVk->getAllocation()->getAllocationSize(), dstTextureVk->getAllocation()->getAllocationSize()); + + // make sure allocated buffer is large enough + if (m_surfaceCopyBuffer == VK_NULL_HANDLE || copySize > m_surfaceCopyBufferSize) + { + if (m_surfaceCopyBuffer != VK_NULL_HANDLE) + { + // free existing buffer + destroyDeviceMemory(m_surfaceCopyBufferMemory); + m_surfaceCopyBufferMemory = VK_NULL_HANDLE; + destroyBuffer(m_surfaceCopyBuffer); + m_surfaceCopyBuffer = VK_NULL_HANDLE; + } + VkDeviceSize allocSize = (copySize + 1024ull * 1024ull - 1ull) & ~(1024ull * 1024ull - 1ull); // align to whole MB + m_surfaceCopyBufferSize = allocSize; + memoryManager->CreateBuffer(m_surfaceCopyBufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, m_surfaceCopyBuffer, m_surfaceCopyBufferMemory); + if (m_surfaceCopyBuffer == VK_NULL_HANDLE) + { + forceLog_printf("Vulkan: Failed to allocate surface copy buffer with size %llu", allocSize); + return; + } + } + if (m_surfaceCopyBuffer == VK_NULL_HANDLE) + return; + + auto vkObjSrcTexture = srcTextureVk->GetImageObj(); + auto vkObjDstTexture = dstTextureVk->GetImageObj(); + vkObjSrcTexture->flagForCurrentCommandBuffer(); + vkObjDstTexture->flagForCurrentCommandBuffer(); + + VkBufferImageCopy region{}; + region.bufferOffset = 0; + region.bufferRowLength = effectiveCopyWidth; + region.bufferImageHeight = effectiveCopyHeight; + + if (srcTextureVk->isDepth) + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + else + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.baseArrayLayer = texSrcSlice; + region.imageSubresource.layerCount = 1; + region.imageSubresource.mipLevel = texSrcMip; + + region.imageOffset = { 0,0,0 }; + region.imageExtent = { (uint32)effectiveCopyWidth, (uint32)effectiveCopyHeight, 1 }; + + // make sure all write operations to the src image have finished + barrier_image<SYNC_OP::IMAGE_WRITE | SYNC_OP::ANY_TRANSFER, SYNC_OP::ANY_TRANSFER>(srcTextureVk, region.imageSubresource, VK_IMAGE_LAYOUT_GENERAL); + + vkCmdCopyImageToBuffer(getCurrentCommandBuffer(), vkObjSrcTexture->m_image, VK_IMAGE_LAYOUT_GENERAL, m_surfaceCopyBuffer, 1, ®ion); + + // copy buffer to image + + VkBufferImageCopy imageRegion[2]{}; + sint32 imageRegionCount = 0; + + // color or depth only copy + imageRegion[0].bufferOffset = 0; + imageRegion[0].imageExtent.width = effectiveCopyWidth; + imageRegion[0].imageExtent.height = effectiveCopyHeight; + imageRegion[0].imageExtent.depth = 1; + + imageRegion[0].imageSubresource.mipLevel = texDstMip; + if (dstTextureVk->isDepth) + imageRegion[0].imageSubresource.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + else + imageRegion[0].imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + imageRegion[0].imageSubresource.baseArrayLayer = texDstSlice; + imageRegion[0].imageSubresource.layerCount = 1; + + imageRegionCount = 1; + + // make sure the transfer to the buffer finished + barrier_bufferRange<SYNC_OP::ANY_TRANSFER, SYNC_OP::ANY_TRANSFER>(m_surfaceCopyBuffer, 0, VK_WHOLE_SIZE); + + // make sure all read and write operations to the dst image have finished + barrier_image<SYNC_OP::IMAGE_READ | SYNC_OP::IMAGE_WRITE | SYNC_OP::ANY_TRANSFER, SYNC_OP::ANY_TRANSFER>(dstTextureVk, imageRegion[0].imageSubresource, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + + vkCmdCopyBufferToImage(m_state.currentCommandBuffer, m_surfaceCopyBuffer, vkObjDstTexture->m_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, imageRegionCount, imageRegion); + + // make sure transfer has finished before any other operation + barrier_image<SYNC_OP::ANY_TRANSFER, SYNC_OP::ANY_TRANSFER | SYNC_OP::IMAGE_READ | SYNC_OP::IMAGE_WRITE>(dstTextureVk, imageRegion[0].imageSubresource, VK_IMAGE_LAYOUT_GENERAL); +} + +void VulkanRenderer::surfaceCopy_copySurfaceWithFormatConversion(LatteTexture* sourceTexture, sint32 srcMip, sint32 srcSlice, LatteTexture* destinationTexture, sint32 dstMip, sint32 dstSlice, sint32 width, sint32 height) +{ + // scale copy size to effective size + sint32 effectiveCopyWidth = width; + sint32 effectiveCopyHeight = height; + LatteTexture_scaleToEffectiveSize(sourceTexture, &effectiveCopyWidth, &effectiveCopyHeight, 0); + sint32 sourceEffectiveWidth; + sint32 sourceEffectiveHeight; + LatteTexture_getEffectiveSize(sourceTexture, &sourceEffectiveWidth, &sourceEffectiveHeight, nullptr, srcMip); + + sint32 texSrcMip = srcMip; + sint32 texSrcSlice = srcSlice; + sint32 texDstMip = dstMip; + sint32 texDstSlice = dstSlice; + + LatteTextureVk* srcTextureVk = (LatteTextureVk*)sourceTexture; + LatteTextureVk* dstTextureVk = (LatteTextureVk*)destinationTexture; + + // check if texture rescale ratios match + // todo - if not, we have to use drawcall based copying + if (!LatteTexture_doesEffectiveRescaleRatioMatch(srcTextureVk, texSrcMip, dstTextureVk, texDstMip)) + { + forceLogDebug_printf("surfaceCopy_copySurfaceViaDrawcall(): Mismatching dimensions"); + return; + } + + // check if bpp size matches + if (srcTextureVk->GetBPP() != dstTextureVk->GetBPP()) + { + forceLogDebug_printf("surfaceCopy_copySurfaceViaDrawcall(): Mismatching BPP"); + return; + } + + VkFormat srcFormatVk = srcTextureVk->GetFormat(); + VkFormat dstFormatVk = dstTextureVk->GetFormat(); + + if ((srcTextureVk->isDepth && !dstTextureVk->isDepth) || + !srcTextureVk->isDepth && dstTextureVk->isDepth) + { + // depth to color or + // color to depth + if (m_featureControl.mode.useBufferSurfaceCopies && vkIsBitCompatibleColorDepthFormat(srcFormatVk, dstFormatVk)) + surfaceCopy_viaBuffer(srcTextureVk, texSrcMip, texSrcSlice, dstTextureVk, texDstMip, texDstSlice, effectiveCopyWidth, effectiveCopyHeight); + else + surfaceCopy_viaDrawcall(srcTextureVk, texSrcMip, texSrcSlice, dstTextureVk, texDstMip, texDstSlice, effectiveCopyWidth, effectiveCopyHeight); + } + else + { + // depth to depth or + // color to color + if (m_featureControl.mode.useBufferSurfaceCopies && srcFormatVk == dstFormatVk) + surfaceCopy_viaBuffer(srcTextureVk, texSrcMip, texSrcSlice, dstTextureVk, texDstMip, texDstSlice, effectiveCopyWidth, effectiveCopyHeight); + else + surfaceCopy_viaDrawcall(srcTextureVk, texSrcMip, texSrcSlice, dstTextureVk, texDstMip, texDstSlice, effectiveCopyWidth, effectiveCopyHeight); + } +} + +// called whenever a texture is destroyed +// it is guaranteed that the texture is not in use and all associated resources (descriptor sets, framebuffers) can be destroyed safely +void VulkanRenderer::surfaceCopy_notifyTextureRelease(LatteTextureVk* hostTexture) +{ + for (auto& itr : m_copySurfacePipelineCache) + { + auto& pipelineInfo = itr.second; + + auto itrDescriptors = pipelineInfo->map_descriptors.find(hostTexture); + if (itrDescriptors != pipelineInfo->map_descriptors.end()) + { + for (auto p : itrDescriptors->second.m_array) + { + if (p) + { + VulkanRenderer::GetInstance()->releaseDestructibleObject(p->vkObjDescriptorSet); + p->vkObjDescriptorSet = nullptr; + VulkanRenderer::GetInstance()->releaseDestructibleObject(p->vkObjImageView); + p->vkObjImageView = nullptr; + } + } + pipelineInfo->map_descriptors.erase(itrDescriptors); + } + + auto itrFramebuffers = pipelineInfo->map_framebuffers.find(hostTexture); + if (itrFramebuffers != pipelineInfo->map_framebuffers.end()) + { + for (auto p : itrFramebuffers->second.m_array) + { + if (p) + { + VulkanRenderer::GetInstance()->releaseDestructibleObject(p->vkObjFramebuffer); + p->vkObjFramebuffer = nullptr; + VulkanRenderer::GetInstance()->releaseDestructibleObject(p->vkObjImageView); + p->vkObjImageView = nullptr; + } + } + pipelineInfo->map_framebuffers.erase(itrFramebuffers); + } + } +} + +void VulkanRenderer::surfaceCopy_cleanup() +{ + // todo - release m_copySurfacePipelineCache etc +} diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanTextureReadback.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanTextureReadback.h new file mode 100644 index 00000000..aeedefbd --- /dev/null +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanTextureReadback.h @@ -0,0 +1,41 @@ +#pragma once +#include "Cafe/HW/Latte/Core/LatteTextureReadbackInfo.h" + +class LatteTextureReadbackInfoVk : public LatteTextureReadbackInfo +{ +public: + LatteTextureReadbackInfoVk(VkDevice device, LatteTextureView* textureView); + ~LatteTextureReadbackInfoVk(); + + static uint32 GetImageSize(LatteTextureView* textureView); + + void StartTransfer() override; + + bool IsFinished() override; + void ForceFinish() override; + + uint8* GetData() override + { + return m_buffer_ptr + m_buffer_offset; + } + + uint32 GetImageSize() const + { + return m_image_size; + } + + void SetBuffer(VkBuffer buffer, uint8* buffer_ptr, const uint32 buffer_offset) + { + m_buffer = buffer; + m_buffer_ptr = buffer_ptr; + m_buffer_offset = buffer_offset; + } + +private: + VkDevice m_device = nullptr; + + VkBuffer m_buffer = nullptr; + uint8* m_buffer_ptr = nullptr; + uint32 m_buffer_offset = 0; + uint64 m_associatedCommandBufferId = 0; +}; diff --git a/src/Cafe/HW/Latte/ShaderInfo/ShaderDescription.cpp b/src/Cafe/HW/Latte/ShaderInfo/ShaderDescription.cpp new file mode 100644 index 00000000..750b4801 --- /dev/null +++ b/src/Cafe/HW/Latte/ShaderInfo/ShaderDescription.cpp @@ -0,0 +1,56 @@ +#include "Cafe/HW/Latte/ShaderInfo/ShaderInfo.h" +#include "Cafe/HW/Latte/ISA/LatteInstructions.h" + +namespace Latte +{ + bool ShaderDescription::analyzeShaderCode(void* shaderProgram, size_t sizeInBytes, LatteConst::ShaderType shaderType) + { + assert_dbg(); + + + // parse CF flow + // we need to parse: + // - Export clauses to gather info about exported attributes and written render targets + // - ALU clauses to gather info about accessed uniforms (and the remapped uniform) + + const LatteCFInstruction* cfCode = (const LatteCFInstruction*)shaderProgram; + const size_t cfMaxCount = sizeInBytes / 8; + + size_t cfIndex = 0; + while (cfIndex < cfMaxCount) + { + const LatteCFInstruction* baseInstr = cfCode + cfIndex; + cfIndex++; + bool isALU = false; + if (const auto cfInstr = baseInstr->getParserIfOpcodeMatch<LatteCFInstruction_DEFAULT>()) + { + cemu_assert_debug(cfInstr->getField_WHOLE_QUAD_MODE() == 0); + + cemu_assert_debug(cfInstr->getField_CALL_COUNT() == 0); // todo + cemu_assert_debug(cfInstr->getField_POP_COUNT() == 0); // todo + + auto cond = cfInstr->getField_COND(); + + assert_dbg(); + } + else if (const auto cfInstr = baseInstr->getParserIfOpcodeMatch<LatteCFInstruction_ALU>()) + { + assert_dbg(); + isALU = true; + } + else if (const auto cfInstr = baseInstr->getParserIfOpcodeMatch<LatteCFInstruction_EXPORT_IMPORT>()) + { + assert_dbg(); + } + else + { + cemuLog_log(LogType::Force, "ShaderDescription::analyzeShaderCode(): Missing implementation for CF opcode 0x%02x\n", baseInstr->getField_Opcode()); + cemu_assert_debug(false); // todo + } + if (!isALU && baseInstr->getField_END_OF_PROGRAM()) + break; + } + + return true; + } +}; diff --git a/src/Cafe/HW/Latte/ShaderInfo/ShaderInfo.h b/src/Cafe/HW/Latte/ShaderInfo/ShaderInfo.h new file mode 100644 index 00000000..15f26043 --- /dev/null +++ b/src/Cafe/HW/Latte/ShaderInfo/ShaderInfo.h @@ -0,0 +1,18 @@ +#pragma once +#include "Cafe/HW/Latte/Core/LatteConst.h" +#include "util/containers/SmallBitset.h" + +namespace Latte +{ + class ShaderDescription + { + public: + bool analyzeShaderCode(void* shaderProgram, size_t sizeInBytes, LatteConst::ShaderType shaderType); + + // public data + + // pixel shader only + SmallBitset<8> renderTargetWriteMask; // true if render target is written + + }; +}; \ No newline at end of file diff --git a/src/Cafe/HW/Latte/ShaderInfo/ShaderInstanceInfo.cpp b/src/Cafe/HW/Latte/ShaderInfo/ShaderInstanceInfo.cpp new file mode 100644 index 00000000..e69de29b diff --git a/src/Cafe/HW/Latte/Transcompiler/LatteTC.cpp b/src/Cafe/HW/Latte/Transcompiler/LatteTC.cpp new file mode 100644 index 00000000..d8c83cbf --- /dev/null +++ b/src/Cafe/HW/Latte/Transcompiler/LatteTC.cpp @@ -0,0 +1,227 @@ +#include "Cafe/HW/Latte/Transcompiler/LatteTC.h" +#include "Cafe/HW/Latte/ISA/LatteInstructions.h" + +#include "util/Zir/Core/IR.h" +#include "util/Zir/Core/ZpIRBuilder.h" +#include "util/Zir/Core/ZpIRDebug.h" + +class CFBlockNode +{ +public: + CFBlockNode(uint32 cfAddress, const LatteCFInstruction& cfInstruction) : cfAddress(cfAddress), cfNext(cfAddress + sizeof(LatteCFInstruction)) + { + m_cfInstructions.emplace_back(cfInstruction); + irBasicBlock = new ZpIR::ZpIRBasicBlock(); + }; + + void addInstruction(const LatteCFInstruction& cfInstruction) + { + m_cfInstructions.emplace_back(cfInstruction); + } + + // next CF address after this block of CF instructions (assuming no branches) + void setNextAddress(uint32 addr) + { + cfNext = addr; + } + + uint32 cfAddress; // offset of the first cf instruction + uint32 cfNext{ 0 }; // offset of the next cf instruction if no branch happens + + ZpIR::ZpIRBasicBlock* irBasicBlock{}; + + std::vector<LatteCFInstruction> m_cfInstructions; + +private: +}; + +// emit IR code for all clauses in a DAG node +void LatteTCGenIR::genIRForNode(CFBlockNode& node) +{ + m_irGenContext.reset(); + m_irGenContext.irBuilder = new ZpIR::BasicBlockBuilder(node.irBasicBlock); + m_irGenContext.isEntryBasicBlock = (&node == m_ctx.mainFunctionDAG.GetEntryNode()); + + // for vertex shaders add initialization code to main() + if (&node == m_ctx.mainFunctionDAG.GetEntryNode() && m_ctx.shaderType == SHADER_TYPE::VERTEX) + { + for (uint32 channel = 0; channel < 4; channel++) + { + ZpIR::IRReg irReg = m_irGenContext.irBuilder->createReg(ZpIR::DataType::U32); + m_irGenContext.irBuilder->emit_RR(ZpIR::IR::OpCode::MOV, irReg, m_irGenContext.irBuilder->createConstU32(0)); + this->m_irGenContext.activeVars.set(0, channel, irReg); + } + // todo - correctly init R0 based on currently set context register state + } + + + for (auto& itr : node.m_cfInstructions) + { + const auto opcode = itr.getField_Opcode(); + if (const auto cfInstr = itr.getParserIfOpcodeMatch<LatteCFInstruction_DEFAULT>()) + { + if(opcode == LatteCFInstruction::OPCODE::INST_CALL_FS) + processCF_CALL_FS(*cfInstr); + else + assert_dbg(); + } + else if (const auto cfInstr = itr.getParserIfOpcodeMatch<LatteCFInstruction_ALU>()) + { + if (opcode == LatteCFInstruction::OPCODE::INST_ALU) + processCF_ALU(*cfInstr); + else + assert_dbg(); + } + else if (const auto cfInstr = itr.getParserIfOpcodeMatch<LatteCFInstruction_EXPORT_IMPORT>()) + { + if (opcode == LatteCFInstruction::OPCODE::INST_EXPORT || + opcode == LatteCFInstruction::OPCODE::INST_EXPORT_DONE) + processCF_EXPORT(*cfInstr); + else + assert_dbg(); + } + else + { + debug_printf("Missing implementation for CF opcode 0x%02x\n", itr.getField_Opcode()); + assert_dbg(); // todo + } + } +} + +// parse CF program and create unlinked DAG +void LatteTCGenIR::parseCF_createNodes(NodeDAG& nodeDAG) +{ + const LatteCFInstruction* cfCode = (const LatteCFInstruction*)m_ctx.programData; + const size_t cfMaxCount = m_ctx.programSize / 8; + + // quick prepass to gather a list of jump destinations used by the next pass + // todo + + // linear pass where we turn uninterrupted sequences of CF instructions (no branch to or from) into CFBlockNode + // algorithm description: + // 1) Create CFBlockNode from first CF instruction. Make it the currently active node + // 2) For each remaining (1 .. n) CF instruction of program + // 2.1) If CF instruction can be merged into active node (no branch destination, no conditionals or other control flow branches) then add it to the currently active node + // 2.2) Otherwise finalize active node, add it to node list. Then create new CFBlockNode node from CF instruction and make it active node + // 3) Finalize active node and add to node list + + cemu_assert_debug(cfMaxCount != 0); // zero not allowed + + CFBlockNode* activeNode = new CFBlockNode(0, cfCode[0]); // first instruction becomes the initial node + size_t cfIndex = 1; + //m_nodes.emplace_back(activeNode); + while (cfIndex < cfMaxCount) + { + const LatteCFInstruction* baseInstr = cfCode + cfIndex; + cfIndex++; + bool canMerge; + bool isALU = false; + if (const auto cfInstr = baseInstr->getParserIfOpcodeMatch<LatteCFInstruction_DEFAULT>()) + { + cemu_assert_debug(cfInstr->getField_WHOLE_QUAD_MODE() == 0); + + cemu_assert_debug(cfInstr->getField_CALL_COUNT() == 0); // todo + cemu_assert_debug(cfInstr->getField_POP_COUNT() == 0); // todo + + auto cond = cfInstr->getField_COND(); + + assert_dbg(); + //cfInstr->getField_COND() == LatteCFInstruction::CF_COND::CF_COND_ACTIVE; + } + else if (const auto cfInstr = baseInstr->getParserIfOpcodeMatch<LatteCFInstruction_ALU>()) + { + // always merge ALU clauses since they dont have their own condition modes? + // todo - except if they are a jump target + canMerge = true; + isALU = true; + } + else if (const auto cfInstr = baseInstr->getParserIfOpcodeMatch<LatteCFInstruction_EXPORT_IMPORT>()) + { + // no extra conditions, always merge + canMerge = true; + } + else + { + debug_printf("Missing implementation for CF opcode 0x%02x\n", baseInstr->getField_Opcode()); + assert_dbg(); // todo + } + + if (canMerge) + { + activeNode->addInstruction(*baseInstr); + } + else + { + activeNode->setNextAddress((uint32)cfIndex - 1); + nodeDAG.m_nodes.emplace_back(activeNode); + // start new active node + activeNode = new CFBlockNode((uint32)cfIndex - 1, *baseInstr); + } + + if (!isALU && baseInstr->getField_END_OF_PROGRAM()) + break; + } + // finalize last node + cemu_assert_debug(!activeNode->m_cfInstructions.empty()); + nodeDAG.m_nodes.emplace_back(activeNode); +} + +void LatteTCGenIR::parseCFToDAG() +{ + // parse CF and create preliminary node DAG + parseCF_createNodes(m_ctx.mainFunctionDAG); + // link up the nodes + cemu_assert_debug(m_ctx.mainFunctionDAG.m_nodes.size() == 1); + // assign to ir object + for (auto& itr : m_ctx.mainFunctionDAG.m_nodes) + m_ctx.irObject->m_basicBlocks.emplace_back(itr->irBasicBlock); + m_ctx.irObject->m_entryBlocks.emplace_back(m_ctx.mainFunctionDAG.m_nodes[0]->irBasicBlock); +} + +void LatteTCGenIR::emitIR() +{ + cemu_assert_debug(m_ctx.mainFunctionDAG.m_nodes.size() == 1); + + for (auto& itr : m_ctx.mainFunctionDAG.m_nodes) + { + genIRForNode(*itr); + } + +} + +void LatteTCGenIR::cleanup() +{ + // clean up + //for (auto itr : m_ctx.list_irNodesCtx) + // delete itr; + //m_ctx.list_irNodesCtx.clear(); +} + +void LatteTCGenIR::setVertexShaderContext(const LatteFetchShader* parsedFetchShader, const uint32* vtxSemanticTable) +{ + m_vertexShaderCtx.parsedFetchShader = parsedFetchShader; + m_vertexShaderCtx.vtxSemanticTable = vtxSemanticTable; +} + + +ZpIR::ZpIRFunction* LatteTCGenIR::transcompileLatteToIR(const void* programData, uint32 programSize, SHADER_TYPE shaderType) +{ + //return nullptr; + ZpIR::ZpIRFunction* irObject = new ZpIR::ZpIRFunction(); + + // init context + m_ctx = {}; + m_ctx.programData = (const uint32*)programData; + m_ctx.programSize = programSize; + m_ctx.irObject = irObject; + m_ctx.shaderType = shaderType; + + // parse control flow instructions and convert it to list of CFBlockNode + // each node is a single IR basic block, consisting of one or multiple CF instructions + parseCFToDAG(); + // process clauses and emit IR nodes + emitIR(); + // cleanup + cleanup(); + return irObject; +} \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Transcompiler/LatteTC.h b/src/Cafe/HW/Latte/Transcompiler/LatteTC.h new file mode 100644 index 00000000..8660deb8 --- /dev/null +++ b/src/Cafe/HW/Latte/Transcompiler/LatteTC.h @@ -0,0 +1,200 @@ +#pragma once +#include "Cafe/HW/Latte/ISA/LatteInstructions.h" +#include "Cafe/HW/Latte/Core/FetchShader.h" +#include "Cafe/HW/Latte/Core/LatteConst.h" +#include "util/Zir/Core/IR.h" + +#include <boost/container/static_vector.hpp> + +namespace ZpIR +{ + class BasicBlockBuilder; +} + +// transforms GPU7/Latte shader binaries into an SSA IR (ZpIR) representation +class LatteTCGenIR +{ + //friend class CFNodeInfo; + friend struct CFNodeInfo_CALL_FS; +private: + struct NodeDAG // one per function (shaders usually have main only) + { + std::vector<class CFBlockNode*> m_nodes; + // class CFBlockNode* entrypoint; + + class CFBlockNode* GetEntryNode() + { + return m_nodes[0]; + } + }; + +public: + typedef unsigned short GPRElementIndex; // grpIndex*4+channel + + enum SHADER_TYPE + { + VERTEX, + GEOMETRY, + PIXEL, + }; + + void setVertexShaderContext(const LatteFetchShader* parsedFetchShader, const uint32* vtxSemanticTable); + + ZpIR::ZpIRFunction* transcompileLatteToIR(const void* programData, uint32 programSize, SHADER_TYPE shaderType); + +private: + ZpIR::IRReg getIRRegFromGPRElement(uint32 gprIndex, uint32 channel, ZpIR::DataType typeHint); + ZpIR::IRReg getTypedIRRegFromGPRElement(uint32 gprIndex, uint32 channel, ZpIR::DataType type); + + ZpIR::IRReg loadALUOperand(LatteALUSrcSel srcSel, uint8 srcChan, bool isNeg, bool isAbs, bool isRel, uint8 indexMode, const uint32* literalData, ZpIR::DataType typeHint, bool convertOnTypeMismatch); + void emitALUGroup(const class LatteClauseInstruction_ALU* aluUnit[5], const uint32* literalData); + + void genIRForNode(class CFBlockNode& node); + + void parseCFToDAG(); + void parseCF_createNodes(NodeDAG& nodeDAG); + void emitIR(); + void cleanup(); + + // IR emitter + void processCF_CALL_FS(const LatteCFInstruction_DEFAULT& cfInstruction); + void processCF_ALU(const LatteCFInstruction_ALU& cfInstruction); + void processCF_EXPORT(const LatteCFInstruction_EXPORT_IMPORT& cfInstruction); + + // helpers + void CF_CALL_FS_emitFetchAttribute(LatteParsedFetchShaderAttribute_t& attribute, Latte::GPRType dstGPR); + +private: + // tracks mapping GPR<->IR variable for current basic block + struct IREmitterActiveVars + { + bool get(uint32 gprIndex, uint32 channelIndex, ZpIR::IRReg& reg) + { + size_t index = gprIndex * 4 + channelIndex; + if (!m_present.test(index)) + return false; + reg = m_irReg[index]; + return true; + } + + void set(uint32 gprIndex, uint32 channelIndex, ZpIR::IRReg reg) + { + size_t index = gprIndex * 4 + channelIndex; + m_present.set(index); + m_irReg[index] = reg; + } + + // set register after current group + struct DelayedAssignment + { + uint16 index; + ZpIR::IRReg reg; + bool isSet{false}; + }; + + struct DelayedAssignmentPVPS + { + ZpIR::IRReg reg; + bool isSet{ false }; + }; + + void setAfterGroup(uint8 aluUnit, uint32 gprIndex, uint32 channelIndex, ZpIR::IRReg reg) + { + cemu_assert_debug(aluUnit < 5); + cemu_assert_debug(!m_delayedAssignments[aluUnit].isSet); + m_delayedAssignments[aluUnit].reg = reg; + m_delayedAssignments[aluUnit].index = gprIndex * 4 + channelIndex; + m_delayedAssignments[aluUnit].isSet = true; + } + + void setAfterGroupPVPS(uint8 aluUnit, ZpIR::IRReg reg) + { + cemu_assert_debug(aluUnit < 5); + cemu_assert_debug(!m_delayedAssignmentsPSPV[aluUnit].isSet); + m_delayedAssignmentsPSPV[aluUnit].reg = reg; + m_delayedAssignmentsPSPV[aluUnit].isSet = true; + } + + void applyDelayedAfterGroup() + { + // GPRs + for (size_t i = 0; i < 5; i++) + { + auto& assignment = m_delayedAssignments[i]; + if(!assignment.isSet) + continue; + setGPR(assignment.index, assignment.reg); + assignment.isSet = false; + } + // PV/PS + for (size_t i = 0; i < 5; i++) + { + auto& assignment = m_delayedAssignmentsPSPV[i]; + if (!assignment.isSet) + continue; + setPVPS((uint32)i, assignment.reg); + assignment.isSet = false; + } + } + + void reset() + { + m_present.reset(); + } + + private: + void setGPR(uint32 gprChannelindex, ZpIR::IRReg reg) + { + m_present.set(gprChannelindex); + m_irReg[gprChannelindex] = reg; + } + + void setPVPS(uint32 unitIndex, ZpIR::IRReg reg) + { + m_presentPVPS.set(unitIndex); + m_irRegPVPS[unitIndex] = reg; + } + + ZpIR::IRReg m_irReg[128 * 4]{}; + ZpIR::IRReg m_irRegPVPS[5]{}; + std::bitset<128 * 4> m_present; + std::bitset<5> m_presentPVPS; + DelayedAssignment m_delayedAssignments[5]{}; + DelayedAssignmentPVPS m_delayedAssignmentsPSPV[5]{}; + }; + + struct + { + IREmitterActiveVars activeVars; + ZpIR::BasicBlockBuilder* irBuilder; + bool isEntryBasicBlock{}; + + void reset() + { + activeVars.reset(); + } + }m_irGenContext; + + struct + { + const uint32* programData; + uint32 programSize; + //std::vector<struct CFNodeInfo*> list_irNodesCtx; + // first node in flow of main() function + //struct CFNodeInfo* mainEntry; + + SHADER_TYPE shaderType; + + NodeDAG mainFunctionDAG; + // current IR object + struct ZpIR::ZpIRFunction* irObject; + }m_ctx; + + + // vertex shader info + struct + { + const LatteFetchShader* parsedFetchShader{}; + const uint32* vtxSemanticTable{}; + }m_vertexShaderCtx{}; +}; diff --git a/src/Cafe/HW/Latte/Transcompiler/LatteTCGenIR.cpp b/src/Cafe/HW/Latte/Transcompiler/LatteTCGenIR.cpp new file mode 100644 index 00000000..5d5f75fa --- /dev/null +++ b/src/Cafe/HW/Latte/Transcompiler/LatteTCGenIR.cpp @@ -0,0 +1,641 @@ +#include "Cafe/HW/Latte/Transcompiler/LatteTC.h" +#include "Cafe/HW/Latte/ISA/LatteInstructions.h" +#include "util/Zir/Core/ZpIRBuilder.h" + +void LatteTCGenIR::CF_CALL_FS_emitFetchAttribute(LatteParsedFetchShaderAttribute_t& attribute, Latte::GPRType dstGPR) +{ + auto irBuilder = m_irGenContext.irBuilder; + + // extract each channel + for (sint32 t = 0; t < 4; t++) + { + uint32 gprElementIndex = (uint32)dstGPR * 4 + t; + LatteConst::VertexFetchDstSel ds = (LatteConst::VertexFetchDstSel)attribute.ds[t]; + + switch (ds) + { + case LatteConst::VertexFetchDstSel::X: + case LatteConst::VertexFetchDstSel::Y: + case LatteConst::VertexFetchDstSel::Z: + case LatteConst::VertexFetchDstSel::W: + { + uint8 channelIndex = (uint8)((uint32)ds - (uint32)LatteConst::VertexFetchDstSel::X); + + ZpIR::IRReg resultHolder = m_irGenContext.irBuilder->createReg(ZpIR::DataType::U32); + ZpIR::LocationSymbolName importSource = ZpIR::ShaderSubset::ShaderImportLocation().SetVertexAttribute(attribute.semanticId, channelIndex); + m_irGenContext.irBuilder->emit_IMPORT(importSource, resultHolder); + + // swap endianness + if (attribute.endianSwap == LatteConst::VertexFetchEndianMode::SWAP_U32) + { + // todo - this may be more complex depending on type + ZpIR::IRReg elementResult; + irBuilder->emit_RR(ZpIR::IR::OpCode::SWAP_ENDIAN, irBuilder->createReg(elementResult, ZpIR::DataType::U32), resultHolder); + resultHolder = elementResult; + } + + bool isSigned = attribute.isSigned; + + // transform + LatteConst::VertexFetchFormat fmt = (LatteConst::VertexFetchFormat)attribute.format; + LatteClauseInstruction_VTX::NUM_FORMAT_ALL nfa = (LatteClauseInstruction_VTX::NUM_FORMAT_ALL)attribute.nfa; + + if (fmt == LatteConst::VertexFetchFormat::VTX_FMT_32_32_32_FLOAT || + fmt == LatteConst::VertexFetchFormat::VTX_FMT_32_32_FLOAT) + { + uint32 numComp; + if (fmt == LatteConst::VertexFetchFormat::VTX_FMT_32_32_32_FLOAT) + numComp = 3; + else if (fmt == LatteConst::VertexFetchFormat::VTX_FMT_32_32_FLOAT) + numComp = 2; + else + { + cemu_assert_debug(false); + } + + cemu_assert_debug(attribute.endianSwap == LatteConst::VertexFetchEndianMode::SWAP_U32); + cemu_assert_debug(nfa == LatteClauseInstruction_VTX::NUM_FORMAT_ALL::NUM_FORMAT_SCALED); + cemu_assert_debug(channelIndex < numComp); + + ZpIR::IRReg elementResult; + irBuilder->emit_RR(ZpIR::IR::OpCode::BITCAST, irBuilder->createReg(elementResult, ZpIR::DataType::F32), resultHolder); + resultHolder = elementResult; + } + else if (fmt == LatteConst::VertexFetchFormat::VTX_FMT_8_8_8_8) + { + uint32 numComp; + switch (fmt) + { + case LatteConst::VertexFetchFormat::VTX_FMT_8_8_8_8: + numComp = 4; + break; + case LatteConst::VertexFetchFormat::VTX_FMT_8_8_8: + numComp = 3; + break; + case LatteConst::VertexFetchFormat::VTX_FMT_8_8: + numComp = 2; + break; + case LatteConst::VertexFetchFormat::VTX_FMT_8: + numComp = 1; + break; + } + + cemu_assert_debug(attribute.endianSwap == LatteConst::VertexFetchEndianMode::SWAP_NONE); + cemu_assert_debug(channelIndex < numComp); + + if (nfa == LatteClauseInstruction_VTX::NUM_FORMAT_ALL::NUM_FORMAT_NORM) + { + // scaled + if (isSigned) + { + assert_dbg(); + // we can fake sign extend by subtracting 128? Would be faster than the AND + Conditional OR + } + else + { + resultHolder = irBuilder->emit_RR(ZpIR::IR::OpCode::CONVERT_INT_TO_FLOAT, ZpIR::DataType::F32, resultHolder); + resultHolder = irBuilder->emit_RRR(ZpIR::IR::OpCode::DIV, ZpIR::DataType::F32, resultHolder, irBuilder->createConstF32(255.0f)); + } + } + else + { + assert_dbg(); + } + } + else + { + assert_dbg(); + } + + // todo - we need a sign-extend instruction for this which should take arbitrary bit count + + + this->m_irGenContext.activeVars.set(dstGPR, t, resultHolder); // set GPR.channel to the result + break; + } + case LatteConst::VertexFetchDstSel::CONST_0F: + { + // todo - this could also be an integer zero. Use attribute format / other channel info to determine if this type is integer/float + ZpIR::IRReg resultHolder; + irBuilder->emit_RR(ZpIR::IR::OpCode::MOV, irBuilder->createReg(resultHolder, ZpIR::DataType::F32), irBuilder->createConstF32(0.0f)); + this->m_irGenContext.activeVars.set(dstGPR, t, resultHolder); + break; + } + case LatteConst::VertexFetchDstSel::CONST_1F: + { + ZpIR::IRReg resultHolder; + irBuilder->emit_RR(ZpIR::IR::OpCode::MOV, irBuilder->createReg(resultHolder, ZpIR::DataType::F32), irBuilder->createConstF32(1.0f)); + this->m_irGenContext.activeVars.set(dstGPR, t, resultHolder); + break; + } + default: + assert_dbg(); + } + } +} + +void LatteTCGenIR::processCF_CALL_FS(const LatteCFInstruction_DEFAULT& cfInstruction) +{ + auto fetchShader = m_vertexShaderCtx.parsedFetchShader; + auto semanticTable = m_vertexShaderCtx.vtxSemanticTable; + + // generate IR to decode vertex attributes + cemu_assert_debug(fetchShader->bufferGroupsInvalid.size() == 0); // todo + for(auto& bufferGroup : fetchShader->bufferGroups) + { + for (sint32 i = 0; i < bufferGroup.attribCount; i++) + { + auto& attribute = bufferGroup.attrib[i]; + + uint32 dstGPR = 0; + // get register index based on vtx semantic table + uint32 attributeShaderLoc = 0xFFFFFFFF; + for (sint32 f = 0; f < 32; f++) + { + if (semanticTable[f] == attribute.semanticId) + { + attributeShaderLoc = f; + break; + } + } + if (attributeShaderLoc == 0xFFFFFFFF) + continue; // attribute is not mapped to VS input + dstGPR = attributeShaderLoc + 1; // R0 is skipped + + // emit IR code for attribute import (decode into GPR) + CF_CALL_FS_emitFetchAttribute(attribute, dstGPR); + } + } +} + +// get IRReg for Latte GPR (single channel) typeHint is used when register has to be imported +// if convertOnTypeMismatch is set then we bitcast the register on type mismatch +ZpIR::IRReg LatteTCGenIR::getIRRegFromGPRElement(uint32 gprIndex, uint32 channel, ZpIR::DataType typeHint) +{ + // get IR register for <GPR>.<channel> from currently active context + ZpIR::IRReg r; + if (m_irGenContext.activeVars.get(gprIndex, channel, r)) + return r; + + // if GPR.channel is not known + // in the entry basic block we can assume a value of zero because there is nowhere to import from + if (m_irGenContext.isEntryBasicBlock) + { + if (typeHint == ZpIR::DataType::F32) + return m_irGenContext.irBuilder->createConstF32(0.0f); + else if (typeHint == ZpIR::DataType::U32) + return m_irGenContext.irBuilder->createConstU32(0); + else if (typeHint == ZpIR::DataType::S32) + return m_irGenContext.irBuilder->createConstS32(0); + cemu_assert_debug(false); + } + + // otherwise create import and resolve later during register allocation + r = m_irGenContext.irBuilder->createReg(typeHint); + m_irGenContext.irBuilder->addImport(r, gprIndex*4 + channel); + + return r; +} + +// similar to getIRRegFromGPRElement() but will bitcast the type if it mismatches +ZpIR::IRReg LatteTCGenIR::getTypedIRRegFromGPRElement(uint32 gprIndex, uint32 channel, ZpIR::DataType type) +{ + auto irReg = getIRRegFromGPRElement(gprIndex, channel, type); + if (m_irGenContext.irBuilder->getRegType(irReg) == type) + return irReg; + // type does not match, bitcast into new reg + auto newReg = m_irGenContext.irBuilder->createReg(type); + m_irGenContext.irBuilder->emit_RR(ZpIR::IR::OpCode::BITCAST, newReg, irReg); + // remember converted register since its likely that it is accessed with the same type again + // todo - ideally, we would keep track of all the types. But it has to be efficient + m_irGenContext.activeVars.set(gprIndex, channel, newReg); + return newReg; +} + +// try to determine the type of the constant from the raw u32 value +ZpIR::DataType _guessTypeFromConstantValue(uint32 bits) +{ + if (bits == 0x3F800000) // float 1.0 + return ZpIR::DataType::F32; + return ZpIR::DataType::S32; +} + +// maybe pass a type hint parameter +ZpIR::IRReg LatteTCGenIR::loadALUOperand(LatteALUSrcSel srcSel, uint8 srcChan, bool isNeg, bool isAbs, bool isRel, uint8 indexMode, const uint32* literalData, ZpIR::DataType typeHint, bool convertOnTypeMismatch) +{ + if (srcSel.isGPR()) + { + //LatteTCGenIR::GPRElement gprElement = srcSel.getGPR() * 4 + srcChan; + if (isRel) + assert_dbg(); + + ZpIR::IRReg reg; + + if(convertOnTypeMismatch) + reg = getTypedIRRegFromGPRElement(srcSel.getGPR(), srcChan, typeHint); + else + reg = getIRRegFromGPRElement(srcSel.getGPR(), srcChan, typeHint); + + // if additional transformations are applied then we create a temporary IRReg here + // todo - is caching&recycling the transformed registers worth it? + if (isAbs || isNeg) + { + // create new var and apply transformation + assert_dbg(); + } + + return reg; + } + else if (srcSel.isAnyConst()) + { + if (srcSel.isConst_0F()) + { + return m_irGenContext.irBuilder->createConstF32(0.0f); // todo - could also be integer type constant? Try to find a way to predict the type correctly + } + else + assert_dbg(); + } + else if (srcSel.isLiteral()) + { + // literal constant + // we guess the type + return m_irGenContext.irBuilder->createTypedConst(literalData[srcChan], _guessTypeFromConstantValue(literalData[srcChan])); + } + else if (srcSel.isCFile()) + { + // constant registers / uniform registers + uint32 cfileIndex = srcSel.getCFile(); + auto newReg = m_irGenContext.irBuilder->createReg(typeHint); + ZpIR::LocationSymbolName importSource = ZpIR::ShaderSubset::ShaderImportLocation().SetUniformRegister(cfileIndex*4 + srcChan); + m_irGenContext.irBuilder->emit_IMPORT(importSource, newReg); + return newReg; + } + else + assert_dbg(); + + return 0; +} + +void LatteTCGenIR::emitALUGroup(const LatteClauseInstruction_ALU* aluUnit[5], const uint32* literalData) +{ + //struct + //{ + // uint32 gprElementIndex; + // ZpIR::IRReg irReg; + // bool isSet; + //}groupOutput[5] = {}; + + ZpIR::BasicBlockBuilder* irBuilder = m_irGenContext.irBuilder; + + // used by MOV instruction which can be used with any 32bit type (float, int, uint) + auto getMOVSourceType = [&](const LatteClauseInstruction_ALU_OP2* instrOP2) -> ZpIR::DataType + { + auto sel = instrOP2->getSrc0Sel(); + if (sel.isGPR()) + { + ZpIR::IRReg r; + if (!m_irGenContext.activeVars.get(sel.getGPR(), instrOP2->getSrc0Chan(), r)) + { + // import, do we have an alternative way to guess the type? + // for now lets assume float because it will be correct more often than not + // getting the type wrong means a temporary register and two bit cast instructions will be spawned + return ZpIR::DataType::F32; + } + return m_irGenContext.irBuilder->getRegType(r); + } + else if (sel.isLiteral()) + { + return _guessTypeFromConstantValue(literalData[instrOP2->getSrc0Chan()]); + } + else + assert_dbg(); + return ZpIR::DataType::S32; + }; + + auto getOp0Reg = [&](const LatteClauseInstruction_ALU_OP2* instrOP2, ZpIR::DataType type) -> ZpIR::IRReg + { + // todo - pass type hint, so internally correct type is used if register needs to be created + ZpIR::IRReg r = loadALUOperand(instrOP2->getSrc0Sel(), instrOP2->getSrc0Chan(), instrOP2->isSrc0Neg(), false, instrOP2->isSrc0Rel(), instrOP2->getIndexMode(), literalData, type, true); + // make sure type matches with 'type' (loadALUOperand should convert) + cemu_assert_debug(irBuilder->getRegType(r) == type); + return r; + }; + + auto getOp1Reg = [&](const LatteClauseInstruction_ALU_OP2* instrOP2, ZpIR::DataType type) -> ZpIR::IRReg + { + // todo - pass type hint, so internally correct type is used if register needs to be created + ZpIR::IRReg r = loadALUOperand(instrOP2->getSrc1Sel(), instrOP2->getSrc1Chan(), instrOP2->isSrc1Neg(), false, instrOP2->isSrc1Rel(), instrOP2->getIndexMode(), literalData, type, true); + // make sure type matches with 'type' (loadALUOperand should convert) + cemu_assert_debug(irBuilder->getRegType(r) == type); + return r; + }; + + auto getResultReg = [&](uint8 aluUnit, const LatteClauseInstruction_ALU_OP2* instrOP2, ZpIR::DataType type) -> ZpIR::IRReg + { + // create output register + ZpIR::IRReg r = m_irGenContext.irBuilder->createReg(type); + + cemu_assert_debug(instrOP2->getDestClamp() == 0); // todo + cemu_assert_debug(instrOP2->getDestRel() == 0); // todo + cemu_assert_debug(instrOP2->getOMod() == 0); // todo + + if (instrOP2->getWriteMask()) + { + // output to GPR + m_irGenContext.activeVars.setAfterGroup(aluUnit, instrOP2->getDestGpr(), instrOP2->getDestElem(), r); + } + else + { + // output only to PV/PS + assert_dbg(); + } + // output to PV/PS + // todo + + // check for disabled destination GPR? + + // also assign PV/PS + // todo + + //ZpIR::IRReg r; + //if (m_irGenContext.activeVars.get(gprIndex, channel, r)) + // return r; + + //// if GPR not present then create import for it + //r = m_irGenContext.irBuilder->createReg(typeHint); + //m_irGenContext.irBuilder->addImport(r, 0x12345678); + + return r; + }; + + for (sint32 aluUnitIndex = 0; aluUnitIndex < 5; aluUnitIndex++) + { + const LatteClauseInstruction_ALU* instr = aluUnit[aluUnitIndex]; + if (instr == nullptr) + continue; + if (instr->isOP3()) + { + assert_dbg(); + } + else + { + auto opcode2 = instr->getOP2Code(); + auto instrOP2 = instr->getOP2Instruction(); + + // prepare operands, load them into IRVars if they aren't already + uint8 indexMode = instrOP2->getIndexMode(); + + switch (opcode2) + { + case LatteClauseInstruction_ALU::OPCODE_OP2::MUL: + case LatteClauseInstruction_ALU::OPCODE_OP2::MUL_IEEE: + { + // how to implement this with least amount of copy paste and still having very good performance? + // maybe use lambdas? Or functions? + irBuilder->emit_RRR(ZpIR::IR::OpCode::MUL, getResultReg(aluUnitIndex, instrOP2, ZpIR::DataType::F32), getOp0Reg(instrOP2, ZpIR::DataType::F32), getOp1Reg(instrOP2, ZpIR::DataType::F32)); + break; + } + case LatteClauseInstruction_ALU::OPCODE_OP2::MOV: + { + // MOV is type-agnostic, but some flags might make it apply float operations + ZpIR::DataType guessedType = getMOVSourceType(instrOP2); + + irBuilder->emit_RR(ZpIR::IR::OpCode::MOV, getResultReg(aluUnitIndex, instrOP2, guessedType), getOp0Reg(instrOP2, guessedType)); + break; + } + case LatteClauseInstruction_ALU::OPCODE_OP2::DOT4: + { + // reduction opcode + // must be mirrored to .xyzw units + cemu_assert_debug(aluUnitIndex == 0); + cemu_assert_debug(aluUnit[0]->getOP2Code() == aluUnit[1]->getOP2Code()); + cemu_assert_debug(aluUnit[1]->getOP2Code() == aluUnit[2]->getOP2Code()); + cemu_assert_debug(aluUnit[2]->getOP2Code() == aluUnit[3]->getOP2Code()); + + auto unit_x = instrOP2; + auto unit_y = aluUnit[1]->getOP2Instruction(); + auto unit_z = aluUnit[2]->getOP2Instruction(); + auto unit_w = aluUnit[3]->getOP2Instruction(); + + cemu_assert_debug(unit_x->getDestClamp() == false); + cemu_assert_debug(unit_x->getOMod() == 0); + cemu_assert_debug(unit_x->getDestRel() == false); + + ZpIR::IRReg productX = irBuilder->emit_RRR(ZpIR::IR::OpCode::MUL, ZpIR::DataType::F32, getOp0Reg(unit_x, ZpIR::DataType::F32), getOp1Reg(unit_x, ZpIR::DataType::F32)); + ZpIR::IRReg productY = irBuilder->emit_RRR(ZpIR::IR::OpCode::MUL, ZpIR::DataType::F32, getOp0Reg(unit_y, ZpIR::DataType::F32), getOp1Reg(unit_y, ZpIR::DataType::F32)); + ZpIR::IRReg productZ = irBuilder->emit_RRR(ZpIR::IR::OpCode::MUL, ZpIR::DataType::F32, getOp0Reg(unit_z, ZpIR::DataType::F32), getOp1Reg(unit_z, ZpIR::DataType::F32)); + ZpIR::IRReg productW = irBuilder->emit_RRR(ZpIR::IR::OpCode::MUL, ZpIR::DataType::F32, getOp0Reg(unit_w, ZpIR::DataType::F32), getOp1Reg(unit_w, ZpIR::DataType::F32)); + + ZpIR::IRReg sum = irBuilder->emit_RRR(ZpIR::IR::OpCode::ADD, ZpIR::DataType::F32, productX, productY); + sum = irBuilder->emit_RRR(ZpIR::IR::OpCode::ADD, ZpIR::DataType::F32, sum, productZ); + sum = irBuilder->emit_RRR(ZpIR::IR::OpCode::ADD, ZpIR::DataType::F32, sum, productW); + + // assign result + if (unit_x->getWriteMask()) + m_irGenContext.activeVars.setAfterGroup(0, unit_x->getDestGpr(), unit_x->getDestElem(), sum); + if (unit_y->getWriteMask()) + m_irGenContext.activeVars.setAfterGroup(1, unit_y->getDestGpr(), unit_y->getDestElem(), sum); + if (unit_z->getWriteMask()) + m_irGenContext.activeVars.setAfterGroup(2, unit_z->getDestGpr(), unit_z->getDestElem(), sum); + if (unit_w->getWriteMask()) + m_irGenContext.activeVars.setAfterGroup(3, unit_w->getDestGpr(), unit_w->getDestElem(), sum); + + // also set result in PV.x + m_irGenContext.activeVars.setAfterGroupPVPS(0, sum); + // todo - do we need to update the other units? + + aluUnitIndex += 3; + continue;; + } + default: + assert_dbg(); + } + + // handle dest clamp + if (instrOP2->getDestClamp()) + { + assert_dbg(); + } + + //uint32 src0Sel = (aluWord0 >> 0) & 0x1FF; // source selection + //uint32 src1Sel = (aluWord0 >> 13) & 0x1FF; + //uint32 src0Rel = (aluWord0 >> 9) & 0x1; // relative addressing mode + //uint32 src1Rel = (aluWord0 >> 22) & 0x1; + //uint32 src0Chan = (aluWord0 >> 10) & 0x3; // component selection x/y/z/w + //uint32 src1Chan = (aluWord0 >> 23) & 0x3; + //uint32 src0Neg = (aluWord0 >> 12) & 0x1; // negate input + //uint32 src1Neg = (aluWord0 >> 25) & 0x1; + //uint32 indexMode = (aluWord0 >> 26) & 7; + //uint32 predSel = (aluWord0 >> 29) & 3; + + //uint32 src0Abs = (aluWord1 >> 0) & 1; + //uint32 src1Abs = (aluWord1 >> 1) & 1; + //uint32 updateExecuteMask = (aluWord1 >> 2) & 1; + //uint32 updatePredicate = (aluWord1 >> 3) & 1; + //uint32 writeMask = (aluWord1 >> 4) & 1; + //uint32 omod = (aluWord1 >> 5) & 3; + + //uint32 destGpr = (aluWord1 >> 21) & 0x7F; + //uint32 destRel = (aluWord1 >> 28) & 1; + //uint32 destElem = (aluWord1 >> 29) & 3; + //uint32 destClamp = (aluWord1 >> 31) & 1; + + } + } + // update IR vars with outputs from group + m_irGenContext.activeVars.applyDelayedAfterGroup(); +} + +void LatteTCGenIR::processCF_ALU(const LatteCFInstruction_ALU& cfInstruction) +{ + uint32 aluAddr = cfInstruction.getField_ADDR(); + uint32 aluCount = cfInstruction.getField_COUNT(); + + const uint32* clauseCode = m_ctx.programData + aluAddr * 2; + uint32 clauseLength = aluCount; + + const LatteClauseInstruction_ALU* aluCode = (const LatteClauseInstruction_ALU*)clauseCode; + + const LatteClauseInstruction_ALU* aluUnit[5] = {}; + + const LatteClauseInstruction_ALU* instr = aluCode; + const LatteClauseInstruction_ALU* instrLast = aluCode + clauseLength; + + // process instructions in groups + uint8 literalMask = 0; + while (instr < instrLast) + { + if (instr->isOP3()) + { + assert_dbg(); + } + else + { + LatteClauseInstruction_ALU::OPCODE_OP2 opcode2 = instr->getOP2Code(); + const LatteClauseInstruction_ALU_OP2* op = instr->getOP2Instruction(); + uint32 unitIndex = 0; + if (op->isTranscedentalUnit()) + unitIndex = 4; + else + { + unitIndex = op->getDestElem(); + if (aluUnit[unitIndex]) // unit already occupied, use transcendental unit instead + unitIndex = 4; + } + cemu_assert_debug(!aluUnit[unitIndex]); // unit already used + aluUnit[unitIndex] = op; + + // check for literal access + if (op->getSrc0Sel().isLiteral()) + literalMask |= (op->getSrc0Chan() >= 2 ? 2 : 1); + if (op->getSrc1Sel().isLiteral()) + literalMask |= (op->getSrc1Chan() >= 2 ? 2 : 1); + } + + if (instr->isLastInGroup()) + { + // emit code for group + // extract literal constants + const uint32* literalData = nullptr; + if (literalMask) + { + literalData = (const uint32*)(instr + 1); + if (literalMask & 2) + instr += 2; + else + instr += 1; + if ((instr + 1) > instrLast) + assert_dbg(); // out of bounds + } + // generate code for group + emitALUGroup(aluUnit, literalData); + // reset group + std::fill(aluUnit, aluUnit + 5, nullptr); + literalMask = 0; + } + instr++; + } + if (aluUnit[0] || aluUnit[1] || aluUnit[2] || aluUnit[3] || aluUnit[4]) + assert_dbg(); +} + +void LatteTCGenIR::processCF_EXPORT(const LatteCFInstruction_EXPORT_IMPORT& cfInstruction) +{ + auto exportType = cfInstruction.getField_TYPE(); + + cemu_assert_debug(cfInstruction.getField_BURST_COUNT() == 1); // todo + + uint32 arrayBase = cfInstruction.getField_ARRAY_BASE(); + + cemu_assert_debug(cfInstruction.isEncodingBUF() == false); // todo + + LatteCFInstruction_EXPORT_IMPORT::COMPSEL sel[4]; + sel[0] = cfInstruction.getSwizField_SEL_X(); + sel[1] = cfInstruction.getSwizField_SEL_Y(); + sel[2] = cfInstruction.getSwizField_SEL_Z(); + sel[3] = cfInstruction.getSwizField_SEL_W(); + + uint32 sourceGPR = cfInstruction.getField_RW_GPR(); + + ZpIR::DataType typeHint; + if (exportType == LatteCFInstruction_EXPORT_IMPORT::EXPORT_TYPE::POSITION) + typeHint = ZpIR::DataType::F32; + else if (exportType == LatteCFInstruction_EXPORT_IMPORT::EXPORT_TYPE::PARAMETER) + { + // todo - determine correct type for parameter + typeHint = ZpIR::DataType::F32; + } + else + assert_dbg(); + + // get xyzw registers + ZpIR::IRReg regArray[4]; + size_t regExportCount = 0; // number of exported registers/channels, number of valid regArray entries + for (size_t i = 0; i < 4; i++) + { + switch (sel[i]) + { + case LatteCFInstruction_EXPORT_IMPORT::COMPSEL::X: + case LatteCFInstruction_EXPORT_IMPORT::COMPSEL::Y: + case LatteCFInstruction_EXPORT_IMPORT::COMPSEL::Z: + case LatteCFInstruction_EXPORT_IMPORT::COMPSEL::W: + { + uint32 channelIndex = (uint32)sel[i]; + regArray[regExportCount] = getTypedIRRegFromGPRElement(sourceGPR, channelIndex, typeHint); + regExportCount++; + break; + } + default: + { + assert_dbg(); + break; + } + } + //ZpIR::IRReg r; + //if (m_irGenContext.activeVars.get(gprIndex, channel, r)) + // return r; + } + + //ZpIR::LocationSymbolName exportSymbolName; + ZpIR::ShaderSubset::ShaderExportLocation loc; + + if (exportType == LatteCFInstruction_EXPORT_IMPORT::EXPORT_TYPE::POSITION) + { + loc.SetPosition(); + } + else if (exportType == LatteCFInstruction_EXPORT_IMPORT::EXPORT_TYPE::PARAMETER) + { + loc.SetOutputAttribute(arrayBase); + //exportSymbolName = 0x20000 + arrayBase; + } + else + { + // todo + assert_dbg(); + } + + cemu_assert_debug(regExportCount == 4); // todo - encode channel mask (e.g. xyz, xw, w, etc.) into export symbol name + + m_irGenContext.irBuilder->emit_EXPORT(loc, std::span(regArray, regArray + regExportCount)); + +} diff --git a/src/Cafe/HW/MMU/MMU.cpp b/src/Cafe/HW/MMU/MMU.cpp new file mode 100644 index 00000000..3982d8d8 --- /dev/null +++ b/src/Cafe/HW/MMU/MMU.cpp @@ -0,0 +1,536 @@ +#include "Cafe/HW/MMU/MMU.h" +#include "Cafe/GraphicPack/GraphicPack2.h" +#include "util/MemMapper/MemMapper.h" +#include <wx/msgdlg.h> +#include "config/ActiveSettings.h" + +uint8* memory_base = NULL; // base address of the reserved 4GB space +uint8* memory_elfCodeArena = NULL; + +void checkMemAlloc(void* result) +{ + if (result == nullptr) + assert_dbg(); +} + +void memory_initPhysicalLayout() +{ + assert_dbg(); + // todo - rewrite this using new MemMapper and MMU tables + //memory_base = (uint8*)VirtualAlloc(NULL, 0x100000000ULL, MEM_RESERVE, PAGE_READWRITE); + //VirtualFree(memory_base, 0, MEM_RELEASE); + + //// todo - figure out all the ranges and allocate them properly + + //// allocate memory for the kernel + ////checkMemAlloc(VirtualAlloc(memory_base + 0x08000000, 1024*1024*2, MEM_COMMIT, PAGE_READWRITE)); + //// allocate memory for bootrom + //checkMemAlloc(VirtualAlloc(memory_base + 0x00000000, 1024*16, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE)); + + + //// allocate memory at 0x016FFFFC (is this some sort of register interface or maybe just temporary storage?) + //checkMemAlloc(VirtualAlloc(memory_base + 0x016FF000, 0x1000, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); + + //// temporary storage for bootrom copy + //checkMemAlloc(VirtualAlloc(memory_base + 0x016c0000, 0x4000 + 0x4000, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); + //// 0x016c0000 + + //// L2 + //checkMemAlloc(VirtualAlloc(memory_base + 0xE0000000, 1024 * 16, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); + + //// kernel memory + //// currently it is unknown if this is it's own physical memory region or if this is mapped somehow + //// considering the ancast is never copied here and no memory mapping is setup it seems like a hardwired mirror to 0x08000000? + ////checkMemAlloc(VirtualAlloc(memory_base + 0xFFE00000, 0x180000, MEM_COMMIT, PAGE_READWRITE)); + //HANDLE hKernelMem = CreateFileMappingA( + // INVALID_HANDLE_VALUE, // use paging file + // NULL, // default security + // PAGE_READWRITE, // read/write access + // 0, // maximum object size (high-order DWORD) + // 1024 * 1024 * 2, // maximum object size (low-order DWORD) + // "kernelMem08000000"); // name of mapping object + // + //checkMemAlloc(MapViewOfFileEx(hKernelMem, FILE_MAP_ALL_ACCESS, 0, 0, 1024 * 1024 * 2, memory_base + 0x08000000)); + //checkMemAlloc(MapViewOfFileEx(hKernelMem, FILE_MAP_ALL_ACCESS, 0, 0, 1024 * 1024 * 2, memory_base + 0xFFE00000)); + + //// IOSU->PPC bootParamBlock + //checkMemAlloc(VirtualAlloc(memory_base + 0x01FFF000, 0x1000, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); + + //// used as dynamic kernel memory? + //checkMemAlloc(VirtualAlloc(memory_base + 0x1C000000, 0x01000000, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); + + //// mapped by kernel to FF200000 (loader.elf?) + //checkMemAlloc(VirtualAlloc(memory_base + 0x1B800000, 0x00800000, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +} + +std::vector<struct MMURange*> g_mmuRanges; + +std::vector<MMURange*> memory_getMMURanges() +{ + return g_mmuRanges; +} + +MMURange* memory_getMMURangeByAddress(MPTR address) +{ + for (auto& itr : g_mmuRanges) + { + if (address >= itr->getBase() && address < itr->getEnd()) + return itr; + } + return nullptr; +} + +MMURange::MMURange(const uint32 baseAddress, const uint32 size, MMU_MEM_AREA_ID areaId, const std::string_view name, MFLAG flags) : baseAddress(baseAddress), size(size), initSize(size), areaId(areaId), name(name), flags(flags) +{ + g_mmuRanges.emplace_back(this); +} + +void MMURange::mapMem() +{ + cemu_assert_debug(!m_isMapped); + if (MemMapper::AllocateMemory(memory_base + baseAddress, size, MemMapper::PAGE_PERMISSION::P_RW, true) == nullptr) + { + std::string errorMsg = fmt::format("Unable to allocate {} memory", name); + wxMessageBox(errorMsg.c_str(), "Error", wxOK | wxCENTRE | wxICON_ERROR); + #if BOOST_OS_WINDOWS + ExitProcess(-1); + #else + exit(-1); + #endif + } + m_isMapped = true; +} + +void MMURange::unmapMem() +{ + cemu_assert_debug(false); + m_isMapped = false; +} + +MMURange mmuRange_LOW0 { 0x00010000, 0x000F0000, MMU_MEM_AREA_ID::CODE_LOW0, "CODE_LOW0" }; // code cave (Cemuhook) +MMURange mmuRange_TRAMPOLINE_AREA { 0x00E00000, 0x00200000, MMU_MEM_AREA_ID::CODE_TRAMPOLINE, "TRAMPOLINE_AREA" }; // code area for trampolines and imports +MMURange mmuRange_CODECAVE { 0x01800000, 0x00400000, MMU_MEM_AREA_ID::CODE_CAVE, "CODECAVE" }; // code cave area (4MiB) +MMURange mmuRange_TEXT_AREA { 0x02000000, 0x0C000000, MMU_MEM_AREA_ID::CODE_MAIN, "TEXT_AREA" }; // module text sections go here (0x02000000 to 0x10000000, 224MiB) +MMURange mmuRange_CEMU_AREA { 0x0E000000, 0x02000000, MMU_MEM_AREA_ID::CEMU_PRIVATE, "CEMU_AREA", MMURange::MFLAG::FLAG_MAP_EARLY }; // Cemu-only, 32MiB. Should be allocated early for SysAllocator +MMURange mmuRange_MEM2 { 0x10000000, 0x40000000, MMU_MEM_AREA_ID::MEM2_DATA, "MEM2" }; // main memory area (1GB) +MMURange mmuRange_OVERLAY_AREA { 0xA0000000, 0x1C000000, MMU_MEM_AREA_ID::OVERLAY, "OVERLAY_AREA", MMURange::MFLAG::FLAG_OPTIONAL }; // has to be requested, 448MiB +MMURange mmuRange_FGBUCKET { 0xE0000000, 0x04000000, MMU_MEM_AREA_ID::FGBUCKET, "FGBUCKET" }; // foreground bucket (64MiB) +MMURange mmuRange_TILINGAPERTURE { 0xE8000000, 0x02000000, MMU_MEM_AREA_ID::TILING_APERATURE, "TILINGAPERTURE" }; // tiling aperture +MMURange mmuRange_MEM1 { 0xF4000000, 0x02000000, MMU_MEM_AREA_ID::MEM1, "MEM1" }; // 32MiB +MMURange mmuRange_RPLLOADER { 0xF6000000, 0x02000000, MMU_MEM_AREA_ID::RPLLOADER, "RPLLOADER_AREA" }; // shared with RPLLoader +MMURange mmuRange_SHARED_AREA { 0xF8000000, 0x02000000, MMU_MEM_AREA_ID::SHAREDDATA, "SHARED_AREA", MMURange::MFLAG::FLAG_MAP_EARLY }; // 32MiB, Cemuhook accesses this memory region at boot +MMURange mmuRange_CORE0_LC { 0xFFC00000, 0x00005000, MMU_MEM_AREA_ID::CPU_LC0, "CORE0_LC" }; // locked L2 cache of core 0 +MMURange mmuRange_CORE1_LC { 0xFFC40000, 0x00005000, MMU_MEM_AREA_ID::CPU_LC1, "CORE1_LC" }; // locked L2 cache of core 1 +MMURange mmuRange_CORE2_LC { 0xFFC80000, 0x00005000, MMU_MEM_AREA_ID::CPU_LC2, "CORE2_LC" }; // locked L2 cache of core 2 +MMURange mmuRange_HIGHMEM { 0xFFFFF000, 0x00001000, MMU_MEM_AREA_ID::CPU_PER_CORE, "PER-CORE" }; // per-core memory? Used by coreinit and PPC kernel to store core context specific data (like current thread ptr). We dont use it but Project Zero has a bug where it writes a byte at 0xfffffffe thus this memory range needs to be writable + +void memory_init() +{ + // reserve a continous range of 4GB + if(!memory_base) + memory_base = (uint8*)MemMapper::ReserveMemory(nullptr, (size_t)0x100000000, MemMapper::PAGE_PERMISSION::P_RW); + if( !memory_base ) + { + debug_printf("memory_init(): Unable to reserve 4GB of memory\n"); + debugBreakpoint(); + wxMessageBox("Unable to reserve 4GB of memory\n", "Error", wxOK | wxCENTRE | wxICON_ERROR); + exit(-1); + } + for (auto& itr : g_mmuRanges) + { + if (itr->isMappedEarly()) + itr->mapMem(); + } +} + +void memory_mapForCurrentTitle() +{ + for (auto& itr : g_mmuRanges) + if(!itr->isMapped()) + itr->resetConfig(); + // expand ranges + auto gfxPackMappings = GraphicPack2::GetActiveRAMMappings(); + for (auto& mapping : gfxPackMappings) + { + MMURange* mmuRange = nullptr; + for (auto& itr : g_mmuRanges) + { + if (itr->getBase() == mapping.first) + { + mmuRange = itr; + break; + } + } + if (!mmuRange) + { + cemuLog_log(LogType::Force, fmt::format("Graphic pack error: Unable to apply modified RAM mapping {:08x}-{:08x}. Start address must match one of the existing MMU ranges:", mapping.first, mapping.second)); + for (auto& itr : g_mmuRanges) + { + if(itr->isMapped()) + continue; + cemuLog_log(LogType::Force, fmt::format("{:08x}-{:08x} ({:})", itr->getBase(), itr->getEnd(), itr->getName())); + } + continue; + } + // make sure the new range isn't overlapping with anything + bool isOverlapping = false; + for (auto& itr : g_mmuRanges) + { + if(itr == mmuRange) + continue; + if (mapping.first < itr->getEnd() && mapping.second > itr->getBase()) + { + cemuLog_log(LogType::Force, fmt::format("Graphic pack error: Unable to apply modified memory range {:08x}-{:08x} since it is overlapping with {:08x}-{:08x} ({:})", mapping.first, mapping.second, itr->getBase(), itr->getEnd(), itr->getName())); + isOverlapping = true; + } + } + if(isOverlapping) + continue; + mmuRange->setEnd(mapping.second); + } + + for (auto& itr : g_mmuRanges) + { + if (!itr->isOptional() && !itr->isMappedEarly()) + itr->mapMem(); + } +} + +void memory_logModifiedMemoryRanges() +{ + auto gfxPackMappings = GraphicPack2::GetActiveRAMMappings(); + for (auto& mapping : gfxPackMappings) + { + MMURange* mmuRange = nullptr; + for (auto& itr : g_mmuRanges) + { + if (itr->getBase() == mapping.first) + { + mmuRange = itr; + break; + } + } + if (!mmuRange) + continue; + sint32 extraMem = (sint32)mapping.second - (sint32)(mmuRange->getBase() + mmuRange->getInitSize()); + extraMem = (extraMem + 1023) / 1024; + std::string memAmountStr; + if (extraMem >= 8 * 1024 * 1024) + memAmountStr = fmt::format("{:+}MiB", (extraMem + 1023) / 1024); + else + memAmountStr = fmt::format("{:+}KiB", extraMem); + cemuLog_log(LogType::Force, fmt::format("Graphic pack: Using modified RAM mapping {:08x}-{:08x} ({})", mapping.first, mapping.second, memAmountStr)); + } +} + +void memory_enableOverlayArena() +{ + if (mmuRange_OVERLAY_AREA.isMapped()) + return; + mmuRange_OVERLAY_AREA.mapMem(); +} + +void memory_enableHBLELFCodeArea() +{ + if (memory_elfCodeArena != NULL) + return; + memory_elfCodeArena = (uint8*)MemMapper::AllocateMemory(memory_base + 0x00800000, 0x00800000, MemMapper::PAGE_PERMISSION::P_RW, true); + if (memory_elfCodeArena == NULL) + { + debug_printf("memory_enableHBLELFCodeArea(): Unable to allocate memory for ELF arena\n"); + debugBreakpoint(); + } +} + +bool memory_isAddressRangeAccessible(MPTR virtualAddress, uint32 size) +{ + for (auto& itr : g_mmuRanges) + { + if(!itr->isMapped()) + continue; + if (virtualAddress >= itr->getBase() && virtualAddress < itr->getEnd()) + { + uint32 remainingSize = itr->getEnd() - virtualAddress; + return size <= remainingSize && itr->isMapped(); + } + } + return false; +} + +uint32 memory_virtualToPhysical(uint32 virtualOffset) +{ + // currently we map virtual to physical space 1:1 + return virtualOffset; +} + +uint32 memory_physicalToVirtual(uint32 physicalOffset) +{ + // currently we map virtual to physical space 1:1 + return physicalOffset; +} + +uint8* memory_getPointerFromPhysicalOffset(uint32 physicalOffset) +{ + return memory_base + physicalOffset; +} + +uint32 memory_getVirtualOffsetFromPointer(void* ptr) +{ + if( !ptr ) + return MPTR_NULL; + return (uint32)((uint8*)ptr - (uint8*)memory_base); +} + +uint8* memory_getPointerFromVirtualOffset(uint32 virtualOffset) +{ + return memory_base + virtualOffset; +} + +uint8* memory_getPointerFromVirtualOffsetAllowNull(uint32 virtualOffset) +{ + if( virtualOffset == MPTR_NULL ) + return nullptr; + return memory_getPointerFromVirtualOffset(virtualOffset); +} + +void memory_writeU32Direct(uint32 address, uint32 v) +{ + *(uint32*)(memory_getPointerFromVirtualOffset(address)) = CPU_swapEndianU32(v); +} + +// write access +void memory_writeDouble(uint32 address, double vf) +{ + uint64 v = *(uint64*)&vf; + uint32 v1 = v&0xFFFFFFFF; + uint32 v2 = v>>32; + uint8* ptr = memory_getPointerFromVirtualOffset(address); + *(uint32*)(ptr+4) = CPU_swapEndianU32(v1); + *(uint32*)(ptr+0) = CPU_swapEndianU32(v2); +} + +void memory_writeFloat(uint32 address, float vf) +{ + uint32 v = *(uint32*)&vf; + *(uint32*)(memory_getPointerFromVirtualOffset(address)) = CPU_swapEndianU32(v); +} + +void memory_writeU32(uint32 address, uint32 v) +{ + *(uint32*)(memory_getPointerFromVirtualOffset(address)) = CPU_swapEndianU32(v); +} + +void memory_writeU64Slow(uint32 address, uint64 v) +{ + memory_writeU32(address+0, (v>>32)&0xFFFFFFFF); + memory_writeU32(address+4, (v)&0xFFFFFFFF); +} + +void memory_writeU64(uint32 address, uint64 v) +{ + *(uint64*)(memory_getPointerFromVirtualOffset(address)) = CPU_swapEndianU64(v); +} + +void memory_writeU16(uint32 address, uint16 v) +{ + *(uint16*)(memory_getPointerFromVirtualOffset(address)) = CPU_swapEndianU16(v); +} + +void memory_writeU8(uint32 address, uint8 v) +{ + *(uint8*)(memory_getPointerFromVirtualOffset(address)) = v; +} + +// read access + +double memory_readDouble(uint32 address) +{ + uint32 v[2]; + v[1] = *(uint32*)(memory_getPointerFromVirtualOffset(address)); + v[0] = *(uint32*)(memory_getPointerFromVirtualOffset(address)+4); + v[0] = CPU_swapEndianU32(v[0]); + v[1] = CPU_swapEndianU32(v[1]); + return *(double*)v; +} + +float memory_readFloat(uint32 address) +{ + uint32 v = *(uint32*)(memory_getPointerFromVirtualOffset(address)); + v = CPU_swapEndianU32(v); + return *(float*)&v; +} + +uint64 memory_readU64(uint32 address) +{ + uint64 v = *(uint64*)(memory_getPointerFromVirtualOffset(address)); + return CPU_swapEndianU64(v); +} + +uint32 memory_readU32(uint32 address) +{ + uint32 v = *(uint32*)(memory_getPointerFromVirtualOffset(address)); + return CPU_swapEndianU32(v); +} + +uint32 memory_readU32Direct(uint32 address) +{ + uint32 v = *(uint32*)(memory_getPointerFromVirtualOffset(address)); + return CPU_swapEndianU32(v); +} + +uint16 memory_readU16(uint32 address) +{ + uint16 v = *(uint16*)(memory_getPointerFromVirtualOffset(address)); + return CPU_swapEndianU16(v); +} + +uint8 memory_readU8(uint32 address) +{ + return *(uint8*)(memory_getPointerFromVirtualOffset(address)); +} + +__declspec(dllexport) void* memory_getBase() +{ + return memory_base; +} + +void memory_writeDumpFile(uint32 startAddr, uint32 size, const fs::path& path) +{ + fs::path filePath = path; + filePath /= fmt::format("{:08x}.bin", startAddr); + FileStream* fs = FileStream::createFile2(filePath); + if (fs) + { + fs->writeData(memory_base + startAddr, size); + delete fs; + } +} + +void memory_createDump() +{ + const uint32 pageSize = MemMapper::GetPageSize(); + fs::path path = ActiveSettings::GetPath("dump/ramDump{:}", (uint32)time(nullptr)); + fs::create_directories(path); + + for (auto& itr : g_mmuRanges) + { + if(!itr->isMapped()) + continue; + memory_writeDumpFile(itr->getBase(), itr->getSize(), path); + } +} + +namespace MMU +{ + // MMIO access handler + // located in address region 0x0C000000 - 0x0E000000 + // there seem to be multiple subregions + special meanings for some address bits maybe? + // Try to figure this out. We know these regions (in Wii U mode): + // 0x0C000000 (the old GC register interface?) + // 0x0D000000 (new Wii U stuff?) + + std::unordered_map<PAddr, MMIOFuncWrite32>* g_mmioHandlerW32{}; + std::unordered_map<PAddr, MMIOFuncWrite16>* g_mmioHandlerW16{}; + std::unordered_map<PAddr, MMIOFuncRead32>* g_mmioHandlerR32{}; + std::unordered_map<PAddr, MMIOFuncRead16>* g_mmioHandlerR16{}; + + void _initHandlers() + { + if (g_mmioHandlerW32) + return; + g_mmioHandlerW32 = new std::unordered_map<PAddr, MMIOFuncWrite32>(); + g_mmioHandlerW16 = new std::unordered_map<PAddr, MMIOFuncWrite16>(); + g_mmioHandlerR32 = new std::unordered_map<PAddr, MMIOFuncRead32>(); + g_mmioHandlerR16 = new std::unordered_map<PAddr, MMIOFuncRead16>(); + } + + PAddr _MakeMMIOAddress(MMIOInterface interfaceLocation, uint32 relativeAddress) + { + PAddr addr = 0; + if (interfaceLocation == MMIOInterface::INTERFACE_0C000000) + addr = 0x0C000000; + else if (interfaceLocation == MMIOInterface::INTERFACE_0D000000) + addr = 0x0D000000; + else + assert_dbg(); + return addr + relativeAddress; + } + + void RegisterMMIO_W32(MMIOInterface interfaceLocation, uint32 relativeAddress, MMIOFuncWrite32 ptr) + { + _initHandlers(); + g_mmioHandlerW32->emplace(_MakeMMIOAddress(interfaceLocation, relativeAddress), ptr); + } + + void RegisterMMIO_W16(MMIOInterface interfaceLocation, uint32 relativeAddress, MMIOFuncWrite16 ptr) + { + _initHandlers(); + g_mmioHandlerW16->emplace(_MakeMMIOAddress(interfaceLocation, relativeAddress), ptr); + } + + void RegisterMMIO_R32(MMIOInterface interfaceLocation, uint32 relativeAddress, MMIOFuncRead32 ptr) + { + _initHandlers(); + PAddr addr = _MakeMMIOAddress(interfaceLocation, relativeAddress); + g_mmioHandlerR32->emplace(addr, ptr); + } + + void RegisterMMIO_R16(MMIOInterface interfaceLocation, uint32 relativeAddress, MMIOFuncRead16 ptr) + { + _initHandlers(); + g_mmioHandlerR16->emplace(_MakeMMIOAddress(interfaceLocation, relativeAddress), ptr); + } + + void WriteMMIO_32(PAddr address, uint32 value) + { + cemu_assert_debug((address & 0x3) == 0); + auto itr = g_mmioHandlerW32->find(address); + if (itr == g_mmioHandlerW32->end()) + { + //forceLogDebug_printf("[MMU] MMIO write u32 0x%08x from unhandeled address 0x%08x", value, address); + return; + } + return itr->second(address, value); + } + + void WriteMMIO_16(PAddr address, uint16 value) + { + cemu_assert_debug((address & 0x1) == 0); + auto itr = g_mmioHandlerW16->find(address); + if (itr == g_mmioHandlerW16->end()) + { + //forceLogDebug_printf("[MMU] MMIO write u16 0x%04x from unhandeled address 0x%08x", (uint32)value, address); + return; + } + return itr->second(address, value); + } + + + // todo - instead of passing the physical address to Read/WriteMMIO we should pass an interface id and a relative address? This would allow remapping the hardware address (tho we can just unregister + register at different addresses) + + uint16 ReadMMIO_32(PAddr address) + { + cemu_assert_debug((address & 0x3) == 0); + auto itr = g_mmioHandlerR32->find(address); + if(itr == g_mmioHandlerR32->end()) + { + //forceLogDebug_printf("[MMU] MMIO read u32 from unhandeled address 0x%08x", address); + return 0; + } + return itr->second(address); + } + + uint16 ReadMMIO_16(PAddr address) + { + cemu_assert_debug((address & 0x1) == 0); + auto itr = g_mmioHandlerR16->find(address); + if (itr == g_mmioHandlerR16->end()) + { + //forceLogDebug_printf("[MMU] MMIO read u16 from unhandeled address 0x%08x", address); + return 0; + } + return itr->second(address); + } + + +} \ No newline at end of file diff --git a/src/Cafe/HW/MMU/MMU.h b/src/Cafe/HW/MMU/MMU.h new file mode 100644 index 00000000..5169ef7e --- /dev/null +++ b/src/Cafe/HW/MMU/MMU.h @@ -0,0 +1,308 @@ +#pragma once + +DLLEXPORT void memory_init(); +void memory_mapForCurrentTitle(); +void memory_logModifiedMemoryRanges(); + +void memory_enableOverlayArena(); +void memory_enableHBLELFCodeArea(); +uint32 memory_getVirtualOffsetFromPointer(void* ptr); +uint8* memory_getPointerFromVirtualOffset(uint32 virtualOffset); +uint8* memory_getPointerFromVirtualOffsetAllowNull(uint32 virtualOffset); + +uint8* memory_getPointerFromPhysicalOffset(uint32 physicalOffset); + +uint32 memory_virtualToPhysical(uint32 virtualOffset); +uint32 memory_physicalToVirtual(uint32 physicalOffset); + +extern uint8* memory_base; // points to 0x00000000 + +enum class MMU_MEM_AREA_ID +{ + CODE_LOW0, + CODE_TRAMPOLINE, + CODE_CAVE, + CODE_MAIN, + MEM2_DATA, + FGBUCKET, + TILING_APERATURE, + OVERLAY, + MAPABLE_SPACE, + MEM1, + RPLLOADER, + SHAREDDATA, + + CPU_LC0, + CPU_LC1, + CPU_LC2, + CPU_PER_CORE, + + CEMU_PRIVATE, +}; + +struct MMURange +{ + enum MFLAG + { + FLAG_OPTIONAL = (1 << 0), // allocate only on explicit request + FLAG_MAP_EARLY = (1 << 1), // map at Cemu launch, normally memory is mapped when a game is loaded + }; + + MMURange(const uint32 baseAddress, const uint32 size, MMU_MEM_AREA_ID areaId, const std::string_view name, MFLAG flags = (MFLAG)0); + + void mapMem(); + void unmapMem(); + + uint8* getPtr() const + { + cemu_assert_debug(m_isMapped); + return memory_base + baseAddress; + } + + uint32 getBase() const + { + return baseAddress; + } + + // reset to initial parameters + void resetConfig() + { + size = initSize; + } + + void setEnd(uint32 endAddress) + { + cemu_assert_debug(!m_isMapped); + cemu_assert_debug((endAddress & 0xFFF) == 0); + size = endAddress - baseAddress; + } + + // returns offset of last byte + 1 (base + size) + uint32 getEnd() const + { + return baseAddress + size; + } + + uint32 getSize() const + { + return size; + } + + uint32 getInitSize() const + { + return initSize; + } + + std::string_view getName() const + { + return name; + } + + bool containsAddress(uint32 addr) const + { + return addr >= getBase() && addr < getEnd(); + } + + bool isMapped() const { return m_isMapped; }; + bool isOptional() const { return (flags & MFLAG::FLAG_OPTIONAL) != 0; }; + bool isMappedEarly() const { return (flags & MFLAG::FLAG_MAP_EARLY) != 0; }; + + const uint32 baseAddress; + const uint32 initSize; // initial size + const std::string name; + const MFLAG flags; + const MMU_MEM_AREA_ID areaId; + // runtime parameters + uint32 size; + bool m_isMapped{}; +}; + + +extern MMURange mmuRange_LOW0; +extern MMURange mmuRange_TRAMPOLINE_AREA; +extern MMURange mmuRange_CODECAVE; +extern MMURange mmuRange_TEXT_AREA; +extern MMURange mmuRange_MEM2; +extern MMURange mmuRange_CEMU_AREA; +extern MMURange mmuRange_OVERLAY_AREA; +extern MMURange mmuRange_FGBUCKET; +extern MMURange mmuRange_TILINGAPERTURE; +extern MMURange mmuRange_MEM1; +extern MMURange mmuRange_RPLLOADER; +extern MMURange mmuRange_SHARED_AREA; +extern MMURange mmuRange_CORE0_LC; +extern MMURange mmuRange_CORE1_LC; +extern MMURange mmuRange_CORE2_LC; +std::vector<MMURange*> memory_getMMURanges(); +MMURange* memory_getMMURangeByAddress(MPTR address); + +bool memory_isAddressRangeAccessible(MPTR virtualAddress, uint32 size); + +#define MEMORY_PAGE_SIZE (0x20000) + +#define MEMORY_CODELOW0_ADDR (0x00010000) +#define MEMORY_CODELOW0_SIZE (0x000F0000) // ~1MB + +#define MEMORY_CODE_TRAMPOLINE_AREA_ADDR (0x00E00000) // code area for trampolines and imports +#define MEMORY_CODE_TRAMPOLINE_AREA_SIZE (0x00200000) // 2MB + +#define MEMORY_CODECAVEAREA_ADDR (0x01800000) +#define MEMORY_CODECAVEAREA_SIZE (0x00400000) // 4MB + +#define MEMORY_CODEAREA_ADDR (0x02000000) +#define MEMORY_CODEAREA_SIZE (0x0E000000) // 224MB + +#define MEMORY_DATA_AREA_ADDR (0x10000000) +#define MEMORY_DATA_AREA_SIZE (0x40000000) + +#define MEMORY_FGBUCKET_AREA_ADDR (0xE0000000) // actual offset is 0xE0000000 according to PPC kernel +#define MEMORY_FGBUCKET_AREA_SIZE (0x04000000) // 64MB split up into multiple subareas, size is verified with value from PPC kernel + +// move these to rpl loader +#define MEMORY_SDA_SIZE (0x10000) +#define MEMORY_SDA2_SIZE (0x10000) + +//#define MEMORY_SYSTEM_AREA_ADDR (0x90000000) +//#define MEMORY_SYSTEM_AREA_SIZE (0x02000000) // 32MB of memory area that can't be allocated by the game directly - this is emulator specific. + +//#define MEMORY_SYSTEM_AREA_ADDR (0x7C000000) +//#define MEMORY_SYSTEM_AREA_SIZE (0x02000000) // 32MB of memory area that can't be allocated by the game directly - this is emulator specific. +// moved the sys area below 0x80000000. Turns out that some games treat stack/os-object pointers as signed and run into issues if the highest bit is set (e.g. Monster Hunter Frontier G) + +#define MEMORY_TILINGAPERTURE_AREA_ADDR (0xE8000000) +#define MEMORY_TILINGAPERTURE_AREA_SIZE (0x02000000) // 32MB + +#define MEMORY_OVERLAY_AREA_OFFSET (0xA0000000) +#define MEMORY_OVERLAY_AREA_SIZE (448*1024*1024) // 448MB (todo: verify if correct) + +#define MEMORY_MAPABLE_PHYS_AREA_OFFSET (0x80000000) // todo: verify offset +#define MEMORY_MAPABLE_PHYS_AREA_SIZE (32*1024*1024) // todo: verify size +#define MEMORY_MAPABLE_VIRT_AREA_OFFSET (0x70000000) // todo: verify offset +#define MEMORY_MAPABLE_VIRT_AREA_SIZE (32*1024*1024) // todo: verify size + +#define MEMORY_MEM1_AREA_ADDR (0xF4000000) +#define MEMORY_MEM1_AREA_SIZE (0x02000000) // 32MB + +#define MEMORY_RPLLOADER_AREA_ADDR (0xF6000000) // workarea for RPLLoader (normally this is kernel workspace) +#define MEMORY_RPLLOADER_AREA_SIZE (0x02000000) // 32MB + +#define MEMORY_SHAREDDATA_AREA_ADDR (0xF8000000) +#define MEMORY_SHAREDDATA_AREA_SIZE (0x02000000) // 32MB + +static uint16 CPU_swapEndianU16(uint16 v) +{ + //return _byteswap_ushort(v); + return (v>>8)|(v<<8); +} + +#if BOOST_OS_WINDOWS +#define CPU_swapEndianU64(_v) _byteswap_uint64((uint64)(_v)) +#define CPU_swapEndianU32(_v) _byteswap_ulong((uint32)(_v)) +#elif BOOST_OS_LINUX +#define CPU_swapEndianU64(_v) bswap_64((uint64)(_v)) +#define CPU_swapEndianU32(_v) bswap_32((uint32)(_v)) +#endif + +// direct memory access (no hardware interface access) +void memory_writeU32Direct(uint32 address, uint32 v); +uint32 memory_readU32Direct(uint32 address); + +// memory access (includes hardware interface, slower) +void memory_writeDouble(uint32 address, double vf); +void memory_writeFloat(uint32 address, float vf); +void memory_writeU32(uint32 address, uint32 v); +void memory_writeU16(uint32 address, uint16 v); +void memory_writeU8(uint32 address, uint8 v); +void memory_writeU64Slow(uint32 address, uint64 v); +void memory_writeU64(uint32 address, uint64 v); + +double memory_readDouble(uint32 address); +float memory_readFloat(uint32 address); +uint64 memory_readU64(uint32 address); +uint32 memory_readU32(uint32 address); +uint16 memory_readU16(uint32 address); +uint8 memory_readU8(uint32 address); + +void memory_createDump(); + +template<size_t count> +void memory_readBytes(uint32 address, std::array<uint8, count>& buffer) +{ + memcpy(buffer.data(), memory_getPointerFromVirtualOffset(address), count); +} + +template <typename T> T memory_read(uint32 address) +{ + if constexpr(std::is_floating_point<T>::value) + { + if constexpr(sizeof(T) == sizeof(float)) + return memory_readFloat(address); + else + return memory_readDouble(address); + } + else if(std::is_integral<T>::value) + { + if constexpr (sizeof(T) == sizeof(uint8)) + return (T)memory_readU8(address); + else if constexpr (sizeof(T) == sizeof(uint16)) + return (T)memory_readU16(address); + else if constexpr (sizeof(T) == sizeof(uint32)) + return (T)memory_readU32(address); + else if constexpr (sizeof(T) == sizeof(uint64)) + return (T)memory_readU64(address); + } + + debugBreakpoint(); + return {}; +} + +// LLE implementation +void memory_initPhysicalLayout(); + +// updated code +using EAddr = uint32; // effective address +using PAddr = uint32; // physical address + +namespace MMU +{ + using MMIOFuncWrite32 = void (*)(PAddr addr, uint32 value); + using MMIOFuncWrite16 = void (*)(PAddr addr, uint16 value); + + using MMIOFuncRead32 = uint32(*)(PAddr addr); + using MMIOFuncRead16 = uint16(*)(PAddr addr); + + enum class MMIOInterface + { + INTERFACE_0C000000, + INTERFACE_0D000000, + //INTERFACE_0D000000, + }; + + void RegisterMMIO_W16(MMIOInterface interfaceLocation, uint32 relativeAddress, MMIOFuncWrite16 ptr); + void RegisterMMIO_W32(MMIOInterface interfaceLocation, uint32 relativeAddress, MMIOFuncWrite32 ptr); + void RegisterMMIO_R32(MMIOInterface interfaceLocation, uint32 relativeAddress, MMIOFuncRead32 ptr); + void RegisterMMIO_R16(MMIOInterface interfaceLocation, uint32 relativeAddress, MMIOFuncRead16 ptr); + + template<typename TRegType, auto TReadFunc, auto TWriteFunc> + void RegisterMMIO_32(MMIOInterface interfaceLocation, uint32 relativeAddress) + { + RegisterMMIO_W32(interfaceLocation, relativeAddress, + [](PAddr addr, uint32 value) -> void + { + TRegType temp; + temp.setFromRaw(value); + TWriteFunc(addr, temp); + }); + RegisterMMIO_R32(interfaceLocation, relativeAddress, + [](PAddr addr) -> uint32 + { + return TReadFunc(addr).getRawValue(); + }); + } + + void WriteMMIO_32(PAddr address, uint32 value); + void WriteMMIO_16(PAddr address, uint16 value); + uint16 ReadMMIO_32(PAddr address); + uint16 ReadMMIO_16(PAddr address); + +} \ No newline at end of file diff --git a/src/Cafe/HW/SI/SI.cpp b/src/Cafe/HW/SI/SI.cpp new file mode 100644 index 00000000..e36bd868 --- /dev/null +++ b/src/Cafe/HW/SI/SI.cpp @@ -0,0 +1,162 @@ +#include "Cafe/HW/MMU/MMU.h" +#include "Cafe/HW/Common/HwReg.h" +#include "si.h" + +namespace HW_SI +{ + + struct + { + struct + { + HWREG::SICOMCSR sicomcsr{}; + HWREG::SIPOLL sipoll{}; + HWREG::SICOUTBUF outBuf[4]{}; + }registerState; + struct + { + uint8 cmd{}; + uint8 buf[2]{}; + }outputBufferState[4]; + struct + { + bool hasErrorNoResponse{}; + }channelStatus[4]; + }g_si; + + // normally we should call this periodically according to the parameters set in SIPOLL + // but for now we just call it whenever status registers are read + void handlePollUpdate() + { + for (uint32 i = 0; i < 4; i++) + { + // note: Order of EN and VBCPY is from MSB to LSB + bool isEnabled = ((g_si.registerState.sipoll.get_EN() >> (3 - i))&1) != 0; + if (isEnabled) + { + g_si.channelStatus[i].hasErrorNoResponse = true; + } + } + } + + void handleQueuedTransfers() + { + + } + + void flushAllOutputBuffers() + { + for (uint32 i = 0; i < 4; i++) + { + g_si.outputBufferState[i].cmd = g_si.registerState.outBuf[i].get_CMD(); + g_si.outputBufferState[i].buf[0] = g_si.registerState.outBuf[i].get_OUTPUT0(); + g_si.outputBufferState[i].buf[1] = g_si.registerState.outBuf[i].get_OUTPUT1(); + } + } + + /* +0x6400/0x640C/0x6418/0x6424 | SI0COUTBUF - SI3COUTBUF */ + + HWREG::SICOUTBUF SI_COUTBUF_R32(PAddr addr) + { + uint32 joyChannelIndex = (addr & 0xFF) / 0xC; + + cemu_assert_debug(false); + return HWREG::SICOUTBUF(); + } + + void SI_COUTBUF_W32(PAddr addr, HWREG::SICOUTBUF newValue) + { + uint32 joyChannelIndex = (addr & 0xFF) / 0xC; + g_si.registerState.outBuf[joyChannelIndex] = newValue; + } + + /* +0x6430 | SIPOLL */ + + HWREG::SIPOLL SI_POLL_R32(PAddr addr) + { + cemu_assert_debug(false); + return g_si.registerState.sipoll; + } + + void SI_POLL_W32(PAddr addr, HWREG::SIPOLL newValue) + { + g_si.registerState.sipoll = newValue; + } + + /* +0x6434 | SICOMCSR */ + + HWREG::SICOMCSR SI_COMCSR_R32(PAddr addr) + { + //forceLogDebug_printf("Read SICOMCSR"); + return g_si.registerState.sicomcsr; + } + + void SI_COMCSR_W32(PAddr addr, HWREG::SICOMCSR newValue) + { + uint32 unhandledBits = g_si.registerState.sicomcsr.getRawValue() & ~(0x80000000); + cemu_assert_debug(unhandledBits == 0); + // clear transfer complete interrupt + if (newValue.get_TCINT()) + { + g_si.registerState.sicomcsr.set_TCINT(0); + } + + if (newValue.get_TRANSFER_START()) + { + cemu_assert_debug(false); + handleQueuedTransfers(); + } + } + + /* +0x6438 | SISR */ + + HWREG::SISR SI_SR_R32(PAddr addr) + { + handlePollUpdate(); + HWREG::SISR reg; + // no response error + if (g_si.channelStatus[0].hasErrorNoResponse) + reg.set_NOREP0(1); + if (g_si.channelStatus[1].hasErrorNoResponse) + reg.set_NOREP1(1); + if (g_si.channelStatus[2].hasErrorNoResponse) + reg.set_NOREP2(1); + if (g_si.channelStatus[3].hasErrorNoResponse) + reg.set_NOREP3(1); + + // todo - other status fields + + return reg; + } + + void SI_SR_W32(PAddr addr, HWREG::SISR newValue) + { + if (newValue.get_NOREP0()) + g_si.channelStatus[0].hasErrorNoResponse = false; + if (newValue.get_NOREP1()) + g_si.channelStatus[1].hasErrorNoResponse = false; + if (newValue.get_NOREP2()) + g_si.channelStatus[2].hasErrorNoResponse = false; + if (newValue.get_NOREP3()) + g_si.channelStatus[3].hasErrorNoResponse = false; + + + if (newValue.get_WR()) + { + // copies contents of SICOUTBUF to the internal shadow buffers + flushAllOutputBuffers(); + } + } + + void Initialize() + { + MMU::RegisterMMIO_32<HWREG::SICOUTBUF, SI_COUTBUF_R32, SI_COUTBUF_W32>(MMU::MMIOInterface::INTERFACE_0D000000, 0x6400); + MMU::RegisterMMIO_32<HWREG::SICOUTBUF, SI_COUTBUF_R32, SI_COUTBUF_W32>(MMU::MMIOInterface::INTERFACE_0D000000, 0x640C); + MMU::RegisterMMIO_32<HWREG::SICOUTBUF, SI_COUTBUF_R32, SI_COUTBUF_W32>(MMU::MMIOInterface::INTERFACE_0D000000, 0x6418); + MMU::RegisterMMIO_32<HWREG::SICOUTBUF, SI_COUTBUF_R32, SI_COUTBUF_W32>(MMU::MMIOInterface::INTERFACE_0D000000, 0x6424); + + MMU::RegisterMMIO_32<HWREG::SIPOLL, SI_POLL_R32, SI_POLL_W32>(MMU::MMIOInterface::INTERFACE_0D000000, 0x6430); + MMU::RegisterMMIO_32<HWREG::SICOMCSR, SI_COMCSR_R32, SI_COMCSR_W32>(MMU::MMIOInterface::INTERFACE_0D000000, 0x6434); + MMU::RegisterMMIO_32<HWREG::SISR, SI_SR_R32, SI_SR_W32>(MMU::MMIOInterface::INTERFACE_0D000000, 0x6438); + } +} diff --git a/src/Cafe/HW/SI/si.h b/src/Cafe/HW/SI/si.h new file mode 100644 index 00000000..5860ee9b --- /dev/null +++ b/src/Cafe/HW/SI/si.h @@ -0,0 +1,5 @@ + +namespace HW_SI +{ + void Initialize(); +}; \ No newline at end of file diff --git a/src/Cafe/HW/VI/VI.cpp b/src/Cafe/HW/VI/VI.cpp new file mode 100644 index 00000000..0e2a50c5 --- /dev/null +++ b/src/Cafe/HW/VI/VI.cpp @@ -0,0 +1,11 @@ +#include "Cafe/HW/MMU/MMU.h" + + +namespace HW_VI +{ + + RunAtCemuBoot _initVI([]() + { + //MMU::RegisterMMIO_R16(MMU::MMIOInterface::INTERFACE_0C000000, 0x1e0002, VI_UKN1E0002_R16); + }); +} diff --git a/src/Cafe/IOSU/PDM/iosu_pdm.cpp b/src/Cafe/IOSU/PDM/iosu_pdm.cpp new file mode 100644 index 00000000..57e2c61d --- /dev/null +++ b/src/Cafe/IOSU/PDM/iosu_pdm.cpp @@ -0,0 +1,376 @@ +#include "iosu_pdm.h" +#include "config/ActiveSettings.h" +#include "Common/filestream.h" +#include "util/helpers/Semaphore.h" + +#if BOOST_OS_LINUX +// using chrono::year_month_date and other features require a relatively recent stdlibc++ +// to avoid upping the required version we use the STL reference implementation for now +#include "Common/linux/date.h" +namespace chrono_d = date; +#else +namespace chrono_d = std::chrono; +#endif + + +namespace iosu +{ + namespace pdm + { + std::mutex sDiaryLock; + + fs::path GetPDFile(const char* filename) + { + // todo - support for per-account tracking + return ActiveSettings::GetMlcPath(fmt::format("usr/save/system/pdm/80000001/{}", filename)); + } + + void MakeDirectory() + { + fs::path path = GetPDFile("."); + std::error_code ec; + fs::create_directories(path, ec); + } + + chrono_d::year_month_day GetDateFromDayIndex(uint16 dayIndex) + { + chrono_d::sys_days startDateDay(chrono_d::year(2000) / chrono_d::January / chrono_d::day(1)); + chrono_d::sys_days lastPlayedDay = startDateDay + chrono_d::days(dayIndex); + return chrono_d::year_month_day(lastPlayedDay); + } + + uint16 GetTodaysDayIndex() + { + chrono_d::sys_days startDateDay(chrono_d::year(2000) / chrono_d::January / chrono_d::day(1)); + chrono_d::sys_days currentDateDay = chrono_d::floor<chrono_d::days>(std::chrono::system_clock::now()); + return (uint16)(currentDateDay - startDateDay).count(); + } + + struct PlayStatsEntry + { + uint32be titleIdHigh; + uint32be titleIdLow; + uint32be totalMinutesPlayed; + uint16be numTimesLaunched; + uint16be firstLaunchDayIndex; // first time this title was launched + uint16be mostRecentDayIndex; // last time this title was played + uint16be ukn12; // maybe just padding? + }; + + static_assert(sizeof(PlayStatsEntry) == 0x14); + + struct + { + FileStream* fs{}; + uint32be numEntries; + PlayStatsEntry entry[NUM_PLAY_STATS_ENTRIES]; + }PlayStats; + + void CreatePlaystats() + { + PlayStats.fs = FileStream::createFile2(GetPDFile("PlayStats.dat")); + if (!PlayStats.fs) + { + cemuLog_log(LogType::Force, "Unable to open or create PlayStats.dat"); + return; + } + uint32be entryCount = 0; + PlayStats.fs->writeData(&entryCount, sizeof(uint32be)); + PlayStats.fs->writeData(PlayStats.entry, NUM_PLAY_STATS_ENTRIES * sizeof(PlayStatsEntry)); + static_assert((NUM_PLAY_STATS_ENTRIES * sizeof(PlayStatsEntry)) == 0x1400); + } + + void LoadPlaystats() + { + PlayStats.numEntries = 0; + for (size_t i = 0; i < NUM_PLAY_STATS_ENTRIES; i++) + { + auto& e = PlayStats.entry[i]; + memset(&e, 0, sizeof(PlayStatsEntry)); + } + PlayStats.fs = FileStream::openFile2(GetPDFile("PlayStats.dat"), true); + if (!PlayStats.fs) + { + CreatePlaystats(); + return; + } + if (PlayStats.fs->GetSize() != (NUM_PLAY_STATS_ENTRIES * 20 + 4)) + { + delete PlayStats.fs; + PlayStats.fs = nullptr; + cemuLog_log(LogType::Force, "PlayStats.dat malformed"); + // dont delete the existing file in case it could still be salvaged (todo) and instead just dont track play time + return; + } + PlayStats.fs->readData(&PlayStats.numEntries, sizeof(uint32be)); + if (PlayStats.numEntries > NUM_PLAY_STATS_ENTRIES) + PlayStats.numEntries = NUM_PLAY_STATS_ENTRIES; + PlayStats.fs->readData(PlayStats.entry, NUM_PLAY_STATS_ENTRIES * 20); + } + + PlayStatsEntry* PlayStats_GetEntry(uint64 titleId) + { + uint32be titleIdHigh = (uint32)(titleId>>32); + uint32be titleIdLow = (uint32)(titleId & 0xFFFFFFFF); + size_t numEntries = PlayStats.numEntries; + for (size_t i = 0; i < numEntries; i++) + { + if (PlayStats.entry[i].titleIdHigh == titleIdHigh && PlayStats.entry[i].titleIdLow == titleIdLow) + return &PlayStats.entry[i]; + } + return nullptr; + } + + void PlayStats_WriteEntry(PlayStatsEntry* entry, bool writeEntryCount = false) + { + if (!PlayStats.fs) + return; + size_t entryIndex = entry - PlayStats.entry; + cemu_assert(entryIndex < NUM_PLAY_STATS_ENTRIES); + PlayStats.fs->SetPosition(4 + entryIndex * sizeof(PlayStatsEntry)); + if (PlayStats.fs->writeData(entry, sizeof(PlayStatsEntry)) != sizeof(PlayStatsEntry)) + { + cemuLog_log(LogType::Force, "Failed to write to PlayStats.dat"); + return; + } + if (writeEntryCount) + { + uint32be numEntries = PlayStats.numEntries; + PlayStats.fs->SetPosition(0); + PlayStats.fs->writeData(&numEntries, sizeof(uint32be)); + } + } + + PlayStatsEntry* PlayStats_CreateEntry(uint64 titleId) + { + bool entryCountChanged = false; + PlayStatsEntry* newEntry; + if(PlayStats.numEntries < NUM_PLAY_STATS_ENTRIES) + { + newEntry = PlayStats.entry + PlayStats.numEntries; + PlayStats.numEntries += 1; + entryCountChanged = true; + } + else + { + // list is full - find existing entry with least amount of minutes and replace it + newEntry = PlayStats.entry + 0; + for (uint32 i = 1; i < NUM_PLAY_STATS_ENTRIES; i++) + { + if(PlayStats.entry[i].totalMinutesPlayed < newEntry->totalMinutesPlayed) + newEntry = PlayStats.entry + i; + } + } + newEntry->titleIdHigh = (uint32)(titleId >> 32); + newEntry->titleIdLow = (uint32)(titleId & 0xFFFFFFFF); + newEntry->firstLaunchDayIndex = GetTodaysDayIndex(); + newEntry->mostRecentDayIndex = newEntry->firstLaunchDayIndex; + newEntry->numTimesLaunched = 1; + newEntry->totalMinutesPlayed = 0; + newEntry->ukn12 = 0; + PlayStats_WriteEntry(newEntry, entryCountChanged); + return newEntry; + } + + // sets last played if entry already exists + // if it does not exist it creates a new entry with first and last played set to today + PlayStatsEntry* PlayStats_BeginNewTracking(uint64 titleId) + { + PlayStatsEntry* entry = PlayStats_GetEntry(titleId); + if (entry) + { + entry->mostRecentDayIndex = GetTodaysDayIndex(); + entry->numTimesLaunched += 1; + PlayStats_WriteEntry(entry); + return entry; + } + return PlayStats_CreateEntry(titleId); + } + + void PlayStats_CountAdditionalMinutes(PlayStatsEntry* entry, uint32 additionalMinutes) + { + if (additionalMinutes == 0) + return; + entry->totalMinutesPlayed += additionalMinutes; + entry->mostRecentDayIndex = GetTodaysDayIndex(); + PlayStats_WriteEntry(entry); + } + + struct PlayDiaryHeader + { + // the play diary is a rolling log + // initially only writeIndex increases + // after the log is filled, writeIndex wraps over and readIndex will increase as well + uint32be readIndex; + uint32be writeIndex; + }; + + static_assert(sizeof(PlayDiaryEntry) == 0x10); + static_assert(sizeof(PlayDiaryHeader) == 0x8); + + struct + { + FileStream* fs{}; + PlayDiaryHeader header; + PlayDiaryEntry entry[NUM_PLAY_DIARY_ENTRIES_MAX]; + }PlayDiaryData; + + void CreatePlayDiary() + { + MakeDirectory(); + PlayDiaryData.fs = FileStream::createFile2(GetPDFile("PlayDiary.dat")); + if (!PlayDiaryData.fs) + { + cemuLog_log(LogType::Force, "Failed to read or write PlayDiary.dat, playtime tracking will not be possible"); + } + // write header + PlayDiaryData.header.readIndex = 0; + PlayDiaryData.header.writeIndex = 0; + if (PlayDiaryData.fs) + PlayDiaryData.fs->writeData(&PlayDiaryData.header, sizeof(PlayDiaryHeader)); + } + + void LoadPlayDiary() + { + std::unique_lock _lock(sDiaryLock); + cemu_assert_debug(!PlayDiaryData.fs); + PlayDiaryData.fs = FileStream::openFile2(GetPDFile("PlayDiary.dat"), true); + if (!PlayDiaryData.fs) + { + CreatePlayDiary(); + return; + } + // read header + if (PlayDiaryData.fs->readData(&PlayDiaryData.header, sizeof(PlayDiaryHeader)) != sizeof(PlayDiaryHeader)) + { + cemuLog_log(LogType::Force, "Failed to read valid PlayDiary header"); + delete PlayDiaryData.fs; + PlayDiaryData.fs = nullptr; + CreatePlayDiary(); + return; + } + if (PlayDiaryData.header.readIndex > NUM_PLAY_DIARY_ENTRIES_MAX || PlayDiaryData.header.writeIndex > NUM_PLAY_DIARY_ENTRIES_MAX) + { + cemuLog_log(LogType::Force, "Bad value in play diary header (read={} write={})", (uint32)PlayDiaryData.header.readIndex, (uint32)PlayDiaryData.header.writeIndex); + PlayDiaryData.header.readIndex = PlayDiaryData.header.readIndex % NUM_PLAY_DIARY_ENTRIES_MAX; + PlayDiaryData.header.writeIndex = PlayDiaryData.header.writeIndex % NUM_PLAY_DIARY_ENTRIES_MAX; + } + // read entries and set any not-yet-written entries to zero + uint32 readBytes = PlayDiaryData.fs->readData(PlayDiaryData.entry, NUM_PLAY_DIARY_ENTRIES_MAX * sizeof(PlayDiaryEntry)); + uint32 readEntries = readBytes / sizeof(PlayDiaryEntry); + while (readEntries < NUM_PLAY_DIARY_ENTRIES_MAX) + { + PlayDiaryData.entry[readEntries].titleId = 0; + PlayDiaryData.entry[readEntries].ukn08 = 0; + PlayDiaryData.entry[readEntries].dayIndex = 0; + PlayDiaryData.entry[readEntries].ukn0E = 0; + readEntries++; + } + } + + uint32 GetDiaryEntries(uint8 accountSlot, PlayDiaryEntry* diaryEntries, uint32 maxEntries) + { + std::unique_lock _lock(sDiaryLock); + uint32 numReadEntries = 0; + uint32 currentEntryIndex = PlayDiaryData.header.readIndex; + while (currentEntryIndex != PlayDiaryData.header.writeIndex && numReadEntries < maxEntries) + { + *diaryEntries = PlayDiaryData.entry[currentEntryIndex]; + numReadEntries++; + diaryEntries++; + currentEntryIndex = (currentEntryIndex+1) % NUM_PLAY_DIARY_ENTRIES_MAX; + } + return numReadEntries; + } + + bool GetStatForGamelist(uint64 titleId, GameListStat& stat) + { + stat.last_played.year = 0; + stat.last_played.month = 0; + stat.last_played.day = 0; + stat.numMinutesPlayed = 0; + std::unique_lock _lock(sDiaryLock); + // the play stats give us last time played and the total minutes + PlayStatsEntry* playStats = PlayStats_GetEntry(titleId); + if (playStats) + { + stat.numMinutesPlayed = playStats->totalMinutesPlayed; + chrono_d::year_month_day ymd = GetDateFromDayIndex(playStats->mostRecentDayIndex); + stat.last_played.year = (int)ymd.year(); + stat.last_played.month = (unsigned int)ymd.month() - 1; + stat.last_played.day = (unsigned int)ymd.day(); + } + _lock.unlock(); + // check legacy time tracking for game entries in settings.xml + std::unique_lock _lockGC(GetConfig().game_cache_entries_mutex); + for (auto& gameEntry : GetConfig().game_cache_entries) + { + if(gameEntry.title_id != titleId) + continue; + stat.numMinutesPlayed += (gameEntry.legacy_time_played / 60); + if (gameEntry.legacy_last_played != 0) + { + time_t td = gameEntry.legacy_last_played; + tm* date = localtime(&td); + uint32 legacyYear = (uint32)date->tm_year + 1900; + uint32 legacyMonth = (uint32)date->tm_mon; + uint32 legacyDay = (uint32)date->tm_mday; + if (stat.last_played.year == 0 || std::tie(legacyYear, legacyMonth, legacyDay) > std::tie(stat.last_played.year, stat.last_played.month, stat.last_played.day)) + { + stat.last_played.year = legacyYear; + stat.last_played.month = legacyMonth; + stat.last_played.day = legacyDay; + } + } + } + return true; + } + + std::thread sPDMTimeTrackingThread; + CounterSemaphore sPDMSem; + std::atomic_bool sPDMRequestExitThread{ false }; + + void TimeTrackingThread(uint64 titleId) + { + PlayStatsEntry* playStatsEntry = PlayStats_BeginNewTracking(titleId); + + auto startTime = std::chrono::steady_clock::now(); + uint32 prevMinuteCounter = 0; + while (true) + { + sPDMSem.decrementWithWaitAndTimeout(15000); + if (sPDMRequestExitThread.load(std::memory_order::relaxed)) + break; + auto currentTime = std::chrono::steady_clock::now(); + uint32 elapsedMinutes = std::chrono::duration_cast<std::chrono::minutes>(currentTime - startTime).count(); + if (elapsedMinutes > prevMinuteCounter) + { + PlayStats_CountAdditionalMinutes(playStatsEntry, (elapsedMinutes - prevMinuteCounter)); + // todo - also update PlayDiary (and other files) + prevMinuteCounter = elapsedMinutes; + } + } + } + + void Initialize() + { + // todo - add support for per-account handling + LoadPlaystats(); + LoadPlayDiary(); + } + + void StartTrackingTime(uint64 titleId) + { + sPDMRequestExitThread = false; + sPDMTimeTrackingThread = std::thread(TimeTrackingThread, titleId); + } + + void Stop() + { + sPDMRequestExitThread.store(true); + sPDMSem.increment(); + sPDMTimeTrackingThread.join(); + } + + }; +}; \ No newline at end of file diff --git a/src/Cafe/IOSU/PDM/iosu_pdm.h b/src/Cafe/IOSU/PDM/iosu_pdm.h new file mode 100644 index 00000000..fbafbc02 --- /dev/null +++ b/src/Cafe/IOSU/PDM/iosu_pdm.h @@ -0,0 +1,38 @@ +#pragma once + +namespace iosu +{ + namespace pdm + { + void Initialize(); + void StartTrackingTime(uint64 titleId); + void Stop(); + + inline constexpr size_t NUM_PLAY_STATS_ENTRIES = 256; + inline constexpr size_t NUM_PLAY_DIARY_ENTRIES_MAX = 18250; // 0x474A + + struct PlayDiaryEntry + { + uint64be titleId; + uint32be ukn08; // probably minutes + uint16be dayIndex; + uint16be ukn0E; + }; + + uint32 GetDiaryEntries(uint8 accountSlot, PlayDiaryEntry* diaryEntries, uint32 maxEntries); + + /* Helper for UI game list */ + struct GameListStat + { + struct + { + uint32 year; // if 0 -> never played + uint32 month; + uint32 day; + }last_played; + uint32 numMinutesPlayed; + }; + + bool GetStatForGamelist(uint64 titleId, GameListStat& stat); + }; +}; \ No newline at end of file diff --git a/src/Cafe/IOSU/fsa/fsa_types.h b/src/Cafe/IOSU/fsa/fsa_types.h new file mode 100644 index 00000000..2d2daa50 --- /dev/null +++ b/src/Cafe/IOSU/fsa/fsa_types.h @@ -0,0 +1,83 @@ +#pragma once + +enum class FS_RESULT : sint32 // aka FSStatus +{ + SUCCESS = 0, + END_ITERATION = -2, // used by FSGetMountSource / FSGetMountSourceNext to indicate when last element was reached + FATAL_ERROR = -0x400, + + ALREADY_EXISTS = -5, + NOT_FOUND = -6, + NOT_FILE = -7, + NOT_DIR = -8, + PERMISSION_ERROR = -10, + + INVALID_CLIENT_HANDLE = -0x30000 - 37, + + ERR_PLACEHOLDER = -9999, // used when exact error code has yet to be determined +}; + +enum class FSA_RESULT : sint32 // aka FSError/FSAStatus +{ + SUCCESS = 0, + INVALID_CLIENT_HANDLE = -0x30000 - 37, + INVALID_HANDLE_UKN38 = -0x30000 - 38, + FATAL_ERROR = -0x30000 - 0x400, +}; +// todo - error handling in the IOSU part is pretty hacky right now and we use FS_RESULT in most places which we shouldn't be doing. Rework it + +using FSResHandle = sint32; +using FSFileHandle2 = FSResHandle; +using FSDirHandle2 = FSResHandle; +#define FS_INVALID_HANDLE_VALUE -1 + +#define FSA_FILENAME_SIZE_MAX 128 +#define FSA_PATH_SIZE_MAX (512 + FSA_FILENAME_SIZE_MAX) +#define FSA_CMD_PATH_MAX_LENGTH FSA_PATH_SIZE_MAX +#define FSA_MAX_CLIENTS 32 + +typedef sint32 FSStatus; // DEPR - replaced by FS_RESULT +typedef uint32 FS_ERROR_MASK; // replace with enum bitmask +typedef uint32 FSFileSize; +typedef uint64 FSLargeSize; +typedef uint64 FSTime; + +enum class FSFlag : uint32 +{ + NONE = 0, + IS_DIR = 0x80000000, +}; +DEFINE_ENUM_FLAG_OPERATORS(FSFlag); + +#pragma pack(1) + +struct FSStat_t +{ + /* +0x000 */ betype<FSFlag> flag; + /* +0x004 */ uint32be permissions; + /* +0x008 */ uint32be ownerId; + /* +0x00C */ uint32be groupId; + /* +0x010 */ betype<FSFileSize> size; + /* +0x014 */ betype<FSFileSize> allocatedSize; + /* +0x018 */ betype<FSLargeSize> quotaSize; + /* +0x020 */ uint32be entryId; + /* +0x024 */ betype<FSTime> createdTime; + /* +0x02C */ betype<FSTime> modifiedTime; + /* +0x034 */ uint8 attributes[0x30]; +}; + +static_assert(sizeof(FSStat_t) == 0x64); + +struct FSDirEntry_t +{ + /* +0x00 */ FSStat_t stat; + /* +0x64 */ char name[FSA_FILENAME_SIZE_MAX]; +}; + +static_assert(sizeof(FSDirEntry_t) == 0xE4); + +#pragma pack() + +// query types for QueryInfo +#define FSA_QUERY_TYPE_FREESPACE 0 +#define FSA_QUERY_TYPE_STAT 5 diff --git a/src/Cafe/IOSU/fsa/iosu_fsa.cpp b/src/Cafe/IOSU/fsa/iosu_fsa.cpp new file mode 100644 index 00000000..aa2ad553 --- /dev/null +++ b/src/Cafe/IOSU/fsa/iosu_fsa.cpp @@ -0,0 +1,824 @@ +#include "fsa_types.h" +#include "iosu_fsa.h" +#include "Cafe/IOSU/kernel/iosu_kernel.h" +#include "Cafe/Filesystem/fsc.h" +#include "util/helpers/helpers.h" + +#include "Cafe/OS/libs/coreinit/coreinit_FS.h" // get rid of this dependency, requires reworking some of the IPC stuff. See locations where we use coreinit::FSCmdBlockBody_t +#include "Cafe/HW/Latte/Core/LatteBufferCache.h" // also remove this dependency + +#include "Cafe/HW/MMU/MMU.h" + +using namespace iosu::kernel; + +namespace iosu +{ + namespace fsa + { + IOSMsgQueueId sFSAIoMsgQueue; + SysAllocator<iosu::kernel::IOSMessage, 352> _m_sFSAIoMsgQueueMsgBuffer; + std::thread sFSAIoThread; + + struct FSAClient // IOSU's counterpart to the coreinit FSClient struct + { + std::string workingDirectory; + bool isAllocated{ false }; + + void AllocateAndInitialize() + { + isAllocated = true; + workingDirectory = std::string("/"); + } + + void ReleaseAndCleanup() + { + isAllocated = false; + } + }; + + std::array<FSAClient, 624> sFSAClientArray; + + IOS_ERROR FSAAllocateClient(sint32& indexOut) + { + for (size_t i = 0; i < sFSAClientArray.size(); i++) + { + if(sFSAClientArray[i].isAllocated) + continue; + sFSAClientArray[i].AllocateAndInitialize(); + indexOut = (sint32)i; + return IOS_ERROR_OK; + } + return (IOS_ERROR)0xFFFCFFEE; + } + + sint32 FSA_convertFSCtoFSStatus(sint32 fscError) + { + if (fscError == FSC_STATUS_OK) + return (FSStatus)FS_RESULT::SUCCESS; + else if (fscError == FSC_STATUS_FILE_NOT_FOUND) + return (sint32)FS_RESULT::NOT_FOUND; + else if (fscError == FSC_STATUS_ALREADY_EXISTS) + return (sint32)FS_RESULT::ALREADY_EXISTS; + cemu_assert_unimplemented(); + return -1; + } + + std::string __FSATranslatePath(FSAClient* fsaClient, std::string_view input, bool endWithSlash = false) + { + std::string tmp; + if (input.empty()) + { + tmp.assign(fsaClient->workingDirectory); + if (endWithSlash) + tmp.push_back('/'); + return tmp; + } + char c = input.front(); + cemu_assert_debug(c != '\\'); // how to handle backward slashes? + + if (c == '/') + { + // absolute path + tmp.assign("/"); // keep the leading slash + } + else if (c == '~') + { + cemu_assert_debug(false); + input.remove_prefix(1); + tmp.assign("/"); + } + else + { + // in all other cases the path is relative to the working directory + tmp.assign(fsaClient->workingDirectory); + } + // parse path + size_t idx = 0; + while (idx < input.size()) + { + c = input[idx]; + if (c == '/') + { + idx++; + continue; // filter leading and repeated slashes + } + + if (c == '.') + { + if ((input.size() - idx) >= 3 && input[idx + 1] == '.' && input[idx + 2] == '/') + { + // "../" + cemu_assert_unimplemented(); + idx += 3; + continue; + } + else if ((input.size() - idx) == 2 && input[idx + 1] == '.') + { + // ".." at the end + cemu_assert_unimplemented(); + idx += 2; + continue; + } + else if ((input.size() - idx) >= 2 && input[idx + 1] == '/') + { + // "./" + idx += 2; + continue; + } + } + if (!tmp.empty() && tmp.back() != '/') + tmp.push_back('/'); + + while (idx < input.size()) + { + c = input[idx]; + if (c == '/') + break; + tmp.push_back(c); + idx++; + } + } + if (endWithSlash) + { + if (tmp.empty() || tmp.back() != '/') + tmp.push_back('/'); + } + return tmp; + } + + FSCVirtualFile* __FSAOpenNode(FSAClient* client, std::string_view path, FSC_ACCESS_FLAG accessFlags, sint32& fscStatus) + { + std::string translatedPath = __FSATranslatePath(client, path); + return fsc_open(translatedPath.c_str(), accessFlags, &fscStatus); + } + + class _FSAHandleTable + { + struct _FSAHandleResource + { + bool isAllocated{ false }; + FSCVirtualFile* fscFile; + uint16 handleCheckValue; + }; + public: + FSA_RESULT AllocateHandle(FSResHandle& handleOut, FSCVirtualFile* fscFile) + { + for (size_t i = 0; i < m_handleTable.size(); i++) + { + auto& it = m_handleTable.at(i); + if(it.isAllocated) + continue; + uint16 checkValue = (uint16)m_currentCounter; + m_currentCounter++; + it.handleCheckValue = checkValue; + it.fscFile = fscFile; + it.isAllocated = true; + uint32 handleVal = ((uint32)i << 16) | (uint32)checkValue; + handleOut = (FSResHandle)handleVal; + return FSA_RESULT::SUCCESS; + } + cemuLog_log(LogType::Force, "FSA: Ran out of file handles"); + return FSA_RESULT::FATAL_ERROR; + } + + FSA_RESULT ReleaseHandle(FSResHandle handle) + { + uint16 index = (uint16)((uint32)handle >> 16); + uint16 checkValue = (uint16)(handle & 0xFFFF); + if (index >= m_handleTable.size()) + return FSA_RESULT::INVALID_HANDLE_UKN38; + auto& it = m_handleTable.at(index); + if(!it.isAllocated) + return FSA_RESULT::INVALID_HANDLE_UKN38; + if(it.handleCheckValue != checkValue) + return FSA_RESULT::INVALID_HANDLE_UKN38; + it.fscFile = nullptr; + it.isAllocated = false; + return FSA_RESULT::SUCCESS; + } + + FSCVirtualFile* GetByHandle(FSResHandle handle) + { + uint16 index = (uint16)((uint32)handle >> 16); + uint16 checkValue = (uint16)(handle & 0xFFFF); + if (index >= m_handleTable.size()) + return nullptr; + auto& it = m_handleTable.at(index); + if (!it.isAllocated) + return nullptr; + if (it.handleCheckValue != checkValue) + return nullptr; + return it.fscFile; + } + + private: + uint32 m_currentCounter = 1; + std::array<_FSAHandleResource, 0x3C0> m_handleTable; + }; + + _FSAHandleTable sFileHandleTable; + _FSAHandleTable sDirHandleTable; + + + FSStatus __FSAOpenFile(FSAClient* client, const char* path, const char* accessModifierStr, sint32* fileHandle) + { + *fileHandle = FS_INVALID_HANDLE_VALUE; + FSC_ACCESS_FLAG accessModifier = FSC_ACCESS_FLAG::NONE; + bool truncateFile = false; // todo: Support for this + bool isAppend = false; // todo: proper support for this (all write operations should move cursor to the end of the file?) + if (strcmp(accessModifierStr, "r") == 0 || strcmp(accessModifierStr, "rb") == 0) + accessModifier = FSC_ACCESS_FLAG::READ_PERMISSION; + else if (strcmp(accessModifierStr, "r+") == 0) + { + // r+ will create a new file if it doesn't exist + // the cursor will be set to the beginning of the file + // allows read and write access + accessModifier = FSC_ACCESS_FLAG::READ_PERMISSION | FSC_ACCESS_FLAG::WRITE_PERMISSION | FSC_ACCESS_FLAG::FILE_ALLOW_CREATE; // create if non exists, read, write + } + else if (strcmp(accessModifierStr, "w") == 0) + { + accessModifier = FSC_ACCESS_FLAG::READ_PERMISSION | FSC_ACCESS_FLAG::WRITE_PERMISSION | FSC_ACCESS_FLAG::FILE_ALWAYS_CREATE; // create new file & write + truncateFile = true; + } + else if (strcmp(accessModifierStr, "w+") == 0) + { + accessModifier = FSC_ACCESS_FLAG::READ_PERMISSION | FSC_ACCESS_FLAG::WRITE_PERMISSION | FSC_ACCESS_FLAG::FILE_ALWAYS_CREATE; // create new file & read & write + truncateFile = true; + } + else if (strcmp(accessModifierStr, "wb") == 0) // used in Super Meat Boy + { + // b flag is allowed has no effect + accessModifier = FSC_ACCESS_FLAG::READ_PERMISSION | FSC_ACCESS_FLAG::WRITE_PERMISSION | FSC_ACCESS_FLAG::FILE_ALWAYS_CREATE; // create new file & write + truncateFile = true; + } + else if (strcmp(accessModifierStr, "a+") == 0) + { + cemu_assert_debug(false); // a+ is kind of special. Writing always happens at the end but the read cursor can dynamically move + // but Cafe OS might not support this. Needs investigation. + // this also used to be FILE_ALWAYS_CREATE in 1.26.2 and before + accessModifier = FSC_ACCESS_FLAG::READ_PERMISSION | FSC_ACCESS_FLAG::WRITE_PERMISSION | FSC_ACCESS_FLAG::FILE_ALLOW_CREATE; + isAppend = true; + } + else + cemu_assert_debug(false); + + accessModifier |= FSC_ACCESS_FLAG::OPEN_DIR | FSC_ACCESS_FLAG::OPEN_FILE; + sint32 fscStatus; + FSCVirtualFile* fscFile = __FSAOpenNode(client, path, accessModifier, fscStatus); + if (!fscFile) + return (sint32)FS_RESULT::NOT_FOUND; + if (fscFile->fscGetType() != FSC_TYPE_FILE) + { + delete fscFile; + return (sint32)FS_RESULT::NOT_FILE; + } + if (isAppend) + fsc_setFileSeek(fscFile, fsc_getFileSize(fscFile)); + FSResHandle fsFileHandle; + FSA_RESULT r = sFileHandleTable.AllocateHandle(fsFileHandle, fscFile); + if (r != FSA_RESULT::SUCCESS) + { + cemuLog_log(LogType::Force, "Exceeded maximum number of FSA file handles"); + delete fscFile; + return -0x400; + } + *fileHandle = fsFileHandle; + cemuLog_log(LogType::File, "Open file {} (access: {} result: ok handle: 0x{})", path, accessModifierStr, (uint32)*fileHandle); + return (FSStatus)FS_RESULT::SUCCESS; + } + + FSStatus __FSAOpenDirectory(FSAClient* client, std::string_view path, sint32* dirHandle) + { + *dirHandle = FS_INVALID_HANDLE_VALUE; + sint32 fscStatus; + FSCVirtualFile* fscFile = __FSAOpenNode(client, path, FSC_ACCESS_FLAG::OPEN_DIR | FSC_ACCESS_FLAG::OPEN_FILE, fscStatus); + if (!fscFile) + return (FSStatus)FS_RESULT::NOT_FOUND; + if (fscFile->fscGetType() != FSC_TYPE_DIRECTORY) + { + delete fscFile; + return (FSStatus)(FS_RESULT::NOT_DIR); + } + FSResHandle fsDirHandle; + FSA_RESULT r = sDirHandleTable.AllocateHandle(fsDirHandle, fscFile); + if (r != FSA_RESULT::SUCCESS) + { + delete fscFile; + return -0x400; + } + *dirHandle = fsDirHandle; + cemuLog_log(LogType::File, "Open directory {} (result: ok handle: 0x{})", path, (uint32)*dirHandle); + return (FSStatus)FS_RESULT::SUCCESS; + } + + FSStatus __FSACloseFile(uint32 fileHandle) + { + uint8 handleType = 0; + FSCVirtualFile* fscFile = sFileHandleTable.GetByHandle(fileHandle); + if (!fscFile) + { + forceLogDebug_printf("__FSACloseFile(): Invalid handle (0x%08x)", fileHandle); + return (FSStatus)FS_RESULT::ERR_PLACEHOLDER; + } + // unregister file + sFileHandleTable.ReleaseHandle(fileHandle); // todo - use the error code of this + fsc_close(fscFile); + return (FSStatus)FS_RESULT::SUCCESS; + } + + FSStatus FSAProcessCmd_remove(FSAClient* client, FSAIpcCommand* cmd) + { + char* path = (char*)cmd->cmdRemove.path; + sint32 fscStatus = FSC_STATUS_FILE_NOT_FOUND; + fsc_remove(path, &fscStatus); + return FSA_convertFSCtoFSStatus(fscStatus); + } + + FSStatus FSAProcessCmd_makeDir(FSAClient* client, FSAIpcCommand* cmd) + { + char* path = (char*)cmd->cmdMakeDir.path; + sint32 fscStatus = FSC_STATUS_FILE_NOT_FOUND; + fsc_createDir(path, &fscStatus); + return FSA_convertFSCtoFSStatus(fscStatus); + } + + FSStatus FSAProcessCmd_rename(FSAClient* client, FSAIpcCommand* cmd) + { + char* srcPath = (char*)cmd->cmdRename.srcPath; + char* dstPath = (char*)cmd->cmdRename.dstPath; + sint32 fscStatus = FSC_STATUS_FILE_NOT_FOUND; + fsc_rename(srcPath, dstPath, &fscStatus); + return FSA_convertFSCtoFSStatus(fscStatus); + } + + bool __FSA_GetStatFromFSCFile(FSCVirtualFile* fscFile, FSStat_t* fsStatOut) + { + memset(fsStatOut, 0x00, sizeof(FSStat_t)); + FSFlag statFlag = FSFlag::NONE; + if (fsc_isDirectory(fscFile)) + { + // note: Only quota (save) directories have the size field set. For other directories it's zero. + // Hyrule Warriors relies on the size field being zero for /vol/content/data/. Otherwise it will try to read it like a file and get stuck in an endless loop. + // Art Academy reads the size for save directories + statFlag |= FSFlag::IS_DIR; + fsStatOut->size = 0; + } + else if (fsc_isFile(fscFile)) + { + fsStatOut->size = fsc_getFileSize(fscFile); + } + else + { + cemu_assert_suspicious(); + } + fsStatOut->permissions = 0x777; // format matches unix (fstat) permissions? + fsStatOut->flag = statFlag; + return true; + } + + FSStatus __FSA_GetFileStat(FSAClient* client, const char* path, FSStat_t* fsStatOut) + { + sint32 fscStatus; + FSCVirtualFile* fscFile = __FSAOpenNode(client, path, FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::OPEN_DIR, fscStatus); + if (!fscFile) + return FSA_convertFSCtoFSStatus(fscStatus); + __FSA_GetStatFromFSCFile(fscFile, fsStatOut); + delete fscFile; + return (FSStatus)FS_RESULT::SUCCESS; + } + + FSStatus FSAProcessCmd_queryInfo(FSAClient* client, FSAIpcCommand* cmd) + { + coreinit::FSCmdBlockBody_t* fullCmd = (coreinit::FSCmdBlockBody_t*)cmd; + + char* path = (char*)cmd->cmdQueryInfo.query; + uint32 queryType = _swapEndianU32(cmd->cmdQueryInfo.queryType); + void* queryResult = memory_getPointerFromVirtualOffset(_swapEndianU32(fullCmd->returnValueMPTR)); + // handle query + sint32 fscStatus = FSC_STATUS_OK; + if (queryType == FSA_QUERY_TYPE_STAT) + { + FSStat_t* fsStat = (FSStat_t*)queryResult; + FSStatus fsStatus = __FSA_GetFileStat(client, path, fsStat); + return fsStatus; + } + else if (queryType == FSA_QUERY_TYPE_FREESPACE) + { + sint32 fscStatus; + FSCVirtualFile* fscFile = __FSAOpenNode(client, path, FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::OPEN_DIR, fscStatus); + if (!fscFile) + return FSA_convertFSCtoFSStatus(fscStatus); + betype<uint64>* fsStatSize = (betype<uint64>*)queryResult; + *fsStatSize = 30ull * 1024 * 1024 * 1024; // placeholder value. How is this determined? + delete fscFile; + return (FSStatus)FS_RESULT::SUCCESS; + } + else + cemu_assert_unimplemented(); + return FSA_convertFSCtoFSStatus(fscStatus); + } + + FSStatus FSAProcessCmd_getStatFile(FSAClient* client, FSAIpcCommand* cmd) + { + coreinit::FSCmdBlockBody_t* fullCmd = (coreinit::FSCmdBlockBody_t*)cmd; + + FSFileHandle2 fileHandle = cmd->cmdGetStatFile.fileHandle; + FSStat_t* statOut = (FSStat_t*)memory_getPointerFromVirtualOffset(_swapEndianU32(fullCmd->returnValueMPTR)); + FSCVirtualFile* fscFile = sFileHandleTable.GetByHandle(fileHandle); + if (!fscFile) + return (FSStatus)FS_RESULT::NOT_FOUND; + cemu_assert_debug(fsc_isFile(fscFile)); + __FSA_GetStatFromFSCFile(fscFile, statOut); + return (FSStatus)FS_RESULT::SUCCESS; + } + + FSStatus FSAProcessCmd_read(FSAClient* client, FSAIpcCommand* cmd) + { + coreinit::FSCmdBlockBody_t* fullCmd = (coreinit::FSCmdBlockBody_t*)cmd; + + uint32 filePos = _swapEndianU32(cmd->cmdDefault.transferFilePos); + uint32 fileHandle = _swapEndianU32(cmd->cmdDefault.fileHandle); + MPTR destOffset = _swapEndianU32(cmd->cmdDefault.destBufferMPTR); + void* destPtr = memory_getPointerFromVirtualOffset(destOffset); + uint32 transferSize = _swapEndianU32(fullCmd->transferSize); + uint32 transferElementSize = _swapEndianU32(fullCmd->transferElemSize); + uint32 flags = _swapEndianU32(cmd->cmdDefault.cmdFlag); + uint32 errHandling = _swapEndianU32(fullCmd->errHandling); + FSCVirtualFile* fscFile = sFileHandleTable.GetByHandle(fileHandle); + if (!fscFile) + return (FSStatus)FS_RESULT::ERR_PLACEHOLDER; + uint32 elementSize = transferElementSize; + uint32 elementCount = 0; + if (transferElementSize != 0) + { + elementCount = transferSize / transferElementSize; + cemu_assert_debug((transferSize % transferElementSize) == 0); + } + else + { + cemu_assert_debug(transferSize == 0); + } + uint32 bytesToRead = transferSize; + // update file position if flag is set + if ((flags & FSA_CMD_FLAG_SET_POS) != 0) + fsc_setFileSeek(fscFile, filePos); + // todo: File permissions + uint32 bytesSuccessfullyRead = fsc_readFile(fscFile, destPtr, bytesToRead); + if (transferElementSize == 0) + return 0; + + LatteBufferCache_notifyDCFlush(memory_getVirtualOffsetFromPointer(destPtr), bytesToRead); + + return bytesSuccessfullyRead / transferElementSize; // return number of elements read + } + + FSStatus FSAProcessCmd_write(FSAClient* client, FSAIpcCommand* cmd) + { + coreinit::FSCmdBlockBody_t* fullCmd = (coreinit::FSCmdBlockBody_t*)cmd; + + uint32 filePos = _swapEndianU32(cmd->cmdDefault.transferFilePos); + uint32 fileHandle = _swapEndianU32(cmd->cmdDefault.fileHandle); + MPTR destOffset = _swapEndianU32(cmd->cmdDefault.destBufferMPTR); + void* destPtr = memory_getPointerFromVirtualOffset(destOffset); + uint32 transferSize = _swapEndianU32(fullCmd->transferSize); + uint32 transferElementSize = _swapEndianU32(fullCmd->transferElemSize); + uint32 flags = _swapEndianU32(cmd->cmdDefault.cmdFlag); + uint32 errHandling = _swapEndianU32(fullCmd->errHandling); + FSCVirtualFile* fscFile = sFileHandleTable.GetByHandle(fileHandle); + if (!fscFile) + return (FSStatus)FS_RESULT::ERR_PLACEHOLDER; + uint32 elementSize = transferElementSize; + uint32 elementCount = transferSize / transferElementSize; + cemu_assert_debug((transferSize % transferElementSize) == 0); + uint32 bytesToWrite = transferSize; + // check for write permission (should this happen before or after setting file position?) + if (!fsc_isWritable(fscFile)) + { + cemu_assert_debug(false); + return (FSStatus)FS_RESULT::PERMISSION_ERROR; + } + // update file position if flag is set + if ((flags & FSA_CMD_FLAG_SET_POS) != 0) + fsc_setFileSeek(fscFile, filePos); + uint32 bytesSuccessfullyWritten = fsc_writeFile(fscFile, destPtr, bytesToWrite); + debug_printf("FSAProcessCmd_write(): Writing 0x%08x bytes (bytes actually written: 0x%08x)\n", bytesToWrite, bytesSuccessfullyWritten); + return bytesSuccessfullyWritten / transferElementSize; // return number of elements read + } + + FSStatus FSAProcessCmd_setPos(FSAClient* client, FSAIpcCommand* cmd) + { + uint32 fileHandle = _swapEndianU32(cmd->cmdDefault.destBufferMPTR); + uint32 filePos = _swapEndianU32(cmd->cmdDefault.ukn0008); + FSCVirtualFile* fscFile = sFileHandleTable.GetByHandle(fileHandle); + if (!fscFile) + return (FSStatus)FS_RESULT::ERR_PLACEHOLDER; + fsc_setFileSeek(fscFile, filePos); + return (FSStatus)FS_RESULT::SUCCESS; + } + + FSStatus FSAProcessCmd_getPos(FSAClient* client, FSAIpcCommand* cmd) + { + coreinit::FSCmdBlockBody_t* fullCmd = (coreinit::FSCmdBlockBody_t*)cmd; + + uint32 fileHandle = _swapEndianU32(cmd->cmdDefault.destBufferMPTR); + MPTR returnedFilePos = _swapEndianU32(fullCmd->returnValueMPTR); + FSCVirtualFile* fscFile = sFileHandleTable.GetByHandle(fileHandle); + if (!fscFile) + return (FSStatus)FS_RESULT::ERR_PLACEHOLDER; + uint32 filePos = fsc_getFileSeek(fscFile); + memory_writeU32(returnedFilePos, filePos); + return (FSStatus)FS_RESULT::SUCCESS; + } + + FSStatus FSAProcessCmd_openFile(FSAClient* client, FSAIpcCommand* cmd) + { + coreinit::FSCmdBlockBody_t* fullCmd = (coreinit::FSCmdBlockBody_t*)cmd; + sint32 fileHandle = 0; + FSStatus fsStatus = __FSAOpenFile(client, (char*)cmd->cmdOpenFile.path, (char*)cmd->cmdOpenFile.mode, &fileHandle); + memory_writeU32(_swapEndianU32(fullCmd->returnValueMPTR), fileHandle); + cmd->cmdOpenFile.fileHandleOutput = fileHandle; + return fsStatus; + } + + FSStatus FSAProcessCmd_closeFile(FSAClient* client, FSAIpcCommand* cmd) + { + return __FSACloseFile(cmd->cmdCloseFile.fileHandle); + } + + FSStatus FSAProcessCmd_openDir(FSAClient* client, FSAIpcCommand* cmd) + { + coreinit::FSCmdBlockBody_t* fullCmd = (coreinit::FSCmdBlockBody_t*)cmd; + sint32 dirHandle = 0; + FSStatus fsStatus = __FSAOpenDirectory(client, (const char*)cmd->cmdOpenFile.path, &dirHandle); + memory_writeU32(_swapEndianU32(fullCmd->returnValueMPTR), dirHandle); + cmd->cmdOpenDir.dirHandleOutput = dirHandle; + return fsStatus; + } + + FSStatus FSAProcessCmd_readDir(FSAClient* client, FSAIpcCommand* cmd) + { + coreinit::FSCmdBlockBody_t* fullCmd = (coreinit::FSCmdBlockBody_t*)cmd; + + FSCVirtualFile* fscFile = sDirHandleTable.GetByHandle((sint32)cmd->cmdReadDir.dirHandle); + if (!fscFile) + return (FSStatus)FS_RESULT::ERR_PLACEHOLDER; + FSDirEntry_t* dirEntryOut = (FSDirEntry_t*)memory_getPointerFromVirtualOffset(_swapEndianU32(fullCmd->returnValueMPTR)); + FSCDirEntry fscDirEntry; + if (fsc_nextDir(fscFile, &fscDirEntry) == false) + return (FSStatus)FS_RESULT::END_ITERATION; + strcpy(dirEntryOut->name, fscDirEntry.path); + FSFlag statFlag = FSFlag::NONE; + dirEntryOut->stat.size = 0; + if (fscDirEntry.isDirectory) + { + statFlag |= FSFlag::IS_DIR; + } + else if (fscDirEntry.isFile) + { + dirEntryOut->stat.size = fscDirEntry.fileSize; + } + dirEntryOut->stat.flag = statFlag; + dirEntryOut->stat.permissions = 0x777; + return (FSStatus)FS_RESULT::SUCCESS; + } + + FSStatus FSAProcessCmd_closeDir(FSAClient* client, FSAIpcCommand* cmd) + { + FSCVirtualFile* fscFile = sDirHandleTable.GetByHandle((sint32)cmd->cmdReadDir.dirHandle); + if (!fscFile) + { + forceLogDebug_printf("CloseDir: Invalid handle (0x%08x)", (sint32)cmd->cmdReadDir.dirHandle); + return (FSStatus)FS_RESULT::ERR_PLACEHOLDER; + } + sDirHandleTable.ReleaseHandle(cmd->cmdReadDir.dirHandle); + fsc_close(fscFile); + return (FSStatus)FS_RESULT::SUCCESS; + } + + FSStatus FSAProcessCmd_flushQuota(FSAClient* client, FSAIpcCommand* cmd) + { + return (FSStatus)FS_RESULT::SUCCESS; + } + + FSStatus FSAProcessCmd_appendFile(FSAClient* client, FSAIpcCommand* cmd) + { + uint32 fileHandle = _swapEndianU32(cmd->cmdDefault.destBufferMPTR); + FSCVirtualFile* fscFile = sFileHandleTable.GetByHandle(fileHandle); + if (!fscFile) + return (FSStatus)FS_RESULT::ERR_PLACEHOLDER; +#ifndef PUBLIC_RELEASE + cemuLog_force("FSAProcessCmd_appendFile(): size 0x{:08x} count 0x{:08x} (todo)\n", _swapEndianU32(cmd->cmdAppendFile.size), _swapEndianU32(cmd->cmdAppendFile.count)); +#endif + return _swapEndianU32(cmd->cmdAppendFile.size) * _swapEndianU32(cmd->cmdAppendFile.count); + } + + FSStatus FSAProcessCmd_truncateFile(FSAClient* client, FSAIpcCommand* cmd) + { + FSFileHandle2 fileHandle = cmd->cmdTruncateFile.fileHandle; + FSCVirtualFile* fscFile = sFileHandleTable.GetByHandle(fileHandle); + if (!fscFile) + return (FSStatus)FS_RESULT::ERR_PLACEHOLDER; + fsc_setFileLength(fscFile, fsc_getFileSeek(fscFile)); + return (FSStatus)FS_RESULT::SUCCESS; + } + + FSStatus FSAProcessCmd_isEof(FSAClient* client, FSAIpcCommand* cmd) + { + uint32 fileHandle = _swapEndianU32(cmd->cmdDefault.destBufferMPTR); + FSCVirtualFile* fscFile = sFileHandleTable.GetByHandle(fileHandle); + if (!fscFile) + return (FSStatus)FS_RESULT::ERR_PLACEHOLDER; + uint32 filePos = fsc_getFileSeek(fscFile); + uint32 fileSize = fsc_getFileSize(fscFile); + if (filePos >= fileSize) + return (FSStatus)FS_RESULT::END_ITERATION; + return (FSStatus)FS_RESULT::SUCCESS; + } + + FSStatus FSAProcessCmd_getCwd(FSAClient* client, FSAIpcCommand* cmd) + { + coreinit::FSCmdBlockBody_t* fullCmd = (coreinit::FSCmdBlockBody_t*)cmd; + + char* pathOutput = (char*)memory_getPointerFromVirtualOffset(_swapEndianU32(fullCmd->returnValueMPTR)); + sint32 pathOutputMaxLen = _swapEndianU32(fullCmd->transferSize); + cemu_assert(pathOutputMaxLen > 0); + sint32 fscStatus = FSC_STATUS_OK; + strncpy(pathOutput, client->workingDirectory.data(), std::min(client->workingDirectory.size() + 1, (size_t)pathOutputMaxLen)); + pathOutput[pathOutputMaxLen - 1] = '\0'; + return FSA_convertFSCtoFSStatus(fscStatus); + } + + FSStatus FSAProcessCmd_changeDir(FSAClient* client, FSAIpcCommand* cmd) + { + const char* path = (const char*)cmd->cmdChangeDir.path; + cmd->cmdChangeDir.path[sizeof(cmd->cmdChangeDir.path) - 1] = '\0'; + sint32 fscStatus = FSC_STATUS_OK; + client->workingDirectory.assign(__FSATranslatePath(client, path, true)); + return FSA_convertFSCtoFSStatus(fscStatus); + } + + void FSAHandleCommandIoctl(FSAClient* client, IPCCommandBody* cmd, uint32 operationId, void* ptrIn, void* ptrOut) + { + FSAIpcCommand* fsaCommand = (FSAIpcCommand*)ptrIn; + FSStatus fsStatus = (FSStatus)(FS_RESULT::FATAL_ERROR); + if (operationId == FSA_CMD_OPERATION_TYPE_REMOVE) + { + fsStatus = FSAProcessCmd_remove(client, fsaCommand); + } + else if (operationId == FSA_CMD_OPERATION_TYPE_MAKEDIR) + { + fsStatus = FSAProcessCmd_makeDir(client, fsaCommand); + } + else if (operationId == FSA_CMD_OPERATION_TYPE_RENAME) + { + fsStatus = FSAProcessCmd_rename(client, fsaCommand); + } + else if (operationId == FSA_CMD_OPERATION_TYPE_READ) + { + fsStatus = FSAProcessCmd_read(client, fsaCommand); + } + else if (operationId == FSA_CMD_OPERATION_TYPE_WRITE) + { + fsStatus = FSAProcessCmd_write(client, fsaCommand); + } + else if (operationId == FSA_CMD_OPERATION_TYPE_SETPOS) + { + fsStatus = FSAProcessCmd_setPos(client, fsaCommand); + } + else if (operationId == FSA_CMD_OPERATION_TYPE_GETPOS) + { + fsStatus = FSAProcessCmd_getPos(client, fsaCommand); + } + else if (operationId == FSA_CMD_OPERATION_TYPE_OPENFILE) + { + fsStatus = FSAProcessCmd_openFile(client, fsaCommand); + } + else if (operationId == FSA_CMD_OPERATION_TYPE_CLOSEFILE) + { + fsStatus = FSAProcessCmd_closeFile(client, fsaCommand); + } + else if (operationId == FSA_CMD_OPERATION_TYPE_APPENDFILE) + { + fsStatus = FSAProcessCmd_appendFile(client, fsaCommand); + } + else if (operationId == FSA_CMD_OPERATION_TYPE_TRUNCATEFILE) + { + fsStatus = FSAProcessCmd_truncateFile(client, fsaCommand); + } + else if (operationId == FSA_CMD_OPERATION_TYPE_ISEOF) + { + fsStatus = FSAProcessCmd_isEof(client, fsaCommand); + } + else if (operationId == FSA_CMD_OPERATION_TYPE_QUERYINFO) + { + fsStatus = FSAProcessCmd_queryInfo(client, fsaCommand); + } + else if (operationId == FSA_CMD_OPERATION_TYPE_GETSTATFILE) + { + fsStatus = FSAProcessCmd_getStatFile(client, fsaCommand); + } + else if (operationId == FSA_CMD_OPERATION_TYPE_GETCWD) + { + fsStatus = FSAProcessCmd_getCwd(client, fsaCommand); + } + else if (operationId == FSA_CMD_OPERATION_TYPE_CHANGEDIR) + { + fsStatus = FSAProcessCmd_changeDir(client, fsaCommand); + } + else if (operationId == FSA_CMD_OPERATION_TYPE_OPENDIR) + { + fsStatus = FSAProcessCmd_openDir(client, fsaCommand); + } + else if (operationId == FSA_CMD_OPERATION_TYPE_READDIR) + { + fsStatus = FSAProcessCmd_readDir(client, fsaCommand); + } + else if (operationId == FSA_CMD_OPERATION_TYPE_CLOSEDIR) + { + fsStatus = FSAProcessCmd_closeDir(client, fsaCommand); + } + else if (operationId == FSA_CMD_OPERATION_TYPE_FLUSHQUOTA) + { + fsStatus = FSAProcessCmd_flushQuota(client, fsaCommand); + } + else + { + cemu_assert_unimplemented(); + } + IOS_ResourceReply(cmd, (IOS_ERROR)fsStatus); + } + + void FSAIoThread() + { + SetThreadName("IOSU-FSA"); + IOSMessage msg; + while (true) + { + IOS_ERROR r = IOS_ReceiveMessage(sFSAIoMsgQueue, &msg, 0); + cemu_assert(!IOS_ResultIsError(r)); + if (msg == 0) + return; // shutdown signaled + IPCCommandBody* cmd = MEMPTR<IPCCommandBody>(msg).GetPtr(); + uint32 clientHandle = (uint32)cmd->devHandle; + if (cmd->cmdId == IPCCommandId::IOS_OPEN) + { + sint32 clientIndex = 0; + r = FSAAllocateClient(clientIndex); + if (r != IOS_ERROR_OK) + { + IOS_ResourceReply(cmd, r); + continue; + } + IOS_ResourceReply(cmd, (IOS_ERROR)clientIndex); + continue; + } + else if (cmd->cmdId == IPCCommandId::IOS_CLOSE) + { + cemu_assert(clientHandle < sFSAClientArray.size()); + sFSAClientArray[clientHandle].ReleaseAndCleanup(); + IOS_ResourceReply(cmd, IOS_ERROR_OK); + continue; + } + else if (cmd->cmdId == IPCCommandId::IOS_IOCTL) + { + cemu_assert(clientHandle < sFSAClientArray.size()); + cemu_assert(sFSAClientArray[clientHandle].isAllocated); + FSAHandleCommandIoctl(sFSAClientArray.data() + clientHandle, cmd, cmd->args[0], MEMPTR<void>(cmd->args[1]), MEMPTR<void>(cmd->args[3])); + } + else if (cmd->cmdId == IPCCommandId::IOS_IOCTLV) + { + cemu_assert_unimplemented(); + //uint32 requestId = cmd->args[0]; + //uint32 numIn = cmd->args[1]; + //uint32 numOut = cmd->args[2]; + //IPCIoctlVector* vec = MEMPTR<IPCIoctlVector>{ cmd->args[3] }.GetPtr(); + IOS_ResourceReply(cmd, IOS_ERROR_INVALID); + continue; + } + else + { + cemuLog_log(LogType::Force, "/dev/fsa: Unsupported IPC cmdId"); + cemu_assert_suspicious(); + IOS_ResourceReply(cmd, IOS_ERROR_INVALID); + } + } + } + + void Initialize() + { + for (auto& it : sFSAClientArray) + it.ReleaseAndCleanup(); + sFSAIoMsgQueue = (IOSMsgQueueId)IOS_CreateMessageQueue(_m_sFSAIoMsgQueueMsgBuffer.GetPtr(), _m_sFSAIoMsgQueueMsgBuffer.GetCount()); + IOS_ERROR r = IOS_RegisterResourceManager("/dev/fsa", sFSAIoMsgQueue); + IOS_DeviceAssociateId("/dev/fsa", 11); + cemu_assert(!IOS_ResultIsError(r)); + sFSAIoThread = std::thread(FSAIoThread); + } + + void Shutdown() + { + IOS_SendMessage(sFSAIoMsgQueue, 0, 0); + sFSAIoThread.join(); + } + } +} diff --git a/src/Cafe/IOSU/fsa/iosu_fsa.h b/src/Cafe/IOSU/fsa/iosu_fsa.h new file mode 100644 index 00000000..35b53d46 --- /dev/null +++ b/src/Cafe/IOSU/fsa/iosu_fsa.h @@ -0,0 +1,162 @@ +#pragma once +#include "fsa_types.h" + +namespace iosu +{ + namespace fsa + { + struct FSAIpcCommand + { + union + { + struct + { + uint32 ukn0000; + uint32 destBufferMPTR; // used as fileHandle for FSSetFilePosAsync + uint32 ukn0008; // used as filePos for FSSetFilePosAsync + uint32 ukn000C; + uint32 transferFilePos; // used as filePos for read/write operation + uint32 fileHandle; + uint32 cmdFlag; + uint32 ukn001C; + uint8 ukn0020[0x10]; + uint8 ukn0030[0x10]; + uint8 ukn0040[0x10]; + uint8 ukn0050[0x10]; + uint8 ukn0060[0x10]; + uint8 ukn0070[0x10]; + uint8 ukn0080[0x10]; + uint8 ukn0090[0x10]; + uint8 ukn00A0[0x10]; + uint8 ukn00B0[0x10]; + uint8 ukn00C0[0x10]; + uint8 ukn00D0[0x10]; + uint8 ukn00E0[0x10]; + uint8 ukn00F0[0x10]; + uint8 ukn0100[0x100]; + uint8 ukn0200[0x100]; + uint8 ukn0300[0x100]; + uint8 ukn0400[0x100]; + uint8 ukn0500[0x100]; + uint8 ukn0600[0x100]; + }cmdDefault; + struct + { + uint32 ukn0000; + uint8 path[FSA_CMD_PATH_MAX_LENGTH]; + uint8 mode[12]; // +0x284 note: code seems to access this value like it has a size of 0x10 but the actual struct element is only 12 bytes? Maybe a typo (10 instead of 0x10 in the struct def) + uint32 createMode; // +0x290 + uint32 openFlags; // +0x294 + uint32 preallocSize; // +0x298 + uint8 ukn[0x2E8]; // +0x29C + // output + uint32be fileHandleOutput; // +0x584 used to return file handle on success + }cmdOpenFile; + struct + { + uint32 ukn0000; // +0x000 + uint32be fileHandle; // +0x004 + }cmdCloseFile; + struct + { + uint32 ukn0000; + uint8 path[FSA_CMD_PATH_MAX_LENGTH]; + uint32 ukn0284; + uint8 ukn0288[0x80 - 8]; + uint8 ukn0300[0x100]; + uint8 ukn0400[0x100]; + uint32 ukn0500; + }cmdRemove; + struct + { + uint32 ukn0000; + uint8 path[FSA_CMD_PATH_MAX_LENGTH]; + uint8 ukn0284[12]; // +0x284 + uint32 ukn0290; // +0x290 + uint32 ukn0294; // +0x294 + uint32 ukn0298; // +0x298 + uint8 ukn[0x2E8]; // +0x29C + // output + uint32be dirHandleOutput; // +0x584 used to return dir handle on success + }cmdOpenDir; + struct + { + uint32 ukn0000; + betype<uint32> dirHandle; + }cmdReadDir; + struct + { + uint32 ukn0000; + betype<uint32> dirHandle; + }cmdCloseDir; + struct + { + uint32 ukn0000; + uint8 path[FSA_CMD_PATH_MAX_LENGTH]; + uint32 uknParam; + uint8 ukn0288[0x80 - 8]; + uint8 ukn0300[0x100]; + uint8 ukn0400[0x100]; + uint32 ukn0500; + }cmdMakeDir; + struct + { + uint32 ukn0000; + uint8 path[FSA_CMD_PATH_MAX_LENGTH]; + uint8 ukn0284[0x80 - 4]; + uint8 ukn0300[0x100]; + uint8 ukn0400[0x100]; + uint32 ukn0500; + }cmdChangeDir; + struct + { + uint32 ukn0000; + uint8 query[FSA_CMD_PATH_MAX_LENGTH]; + uint32 queryType; + uint8 ukn0288[0x80 - 8]; + uint8 ukn0300[0x100]; + uint8 ukn0400[0x100]; + uint32 ukn0500; + }cmdQueryInfo; + struct + { + uint32 ukn0000; + uint8 srcPath[FSA_CMD_PATH_MAX_LENGTH]; + uint8 dstPath[FSA_CMD_PATH_MAX_LENGTH]; + }cmdRename; + struct + { + uint32 ukn0000; + uint32 size; + uint32 count; + uint32 fileHandle; + uint32 uknParam; + }cmdAppendFile; + struct + { + uint32 ukn0000; + betype<uint32> fileHandle; + uint32be ukn0008; + }cmdTruncateFile; + struct + { + uint32 ukn0000; + betype<uint32> fileHandle; + }cmdGetStatFile; + struct + { + uint32 ukn0000; + uint8 path[FSA_CMD_PATH_MAX_LENGTH]; + }cmdFlushQuota; + }; + uint8 ukn0700[0x100]; + uint8 ukn0800[0x10]; + uint8 ukn0810[0x10]; + }; + + static_assert(sizeof(FSAIpcCommand) == 0x820); // exact size of this is not known + + void Initialize(); + void Shutdown(); + } +} diff --git a/src/Cafe/IOSU/iosu_ipc_common.h b/src/Cafe/IOSU/iosu_ipc_common.h new file mode 100644 index 00000000..f9ded224 --- /dev/null +++ b/src/Cafe/IOSU/iosu_ipc_common.h @@ -0,0 +1,73 @@ +#pragma once + +using IOSDevHandle = uint32; + +enum class IPCDriverState : uint32 +{ + CLOSED = 1, + INITIALIZED = 2, + READY = 3, + SUBMITTING = 4 +}; + +enum class IPCCommandId : uint32 +{ + IOS_OPEN = 1, + IOS_CLOSE = 2, + + IOS_IOCTL = 6, + IOS_IOCTLV = 7, +}; + +struct IPCIoctlVector +{ + MEMPTR<void> baseVirt; + uint32be size; + MEMPTR<void> basePhys; +}; + +static_assert(sizeof(IPCIoctlVector) == 0xC); + +struct IPCCommandBody +{ + /* +0x00 */ betype<IPCCommandId> cmdId; + /* +0x04 */ uint32be result; // set by IOSU + /* +0x08 */ betype<IOSDevHandle> devHandle; + /* +0x0C */ uint32be ukn0C; + /* +0x10 */ uint32be ukn10; + /* +0x14 */ uint32be ukn14; + /* +0x18 */ uint32be ukn18; + /* +0x1C */ uint32be ukn1C; + /* +0x20 */ uint32be ukn20; + /* +0x24 */ uint32be args[5]; + // anything below may only be present on the PPC side? + /* +0x38 */ betype<IPCCommandId> prev_cmdId; + /* +0x3C */ betype<IOSDevHandle> prev_devHandle; + /* +0x40 */ MEMPTR<void> ppcVirt0; + /* +0x44 */ MEMPTR<void> ppcVirt1; + + /* + IOS_Open: + args[0] = const char* path + args[1] = pathLen + 1 + args[2] = flags + + IOS_Close: + Only devHandle is set + + IOS_Ioctl: + args[0] = requestId + args[1] = ptrIn + args[2] = sizeIn + args[3] = ptrOut + args[4] = sizeOut + + IOS_Ioctlv: + args[0] = requestId + args[1] = numIn + args[2] = numOut + args[3] = IPCIoctlVector* + + + */ +}; diff --git a/src/Cafe/IOSU/iosu_types_common.h b/src/Cafe/IOSU/iosu_types_common.h new file mode 100644 index 00000000..94c0cd2c --- /dev/null +++ b/src/Cafe/IOSU/iosu_types_common.h @@ -0,0 +1,22 @@ +#pragma once + +using IOSMsgQueueId = uint32; + +// returned for syscalls +// maybe also shared with IPC? +enum IOS_ERROR : sint32 +{ + IOS_ERROR_OK = 0, + IOS_ERROR_INVALID = -4, + IOS_ERROR_MAXIMUM_REACHED = -5, + + IOS_ERROR_NONE_AVAILABLE = -7, // returned by non-blocking IOS_ReceiveMessage on empty message + IOS_ERROR_WOULD_BLOCK = -8, + + IOS_ERROR_INVALID_ARG = -29, +}; + +inline bool IOS_ResultIsError(const IOS_ERROR err) +{ + return (err & 0x80000000) != 0; +} \ No newline at end of file diff --git a/src/Cafe/IOSU/kernel/iosu_kernel.cpp b/src/Cafe/IOSU/kernel/iosu_kernel.cpp new file mode 100644 index 00000000..fb9a0191 --- /dev/null +++ b/src/Cafe/IOSU/kernel/iosu_kernel.cpp @@ -0,0 +1,556 @@ +#include "iosu_kernel.h" +#include "util/helpers/fspinlock.h" +#include "Cafe/OS/libs/coreinit/coreinit_IPC.h" + +namespace iosu +{ + namespace kernel + { + std::mutex sInternalMutex; + + static void _assume_lock() + { +#ifndef PUBLIC_RELEASE + cemu_assert_debug(!sInternalMutex.try_lock()); +#endif + } + + /* message queue */ + + struct IOSMessageQueue + { + // placeholder + /* +0x00 */ uint32be ukn00; + /* +0x04 */ uint32be ukn04; + /* +0x08 */ uint32be numQueuedMessages; + /* +0x0C */ uint32be readIndex; + /* +0x10 */ uint32be msgArraySize; // 0 if queue is not allocated + /* +0x14 */ MEMPTR<betype<IOSMessage>> msgArray; + /* +0x18 */ IOSMsgQueueId queueHandle; + /* +0x1C */ uint32be ukn1C; + + uint32 GetWriteIndex() + { + uint32 idx = readIndex + numQueuedMessages; + if (idx >= msgArraySize) + idx -= msgArraySize; + return idx; + } + + /* HLE extension */ + std::condition_variable cv_send; + std::condition_variable cv_recv; + }; + + std::array<IOSMessageQueue, 750> sMsgQueuePool; + + IOS_ERROR _IOS_GetMessageQueue(IOSMsgQueueId queueHandle, IOSMessageQueue*& queueOut) + { + _assume_lock(); + uint32 index = (queueHandle & 0xFFF); + if (index >= sMsgQueuePool.size()) + return IOS_ERROR_INVALID; + IOSMessageQueue& q = sMsgQueuePool.at(index); + if(q.queueHandle != queueHandle) + return IOS_ERROR_INVALID; + queueOut = &q; + return IOS_ERROR_OK; + } + + IOSMsgQueueId IOS_CreateMessageQueue(IOSMessage* messageArray, uint32 messageCount) + { + std::unique_lock _l(sInternalMutex); + cemu_assert(messageCount != 0); + auto it = std::find_if(sMsgQueuePool.begin(), sMsgQueuePool.end(), [](const IOSMessageQueue& q) { return q.msgArraySize == 0; }); + if (it == sMsgQueuePool.end()) + { + cemu_assert_suspicious(); + return IOS_ERROR_MAXIMUM_REACHED; + } + size_t index = std::distance(sMsgQueuePool.begin(), it); + IOSMessageQueue& msgQueue = sMsgQueuePool.at(index); + // create queue handle + static uint32 sQueueHandleCounter = 0; + uint32 queueHandle = (uint32)index | ((sQueueHandleCounter<<12)&0x7FFFFFFF); + sQueueHandleCounter++; + + msgQueue.queueHandle = queueHandle; + msgQueue.msgArraySize = messageCount; + msgQueue.msgArray = (betype<IOSMessage>*)messageArray; + + msgQueue.numQueuedMessages = 0; + msgQueue.readIndex = 0; + + return queueHandle; + } + + IOS_ERROR IOS_SendMessage(IOSMsgQueueId msgQueueId, IOSMessage message, uint32 flags) + { + std::unique_lock _l(sInternalMutex); + cemu_assert_debug(flags == 0 || flags == 1); + bool dontBlock = (flags & 1) != 0; + IOSMessageQueue* msgQueue = nullptr; + IOS_ERROR r = _IOS_GetMessageQueue(msgQueueId, msgQueue); + if (r != IOS_ERROR_OK) + return r; + while (true) + { + if (msgQueue->numQueuedMessages == msgQueue->msgArraySize) + { + if (dontBlock) + return IOS_ERROR_WOULD_BLOCK; + } + else + break; + msgQueue->cv_send.wait(_l); + // after returning from wait, make sure the queue handle is unchanged + if (msgQueue->queueHandle != msgQueueId) + return IOS_ERROR_INVALID; + } + uint32 writeIndex = msgQueue->GetWriteIndex(); + msgQueue->msgArray[writeIndex] = message; + msgQueue->numQueuedMessages += 1; + msgQueue->cv_recv.notify_one(); + return IOS_ERROR_OK; + } + + IOS_ERROR IOS_ReceiveMessage(IOSMsgQueueId msgQueueId, IOSMessage* messageOut, uint32 flags) + { + std::unique_lock _l(sInternalMutex); + cemu_assert_debug(flags == 0 || flags == 1); + bool dontBlock = (flags & 1) != 0; + IOSMessageQueue* msgQueue = nullptr; + IOS_ERROR r = _IOS_GetMessageQueue(msgQueueId, msgQueue); + if (r != IOS_ERROR_OK) + return r; + while (true) + { + if (msgQueue->numQueuedMessages == 0) + { + if (dontBlock) + return IOS_ERROR_NONE_AVAILABLE; + } + else + break; + msgQueue->cv_recv.wait(_l); + // after returning from wait, make sure the queue handle is unchanged + if (msgQueue->queueHandle != msgQueueId) + return IOS_ERROR_INVALID; + } + *messageOut = msgQueue->msgArray[(uint32)msgQueue->readIndex]; + msgQueue->readIndex = msgQueue->readIndex + 1; + if (msgQueue->readIndex >= msgQueue->msgArraySize) + msgQueue->readIndex -= msgQueue->msgArraySize; + msgQueue->numQueuedMessages -= 1; + msgQueue->cv_send.notify_one(); + return IOS_ERROR_OK; + } + + /* devices and IPC */ + + struct IOSResourceManager + { + bool isSet{false}; + std::string path; + IOSMsgQueueId msgQueueId; + }; + + std::array<IOSResourceManager, 512> sDeviceResources; + + IOSResourceManager* _IOS_FindResourceManager(const char* devicePath) + { + _assume_lock(); + std::string_view devicePathSV{ devicePath }; + for (auto& it : sDeviceResources) + { + if (it.isSet && it.path == devicePathSV) + return ⁢ + } + return nullptr; + } + + IOSResourceManager* _IOS_CreateNewResourceManager(const char* devicePath, IOSMsgQueueId msgQueueId) + { + _assume_lock(); + std::string_view devicePathSV{ devicePath }; + for (auto& it : sDeviceResources) + { + if (!it.isSet) + { + it.isSet = true; + it.path = devicePath; + it.msgQueueId = msgQueueId; + return ⁢ + } + } + return nullptr; + } + + IOS_ERROR IOS_RegisterResourceManager(const char* devicePath, IOSMsgQueueId msgQueueId) + { + std::unique_lock _lock(sInternalMutex); + if (_IOS_FindResourceManager(devicePath)) + { + cemu_assert_suspicious(); + return IOS_ERROR_INVALID; // correct error code? + } + + // verify if queue is valid + IOSMessageQueue* msgQueue; + IOS_ERROR r = _IOS_GetMessageQueue(msgQueueId, msgQueue); + if (r != IOS_ERROR_OK) + return r; + + // create resource manager + IOSResourceManager* resourceMgr = _IOS_CreateNewResourceManager(devicePath, msgQueueId); + if (!resourceMgr) + return IOS_ERROR_MAXIMUM_REACHED; + + return IOS_ERROR_OK; + } + + IOS_ERROR IOS_DeviceAssociateId(const char* devicePath, uint32 id) + { + // not yet implemented + return IOS_ERROR_OK; + } + + /* IPC */ + + struct IOSDispatchableCommand + { + // stores a copy of incoming IPC requests with some extra information required for replies + IPCCommandBody body; // our dispatchable copy + IPCIoctlVector vecCopy[8]; // our copy of the Ioctlv vector array + IPCCommandBody* originalBody; // the original command that was sent to us + uint32 ppcCoreIndex; + IOSDevHandle replyHandle; // handle for outgoing replies + bool isAllocated{false}; + }; + + SysAllocator<IOSDispatchableCommand, 96> sIPCDispatchableCommandPool; + std::queue<IOSDispatchableCommand*> sIPCFreeDispatchableCommands; + FSpinlock sIPCDispatchableCommandPoolLock; + + void _IPCInitDispatchablePool() + { + sIPCDispatchableCommandPoolLock.acquire(); + while (!sIPCFreeDispatchableCommands.empty()) + sIPCFreeDispatchableCommands.pop(); + for (size_t i = 0; i < sIPCDispatchableCommandPool.GetCount(); i++) + sIPCFreeDispatchableCommands.push(sIPCDispatchableCommandPool.GetPtr()+i); + sIPCDispatchableCommandPoolLock.release(); + } + + IOSDispatchableCommand* _IPCAllocateDispatchableCommand() + { + sIPCDispatchableCommandPoolLock.acquire(); + if (sIPCFreeDispatchableCommands.empty()) + { + cemuLog_log(LogType::Force, "IOS: Exhausted pool of dispatchable commands"); + sIPCDispatchableCommandPoolLock.release(); + return nullptr; + } + IOSDispatchableCommand* cmd = sIPCFreeDispatchableCommands.front(); + sIPCFreeDispatchableCommands.pop(); + cemu_assert_debug(!cmd->isAllocated); + cmd->isAllocated = true; + sIPCDispatchableCommandPoolLock.release(); + return cmd; + } + + void _IPCReleaseDispatchableCommand(IOSDispatchableCommand* cmd) + { + sIPCDispatchableCommandPoolLock.acquire(); + cemu_assert_debug(cmd->isAllocated); + cmd->isAllocated = false; + sIPCFreeDispatchableCommands.push(cmd); + sIPCDispatchableCommandPoolLock.release(); + } + + static constexpr size_t MAX_NUM_ACTIVE_DEV_HANDLES = 96; // per process + + struct IPCActiveDeviceHandle + { + bool isSet{false}; + uint32 handleCheckValue{0}; + std::string path; + IOSMsgQueueId msgQueueId; + // dispatch target handle (retrieved via IOS_OPEN command to dispatch target) + bool hasDispatchTargetHandle{false}; + IOSDevHandle dispatchTargetHandle; + }; + + IPCActiveDeviceHandle sActiveDeviceHandles[MAX_NUM_ACTIVE_DEV_HANDLES]; + + IOS_ERROR _IPCCreateResourceHandle(const char* devicePath, IOSDevHandle& handleOut) + { + std::unique_lock _lock(sInternalMutex); + static uint32 sHandleCreationCounter = 1; + // find resource manager for device + IOSResourceManager* resMgr = _IOS_FindResourceManager(devicePath); + if (!resMgr) + { + cemuLog_log(LogType::Force, "IOSU-Kernel: IOS_Open() could not open {}", devicePath); + return IOS_ERROR_INVALID; + } + IOSMsgQueueId msgQueueId = resMgr->msgQueueId; + _lock.unlock(); + // create new handle + sint32 deviceHandleIndex = -1; + for (size_t i = 0; i < MAX_NUM_ACTIVE_DEV_HANDLES; i++) + { + if (!sActiveDeviceHandles[i].isSet) + { + deviceHandleIndex = (sint32)i; + break; + } + } + cemu_assert_debug(deviceHandleIndex >= 0); + if (deviceHandleIndex < 0) + return IOS_ERROR_MAXIMUM_REACHED; + // calc handle + uint32 devHandle = deviceHandleIndex | ((sHandleCreationCounter << 12) & 0x7FFFFFFF); + sHandleCreationCounter++; + // init handle instance + sActiveDeviceHandles[deviceHandleIndex].isSet = true; + sActiveDeviceHandles[deviceHandleIndex].handleCheckValue = devHandle; + sActiveDeviceHandles[deviceHandleIndex].path = devicePath; + sActiveDeviceHandles[deviceHandleIndex].msgQueueId = msgQueueId; + sActiveDeviceHandles[deviceHandleIndex].hasDispatchTargetHandle = false; + handleOut = devHandle; + return IOS_ERROR_OK; + } + + IOS_ERROR _IPCDestroyResourceHandle(IOSDevHandle devHandle) + { + std::unique_lock _lock(sInternalMutex); + uint32 index = devHandle & 0xFFF; + cemu_assert(index < MAX_NUM_ACTIVE_DEV_HANDLES); + if (!sActiveDeviceHandles[index].isSet) + { + cemuLog_log(LogType::Force, "_IPCDispatchToResourceManager(): Resource manager destroyed before all IPC commands were processed"); + return IOS_ERROR_INVALID; + } + if (devHandle != sActiveDeviceHandles[index].handleCheckValue) + { + cemuLog_log(LogType::Force, "_IPCDispatchToResourceManager(): Mismatching handle"); + return IOS_ERROR_INVALID; + } + sActiveDeviceHandles[index].isSet = false; + sActiveDeviceHandles[index].handleCheckValue = 0; + sActiveDeviceHandles[index].hasDispatchTargetHandle = false; + _lock.unlock(); + return IOS_ERROR_OK; + } + + IOS_ERROR _IPCAssignDispatchTargetHandle(IOSDevHandle devHandle, IOSDevHandle internalHandle) + { + std::unique_lock _lock(sInternalMutex); + uint32 index = devHandle & 0xFFF; + cemu_assert(index < MAX_NUM_ACTIVE_DEV_HANDLES); + if (!sActiveDeviceHandles[index].isSet) + { + cemuLog_log(LogType::Force, "_IPCDispatchToResourceManager(): Resource manager destroyed before all IPC commands were processed"); + return IOS_ERROR_INVALID; + } + if (devHandle != sActiveDeviceHandles[index].handleCheckValue) + { + cemuLog_log(LogType::Force, "_IPCDispatchToResourceManager(): Mismatching handle"); + return IOS_ERROR_INVALID; + } + cemu_assert_debug(!sActiveDeviceHandles[index].hasDispatchTargetHandle); + sActiveDeviceHandles[index].hasDispatchTargetHandle = true; + sActiveDeviceHandles[index].dispatchTargetHandle = internalHandle; + _lock.unlock(); + return IOS_ERROR_OK; + } + + IOS_ERROR _IPCDispatchToResourceManager(IOSDevHandle devHandle, IOSDispatchableCommand* dispatchCmd) + { + std::unique_lock _lock(sInternalMutex); + uint32 index = devHandle & 0xFFF; + cemu_assert(index < MAX_NUM_ACTIVE_DEV_HANDLES); + if (!sActiveDeviceHandles[index].isSet) + { + cemuLog_log(LogType::Force, "_IPCDispatchToResourceManager(): Resource manager destroyed before all IPC commands were processed"); + return IOS_ERROR_INVALID; + } + if (devHandle != sActiveDeviceHandles[index].handleCheckValue) + { + cemuLog_log(LogType::Force, "_IPCDispatchToResourceManager(): Mismatching handle"); + return IOS_ERROR_INVALID; + } + IOSMsgQueueId msgQueueId = sActiveDeviceHandles[index].msgQueueId; + if (dispatchCmd->body.cmdId == IPCCommandId::IOS_OPEN) + { + cemu_assert(!sActiveDeviceHandles[index].hasDispatchTargetHandle); + dispatchCmd->body.devHandle = 0; + } + else + { + cemu_assert(sActiveDeviceHandles[index].hasDispatchTargetHandle); + dispatchCmd->body.devHandle = sActiveDeviceHandles[index].dispatchTargetHandle; + } + _lock.unlock(); + MEMPTR<IOSDispatchableCommand> msgVal{ dispatchCmd }; + IOS_ERROR r = IOS_SendMessage(msgQueueId, msgVal.GetMPTR(), 1); + if(r != IOS_ERROR_OK) + cemuLog_log(LogType::Force, "_IPCDispatchToResourceManager(): SendMessage returned {}", (sint32)r); + return r; + } + + void _IPCReplyAndRelease(IOSDispatchableCommand* dispatchCmd, uint32 result) + { + cemu_assert(dispatchCmd >= sIPCDispatchableCommandPool.GetPtr() && dispatchCmd < sIPCDispatchableCommandPool.GetPtr() + sIPCDispatchableCommandPool.GetCount()); + dispatchCmd->originalBody->result = result; + // submit to COS + IPCCommandBody* responseArray[1]; + responseArray[0] = dispatchCmd->originalBody; + coreinit::IPCDriver_NotifyResponses(dispatchCmd->ppcCoreIndex, responseArray, 1); + _IPCReleaseDispatchableCommand(dispatchCmd); + } + + IOS_ERROR _IPCHandlerIn_IOS_Open(IOSDispatchableCommand* dispatchCmd) + { + IPCCommandBody& cmd = dispatchCmd->body; + const char* name = MEMPTR<const char>(cmd.args[0]).GetPtr(); + uint32 nameLenPlusOne = cmd.args[1]; + cemu_assert(nameLenPlusOne > 0); + uint32 flags = cmd.args[2]; + cemu_assert_debug(flags == 0); + + std::string devicePath{ name, nameLenPlusOne - 1 }; + + IOSDevHandle handle; + IOS_ERROR r = _IPCCreateResourceHandle(devicePath.c_str(), handle); + if (r != IOS_ERROR_OK) + return r; + dispatchCmd->replyHandle = handle; + dispatchCmd->body.devHandle = 0; + r = _IPCDispatchToResourceManager(handle, dispatchCmd); + return r; + } + + IOS_ERROR _IPCHandlerIn_IOS_Close(IOSDispatchableCommand* dispatchCmd) + { + IPCCommandBody& cmd = dispatchCmd->body; + IOS_ERROR r = _IPCDispatchToResourceManager(dispatchCmd->body.devHandle, dispatchCmd); + return r; + } + + IOS_ERROR _IPCHandlerIn_IOS_Ioctl(IOSDispatchableCommand* dispatchCmd) + { + IPCCommandBody& cmd = dispatchCmd->body; + IOS_ERROR r = _IPCDispatchToResourceManager(dispatchCmd->body.devHandle, dispatchCmd); + return r; + } + + IOS_ERROR _IPCHandlerIn_IOS_Ioctlv(IOSDispatchableCommand* dispatchCmd) + { + IPCCommandBody& cmd = dispatchCmd->body; + uint32 requestId = dispatchCmd->body.args[0]; + uint32 numIn = dispatchCmd->body.args[1]; + uint32 numOut = dispatchCmd->body.args[2]; + IPCIoctlVector* vec = MEMPTR<IPCIoctlVector>(cmd.args[3]).GetPtr(); + + // copy the vector array + uint32 numVec = numIn + numOut; + if (numVec <= 8) + { + std::copy(vec, vec + numVec, dispatchCmd->vecCopy); + dispatchCmd->body.args[3] = MEMPTR<IPCIoctlVector>(vec).GetMPTR(); + } + else + { + // reuse the original vector pointer + cemuLog_log(LogType::Force, "Info: Ioctlv command with more than 8 vectors"); + } + IOS_ERROR r = _IPCDispatchToResourceManager(dispatchCmd->body.devHandle, dispatchCmd); + return r; + } + + // called by COS directly + void IPCSubmitFromCOS(uint32 ppcCoreIndex, IPCCommandBody* cmd) + { + // create a copy of the cmd + IOSDispatchableCommand* dispatchCmd = _IPCAllocateDispatchableCommand(); + dispatchCmd->body = *cmd; + dispatchCmd->originalBody = cmd; + dispatchCmd->ppcCoreIndex = ppcCoreIndex; + dispatchCmd->replyHandle = cmd->devHandle; + // forward command to device + IOS_ERROR r = IOS_ERROR_INVALID; + switch ((IPCCommandId)cmd->cmdId) + { + case IPCCommandId::IOS_OPEN: + dispatchCmd->replyHandle = 0; + r = _IPCHandlerIn_IOS_Open(dispatchCmd); + break; + case IPCCommandId::IOS_CLOSE: + r = _IPCHandlerIn_IOS_Close(dispatchCmd); + break; + case IPCCommandId::IOS_IOCTL: + r = _IPCHandlerIn_IOS_Ioctl(dispatchCmd); + break; + case IPCCommandId::IOS_IOCTLV: + r = _IPCHandlerIn_IOS_Ioctlv(dispatchCmd); + break; + default: + cemuLog_log(LogType::Force, "Invalid IPC command {}", (uint32)(IPCCommandId)cmd->cmdId); + break; + } + if (r < 0) + { + cemuLog_log(LogType::Force, "Error occurred while trying to dispatch IPC"); + _IPCReplyAndRelease(dispatchCmd, r); + // in non-error case the device handler will send the result asynchronously via IOS_ResourceReply + } + } + + IOS_ERROR IOS_ResourceReply(IPCCommandBody* cmd, IOS_ERROR result) + { + IOSDispatchableCommand* dispatchCmd = (IOSDispatchableCommand*)cmd; + cemu_assert(dispatchCmd >= sIPCDispatchableCommandPool.GetPtr() && dispatchCmd < sIPCDispatchableCommandPool.GetPtr() + sIPCDispatchableCommandPool.GetCount()); + cemu_assert_debug(dispatchCmd->isAllocated); + dispatchCmd->originalBody->result = result; + if (dispatchCmd->originalBody->cmdId == IPCCommandId::IOS_OPEN) + { + IOSDevHandle devHandle = dispatchCmd->replyHandle; + if (IOS_ResultIsError(result)) + { + cemuLog_log(LogType::Force, "IOS_ResourceReply(): Target device triggered an error on IOS_OPEN"); + // dispatch target returned error, destroy our device handle again + IOS_ERROR r = _IPCDestroyResourceHandle(devHandle); + cemu_assert(r == IOS_ERROR_OK); + } + else + { + cemu_assert(_IPCAssignDispatchTargetHandle(devHandle, (IOSDevHandle)result) == IOS_ERROR_OK); + result = (IOS_ERROR)(uint32)devHandle; + } + } + else if (dispatchCmd->originalBody->cmdId == IPCCommandId::IOS_CLOSE) + { + if (IOS_ResultIsError(result)) + { + cemuLog_log(LogType::Force, "IOS_ResourceReply(): Target device triggered an error on IOS_CLOSE"); + } + // reply, then destroy handle + IOSDevHandle devHandle = dispatchCmd->replyHandle; + _IPCReplyAndRelease(dispatchCmd, result); + IOS_ERROR r = _IPCDestroyResourceHandle(devHandle); + cemu_assert_debug(r == IOS_ERROR::IOS_ERROR_OK); + return IOS_ERROR_OK; + } + _IPCReplyAndRelease(dispatchCmd, result); + return IOS_ERROR_OK; + } + + void Initialize() + { + _IPCInitDispatchablePool(); + } + + } +} \ No newline at end of file diff --git a/src/Cafe/IOSU/kernel/iosu_kernel.h b/src/Cafe/IOSU/kernel/iosu_kernel.h new file mode 100644 index 00000000..2b82374e --- /dev/null +++ b/src/Cafe/IOSU/kernel/iosu_kernel.h @@ -0,0 +1,23 @@ +#pragma once +#include "Cafe/IOSU/iosu_ipc_common.h" +#include "Cafe/IOSU/iosu_types_common.h" + +namespace iosu +{ + namespace kernel + { + using IOSMessage = uint32; + + IOSMsgQueueId IOS_CreateMessageQueue(IOSMessage* messageArray, uint32 messageCount); + IOS_ERROR IOS_SendMessage(IOSMsgQueueId msgQueueId, IOSMessage message, uint32 flags); + IOS_ERROR IOS_ReceiveMessage(IOSMsgQueueId msgQueueId, IOSMessage* messageOut, uint32 flags); + + IOS_ERROR IOS_RegisterResourceManager(const char* devicePath, IOSMsgQueueId msgQueueId); + IOS_ERROR IOS_DeviceAssociateId(const char* devicePath, uint32 id); + IOS_ERROR IOS_ResourceReply(IPCCommandBody* cmd, IOS_ERROR result); + + void IPCSubmitFromCOS(uint32 ppcCoreIndex, IPCCommandBody* cmd); + + void Initialize(); + } +} \ No newline at end of file diff --git a/src/Cafe/IOSU/legacy/iosu_acp.cpp b/src/Cafe/IOSU/legacy/iosu_acp.cpp new file mode 100644 index 00000000..828044a0 --- /dev/null +++ b/src/Cafe/IOSU/legacy/iosu_acp.cpp @@ -0,0 +1,612 @@ +#include "iosu_ioctl.h" +#include "iosu_acp.h" +#include "Cafe/OS/libs/nn_common.h" +#include "util/tinyxml2/tinyxml2.h" +#include "Cafe/OS/libs/coreinit/coreinit_Time.h" +#include "Cafe/OS/libs/nn_save/nn_save.h" +#include "util/helpers/helpers.h" +#include "Cafe/OS/libs/nn_acp/nn_acp.h" +#include "Cafe/OS/libs/coreinit/coreinit_FS.h" +#include "Cafe/Filesystem/fsc.h" +#include "Cafe/HW/Espresso/PPCState.h" + +static_assert(sizeof(acpMetaXml_t) == 0x3440); +static_assert(offsetof(acpMetaXml_t, title_id) == 0x0000); +static_assert(offsetof(acpMetaXml_t, boss_id) == 0x0008); +static_assert(offsetof(acpMetaXml_t, os_version) == 0x0010); +static_assert(offsetof(acpMetaXml_t, app_size) == 0x0018); +static_assert(offsetof(acpMetaXml_t, common_save_size) == 0x0020); + +static_assert(offsetof(acpMetaXml_t, version) == 0x0048); +static_assert(offsetof(acpMetaXml_t, product_code) == 0x004C); +static_assert(offsetof(acpMetaXml_t, logo_type) == 0x00B4); +static_assert(offsetof(acpMetaXml_t, pc_cero) == 0x0100); + +static_assert(offsetof(acpMetaXml_t, longname_ja) == 0x038C); +static_assert(offsetof(acpMetaXml_t, shortname_ja) == 0x1B8C); +static_assert(offsetof(acpMetaXml_t, publisher_ja) == 0x278C); + +static_assert(sizeof(acpMetaData_t) == 0x1AB00); +static_assert(offsetof(acpMetaData_t, bootMovie) == 0); +static_assert(offsetof(acpMetaData_t, bootLogoTex) == 0x13B38); + +namespace iosu +{ + + struct + { + bool isInitialized; + }iosuAcp = { 0 }; + + void _xml_parseU32(tinyxml2::XMLElement* xmlElement, const char* name, uint32be* v) + { + tinyxml2::XMLElement* subElement = xmlElement->FirstChildElement(name); + *v = 0; + if (subElement == nullptr) + return; + const char* text = subElement->GetText(); + uint32 value; + if (sscanf(text, "%u", &value) == 0) + return; + *v = value; + } + + void _xml_parseHex16(tinyxml2::XMLElement* xmlElement, const char* name, uint16be* v) + { + tinyxml2::XMLElement* subElement = xmlElement->FirstChildElement(name); + *v = 0; + if (subElement == nullptr) + return; + const char* text = subElement->GetText(); + uint32 value; + if (sscanf(text, "%x", &value) == 0) + return; + *v = value; + } + + void _xml_parseHex32(tinyxml2::XMLElement* xmlElement, const char* name, uint32be* v) + { + tinyxml2::XMLElement* subElement = xmlElement->FirstChildElement(name); + *v = 0; + if (subElement == nullptr) + return; + const char* text = subElement->GetText(); + uint32 value; + if (sscanf(text, "%x", &value) == 0) + return; + *v = value; + } + + void _xml_parseHex64(tinyxml2::XMLElement* xmlElement, const char* name, uint64* v) + { + tinyxml2::XMLElement* subElement = xmlElement->FirstChildElement(name); + *v = 0; + if (subElement == nullptr) + return; + const char* text = subElement->GetText(); + uint64 value; + if (sscanf(text, "%llx", &value) == 0) + return; + *v = _swapEndianU64(value); + } + + void _xml_parseString_(tinyxml2::XMLElement* xmlElement, const char* name, char* output, sint32 maxLength) + { + tinyxml2::XMLElement* subElement = xmlElement->FirstChildElement(name); + output[0] = '\0'; + if (subElement == nullptr) + return; + const char* text = subElement->GetText(); + if (text == nullptr) + { + output[0] = '\0'; + return; + } + strncpy(output, text, maxLength - 1); + output[maxLength - 1] = '\0'; + } + +#define _metaXml_parseString(__xmlElement, __name, __output) _xml_parseString_(__xmlElement, __name, __output, sizeof(__output)); + + void parseSaveMetaXml(uint8* metaXmlData, sint32 metaXmlLength, acpMetaXml_t* metaXml) + { + memset(metaXml, 0, sizeof(acpMetaXml_t)); + tinyxml2::XMLDocument appXml; + appXml.Parse((const char*)metaXmlData, metaXmlLength); + uint32 titleVersion = 0xFFFFFFFF; + tinyxml2::XMLElement* menuElement = appXml.FirstChildElement("menu"); + if (menuElement) + { + _xml_parseHex64(menuElement, "title_id", &metaXml->title_id); + _xml_parseHex64(menuElement, "boss_id", &metaXml->boss_id); + _xml_parseHex64(menuElement, "os_version", &metaXml->os_version); + _xml_parseHex64(menuElement, "app_size", &metaXml->app_size); + _xml_parseHex64(menuElement, "common_save_size", &metaXml->common_save_size); + _xml_parseHex64(menuElement, "account_save_size", &metaXml->account_save_size); + _xml_parseHex64(menuElement, "common_boss_size", &metaXml->common_boss_size); + _xml_parseHex64(menuElement, "account_boss_size", &metaXml->account_boss_size); + _xml_parseHex64(menuElement, "join_game_mode_mask", &metaXml->join_game_mode_mask); + _xml_parseU32(menuElement, "version", &metaXml->version); + _metaXml_parseString(menuElement, "product_code", metaXml->product_code); + _metaXml_parseString(menuElement, "content_platform", metaXml->content_platform); + _metaXml_parseString(menuElement, "company_code", metaXml->company_code); + _metaXml_parseString(menuElement, "mastering_date", metaXml->mastering_date); + _xml_parseU32(menuElement, "logo_type", &metaXml->logo_type); + + _xml_parseU32(menuElement, "app_launch_type", &metaXml->app_launch_type); + _xml_parseU32(menuElement, "invisible_flag", &metaXml->invisible_flag); + _xml_parseU32(menuElement, "no_managed_flag", &metaXml->no_managed_flag); + _xml_parseU32(menuElement, "no_event_log", &metaXml->no_event_log); + _xml_parseU32(menuElement, "no_icon_database", &metaXml->no_icon_database); + _xml_parseU32(menuElement, "launching_flag", &metaXml->launching_flag); + _xml_parseU32(menuElement, "install_flag", &metaXml->install_flag); + _xml_parseU32(menuElement, "closing_msg", &metaXml->closing_msg); + _xml_parseU32(menuElement, "title_version", &metaXml->title_version); + _xml_parseHex32(menuElement, "group_id", &metaXml->group_id); + _xml_parseU32(menuElement, "save_no_rollback", &metaXml->save_no_rollback); + _xml_parseU32(menuElement, "bg_daemon_enable", &metaXml->bg_daemon_enable); + _xml_parseHex32(menuElement, "join_game_id", &metaXml->join_game_id); + + _xml_parseU32(menuElement, "olv_accesskey", &metaXml->olv_accesskey); + _xml_parseU32(menuElement, "wood_tin", &metaXml->wood_tin); + _xml_parseU32(menuElement, "e_manual", &metaXml->e_manual); + _xml_parseU32(menuElement, "e_manual_version", &metaXml->e_manual_version); + _xml_parseHex32(menuElement, "region", &metaXml->region); + + _xml_parseU32(menuElement, "pc_cero", &metaXml->pc_cero); + _xml_parseU32(menuElement, "pc_esrb", &metaXml->pc_esrb); + _xml_parseU32(menuElement, "pc_bbfc", &metaXml->pc_bbfc); + _xml_parseU32(menuElement, "pc_usk", &metaXml->pc_usk); + _xml_parseU32(menuElement, "pc_pegi_gen", &metaXml->pc_pegi_gen); + _xml_parseU32(menuElement, "pc_pegi_fin", &metaXml->pc_pegi_fin); + _xml_parseU32(menuElement, "pc_pegi_prt", &metaXml->pc_pegi_prt); + _xml_parseU32(menuElement, "pc_pegi_bbfc", &metaXml->pc_pegi_bbfc); + + _xml_parseU32(menuElement, "pc_cob", &metaXml->pc_cob); + _xml_parseU32(menuElement, "pc_grb", &metaXml->pc_grb); + _xml_parseU32(menuElement, "pc_cgsrr", &metaXml->pc_cgsrr); + _xml_parseU32(menuElement, "pc_oflc", &metaXml->pc_oflc); + + _xml_parseU32(menuElement, "pc_reserved0", &metaXml->pc_reserved0); + _xml_parseU32(menuElement, "pc_reserved1", &metaXml->pc_reserved1); + _xml_parseU32(menuElement, "pc_reserved2", &metaXml->pc_reserved2); + _xml_parseU32(menuElement, "pc_reserved3", &metaXml->pc_reserved3); + + _xml_parseU32(menuElement, "ext_dev_nunchaku", &metaXml->ext_dev_nunchaku); + _xml_parseU32(menuElement, "ext_dev_classic", &metaXml->ext_dev_classic); + _xml_parseU32(menuElement, "ext_dev_urcc", &metaXml->ext_dev_urcc); + _xml_parseU32(menuElement, "ext_dev_board", &metaXml->ext_dev_board); + _xml_parseU32(menuElement, "ext_dev_usb_keyboard", &metaXml->ext_dev_usb_keyboard); + _xml_parseU32(menuElement, "ext_dev_etc", &metaXml->ext_dev_etc); + + _metaXml_parseString(menuElement, "ext_dev_etc_name", metaXml->ext_dev_etc_name); + + _xml_parseU32(menuElement, "eula_version", &metaXml->eula_version); + _xml_parseU32(menuElement, "drc_use", &metaXml->drc_use); + _xml_parseU32(menuElement, "network_use", &metaXml->network_use); + _xml_parseU32(menuElement, "online_account_use", &metaXml->online_account_use); + _xml_parseU32(menuElement, "direct_boot", &metaXml->direct_boot); + _xml_parseU32(menuElement, "reserved_flag0", &(metaXml->reserved_flag[0])); + _xml_parseU32(menuElement, "reserved_flag1", &(metaXml->reserved_flag[1])); + _xml_parseU32(menuElement, "reserved_flag2", &(metaXml->reserved_flag[2])); + _xml_parseU32(menuElement, "reserved_flag3", &(metaXml->reserved_flag[3])); + _xml_parseU32(menuElement, "reserved_flag4", &(metaXml->reserved_flag[4])); + _xml_parseU32(menuElement, "reserved_flag5", &(metaXml->reserved_flag[5])); + _xml_parseU32(menuElement, "reserved_flag6", &(metaXml->reserved_flag[6])); + _xml_parseU32(menuElement, "reserved_flag7", &(metaXml->reserved_flag[7])); + + _metaXml_parseString(menuElement, "longname_ja", metaXml->longname_ja); + _metaXml_parseString(menuElement, "longname_en", metaXml->longname_en); + _metaXml_parseString(menuElement, "longname_fr", metaXml->longname_fr); + _metaXml_parseString(menuElement, "longname_de", metaXml->longname_de); + _metaXml_parseString(menuElement, "longname_it", metaXml->longname_it); + _metaXml_parseString(menuElement, "longname_es", metaXml->longname_es); + _metaXml_parseString(menuElement, "longname_zhs", metaXml->longname_zhs); + _metaXml_parseString(menuElement, "longname_ko", metaXml->longname_ko); + _metaXml_parseString(menuElement, "longname_nl", metaXml->longname_nl); + _metaXml_parseString(menuElement, "longname_pt", metaXml->longname_pt); + _metaXml_parseString(menuElement, "longname_ru", metaXml->longname_ru); + _metaXml_parseString(menuElement, "longname_zht", metaXml->longname_zht); + + _metaXml_parseString(menuElement, "shortname_ja", metaXml->shortname_ja); + _metaXml_parseString(menuElement, "shortname_en", metaXml->shortname_en); + _metaXml_parseString(menuElement, "shortname_fr", metaXml->shortname_fr); + _metaXml_parseString(menuElement, "shortname_de", metaXml->shortname_de); + _metaXml_parseString(menuElement, "shortname_it", metaXml->shortname_it); + _metaXml_parseString(menuElement, "shortname_es", metaXml->shortname_es); + _metaXml_parseString(menuElement, "shortname_zhs", metaXml->shortname_zhs); + _metaXml_parseString(menuElement, "shortname_ko", metaXml->shortname_ko); + _metaXml_parseString(menuElement, "shortname_nl", metaXml->shortname_nl); + _metaXml_parseString(menuElement, "shortname_pt", metaXml->shortname_pt); + _metaXml_parseString(menuElement, "shortname_ru", metaXml->shortname_ru); + _metaXml_parseString(menuElement, "shortname_zht", metaXml->shortname_zht); + + _metaXml_parseString(menuElement, "publisher_ja", metaXml->publisher_ja); + _metaXml_parseString(menuElement, "publisher_en", metaXml->publisher_en); + _metaXml_parseString(menuElement, "publisher_fr", metaXml->publisher_fr); + _metaXml_parseString(menuElement, "publisher_de", metaXml->publisher_de); + _metaXml_parseString(menuElement, "publisher_it", metaXml->publisher_it); + _metaXml_parseString(menuElement, "publisher_es", metaXml->publisher_es); + _metaXml_parseString(menuElement, "publisher_zhs", metaXml->publisher_zhs); + _metaXml_parseString(menuElement, "publisher_ko", metaXml->publisher_ko); + _metaXml_parseString(menuElement, "publisher_nl", metaXml->publisher_nl); + _metaXml_parseString(menuElement, "publisher_pt", metaXml->publisher_pt); + _metaXml_parseString(menuElement, "publisher_ru", metaXml->publisher_ru); + _metaXml_parseString(menuElement, "publisher_zht", metaXml->publisher_zht); + + for (sint32 i = 0; i < 32; i++) + { + char tempStr[256]; + sprintf(tempStr, "add_on_unique_id%d", i); + _xml_parseU32(menuElement, tempStr, &(metaXml->add_on_unique_id[i])); + } + } + } + + bool _is8DigitHex(const char* str) + { + if (strlen(str) != 8) + return false; + for (sint32 f = 0; f < 8; f++) + { + if (str[f] >= '0' && str[f] <= '9') + continue; + if (str[f] >= 'a' && str[f] <= 'f') + continue; + if (str[f] >= 'A' && str[f] <= 'F') + continue; + return false; + } + return true; + } + + sint32 ACPGetSaveDataTitleIdList(uint32 storageDeviceGuessed, acpTitleId_t* titleIdList, sint32 maxCount, uint32be* countOut) + { + sint32 count = 0; + + const char* devicePath = "/vol/storage_mlc01/"; + if (storageDeviceGuessed != 3) + cemu_assert_unimplemented(); + char searchPath[FSA_CMD_PATH_MAX_LENGTH]; + sprintf(searchPath, "%susr/save/", devicePath); + sint32 fscStatus = 0; + FSCVirtualFile* fscDirIteratorTitleIdHigh = fsc_openDirIterator(searchPath, &fscStatus); + FSCDirEntry dirEntryTitleIdHigh; + FSCDirEntry dirEntryTitleIdLow; + if(fscDirIteratorTitleIdHigh) + { + while (fsc_nextDir(fscDirIteratorTitleIdHigh, &dirEntryTitleIdHigh)) + { + // is 8-digit hex? + if(_is8DigitHex(dirEntryTitleIdHigh.path) == false) + continue; + uint32 titleIdHigh; + sscanf(dirEntryTitleIdHigh.path, "%x", &titleIdHigh); + sprintf(searchPath, "%susr/save/%08x/", devicePath, titleIdHigh); + FSCVirtualFile* fscDirIteratorTitleIdLow = fsc_openDirIterator(searchPath, &fscStatus); + if (fscDirIteratorTitleIdLow) + { + while (fsc_nextDir(fscDirIteratorTitleIdLow, &dirEntryTitleIdLow)) + { + // is 8-digit hex? + if (_is8DigitHex(dirEntryTitleIdLow.path) == false) + continue; + uint32 titleIdLow; + sscanf(dirEntryTitleIdLow.path, "%x", &titleIdLow); + // check if /meta/meta.xml exists + char tempPath[FSA_CMD_PATH_MAX_LENGTH]; + sprintf(tempPath, "%susr/save/%08x/%08x/meta/meta.xml", devicePath, titleIdHigh, titleIdLow); + if (fsc_doesFileExist(tempPath)) + { + if (count < maxCount) + { + titleIdList[count].titleIdHigh = titleIdHigh; + titleIdList[count].titleIdLow = titleIdLow; + count++; + } + } + else + { + forceLogDebug_printf("ACPGetSaveDataTitleIdList(): Missing meta.xml for save %08x-%08x", titleIdHigh, titleIdLow); + } + } + fsc_close(fscDirIteratorTitleIdLow); + } + } + fsc_close(fscDirIteratorTitleIdHigh); + } + *countOut = count; + return 0; + } + + sint32 ACPGetTitleSaveMetaXml(uint64 titleId, acpMetaXml_t* acpMetaXml, sint32 uknType) + { + // uknType is probably the storage device? + if (uknType != 3) // mlc01 ? + assert_dbg(); + + char xmlPath[FSA_CMD_PATH_MAX_LENGTH]; + sprintf(xmlPath, "%susr/save/%08x/%08x/meta/meta.xml", "/vol/storage_mlc01/", (uint32)(titleId>>32), (uint32)(titleId&0xFFFFFFFF)); + + uint32 saveMetaXmlSize = 0; + uint8* saveMetaXmlData = fsc_extractFile(xmlPath, &saveMetaXmlSize); + if (saveMetaXmlData) + { + parseSaveMetaXml(saveMetaXmlData, saveMetaXmlSize, acpMetaXml); + free(saveMetaXmlData); + } + else + { + forceLog_printf("ACPGetTitleSaveMetaXml(): Meta file \"%s\" does not exist", xmlPath); + memset(acpMetaXml, 0, sizeof(acpMetaXml_t)); + } + return 0; + } + + sint32 ACPGetTitleMetaData(uint64 titleId, acpMetaData_t* acpMetaData) + { + memset(acpMetaData, 0, sizeof(acpMetaData_t)); + + char titlePath[1024]; + + if (((titleId >> 32) & 0x10) != 0) + { + sprintf(titlePath, "/vol/storage_mlc01/sys/title/%08x/%08x/", (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF)); + } + else + { + sprintf(titlePath, "/vol/storage_mlc01/usr/title/%08x/%08x/", (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF)); + } + + + char filePath[FSA_CMD_PATH_MAX_LENGTH]; + sprintf(filePath, "%smeta/bootMovie.h264", titlePath); + + // bootMovie.h264 + uint32 metaBootMovieSize = 0; + uint8* metaBootMovieData = fsc_extractFile(filePath, &metaBootMovieSize); + if (metaBootMovieData) + { + memcpy(acpMetaData->bootMovie, metaBootMovieData, std::min<uint32>(metaBootMovieSize, sizeof(acpMetaData->bootMovie))); + free(metaBootMovieData); + } + else + forceLog_printf("ACPGetTitleMetaData(): Unable to load \"%s\"", filePath); + // bootLogoTex.tga + sprintf(filePath, "%smeta/bootLogoTex.tga", titlePath); + uint32 metaBootLogoSize = 0; + uint8* metaBootLogoData = fsc_extractFile(filePath, &metaBootLogoSize); + if (metaBootLogoData) + { + memcpy(acpMetaData->bootLogoTex, metaBootLogoData, std::min<uint32>(metaBootLogoSize, sizeof(acpMetaData->bootLogoTex))); + free(metaBootLogoData); + } + else + forceLog_printf("ACPGetTitleMetaData(): Unable to load \"%s\"", filePath); + + + return 0; + } + + sint32 ACPGetTitleMetaXml(uint64 titleId, acpMetaXml_t* acpMetaXml) + { + memset(acpMetaXml, 0, sizeof(acpMetaXml_t)); + + char titlePath[1024]; + + if (((titleId >> 32) & 0x10) != 0) + { + sprintf(titlePath, "/vol/storage_mlc01/sys/title/%08x/%08x/", (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF)); + } + else + { + sprintf(titlePath, "/vol/storage_mlc01/usr/title/%08x/%08x/", (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF)); + } + + + char filePath[FSA_CMD_PATH_MAX_LENGTH]; + sprintf(filePath, "%smeta/meta.xml", titlePath); + + uint32 metaXmlSize = 0; + uint8* metaXmlData = fsc_extractFile(filePath, &metaXmlSize); + if (metaXmlData) + { + parseSaveMetaXml(metaXmlData, metaXmlSize, acpMetaXml); + free(metaXmlData); + } + else + { + forceLog_printf("ACPGetTitleMetaXml(): Meta file \"%s\" does not exist", filePath); + } + + return 0; + } + + sint32 ACPGetTitleSaveDirEx(uint64 titleId, uint32 storageDeviceGuessed, acpSaveDirInfo_t* saveDirInfo, sint32 maxCount, uint32be* countOut) + { + sint32 count = 0; + + const char* devicePath = "/vol/storage_mlc01/"; + if (storageDeviceGuessed != 3) + cemu_assert_unimplemented(); + + char searchPath[FSA_CMD_PATH_MAX_LENGTH]; + char tempPath[FSA_CMD_PATH_MAX_LENGTH]; + + sint32 fscStatus = 0; + // add common dir + sprintf(searchPath, "%susr/save/%08x/%08x/user/common/", devicePath, (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF)); + if (fsc_doesDirectoryExist(searchPath)) + { + acpSaveDirInfo_t* entry = saveDirInfo + count; + if (count < maxCount) + { + // get dir size + sprintf(tempPath, "%susr/save/%08x/%08x/user/common/", devicePath, (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF)); + FSCVirtualFile* fscDir = fsc_open(tempPath, FSC_ACCESS_FLAG::OPEN_DIR, &fscStatus); + uint64 dirSize = 0; + if (fscDir) + { + dirSize = fsc_getFileSize(fscDir); + fsc_close(fscDir); + } + + memset(entry, 0, sizeof(acpSaveDirInfo_t)); + entry->ukn00 = (uint32)(titleId>>32); + entry->ukn04 = (uint32)(titleId&0xFFFFFFFF); + entry->persistentId = 0; // 0 -> common save + entry->ukn0C = 0; + entry->sizeA = _swapEndianU64(0); // ukn + entry->sizeB = _swapEndianU64(dirSize); + entry->time = _swapEndianU64((coreinit::coreinit_getOSTime() / ESPRESSO_TIMER_CLOCK)); + sprintf(entry->path, "%susr/save/%08x/%08x/meta/", devicePath, (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF)); + count++; + } + } + // add user directories + sprintf(searchPath, "%susr/save/%08x/%08x/user/", devicePath, (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF)); + FSCVirtualFile* fscDirIterator = fsc_openDirIterator(searchPath, &fscStatus); + if (fscDirIterator == nullptr) + { + forceLog_printf("ACPGetTitleSaveDirEx(): Failed to iterate directories in \"%s\"", searchPath); + *countOut = 0; + } + else + { + FSCDirEntry dirEntry; + while( fsc_nextDir(fscDirIterator, &dirEntry) ) + { + if(dirEntry.isDirectory == false) + continue; + // is 8-digit hex name? (persistent id) + if(_is8DigitHex(dirEntry.path) == false ) + continue; + uint32 persistentId = 0; + sscanf(dirEntry.path, "%x", &persistentId); + acpSaveDirInfo_t* entry = saveDirInfo + count; + if (count < maxCount) + { + memset(entry, 0, sizeof(acpSaveDirInfo_t)); + entry->ukn00 = (uint32)(titleId >> 32); // titleId? + entry->ukn04 = (uint32)(titleId & 0xFFFFFFFF); // titleId? + entry->persistentId = persistentId; // 0 -> common save + entry->ukn0C = 0; + entry->sizeA = _swapEndianU64(0); + entry->sizeB = _swapEndianU64(0); + entry->time = _swapEndianU64((coreinit::coreinit_getOSTime() / ESPRESSO_TIMER_CLOCK)); + sprintf(entry->path, "%susr/save/%08x/%08x/meta/", devicePath, (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF)); + count++; + } + } + fsc_close(fscDirIterator); + } + *countOut = count; + return 0; + } + + sint32 ACPCreateSaveDirEx(uint8 accountSlot, uint64 titleId) + { + uint32 persistentId = 0; + nn::save::GetPersistentIdEx(accountSlot, &persistentId); + + uint32 high = GetTitleIdHigh(titleId) & (~0xC); + uint32 low = GetTitleIdLow(titleId); + + sint32 fscStatus = FSC_STATUS_FILE_NOT_FOUND; + char path[256]; + + sprintf(path, "%susr/boss/", "/vol/storage_mlc01/"); + fsc_createDir(path, &fscStatus); + sprintf(path, "%susr/boss/%08x/", "/vol/storage_mlc01/", high); + fsc_createDir(path, &fscStatus); + sprintf(path, "%susr/boss/%08x/%08x/", "/vol/storage_mlc01/", high, low); + fsc_createDir(path, &fscStatus); + sprintf(path, "%susr/boss/%08x/%08x/user/", "/vol/storage_mlc01/", high, low); + fsc_createDir(path, &fscStatus); + sprintf(path, "%susr/boss/%08x/%08x/user/common", "/vol/storage_mlc01/", high, low); + fsc_createDir(path, &fscStatus); + sprintf(path, "%susr/boss/%08x/%08x/user/%08x/", "/vol/storage_mlc01/", high, low, persistentId == 0 ? 0x80000001 : persistentId); + fsc_createDir(path, &fscStatus); + + sprintf(path, "%susr/save/%08x/", "/vol/storage_mlc01/", high); + fsc_createDir(path, &fscStatus); + sprintf(path, "%susr/save/%08x/%08x/", "/vol/storage_mlc01/", high, low); + fsc_createDir(path, &fscStatus); + sprintf(path, "%susr/save/%08x/%08x/meta/", "/vol/storage_mlc01/", high, low); + fsc_createDir(path, &fscStatus); + sprintf(path, "%susr/save/%08x/%08x/user/", "/vol/storage_mlc01/", high, low); + fsc_createDir(path, &fscStatus); + sprintf(path, "%susr/save/%08x/%08x/user/common", "/vol/storage_mlc01/", high, low); + fsc_createDir(path, &fscStatus); + sprintf(path, "%susr/save/%08x/%08x/user/%08x", "/vol/storage_mlc01/", high, low, persistentId == 0 ? 0x80000001 : persistentId); + fsc_createDir(path, &fscStatus); + + // copy xml meta files + nn::acp::CreateSaveMetaFiles(persistentId, titleId); + return 0; + } + + int iosuAcp_thread() + { + SetThreadName("iosuAcp_thread"); + while (true) + { + uint32 returnValue = 0; // Ioctl return value + ioQueueEntry_t* ioQueueEntry = iosuIoctl_getNextWithWait(IOS_DEVICE_ACP_MAIN); + if (ioQueueEntry->request == IOSU_ACP_REQUEST_CEMU) + { + iosuAcpCemuRequest_t* acpCemuRequest = (iosuAcpCemuRequest_t*)ioQueueEntry->bufferVectors[0].buffer.GetPtr(); + if (acpCemuRequest->requestCode == IOSU_ACP_GET_SAVE_DATA_TITLE_ID_LIST) + { + uint32be count = 0; + acpCemuRequest->returnCode = ACPGetSaveDataTitleIdList(acpCemuRequest->type, (acpTitleId_t*)acpCemuRequest->ptr.GetPtr(), acpCemuRequest->maxCount, &count); + acpCemuRequest->resultU32.u32 = count; + } + else if (acpCemuRequest->requestCode == IOSU_ACP_GET_TITLE_SAVE_META_XML) + { + acpCemuRequest->returnCode = ACPGetTitleSaveMetaXml(acpCemuRequest->titleId, (acpMetaXml_t*)acpCemuRequest->ptr.GetPtr(), acpCemuRequest->type); + } + else if (acpCemuRequest->requestCode == IOSU_ACP_GET_TITLE_SAVE_DIR) + { + uint32be count = 0; + acpCemuRequest->returnCode = ACPGetTitleSaveDirEx(acpCemuRequest->titleId, acpCemuRequest->type, (acpSaveDirInfo_t*)acpCemuRequest->ptr.GetPtr(), acpCemuRequest->maxCount, &count); + acpCemuRequest->resultU32.u32 = count; + } + else if (acpCemuRequest->requestCode == IOSU_ACP_GET_TITLE_META_DATA) + { + acpCemuRequest->returnCode = ACPGetTitleMetaData(acpCemuRequest->titleId, (acpMetaData_t*)acpCemuRequest->ptr.GetPtr()); + } + else if (acpCemuRequest->requestCode == IOSU_ACP_GET_TITLE_META_XML) + { + acpCemuRequest->returnCode = ACPGetTitleMetaXml(acpCemuRequest->titleId, (acpMetaXml_t*)acpCemuRequest->ptr.GetPtr()); + } + else if (acpCemuRequest->requestCode == IOSU_ACP_CREATE_SAVE_DIR_EX) + { + acpCemuRequest->returnCode = ACPCreateSaveDirEx(acpCemuRequest->accountSlot, acpCemuRequest->titleId); + } + else + cemu_assert_unimplemented(); + } + else + cemu_assert_unimplemented(); + iosuIoctl_completeRequest(ioQueueEntry, returnValue); + } + return 0; + } + + void iosuAcp_init() + { + if (iosuAcp.isInitialized) + return; + std::thread t(iosuAcp_thread); + t.detach(); + iosuAcp.isInitialized = true; + } + + bool iosuAcp_isInitialized() + { + return iosuAcp.isInitialized; + } + + +} \ No newline at end of file diff --git a/src/Cafe/IOSU/legacy/iosu_acp.h b/src/Cafe/IOSU/legacy/iosu_acp.h new file mode 100644 index 00000000..18197bd8 --- /dev/null +++ b/src/Cafe/IOSU/legacy/iosu_acp.h @@ -0,0 +1,195 @@ +#pragma once + +typedef struct +{ + /* +0x0000 */ uint64 title_id; // parsed via GetHex64 + /* +0x0008 */ uint64 boss_id; // parsed via GetHex64 + /* +0x0010 */ uint64 os_version; // parsed via GetHex64 + /* +0x0018 */ uint64 app_size; // parsed via GetHex64 + /* +0x0020 */ uint64 common_save_size; // parsed via GetHex64 + /* +0x0028 */ uint64 account_save_size; // parsed via GetHex64 + /* +0x0030 */ uint64 common_boss_size; // parsed via GetHex64 + /* +0x0038 */ uint64 account_boss_size; // parsed via GetHex64 + /* +0x0040 */ uint64 join_game_mode_mask; // parsed via GetHex64 + /* +0x0048 */ uint32be version; + /* +0x004C */ char product_code[0x20]; + /* +0x006C */ char content_platform[0x20]; + /* +0x008C */ char company_code[8]; + /* +0x0094 */ char mastering_date[0x20]; + /* +0x00B4 */ uint32be logo_type; + /* +0x00B8 */ uint32be app_launch_type; + /* +0x00BC */ uint32be invisible_flag; + /* +0x00C0 */ uint32be no_managed_flag; + /* +0x00C4 */ uint32be no_event_log; + /* +0x00C8 */ uint32be no_icon_database; + /* +0x00CC */ uint32be launching_flag; + /* +0x00D0 */ uint32be install_flag; + /* +0x00D4 */ uint32be closing_msg; + /* +0x00D8 */ uint32be title_version; + /* +0x00DC */ uint32be group_id; // Hex32 + /* +0x00E0 */ uint32be save_no_rollback; + /* +0x00E4 */ uint32be bg_daemon_enable; + /* +0x00E8 */ uint32be join_game_id; // Hex32 + /* +0x00EC */ uint32be olv_accesskey; + /* +0x00F0 */ uint32be wood_tin; + /* +0x00F4 */ uint32be e_manual; + /* +0x00F8 */ uint32be e_manual_version; + /* +0x00FC */ uint32be region; // Hex32 + /* +0x0100 */ uint32be pc_cero; + /* +0x0104 */ uint32be pc_esrb; + /* +0x0108 */ uint32be pc_bbfc; + /* +0x010C */ uint32be pc_usk; + /* +0x0110 */ uint32be pc_pegi_gen; + /* +0x0114 */ uint32be pc_pegi_fin; + /* +0x0118 */ uint32be pc_pegi_prt; + /* +0x011C */ uint32be pc_pegi_bbfc; + /* +0x0120 */ uint32be pc_cob; + /* +0x0124 */ uint32be pc_grb; + /* +0x0128 */ uint32be pc_cgsrr; + /* +0x012C */ uint32be pc_oflc; + /* +0x0130 */ uint32be pc_reserved0; + /* +0x0134 */ uint32be pc_reserved1; + /* +0x0138 */ uint32be pc_reserved2; + /* +0x013C */ uint32be pc_reserved3; + /* +0x0140 */ uint32be ext_dev_nunchaku; + /* +0x0144 */ uint32be ext_dev_classic; + /* +0x0148 */ uint32be ext_dev_urcc; + /* +0x014C */ uint32be ext_dev_board; + /* +0x0150 */ uint32be ext_dev_usb_keyboard; + /* +0x0154 */ uint32be ext_dev_etc; + /* +0x0158 */ char ext_dev_etc_name[0x200]; + /* +0x0358 */ uint32be eula_version; + /* +0x035C */ uint32be drc_use; + /* +0x0360 */ uint32be network_use; + /* +0x0364 */ uint32be online_account_use; + /* +0x0368 */ uint32be direct_boot; + /* +0x036C */ uint32be reserved_flag[8]; // array of U32, reserved_flag%d + /* +0x038C */ char longname_ja[0x200]; + /* +0x058C */ char longname_en[0x200]; + /* +0x078C */ char longname_fr[0x200]; + /* +0x098C */ char longname_de[0x200]; + /* +0x0B8C */ char longname_it[0x200]; + /* +0x0D8C */ char longname_es[0x200]; + /* +0x0F8C */ char longname_zhs[0x200]; + /* +0x118C */ char longname_ko[0x200]; + /* +0x138C */ char longname_nl[0x200]; + /* +0x158C */ char longname_pt[0x200]; + /* +0x178C */ char longname_ru[0x200]; + /* +0x198C */ char longname_zht[0x200]; + /* +0x1B8C */ char shortname_ja[0x100]; + /* +0x1C8C */ char shortname_en[0x100]; + /* +0x1D8C */ char shortname_fr[0x100]; + /* +0x1E8C */ char shortname_de[0x100]; + /* +0x1F8C */ char shortname_it[0x100]; + /* +0x208C */ char shortname_es[0x100]; + /* +0x218C */ char shortname_zhs[0x100]; + /* +0x228C */ char shortname_ko[0x100]; + /* +0x238C */ char shortname_nl[0x100]; + /* +0x248C */ char shortname_pt[0x100]; + /* +0x258C */ char shortname_ru[0x100]; + /* +0x268C */ char shortname_zht[0x100]; + /* +0x278C */ char publisher_ja[0x100]; + /* +0x288C */ char publisher_en[0x100]; + /* +0x298C */ char publisher_fr[0x100]; + /* +0x2A8C */ char publisher_de[0x100]; + /* +0x2B8C */ char publisher_it[0x100]; + /* +0x2C8C */ char publisher_es[0x100]; + /* +0x2D8C */ char publisher_zhs[0x100]; + /* +0x2E8C */ char publisher_ko[0x100]; + /* +0x2F8C */ char publisher_nl[0x100]; + /* +0x308C */ char publisher_pt[0x100]; + /* +0x318C */ char publisher_ru[0x100]; + /* +0x328C */ char publisher_zht[0x100]; + /* +0x338C */ uint32be add_on_unique_id[0x20]; // Hex32, add_on_unique_id%d + /* +0x340C */ uint8 padding[0x3440 - 0x340C]; // guessed + // struct size is 0x3440 +}acpMetaXml_t; + +typedef struct +{ + /* +0x06BDC | +0x00000 */ uint8 bootMovie[0x13B38]; + /* +0x1A714 | +0x13B38 */ uint8 bootLogoTex[0x6FBC]; + /* +0x216D0 | +0x1AAF4 */ uint8 ukn1AAF4[4]; + /* +0x216D4 | +0x1AAF8 */ uint8 ukn1AAF8[4]; + /* +0x216D8 | +0x1AAFC */ uint8 ukn1AAFC[4]; +}acpMetaData_t; + +typedef struct +{ + uint32be titleIdHigh; + uint32be titleIdLow; +}acpTitleId_t; + +typedef struct +{ + // for ACPGetTitleSaveDirEx + + uint32be ukn00; + uint32be ukn04; + uint32be persistentId; + uint32be ukn0C; + //uint32be ukn10; // ukn10/ukn14 are part of size + //uint32be ukn14; + //uint32be ukn18; // ukn18/ukn1C are part of size + //uint32be ukn1C; + uint64 sizeA; + uint64 sizeB; + // path starts at 0x20, length unknown? + char path[0x40]; // %susr/save/%08x/%08x/meta/ // /vol/storage_mlc01/ + /* +0x60 */ uint64 time; + /* +0x68 */ uint8 padding[0x80 - 0x68]; + // size is 0x80, but actual content size is only 0x60 and padded to 0x80? +}acpSaveDirInfo_t; + + +// custom dev/acp_main protocol (Cemu only) +#define IOSU_ACP_REQUEST_CEMU (0xEE) + +typedef struct +{ + uint32 requestCode; + // input + uint8 accountSlot; + //uint32 unique; + //uint64 titleId; + //uint32 titleVersion; + //uint32 serverId; + uint64 titleId; + sint32 type; + MEMPTR<void> ptr; + sint32 maxCount; + // output + uint32 returnCode; // ACP return value + union + { + //struct + //{ + // uint64 u64; + //}resultU64; + struct + { + uint32 u32; + }resultU32; + //struct + //{ + // char strBuffer[1024]; + //}resultString; + //struct + //{ + // uint8 binBuffer[1024]; + //}resultBinary; + }; +}iosuAcpCemuRequest_t; + +// ACP request Cemu subcodes +#define IOSU_ACP_GET_SAVE_DATA_TITLE_ID_LIST 0x01 +#define IOSU_ACP_GET_TITLE_SAVE_META_XML 0x02 +#define IOSU_ACP_GET_TITLE_SAVE_DIR 0x03 +#define IOSU_ACP_GET_TITLE_META_DATA 0x04 +#define IOSU_ACP_GET_TITLE_META_XML 0x05 +#define IOSU_ACP_CREATE_SAVE_DIR_EX 0x06 + +namespace iosu +{ + void iosuAcp_init(); +} \ No newline at end of file diff --git a/src/Cafe/IOSU/legacy/iosu_act.cpp b/src/Cafe/IOSU/legacy/iosu_act.cpp new file mode 100644 index 00000000..9510a547 --- /dev/null +++ b/src/Cafe/IOSU/legacy/iosu_act.cpp @@ -0,0 +1,742 @@ +#include "iosu_act.h" +#include "iosu_ioctl.h" + +#include "Cafe/OS/libs/nn_common.h" +#include "gui/CemuApp.h" + +#include <algorithm> +#include <mutex> + +#include "openssl/sha.h" +#include "Cafe/Account/Account.h" +#include "config/ActiveSettings.h" +#include "util/helpers/helpers.h" + +#include "Cemu/napi/napi.h" +#include "Cemu/ncrypto/ncrypto.h" + +#include "Cafe/IOSU/kernel/iosu_kernel.h" +#include "Cafe/IOSU/nn/iosu_nn_service.h" + +using namespace iosu::kernel; + +struct +{ + bool isInitialized; +}iosuAct = { }; + +// account manager + +typedef struct +{ + bool isValid; + // options + bool isNetworkAccount; + bool hasParseError; // set if any occurs while parsing account.dat + // IDs + uint8 uuid[16]; + uint32 persistentId; + uint64 transferableIdBase; + uint32 simpleAddressId; + uint32 principalId; + // NNID + char accountId[64]; + uint8 accountPasswordCache[32]; + // country & language + uint32 countryIndex; + char country[8]; + // Mii + FFLData_t miiData; + uint16be miiNickname[ACT_NICKNAME_LENGTH]; +}actAccountData_t; + +#define IOSU_ACT_ACCOUNT_MAX_COUNT (0xC) + +actAccountData_t _actAccountData[IOSU_ACT_ACCOUNT_MAX_COUNT] = {}; +bool _actAccountDataInitialized = false; + +void FillAccountData(const Account& account, const bool online_enabled, int index) +{ + cemu_assert_debug(index < IOSU_ACT_ACCOUNT_MAX_COUNT); + auto& data = _actAccountData[index]; + data.isValid = true; + // options + data.isNetworkAccount = account.IsValidOnlineAccount(); + data.hasParseError = false; + // IDs + std::copy(account.GetUuid().cbegin(), account.GetUuid().cend(), data.uuid); + data.persistentId = account.GetPersistentId(); + data.transferableIdBase = account.GetTransferableIdBase(); + data.simpleAddressId = account.GetSimpleAddressId(); + data.principalId = account.GetPrincipalId(); + // NNID + std::copy(account.GetAccountId().begin(), account.GetAccountId().end(), data.accountId); + std::copy(account.GetAccountPasswordCache().begin(), account.GetAccountPasswordCache().end(), data.accountPasswordCache); + // country & language + data.countryIndex = account.GetCountry(); + strcpy(data.country, NCrypto::GetCountryAsString(data.countryIndex)); + // Mii + std::copy(account.GetMiiData().begin(), account.GetMiiData().end(), (uint8*)&data.miiData); + std::copy(account.GetMiiName().begin(), account.GetMiiName().end(), data.miiNickname); + + // if online mode is disabled, make all accounts offline + if(!online_enabled) + { + data.isNetworkAccount = false; + data.principalId = 0; + data.simpleAddressId = 0; + memset(data.accountId, 0x00, sizeof(data.accountId)); + } +} + +void iosuAct_loadAccounts() +{ + if (_actAccountDataInitialized) + return; + + const bool online_enabled = ActiveSettings::IsOnlineEnabled(); + const auto persistent_id = ActiveSettings::GetPersistentId(); + + // first account is always our selected one + int counter = 0; + const auto& first_acc = Account::GetAccount(persistent_id); + FillAccountData(first_acc, online_enabled, counter); + ++counter; + + cemuLog_force(L"IOSU_ACT: using account {} in first slot", first_acc.GetMiiName()); + + _actAccountDataInitialized = true; +} + +bool iosuAct_isAccountDataLoaded() +{ + return _actAccountDataInitialized; +} + +uint32 iosuAct_acquirePrincipalIdByAccountId(const char* nnid, uint32* pid) +{ + NAPI::AuthInfo authInfo; + NAPI::NAPI_MakeAuthInfoFromCurrentAccount(authInfo); + NAPI::ACTConvertNnidToPrincipalIdResult result = NAPI::ACT_ACTConvertNnidToPrincipalId(authInfo, nnid); + if (result.isValid() && result.isFound) + { + *pid = result.principalId; + } + else + { + *pid = 0; + return BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_ACT, 0); // what error should we return? The friend list app expects nn_act.AcquirePrincipalIdByAccountId to never return an error + } + return 0; +} + +sint32 iosuAct_getAccountIndexBySlot(uint8 slot) +{ + if (slot == iosu::act::ACT_SLOT_CURRENT) + return 0; + if (slot == 0xFF) + return 0; // ? + cemu_assert_debug(slot != 0); + cemu_assert_debug(slot <= IOSU_ACT_ACCOUNT_MAX_COUNT); + return slot - 1; +} + +uint32 iosuAct_getAccountIdOfCurrentAccount() +{ + cemu_assert_debug(_actAccountData[0].isValid); + return _actAccountData[0].persistentId; +} + +// IOSU act API interface + +namespace iosu +{ + namespace act + { + uint8 getCurrentAccountSlot() + { + return 1; + } + + bool getPrincipalId(uint8 slot, uint32* principalId) + { + sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot); + if (_actAccountData[accountIndex].isValid == false) + { + *principalId = 0; + return false; + } + *principalId = _actAccountData[accountIndex].principalId; + return true; + } + + bool getAccountId(uint8 slot, char* accountId) + { + sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot); + if (_actAccountData[accountIndex].isValid == false) + { + *accountId = '\0'; + return false; + } + strcpy(accountId, _actAccountData[accountIndex].accountId); + return true; + } + + bool getMii(uint8 slot, FFLData_t* fflData) + { + sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot); + if (_actAccountData[accountIndex].isValid == false) + { + return false; + } + memcpy(fflData, &_actAccountData[accountIndex].miiData, sizeof(FFLData_t)); + return true; + } + + // return screenname in little-endian wide characters + bool getScreenname(uint8 slot, uint16 screenname[ACT_NICKNAME_LENGTH]) + { + sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot); + if (_actAccountData[accountIndex].isValid == false) + { + screenname[0] = '\0'; + return false; + } + for (sint32 i = 0; i < ACT_NICKNAME_LENGTH; i++) + { + screenname[i] = (uint16)_actAccountData[accountIndex].miiNickname[i]; + } + return true; + } + + bool getCountryIndex(uint8 slot, uint32* countryIndex) + { + sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot); + if (_actAccountData[accountIndex].isValid == false) + { + *countryIndex = 0; + return false; + } + *countryIndex = _actAccountData[accountIndex].countryIndex; + return true; + } + + class ActService : public iosu::nn::IPCService + { + public: + ActService() : iosu::nn::IPCService("/dev/act") {} + + nnResult ServiceCall(uint32 serviceId, void* request, void* response) override + { + cemuLog_log(LogType::Force, "Unsupported service call to /dec/act"); + cemu_assert_unimplemented(); + return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_ACT, 0); + } + }; + + ActService gActService; + + void Initialize() + { + gActService.Start(); + } + + void Stop() + { + gActService.Stop(); + } + } +} + + +// IOSU act IO + +typedef struct +{ + /* +0x00 */ uint32be ukn00; + /* +0x04 */ uint32be ukn04; + /* +0x08 */ uint32be ukn08; + /* +0x0C */ uint32be subcommandCode; + /* +0x10 */ uint8 ukn10; + /* +0x11 */ uint8 ukn11; + /* +0x12 */ uint8 ukn12; + /* +0x13 */ uint8 accountSlot; + /* +0x14 */ uint32be unique; // is this command specific? +}cmdActRequest00_t; + +typedef struct +{ + uint32be returnCode; + uint8 transferableIdBase[8]; +}cmdActGetTransferableIDResult_t; + +#define ACT_SUBCMD_GET_TRANSFERABLE_ID 4 +#define ACT_SUBCMD_INITIALIZE 0x14 + +#define _cancelIfAccountDoesNotExist() \ +if (_actAccountData[accountIndex].isValid == false) \ +{ \ + /* account does not exist*/ \ + ioctlReturnValue = 0; \ + actCemuRequest->setACTReturnCode(BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_ACT, NN_ACT_RESULT_ACCOUNT_DOES_NOT_EXIST)); /* 0xA071F480 */ \ + actCemuRequest->resultU64.u64 = 0; \ + iosuIoctl_completeRequest(ioQueueEntry, ioctlReturnValue); \ + continue; \ +} + +nnResult ServerActErrorCodeToNNResult(NAPI::ACT_ERROR_CODE ec) +{ + switch (ec) + { + case (NAPI::ACT_ERROR_CODE)1: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2401); + case (NAPI::ACT_ERROR_CODE)2: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2402); + case (NAPI::ACT_ERROR_CODE)3: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2403); + case (NAPI::ACT_ERROR_CODE)4: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2458); + case (NAPI::ACT_ERROR_CODE)5: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2642); + case (NAPI::ACT_ERROR_CODE)6: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2641); + case (NAPI::ACT_ERROR_CODE)7: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2522); + case (NAPI::ACT_ERROR_CODE)8: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2534); + case (NAPI::ACT_ERROR_CODE)9: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2404); + case (NAPI::ACT_ERROR_CODE)10: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2451); + case (NAPI::ACT_ERROR_CODE)11: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2511); + case (NAPI::ACT_ERROR_CODE)12: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2812); + case (NAPI::ACT_ERROR_CODE)100: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2571); + case (NAPI::ACT_ERROR_CODE)101: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2572); + case (NAPI::ACT_ERROR_CODE)103: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2575); + case (NAPI::ACT_ERROR_CODE)104: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2452); + case (NAPI::ACT_ERROR_CODE)105: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2592); + case (NAPI::ACT_ERROR_CODE)106: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2611); + case (NAPI::ACT_ERROR_CODE)107: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2502); + case (NAPI::ACT_ERROR_CODE)108: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2802); + case (NAPI::ACT_ERROR_CODE)109: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2503); + case (NAPI::ACT_ERROR_CODE)110: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2501); + case (NAPI::ACT_ERROR_CODE)111: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2632); + case (NAPI::ACT_ERROR_CODE)112: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2631); + case (NAPI::ACT_ERROR_CODE)113: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2452); + case (NAPI::ACT_ERROR_CODE)114: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2593); + case (NAPI::ACT_ERROR_CODE)115: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2591); + case (NAPI::ACT_ERROR_CODE)116: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2614); + case (NAPI::ACT_ERROR_CODE)117: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2651); + case (NAPI::ACT_ERROR_CODE)118: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2484); + case (NAPI::ACT_ERROR_CODE)119: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2803); + case (NAPI::ACT_ERROR_CODE)120: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2813); + case (NAPI::ACT_ERROR_CODE)121: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2804); + case (NAPI::ACT_ERROR_CODE)122: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2814); + case (NAPI::ACT_ERROR_CODE)123: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2882); + case (NAPI::ACT_ERROR_CODE)124: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2512); + case (NAPI::ACT_ERROR_CODE)125: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2485); + case (NAPI::ACT_ERROR_CODE)126: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2805); + case (NAPI::ACT_ERROR_CODE)127: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2815); + case (NAPI::ACT_ERROR_CODE)128: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2661); + case (NAPI::ACT_ERROR_CODE)129: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2615); + case (NAPI::ACT_ERROR_CODE)130: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2531); + case (NAPI::ACT_ERROR_CODE)131: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2616); + case (NAPI::ACT_ERROR_CODE)132: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2822); + case (NAPI::ACT_ERROR_CODE)133: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2832); + case (NAPI::ACT_ERROR_CODE)134: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2823); + case (NAPI::ACT_ERROR_CODE)135: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2833); + case (NAPI::ACT_ERROR_CODE)136: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2824); + case (NAPI::ACT_ERROR_CODE)137: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2834); + case (NAPI::ACT_ERROR_CODE)138: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2825); + case (NAPI::ACT_ERROR_CODE)139: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2835); + case (NAPI::ACT_ERROR_CODE)142: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2635); + case (NAPI::ACT_ERROR_CODE)143: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2634); + case (NAPI::ACT_ERROR_CODE)1004: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2503); + case (NAPI::ACT_ERROR_CODE)1006: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2471); + case (NAPI::ACT_ERROR_CODE)1016: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2532); + case (NAPI::ACT_ERROR_CODE)1017: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2483); + case (NAPI::ACT_ERROR_CODE)1018: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2533); + case (NAPI::ACT_ERROR_CODE)1019: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2481); + case (NAPI::ACT_ERROR_CODE)1020: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2473); + case NAPI::ACT_ERROR_CODE::ACT_GAME_SERVER_NOT_FOUND: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2482); + case (NAPI::ACT_ERROR_CODE)1022: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2472); + case (NAPI::ACT_ERROR_CODE)1023: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2612); + case (NAPI::ACT_ERROR_CODE)1024: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2535); + case (NAPI::ACT_ERROR_CODE)1025: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2536); + case (NAPI::ACT_ERROR_CODE)1031: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2537); + case (NAPI::ACT_ERROR_CODE)1032: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2636); + case (NAPI::ACT_ERROR_CODE)1033: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2662); + case (NAPI::ACT_ERROR_CODE)1035: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2671); + case (NAPI::ACT_ERROR_CODE)1036: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2679); + case (NAPI::ACT_ERROR_CODE)1037: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2672); + case (NAPI::ACT_ERROR_CODE)1038: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2674); + case (NAPI::ACT_ERROR_CODE)1039: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2680); + case (NAPI::ACT_ERROR_CODE)1040: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2675); + case (NAPI::ACT_ERROR_CODE)1041: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2673); + case (NAPI::ACT_ERROR_CODE)1042: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2676); + case (NAPI::ACT_ERROR_CODE)1043: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2681); + case (NAPI::ACT_ERROR_CODE)1044: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2678); + case (NAPI::ACT_ERROR_CODE)1045: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2677); + case (NAPI::ACT_ERROR_CODE)1046: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2596); + case (NAPI::ACT_ERROR_CODE)1100: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2541); + case (NAPI::ACT_ERROR_CODE)1101: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2542); + case (NAPI::ACT_ERROR_CODE)1103: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2594); + case (NAPI::ACT_ERROR_CODE)1104: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2576); + case (NAPI::ACT_ERROR_CODE)1105: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2613); + case (NAPI::ACT_ERROR_CODE)1106: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2633); + case (NAPI::ACT_ERROR_CODE)1107: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2577); + case (NAPI::ACT_ERROR_CODE)1111: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2538); + case (NAPI::ACT_ERROR_CODE)1115: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2597); + case (NAPI::ACT_ERROR_CODE)1125: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2585); + case (NAPI::ACT_ERROR_CODE)1126: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2586); + case (NAPI::ACT_ERROR_CODE)1134: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2587); + case (NAPI::ACT_ERROR_CODE)1200: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2884); + case (NAPI::ACT_ERROR_CODE)2001: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2931); + case (NAPI::ACT_ERROR_CODE)2002: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2881); + case (NAPI::ACT_ERROR_CODE)2999: + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, (NN_ERROR_CODE)2883); + default: + break; + } + cemuLog_log(LogType::Force, "Received unknown ACT error code {}", (uint32)ec); + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, NN_ERROR_CODE::ACT_UNKNOWN_SERVER_ERROR); +} + +int iosuAct_thread() +{ + SetThreadName("iosuAct_thread"); + while (true) + { + uint32 ioctlReturnValue = 0; + ioQueueEntry_t* ioQueueEntry = iosuIoctl_getNextWithWait(IOS_DEVICE_ACT); + if (ioQueueEntry->request == 0) + { + if (ioQueueEntry->countIn != 1 || ioQueueEntry->countOut != 1) + { + assert_dbg(); + } + ioBufferVector_t* vectorsDebug = ioQueueEntry->bufferVectors.GetPtr(); + + void* outputBuffer = ioQueueEntry->bufferVectors[0].buffer.GetPtr(); + cmdActRequest00_t* requestCmd = (cmdActRequest00_t*)ioQueueEntry->bufferVectors[0].unknownBuffer.GetPtr(); + if (requestCmd->subcommandCode == ACT_SUBCMD_INITIALIZE) + { + // do nothing for now (there is no result?) + } + else if (requestCmd->subcommandCode == ACT_SUBCMD_GET_TRANSFERABLE_ID) + { + cmdActGetTransferableIDResult_t* cmdResult = (cmdActGetTransferableIDResult_t*)outputBuffer; + cmdResult->returnCode = 0; + *(uint64*)cmdResult->transferableIdBase = _swapEndianU64(0x1122334455667788); + } + else + assert_dbg(); + } + else if (ioQueueEntry->request == IOSU_ACT_REQUEST_CEMU) + { + iosuActCemuRequest_t* actCemuRequest = (iosuActCemuRequest_t*)ioQueueEntry->bufferVectors[0].buffer.GetPtr(); + sint32 accountIndex; + ioctlReturnValue = 0; + if (actCemuRequest->requestCode == IOSU_ARC_ACCOUNT_ID) + { + accountIndex = iosuAct_getAccountIndexBySlot(actCemuRequest->accountSlot); + _cancelIfAccountDoesNotExist(); + strcpy(actCemuRequest->resultString.strBuffer, _actAccountData[accountIndex].accountId); + actCemuRequest->setACTReturnCode(0); + } + else if (actCemuRequest->requestCode == IOSU_ARC_UUID) + { + accountIndex = iosuAct_getAccountIndexBySlot(actCemuRequest->accountSlot); + if (actCemuRequest->accountSlot == 0xFF) + { + // common uuid (placeholder algorithm) + for (uint32 i = 0; i < 16; i++) + actCemuRequest->resultBinary.binBuffer[i] = i * 0x74 + i + ~i + i * 133; + } + else + { + _cancelIfAccountDoesNotExist(); + memcpy(actCemuRequest->resultBinary.binBuffer, _actAccountData[accountIndex].uuid, 16); + } + + cemu_assert_debug(actCemuRequest->uuidName != -1); // todo + if (actCemuRequest->uuidName != -1 && actCemuRequest->uuidName != -2) + { + // generate name based UUID + // format: + // first 10 bytes of UUID + 6 bytes of a hash + // hash algorithm: + // sha256 of + // 4 bytes uuidName (big-endian) + // 4 bytes 0x3A275E09 (big-endian) + // 6 bytes from the end of UUID + // bytes 10-15 are used from the hash and replace the last 6 bytes of the UUID + + SHA256_CTX ctx_sha256; + SHA256_Init(&ctx_sha256); + + uint8 tempArray[4]; + uint32 name = (uint32)actCemuRequest->uuidName; + tempArray[0] = (name >> 24) & 0xFF; + tempArray[1] = (name >> 16) & 0xFF; + tempArray[2] = (name >> 8) & 0xFF; + tempArray[3] = (name >> 0) & 0xFF; + SHA256_Update(&ctx_sha256, tempArray, 4); + tempArray[0] = 0x3A; + tempArray[1] = 0x27; + tempArray[2] = 0x5E; + tempArray[3] = 0x09; + SHA256_Update(&ctx_sha256, tempArray, 4); + SHA256_Update(&ctx_sha256, actCemuRequest->resultBinary.binBuffer+10, 6); + uint8 h[32]; + SHA256_Final(h, &ctx_sha256); + + memcpy(actCemuRequest->resultBinary.binBuffer + 0xA, h + 0xA, 6); + } + else if (actCemuRequest->uuidName == -2) + { + // return account uuid + } + else + { + forceLogDebug_printf("Gen UUID unknown mode %d", actCemuRequest->uuidName); + } + actCemuRequest->setACTReturnCode(0); + } + else if (actCemuRequest->requestCode == IOSU_ARC_SIMPLEADDRESS) + { + accountIndex = iosuAct_getAccountIndexBySlot(actCemuRequest->accountSlot); + _cancelIfAccountDoesNotExist(); + actCemuRequest->resultU32.u32 = _actAccountData[accountIndex].simpleAddressId; + actCemuRequest->setACTReturnCode(0); + } + else if (actCemuRequest->requestCode == IOSU_ARC_PRINCIPALID) + { + accountIndex = iosuAct_getAccountIndexBySlot(actCemuRequest->accountSlot); + _cancelIfAccountDoesNotExist(); + actCemuRequest->resultU32.u32 = _actAccountData[accountIndex].principalId; + actCemuRequest->setACTReturnCode(0); + } + else if (actCemuRequest->requestCode == IOSU_ARC_TRANSFERABLEID) + { + accountIndex = iosuAct_getAccountIndexBySlot(actCemuRequest->accountSlot); + _cancelIfAccountDoesNotExist(); + actCemuRequest->resultU64.u64 = _actAccountData[accountIndex].transferableIdBase; + // todo - transferable also contains a unique id + actCemuRequest->setACTReturnCode(0); + } + else if (actCemuRequest->requestCode == IOSU_ARC_PERSISTENTID) + { + accountIndex = iosuAct_getAccountIndexBySlot(actCemuRequest->accountSlot); + _cancelIfAccountDoesNotExist(); + actCemuRequest->resultU32.u32 = _actAccountData[accountIndex].persistentId; + actCemuRequest->setACTReturnCode(0); + } + else if (actCemuRequest->requestCode == IOSU_ARC_COUNTRY) + { + accountIndex = iosuAct_getAccountIndexBySlot(actCemuRequest->accountSlot); + _cancelIfAccountDoesNotExist(); + strcpy(actCemuRequest->resultString.strBuffer, _actAccountData[accountIndex].country); + actCemuRequest->setACTReturnCode(0); + } + else if (actCemuRequest->requestCode == IOSU_ARC_ISNETWORKACCOUNT) + { + accountIndex = iosuAct_getAccountIndexBySlot(actCemuRequest->accountSlot); + _cancelIfAccountDoesNotExist(); + actCemuRequest->resultU32.u32 = _actAccountData[accountIndex].isNetworkAccount; + actCemuRequest->setACTReturnCode(0); + } + else if (actCemuRequest->requestCode == IOSU_ARC_ACQUIRENEXTOKEN) + { + NAPI::AuthInfo authInfo; + NAPI::NAPI_MakeAuthInfoFromCurrentAccount(authInfo); + NAPI::ACTGetNexTokenResult nexTokenResult = NAPI::ACT_GetNexToken_WithCache(authInfo, actCemuRequest->titleId, actCemuRequest->titleVersion, actCemuRequest->serverId); + uint32 returnCode = 0; + if (nexTokenResult.isValid()) + { + *(NAPI::ACTNexToken*)actCemuRequest->resultBinary.binBuffer = nexTokenResult.nexToken; + returnCode = NN_RESULT_SUCCESS; + } + else if (nexTokenResult.apiError == NAPI_RESULT::SERVICE_ERROR) + { + returnCode = ServerActErrorCodeToNNResult(nexTokenResult.serviceError); + cemu_assert_debug((returnCode&0x80000000) != 0); + } + else + { + returnCode = nnResultStatus(NN_RESULT_MODULE_NN_ACT, NN_ERROR_CODE::ACT_UNKNOWN_SERVER_ERROR); + } + actCemuRequest->setACTReturnCode(returnCode); + } + else if (actCemuRequest->requestCode == IOSU_ARC_ACQUIREINDEPENDENTTOKEN) + { + NAPI::AuthInfo authInfo; + NAPI::NAPI_MakeAuthInfoFromCurrentAccount(authInfo); + NAPI::ACTGetIndependentTokenResult tokenResult = NAPI::ACT_GetIndependentToken_WithCache(authInfo, actCemuRequest->titleId, actCemuRequest->titleVersion, actCemuRequest->clientId); + + uint32 returnCode = 0; + if (tokenResult.isValid()) + { + for (size_t i = 0; i < std::min(tokenResult.token.size(), (size_t)200); i++) + { + actCemuRequest->resultBinary.binBuffer[i] = tokenResult.token[i]; + actCemuRequest->resultBinary.binBuffer[i + 1] = '\0'; + } + returnCode = 0; + } + else + { + returnCode = 0x80000000; // todo - proper error codes + } + actCemuRequest->setACTReturnCode(returnCode); + } + else if (actCemuRequest->requestCode == IOSU_ARC_ACQUIREPIDBYNNID) + { + uint32 returnCode = iosuAct_acquirePrincipalIdByAccountId(actCemuRequest->clientId, &actCemuRequest->resultU32.u32); + actCemuRequest->setACTReturnCode(returnCode); + } + else if (actCemuRequest->requestCode == IOSU_ARC_MIIDATA) + { + + accountIndex = iosuAct_getAccountIndexBySlot(actCemuRequest->accountSlot); + _cancelIfAccountDoesNotExist(); + memcpy(actCemuRequest->resultBinary.binBuffer, &_actAccountData[accountIndex].miiData, sizeof(FFLData_t)); + actCemuRequest->setACTReturnCode(0); + } + else if (actCemuRequest->requestCode == IOSU_ARC_INIT) + { + iosuAct_loadAccounts(); + actCemuRequest->setACTReturnCode(0); + } + else + assert_dbg(); + } + else + { + assert_dbg(); + } + iosuIoctl_completeRequest(ioQueueEntry, ioctlReturnValue); + } + return 0; +} + +void iosuAct_init_depr() +{ + if (iosuAct.isInitialized) + return; + std::thread t(iosuAct_thread); + t.detach(); + iosuAct.isInitialized = true; +} + +bool iosuAct_isInitialized() +{ + return iosuAct.isInitialized; +} + +uint16 FFLCalculateCRC16(uint8* input, sint32 length) +{ + uint16 crc = 0; + for (sint32 c = 0; c < length; c++) + { + for (sint32 f = 0; f < 8; f++) + { + if ((crc & 0x8000) != 0) + { + uint16 t = crc << 1; + crc = t ^ 0x1021; + } + else + { + crc <<= 1; + } + } + crc ^= (uint16)input[c]; + } + return crc; +} diff --git a/src/Cafe/IOSU/legacy/iosu_act.h b/src/Cafe/IOSU/legacy/iosu_act.h new file mode 100644 index 00000000..8d644d2f --- /dev/null +++ b/src/Cafe/IOSU/legacy/iosu_act.h @@ -0,0 +1,118 @@ +#pragma once + +void iosuAct_init_depr(); +bool iosuAct_isInitialized(); + +// Mii + +#define MII_FFL_STORAGE_SIZE (96) + +#define MII_FFL_NAME_LENGTH (10) // counted in wchar_t elements (16-bit unicode) + +#define ACT_NICKNAME_LENGTH (10) // aka Mii nickname +#define ACT_NICKNAME_SIZE (11) + +typedef struct +{ + uint32be high; + uint32be low; +}FFLDataID_t; + +typedef struct +{ + /* +0x00 */ uint32 uknFlags; + /* +0x04 */ FFLDataID_t miiId; // bytes 8 and 9 are part of the CRC? (miiId is based on account transferable id?) + /* +0x0C */ uint8 ukn0C[0xA]; + /* +0x16 */ uint8 ukn16[2]; + /* +0x18 */ uint16 ukn18; + /* +0x1A */ uint16be miiName[MII_FFL_NAME_LENGTH]; + /* +0x2E */ uint16 ukn2E; + /* +0x30 */ uint8 ukn30[MII_FFL_STORAGE_SIZE-0x30]; +}FFLData_t; + +static_assert(sizeof(FFLData_t) == MII_FFL_STORAGE_SIZE, "FFLData_t size invalid"); +static_assert(offsetof(FFLData_t, miiId) == 0x04, "FFLData->miiId offset invalid"); +static_assert(offsetof(FFLData_t, miiName) == 0x1A, "FFLData->miiName offset invalid"); +static_assert(offsetof(FFLData_t, ukn2E) == 0x2E, "FFLData->ukn2E offset invalid"); + +static uint16 FFLCalculateCRC16(uint8* input, sint32 length); + +namespace iosu +{ + namespace act + { + uint8 getCurrentAccountSlot(); + bool getPrincipalId(uint8 slot, uint32* principalId); + bool getAccountId(uint8 slot, char* accountId); + bool getMii(uint8 slot, FFLData_t* fflData); + bool getScreenname(uint8 slot, uint16 screenname[ACT_NICKNAME_LENGTH]); + bool getCountryIndex(uint8 slot, uint32* countryIndex); + + const uint8 ACT_SLOT_CURRENT = 0xFE; + + void Initialize(); + void Stop(); + } +} + +// custom dev/act/ protocol (Cemu only) +#define IOSU_ACT_REQUEST_CEMU (0xEE) + +struct iosuActCemuRequest_t +{ + uint32 requestCode; + // input + uint8 accountSlot; + uint32 unique; + sint32 uuidName; + uint64 titleId; + uint32 titleVersion; + uint32 serverId; + char clientId[64]; + uint32 expiresIn; + // output + uint32 returnCode; + union + { + struct + { + uint64 u64; + }resultU64; + struct + { + uint32 u32; + }resultU32; + struct + { + char strBuffer[1024]; + }resultString; + struct + { + uint8 binBuffer[1024]; + }resultBinary; + }; + + void setACTReturnCode(uint32 code) + { + returnCode = code; + } +}; + +// Act Request Cemu subcodes +#define IOSU_ARC_INIT 0x00 +#define IOSU_ARC_ACCOUNT_ID 0x01 +#define IOSU_ARC_TRANSFERABLEID 0x02 +#define IOSU_ARC_PERSISTENTID 0x03 +#define IOSU_ARC_UUID 0x04 +#define IOSU_ARC_SIMPLEADDRESS 0x05 +#define IOSU_ARC_PRINCIPALID 0x06 +#define IOSU_ARC_COUNTRY 0x07 +#define IOSU_ARC_ISNETWORKACCOUNT 0x08 +#define IOSU_ARC_ACQUIRENEXTOKEN 0x09 +#define IOSU_ARC_MIIDATA 0x0A +#define IOSU_ARC_ACQUIREINDEPENDENTTOKEN 0x0B +#define IOSU_ARC_ACQUIREPIDBYNNID 0x0C + +uint32 iosuAct_getAccountIdOfCurrentAccount(); + +bool iosuAct_isAccountDataLoaded(); \ No newline at end of file diff --git a/src/Cafe/IOSU/legacy/iosu_boss.cpp b/src/Cafe/IOSU/legacy/iosu_boss.cpp new file mode 100644 index 00000000..4c99f5df --- /dev/null +++ b/src/Cafe/IOSU/legacy/iosu_boss.cpp @@ -0,0 +1,1029 @@ +#include "iosu_boss.h" + +#include <sstream> +#include <iomanip> +#include <thread> +#include <algorithm> +#include <array> +#include <queue> +#include <filesystem> +#include <fstream> + +#include "config/ActiveSettings.h" +#include "curl/curl.h" +#include "openssl/bn.h" +#include "openssl/x509.h" +#include "openssl/ssl.h" + +#include "iosu_ioctl.h" +#include "Cafe/OS/libs/nn_common.h" +#include "iosu_act.h" +#include "iosu_crypto.h" +#include "util/crypto/aes128.h" +#include "config/CemuConfig.h" + +#include "util/helpers/helpers.h" +#include "Cemu/ncrypto/ncrypto.h" +#include "Cafe/CafeSystem.h" + +namespace iosu +{ + enum TurnState + { + kUnknown = 0, + kStopped = 1, + kStoppedByPolicyList = 2, + kWaitTime = 3, + kWaitRun = 4, + kWaitResume = 5, + kRunning = 6, + kFinished = 7, + kSuccess = 16, + kError = 17, + }; + + enum class ContentType + { + kUnknownContent, + kXmlContent, + kBinaryFile, + kText, + }; + + enum class FileType + { + kUnknownFile, + kAppData, + }; + + enum TaskSettingType : uint32 + { + kRawDlTaskSetting = 0x10000698, + }; + + struct TaskFile + { + TaskFile(std::string file_name, uint32 data_id, FileType type, std::string url, uint32 size) + : file_name(file_name), data_id(data_id), file_type(type), url(url), size(size) {} + + std::string file_name; + uint32 data_id; + FileType file_type; + std::string url; + uint32 size; + uint64 last_modified = 0; + }; + + struct TaskSetting + { + static const uint32 kBossCode = 0x7C0; + static const uint32 kBossCodeLen = 0x20; + + static const uint32 kURL = 0x48; + static const uint32 kURLLen = 0x100; + + static const uint32 kClientCert = 0x41; + static const uint32 kCACert = 0x188; + + static const uint32 kServiceToken = 0x590; + static const uint32 kServiceTokenLen = 0x200; + + static const uint32 kDirectorySizeLimit = 0x7F0; + static const uint32 kDirectoryName = 0x7C8; + static const uint32 kDirectoryNameLen = 0x8; + + static const uint32 kFileName = 0x7D0; + static const uint32 kNbdlFileName = 0x7F8; + static const uint32 kFileNameLen = 0x20; + + std::array<uint8, 0x1000> settings; + uint32be taskType; // +0x1000 + }; + + static_assert(sizeof(TaskSetting) == 0x1004, "sizeof(TaskSetting_t)"); + + struct Task + { + char task_id[8]{}; + uint32 account_id; + uint64 title_id; + + TaskSetting task_settings; + + uint32 exec_count = 0; + std::shared_ptr<CURL> curl; + uint64 content_length = 0; + uint64 processed_length = 0; + + uint32 turn_state = 0; + uint32 wait_state = 0; + + uint32 http_status_code = 0; + ContentType content_type = ContentType::kUnknownContent; + + std::vector<uint8> result_buffer; + + std::queue<TaskFile> queued_files; + std::vector<uint8> file_buffer; + uint32 processed_file_size = 0; + + Task(const char* id, uint32 account_id, uint64 title_id, TaskSetting* settings) + { + strncpy(task_id, id, sizeof(task_id)); + this->account_id = account_id; + this->title_id = title_id; + this->task_settings.settings = settings->settings; + this->task_settings.taskType = settings->taskType; + + curl = std::shared_ptr<CURL>(curl_easy_init(), curl_easy_cleanup); + } + }; + +#define FAD_ENTRY_MAX_COUNT (512) + + struct BossStorageFadEntry + { + char name[0x20]; + uint32be fileNameId; + uint32 ukn24; + uint32 ukn28; + uint32 ukn2C; + uint32 ukn30; + uint32be timestampRelated; // guessed + }; + + static_assert(sizeof(BossStorageFadEntry) == 0x38, "sizeof(BossStorageFadEntry)"); + + struct BossStorageFadFile + { + uint8 _00[0x08]; + BossStorageFadEntry entries[FAD_ENTRY_MAX_COUNT]; + }; + + static_assert(sizeof(BossStorageFadFile) == 28680, "sizeof(BossStorageFadFile)"); + + struct BossNbdlHeader + { + /* +0x00 */ + uint32be magic; + /* +0x04 */ + uint32be version; // guessed + /* +0x08 */ + uint16be ukn08; // must always be 1 + /* +0x0A */ + uint16be ukn0A; // must always be 2 + /* +0x0C */ + uint8 nonce[0xC]; + /* +0x18 */ + uint32 padding18; // unused + /* +0x1C */ + uint32 padding1C; // unused + /* +0x20 */ + struct + { + uint8 uknHashData[0x20]; + } encryptedHeader; + } ; + + static_assert(sizeof(BossNbdlHeader) == 0x40, "BossNbdlHeader has invalid size"); + static_assert(offsetof(BossNbdlHeader, encryptedHeader) == 0x20, "offsetof(BossNbdlHeader, encryptedHeader)"); + + + struct + { + bool is_initialized; + + std::vector<Task> tasks; + } g_boss = {}; + + /* + X-BOSS-Closed + X-BOSS-TitleId + X-Boss-UniqueId + �\r���\r�(X-BOSS-Digest + + LOAD:E01038B4 0000000C C %susr/boss/ + LOAD:E0103A6C 00000011 C %susr/boss/%08x/ + LOAD:E0103B04 00000016 C %susr/boss/%08x/%08x/ + LOAD:E0103B1C 0000001B C %susr/boss/%08x/%08x/user/ + LOAD:E0103B5C 00000020 C %susr/boss/%08x/%08x/user/%08x/ + LOAD:E0103B38 00000022 C %susr/boss/%08x/%08x/user/common/ + LOAD:E0103AC4 00000020 C %susr/boss/%08x/%08x/user/temp/ + + LOAD:E01106DC 0000000A C /dev/boss + + LOAD:05063698 0000001C C /vol/storage_mlc01/usr/boss + LOAD:E2299CA8 00000028 C /vol/storage_mlc01/usr/save/system/boss + */ + + template <typename ... TArgs> + curl_slist* append_header_param(struct curl_slist* list, const char* format, TArgs&& ... args) + { + return curl_slist_append(list, fmt::format(format, std::forward<TArgs>(args)...).c_str()); + } + + bool starts_with(const char* str, const char* pre) + { + const size_t len_string = strlen(str); + const size_t len_prefix = strlen(pre); + return len_string < len_prefix ? false : strncmp(pre, str, len_prefix) == 0; + } + + size_t task_write_callback(char* ptr, size_t size, size_t nmemb, void* userdata) + { + Task* task = (Task*)userdata; + const size_t writeByteSize = size * nmemb; + + //if (task->result_buffer.size() - task->processed_length < writeByteSize) + // task->result_buffer.resize(task->result_buffer.size() + writeByteSize); + //writeByteSize = min(writeByteSize, task->result_buffer.capacity() - task->processed_length); + + //forceLogDebug_printf("task_write_callback: %d (processed: %d)", writeByteSize, task->processed_length); + if (writeByteSize > 0) + { + //memcpy(task->result_buffer.data() + task->processed_length, ptr, writeByteSize); + task->result_buffer.insert(task->result_buffer.end(), ptr, ptr + writeByteSize); + task->processed_length += writeByteSize; + } + + return writeByteSize; + } + + size_t task_download_header_callback(char* ptr, size_t size, size_t nitems, void* userdata) + { + //forceLogDebug_printf("\tHeader: %s", ptr); + + return size * nitems; + } + + size_t task_download_filecallback(char* ptr, size_t size, size_t nmemb, void* userdata) + { + Task* task = (Task*)userdata; + const size_t writeByteSize = size * nmemb; + //writeByteSize = min(writeByteSize, task->file_buffer.capacity() - task->processed_file_size); + if (writeByteSize > 0) + { + //memcpy(task->file_buffer.data() + task->processed_file_size, ptr, writeByteSize); + task->file_buffer.insert(task->file_buffer.end(), ptr, ptr + writeByteSize); + task->processed_file_size += writeByteSize; + } + return writeByteSize; + } + + size_t task_header_callback(char* ptr, size_t size, size_t nitems, void* userdata) + { + Task* task = (Task*)userdata; + if (starts_with(ptr, "Content-Length: ")) + { + task->content_length = strtol(&ptr[16], nullptr, 0); + task->result_buffer.clear(); + task->result_buffer.reserve(task->content_length); + task->processed_length = 0; + } + else if (starts_with(ptr, "Content-Type: ")) + { + const char* type = &ptr[14]; + if (starts_with(type, "application/xml")) + task->content_type = ContentType::kXmlContent; + else if (starts_with(type, "x-application/octet-stream")) + task->content_type = ContentType::kBinaryFile; + else if (starts_with(type, "text/html")) + task->content_type = ContentType::kText; + else + { + forceLogDebug_printf("task_header_callback: unknown content type > %s", type); + } + } + else if (starts_with(ptr, "Last-Modified: ")) + { + // TODO timestamp (?) + } + + //forceLogDebug_printf("task_header_callback: len %d (%d) and type %d", task->content_length, task->result_buffer.capacity(), task->content_type); + //forceLogDebug_printf("\t%s", ptr); + return size * nitems; + } + + static CURLcode task_sslctx_function(CURL* curl, void* sslctx, void* param) + { + auto task_settings = (TaskSetting*)param; + if (task_settings->taskType == kRawDlTaskSetting) + { + forceLogDebug_printf("sslctx_function: adding client cert: %d", (int)task_settings->settings[TaskSetting::kClientCert]); + if (!iosuCrypto_addClientCertificate(sslctx, task_settings->settings[TaskSetting::kClientCert])) + assert_dbg(); + + uint32 location = TaskSetting::kCACert; + for (int i = 0; i < 3; ++i) + { + if (task_settings->settings[location] != 0) + { + forceLogDebug_printf("sslctx_function: adding ca cert: %d", (int)task_settings->settings[location]); + if (!iosuCrypto_addCACertificate(sslctx, task_settings->settings[location])) + { + forceLog_printf("Failed to load CA certificate file"); + assert_dbg(); + } + } + + location += TaskSetting::kCACert; + } + } + else + { + if (!iosuCrypto_addCACertificate(sslctx, 105)) + { + forceLog_printf("Failed to load certificate file"); + assert_dbg(); + } + + if (!iosuCrypto_addClientCertificate(sslctx, 3)) + { + forceLog_printf("Failed to load client certificate file"); + assert_dbg(); + } + } + + SSL_CTX_set_cipher_list((SSL_CTX*)sslctx, "AES256-SHA"); + // TLS_RSA_WITH_AES_256_CBC_SHA (in CURL it's called rsa_aes_256_sha) + SSL_CTX_set_mode((SSL_CTX*)sslctx, SSL_MODE_AUTO_RETRY); + SSL_CTX_set_verify_depth((SSL_CTX*)sslctx, 2); + SSL_CTX_set_verify((SSL_CTX*)sslctx, SSL_VERIFY_PEER, nullptr); + return CURLE_OK; + } + + auto get_task(const char* taskId, uint32 accountId, uint64 titleId) + { + const auto it = std::find_if(g_boss.tasks.begin(), g_boss.tasks.end(), [taskId, accountId, titleId](const Task& task) + { + return 0 == strncmp(taskId, task.task_id, sizeof(Task::task_id)) && accountId == task.account_id && titleId == task. + title_id; + }); + + return it; + } + + bool parse_xml_content(Task& task) + { + tinyxml2::XMLDocument doc; + //cafeLog_writeLineToLog((char*)task.result_buffer.data()); + if (doc.Parse((const char*)task.result_buffer.data(), task.processed_length) != tinyxml2::XML_SUCCESS) + return false; + + for (tinyxml2::XMLElement* sheet = doc.FirstChildElement("TaskSheet"); sheet; sheet = sheet-> + NextSiblingElement("TaskSheet")) + { + const auto files = sheet->FirstChildElement("Files"); + if (!files) + continue; + + for (tinyxml2::XMLElement* file = files->FirstChildElement("File"); file; file = file->NextSiblingElement("File")) + { + auto file_name = file->FirstChildElement("Filename"); + if (!file_name) + continue; + + auto data_id = file->FirstChildElement("DataId"); + if (!data_id) + continue; + + auto type = file->FirstChildElement("Type"); + if (!type) + continue; + + auto url = file->FirstChildElement("Url"); + if (!url) + continue; + + auto size = file->FirstChildElement("Size"); + if (!size) + continue; + + FileType file_type; + if (0 == strcmp(type->GetText(), "AppData")) + file_type = FileType::kAppData; + else + { + file_type = FileType::kUnknownFile; + } + + task.queued_files.emplace(file_name->GetText(), data_id->IntText(), file_type, url->GetText(), size->IntText()); + } + } + + return true; + } + + const uint64 kTimeStampConvertSeconds = 946684800ULL; + BossStorageFadEntry* boss_storage_fad_find_entry(BossStorageFadFile& fad_file, uint32 data_id) + { + for (auto& entry : fad_file.entries) + { + if (entry.fileNameId == data_id) + return &entry; + } + + return nullptr; + } + + void boss_storage_fad_append_or_update(BossStorageFadFile& fad_file, const char* name, uint32 data_id, uint64 timestamp) + { + for (auto& entry : fad_file.entries) + { + if (entry.fileNameId == 0 || strcmp(entry.name, name) == 0) + { + entry.fileNameId = data_id; + strcpy(entry.name, name); + entry.timestampRelated = (uint32)(timestamp - kTimeStampConvertSeconds); // time since 2000 + return; + } + } + } + + uint32 task_run(const char* taskId, uint32 accountId, uint64 titleId) + { + const auto it = get_task(taskId, accountId, titleId); + if (it == g_boss.tasks.cend()) + { + //it->turn_state = kError; + //it->wait_state = TRUE; + return BUILD_NN_RESULT(NN_RESULT_LEVEL_FATAL, NN_RESULT_MODULE_NN_BOSS, 0); + } + + if (!ActiveSettings::IsOnlineEnabled()) + { + it->turn_state = kError; + it->wait_state = TRUE; + return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0); + } + forceLogDebug_printf("task run state: %d | exec: %d (tasks: %d)", it->turn_state, it->exec_count, g_boss.tasks.size()); + it->turn_state = kRunning; + it->exec_count++; + + /* + https://nppl.app.nintendo.net/p01/policylist/1/1/AT + https://npts.app.nintendo.net/p01/tasksheet/1/zvGSM4kO***kKnpT/schdat2?c=XX&l=en + https://npts.app.nintendo.net/p01/tasksheet/1/zvGSM4kO***kKnpT/optdat2?c=XX&l=en + https://npts.app.nintendo.net/p01/tasksheet/1/8UsM86l***jFk8z/wood1?c=XX&l=en + https://npts.app.nintendo.net/p01/tasksheet/1/8UsM86l***kjFk8z/woodBGM?c=XX&l=en + + + https://npts.app.nintendo.net/p01/tasksheet/%s/%s/%s/%s?c=%s&l=%s + https://npts.app.nintendo.net/p01/tasksheet/%s/%s/%s?c=%s&l=%s + 1 == version + bossCode + initFile + + */ + + uint32 turnstate = kSuccess; + struct curl_slist* list_headerParam = nullptr; + list_headerParam = append_header_param(list_headerParam, "X-BOSS-Digest"); // ??? + list_headerParam = append_header_param(list_headerParam, "X-Boss-UniqueId: {:05x}", ((titleId >> 8) & 0xFFFFF)); // %05x + list_headerParam = append_header_param(list_headerParam, "X-BOSS-TitleId: /usr/packages/title/{:016x}", titleId); // "/usr/packages/title/%016llx" + + CURL* curl = it->curl.get(); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); + curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 2); +#ifndef PUBLIC_RELEASE + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); + char errbuf[CURL_ERROR_SIZE]{}; + curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf); +#else + curl_easy_setopt(curl, CURLOPT_VERBOSE, 0); +#endif + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, task_write_callback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &(*it)); + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, task_header_callback); + curl_easy_setopt(curl, CURLOPT_HEADERDATA, &(*it)); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, 0x3C); + curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, task_sslctx_function); + curl_easy_setopt(curl, CURLOPT_SSL_CTX_DATA, &it->task_settings); + curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_0); + + char url[512]; + if(it->task_settings.taskType == kRawDlTaskSetting) + { + char serviceToken[TaskSetting::kServiceTokenLen]; + strncpy(serviceToken, (char*)&it->task_settings.settings[TaskSetting::kServiceToken], TaskSetting::kServiceTokenLen); + list_headerParam = append_header_param(list_headerParam, "X-Nintendo-ServiceToken: {}", serviceToken); + + strncpy(url, (char*)&it->task_settings.settings[TaskSetting::kURL], TaskSetting::kURLLen); + forceLogDebug_printf("\tserviceToken: %s", serviceToken); + } + else + { + char languageCode[8]; + switch (GetConfig().console_language) + { + case CafeConsoleLanguage::JA: + strcpy(languageCode, "ja"); + break; + case CafeConsoleLanguage::EN: + strcpy(languageCode, "en"); + break; + case CafeConsoleLanguage::FR: + strcpy(languageCode, "fr"); + break; + case CafeConsoleLanguage::DE: + strcpy(languageCode, "de"); + break; + case CafeConsoleLanguage::IT: + strcpy(languageCode, "it"); + break; + case CafeConsoleLanguage::ES: + strcpy(languageCode, "es"); + break; + case CafeConsoleLanguage::ZH: + strcpy(languageCode, "zh"); + break; + case CafeConsoleLanguage::KO: + strcpy(languageCode, "ko"); + break; + case CafeConsoleLanguage::NL: + strcpy(languageCode, "nl"); + break; + case CafeConsoleLanguage::PT: + strcpy(languageCode, "pt"); + break; + case CafeConsoleLanguage::RU: + strcpy(languageCode, "ru"); + break; + case CafeConsoleLanguage::TW: + strcpy(languageCode, "tw"); // usually zh-tw? + break; + default: + strcpy(languageCode, "en"); + break; + } + + const char* countryCode = NCrypto::GetCountryAsString(Account::GetCurrentAccount().GetCountry()); + + char boss_code[0x20]; + strncpy(boss_code, (char*)&it->task_settings.settings[TaskSetting::kBossCode], TaskSetting::kBossCodeLen); + + sprintf(url, "https://npts.app.nintendo.net/p01/tasksheet/%s/%s/%s?c=%s&l=%s", "1", boss_code, it->task_id, countryCode, languageCode); + } + + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list_headerParam); + curl_easy_setopt(curl, CURLOPT_URL, url); + forceLogDebug_printf("task_run url %s", url); + + int curl_result = curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &it->http_status_code); + + //it->turn_state = kFinished; + + curl_slist_free_all(list_headerParam); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, nullptr); + + if (curl_result != CURLE_OK) + { +#ifndef PUBLIC_RELEASE + forceLogDebug_printf("curl error buff: %s", errbuf); +#endif + it->turn_state = kError; + it->wait_state = TRUE; + forceLogDebug_printf("task_run curl fail: %d", curl_result); + return BUILD_NN_RESULT(NN_RESULT_LEVEL_FATAL, NN_RESULT_MODULE_NN_BOSS, 0); + } + else + { + if (it->http_status_code != 200) + { + forceLogDebug_printf("BOSS task_run: Received unexpected HTTP response code"); + } + if (it->http_status_code == 404) + { + // todo - is this correct behavior? + it->turn_state = kError; + it->wait_state = TRUE; + forceLogDebug_printf("task_run failed due to 404 error"); + return BUILD_NN_RESULT(NN_RESULT_LEVEL_FATAL, NN_RESULT_MODULE_NN_BOSS, 0); + } + } + + switch (it->content_type) + { + case ContentType::kXmlContent: + parse_xml_content(*it); + break; + case ContentType::kBinaryFile: + + break; + case ContentType::kText: + forceLogDebug_printf("task_run returns text: %.*s", it->content_length, it->result_buffer.data()); + break; + } + + + if (!it->queued_files.empty()) + { + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, task_download_header_callback); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, task_download_filecallback); + + std::string taskIdStr = it->task_id; + + try + { + fs::path path = ActiveSettings::GetMlcPath("usr/boss/{:08x}/{:08x}/user/common/data/{}", (uint32)(it->title_id >> 32), (uint32)(it->title_id & 0xFFFFFFFF), taskIdStr); + if (!fs::exists(path)) + fs::create_directories(path); + + char targetFileName[TaskSetting::kFileNameLen + 1]{}; + strncpy(targetFileName, (char*)&it->task_settings.settings[TaskSetting::kNbdlFileName], TaskSetting::kFileNameLen); + forceLogDebug_printf("\tnbdl task target filename: \"%s\"", targetFileName); + const bool hasFileName = targetFileName[0] != '\0'; + + while (!it->queued_files.empty()) + { + auto file = it->queued_files.front(); + it->queued_files.pop(); + // download only specific file + if (hasFileName && file.file_name != targetFileName) + continue; + + it->processed_file_size = 0; + it->file_buffer.clear(); + it->file_buffer.reserve(file.size); + + // create/open fad db content + BossStorageFadFile fad_content{}; + fs::path db_file = ActiveSettings::GetMlcPath("usr/boss/{:08x}/{:08x}/user/common/{:08x}/{}", (uint32)(it->title_id >> 32), (uint32)(it->title_id & 0xFFFFFFFF), it->account_id, taskIdStr); + if (!fs::exists(db_file)) + fs::create_directories(db_file); + + db_file /= "fad.db"; + std::ifstream fad_file(db_file, std::ios::in | std::ios::binary); + if (fad_file.is_open()) + { + if (!fad_file.read((char*)&fad_content, sizeof(BossStorageFadFile))) + fad_content = {}; + + fad_file.close(); + } + + auto currentEntry = boss_storage_fad_find_entry(fad_content, file.data_id); + if(currentEntry) + { + uint64 timestamp = (uint64)currentEntry->timestampRelated + kTimeStampConvertSeconds; + curl_easy_setopt(curl, CURLOPT_TIMEVALUE, timestamp); + curl_easy_setopt(curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE); + } + else + { + curl_easy_setopt(curl, CURLOPT_TIMEVALUE, 0); + curl_easy_setopt(curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_NONE); + } + + curl_easy_setopt(curl, CURLOPT_FILETIME, 1L); + curl_easy_setopt(curl, CURLOPT_HEADERDATA, task_download_header_callback); + curl_easy_setopt(curl, CURLOPT_URL, file.url.c_str()); + curl_result = curl_easy_perform(curl); + if (curl_result != CURLE_OK) + { + forceLogDebug_printf("task_run curl failed on file download (%d): %s > %s", curl_result, file.file_name.c_str(), file.url.c_str()); + if (hasFileName) + { + turnstate = kError; + break; + } + else + continue; + } + + long unmet = 1; + const CURLcode result = curl_easy_getinfo(curl, CURLINFO_CONDITION_UNMET, &unmet); + if(result == CURLE_OK && unmet == 1) + { + // file is already up2date + if (hasFileName) + break; + else + continue; + } + + if(it->processed_file_size != file.size) + { + forceLogDebug_printf("task_run file download size mismatch: %s > %s > %d from %d bytes", file.file_name.c_str(), file.url.c_str(), it->processed_file_size, file.size); + if (hasFileName) + { + turnstate = kError; + break; + } + else + continue; + } + + uint64 filetime; + curl_easy_getinfo(curl, CURLINFO_FILETIME, &filetime); + + // dunno about that one + it->processed_length += it->processed_file_size; + it->content_length += file.size; + + // bossAesKey = OTP.XorKey ^ bossXor + // bossXor: 33 AC 6D 15 C2 26 0A 91 3B BF 73 C3 55 D8 66 04 + uint8 bossAesKey[16] = { 0x39,0x70,0x57,0x35,0x58,0x70,0x34,0x58,0x37,0x41,0x7a,0x30,0x71,0x5a,0x70,0x74 }; // "9pW5Xp4X7Az0qZpt" + + BossNbdlHeader* nbdlHeader = (BossNbdlHeader*)it->file_buffer.data(); + + if (nbdlHeader->magic != 'boss') + break; + if (nbdlHeader->version != 0x20001) + break; + if (nbdlHeader->ukn08 != 1) + break; + if (nbdlHeader->ukn0A != 2) + break; + + // file must be padded to 16 byte alignment for AES decryption (padding is cut off after decryption) + const uint32 file_size = (it->processed_file_size + 0xF) & (~0xF); + if (file_size != it->processed_file_size) + { + it->file_buffer.resize(file_size); + nbdlHeader = (BossNbdlHeader*)it->file_buffer.data(); + } + + // prepare nonce for AES128-CTR + uint8 aesNonce[0x10]; + memset(aesNonce, 0, sizeof(aesNonce)); + memcpy(aesNonce, nbdlHeader->nonce, 0xC); + aesNonce[0xF] = 1; + + // decrypt header + AES128CTR_transform(it->file_buffer.data() + offsetof(BossNbdlHeader, encryptedHeader), sizeof(BossNbdlHeader::encryptedHeader), bossAesKey, aesNonce); + // decrypt everything else + AES128CTR_transform(it->file_buffer.data() + sizeof(BossNbdlHeader), file_size - sizeof(BossNbdlHeader), bossAesKey, aesNonce); + + try + { + // create file with content + fs::path file_path = path / fmt::format(L"{:08x}", file.data_id); + std::ofstream new_file(file_path, std::ios::out | std::ios::binary | std::ios::trunc); + new_file.write((char*)it->file_buffer.data() + sizeof(BossNbdlHeader), it->processed_file_size - sizeof(BossNbdlHeader)); + new_file.flush(); + new_file.close(); + + + boss_storage_fad_append_or_update(fad_content, file.file_name.c_str(), file.data_id, filetime); + std::ofstream fad_file_updated(db_file, std::ios::out | std::ios::binary | std::ios::trunc); + fad_file_updated.write((char*)&fad_content, sizeof(BossStorageFadFile)); + fad_file_updated.flush(); + fad_file_updated.close(); + } + catch (const std::exception& ex) + { + forceLogDebug_printf("file error: %s", ex.what()); + } + + if (hasFileName) + break; + } + } + catch (const std::exception& ex) + { + forceLogDebug_printf("dir error: %s", ex.what()); + } + } + + + if (it->task_settings.taskType == kRawDlTaskSetting) + { + char directoryName[TaskSetting::kDirectoryNameLen + 1]{}; + if (it->task_settings.settings[TaskSetting::kDirectoryName] != '\0') + strncpy(directoryName, (char*)&it->task_settings.settings[TaskSetting::kDirectoryName], TaskSetting::kDirectoryNameLen); + else + strncpy(directoryName, it->task_id, TaskSetting::kDirectoryNameLen); + + char fileName[TaskSetting::kFileNameLen + 1]{}; + strncpy(fileName, (char*)&it->task_settings.settings[TaskSetting::kFileName], TaskSetting::kFileNameLen); + + // mcl01\usr\boss\00050000\1018dd00\user\<persistentId>\<storageName>\<filename> + fs::path path = ActiveSettings::GetMlcPath("usr/boss/{:08x}/{:08x}/user/{:08x}", (uint32)(it->title_id >> 32), + (uint32)(it->title_id & 0xFFFFFFFF), iosuAct_getAccountIdOfCurrentAccount()); + path /= directoryName; + + if (!fs::exists(path)) + fs::create_directories(path); + + path /= fileName; + + std::ofstream file(path); + if (file.is_open()) + { + file.write((char*)it->result_buffer.data(), it->result_buffer.size()); + file.flush(); + file.close(); + } + } + + it->turn_state = turnstate; + it->wait_state = TRUE; + return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0); + } + + + bool task_is_registered(const char* taskId, uint32 accountId, uint64 titleId) + { + const auto it = get_task(taskId, accountId, titleId); + return it != g_boss.tasks.cend(); + } + + bool task_wait(const char* taskId, uint32 accountId, uint64 titleId, uint32 wait_state, uint32 timeout = 0) + { + const auto it = get_task(taskId, accountId, titleId); + if (it == g_boss.tasks.cend()) + { + return false; + } + + const auto start = tick_cached(); + while (it->wait_state != wait_state) + { + if (timeout != 0 && (uint32)std::chrono::duration_cast<std::chrono::seconds>(tick_cached() - start).count() >= timeout) + { + forceLogDebug_printf("task_wait: timeout reached -> %d seconds passed", timeout); + return false; + } + + std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + + return true; + } + + uint32 task_register(const char* taskId, uint32 accountId, uint64 titleId, void* settings) + { + g_boss.tasks.emplace_back(taskId, accountId, titleId, (TaskSetting*)settings); + g_boss.tasks[g_boss.tasks.size() - 1].turn_state = kWaitTime; + return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0); + } + + uint32 task_register_immediate_run(const char* taskId, uint32 accountId, uint64 titleId, void* settings) + { + g_boss.tasks.emplace_back(taskId, accountId, titleId, (TaskSetting*)settings); + g_boss.tasks[g_boss.tasks.size() - 1].turn_state = kWaitRun; + return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0); + } + + void task_unregister(const char* taskId, uint32 accountId, uint64 titleId) + { + const auto it = get_task(taskId, accountId, titleId); + if (it != g_boss.tasks.cend()) + g_boss.tasks.erase(it); + } + + std::pair<uint32, uint64> task_get_content_length(const char* taskId, uint32 accountId, uint64 titleId) + { + const auto it = get_task(taskId, accountId, titleId); + return it != g_boss.tasks.cend() ? std::make_pair(it->exec_count, it->content_length) : std::make_pair(0u, (uint64)0); + } + + std::pair<uint32, uint64> task_get_processed_length(const char* taskId, uint32 accountId, uint64 titleId) + { + const auto it = get_task(taskId, accountId, titleId); + return it != g_boss.tasks.cend() ? std::make_pair(it->exec_count, it->processed_length) : std::make_pair(0u, (uint64)0); + } + + std::pair<uint32, uint32> task_get_http_status_code(const char* taskId, uint32 accountId, uint64 titleId) + { + const auto it = get_task(taskId, accountId, titleId); + return it != g_boss.tasks.cend() ? std::make_pair(it->exec_count, it->http_status_code) : std::make_pair(0u, (uint32)0); + } + + std::pair<uint32, uint32> task_get_turn_state(const char* taskId, uint32 accountId, uint64 titleId) + { + const auto it = get_task(taskId, accountId, titleId); + return it != g_boss.tasks.cend() ? std::make_pair(it->exec_count, it->turn_state) : std::make_pair(0u, (uint32)0); + } + + uint32 task_stop_scheduling(const char* task_id, uint32 account_id, uint64 title_id) + { + const auto it = get_task(task_id, account_id, title_id); + if (it != g_boss.tasks.cend()) + { + it->turn_state = kStopped; + // todo actually cancel the task if currently running (curl call) + // curl_easy_pause() -> resume on start scheduling if paused + } + + return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0); + } + + void boss_thread() + { + SetThreadName("boss_thread"); + while (true) + { + const uint32 return_value = 0; // Ioctl return value + + ioQueueEntry_t* ioQueueEntry = iosuIoctl_getNextWithWait(IOS_DEVICE_BOSS); + if (ioQueueEntry->request == IOSU_BOSS_REQUEST_CEMU) + { + iosuBossCemuRequest_t* cemu_request = (iosuBossCemuRequest_t*)ioQueueEntry->bufferVectors[0].buffer.GetPtr(); + cemu_request->returnCode = 0; + + uint64 title_id; + if (cemu_request->titleId == 0) + title_id = CafeSystem::GetForegroundTitleId(); + else + title_id = cemu_request->titleId; + + uint32 account_id; + if (cemu_request->accountId == 0) + account_id = iosuAct_getAccountIdOfCurrentAccount(); + else + account_id = cemu_request->accountId; + + if (cemu_request->requestCode == IOSU_NN_BOSS_TASK_RUN) + { + cemu_request->returnCode = task_run(cemu_request->taskId, account_id, title_id); + } + else if (cemu_request->requestCode == IOSU_NN_BOSS_TASK_GET_CONTENT_LENGTH) + { + auto result = task_get_content_length(cemu_request->taskId, account_id, title_id); + cemu_request->u64.exec_count = std::get<0>(result); + cemu_request->u64.result = std::get<1>(result); + } + else if (cemu_request->requestCode == IOSU_NN_BOSS_TASK_GET_PROCESSED_LENGTH) + { + auto result = task_get_processed_length(cemu_request->taskId, account_id, title_id); + cemu_request->u64.exec_count = std::get<0>(result); + cemu_request->u64.result = std::get<1>(result); + } + else if (cemu_request->requestCode == IOSU_NN_BOSS_TASK_GET_HTTP_STATUS_CODE) + { + auto result = task_get_http_status_code(cemu_request->taskId, account_id, title_id); + cemu_request->u32.exec_count = std::get<0>(result); + cemu_request->u32.result = std::get<1>(result); + } + else if (cemu_request->requestCode == IOSU_NN_BOSS_TASK_GET_TURN_STATE) + { + auto result = task_get_turn_state(cemu_request->taskId, account_id, title_id); + cemu_request->u32.exec_count = std::get<0>(result); + cemu_request->u32.result = std::get<1>(result); + } + else if (cemu_request->requestCode == IOSU_NN_BOSS_TASK_WAIT) + { + cemu_request->returnCode = task_wait(cemu_request->taskId, account_id, title_id, cemu_request->waitState, + cemu_request->timeout); + } + else if (cemu_request->requestCode == IOSU_NN_BOSS_TASK_REGISTER) + { + cemu_request->returnCode = task_register(cemu_request->taskId, account_id, title_id, cemu_request->settings); + } + else if (cemu_request->requestCode == IOSU_NN_BOSS_TASK_IS_REGISTERED) + { + cemu_request->returnCode = task_is_registered(cemu_request->taskId, account_id, title_id) ? TRUE : FALSE; + } + else if (cemu_request->requestCode == IOSU_NN_BOSS_TASK_REGISTER_FOR_IMMEDIATE_RUN) + { + cemu_request->returnCode = task_register_immediate_run(cemu_request->taskId, account_id, title_id, + cemu_request->settings); + } + else if (cemu_request->requestCode == IOSU_NN_BOSS_TASK_UNREGISTER) + { + task_unregister(cemu_request->taskId, account_id, title_id); + } + else if (cemu_request->requestCode == IOSU_NN_BOSS_TASK_START_SCHEDULING) + { + // we just run it no matter what + //if(cemu_request->bool_parameter) + cemu_request->returnCode = task_run(cemu_request->taskId, account_id, title_id); + /*else + { + const auto it = get_task(cemu_request->taskId, account_id, title_id); + if (it != g_boss.tasks.cend()) + { + it->turn_state = kWaitRun; + } + }*/ + } + else if (cemu_request->requestCode == IOSU_NN_BOSS_TASK_STOP_SCHEDULING) + { + cemu_request->returnCode = task_stop_scheduling(cemu_request->taskId, account_id, title_id); + } + else + assert_dbg(); + } + else + assert_dbg(); + + iosuIoctl_completeRequest(ioQueueEntry, return_value); + } + } + + void boss_init() + { + if (g_boss.is_initialized) + return; + + // start the boss thread + std::thread t(boss_thread); + t.detach(); + + g_boss.is_initialized = true; + } +} diff --git a/src/Cafe/IOSU/legacy/iosu_boss.h b/src/Cafe/IOSU/legacy/iosu_boss.h new file mode 100644 index 00000000..88fa94f7 --- /dev/null +++ b/src/Cafe/IOSU/legacy/iosu_boss.h @@ -0,0 +1,54 @@ +#pragma once + +#include "iosu_ioctl.h" + +// custom dev/boss protocol (Cemu only) +#define IOSU_BOSS_REQUEST_CEMU (0xEE) + +typedef struct +{ + uint32 requestCode; + // input + uint32 accountId; + char* taskId; + bool bool_parameter; + uint64 titleId; + uint32 timeout; + uint32 waitState; + uint32 uk1; + void* settings; + + // output + uint32 returnCode; // ACP return value + union + { + struct + { + uint32 exec_count; + uint32 result; + } u32; + struct + { + uint32 exec_count; + uint64 result; + } u64; + }; +}iosuBossCemuRequest_t; + +#define IOSU_NN_BOSS_TASK_RUN (0x01) +#define IOSU_NN_BOSS_TASK_GET_CONTENT_LENGTH (0x02) +#define IOSU_NN_BOSS_TASK_GET_PROCESSED_LENGTH (0x03) +#define IOSU_NN_BOSS_TASK_GET_HTTP_STATUS_CODE (0x04) +#define IOSU_NN_BOSS_TASK_GET_TURN_STATE (0x05) +#define IOSU_NN_BOSS_TASK_WAIT (0x06) +#define IOSU_NN_BOSS_TASK_REGISTER (0x07) +#define IOSU_NN_BOSS_TASK_IS_REGISTERED (0x08) +#define IOSU_NN_BOSS_TASK_REGISTER_FOR_IMMEDIATE_RUN (0x09) +#define IOSU_NN_BOSS_TASK_UNREGISTER (0x0A) +#define IOSU_NN_BOSS_TASK_START_SCHEDULING (0x0B) +#define IOSU_NN_BOSS_TASK_STOP_SCHEDULING (0x0C) + +namespace iosu +{ + void boss_init(); +} \ No newline at end of file diff --git a/src/Cafe/IOSU/legacy/iosu_crypto.cpp b/src/Cafe/IOSU/legacy/iosu_crypto.cpp new file mode 100644 index 00000000..28419e85 --- /dev/null +++ b/src/Cafe/IOSU/legacy/iosu_crypto.cpp @@ -0,0 +1,699 @@ +#include "iosu_crypto.h" + +#include "config/ActiveSettings.h" +#include "openssl/bn.h" +#include "openssl/obj_mac.h" +#include "openssl/ec.h" +#include "openssl/x509.h" +#include "openssl/ssl.h" +#include "openssl/sha.h" +#include "openssl/ecdsa.h" + +#include "util/crypto/aes128.h" +#include "Common/filestream.h" + +uint8 otpMem[1024]; +bool hasOtpMem = false; + +uint8 seepromMem[512]; +bool hasSeepromMem = false; + +struct +{ + bool hasCertificates; + struct + { + bool isValid; + sint32 id; + X509* cert; + std::vector<uint8> certData; + RSA* pkey; + std::vector<uint8> pkeyDERData; + }certList[256]; + sint32 certListCount; +}iosuCryptoCertificates = { 0 }; + +CertECC_t g_wiiuDeviceCert; + +void iosuCrypto_readOtpData(void* output, sint32 wordIndex, sint32 size) +{ + memcpy(output, otpMem + wordIndex * 4, size); +} + +void iosuCrypto_readOtpData(uint32be& output, sint32 wordIndex) +{ + memcpy(&output, otpMem + wordIndex * 4, sizeof(uint32be)); +} + +void iosuCrypto_readSeepromData(void* output, sint32 wordIndex, sint32 size) +{ + memcpy(output, seepromMem + wordIndex * 2, size); +} + +bool iosuCrypto_getDeviceId(uint32* deviceId) +{ + uint32be deviceIdBE; + *deviceId = 0; + if (otpMem == nullptr) + return false; + iosuCrypto_readOtpData(&deviceIdBE, 0x87, sizeof(uint32)); + *deviceId = (uint32)deviceIdBE; + return true; +} + +void iosuCrypto_getDeviceSerialString(char* serialString) +{ + char serialStringPart0[32]; // code + char serialStringPart1[32]; // serial + if (hasSeepromMem == false) + { + strcpy(serialString, "FEH000000000"); + return; + } + memset(serialStringPart0, 0, sizeof(serialStringPart0)); + memset(serialStringPart1, 0, sizeof(serialStringPart1)); + iosuCrypto_readSeepromData(serialStringPart0, 0xAC, 8); + iosuCrypto_readSeepromData(serialStringPart1, 0xB0, 0x10); + sprintf(serialString, "%s%s", serialStringPart0, serialStringPart1); +} + +static const char* base64_charset = +"ABCDEFGHIJKLMNOPQRSTUVWXYZ" +"abcdefghijklmnopqrstuvwxyz" +"0123456789+/"; + +int iosuCrypto_base64Encode(unsigned char const* bytes_to_encode, unsigned int inputLen, char* output) +{ + int i = 0; + int j = 0; + unsigned char charArray_3[3]; + unsigned char charArray_4[4]; + sint32 outputLength = 0; + while (inputLen--) + { + charArray_3[i++] = *(bytes_to_encode++); + if (i == 3) + { + charArray_4[0] = (charArray_3[0] & 0xfc) >> 2; + charArray_4[1] = ((charArray_3[0] & 0x03) << 4) + ((charArray_3[1] & 0xf0) >> 4); + charArray_4[2] = ((charArray_3[1] & 0x0f) << 2) + ((charArray_3[2] & 0xc0) >> 6); + charArray_4[3] = charArray_3[2] & 0x3f; + for (i = 0; (i < 4); i++) + { + output[outputLength] = base64_charset[charArray_4[i]]; + outputLength++; + } + i = 0; + } + } + if (i) + { + for (j = i; j < 3; j++) + charArray_3[j] = '\0'; + + charArray_4[0] = (charArray_3[0] & 0xfc) >> 2; + charArray_4[1] = ((charArray_3[0] & 0x03) << 4) + ((charArray_3[1] & 0xf0) >> 4); + charArray_4[2] = ((charArray_3[1] & 0x0f) << 2) + ((charArray_3[2] & 0xc0) >> 6); + charArray_4[3] = charArray_3[2] & 0x3f; + + for (j = 0; j < (i + 1); j++) + { + output[outputLength] = base64_charset[charArray_4[j]]; + outputLength++; + } + while (i++ < 3) + { + output[outputLength] = '='; + outputLength++; + } + } + return outputLength; +} + +std::string iosuCrypto_base64Encode(unsigned char const* bytes_to_encode, unsigned int inputLen) +{ + int encodedLen = inputLen / 3 * 4 + 16; + std::string strB64; + strB64.resize(encodedLen); + int outputLen = iosuCrypto_base64Encode(bytes_to_encode, inputLen, strB64.data()); + cemu_assert_debug(outputLen < strB64.size()); + strB64.resize(outputLen); + return strB64; +} +static_assert(sizeof(CertECC_t) == sizeof(CertECC_t)); + +EC_KEY* ECCPubKey_getPublicKey(ECCPubKey& pubKey) // verified and works +{ + BIGNUM* bn_r = BN_new(); + BIGNUM* bn_s = BN_new(); + BN_bin2bn(pubKey.keyData + 0, 30, bn_r); + BN_bin2bn(pubKey.keyData + 30, 30, bn_s); + + EC_KEY* ec_pubKey = EC_KEY_new_by_curve_name(NID_sect233r1); + int r = EC_KEY_set_public_key_affine_coordinates(ec_pubKey, bn_r, bn_s); + + BN_free(bn_r); + BN_free(bn_s); + + return ec_pubKey; +} + +ECDSA_SIG* ECCPubKey_getSignature(CertECC_t& cert) +{ + BIGNUM* bn_r = BN_new(); + BIGNUM* bn_s = BN_new(); + BN_bin2bn(cert.signature + 0, 30, bn_r); + BN_bin2bn(cert.signature + 30, 30, bn_s); + + //EC_KEY* ec_pubKey = EC_KEY_new_by_curve_name(NID_sect233r1); + //int r = EC_KEY_set_public_key_affine_coordinates(ec_pubKey, bn_r, bn_s); + ECDSA_SIG* ec_sig = ECDSA_SIG_new(); + //ECDSA_do_sign_ex +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + ECDSA_SIG_set0(ec_sig, bn_r, bn_s); +#else + BN_copy(ec_sig->r, bn_r); + BN_copy(ec_sig->s, bn_s); +#endif + + BN_free(bn_r); + BN_free(bn_s); + + return ec_sig; +} + +void ECCPubKey_setSignature(CertECC_t& cert, ECDSA_SIG* sig) +{ +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + const BIGNUM* sig_r = nullptr, * sig_s = nullptr; + ECDSA_SIG_get0(sig, &sig_r, &sig_s); + + sint32 lenR = BN_num_bytes(sig_r); + sint32 lenS = BN_num_bytes(sig_s); + + cemu_assert_debug(lenR <= 30); + cemu_assert_debug(lenS <= 30); + + memset(cert.signature, 0, sizeof(cert.signature)); + BN_bn2bin(sig_r, cert.signature + (30 - lenR)); + BN_bn2bin(sig_s, cert.signature + 60 / 2 + (30 - lenS)); +#else + sint32 lenR = BN_num_bytes(sig->r); + sint32 lenS = BN_num_bytes(sig->s); + + cemu_assert_debug(lenR <= 30); + cemu_assert_debug(lenS <= 30); + + memset(cert.signature, 0, sizeof(cert.signature)); + BN_bn2bin(sig->r, cert.signature + (30 - lenR)); + BN_bn2bin(sig->s, cert.signature + 60 / 2 + (30 - lenS)); +#endif +} + +ECCPrivKey g_consoleCertPrivKey{}; + +void iosuCrypto_getDeviceCertPrivateKey(void* privKeyOut, sint32 len) +{ + cemu_assert(len == 30); + memcpy(privKeyOut, g_consoleCertPrivKey.keyData, 30); +} + +void iosuCrypto_getDeviceCertificate(void* certOut, sint32 len) +{ + cemu_assert(len == 0x180); + memcpy(certOut, &g_wiiuDeviceCert, 0x180); +} + +void iosuCrypto_generateDeviceCertificate() +{ + static_assert(sizeof(g_wiiuDeviceCert) == 0x180); + memset(&g_wiiuDeviceCert, 0, sizeof(g_wiiuDeviceCert)); + if (otpMem == nullptr) + return; // cant generate certificate without OPT + + // set header based on otp security mode + g_wiiuDeviceCert.sigType = CertECC_t::SIGTYPE::ECC_SHA256; + + g_wiiuDeviceCert.ukn0C0[0] = 0x00; + g_wiiuDeviceCert.ukn0C0[1] = 0x00; + g_wiiuDeviceCert.ukn0C0[2] = 0x00; + g_wiiuDeviceCert.ukn0C0[3] = 0x02; + + + iosuCrypto_readOtpData(g_wiiuDeviceCert.signature, 0xA3, 0x3C); + + uint32be caValue; + iosuCrypto_readOtpData(caValue, 0xA1); + uint32be msValue; + iosuCrypto_readOtpData(msValue, 0xA0); + + sprintf(g_wiiuDeviceCert.certificateSubject, "Root-CA%08x-MS%08x", (uint32)caValue, (uint32)msValue); + + uint32be ngNameValue; + iosuCrypto_readOtpData(ngNameValue, 0x87); + sprintf(g_wiiuDeviceCert.ngName, "NG%08x", (uint32)ngNameValue); + + iosuCrypto_readOtpData(&g_wiiuDeviceCert.ngKeyId, 0xA2, sizeof(uint32)); + + + uint8 privateKey[0x20]; + memset(privateKey, 0, sizeof(privateKey)); + iosuCrypto_readOtpData(privateKey, 0x88, 0x1E); + memcpy(g_consoleCertPrivKey.keyData, privateKey, 30); + + auto context = BN_CTX_new(); + BN_CTX_start(context); + BIGNUM* bn_privKey = BN_CTX_get(context); + BN_bin2bn(privateKey, 0x1E, bn_privKey); + + EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_sect233r1); + EC_POINT *pubkey = EC_POINT_new(group); + + EC_POINT_mul(group, pubkey, bn_privKey, NULL, NULL, NULL); + + BIGNUM* bn_x = BN_CTX_get(context); + BIGNUM* bn_y = BN_CTX_get(context); + + EC_POINT_get_affine_coordinates_GF2m(group, pubkey, bn_x, bn_y, NULL); + + uint8 publicKeyOutput[0x3C]; + memset(publicKeyOutput, 0, sizeof(publicKeyOutput)); + + sint32 lenX = BN_num_bytes(bn_x); + sint32 lenY = BN_num_bytes(bn_y); + + BN_bn2bin(bn_x, publicKeyOutput + (0x1E - lenX)); // todo - verify if the bias is correct + BN_bn2bin(bn_y, publicKeyOutput + 0x3C / 2 + (0x1E - lenY)); + + memcpy(g_wiiuDeviceCert.publicKey, publicKeyOutput, 0x3C); + + // clean up + EC_POINT_free(pubkey); + BN_CTX_end(context); // clears all BN variables + BN_CTX_free(context); +} + +void CertECC_generateHashForSignature(CertECC_t& cert, CHash256& hashOut) +{ + SHA256_CTX sha256; + SHA256_Init(&sha256); + SHA256_Update(&sha256, cert.certificateSubject, 0x100); + SHA256_Final(hashOut.b, &sha256); +} + +bool iosuCrypto_hasAllDataForLogin() +{ + if (hasOtpMem == false) + return false; + if (hasSeepromMem == false) + return false; + // todo - check if certificates are available + return true; +} + +sint32 iosuCrypto_getDeviceCertificateBase64Encoded(char* output) +{ + iosuCrypto_base64Encode((uint8*)&g_wiiuDeviceCert, sizeof(g_wiiuDeviceCert), output); + sint32 len = sizeof(g_wiiuDeviceCert) / 3 * 4; + output[len] = '\0'; + return len; +} + +bool iosuCrypto_loadCertificate(uint32 id, std::wstring_view mlcSubpath, std::wstring_view pkeyMlcSubpath) +{ + X509* cert = nullptr; + // load cert data + const auto certPath = ActiveSettings::GetMlcPath(mlcSubpath); + auto certData = FileStream::LoadIntoMemory(certPath); + if (!certData) + return false; // file missing + // get optional aes encrypted private key data + std::optional<std::vector<uint8>> pkeyData; + if (!pkeyMlcSubpath.empty()) + { + const auto pkeyPath = ActiveSettings::GetMlcPath(pkeyMlcSubpath); + pkeyData = FileStream::LoadIntoMemory(pkeyPath); + if (!pkeyData || pkeyData->empty()) + { + cemuLog_force("Unable to load private key file {}", pkeyPath.generic_string()); + return false; + } + else if ((pkeyData->size() % 16) != 0) + { + cemuLog_force("Private key file has invalid length. Possibly corrupted? File: {}", pkeyPath.generic_string()); + return false; + } + } + // load certificate + unsigned char* tempPtr = (unsigned char*)certData->data(); + cert = d2i_X509(nullptr, (const unsigned char**)&tempPtr, certData->size()); + if (cert == nullptr) + { + cemuLog_log(LogType::Force, "IOSU_CRYPTO: Unable to load certificate \"{}\"", boost::nowide::narrow(std::wstring(mlcSubpath))); + return false; + } + // load optional rsa key + RSA* pkeyRSA = nullptr; + if (pkeyData) + { + cemu_assert((pkeyData->size() & 15) == 0); + uint8 aesKey[16]; + uint8 iv[16] = { 0 }; + uint8 pkeyDecryptedData[4096]; + // decrypt pkey + iosuCrypto_readOtpData(aesKey, 0x120 / 4, 16); + AES128_CBC_decrypt(pkeyDecryptedData, pkeyData->data(), pkeyData->size(), aesKey, iv); + // convert to OpenSSL RSA pkey + unsigned char* pkeyTempPtr = pkeyDecryptedData; + pkeyRSA = d2i_RSAPrivateKey(nullptr, (const unsigned char **)&pkeyTempPtr, pkeyData->size()); + if (pkeyRSA == nullptr) + { + cemuLog_log(LogType::Force, "IOSU_CRYPTO: Unable to decrypt private key \"{}\"", boost::nowide::narrow(std::wstring(pkeyMlcSubpath))); + return false; + } + // encode private key as DER + EVP_PKEY *evpPkey = EVP_PKEY_new(); + EVP_PKEY_assign_RSA(evpPkey, pkeyRSA); + std::vector<uint8> derPKeyData(1024 * 32); + unsigned char* derPkeyTemp = derPKeyData.data(); + sint32 derPkeySize = i2d_PrivateKey(evpPkey, &derPkeyTemp); + derPKeyData.resize(derPkeySize); + derPKeyData.shrink_to_fit(); + iosuCryptoCertificates.certList[iosuCryptoCertificates.certListCount].pkeyDERData = derPKeyData; + } + + // register certificate and optional pkey + iosuCryptoCertificates.certList[iosuCryptoCertificates.certListCount].cert = cert; + iosuCryptoCertificates.certList[iosuCryptoCertificates.certListCount].certData = *certData; + iosuCryptoCertificates.certList[iosuCryptoCertificates.certListCount].pkey = pkeyRSA; + iosuCryptoCertificates.certList[iosuCryptoCertificates.certListCount].id = id; + iosuCryptoCertificates.certList[iosuCryptoCertificates.certListCount].isValid = true; + iosuCryptoCertificates.certListCount++; + return true; +} + +bool iosuCrypto_addClientCertificate(void* sslctx, sint32 certificateId) +{ + SSL_CTX* ctx = (SSL_CTX*)sslctx; + // find entry + for (sint32 i = 0; i < iosuCryptoCertificates.certListCount; i++) + { + if (iosuCryptoCertificates.certList[i].isValid && iosuCryptoCertificates.certList[i].id == certificateId) + { + if (SSL_CTX_use_certificate(ctx, iosuCryptoCertificates.certList[i].cert) != 1) + { + forceLog_printf("Unable to setup certificate %d", certificateId); + return false; + } + if (SSL_CTX_use_RSAPrivateKey(ctx, iosuCryptoCertificates.certList[i].pkey) != 1) + { + forceLog_printf("Unable to setup certificate %d RSA private key", certificateId); + return false; + } + + if (SSL_CTX_check_private_key(ctx) == false) + { + forceLog_printf("Certificate private key could not be validated (verify required files for online mode or disable online mode)"); + } + + return true; + } + } + forceLog_printf("Certificate not found (verify required files for online mode or disable online mode)"); + return false; +} + +bool iosuCrypto_addCACertificate(void* sslctx, sint32 certificateId) +{ + SSL_CTX* ctx = (SSL_CTX*)sslctx; + // find entry + for (sint32 i = 0; i < iosuCryptoCertificates.certListCount; i++) + { + if (iosuCryptoCertificates.certList[i].isValid && iosuCryptoCertificates.certList[i].id == certificateId) + { + X509_STORE* store = SSL_CTX_get_cert_store((SSL_CTX*)sslctx); + X509_STORE_add_cert(store, iosuCryptoCertificates.certList[i].cert); + return true; + } + } + return false; +} + +bool iosuCrypto_addCustomCACertificate(void* sslctx, uint8* certData, sint32 certLength) +{ + SSL_CTX* ctx = (SSL_CTX*)sslctx; + X509_STORE* store = SSL_CTX_get_cert_store((SSL_CTX*)sslctx); + unsigned char* tempPtr = (unsigned char*)certData; + X509* cert = d2i_X509(NULL, (const unsigned char**)&tempPtr, certLength); + if (cert == nullptr) + { + forceLog_printf("Invalid custom server PKI certificate"); + return false; + } + X509_STORE_add_cert(store, cert); + return true; +} + +uint8* iosuCrypto_getCertificateDataById(sint32 certificateId, sint32* certificateSize) +{ + for (sint32 i = 0; i < iosuCryptoCertificates.certListCount; i++) + { + if (iosuCryptoCertificates.certList[i].isValid && iosuCryptoCertificates.certList[i].id == certificateId) + { + *certificateSize = iosuCryptoCertificates.certList[i].certData.size(); + return iosuCryptoCertificates.certList[i].certData.data(); + } + } + return nullptr; +} + +uint8* iosuCrypto_getCertificatePrivateKeyById(sint32 certificateId, sint32* certificateSize) +{ + for (sint32 i = 0; i < iosuCryptoCertificates.certListCount; i++) + { + if (iosuCryptoCertificates.certList[i].isValid && iosuCryptoCertificates.certList[i].id == certificateId) + { + *certificateSize = iosuCryptoCertificates.certList[i].pkeyDERData.size(); + return iosuCryptoCertificates.certList[i].pkeyDERData.data(); + } + } + return nullptr; +} + +struct +{ + const int id; + const wchar_t name[256]; + const wchar_t key[256]; +} const g_certificates[] = { + // NINTENDO CLIENT CERTS + { 1, L"ccerts/WIIU_COMMON_1_CERT.der", L"ccerts/WIIU_COMMON_1_RSA_KEY.aes" }, + { 3, L"ccerts/WIIU_ACCOUNT_1_CERT.der", L"ccerts/WIIU_ACCOUNT_1_RSA_KEY.aes" }, + { 4, L"ccerts/WIIU_OLIVE_1_CERT.der", L"ccerts/WIIU_OLIVE_1_RSA_KEY.aes" }, + { 5, L"ccerts/WIIU_VINO_1_CERT.der", L"ccerts/WIIU_VINO_1_RSA_KEY.aes" }, + { 6, L"ccerts/WIIU_WOOD_1_CERT.der", L"ccerts/WIIU_WOOD_1_RSA_KEY.aes" }, + { 7, L"ccerts/WIIU_OLIVE_1_CERT.der", L"ccerts/WIIU_OLIVE_1_RSA_KEY.aes" }, + { 8, L"ccerts/WIIU_WOOD_1_CERT.der", L"ccerts/WIIU_WOOD_1_RSA_KEY.aes" }, + + // NINTENDO CA CERTS + { 100, L"scerts/CACERT_NINTENDO_CA.der", L"" }, + { 101, L"scerts/CACERT_NINTENDO_CA_G2.der", L"" }, + { 102, L"scerts/CACERT_NINTENDO_CA_G3.der", L"" }, + { 103, L"scerts/CACERT_NINTENDO_CLASS2_CA.der", L"" }, + { 104, L"scerts/CACERT_NINTENDO_CLASS2_CA_G2.der", L"" }, + { 105, L"scerts/CACERT_NINTENDO_CLASS2_CA_G3.der", L"" }, + + // COMMERCIAL CA CERTS + { 1001, L"scerts/BALTIMORE_CYBERTRUST_ROOT_CA.der", L"" }, + { 1002, L"scerts/CYBERTRUST_GLOBAL_ROOT_CA.der", L"" }, + { 1003, L"scerts/VERIZON_GLOBAL_ROOT_CA.der", L"" }, + { 1004, L"scerts/GLOBALSIGN_ROOT_CA.der", L"" }, + { 1005, L"scerts/GLOBALSIGN_ROOT_CA_R2.der", L"" }, + { 1006, L"scerts/GLOBALSIGN_ROOT_CA_R3.der", L"" }, + { 1007, L"scerts/VERISIGN_CLASS3_PUBLIC_PRIMARY_CA_G3.der", L"" }, + { 1008, L"scerts/VERISIGN_UNIVERSAL_ROOT_CA.der", L"" }, + { 1009, L"scerts/VERISIGN_CLASS3_PUBLIC_PRIMARY_CA_G5.der", L"" }, + { 1010, L"scerts/THAWTE_PRIMARY_ROOT_CA_G3.der", L"" }, + { 1011, L"scerts/THAWTE_PRIMARY_ROOT_CA.der", L"" }, + { 1012, L"scerts/GEOTRUST_GLOBAL_CA.der", L"" }, + { 1013, L"scerts/GEOTRUST_GLOBAL_CA2.der", L"" }, + { 1014, L"scerts/GEOTRUST_PRIMARY_CA.der", L"" }, + { 1015, L"scerts/GEOTRUST_PRIMARY_CA_G3.der", L"" }, + { 1016, L"scerts/ADDTRUST_EXT_CA_ROOT.der", L"" }, + { 1017, L"scerts/COMODO_CA.der", L"" }, + { 1018, L"scerts/UTN_DATACORP_SGC_CA.der", L"" }, + { 1019, L"scerts/UTN_USERFIRST_HARDWARE_CA.der" , L"" }, + { 1020, L"scerts/DIGICERT_HIGH_ASSURANCE_EV_ROOT_CA.der", L"" }, + { 1021, L"scerts/DIGICERT_ASSURED_ID_ROOT_CA.der", L"" }, + { 1022, L"scerts/DIGICERT_GLOBAL_ROOT_CA.der", L"" }, + { 1023, L"scerts/GTE_CYBERTRUST_GLOBAL_ROOT.der", L"" }, + { 1024, L"scerts/VERISIGN_CLASS3_PUBLIC_PRIMARY_CA.der", L"" }, + { 1025, L"scerts/THAWTE_PREMIUM_SERVER_CA.der", L"" }, + { 1026, L"scerts/EQUIFAX_SECURE_CA.der", L"" }, + { 1027, L"scerts/ENTRUST_SECURE_SERVER_CA.der", L"" }, + { 1028, L"scerts/VERISIGN_CLASS3_PUBLIC_PRIMARY_CA_G2.der", L"" }, + { 1029, L"scerts/ENTRUST_CA_2048.der", L"" }, + { 1030, L"scerts/ENTRUST_ROOT_CA.der", L"" }, + { 1031, L"scerts/ENTRUST_ROOT_CA_G2.der", L"" }, + { 1032, L"scerts/DIGICERT_ASSURED_ID_ROOT_CA_G2.der", L"" }, + { 1033, L"scerts/DIGICERT_GLOBAL_ROOT_CA_G2.der", L"" }, +}; + +void iosuCrypto_loadSSLCertificates() +{ + if (iosuCryptoCertificates.hasCertificates) + return; + + if (!hasOtpMem) + return; // cant load certificates without OTP keys + + // load CA certificate + bool hasAllCertificates = true; + for( const auto& c : g_certificates ) + { + std::wstring certDir = L"sys/title/0005001b/10054000/content/"; + std::wstring certFilePath = certDir + c.name; + std::wstring keyFilePath; + + if( *c.key ) + keyFilePath = certDir + c.key; + else + keyFilePath.clear(); + + if (iosuCrypto_loadCertificate(c.id, certFilePath, keyFilePath) == false) + { + cemuLog_log(LogType::Force, "Unable to load certificate \"{}\"", boost::nowide::narrow(certFilePath)); + hasAllCertificates = false; + } + } + iosuCryptoCertificates.hasCertificates = hasAllCertificates; // true +} + +void iosuCrypto_init() +{ + // load OTP dump + if (std::ifstream otp_file(ActiveSettings::GetPath("otp.bin"), std::ifstream::in | std::ios::binary); otp_file.is_open()) + { + otp_file.seekg(0, std::ifstream::end); + const auto length = otp_file.tellg(); + otp_file.seekg(0, std::ifstream::beg); + // verify if OTP is ok + if (length != 1024) // todo - should also check some fixed values to verify integrity of otp dump + { + forceLog_printf("IOSU_CRYPTO: otp.bin has wrong size (must be 1024 bytes)"); + hasOtpMem = false; + } + else + { + otp_file.read((char*)otpMem, 1024); + hasOtpMem = (bool)otp_file; + } + } + else + { + forceLog_printf("IOSU_CRYPTO: No otp.bin found. Online mode cannot be used"); + hasOtpMem = false; + } + + if (std::ifstream seeprom_file(ActiveSettings::GetPath("seeprom.bin"), std::ifstream::in | std::ios::binary); seeprom_file.is_open()) + { + seeprom_file.seekg(0, std::ifstream::end); + const auto length = seeprom_file.tellg(); + seeprom_file.seekg(0, std::ifstream::beg); + // verify if seeprom is ok + if (length != 512) // todo - maybe check some known values to verify integrity of seeprom + { + cemuLog_log(LogType::Force, "IOSU_CRYPTO: seeprom.bin has wrong size (must be 512 bytes)"); + hasSeepromMem = false; + } + else + { + seeprom_file.read((char*)seepromMem, 512); + hasSeepromMem = (bool)seeprom_file; + } + } + else + { + cemuLog_log(LogType::Force, "IOSU_CRYPTO: No Seeprom.bin found. Online mode cannot be used"); + hasSeepromMem = false; + } + + // generate device certificate + iosuCrypto_generateDeviceCertificate(); + // load SSL certificates + iosuCrypto_loadSSLCertificates(); +} + +bool iosuCrypto_checkRequirementMLCFile(std::string_view mlcSubpath, std::wstring& additionalErrorInfo_filePath) +{ + const auto path = ActiveSettings::GetMlcPath(mlcSubpath); + additionalErrorInfo_filePath = path.generic_wstring(); + sint32 fileDataSize = 0; + auto fileData = FileStream::LoadIntoMemory(path); + if (!fileData) + return false; + return true; +} + +sint32 iosuCrypt_checkRequirementsForOnlineMode(std::wstring& additionalErrorInfo) +{ + std::error_code ec; + // check if otp.bin is present + const auto otp_file = ActiveSettings::GetPath("otp.bin"); + if(!fs::exists(otp_file, ec)) + return IOS_CRYPTO_ONLINE_REQ_OTP_MISSING; + if(fs::file_size(otp_file, ec) != 1024) + return IOS_CRYPTO_ONLINE_REQ_OTP_CORRUPTED; + // check if seeprom.bin is present + const auto seeprom_file = ActiveSettings::GetPath("seeprom.bin"); + if (!fs::exists(seeprom_file, ec)) + return IOS_CRYPTO_ONLINE_REQ_SEEPROM_MISSING; + if (fs::file_size(seeprom_file, ec) != 512) + return IOS_CRYPTO_ONLINE_REQ_SEEPROM_CORRUPTED; + + for (const auto& c : g_certificates) + { + std::string subPath = fmt::format("sys/title/0005001b/10054000/content/{}", boost::nowide::narrow(c.name)); + if (iosuCrypto_checkRequirementMLCFile(subPath, additionalErrorInfo) == false) + { + cemuLog_log(LogType::Force, "Missing dumped file for online mode: {}", subPath); + return IOS_CRYPTO_ONLINE_REQ_MISSING_FILE; + } + + if (*c.key) + { + std::string subPath = fmt::format("sys/title/0005001b/10054000/content/{}", boost::nowide::narrow(c.key)); + if (iosuCrypto_checkRequirementMLCFile(subPath, additionalErrorInfo) == false) + { + cemuLog_log(LogType::Force, "Missing dumped file for online mode: {}", subPath); + return IOS_CRYPTO_ONLINE_REQ_MISSING_FILE; + } + } + } + return IOS_CRYPTO_ONLINE_REQ_OK; +} + +std::vector<const wchar_t*> iosuCrypt_getCertificateKeys() +{ + std::vector<const wchar_t*> result; + result.reserve(std::size(g_certificates)); + for (const auto& c : g_certificates) + { + if (c.key[0] == '\0') + continue; + + result.emplace_back(c.key); + } + return result; +} + +std::vector<const wchar_t*> iosuCrypt_getCertificateNames() +{ + std::vector<const wchar_t*> result; + result.reserve(std::size(g_certificates)); + for (const auto& c : g_certificates) + { + result.emplace_back(c.name); + } + return result; +} diff --git a/src/Cafe/IOSU/legacy/iosu_crypto.h b/src/Cafe/IOSU/legacy/iosu_crypto.h new file mode 100644 index 00000000..bf3d7e2f --- /dev/null +++ b/src/Cafe/IOSU/legacy/iosu_crypto.h @@ -0,0 +1,81 @@ +#pragma once + +void iosuCrypto_init(); + +bool iosuCrypto_hasAllDataForLogin(); +bool iosuCrypto_getDeviceId(uint32* deviceId); +void iosuCrypto_getDeviceSerialString(char* serialString); + +// certificate API + +struct ECCPrivKey +{ + uint8 keyData[30]; +}; + +struct ECCPubKey +{ + uint8 keyData[60]; +}; + +struct ECCSig +{ + uint8 keyData[60]; // check size? +}; + +struct CHash256 +{ + uint8 b[32]; +}; + +struct CertECC_t +{ + enum class SIGTYPE : uint32 + { + ECC_SHA256 = 0x00010005 + }; + + /* +0x000 */ betype<SIGTYPE> sigType; // 01 00 02 00 + /* +0x004 */ uint8 signature[0x3C]; // from OTP 0xA3*4 + /* +0x040 */ uint8 ukn040[0x40]; // seems to be just padding + /* +0x080 */ char certificateSubject[0x40]; // "Root - CA%08x - MS%08x" + /* +0x0C0 */ char ukn0C0[0x4]; // ??? 00 00 00 02 ? + /* +0x0C4 */ char ngName[0x40]; // "NG%08X" + /* +0x104 */ uint32 ngKeyId; // big endian? (from OTP 0xA2*4) + /* +0x108 */ uint8 publicKey[0x3C]; + /* +0x144 */ uint8 padding[0x180 - 0x144]; + + +}; + +static_assert(sizeof(CertECC_t) == 0x180); + +sint32 iosuCrypto_getDeviceCertificateBase64Encoded(char* output); +bool iosuCrypto_verifyCert(CertECC_t& cert); + +std::string iosuCrypto_base64Encode(unsigned char const* bytes_to_encode, unsigned int inputLen); + +// certificate store +bool iosuCrypto_addClientCertificate(void* sslctx, sint32 certificateId); +bool iosuCrypto_addCACertificate(void* sslctx, sint32 certificateId); +bool iosuCrypto_addCustomCACertificate(void* sslctx, uint8* certData, sint32 certLength); + +uint8* iosuCrypto_getCertificateDataById(sint32 certificateId, sint32* certificateSize); +uint8* iosuCrypto_getCertificatePrivateKeyById(sint32 certificateId, sint32* certificateSize); + +// helper for online functionality +enum +{ + IOS_CRYPTO_ONLINE_REQ_OK, + IOS_CRYPTO_ONLINE_REQ_OTP_MISSING, + IOS_CRYPTO_ONLINE_REQ_OTP_CORRUPTED, + IOS_CRYPTO_ONLINE_REQ_SEEPROM_MISSING, + IOS_CRYPTO_ONLINE_REQ_SEEPROM_CORRUPTED, + IOS_CRYPTO_ONLINE_REQ_MISSING_FILE +}; + +sint32 iosuCrypt_checkRequirementsForOnlineMode(std::wstring& additionalErrorInfo); +void iosuCrypto_readOtpData(void* output, sint32 wordIndex, sint32 size); + +std::vector<const wchar_t*> iosuCrypt_getCertificateKeys(); +std::vector<const wchar_t*> iosuCrypt_getCertificateNames(); \ No newline at end of file diff --git a/src/Cafe/IOSU/legacy/iosu_fpd.cpp b/src/Cafe/IOSU/legacy/iosu_fpd.cpp new file mode 100644 index 00000000..678c187f --- /dev/null +++ b/src/Cafe/IOSU/legacy/iosu_fpd.cpp @@ -0,0 +1,1050 @@ +#include "iosu_ioctl.h" +#include "iosu_act.h" +#include "iosu_fpd.h" +#include "Cemu/nex/nex.h" +#include "Cemu/nex/nexFriends.h" +#include "util/helpers/helpers.h" +#include "config/CemuConfig.h" +#include "Cafe/CafeSystem.h" +#include "config/ActiveSettings.h" +#include "Cemu/napi/napi.h" +#include "util/helpers/StringHelpers.h" +#include "Cafe/OS/libs/coreinit/coreinit.h" + +uint32 memory_getVirtualOffsetFromPointer(void* ptr); // remove once we updated everything to MEMPTR + +SysAllocator<uint32be> _fpdAsyncLoginRetCode; +SysAllocator<uint32be> _fpdAsyncAddFriendRetCode; + +std::mutex g_friend_notification_mutex; +std::vector< std::pair<std::string, int> > g_friend_notifications; + +namespace iosu +{ + namespace fpd + { + + struct + { + bool isThreadStarted; + bool isInitialized2; + NexFriends* nexFriendSession; + // notification handler + MPTR notificationFunc; + MPTR notificationCustomParam; + uint32 notificationMask; + // login callback + struct + { + MPTR func; + MPTR customParam; + }asyncLoginCallback; + // current state + nexPresenceV2 myPresence; + }g_fpd = {}; + + void notificationHandler(NexFriends::NOTIFICATION_TYPE type, uint32 pid) + { + forceLogDebug_printf("Friends::Notification %02x pid %08x", type, pid); + if(GetConfig().notification.friends) + { + std::unique_lock lock(g_friend_notification_mutex); + std::string message; + if(type == NexFriends::NOTIFICATION_TYPE::NOTIFICATION_TYPE_ONLINE) + { + g_friend_notifications.emplace_back("Connected to friend service", 5000); + if(g_fpd.nexFriendSession && g_fpd.nexFriendSession->getPendingFriendRequestCount() > 0) + g_friend_notifications.emplace_back(fmt::format("You have {} pending friend request(s)", g_fpd.nexFriendSession->getPendingFriendRequestCount()), 5000); + } + else + { + std::string msg_format; + switch(type) + { + case NexFriends::NOTIFICATION_TYPE_ONLINE: break; + case NexFriends::NOTIFICATION_TYPE_FRIEND_LOGIN: msg_format = "{} is now online"; break; + case NexFriends::NOTIFICATION_TYPE_FRIEND_LOGOFF: msg_format = "{} is now offline"; break; + case NexFriends::NOTIFICATION_TYPE_FRIEND_PRESENCE_CHANGE: break; + case NexFriends::NOTIFICATION_TYPE_ADDED_FRIEND: msg_format = "{} has been added to your friend list"; break; + case NexFriends::NOTIFICATION_TYPE_REMOVED_FRIEND: msg_format = "{} has been removed from your friend list"; break; + case NexFriends::NOTIFICATION_TYPE_ADDED_OUTGOING_REQUEST: break; + case NexFriends::NOTIFICATION_TYPE_REMOVED_OUTGOING_REQUEST: break; + case NexFriends::NOTIFICATION_TYPE_ADDED_INCOMING_REQUEST: msg_format = "{} wants to add you to his friend list"; break; + case NexFriends::NOTIFICATION_TYPE_REMOVED_INCOMING_REQUEST: break; + default: ; + } + + if (!msg_format.empty()) + { + std::string name = fmt::format("{:#x}", pid); + if (g_fpd.nexFriendSession) + { + const std::string tmp = g_fpd.nexFriendSession->getAccountNameByPid(pid); + if (!tmp.empty()) + name = tmp; + } + + g_friend_notifications.emplace_back(fmt::format(msg_format, name), 5000); + } + } + } + + if (g_fpd.notificationFunc == MPTR_NULL) + return; + uint32 notificationFlag = 1 << (type - 1); + if ( (notificationFlag&g_fpd.notificationMask) == 0 ) + return; + coreinitAsyncCallback_add(g_fpd.notificationFunc, 3, type, pid, g_fpd.notificationCustomParam); + } + + void convertMultiByteStringToBigEndianWidechar(const char* input, uint16be* output, sint32 maxOutputLength) + { + std::basic_string<uint16be> beStr = StringHelpers::FromUtf8(input); + if (beStr.size() >= maxOutputLength - 1) + beStr.resize(maxOutputLength-1); + for (size_t i = 0; i < beStr.size(); i++) + output[i] = beStr[i]; + output[beStr.size()] = '\0'; + } + + void convertFPDTimestampToDate(uint64 timestamp, fpdDate_t* fpdDate) + { + // if the timestamp is zero then still return a valid date + if (timestamp == 0) + { + fpdDate->second = 0; + fpdDate->minute = 0; + fpdDate->hour = 0; + fpdDate->day = 1; + fpdDate->month = 1; + fpdDate->year = 1970; + return; + } + fpdDate->second = (uint8)((timestamp) & 0x3F); + fpdDate->minute = (uint8)((timestamp >> 6) & 0x3F); + fpdDate->hour = (uint8)((timestamp >> 12) & 0x1F); + fpdDate->day = (uint8)((timestamp >> 17) & 0x1F); + fpdDate->month = (uint8)((timestamp >> 22) & 0xF); + fpdDate->year = (uint16)((timestamp >> 26)); + } + + uint64 convertDateToFPDTimestamp(fpdDate_t* fpdDate) + { + uint64 t = 0; + t |= (uint64)fpdDate->second; + t |= ((uint64)fpdDate->minute<<6); + t |= ((uint64)fpdDate->hour<<12); + t |= ((uint64)fpdDate->day<<17); + t |= ((uint64)fpdDate->month<<22); + t |= ((uint64)(uint16)fpdDate->year<<26); + return t; + } + + void startFriendSession() + { + cemu_assert(!g_fpd.nexFriendSession); + + NAPI::AuthInfo authInfo; + NAPI::NAPI_MakeAuthInfoFromCurrentAccount(authInfo); + NAPI::ACTGetNexTokenResult nexTokenResult = NAPI::ACT_GetNexToken_WithCache(authInfo, 0x0005001010001C00, 0x0000, 0x00003200); + if (nexTokenResult.isValid()) + { + // get values needed for friend session + uint32 myPid; + uint8 currentSlot = iosu::act::getCurrentAccountSlot(); + iosu::act::getPrincipalId(currentSlot, &myPid); + char accountId[256] = { 0 }; + iosu::act::getAccountId(currentSlot, accountId); + FFLData_t miiData; + act::getMii(currentSlot, &miiData); + uint16 screenName[ACT_NICKNAME_LENGTH + 1] = { 0 }; + act::getScreenname(currentSlot, screenName); + uint32 countryCode = 0; + act::getCountryIndex(currentSlot, &countryCode); + // init presence + g_fpd.myPresence.isOnline = 1; + g_fpd.myPresence.gameKey.titleId = CafeSystem::GetForegroundTitleId(); + g_fpd.myPresence.gameKey.ukn = CafeSystem::GetForegroundTitleVersion(); + // start session + uint32 hostIp; + inet_pton(AF_INET, nexTokenResult.nexToken.host, &hostIp); + g_fpd.nexFriendSession = new NexFriends(hostIp, nexTokenResult.nexToken.port, "ridfebb9", myPid, nexTokenResult.nexToken.nexPassword, nexTokenResult.nexToken.token, accountId, (uint8*)&miiData, (wchar_t*)screenName, (uint8)countryCode, g_fpd.myPresence); + g_fpd.nexFriendSession->setNotificationHandler(notificationHandler); + forceLog_printf("IOSU_FPD: Created friend server session"); + } + else + { + forceLogDebug_printf("IOSU_FPD: Failed to acquire nex token for friend server"); + } + } + + void handleRequest_GetFriendList(iosuFpdCemuRequest_t* fpdCemuRequest, bool getAll) + { + if (g_fpd.nexFriendSession == nullptr || g_fpd.nexFriendSession->isOnline() == false) + { + fpdCemuRequest->returnCode = 0; + fpdCemuRequest->resultU32.u32 = 0; // zero entries returned + return; + } + + uint32 temporaryPidList[800]; + uint32 pidCount = 0; + g_fpd.nexFriendSession->getFriendPIDs(temporaryPidList, &pidCount, fpdCemuRequest->getFriendList.startIndex, std::min<uint32>(sizeof(temporaryPidList) / sizeof(temporaryPidList[0]), fpdCemuRequest->getFriendList.maxCount), getAll); + uint32be* pidListOutput = fpdCemuRequest->getFriendList.pidList.GetPtr(); + if (pidListOutput) + { + for (uint32 i = 0; i < pidCount; i++) + pidListOutput[i] = temporaryPidList[i]; + } + fpdCemuRequest->returnCode = 0; + fpdCemuRequest->resultU32.u32 = pidCount; + } + + void handleRequest_GetFriendRequestList(iosuFpdCemuRequest_t* fpdCemuRequest) + { + if (g_fpd.nexFriendSession == nullptr || g_fpd.nexFriendSession->isOnline() == false) + { + fpdCemuRequest->returnCode = 0; + fpdCemuRequest->resultU32.u32 = 0; // zero entries returned + return; + } + + uint32 temporaryPidList[800]; + uint32 pidCount = 0; + // get only incoming friend requests + g_fpd.nexFriendSession->getFriendRequestPIDs(temporaryPidList, &pidCount, fpdCemuRequest->getFriendList.startIndex, std::min<uint32>(sizeof(temporaryPidList) / sizeof(temporaryPidList[0]), fpdCemuRequest->getFriendList.maxCount), true, false); + uint32be* pidListOutput = fpdCemuRequest->getFriendList.pidList.GetPtr(); + if (pidListOutput) + { + for (uint32 i = 0; i < pidCount; i++) + pidListOutput[i] = temporaryPidList[i]; + } + fpdCemuRequest->returnCode = 0; + fpdCemuRequest->resultU32.u32 = pidCount; + } + + void setFriendDataFromNexFriend(friendData_t* friendData, nexFriend* frd) + { + memset(friendData, 0, sizeof(friendData_t)); + // setup friend data + friendData->type = 1; // friend + friendData->pid = frd->nnaInfo.principalInfo.principalId; + memcpy(friendData->mii, frd->nnaInfo.principalInfo.mii.miiData, FFL_SIZE); + strcpy((char*)friendData->nnid, frd->nnaInfo.principalInfo.nnid); + // screenname + convertMultiByteStringToBigEndianWidechar(frd->nnaInfo.principalInfo.mii.miiNickname, friendData->screenname, sizeof(friendData->screenname) / sizeof(uint16be)); + + //friendData->friendExtraData.ukn0E4 = 0; + friendData->friendExtraData.isOnline = frd->presence.isOnline != 0 ? 1 : 0; + + friendData->friendExtraData.gameKeyTitleId = _swapEndianU64(frd->presence.gameKey.titleId); + friendData->friendExtraData.gameKeyUkn = frd->presence.gameKey.ukn; + + friendData->friendExtraData.statusMessage[0] = '\0'; + + // set valid dates + friendData->uknDate.year = 2018; + friendData->uknDate.day = 1; + friendData->uknDate.month = 1; + friendData->uknDate.hour = 1; + friendData->uknDate.minute = 1; + friendData->uknDate.second = 1; + + friendData->friendExtraData.uknDate218.year = 2018; + friendData->friendExtraData.uknDate218.day = 1; + friendData->friendExtraData.uknDate218.month = 1; + friendData->friendExtraData.uknDate218.hour = 1; + friendData->friendExtraData.uknDate218.minute = 1; + friendData->friendExtraData.uknDate218.second = 1; + + convertFPDTimestampToDate(frd->lastOnlineTimestamp, &friendData->friendExtraData.lastOnline); + } + + void setFriendDataFromNexFriendRequest(friendData_t* friendData, nexFriendRequest* frdReq, bool isIncoming) + { + memset(friendData, 0, sizeof(friendData_t)); + // setup friend data + friendData->type = 0; // friend request + friendData->pid = frdReq->principalInfo.principalId; + memcpy(friendData->mii, frdReq->principalInfo.mii.miiData, FFL_SIZE); + strcpy((char*)friendData->nnid, frdReq->principalInfo.nnid); + // screenname + convertMultiByteStringToBigEndianWidechar(frdReq->principalInfo.mii.miiNickname, friendData->screenname, sizeof(friendData->screenname) / sizeof(uint16be)); + + convertMultiByteStringToBigEndianWidechar(frdReq->message.commentStr.c_str(), friendData->requestExtraData.comment, sizeof(friendData->requestExtraData.comment) / sizeof(uint16be)); + + fpdDate_t expireDate; + convertFPDTimestampToDate(frdReq->message.expireTimestamp, &expireDate); + + bool isProvisional = frdReq->message.expireTimestamp == 0; + + //friendData->requestExtraData.ukn0A8 = 0; // no change? + //friendData->requestExtraData.ukn0A0 = 0; // if not set -> provisional friend request + //friendData->requestExtraData.ukn0A4 = isProvisional ? 0 : 123; // no change? + + friendData->requestExtraData.messageId = _swapEndianU64(frdReq->message.messageId); + + + //find the value for 'markedAsReceived' + + ///* +0x0A8 */ uint8 ukn0A8; + ///* +0x0A9 */ uint8 ukn0A9; // comment language? (guessed) + ///* +0x0AA */ uint16be comment[0x40]; + ///* +0x12A */ uint8 ukn12A; // ingame name language? (guessed) + ///* +0x12B */ uint8 _padding12B; + + // set valid dates + + friendData->uknDate.year = 2018; + friendData->uknDate.day = 20; + friendData->uknDate.month = 4; + friendData->uknDate.hour = 12; + friendData->uknDate.minute = 1; + friendData->uknDate.second = 1; + + friendData->requestExtraData.uknData0.year = 2018; + friendData->requestExtraData.uknData0.day = 24; + friendData->requestExtraData.uknData0.month = 4; + friendData->requestExtraData.uknData0.hour = 1; + friendData->requestExtraData.uknData0.minute = 1; + friendData->requestExtraData.uknData0.second = 1; + + // this is the date used for 'Expires in' + convertFPDTimestampToDate(frdReq->message.expireTimestamp, &friendData->requestExtraData.uknData1); + } + + void setFriendRequestFromNexFriendRequest(friendRequest_t* friendRequest, nexFriendRequest* frdReq, bool isIncoming) + { + memset(friendRequest, 0, sizeof(friendRequest_t)); + + friendRequest->pid = frdReq->principalInfo.principalId; + + strncpy((char*)friendRequest->nnid, frdReq->principalInfo.nnid, sizeof(friendRequest->nnid)); + friendRequest->nnid[sizeof(friendRequest->nnid) - 1] = '\0'; + + memcpy(friendRequest->miiData, frdReq->principalInfo.mii.miiData, sizeof(friendRequest->miiData)); + + convertMultiByteStringToBigEndianWidechar(frdReq->message.commentStr.c_str(), friendRequest->message, sizeof(friendRequest->message) / sizeof(friendRequest->message[0])); + convertMultiByteStringToBigEndianWidechar(frdReq->principalInfo.mii.miiNickname, friendRequest->screenname, sizeof(friendRequest->screenname) / sizeof(friendRequest->screenname[0])); + + friendRequest->isMarkedAsReceived = 1; + + friendRequest->ukn98 = _swapEndianU64(frdReq->message.messageId); + + convertFPDTimestampToDate(0, &friendRequest->uknDate); + convertFPDTimestampToDate(0, &friendRequest->uknDate2); + convertFPDTimestampToDate(frdReq->message.expireTimestamp, &friendRequest->expireDate); + } + + void handleRequest_GetFriendListEx(iosuFpdCemuRequest_t* fpdCemuRequest) + { + fpdCemuRequest->returnCode = 0; + if (g_fpd.nexFriendSession == nullptr || g_fpd.nexFriendSession->isOnline() == false) + { + fpdCemuRequest->returnCode = 0x80000000; + return; + } + for (uint32 i = 0; i < fpdCemuRequest->getFriendListEx.count; i++) + { + uint32 pid = fpdCemuRequest->getFriendListEx.pidList[i]; + friendData_t* friendData = fpdCemuRequest->getFriendListEx.friendData.GetPtr() + i; + nexFriend frd; + nexFriendRequest frdReq; + if (g_fpd.nexFriendSession->getFriendByPID(frd, pid)) + { + setFriendDataFromNexFriend(friendData, &frd); + continue; + } + bool incoming = false; + if (g_fpd.nexFriendSession->getFriendRequestByPID(frdReq, &incoming, pid)) + { + setFriendDataFromNexFriendRequest(friendData, &frdReq, incoming); + continue; + } + fpdCemuRequest->returnCode = 0x80000000; + return; + } + } + + void handleRequest_GetFriendRequestListEx(iosuFpdCemuRequest_t* fpdCemuRequest) + { + fpdCemuRequest->returnCode = 0; + if (g_fpd.nexFriendSession == nullptr || g_fpd.nexFriendSession->isOnline() == false) + { + fpdCemuRequest->returnCode = 0x80000000; + return; + } + for (uint32 i = 0; i < fpdCemuRequest->getFriendRequestListEx.count; i++) + { + uint32 pid = fpdCemuRequest->getFriendListEx.pidList[i]; + friendRequest_t* friendRequest = fpdCemuRequest->getFriendRequestListEx.friendRequest.GetPtr() + i; + nexFriendRequest frdReq; + bool incoming = false; + if (!g_fpd.nexFriendSession->getFriendRequestByPID(frdReq, &incoming, pid)) + { + cemuLog_log(LogType::Force, "Failed to get friend request"); + fpdCemuRequest->returnCode = 0x80000000; + return; + } + setFriendRequestFromNexFriendRequest(friendRequest, &frdReq, incoming); + } + } + + typedef struct + { + MPTR funcMPTR; + MPTR customParam; + }fpAsyncCallback_t; + + typedef struct + { + nexPrincipalBasicInfo* principalBasicInfo; + uint32* pidList; + sint32 count; + friendBasicInfo_t* friendBasicInfo; + fpAsyncCallback_t fpCallback; + }getBasicInfoAsyncParams_t; + + SysAllocator<uint32, 32> _fpCallbackResultArray; // use a ring buffer of results to avoid overwriting the result when multiple callbacks are queued at the same time + sint32 fpCallbackResultIndex = 0; + + void handleFPCallback(fpAsyncCallback_t* fpCallback, uint32 resultCode) + { + fpCallbackResultIndex = (fpCallbackResultIndex + 1) % 32; + uint32* resultPtr = _fpCallbackResultArray.GetPtr() + fpCallbackResultIndex; + + *resultPtr = resultCode; + + coreinitAsyncCallback_add(fpCallback->funcMPTR, 2, memory_getVirtualOffsetFromPointer(resultPtr), fpCallback->customParam); + } + + void handleFPCallback2(MPTR funcMPTR, MPTR custom, uint32 resultCode) + { + fpCallbackResultIndex = (fpCallbackResultIndex + 1) % 32; + uint32* resultPtr = _fpCallbackResultArray.GetPtr() + fpCallbackResultIndex; + + *resultPtr = resultCode; + + coreinitAsyncCallback_add(funcMPTR, 2, memory_getVirtualOffsetFromPointer(resultPtr), custom); + } + + void handleResultCB_GetBasicInfoAsync(NexFriends* nexFriends, uint32 result, void* custom) + { + getBasicInfoAsyncParams_t* cbInfo = (getBasicInfoAsyncParams_t*)custom; + if (result != 0) + { + handleFPCallback(&cbInfo->fpCallback, 0x80000000); // todo - properly translate internal error to nn::fp error code + free(cbInfo->principalBasicInfo); + free(cbInfo->pidList); + free(cbInfo); + return; + } + + // convert PrincipalBasicInfo into friendBasicInfo + for (sint32 i = 0; i < cbInfo->count; i++) + { + friendBasicInfo_t* basicInfo = cbInfo->friendBasicInfo + i; + nexPrincipalBasicInfo* principalBasicInfo = cbInfo->principalBasicInfo + i; + + memset(basicInfo, 0, sizeof(friendBasicInfo_t)); + basicInfo->pid = principalBasicInfo->principalId; + strcpy(basicInfo->nnid, principalBasicInfo->nnid); + + convertMultiByteStringToBigEndianWidechar(principalBasicInfo->mii.miiNickname, basicInfo->screenname, sizeof(basicInfo->screenname) / sizeof(uint16be)); + memcpy(basicInfo->miiData, principalBasicInfo->mii.miiData, FFL_SIZE); + + basicInfo->uknDate90.day = 1; + basicInfo->uknDate90.month = 1; + basicInfo->uknDate90.hour = 1; + basicInfo->uknDate90.minute = 1; + basicInfo->uknDate90.second = 1; + + // unknown values not set: + // ukn15 + // ukn2E + // ukn2F + } + // success + handleFPCallback(&cbInfo->fpCallback, 0x00000000); + + free(cbInfo->principalBasicInfo); + free(cbInfo->pidList); + free(cbInfo); + } + + void handleRequest_GetBasicInfoAsync(iosuFpdCemuRequest_t* fpdCemuRequest) + { + fpdCemuRequest->returnCode = 0; + if (g_fpd.nexFriendSession == nullptr || g_fpd.nexFriendSession->isOnline() == false) + { + fpdCemuRequest->returnCode = 0x80000000; + return; + } + sint32 count = fpdCemuRequest->getBasicInfo.count; + + nexPrincipalBasicInfo* principalBasicInfo = new nexPrincipalBasicInfo[count]; + uint32* pidList = (uint32*)malloc(sizeof(uint32)*count); + for (sint32 i = 0; i < count; i++) + pidList[i] = fpdCemuRequest->getBasicInfo.pidList[i]; + getBasicInfoAsyncParams_t* cbInfo = (getBasicInfoAsyncParams_t*)malloc(sizeof(getBasicInfoAsyncParams_t)); + cbInfo->principalBasicInfo = principalBasicInfo; + cbInfo->pidList = pidList; + cbInfo->count = count; + cbInfo->friendBasicInfo = fpdCemuRequest->getBasicInfo.basicInfo.GetPtr(); + cbInfo->fpCallback.funcMPTR = fpdCemuRequest->getBasicInfo.funcPtr; + cbInfo->fpCallback.customParam = fpdCemuRequest->getBasicInfo.custom; + g_fpd.nexFriendSession->requestPrincipleBaseInfoByPID(principalBasicInfo, pidList, count, handleResultCB_GetBasicInfoAsync, cbInfo); + } + + void handleResponse_addOrRemoveFriend(uint32 errorCode, MPTR funcMPTR, MPTR custom) + { + if (errorCode == 0) + { + handleFPCallback2(funcMPTR, custom, 0); + g_fpd.nexFriendSession->requestGetAllInformation(); // refresh list + } + else + handleFPCallback2(funcMPTR, custom, 0x80000000); + } + + void handleRequest_RemoveFriendAsync(iosuFpdCemuRequest_t* fpdCemuRequest) + { + fpdCemuRequest->returnCode = 0; + if (g_fpd.nexFriendSession == nullptr || g_fpd.nexFriendSession->isOnline() == false) + { + fpdCemuRequest->returnCode = 0x80000000; + return; + } + g_fpd.nexFriendSession->removeFriend(fpdCemuRequest->addOrRemoveFriend.pid, std::bind(handleResponse_addOrRemoveFriend, std::placeholders::_1, fpdCemuRequest->addOrRemoveFriend.funcPtr, fpdCemuRequest->addOrRemoveFriend.custom)); + } + + void handleResponse_MarkFriendRequestAsReceivedAsync(uint32 errorCode, MPTR funcMPTR, MPTR custom) + { + if (errorCode == 0) + handleFPCallback2(funcMPTR, custom, 0); + else + handleFPCallback2(funcMPTR, custom, 0x80000000); + } + + void handleRequest_MarkFriendRequestAsReceivedAsync(iosuFpdCemuRequest_t* fpdCemuRequest) + { + fpdCemuRequest->returnCode = 0; + if (g_fpd.nexFriendSession == nullptr || g_fpd.nexFriendSession->isOnline() == false) + { + fpdCemuRequest->returnCode = 0x80000000; + return; + } + // convert messageId list to little endian + uint64 messageIds[100]; + sint32 count = 0; + for (uint32 i = 0; i < fpdCemuRequest->markFriendRequest.count; i++) + { + uint64 mid = _swapEndianU64(fpdCemuRequest->markFriendRequest.messageIdList.GetPtr()[i]); + if (mid == 0) + { + forceLogDebug_printf("MarkFriendRequestAsReceivedAsync - Invalid messageId"); + continue; + } + messageIds[count] = mid; + count++; + if (count >= sizeof(messageIds)/sizeof(messageIds[0])) + break; + } + // skipped for now + g_fpd.nexFriendSession->markFriendRequestsAsReceived(messageIds, count, std::bind(handleResponse_MarkFriendRequestAsReceivedAsync, std::placeholders::_1, fpdCemuRequest->markFriendRequest.funcPtr, fpdCemuRequest->markFriendRequest.custom)); + } + + void handleResponse_cancelMyFriendRequest(uint32 errorCode, uint32 pid, MPTR funcMPTR, MPTR custom) + { + if (errorCode == 0) + { + handleFPCallback2(funcMPTR, custom, 0); + } + else + handleFPCallback2(funcMPTR, custom, 0x80000000); + } + + void handleRequest_CancelFriendRequestAsync(iosuFpdCemuRequest_t* fpdCemuRequest) + { + fpdCemuRequest->returnCode = 0; + if (g_fpd.nexFriendSession == nullptr || g_fpd.nexFriendSession->isOnline() == false) + { + fpdCemuRequest->returnCode = 0x80000000; + return; + } + // find friend request with matching pid + nexFriendRequest frq; + bool isIncoming; + if (g_fpd.nexFriendSession->getFriendRequestByMessageId(frq, &isIncoming, fpdCemuRequest->cancelOrAcceptFriendRequest.messageId)) + { + g_fpd.nexFriendSession->removeFriend(frq.principalInfo.principalId, std::bind(handleResponse_cancelMyFriendRequest, std::placeholders::_1, frq.principalInfo.principalId, fpdCemuRequest->cancelOrAcceptFriendRequest.funcPtr, fpdCemuRequest->cancelOrAcceptFriendRequest.custom)); + } + else + { + fpdCemuRequest->returnCode = 0x80000000; + return; + } + } + + void handleResponse_acceptFriendRequest(uint32 errorCode, uint32 pid, MPTR funcMPTR, MPTR custom) + { + if (errorCode == 0) + { + handleFPCallback2(funcMPTR, custom, 0); + g_fpd.nexFriendSession->requestGetAllInformation(); // refresh list + } + else + handleFPCallback2(funcMPTR, custom, 0x80000000); + } + + void handleRequest_AcceptFriendRequestAsync(iosuFpdCemuRequest_t* fpdCemuRequest) + { + fpdCemuRequest->returnCode = 0; + if (g_fpd.nexFriendSession == nullptr || g_fpd.nexFriendSession->isOnline() == false) + { + fpdCemuRequest->returnCode = 0x80000000; + return; + } + // find friend request with matching pid + nexFriendRequest frq; + bool isIncoming; + if (g_fpd.nexFriendSession->getFriendRequestByMessageId(frq, &isIncoming, fpdCemuRequest->cancelOrAcceptFriendRequest.messageId)) + { + g_fpd.nexFriendSession->acceptFriendRequest(fpdCemuRequest->cancelOrAcceptFriendRequest.messageId, std::bind(handleResponse_acceptFriendRequest, std::placeholders::_1, frq.principalInfo.principalId, fpdCemuRequest->cancelOrAcceptFriendRequest.funcPtr, fpdCemuRequest->cancelOrAcceptFriendRequest.custom)); + } + else + { + fpdCemuRequest->returnCode = 0x80000000; + return; + } + } + + void handleResponse_addFriendRequest(uint32 errorCode, MPTR funcMPTR, MPTR custom) + { + if (errorCode == 0) + { + handleFPCallback2(funcMPTR, custom, 0); + g_fpd.nexFriendSession->requestGetAllInformation(); // refresh list + } + else + handleFPCallback2(funcMPTR, custom, 0x80000000); + } + + void handleRequest_AddFriendRequest(iosuFpdCemuRequest_t* fpdCemuRequest) + { + fpdCemuRequest->returnCode = 0; + if (g_fpd.nexFriendSession == nullptr || g_fpd.nexFriendSession->isOnline() == false) + { + fpdCemuRequest->returnCode = 0x80000000; + return; + } + + uint16be* input = fpdCemuRequest->addFriendRequest.message.GetPtr(); + size_t inputLen = 0; + while (input[inputLen] != 0) + inputLen++; + std::string msg = StringHelpers::ToUtf8({ input, inputLen }); + + g_fpd.nexFriendSession->addFriendRequest(fpdCemuRequest->addFriendRequest.pid, msg.data(), std::bind(handleResponse_addFriendRequest, std::placeholders::_1, fpdCemuRequest->addFriendRequest.funcPtr, fpdCemuRequest->addFriendRequest.custom)); + } + + // called once a second to handle state checking and updates of the friends service + void iosuFpd_updateFriendsService() + { + if (g_fpd.nexFriendSession) + { + g_fpd.nexFriendSession->update(); + + if (g_fpd.asyncLoginCallback.func != MPTR_NULL) + { + if (g_fpd.nexFriendSession->isOnline()) + { + *_fpdAsyncLoginRetCode.GetPtr() = 0x00000000; + coreinitAsyncCallback_add(g_fpd.asyncLoginCallback.func, 2, _fpdAsyncLoginRetCode.GetMPTR(), g_fpd.asyncLoginCallback.customParam); + g_fpd.asyncLoginCallback.func = MPTR_NULL; + g_fpd.asyncLoginCallback.customParam = MPTR_NULL; + } + } + } + } + + void iosuFpd_thread() + { + SetThreadName("iosuFpd_thread"); + while (true) + { + uint32 returnValue = 0; // Ioctl return value + ioQueueEntry_t* ioQueueEntry = iosuIoctl_getNextWithTimeout(IOS_DEVICE_FPD, 1000); + if (ioQueueEntry == nullptr) + { + iosuFpd_updateFriendsService(); + continue; + } + if (ioQueueEntry->request == IOSU_FPD_REQUEST_CEMU) + { + iosuFpdCemuRequest_t* fpdCemuRequest = (iosuFpdCemuRequest_t*)ioQueueEntry->bufferVectors[0].buffer.GetPtr(); + if (fpdCemuRequest->requestCode == IOSU_FPD_INITIALIZE) + { + if (g_fpd.isInitialized2 == false) + { + if(ActiveSettings::IsOnlineEnabled()) + startFriendSession(); + + g_fpd.isInitialized2 = true; + } + } + else if (fpdCemuRequest->requestCode == IOSU_FPD_SET_NOTIFICATION_HANDLER) + { + g_fpd.notificationFunc = fpdCemuRequest->setNotificationHandler.funcPtr; + g_fpd.notificationCustomParam = fpdCemuRequest->setNotificationHandler.custom; + g_fpd.notificationMask = fpdCemuRequest->setNotificationHandler.notificationMask; + } + else if (fpdCemuRequest->requestCode == IOSU_FPD_LOGIN_ASYNC) + { + if (g_fpd.nexFriendSession) + { + g_fpd.asyncLoginCallback.func = fpdCemuRequest->loginAsync.funcPtr; + g_fpd.asyncLoginCallback.customParam = fpdCemuRequest->loginAsync.custom; + } + else + { + // offline mode + *_fpdAsyncLoginRetCode.GetPtr() = 0; // if we return 0x80000000 here then Splatoon softlocks during boot + coreinitAsyncCallback_add(fpdCemuRequest->loginAsync.funcPtr, 2, _fpdAsyncLoginRetCode.GetMPTR(), fpdCemuRequest->loginAsync.custom); + } + } + else if (fpdCemuRequest->requestCode == IOSU_FPD_IS_ONLINE) + { + fpdCemuRequest->resultU32.u32 = g_fpd.nexFriendSession ? g_fpd.nexFriendSession->isOnline() : 0; + } + else if (fpdCemuRequest->requestCode == IOSU_FPD_IS_PREFERENCE_VALID) + { + fpdCemuRequest->resultU32.u32 = 1; // todo (if this returns 0, the friend app will show the first-time-setup screen and ask the user to configure preferences) + } + else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_MY_PRINCIPAL_ID) + { + uint8 slot = iosu::act::getCurrentAccountSlot(); + iosu::act::getPrincipalId(slot, &fpdCemuRequest->resultU32.u32); + } + else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_MY_ACCOUNT_ID) + { + char* accountId = (char*)fpdCemuRequest->common.ptr.GetPtr(); + uint8 slot = iosu::act::getCurrentAccountSlot(); + if (iosu::act::getAccountId(slot, accountId) == false) + fpdCemuRequest->returnCode = 0x80000000; // todo - proper error code + else + fpdCemuRequest->returnCode = 0; + } + else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_MY_MII) + { + FFLData_t* fflData = (FFLData_t*)fpdCemuRequest->common.ptr.GetPtr(); + uint8 slot = iosu::act::getCurrentAccountSlot(); + if (iosu::act::getMii(slot, fflData) == false) + fpdCemuRequest->returnCode = 0x80000000; // todo - proper error code + else + fpdCemuRequest->returnCode = 0; + } + else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_MY_SCREENNAME) + { + uint16be* screennameOutput = (uint16be*)fpdCemuRequest->common.ptr.GetPtr(); + uint8 slot = iosu::act::getCurrentAccountSlot(); + uint16 screennameTemp[ACT_NICKNAME_LENGTH]; + if (iosu::act::getScreenname(slot, screennameTemp)) + { + for (sint32 i = 0; i < ACT_NICKNAME_LENGTH; i++) + { + screennameOutput[i] = screennameTemp[i]; + } + screennameOutput[ACT_NICKNAME_LENGTH] = '\0'; // length is ACT_NICKNAME_LENGTH+1 + fpdCemuRequest->returnCode = 0; + } + else + { + fpdCemuRequest->returnCode = 0x80000000; // todo - proper error code + screennameOutput[0] = '\0'; + } + } + else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_FRIEND_ACCOUNT_ID) + { + if (g_fpd.nexFriendSession == nullptr) + { + fpdCemuRequest->returnCode = 0x80000000; // todo - proper error code + iosuIoctl_completeRequest(ioQueueEntry, returnValue); + return; + } + fpdCemuRequest->returnCode = 0; + for (sint32 i = 0; i < fpdCemuRequest->getFriendAccountId.count; i++) + { + char* nnidOutput = fpdCemuRequest->getFriendAccountId.accountIds.GetPtr()+i*17; + uint32 pid = fpdCemuRequest->getFriendAccountId.pidList[i]; + if (g_fpd.nexFriendSession == nullptr) + { + nnidOutput[0] = '\0'; + continue; + } + nexFriend frd; + nexFriendRequest frdReq; + if (g_fpd.nexFriendSession->getFriendByPID(frd, pid)) + { + strcpy(nnidOutput, frd.nnaInfo.principalInfo.nnid); + continue; + } + bool incoming = false; + if (g_fpd.nexFriendSession->getFriendRequestByPID(frdReq, &incoming, pid)) + { + strcpy(nnidOutput, frdReq.principalInfo.nnid); + continue; + } + nnidOutput[0] = '\0'; + } + } + else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_FRIEND_SCREENNAME) + { + if (g_fpd.nexFriendSession == nullptr) + { + fpdCemuRequest->returnCode = 0x80000000; // todo - proper error code + iosuIoctl_completeRequest(ioQueueEntry, returnValue); + return; + } + fpdCemuRequest->returnCode = 0; + for (sint32 i = 0; i < fpdCemuRequest->getFriendScreenname.count; i++) + { + uint16be* screennameOutput = fpdCemuRequest->getFriendScreenname.nameList.GetPtr()+i*11; + uint32 pid = fpdCemuRequest->getFriendScreenname.pidList[i]; + if(fpdCemuRequest->getFriendScreenname.languageList.IsNull() == false) + fpdCemuRequest->getFriendScreenname.languageList.GetPtr()[i] = 0; + if (g_fpd.nexFriendSession == nullptr) + { + screennameOutput[0] = '\0'; + continue; + } + nexFriend frd; + nexFriendRequest frdReq; + if (g_fpd.nexFriendSession->getFriendByPID(frd, pid)) + { + convertMultiByteStringToBigEndianWidechar(frd.nnaInfo.principalInfo.mii.miiNickname, screennameOutput, 11); + if (fpdCemuRequest->getFriendScreenname.languageList.IsNull() == false) + fpdCemuRequest->getFriendScreenname.languageList.GetPtr()[i] = frd.nnaInfo.principalInfo.regionGuessed; + continue; + } + bool incoming = false; + if (g_fpd.nexFriendSession->getFriendRequestByPID(frdReq, &incoming, pid)) + { + convertMultiByteStringToBigEndianWidechar(frdReq.principalInfo.mii.miiNickname, screennameOutput, 11); + if (fpdCemuRequest->getFriendScreenname.languageList.IsNull() == false) + fpdCemuRequest->getFriendScreenname.languageList.GetPtr()[i] = frdReq.principalInfo.regionGuessed; + continue; + } + screennameOutput[0] = '\0'; + } + } + else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_FRIEND_MII) + { + if (g_fpd.nexFriendSession == nullptr) + { + fpdCemuRequest->returnCode = 0x80000000; // todo - proper error code + iosuIoctl_completeRequest(ioQueueEntry, returnValue); + return; + } + fpdCemuRequest->returnCode = 0; + for (sint32 i = 0; i < fpdCemuRequest->getFriendMii.count; i++) + { + uint8* miiOutput = fpdCemuRequest->getFriendMii.miiList + i * FFL_SIZE; + uint32 pid = fpdCemuRequest->getFriendMii.pidList[i]; + if (g_fpd.nexFriendSession == nullptr) + { + memset(miiOutput, 0, FFL_SIZE); + continue; + } + nexFriend frd; + nexFriendRequest frdReq; + if (g_fpd.nexFriendSession->getFriendByPID(frd, pid)) + { + memcpy(miiOutput, frd.nnaInfo.principalInfo.mii.miiData, FFL_SIZE); + continue; + } + bool incoming = false; + if (g_fpd.nexFriendSession->getFriendRequestByPID(frdReq, &incoming, pid)) + { + memcpy(miiOutput, frdReq.principalInfo.mii.miiData, FFL_SIZE); + continue; + } + memset(miiOutput, 0, FFL_SIZE); + } + } + else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_FRIEND_PRESENCE) + { + if (g_fpd.nexFriendSession == nullptr) + { + fpdCemuRequest->returnCode = 0x80000000; // todo - proper error code + iosuIoctl_completeRequest(ioQueueEntry, returnValue); + return; + } + fpdCemuRequest->returnCode = 0; + for (sint32 i = 0; i < fpdCemuRequest->getFriendPresence.count; i++) + { + friendPresence_t* presenceOutput = (friendPresence_t*)(fpdCemuRequest->getFriendPresence.presenceList + i * sizeof(friendPresence_t)); + memset(presenceOutput, 0, sizeof(friendPresence_t)); + uint32 pid = fpdCemuRequest->getFriendPresence.pidList[i]; + if (g_fpd.nexFriendSession == nullptr) + { + continue; + } + nexFriend frd; + nexFriendRequest frdReq; + if (g_fpd.nexFriendSession->getFriendByPID(frd, pid)) + { + presenceOutput->isOnline = frd.presence.isOnline ? 1 : 0; + presenceOutput->isValid = 1; + + presenceOutput->gameMode.joinFlagMask = frd.presence.joinFlagMask; + presenceOutput->gameMode.matchmakeType = frd.presence.joinAvailability; + presenceOutput->gameMode.joinGameId = frd.presence.gameId; + presenceOutput->gameMode.joinGameMode = frd.presence.gameMode; + presenceOutput->gameMode.hostPid = frd.presence.hostPid; + presenceOutput->gameMode.groupId = frd.presence.groupId; + + memcpy(presenceOutput->gameMode.appSpecificData, frd.presence.appSpecificData, 0x14); + + continue; + } + } + } + else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_FRIEND_RELATIONSHIP) + { + if (g_fpd.nexFriendSession == nullptr) + { + fpdCemuRequest->returnCode = 0x80000000; // todo - proper error code + iosuIoctl_completeRequest(ioQueueEntry, returnValue); + return; + } + fpdCemuRequest->returnCode = 0; + for (sint32 i = 0; i < fpdCemuRequest->getFriendRelationship.count; i++) + { + uint8* relationshipOutput = (fpdCemuRequest->getFriendRelationship.relationshipList + i); + uint32 pid = fpdCemuRequest->getFriendRelationship.pidList[i]; + *relationshipOutput = RELATIONSHIP_INVALID; + if (g_fpd.nexFriendSession == nullptr) + { + continue; + } + nexFriend frd; + nexFriendRequest frdReq; + bool incoming; + if (g_fpd.nexFriendSession->getFriendByPID(frd, pid)) + { + *relationshipOutput = RELATIONSHIP_FRIEND; + continue; + } + else if (g_fpd.nexFriendSession->getFriendRequestByPID(frdReq, &incoming, pid)) + { + if(incoming) + *relationshipOutput = RELATIONSHIP_FRIENDREQUEST_IN; + else + *relationshipOutput = RELATIONSHIP_FRIENDREQUEST_OUT; + } + } + } + else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_FRIEND_LIST) + { + handleRequest_GetFriendList(fpdCemuRequest, false); + } + else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_FRIENDREQUEST_LIST) + { + handleRequest_GetFriendRequestList(fpdCemuRequest); + } + else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_FRIEND_LIST_ALL) + { + handleRequest_GetFriendList(fpdCemuRequest, true); + } + else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_FRIEND_LIST_EX) + { + if (g_fpd.nexFriendSession == nullptr) + { + fpdCemuRequest->returnCode = 0x80000000; // todo - proper error code + iosuIoctl_completeRequest(ioQueueEntry, returnValue); + return; + } + handleRequest_GetFriendListEx(fpdCemuRequest); + } + else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_FRIENDREQUEST_LIST_EX) + { + if (g_fpd.nexFriendSession == nullptr) + { + fpdCemuRequest->returnCode = 0x80000000; // todo - proper error code + iosuIoctl_completeRequest(ioQueueEntry, returnValue); + return; + } + handleRequest_GetFriendRequestListEx(fpdCemuRequest); + } + else if (fpdCemuRequest->requestCode == IOSU_FPD_ADD_FRIEND) + { + // todo - figure out how this works + *_fpdAsyncAddFriendRetCode.GetPtr() = 0; + coreinitAsyncCallback_add(fpdCemuRequest->addOrRemoveFriend.funcPtr, 2, _fpdAsyncAddFriendRetCode.GetMPTR(), fpdCemuRequest->addOrRemoveFriend.custom); + } + else if (fpdCemuRequest->requestCode == IOSU_FPD_ADD_FRIEND_REQUEST) + { + handleRequest_AddFriendRequest(fpdCemuRequest); + } + else if (fpdCemuRequest->requestCode == IOSU_FPD_REMOVE_FRIEND_ASYNC) + { + handleRequest_RemoveFriendAsync(fpdCemuRequest); + } + else if (fpdCemuRequest->requestCode == IOSU_FPD_MARK_FRIEND_REQUEST_AS_RECEIVED_ASYNC) + { + handleRequest_MarkFriendRequestAsReceivedAsync(fpdCemuRequest); + } + else if (fpdCemuRequest->requestCode == IOSU_FPD_CANCEL_FRIEND_REQUEST_ASYNC) + { + handleRequest_CancelFriendRequestAsync(fpdCemuRequest); + } + else if (fpdCemuRequest->requestCode == IOSU_FPD_ACCEPT_FRIEND_REQUEST_ASYNC) + { + handleRequest_AcceptFriendRequestAsync(fpdCemuRequest); + } + else if (fpdCemuRequest->requestCode == IOSU_FPD_GET_BASIC_INFO_ASYNC) + { + handleRequest_GetBasicInfoAsync(fpdCemuRequest); + } + else if (fpdCemuRequest->requestCode == IOSU_FPD_UPDATE_GAMEMODE) + { + gameMode_t* gameMode = fpdCemuRequest->updateGameMode.gameMode.GetPtr(); + uint16be* gameModeMessage = fpdCemuRequest->updateGameMode.gameModeMessage.GetPtr(); + + g_fpd.myPresence.joinFlagMask = gameMode->joinFlagMask; + + g_fpd.myPresence.joinAvailability = (uint8)(uint32)gameMode->matchmakeType; + g_fpd.myPresence.gameId = gameMode->joinGameId; + g_fpd.myPresence.gameMode = gameMode->joinGameMode; + g_fpd.myPresence.hostPid = gameMode->hostPid; + g_fpd.myPresence.groupId = gameMode->groupId; + memcpy(g_fpd.myPresence.appSpecificData, gameMode->appSpecificData, 0x14); + + if (g_fpd.nexFriendSession) + { + g_fpd.nexFriendSession->updateMyPresence(g_fpd.myPresence); + } + + fpdCemuRequest->returnCode = 0; + } + else + cemu_assert_unimplemented(); + } + else + assert_dbg(); + iosuIoctl_completeRequest(ioQueueEntry, returnValue); + } + return; + } + + void Initialize() + { + if (g_fpd.isThreadStarted) + return; + std::thread t1(iosuFpd_thread); + t1.detach(); + g_fpd.isThreadStarted = true; + } + } +} \ No newline at end of file diff --git a/src/Cafe/IOSU/legacy/iosu_fpd.h b/src/Cafe/IOSU/legacy/iosu_fpd.h new file mode 100644 index 00000000..bd52035c --- /dev/null +++ b/src/Cafe/IOSU/legacy/iosu_fpd.h @@ -0,0 +1,351 @@ +#pragma once + +namespace iosu +{ + namespace fpd + { + typedef struct + { + /* +0x0 */ uint16be year; + /* +0x2 */ uint8 month; + /* +0x3 */ uint8 day; + /* +0x4 */ uint8 hour; + /* +0x5 */ uint8 minute; + /* +0x6 */ uint8 second; + /* +0x7 */ uint8 padding; + }fpdDate_t; + + static_assert(sizeof(fpdDate_t) == 8); + + typedef struct + { + /* +0x000 */ uint8 type; // type(Non-Zero -> Friend, 0 -> Friend request ? ) + /* +0x001 */ uint8 _padding1[7]; + /* +0x008 */ uint32be pid; + /* +0x00C */ char nnid[0x10 + 1]; + /* +0x01D */ uint8 ukn01D; + /* +0x01E */ uint8 _padding1E[2]; + /* +0x020 */ uint16be screenname[10 + 1]; + /* +0x036 */ uint8 ukn036; + /* +0x037 */ uint8 _padding037; + /* +0x038 */ uint8 mii[0x60]; + /* +0x098 */ fpdDate_t uknDate; + // sub struct (the part above seems to be shared with friend requests) + union + { + struct + { + /* +0x0A0 */ uint8 ukn0A0; // country code? + /* +0x0A1 */ uint8 ukn0A1; // country subcode? + /* +0x0A2 */ uint8 _paddingA2[2]; + /* +0x0A4 */ uint32be ukn0A4; + /* +0x0A8 */ uint64 gameKeyTitleId; + /* +0x0B0 */ uint16be gameKeyUkn; + /* +0x0B2 */ uint8 _paddingB2[6]; + /* +0x0B8 */ uint32 ukn0B8; + /* +0x0BC */ uint32 ukn0BC; + /* +0x0C0 */ uint32 ukn0C0; + /* +0x0C4 */ uint32 ukn0C4; + /* +0x0C8 */ uint32 ukn0C8; + /* +0x0CC */ uint32 ukn0CC; + /* +0x0D0 */ uint8 appSpecificData[0x14]; + /* +0x0E4 */ uint8 ukn0E4; + /* +0x0E5 */ uint8 _paddingE5; + /* +0x0E6 */ uint16 uknStr[0x80]; // game mode description (could be larger) + /* +0x1E6 */ uint8 _padding1E6[0x1EC - 0x1E6]; + /* +0x1EC */ uint8 isOnline; + /* +0x1ED */ uint8 _padding1ED[3]; + // some other sub struct? + /* +0x1F0 */ uint8 ukn1F0; + /* +0x1F1 */ uint8 _padding1F1; + /* +0x1F2 */ uint16be statusMessage[16 + 1]; // pops up every few seconds in friend list (ingame character name?) + /* +0x214 */ uint8 _padding214[4]; + /* +0x218 */ fpdDate_t uknDate218; + /* +0x220 */ fpdDate_t lastOnline; + }friendExtraData; + struct + { + /* +0x0A0 */ uint64 messageId; // guessed + /* +0x0A8 */ uint8 ukn0A8; + /* +0x0A9 */ uint8 ukn0A9; // comment language? (guessed) + /* +0x0AA */ uint16be comment[0x40]; + /* +0x12A */ uint8 ukn12A; // ingame name language? (guessed) + /* +0x12B */ uint8 _padding12B; + /* +0x12C */ uint16be uknMessage[18]; // exact length unknown, could be shorter (ingame screen name?) + /* +0x150 */ uint64 gameKeyTitleId; + /* +0x158 */ uint16be gameKeyUkn; + /* +0x15A */ uint8 _padding[6]; + /* +0x160 */ fpdDate_t uknData0; + /* +0x168 */ fpdDate_t uknData1; + }requestExtraData; + }; + }friendData_t; + + static_assert(sizeof(friendData_t) == 0x228, ""); + static_assert(offsetof(friendData_t, nnid) == 0x00C, ""); + static_assert(offsetof(friendData_t, friendExtraData.gameKeyTitleId) == 0x0A8, ""); + static_assert(offsetof(friendData_t, friendExtraData.appSpecificData) == 0x0D0, ""); + static_assert(offsetof(friendData_t, friendExtraData.uknStr) == 0x0E6, ""); + static_assert(offsetof(friendData_t, friendExtraData.ukn1F0) == 0x1F0, ""); + + static_assert(offsetof(friendData_t, requestExtraData.messageId) == 0x0A0, ""); + static_assert(offsetof(friendData_t, requestExtraData.comment) == 0x0AA, ""); + static_assert(offsetof(friendData_t, requestExtraData.uknMessage) == 0x12C, ""); + static_assert(offsetof(friendData_t, requestExtraData.gameKeyTitleId) == 0x150, ""); + static_assert(offsetof(friendData_t, requestExtraData.uknData1) == 0x168, ""); + + typedef struct + { + /* +0x00 */ uint32be pid; + /* +0x04 */ char nnid[0x11]; + /* +0x15 */ uint8 ukn15; + /* +0x16 */ uint8 uknOrPadding[2]; + /* +0x18 */ uint16be screenname[11]; + /* +0x2E */ uint8 ukn2E; // bool option + /* +0x2F */ uint8 ukn2F; + /* +0x30 */ uint8 miiData[0x60]; + /* +0x90 */ fpdDate_t uknDate90; + }friendBasicInfo_t; // size is 0x98 + + static_assert(sizeof(friendBasicInfo_t) == 0x98, ""); + static_assert(offsetof(friendBasicInfo_t, nnid) == 0x04, ""); + static_assert(offsetof(friendBasicInfo_t, ukn15) == 0x15, ""); + static_assert(offsetof(friendBasicInfo_t, screenname) == 0x18, ""); + static_assert(offsetof(friendBasicInfo_t, ukn2E) == 0x2E, ""); + static_assert(offsetof(friendBasicInfo_t, miiData) == 0x30, ""); + + typedef struct + { + /* +0x000 */ uint32be pid; + /* +0x004 */ uint8 nnid[17]; // guessed type + /* +0x015 */ uint8 _uknOrPadding15; + /* +0x016 */ uint8 _uknOrPadding16; + /* +0x017 */ uint8 _uknOrPadding17; + /* +0x018 */ uint16be screenname[11]; + /* +0x02E */ uint8 ukn2E; // bool option + /* +0x02F */ uint8 ukn2F; // ukn + /* +0x030 */ uint8 miiData[0x60]; + /* +0x090 */ fpdDate_t uknDate; + /* +0x098 */ uint64 ukn98; + /* +0x0A0 */ uint8 isMarkedAsReceived; + /* +0x0A1 */ uint8 uknA1; + /* +0x0A2 */ uint16be message[0x40]; + /* +0x122 */ uint8 ukn122; + /* +0x123 */ uint8 _padding123; + /* +0x124 */ uint16be uknString2[18]; + /* +0x148 */ uint64 gameKeyTitleId; + /* +0x150 */ uint16be gameKeyUkn; + /* +0x152 */ uint8 _padding152[6]; + /* +0x158 */ fpdDate_t uknDate2; + /* +0x160 */ fpdDate_t expireDate; + }friendRequest_t; + + static_assert(sizeof(friendRequest_t) == 0x168, ""); + static_assert(offsetof(friendRequest_t, uknDate) == 0x090, ""); + static_assert(offsetof(friendRequest_t, message) == 0x0A2, ""); + static_assert(offsetof(friendRequest_t, uknString2) == 0x124, ""); + static_assert(offsetof(friendRequest_t, gameKeyTitleId) == 0x148, ""); + + typedef struct + { + /* +0x00 */ uint32be joinFlagMask; + /* +0x04 */ uint32be matchmakeType; + /* +0x08 */ uint32be joinGameId; + /* +0x0C */ uint32be joinGameMode; + /* +0x10 */ uint32be hostPid; + /* +0x14 */ uint32be groupId; + /* +0x18 */ uint8 appSpecificData[0x14]; + }gameMode_t; + + static_assert(sizeof(gameMode_t) == 0x2C, ""); + + typedef struct + { + gameMode_t gameMode; + /* +0x2C */ uint8 region; + /* +0x2D */ uint8 regionSubcode; + /* +0x2E */ uint8 platform; + /* +0x2F */ uint8 _padding2F; + /* +0x30 */ uint8 isOnline; + /* +0x31 */ uint8 isValid; + /* +0x32 */ uint8 padding[2]; // guessed + }friendPresence_t; + + static_assert(sizeof(friendPresence_t) == 0x34, ""); + static_assert(offsetof(friendPresence_t, region) == 0x2C, ""); + static_assert(offsetof(friendPresence_t, isOnline) == 0x30, ""); + + static const int RELATIONSHIP_INVALID = 0; + static const int RELATIONSHIP_FRIENDREQUEST_OUT = 1; + static const int RELATIONSHIP_FRIENDREQUEST_IN = 2; + static const int RELATIONSHIP_FRIEND = 3; + + typedef struct + { + uint32 requestCode; + union + { + struct + { + MEMPTR<void> ptr; + }common; + struct + { + MPTR funcPtr; + MPTR custom; + }loginAsync; + struct + { + MEMPTR<uint32be> pidList; + uint32 startIndex; + uint32 maxCount; + }getFriendList; + struct + { + MEMPTR<friendData_t> friendData; + MEMPTR<uint32be> pidList; + uint32 count; + }getFriendListEx; + struct + { + MEMPTR<friendRequest_t> friendRequest; + MEMPTR<uint32be> pidList; + uint32 count; + }getFriendRequestListEx; + struct + { + uint32 pid; + MPTR funcPtr; + MPTR custom; + }addOrRemoveFriend; + struct + { + uint64 messageId; + MPTR funcPtr; + MPTR custom; + }cancelOrAcceptFriendRequest; + struct + { + uint32 pid; + MEMPTR<uint16be> message; + MPTR funcPtr; + MPTR custom; + }addFriendRequest; + struct + { + MEMPTR<uint64> messageIdList; + uint32 count; + MPTR funcPtr; + MPTR custom; + }markFriendRequest; + struct + { + MEMPTR<friendBasicInfo_t> basicInfo; + MEMPTR<uint32be> pidList; + sint32 count; + MPTR funcPtr; + MPTR custom; + }getBasicInfo; + struct + { + uint32 notificationMask; + MPTR funcPtr; + MPTR custom; + }setNotificationHandler; + struct + { + MEMPTR<char> accountIds; + MEMPTR<uint32be> pidList; + sint32 count; + }getFriendAccountId; + struct + { + MEMPTR<uint16be> nameList; + MEMPTR<uint32be> pidList; + sint32 count; + bool replaceNonAscii; + MEMPTR<uint8> languageList; + }getFriendScreenname; + struct + { + uint8* miiList; + MEMPTR<uint32be> pidList; + sint32 count; + }getFriendMii; + struct + { + uint8* presenceList; + MEMPTR<uint32be> pidList; + sint32 count; + }getFriendPresence; + struct + { + uint8* relationshipList; + MEMPTR<uint32be> pidList; + sint32 count; + }getFriendRelationship; + struct + { + MEMPTR<gameMode_t> gameMode; + MEMPTR<uint16be> gameModeMessage; + }updateGameMode; + }; + + // output + uint32 returnCode; // return value + union + { + struct + { + uint32 numReturnedCount; + }resultGetFriendList; + struct + { + uint32 u32; + }resultU32; + }; + }iosuFpdCemuRequest_t; + + // custom dev/fpd protocol (Cemu only) + #define IOSU_FPD_REQUEST_CEMU (0xEE) + + // FPD request Cemu subcodes + enum + { + _IOSU_FPD_NONE, + IOSU_FPD_INITIALIZE, + IOSU_FPD_SET_NOTIFICATION_HANDLER, + IOSU_FPD_LOGIN_ASYNC, + IOSU_FPD_IS_ONLINE, + IOSU_FPD_IS_PREFERENCE_VALID, + + IOSU_FPD_UPDATE_GAMEMODE, + + IOSU_FPD_GET_MY_PRINCIPAL_ID, + IOSU_FPD_GET_MY_ACCOUNT_ID, + IOSU_FPD_GET_MY_MII, + IOSU_FPD_GET_MY_SCREENNAME, + + IOSU_FPD_GET_FRIEND_ACCOUNT_ID, + IOSU_FPD_GET_FRIEND_SCREENNAME, + IOSU_FPD_GET_FRIEND_MII, + IOSU_FPD_GET_FRIEND_PRESENCE, + IOSU_FPD_GET_FRIEND_RELATIONSHIP, + + IOSU_FPD_GET_FRIEND_LIST, + IOSU_FPD_GET_FRIENDREQUEST_LIST, + IOSU_FPD_GET_FRIEND_LIST_ALL, + IOSU_FPD_GET_FRIEND_LIST_EX, + IOSU_FPD_GET_FRIENDREQUEST_LIST_EX, + IOSU_FPD_ADD_FRIEND, + IOSU_FPD_ADD_FRIEND_REQUEST, + IOSU_FPD_REMOVE_FRIEND_ASYNC, + IOSU_FPD_CANCEL_FRIEND_REQUEST_ASYNC, + IOSU_FPD_ACCEPT_FRIEND_REQUEST_ASYNC, + IOSU_FPD_MARK_FRIEND_REQUEST_AS_RECEIVED_ASYNC, + IOSU_FPD_GET_BASIC_INFO_ASYNC, + }; + + void Initialize(); + } +} \ No newline at end of file diff --git a/src/Cafe/IOSU/legacy/iosu_ioctl.cpp b/src/Cafe/IOSU/legacy/iosu_ioctl.cpp new file mode 100644 index 00000000..e81fd018 --- /dev/null +++ b/src/Cafe/IOSU/legacy/iosu_ioctl.cpp @@ -0,0 +1,68 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/libs/coreinit/coreinit_Thread.h" +#include "Cafe/OS/libs/coreinit/coreinit.h" +#include "iosu_ioctl.h" +#include "util/helpers/ringbuffer.h" + +#include "util/helpers/Semaphore.h" + +// deprecated IOCTL handling code + +RingBuffer<ioQueueEntry_t*, 256> _ioctlRingbuffer[IOS_DEVICE_COUNT]; +CounterSemaphore _ioctlRingbufferSemaphore[IOS_DEVICE_COUNT]; + +std::mutex ioctlMutex; + +sint32 iosuIoctl_pushAndWait(uint32 ioctlHandle, ioQueueEntry_t* ioQueueEntry) +{ + if (ioctlHandle != IOS_DEVICE_ACT && ioctlHandle != IOS_DEVICE_ACP_MAIN && ioctlHandle != IOS_DEVICE_MCP && ioctlHandle != IOS_DEVICE_BOSS && ioctlHandle != IOS_DEVICE_NIM && ioctlHandle != IOS_DEVICE_FPD) + { + forceLogDebug_printf("Unsupported IOSU device %d", ioctlHandle); + cemu_assert_debug(false); + return 0; + } + __OSLockScheduler(); + ioctlMutex.lock(); + ioQueueEntry->ppcThread = coreinitThread_getCurrentThreadDepr(ppcInterpreterCurrentInstance); + + _ioctlRingbuffer[ioctlHandle].Push(ioQueueEntry); + ioctlMutex.unlock(); + _ioctlRingbufferSemaphore[ioctlHandle].increment(); + coreinit::__OSSuspendThreadInternal(coreinit::OSGetCurrentThread()); + if (ioQueueEntry->isCompleted == false) + assert_dbg(); + __OSUnlockScheduler(); + return ioQueueEntry->returnValue; +} + +ioQueueEntry_t* iosuIoctl_getNextWithWait(uint32 deviceIndex) +{ + _ioctlRingbufferSemaphore[deviceIndex].decrementWithWait(); + if (_ioctlRingbuffer[deviceIndex].HasData() == false) + assert_dbg(); + return _ioctlRingbuffer[deviceIndex].Pop(); +} + +ioQueueEntry_t* iosuIoctl_getNextWithTimeout(uint32 deviceIndex, sint32 ms) +{ + if (!_ioctlRingbufferSemaphore[deviceIndex].decrementWithWaitAndTimeout(ms)) + return nullptr; // timeout or spurious wake up + if (_ioctlRingbuffer[deviceIndex].HasData() == false) + return nullptr; + return _ioctlRingbuffer[deviceIndex].Pop(); +} + +void iosuIoctl_completeRequest(ioQueueEntry_t* ioQueueEntry, uint32 returnValue) +{ + ioQueueEntry->returnValue = returnValue; + ioQueueEntry->isCompleted = true; + coreinit::OSResumeThread(ioQueueEntry->ppcThread); +} + +void iosuIoctl_init() +{ + for (sint32 i = 0; i < IOS_DEVICE_COUNT; i++) + { + _ioctlRingbuffer[i].Clear(); + } +} diff --git a/src/Cafe/IOSU/legacy/iosu_ioctl.h b/src/Cafe/IOSU/legacy/iosu_ioctl.h new file mode 100644 index 00000000..c1fcaee7 --- /dev/null +++ b/src/Cafe/IOSU/legacy/iosu_ioctl.h @@ -0,0 +1,64 @@ +#pragma once + +struct OSThread_t; + +typedef struct _ioBufferVector_t +{ + // the meaning of these values might be arbitrary? (up to the /dev/* handler to process them) + //uint32 ukn00; + MEMPTR<uint8> buffer; + uint32be bufferSize; + uint32 ukn08; + //uint32 ukn0C; + MEMPTR<uint8> unknownBuffer; +}ioBufferVector_t; + +typedef struct +{ + bool isIoctlv; // false -> ioctl, true -> ioctlv + bool isAsync; + uint32 request; + uint32 countIn; + uint32 countOut; + MEMPTR<ioBufferVector_t> bufferVectors; + // info about submitter + OSThread_t* ppcThread; + //MPTR ppcThread; + // result + bool isCompleted; + uint32 returnValue; +}ioQueueEntry_t; + + +#define IOS_PATH_SOCKET "/dev/socket" +#define IOS_PATH_ODM "/dev/odm" +#define IOS_PATH_ACT "/dev/act" +#define IOS_PATH_FPD "/dev/fpd" +#define IOS_PATH_ACP_MAIN "/dev/acp_main" +#define IOS_PATH_MCP "/dev/mcp" +#define IOS_PATH_BOSS "/dev/boss" +#define IOS_PATH_NIM "/dev/nim" +#define IOS_PATH_IOSUHAX "/dev/iosuhax" + +#define IOS_DEVICE_UKN (0x1) +#define IOS_DEVICE_ODM (0x2) +#define IOS_DEVICE_SOCKET (0x3) +#define IOS_DEVICE_ACT (0x4) +#define IOS_DEVICE_FPD (0x5) +#define IOS_DEVICE_ACP_MAIN (0x6) +#define IOS_DEVICE_MCP (0x7) +#define IOS_DEVICE_BOSS (0x8) +#define IOS_DEVICE_NIM (0x9) + +#define IOS_DEVICE_COUNT 10 + +void iosuIoctl_init(); + + +// for use by IOSU +ioQueueEntry_t* iosuIoctl_getNextWithWait(uint32 deviceIndex); +ioQueueEntry_t* iosuIoctl_getNextWithTimeout(uint32 deviceIndex, sint32 ms); +void iosuIoctl_completeRequest(ioQueueEntry_t* ioQueueEntry, uint32 returnValue); + +// for use by PPC +sint32 iosuIoctl_pushAndWait(uint32 ioctlHandle, ioQueueEntry_t* ioQueueEntry); diff --git a/src/Cafe/IOSU/legacy/iosu_mcp.cpp b/src/Cafe/IOSU/legacy/iosu_mcp.cpp new file mode 100644 index 00000000..0b99496a --- /dev/null +++ b/src/Cafe/IOSU/legacy/iosu_mcp.cpp @@ -0,0 +1,268 @@ +#include "iosu_ioctl.h" +#include "iosu_mcp.h" +#include "Cafe/OS/libs/nn_common.h" +#include "util/tinyxml2/tinyxml2.h" + +#include "config/CemuConfig.h" +#include "Cafe/TitleList/GameInfo.h" +#include "util/helpers/helpers.h" +#include "Cafe/TitleList/TitleList.h" +#include "Cafe/CafeSystem.h" +#include "Cafe/IOSU/iosu_ipc_common.h" +#include "Cafe/IOSU/kernel/iosu_kernel.h" + +using namespace iosu::kernel; + +namespace iosu +{ + struct + { + bool isInitialized; + }iosuMcp = { 0 }; + + std::recursive_mutex sTitleInfoMutex; + + bool sHasAllTitlesMounted = false; + + uint32 mcpBuildTitleList(MCPTitleInfo* titleList, uint32 maxTitlesToCopy, std::function<bool(const TitleInfo&)> filterFunc) + { + CafeTitleList::WaitForMandatoryScan(); + std::set<TitleId> titleIdsVisited; + auto cafeTitleList = CafeTitleList::AcquireInternalList(); + + uint32 numTitlesCopied = 0; + for (auto& it : cafeTitleList) + { + if (numTitlesCopied == maxTitlesToCopy) + break; + uint64 titleId = it->GetAppTitleId(); + if (titleIdsVisited.find(titleId) != titleIdsVisited.end()) + continue; + if (!filterFunc(*it)) + continue; + titleIdsVisited.emplace(titleId); + if (titleList) + { + auto& titleOut = titleList[numTitlesCopied]; + titleOut.titleIdHigh = (uint32)(titleId >> 32); + titleOut.titleIdLow = (uint32)(titleId & 0xFFFFFFFF); + titleOut.titleVersion = it->GetAppTitleVersion(); + titleOut.appGroup = it->GetAppGroup(); + titleOut.appType = it->GetAppType(); + + std::string titlePath = CafeSystem::GetMlcStoragePath(titleId); + strcpy(titleOut.appPath, titlePath.c_str()); + + titleOut.osVersion = 0; // todo + titleOut.sdkVersion = 0; + } + + numTitlesCopied++; + } + CafeTitleList::ReleaseInternalList(); + + if (!sHasAllTitlesMounted) + { + CafeSystem::MlcStorageMountAllTitles(); + sHasAllTitlesMounted = true; + } + + return numTitlesCopied; + } + + sint32 mcpGetTitleList(MCPTitleInfo* titleList, uint32 titleListBufferSize, uint32be* titleCount) + { + std::unique_lock _lock(sTitleInfoMutex); + *titleCount = mcpBuildTitleList(titleList, *titleCount, [](const TitleInfo& titleInfo) -> bool { return true; }); + return 0; + } + + sint32 mcpGetTitleCount() + { + std::unique_lock _lock(sTitleInfoMutex); + return mcpBuildTitleList(nullptr, 0xFFFFFFFF, [](const TitleInfo& titleInfo) -> bool { return true; }); + } + + sint32 mcpGetTitleListByAppType(MCPTitleInfo* titleList, uint32 titleListBufferSize, uint32be* titleCount, uint32 appType) + { + std::unique_lock _lock(sTitleInfoMutex); + uint32 maxEntryCount = (uint32)*titleCount; + *titleCount = mcpBuildTitleList(titleList, maxEntryCount, [appType](const TitleInfo& titleInfo) -> bool { return titleInfo.GetAppType() == appType; }); + return 0; + } + + sint32 mcpGetTitleListByTitleId(MCPTitleInfo* titleList, uint32 titleListBufferSize, uint32be* titleCount, uint64 titleId) + { + std::unique_lock _lock(sTitleInfoMutex); + uint32 maxEntryCount = (uint32)*titleCount; + *titleCount = mcpBuildTitleList(titleList, maxEntryCount, [titleId](const TitleInfo& titleInfo) -> bool { return titleInfo.GetAppTitleId() == titleId; }); + return 0; + } + + int iosuMcp_thread() + { + SetThreadName("iosuMcp_thread"); + while (true) + { + uint32 returnValue = 0; // Ioctl return value + ioQueueEntry_t* ioQueueEntry = iosuIoctl_getNextWithWait(IOS_DEVICE_MCP); + if (ioQueueEntry->request == IOSU_MCP_REQUEST_CEMU) + { + iosuMcpCemuRequest_t* mcpCemuRequest = (iosuMcpCemuRequest_t*)ioQueueEntry->bufferVectors[0].buffer.GetPtr(); + if (mcpCemuRequest->requestCode == IOSU_MCP_GET_TITLE_LIST) + { + uint32be titleCount = mcpCemuRequest->titleListRequest.titleCount; + mcpCemuRequest->returnCode = mcpGetTitleList(mcpCemuRequest->titleListRequest.titleList.GetPtr(), mcpCemuRequest->titleListRequest.titleListBufferSize, &titleCount); + mcpCemuRequest->titleListRequest.titleCount = titleCount; + } + else if (mcpCemuRequest->requestCode == IOSU_MCP_GET_TITLE_LIST_BY_APP_TYPE) + { + uint32be titleCount = mcpCemuRequest->titleListRequest.titleCount; + mcpCemuRequest->returnCode = mcpGetTitleListByAppType(mcpCemuRequest->titleListRequest.titleList.GetPtr(), mcpCemuRequest->titleListRequest.titleListBufferSize, &titleCount, mcpCemuRequest->titleListRequest.appType); + mcpCemuRequest->titleListRequest.titleCount = titleCount; + } + else if (mcpCemuRequest->requestCode == IOSU_MCP_GET_TITLE_INFO) + { + uint32be titleCount = mcpCemuRequest->titleListRequest.titleCount; + mcpCemuRequest->returnCode = mcpGetTitleListByTitleId(mcpCemuRequest->titleListRequest.titleList.GetPtr(), mcpCemuRequest->titleListRequest.titleListBufferSize, &titleCount, mcpCemuRequest->titleListRequest.titleId); + mcpCemuRequest->titleListRequest.titleCount = titleCount; + } + else if (mcpCemuRequest->requestCode == IOSU_MCP_GET_TITLE_COUNT) + { + uint32be titleCount = mcpGetTitleCount(); + mcpCemuRequest->returnCode = titleCount; + mcpCemuRequest->titleListRequest.titleCount = titleCount; + } + else + assert_dbg(); + } + else + assert_dbg(); + iosuIoctl_completeRequest(ioQueueEntry, returnValue); + } + return 0; + } + + void iosuMcp_init() + { + if (iosuMcp.isInitialized) + return; + // start the act thread + std::thread t(iosuMcp_thread); + t.detach(); + iosuMcp.isInitialized = true; + } + + namespace mcp + { + IOSMsgQueueId sMCPIoMsgQueue; + SysAllocator<iosu::kernel::IOSMessage, 352> _m_sMCPIoMsgQueueMsgBuffer; + std::thread sMCPIoThread; + + struct MCPClient + { + std::string workingDirectory; + bool isAllocated{ false }; + + void AllocateAndInitialize() + { + isAllocated = true; + workingDirectory = std::string("/"); + } + + void ReleaseAndCleanup() + { + isAllocated = false; + } + }; + + std::array<MCPClient, 256> sMCPClientArray; + + IOS_ERROR MCPAllocateClient(sint32& indexOut) + { + for (size_t i = 0; i < sMCPClientArray.size(); i++) + { + if (sMCPClientArray[i].isAllocated) + continue; + sMCPClientArray[i].AllocateAndInitialize(); + indexOut = (sint32)i; + return IOS_ERROR_OK; + } + return IOS_ERROR::IOS_ERROR_MAXIMUM_REACHED; + } + + void MCPIoThread() + { + SetThreadName("IOSU-MCP"); + IOSMessage msg; + while (true) + { + IOS_ERROR r = IOS_ReceiveMessage(sMCPIoMsgQueue, &msg, 0); + cemu_assert(!IOS_ResultIsError(r)); + if (msg == 0) + return; // shutdown signaled + IPCCommandBody* cmd = MEMPTR<IPCCommandBody>(msg).GetPtr(); + uint32 clientHandle = (uint32)cmd->devHandle; + if (cmd->cmdId == IPCCommandId::IOS_OPEN) + { + sint32 clientIndex = 0; + r = MCPAllocateClient(clientIndex); + if (r != IOS_ERROR_OK) + { + IOS_ResourceReply(cmd, r); + continue; + } + IOS_ResourceReply(cmd, (IOS_ERROR)clientIndex); + continue; + } + else if (cmd->cmdId == IPCCommandId::IOS_CLOSE) + { + cemu_assert(clientHandle < sMCPClientArray.size()); + sMCPClientArray[clientHandle].ReleaseAndCleanup(); + IOS_ResourceReply(cmd, IOS_ERROR_OK); + continue; + } + else if (cmd->cmdId == IPCCommandId::IOS_IOCTL) + { + cemu_assert(clientHandle < sMCPClientArray.size()); + cemu_assert(sMCPClientArray[clientHandle].isAllocated); + IOS_ResourceReply(cmd, (IOS_ERROR)0x80000000); + continue; + } + else if (cmd->cmdId == IPCCommandId::IOS_IOCTLV) + { + cemu_assert_unimplemented(); + //uint32 requestId = cmd->args[0]; + //uint32 numIn = cmd->args[1]; + //uint32 numOut = cmd->args[2]; + //IPCIoctlVector* vec = MEMPTR<IPCIoctlVector>{ cmd->args[3] }.GetPtr(); + IOS_ResourceReply(cmd, IOS_ERROR_INVALID); + continue; + } + else + { + cemuLog_log(LogType::Force, "/dev/mcp: Unsupported IPC cmdId"); + cemu_assert_suspicious(); + IOS_ResourceReply(cmd, IOS_ERROR_INVALID); + } + } + } + + void Init() + { + for (auto& it : sMCPClientArray) + it.ReleaseAndCleanup(); + sMCPIoMsgQueue = (IOSMsgQueueId)IOS_CreateMessageQueue(_m_sMCPIoMsgQueueMsgBuffer.GetPtr(), _m_sMCPIoMsgQueueMsgBuffer.GetCount()); + IOS_ERROR r = IOS_RegisterResourceManager("/dev/mcp", sMCPIoMsgQueue); + IOS_DeviceAssociateId("/dev/mcp", 11); + cemu_assert(!IOS_ResultIsError(r)); + sMCPIoThread = std::thread(MCPIoThread); + } + + void Shutdown() + { + IOS_SendMessage(sMCPIoMsgQueue, 0, 0); + sMCPIoThread.join(); + } + }; +} diff --git a/src/Cafe/IOSU/legacy/iosu_mcp.h b/src/Cafe/IOSU/legacy/iosu_mcp.h new file mode 100644 index 00000000..bca301a4 --- /dev/null +++ b/src/Cafe/IOSU/legacy/iosu_mcp.h @@ -0,0 +1,81 @@ + +#pragma pack(1) + +struct MCPTitleInfo +{ + + /* +0x00 */ uint32be titleIdHigh; // app.xml + /* +0x04 */ uint32be titleIdLow; + /* +0x08 */ uint32be appGroup; + /* +0x0C */ char appPath[0x38]; // size guessed + /* +0x44 */ uint32be appType; // app.xml + /* +0x48 */ uint16be titleVersion; // app.xml + // everything below is uncertain + /* +0x4A */ uint64be osVersion; // app.xml + /* +0x52 */ uint32be sdkVersion; // app.xml + /* +0x56 */ uint8 ukn[0x61 - 0x56]; + //move this and the stuff below +}; + +static_assert(sizeof(MCPTitleInfo) == 0x61); + +#pragma pack() + +// custom dev/mcp protocol (Cemu only) +#define IOSU_MCP_REQUEST_CEMU (0xEE) + +typedef struct +{ + uint32 requestCode; + struct + { + MEMPTR<MCPTitleInfo> titleList; + uint32be titleCount; + uint32be titleListBufferSize; + uint64 titleId; + // filters + uint32be appType; + }titleListRequest; + + // output + uint32 returnCode; // ACP return value + union + { + //struct + //{ + // uint64 u64; + //}resultU64; + struct + { + uint32 u32; + }resultU32; + //struct + //{ + // char strBuffer[1024]; + //}resultString; + //struct + //{ + // uint8 binBuffer[1024]; + //}resultBinary; + }; +}iosuMcpCemuRequest_t; + +// MCP request Cemu subcodes +#define IOSU_MCP_GET_TITLE_LIST 0x01 +#define IOSU_MCP_GET_TITLE_LIST_BY_APP_TYPE 0x02 +#define IOSU_MCP_GET_TITLE_INFO 0x03 // get title list by titleId +#define IOSU_MCP_GET_TITLE_COUNT 0x04 + +namespace iosu +{ + sint32 mcpGetTitleCount(); + sint32 mcpGetTitleList(MCPTitleInfo* titleList, uint32 titleListBufferSize, uint32be* titleCount); + + void iosuMcp_init(); + + namespace mcp + { + void Init(); + void Shutdown(); + }; +} diff --git a/src/Cafe/IOSU/legacy/iosu_nim.cpp b/src/Cafe/IOSU/legacy/iosu_nim.cpp new file mode 100644 index 00000000..8d916329 --- /dev/null +++ b/src/Cafe/IOSU/legacy/iosu_nim.cpp @@ -0,0 +1,333 @@ +#include "iosu_nim.h" +#include "iosu_ioctl.h" +#include "iosu_act.h" +#include "iosu_mcp.h" +#include "util/crypto/aes128.h" +#include "curl/curl.h" +#include "openssl/bn.h" +#include "openssl/x509.h" +#include "openssl/ssl.h" +#include "util/helpers/helpers.h" + +#include <thread> + +#include "Cemu/napi/napi.h" +#include "Cemu/ncrypto/ncrypto.h" + +namespace iosu +{ + namespace nim + { + struct NIMTitleLatestVersion + { + uint64 titleId; + uint32 version; + }; + +#define PACKAGE_TYPE_UPDATE (1) + + struct NIMPackage + { + uint64 titleId; + uint16 version; + uint8 type; + }; + + struct + { + bool isInitialized; + // version list + sint32 latestVersion; + char fqdn[256]; + std::vector<NIMTitleLatestVersion> titlesLatestVersion; + // nim packages + // note: Seems like scope.rpx expects the number of packages to never change after the initial GetNum call? + std::vector<NIMPackage*> packages; + bool packageListReady; + bool backgroundThreadStarted; + } g_nim = {}; + + bool nim_getLatestVersion() + { + g_nim.latestVersion = -1; + NAPI::AuthInfo authInfo; + authInfo.country = NCrypto::GetCountryAsString(Account::GetCurrentAccount().GetCountry()); + authInfo.region = NCrypto::SEEPROM_GetRegion(); + auto versionListVersionResult = NAPI::TAG_GetVersionListVersion(authInfo); + if (!versionListVersionResult.isValid) + return false; + if (versionListVersionResult.fqdnURL.size() >= 256) + { + cemuLog_force("NIM: fqdn URL too long"); + return false; + } + g_nim.latestVersion = (sint32)versionListVersionResult.version; + strcpy(g_nim.fqdn, versionListVersionResult.fqdnURL.c_str()); + return true; + } + + bool nim_getVersionList() + { + g_nim.titlesLatestVersion.clear(); + + NAPI::AuthInfo authInfo; + authInfo.country = NCrypto::GetCountryAsString(Account::GetCurrentAccount().GetCountry()); + authInfo.region = NCrypto::SEEPROM_GetRegion(); + auto versionListVersionResult = NAPI::TAG_GetVersionListVersion(authInfo); + if (!versionListVersionResult.isValid) + return false; + auto versionListResult = TAG_GetVersionList(authInfo, versionListVersionResult.fqdnURL, versionListVersionResult.version); + if (!versionListResult.isValid) + return false; + for (auto& itr : versionListResult.titleVersionList) + { + NIMTitleLatestVersion titleLatestVersion; + titleLatestVersion.titleId = itr.first; + titleLatestVersion.version = itr.second; + g_nim.titlesLatestVersion.push_back(titleLatestVersion); + } + return true; + } + + NIMTitleLatestVersion* nim_findTitleLatestVersion(uint64 titleId) + { + for (auto& titleLatestVersion : g_nim.titlesLatestVersion) + { + if (titleLatestVersion.titleId == titleId) + return &titleLatestVersion; + } + return nullptr; + } + + void nim_buildDownloadList() + { + sint32 titleCount = mcpGetTitleCount(); + MCPTitleInfo* titleList = (MCPTitleInfo*)malloc(titleCount * sizeof(MCPTitleInfo)); + memset(titleList, 0, titleCount * sizeof(MCPTitleInfo)); + + uint32be titleCountBE = titleCount; + if (mcpGetTitleList(titleList, titleCount * sizeof(MCPTitleInfo), &titleCountBE) != 0) + { + forceLog_printf("IOSU: nim failed to acquire title list"); + free(titleList); + return; + } + titleCount = titleCountBE; + // check for game updates + for (sint32 i = 0; i < titleCount; i++) + { + if( titleList[i].titleIdHigh != 0x00050000 ) + continue; + // find update title in title version list + uint64 titleId = (0x0005000EULL << 32) | ((uint64)(uint32)titleList[i].titleIdLow); + NIMTitleLatestVersion* latestVersionInfo = nim_findTitleLatestVersion(titleId); + if(latestVersionInfo == nullptr) + continue; + // compare version + if(latestVersionInfo->version <= (uint32)titleList[i].titleVersion ) + continue; // already on latest version + // add to packages + NIMPackage* nimPackage = (NIMPackage*)malloc(sizeof(NIMPackage)); + memset(nimPackage, 0, sizeof(NIMPackage)); + nimPackage->titleId = titleId; + nimPackage->type = PACKAGE_TYPE_UPDATE; + g_nim.packages.push_back(nimPackage); + } + // check for AOC/titles to download + // todo + free(titleList); + } + + void nim_getPackagesInfo(uint64* titleIdList, sint32 count, titlePackageInfo_t* packageInfoList) + { + memset(packageInfoList, 0, sizeof(titlePackageInfo_t)*count); + for (sint32 i = 0; i < count; i++) + { + uint64 titleId = _swapEndianU64(titleIdList[i]); + titlePackageInfo_t* packageInfo = packageInfoList + i; + + packageInfo->titleId = _swapEndianU64(titleIdList[0]); + + packageInfo->ukn0C = 1; // update + + // pending + packageInfo->ukn48 = 0; // with 0 there is no progress bar, with 3 there is a progress bar + packageInfo->ukn40 = (5 << 20) | (0 << 27) | (1<<31) | (0x30000<<0); + + packageInfo->ukn0F = 1; + + } + + } + + struct idbeIconCacheEntry_t + { + void setIconData(NAPI::IDBEIconDataV0& newIconData) + { + iconData = newIconData; + hasIconData = true; + } + + void setNoIcon() + { + hasIconData = false; + iconData = {}; + } + + uint64 titleId; + uint32 lastRequestTime; + bool hasIconData{ false }; + NAPI::IDBEIconDataV0 iconData{}; + }; + + std::vector<idbeIconCacheEntry_t> idbeIconCache; + + void idbe_addIconToCache(uint64 titleId, NAPI::IDBEIconDataV0* iconData) + { + idbeIconCacheEntry_t newCacheEntry; + newCacheEntry.titleId = titleId; + newCacheEntry.lastRequestTime = GetTickCount(); + if (iconData) + newCacheEntry.setIconData(*iconData); + else + newCacheEntry.setNoIcon(); + idbeIconCache.push_back(newCacheEntry); + } + + sint32 nim_getIconDatabaseEntry(uint64 titleId, void* idbeIconOutput) + { + // if titleId is an update, replace it with gameId instead + if ((uint32)(titleId >> 32) == 0x0005000EULL) + { + titleId &= ~0xF00000000ULL; + } + + for (auto& entry : idbeIconCache) + { + if (entry.titleId == titleId) + { + if( entry.hasIconData ) + memcpy(idbeIconOutput, &entry.iconData, sizeof(NAPI::IDBEIconDataV0)); + else + memset(idbeIconOutput, 0, sizeof(NAPI::IDBEIconDataV0)); + return 0; + } + } + + auto result = NAPI::IDBE_Request(titleId); + if (!result) + { + memset(idbeIconOutput, 0, sizeof(NAPI::IDBEIconDataV0)); + forceLog_printf("NIM: Unable to download IDBE icon"); + return 0; + } + // add new cache entry + idbe_addIconToCache(titleId, &*result); + // return result + memcpy(idbeIconOutput, &*result, sizeof(NAPI::IDBEIconDataV0)); + return 0; + } + + void nim_backgroundThread() + { + while (iosuAct_isAccountDataLoaded() == false) + { + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + } + + if (nim_getLatestVersion()) + { + if (nim_getVersionList()) + { + nim_buildDownloadList(); + } + } + g_nim.packageListReady = true; + } + + void iosuNim_waitUntilPackageListReady() + { + if (g_nim.backgroundThreadStarted == false) + { + forceLog_printf("IOSU: Starting nim background thread"); + std::thread t(nim_backgroundThread); + t.detach(); + g_nim.backgroundThreadStarted = true; + } + while (g_nim.packageListReady == false) + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + } + + + void iosuNim_thread() + { + SetThreadName("iosuNim_thread"); + while (true) + { + uint32 returnValue = 0; // Ioctl return value + ioQueueEntry_t* ioQueueEntry = iosuIoctl_getNextWithWait(IOS_DEVICE_NIM); + if (ioQueueEntry->request == IOSU_NIM_REQUEST_CEMU) + { + iosuNimCemuRequest_t* nimCemuRequest = (iosuNimCemuRequest_t*)ioQueueEntry->bufferVectors[0].buffer.GetPtr(); + if (nimCemuRequest->requestCode == IOSU_NIM_GET_ICON_DATABASE_ENTRY) + { + nimCemuRequest->returnCode = nim_getIconDatabaseEntry(nimCemuRequest->titleId, nimCemuRequest->ptr.GetPtr()); + } + else if (nimCemuRequest->requestCode == IOSU_NIM_GET_PACKAGE_COUNT) + { + iosuNim_waitUntilPackageListReady(); + nimCemuRequest->resultU32.u32 = (uint32)g_nim.packages.size(); + nimCemuRequest->returnCode = 0; + } + else if (nimCemuRequest->requestCode == IOSU_NIM_GET_PACKAGES_TITLEID) + { + iosuNim_waitUntilPackageListReady(); + uint32 maxCount = nimCemuRequest->maxCount; + uint64* titleIdList = (uint64*)nimCemuRequest->ptr.GetPtr(); + uint32 count = 0; + memset(titleIdList, 0, sizeof(uint64) * maxCount); + for (auto& package : g_nim.packages) + { + titleIdList[count] = _swapEndianU64(package->titleId); + count++; + if (count >= maxCount) + break; + } + nimCemuRequest->returnCode = 0; + } + else if (nimCemuRequest->requestCode == IOSU_NIM_GET_PACKAGES_INFO) + { + iosuNim_waitUntilPackageListReady(); + uint32 maxCount = nimCemuRequest->maxCount; + uint64* titleIdList = (uint64*)nimCemuRequest->ptr.GetPtr(); + titlePackageInfo_t* packageInfo = (titlePackageInfo_t*)nimCemuRequest->ptr2.GetPtr(); + nim_getPackagesInfo(titleIdList, maxCount, packageInfo); + nimCemuRequest->returnCode = 0; + } + else + cemu_assert_unimplemented(); + } + else + cemu_assert_unimplemented(); + iosuIoctl_completeRequest(ioQueueEntry, returnValue); + } + return; + } + + void Initialize() + { + if (g_nim.isInitialized) + return; + std::vector<idbeIconCacheEntry_t> idbeIconCache = std::vector<idbeIconCacheEntry_t>(); + g_nim.titlesLatestVersion = std::vector<NIMTitleLatestVersion>(); + g_nim.packages = std::vector<NIMPackage*>(); + g_nim.packageListReady = false; + g_nim.backgroundThreadStarted = false; + std::thread t2(iosuNim_thread); + t2.detach(); + g_nim.isInitialized = true; + } + + } +} + diff --git a/src/Cafe/IOSU/legacy/iosu_nim.h b/src/Cafe/IOSU/legacy/iosu_nim.h new file mode 100644 index 00000000..a34cc538 --- /dev/null +++ b/src/Cafe/IOSU/legacy/iosu_nim.h @@ -0,0 +1,75 @@ +#pragma once + +namespace iosu +{ + namespace nim + { + struct titlePackageInfo_t + { + uint64 titleId; + uint32be ukn08; + uint8 ukn0C; + uint8 ukn0D; + uint8 ukn0E; + uint8 ukn0F; + uint8 ukn10; + uint8 ukn11; + uint8 ukn12; + uint8 ukn13; + uint8 ukn14[0xC]; + uint32be ukn20; + uint32be ukn24; + // ukn sint64 value (uknDA) + //uint32be ukn28_h; + //uint32be ukn2C_l; + uint64 ukn28DLProgressRelatedMax_u64be; + // ukn sint64 value (uknDB) + //uint32be ukn30_h; + //uint32be ukn34_l; + uint64 ukn30DLProgressRelatedCur_u64be; + // ukn sint64 value (uknDC) + uint32be ukn38DLProgressRelatedMax; + uint32be ukn3CDLProgressRelatedCur; + uint32be ukn40; + uint32be ukn44; + uint8 ukn48; // 0-7 are allowed values + uint8 ukn49; + uint8 ukn4A; + uint8 ukn4B; + uint32be ukn4C; + }; + + static_assert(sizeof(titlePackageInfo_t) == 0x50); + + struct iosuNimCemuRequest_t + { + uint32 requestCode; + // input + uint64 titleId; + MEMPTR<void> ptr; + MEMPTR<void> ptr2; + sint32 maxCount; + + // output + uint32 returnCode; // NIM return value + union + { + struct + { + uint32 u32; + }resultU32; + }; + }; + + // custom dev/nim protocol (Cemu only) + #define IOSU_NIM_REQUEST_CEMU (0xEE) + + // NIM request Cemu subcodes + #define IOSU_NIM_GET_ICON_DATABASE_ENTRY 0x01 + #define IOSU_NIM_GET_PACKAGE_COUNT 0x02 + #define IOSU_NIM_GET_PACKAGES_INFO 0x03 + #define IOSU_NIM_GET_PACKAGES_TITLEID 0x04 + + void Initialize(); + } +} \ No newline at end of file diff --git a/src/Cafe/IOSU/nn/iosu_nn_service.cpp b/src/Cafe/IOSU/nn/iosu_nn_service.cpp new file mode 100644 index 00000000..ade1fa2b --- /dev/null +++ b/src/Cafe/IOSU/nn/iosu_nn_service.cpp @@ -0,0 +1,86 @@ +#include "iosu_nn_service.h" +#include "../kernel/iosu_kernel.h" + +using namespace iosu::kernel; + +namespace iosu +{ + namespace nn + { + void IPCService::Start() + { + if (m_isRunning.exchange(true)) + return; + m_threadInitialized = false; + m_requestStop = false; + m_serviceThread = std::thread(&IPCService::ServiceThread, this); + while (!m_threadInitialized) std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + + void IPCService::Stop() + { + if (!m_isRunning.exchange(false)) + return; + m_requestStop = true; + IOS_SendMessage(m_msgQueueId, 0, 0); // wake up thread + m_serviceThread.join(); + } + + void IPCService::ServiceThread() + { + m_msgQueueId = IOS_CreateMessageQueue(_m_msgBuffer.GetPtr(), _m_msgBuffer.GetCount()); + cemu_assert(!IOS_ResultIsError((IOS_ERROR)m_msgQueueId)); + IOS_ERROR r = IOS_RegisterResourceManager(m_devicePath.c_str(), m_msgQueueId); + cemu_assert(!IOS_ResultIsError(r)); + m_threadInitialized = true; + while (true) + { + IOSMessage msg; + IOS_ERROR r = IOS_ReceiveMessage(m_msgQueueId, &msg, 0); + cemu_assert(!IOS_ResultIsError(r)); + if (msg == 0) + { + cemu_assert_debug(m_requestStop); + break; + } + IPCCommandBody* cmd = MEMPTR<IPCCommandBody>(msg).GetPtr(); + if (cmd->cmdId == IPCCommandId::IOS_OPEN) + { + IOS_ResourceReply(cmd, (IOS_ERROR)CreateClientHandle()); + continue; + } + else if (cmd->cmdId == IPCCommandId::IOS_CLOSE) + { + CloseClientHandle((IOSDevHandle)(uint32)cmd->devHandle); + IOS_ResourceReply(cmd, IOS_ERROR_OK); + continue; + } + else if (cmd->cmdId == IPCCommandId::IOS_IOCTLV) + { + uint32 requestId = cmd->args[0]; + uint32 numIn = cmd->args[1]; + uint32 numOut = cmd->args[2]; + IPCIoctlVector* vec = MEMPTR<IPCIoctlVector>{ cmd->args[3] }.GetPtr(); + IPCIoctlVector* vecIn = vec + numIn; + IPCIoctlVector* vecOut = vec + 0; + + cemu_assert(vecIn->size >= 80 && !vecIn->basePhys.IsNull()); + + IPCServiceRequest* serviceRequest = MEMPTR<IPCServiceRequest>(vecIn->basePhys).GetPtr(); + IPCServiceResponse* serviceResponse = MEMPTR<IPCServiceResponse>(vecOut->basePhys).GetPtr(); + + serviceResponse->nnResultCode = (uint32)ServiceCall(serviceRequest->serviceId, nullptr, nullptr); + IOS_ResourceReply(cmd, IOS_ERROR_OK); + continue; + } + else + { + cemuLog_log(LogType::Force, "{}: Unsupported cmdId", m_devicePath); + cemu_assert_unimplemented(); + IOS_ResourceReply(cmd, IOS_ERROR_INVALID); + } + } + m_threadInitialized = false; + } + }; +}; \ No newline at end of file diff --git a/src/Cafe/IOSU/nn/iosu_nn_service.h b/src/Cafe/IOSU/nn/iosu_nn_service.h new file mode 100644 index 00000000..7f06139f --- /dev/null +++ b/src/Cafe/IOSU/nn/iosu_nn_service.h @@ -0,0 +1,64 @@ +#pragma once +#include "Cafe/IOSU/kernel/iosu_kernel.h" +#include "Cafe/IOSU/iosu_ipc_common.h" +#include "Cafe/IOSU/iosu_types_common.h" +#include "Cafe/OS/libs/nn_common.h" + +namespace iosu +{ + namespace nn + { + struct IPCServiceRequest + { + uint32be ukn00; + uint32be serviceId; + uint32be ukn08; + uint32be commandId; + }; + + static_assert(sizeof(IPCServiceRequest) == 0x10); + + struct IPCServiceResponse + { + uint32be nnResultCode; + }; + + class IPCService + { + public: + IPCService(std::string_view devicePath) : m_devicePath(devicePath) {}; + virtual ~IPCService() {}; + + virtual IOSDevHandle CreateClientHandle() + { + return (IOSDevHandle)0; + } + + virtual void CloseClientHandle(IOSDevHandle handle) + { + + } + + virtual nnResult ServiceCall(uint32 serviceId, void* request, void* response) + { + cemu_assert_unimplemented(); + return 0; + } + + void Start(); + void Stop(); + + private: + void ServiceThread(); + + std::string m_devicePath; + std::thread m_serviceThread; + std::atomic_bool m_requestStop{false}; + std::atomic_bool m_isRunning{false}; + std::atomic_bool m_threadInitialized{ false }; + + IOSMsgQueueId m_msgQueueId; + SysAllocator<iosu::kernel::IOSMessage, 128> _m_msgBuffer; + }; + }; +}; \ No newline at end of file diff --git a/src/Cafe/OS/RPL/elf.cpp b/src/Cafe/OS/RPL/elf.cpp new file mode 100644 index 00000000..e4f4218d --- /dev/null +++ b/src/Cafe/OS/RPL/elf.cpp @@ -0,0 +1,129 @@ +#include <zlib.h> +#include "Cafe/OS/RPL/rpl.h" +#include "Cafe/OS/RPL/rpl_structs.h" +#include "util/VirtualHeap/VirtualHeap.h" +#include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h" +#include "Cafe/HW/Espresso/Debugger/Debugger.h" + +typedef struct +{ + /* +0x00 */ uint32be magic; + /* +0x04 */ uint8 eiClass; + /* +0x05 */ uint8 eiData; + /* +0x06 */ uint8 eiVersion; + /* +0x07 */ uint8 eiOSABI; + /* +0x08 */ uint8 eiOSABIVersion; + /* +0x09 */ uint8 eiPadding[7]; + /* +0x10 */ uint16be eType; + /* +0x12 */ uint16be eMachine; + /* +0x14 */ uint32be eVersion; + /* +0x18 */ uint32be entrypoint; + /* +0x1C */ uint32be phOffset; + /* +0x20 */ uint32be shOffset; + + /* +0x24 */ uint32be eFlags; + /* +0x28 */ uint16be eHeaderSize; + /* +0x2A */ uint16be ePHEntrySize; + /* +0x2C */ uint16be ePHNum; + /* +0x2E */ uint16be eSHEntrySize; + /* +0x30 */ uint16be eSHNum; + /* +0x32 */ uint16be eShStrIndex; +}elfHeader_t; + +static_assert(sizeof(elfHeader_t) == 0x34, ""); + +typedef struct +{ + /* +0x00 */ uint32be nameOffset; + /* +0x04 */ uint32be shType; + /* +0x08 */ uint32be shFlags; + /* +0x0C */ uint32be shAddr; + /* +0x10 */ uint32be shOffset; + /* +0x14 */ uint32be shSize; + + /* +0x18 */ uint32be shLink; + /* +0x1C */ uint32be shInfo; + + /* +0x20 */ uint32be shAddrAlign; + /* +0x24 */ uint32be shEntSize; +}elfSectionEntry_t; + +static_assert(sizeof(elfSectionEntry_t) == 0x28, ""); + +// Map elf into memory +uint32 ELF_LoadFromMemory(uint8* elfData, sint32 size, const char* name) +{ + elfHeader_t* header = (elfHeader_t*)elfData; + + uint32 sectionCount = header->eSHNum; + uint32 sectionTableOffset = header->shOffset; + uint32 sectionTableEntrySize = header->eSHEntrySize; + + elfSectionEntry_t* sectionTable = (elfSectionEntry_t*)(elfData + (uint32)header->shOffset); + memory_enableHBLELFCodeArea(); + for (uint32 i = 0; i < sectionCount; i++) + { + debug_printf("%02d Addr %08x Size %08x Offs %08x Flags %08x Type %08x EntSize %08x\n", i, (uint32)sectionTable[i].shAddr, (uint32)sectionTable[i].shSize, (uint32)sectionTable[i].shOffset, (uint32)sectionTable[i].shFlags, (uint32)sectionTable[i].shType, (uint32)sectionTable[i].shEntSize); + uint32 shAddr = (uint32)sectionTable[i].shAddr; + uint32 shSize = (uint32)sectionTable[i].shSize; + uint32 shOffset = (uint32)sectionTable[i].shOffset; + uint32 shType = (uint32)sectionTable[i].shType; + + if (shOffset > (uint32)size) + { + forceLog_printf("ELF section %d out of bounds", i); + continue; + } + uint32 copySize = std::min(shSize, size - shOffset); + + // 0x00802000 + if (shAddr != 0) + { + if (shType == SHT_NOBITS) + { + memset(memory_getPointerFromVirtualOffset(shAddr), 0, shSize); + } + else + { + memcpy(memory_getPointerFromVirtualOffset(shAddr), elfData + shOffset, copySize); + } + // SHT_NOBITS + } + } + return header->entrypoint; +} + +// From Homebrew Launcher: +//#define MEM_BASE (0x00800000) +//#define ELF_DATA_ADDR (*(volatile unsigned int*)(MEM_BASE + 0x1300 + 0x00)) +//#define ELF_DATA_SIZE (*(volatile unsigned int*)(MEM_BASE + 0x1300 + 0x04)) +//#define HBL_CHANNEL (*(volatile unsigned int*)(MEM_BASE + 0x1300 + 0x08)) +//#define RPX_MAX_SIZE (*(volatile unsigned int*)(MEM_BASE + 0x1300 + 0x0C)) +//#define RPX_MAX_CODE_SIZE (*(volatile unsigned int*)(MEM_BASE + 0x1300 + 0x10)) +//#define MAIN_ENTRY_ADDR (*(volatile unsigned int*)(MEM_BASE + 0x1400 + 0x00)) +//#define OS_FIRMWARE (*(volatile unsigned int*)(MEM_BASE + 0x1400 + 0x04)) +// +//#define OS_SPECIFICS ((OsSpecifics*)(MEM_BASE + 0x1500)) +// +//#define MEM_AREA_TABLE ((s_mem_area*)(MEM_BASE + 0x1600)) + +//typedef struct _OsSpecifics +//{ +// unsigned int addr_OSDynLoad_Acquire; +// unsigned int addr_OSDynLoad_FindExport; +// unsigned int addr_OSTitle_main_entry; +// +// unsigned int addr_KernSyscallTbl1; +// unsigned int addr_KernSyscallTbl2; +// unsigned int addr_KernSyscallTbl3; +// unsigned int addr_KernSyscallTbl4; +// unsigned int addr_KernSyscallTbl5; +// +// int(*LiWaitIopComplete)(int, int *); +// int(*LiWaitIopCompleteWithInterrupts)(int, int *); +// unsigned int addr_LiWaitOneChunk; +// unsigned int addr_PrepareTitle_hook; +// unsigned int addr_sgIsLoadingBuffer; +// unsigned int addr_gDynloadInitialized; +// unsigned int orig_LiWaitOneChunkInstr; +//} OsSpecifics; \ No newline at end of file diff --git a/src/Cafe/OS/RPL/rpl.cpp b/src/Cafe/OS/RPL/rpl.cpp new file mode 100644 index 00000000..07cf3d0d --- /dev/null +++ b/src/Cafe/OS/RPL/rpl.cpp @@ -0,0 +1,2389 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/Filesystem/fsc.h" +#include "Cafe/OS/RPL/rpl.h" +#include "Cafe/OS/RPL/rpl_structs.h" +#include "Cafe/OS/RPL/rpl_symbol_storage.h" +#include "util/VirtualHeap/VirtualHeap.h" +#include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h" +#include "Cafe/HW/Espresso/Debugger/Debugger.h" +#include "Cafe/GraphicPack/GraphicPack2.h" +#include "util/ChunkedHeap/ChunkedHeap.h" + +#include <zlib.h> + +#include "util/crypto/crc32.h" +#include "config/ActiveSettings.h" +#include "Cafe/OS/libs/coreinit/coreinit_DynLoad.h" +#include "gui/guiWrapper.h" + +class PPCCodeHeap : public VHeap +{ +public: + PPCCodeHeap(void* heapBase, uint32 heapSize) : VHeap(heapBase, heapSize) { }; + + void* alloc(uint32 size, uint32 alignment = 4) override + { + return VHeap::alloc(size, alignment); + } + + void free(void* addr) override + { + uint32 allocSize = getAllocationSizeFromAddr(addr); + MPTR ppcAddr = memory_getVirtualOffsetFromPointer(addr); + PPCRecompiler_invalidateRange(ppcAddr, ppcAddr + allocSize); + VHeap::free(addr); + } +}; + +VHeap rplLoaderHeap_workarea(nullptr, MEMORY_RPLLOADER_AREA_SIZE); +PPCCodeHeap rplLoaderHeap_lowerAreaCodeMem2(nullptr, MEMORY_CODE_TRAMPOLINE_AREA_SIZE); +PPCCodeHeap rplLoaderHeap_codeArea2(nullptr, MEMORY_CODEAREA_SIZE); + +bool rplLoader_applicationHasMemoryControl = false; +uint32 rplLoader_maxCodeAddress = 0; // highest used code address + +ChunkedFlatAllocator<64 * 1024> g_heapTrampolineArea; + +std::vector<rplDependency_t*> rplDependencyList = std::vector<rplDependency_t*>(); + +__declspec(dllexport) RPLModule* rplModuleList[256]; +__declspec(dllexport) sint32 rplModuleCount = 0; + +uint32 _currentTLSModuleIndex = 1; // value 0 is reserved + +uint32 rplLoader_sdataAddr = MPTR_NULL; // r13 +uint32 rplLoader_sdata2Addr = MPTR_NULL; // r2 + +std::map<void(*)(PPCInterpreter_t* hCPU), uint32> g_map_callableExports; + +struct RPLMappingRegion +{ + MPTR baseAddress; + uint32 endAddress; + uint32 calcEndAddress; // used to verify endAddress +}; + +struct RPLRegionMappingTable +{ + RPLMappingRegion region[4]; +}; + +#define RPL_MAPPING_REGION_DATA 0 +#define RPL_MAPPING_REGION_LOADERINFO 1 +#define RPL_MAPPING_REGION_TEXT 2 +#define RPL_MAPPING_REGION_TEMP 3 + +void RPLLoader_UnloadModule(RPLModule* rpl); +void RPLLoader_RemoveDependency(const char* name); + +char _ansiToLower(char c) +{ + if (c >= 'A' && c <= 'Z') + c -= ('A' - 'a'); + return c; +} + +uint8* RPLLoader_AllocateTrampolineCodeSpace(RPLModule* rplLoaderContext, sint32 size) +{ + if (rplLoaderContext) + { + // allocation owned by rpl + return (uint8*)rplLoaderContext->heapTrampolineArea.alloc(size, 4); + } + // allocation owned by global context + auto result = (uint8*)g_heapTrampolineArea.alloc(size, 4); + rplLoader_maxCodeAddress = std::max(rplLoader_maxCodeAddress, memory_getVirtualOffsetFromPointer(g_heapTrampolineArea.getCurrentBlockPtr()) + g_heapTrampolineArea.getCurrentBlockOffset()); + return result; +} + +uint8* RPLLoader_AllocateTrampolineCodeSpace(sint32 size) +{ + return RPLLoader_AllocateTrampolineCodeSpace(nullptr, size); +} + +MPTR RPLLoader_AllocateCodeSpace(uint32 size, uint32 alignment) +{ + cemu_assert_debug((alignment & (alignment - 1)) == 0); // alignment must be a power of 2 + MPTR codeAddr = memory_getVirtualOffsetFromPointer(rplLoaderHeap_codeArea2.alloc(size, alignment)); + rplLoader_maxCodeAddress = std::max(rplLoader_maxCodeAddress, codeAddr + size); + PPCRecompiler_allocateRange(codeAddr, size); + return codeAddr; +} + +uint32 rpl3_currentDataAllocatorAddr = 0x10000000; + +uint32 RPLLoader_AllocateDataSpace(RPLModule* rpl, uint32 size, uint32 alignment) +{ + if (rplLoader_applicationHasMemoryControl) + { + StackAllocator<uint32be> memPtr; + *(memPtr.GetPointer()) = 0; + PPCCoreCallback(rpl->funcAlloc.value(), size, alignment, memPtr.GetPointer()); + return (uint32)*(memPtr.GetPointer()); + } + rpl3_currentDataAllocatorAddr = (rpl3_currentDataAllocatorAddr + alignment - 1)&~(alignment-1); + uint32 mem = rpl3_currentDataAllocatorAddr; + rpl3_currentDataAllocatorAddr += size; + return mem; +} + +void RPLLoader_FreeData(RPLModule* rpl, void* ptr) +{ + PPCCoreCallback(rpl->funcFree.value(), ptr); +} + +uint32 RPLLoader_GetDataAllocatorAddr() +{ + return (rpl3_currentDataAllocatorAddr + 0xFFF)&(~0xFFF); +} + +uint32 RPLLoader_GetMaxCodeOffset() +{ + return rplLoader_maxCodeAddress; +} + +#define PPCASM_OPC_R_TEMPL_SIMM(_rD, _rA, _IMM) (((_rD)<<21)|((_rA)<<16)|((_IMM)&0xFFFF)) + +// generates 32-bit jump. Modifies R11 and CTR +MPTR _generateTrampolineFarJump(RPLModule* rplLoaderContext, MPTR destAddr) +{ + auto itr = rplLoaderContext->trampolineMap.find(destAddr); + if (itr != rplLoaderContext->trampolineMap.end()) + return itr->second; + + MPTR trampolineAddr = memory_getVirtualOffsetFromPointer(RPLLoader_AllocateTrampolineCodeSpace(rplLoaderContext, 4*4)); + uint32 destAddrU32 = (uint32)destAddr; + uint32 ppcOpcode = 0; + // ADDI R11, R0, ... + ppcOpcode = PPCASM_OPC_R_TEMPL_SIMM(11, 0, destAddrU32 & 0xFFFF); + ppcOpcode |= (14 << 26); + memory_writeU32(trampolineAddr + 0x0, ppcOpcode); + // ADDIS R11, R11, ...<<16 + ppcOpcode = PPCASM_OPC_R_TEMPL_SIMM(11, 11, ((destAddrU32 >> 16) + ((destAddrU32 >> 15) & 1)) & 0xFFFF); + ppcOpcode |= (15 << 26); + memory_writeU32(trampolineAddr + 0x4, ppcOpcode); + // MTCTR r11 + memory_writeU32(trampolineAddr + 0x8, 0x7D6903A6); + // BCTR + memory_writeU32(trampolineAddr + 0xC, 0x4E800420); + // if the destination is a known symbol, create a proxy (duplicate) symbol at the jump + rplSymbolStorage_createJumpProxySymbol(trampolineAddr, destAddr); + rplLoaderContext->trampolineMap.emplace(destAddr, trampolineAddr); + return trampolineAddr; +} + +void* RPLLoader_AllocWorkarea(uint32 size, uint32 alignment, uint32* allocSize) +{ + size = (size + 31)&~31; + *allocSize = size; + void* allocAddr = rplLoaderHeap_workarea.alloc(size, alignment); + cemu_assert(allocAddr != nullptr); + memset(allocAddr, 0, size); + return allocAddr; +} + +void RPLLoader_FreeWorkarea(void* allocAddr) +{ + rplLoaderHeap_workarea.free(allocAddr); +} + +bool RPLLoader_CheckBounds(RPLModule* rplLoaderContext, uint32 offset, uint32 size) +{ + if ((offset + size) > rplLoaderContext->RPLRawData.size_bytes()) + return false; + return true; +} + +bool RPLLoader_ProcessHeaders(std::string_view moduleName, uint8* rplData, uint32 rplSize, RPLModule** rplLoaderContextOut) +{ + rplHeaderNew_t* rplHeader = (rplHeaderNew_t*)rplData; + *rplLoaderContextOut = nullptr; + if (rplHeader->version04 != 0x01) + return false; + if (rplHeader->ukn05 != 0x02) + return false; + if (rplHeader->magic2_0 != 0xCA) + return false; + if (rplHeader->magic2_1 != 0xFE) + return false; + if (rplHeader->ukn06 > 1) + return false; + if (rplHeader->ukn12 != 0x14) + return false; + if (rplHeader->ukn14 != 0x01) + return false; + if (rplHeader->sectionTableEntryCount < 2) + return false; // RPL must end with two sections: CRCS + FILEINFO + // setup RPL info struct + RPLModule* rplLoaderContext = new RPLModule(); + rplLoaderContext->RPLRawData = std::span<uint8>(rplData, rplSize); + rplLoaderContext->rplData_depr = rplData; + rplLoaderContext->heapTrampolineArea.setBaseAllocator(&rplLoaderHeap_lowerAreaCodeMem2); + // load section table + if ((uint32)rplHeader->sectionTableEntrySize != sizeof(rplSectionEntryNew_t)) + assert_dbg(); + sint32 sectionCount = (sint32)rplHeader->sectionTableEntryCount; + sint32 sectionTableSize = (sint32)rplHeader->sectionTableEntrySize * sectionCount; + rplLoaderContext->sectionTablePtr = (rplSectionEntryNew_t*)malloc(sectionTableSize); + memcpy(rplLoaderContext->sectionTablePtr, rplData + (uint32)(rplHeader->sectionTableOffset), sectionTableSize); + // copy rpl header + memcpy(&rplLoaderContext->rplHeader, rplHeader, sizeof(rplHeaderNew_t)); + // verify that section n-1 is FILEINFO + rplSectionEntryNew_t* fileinfoSection = rplLoaderContext->sectionTablePtr + ((uint32)rplLoaderContext->rplHeader.sectionTableEntryCount - 1); + if (fileinfoSection->fileOffset == 0 || (uint32)fileinfoSection->fileOffset >= rplSize || (uint32)fileinfoSection->type != SHT_RPL_FILEINFO) + { + forceLogDebug_printf("RPLLoader: Last section not FILEINFO"); + } + // verify that section n-2 is CRCs + rplSectionEntryNew_t* crcSection = rplLoaderContext->sectionTablePtr + ((uint32)rplLoaderContext->rplHeader.sectionTableEntryCount - 2); + if (crcSection->fileOffset == 0 || (uint32)crcSection->fileOffset >= rplSize || (uint32)crcSection->type != SHT_RPL_CRCS) + { + forceLogDebug_printf("RPLLoader: The section before FILEINFO must be CRCs"); + } + // load FILEINFO section + if (fileinfoSection->sectionSize < sizeof(RPLFileInfoData)) + { + cemuLog_force("RPLLoader: FILEINFO section size is below expected size"); + return false; + } + + // read RPL mapping info + uint8* fileInfoRawPtr = (uint8*)(rplData + fileinfoSection->fileOffset); + if (((uint64)fileinfoSection->fileOffset+fileinfoSection->sectionSize) > (uint64)rplSize) + { + cemuLog_force("RPLLoader: FILEINFO section outside of RPL file bounds"); + return false; + } + rplLoaderContext->sectionData_fileInfo.resize(fileinfoSection->sectionSize); + memcpy(rplLoaderContext->sectionData_fileInfo.data(), fileInfoRawPtr, rplLoaderContext->sectionData_fileInfo.size()); + + RPLFileInfoData* fileInfoPtr = (RPLFileInfoData*)rplLoaderContext->sectionData_fileInfo.data(); + if (fileInfoPtr->fileInfoMagic != 0xCAFE0402) + { + cemuLog_force("RPLLoader: Invalid FILEINFO magic"); + return false; + } + + // process FILEINFO + rplLoaderContext->fileInfo.textRegionSize = fileInfoPtr->textRegionSize; + rplLoaderContext->fileInfo.dataRegionSize = fileInfoPtr->dataRegionSize; + rplLoaderContext->fileInfo.baseAlign = fileInfoPtr->baseAlign; + rplLoaderContext->fileInfo.ukn14 = fileInfoPtr->ukn14; + rplLoaderContext->fileInfo.trampolineAdjustment = fileInfoPtr->trampolineAdjustment; + rplLoaderContext->fileInfo.ukn4C = fileInfoPtr->ukn4C; + rplLoaderContext->fileInfo.tlsModuleIndex = fileInfoPtr->tlsModuleIndex; + rplLoaderContext->fileInfo.sdataBase1 = fileInfoPtr->sdataBase1; + rplLoaderContext->fileInfo.sdataBase2 = fileInfoPtr->sdataBase2; + + // init section address table + rplLoaderContext->sectionAddressTable2.resize(sectionCount); + // init modulename + rplLoaderContext->moduleName2.assign(moduleName); + // convert modulename to lower-case + for(auto& c : rplLoaderContext->moduleName2) + c = _ansiToLower(c); + // cemuhook compatibility + rplLoaderContext->moduleNamePtr__depr = rplLoaderContext->moduleName2.data(); + rplLoaderContext->moduleNameLength__depr = rplLoaderContext->moduleName2.size(); + rplLoaderContext->moduleNameSize = 0; + rplLoaderContext->sectionAddressTable__depr = rplLoaderContext->sectionAddressTable2.data(); + rplLoaderContext->sectionAddressTableSize__depr = rplLoaderContext->sectionAddressTable2.size() * sizeof(rplSectionAddressEntry_t); + + // load CRC section + uint32 crcTableExpectedSize = sectionCount * sizeof(uint32be); + if (!RPLLoader_CheckBounds(rplLoaderContext, crcSection->fileOffset, crcTableExpectedSize)) + { + cemuLog_force("RPLLoader: CRC section outside of RPL file bounds"); + crcSection->sectionSize = 0; + } + else if (crcSection->sectionSize < crcTableExpectedSize) + { + cemuLog_force("RPLLoader: CRC section size (0x{:x}) less than required (0x{:x})", (uint32)crcSection->sectionSize, crcTableExpectedSize); + } + else if (crcSection->sectionSize != crcTableExpectedSize) + { + cemuLog_force("RPLLoader: CRC section size (0x{:x}) does not match expected size (0x{:x})", (uint32)crcSection->sectionSize, crcTableExpectedSize); + } + + uint32 crcActualSectionCount = crcSection->sectionSize / sizeof(uint32); // how many CRCs are actually stored + + rplLoaderContext->crcTable.resize(sectionCount); + if (crcActualSectionCount > 0) + { + uint32be* crcTableData = (uint32be*)(rplData + crcSection->fileOffset); + for (uint32 i = 0; i < crcActualSectionCount; i++) + rplLoaderContext->crcTable[i] = crcTableData[i]; + } + + // verify CRC of FILEINFO section + uint32 crcCalcFileinfo = crc32_calc(0, rplLoaderContext->sectionData_fileInfo.data(), rplLoaderContext->sectionData_fileInfo.size()); + uint32 crcFileinfo = rplLoaderContext->GetSectionCRC(sectionCount - 1); + if (crcCalcFileinfo != crcFileinfo) + { + cemuLog_force("RPLLoader: FILEINFO section has CRC mismatch - Calculated: {:08x} Actual: {:08x}", crcCalcFileinfo, crcFileinfo); + } + + rplLoaderContext->sectionAddressTable2[sectionCount - 1].ptr = rplLoaderContext->sectionData_fileInfo.data(); + rplLoaderContext->sectionAddressTable2[sectionCount - 2].ptr = nullptr;// rplLoaderContext->crcTablePtr; + + // set output + *rplLoaderContextOut = rplLoaderContext; + return true; +} + +class RPLUncompressedSection +{ +public: + std::vector<uint8> sectionData; +}; + +rplSectionEntryNew_t* RPLLoader_GetSection(RPLModule* rplLoaderContext, sint32 sectionIndex) +{ + sint32 sectionCount = rplLoaderContext->rplHeader.sectionTableEntryCount; + if (sectionIndex < 0 || sectionIndex >= sectionCount) + { + forceLog_printf("RPLLoader: Section index out of bounds"); + rplLoaderContext->hasError = true; + return nullptr; + } + rplSectionEntryNew_t* section = rplLoaderContext->sectionTablePtr + sectionIndex; + return section; +} + +RPLUncompressedSection* RPLLoader_LoadUncompressedSection(RPLModule* rplLoaderContext, sint32 sectionIndex) +{ + const rplSectionEntryNew_t* section = RPLLoader_GetSection(rplLoaderContext, sectionIndex); + if (section == nullptr) + return nullptr; + + RPLUncompressedSection* uSection = new RPLUncompressedSection(); + + if ((uint32)section->type == 0x8) + { + uSection->sectionData.resize(section->sectionSize); + std::fill(uSection->sectionData.begin(), uSection->sectionData.end(), 0); + return uSection; + } + + // check if raw size does not exceed bounds of rpl + if (!RPLLoader_CheckBounds(rplLoaderContext, section->fileOffset, section->sectionSize)) + { + // BSS + forceLog_printf("RPLLoader: Raw data for section %d exceeds bounds of RPL file", sectionIndex); + rplLoaderContext->hasError = true; + delete uSection; + return nullptr; + } + + uint32 sectionFlags = section->flags; + if ((sectionFlags & SHF_RPL_COMPRESSED) != 0) + { + // decompress + if (!RPLLoader_CheckBounds(rplLoaderContext, section->fileOffset, sizeof(uint32be)) ) + { + forceLog_printf("RPLLoader: Uncompressed data of section %d is too large", sectionIndex); + rplLoaderContext->hasError = true; + delete uSection; + return nullptr; + } + uint32 uncompressedSize = *(uint32be*)(rplLoaderContext->RPLRawData.data() + (uint32)section->fileOffset); + if (uncompressedSize >= 1*1024*1024*1024) // sections bigger than 1GB not allowed + { + forceLog_printf("RPLLoader: Uncompressed data of section %d is too large", sectionIndex); + rplLoaderContext->hasError = true; + delete uSection; + return nullptr; + } + int ret; + z_stream strm; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + ret = inflateInit(&strm); + if (ret == Z_OK) + { + strm.avail_in = (uint32)section->sectionSize - 4; + strm.next_in = rplLoaderContext->RPLRawData.data() + (uint32)section->fileOffset + 4; + strm.avail_out = uncompressedSize; + uSection->sectionData.resize(uncompressedSize); + strm.next_out = uSection->sectionData.data(); + ret = inflate(&strm, Z_FULL_FLUSH); + inflateEnd(&strm); + if ((ret != Z_OK && ret != Z_STREAM_END) || strm.avail_in != 0 || strm.avail_out != 0) + { + forceLog_printf("RPLLoader: Error while inflating data for section %d", sectionIndex); + rplLoaderContext->hasError = true; + delete uSection; + return nullptr; + } + } + } + else + { + // no decompression + uSection->sectionData.resize(section->sectionSize); + const uint8* sectionDataBegin = rplLoaderContext->RPLRawData.data() + (uint32)section->fileOffset; + std::copy(sectionDataBegin, sectionDataBegin + section->sectionSize, uSection->sectionData.data()); + } + return uSection; +} + +bool RPLLoader_LoadSingleSection(RPLModule* rplLoaderContext, sint32 sectionIndex, RPLMappingRegion* regionMappingInfo, MPTR mappedAddress) +{ + rplSectionEntryNew_t* section = RPLLoader_GetSection(rplLoaderContext, sectionIndex); + if (section == nullptr) + return false; + + uint32 mappingOffset = (uint32)section->virtualAddress - (uint32)regionMappingInfo->baseAddress; + if (mappingOffset >= 0x10000000) + forceLogDebug_printf("Suspicious section mapping offset: 0x%08x", mappingOffset); + uint32 sectionAddress = mappedAddress + mappingOffset; + + rplLoaderContext->sectionAddressTable2[sectionIndex].ptr = memory_getPointerFromVirtualOffset(sectionAddress); + + cemu_assert(rplLoaderContext->debugSectionLoadMask[sectionIndex] == false); + rplLoaderContext->debugSectionLoadMask[sectionIndex] = true; + + // extract section + RPLUncompressedSection* uncompressedSection = RPLLoader_LoadUncompressedSection(rplLoaderContext, sectionIndex); + if (uncompressedSection == nullptr) + { + rplLoaderContext->hasError = true; + return false; + } + + // copy to mapped address + if(section->virtualAddress < regionMappingInfo->baseAddress || (section->virtualAddress + uncompressedSection->sectionData.size()) > regionMappingInfo->endAddress) + cemuLog_force("RPLLoader: Section {} (0x{:08x} to 0x{:08x}) is not fully contained in it's bounding region (0x{:08x} to 0x{:08x})", sectionIndex, section->virtualAddress, section->virtualAddress + uncompressedSection->sectionData.size(), regionMappingInfo->baseAddress, regionMappingInfo->endAddress); + uint8* sectionAddressPtr = memory_getPointerFromVirtualOffset(sectionAddress); + std::copy(uncompressedSection->sectionData.begin(), uncompressedSection->sectionData.end(), sectionAddressPtr); + + // update size in section (todo - use separate field) + if (uncompressedSection->sectionData.size() < section->sectionSize) + forceLog_printf("RPLLoader: Section %d uncompresses to %d bytes but sectionSize is %d", sectionIndex, uncompressedSection->sectionData.size(), (uint32)section->sectionSize); + + section->sectionSize = uncompressedSection->sectionData.size(); + + delete uncompressedSection; + return true; +} + +bool RPLLoader_LoadSections(sint32 aProcId, RPLModule* rplLoaderContext) +{ + RPLRegionMappingTable regionMappingTable; + memset(®ionMappingTable, 0, sizeof(RPLRegionMappingTable)); + regionMappingTable.region[0].baseAddress = 0xFFFFFFFF; + regionMappingTable.region[1].baseAddress = 0xFFFFFFFF; + regionMappingTable.region[2].baseAddress = 0xFFFFFFFF; + regionMappingTable.region[3].baseAddress = 0xFFFFFFFF; + for (sint32 i = 0; i < (sint32)rplLoaderContext->rplHeader.sectionTableEntryCount; i++) + { + rplSectionEntryNew_t* section = rplLoaderContext->sectionTablePtr + i; + uint32 sectionType = section->type; + uint32 sectionFlags = section->flags; + uint32 sectionVirtualAddr = section->virtualAddress; + uint32 sectionFileOffset = section->fileOffset; + uint32 sectionSize = section->sectionSize; + if(sectionSize == 0) + continue; + if (sectionType == SHT_RPL_CRCS) + continue; + if (sectionType == SHT_RPL_FILEINFO) + continue; + //if (sectionType == SHT_RPL_IMPORTS) -> The official loader seems to skip these, leading to incorrect boundary calculations + // continue; + if ((sectionFlags & 2) == 0) + { + uint32 endFileOffset = sectionFileOffset + sectionSize; + regionMappingTable.region[RPL_MAPPING_REGION_TEMP].baseAddress = std::min(regionMappingTable.region[RPL_MAPPING_REGION_TEMP].baseAddress, sectionFileOffset); + regionMappingTable.region[RPL_MAPPING_REGION_TEMP].endAddress = std::max(regionMappingTable.region[RPL_MAPPING_REGION_TEMP].endAddress, endFileOffset); + continue; + } + if ((sectionFlags & 4) != 0 && sectionType != SHT_RPL_EXPORTS && sectionType != SHT_RPL_IMPORTS) + { + regionMappingTable.region[RPL_MAPPING_REGION_TEXT].baseAddress = std::min(regionMappingTable.region[RPL_MAPPING_REGION_TEXT].baseAddress, sectionVirtualAddr); + continue; + } + if ((sectionFlags & 1) != 0) + { + regionMappingTable.region[RPL_MAPPING_REGION_DATA].baseAddress = std::min(regionMappingTable.region[RPL_MAPPING_REGION_DATA].baseAddress, sectionVirtualAddr); + continue; + } + else + { + regionMappingTable.region[RPL_MAPPING_REGION_LOADERINFO].baseAddress = std::min(regionMappingTable.region[RPL_MAPPING_REGION_LOADERINFO].baseAddress, sectionVirtualAddr); + continue; + + } + } + for (sint32 i = 0; i < 4; i++) + { + if (regionMappingTable.region[i].baseAddress == 0xFFFFFFFF) + regionMappingTable.region[i].baseAddress = 0; + } + regionMappingTable.region[RPL_MAPPING_REGION_TEXT].endAddress = (regionMappingTable.region[RPL_MAPPING_REGION_TEXT].baseAddress + rplLoaderContext->fileInfo.textRegionSize) - rplLoaderContext->fileInfo.trampolineAdjustment; + regionMappingTable.region[RPL_MAPPING_REGION_DATA].endAddress = regionMappingTable.region[RPL_MAPPING_REGION_DATA].baseAddress + rplLoaderContext->fileInfo.dataRegionSize; + regionMappingTable.region[RPL_MAPPING_REGION_LOADERINFO].endAddress = (regionMappingTable.region[RPL_MAPPING_REGION_LOADERINFO].baseAddress + rplLoaderContext->fileInfo.ukn14) - rplLoaderContext->fileInfo.ukn4C; + + // calculate region size + uint32 regionDataSize = regionMappingTable.region[RPL_MAPPING_REGION_DATA].endAddress - regionMappingTable.region[RPL_MAPPING_REGION_DATA].baseAddress; + uint32 regionLoaderinfoSize = regionMappingTable.region[RPL_MAPPING_REGION_LOADERINFO].endAddress - regionMappingTable.region[RPL_MAPPING_REGION_LOADERINFO].baseAddress; + uint32 regionTextSize = regionMappingTable.region[RPL_MAPPING_REGION_TEXT].endAddress - regionMappingTable.region[RPL_MAPPING_REGION_TEXT].baseAddress; + + rplLoaderContext->regionMappingBase_data = RPLLoader_AllocateDataSpace(rplLoaderContext, regionDataSize, 0x1000); + rplLoaderContext->regionMappingBase_loaderInfo = RPLLoader_AllocateDataSpace(rplLoaderContext, regionLoaderinfoSize, 0x1000); + rplLoaderContext->regionMappingBase_text = rplLoaderHeap_codeArea2.alloc(regionTextSize + 0x1000, 0x1000); + rplLoader_maxCodeAddress = std::max(rplLoader_maxCodeAddress, rplLoaderContext->regionMappingBase_text.GetMPTR() + regionTextSize + 0x1000); + PPCRecompiler_allocateRange(rplLoaderContext->regionMappingBase_text.GetMPTR(), regionTextSize + 0x1000); + + // workaround for DKC Tropical Freeze + if (boost::iequals(rplLoaderContext->moduleName2, "rs10_production")) + { + // allocate additional 12MB of unused data to get below a size of 0x3E200000 for the main ExpHeap + // otherwise the game will assume it's running on a Devkit unit with 2GB of RAM and subtract 1GB from available space + RPLLoader_AllocateDataSpace(rplLoaderContext, 12*1024*1024, 0x1000); + } + // set region sizes + rplLoaderContext->regionSize_data = regionDataSize; + rplLoaderContext->regionSize_loaderInfo = regionLoaderinfoSize; + rplLoaderContext->regionSize_text = regionTextSize; + + // load data sections + for (sint32 i = 0; i < (sint32)rplLoaderContext->rplHeader.sectionTableEntryCount; i++) + { + rplSectionEntryNew_t* section = rplLoaderContext->sectionTablePtr + i; + uint32 sectionType = section->type; + uint32 sectionFlags = section->flags; + if (section->sectionSize == 0) + continue; + if( rplLoaderContext->sectionAddressTable2[i].ptr != nullptr ) + continue; + if ((sectionFlags & 2) == 0) + continue; + if ((sectionFlags & 1) == 0) + continue; + + RPLLoader_LoadSingleSection(rplLoaderContext, i, regionMappingTable.region + RPL_MAPPING_REGION_DATA, rplLoaderContext->regionMappingBase_data); + } + // load loaderinfo sections + for (sint32 i = 0; i < (sint32)rplLoaderContext->rplHeader.sectionTableEntryCount; i++) + { + rplSectionEntryNew_t* section = rplLoaderContext->sectionTablePtr + i; + uint32 sectionType = section->type; + uint32 sectionFlags = section->flags; + if (section->sectionSize == 0) + continue; + if (rplLoaderContext->sectionAddressTable2[i].ptr != nullptr) + continue; + if ((sectionFlags & 2) == 0) + continue; + if(sectionType != SHT_RPL_EXPORTS && sectionType != SHT_RPL_IMPORTS && (sectionFlags&5) != 0 ) + continue; + bool readRaw = false; + + RPLLoader_LoadSingleSection(rplLoaderContext, i, regionMappingTable.region + RPL_MAPPING_REGION_LOADERINFO, rplLoaderContext->regionMappingBase_loaderInfo); + + if (sectionType == SHT_RPL_EXPORTS) + { + uint8* sectionAddress = (uint8*)rplLoaderContext->sectionAddressTable2[i].ptr; + if ((sectionFlags & 4) != 0) + { + rplLoaderContext->exportFCount = *(uint32be*)(sectionAddress + 0); + rplLoaderContext->exportFDataPtr = (rplExportTableEntry_t*)(sectionAddress + 8); + } + else + { + rplLoaderContext->exportDCount = *(uint32be*)(sectionAddress + 0); + rplLoaderContext->exportDDataPtr = (rplExportTableEntry_t*)(sectionAddress + 8); + } + } + } + // load text sections + uint32 textSectionMappedBase = rplLoaderContext->regionMappingBase_text.GetMPTR() + (uint32)rplLoaderContext->fileInfo.trampolineAdjustment; // leave some space for trampolines before the code section begins + for (sint32 i = 0; i < (sint32)rplLoaderContext->rplHeader.sectionTableEntryCount; i++) + { + rplSectionEntryNew_t* section = rplLoaderContext->sectionTablePtr + i; + uint32 sectionType = section->type; + uint32 sectionFlags = section->flags; + if( section->sectionSize == 0 ) + continue; + if (rplLoaderContext->sectionAddressTable2[i].ptr != nullptr) + continue; + if ((sectionFlags & 2) == 0) + continue; + if ((sectionFlags & 4) == 0) + continue; + if( sectionType == SHT_RPL_EXPORTS) + continue; + + if (section->type == 0x8) + { + cemuLog_force("RPLLoader: Unsupported text section type 0x8"); + cemu_assert_debug(false); + } + + RPLLoader_LoadSingleSection(rplLoaderContext, i, regionMappingTable.region + RPL_MAPPING_REGION_TEXT, textSectionMappedBase); + } + // load temp region sections + uint32 tempRegionSize = regionMappingTable.region[RPL_MAPPING_REGION_TEMP].endAddress - regionMappingTable.region[RPL_MAPPING_REGION_TEMP].baseAddress; + uint8* tempRegionPtr; + uint32 tempRegionAllocSize = 0; + tempRegionPtr = (uint8*)RPLLoader_AllocWorkarea(tempRegionSize, 0x20, &tempRegionAllocSize); + rplLoaderContext->tempRegionPtr = tempRegionPtr; + rplLoaderContext->tempRegionAllocSize = tempRegionAllocSize; + memcpy(tempRegionPtr, rplLoaderContext->RPLRawData.data()+regionMappingTable.region[RPL_MAPPING_REGION_TEMP].baseAddress, tempRegionSize); + // load temp region sections + for (sint32 i = 0; i < (sint32)rplLoaderContext->rplHeader.sectionTableEntryCount; i++) + { + rplSectionEntryNew_t* section = rplLoaderContext->sectionTablePtr + i; + uint32 sectionType = section->type; + uint32 sectionFlags = section->flags; + if (section->sectionSize == 0) + continue; + if (rplLoaderContext->sectionAddressTable2[i].ptr != nullptr) + continue; + if (sectionType == SHT_RPL_FILEINFO || sectionType == SHT_RPL_CRCS) + continue; + // calculate offset within temp section + uint32 sectionFileOffset = section->fileOffset; + uint32 sectionSize = section->sectionSize; + cemu_assert_debug(sectionFileOffset >= regionMappingTable.region[RPL_MAPPING_REGION_TEMP].baseAddress); + cemu_assert_debug((sectionFileOffset + sectionSize) <= regionMappingTable.region[RPL_MAPPING_REGION_TEMP].endAddress); + rplLoaderContext->sectionAddressTable2[i].ptr = (tempRegionPtr + (sectionFileOffset - regionMappingTable.region[RPL_MAPPING_REGION_TEMP].baseAddress)); + + uint32 sectionEndAddress = sectionFileOffset + sectionSize; + regionMappingTable.region[RPL_MAPPING_REGION_TEMP].calcEndAddress = std::max(regionMappingTable.region[RPL_MAPPING_REGION_TEMP].calcEndAddress, sectionEndAddress); + } + // todo: Verify calcEndAddress<=endAddress for each region + + // dump loaded sections + /* + for (sint32 i = 0; i < (sint32)rplLoaderContext->rplHeader.sectionTableEntryCount; i++) + { + rplSectionEntryNew_t* section = rplLoaderContext->sectionTablePtr + i; + uint32 sectionType = section->type; + uint32 sectionFlags = section->flags; + if (section->sectionSize == 0) + continue; + if (rplLoaderContext->sectionAddressTable2[i].ptr == nullptr) + continue; + FileStream* fs = FileStream::createFile2(fmt::format("dump/rpl_sections/{}_{:08x}_type{:08x}.bin", i, (uint32)section->virtualAddress, (uint32)sectionType)); + fs->writeData(rplLoaderContext->sectionAddressTable2[i].ptr, section->sectionSize); + delete fs; + } + */ + return true; +} + +struct RPLFileSymtabEntry +{ + /* +0x0 */ uint32be ukn00; + /* +0x4 */ uint32be symbolAddress; + /* +0x8 */ uint32be ukn08; + /* +0xC */ uint8 info; + /* +0xD */ uint8 ukn0D; + /* +0xE */ uint16be sectionIndex; +}; + +struct RPLSharedImportTracking +{ + RPLModule* rplLoaderContext; // rpl loader context of module with exports + rplSectionEntryNew_t* exportSection; // export section + char modulename[RPL_MODULE_NAME_LENGTH]; +}; + +static_assert(sizeof(RPLFileSymtabEntry) == 0x10, "rplSymtabEntry_t has invalid size"); + +typedef struct +{ + uint64 hash1; + uint64 hash2; + uint32 address; +}mappedFunctionImport_t; + +std::vector<mappedFunctionImport_t> list_mappedFunctionImports = std::vector<mappedFunctionImport_t>(); + +void _calculateMappedImportNameHash(const char* rplName, const char* funcName, uint64* h1Out, uint64* h2Out) +{ + uint64 h1 = 0; + uint64 h2 = 0; + // rplName + while (*rplName) + { + uint64 v = (uint64)*rplName; + h1 += v; + h2 ^= v; + h1 = _rotl64(h1, 3); + h2 = _rotl64(h2, 7); + rplName++; + } + // funcName + while (*funcName) + { + uint64 v = (uint64)*funcName; + h1 += v; + h2 ^= v; + h1 = _rotl64(h1, 3); + h2 = _rotl64(h2, 7); + funcName++; + } + *h1Out = h1; + *h2Out = h2; +} + +uint32 RPLLoader_MakePPCCallable(void(*ppcCallableExport)(PPCInterpreter_t* hCPU)) +{ + auto it = g_map_callableExports.find(ppcCallableExport); + if (it != g_map_callableExports.end()) + return it->second; + // get HLE function index + sint32 functionIndex = PPCInterpreter_registerHLECall(ppcCallableExport); + MPTR codeAddr = memory_getVirtualOffsetFromPointer(RPLLoader_AllocateTrampolineCodeSpace(4)); + uint32 opcode = (1 << 26) | functionIndex; + memory_writeU32Direct(codeAddr, opcode); + g_map_callableExports[ppcCallableExport] = codeAddr; + return codeAddr; +} + +uint32 rpl_mapHLEImport(RPLModule* rplLoaderContext, const char* rplName, const char* funcName, bool functionMustExist) +{ + // calculate import name hash + uint64 mappedImportHash1; + uint64 mappedImportHash2; + _calculateMappedImportNameHash(rplName, funcName, &mappedImportHash1, &mappedImportHash2); + // find already mapped name + for (auto& importItr : list_mappedFunctionImports) + { + if (importItr.hash1 == mappedImportHash1 && importItr.hash2 == mappedImportHash2) + { + return importItr.address; + } + } + // copy lib file name and cut off .rpl from libName if present + char libName[512]; + strcpy_s(libName, rplName); + for (sint32 i = 0; i < sizeof(libName); i++) + { + if (libName[i] == '\0') + break; + if (libName[i] == '.') + { + libName[i] = '\0'; + break; + } + } + // find import in list of known exports + sint32 functionIndex = osLib_getFunctionIndex(libName, funcName); + if (functionIndex >= 0) + { + MPTR codeAddr = memory_getVirtualOffsetFromPointer(RPLLoader_AllocateTrampolineCodeSpace(4)); + uint32 opcode = (1 << 26) | functionIndex; + memory_writeU32Direct(codeAddr, opcode); + // register mapped import + mappedFunctionImport_t newImport; + newImport.hash1 = mappedImportHash1; + newImport.hash2 = mappedImportHash2; + newImport.address = codeAddr; + list_mappedFunctionImports.push_back(newImport); + // remember in symbol storage for debugger + rplSymbolStorage_store(libName, funcName, codeAddr); + return codeAddr; + } + else + { + if (functionMustExist == false) + return MPTR_NULL; + } + // create code for unsupported import + uint32 codeStart = memory_getVirtualOffsetFromPointer(RPLLoader_AllocateTrampolineCodeSpace(256)); + uint32 currentAddress = codeStart; + uint32 opcode = (1 << 26) | (0xFFD0); // opcode for HLE: Unsupported import + memory_writeU32Direct(codeStart + 0, opcode); + memory_writeU32Direct(codeStart + 4, 0x4E800020); + currentAddress += 8; + // write name of lib/function + sint32 libNameLength = std::min(128, (sint32)strlen(libName)); + sint32 funcNameLength = std::min(128, (sint32)strlen(funcName)); + memcpy(memory_getPointerFromVirtualOffset(currentAddress), libName, libNameLength); + currentAddress += libNameLength; + memory_writeU8(currentAddress, '.'); + currentAddress++; + memcpy(memory_getPointerFromVirtualOffset(currentAddress), funcName, funcNameLength); + currentAddress += funcNameLength; + memory_writeU8(currentAddress, '\0'); + currentAddress++; + // align address to 4 byte boundary + currentAddress = (currentAddress + 3)&~3; + // register mapped import + mappedFunctionImport_t newImport; + newImport.hash1 = mappedImportHash1; + newImport.hash2 = mappedImportHash2; + newImport.address = codeStart; + list_mappedFunctionImports.push_back(newImport); + // remember in symbol storage for debugger + rplSymbolStorage_store(libName, funcName, codeStart); + // return address of code start + return codeStart; +} + +MPTR RPLLoader_FindRPLExport(RPLModule* rplLoaderContext, const char* symbolName, bool isData) +{ + if (isData) + { + cemu_assert_debug(false); + // todo - look in DDataPtr + } + if (rplLoaderContext->exportFDataPtr) + { + char* exportNameData = (char*)((uint8*)rplLoaderContext->exportFDataPtr - 8); + for (uint32 f = 0; f < rplLoaderContext->exportFCount; f++) + { + char* name = exportNameData + (uint32)rplLoaderContext->exportFDataPtr[f].nameOffset; + if (strcmp(name, symbolName) == 0) + { + return (uint32)rplLoaderContext->exportFDataPtr[f].virtualOffset; + } + } + } + return MPTR_NULL; +} + +MPTR _findHLEExport(RPLModule* rplLoaderContext, RPLSharedImportTracking* sharedImportTrackingEntry, char* libname, char* symbolName, bool isData) +{ + if (isData) + { + // data export + MPTR weakExportAddr = osLib_getPointer(libname, symbolName); + if (weakExportAddr != 0xFFFFFFFF) + return weakExportAddr; + cemuLog_logDebug(LogType::Force, "Unsupported data export ({}): {}.{}", rplLoaderContext->moduleName2, libname, symbolName); + return MPTR_NULL; + } + else + { + // try to find HLE/placeholder export + MPTR mappedFunctionAddr = rpl_mapHLEImport(rplLoaderContext, libname, symbolName, true); + if (mappedFunctionAddr == MPTR_NULL) + cemu_assert_debug(false); + return mappedFunctionAddr; + } +} + +uint32 RPLLoader_FindModuleExport(RPLModule* rplLoaderContext, bool isData, const char* exportName) +{ + if (isData == false) + { + // find function export + char* exportNameData = (char*)((uint8*)rplLoaderContext->exportFDataPtr - 8); + for (uint32 f = 0; f < rplLoaderContext->exportFCount; f++) + { + char* name = exportNameData + (uint32)rplLoaderContext->exportFDataPtr[f].nameOffset; + if (strcmp(name, exportName) == 0) + { + uint32 exportAddress = rplLoaderContext->exportFDataPtr[f].virtualOffset; + return exportAddress; + } + } + } + else + { + // find data export + char* exportNameData = (char*)((uint8*)rplLoaderContext->exportDDataPtr - 8); + for (uint32 f = 0; f < rplLoaderContext->exportDCount; f++) + { + char* name = exportNameData + (uint32)rplLoaderContext->exportDDataPtr[f].nameOffset; + if (strcmp(name, exportName) == 0) + { + uint32 exportAddress = rplLoaderContext->exportDDataPtr[f].virtualOffset; + return exportAddress; + } + } + } + return 0; +} + +bool RPLLoader_FixImportSymbols(RPLModule* rplLoaderContext, sint32 symtabSectionIndex, rplSectionEntryNew_t* symTabSection, std::span<RPLSharedImportTracking> sharedImportTracking, uint32 linkMode) +{ + uint32 sectionSize = symTabSection->sectionSize; + uint32 symbolEntrySize = symTabSection->ukn24; + if (symbolEntrySize == 0) + symbolEntrySize = 0x10; + cemu_assert(symbolEntrySize == 0x10); + cemu_assert((sectionSize % symbolEntrySize) == 0); + uint32 symbolCount = sectionSize / symbolEntrySize; + cemu_assert(symbolCount >= 2); + + uint16 sectionCount = rplLoaderContext->rplHeader.sectionTableEntryCount; + uint8* symtabData = (uint8*)rplLoaderContext->sectionAddressTable2[symtabSectionIndex].ptr; + + uint32 strtabSectionIndex = symTabSection->symtabSectionIndex; + uint8* strtabData = (uint8*)rplLoaderContext->sectionAddressTable2[strtabSectionIndex].ptr; + uint32 strtabSize = rplLoaderContext->sectionTablePtr[strtabSectionIndex].sectionSize; + + for (uint32 i = 0; i < symbolCount; i++) + { + RPLFileSymtabEntry* sym = (RPLFileSymtabEntry*)(symtabData + i*symbolEntrySize); + uint16 symSectionIndex = sym->sectionIndex; + if (symSectionIndex == 0 || symSectionIndex >= sectionCount) + continue; + void* symbolSectionAddress = rplLoaderContext->sectionAddressTable2[symSectionIndex].ptr; + if (symbolSectionAddress == nullptr) + { + sym->symbolAddress = 0xCD000000 | i; + continue; + } + rplSectionEntryNew_t* symbolSection = rplLoaderContext->sectionTablePtr + symSectionIndex; + uint32 symbolOffset = sym->symbolAddress - symbolSection->virtualAddress; + + if (symSectionIndex >= sharedImportTracking.size()) + { + cemuLog_force("RPL-Loader: Symbol {} references invalid section", i); + } + else if (sharedImportTracking[symSectionIndex].rplLoaderContext != nullptr) + { + if (linkMode == 0) + { + continue; // ? + } + if (symbolOffset < 8) + { + cemu_assert(symbolSectionAddress >= memory_base && symbolSectionAddress <= (memory_base + 0x100000000ULL)); + uint32 symbolSectionMPTR = memory_getVirtualOffsetFromPointer(symbolSectionAddress); + uint32 symbolRelativeAddress = (uint32)sym->symbolAddress - (uint32)symbolSection->virtualAddress; + + sym->symbolAddress = (symbolSectionMPTR + symbolRelativeAddress); + continue; // ? + } + + if (sharedImportTracking[symSectionIndex].rplLoaderContext == HLE_MODULE_PTR) + { + // get address + uint32 nameOffset = sym->ukn00; + char* symbolName = (char*)strtabData + nameOffset; + if (nameOffset >= strtabSize) + { + forceLog_printf("RPLLoader: Symbol %d in section %d has out-of-bounds name offset", i, symSectionIndex); + continue; + } + + uint32 exportAddress; + if (nameOffset == 0) + { + cemu_assert_debug(symbolName[0] == '\0'); + exportAddress = 0; + } + else + { + bool isDataExport = (rplLoaderContext->sectionTablePtr[symSectionIndex].flags & 0x4) == 0; + exportAddress = _findHLEExport(rplLoaderContext, sharedImportTracking.data() + symSectionIndex, sharedImportTracking[symSectionIndex].modulename, symbolName, isDataExport); + } + + sym->symbolAddress = exportAddress; + } + else + { + RPLModule* ctxExportModule = sharedImportTracking[symSectionIndex].rplLoaderContext; + uint32 nameOffset = sym->ukn00; + char* symbolName = (char*)strtabData + nameOffset; + + bool foundExport = false; + if ((rplLoaderContext->sectionTablePtr[symSectionIndex].flags & 0x4) != 0) + { + // find function export + char* exportNameData = (char*)((uint8*)ctxExportModule->exportFDataPtr - 8); + for (uint32 f = 0; f < ctxExportModule->exportFCount; f++) + { + char* name = exportNameData + (uint32)ctxExportModule->exportFDataPtr[f].nameOffset; + if (strcmp(name, symbolName) == 0) + { + uint32 exportAddress = ctxExportModule->exportFDataPtr[f].virtualOffset; + sym->symbolAddress = exportAddress; + foundExport = true; + break; + } + } + } + else + { + // find data export + char* exportNameData = (char*)((uint8*)ctxExportModule->exportDDataPtr - 8); + for (uint32 f = 0; f < ctxExportModule->exportDCount; f++) + { + char* name = exportNameData + (uint32)ctxExportModule->exportDDataPtr[f].nameOffset; + if (strcmp(name, symbolName) == 0) + { + uint32 exportAddress = ctxExportModule->exportDDataPtr[f].virtualOffset; + sym->symbolAddress = exportAddress; + foundExport = true; + break; + } + } + } + if (foundExport == false) + { +#ifndef PUBLIC_RELEASE + if (nameOffset > 0) + { + forceLogDebug_printf("export not found - force lookup in function exports"); + // workaround - force look up export in function exports + char* exportNameData = (char*)((uint8*)ctxExportModule->exportFDataPtr - 8); + for (uint32 f = 0; f < ctxExportModule->exportFCount; f++) + { + char* name = exportNameData + (uint32)ctxExportModule->exportFDataPtr[f].nameOffset; + if (strcmp(name, symbolName) == 0) + { + uint32 exportAddress = ctxExportModule->exportFDataPtr[f].virtualOffset; + sym->symbolAddress = exportAddress; + foundExport = true; + break; + } + } + } +#endif + continue; + } + } + } + else + { + uint32 symbolType = sym->info & 0xF; + if (symbolType == 6) + continue; + if (((uint32)symbolSection->type != SHT_RPL_IMPORTS && linkMode != 2) || + ((uint32)symbolSection->type == SHT_RPL_IMPORTS && linkMode != 1 && linkMode != 2) + ) + { + // update virtual address to match actual mapped address + cemu_assert(symbolSectionAddress >= memory_base && symbolSectionAddress <= (memory_base + 0x100000000ULL)); + uint32 symbolSectionMPTR = memory_getVirtualOffsetFromPointer(symbolSectionAddress); + uint32 symbolRelativeAddress = (uint32)sym->symbolAddress - (uint32)symbolSection->virtualAddress; + sym->symbolAddress = (symbolSectionMPTR + symbolRelativeAddress); + } + } + } + return true; +} + +bool RPLLoader_ApplySingleReloc(RPLModule* rplLoaderContext, uint32 uknR3, uint8* relocTargetSectionAddress, uint32 relocType, bool isSymbolBinding2, uint32 relocOffset, uint32 relocAddend, uint32 symbolAddress, sint16 tlsModuleIndex) +{ + MPTR relocTargetSectionMPTR = memory_getVirtualOffsetFromPointer(relocTargetSectionAddress); + MPTR relocAddrMPTR = relocTargetSectionMPTR + relocOffset; + uint8* relocAddr = memory_getPointerFromVirtualOffset(relocAddrMPTR); + + if (relocType == RPL_RELOC_HA16) + { + uint32 relocDestAddr = symbolAddress + relocAddend; + uint32 p = (relocDestAddr >> 16); + p += (relocDestAddr >> 15) & 1; + *(uint16be*)(relocAddr) = (uint16)p; + } + else if (relocType == RPL_RELOC_LO16) + { + uint32 relocDestAddr = symbolAddress + relocAddend; + uint32 p = relocDestAddr; + *(uint16be*)(relocAddr) = (uint16)p; + } + else if (relocType == RPL_RELOC_HI16) + { + uint32 relocDestAddr = symbolAddress + relocAddend; + uint32 p = relocDestAddr>>16; + *(uint16be*)(relocAddr) = (uint16)p; + } + else if (relocType == RPL_RELOC_REL24) + { + // todo - effect of isSymbolBinding2? + uint32 opc = *(uint32be*)relocAddr; + uint32 relocDestAddr = symbolAddress + relocAddend; + uint32 jumpDistance = relocDestAddr - memory_getVirtualOffsetFromPointer(relocAddr); + if ((jumpDistance>>25) != 0 && (jumpDistance >> 25) != 0x7F) + { + // can't reach with 24bit jump, use trampoline + absolute branch + MPTR trampolineAddr = _generateTrampolineFarJump(rplLoaderContext, relocDestAddr); + // make absolute branch + cemu_assert_debug((opc >> 26) == 18); // should be B/BL instruction + opc &= ~0x03fffffc; + opc |= (trampolineAddr & 0x3FFFFFC); + opc |= (1 << 1); // absolute jump + *(uint32be*)relocAddr = opc; + } + else + { + // within range, update jump opcode + if ((jumpDistance & 3) != 0) + cemuLog_force("RPL-Loader: Encountered unaligned RPL_RELOC_REL24"); + opc &= ~0x03fffffc; + opc |= (jumpDistance &0x03fffffc); + *(uint32be*)relocAddr = opc; + } + } + else if (relocType == RPL_RELOC_REL14) + { + // seen in Your Shape: Fitness Evolved + // todo - effect of isSymbolBinding2? + uint32 opc = *(uint32be*)relocAddr; + uint32 relocDestAddr = symbolAddress + relocAddend; + uint32 jumpDistance = relocDestAddr - memory_getVirtualOffsetFromPointer(relocAddr); + if ((jumpDistance & ~0x7fff) != 0xFFFF8000 && (jumpDistance & ~0x7fff) != 0x00000000) + { + cemu_assert_debug(false); + } + else + { + // within range, update jump opcode + if ((jumpDistance & 3) != 0) + cemuLog_force("RPL-Loader: Encountered unaligned RPL_RELOC_REL14"); + opc &= ~0xfffc; + opc |= (jumpDistance & 0xfffc); + *(uint32be*)relocAddr = opc; + } + } + else if (relocType == RPL_RELOC_ADDR32) + { + uint32 relocDestAddr = symbolAddress + relocAddend; + uint32 p = relocDestAddr; + *(uint32be*)(relocAddr) = (uint32)p; + } + else if (relocType == R_PPC_DTPMOD32) + { + // patch tls_index.moduleIndex + *(uint32be*)(relocAddr) = (uint32)(sint32)tlsModuleIndex; + } + else if (relocType == R_PPC_DTPREL32) + { + // patch tls_index.size + *(uint32be*)(relocAddr) = (uint32)(sint32)(symbolAddress + relocAddend); + } + else if (relocType == R_PPC_REL16_HA) + { + // used by WUT + uint32 relAddr = (symbolAddress + relocAddend) - relocAddrMPTR; + uint32 p = (relAddr >> 16); + p += (relAddr >> 15) & 1; + *(uint16be*)(relocAddr) = (uint16)p; + } + else if (relocType == R_PPC_REL16_HI) + { + // used by WUT + uint32 relAddr = (symbolAddress + relocAddend) - relocAddrMPTR; + uint32 p = (relAddr >> 16); + *(uint16be*)(relocAddr) = (uint16)p; + } + else if (relocType == R_PPC_REL16_LO) + { + // used by WUT + uint32 relAddr = (symbolAddress + relocAddend) - relocAddrMPTR; + uint32 p = (relAddr & 0xFFFF); + *(uint16be*)(relocAddr) = (uint16)p; + } + else if (relocType == 0x6D) // SDATA reloc + { + uint32 opc = *(uint32be*)relocAddr; + + uint32 registerIndex = (opc >> 16) & 0x1F; + uint32 destination = (symbolAddress + relocAddend);; + + if (registerIndex == 2) + { + uint32 offset = destination - rplLoader_sdata2Addr; + uint32 newOpc = (opc & 0xffe00000) | (offset & 0xffff) | (registerIndex << 16); + *(uint32be*)relocAddr = newOpc; + } + else if (registerIndex == 13) + { + uint32 offset = destination - rplLoader_sdataAddr; + uint32 newOpc = (opc & 0xffe00000) | (offset & 0xffff) | (registerIndex << 16); + *(uint32be*)relocAddr = newOpc; + } + else + { + cemuLog_force("RPLLoader: sdata reloc uses register other than r2/r13"); + cemu_assert(false); + } + } + else if (relocType == 0xFB) + { + // relative offset - high + uint32 relocDestAddr = symbolAddress + relocAddend; + uint32 relativeOffset = relocDestAddr - relocAddrMPTR; + uint16 prevValue = *(uint16be*)relocAddr; + uint32 newImm = ((relativeOffset >> 16) + ((relativeOffset >> 15) & 0x1)); + newImm &= 0xFFFF; + *(uint16be*)relocAddr = newImm; + if (symbolAddress != 0) + { + cemu_assert_debug((uint32)prevValue == newImm); + } + } + else if (relocType == 0xFD) + { + // relative offset - low + uint32 relocDestAddr = symbolAddress + relocAddend; + uint32 relativeOffset = relocDestAddr - relocAddrMPTR; + uint16 prevValue = *(uint16be*)relocAddr; + uint32 newImm = relativeOffset; + newImm &= 0xFFFF; + *(uint16be*)relocAddr = newImm; + if (symbolAddress != 0) + { + cemu_assert_debug((uint32)prevValue == newImm); + } + } + else + { + cemuLog_force("RPLLoader: Unsupported reloc type 0x{:02x}", relocType); + cemu_assert_debug(false); // unknown reloc type + } + return true; +} + +bool RPLLoader_ApplyRelocs(RPLModule* rplLoaderContext, sint32 relaSectionIndex, rplSectionEntryNew_t* section, uint32 linkMode) +{ + uint32 relocTargetSectionIndex = section->relocTargetSectionIndex; + if (relocTargetSectionIndex >= (uint32)rplLoaderContext->rplHeader.sectionTableEntryCount) + assert_dbg(); + uint32 symtabSectionIndex = section->symtabSectionIndex; + uint8* relocTargetSectionAddress = (uint8*)(rplLoaderContext->sectionAddressTable2[relocTargetSectionIndex].ptr); + cemu_assert(relocTargetSectionAddress); + // get symtab info + rplSectionEntryNew_t* symtabSection = rplLoaderContext->sectionTablePtr + symtabSectionIndex; + uint32 symtabSectionSize = symtabSection->sectionSize; + uint32 symbolEntrySize = symtabSection->ukn24; + if (symbolEntrySize == 0) + symbolEntrySize = 0x10; + cemu_assert(symbolEntrySize == 0x10); + cemu_assert((symtabSectionSize % symbolEntrySize) == 0); + uint32 symbolCount = symtabSectionSize / symbolEntrySize; + cemu_assert(symbolCount >= 2); + uint8* symtabData = (uint8*)rplLoaderContext->sectionAddressTable2[symtabSectionIndex].ptr; + // decompress reloc section if needed + uint8* relocData; + uint32 relocSize; + if ((uint32)(section->flags) & SHF_RPL_COMPRESSED) + { + uint8* relocRawData = (uint8*)rplLoaderContext->sectionAddressTable2[relaSectionIndex].ptr; + uint32 relocUncompressedSize = *(uint32be*)relocRawData; + relocData = (uint8*)malloc(relocUncompressedSize); + relocSize = relocUncompressedSize; + // decompress + int ret; + z_stream strm; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + ret = inflateInit(&strm); + if (ret == Z_OK) + { + strm.avail_in = (uint32)section->sectionSize - 4; + strm.next_in = relocRawData + 4; + strm.avail_out = relocUncompressedSize; + strm.next_out = relocData; + ret = inflate(&strm, Z_FULL_FLUSH); + cemu_assert_debug(ret == Z_OK || ret == Z_STREAM_END); + cemu_assert_debug(strm.avail_in == 0 && strm.avail_out == 0); + inflateEnd(&strm); + } + } + else + { + relocData = (uint8*)rplLoaderContext->sectionAddressTable2[relaSectionIndex].ptr; + relocSize = section->sectionSize; + } + // check CRC + uint32 calcCRC = crc32_calc(0, relocData, relocSize); + uint32 crc = rplLoaderContext->GetSectionCRC(relaSectionIndex); + if (calcCRC != crc) + { + forceLog_printf("RPLLoader %s - Relocation section %d has CRC mismatch - Calc: %08x Actual: %08x", rplLoaderContext->moduleName2.c_str(), relaSectionIndex, calcCRC, crc); + } + // process relocations + sint32 relocCount = relocSize / sizeof(rplRelocNew_t); + rplRelocNew_t* reloc = (rplRelocNew_t*)relocData; + for (sint32 i = 0; i < relocCount; i++) + { + uint32 relocType = (uint32)reloc->symbolIndexAndType & 0xFF; + uint32 relocSymbolIndex = (uint32)reloc->symbolIndexAndType >> 8; + if (relocType == 0) + { + // next + reloc++; + continue; + } + if (relocSymbolIndex >= symbolCount) + { + forceLogDebug_printf("reloc with symbol index out of range 0x%04x", (uint32)relocSymbolIndex); + reloc++; + continue; + } + // get symbol + RPLFileSymtabEntry* sym = (RPLFileSymtabEntry*)(symtabData + symbolEntrySize*relocSymbolIndex); + + if ((uint32)sym->sectionIndex >= (uint32)rplLoaderContext->rplHeader.sectionTableEntryCount) + { + forceLogDebug_printf("reloc with sectionIndex out of range 0x%04x", (uint32)sym->sectionIndex); + reloc++; + continue; + } + // exclude symbols that arent ready yet + if (linkMode == 0) + { + if ((uint32)rplLoaderContext->sectionTablePtr[(uint32)sym->sectionIndex].type == SHT_RPL_IMPORTS) + { + reloc++; + continue; + } + } + uint32 symbolAddress = sym->symbolAddress; + uint8 symbolType = (sym->info >> 0) & 0xF; + uint8 symbolBinding = (sym->info >> 4) & 0xF; + if ((symbolAddress&0xFF000000) == 0xCD000000) + { + cemu_assert_unimplemented(); + // next + reloc++; + continue; + } + sint16 tlsModuleIndex = -1; + if (relocType == R_PPC_DTPMOD32 || relocType == R_PPC_DTPREL32) + { + // TLS-relocation + if (symbolType != 6) + assert_dbg(); // not a TLS symbol + if (rplLoaderContext->fileInfo.tlsModuleIndex == -1) + { + cemuLog_force("RPLLoader: TLS relocs applied to non-TLS module"); + cemu_assert_debug(false); // module not a TLS-module + } + tlsModuleIndex = rplLoaderContext->fileInfo.tlsModuleIndex; + } + uint32 relocOffset = (uint32)reloc->relocOffset - (uint32)rplLoaderContext->sectionTablePtr[relocTargetSectionIndex].virtualAddress; + RPLLoader_ApplySingleReloc(rplLoaderContext, 0, relocTargetSectionAddress, relocType, symbolBinding == 2, relocOffset, reloc->relocAddend, symbolAddress, tlsModuleIndex); + + // next reloc + reloc++; + } + + if ((uint32)(section->flags) & SHF_RPL_COMPRESSED) + free(relocData); + return true; +} + +bool RPLLoader_HandleRelocs(RPLModule* rplLoaderContext, std::span<RPLSharedImportTracking> sharedImportTracking, uint32 linkMode) +{ + // resolve relocs + for (sint32 i = 0; i < (sint32)rplLoaderContext->rplHeader.sectionTableEntryCount; i++) + { + rplSectionEntryNew_t* section = rplLoaderContext->sectionTablePtr + i; + uint32 sectionType = section->type; + if( sectionType != SHT_SYMTAB ) + continue; + RPLLoader_FixImportSymbols(rplLoaderContext, i, section, sharedImportTracking, linkMode); + } + + // apply relocs again after we have fixed the import section + for (sint32 i = 0; i < (sint32)rplLoaderContext->rplHeader.sectionTableEntryCount; i++) + { + rplSectionEntryNew_t* section = rplLoaderContext->sectionTablePtr + i; + uint32 sectionType = section->type; + if (sectionType != SHT_RELA) + continue; + RPLLoader_ApplyRelocs(rplLoaderContext, i, section, linkMode); + } + return true; +} + +void _RPLLoader_ExtractModuleNameFromPath(char* output, const char* input) +{ + // scan to last '/' + sint32 inputLen = (sint32)strlen(input); + cemu_assert(inputLen > 0); + sint32 startIndex = inputLen - 1; + while (startIndex > 0) + { + if (input[startIndex] == '/') + { + startIndex++; + break; + } + startIndex--; + } + // cut off after '.' + sint32 endIndex = startIndex; + while (endIndex <= inputLen) + { + if (input[endIndex] == '.') + break; + endIndex++; + } + sint32 nameLen = endIndex - startIndex; + cemu_assert(nameLen != 0); + nameLen = std::min(nameLen, RPL_MODULE_NAME_LENGTH-1); + memcpy(output, input + startIndex, nameLen); + output[nameLen] = '\0'; + // convert to lower case + for (sint32 i = 0; i < nameLen; i++) + { + output[i] = _ansiToLower(output[i]); + } +} + +void RPLLoader_InitState() +{ + cemu_assert_debug(!rplLoaderHeap_lowerAreaCodeMem2.hasAllocations()); + cemu_assert_debug(!rplLoaderHeap_codeArea2.hasAllocations()); + cemu_assert_debug(!rplLoaderHeap_workarea.hasAllocations()); + rplLoaderHeap_lowerAreaCodeMem2.setHeapBase(memory_getPointerFromVirtualOffset(MEMORY_CODE_TRAMPOLINE_AREA_ADDR)); + rplLoaderHeap_codeArea2.setHeapBase(memory_getPointerFromVirtualOffset(MEMORY_CODEAREA_ADDR)); + rplLoaderHeap_workarea.setHeapBase(memory_getPointerFromVirtualOffset(MEMORY_RPLLOADER_AREA_ADDR)); + g_heapTrampolineArea.setBaseAllocator(&rplLoaderHeap_lowerAreaCodeMem2); +} + +void RPLLoader_ResetState() +{ + // unload all RPL modules + while (rplModuleCount > 0) + RPLLoader_UnloadModule(rplModuleList[0]); + // clear dependency list + cemu_assert_debug(false); + // unload all remaining symbols + rplSymbolStorage_unloadAll(); + // free all code imports + g_heapTrampolineArea.releaseAll(); + list_mappedFunctionImports.clear(); + g_map_callableExports.clear(); + + rplLoader_applicationHasMemoryControl = false; + rplLoader_maxCodeAddress = 0; + rpl3_currentDataAllocatorAddr = 0x10000000; + cemu_assert_debug(rplDependencyList.empty()); + rplDependencyList.clear(); + _currentTLSModuleIndex = 1; + rplLoader_sdataAddr = MPTR_NULL; + rplLoader_sdata2Addr = MPTR_NULL; +} + +void RPLLoader_BeginCemuhookCRC(RPLModule* rpl) +{ + // calculate some values required for CRC + sint32 sectionSymTableIndex = -1; + sint32 sectionStrTableIndex = -1; + for (sint32 i = 0; i < rpl->rplHeader.sectionTableEntryCount; i++) + { + if (rpl->sectionTablePtr[i].type == SHT_SYMTAB) + sectionSymTableIndex = i; + if (rpl->sectionTablePtr[i].type == SHT_STRTAB && i != rpl->rplHeader.nameSectionIndex && sectionStrTableIndex == -1) + sectionStrTableIndex = i; + } + // init patches CRC + rpl->patchCRC = 0; + static const uint8 rplMagic[4] = { 0x7F, 'R', 'P', 'X' }; + rpl->patchCRC = crc32_calc(rpl->patchCRC, rplMagic, sizeof(rplMagic)); + sint32 sectionCount = rpl->rplHeader.sectionTableEntryCount; + rpl->patchCRC = crc32_calc(rpl->patchCRC, §ionCount, sizeof(sectionCount)); + rpl->patchCRC = crc32_calc(rpl->patchCRC, §ionSymTableIndex, sizeof(sectionSymTableIndex)); + rpl->patchCRC = crc32_calc(rpl->patchCRC, §ionStrTableIndex, sizeof(sectionStrTableIndex)); + sint32 sectionSectNameTableIndex = rpl->rplHeader.nameSectionIndex; + rpl->patchCRC = crc32_calc(rpl->patchCRC, §ionSectNameTableIndex, sizeof(sectionSectNameTableIndex)); + + // sections + for (sint32 i = 0; i < rpl->rplHeader.sectionTableEntryCount; i++) + { + auto sect = rpl->sectionTablePtr + i; + uint32 nameOffset = sect->nameOffset; + uint32 shType = sect->type; + uint32 flags = sect->flags; + uint32 virtualAddress = sect->virtualAddress; + uint32 alignment = sect->alignment; + uint32 sectionFileOffset = sect->fileOffset; + uint32 sectionCompressedSize = sect->sectionSize; + uint32 rawSize = 0; + bool memoryAllocated = false; + void* rawData = nullptr; + if (shType == SHT_NOBITS) + { + rawData = NULL; + rawSize = sectionCompressedSize; + } + else if ((flags&SHF_RPL_COMPRESSED) != 0) + { + uint32 decompressedSize = _swapEndianU32(*(uint32*)(rpl->RPLRawData.data() + sectionFileOffset)); + rawSize = decompressedSize; + if (rawSize >= 1024*1024*1024) + { + forceLogDebug_printf("RPLLoader-CRC: Cannot load section %d which is too large (%u bytes)", i, decompressedSize); + cemu_assert_debug(false); + continue; + } + rawData = (uint8*)malloc(decompressedSize); + if (rawData == nullptr) + { + forceLogDebug_printf("RPLLoader-CRC: Failed to allocate memory for uncompressed section %d (%u bytes)", i, decompressedSize); + cemu_assert_debug(false); + continue; + } + memoryAllocated = true; + // decompress + z_stream strm; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + inflateInit(&strm); + strm.avail_in = sectionCompressedSize - 4; + strm.next_in = rpl->RPLRawData.data() + (uint32)sectionFileOffset + 4; + strm.avail_out = decompressedSize; + strm.next_out = (Bytef*)rawData; + auto ret = inflate(&strm, Z_FULL_FLUSH); + if (ret != Z_OK && ret != Z_STREAM_END || strm.avail_in != 0 || strm.avail_out != 0) + { + forceLogDebug_printf("RPLLoader-CRC: Unable to decompress section %d", i); + forceLogDebug_printf("zRet %d availIn %d availOut %d", ret, (sint32)strm.avail_in, (sint32)strm.avail_out); + cemu_assert_debug(false); + free(rawData); + inflateEnd(&strm); + continue; + } + inflateEnd(&strm); + } + else + { + rawSize = sectionCompressedSize; + rawData = rpl->RPLRawData.data() + sectionFileOffset; + } + + rpl->patchCRC = crc32_calc(rpl->patchCRC, &nameOffset, sizeof(nameOffset)); + rpl->patchCRC = crc32_calc(rpl->patchCRC, &shType, sizeof(shType)); + rpl->patchCRC = crc32_calc(rpl->patchCRC, &flags, sizeof(flags)); + rpl->patchCRC = crc32_calc(rpl->patchCRC, &virtualAddress, sizeof(virtualAddress)); + rpl->patchCRC = crc32_calc(rpl->patchCRC, &rawSize, sizeof(rawSize)); + rpl->patchCRC = crc32_calc(rpl->patchCRC, &alignment, sizeof(alignment)); + + if (rawData != nullptr && rawSize > 0) + { + rpl->patchCRC = crc32_calc(rpl->patchCRC, rawData, rawSize); + } + if (memoryAllocated && rawData) + free(rawData); + } +} + +void RPLLoader_incrementModuleDependencyRefs(RPLModule* rpl) +{ + for (uint32 i = 0; i < (uint32)rpl->rplHeader.sectionTableEntryCount; i++) + { + if (rpl->sectionTablePtr[i].type != (uint32be)SHT_RPL_IMPORTS) + continue; + char* libName = (char*)((uint8*)rpl->sectionAddressTable2[i].ptr + 8); + RPLLoader_AddDependency(libName); + } +} + +void RPLLoader_decrementModuleDependencyRefs(RPLModule* rpl) +{ + for (uint32 i = 0; i < (uint32)rpl->rplHeader.sectionTableEntryCount; i++) + { + if (rpl->sectionTablePtr[i].type != (uint32be)SHT_RPL_IMPORTS) + continue; + char* libName = (char*)((uint8*)rpl->sectionAddressTable2[i].ptr + 8); + RPLLoader_RemoveDependency(libName); + } +} + +void RPLLoader_UpdateEntrypoint(RPLModule* rpl) +{ + uint32 virtualEntrypoint = rpl->rplHeader.entrypoint; + uint32 entrypoint = 0xFFFFFFFF; + for (sint32 i = 0; i < (sint32)rpl->rplHeader.sectionTableEntryCount; i++) + { + rplSectionEntryNew_t* section = rpl->sectionTablePtr + i; + uint32 sectionStartAddr = (uint32)section->virtualAddress; + uint32 sectionEndAddr = (uint32)section->virtualAddress + (uint32)section->sectionSize; + if (virtualEntrypoint >= sectionStartAddr && virtualEntrypoint < sectionEndAddr) + { + cemu_assert_debug(entrypoint == 0xFFFFFFFF); + entrypoint = (virtualEntrypoint - sectionStartAddr + memory_getVirtualOffsetFromPointer(rpl->sectionAddressTable2[i].ptr)); + } + } + cemu_assert(entrypoint != 0xFFFFFFFF); + rpl->entrypoint = entrypoint; +} + +void RPLLoader_InitModuleAllocator(RPLModule* rpl) +{ + if (!rplLoader_applicationHasMemoryControl) + { + rpl->funcAlloc = 0; + rpl->funcFree = 0; + return; + } + coreinit::OSDynLoad_GetAllocator(&rpl->funcAlloc, &rpl->funcFree); +} + +// map rpl into memory, but do not resolve relocs and imports yet +RPLModule* rpl_loadFromMem(uint8* rplData, sint32 size, char* name) +{ + char moduleName[RPL_MODULE_NAME_LENGTH]; + _RPLLoader_ExtractModuleNameFromPath(moduleName, name); + RPLModule* rpl = nullptr; + if (RPLLoader_ProcessHeaders({ moduleName }, rplData, size, &rpl) == false) + { + delete rpl; + return nullptr; + } + RPLLoader_InitModuleAllocator(rpl); + RPLLoader_BeginCemuhookCRC(rpl); + if (RPLLoader_LoadSections(0, rpl) == false) + { + delete rpl; + return nullptr; + } + + forceLogDebug_printf("Load %s Code-Offset: -0x%x", name, rpl->regionMappingBase_text.GetMPTR() - 0x02000000); + + // sdata (r2/r13) + uint32 sdataBaseAddress = rpl->fileInfo.sdataBase1; // base + 0x8000 + uint32 sdataBaseAddress2 = rpl->fileInfo.sdataBase2; // base + 0x8000 + for (uint32 i = 0; i < (uint32)rpl->rplHeader.sectionTableEntryCount; i++) + { + if(rpl->sectionTablePtr[i].sectionSize == 0) + continue; + uint32 sectionFlags = rpl->sectionTablePtr[i].flags; + uint32 sectionVirtualAddress = rpl->sectionTablePtr[i].virtualAddress; + uint32 sectionSize = rpl->sectionTablePtr[i].sectionSize; + if( (sectionFlags&4) != 0 ) + continue; + if(sdataBaseAddress == 0x00008000 && sdataBaseAddress2 == 0x00008000) + continue; // sdata not used (this workaround is needed for Wii U Party to boot) + // sdata 1 + if ((sdataBaseAddress - 0x8000) >= (sectionVirtualAddress) && + (sdataBaseAddress - 0x8000) <= (sectionVirtualAddress + sectionSize)) + { + uint32 rplLoader_sdataAddrNew = memory_getVirtualOffsetFromPointer(rpl->sectionAddressTable2[i].ptr) + (sdataBaseAddress - sectionVirtualAddress); + rplLoader_sdataAddr = rplLoader_sdataAddrNew; + } + // sdata 2 + if ((sdataBaseAddress2 - 0x8000) >= (sectionVirtualAddress) && + (sdataBaseAddress2 - 0x8000) <= (sectionVirtualAddress + sectionSize)) + { + rplLoader_sdata2Addr = memory_getVirtualOffsetFromPointer(rpl->sectionAddressTable2[i].ptr) + (sdataBaseAddress2 - sectionVirtualAddress); + } + + } + // cancel if error + if (rpl->hasError) + { + forceLog_printf("RPLLoader: Unable to load RPL due to errors"); + delete rpl; + return nullptr; + } + // find TLS section + uint32 tlsStartAddress = 0xFFFFFFFF; + uint32 tlsEndAddress = 0; + for (uint32 i = 0; i < (uint32)rpl->rplHeader.sectionTableEntryCount; i++) + { + if ( ((uint32)rpl->sectionTablePtr[i].flags & SHF_TLS) == 0 ) + continue; + uint32 sectionVirtualAddress = rpl->sectionTablePtr[i].virtualAddress; + uint32 sectionSize = rpl->sectionTablePtr[i].sectionSize; + tlsStartAddress = std::min(tlsStartAddress, sectionVirtualAddress); + tlsEndAddress = std::max(tlsEndAddress, sectionVirtualAddress+sectionSize); + } + if (tlsStartAddress == 0xFFFFFFFF) + { + tlsStartAddress = 0; + tlsEndAddress = 0; + } + rpl->tlsStartAddress = tlsStartAddress; + rpl->tlsEndAddress = tlsEndAddress; + + // add to module list + cemu_assert(rplModuleCount < 256); + rplModuleList[rplModuleCount] = rpl; + rplModuleCount++; + + // track dependencies + RPLLoader_incrementModuleDependencyRefs(rpl); + + // update entrypoint + RPLLoader_UpdateEntrypoint(rpl); + return rpl; +} + +void RPLLoader_flushMemory(RPLModule* rpl) +{ + // invalidate recompiler cache + PPCRecompiler_invalidateRange(rpl->regionMappingBase_text.GetMPTR(), rpl->regionMappingBase_text.GetMPTR() + rpl->regionSize_text); + rpl->heapTrampolineArea.forEachBlock([](void* mem, uint32 size) + { + MEMPTR<void> memVirtual = mem; + PPCRecompiler_invalidateRange(memVirtual.GetMPTR(), memVirtual.GetMPTR() + size); + }); +} + +// resolve relocs and imports of all modules. Or resolve exports +void RPLLoader_LinkSingleModule(RPLModule* rplLoaderContext, bool resolveOnlyExports) +{ + // setup shared import tracking + std::vector<RPLSharedImportTracking> sharedImportTracking; + + sharedImportTracking.resize(rplLoaderContext->rplHeader.sectionTableEntryCount - 2); + + memset(sharedImportTracking.data(), 0, sizeof(RPLSharedImportTracking) * sharedImportTracking.size()); + + for (uint32 i = 0; i < (uint32)rplLoaderContext->rplHeader.sectionTableEntryCount; i++) + { + if( rplLoaderContext->sectionTablePtr[i].type != (uint32be)SHT_RPL_IMPORTS ) + continue; + char* libName = (char*)((uint8*)rplLoaderContext->sectionAddressTable2[i].ptr + 8); + // make module name + char _importModuleName[RPL_MODULE_NAME_LENGTH]; + _RPLLoader_ExtractModuleNameFromPath(_importModuleName, libName); + // find in loaded module list + std::string importModuleName{_importModuleName}; + bool foundModule = false; + for (sint32 f = 0; f < rplModuleCount; f++) + { + if (boost::iequals(rplModuleList[f]->moduleName2, importModuleName)) + { + sharedImportTracking[i].rplLoaderContext = rplModuleList[f]; + memset(sharedImportTracking[i].modulename, 0, sizeof(sharedImportTracking[i].modulename)); + strcpy_s(sharedImportTracking[i].modulename, importModuleName.c_str()); + foundModule = true; + break; + } + } + if( foundModule ) + continue; + // if not found, we assume it's a HLE lib + sharedImportTracking[i].rplLoaderContext = HLE_MODULE_PTR; + sharedImportTracking[i].exportSection = nullptr; + strcpy_s(sharedImportTracking[i].modulename, libName); + } + + if (resolveOnlyExports) + RPLLoader_HandleRelocs(rplLoaderContext, sharedImportTracking, 2); + else + RPLLoader_HandleRelocs(rplLoaderContext, sharedImportTracking, 0); + + RPLLoader_flushMemory(rplLoaderContext); +} + +void RPLLoader_LoadSectionDebugSymbols(RPLModule* rplLoaderContext, rplSectionEntryNew_t* section, int symtabSectionIndex) +{ + uint32 sectionSize = section->sectionSize; + uint32 symbolEntrySize = section->ukn24; + if (symbolEntrySize == 0) + symbolEntrySize = 0x10; + cemu_assert(symbolEntrySize == 0x10); + cemu_assert((sectionSize % symbolEntrySize) == 0); + uint32 symbolCount = sectionSize / symbolEntrySize; + cemu_assert(symbolCount >= 2); + + uint16 sectionCount = rplLoaderContext->rplHeader.sectionTableEntryCount; + uint8* symtabData = (uint8*)rplLoaderContext->sectionAddressTable2[symtabSectionIndex].ptr; + + uint32 strtabSectionIndex = section->symtabSectionIndex; + uint8* strtabData = (uint8*)rplLoaderContext->sectionAddressTable2[strtabSectionIndex].ptr; + + for (uint32 i = 0; i < symbolCount; i++) + { + RPLFileSymtabEntry* sym = (RPLFileSymtabEntry*)(symtabData + i * symbolEntrySize); + + uint16 symSectionIndex = sym->sectionIndex; + if (symSectionIndex == 0 || symSectionIndex >= sectionCount) + continue; + void* symbolSectionAddress = rplLoaderContext->sectionAddressTable2[symSectionIndex].ptr; + if (symbolSectionAddress == nullptr) + continue; + rplSectionEntryNew_t* symbolSection = rplLoaderContext->sectionTablePtr + symSectionIndex; + if(symbolSection->type == SHT_RPL_EXPORTS || symbolSection->type == SHT_RPL_IMPORTS) + continue; // exports and imports are handled separately + + uint32 symbolOffset = sym->symbolAddress - symbolSection->virtualAddress; + + uint32 nameOffset = sym->ukn00; + if (nameOffset > 0) + { + char* symbolName = (char*)strtabData + nameOffset; + if (sym->info == 0x12) + { + rplSymbolStorage_store(rplLoaderContext->moduleName2.c_str(), symbolName, sym->symbolAddress); + } + } + } +} + +void RPLLoader_LoadDebugSymbols(RPLModule* rplLoaderContext) +{ + for (sint32 i = 0; i < (sint32)rplLoaderContext->rplHeader.sectionTableEntryCount; i++) + { + rplSectionEntryNew_t* section = rplLoaderContext->sectionTablePtr + i; + uint32 sectionType = section->type; + if (sectionType != SHT_SYMTAB) + continue; + RPLLoader_LoadSectionDebugSymbols(rplLoaderContext, section, i); + } +} + +void RPLLoader_UnloadModule(RPLModule* rpl) +{ + /* + A note: + Mario Party 10's mg0480.rpl (minigame Spike Ball Scramble) has a bug where it keeps running code (function 0x02086BCC for example) after RPL unload + It seems to rely on the RPL loader not zeroing released memory + */ + + // decrease reference counters of all dependencies + RPLLoader_decrementModuleDependencyRefs(rpl); + + // save module config for this module in the debugger + debuggerWindow_notifyModuleUnloaded(rpl); + + // release memory + rplLoaderHeap_codeArea2.free(rpl->regionMappingBase_text.GetPtr()); + rpl->regionMappingBase_text = nullptr; + + // for some reason freeing the data allocations causes a crash in MP10 on boot + //RPLLoader_FreeData(rpl, MEMPTR<void>(rpl->regionMappingBase_data).GetPtr()); + //rpl->regionMappingBase_data = 0; + //RPLLoader_FreeData(rpl, MEMPTR<void>(rpl->regionMappingBase_loaderInfo).GetPtr()); + //rpl->regionMappingBase_loaderInfo = 0; + + rpl->heapTrampolineArea.releaseAll(); + + // todo - remove from rplSymbolStorage_store + + if (rpl->sectionTablePtr) + { + free(rpl->sectionTablePtr); + rpl->sectionTablePtr = nullptr; + } + + // unload temp region + if (rpl->tempRegionPtr) + { + RPLLoader_FreeWorkarea(rpl->tempRegionPtr); + rpl->tempRegionPtr = nullptr; + } + + // remove from rpl module list + for (sint32 i = 0; i < rplModuleCount; i++) + { + if (rplModuleList[i] == rpl) + { + rplModuleList[i] = rplModuleList[rplModuleCount-1]; + rplModuleCount--; + break; + } + } + + delete rpl; +} + +void RPLLoader_FixModuleTLSIndex(RPLModule* rplLoaderContext) +{ + sint16 tlsModuleIndex = -1; + for (auto& dep : rplDependencyList) + { + if (boost::iequals(rplLoaderContext->moduleName2, dep->modulename)) + { + tlsModuleIndex = dep->tlsModuleIndex; + break; + } + } + cemu_assert(tlsModuleIndex != -1); + rplLoaderContext->fileInfo.tlsModuleIndex = tlsModuleIndex; +} + +void RPLLoader_Link() +{ + // calculate TLS index + for (sint32 i = 0; i < rplModuleCount; i++) + { + if (rplModuleList[i]->isLinked) + continue; + RPLLoader_FixModuleTLSIndex(rplModuleList[i]); + } + // resolve relocs + for (sint32 i = 0; i < rplModuleCount; i++) + { + if(rplModuleList[i]->isLinked) + continue; + RPLLoader_LinkSingleModule(rplModuleList[i], false); + } + // resolve imports and load debug symbols + for (sint32 i = 0; i < rplModuleCount; i++) + { + if (rplModuleList[i]->isLinked) + continue; + RPLLoader_LinkSingleModule(rplModuleList[i], true); + RPLLoader_LoadDebugSymbols(rplModuleList[i]); + rplModuleList[i]->isLinked = true; // mark as linked + GraphicPack2::NotifyModuleLoaded(rplModuleList[i]); + debuggerWindow_notifyModuleLoaded(rplModuleList[i]); + } +} + +uint32 RPLLoader_GetModuleEntrypoint(RPLModule* rplLoaderContext) +{ + return rplLoaderContext->entrypoint; +} + +uint32 rplLoader_currentHandleCounter = 0x00001000; +sint16 rplLoader_currentTlsModuleIndex = 0x0001; + +// increment reference counter for module +void RPLLoader_AddDependency(const char* name) +{ + cemu_assert(name[0] != '\0'); + // if name includes a path, cut it off + const char* namePtr = name + strlen(name) - 1; + while (namePtr > name) + { + if (*namePtr == '/') + { + namePtr++; + break; + } + namePtr--; + } + name = namePtr; + // get module name from path + char moduleName[RPL_MODULE_NAME_LENGTH]; + _RPLLoader_ExtractModuleNameFromPath(moduleName, name); + // check if dependency already exists + for (auto& dep : rplDependencyList) + { + if (strcmp(moduleName, dep->modulename) == 0) + { + // entry already exists, increment reference counter + dep->referenceCount++; + return; + } + } + // add new entry + rplDependency_t* newDependency = new rplDependency_t(); + strcpy(newDependency->modulename, moduleName); + newDependency->referenceCount = 1; + newDependency->coreinitHandle = rplLoader_currentHandleCounter; + newDependency->tlsModuleIndex = rplLoader_currentTlsModuleIndex; + rplLoader_currentTlsModuleIndex++; + rplLoader_currentHandleCounter++; + if (rplLoader_currentTlsModuleIndex == 0x7FFF) + cemuLog_force("RPLLoader: Exhausted TLS module indices pool"); + // convert name to path/filename if it isn't already one + if (strstr(name, ".")) + { + strcpy_s(newDependency->filepath, name); + } + else + { + strcpy_s(newDependency->filepath, name); + strcat_s(newDependency->filepath, ".rpl"); + } + newDependency->filepath[RPL_MODULE_PATH_LENGTH - 1] = '\0'; + rplDependencyList.push_back(newDependency); +} + +// decrement reference counter for dependency by module path +void RPLLoader_RemoveDependency(const char* name) +{ + cemu_assert(*name != '\0'); + // if name includes a path, cut it off + const char* namePtr = name + strlen(name) - 1; + while (namePtr > name) + { + if (*namePtr == '/') + { + namePtr++; + break; + } + namePtr--; + } + name = namePtr; + // get module name from path + char moduleName[RPL_MODULE_NAME_LENGTH]; + _RPLLoader_ExtractModuleNameFromPath(moduleName, name); + // find dependency and decrement ref count + for (auto& dep : rplDependencyList) + { + if (strcmp(moduleName, dep->modulename) == 0) + { + dep->referenceCount--; + return; + } + } +} + +// decrement reference counter for dependency by module handle +void RPLLoader_RemoveDependency(uint32 handle) +{ + for (auto& dep : rplDependencyList) + { + if (dep->coreinitHandle == handle) + { + cemu_assert_debug(dep->referenceCount != 0); + if(dep->referenceCount > 0) + dep->referenceCount--; + return; + } + } +} + +uint32 RPLLoader_GetHandleByModuleName(const char* name) +{ + // get module name from path + char moduleName[RPL_MODULE_NAME_LENGTH]; + _RPLLoader_ExtractModuleNameFromPath(moduleName, name); + // search for existing dependency + for (auto& dep : rplDependencyList) + { + if (strcmp(moduleName, dep->modulename) == 0) + { + return dep->coreinitHandle; + } + } + return RPL_INVALID_HANDLE; +} + +uint32 RPLLoader_GetMaxTLSModuleIndex() +{ + return rplLoader_currentTlsModuleIndex - 1; +} + +bool RPLLoader_GetTLSDataByTLSIndex(sint16 tlsModuleIndex, uint8** tlsData, sint32* tlsSize) +{ + RPLModule* rplLoaderContext = nullptr; + for (auto& dep : rplDependencyList) + { + if (dep->tlsModuleIndex == tlsModuleIndex) + { + rplLoaderContext = dep->rplLoaderContext; + break; + } + } + if (rplLoaderContext == nullptr) + return false; + cemu_assert(rplLoaderContext->tlsStartAddress != 0); + uint32 tlsDataSize = rplLoaderContext->tlsEndAddress - rplLoaderContext->tlsStartAddress; + cemu_assert_debug(tlsDataSize < 0x10000); // check for suspiciously large TLS area + *tlsData = (uint8*)memory_getPointerFromVirtualOffset(rplLoaderContext->tlsStartAddress); + *tlsSize = tlsDataSize; + return true; +} + +bool RPLLoader_LoadFromVirtualPath(rplDependency_t* dependency, char* filePath) +{ + uint32 rplSize = 0; + uint8* rplData = fsc_extractFile(filePath, &rplSize); + if (rplData) + { + forceLogDebug_printf("Loading: %s", filePath); + dependency->rplLoaderContext = rpl_loadFromMem(rplData, rplSize, filePath); + free(rplData); + return true; + } + return false; +} + +void RPLLoader_LoadDependency(rplDependency_t* dependency) +{ + dependency->loadAttempted = true; + // check if module is already loaded + for (sint32 i = 0; i < rplModuleCount; i++) + { + if(!boost::iequals(rplModuleList[i]->moduleName2, dependency->modulename)) + continue; + // already loaded + dependency->rplLoaderContext = rplModuleList[i]; + return; + } + // attempt to load rpl from various locations + char filePath[RPL_MODULE_PATH_LENGTH]; + // check if path is absolute + if (dependency->filepath[0] == '/') + { + strcpy_s(filePath, dependency->filepath); + RPLLoader_LoadFromVirtualPath(dependency, filePath); + return; + } + // attempt to load rpl from internal folder + strcpy_s(filePath, "/internal/current_title/code/"); + strcat_s(filePath, dependency->filepath); + // except if it is blacklisted + bool isBlacklisted = false; + if (boost::iequals(dependency->filepath, "erreula.rpl")) + { + if (fsc_doesFileExist(filePath)) + isBlacklisted = true; + } + if (isBlacklisted) + cemuLog_log(LogType::Force, fmt::format("Game tried to load \"{}\" but it is blacklisted (using Cemu's implementation instead)", filePath)); + else if (RPLLoader_LoadFromVirtualPath(dependency, filePath)) + return; + // attempt to load rpl from Cemu's /cafeLibs/ directory + if (ActiveSettings::LoadSharedLibrariesEnabled()) + { + const auto filePath = ActiveSettings::GetPath("cafeLibs/{}", dependency->filepath); + auto fileData = FileStream::LoadIntoMemory(filePath); + if (fileData) + { + forceLog_printf("Loading RPL: /cafeLibs/%s", dependency->filepath); + dependency->rplLoaderContext = rpl_loadFromMem(fileData->data(), fileData->size(), dependency->filepath); + return; + } + } +} + +// loads and unloads modules based on the current dependency list +void RPLLoader_UpdateDependencies() +{ + bool repeat = true; + while (repeat) + { + repeat = false; + for(auto idx = 0; idx<rplDependencyList.size(); ) + { + auto dependency = rplDependencyList[idx]; + // debug_printf("DEP 0x%02x %s\n", dependency->referenceCount, dependency->modulename); + if(dependency->referenceCount == 0) + { + // unload RPLs + // todo - should we let HLE modules know if they are being unloaded? + if (dependency->rplLoaderContext) + { + RPLLoader_UnloadModule(dependency->rplLoaderContext); + dependency->rplLoaderContext = nullptr; + } + // remove from dependency list + rplDependencyList.erase(rplDependencyList.begin()+idx); + idx--; + repeat = true; // unload can effect reference count of other dependencies + break; + } + else if (!dependency->loadAttempted) + { + // load + if (dependency->rplLoaderContext == nullptr) + { + RPLLoader_LoadDependency(dependency); + repeat = true; + idx++; + break; + } + } + idx++; + } + } + RPLLoader_Link(); +} + +RPLModule* rplLoader_mainModule = nullptr; + +void RPLLoader_SetMainModule(RPLModule* rplLoaderContext) +{ + rplLoaderContext->entrypointCalled = true; + rplLoader_mainModule = rplLoaderContext; +} + +uint32 RPLLoader_GetMainModuleHandle() +{ + for (auto& dep : rplDependencyList) + { + if (dep->rplLoaderContext == rplLoader_mainModule) + { + return dep->coreinitHandle; + } + } + cemu_assert(false); + return 0; +} + +RPLModule* RPLLoader_FindModuleByCodeAddr(uint32 addr) +{ + for (sint32 i = 0; i < rplModuleCount; i++) + { + uint32 startAddr = rplModuleList[i]->regionMappingBase_text.GetMPTR(); + uint32 endAddr = rplModuleList[i]->regionMappingBase_text.GetMPTR() + rplModuleList[i]->regionSize_text; + if (addr >= startAddr && addr < endAddr) + return rplModuleList[i]; + } + return nullptr; +} + +RPLModule* RPLLoader_FindModuleByDataAddr(uint32 addr) +{ + for (sint32 i = 0; i < rplModuleCount; i++) + { + // data + uint32 startAddr = rplModuleList[i]->regionMappingBase_data; + uint32 endAddr = rplModuleList[i]->regionMappingBase_data + rplModuleList[i]->regionSize_data; + if (addr >= startAddr && addr < endAddr) + return rplModuleList[i]; + // loaderinfo + startAddr = rplModuleList[i]->regionMappingBase_loaderInfo; + endAddr = rplModuleList[i]->regionMappingBase_loaderInfo + rplModuleList[i]->regionSize_loaderInfo; + if (addr >= startAddr && addr < endAddr) + return rplModuleList[i]; + } + return nullptr; +} + +RPLModule* RPLLoader_FindModuleByName(std::string module) +{ + for (sint32 i = 0; i < rplModuleCount; i++) + { + if (rplModuleList[i]->moduleName2 == module) return rplModuleList[i]; + } + return nullptr; +} + +void RPLLoader_CallEntrypoints() +{ + for (sint32 i = 0; i < rplModuleCount; i++) + { + if( rplModuleList[i]->entrypointCalled ) + continue; + uint32 moduleHandle = RPLLoader_GetHandleByModuleName(rplModuleList[i]->moduleName2.c_str()); + MPTR entryPoint = RPLLoader_GetModuleEntrypoint(rplModuleList[i]); + PPCCoreCallback(entryPoint, moduleHandle, 1); // 1 -> load, 2 -> unload + rplModuleList[i]->entrypointCalled = true; + } +} + +void RPLLoader_NotifyControlPassedToApplication() +{ + rplLoader_applicationHasMemoryControl = true; +} + +uint32 RPLLoader_FindModuleOrHLEExport(uint32 moduleHandle, bool isData, const char* exportName) +{ + // find dependency from handle + RPLModule* rplLoaderContext = nullptr; + rplDependency_t* dependency = nullptr; + for (auto& dep : rplDependencyList) + { + if (dep->coreinitHandle == moduleHandle) + { + rplLoaderContext = dep->rplLoaderContext; + dependency = dep; + break; + } + } + + uint32 exportResult = 0; + if (rplLoaderContext) + { + exportResult = RPLLoader_FindModuleExport(rplLoaderContext, isData, exportName); + } + else + { + // attempt to find HLE export + if (isData) + { + MPTR weakExportAddr = osLib_getPointer(dependency->modulename, exportName); + cemu_assert_debug(weakExportAddr != 0xFFFFFFFF); + exportResult = weakExportAddr; + } + else + { + exportResult = rpl_mapHLEImport(rplLoaderContext, dependency->modulename, exportName, true); + } + } + + return exportResult; +} + +uint32 RPLLoader_GetSDA1Base() +{ + return rplLoader_sdataAddr; +} + +uint32 RPLLoader_GetSDA2Base() +{ + return rplLoader_sdata2Addr; +} + +RPLModule** RPLLoader_GetModuleList() +{ + return rplModuleList; +} + +sint32 RPLLoader_GetModuleCount() +{ + return rplModuleCount; +} + +template<typename TAddr, typename TSize> +class SimpleHeap +{ + struct allocEntry_t + { + TAddr start; + TAddr end; + allocEntry_t(TAddr start, TAddr end) : start(start), end(end) {}; + }; + +public: + SimpleHeap(TAddr baseAddress, TSize size) : m_base(baseAddress), m_size(size) + { + + } + + TAddr alloc(TSize size, TSize alignment) + { + cemu_assert_debug(alignment != 0); + TAddr allocBase = m_base; + allocBase = (allocBase + alignment - 1)&~(alignment-1); + while (true) + { + bool hasCollision = false; + for (auto& itr : list_allocatedEntries) + { + if (allocBase < itr.end && (allocBase + size) >= itr.start) + { + allocBase = itr.end; + allocBase = (allocBase + alignment - 1)&~(alignment - 1); + hasCollision = true; + break; + } + } + if(hasCollision == false) + break; + } + if ((allocBase + size) > (m_base + m_size)) + return 0; + list_allocatedEntries.emplace_back(allocBase, allocBase + size); + return allocBase; + } + + void free(TAddr addr) + { + for (sint32 i = 0; i < list_allocatedEntries.size(); i++) + { + if (list_allocatedEntries[i].start == addr) + { + list_allocatedEntries.erase(list_allocatedEntries.begin() + i); + return; + } + } + cemu_assert(false); + return; + } + +private: + TAddr m_base; + TSize m_size; + std::vector<allocEntry_t> list_allocatedEntries; +}; + +SimpleHeap<uint32, uint32> heapCodeCaveArea(MEMORY_CODECAVEAREA_ADDR, MEMORY_CODECAVEAREA_SIZE); + +MEMPTR<void> RPLLoader_AllocateCodeCaveMem(uint32 alignment, uint32 size) +{ + uint32 addr = heapCodeCaveArea.alloc(size, 256); + return MEMPTR<void>{addr}; +} + +void RPLLoader_ReleaseCodeCaveMem(MEMPTR<void> addr) +{ + heapCodeCaveArea.free(addr.GetMPTR()); +} diff --git a/src/Cafe/OS/RPL/rpl.h b/src/Cafe/OS/RPL/rpl.h new file mode 100644 index 00000000..d7eae552 --- /dev/null +++ b/src/Cafe/OS/RPL/rpl.h @@ -0,0 +1,55 @@ +#pragma once + +struct RPLModule; + +#define RPL_INVALID_HANDLE (0xFFFFFFFF) + +void RPLLoader_InitState(); +void RPLLoader_ResetState(); + +uint8* RPLLoader_AllocateTrampolineCodeSpace(sint32 size); + +MPTR RPLLoader_AllocateCodeSpace(uint32 size, uint32 alignment); + +uint32 RPLLoader_GetMaxCodeOffset(); +uint32 RPLLoader_GetDataAllocatorAddr(); + +__declspec(dllexport) RPLModule* rpl_loadFromMem(uint8* rplData, sint32 size, char* name); +uint32 rpl_mapHLEImport(RPLModule* rplLoaderContext, const char* rplName, const char* funcName, bool functionMustExist); +void RPLLoader_Link(); + +MPTR RPLLoader_FindRPLExport(RPLModule* rplLoaderContext, const char* symbolName, bool isData); +uint32 RPLLoader_GetModuleEntrypoint(RPLModule* rplLoaderContext); + +void RPLLoader_SetMainModule(RPLModule* rplLoaderContext); +uint32 RPLLoader_GetMainModuleHandle(); + +void RPLLoader_CallEntrypoints(); +void RPLLoader_NotifyControlPassedToApplication(); + +void RPLLoader_AddDependency(const char* name); +void RPLLoader_RemoveDependency(uint32 handle); +void RPLLoader_UpdateDependencies(); + +uint32 RPLLoader_GetHandleByModuleName(const char* name); +uint32 RPLLoader_GetMaxTLSModuleIndex(); +bool RPLLoader_GetTLSDataByTLSIndex(sint16 tlsModuleIndex, uint8** tlsData, sint32* tlsSize); + +uint32 RPLLoader_FindModuleOrHLEExport(uint32 moduleHandle, bool isData, const char* exportName); + +uint32 RPLLoader_GetSDA1Base(); +uint32 RPLLoader_GetSDA2Base(); + +sint32 RPLLoader_GetModuleCount(); +RPLModule** RPLLoader_GetModuleList(); + +MEMPTR<void> RPLLoader_AllocateCodeCaveMem(uint32 alignment, uint32 size); +void RPLLoader_ReleaseCodeCaveMem(MEMPTR<void> addr); + +// exports + +uint32 RPLLoader_MakePPCCallable(void(*ppcCallableExport)(struct PPCInterpreter_t* hCPU)); + +// elf loader + +uint32 ELF_LoadFromMemory(uint8* elfData, sint32 size, const char* name); \ No newline at end of file diff --git a/src/Cafe/OS/RPL/rpl_debug_symbols.cpp b/src/Cafe/OS/RPL/rpl_debug_symbols.cpp new file mode 100644 index 00000000..efcf7667 --- /dev/null +++ b/src/Cafe/OS/RPL/rpl_debug_symbols.cpp @@ -0,0 +1,21 @@ +#include "Cafe/OS/RPL/rpl_debug_symbols.h" + +std::map<MPTR, rplDebugSymbolBase*> map_DebugSymbols; + +void rplDebugSymbol_createComment(MPTR address, const wchar_t* comment) +{ + auto new_comment = new rplDebugSymbolComment(); + new_comment->type = RplDebugSymbolComment; + new_comment->comment = comment; + map_DebugSymbols[address] = new_comment; +} + +rplDebugSymbolBase* rplDebugSymbol_getForAddress(MPTR address) +{ + return map_DebugSymbols[address]; +} + +const std::map<MPTR, rplDebugSymbolBase*>& rplDebugSymbol_getSymbols() +{ + return map_DebugSymbols; +} \ No newline at end of file diff --git a/src/Cafe/OS/RPL/rpl_debug_symbols.h b/src/Cafe/OS/RPL/rpl_debug_symbols.h new file mode 100644 index 00000000..9c87eed4 --- /dev/null +++ b/src/Cafe/OS/RPL/rpl_debug_symbols.h @@ -0,0 +1,23 @@ +#pragma once +#include <map> + +enum RplDebugSymbolType : uint8 +{ + RplDebugSymbolComment = 0, +}; + +struct rplDebugSymbolBase +{ + RplDebugSymbolType type; + rplDebugSymbolBase* next; +}; + +struct rplDebugSymbolComment : rplDebugSymbolBase +{ + std::wstring comment; +}; + + +void rplDebugSymbol_createComment(MPTR address, const wchar_t* comment); +rplDebugSymbolBase* rplDebugSymbol_getForAddress(MPTR address); +const std::map<MPTR, rplDebugSymbolBase*>& rplDebugSymbol_getSymbols(); \ No newline at end of file diff --git a/src/Cafe/OS/RPL/rpl_structs.h b/src/Cafe/OS/RPL/rpl_structs.h new file mode 100644 index 00000000..18413049 --- /dev/null +++ b/src/Cafe/OS/RPL/rpl_structs.h @@ -0,0 +1,258 @@ +#pragma once + +#include "util/ChunkedHeap/ChunkedHeap.h" + +#define RPL_MODULE_NAME_LENGTH 64 +#define RPL_MODULE_PATH_LENGTH 256 + +// types +#define SHT_RPL_EXPORTS (0x80000001) +#define SHT_RPL_IMPORTS (0x80000002) +#define SHT_RPL_CRCS (0x80000003) +#define SHT_RPL_FILEINFO (0x80000004) + +#define SHT_PROGBITS (0x00000001) +#define SHT_SYMTAB (0x00000002) +#define SHT_STRTAB (0x00000003) +#define SHT_RELA (0x00000004) +#define SHT_HASH (0x00000005) +#define SHT_DYNAMIC (0x00000006) +#define SHT_NOTE (0x00000007) +#define SHT_NOBITS (0x00000008) // this section contains no data +#define SHT_REL (0x00000009) +#define SHT_SHLIB (0x0000000A) +#define SHT_DYNSYM (0x0000000B) + +// flags +#define SHF_EXECUTE 0x00000004 +#define SHF_RPL_COMPRESSED 0x08000000 +#define SHF_TLS 0x04000000 + +// relocs +#define RPL_RELOC_ADDR32 1 +#define RPL_RELOC_LO16 4 +#define RPL_RELOC_HI16 5 +#define RPL_RELOC_HA16 6 +#define RPL_RELOC_REL24 10 +#define RPL_RELOC_REL14 11 + +#define R_PPC_DTPMOD32 68 +#define R_PPC_DTPREL32 78 + +#define R_PPC_REL16_HA 251 +#define R_PPC_REL16_HI 252 +#define R_PPC_REL16_LO 253 + +#define HLE_MODULE_PTR ((RPLModule*)-1) + +typedef struct +{ + /* +0x00 */ uint32be relocOffset; + /* +0x04 */ uint32be symbolIndexAndType; + /* +0x08 */ uint32be relocAddend; +}rplRelocNew_t; + +typedef struct +{ + /* +0x00 */ uint32be nameOffset; + /* +0x04 */ uint32be type; + /* +0x08 */ uint32be flags; + /* +0x0C */ uint32be virtualAddress; + /* +0x10 */ uint32be fileOffset; + /* +0x14 */ uint32be sectionSize; + /* +0x18 */ uint32be symtabSectionIndex; + /* +0x1C */ uint32be relocTargetSectionIndex; + /* +0x20 */ uint32be alignment; + /* +0x24 */ uint32be ukn24; // for symtab: Size of each symbol entry +}rplSectionEntryNew_t; + +typedef struct +{ + /* +0x00 */ uint32be magic1; + /* +0x04 */ uint8 version04; // probably version? + /* +0x05 */ uint8 ukn05; // probably version? + /* +0x06 */ uint8 ukn06; // probably version? + /* +0x07 */ uint8 magic2_0; // part of second magic + /* +0x08 */ uint8 magic2_1; // part of second magic + /* +0x09 */ uint8 ukn09; + /* +0x0A */ uint8 ukn0A; + /* +0x0B */ uint8 ukn0B; + /* +0x0C */ uint32be dataRegionSize; + /* +0x10 */ uint8 ukn10; + /* +0x11 */ uint8 ukn11; + /* +0x12 */ uint16be ukn12; + /* +0x14 */ uint32be ukn14; + /* +0x18 */ uint32be entrypoint; + /* +0x1C */ uint32be ukn1C; + /* +0x20 */ uint32be sectionTableOffset; + /* +0x24 */ uint32be ukn24; + /* +0x28 */ uint16be ukn28; + /* +0x2A */ uint16be programHeaderTableEntrySize; + /* +0x2C */ uint16be programHeaderTableEntryCount; + /* +0x2E */ uint16be sectionTableEntrySize; + /* +0x30 */ uint16be sectionTableEntryCount; + /* +0x32 */ uint16be nameSectionIndex; +}rplHeaderNew_t; + +static_assert(offsetof(rplHeaderNew_t, dataRegionSize) == 0xC); +static_assert(offsetof(rplHeaderNew_t, programHeaderTableEntrySize) == 0x2A); + + +typedef struct +{ + /* +0x00 */ uint32be fileInfoMagic; // always 0xCAFE0402 + /* +0x04 */ uint32be textRegionSize; // text region size + /* +0x08 */ uint32be ukn08; // base align text + /* +0x0C */ uint32be dataRegionSize; // size of data sections + /* +0x10 */ uint32be baseAlign; // base align data? + /* +0x14 */ uint32be ukn14; + /* +0x18 */ uint32be ukn18; + /* +0x1C */ uint32be ukn1C; + /* +0x20 */ uint32be trampolineAdjustment; + /* +0x24 */ uint32be sdataBase1; + /* +0x28 */ uint32be sdataBase2; + /* +0x2C */ uint32be ukn2C; + /* +0x30 */ uint32be ukn30; + /* +0x34 */ uint32be ukn34; + /* +0x38 */ uint32be ukn38; + /* +0x3C */ uint32be ukn3C; + /* +0x40 */ uint32be toolkitVersion; + /* +0x44 */ uint32be ukn44; + /* +0x48 */ uint32be ukn48; + /* +0x4C */ uint32be ukn4C; + /* +0x50 */ uint32be ukn50; + /* +0x54 */ uint32be ukn54; + /* +0x58 */ sint16be tlsModuleIndex; +}RPLFileInfoData; + +static_assert(offsetof(RPLFileInfoData, tlsModuleIndex) == 0x58); + + +typedef struct +{ + //uint32 address; + void* ptr; +}rplSectionAddressEntry_t; + +typedef struct +{ + uint32be virtualOffset; + uint32be nameOffset; +}rplExportTableEntry_t; + +struct RPLModule +{ + uint32 ukn00; // pointer to shared memory region? (0xEFE01000) + uint32 ukn04; // related to text region size? + char* moduleNamePtr__depr; // converted to lower case + uint32 moduleNameLength__depr; // length of module name + uint32 moduleNameSize; // aligned alloc size, not the same as actual length + uint32 padding14; + uint32 padding18; + rplHeaderNew_t rplHeader; + rplSectionEntryNew_t* sectionTablePtr; // copy of section table + + RPLFileInfoData* fileInfoPtr__depr{}; // copy of fileinfo section + uint32 fileInfoSize__depr{}; // size of fileInfo section + uint32 fileInfoAllocSize__depr{}; // aligned alloc size + + uint32be* crcTablePtr_depr{}; // copy of CRC section + uint32 crcTableAllocSize_depr{}; + + uint32 entrypoint; + + uint8* rplData_depr; // Cemuhook might still read this + + MPTR textRegionTemp; // temporary memory for text section? + + MEMPTR<void> regionMappingBase_text; // base destination address for text region + MPTR regionMappingBase_data; // base destination address for data region + MPTR regionMappingBase_loaderInfo; // base destination address for loaderInfo region + uint8* tempRegionPtr; + uint32 tempRegionAllocSize; + + rplSectionAddressEntry_t* sectionAddressTable__depr; + uint32 sectionAddressTableSize__depr; + + uint32 exportDCount; + rplExportTableEntry_t* exportDDataPtr; + uint32 exportFCount; + rplExportTableEntry_t* exportFDataPtr; + + /* above are hardcoded in Cemuhook */ + std::string moduleName2; + + std::vector<rplSectionAddressEntry_t> sectionAddressTable2; + + uint32 tlsStartAddress; + uint32 tlsEndAddress; + uint32 regionSize_text; + uint32 regionSize_data; + uint32 regionSize_loaderInfo; + + uint32 patchCRC; // Cemuhook style module crc for patches.txt + + // trampoline management + ChunkedFlatAllocator<16 * 1024> heapTrampolineArea; + std::unordered_map<MPTR, MPTR> trampolineMap; + + // section data + std::vector<uint8> sectionData_fileInfo; + std::vector<uint8> sectionData_crc; + + // parsed FILEINFO + struct + { + uint32 textRegionSize; // size of region containing all text sections + //uint32 ukn08; // base align text? + uint32 dataRegionSize; // size of region containing all data sections + uint32 baseAlign; + uint32 ukn14; + uint32 trampolineAdjustment; + uint32 ukn4C; + sint16 tlsModuleIndex; + + uint32 sdataBase1; + uint32 sdataBase2; + }fileInfo; + // parsed CRC + std::vector<uint32> crcTable; + + uint32 GetSectionCRC(size_t sectionIndex) const + { + if (sectionIndex >= crcTable.size()) + return 0; + return crcTable[sectionIndex]; + } + + // state + bool isLinked; // set to true if _linkModule was called on this module + bool entrypointCalled; // set if entrypoint was called + + // allocator + betype<MPTR> funcAlloc; + betype<MPTR> funcFree; + + // replaces rplData ptr + std::span<uint8> RPLRawData; + + bool debugSectionLoadMask[128] = { false }; + bool hasError{ false }; + +}; + +typedef struct +{ + char modulename[RPL_MODULE_NAME_LENGTH]; + char filepath[RPL_MODULE_PATH_LENGTH]; + bool loadAttempted; + //bool isHLEModule; // determined to be a HLE module + RPLModule* rplLoaderContext; // context of loaded module + sint32 referenceCount; + uint32 coreinitHandle; // fake handle for coreinit + sint16 tlsModuleIndex; // tls module index assigned to this dependency +}rplDependency_t; + +RPLModule* RPLLoader_FindModuleByCodeAddr(uint32 addr); +RPLModule* RPLLoader_FindModuleByDataAddr(uint32 addr); +RPLModule* RPLLoader_FindModuleByName(std::string module); diff --git a/src/Cafe/OS/RPL/rpl_symbol_storage.cpp b/src/Cafe/OS/RPL/rpl_symbol_storage.cpp new file mode 100644 index 00000000..f4797c97 --- /dev/null +++ b/src/Cafe/OS/RPL/rpl_symbol_storage.cpp @@ -0,0 +1,150 @@ +#include "Cafe/OS/RPL/rpl.h" +#include "Cafe/OS/RPL/rpl_symbol_storage.h" + +struct rplSymbolLib_t +{ + char* libName; + rplSymbolLib_t* next; +}; + +struct +{ + rplSymbolLib_t* libs; + std::mutex m_symbolStorageMutex; + std::unordered_map<uint32, RPLStoredSymbol*> map_symbolByAddress; + // allocator for strings + char* strAllocatorBlock; + sint32 strAllocatorOffset; + std::vector<void*> list_strAllocatedBlocks; +}rplSymbolStorage = { 0 }; + +#define STR_ALLOC_BLOCK_SIZE (128*1024) // allocate 128KB blocks at once + +char* rplSymbolStorage_allocDupString(const char* str) +{ + sint32 len = (sint32)strlen(str); + if (rplSymbolStorage.strAllocatorBlock == nullptr || (rplSymbolStorage.strAllocatorOffset + len + 1) >= STR_ALLOC_BLOCK_SIZE) + { + // allocate new block + rplSymbolStorage.strAllocatorBlock = (char*)malloc(STR_ALLOC_BLOCK_SIZE); + rplSymbolStorage.strAllocatorOffset = 0; + rplSymbolStorage.list_strAllocatedBlocks.emplace_back(rplSymbolStorage.strAllocatorBlock); + } + cemu_assert_debug((rplSymbolStorage.strAllocatorOffset + len + 1) <= STR_ALLOC_BLOCK_SIZE); + char* allocatedStr = rplSymbolStorage.strAllocatorBlock + rplSymbolStorage.strAllocatorOffset; + rplSymbolStorage.strAllocatorOffset += len + 1; + strcpy(allocatedStr, str); + return allocatedStr; +} + +char* rplSymbolStorage_storeLibname(const char* libName) +{ + if (rplSymbolStorage.libs == NULL) + { + rplSymbolLib_t* libEntry = new rplSymbolLib_t(); + libEntry->libName = rplSymbolStorage_allocDupString(libName); + libEntry->next = NULL; + rplSymbolStorage.libs = libEntry; + return libEntry->libName; + } + rplSymbolLib_t* libItr = rplSymbolStorage.libs; + while (libItr) + { + if (boost::iequals(libItr->libName, libName)) + return libItr->libName; + // next + libItr = libItr->next; + } + // create new entry + rplSymbolLib_t* libEntry = new rplSymbolLib_t(); + libEntry->libName = rplSymbolStorage_allocDupString(libName); + libEntry->next = rplSymbolStorage.libs; + rplSymbolStorage.libs = libEntry; + return libEntry->libName; +} + +RPLStoredSymbol* rplSymbolStorage_store(const char* libName, const char* symbolName, MPTR address) +{ + std::unique_lock<std::mutex> lck(rplSymbolStorage.m_symbolStorageMutex); + char* libNameStorage = rplSymbolStorage_storeLibname(libName); + char* symbolNameStorage = rplSymbolStorage_allocDupString(symbolName); + RPLStoredSymbol* storedSymbol = new RPLStoredSymbol(); + storedSymbol->address = address; + storedSymbol->libName = libNameStorage; + storedSymbol->symbolName = symbolNameStorage; + storedSymbol->flags = 0; + rplSymbolStorage.map_symbolByAddress[address] = storedSymbol; + return storedSymbol; +} + +RPLStoredSymbol* rplSymbolStorage_getByAddress(MPTR address) +{ + std::unique_lock<std::mutex> lck(rplSymbolStorage.m_symbolStorageMutex); + return rplSymbolStorage.map_symbolByAddress[address]; +} + +void rplSymbolStorage_remove(RPLStoredSymbol* storedSymbol) +{ + std::unique_lock<std::mutex> lck(rplSymbolStorage.m_symbolStorageMutex); + if (rplSymbolStorage.map_symbolByAddress[storedSymbol->address] == storedSymbol) + rplSymbolStorage.map_symbolByAddress[storedSymbol->address] = nullptr; + delete storedSymbol; +} + +void rplSymbolStorage_removeRange(MPTR address, sint32 length) +{ + while (length > 0) + { + RPLStoredSymbol* symbol = rplSymbolStorage_getByAddress(address); + if (symbol) + rplSymbolStorage_remove(symbol); + address += 4; + length -= 4; + } +} + +void rplSymbolStorage_createJumpProxySymbol(MPTR jumpAddress, MPTR destAddress) +{ + RPLStoredSymbol* destSymbol = rplSymbolStorage_getByAddress(destAddress); + if (destSymbol) + rplSymbolStorage_store((char*)destSymbol->libName, (char*)destSymbol->symbolName, jumpAddress); +} + +std::unordered_map<uint32, RPLStoredSymbol*>& rplSymbolStorage_lockSymbolMap() +{ + rplSymbolStorage.m_symbolStorageMutex.lock(); + return rplSymbolStorage.map_symbolByAddress; +} + +void rplSymbolStorage_unlockSymbolMap() +{ + rplSymbolStorage.m_symbolStorageMutex.unlock(); +} + +void rplSymbolStorage_init() +{ + cemu_assert_debug(rplSymbolStorage.map_symbolByAddress.empty()); + cemu_assert_debug(rplSymbolStorage.strAllocatorBlock == nullptr); +} + +void rplSymbolStorage_unloadAll() +{ + // free symbols + for (auto& it : rplSymbolStorage.map_symbolByAddress) + delete it.second; + rplSymbolStorage.map_symbolByAddress.clear(); + // free libs + rplSymbolLib_t* lib = rplSymbolStorage.libs; + while (lib) + { + rplSymbolLib_t* next = lib->next; + delete lib; + lib = next; + } + rplSymbolStorage.libs = nullptr; + // free strings + for (auto it : rplSymbolStorage.list_strAllocatedBlocks) + free(it); + rplSymbolStorage.strAllocatorBlock = nullptr; + rplSymbolStorage.strAllocatorOffset = 0; +} diff --git a/src/Cafe/OS/RPL/rpl_symbol_storage.h b/src/Cafe/OS/RPL/rpl_symbol_storage.h new file mode 100644 index 00000000..0f179f59 --- /dev/null +++ b/src/Cafe/OS/RPL/rpl_symbol_storage.h @@ -0,0 +1,18 @@ +struct RPLStoredSymbol +{ + MPTR address; + void* libName; + void* symbolName; + uint32 flags; +}; + +void rplSymbolStorage_init(); +void rplSymbolStorage_unloadAll(); +RPLStoredSymbol* rplSymbolStorage_store(const char* libName, const char* symbolName, MPTR address); +void rplSymbolStorage_remove(RPLStoredSymbol* storedSymbol); +void rplSymbolStorage_removeRange(MPTR address, sint32 length); +RPLStoredSymbol* rplSymbolStorage_getByAddress(MPTR address); +void rplSymbolStorage_createJumpProxySymbol(MPTR jumpAddress, MPTR destAddress); + +std::unordered_map<uint32, RPLStoredSymbol*>& rplSymbolStorage_lockSymbolMap(); +void rplSymbolStorage_unlockSymbolMap(); \ No newline at end of file diff --git a/src/Cafe/OS/common/OSCommon.cpp b/src/Cafe/OS/common/OSCommon.cpp new file mode 100644 index 00000000..dbc9d16e --- /dev/null +++ b/src/Cafe/OS/common/OSCommon.cpp @@ -0,0 +1,220 @@ +#include "Cafe/HW/Espresso/PPCState.h" +#include "Cafe/OS/libs/proc_ui/proc_ui.h" +#include "Cafe/OS/libs/nsysnet/nsysnet.h" +#include "Cafe/OS/libs/nlibnss/nlibnss.h" +#include "Cafe/OS/libs/nlibcurl/nlibcurl.h" +#include "Cafe/OS/libs/nn_nfp/nn_nfp.h" +#include "Cafe/OS/libs/nn_act/nn_act.h" +#include "Cafe/OS/libs/nn_acp/nn_acp.h" +#include "Cafe/OS/libs/nn_ac/nn_ac.h" +#include "Cafe/OS/libs/nn_uds/nn_uds.h" +#include "Cafe/OS/libs/nn_nim/nn_nim.h" +#include "Cafe/OS/libs/nn_ndm/nn_ndm.h" +#include "Cafe/OS/libs/nn_ec/nn_ec.h" +#include "Cafe/OS/libs/nn_boss/nn_boss.h" +#include "Cafe/OS/libs/nn_fp/nn_fp.h" +#include "Cafe/OS/libs/nn_olv/nn_olv.h" +#include "Cafe/OS/libs/nn_idbe/nn_idbe.h" +#include "Cafe/OS/libs/nn_save/nn_save.h" +#include "Cafe/OS/libs/erreula/erreula.h" +#include "Cafe/OS/libs/sysapp/sysapp.h" +#include "Cafe/OS/libs/dmae/dmae.h" +#include "Cafe/OS/libs/snd_core/ax.h" +#include "Cafe/OS/libs/gx2/GX2.h" +#include "Cafe/OS/libs/vpad/vpad.h" +#include "Cafe/OS/libs/nsyskbd/nsyskbd.h" +#include "Cafe/OS/libs/nsyshid/nsyshid.h" +#include "Cafe/OS/libs/snd_user/snd_user.h" +#include "Cafe/OS/libs/zlib125/zlib125.h" +#include "Cafe/OS/libs/padscore/padscore.h" +#include "Cafe/OS/libs/camera/camera.h" +#include "../libs/swkbd/swkbd.h" + +struct osFunctionEntry_t +{ + uint32 libHashA; + uint32 libHashB; + uint32 funcHashA; + uint32 funcHashB; + std::string name; + HLEIDX hleFunc; + + osFunctionEntry_t(uint32 libHashA, uint32 libHashB, uint32 funcHashA, uint32 funcHashB, std::string_view name, HLEIDX hleFunc) : + libHashA(libHashA), libHashB(libHashB), funcHashA(funcHashA), funcHashB(funcHashB), name(name), hleFunc(hleFunc) {}; +}; + +typedef struct +{ + uint32 libHashA; + uint32 libHashB; + uint32 funcHashA; + uint32 funcHashB; + uint32 vPtr; +}osPointerEntry_t; + +std::vector<osFunctionEntry_t>* s_osFunctionTable; +std::vector<osPointerEntry_t> osDataTable; + +void osLib_generateHashFromName(const char* name, uint32* hashA, uint32* hashB) +{ + uint32 h1 = 0x688BA2BA; + uint32 h2 = 0xF64A71D5; + while( *name ) + { + uint32 c = (uint32)*name; + h1 += c; + h1 = (h1<<3)|((h1>>29)); + h2 ^= c; + h2 = (h2<<7)|((h2>>25)); + h1 += h2; + h2 += c; + h2 = (h2<<3)|((h2>>29)); + name++; + } + *hashA = h1; + *hashB = h2; +} + +void osLib_addFunctionInternal(const char* libraryName, const char* functionName, void(*osFunction)(PPCInterpreter_t* hCPU)) +{ + if (!s_osFunctionTable) + s_osFunctionTable = new std::vector<osFunctionEntry_t>(); // replace with static allocation + constinit once we have C++20 available + // calculate hash + uint32 libHashA, libHashB; + uint32 funcHashA, funcHashB; + osLib_generateHashFromName(libraryName, &libHashA, &libHashB); + osLib_generateHashFromName(functionName, &funcHashA, &funcHashB); + // if entry already exists, update it + for (auto& it : *s_osFunctionTable) + { + if (it.libHashA == libHashA && + it.libHashB == libHashB && + it.funcHashA == funcHashA && + it.funcHashB == funcHashB) + { + it.hleFunc = PPCInterpreter_registerHLECall(osFunction); + return; + } + } + s_osFunctionTable->emplace_back(libHashA, libHashB, funcHashA, funcHashB, fmt::format("{}.{}", libraryName, functionName), PPCInterpreter_registerHLECall(osFunction)); +} + +__declspec(dllexport) void osLib_registerHLEFunction(const char* libraryName, const char* functionName, void(*osFunction)(PPCInterpreter_t* hCPU)) +{ + osLib_addFunctionInternal(libraryName, functionName, osFunction); +} + +sint32 osLib_getFunctionIndex(const char* libraryName, const char* functionName) +{ + uint32 libHashA, libHashB; + uint32 funcHashA, funcHashB; + osLib_generateHashFromName(libraryName, &libHashA, &libHashB); + osLib_generateHashFromName(functionName, &funcHashA, &funcHashB); + for (auto& it : *s_osFunctionTable) + { + if (it.libHashA == libHashA && + it.libHashB == libHashB && + it.funcHashA == funcHashA && + it.funcHashB == funcHashB) + { + return it.hleFunc; + } + } + return -1; +} + +void osLib_addVirtualPointer(const char* libraryName, const char* functionName, uint32 vPtr) +{ + // calculate hash + uint32 libHashA, libHashB; + uint32 funcHashA, funcHashB; + osLib_generateHashFromName(libraryName, &libHashA, &libHashB); + osLib_generateHashFromName(functionName, &funcHashA, &funcHashB); + // if entry already exists, update it + for (auto& it : osDataTable) + { + if (it.libHashA == libHashA && + it.libHashB == libHashB && + it.funcHashA == funcHashA && + it.funcHashB == funcHashB) + { + it.vPtr = vPtr; + return; + } + } + // add entry + auto writeIndex = osDataTable.size(); + osDataTable.resize(osDataTable.size() + 1); + osDataTable[writeIndex].libHashA = libHashA; + osDataTable[writeIndex].libHashB = libHashB; + osDataTable[writeIndex].funcHashA = funcHashA; + osDataTable[writeIndex].funcHashB = funcHashB; + osDataTable[writeIndex].vPtr = vPtr; +} + +uint32 osLib_getPointer(const char* libraryName, const char* functionName) +{ + uint32 libHashA, libHashB; + uint32 funcHashA, funcHashB; + osLib_generateHashFromName(libraryName, &libHashA, &libHashB); + osLib_generateHashFromName(functionName, &funcHashA, &funcHashB); + for (auto& it : osDataTable) + { + if (it.libHashA == libHashA && + it.libHashB == libHashB && + it.funcHashA == funcHashA && + it.funcHashB == funcHashB) + { + return it.vPtr; + } + } + return 0xFFFFFFFF; +} + +void osLib_returnFromFunction(PPCInterpreter_t* hCPU, uint32 returnValue) +{ + hCPU->gpr[3] = returnValue; + hCPU->instructionPointer = hCPU->spr.LR; +} + +void osLib_returnFromFunction64(PPCInterpreter_t* hCPU, uint64 returnValue64) +{ + hCPU->gpr[3] = (returnValue64>>32)&0xFFFFFFFF; + hCPU->gpr[4] = (returnValue64>>0)&0xFFFFFFFF; + hCPU->instructionPointer = hCPU->spr.LR; +} + +void osLib_load() +{ + // load HLE modules + coreinit_load(); + zlib::load(); + gx2_load(); + dmae_load(); + padscore::load(); + vpad::load(); + snd_core::loadExports(); + nn::erreula::load(); + nnAct_load(); + nn::acp::load(); + nnAc_load(); + nnEc_load(); + nnBoss_load(); + nn::nfp::load(); + nnUds_load(); + nn::nim::load(); + nn::ndm::load(); + nn::save::load(); + nsysnet_load(); + nn::fp::load(); + nn::olv::load(); + nn::idbe::load(); + nlibnss::load(); + nlibcurl::load(); + sysapp_load(); + nsyshid::load(); + nsyskbd::nsyskbd_load(); + swkbd::load(); + camera::load(); + procui_load(); +} \ No newline at end of file diff --git a/src/Cafe/OS/common/OSCommon.h b/src/Cafe/OS/common/OSCommon.h new file mode 100644 index 00000000..4fb65a47 --- /dev/null +++ b/src/Cafe/OS/common/OSCommon.h @@ -0,0 +1,25 @@ +#pragma once + +struct PPCInterpreter_t; + + +#define OSLIB_FUNCTIONTABLE_TYPE_FUNCTION (1) +#define OSLIB_FUNCTIONTABLE_TYPE_POINTER (2) + +void osLib_load(); +void osLib_generateHashFromName(const char* name, uint32* hashA, uint32* hashB); +sint32 osLib_getFunctionIndex(const char* libraryName, const char* functionName); +uint32 osLib_getPointer(const char* libraryName, const char* functionName); + +void osLib_addFunctionInternal(const char* libraryName, const char* functionName, void(*osFunction)(PPCInterpreter_t* hCPU)); +#define osLib_addFunction(__p1, __p2, __p3) osLib_addFunctionInternal((const char*)__p1, __p2, __p3) +void osLib_addVirtualPointer(const char* libraryName, const char* functionName, uint32 vPtr); + +void osLib_returnFromFunction(PPCInterpreter_t* hCPU, uint32 returnValue); +void osLib_returnFromFunction64(PPCInterpreter_t* hCPU, uint64 returnValue64); + +// libs +#include "Cafe/OS/libs/coreinit/coreinit.h" + +// utility functions +#include "Cafe/OS/common/OSUtil.h" diff --git a/src/Cafe/OS/common/OSUtil.h b/src/Cafe/OS/common/OSUtil.h new file mode 100644 index 00000000..9bf8480b --- /dev/null +++ b/src/Cafe/OS/common/OSUtil.h @@ -0,0 +1,233 @@ +#pragma once +#include "Cafe/OS/libs/coreinit/coreinit_Thread.h" +#include "Cafe/HW/Espresso/PPCState.h" +#include "Cafe/HW/MMU/MMU.h" + +#include <fmt/ostream.h> +#include <fmt/compile.h> +#include <fmt/ranges.h> + +class cafeExportParamWrapper +{ +public: + template <typename T> + static void getParamWrapper(PPCInterpreter_t* hCPU, int& gprIndex, int& fprIndex, T& v) + { + if constexpr (std::is_pointer_v<T>) + { + uint32be addr; + if (gprIndex >= 8) + addr = memory_readU32(hCPU->gpr[1] + 8 + (gprIndex - 8) * 4); + else + addr = hCPU->gpr[3 + gprIndex]; + + using TPtr = std::remove_pointer_t<T>; + v = MEMPTR<TPtr>(addr).GetPtr(); + gprIndex++; + } + else if constexpr (std::is_base_of_v<MEMPTRBase, T>) + { + uint32be addr; + if (gprIndex >= 8) + addr = memory_readU32(hCPU->gpr[1] + 8 + (gprIndex - 8) * 4); + else + addr = hCPU->gpr[3 + gprIndex]; + + v = addr.value(); + gprIndex++; + } + else if constexpr (std::is_enum_v<T>) + { + using TEnum = std::underlying_type_t<T>; + getParamWrapper<TEnum>(hCPU, gprIndex, fprIndex, (TEnum&)v); + } + else if constexpr (std::is_integral_v<T>) + { + if constexpr (sizeof(T) == sizeof(uint64)) + { + gprIndex = (gprIndex + 1)&~1; + if (gprIndex >= 8) + v = (T)memory_readU64(hCPU->gpr[1] + 8 + (gprIndex - 8) * 4); + else + v = (T)(((uint64)hCPU->gpr[3 + gprIndex]) << 32) | ((uint64)hCPU->gpr[3 + gprIndex + 1]); + + gprIndex += 2; + } + else + { + if (gprIndex >= 8) + v = (T)memory_readU32(hCPU->gpr[1] + 8 + (gprIndex - 8) * 4); + else + v = (T)hCPU->gpr[3 + gprIndex]; + + gprIndex++; + } + } + else if constexpr (std::is_floating_point_v<T>) + { + v = (T)ppcInterpreterCurrentInstance->fpr[1 + fprIndex].fpr; + fprIndex++; + } + else + { + assert_dbg(); + } + } + + template<typename T> + static void setReturnResult(PPCInterpreter_t* hCPU, T r) + { + if constexpr (std::is_pointer_v<T>) + { + hCPU->gpr[3] = MEMPTR(r).GetMPTR(); + } + else if constexpr (std::is_reference_v<T>) + { + hCPU->gpr[3] = MEMPTR(&r).GetMPTR(); + } + else if constexpr (std::is_enum_v<T>) + { + using TEnum = std::underlying_type_t<T>; + setReturnResult<TEnum>(hCPU, (TEnum)r); + } + else if constexpr (std::is_integral_v<T>) + { + if constexpr(sizeof(T) == 8) + { + const auto t = static_cast<uint64>(r); + hCPU->gpr[3] = (uint32)(t >> 32); // high + hCPU->gpr[4] = (uint32)(t); // low + } + else + { + hCPU->gpr[3] = (uint32)r; + } + } + else + { + cemu_assert_unimplemented(); + //static_assert(false); + } + } + + template<typename T> + static auto getFormatResult(T r) + { + if constexpr (std::is_pointer_v<T>) + return MEMPTR(r).GetMPTR(); + else if constexpr (std::is_enum_v<T>) + return static_cast<std::underlying_type_t<T>>(r); + else if constexpr(!std::is_fundamental_v<T>) + return MEMPTR(&r).GetMPTR(); + else + return r; + } +}; + +template<typename T> +T cafeExportGetParamWrapper(PPCInterpreter_t* hCPU, int& gprIndex, int& fprIndex) +{ + T v; + cafeExportParamWrapper::getParamWrapper(hCPU, gprIndex, fprIndex, v); + return v; +} + +template <typename R, typename ... Args> +static std::tuple<Args...> cafeExportBuildArgTuple(PPCInterpreter_t* hCPU, R(fn)(Args...)) +{ + int gprIndex = 0; + int fprIndex = 0; + return std::tuple<Args...>{ cafeExportGetParamWrapper<Args>(hCPU, gprIndex, fprIndex)... }; +} + +template<typename T> +using _CAFE_FORMAT_ARG = std::conditional_t<std::is_pointer_v<T>, + std::conditional_t<std::is_same_v<T, char*> || std::is_same_v<T, const char*>, T, MEMPTR<T>>, T>; + +template <typename R, typename... Args> +static auto cafeExportBuildFormatTuple(PPCInterpreter_t* hCPU, R(fn)(Args...)) +{ + int gprIndex = 0; + int fprIndex = 0; + return std::tuple<_CAFE_FORMAT_ARG<Args>...>{ + cafeExportGetParamWrapper<_CAFE_FORMAT_ARG<Args>>(hCPU, gprIndex, fprIndex)... + }; +} + +template<auto fn, typename TNames, LogType TLogType> +void cafeExportCallWrapper(PPCInterpreter_t* hCPU) +{ + auto tup = cafeExportBuildArgTuple(hCPU, fn); + bool shouldLog = false; + if (cemuLog_isLoggingEnabled(TLogType)) + { + const auto format_tup = cafeExportBuildFormatTuple(hCPU, fn); + if(cemuLog_advancedPPCLoggingEnabled()) + { + MPTR threadMPTR = memory_getVirtualOffsetFromPointer(coreinit::OSGetCurrentThread()); + if constexpr (std::tuple_size<decltype(format_tup)>::value > 0) + shouldLog = cemuLog_log(TLogType, "{}.{}{} # LR: {:#x} | Thread: {:#x}", TNames::GetLib(), TNames::GetFunc(), format_tup, hCPU->spr.LR, threadMPTR); + else + shouldLog = cemuLog_log(TLogType, "{}.{}() # LR: {:#x} | Thread: {:#x}", TNames::GetLib(), TNames::GetFunc(), hCPU->spr.LR, threadMPTR); + } + else + { + if constexpr (std::tuple_size<decltype(format_tup)>::value > 0) + { + shouldLog = cemuLog_log(TLogType, "{}.{}{}", TNames::GetLib(), TNames::GetFunc(), format_tup); + } + else + shouldLog = cemuLog_log(TLogType, "{}.{}()", TNames::GetLib(), TNames::GetFunc()); + } + } + + if constexpr (!std::is_void<decltype(std::apply(fn, tup))>::value) + { + // has non-void return type + decltype(auto) result = std::apply(fn, tup); + cafeExportParamWrapper::setReturnResult<decltype(std::apply(fn, tup))>(hCPU, result); + if(shouldLog) + cemuLog_log(TLogType, "\t\t{}.{} -> {}", TNames::GetLib(), TNames::GetFunc(), cafeExportParamWrapper::getFormatResult(result)); + } + else + { + // return type is void + std::apply(fn, tup); + } + // return from func + hCPU->instructionPointer = hCPU->spr.LR; +} + +void osLib_addFunctionInternal(const char* libraryName, const char* functionName, void(*osFunction)(PPCInterpreter_t* hCPU)); + +template<auto fn, typename TNames, LogType TLogType> +void cafeExportMakeWrapper(const char* libname, const char* funcname) +{ + osLib_addFunctionInternal(libname, funcname, &cafeExportCallWrapper<fn, TNames, TLogType>); +} + +#define cafeExportRegister(__libname, __func, __logtype) \ + { \ + struct StringWrapper { \ + static const char* GetLib() { return __libname; }; \ + static const char* GetFunc() { return #__func; }; \ + }; \ + cafeExportMakeWrapper<__func, StringWrapper, __logtype>(__libname, # __func);\ + } + +#define cafeExportRegisterFunc(__func, __libname, __funcname, __logtype) \ + {\ + struct StringWrapper { \ + static const char* GetLib() { return __libname; }; \ + static const char* GetFunc() { return __funcname; }; \ + }; \ + cafeExportMakeWrapper<__func, StringWrapper, __logtype>(__libname, __funcname);\ + } + +template<auto fn> +MPTR makeCallableExport() +{ + return PPCInterpreter_makeCallableExportDepr(&cafeExportCallWrapper<fn, "CALLABLE_EXPORT">); +} + +void osLib_addVirtualPointer(const char* libraryName, const char* functionName, uint32 vPtr); \ No newline at end of file diff --git a/src/Cafe/OS/common/PPCConcurrentQueue.h b/src/Cafe/OS/common/PPCConcurrentQueue.h new file mode 100644 index 00000000..089515c5 --- /dev/null +++ b/src/Cafe/OS/common/PPCConcurrentQueue.h @@ -0,0 +1,68 @@ +#pragma once + +#include <mutex> +#include <condition_variable> +#include <queue> + +#include "Cafe/OS/libs/coreinit/coreinit_Thread.h" + +template <typename T> +class PPCConcurrentQueue +{ +public: + PPCConcurrentQueue() {} + PPCConcurrentQueue(const PPCConcurrentQueue&) = delete; + PPCConcurrentQueue& operator=(const PPCConcurrentQueue&) = delete; + + void push(const T& item, OSThread_t* thread) + { + //if(thread == nullptr) + // thread = coreinitThread_getCurrentThread(ppcInterpreterCurrentInstance); + //OSThread_t* currentThread = coreinit::OSGetCurrentThread(); + //cemu_assert_debug(thread == nullptr || currentThread == thread); + + // forceLogDebug_printf("push suspend count: %d", _swapEndianU32(thread->suspend) - m_suspendCount); + + //__OSLockScheduler(); + + __OSLockScheduler(); + m_queue.push(item); + coreinit::__OSResumeThreadInternal(thread, 1); + __OSUnlockScheduler(); + + //__OSUnlockScheduler(); + + //m_prevSuspendCount = _swapEndianU32(thread->suspend) - m_suspendCount; + //coreinit_resumeThread(thread, _swapEndianU32(thread->suspend)); + } + + T pop(OSThread_t* thread = nullptr) + { + //if (thread == nullptr) + // thread = coreinitThread_getCurrentThread(ppcInterpreterCurrentInstance); + + OSThread_t* currentThread = coreinit::OSGetCurrentThread(); + cemu_assert_debug(thread == nullptr || currentThread == thread); + + //thread = coreinitThread_getCurrentThread(ppcInterpreterCurrentInstance); + + // forceLogDebug_printf("pop suspend count: %d", _swapEndianU32(thread->suspend) + m_suspendCount); + + __OSLockScheduler(); + if (m_queue.empty()) + coreinit::__OSSuspendThreadInternal(thread); + auto val = m_queue.front(); + m_queue.pop(); + __OSUnlockScheduler(); + + //coreinit_suspendThread(thread, m_suspendCount + m_prevSuspendCount); + //m_prevSuspendCount = 0; + //PPCCore_switchToScheduler(); + + return val; + } +private: + //const int m_suspendCount = 8000; + std::queue<T> m_queue; + //std::atomic<uint32> m_prevSuspendCount; +}; diff --git a/src/Cafe/OS/libs/TCL/TCL.cpp b/src/Cafe/OS/libs/TCL/TCL.cpp new file mode 100644 index 00000000..d0c29d44 --- /dev/null +++ b/src/Cafe/OS/libs/TCL/TCL.cpp @@ -0,0 +1,28 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/libs/TCL/TCL.h" + +namespace TCL +{ + + enum class TCL_SUBMISSION_FLAG : uint32 + { + SURFACE_SYNC = 0x400000, // submit surface sync packet before cmd + TRIGGER_INTERRUPT = 0x200000, // probably + UKN_20000000 = 0x20000000, + }; + + int TCLSubmitToRing(uint32be* cmd, uint32 cmdLen, uint32be* controlFlags, uint64* submissionTimestamp) + { + // todo - figure out all the bits of *controlFlags + // if submissionTimestamp != nullptr then set it to the timestamp of the submission. Note: We should make sure that uint64's are written atomically by the GPU command processor + + cemu_assert_debug(false); + + return 0; + } + + void Initialize() + { + cafeExportRegister("TCL", TCLSubmitToRing, LogType::Placeholder); + } +} diff --git a/src/Cafe/OS/libs/TCL/TCL.h b/src/Cafe/OS/libs/TCL/TCL.h new file mode 100644 index 00000000..ab5358b0 --- /dev/null +++ b/src/Cafe/OS/libs/TCL/TCL.h @@ -0,0 +1,4 @@ +namespace TCL +{ + void Initialize(); +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/avm/avm.cpp b/src/Cafe/OS/libs/avm/avm.cpp new file mode 100644 index 00000000..d419f8c0 --- /dev/null +++ b/src/Cafe/OS/libs/avm/avm.cpp @@ -0,0 +1,40 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "avm.h" + +namespace avm +{ + bool AVMIsHDCPAvailable() + { + return true; + } + + bool AVMIsHDCPOn() + { + return true; + } + + bool AVMGetAnalogContentsProtectionEnable(uint32be* isEnable) + { + *isEnable = 1; + return false; + } + + bool AVMIsAnalogContentsProtectionOn() + { + return true; + } + + bool AVMSetAnalogContentsProtectionEnable(sint32 newState) + { + return true; // returns 1 (true) if new state was applied successfully? + } + + void Initialize() + { + cafeExportRegister("avm", AVMIsHDCPAvailable, LogType::Placeholder); + cafeExportRegister("avm", AVMIsHDCPOn, LogType::Placeholder); + cafeExportRegister("avm", AVMGetAnalogContentsProtectionEnable, LogType::Placeholder); + cafeExportRegister("avm", AVMIsAnalogContentsProtectionOn, LogType::Placeholder); + cafeExportRegister("avm", AVMSetAnalogContentsProtectionEnable, LogType::Placeholder); + } +} diff --git a/src/Cafe/OS/libs/avm/avm.h b/src/Cafe/OS/libs/avm/avm.h new file mode 100644 index 00000000..a6127365 --- /dev/null +++ b/src/Cafe/OS/libs/avm/avm.h @@ -0,0 +1,5 @@ + +namespace avm +{ + void Initialize(); +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/camera/camera.cpp b/src/Cafe/OS/libs/camera/camera.cpp new file mode 100644 index 00000000..4debb37f --- /dev/null +++ b/src/Cafe/OS/libs/camera/camera.cpp @@ -0,0 +1,257 @@ +#include "Common/precompiled.h" +#include "Cafe/OS/common/OSCommon.h" +#include "camera.h" +#include "Cafe/OS/RPL/rpl.h" +#include "Cafe/OS/libs/coreinit/coreinit_Alarm.h" +#include "Cafe/OS/libs/coreinit/coreinit_Time.h" +#include "Cafe/HW/Espresso/PPCCallback.h" + +namespace camera +{ + + struct CAMInitInfo_t + { + /* +0x00 */ uint32be ukn00; + /* +0x04 */ uint32be width; + /* +0x08 */ uint32be height; + + /* +0x0C */ uint32be workMemorySize; + /* +0x10 */ MEMPTR<void> workMemory; + + /* +0x14 */ uint32be handlerFuncPtr; + + /* +0x18 */ uint32be ukn18; + /* +0x1C */ uint32be fps; + + /* +0x20 */ uint32be ukn20; + }; + + struct CAMTargetSurface + { + /* +0x00 */ uint32be surfaceSize; + /* +0x04 */ MEMPTR<void> surfacePtr; + /* +0x08 */ uint32be ukn08; + /* +0x0C */ uint32be ukn0C; + /* +0x10 */ uint32be ukn10; + /* +0x14 */ uint32be ukn14; + /* +0x18 */ uint32be ukn18; + /* +0x1C */ uint32be ukn1C; + }; + + struct CAMCallbackParam + { + // type 0 - frame decoded | field1 - imagePtr, field2 - imageSize, field3 - ukn (0) + // type 1 - ??? + + + /* +0x0 */ uint32be type; // 0 -> Frame decoded + /* +0x4 */ uint32be field1; + /* +0x8 */ uint32be field2; + /* +0xC */ uint32be field3; + }; + + + #define CAM_ERROR_SUCCESS 0 + #define CAM_ERROR_INVALID_HANDLE -8 + + std::vector<struct CameraInstance*> g_table_cameraHandles; + std::vector<struct CameraInstance*> g_activeCameraInstances; + std::recursive_mutex g_mutex_camera; + std::atomic_int g_cameraCounter{ 0 }; + SysAllocator<coreinit::OSAlarm_t, 1> g_alarm_camera; + SysAllocator<CAMCallbackParam, 1> g_cameraHandlerParam; + + CameraInstance* GetCameraInstanceByHandle(sint32 camHandle) + { + std::unique_lock<std::recursive_mutex> _lock(g_mutex_camera); + if (camHandle <= 0) + return nullptr; + camHandle -= 1; + if (camHandle >= g_table_cameraHandles.size()) + return nullptr; + return g_table_cameraHandles[camHandle]; + } + + struct CameraInstance + { + CameraInstance(uint32 frameWidth, uint32 frameHeight, MPTR handlerFunc) : width(frameWidth), height(frameHeight), handlerFunc(handlerFunc) { AcquireHandle(); }; + ~CameraInstance() { if (isOpen) { CloseCam(); } ReleaseHandle(); }; + + sint32 handle{ 0 }; + uint32 width; + uint32 height; + bool isOpen{false}; + std::queue<CAMTargetSurface> queue_targetSurfaces; + MPTR handlerFunc; + + bool OpenCam() + { + if (isOpen) + return false; + isOpen = true; + g_activeCameraInstances.push_back(this); + return true; + } + + bool CloseCam() + { + if (!isOpen) + return false; + isOpen = false; + vectorRemoveByValue(g_activeCameraInstances, this); + return true; + } + + void QueueTargetSurface(CAMTargetSurface* targetSurface) + { + std::unique_lock<std::recursive_mutex> _lock(g_mutex_camera); + cemu_assert_debug(queue_targetSurfaces.size() < 100); // check for sane queue length + queue_targetSurfaces.push(*targetSurface); + } + + private: + void AcquireHandle() + { + std::unique_lock<std::recursive_mutex> _lock(g_mutex_camera); + for (uint32 i = 0; i < g_table_cameraHandles.size(); i++) + { + if (g_table_cameraHandles[i] == nullptr) + { + g_table_cameraHandles[i] = this; + this->handle = i + 1; + return; + } + } + this->handle = (sint32)(g_table_cameraHandles.size() + 1); + g_table_cameraHandles.push_back(this); + } + + void ReleaseHandle() + { + for (uint32 i = 0; i < g_table_cameraHandles.size(); i++) + { + if (g_table_cameraHandles[i] == this) + { + g_table_cameraHandles[i] = nullptr; + return; + } + } + cemu_assert_debug(false); + } + }; + + sint32 CAMGetMemReq(void* ukn) + { + return 1 * 1024; // always return 1KB + } + + sint32 CAMCheckMemSegmentation(void* base, uint32 size) + { + return CAM_ERROR_SUCCESS; // always return success + } + + void ppcCAMUpdate60(PPCInterpreter_t* hCPU) + { + // update all open camera instances + size_t numCamInstances = g_activeCameraInstances.size(); + //for (auto& itr : g_activeCameraInstances) + for(size_t i=0; i<numCamInstances; i++) + { + std::unique_lock<std::recursive_mutex> _lock(g_mutex_camera); + if (i >= g_activeCameraInstances.size()) + break; + CameraInstance* camInstance = g_activeCameraInstances[i]; + // todo - handle 30 / 60 FPS + if (camInstance->queue_targetSurfaces.empty()) + continue; + auto& targetSurface = camInstance->queue_targetSurfaces.front(); + g_cameraHandlerParam->type = 0; + g_cameraHandlerParam->field1 = targetSurface.surfacePtr.GetMPTR(); + g_cameraHandlerParam->field2 = targetSurface.surfaceSize; + g_cameraHandlerParam->field3 = 0; + cemu_assert_debug(camInstance->handlerFunc != MPTR_NULL); + camInstance->queue_targetSurfaces.pop(); + _lock.unlock(); + PPCCoreCallback(camInstance->handlerFunc, g_cameraHandlerParam.GetPtr()); + } + osLib_returnFromFunction(hCPU, 0); + } + + + sint32 CAMInit(uint32 cameraId, CAMInitInfo_t* camInitInfo, uint32be* error) + { + CameraInstance* camInstance = new CameraInstance(camInitInfo->width, camInitInfo->height, camInitInfo->handlerFuncPtr); + + std::unique_lock<std::recursive_mutex> _lock(g_mutex_camera); + if (g_cameraCounter == 0) + { + coreinit::OSCreateAlarm(g_alarm_camera.GetPtr()); + coreinit::OSSetPeriodicAlarm(g_alarm_camera.GetPtr(), coreinit::coreinit_getOSTime(), (uint64)ESPRESSO_TIMER_CLOCK / 60ull, RPLLoader_MakePPCCallable(ppcCAMUpdate60)); + } + g_cameraCounter++; + + return camInstance->handle; + } + + sint32 CAMExit(sint32 camHandle) + { + CameraInstance* camInstance = GetCameraInstanceByHandle(camHandle); + if (!camInstance) + return CAM_ERROR_INVALID_HANDLE; + CAMClose(camHandle); + delete camInstance; + + std::unique_lock<std::recursive_mutex> _lock(g_mutex_camera); + g_cameraCounter--; + if (g_cameraCounter == 0) + coreinit::OSCancelAlarm(g_alarm_camera.GetPtr()); + return CAM_ERROR_SUCCESS; + } + + sint32 CAMOpen(sint32 camHandle) + { + CameraInstance* camInstance = GetCameraInstanceByHandle(camHandle); + if (!camInstance) + return CAM_ERROR_INVALID_HANDLE; + camInstance->OpenCam(); + return CAM_ERROR_SUCCESS; + } + + sint32 CAMClose(sint32 camHandle) + { + CameraInstance* camInstance = GetCameraInstanceByHandle(camHandle); + if (!camInstance) + return CAM_ERROR_INVALID_HANDLE; + camInstance->CloseCam(); + return CAM_ERROR_SUCCESS; + } + + sint32 CAMSubmitTargetSurface(sint32 camHandle, CAMTargetSurface* targetSurface) + { + CameraInstance* camInstance = GetCameraInstanceByHandle(camHandle); + if (!camInstance) + return CAM_ERROR_INVALID_HANDLE; + + camInstance->QueueTargetSurface(targetSurface); + + return CAM_ERROR_SUCCESS; + } + + void reset() + { + g_cameraCounter = 0; + } + + void load() + { + reset(); + cafeExportRegister("camera", CAMGetMemReq, LogType::Placeholder); + cafeExportRegister("camera", CAMCheckMemSegmentation, LogType::Placeholder); + cafeExportRegister("camera", CAMInit, LogType::Placeholder); + cafeExportRegister("camera", CAMExit, LogType::Placeholder); + cafeExportRegister("camera", CAMOpen, LogType::Placeholder); + cafeExportRegister("camera", CAMClose, LogType::Placeholder); + cafeExportRegister("camera", CAMSubmitTargetSurface, LogType::Placeholder); + } +} + diff --git a/src/Cafe/OS/libs/camera/camera.h b/src/Cafe/OS/libs/camera/camera.h new file mode 100644 index 00000000..04248bbc --- /dev/null +++ b/src/Cafe/OS/libs/camera/camera.h @@ -0,0 +1,10 @@ +#pragma once + +namespace camera +{ + + sint32 CAMOpen(sint32 camHandle); + sint32 CAMClose(sint32 camHandle); + + void load(); +}; \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit.cpp b/src/Cafe/OS/libs/coreinit/coreinit.cpp new file mode 100644 index 00000000..4a9f3f30 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit.cpp @@ -0,0 +1,378 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Common/SysAllocator.h" +#include "Cafe/OS/RPL/rpl.h" + +#include "Cafe/OS/libs/coreinit/coreinit_Misc.h" + +// includes for Initialize coreinit submodules +#include "Cafe/OS/libs/coreinit/coreinit_BSP.h" +#include "Cafe/OS/libs/coreinit/coreinit_Scheduler.h" +#include "Cafe/OS/libs/coreinit/coreinit_Atomic.h" +#include "Cafe/OS/libs/coreinit/coreinit_OverlayArena.h" +#include "Cafe/OS/libs/coreinit/coreinit_DynLoad.h" +#include "Cafe/OS/libs/coreinit/coreinit_GHS.h" +#include "Cafe/OS/libs/coreinit/coreinit_HWInterface.h" +#include "Cafe/OS/libs/coreinit/coreinit_Memory.h" +#include "Cafe/OS/libs/coreinit/coreinit_IM.h" +#include "Cafe/OS/libs/coreinit/coreinit_LockedCache.h" +#include "Cafe/OS/libs/coreinit/coreinit_MemoryMapping.h" +#include "Cafe/OS/libs/coreinit/coreinit_IPC.h" +#include "Cafe/OS/libs/coreinit/coreinit_IPCBuf.h" +#include "Cafe/OS/libs/coreinit/coreinit_Coroutine.h" +#include "Cafe/OS/libs/coreinit/coreinit_OSScreen.h" +#include "Cafe/OS/libs/coreinit/coreinit_FG.h" +#include "Cafe/OS/libs/coreinit/coreinit_SystemInfo.h" +#include "Cafe/OS/libs/coreinit/coreinit_SysHeap.h" +#include "Cafe/OS/libs/coreinit/coreinit_MCP.h" +#include "Cafe/OS/libs/coreinit/coreinit_Time.h" +#include "Cafe/OS/libs/coreinit/coreinit_Alarm.h" +#include "Cafe/OS/libs/coreinit/coreinit_CodeGen.h" +#include "Cafe/OS/libs/coreinit/coreinit_Thread.h" +#include "Cafe/OS/libs/coreinit/coreinit_MPQueue.h" +#include "Cafe/OS/libs/coreinit/coreinit_FS.h" +#include "Cafe/OS/libs/coreinit/coreinit_MEM_UnitHeap.h" +#include "Cafe/OS/libs/coreinit/coreinit_MEM_FrmHeap.h" +#include "Cafe/OS/libs/coreinit/coreinit_MEM_BlockHeap.h" +#include "Cafe/OS/libs/coreinit/coreinit_MEM_ExpHeap.h" + +coreinitData_t* gCoreinitData = NULL; + +sint32 ScoreStackTrace(OSThread_t* thread, MPTR sp) +{ + uint32 stackMinAddr = _swapEndianU32(thread->stackEnd); + uint32 stackMaxAddr = _swapEndianU32(thread->stackBase); + + sint32 score = 0; + uint32 currentStackPtr = sp; + for (sint32 i = 0; i < 50; i++) + { + uint32 nextStackPtr = memory_readU32(currentStackPtr); + if (nextStackPtr < currentStackPtr) + break; + if (nextStackPtr < stackMinAddr || nextStackPtr > stackMaxAddr) + break; + if ((nextStackPtr & 3) != 0) + break; + score += 10; + + uint32 returnAddress = 0; + returnAddress = memory_readU32(nextStackPtr + 4); + //cemuLog_log(LogType::Force, fmt::format("SP {0:08x} ReturnAddress {1:08x}", nextStackPtr, returnAddress)); + if (returnAddress > 0 && returnAddress < 0x10000000 && (returnAddress&3) == 0) + score += 5; // within code region + else + score -= 5; + + currentStackPtr = nextStackPtr; + + } + return score; +} + +void DebugLogStackTrace(OSThread_t* thread, MPTR sp) +{ + // sp might not point to a valid stackframe + // scan stack and evaluate which sp is most likely the beginning of the stackframe + + // scan 0x400 bytes + sint32 highestScore = -1; + uint32 highestScoreSP = sp; + for (sint32 i = 0; i < 0x100; i++) + { + uint32 sampleSP = sp + i * 4; + sint32 score = ScoreStackTrace(thread, sampleSP); + if (score > highestScore) + { + highestScore = score; + highestScoreSP = sampleSP; + } + } + + if (highestScoreSP != sp) + cemuLog_log(LogType::Force, fmt::format("Trace starting at SP {0:08x} r1 = {1:08x}", highestScoreSP, sp)); + else + cemuLog_log(LogType::Force, fmt::format("Trace starting at SP/r1 {0:08x}", highestScoreSP)); + + // print stack trace + uint32 currentStackPtr = highestScoreSP; + uint32 stackMinAddr = _swapEndianU32(thread->stackEnd); + uint32 stackMaxAddr = _swapEndianU32(thread->stackBase); + for (sint32 i = 0; i < 20; i++) + { + uint32 nextStackPtr = memory_readU32(currentStackPtr); + if (nextStackPtr < currentStackPtr) + break; + if (nextStackPtr < stackMinAddr || nextStackPtr > stackMaxAddr) + break; + + uint32 returnAddress = 0; + returnAddress = memory_readU32(nextStackPtr + 4); + cemuLog_log(LogType::Force, fmt::format("SP {0:08x} ReturnAddr {1:08x}", nextStackPtr, returnAddress)); + + currentStackPtr = nextStackPtr; + } +} + +void coreinitExport_OSPanic(PPCInterpreter_t* hCPU) +{ + debug_printf("OSPanic!\n"); + debug_printf("File: %s:%d\n", memory_getPointerFromVirtualOffset(hCPU->gpr[3]), hCPU->gpr[4]); + debug_printf("Msg: %s\n", memory_getPointerFromVirtualOffset(hCPU->gpr[5])); + DebugLogStackTrace(coreinit::OSGetCurrentThread(), coreinit::OSGetStackPointer()); +#ifndef PUBLIC_RELEASE + assert_dbg(); + while (true) std::this_thread::sleep_for(std::chrono::milliseconds(100)); +#endif + osLib_returnFromFunction(hCPU, 0); +} + +typedef struct +{ + /* +0x00 */ uint32be name; + /* +0x04 */ uint32be fileType; // 2 = font + /* +0x08 */ uint32be kernelFilenamePtr; + /* +0x0C */ MEMPTR<void> data; + /* +0x10 */ uint32be size; + /* +0x14 */ uint32be ukn14; + /* +0x18 */ uint32be ukn18; +}coreinitShareddataEntry_t; + +static_assert(sizeof(coreinitShareddataEntry_t) == 0x1C, ""); + +uint8* extractCafeDefaultFont(sint32* size); + +MPTR placeholderFont = MPTR_NULL; +sint32 placeholderFontSize = 0; + +void coreinitExport_OSGetSharedData(PPCInterpreter_t* hCPU) +{ + // parameters: + // r3 sharedAreaId + // r4 flags + // r5 areaPtrPtr + // r6 areaSizePtr + + // on real Wii U hw/sw there is a list of shared area entries starting at offset +0xF8000000 + // properly formated (each entry is 0x1C bytes) it looks like this: + // FF CA FE 01 00 00 00 02 FF E8 47 AC F8 00 00 70 00 C8 0D 4C 00 00 00 00 FF FF FF FC + // FF CA FE 02 00 00 00 02 FF E8 47 B7 F8 C8 0D C0 00 22 7E B4 00 00 00 00 00 00 11 D5 + // FF CA FE 03 00 00 00 02 FF E8 47 A0 F8 EA 8C 80 00 25 44 E0 00 00 00 00 FF A0 00 00 + // FF CA FE 04 00 00 00 02 FF E8 47 C2 F9 0F D1 60 00 7D 93 5C 00 00 00 00 FF FF FF FC + + uint32 sharedAreaId = hCPU->gpr[3]; + + coreinitShareddataEntry_t* shareddataTable = (coreinitShareddataEntry_t*)memory_getPointerFromVirtualOffset(MEMORY_SHAREDDATA_AREA_ADDR); + + uint32 name = 0xFFCAFE01 + sharedAreaId; + for (sint32 i = 0; i < 4; i++) + { + if ((uint32)shareddataTable[i].name == name) + { + memory_writeU32(hCPU->gpr[5], shareddataTable[i].data.GetMPTR()); + memory_writeU32(hCPU->gpr[6], (uint32)shareddataTable[i].size); + osLib_returnFromFunction(hCPU, 1); + return; + } + } + // some games require a valid result or they will crash, return a pointer to our placeholder font + forceLog_printf("OSGetSharedData() called by game but no shareddata fonts loaded. Use placeholder font"); + if (placeholderFont == MPTR_NULL) + { + // load and then return placeholder font + uint8* placeholderFontPtr = extractCafeDefaultFont(&placeholderFontSize); + placeholderFont = coreinit_allocFromSysArea(placeholderFontSize, 256); + if (placeholderFont == MPTR_NULL) + forceLog_printf("Failed to alloc placeholder font sys memory"); + memcpy(memory_getPointerFromVirtualOffset(placeholderFont), placeholderFontPtr, placeholderFontSize); + free(placeholderFontPtr); + } + // return placeholder font + memory_writeU32(hCPU->gpr[5], placeholderFont); + memory_writeU32(hCPU->gpr[6], placeholderFontSize); + osLib_returnFromFunction(hCPU, 1); +} + +typedef struct +{ + MPTR getDriverName; + MPTR ukn04; + MPTR onAcquiredForeground; + MPTR onReleaseForeground; + MPTR ukn10; +}OSDriverCallbacks_t; + +void coreinitExport_OSDriver_Register(PPCInterpreter_t* hCPU) +{ +#ifndef PUBLIC_RELEASE + forceLog_printf("OSDriver_Register(0x%08x,0x%08x,0x%08x,0x%08x,0x%08x,0x%08x)", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7], hCPU->gpr[8]); +#endif + OSDriverCallbacks_t* driverCallbacks = (OSDriverCallbacks_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[5]); + + // todo + + osLib_returnFromFunction(hCPU, 0); +} + +namespace coreinit +{ + sint32 OSGetCoreId() + { + return PPCInterpreter_getCoreIndex(ppcInterpreterCurrentInstance); + } + + uint32 OSGetCoreCount() + { + return Espresso::CORE_COUNT; + } + + uint32 OSIsDebuggerInitialized() + { + return 0; + } + + uint32 OSGetConsoleType() + { + return 0x03000050; + } + + uint32 OSGetMainCoreId() + { + return 1; + } + + bool OSIsMainCore() + { + return OSGetCoreId() == OSGetMainCoreId(); + } + + uint32 OSGetStackPointer() + { + return ppcInterpreterCurrentInstance->gpr[1]; + } + + void coreinitExport_ENVGetEnvironmentVariable(PPCInterpreter_t* hCPU) + { + forceLogDebug_printf("ENVGetEnvironmentVariable(\"%s\",0x08x,0x%x)\n", (char*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]), hCPU->gpr[4], hCPU->gpr[5]); + char* envKeyStr = (char*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); + char* outputString = (char*)memory_getPointerFromVirtualOffset(hCPU->gpr[4]); + sint32 outputStringMaxLen = (sint32)hCPU->gpr[5]; + // also return the string "" just in case + if (outputStringMaxLen > 0) + { + outputString[0] = '\0'; + } + osLib_returnFromFunction(hCPU, 1); + } + + void coreinit_exit(uint32 r) + { + forceLog_printf("coreinit.exit(%d)", r); + cemu_assert_debug(false); + // never return + while (true) std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + bool OSIsOffBoot() + { + return true; + } + + uint32 OSGetBootPMFlags() + { + forceLogDebug_printf("OSGetBootPMFlags() - placeholder"); + return 0; + } + + uint32 OSGetSystemMode() + { + forceLogDebug_printf("OSGetSystemMode() - placeholder"); + // if this returns 2, barista softlocks shortly after boot + return 0; + } + + void InitializeCore() + { + cafeExportRegister("coreinit", OSGetCoreId, LogType::CoreinitThread); + cafeExportRegister("coreinit", OSGetCoreCount, LogType::CoreinitThread); + cafeExportRegister("coreinit", OSIsDebuggerInitialized, LogType::CoreinitThread); + cafeExportRegister("coreinit", OSGetConsoleType, LogType::CoreinitThread); + cafeExportRegister("coreinit", OSGetMainCoreId, LogType::CoreinitThread); + cafeExportRegister("coreinit", OSIsMainCore, LogType::CoreinitThread); + cafeExportRegister("coreinit", OSGetStackPointer, LogType::CoreinitThread); + + osLib_addFunction("coreinit", "ENVGetEnvironmentVariable", coreinitExport_ENVGetEnvironmentVariable); + + cafeExportRegisterFunc(coreinit_exit, "coreinit", "exit", LogType::CoreinitThread); + cafeExportRegister("coreinit", OSIsOffBoot, LogType::CoreinitThread); + cafeExportRegister("coreinit", OSGetBootPMFlags, LogType::CoreinitThread); + cafeExportRegister("coreinit", OSGetSystemMode, LogType::CoreinitThread); + } +}; + +void coreinit_load() +{ + coreinit::InitializeCore(); + coreinit::InitializeSchedulerLock(); + coreinit::InitializeSysHeap(); + + // allocate coreinit global data + gCoreinitData = (coreinitData_t*)memory_getPointerFromVirtualOffset(coreinit_allocFromSysArea(sizeof(coreinitData_t), 32)); + memset(gCoreinitData, 0x00, sizeof(coreinitData_t)); + + // coreinit weak links + osLib_addVirtualPointer("coreinit", "MEMAllocFromDefaultHeap", memory_getVirtualOffsetFromPointer(&gCoreinitData->MEMAllocFromDefaultHeap)); + osLib_addVirtualPointer("coreinit", "MEMAllocFromDefaultHeapEx", memory_getVirtualOffsetFromPointer(&gCoreinitData->MEMAllocFromDefaultHeapEx)); + osLib_addVirtualPointer("coreinit", "MEMFreeToDefaultHeap", memory_getVirtualOffsetFromPointer(&gCoreinitData->MEMFreeToDefaultHeap)); + osLib_addVirtualPointer("coreinit", "__atexit_cleanup", memory_getVirtualOffsetFromPointer(&gCoreinitData->__atexit_cleanup)); + osLib_addVirtualPointer("coreinit", "__stdio_cleanup", memory_getVirtualOffsetFromPointer(&gCoreinitData->__stdio_cleanup)); + osLib_addVirtualPointer("coreinit", "__cpp_exception_cleanup_ptr", memory_getVirtualOffsetFromPointer(&gCoreinitData->__cpp_exception_cleanup_ptr)); + osLib_addVirtualPointer("coreinit", "__cpp_exception_init_ptr", memory_getVirtualOffsetFromPointer(&gCoreinitData->__cpp_exception_init_ptr)); + + // init GHS and threads + coreinit::PrepareGHSRuntime(); + coreinit::InitializeThread(); + + // reset threads + activeThreadCount = 0; + // init submodules + coreinit::InitializeMEM(); + coreinit::InitializeMEMFrmHeap(); + coreinit::InitializeMEMUnitHeap(); + coreinit::InitializeMEMBlockHeap(); + coreinit::InitializeFG(); + coreinit::InitializeBSP(); + coreinit::InitializeMCP(); + coreinit::InitializeOverlayArena(); + coreinit::InitializeDynLoad(); + coreinit::InitializeGHS(); + coreinit::InitializeHWInterface(); + coreinit::InitializeAtomic(); + coreinit::InitializeMemory(); + coreinit::InitializeIM(); + coreinit::InitializeLC(); + coreinit::InitializeMP(); + coreinit::InitializeTimeAndCalendar(); + coreinit::InitializeAlarm(); + coreinit::InitializeFS(); + coreinit::InitializeSystemInfo(); + coreinit::InitializeConcurrency(); + coreinit::InitializeSpinlock(); + coreinit::InitializeMessageQueue(); + coreinit::InitializeIPC(); + coreinit::InitializeIPCBuf(); + coreinit::InitializeCodeGen(); + coreinit::InitializeCoroutine(); + coreinit::InitializeOSScreen(); + + // legacy mem stuff + coreinit::expheap_load(); + + // misc exports + coreinit::miscInit(); + osLib_addFunction("coreinit", "OSGetSharedData", coreinitExport_OSGetSharedData); + osLib_addFunction("coreinit", "UCReadSysConfig", coreinitExport_UCReadSysConfig); + osLib_addFunction("coreinit", "OSDriver_Register", coreinitExport_OSDriver_Register); + + // async callbacks + InitializeAsyncCallback(); +} diff --git a/src/Cafe/OS/libs/coreinit/coreinit.h b/src/Cafe/OS/libs/coreinit/coreinit.h new file mode 100644 index 00000000..046ffe1e --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit.h @@ -0,0 +1,48 @@ +#pragma once +#include "Cafe/HW/Espresso/Const.h" + +#define PPC_CORE_COUNT (Espresso::CORE_COUNT) + +#include "Cafe/OS/libs/coreinit/coreinit_MessageQueue.h" + +// async callback helper + +void InitializeAsyncCallback(); +void coreinitAsyncCallback_add(MPTR functionMPTR, uint32 numParameters, uint32 r3 = 0, uint32 r4 = 0, uint32 r5 = 0, uint32 r6 = 0, uint32 r7 = 0, uint32 r8 = 0, uint32 r9 = 0, uint32 r10 = 0); +void coreinitAsyncCallback_addWithLock(MPTR functionMPTR, uint32 numParameters, uint32 r3 = 0, uint32 r4 = 0, uint32 r5 = 0, uint32 r6 = 0, uint32 r7 = 0, uint32 r8 = 0, uint32 r9 = 0, uint32 r10 = 0); + +// misc + +void coreinit_load(); + +// coreinit shared memory + +typedef struct +{ + MEMPTR<void> MEMAllocFromDefaultHeap; + MEMPTR<void> MEMAllocFromDefaultHeapEx; + MEMPTR<void> MEMFreeToDefaultHeap; + MPTR __atexit_cleanup; + MPTR __cpp_exception_init_ptr; + MPTR __cpp_exception_cleanup_ptr; + MPTR __stdio_cleanup; +}coreinitData_t; + +extern coreinitData_t* gCoreinitData; + +#include "Cafe/OS/libs/coreinit/coreinit_Spinlock.h" + +// coreinit init +void coreinit_start(PPCInterpreter_t* hCPU); + +MPTR OSAllocFromSystem(uint32 size, uint32 alignment); +void OSFreeToSystem(MPTR mem); + +// above is all the legacy stuff. New code uses namespaces + +namespace coreinit +{ + sint32 OSGetCoreId(); + uint32 OSGetCoreCount(); + uint32 OSGetStackPointer(); +}; \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Alarm.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Alarm.cpp new file mode 100644 index 00000000..a498e40a --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_Alarm.cpp @@ -0,0 +1,359 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/HW/Espresso/PPCCallback.h" +#include "Cafe/OS/libs/coreinit/coreinit_Thread.h" +#include "Cafe/OS/libs/coreinit/coreinit_Time.h" +#include "Cafe/OS/libs/coreinit/coreinit_Alarm.h" +#include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h" +#include "Cafe/OS/RPL/rpl.h" + +// #define ALARM_LOGGING + +namespace coreinit +{ + SysAllocator<OSEvent> g_alarmEvent; + + SysAllocator<OSThread_t> g_alarmThread; + SysAllocator<uint8, 1024 * 128> _g_alarmThreadStack; + SysAllocator<char, 32> _g_alarmThreadName; + + + class OSHostAlarm + { + public: + OSHostAlarm(uint64 nextFire, uint64 period, void(*callbackFunc)(uint64 currentTick, void* context), void* context) : m_nextFire(nextFire), m_period(period), m_callbackFunc(callbackFunc), m_context(context) + { + cemu_assert_debug(__OSHasSchedulerLock()); // must hold lock + auto r = g_activeAlarmList.emplace(this); + cemu_assert_debug(r.second); // check if insertion was successful + m_isActive = true; + updateEarliestAlarmAtomic(); + } + + ~OSHostAlarm() + { + cemu_assert_debug(__OSHasSchedulerLock()); // must hold lock + if (m_isActive) + { + g_activeAlarmList.erase(g_activeAlarmList.find(this)); + updateEarliestAlarmAtomic(); + } + } + + uint64 getFireTick() const + { + return m_nextFire; + } + + void triggerAlarm(uint64 currentTick) + { + m_callbackFunc(currentTick, m_context); + } + + static void updateEarliestAlarmAtomic() + { + cemu_assert_debug(__OSHasSchedulerLock()); + if (!g_activeAlarmList.empty()) + { + auto firstAlarm = g_activeAlarmList.begin(); + g_soonestAlarm = (*firstAlarm)->m_nextFire; + } + else + { + g_soonestAlarm = std::numeric_limits<uint64>::max(); + } + } + + static void updateAlarms(uint64 currentTick) + { + cemu_assert_debug(__OSHasSchedulerLock()); + if (g_activeAlarmList.empty()) + return; + + // debug begin +#ifndef PUBLIC_RELEASE + uint64 prevTick = 0; + auto itr = g_activeAlarmList.begin(); + while (itr != g_activeAlarmList.end()) + { + uint64 t = (*itr)->m_nextFire; + if (t < prevTick) + cemu_assert_suspicious(); + prevTick = t; + ++itr; + } +#endif + // debug end + while (true) + { + auto firstAlarm = g_activeAlarmList.begin(); + if (currentTick >= (*firstAlarm)->m_nextFire) + { + OSHostAlarm* alarm = *firstAlarm; + g_activeAlarmList.erase(firstAlarm); + alarm->triggerAlarm(currentTick); + // if periodic alarm then requeue + if (alarm->m_period > 0) + { + alarm->m_nextFire += alarm->m_period; + g_activeAlarmList.emplace(alarm); + } + else + alarm->m_isActive = false; + updateEarliestAlarmAtomic(); + } + else + break; + } + } + + uint64 getNextFire() const + { + return m_nextFire; + } + + static bool quickCheckForAlarm(uint64 currentTick) + { + // fast way to check if any alarm was triggered without requiring scheduler lock + return currentTick >= g_soonestAlarm; + } + + public: + struct ComparatorFireTime + { + bool operator ()(OSHostAlarm* const & p1, OSHostAlarm* const & p2) const + { + auto p1Fire = p1->getNextFire(); + auto p2Fire = p2->getNextFire(); + if (p1Fire == p2Fire) + return (uintptr_t)p1 < (uintptr_t)p2; // if time is equal, differ by pointer (to allow storing multiple alarms with same firing time) + return p1Fire < p2Fire; + } + }; + + private: + uint64 m_nextFire; + uint64 m_period; // if zero then repeat is disabled + bool m_isActive{ false }; + + void (*m_callbackFunc)(uint64 currentTick, void* context); + void* m_context; + + static std::set<class OSHostAlarm*, ComparatorFireTime> g_activeAlarmList; + static std::atomic_uint64_t g_soonestAlarm; + }; + + + std::set<class OSHostAlarm*, OSHostAlarm::ComparatorFireTime> OSHostAlarm::g_activeAlarmList; + std::atomic_uint64_t OSHostAlarm::g_soonestAlarm{}; + + OSHostAlarm* OSHostAlarmCreate(uint64 nextFire, uint64 period, void(*callbackFunc)(uint64 currentTick, void* context), void* context) + { + OSHostAlarm* hostAlarm = new OSHostAlarm(nextFire, period, callbackFunc, context); + return hostAlarm; + } + + void OSHostAlarmDestroy(OSHostAlarm* hostAlarm) + { + delete hostAlarm; + } + + void alarm_update() + { + cemu_assert_debug(!__OSHasSchedulerLock()); + uint64 currentTick = coreinit::coreinit_getOSTime(); + if (!OSHostAlarm::quickCheckForAlarm(currentTick)) + return; + __OSLockScheduler(); + OSHostAlarm::updateAlarms(currentTick); + __OSUnlockScheduler(); + } + + /* alarm API */ + + void OSCreateAlarm(OSAlarm_t* alarm) + { + memset(alarm, 0, sizeof(OSAlarm_t)); + alarm->setMagic(); + } + + void coreinitExport_OSCreateAlarmEx(PPCInterpreter_t* hCPU) + { + OSAlarm_t* OSAlarm = (OSAlarm_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); + OSCreateAlarm(OSAlarm); + OSAlarm->name = _swapEndianU32(hCPU->gpr[4]); + osLib_returnFromFunction(hCPU, 0); + } + + std::unordered_map<OSAlarm_t*, OSHostAlarm*> g_activeAlarms; + + bool OSCancelAlarm(OSAlarm_t* alarm) + { + __OSLockScheduler(); + bool alarmWasActive = false; + auto itr = g_activeAlarms.find(alarm); + if (itr != g_activeAlarms.end()) + { + OSHostAlarmDestroy(itr->second); + g_activeAlarms.erase(itr); + alarmWasActive = true; + } + + __OSUnlockScheduler(); + return alarmWasActive; + } + + void __OSHostAlarmTriggered(uint64 currentTick, void* context) + { +#ifdef ALARM_LOGGING + cemuLog_log(LogType::Force, "[Alarm] Alarm ready and alarm thread signalled. Current tick: {}", currentTick); +#endif + OSSignalEventInternal(g_alarmEvent.GetPtr()); + } + + void __OSInitiateAlarm(OSAlarm_t* alarm, uint64 startTime, uint64 period, MPTR handlerFunc, bool isPeriodic) + { + cemu_assert_debug(__OSHasSchedulerLock()); + + uint64 nextTime = startTime; + +#ifdef ALARM_LOGGING + double periodInMS = (double)period * 1000.0 / (double)EspressoTime::GetTimerClock(); + cemuLog_log(LogType::Force, "[Alarm] Start alarm 0x{:08x}. Func 0x{:08x}. Period: {}ms", MEMPTR(alarm).GetMPTR(), handlerFunc, periodInMS); +#endif + + if (isPeriodic) + { + cemu_assert_debug(period != 0); + if (period == 0) + return; + + uint64 currentTime = coreinit_getOSTime(); + + uint64 ticksSinceStart = currentTime - startTime; + uint64 numPeriods = ticksSinceStart / period; + + nextTime = startTime + (numPeriods + 1ull) * period; + + alarm->startTime = _swapEndianU64(startTime); + alarm->nextTime = _swapEndianU64(nextTime); + alarm->period = _swapEndianU64(period); + alarm->handler = _swapEndianU32(handlerFunc); + } + else + { + alarm->nextTime = _swapEndianU64(startTime); + alarm->period = 0; + alarm->handler = _swapEndianU32(handlerFunc); + } + + auto existingAlarmItr = g_activeAlarms.find(alarm); + if (existingAlarmItr != g_activeAlarms.end()) + { + // delete existing alarm + forceLogDebug_printf("__OSInitiateAlarm() called on alarm which was already active"); + OSHostAlarmDestroy(existingAlarmItr->second); + g_activeAlarms.erase(existingAlarmItr); + } + + g_activeAlarms[alarm] = OSHostAlarmCreate(nextTime, period, __OSHostAlarmTriggered, nullptr); + } + + void OSSetAlarm(OSAlarm_t* alarm, uint64 delayInTicks, MPTR handlerFunc) + { + __OSLockScheduler(); + __OSInitiateAlarm(alarm, coreinit_getOSTime() + delayInTicks, 0, handlerFunc, false); + __OSUnlockScheduler(); + } + + void OSSetPeriodicAlarm(OSAlarm_t* alarm, uint64 nextFire, uint64 period, MPTR handlerFunc) + { + __OSLockScheduler(); + __OSInitiateAlarm(alarm, nextFire, period, handlerFunc, true); + __OSUnlockScheduler(); + } + + void OSSetAlarmUserData(OSAlarm_t* alarm, uint32 userData) + { + alarm->userData = _swapEndianU32(userData); + } + + void coreinitExport_OSGetAlarmUserData(PPCInterpreter_t* hCPU) + { + OSAlarm_t* OSAlarmBE = (OSAlarm_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); + MPTR userData = _swapEndianU32(OSAlarmBE->userData); + osLib_returnFromFunction(hCPU, userData); + } + + void OSAlarm_resetAll() + { + cemu_assert_debug(g_activeAlarms.empty()); + + cemu_assert_debug(false); + } + + void _OSAlarmThread(PPCInterpreter_t* hCPU) + { + while( true ) + { + OSWaitEvent(g_alarmEvent.GetPtr()); + uint64 currentTick = coreinit_getOSTime(); + while (true) + { + // get alarm to fire + OSAlarm_t* alarm = nullptr; + __OSLockScheduler(); + auto itr = g_activeAlarms.begin(); + while(itr != g_activeAlarms.end()) + { + if (currentTick >= _swapEndianU64(itr->first->nextTime)) + { + alarm = itr->first; + if (alarm->period == 0) + { + // end alarm + g_activeAlarms.erase(itr); + break; + } + else + { + alarm->nextTime = _swapEndianU64(_swapEndianU64(alarm->nextTime) + _swapEndianU64(alarm->period)); + } + break; + } + ++itr; + } + __OSUnlockScheduler(); + if (!alarm) + break; + // do callback for alarm +#ifdef ALARM_LOGGING + double periodInMS = (double)_swapEndianU64(alarm->period) * 1000.0 / (double)EspressoTime::GetTimerClock(); + cemuLog_log(LogType::Force, "[Alarm] Callback 0x{:08x} for alarm 0x{:08x}. Current tick: {}. Period: {}ms", _swapEndianU32(alarm->handler), MEMPTR(alarm).GetMPTR(), currentTick, periodInMS); +#endif + PPCCoreCallback(_swapEndianU32(alarm->handler), alarm, &(g_alarmThread.GetPtr()->context)); + } + } + } + + void InitializeAlarm() + { + cafeExportRegister("coreinit", OSCreateAlarm, LogType::Placeholder); + cafeExportRegister("coreinit", OSCancelAlarm, LogType::Placeholder); + cafeExportRegister("coreinit", OSSetAlarm, LogType::Placeholder); + cafeExportRegister("coreinit", OSSetPeriodicAlarm, LogType::Placeholder); + + cafeExportRegister("coreinit", OSSetAlarmUserData, LogType::Placeholder); + + osLib_addFunction("coreinit", "OSCreateAlarmEx", coreinitExport_OSCreateAlarmEx); + osLib_addFunction("coreinit", "OSGetAlarmUserData", coreinitExport_OSGetAlarmUserData); + + // init event + OSInitEvent(g_alarmEvent.GetPtr(), OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, OSEvent::EVENT_MODE::MODE_AUTO); + + // create alarm callback handler thread + coreinit::OSCreateThreadType(g_alarmThread.GetPtr(), RPLLoader_MakePPCCallable(_OSAlarmThread), 0, nullptr, _g_alarmThreadStack.GetPtr() + _g_alarmThreadStack.GetByteSize(), (sint32)_g_alarmThreadStack.GetByteSize(), 0, 0x7, OSThread_t::THREAD_TYPE::TYPE_IO); + OSResumeThread(g_alarmThread.GetPtr()); + strcpy(_g_alarmThreadName.GetPtr(), "Alarm Thread"); + coreinit::OSSetThreadName(g_alarmThread.GetPtr(), _g_alarmThreadName.GetPtr()); + } +} diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Alarm.h b/src/Cafe/OS/libs/coreinit/coreinit_Alarm.h new file mode 100644 index 00000000..717ecc90 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_Alarm.h @@ -0,0 +1,54 @@ +#pragma once +#include "Cafe/OS/libs/coreinit/coreinit_Thread.h" + +namespace coreinit +{ + + class OSHostAlarm; + OSHostAlarm* OSHostAlarmCreate(uint64 nextFire, uint64 period, void(*callbackFunc)(uint64 currentTick, void* context), void* context); + void OSHostAlarmDestroy(OSHostAlarm* hostAlarm); + + struct OSAlarm_t + { + /* +0x00 */ betype<uint32> magic; + /* +0x04 */ MPTR_UINT8 name; + /* +0x08 */ uint32 ukn08; + /* +0x0C */ MPTR handler; + /* +0x10 */ uint32 ukn10; + /* +0x14 */ uint32 padding14; + /* +0x18 */ uint64 nextTime; // next fire time + /* +0x20 */ MPTR prev; // pointer to OSAlarm + /* +0x24 */ MPTR next; // pointer to OSAlarm + /* +0x28 */ uint64 period; // period (zero for non-periodic timer) + /* +0x30 */ uint64 startTime; // period start + /* +0x38 */ MPTR userData; + /* +0x3C */ uint32 ukn3C; + /* +0x40 */ OSThreadQueue uknThreadQueue; + /* +0x50 */ MPTR alarmQueue; + /* +0x54 */ MPTR ukn54; + + void setMagic() + { + magic = (uint32)'aLrM'; + } + + bool checkMagic() + { + return magic == (uint32)'aLrM'; + } + }; + + static_assert(sizeof(OSAlarm_t) == 0x58); + + void OSCreateAlarm(OSAlarm_t* alarm); + bool OSCancelAlarm(OSAlarm_t* alarm); + void OSSetAlarm(OSAlarm_t* alarm, uint64 time, MPTR handlerFunc); + void OSSetAlarmUserData(OSAlarm_t* alarm, uint32 userData); + void OSSetPeriodicAlarm(OSAlarm_t* OSAlarm, uint64 startTick, uint64 periodTick, MPTR OSAlarmHandler); + + void OSAlarm_resetAll(); + + void alarm_update(); + + void InitializeAlarm(); +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Atomic.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Atomic.cpp new file mode 100644 index 00000000..6ac781c0 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_Atomic.cpp @@ -0,0 +1,139 @@ +#include "Cafe/OS/common/OSCommon.h" +#include <atomic> +#include "coreinit_Atomic.h" + +namespace coreinit +{ + /* 32bit atomic operations */ + + uint32 OSSwapAtomic(std::atomic<uint32be>* mem, uint32 newValue) + { + uint32be _newValue = newValue; + uint32be previousValue = mem->exchange(_newValue); + return previousValue; + } + + bool OSCompareAndSwapAtomic(std::atomic<uint32be>* mem, uint32 compareValue, uint32 swapValue) + { + // seen in GTA3 homebrew port + uint32be _compareValue = compareValue; + uint32be _swapValue = swapValue; + return mem->compare_exchange_strong(_compareValue, _swapValue); + } + + bool OSCompareAndSwapAtomicEx(std::atomic<uint32be>* mem, uint32 compareValue, uint32 swapValue, uint32be* previousValue) + { + // seen in GTA3 homebrew port + uint32be _compareValue = compareValue; + uint32be _swapValue = swapValue; + bool r = mem->compare_exchange_strong(_compareValue, _swapValue); + *previousValue = _compareValue; + return r; + } + + uint32 OSAddAtomic(std::atomic<uint32be>* mem, uint32 adder) + { + uint32be knownValue; + while (true) + { + uint32be knownValue = mem->load(); + uint32be newValue = knownValue + adder; + if (mem->compare_exchange_strong(knownValue, newValue)) + break; + } + return knownValue; + } + + /* 64bit atomic operations */ + + uint64 OSSwapAtomic64(std::atomic<uint64be>* mem, uint64 newValue) + { + uint64be _newValue = newValue; + uint64be previousValue = mem->exchange(_newValue); + return previousValue; + } + + uint64 OSSetAtomic64(std::atomic<uint64be>* mem, uint64 newValue) + { + return OSSwapAtomic64(mem, newValue); + } + + uint64 OSGetAtomic64(std::atomic<uint64be>* mem) + { + return mem->load(); + } + + uint64 OSAddAtomic64(std::atomic<uint64be>* mem, uint64 adder) + { + uint64be knownValue; + while (true) + { + uint64be knownValue = mem->load(); + uint64be newValue = knownValue + adder; + if (mem->compare_exchange_strong(knownValue, newValue)) + break; + } + return knownValue; + } + + uint64 OSAndAtomic64(std::atomic<uint64be>* mem, uint64 val) + { + uint64be knownValue; + while (true) + { + uint64be knownValue = mem->load(); + uint64be newValue = knownValue & val; + if (mem->compare_exchange_strong(knownValue, newValue)) + break; + } + return knownValue; + } + + uint64 OSOrAtomic64(std::atomic<uint64be>* mem, uint64 val) + { + uint64be knownValue; + while (true) + { + uint64be knownValue = mem->load(); + uint64be newValue = knownValue | val; + if (mem->compare_exchange_strong(knownValue, newValue)) + break; + } + return knownValue; + } + + bool OSCompareAndSwapAtomic64(std::atomic<uint64be>* mem, uint64 compareValue, uint64 swapValue) + { + uint64be _compareValue = compareValue; + uint64be _swapValue = swapValue; + return mem->compare_exchange_strong(_compareValue, _swapValue); + } + + bool OSCompareAndSwapAtomicEx64(std::atomic<uint64be>* mem, uint64 compareValue, uint64 swapValue, uint64be* previousValue) + { + uint64be _compareValue = compareValue; + uint64be _swapValue = swapValue; + bool r = mem->compare_exchange_strong(_compareValue, _swapValue); + *previousValue = _compareValue; + return r; + } + + void InitializeAtomic() + { + // 32bit atomic operations + cafeExportRegister("coreinit", OSSwapAtomic, LogType::Placeholder); + cafeExportRegister("coreinit", OSCompareAndSwapAtomic, LogType::Placeholder); + cafeExportRegister("coreinit", OSCompareAndSwapAtomicEx, LogType::Placeholder); + cafeExportRegister("coreinit", OSAddAtomic, LogType::Placeholder); + + // 64bit atomic operations + cafeExportRegister("coreinit", OSSetAtomic64, LogType::Placeholder); + cafeExportRegister("coreinit", OSGetAtomic64, LogType::Placeholder); + cafeExportRegister("coreinit", OSSwapAtomic64, LogType::Placeholder); + cafeExportRegister("coreinit", OSAddAtomic64, LogType::Placeholder); + cafeExportRegister("coreinit", OSAndAtomic64, LogType::Placeholder); + cafeExportRegister("coreinit", OSOrAtomic64, LogType::Placeholder); + cafeExportRegister("coreinit", OSCompareAndSwapAtomic64, LogType::Placeholder); + cafeExportRegister("coreinit", OSCompareAndSwapAtomicEx64, LogType::Placeholder); + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Atomic.h b/src/Cafe/OS/libs/coreinit/coreinit_Atomic.h new file mode 100644 index 00000000..317c20ce --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_Atomic.h @@ -0,0 +1,21 @@ +#pragma once +#include <atomic> + +namespace coreinit +{ + uint32 OSSwapAtomic(std::atomic<uint32be>* mem, uint32 newValue); + bool OSCompareAndSwapAtomic(std::atomic<uint32be>* mem, uint32 compareValue, uint32 swapValue); + bool OSCompareAndSwapAtomicEx(std::atomic<uint32be>* mem, uint32 compareValue, uint32 swapValue, uint32be* previousValue); + uint32 OSAddAtomic(std::atomic<uint32be>* mem, uint32 adder); + + uint64 OSSwapAtomic64(std::atomic<uint64be>* mem, uint64 newValue); + uint64 OSSetAtomic64(std::atomic<uint64be>* mem, uint64 newValue); + uint64 OSGetAtomic64(std::atomic<uint64be>* mem); + uint64 OSAddAtomic64(std::atomic<uint64be>* mem, uint64 adder); + uint64 OSAndAtomic64(std::atomic<uint64be>* mem, uint64 val); + uint64 OSOrAtomic64(std::atomic<uint64be>* mem, uint64 val); + bool OSCompareAndSwapAtomic64(std::atomic<uint64be>* mem, uint64 compareValue, uint64 swapValue); + bool OSCompareAndSwapAtomicEx64(std::atomic<uint64be>* mem, uint64 compareValue, uint64 swapValue, uint64be* previousValue); + + void InitializeAtomic(); +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_BSP.cpp b/src/Cafe/OS/libs/coreinit/coreinit_BSP.cpp new file mode 100644 index 00000000..c7f68649 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_BSP.cpp @@ -0,0 +1,19 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "coreinit_BSP.h" + +namespace coreinit +{ + bool bspGetHardwareVersion(uint32be* version) + { + uint8 highVersion = 0x11; // anything below 0x11 will be considered as Hollywood + // todo: Check version returned on console + uint32 tempVers = highVersion << 24; + *version = tempVers; + return true; + } + + void InitializeBSP() + { + cafeExportRegister("coreinit", bspGetHardwareVersion, LogType::Placeholder); + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_BSP.h b/src/Cafe/OS/libs/coreinit/coreinit_BSP.h new file mode 100644 index 00000000..bb28b549 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_BSP.h @@ -0,0 +1,4 @@ +namespace coreinit +{ + void InitializeBSP(); +}; \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Callbacks.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Callbacks.cpp new file mode 100644 index 00000000..18a36820 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_Callbacks.cpp @@ -0,0 +1,118 @@ +#pragma once + +#include "Cafe/OS/libs/coreinit/coreinit_Thread.h" +#include "util/helpers/fspinlock.h" + +struct CoreinitAsyncCallback +{ + CoreinitAsyncCallback(MPTR functionMPTR, uint32 numParameters, uint32 r3, uint32 r4, uint32 r5, uint32 r6, uint32 r7, uint32 r8, uint32 r9, uint32 r10) : + m_functionMPTR(functionMPTR), m_numParameters(numParameters), m_gprParam{ r3, r4, r5, r6, r7, r8, r9, r10 } {}; + + static void queue(MPTR functionMPTR, uint32 numParameters, uint32 r3, uint32 r4, uint32 r5, uint32 r6, uint32 r7, uint32 r8, uint32 r9, uint32 r10) + { + s_asyncCallbackSpinlock.acquire(); + s_asyncCallbackQueue.emplace_back(allocateAndInitFromPool(functionMPTR, numParameters, r3, r4, r5, r6, r7, r8, r9, r10)); + s_asyncCallbackSpinlock.release(); + } + + static void callNextFromQueue() + { + s_asyncCallbackSpinlock.acquire(); + if (s_asyncCallbackQueue.empty()) + { + cemuLog_log(LogType::Force, "AsyncCallbackQueue is empty. Unexpected behavior"); + s_asyncCallbackSpinlock.release(); + return; + } + CoreinitAsyncCallback* cb = s_asyncCallbackQueue[0]; + s_asyncCallbackQueue.erase(s_asyncCallbackQueue.begin()); + s_asyncCallbackSpinlock.release(); + cb->doCall(); + s_asyncCallbackSpinlock.acquire(); + releaseToPool(cb); + s_asyncCallbackSpinlock.release(); + } + +private: + void doCall() + { + PPCCoreCallback(m_functionMPTR, m_gprParam[0], m_gprParam[1], m_gprParam[2], m_gprParam[3], m_gprParam[4], m_gprParam[5], m_gprParam[6], m_gprParam[7]); + } + + static CoreinitAsyncCallback* allocateAndInitFromPool(MPTR functionMPTR, uint32 numParameters, uint32 r3, uint32 r4, uint32 r5, uint32 r6, uint32 r7, uint32 r8, uint32 r9, uint32 r10) + { + cemu_assert_debug(s_asyncCallbackSpinlock.isHolding()); + if (s_asyncCallbackPool.empty()) + { + CoreinitAsyncCallback* cb = new CoreinitAsyncCallback(functionMPTR, numParameters, r3, r4, r5, r6, r7, r8, r9, r10); + return cb; + } + CoreinitAsyncCallback* cb = s_asyncCallbackPool[0]; + s_asyncCallbackPool.erase(s_asyncCallbackPool.begin()); + + *cb = CoreinitAsyncCallback(functionMPTR, numParameters, r3, r4, r5, r6, r7, r8, r9, r10); + return cb; + } + + static void releaseToPool(CoreinitAsyncCallback* cb) + { + cemu_assert_debug(s_asyncCallbackSpinlock.isHolding()); + s_asyncCallbackPool.emplace_back(cb); + } + + static std::vector<struct CoreinitAsyncCallback*> s_asyncCallbackPool; + static std::vector<struct CoreinitAsyncCallback*> s_asyncCallbackQueue; + static FSpinlock s_asyncCallbackSpinlock; + + sint32 m_numParameters; + uint32 m_gprParam[9]; + MPTR m_functionMPTR; +}; + +std::vector<struct CoreinitAsyncCallback*> CoreinitAsyncCallback::s_asyncCallbackPool; +std::vector<struct CoreinitAsyncCallback*> CoreinitAsyncCallback::s_asyncCallbackQueue; +FSpinlock CoreinitAsyncCallback::s_asyncCallbackSpinlock; + +SysAllocator<OSThread_t> g_coreinitCallbackThread; +SysAllocator<uint8, 1024*64> _g_coreinitCallbackThreadStack; +SysAllocator<coreinit::OSSemaphore> g_asyncCallbackAsync; +SysAllocator<char, 32> _g_coreinitCBThreadName; + +void _coreinitCallbackThread(PPCInterpreter_t* hCPU) +{ + while (coreinit::OSWaitSemaphore(g_asyncCallbackAsync.GetPtr())) + { + CoreinitAsyncCallback::callNextFromQueue(); + } +} + +void coreinitAsyncCallback_addWithLock(MPTR functionMPTR, uint32 numParameters, uint32 r3, uint32 r4, uint32 r5, uint32 r6, uint32 r7, uint32 r8, uint32 r9, uint32 r10) +{ + cemu_assert_debug(numParameters <= 8); + if (functionMPTR >= 0x10000000) + { + cemuLog_log(LogType::Force, fmt::format("Suspicious callback address {0:08x} params: {1:08x} {2:08x} {3:08x} {4:08x}", functionMPTR, r3, r4, r5, r6)); + cemuLog_waitForFlush(); + } + CoreinitAsyncCallback::queue(functionMPTR, numParameters, r3, r4, r5, r6, r7, r8, r9, r10); + __OSLockScheduler(); + coreinit::OSSignalSemaphoreInternal(g_asyncCallbackAsync.GetPtr(), false); + __OSUnlockScheduler(); +} + +void coreinitAsyncCallback_add(MPTR functionMPTR, uint32 numParameters, uint32 r3, uint32 r4, uint32 r5, uint32 r6, uint32 r7, uint32 r8, uint32 r9, uint32 r10) +{ + cemu_assert_debug(__OSHasSchedulerLock() == false); // do not call when holding scheduler lock + coreinitAsyncCallback_addWithLock(functionMPTR, numParameters, r3, r4, r5, r6, r7, r8, r9, r10); +} + +void InitializeAsyncCallback() +{ + coreinit::OSInitSemaphore(g_asyncCallbackAsync.GetPtr(), 0); + + coreinit::OSCreateThreadType(g_coreinitCallbackThread.GetPtr(), PPCInterpreter_makeCallableExportDepr(_coreinitCallbackThread), 0, nullptr, _g_coreinitCallbackThreadStack.GetPtr() + _g_coreinitCallbackThreadStack.GetByteSize(), (sint32)_g_coreinitCallbackThreadStack.GetByteSize(), 0, 7, OSThread_t::THREAD_TYPE::TYPE_IO); + coreinit::OSResumeThread(g_coreinitCallbackThread.GetPtr()); + + strcpy(_g_coreinitCBThreadName.GetPtr(), "Callback Thread"); + coreinit::OSSetThreadName(g_coreinitCallbackThread.GetPtr(), _g_coreinitCBThreadName.GetPtr()); +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_CodeGen.cpp b/src/Cafe/OS/libs/coreinit/coreinit_CodeGen.cpp new file mode 100644 index 00000000..31b184d8 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_CodeGen.cpp @@ -0,0 +1,148 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/RPL/rpl.h" +#include "Cafe/OS/libs/coreinit/coreinit_CodeGen.h" +#include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h" +#include "Common/ExceptionHandler/ExceptionHandler.h" + +namespace coreinit +{ + struct + { + bool rangeIsAllocated; + MPTR rangeStart; + uint32 rangeSize; + uint8* cacheStateCopy; // holds a copy of the entire range, simulates icache state (updated via ICBI) + }coreinitCodeGen = {0}; + + void codeGenArea_memoryWriteCallback(void* pageStart, size_t size) + { + uint32 ea = memory_getVirtualOffsetFromPointer(pageStart); + uint32 eaEnd = ea + (uint32)size; + while (ea <= eaEnd) + { + codeGenHandleICBI(ea); + ea += 0x20; + } + } + + void OSGetCodegenVirtAddrRange(betype<uint32>* rangeStart, betype<uint32>* rangeSize) + { + uint32 codegenSize = 0x01000000; // todo: Read from cos.xml + //debug_printf("OSGetCodegenVirtAddrRange(0x%08x,0x%08x)\n", hCPU->gpr[3], hCPU->gpr[4]); + // on first call, allocate range + if( coreinitCodeGen.rangeIsAllocated == false ) + { + coreinitCodeGen.rangeStart = RPLLoader_AllocateCodeSpace(codegenSize, 0x1000); + coreinitCodeGen.rangeSize = codegenSize; + coreinitCodeGen.cacheStateCopy = new uint8[codegenSize]; + memset(coreinitCodeGen.cacheStateCopy, 0, codegenSize); + coreinitCodeGen.rangeIsAllocated = true; + } + *rangeStart = coreinitCodeGen.rangeStart; + *rangeSize = coreinitCodeGen.rangeSize; + } + + void OSGetCodegenVirtAddrRangeInternal(uint32& rangeStart, uint32& rangeSize) + { + if (coreinitCodeGen.rangeIsAllocated == 0) + { + rangeStart = 0; + rangeSize = 0; + return; + } + rangeStart = coreinitCodeGen.rangeStart; + rangeSize = coreinitCodeGen.rangeSize; + } + + void ICInvalidateRange(uint32 startAddress, uint32 size) + { + uint32 ea = startAddress & ~0x1F; + uint32 eaEnd = (startAddress + size + 0x1F)&~0x1F; + while (ea <= eaEnd) + { + codeGenHandleICBI(ea); + ea += 0x20; + } + + } + + void coreinitExport_OSCodegenCopy(PPCInterpreter_t* hCPU) + { + if( coreinitCodeGen.rangeIsAllocated == false ) + assert_dbg(); + + uint32 codeAddrDest = hCPU->gpr[3]; + uint32 memAddrSrc = hCPU->gpr[4]; + uint32 size = hCPU->gpr[5]; + + if( codeAddrDest < coreinitCodeGen.rangeStart || codeAddrDest >= (coreinitCodeGen.rangeStart+coreinitCodeGen.rangeSize) ) + assert_dbg(); + if( (codeAddrDest+size) < coreinitCodeGen.rangeStart || (codeAddrDest+size) > (coreinitCodeGen.rangeStart+coreinitCodeGen.rangeSize) ) + assert_dbg(); + + memcpy(memory_getPointerFromVirtualOffset(codeAddrDest), memory_getPointerFromVirtualOffset(memAddrSrc), size); + + // invalidate recompiler range + uint32 ea = codeAddrDest & ~0x1F; + uint32 eaEnd = (codeAddrDest + size + 0x1F)&~0x1F; + while (ea <= eaEnd) + { + codeGenHandleICBI(ea); + ea += 0x20; + } + + osLib_returnFromFunction(hCPU, 0); + } + + void codeGenHandleICBI(uint32 ea) + { + cemu_assert_debug((ea & 0x1F) == 0); + if (coreinitCodeGen.rangeIsAllocated == false) + return; + cemu_assert_debug((coreinitCodeGen.rangeStart & 0x1F) == 0); + cemu_assert_debug((coreinitCodeGen.rangeSize & 0x1F) == 0); + if (ea >= coreinitCodeGen.rangeStart && ea < (coreinitCodeGen.rangeStart + coreinitCodeGen.rangeSize)) + { + uint8* cacheCopy = coreinitCodeGen.cacheStateCopy + (ea - coreinitCodeGen.rangeStart); + uint8* currentState = memory_getPointerFromVirtualOffset(ea); + if (memcmp(currentState, cacheCopy, 32) != 0) + { + // instructions changed + // flush cache + PPCRecompiler_invalidateRange(ea, ea+0x20); + // update icache copy + memcpy(cacheCopy, currentState, 32); + } + } + } + + bool _avoidCodeGenJIT = false; + + // currently we dont handle code invalidation well for direct write access + // therefore if we detect attempts to write we will disable JITing the area + bool codeGenShouldAvoid() + { + return _avoidCodeGenJIT; + } + + bool OSSwitchSecCodeGenMode(bool isRXOnly) + { + if (!_avoidCodeGenJIT) + { + forceLog_printf("Disable JIT on dynamic code area"); + } + _avoidCodeGenJIT = true; // this function getting called is usually a sign that + // does this have a return value? + return true; + } + + void InitializeCodeGen() + { + cafeExportRegister("coreinit", OSGetCodegenVirtAddrRange, LogType::Placeholder); + cafeExportRegister("coreinit", ICInvalidateRange, LogType::Placeholder); + + osLib_addFunction("coreinit", "OSCodegenCopy", coreinitExport_OSCodegenCopy); + + cafeExportRegister("coreinit", OSSwitchSecCodeGenMode, LogType::Placeholder); + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_CodeGen.h b/src/Cafe/OS/libs/coreinit/coreinit_CodeGen.h new file mode 100644 index 00000000..ba41dd87 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_CodeGen.h @@ -0,0 +1,10 @@ +#pragma once + +namespace coreinit +{ + void OSGetCodegenVirtAddrRangeInternal(uint32& rangeStart, uint32& rangeSize); + void codeGenHandleICBI(uint32 ea); + bool codeGenShouldAvoid(); + + void InitializeCodeGen(); +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Coroutine.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Coroutine.cpp new file mode 100644 index 00000000..b1f4abb7 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_Coroutine.cpp @@ -0,0 +1,100 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/libs/coreinit/coreinit_Coroutine.h" +#include "Cafe/HW/Espresso/PPCState.h" +#include "Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h" +#include "Cafe/HW/MMU/MMU.h" + +namespace coreinit +{ + + void coreinitExport_OSInitCoroutine(PPCInterpreter_t* hCPU) + { + OSCoroutine* coroutine = (OSCoroutine*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); + coroutine->lr = _swapEndianU32(hCPU->gpr[4]); + coroutine->r1 = _swapEndianU32(hCPU->gpr[5]); + osLib_returnFromFunction(hCPU, 0); + } + + void coreinitCoroutine_OSSaveCoroutine(OSCoroutine* coroutine, PPCInterpreter_t* hCPU) + { + coroutine->lr = _swapEndianU32(hCPU->spr.LR); + coroutine->cr = _swapEndianU32(ppc_getCR(hCPU)); + coroutine->gqr1 = _swapEndianU32(hCPU->spr.UGQR[1]); + coroutine->r1 = _swapEndianU32(hCPU->gpr[1]); + coroutine->r2 = _swapEndianU32(hCPU->gpr[2]); + coroutine->r13 = _swapEndianU32(hCPU->gpr[13]); + for (sint32 i = 14; i < 32; i++) + coroutine->gpr[i - 14] = _swapEndianU32(hCPU->gpr[i]); + for (sint32 i = 14; i < 32; i++) + { + coroutine->fpr[i - 14] = _swapEndianU64(hCPU->fpr[i].fp0int); + } + for (sint32 i = 14; i < 32; i++) + { + coroutine->psr[i - 14] = _swapEndianU64(hCPU->fpr[i].fp1int); + } + } + + void coreinitCoroutine_OSLoadCoroutine(OSCoroutine* coroutine, PPCInterpreter_t* hCPU) + { + hCPU->spr.LR = _swapEndianU32(coroutine->lr); + ppc_setCR(hCPU, _swapEndianU32(coroutine->cr)); + hCPU->spr.UGQR[1] = _swapEndianU32(coroutine->gqr1); + hCPU->gpr[1] = _swapEndianU32(coroutine->r1); + hCPU->gpr[2] = _swapEndianU32(coroutine->r2); + hCPU->gpr[13] = _swapEndianU32(coroutine->r13); + for (sint32 i = 14; i < 32; i++) + hCPU->gpr[i] = _swapEndianU32(coroutine->gpr[i - 14]); + for (sint32 i = 14; i < 32; i++) + { + hCPU->fpr[i].fp0int = _swapEndianU64(coroutine->fpr[i - 14]); + } + for (sint32 i = 14; i < 32; i++) + { + hCPU->fpr[i].fp1int = _swapEndianU64(coroutine->psr[i - 14]); + } + } + + void coreinitExport_OSSwitchCoroutine(PPCInterpreter_t* hCPU) + { + OSCoroutine* coroutineCurrent = (OSCoroutine*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); + OSCoroutine* coroutineNext = (OSCoroutine*)memory_getPointerFromVirtualOffsetAllowNull(hCPU->gpr[4]); + coreinitCoroutine_OSSaveCoroutine(coroutineCurrent, hCPU); + if (coroutineNext != NULL) + { + coreinitCoroutine_OSLoadCoroutine(coroutineNext, hCPU); + } + osLib_returnFromFunction(hCPU, 0); + } + + void coreinitExport_OSSwitchFiberEx(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(param0, 0); + ppcDefineParamU32(param1, 1); + ppcDefineParamU32(param2, 2); + ppcDefineParamU32(param3, 3); + ppcDefineParamMPTR(newInstructionPointer, 4); + ppcDefineParamMPTR(newStackPointer, 5); + + MPTR prevStackpointer = hCPU->gpr[1]; + hCPU->gpr[1] = newStackPointer; + hCPU->gpr[3] = param0; + hCPU->gpr[4] = param1; + hCPU->gpr[5] = param2; + hCPU->gpr[6] = param3; + + PPCCore_executeCallbackInternal(newInstructionPointer); + uint32 returnValue = hCPU->gpr[3]; + + hCPU->gpr[1] = prevStackpointer; + + osLib_returnFromFunction(hCPU, returnValue); + } + + void InitializeCoroutine() + { + osLib_addFunction("coreinit", "OSInitCoroutine", coreinitExport_OSInitCoroutine); + osLib_addFunction("coreinit", "OSSwitchCoroutine", coreinitExport_OSSwitchCoroutine); + osLib_addFunction("coreinit", "OSSwitchFiberEx", coreinitExport_OSSwitchFiberEx); + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Coroutine.h b/src/Cafe/OS/libs/coreinit/coreinit_Coroutine.h new file mode 100644 index 00000000..985137e9 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_Coroutine.h @@ -0,0 +1,21 @@ +#pragma once + +namespace coreinit +{ + struct OSCoroutine + { + /* +0x00 */ uint32 lr; + /* +0x04 */ uint32 cr; + /* +0x08 */ uint32 gqr1; + /* +0x0C */ uint32 r1; // stack pointer + /* +0x10 */ uint32 r2; + /* +0x14 */ uint32 r13; + /* +0x18 */ uint32 gpr[18]; + uint64 fpr[18]; + uint64 psr[18]; + }; + + static_assert(sizeof(OSCoroutine) == 0x180); + + void InitializeCoroutine(); +} diff --git a/src/Cafe/OS/libs/coreinit/coreinit_DynLoad.cpp b/src/Cafe/OS/libs/coreinit/coreinit_DynLoad.cpp new file mode 100644 index 00000000..aae17dfe --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_DynLoad.cpp @@ -0,0 +1,147 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/HW/Espresso/PPCCallback.h" +#include "Cafe/OS/RPL/rpl.h" +#include "Cafe/OS/libs/coreinit/coreinit_DynLoad.h" +#include "Cafe/OS/libs/coreinit/coreinit_MEM.h" + +namespace coreinit +{ + MPTR _osDynLoadFuncAlloc = MPTR_NULL; + MPTR _osDynLoadFuncFree = MPTR_NULL; + MPTR _osDynLoadTLSFuncAlloc = MPTR_NULL; + MPTR _osDynLoadTLSFuncFree = MPTR_NULL; + + uint32 OSDynLoad_SetAllocator(MPTR allocFunc, MPTR freeFunc) + { + _osDynLoadFuncAlloc = allocFunc; + _osDynLoadFuncFree = freeFunc; + return 0; + } + + void OSDynLoad_SetTLSAllocator(MPTR allocFunc, MPTR freeFunc) + { + _osDynLoadTLSFuncAlloc = allocFunc; + _osDynLoadTLSFuncFree = freeFunc; + } + + uint32 OSDynLoad_GetAllocator(betype<MPTR>* funcAlloc, betype<MPTR>* funcFree) + { + *funcAlloc = _osDynLoadFuncAlloc; + *funcFree = _osDynLoadFuncFree; + return 0; + } + + void OSDynLoad_GetTLSAllocator(betype<MPTR>* funcAlloc, betype<MPTR>* funcFree) + { + *funcAlloc = _osDynLoadTLSFuncAlloc; + *funcFree = _osDynLoadTLSFuncFree; + } + + void* OSDynLoad_AllocatorAlloc(sint32 size, sint32 alignment) + { + if (_osDynLoadFuncAlloc == MPTR_NULL) + return MPTR_NULL; + StackAllocator<MEMPTR<void>> _ptrStorage; + int r = PPCCoreCallback(_osDynLoadFuncAlloc, size, alignment, _ptrStorage.GetMPTR()); + if (r != 0) + { + cemu_assert_debug(false); + return MPTR_NULL; + } + return _ptrStorage->GetPtr(); + } + + void OSDynLoad_AllocatorFree(void* mem) + { + if (_osDynLoadFuncFree == MPTR_NULL) + return; + MEMPTR<void> _mem = mem; + PPCCoreCallback(_osDynLoadFuncFree, _mem); + } + + uint32 OSDynLoad_Acquire(const char* libName, uint32be* moduleHandleOut) + { + // truncate path + sint32 fileNameStartIndex = 0; + sint32 tempLen = (sint32)strlen(libName); + for (sint32 i = tempLen - 1; i >= 0; i--) + { + if (libName[i] == '/') + { + fileNameStartIndex = i + 1; + break; + } + } + // truncate file extension + char tempLibName[512]; + strcpy(tempLibName, libName + fileNameStartIndex); + tempLen = (sint32)strlen(tempLibName); + for (sint32 i = tempLen - 1; i >= 0; i--) + { + if (tempLibName[i] == '.') + { + tempLibName[i] = '\0'; + break; + } + } + // search for loaded modules with matching name + uint32 rplHandle = RPLLoader_GetHandleByModuleName(libName); + + if (rplHandle == RPL_INVALID_HANDLE) + { + RPLLoader_AddDependency(libName); + RPLLoader_UpdateDependencies(); + RPLLoader_Link(); + RPLLoader_CallEntrypoints(); + rplHandle = RPLLoader_GetHandleByModuleName(libName); + if (rplHandle == RPL_INVALID_HANDLE) + rplHandle = 0; + } + + *moduleHandleOut = rplHandle; + // return module not found error code + if (rplHandle == RPL_INVALID_HANDLE) + return 0xFFFCFFE9; + return 0; + } + + uint32 OSDynLoad_Release(uint32 moduleHandle) + { + if (moduleHandle == RPL_INVALID_HANDLE) + return 0; + RPLLoader_RemoveDependency(moduleHandle); + RPLLoader_UpdateDependencies(); + + // this function isn't supposed to return anything, but early versions of Cemu did and Cemuhook (up to 0.5.7.6) now relies on it. We still keep the return value around for compatibility + return 0; + } + + uint32 OSDynLoad_FindExport(uint32 moduleHandle, uint32 isData, const char* exportName, betype<MPTR>* addrOut) + { + if (moduleHandle == 0xFFFFFFFF) + { + // main module + // Assassins Creed 4 has this handle hardcoded + moduleHandle = RPLLoader_GetMainModuleHandle(); + } + + MPTR exportResult = RPLLoader_FindModuleOrHLEExport(moduleHandle, isData, exportName); + *addrOut = exportResult; + + if (exportResult == MPTR_NULL) + return 0xFFFFFFFF; + return 0; + } + + void InitializeDynLoad() + { + cafeExportRegister("coreinit", OSDynLoad_SetAllocator, LogType::Placeholder); + cafeExportRegister("coreinit", OSDynLoad_SetTLSAllocator, LogType::Placeholder); + cafeExportRegister("coreinit", OSDynLoad_GetAllocator, LogType::Placeholder); + cafeExportRegister("coreinit", OSDynLoad_GetTLSAllocator, LogType::Placeholder); + + cafeExportRegister("coreinit", OSDynLoad_Acquire, LogType::Placeholder); + cafeExportRegister("coreinit", OSDynLoad_Release, LogType::Placeholder); + cafeExportRegister("coreinit", OSDynLoad_FindExport, LogType::Placeholder); + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_DynLoad.h b/src/Cafe/OS/libs/coreinit/coreinit_DynLoad.h new file mode 100644 index 00000000..061be002 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_DynLoad.h @@ -0,0 +1,18 @@ +#pragma once + +namespace coreinit +{ + uint32 OSDynLoad_SetAllocator(MPTR allocFunc, MPTR freeFunc); + void OSDynLoad_SetTLSAllocator(MPTR allocFunc, MPTR freeFunc); + uint32 OSDynLoad_GetAllocator(betype<MPTR>* funcAlloc, betype<MPTR>* funcFree); + void OSDynLoad_GetTLSAllocator(betype<MPTR>* funcAlloc, betype<MPTR>* funcFree); + + void* OSDynLoad_AllocatorAlloc(sint32 size, sint32 alignment); + void OSDynLoad_AllocatorFree(void* mem); + + uint32 OSDynLoad_Acquire(const char* libName, uint32be* moduleHandleOut); + uint32 OSDynLoad_Release(uint32 moduleHandle); + uint32 OSDynLoad_FindExport(uint32 moduleHandle, uint32 isData, const char* exportName, betype<MPTR>* addrOut); + + void InitializeDynLoad(); +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_FG.cpp b/src/Cafe/OS/libs/coreinit/coreinit_FG.cpp new file mode 100644 index 00000000..4b716373 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_FG.cpp @@ -0,0 +1,200 @@ +#include "Cafe/OS/common/OSCommon.h" +#include <memory> + +#define FG_BUCKET_AREA_FREE 0 // free area available game +#define FG_BUCKET_AREA_AUDIO_TRANSITION 1 // transition audio buffer +#define FG_BUCKET_AREA2 2 // frame storage? TV? +#define FG_BUCKET_AREA3 3 // frame storage? DRC? +#define FG_BUCKET_AREA4 4 // frame storage? TV? +#define FG_BUCKET_AREA5 5 // frame storage? DRC? +#define FG_BUCKET_AREA_SAVE 6 +#define FG_BUCKET_AREA_COPY 7 // for OS copy data (clipboard and title switch parameters) + +#define FG_BUCKET_AREA_FREE_SIZE 0x2800000 +#define FG_BUCKET_AREA_SAVE_SIZE 0x1000 +#define FG_BUCKET_AREA_COPY_SIZE 0x400000 + +#define FG_BUCKET_AREA_COUNT 8 + +namespace coreinit +{ + MEMPTR<void> fgAddr = nullptr; // NULL if not in foreground + MEMPTR<uint8> fgSaveAreaAddr = nullptr; + + struct + { + uint32 id; + uint32 startOffset; + uint32 size; + }fgAreaEntries[FG_BUCKET_AREA_COUNT] = + { + { 0, 0, 0x2800000 }, + { 7, 0x2800000, 0x400000 }, + { 1, 0x2C00000, 0x900000 }, + { 2, 0x3500000, 0x3C0000 }, + { 3, 0x38C0000, 0x1C0000 }, + { 4, 0x3A80000, 0x3C0000 }, + { 5, 0x3E40000, 0x1BF000 }, + { 6, 0x3FFF000, 0x1000 } + }; + + MEMPTR<uint8> GetFGMemByArea(uint32 areaId) + { + if (fgAddr == nullptr) + return nullptr; + for (sint32 i = 0; i < FG_BUCKET_AREA_COUNT; i++) + { + if (fgAreaEntries[i].id == areaId) + return MEMPTR<uint8>(fgAddr.GetPtr<uint8>() + fgAreaEntries[i].startOffset); + } + return nullptr; + } + + + bool OSGetForegroundBucket(MEMPTR<void>* offset, uint32be* size) + { + // return full size of foreground bucket area + if (offset) + *offset = MEMPTR<void>{ (uint32)MEMORY_FGBUCKET_AREA_ADDR }; + if (size) + *size = MEMORY_FGBUCKET_AREA_SIZE; + // return true if in foreground + return true; + } + + bool OSGetForegroundBucketFreeArea(MPTR* offset, MPTR* size) + { + uint8* freeAreaAddr = GetFGMemByArea(FG_BUCKET_AREA_FREE).GetPtr(); + + *offset = _swapEndianU32(memory_getVirtualOffsetFromPointer(freeAreaAddr)); + *size = _swapEndianU32(FG_BUCKET_AREA_FREE_SIZE); + // return true if in foreground + return (fgAddr != nullptr); + } + + void coreinitExport_OSGetForegroundBucket(PPCInterpreter_t* hCPU) + { + //debug_printf("OSGetForegroundBucket(0x%x,0x%x)\n", hCPU->gpr[3], hCPU->gpr[4]); + // returns the whole FG bucket area (if the current process is in the foreground) + ppcDefineParamMPTR(areaOutput, 0); + ppcDefineParamMPTR(areaSize, 1); + bool r = OSGetForegroundBucket((MEMPTR<void>*)memory_getPointerFromVirtualOffsetAllowNull(areaOutput), (uint32be*)memory_getPointerFromVirtualOffsetAllowNull(areaSize)); + osLib_returnFromFunction(hCPU, r ? 1 : 0); + } + + void coreinitExport_OSGetForegroundBucketFreeArea(PPCInterpreter_t* hCPU) + { + debug_printf("OSGetForegroundBucketFreeArea(0x%x,0x%x)\n", hCPU->gpr[3], hCPU->gpr[4]); + ppcDefineParamMPTR(areaOutput, 0); + ppcDefineParamMPTR(areaSize, 1); + bool r = OSGetForegroundBucketFreeArea((MPTR*)memory_getPointerFromVirtualOffsetAllowNull(areaOutput), (MPTR*)memory_getPointerFromVirtualOffsetAllowNull(areaSize)); + osLib_returnFromFunction(hCPU, r ? 1 : 0); + } + + void InitForegroundBucket() + { + uint32be fgSize; + OSGetForegroundBucket(&fgAddr, &fgSize); + uint8* freeAreaPtr = GetFGMemByArea(FG_BUCKET_AREA_FREE).GetPtr(); + memset(freeAreaPtr, 0, FG_BUCKET_AREA_FREE_SIZE); + uint8* saveAreaPtr = GetFGMemByArea(FG_BUCKET_AREA_SAVE).GetPtr(); + fgSaveAreaAddr = saveAreaPtr; + if (*(uint32be*)saveAreaPtr != (uint32be)'Save') + { + // clear save area + memset(saveAreaPtr, 0, FG_BUCKET_AREA_SAVE_SIZE); + // clear copy area + memset(GetFGMemByArea(FG_BUCKET_AREA_COPY).GetPtr(), 0, FG_BUCKET_AREA_COPY_SIZE); + // init save area + *(uint32be*)(saveAreaPtr + 0x00) = 'Save'; + *(uint32be*)(saveAreaPtr + 0x08) |= 0x300; + } + } + + void __OSClearCopyData() + { + uint8* fgCopyArea = GetFGMemByArea(FG_BUCKET_AREA_COPY).GetPtr(); + if (fgCopyArea) + { + *(uint32be*)(fgCopyArea + 0x00) = 0; + } + } + + uint32 __OSGetCopyDataSize() + { + uint8* fgCopyArea = GetFGMemByArea(FG_BUCKET_AREA_COPY).GetPtr(); + if (fgCopyArea) + { + return *(uint32be*)(fgCopyArea + 0x00); + } + return 0; + } + + uint8* __OSGetCopyDataPtr() + { + uint8* fgCopyArea = GetFGMemByArea(FG_BUCKET_AREA_COPY).GetPtr(); + if (fgCopyArea) + return fgCopyArea + 4; + return nullptr; + } + + bool __OSAppendCopyData(uint8* data, sint32 length) + { + uint8* fgCopyArea = GetFGMemByArea(FG_BUCKET_AREA_COPY).GetPtr(); + if (fgCopyArea == nullptr) + return false; + uint32 currentOffset = *(uint32be*)(fgCopyArea + 0x00); + if ((currentOffset + length) > FG_BUCKET_AREA_COPY_SIZE - 4) + { + return false; + } + memcpy(fgCopyArea + currentOffset + 4, data, length); + *(uint32be*)(fgCopyArea + 0x00) += length; + return true; + } + + bool __OSResizeCopyData(sint32 length) + { + uint8* fgCopyArea = GetFGMemByArea(FG_BUCKET_AREA_COPY).GetPtr(); + if (fgCopyArea == nullptr) + return false; + sint32 currentOffset = (sint32) * (uint32be*)(fgCopyArea + 0x00); + if (length < currentOffset) + { + // can only make copy data smaller + *(uint32be*)(fgCopyArea + 0x00) = length; + return true; + } + return false; + } + + bool OSCopyFromClipboard(void* data, uint32be* size) + { + uint32 cSize = *size; + if (cSize == 0 && data == nullptr) + return false; + if (OSGetForegroundBucket(nullptr, nullptr) == false) + return false; + + // todo + + *size = 0; + return true; + } + + void coreinitExport_OSCopyFromClipboard(PPCInterpreter_t* hCPU) + { + forceLogDebug_printf("OSCopyFromClipboard(0x%x,0x%x)\n", hCPU->gpr[3], hCPU->gpr[4]); + ppcDefineParamMEMPTR(buffer, void, 0); + ppcDefineParamMEMPTR(size, uint32be, 1); + bool r = OSCopyFromClipboard(buffer.GetPtr(), size.GetPtr()); + osLib_returnFromFunction(hCPU, r ? 1 : 0); + } + + void InitializeFG() + { + osLib_addFunction("coreinit", "OSGetForegroundBucket", coreinitExport_OSGetForegroundBucket); + osLib_addFunction("coreinit", "OSGetForegroundBucketFreeArea", coreinitExport_OSGetForegroundBucketFreeArea); + osLib_addFunction("coreinit", "OSCopyFromClipboard", coreinitExport_OSCopyFromClipboard); + } +} diff --git a/src/Cafe/OS/libs/coreinit/coreinit_FG.h b/src/Cafe/OS/libs/coreinit/coreinit_FG.h new file mode 100644 index 00000000..846001b9 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_FG.h @@ -0,0 +1,17 @@ +#pragma once + +namespace coreinit +{ + void __OSClearCopyData(); + uint32 __OSGetCopyDataSize(); + uint8* __OSGetCopyDataPtr(); + bool __OSAppendCopyData(uint8* data, sint32 length); + bool __OSResizeCopyData(sint32 length); + + bool OSGetForegroundBucket(MEMPTR<void>* offset, uint32be* size); + bool OSGetForegroundBucketFreeArea(MPTR* offset, MPTR* size); + + void InitForegroundBucket(); + + void InitializeFG(); +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_FS.cpp b/src/Cafe/OS/libs/coreinit/coreinit_FS.cpp new file mode 100644 index 00000000..3d13fdbe --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_FS.cpp @@ -0,0 +1,1662 @@ +#include "config/ActiveSettings.h" +#include "Cafe/OS/libs/coreinit/coreinit_SystemInfo.h" +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/libs/coreinit/coreinit_Thread.h" +#include "Cafe/OS/libs/coreinit/coreinit_FS.h" +#include "Cafe/OS/libs/coreinit/coreinit_MessageQueue.h" +#include "util/helpers/Semaphore.h" +#include "Cafe/HW/Espresso/PPCCallback.h" +#include "Cafe/IOSU/iosu_ipc_common.h" +#include "coreinit_IPC.h" +#include "Cafe/Filesystem/fsc.h" + +#define FS_CB_PLACEHOLDER_FINISHCMD (MPTR)(0xF122330E) + +// return false if src+'\0' does not fit into dst +template<std::size_t Size> +bool strcpy_whole(char(&dst)[Size], const char* src) +{ + size_t inputLength = strlen(src); + if ((inputLength + 1) > Size) + { + dst[0] = '\0'; + return false; + } + strcpy(dst, src); + return true; +} + +bool strcpy_whole(char* dst, size_t dstLength, const char* src) +{ + size_t inputLength = strlen(src); + if ((inputLength + 1) > dstLength) + { + dst[0] = '\0'; + return false; + } + strcpy(dst, src); + return true; +} + +namespace coreinit +{ + std::mutex sFSClientLock; + std::recursive_mutex sFSGlobalMutex; + + inline void FSLockMutex() + { + sFSGlobalMutex.lock(); + } + + inline void FSUnlockMutex() + { + sFSGlobalMutex.unlock(); + } + + void _debugVerifyCommand(const char* stage, FSCmdBlockBody_t* fsCmdBlockBody); + + bool sFSInitialized = true; // this should be false but it seems like some games rely on FSInit being called before main()? Twilight Princess for example reads files before it calls FSInit + bool sFSShutdown = false; + + enum class MOUNT_TYPE : uint32 + { + SD = 0, + // 1 = usb? + }; + + struct FS_MOUNT_SOURCE + { + uint32be sourceType; // ukn values + char path[128]; // todo - determine correct length + }; + + FS_RESULT FSGetMountSourceNext(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, MOUNT_TYPE mountSourceType, FS_MOUNT_SOURCE* mountSourceInfo, FS_ERROR_MASK errMask) + { + // hacky + static FS_MOUNT_SOURCE* s_last_source = nullptr; + if(s_last_source != mountSourceInfo) + { + s_last_source = mountSourceInfo; + fsCmdBlock->data.mount_it = 0; + } + + fsCmdBlock->data.mount_it++; + + // SD + if (mountSourceType == MOUNT_TYPE::SD && fsCmdBlock->data.mount_it == 1) + { + mountSourceInfo->sourceType = 0; + strcpy(mountSourceInfo->path, "/sd"); + return FS_RESULT::SUCCESS; + } + + return FS_RESULT::END_ITERATION; + } + + FS_RESULT FSGetMountSource(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, MOUNT_TYPE mountSourceType, FS_MOUNT_SOURCE* mountSourceInfo, FS_ERROR_MASK errMask) + { + return FSGetMountSourceNext(fsClient, fsCmdBlock, mountSourceType, mountSourceInfo, errMask); + } + + bool _sdCard01Mounted = false; + bool _mlc01Mounted = false; + + void mountSDCard() + { + if (_sdCard01Mounted) + return; + + std::error_code ec; + const auto path = ActiveSettings::GetPath("sdcard/"); + fs::create_directories(path, ec); + FSCDeviceHostFS_Mount("/vol/external01", path.generic_wstring().c_str() , FSC_PRIORITY_BASE); + + _sdCard01Mounted = true; + } + + FS_RESULT FSMount(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FS_MOUNT_SOURCE* mountSourceInfo, char* mountPathOut, sint32 mountPathMaxLength, FS_ERROR_MASK errMask) + { + if (mountSourceInfo->sourceType != 0) + { + return FS_RESULT::ERR_PLACEHOLDER; + } + + if (!strcmp(mountSourceInfo->path, "/sd")) + { + const char* sdMountPath = "/vol/external01"; + // note: mount path should not end with a slash + if (!strcpy_whole(mountPathOut, mountPathMaxLength, sdMountPath)) + return FS_RESULT::ERR_PLACEHOLDER; // string does not fit + mountSDCard(); + } + + return FS_RESULT::SUCCESS; + } + + FS_RESULT FSBindMount(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* mountPathSrc, char* mountPathOut, FS_ERROR_MASK errMask) + { + if (strcmp(mountPathSrc, "/dev/sdcard01") == 0) { + if (_sdCard01Mounted) + return FS_RESULT::ERR_PLACEHOLDER; + + std::error_code ec; + const auto path = ActiveSettings::GetPath("sdcard/"); + fs::create_directories(path, ec); + if (!FSCDeviceHostFS_Mount(mountPathOut, path.generic_wstring().c_str(), FSC_PRIORITY_BASE)) + return FS_RESULT::ERR_PLACEHOLDER; + _sdCard01Mounted = true; + } + else if (strcmp(mountPathSrc, "/dev/mlc01") == 0) { + if (_mlc01Mounted) + return FS_RESULT::ERR_PLACEHOLDER; + + if (!FSCDeviceHostFS_Mount(mountPathOut, ActiveSettings::GetMlcPath().generic_wstring().c_str(), FSC_PRIORITY_BASE)) + return FS_RESULT::ERR_PLACEHOLDER; + _mlc01Mounted = true; + } + else { + return FS_RESULT::ERR_PLACEHOLDER; + } + + return FS_RESULT::SUCCESS; + } + + // return the aligned FSClientBody struct inside a FSClient struct + FSClientBody_t* __FSGetClientBody(FSClient_t* fsClient) + { + // align pointer to 64 bytes + if (fsClient == nullptr) + return nullptr; + FSClientBody_t* fsClientBody = (FSClientBody_t*)(((uintptr_t)fsClient + 0x3F) & ~0x3F); + fsClientBody->selfClient = fsClient; + return fsClientBody; + } + + // return the aligned FSClientBody struct inside a FSClient struct + FSCmdBlockBody_t* __FSGetCmdBlockBody(FSCmdBlock_t* fsCmdBlock) + { + // align pointer to 64 bytes + if (fsCmdBlock == nullptr) + return nullptr; + FSCmdBlockBody_t* fsCmdBlockBody = (FSCmdBlockBody_t*)(((uintptr_t)fsCmdBlock + 0x3F) & ~0x3F); + fsCmdBlockBody->selfCmdBlock = fsCmdBlock; + return fsCmdBlockBody; + } + + + void __FSErrorAndBlock(std::string_view msg) + { + forceLog_printf("Critical error in FS: %s", msg.data()); + while (true) + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + } + + /* FS client management */ + FSClientBody_t* g_fsRegisteredClientBodies = nullptr; + + sint32 FSGetClientNum() + { + sint32 clientNum = 0; + FSClientBody_t* fsBodyItr = g_fsRegisteredClientBodies; + while (fsBodyItr) + { + clientNum++; + fsBodyItr = fsBodyItr->fsClientBodyNext.GetPtr(); + } + return clientNum; + } + + bool __FSIsClientRegistered(FSClientBody_t* fsClientBody) + { + FSLockMutex(); + FSClientBody_t* fsClientBodyFirst = g_fsRegisteredClientBodies; + FSClientBody_t* fsClientBodyItr = fsClientBodyFirst; + if (fsClientBodyItr == NULL) + { + FSUnlockMutex(); + return false; + } + while (true) + { + if (fsClientBody == fsClientBodyItr) + { + FSUnlockMutex(); + return true; + } + // next + fsClientBodyItr = fsClientBodyItr->fsClientBodyNext.GetPtr(); + if (fsClientBodyItr == MPTR_NULL) + break; + if (fsClientBodyItr == fsClientBodyFirst) + break; + } + FSUnlockMutex(); + return false; + } + + void __FSInitCmdQueue(FSCmdQueue* fsCmdQueueBE, MPTR dequeueHandlerFuncMPTR, uint32 numMaxCommandsInFlight) + { + cemu_assert(numMaxCommandsInFlight > 0); + fsCmdQueueBE->dequeueHandlerFuncMPTR = _swapEndianU32(dequeueHandlerFuncMPTR); + fsCmdQueueBE->numCommandsInFlight = 0; + fsCmdQueueBE->numMaxCommandsInFlight = numMaxCommandsInFlight; + coreinit::OSInitMutexEx(&fsCmdQueueBE->mutex, nullptr); + fsCmdQueueBE->firstMPTR = _swapEndianU32(0); + fsCmdQueueBE->lastMPTR = _swapEndianU32(0); + } + + void FSInit() + { + sFSInitialized = true; + } + + void FSShutdown() + { + cemuLog_log(LogType::Force, "FSShutdown called"); + } + + FS_RESULT FSAddClientEx(FSClient_t* fsClient, uint32 uknR4, uint32 errHandling) + { + if (!sFSInitialized || sFSShutdown || !fsClient) + { + __FSErrorAndBlock("Called FSAddClient(Ex) with invalid parameters or while FS is not initialized"); + return FS_RESULT::FATAL_ERROR; + } + FSLockMutex(); + if (uknR4 != 0) + { + uint32 uknValue = memory_readU32(uknR4 + 0x00); + if (uknValue == 0) + { + FSUnlockMutex(); + __FSErrorAndBlock("FSAddClientEx - unknown error"); + return FS_RESULT::FATAL_ERROR; + } + } + if (__FSIsClientRegistered(__FSGetClientBody(fsClient))) + { + FSUnlockMutex(); + __FSErrorAndBlock("Called FSAddClient(Ex) on client that was already added"); + return FS_RESULT::FATAL_ERROR; + } + FSClientBody_t* fsClientBody = __FSGetClientBody(fsClient); + __FSInitCmdQueue(&fsClientBody->fsCmdQueue, MPTR_NULL, 1); + + IOSDevHandle devHandle = IOS_Open("/dev/fsa", 0); + if (IOS_ResultIsError((IOS_ERROR)devHandle)) + { + cemuLog_log(LogType::Force, "FSAddClientEx(): Exhausted device handles"); + cemu_assert(IOS_ResultIsError((IOS_ERROR)devHandle)); + FSUnlockMutex(); + return FS_RESULT::FATAL_ERROR; + } + fsClientBody->iosuFSAHandle = devHandle; + + // add to list of registered clients + if (g_fsRegisteredClientBodies != MPTR_NULL) + { + fsClientBody->fsClientBodyNext = g_fsRegisteredClientBodies; + g_fsRegisteredClientBodies = fsClientBody; + } + else + { + g_fsRegisteredClientBodies = fsClientBody; + fsClientBody->fsClientBodyNext = nullptr; + } + FSUnlockMutex(); + return FS_RESULT::SUCCESS; + } + + FS_RESULT FSAddClient(FSClient_t* fsClient, uint32 errHandling) + { + return FSAddClientEx(fsClient, 0, errHandling); + } + + FS_RESULT FSDelClient(FSClient_t* fsClient, uint32 errHandling) + { + if (sFSInitialized == false || sFSShutdown == true || !fsClient) + { + __FSErrorAndBlock("Called FSDelClient with invalid parameters or while FS is not initialized"); + return FS_RESULT::FATAL_ERROR; + } + FSClientBody_t* fsClientBody = __FSGetClientBody(fsClient); + FSLockMutex(); + IOS_Close(fsClientBody->iosuFSAHandle); + // todo: wait till in-flight transactions are done + // remove from list + if (g_fsRegisteredClientBodies == fsClientBody) + { + // first entry in list + g_fsRegisteredClientBodies = fsClientBody->fsClientBodyNext.GetPtr(); + } + else + { + // scan for entry in list + FSClientBody_t* fsBodyItr = g_fsRegisteredClientBodies; + FSClientBody_t* fsBodyPrev = g_fsRegisteredClientBodies; + bool entryFound = false; + while (fsBodyItr) + { + if (fsBodyItr == fsClientBody) + { + fsBodyPrev->fsClientBodyNext = fsClientBody->fsClientBodyNext; + entryFound = true; + break; + } + // next + fsBodyPrev = fsBodyItr; + fsBodyItr = fsBodyItr->fsClientBodyNext.GetPtr(); + } + if (entryFound == false) + { +#ifndef PUBLIC_RELEASE + assert_dbg(); +#endif + } + } + FSUnlockMutex(); + return FS_RESULT::SUCCESS; + } + + /* FS command queue */ + + Semaphore g_semaphoreQueuedCmds; + + void __FSQueueCmdByPriority(FSCmdQueue* fsCmdQueueBE, FSCmdBlockBody_t* fsCmdBlockBody, bool stopAtEqualPriority) + { + MPTR fsCmdBlockBodyMPTR = memory_getVirtualOffsetFromPointer(fsCmdBlockBody); + if (_swapEndianU32(fsCmdQueueBE->firstMPTR) == MPTR_NULL) + { + // queue is currently empty + cemu_assert(fsCmdQueueBE->lastMPTR == MPTR_NULL); + fsCmdQueueBE->firstMPTR = _swapEndianU32(fsCmdBlockBodyMPTR); + fsCmdQueueBE->lastMPTR = _swapEndianU32(fsCmdBlockBodyMPTR); + fsCmdBlockBody->nextMPTR = _swapEndianU32(MPTR_NULL); + fsCmdBlockBody->previousMPTR = _swapEndianU32(MPTR_NULL); + return; + } + // iterate from last to first element as long as iterated priority is lower + FSCmdBlockBody_t* fsCmdBlockBodyItrPrev = NULL; + FSCmdBlockBody_t* fsCmdBlockBodyItr = (FSCmdBlockBody_t*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(fsCmdQueueBE->lastMPTR)); + while (true) + { + if (fsCmdBlockBodyItr == NULL) + { + // insert at the head of the list + fsCmdQueueBE->firstMPTR = _swapEndianU32(fsCmdBlockBodyMPTR); + fsCmdBlockBody->nextMPTR = _swapEndianU32(memory_getVirtualOffsetFromPointer(fsCmdBlockBodyItrPrev)); + fsCmdBlockBody->previousMPTR = _swapEndianU32(MPTR_NULL); + fsCmdBlockBodyItrPrev->previousMPTR = _swapEndianU32(memory_getVirtualOffsetFromPointer(fsCmdBlockBody)); + return; + } + // compare priority + if ((stopAtEqualPriority && fsCmdBlockBodyItr->priority >= fsCmdBlockBody->priority) || (stopAtEqualPriority == false && fsCmdBlockBodyItr->priority > fsCmdBlockBody->priority)) + { + // insert cmd here + if (fsCmdBlockBodyItrPrev != NULL) + { + fsCmdBlockBody->nextMPTR = _swapEndianU32(memory_getVirtualOffsetFromPointer(fsCmdBlockBodyItrPrev)); + } + else + { + fsCmdBlockBody->nextMPTR = _swapEndianU32(MPTR_NULL); + fsCmdQueueBE->lastMPTR = _swapEndianU32(fsCmdBlockBodyMPTR); + } + fsCmdBlockBody->previousMPTR = _swapEndianU32(memory_getVirtualOffsetFromPointer(fsCmdBlockBodyItr)); + if (fsCmdBlockBodyItrPrev) + fsCmdBlockBodyItrPrev->previousMPTR = _swapEndianU32(memory_getVirtualOffsetFromPointer(fsCmdBlockBody)); + fsCmdBlockBodyItr->nextMPTR = _swapEndianU32(memory_getVirtualOffsetFromPointer(fsCmdBlockBody)); + return; + } + // next + fsCmdBlockBodyItrPrev = fsCmdBlockBodyItr; + fsCmdBlockBodyItr = (FSCmdBlockBody_t*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(fsCmdBlockBodyItr->previousMPTR)); + } + } + + FSCmdBlockBody_t* __FSTakeCommandFromQueue(FSCmdQueue* cmdQueue) + { + FSCmdBlockBody_t* dequeuedCmd = nullptr; + if (_swapEndianU32(cmdQueue->firstMPTR) != MPTR_NULL) + { + dequeuedCmd = (FSCmdBlockBody_t*)memory_getPointerFromVirtualOffset(_swapEndianU32(cmdQueue->firstMPTR)); + // dequeue cmd + if (cmdQueue->firstMPTR == cmdQueue->lastMPTR) + cmdQueue->lastMPTR = _swapEndianU32(MPTR_NULL); + cmdQueue->firstMPTR = dequeuedCmd->nextMPTR; + dequeuedCmd->nextMPTR = _swapEndianU32(MPTR_NULL); + if (_swapEndianU32(dequeuedCmd->nextMPTR) != MPTR_NULL) + { + FSCmdBlockBody_t* fsCmdBodyNext = (FSCmdBlockBody_t*)memory_getPointerFromVirtualOffset(_swapEndianU32(dequeuedCmd->nextMPTR)); + fsCmdBodyNext->previousMPTR = _swapEndianU32(MPTR_NULL); + } + } + return dequeuedCmd; + } + + void __FSAIoctlResponseCallback(PPCInterpreter_t* hCPU); + + void __FSAIPCSubmitCommand(FSCmdBlockBody_t* cmd) + { + if (cmd->ipcReqType == 0) + { + IOS_ERROR r = IOS_IoctlAsync(cmd->fsaDevHandle, cmd->operationType, cmd, 0x520, (uint8*)cmd + 0x580, 0x293, MEMPTR<void>(PPCInterpreter_makeCallableExportDepr(__FSAIoctlResponseCallback)), cmd); + cemu_assert(!IOS_ResultIsError(r)); + } + else + { + cemu_assert_unimplemented(); // IOS_IoctlvAsync + } + } + + void __FSUpdateQueue(FSCmdQueue* cmdQueue) + { + FSLockMutex(); + if (cmdQueue->numCommandsInFlight < cmdQueue->numMaxCommandsInFlight) + { + FSCmdBlockBody_t* dequeuedCommand = __FSTakeCommandFromQueue(cmdQueue); + if (dequeuedCommand) + { + cmdQueue->numCommandsInFlight += 1; + if (cmdQueue->numCommandsInFlight >= cmdQueue->numMaxCommandsInFlight) + cmdQueue->queueFlags = cmdQueue->queueFlags | FSCmdQueue::QUEUE_FLAG::IS_FULL; + cemu_assert_debug(cmdQueue->dequeueHandlerFuncMPTR == 0); // not supported. We HLE call the handler here + __FSAIPCSubmitCommand(dequeuedCommand); + } + } + FSUnlockMutex(); + } + + void __FSQueueCmd(FSCmdQueue* cmdQueue, FSCmdBlockBody_t* fsCmdBlockBody, MPTR finishCmdFunc) + { + fsCmdBlockBody->cmdFinishFuncMPTR = _swapEndianU32(finishCmdFunc); + FSLockMutex(); + fsCmdBlockBody->statusCode = _swapEndianU32(FSA_CMD_STATUS_CODE_D900A22); + __FSQueueCmdByPriority(cmdQueue, fsCmdBlockBody, true); + FSUnlockMutex(); + __FSUpdateQueue(cmdQueue); + } + + FSA_RESULT _FSIosErrorToFSAStatus(IOS_ERROR iosError) + { + return (FSA_RESULT)iosError; + } + + FS_RESULT _FSAStatusToFSStatus(FSA_RESULT err) + { + // todo + // currently /dev/fsa uses FS status codes internally. We should refactor everything to use FSA error codes (which are compatible with IOS_ERROR) and then translate them here to FS status + return (FS_RESULT)err; + } + + void __FSCmdSubmitResult(FSCmdBlockBody_t* fsCmdBlockBody, FS_RESULT result) + { + _debugVerifyCommand("FSCmdSubmitResult", fsCmdBlockBody); + + FSClientBody_t* fsClientBody = fsCmdBlockBody->fsClientBody.GetPtr(); + sFSClientLock.lock(); // OSFastMutex_Lock(&fsClientBody->fsCmdQueue.mutex) + fsCmdBlockBody->cancelState &= ~(1 << 0); // clear cancel bit + if (fsClientBody->currentCmdBlockBody.GetPtr() == fsCmdBlockBody) + fsClientBody->currentCmdBlockBody = nullptr; + fsCmdBlockBody->statusCode = _swapEndianU32(FSA_CMD_STATUS_CODE_D900A24); + sFSClientLock.unlock(); + // send result via msg queue or callback + cemu_assert_debug(!fsCmdBlockBody->asyncResult.fsAsyncParamsNew.ioMsgQueue != !fsCmdBlockBody->asyncResult.fsAsyncParamsNew.userCallback); // either must be set + fsCmdBlockBody->ukn09EA = 0; + fsCmdBlockBody->ukn9F4_lastErrorRelated = 0; + if (fsCmdBlockBody->asyncResult.fsAsyncParamsNew.ioMsgQueue) + { + // msg queue + _debugVerifyCommand("SubmitResultQueue", fsCmdBlockBody); + coreinit::OSMessageQueue* ioMsgQueue = fsCmdBlockBody->asyncResult.fsAsyncParamsNew.ioMsgQueue.GetPtr(); + fsCmdBlockBody->asyncResult.fsCmdBlock = fsCmdBlockBody->selfCmdBlock; + fsCmdBlockBody->asyncResult.fsStatusNew = (uint32)result; + while (OSSendMessage(ioMsgQueue, &fsCmdBlockBody->asyncResult.msgUnion.osMsg, 0) == 0) + { + forceLog_printf("FS driver: Failed to add message to result queue. Retrying..."); + if (ppcInterpreterCurrentInstance) + PPCCore_switchToScheduler(); + else + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + } + else + { + // callback + _debugVerifyCommand("SubmitResultCallback", fsCmdBlockBody); + FSClient_t* fsClient = fsCmdBlockBody->asyncResult.fsClient.GetPtr(); + FSCmdBlock_t* fsCmdBlock = fsCmdBlockBody->asyncResult.fsCmdBlock.GetPtr(); + PPCCoreCallback(fsCmdBlockBody->asyncResult.fsAsyncParamsNew.userCallback, fsClient, fsCmdBlock, (sint32)result, fsCmdBlockBody->asyncResult.fsAsyncParamsNew.userContext); + } + } + + void __FSAIoctlResponseCallback(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(iosResult, 0); + ppcDefineParamPtr(cmd, FSCmdBlockBody_t, 1); + + FSA_RESULT fsaStatus = _FSIosErrorToFSAStatus((IOS_ERROR)iosResult); + + cmd->statusCode = _swapEndianU32(FSA_CMD_STATUS_CODE_D900A26); + cmd->lastFSAStatus = fsaStatus; + + // translate error code to FSStatus + FS_RESULT fsStatus = _FSAStatusToFSStatus(fsaStatus); + + // On actual hardware this delegates the processing to the AppIO threads, but for now we just run it directly from the IPC thread + FSClientBody_t* client = cmd->fsClientBody; + FSCmdQueue& cmdQueue = client->fsCmdQueue; + FSLockMutex(); + cmdQueue.numCommandsInFlight -= 1; + cmdQueue.queueFlags = cmdQueue.queueFlags & ~FSCmdQueue::QUEUE_FLAG::IS_FULL; + FSUnlockMutex(); + __FSCmdSubmitResult(cmd, fsStatus); + __FSUpdateQueue(&cmd->fsClientBody->fsCmdQueue); + osLib_returnFromFunction(hCPU, 0); + } + + /* FS commands */ + + void FSInitCmdBlock(FSCmdBlock_t* fsCmdBlock) + { + memset(fsCmdBlock, 0x00, sizeof(FSCmdBlock_t)); + FSCmdBlockBody_t* fsCmdBlockBody = __FSGetCmdBlockBody(fsCmdBlock); + fsCmdBlockBody->statusCode = _swapEndianU32(FSA_CMD_STATUS_CODE_D900A21); + fsCmdBlockBody->priority = 0x10; + } + + void __FSAsyncToSyncInit(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSAsyncParamsNew_t* asyncParams) + { + if (fsClient == nullptr || fsCmdBlock == nullptr || asyncParams == nullptr) + assert_dbg(); + FSCmdBlockBody_t* fsCmdBlockBody = __FSGetCmdBlockBody(fsCmdBlock); + coreinit::OSInitMessageQueue(&fsCmdBlockBody->syncTaskMsgQueue, fsCmdBlockBody->_syncTaskMsg, 1); + asyncParams->userCallback = nullptr; + asyncParams->userContext = nullptr; + asyncParams->ioMsgQueue = &fsCmdBlockBody->syncTaskMsgQueue; + } + + void __FSPrepareCmdAsyncResult(FSClientBody_t* fsClientBody, FSCmdBlockBody_t* fsCmdBlockBody, FSAsyncResult* fsCmdBlockAsyncResult, FSAsyncParamsNew_t* fsAsyncParams) + { + memcpy(&fsCmdBlockAsyncResult->fsAsyncParamsNew, fsAsyncParams, sizeof(FSAsyncParamsNew_t)); + + fsCmdBlockAsyncResult->fsClient = fsClientBody->selfClient; + fsCmdBlockAsyncResult->fsCmdBlock = fsCmdBlockBody->selfCmdBlock; + + fsCmdBlockAsyncResult->msgUnion.fsMsg.fsAsyncResult = fsCmdBlockAsyncResult; + fsCmdBlockAsyncResult->msgUnion.fsMsg.commandType = _swapEndianU32(8); + } + + sint32 __FSPrepareCmd(FSClientBody_t* fsClientBody, FSCmdBlockBody_t* fsCmdBlockBody, uint32 errHandling, FSAsyncParamsNew_t* fsAsyncParams) + { + if (sFSInitialized == false || sFSShutdown == true) + return -0x400; + if (!fsClientBody || !fsCmdBlockBody) + { + cemu_assert(false); + return -0x400; + } + if (_swapEndianU32(fsCmdBlockBody->statusCode) != FSA_CMD_STATUS_CODE_D900A21 && _swapEndianU32(fsCmdBlockBody->statusCode) != FSA_CMD_STATUS_CODE_D900A24) + { + cemu_assert(false); + return -0x400; + } + if ((fsAsyncParams->userCallback == nullptr && fsAsyncParams->ioMsgQueue == nullptr) || + (fsAsyncParams->userCallback != nullptr && fsAsyncParams->ioMsgQueue != nullptr)) + { + // userCallback and ioMsgQueue both set or none set + cemu_assert(false); + return -0x400; + } + fsCmdBlockBody->fsClientBody = fsClientBody; + fsCmdBlockBody->errHandling = _swapEndianU32(errHandling); + fsCmdBlockBody->uknStatusGuessed09E9 = 0; + fsCmdBlockBody->cancelState &= ~(1 << 0); // clear cancel bit + fsCmdBlockBody->fsaDevHandle = fsClientBody->iosuFSAHandle; + __FSPrepareCmdAsyncResult(fsClientBody, fsCmdBlockBody , &fsCmdBlockBody->asyncResult, fsAsyncParams); + return 0; + } + +#define _FSCmdIntro() FSClientBody_t* fsClientBody = __FSGetClientBody(fsClient); \ + FSCmdBlockBody_t* fsCmdBlockBody = __FSGetCmdBlockBody(fsCmdBlock); \ + sint32 fsError = __FSPrepareCmd(fsClientBody, fsCmdBlockBody, errorMask, fsAsyncParams); \ + if (fsError != 0) return fsError; + + void _debugVerifyCommand(const char* stage, FSCmdBlockBody_t* fsCmdBlockBody) + { + if (fsCmdBlockBody->asyncResult.msgUnion.fsMsg.commandType != _swapEndianU32(8)) + { + forceLog_printf("Corrupted FS command detected in stage %s", stage); + forceLog_printf("Printing CMD block: "); + for (uint32 i = 0; i < (sizeof(FSCmdBlockBody_t) + 31) / 32; i++) + { + uint8* p = ((uint8*)fsCmdBlockBody) + i * 32; + forceLog_printf("%04x: %02x %02x %02x %02x - %02x %02x %02x %02x - %02x %02x %02x %02x - %02x %02x %02x %02x | %02x %02x %02x %02x - %02x %02x %02x %02x - %02x %02x %02x %02x - %02x %02x %02x %02x", + i * 32, + p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15], + p[16], p[17], p[18], p[19], p[20], p[21], p[22], p[23], p[24], p[25], p[26], p[27], p[28], p[29], p[30], p[31]); + } + } + } + + FSAsyncResult* FSGetAsyncResult(OSMessage* msg) + { + return (FSAsyncResult*)memory_getPointerFromVirtualOffset(_swapEndianU32(msg->message)); + } + + sint32 __FSProcessAsyncResult(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, sint32 fsStatus, uint32 errHandling) + { + // a positive result (or zero) means success. Most operations return zero in case of success. Read and write operations return the number of transferred units + if (fsStatus >= 0) + { + FSCmdBlockBody_t* fsCmdBlockBody = __FSGetCmdBlockBody(fsCmdBlock); + OSMessage msg; + OSReceiveMessage(&fsCmdBlockBody->syncTaskMsgQueue, &msg, OS_MESSAGE_BLOCK); + _debugVerifyCommand("handleAsyncResult", fsCmdBlockBody); + FSAsyncResult* asyncResult = FSGetAsyncResult(&msg); + return asyncResult->fsStatusNew; + } + else + { + // todo - error handling + forceLog_printf("FS handleAsyncResult(): unexpected error %08x", errHandling); + cemu_assert_debug(false); + return 0; + } + } + + uint32 __FSPrepareCmd_OpenFile(FSCmdBlockBody_t* fsCmdBlockBody, char* path, char* mode, uint32 createMode, uint32 openFlags, uint32 preallocSize) + { + fsCmdBlockBody->fsCmdBlockBodyMPTR = _swapEndianU32(memory_getVirtualOffsetFromPointer(fsCmdBlockBody)); + if (fsCmdBlockBody == NULL) + return 0xFFFCFFDD; + if (path == NULL) + return 0xFFFCFFDD + 1; + if (mode == NULL) + return 0xFFFCFFDD + 2; + fsCmdBlockBody->fsCmdBlockBodyMPTR = _swapEndianU32(memory_getVirtualOffsetFromPointer(fsCmdBlockBody)); + fsCmdBlockBody->ipcData.cmdOpenFile.fileHandleOutput = _swapEndianU32(0xFFFFFFFF); + fsCmdBlockBody->operationType = FSA_CMD_OPERATION_TYPE_OPENFILE; + // path + size_t pathLen = strlen((char*)path); + if (pathLen >= FSA_CMD_PATH_MAX_LENGTH) + { + cemu_assert_debug(false); + pathLen = FSA_CMD_PATH_MAX_LENGTH - 1; + } + for (sint32 i = 0; i < pathLen; i++) + fsCmdBlockBody->ipcData.cmdOpenFile.path[i] = path[i]; + for (size_t i = pathLen; i < FSA_CMD_PATH_MAX_LENGTH; i++) + fsCmdBlockBody->ipcData.cmdOpenFile.path[i] = '\0'; + // mode + size_t modeLen = strlen((char*)mode); + if (modeLen >= 12) + { + cemu_assert_debug(false); + modeLen = 12 - 1; + } + for (sint32 i = 0; i < modeLen; i++) + fsCmdBlockBody->ipcData.cmdOpenFile.mode[i] = mode[i]; + for (size_t i = modeLen; i < 12; i++) + fsCmdBlockBody->ipcData.cmdOpenFile.mode[i] = '\0'; + // createMode + fsCmdBlockBody->ipcData.cmdOpenFile.createMode = createMode; + // openFlags + fsCmdBlockBody->ipcData.cmdOpenFile.openFlags = openFlags; + // preallocSize + fsCmdBlockBody->ipcData.cmdOpenFile.preallocSize = preallocSize; + return 0; + } + + sint32 FSOpenFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, char* mode, FSFileHandleDepr_t* fileHandle, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + { + _FSCmdIntro(); + if (fileHandle == nullptr || path == nullptr || mode == nullptr) + return -0x400; + fsCmdBlockBody->returnValueMPTR = _swapEndianU32(memory_getVirtualOffsetFromPointer(fileHandle)); + fsError = __FSPrepareCmd_OpenFile(fsCmdBlockBody, path, mode, 0x660, 0, 0); + if (fsError != (FSStatus)FS_RESULT::SUCCESS) + return fsError; + __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, FS_CB_PLACEHOLDER_FINISHCMD); + return (FSStatus)FS_RESULT::SUCCESS; + } + + sint32 FSOpenFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, char* mode, FSFileHandleDepr_t* fileHandle, uint32 errHandling) + { + StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); + sint32 fsAsyncRet = FSOpenFileAsync(fsClient, fsCmdBlock, path, mode, fileHandle, errHandling, asyncParams); + return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errHandling); + } + + sint32 FSOpenFileExAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, char* mode, uint32 createMode, uint32 openFlag, uint32 preallocSize, FSFileHandleDepr_t* fileHandle, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + { + if (openFlag != 0) + { + cemuLog_log(LogType::Force, "FSOpenFileEx called with unsupported flags!"); + cemu_assert_debug(false); + } + + _FSCmdIntro(); + if (fileHandle == nullptr || path == nullptr || mode == nullptr) + return -0x400; + fsCmdBlockBody->returnValueMPTR = _swapEndianU32(memory_getVirtualOffsetFromPointer(fileHandle)); + fsError = __FSPrepareCmd_OpenFile(fsCmdBlockBody, path, mode, createMode, openFlag, preallocSize); + if (fsError != (FSStatus)FS_RESULT::SUCCESS) + return fsError; + __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, FS_CB_PLACEHOLDER_FINISHCMD); + return (FSStatus)FS_RESULT::SUCCESS; + } + + sint32 FSOpenFileEx(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, char* mode, uint32 createMode, uint32 openFlag, uint32 preallocSize, FSFileHandleDepr_t* fileHandle, uint32 errHandling) + { + StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); + sint32 fsAsyncRet = FSOpenFileExAsync(fsClient, fsCmdBlock, path, mode, createMode, openFlag, preallocSize, fileHandle, errHandling, asyncParams); + return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errHandling); + } + + void __FSPrepareCmd_CloseFile(FSCmdBlockBody_t* fsCmdBlockBody, uint32 fileHandle) + { + fsCmdBlockBody->operationType = FSA_CMD_OPERATION_TYPE_CLOSEFILE; + fsCmdBlockBody->ipcData.cmdCloseFile.fileHandle = fileHandle; + } + + sint32 FSCloseFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + { + _FSCmdIntro(); + __FSPrepareCmd_CloseFile(fsCmdBlockBody, fileHandle); + __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, FS_CB_PLACEHOLDER_FINISHCMD); + return (FSStatus)FS_RESULT::SUCCESS; + } + + sint32 FSCloseFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errHandling) + { + StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); + sint32 fsAsyncRet = FSCloseFileAsync(fsClient, fsCmdBlock, fileHandle, errHandling, asyncParams); + return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errHandling); + } + + uint32 __FSPrepareCmd_ReadFile(FSCmdBlockBody_t* fsCmdBlockBody, void* dest, uint32 uknR6, uint32 transferSizeUknAligned, uint32 filePos, uint32 fileHandle, uint32 flag) + { + fsCmdBlockBody->fsCmdBlockBodyMPTR = _swapEndianU32(memory_getVirtualOffsetFromPointer(fsCmdBlockBody)); + if (fsCmdBlockBody == NULL || dest == NULL) + return 0xFFFCFFDD; + MPTR destMPTR = memory_getVirtualOffsetFromPointer(dest); + if ((destMPTR & 0x3F) != 0) + return 0xFFFCFFDC; + fsCmdBlockBody->fsCmdBlockBodyMPTR = _swapEndianU32(memory_getVirtualOffsetFromPointer(fsCmdBlockBody)); + fsCmdBlockBody->ipcData.cmdDefault.fileHandle = _swapEndianU32(fileHandle); + fsCmdBlockBody->ukn0898 = _swapEndianU32(memory_getVirtualOffsetFromPointer(fsCmdBlockBody) + 0x580); + fsCmdBlockBody->ipcData.cmdDefault.ukn0008 = _swapEndianU32(uknR6); + fsCmdBlockBody->ipcData.cmdDefault.ukn000C = _swapEndianU32(transferSizeUknAligned); + uint32 fullTransferSize = transferSizeUknAligned * uknR6; + fsCmdBlockBody->ukn090B = 2; // byte + fsCmdBlockBody->ukn089C = _swapEndianU32(0x293); + fsCmdBlockBody->ukn0890 = _swapEndianU32(fullTransferSize); + fsCmdBlockBody->ukn0884 = _swapEndianU32(0x520); + fsCmdBlockBody->destBuffer88CMPTR = _swapEndianU32(destMPTR); + fsCmdBlockBody->ukn090A = 1; + fsCmdBlockBody->operationType = FSA_CMD_OPERATION_TYPE_READ; + fsCmdBlockBody->ipcData.cmdDefault.destBufferMPTR = _swapEndianU32(destMPTR); + fsCmdBlockBody->ipcData.cmdDefault.transferFilePos = _swapEndianU32(filePos); + fsCmdBlockBody->ipcData.cmdDefault.cmdFlag = _swapEndianU32(flag); + return 0; + } + + SysAllocator<uint8, 128, 64> _tempFSSpace; + + sint32 __FSReadFileAsyncEx(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dest, uint32 size, uint32 count, bool usePos, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + { + _FSCmdIntro(); + if (size == 0 || count == 0 || dest == NULL) + dest = _tempFSSpace.GetPtr(); + sint64 transferSizeS64 = (sint64)size * (sint64)count; + if (transferSizeS64 < 0 || (transferSizeS64 >= 2LL * 1024LL * 1024LL * 1024LL)) + { + // size below 0 or over 2GB + cemu_assert(false); + return -0x400; + } + uint32 transferSize = (uint32)transferSizeS64; + fsCmdBlockBody->transferSize = _swapEndianU32(transferSize); + fsCmdBlockBody->transferElemSize = _swapEndianU32(size); + fsCmdBlockBody->uknVal094C = _swapEndianU32(0); + if (transferSize < 0x10) + transferSize = 0x10; + fsCmdBlockBody->uknVal0954 = _swapEndianU32(transferSize); + if(usePos) + flag |= FSA_CMD_FLAG_SET_POS; + else + flag &= ~FSA_CMD_FLAG_SET_POS; + fsError = __FSPrepareCmd_ReadFile(fsCmdBlockBody, dest, 1, transferSize, filePos, fileHandle, flag); + if (fsError != (FSStatus)FS_RESULT::SUCCESS) + return fsError; + __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, FS_CB_PLACEHOLDER_FINISHCMD); + return (FSStatus)FS_RESULT::SUCCESS; + } + + sint32 FSReadFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + { + cemu_assert_debug(flag == 0); // todo + return __FSReadFileAsyncEx(fsClient, fsCmdBlock, dst, size, count, false, 0, fileHandle, flag, errorMask, fsAsyncParams); + } + + sint32 FSReadFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask) + { + StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); + sint32 fsAsyncRet = FSReadFileAsync(fsClient, fsCmdBlock, dst, size, count, fileHandle, flag, errorMask, asyncParams.GetPointer()); + return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); + } + + sint32 FSReadFileWithPosAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + { + cemu_assert_debug(flag == 0); // todo + sint32 fsStatus = __FSReadFileAsyncEx(fsClient, fsCmdBlock, dst, size, count, true, filePos, fileHandle, flag, errorMask, fsAsyncParams); + return fsStatus; + } + + sint32 FSReadFileWithPos(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask) + { + StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); + sint32 fsAsyncRet = FSReadFileWithPosAsync(fsClient, fsCmdBlock, dst, size, count, filePos, fileHandle, flag, errorMask, asyncParams.GetPointer()); + return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); + } + + uint32 __FSPrepareCmd_WriteFile(FSCmdBlockBody_t* fsCmdBlockBody, void* dest, uint32 uknR6, uint32 transferSizeUknAligned, uint32 filePos, uint32 fileHandle, uint32 flag) + { + fsCmdBlockBody->fsCmdBlockBodyMPTR = _swapEndianU32(memory_getVirtualOffsetFromPointer(fsCmdBlockBody)); + if (fsCmdBlockBody == NULL && dest == NULL) + return 0xFFFCFFDD; + MPTR destMPTR = memory_getVirtualOffsetFromPointer(dest); + if ((destMPTR & 0x3F) != 0) + return 0xFFFCFFDC; + cemu_assert_debug((uknR6 * transferSizeUknAligned) != 0); // todo: do zero-sized writes need special treatment? + fsCmdBlockBody->fsCmdBlockBodyMPTR = _swapEndianU32(memory_getVirtualOffsetFromPointer(fsCmdBlockBody)); + fsCmdBlockBody->ipcData.cmdDefault.fileHandle = _swapEndianU32(fileHandle); + fsCmdBlockBody->ukn0898 = _swapEndianU32(memory_getVirtualOffsetFromPointer(fsCmdBlockBody) + 0x580); // verified + fsCmdBlockBody->ipcData.cmdDefault.ukn0008 = _swapEndianU32(uknR6); + fsCmdBlockBody->ipcData.cmdDefault.ukn000C = _swapEndianU32(transferSizeUknAligned); + uint32 fullTransferSize = transferSizeUknAligned * uknR6; + fsCmdBlockBody->ukn090B = 1; // byte - verified (note: This member holds 2 for read operations) + fsCmdBlockBody->ukn089C = _swapEndianU32(0x293); + fsCmdBlockBody->ukn0890 = _swapEndianU32(fullTransferSize); + fsCmdBlockBody->ukn0884 = _swapEndianU32(0x520); // verified + fsCmdBlockBody->destBuffer88CMPTR = _swapEndianU32(destMPTR); + fsCmdBlockBody->ukn090A = 1; + fsCmdBlockBody->operationType = FSA_CMD_OPERATION_TYPE_WRITE; + fsCmdBlockBody->ipcData.cmdDefault.destBufferMPTR = _swapEndianU32(destMPTR); + fsCmdBlockBody->ipcData.cmdDefault.transferFilePos = _swapEndianU32(filePos); + fsCmdBlockBody->ipcData.cmdDefault.cmdFlag = _swapEndianU32(flag); + return (FSStatus)FS_RESULT::SUCCESS; + } + + sint32 __FSWriteFileWithPosAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dest, uint32 size, uint32 count, bool useFilePos, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + { + _FSCmdIntro(); + if (size == 0 || count == 0 || dest == nullptr) + dest = _tempFSSpace.GetPtr(); + sint64 transferSizeS64 = (sint64)size * (sint64)count; + if (transferSizeS64 < 0 || (transferSizeS64 >= 2LL * 1024LL * 1024LL * 1024LL)) + { + // size below 0 or over 2GB + cemu_assert(false); + return -0x400; + } + uint32 transferSize = (uint32)transferSizeS64; + fsCmdBlockBody->transferSize = _swapEndianU32(transferSize); + fsCmdBlockBody->transferElemSize = _swapEndianU32(size); + fsCmdBlockBody->uknVal094C = _swapEndianU32(0); + fsCmdBlockBody->uknVal0954 = _swapEndianU32(transferSize); + if (useFilePos) + flag |= FSA_CMD_FLAG_SET_POS; + else + flag &= ~FSA_CMD_FLAG_SET_POS; + fsError = __FSPrepareCmd_WriteFile(fsCmdBlockBody, dest, 1, transferSize, filePos, fileHandle, flag); + if (fsError != (FSStatus)FS_RESULT::SUCCESS) + return fsError; + __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, FS_CB_PLACEHOLDER_FINISHCMD); + return (FSStatus)FS_RESULT::SUCCESS; + } + + sint32 FSWriteFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + { + return __FSWriteFileWithPosAsync(fsClient, fsCmdBlock, src, size, count, false, 0, fileHandle, flag, errorMask, fsAsyncParams); + } + + sint32 FSWriteFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask) + { + StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); + sint32 fsAsyncRet = FSWriteFileAsync(fsClient, fsCmdBlock, src, size, count, fileHandle, flag, errorMask, asyncParams.GetPointer()); + return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); + } + + sint32 FSWriteFileWithPosAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + { + return __FSWriteFileWithPosAsync(fsClient, fsCmdBlock, src, size, count, true, filePos, fileHandle, flag, errorMask, fsAsyncParams); + } + + sint32 FSWriteFileWithPos(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask) + { + StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); + sint32 fsAsyncRet = FSWriteFileWithPosAsync(fsClient, fsCmdBlock, src, size, count, filePos, fileHandle, flag, errorMask, asyncParams.GetPointer()); + return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); + } + + uint32 __FSPrepareCmd_SetPosFile(FSCmdBlockBody_t* fsCmdBlockBody, uint32 fileHandle, uint32 filePos) + { + if (fsCmdBlockBody == NULL) + return 0xFFFCFFDD; + fsCmdBlockBody->ipcData.cmdDefault.destBufferMPTR = _swapEndianU32(fileHandle); + fsCmdBlockBody->ipcData.cmdDefault.ukn0008 = _swapEndianU32(filePos); + fsCmdBlockBody->operationType = FSA_CMD_OPERATION_TYPE_SETPOS; + return 0; + } + + sint32 FSSetPosFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 filePos, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + { + _FSCmdIntro(); + fsError = __FSPrepareCmd_SetPosFile(fsCmdBlockBody, fileHandle, filePos); + if (fsError != (FSStatus)FS_RESULT::SUCCESS) + return fsError; + __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, FS_CB_PLACEHOLDER_FINISHCMD); + return (FSStatus)FS_RESULT::SUCCESS; + } + + sint32 FSSetPosFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 filePos, uint32 errorMask) + { + // used by games: Mario Kart 8 + StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); + sint32 fsAsyncRet = FSSetPosFileAsync(fsClient, fsCmdBlock, fileHandle, filePos, errorMask, asyncParams.GetPointer()); + return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); + } + + uint32 __FSPrepareCmd_GetPosFile(FSCmdBlockBody_t* fsCmdBlockBody, uint32 fileHandle) + { + if (fsCmdBlockBody == NULL) + return 0xFFFCFFDD; + fsCmdBlockBody->ipcData.cmdDefault.destBufferMPTR = _swapEndianU32(fileHandle); + fsCmdBlockBody->operationType = FSA_CMD_OPERATION_TYPE_GETPOS; + return 0; + } + + sint32 FSGetPosFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32be* returnedFilePos, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + { + // games using this: Darksiders Warmastered Edition + _FSCmdIntro(); + fsCmdBlockBody->returnValueMPTR = _swapEndianU32(memory_getVirtualOffsetFromPointer(returnedFilePos)); + fsError = __FSPrepareCmd_GetPosFile(fsCmdBlockBody, fileHandle); + if (fsError != (FSStatus)FS_RESULT::SUCCESS) + return fsError; + __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, FS_CB_PLACEHOLDER_FINISHCMD); + return (FSStatus)FS_RESULT::SUCCESS; + } + + sint32 FSGetPosFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32be* returnedFilePos, uint32 errorMask) + { + StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); + sint32 fsAsyncRet = FSGetPosFileAsync(fsClient, fsCmdBlock, fileHandle, returnedFilePos, errorMask, asyncParams.GetPointer()); + return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); + } + + sint32 __FSPrepareCmd_OpenDir(FSCmdBlockBody_t* fsCmdBlockBody, char* path) + { + fsCmdBlockBody->fsCmdBlockBodyMPTR = _swapEndianU32(memory_getVirtualOffsetFromPointer(fsCmdBlockBody)); + if (fsCmdBlockBody == nullptr) + return 0xFFFCFFDD; + if (path == nullptr) + return 0xFFFCFFDD + 1; + + fsCmdBlockBody->fsCmdBlockBodyMPTR = _swapEndianU32(memory_getVirtualOffsetFromPointer(fsCmdBlockBody)); + fsCmdBlockBody->ipcData.cmdOpenDir.dirHandleOutput = -1; + fsCmdBlockBody->operationType = FSA_CMD_OPERATION_TYPE_OPENDIR; + // path + sint32 pathLen = (sint32)strlen((char*)path); + if (pathLen >= FSA_CMD_PATH_MAX_LENGTH) + { + cemu_assert_debug(false); + pathLen = FSA_CMD_PATH_MAX_LENGTH - 1; + } + for (sint32 i = 0; i < pathLen; i++) + fsCmdBlockBody->ipcData.cmdOpenDir.path[i] = path[i]; + for (sint32 i = pathLen; i < FSA_CMD_PATH_MAX_LENGTH; i++) + fsCmdBlockBody->ipcData.cmdOpenDir.path[i] = '\0'; + return 0; + } + + sint32 FSOpenDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, FSDirHandlePtr dirHandleOut, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + { + _FSCmdIntro(); + cemu_assert(dirHandleOut && path); + fsCmdBlockBody->returnValueMPTR = _swapEndianU32(dirHandleOut.GetMPTR()); + fsError = __FSPrepareCmd_OpenDir(fsCmdBlockBody, path); + if (fsError != (FSStatus)FS_RESULT::SUCCESS) + return fsError; + __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, FS_CB_PLACEHOLDER_FINISHCMD); + return (FSStatus)FS_RESULT::SUCCESS; + } + + sint32 FSOpenDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, FSDirHandlePtr dirHandleOut, uint32 errorMask) + { + StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); + sint32 fsAsyncRet = FSOpenDirAsync(fsClient, fsCmdBlock, path, dirHandleOut, errorMask, asyncParams); + return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); + } + + void __FSPrepareCmd_ReadDir(FSCmdBlockBody_t* fsCmdBlockBody, FSDirHandle2 dirHandle, FSDirEntry_t* dirEntryOut) + { + fsCmdBlockBody->returnValueMPTR = _swapEndianU32(memory_getVirtualOffsetFromPointer(dirEntryOut)); + fsCmdBlockBody->operationType = FSA_CMD_OPERATION_TYPE_READDIR; + fsCmdBlockBody->ipcData.cmdReadDir.dirHandle = dirHandle; + } + + sint32 FSReadDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, FSDirEntry_t* dirEntryOut, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + { + _FSCmdIntro(); + __FSPrepareCmd_ReadDir(fsCmdBlockBody, dirHandle, dirEntryOut); + __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, FS_CB_PLACEHOLDER_FINISHCMD); + return (FSStatus)FS_RESULT::SUCCESS; + } + + sint32 FSReadDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, FSDirEntry_t* dirEntryOut, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + { + StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); + sint32 fsAsyncRet = FSReadDirAsync(fsClient, fsCmdBlock, dirHandle, dirEntryOut, errorMask, asyncParams); + return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); + } + + void __FSPrepareCmd_CloseDir(FSCmdBlockBody_t* fsCmdBlockBody, FSDirHandle2 dirHandle) + { + fsCmdBlockBody->operationType = FSA_CMD_OPERATION_TYPE_CLOSEDIR; + fsCmdBlockBody->ipcData.cmdCloseDir.dirHandle = dirHandle; + } + + sint32 FSCloseDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + { + _FSCmdIntro(); + __FSPrepareCmd_CloseDir(fsCmdBlockBody, dirHandle); + __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, FS_CB_PLACEHOLDER_FINISHCMD); + return (FSStatus)FS_RESULT::SUCCESS; + } + + sint32 FSCloseDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, uint32 errorMask) + { + StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); + sint32 fsAsyncRet = FSCloseDirAsync(fsClient, fsCmdBlock, dirHandle, errorMask, asyncParams); + return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); + } + + void __FSPrepareCmd_AppendFile(FSCmdBlockBody_t* fsCmdBlockBody, uint32 fileHandle, uint32 size, uint32 count, uint32 uknParam) + { + fsCmdBlockBody->ipcData.cmdAppendFile.fileHandle = _swapEndianU32(fileHandle); + fsCmdBlockBody->ipcData.cmdAppendFile.count = _swapEndianU32(size); + fsCmdBlockBody->ipcData.cmdAppendFile.size = _swapEndianU32(count); + fsCmdBlockBody->ipcData.cmdAppendFile.uknParam = _swapEndianU32(uknParam); + fsCmdBlockBody->operationType = FSA_CMD_OPERATION_TYPE_APPENDFILE; + } + + sint32 FSAppendFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 size, uint32 count, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + { + _FSCmdIntro(); + __FSPrepareCmd_AppendFile(fsCmdBlockBody, fileHandle, size, count, 0); + __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, FS_CB_PLACEHOLDER_FINISHCMD); + return (FSStatus)FS_RESULT::SUCCESS; + } + + sint32 FSAppendFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 size, uint32 count, uint32 errorMask) + { + StackAllocator<FSAsyncParamsNew_t> asyncParams; + __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); + sint32 fsAsyncRet = FSAppendFileAsync(fsClient, fsCmdBlock, fileHandle, size, count, errorMask, asyncParams.GetPointer()); + return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); + } + + void __FSPrepareCmd_TruncateFile(FSCmdBlockBody_t* fsCmdBlockBody, FSFileHandle2 fileHandle) + { + fsCmdBlockBody->ipcData.cmdTruncateFile.fileHandle = fileHandle; + fsCmdBlockBody->ipcData.cmdTruncateFile.ukn0008 = 0; + fsCmdBlockBody->operationType = FSA_CMD_OPERATION_TYPE_TRUNCATEFILE; + } + + sint32 FSTruncateFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSFileHandle2 fileHandle, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + { + _FSCmdIntro(); + __FSPrepareCmd_TruncateFile(fsCmdBlockBody, fileHandle); + __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, FS_CB_PLACEHOLDER_FINISHCMD); + return (FSStatus)FS_RESULT::SUCCESS; + } + + sint32 FSTruncateFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSFileHandle2 fileHandle, uint32 errorMask) + { + StackAllocator<FSAsyncParamsNew_t> asyncParams; + __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); + sint32 fsAsyncRet = FSTruncateFileAsync(fsClient, fsCmdBlock, fileHandle, errorMask, asyncParams.GetPointer()); + return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); + } + + sint32 __FSPrepareCmd_Rename(FSCmdBlockBody_t* fsCmdBlockBody, char* srcPath, char* dstPath) + { + if (fsCmdBlockBody == NULL) + return 0xFFFCFFDD; + if (srcPath == NULL || dstPath == NULL) + return 0xFFFCFFDE; + // source path + size_t stringLen = strlen((char*)srcPath); + if (stringLen >= FSA_CMD_PATH_MAX_LENGTH) + { + cemu_assert_debug(false); + stringLen = FSA_CMD_PATH_MAX_LENGTH - 1; + } + for (sint32 i = 0; i < stringLen; i++) + { + fsCmdBlockBody->ipcData.cmdRename.srcPath[i] = srcPath[i]; + } + fsCmdBlockBody->ipcData.cmdRename.srcPath[stringLen] = '\0'; + // destination path + stringLen = strlen((char*)dstPath); + if (stringLen >= FSA_CMD_PATH_MAX_LENGTH) + { + cemu_assert_debug(false); + stringLen = FSA_CMD_PATH_MAX_LENGTH - 1; + } + for (sint32 i = 0; i < stringLen; i++) + { + fsCmdBlockBody->ipcData.cmdRename.dstPath[i] = dstPath[i]; + } + fsCmdBlockBody->ipcData.cmdRename.dstPath[stringLen] = '\0'; + fsCmdBlockBody->operationType = FSA_CMD_OPERATION_TYPE_RENAME; + return 0; + } + + sint32 FSRenameAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* srcPath, char* dstPath, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + { + // used by titles: XCX (via SAVERenameAsync) + _FSCmdIntro(); + if (srcPath == NULL || dstPath == NULL) + { + cemu_assert_debug(false); // path must not be NULL + return -0x400; + } + fsError = __FSPrepareCmd_Rename(fsCmdBlockBody, srcPath, dstPath); + if (fsError != (FSStatus)FS_RESULT::SUCCESS) + return fsError; + __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, FS_CB_PLACEHOLDER_FINISHCMD); + return (FSStatus)FS_RESULT::SUCCESS; + } + + sint32 FSRename(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* srcPath, char* dstPath, uint32 errorMask) + { + StackAllocator<FSAsyncParamsNew_t> asyncParams; + __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); + sint32 fsAsyncRet = FSRenameAsync(fsClient, fsCmdBlock, srcPath, dstPath, errorMask, asyncParams.GetPointer()); + return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); + } + + sint32 __FSPrepareCmd_Remove(FSCmdBlockBody_t* fsCmdBlockBody, uint8* path) + { + if (fsCmdBlockBody == NULL) + return 0xFFFCFFDD; + if (path == NULL) + return 0xFFFCFFDE; + size_t pathLen = strlen((char*)path); + if (pathLen >= FSA_CMD_PATH_MAX_LENGTH) + { + cemu_assert_debug(false); + pathLen = FSA_CMD_PATH_MAX_LENGTH - 1; + } + for (sint32 i = 0; i < pathLen; i++) + { + fsCmdBlockBody->ipcData.cmdRemove.path[i] = path[i]; + } + fsCmdBlockBody->ipcData.cmdRemove.path[pathLen] = '\0'; + fsCmdBlockBody->operationType = FSA_CMD_OPERATION_TYPE_REMOVE; + return 0; + } + + sint32 FSRemoveAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint8* filePath, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + { + // used by titles: XCX (via SAVERemoveAsync) + _FSCmdIntro(); + if (filePath == NULL) + { + cemu_assert_debug(false); // path must not be NULL + return -0x400; + } + uint32 ukn1444 = _swapEndianU32(fsClientBody->iosuFSAHandle); + fsError = __FSPrepareCmd_Remove(fsCmdBlockBody, filePath); + if (fsError != (FSStatus)FS_RESULT::SUCCESS) + { + cemu_assert_debug(false); + return fsError; + } + __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, FS_CB_PLACEHOLDER_FINISHCMD); + return (FSStatus)FS_RESULT::SUCCESS; + } + + sint32 FSRemove(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint8* filePath, uint32 errorMask) + { + StackAllocator<FSAsyncParamsNew_t> asyncParams; + __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); + sint32 fsAsyncRet = FSRemoveAsync(fsClient, fsCmdBlock, filePath, errorMask, asyncParams); + return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); + } + + uint32 __FSPrepareCmd_MakeDir(FSCmdBlockBody_t* fsCmdBlockBody, const uint8* path, uint32 uknVal660) + { + if (fsCmdBlockBody == NULL) + return 0xFFFCFFDD; + if (path == NULL) + return 0xFFFCFFDE; + size_t pathLen = strlen((char*)path); + if (pathLen >= FSA_CMD_PATH_MAX_LENGTH) + { + cemu_assert_debug(false); + pathLen = FSA_CMD_PATH_MAX_LENGTH - 1; + } + for (sint32 i = 0; i < pathLen; i++) + { + fsCmdBlockBody->ipcData.cmdMakeDir.path[i] = path[i]; + } + fsCmdBlockBody->ipcData.cmdMakeDir.path[pathLen] = '\0'; + fsCmdBlockBody->ipcData.cmdMakeDir.uknParam = _swapEndianU32(uknVal660); + fsCmdBlockBody->operationType = FSA_CMD_OPERATION_TYPE_MAKEDIR; + return 0; + } + + sint32 FSMakeDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const uint8* dirPath, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + { + // used by titles: XCX (via SAVEMakeDirAsync) + _FSCmdIntro(); + if (dirPath == NULL) + { + cemu_assert_debug(false); // path must not be NULL + return -0x400; + } + uint32 ukn1444 = _swapEndianU32(fsClientBody->iosuFSAHandle); + fsError = __FSPrepareCmd_MakeDir(fsCmdBlockBody, dirPath, 0x660); + if (fsError != (FSStatus)FS_RESULT::SUCCESS) + { + cemu_assert_debug(false); + return fsError; + } + __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, FS_CB_PLACEHOLDER_FINISHCMD); + return (FSStatus)FS_RESULT::SUCCESS; + } + + sint32 FSMakeDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const uint8* path, uint32 errorMask) + { + StackAllocator<FSAsyncParamsNew_t> asyncParams; + __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); + sint32 fsAsyncRet = FSMakeDirAsync(fsClient, fsCmdBlock, path, errorMask, asyncParams); + return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); + } + + sint32 __FSPrepareCmd_ChangeDir(FSCmdBlockBody_t* fsCmdBlockBody, uint8* path) + { + if (fsCmdBlockBody == NULL) + return 0xFFFCFFDD; + if (path == NULL) + return 0xFFFCFFDE; + size_t pathLen = strlen((char*)path); + if (pathLen >= FSA_CMD_PATH_MAX_LENGTH) + { + cemu_assert_debug(false); + pathLen = FSA_CMD_PATH_MAX_LENGTH - 1; + } + for (sint32 i = 0; i < pathLen; i++) + fsCmdBlockBody->ipcData.cmdChangeDir.path[i] = path[i]; + fsCmdBlockBody->ipcData.cmdChangeDir.path[pathLen] = '\0'; + fsCmdBlockBody->operationType = FSA_CMD_OPERATION_TYPE_CHANGEDIR; + return 0; + } + + sint32 FSChangeDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + { + _FSCmdIntro(); + if (path == NULL) + { + cemu_assert_debug(false); // path must not be NULL + return -0x400; + } + fsError = __FSPrepareCmd_ChangeDir(fsCmdBlockBody, (uint8*)path); + if (fsError != (FSStatus)FS_RESULT::SUCCESS) + return fsError; + __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, FS_CB_PLACEHOLDER_FINISHCMD); + return (FSStatus)FS_RESULT::SUCCESS; + } + + sint32 FSChangeDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask) + { + StackAllocator<FSAsyncParamsNew_t> asyncParams; + __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); + sint32 fsAsyncRet = FSChangeDirAsync(fsClient, fsCmdBlock, path, errorMask, asyncParams); + return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); + } + + sint32 __FSPrepareCmd_GetCwd(FSCmdBlockBody_t* fsCmdBlockBody) + { + if (fsCmdBlockBody == NULL) + return 0xFFFCFFDD; + fsCmdBlockBody->operationType = FSA_CMD_OPERATION_TYPE_GETCWD; + return 0; + } + + sint32 FSGetCwdAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* dirPathOut, sint32 dirPathMaxLen, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + { + // used by titles: Super Mario Maker + _FSCmdIntro(); + fsCmdBlockBody->returnValueMPTR = _swapEndianU32(memory_getVirtualOffsetFromPointer(dirPathOut)); + fsCmdBlockBody->transferSize = _swapEndianU32(dirPathMaxLen); + fsError = __FSPrepareCmd_GetCwd(fsCmdBlockBody); + if (fsError != (FSStatus)FS_RESULT::SUCCESS) + return fsError; + __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, FS_CB_PLACEHOLDER_FINISHCMD); + return (FSStatus)FS_RESULT::SUCCESS; + } + + sint32 FSGetCwd(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* dirPathOut, sint32 dirPathMaxLen, uint32 errorMask) + { + StackAllocator<FSAsyncParamsNew_t> asyncParams; + __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); + sint32 fsAsyncRet = FSGetCwdAsync(fsClient, fsCmdBlock, dirPathOut, dirPathMaxLen, errorMask, asyncParams); + auto r = __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); + return r; + } + + void __FSPrepareCmd_FlushQuota(FSCmdBlockBody_t* fsCmdBlockBody, char* path) + { + size_t pathLen = strlen((char*)path); + if (pathLen >= FSA_CMD_PATH_MAX_LENGTH) + { + cemu_assert_debug(false); + pathLen = FSA_CMD_PATH_MAX_LENGTH - 1; + } + for (sint32 i = 0; i < pathLen; i++) + fsCmdBlockBody->ipcData.cmdFlushQuota.path[i] = path[i]; + fsCmdBlockBody->ipcData.cmdFlushQuota.path[pathLen] = '\0'; + fsCmdBlockBody->operationType = FSA_CMD_OPERATION_TYPE_FLUSHQUOTA; + } + + sint32 FSFlushQuotaAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + { + _FSCmdIntro(); + fsCmdBlockBody->returnValueMPTR = 0; + __FSPrepareCmd_FlushQuota(fsCmdBlockBody, path); + __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, FS_CB_PLACEHOLDER_FINISHCMD); + return (FSStatus)FS_RESULT::SUCCESS; + } + + sint32 FSFlushQuota(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask) + { + StackAllocator<FSAsyncParamsNew_t> asyncParams; + __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); + sint32 fsAsyncRet = FSFlushQuotaAsync(fsClient, fsCmdBlock, path, errorMask, asyncParams); + return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); + } + + uint32 __FSPrepareCmd_QueryInfo(FSCmdBlockBody_t* fsCmdBlockBody, uint8* queryString, uint32 queryType) + { + // note: The output result is stored to fsCmdBlockBody+0x944 + size_t stringLen = strlen((char*)queryString); + if (stringLen >= FSA_CMD_PATH_MAX_LENGTH) + { + cemu_assert_debug(false); + stringLen = FSA_CMD_PATH_MAX_LENGTH - 1; + } + for (sint32 i = 0; i < stringLen; i++) + { + fsCmdBlockBody->ipcData.cmdQueryInfo.query[i] = queryString[i]; + } + fsCmdBlockBody->ipcData.cmdQueryInfo.query[stringLen] = '\0'; + fsCmdBlockBody->ipcData.cmdQueryInfo.queryType = _swapEndianU32(queryType); + fsCmdBlockBody->operationType = FSA_CMD_OPERATION_TYPE_QUERYINFO; + return 0; + } + + sint32 __FSQueryInfoAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint8* queryString, uint32 queryType, void* queryResult, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + { + _FSCmdIntro(); + cemu_assert(queryString && queryResult); // query string and result must not be null + fsCmdBlockBody->returnValueMPTR = _swapEndianU32(memory_getVirtualOffsetFromPointer(queryResult)); + fsError = __FSPrepareCmd_QueryInfo(fsCmdBlockBody, queryString, queryType); + if (fsError != (FSStatus)FS_RESULT::SUCCESS) + return fsError; + __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, FS_CB_PLACEHOLDER_FINISHCMD); + return (FSStatus)FS_RESULT::SUCCESS; + } + + sint32 FSGetStatAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* path, FSStat_t* statOut, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + { + sint32 fsStatus = __FSQueryInfoAsync(fsClient, fsCmdBlock, (uint8*)path, FSA_QUERY_TYPE_STAT, statOut, errorMask, fsAsyncParams); + return fsStatus; + } + + sint32 FSGetStat(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* path, FSStat_t* statOut, uint32 errorMask) + { + StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); + sint32 fsAsyncRet = FSGetStatAsync(fsClient, fsCmdBlock, path, statOut, errorMask, asyncParams); + sint32 ret = __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); + return ret; + } + + void __FSPrepareCmd_GetStatFile(FSCmdBlockBody_t* fsCmdBlockBody, FSFileHandle2 fileHandle, FSStat_t* statOut) + { + fsCmdBlockBody->ipcData.cmdGetStatFile.fileHandle = fileHandle; + fsCmdBlockBody->operationType = FSA_CMD_OPERATION_TYPE_GETSTATFILE; + fsCmdBlockBody->returnValueMPTR = _swapEndianU32(memory_getVirtualOffsetFromPointer(statOut)); + } + + sint32 FSGetStatFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSFileHandle2 fileHandle, FSStat_t* statOut, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + { + _FSCmdIntro(); + cemu_assert(statOut); // statOut must not be null + __FSPrepareCmd_GetStatFile(fsCmdBlockBody, fileHandle, statOut); + __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, FS_CB_PLACEHOLDER_FINISHCMD); + return (FSStatus)FS_RESULT::SUCCESS; + } + + sint32 FSGetStatFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSFileHandle2 fileHandle, FSStat_t* statOut, uint32 errorMask) + { + StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); + sint32 fsAsyncRet = FSGetStatFileAsync(fsClient, fsCmdBlock, fileHandle, statOut, errorMask, asyncParams); + return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); + } + + sint32 FSGetFreeSpaceSizeAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* path, FSLargeSize* returnedFreeSize, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + { + // used by: Wii U system settings app, Art Academy, Unity (e.g. Snoopy's Grand Adventure), Super Smash Bros + sint32 fsStatus = __FSQueryInfoAsync(fsClient, fsCmdBlock, (uint8*)path, FSA_QUERY_TYPE_FREESPACE, returnedFreeSize, errorMask, fsAsyncParams); + return fsStatus; + } + + sint32 FSGetFreeSpaceSize(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* path, FSLargeSize* returnedFreeSize, uint32 errorMask) + { + StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); + sint32 fsAsyncRet = FSGetFreeSpaceSizeAsync(fsClient, fsCmdBlock, path, returnedFreeSize, errorMask, asyncParams); + return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); + } + + uint32 __FSPrepareCmd_IsEof(FSCmdBlockBody_t* fsCmdBlockBody, uint32 fileHandle) + { + fsCmdBlockBody->ipcData.cmdDefault.destBufferMPTR = _swapEndianU32(fileHandle); + fsCmdBlockBody->operationType = FSA_CMD_OPERATION_TYPE_ISEOF; + return 0; + } + + sint32 FSIsEofAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + { + // used by Paper Monsters Recut + _FSCmdIntro(); + __FSPrepareCmd_IsEof(fsCmdBlockBody, fileHandle); + __FSQueueCmd(&fsClientBody->fsCmdQueue, fsCmdBlockBody, FS_CB_PLACEHOLDER_FINISHCMD); + return (FSStatus)FS_RESULT::SUCCESS; + } + + sint32 FSIsEof(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errorMask) + { + StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + __FSAsyncToSyncInit(fsClient, fsCmdBlock, asyncParams); + sint32 fsAsyncRet = FSIsEofAsync(fsClient, fsCmdBlock, fileHandle, errorMask, asyncParams); + return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); + } + + void FSSetUserData(FSCmdBlock_t* fsCmdBlock, void* userData) + { + FSCmdBlockBody_t* fsCmdBlockBody = __FSGetCmdBlockBody(fsCmdBlock); + if (fsCmdBlockBody) + fsCmdBlockBody->userData = userData; + } + + void* FSGetUserData(FSCmdBlock_t* fsCmdBlock) + { + FSCmdBlockBody_t* fsCmdBlockBody = __FSGetCmdBlockBody(fsCmdBlock); + void* userData = nullptr; + if (fsCmdBlockBody) + userData = fsCmdBlockBody->userData.GetPtr(); + return userData; + } + + FS_VOLSTATE FSGetVolumeState(FSClient_t* fsClient) + { + // todo + return FS_VOLSTATE_READY; + } + + sint32 FSGetErrorCodeForViewer(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock) + { + // todo + return 0; // no error + } + + FSCmdBlock_t* FSGetCurrentCmdBlock(FSClient_t* fsClient) + { + FSClientBody_t* fsClientBody = __FSGetClientBody(fsClient); + if (!fsClientBody) + return nullptr; + FSCmdBlockBody_t* cmdBlockBody = fsClientBody->currentCmdBlockBody; + if (!cmdBlockBody) + return nullptr; + return cmdBlockBody->selfCmdBlock; + } + + sint32 FSGetLastErrorCodeForViewer(FSClient_t* fsClient) + { + FSCmdBlock_t* cmdBlock = FSGetCurrentCmdBlock(fsClient); + if (cmdBlock) + { + cemu_assert_unimplemented(); + } + return 0; // no error + } + + void InitializeFS() + { + cafeExportRegister("coreinit", FSInit, LogType::File); + cafeExportRegister("coreinit", FSShutdown, LogType::File); + + cafeExportRegister("coreinit", FSGetMountSource, LogType::File); + cafeExportRegister("coreinit", FSGetMountSourceNext, LogType::File); + + cafeExportRegister("coreinit", FSMount, LogType::File); + cafeExportRegister("coreinit", FSBindMount, LogType::File); + + // client management + cafeExportRegister("coreinit", FSAddClientEx, LogType::File); + cafeExportRegister("coreinit", FSAddClient, LogType::File); + cafeExportRegister("coreinit", FSDelClient, LogType::File); + cafeExportRegister("coreinit", FSGetClientNum, LogType::File); + + // cmd + cafeExportRegister("coreinit", FSInitCmdBlock, LogType::File); + cafeExportRegister("coreinit", FSGetAsyncResult, LogType::File); + + // file operations + cafeExportRegister("coreinit", FSOpenFileAsync, LogType::File); + cafeExportRegister("coreinit", FSOpenFile, LogType::File); + cafeExportRegister("coreinit", FSOpenFileExAsync, LogType::File); + cafeExportRegister("coreinit", FSOpenFileEx, LogType::File); + cafeExportRegister("coreinit", FSCloseFileAsync, LogType::File); + cafeExportRegister("coreinit", FSCloseFile, LogType::File); + + cafeExportRegister("coreinit", FSReadFileAsync, LogType::File); + cafeExportRegister("coreinit", FSReadFile, LogType::File); + cafeExportRegister("coreinit", FSReadFileWithPosAsync, LogType::File); + cafeExportRegister("coreinit", FSReadFileWithPos, LogType::File); + + cafeExportRegister("coreinit", FSWriteFileAsync, LogType::File); + cafeExportRegister("coreinit", FSWriteFile, LogType::File); + cafeExportRegister("coreinit", FSWriteFileWithPosAsync, LogType::File); + cafeExportRegister("coreinit", FSWriteFileWithPos, LogType::File); + + cafeExportRegister("coreinit", FSSetPosFileAsync, LogType::File); + cafeExportRegister("coreinit", FSSetPosFile, LogType::File); + cafeExportRegister("coreinit", FSGetPosFileAsync, LogType::File); + cafeExportRegister("coreinit", FSGetPosFile, LogType::File); + + cafeExportRegister("coreinit", FSAppendFileAsync, LogType::File); + cafeExportRegister("coreinit", FSAppendFile, LogType::File); + + cafeExportRegister("coreinit", FSTruncateFileAsync, LogType::File); + cafeExportRegister("coreinit", FSTruncateFile, LogType::File); + + cafeExportRegister("coreinit", FSRenameAsync, LogType::File); + cafeExportRegister("coreinit", FSRename, LogType::File); + cafeExportRegister("coreinit", FSRemoveAsync, LogType::File); + cafeExportRegister("coreinit", FSRemove, LogType::File); + cafeExportRegister("coreinit", FSMakeDirAsync, LogType::File); + cafeExportRegister("coreinit", FSMakeDir, LogType::File); + cafeExportRegister("coreinit", FSChangeDirAsync, LogType::File); + cafeExportRegister("coreinit", FSChangeDir, LogType::File); + cafeExportRegister("coreinit", FSGetCwdAsync, LogType::File); + cafeExportRegister("coreinit", FSGetCwd, LogType::File); + + cafeExportRegister("coreinit", FSIsEofAsync, LogType::File); + cafeExportRegister("coreinit", FSIsEof, LogType::File); + + // directory operations + cafeExportRegister("coreinit", FSOpenDirAsync, LogType::File); + cafeExportRegister("coreinit", FSOpenDir, LogType::File); + cafeExportRegister("coreinit", FSReadDirAsync, LogType::File); + cafeExportRegister("coreinit", FSReadDir, LogType::File); + cafeExportRegister("coreinit", FSCloseDirAsync, LogType::File); + cafeExportRegister("coreinit", FSCloseDir, LogType::File); + + // stat + cafeExportRegister("coreinit", FSGetFreeSpaceSizeAsync, LogType::File); + cafeExportRegister("coreinit", FSGetFreeSpaceSize, LogType::File); + + cafeExportRegister("coreinit", FSGetStatAsync, LogType::File); + cafeExportRegister("coreinit", FSGetStat, LogType::File); + + cafeExportRegister("coreinit", FSGetStatFileAsync, LogType::File); + cafeExportRegister("coreinit", FSGetStatFile, LogType::File); + + // misc + cafeExportRegister("coreinit", FSFlushQuotaAsync, LogType::File); + cafeExportRegister("coreinit", FSFlushQuota, LogType::File); + + cafeExportRegister("coreinit", FSSetUserData, LogType::File); + cafeExportRegister("coreinit", FSGetUserData, LogType::File); + + cafeExportRegister("coreinit", FSGetCurrentCmdBlock, LogType::File); + + cafeExportRegister("coreinit", FSGetVolumeState, LogType::File); + cafeExportRegister("coreinit", FSGetErrorCodeForViewer, LogType::Placeholder); + cafeExportRegister("coreinit", FSGetLastErrorCodeForViewer, LogType::Placeholder); + + g_fsRegisteredClientBodies = nullptr; + } +} diff --git a/src/Cafe/OS/libs/coreinit/coreinit_FS.h b/src/Cafe/OS/libs/coreinit/coreinit_FS.h new file mode 100644 index 00000000..cdda692a --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_FS.h @@ -0,0 +1,325 @@ +#pragma once +#include "Cafe/OS/libs/coreinit/coreinit_Thread.h" +#include "Cafe/IOSU/iosu_ipc_common.h" +#include "Cafe/IOSU/fsa/fsa_types.h" +#include "Cafe/IOSU/fsa/iosu_fsa.h" +#include "coreinit_MessageQueue.h" + +typedef struct +{ + uint32be fileHandle; +}FSFileHandleDepr_t; + +typedef MEMPTR<betype<FSDirHandle2>> FSDirHandlePtr; + +typedef struct +{ + MEMPTR<void> userCallback; + MEMPTR<void> userContext; + MEMPTR<coreinit::OSMessageQueue> ioMsgQueue; +}FSAsyncParamsNew_t; + +static_assert(sizeof(FSAsyncParamsNew_t) == 0xC); + +typedef struct +{ + MPTR userCallback; // 0x96C + MPTR userContext; + MPTR ioMsgQueue; +}FSAsyncParams_t; // legacy struct. Replace with FSAsyncParamsNew_t + +namespace coreinit +{ + struct FSCmdQueue + { + enum class QUEUE_FLAG : uint32 + { + IS_FULL = (1 << 0), // waiting for Ioctl(v) result + CANCEL_ALL = (1 << 4), + }; + + /* +0x00 */ MPTR firstMPTR; + /* +0x04 */ MPTR lastMPTR; + /* +0x08 */ OSMutex mutex; + /* +0x34 */ MPTR dequeueHandlerFuncMPTR; + /* +0x38 */ uint32be numCommandsInFlight; + /* +0x3C */ uint32 numMaxCommandsInFlight; + /* +0x40 */ betype<QUEUE_FLAG> queueFlags; + }; + DEFINE_ENUM_FLAG_OPERATORS(FSCmdQueue::QUEUE_FLAG); + + #define FS_CLIENT_BUFFER_SIZE (5888) + #define FS_CMD_BLOCK_SIZE (2688) + + struct FSClient_t + { + uint8 buffer[FS_CLIENT_BUFFER_SIZE]; + }; + + struct FSCmdBlock_t + { + union + { + uint8 buffer[FS_CMD_BLOCK_SIZE]; + struct + { + uint32 mount_it; + }data; + }; + }; + + static_assert(sizeof(FSCmdBlock_t) == FS_CMD_BLOCK_SIZE); + + struct FSClientBody_t + { + uint8 ukn0000[0x100]; + uint8 ukn0100[0x100]; + uint8 ukn0200[0x100]; + uint8 ukn0300[0x100]; + uint8 ukn0400[0x100]; + uint8 ukn0500[0x100]; + uint8 ukn0600[0x100]; + uint8 ukn0700[0x100]; + uint8 ukn0800[0x100]; + uint8 ukn0900[0x100]; + uint8 ukn0A00[0x100]; + uint8 ukn0B00[0x100]; + uint8 ukn0C00[0x100]; + uint8 ukn0D00[0x100]; + uint8 ukn0E00[0x100]; + uint8 ukn0F00[0x100]; + uint8 ukn1000[0x100]; + uint8 ukn1100[0x100]; + uint8 ukn1200[0x100]; + uint8 ukn1300[0x100]; + uint8 ukn1400[0x10]; + uint8 ukn1410[0x10]; + uint8 ukn1420[0x10]; + uint8 ukn1430[0x10]; + uint32 ukn1440; + betype<IOSDevHandle> iosuFSAHandle; + uint32 ukn1448; + uint32 ukn144C; + uint8 ukn1450[0x10]; + uint8 ukn1460[0x10]; + uint8 ukn1470[0x10]; + FSCmdQueue fsCmdQueue; + /* +0x14C4 */ MEMPTR<struct FSCmdBlockBody_t> currentCmdBlockBody; // set to currently active cmd + uint32 ukn14C8; + uint32 ukn14CC; + uint8 ukn14D0[0x10]; + uint8 ukn14E0[0x10]; + uint8 ukn14F0[0x10]; + uint8 ukn1500[0x100]; + uint32 ukn1600; + uint32 ukn1604; + uint32 ukn1608; + uint32 ukn160C; + uint32 ukn1610; + MEMPTR<FSClientBody_t> fsClientBodyNext; // next FSClientBody_t* in list of registered clients (list is circular, the last element points to the first element) + uint32 ukn1618; + /* +0x161C */ MEMPTR<FSClient_t> selfClient; // pointer to FSClient struct which holds this FSClientBody + uint32 ukn1620; + }; + + struct FSAsyncResult + { + /* +0x00 */ FSAsyncParamsNew_t fsAsyncParamsNew; + + // fs message storage + struct FSMessage + { + /* +0x0C / 0x0978 */ MEMPTR<FSAsyncResult> fsAsyncResult; + /* +0x10 */ MPTR fsClientMPTR2; // 0x097C + /* +0x14 */ MPTR fsCmdBlockMPTR; // 0x0980 + /* +0x18 */ MPTR commandType; // 0x0984 + }; + + union + { + OSMessage osMsg; + FSMessage fsMsg; + }msgUnion; + + /* +0x1C */ MEMPTR<FSClient_t> fsClient; // 0x0988 + /* +0x20 */ MEMPTR<FSCmdBlock_t> fsCmdBlock; // 0x98C + /* +0x24 */ uint32be fsStatusNew; // 0x990 + }; + + static_assert(sizeof(FSAsyncResult) == 0x28); + + struct FSCmdBlockBody_t + { + iosu::fsa::FSAIpcCommand ipcData; + uint8 ukn0820[0x10]; + uint8 ukn0830[0x10]; + uint8 ukn0840[0x10]; + uint8 ukn0850[0x10]; + uint8 ukn0860[0x10]; + uint8 ukn0870[0x10]; + MPTR fsCmdBlockBodyMPTR; + uint32 ukn0884; + uint32 ukn0888; + uint32 destBuffer88CMPTR; + uint32 ukn0890; + uint32 ukn0894; + uint32 ukn0898; + uint32 ukn089C; + uint32 ukn08A0; + uint32 ukn08A4; + uint32 ukn08A8; + uint32 ukn08AC; + uint8 ukn08B0[0x10]; + uint8 ukn08C0[0x10]; + uint8 ukn08D0[0x10]; + uint8 ukn08E0[0x10]; + uint8 ukn08F0[0x10]; + /* +0x0900 */ uint32be operationType; + betype<IOSDevHandle> fsaDevHandle; + /* +0x0908 */ uint16be ipcReqType; // 0 -> IoctlAsync, 1 -> IoctlvAsync + uint8 ukn090A; + uint8 ukn090B; + uint32 ukn090C; + uint32 ukn0910; + uint32 ukn0914; + uint32 ukn0918; + uint32 ukn091C; + uint32 ukn0920; + uint32 ukn0924; + uint32 ukn0928; + uint32 ukn092C; + uint32 ukn0930; + uint32 ukn0934; + /* +0x0938 */ MEMPTR<FSClientBody_t> fsClientBody; + /* +0x093C */ uint32 statusCode; // not a status code but rather the state? Uses weird values for some reason + /* +0x0940 */ uint32be cancelState; // bitmask. Bit 0 -> If set command has been canceled + // return values + /* +0x0944 */ uint32 returnValueMPTR; // returnedFilePos (used to store pointer to variable that holds return value?), also used by QUERYINFO to store pointer for result. Also used for GetCwd() to hold the pointer for the returned dir path. Also used by OPENFILE to hold returned fileHandle + /* +0x0948 */ uint32 transferSize; // number of bytes to transfer + // transfer control? + uint32 uknVal094C; + uint32 transferElemSize; // number of bytes of a single transferred element (count of elements can be calculated via count = transferSize/transferElemSize) + uint32 uknVal0954; // this is set to max(0x10, transferSize) for reads and to min(0x40000, transferSize) for writes? + // link for cmd queue + MPTR nextMPTR; // points towards FSCmdQueue->first + MPTR previousMPTR; // points towards FSCmdQueue->last + + /* +0x960 */ betype<FSA_RESULT> lastFSAStatus; + uint32 ukn0964; + /* +0x0968 */ uint8 errHandling; // return error flag mask + /* +0x096C */ FSAsyncResult asyncResult; + /* +0x0994 */ MEMPTR<void> userData; + /* +0x0998 */ OSMessageQueue syncTaskMsgQueue; // this message queue is used when mapping asynchronous tasks to synchronous API + /* +0x09D4 */ OSMessage _syncTaskMsg[1]; + /* +0x09E4 */ MPTR cmdFinishFuncMPTR; + /* +0x09E8 */ uint8 priority; + uint8 uknStatusGuessed09E9; + uint8 ukn09EA; + uint8 ukn09EB; + uint32 ukn09EC; + uint32 ukn9F0; + uint32be ukn9F4_lastErrorRelated; + /* +0x9F8 */ MEMPTR<FSCmdBlock_t> selfCmdBlock; + uint32 ukn9FC; + }; + + static_assert(sizeof(FSAsyncParams_t) == 0xC); + static_assert(sizeof(FSCmdBlock_t) == 0xA80); + + #define FSA_CMD_FLAG_SET_POS (1<<0) + + #define FSA_CMD_OPERATION_TYPE_CHANGEDIR (0x5) + #define FSA_CMD_OPERATION_TYPE_GETCWD (0x6) + #define FSA_CMD_OPERATION_TYPE_MAKEDIR (0x7) + #define FSA_CMD_OPERATION_TYPE_REMOVE (0x8) + #define FSA_CMD_OPERATION_TYPE_RENAME (0x9) + #define FSA_CMD_OPERATION_TYPE_OPENDIR (0xA) + #define FSA_CMD_OPERATION_TYPE_READDIR (0xB) + #define FSA_CMD_OPERATION_TYPE_CLOSEDIR (0xD) + #define FSA_CMD_OPERATION_TYPE_OPENFILE (0xE) + #define FSA_CMD_OPERATION_TYPE_READ (0xF) + #define FSA_CMD_OPERATION_TYPE_WRITE (0x10) + #define FSA_CMD_OPERATION_TYPE_GETPOS (0x11) + #define FSA_CMD_OPERATION_TYPE_SETPOS (0x12) + #define FSA_CMD_OPERATION_TYPE_ISEOF (0x13) + #define FSA_CMD_OPERATION_TYPE_GETSTATFILE (0x14) + #define FSA_CMD_OPERATION_TYPE_CLOSEFILE (0x15) + #define FSA_CMD_OPERATION_TYPE_QUERYINFO (0x18) + #define FSA_CMD_OPERATION_TYPE_APPENDFILE (0x19) + #define FSA_CMD_OPERATION_TYPE_TRUNCATEFILE (0x1A) + #define FSA_CMD_OPERATION_TYPE_FLUSHQUOTA (0x1E) + + + #define FSA_CMD_STATUS_CODE_D900A21 0xD900A21 // cmd block is initialized + #define FSA_CMD_STATUS_CODE_D900A22 0xD900A22 // cmd block is queued + #define FSA_CMD_STATUS_CODE_D900A24 0xD900A24 // cmd block was processed and is available again + #define FSA_CMD_STATUS_CODE_D900A26 0xD900A26 // cmd block result is being processed + + enum FS_VOLSTATE : sint32 + { + FS_VOLSTATE_READY = 1, + }; + + // internal interface + sint32 __FSQueryInfoAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint8* queryString, uint32 queryType, void* queryResult, uint32 errHandling, FSAsyncParamsNew_t* fsAsyncParams); + + // coreinit exports + FS_RESULT FSAddClientEx(FSClient_t* fsClient, uint32 uknR4, uint32 errHandling); + FS_RESULT FSAddClient(FSClient_t* fsClient, uint32 errHandling); + FS_RESULT FSDelClient(FSClient_t* fsClient, uint32 errHandling); + + void FSInitCmdBlock(FSCmdBlock_t* fsCmdBlock); + + sint32 FSOpenFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, char* mode, FSFileHandleDepr_t* fileHandle, uint32 errHandling, FSAsyncParamsNew_t* asyncParams); + sint32 FSOpenFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, char* mode, FSFileHandleDepr_t* fileHandle, uint32 errHandling); + + sint32 FSReadFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSReadFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask); + sint32 FSReadFileWithPosAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSReadFileWithPos(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask); + + sint32 FSWriteFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSWriteFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask); + sint32 FSWriteFileWithPosAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSWriteFileWithPos(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask); + + sint32 FSSetPosFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 filePos, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSSetPosFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 filePos, uint32 errorMask); + sint32 FSGetPosFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32be* returnedFilePos, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSGetPosFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32be* returnedFilePos, uint32 errorMask); + + sint32 FSAppendFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 size, uint32 count, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSAppendFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 size, uint32 count, uint32 errorMask); + + sint32 FSIsEofAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSIsEof(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errorMask); + + sint32 FSRenameAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* srcPath, char* dstPath, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSRename(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* srcPath, char* dstPath, uint32 errorMask); + sint32 FSRemoveAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint8* filePath, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSRemove(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint8* filePath, uint32 errorMask); + sint32 FSMakeDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const uint8* dirPath, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSMakeDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const uint8* path, uint32 errorMask); + sint32 FSChangeDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSChangeDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask); + sint32 FSGetCwdAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* dirPathOut, sint32 dirPathMaxLen, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSGetCwd(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* dirPathOut, sint32 dirPathMaxLen, uint32 errorMask); + + sint32 FSGetFreeSpaceSizeAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* path, FSLargeSize* returnedFreeSize, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSGetFreeSpaceSize(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* path, FSLargeSize* returnedFreeSize, uint32 errorMask); + + sint32 FSOpenDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, FSDirHandlePtr dirHandleOut, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSOpenDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, FSDirHandlePtr dirHandleOut, uint32 errorMask); + sint32 FSReadDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, FSDirEntry_t* dirEntryOut, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSReadDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, FSDirEntry_t* dirEntryOut, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSCloseDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSCloseDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, uint32 errorMask); + + sint32 FSFlushQuotaAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSFlushQuota(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask); + + FS_VOLSTATE FSGetVolumeState(FSClient_t* fsClient); + + void InitializeFS(); +}; + diff --git a/src/Cafe/OS/libs/coreinit/coreinit_GHS.cpp b/src/Cafe/OS/libs/coreinit/coreinit_GHS.cpp new file mode 100644 index 00000000..d7952053 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_GHS.cpp @@ -0,0 +1,285 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/libs/coreinit/coreinit_GHS.h" +#include "Cafe/OS/libs/coreinit/coreinit_MEM.h" +#include "Cafe/OS/libs/coreinit/coreinit_Thread.h" +#include "Cafe/OS/RPL/rpl.h" + +namespace coreinit +{ + struct iobbuf + { + uint32be ukn00; // lock index? + uint32be ukn04; // ? + uint32be ukn08; // ? + uint32be flags; // permissions and channel + }; + + #define GHS_FOPEN_MAX 100 + + struct GHSAccessibleData + { + iobbuf _iob[GHS_FOPEN_MAX]; + MPTR _iob_lock[GHS_FOPEN_MAX]; + uint16be __gh_FOPEN_MAX; + MEMPTR<void> ghs_environ; + uint32 ghs_Errno; // exposed by __gh_errno_ptr() or via 'errno' data export + }; + + SysAllocator<GHSAccessibleData> g_ghs_data; + + struct ghs_flock + { + uint32be mutexIndex; + }; + + void __ghs_flock_create(ghs_flock* flock); + ghs_flock* __ghs_flock_ptr(iobbuf* iob); + + std::recursive_mutex g_ghsLock; + std::recursive_mutex g_ghsLockFlock; + + SysAllocator<coreinit::OSMutex, GHS_FOPEN_MAX> _flockMutexArray; + bool _flockMutexMask[GHS_FOPEN_MAX]; // if set, mutex in _flockMutexArray is reserved + + #define IOB_FLAG_IN (0x1) + #define IOB_FLAG_OUT (0x2) + + #define IOB_FLAG_CHANNEL(__x) ((__x)<<18) + + void PrepareGHSRuntime() + { + g_ghs_data->ghs_environ = nullptr; + g_ghs_data->__gh_FOPEN_MAX = GHS_FOPEN_MAX; + g_ghs_data->ghs_Errno = 0; + + for (sint32 i = 0; i < GHS_FOPEN_MAX; i++) + _flockMutexMask[i] = false; + // init stdin/stdout/stderr + g_ghs_data->_iob[0].flags = IOB_FLAG_IN; + g_ghs_data->_iob[1].flags = IOB_FLAG_OUT; + g_ghs_data->_iob[1].flags = IOB_FLAG_OUT; + g_ghs_data->_iob[0].flags |= IOB_FLAG_CHANNEL(0); + g_ghs_data->_iob[1].flags |= IOB_FLAG_CHANNEL(1); + g_ghs_data->_iob[2].flags |= IOB_FLAG_CHANNEL(2); + __ghs_flock_create(__ghs_flock_ptr(g_ghs_data->_iob + 0)); + __ghs_flock_create(__ghs_flock_ptr(g_ghs_data->_iob + 1)); + __ghs_flock_create(__ghs_flock_ptr(g_ghs_data->_iob + 2)); + + osLib_addVirtualPointer("coreinit", "__gh_FOPEN_MAX", memory_getVirtualOffsetFromPointer(&g_ghs_data->__gh_FOPEN_MAX)); + osLib_addVirtualPointer("coreinit", "_iob", memory_getVirtualOffsetFromPointer(g_ghs_data->_iob)); + osLib_addVirtualPointer("coreinit", "environ", memory_getVirtualOffsetFromPointer(&g_ghs_data->ghs_environ)); + osLib_addVirtualPointer("coreinit", "errno", memory_getVirtualOffsetFromPointer(&g_ghs_data->ghs_Errno)); + } + + void __ghs_flock_create(ghs_flock* flock) + { + g_ghsLockFlock.lock(); + // find available mutex + sint32 mutexIndex = -1; + for (sint32 i = 0; i < GHS_FOPEN_MAX; i++) + { + if (!_flockMutexMask[i]) + { + mutexIndex = i; + break; + } + } + if (mutexIndex == -1) + { + forceLog_printf("__ghs_flock_create(): No flock available"); + cemu_assert(false); // no available mutex + } + // mark mutex as reserved + _flockMutexMask[mutexIndex] = true; + // init mutex + coreinit::OSInitMutexEx(_flockMutexArray.GetPtr() + mutexIndex, NULL); + // update flock to point to the reserved mutex + flock->mutexIndex = mutexIndex; + g_ghsLockFlock.unlock(); + } + + void __ghs_flock_destroy(uint32 index) + { + g_ghsLockFlock.lock(); + cemu_assert_debug(index > 2); // stdin/stdout/stderr should never be released? + cemu_assert(index < GHS_FOPEN_MAX); + cemu_assert_debug(_flockMutexMask[index]); + _flockMutexMask[index] = false; + g_ghsLockFlock.unlock(); + } + + ghs_flock* __ghs_flock_ptr(iobbuf* iob) + { + size_t streamIndex = iob - g_ghs_data->_iob; + return (ghs_flock*)&(g_ghs_data->_iob_lock[streamIndex]); + } + + void __ghs_flock_file(uint32 index) + { + cemu_assert(index < GHS_FOPEN_MAX); + OSLockMutex(_flockMutexArray.GetPtr() + index); + } + + void __ghs_funlock_file(uint32 index) + { + cemu_assert(index < GHS_FOPEN_MAX); + OSUnlockMutex(_flockMutexArray.GetPtr() + index); + } + + void __ghsLock() + { + while (!g_ghsLock.try_lock()) + { + PPCCore_switchToScheduler(); + } + } + + void __ghsUnlock() + { + g_ghsLock.unlock(); + } + + void* __get_eh_init_block() + { + return nullptr; + } + + void* __get_eh_globals() + { + OSThread_t* currentThread = coreinit::OSGetCurrentThread(); + return currentThread->crt.eh_globals.GetPtr(); + } + + void* __get_eh_mem_manage() + { + OSThread_t* currentThread = coreinit::OSGetCurrentThread(); + return ¤tThread->crt.eh_mem_manage; + } + + void* __gh_errno_ptr() + { + OSThread_t* currentThread = coreinit::OSGetCurrentThread(); + return ¤tThread->context.error; + } + + void* __get_eh_store_globals() + { + OSThread_t* currentThread = coreinit::OSGetCurrentThread(); + return ¤tThread->crt.eh_store_globals; + } + + void* __get_eh_store_globals_tdeh() + { + OSThread_t* currentThread = coreinit::OSGetCurrentThread(); + return ¤tThread->crt.eh_store_globals_tdeh; + } + + struct ghs_mtx_t + { + MEMPTR<coreinit::OSMutex> mutexPtr; + }; + + void __ghs_mtx_init(ghs_mtx_t* mtx) + { + mtx->mutexPtr = (coreinit::OSMutex*)coreinit::_weak_MEMAllocFromDefaultHeapEx(ppcsizeof<coreinit::OSMutex>(), 8); + coreinit::OSInitMutex(mtx->mutexPtr.GetPtr()); + } + + void __ghs_mtx_dst(ghs_mtx_t* mtx) + { + coreinit::_weak_MEMFreeToDefaultHeap(mtx->mutexPtr.GetPtr()); + mtx->mutexPtr = nullptr; + } + + void __ghs_mtx_lock(ghs_mtx_t* mtx) + { + coreinit::OSLockMutex(mtx->mutexPtr.GetPtr()); + } + + void __ghs_mtx_unlock(ghs_mtx_t* mtx) + { + coreinit::OSUnlockMutex(mtx->mutexPtr.GetPtr()); + } + + struct OSTLSBlock + { + MPTR addr; + uint32 ukn04; + }; + + static_assert(sizeof(OSTLSBlock) == 8); + + struct TLS_Index + { + uint16 ukn00; + uint16 tlsModuleIndex; + MPTR ukn04; + }; + + void* __tls_get_addr(TLS_Index* tlsIndex) + { + OSThread_t* currentThread = coreinit::OSGetCurrentThread(); + + if (_swapEndianU16(tlsIndex->tlsModuleIndex) == 0) + assert_dbg(); + + // check if we need to allocate additional TLS blocks for this thread + if (_swapEndianU16(tlsIndex->tlsModuleIndex) >= _swapEndianU32(currentThread->numAllocatedTLSBlocks)) + { + uint32 allocSize = (RPLLoader_GetMaxTLSModuleIndex() + 1) * sizeof(OSTLSBlock); // __OSDynLoad_gTLSHeader.ukn00 * 8; + MPTR allocMem = coreinit_allocFromSysArea(allocSize, 4); + memset(memory_getPointerFromVirtualOffset(allocMem), 0, allocSize); + if (_swapEndianU32(currentThread->numAllocatedTLSBlocks) != 0) + { + // keep previously allocated blocks + memcpy(memory_getPointerFromVirtualOffset(allocMem), memory_getPointerFromVirtualOffset(_swapEndianU32(currentThread->tlsBlocksMPTR)), _swapEndianU32(currentThread->numAllocatedTLSBlocks) * 8); + } + currentThread->tlsBlocksMPTR = _swapEndianU32(allocMem); + currentThread->numAllocatedTLSBlocks = _swapEndianU16(RPLLoader_GetMaxTLSModuleIndex() + 1); + } + // look up TLS address based on moduleIndex + OSTLSBlock* tlsBlock = (OSTLSBlock*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(currentThread->tlsBlocksMPTR) + sizeof(OSTLSBlock) * (uint32)_swapEndianU16(tlsIndex->tlsModuleIndex)); + if (tlsBlock->addr != _swapEndianU32(MPTR_NULL)) + { + //osLib_returnFromFunction(hCPU, _swapEndianU32(tlsBlock->addr)+_swapEndianU32(tlsIndex->ukn04)); + return memory_getPointerFromVirtualOffset(_swapEndianU32(tlsBlock->addr) + _swapEndianU32(tlsIndex->ukn04)); + } + // alloc data for TLS block + uint8* tlsSectionData = nullptr; + sint32 tlsSize = 0; + + bool r = RPLLoader_GetTLSDataByTLSIndex((sint16)_swapEndianU16(tlsIndex->tlsModuleIndex), &tlsSectionData, &tlsSize); + cemu_assert(r); + cemu_assert(tlsSize != 0); + + MPTR tlsData = coreinit_allocFromSysArea(tlsSize, 32); + memcpy(memory_getPointerFromVirtualOffset(tlsData), tlsSectionData, tlsSize); + tlsBlock->addr = _swapEndianU32(tlsData); + return memory_getPointerFromVirtualOffset(_swapEndianU32(tlsBlock->addr) + _swapEndianU32(tlsIndex->ukn04)); + } + + void InitializeGHS() + { + cafeExportRegister("coreinit", __ghs_flock_create, LogType::Placeholder); + cafeExportRegister("coreinit", __ghs_flock_destroy, LogType::Placeholder); + cafeExportRegister("coreinit", __ghs_flock_ptr, LogType::Placeholder); + cafeExportRegister("coreinit", __ghs_flock_file, LogType::Placeholder); + cafeExportRegister("coreinit", __ghs_funlock_file, LogType::Placeholder); + cafeExportRegister("coreinit", __ghsLock, LogType::Placeholder); + cafeExportRegister("coreinit", __ghsUnlock, LogType::Placeholder); + + cafeExportRegister("coreinit", __get_eh_init_block, LogType::Placeholder); + cafeExportRegister("coreinit", __get_eh_globals, LogType::Placeholder); + cafeExportRegister("coreinit", __get_eh_mem_manage, LogType::Placeholder); + cafeExportRegister("coreinit", __gh_errno_ptr, LogType::Placeholder); + cafeExportRegister("coreinit", __get_eh_store_globals, LogType::Placeholder); + cafeExportRegister("coreinit", __get_eh_store_globals_tdeh, LogType::Placeholder); + + cafeExportRegister("coreinit", __ghs_mtx_init, LogType::Placeholder); + cafeExportRegister("coreinit", __ghs_mtx_dst, LogType::Placeholder); + cafeExportRegister("coreinit", __ghs_mtx_lock, LogType::Placeholder); + cafeExportRegister("coreinit", __ghs_mtx_unlock, LogType::Placeholder); + + cafeExportRegister("coreinit", __tls_get_addr, LogType::Placeholder); + } +}; \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_GHS.h b/src/Cafe/OS/libs/coreinit/coreinit_GHS.h new file mode 100644 index 00000000..0ac09e94 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_GHS.h @@ -0,0 +1,8 @@ +#pragma once + +namespace coreinit +{ + void PrepareGHSRuntime(); + + void InitializeGHS(); +}; \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_HWInterface.cpp b/src/Cafe/OS/libs/coreinit/coreinit_HWInterface.cpp new file mode 100644 index 00000000..b95720f7 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_HWInterface.cpp @@ -0,0 +1,138 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "coreinit_HWInterface.h" + +namespace coreinit +{ + enum class RegisterInterfaceId : uint32 // for __OSRead/__OSWrite API (register access in userspace) + { + INTERFACE_VI_UKN = 0, // 0x0C1E0000 + + INTERFACE_VI2_UKN = 3, // might also be some other interface? + + + }; + + enum class SysRegisterInterfaceId : uint32 // for __OSRead/__OSWriteRegister (register access via kernel systemcall) + { + INTERFACE_UKN = 0, + + INTERFACE_3_ACR_VI = 3, // 0x0D00021C + + INTERFACE_6_SI = 6, // 0x0D006400 + INTERFACE_7_AI_PROBABLY = 7, // 0x0D046C00 // AI or some secondary AI interface? + + }; + + PAddr _GetRegisterPhysicalAddress(RegisterInterfaceId interfaceId, uint32 offset) + { + PAddr base = 0; + switch (interfaceId) + { + case RegisterInterfaceId::INTERFACE_VI_UKN: + base = 0x0C1E0000; + break; + default: + cemu_assert_debug(false); // todo + return 0; + } + return base + offset; + } + + + PAddr _GetSysRegisterPhysicalAddress(SysRegisterInterfaceId interfaceId, uint32 offset) + { + PAddr base = 0; + switch (interfaceId) + { + case SysRegisterInterfaceId::INTERFACE_3_ACR_VI: + base = 0x0D00021C; + break; + case SysRegisterInterfaceId::INTERFACE_6_SI: + base = 0x0D006400; + break; + default: + cemu_assert_debug(false); // todo + return 0; + } + return base + offset; + } + + /* Userspace register interface */ + + uint32 OSReadRegister32(RegisterInterfaceId interfaceId, uint32 offset) + { + PAddr regAddr = _GetRegisterPhysicalAddress(interfaceId, offset); + cemu_assert_debug(regAddr); + return MMU::ReadMMIO_32(regAddr); + } + + uint16 OSReadRegister16(RegisterInterfaceId interfaceId, uint32 offset) + { + PAddr regAddr = _GetRegisterPhysicalAddress(interfaceId, offset); + cemu_assert_debug(regAddr); + return MMU::ReadMMIO_16(regAddr); + } + + void OSWriteRegister16(uint16 newValue, RegisterInterfaceId interfaceId, uint32 offset) + { + static bool s_dbg = false; + if (!s_dbg) + { + cemu_assert_debug(false); + s_dbg = true; + } + } + + void OSWriteRegister32(uint16 newValue, RegisterInterfaceId interfaceId, uint32 offset) + { + static bool s_dbg = false; + if (!s_dbg) + { + cemu_assert_debug(false); + s_dbg = true; + } + } + + void OSModifyRegister16(RegisterInterfaceId interfaceId, uint32 uknR4, uint32 uknR5, uint32 uknR6) + { + static bool s_dbg = false; + if (!s_dbg) + { + cemu_assert_debug(false); + s_dbg = true; + } + } + + /* Kernel register interface */ + + uint32 __OSReadRegister32Ex(SysRegisterInterfaceId interfaceId, uint32 registerId) + { + uint32 offset = registerId * 4; + cemu_assert_debug(offset < 0x40); + PAddr regAddr = _GetSysRegisterPhysicalAddress(interfaceId, offset); + cemu_assert_debug(regAddr); + return MMU::ReadMMIO_32(regAddr); + } + + void __OSWriteRegister32Ex(SysRegisterInterfaceId interfaceId, uint32 registerId, uint32 newValue) + { + uint32 offset = registerId * 4; + cemu_assert_debug(offset < 0x40); + PAddr regAddr = _GetSysRegisterPhysicalAddress(interfaceId, offset); + cemu_assert_debug(regAddr); + MMU::WriteMMIO_32(regAddr, newValue); + } + + void InitializeHWInterface() + { + cafeExportRegister("coreinit", OSReadRegister32, LogType::Placeholder); + cafeExportRegister("coreinit", OSReadRegister16, LogType::Placeholder); + cafeExportRegister("coreinit", OSWriteRegister16, LogType::Placeholder); + cafeExportRegister("coreinit", OSWriteRegister32, LogType::Placeholder); + cafeExportRegister("coreinit", OSModifyRegister16, LogType::Placeholder); + + + cafeExportRegister("coreinit", __OSReadRegister32Ex, LogType::Placeholder); + cafeExportRegister("coreinit", __OSWriteRegister32Ex, LogType::Placeholder); + }; +}; \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_HWInterface.h b/src/Cafe/OS/libs/coreinit/coreinit_HWInterface.h new file mode 100644 index 00000000..39eb2ac6 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_HWInterface.h @@ -0,0 +1,4 @@ +namespace coreinit +{ + void InitializeHWInterface(); +}; \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_IM.cpp b/src/Cafe/OS/libs/coreinit/coreinit_IM.cpp new file mode 100644 index 00000000..2f4779fe --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_IM.cpp @@ -0,0 +1,137 @@ +#include "Cafe/OS/common/OSCommon.h" + +// APD = Automatic Power Down + +namespace coreinit +{ + #define IM_ERROR_NONE 0 + + void coreinitExport_IMIsAPDEnabledBySysSettings(PPCInterpreter_t* hCPU) + { + debug_printf("IMIsAPDEnabledBySysSettings(0x%08x)\n", hCPU->gpr[3]); + ppcDefineParamTypePtr(isAPDEnabled, uint32be, 0); + *isAPDEnabled = 0; + osLib_returnFromFunction(hCPU, IM_ERROR_NONE); + } + + void coreinitExport_IMGetTimeBeforeAPD(PPCInterpreter_t* hCPU) + { + // parameters: + // r3 uint32* returns the remaining number of seconds until auto-shutdown + memory_writeU32(hCPU->gpr[3], 60 * 30); // 30 minutes + osLib_returnFromFunction(hCPU, IM_ERROR_NONE); + } + + void coreinitExport_IMGetTimeBeforeDimming(PPCInterpreter_t* hCPU) + { + // parameters: + // r3 uint32* returns the remaining number of seconds until dimming + memory_writeU32(hCPU->gpr[3], 60 * 30); // 30 minutes + osLib_returnFromFunction(hCPU, IM_ERROR_NONE); + } + + bool imDimIsEnabled = true; + + void coreinitExport_IMEnableDim(PPCInterpreter_t* hCPU) + { + imDimIsEnabled = true; + osLib_returnFromFunction(hCPU, IM_ERROR_NONE); + } + + void coreinitExport_IMIsDimEnabled(PPCInterpreter_t* hCPU) + { + // parameters: + // r3 uint32* returns the remaining number of seconds until auto-shutdown + memory_writeU32(hCPU->gpr[3], imDimIsEnabled ? 1 : 0); // enabled + osLib_returnFromFunction(hCPU, IM_ERROR_NONE); + } + + void coreinitExport_IMGetAPDPeriod(PPCInterpreter_t* hCPU) + { + forceLogDebug_printf("IMGetAPDPeriod(0x%08x)\n", hCPU->gpr[3]); + // parameters: + // r3 uint32* returns the number of seconds until auto-shutdown occurs + memory_writeU32(hCPU->gpr[3], 600); + osLib_returnFromFunction(hCPU, IM_ERROR_NONE); + } + + void coreinitExport_IM_GetParameter(PPCInterpreter_t* hCPU) + { + forceLogDebug_printf("IM_GetParameter()"); + + ppcDefineParamS32(imHandle, 0); // handle from IM_Open() + ppcDefineParamS32(uknR4, 1); + ppcDefineParamS32(parameterId, 2); + ppcDefineParamStructPtr(output, void, 3); + ppcDefineParamS32(uknR7, 4); + ppcDefineParamS32(uknR8, 5); + + if (parameterId == 0) + { + // inactive seconds + *(uint32be*)output = 600; + } + else + { + cemu_assert_unimplemented(); + } + + osLib_returnFromFunction(hCPU, IM_ERROR_NONE); + } + + void coreinitExport_IM_GetRuntimeParameter(PPCInterpreter_t* hCPU) + { + forceLogDebug_printf("IM_GetRuntimeParameter()"); + + ppcDefineParamS32(parameterId, 0); + ppcDefineParamStructPtr(output, void, 1); + + if (parameterId == 8) + { + // indicates if last session was ended due to auto-power-down + *(uint32be*)output = 0; + } + else + { + cemu_assert_unimplemented(); + } + + osLib_returnFromFunction(hCPU, IM_ERROR_NONE); + } + + void coreinitExport_IM_GetHomeButtonParams(PPCInterpreter_t* hCPU) + { + debug_printf("IM_GetHomeButtonParams(...)\n"); + ppcDefineParamS32(imObj, 0); + ppcDefineParamMPTR(ipcBuf, 1); + ppcDefineParamMPTR(paramOut, 2); + ppcDefineParamS32(uknR6, 3); + ppcDefineParamS32(uknR7, 4); + + // todo + // note: No idea what these values mean. But they were chosen so that the Browser (surf.rpx) does not OSPanic() + memory_writeU32(paramOut + 0x0, 0); + memory_writeU32(paramOut + 0x4, 0); + + // for scope.rpx (Download Manager) + //memory_writeU32(paramOut + 0x0, 1); + //memory_writeU32(paramOut + 0x4, 2); // some sort of index (starting at 1?) + + + osLib_returnFromFunction(hCPU, IM_ERROR_NONE); + } + + void InitializeIM() + { + osLib_addFunction("coreinit", "IMIsAPDEnabledBySysSettings", coreinitExport_IMIsAPDEnabledBySysSettings); + osLib_addFunction("coreinit", "IMGetTimeBeforeAPD", coreinitExport_IMGetTimeBeforeAPD); + osLib_addFunction("coreinit", "IMGetTimeBeforeDimming", coreinitExport_IMGetTimeBeforeDimming); + osLib_addFunction("coreinit", "IMEnableDim", coreinitExport_IMEnableDim); + osLib_addFunction("coreinit", "IMIsDimEnabled", coreinitExport_IMIsDimEnabled); + osLib_addFunction("coreinit", "IMGetAPDPeriod", coreinitExport_IMGetAPDPeriod); + osLib_addFunction("coreinit", "IM_GetHomeButtonParams", coreinitExport_IM_GetHomeButtonParams); + osLib_addFunction("coreinit", "IM_GetParameter", coreinitExport_IM_GetParameter); + osLib_addFunction("coreinit", "IM_GetRuntimeParameter", coreinitExport_IM_GetRuntimeParameter); + } + +}; \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_IM.h b/src/Cafe/OS/libs/coreinit/coreinit_IM.h new file mode 100644 index 00000000..8f19d5f0 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_IM.h @@ -0,0 +1,6 @@ +#pragma once + +namespace coreinit +{ + void InitializeIM(); +}; \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_IOS.cpp b/src/Cafe/OS/libs/coreinit/coreinit_IOS.cpp new file mode 100644 index 00000000..2815bf83 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_IOS.cpp @@ -0,0 +1,91 @@ +#include "Cafe/OS/libs/coreinit/coreinit_IOS.h" +#include "Cafe/IOSU/legacy/iosu_ioctl.h" + +// superseded by coreinit_IPC.cpp/h + +sint32 __depr__IOS_Open(char* path, uint32 mode) +{ + sint32 iosDevice = 0; + if (path == nullptr) + { + iosDevice = 0; + } + else + { + if (strcmp(path, IOS_PATH_ODM) == 0) + iosDevice = IOS_DEVICE_ODM; + else if (strcmp(path, IOS_PATH_SOCKET) == 0) + iosDevice = IOS_DEVICE_SOCKET; + else if (strcmp(path, IOS_PATH_ACT) == 0) + iosDevice = IOS_DEVICE_ACT; + else if (strcmp(path, IOS_PATH_FPD) == 0) + iosDevice = IOS_DEVICE_FPD; + else if (strcmp(path, IOS_PATH_ACP_MAIN) == 0) + iosDevice = IOS_DEVICE_ACP_MAIN; + else if (strcmp(path, IOS_PATH_MCP) == 0) + iosDevice = IOS_DEVICE_MCP; + else if (strcmp(path, IOS_PATH_BOSS) == 0) + iosDevice = IOS_DEVICE_BOSS; + else if (strcmp(path, IOS_PATH_NIM) == 0) + iosDevice = IOS_DEVICE_NIM; + else if (strcmp(path, IOS_PATH_IOSUHAX) == 0) + return -1; + else + iosDevice = IOS_DEVICE_UKN; + } + return iosDevice; +} + +sint32 __depr__IOS_Ioctl(uint32 fd, uint32 request, void* inBuffer, uint32 inSize, void* outBuffer, uint32 outSize) +{ + switch (fd) + { + case IOS_DEVICE_ODM: + { + // Home Menu uses ioctl cmd 5 on startup and then repeats cmd 4 every frame + if (request == 4) + { + // check drive state + debug_printf("checkDriveState()\n"); + *(uint32be*)outBuffer = 0xA; + } + else + { + debug_printf("odm unsupported ioctl %d\n", request); + } + break; + } + default: + { + // todo + forceLogDebug_printf("Unsupported Ioctl command"); + } + } + return 0; +} + +sint32 __depr__IOS_Ioctlv(uint32 fd, uint32 request, uint32 countIn, uint32 countOut, ioBufferVector_t* ioBufferVectors) +{ + StackAllocator<ioQueueEntry_t> _queueEntryBuf; + ioQueueEntry_t* queueEntry = _queueEntryBuf.GetPointer(); + + queueEntry->isIoctlv = true; + queueEntry->isAsync = false; + queueEntry->request = request; + queueEntry->countIn = countIn; + queueEntry->countOut = countOut; + queueEntry->bufferVectors = ioBufferVectors; + + queueEntry->ppcThread = nullptr; + queueEntry->returnValue = 0; + queueEntry->isCompleted = false; + + sint32 r = iosuIoctl_pushAndWait(fd, queueEntry); + + return r; +} + +sint32 __depr__IOS_Close(uint32 fd) +{ + return 0; +} diff --git a/src/Cafe/OS/libs/coreinit/coreinit_IOS.h b/src/Cafe/OS/libs/coreinit/coreinit_IOS.h new file mode 100644 index 00000000..4829ef6f --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_IOS.h @@ -0,0 +1,9 @@ +// IOS +typedef struct _ioBufferVector_t ioBufferVector_t; + +sint32 __depr__IOS_Open(char* path, uint32 mode); +sint32 __depr__IOS_Ioctl(uint32 fd, uint32 request, void* inBuffer, uint32 inSize, void* outBuffer, uint32 outSize); +sint32 __depr__IOS_Ioctlv(uint32 fd, uint32 request, uint32 countIn, uint32 countOut, ioBufferVector_t* ioBufferVectors); +sint32 __depr__IOS_Close(uint32 fd); + +// superseded by coreinit_IPC.h \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_IPC.cpp b/src/Cafe/OS/libs/coreinit/coreinit_IPC.cpp new file mode 100644 index 00000000..ef847f26 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_IPC.cpp @@ -0,0 +1,482 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/IOSU/kernel/iosu_kernel.h" +#include "Cafe/HW/Espresso/Const.h" +#include "Cafe/HW/Espresso/PPCCallback.h" +#include "coreinit_MessageQueue.h" +#include "coreinit_IPC.h" + +namespace coreinit +{ + static constexpr inline size_t IPC_NUM_RESOURCE_BUFFERS = 0x30; + + struct IPCResourceBuffer + { + IPCCommandBody commandBody; + uint8 bufferData[0x80 - 0x48]; + }; + + static_assert(sizeof(IPCCommandBody) == 0x48); + static_assert(sizeof(IPCResourceBuffer) == 0x80); + + struct IPCResourceBufferDescriptor + { + /* +0x00 */ uint32be IsAllocated; + /* +0x04 */ MEMPTR<OSMessageQueue> asyncMsgQueue; // optional, if set a message will be sent to this queue... + /* +0x08 */ MEMPTR<void> asyncResultFunc; // ...otherwise this is checked and delegated to the IPC threads. If false, only eventSynchronousIPC will be signaled. If true, a message will be sent to the per-core ipc queue + /* +0x0C */ MEMPTR<void> asyncResultUserParam; + /* +0x10 */ uint32 ukn10; + /* +0x14 */ MEMPTR<IPCResourceBuffer> resourcePtr; + /* +0x18 */ OSEvent eventSynchronousIPC; + }; + + static_assert(sizeof(IPCResourceBufferDescriptor) == 0x3C); + + struct IPCBufferFIFO + { + sint32be writeIndex; + sint32be readIndex; + sint32be numQueuedEntries; + sint32be mostQueuedEntries; + MEMPTR<IPCResourceBufferDescriptor> ringbufferArray[IPC_NUM_RESOURCE_BUFFERS]; + + void Init() + { + writeIndex = 0; + readIndex = -1; + numQueuedEntries = 0; + mostQueuedEntries = 0; + for (size_t i = 0; i < IPC_NUM_RESOURCE_BUFFERS; i++) + ringbufferArray[i] = nullptr; + } + + void Push(IPCResourceBufferDescriptor* descriptor) + { + cemu_assert(readIndex != writeIndex); // if equal, fifo is full (should not happen as there not more buffers than ringbuffer entries) + ringbufferArray[writeIndex] = descriptor; + if (readIndex < 0) + readIndex = writeIndex; + writeIndex = (writeIndex + 1) % IPC_NUM_RESOURCE_BUFFERS; + ++numQueuedEntries; + if (numQueuedEntries > mostQueuedEntries) + mostQueuedEntries = numQueuedEntries; + } + + IPCResourceBufferDescriptor* Pop() + { + if (numQueuedEntries == 0) + return nullptr; + IPCResourceBufferDescriptor* r = ringbufferArray[readIndex].GetPtr(); + --numQueuedEntries; + if (numQueuedEntries == 0) + readIndex = -1; + else + readIndex = (readIndex + 1) % IPC_NUM_RESOURCE_BUFFERS; + return r; + } + }; + + struct IPCDriverCOSKernelCommunicationArea + { + uint32be numAvailableResponses; + MEMPTR<IPCCommandBody> responseArray[11]; + }; + + static_assert(sizeof(IPCDriverCOSKernelCommunicationArea) == 0x30); + + struct alignas(64) IPCDriver + { + betype<IPCDriverState> state; + uint32 ukn04; + uint32 coreIndex; + uint32 writeIndexCmd020; + MEMPTR<IPCResourceBuffer> resourceBuffers; + + /* 0x16C */ IPCBufferFIFO fifoFreeBuffers; + /* 0x23C */ IPCBufferFIFO fifoBuffersInFlight; + + /* 0x334 */ uint32 resourceBuffersInitialized; + /* 0x338 */ IPCDriverCOSKernelCommunicationArea kernelSharedArea; // this is passed to system call 0x1E00 (IPCOpen) + + /* 0x3FC */ IPCResourceBufferDescriptor resBufferDescriptor[IPC_NUM_RESOURCE_BUFFERS]; + }; + + //static_assert(sizeof(IPCDriverInstance) == 0x1740); + + SysAllocator<IPCResourceBuffer, IPC_NUM_RESOURCE_BUFFERS * Espresso::CORE_COUNT, 0x40> s_ipcResourceBuffers; + SysAllocator<IPCDriver, Espresso::CORE_COUNT, 0x40> s_ipcDriver; + + IPCDriver& IPCDriver_GetByCore(uint32 coreIndex) + { + cemu_assert_debug(coreIndex >= 0 && coreIndex < (uint32)Espresso::CORE_COUNT); + return s_ipcDriver[coreIndex]; + } + + void IPCDriver_InitForCore(uint32 coreIndex) + { + IPCDriver& ipcDriver = IPCDriver_GetByCore(coreIndex); + ipcDriver.coreIndex = coreIndex; + ipcDriver.state = IPCDriverState::INITIALIZED; + ipcDriver.resourceBuffers = s_ipcResourceBuffers.GetPtr() + IPC_NUM_RESOURCE_BUFFERS * coreIndex; + ipcDriver.resourceBuffersInitialized = 0; + // setup resource descriptors + for (size_t i = 0; i < IPC_NUM_RESOURCE_BUFFERS; i++) + { + ipcDriver.resBufferDescriptor[i].resourcePtr = ipcDriver.resourceBuffers.GetPtr() + i; + ipcDriver.resBufferDescriptor[i].asyncResultFunc = nullptr; + ipcDriver.resBufferDescriptor[i].asyncResultUserParam = nullptr; + } + ipcDriver.resourceBuffersInitialized = 1; + // setup resource buffer FIFOs + ipcDriver.fifoFreeBuffers.Init(); + ipcDriver.fifoBuffersInFlight.Init(); + for (size_t i = 0; i < IPC_NUM_RESOURCE_BUFFERS; i++) + ipcDriver.fifoFreeBuffers.Push(ipcDriver.resBufferDescriptor + i); + } + + IPCResourceBufferDescriptor* IPCDriver_AllocateResource(IPCDriver* ipcDriver, IOSDevHandle devHandle, IPCCommandId cmdId, OSMessageQueue* asyncMessageQueue, MEMPTR<void> asyncResultFunc, MEMPTR<void> asyncResultUserParam) + { + cemu_assert_debug(ipcDriver->coreIndex == OSGetCoreId()); + IPCResourceBufferDescriptor* descriptor = nullptr; + while (true) + { + descriptor = ipcDriver->fifoFreeBuffers.Pop(); + if (!descriptor) + { + cemuLog_log(LogType::Force, "IPCDriver: Exceeded free resources"); + OSYieldThread(); + cemu_assert_unimplemented(); // we should wait for an event instead of busylooping + } + else + break; + } + cemu_assert_debug(descriptor >= ipcDriver->resBufferDescriptor && descriptor < ipcDriver->resBufferDescriptor + IPC_NUM_RESOURCE_BUFFERS); + cemu_assert_debug(descriptor->resourcePtr.GetPtr() >= ipcDriver->resourceBuffers.GetPtr() && descriptor->resourcePtr.GetPtr() < (ipcDriver->resourceBuffers.GetPtr() + IPC_NUM_RESOURCE_BUFFERS)); + IPCResourceBuffer* res = descriptor->resourcePtr; + IPCCommandBody& cmdBody = res->commandBody; + + descriptor->IsAllocated = 1; + descriptor->asyncMsgQueue = asyncMessageQueue; + descriptor->asyncResultFunc = asyncResultFunc; + descriptor->asyncResultUserParam = asyncResultUserParam; + + cmdBody.cmdId = cmdId; + cmdBody.ukn0C = 0; + cmdBody.ukn14 = 0; + cmdBody.result = 0; + cmdBody.devHandle = devHandle; + + return descriptor; + } + + void IPCDriver_ReleaseResource(IPCDriver* ipcDriver, IPCResourceBufferDescriptor* requestDescriptor) + { + requestDescriptor->IsAllocated = 0; + ipcDriver->fifoFreeBuffers.Push(requestDescriptor); + } + + /* IPC threads */ + + SysAllocator<OSThread_t, Espresso::CORE_COUNT> gIPCThread; + SysAllocator<uint8, 0x4000 * Espresso::CORE_COUNT> _gIPCThreadStack; + SysAllocator<uint8, 0x18 * Espresso::CORE_COUNT> _gIPCThreadNameStorage; + SysAllocator<OSMessageQueue, Espresso::CORE_COUNT> gIPCThreadMsgQueue; + SysAllocator<OSMessage, Espresso::CORE_COUNT * IPC_NUM_RESOURCE_BUFFERS> _gIPCThreadSemaphoreStorage; + + // handler thread for asynchronous callbacks for IPC responses + void __IPCDriverThreadFunc(PPCInterpreter_t* hCPU) + { + uint32 coreIndex = OSGetCoreId(); + while (true) + { + OSMessage msg; + OSReceiveMessage(gIPCThreadMsgQueue.GetPtr() + coreIndex, &msg, OS_MESSAGE_BLOCK); + cemu_assert(msg.data2 == 1); // type must be callback + MEMPTR<void> cbFunc{ msg.message }; + cemu_assert(cbFunc != nullptr); + PPCCoreCallback(cbFunc.GetPtr(), (uint32)msg.data0, (uint32)msg.data1); + } + osLib_returnFromFunction(hCPU, 0); + } + + void IPCDriver_InitIPCThread(uint32 coreIndex) + { + // create a thread with 0x4000 stack space + // and a message queue large enough to hold the maximum number of commands (IPC_NUM_RESOURCE_BUFFERS) + OSInitMessageQueue(gIPCThreadMsgQueue.GetPtr() + coreIndex, _gIPCThreadSemaphoreStorage.GetPtr() + coreIndex * IPC_NUM_RESOURCE_BUFFERS, IPC_NUM_RESOURCE_BUFFERS); + OSThread_t* ipcThread = gIPCThread.GetPtr() + coreIndex; + OSCreateThreadType(ipcThread, PPCInterpreter_makeCallableExportDepr(__IPCDriverThreadFunc), 0, nullptr, _gIPCThreadStack.GetPtr() + 0x4000 * coreIndex + 0x4000, 0x4000, 15, (1 << coreIndex), OSThread_t::THREAD_TYPE::TYPE_DRIVER); + sprintf((char*)_gIPCThreadNameStorage.GetPtr()+coreIndex*0x18, "{SYS IPC Core %d}", coreIndex); + OSSetThreadName(ipcThread, (char*)_gIPCThreadNameStorage.GetPtr() + coreIndex * 0x18); + OSResumeThread(ipcThread); + } + + /* coreinit IOS_* API */ + + void _IPCDriver_SubmitCmdAllQueued(IPCDriver& ipcDriver) + { + // on COS, submitted commands first go to the COS kernel via syscall 0x2000, where they are processed, copied and queued again + // we skip all of this and just pass our IPC commands directly to the IOSU kernel handler HLE function + // important: IOSU needs to know which PPC core sent the command, so that it can also notify the same core about the result + ipcDriver.state = IPCDriverState::SUBMITTING; + while (true) + { + IPCResourceBufferDescriptor* res = ipcDriver.fifoBuffersInFlight.Pop(); + if (!res) + break; + // resolve pointers + switch (res->resourcePtr->commandBody.cmdId) + { + case IPCCommandId::IOS_OPEN: + res->resourcePtr->commandBody.args[0] = res->resourcePtr->commandBody.ppcVirt0.GetMPTR(); + break; + case IPCCommandId::IOS_CLOSE: + break; + case IPCCommandId::IOS_IOCTL: + res->resourcePtr->commandBody.args[1] = res->resourcePtr->commandBody.ppcVirt0.GetMPTR(); + res->resourcePtr->commandBody.args[3] = res->resourcePtr->commandBody.ppcVirt1.GetMPTR(); + break; + case IPCCommandId::IOS_IOCTLV: + res->resourcePtr->commandBody.args[3] = res->resourcePtr->commandBody.ppcVirt0.GetMPTR(); + break; + default: + cemu_assert_unimplemented(); + break; + } + iosu::kernel::IPCSubmitFromCOS(ipcDriver.coreIndex, &res->resourcePtr->commandBody); + } + ipcDriver.state = IPCDriverState::READY; + } + + void _IPCDriver_SubmitCmd(IPCDriver& ipcDriver, IPCResourceBufferDescriptor* requestDescriptor) + { + if (requestDescriptor->asyncResultFunc == nullptr) + OSInitEvent(&requestDescriptor->eventSynchronousIPC, OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, OSEvent::EVENT_MODE::MODE_AUTO); + ipcDriver.fifoBuffersInFlight.Push(requestDescriptor); + cemu_assert_debug(ipcDriver.state == IPCDriverState::READY || ipcDriver.state == IPCDriverState::INITIALIZED); + _IPCDriver_SubmitCmdAllQueued(ipcDriver); + } + + uint32 _IPCDriver_WaitForResultAndRelease(IPCDriver& ipcDriver, IPCResourceBufferDescriptor* requestDescriptor) + { + OSWaitEvent(&requestDescriptor->eventSynchronousIPC); + uint32 r = requestDescriptor->resourcePtr->commandBody.result; + IPCDriver_ReleaseResource(&ipcDriver, requestDescriptor); + return r; + } + + void IPCDriver_HandleResponse(IPCDriver& ipcDriver, IPCCommandBody* res, uint32 ppcCoreIndex) + { + size_t index = (IPCResourceBuffer*)res - ipcDriver.resourceBuffers.GetPtr(); + cemu_assert(index < IPC_NUM_RESOURCE_BUFFERS); + IPCResourceBufferDescriptor* descriptor = ipcDriver.resBufferDescriptor + index; + cemu_assert(descriptor->IsAllocated != 0); + if (descriptor->asyncMsgQueue != nullptr) + { + OSMessage msg; + msg.message = 0; + msg.data0 = res->result; + msg.data1 = descriptor->asyncResultUserParam.GetMPTR(); + msg.data2 = 0; + sint32 r = OSSendMessage(descriptor->asyncMsgQueue.GetPtr(), &msg, 0); + cemu_assert(r != 0); + IPCDriver_ReleaseResource(&ipcDriver, descriptor); + return; + } + if (descriptor->asyncResultFunc != nullptr) + { + OSMessage msg; + msg.message = descriptor->asyncResultFunc.GetMPTR(); + msg.data0 = res->result; + msg.data1 = descriptor->asyncResultUserParam.GetMPTR(); + msg.data2 = 1; + sint32 r = OSSendMessage(gIPCThreadMsgQueue.GetPtr() + ppcCoreIndex, &msg, 0); + cemu_assert(r != 0); + IPCDriver_ReleaseResource(&ipcDriver, descriptor); + return; + } + // signal event for synchronous IPC + OSSignalEvent(&descriptor->eventSynchronousIPC); + } + + // handles responses queued in shared region + void IPCDriver_KernelCallback(IPCDriver& ipcDriver) + { + cemu_assert_debug(ipcDriver.kernelSharedArea.numAvailableResponses != 0); + for (uint32 i = 0; i < ipcDriver.kernelSharedArea.numAvailableResponses; i++) + IPCDriver_HandleResponse(ipcDriver, ipcDriver.kernelSharedArea.responseArray[i], ipcDriver.coreIndex); + ipcDriver.kernelSharedArea.numAvailableResponses = 0; + } + + // called by our HLE'd IOSU directly + void IPCDriver_NotifyResponses(uint32 ppcCoreIndex, IPCCommandBody** responseArray, uint32 numResponses) + { + cemu_assert(numResponses < 11); + IPCDriver& ipcDriver = IPCDriver_GetByCore(ppcCoreIndex); + ipcDriver.kernelSharedArea.numAvailableResponses = numResponses; + for (uint32 i = 0; i < numResponses; i++) + ipcDriver.kernelSharedArea.responseArray[i] = responseArray[i]; + IPCDriver_KernelCallback(ipcDriver); + } + + void _IPCDriver_SetupCmd_IOSOpen(IPCDriver& ipcDriver, IPCResourceBufferDescriptor* requestDescriptor, const char* devicePath, uint32 flags) + { + // store the path in the buffer after the command body + IPCResourceBuffer* resBuffer = requestDescriptor->resourcePtr; + IPCCommandBody& cmdBody = resBuffer->commandBody; + + uint8* buffer = resBuffer->bufferData; + size_t pathLen = strlen(devicePath); + if (pathLen > 31) + { + cemuLog_log(LogType::Force, "IOS_Open(): Device path must not exceed 31 characters"); + cemu_assert_error(); + } + memcpy(buffer, devicePath, pathLen + 1); + + cmdBody.ppcVirt0 = MEMPTR<void>(buffer).GetMPTR(); + cmdBody.args[0] = 0; + cmdBody.args[1] = (uint32)(pathLen + 1); + cmdBody.args[2] = flags; + } + + IOS_ERROR _IPCDriver_SetupCmd_IOSIoctl(IPCDriver& ipcDriver, IPCResourceBufferDescriptor* requestDescriptor, uint32 requestId, void* ptrIn, uint32 sizeIn, void* ptrOut, uint32 sizeOut) + { + IPCCommandBody& cmdBody = requestDescriptor->resourcePtr->commandBody; + cmdBody.args[0] = requestId; + cmdBody.args[1] = MPTR_NULL; // set to ppcVirt0 later + cmdBody.args[2] = sizeIn; + cmdBody.args[3] = MPTR_NULL; // set to ppcVirt1 later + cmdBody.args[4] = sizeOut; + cmdBody.ppcVirt0 = MEMPTR<void>(ptrIn).GetMPTR(); + cmdBody.ppcVirt1 = MEMPTR<void>(ptrOut).GetMPTR(); + return IOS_ERROR_OK; + } + + IOS_ERROR _IPCDriver_SetupCmd_IOSIoctlv(IPCDriver& ipcDriver, IPCResourceBufferDescriptor* requestDescriptor, uint32 requestId, uint32 numIn, uint32 numOut, IPCIoctlVector* vec) + { + IPCCommandBody& cmdBody = requestDescriptor->resourcePtr->commandBody; + // verify input and output vectors + IPCIoctlVector* vecIn = vec; + IPCIoctlVector* vecOut = vec + numIn; + for (uint32 i = 0; i < numIn; i++) + { + if (vecIn[i].baseVirt == nullptr && vecIn[i].size != 0) + return IOS_ERROR_INVALID_ARG; + vecIn[i].basePhys = vecIn[i].baseVirt; + vecIn[i].baseVirt = nullptr; + } + for (uint32 i = 0; i < numOut; i++) + { + if (vecOut[i].baseVirt == nullptr && vecOut[i].size != 0) + return IOS_ERROR_INVALID_ARG; + vecOut[i].basePhys = vecOut[i].baseVirt; + vecOut[i].baseVirt = nullptr; + } + // set args + cmdBody.ppcVirt0 = MEMPTR<void>(vec).GetMPTR(); + cmdBody.args[0] = requestId; + cmdBody.args[1] = numIn; + cmdBody.args[2] = numOut; + cmdBody.args[3] = 0; // set to ppcVirt0 later + return IOS_ERROR_OK; + } + + IOSDevHandle IOS_Open(const char* devicePath, uint32 flags) + { + IPCDriver& ipcDriver = IPCDriver_GetByCore(OSGetCoreId()); + IPCResourceBufferDescriptor* ipcDescriptor = IPCDriver_AllocateResource(&ipcDriver, 0, IPCCommandId::IOS_OPEN, nullptr, nullptr, nullptr); + _IPCDriver_SetupCmd_IOSOpen(ipcDriver, ipcDescriptor, devicePath, flags); + _IPCDriver_SubmitCmd(ipcDriver, ipcDescriptor); + uint32 r = _IPCDriver_WaitForResultAndRelease(ipcDriver, ipcDescriptor); + return r; + } + + IOS_ERROR IOS_Close(IOSDevHandle devHandle) + { + IPCDriver& ipcDriver = IPCDriver_GetByCore(OSGetCoreId()); + IPCResourceBufferDescriptor* ipcDescriptor = IPCDriver_AllocateResource(&ipcDriver, devHandle, IPCCommandId::IOS_CLOSE, nullptr, nullptr, nullptr); + _IPCDriver_SubmitCmd(ipcDriver, ipcDescriptor); + IOS_ERROR r = (IOS_ERROR)_IPCDriver_WaitForResultAndRelease(ipcDriver, ipcDescriptor); + return r; + } + + IOS_ERROR IOS_Ioctl(IOSDevHandle devHandle, uint32 requestId, void* ptrIn, uint32 sizeIn, void* ptrOut, uint32 sizeOut) + { + IPCDriver& ipcDriver = IPCDriver_GetByCore(OSGetCoreId()); + IPCResourceBufferDescriptor* ipcDescriptor = IPCDriver_AllocateResource(&ipcDriver, devHandle, IPCCommandId::IOS_IOCTL, nullptr, nullptr, nullptr); + IOS_ERROR r = _IPCDriver_SetupCmd_IOSIoctl(ipcDriver, ipcDescriptor, requestId, ptrIn, sizeIn, ptrOut, sizeOut); + if (r != IOS_ERROR_OK) + { + cemuLog_log(LogType::Force, "IOS_Ioctl failed due to bad parameters"); + IPCDriver_ReleaseResource(&ipcDriver, ipcDescriptor); + return r; + } + _IPCDriver_SubmitCmd(ipcDriver, ipcDescriptor); + r = (IOS_ERROR)_IPCDriver_WaitForResultAndRelease(ipcDriver, ipcDescriptor); + return r; + } + + IOS_ERROR IOS_IoctlAsync(IOSDevHandle devHandle, uint32 requestId, void* ptrIn, uint32 sizeIn, void* ptrOut, uint32 sizeOut, MEMPTR<void> asyncResultFunc, MEMPTR<void> asyncResultUserParam) + { + IPCDriver& ipcDriver = IPCDriver_GetByCore(OSGetCoreId()); + IPCResourceBufferDescriptor* ipcDescriptor = IPCDriver_AllocateResource(&ipcDriver, devHandle, IPCCommandId::IOS_IOCTL, nullptr, asyncResultFunc, asyncResultUserParam); + IOS_ERROR r = _IPCDriver_SetupCmd_IOSIoctl(ipcDriver, ipcDescriptor, requestId, ptrIn, sizeIn, ptrOut, sizeOut); + if (r != IOS_ERROR_OK) + { + cemuLog_log(LogType::Force, "IOS_Ioctl failed due to bad parameters"); + IPCDriver_ReleaseResource(&ipcDriver, ipcDescriptor); + return r; + } + _IPCDriver_SubmitCmd(ipcDriver, ipcDescriptor); + return r; + } + + IOS_ERROR IOS_Ioctlv(IOSDevHandle devHandle, uint32 requestId, uint32 numIn, uint32 numOut, IPCIoctlVector* vec) + { + IPCDriver& ipcDriver = IPCDriver_GetByCore(OSGetCoreId()); + IPCResourceBufferDescriptor* ipcDescriptor = IPCDriver_AllocateResource(&ipcDriver, devHandle, IPCCommandId::IOS_IOCTLV, nullptr, nullptr, nullptr); + IOS_ERROR r = _IPCDriver_SetupCmd_IOSIoctlv(ipcDriver, ipcDescriptor, requestId, numIn, numOut, vec); + if (r != IOS_ERROR_OK) + { + cemuLog_log(LogType::Force, "IOS_Ioctlv failed due to bad parameters"); + IPCDriver_ReleaseResource(&ipcDriver, ipcDescriptor); + return r; + } + _IPCDriver_SubmitCmd(ipcDriver, ipcDescriptor); + r = (IOS_ERROR)_IPCDriver_WaitForResultAndRelease(ipcDriver, ipcDescriptor); + return r; + } + + IOS_ERROR IOS_IoctlvAsync(IOSDevHandle devHandle, uint32 requestId, uint32 numIn, uint32 numOut, IPCIoctlVector* vec, MEMPTR<void> asyncResultFunc, MEMPTR<void> asyncResultUserParam) + { + IPCDriver& ipcDriver = IPCDriver_GetByCore(OSGetCoreId()); + IPCResourceBufferDescriptor* ipcDescriptor = IPCDriver_AllocateResource(&ipcDriver, devHandle, IPCCommandId::IOS_IOCTLV, nullptr, asyncResultFunc, asyncResultUserParam); + IOS_ERROR r = _IPCDriver_SetupCmd_IOSIoctlv(ipcDriver, ipcDescriptor, requestId, numIn, numOut, vec); + if (r != IOS_ERROR_OK) + { + cemuLog_log(LogType::Force, "IOS_Ioctlv failed due to bad parameters"); + IPCDriver_ReleaseResource(&ipcDriver, ipcDescriptor); + return r; + } + _IPCDriver_SubmitCmd(ipcDriver, ipcDescriptor); + return r; + } + + void InitializeIPC() + { + for (uint32 i = 0; i < Espresso::CORE_COUNT; i++) + { + IPCDriver_InitForCore(i); + IPCDriver_InitIPCThread(i); + } + + // register API + cafeExportRegister("coreinit", IOS_Open, LogType::PPC_IPC); + cafeExportRegister("coreinit", IOS_Close, LogType::PPC_IPC); + cafeExportRegister("coreinit", IOS_Ioctl, LogType::PPC_IPC); + cafeExportRegister("coreinit", IOS_IoctlAsync, LogType::PPC_IPC); + cafeExportRegister("coreinit", IOS_Ioctlv, LogType::PPC_IPC); + cafeExportRegister("coreinit", IOS_IoctlvAsync, LogType::PPC_IPC); + } + +}; diff --git a/src/Cafe/OS/libs/coreinit/coreinit_IPC.h b/src/Cafe/OS/libs/coreinit/coreinit_IPC.h new file mode 100644 index 00000000..362c4725 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_IPC.h @@ -0,0 +1,16 @@ +#include "Cafe/IOSU/iosu_ipc_common.h" +#include "Cafe/IOSU/iosu_types_common.h" + +namespace coreinit +{ + void IPCDriver_NotifyResponses(uint32 ppcCoreIndex, IPCCommandBody** responseArray, uint32 numResponses); + + IOSDevHandle IOS_Open(const char* devicePath, uint32 flags); + IOS_ERROR IOS_Close(IOSDevHandle devHandle); + IOS_ERROR IOS_Ioctl(IOSDevHandle devHandle, uint32 requestId, void* ptrIn, uint32 sizeIn, void* ptrOut, uint32 sizeOut); + IOS_ERROR IOS_IoctlAsync(IOSDevHandle devHandle, uint32 requestId, void* ptrIn, uint32 sizeIn, void* ptrOut, uint32 sizeOut, MEMPTR<void> asyncResultFunc, MEMPTR<void> asyncResultUserParam); + IOS_ERROR IOS_Ioctlv(IOSDevHandle devHandle, uint32 requestId, uint32 numIn, uint32 numOut, IPCIoctlVector* vec); + IOS_ERROR IOS_IoctlvAsync(IOSDevHandle devHandle, uint32 requestId, uint32 numIn, uint32 numOut, IPCIoctlVector* vec, MEMPTR<void> asyncResultFunc, MEMPTR<void> asyncResultUserParam); + + void InitializeIPC(); +}; \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_IPCBuf.cpp b/src/Cafe/OS/libs/coreinit/coreinit_IPCBuf.cpp new file mode 100644 index 00000000..33f3e7c2 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_IPCBuf.cpp @@ -0,0 +1,233 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/libs/coreinit/coreinit_Thread.h" + +namespace coreinit +{ + + struct FIFOEntry_t + { + MEMPTR<uint8> p; + }; + + struct IPCFifo_t + { + uint32be writeIndex; + uint32be readIndex; + uint32be availableEntries; // number of available entries + uint32be entryCount; + MEMPTR<FIFOEntry_t> entryArray; + }; + + struct IPCBufPool_t + { + /* +0x00 */ uint32be magic; + /* +0x04 */ MEMPTR<void> fullBufferPtr; + /* +0x08 */ uint32be fullBufferSize; + /* +0x0C */ uint32be uknFromParamR7; // boolean? + /* +0x10 */ uint32be ukn10; // set to zero on init + /* +0x14 */ uint32be entrySize1; + /* +0x18 */ uint32be entrySize2; // set to same value as entrySize1 + /* +0x1C */ uint32be entryCount; // actual number of used entries + /* +0x20 */ MEMPTR<uint8> entryStartPtr; + /* +0x24 */ uint32be entryCountMul4; + /* +0x28 */ IPCFifo_t fifo; + /* +0x3C */ coreinit::OSMutex mutex; + // full size is 0x68 + }; + + void FIFOInit(IPCFifo_t* fifo, uint32 entryCount, void* entryArray) + { + fifo->entryCount = entryCount; + fifo->entryArray = (FIFOEntry_t*)entryArray; + fifo->writeIndex = 0; + fifo->readIndex = -1; + fifo->availableEntries = 0; + } + + sint32 FIFOPush(IPCFifo_t* fifo, void* entry) + { + if (fifo->readIndex == fifo->writeIndex) + { + assert_dbg(); + return -8; + } + fifo->entryArray[(uint32)fifo->writeIndex].p = (uint8*)entry; + if ((sint32)fifo->readIndex < 0) + { + // set readIndex to valid value when fifo was empty + fifo->readIndex = fifo->writeIndex; + } + fifo->availableEntries = (uint32)fifo->availableEntries + 1; + fifo->writeIndex = ((uint32)fifo->writeIndex + 1) % (uint32)fifo->entryCount; + return 0; + } + + sint32 FIFOPop(IPCFifo_t* fifo, uint8** entry) + { + *entry = nullptr; + if ((sint32)fifo->readIndex < 0) + return -7; + fifo->availableEntries = (uint32)fifo->availableEntries - 1; + *entry = fifo->entryArray[(uint32)fifo->readIndex].p.GetPtr(); + fifo->readIndex = ((uint32)fifo->readIndex + 1) % (uint32)fifo->entryCount; + + if (fifo->availableEntries == (uint32be)0) + { + fifo->readIndex = -1; + } + return 0; + } + + void* getUpwardsAlignedAddr(void* addr, uint32 alignment) + { + uint64 v = ((uint64)addr + alignment - 1) & ~(uint64)(alignment - 1); + return (uint8*)v; + } + + void* getDownwardsAlignedAddr(void* addr, uint32 alignment) + { + uint64 v = ((uint64)addr) & ~(uint64)(alignment - 1); + return (uint8*)v; + } + + static_assert(sizeof(IPCBufPool_t) == 0x68); + + IPCBufPool_t* IPCBufPoolCreate(uint8* bufferArea, uint32 bufferSize, uint32 entrySize, uint32be* entryCountOutput, uint32 uknR7) + { + memset(bufferArea, 0, bufferSize); + IPCBufPool_t* ipcBufPool = (IPCBufPool_t*)getUpwardsAlignedAddr(bufferArea, 4); + uint8* alignedEnd = (uint8*)getDownwardsAlignedAddr(bufferArea + bufferSize, 4); + uint8* dataStart = (uint8*)getUpwardsAlignedAddr(ipcBufPool + 1, 0x4); + + *entryCountOutput = 0; + // todo: Validate parameters + + OSInitMutexEx(&ipcBufPool->mutex, NULL); + + ipcBufPool->fullBufferPtr = bufferArea; + ipcBufPool->fullBufferSize = bufferSize; + ipcBufPool->uknFromParamR7 = uknR7; + + uint32 paddedEntrySize = (entrySize + 0x3F) & ~0x3F; + + ipcBufPool->ukn10 = 0; + ipcBufPool->magic = 0xBADF00D; + + ipcBufPool->entrySize1 = paddedEntrySize; + ipcBufPool->entrySize2 = paddedEntrySize; + + uint32 remainingSize = (uint32)((bufferArea + bufferSize) - dataStart); + uint32 entryCount = remainingSize / paddedEntrySize; + if (entryCount <= 1) + assert_dbg(); + + ipcBufPool->entryCountMul4 = entryCount * 4; + if ((uint32)ipcBufPool->entryCountMul4 >= paddedEntrySize) + { + // special handling required (need to adjust entry count?) + assert_dbg(); + } + else + { + entryCount--; // remove one entry to make room for entry pointer array + ipcBufPool->entryCount = entryCount; + *entryCountOutput = entryCount; + ipcBufPool->entryStartPtr = (dataStart + (uint32)ipcBufPool->entryCountMul4); + FIFOInit(&ipcBufPool->fifo, (uint32)ipcBufPool->entryCount, dataStart); + } + // add all entries to the fifo + for (sint32 i = 0; i < (sint32)ipcBufPool->entryCount; i++) + { + uint8* entry = ipcBufPool->entryStartPtr.GetPtr() + i * (uint32)ipcBufPool->entrySize2; + if (FIFOPush(&ipcBufPool->fifo, entry) != 0) + return nullptr; + } + return ipcBufPool; + } + + uint8* IPCBufPoolAllocate(IPCBufPool_t* ipcBufPool, uint32 size) + { + uint8* entry = nullptr; + OSLockMutex(&ipcBufPool->mutex); + if (ipcBufPool->magic == (uint32be)0xBADF00D && size <= (uint32)ipcBufPool->entrySize1) + { + FIFOPop(&ipcBufPool->fifo, &entry); + } + else + { + assert_dbg(); + } + OSUnlockMutex(&ipcBufPool->mutex); + return entry; + } + + sint32 IPCBufPoolFree(IPCBufPool_t* ipcBufPool, uint8* entry) + { + OSLockMutex(&ipcBufPool->mutex); + sint32 res = 0; + if (ipcBufPool->magic == (uint32be)0xBADF00D) + { + // check if entry is actually part of the pool + uint32 offset = (uint32)(entry - (uint8*)ipcBufPool->entryStartPtr.GetPtr()); + if ((offset % (uint32)ipcBufPool->entrySize2) != 0) + assert_dbg(); + uint32 index = offset / (uint32)ipcBufPool->entrySize2; + if ((index >= (uint32)ipcBufPool->entryCount)) + assert_dbg(); + FIFOPush(&ipcBufPool->fifo, entry); + } + else + { + assert_dbg(); + res = -4; + } + OSUnlockMutex(&ipcBufPool->mutex); + return res; + } + + void coreinitExport_IPCBufPoolCreate(PPCInterpreter_t* hCPU) + { + ppcDefineParamTypePtr(bufferArea, uint8, 0); + ppcDefineParamU32(bufferSize, 1); + ppcDefineParamU32(entrySize, 2); + ppcDefineParamU32BEPtr(entryCountOutput, 3); + ppcDefineParamU32(uknR7, 4); + IPCBufPool_t* ipcBufPool = IPCBufPoolCreate(bufferArea, bufferSize, entrySize, entryCountOutput, uknR7); + osLib_returnFromFunction(hCPU, memory_getVirtualOffsetFromPointer(ipcBufPool)); + return; + // example dump of IPC buffer (at 0x1011FF40): + // 0000 0B AD F0 0D 10 11 FF 40 00 00 53 01 00 00 00 01 00 00 00 00 00 00 02 C0 00 00 02 C0 00 00 00 1D + // 0020 10 12 00 40 00 00 00 78 00 00 00 00 00 00 00 00 00 00 00 1D 00 00 00 1D 10 11 FF A8 6D 55 74 58 + // 0040 10 00 87 2C 00 00 00 00 00 00 00 00 00 00 00 00 10 11 FF 7C 00 00 00 00 00 00 00 00 00 00 00 00 + // 0060 00 00 00 00 00 00 00 00 10 12 00 40 10 12 03 00 10 12 05 C0 10 12 08 80 10 12 0B 40 10 12 0E 00 + // 0080 10 12 10 C0 10 12 13 80 10 12 16 40 10 12 19 00 10 12 1B C0 10 12 1E 80 10 12 21 40 10 12 24 00 + // 00A0 10 12 26 C0 10 12 29 80 10 12 2C 40 10 12 2F 00 10 12 31 C0 10 12 34 80 10 12 37 40 10 12 3A 00 + // 00C0 10 12 3C C0 10 12 3F 80 10 12 42 40 10 12 45 00 10 12 47 C0 10 12 4A 80 10 12 4D 40 00 00 00 00 + // 00E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + } + + void coreinitExport_IPCBufPoolAllocate(PPCInterpreter_t* hCPU) + { + debug_printf("IPCBufPoolAllocate(0x%08x,0x%x)\n", hCPU->gpr[3], hCPU->gpr[4]); + ppcDefineParamStructPtr(ipcBufPool, IPCBufPool_t, 0); + ppcDefineParamU32(size, 1); + uint8* entry = IPCBufPoolAllocate(ipcBufPool, size); + osLib_returnFromFunction(hCPU, memory_getVirtualOffsetFromPointer(entry)); + } + + void coreinitExport_IPCBufPoolFree(PPCInterpreter_t* hCPU) + { + debug_printf("IPCBufPoolFree(0x%08x,0x%08x)\n", hCPU->gpr[3], hCPU->gpr[4]); + ppcDefineParamStructPtr(ipcBufPool, IPCBufPool_t, 0); + ppcDefineParamTypePtr(entry, uint8, 1); + sint32 res = IPCBufPoolFree(ipcBufPool, entry); + osLib_returnFromFunction(hCPU, res); + } + + void InitializeIPCBuf() + { + osLib_addFunction("coreinit", "IPCBufPoolCreate", coreinitExport_IPCBufPoolCreate); + osLib_addFunction("coreinit", "IPCBufPoolAllocate", coreinitExport_IPCBufPoolAllocate); + osLib_addFunction("coreinit", "IPCBufPoolFree", coreinitExport_IPCBufPoolFree); + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_IPCBuf.h b/src/Cafe/OS/libs/coreinit/coreinit_IPCBuf.h new file mode 100644 index 00000000..004fb178 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_IPCBuf.h @@ -0,0 +1,6 @@ +#pragma once + +namespace coreinit +{ + void InitializeIPCBuf(); +} diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Init.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Init.cpp new file mode 100644 index 00000000..4cedc7fb --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_Init.cpp @@ -0,0 +1,198 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/HW/Espresso/PPCCallback.h" +#include "Cafe/OS/libs/coreinit/coreinit.h" +#include "Cafe/OS/RPL/rpl.h" +#include "Cafe/OS/libs/padscore/padscore.h" +#include "Cafe/OS/libs/coreinit/coreinit_SysHeap.h" +#include "Cafe/OS/libs/coreinit/coreinit_Alarm.h" +#include "Cafe/OS/libs/vpad/vpad.h" +#include "Cafe/OS/libs/coreinit/coreinit_GHS.h" +#include "Cafe/OS/libs/coreinit/coreinit_DynLoad.h" +#include "Cafe/OS/libs/coreinit/coreinit_MEM.h" +#include "Cafe/OS/libs/coreinit/coreinit_FG.h" +#include "Cafe/CafeSystem.h" + +extern MPTR _entryPoint; +extern RPLModule* applicationRPX; + +typedef struct +{ + MPTR argv[32]; + uint32be argc; + char argStorage[0x1000]; +}coreinitInit_t; + +coreinitInit_t* _coreinitInfo = nullptr; + +MPTR OSAllocFromSystem(uint32 size, uint32 alignment) +{ + return coreinit_allocFromSysArea(size, alignment); +} + +void OSFreeToSystem(MPTR mem) +{ + coreinit_freeToSysArea(mem); +} + +extern std::string _pathToExecutable; +sint32 argStorageIndex; + +void _AddArg(const char* arg, sint32 len) +{ + uint32 argc = _coreinitInfo->argc; + + char* argStorageStr = _coreinitInfo->argStorage + argStorageIndex; + memcpy(argStorageStr, arg, len); + argStorageStr[len] = '\0'; + argStorageIndex += (sint32)strlen(arg) + 1; + + _coreinitInfo->argv[argc] = _swapEndianU32(memory_getVirtualOffsetFromPointer(argStorageStr)); + _coreinitInfo->argc = argc+1; +} + +sint32 _GetArgLength(const char* arg) +{ + sint32 c = 0; + while (*arg) + { + if (*arg == ' ') + break; // end at whitespace + cemu_assert_debug(*arg != '\"' && *arg != '\''); // todo + arg++; + c++; + } + return c; +} + +void CafeInit() +{ + // extract executable filename + sint32 rpxPathStart = (sint32)_pathToExecutable.size() - 1; + if (rpxPathStart > 0) + { + while (rpxPathStart > 0 && _pathToExecutable[rpxPathStart-1] != '/') + rpxPathStart--; + } + else + { + rpxPathStart = 0; + } + + std::string_view rpxFileName = std::basic_string_view<char>(_pathToExecutable.data() + rpxPathStart, _pathToExecutable.data() + _pathToExecutable.size()); + + argStorageIndex = 0; + _coreinitInfo->argc = 0; + _AddArg(rpxFileName.data(), rpxFileName.size()); + strcpy((char*)_coreinitInfo->argStorage, std::string(rpxFileName).c_str()); + + std::string _argStr = CafeSystem::GetForegroundTitleArgStr(); + const char* argString = _argStr.c_str(); + // attach parameters from arg string + if (argString && argString[0] != '\0') + { + const char* t = strstr(argString, ".rpx"); + if (t) + { + t += 4; + while (*t) + { + // skip all whitespace + while (*t) + { + if (*t == ' ') + { + t++; + } + else + break; + } + // get length of arg + sint32 argLength = _GetArgLength(t); + if (argLength > 0) + { + // add arg + _AddArg(t, argLength); + // next + t += argLength; + } + } + } + else + { + forceLogDebug_printf("Unable to find end of rpx file name in arg string"); + } + } + // setup UGQR + ppcInterpreterCurrentInstance->spr.UGQR[0 + 2] = 0x00040004; + ppcInterpreterCurrentInstance->spr.UGQR[0 + 3] = 0x00050005; + ppcInterpreterCurrentInstance->spr.UGQR[0 + 4] = 0x00060006; + ppcInterpreterCurrentInstance->spr.UGQR[0 + 5] = 0x00070007; + coreinit::InitForegroundBucket(); + coreinit::InitSysHeap(); +} + +struct PreinitUserHeapStruct +{ + MEMPTR<coreinit::MEMHeapBase> heapTempMEM1; + MEMPTR<coreinit::MEMHeapBase> heapTempFG; + MEMPTR<coreinit::MEMHeapBase> heapTempMEM2; +}; + +SysAllocator<PreinitUserHeapStruct> g_preinitUserParam; + +void InitCafeHeaps() +{ + // init default heaps + g_preinitUserParam->heapTempMEM1 = nullptr; + g_preinitUserParam->heapTempFG = nullptr; + g_preinitUserParam->heapTempMEM2 = nullptr; + coreinit::InitDefaultHeaps(g_preinitUserParam->heapTempMEM1, g_preinitUserParam->heapTempFG, g_preinitUserParam->heapTempMEM2); + // if __preinit_user export exists in main executable, run it and pass our heaps + MPTR exportAddr = applicationRPX ? RPLLoader_FindRPLExport(applicationRPX, "__preinit_user", false) : MPTR_NULL; + if (exportAddr != MPTR_NULL) + { + PPCCoreCallback(exportAddr, &g_preinitUserParam->heapTempMEM1, &g_preinitUserParam->heapTempFG, &g_preinitUserParam->heapTempMEM2); + } + // setup heaps + if (g_preinitUserParam->heapTempMEM1 != nullptr) + coreinit::MEMSetBaseHeapHandle(0, g_preinitUserParam->heapTempMEM1); + if (g_preinitUserParam->heapTempFG != nullptr) + coreinit::MEMSetBaseHeapHandle(8, g_preinitUserParam->heapTempFG); + if (g_preinitUserParam->heapTempMEM2 != nullptr) + coreinit::MEMSetBaseHeapHandle(1, g_preinitUserParam->heapTempMEM2); +} + +MPTR CoreInitEntry(sint32 argc, MPTR argv) +{ + const char* rpxPath = (char*)memory_getPointerFromVirtualOffset(memory_readU32(argv + 0)); + InitCafeHeaps(); + // do a dummy allocation via the OSDynLoad allocator + // Watch Dogs relies on this to correctly set up its malloc() allocator + // must be larger than 0x190 to trigger creation of a new memory segment. But also must not be close to page alignment (0x1000) or else the bug will trigger + void* dummyAlloc = coreinit::OSDynLoad_AllocatorAlloc(0x500, 0x4); + if (dummyAlloc) + coreinit::OSDynLoad_AllocatorFree(dummyAlloc); + return _entryPoint; +} + +sint32 _coreinitTitleEntryPoint; + +void coreinit_start(PPCInterpreter_t* hCPU) +{ + _coreinitInfo = (coreinitInit_t*)memory_getPointerFromVirtualOffset(coreinit_allocFromSysArea(sizeof(coreinitInit_t), 32)); + memset(_coreinitInfo, 0, sizeof(coreinitInit_t)); + + CafeInit(); + _coreinitTitleEntryPoint = CoreInitEntry(_coreinitInfo->argc, memory_getVirtualOffsetFromPointer(_coreinitInfo->argv)); + + RPLLoader_CallEntrypoints(); + + // init vpadbase (todo - simulate entrypoints for HLE modules) + padscore::start(); + vpad::start(); + + // continue at main executable entrypoint + hCPU->gpr[4] = memory_getVirtualOffsetFromPointer(_coreinitInfo->argv); + hCPU->gpr[3] = _coreinitInfo->argc; + hCPU->instructionPointer = _coreinitTitleEntryPoint; +} diff --git a/src/Cafe/OS/libs/coreinit/coreinit_LockedCache.cpp b/src/Cafe/OS/libs/coreinit/coreinit_LockedCache.cpp new file mode 100644 index 00000000..9ab614c3 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_LockedCache.cpp @@ -0,0 +1,299 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/libs/coreinit/coreinit.h" +#include "Cafe/HW/Latte/Core/LatteBufferCache.h" + +/* + Locked cache is mapped to the following memory regions: + offset size + core0: 0xFFC00000 0x4000 + core1: 0xFFC40000 0x4000 + core2: 0xFFC80000 0x4000 +*/ + +#define LC_LOCKED_CACHE_GRANULARITY (0x200) // 512B +#define LC_LOCKED_CACHE_SIZE (0x4000) // 16KB + +#define LC_MASK_FREE (0) +#define LC_MASK_RESERVED (1) +#define LC_MASK_RESERVED_END (2) // indicates end of reserved block + +namespace coreinit +{ + uint8 lcCacheMask[PPC_CORE_COUNT][(LC_LOCKED_CACHE_SIZE + LC_LOCKED_CACHE_GRANULARITY - 1) / LC_LOCKED_CACHE_GRANULARITY] = { 0 }; + uint32 lcAllocatedBlocks[PPC_CORE_COUNT] = { 0 }; + + MPTR lcAddr[] = + { + 0xFFC00000, // core 0 + 0xFFC40000, // core 1 + 0xFFC80000 // core 2 + }; + + void coreinitExport_LCGetMaxSize(PPCInterpreter_t* hCPU) + { + osLib_returnFromFunction(hCPU, LC_LOCKED_CACHE_SIZE); + } + + void coreinitExport_LCAlloc(PPCInterpreter_t* hCPU) + { + //debug_printf("LCAlloc(0x%04x) Thread %08x Core %d", hCPU->gpr[3], coreinitThread_getCurrentThread(hCPU), PPCInterpreter_getCoreIndex(hCPU)); + uint32 size = hCPU->gpr[3]; + if (size == 0 || size > LC_LOCKED_CACHE_SIZE) + { + debug_printf("LCAlloc: Invalid alloc size %d\n", size); + osLib_returnFromFunction(hCPU, MPTR_NULL); + return; + } + if ((size % LC_LOCKED_CACHE_GRANULARITY) != 0) + { + debug_printf("LCAlloc: Unaligned alloc size 0x%04x\n", size); + size += (LC_LOCKED_CACHE_GRANULARITY - 1); + size &= ~(LC_LOCKED_CACHE_GRANULARITY - 1); + } + uint32 coreIndex = PPCInterpreter_getCoreIndex(hCPU); + uint8* cacheMask = lcCacheMask[coreIndex]; + uint32 entryMaskLength = size / LC_LOCKED_CACHE_GRANULARITY; + for (uint32 i = 0; i <= (LC_LOCKED_CACHE_SIZE / LC_LOCKED_CACHE_GRANULARITY) - entryMaskLength; i++) + { + // check if range starting at i is free + bool rangeIsFree = true; + for (uint32 f = 0; f < entryMaskLength; f++) + { + if (cacheMask[i + f] != LC_MASK_FREE) + { + rangeIsFree = false; + break; + } + } + if (rangeIsFree) + { + // found space for allocation + MPTR allocAddr = lcAddr[coreIndex] + i * LC_LOCKED_CACHE_GRANULARITY; + // mark range as allocated + for (uint32 f = 0; f < entryMaskLength - 1; f++) + { + cacheMask[i + f] = LC_MASK_RESERVED; + } + cacheMask[i + entryMaskLength - 1] = LC_MASK_RESERVED_END; + // update allocation counter + lcAllocatedBlocks[coreIndex] += entryMaskLength; + // return allocAddr + //debug_printf("LCAlloc result %08x Thread %08x Core %d", (uint32)allocAddr, coreinitThread_getCurrentThread(hCPU), PPCInterpreter_getCoreIndex(hCPU)); + osLib_returnFromFunction(hCPU, allocAddr); + return; + } + } + // not enough space left + //debug_printf("LCAlloc failed Thread %08x Core %d", coreinitThread_getCurrentThread(hCPU), PPCInterpreter_getCoreIndex(hCPU)); + osLib_returnFromFunction(hCPU, MPTR_NULL); + } + + + void coreinitExport_LCDealloc(PPCInterpreter_t* hCPU) + { + uint32 coreIndex = PPCInterpreter_getCoreIndex(hCPU); + uint8* cacheMask = lcCacheMask[coreIndex]; + //printf("LCDealloc(0x%08x)\n", hCPU->gpr[3]); + + MPTR deallocAddr = hCPU->gpr[3]; + if (deallocAddr < lcAddr[coreIndex] || deallocAddr >= (lcAddr[coreIndex] + LC_LOCKED_CACHE_SIZE)) + { + // out of bounds +#ifndef PUBLIC_RELEASE + forceLog_printf("LCDealloc(): Out of bounds"); +#endif + osLib_returnFromFunction(hCPU, 0); + return; + } + uint32 relativeOffset = (uint32)deallocAddr - lcAddr[coreIndex]; + if ((relativeOffset % LC_LOCKED_CACHE_GRANULARITY) != 0) + { + debug_printf("LCDealloc: Offset alignment is invalid (0x%08x / 0x%08x)\n", deallocAddr, relativeOffset); + osLib_returnFromFunction(hCPU, 0); + return; + } + uint32 startIndex = relativeOffset / LC_LOCKED_CACHE_GRANULARITY; + // check this is really the beginning of an allocated block + if (startIndex > 0 && (cacheMask[startIndex - 1] != LC_MASK_RESERVED_END && cacheMask[startIndex - 1] != LC_MASK_FREE)) + { + debug_printf("LCDealloc: Offset is invalid (0x%08x / 0x%08x)\n", deallocAddr, relativeOffset); + osLib_returnFromFunction(hCPU, 0); + return; + } + // free range by reseting mask in allocated region + for (uint32 i = startIndex; i < (LC_LOCKED_CACHE_SIZE / LC_LOCKED_CACHE_GRANULARITY); i++) + { + if (cacheMask[i] == LC_MASK_RESERVED_END) + { + // end of allocated block reached + cacheMask[i] = LC_MASK_FREE; + // update allocation counter + lcAllocatedBlocks[coreIndex]--; + break; + } + else if (cacheMask[i] == LC_MASK_FREE) + { + debug_printf("LCDealloc: Allocation mask error detected\n"); + break; + } + cacheMask[i] = LC_MASK_FREE; + // update allocation counter + lcAllocatedBlocks[coreIndex]--; + } + osLib_returnFromFunction(hCPU, 0); + } + + void coreinitExport_LCGetUnallocated(PPCInterpreter_t* hCPU) + { + uint32 unallocatedBytes = 0; + uint32 coreIndex = PPCInterpreter_getCoreIndex(hCPU); + unallocatedBytes = LC_LOCKED_CACHE_SIZE - (lcAllocatedBlocks[coreIndex] * LC_LOCKED_CACHE_GRANULARITY); + //debug_printf("LCGetUnallocated() Result: 0x%x\n", unallocatedBytes); + osLib_returnFromFunction(hCPU, unallocatedBytes); + } + + void coreinitExport_LCGetAllocatableSize(PPCInterpreter_t* hCPU) + { + debug_printf("LCGetAllocatableSize()\n"); + // no parameters, returns largest allocatable block size + // get core LC parameters + uint32 coreIndex = PPCInterpreter_getCoreIndex(hCPU); + uint8* cacheMask = lcCacheMask[coreIndex]; + // scan for largest range of available blocks + uint32 largestFreeRange = 0; + uint32 currentRangeSize = 0; + for (uint32 i = 0; i < LC_LOCKED_CACHE_SIZE / LC_LOCKED_CACHE_GRANULARITY; i++) + { + if (cacheMask[i] == LC_MASK_FREE) + { + currentRangeSize++; + } + else + { + largestFreeRange = std::max(largestFreeRange, currentRangeSize); + currentRangeSize = 0; + } + } + largestFreeRange = std::max(largestFreeRange, currentRangeSize); + osLib_returnFromFunction(hCPU, largestFreeRange * LC_LOCKED_CACHE_GRANULARITY); + } + + uint32 LCIsEnabled[PPC_CORE_COUNT] = { 0 }; + + void coreinitExport_LCEnableDMA(PPCInterpreter_t* hCPU) + { + //debug_printf("LCEnableDMA()\n"); + + LCIsEnabled[PPCInterpreter_getCoreIndex(hCPU)]++; + osLib_returnFromFunction(hCPU, 1); + } + + sint32 _lcDisableErrorCounter = 0; + + void coreinitExport_LCDisableDMA(PPCInterpreter_t* hCPU) + { + //debug_printf("LCDisableDMA()\n"); +#ifndef PUBLIC_RELASE + if (LCIsEnabled[PPCInterpreter_getCoreIndex(hCPU)] == 0) + assert_dbg(); +#endif + LCIsEnabled[PPCInterpreter_getCoreIndex(hCPU)]--; +#ifndef PUBLIC_RELEASE + if (LCIsEnabled[PPCInterpreter_getCoreIndex(hCPU)] == 0) + { + uint32 coreIndex = PPCInterpreter_getCoreIndex(hCPU); + uint8* cacheMask = lcCacheMask[coreIndex]; + for (uint32 i = 0; i <= (LC_LOCKED_CACHE_SIZE / LC_LOCKED_CACHE_GRANULARITY); i++) + { + if (cacheMask[i] != LC_MASK_FREE) + { + if (_lcDisableErrorCounter < 15) + { + forceLog_printf("LC disabled but there is still memory allocated"); + _lcDisableErrorCounter++; + } + } + } + } +#endif + + osLib_returnFromFunction(hCPU, 1); + } + + void coreinitExport_LCIsDMAEnabled(PPCInterpreter_t* hCPU) + { + osLib_returnFromFunction(hCPU, LCIsEnabled[PPCInterpreter_getCoreIndex(hCPU)] ? 1 : 0); + } + + void coreinitExport_LCHardwareIsAvailable(PPCInterpreter_t* hCPU) + { + // on the real HW, LC is shared between processes and not all processes can access it. In the emulator we don't care and always return true. + osLib_returnFromFunction(hCPU, 1); + } + + void coreinitExport_LCLoadDMABlocks(PPCInterpreter_t* hCPU) + { + //printf("LCLoadDMABlocks(0x%08x, 0x%08x, 0x%08x)\n", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); + uint32 numBlocks = hCPU->gpr[5]; + if (numBlocks == 0) + numBlocks = 128; + uint32 transferSize = numBlocks * 32; + uint8* destPtr = memory_getPointerFromVirtualOffset(hCPU->gpr[3]); + uint8* srcPtr = memory_getPointerFromVirtualOffset(hCPU->gpr[4]); + // copy right away, we don't emulate the DMAQueue currently + memcpy(destPtr, srcPtr, transferSize); + + osLib_returnFromFunction(hCPU, 0); + } + + void coreinitExport_LCStoreDMABlocks(PPCInterpreter_t* hCPU) + { + //printf("LCStoreDMABlocks(0x%08x, 0x%08x, 0x%08x)\n", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); + uint32 numBlocks = hCPU->gpr[5]; + if (numBlocks == 0) + numBlocks = 128; + //uint32 transferSize = numBlocks*32; + uint8* destPtr = memory_getPointerFromVirtualOffset(hCPU->gpr[3]); + uint8* srcPtr = memory_getPointerFromVirtualOffset(hCPU->gpr[4]); + // copy right away, we don't emulate the DMAQueue currently + memcpy_qwords(destPtr, srcPtr, numBlocks * (32 / sizeof(uint64))); + + LatteBufferCache_notifyDCFlush(hCPU->gpr[3], numBlocks * 32); + + osLib_returnFromFunction(hCPU, 0); + } + + void coreinitExport_LCWaitDMAQueue(PPCInterpreter_t* hCPU) + { + //printf("LCWaitDMAQueue(%d)\n", hCPU->gpr[3]); + uint32 len = hCPU->gpr[3]; + // todo: Implement (be aware DMA queue is per core) + osLib_returnFromFunction(hCPU, 0); + } + + void InitializeLC() + { + for (sint32 f = 0; f < PPC_CORE_COUNT; f++) + { + memset(lcCacheMask[f], LC_MASK_FREE, (LC_LOCKED_CACHE_SIZE + LC_LOCKED_CACHE_GRANULARITY - 1) / LC_LOCKED_CACHE_GRANULARITY); + } + + osLib_addFunction("coreinit", "LCGetMaxSize", coreinitExport_LCGetMaxSize); + + osLib_addFunction("coreinit", "LCAlloc", coreinitExport_LCAlloc); + osLib_addFunction("coreinit", "LCDealloc", coreinitExport_LCDealloc); + osLib_addFunction("coreinit", "LCGetUnallocated", coreinitExport_LCGetUnallocated); + osLib_addFunction("coreinit", "LCGetAllocatableSize", coreinitExport_LCGetAllocatableSize); + + osLib_addFunction("coreinit", "LCEnableDMA", coreinitExport_LCEnableDMA); + osLib_addFunction("coreinit", "LCDisableDMA", coreinitExport_LCDisableDMA); + osLib_addFunction("coreinit", "LCIsDMAEnabled", coreinitExport_LCIsDMAEnabled); + osLib_addFunction("coreinit", "LCHardwareIsAvailable", coreinitExport_LCHardwareIsAvailable); + + osLib_addFunction("coreinit", "LCLoadDMABlocks", coreinitExport_LCLoadDMABlocks); + osLib_addFunction("coreinit", "LCStoreDMABlocks", coreinitExport_LCStoreDMABlocks); + osLib_addFunction("coreinit", "LCWaitDMAQueue", coreinitExport_LCWaitDMAQueue); + + } + +} diff --git a/src/Cafe/OS/libs/coreinit/coreinit_LockedCache.h b/src/Cafe/OS/libs/coreinit/coreinit_LockedCache.h new file mode 100644 index 00000000..d2fbcbdf --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_LockedCache.h @@ -0,0 +1,6 @@ +#pragma once + +namespace coreinit +{ + void InitializeLC(); +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_MCP.cpp b/src/Cafe/OS/libs/coreinit/coreinit_MCP.cpp new file mode 100644 index 00000000..fbc42683 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_MCP.cpp @@ -0,0 +1,561 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/IOSU/legacy/iosu_ioctl.h" +#include "Cafe/IOSU/legacy/iosu_mcp.h" +#include "Cafe/OS/libs/coreinit/coreinit_IOS.h" +#include "Cafe/OS/libs/coreinit/coreinit_MCP.h" + + +#include "Cafe/OS/libs/nn_common.h" +#include "Cafe/OS/libs/nn_act/nn_act.h" +#include "config/CemuConfig.h" + +#include "Cafe/CafeSystem.h" + +#define mcpPrepareRequest() \ +StackAllocator<iosuMcpCemuRequest_t> _buf_mcpRequest; \ +StackAllocator<ioBufferVector_t> _buf_bufferVector; \ +iosuMcpCemuRequest_t* mcpRequest = _buf_mcpRequest.GetPointer(); \ +ioBufferVector_t* mcpBufferVector = _buf_bufferVector.GetPointer(); \ +memset(mcpRequest, 0, sizeof(iosuMcpCemuRequest_t)); \ +memset(mcpBufferVector, 0, sizeof(ioBufferVector_t)); \ +mcpBufferVector->buffer = (uint8*)mcpRequest; + +#define MCP_FAKE_HANDLE (0x00000010) + +typedef struct +{ + uint32be n0; + uint32be n1; + uint32be n2; +}mcpSystemVersion_t; + +static_assert(sizeof(MCPTitleInfo) == 0x61, "title entry size is invalid"); +static_assert(offsetof(MCPTitleInfo, appPath) == 0xC, "title entry appPath has invalid offset"); +static_assert(offsetof(MCPTitleInfo, titleVersion) == 0x48, "title entry titleVersion has invalid offset"); +static_assert(offsetof(MCPTitleInfo, osVersion) == 0x4A, "title entry osVersion has invalid offset"); + +MCPHANDLE MCP_Open() +{ + // placeholder + return MCP_FAKE_HANDLE; +} + +void MCP_Close(MCPHANDLE mcpHandle) +{ + // placeholder +} + +void coreinitExport_MCP_Open(PPCInterpreter_t* hCPU) +{ + // placeholder + osLib_returnFromFunction(hCPU, MCP_Open()); +} + +void coreinitExport_MCP_Close(PPCInterpreter_t* hCPU) +{ + // placeholder + osLib_returnFromFunction(hCPU, 0); +} + +sint32 MCP_GetSysProdSettings(MCPHANDLE mcpHandle, SysProdSettings* sysProdSettings) +{ + memset(sysProdSettings, 0x00, sizeof(SysProdSettings)); + // todo: Other fields are currently unknown + + sysProdSettings->gameRegion = (uint8)CafeSystem::GetForegroundTitleRegion(); + sysProdSettings->platformRegion = (uint8)CafeSystem::GetPlatformRegion(); + + // contains Wii U serial parts at +0x1A and +0x22? + return 0; +} + +void coreinitExport_MCP_GetSysProdSettings(PPCInterpreter_t* hCPU) +{ + forceLogDebug_printf("MCP_GetSysProdSettings(0x%08x,0x%08x)", hCPU->gpr[3], hCPU->gpr[4]); + sint32 result = MCP_GetSysProdSettings(hCPU->gpr[3], (SysProdSettings*)memory_getPointerFromVirtualOffset(hCPU->gpr[4])); + osLib_returnFromFunction(hCPU, result); +} + +void coreinitExport_MCP_TitleListByAppType(PPCInterpreter_t* hCPU) +{ + forceLogDebug_printf("MCP_TitleListByAppType(0x%08x,0x%08x,0x%08x,0x%08x,0x%08x)", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7]); + + ppcDefineParamU32(mcpHandle, 0); + ppcDefineParamU32(appType, 1); + ppcDefineParamU32BEPtr(countOutput, 2); + ppcDefineParamStructPtr(titleList, MCPTitleInfo, 3); + ppcDefineParamU32(titleListSize, 4); + + sint32 maxCount = titleListSize / sizeof(MCPTitleInfo); + + mcpPrepareRequest(); + mcpRequest->requestCode = IOSU_MCP_GET_TITLE_LIST_BY_APP_TYPE; + mcpRequest->titleListRequest.titleCount = maxCount; + mcpRequest->titleListRequest.titleList = titleList; + mcpRequest->titleListRequest.titleListBufferSize = sizeof(MCPTitleInfo) * 1; + mcpRequest->titleListRequest.appType = appType; + __depr__IOS_Ioctlv(IOS_DEVICE_MCP, IOSU_MCP_REQUEST_CEMU, 1, 1, mcpBufferVector); + + *countOutput = mcpRequest->titleListRequest.titleCount; + + osLib_returnFromFunction(hCPU, 0); +} + +void coreinitExport_MCP_TitleList(PPCInterpreter_t* hCPU) +{ + forceLogDebug_printf("MCP_TitleList(...) unimplemented"); + ppcDefineParamU32(mcpHandle, 0); + ppcDefineParamU32BEPtr(countOutput, 1); + ppcDefineParamStructPtr(titleList, MCPTitleInfo, 2); + ppcDefineParamU32(titleListBufferSize, 3); + + // todo -> Other parameters + + mcpPrepareRequest(); + mcpRequest->requestCode = IOSU_MCP_GET_TITLE_LIST; + mcpRequest->titleListRequest.titleCount = *countOutput; + mcpRequest->titleListRequest.titleList = titleList; + mcpRequest->titleListRequest.titleListBufferSize = titleListBufferSize; + __depr__IOS_Ioctlv(IOS_DEVICE_MCP, IOSU_MCP_REQUEST_CEMU, 1, 1, mcpBufferVector); + + *countOutput = mcpRequest->titleListRequest.titleCount; + + osLib_returnFromFunction(hCPU, mcpRequest->returnCode); +} + +void coreinitExport_MCP_TitleCount(PPCInterpreter_t* hCPU) +{ + forceLogDebug_printf("MCP_TitleCount(): Untested"); + ppcDefineParamU32(mcpHandle, 0); + + mcpPrepareRequest(); + mcpRequest->requestCode = IOSU_MCP_GET_TITLE_COUNT; + mcpRequest->titleListRequest.titleCount = 0; + __depr__IOS_Ioctlv(IOS_DEVICE_MCP, IOSU_MCP_REQUEST_CEMU, 1, 1, mcpBufferVector); + + osLib_returnFromFunction(hCPU, mcpRequest->titleListRequest.titleCount); +} + +void coreinitExport_MCP_GetTitleInfo(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(mcpHandle, 0); + ppcDefineParamU64(titleId, 2); + ppcDefineParamStructPtr(titleList, MCPTitleInfo, 4); + + forceLogDebug_printf("MCP_GetTitleInfo() called"); + + mcpPrepareRequest(); + mcpRequest->requestCode = IOSU_MCP_GET_TITLE_INFO; + mcpRequest->titleListRequest.titleCount = 1; + mcpRequest->titleListRequest.titleList = titleList; + mcpRequest->titleListRequest.titleListBufferSize = sizeof(MCPTitleInfo); + mcpRequest->titleListRequest.titleId = titleId; + __depr__IOS_Ioctlv(IOS_DEVICE_MCP, IOSU_MCP_REQUEST_CEMU, 1, 1, mcpBufferVector); + + if (mcpRequest->titleListRequest.titleCount == 0) + { + forceLog_printf("MCP_GetTitleInfo() failed to get title info"); + } + + osLib_returnFromFunction(hCPU, mcpRequest->returnCode); + +} + +void coreinitExport_MCP_GetTitleInfoByTitleAndDevice(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(mcpHandle, 0); + ppcDefineParamU64(titleId, 2); + ppcDefineParamStr(device, 4); // e.g. "odd" + ppcDefineParamStructPtr(titleList, MCPTitleInfo, 5); + + forceLogDebug_printf("MCP_GetTitleInfoByTitleAndDevice() called (todo - device type support)"); + + mcpPrepareRequest(); + mcpRequest->requestCode = IOSU_MCP_GET_TITLE_INFO; + mcpRequest->titleListRequest.titleCount = 1; + mcpRequest->titleListRequest.titleList = titleList; + mcpRequest->titleListRequest.titleListBufferSize = sizeof(MCPTitleInfo); + mcpRequest->titleListRequest.titleId = titleId; + __depr__IOS_Ioctlv(IOS_DEVICE_MCP, IOSU_MCP_REQUEST_CEMU, 1, 1, mcpBufferVector); + + if (mcpRequest->titleListRequest.titleCount == 0) + { + forceLogDebug_printf("MCP_GetTitleInfoByTitleAndDevice() no title found"); + osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_MCP, 0)); // E-Shop/nn_vctl.rpl expects error to be returned when no title is found + return; + } + + osLib_returnFromFunction(hCPU, mcpRequest->returnCode); + +} + +namespace coreinit +{ + + void export_MCP_GetSystemVersion(PPCInterpreter_t* hCPU) + { + forceLogDebug_printf("MCP_GetSystemVersion(%d,0x%08x)", hCPU->gpr[3], hCPU->gpr[4]); + ppcDefineParamU32(mcpHandle, 0); + ppcDefineParamStructPtr(systemVersion, mcpSystemVersion_t, 1); + + systemVersion->n0 = 0x5; + systemVersion->n1 = 0x5; + systemVersion->n2 = 0x2; + // todo: Load this from \sys\title\00050010\10041200\content\version.bin + + osLib_returnFromFunction(hCPU, 0); + } + + void export_MCP_Get4SecondOffStatus(PPCInterpreter_t* hCPU) + { + // r3 = mcpHandle + forceLogDebug_printf("MCP_Get4SecondOffStatus(...) placeholder"); + // if this returns 1 then Barista will display the warning about cold-shutdown ('Holding the POWER button for at least four seconds...') + osLib_returnFromFunction(hCPU, 0); + } + + void export_MCP_TitleListByDevice(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(mcpHandle, 0); + ppcDefineParamStr(deviceName, 1); + ppcDefineParamU32BEPtr(titleCount, 2); + ppcDefineParamStructPtr(titleList, MCPTitleInfo, 3); // type guessed + // purpose of r7 parameter is unknown + + cemu_assert_unimplemented(); + + titleList[0].titleIdHigh = 0x00050000; + titleList[0].titleIdLow = 0x11223344; + strcpy(titleList[0].appPath, "titlePathTest"); + + *titleCount = 1; + + forceLogDebug_printf("MCP_TitleListByDevice() - placeholder"); + + osLib_returnFromFunction(hCPU, 0); + } + +#pragma pack(1) + typedef struct + { + /* +0x000 */ char storageName[0x90]; // the name in the storage path + /* +0x090 */ char storagePath[0x280 - 1]; // /vol/storage_%s%02x + /* +0x30F */ uint32be storageSubindexOrMask; // the id in the storage path, but this might also be a MASK of indices (e.g. 1 -> Only device 1, 7 -> Device 1,2,3) men.rpx expects 0xF (or 0x7?) to be set for MLC, SLC and USB for MLC_FullDeviceList + uint8 ukn313[4]; + uint8 ukn317[4]; + }MCPDevice_t; +#pragma pack() + + static_assert(sizeof(MCPDevice_t) == 0x31B, "MCPDevice_t has invalid size"); + static_assert(offsetof(MCPDevice_t, storagePath) == 0x090, "MCPDevice_t.storagePath has invalid offset"); + static_assert(offsetof(MCPDevice_t, storageSubindexOrMask) == 0x30F, "MCPDevice_t.storageSubindex has invalid offset"); + static_assert(offsetof(MCPDevice_t, ukn313) == 0x313, "MCPDevice_t.ukn313 has invalid offset"); + static_assert(offsetof(MCPDevice_t, ukn317) == 0x317, "MCPDevice_t.ukn317 has invalid offset"); + + void MCP_DeviceListEx(uint32 mcpHandle, uint32be* deviceCount, MCPDevice_t* deviceList, uint32 deviceListSize, bool returnFullList) + { + sint32 maxDeviceCount = deviceListSize / sizeof(MCPDevice_t); + + if (maxDeviceCount < 3*3) + assert_dbg(); + + // if this doesnt return both MLC and SLC friendlist (frd.rpx) will softlock during boot + + memset(deviceList, 0, sizeof(MCPDevice_t) * 1); + sint32 index = 0; + for (sint32 f = 0; f < 1; f++) + { + // 0 + strcpy(deviceList[index].storageName, "mlc"); + deviceList[index].storageSubindexOrMask = 0xF; // bitmask? + sprintf(deviceList[index].storagePath, "/vol/storage_%s%02x", deviceList[index].storageName, (sint32)deviceList[index].storageSubindexOrMask); + index++; + // 1 + strcpy(deviceList[index].storageName, "slc"); + deviceList[index].storageSubindexOrMask = 0xF; // bitmask? + sprintf(deviceList[index].storagePath, "/vol/storage_%s%02x", deviceList[index].storageName, (sint32)deviceList[index].storageSubindexOrMask); + index++; + // 2 + strcpy(deviceList[index].storageName, "usb"); + deviceList[index].storageSubindexOrMask = 0xF; + sprintf(deviceList[index].storagePath, "/vol/storage_%s%02x", deviceList[index].storageName, (sint32)deviceList[index].storageSubindexOrMask); + index++; + } + + + *deviceCount = index; + } + + + void export_MCP_DeviceList(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(mcpHandle, 0); + ppcDefineParamU32BEPtr(deviceCount, 1); + ppcDefineParamStructPtr(deviceList, MCPDevice_t, 2); + ppcDefineParamU32(deviceListSize, 3); + + forceLogDebug_printf("MCP_DeviceList() - placeholder LR %08x", hCPU->spr.LR); + + sint32 maxDeviceCount = deviceListSize / sizeof(MCPDevice_t); + + if (maxDeviceCount < 2) + assert_dbg(); + + // if this doesnt return both MLC and SLC friendlist (frd.rpx) will softlock during boot + + memset(deviceList, 0, sizeof(MCPDevice_t) * 1); + // 0 + strcpy(deviceList[0].storageName, "mlc"); + deviceList[0].storageSubindexOrMask = (0x01); // bitmask? + sprintf(deviceList[0].storagePath, "/vol/storage_%s%02x", deviceList[0].storageName, (sint32)deviceList[0].storageSubindexOrMask); + // 1 + strcpy(deviceList[1].storageName, "slc"); + deviceList[1].storageSubindexOrMask = (0x01); // bitmask? + sprintf(deviceList[1].storagePath, "/vol/storage_%s%02x", deviceList[1].storageName, (sint32)deviceList[1].storageSubindexOrMask); + + // 2 + //strcpy(deviceList[2].storageName, "usb"); + //deviceList[2].storageSubindex = 1; + //sprintf(deviceList[2].storagePath, "/vol/storage_%s%02x", deviceList[2].storageName, (sint32)deviceList[2].storageSubindex); + + + *deviceCount = 2; + + osLib_returnFromFunction(hCPU, 0); + } + + void export_MCP_FullDeviceList(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(mcpHandle, 0); + ppcDefineParamU32BEPtr(deviceCount, 1); + ppcDefineParamStructPtr(deviceList, MCPDevice_t, 2); + ppcDefineParamU32(deviceListSize, 3); + + forceLogDebug_printf("MCP_FullDeviceList() - placeholder LR %08x", hCPU->spr.LR); + + MCP_DeviceListEx(mcpHandle, deviceCount, deviceList, deviceListSize, true); + + osLib_returnFromFunction(hCPU, 0); + } + + void export_MCP_UpdateCheckContext(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(mcpHandle, 0); + ppcDefineParamU32BEPtr(unknownParam, 1); + + forceLogDebug_printf("MCP_UpdateCheckContext() - placeholder (might be wrong)"); + + *unknownParam = 1; + + + osLib_returnFromFunction(hCPU, 0); + } + + void export_MCP_TitleListUpdateGetNext(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(mcpHandle, 0); + ppcDefineParamMPTR(callbackMPTR, 1); + + forceLogDebug_printf("MCP_TitleListUpdateGetNext() - placeholder/unimplemented"); + + // this callback is to let the app know when the title list changed? + + osLib_returnFromFunction(hCPU, 0); + } + + void export_MCP_GetOverlayAppInfo(PPCInterpreter_t* hCPU) + { + forceLogDebug_printf("MCP_GetOverlayAppInfo(...) placeholder"); + ppcDefineParamU32(mcpHandle, 0); + ppcDefineParamU32(appType, 1); + ppcDefineParamU32(uknR5, 2); + ppcDefineParamStructPtr(titleList, MCPTitleInfo, 3); + + // hacky + + mcpPrepareRequest(); + mcpRequest->requestCode = IOSU_MCP_GET_TITLE_LIST_BY_APP_TYPE; + mcpRequest->titleListRequest.titleCount = 1; + mcpRequest->titleListRequest.titleList = titleList; + mcpRequest->titleListRequest.titleListBufferSize = sizeof(MCPTitleInfo)*1; + mcpRequest->titleListRequest.appType = appType; + __depr__IOS_Ioctlv(IOS_DEVICE_MCP, IOSU_MCP_REQUEST_CEMU, 1, 1, mcpBufferVector); + + if (mcpRequest->titleListRequest.titleCount != 1) + assert_dbg(); + + osLib_returnFromFunction(hCPU, 0); + } + + void InitializeMCP() + { + osLib_addFunction("coreinit", "MCP_Open", coreinitExport_MCP_Open); + osLib_addFunction("coreinit", "MCP_Close", coreinitExport_MCP_Close); + osLib_addFunction("coreinit", "MCP_GetSysProdSettings", coreinitExport_MCP_GetSysProdSettings); + osLib_addFunction("coreinit", "MCP_TitleListByAppType", coreinitExport_MCP_TitleListByAppType); + osLib_addFunction("coreinit", "MCP_TitleList", coreinitExport_MCP_TitleList); + osLib_addFunction("coreinit", "MCP_TitleCount", coreinitExport_MCP_TitleCount); + osLib_addFunction("coreinit", "MCP_GetTitleInfo", coreinitExport_MCP_GetTitleInfo); + osLib_addFunction("coreinit", "MCP_GetTitleInfoByTitleAndDevice", coreinitExport_MCP_GetTitleInfoByTitleAndDevice); + + osLib_addFunction("coreinit", "MCP_TitleListByDevice", export_MCP_TitleListByDevice); + osLib_addFunction("coreinit", "MCP_GetSystemVersion", export_MCP_GetSystemVersion); + osLib_addFunction("coreinit", "MCP_Get4SecondOffStatus", export_MCP_Get4SecondOffStatus); + + osLib_addFunction("coreinit", "MCP_DeviceList", export_MCP_DeviceList); + osLib_addFunction("coreinit", "MCP_FullDeviceList", export_MCP_FullDeviceList); + + osLib_addFunction("coreinit", "MCP_UpdateCheckContext", export_MCP_UpdateCheckContext); + osLib_addFunction("coreinit", "MCP_TitleListUpdateGetNext", export_MCP_TitleListUpdateGetNext); + osLib_addFunction("coreinit", "MCP_GetOverlayAppInfo", export_MCP_GetOverlayAppInfo); + } + +} + +typedef struct +{ + char settingName[0x40]; + uint32 ukn1; + uint32 ukn2; + uint32 ukn3; + uint32 ukn4_size; // size guessed + MPTR resultPtr; // pointer to output value +}UCParamStruct_t; + +static_assert(sizeof(UCParamStruct_t) == 0x54); // unsure + +#if BOOST_OS_LINUX +#define _strcmpi strcasecmp +#endif + +void coreinitExport_UCReadSysConfig(PPCInterpreter_t* hCPU) +{ + // parameters: + // r3 = UC handle? + // r4 = Number of values to read (count of UCParamStruct_t entries) + // r5 = UCParamStruct_t* + UCParamStruct_t* ucParamBase = (UCParamStruct_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[5]); + uint32 ucParamCount = hCPU->gpr[4]; + for(uint32 i=0; i<ucParamCount; i++) + { + UCParamStruct_t* ucParam = ucParamBase+i; + if(_strcmpi(ucParam->settingName, "cafe.cntry_reg") == 0) + { + // country code + // get country from simple address + uint32be simpleAddress = 0; + nn::act::GetSimpleAddressIdEx(&simpleAddress, nn::act::ACT_SLOT_CURRENT); + + uint32 countryCode = nn::act::getCountryCodeFromSimpleAddress(simpleAddress); + + if( ucParam->resultPtr != _swapEndianU32(MPTR_NULL) ) + memory_writeU32(_swapEndianU32(ucParam->resultPtr), countryCode); + } + else if (_strcmpi(ucParam->settingName, "cafe.language") == 0) + { + // language + // 0 -> Japanese + // 1 -> English + // ... + uint32 languageId = (uint32)GetConfig().console_language.GetValue(); + if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL)) + memory_writeU32(_swapEndianU32(ucParam->resultPtr), languageId); + } + else if (_strcmpi(ucParam->settingName, "cafe.initial_launch") == 0) + { + // initial launch + // possible ids: + // 0xFF Set by SCIFormatSystem (default?) + // 0x01 Inited but no user created yet (setting this will make the home menu go into user creation dialog) + // 0x02 Inited, with user + // other values are unknown + memory_writeU8(_swapEndianU32(ucParam->resultPtr), 2); + } + else if (_strcmpi(ucParam->settingName, "cafe.eula_version") == 0) + { + // todo - investigate values + memory_writeU32(_swapEndianU32(ucParam->resultPtr), 0); + } + else if (_strcmpi(ucParam->settingName, "cafe.eula_agree") == 0) + { + // todo - investigate values + memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0); + } + else if (_strcmpi(ucParam->settingName, "cafe.version") == 0) + { + // todo - investigate values + memory_writeU16(_swapEndianU32(ucParam->resultPtr), 0); + } + else if (_strcmpi(ucParam->settingName, "cafe.eco") == 0) + { + // todo - investigate values + memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0); + } + else if (_strcmpi(ucParam->settingName, "cafe.fast_boot") == 0) + { + // todo - investigate values + memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0); + } + else if (_strcmpi(ucParam->settingName, "parent.enable") == 0) + { + // parental feature enabled + if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL)) + memory_writeU32(_swapEndianU32(ucParam->resultPtr), 0); + } + else if (_strcmpi(ucParam->settingName, "nn.act.account_repaired") == 0) + { + if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL)) + memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0); + } + else if (_strcmpi(ucParam->settingName, "p_acct1.net_communication_on_game") == 0) + { + // get parental online control for online features + // note: This option is account-bound, the p_acct1 prefix indicates that the account in slot 1 is used + // account in slot 1 + if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL)) + memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0); // data type is guessed + } + else if (_strcmpi(ucParam->settingName, "p_acct1.int_movie") == 0) + { + // get parental online control for movies? + // used by Pikmin HD movies + if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL)) + memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0); // 0 -> allowed + } + else if (_strcmpi(ucParam->settingName, "p_acct1.network_launcher") == 0) + { + // miiverse restrictions + if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL)) + memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0); // data type is guessed (0 -> no restrictions, 1 -> read only?, 2 -> no access?) + } + else if (_strcmpi(ucParam->settingName, "s_acct01.uuid") == 0) + { + if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL) && ucParam->ukn4_size >= 37) + { + StackAllocator<uint8, 16> _uuid; + uint8* uuid = _uuid.GetPointer(); + nn::act::GetUuidEx(uuid, 1, 0); + char tempStr[64]; + sprintf(tempStr, "%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X", uuid[0], uuid[1], uuid[2], uuid[3], uuid[4], uuid[5], uuid[6], uuid[7], + uuid[8], uuid[9], uuid[10], uuid[11], uuid[12], uuid[13], uuid[14], uuid[15]); + strcpy((char*)memory_getPointerFromVirtualOffset(_swapEndianU32(ucParam->resultPtr)), tempStr); + } + } + else if (_strcmpi(ucParam->settingName, "s_acct01.nn.ec.eshop_initialized") == 0) + { + // E-Shop welcome screen + if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL)) + memory_writeU8(_swapEndianU32(ucParam->resultPtr), 1); // data type is guessed + } + else if (_strcmpi(ucParam->settingName, "p_acct1.int_browser") == 0) + { + if (ucParam->resultPtr != _swapEndianU32(MPTR_NULL)) + memory_writeU8(_swapEndianU32(ucParam->resultPtr), 0); + } + else + { + forceLogDebug_printf("Unsupported SCI value: %s Size %08x\n", ucParam->settingName, ucParam->ukn4_size); + } + } + osLib_returnFromFunction(hCPU, 0); // 0 -> success +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_MCP.h b/src/Cafe/OS/libs/coreinit/coreinit_MCP.h new file mode 100644 index 00000000..3b86b29d --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_MCP.h @@ -0,0 +1,54 @@ +#pragma once + +struct SysProdSettings +{ + uint8 ukn00; // 0x00 + uint8 ukn01; // 0x01 + uint8 ukn02; // 0x02 + uint8 platformRegion; // 0x03 + uint8 ukn04; + uint8 ukn05; + uint8 ukn06; + uint8 ukn07; + uint8 prevBlock; + uint8 ukn09; + uint8 ukn0A; + uint8 gameRegion; // game region? + uint8 nextBlock; + uint8 ukn0D; + uint8 ukn0E; + uint8 ukn0F; + uint8 ukn10; + uint8 ukn11; + uint8 ukn12; + uint8 ukn13; + uint8 ukn14[4]; + uint8 ukn18[4]; + uint8 ukn1C[4]; + uint8 ukn20[4]; + uint8 ukn24[4]; + uint8 ukn28[4]; + uint8 ukn2C[4]; + uint8 ukn30[4]; + uint8 ukn34[4]; + uint8 ukn38[4]; + uint8 ukn3C[4]; + uint8 ukn40[4]; + uint8 ukn44; + uint8 ukn45; +}; + +static_assert(sizeof(SysProdSettings) == 0x46); + +typedef uint32 MCPHANDLE; + +MCPHANDLE MCP_Open(); +void MCP_Close(MCPHANDLE mcpHandle); +sint32 MCP_GetSysProdSettings(MCPHANDLE mcpHandle, SysProdSettings* sysProdSettings); + +void coreinitExport_UCReadSysConfig(PPCInterpreter_t* hCPU); + +namespace coreinit +{ + void InitializeMCP(); +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_MEM.cpp b/src/Cafe/OS/libs/coreinit/coreinit_MEM.cpp new file mode 100644 index 00000000..e1904f72 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_MEM.cpp @@ -0,0 +1,651 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/libs/coreinit/coreinit_FG.h" +#include "Cafe/OS/libs/coreinit/coreinit_MEM.h" +#include "Cafe/OS/libs/coreinit/coreinit_MEM_ExpHeap.h" +#include "Cafe/OS/libs/coreinit/coreinit_Memory.h" +#include "Cafe/OS/RPL/rpl.h" +#include "Cafe/OS/libs/coreinit/coreinit_MEM_FrmHeap.h" +#include "coreinit_DynLoad.h" + +// the system area is a block of memory that exists only in the emulator. It is used to simplify dynamic memory allocation for system data +// this partially overlaps with the system heap from coreinit_SysHeap.cpp -> Use SysHeap for everything +MPTR sysAreaAllocatorOffset = 0; + +MPTR coreinit_allocFromSysArea(uint32 size, uint32 alignment) +{ + cemu_assert_debug(mmuRange_CEMU_AREA.isMapped()); + // deprecated, use OSAllocFromSystem instead (for everything but SysAllocator which probably should use its own allocator?) + static std::mutex s_allocator_mutex; + s_allocator_mutex.lock(); + sysAreaAllocatorOffset = (sysAreaAllocatorOffset+alignment-1); + sysAreaAllocatorOffset -= (sysAreaAllocatorOffset%alignment); + uint32 newMemOffset = mmuRange_CEMU_AREA.getBase() + sysAreaAllocatorOffset; + sysAreaAllocatorOffset += (size+3)&~3; + if( sysAreaAllocatorOffset >= mmuRange_CEMU_AREA.getSize() ) + { + forceLog_printf("Ran out of system memory"); + cemu_assert(false); // out of bounds + } + s_allocator_mutex.unlock(); + return newMemOffset; +} + +void coreinit_freeToSysArea(MPTR mem) +{ + // todo +} + +namespace coreinit +{ +#define MEM_MAX_HEAP_TABLE (0x20) + + sint32 g_heapTableCount = 0; + MEMHeapBase* g_heapTable[MEM_MAX_HEAP_TABLE] = {}; + + bool g_slockInitialized = false; + bool g_listsInitialized = false; + + MEMList g_list1; + MEMList g_list2; + MEMList g_list3; + + std::array<uint32, 3> gHeapFillValues{ 0xC3C3C3C3, 0xF3F3F3F3, 0xD3D3D3D3 }; + OSSpinLock gHeapGlobalLock; + MEMHeapBase* gDefaultHeap; + + bool MEMHeapTable_Add(MEMHeapBase* heap) + { + if (g_heapTableCount >= MEM_MAX_HEAP_TABLE) + return false; + g_heapTable[g_heapTableCount] = heap; + g_heapTableCount++; + return true; + } + + bool MEMHeapTable_Remove(MEMHeapBase* heap) + { + if (g_heapTableCount == 0) + return false; + + if (g_heapTableCount > MEM_MAX_HEAP_TABLE) + return false; + + for (sint32 i = 0; i < g_heapTableCount; ++i) + { + if (g_heapTable[i] == heap) + { + g_heapTable[i] = nullptr; + g_heapTableCount--; + + if (g_heapTableCount == 0) + return true; + + if (i >= g_heapTableCount) + return true; + + cemu_assert_debug(i < MEM_MAX_HEAP_TABLE); + for (; i < g_heapTableCount; ++i) + { + g_heapTable[i] = g_heapTable[i + 1]; + } + + cemu_assert_debug(i < MEM_MAX_HEAP_TABLE); + g_heapTable[i] = nullptr; + + return true; + } + } + + return false; + } + + MEMHeapBase* _MEMList_FindContainingHeap(MEMList* list, MEMHeapBase* heap) + { + for (MEMHeapBase* obj = (MEMHeapBase*)MEMGetFirstListObject(list); obj; obj = (MEMHeapBase*)MEMGetNextListObject(list, obj)) + { + if (obj->heapStart.GetPtr() <= heap && heap < obj->heapEnd.GetPtr()) + { + const MEMHeapHandle containHeap = _MEMList_FindContainingHeap(&obj->childList, heap); + return containHeap ? containHeap : obj; + } + } + + return nullptr; + } + + bool MEMList_ContainsHeap(MEMList* list, MEMHeapBase* heap) + { + for (MEMHeapBase* obj = (MEMHeapBase*)MEMGetFirstListObject(list); obj; obj = (MEMHeapBase*)MEMGetNextListObject(list, obj)) + { + if (obj == heap) + return true; + } + + return false; + } + + MEMList* MEMList_FindContainingHeap(MEMHeapBase* head) + { + MEMPTR<void> memBound; + uint32be memBoundSize; + OSGetMemBound(1, (MPTR*)memBound.GetBEPtr(), (uint32*)&memBoundSize); + + MEMPTR<void> bucket; + uint32be bucketSize; + coreinit::OSGetForegroundBucket(&bucket, &bucketSize); + + if ((uintptr_t)memBound.GetPtr() > (uintptr_t)head || (uintptr_t)head >= (uintptr_t)memBound.GetPtr() + (uint32)memBoundSize) + { + if ((uintptr_t)bucket.GetPtr() > (uintptr_t)head || (uintptr_t)head >= (uintptr_t)bucket.GetPtr() + (uint32)bucketSize) + { + MEMHeapBase* containHeap = _MEMList_FindContainingHeap(&g_list1, head); + if (containHeap) + return &containHeap->childList; + + return &g_list1; + } + + MEMHeapBase* containHeap = _MEMList_FindContainingHeap(&g_list3, head); + if (containHeap) + return &containHeap->childList; + + return &g_list3; + } + + MEMHeapBase* containHeap = _MEMList_FindContainingHeap(&g_list2, head); + if (containHeap) + return &containHeap->childList; + + return &g_list2; + } + + void MEMInitHeapBase(MEMHeapBase* heap, MEMHeapMagic magic, void* heapStart, void* heapEnd, uint32 createFlags) + { + memset(heap, 0, sizeof(MEMHeapBase)); + heap->magic = magic; + heap->heapStart = heapStart; + heap->heapEnd = heapEnd; + heap->flags = (uint8)createFlags; + + MEMInitList(&heap->childList, 4); + + if (!g_slockInitialized) + { + OSInitSpinLock(&gHeapGlobalLock); + g_slockInitialized = true; + } + + if (!g_listsInitialized) + { + MEMInitList(&g_list1, offsetof(MEMHeapBase, link)); + MEMInitList(&g_list2, offsetof(MEMHeapBase, link)); + MEMInitList(&g_list3, offsetof(MEMHeapBase, link)); + g_listsInitialized = true; + } + + OSInitSpinLock(&heap->spinlock); + + OSUninterruptibleSpinLock_Acquire(&gHeapGlobalLock); + + MEMList* list = MEMList_FindContainingHeap(heap); + MEMAppendListObject(list, heap); + + OSUninterruptibleSpinLock_Release(&gHeapGlobalLock); + } + + void MEMBaseDestroyHeap(MEMHeapBase* heap) + { + OSUninterruptibleSpinLock_Acquire(&gHeapGlobalLock); + + if (HAS_FLAG(heap->flags, MEM_HEAP_OPTION_THREADSAFE)) + OSUninterruptibleSpinLock_Acquire(&heap->spinlock); + + MEMList* containHeap = MEMList_FindContainingHeap(heap); + cemu_assert_debug(MEMList_ContainsHeap(containHeap, heap)); + MEMRemoveListObject(containHeap, heap); + + if (HAS_FLAG(heap->flags, MEM_HEAP_OPTION_THREADSAFE)) + OSUninterruptibleSpinLock_Release(&heap->spinlock); + + OSUninterruptibleSpinLock_Release(&gHeapGlobalLock); + } + + std::array<MEMHeapBase*, 9> sHeapBaseHandle{}; + + MEMHeapBase* MEMGetBaseHeapHandle(uint32 index) + { + if (index >= sHeapBaseHandle.size()) + return nullptr; + return sHeapBaseHandle[index]; + } + + MEMHeapBase* MEMSetBaseHeapHandle(uint32 index, MEMHeapBase* heapBase) + { + if (index >= sHeapBaseHandle.size()) + return nullptr; + if (sHeapBaseHandle[index] != nullptr) + { + cemuLog_log(LogType::Force, "MEMSetBaseHeapHandle(): Trying to assign heap to non-empty slot"); + return sHeapBaseHandle[index]; + } + sHeapBaseHandle[index] = heapBase; + return nullptr; + } + + uint32 MEMSetFillValForHeap(HEAP_FILL_TYPE type, uint32 value) + { + uint32 idx = (uint32)type; + cemu_assert(idx < gHeapFillValues.size()); + OSUninterruptibleSpinLock_Acquire(&gHeapGlobalLock); + const uint32 oldValue = gHeapFillValues[idx]; + gHeapFillValues[idx] = value; + OSUninterruptibleSpinLock_Release(&gHeapGlobalLock); + return oldValue; + } + + uint32 MEMGetFillValForHeap(HEAP_FILL_TYPE type) + { + uint32 idx = (uint32)type; + cemu_assert(idx < gHeapFillValues.size()); + OSUninterruptibleSpinLock_Acquire(&gHeapGlobalLock); + const uint32 value = gHeapFillValues[idx]; + OSUninterruptibleSpinLock_Release(&gHeapGlobalLock); + return value; + } + + MEMHeapBase* MEMFindContainHeap(const void* memBlock) + { + MEMPTR<void> memBound; + uint32be memBoundSize; + OSGetMemBound(1, (MPTR*)memBound.GetBEPtr(), (uint32*)&memBoundSize); + + MEMPTR<void> bucket; + uint32be bucketSize; + coreinit::OSGetForegroundBucket(&bucket, &bucketSize); + + OSUninterruptibleSpinLock_Acquire(&gHeapGlobalLock); + + MEMHeapBase* result; + if ((uintptr_t)memBound.GetPtr() > (uintptr_t)memBlock || (uintptr_t)memBlock >= (uintptr_t)memBound.GetPtr() + (uint32)memBoundSize) + { + if ((uintptr_t)bucket.GetPtr() > (uintptr_t)memBlock || (uintptr_t)memBlock >= (uintptr_t)bucket.GetPtr() + (uint32)bucketSize) + { + result = _MEMList_FindContainingHeap(&g_list1, (MEMHeapBase*)memBlock); + } + else + { + if (coreinit::OSGetForegroundBucket(nullptr, nullptr) == 0) + { + cemu_assert_unimplemented(); // foreground required + result = nullptr; + } + else + { + result = _MEMList_FindContainingHeap(&g_list3, (MEMHeapBase*)memBlock); + } + } + } + else + { + if (coreinit::OSGetForegroundBucket(nullptr, nullptr) == 0) + { + cemu_assert_unimplemented(); // foreground required + result = nullptr; + } + else + { + result = _MEMList_FindContainingHeap(&g_list2, (MEMHeapBase*)memBlock); + } + } + + OSUninterruptibleSpinLock_Release(&gHeapGlobalLock); + + return result; + } + + void* MEMCreateUserHeapHandle(void* heapAddress, uint32 heapSize) + { + MEMInitHeapBase((MEMHeapBase*)heapAddress, MEMHeapMagic::USER_HEAP, (uint8*)heapAddress + 0x40, (uint8*)heapAddress + heapSize, 0); + return heapAddress; + } + + /* MEM Heap list */ + +#define GET_MEM_LINK(__obj__,__offset__) ((MEMLink*)((uintptr_t)__obj__ + (uint16)__offset__)) + + void* MEMGetFirstListObject(MEMList* list) + { + return MEMGetNextListObject(list, nullptr); + } + + void MEMList_SetSingleObject(MEMList* list, void* object) + { + cemu_assert_debug(list != nullptr); + cemu_assert_debug(object != nullptr); + + MEMLink* link = GET_MEM_LINK(object, list->offset); + link->prev = nullptr; + link->next = nullptr; + + list->numObjects = list->numObjects + 1; + list->head = object; + list->tail = object; + } + + void MEMInitList(MEMList* list, uint32 offset) + { + cemu_assert_debug(list != nullptr); + cemu_assert(offset <= 0xFFFF); + memset(list, 0x00, sizeof(MEMLink)); + list->offset = (uint16)offset; + } + + void MEMAppendListObject(MEMList* list, void* object) + { + cemu_assert_debug(list != nullptr); + cemu_assert_debug(object != nullptr); + + if (list->head) + { + list->numObjects = list->numObjects + 1; + + MEMLink* link = GET_MEM_LINK(object, list->offset); + link->prev = list->tail; + link->next = nullptr; + + MEMLink* tailLink = GET_MEM_LINK(list->tail.GetPtr(), list->offset); + tailLink->next = object; + + list->tail = object; + } + else + MEMList_SetSingleObject(list, object); + } + + void MEMPrependListObject(MEMList* list, void* object) + { + cemu_assert_debug(list != nullptr); + cemu_assert_debug(object != nullptr); + + MEMPTR<void> headObject = list->head; + if (headObject) + { + list->numObjects = list->numObjects + 1; + + MEMLink* link = GET_MEM_LINK(object, list->offset); + link->prev = nullptr; + link->next = headObject; + + MEMLink* headLink = GET_MEM_LINK(headObject.GetPtr(), list->offset); + headLink->prev = object; + + list->head = object; + } + else + MEMList_SetSingleObject(list, object); + } + + void MEMRemoveListObject(MEMList* list, void* object) + { + cemu_assert_debug(list != nullptr); + cemu_assert_debug(object != nullptr); + + MEMLink* link = GET_MEM_LINK(object, list->offset); + + MEMPTR<void> prevObject = link->prev; + if (prevObject) + { + MEMLink* prevLink = GET_MEM_LINK(prevObject.GetPtr(), list->offset); + prevLink->next = link->next; + } + else + list->head = link->next; + + MEMPTR<void> nextObject = link->next; + if (nextObject) + { + MEMLink* nextLink = GET_MEM_LINK(nextObject.GetPtr(), list->offset); + nextLink->prev = prevObject; + } + else + list->tail = prevObject; + + link->prev = nullptr; + link->next = nullptr; + + list->numObjects = list->numObjects - 1; + } + + void* MEMGetNextListObject(MEMList* list, void* object) + { + cemu_assert_debug(list != nullptr); + + if (!object) + return list->head.GetPtr(); + + MEMLink* result = GET_MEM_LINK(object, list->offset); + return result->next.GetPtr(); + } + + void* MEMGetPrevListObject(MEMList* list, void* object) + { + cemu_assert_debug(list != nullptr); + + if (!object) + return list->tail.GetPtr(); + + MEMLink* result = GET_MEM_LINK(object, list->offset); + return result->prev.GetPtr(); + } + + void* MEMGetNthListObject(MEMList* list, uint32 index) + { + cemu_assert_debug(list != nullptr); + + void* result = MEMGetNextListObject(list, nullptr); + for (uint32 i = 0; i != index; ++i) + { + result = MEMGetNextListObject(list, result); + } + + return result; + } + + /* Default allocators */ + + MEMHeapBase* MEMDefaultHeap_Init(void* mem, uint32 size) + { + MEMHeapBase* heap = MEMCreateExpHeapEx(mem, size, 4); + gDefaultHeap = heap; + cemu_assert_debug(heap); + return heap; + } + + void* default_MEMAllocFromDefaultHeap(uint32 size) + { + void* mem = MEMAllocFromExpHeapEx(gDefaultHeap, size, 0x40); + coreinitMemLog_printf("MEMAllocFromDefaultHeap(0x%08x) Result: 0x%08x", size, memory_getVirtualOffsetFromPointer(mem)); + return mem; + } + + void export_default_MEMAllocFromDefaultHeap(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(size, 0); + MEMPTR<void> mem = default_MEMAllocFromDefaultHeap(size); + osLib_returnFromFunction(hCPU, mem.GetMPTR()); + } + + void* default_MEMAllocFromDefaultHeapEx(uint32 size, sint32 alignment) + { + void* mem = MEMAllocFromExpHeapEx(gDefaultHeap, size, alignment); + coreinitMemLog_printf("MEMAllocFromDefaultHeap(0x%08x,%d) Result: 0x%08x", size, alignment, memory_getVirtualOffsetFromPointer(mem)); + return mem; + } + + void export_default_MEMAllocFromDefaultHeapEx(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(size, 0); + ppcDefineParamS32(alignment, 1); + MEMPTR<void> mem = default_MEMAllocFromDefaultHeapEx(size, alignment); + osLib_returnFromFunction(hCPU, mem.GetMPTR()); + } + + void default_MEMFreeToDefaultHeap(void* mem) + { + MEMFreeToExpHeap(gDefaultHeap, mem); + } + + void export_default_MEMFreeToDefaultHeap(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(mem, void, 0); + default_MEMFreeToDefaultHeap(mem.GetPtr()); + osLib_returnFromFunction(hCPU, 0); + } + + void default_DynLoadAlloc(PPCInterpreter_t* hCPU) + { + ppcDefineParamS32(size, 0); + ppcDefineParamS32(alignment, 1); + ppcDefineParamMEMPTR(memResultPtr, uint32be, 2); + MPTR mem = PPCCoreCallback(gCoreinitData->MEMAllocFromDefaultHeapEx.GetMPTR(), size, alignment); + *memResultPtr = mem; + osLib_returnFromFunction(hCPU, 0); + } + + void default_DynLoadFree(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(mem, void, 0); + PPCCoreCallback(gCoreinitData->MEMFreeToDefaultHeap.GetMPTR(), mem); + osLib_returnFromFunction(hCPU, 0); + } + + void* _weak_MEMAllocFromDefaultHeapEx(uint32 size, sint32 alignment) + { + MEMPTR<void> r{ PPCCoreCallback(gCoreinitData->MEMAllocFromDefaultHeapEx.GetMPTR(), size, alignment) }; + return r.GetPtr(); + } + + void* _weak_MEMAllocFromDefaultHeap(uint32 size) + { + MEMPTR<void> r{ PPCCoreCallback(gCoreinitData->MEMAllocFromDefaultHeap.GetMPTR(), size) }; + return r.GetPtr(); + } + + void _weak_MEMFreeToDefaultHeap(void* ptr) + { + PPCCoreCallback(gCoreinitData->MEMFreeToDefaultHeap.GetMPTR(), ptr); + } + + void coreinitExport_MEMAllocFromAllocator(PPCInterpreter_t* hCPU) + { + debug_printf("MEMAllocFromAllocator(0x%x, 0x%x)\n", hCPU->gpr[3], hCPU->gpr[4]); + MEMAllocator* memAllocator = (MEMAllocator*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); + // redirect execution to allocator alloc callback + hCPU->instructionPointer = memAllocator->func->funcAlloc.GetMPTR(); + } + + void coreinitExport_MEMFreeToAllocator(PPCInterpreter_t* hCPU) + { + debug_printf("MEMFreeToAllocator(0x%x, 0x%08x)\n", hCPU->gpr[3], hCPU->gpr[4]); + MEMAllocator* memAllocator = (MEMAllocator*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); + // redirect execution to allocator free callback + hCPU->instructionPointer = memAllocator->func->funcFree.GetMPTR(); + } + + void _DefaultHeapAllocator_Alloc(PPCInterpreter_t* hCPU) + { + hCPU->gpr[3] = hCPU->gpr[4]; + hCPU->instructionPointer = gCoreinitData->MEMAllocFromDefaultHeap.GetMPTR(); + } + + void _DefaultHeapAllocator_Free(PPCInterpreter_t* hCPU) + { + hCPU->gpr[3] = hCPU->gpr[4]; + hCPU->instructionPointer = gCoreinitData->MEMFreeToDefaultHeap.GetMPTR(); + } + + SysAllocator<MEMAllocatorFunc> gDefaultHeapAllocator; + + void coreinitExport_MEMInitAllocatorForDefaultHeap(PPCInterpreter_t* hCPU) + { + debug_printf("MEMInitAllocatorForDefaultHeap(0x%08x)", hCPU->gpr[3]); + ppcDefineParamStructPtr(memAllocator, MEMAllocator, 0); + + gDefaultHeapAllocator->funcAlloc = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(_DefaultHeapAllocator_Alloc)); + gDefaultHeapAllocator->funcFree = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(_DefaultHeapAllocator_Free)); + + memAllocator->func = gDefaultHeapAllocator.GetPtr(); + memAllocator->heap = MEMPTR<void>(MEMGetBaseHeapHandle(1)); + memAllocator->param1 = 0; + memAllocator->param2 = 0; + + osLib_returnFromFunction(hCPU, 0); + } + + void InitDefaultHeaps(MEMPTR<MEMHeapBase>& mem1Heap, MEMPTR<MEMHeapBase>& memFGHeap, MEMPTR<MEMHeapBase>& mem2Heap) + { + mem1Heap = nullptr; + memFGHeap = nullptr; + mem2Heap = nullptr; + + gCoreinitData->MEMAllocFromDefaultHeap = RPLLoader_MakePPCCallable(export_default_MEMAllocFromDefaultHeap); + gCoreinitData->MEMAllocFromDefaultHeapEx = RPLLoader_MakePPCCallable(export_default_MEMAllocFromDefaultHeapEx); + gCoreinitData->MEMFreeToDefaultHeap = RPLLoader_MakePPCCallable(export_default_MEMFreeToDefaultHeap); + + if (OSGetForegroundBucket(nullptr, nullptr)) + { + MEMPTR<void> memBound; + uint32be memBoundSize; + OSGetMemBound(1, (MPTR*)memBound.GetBEPtr(), (uint32*)&memBoundSize); + mem1Heap = MEMCreateFrmHeapEx(memBound.GetPtr(), (uint32)memBoundSize, 0); + + OSGetForegroundBucketFreeArea((MPTR*)memBound.GetBEPtr(), (MPTR*)&memBoundSize); + memFGHeap = MEMCreateFrmHeapEx(memBound.GetPtr(), (uint32)memBoundSize, 0); + } + + MEMPTR<void> memBound; + uint32be memBoundSize; + OSGetMemBound(2, (MPTR*)memBound.GetBEPtr(), (uint32*)&memBoundSize); + mem2Heap = MEMDefaultHeap_Init(memBound.GetPtr(), (uint32)memBoundSize); + + // set DynLoad allocators + OSDynLoad_SetAllocator(RPLLoader_MakePPCCallable(default_DynLoadAlloc), RPLLoader_MakePPCCallable(default_DynLoadFree)); + OSDynLoad_SetTLSAllocator(RPLLoader_MakePPCCallable(default_DynLoadAlloc), RPLLoader_MakePPCCallable(default_DynLoadFree)); + } + + void CoreInitDefaultHeap(/* ukn */) + { + cemu_assert_unimplemented(); + } + + void InitializeMEM() + { + for (auto& it : sHeapBaseHandle) + it = nullptr; + + cafeExportRegister("coreinit", CoreInitDefaultHeap, LogType::CoreinitMem); + + osLib_addFunction("coreinit", "MEMInitAllocatorForDefaultHeap", coreinitExport_MEMInitAllocatorForDefaultHeap); + osLib_addFunction("coreinit", "MEMAllocFromAllocator", coreinitExport_MEMAllocFromAllocator); + osLib_addFunction("coreinit", "MEMFreeToAllocator", coreinitExport_MEMFreeToAllocator); + + cafeExportRegister("coreinit", MEMGetBaseHeapHandle, LogType::CoreinitMem); + cafeExportRegister("coreinit", MEMSetBaseHeapHandle, LogType::CoreinitMem); + + cafeExportRegister("coreinit", MEMFindContainHeap, LogType::CoreinitMem); + + cafeExportRegister("coreinit", MEMGetFillValForHeap, LogType::CoreinitMem); + cafeExportRegister("coreinit", MEMSetFillValForHeap, LogType::CoreinitMem); + + cafeExportRegister("coreinit", MEMCreateUserHeapHandle, LogType::CoreinitMem); + + cafeExportRegister("coreinit", MEMInitList, LogType::CoreinitMem); + cafeExportRegister("coreinit", MEMPrependListObject, LogType::CoreinitMem); + cafeExportRegister("coreinit", MEMAppendListObject, LogType::CoreinitMem); + cafeExportRegister("coreinit", MEMRemoveListObject, LogType::CoreinitMem); + cafeExportRegister("coreinit", MEMGetNextListObject, LogType::CoreinitMem); + cafeExportRegister("coreinit", MEMGetNthListObject, LogType::CoreinitMem); + cafeExportRegister("coreinit", MEMGetPrevListObject, LogType::CoreinitMem); + } + +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_MEM.h b/src/Cafe/OS/libs/coreinit/coreinit_MEM.h new file mode 100644 index 00000000..7e583195 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_MEM.h @@ -0,0 +1,183 @@ +#pragma once + +#include "Cafe/OS/libs/coreinit/coreinit_Spinlock.h" + +struct MEMLink_t +{ + MPTR prevObject; + MPTR nextObject; +}; + +static_assert(sizeof(MEMLink_t) == 8); + +struct MEMList_t +{ + MPTR headObject; + MPTR tailObject; + uint16 numObjects; + uint16 offset; +}; + +static_assert(sizeof(MEMList_t) == 0xC); + +struct MEMAllocatorFunc +{ + MEMPTR<void> funcAlloc; + MEMPTR<void> funcFree; +}; + +static_assert(sizeof(MEMAllocatorFunc) == 8); + +struct MEMAllocator +{ + /* +0x000 */ MEMPTR<MEMAllocatorFunc> func; + /* +0x004 */ MEMPTR<void> heap; + /* +0x00C */ uint32be param1; + /* +0x010 */ uint32be param2; +}; + +static_assert(sizeof(MEMAllocator) == 0x10); + +MPTR coreinit_allocFromSysArea(uint32 size, uint32 alignment); +void coreinit_freeToSysArea(MPTR mem); + +// mem exports +void coreinitExport_MEMInitAllocatorForDefaultHeap(PPCInterpreter_t* hCPU); +void coreinitExport_MEMGetBaseHeapHandle(PPCInterpreter_t* hCPU); + +/* legacy stuff above */ + +namespace coreinit +{ +#define MEM_HEAP_INVALID_HANDLE (nullptr) +#define MEM_HEAP_DEFAULT_ALIGNMENT 4 +#define MIN_ALIGNMENT 4 +#define MIN_ALIGNMENT_MINUS_ONE (MIN_ALIGNMENT-1) + +#define MEM_HEAP_OPTION_NONE (0) +#define MEM_HEAP_OPTION_CLEAR (1 << 0) +#define MEM_HEAP_OPTION_FILL (1 << 1) +#define MEM_HEAP_OPTION_THREADSAFE (1 << 2) + + enum class MEMHeapMagic : uint32 + { + UNIT_HEAP = 'UNTH', + BLOCK_HEAP = 'BLKH', + FRAME_HEAP = 'FRMH', + EXP_HEAP = 'EXPH', + USER_HEAP = 'USRH', + }; + + struct MEMLink + { + MEMPTR<void> prev; + MEMPTR<void> next; + }; + static_assert(sizeof(MEMLink) == 0x8); + + struct MEMList + { + /* 0x00 */ MEMPTR<void> head; + /* 0x04 */ MEMPTR<void> tail; + /* 0x08 */ uint16be numObjects; + /* 0x0A */ uint16be offset; + }; + static_assert(sizeof(MEMList) == 0xC); + + void MEMInitList(MEMList* list, uint32 offset); + void MEMAppendListObject(MEMList* list, void* object); + void MEMRemoveListObject(MEMList* list, void* object); + + void* MEMGetFirstListObject(MEMList* list); + void* MEMGetNextListObject(MEMList* list, void* object); + + struct MEMHeapBase + { + /* +0x00 */ betype<MEMHeapMagic> magic; + /* +0x04 */ MEMLink link; + /* +0x0C */ MEMList childList; + /* +0x18 */ MEMPTR<void> heapStart; + /* +0x1C */ MEMPTR<void> heapEnd; // heap end + 1 + /* +0x20 */ OSSpinLock spinlock; + /* +0x30 */ uint8 _ukn[3]; + /* +0x33 */ uint8 flags; + + void AcquireLock() + { + if (flags & MEM_HEAP_OPTION_THREADSAFE) + OSUninterruptibleSpinLock_Acquire(&spinlock); + } + + void ReleaseLock() + { + if (flags & MEM_HEAP_OPTION_THREADSAFE) + OSUninterruptibleSpinLock_Release(&spinlock); + } + + // if set, memset allocations to zero + bool HasOptionClear() const + { + return (flags & MEM_HEAP_OPTION_CLEAR) != 0; + } + + // if set, memset allocations/releases to specific fill values + bool HasOptionFill() const + { + return (flags & MEM_HEAP_OPTION_FILL) != 0; + } + }; + + static_assert(offsetof(MEMHeapBase, childList) == 0xC); + static_assert(offsetof(MEMHeapBase, spinlock) == 0x20); + static_assert(offsetof(MEMHeapBase, flags) == 0x33); + static_assert(sizeof(MEMHeapBase) == 0x34); // heap base is actually 0x40 but bytes 0x34 to 0x40 are padding? + + typedef MEMHeapBase* MEMHeapHandle; + + /* Heap base */ + + void MEMInitHeapBase(MEMHeapBase* heap, MEMHeapMagic magic, void* heapStart, void* heapEnd, uint32 createFlags); + void MEMBaseDestroyHeap(MEMHeapBase* heap); + + MEMHeapBase* MEMGetBaseHeapHandle(uint32 index); + MEMHeapBase* MEMSetBaseHeapHandle(uint32 index, MEMHeapBase* heapBase); + + /* Heap list */ + + bool MEMHeapTable_Add(MEMHeapBase* heap); + bool MEMHeapTable_Remove(MEMHeapBase* heap); + MEMHeapBase* _MEMList_FindContainingHeap(MEMList* list, MEMHeapBase* heap); + bool MEMList_ContainsHeap(MEMList* list, MEMHeapBase* heap); + MEMList* MEMList_FindContainingHeap(MEMHeapBase* head); + + /* Heap settings */ + + enum class HEAP_FILL_TYPE : uint32 + { + ON_HEAP_CREATE = 0, + ON_ALLOC = 1, + ON_FREE = 2, + }; + + uint32 MEMGetFillValForHeap(HEAP_FILL_TYPE type); + uint32 MEMSetFillValForHeap(HEAP_FILL_TYPE type, uint32 value); + MEMHeapHandle MEMFindContainHeap(const void* memBlock); + + /* Heap default allocators */ + + void InitDefaultHeaps(MEMPTR<MEMHeapBase>& mem1Heap, MEMPTR<MEMHeapBase>& memFGHeap, MEMPTR<MEMHeapBase>& mem2Heap); + + void* default_MEMAllocFromDefaultHeap(uint32 size); + void* default_MEMAllocFromDefaultHeapEx(uint32 size, sint32 alignment); + void default_MEMFreeToDefaultHeap(void* mem); + + void* _weak_MEMAllocFromDefaultHeapEx(uint32 size, sint32 alignment); + void* _weak_MEMAllocFromDefaultHeap(uint32 size); + void _weak_MEMFreeToDefaultHeap(void* ptr); + + /* Unit heap */ + + void InitializeMEMUnitHeap(); + + void InitializeMEM(); +} diff --git a/src/Cafe/OS/libs/coreinit/coreinit_MEM_BlockHeap.cpp b/src/Cafe/OS/libs/coreinit/coreinit_MEM_BlockHeap.cpp new file mode 100644 index 00000000..1738677e --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_MEM_BlockHeap.cpp @@ -0,0 +1,596 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/libs/coreinit/coreinit_MEM.h" +#include "Cafe/OS/libs/coreinit/coreinit_MEM_BlockHeap.h" + +namespace coreinit +{ + MEMHeapHandle MEMInitBlockHeap(MEMBlockHeap2_t* memStart, void* startAddr, void* endAddr, void* initTrackMem, uint32 initTrackMemSize, uint32 createFlags) + { + if (memStart == nullptr || startAddr == nullptr || endAddr == nullptr || (uintptr_t)startAddr >= (uintptr_t)endAddr) + return nullptr; + + if (initTrackMemSize == 0) + initTrackMem = nullptr; + else if (initTrackMem == nullptr) + initTrackMemSize = 0; + + MEMInitHeapBase(memStart, MEMHeapMagic::BLOCK_HEAP, startAddr, endAddr, createFlags); + + memStart->track.addrStart = startAddr; + memStart->track.addrEnd = (void*)((uintptr_t)endAddr - 1); + memStart->track.isFree = 1; + memStart->track.previousBlock = nullptr; + memStart->track.nextBlock = nullptr; + + memStart->headBlock = &memStart->track; + memStart->tailBlock = &memStart->track; + memStart->nextFreeBlock = nullptr; + memStart->freeBlocksLeft = 0; + + uint8 flags = memStart->flags; + if(HAS_FLAG(flags, MEM_HEAP_OPTION_FILL)) + { + cemu_assert_unimplemented(); + } + + if(initTrackMemSize != 0) + { + sint32 result = MEMAddBlockHeapTracking(MEMPTR<void>(memStart).GetMPTR(), MEMPTR<void>(initTrackMem).GetMPTR(), initTrackMemSize); + if(result != 0) + { + MEMBaseDestroyHeap(memStart); + return nullptr; + } + } + + MEMHeapTable_Add(memStart); + return memStart; + } + + void* MEMDestroyBlockHeap(MEMHeapHandle hHeap) + { + if (!hHeap) + return nullptr; + cemu_assert_debug(hHeap->magic == MEMHeapMagic::BLOCK_HEAP); + MEMBaseDestroyHeap(hHeap); + MEMHeapTable_Remove(hHeap); + memset(hHeap, 0x00, sizeof(MEMBlockHeap2_t)); + return hHeap; + } + + sint32 MEMGetAllocatableSizeForBlockHeapEx(MEMBlockHeap2_t* blockHeap, sint32 alignment) + { + if (!blockHeap || blockHeap->magic != MEMHeapMagic::BLOCK_HEAP) + { + cemuLog_log(LogType::Force, "MEMGetAllocatableSizeForBlockHeapEx(): Not a valid block heap"); + return 0; + } + if (alignment < 0) + alignment = -alignment; + else if (alignment == 0) + alignment = 4; + + if (HAS_FLAG(blockHeap->flags, MEM_HEAP_OPTION_THREADSAFE)) + OSUninterruptibleSpinLock_Acquire(&blockHeap->spinlock); + + MEMBlockHeapTrack2_t* track = (MEMBlockHeapTrack2_t*)blockHeap->headBlock.GetPtr(); + uint32 allocatableSize = 0; + while (track) + { + if (track->isFree != 0) + { + uint32 addrStart = track->addrStart.GetMPTR(); + uint32 addrEnd = track->addrEnd.GetMPTR(); + uint32 alignmentMinusOne = alignment - 1; + uint32 alignedAddrStart = ((addrStart + alignmentMinusOne) / alignment) * alignment; + if (alignedAddrStart <= addrEnd) + { + uint32 size = (addrEnd + 1) - alignedAddrStart; + allocatableSize = std::max(allocatableSize, size); + } + } + // next + track = (MEMBlockHeapTrack2_t*)track->nextBlock.GetPtr(); + } + + if (HAS_FLAG(blockHeap->flags, MEM_HEAP_OPTION_THREADSAFE)) + OSUninterruptibleSpinLock_Release(&blockHeap->spinlock); + + return allocatableSize; + } + + void MEMDumpBlockHeap(MPTR heap); + + typedef struct + { + /* +0x00 */ uint32 addrStart; + /* +0x04 */ uint32 addrEnd; + /* +0x08 */ uint32 isFree; // if 0 -> block is used + /* +0x0C */ MPTR previousBlock; + /* +0x10 */ MPTR nextBlock; + }MEMBlockHeapTrackDEPR; + + typedef struct + { + /* +0x00 */ uint32 ukn00; + /* +0x04 */ uint32 ukn04; + /* +0x08 */ uint32 trackArray; + /* +0x0C */ uint32 trackCount; + }MEMBlockHeapGroupDEPR; + + typedef struct + { + /* +0x000 */ betype<MEMHeapMagic> magic; + /* +0x004 */ MEMLink_t link; + /* +0x00C */ MEMList_t childList; + /* +0x018 */ MPTR heapStart; + /* +0x01C */ MPTR heapEnd; + /* +0x020 */ coreinit::OSSpinLock spinlock; + /* +0x030 */ + union + { + uint32 val; + uint32 flags; + } + attribute; + // start of block heap header + /* +0x034 */ uint8 ukn034[0x50 - 0x34]; // ? + /* +0x050 */ MEMBlockHeapTrackDEPR nullBlockTrack; + /* +0x064 */ MPTR headBlock; + /* +0x068 */ MPTR tailBlock; + /* +0x06C */ MPTR nextFreeBlock; + /* +0x070 */ uint32 freeBlocksLeft; + }MEMBlockHeapDEPR; + + void _blockHeapDebugVerifyIfBlockIsLinked(MEMBlockHeapDEPR* blockHeapHead, MEMBlockHeapTrackDEPR* track) + { + //MEMBlockHeapTrack_t* trackItr = (MEMBlockHeapTrack_t*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(blockHeapHead->firstBlock)); + //MEMBlockHeapTrack_t* prevHistory[4]; + //while( trackItr ) + //{ + // if( trackItr == track ) + // { + // debug_printf("PrevBlock3 %08x\n", memory_getVirtualOffsetFromPointer(prevHistory[0])); + // debug_printf("PrevBlock2 %08x\n", memory_getVirtualOffsetFromPointer(prevHistory[1])); + // debug_printf("PrevBlock1 %08x\n", memory_getVirtualOffsetFromPointer(prevHistory[2])); + // debug_printf("PrevBlock0 %08x\n", memory_getVirtualOffsetFromPointer(prevHistory[3])); + // assert_dbg(); + // } + // prevHistory[3] = prevHistory[2]; + // prevHistory[2] = prevHistory[1]; + // prevHistory[1] = prevHistory[0]; + // prevHistory[0] = trackItr; + // // next + // trackItr = (MEMBlockHeapTrack_t*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(trackItr->nextBlock)); + //} + } + + void _blockHeapDebugVerifyLinkOrder(MEMBlockHeapDEPR* blockHeapHead) + { + //MEMBlockHeapTrack_t* trackItr = (MEMBlockHeapTrack_t*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(blockHeapHead->firstBlock)); + //MEMBlockHeapTrack_t* prev = NULL; + //while( trackItr ) + //{ + // MPTR prevMPTR = memory_getVirtualOffsetFromPointer(prev); + // if( _swapEndianU32(trackItr->previousBlock) != prevMPTR ) + // assert_dbg(); + // // next + // prev = trackItr; + // trackItr = (MEMBlockHeapTrack_t*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(trackItr->nextBlock)); + //} + } + + sint32 MEMAddBlockHeapTracking(MPTR heap, MPTR trackMem, uint32 trackMemSize) + { + MEMBlockHeapDEPR* blockHeapHead = (MEMBlockHeapDEPR*)memory_getPointerFromVirtualOffset(heap); + if (blockHeapHead->magic != MEMHeapMagic::BLOCK_HEAP) + { + return 0; + } + if (trackMem == MPTR_NULL || trackMemSize <= (sizeof(MEMBlockHeapGroupDEPR) + sizeof(MEMBlockHeapTrackDEPR))) + { + return -4; + } + uint32 trackCount = (trackMemSize - sizeof(MEMBlockHeapGroupDEPR)) / sizeof(MEMBlockHeapTrackDEPR); + MEMBlockHeapGroupDEPR* group = (MEMBlockHeapGroupDEPR*)memory_getPointerFromVirtualOffset(trackMem); + group->ukn00 = _swapEndianU32(0); + group->ukn04 = _swapEndianU32(0); + group->trackArray = _swapEndianU32(trackMem + sizeof(MEMBlockHeapGroupDEPR)); + group->trackCount = _swapEndianU32(trackCount); + MEMBlockHeapTrackDEPR* track = (MEMBlockHeapTrackDEPR*)(group + 1); + track[0].previousBlock = _swapEndianU32(0); + if (trackCount > 1) + { + for (uint32 i = 0; i < trackCount - 1; i++) + { + track[i].nextBlock = _swapEndianU32(memory_getVirtualOffsetFromPointer(track + i + 1)); + track[i + 1].previousBlock = _swapEndianU32(0); + } + } + + // todo: Use spinlock from heap (and only use it if threadsafe heap flag is set) + __OSLockScheduler(); + track[trackCount - 1].nextBlock = blockHeapHead->nextFreeBlock; + blockHeapHead->nextFreeBlock = _swapEndianU32(memory_getVirtualOffsetFromPointer(track)); + blockHeapHead->freeBlocksLeft = _swapEndianU32(_swapEndianU32(blockHeapHead->freeBlocksLeft) + trackCount); + __OSUnlockScheduler(); + return 0; + } + + uint32 MEMGetTrackingLeftInBlockHeap(MPTR heap) + { + MEMBlockHeapDEPR* blockHeapHead = (MEMBlockHeapDEPR*)memory_getPointerFromVirtualOffset(heap); + if (blockHeapHead->magic != MEMHeapMagic::BLOCK_HEAP) + { + return 0; + } + return _swapEndianU32(blockHeapHead->freeBlocksLeft); + } + + MPTR _MEMBlockHeap_GetFreeBlockTrack(MEMBlockHeapDEPR* blockHeapHead) + { + MPTR trackMPTR = _swapEndianU32(blockHeapHead->nextFreeBlock); + MEMBlockHeapTrackDEPR* track = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffset(trackMPTR); + MPTR nextFreeBlockMPTR = _swapEndianU32(track->nextBlock); // for unreserved tracks, nextBlock holds the next unreserved block + track->nextBlock = _swapEndianU32(MPTR_NULL); + blockHeapHead->nextFreeBlock = _swapEndianU32(nextFreeBlockMPTR); + if (_swapEndianU32(blockHeapHead->freeBlocksLeft) == 0) + { + forceLog_printf("BlockHeap: No free blocks left\n"); + assert_dbg(); + } + blockHeapHead->freeBlocksLeft = _swapEndianU32(_swapEndianU32(blockHeapHead->freeBlocksLeft) - 1); + return trackMPTR; + } + + /* + * Release MEMBlockHeapTrack_t struct for block heap + */ + void _MEMBlockHeap_ReleaseBlockTrack(MEMBlockHeapDEPR* blockHeapHead, MPTR trackMPTR) + { + MEMBlockHeapTrackDEPR* track = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffset(trackMPTR); + track->nextBlock = blockHeapHead->nextFreeBlock; + blockHeapHead->nextFreeBlock = _swapEndianU32(trackMPTR); + blockHeapHead->freeBlocksLeft = _swapEndianU32(_swapEndianU32(blockHeapHead->freeBlocksLeft) + 1); + } + + sint32 _MEMBlockHeap_AllocAtBlock(MEMBlockHeapDEPR* blockHeapHead, MEMBlockHeapTrackDEPR* track, MPTR allocationAddress, uint32 size) + { + MPTR trackMPTR = memory_getVirtualOffsetFromPointer(track); + uint32 trackEndAddr = _swapEndianU32(track->addrEnd); + uint32 prefixBlockSize = allocationAddress - _swapEndianU32(track->addrStart); + uint32 suffixBlockSize = (_swapEndianU32(track->addrEnd) + 1) - (allocationAddress + size); + if (prefixBlockSize > 0 && suffixBlockSize > 0) + { + // requires two free blocks + if (_swapEndianU32(blockHeapHead->freeBlocksLeft) < 2) + return -1; + } + else if (prefixBlockSize > 0 || suffixBlockSize > 0) + { + // requires one free block + if (_swapEndianU32(blockHeapHead->freeBlocksLeft) < 1) + return -2; + } + MPTR currentPreviousTrack = _swapEndianU32(track->previousBlock); + // remove used block from chain of free blocks (debug) + if (track->isFree != _swapEndianU32(0)) + { + // check if block is in list of free blocks (it shouldnt be) + MEMBlockHeapTrackDEPR* trackItr = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(blockHeapHead->nextFreeBlock)); + while (trackItr) + { + if (trackItr == track) + assert_dbg(); + // next + trackItr = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(trackItr->nextBlock)); + } + } + _blockHeapDebugVerifyLinkOrder(blockHeapHead); + // create prefix block + if (prefixBlockSize > 0) + { + uint32 blockRangeStart = _swapEndianU32(track->addrStart); + uint32 blockRangeEnd = allocationAddress - 1; + MPTR prefixTrackMPTR = _MEMBlockHeap_GetFreeBlockTrack(blockHeapHead); + MEMBlockHeapTrackDEPR* prefixTrack = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffset(prefixTrackMPTR); + prefixTrack->isFree = _swapEndianU32(1); + prefixTrack->addrStart = _swapEndianU32(blockRangeStart); + prefixTrack->addrEnd = _swapEndianU32(blockRangeEnd); + // register new firstBlock + if (blockHeapHead->headBlock == _swapEndianU32(trackMPTR)) + blockHeapHead->headBlock = _swapEndianU32(prefixTrackMPTR); + // update link in original previous block + if (_swapEndianU32(track->previousBlock) != MPTR_NULL) + { + MEMBlockHeapTrackDEPR* tempTrack = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffset(_swapEndianU32(track->previousBlock)); + tempTrack->nextBlock = _swapEndianU32(prefixTrackMPTR); + } + // update previous/next track link + prefixTrack->nextBlock = _swapEndianU32(trackMPTR); + prefixTrack->previousBlock = _swapEndianU32(currentPreviousTrack); + // set prefix track as current previous track + currentPreviousTrack = prefixTrackMPTR; + } + // update used block + track->isFree = _swapEndianU32(0); + track->addrStart = _swapEndianU32(allocationAddress); + track->addrEnd = _swapEndianU32(allocationAddress + size - 1); + track->previousBlock = _swapEndianU32(currentPreviousTrack); + currentPreviousTrack = trackMPTR; + // create suffix block + if (suffixBlockSize > 0) + { + uint32 blockRangeStart = allocationAddress + size; + uint32 blockRangeEnd = trackEndAddr; + // get suffix track + MPTR suffixTrackMPTR = _MEMBlockHeap_GetFreeBlockTrack(blockHeapHead); + MEMBlockHeapTrackDEPR* suffixTrack = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffset(suffixTrackMPTR); + suffixTrack->isFree = _swapEndianU32(1); + suffixTrack->addrStart = _swapEndianU32(blockRangeStart); + suffixTrack->addrEnd = _swapEndianU32(blockRangeEnd); + // update previous/next track link + suffixTrack->previousBlock = _swapEndianU32(currentPreviousTrack); + suffixTrack->nextBlock = track->nextBlock; + // update last block of heap + if (_swapEndianU32(blockHeapHead->tailBlock) == trackMPTR) + blockHeapHead->tailBlock = _swapEndianU32(suffixTrackMPTR); + // update link in original next block + if (_swapEndianU32(track->nextBlock) != MPTR_NULL) + { + MEMBlockHeapTrackDEPR* tempTrack = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffset(_swapEndianU32(track->nextBlock)); + tempTrack->previousBlock = _swapEndianU32(suffixTrackMPTR); + } + // update next block + track->nextBlock = _swapEndianU32(suffixTrackMPTR); + } + _blockHeapDebugVerifyLinkOrder(blockHeapHead); + // todo: Get fill value via MEMGetFillValForHeap + memset(memory_getPointerFromVirtualOffset(allocationAddress), 0x00, size); + return 0; + } + + MEMBlockHeapTrackDEPR* _MEMBlockHeap_FindBlockContaining(MEMBlockHeapDEPR* blockHeapHead, MPTR memAddr) + { + MPTR heapStart = _swapEndianU32(blockHeapHead->heapStart); + MPTR heapEnd = _swapEndianU32(blockHeapHead->heapEnd); + if (memAddr < heapStart) + return NULL; + if (memAddr > heapEnd) + return NULL; + uint32 distanceToStart = memAddr - heapStart; + uint32 distanceToEnd = heapEnd - memAddr; + // todo: If distanceToStart < distanceToEnd -> Iterate starting from firstBlock, else iterate starting from lastBlock (and continue via track->previousBlock) + MEMBlockHeapTrackDEPR* track = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(blockHeapHead->headBlock)); + if (track == NULL) + return NULL; + while (track) + { + if (_swapEndianU32(track->addrStart) == memAddr) + return track; + // next + // todo: Should this be ->previousBlock ? + track = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(track->nextBlock)); + } + return NULL; + } + + MPTR MEMAllocFromBlockHeapEx(MPTR heap, uint32 size, sint32 alignment) + { + MEMBlockHeapDEPR* blockHeapHead = (MEMBlockHeapDEPR*)memory_getPointerFromVirtualOffset(heap); + if (blockHeapHead->magic != MEMHeapMagic::BLOCK_HEAP) + { + return MPTR_NULL; + } + // find free block which can hold the data (including alignment) + __OSLockScheduler(); // todo: replace with spinlock from heap + if (alignment == 0) + alignment = 4; + bool allocateAtEndOfBlock = false; + if (alignment < 0) + { + allocateAtEndOfBlock = true; + alignment = -alignment; + } + MEMBlockHeapTrackDEPR* track; + if (allocateAtEndOfBlock) + track = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(blockHeapHead->tailBlock)); + else + track = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(blockHeapHead->headBlock)); + + cemu_assert_debug(__popcnt(alignment) == 1); // not a supported alignment value + while (track) + { + if (track->isFree != _swapEndianU32(0)) + { + uint32 blockRangeStart = _swapEndianU32(track->addrStart); + uint32 blockRangeEnd = _swapEndianU32(track->addrEnd) + 1; + if (allocateAtEndOfBlock == false) + { + // calculate remaining size with proper alignment + uint32 alignedBlockRangeStart = (blockRangeStart + alignment - 1) & ~(alignment - 1); + if (alignedBlockRangeStart < blockRangeEnd) + { + uint32 allocRange = blockRangeEnd - alignedBlockRangeStart; + if (allocRange >= size) + { + sint32 allocError = _MEMBlockHeap_AllocAtBlock(blockHeapHead, track, alignedBlockRangeStart, size); + _blockHeapDebugVerifyLinkOrder(blockHeapHead); + __OSUnlockScheduler(); + if (allocError == 0) + return alignedBlockRangeStart; + return MPTR_NULL; + } + } + } + else + { + uint32 alignedBlockRangeStart = (blockRangeEnd + 1 - size) & ~(alignment - 1); + if (alignedBlockRangeStart >= blockRangeStart) + { + sint32 allocError = _MEMBlockHeap_AllocAtBlock(blockHeapHead, track, alignedBlockRangeStart, size); + _blockHeapDebugVerifyLinkOrder(blockHeapHead); + __OSUnlockScheduler(); + if (allocError == 0) + return alignedBlockRangeStart; + return MPTR_NULL; + } + } + } + // next + if (allocateAtEndOfBlock) + track = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(track->previousBlock)); + else + track = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(track->nextBlock)); + if (track == nullptr) + break; + } + __OSUnlockScheduler(); + return MPTR_NULL; + } + + void MEMFreeToBlockHeap(MPTR heap, MPTR memAddr) + { + MEMBlockHeapDEPR* blockHeapHead = (MEMBlockHeapDEPR*)memory_getPointerFromVirtualOffset(heap); + if (blockHeapHead->magic != MEMHeapMagic::BLOCK_HEAP) + { + return; + } + __OSLockScheduler(); // todo: replace with spinlock from heap (if heap threadsafe flag is set) + _blockHeapDebugVerifyLinkOrder(blockHeapHead); + MEMBlockHeapTrackDEPR* block = _MEMBlockHeap_FindBlockContaining(blockHeapHead, memAddr); + if (block != NULL) + { + MPTR blockMPTR = memory_getVirtualOffsetFromPointer(block); +#ifndef PUBLIC_RELEASE + if (block->isFree != 0) + assert_dbg(); + _blockHeapDebugVerifyLinkOrder(blockHeapHead); +#endif + // mark current block as free + block->isFree = _swapEndianU32(1); + // attempt to merge with previous block + if (_swapEndianU32(block->previousBlock) != NULL) + { + MPTR previousBlockMPTR = _swapEndianU32(block->previousBlock); + MEMBlockHeapTrackDEPR* previousBlock = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffset(previousBlockMPTR); + if (_swapEndianU32(previousBlock->isFree) != 0) + { + // merge + previousBlock->addrEnd = block->addrEnd; + previousBlock->nextBlock = block->nextBlock; + if (_swapEndianU32(block->nextBlock) != MPTR_NULL) + { + MEMBlockHeapTrackDEPR* tempNextBlock = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffset(_swapEndianU32(block->nextBlock)); + tempNextBlock->previousBlock = _swapEndianU32(previousBlockMPTR); + } + // release removed block + _MEMBlockHeap_ReleaseBlockTrack(blockHeapHead, blockMPTR); + // debug + _blockHeapDebugVerifyIfBlockIsLinked(blockHeapHead, (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffset(blockMPTR)); + // merged block becomes the new current block + blockMPTR = previousBlockMPTR; + block = previousBlock; + } + } + // attempt to merge with next block + if (_swapEndianU32(block->nextBlock) != NULL) + { + MPTR nextBlockMPTR = _swapEndianU32(block->nextBlock); + MEMBlockHeapTrackDEPR* nextBlock = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffset(nextBlockMPTR); + if (_swapEndianU32(nextBlock->isFree) != 0) + { + // merge + block->addrEnd = nextBlock->addrEnd; + block->nextBlock = nextBlock->nextBlock; + if (_swapEndianU32(nextBlock->nextBlock) != MPTR_NULL) + { + MEMBlockHeapTrackDEPR* tempNextBlock = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffset(_swapEndianU32(nextBlock->nextBlock)); + tempNextBlock->previousBlock = _swapEndianU32(blockMPTR); + } + //// merged block becomes the new current block + //blockMPTR = previousBlockMPTR; + //block = previousBlock; + // update last block + if (_swapEndianU32(blockHeapHead->tailBlock) == nextBlockMPTR) + { + blockHeapHead->tailBlock = _swapEndianU32(blockMPTR); + } + // release removed block + _MEMBlockHeap_ReleaseBlockTrack(blockHeapHead, nextBlockMPTR); + // debug + _blockHeapDebugVerifyIfBlockIsLinked(blockHeapHead, (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffset(nextBlockMPTR)); + } + } + } + _blockHeapDebugVerifyLinkOrder(blockHeapHead); + __OSUnlockScheduler(); + } + + uint32 MEMGetTotalFreeSizeForBlockHeap(MEMBlockHeapDEPR* blockHeap) + { + if (!blockHeap || blockHeap->magic != MEMHeapMagic::BLOCK_HEAP) + { + cemu_assert_suspicious(); + return 0; + } + + __OSLockScheduler(); // todo: replace with spinlock from heap (if heap threadsafe flag is set) + uint32 totalSize = 0; + // sum up all free blocks + MPTR blockMPTR = _swapEndianU32(blockHeap->headBlock); + while (blockMPTR) + { + MEMBlockHeapTrackDEPR* track = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffset(blockMPTR); + if (track->isFree != _swapEndianU32(0)) + { + // get block size + uint32 blockSize = _swapEndianU32(track->addrEnd) - _swapEndianU32(track->addrStart) + 1; + // add to totalSize + totalSize += blockSize; + } + // next + blockMPTR = _swapEndianU32(track->nextBlock); + } + __OSUnlockScheduler(); // todo: replace with spinlock from heap (if heap threadsafe flag is set) + + return totalSize; + } + + void MEMDumpBlockHeap(MPTR heap) + { + MEMBlockHeapDEPR* blockHeapHead = (MEMBlockHeapDEPR*)memory_getPointerFromVirtualOffset(heap); + if (blockHeapHead->magic != MEMHeapMagic::BLOCK_HEAP) + { + cemu_assert_suspicious(); + return; + } + // iterate reserved/sized blocks + debug_printf("### MEMDumpBlockHeap ###\n"); + __OSLockScheduler(); // todo: replace with spinlock from heap + MEMBlockHeapTrackDEPR* track = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(blockHeapHead->headBlock)); + while (track) + { + uint32 blockRangeStart = _swapEndianU32(track->addrStart); + uint32 blockRangeEnd = _swapEndianU32(track->addrEnd) + 1; + debug_printf(" %08x %08x - %08x prev/next %08x %08x isFree: %d\n", memory_getVirtualOffsetFromPointer(track), blockRangeStart, blockRangeEnd, _swapEndianU32(track->previousBlock), _swapEndianU32(track->nextBlock), _swapEndianU32(track->isFree)); + // next + track = (MEMBlockHeapTrackDEPR*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(track->nextBlock)); + } + debug_printf("\n"); + __OSUnlockScheduler(); + } + + void InitializeMEMBlockHeap() + { + cafeExportRegister("coreinit", MEMInitBlockHeap, LogType::CoreinitMem); + cafeExportRegister("coreinit", MEMDestroyBlockHeap, LogType::CoreinitMem); + cafeExportRegister("coreinit", MEMGetAllocatableSizeForBlockHeapEx, LogType::CoreinitMem); + + cafeExportRegister("coreinit", MEMAddBlockHeapTracking, LogType::CoreinitMem); + cafeExportRegister("coreinit", MEMGetTrackingLeftInBlockHeap, LogType::CoreinitMem); + cafeExportRegister("coreinit", MEMAllocFromBlockHeapEx, LogType::CoreinitMem); + cafeExportRegister("coreinit", MEMFreeToBlockHeap, LogType::CoreinitMem); + cafeExportRegister("coreinit", MEMGetTotalFreeSizeForBlockHeap, LogType::CoreinitMem); + } +} diff --git a/src/Cafe/OS/libs/coreinit/coreinit_MEM_BlockHeap.h b/src/Cafe/OS/libs/coreinit/coreinit_MEM_BlockHeap.h new file mode 100644 index 00000000..5d4bc55d --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_MEM_BlockHeap.h @@ -0,0 +1,34 @@ +#pragma once +#include "Cafe/OS/libs/coreinit/coreinit_MEM.h" + +namespace coreinit +{ + struct MEMBlockHeapTrack2_t + { + /* +0x00 */ MEMPTR<void> addrStart; + /* +0x04 */ MEMPTR<void> addrEnd; + /* +0x08 */ uint32be isFree; // if 0 -> block is used + /* +0x0C */ MEMPTR<void> previousBlock; + /* +0x10 */ MEMPTR<void> nextBlock; + }; + static_assert(sizeof(MEMBlockHeapTrack2_t) == 0x14); + + struct MEMBlockHeap2_t : MEMHeapBase + { + /* +0x34 */ uint8 ukn[0x50 - 0x34]; + /* +0x50 */ MEMBlockHeapTrack2_t track; + /* +0x64 */ MEMPTR<void> headBlock; + /* +0x68 */ MEMPTR<void> tailBlock; + /* +0x6C */ MEMPTR<void> nextFreeBlock; + /* +0x70 */ uint32be freeBlocksLeft; + /* +0x74 */ uint8 padding[0x80 - 0x74]; + }; + static_assert(sizeof(MEMBlockHeap2_t) == 0x80); + + MEMHeapHandle MEMInitBlockHeap(MEMBlockHeap2_t* memStart, void* startAddr, void* endAddr, void* initTrackMem, uint32 initTrackMemSize, uint32 createFlags); + void* MEMDestroyBlockHeap(MEMHeapHandle hHeap); + + sint32 MEMAddBlockHeapTracking(MPTR heap, MPTR trackMem, uint32 trackMemSize); + + void InitializeMEMBlockHeap(); +} diff --git a/src/Cafe/OS/libs/coreinit/coreinit_MEM_ExpHeap.cpp b/src/Cafe/OS/libs/coreinit/coreinit_MEM_ExpHeap.cpp new file mode 100644 index 00000000..fe221717 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_MEM_ExpHeap.cpp @@ -0,0 +1,1105 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/HW/Espresso/PPCCallback.h" +#include "Cafe/OS/libs/coreinit/coreinit_MEM_ExpHeap.h" + +void DebugLogStackTrace(OSThread_t* thread, MPTR sp); + +#define EXP_HEAP_GET_FROM_FREE_BLOCKCHAIN(__blockchain__) (MEMExpHeapHead2*)((uintptr_t)__blockchain__ - offsetof(MEMExpHeapHead2, expHeapHead) - offsetof(MEMExpHeapHead40_t, chainFreeBlocks)) + +namespace coreinit +{ + #define MBLOCK_TYPE_FREE ('FR') + #define MBLOCK_TYPE_USED ('UD') + + struct MBlock2_t + { + uint32be fields; // 0x00 + uint32be dataSize; // 0x04 | size of raw data (excluding size of this struct) + MEMPTR<MBlock2_t> prevBlock; // 0x08 + MEMPTR<MBlock2_t> nextBlock; // 0x0C + uint16be typeCode; // 0x10 + uint16be padding; // 0x12 + }; + static_assert(sizeof(MBlock2_t) == 0x14); + + struct ExpMemBlockRegion + { + uintptr_t start; + uintptr_t end; + }; + +#define MBLOCK_GET_HEADER(__mblock__) (MBlock2_t*)((uintptr_t)__mblock__ - sizeof(MBlock2_t)) +#define MBLOCK_GET_MEMORY(__mblock__) ((uintptr_t)__mblock__ + sizeof(MBlock2_t)) +#define MBLOCK_GET_END(__mblock__) ((uintptr_t)__mblock__ + sizeof(MBlock2_t) + (uint32)__mblock__->dataSize) + +#pragma region internal + MBlock2_t* _MEMExpHeap_InitMBlock(ExpMemBlockRegion* region, uint16 typeCode) + { + MBlock2_t* mBlock = (MBlock2_t*)region->start; + memset(mBlock, 0x00, sizeof(MBlock2_t)); + mBlock->typeCode = typeCode; + mBlock->dataSize = (uint32)(region->end - (region->start + sizeof(MBlock2_t))); + return mBlock; + } + + bool _MEMExpHeap_CheckMBlock(MBlock2_t* mBlock, MEMHeapHandle heap, uint16 typeCode, const char* debugText, int options) + { +#ifndef MBLOCK_DEBUG_ASSERT + return true; +#endif + const uintptr_t mBlockAddr = (uintptr_t)mBlock; + const uintptr_t mBlockRawAddr = MBLOCK_GET_MEMORY(mBlock); + uintptr_t heapStart = 0, heapEnd = 0; + if (heap) + { + heapStart = (uintptr_t)heap->heapStart.GetPtr(); + heapEnd = (uintptr_t)heap->heapEnd.GetPtr(); + if (heap == MEM_HEAP_INVALID_HANDLE) + { + if (mBlockAddr < heapStart || mBlockRawAddr > heapEnd) + { + return false; + } + } + } + if (typeCode == (uint16)mBlock->typeCode) + { + if (heap == MEM_HEAP_INVALID_HANDLE) + return true; + + const uint32 dataSize = mBlock->dataSize; + if (mBlockRawAddr + dataSize <= heapEnd) + return true; + return false; + } + return false; + } + + bool _MEMExpHeap_IsValidUsedMBlock(const void* memBlock, MEMHeapHandle heap) + { +#ifndef MBLOCK_DEBUG_ASSERT + return true; +#endif + if (!memBlock) + return false; + + heap->AcquireLock(); + + bool valid = false; + MBlock2_t* mBlock = MBLOCK_GET_HEADER(memBlock); + + if (heap) + { + MEMExpHeapHead2* expHeap = (MEMExpHeapHead2*)heap; + MEMPTR<MBlock2_t> usedBlock = expHeap->expHeapHead.chainUsedBlocks.headMBlock; + while (usedBlock) + { + if (mBlock == usedBlock.GetPtr()) + { + valid = _MEMExpHeap_CheckMBlock(mBlock, heap, MBLOCK_TYPE_USED, "used", 0); + break; + } + + usedBlock = usedBlock->nextBlock; + } + } + else + { + valid = _MEMExpHeap_CheckMBlock(mBlock, heap, MBLOCK_TYPE_USED, "used", 0); + } + + heap->ReleaseLock(); + + return valid; + } + + void _MEMExpHeap_GetRegionOfMBlock(ExpMemBlockRegion* region, MBlock2_t* memBlock) + { + uint32 alignment = ((memBlock->fields) >> 8) & 0x7FFFFF; + + uintptr_t memBlockAddr = (uintptr_t)memBlock; + region->start = memBlockAddr - alignment; + region->end = memBlockAddr + sizeof(MBlock2_t) + (uint32)memBlock->dataSize; + } + + void* _MEMExpHeap_RemoveMBlock(MBlockChain2_t* blockChain, MBlock2_t* block) + { + MEMPTR<MBlock2_t> prevBlock = block->prevBlock; + MEMPTR<MBlock2_t> nextBlock = block->nextBlock; + + if (prevBlock) + prevBlock->nextBlock = nextBlock; + else + blockChain->headMBlock = nextBlock; + + if (nextBlock) + nextBlock->prevBlock = prevBlock; + else + blockChain->tailMBlock = prevBlock; + + return prevBlock.GetPtr(); + } + + MBlock2_t* _MEMExpHeap_InsertMBlock(MBlockChain2_t* blockChain, MBlock2_t* newBlock, MBlock2_t* prevBlock) + { + newBlock->prevBlock = prevBlock; + + MEMPTR<MBlock2_t> nextBlock; + if (prevBlock) + { + nextBlock = prevBlock->nextBlock; + prevBlock->nextBlock = newBlock; + } + else + { + nextBlock = blockChain->headMBlock; + blockChain->headMBlock = newBlock; + } + + newBlock->nextBlock = nextBlock; + if (nextBlock) + nextBlock->prevBlock = newBlock; + else + blockChain->tailMBlock = newBlock; + + return newBlock; + } + + bool _MEMExpHeap_RecycleRegion(MBlockChain2_t* blockChain, ExpMemBlockRegion* region) + { + ExpMemBlockRegion newRegion; + newRegion.start = region->start; + newRegion.end = region->end; + + MEMPTR<MBlock2_t> prev; + MEMPTR<MBlock2_t> find = blockChain->headMBlock; + while (find) + { + MBlock2_t* findMBlock = find.GetPtr(); + const uintptr_t blockAddr = (uintptr_t)findMBlock; + if (blockAddr >= region->start) + { + if (blockAddr == region->end) + { + newRegion.end = MBLOCK_GET_END(findMBlock); + _MEMExpHeap_RemoveMBlock(blockChain, findMBlock); + + MEMExpHeapHead2* heap = EXP_HEAP_GET_FROM_FREE_BLOCKCHAIN(blockChain); + uint8 options = heap->flags; + if (HAS_FLAG(options, MEM_HEAP_OPTION_FILL)) + { + const uint32 fillVal = MEMGetFillValForHeap(HEAP_FILL_TYPE::ON_FREE); + memset(findMBlock, fillVal, sizeof(MBlock2_t)); + } + } + + break; + } + + prev = find; + find = findMBlock->nextBlock; + } + + if (prev) + { + MBlock2_t* prevMBlock = prev.GetPtr(); + if (MBLOCK_GET_END(prevMBlock) == region->start) + { + newRegion.start = (uintptr_t)prevMBlock; + prev = (MBlock2_t*)_MEMExpHeap_RemoveMBlock(blockChain, prevMBlock); + } + } + + if ((newRegion.end - newRegion.start) < sizeof(MBlock2_t)) + return false; + + MEMExpHeapHead2* heap = EXP_HEAP_GET_FROM_FREE_BLOCKCHAIN(blockChain); + uint8 options = heap->flags; + if (HAS_FLAG(options, MEM_HEAP_OPTION_FILL)) + { + const uint32 fillVal = MEMGetFillValForHeap(HEAP_FILL_TYPE::ON_FREE); + memset((void*)region->start, fillVal, region->end - region->start); + } + + MBlock2_t* newBlock = _MEMExpHeap_InitMBlock(&newRegion, MBLOCK_TYPE_FREE); + _MEMExpHeap_InsertMBlock(blockChain, newBlock, prev.GetPtr()); + return true; + } + +void* _MEMExpHeap_AllocUsedBlockFromFreeBlock(MBlockChain2_t* blockChain, MBlock2_t* freeBlock, uintptr_t blockMemStart, uint32 size, MEMExpHeapAllocDirection direction) +{ + MEMExpHeapHead2* heap = EXP_HEAP_GET_FROM_FREE_BLOCKCHAIN(blockChain); + + ExpMemBlockRegion freeRegion; + _MEMExpHeap_GetRegionOfMBlock(&freeRegion, freeBlock); + + ExpMemBlockRegion newRegion = {blockMemStart + size, freeRegion.end}; + freeRegion.end = blockMemStart - sizeof(MBlock2_t); + + MBlock2_t* prevBlock = (MBlock2_t*)_MEMExpHeap_RemoveMBlock(blockChain, freeBlock); + + if ((freeRegion.end - freeRegion.start) >= 0x18 && (direction != MEMExpHeapAllocDirection::HEAD || HAS_FLAG(heap->expHeapHead.fields, MEM_EXPHEAP_USE_ALIGN_MARGIN))) + { + MBlock2_t* newBlock = _MEMExpHeap_InitMBlock(&freeRegion, MBLOCK_TYPE_FREE); + prevBlock = _MEMExpHeap_InsertMBlock(blockChain, newBlock, prevBlock); + } + else + freeRegion.end = freeRegion.start; + + if ((newRegion.end - newRegion.start) >= 0x18 && (direction != MEMExpHeapAllocDirection::TAIL || HAS_FLAG(heap->expHeapHead.fields, MEM_EXPHEAP_USE_ALIGN_MARGIN))) + { + MBlock2_t* newBlock = _MEMExpHeap_InitMBlock(&newRegion, MBLOCK_TYPE_FREE); + prevBlock = _MEMExpHeap_InsertMBlock(blockChain, newBlock, prevBlock); + } + else + newRegion.start = newRegion.end; + + uint8 options = heap->flags; + if (HAS_FLAG(options, MEM_HEAP_OPTION_CLEAR)) + { + memset((void*)freeRegion.end, 0x00, newRegion.start - freeRegion.end); + } + else if (HAS_FLAG(options, MEM_HEAP_OPTION_FILL)) + { + const uint32 fillValue = MEMGetFillValForHeap(HEAP_FILL_TYPE::ON_ALLOC); + memset((void*)freeRegion.end, fillValue, newRegion.start - freeRegion.end); + } + + ExpMemBlockRegion allocRegion = {blockMemStart - sizeof(MBlock2_t), newRegion.start}; + MBlock2_t* newBlock = _MEMExpHeap_InitMBlock(&allocRegion, MBLOCK_TYPE_USED); + + uint32 fields = (uint32)newBlock->fields; + if(direction == MEMExpHeapAllocDirection::TAIL) + fields |= 1 << 31; + else + fields &= ~(1 << 31); + uint32 alignmentPadding = (uint32)((uintptr_t)newBlock - (uintptr_t)freeRegion.end); + fields |= (alignmentPadding & 0x7FFFFF) << 8; + fields |= ((uint32)heap->expHeapHead.groupID & 0xFF); + newBlock->fields = fields; + + MBlock2_t* tailBlock = heap->expHeapHead.chainUsedBlocks.tailMBlock.GetPtr(); + _MEMExpHeap_InsertMBlock(&heap->expHeapHead.chainUsedBlocks, newBlock, tailBlock); + + return (void*)blockMemStart; +} + +void* _MEMExpHeap_AllocFromTail(MEMHeapHandle heap, uint32 size, int alignment) +{ + MEMExpHeapHead2* expHeap = (MEMExpHeapHead2*)heap; + + const bool searchForFirstEntry = (expHeap->expHeapHead.fields&1) == MEM_EXPHEAP_ALLOC_MODE_FIRST; + const int alignmentMinusOne = alignment - 1; + + MBlock2_t* freeBlock = nullptr; + uintptr_t blockMemStart = 0; + uint32 foundSize = -1; + + for (MBlock2_t* findBlock = expHeap->expHeapHead.chainFreeBlocks.tailMBlock.GetPtr(); findBlock != nullptr; findBlock = findBlock->prevBlock.GetPtr()) + { + const uintptr_t blockMemory = MBLOCK_GET_MEMORY(findBlock); + + const uint32 dataSize = (uint32)findBlock->dataSize; + const uintptr_t alignedEndBlockMemory = (blockMemory + dataSize - size) & ~alignmentMinusOne; + + if (alignedEndBlockMemory < blockMemory) + continue; + + if (foundSize <= dataSize) + continue; + + freeBlock = findBlock; + blockMemStart = alignedEndBlockMemory; + foundSize = dataSize; + + if (searchForFirstEntry) + break; + + if (foundSize == size) + break; + } + + void* mem = nullptr; + if (freeBlock) + mem = _MEMExpHeap_AllocUsedBlockFromFreeBlock(&expHeap->expHeapHead.chainFreeBlocks, freeBlock, blockMemStart, size, coreinit::MEMExpHeapAllocDirection::TAIL); + + return mem; +} + +void* _MEMExpHeap_AllocFromHead(MEMHeapHandle heap, uint32 size, int alignment) +{ + MEMExpHeapHead2* expHeap = (MEMExpHeapHead2*)heap; + + const bool searchForFirstEntry = (expHeap->expHeapHead.fields&1) == MEM_EXPHEAP_ALLOC_MODE_FIRST; + const int alignmentMinusOne = alignment - 1; + + MBlock2_t* freeBlock = nullptr; + uintptr_t blockMemStart = 0; + uint32 foundSize = -1; + + for (MBlock2_t* findBlock = expHeap->expHeapHead.chainFreeBlocks.headMBlock.GetPtr(); findBlock != nullptr; findBlock = findBlock->nextBlock.GetPtr()) + { + const uintptr_t blockMemory = MBLOCK_GET_MEMORY(findBlock); + const uintptr_t alignedBlockMemory = (blockMemory + alignmentMinusOne) & ~alignmentMinusOne; + const uint32 dataSize = (uint32)findBlock->dataSize; + + if (dataSize < alignedBlockMemory - blockMemory + size) + continue; + + if (foundSize <= dataSize) + continue; + + freeBlock = findBlock; + blockMemStart = alignedBlockMemory; + foundSize = dataSize; + + if (searchForFirstEntry) + break; + + if (foundSize == size) + break; + } + + void* mem = nullptr; + if (freeBlock) + mem = _MEMExpHeap_AllocUsedBlockFromFreeBlock(&expHeap->expHeapHead.chainFreeBlocks, freeBlock, blockMemStart, size, coreinit::MEMExpHeapAllocDirection::HEAD); + + return mem; +} + +MEMHeapHandle _MEMExpHeap_InitHeap(void* startAddress, void* endAddress, uint32 createFlags) +{ + MEMExpHeapHead2* header = (MEMExpHeapHead2*)startAddress; + uintptr_t heapStart = (uintptr_t)startAddress + sizeof(MEMExpHeapHead2); + + MEMInitHeapBase(header, coreinit::MEMHeapMagic::EXP_HEAP, (void*)heapStart, endAddress, createFlags); + + ExpMemBlockRegion region; + region.start = (uintptr_t)header->heapStart.GetPtr(); + region.end = (uintptr_t)header->heapEnd.GetPtr(); + + MBlock2_t* mBlock = _MEMExpHeap_InitMBlock(®ion, MBLOCK_TYPE_FREE); + + header->expHeapHead.chainFreeBlocks.headMBlock = mBlock; + header->expHeapHead.chainFreeBlocks.tailMBlock = mBlock; + + header->expHeapHead.chainUsedBlocks.headMBlock = nullptr; + header->expHeapHead.chainUsedBlocks.tailMBlock = nullptr; + + header->expHeapHead.groupID = 0; + header->expHeapHead.fields = 0; + + return (MEMHeapHandle)header; +} + +MEMHeapHandle MEMCreateExpHeap(void* startAddress, uint32 size) +{ + return MEMCreateExpHeapEx(startAddress, size, MEM_HEAP_OPTION_NONE); +} + +void* MEMAllocFromExpHeap(MEMHeapHandle heap, uint32 size) +{ + return MEMAllocFromExpHeapEx(heap, size, MEM_HEAP_DEFAULT_ALIGNMENT); +} + +uint32 MEMGetAllocatableSizeForExpHeap(MEMHeapHandle heap) +{ + return MEMGetAllocatableSizeForExpHeapEx(heap, MEM_HEAP_DEFAULT_ALIGNMENT); +} + +void IsValidExpHeapHandle_(MEMHeapHandle heap) +{ + cemu_assert_debug(heap != MEM_HEAP_INVALID_HANDLE); + cemu_assert_debug(heap->magic == coreinit::MEMHeapMagic::EXP_HEAP); +} + +#pragma endregion + +#pragma region exported + +MEMHeapHandle MEMCreateExpHeapEx(void* startAddress, uint32 size, uint32 createFlags) +{ + if (startAddress == nullptr) + return MEM_HEAP_INVALID_HANDLE; + + const uintptr_t alignedStart = ((uintptr_t)startAddress + MIN_ALIGNMENT_MINUS_ONE) & (~MIN_ALIGNMENT_MINUS_ONE); + const uintptr_t alignedEnd = ((uintptr_t)startAddress + size) & (~MIN_ALIGNMENT_MINUS_ONE); + + if (alignedStart > alignedEnd) + return MEM_HEAP_INVALID_HANDLE; + + if (alignedEnd - alignedStart < 0x6C) + return MEM_HEAP_INVALID_HANDLE; + + MEMHeapHandle result = _MEMExpHeap_InitHeap((void*)alignedStart, (void*)alignedEnd, createFlags); + if (result) + MEMHeapTable_Add(result); + + return result; +} + +void* MEMDestroyExpHeap(MEMHeapHandle heap) +{ + IsValidExpHeapHandle_(heap); + MEMBaseDestroyHeap(heap); + MEMHeapTable_Remove(heap); + return heap; +} + +void* MEMAllocFromExpHeapEx(MEMHeapHandle heap, uint32 size, sint32 alignment) +{ + IsValidExpHeapHandle_(heap); + if (alignment == 0) + { + // this is a guaranteed crash on the actual console + forceLog_printf("MEMAllocFromExpHeapEx(): Alignment 0 not allowed"); + alignment = 4; + return nullptr; + } + + if (size == 0) + size = 1; + + size = (size + MIN_ALIGNMENT_MINUS_ONE) & ~MIN_ALIGNMENT_MINUS_ONE; + + heap->AcquireLock(); + + void* mem; + if (alignment < 0) + { + alignment = -alignment; + mem = _MEMExpHeap_AllocFromTail(heap, size, alignment); + } + else + { + mem = _MEMExpHeap_AllocFromHead(heap, size, alignment); + } + + heap->ReleaseLock(); + + return mem; +} + +void MEMFreeToExpHeap(MEMHeapHandle heap, void* mem) +{ + IsValidExpHeapHandle_(heap); + if (mem) + { + heap->AcquireLock(); + + cemu_assert_debug(_MEMExpHeap_IsValidUsedMBlock(mem, heap) == true); + cemu_assert_debug((uintptr_t)heap->heapStart.GetPtr() <= (uintptr_t)mem && (uintptr_t)mem < (uintptr_t)heap->heapEnd.GetPtr()); + + MEMExpHeapHead2* expHeap = (MEMExpHeapHead2*)heap; + + ExpMemBlockRegion region; + MBlock2_t* mBlock = MBLOCK_GET_HEADER(mem); + _MEMExpHeap_GetRegionOfMBlock(®ion, mBlock); + + _MEMExpHeap_RemoveMBlock(&expHeap->expHeapHead.chainUsedBlocks, mBlock); + _MEMExpHeap_RecycleRegion(&expHeap->expHeapHead.chainFreeBlocks, ®ion); + + heap->ReleaseLock(); + } +} + +uint16 MEMSetAllocModeForExpHeap(MEMHeapHandle heap, uint16 mode) +{ + IsValidExpHeapHandle_(heap); + heap->AcquireLock(); + + MEMExpHeapHead2* expHeap = (MEMExpHeapHead2*)heap; + const uint16 oldMode = expHeap->expHeapHead.fields & 0x1; + expHeap->expHeapHead.fields |= (mode & 0x1); + + heap->ReleaseLock(); + + return oldMode; +} + +uint16 MEMGetAllocModeForExpHeap(MEMHeapHandle heap) +{ + IsValidExpHeapHandle_(heap); + heap->AcquireLock(); + + MEMExpHeapHead2* expHeap = (MEMExpHeapHead2*)heap; + const uint16 mode = expHeap->expHeapHead.fields & 0x1; + + heap->ReleaseLock(); + + return mode; +} + +uint32 MEMAdjustExpHeap(MEMHeapHandle heap) +{ + uint32 newSize = 0; + + IsValidExpHeapHandle_(heap); + heap->AcquireLock(); + + MEMExpHeapHead2* expHeap = (MEMExpHeapHead2*)heap; + + MEMPTR<MBlock2_t> tailBlock = expHeap->expHeapHead.chainFreeBlocks.tailMBlock; + if (tailBlock) + { + MBlock2_t* tail = tailBlock.GetPtr(); + + uintptr_t blockMemEnd = MBLOCK_GET_END(tail); + uintptr_t heapEnd = (uintptr_t)heap->heapEnd.GetPtr(); + if (blockMemEnd == heapEnd) + { + _MEMExpHeap_RemoveMBlock(&expHeap->expHeapHead.chainFreeBlocks, tail); + + uint32 removedBlockSize = sizeof(MBlock2_t) + (uint32)tail->dataSize; + uintptr_t newHeapEnd = heapEnd - removedBlockSize; + heap->heapEnd = (void*)newHeapEnd; + + newSize = (uint32)(newHeapEnd - (uintptr_t)heap); + } + } + + heap->ReleaseLock(); + + return newSize; +} + +uint32 MEMResizeForMBlockExpHeap(MEMHeapHandle heap, void* memBlock, uint32 size) +{ + uint32 newSize = 0; + + IsValidExpHeapHandle_(heap); + cemu_assert_debug(memBlock != nullptr); + heap->AcquireLock(); + + MBlock2_t* mBlock = MBLOCK_GET_HEADER(memBlock); + const uint32 dataSize = mBlock->dataSize; + if (dataSize != size) + { + cemu_assert_debug(_MEMExpHeap_IsValidUsedMBlock(memBlock, heap)); + + MEMExpHeapHead2* expHeap = (MEMExpHeapHead2*)heap; + + if (size <= dataSize) + { + ExpMemBlockRegion region; + region.start = (uintptr_t)memBlock + size; + region.end = MBLOCK_GET_END(mBlock); + + mBlock->dataSize = size; + + if (!_MEMExpHeap_RecycleRegion(&expHeap->expHeapHead.chainFreeBlocks, ®ion)) + mBlock->dataSize = dataSize; + + newSize = (uint32)mBlock->dataSize; + } + else + { + MEMPTR<MBlock2_t> free = expHeap->expHeapHead.chainFreeBlocks.headMBlock; + const uintptr_t blockEndAddr = MBLOCK_GET_END(mBlock); + while (free) + { + MBlock2_t* freeBlock = free.GetPtr(); + if ((uintptr_t)freeBlock == blockEndAddr) + { + const uint32 freeDataSize = freeBlock->dataSize; + if (size > (dataSize + freeDataSize + sizeof(MBlock2_t))) + { + newSize = 0; + break; + } + + ExpMemBlockRegion region; + _MEMExpHeap_GetRegionOfMBlock(®ion, freeBlock); + MBlock2_t* prevBlock = (MBlock2_t*)_MEMExpHeap_RemoveMBlock(&expHeap->expHeapHead.chainFreeBlocks, freeBlock); + + uintptr_t oldStart = region.start; + region.start = (uintptr_t)memBlock + size; + + if (region.end - region.start < sizeof(MBlock2_t)) + region.start = region.end; + + mBlock->dataSize = (uint32)(region.start - (uintptr_t)memBlock); + if (region.end - region.start >= sizeof(MBlock2_t)) + { + MBlock2_t* newBlock = _MEMExpHeap_InitMBlock(®ion, MBLOCK_TYPE_FREE); + _MEMExpHeap_InsertMBlock(&expHeap->expHeapHead.chainFreeBlocks, newBlock, prevBlock); + } + + if (HAS_FLAG(heap->flags, MEM_HEAP_OPTION_CLEAR)) + memset((void*)oldStart, 0x00, region.start - oldStart); + else if (HAS_FLAG(heap->flags, MEM_HEAP_OPTION_FILL)) + { + const uint32 fillValue = MEMGetFillValForHeap(HEAP_FILL_TYPE::ON_ALLOC); + memset((void*)oldStart, fillValue, region.start - oldStart); + } + + newSize = (uint32)mBlock->dataSize; + + break; + } + + free = freeBlock->nextBlock; + } + + if (!free) + newSize = 0; + } + } + + heap->ReleaseLock(); + + return newSize; +} + +uint32 MEMGetTotalFreeSizeForExpHeap(MEMHeapHandle heap) +{ + uint32 freeSize = 0; + + IsValidExpHeapHandle_(heap); + heap->AcquireLock(); + + MEMExpHeapHead2* expHeap = (MEMExpHeapHead2*)heap; + + MEMPTR<MBlock2_t> block = expHeap->expHeapHead.chainFreeBlocks.headMBlock; + while (block) + { + freeSize += (uint32)block->dataSize; + block = block->nextBlock; + } + + heap->ReleaseLock(); + + return freeSize; +} + +uint32 MEMGetAllocatableSizeForExpHeapEx(MEMHeapHandle heap, sint32 alignment) +{ + uint32 result = 0; + + IsValidExpHeapHandle_(heap); + cemu_assert_debug((alignment & 3) == 0); + alignment = abs(alignment); + + heap->AcquireLock(); + + MEMExpHeapHead2* exp_heap = (MEMExpHeapHead2*)heap; + const sint32 alignment_minus_one = alignment - 1; + + uint32 smallest_alignment_space = -1; + for(auto free_block = exp_heap->expHeapHead.chainFreeBlocks.headMBlock; free_block; free_block = free_block->nextBlock) + { + MBlock2_t* block = free_block.GetPtr(); + const uintptr_t block_memory = MBLOCK_GET_MEMORY(block); + const uintptr_t block_end = MBLOCK_GET_END(block); + + const uintptr_t aligned_memory = (block_memory + alignment_minus_one) & ~alignment_minus_one; + if (aligned_memory >= block_end) + continue; + + const uint32 size = (uint32)(block_end - aligned_memory); + const uint32 alignment_space = (uint32)(aligned_memory - block_memory); + if (size < result) + continue; + + if (size == result) + { + if (smallest_alignment_space <= alignment_space) + continue; + } + + smallest_alignment_space = alignment_space; + result = size; + } + + heap->ReleaseLock(); + + return result; +} + +uint16 MEMSetGroupIDForExpHeap(MEMHeapHandle heap, uint16 groupId) +{ + IsValidExpHeapHandle_(heap); + + heap->AcquireLock(); + + MEMExpHeapHead2* expHeap = (MEMExpHeapHead2*)heap; + const uint16 oldGroupId = expHeap->expHeapHead.groupID; + expHeap->expHeapHead.groupID = groupId; + + heap->ReleaseLock(); + + return oldGroupId; +} + +uint16 MEMGetGroupIDForExpHeap(MEMHeapHandle heap) +{ + IsValidExpHeapHandle_(heap); + + heap->AcquireLock(); + + MEMExpHeapHead2* expHeap = (MEMExpHeapHead2*)heap; + const uint16 oldGroupId = expHeap->expHeapHead.groupID; + + heap->ReleaseLock(); + + return oldGroupId; +} + +void MEMVisitAllocatedForExpHeap(MEMHeapHandle heap, const MEMPTR<void>& visitor, uint32 userParam) +{ + IsValidExpHeapHandle_(heap); + cemu_assert_debug(visitor != nullptr); + + heap->AcquireLock(); + + MEMPTR<MEMHeapBase> heapMPTR = heap; + + MEMExpHeapHead2* expHeap = (MEMExpHeapHead2*)heap; + MEMPTR<MBlock2_t> find = expHeap->expHeapHead.chainUsedBlocks.headMBlock; + while (find) + { + MBlock2_t* mBlock = find.GetPtr(); + + MEMPTR<void> memMPTR = (void*)MBLOCK_GET_MEMORY(mBlock); + PPCCoreCallback(visitor.GetMPTR(), memMPTR.GetMPTR(), heapMPTR.GetMPTR(), userParam); + + find = mBlock->nextBlock; + } + + heap->ReleaseLock(); +} + +uint32 MEMGetSizeForMBlockExpHeap(const void* memBlock) +{ + cemu_assert_debug(_MEMExpHeap_IsValidUsedMBlock(memBlock, nullptr)); + MBlock2_t* mBlock = MBLOCK_GET_HEADER(memBlock); + return mBlock->dataSize; +} + +uint16 MEMGetGroupIDForMBlockExpHeap(const void* memBlock) +{ + cemu_assert_debug(_MEMExpHeap_IsValidUsedMBlock(memBlock, nullptr)); + MBlock2_t* mBlock = MBLOCK_GET_HEADER(memBlock); + return (uint32)mBlock->fields & 0xFF; +} + +uint16 MEMGetAllocDirForMBlockExpHeap(const void* memBlock) +{ + cemu_assert_debug(_MEMExpHeap_IsValidUsedMBlock(memBlock, nullptr)); + MBlock2_t* mBlock = MBLOCK_GET_HEADER(memBlock); + const uint32 fields = mBlock->fields; + return (fields >> 31) & 1; +} + +bool MEMCheckExpHeap(MEMHeapHandle heap, uint32 options) +{ + if (heap == MEM_HEAP_INVALID_HANDLE || heap->magic != coreinit::MEMHeapMagic::EXP_HEAP) + return false; + + heap->AcquireLock(); + + // todo + + heap->ReleaseLock(); + + return true; +} + +bool MEMCheckForMBlockExpHeap(const void* memBlock, MEMHeapHandle heap, uint32 options) +{ + return true; +} + +void _DefaultAllocatorForExpHeap_Alloc(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(allocator, MEMAllocator, 0); + ppcDefineParamU32(size, 1); + MEMPTR<void> result = MEMAllocFromExpHeapEx((MEMHeapHandle)allocator->heap.GetPtr(), size, (uint32)allocator->param1); + osLib_returnFromFunction(hCPU, result.GetMPTR()); +} + +void _DefaultAllocatorForExpHeap_Free(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(allocator, MEMAllocator, 0); + ppcDefineParamMEMPTR(mem, void, 1); + MEMFreeToExpHeap((MEMHeapHandle)allocator->heap.GetPtr(), mem); + osLib_returnFromFunction(hCPU, 0); +} + +SysAllocator<MEMAllocatorFunc> gExpHeapDefaultAllocator; + +void MEMInitAllocatorForExpHeap(MEMAllocator* allocator, MEMHeapHandle heap, sint32 alignment) +{ + allocator->func = gExpHeapDefaultAllocator.GetPtr(); + gExpHeapDefaultAllocator->funcAlloc = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(_DefaultAllocatorForExpHeap_Alloc)); + gExpHeapDefaultAllocator->funcFree = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(_DefaultAllocatorForExpHeap_Free)); + + allocator->heap = heap; + allocator->param1 = alignment; + allocator->param2 = 0; +} +#pragma endregion + +#pragma region wrapper + +void expheap_test(); + +void export_MEMCreateExpHeapEx(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(startAddress, void, 0); + ppcDefineParamU32(size, 1); + ppcDefineParamU16(options, 2); + MEMPTR<MEMHeapBase> heap = MEMCreateExpHeapEx(startAddress.GetPtr(), size, options); + coreinitMemLog_printf("MEMCreateExpHeapEx(0x%08x, 0x%x, 0x%x) Result: 0x%08x", startAddress.GetMPTR(), size, options, heap.GetMPTR()); + osLib_returnFromFunction(hCPU, heap.GetMPTR()); +} + +void export_MEMDestroyExpHeap(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(heap, MEMHeapBase, 0); + coreinitMemLog_printf("MEMDestroyExpHeap(0x%08x)", heap.GetMPTR()); + MEMPTR<MEMHeapBase> oldHeap = (MEMHeapBase*)MEMDestroyExpHeap(heap.GetPtr()); + osLib_returnFromFunction(hCPU, oldHeap.GetMPTR()); +} + +void export_MEMAllocFromExpHeapEx(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(heap, MEMHeapBase, 0); + ppcDefineParamU32(size, 1); + ppcDefineParamS32(alignment, 2); + MEMPTR<void> mem = MEMAllocFromExpHeapEx(heap.GetPtr(), size, alignment); + coreinitMemLog_printf("MEMAllocFromExpHeapEx(0x%08x, 0x%x, %d) Result: 0x%08x", heap.GetMPTR(), size, alignment, mem.GetMPTR()); + osLib_returnFromFunction(hCPU, mem.GetMPTR()); +} + +void export_MEMFreeToExpHeap(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(heap, MEMHeapBase, 0); + ppcDefineParamMEMPTR(mem, void, 1); + coreinitMemLog_printf("MEMFreeToExpHeap(0x%08x, 0x%08x)", heap.GetMPTR(), mem.GetMPTR()); + MEMFreeToExpHeap(heap.GetPtr(), mem.GetPtr()); + osLib_returnFromFunction(hCPU, 0); +} + +void export_MEMSetAllocModeForExpHeap(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(heap, MEMHeapBase, 0); + ppcDefineParamU16(mode, 1); + coreinitMemLog_printf("MEMSetAllocModeForExpHeap(0x%08x, %d)", heap.GetMPTR(), mode); + uint16 oldMode = MEMSetAllocModeForExpHeap(heap.GetPtr(), mode); + osLib_returnFromFunction(hCPU, oldMode); +} + +void export_MEMGetAllocModeForExpHeap(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(heap, MEMHeapBase, 0); + coreinitMemLog_printf("MEMGetAllocModeForExpHeap(0x%08x)", heap.GetMPTR()); + uint16 oldMode = MEMGetAllocModeForExpHeap(heap.GetPtr()); + osLib_returnFromFunction(hCPU, oldMode); +} + +void export_MEMAdjustExpHeap(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(heap, MEMHeapBase, 0); + coreinitMemLog_printf("MEMAdjustExpHeap(0x%08x)", heap.GetMPTR()); + uint32 newSize = MEMAdjustExpHeap(heap.GetPtr()); + osLib_returnFromFunction(hCPU, newSize); +} + +void export_MEMResizeForMBlockExpHeap(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(heap, MEMHeapBase, 0); + ppcDefineParamMEMPTR(mem, void, 1); + ppcDefineParamU32(size, 2); + uint32 newSize = MEMResizeForMBlockExpHeap(heap.GetPtr(), mem.GetPtr(), size); + coreinitMemLog_printf("MEMResizeForMBlockExpHeap(0x%08x, 0x%08x, 0x%x) Result: 0x%x", heap.GetMPTR(), mem.GetMPTR(), size, newSize); + osLib_returnFromFunction(hCPU, newSize); +} + +void export_MEMGetTotalFreeSizeForExpHeap(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(heap, MEMHeapBase, 0); + uint32 size = MEMGetTotalFreeSizeForExpHeap(heap.GetPtr()); + coreinitMemLog_printf("MEMGetTotalFreeSizeForExpHeap(0x%08x) Result: 0x%x", heap.GetMPTR(), size); + osLib_returnFromFunction(hCPU, size); +} + +void export_MEMGetAllocatableSizeForExpHeapEx(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(heap, MEMHeapBase, 0); + ppcDefineParamS32(alignment, 1); + uint32 size = MEMGetAllocatableSizeForExpHeapEx(heap.GetPtr(), alignment); + coreinitMemLog_printf("MEMGetAllocatableSizeForExpHeapEx(0x%08x, 0x%x) Result: 0x%x", heap.GetMPTR(), alignment, size); + osLib_returnFromFunction(hCPU, size); +} + +void export_MEMSetGroupIDForExpHeap(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(heap, MEMHeapBase, 0); + ppcDefineParamU16(groupId, 1); + coreinitMemLog_printf("MEMSetGroupIDForExpHeap(0x%08x, %d)", heap.GetMPTR(), groupId); +#ifndef PUBLIC_RELEASE + assert_dbg(); // someone test this and the entire groupId feature +#endif + uint16 oldGroupId = MEMSetGroupIDForExpHeap(heap.GetPtr(), groupId); + osLib_returnFromFunction(hCPU, oldGroupId); +} + +void export_MEMGetGroupIDForExpHeap(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(heap, MEMHeapBase, 0); + forceLogDebug_printf("MEMGetGroupIDForExpHeap(0x%08x)", heap.GetMPTR()); + uint16 oldGroupId = MEMGetGroupIDForExpHeap(heap.GetPtr()); + osLib_returnFromFunction(hCPU, oldGroupId); +} + +void export_MEMVisitAllocatedForExpHeap(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(heap, MEMHeapBase, 0); + ppcDefineParamMEMPTR(visitor, void, 1); + ppcDefineParamU32(userParam, 2); + coreinitMemLog_printf("MEMVisitAllocatedForExpHeap(0x%08x, 0x%08x, 0x%x)", heap.GetMPTR(), visitor.GetMPTR(), userParam); + MEMVisitAllocatedForExpHeap(heap.GetPtr(), visitor, userParam); + osLib_returnFromFunction(hCPU, 0); +} + +void export_MEMGetSizeForMBlockExpHeap(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(memBlock, void, 0); + uint32 size = MEMGetSizeForMBlockExpHeap(memBlock.GetPtr()); + coreinitMemLog_printf("MEMGetSizeForMBlockExpHeap(0x%08x) Result: 0x%x", memBlock.GetMPTR(), size); + osLib_returnFromFunction(hCPU, size); +} + +void export_MEMGetGroupIDForMBlockExpHeap(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(memBlock, void, 0); + coreinitMemLog_printf("MEMGetGroupIDForMBlockExpHeap(0x%08x)", memBlock.GetMPTR()); + uint16 groupId = MEMGetGroupIDForMBlockExpHeap(memBlock.GetPtr()); + osLib_returnFromFunction(hCPU, groupId); +} + +void export_MEMGetAllocDirForMBlockExpHeap(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(memBlock, void, 0); + coreinitMemLog_printf("MEMGetAllocDirForMBlockExpHeap(0x%08x)", memBlock.GetMPTR()); + uint16 allocDir = MEMGetAllocDirForMBlockExpHeap(memBlock.GetPtr()); + osLib_returnFromFunction(hCPU, allocDir); +} + +void export_MEMCheckExpHeap(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(heap, MEMHeapBase, 0); + ppcDefineParamU32(options, 1); + coreinitMemLog_printf("MEMCheckExpHeap(0x%08x, 0x%x)", heap.GetMPTR(), options); + bool result = MEMCheckExpHeap(heap.GetPtr(), options); + osLib_returnFromFunction(hCPU, result ? 1 : 0); +} + +void export_MEMCheckForMBlockExpHeap(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(memBlock, void, 0); + ppcDefineParamMEMPTR(heap, MEMHeapBase, 1); + ppcDefineParamU32(options, 2); + bool result = MEMCheckForMBlockExpHeap(memBlock.GetPtr(), heap.GetPtr(), options); + coreinitMemLog_printf("MEMCheckForMBlockExpHeap(0x%08x, 0x%08x, 0x%x) Result: %d", memBlock.GetMPTR(), heap.GetMPTR(), options, result); + osLib_returnFromFunction(hCPU, result ? 1 : 0); +} + +void export_MEMInitAllocatorForExpHeap(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(allocator, MEMAllocator, 0); + ppcDefineParamMEMPTR(heap, MEMHeapBase, 1); + ppcDefineParamS32(alignment, 2); + coreinitMemLog_printf("MEMInitAllocatorForExpHeap(0x%08x, 0x%08x, %d)", allocator.GetMPTR(), heap.GetMPTR(), alignment); + MEMInitAllocatorForExpHeap(allocator.GetPtr(), heap.GetPtr(), alignment); + osLib_returnFromFunction(hCPU, 0); +} + +void expheap_test() +{ + srand(1000); + + MEMHeapHandle heapHandle = MEMCreateExpHeapEx(memory_getPointerFromVirtualOffset(0x11000000), 0x10000000, 0); + MEMExpHeapHead2* expHeap = (MEMExpHeapHead2*)heapHandle; + sint32 idx = 0; + + for (MBlock2_t* findBlock = expHeap->expHeapHead.chainFreeBlocks.headMBlock.GetPtr(); findBlock != nullptr; findBlock = findBlock->nextBlock.GetPtr()) + { + const uintptr_t blockMemory = MBLOCK_GET_MEMORY(findBlock); + const uint32 dataSize = (uint32)findBlock->dataSize; + + debug_printf(">> freeBlock %04d addr %08x - %08x\n", idx, memory_getVirtualOffsetFromPointer((void*)blockMemory), memory_getVirtualOffsetFromPointer((void*)blockMemory) + dataSize); + idx++; + } + + void* allocTable[1024]; + sint32 allocCount = 0; + + printf("Run ExpHeap test...\n"); + for (sint32 t = 0; t < 2; t++) + { + // allocate a bunch of entries + sint32 r = rand() % 100; + //for (sint32 i = 0; i < r; i++) + { + if( allocCount >= 1024 ) + continue; + + uint32 size = (rand() % 1000) * 12; + void* mem = MEMAllocFromExpHeapEx(heapHandle, size * 12, -0x2000); + if (mem) + { + allocTable[allocCount] = mem; + allocCount++; + + } + } + } + // free everything that remains + for (sint32 i = 0; i < allocCount; i++) + { + MEMFreeToExpHeap(heapHandle, allocTable[i]); + } + allocCount = 0; + // debug print free blocks (only one should remain) + expHeap = (MEMExpHeapHead2*)heapHandle; + idx = 0; + for (MBlock2_t* findBlock = expHeap->expHeapHead.chainFreeBlocks.headMBlock.GetPtr(); findBlock != nullptr; findBlock = findBlock->nextBlock.GetPtr()) + { + const uintptr_t blockMemory = MBLOCK_GET_MEMORY(findBlock); + const uint32 dataSize = (uint32)findBlock->dataSize; + + debug_printf("freeBlock %04d addr %08x - %08x\n", idx, memory_getVirtualOffsetFromPointer((void*)blockMemory), memory_getVirtualOffsetFromPointer((void*)blockMemory) + dataSize); + idx++; + } + + assert_dbg(); +} + +void expheap_load() +{ + osLib_addFunction("coreinit", "MEMCreateExpHeapEx", export_MEMCreateExpHeapEx); + osLib_addFunction("coreinit", "MEMDestroyExpHeap", export_MEMDestroyExpHeap); + osLib_addFunction("coreinit", "MEMAllocFromExpHeapEx", export_MEMAllocFromExpHeapEx); + osLib_addFunction("coreinit", "MEMFreeToExpHeap", export_MEMFreeToExpHeap); + osLib_addFunction("coreinit", "MEMSetAllocModeForExpHeap", export_MEMSetAllocModeForExpHeap); + osLib_addFunction("coreinit", "MEMGetAllocModeForExpHeap", export_MEMGetAllocModeForExpHeap); + osLib_addFunction("coreinit", "MEMAdjustExpHeap", export_MEMAdjustExpHeap); + osLib_addFunction("coreinit", "MEMResizeForMBlockExpHeap", export_MEMResizeForMBlockExpHeap); + osLib_addFunction("coreinit", "MEMGetTotalFreeSizeForExpHeap", export_MEMGetTotalFreeSizeForExpHeap); + osLib_addFunction("coreinit", "MEMGetAllocatableSizeForExpHeapEx", export_MEMGetAllocatableSizeForExpHeapEx); + osLib_addFunction("coreinit", "MEMSetGroupIDForExpHeap", export_MEMSetGroupIDForExpHeap); + osLib_addFunction("coreinit", "MEMGetGroupIDForExpHeap", export_MEMGetGroupIDForExpHeap); + osLib_addFunction("coreinit", "MEMVisitAllocatedForExpHeap", export_MEMVisitAllocatedForExpHeap); + osLib_addFunction("coreinit", "MEMGetSizeForMBlockExpHeap", export_MEMGetSizeForMBlockExpHeap); + osLib_addFunction("coreinit", "MEMGetGroupIDForMBlockExpHeap", export_MEMGetGroupIDForMBlockExpHeap); + osLib_addFunction("coreinit", "MEMGetAllocDirForMBlockExpHeap", export_MEMGetAllocDirForMBlockExpHeap); + osLib_addFunction("coreinit", "MEMCheckExpHeap", export_MEMCheckExpHeap); + osLib_addFunction("coreinit", "MEMCheckForMBlockExpHeap", export_MEMCheckForMBlockExpHeap); + + osLib_addFunction("coreinit", "MEMInitAllocatorForExpHeap", export_MEMInitAllocatorForExpHeap); +} +#pragma endregion +} + diff --git a/src/Cafe/OS/libs/coreinit/coreinit_MEM_ExpHeap.h b/src/Cafe/OS/libs/coreinit/coreinit_MEM_ExpHeap.h new file mode 100644 index 00000000..c111e29a --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_MEM_ExpHeap.h @@ -0,0 +1,52 @@ +#pragma once + +#include "Cafe/OS/libs/coreinit/coreinit_Spinlock.h" +#include "Cafe/OS/libs/coreinit/coreinit_MEM.h" + +namespace coreinit +{ + void expheap_load(); + +#define MEM_EXPHEAP_ALLOC_MODE_FIRST (0) +#define MEM_EXPHEAP_ALLOC_MODE_NEAR (1) +#define MEM_EXPHEAP_USE_ALIGN_MARGIN (2) + + enum class MEMExpHeapAllocDirection : uint32 + { + HEAD = 0, + TAIL = 1 + }; + + struct MBlockChain2_t + { + MEMPTR<struct MBlock2_t> headMBlock; // 0x00 + MEMPTR<struct MBlock2_t> tailMBlock; // 0x04 + }; + static_assert(sizeof(MBlockChain2_t) == 8); + + struct MEMExpHeapHead40_t + { + /* +0x00 */ MBlockChain2_t chainFreeBlocks; // 0x00 + /* +0x08 */ MBlockChain2_t chainUsedBlocks; // 0x08 + /* +0x10 */ uint16 groupID; + /* +0x12 */ uint16 fields; // Bit 0 -> Alloc mode, Bit 1 -> Allocate within alignment (create free blocks inside alignment padding) + }; + + static_assert(sizeof(MEMExpHeapHead40_t) == 0x14); + + struct MEMExpHeapHead2 : MEMHeapBase + { + // Base + /* +0x34 */ uint32be ukn34; + /* +0x38 */ uint32be ukn38; + /* +0x3C */ uint32be ukn3C; + /* +0x40 */ MEMExpHeapHead40_t expHeapHead; + }; + + static_assert(sizeof(MEMExpHeapHead2) == 0x54); + + MEMHeapHandle MEMCreateExpHeapEx(void* startAddress, uint32 size, uint32 createFlags); + void* MEMAllocFromExpHeapEx(MEMHeapHandle heap, uint32 size, sint32 alignment); + void MEMFreeToExpHeap(MEMHeapHandle heap, void* mem); + uint32 MEMGetAllocatableSizeForExpHeapEx(MEMHeapHandle heap, sint32 alignment); +} diff --git a/src/Cafe/OS/libs/coreinit/coreinit_MEM_FrmHeap.cpp b/src/Cafe/OS/libs/coreinit/coreinit_MEM_FrmHeap.cpp new file mode 100644 index 00000000..88c52901 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_MEM_FrmHeap.cpp @@ -0,0 +1,246 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/libs/coreinit/coreinit_MEM.h" +#include "Cafe/OS/libs/coreinit/coreinit_MEM_FrmHeap.h" + +namespace coreinit +{ + bool __FrmHeapDebug_IsValid(MEMFrmHeap* frmHeap, const char* funcName) + { + if (!frmHeap) + { + cemuLog_log(LogType::APIErrors, "{}: Heap is nullptr", funcName); + return false; + } + if (frmHeap->magic != MEMHeapMagic::FRAME_HEAP) + { + cemuLog_log(LogType::APIErrors, "{}: Heap has bad magic. Not initialized?", funcName); + return false; + } + return true; + } + + MEMFrmHeap* MEMCreateFrmHeapEx(void* memStart, uint32 size, uint32 createFlags) + { + cemu_assert_debug(memStart); + + uintptr_t startAddr = (uintptr_t)memStart; + uintptr_t endAddr = startAddr + size; + + // align and pad address + startAddr = (startAddr + 3) & ~3; + endAddr &= ~3; + + if (startAddr == 0) + return nullptr; + if (startAddr > endAddr || (endAddr - startAddr) < sizeof(MEMFrmHeap)) + return nullptr; + + MEMFrmHeap* frmHeap = (MEMFrmHeap*)startAddr; + MEMInitHeapBase(frmHeap, MEMHeapMagic::FRAME_HEAP, (void*)((uintptr_t)startAddr + sizeof(MEMFrmHeap)), (void*)endAddr, createFlags); + frmHeap->allocationHead = frmHeap->heapStart; + frmHeap->allocationTail = frmHeap->heapEnd; + frmHeap->recordedStates = nullptr; + + MEMHeapTable_Add(frmHeap); + return frmHeap; + } + + void* MEMDestroyFrmHeap(MEMFrmHeap* frmHeap) + { + if (!__FrmHeapDebug_IsValid(frmHeap, "MEMDestroyFrmHeap")) + return nullptr; + MEMBaseDestroyHeap(frmHeap); + MEMHeapTable_Remove(frmHeap); + return frmHeap; + } + + uint32 MEMGetAllocatableSizeForFrmHeapEx(MEMFrmHeap* frmHeap, sint32 alignment) + { + if (!__FrmHeapDebug_IsValid(frmHeap, "MEMGetAllocatableSizeForFrmHeapEx")) + return 0; + frmHeap->AcquireLock(); + uint32 allocatableSize = 0; + bool negativeAlignment = alignment < 0; + if (negativeAlignment) + alignment = -alignment; + if (!std::has_single_bit<uint32>((uint32)alignment)) + { + cemuLog_log(LogType::APIErrors, "MEMGetAllocatableSizeForFrmHeapEx(): Invalid alignment"); + return 0; + } + if (negativeAlignment) + { + cemu_assert_unimplemented(); + } + else + { + uint32 headAllocator = frmHeap->allocationHead.GetMPTR(); + uint32 tailAllocator = frmHeap->allocationTail.GetMPTR(); + uint32 allocStart = (headAllocator + alignment - 1) & ~(alignment - 1); + if (allocStart <= tailAllocator) + { + allocatableSize = tailAllocator - allocStart; + } + } + frmHeap->ReleaseLock(); + return allocatableSize; + } + + void* MEMiGetFreeStartForFrmHeap(MEMFrmHeap* heap) + { + if (!__FrmHeapDebug_IsValid(heap, "MEMiGetFreeStartForFrmHeap")) + return nullptr; + return heap->allocationHead; + } + + void* MEMiGetFreeEndForFrmHeap(MEMFrmHeap* heap) + { + if (!__FrmHeapDebug_IsValid(heap, "MEMiGetFreeEndForFrmHeap")) + return nullptr; + return heap->allocationTail; + } + + void* __FrmHeap_AllocFromHead(MEMFrmHeap* frmHeap, uint32 size, sint32 alignment) + { + uint32 head = frmHeap->allocationHead.GetMPTR(); + uint32 tail = frmHeap->allocationTail.GetMPTR(); + uint32 allocStart = (head + alignment - 1) & ~(alignment - 1); + if ((allocStart + size) <= tail) + { + auto prevHead = frmHeap->allocationHead; + frmHeap->allocationHead = allocStart + size; + if (frmHeap->HasOptionClear()) + memset(prevHead.GetPtr(), 0, frmHeap->allocationHead.GetMPTR() - prevHead.GetMPTR()); + return MEMPTR<void>(allocStart).GetPtr(); + } + return nullptr; + } + + void* __FrmHeap_AllocFromTail(MEMFrmHeap* frmHeap, uint32 size, sint32 alignment) + { + uint32 head = frmHeap->allocationHead.GetMPTR(); + uint32 tail = frmHeap->allocationTail.GetMPTR(); + uint32 allocStart = (tail - size) & ~(alignment - 1); + if (allocStart >= head) + { + auto prevTail = frmHeap->allocationTail; + frmHeap->allocationTail = allocStart; + if (frmHeap->HasOptionClear()) + memset(frmHeap->allocationTail.GetPtr(), 0, prevTail.GetMPTR() - frmHeap->allocationTail.GetMPTR()); + return MEMPTR<void>(allocStart).GetPtr(); + } + return nullptr; + } + + void* MEMAllocFromFrmHeapEx(MEMFrmHeap* frmHeap, uint32 size, sint32 alignment) + { + if (!__FrmHeapDebug_IsValid(frmHeap, "MEMAllocFromFrmHeapEx")) + return nullptr; + if (size == 0) + size = 4; + size = (size + 3) & ~3; // pad to 4 byte alignment + frmHeap->AcquireLock(); + void* mem; + if (alignment >= 0) + mem = __FrmHeap_AllocFromHead(frmHeap, size, alignment); + else + mem = __FrmHeap_AllocFromTail(frmHeap, size, -alignment); + frmHeap->ReleaseLock(); + return mem; + } + + void __FrmHeap_FreeFromHead(MEMFrmHeap* frmHeap) + { + cemu_assert_debug(frmHeap->recordedStates.IsNull()); + frmHeap->recordedStates = nullptr; + frmHeap->allocationHead = frmHeap->heapStart; + } + + void __FrmHeap_FreeFromTail(MEMFrmHeap* frmHeap) + { + cemu_assert_debug(frmHeap->recordedStates.IsNull()); + frmHeap->recordedStates = nullptr; + void* heapEnd = frmHeap->heapEnd.GetPtr(); + frmHeap->allocationTail = heapEnd; + } + + void MEMFreeToFrmHeap(MEMFrmHeap* frmHeap, FrmHeapMode mode) + { + if (!__FrmHeapDebug_IsValid(frmHeap, "MEMFreeToFrmHeap")) + return; + frmHeap->AcquireLock(); + + if ((mode & FrmHeapMode::Head) != 0) + __FrmHeap_FreeFromHead(frmHeap); + + if ((mode & FrmHeapMode::Tail) != 0) + __FrmHeap_FreeFromTail(frmHeap); + + frmHeap->ReleaseLock(); + } + + bool MEMRecordStateForFrmHeap(MEMFrmHeap* frmHeap, uint32 id) + { + if (!__FrmHeapDebug_IsValid(frmHeap, "MEMRecordStateForFrmHeap")) + return false; + frmHeap->AcquireLock(); + auto allocationHead = frmHeap->allocationHead; + auto allocationTail = frmHeap->allocationTail; + MEMFrmHeapRecordedState* rState = (MEMFrmHeapRecordedState*)__FrmHeap_AllocFromHead(frmHeap, sizeof(MEMFrmHeapRecordedState), 4); // modifies memHeap->allocationHead + cemu_assert_debug(rState); + if (!rState) + { + frmHeap->ReleaseLock(); + return false; + } + rState->id = id; + rState->allocationHead = allocationHead; + rState->allocationTail = allocationTail; + rState->prevRecordedState = frmHeap->recordedStates; + frmHeap->recordedStates = rState; + frmHeap->ReleaseLock(); + return true; + } + + bool MEMFreeByStateToFrmHeap(MEMFrmHeap* frmHeap, uint32 id) + { + if (!__FrmHeapDebug_IsValid(frmHeap, "MEMFreeByStateToFrmHeap")) + return false; + frmHeap->AcquireLock(); + // find matching state + MEMFrmHeapRecordedState* rState = frmHeap->recordedStates.GetPtr(); + while (rState) + { + if (id == 0) + break; + if (rState->id == id) + break; + rState = rState->prevRecordedState.GetPtr(); + } + if (!rState) + { + frmHeap->ReleaseLock(); + return false; + } + // apply state + frmHeap->allocationHead = rState->allocationHead; + frmHeap->allocationTail = rState->allocationTail; + frmHeap->recordedStates = rState->prevRecordedState; + frmHeap->ReleaseLock(); + return true; + } + + void InitializeMEMFrmHeap() + { + cafeExportRegister("coreinit", MEMCreateFrmHeapEx, LogType::CoreinitMem); + cafeExportRegister("coreinit", MEMDestroyFrmHeap, LogType::CoreinitMem); + cafeExportRegister("coreinit", MEMGetAllocatableSizeForFrmHeapEx, LogType::CoreinitMem); + cafeExportRegister("coreinit", MEMiGetFreeStartForFrmHeap, LogType::CoreinitMem); + cafeExportRegister("coreinit", MEMiGetFreeEndForFrmHeap, LogType::CoreinitMem); + cafeExportRegister("coreinit", MEMAllocFromFrmHeapEx, LogType::CoreinitMem); + cafeExportRegister("coreinit", MEMFreeToFrmHeap, LogType::CoreinitMem); + cafeExportRegister("coreinit", MEMFreeToFrmHeap, LogType::CoreinitMem); + cafeExportRegister("coreinit", MEMRecordStateForFrmHeap, LogType::CoreinitMem); + cafeExportRegister("coreinit", MEMFreeByStateToFrmHeap, LogType::CoreinitMem); + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_MEM_FrmHeap.h b/src/Cafe/OS/libs/coreinit/coreinit_MEM_FrmHeap.h new file mode 100644 index 00000000..43556ec4 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_MEM_FrmHeap.h @@ -0,0 +1,41 @@ +#pragma once +#include "Cafe/OS/libs/coreinit/coreinit_MEM.h" + +namespace coreinit +{ + struct MEMFrmHeapRecordedState + { + uint32be id; + MEMPTR<void> allocationHead; + MEMPTR<void> allocationTail; + MEMPTR<MEMFrmHeapRecordedState> prevRecordedState; + }; + static_assert(sizeof(MEMFrmHeapRecordedState) == 0x10); + + struct MEMFrmHeap : MEMHeapBase + { + /* +0x34 */ uint32be ukn34; + /* +0x38 */ uint32be ukn38; + /* +0x3C */ uint32be ukn3C; + /* +0x40 */ MEMPTR<void> allocationHead; + /* +0x44 */ MEMPTR<void> allocationTail; + /* +0x48 */ MEMPTR<MEMFrmHeapRecordedState> recordedStates; + }; + static_assert(sizeof(MEMFrmHeap) == 0x4C); + + enum class FrmHeapMode : uint32 + { + Head = (1 << 0), + Tail = (1 << 1), + All = (Head | Tail), + }; + + MEMFrmHeap* MEMCreateFrmHeapEx(void* memStart, uint32 size, uint32 createFlags); + void* MEMDestroyFrmHeap(MEMFrmHeap* frmHeap); + + void* MEMAllocFromFrmHeapEx(MEMFrmHeap* frmHeap, uint32 size, sint32 alignment); + void MEMFreeToFrmHeap(MEMFrmHeap* frmHeap, FrmHeapMode mode); + + void InitializeMEMFrmHeap(); +} +ENABLE_BITMASK_OPERATORS(coreinit::FrmHeapMode); \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_MEM_UnitHeap.cpp b/src/Cafe/OS/libs/coreinit/coreinit_MEM_UnitHeap.cpp new file mode 100644 index 00000000..e202df4d --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_MEM_UnitHeap.cpp @@ -0,0 +1,146 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/libs/coreinit/coreinit_MEM.h" +#include "Cafe/OS/libs/coreinit/coreinit_MEM_UnitHeap.h" +#include "Cafe/OS/libs/coreinit/coreinit_Spinlock.h" + +namespace coreinit +{ + void _MEMUnitHeap_IsValidHeap(MEMHeapHandle heap) + { + cemu_assert(heap != MEM_HEAP_INVALID_HANDLE); + cemu_assert(heap->magic == MEMHeapMagic::UNIT_HEAP); + } + + MEMHeapBase* MEMCreateUnitHeapEx(void* memStart, uint32 heapSize, uint32 blockSize, uint32 alignment, uint32 createFlags) + { + cemu_assert_debug(memStart != nullptr); + cemu_assert_debug(alignment % MIN_ALIGNMENT == 0); + cemu_assert_debug(MIN_ALIGNMENT <= alignment && alignment <= 32); + + uintptr_t startAddr = (uintptr_t)memStart; + uintptr_t endAddr = startAddr + heapSize; + + startAddr = (startAddr + MIN_ALIGNMENT_MINUS_ONE) & (~MIN_ALIGNMENT_MINUS_ONE); + endAddr &= (~MIN_ALIGNMENT_MINUS_ONE); + + if (startAddr > endAddr) + return nullptr; + + const uint32 alignmentMinusOne = alignment - 1; + + MEMUnitHeap* unitHeap = (MEMUnitHeap*)startAddr; + uintptr_t alignedStart = startAddr + sizeof(MEMUnitHeap) + alignmentMinusOne & ~((uintptr_t)alignmentMinusOne); + if (alignedStart > endAddr) + return nullptr; + + blockSize = blockSize + alignmentMinusOne & ~alignmentMinusOne; + uint32 totalBlockSize = (uint32)(endAddr - alignedStart); + uint32 numBlocks = totalBlockSize / blockSize; + if (numBlocks == 0) + return nullptr; + + MEMInitHeapBase(unitHeap, MEMHeapMagic::UNIT_HEAP, (void*)alignedStart, (void*)(alignedStart + (numBlocks * blockSize)), createFlags); + + unitHeap->firstFreeBlock = (MEMUnitHeapBlock*)alignedStart; + unitHeap->blockSize = blockSize; + + MEMUnitHeapBlock* currentBlock = (MEMUnitHeapBlock*)alignedStart; + for (uint32 i = 0; i < numBlocks - 1; ++i) + { + MEMUnitHeapBlock* nextBlock = (MEMUnitHeapBlock*)((uintptr_t)currentBlock + blockSize); + currentBlock->nextBlock = nextBlock; + currentBlock = nextBlock; + } + + currentBlock->nextBlock = nullptr; + + if ((MEMHeapHandle*)startAddr != nullptr) + { + MEMHeapTable_Add((MEMHeapHandle)startAddr); + } + + return (MEMHeapBase*)startAddr; + } + + void* MEMDestroyUnitHeap(MEMHeapHandle heap) + { + _MEMUnitHeap_IsValidHeap(heap); + MEMBaseDestroyHeap(heap); + MEMHeapTable_Remove(heap); + return heap; + } + + uint32 MEMCalcHeapSizeForUnitHeap(uint32 blockSize, uint32 blockCount, uint32 alignment) + { + uint32 alignedBlockSize = (blockSize + (alignment - 1)) & ~(alignment - 1); + uint32 blockTotalSize = blockCount * alignedBlockSize; + uint32 heapSize = blockTotalSize + (alignment - 4) + sizeof(MEMUnitHeap); + return heapSize; + } + + uint32 MEMCountFreeBlockForUnitHeap(coreinit::MEMUnitHeap* heap) + { + _MEMUnitHeap_IsValidHeap(heap); + heap->AcquireLock(); + MEMUnitHeapBlock* currentBlock = heap->firstFreeBlock; + uint32 blockCount = 0; + while (currentBlock) + { + blockCount++; + currentBlock = currentBlock->nextBlock; + } + heap->ReleaseLock(); + return blockCount; + } + + void* MEMAllocFromUnitHeap(MEMUnitHeap* heap) + { + _MEMUnitHeap_IsValidHeap(heap); + heap->AcquireLock(); + MEMUnitHeapBlock* currentBlock = heap->firstFreeBlock; + if (!currentBlock) + { + heap->ReleaseLock(); + return nullptr; + } + // remove from list of free blocks + heap->firstFreeBlock = currentBlock->nextBlock; + // fill block + if (heap->HasOptionClear()) + { + memset(currentBlock, 0, heap->blockSize); + } + else if (heap->HasOptionFill()) + { + memset(currentBlock, coreinit::MEMGetFillValForHeap(coreinit::HEAP_FILL_TYPE::ON_ALLOC), heap->blockSize); + } + heap->ReleaseLock(); + return currentBlock; + } + + void MEMFreeToUnitHeap(MEMUnitHeap* heap, void* mem) + { + _MEMUnitHeap_IsValidHeap(heap); + if (!mem) + return; + heap->AcquireLock(); + cemu_assert_debug(mem >= heap->heapStart.GetPtr() && mem < heap->heapEnd.GetPtr()); + // add to list of free blocks + MEMUnitHeapBlock* releasedBlock = (MEMUnitHeapBlock*)mem; + releasedBlock->nextBlock = heap->firstFreeBlock; + heap->firstFreeBlock = releasedBlock; + if (heap->HasOptionFill()) + memset(releasedBlock, coreinit::MEMGetFillValForHeap(coreinit::HEAP_FILL_TYPE::ON_FREE), heap->blockSize); + heap->ReleaseLock(); + } + + void InitializeMEMUnitHeap() + { + cafeExportRegister("coreinit", MEMCreateUnitHeapEx, LogType::CoreinitMem); + cafeExportRegister("coreinit", MEMDestroyUnitHeap, LogType::CoreinitMem); + cafeExportRegister("coreinit", MEMCalcHeapSizeForUnitHeap, LogType::CoreinitMem); + cafeExportRegister("coreinit", MEMCountFreeBlockForUnitHeap, LogType::CoreinitMem); + cafeExportRegister("coreinit", MEMAllocFromUnitHeap, LogType::CoreinitMem); + cafeExportRegister("coreinit", MEMFreeToUnitHeap, LogType::CoreinitMem); + } +} diff --git a/src/Cafe/OS/libs/coreinit/coreinit_MEM_UnitHeap.h b/src/Cafe/OS/libs/coreinit/coreinit_MEM_UnitHeap.h new file mode 100644 index 00000000..1bd8d8b1 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_MEM_UnitHeap.h @@ -0,0 +1,24 @@ +#pragma once +#include "Cafe/OS/libs/coreinit/coreinit_MEM.h" + +namespace coreinit +{ + struct MEMUnitHeapBlock + { + MEMPTR<MEMUnitHeapBlock> nextBlock; + }; + static_assert(sizeof(MEMUnitHeapBlock) == 4); + + struct MEMUnitHeap : public MEMHeapBase + { + /* +0x34 */ uint32 padding034; + /* +0x38 */ uint32 padding038; + /* +0x3C */ uint32 padding03C; + /* +0x40 */ MEMPTR<MEMUnitHeapBlock> firstFreeBlock; + /* +0x44 */ uint32be blockSize; + }; + static_assert(sizeof(MEMUnitHeap) == 0x48); + + MEMHeapBase* MEMCreateUnitHeapEx(void* memStart, uint32 heapSize, uint32 memBlockSize, uint32 alignment, uint32 createFlags); + void* MEMDestroyUnitHeap(MEMHeapHandle hHeap); +} diff --git a/src/Cafe/OS/libs/coreinit/coreinit_MPQueue.cpp b/src/Cafe/OS/libs/coreinit/coreinit_MPQueue.cpp new file mode 100644 index 00000000..8fc5a935 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_MPQueue.cpp @@ -0,0 +1,516 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/libs/coreinit/coreinit_MPQueue.h" +#include "Cafe/OS/libs/coreinit/coreinit_Time.h" +#include "Cafe/HW/Espresso/PPCCallback.h" +#include "util/helpers/fspinlock.h" + +// titles that utilize MP task queue: Yoshi's Woolly World, Fast Racing Neo, Tokyo Mirage Sessions, Mii Maker + +#define AcquireMPQLock() s_workaroundSpinlock.acquire() +#define ReleaseMPQLock() s_workaroundSpinlock.release() + +namespace coreinit +{ + + FSpinlock s_workaroundSpinlock; // workaround for a race condition + /* + Race condition pseudo-code: + + WorkerThreads: + while( task = MPDequeTask() ) MPRunTask(task); + + MainThread: + QueueTasks(); + // wait and reset + MPWaitForTaskQWithTimeout(DONE) + MPTermTaskQ() + MPInitTaskQ() + + The race condition then happens when a worker thread calls MPDequeTask()/MPRunTask while MPInitTaskQ() is being executed on the main thread. + Since MPInitTaskQ() (re)initializes the internal spinlock it's not thread-safe, leading to a corruption of the spinlock state for other threads + + We work around this by using a global spinlock instead of the taskQ specific one + */ + + + void MPInitTask(MPTask* task, void* func, void* data, uint32 size) + { + s_workaroundSpinlock.acquire(); + task->thisptr = task; + + task->coreIndex = PPC_CORE_COUNT; + + task->taskFunc.func = func; + task->taskFunc.data = data; + task->taskFunc.size = size; + + task->taskState = MP_TASK_STATE_INIT; + + task->userdata = nullptr; + task->runtime = 0; + s_workaroundSpinlock.release(); + } + + bool MPTermTask(MPTask* task) + { + return true; + } + + bool MPRunTask(MPTask* task) + { + if (task->taskState != MP_TASK_STATE_READY) + return false; + + auto* taskQ = task->taskQ.GetPtr(); + + if(taskQ->state != MP_TASKQ_STATE_STOPPING && taskQ->state != MP_TASKQ_STATE_STOP) + { + AcquireMPQLock(); // OSUninterruptibleSpinLock_Acquire(&taskQ->spinlock); + if (taskQ->state == MP_TASKQ_STATE_STOPPING || taskQ->state == MP_TASKQ_STATE_STOP) + { + ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskQ->spinlock); + return false; + } + + taskQ->taskReadyCount = taskQ->taskReadyCount - 1; + taskQ->taskRunCount = taskQ->taskRunCount + 1; + ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskQ->spinlock); + + const auto startTime = OSGetSystemTime(); + + task->coreIndex = OSGetCoreId(); + task->taskState = MP_TASK_STATE_RUN; + + task->taskFunc.result = PPCCoreCallback(task->taskFunc.func, task->taskFunc.data, task->taskFunc.size); + + task->taskState = MP_TASK_STATE_DONE; + + const auto endTime = OSGetSystemTime(); + task->runtime = endTime - startTime; + + cemu_assert_debug(taskQ->state != MP_TASKQ_STATE_DONE); + + AcquireMPQLock(); // OSUninterruptibleSpinLock_Acquire(&taskQ->spinlock); + taskQ->taskRunCount = taskQ->taskRunCount - 1; + taskQ->taskDoneCount[1] = taskQ->taskDoneCount[1] + 1; + if (taskQ->state == MP_TASKQ_STATE_STOPPING && taskQ->taskRunCount == 0) + taskQ->state = MP_TASKQ_STATE_STOP; + + if (taskQ->taskCount == taskQ->taskDoneCount[1]) + taskQ->state = MP_TASKQ_STATE_DONE; + + ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskQ->spinlock); + + return true; + } + + return false; + } + + bool MPGetTaskInfo(MPTask* task, MPTaskInfo* info) + { + info->state = task->taskState; + info->coreIndex = task->coreIndex; + info->runtime = task->runtime; + // not setting function result? + return true; + } + + void* MPGetTaskUserData(MPTask* task) + { + return task->userdata.GetPtr(); + } + + void MPSetTaskUserData(MPTask* task, void* userdata) + { + task->userdata = userdata; + } + + void MPInitTaskQ(MPTaskQ* taskQ, MPTask** tasks, uint32 taskCount) + { + AcquireMPQLock(); + taskQ->thisptr = taskQ; + OSInitSpinLock(&taskQ->spinlock); + taskQ->state = MP_TASKQ_STATE_INIT; + taskQ->taskReadyCount = 0; + taskQ->taskCount = 0; + taskQ->taskRunCount = 0; + for(uint32 i = 0; i < OSGetCoreCount(); ++i) + { + taskQ->taskDoneCount[i] = 0; + taskQ->nextIndex[i] = 0; + taskQ->endIndex[i] = 0; + } + + taskQ->taskQueue = (MEMPTR<MPTask>*)tasks; + taskQ->taskQueueSize = taskCount; + + ReleaseMPQLock(); + } + + bool MPEnqueTask(MPTaskQ* taskq, MPTask* task) + { + bool result = false; + if(task->taskState == MP_TASK_STATE_INIT) + { + AcquireMPQLock(); // OSUninterruptibleSpinLock_Acquire(&taskq->spinlock); + + const uint32 taskQState = taskq->state; + if((uint32)taskq->endIndex[1] < taskq->taskQueueSize + && (taskQState == MP_TASKQ_STATE_INIT || taskQState == MP_TASKQ_STATE_RUN || taskQState == MP_TASKQ_STATE_STOPPING || taskQState == MP_TASKQ_STATE_STOP || taskQState == MP_TASKQ_STATE_DONE)) + { + task->taskQ = taskq; + task->taskState = MP_TASK_STATE_READY; + + taskq->thisptr = taskq; + + const uint32 endIndex = taskq->endIndex[1]; + taskq->endIndex[1] = endIndex + 1; + + taskq->taskCount = taskq->taskCount + 1; + taskq->taskReadyCount = taskq->taskReadyCount + 1; + taskq->taskQueue[endIndex] = task; + + if (taskQState == MP_TASKQ_STATE_DONE) + taskq->state = MP_TASKQ_STATE_RUN; + + result = true; + } + + ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskq->spinlock); + } + + return result; + } + + bool MPTermTaskQ(MPTaskQ* taskq) + { + // workaround code for TMS + AcquireMPQLock(); // OSUninterruptibleSpinLock_Acquire(&taskq->spinlock); + //if ((uint32)taskq->taskReadyCount > 0 && taskq->state == MP_TASKQ_STATE_RUN) + if (taskq->state == MP_TASKQ_STATE_RUN) + { + taskq->state = MP_TASKQ_STATE_STOP; + } + + while (taskq->taskRunCount != 0) + { + // wait for tasks to finish + ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskq->spinlock); + OSYieldThread(); + AcquireMPQLock(); // OSUninterruptibleSpinLock_Acquire(&taskq->spinlock); + } + ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskq->spinlock); + return true; + } + + bool MPGetTaskQInfo(MPTaskQ* taskq, MPTaskQInfo* info) + { + AcquireMPQLock(); // OSUninterruptibleSpinLock_Acquire(&taskq->spinlock); + info->state = taskq->state; + info->taskCount = taskq->taskCount; + info->taskReadyCount = taskq->taskReadyCount; + info->taskRunCount = taskq->taskRunCount; + info->taskDoneCount = taskq->taskDoneCount[1]; + ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskq->spinlock); + return true; + } + + bool MPStartTaskQ(MPTaskQ* taskq) + { + bool result = false; + AcquireMPQLock(); // OSUninterruptibleSpinLock_Acquire(&taskq->spinlock); + if (taskq->state == MP_TASKQ_STATE_INIT || taskq->state == MP_TASKQ_STATE_STOP) + { + taskq->state = MP_TASKQ_STATE_RUN; + result = true; + } + + ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskq->spinlock); + return result; + } + + bool MPRunTasksFromTaskQ(MPTaskQ* taskq, int granularity) + { + uint32 result = 0; + while (true) + { + if (taskq->state != MP_TASKQ_STATE_RUN) + return (taskq->state & MP_TASKQ_STATE_DONE) != 0; + + AcquireMPQLock(); // OSUninterruptibleSpinLock_Acquire(&taskq->spinlock); + const auto nextIndex = taskq->nextIndex[1]; + const auto endIndex = taskq->endIndex[1]; + if (nextIndex == endIndex) + break; + + auto newNextIndex = nextIndex + granularity; + if (endIndex < nextIndex + granularity) + newNextIndex = endIndex; + + const auto workCount = (newNextIndex - nextIndex); + + taskq->nextIndex[1] = newNextIndex; + taskq->taskReadyCount = taskq->taskReadyCount - workCount; + taskq->taskRunCount = taskq->taskRunCount + workCount; + ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskq->spinlock); + + // since we are having a granularity parameter, we might want to give the scheduler the chance for other stuff when having multiple tasks + if(result != 0) + PPCCore_switchToScheduler(); + + for (int i = nextIndex; i < newNextIndex; ++i) + { + const auto startTime = OSGetSystemTime(); + + const auto& task = taskq->taskQueue[i]; + result = task->thisptr.GetMPTR(); + + task->taskState = MP_TASK_STATE_RUN; + task->coreIndex = OSGetCoreId(); + + task->taskFunc.result = PPCCoreCallback(task->taskFunc.func, task->taskFunc.data, task->taskFunc.size); + + task->taskState = MP_TASK_STATE_DONE; + + const auto endTime = OSGetSystemTime(); + task->runtime = endTime - startTime; + } + + AcquireMPQLock(); // OSUninterruptibleSpinLock_Acquire(&taskq->spinlock); + const auto runRemaining = taskq->taskRunCount - workCount; + taskq->taskRunCount = runRemaining; + + const auto doneCount = taskq->taskDoneCount[1] + workCount; + taskq->taskDoneCount[1] = doneCount; + + if (taskq->state == 4 && runRemaining == 0) + taskq->state = MP_TASKQ_STATE_STOP; + + if (taskq->taskCount == doneCount) + taskq->state = MP_TASKQ_STATE_DONE; + + ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskq->spinlock); + } + + ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskq->spinlock); + return result != 0; + } + + bool MPStopTaskQ(MPTaskQ* taskq) + { + bool result = false; + AcquireMPQLock(); // OSUninterruptibleSpinLock_Acquire(&taskq->spinlock); + if (taskq->state == MP_TASKQ_STATE_RUN) + { + taskq->state = MP_TASKQ_STATE_STOPPING; + if (taskq->taskRunCount == 0) + taskq->state = MP_TASKQ_STATE_STOP; + + result = true; + } + ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskq->spinlock); + return result; + } + + bool MPWaitTaskQ(MPTaskQ* taskQ, uint32 waitState) + { + bool waitRun = (waitState & MP_TASKQ_STATE_RUN) != 0; + bool waitStop = (waitState & MP_TASKQ_STATE_STOP) != 0; + bool waitDone = (waitState & MP_TASKQ_STATE_DONE) != 0; + + size_t loopCounter = 0; + while (waitStop || waitDone || waitRun) + { + const uint32 state = taskQ->state; + if (waitRun && HAS_FLAG(state, MP_TASKQ_STATE_RUN)) + { + waitRun = false; + waitDone = false; + continue; + } + + if (waitStop && HAS_FLAG(state, MP_TASKQ_STATE_STOP)) + { + waitStop = false; + waitDone = false; + continue; + } + + if (waitDone && HAS_FLAG(state, MP_TASKQ_STATE_DONE)) + { + waitDone = false; + waitRun = false; + waitStop = false; + continue; + } + if (loopCounter > 0) + coreinit::OSSleepTicks(EspressoTime::ConvertNsToTimerTicks(50000)); // sleep thread for 0.05ms to give other threads a chance to run (avoids softlocks in YWW) + else + PPCCore_switchToScheduler(); + loopCounter++; + } + return true; + } + + bool MPWaitTaskQWithTimeout(MPTaskQ* taskQ, uint32 waitState, sint64 timeout) + { + bool waitRun = (waitState & MP_TASKQ_STATE_RUN) != 0; + bool waitStop = (waitState & MP_TASKQ_STATE_STOP) != 0; + bool waitDone = (waitState & MP_TASKQ_STATE_DONE) != 0; + + const auto startTime = OSGetSystemTime(); + const auto timerTicks = EspressoTime::ConvertNsToTimerTicks(timeout); + const auto endTime = startTime + timerTicks; + + while (waitStop || waitDone || waitRun) + { + const uint32 state = taskQ->state; + if (waitRun && HAS_FLAG(state, MP_TASKQ_STATE_RUN)) + { + waitRun = false; + waitDone = false; + continue; + } + + if (waitStop && HAS_FLAG(state, MP_TASKQ_STATE_STOP)) + { + waitStop = false; + waitDone = false; + continue; + } + + if (waitDone && HAS_FLAG(state, MP_TASKQ_STATE_DONE)) + { + waitDone = false; + waitRun = false; + waitStop = false; + continue; + } + + if (OSGetSystemTime() >= endTime) + { + if (waitState == MP_TASKQ_STATE_DONE) + cemuLog_log(LogType::Force, "MPWaitTaskQWithTimeout(): Timeout occurred while waiting for done-only state"); + return false; + } + + PPCCore_switchToScheduler(); + } + return true; + } + + MPTask* MPDequeTask(MPTaskQ* taskq) + { + MPTask* result = nullptr; + if (taskq->state == MP_TASKQ_STATE_RUN) + { + AcquireMPQLock(); // OSUninterruptibleSpinLock_Acquire(&taskq->spinlock); + if (taskq->state == MP_TASKQ_STATE_RUN && taskq->nextIndex[1] != taskq->endIndex[1]) + { + result = taskq->taskQueue[taskq->nextIndex[1]].GetPtr(); + taskq->nextIndex[1] = taskq->nextIndex[1] + 1; + } + ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskq->spinlock); + } + return result; + } + + uint32 MPDequeTasks(MPTaskQ* taskq, MPTask** tasks, sint32 maxTasks) + { + uint32 dequeCount = 0; + if (taskq->state == MP_TASKQ_STATE_RUN) + { + AcquireMPQLock(); // OSUninterruptibleSpinLock_Acquire(&taskq->spinlock); + if (taskq->state == MP_TASKQ_STATE_RUN) + { + auto nextIndex = (sint32)taskq->nextIndex[1]; + auto newEndIndex = nextIndex + maxTasks; + if (taskq->endIndex[1] < nextIndex + maxTasks) + newEndIndex = taskq->endIndex[1]; + + dequeCount = newEndIndex - nextIndex; + taskq->nextIndex[1] = newEndIndex; + + for(int i = 0; nextIndex < newEndIndex; ++nextIndex, ++i) + { + tasks[i] = taskq->taskQueue[nextIndex].GetPtr(); + } + + auto idx = 0; + while (nextIndex < newEndIndex) + { + tasks[idx] = taskq->taskQueue[nextIndex].GetPtr(); + nextIndex = nextIndex + 1; + idx = idx + 1; + } + } + + ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskq->spinlock); + } + return dequeCount; + } + + bool MPResetTaskQ(MPTaskQ* taskq) + { + debug_printf("MPResetTaskQ called\n"); + bool result = false; + AcquireMPQLock(); // OSUninterruptibleSpinLock_Acquire(&taskq->spinlock); + if (taskq->state == MP_TASKQ_STATE_DONE || taskq->state == MP_TASKQ_STATE_STOP) + { + taskq->state = MP_TASKQ_STATE_INIT; + taskq->taskRunCount = 0; + taskq->taskCount = taskq->endIndex[1]; + taskq->taskReadyCount = taskq->endIndex[1]; + + for(uint32 i = 0; i < OSGetCoreCount(); ++i) + { + taskq->taskDoneCount[i] = 0; + taskq->nextIndex[i] = 0; + } + + for(uint32 i = 0; i < taskq->taskCount; ++i) + { + const auto& task = taskq->taskQueue[i]; + task->taskFunc.result = 0; + + task->coreIndex = PPC_CORE_COUNT; + task->runtime = 0; + task->taskState = MP_TASK_STATE_READY; + } + + result = true; + } + + ReleaseMPQLock(); // OSUninterruptibleSpinLock_Release(&taskq->spinlock); + return result; + } + + void InitializeMP() + { + // task + cafeExportRegister("coreinit", MPInitTask, LogType::CoreinitMP); + cafeExportRegister("coreinit", MPTermTask, LogType::CoreinitMP); + cafeExportRegister("coreinit", MPRunTask, LogType::CoreinitMP); + cafeExportRegister("coreinit", MPGetTaskInfo, LogType::CoreinitMP); + cafeExportRegister("coreinit", MPGetTaskUserData, LogType::CoreinitMP); + cafeExportRegister("coreinit", MPSetTaskUserData, LogType::CoreinitMP); + + // taskq + cafeExportRegister("coreinit", MPInitTaskQ, LogType::CoreinitMP); + cafeExportRegister("coreinit", MPResetTaskQ, LogType::CoreinitMP); + + cafeExportRegister("coreinit", MPEnqueTask, LogType::CoreinitMP); + cafeExportRegister("coreinit", MPDequeTask, LogType::CoreinitMP); + cafeExportRegister("coreinit", MPDequeTasks, LogType::CoreinitMP); + + cafeExportRegister("coreinit", MPRunTasksFromTaskQ, LogType::CoreinitMP); + cafeExportRegister("coreinit", MPStartTaskQ, LogType::CoreinitMP); + cafeExportRegister("coreinit", MPWaitTaskQ, LogType::CoreinitMP); + cafeExportRegister("coreinit", MPWaitTaskQWithTimeout, LogType::CoreinitMP); + cafeExportRegister("coreinit", MPStopTaskQ, LogType::CoreinitMP); + + cafeExportRegister("coreinit", MPTermTaskQ, LogType::CoreinitMP); + cafeExportRegister("coreinit", MPGetTaskQInfo, LogType::CoreinitMP); + } +} diff --git a/src/Cafe/OS/libs/coreinit/coreinit_MPQueue.h b/src/Cafe/OS/libs/coreinit/coreinit_MPQueue.h new file mode 100644 index 00000000..bd68d627 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_MPQueue.h @@ -0,0 +1,106 @@ +#pragma once + +#include "Cafe/OS/libs/coreinit/coreinit.h" +#include "Cafe/OS/libs/coreinit/coreinit_Spinlock.h" + +namespace coreinit +{ + enum MPTaskState + { + MP_TASK_STATE_INIT = (1 << 0), + MP_TASK_STATE_READY = (1 << 1), + MP_TASK_STATE_RUN = (1 << 2), + MP_TASK_STATE_DONE = (1 << 3) + }; + + enum MPTaskQState + { + MP_TASKQ_STATE_INIT = (1 << 0), + MP_TASKQ_STATE_RUN = (1 << 1), + MP_TASKQ_STATE_STOPPING = (1 << 2), + MP_TASKQ_STATE_STOP = (1 << 3), + MP_TASKQ_STATE_DONE = (1 << 4) + }; + + struct MPTaskFunction + { + /* +0x00 */ MEMPTR<void> func; + /* +0x04 */ MEMPTR<void> data; + /* +0x08 */ uint32be size; + /* +0x0C */ uint32be result; + }; + static_assert(sizeof(MPTaskFunction) == 0x10); + +#pragma pack(1) + + struct MPTask + { + /* +0x00 */ MEMPTR<void> thisptr; + /* +0x04 */ MEMPTR<struct MPTaskQ> taskQ; + /* +0x08 */ uint32be taskState; + /* +0x0C */ MPTaskFunction taskFunc; + /* +0x1C */ uint32be coreIndex; + /* +0x20 */ sint64be runtime; + /* +0x28 */ MEMPTR<void> userdata; + }; + static_assert(sizeof(MPTask) == 0x2C); + +#pragma pack() + + struct MPTaskQ + { + /* +0x00 */ MEMPTR<void> thisptr; + /* +0x04 */ uint32be state; + /* +0x08 */ uint32be taskCount; + /* +0x0C */ uint32be taskReadyCount; + /* +0x10 */ uint32be taskRunCount; + /* +0x14 */ uint32be taskDoneCount[PPC_CORE_COUNT]; + /* +0x20 */ sint32be nextIndex[PPC_CORE_COUNT]; + /* +0x2C */ sint32be endIndex[PPC_CORE_COUNT]; + /* +0x38 */ MEMPTR<MEMPTR<MPTask>> taskQueue; + /* +0x3C */ uint32be taskQueueSize; + /* +0x40 */ OSSpinLock spinlock; + }; + static_assert(sizeof(MPTaskQ) == 0x50); + + struct MPTaskQInfo + { + /* +0x00 */ uint32be state; + /* +0x04 */ uint32be taskCount; + /* +0x08 */ uint32be taskReadyCount; + /* +0x0C */ uint32be taskRunCount; + /* +0x10 */ uint32be taskDoneCount; + }; + static_assert(sizeof(MPTaskQInfo) == 0x14); + + struct MPTaskInfo + { + /* +0x00 */ uint32be state; + /* +0x04 */ uint32be funcResult; + /* +0x08 */ uint32be coreIndex; + /* +0x0C */ sint64be runtime; + }; + static_assert(sizeof(MPTaskQInfo) == 0x14); + + void MPInitTask(MPTask* task, void* func, void* data, uint32 size); + bool MPTermTask(MPTask* task); + bool MPRunTask(MPTask* task); + bool MPGetTaskInfo(MPTask* task, MPTaskInfo* info); + void* MPGetTaskUserData(MPTask* task); + void MPSetTaskUserData(MPTask* task, void* userdata); + + void MPInitTaskQ(MPTaskQ* taskq, MPTask** tasks, uint32 taskCount); + bool MPEnqueTask(MPTaskQ* taskq, MPTask* task); + bool MPTermTaskQ(MPTaskQ* taskq); + bool MPGetTaskQInfo(MPTaskQ* taskq, MPTaskQInfo* info); + bool MPStartTaskQ(MPTaskQ* taskq); + bool MPRunTasksFromTaskQ(MPTaskQ* taskq, int granularity); + bool MPStopTaskQ(MPTaskQ* taskq); + bool MPWaitTaskQ(MPTaskQ* taskq, uint32 waitState); + bool MPWaitTaskQWithTimeout(MPTaskQ* taskq, uint32 waitState, sint64 timeout); + MPTask* MPDequeTask(MPTaskQ* taskq); + uint32 MPDequeTasks(MPTaskQ* taskq, MPTask** tasks, sint32 maxTasks); + bool MPResetTaskQ(MPTaskQ* taskq); + + void InitializeMP(); +} diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Memory.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Memory.cpp new file mode 100644 index 00000000..f6ac1116 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_Memory.cpp @@ -0,0 +1,225 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "coreinit_Memory.h" +#include "Cafe/HW/Latte/Core/LatteBufferCache.h" +#include "Cafe/OS/RPL/rpl.h" +#include "Cafe/GraphicPack/GraphicPack2.h" +#include "Cafe/CafeSystem.h" + +namespace coreinit +{ + + void DCInvalidateRange(MPTR addr, uint32 size) + { + MPTR addrEnd = (addr + size + 0x1F) & ~0x1F; + addr &= ~0x1F; + //LatteBufferCache_notifyDCFlush(addr, addrEnd - addr); + } + + void DCFlushRange(MPTR addr, uint32 size) + { + MPTR addrEnd = (addr + size + 0x1F) & ~0x1F; + addr &= ~0x1F; + LatteBufferCache_notifyDCFlush(addr, addrEnd - addr); + } + + void DCFlushRangeNoSync(MPTR addr, uint32 size) + { + MPTR addrEnd = (addr + size + 0x1F) & ~0x1F; + addr &= ~0x1F; + LatteBufferCache_notifyDCFlush(addr, addrEnd - addr); + } + + void DCStoreRange(MPTR addr, uint32 size) + { + MPTR addrEnd = (addr + size + 0x1F) & ~0x1F; + addr &= ~0x1F; + //LatteBufferCache_notifyDCFlush(addr, addrEnd - addr); + } + + void DCStoreRangeNoSync(MPTR addr, uint32 size) + { + MPTR addrEnd = (addr + size + 0x1F) & ~0x1F; + addr &= ~0x1F; + LatteBufferCache_notifyDCFlush(addr, addrEnd - addr); + } + + void DCZeroRange(MPTR addr, uint32 size) + { + MPTR alignedAddr = addr & ~31; + uint32 cachlineOffset = addr & 31; + uint32 blocks = (cachlineOffset + size + 31) / 32; + + if (blocks > 0) + { + memset(memory_getPointerFromVirtualOffset(alignedAddr), 0x00, blocks * 32); + LatteBufferCache_notifyDCFlush(alignedAddr, blocks * 32); + } + } + + bool OSIsAddressRangeDCValid(uint32 startOffset, uint32 range) + { + uint32 endOffset = startOffset + range - 1; + uint32 boundaryLow = 0xE8000000; + uint32 boundaryHigh = 0xEC000000; + if (startOffset < boundaryLow || startOffset >= boundaryHigh) + return false; + if (endOffset < boundaryLow || endOffset >= boundaryHigh) + return false; + return true; + } + + void* coreinit_memset(void* dst, uint32 value, uint32 size) + { + memset(dst, value, size); + return dst; + } + + void* coreinit_memcpy(MEMPTR<void> dst, MEMPTR<void> src, uint32 size) + { + if (dst.GetMPTR() == 0xFFFFFFFF) + { + // games this was seen in: The Swapper + // this may be a bug in the game. The last few bytes of the address space are writable, but wrap-around behavior of COS memcpy is unknown + cemu_assert_debug(false); + } + if (size > 0) + { + memcpy(dst.GetPtr(), src.GetPtr(), size); + // always flushes the cache! + LatteBufferCache_notifyDCFlush(dst.GetMPTR(), size); + } + + return dst.GetPtr(); + } + + void* coreinit_memmove(MEMPTR<void> dst, void* src, uint32 size) + { + if (size > 0) + { + memmove(dst.GetPtr(), src, size); + // always flushes the cache! + LatteBufferCache_notifyDCFlush(dst.GetMPTR(), size); + } + return dst.GetPtr(); + } + + void* OSBlockMove(MEMPTR<void> dst, MEMPTR<void> src, uint32 size, bool flushDC) + { + if (size > 0) + { + memmove(dst.GetPtr(), src.GetPtr(), size); + if (flushDC) + LatteBufferCache_notifyDCFlush(dst.GetMPTR(), size); + } + return dst.GetPtr(); + } + + void* OSBlockSet(MEMPTR<void> dst, uint32 value, uint32 size) + { + memset(dst.GetPtr(), value&0xFF, size); + return dst.GetPtr(); + } + + MPTR OSEffectiveToPhysical(MPTR effectiveAddr) + { + MPTR physicalAddr = memory_virtualToPhysical(effectiveAddr); + return physicalAddr; + } + + void OSMemoryBarrier(PPCInterpreter_t* hCPU) + { + // no-op + } + + void OSGetMemBound(sint32 memType, MPTR* offsetOutput, uint32* sizeOutput) + { + MPTR memAddr = MPTR_NULL; + uint32 memSize = 0; + + /* + Data taken from browser dump: + type start size + MEM1 0xF4000000 0x02000000 + MEM2 0x106DE000 0x170C2000 + */ + if (memType == 1) + { + // MEM1 + memAddr = mmuRange_MEM1.getBase(); + memSize = mmuRange_MEM1.getSize(); + } + else if (memType == 2) + { + // MEM2 + uint32 currentRPLAllocatorOffset = RPLLoader_GetDataAllocatorAddr(); + + // due to differences in our library implementations we currently allocate less memory for the OS/RPLs than on the actual hardware, + // as a result more memory is available to games + // however, some games crash due to internal overflows if there is too much memory available + + // here we artificially reduce the available memory for the affected games + uint64 titleId = CafeSystem::GetForegroundTitleId(); + if ( + titleId == 0x0005000010132400ULL || // Lego Marvel Super Heroes (EU) + titleId == 0x0005000010132B00ULL || // Lego Marvel Super Heroes (US) + titleId == 0x0005000010194200ull || // Lego Dimensions (US) + titleId == 0x0005000010195D00ull || // Lego Dimensions (EU) + titleId == 0x00050000101A6200ull || // Lego Jurassic World (US) + titleId == 0x00050000101A5C00 || // Lego Jurassic World (EU) + titleId == 0x000500001014DE00 || // The Lego Movie Videogame (US) + titleId == 0x000500001014E000 || // The Lego Movie Videogame (EU) + titleId == 0x0005000010168D00 || // Lego The Hobbit (EU) + titleId == 0x000500001016A700 || // Lego The Hobbit (JP) + // The Hobbit US title id? + titleId == 0x00050000101DAB00 || // Lego Star Wars: The Force Awakens (US) + titleId == 0x00050000101DAA00 || // Lego Star Wars: The Force Awakens (EU) + // LEGO Batman 3: BEYOND GOTHAM + titleId == 0x000500001016A400 || // EU + titleId == 0x000500001016AD00 || // US + // Lego Marvel Avengers + titleId == 0x00050000101BE900 || // EU + titleId == 0x00050000101BEF00 || // US + // LEGO BATMAN 2: DC Super Heroes + titleId == 0x0005000010135500 || // EU + titleId == 0x0005000010135E00 // US + ) + { + forceLogDebug_printf("Hack: Reduce available memory to simulate loaded RPLs"); + currentRPLAllocatorOffset += (48 * 1024 * 1024); // 48MB + } + memAddr = currentRPLAllocatorOffset; + memSize = mmuRange_MEM2.getEnd() - currentRPLAllocatorOffset; + } + else + { + cemu_assert_debug(false); + } + if (offsetOutput) + *offsetOutput = _swapEndianU32(memAddr); + if (sizeOutput) + *sizeOutput = _swapEndianU32(memSize); + } + + void InitializeMemory() + { + cafeExportRegister("coreinit", DCInvalidateRange, LogType::Placeholder); + cafeExportRegister("coreinit", DCFlushRange, LogType::Placeholder); + cafeExportRegister("coreinit", DCFlushRangeNoSync, LogType::Placeholder); + cafeExportRegister("coreinit", DCStoreRange, LogType::Placeholder); + cafeExportRegister("coreinit", DCStoreRangeNoSync, LogType::Placeholder); + cafeExportRegister("coreinit", DCZeroRange, LogType::Placeholder); + cafeExportRegister("coreinit", OSIsAddressRangeDCValid, LogType::Placeholder); + + cafeExportRegisterFunc(coreinit_memcpy, "coreinit", "memcpy", LogType::Placeholder); + cafeExportRegisterFunc(coreinit_memset, "coreinit", "memset", LogType::Placeholder); + cafeExportRegisterFunc(coreinit_memmove, "coreinit", "memmove", LogType::Placeholder); + cafeExportRegister("coreinit", OSBlockMove, LogType::Placeholder); + cafeExportRegister("coreinit", OSBlockSet, LogType::Placeholder); + + cafeExportRegister("coreinit", OSEffectiveToPhysical, LogType::Placeholder); + cafeExportRegister("coreinit", OSMemoryBarrier, LogType::Placeholder); + + cafeExportRegister("coreinit", OSGetMemBound, LogType::Placeholder); + } + +} diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Memory.h b/src/Cafe/OS/libs/coreinit/coreinit_Memory.h new file mode 100644 index 00000000..cfb3ed06 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_Memory.h @@ -0,0 +1,8 @@ +#pragma once + +namespace coreinit +{ + void InitializeMemory(); + + void OSGetMemBound(sint32 memType, MPTR* offsetOutput, uint32* sizeOutput); +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_MemoryMapping.cpp b/src/Cafe/OS/libs/coreinit/coreinit_MemoryMapping.cpp new file mode 100644 index 00000000..26acedce --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_MemoryMapping.cpp @@ -0,0 +1,168 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "util/MemMapper/MemMapper.h" + +#define OS_MAP_READ_ONLY (1) +#define OS_MAP_READ_WRITE (2) + +namespace coreinit +{ + + struct OSVirtMemory + { + MPTR virtualAddress; + uint32 size; + uint32 alignment; + OSVirtMemory* next; + }; + + OSVirtMemory* virtualMemoryList = nullptr; + + MPTR _VirtualMemoryAlloc(uint32 size, uint32 alignment) + { + uint32 currentAddress = MEMORY_MAPABLE_VIRT_AREA_OFFSET; + uint32 endAddress = MEMORY_MAPABLE_VIRT_AREA_OFFSET + MEMORY_MAPABLE_VIRT_AREA_SIZE; + uint32 pageSize = (uint32)MemMapper::GetPageSize(); + while (true) + { + // calculated aligned start and end address for current region + currentAddress = (currentAddress + alignment - 1) & ~(alignment - 1); + currentAddress = (currentAddress + pageSize - 1) & ~(pageSize - 1); + uint32 currentEndAddress = currentAddress + size; + currentEndAddress = (currentEndAddress + pageSize - 1) & ~(pageSize - 1); + // check if out of available space + if (currentEndAddress >= endAddress) + { + debug_printf("coreinitVirtualMemory_alloc(): Unable to allocate memory\n"); + debugBreakpoint(); + return NULL; + } + // check for overlapping regions + OSVirtMemory* virtMemItr = virtualMemoryList; + bool emptySpaceFound = true; + while (virtMemItr) + { + // check for range collision + if (currentAddress < (virtMemItr->virtualAddress + virtMemItr->size) && currentEndAddress > virtMemItr->virtualAddress) + { + // regions overlap + // adjust current address and try again + currentAddress = virtMemItr->virtualAddress + virtMemItr->size; + emptySpaceFound = false; + break; + } + // next + virtMemItr = virtMemItr->next; + } + if (emptySpaceFound) + { + // add entry + OSVirtMemory* virtMemory = (OSVirtMemory*)malloc(sizeof(OSVirtMemory)); + memset(virtMemory, 0x00, sizeof(OSVirtMemory)); + virtMemory->virtualAddress = currentAddress; + virtMemory->size = currentEndAddress - currentAddress; + virtMemory->alignment = alignment; + virtMemory->next = virtualMemoryList; + virtualMemoryList = virtMemory; + return currentAddress; + } + } + return NULL; + } + + void coreinitExport_OSGetAvailPhysAddrRange(PPCInterpreter_t* hCPU) + { + // parameters: + // r3 MPTR* areaStart + // r4 uint32 areaSize + memory_writeU32(hCPU->gpr[3], MEMORY_MAPABLE_PHYS_AREA_OFFSET); + memory_writeU32(hCPU->gpr[4], MEMORY_MAPABLE_PHYS_AREA_SIZE); + + osLib_returnFromFunction(hCPU, 0); + } + + void coreinitExport_OSAllocVirtAddr(PPCInterpreter_t* hCPU) + { + // parameters: + // r3 MPTR address + // r4 uint32 size + // r5 uint32 align + + uint32 address = hCPU->gpr[3]; + uint32 size = hCPU->gpr[4]; + uint32 align = hCPU->gpr[5]; + if (address != MPTR_NULL) + { + debug_printf("coreinitExport_OSAllocVirtAddr(): Unsupported address != NULL\n"); + debugBreakpoint(); + } + if (align == 0) + align = 1; + if (align != 0 && align != 1) + assert_dbg(); + + address = _VirtualMemoryAlloc(size, align); + debug_printf("coreinitExport_OSAllocVirtAddr(): Allocated virtual memory at 0x%08x\n", address); + osLib_returnFromFunction(hCPU, address); + } + + void coreinitExport_OSMapMemory(PPCInterpreter_t* hCPU) + { + // parameters: + // r3 MPTR virtualAddress + // r4 MPTR physicalAddress + // r5 uint32 size + // r6 uint32 mode + MPTR virtualAddress = hCPU->gpr[3]; + MPTR physicalAddress = hCPU->gpr[4]; + uint32 size = hCPU->gpr[5]; + uint32 mode = hCPU->gpr[6]; + + if (virtualAddress < MEMORY_MAPABLE_VIRT_AREA_OFFSET || virtualAddress >= (MEMORY_MAPABLE_VIRT_AREA_OFFSET + MEMORY_MAPABLE_VIRT_AREA_SIZE)) + cemu_assert_suspicious(); + + uint8* virtualPtr = memory_getPointerFromVirtualOffset(virtualAddress); + + MemMapper::PAGE_PERMISSION pageProtect = MemMapper::PAGE_PERMISSION::P_NONE; + if (mode == OS_MAP_READ_ONLY) + pageProtect = MemMapper::PAGE_PERMISSION::P_READ; + else if (mode == OS_MAP_READ_WRITE) + pageProtect = MemMapper::PAGE_PERMISSION::P_RW; + else + cemu_assert_unimplemented(); + void* allocationResult = MemMapper::AllocateMemory(virtualPtr, size, pageProtect, true); + if (!allocationResult) + { + cemuLog_log(LogType::Force, "OSMapMemory failed"); + osLib_returnFromFunction(hCPU, 0); + return; + } + osLib_returnFromFunction(hCPU, 1); + } + + void coreinitExport_OSUnmapMemory(PPCInterpreter_t* hCPU) + { + // parameters: + // r3 MPTR virtualAddress + // r4 uint32 size + MPTR virtualAddress = hCPU->gpr[3]; + uint32 size = hCPU->gpr[4]; + + if (virtualAddress < MEMORY_MAPABLE_VIRT_AREA_OFFSET || virtualAddress >= (MEMORY_MAPABLE_VIRT_AREA_OFFSET + MEMORY_MAPABLE_VIRT_AREA_SIZE)) + cemu_assert_suspicious(); + + cemu_assert((size % MemMapper::GetPageSize()) == 0); + + uint8* virtualPtr = memory_getPointerFromVirtualOffset(virtualAddress); + + MemMapper::FreeMemory(virtualPtr, size, true); + osLib_returnFromFunction(hCPU, 1); + } + + void InitializeMemoryMapping() + { + osLib_addFunction("coreinit", "OSGetAvailPhysAddrRange", coreinitExport_OSGetAvailPhysAddrRange); + osLib_addFunction("coreinit", "OSAllocVirtAddr", coreinitExport_OSAllocVirtAddr); + osLib_addFunction("coreinit", "OSMapMemory", coreinitExport_OSMapMemory); + osLib_addFunction("coreinit", "OSUnmapMemory", coreinitExport_OSUnmapMemory); + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_MemoryMapping.h b/src/Cafe/OS/libs/coreinit/coreinit_MemoryMapping.h new file mode 100644 index 00000000..f51d9a6a --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_MemoryMapping.h @@ -0,0 +1,5 @@ + +namespace coreinit +{ + void InitializeMemoryMapping(); +} diff --git a/src/Cafe/OS/libs/coreinit/coreinit_MessageQueue.cpp b/src/Cafe/OS/libs/coreinit/coreinit_MessageQueue.cpp new file mode 100644 index 00000000..6e6a7bc1 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_MessageQueue.cpp @@ -0,0 +1,132 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/libs/coreinit/coreinit_MessageQueue.h" + +namespace coreinit +{ + + SysAllocator<OSMessageQueue> g_systemMessageQueue; + SysAllocator<OSMessage, 16> _systemMessageQueueArray; + + void OSInitMessageQueueEx(OSMessageQueue* msgQueue, OSMessage* msgArray, uint32 msgCount, void* userData) + { + msgQueue->magic = 'mSgQ'; + msgQueue->userData = userData; + msgQueue->msgArray = msgArray; + msgQueue->msgCount = msgCount; + msgQueue->firstIndex = 0; + msgQueue->usedCount = 0; + msgQueue->ukn08 = 0; + OSInitThreadQueueEx(&msgQueue->threadQueueReceive, msgQueue); + OSInitThreadQueueEx(&msgQueue->threadQueueSend, msgQueue); + } + + void OSInitMessageQueue(OSMessageQueue* msgQueue, OSMessage* msgArray, uint32 msgCount) + { + OSInitMessageQueueEx(msgQueue, msgArray, msgCount, nullptr); + } + + bool OSReceiveMessage(OSMessageQueue* msgQueue, OSMessage* msg, uint32 flags) + { + __OSLockScheduler(msgQueue); + while (msgQueue->usedCount == (uint32be)0) + { + if ((flags & OS_MESSAGE_BLOCK)) + { + msgQueue->threadQueueReceive.queueAndWait(OSGetCurrentThread()); + } + else + { + __OSUnlockScheduler(msgQueue); + return false; + } + } + // copy message + sint32 messageIndex = msgQueue->firstIndex; + OSMessage* readMsg = &(msgQueue->msgArray[messageIndex]); + memcpy(msg, readMsg, sizeof(OSMessage)); + msgQueue->firstIndex = ((uint32)msgQueue->firstIndex + 1) % (uint32)(msgQueue->msgCount); + msgQueue->usedCount = (uint32)msgQueue->usedCount - 1; + // wake up any thread waiting to add a message + if (!msgQueue->threadQueueSend.isEmpty()) + msgQueue->threadQueueSend.wakeupSingleThreadWaitQueue(true); + __OSUnlockScheduler(msgQueue); + return true; + } + + bool OSPeekMessage(OSMessageQueue* msgQueue, OSMessage* msg) + { + __OSLockScheduler(msgQueue); + if ((msgQueue->usedCount == (uint32be)0)) + { + __OSUnlockScheduler(msgQueue); + return false; + } + // copy message + sint32 messageIndex = msgQueue->firstIndex; + if (msg) + { + OSMessage* readMsg = &(msgQueue->msgArray[messageIndex]); + memcpy(msg, readMsg, sizeof(OSMessage)); + } + __OSUnlockScheduler(msgQueue); + return true; + } + + sint32 OSSendMessage(OSMessageQueue* msgQueue, OSMessage* msg, uint32 flags) + { + __OSLockScheduler(); + while (msgQueue->usedCount >= msgQueue->msgCount) + { + if ((flags & OS_MESSAGE_BLOCK)) + { + msgQueue->threadQueueSend.queueAndWait(OSGetCurrentThread()); + } + else + { + __OSUnlockScheduler(); + return 0; + } + } + // add message + if ((flags & OS_MESSAGE_HIGH_PRIORITY)) + { + // decrease firstIndex + sint32 newFirstIndex = (sint32)((sint32)msgQueue->firstIndex + (sint32)msgQueue->msgCount - 1) % (sint32)msgQueue->msgCount; + msgQueue->firstIndex = newFirstIndex; + // insert message at new first index + msgQueue->usedCount = (uint32)msgQueue->usedCount + 1; + OSMessage* newMsg = &(msgQueue->msgArray[newFirstIndex]); + memcpy(newMsg, msg, sizeof(OSMessage)); + } + else + { + sint32 messageIndex = (uint32)(msgQueue->firstIndex + msgQueue->usedCount) % (uint32)msgQueue->msgCount; + msgQueue->usedCount = (uint32)msgQueue->usedCount + 1; + OSMessage* newMsg = &(msgQueue->msgArray[messageIndex]); + memcpy(newMsg, msg, sizeof(OSMessage)); + } + // wake up any thread waiting to read a message + if (!msgQueue->threadQueueReceive.isEmpty()) + msgQueue->threadQueueReceive.wakeupSingleThreadWaitQueue(true); + __OSUnlockScheduler(); + return 1; + } + + OSMessageQueue* OSGetSystemMessageQueue() + { + return g_systemMessageQueue.GetPtr(); + } + + void InitializeMessageQueue() + { + OSInitMessageQueue(g_systemMessageQueue.GetPtr(), _systemMessageQueueArray.GetPtr(), _systemMessageQueueArray.GetCount()); + + cafeExportRegister("coreinit", OSInitMessageQueueEx, LogType::CoreinitThread); + cafeExportRegister("coreinit", OSInitMessageQueue, LogType::CoreinitThread); + cafeExportRegister("coreinit", OSReceiveMessage, LogType::CoreinitThread); + cafeExportRegister("coreinit", OSPeekMessage, LogType::CoreinitThread); + cafeExportRegister("coreinit", OSSendMessage, LogType::CoreinitThread); + cafeExportRegister("coreinit", OSGetSystemMessageQueue, LogType::CoreinitThread); + } +}; + diff --git a/src/Cafe/OS/libs/coreinit/coreinit_MessageQueue.h b/src/Cafe/OS/libs/coreinit/coreinit_MessageQueue.h new file mode 100644 index 00000000..6741ab84 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_MessageQueue.h @@ -0,0 +1,40 @@ +#pragma once +#include "Cafe/OS/libs/coreinit/coreinit_Thread.h" + +namespace coreinit +{ + struct OSMessage + { + MPTR message; + uint32 data0; + uint32 data1; + uint32 data2; + }; + + struct OSMessageQueue + { + /* +0x00 */ uint32be magic; + /* +0x04 */ MEMPTR<void> userData; + /* +0x08 */ uint32be ukn08; + /* +0x0C */ OSThreadQueue threadQueueSend; + /* +0x1C */ OSThreadQueue threadQueueReceive; + /* +0x2C */ MEMPTR<OSMessage> msgArray; + /* +0x30 */ uint32be msgCount; + /* +0x34 */ uint32be firstIndex; + /* +0x38 */ uint32be usedCount; + }; + + static_assert(sizeof(OSMessageQueue) == 0x3C); + + // flags + #define OS_MESSAGE_BLOCK 1 // blocking send/receive + #define OS_MESSAGE_HIGH_PRIORITY 2 // put message in front of all queued messages + + void OSInitMessageQueueEx(OSMessageQueue* msgQueue, OSMessage* msgArray, uint32 msgCount, void* userData); + void OSInitMessageQueue(OSMessageQueue* msgQueue, OSMessage* msgArray, uint32 msgCount); + bool OSReceiveMessage(OSMessageQueue* msgQueue, OSMessage* msg, uint32 flags); + bool OSPeekMessage(OSMessageQueue* msgQueue, OSMessage* msg); + sint32 OSSendMessage(OSMessageQueue* msgQueue, OSMessage* msg, uint32 flags); + + void InitializeMessageQueue(); +}; \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp new file mode 100644 index 00000000..50c6f758 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp @@ -0,0 +1,357 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/libs/coreinit/coreinit_Misc.h" + +namespace coreinit +{ + + /* coreinit logging and string format */ + + sint32 ppcSprintf(const char* formatStr, char* strOut, sint32 maxLength, PPCInterpreter_t* hCPU, sint32 initialParamIndex) + { + char tempStr[4096]; + sint32 integerParamIndex = initialParamIndex; + sint32 floatParamIndex = 0; + sint32 writeIndex = 0; + while (*formatStr) + { + char c = *formatStr; + if (c == '%') + { + const char* formatStart = formatStr; + formatStr++; + if (*formatStr == '%') + { + // percent sign + if (writeIndex >= maxLength) + break; + strOut[writeIndex] = '%'; + writeIndex++; + formatStr++; + continue; + } + // flags + bool flag_leftJustify = false; + bool flag_zeroPadding = false; + if (*formatStr == '-') + { + flag_leftJustify = true; + formatStr++; + } + if (*formatStr == '+') + { + // todo + formatStr++; + } + if (*formatStr == ' ') + { + // todo + formatStr++; + } + if (*formatStr == '#') + { + // todo + formatStr++; + } + if (*formatStr == '0') + { + flag_zeroPadding = true; + formatStr++; + } + // width + if (*formatStr == '*') + { + cemu_assert_debug(false); + formatStr++; + } + bool widthIsSpecified = false; + sint32 width = 0; + while (*formatStr >= '0' && *formatStr <= '9') + { + width *= 10; + width += (*formatStr - '0'); + formatStr++; + widthIsSpecified = true; + } + // precision + if (*formatStr == '.') + { + formatStr++; + if (*formatStr == '*') + { + cemu_assert_debug(false); + } + while (*formatStr >= '0' && *formatStr <= '9') + { + formatStr++; + } + } + // length + specifier + char tempFormat[64]; + if (*formatStr == 'X' || *formatStr == 'x' || *formatStr == 'u' || *formatStr == 'd' || *formatStr == 'p' || *formatStr == 'i' || + (formatStr[0] == 'l' && formatStr[1] == 'd')) + { + // number + formatStr++; + strncpy(tempFormat, formatStart, std::min((std::ptrdiff_t)sizeof(tempFormat) - 1, formatStr - formatStart)); + if ((formatStr - formatStart) < sizeof(tempFormat)) + tempFormat[(formatStr - formatStart)] = '\0'; + else + tempFormat[sizeof(tempFormat) - 1] = '\0'; + sint32 tempLen = sprintf(tempStr, tempFormat, PPCInterpreter_getCallParamU32(hCPU, integerParamIndex)); + integerParamIndex++; + for (sint32 i = 0; i < tempLen; i++) + { + if (writeIndex >= maxLength) + break; + strOut[writeIndex] = tempStr[i]; + writeIndex++; + } + } + else if (*formatStr == 's') + { + // string + formatStr++; + strncpy(tempFormat, formatStart, std::min((std::ptrdiff_t)sizeof(tempFormat) - 1, formatStr - formatStart)); + if ((formatStr - formatStart) < sizeof(tempFormat)) + tempFormat[(formatStr - formatStart)] = '\0'; + else + tempFormat[sizeof(tempFormat) - 1] = '\0'; + MPTR strOffset = PPCInterpreter_getCallParamU32(hCPU, integerParamIndex); + sint32 tempLen = 0; + if (strOffset == MPTR_NULL) + tempLen = sprintf(tempStr, "NULL"); + else + tempLen = sprintf(tempStr, tempFormat, memory_getPointerFromVirtualOffset(strOffset)); + integerParamIndex++; + for (sint32 i = 0; i < tempLen; i++) + { + if (writeIndex >= maxLength) + break; + strOut[writeIndex] = tempStr[i]; + writeIndex++; + } + strOut[std::min(maxLength - 1, writeIndex)] = '\0'; + } + else if (*formatStr == 'f') + { + // float + formatStr++; + strncpy(tempFormat, formatStart, std::min((std::ptrdiff_t)sizeof(tempFormat) - 1, formatStr - formatStart)); + if ((formatStr - formatStart) < sizeof(tempFormat)) + tempFormat[(formatStr - formatStart)] = '\0'; + else + tempFormat[sizeof(tempFormat) - 1] = '\0'; + sint32 tempLen = sprintf(tempStr, tempFormat, (float)hCPU->fpr[1 + floatParamIndex].fp0); + floatParamIndex++; + for (sint32 i = 0; i < tempLen; i++) + { + if (writeIndex >= maxLength) + break; + strOut[writeIndex] = tempStr[i]; + writeIndex++; + } + } + else if (*formatStr == 'c') + { + // character + formatStr++; + strncpy(tempFormat, formatStart, std::min((std::ptrdiff_t)sizeof(tempFormat) - 1, formatStr - formatStart)); + if ((formatStr - formatStart) < sizeof(tempFormat)) + tempFormat[(formatStr - formatStart)] = '\0'; + else + tempFormat[sizeof(tempFormat) - 1] = '\0'; + sint32 tempLen = sprintf(tempStr, tempFormat, PPCInterpreter_getCallParamU32(hCPU, integerParamIndex)); + integerParamIndex++; + for (sint32 i = 0; i < tempLen; i++) + { + if (writeIndex >= maxLength) + break; + strOut[writeIndex] = tempStr[i]; + writeIndex++; + } + } + else if (formatStr[0] == 'l' && formatStr[1] == 'f') + { + // double + formatStr += 2; + strncpy(tempFormat, formatStart, std::min((std::ptrdiff_t)sizeof(tempFormat) - 1, formatStr - formatStart)); + if ((formatStr - formatStart) < sizeof(tempFormat)) + tempFormat[(formatStr - formatStart)] = '\0'; + else + tempFormat[sizeof(tempFormat) - 1] = '\0'; + sint32 tempLen = sprintf(tempStr, tempFormat, (double)hCPU->fpr[1 + floatParamIndex].fp0); + floatParamIndex++; + for (sint32 i = 0; i < tempLen; i++) + { + if (writeIndex >= maxLength) + break; + strOut[writeIndex] = tempStr[i]; + writeIndex++; + } + } + else if ((formatStr[0] == 'l' && formatStr[1] == 'l' && (formatStr[2] == 'x' || formatStr[2] == 'X'))) + { + formatStr += 3; + // number (64bit) + strncpy(tempFormat, formatStart, std::min((std::ptrdiff_t)sizeof(tempFormat) - 1, formatStr - formatStart)); + if ((formatStr - formatStart) < sizeof(tempFormat)) + tempFormat[(formatStr - formatStart)] = '\0'; + else + tempFormat[sizeof(tempFormat) - 1] = '\0'; + if (integerParamIndex & 1) + integerParamIndex++; + sint32 tempLen = sprintf(tempStr, tempFormat, PPCInterpreter_getCallParamU64(hCPU, integerParamIndex)); + integerParamIndex += 2; + for (sint32 i = 0; i < tempLen; i++) + { + if (writeIndex >= maxLength) + break; + strOut[writeIndex] = tempStr[i]; + writeIndex++; + } + } + else + { + // unsupported / unknown specifier + cemu_assert_debug(false); + break; + } + } + else + { + if (writeIndex >= maxLength) + break; + strOut[writeIndex] = c; + writeIndex++; + formatStr++; + } + } + strOut[std::min(writeIndex, maxLength - 1)] = '\0'; + return std::min(writeIndex, maxLength - 1); + } + + sint32 __os_snprintf(char* outputStr, sint32 maxLength, const char* formatStr) + { + sint32 r = ppcSprintf(formatStr, outputStr, maxLength, ppcInterpreterCurrentInstance, 3); + return r; + } + + enum class CafeLogType + { + OSCONSOLE = 0, + }; + + struct CafeLogBuffer + { + std::array<char, 270> lineBuffer; + size_t lineLength{}; + }; + + CafeLogBuffer g_logBuffer_OSReport; + + CafeLogBuffer& getLogBuffer(CafeLogType cafeLogType) + { + if (cafeLogType == CafeLogType::OSCONSOLE) + return g_logBuffer_OSReport; + // default to OSReport + return g_logBuffer_OSReport; + } + + std::string_view getLogBufferName(CafeLogType cafeLogType) + { + if (cafeLogType == CafeLogType::OSCONSOLE) + return "OSConsole"; + return "Unknown"; + } + + void WriteCafeConsole(CafeLogType cafeLogType, const char* msg, sint32 len) + { + // once a line is full or \n is written it will be posted to log + CafeLogBuffer& logBuffer = getLogBuffer(cafeLogType); + + auto flushLine = [](CafeLogBuffer& cafeLogBuffer, std::string_view cafeLogName) -> void + { + cemuLog_log(LogType::CoreinitLogging, "[{0}] {1}", cafeLogName, std::basic_string_view(cafeLogBuffer.lineBuffer.data(), cafeLogBuffer.lineLength)); + cafeLogBuffer.lineLength = 0; + }; + + while (len) + { + char c = *msg; + msg++; + len--; + if (c == '\r') + continue; + if (c == '\n') + { + // flush line immediately + flushLine(logBuffer, getLogBufferName(cafeLogType)); + continue; + } + logBuffer.lineBuffer[logBuffer.lineLength] = c; + logBuffer.lineLength++; + if (logBuffer.lineLength >= logBuffer.lineBuffer.size()) + flushLine(logBuffer, getLogBufferName(cafeLogType)); + } + } + + void OSReport(const char* format) + { + char buffer[1024 * 2]; + sint32 len = ppcSprintf(format, buffer, sizeof(buffer), ppcInterpreterCurrentInstance, 1); + WriteCafeConsole(CafeLogType::OSCONSOLE, buffer, len); + } + + void OSVReport(const char* format, MPTR vaArgs) + { + cemu_assert_unimplemented(); + } + + void COSWarn() + { + cemu_assert_debug(false); + } + + void OSLogPrintf() + { + cemu_assert_debug(false); + } + + void OSConsoleWrite(const char* strPtr, sint32 length) + { + if (length < 0) + return; + WriteCafeConsole(CafeLogType::OSCONSOLE, strPtr, length); + } + + /* home button menu */ + + bool g_homeButtonMenuEnabled = false; + + bool OSIsHomeButtonMenuEnabled() + { + return g_homeButtonMenuEnabled; + } + + bool OSEnableHomeButtonMenu(bool enable) + { + g_homeButtonMenuEnabled = enable; + return true; + } + + void miscInit() + { + cafeExportRegister("coreinit", __os_snprintf, LogType::Placeholder); + cafeExportRegister("coreinit", OSReport, LogType::Placeholder); + cafeExportRegister("coreinit", OSVReport, LogType::Placeholder); + cafeExportRegister("coreinit", COSWarn, LogType::Placeholder); + cafeExportRegister("coreinit", OSLogPrintf, LogType::Placeholder); + cafeExportRegister("coreinit", OSConsoleWrite, LogType::Placeholder); + + g_homeButtonMenuEnabled = true; // enabled by default + // Disney Infinity 2.0 actually relies on home button menu being enabled by default. If it's false it will crash due to calling erreula->IsAppearHomeNixSign() before initializing erreula + cafeExportRegister("coreinit", OSIsHomeButtonMenuEnabled, LogType::CoreinitThread); + cafeExportRegister("coreinit", OSEnableHomeButtonMenu, LogType::CoreinitThread); + } + +}; \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Misc.h b/src/Cafe/OS/libs/coreinit/coreinit_Misc.h new file mode 100644 index 00000000..5cb0ee10 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_Misc.h @@ -0,0 +1,6 @@ +#pragma once + +namespace coreinit +{ + void miscInit(); +}; \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_OSScreen.cpp b/src/Cafe/OS/libs/coreinit/coreinit_OSScreen.cpp new file mode 100644 index 00000000..23b8f76b --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_OSScreen.cpp @@ -0,0 +1,193 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/libs/gx2/GX2.h" +#include "Cafe/HW/Latte/Core/Latte.h" + +#include "Cafe/OS/libs/coreinit/coreinit_OSScreen_font.h" +#include "Cafe/OS/libs/coreinit/coreinit_OSScreen.h" + +#define OSSCREEN_TV (0) +#define OSSCREEN_DRC (1) + +namespace coreinit +{ + + struct + { + sint32 x; + sint32 y; + sint32 pitch; + }screenSizes[2] = + { + { 1280, 720, 1280}, // TV + { 896, 480, 896 } // DRC (values might be incorrect) + }; + + void* currentScreenBasePtr[2] = { 0 }; + + void _OSScreen_Clear(uint32 screenIndex, uint32 color) + { + if (!currentScreenBasePtr[screenIndex]) + return; + + uint32* output = (uint32*)currentScreenBasePtr[screenIndex]; + sint32 sizeInPixels = screenSizes[screenIndex].pitch * screenSizes[screenIndex].y; + color = _swapEndianU32(color); + for (sint32 i = 0; i < sizeInPixels; i++) + { + *output = color; + output++; + } + } + + void coreinitExport_OSScreenInit(PPCInterpreter_t* hCPU) + { + // todo - init VI registers? + + osLib_returnFromFunction(hCPU, 0); + } + + void coreinitExport_OSScreenGetBufferSizeEx(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(screenIndex, 0); + cemu_assert(screenIndex < 2); + uint32 bufferSize = screenSizes[screenIndex].pitch * screenSizes[screenIndex].y * 4 * 2; + osLib_returnFromFunction(hCPU, bufferSize); + } + + void _updateCurrentDrawScreen(sint32 screenIndex) + { + uint32 screenDataSize = screenSizes[screenIndex].pitch * screenSizes[screenIndex].y * 4; + + if ((LatteGPUState.osScreen.screen[screenIndex].flipRequestCount & 1) != 0) + currentScreenBasePtr[screenIndex] = memory_getPointerFromPhysicalOffset(LatteGPUState.osScreen.screen[screenIndex].physPtr + screenDataSize); + else + currentScreenBasePtr[screenIndex] = memory_getPointerFromPhysicalOffset(LatteGPUState.osScreen.screen[screenIndex].physPtr); + } + + void coreinitExport_OSScreenSetBufferEx(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(screenIndex, 0); + ppcDefineParamU32(buffer, 1); + cemu_assert(screenIndex < 2); + LatteGPUState.osScreen.screen[screenIndex].physPtr = buffer; + _updateCurrentDrawScreen(screenIndex); + osLib_returnFromFunction(hCPU, 0); + } + + void coreinitExport_OSScreenEnableEx(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(screenIndex, 0); + ppcDefineParamU32(isEnabled, 1); + cemu_assert(screenIndex < 2); + LatteGPUState.osScreen.screen[screenIndex].isEnabled = isEnabled != 0; + osLib_returnFromFunction(hCPU, 0); + } + + void coreinitExport_OSScreenClearBufferEx(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(screenIndex, 0); + ppcDefineParamU32(color, 1); + cemu_assert(screenIndex < 2); + _OSScreen_Clear(screenIndex, color); + osLib_returnFromFunction(hCPU, 0); + } + + void coreinitExport_OSScreenFlipBuffersEx(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(screenIndex, 0); + cemu_assert(screenIndex < 2); + forceLogDebug_printf("OSScreenFlipBuffersEx %d", screenIndex); + LatteGPUState.osScreen.screen[screenIndex].flipRequestCount++; + _updateCurrentDrawScreen(screenIndex); + osLib_returnFromFunction(hCPU, 0); + } + + void coreinitExport_OSScreenPutPixelEx(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(screenIndex, 0); + ppcDefineParamS32(x, 1); + ppcDefineParamS32(y, 2); + ppcDefineParamU32(color, 3); + if (screenIndex >= 2) + { + osLib_returnFromFunction(hCPU, 0); + return; + } + if (x >= 0 && x < screenSizes[screenIndex].x && y >= 0 && y < screenSizes[screenIndex].y) + { + *(uint32*)((uint8*)currentScreenBasePtr[screenIndex] + (x + y * screenSizes[screenIndex].pitch) * 4) = _swapEndianS32(color); + } + osLib_returnFromFunction(hCPU, 0); + } + + const char* osScreenCharset = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"; + + sint32 _getOSScreenFontCharIndex(char c) + { + const char* charset = osScreenCharset; + while (*charset) + { + if (*charset == c) + { + return (sint32)(charset - osScreenCharset); + } + charset++; + } + return -1; + } + + void coreinitExport_OSScreenPutFontEx(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(screenIndex, 0); + ppcDefineParamS32(x, 1); + ppcDefineParamS32(y, 2); + ppcDefineParamStr(str, 3); + + // characters are: + // 16 x 32 (including the margin) + // with a margin of 4 x 8 + + if (y < 0) + { + debug_printf("OSScreenPutFontEx: y has invalid value\n"); + osLib_returnFromFunction(hCPU, 0); + return; + } + + sint32 px = x * 16; + sint32 py = y * 24; + + while (*str) + { + sint32 charIndex = _getOSScreenFontCharIndex(*str); + if (charIndex >= 0) + { + const uint8* charBitmap = osscreenBitmapFont + charIndex * 50; + for (sint32 fy = 0; fy < 25; fy++) + { + for (sint32 fx = 0; fx < 14; fx++) + { + if (((charBitmap[(fx / 8) + (fy) * 2] >> (7 - (fx & 7))) & 1) == 0) + continue; + *(uint32*)((uint8*)currentScreenBasePtr[screenIndex] + ((px + fx) + (py + fy) * screenSizes[screenIndex].pitch) * 4) = 0xFFFFFFFF; + } + } + } + px += 16; + str++; + } + osLib_returnFromFunction(hCPU, 0); + } + + void InitializeOSScreen() + { + osLib_addFunction("coreinit", "OSScreenInit", coreinitExport_OSScreenInit); + osLib_addFunction("coreinit", "OSScreenGetBufferSizeEx", coreinitExport_OSScreenGetBufferSizeEx); + osLib_addFunction("coreinit", "OSScreenSetBufferEx", coreinitExport_OSScreenSetBufferEx); + osLib_addFunction("coreinit", "OSScreenEnableEx", coreinitExport_OSScreenEnableEx); + osLib_addFunction("coreinit", "OSScreenClearBufferEx", coreinitExport_OSScreenClearBufferEx); + osLib_addFunction("coreinit", "OSScreenFlipBuffersEx", coreinitExport_OSScreenFlipBuffersEx); + osLib_addFunction("coreinit", "OSScreenPutPixelEx", coreinitExport_OSScreenPutPixelEx); + osLib_addFunction("coreinit", "OSScreenPutFontEx", coreinitExport_OSScreenPutFontEx); + } +} diff --git a/src/Cafe/OS/libs/coreinit/coreinit_OSScreen.h b/src/Cafe/OS/libs/coreinit/coreinit_OSScreen.h new file mode 100644 index 00000000..29011123 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_OSScreen.h @@ -0,0 +1,6 @@ +#pragma once + +namespace coreinit +{ + void InitializeOSScreen(); +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_OSScreen_font.h b/src/Cafe/OS/libs/coreinit/coreinit_OSScreen_font.h new file mode 100644 index 00000000..9965df96 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_OSScreen_font.h @@ -0,0 +1,2542 @@ + +// font 'More Perfect DOS VGA' 20pts +const uint8 osscreenBitmapFont[] = +{ +// @0 '!' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x07, 0x00, // ### +0x07, 0x00, // ### +0x1F, 0xC0, // ####### +0x1F, 0xC0, // ####### +0x1F, 0xC0, // ####### +0x1F, 0xC0, // ####### +0x1F, 0xC0, // ####### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x00, 0x00, // +0x00, 0x00, // +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @50 '"' (14 pixels wide) +0x00, 0x00, // +0x38, 0xE0, // ### ### +0x38, 0xE0, // ### ### +0x38, 0xE0, // ### ### +0x38, 0xE0, // ### ### +0x38, 0xE0, // ### ### +0x18, 0xC0, // ## ## +0x18, 0xC0, // ## ## +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @100 '#' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x39, 0xC0, // ### ### +0x39, 0xC0, // ### ### +0x39, 0xC0, // ### ### +0xFF, 0xF0, // ############ +0xFF, 0xF0, // ############ +0x39, 0xC0, // ### ### +0x39, 0xC0, // ### ### +0x39, 0xC0, // ### ### +0x39, 0xC0, // ### ### +0x39, 0xC0, // ### ### +0xFF, 0xF0, // ############ +0xFF, 0xF0, // ############ +0x39, 0xC0, // ### ### +0x39, 0xC0, // ### ### +0x39, 0xC0, // ### ### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @150 '$' (14 pixels wide) +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x3F, 0x80, // ####### +0x3F, 0x80, // ####### +0xE0, 0xE0, // ### ### +0xE0, 0x60, // ### ## +0xE0, 0x60, // ### ## +0xE0, 0x00, // ### +0xE0, 0x00, // ### +0x3F, 0x80, // ####### +0x38, 0xA0, // ### # # +0x00, 0xE0, // ### +0x00, 0xE0, // ### +0x00, 0xE0, // ### +0xC0, 0xE0, // ## ### +0xC0, 0xE0, // ## ### +0xE0, 0xE0, // ### ### +0x3F, 0x80, // ####### +0x3F, 0x80, // ####### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x00, 0x00, // +0x00, 0x00, // + +// @200 '%' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0xE0, 0x20, // ### # +0xF0, 0xF0, // #### #### +0xF0, 0xF0, // #### #### +0x01, 0xE0, // #### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x1E, 0x00, // #### +0x38, 0x00, // ### +0x38, 0x00, // ### +0xF0, 0xE0, // #### ### +0xC0, 0xF0, // ## #### +0xC0, 0xF0, // ## #### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @250 '&' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x1F, 0x00, // ##### +0x1F, 0x00, // ##### +0x39, 0xC0, // ### ### +0x39, 0xC0, // ### ### +0x39, 0xC0, // ### ### +0x1F, 0x00, // ##### +0x1F, 0x00, // ##### +0x3E, 0xE0, // ##### ### +0xB7, 0xE0, // # ## ###### +0xE7, 0xC0, // ### ##### +0xE1, 0xC0, // ### ### +0xE1, 0xC0, // ### ### +0xE1, 0xC0, // ### ### +0xE1, 0xC0, // ### ### +0xE1, 0xC0, // ### ### +0x3E, 0xE0, // ##### ### +0x3E, 0xE0, // ##### ### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @300 ''' (14 pixels wide) +0x00, 0x00, // +0x0E, 0x00, // ### +0x0E, 0x00, // ### +0x0E, 0x00, // ### +0x0E, 0x00, // ### +0x0E, 0x00, // ### +0x38, 0x00, // ### +0x38, 0x00, // ### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @350 '(' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0x07, 0x00, // ### +0x0E, 0x00, // ### +0x0E, 0x00, // ### +0x0E, 0x00, // ### +0x0E, 0x00, // ### +0x0E, 0x00, // ### +0x0E, 0x00, // ### +0x0E, 0x00, // ### +0x0E, 0x00, // ### +0x0E, 0x00, // ### +0x0E, 0x00, // ### +0x0E, 0x00, // ### +0x07, 0x00, // ### +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @400 ')' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x0E, 0x00, // ### +0x0E, 0x00, // ### +0x07, 0x00, // ### +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0x07, 0x00, // ### +0x0E, 0x00, // ### +0x0E, 0x00, // ### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @450 '*' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x1F, 0xC0, // ####### +0xFF, 0xF8, // ############# +0xFF, 0xF8, // ############# +0x1F, 0xC0, // ####### +0x1F, 0xC0, // ####### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @500 '+' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x3F, 0xE0, // ######### +0x3F, 0xE0, // ######### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @550 ',' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x0E, 0x00, // ### +0x0E, 0x00, // ### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @600 '-' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0xFF, 0xF0, // ############ +0xFF, 0xF0, // ############ +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @650 '.' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @700 '/' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x20, // # +0x00, 0xE0, // ### +0x00, 0xE0, // ### +0x01, 0xC0, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x0E, 0x00, // ### +0x38, 0x00, // ### +0x38, 0x00, // ### +0xF0, 0x00, // #### +0xC0, 0x00, // ## +0xC0, 0x00, // ## +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @750 '0' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x1F, 0xC0, // ####### +0x1F, 0xC0, // ####### +0x38, 0xE0, // ### ### +0xE0, 0x38, // ### ### +0xE0, 0x38, // ### ### +0xE0, 0x38, // ### ### +0xE0, 0x38, // ### ### +0xE7, 0x38, // ### ### ### +0xE7, 0x38, // ### ### ### +0xE7, 0x38, // ### ### ### +0xE0, 0x38, // ### ### +0xE0, 0x38, // ### ### +0xE0, 0x38, // ### ### +0xE0, 0x38, // ### ### +0x38, 0xE0, // ### ### +0x1F, 0xC0, // ####### +0x1F, 0xC0, // ####### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @800 '1' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x03, 0x80, // ### +0x03, 0x80, // ### +0x0F, 0x80, // ##### +0x3F, 0x80, // ####### +0x3F, 0x80, // ####### +0x03, 0x80, // ### +0x03, 0x80, // ### +0x03, 0x80, // ### +0x03, 0x80, // ### +0x03, 0x80, // ### +0x03, 0x80, // ### +0x03, 0x80, // ### +0x03, 0x80, // ### +0x03, 0x80, // ### +0x03, 0x80, // ### +0x3F, 0xF0, // ########## +0x3F, 0xF0, // ########## +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @850 '2' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x3F, 0x80, // ####### +0x3F, 0x80, // ####### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0x00, 0xE0, // ### +0x03, 0x80, // ### +0x03, 0x80, // ### +0x07, 0x00, // ### +0x1C, 0x00, // ### +0x1C, 0x00, // ### +0x38, 0x00, // ### +0x38, 0x00, // ### +0xE0, 0x00, // ### +0xE0, 0x00, // ### +0xE0, 0xE0, // ### ### +0xFF, 0xE0, // ########### +0xFF, 0xE0, // ########### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @900 '3' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x3F, 0xC0, // ######## +0x3F, 0xC0, // ######## +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0x00, 0x70, // ### +0x00, 0x70, // ### +0x00, 0x70, // ### +0x1F, 0xC0, // ####### +0x1C, 0x50, // ### # # +0x00, 0x70, // ### +0x00, 0x70, // ### +0x00, 0x70, // ### +0x00, 0x70, // ### +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0x3F, 0xC0, // ######## +0x3F, 0xC0, // ######## +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @950 '4' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0x07, 0xC0, // ##### +0x07, 0xC0, // ##### +0x1F, 0xC0, // ####### +0x39, 0xC0, // ### ### +0x39, 0xC0, // ### ### +0xE1, 0xC0, // ### ### +0xFF, 0xF0, // ############ +0xFF, 0xF0, // ############ +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0x07, 0xF0, // ####### +0x07, 0xF0, // ####### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @1000 '5' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0xFF, 0xE0, // ########### +0xFF, 0xE0, // ########### +0xE0, 0x00, // ### +0xE0, 0x00, // ### +0xE0, 0x00, // ### +0xE0, 0x00, // ### +0xE0, 0x00, // ### +0xFF, 0x80, // ######### +0xF0, 0xA0, // #### # # +0x00, 0xE0, // ### +0x00, 0xE0, // ### +0x00, 0xE0, // ### +0x00, 0xE0, // ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0x3F, 0x80, // ####### +0x3F, 0x80, // ####### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @1050 '6' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x1F, 0x80, // ###### +0x1F, 0x80, // ###### +0x38, 0x00, // ### +0xE0, 0x00, // ### +0xE0, 0x00, // ### +0xE0, 0x00, // ### +0xFF, 0xC0, // ########## +0xFF, 0xC0, // ########## +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0x3F, 0xC0, // ######## +0x3F, 0xC0, // ######## +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @1100 '7' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0xFF, 0xE0, // ########### +0xFF, 0xE0, // ########### +0xE0, 0xE0, // ### ### +0x00, 0xE0, // ### +0x00, 0xE0, // ### +0x00, 0xE0, // ### +0x00, 0xE0, // ### +0x03, 0x80, // ### +0x07, 0x80, // #### +0x07, 0x00, // ### +0x1C, 0x00, // ### +0x1C, 0x00, // ### +0x1C, 0x00, // ### +0x1C, 0x00, // ### +0x1C, 0x00, // ### +0x1C, 0x00, // ### +0x1C, 0x00, // ### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @1150 '8' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x3F, 0x80, // ####### +0x3F, 0x80, // ####### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0x3F, 0x80, // ####### +0xA7, 0xC0, // # # ##### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0x3F, 0x80, // ####### +0x3F, 0x80, // ####### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @1200 '9' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x3F, 0xC0, // ######## +0x3F, 0xC0, // ######## +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0x3F, 0xF0, // ########## +0x3F, 0xF0, // ########## +0x00, 0x70, // ### +0x00, 0x70, // ### +0x00, 0x70, // ### +0x00, 0x70, // ### +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0x3F, 0x80, // ####### +0x3F, 0x80, // ####### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @1250 ':' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @1300 ';' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x0E, 0x00, // ### +0x0E, 0x00, // ### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @1350 '<' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0xE0, // ### +0x00, 0xE0, // ### +0x01, 0xC0, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x0E, 0x00, // ### +0x38, 0x00, // ### +0x38, 0x00, // ### +0x1C, 0x00, // ### +0x0E, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x01, 0xC0, // ### +0x00, 0xE0, // ### +0x00, 0xE0, // ### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @1400 '=' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x3F, 0xF0, // ########## +0x3F, 0xF0, // ########## +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x3F, 0xF0, // ########## +0x3F, 0xF0, // ########## +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @1450 '>' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x38, 0x00, // ### +0x38, 0x00, // ### +0x0E, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x01, 0xC0, // ### +0x00, 0xE0, // ### +0x00, 0xE0, // ### +0x00, 0xE0, // ### +0x01, 0xC0, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x0E, 0x00, // ### +0x38, 0x00, // ### +0x38, 0x00, // ### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @1500 '?' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x3F, 0xC0, // ######## +0x3F, 0xC0, // ######## +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x00, 0x00, // +0x00, 0x00, // +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @1550 '@' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x3F, 0xC0, // ######## +0x3F, 0xC0, // ######## +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0xE7, 0xF0, // ### ####### +0xE7, 0xF0, // ### ####### +0xE7, 0xF0, // ### ####### +0xE7, 0xF0, // ### ####### +0xE7, 0xF0, // ### ####### +0xE7, 0xC0, // ### ##### +0xE7, 0xC0, // ### ##### +0xE0, 0x00, // ### +0x3F, 0xC0, // ######## +0x3F, 0xC0, // ######## +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @1600 'A' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x06, 0x00, // ## +0x06, 0x00, // ## +0x1F, 0x80, // ###### +0x39, 0xC0, // ### ### +0x39, 0xC0, // ### ### +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0xFF, 0xF0, // ############ +0xFF, 0xF0, // ############ +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @1650 'B' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0xFF, 0xC0, // ########## +0xFF, 0xC0, // ########## +0x38, 0xE0, // ### ### +0x38, 0xE0, // ### ### +0x38, 0xE0, // ### ### +0x38, 0xE0, // ### ### +0x38, 0xE0, // ### ### +0x3F, 0x80, // ####### +0x3F, 0x80, // ####### +0x38, 0xE0, // ### ### +0x38, 0xE0, // ### ### +0x38, 0xE0, // ### ### +0x38, 0xE0, // ### ### +0x38, 0xE0, // ### ### +0x38, 0xE0, // ### ### +0xFF, 0xC0, // ########## +0xFF, 0xC0, // ########## +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @1700 'C' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x1F, 0xC0, // ####### +0x1F, 0xC0, // ####### +0x38, 0x70, // ### ### +0xE0, 0x30, // ### ## +0xE0, 0x30, // ### ## +0xE0, 0x00, // ### +0xE0, 0x00, // ### +0xE0, 0x00, // ### +0xE0, 0x00, // ### +0xE0, 0x00, // ### +0xE0, 0x00, // ### +0xE0, 0x00, // ### +0xE0, 0x30, // ### ## +0xE0, 0x30, // ### ## +0x38, 0x70, // ### ### +0x1F, 0xC0, // ####### +0x1F, 0xC0, // ####### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @1750 'D' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0xFF, 0x80, // ######### +0xFF, 0x80, // ######### +0x39, 0xC0, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x39, 0xC0, // ### ### +0xFF, 0x80, // ######### +0xFF, 0x80, // ######### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @1800 'E' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0xFF, 0xF0, // ############ +0xFF, 0xF0, // ############ +0x38, 0x70, // ### ### +0x38, 0x30, // ### ## +0x38, 0x30, // ### ## +0x39, 0x80, // ### ## +0x39, 0x80, // ### ## +0x3F, 0x80, // ####### +0x3F, 0x80, // ####### +0x39, 0x80, // ### ## +0x39, 0x80, // ### ## +0x38, 0x00, // ### +0x38, 0x30, // ### ## +0x38, 0x30, // ### ## +0x38, 0x70, // ### ### +0xFF, 0xF0, // ############ +0xFF, 0xF0, // ############ +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @1850 'F' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0xFF, 0xF0, // ############ +0xFF, 0xF0, // ############ +0x38, 0x70, // ### ### +0x38, 0x30, // ### ## +0x38, 0x30, // ### ## +0x39, 0x80, // ### ## +0x39, 0x80, // ### ## +0x3F, 0x80, // ####### +0x3F, 0x80, // ####### +0x39, 0x80, // ### ## +0x39, 0x80, // ### ## +0x38, 0x00, // ### +0x38, 0x00, // ### +0x38, 0x00, // ### +0x38, 0x00, // ### +0xFE, 0x00, // ####### +0xFE, 0x00, // ####### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @1900 'G' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x1F, 0xC0, // ####### +0x1F, 0xC0, // ####### +0x38, 0xF0, // ### #### +0xE0, 0x30, // ### ## +0xE0, 0x20, // ### # +0xE0, 0x00, // ### +0xE0, 0x00, // ### +0xE0, 0x00, // ### +0xE7, 0xF0, // ### ####### +0xE7, 0xF0, // ### ####### +0xE0, 0xF0, // ### #### +0xE0, 0xF0, // ### #### +0xE0, 0xF0, // ### #### +0xE0, 0xE0, // ### ### +0x38, 0xE0, // ### ### +0x1F, 0x20, // ##### # +0x1F, 0x20, // ##### # +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @1950 'H' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xFF, 0xE0, // ########### +0xFF, 0xE0, // ########### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @2000 'I' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x1F, 0xC0, // ####### +0x1F, 0xC0, // ####### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x1F, 0xC0, // ####### +0x1F, 0xC0, // ####### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @2050 'J' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x07, 0xF0, // ####### +0x07, 0xF0, // ####### +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0xE1, 0xC0, // ### ### +0xE1, 0xC0, // ### ### +0xE1, 0xC0, // ### ### +0xE1, 0xC0, // ### ### +0xE1, 0xC0, // ### ### +0x3F, 0x00, // ###### +0x3F, 0x00, // ###### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @2100 'K' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0xF8, 0x70, // ##### ### +0xF8, 0x70, // ##### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x39, 0xC0, // ### ### +0x39, 0xC0, // ### ### +0x3F, 0x80, // ####### +0x3F, 0x80, // ####### +0x3F, 0x80, // ####### +0x39, 0xC0, // ### ### +0x39, 0xC0, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0xF8, 0x70, // ##### ### +0xF8, 0x70, // ##### ### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @2150 'L' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0xFE, 0x00, // ####### +0xFE, 0x00, // ####### +0x38, 0x00, // ### +0x38, 0x00, // ### +0x38, 0x00, // ### +0x38, 0x00, // ### +0x38, 0x00, // ### +0x38, 0x00, // ### +0x38, 0x00, // ### +0x38, 0x00, // ### +0x38, 0x00, // ### +0x38, 0x00, // ### +0x38, 0x30, // ### ## +0x38, 0x30, // ### ## +0x38, 0x70, // ### ### +0xFF, 0xF0, // ############ +0xFF, 0xF0, // ############ +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @2200 'M' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0xE0, 0x38, // ### ### +0xE0, 0x38, // ### ### +0xF8, 0xF8, // ##### ##### +0xF8, 0xF8, // ##### ##### +0xFF, 0xF8, // ############# +0xFF, 0xF8, // ############# +0xFF, 0xF8, // ############# +0xE7, 0x38, // ### ### ### +0xE7, 0x38, // ### ### ### +0xE0, 0x38, // ### ### +0xE0, 0x38, // ### ### +0xE0, 0x38, // ### ### +0xE0, 0x38, // ### ### +0xE0, 0x38, // ### ### +0xE0, 0x38, // ### ### +0xE0, 0x38, // ### ### +0xE0, 0x38, // ### ### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @2250 'N' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0xF8, 0x70, // ##### ### +0xFE, 0x70, // ####### ### +0xFE, 0x70, // ####### ### +0xFF, 0xF0, // ############ +0xFF, 0xF0, // ############ +0xE7, 0xF0, // ### ####### +0xE7, 0xF0, // ### ####### +0xE1, 0xF0, // ### ##### +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @2300 'O' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x3F, 0x80, // ####### +0x3F, 0x80, // ####### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0x3F, 0x80, // ####### +0x3F, 0x80, // ####### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @2350 'P' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0xFF, 0xC0, // ########## +0xFF, 0xC0, // ########## +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x3F, 0xC0, // ######## +0x3F, 0xC0, // ######## +0x38, 0x00, // ### +0x38, 0x00, // ### +0x38, 0x00, // ### +0x38, 0x00, // ### +0x38, 0x00, // ### +0x38, 0x00, // ### +0xFE, 0x00, // ####### +0xFE, 0x00, // ####### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @2400 'Q' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x3F, 0xC0, // ######## +0x3F, 0xC0, // ######## +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0xE6, 0x70, // ### ## ### +0xE7, 0xF0, // ### ####### +0xE7, 0xF0, // ### ####### +0x3F, 0xC0, // ######## +0x3F, 0xC0, // ######## +0x01, 0xC0, // ### +0x01, 0xF0, // ##### +0x01, 0xF0, // ##### +0x00, 0x00, // +0x00, 0x00, // + +// @2450 'R' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0xFF, 0xC0, // ########## +0xFF, 0xC0, // ########## +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x3F, 0xC0, // ######## +0x3F, 0xC0, // ######## +0x39, 0xC0, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0xF8, 0x70, // ##### ### +0xF8, 0x70, // ##### ### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @2500 'S' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x3F, 0xC0, // ######## +0x3F, 0xC0, // ######## +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0x38, 0x00, // ### +0x38, 0x00, // ### +0x1F, 0x80, // ###### +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0x00, 0x70, // ### +0x00, 0x70, // ### +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0x3F, 0xC0, // ######## +0x3F, 0xC0, // ######## +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @2550 'T' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0xFF, 0xFC, // ############## +0xFF, 0xFC, // ############## +0xE7, 0x3C, // ### ### #### +0xE7, 0x0C, // ### ### ## +0xC7, 0x0C, // ## ### ## +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x1F, 0xC0, // ####### +0x1F, 0xC0, // ####### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @2600 'U' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0x3F, 0x80, // ####### +0x3F, 0x80, // ####### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @2650 'V' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0xE0, 0x38, // ### ### +0xE0, 0x38, // ### ### +0xE0, 0x38, // ### ### +0xE0, 0x38, // ### ### +0xE0, 0x38, // ### ### +0xE0, 0x38, // ### ### +0xE0, 0x38, // ### ### +0xE0, 0x38, // ### ### +0xE0, 0x38, // ### ### +0xE0, 0x38, // ### ### +0xE0, 0x38, // ### ### +0xE0, 0x38, // ### ### +0x38, 0xE0, // ### ### +0x38, 0xE0, // ### ### +0x1F, 0xC0, // ####### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @2700 'W' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0xE0, 0x38, // ### ### +0xE0, 0x38, // ### ### +0xE0, 0x38, // ### ### +0xE0, 0x38, // ### ### +0xE0, 0x38, // ### ### +0xE0, 0x38, // ### ### +0xE0, 0x38, // ### ### +0xE7, 0x38, // ### ### ### +0xE7, 0x38, // ### ### ### +0xE7, 0x38, // ### ### ### +0xE7, 0x38, // ### ### ### +0xE7, 0x38, // ### ### ### +0xFF, 0xF8, // ############# +0xFF, 0xF8, // ############# +0xF8, 0xF8, // ##### ##### +0x38, 0xE0, // ### ### +0x38, 0xE0, // ### ### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @2750 'X' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0xE0, 0x38, // ### ### +0xE0, 0x38, // ### ### +0xE0, 0x38, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x3F, 0xF0, // ########## +0x3F, 0xF0, // ########## +0x1F, 0xC0, // ####### +0x1F, 0xC0, // ####### +0x1F, 0xC0, // ####### +0x3F, 0xF0, // ########## +0x3F, 0xF0, // ########## +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0xE0, 0x38, // ### ### +0xE0, 0x38, // ### ### +0xE0, 0x38, // ### ### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @2800 'Y' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0xE0, 0x3C, // ### #### +0xE0, 0x38, // ### ### +0xE0, 0x38, // ### ### +0xE0, 0x38, // ### ### +0xE0, 0x38, // ### ### +0x38, 0xF0, // ### #### +0x38, 0xF0, // ### #### +0x1F, 0xC0, // ####### +0x1F, 0xC0, // ####### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x1F, 0xC0, // ####### +0x1F, 0xC0, // ####### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @2850 'Z' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0xFF, 0xFC, // ############## +0xFF, 0xF8, // ############# +0xE0, 0x38, // ### ### +0xE0, 0xF0, // ### #### +0xC0, 0xF0, // ## #### +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x1E, 0x00, // #### +0x38, 0x00, // ### +0x38, 0x00, // ### +0xE0, 0x0C, // ### ## +0xE0, 0x0C, // ### ## +0xE0, 0x3C, // ### #### +0xFF, 0xFC, // ############## +0xFF, 0xFC, // ############## +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @2900 '[' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x0F, 0xE0, // ####### +0x0F, 0xE0, // ####### +0x0E, 0x00, // ### +0x0E, 0x00, // ### +0x0E, 0x00, // ### +0x0E, 0x00, // ### +0x0E, 0x00, // ### +0x0E, 0x00, // ### +0x0E, 0x00, // ### +0x0E, 0x00, // ### +0x0E, 0x00, // ### +0x0E, 0x00, // ### +0x0E, 0x00, // ### +0x0E, 0x00, // ### +0x0E, 0x00, // ### +0x0F, 0xE0, // ####### +0x0F, 0xE0, // ####### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @2950 '\' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0xC0, 0x00, // ## +0xE0, 0x00, // ### +0xE0, 0x00, // ### +0xF8, 0x00, // ##### +0xF8, 0x00, // ##### +0x3E, 0x00, // ##### +0x3F, 0x80, // ####### +0x1F, 0x80, // ###### +0x07, 0xC0, // ##### +0x07, 0xC0, // ##### +0x01, 0xF0, // ##### +0x01, 0xF0, // ##### +0x00, 0x70, // ### +0x00, 0x30, // ## +0x00, 0x30, // ## +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @3000 ']' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x1F, 0xC0, // ####### +0x1F, 0xC0, // ####### +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0x1F, 0xC0, // ####### +0x1F, 0xC0, // ####### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @3050 '^' (14 pixels wide) +0x06, 0x00, // ## +0x06, 0x00, // ## +0x1F, 0x80, // ###### +0x39, 0xC0, // ### ### +0xA9, 0x60, // # # # # ## +0xE0, 0x70, // ### ### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @3100 '_' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0xFF, 0xFC, // ############## +0xFF, 0xFC, // ############## +0x00, 0x00, // + +// @3150 '`' (14 pixels wide) +0x00, 0x00, // +0x0E, 0x00, // ### +0x0E, 0x00, // ### +0x07, 0x00, // ### +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @3200 'a' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x3F, 0x80, // ####### +0x3F, 0x80, // ####### +0x01, 0xC0, // ### +0x3F, 0xC0, // ######## +0x3F, 0xC0, // ######## +0xE1, 0xC0, // ### ### +0xE1, 0xC0, // ### ### +0xE1, 0xC0, // ### ### +0xE1, 0xC0, // ### ### +0xE1, 0xC0, // ### ### +0x3E, 0x70, // ##### ### +0x3E, 0x70, // ##### ### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @3250 'b' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0xF8, 0x00, // ##### +0xF8, 0x00, // ##### +0x38, 0x00, // ### +0x38, 0x00, // ### +0x38, 0x00, // ### +0x3F, 0x80, // ####### +0x3F, 0x80, // ####### +0x39, 0xC0, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x3F, 0xC0, // ######## +0x3F, 0xC0, // ######## +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @3300 'c' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x3F, 0x80, // ####### +0x3F, 0x80, // ####### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0x00, // ### +0xE0, 0x00, // ### +0xE0, 0x00, // ### +0xE0, 0x00, // ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0x3F, 0x80, // ####### +0x3F, 0x80, // ####### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @3350 'd' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x07, 0xC0, // ##### +0x07, 0xC0, // ##### +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0x1F, 0xC0, // ####### +0x1F, 0xC0, // ####### +0x39, 0xC0, // ### ### +0xE1, 0xC0, // ### ### +0xE1, 0xC0, // ### ### +0xE1, 0xC0, // ### ### +0xE1, 0xC0, // ### ### +0xE1, 0xC0, // ### ### +0xE1, 0xC0, // ### ### +0xE1, 0xC0, // ### ### +0x3E, 0x70, // ##### ### +0x3E, 0x70, // ##### ### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @3400 'e' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x3F, 0x80, // ####### +0x3F, 0x80, // ####### +0xE0, 0xE0, // ### ### +0xFF, 0xE0, // ########### +0xFF, 0xE0, // ########### +0xE0, 0x00, // ### +0xE0, 0x00, // ### +0xE0, 0x00, // ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0x3F, 0x80, // ####### +0x3F, 0x80, // ####### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @3450 'f' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x07, 0xC0, // ##### +0x07, 0xC0, // ##### +0x0E, 0x70, // ### ### +0x0E, 0x30, // ### ## +0x0E, 0x30, // ### ## +0x0E, 0x00, // ### +0x0E, 0x00, // ### +0x3F, 0x80, // ####### +0x3F, 0x80, // ####### +0x0E, 0x00, // ### +0x0E, 0x00, // ### +0x0E, 0x00, // ### +0x0E, 0x00, // ### +0x0E, 0x00, // ### +0x0E, 0x00, // ### +0x3F, 0x80, // ####### +0x3F, 0x80, // ####### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @3500 'g' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x3E, 0x70, // ##### ### +0x3E, 0x70, // ##### ### +0xE1, 0xC0, // ### ### +0xE1, 0xC0, // ### ### +0xE1, 0xC0, // ### ### +0xE1, 0xC0, // ### ### +0xE1, 0xC0, // ### ### +0xE1, 0xC0, // ### ### +0xE1, 0xC0, // ### ### +0xE1, 0xC0, // ### ### +0x3F, 0xC0, // ######## +0x3F, 0xC0, // ######## +0x01, 0xC0, // ### +0xE1, 0xC0, // ### ### +0xE1, 0xC0, // ### ### +0x3F, 0x80, // ####### +0x3F, 0x80, // ####### + +// @3550 'h' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0xF8, 0x00, // ##### +0xF8, 0x00, // ##### +0x38, 0x00, // ### +0x38, 0x00, // ### +0x38, 0x00, // ### +0x39, 0xC0, // ### ### +0x39, 0xC0, // ### ### +0x3E, 0x70, // ##### ### +0x3E, 0x70, // ##### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0xF8, 0x70, // ##### ### +0xF8, 0x70, // ##### ### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @3600 'i' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x00, 0x00, // +0x00, 0x00, // +0x1F, 0x00, // ##### +0x1F, 0x00, // ##### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x1F, 0xC0, // ####### +0x1F, 0xC0, // ####### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @3650 'j' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x70, // ### +0x00, 0x70, // ### +0x00, 0x70, // ### +0x00, 0x00, // +0x00, 0x00, // +0x01, 0xF0, // ##### +0x01, 0xF0, // ##### +0x00, 0x70, // ### +0x00, 0x70, // ### +0x00, 0x70, // ### +0x00, 0x70, // ### +0x00, 0x70, // ### +0x00, 0x70, // ### +0x00, 0x70, // ### +0x00, 0x70, // ### +0x00, 0x70, // ### +0x00, 0x70, // ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x0F, 0xC0, // ###### +0x0F, 0xC0, // ###### + +// @3700 'k' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0xF8, 0x00, // ##### +0xF8, 0x00, // ##### +0x38, 0x00, // ### +0x38, 0x00, // ### +0x38, 0x00, // ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x39, 0xC0, // ### ### +0x3F, 0xC0, // ######## +0x3F, 0x80, // ####### +0x3F, 0x80, // ####### +0x3F, 0x80, // ####### +0x39, 0xC0, // ### ### +0x39, 0xC0, // ### ### +0x38, 0x70, // ### ### +0xF8, 0x70, // ##### ### +0xF8, 0x70, // ##### ### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @3750 'l' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x1F, 0x00, // ##### +0x1F, 0x00, // ##### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x1F, 0xC0, // ####### +0x1F, 0xC0, // ####### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @3800 'm' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0xF8, 0xE0, // ##### ### +0xF8, 0xE0, // ##### ### +0xFF, 0xF8, // ############# +0xE7, 0x38, // ### ### ### +0xE7, 0x38, // ### ### ### +0xE7, 0x38, // ### ### ### +0xE7, 0x38, // ### ### ### +0xE7, 0x38, // ### ### ### +0xE7, 0x38, // ### ### ### +0xE7, 0x38, // ### ### ### +0xE0, 0x38, // ### ### +0xE0, 0x38, // ### ### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @3850 'n' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0xE7, 0xC0, // ### ##### +0xE7, 0xC0, // ### ##### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @3900 'o' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x3F, 0x80, // ####### +0x3F, 0x80, // ####### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0xE0, 0xE0, // ### ### +0x3F, 0x80, // ####### +0x3F, 0x80, // ####### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @3950 'p' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0xE7, 0xC0, // ### ##### +0xE7, 0xC0, // ### ##### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x3F, 0xC0, // ######## +0x3F, 0xC0, // ######## +0x38, 0x00, // ### +0x38, 0x00, // ### +0x38, 0x00, // ### +0xFE, 0x00, // ####### +0xFE, 0x00, // ####### + +// @4000 'q' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x3E, 0x70, // ##### ### +0x3E, 0x70, // ##### ### +0xE1, 0xC0, // ### ### +0xE1, 0xC0, // ### ### +0xE1, 0xC0, // ### ### +0xE1, 0xC0, // ### ### +0xE1, 0xC0, // ### ### +0xE1, 0xC0, // ### ### +0xE1, 0xC0, // ### ### +0xE1, 0xC0, // ### ### +0x3F, 0xC0, // ######## +0x3F, 0xC0, // ######## +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0x01, 0xC0, // ### +0x07, 0xF0, // ####### +0x07, 0xF0, // ####### + +// @4050 'r' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0xE7, 0xC0, // ### ##### +0xE7, 0xC0, // ### ##### +0x3E, 0x70, // ##### ### +0x38, 0x70, // ### ### +0x38, 0x70, // ### ### +0x38, 0x00, // ### +0x38, 0x00, // ### +0x38, 0x00, // ### +0x38, 0x00, // ### +0x38, 0x00, // ### +0xFE, 0x00, // ####### +0xFE, 0x00, // ####### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @4100 's' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x3F, 0xC0, // ######## +0x3F, 0xC0, // ######## +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0x38, 0x00, // ### +0x1F, 0x80, // ###### +0x1F, 0x80, // ###### +0x01, 0xC0, // ### +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0x3F, 0xC0, // ######## +0x3F, 0xC0, // ######## +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @4150 't' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x06, 0x00, // ## +0x06, 0x00, // ## +0x0E, 0x00, // ### +0x0E, 0x00, // ### +0x0E, 0x00, // ### +0x7F, 0xC0, // ######### +0x7F, 0xC0, // ######### +0x0E, 0x00, // ### +0x0E, 0x00, // ### +0x0E, 0x00, // ### +0x0E, 0x00, // ### +0x0E, 0x00, // ### +0x0E, 0x00, // ### +0x0E, 0x70, // ### ### +0x0E, 0x70, // ### ### +0x07, 0xC0, // ##### +0x07, 0xC0, // ##### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @4200 'u' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0xE1, 0xC0, // ### ### +0xE1, 0xC0, // ### ### +0xE1, 0xC0, // ### ### +0xE1, 0xC0, // ### ### +0xE1, 0xC0, // ### ### +0xE1, 0xC0, // ### ### +0xE1, 0xC0, // ### ### +0xE1, 0xC0, // ### ### +0xE1, 0xC0, // ### ### +0xE1, 0xC0, // ### ### +0x3E, 0x70, // ##### ### +0x3E, 0x70, // ##### ### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @4250 'v' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0xE0, 0x3C, // ### #### +0xE0, 0x3C, // ### #### +0xE0, 0x3C, // ### #### +0xE0, 0x3C, // ### #### +0xE0, 0x3C, // ### #### +0xE0, 0x3C, // ### #### +0xE0, 0x3C, // ### #### +0x38, 0xF0, // ### #### +0x38, 0xF0, // ### #### +0x1F, 0xC0, // ####### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @4300 'w' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0xE0, 0x38, // ### ### +0xE0, 0x38, // ### ### +0xE0, 0x38, // ### ### +0xE7, 0x38, // ### ### ### +0xE7, 0x38, // ### ### ### +0xE7, 0x38, // ### ### ### +0xE7, 0x38, // ### ### ### +0xE7, 0x38, // ### ### ### +0xE7, 0x38, // ### ### ### +0xFF, 0xF8, // ############# +0x38, 0xE0, // ### ### +0x38, 0xE0, // ### ### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @4350 'x' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0xE0, 0x38, // ### ### +0xE0, 0x38, // ### ### +0x38, 0x70, // ### ### +0x1F, 0xC0, // ####### +0x1F, 0xC0, // ####### +0x1F, 0xC0, // ####### +0x1F, 0xC0, // ####### +0x1F, 0xC0, // ####### +0x1F, 0xC0, // ####### +0x38, 0x70, // ### ### +0xE0, 0x38, // ### ### +0xE0, 0x38, // ### ### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @4400 'y' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0xE0, 0x70, // ### ### +0x3F, 0xF0, // ########## +0x3F, 0xF0, // ########## +0x00, 0x70, // ### +0x00, 0x70, // ### +0x01, 0xC0, // ### +0xFF, 0x80, // ######### +0xFF, 0x80, // ######### + +// @4450 'z' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0xFF, 0xE0, // ########### +0xFF, 0xE0, // ########### +0xE3, 0x80, // ### ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x1C, 0x00, // ### +0x1C, 0x00, // ### +0x38, 0x00, // ### +0x38, 0x00, // ### +0xE0, 0xE0, // ### ### +0xFF, 0xE0, // ########### +0xFF, 0xE0, // ########### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @4500 '{' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x01, 0xF0, // ##### +0x01, 0xF0, // ##### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x3E, 0x00, // ##### +0x3E, 0x00, // ##### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x01, 0xF0, // ##### +0x01, 0xF0, // ##### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @4550 '|' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @4600 '}' (14 pixels wide) +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x3E, 0x00, // ##### +0x3E, 0x00, // ##### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x01, 0xE0, // #### +0x01, 0xE0, // #### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x07, 0x00, // ### +0x3E, 0x00, // ##### +0x3E, 0x00, // ##### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // + +// @4650 '~' (14 pixels wide) +0x00, 0x00, // +0x3C, 0xE0, // #### ### +0x3C, 0xE0, // #### ### +0xE7, 0x80, // ### #### +0xE7, 0x80, // ### #### +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +0x00, 0x00, // +}; diff --git a/src/Cafe/OS/libs/coreinit/coreinit_OverlayArena.cpp b/src/Cafe/OS/libs/coreinit/coreinit_OverlayArena.cpp new file mode 100644 index 00000000..5e5d8852 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_OverlayArena.cpp @@ -0,0 +1,34 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/common/OSCommon.h" +#include "coreinit_OverlayArena.h" + +namespace coreinit +{ + struct + { + bool isEnabled; + }g_coreinitOverlayArena = { 0 }; + + uint32 OSIsEnabledOverlayArena() + { + return g_coreinitOverlayArena.isEnabled ? 1 : 0; + } + + void OSEnableOverlayArena(uint32 uknParam, uint32be* areaOffset, uint32be* areaSize) + { + if (g_coreinitOverlayArena.isEnabled == false) + { + memory_enableOverlayArena(); + g_coreinitOverlayArena.isEnabled = true; + } + *areaOffset = MEMORY_OVERLAY_AREA_OFFSET; + *areaSize = MEMORY_OVERLAY_AREA_SIZE; + } + + void InitializeOverlayArena() + { + cafeExportRegister("coreinit", OSIsEnabledOverlayArena, LogType::Placeholder); + cafeExportRegister("coreinit", OSEnableOverlayArena, LogType::Placeholder); + g_coreinitOverlayArena.isEnabled = false; + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_OverlayArena.h b/src/Cafe/OS/libs/coreinit/coreinit_OverlayArena.h new file mode 100644 index 00000000..d9d5868f --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_OverlayArena.h @@ -0,0 +1,4 @@ +namespace coreinit +{ + void InitializeOverlayArena(); +}; \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Scheduler.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Scheduler.cpp new file mode 100644 index 00000000..d166690e --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_Scheduler.cpp @@ -0,0 +1,128 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "coreinit_Scheduler.h" + +thread_local sint32 s_schedulerLockCount = 0; + +#if BOOST_OS_WINDOWS +#include <synchapi.h> +CRITICAL_SECTION s_csSchedulerLock; +#else +#include <pthread.h> +pthread_mutex_t s_ptmSchedulerLock; +#endif + +void __OSLockScheduler(void* obj) +{ +#if BOOST_OS_WINDOWS + EnterCriticalSection(&s_csSchedulerLock); +#else + pthread_mutex_lock(&s_ptmSchedulerLock); +#endif + s_schedulerLockCount++; + cemu_assert_debug(s_schedulerLockCount <= 1); // >= 2 should not happen. Scheduler lock does not allow recursion +} + +bool __OSHasSchedulerLock() +{ + return s_schedulerLockCount > 0; +} + +bool __OSTryLockScheduler(void* obj) +{ + bool r; +#if BOOST_OS_WINDOWS + r = TryEnterCriticalSection(&s_csSchedulerLock); +#else + r = pthread_mutex_trylock(&s_ptmSchedulerLock) == 0; +#endif + if (r) + { + s_schedulerLockCount++; + return true; + } + return false; +} + +void __OSUnlockScheduler(void* obj) +{ + s_schedulerLockCount--; + cemu_assert_debug(s_schedulerLockCount >= 0); +#if BOOST_OS_WINDOWS + LeaveCriticalSection(&s_csSchedulerLock); +#else + pthread_mutex_unlock(&s_ptmSchedulerLock); +#endif +} + +namespace coreinit +{ + uint32 OSIsInterruptEnabled() + { + PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); + if (hCPU == nullptr) + return 0; + + return hCPU->coreInterruptMask; + } + + // disables interrupts and scheduling + uint32 OSDisableInterrupts() + { + // todo - rename SchedulerLock.cpp/h to Scheduler.cpp and move this there? + PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); + if (hCPU == nullptr) + return 0; + uint32 prevInterruptMask = hCPU->coreInterruptMask; + if (hCPU->coreInterruptMask != 0) + { + // we have no efficient method to turn off scheduling completely, so instead we just increase the remaining cycles + if (hCPU->remainingCycles >= 0x40000000) + { + forceLogDebug_printf("OSDisableInterrupts(): Warning - Interrupts already disabled? remCycles %08x LR %08x", hCPU->remainingCycles, hCPU->spr.LR); + } + hCPU->remainingCycles += 0x40000000; + } + hCPU->coreInterruptMask = 0; + return prevInterruptMask; + } + + uint32 OSRestoreInterrupts(uint32 interruptMask) + { + PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); + if (hCPU == nullptr) + return 0; + uint32 prevInterruptMask = hCPU->coreInterruptMask; + if (hCPU->coreInterruptMask == 0 && interruptMask != 0) + { + hCPU->remainingCycles -= 0x40000000; + } + hCPU->coreInterruptMask = interruptMask; + return prevInterruptMask; + } + + uint32 OSEnableInterrupts() + { + PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); + uint32 prevInterruptMask = hCPU->coreInterruptMask; + OSRestoreInterrupts(1); + return prevInterruptMask; + } + + void InitializeSchedulerLock() + { +#if BOOST_OS_WINDOWS + InitializeCriticalSection(&s_csSchedulerLock); +#else + pthread_mutexattr_t ma; + pthread_mutexattr_init(&ma); + pthread_mutexattr_settype(&ma, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&s_ptmSchedulerLock, &ma); +#endif + cafeExportRegister("coreinit", __OSLockScheduler, LogType::Placeholder); + cafeExportRegister("coreinit", __OSUnlockScheduler, LogType::Placeholder); + + cafeExportRegister("coreinit", OSDisableInterrupts, LogType::CoreinitThread); + cafeExportRegister("coreinit", OSEnableInterrupts, LogType::CoreinitThread); + cafeExportRegister("coreinit", OSRestoreInterrupts, LogType::CoreinitThread); + } +}; diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Scheduler.h b/src/Cafe/OS/libs/coreinit/coreinit_Scheduler.h new file mode 100644 index 00000000..5442ff12 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_Scheduler.h @@ -0,0 +1,16 @@ +#pragma once + +void __OSLockScheduler(void* obj = nullptr); +bool __OSHasSchedulerLock(); +bool __OSTryLockScheduler(void* obj = nullptr); +void __OSUnlockScheduler(void* obj = nullptr); + +namespace coreinit +{ + uint32 OSIsInterruptEnabled(); + uint32 OSDisableInterrupts(); + uint32 OSRestoreInterrupts(uint32 interruptMask); + uint32 OSEnableInterrupts(); + + void InitializeSchedulerLock(); +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Spinlock.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Spinlock.cpp new file mode 100644 index 00000000..16827d0f --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_Spinlock.cpp @@ -0,0 +1,217 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/libs/coreinit/coreinit_Thread.h" +#include "Cafe/OS/libs/coreinit/coreinit_Spinlock.h" +#include "Cafe/OS/libs/coreinit/coreinit_Time.h" +#include "Cafe/OS/libs/coreinit/coreinit.h" + +namespace coreinit +{ + void __OSBoostThread(OSThread_t* thread) + { + __OSLockScheduler(); + thread->stateFlags |= 0x20000; + thread->context.boostCount += 1; + __OSUpdateThreadEffectivePriority(thread); // sets thread->effectivePriority to zero since boostCount != 0 + __OSUnlockScheduler(); + } + + void __OSDeboostThread(OSThread_t* thread) + { + __OSLockScheduler(); + cemu_assert_debug(thread->context.boostCount != 0); + thread->context.boostCount -= 1; + if (thread->context.boostCount == 0) + { + thread->stateFlags &= ~0x20000; + __OSUpdateThreadEffectivePriority(thread); + // todo - reschedule if lower priority than other threads on current core? + } + __OSUnlockScheduler(); + } + + void OSInitSpinLock(OSSpinLock* spinlock) + { + spinlock->userData = spinlock; + spinlock->ownerThread = nullptr; + spinlock->count = 0; + spinlock->interruptMask = 1; + } + + bool OSAcquireSpinLock(OSSpinLock* spinlock) + { + OSThread_t* currentThread = OSGetCurrentThread(); + if (spinlock->ownerThread == currentThread) + { + spinlock->count += 1; + return true; + } + else + { + // loop until lock acquired + while (!spinlock->ownerThread.atomic_compare_exchange(nullptr, currentThread)) + { + OSYieldThread(); + } + } + __OSBoostThread(currentThread); + return true; + } + + bool OSTryAcquireSpinLock(OSSpinLock* spinlock) + { + OSThread_t* currentThread = OSGetCurrentThread(); + if (spinlock->ownerThread == currentThread) + { + spinlock->count += 1; + return true; + } + // try acquire once + if (!spinlock->ownerThread.atomic_compare_exchange(nullptr, currentThread)) + return false; + __OSBoostThread(currentThread); + return true; + } + + bool OSTryAcquireSpinLockWithTimeout(OSSpinLock* spinlock, uint64 timeout) + { + // used by CoD: Ghosts + cemu_assert_debug((timeout >> 63) == 0); // negative? + + OSThread_t* currentThread = OSGetCurrentThread(); + if (spinlock->ownerThread == currentThread) + { + spinlock->count += 1; + return true; + } + else + { + // loop until lock acquired or timeout occurred + uint64 timeoutValue = coreinit_getTimerTick() + coreinit::EspressoTime::ConvertNsToTimerTicks(timeout); + while (!spinlock->ownerThread.atomic_compare_exchange(nullptr, currentThread)) + { + OSYieldThread(); + if (coreinit_getTimerTick() >= timeoutValue) + { + return false; + } + } + } + __OSBoostThread(currentThread); + return true; + } + + bool OSReleaseSpinLock(OSSpinLock* spinlock) + { + OSThread_t* currentThread = OSGetCurrentThread(); + cemu_assert_debug(spinlock->ownerThread == currentThread); + if (spinlock->count != 0) + { + spinlock->count -= 1; + return true; + } + // release spinlock + while (!spinlock->ownerThread.atomic_compare_exchange(currentThread, nullptr)); + __OSDeboostThread(currentThread); + return true; + } + + bool OSUninterruptibleSpinLock_Acquire(OSSpinLock* spinlock) + { + // frequently used by VC DS + OSThread_t* currentThread = OSGetCurrentThread(); + cemu_assert_debug(currentThread != nullptr); + if (spinlock->ownerThread == currentThread) + { + spinlock->count += 1; + return true; + } + else + { + // loop until lock acquired + while (!spinlock->ownerThread.atomic_compare_exchange(nullptr, currentThread)) + { + OSYieldThread(); + } + } + __OSBoostThread(currentThread); + spinlock->interruptMask = OSDisableInterrupts(); + cemu_assert_debug(spinlock->ownerThread == currentThread); + return true; + } + + bool OSUninterruptibleSpinLock_TryAcquire(OSSpinLock* spinlock) + { + OSThread_t* currentThread = OSGetCurrentThread(); + if (spinlock->ownerThread == currentThread) + { + spinlock->count += 1; + return true; + } + // try acquire once + if (!spinlock->ownerThread.atomic_compare_exchange(nullptr, currentThread)) + return false; + __OSBoostThread(currentThread); + spinlock->interruptMask = OSDisableInterrupts(); + return true; + } + + bool OSUninterruptibleSpinLock_TryAcquireWithTimeout(OSSpinLock* spinlock, uint64 timeout) + { + cemu_assert_debug((timeout >> 63) == 0); // negative? + + OSThread_t* currentThread = OSGetCurrentThread(); + if (spinlock->ownerThread == currentThread) + { + spinlock->count += 1; + return true; + } + else + { + // loop until lock acquired or timeout occurred + uint64 timeoutValue = coreinit_getTimerTick() + coreinit::EspressoTime::ConvertNsToTimerTicks(timeout); + while (!spinlock->ownerThread.atomic_compare_exchange(nullptr, currentThread)) + { + OSYieldThread(); + if (coreinit_getTimerTick() >= timeoutValue) + { + return false; + } + } + } + __OSBoostThread(currentThread); + spinlock->interruptMask = OSDisableInterrupts(); + return true; + } + + bool OSUninterruptibleSpinLock_Release(OSSpinLock* spinlock) + { + OSThread_t* currentThread = OSGetCurrentThread(); + cemu_assert_debug(spinlock->ownerThread == currentThread); + if (spinlock->count != 0) + { + spinlock->count -= 1; + return true; + } + // release spinlock + OSRestoreInterrupts(spinlock->interruptMask); + spinlock->interruptMask = 1; + while (!spinlock->ownerThread.atomic_compare_exchange(currentThread, nullptr)); + __OSDeboostThread(currentThread); + return true; + } + + void InitializeSpinlock() + { + cafeExportRegister("coreinit", OSInitSpinLock, LogType::Placeholder); + cafeExportRegister("coreinit", OSAcquireSpinLock, LogType::Placeholder); + cafeExportRegister("coreinit", OSTryAcquireSpinLock, LogType::Placeholder); + cafeExportRegister("coreinit", OSTryAcquireSpinLockWithTimeout, LogType::Placeholder); + cafeExportRegister("coreinit", OSReleaseSpinLock, LogType::Placeholder); + + cafeExportRegister("coreinit", OSUninterruptibleSpinLock_Acquire, LogType::Placeholder); + cafeExportRegister("coreinit", OSUninterruptibleSpinLock_TryAcquire, LogType::Placeholder); + cafeExportRegister("coreinit", OSUninterruptibleSpinLock_TryAcquireWithTimeout, LogType::Placeholder); + cafeExportRegister("coreinit", OSUninterruptibleSpinLock_Release, LogType::Placeholder); + } +#pragma endregion +} diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Spinlock.h b/src/Cafe/OS/libs/coreinit/coreinit_Spinlock.h new file mode 100644 index 00000000..cb1934a1 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_Spinlock.h @@ -0,0 +1,28 @@ +#pragma once + +namespace coreinit +{ + struct OSSpinLock + { + /* +0x00 */ MEMPTR<struct OSThread_t> ownerThread; + /* +0x04 */ MEMPTR<void> userData; + /* +0x08 */ uint32be count; + /* +0x0C */ uint32be interruptMask; + }; + + static_assert(sizeof(OSSpinLock) == 0x10); + + void InitializeSpinlock(); + + void OSInitSpinLock(OSSpinLock* spinlock); + + bool OSAcquireSpinLock(OSSpinLock* spinlock); + bool OSTryAcquireSpinLock(OSSpinLock* spinlock); + bool OSTryAcquireSpinLockWithTimeout(OSSpinLock* spinlock, uint64 timeout); + bool OSReleaseSpinLock(OSSpinLock* spinlock); + + bool OSUninterruptibleSpinLock_Acquire(OSSpinLock* spinlock); + bool OSUninterruptibleSpinLock_TryAcquire(OSSpinLock* spinlock); + bool OSUninterruptibleSpinLock_TryAcquireWithTimeout(OSSpinLock* spinlock, uint64 timeout); + bool OSUninterruptibleSpinLock_Release(OSSpinLock* spinlock); +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Synchronization.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Synchronization.cpp new file mode 100644 index 00000000..84f7c5cf --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_Synchronization.cpp @@ -0,0 +1,669 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/libs/coreinit/coreinit.h" +#include "Cafe/OS/libs/coreinit/coreinit_Thread.h" +#include "Cafe/OS/libs/coreinit/coreinit_Alarm.h" +#include "Cafe/OS/libs/coreinit/coreinit_Time.h" +#include "util/helpers/fspinlock.h" + +namespace coreinit +{ + /************* OSEvent ************/ + + void OSInitEvent(OSEvent* event, OSEvent::EVENT_STATE initialState, OSEvent::EVENT_MODE mode) + { + event->magic = 'eVnT'; + cemu_assert_debug(event->magic == 0x65566e54); + event->userData = nullptr; + event->ukn08 = 0; + event->state = initialState; + event->mode = mode; + OSInitThreadQueueEx(&event->threadQueue, event); + } + + void OSInitEventEx(OSEvent* event, OSEvent::EVENT_STATE initialState, OSEvent::EVENT_MODE mode, void* userData) + { + OSInitEvent(event, initialState, mode); + event->userData = userData; + } + + void OSResetEvent(OSEvent* event) + { + __OSLockScheduler(); + if (event->state == OSEvent::EVENT_STATE::STATE_SIGNALED) + event->state = OSEvent::EVENT_STATE::STATE_NOT_SIGNALED; + __OSUnlockScheduler(); + } + + void OSWaitEventInternal(OSEvent* event) + { + if (event->state == OSEvent::EVENT_STATE::STATE_SIGNALED) + { + if (event->mode == OSEvent::EVENT_MODE::MODE_AUTO) + event->state = OSEvent::EVENT_STATE::STATE_NOT_SIGNALED; + } + else + { + // enter wait queue + event->threadQueue.queueAndWait(OSGetCurrentThread()); + } + } + + void OSWaitEvent(OSEvent* event) + { + __OSLockScheduler(); + OSWaitEventInternal(event); + __OSUnlockScheduler(); + } + + struct WaitEventWithTimeoutData + { + OSThread_t* thread; + OSThreadQueue* threadQueue; + std::atomic_bool hasTimeout; + }; + + void _OSWaitEventWithTimeoutHandler(uint64 currentTick, void* context) + { + cemu_assert_debug(__OSHasSchedulerLock()); + WaitEventWithTimeoutData* data = (WaitEventWithTimeoutData*)context; + if (data->thread->state == OSThread_t::THREAD_STATE::STATE_WAITING) + { + data->hasTimeout = true; + data->threadQueue->cancelWait(data->thread); + } + } + + uint64 coreinit_getOSTime(); + + bool OSWaitEventWithTimeout(OSEvent* event, uint64 timeout) + { + __OSLockScheduler(); + if (event->state == OSEvent::EVENT_STATE::STATE_SIGNALED) + { + if (event->mode == OSEvent::EVENT_MODE::MODE_AUTO) + event->state = OSEvent::EVENT_STATE::STATE_NOT_SIGNALED; + } + else + { + if (timeout == 0) + { + // fail immediately + __OSUnlockScheduler(); + return false; + } + // wait and set timeout + + // workaround for a bad implementation in some Unity games (like Qube Directors Cut, see FEventWiiU::Wait) + // where the the return value of OSWaitEventWithTimeout is ignored and instead the game measures the elapsed time to determine if a timeout occurred + timeout = timeout * 98ULL / 100ULL; // 98% (we want the function to return slightly before the actual timeout) + + WaitEventWithTimeoutData data; + data.thread = OSGetCurrentThread(); + data.threadQueue = &event->threadQueue; + data.hasTimeout = false; + + auto hostAlarm = coreinit::OSHostAlarmCreate(coreinit::coreinit_getOSTime() + coreinit::EspressoTime::ConvertNsToTimerTicks(timeout), 0, _OSWaitEventWithTimeoutHandler, &data); + event->threadQueue.queueAndWait(OSGetCurrentThread()); + coreinit::OSHostAlarmDestroy(hostAlarm); + if (data.hasTimeout) + { + __OSUnlockScheduler(); + return false; + } + } + __OSUnlockScheduler(); + return true; + } + + void OSSignalEventInternal(OSEvent* event) + { + cemu_assert_debug(__OSHasSchedulerLock()); + if (event->state == OSEvent::EVENT_STATE::STATE_SIGNALED) + { + return; + } + if (event->mode == OSEvent::EVENT_MODE::MODE_AUTO) + { + // in auto mode wake up one thread or if there is none then set signaled + if (event->threadQueue.isEmpty()) + event->state = OSEvent::EVENT_STATE::STATE_SIGNALED; + else + event->threadQueue.wakeupSingleThreadWaitQueue(true); + } + else + { + // in manual mode wake up all threads and set to signaled + event->state = OSEvent::EVENT_STATE::STATE_SIGNALED; + event->threadQueue.wakeupEntireWaitQueue(true); + } + } + + void OSSignalEvent(OSEvent* event) + { + __OSLockScheduler(); + OSSignalEventInternal(event); + __OSUnlockScheduler(); + } + + void OSSignalEventAllInternal(OSEvent* event) + { + if (event->state == OSEvent::EVENT_STATE::STATE_SIGNALED) + { + return; + } + if (event->mode == OSEvent::EVENT_MODE::MODE_AUTO) + { + // in auto mode wake up one thread or if there is none then set signaled + if (event->threadQueue.isEmpty()) + event->state = OSEvent::EVENT_STATE::STATE_SIGNALED; + else + event->threadQueue.wakeupEntireWaitQueue(true); + } + else + { + // in manual mode wake up all threads and set to signaled + event->state = OSEvent::EVENT_STATE::STATE_SIGNALED; + event->threadQueue.wakeupEntireWaitQueue(true); + } + } + + void OSSignalEventAll(OSEvent* event) + { + __OSLockScheduler(); + OSSignalEventAllInternal(event); + __OSUnlockScheduler(); + } + + /************* OSRendezvous ************/ + + SysAllocator<OSEvent> g_rendezvousEvent; + + void OSInitRendezvous(OSRendezvous* rendezvous) + { + __OSLockScheduler(); + rendezvous->userData = rendezvous; + for (sint32 i = 0; i < PPC_CORE_COUNT; i++) + rendezvous->coreHit[i] = 0; + __OSUnlockScheduler(); + } + + bool OSWaitRendezvous(OSRendezvous* rendezvous, uint32 coreMask) + { + __OSLockScheduler(); + rendezvous->coreHit[OSGetCoreId()] = 1; + OSSignalEventAllInternal(g_rendezvousEvent.GetPtr()); + while (true) + { + bool metAll = true; + for(sint32 i=0; i<PPC_CORE_COUNT; i++) + { + if( (coreMask & (1<<i)) == 0 ) + continue; // core not required by core mask + if (rendezvous->coreHit[i] == 0) + { + metAll = false; + break; + } + } + if (metAll) + break; + OSWaitEventInternal(g_rendezvousEvent.GetPtr()); + } + __OSUnlockScheduler(); + return true; + } + + /************* OSMutex ************/ + + void OSInitMutexEx(OSMutex* mutex, void* userData) + { + mutex->magic = 'mUtX'; + mutex->userData = userData; + mutex->ukn08 = 0; + mutex->owner = nullptr; + mutex->lockCount = 0; + OSInitThreadQueueEx(&mutex->threadQueue, mutex); + } + + void OSInitMutex(OSMutex* mutex) + { + OSInitMutexEx(mutex, nullptr); + } + + void OSLockMutexInternal(OSMutex* mutex) + { + OSThread_t* currentThread = OSGetCurrentThread(); + int_fast32_t failedAttempts = 0; + while (true) + { + if (mutex->owner == nullptr) + { + // acquire lock + mutex->owner = currentThread; + cemu_assert_debug(mutex->lockCount == 0); + mutex->lockCount = 1; + // cemu_assert_debug(mutex->next == nullptr && mutex->prev == nullptr); -> not zero initialized + currentThread->mutexQueue.addMutex(mutex); + break; + } + else if (mutex->owner == currentThread) + { + mutex->lockCount = mutex->lockCount + 1; + break; + } + else + { + if (failedAttempts >= 0x800) + cemuLog_force("Detected long-term contested OSLockMutex"); + currentThread->waitingForMutex = mutex; + mutex->threadQueue.queueAndWait(currentThread); + currentThread->waitingForMutex = nullptr; + failedAttempts++; + } + } + } + + void OSLockMutex(OSMutex* mutex) + { + __OSLockScheduler(); + OSTestThreadCancelInternal(); + OSLockMutexInternal(mutex); + __OSUnlockScheduler(); + } + + bool OSTryLockMutex(OSMutex* mutex) + { + OSThread_t* currentThread = OSGetCurrentThread(); + __OSLockScheduler(); + OSTestThreadCancelInternal(); + if (mutex->owner == nullptr) + { + // acquire lock + mutex->owner = currentThread; + cemu_assert_debug(mutex->lockCount == 0); + mutex->lockCount = 1; + // cemu_assert_debug(mutex->next == nullptr && mutex->prev == nullptr); -> not zero initialized + currentThread->mutexQueue.addMutex(mutex); + // currentThread->cancelState = currentThread->cancelState | 0x10000; + } + else if (mutex->owner == currentThread) + { + mutex->lockCount = mutex->lockCount + 1; + } + else + { + __OSUnlockScheduler(); + return false; + } + __OSUnlockScheduler(); + return true; + } + + void OSUnlockMutexInternal(OSMutex* mutex) + { + OSThread_t* currentThread = OSGetCurrentThread(); + cemu_assert_debug(mutex->owner == currentThread); + cemu_assert_debug(mutex->lockCount > 0); + mutex->lockCount = mutex->lockCount - 1; + if (mutex->lockCount == 0) + { + currentThread->mutexQueue.removeMutex(mutex); + mutex->owner = nullptr; + if (!mutex->threadQueue.isEmpty()) + mutex->threadQueue.wakeupSingleThreadWaitQueue(true); + } + // currentThread->cancelState = currentThread->cancelState & ~0x10000; + } + + void OSUnlockMutex(OSMutex* mutex) + { + __OSLockScheduler(); + OSUnlockMutexInternal(mutex); + __OSUnlockScheduler(); + } + + /************* OSCond ************/ + + void OSInitCond(OSCond* cond) + { + cond->magic = 0x634e6456; + cond->userData = nullptr; + cond->ukn08 = 0; + OSInitThreadQueueEx(&cond->threadQueue, cond); + } + + void OSInitCondEx(OSCond* cond, void* userData) + { + OSInitCond(cond); + cond->userData = userData; + } + + void OSSignalCond(OSCond* cond) + { + OSWakeupThread(&cond->threadQueue); + } + + void OSWaitCond(OSCond* cond, OSMutex* mutex) + { + // seen in Bayonetta 2 + // releases the mutex while waiting for the condition to be signaled + __OSLockScheduler(); + OSThread_t* currentThread = OSGetCurrentThread(); + cemu_assert_debug(mutex->owner == currentThread); + sint32 prevLockCount = mutex->lockCount; + // unlock mutex + mutex->lockCount = 0; + currentThread->mutexQueue.removeMutex(mutex); + mutex->owner = nullptr; + if (!mutex->threadQueue.isEmpty()) + mutex->threadQueue.wakeupEntireWaitQueue(false); + // wait on condition + cond->threadQueue.queueAndWait(currentThread); + // reacquire mutex + OSLockMutexInternal(mutex); + mutex->lockCount = prevLockCount; + __OSUnlockScheduler(); + } + + /************* OSSemaphore ************/ + + void OSInitSemaphoreEx(OSSemaphore* semaphore, sint32 initialCount, void* userData) + { + __OSLockScheduler(); + semaphore->magic = 0x73506852; + semaphore->userData = userData; + semaphore->ukn08 = 0; + semaphore->count = initialCount; + OSInitThreadQueueEx(&semaphore->threadQueue, semaphore); + __OSUnlockScheduler(); + } + + void OSInitSemaphore(OSSemaphore* semaphore, sint32 initialCount) + { + OSInitSemaphoreEx(semaphore, initialCount, nullptr); + } + + sint32 OSWaitSemaphoreInternal(OSSemaphore* semaphore) + { + cemu_assert_debug(__OSHasSchedulerLock()); + while (true) + { + sint32 prevCount = semaphore->count; + if (prevCount > 0) + { + semaphore->count = prevCount - 1; + return prevCount; + } + semaphore->threadQueue.queueAndWait(OSGetCurrentThread()); + } + } + + sint32 OSWaitSemaphore(OSSemaphore* semaphore) + { + __OSLockScheduler(); + sint32 r = OSWaitSemaphoreInternal(semaphore); + __OSUnlockScheduler(); + return r; + } + + sint32 OSTryWaitSemaphore(OSSemaphore* semaphore) + { + __OSLockScheduler(); + sint32 prevCount = semaphore->count; + if (prevCount > 0) + { + semaphore->count = prevCount - 1; + } + __OSUnlockScheduler(); + return prevCount; + } + + sint32 OSSignalSemaphoreInternal(OSSemaphore* semaphore, bool reschedule) + { + cemu_assert_debug(__OSHasSchedulerLock()); + sint32 prevCount = semaphore->count; + semaphore->count = prevCount + 1; + semaphore->threadQueue.wakeupEntireWaitQueue(reschedule); + return prevCount; + } + + sint32 OSSignalSemaphore(OSSemaphore* semaphore) + { + __OSLockScheduler(); + sint32 r = OSSignalSemaphoreInternal(semaphore, true); + __OSUnlockScheduler(); + return r; + } + + sint32 OSGetSemaphoreCount(OSSemaphore* semaphore) + { + // seen in Assassin's Creed 4 + __OSLockScheduler(); + sint32 currentCount = semaphore->count; + __OSUnlockScheduler(); + return currentCount; + } + + /************* OSFastMutex ************/ + + void OSFastMutex_Init(OSFastMutex* fastMutex, void* userData) + { + fastMutex->magic = 0x664d7458; + fastMutex->userData = userData; + fastMutex->owner = nullptr; + fastMutex->lockCount = 0; + fastMutex->contendedState = 0; + fastMutex->threadQueueSmall.head = nullptr; + fastMutex->threadQueueSmall.tail = nullptr; + fastMutex->ownedLink.next = nullptr; + fastMutex->ownedLink.prev = nullptr; + fastMutex->contendedLink.next = nullptr; + fastMutex->contendedLink.prev = nullptr; + } + + FSpinlock g_fastMutexSpinlock; + + void _OSFastMutex_AcquireContention(OSFastMutex* fastMutex) + { + g_fastMutexSpinlock.acquire(); + } + + void _OSFastMutex_ReleaseContention(OSFastMutex* fastMutex) + { + g_fastMutexSpinlock.release(); + } + + void OSFastMutex_LockInternal(OSFastMutex* fastMutex) + { + cemu_assert_debug(!__OSHasSchedulerLock()); + OSThread_t* currentThread = OSGetCurrentThread(); + _OSFastMutex_AcquireContention(fastMutex); + while (true) + { + if (fastMutex->owner.atomic_compare_exchange(nullptr, currentThread))//(fastMutex->owner == nullptr) + { + // acquire lock + cemu_assert_debug(fastMutex->owner == currentThread); + cemu_assert_debug(fastMutex->lockCount == 0); + fastMutex->lockCount = 1; + + // todo - add to thread owned fast mutex queue + break; + } + else if (fastMutex->owner == currentThread) + { + fastMutex->lockCount = fastMutex->lockCount + 1; + break; + } + else + { + currentThread->waitingForFastMutex = fastMutex; + __OSLockScheduler(); + fastMutex->threadQueueSmall.queueOnly(currentThread); + _OSFastMutex_ReleaseContention(fastMutex); + PPCCore_switchToSchedulerWithLock(); + currentThread->waitingForFastMutex = nullptr; + __OSUnlockScheduler(); + _OSFastMutex_AcquireContention(fastMutex); + continue; + } + } + _OSFastMutex_ReleaseContention(fastMutex); + } + + void OSFastMutex_Lock(OSFastMutex* fastMutex) + { + OSFastMutex_LockInternal(fastMutex); + } + + bool OSFastMutex_TryLock(OSFastMutex* fastMutex) + { + OSThread_t* currentThread = OSGetCurrentThread(); + _OSFastMutex_AcquireContention(fastMutex); + if (fastMutex->owner.atomic_compare_exchange(nullptr, currentThread)) + { + // acquire lock + cemu_assert_debug(fastMutex->owner == currentThread); + cemu_assert_debug(fastMutex->lockCount == 0); + fastMutex->lockCount = 1; + + // todo - add to thread owned fast mutex queue + } + else if (fastMutex->owner == currentThread) + { + fastMutex->lockCount = fastMutex->lockCount + 1; + } + else + { + _OSFastMutex_ReleaseContention(fastMutex); + return false; + } + _OSFastMutex_ReleaseContention(fastMutex); + return true; + } + + void OSFastMutex_UnlockInternal(OSFastMutex* fastMutex) + { + cemu_assert_debug(!__OSHasSchedulerLock()); + OSThread_t* currentThread = OSGetCurrentThread(); + _OSFastMutex_AcquireContention(fastMutex); + if (fastMutex->owner != currentThread) + { + // seen in Paper Mario Color Splash + //forceLog_printf("OSFastMutex_Unlock() called on mutex which is not owned by current thread"); + _OSFastMutex_ReleaseContention(fastMutex); + return; + } + cemu_assert_debug(fastMutex->lockCount > 0); + fastMutex->lockCount = fastMutex->lockCount - 1; + if (fastMutex->lockCount == 0) + { + // set owner to null + if (!fastMutex->owner.atomic_compare_exchange(currentThread, nullptr)) + { + cemu_assert_debug(false); // should never happen + } + if (!fastMutex->threadQueueSmall.isEmpty()) + { + __OSLockScheduler(); + fastMutex->threadQueueSmall.wakeupSingleThreadWaitQueue(false); + __OSUnlockScheduler(); + } + } + _OSFastMutex_ReleaseContention(fastMutex); + } + + void OSFastMutex_Unlock(OSFastMutex* fastMutex) + { + //__OSLockScheduler(); + OSFastMutex_UnlockInternal(fastMutex); + //__OSUnlockScheduler(); + } + + /************* OSFastCond ************/ + + void OSFastCond_Init(OSFastCond* fastCond, void* userData) + { + fastCond->magic = 0x664e6456; + fastCond->userData = userData; + fastCond->ukn08 = 0; + OSInitThreadQueueEx(&fastCond->threadQueue, fastCond); + } + + void OSFastCond_Wait(OSFastCond* fastCond, OSFastMutex* fastMutex) + { + // releases the mutex while waiting for the condition to be signaled + __OSLockScheduler(); + cemu_assert_debug(fastMutex->owner == OSGetCurrentThread()); + sint32 prevLockCount = fastMutex->lockCount; + // unlock mutex + fastMutex->lockCount = 0; + fastMutex->owner = nullptr; + if (!fastMutex->threadQueueSmall.isEmpty()) + fastMutex->threadQueueSmall.wakeupEntireWaitQueue(false); + // wait on condition + fastCond->threadQueue.queueAndWait(OSGetCurrentThread()); + // reacquire mutex + __OSUnlockScheduler(); + OSFastMutex_LockInternal(fastMutex); + fastMutex->lockCount = prevLockCount; + } + + void OSFastCond_Signal(OSFastCond* fastCond) + { + OSWakeupThread(&fastCond->threadQueue); + } + + /************* init ************/ + + void InitializeConcurrency() + { + OSInitEvent(g_rendezvousEvent.GetPtr(), OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, OSEvent::EVENT_MODE::MODE_AUTO); + + // OSEvent + cafeExportRegister("coreinit", OSInitEvent, LogType::ThreadSync); + cafeExportRegister("coreinit", OSInitEventEx, LogType::ThreadSync); + cafeExportRegister("coreinit", OSResetEvent, LogType::ThreadSync); + cafeExportRegister("coreinit", OSWaitEvent, LogType::ThreadSync); + cafeExportRegister("coreinit", OSWaitEventWithTimeout, LogType::ThreadSync); + cafeExportRegister("coreinit", OSSignalEvent, LogType::ThreadSync); + cafeExportRegister("coreinit", OSSignalEventAll, LogType::ThreadSync); + + // OSRendezvous + cafeExportRegister("coreinit", OSInitRendezvous, LogType::ThreadSync); + cafeExportRegister("coreinit", OSWaitRendezvous, LogType::ThreadSync); + + // OSMutex + cafeExportRegister("coreinit", OSInitMutex, LogType::ThreadSync); + cafeExportRegister("coreinit", OSInitMutexEx, LogType::ThreadSync); + cafeExportRegister("coreinit", OSLockMutex, LogType::ThreadSync); + cafeExportRegister("coreinit", OSTryLockMutex, LogType::ThreadSync); + cafeExportRegister("coreinit", OSUnlockMutex, LogType::ThreadSync); + + // OSCond + cafeExportRegister("coreinit", OSInitCond, LogType::ThreadSync); + cafeExportRegister("coreinit", OSInitCondEx, LogType::ThreadSync); + cafeExportRegister("coreinit", OSSignalCond, LogType::ThreadSync); + cafeExportRegister("coreinit", OSWaitCond, LogType::ThreadSync); + + // OSSemaphore + cafeExportRegister("coreinit", OSInitSemaphore, LogType::ThreadSync); + cafeExportRegister("coreinit", OSInitSemaphoreEx, LogType::ThreadSync); + cafeExportRegister("coreinit", OSWaitSemaphore, LogType::ThreadSync); + cafeExportRegister("coreinit", OSTryWaitSemaphore, LogType::ThreadSync); + cafeExportRegister("coreinit", OSSignalSemaphore, LogType::ThreadSync); + cafeExportRegister("coreinit", OSGetSemaphoreCount, LogType::ThreadSync); + + // OSFastMutex + cafeExportRegister("coreinit", OSFastMutex_Init, LogType::ThreadSync); + cafeExportRegister("coreinit", OSFastMutex_Lock, LogType::ThreadSync); + cafeExportRegister("coreinit", OSFastMutex_TryLock, LogType::ThreadSync); + cafeExportRegister("coreinit", OSFastMutex_Unlock, LogType::ThreadSync); + + // OSFastCond + cafeExportRegister("coreinit", OSFastCond_Init, LogType::ThreadSync); + cafeExportRegister("coreinit", OSFastCond_Wait, LogType::ThreadSync); + cafeExportRegister("coreinit", OSFastCond_Signal, LogType::ThreadSync); + } + +}; diff --git a/src/Cafe/OS/libs/coreinit/coreinit_SysHeap.cpp b/src/Cafe/OS/libs/coreinit/coreinit_SysHeap.cpp new file mode 100644 index 00000000..caa3d76d --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_SysHeap.cpp @@ -0,0 +1,40 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/libs/coreinit/coreinit_SysHeap.h" +#include "Cafe/OS/libs/coreinit/coreinit_MEM_ExpHeap.h" + +namespace coreinit +{ + coreinit::MEMHeapHandle _sysHeapHandle = MPTR_NULL; + sint32 _sysHeapAllocCounter = 0; + sint32 _sysHeapFreeCounter = 0; + + void* OSAllocFromSystem(uint32 size, uint32 alignment) + { + _sysHeapAllocCounter++; + return coreinit::MEMAllocFromExpHeapEx(_sysHeapHandle, size, alignment); + } + + void export_OSAllocFromSystem(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(size, 0); + ppcDefineParamS32(alignment, 1); + MEMPTR<void> mem = OSAllocFromSystem(size, alignment); + forceLogDebug_printf("OSAllocFromSystem(0x%x, %d) -> 0x%08x", size, alignment, mem.GetMPTR()); + osLib_returnFromFunction(hCPU, mem.GetMPTR()); + } + + void InitSysHeap() + { + uint32 sysHeapSize = 8 * 1024 * 1024; // actual size is unknown + MEMPTR<void> heapBaseAddress = memory_getPointerFromVirtualOffset(coreinit_allocFromSysArea(sysHeapSize, 0x1000)); + _sysHeapHandle = coreinit::MEMCreateExpHeapEx(heapBaseAddress.GetPtr(), sysHeapSize, MEM_HEAP_OPTION_THREADSAFE); + _sysHeapAllocCounter = 0; + _sysHeapFreeCounter = 0; + } + + void InitializeSysHeap() + { + osLib_addFunction("coreinit", "OSAllocFromSystem", export_OSAllocFromSystem); + } + +} diff --git a/src/Cafe/OS/libs/coreinit/coreinit_SysHeap.h b/src/Cafe/OS/libs/coreinit/coreinit_SysHeap.h new file mode 100644 index 00000000..428224af --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_SysHeap.h @@ -0,0 +1,8 @@ +#pragma once + +namespace coreinit +{ + void InitSysHeap(); + + void InitializeSysHeap(); +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_SystemInfo.cpp b/src/Cafe/OS/libs/coreinit/coreinit_SystemInfo.cpp new file mode 100644 index 00000000..83925acd --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_SystemInfo.cpp @@ -0,0 +1,25 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/libs/coreinit/coreinit_SystemInfo.h" + +namespace coreinit +{ + SysAllocator<OSSystemInfo> g_system_info; + + const OSSystemInfo& OSGetSystemInfo() + { + return *g_system_info.GetPtr(); + } + + void InitializeSystemInfo() + { + cemu_assert(ppcCyclesSince2000 != 0); + g_system_info->busClock = ESPRESSO_BUS_CLOCK; + g_system_info->coreClock = ESPRESSO_CORE_CLOCK; + g_system_info->ticksSince2000 = ESPRESSO_CORE_CLOCK_TO_TIMER_CLOCK(ppcCyclesSince2000); + g_system_info->l2cacheSize[0] = 512*1024; // 512KB // 512KB + g_system_info->l2cacheSize[1] = 2*1024*1924; // 2MB + g_system_info->l2cacheSize[2] = 512*1024; // 512KB + g_system_info->coreClockToBusClockRatio = 5; + cafeExportRegister("coreinit", OSGetSystemInfo, LogType::Placeholder); + } +} diff --git a/src/Cafe/OS/libs/coreinit/coreinit_SystemInfo.h b/src/Cafe/OS/libs/coreinit/coreinit_SystemInfo.h new file mode 100644 index 00000000..b2c50069 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_SystemInfo.h @@ -0,0 +1,21 @@ +#pragma once +#include "Cafe/OS/libs/coreinit/coreinit.h" + +namespace coreinit +{ + struct OSSystemInfo + { + uint32be busClock; + uint32be coreClock; + uint64be ticksSince2000; + uint32be l2cacheSize[PPC_CORE_COUNT]; + uint32be coreClockToBusClockRatio; + }; + + static_assert(sizeof(OSSystemInfo) == 0x20); + + const OSSystemInfo& OSGetSystemInfo(); + + void InitializeSystemInfo(); +}; + diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp new file mode 100644 index 00000000..43eae921 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp @@ -0,0 +1,1368 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/HW/Espresso/PPCCallback.h" +#include "Cafe/OS/RPL/rpl.h" +#include "Cafe/OS/libs/coreinit/coreinit_Thread.h" +#include "Cafe/OS/libs/coreinit/coreinit_Time.h" +#include "Cafe/OS/libs/coreinit/coreinit_Alarm.h" +#include "Cafe/OS/libs/snd_core/ax.h" +#include "Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h" +#include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h" + +#include "util/helpers/Semaphore.h" +#include "util/helpers/ConcurrentQueue.h" +#include "util/Fiber/Fiber.h" + +#include "util/helpers/helpers.h" + +SlimRWLock srwlock_activeThreadList; + +// public list of active threads +MPTR activeThread[256]; +sint32 activeThreadCount = 0; + +MPTR exitThreadPtr = 0; + +void nnNfp_update(); + +namespace coreinit +{ + void __OSFiberThreadEntry(void* thread); + void __OSAddReadyThreadToRunQueue(OSThread_t* thread); + void __OSRemoveThreadFromRunQueues(OSThread_t* thread); +}; + +namespace coreinit +{ + // scheduler state + std::atomic<bool> sSchedulerActive; + std::vector<std::thread> sSchedulerThreads; + std::mutex sSchedulerStateMtx; + + SysAllocator<OSThreadQueue> g_activeThreadQueue; // list of all threads (can include non-detached inactive threads) + + SysAllocator<OSThreadQueue, 3> g_coreRunQueue; + CounterSemaphore g_coreRunQueueThreadCount[3]; + + bool g_isMulticoreMode; + + __declspec(thread) uint32 t_assignedCoreIndex; + __declspec(thread) Fiber* t_schedulerFiber; + + struct OSHostThread + { + OSHostThread(OSThread_t* thread) : m_thread(thread), m_fiber(__OSFiberThreadEntry, this, this) + { + } + + ~OSHostThread() + { + } + + OSThread_t* m_thread; + Fiber m_fiber; + // padding (used as stack memory in recompiler) + uint8 padding[1024 * 128]; + PPCInterpreter_t ppcInstance; + uint32 selectedCore; + }; + + std::unordered_map<OSThread_t*, OSHostThread*> s_threadToFiber; + + // create host thread (fiber) that will be used to run the PPC instance + // note that host threads are fibers and not actual threads + void __OSCreateHostThread(OSThread_t* thread) + { + cemu_assert_debug(__OSHasSchedulerLock()); + cemu_assert_debug(s_threadToFiber.find(thread) == s_threadToFiber.end()); + + OSHostThread* hostThread = new OSHostThread(thread); + s_threadToFiber.emplace(thread, hostThread); + } + + // delete host thread + void __OSDeleteHostThread(OSThread_t* thread) + { + static OSHostThread* _deleteQueue = nullptr; + cemu_assert_debug(__OSHasSchedulerLock()); + + if (_deleteQueue) + { + delete _deleteQueue; + _deleteQueue = nullptr; + } + + // delete with a delay (using queue of length 1) + // since the fiber might still be in use right now we have to delay the deletion + + auto hostThread = s_threadToFiber[thread]; + s_threadToFiber.erase(thread); + _deleteQueue = hostThread; + } + + + // add thread to active queue + void __OSActivateThread(OSThread_t* thread) + { + cemu_assert_debug(__OSHasSchedulerLock()); + + g_activeThreadQueue->addThread(thread, &thread->activeThreadChain); // todo - check if thread already in queue + + MPTR threadMPTR = memory_getVirtualOffsetFromPointer(thread); + + srwlock_activeThreadList.LockWrite(); + bool alreadyActive = false; + for (sint32 i = 0; i < activeThreadCount; i++) + { + if (activeThread[i] == threadMPTR) + { + cemu_assert_debug(false); // should not happen + alreadyActive = true; + } + } + if (alreadyActive == false) + { + activeThread[activeThreadCount] = threadMPTR; + activeThreadCount++; + } + + __OSCreateHostThread(thread); + + srwlock_activeThreadList.UnlockWrite(); + } + + // remove thread from active queue. Reset id and state + void __OSDeactivateThread(OSThread_t* thread) + { + cemu_assert_debug(__OSHasSchedulerLock()); + + // remove thread from active thread list + MPTR t = memory_getVirtualOffsetFromPointer(thread); + srwlock_activeThreadList.LockWrite(); + bool noHit = true; + for (sint32 i = 0; i < activeThreadCount; i++) + { + if (activeThread[i] == t) + { + activeThread[i] = activeThread[activeThreadCount - 1]; + activeThreadCount--; + noHit = false; + break; + } + } + cemu_assert_debug(noHit == false); + srwlock_activeThreadList.UnlockWrite(); + + g_activeThreadQueue->removeThread(thread, &thread->activeThreadChain); // todo - check if thread in queue + + cemu_assert_debug(thread->state == OSThread_t::THREAD_STATE::STATE_NONE); + thread->id = 0x8000; + + __OSDeleteHostThread(thread); + } + + bool __OSIsThreadActive(OSThread_t* thread) + { + cemu_assert_debug(__OSHasSchedulerLock()); + MPTR threadMPTR = memory_getVirtualOffsetFromPointer(thread); + srwlock_activeThreadList.LockWrite(); + bool isRunable = false; + for (sint32 i = 0; i < activeThreadCount; i++) + { + if (activeThread[i] == threadMPTR) + { + srwlock_activeThreadList.UnlockWrite(); + return true; + } + } + srwlock_activeThreadList.UnlockWrite(); + return false; + } + + // thread + OSThread_t* __currentCoreThread[3] = {}; + + void OSSetCurrentThread(uint32 coreIndex, OSThread_t* thread) + { + if (coreIndex < 3) + __currentCoreThread[coreIndex] = thread; + } + + OSThread_t* OSGetCurrentThread() + { + PPCInterpreter_t* currentInstance = PPCInterpreter_getCurrentInstance(); + if (currentInstance == nullptr) + return nullptr; + return __currentCoreThread[currentInstance->spr.UPIR]; + } + + MPTR funcPtr_threadEntry = 0; + + void threadEntry(PPCInterpreter_t* hCPU) + { + OSThread_t* currentThread = coreinitThread_getCurrentThreadDepr(hCPU); + uint32 r3 = hCPU->gpr[3]; + uint32 r4 = hCPU->gpr[4]; + uint32 lr = hCPU->spr.LR; + + // cpp exception init callback + //const uint32 im = OSDisableInterrupts(); -> on an actual Wii U interrupts are disabled for this callback, but there are games that yield the thread in the callback (see Angry Birds Star Wars) + if (gCoreinitData->__cpp_exception_init_ptr != MPTR_NULL) + { + PPCCoreCallback(_swapEndianU32(gCoreinitData->__cpp_exception_init_ptr), ¤tThread->crt.eh_globals); + } + //OSRestoreInterrupts(im); + // forward to thread entrypoint + hCPU->spr.LR = lr; + hCPU->gpr[3] = r3; + hCPU->gpr[4] = r4; + hCPU->instructionPointer = _swapEndianU32(currentThread->entrypoint); + } + + void coreinitExport_OSExitThreadDepr(PPCInterpreter_t* hCPU); + + void initFunctionPointers() + { + exitThreadPtr = PPCInterpreter_makeCallableExportDepr(coreinitExport_OSExitThreadDepr); + funcPtr_threadEntry = PPCInterpreter_makeCallableExportDepr(threadEntry); + } + + void OSCreateThreadInternal(OSThread_t* thread, uint32 entryPoint, MPTR stackLowerBaseAddr, uint32 stackSize, uint8 affinityMask, OSThread_t::THREAD_TYPE threadType) + { + cemu_assert_debug(thread != nullptr); // make thread struct mandatory. Caller can always use SysAllocator + __OSLockScheduler(); + bool isThreadStillActive = __OSIsThreadActive(thread); + if (isThreadStillActive) + { + // workaround for games that restart threads to quickly + // seen in Fast Racing Neo at boot (0x020617BC OSCreateThread) + forceLog_printf("Game attempting to re-initialize existing thread"); + while ((thread->state == OSThread_t::THREAD_STATE::STATE_READY || thread->state == OSThread_t::THREAD_STATE::STATE_RUNNING) && thread->suspendCounter == 0) + { + // wait for thread to finish + __OSUnlockScheduler(); + OSSleepTicks(ESPRESSO_TIMER_CLOCK / 2000); // sleep 0.5ms + __OSLockScheduler(); + } + if (__OSIsThreadActive(thread) && thread->state == OSThread_t::THREAD_STATE::STATE_MORIBUND) + { + forceLog_printf("Calling OSCreateThread() on thread which is still active (Thread exited without detached flag). Forcing OSDetachThread()..."); + __OSUnlockScheduler(); + OSDetachThread(thread); + __OSLockScheduler(); + } + + } + cemu_assert_debug(__OSIsThreadActive(thread) == false); + __OSUnlockScheduler(); + + initFunctionPointers(); + if (thread == nullptr) + thread = (OSThread_t*)memory_getPointerFromVirtualOffset(coreinit_allocFromSysArea(sizeof(OSThread_t), 32)); + memset(thread, 0x00, sizeof(OSThread_t)); + // init signatures + thread->context.magic0 = OS_CONTEXT_MAGIC_0; + thread->context.magic1 = OS_CONTEXT_MAGIC_1; + thread->magic = 'tHrD'; + thread->type = threadType; + thread->state = (entryPoint != MPTR_NULL) ? OSThread_t::THREAD_STATE::STATE_READY : OSThread_t::THREAD_STATE::STATE_NONE; + thread->entrypoint = _swapEndianU32(entryPoint); + __OSSetThreadBasePriority(thread, 0); + __OSUpdateThreadEffectivePriority(thread); + // untested, but seems to work (Batman Arkham City uses these values to calculate the stack size for duplicated threads) + thread->stackBase = _swapEndianU32(stackLowerBaseAddr + stackSize); // these fields are quite important and lots of games rely on them being accurate (Examples: Darksiders 2, SMW3D, Batman Arkham City) + thread->stackEnd = _swapEndianU32(stackLowerBaseAddr); + // init stackpointer + thread->context.gpr[GPR_SP] = _swapEndianU32(stackLowerBaseAddr + stackSize - 0x20); // how many free bytes should there be at the beginning of the stack? + // init misc stuff + thread->attr = affinityMask; + thread->context.setAffinity(affinityMask); + thread->context.srr0 = funcPtr_threadEntry; + thread->context.lr = _swapEndianU32(exitThreadPtr); + thread->id = 0x8000; // Warriors Orochi 3 softlocks if this is zero due to confusing threads (_OSActivateThread should set this?) + // init ugqr + thread->context.gqr[0] = 0x00000000; + thread->context.gqr[1] = 0x00000000; + thread->context.gqr[2] = 0x00040004; + thread->context.gqr[3] = 0x00050005; + thread->context.gqr[4] = 0x00060006; + thread->context.gqr[5] = 0x00070007; + thread->context.gqr[6] = 0x00000000; + thread->context.gqr[7] = 0x00000000; + // init r2 (SDA2) and r3 (SDA) + thread->context.gpr[2] = _swapEndianU32(RPLLoader_GetSDA2Base()); + thread->context.gpr[13] = _swapEndianU32(RPLLoader_GetSDA1Base()); + // GHS related thread init? + + __OSLockScheduler(); + // if entrypoint is non-zero then put the thread on the active list and suspend it + if (entryPoint != MPTR_NULL) + { + thread->suspendCounter = 1; + __OSActivateThread(thread); + thread->state = OSThread_t::THREAD_STATE::STATE_READY; + } + else + thread->suspendCounter = 0; + __OSUnlockScheduler(); + } + + bool OSCreateThreadType(OSThread_t* thread, MPTR entryPoint, sint32 numParam, void* ptrParam, void* stackTop2, sint32 stackSize, sint32 priority, uint32 attr, OSThread_t::THREAD_TYPE threadType) + { + OSCreateThreadInternal(thread, entryPoint, memory_getVirtualOffsetFromPointer(stackTop2) - stackSize, stackSize, attr, threadType); + thread->context.gpr[3] = _swapEndianU32(numParam); // num arguments + thread->context.gpr[4] = _swapEndianU32(memory_getVirtualOffsetFromPointer(ptrParam)); // arguments pointer + __OSSetThreadBasePriority(thread, priority); + __OSUpdateThreadEffectivePriority(thread); + // set affinity + uint8 affinityMask = 0; + affinityMask = attr & 0x7; + // if no core is selected -> set current one + if (affinityMask == 0) + affinityMask |= (1 << PPCInterpreter_getCoreIndex(ppcInterpreterCurrentInstance)); + // set attr + // todo: Support for other attr bits + thread->attr = (affinityMask & 0xFF) | (attr & OSThread_t::ATTR_BIT::ATTR_DETACHED); + thread->context.setAffinity(affinityMask); + // recompile entry point function + if (entryPoint != MPTR_NULL) + PPCRecompiler_recompileIfUnvisited(entryPoint); + return true; + } + + bool OSCreateThread(OSThread_t* thread, MPTR entryPoint, sint32 numParam, void* ptrParam, void* stackTop2, sint32 stackSize, sint32 priority, uint32 attr) + { + return OSCreateThreadType(thread, entryPoint, numParam, ptrParam, stackTop2, stackSize, priority, attr, OSThread_t::THREAD_TYPE::TYPE_APP); + } + + bool OSRunThread(OSThread_t* thread, MPTR funcAddress, sint32 numParam, void* ptrParam) + { + __OSLockScheduler(); + + cemu_assert_debug(ppcInterpreterCurrentInstance == nullptr || OSGetCurrentThread() != thread); // called on self, what should this function do? + + if (thread->state != OSThread_t::THREAD_STATE::STATE_NONE && thread->state != OSThread_t::THREAD_STATE::STATE_MORIBUND) + { + // unsure about this case + forceLogDebug_printf("OSRunThread called on thread which cannot be ran"); + __OSUnlockScheduler(); + return false; + } + + if (thread->state == OSThread_t::THREAD_STATE::STATE_MORIBUND) + { + thread->state = OSThread_t::THREAD_STATE::STATE_NONE; + coreinit::__OSDeactivateThread(thread); + coreinit::__OSRemoveThreadFromRunQueues(thread); + } + + // set thread state + // todo - this should fully reinitialize the thread? + + thread->entrypoint = _swapEndianU32(funcAddress); + thread->context.srr0 = coreinit::funcPtr_threadEntry; + thread->context.lr = _swapEndianU32(exitThreadPtr); + thread->context.gpr[3] = _swapEndianU32(numParam); + thread->context.gpr[4] = _swapEndianU32(memory_getVirtualOffsetFromPointer(ptrParam)); + thread->suspendCounter = 0; // verify + + MPTR threadMPTR = memory_getVirtualOffsetFromPointer(thread); + + coreinit::__OSActivateThread(thread); + thread->state = OSThread_t::THREAD_STATE::STATE_READY; + + __OSAddReadyThreadToRunQueue(thread); + + __OSUnlockScheduler(); + + return true; + } + + void OSExitThread(sint32 exitValue) + { + PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); + hCPU->gpr[3] = exitValue; + OSThread_t* threadBE = coreinitThread_getCurrentThreadDepr(hCPU); + MPTR t = memory_getVirtualOffsetFromPointer(threadBE); + + // thread cleanup callback + if (!threadBE->cleanupCallback2.IsNull()) + { + threadBE->stateFlags = _swapEndianU32(_swapEndianU32(threadBE->stateFlags) | 0x00000001); + PPCCoreCallback(threadBE->cleanupCallback2.GetMPTR(), threadBE, _swapEndianU32(threadBE->stackEnd)); + } + // cpp exception cleanup + if (gCoreinitData->__cpp_exception_cleanup_ptr != 0 && threadBE->crt.eh_globals != nullptr) + { + PPCCoreCallback(_swapEndianU32(gCoreinitData->__cpp_exception_cleanup_ptr), &threadBE->crt.eh_globals); + threadBE->crt.eh_globals = nullptr; + } + // set exit code + threadBE->exitValue = exitValue; + + __OSLockScheduler(); + + // release held synchronization primitives + if (!threadBE->mutexQueue.isEmpty()) + { + cemuLog_force("OSExitThread: Thread is holding mutexes"); + while (true) + { + OSMutex* mutex = threadBE->mutexQueue.getFirst(); + if (!mutex) + break; + if (mutex->owner != threadBE) + { + cemuLog_force("OSExitThread: Thread is holding mutex which it doesn't own"); + threadBE->mutexQueue.removeMutex(mutex); + continue; + } + coreinit::OSUnlockMutexInternal(mutex); + } + } + // todo - release all fast mutexes + + // handle join queue + if (!threadBE->joinQueue.isEmpty()) + threadBE->joinQueue.wakeupEntireWaitQueue(false); + + if ((threadBE->attr & 8) != 0) + { + // deactivate thread since it is detached + threadBE->state = OSThread_t::THREAD_STATE::STATE_NONE; + coreinit::__OSDeactivateThread(threadBE); + // queue call to thread deallocator if set + if (!threadBE->deallocatorFunc.IsNull()) + __OSQueueThreadDeallocation(threadBE); + } + else + { + // non-detached threads remain active + threadBE->state = OSThread_t::THREAD_STATE::STATE_MORIBUND; + } + PPCCore_switchToSchedulerWithLock(); + } + + void OSSetThreadSpecific(uint32 index, void* value) + { + OSThread_t* currentThread = OSGetCurrentThread(); + if (index >= (uint32)currentThread->specificArray.size()) + return; + currentThread->specificArray[index] = value; + } + + void* OSGetThreadSpecific(uint32 index) + { + OSThread_t* currentThread = OSGetCurrentThread(); + if (index >= (uint32)currentThread->specificArray.size()) + return nullptr; + return currentThread->specificArray[index].GetPtr(); + } + + void OSSetThreadName(OSThread_t* thread, char* name) + { + thread->threadName = name; + } + + char* OSGetThreadName(OSThread_t* thread) + { + return thread->threadName.GetPtr(); + } + + void coreinitExport_OSExitThreadDepr(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(exitCode, 0); + OSExitThread(exitCode); + } + + void OSYieldThread() + { + PPCCore_switchToScheduler(); + } + + void _OSSleepTicks_alarmHandler(uint64 currentTick, void* context) + { + cemu_assert_debug(__OSHasSchedulerLock()); + OSThreadQueue* threadQueue = (OSThreadQueue*)context; + threadQueue->wakeupEntireWaitQueue(false); + } + + void OSSleepTicks(uint64 ticks) + { + cemu_assert_debug(__OSHasSchedulerLock() == false); + StackAllocator<OSThreadQueue> _threadQueue; + OSInitThreadQueue(_threadQueue.GetPointer()); + __OSLockScheduler(); + OSHostAlarm* hostAlarm = OSHostAlarmCreate(coreinit_getOSTime() + ticks, 0, _OSSleepTicks_alarmHandler, _threadQueue.GetPointer()); + _threadQueue.GetPointer()->queueAndWait(OSGetCurrentThread()); + OSHostAlarmDestroy(hostAlarm); + __OSUnlockScheduler(); + } + + void OSDetachThread(OSThread_t* thread) + { + __OSLockScheduler(); + thread->attr |= OSThread_t::ATTR_BIT::ATTR_DETACHED; + if (thread->state == OSThread_t::THREAD_STATE::STATE_MORIBUND) + { + // exit thread + // ? + + // todo -> call deallocator + thread->state = OSThread_t::THREAD_STATE::STATE_NONE; + thread->id = 0x8000; + coreinit::__OSDeactivateThread(thread); + if (!thread->joinQueue.isEmpty()) + { + // handle join queue + thread->joinQueue.wakeupEntireWaitQueue(true); + } + } + __OSUnlockScheduler(); + } + + bool OSJoinThread(OSThread_t* thread, uint32be* exitValue) + { + __OSLockScheduler(); + + if ((thread->attr & OSThread_t::ATTR_DETACHED) == 0 && thread->state != OSThread_t::THREAD_STATE::STATE_MORIBUND) + { + cemu_assert_debug(thread->joinQueue.isEmpty()); + // thread still running, wait in join queue + thread->joinQueue.queueAndWait(OSGetCurrentThread()); + } + else if (thread->state != OSThread_t::THREAD_STATE::STATE_MORIBUND) + { + // cannot join detached and active threads + forceLogDebug_printf("Cannot join detached active thread"); + __OSUnlockScheduler(); + return false; + } + + // thread already ended and is still attached, get return value + cemu_assert_debug(thread->state == OSThread_t::THREAD_STATE::STATE_MORIBUND); + cemu_assert_debug((thread->attr & OSThread_t::ATTR_DETACHED) == 0); + if (exitValue) + *exitValue = thread->exitValue; + // end thread + thread->state = OSThread_t::THREAD_STATE::STATE_NONE; + __OSDeactivateThread(thread); + coreinit::__OSRemoveThreadFromRunQueues(thread); + thread->id = 0x8000; + + if (!thread->deallocatorFunc.IsNull()) + __OSQueueThreadDeallocation(thread); + + __OSUnlockScheduler(); + + return true; + } + + // adds the thread to each core's run queue if in runable state + void __OSAddReadyThreadToRunQueue(OSThread_t* thread) + { + cemu_assert_debug(__OSHasSchedulerLock()); + if (thread->state != OSThread_t::THREAD_STATE::STATE_READY) + return; + if (thread->suspendCounter != 0) + return; + for (sint32 i = 0; i < PPC_CORE_COUNT; i++) + { + if (thread->currentRunQueue[i] != nullptr) + continue; // already on the queue + // check affinity + if(!thread->context.hasCoreAffinitySet(i)) + continue; + g_coreRunQueue.GetPtr()[i].addThread(thread, thread->linkRun + i); + thread->currentRunQueue[i] = (g_coreRunQueue.GetPtr() + i); + g_coreRunQueueThreadCount[i].increment(); + } + } + + void __OSRemoveThreadFromRunQueues(OSThread_t* thread) + { + cemu_assert_debug(__OSHasSchedulerLock()); + for (sint32 i = 0; i < PPC_CORE_COUNT; i++) + { + if(thread->currentRunQueue[i] == nullptr) + continue; + g_coreRunQueue.GetPtr()[i].removeThread(thread, thread->linkRun + i); + thread->currentRunQueue[i] = nullptr; + g_coreRunQueueThreadCount[i].decrement(); + } + } + + // returns true if thread runs on same core and has higher priority + bool __OSCoreShouldSwitchToThread(OSThread_t* currentThread, OSThread_t* newThread) + { + uint32 coreIndex = OSGetCoreId(); + if (!newThread->context.hasCoreAffinitySet(coreIndex)) + return false; + return newThread->effectivePriority < currentThread->effectivePriority; + } + + sint32 __OSResumeThreadInternal(OSThread_t* thread, sint32 resumeCount) + { + cemu_assert_debug(__OSHasSchedulerLock()); + sint32 previousSuspendCount = thread->suspendCounter; + cemu_assert_debug(previousSuspendCount >= 0); + if (previousSuspendCount == 0) + return 0; + thread->suspendCounter = previousSuspendCount - resumeCount; + if (thread->suspendCounter < 0) + thread->suspendCounter = 0; + if (thread->suspendCounter == 0) + { + __OSAddReadyThreadToRunQueue(thread); + // set awakened time if thread is ready + // todo - only set this once? + thread->wakeUpTime = PPCInterpreter_getMainCoreCycleCounter(); + // reschedule if thread has higher priority + if (ppcInterpreterCurrentInstance && __OSCoreShouldSwitchToThread(coreinit::OSGetCurrentThread(), thread)) + PPCCore_switchToSchedulerWithLock(); + } + return previousSuspendCount; + } + + sint32 OSResumeThread(OSThread_t* thread) + { + __OSLockScheduler(); + sint32 previousSuspendCount = __OSResumeThreadInternal(thread, 1); + __OSUnlockScheduler(); + return previousSuspendCount; + } + + void OSContinueThread(OSThread_t* thread) + { + __OSLockScheduler(); + __OSResumeThreadInternal(thread, thread->suspendCounter); + __OSUnlockScheduler(); + } + + void __OSSuspendThreadInternal(OSThread_t* thread) + { + cemu_assert_debug(__OSHasSchedulerLock()); + cemu_assert_debug(thread->state != OSThread_t::THREAD_STATE::STATE_NONE && thread->state != OSThread_t::THREAD_STATE::STATE_MORIBUND); // how to handle these? + + sint32 previousSuspendCount = thread->suspendCounter; + + if (OSGetCurrentThread() == thread) + { + thread->suspendCounter = thread->suspendCounter + 1; + PPCCore_switchToSchedulerWithLock(); + } + else + { + thread->suspendCounter = thread->suspendCounter + 1; + if (previousSuspendCount == 0) + { + __OSRemoveThreadFromRunQueues(thread); + // todo - if thread is still running find a way to cancel it's timeslice immediately + } + } + } + + void OSSuspendThread(OSThread_t* thread) + { + __OSLockScheduler(); + __OSSuspendThreadInternal(thread); + __OSUnlockScheduler(); + } + + void OSSleepThread(OSThreadQueue* threadQueue) + { + __OSLockScheduler(); + threadQueue->queueAndWait(OSGetCurrentThread()); + __OSUnlockScheduler(); + } + + void OSWakeupThread(OSThreadQueue* threadQueue) + { + __OSLockScheduler(); + threadQueue->wakeupEntireWaitQueue(true); + __OSUnlockScheduler(); + } + + bool OSSetThreadAffinity(OSThread_t* thread, uint32 affinityMask) + { + cemu_assert_debug((affinityMask & ~7) == 0); + __OSLockScheduler(); + uint32 prevAffinityMask = thread->context.getAffinity(); + if (thread->state == OSThread_t::THREAD_STATE::STATE_RUNNING) + { + thread->attr = (thread->attr & ~7) | (affinityMask & 7); + thread->context.setAffinity(affinityMask); + // should this reschedule the thread? + } + else if (prevAffinityMask != affinityMask) + { + __OSRemoveThreadFromRunQueues(thread); + thread->attr = (thread->attr & ~7) | (affinityMask & 7); + thread->context.setAffinity(affinityMask); + __OSAddReadyThreadToRunQueue(thread); + } + __OSUnlockScheduler(); + return true; + } + + uint32 OSGetThreadAffinity(OSThread_t* thread) + { + auto affinityMask = thread->context.getAffinity(); + cemu_assert_debug((affinityMask & ~7) == 0); + return affinityMask; + } + + void* OSSetThreadDeallocator(OSThread_t* thread, void* newDeallocatorFunc) + { + __OSLockScheduler(); + void* previousFunc = thread->deallocatorFunc.GetPtr(); + thread->deallocatorFunc = newDeallocatorFunc; + __OSUnlockScheduler(); + return previousFunc; + } + + void* OSSetThreadCleanupCallback(OSThread_t* thread, void* cleanupCallback) + { + __OSLockScheduler(); + void* previousFunc = thread->cleanupCallback2.GetPtr(); + thread->cleanupCallback2 = cleanupCallback; + __OSUnlockScheduler(); + return previousFunc; + } + + void __OSSetThreadBasePriority(OSThread_t* thread, sint32 newPriority) + { + cemu_assert_debug(newPriority >= 0 && newPriority < 32); + newPriority += OSThread_t::GetTypePriorityBase(thread->type); + thread->basePriority = newPriority; + } + + void __OSUpdateThreadEffectivePriority(OSThread_t* thread) + { + if (thread->context.boostCount != 0) + { + // temporarily boosted threads have their priority set to 0 (maximum) + thread->effectivePriority = 0; + return; + } + thread->effectivePriority = thread->basePriority; + } + + bool OSSetThreadPriority(OSThread_t* thread, sint32 newPriority) + { + if (newPriority < 0 || newPriority >= 0x20) + { + cemu_assert_debug(false); + return false; // invalid priority value + } + __OSLockScheduler(); + __OSSetThreadBasePriority(thread, newPriority); + __OSUpdateThreadEffectivePriority(thread); + // reschedule if needed + OSThread_t* currentThread = OSGetCurrentThread(); + if (currentThread && currentThread != thread) + { + if (__OSCoreShouldSwitchToThread(currentThread, thread)) + PPCCore_switchToSchedulerWithLock(); + } + __OSUnlockScheduler(); + return true; + } + + sint32 OSGetThreadPriority(OSThread_t* thread) + { + sint32 threadPriority = thread->basePriority; + OSThread_t::THREAD_TYPE threadType = thread->type; + threadPriority -= OSThread_t::GetTypePriorityBase(threadType); + cemu_assert_debug(threadPriority >= 0 && threadPriority < 32); + return threadPriority; + } + + bool OSIsThreadTerminated(OSThread_t* thread) + { + __OSLockScheduler(); + bool isTerminated = false; + if (thread->state == OSThread_t::THREAD_STATE::STATE_MORIBUND || thread->state == OSThread_t::THREAD_STATE::STATE_NONE) + isTerminated = true; + __OSUnlockScheduler(); + return isTerminated; + } + + bool OSIsThreadSuspended(OSThread_t* thread) + { + __OSLockScheduler(); + sint32 suspendCounter = thread->suspendCounter; + __OSUnlockScheduler(); + return suspendCounter > 0; + } + + void OSCancelThread(OSThread_t* thread) + { + __OSLockScheduler(); + cemu_assert_debug(thread->requestFlags == 0 || thread->requestFlags == OSThread_t::REQUEST_FLAG_CANCEL); // todo - how to handle cases where other flags are already set? + thread->requestFlags = OSThread_t::REQUEST_FLAG_CANCEL; + __OSUnlockScheduler(); + // if the canceled thread is self, then exit immediately + if (thread == OSGetCurrentThread()) + OSExitThread(-1); + } + + void OSTestThreadCancelInternal() + { + // also handles suspend request ? + cemu_assert_debug(__OSHasSchedulerLock()); + OSThread_t* thread = OSGetCurrentThread(); + if (thread->requestFlags == OSThread_t::REQUEST_FLAG_CANCEL) + { + __OSUnlockScheduler(); + OSExitThread(-1); + } + } + + void OSTestThreadCancel() + { + __OSLockScheduler(); + OSTestThreadCancelInternal(); + __OSUnlockScheduler(); + } + + void __OSSwitchToThreadFiber(OSThread_t* thread, uint32 coreIndex) + { + cemu_assert_debug(__OSHasSchedulerLock()); + cemu_assert_debug(s_threadToFiber.find(thread) != s_threadToFiber.end()); + + OSHostThread* hostThread = s_threadToFiber.find(thread)->second; + hostThread->selectedCore = coreIndex; + Fiber::Switch(hostThread->m_fiber); + } + + void __OSThreadLoadContext(PPCInterpreter_t* hCPU, OSThread_t* thread) + { + // load GPR + for (uint32 i = 0; i < 32; i++) + hCPU->gpr[i] = _swapEndianU32(thread->context.gpr[i]); + + // load SPR cr, lr, ctr, xer + ppc_setCR(hCPU, thread->context.cr); + hCPU->spr.LR = _swapEndianU32(thread->context.lr); + hCPU->spr.CTR = thread->context.ctr; + PPCInterpreter_setXER(hCPU, thread->context.xer); + + hCPU->fpscr = thread->context.fpscr.fpscr; + + // store floating point and Gekko registers + for (uint32 i = 0; i < 32; i++) + { + hCPU->fpr[i].fp0int = thread->context.fp_ps0[i]; + hCPU->fpr[i].fp1int = thread->context.fp_ps1[i]; + } + + for (uint32 i = 0; i < 8; i++) + { + hCPU->spr.UGQR[0 + i] = thread->context.gqr[i]; + } + + hCPU->instructionPointer = thread->context.srr0; + } + + void __OSThreadStoreContext(PPCInterpreter_t* hCPU, OSThread_t* thread) + { + // store GPR + for (uint32 i = 0; i < 32; i++) + thread->context.gpr[i] = _swapEndianU32(hCPU->gpr[i]); + // store SPRs + thread->context.cr = ppc_getCR(hCPU); + thread->context.lr = _swapEndianU32(hCPU->spr.LR); + thread->context.ctr = hCPU->spr.CTR; + thread->context.xer = PPCInterpreter_getXER(hCPU); + + thread->context.fpscr.fpscr = hCPU->fpscr; + thread->context.fpscr.padding = 0; + + // store floating point and Gekko registers + for (uint32 i = 0; i < 32; i++) + { + thread->context.fp_ps0[i] = hCPU->fpr[i].fp0int; + thread->context.fp_ps1[i] = hCPU->fpr[i].fp1int; + } + + for (uint32 i = 0; i < 8; i++) + { + thread->context.gqr[i] = hCPU->spr.UGQR[0 + i]; + } + + thread->context.srr0 = hCPU->instructionPointer; + } + + void __OSStoreThread(OSThread_t* thread, PPCInterpreter_t* hCPU) + { + if (thread->state == OSThread_t::THREAD_STATE::STATE_RUNNING) + { + thread->state = OSThread_t::THREAD_STATE::STATE_READY; + __OSAddReadyThreadToRunQueue(thread); + } + else if (thread->state == OSThread_t::THREAD_STATE::STATE_MORIBUND || thread->state == OSThread_t::THREAD_STATE::STATE_NONE || thread->state == OSThread_t::THREAD_STATE::STATE_WAITING) + { + // thread exited or suspending/waiting + } + else + { + cemu_assert_debug(false); + } + + thread->requestFlags = (OSThread_t::REQUEST_FLAG_BIT)(thread->requestFlags & OSThread_t::REQUEST_FLAG_CANCEL); // remove all flags except cancel flag + + // update total cycles + uint64 remainingCycles = std::min((uint64)ppcInterpreterCurrentInstance->remainingCycles, (uint64)thread->quantumTicks); + uint64 executedCycles = thread->quantumTicks - remainingCycles; + if (executedCycles < ppcInterpreterCurrentInstance->skippedCycles) + executedCycles = 0; + else + executedCycles -= ppcInterpreterCurrentInstance->skippedCycles; + thread->totalCycles += executedCycles; + // store context and set current thread to null + __OSThreadStoreContext(hCPU, thread); + OSSetCurrentThread(OSGetCoreId(), nullptr); + ppcInterpreterCurrentInstance = nullptr; + } + + void __OSLoadThread(OSThread_t* thread, PPCInterpreter_t* hCPU, uint32 coreIndex) + { + hCPU->LSQE = 1; + hCPU->PSE = 1; + hCPU->reservedMemAddr = MPTR_NULL; + hCPU->reservedMemValue = 0; + hCPU->spr.UPIR = coreIndex; + hCPU->coreInterruptMask = 1; + ppcInterpreterCurrentInstance = hCPU; + OSSetCurrentThread(OSGetCoreId(), thread); + __OSThreadLoadContext(hCPU, thread); + thread->context.upir = coreIndex; + thread->quantumTicks = ppcThreadQuantum; + // statistics + thread->wakeUpTime = PPCInterpreter_getMainCoreCycleCounter(); + thread->wakeUpCount = thread->wakeUpCount + 1; + } + + OSThread_t* __OSGetNextRunableThread(uint32 coreIndex) + { + cemu_assert_debug(__OSHasSchedulerLock()); + // pick thread, then remove from run queue + OSThreadQueue* runQueue = g_coreRunQueue.GetPtr() + coreIndex; + + OSThread_t* threadItr = runQueue->head.GetPtr(); + OSThread_t* selectedThread = nullptr; + if (!threadItr) + return nullptr; + selectedThread = threadItr; + + while (threadItr) + { + if (threadItr->effectivePriority < selectedThread->effectivePriority) + selectedThread = threadItr; + threadItr = threadItr->linkRun[coreIndex].next.GetPtr(); + } + + cemu_assert_debug(selectedThread->state == OSThread_t::THREAD_STATE::STATE_READY); + + __OSRemoveThreadFromRunQueues(selectedThread); + selectedThread->state = OSThread_t::THREAD_STATE::STATE_RUNNING; + + return selectedThread; + } + + void __OSCheckSystemEvents() + { + // AX update + snd_core::AXOut_update(); + // alarm update + coreinit::alarm_update(); + // nfp update + nnNfp_update(); + } + + Fiber* g_idleLoopFiber[3]{}; + + // idle fiber per core if no thread is runnable + // this is necessary since we can't block in __OSThreadSwitchToNext() (__OSStoreThread + thread switch must happen inside same scheduler lock) + void __OSThreadCoreIdle(void* unusedParam) + { + bool isMainCore = g_isMulticoreMode == false || t_assignedCoreIndex == 1; + sint32 coreIndex = t_assignedCoreIndex; + __OSUnlockScheduler(); + while (true) + { + if (!g_coreRunQueueThreadCount[coreIndex].isZero()) // avoid hammering the lock on the main core if there is no runable thread + { + __OSLockScheduler(); + OSThread_t* nextThread = __OSGetNextRunableThread(coreIndex); + if (nextThread) + { + cemu_assert_debug(nextThread->state == OSThread_t::THREAD_STATE::STATE_RUNNING); + __OSSwitchToThreadFiber(nextThread, coreIndex); + } + __OSUnlockScheduler(); + } + if (isMainCore) + { + __OSCheckSystemEvents(); + if(g_isMulticoreMode == false) + coreIndex = (coreIndex + 1) % 3; + } + else + { + // wait for semaphore (only in multicore mode) + g_coreRunQueueThreadCount[t_assignedCoreIndex].waitUntilNonZero(); + if (!sSchedulerActive.load(std::memory_order::relaxed)) + Fiber::Switch(*t_schedulerFiber); // switch back to original thread to exit + } + } + } + + void __OSThreadSwitchToNext() + { + cemu_assert_debug(__OSHasSchedulerLock()); + + OSHostThread* hostThread = (OSHostThread*)Fiber::GetFiberPrivateData(); + cemu_assert_debug(hostThread); + + //if (ppcInterpreterCurrentInstance) + // debug_printf("Core %d store thread %08x (t = %d)\n", hostThread->ppcInstance.sprNew.UPIR, memory_getVirtualOffsetFromPointer(hostThread->thread), t_assignedCoreIndex); + + // store context of current thread + __OSStoreThread(OSGetCurrentThread(), &hostThread->ppcInstance); + cemu_assert_debug(ppcInterpreterCurrentInstance == nullptr); + + if (!sSchedulerActive.load(std::memory_order::relaxed)) + { + __OSUnlockScheduler(); + Fiber::Switch(*t_schedulerFiber); // switch back to original thread entry for it to exit + } + // choose core + static uint32 _coreCounter = 0; + + sint32 coreIndex; + if (g_isMulticoreMode) + coreIndex = t_assignedCoreIndex; + else + { + coreIndex = _coreCounter; + _coreCounter = (_coreCounter + 1) % 3; + } + + // if main thread then dont forget to do update checks + bool isMainThread = g_isMulticoreMode == false || t_assignedCoreIndex == 1; + + // find next thread to run + // for main thread we force switching to the idle loop since it calls __OSCheckSystemEvents() + if(isMainThread) + Fiber::Switch(*g_idleLoopFiber[t_assignedCoreIndex]); + else if (OSThread_t* nextThread = __OSGetNextRunableThread(coreIndex)) + { + cemu_assert_debug(nextThread->state == OSThread_t::THREAD_STATE::STATE_RUNNING); + __OSSwitchToThreadFiber(nextThread, coreIndex); + } + else + { + Fiber::Switch(*g_idleLoopFiber[t_assignedCoreIndex]); + } + + cemu_assert_debug(__OSHasSchedulerLock()); + cemu_assert_debug(g_isMulticoreMode == false || hostThread->selectedCore == t_assignedCoreIndex); + + // load self thread + __OSLoadThread(hostThread->m_thread, &hostThread->ppcInstance, hostThread->selectedCore); + } + + void __OSFiberThreadEntry(void* _thread) + { + OSHostThread* hostThread = (OSHostThread*)_thread; + + _mm_setcsr(_mm_getcsr() | 0x8000); // flush denormals to zero + + uint32 lehmer_lcg = 12345; + + PPCInterpreter_t* hCPU = &hostThread->ppcInstance; + __OSLoadThread(hostThread->m_thread, hCPU, hostThread->selectedCore); + __OSUnlockScheduler(); // lock is always held when switching to a fiber, so we need to unlock it here + while (true) + { + // run one timeslice + hCPU->remainingCycles = ppcThreadQuantum; + hCPU->skippedCycles = 0; + // we add a slight randomized variance to the thread quantum to avoid getting stuck in repeated code sequences where the duration matches the thread quantum perfectly + // this was seen in Mario Party 10 on launch where several OSLockMutex operations would align in such a way that one thread would never successfully acquire the lock + hCPU->remainingCycles += (lehmer_lcg & 0x7F); + lehmer_lcg = (uint32)((uint64)lehmer_lcg * 279470273ull % 0xfffffffbull); + + if (hCPU->remainingCycles > 0) + { + // try to enter recompiler immediately + PPCRecompiler_attemptEnterWithoutRecompile(hCPU, hCPU->instructionPointer); + // keep executing as long as there are cycles left + while ((--hCPU->remainingCycles) >= 0) + PPCInterpreterSlim_executeInstruction(hCPU); + } + + // reset reservation + hCPU->reservedMemAddr = 0; + hCPU->reservedMemValue = 0; + + // reschedule + __OSLockScheduler(); + __OSThreadSwitchToNext(); + __OSUnlockScheduler(); + } + } + + void OSSchedulerCoreEmulationThread(void* _assignedCoreIndex) + { + SetThreadName(fmt::format("OSSchedulerThread[core={}]", (uintptr_t)_assignedCoreIndex).c_str()); + t_assignedCoreIndex = (sint32)(uintptr_t)_assignedCoreIndex; + _mm_setcsr(_mm_getcsr() | 0x8000); // flush denormals to zero + t_schedulerFiber = Fiber::PrepareCurrentThread(); + + // create scheduler idle fiber and switch to it + g_idleLoopFiber[t_assignedCoreIndex] = new Fiber(__OSThreadCoreIdle, nullptr, nullptr); + cemu_assert_debug(ppcInterpreterCurrentInstance == nullptr); + __OSLockScheduler(); + Fiber::Switch(*g_idleLoopFiber[t_assignedCoreIndex]); + // returned from scheduler loop, exit thread + cemu_assert_debug(!__OSHasSchedulerLock()); + } + + std::vector<std::thread::native_handle_type> g_schedulerThreadHandles; + + std::vector<std::thread::native_handle_type>& OSGetSchedulerThreads() + { + return g_schedulerThreadHandles; + } + + // starts PPC core emulation + void OSSchedulerBegin(sint32 numCPUEmulationThreads) + { + std::unique_lock _lock(sSchedulerStateMtx); + if (sSchedulerActive.exchange(true)) + return; + cemu_assert_debug(numCPUEmulationThreads == 1 || numCPUEmulationThreads == 3); + g_isMulticoreMode = numCPUEmulationThreads > 1; + if (numCPUEmulationThreads == 1) + sSchedulerThreads.emplace_back(OSSchedulerCoreEmulationThread, (void*)0); + else if (numCPUEmulationThreads == 3) + { + for (size_t i = 0; i < 3; i++) + sSchedulerThreads.emplace_back(OSSchedulerCoreEmulationThread, (void*)i); + } + else + cemu_assert_debug(false); + for (auto& it : sSchedulerThreads) + g_schedulerThreadHandles.emplace_back(it.native_handle()); + } + + // shuts down all scheduler host threads and deletes all fibers and their state + void OSSchedulerEnd() + { + std::unique_lock _lock(sSchedulerStateMtx); + sSchedulerActive.store(false); + for (size_t i = 0; i < Espresso::CORE_COUNT; i++) + g_coreRunQueueThreadCount[i].increment(); // make sure to wake up cores if they are paused and waiting for runnable threads + // wait for threads to stop execution + for (auto& threadItr : sSchedulerThreads) + threadItr.join(); + sSchedulerThreads.clear(); + g_schedulerThreadHandles.clear(); + // clean up all fibers + for (auto& it : g_idleLoopFiber) + { + delete it; + it = nullptr; + } + for (auto& it : s_threadToFiber) + { + OSHostThread* hostThread = it.second; + delete hostThread; + } + s_threadToFiber.clear(); + } + + SysAllocator<OSThread_t, PPC_CORE_COUNT> s_defaultThreads; + SysAllocator<uint8, PPC_CORE_COUNT * 1024 * 1024> s_stack; + + OSThread_t* OSGetDefaultThread(sint32 coreIndex) + { + if (coreIndex < 0 || coreIndex >= PPC_CORE_COUNT) + return nullptr; + return s_defaultThreads.GetPtr() + coreIndex; + } + + void* OSGetDefaultThreadStack(sint32 coreIndex, uint32& size) + { + if (coreIndex < 0 || coreIndex >= PPC_CORE_COUNT) + return nullptr; + size = 1024 * 1024; + return s_stack.GetPtr() + coreIndex * size; + } + + void OSInitThreadQueue(OSThreadQueue* threadQueue) + { + threadQueue->head = nullptr; + threadQueue->tail = nullptr; + threadQueue->userData = nullptr; + threadQueue->ukn0C = 0; + } + + void OSInitThreadQueueEx(OSThreadQueue* threadQueue, void* userData) + { + threadQueue->head = nullptr; + threadQueue->tail = nullptr; + threadQueue->userData = userData; + threadQueue->ukn0C = 0; + } + + /* Thread terminator threads (for calling thread deallocators) */ + struct TerminatorThread + { + struct DeallocatorQueueEntry + { + DeallocatorQueueEntry() {}; + DeallocatorQueueEntry(OSThread_t* thread, MEMPTR<void> stack, MEMPTR<void> deallocatorFunc) : thread(thread), stack(stack), deallocatorFunc(deallocatorFunc) {}; + + OSThread_t* thread{}; + MEMPTR<void> stack; + MEMPTR<void> deallocatorFunc; + }; + + SysAllocator<OSThread_t> terminatorThread; + SysAllocator<uint8, 16 * 1024> threadStack; + SysAllocator<char, 64> threadName; + SysAllocator<OSSemaphore> semaphoreQueuedDeallocators; + ConcurrentQueue<DeallocatorQueueEntry> queueDeallocators; + }; + + TerminatorThread s_terminatorThreads[PPC_CORE_COUNT]; + + void __OSTerminatorThreadFunc(PPCInterpreter_t* hCPU) + { + uint32 coreIndex = OSGetCoreId(); + while (OSWaitSemaphore(s_terminatorThreads[coreIndex].semaphoreQueuedDeallocators.GetPtr())) + { + TerminatorThread::DeallocatorQueueEntry queueEntry; + s_terminatorThreads[coreIndex].queueDeallocators.pop(queueEntry); + PPCCoreCallback(queueEntry.deallocatorFunc, queueEntry.thread, queueEntry.stack); + } + osLib_returnFromFunction(hCPU, 0); + } + + // queue thread deallocation to run after current thread finishes + // the termination threads run at a higher priority on the same threads + void __OSQueueThreadDeallocation(OSThread_t* thread) + { + uint32 coreIndex = OSGetCoreId(); + TerminatorThread::DeallocatorQueueEntry queueEntry(thread, memory_getPointerFromVirtualOffset(_swapEndianU32(thread->stackEnd)), thread->deallocatorFunc); + s_terminatorThreads[coreIndex].queueDeallocators.push(queueEntry); + OSSignalSemaphoreInternal(s_terminatorThreads[coreIndex].semaphoreQueuedDeallocators.GetPtr(), false); // do not reschedule here! Current thread must not be interrupted otherwise deallocator will run too early + } + + void __OSInitTerminatorThreads() + { + for (sint32 i = 0; i < PPC_CORE_COUNT; i++) + { + OSInitSemaphore(s_terminatorThreads[i].semaphoreQueuedDeallocators.GetPtr(), 0); + sprintf(s_terminatorThreads[i].threadName.GetPtr(), "{SYS Thread Terminator Core %d}", i); + OSCreateThreadType(s_terminatorThreads[i].terminatorThread.GetPtr(), PPCInterpreter_makeCallableExportDepr(__OSTerminatorThreadFunc), 0, nullptr, (uint8*)s_terminatorThreads[i].threadStack.GetPtr() + s_terminatorThreads[i].threadStack.GetByteSize(), (sint32)s_terminatorThreads[i].threadStack.GetByteSize(), 0, 1 << i, OSThread_t::THREAD_TYPE::TYPE_IO); + OSSetThreadName(s_terminatorThreads[i].terminatorThread.GetPtr(), s_terminatorThreads[i].threadName.GetPtr()); + OSResumeThread(s_terminatorThreads[i].terminatorThread.GetPtr()); + } + } + + SysAllocator<char, 64> _defaultThreadName[PPC_CORE_COUNT]; + + void __OSInitDefaultThreads() + { + for (sint32 i = 0; i < PPC_CORE_COUNT; i++) + { + sprintf(_defaultThreadName[i].GetPtr(), "Default Core %d", i); + OSThread_t* coreDefaultThread = coreinit::OSGetDefaultThread(i); + uint32 stackSize; + void* stackBase = coreinit::OSGetDefaultThreadStack(i, stackSize); + coreinit::OSCreateThreadType(coreDefaultThread, MPTR_NULL, 0, nullptr, (uint8*)stackBase + stackSize, stackSize, 16, 1 << i, OSThread_t::THREAD_TYPE::TYPE_APP); + coreinit::OSSetThreadName(coreDefaultThread, _defaultThreadName[i].GetPtr()); + } + } + + void InitializeThread() + { + cafeExportRegister("coreinit", OSCreateThreadType, LogType::CoreinitThread); + cafeExportRegister("coreinit", OSCreateThread, LogType::CoreinitThread); + cafeExportRegister("coreinit", OSExitThread, LogType::CoreinitThread); + + cafeExportRegister("coreinit", OSGetCurrentThread, LogType::CoreinitThread); + cafeExportRegister("coreinit", OSSetThreadSpecific, LogType::CoreinitThread); + cafeExportRegister("coreinit", OSGetThreadSpecific, LogType::CoreinitThread); + cafeExportRegister("coreinit", OSSetThreadName, LogType::CoreinitThread); + cafeExportRegister("coreinit", OSGetThreadName, LogType::CoreinitThread); + + cafeExportRegister("coreinit", OSRunThread, LogType::CoreinitThread); + cafeExportRegister("coreinit", OSDetachThread, LogType::CoreinitThread); + cafeExportRegister("coreinit", OSJoinThread, LogType::CoreinitThread); + + cafeExportRegister("coreinit", OSResumeThread, LogType::CoreinitThread); + cafeExportRegister("coreinit", OSContinueThread, LogType::CoreinitThread); + cafeExportRegister("coreinit", OSSuspendThread, LogType::CoreinitThread); + cafeExportRegister("coreinit", OSSleepThread, LogType::CoreinitThread); + cafeExportRegister("coreinit", OSWakeupThread, LogType::CoreinitThread); + + cafeExportRegister("coreinit", OSYieldThread, LogType::CoreinitThread); + cafeExportRegister("coreinit", OSSleepTicks, LogType::CoreinitThread); + + cafeExportRegister("coreinit", OSSetThreadDeallocator, LogType::CoreinitThread); + cafeExportRegister("coreinit", OSSetThreadCleanupCallback, LogType::CoreinitThread); + cafeExportRegister("coreinit", OSSetThreadPriority, LogType::CoreinitThread); + cafeExportRegister("coreinit", OSGetThreadPriority, LogType::CoreinitThread); + cafeExportRegister("coreinit", OSSetThreadAffinity, LogType::CoreinitThread); + cafeExportRegister("coreinit", OSGetThreadAffinity, LogType::CoreinitThread); + + cafeExportRegister("coreinit", OSIsThreadTerminated, LogType::CoreinitThread); + cafeExportRegister("coreinit", OSIsThreadSuspended, LogType::CoreinitThread); + cafeExportRegister("coreinit", OSCancelThread, LogType::CoreinitThread); + cafeExportRegister("coreinit", OSTestThreadCancel, LogType::CoreinitThread); + + cafeExportRegister("coreinit", OSGetDefaultThread, LogType::CoreinitThread); + + // OSThreadQueue + cafeExportRegister("coreinit", OSInitThreadQueue, LogType::CoreinitThread); + cafeExportRegister("coreinit", OSInitThreadQueueEx, LogType::CoreinitThread); + + OSInitThreadQueue(g_activeThreadQueue.GetPtr()); + for (sint32 i = 0; i < PPC_CORE_COUNT; i++) + OSInitThreadQueue(g_coreRunQueue.GetPtr() + i); + + for (sint32 i = 0; i < PPC_CORE_COUNT; i++) + __currentCoreThread[i] = nullptr; + + __OSInitDefaultThreads(); + __OSInitTerminatorThreads(); + } +} + +void coreinit_suspendThread(OSThread_t* OSThreadBE, sint32 count) +{ + // for legacy source + OSThreadBE->suspendCounter += count; +} + +void coreinit_resumeThread(OSThread_t* OSThreadBE, sint32 count) +{ + __OSLockScheduler(); + coreinit::__OSResumeThreadInternal(OSThreadBE, count); + __OSUnlockScheduler(); +} + +MPTR coreinitThread_getCurrentThreadMPTRDepr(PPCInterpreter_t* hCPU) +{ + return memory_getVirtualOffsetFromPointer(coreinit::__currentCoreThread[PPCInterpreter_getCoreIndex(hCPU)]); +} + +OSThread_t* coreinitThread_getCurrentThreadDepr(PPCInterpreter_t* hCPU) +{ + return coreinit::__currentCoreThread[PPCInterpreter_getCoreIndex(hCPU)]; +} diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Thread.h b/src/Cafe/OS/libs/coreinit/coreinit_Thread.h new file mode 100644 index 00000000..a0561f41 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_Thread.h @@ -0,0 +1,605 @@ +#pragma once +#include "Cafe/HW/Espresso/Const.h" +#include "Cafe/OS/libs/coreinit/coreinit_Scheduler.h" + +#define OS_CONTEXT_MAGIC_0 'OSCo' +#define OS_CONTEXT_MAGIC_1 'ntxt' + +struct OSThread_t; + +struct OSContextRegFPSCR_t +{ + // FPSCR is a 32bit register but it's stored as a 64bit double + /* +0x00 */ uint32be padding; + /* +0x04 */ uint32be fpscr; +}; + +struct OSContext_t +{ + /* +0x000 */ betype<uint32> magic0; + /* +0x004 */ betype<uint32> magic1; + /* +0x008 */ uint32 gpr[32]; + /* +0x088 */ uint32 cr; + /* +0x08C */ uint32 lr; + /* +0x090 */ uint32 ctr; + /* +0x094 */ uint32 xer; + /* +0x098 */ uint32 srr0; + /* +0x09C */ uint32 srr1; + /* +0x0A0 */ uint32 dsi_dsisr; + /* +0x0A4 */ uint32 dsi_dar; + /* +0x0A8 */ uint32 ukn0A8; + /* +0x0AC */ uint32 ukn0AC; + /* +0x0B0 */ OSContextRegFPSCR_t fpscr; + /* +0x0B8 */ uint64be fp_ps0[32]; + /* +0x1B8 */ uint16be boostCount; + /* +0x1BA */ uint16 state; // OS_CONTEXT_STATE_* + /* +0x1BC */ uint32 gqr[8]; // GQR/UGQR + /* +0x1DC */ uint32be upir; // set to current core index + /* +0x1E0 */ uint64be fp_ps1[32]; + /* +0x2E0 */ uint64 uknTime2E0; + /* +0x2E8 */ uint64 uknTime2E8; + /* +0x2F0 */ uint64 uknTime2F0; + /* +0x2F8 */ uint64 uknTime2F8; + /* +0x300 */ uint32 error; // returned by __gh_errno_ptr() (used by socketlasterr) + /* +0x304 */ uint32be affinity; + /* +0x308 */ uint32 ukn0308; + /* +0x30C */ uint32 ukn030C; + /* +0x310 */ uint32 ukn0310; + /* +0x314 */ uint32 ukn0314; + /* +0x318 */ uint32 ukn0318; + /* +0x31C */ uint32 ukn031C; + + bool checkMagic() + { + return magic0 == (uint32)OS_CONTEXT_MAGIC_0 && magic1 == (uint32)OS_CONTEXT_MAGIC_1; + } + + bool hasCoreAffinitySet(uint32 coreIndex) const + { + return (((uint32)affinity >> coreIndex) & 1) != 0; + } + + void setAffinity(uint32 mask) + { + affinity = mask & 7; + } + + uint32 getAffinity() const + { + return affinity; + } +}; + +static_assert(sizeof(OSContext_t) == 0x320); + +typedef struct +{ + /* +0x000 | +0x3A0 */ uint32 ukn000[0x68 / 4]; + /* +0x068 | +0x408 */ MEMPTR<void> eh_globals; + /* +0x06C | +0x40C */ uint32 eh_mem_manage[9]; // struct + /* +0x090 | +0x430 */ MPTR eh_store_globals[6]; + /* +0x0A8 | +0x448 */ MPTR eh_store_globals_tdeh[76]; +}crt_t; // size: 0x1D8 + +static_assert(sizeof(crt_t) == 0x1D8, ""); + +#pragma pack(1) + +namespace coreinit +{ + + /********* OSThreadQueue *********/ + + struct OSThreadLink + { + MEMPTR<struct OSThread_t> next; + MEMPTR<struct OSThread_t> prev; + }; + + static_assert(sizeof(OSThreadLink) == 8); + + struct OSThreadQueueInternal + { + MEMPTR<OSThread_t> head; + MEMPTR<OSThread_t> tail; + + bool isEmpty() const + { + cemu_assert_debug((!head.IsNull() == !tail.IsNull()) || (head.IsNull() == tail.IsNull())); + return head.IsNull(); + } + + void addThread(OSThread_t* thread, OSThreadLink* threadLink); + void addThreadByPriority(OSThread_t* thread, OSThreadLink* threadLink); + void removeThread(OSThread_t* thread, OSThreadLink* threadLink); + + // puts the thread on the waiting queue and changes state to WAITING + // relinquishes timeslice + // always uses thread->waitQueueLink + void queueAndWait(OSThread_t* thread); + void queueOnly(OSThread_t* thread); + + // counterparts for queueAndWait + void cancelWait(OSThread_t* thread); + void wakeupEntireWaitQueue(bool reschedule); + void wakeupSingleThreadWaitQueue(bool reschedule); + + private: + OSThread_t* takeFirstFromQueue(size_t linkOffset) + { + cemu_assert_debug(__OSHasSchedulerLock()); + if (head == nullptr) + return nullptr; + OSThread_t* thread = head.GetPtr(); + OSThreadLink* link = _getThreadLink(thread, linkOffset); + removeThread(thread, link); + return thread; + } + + static size_t getLinkOffset(OSThread_t* thread, OSThreadLink* threadLink) + { + cemu_assert_debug((void*)threadLink >= (void*)thread && (void*)threadLink < (void*)((uint8*)thread + 0x680)); + return (uint8*)threadLink - (uint8*)thread; + } + + static OSThreadLink* _getThreadLink(OSThread_t* thread, size_t linkOffset) + { + return (OSThreadLink*)((uint8*)thread + linkOffset); + } + + void _debugCheckChain(OSThread_t* thread, OSThreadLink* threadLink) + { +#ifndef PUBLIC_RELEASE + cemu_assert_debug(tail.IsNull() == head.IsNull()); + size_t linkOffset = getLinkOffset(thread, threadLink); + // expects thread to be in the chain + OSThread_t* threadItr = head.GetPtr(); + while (threadItr) + { + if (threadItr == thread) + return; + threadItr = _getThreadLink(threadItr, linkOffset)->next.GetPtr(); + } + cemu_assert_debug(false); // thread not in list! +#endif + } + }; + + static_assert(sizeof(OSThreadQueueInternal) == 0x8); + + struct OSThreadQueueSmall : public OSThreadQueueInternal + { + // no extra members + }; + + static_assert(sizeof(OSThreadQueueSmall) == 8); + static_assert(offsetof(OSThreadQueueSmall, head) == 0x0); + static_assert(offsetof(OSThreadQueueSmall, tail) == 0x4); + + struct OSThreadQueue : public OSThreadQueueInternal + { + MEMPTR<void> userData; + uint32be ukn0C; + }; + + static_assert(sizeof(OSThreadQueue) == 0x10); + static_assert(offsetof(OSThreadQueue, head) == 0x0); + static_assert(offsetof(OSThreadQueue, tail) == 0x4); + static_assert(offsetof(OSThreadQueue, userData) == 0x8); + static_assert(offsetof(OSThreadQueue, ukn0C) == 0xC); + + /********* OSMutex *********/ + + struct OSMutex + { + /* +0x00 */ uint32 magic; + /* +0x04 */ MEMPTR<void> userData; + /* +0x08 */ uint32be ukn08; + /* +0x0C */ OSThreadQueue threadQueue; + /* +0x1C */ MEMPTR<OSThread_t> owner; + /* +0x20 */ sint32be lockCount; + /* +0x24 */ MEMPTR<OSMutex> next; + /* +0x28 */ MEMPTR<OSMutex> prev; + + }; // size: 0x2C + + static_assert(sizeof(OSMutex) == 0x2C); + + struct OSMutexQueue + { + MEMPTR<OSMutex> head; + MEMPTR<OSMutex> tail; + MEMPTR<void> ukn08; + uint32be ukn0C; + + bool isEmpty() const + { + cemu_assert_debug((!head.IsNull() == !tail.IsNull()) || (head.IsNull() == tail.IsNull())); + return head.IsNull(); + } + + void addMutex(OSMutex* mutex) + { + cemu_assert_debug(__OSHasSchedulerLock()); + // insert at end + if (tail.IsNull()) + { + mutex->next = nullptr; + mutex->prev = nullptr; + head = mutex; + tail = mutex; + } + else + { + tail->next = mutex; + mutex->prev = tail; + mutex->next = nullptr; + tail = mutex; + } + } + + void removeMutex(OSMutex* mutex) + { + cemu_assert_debug(__OSHasSchedulerLock()); + cemu_assert_debug(!head.IsNull() && !tail.IsNull()); + if (mutex->prev) + mutex->prev->next = mutex->next; + else + head = mutex->next; + if (mutex->next) + mutex->next->prev = mutex->prev; + else + tail = mutex->prev; + mutex->next = nullptr; + mutex->prev = nullptr; + } + + OSMutex* getFirst() + { + return head.GetPtr(); + } + }; + + static_assert(sizeof(OSMutexQueue) == 0x10); + + /********* OSFastMutex *********/ + + struct OSFastMutexLink + { + /* +0x00 */ MEMPTR<struct OSMutex> next; + /* +0x04 */ MEMPTR<struct OSMutex> prev; + }; + + struct OSFastMutex + { + /* +0x00 */ uint32be magic; + /* +0x04 */ MEMPTR<void> userData; + /* +0x08 */ uint32be contendedState; // tracks current contention state + /* +0x0C */ OSThreadQueueSmall threadQueueSmall; + /* +0x14 */ OSFastMutexLink ownedLink; // part of thread->fastMutexOwnedQueue + /* +0x1C */ MEMPTR<OSThread_t> owner; + /* +0x20 */ uint32be lockCount; + /* +0x24 */ OSFastMutexLink contendedLink; + }; + + static_assert(sizeof(OSFastMutex) == 0x2C); + + /********* OSEvent *********/ + + struct OSEvent + { + enum class EVENT_MODE : uint32 + { + MODE_MANUAL = 0, + MODE_AUTO = 1, + }; + + enum class EVENT_STATE : uint32 + { + STATE_NOT_SIGNALED = 0, + STATE_SIGNALED = 1 + }; + + /* +0x00 */ uint32be magic; // 'eVnT' + /* +0x04 */ MEMPTR<void> userData; + /* +0x08 */ uint32be ukn08; + /* +0x0C */ betype<EVENT_STATE> state; // 0 -> not signaled, 1 -> signaled + /* +0x10 */ OSThreadQueue threadQueue; + /* +0x20 */ betype<EVENT_MODE> mode; + }; + + static_assert(sizeof(OSEvent) == 0x24); + + /********* OSRendezvous *********/ + + struct OSRendezvous + { + /* +0x00 */ uint32be coreHit[3]; + /* +0x0C */ MEMPTR<void> userData; + }; + + static_assert(sizeof(OSRendezvous) == 0x10); + + /********* OSCond *********/ + + struct OSCond + { + uint32be magic; + MEMPTR<void> userData; + uint32be ukn08; + OSThreadQueue threadQueue; + }; + + static_assert(sizeof(OSCond) == 0x1C); + + /********* OSSemaphore *********/ + + struct OSSemaphore + { + uint32be magic; + MEMPTR<void> userData; + uint32be ukn08; + sint32be count; + OSThreadQueue threadQueue; + }; + + static_assert(sizeof(OSSemaphore) == 0x20); + + /********* OSFastCond *********/ + + struct OSFastCond + { + uint32be magic; + MEMPTR<void> userData; + uint32be ukn08; + OSThreadQueue threadQueue; + }; + + static_assert(sizeof(OSFastCond) == 0x1C); + +}; + +struct OSThread_t +{ + enum class THREAD_TYPE : uint32 + { + TYPE_DRIVER = 0, + TYPE_IO = 1, + TYPE_APP = 2 + }; + + enum class THREAD_STATE : uint8 + { + STATE_NONE = 0, + STATE_READY = 1, + STATE_RUNNING = 2, + STATE_WAITING = 4, + STATE_MORIBUND = 8, + }; + + enum ATTR_BIT : uint32 + { + ATTR_AFFINITY_CORE0 = 0x1, + ATTR_AFFINITY_CORE1 = 0x2, + ATTR_AFFINITY_CORE2 = 0x4, + ATTR_DETACHED = 0x8, + // more flags? + }; + + enum REQUEST_FLAG_BIT : uint32 + { + REQUEST_FLAG_NONE = 0, + REQUEST_FLAG_SUSPEND = 1, + REQUEST_FLAG_CANCEL = 2, + }; + + static sint32 GetTypePriorityBase(THREAD_TYPE threadType) + { + if (threadType == OSThread_t::THREAD_TYPE::TYPE_DRIVER) + return 0; + else if (threadType == OSThread_t::THREAD_TYPE::TYPE_IO) + return 32; + else if (threadType == OSThread_t::THREAD_TYPE::TYPE_APP) + return 64; + return 0; + } + + /* +0x000 */ OSContext_t context; + /* +0x320 */ uint32be magic; // 'tHrD' + /* +0x324 */ betype<THREAD_STATE> state; + /* +0x325 */ uint8 attr; + /* +0x326 */ uint16be id; // Warriors Orochi 3 uses this to identify threads. Seems like this is always set to 0x8000 ? + /* +0x328 */ betype<sint32> suspendCounter; + /* +0x32C */ sint32be effectivePriority; // effective priority (lower is higher) + /* +0x330 */ sint32be basePriority; // base priority (lower is higher) + /* +0x334 */ uint32be exitValue; // set by OSExitThread() or by returning from the thread entrypoint + + /* +0x338 */ MEMPTR<coreinit::OSThreadQueue> currentRunQueue[Espresso::CORE_COUNT]; // points to the OSThreadQueue on which this thread is (null if not in queue) + /* +0x344 */ coreinit::OSThreadLink linkRun[Espresso::CORE_COUNT]; + + // general wait queue / thread dependency queue + /* +0x35C */ MEMPTR<coreinit::OSThreadQueueInternal> currentWaitQueue; // shared between OSThreadQueue and OSThreadQueueSmall + /* +0x360 */ coreinit::OSThreadLink waitQueueLink; + + /* +0x368 */ coreinit::OSThreadQueue joinQueue; + + /* +0x378 */ MEMPTR<coreinit::OSMutex> waitingForMutex; // set when thread is waiting for OSMutex to unlock + /* +0x37C */ coreinit::OSMutexQueue mutexQueue; + + /* +0x38C */ coreinit::OSThreadLink activeThreadChain; // queue of active threads (g_activeThreadQueue) + + /* +0x394 */ MPTR_UINT8 stackBase; // upper limit of stack + /* +0x398 */ MPTR_UINT32 stackEnd; // lower limit of stack + + /* +0x39C */ MPTR entrypoint; + /* +0x3A0 */ crt_t crt; + + /* +0x578 */ sint32 alarmRelatedUkn; + /* +0x57C */ std::array<MEMPTR<void>, 16> specificArray; + /* +0x5BC */ betype<THREAD_TYPE> type; + /* +0x5C0 */ MEMPTR<char> threadName; + /* +0x5C4 */ MPTR waitAlarm; // used only by OSWaitEventWithTimeout/OSSignalEvent ? + + /* +0x5C8 */ uint32 userStackPointer; + + /* +0x5CC */ MEMPTR<void> cleanupCallback2; + /* +0x5D0 */ //MPTR deallocator; + MEMPTR<void> deallocatorFunc; + + /* +0x5D4 */ uint32 stateFlags; // 0x5D4 | various flags? Controls if canceling/suspension is allowed (at cancel points) or not? If 1 -> Cancel/Suspension not allowed, if 0 -> Cancel/Suspension allowed + /* +0x5D8 */ betype<REQUEST_FLAG_BIT> requestFlags; + /* +0x5DC */ sint32 pendingSuspend; + /* +0x5E0 */ sint32 suspendResult; + /* +0x5E4 */ coreinit::OSThreadQueue suspendQueue; + /* +0x5F4 */ uint32 _padding5F4; + /* +0x5F8 */ uint64be quantumTicks; + /* +0x600 */ uint64 coretimeSumQuantumStart; + + /* +0x608 */ uint64be wakeUpCount; // number of times the thread entered running state + /* +0x610 */ uint64be totalCycles; // sum of cycles this thread was active since it was created + /* +0x618 */ uint64be wakeUpTime; // time when thread first became active (Should only be set once(?) but we currently set this on every timeslice since thats more useful for debugging) + /* +0x620 */ uint64 wakeTimeRelatedUkn1; + /* +0x628 */ uint64 wakeTimeRelatedUkn2; + + // set via OSSetExceptionCallback + /* +0x630 */ MPTR ukn630Callback[Espresso::CORE_COUNT]; + /* +0x63C */ MPTR ukn63CCallback[Espresso::CORE_COUNT]; + /* +0x648 */ MPTR ukn648Callback[Espresso::CORE_COUNT]; + /* +0x654 */ MPTR ukn654Callback[Espresso::CORE_COUNT]; + + /* +0x660 */ uint32 ukn660; + + // TLS + /* +0x664 */ uint16 numAllocatedTLSBlocks; + /* +0x666 */ sint16 tlsStatus; + /* +0x668 */ MPTR tlsBlocksMPTR; + + /* +0x66C */ MEMPTR<coreinit::OSFastMutex> waitingForFastMutex; + /* +0x670 */ coreinit::OSFastMutexLink contendedFastMutex; // link or queue? + /* +0x678 */ coreinit::OSFastMutexLink ownedFastMutex; // link or queue? + + /* +0x680 */ uint32 padding680[28 / 4]; +}; + +static_assert(sizeof(OSThread_t) == 0x6A0-4); // todo - determine correct size + +namespace coreinit +{ + void InitializeThread(); + void InitializeConcurrency(); + + OSThread_t* OSGetDefaultThread(sint32 coreIndex); + void* OSGetDefaultThreadStack(sint32 coreIndex, uint32& size); + + bool OSCreateThreadType(OSThread_t* thread, MPTR entryPoint, sint32 numParam, void* ptrParam, void* stackTop2, sint32 stackSize, sint32 priority, uint32 attr, OSThread_t::THREAD_TYPE threadType); + void OSCreateThreadInternal(OSThread_t* thread, uint32 entryPoint, MPTR stackLowerBaseAddr, uint32 stackSize, uint8 affinityMask, OSThread_t::THREAD_TYPE threadType); + bool OSRunThread(OSThread_t* thread, MPTR funcAddress, sint32 numParam, void* ptrParam); + void OSExitThread(sint32 exitValue); + void OSDetachThread(OSThread_t* thread); + + OSThread_t* OSGetCurrentThread(); + void OSSetCurrentThread(uint32 coreIndex, OSThread_t* thread); + + void __OSSetThreadBasePriority(OSThread_t* thread, sint32 newPriority); + void __OSUpdateThreadEffectivePriority(OSThread_t* thread); + + bool OSSetThreadPriority(OSThread_t* thread, sint32 newPriority); + uint32 OSGetThreadAffinity(OSThread_t* thread); + + void OSSetThreadName(OSThread_t* thread, char* name); + char* OSGetThreadName(OSThread_t* thread); + + sint32 __OSResumeThreadInternal(OSThread_t* thread, sint32 resumeCount); + sint32 OSResumeThread(OSThread_t* thread); + void OSContinueThread(OSThread_t* thread); + void __OSSuspendThreadInternal(OSThread_t* thread); + void OSSuspendThread(OSThread_t* thread); + void OSSleepThread(OSThreadQueue* threadQueue); + void OSWakeupThread(OSThreadQueue* threadQueue); + + void OSTestThreadCancelInternal(); + + void OSYieldThread(); + void OSSleepTicks(uint64 ticks); + + bool OSIsThreadTerminated(OSThread_t* thread); + bool OSIsThreadSuspended(OSThread_t* thread); + + // OSThreadQueue + void OSInitThreadQueue(OSThreadQueue* threadQueue); + void OSInitThreadQueueEx(OSThreadQueue* threadQueue, void* userData); + + // OSEvent + void OSInitEvent(OSEvent* event, OSEvent::EVENT_STATE initialState, OSEvent::EVENT_MODE mode); + void OSInitEventEx(OSEvent* event, OSEvent::EVENT_STATE initialState, OSEvent::EVENT_MODE mode, void* userData); + void OSResetEvent(OSEvent* event); + void OSWaitEventInternal(OSEvent* event); // assumes lock is already held + void OSWaitEvent(OSEvent* event); + bool OSWaitEventWithTimeout(OSEvent* event, uint64 timeout); + void OSSignalEventInternal(OSEvent* event); // assumes lock is already held + void OSSignalEvent(OSEvent* event); + void OSSignalEventAllInternal(OSEvent* event); // assumes lock is already held + void OSSignalEventAll(OSEvent* event); + + // OSRendezvous + void OSInitRendezvous(OSRendezvous* rendezvous); + bool OSWaitRendezvous(OSRendezvous* rendezvous, uint32 coreMask); + + // OSMutex + void OSInitMutex(OSMutex* mutex); + void OSInitMutexEx(OSMutex* mutex, void* userData); + void OSLockMutex(OSMutex* mutex); + bool OSTryLockMutex(OSMutex* mutex); + void OSUnlockMutex(OSMutex* mutex); + void OSUnlockMutexInternal(OSMutex* mutex); + + // OSCond + void OSInitCond(OSCond* cond); + void OSInitCondEx(OSCond* cond, void* userData); + void OSSignalCond(OSCond* cond); + void OSWaitCond(OSCond* cond, OSMutex* mutex); + + // OSSemaphore + void OSInitSemaphore(OSSemaphore* semaphore, sint32 initialCount); + void OSInitSemaphoreEx(OSSemaphore* semaphore, sint32 initialCount, void* userData); + sint32 OSWaitSemaphoreInternal(OSSemaphore* semaphore); + sint32 OSWaitSemaphore(OSSemaphore* semaphore); + sint32 OSTryWaitSemaphore(OSSemaphore* semaphore); + sint32 OSSignalSemaphore(OSSemaphore* semaphore); + sint32 OSSignalSemaphoreInternal(OSSemaphore* semaphore, bool reschedule); + sint32 OSGetSemaphoreCount(OSSemaphore* semaphore); + + // OSFastMutex + void OSFastMutex_Init(OSFastMutex* fastMutex, void* userData); + void OSFastMutex_Lock(OSFastMutex* fastMutex); + bool OSFastMutex_TryLock(OSFastMutex* fastMutex); + void OSFastMutex_Unlock(OSFastMutex* fastMutex); + + // OSFastCond + void OSFastCond_Init(OSFastCond* fastCond, void* userData); + void OSFastCond_Wait(OSFastCond* fastCond, OSFastMutex* fastMutex); + void OSFastCond_Signal(OSFastCond* fastCond); + + // scheduler + void OSSchedulerBegin(sint32 numCPUEmulationThreads); + void OSSchedulerEnd(); + + // internal + void __OSAddReadyThreadToRunQueue(OSThread_t* thread); + bool __OSCoreShouldSwitchToThread(OSThread_t* currentThread, OSThread_t* newThread); + void __OSQueueThreadDeallocation(OSThread_t* thread); +} + +#pragma pack() + +// deprecated / clean up required +void coreinit_suspendThread(OSThread_t* OSThreadBE, sint32 count = 1); +void coreinit_resumeThread(OSThread_t* OSThreadBE, sint32 count = 1); + +MPTR coreinitThread_getCurrentThreadMPTRDepr(PPCInterpreter_t* hCPU); +OSThread_t* coreinitThread_getCurrentThreadDepr(PPCInterpreter_t* hCPU); + +extern MPTR activeThread[256]; +extern sint32 activeThreadCount; + +extern SlimRWLock srwlock_activeThreadList; \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_ThreadQueue.cpp b/src/Cafe/OS/libs/coreinit/coreinit_ThreadQueue.cpp new file mode 100644 index 00000000..79318c55 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_ThreadQueue.cpp @@ -0,0 +1,169 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/libs/coreinit/coreinit_Thread.h" + +namespace coreinit +{ + + // puts the thread on the waiting queue and changes state to WAITING + // relinquishes timeslice + // always uses thread->waitQueueLink + void OSThreadQueueInternal::queueAndWait(OSThread_t* thread) + { + cemu_assert_debug(__OSHasSchedulerLock()); + cemu_assert_debug(thread->waitQueueLink.next == nullptr && thread->waitQueueLink.prev == nullptr); + thread->currentWaitQueue = this; + this->addThreadByPriority(thread, &thread->waitQueueLink); + cemu_assert_debug(thread->state == OSThread_t::THREAD_STATE::STATE_RUNNING); + thread->state = OSThread_t::THREAD_STATE::STATE_WAITING; + PPCCore_switchToSchedulerWithLock(); + cemu_assert_debug(thread->state == OSThread_t::THREAD_STATE::STATE_RUNNING); + } + + void OSThreadQueueInternal::queueOnly(OSThread_t* thread) + { + cemu_assert_debug(__OSHasSchedulerLock()); + cemu_assert_debug(thread->waitQueueLink.next == nullptr && thread->waitQueueLink.prev == nullptr); + thread->currentWaitQueue = this; + this->addThreadByPriority(thread, &thread->waitQueueLink); + cemu_assert_debug(thread->state == OSThread_t::THREAD_STATE::STATE_RUNNING); + thread->state = OSThread_t::THREAD_STATE::STATE_WAITING; + } + + // remove thread from wait queue and wake it up + void OSThreadQueueInternal::cancelWait(OSThread_t* thread) + { + cemu_assert_debug(__OSHasSchedulerLock()); + this->removeThread(thread, &thread->waitQueueLink); + thread->state = OSThread_t::THREAD_STATE::STATE_READY; + thread->currentWaitQueue = nullptr; + coreinit::__OSAddReadyThreadToRunQueue(thread); + // todo - if waking up a thread on the same core with higher priority, reschedule + } + + void OSThreadQueueInternal::addThread(OSThread_t* thread, OSThreadLink* threadLink) + { + cemu_assert_debug(__OSHasSchedulerLock()); + size_t linkOffset = getLinkOffset(thread, threadLink); + // insert after tail + if (tail.IsNull()) + { + threadLink->next = nullptr; + threadLink->prev = nullptr; + head = thread; + tail = thread; + } + else + { + threadLink->next = nullptr; + threadLink->prev = tail; + _getThreadLink(tail.GetPtr(), linkOffset)->next = thread; + tail = thread; + } + _debugCheckChain(thread, threadLink); + } + + void OSThreadQueueInternal::addThreadByPriority(OSThread_t* thread, OSThreadLink* threadLink) + { + cemu_assert_debug(tail.IsNull() == head.IsNull()); // either must be set or none at all + cemu_assert_debug(__OSHasSchedulerLock()); + size_t linkOffset = getLinkOffset(thread, threadLink); + if (tail.IsNull()) + { + threadLink->next = nullptr; + threadLink->prev = nullptr; + head = thread; + tail = thread; + } + else + { + // insert towards tail based on priority + OSThread_t* threadItr = tail.GetPtr(); + while (threadItr && threadItr->effectivePriority > thread->effectivePriority) + threadItr = _getThreadLink(threadItr, linkOffset)->prev.GetPtr(); + if (threadItr == nullptr) + { + // insert in front + threadLink->next = head; + threadLink->prev = nullptr; + _getThreadLink(head.GetPtr(), linkOffset)->prev = thread; + head = thread; + } + else + { + threadLink->prev = threadItr; + threadLink->next = _getThreadLink(threadItr, linkOffset)->next; + if (_getThreadLink(threadItr, linkOffset)->next) + { + OSThread_t* threadAfterItr = _getThreadLink(threadItr, linkOffset)->next.GetPtr(); + _getThreadLink(threadAfterItr, linkOffset)->prev = thread; + _getThreadLink(threadItr, linkOffset)->next = thread; + } + else + { + tail = thread; + _getThreadLink(threadItr, linkOffset)->next = thread; + } + } + } + _debugCheckChain(thread, threadLink); + } + + void OSThreadQueueInternal::removeThread(OSThread_t* thread, OSThreadLink* threadLink) + { + cemu_assert_debug(__OSHasSchedulerLock()); + size_t linkOffset = getLinkOffset(thread, threadLink); + _debugCheckChain(thread, threadLink); + if (threadLink->prev) + _getThreadLink(threadLink->prev.GetPtr(), linkOffset)->next = threadLink->next; + else + head = threadLink->next; + if (threadLink->next) + _getThreadLink(threadLink->next.GetPtr(), linkOffset)->prev = threadLink->prev; + else + tail = threadLink->prev; + + threadLink->next = nullptr; + threadLink->prev = nullptr; + } + + // counterpart for queueAndWait + // if reschedule is true then scheduler will switch to woken up thread (if it is runnable on the same core) + void OSThreadQueueInternal::wakeupEntireWaitQueue(bool reschedule) + { + cemu_assert_debug(__OSHasSchedulerLock()); + bool shouldReschedule = false; + while (OSThread_t* thread = takeFirstFromQueue(offsetof(OSThread_t, waitQueueLink))) + { + cemu_assert_debug(thread->state == OSThread_t::THREAD_STATE::STATE_WAITING); + cemu_assert_debug(thread->suspendCounter == 0); + thread->state = OSThread_t::THREAD_STATE::STATE_READY; + thread->currentWaitQueue = nullptr; + coreinit::__OSAddReadyThreadToRunQueue(thread); + if (reschedule && thread->suspendCounter == 0 && ppcInterpreterCurrentInstance && __OSCoreShouldSwitchToThread(coreinit::OSGetCurrentThread(), thread)) + shouldReschedule = true; + } + if (shouldReschedule) + PPCCore_switchToSchedulerWithLock(); + } + + // counterpart for queueAndWait + // if reschedule is true then scheduler will switch to woken up thread (if it is runnable on the same core) + void OSThreadQueueInternal::wakeupSingleThreadWaitQueue(bool reschedule) + { + cemu_assert_debug(__OSHasSchedulerLock()); + OSThread_t* thread = takeFirstFromQueue(offsetof(OSThread_t, waitQueueLink)); + cemu_assert_debug(thread); + bool shouldReschedule = false; + if (thread) + { + thread->state = OSThread_t::THREAD_STATE::STATE_READY; + thread->currentWaitQueue = nullptr; + coreinit::__OSAddReadyThreadToRunQueue(thread); + if (reschedule && thread->suspendCounter == 0 && ppcInterpreterCurrentInstance && __OSCoreShouldSwitchToThread(coreinit::OSGetCurrentThread(), thread)) + shouldReschedule = true; + } + if (shouldReschedule) + PPCCore_switchToSchedulerWithLock(); + } + +} diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Time.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Time.cpp new file mode 100644 index 00000000..823d4938 --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_Time.cpp @@ -0,0 +1,379 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/libs/coreinit/coreinit_Time.h" + +namespace coreinit +{ + + uint64 coreinit_getTimerTick() + { + // bus clock is 1/5th of core clock + // timer clock is 1/4th of bus clock + return PPCInterpreter_getMainCoreCycleCounter() / 20ULL; + } + + uint64 coreinit_getOSTime() + { + return coreinit_getTimerTick() + ppcCyclesSince2000TimerClock; + } + + void export_OSGetTick(PPCInterpreter_t* hCPU) + { + uint64 osTime = coreinit_getOSTime(); + osLib_returnFromFunction(hCPU, (uint32)osTime); + } + + void export_OSGetTime(PPCInterpreter_t* hCPU) + { + uint64 osTime = coreinit_getOSTime(); + osLib_returnFromFunction64(hCPU, osTime); + } + + __declspec(noinline) uint64 coreinit_getTimeBase_dummy() + { + return __rdtsc(); + } + + void export_OSGetSystemTimeDummy(PPCInterpreter_t* hCPU) + { + osLib_returnFromFunction64(hCPU, coreinit_getTimeBase_dummy()); + } + + void export_OSGetSystemTime(PPCInterpreter_t* hCPU) + { + osLib_returnFromFunction64(hCPU, coreinit_getTimerTick()); + } + + uint32 getLeapDaysUntilYear(uint32 year) + { + if (year == 0) + return 0; + return (year + 3) / 4 - (year - 1) / 100 + (year - 1) / 400; + } + + bool IsLeapYear(uint32 year) + { + if (((year & 3) == 0) && (year % 100) != 0) + return true; + if ((year % 400) == 0) + return true; + return false; + } + + uint32 dayToMonth[12] = + { + 0,31,59,90,120,151,181,212,243,273,304,334 + }; + + uint32 dayToMonthLeapYear[12] = + { + 0,31,60,91,121,152,182,213,244,274,305,335 + }; + + uint32 getDayInYearByYearAndMonth(uint32 year, uint32 month) + { + // Project Zero Maiden of Black Water (JPN) gives us an invalid calendar object + month %= 12; // or return 0 if too big? + + if (IsLeapYear(year)) + return dayToMonthLeapYear[month]; + + return dayToMonth[month]; + } + + + inline const uint64 DAY_BIAS_2000 = 0xB2575; + + uint64 OSCalendarTimeToTicks(OSCalendarTime_t *calendar) + { + uint32 year = calendar->year; + + uint32 leapDays = getLeapDaysUntilYear(year); + uint32 startDayOfCurrentMonth = getDayInYearByYearAndMonth(year, calendar->month); + + uint64 dayInYear = (startDayOfCurrentMonth + calendar->dayOfMonth) - 1; + + uint64 dayCount = dayInYear + year * 365 + leapDays - DAY_BIAS_2000; + + // convert date to seconds + uint64 tSeconds = 0; + tSeconds += (uint64)dayCount * 24 * 60 * 60; + tSeconds += (uint64)calendar->hour * 60 * 60; + tSeconds += (uint64)calendar->minute * 60; + tSeconds += (uint64)calendar->second; + + uint64 tSubSecondTicks = 0; + tSubSecondTicks += (uint64)calendar->millisecond * ESPRESSO_TIMER_CLOCK / 1000; + tSubSecondTicks += (uint64)calendar->microsecond * ESPRESSO_TIMER_CLOCK / 1000000; + + return tSeconds * ESPRESSO_TIMER_CLOCK + tSubSecondTicks; + } + + void OSTicksToCalendarTime(uint64 ticks, OSCalendarTime_t* calenderStruct) + { + uint64 tSubSecondTicks = ticks % ESPRESSO_TIMER_CLOCK; + uint64 tSeconds = ticks / ESPRESSO_TIMER_CLOCK; + + uint64 microsecond = tSubSecondTicks * 1000000ull / ESPRESSO_TIMER_CLOCK; + microsecond %= 1000ull; + calenderStruct->microsecond = (uint32)microsecond; + + uint64 millisecond = tSubSecondTicks * 1000ull / ESPRESSO_TIMER_CLOCK; + millisecond %= 1000ull; + calenderStruct->millisecond = (uint32)millisecond; + + uint64 dayOfWeek = (tSeconds/(24ull * 60 * 60) + 6ull) % 7ull; + uint64 secondOfDay = (tSeconds % (24ull * 60 * 60)); + + calenderStruct->dayOfWeek = (sint32)dayOfWeek; + + uint64 daysSince0AD = tSeconds / (24ull * 60 * 60) + DAY_BIAS_2000; + uint32 year = (uint32)(daysSince0AD / 365ull); + uint64 yearStartDay = year * 365 + getLeapDaysUntilYear(year); + while (yearStartDay > daysSince0AD) + { + year--; + if (IsLeapYear(year)) + yearStartDay -= 366; + else + yearStartDay -= 365; + } + + calenderStruct->year = year; + + // calculate time of day + uint32 tempSecond = (uint32)secondOfDay; + calenderStruct->second = tempSecond % 60; + tempSecond /= 60; + calenderStruct->minute = tempSecond % 60; + tempSecond /= 60; + calenderStruct->hour = tempSecond % 24; + tempSecond /= 24; + + // calculate month and day + uint32 dayInYear = (uint32)(daysSince0AD - yearStartDay); + bool isLeapYear = IsLeapYear(year); + + uint32 month = 0; // 0-11 + uint32 dayInMonth = 0; + + if (isLeapYear && dayInYear < (31+29)) + { + if (dayInYear < 31) + { + // January + month = 0; + dayInMonth = dayInYear; + } + else + { + // February + month = 1; + dayInMonth = dayInYear-31; + } + } + else + { + if (isLeapYear) + dayInYear--; + + dayInMonth = dayInYear; + if (dayInYear >= 334) + { + // December + month = 11; + dayInMonth -= 334; + } + else if (dayInYear >= 304) + { + // November + month = 10; + dayInMonth -= 304; + } + else if (dayInYear >= 273) + { + // October + month = 9; + dayInMonth -= 273; + } + else if (dayInYear >= 243) + { + // September + month = 8; + dayInMonth -= 243; + } + else if (dayInYear >= 212) + { + // August + month = 7; + dayInMonth -= 212; + } + else if (dayInYear >= 181) + { + // July + month = 6; + dayInMonth -= 181; + } + else if (dayInYear >= 151) + { + // June + month = 5; + dayInMonth -= 151; + } + else if (dayInYear >= 120) + { + // May + month = 4; + dayInMonth -= 120; + } + else if (dayInYear >= 90) + { + // April + month = 3; + dayInMonth -= 90; + } + else if (dayInYear >= 59) + { + // March + month = 2; + dayInMonth -= 59; + } + else if (dayInYear >= 31) + { + // February + month = 1; + dayInMonth -= 31; + } + else + { + // January + month = 0; + dayInMonth -= 0; + } + } + + calenderStruct->dayOfYear = dayInYear; + calenderStruct->month = month; + calenderStruct->dayOfMonth = dayInMonth + 1; + } + + uint32 getDaysInMonth(uint32 year, uint32 month) + { + switch (month) + { + case 0: // January + return 31; + case 1: // February + return IsLeapYear(year) ? 29 : 28; + case 2: // March + return 31; + case 3: // April + return 30; + case 4: // May + return 31; + case 5: // June + return 30; + case 6: // July + return 31; + case 7: // August + return 31; + case 8: // September + return 30; + case 9: // October + return 31; + case 10: // November + return 30; + case 11: // December + return 31; + default: + cemu_assert(false); + } + return 31; + } + + void verifyDateMatch(OSCalendarTime_t* ct1, OSCalendarTime_t* ct2) + { + sint64 m1 = ct1->millisecond * 1000 + ct1->microsecond; + sint64 m2 = ct2->millisecond * 1000 + ct2->microsecond; + sint64 microDif = std::abs(m1 - m2); + + if (ct1->year != ct2->year || + ct1->month != ct2->month || + ct1->dayOfMonth != ct2->dayOfMonth || + ct1->hour != ct2->hour || + ct1->minute != ct2->minute || + ct1->second != ct2->second || + microDif > 1ull) + { + debug_printf("Mismatch\n"); + assert_dbg(); + } + } + + void timeTest() + { + sint32 iterCount = 0; + + OSCalendarTime_t ct{}; + ct.year = 2000; + ct.month = 1; + ct.dayOfMonth = 1; + ct.hour = 1; + ct.minute = 1; + ct.second = 1; + ct.millisecond = 123; + ct.microsecond = 321; + + while (true) + { + iterCount++; + + + uint64 ticks = OSCalendarTimeToTicks(&ct); + + // make sure converting it back results in the same date + OSCalendarTime_t ctTemp; + OSTicksToCalendarTime(ticks, &ctTemp); + verifyDateMatch(&ct, &ctTemp); + + // add a day + ticks += 24ull * 60 * 60 * ESPRESSO_TIMER_CLOCK; + + OSCalendarTime_t ctOutput; + OSTicksToCalendarTime(ticks, &ctOutput); + + OSCalendarTime_t ctExpected; + ctExpected = ct; + // add a day manually + sint32 daysInMonth = getDaysInMonth(ctExpected.year, ctExpected.month); + ctExpected.dayOfMonth = ctExpected.dayOfMonth + 1; + if (ctExpected.dayOfMonth >= daysInMonth+1) + { + ctExpected.dayOfMonth = 1; + ctExpected.month = ctExpected.month + 1; + if (ctExpected.month > 11) + { + ctExpected.month = 0; + ctExpected.year = ctExpected.year + 1; + } + } + + verifyDateMatch(&ctExpected, &ctOutput); + + ct = ctOutput; + } + } + + void InitializeTimeAndCalendar() + { + osLib_addFunction("coreinit", "OSGetTime", export_OSGetTime); + osLib_addFunction("coreinit", "OSGetSystemTime", export_OSGetSystemTimeDummy); // register dummy HLE function to get Cemuhook to patch our dummy instead of the real function + osLib_addFunction("coreinit", "OSGetTick", export_OSGetTick); + + cafeExportRegister("coreinit", OSTicksToCalendarTime, LogType::Placeholder); + cafeExportRegister("coreinit", OSCalendarTimeToTicks, LogType::Placeholder); + + osLib_addFunction("coreinit", "OSGetSystemTime", export_OSGetSystemTime); + + //timeTest(); + } +}; \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Time.h b/src/Cafe/OS/libs/coreinit/coreinit_Time.h new file mode 100644 index 00000000..f5dcf22e --- /dev/null +++ b/src/Cafe/OS/libs/coreinit/coreinit_Time.h @@ -0,0 +1,58 @@ +#pragma once +#include "Cafe/HW/Espresso/Const.h" + +namespace coreinit +{ + typedef struct + { + /* +0x00 */ sint32be second; // 0-59 + /* +0x04 */ sint32be minute; // 0-59 + /* +0x08 */ sint32be hour; // 0-23 + /* +0x0C */ sint32be dayOfMonth; // 1-31 + /* +0x10 */ sint32be month; // 0-11 + /* +0x14 */ sint32be year; // 2000-... + /* +0x18 */ sint32be dayOfWeek; // 0-6 + /* +0x1C */ sint32be dayOfYear; // 0-365 + /* +0x20 */ sint32be millisecond; // 0-999 + /* +0x24 */ sint32be microsecond; // 0-999 + }OSCalendarTime_t; + + static_assert(sizeof(OSCalendarTime_t) == 0x28); + + namespace EspressoTime + { + typedef sint64 TimerTicks; + + constexpr sint64 GetCoreClock() + { + return Espresso::CORE_CLOCK; + } + + constexpr sint64 GetBusClock() + { + return Espresso::BUS_CLOCK; + } + + constexpr sint64 GetTimerClock() + { + return Espresso::TIMER_CLOCK; + } + + inline TimerTicks ConvertNsToTimerTicks(uint64 ns) + { + return ((GetTimerClock() / 31250LL) * ((ns)) / 32000LL); + } + }; + + void OSTicksToCalendarTime(uint64 ticks, OSCalendarTime_t* calenderStruct); + + uint64 coreinit_getOSTime(); + uint64 coreinit_getTimerTick(); + + static uint64 OSGetSystemTime() + { + return coreinit_getTimerTick(); + } + + void InitializeTimeAndCalendar(); +}; diff --git a/src/Cafe/OS/libs/dmae/dmae.cpp b/src/Cafe/OS/libs/dmae/dmae.cpp new file mode 100644 index 00000000..0d193d33 --- /dev/null +++ b/src/Cafe/OS/libs/dmae/dmae.cpp @@ -0,0 +1,119 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/libs/coreinit/coreinit_Time.h" +#include "Cafe/HW/Latte/Core/LatteBufferCache.h" + +#define DMAE_ENDIAN_NONE 0 +#define DMAE_ENDIAN_16 1 +#define DMAE_ENDIAN_32 2 +#define DMAE_ENDIAN_64 3 + +uint64 dmaeRetiredTimestamp = 0; + +uint64 dmae_getTimestamp() +{ + return coreinit::coreinit_getTimerTick(); +} + +void dmae_setRetiredTimestamp(uint64 timestamp) +{ + dmaeRetiredTimestamp = timestamp; +} + +void dmaeExport_DMAECopyMem(PPCInterpreter_t* hCPU) +{ + if( hCPU->gpr[6] == DMAE_ENDIAN_NONE ) + { + // don't change endianness + memcpy(memory_getPointerFromVirtualOffset(hCPU->gpr[3]), memory_getPointerFromVirtualOffset(hCPU->gpr[4]), hCPU->gpr[5]*4); + } + else if( hCPU->gpr[6] == DMAE_ENDIAN_32 ) + { + // swap per uint32 + uint32* srcBuffer = (uint32*)memory_getPointerFromVirtualOffset(hCPU->gpr[4]); + uint32* dstBuffer = (uint32*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); + for(uint32 i=0; i<hCPU->gpr[5]; i++) + { + dstBuffer[i] = _swapEndianU32(srcBuffer[i]); + } + } + else + { + cemuLog_logDebug(LogType::Force, "DMAECopyMem(): Unsupported endian swap\n"); + } + uint64 dmaeTimestamp = dmae_getTimestamp(); + dmae_setRetiredTimestamp(dmaeTimestamp); + if(hCPU->gpr[5] > 0) + LatteBufferCache_notifyDCFlush(hCPU->gpr[3], hCPU->gpr[5]*4); + osLib_returnFromFunction64(hCPU, dmaeTimestamp); +} + +void dmaeExport_DMAEFillMem(PPCInterpreter_t* hCPU) +{ + uint32* dstBuffer = (uint32*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); + uint32 value = hCPU->gpr[4]; + uint32 numU32s = hCPU->gpr[5]; + value = _swapEndianU32(value); + for(uint32 i=0; i<numU32s; i++) + { + *dstBuffer = value; + dstBuffer++; + } + uint64 dmaeTimestamp = dmae_getTimestamp(); + dmae_setRetiredTimestamp(dmaeTimestamp); + osLib_returnFromFunction64(hCPU, dmaeTimestamp); +} + +void dmaeExport_DMAEWaitDone(PPCInterpreter_t* hCPU) +{ + //debug_printf("DMAEWaitDone(...)\n"); + // parameter: + // r3/r4 uint64 dmaeTimestamp + osLib_returnFromFunction(hCPU, 1); +} + +void dmaeExport_DMAESemaphore(PPCInterpreter_t* hCPU) +{ + // parameter: + // r3 MPTR addr + // r4 uint32 actionType + + uint32 actionType = hCPU->gpr[4]; + + std::atomic<uint64le>* semaphore = _rawPtrToAtomic((uint64le*)memory_getPointerFromVirtualOffset(hCPU->gpr[3])); + + if( actionType == 1 ) + { + // Signal Semaphore + semaphore->fetch_add(1); + } + else if (actionType == 0) // wait + { + forceLogDebug_printf("DMAESemaphore: Unsupported wait operation"); + semaphore->fetch_sub(1); + } + else + { + forceLogDebug_printf("DMAESemaphore unknown action type %d", actionType); + cemu_assert_debug(false); + } + + uint64 dmaeTimestamp = dmae_getTimestamp(); + dmae_setRetiredTimestamp(dmaeTimestamp); + osLib_returnFromFunction64(hCPU, dmaeTimestamp); +} + +void dmaeExport_DMAEGetRetiredTimeStamp(PPCInterpreter_t* hCPU) +{ + debug_printf("DMAEGetRetiredTimeStamp()\n"); + osLib_returnFromFunction64(hCPU, dmaeRetiredTimestamp); +} + + +void dmae_load() +{ + osLib_addFunction("dmae", "DMAECopyMem", dmaeExport_DMAECopyMem); + osLib_addFunction("dmae", "DMAEFillMem", dmaeExport_DMAEFillMem); + osLib_addFunction("dmae", "DMAEWaitDone", dmaeExport_DMAEWaitDone); + osLib_addFunction("dmae", "DMAESemaphore", dmaeExport_DMAESemaphore); + osLib_addFunction("dmae", "DMAEGetRetiredTimeStamp", dmaeExport_DMAEGetRetiredTimeStamp); +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/dmae/dmae.h b/src/Cafe/OS/libs/dmae/dmae.h new file mode 100644 index 00000000..26d371c9 --- /dev/null +++ b/src/Cafe/OS/libs/dmae/dmae.h @@ -0,0 +1 @@ +void dmae_load(); \ No newline at end of file diff --git a/src/Cafe/OS/libs/drmapp/drmapp.cpp b/src/Cafe/OS/libs/drmapp/drmapp.cpp new file mode 100644 index 00000000..8fa5db8a --- /dev/null +++ b/src/Cafe/OS/libs/drmapp/drmapp.cpp @@ -0,0 +1,16 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "drmapp.h" + +namespace drmapp +{ + uint32 NupChkIsFinished(uint32 ukn) + { + forceLogDebug_printf("drmapp.NupChkIsFinished() - placeholder"); + return 1; + } + + void Initialize() + { + cafeExportRegisterFunc(NupChkIsFinished, "drmapp", "NupChkIsFinished__3RplFv", LogType::Placeholder); + } +} diff --git a/src/Cafe/OS/libs/drmapp/drmapp.h b/src/Cafe/OS/libs/drmapp/drmapp.h new file mode 100644 index 00000000..1bbbec83 --- /dev/null +++ b/src/Cafe/OS/libs/drmapp/drmapp.h @@ -0,0 +1,5 @@ + +namespace drmapp +{ + void Initialize(); +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/erreula/erreula.cpp b/src/Cafe/OS/libs/erreula/erreula.cpp new file mode 100644 index 00000000..1d1e67fb --- /dev/null +++ b/src/Cafe/OS/libs/erreula/erreula.cpp @@ -0,0 +1,396 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "erreula.h" +#include "Cafe/HW/Latte/Renderer/Renderer.h" +#include "util/helpers/helpers.h" + +#include <imgui.h> +#include "imgui/imgui_extension.h" + +#include <wx/msgdlg.h> + +#include "Cafe/OS/libs/coreinit/coreinit_FS.h" +#include "Cafe/OS/libs/vpad/vpad.h" + +namespace nn +{ +namespace erreula +{ +#define RESULTTYPE_NONE 0 +#define RESULTTYPE_FINISH 1 +#define RESULTTYPE_NEXT 2 +#define RESULTTYPE_JUMP 3 +#define RESULTTYPE_PASSWORD 4 + +#define ERRORTYPE_CODE 0 +#define ERRORTYPE_TEXT 1 +#define ERRORTYPE_TEXT_ONE_BUTTON 2 +#define ERRORTYPE_TEXT_TWO_BUTTON 3 + +#define ERREULA_STATE_HIDDEN 0 +#define ERREULA_STATE_APPEARING 1 +#define ERREULA_STATE_VISIBLE 2 +#define ERREULA_STATE_DISAPPEARING 3 + + struct AppearArg_t + { + AppearArg_t() = default; + AppearArg_t(const AppearArg_t& o) + { + errorType = o.errorType; + screenType = o.screenType; + controllerType = o.controllerType; + holdType = o.holdType; + errorCode = o.errorCode; + framerate = o.framerate; + text = o.text; + button1Text = o.button1Text; + button2Text = o.button2Text; + title = o.title; + drawCursor = o.drawCursor; + } + + uint32be errorType; + uint32be screenType; + uint32be controllerType; + uint32be holdType; + uint32be errorCode; + uint32be framerate; + MEMPTR<uint16be> text; + MEMPTR<uint16be> button1Text; + MEMPTR<uint16be> button2Text; + MEMPTR<uint16be> title; + uint8 padding[3]; + bool drawCursor{}; + }; + + static_assert(sizeof(AppearArg_t) == 0x2C); // maybe larger + + struct HomeNixSignArg_t + { + uint32be framerate; + }; + + static_assert(sizeof(HomeNixSignArg_t) == 0x4); // maybe larger + + struct ControllerInfo_t + { + MEMPTR<VPADStatus_t> vpadStatus; + MEMPTR<KPADStatus_t> kpadStatus[4]; // or 7 now like KPAD_MAX_CONTROLLERS? + }; + + static_assert(sizeof(ControllerInfo_t) == 0x14); // maybe larger + + struct ErrEula_t + { + coreinit::OSMutex mutex; + uint32 regionType; + uint32 langType; + MEMPTR<coreinit::FSClient_t> fsClient; + + AppearArg_t currentDialog; + uint32 state; + bool buttonPressed; + bool rightButtonPressed; + + bool homeNixSignVisible; + + std::chrono::steady_clock::time_point stateTimer{}; + } g_errEula = {}; + + + + std::wstring GetText(uint16be* text) + { + std::wstringstream result; + while(*text != 0) + { + auto c = (uint16)*text; + result << static_cast<wchar_t>(c); + text++; + } + + return result.str(); + } + + + void export_ErrEulaCreate(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(thisptr, uint8, 0); + ppcDefineParamU32(regionType, 1); + ppcDefineParamU32(langType, 2); + ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 3); + + coreinit::OSLockMutex(&g_errEula.mutex); + + g_errEula.regionType = regionType; + g_errEula.langType = langType; + g_errEula.fsClient = fsClient; + + coreinit::OSUnlockMutex(&g_errEula.mutex); + + osLib_returnFromFunction(hCPU, 0); + } + + void export_AppearHomeNixSign(PPCInterpreter_t* hCPU) + { + g_errEula.homeNixSignVisible = TRUE; + osLib_returnFromFunction(hCPU, 0); + } + + void export_AppearError(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(arg, AppearArg_t, 0); + + g_errEula.currentDialog = *arg.GetPtr(); + g_errEula.state = ERREULA_STATE_APPEARING; + g_errEula.buttonPressed = false; + g_errEula.rightButtonPressed = false; + + g_errEula.stateTimer = tick_cached(); + + osLib_returnFromFunction(hCPU, 0); + } + + void export_GetStateErrorViewer(PPCInterpreter_t* hCPU) + { + osLib_returnFromFunction(hCPU, g_errEula.state); + } + void export_DisappearError(PPCInterpreter_t* hCPU) + { + g_errEula.state = ERREULA_STATE_HIDDEN; + osLib_returnFromFunction(hCPU, 0); + } + + void export_ChangeLang(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(langType, 0); + g_errEula.langType = langType; + osLib_returnFromFunction(hCPU, 0); + } + + void export_IsDecideSelectButtonError(PPCInterpreter_t* hCPU) + { + if (g_errEula.buttonPressed) + forceLogDebug_printf("IsDecideSelectButtonError: TRUE"); + osLib_returnFromFunction(hCPU, g_errEula.buttonPressed); + } + + void export_IsDecideSelectLeftButtonError(PPCInterpreter_t* hCPU) + { + if (g_errEula.buttonPressed) + forceLogDebug_printf("IsDecideSelectLeftButtonError: TRUE"); + osLib_returnFromFunction(hCPU, g_errEula.buttonPressed); + } + + void export_IsDecideSelectRightButtonError(PPCInterpreter_t* hCPU) + { + if (g_errEula.rightButtonPressed) + forceLogDebug_printf("IsDecideSelectRightButtonError: TRUE"); + osLib_returnFromFunction(hCPU, g_errEula.rightButtonPressed); + } + + void export_IsAppearHomeNixSign(PPCInterpreter_t* hCPU) + { + osLib_returnFromFunction(hCPU, g_errEula.homeNixSignVisible); + } + + void export_DisappearHomeNixSign(PPCInterpreter_t* hCPU) + { + g_errEula.homeNixSignVisible = FALSE; + osLib_returnFromFunction(hCPU, 0); + } + + void export_GetResultType(PPCInterpreter_t* hCPU) + { + uint32 result = RESULTTYPE_NONE; + if (g_errEula.buttonPressed || g_errEula.rightButtonPressed) + { + forceLogDebug_printf("GetResultType: FINISH"); + result = RESULTTYPE_FINISH; + } + + osLib_returnFromFunction(hCPU, result); + } + + void export_Calc(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(controllerInfo, ControllerInfo_t, 0); + // TODO: check controller buttons bla to accept dialog? + osLib_returnFromFunction(hCPU, 0); + } + + void render(bool mainWindow) + { + if(g_errEula.state == ERREULA_STATE_HIDDEN) + return; + + if(g_errEula.state == ERREULA_STATE_APPEARING) + { + if(std::chrono::duration_cast<std::chrono::milliseconds>(tick_cached() - g_errEula.stateTimer).count() <= 1000) + { + return; + } + + g_errEula.state = ERREULA_STATE_VISIBLE; + g_errEula.stateTimer = tick_cached(); + } + /*else if(g_errEula.state == STATE_VISIBLE) + { + if (std::chrono::duration_cast<std::chrono::milliseconds>(tick_cached() - g_errEula.stateTimer).count() >= 1000) + { + g_errEula.state = STATE_DISAPPEARING; + g_errEula.stateTimer = tick_cached(); + return; + } + }*/ + else if(g_errEula.state == ERREULA_STATE_DISAPPEARING) + { + if (std::chrono::duration_cast<std::chrono::milliseconds>(tick_cached() - g_errEula.stateTimer).count() >= 2000) + { + g_errEula.state = ERREULA_STATE_HIDDEN; + g_errEula.stateTimer = tick_cached(); + } + + return; + } + + const AppearArg_t& appearArg = g_errEula.currentDialog; + std::string text; + const uint32 errorCode = (uint32)appearArg.errorCode; + if (errorCode != 0) + { + const uint32 errorCodeHigh = errorCode / 10000; + const uint32 errorCodeLow = errorCode % 10000; + text = fmt::format("Error-Code: {:03}-{:04}\n", errorCodeHigh, errorCodeLow); + } + + auto font = ImGui_GetFont(32.0f); + if (!font) + return; + + const auto kPopupFlags = ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings; + + auto& io = ImGui::GetIO(); + ImVec2 position = { io.DisplaySize.x / 2.0f, io.DisplaySize.y / 2.0f }; + ImVec2 pivot = { 0.5f, 0.5f }; + ImGui::SetNextWindowPos(position, ImGuiCond_Always, pivot); + ImGui::SetNextWindowBgAlpha(0.9f); + ImGui::PushFont(font); + + std::string title = "ErrEula"; + if (appearArg.title) + title = boost::nowide::narrow(GetText(appearArg.title.GetPtr())); + + if (ImGui::Begin(title.c_str(), nullptr, kPopupFlags)) + { + const float startx = ImGui::GetWindowSize().x / 2.0f; + + switch ((uint32)appearArg.errorType) + { + default: + { + // TODO layout based on error code + ImGui::TextUnformatted(text.c_str(), text.c_str() + text.size()); + ImGui::Spacing(); + ImGui::SetCursorPosX(startx - 50); + g_errEula.buttonPressed |= ImGui::Button("OK", {100, 0}); + + break; + } + case ERRORTYPE_TEXT: + { + std::string txtTmp = "Unknown Error"; + if (appearArg.text) + txtTmp = boost::nowide::narrow(GetText(appearArg.text.GetPtr())); + + text += txtTmp; + ImGui::TextUnformatted(text.c_str(), text.c_str() + text.size()); + ImGui::Spacing(); + + ImGui::SetCursorPosX(startx - 50); + g_errEula.buttonPressed |= ImGui::Button("OK", { 100, 0 }); + break; + } + case ERRORTYPE_TEXT_ONE_BUTTON: + { + std::string txtTmp = "Unknown Error"; + if (appearArg.text) + txtTmp = boost::nowide::narrow(GetText(appearArg.text.GetPtr())); + + text += txtTmp; + ImGui::TextUnformatted(text.c_str(), text.c_str() + text.size()); + ImGui::Spacing(); + + std::string button1 = "Yes"; + if (appearArg.button1Text) + button1 = boost::nowide::narrow(GetText(appearArg.button1Text.GetPtr())); + + float width = std::max(100.0f, ImGui::CalcTextSize(button1.c_str()).x + 10.0f); + ImGui::SetCursorPosX(startx - (width / 2.0f)); + g_errEula.buttonPressed |= ImGui::Button(button1.c_str(), { width, 0 }); + break; + } + case ERRORTYPE_TEXT_TWO_BUTTON: + { + std::string txtTmp = "Unknown Error"; + if (appearArg.text) + txtTmp = boost::nowide::narrow(GetText(appearArg.text.GetPtr())); + + text += txtTmp; + ImGui::TextUnformatted(text.c_str(), text.c_str() + text.size()); + ImGui::Spacing(); + + std::string button1 = "Yes"; + if (appearArg.button1Text) + button1 = boost::nowide::narrow(GetText(appearArg.button1Text.GetPtr())); + std::string button2 = "No"; + if (appearArg.button2Text) + button2 = boost::nowide::narrow(GetText(appearArg.button2Text.GetPtr())); + + + float width1 = std::max(100.0f, ImGui::CalcTextSize(button1.c_str()).x + 10.0f); + float width2 = std::max(100.0f, ImGui::CalcTextSize(button2.c_str()).x + 10.0f); + ImGui::SetCursorPosX(startx - (width1 / 2.0f) - (width2 / 2.0f) - 10); + + g_errEula.buttonPressed |= ImGui::Button(button1.c_str(), { width1, 0 }); + ImGui::SameLine(); + + g_errEula.rightButtonPressed |= ImGui::Button(button2.c_str(), { width2, 0 }); + break; + } + } + + ImGui::End(); + } + + ImGui::PopFont(); + + if(g_errEula.buttonPressed || g_errEula.rightButtonPressed) + { + g_errEula.state = ERREULA_STATE_DISAPPEARING; + g_errEula.stateTimer = tick_cached(); + } + } + + void load() + { + OSInitMutexEx(&g_errEula.mutex, nullptr); + + //osLib_addFunction("erreula", "ErrEulaCreate__3RplFPUcQ3_2nn7erreula10", export_ErrEulaCreate); // copy ctor? + osLib_addFunction("erreula", "ErrEulaCreate__3RplFPUcQ3_2nn7erreula10RegionTypeQ3_2nn7erreula8LangTypeP8FSClient", export_ErrEulaCreate); + osLib_addFunction("erreula", "ErrEulaAppearHomeNixSign__3RplFRCQ3_2nn7erreula14HomeNixSignArg", export_AppearHomeNixSign); + osLib_addFunction("erreula", "ErrEulaAppearError__3RplFRCQ3_2nn7erreula9AppearArg", export_AppearError); + osLib_addFunction("erreula", "ErrEulaGetStateErrorViewer__3RplFv", export_GetStateErrorViewer); + osLib_addFunction("erreula", "ErrEulaChangeLang__3RplFQ3_2nn7erreula8LangType", export_ChangeLang); + osLib_addFunction("erreula", "ErrEulaIsDecideSelectButtonError__3RplFv", export_IsDecideSelectButtonError); + osLib_addFunction("erreula", "ErrEulaCalc__3RplFRCQ3_2nn7erreula14ControllerInfo", export_Calc); + osLib_addFunction("erreula", "ErrEulaIsDecideSelectLeftButtonError__3RplFv", export_IsDecideSelectLeftButtonError); + osLib_addFunction("erreula", "ErrEulaIsDecideSelectRightButtonError__3RplFv", export_IsDecideSelectRightButtonError); + osLib_addFunction("erreula", "ErrEulaIsAppearHomeNixSign__3RplFv", export_IsAppearHomeNixSign); + osLib_addFunction("erreula", "ErrEulaDisappearHomeNixSign__3RplFv", export_DisappearHomeNixSign); + osLib_addFunction("erreula", "ErrEulaGetResultType__3RplFv", export_GetResultType); + osLib_addFunction("erreula", "ErrEulaDisappearError__3RplFv", export_DisappearError); + } +} +} + diff --git a/src/Cafe/OS/libs/erreula/erreula.h b/src/Cafe/OS/libs/erreula/erreula.h new file mode 100644 index 00000000..87b96bb1 --- /dev/null +++ b/src/Cafe/OS/libs/erreula/erreula.h @@ -0,0 +1,12 @@ +#pragma once + +namespace nn +{ + namespace erreula + { + void render(bool mainWindow); + + void load(); + + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/gx2/GX2.cpp b/src/Cafe/OS/libs/gx2/GX2.cpp new file mode 100644 index 00000000..0945d5dc --- /dev/null +++ b/src/Cafe/OS/libs/gx2/GX2.cpp @@ -0,0 +1,472 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/HW/Latte/ISA/RegDefines.h" +#include "Cafe/HW/Espresso/PPCCallback.h" +#include "GX2.h" +#include "Cafe/HW/Latte/Core/Latte.h" +#include "Cafe/OS/libs/coreinit/coreinit_Time.h" +#include "Cafe/CafeSystem.h" + +#include "Cafe/HW/Latte/Core/LattePM4.h" + +#include "GX2_Command.h" +#include "GX2_State.h" +#include "GX2_Memory.h" +#include "GX2_Event.h" +#include "GX2_Shader.h" +#include "GX2_Blit.h" +#include "GX2_Draw.h" +#include "GX2_Query.h" +#include "GX2_Misc.h" +#include "GX2_Surface.h" +#include "GX2_Surface_Copy.h" +#include "GX2_Texture.h" + +#define GX2_TV_RENDER_NONE 0 +#define GX2_TV_RENDER_480 1 +#define GX2_TV_RENDER_480_WIDE 2 +#define GX2_TV_RENDER_720 3 +#define GX2_TV_RENDER_720I 4 +#define GX2_TV_RENDER_1080 5 +#define GX2_TV_RENDER_COUNT 6 + +struct +{ + sint32 width; + sint32 height; +}tvScanBufferResolutions[GX2_TV_RENDER_COUNT] = { +0,0, +640,480, +854,480, +1280,720, +1280,720, +1920,1080 +}; + +uint64 lastSwapTime = 0; + +void gx2Export_GX2SwapScanBuffers(PPCInterpreter_t* hCPU) +{ + gx2Log_printf("GX2SwapScanBuffers()"); + + bool isPokken = false; + + uint64 titleId = CafeSystem::GetForegroundTitleId(); + if (titleId == 0x00050000101DF500ull || titleId == 0x00050000101C5800ull || titleId == 0x00050000101DF400ull) + isPokken = true; + + if (isPokken) + GX2::GX2DrawDone(); + + GX2ReserveCmdSpace(5+2); + + uint64 tick64 = PPCInterpreter_getMainCoreCycleCounter() / 20ULL; + lastSwapTime = tick64; + // count flip request + // is this updated via a PM4 MEM_WRITE operation? + + // Orochi Warriors seems to call GX2SwapScanBuffers on arbitrary threads/cores. The PM4 commands should go through to the GPU as long as there is no active display list and no other core is submitting commands simultaneously + // right now, we work around this by avoiding the infinite loop below (request counter incremented, but PM4 not sent) + uint32 coreIndex = PPCInterpreter_getCoreIndex(ppcInterpreterCurrentInstance); + if (GX2::sGX2MainCoreIndex == coreIndex) + LatteGPUState.sharedArea->flipRequestCountBE = _swapEndianU32(_swapEndianU32(LatteGPUState.sharedArea->flipRequestCountBE) + 1); + + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_REQUEST_SWAP_BUFFERS, 1)); + gx2WriteGather_submitU32AsBE(0); // reserved + + // swap frames + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_TRIGGER_SCANBUFFER_SWAP, 1)); + gx2WriteGather_submitU32AsBE(0); // reserved + + // wait for flip if the CPU is too far ahead + // doing it after swap request is how the actual console does it, but that still causes issues in Pokken + while ((sint32)(_swapEndianU32(LatteGPUState.sharedArea->flipRequestCountBE) - _swapEndianU32(LatteGPUState.sharedArea->flipExecuteCountBE)) > 5) + { + GX2::GX2WaitForFlip(); + } + + GX2::GX2WriteGather_checkAndInsertWrapAroundMark(); + osLib_returnFromFunction(hCPU, 0); +} + +void gx2Export_GX2CopyColorBufferToScanBuffer(PPCInterpreter_t* hCPU) +{ + gx2Log_printf("GX2CopyColorBufferToScanBuffer(0x%08x,%d)\n", hCPU->gpr[3], hCPU->gpr[4]); + GX2ReserveCmdSpace(5); + + // todo: proper implementation + + // hack: Avoid running to far ahead of GPU. Normally this would be guaranteed by the circular buffer model, which we currently dont fully emulate + if(GX2::GX2WriteGather_getReadWriteDistance() > 32*1024*1024 ) + { + debug_printf("Waiting for GPU to catch up...\n"); + PPCInterpreter_relinquishTimeslice(); // release current thread + return; + } + GX2ColorBuffer* colorBuffer = (GX2ColorBuffer*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); + + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_COPY_COLORBUFFER_TO_SCANBUFFER, 9)); + gx2WriteGather_submitU32AsBE(memory_virtualToPhysical(colorBuffer->surface.imagePtr)); + gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.width); + gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.height); + gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.pitch); + gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.tileMode.value()); + gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.swizzle); + gx2WriteGather_submitU32AsBE(_swapEndianU32(colorBuffer->viewFirstSlice)); + gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.format.value()); + gx2WriteGather_submitU32AsBE(hCPU->gpr[4]); + + osLib_returnFromFunction(hCPU, 0); +} + +void gx2Export_GX2WaitForFreeScanBuffer(PPCInterpreter_t* hCPU) +{ + // todo: proper implementation + debug_printf("GX2WaitForFreeScanBuffer(): Unimplemented\n"); + + osLib_returnFromFunction(hCPU, 0); +} + +void gx2Export_GX2GetCurrentScanBuffer(PPCInterpreter_t* hCPU) +{ + // todo: proper implementation + uint32 scanTarget = hCPU->gpr[3]; + GX2ColorBuffer* colorBufferBE = (GX2ColorBuffer*)memory_getPointerFromVirtualOffset(hCPU->gpr[4]); + memset(colorBufferBE, 0x00, sizeof(GX2ColorBuffer)); + colorBufferBE->surface.width = 100; + colorBufferBE->surface.height = 100; + // note: For now we abuse the tiling aperture memory area as framebuffer pointers + if( scanTarget == GX2_SCAN_TARGET_TV ) + { + colorBufferBE->surface.imagePtr = MEMORY_TILINGAPERTURE_AREA_ADDR+0x200000; + } + else if( scanTarget == GX2_SCAN_TARGET_DRC_FIRST ) + { + colorBufferBE->surface.imagePtr = MEMORY_TILINGAPERTURE_AREA_ADDR+0x40000; + } + osLib_returnFromFunction(hCPU, 0); +} + +void coreinitExport_GX2GetSystemTVScanMode(PPCInterpreter_t* hCPU) +{ + // 1080p = 7 + osLib_returnFromFunction(hCPU, 7); +} + +void coreinitExport_GX2GetSystemTVAspectRatio(PPCInterpreter_t* hCPU) +{ + osLib_returnFromFunction(hCPU, 1); // 16:9 +} + +void gx2Export_GX2TempGetGPUVersion(PPCInterpreter_t* hCPU) +{ + osLib_returnFromFunction(hCPU, 2); +} + +void _GX2InitScanBuffer(GX2ColorBuffer* colorBuffer, sint32 width, sint32 height, Latte::E_GX2SURFFMT format) +{ + colorBuffer->surface.resFlag = GX2_RESFLAG_USAGE_TEXTURE | GX2_RESFLAG_USAGE_COLOR_BUFFER; + colorBuffer->surface.width = width; + colorBuffer->surface.height = height; + colorBuffer->viewFirstSlice = _swapEndianU32(0); + colorBuffer->viewNumSlices = _swapEndianU32(1); + colorBuffer->viewMip = _swapEndianU32(0); + colorBuffer->surface.numLevels = 1; + colorBuffer->surface.dim = Latte::E_DIM::DIM_2D; + colorBuffer->surface.swizzle = 0; + colorBuffer->surface.depth = 1; + colorBuffer->surface.tileMode = Latte::E_GX2TILEMODE::TM_LINEAR_GENERAL; + colorBuffer->surface.format = format; + colorBuffer->surface.mipPtr = MPTR_NULL; + colorBuffer->surface.aa = 0; + GX2::GX2CalcSurfaceSizeAndAlignment(&colorBuffer->surface); + colorBuffer->surface.resFlag = GX2_RESFLAG_USAGE_TEXTURE | GX2_RESFLAG_USAGE_COLOR_BUFFER | GX2_RESFLAG_USAGE_SCAN_BUFFER; +} + +void gx2Export_GX2CalcTVSize(PPCInterpreter_t* hCPU) +{ + uint32 tvRenderMode = hCPU->gpr[3]; + Latte::E_GX2SURFFMT format = (Latte::E_GX2SURFFMT)hCPU->gpr[4]; + uint32 bufferingMode = hCPU->gpr[5]; + uint32 outputSizeMPTR = hCPU->gpr[6]; + uint32 outputScaleNeededMPTR = hCPU->gpr[7]; + + cemu_assert(tvRenderMode < GX2_TV_RENDER_COUNT); + + uint32 width = tvScanBufferResolutions[tvRenderMode].width; + uint32 height = tvScanBufferResolutions[tvRenderMode].height; + + GX2ColorBuffer colorBuffer; + memset(&colorBuffer, 0, sizeof(GX2ColorBuffer)); + _GX2InitScanBuffer(&colorBuffer, width, height, format); + + uint32 imageSize = colorBuffer.surface.imageSize; + uint32 alignment = colorBuffer.surface.alignment; + + uint32 alignmentPaddingSize = (alignment - (imageSize%alignment)) % alignment; + + uint32 uknMult = 1; // probably for interlaced? + if (tvRenderMode == GX2_TV_RENDER_720I) + uknMult = 2; + + uint32 adjustedBufferingMode = bufferingMode; + if (tvRenderMode < GX2_TV_RENDER_720) + adjustedBufferingMode = 4; + + uint32 bufferedImageSize = (imageSize + alignmentPaddingSize) * adjustedBufferingMode; + bufferedImageSize = bufferedImageSize * uknMult - alignmentPaddingSize; + + memory_writeU32(outputSizeMPTR, bufferedImageSize); + memory_writeU32(outputScaleNeededMPTR, 0); // todo + osLib_returnFromFunction(hCPU, 0); +} + +void gx2Export_GX2CalcDRCSize(PPCInterpreter_t* hCPU) +{ + + ppcDefineParamS32(drcMode, 0); + ppcDefineParamU32(format, 1); + ppcDefineParamU32(bufferingMode, 2); + ppcDefineParamMPTR(sizeMPTR, 3); + ppcDefineParamMPTR(scaleNeededMPTR, 4); + + uint32 width = 0; + uint32 height = 0; + if (drcMode > 0) + { + width = 854; + height = 480; + } + + GX2ColorBuffer colorBuffer = {}; + memset(&colorBuffer, 0, sizeof(colorBuffer)); + _GX2InitScanBuffer(&colorBuffer, width, height, (Latte::E_GX2SURFFMT)format); + + uint32 imageSize = colorBuffer.surface.imageSize; + uint32 alignment = colorBuffer.surface.alignment; + + uint32 alignmentPaddingSize = (alignment - (imageSize%alignment)) % alignment; + + + uint32 adjustedBufferingMode = bufferingMode; + + uint32 bufferedImageSize = (imageSize + alignmentPaddingSize) * adjustedBufferingMode; + bufferedImageSize = bufferedImageSize - alignmentPaddingSize; + + memory_writeU32(sizeMPTR, bufferedImageSize); + memory_writeU32(scaleNeededMPTR, 0); + + osLib_returnFromFunction(hCPU, 0); +} + +void gx2Export_GX2SetDRCScale(PPCInterpreter_t* hCPU) +{ + gx2Log_printf("GX2SetDRCScale(%d,%d)", hCPU->gpr[3], hCPU->gpr[4]); + osLib_returnFromFunction(hCPU, 0); +} + +void gx2Export_GX2SetDRCConnectCallback(PPCInterpreter_t* hCPU) +{ + ppcDefineParamS32(channel, 0); + ppcDefineParamMEMPTR(callback, void, 1); + gx2Log_printf("GX2SetDRCConnectCallback(%d, 0x%08x)", channel, callback.GetMPTR()); + if(callback.GetPtr()) + PPCCoreCallback(callback, channel, TRUE); + osLib_returnFromFunction(hCPU, 0); +} + +void gx2Export_GX2SetSemaphore(PPCInterpreter_t* hCPU) +{ + gx2Log_printf("GX2SetSemaphore(0x%08x,%d)", hCPU->gpr[3], hCPU->gpr[4]); + ppcDefineParamMPTR(semaphoreMPTR, 0); + ppcDefineParamS32(mode, 1); + + uint32 SEM_SEL; + + if (mode == 0) + { + // wait + SEM_SEL = 7; + } + else if (mode == 1) + { + // signal + SEM_SEL = 6; + } + else + { + cemu_assert_debug(false); + osLib_returnFromFunction(hCPU, 0); + return; + } + uint32 semaphoreControl = (SEM_SEL << 29); + semaphoreControl |= 0x1000; // WAIT_ON_SIGNAL + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_MEM_SEMAPHORE, 2)); + gx2WriteGather_submitU32AsBE(memory_virtualToPhysical(semaphoreMPTR)); // semaphore physical address + gx2WriteGather_submitU32AsBE(semaphoreControl); // control + + osLib_returnFromFunction(hCPU, 0); +} + +void gx2Export_GX2Flush(PPCInterpreter_t* hCPU) +{ + gx2Log_printf("GX2Flush()"); + _GX2SubmitToTCL(); + osLib_returnFromFunction(hCPU, 0); +} + +uint8* _GX2LastFlushPtr[PPC_CORE_COUNT] = {NULL}; + +uint64 _prevReturnedGPUTime = 0; + +uint64 Latte_GetTime() +{ + uint64 gpuTime = coreinit::coreinit_getTimerTick(); + gpuTime *= 20000ULL; + if (gpuTime <= _prevReturnedGPUTime) + gpuTime = _prevReturnedGPUTime + 1; // avoid ever returning identical timestamps + _prevReturnedGPUTime = gpuTime; + return gpuTime; +} + +void _GX2SubmitToTCL() +{ + uint32 coreIndex = PPCInterpreter_getCoreIndex(ppcInterpreterCurrentInstance); + // do nothing if called from non-main GX2 core + if (GX2::sGX2MainCoreIndex != coreIndex) + { + forceLogDebug_printf("_GX2SubmitToTCL() called on non-main GX2 core"); + return; + } + if( gx2WriteGatherPipe.displayListStart[coreIndex] != MPTR_NULL ) + return; // quit if in display list + _GX2LastFlushPtr[coreIndex] = (gx2WriteGatherPipe.writeGatherPtrGxBuffer[coreIndex]); + // update last submitted CB timestamp + uint64 commandBufferTimestamp = Latte_GetTime(); + LatteGPUState.lastSubmittedCommandBufferTimestamp.store(commandBufferTimestamp); + gx2Log_printf("Submitting GX2 command buffer with timestamp %016I64x", commandBufferTimestamp); + // submit HLE packet to write retirement timestamp + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_SET_CB_RETIREMENT_TIMESTAMP, 2)); + gx2WriteGather_submitU32AsBE((uint32)(commandBufferTimestamp>>32ULL)); + gx2WriteGather_submitU32AsBE((uint32)(commandBufferTimestamp&0xFFFFFFFFULL)); +} + +uint32 _GX2GetUnflushedBytes(uint32 coreIndex) +{ + uint32 unflushedBytes = 0; + if (_GX2LastFlushPtr[coreIndex] != NULL) + { + if (_GX2LastFlushPtr[coreIndex] > gx2WriteGatherPipe.writeGatherPtrGxBuffer[coreIndex]) + unflushedBytes = (uint32)(gx2WriteGatherPipe.writeGatherPtrGxBuffer[coreIndex] - gx2WriteGatherPipe.gxRingBuffer + 4); // this isn't 100% correct since we ignore the bytes between the last flush address and the start of the wrap around + else + unflushedBytes = (uint32)(gx2WriteGatherPipe.writeGatherPtrGxBuffer[coreIndex] - _GX2LastFlushPtr[coreIndex]); + } + else + unflushedBytes = (uint32)(gx2WriteGatherPipe.writeGatherPtrGxBuffer[coreIndex] - gx2WriteGatherPipe.gxRingBuffer); + return unflushedBytes; +} + +/* + * Guarantees that the requested amount of space is available on the current command buffer + * If the space is not available, the current command buffer is pushed to the GPU and a new one is allocated + */ +void GX2ReserveCmdSpace(uint32 reservedFreeSpaceInU32) +{ + uint32 coreIndex = PPCInterpreter_getCoreIndex(ppcInterpreterCurrentInstance); + // if we are in a display list then do nothing + if( gx2WriteGatherPipe.displayListStart[coreIndex] != MPTR_NULL ) + return; + uint32 unflushedBytes = _GX2GetUnflushedBytes(coreIndex); + if( unflushedBytes >= 0x1000 ) + { + _GX2SubmitToTCL(); + } +} + +void gx2_load() +{ + osLib_addFunction("gx2", "GX2GetContextStateDisplayList", gx2Export_GX2GetContextStateDisplayList); + + // swap, vsync & timing + osLib_addFunction("gx2", "GX2SwapScanBuffers", gx2Export_GX2SwapScanBuffers); + osLib_addFunction("gx2", "GX2GetSwapStatus", gx2Export_GX2GetSwapStatus); + osLib_addFunction("gx2", "GX2CopyColorBufferToScanBuffer", gx2Export_GX2CopyColorBufferToScanBuffer); + osLib_addFunction("gx2", "GX2WaitForFreeScanBuffer", gx2Export_GX2WaitForFreeScanBuffer); + osLib_addFunction("gx2", "GX2GetCurrentScanBuffer", gx2Export_GX2GetCurrentScanBuffer); + + // shader stuff + osLib_addFunction("gx2", "GX2GetVertexShaderGPRs", gx2Export_GX2GetVertexShaderGPRs); + osLib_addFunction("gx2", "GX2GetVertexShaderStackEntries", gx2Export_GX2GetVertexShaderStackEntries); + osLib_addFunction("gx2", "GX2GetPixelShaderGPRs", gx2Export_GX2GetPixelShaderGPRs); + osLib_addFunction("gx2", "GX2GetPixelShaderStackEntries", gx2Export_GX2GetPixelShaderStackEntries); + osLib_addFunction("gx2", "GX2SetFetchShader", gx2Export_GX2SetFetchShader); + osLib_addFunction("gx2", "GX2SetVertexShader", gx2Export_GX2SetVertexShader); + osLib_addFunction("gx2", "GX2SetPixelShader", gx2Export_GX2SetPixelShader); + osLib_addFunction("gx2", "GX2SetGeometryShader", gx2Export_GX2SetGeometryShader); + osLib_addFunction("gx2", "GX2SetComputeShader", gx2Export_GX2SetComputeShader); + osLib_addFunction("gx2", "GX2SetVertexUniformReg", gx2Export_GX2SetVertexUniformReg); + osLib_addFunction("gx2", "GX2SetVertexUniformBlock", gx2Export_GX2SetVertexUniformBlock); + osLib_addFunction("gx2", "GX2RSetVertexUniformBlock", gx2Export_GX2RSetVertexUniformBlock); + + osLib_addFunction("gx2", "GX2SetPixelUniformBlock", gx2Export_GX2SetPixelUniformBlock); + osLib_addFunction("gx2", "GX2SetPixelUniformReg", gx2Export_GX2SetPixelUniformReg); + osLib_addFunction("gx2", "GX2SetGeometryUniformBlock", gx2Export_GX2SetGeometryUniformBlock); + osLib_addFunction("gx2", "GX2SetShaderModeEx", gx2Export_GX2SetShaderModeEx); + + osLib_addFunction("gx2", "GX2CalcGeometryShaderInputRingBufferSize", gx2Export_GX2CalcGeometryShaderInputRingBufferSize); + osLib_addFunction("gx2", "GX2CalcGeometryShaderOutputRingBufferSize", gx2Export_GX2CalcGeometryShaderOutputRingBufferSize); + + // color/depth buffers + osLib_addFunction("gx2", "GX2InitColorBufferRegs", gx2Export_GX2InitColorBufferRegs); + osLib_addFunction("gx2", "GX2InitDepthBufferRegs", gx2Export_GX2InitDepthBufferRegs); + osLib_addFunction("gx2", "GX2SetColorBuffer", gx2Export_GX2SetColorBuffer); + osLib_addFunction("gx2", "GX2SetDepthBuffer", gx2Export_GX2SetDepthBuffer); + + osLib_addFunction("gx2", "GX2SetDRCBuffer", gx2Export_GX2SetDRCBuffer); + osLib_addFunction("gx2", "GX2MarkScanBufferCopied", gx2Export_GX2MarkScanBufferCopied); + + // misc + osLib_addFunction("gx2", "GX2TempGetGPUVersion", gx2Export_GX2TempGetGPUVersion); + osLib_addFunction("gx2", "GX2CalcTVSize", gx2Export_GX2CalcTVSize); + osLib_addFunction("gx2", "GX2CalcDRCSize", gx2Export_GX2CalcDRCSize); + osLib_addFunction("gx2", "GX2SetDRCScale", gx2Export_GX2SetDRCScale); + osLib_addFunction("gx2", "GX2SetDRCConnectCallback", gx2Export_GX2SetDRCConnectCallback); + + osLib_addFunction("gx2", "GX2GetSystemTVScanMode", coreinitExport_GX2GetSystemTVScanMode); + osLib_addFunction("gx2", "GX2GetSystemTVAspectRatio", coreinitExport_GX2GetSystemTVAspectRatio); + + osLib_addFunction("gx2", "GX2SetSwapInterval", gx2Export_GX2SetSwapInterval); + osLib_addFunction("gx2", "GX2GetSwapInterval", gx2Export_GX2GetSwapInterval); + osLib_addFunction("gx2", "GX2GetGPUTimeout", gx2Export_GX2GetGPUTimeout); + osLib_addFunction("gx2", "GX2SampleTopGPUCycle", gx2Export_GX2SampleTopGPUCycle); + osLib_addFunction("gx2", "GX2SampleBottomGPUCycle", gx2Export_GX2SampleBottomGPUCycle); + + osLib_addFunction("gx2", "GX2AllocateTilingApertureEx", gx2Export_GX2AllocateTilingApertureEx); + osLib_addFunction("gx2", "GX2FreeTilingAperture", gx2Export_GX2FreeTilingAperture); + + // context state + osLib_addFunction("gx2", "GX2SetDefaultState", gx2Export_GX2SetDefaultState); + osLib_addFunction("gx2", "GX2SetupContextStateEx", gx2Export_GX2SetupContextStateEx); + osLib_addFunction("gx2", "GX2SetContextState", gx2Export_GX2SetContextState); + + // semaphore + osLib_addFunction("gx2", "GX2SetSemaphore", gx2Export_GX2SetSemaphore); + + // command buffer + osLib_addFunction("gx2", "GX2Flush", gx2Export_GX2Flush); + + GX2::GX2Init_writeGather(); + GX2::GX2MemInit(); + GX2::GX2ResourceInit(); + GX2::GX2CommandInit(); + GX2::GX2SurfaceInit(); + GX2::GX2SurfaceCopyInit(); + GX2::GX2TextureInit(); + GX2::GX2StateInit(); + GX2::GX2ShaderInit(); + GX2::GX2EventInit(); + GX2::GX2BlitInit(); + GX2::GX2DrawInit(); + GX2::GX2StreamoutInit(); + GX2::GX2QueryInit(); + GX2::GX2MiscInit(); +} diff --git a/src/Cafe/OS/libs/gx2/GX2.h b/src/Cafe/OS/libs/gx2/GX2.h new file mode 100644 index 00000000..c9607ee4 --- /dev/null +++ b/src/Cafe/OS/libs/gx2/GX2.h @@ -0,0 +1,89 @@ +#pragma once +#include "Cafe/HW/Latte/Core/LatteConst.h" + +// base defines for GX2 +#define GX2_TRUE 1 +#define GX2_FALSE 0 +#define GX2_ENABLE 1 +#define GX2_DISABLE 0 + +// tex unit base for render backends +#define CEMU_PS_TEX_UNIT_BASE 0 +#define CEMU_VS_TEX_UNIT_BASE 32 +#define CEMU_GS_TEX_UNIT_BASE 64 + +#include "GX2_Surface.h" + +// general + +void gx2_load(); + +// shader + +void gx2Export_GX2SetFetchShader(PPCInterpreter_t* hCPU); +void gx2Export_GX2GetVertexShaderGPRs(PPCInterpreter_t* hCPU); +void gx2Export_GX2GetVertexShaderStackEntries(PPCInterpreter_t* hCPU); +void gx2Export_GX2GetPixelShaderGPRs(PPCInterpreter_t* hCPU); +void gx2Export_GX2GetPixelShaderStackEntries(PPCInterpreter_t* hCPU); +void gx2Export_GX2SetVertexShader(PPCInterpreter_t* hCPU); +void gx2Export_GX2SetPixelShader(PPCInterpreter_t* hCPU); +void gx2Export_GX2SetGeometryShader(PPCInterpreter_t* hCPU); +void gx2Export_GX2SetComputeShader(PPCInterpreter_t* hCPU); +void gx2Export_GX2SetVertexUniformReg(PPCInterpreter_t* hCPU); +void gx2Export_GX2SetVertexUniformBlock(PPCInterpreter_t* hCPU); +void gx2Export_GX2RSetVertexUniformBlock(PPCInterpreter_t* hCPU); +void gx2Export_GX2SetPixelUniformBlock(PPCInterpreter_t* hCPU); +void gx2Export_GX2SetPixelUniformReg(PPCInterpreter_t* hCPU); +void gx2Export_GX2SetGeometryUniformBlock(PPCInterpreter_t* hCPU); +void gx2Export_GX2SetShaderModeEx(PPCInterpreter_t* hCPU); +void gx2Export_GX2CalcGeometryShaderInputRingBufferSize(PPCInterpreter_t* hCPU); +void gx2Export_GX2CalcGeometryShaderOutputRingBufferSize(PPCInterpreter_t* hCPU); + +// write gather / command queue + +#define GX2_COMMAND_RING_BUFFER_SIZE (64*1024*1024) // 64MB + +void gx2Export_GX2GetContextStateDisplayList(PPCInterpreter_t* hCPU); + +#include "GX2_Command.h" + +// misc +void gx2Export_GX2AllocateTilingApertureEx(PPCInterpreter_t* hCPU); +void gx2Export_GX2FreeTilingAperture(PPCInterpreter_t* hCPU); + +void gx2Export_GX2SetSwapInterval(PPCInterpreter_t* hCPU); +void gx2Export_GX2GetSwapInterval(PPCInterpreter_t* hCPU); +void gx2Export_GX2GetSwapStatus(PPCInterpreter_t* hCPU); +void gx2Export_GX2GetGPUTimeout(PPCInterpreter_t* hCPU); +void gx2Export_GX2SampleTopGPUCycle(PPCInterpreter_t* hCPU); +void gx2Export_GX2SampleBottomGPUCycle(PPCInterpreter_t* hCPU); + +// color/depth buffers + +#define GX2_SCAN_TARGET_TV 1 +#define GX2_SCAN_TARGET_TV_RIGH 2 +#define GX2_SCAN_TARGET_DRC_FIRST 4 +#define GX2_SCAN_TARGET_DRC_SECOND 8 + +void gx2Export_GX2InitColorBufferRegs(PPCInterpreter_t* hCPU); +void gx2Export_GX2InitDepthBufferRegs(PPCInterpreter_t* hCPU); +void gx2Export_GX2SetColorBuffer(PPCInterpreter_t* hCPU); +void gx2Export_GX2SetDepthBuffer(PPCInterpreter_t* hCPU); +void gx2Export_GX2SetDRCBuffer(PPCInterpreter_t* hCPU); +void gx2Export_GX2MarkScanBufferCopied(PPCInterpreter_t* hCPU); + +// special state + +#define GX2_SPECIAL_STATE_COUNT 9 + +// context state + +void gx2Export_GX2SetDefaultState(PPCInterpreter_t* hCPU); +void gx2Export_GX2SetupContextStateEx(PPCInterpreter_t* hCPU); +void gx2Export_GX2SetContextState(PPCInterpreter_t* hCPU); + +// command buffer + +uint32 _GX2GetUnflushedBytes(uint32 coreIndex); +void _GX2SubmitToTCL(); +void GX2ReserveCmdSpace(uint32 reservedFreeSpaceInU32); \ No newline at end of file diff --git a/src/Cafe/OS/libs/gx2/GX2_AddrTest.cpp b/src/Cafe/OS/libs/gx2/GX2_AddrTest.cpp new file mode 100644 index 00000000..6b32da58 --- /dev/null +++ b/src/Cafe/OS/libs/gx2/GX2_AddrTest.cpp @@ -0,0 +1,422 @@ +#include "Cafe/OS/libs/coreinit/coreinit_DynLoad.h" +#include "Cafe/HW/Espresso/PPCCallback.h" +#include "Cafe/HW/Espresso/PPCState.h" +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/HW/Latte/ISA/LatteReg.h" +#include "Cafe/HW/Latte/Core/Latte.h" +#include "Cafe/HW/Latte/LatteAddrLib/LatteAddrLib.h" +#include "util/highresolutiontimer/HighResolutionTimer.h" + +namespace GX2 +{ + struct AddrCreate_INPUT + { + /* +0x00 */ uint32be structSize; + /* +0x04 */ uint32be ukn04_maybeGen; + /* +0x08 */ uint32be ukn08; + /* +0x0C */ uint32be revision; + /* +0x10 */ uint32be func_Alloc; + /* +0x14 */ uint32be func_Free; + /* +0x18 */ uint32be func_Debug; + /* +0x1C */ uint32be ukn1C; + /* +0x20 */ uint32be reg263C; + /* +0x24 */ uint32be ukn24; + /* +0x28 */ uint32be ukn28; + /* +0x2C */ uint32be ukn2C; + /* +0x30 */ uint32be ukn30; + /* +0x34 */ uint32be ukn34; + /* +0x38 */ uint32be ukn38; + /* +0x3C */ uint32be ukn3C; + /* +0x40 */ uint32be ukn40; + }; + + struct AddrCreate_OUTPUT + { + uint32be structSize; + MEMPTR<void> addrLibPtr; + }; + + static_assert(sizeof(AddrCreate_INPUT) == 0x44); + static_assert(sizeof(AddrCreate_OUTPUT) == 8); + + struct ADDRAllocParam + { + uint32be ukn00; // alignment? + uint32be ukn04; + uint32be size; + }; + + struct ADDRComputeSurfaceInfo_INPUT + { + uint32be structSize; + betype<Latte::E_HWTILEMODE> tileMode; + betype<Latte::E_HWSURFFMT> format; + uint32be bpp; + uint32be numSamples; + uint32be width; + uint32be height; + uint32be numSlices; + uint32be slice; + uint32be mipLevel; + uint32be _flags; + uint32be numFrags; + MEMPTR<void> tileInfo; + uint32be tileType; + uint32be tileIndex; + + enum FLAG_BITS + { + FLAG_BIT_CUBE = (1 << 27), + FLAG_BIT_VOLUME = (1 << 26), + + FLAG_BIT_OPT4SPACE = (1 << 19), + }; + + void SetFlagCube(bool f) + { + if (f) _flags |= FLAG_BIT_CUBE; + else _flags &= ~FLAG_BIT_CUBE; + } + + void SetFlagVolume(bool f) + { + if (f) _flags |= FLAG_BIT_VOLUME; + else _flags &= ~FLAG_BIT_VOLUME; + } + + void SetFlagOpt4Space(bool f) + { + if (f) _flags |= FLAG_BIT_OPT4SPACE; + else _flags &= ~FLAG_BIT_OPT4SPACE; + } + }; + + static_assert(sizeof(ADDRComputeSurfaceInfo_INPUT) == 0x3C); + + struct ADDRComputeSurfaceInfo_OUTPUT + { + /* 0x00 */ uint32be structSize; + /* 0x04 */ uint32be pitch; + /* 0x08 */ uint32be height; + /* 0x0C */ uint32be depth; + /* 0x10 */ uint64be surfSize; + /* 0x18 */ uint32be tileMode; + /* 0x1C */ uint32be baseAlign; + /* 0x20 */ uint32be pitchAlign; + /* 0x24 */ uint32be heightAlign; + /* 0x28 */ uint32be depthAlign; + /* 0x2C */ uint32be bpp; + /* 0x30 */ uint32be pixelPitch; + /* 0x34 */ uint32be pixelHeight; + /* 0x38 */ uint32be pixelBits; + /* 0x3C */ uint32be sliceSize; + /* 0x40 */ uint32be pitchTileMax; + /* 0x44 */ uint32be heightTileMax; + /* 0x48 */ uint32be sliceTileMax; + /* 0x4C */ MEMPTR<void> tileInfo; + /* 0x50 */ uint32be tileType; + /* 0x54 */ uint32be tileIndex; + /* 0x58 */ MEMPTR<void> stereoInfo; + /* 0x5C */ uint32be _padding; + }; + + static_assert(sizeof(ADDRComputeSurfaceInfo_OUTPUT) == 0x60); + + static void _cb_alloc(PPCInterpreter_t* hCPU) + { + ppcDefineParamStructPtr(param, ADDRAllocParam, 0); + uint32 r = coreinit_allocFromSysArea(param->size, 0x10); + osLib_returnFromFunction(hCPU, r); + } + + static void _cb_free(PPCInterpreter_t* hCPU) + { + cemu_assert_unimplemented(); + } + + static void _cb_debug(PPCInterpreter_t* hCPU) + { + cemu_assert_unimplemented(); + } + + static void* sAddrLib{}; + static uint32be tclFunc_AddrCreate = 0; + static uint32be tclFunc_AddrComputeSurfaceInfo = 0; + + void _TestAddrLib_Init() + { + // load tcl_addr_test.rpl (from /cafelibs/) + uint32be tclHandle; + uint32 r = coreinit::OSDynLoad_Acquire("tcl_addr_test.rpl", &tclHandle); + cemu_assert_debug(r == 0); + + // get imports + r = coreinit::OSDynLoad_FindExport(tclHandle, 0, "AddrCreate", &tclFunc_AddrCreate); + cemu_assert_debug(r == 0); + r = coreinit::OSDynLoad_FindExport(tclHandle, 0, "AddrComputeSurfaceInfo", &tclFunc_AddrComputeSurfaceInfo); + cemu_assert_debug(r == 0); + + // call AddrCreate + StackAllocator<AddrCreate_INPUT> addrCreateIn; + memset(addrCreateIn.GetPointer(), 0, sizeof(addrCreateIn)); + addrCreateIn->structSize = sizeof(addrCreateIn); + + addrCreateIn->ukn04_maybeGen = 6; // R600? + addrCreateIn->ukn08 = 0x51; + addrCreateIn->revision = 71; + addrCreateIn->reg263C = 0x44902; + addrCreateIn->ukn24 = 0; // ukn + + addrCreateIn->func_Alloc = PPCInterpreter_makeCallableExportDepr(_cb_alloc); + addrCreateIn->func_Free = PPCInterpreter_makeCallableExportDepr(_cb_free); + addrCreateIn->func_Debug = PPCInterpreter_makeCallableExportDepr(_cb_debug); + + StackAllocator<AddrCreate_OUTPUT> addrCreateOut; + memset(addrCreateOut.GetPointer(), 0, sizeof(addrCreateOut)); + addrCreateOut->structSize = sizeof(addrCreateOut); + + r = PPCCoreCallback((uint32)tclFunc_AddrCreate, addrCreateIn.GetPointer(), addrCreateOut.GetPointer()); + sAddrLib = addrCreateOut->addrLibPtr; + cemu_assert_debug(r == 0 && sAddrLib != nullptr); + } + + void _TestAddrLib_CalculateSurfaceInfo(Latte::E_GX2SURFFMT surfaceFormat, uint32 surfaceWidth, uint32 surfaceHeight, uint32 surfaceDepth, Latte::E_DIM surfaceDim, Latte::E_GX2TILEMODE surfaceTileMode, uint32 surfaceAA, uint32 level, ADDRComputeSurfaceInfo_OUTPUT* paramOut) + { + StackAllocator<ADDRComputeSurfaceInfo_INPUT> _paramIn; + ADDRComputeSurfaceInfo_INPUT& paramIn = *_paramIn.GetPointer(); + memset(¶mIn, 0, sizeof(ADDRComputeSurfaceInfo_INPUT)); + memset(paramOut, 0, sizeof(ADDRComputeSurfaceInfo_OUTPUT)); + Latte::E_HWSURFFMT hwFormat = GetHWFormat(surfaceFormat); + if (surfaceTileMode == Latte::E_GX2TILEMODE::TM_LINEAR_SPECIAL) + { + uint32 numSamples = 1 << surfaceAA; + uint32 blockSize = IsCompressedFormat(surfaceFormat) ? 4 : 1; + uint32 width = ((surfaceWidth >> level) + blockSize - 1) & ~(blockSize - 1); + paramOut->bpp = GetFormatBits(hwFormat); + paramOut->structSize = sizeof(ADDRComputeSurfaceInfo_OUTPUT); + paramOut->pitch = width / blockSize; + paramOut->pixelBits = paramOut->bpp; + paramOut->baseAlign = 1; + paramOut->pitchAlign = 1; + paramOut->heightAlign = 1; + paramOut->depthAlign = 1; + switch (surfaceDim) + { + case Latte::E_DIM::DIM_1D: + paramOut->height = 1; + paramOut->depth = 1; + break; + case Latte::E_DIM::DIM_2D: + paramOut->height = std::max<uint32>(surfaceHeight >> level, 1); + paramOut->depth = 1; + break; + case Latte::E_DIM::DIM_3D: + paramOut->height = surfaceHeight >> level; + paramOut->height = std::max<uint32>(paramOut->height, 1); + paramOut->depth = std::max<uint32>(surfaceDepth >> level, 1); + break; + case Latte::E_DIM::DIM_CUBEMAP: + paramOut->height = std::max<uint32>(surfaceHeight >> level, 1); + paramOut->depth = std::max<uint32>(surfaceDepth, 6); + break; + case Latte::E_DIM::DIM_1D_ARRAY: + paramOut->height = 1; + paramOut->depth = surfaceDepth; + break; + case Latte::E_DIM::DIM_2D_ARRAY: + paramOut->height = std::max<uint32>(surfaceHeight >> level, 1); + paramOut->depth = surfaceDepth; + break; + default: + break; + } + paramOut->height = ((paramOut->height + blockSize - 1) & ~(blockSize - 1)) / (uint64)blockSize; + paramOut->pixelPitch = ((surfaceWidth >> level) + blockSize - 1) & ~(blockSize - 1); + paramOut->pixelPitch = std::max<uint32>(paramOut->pixelPitch, blockSize); + paramOut->pixelHeight = ((surfaceHeight >> level) + blockSize - 1) & ~(blockSize - 1); + paramOut->pixelHeight = std::max<uint32>(paramOut->pixelHeight, blockSize);; + paramOut->pitch = std::max<uint32>(paramOut->pitch, 1); + paramOut->height = std::max<uint32>(paramOut->height, 1); + paramOut->surfSize = paramOut->bpp * numSamples * paramOut->depth * paramOut->height * paramOut->pitch >> 3; + if (surfaceDim == Latte::E_DIM::DIM_3D) + paramOut->sliceSize = (uint32)(paramOut->surfSize); + else + { + if (paramOut->surfSize == 0 && paramOut->depth == 0) + paramOut->sliceSize = 0; // edge case for (1D)_ARRAY textures with 0/0/0 res + else + paramOut->sliceSize = ((uint32)paramOut->surfSize.value() / paramOut->depth); + } + paramOut->pitchTileMax = (paramOut->pitch >> 3) - 1; + paramOut->heightTileMax = (paramOut->height >> 3) - 1; + paramOut->sliceTileMax = (paramOut->height * paramOut->pitch >> 6) - 1; + } + else + { + paramIn.structSize = sizeof(paramIn); + paramIn.tileMode = Latte::MakeHWTileMode(surfaceTileMode); + paramIn.format = hwFormat; + paramIn.bpp = GetFormatBits(hwFormat); + paramIn.numSamples = 1 << surfaceAA; + paramIn.numFrags = paramIn.numSamples; + paramIn.width = std::max<uint32>(surfaceWidth >> level, 1); + switch (surfaceDim) + { + case Latte::E_DIM::DIM_1D: + paramIn.height = 1; + paramIn.numSlices = 1; + break; + case Latte::E_DIM::DIM_2D: + paramIn.height = std::max<uint32>(surfaceHeight >> level, 1); + paramIn.numSlices = 1; + break; + case Latte::E_DIM::DIM_3D: + paramIn.height = std::max<uint32>(surfaceHeight >> level, 1); + paramIn.numSlices = std::max<uint32>(surfaceDepth >> level, 1); + break; + case Latte::E_DIM::DIM_CUBEMAP: + paramIn.height = std::max<uint32>(surfaceHeight >> level, 1); + paramIn.numSlices = std::max<uint32>(surfaceDepth, 6); + paramIn.SetFlagCube(true); + break; + case Latte::E_DIM::DIM_1D_ARRAY: + paramIn.height = 1; + paramIn.numSlices = surfaceDepth; + break; + case Latte::E_DIM::DIM_2D_ARRAY: + paramIn.height = std::max<uint32>(surfaceHeight >> level, 1); + paramIn.numSlices = surfaceDepth; + break; + case Latte::E_DIM::DIM_2D_MSAA: + paramIn.height = std::max<uint32>(surfaceHeight >> level, 1); + paramIn.numSlices = 1; + break; + case Latte::E_DIM::DIM_2D_ARRAY_MSAA: + paramIn.height = std::max<uint32>(surfaceHeight >> level, 1); + paramIn.numSlices = surfaceDepth; + break; + default: + break; + } + paramIn.slice = 0; + paramIn.mipLevel = level; + if (surfaceDim == Latte::E_DIM::DIM_3D) + paramIn.SetFlagVolume(true); + paramIn.SetFlagOpt4Space(level == 0); + paramOut->structSize = sizeof(ADDRComputeSurfaceInfo_OUTPUT); + PPCCoreCallback((uint32)tclFunc_AddrComputeSurfaceInfo, sAddrLib, _paramIn.GetPointer(), paramOut); + } + } + + void _TestAddrLib_Compare(uint32 surfaceWidth, uint32 surfaceHeight, uint32 surfaceDepth, Latte::E_DIM surfaceDim, Latte::E_GX2SURFFMT surfaceFormat, Latte::E_GX2TILEMODE surfaceTileMode, uint32 surfaceAA, uint32 level) + { + // get result from tcl.rpl + StackAllocator<ADDRComputeSurfaceInfo_OUTPUT> _paramOut; + ADDRComputeSurfaceInfo_OUTPUT& tclSurfInfo = *_paramOut.GetPointer(); + _TestAddrLib_CalculateSurfaceInfo(surfaceFormat, surfaceWidth, surfaceHeight, surfaceDepth, surfaceDim, surfaceTileMode, surfaceAA, level, _paramOut.GetPointer()); + // get result from our implementation + LatteAddrLib::AddrSurfaceInfo_OUT ourSurfInfo; + LatteAddrLib::GX2CalculateSurfaceInfo(surfaceFormat, surfaceWidth, surfaceHeight, surfaceDepth, surfaceDim, surfaceTileMode, surfaceAA, level, &ourSurfInfo); + // compare + cemu_assert(tclSurfInfo.pitchAlign == ourSurfInfo.pitchAlign); + cemu_assert((Latte::E_HWTILEMODE)tclSurfInfo.tileMode.value() == ourSurfInfo.hwTileMode); + cemu_assert(tclSurfInfo.baseAlign == ourSurfInfo.baseAlign); + cemu_assert(tclSurfInfo.surfSize == ourSurfInfo.surfSize); + cemu_assert(tclSurfInfo.depthAlign == ourSurfInfo.depthAlign); + cemu_assert(tclSurfInfo.pitch == ourSurfInfo.pitch); + cemu_assert(tclSurfInfo.sliceSize == ourSurfInfo.sliceSize); + } + + void _TestAddrLib_Run() + { + uint32 surfaceAA = 0; + + std::vector<Latte::E_DIM> dimList = { + Latte::E_DIM::DIM_1D, + Latte::E_DIM::DIM_2D, + Latte::E_DIM::DIM_3D, + Latte::E_DIM::DIM_CUBEMAP, + Latte::E_DIM::DIM_1D_ARRAY, + Latte::E_DIM::DIM_2D_ARRAY, + Latte::E_DIM::DIM_2D_MSAA, + Latte::E_DIM::DIM_2D_ARRAY_MSAA + }; + + std::vector<Latte::E_GX2TILEMODE> tilemodeList = { + // linear + Latte::E_GX2TILEMODE::TM_LINEAR_GENERAL, + Latte::E_GX2TILEMODE::TM_LINEAR_ALIGNED, + // micro tiled + Latte::E_GX2TILEMODE::TM_1D_TILED_THIN1, + Latte::E_GX2TILEMODE::TM_1D_TILED_THICK, + // macro tiled + Latte::E_GX2TILEMODE::TM_2D_TILED_THIN1, + Latte::E_GX2TILEMODE::TM_2D_TILED_THIN4, + Latte::E_GX2TILEMODE::TM_2D_TILED_THIN2, + Latte::E_GX2TILEMODE::TM_2D_TILED_THICK, + Latte::E_GX2TILEMODE::TM_2B_TILED_THIN1, + Latte::E_GX2TILEMODE::TM_2B_TILED_THIN2, + Latte::E_GX2TILEMODE::TM_2B_TILED_THIN4, + Latte::E_GX2TILEMODE::TM_2B_TILED_THICK, + Latte::E_GX2TILEMODE::TM_3D_TILED_THIN1, + Latte::E_GX2TILEMODE::TM_3D_TILED_THICK, + Latte::E_GX2TILEMODE::TM_3B_TILED_THIN1, + Latte::E_GX2TILEMODE::TM_3B_TILED_THICK, + // special + Latte::E_GX2TILEMODE::TM_LINEAR_SPECIAL, + Latte::E_GX2TILEMODE::TM_32_SPECIAL, // note: Specific to GX2CalcSurfaceSizeAndAlignment, for AddrLib this should just be interpreted as (tm&0xF) + }; + + std::vector<Latte::E_GX2SURFFMT> formatList = { + Latte::E_GX2SURFFMT::HWFMT_8, Latte::E_GX2SURFFMT::HWFMT_8_8, Latte::E_GX2SURFFMT::HWFMT_8_8_8_8, // 8, 16, 32 + Latte::E_GX2SURFFMT::R32_UINT, Latte::E_GX2SURFFMT::R32_G32_UINT, Latte::E_GX2SURFFMT::R32_G32_B32_A32_UINT, // 32, 64, 128 + Latte::E_GX2SURFFMT::HWFMT_BC1, Latte::E_GX2SURFFMT::HWFMT_BC2, Latte::E_GX2SURFFMT::HWFMT_BC3, Latte::E_GX2SURFFMT::HWFMT_BC4, Latte::E_GX2SURFFMT::HWFMT_BC5 + }; + + std::vector<uint32> resXYList = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, + 31, 32, 33, 50, 63, 64, 65, 127, 128, 129, 200, 253, 254, 255, 256, 257, + 511, 512, 513, 1023, 1024, 1025, 2047, 2048, 2049, 4095, 4096, 4097 + }; + + debug_printf("Running AddrLib test...\n"); + + BenchmarkTimer timer; + timer.Start(); + size_t index = 0; + for (auto dim : dimList) + { + debug_printf("%d/%d\n", (int)index, (int)dimList.size()); + index++; + for (auto tileMode : tilemodeList) + { + for (auto format : formatList) + { + for (uint32 level = 0; level < 16; level++) + { + for (auto depth : resXYList) + { + for (auto height : resXYList) + { + for (auto width : resXYList) + { + _TestAddrLib_Compare(width, height, depth, dim, format, tileMode, surfaceAA, level); + } + } + } + } + } + } + } + timer.Stop(); + debug_printf("Test complete (in %d seconds)\n", (int)(timer.GetElapsedMilliseconds() * 0.001)); + assert_dbg(); + } + + void _test_AddrLib() + { + return; + _TestAddrLib_Init(); + _TestAddrLib_Run(); + } +} diff --git a/src/Cafe/OS/libs/gx2/GX2_Blit.cpp b/src/Cafe/OS/libs/gx2/GX2_Blit.cpp new file mode 100644 index 00000000..c8080f38 --- /dev/null +++ b/src/Cafe/OS/libs/gx2/GX2_Blit.cpp @@ -0,0 +1,225 @@ +#include "Common/precompiled.h" +#include "GX2_Blit.h" +#include "GX2_Command.h" +#include "GX2_Surface.h" +#include "Cafe/HW/Latte/ISA/LatteReg.h" +#include "Cafe/HW/Latte/Core/LattePM4.h" +#include "Cafe/OS/common/OSCommon.h" +#include "GX2_Resource.h" + +namespace GX2 +{ + // sets the depth/stencil clear registers and updates clear values in DepthBuffer struct + void GX2SetClearDepthStencil(GX2DepthBuffer* depthBuffer, float depthClearValue, uint8 stencilClearValue) + { + GX2ReserveCmdSpace(4); + *(uint32*)&depthBuffer->clearDepth = _swapEndianU32(*(uint32*)&depthClearValue); + depthBuffer->clearStencil = _swapEndianU32(stencilClearValue); + Latte::LATTE_DB_STENCIL_CLEAR stencilClearReg; + stencilClearReg.set_clearValue(stencilClearValue); + Latte::LATTE_DB_DEPTH_CLEAR depthClearReg; + depthClearReg.set_clearValue(depthClearValue); + gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 2), + Latte::REGADDR::DB_STENCIL_CLEAR - 0xA000, + stencilClearReg, depthClearReg); + } + + // similar to GX2SetClearDepthStencil but only sets depth + void GX2SetClearDepth(GX2DepthBuffer* depthBuffer, float depthClearValue) + { + GX2ReserveCmdSpace(3); + *(uint32*)&depthBuffer->clearDepth = _swapEndianU32(*(uint32*)&depthClearValue); + Latte::LATTE_DB_DEPTH_CLEAR depthClearReg; + depthClearReg.set_clearValue(depthClearValue); + gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), + Latte::REGADDR::DB_DEPTH_CLEAR - 0xA000, + depthClearReg); + } + + // similar to GX2SetClearDepthStencil but only sets stencil + void GX2SetClearStencil(GX2DepthBuffer* depthBuffer, uint8 stencilClearValue) + { + GX2ReserveCmdSpace(3); + depthBuffer->clearStencil = _swapEndianU32(stencilClearValue); + Latte::LATTE_DB_STENCIL_CLEAR stencilClearReg; + stencilClearReg.set_clearValue(stencilClearValue); + gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), + Latte::REGADDR::DB_STENCIL_CLEAR - 0xA000, + stencilClearReg); + } + + // update DB_STENCIL_CLEAR and DB_STENCIL_CLEAR based on clear flags + void _updateDepthStencilClearRegs(float depthClearValue, uint8 stencilClearValue, GX2ClearFlags clearFlags) + { + if ((clearFlags & GX2ClearFlags::SET_DEPTH_REG) != 0 && (clearFlags & GX2ClearFlags::SET_STENCIL_REG) != 0) + { + GX2ReserveCmdSpace(4); + Latte::LATTE_DB_STENCIL_CLEAR stencilClearReg; + stencilClearReg.set_clearValue(stencilClearValue); + Latte::LATTE_DB_DEPTH_CLEAR depthClearReg; + depthClearReg.set_clearValue(depthClearValue); + gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 2), + Latte::REGADDR::DB_STENCIL_CLEAR - 0xA000, + stencilClearReg, depthClearReg); + } + else if ((clearFlags & GX2ClearFlags::SET_DEPTH_REG) != 0) + { + GX2ReserveCmdSpace(3); + Latte::LATTE_DB_DEPTH_CLEAR depthClearReg; + depthClearReg.set_clearValue(depthClearValue); + gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), + Latte::REGADDR::DB_DEPTH_CLEAR - 0xA000, + depthClearReg); + } + else if ((clearFlags & GX2ClearFlags::SET_STENCIL_REG) != 0) + { + GX2ReserveCmdSpace(3); + Latte::LATTE_DB_STENCIL_CLEAR stencilClearReg; + stencilClearReg.set_clearValue(stencilClearValue); + gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), + Latte::REGADDR::DB_STENCIL_CLEAR - 0xA000, + stencilClearReg); + } + } + + void GX2ClearColor(GX2ColorBuffer* colorBuffer, float r, float g, float b, float a) + { + GX2ReserveCmdSpace(50); + if ((colorBuffer->surface.resFlag & GX2_RESFLAG_USAGE_COLOR_BUFFER) != 0) + { + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_CLEAR_COLOR_DEPTH_STENCIL, 23)); + gx2WriteGather_submitU32AsBE(1); // color (1) + gx2WriteGather_submitU32AsBE(memory_virtualToPhysical(colorBuffer->surface.imagePtr)); + gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.format.value()); + gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.tileMode.value()); + gx2WriteGather_submitU32AsBE(colorBuffer->surface.width); + gx2WriteGather_submitU32AsBE(colorBuffer->surface.height); + gx2WriteGather_submitU32AsBE(colorBuffer->surface.pitch); + gx2WriteGather_submitU32AsBE(_swapEndianU32(colorBuffer->viewFirstSlice)); + gx2WriteGather_submitU32AsBE(_swapEndianU32(colorBuffer->viewNumSlices)); + gx2WriteGather_submitU32AsBE(MPTR_NULL); + gx2WriteGather_submitU32AsBE(0); // depth buffer format + gx2WriteGather_submitU32AsBE(0); // tilemode for depth buffer + gx2WriteGather_submitU32AsBE(0); + gx2WriteGather_submitU32AsBE(0); + gx2WriteGather_submitU32AsBE(0); + gx2WriteGather_submitU32AsBE(0); + gx2WriteGather_submitU32AsBE(0); + gx2WriteGather_submitU32AsBE((uint32)(r * 255.0f)); + gx2WriteGather_submitU32AsBE((uint32)(g * 255.0f)); + gx2WriteGather_submitU32AsBE((uint32)(b * 255.0f)); + gx2WriteGather_submitU32AsBE((uint32)(a * 255.0f)); + gx2WriteGather_submitU32AsBE(0); // clear depth + gx2WriteGather_submitU32AsBE(0); // clear stencil + } + else + { + debug_printf("GX2ClearColor() - unsupported surface flags\n"); + } + } + + void GX2ClearBuffersEx(GX2ColorBuffer* colorBuffer, GX2DepthBuffer* depthBuffer, float r, float g, float b, float a, float depthClearValue, uint8 stencilClearValue, GX2ClearFlags clearFlags) + { + GX2ReserveCmdSpace(50); + _updateDepthStencilClearRegs(depthClearValue, stencilClearValue, clearFlags); + + uint32 hleClearFlags = 0; + if ((clearFlags & GX2ClearFlags::CLEAR_DEPTH) != 0) + hleClearFlags |= 2; + if ((clearFlags & GX2ClearFlags::CLEAR_STENCIL) != 0) + hleClearFlags |= 4; + hleClearFlags |= 1; + + // send command to clear color, depth and stencil + if (_swapEndianU32(colorBuffer->viewFirstSlice) != 0) + debugBreakpoint(); + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_CLEAR_COLOR_DEPTH_STENCIL, 23)); + gx2WriteGather_submitU32AsBE(hleClearFlags); // color (1), depth (2), stencil (4) + gx2WriteGather_submitU32AsBE(memory_virtualToPhysical(colorBuffer->surface.imagePtr)); + gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.format.value()); + gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.tileMode.value()); + gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.width); + gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.height); + gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.pitch); + gx2WriteGather_submitU32AsBE(_swapEndianU32(colorBuffer->viewFirstSlice)); + gx2WriteGather_submitU32AsBE(_swapEndianU32(colorBuffer->viewNumSlices)); + gx2WriteGather_submitU32AsBE(memory_virtualToPhysical(depthBuffer->surface.imagePtr)); + gx2WriteGather_submitU32AsBE((uint32)depthBuffer->surface.format.value()); + gx2WriteGather_submitU32AsBE((uint32)depthBuffer->surface.tileMode.value()); + gx2WriteGather_submitU32AsBE((uint32)depthBuffer->surface.width); + gx2WriteGather_submitU32AsBE((uint32)depthBuffer->surface.height); + gx2WriteGather_submitU32AsBE((uint32)depthBuffer->surface.pitch); + gx2WriteGather_submitU32AsBE(_swapEndianU32(depthBuffer->viewFirstSlice)); + gx2WriteGather_submitU32AsBE(_swapEndianU32(depthBuffer->viewNumSlices)); + + gx2WriteGather_submitU32AsBE((uint32)(r * 255.0f)); + gx2WriteGather_submitU32AsBE((uint32)(g * 255.0f)); + gx2WriteGather_submitU32AsBE((uint32)(b * 255.0f)); + gx2WriteGather_submitU32AsBE((uint32)(a * 255.0f)); + + gx2WriteGather_submitU32AsBE(*(uint32*)&depthClearValue); // clear depth + gx2WriteGather_submitU32AsBE(stencilClearValue&0xFF); // clear stencil + } + + // always uses passed depthClearValue/stencilClearValue for clearing, even if clear flags dont specify value updates + void GX2ClearDepthStencilEx(GX2DepthBuffer* depthBuffer, float depthClearValue, uint8 stencilClearValue, GX2ClearFlags clearFlags) + { + GX2ReserveCmdSpace(50); + + if (!depthBuffer && (depthBuffer->surface.width == 0 || depthBuffer->surface.height == 0)) + { + // Super Smash Bros tries to clear an uninitialized depth surface? + debug_printf("GX2ClearDepthStencilEx(): Attempting to clear invalid depthbuffer\n"); + return; + } + + _updateDepthStencilClearRegs(depthClearValue, stencilClearValue, clearFlags); + + uint32 hleClearFlags = 0; + if ((clearFlags & GX2ClearFlags::CLEAR_DEPTH) != 0) + hleClearFlags |= 2; + if ((clearFlags & GX2ClearFlags::CLEAR_STENCIL) != 0) + hleClearFlags |= 4; + + // send command to clear color, depth and stencil + if (hleClearFlags != 0) + { + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_CLEAR_COLOR_DEPTH_STENCIL, 23)); + gx2WriteGather_submitU32AsBE(hleClearFlags); // color (1), depth (2), stencil (4) + gx2WriteGather_submitU32AsBE(MPTR_NULL); + gx2WriteGather_submitU32AsBE(0); // format for color buffer + gx2WriteGather_submitU32AsBE(0); // tilemode for color buffer + gx2WriteGather_submitU32AsBE(0); + gx2WriteGather_submitU32AsBE(0); + gx2WriteGather_submitU32AsBE(0); + gx2WriteGather_submitU32AsBE(0); + gx2WriteGather_submitU32AsBE(0); + gx2WriteGather_submitU32AsBE(memory_virtualToPhysical(depthBuffer->surface.imagePtr)); + gx2WriteGather_submitU32AsBE((uint32)depthBuffer->surface.format.value()); + gx2WriteGather_submitU32AsBE((uint32)depthBuffer->surface.tileMode.value()); + gx2WriteGather_submitU32AsBE((uint32)depthBuffer->surface.width); + gx2WriteGather_submitU32AsBE((uint32)depthBuffer->surface.height); + gx2WriteGather_submitU32AsBE((uint32)depthBuffer->surface.pitch); + gx2WriteGather_submitU32AsBE(_swapEndianU32(depthBuffer->viewFirstSlice)); + gx2WriteGather_submitU32AsBE(_swapEndianU32(depthBuffer->viewNumSlices)); + gx2WriteGather_submitU32AsBE(0); + gx2WriteGather_submitU32AsBE(0); + gx2WriteGather_submitU32AsBE(0); + gx2WriteGather_submitU32AsBE(0); + + gx2WriteGather_submitU32AsBE(*(uint32*)&depthClearValue); // clear depth + gx2WriteGather_submitU32AsBE(stencilClearValue & 0xFF); // clear stencil + } + } + + void GX2BlitInit() + { + cafeExportRegister("gx2", GX2SetClearDepthStencil, LogType::GX2); + cafeExportRegister("gx2", GX2SetClearDepth, LogType::GX2); + cafeExportRegister("gx2", GX2SetClearStencil, LogType::GX2); + + cafeExportRegister("gx2", GX2ClearColor, LogType::GX2); + cafeExportRegister("gx2", GX2ClearBuffersEx, LogType::GX2); + cafeExportRegister("gx2", GX2ClearDepthStencilEx, LogType::GX2); + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/gx2/GX2_Blit.h b/src/Cafe/OS/libs/gx2/GX2_Blit.h new file mode 100644 index 00000000..f8256f51 --- /dev/null +++ b/src/Cafe/OS/libs/gx2/GX2_Blit.h @@ -0,0 +1,15 @@ +#pragma once + +namespace GX2 +{ + enum class GX2ClearFlags : uint32 + { + CLEAR_DEPTH = 0x01, // clear depth to given clear value + CLEAR_STENCIL = 0x02, // clear stencil to given stencil clear value + SET_DEPTH_REG = 0x04, // + SET_STENCIL_REG = 0x08, + }; + + void GX2BlitInit(); +} +ENABLE_BITMASK_OPERATORS(GX2::GX2ClearFlags); \ No newline at end of file diff --git a/src/Cafe/OS/libs/gx2/GX2_Command.cpp b/src/Cafe/OS/libs/gx2/GX2_Command.cpp new file mode 100644 index 00000000..0f4f1343 --- /dev/null +++ b/src/Cafe/OS/libs/gx2/GX2_Command.cpp @@ -0,0 +1,308 @@ +#include "Cafe/HW/Latte/Core/Latte.h" +#include "Cafe/HW/Latte/Core/LatteDraw.h" +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/HW/Latte/Core/LattePM4.h" +#include "Cafe/OS/libs/coreinit/coreinit.h" +#include "Cafe/HW/Latte/ISA/RegDefines.h" +#include "GX2.h" +#include "GX2_Command.h" +#include "GX2_Shader.h" +#include "GX2_Misc.h" + +extern uint8* gxRingBufferReadPtr; + +GX2WriteGatherPipeState gx2WriteGatherPipe = { 0 }; + +void gx2WriteGather_submitU32AsBE(uint32 v) +{ + uint32 coreIndex = PPCInterpreter_getCoreIndex(ppcInterpreterCurrentInstance); + if (gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex] == NULL) + return; + *(uint32*)(*gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex]) = _swapEndianU32(v); + (*gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex]) += 4; +} + +void gx2WriteGather_submitU32AsLE(uint32 v) +{ + uint32 coreIndex = PPCInterpreter_getCoreIndex(ppcInterpreterCurrentInstance); + if (gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex] == NULL) + return; + *(uint32*)(*gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex]) = v; + (*gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex]) += 4; +} + +void gx2WriteGather_submitU32AsLEArray(uint32* v, uint32 numValues) +{ + uint32 coreIndex = PPCInterpreter_getCoreIndex(ppcInterpreterCurrentInstance); + if (gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex] == NULL) + return; + memcpy_dwords((*gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex]), v, numValues); + (*gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex]) += 4 * numValues; +} + +namespace GX2 +{ + sint32 gx2WriteGatherCurrentMainCoreIndex = -1; + bool gx2WriteGatherInited = false; + + void GX2Init_writeGather() // init write gather, make current core + { + if (gx2WriteGatherPipe.gxRingBuffer == NULL) + gx2WriteGatherPipe.gxRingBuffer = (uint8*)malloc(GX2_COMMAND_RING_BUFFER_SIZE); + if (gx2WriteGatherCurrentMainCoreIndex == sGX2MainCoreIndex) + return; // write gather already configured for same core + for (sint32 i = 0; i < PPC_CORE_COUNT; i++) + { + if (i == sGX2MainCoreIndex) + { + gx2WriteGatherPipe.writeGatherPtrGxBuffer[i] = gx2WriteGatherPipe.gxRingBuffer; + gx2WriteGatherPipe.writeGatherPtrWrite[i] = &gx2WriteGatherPipe.writeGatherPtrGxBuffer[i]; + } + else + { + gx2WriteGatherPipe.writeGatherPtrGxBuffer[i] = NULL; + gx2WriteGatherPipe.writeGatherPtrWrite[i] = NULL; + } + gx2WriteGatherPipe.displayListStart[i] = MPTR_NULL; + gx2WriteGatherPipe.writeGatherPtrDisplayList[i] = NULL; + gx2WriteGatherPipe.displayListMaxSize[i] = 0; + } + gx2WriteGatherCurrentMainCoreIndex = sGX2MainCoreIndex; + gx2WriteGatherInited = true; + } + + void GX2WriteGather_beginDisplayList(PPCInterpreter_t* hCPU, MPTR buffer, uint32 maxSize) + { + uint32 coreIndex = PPCInterpreter_getCoreIndex(hCPU); + gx2WriteGatherPipe.displayListStart[coreIndex] = buffer; + gx2WriteGatherPipe.displayListMaxSize[coreIndex] = maxSize; + // set new write gather ptr + gx2WriteGatherPipe.writeGatherPtrDisplayList[coreIndex] = memory_getPointerFromVirtualOffset(gx2WriteGatherPipe.displayListStart[coreIndex]); + gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex] = &gx2WriteGatherPipe.writeGatherPtrDisplayList[coreIndex]; + } + + uint32 GX2WriteGather_getDisplayListWriteDistance(sint32 coreIndex) + { + return (uint32)(*gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex] - memory_getPointerFromVirtualOffset(gx2WriteGatherPipe.displayListStart[coreIndex])); + } + + uint32 GX2WriteGather_getFifoWriteDistance(uint32 coreIndex) + { + uint32 writeDistance = (uint32)(gx2WriteGatherPipe.writeGatherPtrGxBuffer[coreIndex] - gx2WriteGatherPipe.gxRingBuffer); + return writeDistance; + } + + uint32 GX2WriteGather_endDisplayList(PPCInterpreter_t* hCPU, MPTR buffer) + { + uint32 coreIndex = PPCInterpreter_getCoreIndex(hCPU); + if (gx2WriteGatherPipe.displayListStart[coreIndex] != MPTR_NULL) + { + uint32 currentWriteSize = GX2WriteGather_getDisplayListWriteDistance(coreIndex); + // pad to 32 byte + if (gx2WriteGatherPipe.displayListMaxSize[coreIndex] >= ((gx2WriteGatherPipe.displayListMaxSize[coreIndex] + 0x1F) & ~0x1F)) + { + while ((currentWriteSize & 0x1F) != 0) + { + gx2WriteGather_submitU32AsBE(pm4HeaderType2Filler()); + currentWriteSize += 4; + } + } + // get size of written data + currentWriteSize = GX2WriteGather_getDisplayListWriteDistance(coreIndex); + // disable current display list and restore write gather ptr + gx2WriteGatherPipe.displayListStart[coreIndex] = MPTR_NULL; + if (sGX2MainCoreIndex == coreIndex) + gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex] = &gx2WriteGatherPipe.writeGatherPtrGxBuffer[coreIndex]; + else + gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex] = NULL; + // return size of (written) display list + return currentWriteSize; + } + else + { + // no active display list + // return a size of 0 + return 0; + } + } + + bool GX2GetCurrentDisplayList(betype<MPTR>* displayListAddr, uint32be* displayListSize) + { + uint32 coreIndex = PPCInterpreter_getCoreIndex(ppcInterpreterCurrentInstance); + if (gx2WriteGatherPipe.displayListStart[coreIndex] == MPTR_NULL) + return false; + + if (displayListAddr) + *displayListAddr = gx2WriteGatherPipe.displayListStart[coreIndex]; + if (displayListSize) + *displayListSize = gx2WriteGatherPipe.displayListMaxSize[coreIndex]; + + return true; + } + + bool GX2GetDisplayListWriteStatus() + { + // returns true if we are writing to a display list + uint32 coreIndex = PPCInterpreter_getCoreIndex(ppcInterpreterCurrentInstance); + return gx2WriteGatherPipe.displayListStart[coreIndex] != MPTR_NULL; + } + + bool GX2WriteGather_isDisplayListActive() + { + uint32 coreIndex = PPCInterpreter_getCoreIndex(ppcInterpreterCurrentInstance); + if (gx2WriteGatherPipe.displayListStart[coreIndex] != MPTR_NULL) + return true; + return false; + } + + uint32 GX2WriteGather_getReadWriteDistance() + { + uint32 coreIndex = sGX2MainCoreIndex; + uint32 writeDistance = (uint32)(gx2WriteGatherPipe.writeGatherPtrGxBuffer[coreIndex] + GX2_COMMAND_RING_BUFFER_SIZE - gxRingBufferReadPtr); + writeDistance %= GX2_COMMAND_RING_BUFFER_SIZE; + return writeDistance; + } + + void GX2WriteGather_checkAndInsertWrapAroundMark() + { + uint32 coreIndex = PPCInterpreter_getCoreIndex(ppcInterpreterCurrentInstance); + if (coreIndex != sGX2MainCoreIndex) // only if main gx2 core + return; + if (gx2WriteGatherPipe.displayListStart[coreIndex] != MPTR_NULL) + return; + uint32 writeDistance = GX2WriteGather_getFifoWriteDistance(coreIndex); + if (writeDistance >= (GX2_COMMAND_RING_BUFFER_SIZE * 3 / 5)) + { + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_FIFO_WRAP_AROUND, 1)); + gx2WriteGather_submitU32AsBE(0); // empty word since we can't send commands with zero data words + gx2WriteGatherPipe.writeGatherPtrGxBuffer[coreIndex] = gx2WriteGatherPipe.gxRingBuffer; + } + } + + void GX2BeginDisplayList(MEMPTR<void> displayListAddr, uint32 size) + { + GX2WriteGather_beginDisplayList(ppcInterpreterCurrentInstance, displayListAddr.GetMPTR(), size); + } + + void GX2BeginDisplayListEx(MEMPTR<void> displayListAddr, uint32 size, bool profiling) + { + GX2WriteGather_beginDisplayList(ppcInterpreterCurrentInstance, displayListAddr.GetMPTR(), size); + } + + uint32 GX2EndDisplayList(MEMPTR<void> displayListAddr) + { + cemu_assert_debug(displayListAddr != nullptr); + uint32 displayListSize = GX2WriteGather_endDisplayList(ppcInterpreterCurrentInstance, displayListAddr.GetMPTR()); + return displayListSize; + } + + void GX2CallDisplayList(MPTR addr, uint32 size) + { + cemu_assert_debug((size&3) == 0); + // write PM4 command + GX2ReserveCmdSpace(4); + gx2WriteGather_submit(pm4HeaderType3(IT_INDIRECT_BUFFER_PRIV, 3), + memory_virtualToPhysical(addr), + 0, // high address bits + size / 4); + GX2::GX2WriteGather_checkAndInsertWrapAroundMark(); + } + + void GX2DirectCallDisplayList(void* addr, uint32 size) + { + // this API submits to TCL directly and bypasses write-gatherer + // its basically a way to manually submit a command buffer to the GPU + // as such it also affects the submission and retire timestamps + + uint32 coreIndex = PPCInterpreter_getCoreIndex(ppcInterpreterCurrentInstance); + cemu_assert_debug(coreIndex == sGX2MainCoreIndex); + coreIndex = sGX2MainCoreIndex; // always submit to main queue which is owned by GX2 main core (TCLSubmitToRing does not need this workaround) + + uint32be* cmdStream = (uint32be*)(gx2WriteGatherPipe.writeGatherPtrGxBuffer[coreIndex]); + cmdStream[0] = pm4HeaderType3(IT_INDIRECT_BUFFER_PRIV, 3); + cmdStream[1] = memory_virtualToPhysical(MEMPTR<void>(addr).GetMPTR()); + cmdStream[2] = 0; + cmdStream[3] = size / 4; + gx2WriteGatherPipe.writeGatherPtrGxBuffer[coreIndex] += 16; + + // update submission timestamp and retired timestamp + _GX2SubmitToTCL(); + } + + void GX2CopyDisplayList(MEMPTR<uint32be*> addr, uint32 size) + { + // copy display list to write gather + uint32* displayListDWords = (uint32*)addr.GetPtr(); + uint32 dwordCount = size / 4; + if (dwordCount > 0) + { + GX2ReserveCmdSpace(dwordCount); + gx2WriteGather_submitU32AsLEArray(displayListDWords, dwordCount); + } + } + + enum class GX2_PATCH_TYPE : uint32 + { + FETCH_SHADER = 1, + VERTEX_SHADER = 2, + GEOMETRY_COPY_SHADER = 3, + GEOMETRY_SHADER = 4, + PIXEL_SHADER = 5, + COMPUTE_SHADER = 6 + }; + + void GX2PatchDisplayList(uint32be* displayData, GX2_PATCH_TYPE patchType, uint32 patchOffset, void* obj) + { + cemu_assert_debug((patchOffset & 3) == 0); + + if (patchType == GX2_PATCH_TYPE::VERTEX_SHADER) + { + GX2VertexShader_t* vertexShader = (GX2VertexShader_t*)obj; + displayData[patchOffset / 4 + 2] = memory_virtualToPhysical(vertexShader->GetProgramAddr()) >> 8; + } + else if (patchType == GX2_PATCH_TYPE::PIXEL_SHADER) + { + GX2PixelShader_t* pixelShader = (GX2PixelShader_t*)obj; + displayData[patchOffset / 4 + 2] = memory_virtualToPhysical(pixelShader->GetProgramAddr()) >> 8; + } + else if (patchType == GX2_PATCH_TYPE::FETCH_SHADER) + { + GX2FetchShader_t* fetchShader = (GX2FetchShader_t*)obj; + displayData[patchOffset / 4 + 2] = memory_virtualToPhysical(fetchShader->GetProgramAddr()) >> 8; + } + else if (patchType == GX2_PATCH_TYPE::GEOMETRY_COPY_SHADER) + { + GX2GeometryShader_t* geometryShader = (GX2GeometryShader_t*)obj; + displayData[patchOffset / 4 + 2] = memory_virtualToPhysical(geometryShader->GetCopyProgramAddr()) >> 8; + } + else if (patchType == GX2_PATCH_TYPE::GEOMETRY_SHADER) + { + GX2GeometryShader_t* geometryShader = (GX2GeometryShader_t*)obj; + displayData[patchOffset / 4 + 2] = memory_virtualToPhysical(geometryShader->GetGeometryProgramAddr()) >> 8; + } + else + { + forceLog_printf("GX2PatchDisplayList(): unsupported patchType %d", (uint32)patchType); + cemu_assert_debug(false); + } + } + + void GX2CommandInit() + { + + cafeExportRegister("gx2", GX2BeginDisplayList, LogType::GX2); + cafeExportRegister("gx2", GX2BeginDisplayListEx, LogType::GX2); + cafeExportRegister("gx2", GX2EndDisplayList, LogType::GX2); + + cafeExportRegister("gx2", GX2GetCurrentDisplayList, LogType::GX2); + cafeExportRegister("gx2", GX2GetDisplayListWriteStatus, LogType::GX2); + + + cafeExportRegister("gx2", GX2CallDisplayList, LogType::GX2); + cafeExportRegister("gx2", GX2DirectCallDisplayList, LogType::GX2); + cafeExportRegister("gx2", GX2CopyDisplayList, LogType::GX2); + + cafeExportRegister("gx2", GX2PatchDisplayList, LogType::GX2); + } + +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/gx2/GX2_Command.h b/src/Cafe/OS/libs/gx2/GX2_Command.h new file mode 100644 index 00000000..a8d3671f --- /dev/null +++ b/src/Cafe/OS/libs/gx2/GX2_Command.h @@ -0,0 +1,101 @@ +#pragma once +#include "Cafe/HW/Latte/ISA/LatteReg.h" +#include "Cafe/HW/Espresso/Const.h" + +struct GX2WriteGatherPipeState +{ + uint8* gxRingBuffer; + // each core has it's own write gatherer and display list state (writing) + uint8* writeGatherPtrGxBuffer[Espresso::CORE_COUNT]; + uint8** writeGatherPtrWrite[Espresso::CORE_COUNT]; + uint8* writeGatherPtrDisplayList[Espresso::CORE_COUNT]; + MPTR displayListStart[Espresso::CORE_COUNT]; + uint32 displayListMaxSize[Espresso::CORE_COUNT]; +}; + +extern GX2WriteGatherPipeState gx2WriteGatherPipe; + +void GX2ReserveCmdSpace(uint32 reservedFreeSpaceInU32); // move to GX2 namespace eventually + +void gx2WriteGather_submitU32AsBE(uint32 v); +void gx2WriteGather_submitU32AsLE(uint32 v); +void gx2WriteGather_submitU32AsLEArray(uint32* v, uint32 numValues); + +uint32 PPCInterpreter_getCurrentCoreIndex(); + +// gx2WriteGather_submit functions +template <typename ...Targs> +inline void gx2WriteGather_submit_(uint32 coreIndex, uint32be* writePtr) +{ + (*gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex]) = (uint8*)writePtr; +} + +template <typename T, typename ...Targs> +inline void gx2WriteGather_submit_(uint32 coreIndex, uint32be* writePtr, const betype<T>& arg, Targs... args) +{ + static_assert(sizeof(betype<T>) == sizeof(uint32be)); + *(betype<T>*)writePtr = arg; + writePtr++; + gx2WriteGather_submit_(coreIndex, writePtr, args...); +} + +template <typename T, typename ...Targs> +inline +typename std::enable_if< std::is_floating_point<T>::value, void>::type +gx2WriteGather_submit_(uint32 coreIndex, uint32be* writePtr, const T& arg, Targs... args) +{ + static_assert(sizeof(T) == sizeof(uint32)); + *writePtr = *(uint32*)&arg; + writePtr++; + gx2WriteGather_submit_(coreIndex, writePtr, args...); +} + +template <typename T, typename ...Targs> +inline +typename std::enable_if< std::is_base_of<Latte::LATTEREG, T>::value, void>::type +gx2WriteGather_submit_(uint32 coreIndex, uint32be* writePtr, const T& arg, Targs... args) +{ + static_assert(sizeof(Latte::LATTEREG) == sizeof(uint32be)); + *writePtr = arg.getRawValue(); + writePtr++; + gx2WriteGather_submit_(coreIndex, writePtr, args...); +} + +template <typename T, typename ...Targs> +inline +typename std::enable_if< !std::is_base_of<Latte::LATTEREG, T>::value && !std::is_floating_point<T>::value, void>::type +gx2WriteGather_submit_(uint32 coreIndex, uint32be* writePtr, const T& arg, Targs... args) +{ + *writePtr = arg; + writePtr++; + gx2WriteGather_submit_(coreIndex, writePtr, args...); +} + +template <typename ...Targs> +inline void gx2WriteGather_submit(Targs... args) +{ + uint32 coreIndex = PPCInterpreter_getCurrentCoreIndex(); + if (gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex] == nullptr) + return; + + uint32be* writePtr = (uint32be*)(*gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex]); + gx2WriteGather_submit_(coreIndex, writePtr, std::forward<Targs>(args)...); +} + +namespace GX2 +{ + + bool GX2WriteGather_isDisplayListActive(); + uint32 GX2WriteGather_getReadWriteDistance(); + void GX2WriteGather_checkAndInsertWrapAroundMark(); + + void GX2BeginDisplayList(MEMPTR<void> displayListAddr, uint32 size); + void GX2BeginDisplayListEx(MEMPTR<void> displayListAddr, uint32 size, bool profiling); + uint32 GX2EndDisplayList(MEMPTR<void> displayListAddr); + + void GX2CallDisplayList(MPTR addr, uint32 size); + void GX2DirectCallDisplayList(void* addr, uint32 size); + + void GX2Init_writeGather(); + void GX2CommandInit(); +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/gx2/GX2_ContextState.cpp b/src/Cafe/OS/libs/gx2/GX2_ContextState.cpp new file mode 100644 index 00000000..5650999f --- /dev/null +++ b/src/Cafe/OS/libs/gx2/GX2_ContextState.cpp @@ -0,0 +1,392 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/HW/Latte/ISA/RegDefines.h" +#include "GX2.h" +#include "Cafe/HW/Latte/Core/Latte.h" +#include "Cafe/HW/Latte/Core/LatteDraw.h" + +#include "Cafe/HW/Latte/Core/LattePM4.h" + +#include "GX2_Command.h" +#include "GX2_State.h" +#include "Cafe/CafeSystem.h" + +#define GPU7_REG_AREA_SIZE_CONFIG_REG 0xB00 +#define GPU7_REG_AREA_SIZE_CONTEXT_REG 0x400 +#define GPU7_REG_AREA_SIZE_ALU_CONST 0x800 +#define GPU7_REG_AREA_SIZE_LOOP_CONST 0x60 +#define GPU7_REG_AREA_SIZE_RESOURCE 0xD9E +#define GPU7_REG_AREA_SIZE_SAMPLER 0xA2 // (guessed) + +#define _GX2_CALC_SHADOWMEM_NUM_U32(__v) (((((__v)*4)+0xFF)&~0xFF)/4) + +MPTR gx2CurrentContextStateMPTR = MPTR_NULL; + +typedef struct +{ + uint32 regOffset; + uint32 regCount; +}GX2RegLoadPktEntry_t; + +GX2RegLoadPktEntry_t aluConst_loadPktEntries[1] = // base: 0xC000 +{ + {0, 0x800}, +}; + +GX2RegLoadPktEntry_t loopConst_loadPktEntries[1] = // base: 0xF880 +{ + {0, 0x60}, +}; + +GX2RegLoadPktEntry_t samplerReg_loadPktEntries[3] = // base: 0xF000 +{ + {0, 0x36}, + {0x36, 0x36}, + {0x6C, 0x36}, +}; + +GX2RegLoadPktEntry_t configReg_loadPktEntries[0xF] = // base: 0x2000 +{ + {0x300, 0x6}, + {0x900, 0x48}, + {0x980, 0x48}, + {0xA00, 0x48}, + {0x310, 0xC}, + {0x542, 0x1}, + {0x235, 0x1}, + {0x232, 0x2}, + {0x23A, 0x1}, + {0x256, 0x1}, + {0x60C, 0x1}, + {0x5C5, 0x1}, + {0x2C8, 0x1}, + {0x363, 0x1}, + {0x404, 0x2} +}; + +GX2RegLoadPktEntry_t contextReg_loadPktEntries[0x2D] = // base: 0xA000 +{ + {0x0, 0x2}, + {0x3, 0x3}, + {0xA, 0x4}, + {0x10, 0x38}, + {0x50, 0x34}, + {0x8E, 0x4}, + {0x94, 0x40}, + {0x100, 0x9}, + {0x10C, 0x3}, + {0x10F, 0x60}, + {0x185, 0xA}, + {0x191, 0x27}, + {0x1E0, 0x9}, + {0x200, 0x1}, + {0x202, 0x7}, + {0xE0, 0x20}, + {0x210, 0x29}, + {0x250, 0x34}, + {0x290, 0x1}, + {0x292, 0x2}, + {0x2A1, 0x1}, + {0x2A5, 0x1}, + {0x2A8, 0x2}, + {0x2AC, 0x3}, + {0x2CA, 0x1}, + {0x2CC, 0x1}, + {0x2CE, 0x1}, + {0x300, 0x9}, + {0x30C, 0x1}, + {0x312, 0x1}, + {0x316, 0x2}, + {0x343, 0x2}, + {0x349, 0x3}, + {0x34C, 0x2}, + {0x351, 0x1}, + {0x37E, 0x6}, + {0x2B4, 0x3}, + {0x2B8, 0x3}, + {0x2BC, 0x3}, + {0x2C0, 0x3}, + {0x2C8, 0x1}, + {0x29B, 0x1}, + {0x8C, 0x1}, + {0xD5, 0x1}, + {0x284, 0xC} +}; + +GX2RegLoadPktEntry_t resourceReg_loadPktEntries[9] = // base: 0xE000 +{ + {0, 0x70}, // ps tex + {0x380, 0x70}, + {0x460, 0x70}, // vs tex + {0x7E0, 0x70}, + {0x8B9, 0x7}, + {0x8C0, 0x70}, + {0x930, 0x70}, // gs tex + {0xCB0, 0x70}, + {0xD89, 0x7} +}; + +typedef struct +{ + // Hardware view of context state (register areas) + uint32 areaConfigReg[_GX2_CALC_SHADOWMEM_NUM_U32(GPU7_REG_AREA_SIZE_CONFIG_REG)]; + uint32 areaContextReg[_GX2_CALC_SHADOWMEM_NUM_U32(GPU7_REG_AREA_SIZE_CONTEXT_REG)]; + uint32 areaALUConst[_GX2_CALC_SHADOWMEM_NUM_U32(GPU7_REG_AREA_SIZE_ALU_CONST)]; + uint32 areaLoopConst[_GX2_CALC_SHADOWMEM_NUM_U32(GPU7_REG_AREA_SIZE_LOOP_CONST)]; + uint32 areaResource[_GX2_CALC_SHADOWMEM_NUM_U32(GPU7_REG_AREA_SIZE_RESOURCE)]; + uint32 areaSampler[_GX2_CALC_SHADOWMEM_NUM_U32(GPU7_REG_AREA_SIZE_SAMPLER)]; +}GX2HwContextState_t; + +typedef struct +{ + GX2HwContextState_t hwContext; + uint32 enableProfling; + /* + 0x9804 */ uint32be loadDL_size; + uint8 ukn9808[0x3FC-4]; + uint8 ukn9C00[0x200]; + /* +0x9E00 */ uint8 loadDL_buffer[0x300]; // this displaylist caches the IT_LOAD_* commands for swapping out context +}GX2ContextState_t; + +static_assert(offsetof(GX2ContextState_t, loadDL_size) == 0x9804); +static_assert(sizeof(GX2ContextState_t) == 0xA100); + +uint32 _GX2Context_CalcShadowMemSize(uint32 regCount) +{ + return (regCount*4+0xFF)&~0xFF; +} + +uint32 _GX2Context_CalcStateSize() +{ + uint32 contextStateSize = 0; + contextStateSize += _GX2Context_CalcShadowMemSize(GPU7_REG_AREA_SIZE_CONFIG_REG); + contextStateSize += _GX2Context_CalcShadowMemSize(GPU7_REG_AREA_SIZE_CONTEXT_REG); + contextStateSize += _GX2Context_CalcShadowMemSize(GPU7_REG_AREA_SIZE_ALU_CONST); + contextStateSize += _GX2Context_CalcShadowMemSize(GPU7_REG_AREA_SIZE_LOOP_CONST); + contextStateSize += _GX2Context_CalcShadowMemSize(GPU7_REG_AREA_SIZE_RESOURCE); + contextStateSize += _GX2Context_CalcShadowMemSize(GPU7_REG_AREA_SIZE_SAMPLER); + return contextStateSize; +} + +void _GX2Context_CreateLoadDL() +{ + GX2ReserveCmdSpace(3); + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_CONTEXT_CONTROL, 2)); + gx2WriteGather_submitU32AsBE(0x80000077); + gx2WriteGather_submitU32AsBE(0x80000077); +} + +void _GX2Context_WriteCmdDisableStateShadowing() +{ + GX2ReserveCmdSpace(3); + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_CONTEXT_CONTROL, 2)); + gx2WriteGather_submitU32AsBE(0x80000000); + gx2WriteGather_submitU32AsBE(0x80000000); +} + +void _GX2Context_cmdLoad(void* gx2ukn, uint32 pm4Header, MPTR physAddrRegArea, uint32 waitForIdle, uint32 numRegOffsetEntries, GX2RegLoadPktEntry_t* regOffsetEntries) +{ + GX2ReserveCmdSpace(3 + numRegOffsetEntries*2); + gx2WriteGather_submitU32AsBE(pm4Header); + gx2WriteGather_submitU32AsBE(physAddrRegArea); + gx2WriteGather_submitU32AsBE(waitForIdle); + for(uint32 i=0; i<numRegOffsetEntries; i++) + { + gx2WriteGather_submitU32AsBE(regOffsetEntries[i].regOffset); // regOffset + gx2WriteGather_submitU32AsBE(regOffsetEntries[i].regCount); // regCount + } +} + +#define __cmdStateLoad(__gx2State, __pm4Command, __regArea, __waitForIdle, __regLoadPktEntries) _GX2Context_cmdLoad(NULL, pm4HeaderType3(__pm4Command, 2+sizeof(__regLoadPktEntries)/sizeof(__regLoadPktEntries[0])*2), memory_virtualToPhysical(memory_getVirtualOffsetFromPointer(__regArea)), __waitForIdle, sizeof(__regLoadPktEntries)/sizeof(__regLoadPktEntries[0]), __regLoadPktEntries) + +void _GX2Context_WriteCmdRestoreState(GX2ContextState_t* gx2ContextState, uint32 ukn) +{ + GX2::GX2WriteGather_checkAndInsertWrapAroundMark(); + MPTR physAddrContextState = memory_virtualToPhysical(memory_getVirtualOffsetFromPointer(gx2ContextState)); + _GX2Context_CreateLoadDL(); + __cmdStateLoad(NULL, IT_LOAD_CONFIG_REG, gx2ContextState->hwContext.areaConfigReg, 0x80000000, configReg_loadPktEntries); + __cmdStateLoad(NULL, IT_LOAD_CONTEXT_REG, gx2ContextState->hwContext.areaContextReg, 0, contextReg_loadPktEntries); + __cmdStateLoad(NULL, IT_LOAD_ALU_CONST, gx2ContextState->hwContext.areaALUConst, 0, aluConst_loadPktEntries); + __cmdStateLoad(NULL, IT_LOAD_LOOP_CONST, gx2ContextState->hwContext.areaLoopConst, 0, loopConst_loadPktEntries); + __cmdStateLoad(NULL, IT_LOAD_RESOURCE, gx2ContextState->hwContext.areaResource, 0, resourceReg_loadPktEntries); + __cmdStateLoad(NULL, IT_LOAD_SAMPLER, gx2ContextState->hwContext.areaSampler, 0, samplerReg_loadPktEntries); +} + +void GX2SetDefaultState() +{ + GX2ReserveCmdSpace(0x100); + + Latte::LATTE_PA_CL_VTE_CNTL reg{}; + reg.set_VPORT_X_OFFSET_ENA(true).set_VPORT_X_SCALE_ENA(true); + reg.set_VPORT_Y_OFFSET_ENA(true).set_VPORT_Y_SCALE_ENA(true); + reg.set_VPORT_Z_OFFSET_ENA(true).set_VPORT_Z_SCALE_ENA(true); + reg.set_VTX_W0_FMT(true); + gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), + Latte::REGADDR::PA_CL_VTE_CNTL - 0xA000, + reg); + + uint32 stencilTestEnable = GX2_FALSE; + uint32 backStencilEnable = GX2_FALSE; + uint32 frontStencilFunc = 0; + uint32 frontStencilZPass = 0; + uint32 frontStencilZFail = 0; + uint32 frontStencilFail = 0; + uint32 backStencilFunc = 0; + uint32 backStencilZPass = 0; + uint32 backStencilZFail = 0; + uint32 backStencilFail = 0; + + uint32 depthControlReg = 0; + // depth stuff + depthControlReg |= (1<<1); + depthControlReg |= (1<<2); + depthControlReg |= ((1&7)<<4); + // stencil stuff + depthControlReg |= ((stencilTestEnable&1)<<0); + depthControlReg |= ((backStencilEnable&1)<<7); + depthControlReg |= ((frontStencilFunc&7)<<8); + depthControlReg |= ((frontStencilZPass&7)<<14); + depthControlReg |= ((frontStencilZFail&7)<<17); + depthControlReg |= ((frontStencilFail&7)<<11); + depthControlReg |= ((backStencilFunc&7)<<20); + depthControlReg |= ((backStencilZPass&7)<<26); + depthControlReg |= ((backStencilZFail&3)<<29); + depthControlReg |= ((backStencilFail&7)<<23); + + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 1+1)); + gx2WriteGather_submitU32AsBE(Latte::REGADDR::DB_DEPTH_CONTROL-0xA000); + gx2WriteGather_submitU32AsBE(depthControlReg); + + GX2::GX2SetAlphaTest(GX2_DISABLE, GX2::GX2_ALPHAFUNC::LESS, 0.0f); + GX2::GX2SetPolygonControl(Latte::LATTE_PA_SU_SC_MODE_CNTL::E_FRONTFACE::CCW, GX2_DISABLE, GX2_DISABLE, Latte::LATTE_PA_SU_SC_MODE_CNTL::E_POLYGONMODE::UKN0, Latte::LATTE_PA_SU_SC_MODE_CNTL::E_PTYPE::TRIANGLES, Latte::LATTE_PA_SU_SC_MODE_CNTL::E_PTYPE::TRIANGLES, GX2_DISABLE, GX2_DISABLE, GX2_DISABLE); + GX2::GX2SetPolygonOffset(0.0f, 0.0f, 0.0f, 0.0f, 0.0f); + GX2::GX2SetPrimitiveRestartIndex(0xffffffff); + GX2::GX2SetTargetChannelMasks(0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF, 0xF); + GX2::GX2SetBlendConstantColor(0.0f, 0.0f, 0.0f, 0.0f); + GX2::GX2SetPointSize(1.0f, 1.0f); + GX2::GX2SetPointLimits(1.0f, 1.0f); + GX2::GX2SetColorControl(GX2::GX2_LOGICOP::COPY, GX2_DISABLE, GX2_DISABLE, GX2_ENABLE); + GX2::GX2SetRasterizerClipControlEx(true, true, false); + + // Set clear depth to 1.0 (workaround for Darksiders 2. Investigate whether actual GX2 driver also sets this) + float depth = 1.0; + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 1+1)); + gx2WriteGather_submitU32AsBE(mmDB_DEPTH_CLEAR - 0xA000); + gx2WriteGather_submitU32AsBE(*(uint32*)&depth); // depth (as float) + + // reset HLE special states + for (sint32 i = 0; i <= GX2_SPECIAL_STATE_COUNT; i++) + { + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_SPECIAL_STATE, 2)); + gx2WriteGather_submitU32AsBE(i); // state id + gx2WriteGather_submitU32AsBE(0); // disable + } +} + +void gx2Export_GX2SetDefaultState(PPCInterpreter_t* hCPU) +{ + gx2Log_printf("GX2SetDefaultState()"); + GX2SetDefaultState(); + osLib_returnFromFunction(hCPU, 0); +} + +void _GX2ContextCreateRestoreStateDL(GX2ContextState_t* gx2ContextState) +{ + // begin display list + if (GX2::GX2WriteGather_isDisplayListActive()) + assert_dbg(); + GX2::GX2BeginDisplayList((void*)gx2ContextState->loadDL_buffer, sizeof(gx2ContextState->loadDL_buffer)); + _GX2Context_WriteCmdRestoreState(gx2ContextState, 0); + uint32 displayListSize = GX2::GX2EndDisplayList((void*)gx2ContextState->loadDL_buffer); + gx2ContextState->loadDL_size = displayListSize; +} + +void gx2Export_GX2SetupContextStateEx(PPCInterpreter_t* hCPU) +{ + gx2Log_printf("GX2SetupContextStateEx(0x%08x)\n", hCPU->gpr[3]); + cemu_assert_debug(hCPU->gpr[4] == 0 || hCPU->gpr[4] == 1); + + GX2ContextState_t* gx2ContextState = (GX2ContextState_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); + + uint32 hwContextSize = _GX2Context_CalcStateSize(); + if( hwContextSize != sizeof(GX2HwContextState_t) ) + assert_dbg(); + if( sizeof(GX2HwContextState_t) != 0x9800 ) + assert_dbg(); // GX2 HW context size mismatch + if( sizeof(GX2ContextState_t) != 0xA100 ) + assert_dbg(); // GX2 context size mismatch + + memset(gx2ContextState, 0x00, sizeof(GX2ContextState_t)); + gx2ContextState->enableProfling = _swapEndianU32(hCPU->gpr[4]&1); + _GX2Context_WriteCmdRestoreState(gx2ContextState, 1); + + gx2CurrentContextStateMPTR = hCPU->gpr[3]; + + _GX2Context_CreateLoadDL(); + GX2SetDefaultState(); + _GX2ContextCreateRestoreStateDL(gx2ContextState); + + osLib_returnFromFunction(hCPU, 0); +} + +void gx2Export_GX2SetContextState(PPCInterpreter_t* hCPU) +{ + gx2Log_printf("GX2SetContextState(0x%08x)\n", hCPU->gpr[3]); + // parameters: + if( hCPU->gpr[3] == MPTR_NULL ) + { + // disable state shadowing + _GX2Context_WriteCmdDisableStateShadowing(); + osLib_returnFromFunction(hCPU, 0); + return; + } + // check if context state changed + bool boiWorkaround = CafeSystem::GetRPXHashBase() == 0x6BCD618E; // workaround for a bug in Binding of Isaac to avoid flicker + if( boiWorkaround ) + { + if( hCPU->gpr[3] != gx2CurrentContextStateMPTR ) // dont reload same state + { + GX2ContextState_t* gx2ContextState = (GX2ContextState_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); + _GX2Context_WriteCmdRestoreState(gx2ContextState, 0); + _GX2Context_CreateLoadDL(); + // set new context state + gx2CurrentContextStateMPTR = hCPU->gpr[3]; + } + else + { + // even if it's the same context, make sure state shadowing is enabled. + _GX2Context_CreateLoadDL(); + } + } + else + { + GX2ContextState_t* gx2ContextState = (GX2ContextState_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); + if (gx2ContextState->loadDL_size == 0) + { + _GX2Context_CreateLoadDL(); + _GX2Context_WriteCmdRestoreState(gx2ContextState, 0); + } + else + { + _GX2Context_CreateLoadDL(); + GX2::GX2CallDisplayList(memory_getVirtualOffsetFromPointer(gx2ContextState->loadDL_buffer), gx2ContextState->loadDL_size); + } + // set new context state + gx2CurrentContextStateMPTR = hCPU->gpr[3]; + + } + // todo: Save/restore GX2 special state as well -> handle this by correctly emulating the state + osLib_returnFromFunction(hCPU, 0); +} + + +void gx2Export_GX2GetContextStateDisplayList(PPCInterpreter_t* hCPU) +{ + gx2Log_printf("GX2GetContextStateDisplayList(0x%08x, 0x%08x, 0x%08x)\n", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); + ppcDefineParamStructPtr(gx2ContextState, GX2ContextState_t, 0); + ppcDefineParamU32BEPtr(displayListPtrOut, 1); + ppcDefineParamU32BEPtr(displayListSizeOut, 2); + + *displayListPtrOut = memory_getVirtualOffsetFromPointer(gx2ContextState->loadDL_buffer); + *displayListSizeOut = gx2ContextState->loadDL_size; + + osLib_returnFromFunction(hCPU, 0); +} diff --git a/src/Cafe/OS/libs/gx2/GX2_Draw.cpp b/src/Cafe/OS/libs/gx2/GX2_Draw.cpp new file mode 100644 index 00000000..053b787b --- /dev/null +++ b/src/Cafe/OS/libs/gx2/GX2_Draw.cpp @@ -0,0 +1,231 @@ +#include "Cafe/HW/Latte/ISA/RegDefines.h" +#include "GX2.h" +#include "Cafe/HW/Latte/Core/Latte.h" +#include "Cafe/HW/Latte/Core/LatteDraw.h" +#include "Cafe/HW/Latte/ISA/LatteReg.h" +#include "Cafe/HW/Latte/Core/LattePM4.h" + +#include "Cafe/OS/common/OSCommon.h" + +#include "GX2_Command.h" +#include "GX2_Draw.h" + +namespace GX2 +{ + void GX2SetAttribBuffer(uint32 bufferIndex, uint32 sizeInBytes, uint32 stride, void* data) + { + GX2ReserveCmdSpace(9); + MPTR physicalAddress = memory_virtualToPhysical(memory_getVirtualOffsetFromPointer(data)); + // write PM4 command + gx2WriteGather_submit( + pm4HeaderType3(IT_SET_RESOURCE, 8), + 0x8C0 + bufferIndex * 7, + physicalAddress, + sizeInBytes - 1, // size + (stride & 0xFFFF) << 11, // stride + 0, // ukn + 0, // ukn + 0, // ukn + 0xC0000000); // ukn + } + + void GX2DrawIndexedEx(GX2PrimitiveMode2 primitiveMode, uint32 count, GX2IndexType indexType, void* indexData, uint32 baseVertex, uint32 numInstances) + { + GX2ReserveCmdSpace(3 + 3 + 2 + 2 + 6); + gx2WriteGather_submit( + // IT_SET_CTL_CONST + pm4HeaderType3(IT_SET_CTL_CONST, 2), 0, + baseVertex, + // IT_SET_CONFIG_REG + pm4HeaderType3(IT_SET_CONFIG_REG, 2), Latte::REGADDR::VGT_PRIMITIVE_TYPE - 0x2000, + (uint32)primitiveMode, + // IT_INDEX_TYPE + pm4HeaderType3(IT_INDEX_TYPE, 1), + (uint32)indexType, + // IT_NUM_INSTANCES + pm4HeaderType3(IT_NUM_INSTANCES, 1), + numInstances, + // IT_DRAW_INDEX_2 + pm4HeaderType3(IT_DRAW_INDEX_2, 5) | 0x00000001, + -1, + memory_virtualToPhysical(memory_getVirtualOffsetFromPointer(indexData)), + 0, + count, + 0); + GX2::GX2WriteGather_checkAndInsertWrapAroundMark(); + } + + void GX2DrawIndexedEx2(GX2PrimitiveMode2 primitiveMode, uint32 count, GX2IndexType indexType, void* indexData, uint32 baseVertex, uint32 numInstances, uint32 baseInstance) + { + GX2ReserveCmdSpace(3 + 3 + 2 + 2 + 6); + gx2WriteGather_submit( + // IT_SET_CTL_CONST + pm4HeaderType3(IT_SET_CTL_CONST, 2), 0, + baseVertex, + // set base instance + pm4HeaderType3(IT_SET_CTL_CONST, 2), 1, + baseInstance, + // IT_SET_CONFIG_REG + pm4HeaderType3(IT_SET_CONFIG_REG, 2), Latte::REGADDR::VGT_PRIMITIVE_TYPE - 0x2000, + (uint32)primitiveMode, + // IT_INDEX_TYPE + pm4HeaderType3(IT_INDEX_TYPE, 1), + (uint32)indexType, + // IT_NUM_INSTANCES + pm4HeaderType3(IT_NUM_INSTANCES, 1), + numInstances, + // IT_DRAW_INDEX_2 + pm4HeaderType3(IT_DRAW_INDEX_2, 5) | 0x00000001, + -1, + memory_virtualToPhysical(memory_getVirtualOffsetFromPointer(indexData)), + 0, + count, + 0, + // reset base instance + pm4HeaderType3(IT_SET_CTL_CONST, 2), 1, + 0 // baseInstance + ); + GX2::GX2WriteGather_checkAndInsertWrapAroundMark(); + } + + void GX2DrawEx(GX2PrimitiveMode2 primitiveMode, uint32 count, uint32 baseVertex, uint32 numInstances) + { + GX2ReserveCmdSpace(3 + 3 + 2 + 2 + 6); + gx2WriteGather_submit( + // IT_SET_CTL_CONST + pm4HeaderType3(IT_SET_CTL_CONST, 2), 0, + baseVertex, + // IT_SET_CONFIG_REG + pm4HeaderType3(IT_SET_CONFIG_REG, 2), Latte::REGADDR::VGT_PRIMITIVE_TYPE - 0x2000, + (uint32)primitiveMode, + // IT_INDEX_TYPE + pm4HeaderType3(IT_INDEX_TYPE, 1), + (uint32)GX2IndexType::U32_BE, + // IT_NUM_INSTANCES + pm4HeaderType3(IT_NUM_INSTANCES, 1), + numInstances, + // IT_DRAW_INDEX_2 + pm4HeaderType3(IT_DRAW_INDEX_AUTO, 2) | 0x00000001, + count, + 0 // DRAW_INITIATOR + ); + GX2::GX2WriteGather_checkAndInsertWrapAroundMark(); + } + + void GX2DrawIndexedImmediateEx(GX2PrimitiveMode2 primitiveMode, uint32 count, GX2IndexType indexType, void* indexData, uint32 baseVertex, uint32 numInstances) + { + uint32* indexDataU32 = (uint32*)indexData; + uint32 numIndexU32s; + bool use32BitIndices = false; + if (indexType == GX2IndexType::U16_BE || indexType == GX2IndexType::U16_LE) + { + // 16bit indices + numIndexU32s = (count + 1) / 2; + } + else if (indexType == GX2IndexType::U32_BE || indexType == GX2IndexType::U32_LE) + { + // 32bit indices + numIndexU32s = count; + use32BitIndices = true; + } + else + { + cemu_assert_unimplemented(); + } + + GX2ReserveCmdSpace(3 + 3 + 3 + 2 + 2 + 6 + 3 + numIndexU32s); + + if (numIndexU32s > 0x4000 - 2) + { + cemuLog_log(LogType::Force, "GX2DrawIndexedImmediateEx(): Draw exceeds maximum PM4 command size. Keep index size below 16KiB minus 8 byte"); + return; + } + + // set base vertex + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CTL_CONST, 2)); + gx2WriteGather_submitU32AsBE(0); + gx2WriteGather_submitU32AsBE(baseVertex); + // set primitive mode + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONFIG_REG, 2)); + gx2WriteGather_submitU32AsBE(Latte::REGADDR::VGT_PRIMITIVE_TYPE - 0x2000); + gx2WriteGather_submitU32AsBE((uint32)primitiveMode); + // set index type + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_INDEX_TYPE, 1)); + gx2WriteGather_submitU32AsBE((uint32)indexType); + // set number of instances + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_NUM_INSTANCES, 1)); + gx2WriteGather_submitU32AsBE((uint32)numInstances); + // request indexed draw with indices embedded into command buffer + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_DRAW_INDEX_IMMD, 2 + numIndexU32s) | 0x00000001); + gx2WriteGather_submitU32AsBE(count); + gx2WriteGather_submitU32AsBE(0); // ukn + if (use32BitIndices) + { + for (uint32 i = 0; i < numIndexU32s; i++) + { + gx2WriteGather_submitU32AsLE(indexDataU32[i]); + } + } + else + { + for (uint32 i = 0; i < numIndexU32s; i++) + { + uint32 indexPair = indexDataU32[i]; + // swap index pair + indexPair = (indexPair >> 16) | (indexPair << 16); + gx2WriteGather_submitU32AsLE(indexPair); + } + } + + GX2::GX2WriteGather_checkAndInsertWrapAroundMark(); + } + + struct GX2DispatchComputeParam + { + /* +0x00 */ uint32be worksizeX; + /* +0x04 */ uint32be worksizeY; + /* +0x08 */ uint32be worksizeZ; + }; + + void GX2DispatchCompute(GX2DispatchComputeParam* dispatchParam) + { + GX2ReserveCmdSpace(9 + 10); + + gx2WriteGather_submit(pm4HeaderType3(IT_SET_RESOURCE, 8), + (mmSQ_CS_DISPATCH_PARAMS - mmSQ_TEX_RESOURCE_WORD0), + memory_virtualToPhysical(MEMPTR<GX2DispatchComputeParam>(dispatchParam).GetMPTR()), + 0xF, + 0x862000, + 1, + 0xABCD1234, + 0xABCD1234, + 0xC0000000); + + // IT_EVENT_WRITE with RST_VTX_CNT? + + // set primitive mode + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONFIG_REG, 2)); + gx2WriteGather_submitU32AsBE(Latte::REGADDR::VGT_PRIMITIVE_TYPE - 0x2000); + gx2WriteGather_submitU32AsBE(1); // mode + // set number of instances + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_NUM_INSTANCES, 1)); + gx2WriteGather_submitU32AsBE(1); // numInstances + + uint32 workCount = (uint32)dispatchParam->worksizeX * (uint32)dispatchParam->worksizeY * (uint32)dispatchParam->worksizeZ; + + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_DRAW_INDEX_AUTO, 2) | 0x00000001); + gx2WriteGather_submitU32AsBE(workCount); + gx2WriteGather_submitU32AsBE(0); // DRAW_INITIATOR (has source select for index generator + other unknown info) + } + + void GX2DrawInit() + { + cafeExportRegister("gx2", GX2SetAttribBuffer, LogType::GX2); + cafeExportRegister("gx2", GX2DrawIndexedEx, LogType::GX2); + cafeExportRegister("gx2", GX2DrawIndexedEx2, LogType::GX2); + cafeExportRegister("gx2", GX2DrawEx, LogType::GX2); + cafeExportRegister("gx2", GX2DrawIndexedImmediateEx, LogType::GX2); + cafeExportRegister("gx2", GX2DispatchCompute, LogType::GX2); + } + +} diff --git a/src/Cafe/OS/libs/gx2/GX2_Draw.h b/src/Cafe/OS/libs/gx2/GX2_Draw.h new file mode 100644 index 00000000..7cbe77b3 --- /dev/null +++ b/src/Cafe/OS/libs/gx2/GX2_Draw.h @@ -0,0 +1,13 @@ +#pragma once +#include "Cafe/HW/Latte/ISA/LatteReg.h" + +namespace GX2 +{ + using GX2IndexType = Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE; + using GX2PrimitiveMode2 = Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE; + + void GX2SetAttribBuffer(uint32 bufferIndex, uint32 sizeInBytes, uint32 stride, void* data); + void GX2DrawIndexedEx(GX2PrimitiveMode2 primitiveMode, uint32 count, GX2IndexType indexType, void* indexData, uint32 baseVertex, uint32 numInstances); + + void GX2DrawInit(); +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/gx2/GX2_Event.cpp b/src/Cafe/OS/libs/gx2/GX2_Event.cpp new file mode 100644 index 00000000..ffba8ba5 --- /dev/null +++ b/src/Cafe/OS/libs/gx2/GX2_Event.cpp @@ -0,0 +1,311 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "GX2_Command.h" +#include "GX2_Event.h" +#include "Cafe/HW/Latte/Core/LattePM4.h" +#include "Cafe/HW/MMU/MMU.h" +#include "Cafe/OS/libs/coreinit/coreinit_Thread.h" +#include "Cafe/HW/Latte/Core/Latte.h" +#include "GX2.h" +#include "Cafe/HW/Latte/Renderer/Renderer.h" +#include "config/ActiveSettings.h" +#include "util/helpers/ConcurrentQueue.h" + +namespace GX2 +{ + + SysAllocator<coreinit::OSThreadQueue> g_vsyncThreadQueue; + SysAllocator<coreinit::OSThreadQueue> g_flipThreadQueue; + + SysAllocator<coreinit::OSEvent> s_updateRetirementEvent; + std::atomic<uint64> s_lastRetirementTimestamp = 0; + + // called from GPU code when a command buffer is retired + void __GX2NotifyNewRetirementTimestamp(uint64 tsRetire) + { + __OSLockScheduler(); + s_lastRetirementTimestamp = tsRetire; + coreinit::OSSignalEventAllInternal(s_updateRetirementEvent.GetPtr()); + __OSUnlockScheduler(); + } + + void GX2SetGPUFence(uint32be* fencePtr, uint32 mask, uint32 compareOp, uint32 compareValue) + { + GX2ReserveCmdSpace(7); + uint8 compareOpTable[] = { 0x7,0x1,0x3,0x2,0x6,0x4,0x5,0x0 }; + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_WAIT_REG_MEM, 6)); + gx2WriteGather_submitU32AsBE((uint32)(compareOpTable[compareOp & 7]) | 0x10); // compare operand + memory select (0x10) + gx2WriteGather_submitU32AsBE(memory_virtualToPhysical(memory_getVirtualOffsetFromPointer(fencePtr)) | 2); // physical address + type size flag(?) + gx2WriteGather_submitU32AsBE(0); // ukn, always set to 0 + gx2WriteGather_submitU32AsBE(compareValue); // fence value + gx2WriteGather_submitU32AsBE(mask); // fence mask + gx2WriteGather_submitU32AsBE(0xA); // unknown purpose + } + + enum class GX2PipeEventType : uint32 + { + TOP = 0, + BOTTOM = 1, + BOTTOM_AFTER_FLUSH = 2 + }; + + void GX2SubmitUserTimeStamp(uint64* timestampOut, uint64 value, GX2PipeEventType eventType, uint32 triggerInterrupt) + { + GX2ReserveCmdSpace(7); + + MPTR physTimestampAddr = memory_virtualToPhysical(memory_getVirtualOffsetFromPointer(timestampOut)); + uint32 valHigh = (uint32)(value >> 32); + uint32 valLow = (uint32)(value & 0xffffffff); + + if (eventType == GX2PipeEventType::TOP) + { + // write when on top of pipe + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_MEM_WRITE, 4)); + gx2WriteGather_submitU32AsBE(physTimestampAddr | 0x2); + gx2WriteGather_submitU32AsBE(0); // 0x40000 -> 32bit write, 0x0 -> 64bit write? + gx2WriteGather_submitU32AsBE(valLow); // low + gx2WriteGather_submitU32AsBE(valHigh); // high + if (triggerInterrupt != 0) + { + // top callback + gx2WriteGather_submitU32AsBE(0x0000304A); + gx2WriteGather_submitU32AsBE(0x40000000); + } + } + else if (eventType == GX2PipeEventType::BOTTOM_AFTER_FLUSH) + { + // write when on bottom of pipe + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_MEM_WRITE, 4)); + gx2WriteGather_submitU32AsBE(physTimestampAddr | 0x2); + gx2WriteGather_submitU32AsBE(0); // 0x40000 -> 32bit write, 0x0 -> 64bit write? + gx2WriteGather_submitU32AsBE(valLow); // low + gx2WriteGather_submitU32AsBE(valHigh); // high + // trigger CB + if (triggerInterrupt != 0) + { + // bottom callback + // todo -> Fix this + gx2WriteGather_submitU32AsBE(0x0000304B); // hax -> This event is handled differently and uses a different packet? + gx2WriteGather_submitU32AsBE(0x40000000); + // trigger bottom of pipe callback + // used by Mario & Sonic Rio + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_BOTTOM_OF_PIPE_CB, 3)); + gx2WriteGather_submitU32AsBE(physTimestampAddr); + gx2WriteGather_submitU32AsBE(valLow); // low + gx2WriteGather_submitU32AsBE(valHigh); // high + } + } + else if (eventType == GX2PipeEventType::BOTTOM) + { + // fix this + // write timestamp when on bottom of pipe + if (triggerInterrupt != 0) + { + // write value and trigger CB + // todo: Use correct packet + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_BOTTOM_OF_PIPE_CB, 3)); + gx2WriteGather_submitU32AsBE(physTimestampAddr); + gx2WriteGather_submitU32AsBE(valLow); // low + gx2WriteGather_submitU32AsBE(valHigh); // high + } + else + { + // write value but don't trigger CB + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_MEM_WRITE, 4)); + gx2WriteGather_submitU32AsBE(physTimestampAddr | 0x2); + gx2WriteGather_submitU32AsBE(0); // 0x40000 -> 32bit write, 0x0 -> 64bit write? + gx2WriteGather_submitU32AsBE(valLow); // low + gx2WriteGather_submitU32AsBE(valHigh); // high + } + } + else + { + cemu_assert_debug(false); + } + } + + struct GX2EventFunc + { + MEMPTR<void> callbackFuncPtr; + MEMPTR<void> userData; + }s_eventCallback[GX2CallbackEventTypeCount]{}; + + void GX2SetEventCallback(GX2CallbackEventType eventType, void* callbackFuncPtr, void* userData) + { + if ((size_t)eventType >= GX2CallbackEventTypeCount) + { + forceLog_printf("GX2SetEventCallback(): Unknown eventType\n"); + return; + } + s_eventCallback[(size_t)eventType].callbackFuncPtr = callbackFuncPtr; + s_eventCallback[(size_t)eventType].userData = userData; + } + + void GX2GetEventCallback(GX2CallbackEventType eventType, MEMPTR<void>* callbackFuncPtrOut, MEMPTR<void>* userDataOut) + { + if ((size_t)eventType >= GX2CallbackEventTypeCount) + { + forceLog_printf("GX2GetEventCallback(): Unknown eventType\n"); + return; + } + if (callbackFuncPtrOut) + *callbackFuncPtrOut = s_eventCallback[(size_t)eventType].callbackFuncPtr; + if (userDataOut) + *userDataOut = s_eventCallback[(size_t)eventType].userData; + } + + // event callback thread + bool s_callbackThreadLaunched{}; + SysAllocator<OSThread_t> s_eventCallbackThread; + SysAllocator<uint8, 0x2000> s_eventCallbackThreadStack; + SysAllocator<char, 64> s_eventCallbackThreadName; + // event callback queue + struct GX2EventQueueEntry + { + GX2EventQueueEntry() {}; + GX2EventQueueEntry(GX2CallbackEventType eventType) : eventType(eventType) {}; + GX2CallbackEventType eventType{(GX2CallbackEventType)-1}; + }; + + SysAllocator<coreinit::OSSemaphore> s_eventCbQueueSemaphore; + ConcurrentQueue<GX2EventQueueEntry> s_eventCbQueue; + + void __GX2NotifyEvent(GX2CallbackEventType eventType) + { + if ((size_t)eventType >= GX2CallbackEventTypeCount) + { + cemu_assert_debug(false); + return; + } + if (s_eventCallback[(size_t)eventType].callbackFuncPtr) + { + s_eventCbQueue.push(eventType); + coreinit::OSSignalSemaphore(s_eventCbQueueSemaphore); + } + // wake up threads that are waiting for VSYNC or FLIP event + if (eventType == GX2CallbackEventType::VSYNC) + { + __OSLockScheduler(); + g_vsyncThreadQueue->wakeupEntireWaitQueue(false); + __OSUnlockScheduler(); + } + else if (eventType == GX2CallbackEventType::FLIP) + { + __OSLockScheduler(); + g_flipThreadQueue->wakeupEntireWaitQueue(false); + __OSUnlockScheduler(); + } + } + + void __GX2CallbackThread(PPCInterpreter_t* hCPU) + { + while (coreinit::OSWaitSemaphore(s_eventCbQueueSemaphore)) + { + GX2EventQueueEntry entry; + if (!s_eventCbQueue.peek2(entry)) + continue; + if(!s_eventCallback[(size_t)entry.eventType].callbackFuncPtr) + continue; + PPCCoreCallback(s_eventCallback[(size_t)entry.eventType].callbackFuncPtr, (sint32)entry.eventType, s_eventCallback[(size_t)entry.eventType].userData); + } + osLib_returnFromFunction(hCPU, 0); + } + + uint64 GX2GetLastSubmittedTimeStamp() + { + return LatteGPUState.lastSubmittedCommandBufferTimestamp.load(); + } + + uint64 GX2GetRetiredTimeStamp() + { + return s_lastRetirementTimestamp; + } + + void GX2WaitForVsync() + { + __OSLockScheduler(); + g_vsyncThreadQueue.GetPtr()->queueAndWait(coreinit::OSGetCurrentThread()); + __OSUnlockScheduler(); + } + + void GX2WaitForFlip() + { + if ((sint32)(_swapEndianU32(LatteGPUState.sharedArea->flipRequestCountBE) == _swapEndianU32(LatteGPUState.sharedArea->flipExecuteCountBE))) + return; // dont wait if no flip is requested + __OSLockScheduler(); + g_flipThreadQueue.GetPtr()->queueAndWait(coreinit::OSGetCurrentThread()); + __OSUnlockScheduler(); + } + + bool GX2WaitTimeStamp(uint64 tsWait) + { + __OSLockScheduler(); + while (tsWait > s_lastRetirementTimestamp) + { + // GPU hasn't caught up yet + coreinit::OSWaitEventInternal(s_updateRetirementEvent.GetPtr()); + } + __OSUnlockScheduler(); + // return true to indicate no timeout + return true; + } + + void GX2DrawDone() + { + // optional force full sync (texture readback and occlusion queries) + bool forceFullSync = false; + if (g_renderer && g_renderer->GetType() == RendererAPI::Vulkan) + forceFullSync = true; + if (forceFullSync || ActiveSettings::WaitForGX2DrawDoneEnabled()) + { + GX2ReserveCmdSpace(2); + // write PM4 command + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_SYNC_ASYNC_OPERATIONS, 1)); + gx2WriteGather_submitU32AsBE(0x00000000); // unused + } + // flush pipeline + if (_GX2GetUnflushedBytes(PPCInterpreter_getCoreIndex(ppcInterpreterCurrentInstance)) > 0) + _GX2SubmitToTCL(); + + uint64 ts = GX2GetLastSubmittedTimeStamp(); + GX2WaitTimeStamp(ts); + + GX2::GX2WriteGather_checkAndInsertWrapAroundMark(); + } + + void GX2Init_event() + { + // clear queue + + // launch event callback thread + if (s_callbackThreadLaunched) + return; + s_callbackThreadLaunched = true; + strcpy(s_eventCallbackThreadName.GetPtr(), "GX2 event callback"); + coreinit::OSCreateThreadType(s_eventCallbackThread, PPCInterpreter_makeCallableExportDepr(__GX2CallbackThread), 0, nullptr, (uint8*)s_eventCallbackThreadStack.GetPtr() + s_eventCallbackThreadStack.GetByteSize(), (sint32)s_eventCallbackThreadStack.GetByteSize(), 16, OSThread_t::ATTR_DETACHED, OSThread_t::THREAD_TYPE::TYPE_IO); + coreinit::OSSetThreadName(s_eventCallbackThread, s_eventCallbackThreadName); + coreinit::OSResumeThread(s_eventCallbackThread); + } + + void GX2EventInit() + { + cafeExportRegister("gx2", GX2SetGPUFence, LogType::GX2); + cafeExportRegister("gx2", GX2SubmitUserTimeStamp, LogType::GX2); + + cafeExportRegister("gx2", GX2SetEventCallback, LogType::GX2); + cafeExportRegister("gx2", GX2GetEventCallback, LogType::GX2); + + cafeExportRegister("gx2", GX2GetLastSubmittedTimeStamp, LogType::GX2); + cafeExportRegister("gx2", GX2GetRetiredTimeStamp, LogType::GX2); + + cafeExportRegister("gx2", GX2WaitForVsync, LogType::GX2); + cafeExportRegister("gx2", GX2WaitForFlip, LogType::GX2); + cafeExportRegister("gx2", GX2WaitTimeStamp, LogType::GX2); + cafeExportRegister("gx2", GX2DrawDone, LogType::GX2); + + coreinit::OSInitThreadQueue(g_vsyncThreadQueue.GetPtr()); + coreinit::OSInitThreadQueue(g_flipThreadQueue.GetPtr()); + + coreinit::OSInitEvent(s_updateRetirementEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO); + coreinit::OSInitSemaphore(s_eventCbQueueSemaphore, 0); + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/gx2/GX2_Event.h b/src/Cafe/OS/libs/gx2/GX2_Event.h new file mode 100644 index 00000000..70c4ba61 --- /dev/null +++ b/src/Cafe/OS/libs/gx2/GX2_Event.h @@ -0,0 +1,26 @@ +#pragma once + +namespace GX2 +{ + void GX2EventInit(); + void GX2Init_event(); + + void GX2WaitForVsync(); + void GX2WaitForFlip(); + void GX2DrawDone(); + + enum class GX2CallbackEventType + { + TIMESTAMP_TOP = 0, + TIMESTAMP_BOTTOM = 1, + VSYNC = 2, + FLIP = 3, + // 4 is buffer overrun? + }; + inline constexpr size_t GX2CallbackEventTypeCount = 5; + + // notification callbacks for GPU + void __GX2NotifyNewRetirementTimestamp(uint64 tsRetire); + void __GX2NotifyEvent(GX2CallbackEventType eventType); + +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/gx2/GX2_Memory.cpp b/src/Cafe/OS/libs/gx2/GX2_Memory.cpp new file mode 100644 index 00000000..977fcbac --- /dev/null +++ b/src/Cafe/OS/libs/gx2/GX2_Memory.cpp @@ -0,0 +1,77 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/HW/Latte/ISA/RegDefines.h" +#include "GX2.h" +#include "GX2_Resource.h" +#include "Cafe/HW/Latte/Core/Latte.h" +#include "Cafe/HW/Latte/Core/LatteDraw.h" + +// default GX2 allocator (not the same as the GX2R allocator, but GX2R uses this allocator by default) +MPTR gx2Mem_defaultAlloc = MPTR_NULL; +MPTR gx2Mem_defaultFree = MPTR_NULL; + +void gx2Memory_GX2SetDefaultAllocator(MPTR defaultAllocFunc, MPTR defaulFreeFunc) +{ + gx2Mem_defaultAlloc = defaultAllocFunc; + gx2Mem_defaultFree = defaulFreeFunc; +} + +void _GX2DefaultAlloc_Alloc(PPCInterpreter_t* hCPU) +{ + // parameters: + // r3 uint32 userParam + // r4 uint32 size + // r5 sint32 alignment + hCPU->gpr[3] = hCPU->gpr[4]; + hCPU->gpr[4] = hCPU->gpr[5]; + hCPU->instructionPointer = gCoreinitData->MEMAllocFromDefaultHeapEx.GetMPTR(); +} + +void _GX2DefaultAlloc_Free(PPCInterpreter_t* hCPU) +{ + hCPU->gpr[3] = hCPU->gpr[4]; + hCPU->instructionPointer = gCoreinitData->MEMFreeToDefaultHeap.GetMPTR(); +} + +void gx2Export_GX2SetDefaultAllocator(PPCInterpreter_t* hCPU) +{ + gx2Log_printf("GX2SetDefaultAllocator(0x%08x, 0x%08x)\n", hCPU->gpr[3], hCPU->gpr[4]); + gx2Mem_defaultAlloc = hCPU->gpr[3]; + gx2Mem_defaultFree = hCPU->gpr[4]; + osLib_returnFromFunction(hCPU, 0); +} + +void _GX2DefaultAllocR_Alloc(PPCInterpreter_t* hCPU) +{ + gx2Log_printf("GX2DefaultAllocate(0x%08x, 0x%08x, 0x%08x)\n", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); + // parameters: + // r3 uint32 userParam + // r4 uint32 size + // r5 sint32 alignment + hCPU->instructionPointer = gx2Mem_defaultAlloc; +} + +void _GX2DefaultAllocR_Free(PPCInterpreter_t* hCPU) +{ + gx2Log_printf("GX2DefaultFree(0x%08x, 0x%08x)\n", hCPU->gpr[3], hCPU->gpr[4]); + // parameters: + // r3 uint32 userParam + // r4 void* mem + hCPU->instructionPointer = gx2Mem_defaultFree; +} + +namespace GX2 +{ + void GX2MEMAllocatorsInit() + { + // set default allocators (can be overwritten by GX2SetDefaultAllocator) + gx2Mem_defaultAlloc = PPCInterpreter_makeCallableExportDepr(_GX2DefaultAlloc_Alloc); + gx2Mem_defaultFree = PPCInterpreter_makeCallableExportDepr(_GX2DefaultAlloc_Free); + // set resource default allocator + GX2::GX2RSetAllocator(PPCInterpreter_makeCallableExportDepr(_GX2DefaultAllocR_Alloc), PPCInterpreter_makeCallableExportDepr(_GX2DefaultAllocR_Free)); + } + + void GX2MemInit() + { + osLib_addFunction("gx2", "GX2SetDefaultAllocator", gx2Export_GX2SetDefaultAllocator); + } +}; \ No newline at end of file diff --git a/src/Cafe/OS/libs/gx2/GX2_Memory.h b/src/Cafe/OS/libs/gx2/GX2_Memory.h new file mode 100644 index 00000000..5670f76d --- /dev/null +++ b/src/Cafe/OS/libs/gx2/GX2_Memory.h @@ -0,0 +1,8 @@ +#pragma once + +namespace GX2 +{ + void GX2MEMAllocatorsInit(); + + void GX2MemInit(); +}; \ No newline at end of file diff --git a/src/Cafe/OS/libs/gx2/GX2_Misc.cpp b/src/Cafe/OS/libs/gx2/GX2_Misc.cpp new file mode 100644 index 00000000..ae079dc1 --- /dev/null +++ b/src/Cafe/OS/libs/gx2/GX2_Misc.cpp @@ -0,0 +1,255 @@ +#include "Cafe/HW/Latte/ISA/RegDefines.h" +#include "Cafe/OS/common/OSCommon.h" +#include "GX2.h" +#include "config/CemuConfig.h" +#include "Cafe/OS/libs/coreinit/coreinit_Time.h" +#include "config/ActiveSettings.h" +#include "Cafe/HW/Latte/Renderer/Renderer.h" +#include "Cafe/HW/Latte/Core/LatteBufferCache.h" +#include "Cafe/HW/Latte/Core/LattePM4.h" +#include "GX2_Command.h" +#include "GX2_Event.h" +#include "GX2_Misc.h" +#include "GX2_Memory.h" +#include "GX2_Texture.h" + +void gx2Export_GX2SetSwapInterval(PPCInterpreter_t* hCPU) +{ + gx2Log_printf("GX2SetSwapInterval(%d)\n", hCPU->gpr[3]); + if( hCPU->gpr[3] >= 20 ) + { + forceLog_printf("GX2SetSwapInterval() called with out of range value (%d)\n", hCPU->gpr[3]); + } + else + LatteGPUState.sharedArea->swapInterval = hCPU->gpr[3]; + osLib_returnFromFunction(hCPU, 0); +} + +void gx2Export_GX2GetSwapInterval(PPCInterpreter_t* hCPU) +{ + gx2Log_printf("GX2GetSwapInterval()\n"); + osLib_returnFromFunction(hCPU, LatteGPUState.sharedArea->swapInterval); +} + +extern uint64 lastSwapTime; + +void gx2Export_GX2GetSwapStatus(PPCInterpreter_t* hCPU) +{ + memory_writeU32(hCPU->gpr[3], _swapEndianU32(LatteGPUState.sharedArea->flipRequestCountBE)); + memory_writeU32(hCPU->gpr[4], _swapEndianU32(LatteGPUState.sharedArea->flipExecuteCountBE)); + memory_writeU64Slow(hCPU->gpr[5], lastSwapTime); + memory_writeU64Slow(hCPU->gpr[6], lastSwapTime); + + osLib_returnFromFunction(hCPU, 0); +} + +void gx2Export_GX2GetGPUTimeout(PPCInterpreter_t* hCPU) +{ + gx2Log_printf("GX2GetGPUTimeout()\n"); + osLib_returnFromFunction(hCPU, 0x3E8); +} + +#define GX2_INVALID_COUNTER_VALUE_U64 0xFFFFFFFFFFFFFFFFULL + +void gx2Export_GX2SampleTopGPUCycle(PPCInterpreter_t* hCPU) +{ + gx2Log_printf("GX2SampleTopGPUCycle(0x%08x)\n", hCPU->gpr[3]); + memory_writeU64Slow(hCPU->gpr[3], coreinit::coreinit_getTimerTick()); + osLib_returnFromFunction(hCPU, 0); +} + +void gx2Export_GX2SampleBottomGPUCycle(PPCInterpreter_t* hCPU) +{ + gx2Log_printf("GX2SampleBottomGPUCycle(0x%08x)\n", hCPU->gpr[3]); + memory_writeU64Slow(hCPU->gpr[3], GX2_INVALID_COUNTER_VALUE_U64); + + osLib_returnFromFunction(hCPU, 0); + return; + // seems like implementing this correctly causes more harm than good as games will try to dynamically scale their resolution, which our texture cache and graphic packs cant handle well. If we just never return a valid timestamp, it seems like games stop dynamically scaling resolution + // Whats a good solution here? Should we implement it correctly and instead rely on graphic pack patches to patch out the dynamic scaling? + // some known affected games: Wind Waker HD, Super Mario 3D World + + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_SAMPLE_TIMER, 1)); + gx2WriteGather_submitU32AsBE(hCPU->gpr[3]); + osLib_returnFromFunction(hCPU, 0); +} + +namespace GX2 +{ + SysAllocator<uint8, 640 * 480 * 4, 0x1000> _lastFrame; + uint32 sGX2MainCoreIndex = 0; + + void _test_AddrLib(); + + void GX2Init(void* initSettings) + { + if (LatteGPUState.gx2InitCalled) + { + cemuLog_logDebug(LogType::Force, "GX2Init() called while already initialized"); + return; + } + uint32 coreIndex = coreinit::OSGetCoreId(); + cemuLog_log(LogType::GX2, "GX2Init() on core {} by thread 0x{:08x}", coreIndex, MEMPTR<OSThread_t>(coreinit::OSGetCurrentThread()).GetMPTR()); + sGX2MainCoreIndex = coreIndex; + // init submodules + GX2::GX2Init_event(); + GX2::GX2Init_writeGather(); + // init shared area + if (LatteGPUState.sharedAreaAddr == MPTR_NULL) + { + LatteGPUState.sharedAreaAddr = coreinit_allocFromSysArea(sizeof(gx2GPUSharedArea_t), 0x20); + LatteGPUState.sharedArea = (gx2GPUSharedArea_t*)memory_getPointerFromVirtualOffset(LatteGPUState.sharedAreaAddr); + } + // init shared variables + LatteGPUState.sharedArea->flipRequestCountBE = _swapEndianU32(0); + LatteGPUState.sharedArea->flipExecuteCountBE = _swapEndianU32(0); + LatteGPUState.sharedArea->swapInterval = 1; + // init memory handling + GX2::GX2MEMAllocatorsInit(); + // let GPU know that GX2 is initialized + LatteGPUState.gx2InitCalled++; + // run tests + _test_AddrLib(); + } + + void _GX2DriverReset() + { + LatteGPUState.gx2InitCalled = 0; + } + + sint32 GX2GetMainCoreId(PPCInterpreter_t* hCPU) + { + if (LatteGPUState.gx2InitCalled == 0) + return -1; + return sGX2MainCoreIndex; + } + + void GX2ResetGPU(uint32 ukn) + { + cemuLog_log(LogType::Force, "GX2ResetGPU()"); // always log this + GX2::GX2DrawDone(); + } + + void GX2SetTVBuffer(void* imageBuffePtr, uint32 imageBufferSize, E_TVRES tvResolutionMode, uint32 _surfaceFormat, E_TVBUFFERMODE bufferMode) + { + Latte::E_GX2SURFFMT surfaceFormat = (Latte::E_GX2SURFFMT)_surfaceFormat; + LatteGPUState.tvBufferUsesSRGB = HAS_FLAG(surfaceFormat, Latte::E_GX2SURFFMT::FMT_BIT_SRGB); + // todo - actually allocate a scanbuffer + } + + void GX2SetTVGamma(float gamma) + { + if (abs(gamma - 1.0f) > 0.01f) + cemuLog_logDebug(LogType::Force, "TV gamma set to {} which is not supported", gamma); + } + + bool GX2GetLastFrame(uint32 deviceId, GX2Texture* textureOut) + { + // return a 480p image + textureOut->viewFirstMip = 0; + textureOut->viewFirstSlice = 0; + textureOut->viewNumMips = 1; + textureOut->viewNumSlices = 1; + textureOut->compSel = 0x00010203; + + textureOut->surface.width = 640; + textureOut->surface.height = 480; + textureOut->surface.depth = 1; + textureOut->surface.dim = Latte::E_DIM::DIM_2D; + textureOut->surface.format = Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM; + textureOut->surface.tileMode = Latte::E_GX2TILEMODE::TM_LINEAR_ALIGNED; + textureOut->surface.pitch = 0; + textureOut->surface.resFlag = 0; + textureOut->surface.aa = 0; + + GX2CalcSurfaceSizeAndAlignment(&textureOut->surface); + + textureOut->surface.imagePtr = _lastFrame.GetMPTR(); + + GX2InitTextureRegs(textureOut); + + return true; + } + + bool GX2GetLastFrameGammaA(uint32 deviceId, float32be* gamma) + { + *gamma = 1.0f; + return true; + } + + bool GX2GetLastFrameGammaB(uint32 deviceId, float32be* gamma) + { + *gamma = 1.0f; + return true; + } + + uint64 GX2GPUTimeToCPUTime(uint64 gpuTime) + { + return 0; // hack, see note in GX2SampleBottomGPUCycle + } + + uint32 GX2GetSystemDRCMode() + { + return 1; + } + + uint32 GX2IsVideoOutReady() + { + return 1; + } + + void GX2Invalidate(uint32 invalidationFlags, MPTR invalidationAddr, uint32 invalidationSize) + { + uint32 surfaceSyncFlags = 0; + + if (invalidationFlags & 0x04) + { + // uniform block + surfaceSyncFlags |= 0x8800000; + } + if (invalidationFlags & 0x01) + { + // attribute data + surfaceSyncFlags |= 0x800000; + } + + if (invalidationFlags & 0x40) + { + // CPU cache + LatteBufferCache_notifyDCFlush(invalidationAddr, invalidationSize); + } + + if (surfaceSyncFlags != 0) + { + GX2ReserveCmdSpace(5); + // write PM4 command + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SURFACE_SYNC, 4)); // IT_SURFACE_SYNC + 4 data dwords + gx2WriteGather_submitU32AsBE(surfaceSyncFlags); + gx2WriteGather_submitU32AsBE((invalidationSize + 0xFF) >> 8); // size + gx2WriteGather_submitU32AsBE(memory_virtualToPhysical(invalidationAddr) >> 8); // base address (divided by 0x100) + gx2WriteGather_submitU32AsBE(0x00000004); // poll interval + } + } + + void GX2MiscInit() + { + cafeExportRegister("gx2", GX2Init, LogType::GX2); + cafeExportRegister("gx2", GX2GetMainCoreId, LogType::GX2); + cafeExportRegister("gx2", GX2ResetGPU, LogType::GX2); + + cafeExportRegister("gx2", GX2SetTVBuffer, LogType::GX2); + cafeExportRegister("gx2", GX2SetTVGamma, LogType::GX2); + + cafeExportRegister("gx2", GX2GetLastFrame, LogType::GX2); + cafeExportRegister("gx2", GX2GetLastFrameGammaA, LogType::GX2); + cafeExportRegister("gx2", GX2GetLastFrameGammaB, LogType::GX2); + + cafeExportRegister("gx2", GX2GPUTimeToCPUTime, LogType::GX2); + cafeExportRegister("gx2", GX2GetSystemDRCMode, LogType::GX2); + cafeExportRegister("gx2", GX2IsVideoOutReady, LogType::GX2); + + cafeExportRegister("gx2", GX2Invalidate, LogType::GX2); + + sGX2MainCoreIndex = 0; + } +}; diff --git a/src/Cafe/OS/libs/gx2/GX2_Misc.h b/src/Cafe/OS/libs/gx2/GX2_Misc.h new file mode 100644 index 00000000..e6ac8010 --- /dev/null +++ b/src/Cafe/OS/libs/gx2/GX2_Misc.h @@ -0,0 +1,23 @@ +#pragma once + +namespace GX2 +{ + extern uint32 sGX2MainCoreIndex; + + enum class E_TVRES + { + TODO, + }; + + enum class E_TVBUFFERMODE + { + DOUBLE_BUFFER = 2, + }; + + void _GX2DriverReset(); + + void GX2SetTVBuffer(void* imageBuffePtr, uint32 imageBufferSize, E_TVRES tvResolutionMode, uint32 surfaceFormat, E_TVBUFFERMODE bufferMode); + void GX2SetTVGamma(float gamma); + + void GX2MiscInit(); +}; \ No newline at end of file diff --git a/src/Cafe/OS/libs/gx2/GX2_Query.cpp b/src/Cafe/OS/libs/gx2/GX2_Query.cpp new file mode 100644 index 00000000..f79cf70e --- /dev/null +++ b/src/Cafe/OS/libs/gx2/GX2_Query.cpp @@ -0,0 +1,153 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/HW/Latte/ISA/RegDefines.h" +#include "GX2.h" +#include "Cafe/HW/Latte/Core/LattePM4.h" +#include "Cafe/CafeSystem.h" +#include "GX2_Query.h" + +#define LATTE_GC_NUM_RB 2 +#define _QUERY_REG_COUNT 8 // each reg/result is 64bits, little endian + +namespace GX2 +{ + struct GX2Query + { + // 4*2 sets of uint64 results + uint32 reg[_QUERY_REG_COUNT * 2]; + }; + + static_assert(sizeof(GX2Query) == 0x40); + + void _BeginOcclusionQuery(GX2Query* queryInfo, bool isGPUQuery) + { + if (isGPUQuery) + { + uint64 titleId = CafeSystem::GetForegroundTitleId(); + if (titleId == 0x00050000101c4c00ULL || titleId == 0x00050000101c4d00 || titleId == 0x0005000010116100) // XCX EU, US, JPN + { + // in XCX queries are used to determine if certain objects are visible + // if we are not setting the result fast enough and the query still holds a value of 0 (which is the default for GPU queries) + // then XCX will not render affected objects, causing flicker + // note: This is a very old workaround. It may no longer be necessary since the introduction of full sync. Investigate + *(uint64*)(queryInfo->reg + 2) = 0x100000; + } + else + { + GX2ReserveCmdSpace(5 * _QUERY_REG_COUNT); + MPTR queryInfoPhys = memory_virtualToPhysical(memory_getVirtualOffsetFromPointer(queryInfo)); + for (sint32 i = 0; i < _QUERY_REG_COUNT; i++) + { + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_MEM_WRITE, 4)); + gx2WriteGather_submitU32AsBE((queryInfoPhys + i * 8) | 0x2); + gx2WriteGather_submitU32AsBE(0x20000); // 0x20000 -> ? + uint32 v = 0; + if (i >= LATTE_GC_NUM_RB * 2) + v |= 0x80000000; + gx2WriteGather_submitU32AsBE(0); + gx2WriteGather_submitU32AsBE(v); + } + } + } + else + { + memset(queryInfo, 0, 0x10); // size maybe GPU7_GC_NUM_RB*2*4 ? + queryInfo->reg[LATTE_GC_NUM_RB * 4 + 0] = 0; + queryInfo->reg[LATTE_GC_NUM_RB * 4 + 1] = _swapEndianU32('OCPU'); + } + // todo: Set mmDB_RENDER_CONTROL + } + + void GX2QueryBegin(uint32 queryType, GX2Query* query) + { + if (queryType == GX2_QUERY_TYPE_OCCLUSION_CPU) + { + _BeginOcclusionQuery(query, false); + } + else if (queryType == GX2_QUERY_TYPE_OCCLUSION_GPU) + { + _BeginOcclusionQuery(query, true); + } + else + { + debug_printf("GX2QueryBegin(): Unsupported type %d\n", queryType); + debugBreakpoint(); + return; + } + // HLE packet + GX2ReserveCmdSpace(2); + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_BEGIN_OCCLUSION_QUERY, 1)); + gx2WriteGather_submitU32AsBE(MEMPTR<GX2Query>(query).GetMPTR()); + } + + void GX2QueryEnd(uint32 queryType, GX2Query* query) + { + GX2ReserveCmdSpace(2); + if (queryType == GX2_QUERY_TYPE_OCCLUSION_CPU || queryType == GX2_QUERY_TYPE_OCCLUSION_GPU) + { + // HLE packet + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_END_OCCLUSION_QUERY, 1)); + gx2WriteGather_submitU32AsBE(MEMPTR<GX2Query>(query).GetMPTR()); + } + else + { + debug_printf("GX2QueryBegin(): Unsupported %d\n", queryType); + debugBreakpoint(); + return; + } + } + + uint32 GX2QueryGetOcclusionResult(GX2Query* query, uint64be* resultOut) + { + if (query->reg[LATTE_GC_NUM_RB * 4 + 1] == _swapEndianU32('OCPU') && query->reg[LATTE_GC_NUM_RB * 4 + 0] == 0) + { + // CPU query result not ready + return GX2_FALSE; + } + + uint64 startValue = *(uint64*)(query->reg + 0); + uint64 endValue = *(uint64*)(query->reg + 2); + if ((startValue & 0x8000000000000000ULL) || (endValue & 0x8000000000000000ULL)) + { + return GX2_FALSE; + } + *resultOut = endValue - startValue; + return GX2_TRUE; + } + + void GX2QueryBeginConditionalRender(uint32 queryType, GX2Query* query, uint32 dontWaitBool, uint32 pixelsMustPassBool) + { + GX2ReserveCmdSpace(3); + + uint32 flags = 0; + if (pixelsMustPassBool) + flags |= (1<<31); + if (queryType == GX2_QUERY_TYPE_OCCLUSION_GPU) + flags |= (1 << 13); + else + flags |= (2 << 13); + + flags |= ((dontWaitBool != 0) << 19); + + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_PREDICATION, 2)); + gx2WriteGather_submitU32AsBE(memory_virtualToPhysical(MEMPTR<GX2Query>(query).GetMPTR())); + gx2WriteGather_submitU32AsBE(flags); + } + + void GX2QueryEndConditionalRender() + { + GX2ReserveCmdSpace(3); + + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_PREDICATION, 2)); + gx2WriteGather_submitU32AsBE(MPTR_NULL); + gx2WriteGather_submitU32AsBE(0); // unknown / todo + } + + void GX2QueryInit() + { + cafeExportRegister("gx2", GX2QueryBegin, LogType::GX2); + cafeExportRegister("gx2", GX2QueryEnd, LogType::GX2); + cafeExportRegister("gx2", GX2QueryGetOcclusionResult, LogType::GX2); + cafeExportRegister("gx2", GX2QueryBeginConditionalRender, LogType::GX2); + cafeExportRegister("gx2", GX2QueryEndConditionalRender, LogType::GX2); + } +}; \ No newline at end of file diff --git a/src/Cafe/OS/libs/gx2/GX2_Query.h b/src/Cafe/OS/libs/gx2/GX2_Query.h new file mode 100644 index 00000000..8c18688d --- /dev/null +++ b/src/Cafe/OS/libs/gx2/GX2_Query.h @@ -0,0 +1,10 @@ +#pragma once + +#define GX2_QUERY_TYPE_OCCLUSION_CPU 0 +#define GX2_QUERY_TYPE_OCCLUSION_GPU 2 +// 1 and 3 are streamout related? + +namespace GX2 +{ + void GX2QueryInit(); +}; \ No newline at end of file diff --git a/src/Cafe/OS/libs/gx2/GX2_RenderTarget.cpp b/src/Cafe/OS/libs/gx2/GX2_RenderTarget.cpp new file mode 100644 index 00000000..1b3cc185 --- /dev/null +++ b/src/Cafe/OS/libs/gx2/GX2_RenderTarget.cpp @@ -0,0 +1,303 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "GX2.h" +#include "Cafe/HW/Latte/Core/Latte.h" +#include "Cafe/HW/Latte/Core/LatteDraw.h" + +#include "Cafe/HW/Latte/LatteAddrLib/LatteAddrLib.h" + +#include "Cafe/HW/Latte/ISA/LatteReg.h" +#include "Cafe/HW/Latte/ISA/RegDefines.h" +#include "Cafe/HW/Latte/Core/LattePM4.h" + +#include "GX2_Command.h" + +void gx2Export_GX2InitColorBufferRegs(PPCInterpreter_t* hCPU) +{ + gx2Log_printf("GX2InitColorBufferRegs(0x%08x)\n", hCPU->gpr[3]); + ppcDefineParamStructPtr(colorBuffer, GX2ColorBuffer, 0); + + LatteAddrLib::AddrSurfaceInfo_OUT surfaceInfo; + LatteAddrLib::GX2CalculateSurfaceInfo(colorBuffer->surface.format, colorBuffer->surface.width, colorBuffer->surface.height, colorBuffer->surface.depth, colorBuffer->surface.dim, colorBuffer->surface.tileMode, colorBuffer->surface.aa, _swapEndianU32(colorBuffer->viewMip), &surfaceInfo); + + uint32 pitchHeight = (surfaceInfo.height * surfaceInfo.pitch) >> 6; +#ifndef PUBLIC_RELEASE + if (colorBuffer->viewNumSlices != _swapEndianU32(1)) + forceLogDebug_printf("GX2InitColorBufferRegs(): With unsupported slice count %d", _swapEndianU32(colorBuffer->viewNumSlices)); + if (surfaceInfo.pitch < 7) + forceLogDebug_printf("GX2InitColorBufferRegs(): Pitch too small (pitch = %d)", surfaceInfo.pitch); + if ((surfaceInfo.pitch & 7) != 0) + forceLogDebug_printf("GX2InitColorBufferRegs(): Pitch has invalid alignment (pitch = %d)", surfaceInfo.pitch); + if (pitchHeight == 0) + forceLogDebug_printf("GX2InitColorBufferRegs(): Invalid value (pitchHeight = %d)", pitchHeight); +#endif + + uint32 cSize = ((surfaceInfo.pitch >> 3) - 1) & 0x3FF; + cSize |= (((pitchHeight - 1) & 0xFFFFF) << 10); + colorBuffer->reg_size = cSize; + colorBuffer->reg_mask = 0; + // reg color_info + Latte::E_GX2SURFFMT format = colorBuffer->surface.format; + Latte::E_HWSURFFMT hwFormat = Latte::GetHWFormat(format); + uint32 formatHighBits = (uint32)format & 0xF00; + uint32 regInfo = 0; + regInfo = (uint32)GX2::GetSurfaceFormatSwapMode(colorBuffer->surface.format); + regInfo |= ((uint32)hwFormat<<2); + cemu_assert_debug(LatteAddrLib::IsValidHWTileMode(surfaceInfo.hwTileMode)); + regInfo |= ((uint32)surfaceInfo.hwTileMode << 8); + bool clampBlend = false; + if (formatHighBits == 0x000) + { + regInfo |= (0 << 12); + clampBlend = true; + } + else if (formatHighBits == 0x100) // integer + { + regInfo |= (4 << 12); + } + else if (formatHighBits == 0x200) // signed + { + regInfo |= (1 << 12); + clampBlend = true; + } + else if (formatHighBits == 0x300) // integer + signed + { + regInfo |= (5 << 12); + } + else if (formatHighBits == 0x400) // srgb + { + clampBlend = true; + regInfo |= (6 << 12); + } + else if (formatHighBits == 0x800) // float + { + regInfo |= (7 << 12); + } + else + cemu_assert_debug(false); + if (hwFormat == Latte::E_HWSURFFMT::HWFMT_5_5_5_1 || hwFormat == Latte::E_HWSURFFMT::HWFMT_10_10_10_2 ) + regInfo |= (2 << 16); + else + regInfo &= ~(3 << 16); // COMP_SWAP_mask + if(colorBuffer->surface.aa != 0) + regInfo |= (2 << 18); // TILE_MODE + bool isIntegerFormat = (uint32)(format & Latte::E_GX2SURFFMT::FMT_BIT_INT) != 0; + if (isIntegerFormat == false) + regInfo |= (GX2::GetSurfaceColorBufferExportFormat(colorBuffer->surface.format) << 27); // 0 -> full, 1 -> normalized + if (isIntegerFormat + || format ==Latte::E_GX2SURFFMT::R24_X8_UNORM + || format ==Latte::E_GX2SURFFMT::R24_X8_FLOAT + || format ==Latte::E_GX2SURFFMT::R32_X8_FLOAT) + { + // set the blend bypass bit for formats which dont support blending + regInfo |= (1<<22); + clampBlend = false; + } + if (clampBlend) + regInfo |= (1<<20); // BLEND_CLAMP_bit + if ((uint32)(format & Latte::E_GX2SURFFMT::FMT_BIT_FLOAT) != 0) + regInfo |= (1<<25); // ROUND_MODE_bit + colorBuffer->reg_info = regInfo; + // reg color_view + uint32 regView = 0; + if (colorBuffer->surface.tileMode != Latte::E_GX2TILEMODE::TM_LINEAR_SPECIAL) + { + regView |= (_swapEndianU32(colorBuffer->viewFirstSlice) & 0x7FF); + regView |= (((_swapEndianU32(colorBuffer->viewNumSlices) + _swapEndianU32(colorBuffer->viewFirstSlice) - 1) & 0x7FF) << 13); + } + colorBuffer->reg_view = regView; + colorBuffer->reg_mask = 0; + + // todo - aa stuff + + osLib_returnFromFunction(hCPU, 0); +} + +void gx2Export_GX2InitDepthBufferRegs(PPCInterpreter_t* hCPU) +{ + gx2Log_printf("GX2InitDepthBufferRegs(0x%08x)\n", hCPU->gpr[3]); + ppcDefineParamStructPtr(depthBuffer, GX2DepthBuffer, 0); + + LatteAddrLib::AddrSurfaceInfo_OUT surfaceInfo; + LatteAddrLib::GX2CalculateSurfaceInfo(depthBuffer->surface.format, depthBuffer->surface.width, depthBuffer->surface.height, depthBuffer->surface.depth, depthBuffer->surface.dim, depthBuffer->surface.tileMode, depthBuffer->surface.aa, _swapEndianU32(depthBuffer->viewMip), &surfaceInfo); + + cemu_assert_debug(depthBuffer->viewNumSlices != 0); + + uint32 cSize = ((surfaceInfo.pitch >> 3) - 1) & 0x3FF; + cSize |= ((((surfaceInfo.height * surfaceInfo.pitch >> 6) - 1) & 0xFFFFF) << 10); + + depthBuffer->reg_size = cSize; + // todo - other regs + + osLib_returnFromFunction(hCPU, 0); +} + + +void gx2Export_GX2SetColorBuffer(PPCInterpreter_t* hCPU) +{ + gx2Log_printf("GX2SetColorBuffer(0x%08x, %d)", hCPU->gpr[3], hCPU->gpr[4]); + GX2ReserveCmdSpace(20); + + GX2ColorBuffer* colorBufferBE = (GX2ColorBuffer*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); + +#ifndef PUBLIC_RELEASE + gx2Log_printf("ColorBuffer tileMode %01x PhysAddr %08x fmt %04x res %dx%d Mip %d Slice %d", (uint32)colorBufferBE->surface.tileMode.value(), (uint32)colorBufferBE->surface.imagePtr, (uint32)colorBufferBE->surface.format.value(), (uint32)colorBufferBE->surface.width, (uint32)colorBufferBE->surface.height, _swapEndianU32(colorBufferBE->viewMip), _swapEndianU32(colorBufferBE->viewFirstSlice)); +#endif + + // regs[0] = mmCB_COLOR0_SIZE + // regs[1] = mmCB_COLOR0_INFO + // regs[2] = mmCB_COLOR0_VIEW + // regs[3] = mmCB_COLOR0_MASK + // regs[4] = mmCB_COLOR0_TILE + + uint32 targetIndex = hCPU->gpr[4]; + + uint32 viewMip = _swapEndianU32(colorBufferBE->viewMip); + uint32 colorBufferBase = memory_virtualToPhysical(colorBufferBE->surface.imagePtr); + + if( viewMip != 0 ) + { + uint32 baseImagePtr = colorBufferBE->surface.mipPtr; + if( viewMip == 1 ) + colorBufferBase = memory_virtualToPhysical(baseImagePtr); + else + colorBufferBase = memory_virtualToPhysical(baseImagePtr+colorBufferBE->surface.mipOffset[viewMip-1]); + + } + + Latte::E_GX2TILEMODE tileMode = colorBufferBE->surface.tileMode; + uint32 viewMipIndex = _swapEndianU32(colorBufferBE->viewMip); + uint32 swizzle = colorBufferBE->surface.swizzle; + + if (Latte::TM_IsMacroTiled(tileMode) && viewMipIndex < ((swizzle >> 16) & 0xFF)) + { + // remove swizzle for small mips + colorBufferBase ^= (swizzle & 0xFFFF); + } + // set color buffer pointer for render target + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 2)); + gx2WriteGather_submitU32AsBE(mmCB_COLOR0_BASE - 0xA000 + hCPU->gpr[4]); + gx2WriteGather_submitU32AsBE(colorBufferBase); + // set color buffer size + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 2)); + gx2WriteGather_submitU32AsBE(mmCB_COLOR0_SIZE - 0xA000 + hCPU->gpr[4]); + gx2WriteGather_submitU32AsBE((uint32)colorBufferBE->reg_size); + + cemu_assert_debug(tileMode != Latte::E_GX2TILEMODE::TM_LINEAR_SPECIAL); + + // set mmCB_COLOR*_VIEW + gx2WriteGather_submit( + pm4HeaderType3(IT_SET_CONTEXT_REG, 2), + mmCB_COLOR0_VIEW - 0xA000 + hCPU->gpr[4], + colorBufferBE->reg_view); + + // todo: mmCB_COLOR0_TILE and mmCB_COLOR0_FRAG + + // set mmCB_COLOR*_INFO + gx2WriteGather_submit( + pm4HeaderType3(IT_SET_CONTEXT_REG, 2), + mmCB_COLOR0_INFO - 0xA000 + hCPU->gpr[4], + colorBufferBE->reg_info); + + GX2::GX2WriteGather_checkAndInsertWrapAroundMark(); + + osLib_returnFromFunction(hCPU, 0); +} + +void gx2Export_GX2SetDepthBuffer(PPCInterpreter_t* hCPU) +{ + gx2Log_printf("GX2SetDepthBuffer(0x%08x)\n", hCPU->gpr[3]); + GX2ReserveCmdSpace(20); + + GX2DepthBuffer* depthBufferBE = (GX2DepthBuffer*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); + + gx2Log_printf("DepthBuffer tileMode %01x PhysAddr %08x fmt %04x res %dx%d", (uint32)depthBufferBE->surface.tileMode.value(), (uint32)depthBufferBE->surface.imagePtr, (uint32)depthBufferBE->surface.format.value(), (uint32)depthBufferBE->surface.width, (uint32)depthBufferBE->surface.height); + + uint32 viewMip = _swapEndianU32(depthBufferBE->viewMip); + + // todo: current code for the PM4 packets is a hack, replace with proper implementation + + uint32 regHTileDataBase = memory_virtualToPhysical(depthBufferBE->surface.imagePtr)>>8; + + if( viewMip > 0 ) + { + forceLogDebug_printf("GX2SetDepthBuffer: Unsupported non-zero mip (%d) Pointer: %08X Base: %08X", viewMip, regHTileDataBase, 0); + } + + // setup depthbuffer info register + uint32 regDepthBufferInfo = 0; + uint32 depthBufferTileMode = (uint32)depthBufferBE->surface.tileMode.value(); + Latte::E_GX2SURFFMT depthBufferFormat = depthBufferBE->surface.format; + + regDepthBufferInfo |= ((depthBufferTileMode&0xF)<<15); + if (depthBufferFormat == Latte::E_GX2SURFFMT::D16_UNORM) + regDepthBufferInfo |= (1 << 0); + else if (depthBufferFormat == Latte::E_GX2SURFFMT::D24_S8_UNORM) + regDepthBufferInfo |= (3 << 0); + else if (depthBufferFormat == Latte::E_GX2SURFFMT::D32_FLOAT) + regDepthBufferInfo |= (6 << 0); + else if (depthBufferFormat == Latte::E_GX2SURFFMT::D32_S8_FLOAT) + regDepthBufferInfo |= (7 << 0); + else if (depthBufferFormat == Latte::E_GX2SURFFMT::D24_S8_FLOAT) + regDepthBufferInfo |= (5 << 0); + else + { + debug_printf("Unsupported depth buffer format 0x%04x\n", depthBufferFormat); + } + + // set color buffer pointer for render target + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 1+1)); + gx2WriteGather_submitU32AsBE(mmDB_DEPTH_SIZE - 0xA000); + gx2WriteGather_submitU32AsBE((uint32)depthBufferBE->reg_size); // hack + // set color buffer size + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 1+3)); + gx2WriteGather_submitU32AsBE(mmDB_DEPTH_BASE - 0xA000); + + gx2WriteGather_submitU32AsBE(0); // DB_DEPTH_BASE + gx2WriteGather_submitU32AsBE(regDepthBufferInfo); // DB_DEPTH_INFO + gx2WriteGather_submitU32AsBE(regHTileDataBase); // DB_HTILE_DATA_BASE + + // set DB_DEPTH_VIEW + uint32 db_view = 0; + db_view |= (_swapEndianU32(depthBufferBE->viewFirstSlice)&0x7FF); + db_view |= (((_swapEndianU32(depthBufferBE->viewNumSlices)+_swapEndianU32(depthBufferBE->viewFirstSlice)-1)&0x7FF)<<13); + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 2)); + gx2WriteGather_submitU32AsBE(mmDB_DEPTH_VIEW - 0xA000); + gx2WriteGather_submitU32AsBE(db_view); + + GX2::GX2WriteGather_checkAndInsertWrapAroundMark(); + + osLib_returnFromFunction(hCPU, 0); +} + +void gx2Export_GX2SetDRCBuffer(PPCInterpreter_t* hCPU) +{ + Latte::E_GX2SURFFMT format = (Latte::E_GX2SURFFMT)hCPU->gpr[6]; + LatteGPUState.drcBufferUsesSRGB = HAS_FLAG(format, Latte::E_GX2SURFFMT::FMT_BIT_SRGB); + osLib_returnFromFunction(hCPU, 0); +} + +void gx2Export_GX2MarkScanBufferCopied(PPCInterpreter_t* hCPU) +{ + uint32 scanTarget = hCPU->gpr[3]; + if( scanTarget == GX2_SCAN_TARGET_TV ) + { + GX2ReserveCmdSpace(10); + + uint32 physAddr = (MEMORY_TILINGAPERTURE_AREA_ADDR+0x200000); + + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_COPY_COLORBUFFER_TO_SCANBUFFER, 9)); + gx2WriteGather_submitU32AsBE(physAddr); + gx2WriteGather_submitU32AsBE(1920); + gx2WriteGather_submitU32AsBE(1080); + + gx2WriteGather_submitU32AsBE(1920); // pitch + gx2WriteGather_submitU32AsBE(4); // tileMode + gx2WriteGather_submitU32AsBE(0); // swizzle + + gx2WriteGather_submitU32AsBE(0); + gx2WriteGather_submitU32AsBE((uint32)Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM); + gx2WriteGather_submitU32AsBE(scanTarget); + } + + osLib_returnFromFunction(hCPU, 0); +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/gx2/GX2_Resource.cpp b/src/Cafe/OS/libs/gx2/GX2_Resource.cpp new file mode 100644 index 00000000..70390921 --- /dev/null +++ b/src/Cafe/OS/libs/gx2/GX2_Resource.cpp @@ -0,0 +1,253 @@ +#include "Cafe/HW/Latte/Core/LatteBufferCache.h" +#include "Cafe/HW/Latte/Core/LattePM4.h" +#include "Cafe/OS/common/OSCommon.h" +#include "GX2.h" +#include "GX2_Command.h" +#include "GX2_Resource.h" +#include "GX2_Streamout.h" +#include "GX2_Draw.h" + +namespace GX2 +{ + + MPTR GX2RAllocateFunc = MPTR_NULL; + MPTR GX2RFreeFunc = MPTR_NULL; + + void GX2RSetAllocator(MPTR funcAllocMPTR, MPTR funcFreeMPR) + { + GX2RAllocateFunc = funcAllocMPTR; + GX2RFreeFunc = funcFreeMPR; + } + + uint32 GX2RGetBufferAllocationSize(GX2RBuffer* buffer) + { + return (buffer->GetSize() + 0x3F) & ~0x3F; // pad to 64 byte alignment + } + + uint32 GX2RGetBufferAlignment(uint32 resFlags) + { + if ((resFlags & GX2R_RESFLAG_USAGE_STREAM_OUTPUT) != 0) + return 0x100; + if ((resFlags & GX2R_RESFLAG_USAGE_UNIFORM_BLOCK) != 0) + return 0x100; + if ((resFlags & GX2R_RESFLAG_USAGE_SHADER_PROGRAM) != 0) + return 0x100; + if ((resFlags & GX2R_RESFLAG_USAGE_GS_RINGBUFFER) != 0) + return 0x100; + + if ((resFlags & GX2R_RESFLAG_USAGE_VERTEX_BUFFER) != 0) + return 0x40; + if ((resFlags & GX2R_RESFLAG_USAGE_INDEX_BUFFER) != 0) + return 0x40; + if ((resFlags & GX2R_RESFLAG_USAGE_DISPLAY_LIST) != 0) + return 0x40; + + return 0x100; + } + + bool GX2RCreateBuffer(GX2RBuffer* buffer) + { + uint32 bufferAlignment = GX2RGetBufferAlignment(buffer->resFlags); + uint32 bufferSize = GX2RGetBufferAllocationSize(buffer); + MPTR allocResult = PPCCoreCallback(GX2RAllocateFunc, (uint32)buffer->resFlags, bufferSize, bufferAlignment); + buffer->ptr = allocResult; + buffer->resFlags &= ~GX2R_RESFLAG_LOCKED; + buffer->resFlags |= GX2R_RESFLAG_ALLOCATED_BY_GX2R; + // todo: invalidation + return allocResult != MPTR_NULL; + } + + bool GX2RCreateBufferUserMemory(GX2RBuffer* buffer, void* ptr, uint32 unusedSizeParameter) + { + buffer->ptr = ptr; + buffer->resFlags &= ~GX2R_RESFLAG_LOCKED; + buffer->resFlags &= ~GX2R_RESFLAG_ALLOCATED_BY_GX2R; + // todo: invalidation + return true; + } + + void GX2RDestroyBufferEx(GX2RBuffer* buffer, uint32 resFlags) + { + if ((buffer->resFlags & GX2R_RESFLAG_ALLOCATED_BY_GX2R) == 0) + { + // this buffer is user-allocated + buffer->ptr = nullptr; + return; + } + PPCCoreCallback(GX2RFreeFunc, (uint32)buffer->resFlags, buffer->GetPtr()); + buffer->ptr = nullptr; + } + + bool GX2RBufferExists(GX2RBuffer* buffer) + { + if (!buffer) + return false; + if (!buffer->GetPtr()) + return false; + return true; + } + + void* GX2RLockBufferEx(GX2RBuffer* buffer, uint32 resFlags) + { + return buffer->GetPtr(); + } + + void GX2RUnlockBufferEx(GX2RBuffer* buffer, uint32 resFlags) + { + // todo - account for flags, not all buffer types need flushing + LatteBufferCache_notifyDCFlush(buffer->GetVirtualAddr(), buffer->GetSize()); + } + + void GX2RInvalidateBuffer(GX2RBuffer* buffer, uint32 resFlags) + { + // todo - account for flags, not all buffer types need flushing + LatteBufferCache_notifyDCFlush(buffer->GetVirtualAddr(), buffer->GetSize()); + } + + void GX2RSetAttributeBuffer(GX2RBuffer* buffer, uint32 bufferIndex, uint32 stride, uint32 offset) + { + uint32 bufferSize = buffer->GetSize(); + if (offset > bufferSize) + cemuLog_log(LogType::Force, "GX2RSetAttributeBuffer(): Offset exceeds buffer size"); + GX2SetAttribBuffer(bufferIndex, bufferSize - offset, stride, ((uint8be*)buffer->GetPtr()) + offset); + } + + void GX2RSetStreamOutBuffer(uint32 bufferIndex, GX2StreamOutBuffer* soBuffer) + { + // seen in CoD: Ghosts + GX2SetStreamOutBuffer(bufferIndex, soBuffer); + } + + bool GX2RCreateSurface(GX2Surface* surface, uint32 resFlags) + { + // seen in Transformers Prime + surface->resFlag = resFlags; + GX2CalcSurfaceSizeAndAlignment(surface); + surface->resFlag &= ~GX2R_RESFLAG_LOCKED; + surface->resFlag |= GX2R_RESFLAG_ALLOCATED_BY_GX2R; + MPTR allocResult = PPCCoreCallback(GX2RAllocateFunc, (uint32)surface->resFlag, (uint32)surface->imageSize + (uint32)surface->mipSize, (uint32)surface->alignment); + surface->imagePtr = allocResult; + if (surface->imagePtr != MPTR_NULL && surface->mipSize > 0) + { + surface->mipPtr = (uint32)surface->imagePtr + surface->imageSize; + } + else + { + surface->mipPtr = MPTR_NULL; + } + // todo: Cache invalidation based on resourceFlags? + return allocResult != MPTR_NULL; + } + + bool GX2RCreateSurfaceUserMemory(GX2Surface* surface, void* imagePtr, void* mipPtr, uint32 resFlags) + { + surface->resFlag = resFlags; + surface->resFlag &= ~(GX2R_RESFLAG_LOCKED | GX2R_RESFLAG_ALLOCATED_BY_GX2R); + GX2CalcSurfaceSizeAndAlignment(surface); + surface->imagePtr = memory_getVirtualOffsetFromPointer(imagePtr); + surface->mipPtr = memory_getVirtualOffsetFromPointer(mipPtr); + if (surface->resFlag & 0x14000) + { + // memory invalidate + } + return true; + } + + void GX2RDestroySurfaceEx(GX2Surface* surface, uint32 resFlags) + { + if ((surface->resFlag & GX2R_RESFLAG_ALLOCATED_BY_GX2R) == 0) + { + // this surface is user-allocated + surface->imagePtr = MPTR_NULL; + return; + } + resFlags &= (GX2R_RESFLAG_UKN_BIT_19 | GX2R_RESFLAG_UKN_BIT_20 | GX2R_RESFLAG_UKN_BIT_21 | GX2R_RESFLAG_UKN_BIT_22 | GX2R_RESFLAG_UKN_BIT_23); + PPCCoreCallback(GX2RFreeFunc, (uint32)surface->resFlag | resFlags, (uint32)surface->imagePtr); + surface->imagePtr = MPTR_NULL; + } + + bool GX2RSurfaceExists(GX2Surface* surface) + { + if (!surface) + return false; + if (surface->imagePtr == MPTR_NULL) + return false; + if ((surface->resFlag & (GX2R_RESFLAG_USAGE_CPU_READ | GX2R_RESFLAG_USAGE_CPU_WRITE | GX2R_RESFLAG_USAGE_GPU_READ | GX2R_RESFLAG_USAGE_GPU_WRITE)) == 0) + return false; + return true; + } + + void* GX2RLockSurfaceEx(GX2Surface* surface, uint32 mipLevel, uint32 resFlags) + { + // todo: handle invalidation + surface->resFlag |= GX2R_RESFLAG_LOCKED; + return memory_getPointerFromVirtualOffset(surface->imagePtr); + } + + void GX2RUnlockSurfaceEx(GX2Surface* surface, uint32 mipLevel, uint32 resFlags) + { + // todo: handle invalidation + surface->resFlag &= ~GX2R_RESFLAG_LOCKED; + } + + void GX2RBeginDisplayListEx(GX2RBuffer* buffer, bool ukn, uint32 resFlags) + { + // todo: handle invalidation + GX2::GX2BeginDisplayList(buffer->GetPtr(), buffer->GetSize()); + } + + uint32 GX2REndDisplayList(GX2RBuffer* buffer) + { + return GX2::GX2EndDisplayList(buffer->GetPtr()); + } + + void GX2RCallDisplayList(GX2RBuffer* buffer, uint32 size) + { + GX2::GX2CallDisplayList(buffer->GetVirtualAddr(), size); + } + + void GX2RDirectCallDisplayList(GX2RBuffer* buffer, uint32 size) + { + GX2::GX2DirectCallDisplayList(buffer->GetPtr(), size); + } + + void GX2RDrawIndexed(GX2PrimitiveMode2 primitiveMode, GX2RBuffer* indexBuffer, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, uint32 count, uint32 baseIndex, uint32 baseVertex, uint32 numInstances) + { + GX2DrawIndexedEx(primitiveMode, count, indexType, (uint8be*)indexBuffer->GetPtr() + (baseIndex * (uint32)indexBuffer->elementSize), baseVertex, numInstances); + } + + void GX2ResourceInit() + { + cafeExportRegister("gx2", GX2RSetAllocator, LogType::GX2); + cafeExportRegister("gx2", GX2RGetBufferAllocationSize, LogType::GX2); + cafeExportRegister("gx2", GX2RGetBufferAlignment, LogType::GX2); + + cafeExportRegister("gx2", GX2RCreateBuffer, LogType::GX2); + cafeExportRegister("gx2", GX2RCreateBufferUserMemory, LogType::GX2); + cafeExportRegister("gx2", GX2RDestroyBufferEx, LogType::GX2); + cafeExportRegister("gx2", GX2RBufferExists, LogType::GX2); + cafeExportRegister("gx2", GX2RLockBufferEx, LogType::GX2); + cafeExportRegister("gx2", GX2RUnlockBufferEx, LogType::GX2); + cafeExportRegister("gx2", GX2RInvalidateBuffer, LogType::GX2); + + cafeExportRegister("gx2", GX2RSetAttributeBuffer, LogType::GX2); + cafeExportRegister("gx2", GX2RSetStreamOutBuffer, LogType::GX2); + + cafeExportRegister("gx2", GX2RCreateSurface, LogType::GX2); + cafeExportRegister("gx2", GX2RCreateSurfaceUserMemory, LogType::GX2); + cafeExportRegister("gx2", GX2RDestroySurfaceEx, LogType::GX2); + cafeExportRegister("gx2", GX2RSurfaceExists, LogType::GX2); + cafeExportRegister("gx2", GX2RLockSurfaceEx, LogType::GX2); + cafeExportRegister("gx2", GX2RUnlockSurfaceEx, LogType::GX2); + + cafeExportRegister("gx2", GX2RBeginDisplayListEx, LogType::GX2); + cafeExportRegister("gx2", GX2REndDisplayList, LogType::GX2); + cafeExportRegister("gx2", GX2RCallDisplayList, LogType::GX2); + cafeExportRegister("gx2", GX2RDirectCallDisplayList, LogType::GX2); + + cafeExportRegister("gx2", GX2RDrawIndexed, LogType::GX2); + + GX2RAllocateFunc = MPTR_NULL; + GX2RFreeFunc = MPTR_NULL; + } +}; \ No newline at end of file diff --git a/src/Cafe/OS/libs/gx2/GX2_Resource.h b/src/Cafe/OS/libs/gx2/GX2_Resource.h new file mode 100644 index 00000000..e0075ff4 --- /dev/null +++ b/src/Cafe/OS/libs/gx2/GX2_Resource.h @@ -0,0 +1,64 @@ +#pragma once + +// basic resource flags +#define GX2_RESFLAG_USAGE_TEXTURE (1<<0) +#define GX2_RESFLAG_USAGE_COLOR_BUFFER (1<<1) +#define GX2_RESFLAG_USAGE_DEPTH_BUFFER (1<<2) +#define GX2_RESFLAG_USAGE_SCAN_BUFFER (1<<3) + +// extended resource flags used by GX2R API +#define GX2R_RESFLAG_USAGE_VERTEX_BUFFER (1<<4) +#define GX2R_RESFLAG_USAGE_INDEX_BUFFER (1<<5) +#define GX2R_RESFLAG_USAGE_UNIFORM_BLOCK (1<<6) +#define GX2R_RESFLAG_USAGE_SHADER_PROGRAM (1<<7) +#define GX2R_RESFLAG_USAGE_STREAM_OUTPUT (1<<8) +#define GX2R_RESFLAG_USAGE_DISPLAY_LIST (1<<9) +#define GX2R_RESFLAG_USAGE_GS_RINGBUFFER (1<<10) + +#define GX2R_RESFLAG_USAGE_CPU_READ (1<<11) +#define GX2R_RESFLAG_USAGE_CPU_WRITE (1<<12) +#define GX2R_RESFLAG_USAGE_GPU_READ (1<<13) +#define GX2R_RESFLAG_USAGE_GPU_WRITE (1<<14) + +#define GX2R_RESFLAG_USE_MEM1 (1<<17) + +#define GX2R_RESFLAG_UKN_BIT_19 (1<<19) +#define GX2R_RESFLAG_UKN_BIT_20 (1<<20) +#define GX2R_RESFLAG_UKN_BIT_21 (1<<21) +#define GX2R_RESFLAG_UKN_BIT_22 (1<<22) +#define GX2R_RESFLAG_UKN_BIT_23 (1<<23) + +#define GX2R_RESFLAG_ALLOCATED_BY_GX2R (1<<29) +#define GX2R_RESFLAG_LOCKED (1<<30) + +struct GX2RBuffer +{ + /* +0x00 */ uint32be resFlags; + /* +0x04 */ uint32be elementSize; + /* +0x08 */ uint32be elementCount; + /* +0x0C */ MEMPTR<void> ptr; + + uint32 GetSize() const + { + return (uint32)elementSize * (uint32)elementCount; + } + + MPTR GetVirtualAddr() const + { + return ptr.GetMPTR(); + } + + void* GetPtr() const + { + return ptr.GetPtr(); + } +}; + +static_assert(sizeof(GX2RBuffer) == 0x10); + +namespace GX2 +{ + void GX2ResourceInit(); + + void GX2RSetAllocator(MPTR funcAllocMPTR, MPTR funcFreeMPR); +}; \ No newline at end of file diff --git a/src/Cafe/OS/libs/gx2/GX2_Shader.cpp b/src/Cafe/OS/libs/gx2/GX2_Shader.cpp new file mode 100644 index 00000000..c63688eb --- /dev/null +++ b/src/Cafe/OS/libs/gx2/GX2_Shader.cpp @@ -0,0 +1,264 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "GX2.h" +#include "GX2_Shader.h" +#include "Cafe/HW/Latte/Core/LatteConst.h" +#include "Cafe/HW/Latte/Core/LattePM4.h" +#include "Cafe/HW/Latte/ISA/LatteInstructions.h" + +uint32 memory_getVirtualOffsetFromPointer(void* ptr); // remove once we updated everything to MEMPTR + +namespace GX2 +{ + using namespace Latte; + + LatteConst::VertexFetchEndianMode _getVtxFormatEndianSwapDefault(uint32 vertexFormat) + { + switch (vertexFormat) + { + case 0: + case 1: + case 4: + case 10: + return LatteConst::VertexFetchEndianMode::SWAP_NONE; // 0 + case 2: + case 3: + case 7: + case 8: + case 14: + case 15: + return LatteConst::VertexFetchEndianMode::SWAP_U16; // 1 + case 5: + case 6: + case 9: + case 11: + case 12: + case 13: + case 16: + case 17: + case 18: + case 19: + return LatteConst::VertexFetchEndianMode::SWAP_U32; // 2 + default: + break; + } + cemu_assert_suspicious(); + return LatteConst::VertexFetchEndianMode::SWAP_NONE; + } + + uint32 rawFormatToFetchFormat[] = + { + 1, 2, 5, 6, + 7, 0xD, 0xE, 0xF, + 0x10, 0x16, 0x1A, 0x19, + 0x1D, 0x1E, 0x1F, 0x20, + 0x2F, 0x30, 0x22, 0x23, + }; + + struct GX2AttribDescription + { + /* +0x00 */ uint32 location; + /* +0x04 */ uint32 buffer; + /* +0x08 */ uint32be offset; + /* +0x0C */ uint32 format; + /* +0x10 */ uint32 indexType; + /* +0x14 */ uint32 aluDivisor; + /* +0x18 */ uint32 destSel; + /* +0x1C */ betype<LatteConst::VertexFetchEndianMode> endianSwap; + }; + + static_assert(sizeof(GX2AttribDescription) == 0x20); + static_assert(sizeof(betype<LatteConst::VertexFetchEndianMode>) == 0x4); + + // calculate size of CF program subpart, includes alignment padding for clause instructions + size_t _calcFetchShaderCFCodeSize(uint32 attributeCount, GX2FetchShader_t::FetchShaderType fetchShaderType, uint32 tessellationMode) + { + cemu_assert_debug(fetchShaderType == GX2FetchShader_t::FetchShaderType::NO_TESSELATION); + cemu_assert_debug(tessellationMode == 0); + uint32 numCFInstructions = ((attributeCount + 15) / 16) + 1; // one VTX clause can have up to 16 instructions + final CF instruction is RETURN + size_t cfSize = numCFInstructions * 8; + cfSize = (cfSize + 0xF) & ~0xF; // pad to 16 byte alignment + return cfSize; + } + + size_t _calcFetchShaderClauseCodeSize(uint32 attributeCount, GX2FetchShader_t::FetchShaderType fetchShaderType, uint32 tessellationMode) + { + cemu_assert_debug(fetchShaderType == GX2FetchShader_t::FetchShaderType::NO_TESSELATION); + cemu_assert_debug(tessellationMode == 0); + uint32 numClauseInstructions = attributeCount; + size_t clauseSize = numClauseInstructions * 16; + return clauseSize; + } + + void _writeFetchShaderCFCode(void* programBufferOut, uint32 attributeCount, GX2FetchShader_t::FetchShaderType fetchShaderType, uint32 tessellationMode) + { + LatteCFInstruction* cfInstructionWriter = (LatteCFInstruction*)programBufferOut; + uint32 attributeIndex = 0; + uint32 cfSize = (uint32)_calcFetchShaderCFCodeSize(attributeCount, fetchShaderType, tessellationMode); + while (attributeIndex < attributeCount) + { + LatteCFInstruction_DEFAULT defaultInstr; + defaultInstr.setField_Opcode(LatteCFInstruction::INST_VTX_TC); + defaultInstr.setField_COUNT(std::min(attributeCount - attributeIndex, 16u)); + defaultInstr.setField_ADDR(cfSize + attributeIndex*16); + memcpy(cfInstructionWriter, &defaultInstr, sizeof(LatteCFInstruction)); + attributeIndex += 16; + cfInstructionWriter++; + } + // write RETURN instruction + LatteCFInstruction_DEFAULT returnInstr; + returnInstr.setField_Opcode(LatteCFInstruction::INST_RETURN); + returnInstr.setField_BARRIER(true); + memcpy(cfInstructionWriter, &returnInstr, sizeof(LatteCFInstruction)); + } + + void _writeFetchShaderVTXCode(GX2FetchShader_t* fetchShader, void* programOut, uint32 attributeCount, GX2AttribDescription* attributeDescription, GX2FetchShader_t::FetchShaderType fetchShaderType, uint32 tessellationMode) + { + uint8* writePtr = (uint8*)programOut; + // one instruction per attribute (hardcoded into _writeFetchShaderCFCode) + for (uint32 i = 0; i < attributeCount; i++) + { + uint32 attrFormat = _swapEndianU32(attributeDescription[i].format); + uint32 attrDestSel = _swapEndianU32(attributeDescription[i].destSel); + uint32 attrLocation = _swapEndianU32(attributeDescription[i].location); + uint32 attrBufferId = _swapEndianU32(attributeDescription[i].buffer); + uint32 attrIndexType = _swapEndianU32(attributeDescription[i].indexType); + uint32 attrAluDivisor = _swapEndianU32(attributeDescription[i].aluDivisor); + + cemu_assert_debug(attrIndexType <= 1); + + LatteConst::VertexFetchEndianMode endianSwap = attributeDescription[i].endianSwap; + if (endianSwap == LatteConst::VertexFetchEndianMode::SWAP_DEFAULT) // use per-format default + endianSwap = _getVtxFormatEndianSwapDefault(attrFormat & 0x3F); + + uint32 srcSelX = 0; // this field is used to store the divisor index/mode (0 -> per-vertex index, 1 -> alu divisor 0, 2 -> alu divisor 1, 3 -> per-instance index) + if (attrIndexType == 0) + { + srcSelX = 0; // increase index per vertex + } + else if (attrIndexType == 1) + { + // instance based index + if (attrAluDivisor == 1) + { + // special encoding if alu divisor is 1 + srcSelX = 3; + } + else + { + cemu_assert_debug(attrAluDivisor != 0); // divisor should not be zero if instance based index is selected? + // store alu divisor in divisor table (up to two entries) + uint32 numDivisors = _swapEndianU32(fetchShader->divisorCount); + bool divisorFound = false; + for (uint32 i = 0; i < numDivisors; i++) + { + if (_swapEndianU32(fetchShader->divisors[i]) == attrAluDivisor) + { + srcSelX = i != 0 ? 2 : 1; + divisorFound = true; + break; + } + } + if (divisorFound == false) + { + // add new divisor + if (numDivisors >= 2) + { + cemu_assert_debug(false); // not enough space for additional divisor + } + else + { + srcSelX = numDivisors != 0 ? 2 : 1; + fetchShader->divisors[numDivisors] = _swapEndianU32(attrAluDivisor); + numDivisors++; + fetchShader->divisorCount = _swapEndianU32(numDivisors); + } + } + } + } + else + { + cemu_assert_debug(false); + } + + // convert attribute format to fetch format + uint32 fetchFormat = rawFormatToFetchFormat[attrFormat & 0x3F] & 0x3F; + + uint32 nfa = 0; + if ((attrFormat & 0x800) != 0) + nfa = 2; + else if ((attrFormat & 0x100) != 0) + nfa = 1; + else + nfa = 0; + + LatteClauseInstruction_VTX vtxInstruction; + vtxInstruction.setField_VTX_INST(LatteClauseInstruction_VTX::VTX_INST::_VTX_INST_SEMANTIC); + vtxInstruction.setFieldSEM_SEMANTIC_ID(attrLocation&0xFF); + vtxInstruction.setField_BUFFER_ID(attrBufferId + 0xA0); + vtxInstruction.setField_FETCH_TYPE((LatteConst::VertexFetchType2)attrIndexType); + vtxInstruction.setField_SRC_SEL_X((LatteClauseInstruction_VTX::SRC_SEL)srcSelX); + vtxInstruction.setField_DATA_FORMAT((LatteConst::VertexFetchFormat)fetchFormat); + vtxInstruction.setField_NUM_FORMAT_ALL((LatteClauseInstruction_VTX::NUM_FORMAT_ALL)nfa); + vtxInstruction.setField_OFFSET(attributeDescription[i].offset); + if ((attrFormat & 0x200) != 0) + vtxInstruction.setField_FORMAT_COMP_ALL(LatteClauseInstruction_VTX::FORMAT_COMP::COMP_SIGNED); + vtxInstruction.setField_ENDIAN_SWAP((LatteConst::VertexFetchEndianMode)endianSwap); + vtxInstruction.setField_DST_SEL(0, (LatteClauseInstruction_VTX::DST_SEL)((attrDestSel >> 24) & 0x7)); + vtxInstruction.setField_DST_SEL(1, (LatteClauseInstruction_VTX::DST_SEL)((attrDestSel >> 16) & 0x7)); + vtxInstruction.setField_DST_SEL(2, (LatteClauseInstruction_VTX::DST_SEL)((attrDestSel >> 8) & 0x7)); + vtxInstruction.setField_DST_SEL(3, (LatteClauseInstruction_VTX::DST_SEL)((attrDestSel >> 0) & 0x7)); + + memcpy(writePtr, &vtxInstruction, 16); + writePtr += 16; + } + } + + uint32 GX2CalcFetchShaderSizeEx(uint32 attributeCount, GX2FetchShader_t::FetchShaderType fetchShaderType, uint32 tessellationMode) + { + cemu_assert_debug(fetchShaderType == GX2FetchShader_t::FetchShaderType::NO_TESSELATION); // other types are todo + cemu_assert_debug(tessellationMode == 0); // other modes are todo + + uint32 finalSize = + (uint32)_calcFetchShaderCFCodeSize(attributeCount, fetchShaderType, tessellationMode) + + (uint32)_calcFetchShaderClauseCodeSize(attributeCount, fetchShaderType, tessellationMode); + + return finalSize; + } + + void GX2InitFetchShaderEx(GX2FetchShader_t* fetchShader, void* programBufferOut, uint32 attributeCount, GX2AttribDescription* attributeDescription, GX2FetchShader_t::FetchShaderType fetchShaderType, uint32 tessellationMode) + { + cemu_assert_debug(fetchShaderType == GX2FetchShader_t::FetchShaderType::NO_TESSELATION); + cemu_assert_debug(tessellationMode == 0); + + /* + Fetch shader program: + [CF_PROGRAM] + [Last CF instruction: 0x0 0x8A000000 (INST_RETURN)] + [PAD_TO_16_ALIGNMENT] + [CLAUSES] + */ + + memset(fetchShader, 0x00, sizeof(GX2FetchShader_t)); + fetchShader->attribCount = _swapEndianU32(attributeCount); + fetchShader->shaderPtr = (MPTR)_swapEndianU32(memory_getVirtualOffsetFromPointer(programBufferOut)); + + uint8* shaderStart = (uint8*)programBufferOut; + uint8* shaderOutput = shaderStart; + + _writeFetchShaderCFCode(shaderOutput, attributeCount, fetchShaderType, tessellationMode); + shaderOutput += _calcFetchShaderCFCodeSize(attributeCount, fetchShaderType, tessellationMode); + _writeFetchShaderVTXCode(fetchShader, shaderOutput, attributeCount, attributeDescription, fetchShaderType, tessellationMode); + shaderOutput += _calcFetchShaderClauseCodeSize(attributeCount, fetchShaderType, tessellationMode); + + uint32 shaderSize = (uint32)(shaderOutput - shaderStart); + cemu_assert_debug(shaderSize == GX2CalcFetchShaderSizeEx(attributeCount, GX2FetchShader_t::FetchShaderType::NO_TESSELATION, tessellationMode)); + + fetchShader->shaderSize = _swapEndianU32((uint32)(shaderOutput - shaderStart)); + } + + void GX2ShaderInit() + { + cafeExportRegister("gx2", GX2CalcFetchShaderSizeEx, LogType::GX2); + cafeExportRegister("gx2", GX2InitFetchShaderEx, LogType::GX2); + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/gx2/GX2_Shader.h b/src/Cafe/OS/libs/gx2/GX2_Shader.h new file mode 100644 index 00000000..1d1c79cc --- /dev/null +++ b/src/Cafe/OS/libs/gx2/GX2_Shader.h @@ -0,0 +1,172 @@ +#pragma once +#include "Cafe/HW/Latte/ISA/LatteReg.h" +#include "GX2_Streamout.h" + +struct GX2FetchShader_t +{ + enum class FetchShaderType : uint32 + { + NO_TESSELATION = 0, + }; + + /* +0x00 */ betype<FetchShaderType> fetchShaderType; + /* +0x04 */ uint32 _regs[1]; + /* +0x08 */ uint32 shaderSize; + /* +0x0C */ MPTR shaderPtr; + /* +0x10 */ uint32 attribCount; + /* +0x14 */ uint32 divisorCount; + /* +0x18 */ uint32 divisors[2]; + + MPTR GetProgramAddr() const + { + return _swapEndianU32(shaderPtr); + } +}; + +static_assert(sizeof(GX2FetchShader_t) == 0x20); +static_assert(sizeof(betype<GX2FetchShader_t::FetchShaderType>) == 4); + +namespace GX2 +{ + + void GX2ShaderInit(); +} + +// code below still needs to be modernized (use betype, enum classes) + +#define GX2_SHADER_MODE_UNIFORM_REGISTER 0 +#define GX2_SHADER_MODE_UNIFORM_BLOCK 1 +#define GX2_SHADER_MODE_GEOMETRY_SHADER 2 +#define GX2_SHADER_MODE_COMPUTE_SHADER 3 + +struct GX2VertexShader_t +{ + /* +0x000 */ uint32 regs[52]; + /* +0x0D0 */ uint32 shaderSize; + /* +0x0D4 */ MPTR shaderPtr; + /* +0x0D8 */ uint32 shaderMode; // GX2_SHADER_MODE_* + /* +0x0DC */ uint32 uniformBlockCount; + /* +0x0E0 */ MPTR uniformBlockInfo; + /* +0x0E4 */ uint32 uniformVarCount; + /* +0x0E8 */ MPTR uniformVarInfo; + /* +0x0EC */ uint32 uknEC; + /* +0x0F0 */ MPTR uknF0; + /* +0x0F4 */ uint32 uknF4; + /* +0x0F8 */ MPTR uknF8; // each entry has 8 byte? + /* +0x0FC */ uint32 samplerCount; + /* +0x100 */ MPTR samplerInfo; + /* +0x104 */ uint32 attribCount; + /* +0x108 */ MPTR attribInfo; + /* +0x10C */ uint32 ringItemsize; // for GS + /* +0x110 */ uint32 usesStreamOut; + /* +0x114 */ uint32 streamOutVertexStride[GX2_MAX_STREAMOUT_BUFFERS]; + /* +0x124 */ GX2RBuffer rBuffer; + + MPTR GetProgramAddr() const + { + if (_swapEndianU32(this->shaderPtr) != MPTR_NULL) + return _swapEndianU32(this->shaderPtr); + return this->rBuffer.GetVirtualAddr(); + } +}; + +static_assert(sizeof(GX2VertexShader_t) == 0x134); + +typedef struct _GX2PixelShader +{ + uint32 regs[41]; + // regs: + // 0 ? Used by GPR count API? + // 1 ? + // 2 mmSPI_PS_IN_CONTROL_0 + // 3 mmSPI_PS_IN_CONTROL_1 + // 4 numInputs + // 5 mmSPI_PS_INPUT_CNTL_0 + // ... + // 36 mmSPI_PS_INPUT_CNTL_31 + // 37 mmCB_SHADER_MASK + // 38 mmCB_SHADER_CONTROL + // 39 mmDB_SHADER_CONTROL + // 40 mmSPI_INPUT_Z + + /* +0xA4 */ uint32 shaderSize; + /* +0xA8 */ MPTR shaderPtr; + /* +0xAC */ uint32 shaderMode; + /* +0xB0 */ uint32 uniformBlockCount; + /* +0xB4 */ MPTR uniformBlockInfo; + /* +0xB8 */ uint32 uniformVarCount; + /* +0xBC */ MPTR uniformVarInfo; + /* +0xC0 */ uint32 uknC0; + /* +0xC4 */ MPTR uknC4; + /* +0xC8 */ uint32 uknC8; + /* +0xCC */ MPTR uknCC; + /* +0xD0 */ uint32 samplerCount; + /* +0xD4 */ MPTR samplerInfo; + /* +0xD8 */ GX2RBuffer rBuffer; + + MPTR GetProgramAddr() const + { + if (_swapEndianU32(shaderPtr) != MPTR_NULL) + return _swapEndianU32(shaderPtr); + return rBuffer.GetVirtualAddr(); + } +}GX2PixelShader_t; + +static_assert(sizeof(GX2PixelShader_t) == 0xE8); + +struct GX2GeometryShader_t +{ + union + { + /* +0x00 */ uint32 regs[19]; + struct + { + uint32be reg0; + uint32be reg1; + uint32be VGT_GS_MODE; + uint32be reg3; + uint32be reg4; + uint32be reg5; + uint32be reg6; + uint32be reg7; + // todo + }reg; + }; + /* +0x4C */ uint32 shaderSize; + /* +0x50 */ MPTR shaderPtr; + /* +0x54 */ uint32 copyShaderSize; + /* +0x58 */ MPTR copyShaderPtr; + /* +0x5C */ uint32 shaderMode; + /* +0x60 */ uint32 uniformBlockCount; + /* +0x64 */ MPTR uniformBlockInfo; + /* +0x68 */ uint32 uniformVarCount; + /* +0x6C */ MPTR uniformVarInfo; + /* +0x70 */ uint32 ukn70; + /* +0x74 */ MPTR ukn74; + /* +0x78 */ uint32 ukn78; + /* +0x7C */ MPTR ukn7C; + /* +0x80 */ uint32 samplerCount; + /* +0x84 */ MPTR samplerInfo; + /* +0x88 */ uint32 ringItemsize; + /* +0x8C */ uint32 useStreamout; + /* +0x90 */ uint32 streamoutStride[GX2_MAX_STREAMOUT_BUFFERS]; + /* +0xA0 */ GX2RBuffer rBuffer; + /* +0xB0 */ GX2RBuffer rBufferCopyProgram; + + MPTR GetGeometryProgramAddr() const + { + if (_swapEndianU32(shaderPtr) != MPTR_NULL) + return _swapEndianU32(shaderPtr); + return rBuffer.GetVirtualAddr(); + } + + MPTR GetCopyProgramAddr() const + { + if (_swapEndianU32(copyShaderPtr) != MPTR_NULL) + return _swapEndianU32(copyShaderPtr); + return rBufferCopyProgram.GetVirtualAddr(); + } + +}; + +static_assert(sizeof(GX2GeometryShader_t) == 0xC0); diff --git a/src/Cafe/OS/libs/gx2/GX2_State.cpp b/src/Cafe/OS/libs/gx2/GX2_State.cpp new file mode 100644 index 00000000..d9c0420f --- /dev/null +++ b/src/Cafe/OS/libs/gx2/GX2_State.cpp @@ -0,0 +1,726 @@ +#include "Common/precompiled.h" +#include "GX2_State.h" +#include "GX2_Command.h" +#include "Cafe/HW/Latte/ISA/LatteReg.h" +#include "Cafe/HW/Latte/Core/LattePM4.h" +#include "Cafe/OS/common/OSCommon.h" + +namespace GX2 +{ + using namespace Latte; + + void GX2InitAlphaTestReg(GX2AlphaTestReg* reg, uint32 alphaTestEnable, GX2_ALPHAFUNC alphaFunc, float alphaRef) + { + Latte::LATTE_SX_ALPHA_TEST_CONTROL tmpRegCtrl; + tmpRegCtrl.set_ALPHA_FUNC(alphaFunc); + tmpRegCtrl.set_ALPHA_TEST_ENABLE(alphaTestEnable != 0); + reg->regAlphaTestControl = tmpRegCtrl; + + Latte::LATTE_SX_ALPHA_REF tmpRegRef; + tmpRegRef.set_ALPHA_TEST_REF(alphaRef); + reg->regAlphaTestRef = tmpRegRef; + } + + void GX2SetAlphaTestReg(GX2AlphaTestReg* reg) + { + GX2ReserveCmdSpace(3 + 3); + gx2WriteGather_submit( + pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), + Latte::REGADDR::SX_ALPHA_TEST_CONTROL - 0xA000, + reg->regAlphaTestControl, + pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), + Latte::REGADDR::SX_ALPHA_REF - 0xA000, + reg->regAlphaTestRef); + } + + void GX2SetAlphaTest(uint32 alphaTestEnable, GX2_ALPHAFUNC alphaFunc, float alphaRef) + { + GX2AlphaTestReg tmpReg; + GX2InitAlphaTestReg(&tmpReg, alphaTestEnable, alphaFunc, alphaRef); + GX2SetAlphaTestReg(&tmpReg); + } + + void GX2InitColorControlReg(GX2ColorControlReg* reg, GX2_LOGICOP logicOp, uint32 blendMask, uint32 multiwriteEnable, uint32 colorBufferEnable) + { + Latte::LATTE_CB_COLOR_CONTROL colorControlReg2; + colorControlReg2.set_MULTIWRITE_ENABLE(multiwriteEnable != 0); + if (colorBufferEnable == 0) + colorControlReg2.set_SPECIAL_OP(Latte::LATTE_CB_COLOR_CONTROL::E_SPECIALOP::DISABLE); + else + colorControlReg2.set_SPECIAL_OP(Latte::LATTE_CB_COLOR_CONTROL::E_SPECIALOP::NORMAL); + colorControlReg2.set_BLEND_MASK(blendMask); + colorControlReg2.set_ROP(logicOp); + reg->reg = colorControlReg2; + } + + void GX2SetColorControlReg(GX2ColorControlReg* reg) + { + GX2ReserveCmdSpace(3); + gx2WriteGather_submit( + pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), + Latte::REGADDR::CB_COLOR_CONTROL - 0xA000, + reg->reg); + } + + void GX2SetColorControl(GX2_LOGICOP logicOp, uint32 blendMask, uint32 multiwriteEnable, uint32 colorBufferEnable) + { + GX2ColorControlReg colorControlReg; + GX2InitColorControlReg(&colorControlReg, logicOp, blendMask, multiwriteEnable, colorBufferEnable); + GX2SetColorControlReg(&colorControlReg); + } + + + void GX2InitPolygonControlReg(GX2PolygonControlReg* reg, + Latte::LATTE_PA_SU_SC_MODE_CNTL::E_FRONTFACE frontFace, + uint32 cullFront, + uint32 cullBack, + Latte::LATTE_PA_SU_SC_MODE_CNTL::E_POLYGONMODE usePolygonMode, + Latte::LATTE_PA_SU_SC_MODE_CNTL::E_PTYPE polyModeFront, + Latte::LATTE_PA_SU_SC_MODE_CNTL::E_PTYPE polyModeBack, + uint32 polygonOffsetFrontEnable, + uint32 polygonOffsetBackEnable, + uint32 paraOffsetEnable) + { + Latte::LATTE_PA_SU_SC_MODE_CNTL v; + v.set_FRONT_FACE(frontFace); + v.set_CULL_FRONT((cullFront & 1) != 0); + v.set_CULL_BACK((cullBack & 1) != 0); + v.set_POLYGON_MODE(usePolygonMode); + v.set_FRONT_POLY_MODE(polyModeFront); + v.set_BACK_POLY_MODE(polyModeBack); + v.set_OFFSET_PARA_ENABLED((paraOffsetEnable & 1) != 0); + v.set_OFFSET_FRONT_ENABLED((polygonOffsetFrontEnable & 1) != 0); + v.set_OFFSET_BACK_ENABLED((polygonOffsetBackEnable & 1) != 0); + reg->reg = v; + } + + void GX2SetPolygonControlReg(GX2PolygonControlReg* reg) + { + GX2ReserveCmdSpace(3); + gx2WriteGather_submit( + pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), + Latte::REGADDR::PA_SU_SC_MODE_CNTL - 0xA000, + reg->reg); + } + + void GX2SetPolygonControl(Latte::LATTE_PA_SU_SC_MODE_CNTL::E_FRONTFACE frontFace, + uint32 cullFront, + uint32 cullBack, + Latte::LATTE_PA_SU_SC_MODE_CNTL::E_POLYGONMODE usePolygonMode, + Latte::LATTE_PA_SU_SC_MODE_CNTL::E_PTYPE polyModeFront, + Latte::LATTE_PA_SU_SC_MODE_CNTL::E_PTYPE polyModeBack, + uint32 polygonOffsetFrontEnable, + uint32 polygonOffsetBackEnable, + uint32 paraOffsetEnable) + { + GX2PolygonControlReg reg{}; + GX2InitPolygonControlReg(®, frontFace, cullFront, cullBack, usePolygonMode, polyModeFront, polyModeBack, polygonOffsetFrontEnable, polygonOffsetBackEnable, paraOffsetEnable); + GX2SetPolygonControlReg(®); + } + + void GX2SetCullOnlyControl(Latte::LATTE_PA_SU_SC_MODE_CNTL::E_FRONTFACE frontFace, uint32 cullFront, uint32 cullBack) + { + GX2PolygonControlReg reg{}; + GX2InitPolygonControlReg(®, frontFace, cullFront, cullBack, Latte::LATTE_PA_SU_SC_MODE_CNTL::E_POLYGONMODE::UKN0, Latte::LATTE_PA_SU_SC_MODE_CNTL::E_PTYPE::POINTS, Latte::LATTE_PA_SU_SC_MODE_CNTL::E_PTYPE::POINTS, 0, 0, 0); + GX2SetPolygonControlReg(®); + } + + void GX2InitPolygonOffsetReg(GX2PolygonOffsetReg* reg, float frontOffset, float frontScale, float backOffset, float backScale, float clampOffset) + { + frontScale *= 16.0; + backScale *= 16.0; + reg->regFrontScale = Latte::LATTE_PA_SU_POLY_OFFSET_FRONT_SCALE().set_SCALE(frontScale); + reg->regFrontOffset = Latte::LATTE_PA_SU_POLY_OFFSET_FRONT_OFFSET().set_OFFSET(frontOffset); + reg->regBackScale = Latte::LATTE_PA_SU_POLY_OFFSET_BACK_SCALE().set_SCALE(backScale); + reg->regBackOffset = Latte::LATTE_PA_SU_POLY_OFFSET_BACK_OFFSET().set_OFFSET(backOffset); + reg->regClamp = Latte::LATTE_PA_SU_POLY_OFFSET_CLAMP().set_CLAMP(clampOffset); + } + + void GX2SetPolygonOffsetReg(GX2PolygonOffsetReg* reg) + { + GX2ReserveCmdSpace(6 + 3); + + gx2WriteGather_submit( + pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 4), + Latte::REGADDR::PA_SU_POLY_OFFSET_FRONT_SCALE - 0xA000, + reg->regFrontScale, + reg->regFrontOffset, + reg->regBackScale, + reg->regBackOffset, + + pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), + Latte::REGADDR::PA_SU_POLY_OFFSET_CLAMP - 0xA000, + reg->regClamp); + } + + void GX2SetPolygonOffset(float frontOffset, float frontScale, float backOffset, float backScale, float clampOffset) + { + GX2PolygonOffsetReg tmpReg; + GX2InitPolygonOffsetReg(&tmpReg, frontOffset, frontScale, backOffset, backScale, clampOffset); + GX2SetPolygonOffsetReg(&tmpReg); + } + + void GX2SetRasterizerClipControlEx(bool enableRasterizer, bool enableZClip, bool enableHalfZ) + { + GX2ReserveCmdSpace(3); + + //if (enableHalfZ) + //{ + // // Smash has a bug where it enables half space clipping during streamout drawcalls and shadowing and then doesn't turn it off until the next GX2SetRasterizerClipControl call + // // this leads to some stuff being rendered at the wrong z-plane (e.g. shields behind characters) if the game's default depth range -1 to 1 isn't supported (on OpenGL only Nvidia's glDepthRangedNV allows unclamped values) + // uint64 titleId = gameMeta_getTitleId(); + // if (titleId == 0x0005000010144F00ULL || + // titleId == 0x0005000010145000ULL || + // titleId == 0x0005000010110E00ULL) + // { + // // force disable half space clipping + // if (g_renderer && g_renderer->GetType() == RendererAPI::OpenGL && LatteGPUState.glVendor != GLVENDOR_NVIDIA) + // enableHalfZ = false; + // } + //} + + Latte::LATTE_PA_CL_CLIP_CNTL reg{}; + reg.set_ZCLIP_NEAR_DISABLE(!enableZClip).set_ZCLIP_FAR_DISABLE(!enableZClip); + reg.set_DX_RASTERIZATION_KILL(!enableRasterizer); + reg.set_DX_CLIP_SPACE_DEF(enableHalfZ); + reg.set_DX_LINEAR_ATTR_CLIP_ENA(true); + + gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), + Latte::REGADDR::PA_CL_CLIP_CNTL - 0xA000, + reg); + } + + void GX2SetRasterizerClipControl(bool enableRasterizer, bool enableZClip) + { + GX2SetRasterizerClipControlEx(enableRasterizer, enableZClip, false); + } + + void GX2SetRasterizerClipControlHalfZ(bool enableRasterizer, bool enableZClip, bool enableHalfZ) + { + GX2SetRasterizerClipControlEx(enableRasterizer, enableZClip, enableHalfZ); + } + + void GX2InitViewportReg(GX2ViewportReg* viewportReg, float x, float y, float width, float height, float nearZ, float farZ) + { + // todo: set clipping registers and zMin/zMax registers + viewportReg->xScale = Latte::LATTE_PA_CL_VPORT_XSCALE().set_SCALE(width * 0.5f); + viewportReg->xOffset = Latte::LATTE_PA_CL_VPORT_XOFFSET().set_OFFSET(x + (width * 0.5f)); + viewportReg->yScale = Latte::LATTE_PA_CL_VPORT_YSCALE().set_SCALE(height * -0.5f); + viewportReg->yOffset = Latte::LATTE_PA_CL_VPORT_YOFFSET().set_OFFSET(y + (height * 0.5f)); + viewportReg->zScale = Latte::LATTE_PA_CL_VPORT_ZSCALE().set_SCALE((farZ - nearZ) * 0.5f); + viewportReg->zOffset = Latte::LATTE_PA_CL_VPORT_ZOFFSET().set_OFFSET((nearZ + farZ) * 0.5f); + } + + void GX2SetViewportReg(GX2ViewportReg* viewportReg) + { + GX2::GX2WriteGather_checkAndInsertWrapAroundMark(); + GX2ReserveCmdSpace(2 + 6); + + gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 6), + Latte::REGADDR::PA_CL_VPORT_XSCALE - 0xA000, + viewportReg->xScale, viewportReg->xOffset, + viewportReg->yScale, viewportReg->yOffset, + viewportReg->zScale, viewportReg->zOffset); + } + + void GX2SetViewport(float x, float y, float width, float height, float nearZ, float farZ) + { + GX2ViewportReg viewportReg; + GX2InitViewportReg(&viewportReg, x, y, width, height, nearZ, farZ); + GX2SetViewportReg(&viewportReg); + } + + void GX2InitScissorReg(GX2ScissorReg* scissorReg, uint32 x, uint32 y, uint32 width, uint32 height) + { + uint32 tlx = x; + uint32 tly = y; + uint32 brx = x + width; + uint32 bry = y + height; + + tlx = std::min(tlx, 8192u); + tly = std::min(tly, 8192u); + brx = std::min(brx, 8192u); + bry = std::min(bry, 8192u); + + scissorReg->scissorTL = Latte::LATTE_PA_SC_GENERIC_SCISSOR_TL().set_TL_X(tlx).set_TL_Y(tly).set_WINDOW_OFFSET_DISABLE(true); + scissorReg->scissorBR = Latte::LATTE_PA_SC_GENERIC_SCISSOR_BR().set_BR_X(brx).set_BR_Y(bry); + } + + void GX2SetScissorReg(GX2ScissorReg* scissorReg) + { + GX2ReserveCmdSpace(4); + + gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 2), + Latte::REGADDR::PA_SC_GENERIC_SCISSOR_TL - 0xA000, + scissorReg->scissorTL, scissorReg->scissorBR); + } + + void GX2GetScissorReg(GX2ScissorReg* scissorReg, uint32be* x, uint32be* y, uint32be* width, uint32be* height) + { + *x = scissorReg->scissorTL.value().get_TL_X(); + *y = scissorReg->scissorTL.value().get_TL_Y(); + *width = scissorReg->scissorBR.value().get_BR_X() - scissorReg->scissorTL.value().get_TL_X(); + *height = scissorReg->scissorBR.value().get_BR_Y() - scissorReg->scissorTL.value().get_TL_Y(); + } + + void GX2SetScissor(uint32 x, uint32 y, uint32 width, uint32 height) + { + GX2ScissorReg scissorReg; + GX2InitScissorReg(&scissorReg, x, y, width, height); + GX2SetScissorReg(&scissorReg); + } + + void GX2SetDepthOnlyControl(bool depthTestEnable, bool depthWriteEnable, LATTE_DB_DEPTH_CONTROL::E_ZFUNC depthFunction) + { + // disables any currently set stencil test + GX2ReserveCmdSpace(3); + + Latte::LATTE_DB_DEPTH_CONTROL reg{}; + reg.set_Z_ENABLE(depthTestEnable); + reg.set_Z_WRITE_ENABLE(depthWriteEnable); + reg.set_Z_FUNC(depthFunction); + + gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), + Latte::REGADDR::DB_DEPTH_CONTROL - 0xA000, + reg); + } + + void GX2SetDepthStencilControl( + bool depthTestEnable, bool depthWriteEnable, LATTE_DB_DEPTH_CONTROL::E_ZFUNC depthFunction, + bool stencilTestEnable, bool backStencilTestEnable, + LATTE_DB_DEPTH_CONTROL::E_STENCILFUNC frontStencilFunction, + LATTE_DB_DEPTH_CONTROL::E_STENCILACTION frontStencilZPass, LATTE_DB_DEPTH_CONTROL::E_STENCILACTION frontStencilZFail, LATTE_DB_DEPTH_CONTROL::E_STENCILACTION frontStencilFail, + LATTE_DB_DEPTH_CONTROL::E_STENCILFUNC backStencilFunction, + LATTE_DB_DEPTH_CONTROL::E_STENCILACTION backStencilZPass, LATTE_DB_DEPTH_CONTROL::E_STENCILACTION backStencilZFail, LATTE_DB_DEPTH_CONTROL::E_STENCILACTION backStencilFail + ) + { + GX2ReserveCmdSpace(3); + + Latte::LATTE_DB_DEPTH_CONTROL reg{}; + reg.set_Z_ENABLE(depthTestEnable).set_Z_WRITE_ENABLE(depthWriteEnable).set_Z_FUNC(depthFunction); + reg.set_STENCIL_ENABLE(stencilTestEnable).set_BACK_STENCIL_ENABLE(backStencilTestEnable); + reg.set_STENCIL_FUNC_F(frontStencilFunction).set_STENCIL_FUNC_B(backStencilFunction); + reg.set_STENCIL_ZPASS_F(frontStencilZPass).set_STENCIL_ZFAIL_F(frontStencilZFail).set_STENCIL_FAIL_F(frontStencilFail); + reg.set_STENCIL_ZPASS_B(backStencilZPass).set_STENCIL_ZFAIL_B(backStencilZFail).set_STENCIL_FAIL_B(backStencilFail); + + gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), + Latte::REGADDR::DB_DEPTH_CONTROL - 0xA000, + reg); + } + + void GX2InitDepthStencilControlReg( + GX2DepthStencilControlReg* depthStencilControlReg, + bool depthTestEnable, bool depthWriteEnable, LATTE_DB_DEPTH_CONTROL::E_ZFUNC depthFunction, + bool stencilTestEnable, bool backStencilTestEnable, + LATTE_DB_DEPTH_CONTROL::E_STENCILFUNC frontStencilFunction, + LATTE_DB_DEPTH_CONTROL::E_STENCILACTION frontStencilZPass, LATTE_DB_DEPTH_CONTROL::E_STENCILACTION frontStencilZFail, LATTE_DB_DEPTH_CONTROL::E_STENCILACTION frontStencilFail, + LATTE_DB_DEPTH_CONTROL::E_STENCILFUNC backStencilFunction, + LATTE_DB_DEPTH_CONTROL::E_STENCILACTION backStencilZPass, LATTE_DB_DEPTH_CONTROL::E_STENCILACTION backStencilZFail, LATTE_DB_DEPTH_CONTROL::E_STENCILACTION backStencilFail) + { + Latte::LATTE_DB_DEPTH_CONTROL reg{}; + reg.set_Z_ENABLE(depthTestEnable).set_Z_WRITE_ENABLE(depthWriteEnable).set_Z_FUNC(depthFunction); + reg.set_STENCIL_ENABLE(stencilTestEnable).set_BACK_STENCIL_ENABLE(backStencilTestEnable); + reg.set_STENCIL_FUNC_F(frontStencilFunction).set_STENCIL_FUNC_B(backStencilFunction); + reg.set_STENCIL_ZPASS_F(frontStencilZPass).set_STENCIL_ZFAIL_F(frontStencilZFail).set_STENCIL_FAIL_F(frontStencilFail); + reg.set_STENCIL_ZPASS_B(backStencilZPass).set_STENCIL_ZFAIL_B(backStencilZFail).set_STENCIL_FAIL_B(backStencilFail); + depthStencilControlReg->reg = reg; + } + + void GX2SetDepthStencilControlReg(GX2DepthStencilControlReg* depthStencilControlReg) + { + GX2ReserveCmdSpace(3); + + gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), + Latte::REGADDR::DB_DEPTH_CONTROL - 0xA000, + depthStencilControlReg->reg); + } + + void GX2GetDepthStencilControlReg( + GX2DepthStencilControlReg* depthStencilControlReg, + uint32be* depthTestEnable, uint32be* depthWriteEnable, uint32be* depthFunction, + uint32be* stencilTestEnable, uint32be* backStencilTestEnable, + uint32be* frontStencilFunction, + uint32be* frontStencilZPass, uint32be* frontStencilZFail, uint32be* frontStencilFail, + uint32be* backStencilFunction, + uint32be* backStencilZPass, uint32be* backStencilZFail, uint32be* backStencilFail) + { + // used by Hyrule Warriors + + *depthTestEnable = depthStencilControlReg->reg.value().get_Z_ENABLE(); + + *depthWriteEnable = depthStencilControlReg->reg.value().get_Z_WRITE_ENABLE(); + *depthFunction = (uint32)depthStencilControlReg->reg.value().get_Z_FUNC(); + + *stencilTestEnable = depthStencilControlReg->reg.value().get_STENCIL_ENABLE(); + *backStencilTestEnable = depthStencilControlReg->reg.value().get_BACK_STENCIL_ENABLE(); + + *frontStencilFunction = (uint32)depthStencilControlReg->reg.value().get_STENCIL_FUNC_F(); + *backStencilFunction = (uint32)depthStencilControlReg->reg.value().get_STENCIL_FUNC_B(); + + *frontStencilZPass = (uint32)depthStencilControlReg->reg.value().get_STENCIL_ZPASS_F(); + *frontStencilZFail = (uint32)depthStencilControlReg->reg.value().get_STENCIL_ZFAIL_F(); + *frontStencilFail = (uint32)depthStencilControlReg->reg.value().get_STENCIL_FAIL_F(); + + *backStencilZPass = (uint32)depthStencilControlReg->reg.value().get_STENCIL_ZPASS_B(); + *backStencilZFail = (uint32)depthStencilControlReg->reg.value().get_STENCIL_ZFAIL_B(); + *backStencilFail = (uint32)depthStencilControlReg->reg.value().get_STENCIL_FAIL_B(); + } + + void GX2InitStencilMaskReg(GX2StencilMaskReg* stencilMaskReg, uint8 compareMaskFront, uint8 writeMaskFront, uint8 refFront, uint8 compareMaskBack, uint8 writeMaskBack, uint8 refBack) + { + stencilMaskReg->stencilRefMaskFrontReg = LATTE_DB_STENCILREFMASK().set_STENCILREF_F(refFront).set_STENCILMASK_F(compareMaskFront).set_STENCILWRITEMASK_F(writeMaskFront); + stencilMaskReg->stencilRefMaskBackReg = LATTE_DB_STENCILREFMASK_BF().set_STENCILREF_B(refBack).set_STENCILMASK_B(compareMaskBack).set_STENCILWRITEMASK_B(writeMaskBack); + } + + void GX2SetStencilMask(uint8 compareMaskFront, uint8 writeMaskFront, uint8 refFront, uint8 compareMaskBack, uint8 writeMaskBack, uint8 refBack) + { + GX2ReserveCmdSpace(3 + 3); + + LATTE_DB_STENCILREFMASK frontReg; + frontReg.set_STENCILREF_F(refFront).set_STENCILMASK_F(compareMaskFront).set_STENCILWRITEMASK_F(writeMaskFront); + LATTE_DB_STENCILREFMASK_BF backReg; + backReg.set_STENCILREF_B(refBack).set_STENCILMASK_B(compareMaskBack).set_STENCILWRITEMASK_B(writeMaskBack); + + gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), + REGADDR::DB_STENCILREFMASK - 0xA000, + frontReg, + pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), + REGADDR::DB_STENCILREFMASK_BF - 0xA000, + backReg); + } + + void GX2SetStencilMaskReg(GX2StencilMaskReg* stencilMaskReg) + { + GX2ReserveCmdSpace(3 + 3); + + gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), + REGADDR::DB_STENCILREFMASK - 0xA000, + stencilMaskReg->stencilRefMaskFrontReg, + pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), + REGADDR::DB_STENCILREFMASK_BF - 0xA000, + stencilMaskReg->stencilRefMaskBackReg); + } + + void GX2SetPrimitiveRestartIndex(uint32 restartIndex) + { + GX2ReserveCmdSpace(3); + Latte::LATTE_VGT_MULTI_PRIM_IB_RESET_INDX reg{}; + reg.set_RESTART_INDEX(restartIndex); + gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), + Latte::REGADDR::VGT_MULTI_PRIM_IB_RESET_INDX - 0xA000, + reg); + } + + void GX2InitTargetChannelMasksReg(GX2TargetChannelMaskReg* reg, GX2_CHANNELMASK t0, GX2_CHANNELMASK t1, GX2_CHANNELMASK t2, GX2_CHANNELMASK t3, GX2_CHANNELMASK t4, GX2_CHANNELMASK t5, GX2_CHANNELMASK t6, GX2_CHANNELMASK t7) + { + uint32 targetMask = 0; + targetMask |= ((t0 & 0xF) << 0); + targetMask |= ((t1 & 0xF) << 4); + targetMask |= ((t2 & 0xF) << 8); + targetMask |= ((t3 & 0xF) << 12); + targetMask |= ((t4 & 0xF) << 16); + targetMask |= ((t5 & 0xF) << 20); + targetMask |= ((t6 & 0xF) << 24); + targetMask |= ((t7 & 0xF) << 28); + Latte::LATTE_CB_TARGET_MASK r; + r.set_MASK(targetMask); + reg->reg = r; + } + + void GX2SetTargetChannelMasksReg(GX2TargetChannelMaskReg* reg) + { + GX2ReserveCmdSpace(3); + + gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), + Latte::REGADDR::CB_TARGET_MASK - 0xA000, + reg->reg); + } + + void GX2SetTargetChannelMasks(GX2_CHANNELMASK t0, GX2_CHANNELMASK t1, GX2_CHANNELMASK t2, GX2_CHANNELMASK t3, GX2_CHANNELMASK t4, GX2_CHANNELMASK t5, GX2_CHANNELMASK t6, GX2_CHANNELMASK t7) + { + GX2TargetChannelMaskReg tmpReg; + GX2InitTargetChannelMasksReg(&tmpReg, t0, t1, t2, t3, t4, t5, t6, t7); + GX2SetTargetChannelMasksReg(&tmpReg); + } + + static_assert(sizeof(GX2_CHANNELMASK) == 4); + + void GX2GetTargetChannelMasksReg(GX2TargetChannelMaskReg* reg, betype<GX2_CHANNELMASK>* t0, betype<GX2_CHANNELMASK>* t1, betype<GX2_CHANNELMASK>* t2, betype<GX2_CHANNELMASK>* t3, + betype<GX2_CHANNELMASK>* t4, betype<GX2_CHANNELMASK>* t5, betype<GX2_CHANNELMASK>* t6, betype<GX2_CHANNELMASK>* t7) + { + uint32 maskValue = reg->reg.value().get_MASK(); + *t0 = (maskValue >> 0) & 0xF; + *t1 = (maskValue >> 4) & 0xF; + *t2 = (maskValue >> 8) & 0xF; + *t3 = (maskValue >> 12) & 0xF; + *t4 = (maskValue >> 16) & 0xF; + *t5 = (maskValue >> 20) & 0xF; + *t6 = (maskValue >> 24) & 0xF; + *t7 = (maskValue >> 28) & 0xF; + } + + void GX2InitBlendControlReg(GX2BlendControlReg* reg, uint32 renderTargetIndex, GX2_BLENDFACTOR colorSrcFactor, GX2_BLENDFACTOR colorDstFactor, GX2_BLENDFUNC colorCombineFunc, uint32 separateAlphaBlend, GX2_BLENDFACTOR alphaSrcFactor, GX2_BLENDFACTOR alphaDstFactor, GX2_BLENDFUNC alphaCombineFunc) + { + Latte::LATTE_CB_BLENDN_CONTROL tmpReg; + tmpReg.set_COLOR_SRCBLEND(colorSrcFactor); + tmpReg.set_COLOR_DSTBLEND(colorDstFactor); + tmpReg.set_COLOR_COMB_FCN(colorCombineFunc); + tmpReg.set_ALPHA_SRCBLEND(alphaSrcFactor); + tmpReg.set_ALPHA_DSTBLEND(alphaDstFactor); + tmpReg.set_ALPHA_COMB_FCN(alphaCombineFunc); + tmpReg.set_SEPARATE_ALPHA_BLEND(separateAlphaBlend != 0); + + reg->index = renderTargetIndex; + reg->reg = tmpReg; + } + + void GX2SetBlendControlReg(GX2BlendControlReg* reg) + { + GX2ReserveCmdSpace(3); + gx2WriteGather_submit( + pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), + (Latte::REGADDR::CB_BLEND0_CONTROL + (uint32)reg->index) - 0xA000, + reg->reg + ); + } + + void GX2SetBlendControl(uint32 renderTargetIndex, GX2_BLENDFACTOR colorSrcFactor, GX2_BLENDFACTOR colorDstFactor, GX2_BLENDFUNC colorCombineFunc, uint32 separateAlphaBlend, GX2_BLENDFACTOR alphaSrcFactor, GX2_BLENDFACTOR alphaDstFactor, GX2_BLENDFUNC alphaCombineFunc) + { + GX2BlendControlReg tmpReg; + GX2InitBlendControlReg(&tmpReg, renderTargetIndex, colorSrcFactor, colorDstFactor, colorCombineFunc, separateAlphaBlend, alphaSrcFactor, alphaDstFactor, alphaCombineFunc); + GX2SetBlendControlReg(&tmpReg); + } + + void GX2InitBlendConstantColorReg(GX2BlendConstantColorReg* reg, float red, float green, float blue, float alpha) + { + reg->regRed = Latte::LATTE_CB_BLEND_RED().set_RED(red); + reg->regGreen = Latte::LATTE_CB_BLEND_GREEN().set_GREEN(green); + reg->regBlue = Latte::LATTE_CB_BLEND_BLUE().set_BLUE(blue); + reg->regAlpha = Latte::LATTE_CB_BLEND_ALPHA().set_ALPHA(alpha); + } + + void GX2SetBlendConstantColorReg(GX2BlendConstantColorReg* reg) + { + GX2ReserveCmdSpace(6); + gx2WriteGather_submit( + pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 4), + Latte::REGADDR::CB_BLEND_RED - 0xA000, + reg->regRed, + reg->regGreen, + reg->regBlue, + reg->regAlpha + ); + } + + void GX2SetBlendConstantColor(float red, float green, float blue, float alpha) + { + GX2BlendConstantColorReg tmpReg; + GX2InitBlendConstantColorReg(&tmpReg, red, green, blue, alpha); + GX2SetBlendConstantColorReg(&tmpReg); + } + + void GX2InitHiStencilInfoRegs(GX2HiStencilInfoReg* hiStencilInfo) + { + // seen in Color Splash + // but the game never calls GX2SetHiStencilInfo thus this has no effect + } + + void GX2InitPointSizeReg(GX2PointSizeReg* reg, float width, float height) + { + if (width < 0.0f || height < 0.0f) + { + cemu_assert_suspicious(); + } + + uint32 widthI = (uint32)(width * 8.0f); + uint32 heightI = (uint32)(height * 8.0f); + + widthI = std::min<uint32>(widthI, 0xFFFF); + heightI = std::min<uint32>(heightI, 0xFFFF); + + reg->reg = Latte::LATTE_PA_SU_POINT_SIZE().set_WIDTH(widthI).set_HEIGHT(heightI); + } + + void GX2SetPointSizeReg(GX2PointSizeReg* reg) + { + GX2ReserveCmdSpace(3); + gx2WriteGather_submit( + pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), + Latte::REGADDR::PA_SU_POINT_SIZE - 0xA000, + reg->reg + ); + } + + void GX2SetPointSize(float width, float height) + { + GX2PointSizeReg tmpReg; + GX2InitPointSizeReg(&tmpReg, width, height); + GX2SetPointSizeReg(&tmpReg); + } + + void GX2InitPointLimitsReg(GX2PointLimitsReg* reg, float minSize, float maxSize) + { + if (minSize < 0.0f || maxSize < 0.0f) + { + cemu_assert_suspicious(); + } + + uint32 minSizeI = (uint32)(minSize * 8.0f); + uint32 maxSizeI = (uint32)(maxSize * 8.0f); + + minSizeI = std::min<uint32>(minSizeI, 0xFFFF); + maxSizeI = std::min<uint32>(maxSizeI, 0xFFFF); + + reg->reg = Latte::LATTE_PA_SU_POINT_MINMAX().set_MIN_SIZE(minSizeI).set_MAX_SIZE(maxSizeI); + } + + void GX2SetPointLimitsReg(GX2PointLimitsReg* reg) + { + GX2ReserveCmdSpace(3); + gx2WriteGather_submit( + pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), + Latte::REGADDR::PA_SU_POINT_MINMAX - 0xA000, + reg->reg + ); + } + + void GX2SetPointLimits(float minSize, float maxSize) + { + GX2PointLimitsReg tmpReg; + GX2InitPointLimitsReg(&tmpReg, minSize, maxSize); + GX2SetPointLimitsReg(&tmpReg); + } + + enum class GX2_SPECIAL_STATE : uint32 + { + FAST_CLEAR = 0, + FAST_CLEAR_HIZ = 1, + }; + + void _setSpecialState0(bool isEnabled) + { + GX2ReserveCmdSpace(6); + if (isEnabled) + { + // set PA_CL_VTE_CNTL to 0x300 + Latte::LATTE_PA_CL_VTE_CNTL regVTE{}; + regVTE.set_VTX_XY_FMT(true); + regVTE.set_VTX_Z_FMT(true); + gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), + Latte::REGADDR::PA_CL_VTE_CNTL - 0xA000, + regVTE); + // set PA_CL_CLIP_CNTL to 0x490000 + Latte::LATTE_PA_CL_CLIP_CNTL regClip{}; + regClip.set_CLIP_DISABLE(true); // 0x10000 + regClip.set_DX_CLIP_SPACE_DEF(true); // 0x80000 + regClip.set_DX_RASTERIZATION_KILL(true); // 0x400000 + + gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), + Latte::REGADDR::PA_CL_CLIP_CNTL - 0xA000, + regClip); + } + else + { + // set PA_CL_VTE_CNTL to 0x43F + Latte::LATTE_PA_CL_VTE_CNTL reg{}; + reg.set_VPORT_X_OFFSET_ENA(true).set_VPORT_X_SCALE_ENA(true); + reg.set_VPORT_Y_OFFSET_ENA(true).set_VPORT_Y_SCALE_ENA(true); + reg.set_VPORT_Z_OFFSET_ENA(true).set_VPORT_Z_SCALE_ENA(true); + reg.set_VTX_W0_FMT(true); + gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1), + Latte::REGADDR::PA_CL_VTE_CNTL - 0xA000, + reg); + // reset PA_CL_CLIP_CNTL + GX2SetRasterizerClipControl(true, true); + } + } + + void GX2SetSpecialState(GX2_SPECIAL_STATE stateId, uint32 isEnabled) + { + if (stateId == GX2_SPECIAL_STATE::FAST_CLEAR) + { + _setSpecialState0(isEnabled != 0); + } + else if (stateId == GX2_SPECIAL_STATE::FAST_CLEAR_HIZ) + { + // todo + // enables additional flags for special state 0 + } + else + { + // legacy style + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_SPECIAL_STATE, 2)); + gx2WriteGather_submitU32AsBE((uint32)stateId); // state id + gx2WriteGather_submitU32AsBE(isEnabled); // enable/disable bool + } + } + + void GX2StateInit() + { + cafeExportRegister("gx2", GX2InitAlphaTestReg, LogType::GX2); + cafeExportRegister("gx2", GX2SetAlphaTestReg, LogType::GX2); + cafeExportRegister("gx2", GX2SetAlphaTest, LogType::GX2); + + cafeExportRegister("gx2", GX2InitColorControlReg, LogType::GX2); + cafeExportRegister("gx2", GX2SetColorControl, LogType::GX2); + cafeExportRegister("gx2", GX2SetColorControlReg, LogType::GX2); + + cafeExportRegister("gx2", GX2InitPolygonControlReg, LogType::GX2); + cafeExportRegister("gx2", GX2SetPolygonControlReg, LogType::GX2); + cafeExportRegister("gx2", GX2SetPolygonControl, LogType::GX2); + cafeExportRegister("gx2", GX2SetCullOnlyControl, LogType::GX2); + + cafeExportRegister("gx2", GX2InitPolygonOffsetReg, LogType::GX2); + cafeExportRegister("gx2", GX2SetPolygonOffsetReg, LogType::GX2); + cafeExportRegister("gx2", GX2SetPolygonOffset, LogType::GX2); + + cafeExportRegister("gx2", GX2SetRasterizerClipControl, LogType::GX2); + cafeExportRegister("gx2", GX2SetRasterizerClipControlHalfZ, LogType::GX2); + cafeExportRegister("gx2", GX2SetRasterizerClipControlEx, LogType::GX2); + + cafeExportRegister("gx2", GX2InitViewportReg, LogType::GX2); + cafeExportRegister("gx2", GX2SetViewportReg, LogType::GX2); + cafeExportRegister("gx2", GX2SetViewport, LogType::GX2); + + cafeExportRegister("gx2", GX2InitScissorReg, LogType::GX2); + cafeExportRegister("gx2", GX2SetScissorReg, LogType::GX2); + cafeExportRegister("gx2", GX2GetScissorReg, LogType::GX2); + cafeExportRegister("gx2", GX2SetScissor, LogType::GX2); + + cafeExportRegister("gx2", GX2InitDepthStencilControlReg, LogType::GX2); + cafeExportRegister("gx2", GX2SetDepthStencilControlReg, LogType::GX2); + cafeExportRegister("gx2", GX2GetDepthStencilControlReg, LogType::GX2); + cafeExportRegister("gx2", GX2SetDepthOnlyControl, LogType::GX2); + cafeExportRegister("gx2", GX2SetDepthStencilControl, LogType::GX2); + + cafeExportRegister("gx2", GX2InitStencilMaskReg, LogType::GX2); + cafeExportRegister("gx2", GX2SetStencilMask, LogType::GX2); + cafeExportRegister("gx2", GX2SetStencilMaskReg, LogType::GX2); + + cafeExportRegister("gx2", GX2SetPrimitiveRestartIndex, LogType::GX2); + + cafeExportRegister("gx2", GX2InitTargetChannelMasksReg, LogType::GX2); + cafeExportRegister("gx2", GX2SetTargetChannelMasksReg, LogType::GX2); + cafeExportRegister("gx2", GX2SetTargetChannelMasks, LogType::GX2); + cafeExportRegister("gx2", GX2GetTargetChannelMasksReg, LogType::GX2); + + cafeExportRegister("gx2", GX2InitBlendControlReg, LogType::GX2); + cafeExportRegister("gx2", GX2SetBlendControlReg, LogType::GX2); + cafeExportRegister("gx2", GX2SetBlendControl, LogType::GX2); + + cafeExportRegister("gx2", GX2InitBlendConstantColorReg, LogType::GX2); + cafeExportRegister("gx2", GX2SetBlendConstantColorReg, LogType::GX2); + cafeExportRegister("gx2", GX2SetBlendConstantColor, LogType::GX2); + cafeExportRegister("gx2", GX2InitHiStencilInfoRegs, LogType::GX2); + + cafeExportRegister("gx2", GX2InitPointSizeReg, LogType::GX2); + cafeExportRegister("gx2", GX2SetPointSizeReg, LogType::GX2); + cafeExportRegister("gx2", GX2SetPointSize, LogType::GX2); + cafeExportRegister("gx2", GX2InitPointLimitsReg, LogType::GX2); + cafeExportRegister("gx2", GX2SetPointLimitsReg, LogType::GX2); + cafeExportRegister("gx2", GX2SetPointLimits, LogType::GX2); + + cafeExportRegister("gx2", GX2SetSpecialState, LogType::GX2); + } + +} diff --git a/src/Cafe/OS/libs/gx2/GX2_State.h b/src/Cafe/OS/libs/gx2/GX2_State.h new file mode 100644 index 00000000..d6c95b97 --- /dev/null +++ b/src/Cafe/OS/libs/gx2/GX2_State.h @@ -0,0 +1,192 @@ +#pragma once +#include "Cafe/HW/Latte/ISA/LatteReg.h" + +namespace GX2 +{ + struct GX2AlphaTestReg + { + betype<Latte::LATTE_SX_ALPHA_TEST_CONTROL> regAlphaTestControl; + betype<Latte::LATTE_SX_ALPHA_REF> regAlphaTestRef; + }; + + static_assert(sizeof(GX2AlphaTestReg) == 8); + + struct GX2ColorControlReg + { + betype<Latte::LATTE_CB_COLOR_CONTROL> reg; + }; + + static_assert(sizeof(GX2ColorControlReg) == 4); + + struct GX2PolygonControlReg + { + betype<Latte::LATTE_PA_SU_SC_MODE_CNTL> reg; + }; + + static_assert(sizeof(GX2PolygonControlReg) == 4); + + struct GX2PolygonOffsetReg + { + betype<Latte::LATTE_PA_SU_POLY_OFFSET_FRONT_SCALE> regFrontScale; + betype<Latte::LATTE_PA_SU_POLY_OFFSET_FRONT_OFFSET> regFrontOffset; + betype<Latte::LATTE_PA_SU_POLY_OFFSET_BACK_SCALE> regBackScale; + betype<Latte::LATTE_PA_SU_POLY_OFFSET_BACK_OFFSET> regBackOffset; + betype<Latte::LATTE_PA_SU_POLY_OFFSET_CLAMP> regClamp; + }; + + static_assert(sizeof(GX2PolygonOffsetReg) == 0x14); + + struct GX2DepthStencilControlReg + { + betype<Latte::LATTE_DB_DEPTH_CONTROL> reg; + }; + + static_assert(sizeof(GX2DepthStencilControlReg) == 4); + + struct GX2StencilMaskReg + { + betype<Latte::LATTE_DB_STENCILREFMASK> stencilRefMaskFrontReg; + betype<Latte::LATTE_DB_STENCILREFMASK_BF> stencilRefMaskBackReg; + }; + + static_assert(sizeof(GX2StencilMaskReg) == 8); + + struct GX2TargetChannelMaskReg + { + betype<Latte::LATTE_CB_TARGET_MASK> reg; + }; + + static_assert(sizeof(GX2TargetChannelMaskReg) == 4); + + struct GX2HIStencilInfoData + { + /* +0x00 */ uint32be ukn00; + /* +0x04 */ uint8be ukn04; + /* +0x05 */ uint8be ukn05; + /* +0x06 */ uint8be ukn06; // probably padding? + /* +0x07 */ uint8be ukn07; // probably padding? + /* +0x08 */ uint32be isEnable; // 0 or 1 + }; + + static_assert(sizeof(GX2HIStencilInfoData) == 0xC); + + struct GX2HiStencilInfoReg + { + GX2HIStencilInfoData state[2]; + uint32be reg[2]; // DB_SRESULTS_COMPARE_STATE0 and DB_SRESULTS_COMPARE_STATE1 + }; + + static_assert(sizeof(GX2HiStencilInfoReg) == 0x20); + + struct GX2BlendControlReg + { + uint32be index; + betype<Latte::LATTE_CB_BLENDN_CONTROL> reg; + }; + + static_assert(sizeof(GX2BlendControlReg) == 8); + + struct GX2BlendConstantColorReg + { + betype<Latte::LATTE_CB_BLEND_RED> regRed; + betype<Latte::LATTE_CB_BLEND_GREEN> regGreen; + betype<Latte::LATTE_CB_BLEND_BLUE> regBlue; + betype<Latte::LATTE_CB_BLEND_ALPHA> regAlpha; + }; + + static_assert(sizeof(GX2BlendConstantColorReg) == 16); + + struct GX2PointSizeReg + { + betype<Latte::LATTE_PA_SU_POINT_SIZE> reg; + }; + + static_assert(sizeof(GX2PointSizeReg) == 4); + + struct GX2PointLimitsReg + { + betype<Latte::LATTE_PA_SU_POINT_MINMAX> reg; + }; + + static_assert(sizeof(GX2PointLimitsReg) == 4); + + struct GX2ViewportReg + { + betype<Latte::LATTE_PA_CL_VPORT_XSCALE> xScale; + betype<Latte::LATTE_PA_CL_VPORT_XOFFSET> xOffset; + betype<Latte::LATTE_PA_CL_VPORT_YSCALE> yScale; + betype<Latte::LATTE_PA_CL_VPORT_YOFFSET> yOffset; + betype<Latte::LATTE_PA_CL_VPORT_ZSCALE> zScale; + betype<Latte::LATTE_PA_CL_VPORT_ZOFFSET> zOffset; + uint32 ukn[6]; // clipping registers? + }; + + static_assert(sizeof(GX2ViewportReg) == 48); + + struct GX2ScissorReg + { + betype<Latte::LATTE_PA_SC_GENERIC_SCISSOR_TL> scissorTL; + betype<Latte::LATTE_PA_SC_GENERIC_SCISSOR_BR> scissorBR; + }; + + static_assert(sizeof(GX2ScissorReg) == 8); + + using GX2_ALPHAFUNC = Latte::LATTE_SX_ALPHA_TEST_CONTROL::E_ALPHA_FUNC; // alias Latte::E_COMPAREFUNC + using GX2_LOGICOP = Latte::LATTE_CB_COLOR_CONTROL::E_LOGICOP; + using GX2_CHANNELMASK = uint32; + using GX2_BLENDFACTOR = Latte::LATTE_CB_BLENDN_CONTROL::E_BLENDFACTOR; + using GX2_BLENDFUNC = Latte::LATTE_CB_BLENDN_CONTROL::E_COMBINEFUNC; + + void GX2InitAlphaTestReg(GX2AlphaTestReg* reg, uint32 alphaTestEnable, GX2_ALPHAFUNC alphaFunc, float alphaRef); + void GX2SetAlphaTestReg(GX2AlphaTestReg* reg); + void GX2SetAlphaTest(uint32 alphaTestEnable, GX2_ALPHAFUNC alphaFunc, float alphaRef); + + void GX2InitColorControlReg(GX2ColorControlReg* reg, GX2_LOGICOP logicOp, uint32 blendMask, uint32 multiwriteEnable, uint32 colorBufferEnable); + void GX2SetColorControl(GX2_LOGICOP logicOp, uint32 blendMask, uint32 multiwriteEnable, uint32 colorBufferEnable); + void GX2SetColorControlReg(GX2ColorControlReg* reg); + + void GX2InitPolygonControlReg(GX2PolygonControlReg* reg, + Latte::LATTE_PA_SU_SC_MODE_CNTL::E_FRONTFACE frontFace, uint32 cullFront, uint32 cullBack, + Latte::LATTE_PA_SU_SC_MODE_CNTL::E_POLYGONMODE usePolygonMode, + Latte::LATTE_PA_SU_SC_MODE_CNTL::E_PTYPE polyModeFront, + Latte::LATTE_PA_SU_SC_MODE_CNTL::E_PTYPE polyModeBack, + uint32 polygonOffsetFrontEnable, uint32 polygonOffsetBackEnable, uint32 paraOffsetEnable); + void GX2SetPolygonControlReg(GX2PolygonControlReg* reg); + void GX2SetPolygonControl(Latte::LATTE_PA_SU_SC_MODE_CNTL::E_FRONTFACE frontFace, uint32 cullFront, uint32 cullBack, + Latte::LATTE_PA_SU_SC_MODE_CNTL::E_POLYGONMODE usePolygonMode, + Latte::LATTE_PA_SU_SC_MODE_CNTL::E_PTYPE polyModeFront, + Latte::LATTE_PA_SU_SC_MODE_CNTL::E_PTYPE polyModeBack, + uint32 polygonOffsetFrontEnable, uint32 polygonOffsetBackEnable, uint32 paraOffsetEnable); + void GX2SetCullOnlyControl(Latte::LATTE_PA_SU_SC_MODE_CNTL::E_FRONTFACE frontFace, uint32 cullFront, uint32 cullBack); + + void GX2InitPolygonOffsetReg(GX2PolygonOffsetReg* reg, float frontOffset, float frontScale, float backOffset, float backScale, float clampOffset); + void GX2SetPolygonOffsetReg(GX2PolygonOffsetReg* reg); + void GX2SetPolygonOffset(float frontOffset, float frontScale, float backOffset, float backScale, float clampOffset); + + void GX2InitPointSizeReg(GX2PointSizeReg* reg, float width, float height); + void GX2SetPointSizeReg(GX2PointSizeReg* reg); + void GX2SetPointSize(float width, float height); + void GX2InitPointLimitsReg(GX2PointLimitsReg* reg, float minSize, float maxSize); + void GX2SetPointLimitsReg(GX2PointLimitsReg* reg); + void GX2SetPointLimits(float minSize, float maxSize); + + void GX2SetRasterizerClipControl(bool enableRasterizer, bool enableZClip); + void GX2SetRasterizerClipControlHalfZ(bool enableRasterizer, bool enableZClip, bool enableHalfZ); + void GX2SetRasterizerClipControlEx(bool enableRasterizer, bool enableZClip, bool enableHalfZ); + + void GX2SetPrimitiveRestartIndex(uint32 restartIndex); + + void GX2InitTargetChannelMasksReg(GX2TargetChannelMaskReg* reg, GX2_CHANNELMASK t0, GX2_CHANNELMASK t1, GX2_CHANNELMASK t2, GX2_CHANNELMASK t3, GX2_CHANNELMASK t4, GX2_CHANNELMASK t5, GX2_CHANNELMASK t6, GX2_CHANNELMASK t7); + void GX2SetTargetChannelMasksReg(GX2TargetChannelMaskReg* reg); + void GX2SetTargetChannelMasks(GX2_CHANNELMASK t0, GX2_CHANNELMASK t1, GX2_CHANNELMASK t2, GX2_CHANNELMASK t3, GX2_CHANNELMASK t4, GX2_CHANNELMASK t5, GX2_CHANNELMASK t6, GX2_CHANNELMASK t7); + + void GX2InitBlendControlReg(GX2BlendControlReg* reg, uint32 renderTargetIndex, GX2_BLENDFACTOR colorSrcFactor, GX2_BLENDFACTOR colorDstFactor, GX2_BLENDFUNC colorCombineFunc, uint32 separateAlphaBlend, GX2_BLENDFACTOR alphaSrcFactor, GX2_BLENDFACTOR alphaDstFactor, GX2_BLENDFUNC alphaCombineFunc); + void GX2SetBlendControlReg(GX2BlendControlReg* reg); + void GX2SetBlendControl(uint32 renderTargetIndex, GX2_BLENDFACTOR colorSrcFactor, GX2_BLENDFACTOR colorDstFactor, GX2_BLENDFUNC colorCombineFunc, uint32 separateAlphaBlend, GX2_BLENDFACTOR alphaSrcFactor, GX2_BLENDFACTOR alphaDstFactor, GX2_BLENDFUNC alphaCombineFunc); + + void GX2InitBlendConstantColorReg(GX2BlendConstantColorReg* reg, float red, float green, float blue, float alpha); + void GX2SetBlendConstantColorReg(GX2BlendConstantColorReg* reg); + void GX2SetBlendConstantColor(float red, float green, float blue, float alpha); + + void GX2StateInit(); +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/gx2/GX2_Streamout.cpp b/src/Cafe/OS/libs/gx2/GX2_Streamout.cpp new file mode 100644 index 00000000..a5188d69 --- /dev/null +++ b/src/Cafe/OS/libs/gx2/GX2_Streamout.cpp @@ -0,0 +1,113 @@ +#include "GX2_Streamout.h" +#include "GX2_Command.h" +#include "Cafe/HW/Latte/ISA/RegDefines.h" +#include "Cafe/HW/Latte/Core/LattePM4.h" +#include "Cafe/OS/common/OSCommon.h" + +namespace GX2 +{ + void GX2SetStreamOutBuffer(uint32 bufferIndex, GX2StreamOutBuffer* streamOutBuffer) + { + if (bufferIndex >= GX2_MAX_STREAMOUT_BUFFERS) + { + cemu_assert_suspicious(); + debug_printf("GX2SetStreamOutBuffer(): Set out-of-bounds buffer\n"); + return; + } + + MPTR bufferAddr; + uint32 bufferSize; + if (streamOutBuffer->dataPtr.IsNull()) + { + bufferAddr = streamOutBuffer->rBuffer.GetVirtualAddr(); + bufferSize = streamOutBuffer->rBuffer.GetSize(); + } + else + { + bufferAddr = streamOutBuffer->dataPtr.GetMPTR(); + bufferSize = streamOutBuffer->size; + } + + GX2ReserveCmdSpace(3 + 3); + // set buffer size + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1)); + gx2WriteGather_submitU32AsBE((mmVGT_STRMOUT_BUFFER_SIZE_0 + bufferIndex * 4) - 0xA000); + gx2WriteGather_submitU32AsBE((bufferSize >> 2)); + // set buffer base + uint32 physMem = memory_virtualToPhysical(bufferAddr); + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1)); + gx2WriteGather_submitU32AsBE((mmVGT_STRMOUT_BUFFER_BASE_0 + bufferIndex * 4) - 0xA000); + gx2WriteGather_submitU32AsBE((physMem >> 8)); + // todo: Research and send IT_STRMOUT_BASE_UPDATE (0x72) + // note: Other stream out registers maybe set in GX2SetVertexShader() or GX2SetGeometryShader() + } + + void GX2SetStreamOutEnable(uint32 enable) + { + cemu_assert_debug(enable == 0 || enable == 1); + GX2ReserveCmdSpace(3); + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 1)); + gx2WriteGather_submitU32AsBE(mmVGT_STRMOUT_EN - 0xA000); + gx2WriteGather_submitU32AsBE(enable & 1); + } + + void GX2SetStreamOutContext(uint32 bufferIndex, GX2StreamOutBuffer* streamOutBuffer, uint32 mode) + { + if (bufferIndex >= GX2_MAX_STREAMOUT_BUFFERS) + { + cemu_assert_suspicious(); + debug_printf("GX2SetStreamOutContext(): Set out-of-bounds buffer\n"); + return; + } + + GX2ReserveCmdSpace(6); + if (mode == 0) + { + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_STRMOUT_BUFFER_UPDATE, 5)); + gx2WriteGather_submitU32AsBE((2 << 1) | (bufferIndex << 8)); + gx2WriteGather_submitU32AsBE(MPTR_NULL); + gx2WriteGather_submitU32AsBE(0); + gx2WriteGather_submitU32AsBE(memory_virtualToPhysical(streamOutBuffer->ctxPtr.GetMPTR())); + gx2WriteGather_submitU32AsBE(0); + } + else if (mode == 1) + { + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_STRMOUT_BUFFER_UPDATE, 5)); + gx2WriteGather_submitU32AsBE((0 << 1) | (bufferIndex << 8)); + gx2WriteGather_submitU32AsBE(MPTR_NULL); + gx2WriteGather_submitU32AsBE(0); + gx2WriteGather_submitU32AsBE(memory_virtualToPhysical(streamOutBuffer->ctxPtr.GetMPTR())); + gx2WriteGather_submitU32AsBE(0); + } + else + { + cemu_assert_unimplemented(); + } + } + + void GX2SaveStreamOutContext(uint32 bufferIndex, GX2StreamOutBuffer* streamOutBuffer) + { + if (bufferIndex >= GX2_MAX_STREAMOUT_BUFFERS) + { + cemu_assert_suspicious(); + debug_printf("GX2SaveStreamOutContext(): Set out-of-bounds buffer\n"); + return; + } + GX2ReserveCmdSpace(6); + + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_STRMOUT_BUFFER_UPDATE, 5)); + gx2WriteGather_submitU32AsBE(1 | (3 << 1) | (bufferIndex << 8)); + gx2WriteGather_submitU32AsBE(memory_virtualToPhysical(streamOutBuffer->ctxPtr.GetMPTR())); + gx2WriteGather_submitU32AsBE(0); + gx2WriteGather_submitU32AsBE(MPTR_NULL); + gx2WriteGather_submitU32AsBE(0); + } + + void GX2StreamoutInit() + { + cafeExportRegister("gx2", GX2SetStreamOutBuffer, LogType::GX2); + cafeExportRegister("gx2", GX2SetStreamOutEnable, LogType::GX2); + cafeExportRegister("gx2", GX2SetStreamOutContext, LogType::GX2); + cafeExportRegister("gx2", GX2SaveStreamOutContext, LogType::GX2); + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/gx2/GX2_Streamout.h b/src/Cafe/OS/libs/gx2/GX2_Streamout.h new file mode 100644 index 00000000..2c326efe --- /dev/null +++ b/src/Cafe/OS/libs/gx2/GX2_Streamout.h @@ -0,0 +1,21 @@ +#pragma once +#include "GX2_Resource.h" + +#define GX2_MAX_STREAMOUT_BUFFERS 4 + +namespace GX2 +{ + struct GX2StreamOutBuffer + { + /* +0x00 */ uint32be size; // size of buffer (if dataPtr is not NULL) + /* +0x04 */ MEMPTR<void> dataPtr; + /* +0x08 */ uint32be vertexStride; + /* +0x0C */ GX2RBuffer rBuffer; // if dataPtr is NULL, use this as the buffer and size + /* +0x1C */ MEMPTR<void> ctxPtr; // stream out context + }; + + static_assert(sizeof(GX2StreamOutBuffer) == 0x20, "GX2StreamOutBuffer_t has invalid size"); + + void GX2SetStreamOutBuffer(uint32 bufferIndex, GX2StreamOutBuffer* streamOutBuffer); + void GX2StreamoutInit(); +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/gx2/GX2_Surface.cpp b/src/Cafe/OS/libs/gx2/GX2_Surface.cpp new file mode 100644 index 00000000..5ce8a453 --- /dev/null +++ b/src/Cafe/OS/libs/gx2/GX2_Surface.cpp @@ -0,0 +1,278 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "GX2.h" +#include "GX2_Surface.h" +#include "GX2_Resource.h" +#include "Cafe/HW/Latte/Core/Latte.h" +#include "Cafe/HW/Latte/Core/LatteDraw.h" +#include "Cafe/HW/Latte/Core/LattePM4.h" +#include "Cafe/HW/Latte/LatteAddrLib/LatteAddrLib.h" + +namespace GX2 +{ + + uint32 GX2GetSurfaceMipPitch(GX2Surface* surface, uint32 level) + { + LatteAddrLib::AddrSurfaceInfo_OUT surfOut; + GX2::GX2CalculateSurfaceInfo(surface, level, &surfOut); + return surfOut.pitch; + } + + uint32 GX2GetSurfaceFormatBits(Latte::E_GX2SURFFMT surfaceFormat) + { + uint32 bpp = Latte::GetFormatBits(surfaceFormat); + if (Latte::IsCompressedFormat(surfaceFormat)) + { + cemu_assert_debug((bpp & 0xF) == 0); + bpp /= (4 * 4); + } + return bpp; + } + + uint32 _GX2CalculateSliceSize(GX2Surface* surface, const LatteAddrLib::AddrSurfaceInfo_OUT* surfaceInfo) + { + uint32 aaScaler = 1 << surface->aa; + return aaScaler * (surfaceInfo->bpp >> 3) * surfaceInfo->height * surfaceInfo->pitch; + } + + uint32 GX2GetSurfaceMipSliceSize(GX2Surface* surface, uint32 level) + { + LatteAddrLib::AddrSurfaceInfo_OUT surfOut; + GX2::GX2CalculateSurfaceInfo(surface, level, &surfOut); + return _GX2CalculateSliceSize(surface, &surfOut); + } + + uint32 GX2GetSurfaceSwizzleOffset(GX2Surface* surface, uint32 level) + { + uint32 swizzleOffset = 0; + uint32 swizzle = surface->swizzle; + if (!Latte::TM_IsMacroTiled(surface->tileMode) || level >= ((swizzle >> 16) & 0xFF)) + swizzleOffset = 0; + else + swizzleOffset = swizzle & 0xFFFF; + return swizzleOffset; + } + + uint32 GX2GetSurfaceSwizzle(GX2Surface* surface) + { + uint32 swizzle = surface->swizzle; + swizzle = (swizzle >> 8) & 0xFF; + return swizzle; + } + + uint32 GX2SurfaceIsCompressed(Latte::E_GX2SURFFMT surfaceFormat) + { + return Latte::IsCompressedFormat(surfaceFormat) ? GX2_TRUE : GX2_FALSE; + } + + void GX2CalcDepthBufferHiZInfo(GX2DepthBuffer* depthBuffer, uint32be* sizeOut, uint32be* alignOut) + { + *sizeOut = 0x1000; + *alignOut = 0x100; + + // todo: implement + } + + void GX2CalcColorBufferAuxInfo(GX2ColorBuffer* colorBuffer, uint32be* sizeOut, uint32be* alignOut) + { + *sizeOut = 0x1000; + *alignOut = 0x100; + + // todo: implement + } + + void GX2CalculateSurfaceInfo(GX2Surface* surfacePtr, uint32 level, LatteAddrLib::AddrSurfaceInfo_OUT* pSurfOut) + { + bool optimizeForDepthBuffer = (surfacePtr->resFlag & GX2_RESFLAG_USAGE_DEPTH_BUFFER) != 0; + bool optimizeForScanBuffer = (surfacePtr->resFlag & GX2_RESFLAG_USAGE_SCAN_BUFFER) != 0; + LatteAddrLib::GX2CalculateSurfaceInfo(surfacePtr->format, surfacePtr->width, surfacePtr->height, surfacePtr->depth, surfacePtr->dim, surfacePtr->tileMode, surfacePtr->aa, level, pSurfOut, optimizeForDepthBuffer, optimizeForScanBuffer); + } + + uint32 _CalculateLevels(uint32 resolution) + { + uint32 x = 0x80000000; + uint32 v = resolution; + uint32 n = 0; + while (!(v & x)) + { + n++; + if (n == 32) + break; + x >>= 1; + } + return 32 - n; + } + + uint32 _GX2AdjustLevelCount(GX2Surface* surfacePtr) + { + if (surfacePtr->numLevels <= 1) + return 1; + uint32 levels = std::max(_CalculateLevels(surfacePtr->width), _CalculateLevels(surfacePtr->height)); + if (surfacePtr->dim == Latte::E_DIM::DIM_3D) + levels = std::max(levels, _CalculateLevels(surfacePtr->depth)); + return levels; + } + + void GX2CalcSurfaceSizeAndAlignment(GX2Surface* surface) + { + LatteAddrLib::AddrSurfaceInfo_OUT surfOut = { 0 }; + uint32 firstMipOffset = 0; + bool changeTilemode = false; + Latte::E_GX2TILEMODE lastTilemode = surface->tileMode; + bool hasTileMode32 = surface->tileMode == Latte::E_GX2TILEMODE::TM_32_SPECIAL; + if (surface->tileMode == Latte::E_GX2TILEMODE::TM_LINEAR_GENERAL || hasTileMode32) + { + if (surface->dim != Latte::E_DIM::DIM_1D || (surface->resFlag & GX2_RESFLAG_USAGE_DEPTH_BUFFER) != 0 || surface->aa) + { + if (surface->dim != Latte::E_DIM::DIM_3D || (surface->resFlag & GX2_RESFLAG_USAGE_COLOR_BUFFER) != 0) + surface->tileMode = Latte::E_GX2TILEMODE::TM_2D_TILED_THIN1; + else + surface->tileMode = Latte::E_GX2TILEMODE::TM_2D_TILED_THICK; + changeTilemode = true; + } + else + { + surface->tileMode = Latte::E_GX2TILEMODE::TM_LINEAR_ALIGNED; + } + lastTilemode = surface->tileMode; + } + + if (surface->numLevels == 0) + surface->numLevels = 1; + surface->numLevels = std::min<uint32>(surface->numLevels, _GX2AdjustLevelCount(surface)); + surface->mipOffset[0] = 0; + if (Latte::TM_IsMacroTiled(surface->tileMode)) + surface->swizzle = (surface->swizzle & 0xFF00FFFF) | 0xD0000; + else + surface->swizzle = surface->swizzle & 0xFF00FFFF; + // FIX 32 + uint32 fix32Mode; + if (hasTileMode32) + { + if (Latte::IsCompressedFormat(surface->format)) + fix32Mode = 2; + else + fix32Mode = 0; + } + else + { + fix32Mode = 0; + } + // setup levels + uint32 prevSize = 0; + for (uint32 level = 0; level < surface->numLevels; ++level) + { + GX2CalculateSurfaceInfo(surface, level, &surfOut); + if (level) + { + uint32 pad = 0; + if (Latte::TM_IsMacroTiled(lastTilemode) && !Latte::TM_IsMacroTiled(surfOut.hwTileMode)) + { + surface->swizzle = (surface->swizzle & 0xFF00FFFF) | (level << 16); + lastTilemode = (Latte::E_GX2TILEMODE)surfOut.hwTileMode; + if (level > 1) + pad = surface->swizzle & 0xFFFF; + } + pad += (surfOut.baseAlign - prevSize % surfOut.baseAlign) % surfOut.baseAlign; + if (level == 1) + { + firstMipOffset = pad + prevSize; + } + else if (level > 1) + { + surface->mipOffset[level - 1] = pad + prevSize + surface->mipOffset[level - 2]; + } + } + else + { + if (changeTilemode) + { + if (surface->tileMode != (Latte::E_GX2TILEMODE)surfOut.hwTileMode) + { + surface->tileMode = (Latte::E_GX2TILEMODE)surfOut.hwTileMode; + GX2CalculateSurfaceInfo(surface, 0, &surfOut); + if (!Latte::TM_IsMacroTiled(surface->tileMode)) + surface->swizzle = surface->swizzle & 0xFF00FFFF; + lastTilemode = surface->tileMode; + } + if (surface->width < (surfOut.pitchAlign << fix32Mode) + && surface->height < (surfOut.heightAlign << fix32Mode)) + { + if (surface->tileMode == Latte::E_GX2TILEMODE::TM_2D_TILED_THICK) + surface->tileMode = Latte::E_GX2TILEMODE::TM_1D_TILED_THICK; + else + surface->tileMode = Latte::E_GX2TILEMODE::TM_1D_TILED_THIN1; + GX2CalculateSurfaceInfo(surface, 0, &surfOut); + surface->swizzle = surface->swizzle & 0xFF00FFFF; + lastTilemode = surface->tileMode; + } + } + + surface->imageSize = (uint32)(surfOut.surfSize); + surface->alignment = surfOut.baseAlign; + surface->pitch = surfOut.pitch; + } + prevSize = (uint32)(surfOut.surfSize); + } + if (surface->numLevels > 1) + surface->mipSize = prevSize + surface->mipOffset[surface->numLevels - 2]; + else + surface->mipSize = 0; + surface->mipOffset[0] = firstMipOffset; + if (surface->format == Latte::E_GX2SURFFMT::NV12_UNORM) + { + uint32 padding = (surface->alignment - surface->imageSize % surface->alignment) % surface->alignment; + surface->mipOffset[0] = padding + surface->imageSize; + surface->imageSize = surface->mipOffset[0] + ((uint32)surface->imageSize >> 1); + } + } + + Latte::E_ENDIAN_SWAP GetSurfaceFormatSwapMode(Latte::E_GX2SURFFMT fmt) + { + // swap mode is 0 for all formats + return Latte::E_ENDIAN_SWAP::SWAP_NONE; + } + + uint32 GetSurfaceColorBufferExportFormat(Latte::E_GX2SURFFMT fmt) + { + const uint8 table[0x40] = { + 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + uint32 fmtHW = (uint32)fmt & 0x3F; + return table[fmtHW]; + } + + uint32 GX2CheckSurfaceUseVsFormat(uint32 resFlags, uint32 surfaceFormat) + { + cemuLog_logDebug(LogType::Force, "GX2CheckSurfaceUseVsFormat - stub"); + return 1; + } + + void GX2SetSurfaceSwizzle(GX2Surface* surface, uint32 newSwizzle) + { + uint32 currentSwizzle = surface->swizzle; + currentSwizzle &= ~0xFF00; + currentSwizzle |= (newSwizzle << 8); // newSwizzle isn't actually masked and some games set it to values above 0xFF + surface->swizzle = currentSwizzle; + } + + void GX2SurfaceInit() + { + cafeExportRegister("gx2", GX2GetSurfaceMipPitch, LogType::GX2); + cafeExportRegister("gx2", GX2GetSurfaceFormatBits, LogType::GX2); + cafeExportRegister("gx2", GX2GetSurfaceMipSliceSize, LogType::GX2); + cafeExportRegister("gx2", GX2GetSurfaceSwizzleOffset, LogType::GX2); + cafeExportRegister("gx2", GX2GetSurfaceSwizzle, LogType::GX2); + cafeExportRegister("gx2", GX2SurfaceIsCompressed, LogType::GX2); + cafeExportRegister("gx2", GX2CalcDepthBufferHiZInfo, LogType::GX2); + cafeExportRegister("gx2", GX2CalcColorBufferAuxInfo, LogType::GX2); + cafeExportRegister("gx2", GX2CalcSurfaceSizeAndAlignment, LogType::GX2); + cafeExportRegister("gx2", GX2CheckSurfaceUseVsFormat, LogType::GX2); + cafeExportRegister("gx2", GX2SetSurfaceSwizzle, LogType::GX2); + } +}; \ No newline at end of file diff --git a/src/Cafe/OS/libs/gx2/GX2_Surface.h b/src/Cafe/OS/libs/gx2/GX2_Surface.h new file mode 100644 index 00000000..2586503a --- /dev/null +++ b/src/Cafe/OS/libs/gx2/GX2_Surface.h @@ -0,0 +1,82 @@ +#pragma once +#include "Cafe/HW/Latte/ISA/LatteReg.h" +#include "Cafe/HW/Latte/LatteAddrLib/LatteAddrLib.h" + +// todo - move into GX2 namespace + +struct GX2Surface +{ + /* +0x000 */ betype<Latte::E_DIM> dim; + /* +0x004 */ uint32be width; + /* +0x008 */ uint32be height; + /* +0x00C */ uint32be depth; + /* +0x010 */ uint32be numLevels; // number of mipmap levels including base image. Should be at least 1 + /* +0x014 */ betype<Latte::E_GX2SURFFMT> format; + /* +0x018 */ uint32be aa; // anti-aliasing mode + /* +0x01C */ uint32be resFlag; // GX2_RESFLAG_* and GX2R_RESFLAG_* + /* +0x020 */ uint32be imageSize; + /* +0x024 */ uint32be imagePtr; + /* +0x028 */ uint32be mipSize; + /* +0x02C */ uint32be mipPtr; + /* +0x030 */ betype<Latte::E_GX2TILEMODE> tileMode; + /* +0x034 */ uint32be swizzle; + /* +0x038 */ uint32be alignment; + /* +0x03C */ uint32be pitch; + /* +0x040 */ uint32be mipOffset[13]; +}; // size: 0x74 + +static_assert(sizeof(betype<Latte::E_DIM>) == 4); +static_assert(sizeof(betype<Latte::E_GX2TILEMODE>) == 4); +static_assert(sizeof(GX2Surface) == 0x74); + +// color and depth buffer + +struct GX2ColorBuffer +{ + /* +0x00 */ GX2Surface surface; + /* +0x74 */ uint32 viewMip; + /* +0x78 */ uint32 viewFirstSlice; + /* +0x7C */ uint32 viewNumSlices; + /* +0x80 */ MPTR auxData; + /* +0x84 */ uint32 auxSize; + /* +0x88 */ uint32be reg_size; // CB_COLOR*_SIZE + /* +0x8C */ uint32be reg_info; // CB_COLOR*_INFO + /* +0x90 */ uint32be reg_view; // CB_COLOR*_VIEW + /* +0x94 */ uint32be reg_mask; // CB_COLOR*_MASK + /* +0x98 */ uint32be reg4; // ? +}; + +static_assert(sizeof(GX2ColorBuffer) == 0x9C); + +struct GX2DepthBuffer +{ + /* +0x00 */ GX2Surface surface; + /* +0x74 */ uint32 viewMip; + /* +0x78 */ uint32 viewFirstSlice; + /* +0x7C */ uint32 viewNumSlices; + /* +0x80 */ MPTR hiZPtr; + /* +0x84 */ uint32 hiZSize; + /* +0x88 */ float clearDepth; + /* +0x8C */ uint32 clearStencil; + /* +0x90 */ uint32be reg_size; + /* +0x94 */ uint32be reg_view; + /* +0x98 */ uint32be reg_base; + /* +0x9C */ uint32be reg_htile_surface; + /* +0xA0 */ uint32be reg_prefetch_limit; + /* +0xA4 */ uint32be reg_preload_control; + /* +0xA8 */ uint32be reg_poly_offset_db_fmt_cntl; +}; + +static_assert(sizeof(GX2DepthBuffer) == 0xAC); + +namespace GX2 +{ + void GX2CalculateSurfaceInfo(GX2Surface* surfacePtr, uint32 level, LatteAddrLib::AddrSurfaceInfo_OUT* pSurfOut); + + Latte::E_ENDIAN_SWAP GetSurfaceFormatSwapMode(Latte::E_GX2SURFFMT fmt); + uint32 GetSurfaceColorBufferExportFormat(Latte::E_GX2SURFFMT fmt); + + void GX2CalcSurfaceSizeAndAlignment(GX2Surface* surface); + + void GX2SurfaceInit(); +}; \ No newline at end of file diff --git a/src/Cafe/OS/libs/gx2/GX2_Surface_Copy.cpp b/src/Cafe/OS/libs/gx2/GX2_Surface_Copy.cpp new file mode 100644 index 00000000..9bbc1b80 --- /dev/null +++ b/src/Cafe/OS/libs/gx2/GX2_Surface_Copy.cpp @@ -0,0 +1,743 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/HW/Latte/ISA/RegDefines.h" +#include "Cafe/HW/Latte/Core/LattePM4.h" +#include "Cafe/HW/Latte/Core/Latte.h" +#include "Cafe/HW/Latte/Core/LatteDraw.h" +#include "Cafe/HW/Latte/Core/LatteAsyncCommands.h" +#include "Cafe/HW/Latte/LatteAddrLib/LatteAddrLib.h" +#include "util/highresolutiontimer/HighResolutionTimer.h" +#include "GX2.h" +#include "GX2_Resource.h" + +template<uint32 copyBpp> +void gx2SurfaceCopySoftware_specialized( + uint8* inputData, sint32 surfSrcHeight, sint32 srcPitch, sint32 srcDepth, uint32 srcSlice, uint32 srcSwizzle, uint32 srcHwTileMode, + uint8* outputData, sint32 surfDstHeight, sint32 dstPitch, sint32 dstDepth, uint32 dstSlice, uint32 dstSwizzle, uint32 dstHwTileMode, + uint32 copyWidth, uint32 copyHeight) +{ + uint32 srcPipeSwizzle = (srcSwizzle >> 8) & 1; + uint32 srcBankSwizzle = ((srcSwizzle >> 9) & 3); + uint32 dstPipeSwizzle = (dstSwizzle >> 8) & 1; + uint32 dstBankSwizzle = ((dstSwizzle >> 9) & 3); + + for (uint32 y = 0; y < copyHeight; y++) + { + for (uint32 x = 0; x < copyWidth; x++) + { + // calculate address of input block + uint32 srcOffset = 0; + if (srcHwTileMode == 0 || srcHwTileMode == 1) + srcOffset = LatteAddrLib::ComputeSurfaceAddrFromCoordLinear(x, y, srcSlice, 0, copyBpp, srcPitch, surfSrcHeight, srcDepth); + else if (srcHwTileMode == 2 || srcHwTileMode == 3) + srcOffset = LatteAddrLib::ComputeSurfaceAddrFromCoordMicroTiled(x, y, srcSlice, copyBpp, srcPitch, surfSrcHeight, (Latte::E_HWTILEMODE)srcHwTileMode, false); + else + srcOffset = LatteAddrLib::ComputeSurfaceAddrFromCoordMacroTiled(x, y, srcSlice, 0, copyBpp, srcPitch, surfSrcHeight, 1 * 1, (Latte::E_HWTILEMODE)srcHwTileMode, false, srcPipeSwizzle, srcBankSwizzle); + uint8* inputBlockData = inputData + srcOffset; + // calculate address of output block + uint32 dstOffset = 0; + if (dstHwTileMode == 0 || dstHwTileMode == 1) + dstOffset = LatteAddrLib::ComputeSurfaceAddrFromCoordLinear(x, y, dstSlice, 0, copyBpp, dstPitch, surfDstHeight, dstDepth); + else if (dstHwTileMode == 2 || dstHwTileMode == 3) + dstOffset = LatteAddrLib::ComputeSurfaceAddrFromCoordMicroTiled(x, y, dstSlice, copyBpp, dstPitch, surfDstHeight, (Latte::E_HWTILEMODE)dstHwTileMode, false); + else + dstOffset = LatteAddrLib::ComputeSurfaceAddrFromCoordMacroTiled(x, y, dstSlice, 0, copyBpp, dstPitch, surfDstHeight, 1 * 1, (Latte::E_HWTILEMODE)dstHwTileMode, false, dstPipeSwizzle, dstBankSwizzle); + uint8* outputBlockData = outputData + dstOffset; + + if constexpr (copyBpp == 8) + { + outputBlockData[0] = inputBlockData[0]; + } + else if constexpr (copyBpp == 16) + { + *(uint16*)outputBlockData = *(uint16*)inputBlockData; + } + else if constexpr (copyBpp == 32) + { + *(uint32*)outputBlockData = *(uint32*)inputBlockData; + } + else if constexpr (copyBpp == 64) + { + *(uint32*)(outputBlockData + 0) = *(uint32*)(inputBlockData + 0); + *(uint32*)(outputBlockData + 4) = *(uint32*)(inputBlockData + 4); + } + else if constexpr (copyBpp == 128) + { + *(uint32*)(outputBlockData + 0) = *(uint32*)(inputBlockData + 0); + *(uint32*)(outputBlockData + 4) = *(uint32*)(inputBlockData + 4); + *(uint32*)(outputBlockData + 8) = *(uint32*)(inputBlockData + 8); + *(uint32*)(outputBlockData + 12) = *(uint32*)(inputBlockData + 12); + } + } + } +} + +// fast copy for tilemode 4 to tilemode 4 +// assumes aa 1 +// this only supports the cases where every micro tile fits within 256 bytes (group size) +// we could accelerate this even further if we copied whole macro blocks +void gx2SurfaceCopySoftware_fastPath_tm4Copy(uint8* inputData, sint32 surfSrcHeight, sint32 srcPitch, sint32 srcDepth, uint32 srcSlice, uint32 srcSwizzle, + uint8* outputData, sint32 surfDstHeight, sint32 dstPitch, sint32 dstDepth, uint32 dstSlice, uint32 dstSwizzle, + uint32 copyWidth, uint32 copyHeight, uint32 copyBpp) +{ + cemu_assert_debug((copyWidth & 7) == 0); + cemu_assert_debug((copyHeight & 7) == 0); + + uint32 srcPipeSwizzle = (srcSwizzle >> 8) & 1; + uint32 srcBankSwizzle = ((srcSwizzle >> 9) & 3); + uint32 dstPipeSwizzle = (dstSwizzle >> 8) & 1; + uint32 dstBankSwizzle = ((dstSwizzle >> 9) & 3); + + uint32 texelBytes = copyBpp / 8; + + if (srcSlice == dstSlice && srcSwizzle == dstSwizzle && surfSrcHeight == surfDstHeight && srcPitch == dstPitch) + { + // shared tile offsets + for (uint32 y = 0; y < copyHeight; y += 8) + { + for (uint32 x = 0; x < copyWidth; x += 8) + { + // copy 8x8 micro tile + uint32 offset = LatteAddrLib::ComputeSurfaceAddrFromCoordMacroTiled(x, y, srcSlice, 0, copyBpp, srcPitch, surfSrcHeight, 1 * 1, Latte::E_HWTILEMODE::TM_2D_TILED_THIN1, false, srcPipeSwizzle, srcBankSwizzle); + uint8* inputBlockData = inputData + offset; + uint8* outputBlockData = outputData + offset; + memcpy(outputBlockData, inputBlockData, texelBytes * (8 * 8)); + } + } + } + else + { + // separate tile offsets + for (uint32 y = 0; y < copyHeight; y += 8) + { + for (uint32 x = 0; x < copyWidth; x += 8) + { + // copy 8x8 micro tile + uint32 srcOffset = LatteAddrLib::ComputeSurfaceAddrFromCoordMacroTiled(x, y, srcSlice, 0, copyBpp, srcPitch, surfSrcHeight, 1 * 1, Latte::E_HWTILEMODE::TM_2D_TILED_THIN1, false, srcPipeSwizzle, srcBankSwizzle); + uint8* inputBlockData = inputData + srcOffset; + uint32 dstOffset = LatteAddrLib::ComputeSurfaceAddrFromCoordMacroTiled(x, y, dstSlice, 0, copyBpp, dstPitch, surfDstHeight, 1 * 1, Latte::E_HWTILEMODE::TM_2D_TILED_THIN1, false, dstPipeSwizzle, dstBankSwizzle); + uint8* outputBlockData = outputData + dstOffset; + memcpy(outputBlockData, inputBlockData, texelBytes * (8 * 8)); + } + } + } +} + +void gx2SurfaceCopySoftware( + uint8* inputData, sint32 surfSrcHeight, sint32 srcPitch, sint32 srcDepth, uint32 srcSlice, uint32 srcSwizzle, uint32 srcHwTileMode, + uint8* outputData, sint32 surfDstHeight, sint32 dstPitch, sint32 dstDepth, uint32 dstSlice, uint32 dstSwizzle, uint32 dstHwTileMode, + uint32 copyWidth, uint32 copyHeight, uint32 copyBpp) +{ + if (srcHwTileMode == 4 && dstHwTileMode == 4 && (copyWidth & 7) == 0 && (copyHeight & 7) == 0 && copyBpp <= 32) // todo - check sample == 1 + { + gx2SurfaceCopySoftware_fastPath_tm4Copy(inputData, surfSrcHeight, srcPitch, srcDepth, srcSlice, srcSwizzle, outputData, surfDstHeight, dstPitch, dstDepth, dstSlice, dstSwizzle, copyWidth, copyHeight, copyBpp); + return; + } + + if (copyBpp == 8) + gx2SurfaceCopySoftware_specialized<8>(inputData, surfSrcHeight, srcPitch, srcDepth, srcSlice, srcSwizzle, srcHwTileMode, outputData, surfDstHeight, dstPitch, dstDepth, dstSlice, dstSwizzle, dstHwTileMode, copyWidth, copyHeight); + else if (copyBpp == 16) + gx2SurfaceCopySoftware_specialized<16>(inputData, surfSrcHeight, srcPitch, srcDepth, srcSlice, srcSwizzle, srcHwTileMode, outputData, surfDstHeight, dstPitch, dstDepth, dstSlice, dstSwizzle, dstHwTileMode, copyWidth, copyHeight); + else if (copyBpp == 32) + gx2SurfaceCopySoftware_specialized<32>(inputData, surfSrcHeight, srcPitch, srcDepth, srcSlice, srcSwizzle, srcHwTileMode, outputData, surfDstHeight, dstPitch, dstDepth, dstSlice, dstSwizzle, dstHwTileMode, copyWidth, copyHeight); + else if (copyBpp == 64) + gx2SurfaceCopySoftware_specialized<64>(inputData, surfSrcHeight, srcPitch, srcDepth, srcSlice, srcSwizzle, srcHwTileMode, outputData, surfDstHeight, dstPitch, dstDepth, dstSlice, dstSwizzle, dstHwTileMode, copyWidth, copyHeight); + else if (copyBpp == 128) + gx2SurfaceCopySoftware_specialized<128>(inputData, surfSrcHeight, srcPitch, srcDepth, srcSlice, srcSwizzle, srcHwTileMode, outputData, surfDstHeight, dstPitch, dstDepth, dstSlice, dstSwizzle, dstHwTileMode, copyWidth, copyHeight); + else + cemu_assert_debug(false); +} + +void gx2Surface_GX2CopySurface(GX2Surface* srcSurface, uint32 srcMip, uint32 srcSlice, GX2Surface* dstSurface, uint32 dstMip, uint32 dstSlice) +{ + sint32 dstWidth = dstSurface->width; + sint32 dstHeight = dstSurface->height; + sint32 srcWidth = srcSurface->width; + sint32 srcHeight = srcSurface->height; + + sint32 dstMipWidth = std::max(dstWidth>>dstMip, 1); + sint32 dstMipHeight = std::max(dstHeight>>dstMip, 1); + sint32 srcMipWidth = std::max(srcWidth>>srcMip, 1); + sint32 srcMipHeight = std::max(srcHeight>>srcMip, 1); + + if( dstMipWidth != srcMipWidth || dstMipHeight != srcMipHeight ) + { + cemu_assert_debug(false); + return; + } + // handle format + Latte::E_GX2SURFFMT srcFormat = srcSurface->format; + Latte::E_GX2SURFFMT dstFormat = dstSurface->format; + uint32 srcBPP = Latte::GetFormatBits(srcFormat); + uint32 dstBPP = Latte::GetFormatBits(dstFormat); + auto srcHwFormat = Latte::GetHWFormat(srcFormat); + auto dstHwFormat = Latte::GetHWFormat(dstFormat); + // get texture info + LatteAddrLib::AddrSurfaceInfo_OUT surfOutSrc = {0}; + GX2::GX2CalculateSurfaceInfo(srcSurface, srcMip, &surfOutSrc); + LatteAddrLib::AddrSurfaceInfo_OUT surfOutDst = {0}; + GX2::GX2CalculateSurfaceInfo(dstSurface, dstMip, &surfOutDst); + // check parameters + if (srcSurface->numLevels == 0) + { + debug_printf("GX2CopySurface(): mip count is 0\n"); + return; + } + // get input pointer + uint8* inputData = NULL; + cemu_assert(srcMip < srcSurface->numLevels); + if( srcMip == 0 ) + inputData = (uint8*)memory_getPointerFromVirtualOffset(srcSurface->imagePtr); + else if( srcMip == 1 ) + inputData = (uint8*)memory_getPointerFromVirtualOffset(srcSurface->mipPtr); + else + { + inputData = (uint8*)memory_getPointerFromVirtualOffset(srcSurface->mipPtr + srcSurface->mipOffset[srcMip - 1]); + } + // get output pointer + uint8* outputData = NULL; + cemu_assert(dstMip < dstSurface->numLevels); + if( dstMip == 0 ) + outputData = (uint8*)memory_getPointerFromVirtualOffset(dstSurface->imagePtr); + else if( dstMip == 1 ) + outputData = (uint8*)memory_getPointerFromVirtualOffset(dstSurface->mipPtr); + else + { + outputData = (uint8*)memory_getPointerFromVirtualOffset(dstSurface->mipPtr + dstSurface->mipOffset[dstMip - 1]); + } + + if( srcHwFormat != dstHwFormat ) + { + // mismatching format + forceLogDebug_printf("GX2CopySurface(): Format mismatch\n"); + return; + } + + // note: Do not trust values from the input GX2Surface* structs but rely on surfOutDst/surfOutSrc instead if possible. + // src + uint32 srcPitch = surfOutSrc.pitch; + uint32 srcSwizzle = srcSurface->swizzle; + uint32 srcHwTileMode = (uint32)surfOutSrc.hwTileMode; + uint32 srcDepth = std::max<uint32>(surfOutSrc.depth, 1); + if (srcHwTileMode == 0) // linear + { + srcPitch = srcSurface->pitch >> srcMip; + srcPitch = std::max<uint32>(srcPitch, 1); + } + // dst + uint32 dstPitch = surfOutDst.pitch; + uint32 dstSwizzle = dstSurface->swizzle; + uint32 dstHwTileMode = (uint32)surfOutDst.hwTileMode; + uint32 dstDepth = std::max<uint32>(surfOutDst.depth, 1); + uint32 dstBpp = surfOutDst.bpp; + + //debug_printf("Src Tex: %08X %dx%d Swizzle: %08x tm: %d fmt: %04x use: %02x\n", _swapEndianU32(srcSurface->imagePtr), _swapEndianU32(srcSurface->width), _swapEndianU32(srcSurface->height), _swapEndianU32(srcSurface->swizzle), _swapEndianU32(srcSurface->tileMode), _swapEndianU32(srcSurface->format), (uint32)srcSurface->resFlag); + //debug_printf("Dst Tex: %08X %dx%d Swizzle: %08x tm: %d fmt: %04x use: %02x\n", _swapEndianU32(dstSurface->imagePtr), _swapEndianU32(dstSurface->width), _swapEndianU32(dstSurface->height), _swapEndianU32(dstSurface->swizzle), _swapEndianU32(dstSurface->tileMode), _swapEndianU32(dstSurface->format), (uint32)dstSurface->resFlag); + + bool requestGPURAMCopy = false; + bool debugTestForceCPUCopy = false; + + if (srcSurface->tileMode == Latte::E_GX2TILEMODE::TM_LINEAR_SPECIAL && dstSurface->tileMode == Latte::E_GX2TILEMODE::TM_2D_TILED_THIN1) + debugTestForceCPUCopy = true; + + if (srcSurface->tileMode == Latte::E_GX2TILEMODE::TM_2D_TILED_THIN1 && dstSurface->tileMode == Latte::E_GX2TILEMODE::TM_LINEAR_SPECIAL ) + { + LatteAsyncCommands_queueForceTextureReadback( + srcSurface->imagePtr, + srcSurface->mipPtr, + srcSurface->swizzle, + (uint32)srcSurface->format.value(), + srcSurface->width, + srcSurface->height, + srcSurface->depth, + srcSurface->pitch, + srcSlice, + (uint32)srcSurface->dim.value(), + Latte::MakeHWTileMode(srcSurface->tileMode), + srcSurface->aa, + srcMip); + + LatteAsyncCommands_waitUntilAllProcessed(); + + debugTestForceCPUCopy = true; + } + + // send copy command to GPU + if( srcHwTileMode > 0 && srcHwTileMode < 16 && dstHwTileMode > 0 && dstHwTileMode < 16 || requestGPURAMCopy ) + { + GX2ReserveCmdSpace(1+13*2); + + gx2WriteGather_submit(pm4HeaderType3(IT_HLE_COPY_SURFACE_NEW, 13*2), + // src + (uint32)srcSurface->imagePtr, + (uint32)srcSurface->mipPtr, + (uint32)srcSurface->swizzle, + (uint32)srcSurface->format.value(), + (uint32)srcSurface->width, + (uint32)srcSurface->height, + (uint32)srcSurface->depth, + (uint32)srcSurface->pitch, + srcSlice, + (uint32)srcSurface->dim.value(), + (uint32)srcSurface->tileMode.value(), + (uint32)srcSurface->aa, + srcMip, + // dst + (uint32)dstSurface->imagePtr, + (uint32)dstSurface->mipPtr, + (uint32)dstSurface->swizzle, + (uint32)dstSurface->format.value(), + (uint32)dstSurface->width, + (uint32)dstSurface->height, + (uint32)dstSurface->depth, + (uint32)dstSurface->pitch, + dstSlice, + (uint32)dstSurface->dim.value(), + (uint32)dstSurface->tileMode.value(), + (uint32)dstSurface->aa, + dstMip); + } + + if (requestGPURAMCopy) + return; // if RAM copy happens on the GPU side we skip it here + + // manually exclude expensive CPU texture copies for some known game framebuffer textures + // todo - find a better way to solve this + bool isDynamicTexCopy = false; + isDynamicTexCopy = isDynamicTexCopy || (srcSurface->imagePtr >= 0xF4000000 && srcSurface->width >= 800 && srcFormat == Latte::E_GX2SURFFMT::R11_G11_B10_FLOAT); // SM3DW + isDynamicTexCopy = isDynamicTexCopy || (srcSurface->imagePtr >= 0xF4000000 && srcSurface->width >= 800 && srcFormat == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM); // Trine 2 + isDynamicTexCopy = isDynamicTexCopy || (srcSurface->imagePtr >= 0xF4000000 && srcSurface->width == 0xA0 && srcFormat == Latte::E_GX2SURFFMT::R32_FLOAT); // Little Inferno + isDynamicTexCopy = isDynamicTexCopy || (srcSurface->imagePtr >= 0xF4000000 && srcSurface->width == 1280 && srcFormat == Latte::E_GX2SURFFMT::R32_FLOAT); // Donkey Kong Tropical Freeze + isDynamicTexCopy = isDynamicTexCopy || (srcSurface->imagePtr >= 0xF4000000 && srcSurface->width == 640 && srcSurface->height == 320 && srcFormat == Latte::E_GX2SURFFMT::R11_G11_B10_FLOAT); // SM3DW Switch Scramble Circus + isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 1280 && srcSurface->height == 720 && srcFormat == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM && srcSurface->tileMode != Latte::E_GX2TILEMODE::TM_LINEAR_ALIGNED ); // Affordable Space Adventures + isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 854 && srcSurface->height == 480 && srcFormat == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM); // Affordable Space Adventures + isDynamicTexCopy = isDynamicTexCopy || (srcSurface->imagePtr >= 0xF4000000 && srcSurface->width == 1152 && srcSurface->height == 720 && srcFormat == Latte::E_GX2SURFFMT::R11_G11_B10_FLOAT && (srcSurface->resFlag&GX2_RESFLAG_USAGE_COLOR_BUFFER) != 0 ); // Star Fox Zero + isDynamicTexCopy = isDynamicTexCopy || (srcSurface->imagePtr >= 0xF4000000 && srcSurface->width == 680 && srcSurface->height == 480 && srcFormat == Latte::E_GX2SURFFMT::R11_G11_B10_FLOAT && (srcSurface->resFlag&GX2_RESFLAG_USAGE_COLOR_BUFFER) != 0 ); // Star Fox Zero + isDynamicTexCopy = isDynamicTexCopy || (srcSurface->imagePtr >= 0xF4000000 && srcSurface->width == 1280 && srcSurface->height == 720 && srcFormat == Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT ); // Qube + isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 322 && srcSurface->height == 182 && srcFormat == Latte::E_GX2SURFFMT::R16_G16_B16_A16_UNORM ); // Qube + isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 640 && srcSurface->height == 360 && srcFormat == Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT ); // Qube + isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 1920 && srcSurface->height == 1080 && srcFormat == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM && dstSurface->resFlag == 0x80000003); // Cosmophony + isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 854 && srcSurface->height == 480 && srcFormat == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM && dstSurface->resFlag == 0x3); // Cosmophony + isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 1280 && srcSurface->height == 720 && srcFormat == Latte::E_GX2SURFFMT::R8_G8_B8_A8_SRGB && dstSurface->resFlag == 0x3); // The Fall + isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 854 && srcSurface->height == 480 && srcFormat == Latte::E_GX2SURFFMT::R8_G8_B8_A8_SRGB && dstSurface->resFlag == 0x3); // The Fall + isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 1280 && srcSurface->height == 720 && srcFormat == Latte::E_GX2SURFFMT::R8_G8_B8_A8_SRGB && dstSurface->resFlag == 0x80000003); // The Fall + isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 1280 && srcSurface->height == 720 && srcFormat == Latte::E_GX2SURFFMT::R8_G8_B8_A8_SRGB && srcSurface->resFlag == 0x80000003); // Nano Assault Neo + isDynamicTexCopy = isDynamicTexCopy || (srcSurface->imagePtr >= 0xF4000000 && srcSurface->width == 1280 && srcSurface->height == 720 && srcFormat == Latte::E_GX2SURFFMT::R10_G10_B10_A2_UNORM); // Mario Party 10 + isDynamicTexCopy = isDynamicTexCopy || (srcSurface->imagePtr >= 0xF4000000 && srcSurface->width == 854 && srcSurface->height == 480 && srcFormat == Latte::E_GX2SURFFMT::R10_G10_B10_A2_UNORM); // Mario Party 10 + isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 1920 && srcSurface->height == 1080 && srcFormat == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM && dstSurface->resFlag == 0x3); // Hello Kitty Kruisers + isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 1024 && srcSurface->height == 1024 && srcFormat == Latte::E_GX2SURFFMT::R32_FLOAT && dstSurface->resFlag == 0x5); // Art Academy + isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 260 && srcSurface->height == 148 && srcFormat == Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT && dstSurface->resFlag == 0x3); // Transformers: Rise of the Dark Spark + isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 1040 && srcSurface->height == 592 && srcFormat == Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT && dstSurface->resFlag == 0x3); // Transformers: Rise of the Dark Spark + isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 854 && srcSurface->height == 480 && srcFormat == Latte::E_GX2SURFFMT::R8_G8_B8_A8_SRGB && srcSurface->resFlag == 0x3); // Nano Assault Neo + isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 1024 && srcSurface->height == 576 && srcFormat == Latte::E_GX2SURFFMT::D24_S8_UNORM && srcSurface->resFlag == 0x1); // Skylanders SuperChargers + isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 1152 && srcSurface->height == 648 && (srcFormat == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM || srcFormat == Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT) && srcSurface->resFlag == 0x1); // Watch Dogs + isDynamicTexCopy = isDynamicTexCopy || (srcSurface->width == 576 && srcSurface->height == 324 && (srcFormat == Latte::E_GX2SURFFMT::R8_G8_B8_A8_UNORM || srcFormat == Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT) && srcSurface->resFlag == 0x1); // Watch Dogs + + if( isDynamicTexCopy && debugTestForceCPUCopy == false) + { + debug_printf("Software tex copy blocked\n"); + return; + } + + sint32 copyWidth = dstMipWidth; + sint32 copyHeight = dstMipHeight; + if (Latte::IsCompressedFormat(dstHwFormat)) + { + copyWidth = (copyWidth + 3) / 4; + copyHeight = (copyHeight + 3) / 4; + } + + gx2SurfaceCopySoftware(inputData, surfOutSrc.height, srcPitch, srcDepth, srcSlice, srcSwizzle, srcHwTileMode, + outputData, surfOutDst.height, dstPitch, dstDepth, dstSlice, dstSwizzle, dstHwTileMode, + copyWidth, copyHeight, dstBpp); +} + +void gx2Export_GX2CopySurface(PPCInterpreter_t* hCPU) +{ + GX2Surface* srcSurface = (GX2Surface*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); + uint32 srcMip = hCPU->gpr[4]; + uint32 srcSlice = hCPU->gpr[5]; + GX2Surface* dstSurface = (GX2Surface*)memory_getPointerFromVirtualOffset(hCPU->gpr[6]); + uint32 dstMip = hCPU->gpr[7]; + uint32 dstSlice = hCPU->gpr[8]; + gx2Surface_GX2CopySurface(srcSurface, srcMip, srcSlice, dstSurface, dstMip, dstSlice); + osLib_returnFromFunction(hCPU, 0); +} + +typedef struct +{ + sint32 left; + sint32 top; + sint32 right; + sint32 bottom; +}GX2Rect_t; + +typedef struct +{ + sint32 x; + sint32 y; +}GX2Point_t; + +void gx2Export_GX2CopySurfaceEx(PPCInterpreter_t* hCPU) +{ + forceLogDebug_printf("GX2CopySurfaceEx(0x%08x,%d,%d,0x%08x,%d,%d,%d,0x%08x,0x%08x)", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7], hCPU->gpr[8], hCPU->gpr[9], hCPU->gpr[10], memory_readU32(hCPU->gpr[1]+0x8)); + GX2Surface* srcSurface = (GX2Surface*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); + uint32 srcMip = hCPU->gpr[4]; + uint32 srcSlice = hCPU->gpr[5]; + GX2Surface* dstSurface = (GX2Surface*)memory_getPointerFromVirtualOffset(hCPU->gpr[6]); + uint32 dstMip = hCPU->gpr[7]; + uint32 dstSlice = hCPU->gpr[8]; + sint32 rectCount = hCPU->gpr[9]; + MPTR rectSrcArrayMPTR = hCPU->gpr[10]; + MPTR pointDstArrayMPTR = memory_readU32(hCPU->gpr[1]+0x8); + + GX2Rect_t* rectSrc = (GX2Rect_t*)memory_getPointerFromVirtualOffset(rectSrcArrayMPTR); + GX2Point_t* rectDst = (GX2Point_t*)memory_getPointerFromVirtualOffset(pointDstArrayMPTR); + for (sint32 i = 0; i < rectCount; i++) + { + forceLogDebug_printf("rect left-top: %d/%d size: %d/%d", _swapEndianU32(rectSrc->left), _swapEndianU32(rectSrc->top), _swapEndianU32(rectSrc->right) - _swapEndianU32(rectSrc->left), _swapEndianU32(rectSrc->bottom) - _swapEndianU32(rectSrc->top)); + } + +#ifndef PUBLIC_RELEASE + if( rectCount != 1 ) + assert_dbg(); + if( srcMip != 0 ) + assert_dbg(); + if( srcSlice != 0 ) + assert_dbg(); + if( dstMip != 0 ) + assert_dbg(); + if( dstSlice != 0 ) + assert_dbg(); +#endif + + for(sint32 i=0; i<rectCount; i++) + { + uint32 srcWidth = srcSurface->width; + uint32 srcHeight = srcSurface->height; + // calculate rect size + sint32 rectSrcX = (sint32)_swapEndianU32((uint32)rectSrc[i].left); + sint32 rectSrcY = (sint32)_swapEndianU32((uint32)rectSrc[i].top); + sint32 rectWidth = (sint32)_swapEndianU32((uint32)rectSrc[i].right) - rectSrcX; + sint32 rectHeight = (sint32)_swapEndianU32((uint32)rectSrc[i].bottom) - rectSrcY; + if( rectSrcX == 0 && rectSrcY == 0 && rectWidth == srcWidth && rectHeight == srcHeight ) + { + // special case in which GX2CopySurfaceEx acts like GX2CopySurface() + gx2Surface_GX2CopySurface(srcSurface, srcMip, srcSlice, dstSurface, dstMip, dstSlice); + } + } + + osLib_returnFromFunction(hCPU, 0); +} + +void gx2Export_GX2ResolveAAColorBuffer(PPCInterpreter_t* hCPU) +{ + debug_printf("GX2ResolveAAColorBuffer(0x%08x,0x%08x,%d,%d)\n", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6]); + GX2ColorBuffer* srcColorBuffer = (GX2ColorBuffer*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); + GX2Surface* srcSurface = &srcColorBuffer->surface; + GX2Surface* dstSurface = (GX2Surface*)memory_getPointerFromVirtualOffset(hCPU->gpr[4]); + uint32 srcMip = _swapEndianU32(srcColorBuffer->viewMip); + uint32 dstMip = hCPU->gpr[5]; + uint32 srcSlice = _swapEndianU32(srcColorBuffer->viewFirstSlice); + uint32 dstSlice = hCPU->gpr[6]; + +#ifndef PUBLIC_RELEASE + if( _swapEndianU32(srcColorBuffer->viewMip) != 0 || _swapEndianU32(srcColorBuffer->viewFirstSlice) != 0 ) + assert_dbg(); +#endif + + // allocate pixel buffer + sint32 dstWidth = dstSurface->width; + sint32 dstHeight = dstSurface->height; + sint32 srcWidth = srcSurface->width; + sint32 srcHeight = srcSurface->height; + + uint32 dstMipWidth = std::max(dstWidth>>dstMip, 1); + uint32 dstMipHeight = std::max(dstHeight>>dstMip, 1); + uint32 srcMipWidth = std::max(srcWidth>>srcMip, 1); + uint32 srcMipHeight = std::max(srcHeight>>srcMip, 1); + + // check if surface properties match + if( srcSurface->width != dstSurface->width || srcSurface->height != dstSurface->height ) + { + osLib_returnFromFunction(hCPU, 0); + return; + } + if( dstMipWidth != srcMipWidth || dstMipHeight != srcMipHeight ) + { + cemu_assert_suspicious(); + osLib_returnFromFunction(hCPU, 0); + return; + } + // handle format + Latte::E_GX2SURFFMT srcFormat = srcSurface->format; + Latte::E_GX2SURFFMT dstFormat = dstSurface->format; + uint32 srcBPP = Latte::GetFormatBits(srcFormat); + uint32 dstBPP = Latte::GetFormatBits(dstFormat); + sint32 srcStepX = 1; + sint32 srcStepY = 1; + sint32 dstStepX = 1; + sint32 dstStepY = 1; + auto srcHwFormat = Latte::GetHWFormat(srcFormat); + auto dstHwFormat = Latte::GetHWFormat(dstFormat); + // get texture info + LatteAddrLib::AddrSurfaceInfo_OUT surfOutSrc = {0}; + GX2::GX2CalculateSurfaceInfo(srcSurface, srcMip, &surfOutSrc); + LatteAddrLib::AddrSurfaceInfo_OUT surfOutDst = {0}; + GX2::GX2CalculateSurfaceInfo(dstSurface, dstMip, &surfOutDst); + // get input pointer + uint8* inputData = NULL; + cemu_assert(srcMip < srcSurface->numLevels); + if( srcMip == 0 ) + inputData = (uint8*)memory_getPointerFromVirtualOffset(srcSurface->imagePtr); + else if( srcMip == 1 ) + inputData = (uint8*)memory_getPointerFromVirtualOffset(srcSurface->mipPtr); + else + inputData = (uint8*)memory_getPointerFromVirtualOffset(srcSurface->mipPtr+srcSurface->mipOffset[srcMip-1]); + // get output pointer + uint8* outputData = NULL; + cemu_assert(dstMip < dstSurface->numLevels); + if( dstMip == 0 ) + outputData = (uint8*)memory_getPointerFromVirtualOffset(dstSurface->imagePtr); + else if( dstMip == 1 ) + outputData = (uint8*)memory_getPointerFromVirtualOffset(dstSurface->mipPtr); + else + outputData = (uint8*)memory_getPointerFromVirtualOffset(dstSurface->mipPtr+dstSurface->mipOffset[dstMip-1]); + // calculate step size for compressed textures + if( Latte::IsCompressedFormat(srcHwFormat) ) + { + srcStepX = 4; + srcStepY = 4; + } + if(Latte::IsCompressedFormat(dstHwFormat) ) + { + dstStepX = 4; + dstStepY = 4; + } + if( srcStepX != dstStepX || srcStepY != dstStepY ) + assert_dbg(); + + if( srcHwFormat != dstHwFormat ) + { + // mismatching format + debug_printf("GX2CopySurface(): Format mismatch\n"); + osLib_returnFromFunction(hCPU, 0); + return; + } + + // src + uint32 srcPitch = surfOutSrc.pitch; + uint32 srcSwizzle = srcSurface->swizzle; + uint32 srcPipeSwizzle = (srcSwizzle>>8)&1; + uint32 srcBankSwizzle = ((srcSwizzle>>9)&3); + uint32 srcTileMode = (uint32)surfOutSrc.hwTileMode; + uint32 srcDepth = std::max<uint32>(surfOutSrc.depth, 1); + // dst + uint32 dstPitch = surfOutDst.pitch; + uint32 dstSwizzle = dstSurface->swizzle; + uint32 dstPipeSwizzle = (dstSwizzle>>8)&1; + uint32 dstBankSwizzle = ((dstSwizzle>>9)&3); + uint32 dstTileMode = (uint32)surfOutDst.hwTileMode; + uint32 dstDepth = std::max<uint32>(surfOutDst.depth, 1); + + // send copy command to GPU + GX2ReserveCmdSpace(1 + 13 * 2); + gx2WriteGather_submit(pm4HeaderType3(IT_HLE_COPY_SURFACE_NEW, 13 * 2), + // src + (uint32)srcSurface->imagePtr, + (uint32)srcSurface->mipPtr, + (uint32)srcSurface->swizzle, + (uint32)srcSurface->format.value(), + (uint32)srcSurface->width, + (uint32)srcSurface->height, + (uint32)srcSurface->depth, + (uint32)srcSurface->pitch, + srcSlice, + (uint32)srcSurface->dim.value(), + (uint32)srcSurface->tileMode.value(), + (uint32)srcSurface->aa, + srcMip, + // dst + (uint32)dstSurface->imagePtr, + (uint32)dstSurface->mipPtr, + (uint32)dstSurface->swizzle, + (uint32)dstSurface->format.value(), + (uint32)dstSurface->width, + (uint32)dstSurface->height, + (uint32)dstSurface->depth, + (uint32)dstSurface->pitch, + dstSlice, + (uint32)dstSurface->dim.value(), + (uint32)dstSurface->tileMode.value(), + (uint32)dstSurface->aa, + dstMip); + + osLib_returnFromFunction(hCPU, 0); +} + +void gx2Export_GX2ConvertDepthBufferToTextureSurface(PPCInterpreter_t* hCPU) +{ + gx2Log_printf("GX2ConvertDepthBufferToTextureSurface(0x%x, 0x%x, %d, %d)\n", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6]); + GX2DepthBuffer* depthBuffer = (GX2DepthBuffer*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); + GX2Surface* dstSurface = (GX2Surface*)memory_getPointerFromVirtualOffset(hCPU->gpr[4]); + uint32 dstMip = hCPU->gpr[5]; + uint32 dstSlice = hCPU->gpr[6]; + + if (dstMip != 0 || dstSlice != 0) + debugBreakpoint(); + // get texture info + LatteAddrLib::AddrSurfaceInfo_OUT surfOutSrc = { 0 }; + GX2::GX2CalculateSurfaceInfo(&depthBuffer->surface, 0, &surfOutSrc); + LatteAddrLib::AddrSurfaceInfo_OUT surfOutDst = { 0 }; + GX2::GX2CalculateSurfaceInfo(dstSurface, 0, &surfOutDst); + + if (depthBuffer->surface.imagePtr == dstSurface->imagePtr) + { + // in-place re-tiling doesn't need any actual copy operation? + if (dstMip != 0 || dstSlice != 0) + debugBreakpoint(); + debug_printf("In-place re-tiling\n"); + osLib_returnFromFunction(hCPU, 0); + return; + } + + // note: Do not trust values from the input GX2Surface* structs but rely on surfOutDst/surfOutSrc instead if possible. + // src + uint32 srcPitch = surfOutSrc.pitch; + uint32 srcSwizzle = depthBuffer->surface.swizzle; + uint32 srcPipeSwizzle = (srcSwizzle >> 8) & 1; + uint32 srcBankSwizzle = ((srcSwizzle >> 9) & 3); + uint32 srcTileMode = (uint32)surfOutSrc.hwTileMode; + uint32 srcDepth = std::max<uint32>(surfOutSrc.depth, 1); + // dst + uint32 dstPitch = surfOutDst.pitch; + uint32 dstSwizzle = dstSurface->swizzle; + uint32 dstPipeSwizzle = (dstSwizzle >> 8) & 1; + uint32 dstBankSwizzle = ((dstSwizzle >> 9) & 3); + uint32 dstTileMode = (uint32)surfOutDst.hwTileMode; + uint32 dstDepth = srcDepth; + + sint32 srcMip = 0; + + uint32 numSlices = std::max<uint32>(_swapEndianU32(depthBuffer->viewNumSlices), 1); + GX2ReserveCmdSpace((1 + 13 * 2) * numSlices); + for (uint32 subSliceIndex = 0; subSliceIndex < numSlices; subSliceIndex++) + { + // send copy command to GPU + gx2WriteGather_submit(pm4HeaderType3(IT_HLE_COPY_SURFACE_NEW, 13 * 2), + // src + (uint32)(depthBuffer->surface.imagePtr), + (uint32)(depthBuffer->surface.mipPtr), + (uint32)(depthBuffer->surface.swizzle), + (uint32)(depthBuffer->surface.format.value()), + (uint32)(depthBuffer->surface.width), + (uint32)(depthBuffer->surface.height), + (uint32)(depthBuffer->surface.depth), + (uint32)(depthBuffer->surface.pitch), + (uint32)(depthBuffer->viewFirstSlice) + subSliceIndex, + (uint32)(depthBuffer->surface.dim.value()), + (uint32)(depthBuffer->surface.tileMode.value()), + (uint32)(depthBuffer->surface.aa), + srcMip, + // dst + (uint32)(dstSurface->imagePtr), + (uint32)(dstSurface->mipPtr), + (uint32)(dstSurface->swizzle), + (uint32)(dstSurface->format.value()), + (uint32)(dstSurface->width), + (uint32)(dstSurface->height), + (uint32)(dstSurface->depth), + (uint32)(dstSurface->pitch), + dstSlice + subSliceIndex, + (uint32)(dstSurface->dim.value()), + (uint32)(dstSurface->tileMode.value()), + (uint32)(dstSurface->aa), + dstMip); + } + + osLib_returnFromFunction(hCPU, 0); +} + +namespace GX2 +{ + void GX2SurfaceCopyInit() + { + osLib_addFunction("gx2", "GX2CopySurface", gx2Export_GX2CopySurface); + osLib_addFunction("gx2", "GX2CopySurfaceEx", gx2Export_GX2CopySurfaceEx); + osLib_addFunction("gx2", "GX2ResolveAAColorBuffer", gx2Export_GX2ResolveAAColorBuffer); + osLib_addFunction("gx2", "GX2ConvertDepthBufferToTextureSurface", gx2Export_GX2ConvertDepthBufferToTextureSurface); + } +}; + +void gx2CopySurfaceTest() +{ + + return; + + BenchmarkTimer bt; + + // copy 0 + bt.Start(); + for(sint32 i=0; i<100; i++) + gx2SurfaceCopySoftware( + memory_base + 0x10000000, 256, 256, 1, 0, 0, 4, + memory_base + 0x20000000, 256, 256, 1, 0, 0, 4, + 64, 64, 32 + ); + bt.Stop(); + debug_printf("Copy 0 - %lfms\n", bt.GetElapsedMilliseconds()); + // copy 1 + bt.Start(); + for (sint32 i = 0; i < 100; i++) + gx2SurfaceCopySoftware( + memory_base + 0x11000000, 256, 256, 1, 0, 0, 4, + memory_base + 0x21000000, 256, 256, 1, 0, 0, 2, + 64, 64, 32 + ); + bt.Stop(); + debug_printf("Copy 1 - %lfms\n", bt.GetElapsedMilliseconds()); + // copy 2 + bt.Start(); + for (sint32 i = 0; i < 100; i++) + gx2SurfaceCopySoftware( + memory_base + 0x12000000, 256, 256, 1, 0, 0, 1, + memory_base + 0x22000000, 256, 256, 1, 0, 0, 4, + 64, 64, 128 + ); + bt.Stop(); + debug_printf("Copy 2 - %lfms\n", bt.GetElapsedMilliseconds()); + // copy 3 + bt.Start(); + for (sint32 i = 0; i < 100; i++) + gx2SurfaceCopySoftware( + memory_base + 0x12000000, 256, 256, 1, 0, 0, 4, + memory_base + 0x22000000, 256, 256, 1, 0, 0, 4, + 64, 512, 32 + ); + bt.Stop(); + debug_printf("Copy 3 - %lfms\n", bt.GetElapsedMilliseconds()); + + cemu_assert_debug(false); + + // with bpp switch optimized away: + // Copy 0 - 19.777100ms + // Copy 1 - 14.311300ms + // Copy 2 - 10.837700ms + // Copy 3 - 158.174400ms + + // Copy 0 - 19.846800ms + // Copy 1 - 14.054000ms + // Copy 2 - 11.013500ms + // Copy 3 - 159.916000ms + + // with fast path added: + // Copy 0 - 0.222400ms + // Copy 1 - 14.125700ms + // Copy 2 - 13.298100ms + // Copy 3 - 1.764500ms + + // with shared offset: + // Copy 0 - 0.143300ms + // Copy 1 - 13.814200ms + // Copy 2 - 10.309500ms + // Copy 3 - 1.191900ms +} diff --git a/src/Cafe/OS/libs/gx2/GX2_Surface_Copy.h b/src/Cafe/OS/libs/gx2/GX2_Surface_Copy.h new file mode 100644 index 00000000..6f94a431 --- /dev/null +++ b/src/Cafe/OS/libs/gx2/GX2_Surface_Copy.h @@ -0,0 +1,6 @@ +#pragma once + +namespace GX2 +{ + void GX2SurfaceCopyInit(); +}; diff --git a/src/Cafe/OS/libs/gx2/GX2_Texture.cpp b/src/Cafe/OS/libs/gx2/GX2_Texture.cpp new file mode 100644 index 00000000..d986e307 --- /dev/null +++ b/src/Cafe/OS/libs/gx2/GX2_Texture.cpp @@ -0,0 +1,392 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/HW/Latte/ISA/RegDefines.h" +#include "GX2.h" +#include "GX2_Texture.h" + +#include "Cafe/HW/Latte/Core/Latte.h" +#include "Cafe/HW/Latte/Core/LatteDraw.h" +#include "Cafe/HW/Latte/Core/LattePM4.h" +#include "Cafe/HW/Latte/ISA/LatteReg.h" + +namespace GX2 +{ + using namespace Latte; + + /****** Texture functions ******/ + + void GX2InitTextureRegs(GX2Texture* texture) + { + uint32 _regs[5] = { 0 }; + + // some values may not be zero + if (texture->viewNumMips == 0) + texture->viewNumMips = 1; + if (texture->viewNumSlices == 0) + texture->viewNumSlices = 1; + + if (texture->surface.height == 0) + texture->surface.height = 1; + if (texture->surface.depth == 0) + texture->surface.depth = 1; + if (texture->surface.numLevels == 0) + texture->surface.numLevels = 1; + + // texture parameters + uint32 viewNumMips = texture->viewNumMips; + uint32 viewNumSlices = texture->viewNumSlices; + uint32 viewFirstMip = texture->viewFirstMip; + uint32 viewFirstSlice = texture->viewFirstSlice; + uint32 compSel = texture->compSel; + + // surface parameters + uint32 width = texture->surface.width; + uint32 height = texture->surface.height; + uint32 depth = texture->surface.depth; + uint32 pitch = texture->surface.pitch; + uint32 numMips = texture->surface.numLevels; + Latte::E_GX2SURFFMT format = texture->surface.format; + Latte::E_DIM dim = texture->surface.dim; + uint32 tileMode = (uint32)texture->surface.tileMode.value(); + uint32 surfaceFlags = texture->surface.resFlag; + uint32 surfaceAA = texture->surface.aa; + + // calculate register word 0 + Latte::E_HWSURFFMT formatHw = Latte::GetHWFormat(format); + + Latte::LATTE_SQ_TEX_RESOURCE_WORD0_N newRegWord0; + newRegWord0.set_DIM(dim); + newRegWord0.set_TILE_MODE(Latte::MakeHWTileMode(texture->surface.tileMode)); + newRegWord0.set_TILE_TYPE((surfaceFlags&4) != 0); + + uint32 pixelPitch = pitch; + if (Latte::IsCompressedFormat(formatHw)) + pixelPitch *= 4; + + if(pixelPitch == 0) + newRegWord0.set_PITCH(0x7FF); + else + newRegWord0.set_PITCH((pixelPitch >> 3) - 1); + + if (width == 0) + newRegWord0.set_WIDTH(0x1FFF); + else + newRegWord0.set_WIDTH(width - 1); + + texture->regTexWord0 = newRegWord0; + + // calculate register word 1 + Latte::LATTE_SQ_TEX_RESOURCE_WORD1_N newRegWord1; + newRegWord1.set_HEIGHT(height - 1); + + if (dim == Latte::E_DIM::DIM_CUBEMAP) + { + newRegWord1.set_DEPTH((depth / 6) - 1); + } + else if (dim == E_DIM::DIM_3D || + dim == E_DIM::DIM_2D_ARRAY_MSAA || + dim == E_DIM::DIM_2D_ARRAY || + dim == E_DIM::DIM_1D_ARRAY) + { + newRegWord1.set_DEPTH(depth - 1); + } + else + { + newRegWord1.set_DEPTH(0); + } + newRegWord1.set_DATA_FORMAT(formatHw); + texture->regTexWord1 = newRegWord1; + + // calculate register word 2 + LATTE_SQ_TEX_RESOURCE_WORD4_N newRegWord4; + + LATTE_SQ_TEX_RESOURCE_WORD4_N::E_FORMAT_COMP formatComp; + if (HAS_FLAG(format, Latte::E_GX2SURFFMT::FMT_BIT_SIGNED)) + formatComp = LATTE_SQ_TEX_RESOURCE_WORD4_N::E_FORMAT_COMP::COMP_SIGNED; + else + formatComp = LATTE_SQ_TEX_RESOURCE_WORD4_N::E_FORMAT_COMP::COMP_UNSIGNED; + newRegWord4.set_FORMAT_COMP_X(formatComp); + newRegWord4.set_FORMAT_COMP_Y(formatComp); + newRegWord4.set_FORMAT_COMP_Z(formatComp); + newRegWord4.set_FORMAT_COMP_W(formatComp); + + if (HAS_FLAG(format, Latte::E_GX2SURFFMT::FMT_BIT_FLOAT)) + newRegWord4.set_NUM_FORM_ALL(LATTE_SQ_TEX_RESOURCE_WORD4_N::E_NUM_FORMAT_ALL::NUM_FORMAT_SCALED); + else if (HAS_FLAG(format, Latte::E_GX2SURFFMT::FMT_BIT_INT)) + newRegWord4.set_NUM_FORM_ALL(LATTE_SQ_TEX_RESOURCE_WORD4_N::E_NUM_FORMAT_ALL::NUM_FORMAT_INT); + else + newRegWord4.set_NUM_FORM_ALL(LATTE_SQ_TEX_RESOURCE_WORD4_N::E_NUM_FORMAT_ALL::NUM_FORMAT_NORM); + + if (HAS_FLAG(format, Latte::E_GX2SURFFMT::FMT_BIT_SRGB)) + newRegWord4.set_FORCE_DEGAMMA(true); + + newRegWord4.set_ENDIAN_SWAP(GX2::GetSurfaceFormatSwapMode((Latte::E_GX2SURFFMT)format)); + + newRegWord4.set_REQUEST_SIZE(2); + + newRegWord4.set_DST_SEL_X((Latte::LATTE_SQ_TEX_RESOURCE_WORD4_N::E_SEL)((compSel >> 24) & 0x7)); + newRegWord4.set_DST_SEL_Y((Latte::LATTE_SQ_TEX_RESOURCE_WORD4_N::E_SEL)((compSel >> 16) & 0x7)); + newRegWord4.set_DST_SEL_Z((Latte::LATTE_SQ_TEX_RESOURCE_WORD4_N::E_SEL)((compSel >> 8) & 0x7)); + newRegWord4.set_DST_SEL_W((Latte::LATTE_SQ_TEX_RESOURCE_WORD4_N::E_SEL)((compSel >> 0) & 0x7)); + + newRegWord4.set_BASE_LEVEL(viewFirstMip); + texture->regTexWord4 = newRegWord4; + + // calculate register word 3 + LATTE_SQ_TEX_RESOURCE_WORD5_N newRegWord5; + newRegWord5.set_LAST_LEVEL(viewFirstMip + viewNumMips - 1); + newRegWord5.set_BASE_ARRAY(viewFirstSlice); + newRegWord5.set_LAST_ARRAY(viewFirstSlice + viewNumSlices - 1); + if (dim == Latte::E_DIM::DIM_CUBEMAP && ((depth / 6) - 1) != 0) + newRegWord5.set_UKN_BIT_30(true); + if(surfaceAA >= 1 && surfaceAA <= 3) + newRegWord5.set_LAST_LEVEL(surfaceAA); + texture->regTexWord5 = newRegWord5; + // calculate register word 4 + LATTE_SQ_TEX_RESOURCE_WORD6_N newRegWord6; + newRegWord6.set_MAX_ANISO(4); + newRegWord6.set_PERF_MODULATION(7); + newRegWord6.set_TYPE(Latte::LATTE_SQ_TEX_RESOURCE_WORD6_N::E_TYPE::VTX_VALID_TEXTURE); + texture->regTexWord6 = newRegWord6; + } + + void _GX2SetTexture(GX2Texture* tex, Latte::REGADDR baseRegister, uint32 textureUnitIndex) + { + GX2ReserveCmdSpace(2 + 7); + + MPTR imagePtr = tex->surface.imagePtr; + MPTR mipPtr = tex->surface.mipPtr; + if (mipPtr == MPTR_NULL) + mipPtr = imagePtr; + + uint32 swizzle = tex->surface.swizzle; + + cemu_assert_debug((swizzle & 0xFF) == 0); // does the low byte in swizzle field have any meaning? + + if (Latte::TM_IsMacroTiled(tex->surface.tileMode)) + { + uint32 swizzleStopLevel = (swizzle >> 16) & 0xFF; + // combine swizzle with image ptr if base level is macro tiled + if (swizzleStopLevel > 0) + imagePtr ^= (swizzle & 0xFFFF); + // combine swizzle with mip ptr if first mip (level 1) is macro tiled + if (swizzleStopLevel > 1) + mipPtr ^= (swizzle & 0xFFFF); + } + + gx2WriteGather_submit(pm4HeaderType3(IT_SET_RESOURCE, 8), + baseRegister + textureUnitIndex * 7 - mmSQ_TEX_RESOURCE_WORD0, + tex->regTexWord0, + tex->regTexWord1, + memory_virtualToPhysical(imagePtr) >> 8, + memory_virtualToPhysical(mipPtr) >> 8, + tex->regTexWord4, + tex->regTexWord5, + tex->regTexWord6); + } + + void GX2SetPixelTexture(GX2Texture* tex, uint32 texUnit) + { + cemu_assert_debug(texUnit < Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE); + _GX2SetTexture(tex, Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_PS, texUnit); + } + + void GX2SetVertexTexture(GX2Texture* tex, uint32 texUnit) + { + cemu_assert_debug(texUnit < Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE); + _GX2SetTexture(tex, Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_VS, texUnit); + } + + void GX2SetGeometryTexture(GX2Texture* tex, uint32 texUnit) + { + cemu_assert_debug(texUnit < Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE); + _GX2SetTexture(tex, Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_GS, texUnit); + } + + void GX2SetComputeTexture(GX2Texture* tex, uint32 texUnit) + { + GX2SetVertexTexture(tex, texUnit); + } + + /****** Sampler functions ******/ + + void GX2InitSampler(GX2Sampler* sampler, LATTE_SQ_TEX_SAMPLER_WORD0_0::E_CLAMP clampXYZ, LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER filterMinMag) + { + LATTE_SQ_TEX_SAMPLER_WORD0_0 word0{}; + word0.set_CLAMP_X(clampXYZ).set_CLAMP_Y(clampXYZ).set_CLAMP_Z(clampXYZ); + word0.set_XY_MAG_FILTER(filterMinMag).set_XY_MIN_FILTER(filterMinMag); + word0.set_Z_FILTER(LATTE_SQ_TEX_SAMPLER_WORD0_0::E_Z_FILTER::POINT); + word0.set_MIP_FILTER(LATTE_SQ_TEX_SAMPLER_WORD0_0::E_Z_FILTER::POINT); + word0.set_TEX_ARRAY_OVERRIDE(true); + + LATTE_SQ_TEX_SAMPLER_WORD1_0 word1{}; + word1.set_MAX_LOD(0x3FF); + + LATTE_SQ_TEX_SAMPLER_WORD2_0 word2{}; + word2.set_TYPE(LATTE_SQ_TEX_SAMPLER_WORD2_0::E_SAMPLER_TYPE::UKN1); + + sampler->word0 = word0; + sampler->word1 = word1; + sampler->word2 = word2; + } + + void GX2InitSamplerXYFilter(GX2Sampler* sampler, LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER magFilter, LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER minFilter, uint32 maxAnisoRatio) + { + LATTE_SQ_TEX_SAMPLER_WORD0_0 word0 = sampler->word0; + if (maxAnisoRatio == 0) + { + word0.set_XY_MAG_FILTER(magFilter); + word0.set_XY_MIN_FILTER(minFilter); + word0.set_MAX_ANISO_RATIO(0); + } + else + { + auto getAnisoFilter = [](LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER filter) -> LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER + { + if (filter == LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER::POINT) + return LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER::ANISO_POINT; + else if (filter == LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER::BILINEAR) + return LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER::ANISO_BILINEAR; + else + cemu_assert_debug(false); + return LATTE_SQ_TEX_SAMPLER_WORD0_0::E_XY_FILTER::POINT; + }; + word0.set_XY_MAG_FILTER(getAnisoFilter(magFilter)); + word0.set_XY_MIN_FILTER(getAnisoFilter(minFilter)); + word0.set_MAX_ANISO_RATIO(maxAnisoRatio); + } + sampler->word0 = word0; + } + + void GX2InitSamplerZMFilter(GX2Sampler* sampler, LATTE_SQ_TEX_SAMPLER_WORD0_0::E_Z_FILTER zFilter, LATTE_SQ_TEX_SAMPLER_WORD0_0::E_Z_FILTER mipFilter) + { + LATTE_SQ_TEX_SAMPLER_WORD0_0 word0 = sampler->word0; + word0.set_Z_FILTER(zFilter); + word0.set_MIP_FILTER(mipFilter); + sampler->word0 = word0; + } + + void GX2InitSamplerLOD(GX2Sampler* sampler, float minLod, float maxLod, float lodBias) + { + // known special cases: Mario & Sonic Rio passes minimum and maximum float values for minLod/maxLod + if (minLod < 0.0) + minLod = 0.0; + if (maxLod > 16.0) + maxLod = 16.0; + + uint32 iMinLod = ((uint32)floorf(minLod * 64.0f)); + uint32 iMaxLod = ((uint32)floorf(maxLod * 64.0f)); + sint32 iLodBias = (sint32)((sint32)floorf(lodBias * 64.0f)); // input range: -32.0 to 32.0 + iMinLod = std::clamp(iMinLod, 0u, 1023u); + iMaxLod = std::clamp(iMaxLod, 0u, 1023u); + iLodBias = std::clamp(iLodBias, -2048, 2047); + + LATTE_SQ_TEX_SAMPLER_WORD1_0 word1 = sampler->word1; + word1.set_MIN_LOD(iMinLod); + word1.set_MAX_LOD(iMaxLod); + word1.set_LOD_BIAS(iLodBias); + + sampler->word1 = word1; + } + + void GX2InitSamplerClamping(GX2Sampler* sampler, LATTE_SQ_TEX_SAMPLER_WORD0_0::E_CLAMP clampX, LATTE_SQ_TEX_SAMPLER_WORD0_0::E_CLAMP clampY, LATTE_SQ_TEX_SAMPLER_WORD0_0::E_CLAMP clampZ) + { + LATTE_SQ_TEX_SAMPLER_WORD0_0 word0 = sampler->word0; + word0.set_CLAMP_X(clampX); + word0.set_CLAMP_Y(clampY); + word0.set_CLAMP_Z(clampZ); + sampler->word0 = word0; + } + + void GX2InitSamplerBorderType(GX2Sampler* sampler, LATTE_SQ_TEX_SAMPLER_WORD0_0::E_BORDER_COLOR_TYPE borderColorType) + { + LATTE_SQ_TEX_SAMPLER_WORD0_0 word0 = sampler->word0; + word0.set_BORDER_COLOR_TYPE(borderColorType); + sampler->word0 = word0; + } + + void GX2InitSamplerDepthCompare(GX2Sampler* sampler, LATTE_SQ_TEX_SAMPLER_WORD0_0::E_DEPTH_COMPARE depthCompareFunction) + { + LATTE_SQ_TEX_SAMPLER_WORD0_0 word0 = sampler->word0; + word0.set_DEPTH_COMPARE_FUNCTION(depthCompareFunction); + sampler->word0 = word0; + } + + void _GX2SetSampler(GX2Sampler* sampler, uint32 samplerIndex) + { + GX2ReserveCmdSpace(5); + gx2WriteGather_submit(pm4HeaderType3(IT_SET_SAMPLER, 1 + 3), + samplerIndex * 3, + sampler->word0, sampler->word1, sampler->word2); + } + + void GX2SetPixelSampler(GX2Sampler* sampler, uint32 samplerIndex) + { + _GX2SetSampler(sampler, samplerIndex + SAMPLER_BASE_INDEX_PIXEL); + } + + void GX2SetVertexSampler(GX2Sampler* sampler, uint32 vertexSamplerIndex) + { + _GX2SetSampler(sampler, vertexSamplerIndex + SAMPLER_BASE_INDEX_VERTEX); + } + + void GX2SetGeometrySampler(GX2Sampler* sampler, uint32 geometrySamplerIndex) + { + _GX2SetSampler(sampler, geometrySamplerIndex + SAMPLER_BASE_INDEX_GEOMETRY); + } + + void GX2SetComputeSampler(GX2Sampler* sampler, uint32 computeSamplerIndex) + { + _GX2SetSampler(sampler, computeSamplerIndex + SAMPLER_BASE_INDEX_VERTEX); // uses vertex shader stage + } + + void GX2SetSamplerBorderColor(uint32 registerBaseOffset, uint32 samplerIndex, float red, float green, float blue, float alpha) + { + GX2ReserveCmdSpace(6); + gx2WriteGather_submit( + pm4HeaderType3(IT_SET_CONFIG_REG, 1 + 4), + registerBaseOffset + samplerIndex * 4 - LATTE_REG_BASE_CONFIG, + red, green, blue, alpha); + } + + void GX2SetPixelSamplerBorderColor(uint32 pixelSamplerIndex, float red, float green, float blue, float alpha) + { + GX2SetSamplerBorderColor(REGADDR::TD_PS_SAMPLER0_BORDER_RED, pixelSamplerIndex, red, green, blue, alpha); + } + + void GX2SetVertexSamplerBorderColor(uint32 vertexSamplerIndex, float red, float green, float blue, float alpha) + { + GX2SetSamplerBorderColor(REGADDR::TD_VS_SAMPLER0_BORDER_RED, vertexSamplerIndex, red, green, blue, alpha); + } + + void GX2SetGeometrySamplerBorderColor(uint32 geometrySamplerIndex, float red, float green, float blue, float alpha) + { + GX2SetSamplerBorderColor(REGADDR::TD_GS_SAMPLER0_BORDER_RED, geometrySamplerIndex, red, green, blue, alpha); + } + + void GX2TextureInit() + { + // texture + cafeExportRegister("gx2", GX2InitTextureRegs, LogType::GX2); + cafeExportRegister("gx2", GX2SetPixelTexture, LogType::GX2); + cafeExportRegister("gx2", GX2SetVertexTexture, LogType::GX2); + cafeExportRegister("gx2", GX2SetGeometryTexture, LogType::GX2); + cafeExportRegister("gx2", GX2SetComputeTexture, LogType::GX2); + + // sampler + cafeExportRegister("gx2", GX2InitSampler, LogType::GX2); + cafeExportRegister("gx2", GX2InitSamplerXYFilter, LogType::GX2); + cafeExportRegister("gx2", GX2InitSamplerZMFilter, LogType::GX2); + cafeExportRegister("gx2", GX2InitSamplerLOD, LogType::GX2); + cafeExportRegister("gx2", GX2InitSamplerClamping, LogType::GX2); + cafeExportRegister("gx2", GX2InitSamplerBorderType, LogType::GX2); + cafeExportRegister("gx2", GX2InitSamplerDepthCompare, LogType::GX2); + cafeExportRegister("gx2", GX2SetPixelSampler, LogType::GX2); + cafeExportRegister("gx2", GX2SetVertexSampler, LogType::GX2); + cafeExportRegister("gx2", GX2SetGeometrySampler, LogType::GX2); + cafeExportRegister("gx2", GX2SetComputeSampler, LogType::GX2); + cafeExportRegister("gx2", GX2SetPixelSamplerBorderColor, LogType::GX2); + cafeExportRegister("gx2", GX2SetVertexSamplerBorderColor, LogType::GX2); + cafeExportRegister("gx2", GX2SetGeometrySamplerBorderColor, LogType::GX2); + } + +}; diff --git a/src/Cafe/OS/libs/gx2/GX2_Texture.h b/src/Cafe/OS/libs/gx2/GX2_Texture.h new file mode 100644 index 00000000..530d6e28 --- /dev/null +++ b/src/Cafe/OS/libs/gx2/GX2_Texture.h @@ -0,0 +1,37 @@ +#pragma once +#include "Cafe/HW/Latte/ISA/LatteReg.h" +#include "GX2_Surface.h" + +namespace GX2 +{ + struct GX2Texture + { + /* +0x00 */ GX2Surface surface; + /* +0x74 */ uint32be viewFirstMip; + /* +0x78 */ uint32be viewNumMips; + /* +0x7C */ uint32be viewFirstSlice; + /* +0x80 */ uint32be viewNumSlices; + /* +0x84 */ uint32be compSel; + /* +0x88 */ betype<Latte::LATTE_SQ_TEX_RESOURCE_WORD0_N> regTexWord0; + /* +0x8C */ betype<Latte::LATTE_SQ_TEX_RESOURCE_WORD1_N> regTexWord1; + // word2 and word3 are the base/mip address and are not stored + /* +0x90 */ betype<Latte::LATTE_SQ_TEX_RESOURCE_WORD4_N> regTexWord4; + /* +0x94 */ betype<Latte::LATTE_SQ_TEX_RESOURCE_WORD5_N> regTexWord5; + /* +0x98 */ betype<Latte::LATTE_SQ_TEX_RESOURCE_WORD6_N> regTexWord6; + }; + + static_assert(sizeof(GX2Texture) == 0x9C); + + struct GX2Sampler + { + betype<Latte::LATTE_SQ_TEX_SAMPLER_WORD0_0> word0; + betype<Latte::LATTE_SQ_TEX_SAMPLER_WORD1_0> word1; + betype<Latte::LATTE_SQ_TEX_SAMPLER_WORD2_0> word2; + }; + + static_assert(sizeof(GX2Sampler) == 12); + + void GX2InitTextureRegs(GX2Texture* texture); + + void GX2TextureInit(); +}; \ No newline at end of file diff --git a/src/Cafe/OS/libs/gx2/GX2_TilingAperture.cpp b/src/Cafe/OS/libs/gx2/GX2_TilingAperture.cpp new file mode 100644 index 00000000..5b68a00d --- /dev/null +++ b/src/Cafe/OS/libs/gx2/GX2_TilingAperture.cpp @@ -0,0 +1,460 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "GX2.h" +#include "Cafe/HW/Latte/LatteAddrLib/LatteAddrLib.h" +#include "Cafe/HW/Latte/Core/LatteTextureLoader.h" + +#define GX2_MAX_ACTIVE_TILING_APERATURES (32) + +struct ActiveTilingAperature +{ + uint32 addr; + uint32 size; + uint32 handle; + uint32 endianMode; + // surface info + GX2Surface surface; + uint32 sliceIndex; + uint32 mipLevel; +}; + +ActiveTilingAperature activeTilingAperature[GX2_MAX_ACTIVE_TILING_APERATURES]; +sint32 activeTilingAperatureCount = 0; + +MPTR GX2TilingAperature_allocateTilingMemory(uint32 size) +{ + uint32 currentOffset = 0; + while( true ) + { + // align offset + currentOffset = (currentOffset+0xFFF)&~0xFFF; + // check if out of range + if( (currentOffset+size) >= MEMORY_TILINGAPERTURE_AREA_SIZE ) + break; + // check if range intersects with any already allocated range + bool isAvailable = true; + uint32 nextOffset = 0xFFFFFFFF; + for(sint32 i=0; i<activeTilingAperatureCount; i++) + { + uint32 startA = currentOffset; + uint32 endA = startA+size; + uint32 startB = activeTilingAperature[i].addr - MEMORY_TILINGAPERTURE_AREA_ADDR; + uint32 endB = startB+activeTilingAperature[i].size; + if( startA < endB && endA >= startB ) + { + isAvailable = false; + nextOffset = std::min(nextOffset, endB); + } + } + if( isAvailable ) + return currentOffset + MEMORY_TILINGAPERTURE_AREA_ADDR; + currentOffset = nextOffset; + } + return MPTR_NULL; +} + +std::atomic<uint32> sGenAperatureHandle{1}; + +uint32 GX2TilingAperature_GenerateHandle() +{ + return sGenAperatureHandle.fetch_add(1); +} + +template<typename copyType, int count, bool isWrite> +void copyValue(uint8* outputBlockData, uint8* inputBlockData) +{ + if (isWrite) + { + *(copyType*)outputBlockData = *(copyType*)inputBlockData; + if (count >= 2) + ((copyType*)outputBlockData)[1] = ((copyType*)inputBlockData)[1]; + if (count >= 3) + ((copyType*)outputBlockData)[2] = ((copyType*)inputBlockData)[2]; + if (count >= 4) + ((copyType*)outputBlockData)[3] = ((copyType*)inputBlockData)[3]; + } + else + { + *(copyType*)inputBlockData = *(copyType*)outputBlockData; + if (count >= 2) + ((copyType*)inputBlockData)[1] = ((copyType*)outputBlockData)[1]; + if (count >= 3) + ((copyType*)inputBlockData)[2] = ((copyType*)outputBlockData)[2]; + if (count >= 4) + ((copyType*)inputBlockData)[3] = ((copyType*)outputBlockData)[3]; + } +} + +template<int bpp, bool isWrite, int surfaceTileMode> +void retileTexture(ActiveTilingAperature* tilingAperture, uint8* inputData, uint8* outputData, sint32 texelWidth, sint32 texelHeight, sint32 surfaceSlice, sint32 surfacePitch, sint32 surfaceHeight, sint32 surfaceDepth, LatteAddrLib::CachedSurfaceAddrInfo* cachedInfo) +{ + for (sint32 y = 0; y < texelHeight; y++) + { + uint32 srcOffset; + uint8* inputBlockData; + if (bpp != 8) + { + srcOffset = (0 + y*surfacePitch)*(bpp / 8); + inputBlockData = inputData + srcOffset; + } + for (sint32 x = 0; x < texelWidth; x++) + { + // calculate address of input block + sint32 texelX = x; + sint32 texelY = y; + if (bpp == 8) + { + texelX ^= 8; + texelY ^= 2; + srcOffset = (texelX + texelY*surfacePitch)*(bpp / 8); + inputBlockData = inputData + srcOffset; + } + // calculate address of output block + uint32 dstBitPos = 0; + uint32 dstOffset = 0; + if (surfaceTileMode == 4) + dstOffset = ComputeSurfaceAddrFromCoordMacroTiledCached_tm04_sample1(x, y, cachedInfo); + else if (surfaceTileMode == 2 || surfaceTileMode == 3) + { + dstOffset = LatteAddrLib::ComputeSurfaceAddrFromCoordMicroTiled(x, y, cachedInfo->slice, cachedInfo->bpp, cachedInfo->pitch, cachedInfo->height, (Latte::E_HWTILEMODE)cachedInfo->tileMode, false); + } + else if (surfaceTileMode == 1 || surfaceTileMode == 0) + { + dstOffset = LatteAddrLib::ComputeSurfaceAddrFromCoordLinear(x, y, cachedInfo->slice, 0, cachedInfo->bpp, cachedInfo->pitch, cachedInfo->height, cachedInfo->depth); + } + else + dstOffset = LatteAddrLib::ComputeSurfaceAddrFromCoordMacroTiledCached(x, y, cachedInfo); + uint8* outputBlockData = outputData + dstOffset; + if (bpp == 32) + copyValue<uint32, 1, isWrite>(outputBlockData, inputBlockData); + else if (bpp == 16) + copyValue<uint16, 1, isWrite>(outputBlockData, inputBlockData); + else if (bpp == 8) + copyValue<uint8, 1, isWrite>(outputBlockData, inputBlockData); + else if (bpp == 64) + copyValue<uint64, 1, isWrite>(outputBlockData, inputBlockData); + else if (bpp == 128) + copyValue<uint64, 2, isWrite>(outputBlockData, inputBlockData); + else + { + cemu_assert_unimplemented(); + } + if (bpp != 8) + { + inputBlockData += (bpp / 8); + } + } + } +} + +template<int bpp, bool isWrite> +void retileTexture_tm04_sample1(ActiveTilingAperature* tilingAperture, uint8* inputData, uint8* outputData, sint32 texelWidth, sint32 texelHeight, sint32 surfaceSlice, sint32 surfacePitch, sint32 surfaceHeight, sint32 surfaceDepth, LatteAddrLib::CachedSurfaceAddrInfo* cachedInfo) +{ + uint16* tableBase = cachedInfo->microTilePixelIndexTable + ((cachedInfo->slice & 7) << 6); + for (sint32 y = 0; y < texelHeight; y++) + { + uint32 srcOffset; + uint8* inputBlockData; + if (bpp != 8) + { + srcOffset = (0 + y*surfacePitch)*(bpp / 8); + inputBlockData = inputData + srcOffset; + } + for (sint32 bx = 0; bx < texelWidth; bx += 8) + { + uint16* pixelOffsets = tableBase + ((y&7) << 3); + uint32 baseOffset = ComputeSurfaceAddrFromCoordMacroTiledCached_tm04_sample1(bx, y, cachedInfo); + for (sint32 x = bx; x < bx+8; x++) + { + // calculate address of input block + if (bpp == 8) + { + sint32 texelX = x; + sint32 texelY = y; + texelX ^= 8; + texelY ^= 2; + srcOffset = (texelX + texelY*surfacePitch)*(bpp / 8); + inputBlockData = inputData + srcOffset; + } + // calculate address of output block + uint32 dstBitPos = 0; + uint32 pixelIndex = *pixelOffsets; + pixelOffsets++; + uint32 pixelOffset = pixelIndex * (bpp/8); + uint32 elemOffset = pixelOffset; + if ((bpp * 8) > 256) + { + // separate group bytes, for small formats this step is not necessary since elemOffset is never over 0xFF (maximum is 8*8*bpp) + elemOffset = (elemOffset & 0xFF) | ((elemOffset&~0xFF) << 3); + } + sint32 offset = baseOffset + elemOffset; + + uint8* outputBlockData = outputData + offset; + if (bpp == 32) + copyValue<uint32, 1, isWrite>(outputBlockData, inputBlockData); + else if (bpp == 16) + copyValue<uint16, 1, isWrite>(outputBlockData, inputBlockData); + else if (bpp == 8) + copyValue<uint8, 1, isWrite>(outputBlockData, inputBlockData); + else if (bpp == 64) + copyValue<uint64, 1, isWrite>(outputBlockData, inputBlockData); + else if (bpp == 128) + copyValue<uint64, 2, isWrite>(outputBlockData, inputBlockData); + else + { + cemu_assert_unimplemented(); + } + if (bpp != 8) + { + inputBlockData += (bpp / 8); + } + } + } + // copy remaining partial block + for (sint32 x = (texelWidth&~7); x < texelWidth; x++) + { + // calculate address of input block + sint32 texelX = x; + sint32 texelY = y; + if (bpp == 8) + { + texelX ^= 8; + texelY ^= 2; + srcOffset = (texelX + texelY*surfacePitch)*(bpp / 8); + inputBlockData = inputData + srcOffset; + } + // calculate address of output block + uint32 dstBitPos = 0; + uint32 dstOffset = 0; + dstOffset = ComputeSurfaceAddrFromCoordMacroTiledCached_tm04_sample1(x, y, cachedInfo); + + uint8* outputBlockData = outputData + dstOffset; + if (bpp == 32) + copyValue<uint32, 1, isWrite>(outputBlockData, inputBlockData); + else if (bpp == 16) + copyValue<uint16, 1, isWrite>(outputBlockData, inputBlockData); + else if (bpp == 8) + copyValue<uint8, 1, isWrite>(outputBlockData, inputBlockData); + else if (bpp == 64) + copyValue<uint64, 1, isWrite>(outputBlockData, inputBlockData); + else if (bpp == 128) + copyValue<uint64, 2, isWrite>(outputBlockData, inputBlockData); + else + { + cemu_assert_unimplemented(); + } + if (bpp != 8) + { + inputBlockData += (bpp / 8); + } + } + } +} + +template<int bpp, bool isWrite> +void retileTextureWrapper(ActiveTilingAperature* tilingAperture, uint8* inputData, uint8* outputData, sint32 texelWidth, sint32 texelHeight, sint32 surfaceSlice, sint32 surfaceTileMode, sint32 surfacePitch, sint32 surfaceHeight, sint32 surfaceDepth, LatteAddrLib::CachedSurfaceAddrInfo* cachedInfo) +{ + if (surfaceTileMode == 0) + retileTexture<bpp, isWrite, 0>(tilingAperture, inputData, outputData, texelWidth, texelHeight, surfaceSlice, surfacePitch, surfaceHeight, surfaceDepth, cachedInfo); + else if (surfaceTileMode == 1) + retileTexture<bpp, isWrite, 1>(tilingAperture, inputData, outputData, texelWidth, texelHeight, surfaceSlice, surfacePitch, surfaceHeight, surfaceDepth, cachedInfo); + else if (surfaceTileMode == 2) + retileTexture<bpp, isWrite, 2>(tilingAperture, inputData, outputData, texelWidth, texelHeight, surfaceSlice, surfacePitch, surfaceHeight, surfaceDepth, cachedInfo); + else if (surfaceTileMode == 3) + retileTexture<bpp, isWrite, 3>(tilingAperture, inputData, outputData, texelWidth, texelHeight, surfaceSlice, surfacePitch, surfaceHeight, surfaceDepth, cachedInfo); + else if (surfaceTileMode == 4) + retileTexture<bpp, isWrite, 4>(tilingAperture, inputData, outputData, texelWidth, texelHeight, surfaceSlice, surfacePitch, surfaceHeight, surfaceDepth, cachedInfo); + else if (surfaceTileMode == 7) + retileTexture<bpp, isWrite, 7>(tilingAperture, inputData, outputData, texelWidth, texelHeight, surfaceSlice, surfacePitch, surfaceHeight, surfaceDepth, cachedInfo); + else + { + cemu_assert_unimplemented(); + } +} + +void LatteTextureLoader_begin(LatteTextureLoaderCtx* textureLoader, uint32 sliceIndex, uint32 mipIndex, MPTR physImagePtr, MPTR physMipPtr, Latte::E_GX2SURFFMT format, Latte::E_DIM dim, uint32 width, uint32 height, uint32 depth, uint32 mipLevels, uint32 pitch, Latte::E_HWTILEMODE tileMode, uint32 swizzle); + +void GX2TilingAperature_RetileTexture(ActiveTilingAperature* tilingAperture, bool doWrite) +{ + //uint64 timerTilingStart = benchmarkTimer_start(); + + Latte::E_GX2SURFFMT surfaceFormat = tilingAperture->surface.format; + uint32 surfaceSlice = tilingAperture->sliceIndex; + LatteAddrLib::AddrSurfaceInfo_OUT surfaceInfo = {0}; + GX2::GX2CalculateSurfaceInfo(&tilingAperture->surface, tilingAperture->mipLevel, &surfaceInfo); + uint32 surfacePitch = surfaceInfo.pitch; + uint32 surfaceSwizzle = tilingAperture->surface.swizzle; + uint32 surfacePipeSwizzle = (surfaceSwizzle>>8)&1; + uint32 surfaceBankSwizzle = ((surfaceSwizzle>>9)&3); + Latte::E_HWTILEMODE surfaceTileMode = surfaceInfo.hwTileMode; + uint32 surfaceDepth = std::max<uint32>(surfaceInfo.depth, 1); + sint32 width = std::max<uint32>((uint32)tilingAperture->surface.width >> tilingAperture->mipLevel, 1); + sint32 height = std::max<uint32>((uint32)tilingAperture->surface.height >> tilingAperture->mipLevel, 1); + + Latte::E_DIM surfaceDim = tilingAperture->surface.dim; + uint32 surfaceMipSwizzle = 0; // todo + uint32 mipLevels = tilingAperture->surface.numLevels; + // get texture info + uint8* inputData = (uint8*)memory_getPointerFromVirtualOffset(tilingAperture->addr); + uint8* outputData; + if( tilingAperture->mipLevel == 0 ) + outputData = (uint8*)memory_getPointerFromVirtualOffset(tilingAperture->surface.imagePtr); + else if( tilingAperture->mipLevel == 1 ) + outputData = (uint8*)memory_getPointerFromVirtualOffset(tilingAperture->surface.mipPtr); + else + outputData = (uint8*)memory_getPointerFromVirtualOffset(tilingAperture->surface.mipPtr + tilingAperture->surface.mipOffset[tilingAperture->mipLevel-1]); + + sint32 stepX = 1; + sint32 stepY = 1; + bool isCompressed = false; + if( Latte::IsCompressedFormat(surfaceFormat) ) + { + isCompressed = true; + stepX = 4; + stepY = 4; + } + uint32 bpp = surfaceInfo.bpp; + uint32 bytesPerPixel = bpp/8; + + LatteAddrLib::CachedSurfaceAddrInfo computeAddrInfo = { 0 }; + + SetupCachedSurfaceAddrInfo(&computeAddrInfo, surfaceSlice, 0, bpp, surfacePitch, surfaceInfo.height, surfaceInfo.depth, 1 * 1, surfaceTileMode, false, surfacePipeSwizzle, surfaceBankSwizzle); + + // init info for swizzle encoder/decoder + LatteTextureLoaderCtx textureLoaderCtx{}; + LatteTextureLoader_begin(&textureLoaderCtx, surfaceSlice, 0, tilingAperture->surface.imagePtr, tilingAperture->surface.mipPtr, surfaceFormat, surfaceDim, width, height, surfaceDepth, mipLevels, surfacePitch, surfaceTileMode, surfaceSwizzle); + + textureLoaderCtx.decodedTexelCountX = surfacePitch; + textureLoaderCtx.decodedTexelCountY = isCompressed ? (height + 3) / 4 : height; + + if( doWrite ) + { + if (surfaceTileMode == Latte::E_HWTILEMODE::TM_2D_TILED_THIN1 && bpp == 32 && isCompressed == false) + { + optimizedDecodeLoops<uint32, 1, true, false>(&textureLoaderCtx, inputData); + } + else if (bpp == 8) + retileTextureWrapper<8, true>(tilingAperture, inputData, outputData, width / stepX, height / stepY, surfaceSlice, (uint32)surfaceTileMode, surfacePitch, surfaceInfo.height, surfaceDepth, &computeAddrInfo); + else if (bpp == 16) + retileTextureWrapper<16, true>(tilingAperture, inputData, outputData, width / stepX, height / stepY, surfaceSlice, (uint32)surfaceTileMode, surfacePitch, surfaceInfo.height, surfaceDepth, &computeAddrInfo); + else if (bpp == 32) + retileTextureWrapper<32, true>(tilingAperture, inputData, outputData, width / stepX, height / stepY, surfaceSlice, (uint32)surfaceTileMode, surfacePitch, surfaceInfo.height, surfaceDepth, &computeAddrInfo); + else if (bpp == 64) + retileTextureWrapper<64, true>(tilingAperture, inputData, outputData, width / stepX, height / stepY, surfaceSlice, (uint32)surfaceTileMode, surfacePitch, surfaceInfo.height, surfaceDepth, &computeAddrInfo); + else if (bpp == 128) + retileTextureWrapper<128, true>(tilingAperture, inputData, outputData, width / stepX, height / stepY, surfaceSlice, (uint32)surfaceTileMode, surfacePitch, surfaceInfo.height, surfaceDepth, &computeAddrInfo); + else + { + cemu_assert_unimplemented(); + } + } + else + { + if (surfaceTileMode == Latte::E_HWTILEMODE::TM_2D_TILED_THIN1 && bpp == 32 && isCompressed == false) + { + optimizedDecodeLoops<uint32, 1, false, false>(&textureLoaderCtx, inputData); + } + else if (bpp == 8) + retileTextureWrapper<8, false>(tilingAperture, inputData, outputData, width / stepX, height / stepY, surfaceSlice, (uint32)surfaceTileMode, surfacePitch, surfaceInfo.height, surfaceDepth, &computeAddrInfo); + else if (bpp == 16) + retileTextureWrapper<16, false>(tilingAperture, inputData, outputData, width / stepX, height / stepY, surfaceSlice, (uint32)surfaceTileMode, surfacePitch, surfaceInfo.height, surfaceDepth, &computeAddrInfo); + else if (bpp == 32) + retileTextureWrapper<32, false>(tilingAperture, inputData, outputData, width / stepX, height / stepY, surfaceSlice, (uint32)surfaceTileMode, surfacePitch, surfaceInfo.height, surfaceDepth, &computeAddrInfo); + else if (bpp == 64) + retileTextureWrapper<64, false>(tilingAperture, inputData, outputData, width / stepX, height / stepY, surfaceSlice, (uint32)surfaceTileMode, surfacePitch, surfaceInfo.height, surfaceDepth, &computeAddrInfo); + else if (bpp == 128) + retileTextureWrapper<128, false>(tilingAperture, inputData, outputData, width / stepX, height / stepY, surfaceSlice, (uint32)surfaceTileMode, surfacePitch, surfaceInfo.height, surfaceDepth, &computeAddrInfo); + else + { + cemu_assert_unimplemented(); + } + } + + //double benchmarkTime = benchmarkTimer_stop(timerTilingStart); + //forceLogDebug_printf("TilingAperture res %04dx%04d fmt %04x tm %02x mip %d isWrite %d", (uint32)tilingAperture->surface.width, (uint32)tilingAperture->surface.height, (uint32)tilingAperture->surface.format, (uint32)tilingAperture->surface.tileMode, tilingAperture->mipLevel, doWrite?1:0); + //forceLogDebug_printf("Tiling took %.4lfms", benchmarkTime); +} + +void gx2Export_GX2AllocateTilingApertureEx(PPCInterpreter_t* hCPU) +{ + gx2Log_printf("GX2AllocateTilingApertureEx(0x%08x, %d, %d, %d, 0x%08x, 0x%08x)", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7], hCPU->gpr[8]); + GX2Surface* surface = (GX2Surface*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); + gx2Log_printf("Tiling Tex: %08X %dx%d Swizzle: %08x tm: %d fmt: %04x use: %02x", (uint32)surface->imagePtr, (uint32)surface->width, (uint32)surface->height, (uint32)surface->swizzle, (uint32)surface->tileMode.value(), (uint32)surface->format.value(), (uint32)surface->resFlag); + + if( activeTilingAperatureCount >= GX2_MAX_ACTIVE_TILING_APERATURES ) + { + debugBreakpoint(); + memory_writeU32(hCPU->gpr[8], MPTR_NULL); + memory_writeU32(hCPU->gpr[7], 0); + osLib_returnFromFunction(hCPU, 0); + return; + } + uint32 mipLevel = hCPU->gpr[4]; + uint32 sliceIndex = hCPU->gpr[5]; + + uint32 tilingSize = 0; + // calculate size of texture + Latte::E_GX2SURFFMT surfaceFormat = surface->format; + uint32 bitsPerPixel = Latte::GetFormatBits(surfaceFormat); + if (Latte::IsCompressedFormat(surfaceFormat)) + bitsPerPixel /= (4*4); + + // get surface pitch + LatteAddrLib::AddrSurfaceInfo_OUT surfaceInfo = {0}; + GX2::GX2CalculateSurfaceInfo(surface, 0, &surfaceInfo); + uint32 surfacePitch = surfaceInfo.pitch; + uint32 width = std::max<uint32>((uint32)surface->width >> mipLevel, 1); + uint32 height = std::max<uint32>((uint32)surface->height >> mipLevel, 1); + uint32 alignedWidth = (width+3)&~3; + uint32 alignedHeight = (height+3)&~3; + tilingSize = (surfacePitch*alignedHeight*bitsPerPixel+7)/8; + uint32 taHandle = GX2TilingAperature_GenerateHandle(); + // allocate memory for tiling space + MPTR tilingAddress = GX2TilingAperature_allocateTilingMemory(tilingSize); + if( tilingAddress == MPTR_NULL ) + { + cemu_assert_suspicious(); + memory_writeU32(hCPU->gpr[8], MPTR_NULL); + memory_writeU32(hCPU->gpr[7], 0); + osLib_returnFromFunction(hCPU, 0); + return; + } + // add tiling aperture entry + activeTilingAperature[activeTilingAperatureCount].addr = tilingAddress; + activeTilingAperature[activeTilingAperatureCount].size = tilingSize; + activeTilingAperature[activeTilingAperatureCount].handle = taHandle; + activeTilingAperature[activeTilingAperatureCount].endianMode = hCPU->gpr[6]; + activeTilingAperature[activeTilingAperatureCount].sliceIndex = sliceIndex; + activeTilingAperature[activeTilingAperatureCount].mipLevel = mipLevel; + memcpy(&activeTilingAperature[activeTilingAperatureCount].surface, surface, sizeof(GX2Surface)); + activeTilingAperatureCount++; + // return values + memory_writeU32(hCPU->gpr[8], tilingAddress); + memory_writeU32(hCPU->gpr[7], taHandle); + // load texture data into tiling area + GX2TilingAperature_RetileTexture(activeTilingAperature+activeTilingAperatureCount-1, false); + osLib_returnFromFunction(hCPU, 0); +} + +void gx2Export_GX2FreeTilingAperture(PPCInterpreter_t* hCPU) +{ + gx2Log_printf("GX2FreeTilingAperture(0x%08x)\n", hCPU->gpr[3]); + uint32 handle = hCPU->gpr[3]; + for(sint32 i=0; i<activeTilingAperatureCount; i++) + { + if( activeTilingAperature[i].handle == handle ) + { + // flush texture + GX2TilingAperature_RetileTexture(activeTilingAperature+i, true); + // remove entry + if( i+1 < activeTilingAperatureCount ) + { + memcpy(activeTilingAperature+i, activeTilingAperature+activeTilingAperatureCount-1, sizeof(ActiveTilingAperature)); + } + activeTilingAperatureCount--; + osLib_returnFromFunction(hCPU, 0); + return; + } + } + + osLib_returnFromFunction(hCPU, 0); +} diff --git a/src/Cafe/OS/libs/gx2/GX2_shader_legacy.cpp b/src/Cafe/OS/libs/gx2/GX2_shader_legacy.cpp new file mode 100644 index 00000000..b4555fae --- /dev/null +++ b/src/Cafe/OS/libs/gx2/GX2_shader_legacy.cpp @@ -0,0 +1,599 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/HW/Latte/ISA/RegDefines.h" +#include "Cafe/HW/Latte/Core/Latte.h" +#include "Cafe/HW/Latte/Core/LatteDraw.h" +#include "Cafe/HW/Latte/ISA/LatteReg.h" +#include "Cafe/HW/Latte/Core/LattePM4.h" + +#include "GX2.h" +#include "GX2_Shader.h" + +void gx2Export_GX2SetFetchShader(PPCInterpreter_t* hCPU) +{ + gx2Log_printf("GX2SetFetchShader(0x%08x)\n", hCPU->gpr[3]); + GX2ReserveCmdSpace(11); + GX2FetchShader_t* fetchShaderPtr = (GX2FetchShader_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); + cemu_assert_debug((_swapEndianU32(fetchShaderPtr->shaderPtr) & 0xFF) == 0); + + gx2WriteGather_submit( + // setup fetch shader + pm4HeaderType3(IT_SET_CONTEXT_REG, 1+5), + mmSQ_PGM_START_FS-0xA000, + _swapEndianU32(fetchShaderPtr->shaderPtr)>>8, // pointer divided by 256 + _swapEndianU32(fetchShaderPtr->shaderSize)>>3, // size divided by 8 + 0x10000, // ukn (ring buffer size?) + 0x10000, // ukn (ring buffer size?) + *(uint32be*)&(fetchShaderPtr->_regs[0]), + + // write instance step + pm4HeaderType3(IT_SET_CONTEXT_REG, 1+2), + mmVGT_INSTANCE_STEP_RATE_0-0xA000, + *(uint32be*)&(fetchShaderPtr->divisors[0]), + *(uint32be*)&(fetchShaderPtr->divisors[1])); + + osLib_returnFromFunction(hCPU, 0); +} + +void gx2Export_GX2GetVertexShaderGPRs(PPCInterpreter_t* hCPU) +{ + gx2Log_printf("GX2GetVertexShaderGPRs(0x%08x)\n", hCPU->gpr[3]); + GX2VertexShader_t* vertexShader = (GX2VertexShader_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); + uint8 numGPRs = _swapEndianU32(vertexShader->regs[0])&0xFF; + osLib_returnFromFunction(hCPU, numGPRs); +} + +void gx2Export_GX2GetVertexShaderStackEntries(PPCInterpreter_t* hCPU) +{ + gx2Log_printf("GX2GetVertexShaderStackEntries(0x%08x)\n", hCPU->gpr[3]); + GX2VertexShader_t* vertexShader = (GX2VertexShader_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); + uint8 stackEntries = (_swapEndianU32(vertexShader->regs[0])>>8)&0xFF; + osLib_returnFromFunction(hCPU, stackEntries); +} + +void gx2Export_GX2GetPixelShaderGPRs(PPCInterpreter_t* hCPU) +{ + gx2Log_printf("GX2GetPixelShaderGPRs(0x%08x)\n", hCPU->gpr[3]); + GX2PixelShader_t* pixelShader = (GX2PixelShader_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); + uint8 stackEntries = (_swapEndianU32(pixelShader->regs[0]))&0xFF; + osLib_returnFromFunction(hCPU, stackEntries); +} + +void gx2Export_GX2GetPixelShaderStackEntries(PPCInterpreter_t* hCPU) +{ + gx2Log_printf("GX2GetPixelShaderStackEntries(0x%08x)\n", hCPU->gpr[3]); + GX2PixelShader_t* pixelShader = (GX2PixelShader_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); + uint8 numGPRs = (_swapEndianU32(pixelShader->regs[0]>>8))&0xFF; + osLib_returnFromFunction(hCPU, numGPRs); +} + +void gx2Export_GX2SetVertexShader(PPCInterpreter_t* hCPU) +{ + gx2Log_printf("GX2SetVertexShader(0x%08x)\n", hCPU->gpr[3]); + GX2ReserveCmdSpace(100); + + GX2VertexShader_t* vertexShader = (GX2VertexShader_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); + + MPTR shaderProgramAddr; + uint32 shaderProgramSize; + + if( _swapEndianU32(vertexShader->shaderPtr) != MPTR_NULL ) + { + // without R API + shaderProgramAddr = _swapEndianU32(vertexShader->shaderPtr); + shaderProgramSize = _swapEndianU32(vertexShader->shaderSize); + } + else + { + shaderProgramAddr = vertexShader->rBuffer.GetVirtualAddr(); + shaderProgramSize = vertexShader->rBuffer.GetSize(); + } + + cemu_assert_debug(shaderProgramAddr != 0); + cemu_assert_debug(shaderProgramSize != 0); + + if( _swapEndianU32(vertexShader->shaderMode) == GX2_SHADER_MODE_GEOMETRY_SHADER ) + { + // in geometry shader mode the vertex shader is written to _ES register and almost all vs control registers are set by GX2SetGeometryShader + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 6)); + gx2WriteGather_submitU32AsBE(mmSQ_PGM_START_ES-0xA000); + gx2WriteGather_submitU32AsBE(memory_virtualToPhysical(shaderProgramAddr)>>8); + gx2WriteGather_submitU32AsBE(shaderProgramSize>>3); + gx2WriteGather_submitU32AsBE(0x100000); + gx2WriteGather_submitU32AsBE(0x100000); + gx2WriteGather_submitU32AsBE(_swapEndianU32(vertexShader->regs[0])); // unknown + } + else + { + gx2WriteGather_submit( + /* vertex shader program */ + pm4HeaderType3(IT_SET_CONTEXT_REG, 6), + mmSQ_PGM_START_VS-0xA000, + memory_virtualToPhysical(shaderProgramAddr)>>8, // physical address + shaderProgramSize>>3, // size + 0x100000, + 0x100000, + _swapEndianU32(vertexShader->regs[0]), // unknown + /* primitive id enable */ + pm4HeaderType3(IT_SET_CONTEXT_REG, 2), + mmVGT_PRIMITIVEID_EN-0xA000, + _swapEndianU32(vertexShader->regs[1]), + /* output config */ + pm4HeaderType3(IT_SET_CONTEXT_REG, 2), + mmSPI_VS_OUT_CONFIG-0xA000, + _swapEndianU32(vertexShader->regs[2])); + + if( (_swapEndianU32(vertexShader->regs[2]) & 1) != 0 ) + debugBreakpoint(); // per-component flag? + + // ukn + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 2)); + gx2WriteGather_submitU32AsBE(mmPA_CL_VS_OUT_CNTL-0xA000); + gx2WriteGather_submitU32AsBE(_swapEndianU32(vertexShader->regs[14])); + + uint32 numOutputIds = _swapEndianU32(vertexShader->regs[3]); + numOutputIds = std::min<uint32>(numOutputIds, 0xA); + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 1+numOutputIds)); + gx2WriteGather_submitU32AsBE(mmSPI_VS_OUT_ID_0-0xA000); + for(uint32 i=0; i<numOutputIds; i++) + { + gx2WriteGather_submitU32AsBE(_swapEndianU32(vertexShader->regs[4+i])); + } + + /* + VS _regs[]: + 0 ? + 1 mmVGT_PRIMITIVEID_EN (?) + 2 mmSPI_VS_OUT_CONFIG + 3 Number of used SPI_VS_OUT_ID_* entries + 4 - 13 SPI_VS_OUT_ID_0 - SPI_VS_OUT_ID_9 + 14 pa_cl_vs_out_cntl + ... + 17 - ?? semantic table entry (input) + + ... + 50 vgt_vertex_reuse_block_cntl + 51 vgt_hos_reuse_depth + */ + + // todo: mmSQ_PGM_CF_OFFSET_VS + // todo: mmVGT_STRMOUT_BUFFER_EN + // stream out + if( _swapEndianU32(vertexShader->usesStreamOut) != 0 ) + { + // stride 0 + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 2)); + gx2WriteGather_submitU32AsBE(mmVGT_STRMOUT_VTX_STRIDE_0-0xA000); + gx2WriteGather_submitU32AsBE(_swapEndianU32(vertexShader->streamOutVertexStride[0])>>2); + // stride 1 + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 2)); + gx2WriteGather_submitU32AsBE(mmVGT_STRMOUT_VTX_STRIDE_1-0xA000); + gx2WriteGather_submitU32AsBE(_swapEndianU32(vertexShader->streamOutVertexStride[1])>>2); + // stride 2 + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 2)); + gx2WriteGather_submitU32AsBE(mmVGT_STRMOUT_VTX_STRIDE_2-0xA000); + gx2WriteGather_submitU32AsBE(_swapEndianU32(vertexShader->streamOutVertexStride[2])>>2); + // stride 3 + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 2)); + gx2WriteGather_submitU32AsBE(mmVGT_STRMOUT_VTX_STRIDE_3-0xA000); + gx2WriteGather_submitU32AsBE(_swapEndianU32(vertexShader->streamOutVertexStride[3])>>2); + } + } + // update semantic table + uint32 vsSemanticTableSize = _swapEndianU32(vertexShader->regs[0x40/4]); + if( vsSemanticTableSize > 0 ) + { + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 1+1)); + gx2WriteGather_submitU32AsBE(mmSQ_VTX_SEMANTIC_CLEAR-0xA000); + gx2WriteGather_submitU32AsBE(0xFFFFFFFF); + if( vsSemanticTableSize == 0 ) + { + // todo: Figure out how this is done on real SW/HW (some vertex shaders don't have a semantic table) + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 1+1)); + gx2WriteGather_submitU32AsBE(mmSQ_VTX_SEMANTIC_0-0xA000); + gx2WriteGather_submitU32AsBE(0xFFFFFFFF); + } + else + { + uint32* vsSemanticTable = vertexShader->regs+(0x44/4); + vsSemanticTableSize = std::min<uint32>(vsSemanticTableSize, 0x20); + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 1+vsSemanticTableSize)); + gx2WriteGather_submitU32AsBE(mmSQ_VTX_SEMANTIC_0-0xA000); + for(uint32 i=0; i<vsSemanticTableSize; i++) + gx2WriteGather_submitU32AsLE(vsSemanticTable[i]); + } + } + + osLib_returnFromFunction(hCPU, 0); +} + +void gx2Export_GX2SetPixelShader(PPCInterpreter_t* hCPU) +{ + gx2Log_printf("GX2SetPixelShader(0x%08x)\n", hCPU->gpr[3]); + GX2ReserveCmdSpace(100); + + GX2PixelShader_t* pixelShader = (GX2PixelShader_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); + MPTR shaderProgramAddr; + uint32 shaderProgramSize; + + if( _swapEndianU32(pixelShader->shaderPtr) != MPTR_NULL ) + { + // old format + shaderProgramAddr = _swapEndianU32(pixelShader->shaderPtr); + shaderProgramSize = _swapEndianU32(pixelShader->shaderSize); + } + else + { + shaderProgramAddr = pixelShader->rBuffer.GetVirtualAddr(); + shaderProgramSize = pixelShader->rBuffer.GetSize(); + } + + gx2WriteGather_submit( + /* pixel shader program */ + pm4HeaderType3(IT_SET_CONTEXT_REG, 6), + mmSQ_PGM_START_PS - 0xA000, + memory_virtualToPhysical(shaderProgramAddr)>>8, // address + shaderProgramSize>>3, // size + 0x100000, + 0x100000, + _swapEndianU32(pixelShader->regs[0]), // ukn + /* setup pixel shader input control */ + pm4HeaderType3(IT_SET_CONTEXT_REG, 3), + mmSPI_PS_IN_CONTROL_0-0xA000, + _swapEndianU32(pixelShader->regs[2]), + _swapEndianU32(pixelShader->regs[3])); + // setup pixel shader extended inputs control + uint32 numInputs = _swapEndianU32(pixelShader->regs[4]); + if( numInputs > 0x20 ) + numInputs = 0x20; + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 1+numInputs)); + gx2WriteGather_submitU32AsBE(mmSPI_PS_INPUT_CNTL_0-0xA000); + for(uint32 i=0; i<numInputs; i++) + { + uint32 inputData = _swapEndianU32(pixelShader->regs[5+i]); + gx2WriteGather_submitU32AsBE(inputData); + } + + gx2WriteGather_submit( + /* mmCB_SHADER_MASK */ + pm4HeaderType3(IT_SET_CONTEXT_REG, 2), + mmCB_SHADER_MASK-0xA000, + _swapEndianU32(pixelShader->regs[37]), + /* mmCB_SHADER_CONTROL */ + pm4HeaderType3(IT_SET_CONTEXT_REG, 2), + mmCB_SHADER_CONTROL-0xA000, + _swapEndianU32(pixelShader->regs[38]), + /* mmDB_SHADER_CONTROL */ + pm4HeaderType3(IT_SET_CONTEXT_REG, 2), + mmDB_SHADER_CONTROL-0xA000, + _swapEndianU32(pixelShader->regs[39]), + /* SPI_INPUT_Z */ + pm4HeaderType3(IT_SET_CONTEXT_REG, 2), + mmSPI_INPUT_Z-0xA000, + _swapEndianU32(pixelShader->regs[40])); + + osLib_returnFromFunction(hCPU, 0); +} + +void gx2Export_GX2SetGeometryShader(PPCInterpreter_t* hCPU) +{ + gx2Log_printf("GX2SetGeometryShader(0x%08x)\n", hCPU->gpr[3]); + GX2ReserveCmdSpace(100); + + GX2GeometryShader_t* geometryShader = (GX2GeometryShader_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); + + MPTR shaderProgramAddr; + uint32 shaderProgramSize; + + if( _swapEndianU32(geometryShader->shaderPtr) != MPTR_NULL ) + { + // old format + shaderProgramAddr = _swapEndianU32(geometryShader->shaderPtr); + shaderProgramSize = _swapEndianU32(geometryShader->shaderSize); + } + else + { + shaderProgramAddr = geometryShader->rBuffer.GetVirtualAddr(); + shaderProgramSize = geometryShader->rBuffer.GetSize(); + } + + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 6)); + gx2WriteGather_submitU32AsBE(mmSQ_PGM_START_GS-0xA000); + gx2WriteGather_submitU32AsBE(memory_virtualToPhysical(shaderProgramAddr)>>8); + gx2WriteGather_submitU32AsBE(shaderProgramSize>>3); + gx2WriteGather_submitU32AsBE(0x100000); + gx2WriteGather_submitU32AsBE(0x100000); + gx2WriteGather_submitU32AsBE(_swapEndianU32(geometryShader->regs[0])); // unknown content (SQ_PGM_RESOURCES_GS) + + uint32 primitiveOut = _swapEndianU32(geometryShader->regs[1]); + + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 2)); + gx2WriteGather_submitU32AsBE(mmVGT_GS_OUT_PRIM_TYPE-0xA000); + gx2WriteGather_submitU32AsLE(geometryShader->regs[1]); + + gx2WriteGather_submit( + pm4HeaderType3(IT_SET_CONTEXT_REG, 2), + Latte::REGADDR::VGT_GS_MODE - 0xA000, + geometryShader->reg.VGT_GS_MODE + ); + + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 2)); + gx2WriteGather_submitU32AsBE(mmSQ_PGM_RESOURCES_GS-0xA000); + gx2WriteGather_submitU32AsLE(geometryShader->regs[0]); + + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 2)); + gx2WriteGather_submitU32AsBE(mmSQ_GS_VERT_ITEMSIZE-0xA000); + gx2WriteGather_submitU32AsLE(geometryShader->regs[5]); + + if( _swapEndianU32(geometryShader->useStreamout) != 0 ) + { + // stride 0 + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 2)); + gx2WriteGather_submitU32AsBE(mmVGT_STRMOUT_VTX_STRIDE_0-0xA000); + gx2WriteGather_submitU32AsBE(_swapEndianU32(geometryShader->streamoutStride[0])>>2); + // stride 1 + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 2)); + gx2WriteGather_submitU32AsBE(mmVGT_STRMOUT_VTX_STRIDE_1-0xA000); + gx2WriteGather_submitU32AsBE(_swapEndianU32(geometryShader->streamoutStride[1])>>2); + // stride 2 + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 2)); + gx2WriteGather_submitU32AsBE(mmVGT_STRMOUT_VTX_STRIDE_2-0xA000); + gx2WriteGather_submitU32AsBE(_swapEndianU32(geometryShader->streamoutStride[2])>>2); + // stride 3 + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 2)); + gx2WriteGather_submitU32AsBE(mmVGT_STRMOUT_VTX_STRIDE_3-0xA000); + gx2WriteGather_submitU32AsBE(_swapEndianU32(geometryShader->streamoutStride[3])>>2); + } + + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 2)); + gx2WriteGather_submitU32AsBE(mmVGT_STRMOUT_BUFFER_EN-0xA000); + gx2WriteGather_submitU32AsBE(_swapEndianU32(geometryShader->regs[18])); + + // set copy shader (written to vertex shader registers, vs in turn is written to es registers) + MPTR copyShaderProgramAddr; + uint32 copyShaderProgramSize; + if( _swapEndianU32(geometryShader->copyShaderPtr) != MPTR_NULL ) + { + copyShaderProgramAddr = _swapEndianU32(geometryShader->copyShaderPtr); + copyShaderProgramSize = _swapEndianU32(geometryShader->copyShaderSize); + } + else + { + copyShaderProgramAddr = geometryShader->rBufferCopyProgram.GetVirtualAddr(); + copyShaderProgramSize = geometryShader->rBufferCopyProgram.GetSize(); + } + + cemu_assert_debug((copyShaderProgramAddr>>8) != 0); + cemu_assert_debug((copyShaderProgramSize>>3) != 0); + + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 6)); + gx2WriteGather_submitU32AsBE(mmSQ_PGM_START_VS-0xA000); + gx2WriteGather_submitU32AsBE(memory_virtualToPhysical(copyShaderProgramAddr)>>8); + gx2WriteGather_submitU32AsBE(copyShaderProgramSize>>3); + gx2WriteGather_submitU32AsBE(0x100000); + gx2WriteGather_submitU32AsBE(0x100000); + gx2WriteGather_submitU32AsBE(_swapEndianU32(geometryShader->regs[4])); // mmSQ_PGM_RESOURCES_VS + + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 2)); + gx2WriteGather_submitU32AsBE(mmPA_CL_VS_OUT_CNTL-0xA000); + gx2WriteGather_submitU32AsBE(_swapEndianU32(geometryShader->regs[3])); + + // GS outputs + uint32 numOutputIds = _swapEndianU32(geometryShader->regs[7]); + numOutputIds = std::min<uint32>(numOutputIds, 0xA); + if( numOutputIds != 0 ) + { + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 1+numOutputIds)); + gx2WriteGather_submitU32AsBE(mmSPI_VS_OUT_ID_0-0xA000); + for(uint32 i=0; i<numOutputIds; i++) + { + gx2WriteGather_submitU32AsBE(_swapEndianU32(geometryShader->regs[8+i])); + } + } + + // output config + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 2)); + gx2WriteGather_submitU32AsBE(mmSPI_VS_OUT_CONFIG-0xA000); + gx2WriteGather_submitU32AsBE(_swapEndianU32(geometryShader->regs[6])); + + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 2)); + gx2WriteGather_submitU32AsBE(mmSQ_GSVS_RING_ITEMSIZE-0xA000); + gx2WriteGather_submitU32AsBE(_swapEndianU32(geometryShader->ringItemsize)&0x7FFF); + + /* + Geometry shader registers in regs[19]: + 0 SQ_PGM_RESOURCES_GS ? + 1 mmVGT_GS_OUT_PRIM_TYPE + 2 mmVGT_GS_MODE + 3 mmPA_CL_VS_OUT_CNTL + 4 mmSQ_PGM_RESOURCES_VS (set in combination with mmSQ_PGM_START_VS) + 5 mmSQ_GS_VERT_ITEMSIZE + 6 mmSPI_VS_OUT_CONFIG + 7 number of active mmSPI_VS_OUT_ID_* fields? + 8-17 mmSPI_VS_OUT_ID_* + 18 mmVGT_STRMOUT_BUFFER_EN + */ + osLib_returnFromFunction(hCPU, 0); +} + +struct GX2ComputeShader_t +{ + /* +0x00 */ uint32be regs[12]; + /* +0x30 */ uint32be programSize; + /* +0x34 */ uint32be programPtr; + /* +0x38 */ uint32 ukn38; + /* +0x3C */ uint32 ukn3C; + /* +0x40 */ uint32 ukn40[8]; + /* +0x60 */ uint32be workgroupSizeX; + /* +0x64 */ uint32be workgroupSizeY; + /* +0x68 */ uint32be workgroupSizeZ; + /* +0x6C */ uint32be workgroupSizeSpecial; + /* +0x70 */ uint32be ukn70; + /* +0x74 */ GX2RBuffer rBuffer; +}; + +static_assert(offsetof(GX2ComputeShader_t, programSize) == 0x30); +static_assert(offsetof(GX2ComputeShader_t, workgroupSizeX) == 0x60); +static_assert(offsetof(GX2ComputeShader_t, rBuffer) == 0x74); + +void gx2Export_GX2SetComputeShader(PPCInterpreter_t* hCPU) +{ + ppcDefineParamTypePtr(computeShader, GX2ComputeShader_t, 0); + gx2Log_printf("GX2SetComputeShader(0x%08x)", hCPU->gpr[3]); + + MPTR shaderPtr; + uint32 shaderSize; + if (computeShader->programPtr) + { + shaderPtr = computeShader->programPtr; + shaderSize = computeShader->programSize; + } + else + { + shaderPtr = computeShader->rBuffer.GetVirtualAddr(); + shaderSize = computeShader->rBuffer.GetSize(); + } + + GX2ReserveCmdSpace(0x11); + + gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 6), + mmSQ_PGM_START_ES-0xA000, + memory_virtualToPhysical(shaderPtr) >> 8, + shaderSize >> 3, + 0x100000, + 0x100000, + computeShader->regs[0]); + + // todo: Other registers + + osLib_returnFromFunction(hCPU, 0); +} + +void _GX2SubmitUniformReg(uint32 aluRegisterOffset, MPTR virtualAddress, uint32 count) +{ + uint32* dataWords = (uint32*)memory_getPointerFromVirtualOffset(virtualAddress); + GX2ReserveCmdSpace(2 + (count / 0xFF) * 2 + count); + // write PM4 command(s) + uint32 currentRegisterOffset = aluRegisterOffset; + while (count > 0) + { + uint32 subCount = std::min(count, 0xFFu); // a single command can write at most 0xFF values + gx2WriteGather_submit(pm4HeaderType3(IT_SET_ALU_CONST, 1 + subCount), + currentRegisterOffset); + gx2WriteGather_submitU32AsLEArray(dataWords, subCount); + + dataWords += subCount; + count -= subCount; + currentRegisterOffset += subCount; + } +} + +void gx2Export_GX2SetVertexUniformReg(PPCInterpreter_t* hCPU) +{ + gx2Log_printf("GX2SetVertexUniformReg(0x%08x,0x%x,0x%08x)", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); + _GX2SubmitUniformReg(hCPU->gpr[3] + 0x400, hCPU->gpr[5], hCPU->gpr[4]); + cemu_assert_debug((hCPU->gpr[3] + hCPU->gpr[4]) <= 0x400); + osLib_returnFromFunction(hCPU, 0); +} + +void gx2Export_GX2SetPixelUniformReg(PPCInterpreter_t* hCPU) +{ + gx2Log_printf("GX2SetPixelUniformReg(0x%08x,0x%x,0x%08x)", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); + _GX2SubmitUniformReg(hCPU->gpr[3], hCPU->gpr[5], hCPU->gpr[4]); + cemu_assert_debug((hCPU->gpr[3] + hCPU->gpr[4]) <= 0x400); + osLib_returnFromFunction(hCPU, 0); +} + +void _GX2SubmitUniformBlock(uint32 registerBase, uint32 index, MPTR virtualAddress, uint32 size) +{ + GX2ReserveCmdSpace(9); + gx2WriteGather_submit(pm4HeaderType3(IT_SET_RESOURCE, 8), + registerBase + index * 7, + memory_virtualToPhysical(virtualAddress), + size - 1, + 0, + 1, + 0, // ukn + 0, // ukn + 0xC0000000); +} + +void gx2Export_GX2SetVertexUniformBlock(PPCInterpreter_t* hCPU) +{ + gx2Log_printf("GX2SetVertexUniformBlock(0x%08x,0x%x,0x%08x)", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); + _GX2SubmitUniformBlock(mmSQ_VTX_UNIFORM_BLOCK_START - mmSQ_TEX_RESOURCE_WORD0, hCPU->gpr[3], hCPU->gpr[5], hCPU->gpr[4]); + osLib_returnFromFunction(hCPU, 0); +} + +void gx2Export_GX2SetPixelUniformBlock(PPCInterpreter_t* hCPU) +{ + gx2Log_printf("GX2SetPixelUniformBlock(0x%08x,0x%x,0x%08x)", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); + _GX2SubmitUniformBlock(mmSQ_PS_UNIFORM_BLOCK_START - mmSQ_TEX_RESOURCE_WORD0, hCPU->gpr[3], hCPU->gpr[5], hCPU->gpr[4]); + osLib_returnFromFunction(hCPU, 0); +} + +void gx2Export_GX2SetGeometryUniformBlock(PPCInterpreter_t* hCPU) +{ + gx2Log_printf("GX2SetGeometryUniformBlock(0x%08x,0x%x,0x%08x)", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); + _GX2SubmitUniformBlock(mmSQ_GS_UNIFORM_BLOCK_START - mmSQ_TEX_RESOURCE_WORD0, hCPU->gpr[3], hCPU->gpr[5], hCPU->gpr[4]); + osLib_returnFromFunction(hCPU, 0); +} + +void gx2Export_GX2RSetVertexUniformBlock(PPCInterpreter_t* hCPU) +{ + GX2ReserveCmdSpace(9); + + GX2RBuffer* bufferPtr = (GX2RBuffer*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); + uint32 index = hCPU->gpr[4]; + uint32 offset = hCPU->gpr[5]; + + _GX2SubmitUniformBlock(mmSQ_VTX_UNIFORM_BLOCK_START - mmSQ_TEX_RESOURCE_WORD0, index, bufferPtr->GetVirtualAddr() + offset, bufferPtr->GetSize() - offset); + + osLib_returnFromFunction(hCPU, 0); +} + +void gx2Export_GX2SetShaderModeEx(PPCInterpreter_t* hCPU) +{ + GX2ReserveCmdSpace(8+4); + uint32 mode = hCPU->gpr[3]; + + uint32 sqConfig = hCPU->gpr[3] == 0 ? 4 : 0; + if (mode == GX2_SHADER_MODE_COMPUTE_SHADER) + sqConfig |= 0xE4000000; // ES/GS/PS priority? + // todo - other sqConfig bits + + gx2WriteGather_submit((uint32)(pm4HeaderType3(IT_SET_CONFIG_REG, 7)), + (uint32)(mmSQ_CONFIG - 0x2000), + sqConfig, + 0, // ukn / todo + 0, // ukn / todo + 0, // ukn / todo + 0, // ukn / todo + 0 // ukn / todo + ); + + // if not GS, then update mmVGT_GS_MODE + if( mode != GX2_SHADER_MODE_GEOMETRY_SHADER ) + { + // update VGT_GS_MODE only if no geometry shader is used (else this register is already set by GX2SetGeometryShader) + gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 2)); + gx2WriteGather_submitU32AsBE(Latte::REGADDR::VGT_GS_MODE-0xA000); + if (mode == GX2_SHADER_MODE_COMPUTE_SHADER) + gx2WriteGather_submitU32AsBE(Latte::LATTE_VGT_GS_MODE().set_MODE(Latte::LATTE_VGT_GS_MODE::E_MODE::SCENARIO_G).set_COMPUTE_MODE(Latte::LATTE_VGT_GS_MODE::E_COMPUTE_MODE::ON).set_PARTIAL_THD_AT_EOI(true).getRawValueBE()); + else + gx2WriteGather_submitU32AsBE(_swapEndianU32(0)); + } + + osLib_returnFromFunction(hCPU, 0); +} + +void gx2Export_GX2CalcGeometryShaderInputRingBufferSize(PPCInterpreter_t* hCPU) +{ + uint32 size = (hCPU->gpr[3]*4) * 0x1000; + osLib_returnFromFunction(hCPU, size); +} + +void gx2Export_GX2CalcGeometryShaderOutputRingBufferSize(PPCInterpreter_t* hCPU) +{ + uint32 size = (hCPU->gpr[3]*4) * 0x1000; + osLib_returnFromFunction(hCPU, size); +} diff --git a/src/Cafe/OS/libs/h264_avc/H264Dec.cpp b/src/Cafe/OS/libs/h264_avc/H264Dec.cpp new file mode 100644 index 00000000..d15f6683 --- /dev/null +++ b/src/Cafe/OS/libs/h264_avc/H264Dec.cpp @@ -0,0 +1,1123 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/HW/Espresso/PPCCallback.h" +#include "Cafe/OS/libs/h264_avc/parser/H264Parser.h" +#include "util/highresolutiontimer/HighResolutionTimer.h" +#include "Cafe/CafeSystem.h" + +#include "h264dec.h" + +extern "C" +{ +#include "../dependencies/ih264d/common/ih264_typedefs.h" +#include "../dependencies/ih264d/decoder/ih264d.h" +}; + +enum class H264DEC_STATUS : uint32 +{ + SUCCESS = 0x0, + BAD_STREAM = 0x1000000, + INVALID_PARAM = 0x1010000, +}; + +namespace H264 +{ + bool H264_IsBotW() + { + // Cemuhook has a hack where it always returns a small size for H264DECMemoryRequirement (256 bytes) + // it also outputs images pre-cropped instead of giving the game raw uncropped images + // both of these are required to allow Breath of the Wild to playback the higher res (1080p) videos from the Switch version + // we mirror these hacks for user convenience and because there are no downsides + uint64 currentTitleId = CafeSystem::GetForegroundTitleId(); + if (currentTitleId == 0x00050000101c9500 || currentTitleId == 0x00050000101c9400 || currentTitleId == 0x0005000e101c9300) + return true; + return false; + } + + uint32 H264DECMemoryRequirement(uint32 codecProfile, uint32 codecLevel, uint32 width, uint32 height, uint32be* sizeRequirementOut) + { + if (H264_IsBotW()) + { + *sizeRequirementOut = 256; + return 0; + } + + // note: On console this seems to check if maxWidth or maxHeight < 64 but Pikmin 3 passes 32x32 and crashes if this function fails ? + if (width < 0x20 || height < 0x20 || width > 2800 || height > 1408 || sizeRequirementOut == MPTR_NULL || codecLevel >= 52 || (codecProfile != 0x42 && codecProfile != 0x4D && codecProfile != 0x64)) + return 0x1010000; + + uint32 workbufferSize = 0; + if (codecLevel < 0xB) + { + workbufferSize = 0x18C << 10; + } + else if (codecLevel == 0xB) + { + workbufferSize = 0x384 << 10; + } + else if (codecLevel >= 0xC && codecLevel <= 0x14) + { + workbufferSize = 0x948 << 10; + } + else if (codecLevel == 0x15) + { + workbufferSize = 0x1290 << 10; + } + else if (codecLevel >= 0x16 && codecLevel <= 0x1E) + { + workbufferSize = 0x1FA4 << 10; + } + else if (codecLevel == 0x1F) + { + workbufferSize = 0x4650 << 10; + } + else if (codecLevel == 0x20) + { + workbufferSize = 0x1400000; + } + else if (codecLevel >= 0x21 && codecLevel <= 0x29) + { + workbufferSize = 0x8000 << 10; + } + else if (codecLevel == 0x2A) + { + workbufferSize = 0x2200000; + } + else if (codecLevel >= 0x2B && codecLevel <= 0x32) + { + workbufferSize = 0x1AF40 << 10; + } + else if (codecLevel >= 0x33) + { + workbufferSize = 0x2D000 << 10; + } + workbufferSize += 0x447; + *sizeRequirementOut = workbufferSize; + return 0; + } + + uint32 H264DECCheckMemSegmentation(MPTR memory, uint32 size) + { + // return 0 for valid, 1 for invalid + // currently we allow any range + return 0; + } + + H264DEC_STATUS H264DECFindDecstartpoint(uint8* ptr, uint32 length, uint32be* offsetOut) + { + if (!ptr || length < 4 || !offsetOut) + return H264DEC_STATUS::INVALID_PARAM; + for (uint32 i = 0; i < length - 4; ++i) + { + uint8 b = ptr[i]; + if (b != 0) + continue; + + b = ptr[i + 1]; + if (b != 0) + continue; + + b = ptr[i + 2]; + if (b != 1) + continue; + + b = ptr[i + 3]; + b &= 0x9F; + if (b != 7) // check for NAL type SPS + continue; + + if (i > 0) + *offsetOut = i - 1; + else + *offsetOut = 0; + + return H264DEC_STATUS::SUCCESS; + } + return H264DEC_STATUS::BAD_STREAM; + } + + H264DEC_STATUS H264DECFindIdrpoint(uint8* ptr, uint32 length, uint32be* offsetOut) + { + if (!ptr || length < 4 || !offsetOut) + return H264DEC_STATUS::INVALID_PARAM; + + for (uint32 i = 0; i < length - 4; ++i) + { + uint8 b = ptr[i]; + if (b != 0) + continue; + + b = ptr[i + 1]; + if (b != 0) + continue; + + b = ptr[i + 2]; + if (b != 1) + continue; + + b = ptr[i + 3]; + b &= 0x9F; + if (b != 5 && b != 7 && b != 8) // check for NAL type IDR slice, but also accept SPS or PPS slices + continue; + + if (i > 0) + *offsetOut = i - 1; + else + *offsetOut = 0; + + return H264DEC_STATUS::SUCCESS; + } + return H264DEC_STATUS::BAD_STREAM; + } + + struct H264Context + { + struct + { + MEMPTR<void> ptr{ nullptr }; + uint32be length{ 0 }; + float64be timestamp; + }BitStream; + struct + { + MEMPTR<void> outputFunc{ nullptr }; + uint8be outputPerFrame{ 0 }; // whats the default? + MEMPTR<void> userMemoryParam{ nullptr }; + }Param; + // misc + uint32be sessionHandle; + }; + + class H264AVCDecoder + { + static void* ivd_aligned_malloc(void* ctxt, WORD32 alignment, WORD32 size) + { +#ifdef _WIN32 + return _aligned_malloc(size, alignment); +#else + return aligned_alloc(alignment, size); +#endif + } + + static void ivd_aligned_free(void* ctxt, void* buf) + { +#ifdef _WIN32 + _aligned_free(buf); +#else + free(buf); +#endif + return; + } + + public: + struct DecodeResult + { + bool frameReady{ false }; + double timestamp; + void* imageOutput; + ivd_video_decode_op_t decodeOutput; + }; + + void Init(bool isBufferedMode) + { + ih264d_create_ip_t s_create_ip{ 0 }; + ih264d_create_op_t s_create_op{ 0 }; + + s_create_ip.s_ivd_create_ip_t.u4_size = sizeof(ih264d_create_ip_t); + s_create_ip.s_ivd_create_ip_t.e_cmd = IVD_CMD_CREATE; + s_create_ip.s_ivd_create_ip_t.u4_share_disp_buf = 1; // shared display buffer mode -> We give the decoder a list of buffers that it will use (?) + + s_create_op.s_ivd_create_op_t.u4_size = sizeof(ih264d_create_op_t); + s_create_ip.s_ivd_create_ip_t.e_output_format = IV_YUV_420SP_UV; + s_create_ip.s_ivd_create_ip_t.pf_aligned_alloc = ivd_aligned_malloc; + s_create_ip.s_ivd_create_ip_t.pf_aligned_free = ivd_aligned_free; + s_create_ip.s_ivd_create_ip_t.pv_mem_ctxt = NULL; + + WORD32 status = ih264d_api_function(m_codecCtx, &s_create_ip, &s_create_op); + cemu_assert(!status); + + m_codecCtx = (iv_obj_t*)s_create_op.s_ivd_create_op_t.pv_handle; + m_codecCtx->pv_fxns = (void*)&ih264d_api_function; + m_codecCtx->u4_size = sizeof(iv_obj_t); + + m_isBufferedMode = isBufferedMode; + + UpdateParameters(false); + + m_bufferedResults.clear(); + m_numDecodedFrames = 0; + m_hasBufferSizeInfo = false; + m_timestampIndex = 0; + } + + void Destroy() + { + if (!m_codecCtx) + return; + ih264d_delete_ip_t s_delete_ip{ 0 }; + ih264d_delete_op_t s_delete_op{ 0 }; + s_delete_ip.s_ivd_delete_ip_t.u4_size = sizeof(ih264d_delete_ip_t); + s_delete_ip.s_ivd_delete_ip_t.e_cmd = IVD_CMD_DELETE; + s_delete_op.s_ivd_delete_op_t.u4_size = sizeof(ih264d_delete_op_t); + WORD32 status = ih264d_api_function(m_codecCtx, &s_delete_ip, &s_delete_op); + cemu_assert_debug(!status); + m_codecCtx = nullptr; + } + + static bool GetImageInfo(uint8* stream, uint32 length, uint32& imageWidth, uint32& imageHeight) + { + // create temporary decoder + ih264d_create_ip_t s_create_ip{ 0 }; + ih264d_create_op_t s_create_op{ 0 }; + s_create_ip.s_ivd_create_ip_t.u4_size = sizeof(ih264d_create_ip_t); + s_create_ip.s_ivd_create_ip_t.e_cmd = IVD_CMD_CREATE; + s_create_ip.s_ivd_create_ip_t.u4_share_disp_buf = 0; + s_create_op.s_ivd_create_op_t.u4_size = sizeof(ih264d_create_op_t); + s_create_ip.s_ivd_create_ip_t.e_output_format = IV_YUV_420SP_UV; + s_create_ip.s_ivd_create_ip_t.pf_aligned_alloc = ivd_aligned_malloc; + s_create_ip.s_ivd_create_ip_t.pf_aligned_free = ivd_aligned_free; + s_create_ip.s_ivd_create_ip_t.pv_mem_ctxt = NULL; + iv_obj_t* ctx = nullptr; + WORD32 status = ih264d_api_function(ctx, &s_create_ip, &s_create_op); + cemu_assert_debug(!status); + if (status != IV_SUCCESS) + return false; + ctx = (iv_obj_t*)s_create_op.s_ivd_create_op_t.pv_handle; + ctx->pv_fxns = (void*)&ih264d_api_function; + ctx->u4_size = sizeof(iv_obj_t); + // set header-only mode + ih264d_ctl_set_config_ip_t s_h264d_ctl_ip{ 0 }; + ih264d_ctl_set_config_op_t s_h264d_ctl_op{ 0 }; + ivd_ctl_set_config_ip_t* ps_ctl_ip = &s_h264d_ctl_ip.s_ivd_ctl_set_config_ip_t; + ivd_ctl_set_config_op_t* ps_ctl_op = &s_h264d_ctl_op.s_ivd_ctl_set_config_op_t; + ps_ctl_ip->u4_disp_wd = 0; + ps_ctl_ip->e_frm_skip_mode = IVD_SKIP_NONE; + ps_ctl_ip->e_frm_out_mode = IVD_DISPLAY_FRAME_OUT; + ps_ctl_ip->e_vid_dec_mode = IVD_DECODE_HEADER; + ps_ctl_ip->e_cmd = IVD_CMD_VIDEO_CTL; + ps_ctl_ip->e_sub_cmd = IVD_CMD_CTL_SETPARAMS; + ps_ctl_ip->u4_size = sizeof(ih264d_ctl_set_config_ip_t); + ps_ctl_op->u4_size = sizeof(ih264d_ctl_set_config_op_t); + status = ih264d_api_function(ctx, &s_h264d_ctl_ip, &s_h264d_ctl_op); + cemu_assert(!status); + // decode stream + ivd_video_decode_ip_t s_dec_ip{ 0 }; + ivd_video_decode_op_t s_dec_op{ 0 }; + s_dec_ip.u4_size = sizeof(ivd_video_decode_ip_t); + s_dec_op.u4_size = sizeof(ivd_video_decode_op_t); + s_dec_ip.e_cmd = IVD_CMD_VIDEO_DECODE; + s_dec_ip.pv_stream_buffer = stream; + s_dec_ip.u4_num_Bytes = length; + s_dec_ip.s_out_buffer.u4_num_bufs = 0; + + s_dec_op.u4_raw_wd = 0; + s_dec_op.u4_raw_ht = 0; + + status = ih264d_api_function(ctx, &s_dec_ip, &s_dec_op); + //cemu_assert(status == 0); -> This errors when not both the headers are present, but it will still set the parameters we need + bool isValid = false; + if (true)//status == 0) + { + imageWidth = s_dec_op.u4_raw_wd; + imageHeight = s_dec_op.u4_raw_ht; + cemu_assert_debug(imageWidth != 0 && imageHeight != 0); + isValid = true; + } + // destroy decoder + ih264d_delete_ip_t s_delete_ip{ 0 }; + ih264d_delete_op_t s_delete_op{ 0 }; + s_delete_ip.s_ivd_delete_ip_t.u4_size = sizeof(ih264d_delete_ip_t); + s_delete_ip.s_ivd_delete_ip_t.e_cmd = IVD_CMD_DELETE; + s_delete_op.s_ivd_delete_op_t.u4_size = sizeof(ih264d_delete_op_t); + status = ih264d_api_function(ctx, &s_delete_ip, &s_delete_op); + cemu_assert_debug(!status); + return isValid; + } + + void Decode(void* data, uint32 length, double timestamp, void* imageOutput, DecodeResult& decodeResult) + { + if (!m_hasBufferSizeInfo) + { + uint32 numByteConsumed = 0; + if (!DetermineBufferSizes(data, length, numByteConsumed)) + { + cemuLog_force("H264: Unable to determine picture size. Ignoring decode input"); + decodeResult.frameReady = false; + return; + } + length -= numByteConsumed; + data = (uint8*)data + numByteConsumed; + m_hasBufferSizeInfo = true; + } + + ivd_video_decode_ip_t s_dec_ip{ 0 }; + ivd_video_decode_op_t s_dec_op{ 0 }; + s_dec_ip.u4_size = sizeof(ivd_video_decode_ip_t); + s_dec_op.u4_size = sizeof(ivd_video_decode_op_t); + + s_dec_ip.e_cmd = IVD_CMD_VIDEO_DECODE; + + // remember timestamp and associated output buffer + m_timestamps[m_timestampIndex] = timestamp; + m_imageBuffers[m_timestampIndex] = imageOutput; + s_dec_ip.u4_ts = m_timestampIndex; + m_timestampIndex = (m_timestampIndex + 1) % 64; + + s_dec_ip.pv_stream_buffer = (uint8*)data; + s_dec_ip.u4_num_Bytes = length; + + s_dec_ip.s_out_buffer.u4_min_out_buf_size[0] = 0; + s_dec_ip.s_out_buffer.u4_min_out_buf_size[1] = 0; + s_dec_ip.s_out_buffer.u4_num_bufs = 0; + + BenchmarkTimer bt; + bt.Start(); + WORD32 status = ih264d_api_function(m_codecCtx, &s_dec_ip, &s_dec_op); + if (status != 0 && (s_dec_op.u4_error_code&0xFF) == IVD_RES_CHANGED) + { + // resolution change + ResetDecoder(); + m_hasBufferSizeInfo = false; + Decode(data, length, timestamp, imageOutput, decodeResult); + return; + } + else if (status != 0) + { + cemuLog_force("H264: Failed to decode frame (error 0x{:08x})", status); + decodeResult.frameReady = false; + return; + } + + bt.Stop(); + double decodeTime = bt.GetElapsedMilliseconds(); + + cemu_assert(s_dec_op.u4_frame_decoded_flag); + cemu_assert_debug(s_dec_op.u4_num_bytes_consumed == length); + + cemu_assert_debug(m_isBufferedMode || s_dec_op.u4_output_present); // if buffered mode is disabled, then every input should output a frame (except for partial slices?) + + if (s_dec_op.u4_output_present) + { + cemu_assert(s_dec_op.e_output_format == IV_YUV_420SP_UV); + if (H264_IsBotW()) + { + if (s_dec_op.s_disp_frm_buf.u4_y_wd == 1920 && s_dec_op.s_disp_frm_buf.u4_y_ht == 1088) + s_dec_op.s_disp_frm_buf.u4_y_ht = 1080; + } + DecodeResult tmpResult; + tmpResult.frameReady = s_dec_op.u4_output_present != 0; + tmpResult.timestamp = m_timestamps[s_dec_op.u4_ts]; + tmpResult.imageOutput = m_imageBuffers[s_dec_op.u4_ts]; + tmpResult.decodeOutput = s_dec_op; + AddBufferedResult(tmpResult); + // transfer image to PPC output buffer and also correct stride + bt.Start(); + CopyImageToResultBuffer((uint8*)s_dec_op.s_disp_frm_buf.pv_y_buf, (uint8*)s_dec_op.s_disp_frm_buf.pv_u_buf, (uint8*)m_imageBuffers[s_dec_op.u4_ts], s_dec_op); + bt.Stop(); + double copyTime = bt.GetElapsedMilliseconds(); + // release buffer + sint32 bufferId = -1; + for (size_t i = 0; i < m_displayBuf.size(); i++) + { + if (s_dec_op.s_disp_frm_buf.pv_y_buf >= m_displayBuf[i].data() && s_dec_op.s_disp_frm_buf.pv_y_buf < (m_displayBuf[i].data() + m_displayBuf[i].size())) + { + bufferId = (sint32)i; + break; + } + } + cemu_assert_debug(bufferId == s_dec_op.u4_disp_buf_id); + cemu_assert(bufferId >= 0); + ivd_rel_display_frame_ip_t s_video_rel_disp_ip{ 0 }; + ivd_rel_display_frame_op_t s_video_rel_disp_op{ 0 }; + s_video_rel_disp_ip.e_cmd = IVD_CMD_REL_DISPLAY_FRAME; + s_video_rel_disp_ip.u4_size = sizeof(ivd_rel_display_frame_ip_t); + s_video_rel_disp_op.u4_size = sizeof(ivd_rel_display_frame_op_t); + s_video_rel_disp_ip.u4_disp_buf_id = bufferId; + status = ih264d_api_function(m_codecCtx, &s_video_rel_disp_ip, &s_video_rel_disp_op); + cemu_assert(!status); + + cemuLog_log(LogType::H264, "H264Bench | DecodeTime {}ms CopyTime {}ms", decodeTime, copyTime); + } + else + { + cemuLog_log(LogType::H264, "H264Bench | DecodeTime{}ms", decodeTime); + } + + if (s_dec_op.u4_frame_decoded_flag) + m_numDecodedFrames++; + + if (m_isBufferedMode) + { + // in buffered mode, always buffer 5 frames regardless of actual reordering and decoder latency + if (m_numDecodedFrames > 5) + GetCurrentBufferedResult(decodeResult); + } + else if(m_numDecodedFrames > 0) + GetCurrentBufferedResult(decodeResult); + + // get VUI + //ih264d_ctl_get_vui_params_ip_t s_ctl_get_vui_params_ip; + //ih264d_ctl_get_vui_params_op_t s_ctl_get_vui_params_op; + + //s_ctl_get_vui_params_ip.e_cmd = IVD_CMD_VIDEO_CTL; + //s_ctl_get_vui_params_ip.e_sub_cmd = (IVD_CONTROL_API_COMMAND_TYPE_T)IH264D_CMD_CTL_GET_VUI_PARAMS; + //s_ctl_get_vui_params_ip.u4_size = sizeof(ih264d_ctl_get_vui_params_ip_t); + //s_ctl_get_vui_params_op.u4_size = sizeof(ih264d_ctl_get_vui_params_op_t); + + //status = ih264d_api_function(mCodecCtx, &s_ctl_get_vui_params_ip, &s_ctl_get_vui_params_op); + //cemu_assert(status == 0); + } + + std::vector<DecodeResult> Flush() + { + std::vector<DecodeResult> results; + // set flush mode + ivd_ctl_flush_ip_t s_video_flush_ip{ 0 }; + ivd_ctl_flush_op_t s_video_flush_op{ 0 }; + s_video_flush_ip.e_cmd = IVD_CMD_VIDEO_CTL; + s_video_flush_ip.e_sub_cmd = IVD_CMD_CTL_FLUSH; + s_video_flush_ip.u4_size = sizeof(ivd_ctl_flush_ip_t); + s_video_flush_op.u4_size = sizeof(ivd_ctl_flush_op_t); + WORD32 status = ih264d_api_function(m_codecCtx, &s_video_flush_ip, &s_video_flush_op); + if (status != 0) + cemuLog_log(LogType::Force, "H264Dec: Unexpected error during flush ({})", status); + // get all frames from the codec + while (true) + { + ivd_video_decode_ip_t s_dec_ip{ 0 }; + ivd_video_decode_op_t s_dec_op{ 0 }; + s_dec_ip.u4_size = sizeof(ivd_video_decode_ip_t); + s_dec_op.u4_size = sizeof(ivd_video_decode_op_t); + s_dec_ip.e_cmd = IVD_CMD_VIDEO_DECODE; + s_dec_ip.pv_stream_buffer = NULL; + s_dec_ip.u4_num_Bytes = 0; + s_dec_ip.s_out_buffer.u4_min_out_buf_size[0] = 0; + s_dec_ip.s_out_buffer.u4_min_out_buf_size[1] = 0; + s_dec_ip.s_out_buffer.u4_num_bufs = 0; + status = ih264d_api_function(m_codecCtx, &s_dec_ip, &s_dec_op); + if (status != 0) + break; + cemu_assert_debug(s_dec_op.u4_output_present != 0); // should never be zero? + if(s_dec_op.u4_output_present == 0) + continue; + if (H264_IsBotW()) + { + if (s_dec_op.s_disp_frm_buf.u4_y_wd == 1920 && s_dec_op.s_disp_frm_buf.u4_y_ht == 1088) + s_dec_op.s_disp_frm_buf.u4_y_ht = 1080; + } + DecodeResult tmpResult; + tmpResult.frameReady = s_dec_op.u4_output_present != 0; + tmpResult.timestamp = m_timestamps[s_dec_op.u4_ts]; + tmpResult.imageOutput = m_imageBuffers[s_dec_op.u4_ts]; + tmpResult.decodeOutput = s_dec_op; + AddBufferedResult(tmpResult); + CopyImageToResultBuffer((uint8*)s_dec_op.s_disp_frm_buf.pv_y_buf, (uint8*)s_dec_op.s_disp_frm_buf.pv_u_buf, (uint8*)m_imageBuffers[s_dec_op.u4_ts], s_dec_op); + } + results = std::move(m_bufferedResults); + return results; + } + + void CopyImageToResultBuffer(uint8* yIn, uint8* uvIn, uint8* bufOut, ivd_video_decode_op_t& decodeInfo) + { + uint32 imageWidth = decodeInfo.s_disp_frm_buf.u4_y_wd; + uint32 imageHeight = decodeInfo.s_disp_frm_buf.u4_y_ht; + + size_t inputStride = decodeInfo.s_disp_frm_buf.u4_y_strd; + size_t outputStride = (imageWidth + 0xFF) & ~0xFF; + + // copy Y + uint8* yOut = bufOut; + for (uint32 row = 0; row < imageHeight; row++) + { + memcpy(yOut, yIn, imageWidth); + yIn += inputStride; + yOut += outputStride; + } + + // copy UV + uint8* uvOut = bufOut + outputStride * imageHeight; + for (uint32 row = 0; row < imageHeight/2; row++) + { + memcpy(uvOut, uvIn, imageWidth); + uvIn += inputStride; + uvOut += outputStride; + } + } + + private: + + bool DetermineBufferSizes(void* data, uint32 length, uint32& numByteConsumed) + { + numByteConsumed = 0; + UpdateParameters(true); + + ivd_video_decode_ip_t s_dec_ip{ 0 }; + ivd_video_decode_op_t s_dec_op{ 0 }; + s_dec_ip.u4_size = sizeof(ivd_video_decode_ip_t); + s_dec_op.u4_size = sizeof(ivd_video_decode_op_t); + + s_dec_ip.e_cmd = IVD_CMD_VIDEO_DECODE; + s_dec_ip.pv_stream_buffer = (uint8*)data; + s_dec_ip.u4_num_Bytes = length; + s_dec_ip.s_out_buffer.u4_num_bufs = 0; + WORD32 status = ih264d_api_function(m_codecCtx, &s_dec_ip, &s_dec_op); + if (status != 0) + { + forceLog_printf("H264: Unable to determine buffer sizes for stream"); + return false; + } + numByteConsumed = s_dec_op.u4_num_bytes_consumed; + cemu_assert(status == 0); + if (s_dec_op.u4_pic_wd == 0 || s_dec_op.u4_pic_ht == 0) + return false; + UpdateParameters(false); + ReinitBuffers(); + return true; + } + + void ReinitBuffers() + { + ivd_ctl_getbufinfo_ip_t s_ctl_ip{ 0 }; + ivd_ctl_getbufinfo_op_t s_ctl_op{ 0 }; + WORD32 outlen = 0; + + s_ctl_ip.e_cmd = IVD_CMD_VIDEO_CTL; + s_ctl_ip.e_sub_cmd = IVD_CMD_CTL_GETBUFINFO; + s_ctl_ip.u4_size = sizeof(ivd_ctl_getbufinfo_ip_t); + s_ctl_op.u4_size = sizeof(ivd_ctl_getbufinfo_op_t); + + WORD32 status = ih264d_api_function(m_codecCtx, &s_ctl_ip, &s_ctl_op); + cemu_assert(!status); + + // allocate + for (uint32 i = 0; i < s_ctl_op.u4_num_disp_bufs; i++) + { + m_displayBuf.emplace_back().resize(s_ctl_op.u4_min_out_buf_size[0] + s_ctl_op.u4_min_out_buf_size[1]); + } + // set + ivd_set_display_frame_ip_t s_set_display_frame_ip{ 0 }; // make sure to zero-initialize this. The codec seems to check the first 3 pointers/sizes per frame, regardless of the value of u4_num_bufs + ivd_set_display_frame_op_t s_set_display_frame_op{ 0 }; + + s_set_display_frame_ip.e_cmd = IVD_CMD_SET_DISPLAY_FRAME; + s_set_display_frame_ip.u4_size = sizeof(ivd_set_display_frame_ip_t); + s_set_display_frame_op.u4_size = sizeof(ivd_set_display_frame_op_t); + + cemu_assert_debug(s_ctl_op.u4_min_num_out_bufs == 2); + cemu_assert_debug(s_ctl_op.u4_min_out_buf_size[0] != 0 && s_ctl_op.u4_min_out_buf_size[1] != 0); + + s_set_display_frame_ip.num_disp_bufs = s_ctl_op.u4_num_disp_bufs; + + for (uint32 i = 0; i < s_ctl_op.u4_num_disp_bufs; i++) + { + s_set_display_frame_ip.s_disp_buffer[i].u4_num_bufs = 2; + s_set_display_frame_ip.s_disp_buffer[i].u4_min_out_buf_size[0] = s_ctl_op.u4_min_out_buf_size[0]; + s_set_display_frame_ip.s_disp_buffer[i].u4_min_out_buf_size[1] = s_ctl_op.u4_min_out_buf_size[1]; + s_set_display_frame_ip.s_disp_buffer[i].pu1_bufs[0] = m_displayBuf[i].data() + 0; + s_set_display_frame_ip.s_disp_buffer[i].pu1_bufs[1] = m_displayBuf[i].data() + s_ctl_op.u4_min_out_buf_size[0]; + } + + status = ih264d_api_function(m_codecCtx, &s_set_display_frame_ip, &s_set_display_frame_op); + cemu_assert(!status); + + + // mark all as released (available) + for (uint32 i = 0; i < s_ctl_op.u4_num_disp_bufs; i++) + { + ivd_rel_display_frame_ip_t s_video_rel_disp_ip{ 0 }; + ivd_rel_display_frame_op_t s_video_rel_disp_op{ 0 }; + + s_video_rel_disp_ip.e_cmd = IVD_CMD_REL_DISPLAY_FRAME; + s_video_rel_disp_ip.u4_size = sizeof(ivd_rel_display_frame_ip_t); + s_video_rel_disp_op.u4_size = sizeof(ivd_rel_display_frame_op_t); + s_video_rel_disp_ip.u4_disp_buf_id = i; + + status = ih264d_api_function(m_codecCtx, &s_video_rel_disp_ip, &s_video_rel_disp_op); + cemu_assert(!status); + } + } + + void ResetDecoder() + { + ivd_ctl_reset_ip_t s_ctl_ip; + ivd_ctl_reset_op_t s_ctl_op; + + s_ctl_ip.e_cmd = IVD_CMD_VIDEO_CTL; + s_ctl_ip.e_sub_cmd = IVD_CMD_CTL_RESET; + s_ctl_ip.u4_size = sizeof(ivd_ctl_reset_ip_t); + s_ctl_op.u4_size = sizeof(ivd_ctl_reset_op_t); + + WORD32 status = ih264d_api_function(m_codecCtx, (void*)&s_ctl_ip, (void*)&s_ctl_op); + cemu_assert_debug(status == 0); + } + + void UpdateParameters(bool headerDecodeOnly) + { + ih264d_ctl_set_config_ip_t s_h264d_ctl_ip{ 0 }; + ih264d_ctl_set_config_op_t s_h264d_ctl_op{ 0 }; + ivd_ctl_set_config_ip_t* ps_ctl_ip = &s_h264d_ctl_ip.s_ivd_ctl_set_config_ip_t; + ivd_ctl_set_config_op_t* ps_ctl_op = &s_h264d_ctl_op.s_ivd_ctl_set_config_op_t; + + ps_ctl_ip->u4_disp_wd = 0; + ps_ctl_ip->e_frm_skip_mode = IVD_SKIP_NONE; + ps_ctl_ip->e_frm_out_mode = m_isBufferedMode ? IVD_DISPLAY_FRAME_OUT : IVD_DECODE_FRAME_OUT; + ps_ctl_ip->e_vid_dec_mode = headerDecodeOnly ? IVD_DECODE_HEADER : IVD_DECODE_FRAME; + ps_ctl_ip->e_cmd = IVD_CMD_VIDEO_CTL; + ps_ctl_ip->e_sub_cmd = IVD_CMD_CTL_SETPARAMS; + ps_ctl_ip->u4_size = sizeof(ih264d_ctl_set_config_ip_t); + ps_ctl_op->u4_size = sizeof(ih264d_ctl_set_config_op_t); + + WORD32 status = ih264d_api_function(m_codecCtx, &s_h264d_ctl_ip, &s_h264d_ctl_op); + cemu_assert(status == 0); + } + + /* In non-flush mode we have a delay of (at least?) 5 frames */ + void AddBufferedResult(DecodeResult& decodeResult) + { + if (decodeResult.frameReady) + m_bufferedResults.emplace_back(decodeResult); + } + + void GetCurrentBufferedResult(DecodeResult& decodeResult) + { + cemu_assert(!m_bufferedResults.empty()); + if (m_bufferedResults.empty()) + { + decodeResult.frameReady = false; + return; + } + decodeResult = m_bufferedResults.front(); + m_bufferedResults.erase(m_bufferedResults.begin()); + } + + private: + iv_obj_t* m_codecCtx{nullptr}; + bool m_hasBufferSizeInfo{ false }; + bool m_isBufferedMode{ false }; + double m_timestamps[64]; + void* m_imageBuffers[64]; + uint32 m_timestampIndex{0}; + std::vector<DecodeResult> m_bufferedResults; + uint32 m_numDecodedFrames{0}; + std::vector<std::vector<uint8>> m_displayBuf; + }; + + H264DEC_STATUS H264DECGetImageSize(uint8* stream, uint32 length, uint32 offset, uint32be* outputWidth, uint32be* outputHeight) + { + cemu_assert(offset <= length); + + uint32 imageWidth, imageHeight; + + if (H264AVCDecoder::GetImageInfo(stream, length, imageWidth, imageHeight)) + { + if (H264_IsBotW()) + { + if (imageWidth == 1920 && imageHeight == 1088) + imageHeight = 1080; + } + *outputWidth = imageWidth; + *outputHeight = imageHeight; + } + else + { + *outputWidth = 0; + *outputHeight = 0; + return H264DEC_STATUS::BAD_STREAM; + } + + return H264DEC_STATUS::SUCCESS; + } + + uint32 H264DECInitParam(uint32 workMemorySize, void* workMemory) + { + H264Context* ctx = (H264Context*)workMemory; + *ctx = {}; + return 0; + } + + std::unordered_map<uint32, H264AVCDecoder*> sDecoderSessions; + std::mutex sDecoderSessionsMutex; + std::atomic_uint32_t sCurrentSessionHandle{ 1 }; + + static H264AVCDecoder* _CreateDecoderSession(uint32& handleOut) + { + std::unique_lock _lock(sDecoderSessionsMutex); + handleOut = sCurrentSessionHandle.fetch_add(1); + H264AVCDecoder* session = new H264AVCDecoder(); + sDecoderSessions.try_emplace(handleOut, session); + return session; + } + + static H264AVCDecoder* _AcquireDecoderSession(uint32 handle) + { + std::unique_lock _lock(sDecoderSessionsMutex); + auto it = sDecoderSessions.find(handle); + if (it == sDecoderSessions.end()) + return nullptr; + H264AVCDecoder* session = it->second; + if (sDecoderSessions.size() >= 5) + { + cemuLog_log(LogType::Force, "H264: Warning - more than 5 active sessions"); + cemu_assert_suspicious(); + } + return session; + } + + static void _ReleaseDecoderSession(H264AVCDecoder* session) + { + std::unique_lock _lock(sDecoderSessionsMutex); + + } + + static void _DestroyDecoderSession(uint32 handle) + { + std::unique_lock _lock(sDecoderSessionsMutex); + auto it = sDecoderSessions.find(handle); + if (it == sDecoderSessions.end()) + return; + H264AVCDecoder* session = it->second; + session->Destroy(); + delete session; + sDecoderSessions.erase(it); + } + + uint32 H264DECOpen(void* workMemory) + { + H264Context* ctx = (H264Context*)workMemory; + uint32 sessionHandle; + _CreateDecoderSession(sessionHandle); + ctx->sessionHandle = sessionHandle; + return 0; + } + + uint32 H264DECClose(void* workMemory) + { + if (workMemory) + { + H264Context* ctx = (H264Context*)workMemory; + _DestroyDecoderSession(ctx->sessionHandle); + } + return 0; + } + + uint32 H264DECBegin(void* workMemory) + { + H264Context* ctx = (H264Context*)workMemory; + H264AVCDecoder* session = _AcquireDecoderSession(ctx->sessionHandle); + if (!session) + { + cemuLog_log(LogType::Force, "H264DECBegin(): Invalid session"); + return 0; + } + session->Init(ctx->Param.outputPerFrame == 0); + _ReleaseDecoderSession(session); + return 0; + } + + void H264DoFrameOutputCallback(H264Context* ctx, H264AVCDecoder::DecodeResult& decodeResult); + + void _async_H264DECEnd(coreinit::OSEvent* executeDoneEvent, H264AVCDecoder* session, H264Context* ctx, std::vector<H264AVCDecoder::DecodeResult>* decodeResultsOut) + { + *decodeResultsOut = session->Flush(); + coreinit::OSSignalEvent(executeDoneEvent); + } + + H264DEC_STATUS H264DECEnd(void* workMemory) + { + H264Context* ctx = (H264Context*)workMemory; + H264AVCDecoder* session = _AcquireDecoderSession(ctx->sessionHandle); + if (!session) + { + cemuLog_log(LogType::Force, "H264DECEnd(): Invalid session"); + return H264DEC_STATUS::SUCCESS; + } + StackAllocator<coreinit::OSEvent> executeDoneEvent; + coreinit::OSInitEvent(executeDoneEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_MANUAL); + std::vector<H264AVCDecoder::DecodeResult> results; + auto asyncTask = std::async(std::launch::async, _async_H264DECEnd, executeDoneEvent.GetPointer(), session, ctx, &results); + coreinit::OSWaitEvent(executeDoneEvent); + _ReleaseDecoderSession(session); + for (auto& itr : results) + H264DoFrameOutputCallback(ctx, itr); + return H264DEC_STATUS::SUCCESS; + } + + H264DEC_STATUS H264DECSetParam_FPTR_OUTPUT(H264Context* ctx, void* outputFunc) + { + ctx->Param.outputFunc = outputFunc; + return H264DEC_STATUS::SUCCESS; + } + + H264DEC_STATUS H264DECSetParam_OUTPUT_PER_FRAME(H264Context* ctx, uint32 outputPerFrame) + { + ctx->Param.outputPerFrame = outputPerFrame != 0 ? 1 : 0; + return H264DEC_STATUS::SUCCESS; + } + + H264DEC_STATUS H264DECSetParam_USER_MEMORY(H264Context* ctx, MEMPTR<void*>* userMemoryParamPtr) + { + ctx->Param.userMemoryParam = *userMemoryParamPtr; + return H264DEC_STATUS::SUCCESS; + } + + H264DEC_STATUS H264DECSetParam(H264Context* ctx, uint32 paramId, void* paramValue) + { + const uint32 PARAMID_FPTR_OUTPUT = 0x1; + const uint32 PARAMID_OUTPUT_PER_FRAME = 0x20000002; + const uint32 PARAMID_USER_MEMORY = 0x70000001; + const uint32 PARAMID_UKN = 0x20000030; + + if (paramId == PARAMID_FPTR_OUTPUT) + { + ctx->Param.outputFunc = paramValue; + } + else if (paramId == PARAMID_USER_MEMORY) + { + ctx->Param.userMemoryParam = paramValue; + } + else if (paramId == PARAMID_OUTPUT_PER_FRAME) + { + ctx->Param.outputPerFrame = *(uint8be*)paramValue != 0; + } + else if (paramId == PARAMID_UKN) + { + // unknown purpose, seen in MK8. paramValue points to a bool + } + else + { + cemuLog_force("h264Export_H264DECSetParam(): Unsupported parameterId 0x{:08x}\n", paramId); + cemu_assert_unimplemented(); + } + return H264DEC_STATUS::SUCCESS; + } + + uint32 H264DECSetBitstream(void* workMemory, void* ptr, uint32 length, double timestamp) + { + H264Context* ctx = (H264Context*)workMemory; + ctx->BitStream.ptr = ptr; + ctx->BitStream.length = length; + ctx->BitStream.timestamp = timestamp; + return 0; + } + + + struct H264DECFrameOutput + { + /* +0x00 */ uint32be result; + /* +0x04 */ uint32be padding04; + /* +0x08 */ betype<double> timestamp; + /* +0x10 */ uint32be frameWidth; + /* +0x14 */ uint32be frameHeight; + /* +0x18 */ uint32be bytesPerRow; + /* +0x1C */ uint32be cropEnable; + /* +0x20 */ uint32be cropTop; + /* +0x24 */ uint32be cropBottom; + /* +0x28 */ uint32be cropLeft; + /* +0x2C */ uint32be cropRight; + + /* +0x30 */ uint32be ukn30; + /* +0x34 */ uint32be ukn34; + /* +0x38 */ uint32be ukn38; + /* +0x3C */ uint32be ukn3C; + /* +0x40 */ uint32be ukn40; + + /* +0x44 */ MEMPTR<uint8> imagePtr; + + /* +0x48 */ uint32 vuiEnable; + /* +0x4C */ MPTR vuiPtr; + /* +0x50 */ sint32 unused[10]; + }; + + struct H264OutputCBStruct + { + uint32be frameCount; + MEMPTR<MEMPTR<H264DECFrameOutput>> resultArray; + uint32be userParam; + }; + + static_assert(sizeof(H264OutputCBStruct) == 12); + + void H264DoFrameOutputCallback(H264Context* ctx, H264AVCDecoder::DecodeResult& decodeResult) + { + sint32 outputFrameCount = 1; + + cemu_assert(outputFrameCount < 8); + + StackAllocator<MEMPTR<void>, 8> stack_resultPtrArray; + StackAllocator<H264DECFrameOutput, 8> stack_decodedFrameResult; + + for (sint32 i = 0; i < outputFrameCount; i++) + stack_resultPtrArray[i] = stack_decodedFrameResult + i; + + H264DECFrameOutput* frameOutput = stack_decodedFrameResult + 0; + memset(frameOutput, 0x00, sizeof(H264DECFrameOutput)); + frameOutput->imagePtr = (uint8*)decodeResult.imageOutput; + frameOutput->result = 100; + frameOutput->timestamp = decodeResult.timestamp; + frameOutput->frameWidth = decodeResult.decodeOutput.u4_pic_wd; + frameOutput->frameHeight = decodeResult.decodeOutput.u4_pic_ht; + frameOutput->bytesPerRow = (decodeResult.decodeOutput.u4_pic_wd + 0xFF) & ~0xFF; + frameOutput->cropEnable = decodeResult.decodeOutput.u1_frame_cropping_flag; + frameOutput->cropTop = decodeResult.decodeOutput.u1_frame_cropping_rect_top_ofst; + frameOutput->cropBottom = decodeResult.decodeOutput.u1_frame_cropping_rect_bottom_ofst; + frameOutput->cropLeft = decodeResult.decodeOutput.u1_frame_cropping_rect_left_ofst; + frameOutput->cropRight = decodeResult.decodeOutput.u1_frame_cropping_rect_right_ofst; + + StackAllocator<H264OutputCBStruct> stack_fptrOutputData; + stack_fptrOutputData->frameCount = outputFrameCount; + stack_fptrOutputData->resultArray = (MEMPTR<H264DECFrameOutput>*)stack_resultPtrArray.GetPointer(); + stack_fptrOutputData->userParam = ctx->Param.userMemoryParam.GetBEValue(); + + // FPTR callback + if (!ctx->Param.outputFunc.IsNull()) + { + cemuLog_log(LogType::H264, "H264: Outputting frame via callback. Timestamp: {} Buffer 0x{:08x} UserParam 0x{:08x}", (double)decodeResult.timestamp, (uint32)frameOutput->imagePtr.GetMPTR(), ctx->Param.userMemoryParam.GetMPTR()); + PPCCoreCallback(ctx->Param.outputFunc.GetMPTR(), stack_fptrOutputData.GetMPTR()); + } + } + + void _async_H264DECExecute(coreinit::OSEvent* executeDoneEvent, H264AVCDecoder* session, H264Context* ctx, void* imageOutput, H264AVCDecoder::DecodeResult* decodeResult) + { + session->Decode(ctx->BitStream.ptr.GetPtr(), ctx->BitStream.length, ctx->BitStream.timestamp, imageOutput, *decodeResult); + coreinit::OSSignalEvent(executeDoneEvent); + } + + uint32 H264DECExecute(void* workMemory, void* imageOutput) + { + H264Context* ctx = (H264Context*)workMemory; + H264AVCDecoder* session = _AcquireDecoderSession(ctx->sessionHandle); + if (!session) + { + cemuLog_log(LogType::Force, "H264DECExecute(): Invalid session"); + return 0; + } + StackAllocator<coreinit::OSEvent> executeDoneEvent; + coreinit::OSInitEvent(executeDoneEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_MANUAL); + H264AVCDecoder::DecodeResult decodeResult; + auto asyncTask = std::async(std::launch::async, _async_H264DECExecute, executeDoneEvent.GetPointer(), session, ctx, imageOutput , &decodeResult); + coreinit::OSWaitEvent(executeDoneEvent); + _ReleaseDecoderSession(session); + if(decodeResult.frameReady) + H264DoFrameOutputCallback(ctx, decodeResult); + return 0x80 | 100; + } + + H264DEC_STATUS H264DECCheckDecunitLength(void* workMemory, uint8* data, uint32 maxLength, uint32 offset, uint32be* unitLengthOut) + { + // todo: our implementation for this currently doesn't parse slice headers and instead assumes that each frame is encoded into a single NAL slice. For all known cases this is sufficient but it doesn't match console behavior for cases where frames are split into multiple NALs + if (offset >= maxLength || maxLength < 4) + { + return H264DEC_STATUS::INVALID_PARAM; + } + + data += offset; + maxLength -= offset; + + NALInputBitstream nalStream(data, maxLength); + + if (nalStream.hasError()) + { + cemu_assert_debug(false); + return H264DEC_STATUS::BAD_STREAM; + } + + // search for start code + sint32 startCodeOffset = 0; + bool hasStartcode = false; + while (startCodeOffset < (sint32)(maxLength - 3)) + { + if (data[startCodeOffset + 0] == 0x00 && data[startCodeOffset + 1] == 0x00 && data[startCodeOffset + 2] == 0x01) + { + hasStartcode = true; + break; + } + startCodeOffset++; + } + if (hasStartcode == false) + return H264DEC_STATUS::BAD_STREAM; + data += startCodeOffset; + maxLength -= startCodeOffset; + + // parse NAL data + while (true) + { + if (nalStream.isEndOfStream()) + break; + RBSPInputBitstream rbspStream; + if (nalStream.getNextRBSP(rbspStream, true) == false) + break; + + sint32 streamSubOffset = (sint32)(rbspStream.getBasePtr() - data); + sint32 streamSubLength = rbspStream.getBaseLength(); + + // parse NAL header + uint8 nalHeaderByte = rbspStream.readU8(); + if ((nalHeaderByte & 0x80) != 0) + { + // MSB must be zero + cemu_assert_debug(false); + continue; + } + uint8 nal_ref_idc = (nalHeaderByte >> 5) & 0x3; + uint8 nal_unit_type = (nalHeaderByte >> 0) & 0x1f; + if (nal_unit_type == 14 || nal_unit_type == 20 || nal_unit_type == 21) + { + cemu_assert_debug(false); + continue; + } + switch (nal_unit_type) + { + case 1: + case 5: + { + *unitLengthOut = (sint32)((rbspStream.getBasePtr() + rbspStream.getBaseLength()) - data) + startCodeOffset; + return H264DEC_STATUS::SUCCESS; + } + case 6: + // SEI + break; + case 7: + // SPS + break; + case 8: + // PPS + break; + case 9: + // access unit delimiter + break; + case 10: + // end of sequence + break; + default: + forceLogDebug_printf("Unsupported NAL unit type %d", nal_unit_type); + cemu_assert_unimplemented(); + // todo + break; + } + } + return H264DEC_STATUS::BAD_STREAM; + } + + void Initialize() + { + cafeExportRegister("h264", H264DECCheckMemSegmentation, LogType::H264); + cafeExportRegister("h264", H264DECMemoryRequirement, LogType::H264); + cafeExportRegister("h264", H264DECFindDecstartpoint, LogType::H264); + cafeExportRegister("h264", H264DECFindIdrpoint, LogType::H264); + cafeExportRegister("h264", H264DECGetImageSize, LogType::H264); + + cafeExportRegister("h264", H264DECInitParam, LogType::H264); + cafeExportRegister("h264", H264DECOpen, LogType::H264); + cafeExportRegister("h264", H264DECClose, LogType::H264); + cafeExportRegister("h264", H264DECBegin, LogType::H264); + cafeExportRegister("h264", H264DECEnd, LogType::H264); + + cafeExportRegister("h264", H264DECSetParam_FPTR_OUTPUT, LogType::H264); + cafeExportRegister("h264", H264DECSetParam_OUTPUT_PER_FRAME, LogType::H264); + cafeExportRegister("h264", H264DECSetParam_USER_MEMORY, LogType::H264); + cafeExportRegister("h264", H264DECSetParam, LogType::H264); + + cafeExportRegister("h264", H264DECSetBitstream, LogType::H264); + cafeExportRegister("h264", H264DECExecute, LogType::H264); + + cafeExportRegister("h264", H264DECCheckDecunitLength, LogType::H264); + } +} diff --git a/src/Cafe/OS/libs/h264_avc/h264dec.h b/src/Cafe/OS/libs/h264_avc/h264dec.h new file mode 100644 index 00000000..c371cc92 --- /dev/null +++ b/src/Cafe/OS/libs/h264_avc/h264dec.h @@ -0,0 +1,4 @@ +namespace H264 +{ + void Initialize(); +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/h264_avc/parser/H264Parser.cpp b/src/Cafe/OS/libs/h264_avc/parser/H264Parser.cpp new file mode 100644 index 00000000..23e388ff --- /dev/null +++ b/src/Cafe/OS/libs/h264_avc/parser/H264Parser.cpp @@ -0,0 +1,1075 @@ +#include "Cafe/OS/libs/h264_avc/parser/H264Parser.h" + +void parse_hrd_parameters(h264ParserState_t* h264ParserState, RBSPInputBitstream& nalStream) +{ + uint32 cpb_cnt_minus1 = nalStream.readUV_E(); + uint8 bit_rate_scale = nalStream.readBits<4>(); + uint8 cpb_size_scale = nalStream.readBits<4>(); + for (uint8 schedSelIdx = 0; schedSelIdx <= cpb_cnt_minus1; schedSelIdx++) + { + uint32 bit_rate_value_minus1 = nalStream.readUV_E(); + uint32 cpb_size_value_minus1 = nalStream.readUV_E(); + uint8 cbr_flag = nalStream.readBit(); + } + uint8 initial_cpb_removal_delay_length_minus1 = nalStream.readBits<5>(); + uint8 cpb_removal_delay_length_minus1 = nalStream.readBits<5>(); + uint8 dpb_output_delay_length_minus1 = nalStream.readBits<5>(); + uint8 time_offset_length = nalStream.readBits<5>(); +} + +void parseNAL_scaling_list4x4(RBSPInputBitstream& rbspStream, h264_scaling_matrix4x4_t& scalingMatrix4x4) +{ + if (rbspStream.readBit() == 0) + { + scalingMatrix4x4.isPresent = 0; + return; + } + scalingMatrix4x4.isPresent = 1; + + cemu_assert_debug(false); // needs testing + sint32 lastScale = 8; + sint32 nextScale = 8; + for (sint32 j = 0; j < 4 * 4; j++) + { + if (nextScale != 0) + { + sint32 delta_scale = rbspStream.readSV_E(); + nextScale = (lastScale + delta_scale + 256) % 256; + scalingMatrix4x4.UseDefaultScalingMatrix = (j == 0 && nextScale == 0); + } + scalingMatrix4x4.list[j] = (nextScale == 0) ? lastScale : nextScale; + lastScale = scalingMatrix4x4.list[j]; + } +} + +void parseNAL_scaling_list8x8(RBSPInputBitstream& rbspStream, h264_scaling_matrix8x8_t& scalingMatrix8x8) +{ + if (rbspStream.readBit() == 0) + { + scalingMatrix8x8.isPresent = 0; + return; + } + scalingMatrix8x8.isPresent = 1; + + cemu_assert_debug(false); // needs testing + sint32 lastScale = 8; + sint32 nextScale = 8; + for (sint32 j = 0; j < 8 * 8; j++) + { + if (nextScale != 0) + { + sint32 delta_scale = rbspStream.readSV_E(); + nextScale = (lastScale + delta_scale + 256) % 256; + scalingMatrix8x8.UseDefaultScalingMatrix = (j == 0 && nextScale == 0); + } + scalingMatrix8x8.list[j] = (nextScale == 0) ? lastScale : nextScale; + lastScale = scalingMatrix8x8.list[j]; + } +} + +void parseNAL_pps_scaling_lists(h264ParserState_t* h264ParserState, RBSPInputBitstream& nalStream) +{ + parseNAL_scaling_list4x4(nalStream, h264ParserState->pps.ScalingList4x4[0]); + parseNAL_scaling_list4x4(nalStream, h264ParserState->pps.ScalingList4x4[1]); + parseNAL_scaling_list4x4(nalStream, h264ParserState->pps.ScalingList4x4[2]); + parseNAL_scaling_list4x4(nalStream, h264ParserState->pps.ScalingList4x4[3]); + parseNAL_scaling_list4x4(nalStream, h264ParserState->pps.ScalingList4x4[4]); + parseNAL_scaling_list4x4(nalStream, h264ParserState->pps.ScalingList4x4[5]); + + if (h264ParserState->pps.transform_8x8_mode_flag) + { + parseNAL_scaling_list8x8(nalStream, h264ParserState->pps.ScalingList8x8[0]); + parseNAL_scaling_list8x8(nalStream, h264ParserState->pps.ScalingList8x8[1]); + + if (h264ParserState->sps.chroma_format_idc == 3) + cemu_assert(false); // todo - more scaling lists to parse + } +} + +void parseNAL_sps_scaling_lists(h264ParserState_t* h264ParserState, RBSPInputBitstream& nalStream) +{ + parseNAL_scaling_list4x4(nalStream, h264ParserState->sps.ScalingList4x4[0]); + parseNAL_scaling_list4x4(nalStream, h264ParserState->sps.ScalingList4x4[1]); + parseNAL_scaling_list4x4(nalStream, h264ParserState->sps.ScalingList4x4[2]); + parseNAL_scaling_list4x4(nalStream, h264ParserState->sps.ScalingList4x4[3]); + parseNAL_scaling_list4x4(nalStream, h264ParserState->sps.ScalingList4x4[4]); + parseNAL_scaling_list4x4(nalStream, h264ParserState->sps.ScalingList4x4[5]); + + parseNAL_scaling_list8x8(nalStream, h264ParserState->sps.ScalingList8x8[0]); + parseNAL_scaling_list8x8(nalStream, h264ParserState->sps.ScalingList8x8[1]); + + if (h264ParserState->sps.chroma_format_idc == 3) + cemu_assert(false); // todo - more scaling lists to parse +} + +bool parseNAL_seq_parameter_set_rbsp(h264ParserState_t* h264ParserState, h264ParserOutput_t* output, RBSPInputBitstream& nalStream) +{ + memset(&h264ParserState->sps, 0, sizeof(h264State_seq_parameter_set_t)); + + h264ParserState->sps.profile_idc = nalStream.readU8(); // 0x64 = high profile + h264ParserState->sps.constraint = nalStream.readU8(); // 6 flags + 2 reserved bits + h264ParserState->sps.level_idc = nalStream.readU8(); // 0x29 = level 4.1 + + // some default values in case flags are not set + h264ParserState->sps.separate_colour_plane_flag = 0; + h264ParserState->sps.chroma_format_idc = 1; // Spec 7.4.2.1.1 + h264ParserState->sps.qpprime_y_zero_transform_bypass_flag = 0; + h264ParserState->sps.seq_scaling_matrix_present_flag = 0; + //h264ParserState->sps.mb_adaptive_frame_field_flag = 0; + + + + uint32 seq_parameter_set_id = nalStream.readUV_E(); + if (h264ParserState->sps.profile_idc == 100 || h264ParserState->sps.profile_idc == 110 || h264ParserState->sps.profile_idc == 122 || + h264ParserState->sps.profile_idc == 244 || h264ParserState->sps.profile_idc == 44 || h264ParserState->sps.profile_idc == 83 || + h264ParserState->sps.profile_idc == 86 || h264ParserState->sps.profile_idc == 118 || h264ParserState->sps.profile_idc == 128 || + h264ParserState->sps.profile_idc == 138 || h264ParserState->sps.profile_idc == 139 || h264ParserState->sps.profile_idc == 134 || h264ParserState->sps.profile_idc == 135) + { + h264ParserState->sps.chroma_format_idc = nalStream.readUV_E(); + if (h264ParserState->sps.chroma_format_idc == 3) + { + h264ParserState->sps.separate_colour_plane_flag = nalStream.readBit(); + } + h264ParserState->sps.bit_depth_luma_minus8 = nalStream.readUV_E(); + h264ParserState->sps.bit_depth_chroma_minus8 = nalStream.readUV_E(); + h264ParserState->sps.qpprime_y_zero_transform_bypass_flag = nalStream.readBit(); + h264ParserState->sps.seq_scaling_matrix_present_flag = nalStream.readBit(); + if (h264ParserState->sps.seq_scaling_matrix_present_flag) + { + parseNAL_sps_scaling_lists(h264ParserState, nalStream); + } + } + + h264ParserState->sps.log2_max_frame_num_minus4 = nalStream.readUV_E(); + h264ParserState->sps.pic_order_cnt_type = nalStream.readUV_E(); + if (h264ParserState->sps.pic_order_cnt_type == 0) + { + h264ParserState->sps.log2_max_pic_order_cnt_lsb_minus4 = nalStream.readUV_E(); + } + else if (h264ParserState->sps.pic_order_cnt_type == 2) + { + // nothing to parse + } + else + { + // todo - parse fields + cemu_assert_debug(false); + } + h264ParserState->sps.num_ref_frames = nalStream.readUV_E(); + h264ParserState->sps.gaps_in_frame_num_value_allowed_flag = nalStream.readBit(); + h264ParserState->sps.pic_width_in_mbs_minus1 = nalStream.readUV_E(); + h264ParserState->sps.pic_height_in_map_units_minus1 = nalStream.readUV_E(); + h264ParserState->sps.frame_mbs_only_flag = nalStream.readBit(); + if (h264ParserState->sps.frame_mbs_only_flag == 0) + { + h264ParserState->sps.mb_adaptive_frame_field_flag = nalStream.readBit(); + cemu_assert_debug(false); + } + else + h264ParserState->sps.mb_adaptive_frame_field_flag = 0; // default is zero? + + h264ParserState->sps.direct_8x8_inference_flag = nalStream.readBit(); + if (h264ParserState->sps.frame_mbs_only_flag == 0 && h264ParserState->sps.direct_8x8_inference_flag != 1) + { + cemu_assert_debug(false); // not allowed + } + + h264ParserState->sps.frame_cropping_flag = nalStream.readBit(); + if (h264ParserState->sps.frame_cropping_flag) + { + h264ParserState->sps.frame_crop_left_offset = nalStream.readUV_E(); + h264ParserState->sps.frame_crop_right_offset = nalStream.readUV_E(); + h264ParserState->sps.frame_crop_top_offset = nalStream.readUV_E(); + h264ParserState->sps.frame_crop_bottom_offset = nalStream.readUV_E(); + } + uint8 vui_parameters_present_flag = nalStream.readBit(); + if (vui_parameters_present_flag) + { + // vui_parameters + uint8 aspect_ratio_info_present_flag = nalStream.readBit(); + if (aspect_ratio_info_present_flag) + { + uint32 aspect_ratio_idc = nalStream.readBits<8>(); + if (aspect_ratio_idc == 255) // Extended_SAR + { + uint16 sar_width = nalStream.readBits<16>(); + uint16 sar_height = nalStream.readBits<16>(); + } + } + uint8 overscan_info_present_flag = nalStream.readBit(); + if (overscan_info_present_flag) + { + cemu_assert_debug(false); + } + uint8 video_signal_type_present_flag = nalStream.readBit(); + if (video_signal_type_present_flag) + { + uint8 video_format = nalStream.readBits<3>(); + uint8 video_full_range_flag = nalStream.readBit(); + uint8 colour_description_present_flag = nalStream.readBit(); + if (colour_description_present_flag) + { + uint8 colour_primaries = nalStream.readBits<8>(); + uint8 transfer_characteristics = nalStream.readBits<8>(); + uint8 matrix_coefficients = nalStream.readBits<8>(); + } + } + uint8 chroma_loc_info_present_flag = nalStream.readBit(); + if (chroma_loc_info_present_flag) + { + uint32 chroma_sample_loc_type_top_field = nalStream.readUV_E(); + uint32 chroma_sample_loc_type_bottom_field = nalStream.readUV_E(); + } + uint8 timing_info_present_flag = nalStream.readBit(); + if (timing_info_present_flag) + { + uint32 num_units_in_tick = nalStream.readBits<32>(); + uint32 time_scale = nalStream.readBits<32>(); + uint8 fixed_frame_rate_flag = nalStream.readBits<1>(); + } + uint8 nal_hrd_parameters_present_flag = nalStream.readBit(); + if (nal_hrd_parameters_present_flag) + { + parse_hrd_parameters(h264ParserState, nalStream); + } + uint8 vcl_hrd_parameters_present_flag = nalStream.readBit(); + if (vcl_hrd_parameters_present_flag) + { + parse_hrd_parameters(h264ParserState, nalStream); + } + if (nal_hrd_parameters_present_flag || vcl_hrd_parameters_present_flag) + { + uint8 low_delay_hrd_flag = nalStream.readBit(); + } + uint8 pic_struct_present_flag = nalStream.readBit(); + uint8 bitstream_restriction_flag = nalStream.readBit(); + if (bitstream_restriction_flag) + { + uint8 motion_vectors_over_pic_boundaries_flag = nalStream.readBit(); + uint32 max_bytes_per_pic_denom = nalStream.readUV_E(); + uint32 max_bits_per_mb_denom = nalStream.readUV_E(); + uint32 log2_max_mv_length_horizontal = nalStream.readUV_E(); + uint32 log2_max_mv_length_vertical = nalStream.readUV_E(); + uint32 max_num_reorder_frames = nalStream.readUV_E(); + uint32 max_dec_frame_buffering = nalStream.readUV_E(); + } + } + // trailing bits + bool nalValid = true; + if (nalStream.readTrailingRBSPBits() == false) + nalValid = false; + if (nalValid) + { + if(output) + output->hasSPS = true; + h264ParserState->hasSPS = true; + } + return true; +} + +bool parseNAL_pic_parameter_set_rbsp(h264ParserState_t* h264ParserState, h264ParserOutput_t* output, RBSPInputBitstream& nalStream) +{ + memset(&h264ParserState->pps, 0, sizeof(h264State_pic_parameter_set_t)); + + h264ParserState->pps.pic_parameter_set_id = nalStream.readUV_E(); + h264ParserState->pps.seq_parameter_set_id = nalStream.readUV_E(); + h264ParserState->pps.entropy_coding_mode_flag = nalStream.readBit(); + h264ParserState->pps.bottom_field_pic_order_in_frame_present_flag = nalStream.readBit(); + + h264ParserState->pps.num_slice_groups_minus1 = nalStream.readUV_E(); + if (h264ParserState->pps.num_slice_groups_minus1 > 0) + { + cemu_assert_debug(false); + return false; + } + + h264ParserState->pps.num_ref_idx_l0_default_active_minus1 = nalStream.readUV_E(); + h264ParserState->pps.num_ref_idx_l1_default_active_minus1 = nalStream.readUV_E(); + h264ParserState->pps.weighted_pred_flag = nalStream.readBit(); + h264ParserState->pps.weighted_bipred_idc = nalStream.readBits<2>(); + h264ParserState->pps.pic_init_qp_minus26 = nalStream.readSV_E(); + h264ParserState->pps.pic_init_qs_minus26 = nalStream.readSV_E(); + h264ParserState->pps.chroma_qp_index_offset = nalStream.readSV_E(); + + h264ParserState->pps.deblocking_filter_control_present_flag = nalStream.readBit(); + h264ParserState->pps.constrained_intra_pred_flag = nalStream.readBit(); + h264ParserState->pps.redundant_pic_cnt_present_flag = nalStream.readBit(); + + if (nalStream.more_rbsp_data()) + { + h264ParserState->pps.transform_8x8_mode_flag = nalStream.readBit(); + h264ParserState->pps.pic_scaling_matrix_present_flag = nalStream.readBit(); + if (h264ParserState->pps.pic_scaling_matrix_present_flag) + { + parseNAL_pps_scaling_lists(h264ParserState, nalStream); + } + h264ParserState->pps.second_chroma_qp_index_offset = nalStream.readSV_E(); + } + + // trailing bits + bool nalValid = true; + if (nalStream.readTrailingRBSPBits() == false) + nalValid = false; + if (nalValid) + { + if(output) + output->hasPPS = true; + h264ParserState->hasPPS = true; + } + return true; +} + +void parseNAL_ref_pic_list_modification(const h264State_seq_parameter_set_t& sps, const h264State_pic_parameter_set_t& pps, RBSPInputBitstream& nalStream, nal_slice_header_t* sliceHeader) +{ + if (!sliceHeader->slice_type.isSliceTypeI() && !sliceHeader->slice_type.isSliceTypeSI()) + { + uint8 ref_pic_list_modification_flag_l0 = nalStream.readBit(); + if (ref_pic_list_modification_flag_l0) + { + sliceHeader->pic_list_modification0Count = 0; + uint32 modType; + while(true) + { + if (sliceHeader->pic_list_modification0Count >= 32) + { + cemu_assert_debug(false); + return; + } + modType = nalStream.readUV_E(); + sliceHeader->pic_list_modification0Array[sliceHeader->pic_list_modification0Count].type = modType; + if (modType == 0 || modType == 1) + { + sliceHeader->pic_list_modification0Array[sliceHeader->pic_list_modification0Count].abs_diff_pic_num_minus1 = nalStream.readUV_E(); + } + else if (modType == 2) + { + sliceHeader->pic_list_modification0Array[sliceHeader->pic_list_modification0Count].long_term_pic_num = nalStream.readUV_E(); + } + else if (modType == 3) + { + // end of list + break; + } + else + { + cemu_assert_debug(false); // invalid type + return; + } + sliceHeader->pic_list_modification0Count++; + } + } + } + if (sliceHeader->slice_type.isSliceTypeB()) + { + uint8 ref_pic_list_modification_flag_l1 = nalStream.readBit(); + if (ref_pic_list_modification_flag_l1) + { + cemu_assert_debug(false); // testing required + while (true) + { + if (sliceHeader->pic_list_modification1Count >= 32) + { + cemu_assert_debug(false); + return; + } + uint32 modType = nalStream.readUV_E(); // aka modification_of_pic_nums_idc + sliceHeader->pic_list_modification1Array[sliceHeader->pic_list_modification1Count].type = modType; + if (modType == 0 || modType == 1) + { + sliceHeader->pic_list_modification1Array[sliceHeader->pic_list_modification1Count].abs_diff_pic_num_minus1 = nalStream.readUV_E(); + } + else if (modType == 2) + { + sliceHeader->pic_list_modification1Array[sliceHeader->pic_list_modification1Count].long_term_pic_num = nalStream.readUV_E(); + } + else if(modType == 3) + { + break; + } + else + { + cemu_assert_debug(false); // invalid mode + break; + } + sliceHeader->pic_list_modification0Count++; + } + if (sliceHeader->pic_list_modification1Count > 0) + { + forceLogDebug_printf("sliceHeader->pic_list_modification1Count non-zero is not supported"); + cemu_assert_unimplemented(); + } + } + } +} + +void parseNAL_dec_ref_pic_marking(const h264State_seq_parameter_set_t& sps, const h264State_pic_parameter_set_t& pps, RBSPInputBitstream& nalStream, nal_slice_header_t* sliceHeader) +{ + sliceHeader->memory_management_control_operation_num = 0; + if (sliceHeader->IdrPicFlag) + { + uint8 no_output_of_prior_pics_flag = nalStream.readBit(); + if (no_output_of_prior_pics_flag) + cemu_assert_debug(false); + uint8 long_term_reference_flag = nalStream.readBit(); + if (long_term_reference_flag) + cemu_assert_debug(false); + + sliceHeader->adaptive_ref_pic_marking_mode_flag = 1; + } + else + { + sliceHeader->adaptive_ref_pic_marking_mode_flag = nalStream.readBit(); + if (sliceHeader->adaptive_ref_pic_marking_mode_flag) + { + while (true) + { + uint32 memory_management_control_operation = nalStream.readUV_E(); + if (memory_management_control_operation == nal_slice_header_t::MEMOP_END) + break; + if (sliceHeader->memory_management_control_operation_num >= 16) + { + cemu_assert_debug(false); + return; + } + sliceHeader->memory_management_control_operation[sliceHeader->memory_management_control_operation_num].op = memory_management_control_operation; + if (memory_management_control_operation == nal_slice_header_t::MEMOP_REMOVE_REF_FROM_SHORT_TERM || memory_management_control_operation == nal_slice_header_t::MEMOP_MAKE_LONG_TERM_REF) + { + sliceHeader->memory_management_control_operation[sliceHeader->memory_management_control_operation_num].difference_of_pic_nums_minus1 = nalStream.readUV_E(); + } + else if (memory_management_control_operation == nal_slice_header_t::MEMOP_REMOVE_REF_FROM_LONG_TERM) + { + sliceHeader->memory_management_control_operation[sliceHeader->memory_management_control_operation_num].long_term_pic_num = nalStream.readUV_E(); + } + if (memory_management_control_operation == nal_slice_header_t::MEMOP_MAKE_LONG_TERM_REF || memory_management_control_operation == nal_slice_header_t::MEMOP_MAKE_CURRENT_LONG_TERM_REF) + { + sliceHeader->memory_management_control_operation[sliceHeader->memory_management_control_operation_num].long_term_frame_idx = nalStream.readUV_E(); + } + if (memory_management_control_operation == nal_slice_header_t::MEMOP_MAX_LONG_TERM_INDEX) + { + sliceHeader->memory_management_control_operation[sliceHeader->memory_management_control_operation_num].max_long_term_frame_idx_plus1 = nalStream.readUV_E(); + } + sliceHeader->memory_management_control_operation_num++; + } + } + } +} + +void parseNAL_pred_weight_table(const h264State_seq_parameter_set_t& sps, const h264State_pic_parameter_set_t& pps, RBSPInputBitstream& nalStream, nal_slice_header_t* sliceHeader) +{ + uint8 luma_log2_weight_denom = nalStream.readUV_E(); + + uint32 ChromaArrayType; + if (sps.separate_colour_plane_flag == 0) + ChromaArrayType = sps.chroma_format_idc; + else + ChromaArrayType = 0; + + if (ChromaArrayType != 0) + { + uint32 chroma_log2_weight_denom = nalStream.readUV_E(); + } + + for (uint32 i = 0; i <= sliceHeader->num_ref_idx_l0_active_minus1; i++) + { + uint8 luma_weight_l0_flag = nalStream.readBit(); + if (luma_weight_l0_flag) + { + uint32 luma_weight_l0 = nalStream.readSV_E(); + uint32 luma_offset_l0 = nalStream.readSV_E(); + } + if (ChromaArrayType != 0) + { + uint8 chroma_weight_l0_flag = nalStream.readBit(); + if (chroma_weight_l0_flag) + { + for (sint32 j = 0; j < 2; j++) + { + uint32 chroma_weight_l0 = nalStream.readSV_E(); + uint32 chroma_offset_l0 = nalStream.readSV_E(); + } + } + } + } + if (sliceHeader->slice_type.isSliceTypeB()) + { + for (uint32 i = 0; i <= sliceHeader->num_ref_idx_l1_active_minus1; i++) + { + uint8 luma_weight_l1_flag = nalStream.readBit(); + if (luma_weight_l1_flag) + { + cemu_assert_debug(false); + //luma_weight_l1[i] + //luma_offset_l1[i] + } + if (ChromaArrayType != 0) + { + cemu_assert_debug(false); + //chroma_weight_l1_flag + //if (chroma_weight_l1_flag) + //{ + // for (j = 0; j < 2; j++) + // { + // chroma_weight_l1[i][j] + // chroma_offset_l1[i][j] + // } + //} + } + } + } +} + +void parseNAL_slice_header(const h264State_seq_parameter_set_t& sps, const h264State_pic_parameter_set_t& pps, RBSPInputBitstream& nalStream, uint8 nal_unit_type, uint8 nal_ref_idc, nal_slice_header_t* sliceHeader) +{ + bool IdrPicFlag = nal_unit_type == 5; + + memset(sliceHeader, 0, sizeof(nal_slice_header_t)); + sliceHeader->nal_ref_idc = nal_ref_idc; + sliceHeader->nal_unit_type = nal_unit_type; + sliceHeader->first_mb_in_slice = nalStream.readUV_E(); // address of first macroblock in slice + sliceHeader->slice_type = { nalStream.readUV_E() }; + sliceHeader->pic_parameter_set_id = nalStream.readUV_E(); + + if (sps.separate_colour_plane_flag == 1) + { + cemu_assert_debug(false); + return; + } + + sliceHeader->frame_num = nalStream.readBits(sps.log2_max_frame_num_minus4 + 4); + + if (sps.frame_mbs_only_flag == 0) + { + sliceHeader->field_pic_flag = nalStream.readBit(); + if (sliceHeader->field_pic_flag) + { + sliceHeader->bottom_field_flag = nalStream.readBit(); + } + } + + sliceHeader->IdrPicFlag = IdrPicFlag?1:0; + + if (IdrPicFlag) + { + sliceHeader->idr_pic_id = nalStream.readUV_E(); + } + if (sps.pic_order_cnt_type == 0) + { + sliceHeader->pic_order_cnt_lsb = nalStream.readBits(sps.log2_max_pic_order_cnt_lsb_minus4 + 4); + if (pps.bottom_field_pic_order_in_frame_present_flag && sliceHeader->field_pic_flag == 0) + sliceHeader->delta_pic_order_cnt_bottom = nalStream.readSV_E(); + } + else if (sps.pic_order_cnt_type == 1) + { + cemu_assert(false); + } + if (pps.redundant_pic_cnt_present_flag) + { + sliceHeader->redundant_pic_cnt = nalStream.readUV_E(); + } + + if (sliceHeader->slice_type.isSliceTypeB()) + { + sliceHeader->direct_spatial_mv_pred_flag = nalStream.readBit(); + } + + sliceHeader->num_ref_idx_l0_active_minus1 = pps.num_ref_idx_l0_default_active_minus1; + sliceHeader->num_ref_idx_l1_active_minus1 = pps.num_ref_idx_l1_default_active_minus1; + + if (sliceHeader->slice_type.isSliceTypeP() || sliceHeader->slice_type.isSliceTypeSP() || sliceHeader->slice_type.isSliceTypeB()) + { + sliceHeader->num_ref_idx_active_override_flag = nalStream.readBit(); + if (sliceHeader->num_ref_idx_active_override_flag) + { + sliceHeader->num_ref_idx_l0_active_minus1 = nalStream.readUV_E(); + if (sliceHeader->slice_type.isSliceTypeB()) + { + sliceHeader->num_ref_idx_l1_active_minus1 = nalStream.readUV_E(); + } + } + } + + // todo - ref_pic_list_mvc_modification etc + if (nal_unit_type == 20 || nal_unit_type == 21) + { + cemu_assert_debug(false); + } + else + { + parseNAL_ref_pic_list_modification(sps, pps, nalStream, sliceHeader); + } + + if ((pps.weighted_pred_flag && (sliceHeader->slice_type.isSliceTypeP() || sliceHeader->slice_type.isSliceTypeSP())) || (pps.weighted_bipred_idc == 1 && sliceHeader->slice_type.isSliceTypeB())) + { + parseNAL_pred_weight_table(sps, pps, nalStream, sliceHeader); + } + + if (sliceHeader->nal_ref_idc != 0) + { + parseNAL_dec_ref_pic_marking(sps, pps, nalStream, sliceHeader); + } + + if (pps.entropy_coding_mode_flag && !sliceHeader->slice_type.isSliceTypeI() && !sliceHeader->slice_type.isSliceTypeSI()) + { + uint32 cabac_init_idc = nalStream.readUV_E(); + cemu_assert_debug(cabac_init_idc <= 2); // invalid value + } + + sint32 slice_qp_delta = nalStream.readSV_E(); + if (sliceHeader->slice_type.isSliceTypeSP() || sliceHeader->slice_type.isSliceTypeSI()) + { + if (sliceHeader->slice_type.isSliceTypeSP()) + { + uint8 sp_for_switch_flag = nalStream.readBit(); + } + sint32 slice_qs_delta = nalStream.readSV_E(); + } + + if (pps.deblocking_filter_control_present_flag) + { + uint32 disable_deblocking_filter_idc = nalStream.readUV_E(); + if (disable_deblocking_filter_idc != 1) + { + sint32 slice_alpha_c0_offset_div2 = nalStream.readSV_E(); + sint32 slice_beta_offset_div2 = nalStream.readSV_E(); + } + } + if (pps.num_slice_groups_minus1 > 0 && pps.slice_group_map_type >= 3 && pps.slice_group_map_type <= 5) + { + cemu_assert_debug(false); // todo + } +} + +void _calculateFrameOrder(h264ParserState_t* h264ParserState, const h264State_seq_parameter_set_t& sps, const h264State_pic_parameter_set_t& pps, nal_slice_header_t* sliceHeader) +{ + if (sps.pic_order_cnt_type == 0) + { + // calculate TopFieldOrderCnt + uint32 prevPicOrderCntMsb; + uint32 prevPicOrderCntLsb; + if (sliceHeader->IdrPicFlag) + { + sliceHeader->calculated.TopFieldOrderCnt = 0; + + prevPicOrderCntMsb = 0; + prevPicOrderCntLsb = 0; + + } + else + { + uint32 prevRefPic_PicOrderCntMsb = h264ParserState->picture_order.prevPicOrderCntMsb; + uint32 prevRefPic_pic_order_cnt_lsb = h264ParserState->picture_order.prevPicOrderCntLsb; // todo + + prevPicOrderCntMsb = prevRefPic_PicOrderCntMsb; + prevPicOrderCntLsb = prevRefPic_pic_order_cnt_lsb; + + } + + uint32 MaxPicOrderCntLsb = 1 << (sps.log2_max_pic_order_cnt_lsb_minus4 + 4); // todo - verify + + uint32 PicOrderCntMsb; + + if ((sliceHeader->pic_order_cnt_lsb < prevPicOrderCntLsb) && (((sint32)prevPicOrderCntLsb - (sint32)sliceHeader->pic_order_cnt_lsb) >= (sint32)(MaxPicOrderCntLsb / 2))) + PicOrderCntMsb = prevPicOrderCntMsb + MaxPicOrderCntLsb; + else if ((sliceHeader->pic_order_cnt_lsb > prevPicOrderCntLsb) && (((sint32)sliceHeader->pic_order_cnt_lsb - (sint32)prevPicOrderCntLsb) > (sint32)(MaxPicOrderCntLsb / 2))) + PicOrderCntMsb = prevPicOrderCntMsb - MaxPicOrderCntLsb; + else + PicOrderCntMsb = prevPicOrderCntMsb; + + uint32 TopFieldOrderCnt = PicOrderCntMsb + sliceHeader->pic_order_cnt_lsb; + + if (sliceHeader->IdrPicFlag != 0 && TopFieldOrderCnt != 0) + cemu_assert(false); + + sliceHeader->calculated.TopFieldOrderCnt = TopFieldOrderCnt; + + h264ParserState->picture_order.prevPicOrderCntMsb = PicOrderCntMsb; + h264ParserState->picture_order.prevPicOrderCntLsb = sliceHeader->pic_order_cnt_lsb; + } + else if (sps.pic_order_cnt_type == 2) + { + // display order matches decode order + + uint32 prevFrameNum = h264ParserState->picture_order.prevFrameNum; + ; + uint32 FrameNumOffset; + if (sliceHeader->IdrPicFlag) + { + FrameNumOffset = 0; + } + else + { + // todo - check for memory_management_control_operation 5 + // prevFrameNumOffset is set equal to the value of FrameNumOffset of the previous picture in decoding order. + uint32 prevFrameNumOffset = h264ParserState->picture_order.prevFrameNumOffset; + + if (prevFrameNum > sliceHeader->frame_num) + FrameNumOffset = prevFrameNumOffset + sps.getMaxFrameNum(); + else + FrameNumOffset = prevFrameNumOffset; + + + + } + + uint32 tempPicOrderCnt; + if (sliceHeader->IdrPicFlag == 1) + tempPicOrderCnt = 0; + else if (sliceHeader->nal_ref_idc == 0) + tempPicOrderCnt = 2 * (FrameNumOffset + sliceHeader->frame_num) - 1; + else + tempPicOrderCnt = 2 * (FrameNumOffset + sliceHeader->frame_num); + + uint32 TopFieldOrderCnt = 0; + uint32 BottomFieldOrderCnt = 0; + + if (sliceHeader->field_pic_flag == 0) + { + TopFieldOrderCnt = tempPicOrderCnt; + BottomFieldOrderCnt = tempPicOrderCnt; + } + else if (sliceHeader->bottom_field_flag != 0) + { + cemu_assert_debug(false); // fields aren't supported + BottomFieldOrderCnt = tempPicOrderCnt; + } + else + { + cemu_assert_debug(false); // fields aren't supported + TopFieldOrderCnt = tempPicOrderCnt; + } + + sliceHeader->calculated.TopFieldOrderCnt = TopFieldOrderCnt; + + h264ParserState->picture_order.prevFrameNum = sliceHeader->frame_num; + h264ParserState->picture_order.prevFrameNumOffset = FrameNumOffset; + } + else + { + cemu_assert_debug(false); + } +} + +void parseNAL_slice_layer_without_partitioning_rbsp(h264ParserState_t* h264ParserState, h264ParserOutput_t* output, sint32 streamSubOffset, sint32 streamSubLength, RBSPInputBitstream& nalStream, uint8 nal_ref_idc, uint8 nal_unit_type) +{ + nal_slice_t slice = {}; + cemu_assert_debug(h264ParserState->hasPPS && h264ParserState->hasSPS); + parseNAL_slice_header(h264ParserState->sps, h264ParserState->pps, nalStream, nal_unit_type, nal_ref_idc, &slice.slice_header); + _calculateFrameOrder(h264ParserState, h264ParserState->sps, h264ParserState->pps, &slice.slice_header); + if (output->sliceCount >= output->sliceInfo.size()) + { + cemu_assert_debug(false); // internal slice buffer full + return; + } + output->sliceInfo[output->sliceCount].header = slice.slice_header; + output->sliceInfo[output->sliceCount].streamSubOffset = streamSubOffset; + output->sliceInfo[output->sliceCount].streamSubSize = streamSubLength; + output->sliceCount++; +} + +void h264Parse(h264ParserState_t* h264ParserState, h264ParserOutput_t* output, uint8* data, uint32 length, bool parseSlices) +{ + memset(output, 0, sizeof(h264ParserOutput_t)); + NALInputBitstream nalStream(data, length); + + if (nalStream.hasError()) + { + cemu_assert_debug(false); + return; + } + + // parse NAL data + while (true) + { + if (nalStream.isEndOfStream()) + break; + RBSPInputBitstream rbspStream; + if (nalStream.getNextRBSP(rbspStream) == false) + break; + + sint32 streamSubOffset = (sint32)(rbspStream.getBasePtr() - data); + sint32 streamSubLength = rbspStream.getBaseLength(); + + // parse NAL header + uint8 nalHeaderByte = rbspStream.readU8(); + if ((nalHeaderByte & 0x80) != 0) + { + // MSB must be zero + cemu_assert_debug(false); + continue; + } + uint8 nal_ref_idc = (nalHeaderByte >> 5) & 0x3; + uint8 nal_unit_type = (nalHeaderByte >> 0) & 0x1f; + // nal_ref_idc -> If not 0, reference picture. Contains sequence set ?? + // nal_unit_type -> RBSP type + // nal_unit_type: + // 0 - unspecified + // 1 - Coded slice of a non-IDR picture slice_layer_without_partitioning_rbsp() + // 2 - Coded slice data partition A slice_data_partition_a_layer_rbsp() + // 3 - Coded slice data partition B slice_data_partition_b_layer_rbsp() + // 4 - Coded slice data partition C slice_data_partition_c_layer_rbsp() + // 5 - Coded slice of an IDR picture slice_layer_without_partitioning_rbsp() + // 6 - Supplemental enhancement information (SEI) sei_rbsp() + // 7 - Sequence parameter set seq_parameter_set_rbsp() + // 8 - Picture parameter set pic_parameter_set_rbsp() + // 9 - Access unit delimiter access_unit_delimiter_rbsp() + // 10 - End of sequence end_of_seq_rbsp() + // 11 - End of stream end_of_stream_rbsp() + // 12 - Filler data filler_data_rbsp() + // 13 to 23 - reserved + // 24 to 31 - unspecified + // VCL NAL -> nal_unit_type 1,2,3,4,5 + // non-VCL NAL -> All other nal_unit_type values + + if (nal_unit_type == 14 || nal_unit_type == 20 || nal_unit_type == 21) + { + cemu_assert_debug(false); + // todo - there are fields to parse here + } + switch (nal_unit_type) + { + case 1: + if (parseSlices) + parseNAL_slice_layer_without_partitioning_rbsp(h264ParserState, output, streamSubOffset, streamSubLength, rbspStream, nal_ref_idc, nal_unit_type); + break; + case 5: + if (parseSlices) + parseNAL_slice_layer_without_partitioning_rbsp(h264ParserState, output, streamSubOffset, streamSubLength, rbspStream, nal_ref_idc, nal_unit_type); + break; + case 6: + // SEI + break; + case 7: + cemu_assert_debug(parseNAL_seq_parameter_set_rbsp(h264ParserState, output, rbspStream)); + break; + case 8: + cemu_assert_debug(parseNAL_pic_parameter_set_rbsp(h264ParserState, output, rbspStream)); + break; + case 9: + // access unit delimiter + break; + case 10: + // end of sequence + break; + case 12: + // filler data + // seen in Cocoto Magic Circus 2 intro video + break; + default: + forceLogDebug_printf("Unsupported NAL unit type %d", nal_unit_type); + cemu_assert_debug(false); + // todo + break; + } + } +} + +sint32 h264GetUnitLength(h264ParserState_t* h264ParserState, uint8* data, uint32 length) +{ + NALInputBitstream nalStream(data, length); + + if (nalStream.hasError()) + { + cemu_assert_debug(false); + return -1; + } + + // search for start code + sint32 startCodeOffset = 0; + bool hasStartcode = false; + while (startCodeOffset < (sint32)(length - 3)) + { + if (data[startCodeOffset + 0] == 0x00 && data[startCodeOffset + 1] == 0x00 && data[startCodeOffset + 2] == 0x01) + { + hasStartcode = true; + break; + } + startCodeOffset++; + } + if (hasStartcode == false) + return -1; + data += startCodeOffset; + length -= startCodeOffset; + + // parse NAL data + while (true) + { + if (nalStream.isEndOfStream()) + break; + RBSPInputBitstream rbspStream; + if (nalStream.getNextRBSP(rbspStream, true) == false) + break; + + sint32 streamSubOffset = (sint32)(rbspStream.getBasePtr() - data); + sint32 streamSubLength = rbspStream.getBaseLength(); + + // parse NAL header + uint8 nalHeaderByte = rbspStream.readU8(); + if ((nalHeaderByte & 0x80) != 0) + { + // MSB must be zero + cemu_assert_debug(false); + continue; + } + uint8 nal_ref_idc = (nalHeaderByte >> 5) & 0x3; + uint8 nal_unit_type = (nalHeaderByte >> 0) & 0x1f; + if (nal_unit_type == 14 || nal_unit_type == 20 || nal_unit_type == 21) + { + cemu_assert_debug(false); + continue; + } + switch (nal_unit_type) + { + case 1: + case 5: + { + // note: We cant parse the slice data because we dont have SPS and PPS data reliably available + // + // currently we just assume there is 1 slice per unit + return (sint32)((rbspStream.getBasePtr() + rbspStream.getBaseLength()) - data) + startCodeOffset; + + break; + } + case 6: + // SEI + break; + case 7: + cemu_assert_debug(parseNAL_seq_parameter_set_rbsp(h264ParserState, nullptr, rbspStream)); + break; + case 8: + cemu_assert_debug(parseNAL_pic_parameter_set_rbsp(h264ParserState, nullptr, rbspStream)); + break; + case 9: + // access unit delimiter + break; + case 10: + // end of sequence + break; + default: + forceLogDebug_printf("Unsupported NAL unit type %d", nal_unit_type); + assert_dbg(); // todo - NAL 10 is used in DKC TF + // todo + break; + } + } + return -1; +} + +static const unsigned char h264_default_4x4_Intra[16] = +{ + 6, 13, 13, 20, + 20, 20, 28, 28, + 28, 28, 32, 32, + 32, 37, 37, 42 +}; + +static const unsigned char h264_default_4x4_Inter[16] = +{ + 10, 14, 14, 20, + 20, 20, 24, 24, + 24, 24, 27, 27, + 27, 30, 30, 34 +}; + +static const unsigned char h264_default_8x8_Intra[64] = +{ + 6, 10, 10, 13, 11, 13, 16, 16, + 16, 16, 18, 18, 18, 18, 18, 23, + 23, 23, 23, 23, 23, 25, 25, 25, + 25, 25, 25, 25, 27, 27, 27, 27, + 27, 27, 27, 27, 29, 29, 29, 29, + 29, 29, 29, 31, 31, 31, 31, 31, + 31, 33, 33, 33, 33, 33, 36, 36, + 36, 36, 38, 38, 38, 40, 40, 42 +}; + +static const unsigned char h264_default_8x8_Inter[64] = +{ + 9, 13, 13, 15, 13, 15, 17, 17, + 17, 17, 19, 19, 19, 19, 19, 21, + 21, 21, 21, 21, 21, 22, 22, 22, + 22, 22, 22, 22, 24, 24, 24, 24, + 24, 24, 24, 24, 25, 25, 25, 25, + 25, 25, 25, 27, 27, 27, 27, 27, + 27, 28, 28, 28, 28, 28, 30, 30, + 30, 30, 32, 32, 32, 33, 33, 35 +}; + +void h264Parser_getScalingMatrix4x4(h264State_seq_parameter_set_t* sps, h264State_pic_parameter_set_t* pps, nal_slice_header_t* sliceHeader, sint32 index, uint8* matrix4x4) +{ + // use scaling lists from PPS first + if (pps->pic_scaling_matrix_present_flag) + { + if (pps->ScalingList4x4[index].isPresent) + { + cemu_assert(false); // testing needed (what about UseDefaultScalingMatrix?) + memcpy(matrix4x4, pps->ScalingList4x4[index].list, 4 * 4 * sizeof(uint8)); + } + else + { + if (index < 3) + memcpy(matrix4x4, h264_default_4x4_Intra, 4 * 4 * sizeof(uint8)); + else + memcpy(matrix4x4, h264_default_4x4_Inter, 4 * 4 * sizeof(uint8)); + } + return; + } + // use scaling lists from SPS + if (sps->seq_scaling_matrix_present_flag) + { + if (sps->ScalingList4x4[index].isPresent) + { + cemu_assert(false); // testing needed (what about UseDefaultScalingMatrix?) + memcpy(matrix4x4, sps->ScalingList4x4[index].list, 4 * 4 * sizeof(uint8)); + } + else + { + if (index < 3) + memcpy(matrix4x4, h264_default_4x4_Intra, 4 * 4 * sizeof(uint8)); + else + memcpy(matrix4x4, h264_default_4x4_Inter, 4 * 4 * sizeof(uint8)); + } + return; + } + // default (Flat_4x4_16) + memset(matrix4x4, 16, 4 * 4 * sizeof(uint8)); +} + +void h264Parser_getScalingMatrix8x8(h264State_seq_parameter_set_t* sps, h264State_pic_parameter_set_t* pps, nal_slice_header_t* sliceHeader, sint32 index, uint8* matrix8x8) +{ + // use scaling lists from PPS first + if (pps->pic_scaling_matrix_present_flag) + { + if (pps->ScalingList8x8[index].isPresent) + { + cemu_assert(false); // testing needed (what about UseDefaultScalingMatrix?) + memcpy(matrix8x8, pps->ScalingList8x8[index].list, 8 * 8 * sizeof(uint8)); + } + else + { + if ((index&1) == 0) + memcpy(matrix8x8, h264_default_8x8_Intra, 8 * 8 * sizeof(uint8)); + else + memcpy(matrix8x8, h264_default_8x8_Inter, 8 * 8 * sizeof(uint8)); + } + return; + } + // use scaling lists from SPS + if (sps->seq_scaling_matrix_present_flag) + { + if (sps->ScalingList8x8[index].isPresent) + { + cemu_assert(false); // testing needed (what about UseDefaultScalingMatrix?) + memcpy(matrix8x8, sps->ScalingList8x8[index].list, 8 * 8 * sizeof(uint8)); + } + else + { + if ((index & 1) == 0) + memcpy(matrix8x8, h264_default_8x8_Intra, 8 * 8 * sizeof(uint8)); + else + memcpy(matrix8x8, h264_default_8x8_Inter, 8 * 8 * sizeof(uint8)); + } + return; + } + // default (Flat_8x8_16) + memset(matrix8x8, 16, 8 * 8 * sizeof(uint8)); +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/h264_avc/parser/H264Parser.h b/src/Cafe/OS/libs/h264_avc/parser/H264Parser.h new file mode 100644 index 00000000..ee32ca8b --- /dev/null +++ b/src/Cafe/OS/libs/h264_avc/parser/H264Parser.h @@ -0,0 +1,528 @@ +#pragma once + +/* stream parsers */ + +class RBSPInputBitstream +{ +public: + RBSPInputBitstream() {}; + + RBSPInputBitstream(uint8* stream, uint32 length) + { + this->streamPtr = stream; + this->streamLength = length; + if (streamLength >= 1) + currentRBSPByte = streamPtr[0]; + else + currentRBSPByte = 0; + errorFlag = false; + } + + bool hasError() const + { + return errorFlag; + } + + bool isByteAligned() const + { + return bitIndex == 0; + } + + uint8 readU8() + { + if (readIndex >= streamLength) + return 0; + cemu_assert_debug(bitIndex == 0); + uint8 v = _getCurrentRBSPByte(); + _nextRBSPByte(); + return v; + } + + uint8 readBit() + { + if (readIndex >= streamLength) + return 0; + uint8 v = _getCurrentRBSPByte(); + uint8 bitValue = (v >> (7 - bitIndex)) & 1; + bitIndex += 1; + if (bitIndex >= 8) + { + bitIndex = 0; + _nextRBSPByte(); + } + return bitValue; + } + + template<int N> + uint32 readBits() + { + uint32 v = 0; + for (sint32 i = 0; i < N; i++) + { + v <<= 1; + v |= (readBit() ? 1 : 0); + } + return v; + } + + uint32 readBits(sint32 N) + { + uint32 v = 0; + for (sint32 i = 0; i < N; i++) + { + v <<= 1; + v |= (readBit() ? 1 : 0); + } + return v; + } + + uint32 readUV_E() + { + if (readBit() != 0) + { + return 0; + } + for (sint32 i = 1; i < 31; i++) + { + uint8 bitValue = readBit(); + if (bitValue) + { + uint32 rangeBase = (1 << i) - 1; + // read range bits + uint32 suffixBits = 0; + for (sint32 b = 0; b < i; b++) + { + suffixBits <<= 1; + suffixBits |= (readBit() ? 1 : 0); + } + return rangeBase + suffixBits; + } + } + cemu_assert_debug(false); + return 0; + } + + sint32 readSV_E() + { + uint32 t = readUV_E(); + if (t == 0) + return 0; + if ((t & 1) != 0) + { + // positive + return (sint32)((t + 1) / 2); + } + // negative + return -(sint32)((t + 1) / 2); + } + + bool readTrailingRBSPBits() + { + // read the stop bit which must always be 1 + if (readBit() == 0) + return false; + // read zero bits until byte aligned + while (bitIndex != 0) + { + if (readBit() != 0) + return false; // invalid padding bit + } + // check if we reached end of stream + if (readIndex >= streamLength) + return true; + return false; + } + + bool more_rbsp_data() + { + uint32 storedReadIndex = readIndex; + uint32 storedBitIndex = bitIndex; + bool hasMoreRBSPData = readTrailingRBSPBits() == false; + readIndex = storedReadIndex; + bitIndex = storedBitIndex; + currentRBSPByte = streamPtr[readIndex]; + return hasMoreRBSPData; + } + + bool isEndOfStream() + { + return readIndex >= streamLength; + } + + + uint8* getBasePtr() + { + return streamPtr; + } + + sint32 getBaseLength() + { + return streamLength; + } + +private: + uint8 _getCurrentRBSPByte() + { + return currentRBSPByte; + } + + void _nextRBSPByte() + { + readIndex += sizeof(uint8); + if (readIndex >= 2 && streamPtr[readIndex - 2] == 0x00 && streamPtr[readIndex - 1] == 0x00 && streamPtr[readIndex] == 0x03) + { + readIndex += 1; + currentRBSPByte = streamPtr[readIndex]; + return; + } + else + { + currentRBSPByte = streamPtr[readIndex]; + } + } + +private: + uint8* streamPtr; + uint32 streamLength; + uint32 readIndex{}; + uint8 currentRBSPByte{}; + sint32 bitIndex{}; + bool errorFlag{}; +}; + +class NALInputBitstream +{ +public: + NALInputBitstream(uint8* stream, uint32 length) + { + this->nalStreamPtr = stream; + this->nalStreamLength = length; + errorFlag = false; + } + + bool hasError() + { + return errorFlag; + } + + bool getNextRBSP(RBSPInputBitstream& rbspInputBitstream, bool mustEndAtStartCode = false) + { + sint32 indexNextStartSignature = findNextStartSignature(); + + if (indexNextStartSignature < 0) + { + if (mustEndAtStartCode) + return false; + indexNextStartSignature = nalStreamLength; + } + if (indexNextStartSignature <= readIndex) + return false; + // skip current start signature + if ((nalStreamLength - readIndex) >= 3 && nalStreamPtr[readIndex + 0] == 0 && nalStreamPtr[readIndex + 1] == 0 && nalStreamPtr[readIndex + 2] == 1) + { + readIndex += 3; + } + else if ((nalStreamLength - readIndex) >= 3 && nalStreamPtr[readIndex + 0] == 0 && nalStreamPtr[readIndex + 1] == 0 && nalStreamPtr[readIndex + 2] == 0 && nalStreamPtr[readIndex + 3] == 1) + { + readIndex += 4; + } + else + { + // no start signature + } + cemu_assert(readIndex <= indexNextStartSignature); + cemu_assert_debug(readIndex != indexNextStartSignature); + // create rbsp substream + rbspInputBitstream = RBSPInputBitstream(nalStreamPtr + readIndex, indexNextStartSignature - readIndex); + readIndex = indexNextStartSignature; + return true; + } + + bool isEndOfStream() + { + return readIndex >= nalStreamLength; + } + +private: + sint32 findNextStartSignature() + { + if (readIndex >= nalStreamLength) + return -1; + sint32 offset = readIndex; + // if there is a start signature at the current address, skip it + if ((offset + 3) <= nalStreamLength && nalStreamPtr[offset + 0] == 0x00 && nalStreamPtr[offset + 1] == 0x00 && nalStreamPtr[offset + 2] == 0x01) + { + offset += 3; + } + else if ((offset + 4) <= nalStreamLength && nalStreamPtr[offset + 0] == 0x00 && nalStreamPtr[offset + 1] == 0x00 && nalStreamPtr[offset + 2] == 0x00 && nalStreamPtr[offset + 3] == 0x01) + { + offset += 4; + } + while (offset < (nalStreamLength - 3)) + { + if (nalStreamPtr[offset + 0] == 0x00 && nalStreamPtr[offset + 1] == 0x00) + { + if (nalStreamPtr[offset + 2] == 0x01) + { + return offset; + } + else if ((nalStreamLength - offset) >= 4 && nalStreamPtr[offset + 2] == 0x00 && nalStreamPtr[offset + 3] == 0x01) + { + return offset; + } + } + offset++; + } + return -1; + } + +private: + uint8* nalStreamPtr; + sint32 nalStreamLength; + sint32 readIndex{}; + bool errorFlag{}; +}; + +/* H264 NAL parser */ + +struct h264_scaling_matrix4x4_t +{ + uint8 isPresent; + uint8 UseDefaultScalingMatrix; + sint32 list[4*4]; +}; + +struct h264_scaling_matrix8x8_t +{ + uint8 isPresent; + uint8 UseDefaultScalingMatrix; + sint32 list[8*8]; +}; + +struct h264State_pic_parameter_set_t +{ + uint32 pic_parameter_set_id; + uint32 seq_parameter_set_id; + uint8 entropy_coding_mode_flag; + uint8 bottom_field_pic_order_in_frame_present_flag; // alias pic_order_present_flag + uint32 num_slice_groups_minus1; + // slice group + uint32 slice_group_map_type; + // todo - some variable sized fields + + uint32 slice_group_change_rate_minus1; // todo + + uint32 num_ref_idx_l0_default_active_minus1; + uint32 num_ref_idx_l1_default_active_minus1; + uint8 weighted_pred_flag; + uint8 weighted_bipred_idc; + sint32 pic_init_qp_minus26; + sint32 pic_init_qs_minus26; + sint32 chroma_qp_index_offset; + + uint8 deblocking_filter_control_present_flag; + uint8 constrained_intra_pred_flag; + uint8 redundant_pic_cnt_present_flag; + + uint8 transform_8x8_mode_flag; + uint8 pic_scaling_matrix_present_flag; + h264_scaling_matrix4x4_t ScalingList4x4[6]; + h264_scaling_matrix8x8_t ScalingList8x8[6]; + + sint32 second_chroma_qp_index_offset; +}; + + +struct h264State_seq_parameter_set_t +{ + uint8 profile_idc; // 0x64 = high profile + uint8 constraint; // 6 flags + 2 reserved bits + uint8 level_idc; // 0x29 = level 4.1 + + uint32 seq_parameter_set_id; + uint32 chroma_format_idc; + + uint8 separate_colour_plane_flag; // aka residual_colour_transform_flag + + uint32 bit_depth_luma_minus8; + uint32 bit_depth_chroma_minus8; + uint8 qpprime_y_zero_transform_bypass_flag; + uint8 seq_scaling_matrix_present_flag; + h264_scaling_matrix4x4_t ScalingList4x4[6]; + h264_scaling_matrix8x8_t ScalingList8x8[6]; + + uint32 log2_max_frame_num_minus4; + uint32 pic_order_cnt_type; + + // picture order count type 0 + uint32 log2_max_pic_order_cnt_lsb_minus4; + + uint32 num_ref_frames; + uint32 gaps_in_frame_num_value_allowed_flag; + uint32 pic_width_in_mbs_minus1; + uint32 pic_height_in_map_units_minus1; + uint8 frame_mbs_only_flag; + uint8 mb_adaptive_frame_field_flag; + + uint8 direct_8x8_inference_flag; + + uint8 frame_cropping_flag; + uint32 frame_crop_left_offset; + uint32 frame_crop_right_offset; + uint32 frame_crop_top_offset; + uint32 frame_crop_bottom_offset; + + uint32 getMaxFrameNum() const + { + return 1<<(log2_max_frame_num_minus4+4); + } + +}; + +struct H264SliceType +{ + H264SliceType() {}; + H264SliceType(uint32 slice_type) : m_sliceType(slice_type) {}; + + bool isSliceTypeP() const { return (this->m_sliceType % 5) == 0; }; + bool isSliceTypeB() const { return (this->m_sliceType % 5) == 1; }; + bool isSliceTypeI() const { return (this->m_sliceType % 5) == 2; }; + bool isSliceTypeSP() const { return (this->m_sliceType % 5) == 3; }; + bool isSliceTypeSI() const { return (this->m_sliceType % 5) == 4; }; + +private: + uint32 m_sliceType{}; +}; + +typedef struct +{ + uint8 nal_ref_idc; + uint8 nal_unit_type; + + uint32 first_mb_in_slice; + H264SliceType slice_type; + uint32 pic_parameter_set_id; + + uint32 frame_num; + + uint8 field_pic_flag; + uint8 bottom_field_flag; + + uint32 idr_pic_id; + + uint32 pic_order_cnt_lsb; + sint32 delta_pic_order_cnt_bottom; + + uint32 redundant_pic_cnt; + + // slice type B + uint8 direct_spatial_mv_pred_flag; + + // slice type P, SP, B + uint8 num_ref_idx_active_override_flag; + uint32 num_ref_idx_l0_active_minus1; + uint32 num_ref_idx_l1_active_minus1; + + // ref_pic_list_modification_flag_l0 + struct + { + uint8 type; + uint32 abs_diff_pic_num_minus1; + uint32 long_term_pic_num; + }pic_list_modification0Array[32]; + sint32 pic_list_modification0Count; + + // ref_pic_list_modification_flag_l1 + struct + { + uint8 type; + uint32 abs_diff_pic_num_minus1; + uint32 long_term_pic_num; + }pic_list_modification1Array[32]; + sint32 pic_list_modification1Count; + + // memory_management_control_operation + enum + { + MEMOP_END = 0, + MEMOP_REMOVE_REF_FROM_SHORT_TERM = 1, + MEMOP_REMOVE_REF_FROM_LONG_TERM = 2, + MEMOP_MAKE_LONG_TERM_REF = 3, + MEMOP_MAX_LONG_TERM_INDEX = 4, + MEMOP_REMOVE_ALL_REFS = 5, + MEMOP_MAKE_CURRENT_LONG_TERM_REF = 6 + }; + + uint8 adaptive_ref_pic_marking_mode_flag; + struct + { + uint8 op; + uint32 difference_of_pic_nums_minus1; + uint32 long_term_pic_num; + uint32 long_term_frame_idx; + uint32 max_long_term_frame_idx_plus1; + }memory_management_control_operation[16]; + sint32 memory_management_control_operation_num; + + // derived values + uint8 IdrPicFlag; + + struct + { + uint32 TopFieldOrderCnt; + }calculated; +}nal_slice_header_t; + +typedef struct +{ + sint32 streamSubOffset; + sint32 streamSubSize; + nal_slice_header_t header; +}nal_slice_info_t; + +typedef struct +{ + bool hasSPS; + bool hasPPS; + // list of NAL slices + sint32 sliceCount; + std::array<nal_slice_info_t, 32> sliceInfo; +}h264ParserOutput_t; + +typedef struct +{ + nal_slice_header_t slice_header; +}nal_slice_t; + +typedef struct +{ + //static const int MAX_SLICES = 32; + bool hasSPS; + bool hasPPS; + h264State_seq_parameter_set_t sps; + h264State_pic_parameter_set_t pps; + struct + { + uint32 prevPicOrderCntMsb; + uint32 prevPicOrderCntLsb; + uint32 prevFrameNum; + uint32 prevFrameNumOffset; + }picture_order; +}h264ParserState_t; + +void h264Parse(h264ParserState_t* h264ParserState, h264ParserOutput_t* output, uint8* data, uint32 length, bool parseSlices = true); +sint32 h264GetUnitLength(h264ParserState_t* h264ParserState, uint8* data, uint32 length); + +void h264Parser_getScalingMatrix4x4(h264State_seq_parameter_set_t* sps, h264State_pic_parameter_set_t* pps, nal_slice_header_t* sliceHeader, sint32 index, uint8* matrix4x4); +void h264Parser_getScalingMatrix8x8(h264State_seq_parameter_set_t* sps, h264State_pic_parameter_set_t* pps, nal_slice_header_t* sliceHeader, sint32 index, uint8* matrix8x8); + +static sint32 h264GetFramePitch(sint32 width) +{ + return (width+0xFF)&~0xFF; +} + +static sint32 h264GetFrameSize(sint32 width, sint32 height) +{ + sint32 pitch = h264GetFramePitch(width); + return height * pitch + (height / 2) * pitch; +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/mic/mic.cpp b/src/Cafe/OS/libs/mic/mic.cpp new file mode 100644 index 00000000..4bc6a969 --- /dev/null +++ b/src/Cafe/OS/libs/mic/mic.cpp @@ -0,0 +1,405 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "input/InputManager.h" + +enum class MIC_RESULT +{ + SUCCESS = 0, + BAD_PARAM = -2, + ALREADY_OPEN = -5, + NOT_OPEN = -6, + NOT_INITIALIZED = -7, + NOT_CONNECTED = -8 +}; + +#define MIC_SAMPLERATE 32000 + + +enum class MIC_STATUS_FLAGS : uint32 +{ + FORMAT_PCM16 = (1 << 0), + IS_OPEN = (1 << 1), + IS_CONNECTED = (1 << 2) +}; +DEFINE_ENUM_FLAG_OPERATORS(MIC_STATUS_FLAGS); + +#define MIC_HANDLE_DRC0 100 +#define MIC_HANDLE_DRC1 101 + +enum class MIC_STATEID +{ + SAMPLERATE = 0, + GAIN_DB = 1, + GAIN_MIN = 2, + GAIN_MAX = 3, + GAIN_STEP = 4, + MUTE = 5, + ECHO_CANCELLATION = 7, + AUTO_SELECTION = 8 +}; + +struct +{ + struct + { + bool isInited; + bool isOpen; + // ringbuffer information + void* ringbufferSampleData; + uint32 ringbufferSize; + uint32 readIndex; + uint32 writeIndex; + // state + uint32 echoCancellation; + uint32 autoSelection; + uint32 gainDB; + }drc[2]; +}MICStatus = {0}; + +typedef struct +{ + uint32 size; + MPTR samples; +}micRingbuffer_t; + +struct micStatus_t +{ + betype<MIC_STATUS_FLAGS> flags; + uint32be numSamplesAvailable; + uint32be readIndex; +}; + +static_assert(sizeof(micStatus_t) == 0xC); + +bool mic_isConnected(uint32 drcIndex) +{ + if( drcIndex != 0 ) + return false; + + return InputManager::instance().get_vpad_controller(drcIndex) != nullptr; +} + +sint32 mic_availableSamples(uint32 drcIndex) +{ + return (MICStatus.drc[drcIndex].ringbufferSize+MICStatus.drc[drcIndex].writeIndex-MICStatus.drc[drcIndex].readIndex) % MICStatus.drc[drcIndex].ringbufferSize; +} + +bool mic_isActive(uint32 drcIndex) +{ + return MICStatus.drc[drcIndex].isOpen; +} + +void mic_feedSamples(uint32 drcIndex, sint16* sampleData, sint32 numSamples) +{ + uint16* sampleDataU16 = (uint16*)sampleData; + sint32 ringBufferSize = MICStatus.drc[0].ringbufferSize; + sint32 writeIndex = MICStatus.drc[0].writeIndex; + uint16* ringBufferBase = (uint16*)MICStatus.drc[0].ringbufferSampleData; + do + { + ringBufferBase[writeIndex] = _swapEndianU16(*sampleDataU16); + sampleDataU16++; + writeIndex++; + writeIndex %= ringBufferSize; + }while( (--numSamples) > 0 ); + MICStatus.drc[0].writeIndex = writeIndex; +} + +void micExport_MICInit(PPCInterpreter_t* hCPU) +{ + debug_printf("MICInit(%d,%d,0x%08x,0x%08x)\n", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6]); + uint32 drcIndex = hCPU->gpr[3]; + if( drcIndex > 1 || (drcIndex == 0 && mic_isConnected(drcIndex) == false) ) + { + memory_writeU32(hCPU->gpr[6], (uint32)MIC_RESULT::NOT_CONNECTED); + osLib_returnFromFunction(hCPU, -1); + return; + } + if( drcIndex != 0 ) + { + // DRC1 microphone is not supported (only DRC0) + memory_writeU32(hCPU->gpr[6], (uint32)MIC_RESULT::NOT_CONNECTED); + osLib_returnFromFunction(hCPU, -1); + return; + } + if( MICStatus.drc[drcIndex].isInited ) + { + memory_writeU32(hCPU->gpr[6], (uint32)MIC_RESULT::ALREADY_OPEN); + osLib_returnFromFunction(hCPU, -1); + return; + } + micRingbuffer_t* micRingbuffer = (micRingbuffer_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[5]); + MICStatus.drc[drcIndex].ringbufferSampleData = memory_getPointerFromVirtualOffset(_swapEndianU32(micRingbuffer->samples)); + MICStatus.drc[drcIndex].ringbufferSize = _swapEndianU32(micRingbuffer->size); + MICStatus.drc[drcIndex].readIndex = 0; + MICStatus.drc[drcIndex].writeIndex = 0; + MICStatus.drc[drcIndex].isInited = true; + // init default states + MICStatus.drc[drcIndex].echoCancellation = 1; // guessed + MICStatus.drc[drcIndex].autoSelection = 1; // guessed + // return status + memory_writeU32(hCPU->gpr[6], 0); // no error + osLib_returnFromFunction(hCPU, (drcIndex==0)?MIC_HANDLE_DRC0:MIC_HANDLE_DRC1); // success +} + +void micExport_MICOpen(PPCInterpreter_t* hCPU) +{ + debug_printf("MICOpen(%d)\n", hCPU->gpr[3]); + uint32 micHandle = hCPU->gpr[3]; + if( micHandle != MIC_HANDLE_DRC0 && micHandle != MIC_HANDLE_DRC1 ) + { + osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::BAD_PARAM); + return; + } + uint32 drcIndex = (micHandle==MIC_HANDLE_DRC0)?0:1; + if( MICStatus.drc[drcIndex].isInited == false ) + { + osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::NOT_INITIALIZED); + return; + } + // check if already open + if( MICStatus.drc[drcIndex].isOpen ) + { + osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::ALREADY_OPEN); + return; + } + // check if DRC is connected + bool hasDRCConnected = InputManager::instance().get_vpad_controller(drcIndex) != nullptr; + if( hasDRCConnected == false ) + { + osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::NOT_CONNECTED); + return; + } + // success + MICStatus.drc[drcIndex].isOpen = true; + osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::SUCCESS); +} + +void micExport_MICClose(PPCInterpreter_t* hCPU) +{ + // debug_printf("MICClose(%d)\n", hCPU->gpr[3]); + uint32 micHandle = hCPU->gpr[3]; + if( micHandle != MIC_HANDLE_DRC0 && micHandle != MIC_HANDLE_DRC1 ) + { + osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::BAD_PARAM); + return; + } + uint32 drcIndex = (micHandle==MIC_HANDLE_DRC0)?0:1; + if( MICStatus.drc[drcIndex].isInited == false ) + { + osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::NOT_INITIALIZED); + return; + } + // check if already closed + if( MICStatus.drc[drcIndex].isOpen == false ) + { + osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::ALREADY_OPEN); + return; + } + // success + MICStatus.drc[drcIndex].isOpen = false; + osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::SUCCESS); +} + +void micExport_MICGetStatus(PPCInterpreter_t* hCPU) +{ + debug_printf("MICGetStatus(%d,0x%08x)\n", hCPU->gpr[3], hCPU->gpr[4]); + uint32 micHandle = hCPU->gpr[3]; + if( micHandle != MIC_HANDLE_DRC0 && micHandle != MIC_HANDLE_DRC1 ) + { + osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::BAD_PARAM); + return; + } + uint32 drcIndex = (micHandle==MIC_HANDLE_DRC0)?0:1; + if( MICStatus.drc[drcIndex].isInited == false ) + { + osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::NOT_INITIALIZED); + return; + } + micStatus_t* micStatus = (micStatus_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[4]); + MIC_STATUS_FLAGS micFlags = MIC_STATUS_FLAGS::FORMAT_PCM16; + if( mic_isConnected(drcIndex) ) + micFlags |= MIC_STATUS_FLAGS::IS_CONNECTED; + if( MICStatus.drc[drcIndex].isOpen ) + micFlags |= MIC_STATUS_FLAGS::IS_OPEN; + micStatus->flags = micFlags; + micStatus->numSamplesAvailable = mic_availableSamples(drcIndex); + micStatus->readIndex = MICStatus.drc[drcIndex].readIndex; + osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::SUCCESS); +} + +void micExport_MICGetState(PPCInterpreter_t* hCPU) +{ + // debug_printf("MICGetState(%d,%d,0x%08x)\n", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); + // parameters: + // r3 uint32 micHandle + // r4 uint32 stateId + // r5 uint32* value + uint32 micHandle = hCPU->gpr[3]; + if( micHandle != MIC_HANDLE_DRC0 && micHandle != MIC_HANDLE_DRC1 ) + { + osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::BAD_PARAM); + return; + } + uint32 drcIndex = (micHandle==MIC_HANDLE_DRC0)?0:1; + if( MICStatus.drc[drcIndex].isInited == false ) + { + osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::NOT_INITIALIZED); + return; + } + // get state + MIC_STATEID stateId = (MIC_STATEID)hCPU->gpr[4]; + if (stateId == MIC_STATEID::SAMPLERATE) + { + memory_writeU32(hCPU->gpr[5], MIC_SAMPLERATE); + } + else if (stateId == MIC_STATEID::AUTO_SELECTION) + { + memory_writeU32(hCPU->gpr[5], MICStatus.drc[drcIndex].autoSelection); + } + else if (stateId == MIC_STATEID::GAIN_MIN) + { + // value guessed + memory_writeU32(hCPU->gpr[5], 0x0000); // S7.8 + } + else if (stateId == MIC_STATEID::GAIN_MAX) + { + // value guessed + memory_writeU32(hCPU->gpr[5], 0x0200); // S7.8 + } + else if (stateId == MIC_STATEID::GAIN_STEP) + { + // value guessed + memory_writeU32(hCPU->gpr[5], 0x0001); // S7.8 + } + else if (stateId == MIC_STATEID::ECHO_CANCELLATION) + { + memory_writeU32(hCPU->gpr[5], MICStatus.drc[drcIndex].echoCancellation); + } + else if (stateId == MIC_STATEID::GAIN_DB) + { + // value guessed + memory_writeU32(hCPU->gpr[5], MICStatus.drc[drcIndex].gainDB); // S7.8 + } + else + cemu_assert_unimplemented(); + osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::SUCCESS); + return; +} + +void micExport_MICSetState(PPCInterpreter_t* hCPU) +{ + uint32 micHandle = hCPU->gpr[3]; + if( micHandle != MIC_HANDLE_DRC0 && micHandle != MIC_HANDLE_DRC1 ) + { + osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::BAD_PARAM); + return; + } + uint32 drcIndex = (micHandle==MIC_HANDLE_DRC0)?0:1; + if( MICStatus.drc[drcIndex].isInited == false ) + { + osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::NOT_INITIALIZED); + return; + } + // set state + MIC_STATEID stateId = (MIC_STATEID)hCPU->gpr[4]; + uint32 newValue = hCPU->gpr[5]; + if( stateId == MIC_STATEID::ECHO_CANCELLATION ) + { + MICStatus.drc[drcIndex].echoCancellation = (newValue!=0)?1:0; + } + else if( stateId == MIC_STATEID::AUTO_SELECTION ) + { + MICStatus.drc[drcIndex].autoSelection = (newValue!=0)?1:0; + } + else if( stateId == MIC_STATEID::GAIN_DB ) + { + MICStatus.drc[drcIndex].gainDB = newValue; + } + else + assert_dbg(); + osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::SUCCESS); + return; +} + +void micExport_MICSetDataConsumed(PPCInterpreter_t* hCPU) +{ + // debug_printf("MICSetDataConsumed(%d,%d)\n", hCPU->gpr[3], hCPU->gpr[4]); + // parameters: + // r3 uint32 micHandle + // r4 uint32 numConsumedSamples + uint32 micHandle = hCPU->gpr[3]; + if( micHandle != MIC_HANDLE_DRC0 && micHandle != MIC_HANDLE_DRC1 ) + { + osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::BAD_PARAM); + return; + } + uint32 drcIndex = (micHandle==MIC_HANDLE_DRC0)?0:1; + if( MICStatus.drc[drcIndex].isInited == false ) + { + osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::NOT_INITIALIZED); + return; + } + if( MICStatus.drc[drcIndex].isOpen == false ) + { + osLib_returnFromFunction(hCPU, (uint32)MIC_RESULT::NOT_OPEN); + return; + } + sint32 numConsumedSamples = (sint32)hCPU->gpr[4]; + //debug_printf("MIC consume samples 0x%04x\n", numConsumedSamples); + if( mic_availableSamples(drcIndex) < numConsumedSamples ) + { + MICStatus.drc[drcIndex].readIndex = MICStatus.drc[drcIndex].writeIndex; + osLib_returnFromFunction(hCPU, -81); + } + else + { + MICStatus.drc[drcIndex].readIndex += numConsumedSamples; + MICStatus.drc[drcIndex].readIndex %= MICStatus.drc[drcIndex].ringbufferSize; + osLib_returnFromFunction(hCPU, 0); + } + return; +} + +void mic_updateOnAXFrame() +{ + sint32 drcIndex = 0; + if( mic_isActive(0) == false ) + { + drcIndex = 1; + if (mic_isActive(1) == false) + { + return; + } + } + + const sint32 micSampleCount = 32000/32; + sint16 micSampleData[micSampleCount]; + + auto controller = InputManager::instance().get_vpad_controller(drcIndex); + if( controller && controller->is_mic_active() ) + { + for(sint32 i=0; i<micSampleCount; i++) + { + micSampleData[i] = (sint16)(sin((float)GetTickCount()*0.1f+sin((float)GetTickCount()*0.0001f)*100.0f)*30000.0f); + } + } + else + { + memset(micSampleData, 0x00, sizeof(micSampleData)); + } + mic_feedSamples(0, micSampleData, micSampleCount); +} + +namespace mic +{ + void Initialize() + { + osLib_addFunction("mic", "MICInit", micExport_MICInit); + osLib_addFunction("mic", "MICOpen", micExport_MICOpen); + osLib_addFunction("mic", "MICClose", micExport_MICClose); + osLib_addFunction("mic", "MICGetStatus", micExport_MICGetStatus); + osLib_addFunction("mic", "MICGetState", micExport_MICGetState); + osLib_addFunction("mic", "MICSetState", micExport_MICSetState); + osLib_addFunction("mic", "MICSetDataConsumed", micExport_MICSetDataConsumed); + } +}; \ No newline at end of file diff --git a/src/Cafe/OS/libs/mic/mic.h b/src/Cafe/OS/libs/mic/mic.h new file mode 100644 index 00000000..87843a9f --- /dev/null +++ b/src/Cafe/OS/libs/mic/mic.h @@ -0,0 +1,7 @@ +bool mic_isActive(uint32 drcIndex); +void mic_updateOnAXFrame(); + +namespace mic +{ + void Initialize(); +}; \ No newline at end of file diff --git a/src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp b/src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp new file mode 100644 index 00000000..4208c1ff --- /dev/null +++ b/src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp @@ -0,0 +1,1375 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/HW/Espresso/PPCCallback.h" +#include "nlibcurl.h" + +#include "openssl/bn.h" +#include "openssl/x509.h" +#include "openssl/ssl.h" + +#include "curl/curl.h" +#include <unordered_map> +#include <atomic> + +#include "Cafe/IOSU/legacy/iosu_crypto.h" +#include "Cafe/OS/libs/nsysnet/nsysnet.h" +#include "Cafe/OS/libs/coreinit/coreinit_MEM.h" + +#include "util/helpers/ConcurrentQueue.h" +#include "Cafe/OS/common/PPCConcurrentQueue.h" +#include "Common/filestream.h" +#include "config/ActiveSettings.h" + +namespace nlibcurl +{ +#define CURL_MULTI_HANDLE (0x000bab1e) + +#define NSSL_VERIFY_NONE (0x0) +#define NSSL_VERIFY_PEER (1<<0) +#define NSSL_VERIFY_HOSTNAME (1<<1) +#define NSSL_VERIFY_DATE (1<<2) + + enum QueueOrder + { + QueueOrder_None, + + QueueOrder_Result, + QueueOrder_CBDone, + + QueueOrder_HeaderCB, + QueueOrder_ReadCB, + QueueOrder_WriteCB, + QueueOrder_ProgressCB, + + QueueOrder_Perform, + QueueOrder_Pause, + }; + + struct QueueMsg_t + { + QueueOrder order; + union + { + uint32 result; + + struct + { + char* buffer; + uint32 size; + uint32 nitems; + } header_cb; + + struct + { + char* buffer; + uint32 size; + uint32 nitems; + } read_cb; + + struct + { + char* buffer; + uint32 size; + uint32 nmemb; + } write_cb; + + struct + { + uint32 clientp; + double dltotal; + double dlnow; + double ultotal; + double ulnow; + } progress_cb; + + struct + { + sint32 bitmask; + } pause; + + }; + + }; + +struct MEMPTRHash_t +{ + size_t operator()(const MEMPTR<void>& p) const + { + return p.GetMPTR(); + } +}; + +struct +{ + sint32 initialized; + + MEMPTR<void> proxyConfig; + MEMPTR<curl_malloc_callback> malloc; + MEMPTR<curl_free_callback> free; + MEMPTR<curl_realloc_callback> realloc; + MEMPTR<curl_strdup_callback> strdup; + MEMPTR<curl_calloc_callback> calloc; +} g_nlibcurl = {}; + + +#pragma pack(1) + +struct CURL_t +{ + CURL* curl; + + uint32be hNSSL; + uint32be nsslVerifyOptions; + MEMPTR<void> out; // CURLOPT_WRITEDATA + MEMPTR<void> in_set; // CURLOPT_READDATA + MEMPTR<void> writeheader; // CURLOPT_HEADERDATA + + MEMPTR<void> fwrite_func; // CURLOPT_WRITEFUNCTION + MEMPTR<void> fwrite_header; // CURLOPT_HEADERFUNCTION + MEMPTR<void> fread_func_set; // CURLOPT_READFUNCTION + + MEMPTR<void> progress_client; // CURLOPT_PROGRESSDATA + MEMPTR<void> fprogress; // CURLOPT_PROGRESSFUNCTION + + MEMPTR<void> fsockopt; // CURLOPT_SOCKOPTFUNCTION + MEMPTR<void> sockopt_client; // CURLOPT_SOCKOPTDATA + + // Cemu specific + OSThread_t* curlThread; + MEMPTR<char> info_redirectUrl; // stores CURLINFO_REDIRECT_URL ptr + MEMPTR<char> info_contentType; // stores CURLINFO_CONTENT_TYPE ptr + + // debug + struct + { + uint32 activeRequestIndex{}; + uint32 responseRequestIndex{}; // set to activeRequestIndex once perform is called + bool hasDumpedResultInfo{}; // set once the response info has been dumped (reset when next request is performed) + FileStream* file_requestParam{}; + FileStream* file_responseHeaders{}; + FileStream* file_responseRaw{}; + }debug; + +}; +static_assert(sizeof(CURL_t) <= 0x8698); +typedef MEMPTR<CURL_t> CURLPtr; + +typedef struct +{ + //uint32be specifier; // 0x00 + //uint32be dirty; // 0x04 + MEMPTR<void> lockfunc; // 0x08 + MEMPTR<void> unlockfunc; // 0x0C + MEMPTR<void> data; // 0x10 + //MEMPTR<void> uk14; // 0x14 + //MEMPTR<void> uk18; // 0x18 + + CURLSH* curlsh; + MEMPTR <CURL_t> curl; + uint32 uk1; +}CURLSH_t; // SCURL +static_assert(sizeof(CURLSH_t) <= 0x1C); +typedef MEMPTR<CURLSH_t> CURLSHPtr; + +typedef struct +{ + CURLM* curlm; + std::vector< MEMPTR<CURL> > curl; +}CURLM_t; +static_assert(sizeof(CURLM_t) <= 0x80, "sizeof(CURLM_t)"); +typedef MEMPTR<CURLM_t> CURLMPtr; + +struct curl_slist_t +{ + MEMPTR<char> data; + MEMPTR<curl_slist_t> next; +}; + +static_assert(sizeof(curl_slist_t) <= 0x8, "sizeof(curl_slist_t)"); + +struct CURLMsg_t +{ + uint32be msg; + MEMPTR<CURL_t> curl; + uint32be result; // CURLcode +}; + +static_assert(sizeof(CURLMsg_t) <= 0xC, "sizeof(CURLMsg_t)"); + +#pragma pack() + +#include "nlibcurlDebug.hpp" + +size_t header_callback(char* buffer, size_t size, size_t nitems, void* userdata); + +__declspec(thread) PPCConcurrentQueue<QueueMsg_t>* g_callerQueue; +__declspec(thread) ConcurrentQueue<QueueMsg_t>* g_threadQueue; +void CurlWorkerThread(CURL_t* curl, PPCConcurrentQueue<QueueMsg_t>* callerQueue, ConcurrentQueue<QueueMsg_t>* threadQueue) +{ + g_callerQueue = callerQueue; + g_threadQueue = threadQueue; + + const QueueMsg_t msg = threadQueue->pop(); + + QueueMsg_t resultMsg = {}; + resultMsg.order = QueueOrder_Result; + + if (msg.order == QueueOrder_Perform) + resultMsg.result = ::curl_easy_perform(curl->curl); + else if(msg.order == QueueOrder_Pause) + resultMsg.result = ::curl_easy_pause(curl->curl, msg.pause.bitmask); + else + assert_dbg(); + + callerQueue->push(resultMsg, curl->curlThread); +} + +uint32 SendOrderToWorker(CURL_t* curl, QueueOrder order, uint32 arg1 = 0) +{ + OSThread_t* currentThread = coreinitThread_getCurrentThreadDepr(ppcInterpreterCurrentInstance); + curl->curlThread = currentThread; + + // forceLogDebug_printf("CURRENTTHREAD: 0x%p -> %d",currentThread, order) + + PPCConcurrentQueue<QueueMsg_t> callerQueue; + ConcurrentQueue<QueueMsg_t> threadQueue; + std::thread worker(CurlWorkerThread, curl, &callerQueue, &threadQueue); + worker.detach(); + + QueueMsg_t orderMsg = {}; + orderMsg.order = order; + + if (order == QueueOrder_Pause) + orderMsg.pause.bitmask = arg1; + + threadQueue.push(orderMsg); + + uint32 result; + while (true) + { + const QueueMsg_t msg = callerQueue.pop(currentThread); + if (msg.order == QueueOrder_ProgressCB) + { + QueueMsg_t sendMsg = {}; + sendMsg.order = QueueOrder_CBDone; + sendMsg.result = PPCCoreCallback(curl->fprogress.GetMPTR(), curl->progress_client.GetMPTR(), msg.progress_cb.dltotal, msg.progress_cb.dlnow, msg.progress_cb.ultotal, msg.progress_cb.ulnow); + threadQueue.push(sendMsg); + } + else if (msg.order == QueueOrder_HeaderCB) + { + StackAllocator<char> tmp(msg.header_cb.size * msg.header_cb.nitems); + memcpy(tmp.GetPointer(), msg.header_cb.buffer, msg.header_cb.size * msg.header_cb.nitems); + + QueueMsg_t sendMsg = {}; + sendMsg.order = QueueOrder_CBDone; + sendMsg.result = PPCCoreCallback(curl->fwrite_header.GetMPTR(), tmp.GetMPTR(), msg.header_cb.size, msg.header_cb.nitems, curl->writeheader.GetMPTR()); + threadQueue.push(sendMsg); + } + else if (msg.order == QueueOrder_ReadCB) + { + StackAllocator<char> tmp(msg.read_cb.size * msg.read_cb.nitems); + + QueueMsg_t sendMsg = {}; + sendMsg.order = QueueOrder_CBDone; + forceLogDebug_printf("QueueOrder_ReadCB size: %d nitems: %d", msg.read_cb.size, msg.read_cb.nitems); + sendMsg.result = PPCCoreCallback(curl->fread_func_set.GetMPTR(), tmp.GetMPTR(), msg.read_cb.size, msg.read_cb.nitems, curl->in_set.GetMPTR()); + forceLogDebug_printf("readcb size: %d", (sint32)sendMsg.result); + if (sendMsg.result > 0) + memcpy(msg.read_cb.buffer, tmp.GetPointer(), sendMsg.result); + + threadQueue.push(sendMsg); + } + else if (msg.order == QueueOrder_WriteCB) + { + StackAllocator<char> tmp(msg.write_cb.size * msg.write_cb.nmemb); + memcpy(tmp.GetPointer(), msg.write_cb.buffer, msg.write_cb.size * msg.write_cb.nmemb); + + QueueMsg_t sendMsg = {}; + sendMsg.order = QueueOrder_CBDone; + sendMsg.result = PPCCoreCallback(curl->fwrite_func.GetMPTR(), tmp.GetMPTR(), msg.write_cb.size, msg.write_cb.nmemb, curl->out.GetMPTR()); + threadQueue.push(sendMsg); + } + else if (msg.order == QueueOrder_Result) + { + result = msg.result; + break; + } + } + + return result; +} + +void export_malloc(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(size, 0); + MPTR memAddr = PPCCoreCallback(gCoreinitData->MEMAllocFromDefaultHeap, size); + osLib_returnFromFunction(hCPU, memAddr); +} + +void export_calloc(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(count, 0); + ppcDefineParamU32(size, 1); + + MPTR memAddr = PPCCoreCallback(gCoreinitData->MEMAllocFromDefaultHeap, count*size); + + osLib_returnFromFunction(hCPU, memAddr); +} +void export_free(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(mem, void, 0); + PPCCoreCallback(gCoreinitData->MEMFreeToDefaultHeap, mem.GetMPTR()); + + osLib_returnFromFunction(hCPU, 0); +} +void export_strdup(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(str, const char, 0); + int len = (int)strlen(str.GetPtr()) + 1; + MEMPTR<char> result = (char*)coreinit::default_MEMAllocFromDefaultHeap(len); + strcpy(result.GetPtr(), str.GetPtr()); + osLib_returnFromFunction(hCPU, result.GetMPTR()); +} + +void export_realloc(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(mem, void, 0); + ppcDefineParamU32(size, 1); + MEMPTR<void> result = coreinit::default_MEMAllocFromDefaultHeap(size); + memcpy(result.GetPtr(), mem.GetPtr(), size); + coreinit::default_MEMFreeToDefaultHeap(mem.GetPtr()); + osLib_returnFromFunction(hCPU, result.GetMPTR()); +} + + +CURLcode curl_global_init(uint32 flags) +{ + if (g_nlibcurl.initialized++) + { + return CURLE_OK; + } + + g_nlibcurl.malloc = PPCInterpreter_makeCallableExportDepr(export_malloc); + g_nlibcurl.calloc = PPCInterpreter_makeCallableExportDepr(export_calloc); + g_nlibcurl.free = PPCInterpreter_makeCallableExportDepr(export_free); + g_nlibcurl.strdup = PPCInterpreter_makeCallableExportDepr(export_strdup); + g_nlibcurl.realloc = PPCInterpreter_makeCallableExportDepr(export_realloc); + + // curl_read_default_proxy_config() + + return ::curl_global_init(flags); +} + +void export_curl_multi_init(PPCInterpreter_t* hCPU) +{ + CURLMPtr result{ PPCCoreCallback(g_nlibcurl.calloc, 1, ppcsizeof<CURLM_t>()) }; + forceLogDebug_printf("curl_multi_init() -> 0x%08x", result.GetMPTR()); + if (result) + { + memset(result.GetPtr(), 0, sizeof(CURLM_t)); + *result = {}; + result->curlm = curl_multi_init(); + } + + osLib_returnFromFunction(hCPU, result.GetMPTR()); +} + +void export_curl_multi_add_handle(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(curlm, CURLM_t, 0); + ppcDefineParamMEMPTR(curl, CURL_t, 1); + + curlDebug_markActiveRequest(curl.GetPtr()); + curlDebug_notifySubmitRequest(curl.GetPtr()); + + CURLMcode result = ::curl_multi_add_handle(curlm->curlm, curl->curl); + if (result == CURLM_OK) + curlm->curl.push_back(curl.GetPtr()); + + forceLogDebug_printf("curl_multi_add_handle(0x%08x, 0x%08x) -> 0x%x", curlm.GetMPTR(), curl.GetMPTR(), result); + osLib_returnFromFunction(hCPU, result); +} + +void export_curl_multi_remove_handle(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(curlm, CURLM_t, 0); + ppcDefineParamMEMPTR(curl, CURL_t, 1); + + CURLMcode result = curl_multi_remove_handle(curlm->curlm, curl->curl); + + if (result == CURLM_OK) + { + curlm->curl.erase(std::remove(curlm->curl.begin(), curlm->curl.end(), (void*)curl.GetPtr()), curlm->curl.end()); + } + + forceLogDebug_printf("curl_multi_remove_handle(0x%08x, 0x%08x) -> 0x%x", curlm.GetMPTR(), curl.GetMPTR(), result); + osLib_returnFromFunction(hCPU, result); +} + +void export_curl_multi_timeout(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(curlm, CURLM_t, 0); + ppcDefineParamMEMPTR(timeoParam, uint32be, 1); + + long timeoutLE = (long)(uint32)*timeoParam; + + CURLMcode result = ::curl_multi_timeout(curlm->curlm, &timeoutLE); + *timeoParam = (uint32)timeoutLE; + + forceLogDebug_printf("curl_multi_timeout(0x%08x, 0x%08x [%d]) -> 0x%x", curlm.GetMPTR(), timeoParam.GetMPTR(), timeoutLE, result); + osLib_returnFromFunction(hCPU, result); +} + +void export_curl_multi_cleanup(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(curlm, CURLM_t, 0); + CURLMcode result = ::curl_multi_cleanup(curlm->curlm); + forceLogDebug_printf("curl_multi_cleanup(0x%08x) -> 0x%x", curlm.GetMPTR(), result); + curlm->curl.clear(); + PPCCoreCallback(g_nlibcurl.free.GetMPTR(), curlm.GetMPTR()); + osLib_returnFromFunction(hCPU, result); +} + +void export_curl_multi_perform(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(curlm, CURLM_t, 0); + ppcDefineParamMEMPTR(runningHandles, uint32be, 1); + + //forceLogDebug_printf("curl_multi_perform(0x%08x, 0x%08x)", curlm.GetMPTR(), runningHandles.GetMPTR()); + + //g_callerQueue = curlm->callerQueue; + //g_threadQueue = curlm->threadQueue; + int tempRunningHandles = 0; + CURLMcode result = curl_multi_perform(curlm->curlm, &tempRunningHandles); + *(runningHandles.GetPtr()) = tempRunningHandles; + forceLogDebug_printf("curl_multi_perform(0x%08x, 0x%08x) -> 0x%x (running handles: %d) LR: 0x%08x", curlm.GetMPTR(), runningHandles.GetMPTR(), result, tempRunningHandles, hCPU->spr.LR); + //const uint32 result = SendOrderToWorker(curlm.GetPtr(), QueueOrder_MultiPerform); + + osLib_returnFromFunction(hCPU, result); +} + +void export_curl_multi_fdset(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(curlm, CURLM_t, 0); + ppcDefineParamMEMPTR(readFd, wu_fd_set, 1); + ppcDefineParamMEMPTR(writeFd, wu_fd_set, 2); + ppcDefineParamMEMPTR(exceptionFd, wu_fd_set, 3); + ppcDefineParamU32BEPtr(maxFd, 4); + +#if BOOST_OS_LINUX > 0 + cemuLog_log(LogType::Force, "curl_multi_fdset(...) - todo"); + + osLib_returnFromFunction(hCPU, 0); +#else + fd_set h_readFd; + fd_set h_writeFd; + fd_set h_exceptionFd; + FD_ZERO(&h_readFd); + FD_ZERO(&h_writeFd); + FD_ZERO(&h_exceptionFd); + sint32 h_maxFd = 0; + + CURLMcode result = curl_multi_fdset(curlm->curlm, &h_readFd, &h_writeFd, &h_exceptionFd, &h_maxFd); + + nsysnet::wuResetFD(readFd.GetPtr()); + nsysnet::wuResetFD(writeFd.GetPtr()); + nsysnet::wuResetFD(exceptionFd.GetPtr()); + + sint32 c_maxFD = -1; + // fd read set + for (uint32 i = 0; i < h_readFd.fd_count; i++) + { + sint32 wuSocket = nsysnet_getVirtualSocketHandleFromHostHandle(h_readFd.fd_array[i]); + if (wuSocket < 0) + wuSocket = nsysnet_createVirtualSocketFromExistingSocket(h_readFd.fd_array[i]); + if (wuSocket >= 0) + { + c_maxFD = std::max(wuSocket, c_maxFD); + nsysnet::wuSetFD(readFd.GetPtr(), wuSocket); + } + } + // fd write set + for (uint32 i = 0; i < h_writeFd.fd_count; i++) + { + cemu_assert_debug(false); + } + // fd exception set + for (uint32 i = 0; i < h_exceptionFd.fd_count; i++) + { + cemu_assert_debug(false); + } + + *maxFd = c_maxFD; + osLib_returnFromFunction(hCPU, result); +#endif + +} + +void export_curl_multi_setopt(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(curlm, CURLM_t, 0); + ppcDefineParamU32(option, 1); + ppcDefineParamMEMPTR(parameter, void, 2); + ppcDefineParamU64(parameterU64, 2); + + CURLMcode result = CURLM_OK; + switch (option) + { + case CURLMOPT_MAXCONNECTS: + result = ::curl_multi_setopt(curlm->curlm, (CURLMoption)option, parameter.GetMPTR()); + break; + default: + forceLogDebug_printf("curl_multi_setopt(0x%08x, %d, 0x%08x) unsupported option", curlm.GetMPTR(), option, parameter.GetMPTR()); + } + + forceLogDebug_printf("curl_multi_setopt(0x%08x, %d, 0x%08x) -> 0x%x", curlm.GetMPTR(), option, parameter.GetMPTR(), result); + + osLib_returnFromFunction(hCPU, result); +} + +void export_curl_multi_info_read(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(curlm, CURLM_t, 0); + ppcDefineParamMEMPTR(msgsInQueue, int, 1); + + CURLMsg* msg = ::curl_multi_info_read(curlm->curlm, msgsInQueue.GetPtr()); + + forceLogDebug_printf("curl_multi_info_read - todo"); + if (msg) + { + MEMPTR<CURLMsg_t> result{ PPCCoreCallback(g_nlibcurl.malloc.GetMPTR(), ppcsizeof<CURLMsg_t>()) }; + result->msg = msg->msg; + result->result = msg->data.result; + if (msg->easy_handle) + { + const auto it = find_if(curlm->curl.cbegin(), curlm->curl.cend(), + [msg](const MEMPTR<void>& curl) + { + const MEMPTR<CURL_t> _curl{ curl }; + return _curl->curl = msg->easy_handle; + }); + + if (it != curlm->curl.cend()) + result->curl = (CURL_t*)(*it).GetPtr(); + + } + else + result->curl = nullptr; + + forceLogDebug_printf("curl_multi_info_read(0x%08x, 0x%08x [%d]) -> 0x%08x (0x%x, 0x%08x, 0x%x)", curlm.GetMPTR(), msgsInQueue.GetMPTR(), *msgsInQueue.GetPtr(), + result.GetMPTR(), msg->msg, result->curl.GetMPTR(), msg->data.result); + osLib_returnFromFunction(hCPU, result.GetMPTR()); + } + else + { + osLib_returnFromFunction(hCPU, 0); + } +} + +void lock_function(CURL* handle, curl_lock_data data, curl_lock_access access, void* userptr) +{ + CURLSH_t* share = (CURLSH_t*)userptr; + PPCCoreCallback(share->lockfunc.GetMPTR(), share->curl.GetMPTR(), (uint32)data, (uint32)access, share->data.GetMPTR()); +} + +void unlock_function(CURL* handle, curl_lock_data data, void* userptr) +{ + CURLSH_t* share = (CURLSH_t*)userptr; + PPCCoreCallback(share->unlockfunc.GetMPTR(), share->curl.GetMPTR(), (uint32)data, share->data.GetMPTR()); +} + +void export_curl_share_setopt(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(share, CURLSH_t, 0); + ppcDefineParamU32(option, 1); + ppcDefineParamMEMPTR(parameter, void, 2); + + /*if(share->dirty) + { + osLib_returnFromFunction(hCPU, CURLSHE_IN_USE); + return; + }*/ + + CURLSH* curlSh = share->curlsh; + + CURLSHcode result = CURLSHE_OK; + switch (option) + { + case CURLSHOPT_USERDATA: + share->data = parameter; + result = curl_share_setopt(curlSh, CURLSHOPT_USERDATA, share.GetPtr()); + break; + case CURLSHOPT_LOCKFUNC: + share->lockfunc = parameter; + result = curl_share_setopt(curlSh, CURLSHOPT_LOCKFUNC, lock_function); + break; + case CURLSHOPT_UNLOCKFUNC: + share->unlockfunc = parameter; + result = curl_share_setopt(curlSh, CURLSHOPT_UNLOCKFUNC, unlock_function); + break; + case CURLSHOPT_SHARE: + { + const sint32 type = parameter.GetMPTR(); + result = curl_share_setopt(curlSh, CURLSHOPT_SHARE, type); + break; + } + case CURLSHOPT_UNSHARE: + { + const sint32 type = parameter.GetMPTR(); + result = curl_share_setopt(curlSh, CURLSHOPT_UNSHARE, type); + break; + } + default: + cemu_assert_unimplemented(); + } + forceLogDebug_printf("curl_share_setopt(0x%08x, 0x%x, 0x%08x) -> 0x%x", share.GetMPTR(), option, parameter.GetMPTR(), result); + osLib_returnFromFunction(hCPU, result); +} + +void export_curl_share_init(PPCInterpreter_t* hCPU) +{ + CURLSHPtr result{ PPCCoreCallback(g_nlibcurl.calloc.GetMPTR(), (uint32)1, ppcsizeof<CURLSH_t>()) }; + forceLogDebug_printf("curl_share_init() -> 0x%08x", result.GetMPTR()); + if (result) + { + memset(result.GetPtr(), 0, sizeof(CURLSH_t)); + *result = {}; + result->curlsh = curl_share_init(); + } + + osLib_returnFromFunction(hCPU, result.GetMPTR()); +} + +void export_curl_share_cleanup(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(curlsh, CURLSH_t, 0); + uint32 result = ::curl_share_cleanup(curlsh->curlsh); + forceLogDebug_printf("curl_share_cleanup(0x%08x) -> 0x%x", curlsh.GetMPTR(), result); + PPCCoreCallback(g_nlibcurl.free.GetMPTR(), curlsh.GetMPTR()); + osLib_returnFromFunction(hCPU, 0); +} + +int my_trace(CURL *handle, curl_infotype type, char *ptr, size_t size, + void *userp) +{ + FILE* f = (FILE*)userp; + + //if (type == CURLINFO_TEXT) + { + char tmp[1024] = {}; + sprintf(tmp, "0x%p: ", handle); + fwrite(tmp, 1, strlen(tmp), f); + + memcpy(tmp, ptr, std::min(size, (size_t)990)); + fwrite(tmp, 1, std::min(size + 1, (size_t)991), f); + + fflush(f); + + } + return 0; +} + +static int curl_closesocket(void *clientp, curl_socket_t item) +{ + nsysnet_notifyCloseSharedSocket((SOCKET)item); + closesocket(item); + return 0; +} + +void export_curl_easy_init(PPCInterpreter_t* hCPU) +{ + if (g_nlibcurl.initialized == 0) + { + if (curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK) + { + osLib_returnFromFunction(hCPU, 0); + return; + } + } + + // Curl_open + CURLPtr result{ PPCCoreCallback(g_nlibcurl.calloc.GetMPTR(), (uint32)1, ppcsizeof<CURL_t>()) }; + forceLogDebug_printf("curl_easy_init() -> 0x%08x", result.GetMPTR()); + if (result) + { + memset(result.GetPtr(), 0, sizeof(CURL_t)); + *result = {}; + result->curl = curl_easy_init(); + result->curlThread = coreinitThread_getCurrentThreadDepr(ppcInterpreterCurrentInstance); + + result->info_contentType = nullptr; + result->info_redirectUrl = nullptr; + + // default parameters + curl_easy_setopt(result->curl, CURLOPT_HEADERFUNCTION, header_callback); + curl_easy_setopt(result->curl, CURLOPT_HEADERDATA, result.GetPtr()); + + curl_easy_setopt(result->curl, CURLOPT_CLOSESOCKETFUNCTION, curl_closesocket); + curl_easy_setopt(result->curl, CURLOPT_CLOSESOCKETDATA, nullptr); + + if (g_nlibcurl.proxyConfig) + { + // todo + } + } + + osLib_returnFromFunction(hCPU, result.GetMPTR()); +} + +void export_curl_easy_pause(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(curl, CURL_t, 0); + ppcDefineParamS32(bitmask, 1); + forceLogDebug_printf("curl_easy_pause(0x%08x, 0x%x)", curl.GetMPTR(), bitmask); + + //const CURLcode result = ::curl_easy_pause(curl->curl, bitmask); + const uint32 result = SendOrderToWorker(curl.GetPtr(), QueueOrder_Pause, bitmask); + forceLogDebug_printf("curl_easy_pause(0x%08x, 0x%x) DONE", curl.GetMPTR(), bitmask); + osLib_returnFromFunction(hCPU, result); +} + +void export_curl_easy_cleanup(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(curl, CURL_t, 0); + forceLogDebug_printf("curl_easy_cleanup(0x%08x)", curl.GetMPTR()); + + curlDebug_cleanup(curl.GetPtr()); + + ::curl_easy_cleanup(curl->curl); + PPCCoreCallback(g_nlibcurl.free.GetMPTR(), curl.GetMPTR()); + + if (curl->info_contentType.IsNull() == false) + PPCCoreCallback(g_nlibcurl.free.GetMPTR(), curl->info_contentType.GetMPTR()); + if (curl->info_redirectUrl.IsNull() == false) + PPCCoreCallback(g_nlibcurl.free.GetMPTR(), curl->info_redirectUrl.GetMPTR()); + + osLib_returnFromFunction(hCPU, 0); +} + +void export_curl_easy_reset(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(curl, CURL_t, 0); + forceLogDebug_printf("curl_easy_reset(0x%08x)", curl.GetMPTR()); + // curl_apply_default_proxy_config(); + ::curl_easy_reset(curl->curl); + osLib_returnFromFunction(hCPU, 0); +} + +int ssl_verify_callback(int preverify_ok, X509_STORE_CTX* ctx) +{ + if (preverify_ok) return preverify_ok; + int err = X509_STORE_CTX_get_error(ctx); + if(err != 0) + forceLogDebug_printf("ssl_verify_callback: Error %d but allow certificate anyway", err); + X509_STORE_CTX_set_error(ctx, 0); + return 1; +} + +CURLcode ssl_ctx_callback(CURL* curl, void* sslctx, void* param) +{ + //peterBreak(); + CURL_t* ppcCurl = (CURL_t*)param; + nsysnet::NSSLInternalState_t* nssl = nsysnet::GetNSSLContext((uint32)ppcCurl->hNSSL); + + for (uint32 i : nssl->serverPKIs) + { + if (iosuCrypto_addCACertificate(sslctx, i) == false) + { + cemu_assert_suspicious(); + return CURLE_SSL_CACERT; + } + } + for (auto& customPKI : nssl->serverCustomPKIs) + { + if (iosuCrypto_addCustomCACertificate(sslctx, &customPKI[0], (sint32)customPKI.size()) == false) + { + cemu_assert_suspicious(); + return CURLE_SSL_CACERT; + } + } + + if (nssl->clientPKI != 0 && iosuCrypto_addClientCertificate(sslctx, nssl->clientPKI) == false) + { + cemu_assert_suspicious(); + return CURLE_SSL_CERTPROBLEM; + } + + uint32 flags = ppcCurl->nsslVerifyOptions; + if (HAS_FLAG(flags, NSSL_VERIFY_PEER)) + { + ::SSL_CTX_set_cipher_list((SSL_CTX*)sslctx, "AES256-SHA"); // TLS_RSA_WITH_AES_256_CBC_SHA (in CURL it's called rsa_aes_256_sha) + ::SSL_CTX_set_mode((SSL_CTX*)sslctx, SSL_MODE_AUTO_RETRY); + ::SSL_CTX_set_verify_depth((SSL_CTX*)sslctx, 2); + ::SSL_CTX_set_verify((SSL_CTX*)sslctx, SSL_VERIFY_PEER, ssl_verify_callback); + } + else + { + ::SSL_CTX_set_verify((SSL_CTX*)sslctx, SSL_VERIFY_NONE, nullptr); + } + + return CURLE_OK; +} + +size_t header_callback(char* buffer, size_t size, size_t nitems, void* userdata) +{ + //peterBreak(); + CURL_t* curl = (CURL_t*)userdata; + + curlDebug_headerWrite(curl, buffer, size, nitems); + + if (curl->fwrite_header.IsNull()) + { + return size * nitems; + } + + if (g_callerQueue == nullptr || g_threadQueue == nullptr) + { + StackAllocator<char> tmp((uint32)(size * nitems)); + memcpy(tmp.GetPointer(), buffer, size * nitems); + return PPCCoreCallback(curl->fwrite_header.GetMPTR(), tmp.GetMPTR(), (uint32)size, (uint32)nitems, curl->writeheader.GetMPTR()); + } + + QueueMsg_t msg = {}; + msg.order = QueueOrder_HeaderCB; + msg.header_cb.buffer = buffer; + msg.header_cb.size = (uint32)size; + msg.header_cb.nitems = (uint32)nitems; + g_callerQueue->push(msg, curl->curlThread); + + msg = g_threadQueue->pop(); + if (msg.order != QueueOrder_CBDone) + cemu_assert_suspicious(); + +#ifndef PUBLIC_RELEASE + char debug[500]; + cemu_assert_debug((size*nitems) < 500); + memcpy(debug, buffer, size*nitems); + debug[size*nitems] = 0; + forceLogDebug_printf("header_callback(0x%p, 0x%x, 0x%x, 0x%08x) [%s]", (void*)buffer, size, nitems, curl->writeheader.GetMPTR(), debug); +#endif + return msg.result; +} + +size_t write_callback(char* ptr, size_t size, size_t nmemb, void* userdata) +{ + CURL_t* curl = (CURL_t*)userdata; + + curlDebug_resultWrite(curl, ptr, size, nmemb); + //StackAllocator<char> tmp(size * nmemb); + //memcpy(tmp.GetPointer(), ptr, size * nmemb); + + forceLogDebug_printf("write_callback(0x%p 0x%x, 0x%x, 0x%08x)", (void*)ptr, size, nmemb, curl->out.GetMPTR()); + + if (g_callerQueue == nullptr || g_threadQueue == nullptr) + { + StackAllocator<char> tmp((uint32)(size * nmemb)); + memcpy(tmp.GetPointer(), ptr, size * nmemb); + int r = PPCCoreCallback(curl->fwrite_func.GetMPTR(), tmp.GetMPTR(), (uint32)size, (uint32)nmemb, curl->out.GetMPTR()); + return r; + } + + QueueMsg_t msg = {}; + msg.order = QueueOrder_WriteCB; + msg.write_cb.buffer = ptr; + msg.write_cb.size = (uint32)size; + msg.write_cb.nmemb = (uint32)nmemb; + g_callerQueue->push(msg, curl->curlThread); + + msg = g_threadQueue->pop(); + if (msg.order != QueueOrder_CBDone) + cemu_assert_suspicious(); + + return msg.result; +} + +int sockopt_callback(void* clientp, curl_socket_t curlfd, curlsocktype purpose) +{ + CURL_t* curl = (CURL_t*)clientp; + + forceLogDebug_printf("sockopt_callback called!"); + + sint32 r = 0; + + return r; +} + +size_t read_callback(char* buffer, size_t size, size_t nitems, void* instream) +{ + CURL_t* curl = (CURL_t*)instream; + + forceLogDebug_printf("read_callback(0x%p, 0x%x, 0x%x, 0x%08x) [func: 0x%x]", (void*)buffer, size, nitems, curl->in_set.GetMPTR(), curl->fread_func_set.GetMPTR()); + + if (g_callerQueue == nullptr || g_threadQueue == nullptr) + { + StackAllocator<char> tmp((uint32)(size * nitems)); + const sint32 result = PPCCoreCallback(curl->fread_func_set.GetMPTR(), tmp.GetMPTR(), (uint32)size, (uint32)nitems, curl->in_set.GetMPTR()); + memcpy(buffer, tmp.GetPointer(), result); + return result; + } + + QueueMsg_t msg = {}; + msg.order = QueueOrder_ReadCB; + msg.read_cb.buffer = buffer; + msg.read_cb.size = (uint32)size; + msg.read_cb.nitems = (uint32)std::min<uint32>(nitems, 0x4000); // limit this to 16KB which is the limit in nlibcurl.rpl (Super Mario Maker crashes on level upload if the size is too big) + + forceLogDebug_printf("read_callback(0x%p, 0x%x, 0x%x, 0x%08x) [func: 0x%x] PUSH", (void*)buffer, size, nitems, curl->in_set.GetMPTR(), curl->fread_func_set.GetMPTR()); + g_callerQueue->push(msg, curl->curlThread); + + msg = g_threadQueue->pop(); + if (msg.order != QueueOrder_CBDone) + cemu_assert_suspicious(); + + forceLogDebug_printf("read_callback(0x%p, 0x%x, 0x%x, 0x%08x) DONE Result: %d", (void*)buffer, size, nitems, curl->in_set.GetMPTR(), msg.result); + + return msg.result; +} + + +int progress_callback(void* clientp, double dltotal, double dlnow, double ultotal, double ulnow) +{ + //peterBreak(); + CURL_t* curl = (CURL_t*)clientp; + //int result = PPCCoreCallback(curl->fprogress.GetMPTR(), curl->progress_client.GetMPTR(), dltotal, dlnow, ultotal, ulnow); + if(g_callerQueue == nullptr || g_threadQueue == nullptr) + return PPCCoreCallback(curl->fprogress.GetMPTR(), curl->progress_client.GetMPTR(), dltotal, dlnow, ultotal, ulnow); + + QueueMsg_t msg = {}; + msg.order = QueueOrder_ProgressCB; + msg.progress_cb.dltotal = dltotal; + msg.progress_cb.dlnow = dlnow; + msg.progress_cb.ultotal = ultotal; + msg.progress_cb.ulnow = ulnow; + g_callerQueue->push(msg, curl->curlThread); + + msg = g_threadQueue->pop(); + if (msg.order != QueueOrder_CBDone) + cemu_assert_suspicious(); + + forceLogDebug_printf("progress_callback(%.02lf, %.02lf, %.02lf, %.02lf) -> %d", dltotal, dlnow, ultotal, ulnow, msg.result); + return msg.result; +} + +void export_curl_easy_setopt(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(curl, CURL_t, 0); + ppcDefineParamU32(option, 1); + ppcDefineParamMEMPTR(parameter, void, 2); + ppcDefineParamU64(parameterU64, 2); + + CURL* curlObj = curl->curl; + + CURLcode result = CURLE_OK; + switch (option) + { + case CURLOPT_NOSIGNAL: + case CURLOPT_HTTPGET: + case CURLOPT_FOLLOWLOCATION: + case CURLOPT_BUFFERSIZE: + case CURLOPT_TIMEOUT: + case CURLOPT_CONNECTTIMEOUT_MS: + case CURLOPT_POST: + case CURLOPT_INFILESIZE: + case CURLOPT_NOPROGRESS: + case CURLOPT_LOW_SPEED_LIMIT: + case CURLOPT_LOW_SPEED_TIME: + case CURLOPT_CONNECTTIMEOUT: + { + result = ::curl_easy_setopt(curlObj, (CURLoption)option, parameter.GetMPTR()); + break; + } + case CURLOPT_URL: + { + curlDebug_logEasySetOptStr(curl.GetPtr(), "CURLOPT_URL", (const char*)parameter.GetPtr()); + forceLogDebug_printf("curl_easy_setopt(%d) [%s]", option, parameter.GetPtr()); + result = ::curl_easy_setopt(curlObj, (CURLoption)option, parameter.GetPtr()); + break; + } + + case CURLOPT_PROXY: + case CURLOPT_USERAGENT: + { + forceLogDebug_printf("curl_easy_setopt(%d) [%s]", option, parameter.GetPtr()); + result = ::curl_easy_setopt(curlObj, (CURLoption)option, parameter.GetPtr()); + break; + } + + case CURLOPT_POSTFIELDS: + { + curlDebug_logEasySetOptStr(curl.GetPtr(), "CURLOPT_POSTFIELDS", (const char*)parameter.GetPtr()); + forceLogDebug_printf("curl_easy_setopt(%d) [%s]", option, parameter.GetPtr()); + result = ::curl_easy_setopt(curlObj, (CURLoption)option, parameter.GetPtr()); + break; + } + + case CURLOPT_MAX_SEND_SPEED_LARGE: + case CURLOPT_MAX_RECV_SPEED_LARGE: + case CURLOPT_POSTFIELDSIZE_LARGE: + { + result = ::curl_easy_setopt(curlObj, (CURLoption)option, parameterU64); + break; + } + case 211: // verifyOpt + { + uint32 flags = parameter.GetMPTR(); + curl->nsslVerifyOptions = flags; + + if (HAS_FLAG(flags, NSSL_VERIFY_PEER)) + ::curl_easy_setopt(curlObj, CURLOPT_SSL_VERIFYPEER, 1); + else + ::curl_easy_setopt(curlObj, CURLOPT_SSL_VERIFYPEER, 0); + + if (HAS_FLAG(flags, NSSL_VERIFY_HOSTNAME)) + ::curl_easy_setopt(curlObj, CURLOPT_SSL_VERIFYHOST, 2); + else + ::curl_easy_setopt(curlObj, CURLOPT_SSL_VERIFYHOST, 0); + + break; + } + case 210: // SSL_CONTEXT -> set context created with NSSLCreateContext before + { + const uint32 nsslIndex = parameter.GetMPTR(); + + nsysnet::NSSLInternalState_t* nssl = nsysnet::GetNSSLContext(nsslIndex); + if (nssl) + { + curl->hNSSL = nsslIndex; + result = ::curl_easy_setopt(curlObj, CURLOPT_SSL_CTX_FUNCTION, ssl_ctx_callback); + ::curl_easy_setopt(curlObj, CURLOPT_SSL_CTX_DATA, curl.GetPtr()); + + if (nssl->sslVersion == 2) + ::curl_easy_setopt(curlObj, CURLOPT_SSLVERSION, CURL_SSLVERSION_SSLv3); + else // auto = highest = 0 || 2 for v3 + ::curl_easy_setopt(curlObj, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2); + } + else + cemu_assert_suspicious(); + break; + } + case CURLOPT_SHARE: + { + CURLSH* shObj = nullptr; + if (parameter) + { + auto curlSh = (MEMPTR<CURLSH_t>)parameter; + curlSh->curl = curl; + shObj = curlSh->curlsh; + } + + + result = ::curl_easy_setopt(curlObj, CURLOPT_SHARE, shObj); + break; + } + case CURLOPT_HEADERFUNCTION: + { + curlDebug_logEasySetOptPtr(curl.GetPtr(), "CURLOPT_HEADERFUNCTION", parameter.GetMPTR()); + curl->fwrite_header = parameter; + break; + } + case CURLOPT_HEADERDATA: + { + curlDebug_logEasySetOptPtr(curl.GetPtr(), "CURLOPT_HEADERDATA", parameter.GetMPTR()); + curl->writeheader = parameter; + break; + } + case CURLOPT_WRITEFUNCTION: + { + curlDebug_logEasySetOptPtr(curl.GetPtr(), "CURLOPT_WRITEFUNCTION", parameter.GetMPTR()); + curl->fwrite_func = parameter; + result = ::curl_easy_setopt(curlObj, CURLOPT_WRITEFUNCTION, write_callback); + ::curl_easy_setopt(curlObj, CURLOPT_WRITEDATA, curl.GetPtr()); + break; + } + case CURLOPT_WRITEDATA: // aka CURLOPT_FILE + { + curlDebug_logEasySetOptPtr(curl.GetPtr(), "CURLOPT_WRITEDATA", parameter.GetMPTR()); + curl->out = parameter; + break; + } + case CURLOPT_HTTPHEADER: + { + struct curl_slist* list = nullptr; + bool isFirst = true; + for (curl_slist_t* ppcList = (curl_slist_t*)parameter.GetPtr(); ppcList; ppcList = ppcList->next.GetPtr()) + { + forceLogDebug_printf("curl_slist_append: %s", ppcList->data.GetPtr()); + curlDebug_logEasySetOptStr(curl.GetPtr(), isFirst?"CURLOPT_HTTPHEADER" : "CURLOPT_HTTPHEADER(continue)", (const char*)ppcList->data.GetPtr()); + list = ::curl_slist_append(list, ppcList->data.GetPtr()); + isFirst = false; + } + + result = ::curl_easy_setopt(curlObj, CURLOPT_HTTPHEADER, list); + break; + } + case CURLOPT_SOCKOPTFUNCTION: + { + curl->fsockopt = parameter; + result = ::curl_easy_setopt(curlObj, CURLOPT_SOCKOPTFUNCTION, sockopt_callback); + ::curl_easy_setopt(curlObj, CURLOPT_SOCKOPTDATA, curl.GetPtr()); + break; + } + case CURLOPT_SOCKOPTDATA: + { + curl->sockopt_client = parameter; + break; + } + case CURLOPT_READFUNCTION: + { + curlDebug_logEasySetOptPtr(curl.GetPtr(), "CURLOPT_READFUNCTION", parameter.GetMPTR()); + curl->fread_func_set = parameter; + result = ::curl_easy_setopt(curlObj, CURLOPT_READFUNCTION, read_callback); + ::curl_easy_setopt(curlObj, CURLOPT_READDATA, curl.GetPtr()); + break; + } + case CURLOPT_READDATA: + { + curlDebug_logEasySetOptPtr(curl.GetPtr(), "CURLOPT_READDATA", parameter.GetMPTR()); + curl->in_set = parameter; + break; + } + case CURLOPT_PROGRESSFUNCTION: + { + curlDebug_logEasySetOptPtr(curl.GetPtr(), "CURLOPT_PROGRESSFUNCTION", parameter.GetMPTR()); + curl->fprogress = parameter; + result = ::curl_easy_setopt(curlObj, CURLOPT_PROGRESSFUNCTION, progress_callback); + ::curl_easy_setopt(curlObj, CURLOPT_PROGRESSDATA, curl.GetPtr()); + break; + } + case CURLOPT_PROGRESSDATA: + { + curlDebug_logEasySetOptPtr(curl.GetPtr(), "CURLOPT_PROGRESSDATA", parameter.GetMPTR()); + curl->progress_client = parameter; + break; + } + default: + forceLogDebug_printf("curl_easy_setopt(0x%08x, %d, 0x%08x) unsupported option", curl.GetMPTR(), option, parameter.GetMPTR()); + } + + forceLogDebug_printf("curl_easy_setopt(0x%08x, %d, 0x%08x) -> 0x%x", curl.GetMPTR(), option, parameter.GetMPTR(), result); + + osLib_returnFromFunction(hCPU, result); +} + +void export_curl_easy_perform(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(curl, CURL_t, 0); + curlDebug_markActiveRequest(curl.GetPtr()); + curlDebug_notifySubmitRequest(curl.GetPtr()); + forceLogDebug_printf("curl_easy_perform(0x%08x)", curl.GetMPTR()); + const uint32 result = SendOrderToWorker(curl.GetPtr(), QueueOrder_Perform); + forceLogDebug_printf("curl_easy_perform(0x%08x) -> 0x%x DONE", curl.GetMPTR(), result); + osLib_returnFromFunction(hCPU, result); +} + +void _updateGuestString(CURL_t* curl, MEMPTR<char>& ppcStr, char* hostStr) +{ + // free current string + if (ppcStr.IsNull() == false) + { + PPCCoreCallback(g_nlibcurl.free.GetMPTR(), ppcStr); + ppcStr = nullptr; + } + if (hostStr == nullptr) + return; + sint32 length = (sint32)strlen(hostStr); + ppcStr = PPCCoreCallback(g_nlibcurl.malloc.GetMPTR(), length+1); + memcpy(ppcStr.GetPtr(), hostStr, length + 1); +} + +void export_curl_easy_getinfo(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(curl, CURL_t, 0); + ppcDefineParamU32(info, 1); + ppcDefineParamMEMPTR(parameter, void, 2); + + CURL* curlObj = curl->curl; + + CURLcode result = CURLE_OK; + switch (info) + { + case CURLINFO_SIZE_DOWNLOAD: + case CURLINFO_SPEED_DOWNLOAD: + case CURLINFO_SIZE_UPLOAD: + case CURLINFO_SPEED_UPLOAD: + case CURLINFO_CONTENT_LENGTH_DOWNLOAD: + { + double tempDouble = 0.0; + result = curl_easy_getinfo(curlObj, (CURLINFO)info, &tempDouble); + *(uint64*)parameter.GetPtr() = _swapEndianU64(*(uint64*)&tempDouble); + break; + } + case CURLINFO_RESPONSE_CODE: + case CURLINFO_SSL_VERIFYRESULT: + { + long tempLong = 0; + result = curl_easy_getinfo(curlObj, (CURLINFO)info, &tempLong); + *(uint32*)parameter.GetPtr() = _swapEndianU32((uint32)tempLong); + break; + } + case CURLINFO_CONTENT_TYPE: + { + //forceLogDebug_printf("CURLINFO_CONTENT_TYPE not supported"); + //*(uint32*)parameter.GetPtr() = MPTR_NULL; + char* contentType = nullptr; + result = curl_easy_getinfo(curlObj, CURLINFO_REDIRECT_URL, &contentType); + _updateGuestString(curl.GetPtr(), curl->info_contentType, contentType); + *(uint32*)parameter.GetPtr() = curl->info_contentType.GetMPTRBE(); + break; + } + case CURLINFO_REDIRECT_URL: + { + char* redirectUrl = nullptr; + result = curl_easy_getinfo(curlObj, CURLINFO_REDIRECT_URL, &redirectUrl); + _updateGuestString(curl.GetPtr(), curl->info_redirectUrl, redirectUrl); + *(uint32*)parameter.GetPtr() = curl->info_redirectUrl.GetMPTRBE(); + break; + } + default: + cemu_assert_unimplemented(); + result = curl_easy_getinfo(curlObj, (CURLINFO)info, (double*)parameter.GetPtr()); + } + + forceLogDebug_printf("curl_easy_getinfo(0x%08x, 0x%x, 0x%08x) -> 0x%x", curl.GetMPTR(), info, parameter.GetMPTR(), result); + osLib_returnFromFunction(hCPU, result); +} + + + +void export_curl_global_init(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(flags, 0); + osLib_returnFromFunction(hCPU, curl_global_init(flags)); +} + +void export_curl_easy_strerror(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(code, 0); + MEMPTR<char> result; + + const char* error = curl_easy_strerror((CURLcode)code); + if (error) + { + forceLogDebug_printf("curl_easy_strerror: %s", error); + int len = (int)strlen(error) + 1; + result = coreinit_allocFromSysArea(len, 4); + memcpy(result.GetPtr(), error, len); + } + osLib_returnFromFunction(hCPU, result.GetMPTR()); +} + +void export_curl_slist_append(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(list, curl_slist_t, 0); + ppcDefineParamMEMPTR(data, const char, 1); + + + MEMPTR<char> dupdata{ PPCCoreCallback(g_nlibcurl.strdup.GetMPTR(), data.GetMPTR()) }; + if (!dupdata) + { + forceLogDebug_printf("curl_slist_append(0x%08x, 0x%08x [%s]) -> 0x00000000", list.GetMPTR(), data.GetMPTR(), data.GetPtr()); + osLib_returnFromFunction(hCPU, 0); + return; + } + + MEMPTR<curl_slist_t> result{ PPCCoreCallback(g_nlibcurl.malloc.GetMPTR(), ppcsizeof<curl_slist_t>()) }; + if (result) + { + result->data = dupdata; + result->next = nullptr; + + // update last obj of list + if (list) + { + MEMPTR<curl_slist_t> tmp = list; + while (tmp->next) + { + tmp = tmp->next; + } + + tmp->next = result; + } + } + else + PPCCoreCallback(g_nlibcurl.free.GetMPTR(), dupdata.GetMPTR()); + + forceLogDebug_printf("curl_slist_append(0x%08x, 0x%08x [%s]) -> 0x%08x", list.GetMPTR(), data.GetMPTR(), data.GetPtr(), result.GetMPTR()); + if(list) + osLib_returnFromFunction(hCPU, list.GetMPTR()); + else + osLib_returnFromFunction(hCPU, result.GetMPTR()); +} + +void export_curl_slist_free_all(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(list, curl_slist_t, 0); + + forceLogDebug_printf("export_curl_slist_free_all: TODO"); + + osLib_returnFromFunction(hCPU, 0); +} + +void export_curl_global_init_mem(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(flags, 0); + ppcDefineParamMEMPTR(m, curl_malloc_callback, 1); + ppcDefineParamMEMPTR(f, curl_free_callback, 2); + ppcDefineParamMEMPTR(r, curl_realloc_callback, 3); + ppcDefineParamMEMPTR(s, curl_strdup_callback, 4); + ppcDefineParamMEMPTR(c, curl_calloc_callback, 5); + + if (!m || !f || !r || !s || !c) + { + osLib_returnFromFunction(hCPU, CURLE_FAILED_INIT); + return; + } + + CURLcode result = CURLE_OK; + if (g_nlibcurl.initialized == 0) + { + result = curl_global_init(flags); + if (result == CURLE_OK) + { + g_nlibcurl.malloc = m; + g_nlibcurl.free = f; + g_nlibcurl.realloc = r; + g_nlibcurl.strdup = s; + g_nlibcurl.calloc = c; + } + } + + forceLogDebug_printf("curl_global_init_mem(0x%x, 0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x) -> 0x%08x", flags, m.GetMPTR(), f.GetMPTR(), r.GetMPTR(), s.GetMPTR(), c.GetMPTR(), result); + osLib_returnFromFunction(hCPU, result); +} + +void load() +{ + osLib_addFunction("nlibcurl", "curl_global_init_mem", export_curl_global_init_mem); + osLib_addFunction("nlibcurl", "curl_global_init", export_curl_global_init); + + osLib_addFunction("nlibcurl", "curl_slist_append", export_curl_slist_append); + osLib_addFunction("nlibcurl", "curl_slist_free_all", export_curl_slist_free_all); + osLib_addFunction("nlibcurl", "curl_easy_strerror", export_curl_easy_strerror); + + osLib_addFunction("nlibcurl", "curl_share_init", export_curl_share_init); + osLib_addFunction("nlibcurl", "curl_share_setopt", export_curl_share_setopt); + osLib_addFunction("nlibcurl", "curl_share_cleanup", export_curl_share_cleanup); + + osLib_addFunction("nlibcurl", "curl_multi_init", export_curl_multi_init); + osLib_addFunction("nlibcurl", "curl_multi_add_handle", export_curl_multi_add_handle); + osLib_addFunction("nlibcurl", "curl_multi_perform", export_curl_multi_perform); + osLib_addFunction("nlibcurl", "curl_multi_info_read", export_curl_multi_info_read); + osLib_addFunction("nlibcurl", "curl_multi_remove_handle", export_curl_multi_remove_handle); + osLib_addFunction("nlibcurl", "curl_multi_setopt", export_curl_multi_setopt); + osLib_addFunction("nlibcurl", "curl_multi_fdset", export_curl_multi_fdset); + osLib_addFunction("nlibcurl", "curl_multi_cleanup", export_curl_multi_cleanup); + osLib_addFunction("nlibcurl", "curl_multi_timeout", export_curl_multi_timeout); + + osLib_addFunction("nlibcurl", "curl_easy_init", export_curl_easy_init); + osLib_addFunction("nlibcurl", "mw_curl_easy_init", export_curl_easy_init); + osLib_addFunction("nlibcurl", "curl_easy_reset", export_curl_easy_reset); + osLib_addFunction("nlibcurl", "curl_easy_setopt", export_curl_easy_setopt); + osLib_addFunction("nlibcurl", "curl_easy_getinfo", export_curl_easy_getinfo); + osLib_addFunction("nlibcurl", "curl_easy_perform", export_curl_easy_perform); + osLib_addFunction("nlibcurl", "curl_easy_cleanup", export_curl_easy_cleanup); + osLib_addFunction("nlibcurl", "curl_easy_pause", export_curl_easy_pause); +} +} diff --git a/src/Cafe/OS/libs/nlibcurl/nlibcurl.h b/src/Cafe/OS/libs/nlibcurl/nlibcurl.h new file mode 100644 index 00000000..5075a79c --- /dev/null +++ b/src/Cafe/OS/libs/nlibcurl/nlibcurl.h @@ -0,0 +1,6 @@ +#pragma once + +namespace nlibcurl +{ + void load(); +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nlibcurl/nlibcurlDebug.hpp b/src/Cafe/OS/libs/nlibcurl/nlibcurlDebug.hpp new file mode 100644 index 00000000..63d0ff3d --- /dev/null +++ b/src/Cafe/OS/libs/nlibcurl/nlibcurlDebug.hpp @@ -0,0 +1,138 @@ + +uint32 _curlDebugSessionId = 0; + +// begin a new request if one is not already active +void curlDebug_markActiveRequest(CURL_t* curl) +{ + if (!ActiveSettings::DumpLibcurlRequestsEnabled()) + return; + + if (curl->debug.activeRequestIndex != 0) + return; // already tracking request + + if (_curlDebugSessionId == 0) + { + _curlDebugSessionId = (uint32)std::chrono::seconds(std::time(NULL)).count(); + if (_curlDebugSessionId == 0) + _curlDebugSessionId = 1; + wchar_t filePath[256]; + swprintf(filePath, sizeof(filePath), L"dump//curl//session%u", _curlDebugSessionId); + fs::create_directories(fs::path(filePath)); + } + + static uint32 _nextRequestIndex = 1; + curl->debug.activeRequestIndex = _nextRequestIndex; + _nextRequestIndex++; + + wchar_t filePath[256]; + swprintf(filePath, sizeof(filePath) / sizeof(wchar_t), L"dump//curl//session%u//request%04d_param.txt", _curlDebugSessionId, (sint32)curl->debug.activeRequestIndex); + curl->debug.file_requestParam = FileStream::createFile(filePath); + auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + if (curl->debug.file_requestParam) + { + auto t = std::localtime(&now); + curl->debug.file_requestParam->writeStringFmt("Request %d %d-%d-%d %d:%02d:%02d\r\n", (sint32)curl->debug.activeRequestIndex, t->tm_year+1900, t->tm_mon, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec); + } +} + +void curlDebug_notifySubmitRequest(CURL_t* curl) +{ + // start new response + curl->debug.responseRequestIndex = curl->debug.activeRequestIndex; + if (curl->debug.file_responseRaw) + { + delete curl->debug.file_responseRaw; + curl->debug.file_responseRaw = nullptr; + } + if (curl->debug.file_responseHeaders) + { + delete curl->debug.file_responseHeaders; + curl->debug.file_responseHeaders = nullptr; + } + curl->debug.hasDumpedResultInfo = false; + // end current request + curl->debug.activeRequestIndex = 0; + if (curl->debug.file_requestParam) + { + delete curl->debug.file_requestParam; + curl->debug.file_requestParam = nullptr; + } +} + +void curlDebug_logEasySetOptStr(CURL_t* curl, const char* optName, const char* optValue) +{ + curlDebug_markActiveRequest(curl); + if (curl->debug.file_requestParam) + { + curl->debug.file_requestParam->writeStringFmt("SetOpt %s: ", optName, optValue ? optValue : "NULL"); + if(optValue) + curl->debug.file_requestParam->writeString(optValue); + else + curl->debug.file_requestParam->writeString("NULL"); + curl->debug.file_requestParam->writeString("\r\n"); + } +} + +void curlDebug_logEasySetOptPtr(CURL_t* curl, const char* optName, uint32 ppcPtr) +{ + curlDebug_markActiveRequest(curl); + if (curl->debug.file_requestParam) + { + curl->debug.file_requestParam->writeStringFmt("SetOpt %s: 0x%08x\r\n", optName, ppcPtr); + } +} + +void curlDebug_resultWrite(CURL_t* curl, char* ptr, size_t size, size_t nmemb) +{ + if (!ActiveSettings::DumpLibcurlRequestsEnabled()) + return; + if (curl->debug.responseRequestIndex == 0) + return; + if (curl->debug.file_responseRaw == nullptr) + { + wchar_t filePath[256]; + swprintf(filePath, sizeof(filePath) / sizeof(wchar_t), L"dump//curl//session%u//request%04d_responseRaw.bin", _curlDebugSessionId, (sint32)curl->debug.responseRequestIndex); + curl->debug.file_responseRaw = FileStream::createFile(filePath); + } + if (curl->debug.file_responseRaw) + { + curl->debug.file_responseRaw->writeData(ptr, size*nmemb); + } +} + +void curlDebug_headerWrite(CURL_t* curl, char* ptr, size_t size, size_t nmemb) +{ + if (!ActiveSettings::DumpLibcurlRequestsEnabled()) + return; + if (curl->debug.responseRequestIndex == 0) + return; + if (curl->debug.file_responseHeaders == nullptr) + { + wchar_t filePath[256]; + swprintf(filePath, sizeof(filePath) / sizeof(wchar_t), L"dump//curl//session%u//request%04d_responseHeaders.txt", _curlDebugSessionId, (sint32)curl->debug.responseRequestIndex); + curl->debug.file_responseHeaders = FileStream::createFile(filePath); + } + if (curl->debug.file_responseHeaders) + { + curl->debug.file_responseHeaders->writeData(ptr, size*nmemb); + } +} + +void curlDebug_cleanup(CURL_t* curl) +{ + if (curl->debug.file_requestParam) + { + delete curl->debug.file_requestParam; + curl->debug.file_requestParam = nullptr; + } + if (curl->debug.file_responseRaw) + { + delete curl->debug.file_responseRaw; + curl->debug.file_responseRaw = nullptr; + } + if (curl->debug.file_responseHeaders) + { + delete curl->debug.file_responseHeaders; + curl->debug.file_responseHeaders = nullptr; + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nlibnss/nlibnss.cpp b/src/Cafe/OS/libs/nlibnss/nlibnss.cpp new file mode 100644 index 00000000..b4ea77dc --- /dev/null +++ b/src/Cafe/OS/libs/nlibnss/nlibnss.cpp @@ -0,0 +1,37 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "nlibnss.h" + +namespace nlibnss +{ + int NSSExportDeviceCertChain(uint8* uknPtr1, uint32be* uknLength1, uint8* uknPtr2, uint32be* uknLength2, uint32 uknR7, uint32 uknR8) + { + forceLogDebug_printf("NSSExportDeviceCertChain called but not implemented"); + cemu_assert_debug(false); + + // uknR3 = pointer (can be null, in which case only length is written) + // uknR4 = length + // uknR5 = pointer2 + // uknR6 = length2 + // uknR7 = some integer + // uknR8 = ??? + + *uknLength1 = 0x100; + *uknLength2 = 0x100; + + + return 0; // failed + } + + int NSSSignatureGetSignatureLength() + { + // parameters are unknown + cemu_assert_debug(false); + return 0x1C; // signature length + } + + void load() + { + cafeExportRegister("nlibnss", NSSSignatureGetSignatureLength, LogType::Placeholder); + cafeExportRegister("nlibnss", NSSExportDeviceCertChain, LogType::Placeholder); + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nlibnss/nlibnss.h b/src/Cafe/OS/libs/nlibnss/nlibnss.h new file mode 100644 index 00000000..2746aa68 --- /dev/null +++ b/src/Cafe/OS/libs/nlibnss/nlibnss.h @@ -0,0 +1,6 @@ +#pragma once + +namespace nlibnss +{ + void load(); +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_ac/nn_ac.cpp b/src/Cafe/OS/libs/nn_ac/nn_ac.cpp new file mode 100644 index 00000000..42d1b268 --- /dev/null +++ b/src/Cafe/OS/libs/nn_ac/nn_ac.cpp @@ -0,0 +1,271 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/libs/nn_common.h" +#include "nn_ac.h" + +#if BOOST_OS_WINDOWS +#include <iphlpapi.h> +#endif + +// AC lib (manages internet connection) + +#define AC_STATUS_FAILED (-1) +#define AC_STATUS_OK (0) + +void nn_acExport_ConnectAsync(PPCInterpreter_t* hCPU) +{ + forceLogDebug_printf("nn_ac.ConnectAsync();"); + uint32 nnResultCode = BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); + osLib_returnFromFunction(hCPU, nnResultCode); +} + +void nn_acExport_Connect(PPCInterpreter_t* hCPU) +{ + forceLogDebug_printf("nn_ac.Connect();"); + + // Terraria expects this (or GetLastErrorCode) to return 0 on success + // investigate on the actual console + // maybe all success codes are always 0 and dont have any of the other fields set? + + uint32 nnResultCode = 0;// BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); // Splatoon freezes if this function fails? + osLib_returnFromFunction(hCPU, nnResultCode); +} + +static_assert(TRUE == 1, "TRUE not 1"); + +void nn_acExport_IsApplicationConnected(PPCInterpreter_t* hCPU) +{ + //forceLogDebug_printf("nn_ac.IsApplicationConnected(0x%08x)", hCPU->gpr[3]); + ppcDefineParamMEMPTR(connected, uint8, 0); + if (connected) + *connected = TRUE; + //memory_writeU8(hCPU->gpr[3], 1); // always return true regardless of actual online state + + const uint32 nnResultCode = BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); + osLib_returnFromFunction(hCPU, nnResultCode); +} + +void nn_acExport_GetConnectStatus(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(status, uint32, 0); + if (status) + *status = AC_STATUS_OK; + + const uint32 nnResultCode = BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); + osLib_returnFromFunction(hCPU, nnResultCode); +} + +void nn_acExport_GetLastErrorCode(PPCInterpreter_t* hCPU) +{ + //forceLogDebug_printf("nn_ac.GetLastErrorCode();"); + ppcDefineParamMEMPTR(errorCode, uint32, 0); + if (errorCode) + *errorCode = 0; + const uint32 nnResultCode = BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); + osLib_returnFromFunction(hCPU, nnResultCode); +} + +void nn_acExport_GetStatus(PPCInterpreter_t* hCPU) +{ + forceLogDebug_printf("nn_ac.GetStatus();"); + ppcDefineParamMEMPTR(status, uint32, 0); + if (status) + *status = AC_STATUS_OK; + const uint32 nnResultCode = BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); + osLib_returnFromFunction(hCPU, nnResultCode); +} + +void nn_acExport_GetConnectResult(PPCInterpreter_t* hCPU) +{ + // GetConnectStatus__Q2_2nn2acFPQ3_2nn2ac6Status + forceLogDebug_printf("nn_ac.GetConnectResult(0x%08x) LR %08x", hCPU->spr.LR, hCPU->gpr[3]); + ppcDefineParamMEMPTR(result, uint32, 0); + const uint32 nnResultCode = BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); + if (result) + *result = nnResultCode; + osLib_returnFromFunction(hCPU, nnResultCode); +} + +void _GetLocalIPAndSubnetMaskFallback(uint32& localIp, uint32& subnetMask) +{ + // default to some hardcoded values + localIp = (192 << 24) | (168 << 16) | (0 << 8) | (100 << 0); + subnetMask = (255 << 24) | (255 << 16) | (255 << 8) | (0 << 0); +} + +#if BOOST_OS_WINDOWS +void _GetLocalIPAndSubnetMask(uint32& localIp, uint32& subnetMask) +{ + std::vector<IP_ADAPTER_ADDRESSES> buf_adapter_addresses; + buf_adapter_addresses.resize(32); + DWORD buf_size; + DWORD r; + + for (uint32 i = 0; i < 6; i++) + { + buf_size = (uint32)(buf_adapter_addresses.size() * sizeof(IP_ADAPTER_ADDRESSES)); + r = GetAdaptersAddresses(AF_INET, GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_INCLUDE_GATEWAYS, nullptr, buf_adapter_addresses.data(), &buf_size); + if (r != ERROR_BUFFER_OVERFLOW) + break; + buf_adapter_addresses.resize(buf_adapter_addresses.size() * 2); + } + if (r != ERROR_SUCCESS) + { + cemuLog_log(LogType::Force, "Failed to acquire local IP and subnet mask"); + _GetLocalIPAndSubnetMaskFallback(localIp, subnetMask); + return; + } + IP_ADAPTER_ADDRESSES* currentAddress = buf_adapter_addresses.data(); + while (currentAddress) + { + if (currentAddress->OperStatus != IfOperStatusUp) + { + currentAddress = currentAddress->Next; + continue; + } + if (!currentAddress->FirstUnicastAddress || !currentAddress->FirstUnicastAddress->Address.lpSockaddr) + { + currentAddress = currentAddress->Next; + continue; + } + if (!currentAddress->FirstGatewayAddress) + { + currentAddress = currentAddress->Next; + continue; + } + + SOCKADDR* sockAddr = currentAddress->FirstUnicastAddress->Address.lpSockaddr; + if (sockAddr->sa_family == AF_INET) + { + ULONG mask = 0; + if (ConvertLengthToIpv4Mask(currentAddress->FirstUnicastAddress->OnLinkPrefixLength, &mask) != NO_ERROR) + mask = 0; + sockaddr_in* inAddr = (sockaddr_in*)sockAddr; + localIp = _byteswap_ulong(inAddr->sin_addr.S_un.S_addr); + subnetMask = _byteswap_ulong(mask); + return; + } + currentAddress = currentAddress->Next; + } + cemuLog_logDebug(LogType::Force, "_GetLocalIPAndSubnetMask(): Failed to find network IP and subnet mask"); + _GetLocalIPAndSubnetMaskFallback(localIp, subnetMask); +} +#else +void _GetLocalIPAndSubnetMask(uint32& localIp, uint32& subnetMask) +{ + cemuLog_logDebug(LogType::Force, "_GetLocalIPAndSubnetMask(): Not implemented"); + _GetLocalIPAndSubnetMaskFallback(localIp, subnetMask); +} +#endif + +void nnAcExport_GetAssignedAddress(PPCInterpreter_t* hCPU) +{ + forceLogDebug_printf("GetAssignedAddress() called"); + ppcDefineParamU32BEPtr(ipAddrOut, 0); + + uint32 localIp; + uint32 subnetMask; + _GetLocalIPAndSubnetMask(localIp, subnetMask); + + *ipAddrOut = localIp; + + const uint32 nnResultCode = BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); + osLib_returnFromFunction(hCPU, nnResultCode); +} + +void nnAcExport_GetAssignedSubnet(PPCInterpreter_t* hCPU) +{ + forceLogDebug_printf("GetAssignedSubnet() called"); + + ppcDefineParamU32BEPtr(subnetMaskOut, 0); + + uint32 localIp; + uint32 subnetMask; + _GetLocalIPAndSubnetMask(localIp, subnetMask); + + *subnetMaskOut = subnetMask; + + const uint32 nnResultCode = BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); + osLib_returnFromFunction(hCPU, nnResultCode); +} + +void nnAcExport_ACGetAssignedAddress(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32BEPtr(ipAddrOut, 0); + + uint32 localIp; + uint32 subnetMask; + _GetLocalIPAndSubnetMask(localIp, subnetMask); + *ipAddrOut = localIp; + + const uint32 nnResultCode = BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); + osLib_returnFromFunction(hCPU, nnResultCode); +} + +void nnAcExport_IsSystemConnected(PPCInterpreter_t* hCPU) +{ + ppcDefineParamTypePtr(isConnectedOut, uint8, 0); + ppcDefineParamTypePtr(apTypeOut, uint32be, 1); + + forceLogDebug_printf("nn_ac.IsSystemConnected() - placeholder"); + *apTypeOut = 0; // ukn + *isConnectedOut = 1; + + osLib_returnFromFunction(hCPU, 0); +} + +void nnAcExport_IsConfigExisting(PPCInterpreter_t* hCPU) +{ + forceLogDebug_printf("nn_ac.IsConfigExisting() - placeholder"); + + ppcDefineParamU32(configId, 0); + ppcDefineParamTypePtr(isConfigExisting, uint8, 1); + + *isConfigExisting = 0; + + osLib_returnFromFunction(hCPU, 0); +} + +namespace nn_ac +{ + bool ACIsSuccess(betype<nnResult>* r) + { + return NN_RESULT_IS_SUCCESS(*r) ? 1 : 0; + } + + nnResult ACGetConnectStatus(uint32be* connectionStatus) + { + + *connectionStatus = 0; // 0 means connected? + + return NN_RESULT_SUCCESS; + } + + void load() + { + cafeExportRegister("nn_ac", ACIsSuccess, LogType::Placeholder); + cafeExportRegister("nn_ac", ACGetConnectStatus, LogType::Placeholder); + } + +} + +void nnAc_load() +{ + osLib_addFunction("nn_ac", "Connect__Q2_2nn2acFv", nn_acExport_Connect); + osLib_addFunction("nn_ac", "ConnectAsync__Q2_2nn2acFv", nn_acExport_ConnectAsync); + osLib_addFunction("nn_ac", "IsApplicationConnected__Q2_2nn2acFPb", nn_acExport_IsApplicationConnected); + osLib_addFunction("nn_ac", "GetConnectStatus__Q2_2nn2acFPQ3_2nn2ac6Status", nn_acExport_GetConnectStatus); + osLib_addFunction("nn_ac", "GetConnectResult__Q2_2nn2acFPQ2_2nn6Result", nn_acExport_GetConnectResult); + osLib_addFunction("nn_ac", "GetLastErrorCode__Q2_2nn2acFPUi", nn_acExport_GetLastErrorCode); + osLib_addFunction("nn_ac", "GetStatus__Q2_2nn2acFPQ3_2nn2ac6Status", nn_acExport_GetStatus); + + osLib_addFunction("nn_ac", "GetAssignedAddress__Q2_2nn2acFPUl", nnAcExport_GetAssignedAddress); + osLib_addFunction("nn_ac", "GetAssignedSubnet__Q2_2nn2acFPUl", nnAcExport_GetAssignedSubnet); + + osLib_addFunction("nn_ac", "IsSystemConnected__Q2_2nn2acFPbPQ3_2nn2ac6ApType", nnAcExport_IsSystemConnected); + + osLib_addFunction("nn_ac", "IsConfigExisting__Q2_2nn2acFQ3_2nn2ac11ConfigIdNumPb", nnAcExport_IsConfigExisting); + + osLib_addFunction("nn_ac", "ACGetAssignedAddress", nnAcExport_ACGetAssignedAddress); + + nn_ac::load(); +} diff --git a/src/Cafe/OS/libs/nn_ac/nn_ac.h b/src/Cafe/OS/libs/nn_ac/nn_ac.h new file mode 100644 index 00000000..b8827200 --- /dev/null +++ b/src/Cafe/OS/libs/nn_ac/nn_ac.h @@ -0,0 +1 @@ +void nnAc_load(); \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_acp/nn_acp.cpp b/src/Cafe/OS/libs/nn_acp/nn_acp.cpp new file mode 100644 index 00000000..97f183c1 --- /dev/null +++ b/src/Cafe/OS/libs/nn_acp/nn_acp.cpp @@ -0,0 +1,509 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/Filesystem/fsc.h" +#include "nn_acp.h" +#include "Cafe/OS/libs/nn_common.h" +#include "Cafe/OS/libs/coreinit/coreinit_IOS.h" +#include "Cafe/OS/libs/coreinit/coreinit_Time.h" + +#include <filesystem> +#include <fstream> + +#include "config/ActiveSettings.h" +#include "Cafe/IOSU/legacy/iosu_acp.h" +#include "Cafe/IOSU/legacy/iosu_ioctl.h" + +#include "Cafe/OS/libs/sysapp/sysapp.h" +#include "Common/filestream.h" +#include "Cafe/CafeSystem.h" + +#define acpPrepareRequest() \ +StackAllocator<iosuAcpCemuRequest_t> _buf_acpRequest; \ +StackAllocator<ioBufferVector_t> _buf_bufferVector; \ +iosuAcpCemuRequest_t* acpRequest = _buf_acpRequest.GetPointer(); \ +ioBufferVector_t* acpBufferVector = _buf_bufferVector.GetPointer(); \ +memset(acpRequest, 0, sizeof(iosuAcpCemuRequest_t)); \ +memset(acpBufferVector, 0, sizeof(ioBufferVector_t)); \ +acpBufferVector->buffer = (uint8*)acpRequest; + +namespace nn +{ +namespace acp +{ + ACPStatus _ACPConvertResultToACPStatus(uint32* nnResult, const char* functionName, uint32 someConstant) + { + // todo + return ACPStatus::SUCCESS; + } + + ACPStatus ACPGetApplicationBox(uint32be* applicationBox, uint64 titleId) + { + // todo + *applicationBox = 3; // storage mlc + return ACPStatus::SUCCESS; + } + + ACPStatus ACPMountSaveDir() + { + uint64 titleId = CafeSystem::GetForegroundTitleId(); + uint32 high = GetTitleIdHigh(titleId) & (~0xC); + uint32 low = GetTitleIdLow(titleId); + + // mount save path + const auto mlc = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/user/", high, low); + FSCDeviceHostFS_Mount("/vol/save/", mlc.generic_wstring().c_str(), FSC_PRIORITY_BASE); + nnResult mountResult = BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_ACP, 0); + return _ACPConvertResultToACPStatus(&mountResult, "ACPMountSaveDir", 0x60); + } + + uint64 _acpGetTimestamp() + { + return coreinit::coreinit_getOSTime() / ESPRESSO_TIMER_CLOCK; + } + + nnResult __ACPUpdateSaveTimeStamp(uint32 persistentId, uint64 titleId, ACPDeviceType deviceType) + { + if (deviceType == UnknownType) + { + return (nnResult)0xA030FB80; + } + + // create or modify the saveinfo + const auto saveinfoPath = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/meta/saveinfo.xml", GetTitleIdHigh(titleId), GetTitleIdLow(titleId)); + auto saveinfoData = FileStream::LoadIntoMemory(saveinfoPath); + if (saveinfoData && !saveinfoData->empty()) + { + namespace xml = tinyxml2; + xml::XMLDocument doc; + tinyxml2::XMLError xmlError = doc.Parse((const char*)saveinfoData->data(), saveinfoData->size()); + if (xmlError == xml::XML_SUCCESS || xmlError == xml::XML_ERROR_EMPTY_DOCUMENT) + { + xml::XMLNode* child = doc.FirstChild(); + // check for declaration -> <?xml version="1.0" encoding="utf-8"?> + if (!child || !child->ToDeclaration()) + { + xml::XMLDeclaration* decl = doc.NewDeclaration(); + doc.InsertFirstChild(decl); + } + + xml::XMLElement* info = doc.FirstChildElement("info"); + if (!info) + { + info = doc.NewElement("info"); + doc.InsertEndChild(info); + } + + // find node with persistentId + char tmp[64]; + sprintf(tmp, "%08x", persistentId); + bool foundNode = false; + for (xml::XMLElement* account = info->FirstChildElement("account"); account; account = account->NextSiblingElement("account")) + { + if (account->Attribute("persistentId", tmp)) + { + // found the entry! -> update timestamp + xml::XMLElement* timestamp = account->FirstChildElement("timestamp"); + sprintf(tmp, "%016llx", _acpGetTimestamp()); + if (timestamp) + timestamp->SetText(tmp); + else + { + timestamp = doc.NewElement("timestamp"); + account->InsertFirstChild(timestamp); + } + + foundNode = true; + break; + } + } + + if (!foundNode) + { + tinyxml2::XMLElement* account = doc.NewElement("account"); + { + sprintf(tmp, "%08x", persistentId); + account->SetAttribute("persistentId", tmp); + + tinyxml2::XMLElement* timestamp = doc.NewElement("timestamp"); + { + sprintf(tmp, "%016llx", _acpGetTimestamp()); + timestamp->SetText(tmp); + } + + account->InsertFirstChild(timestamp); + } + + info->InsertFirstChild(account); + } + + // update file + tinyxml2::XMLPrinter printer; + doc.Print(&printer); + FileStream* fs = FileStream::createFile2(saveinfoPath); + if (fs) + { + fs->writeString(printer.CStr()); + delete fs; + } + } + } + return NN_RESULT_SUCCESS; + } + + ACPStatus ACPUpdateSaveTimeStamp(uint32 persistentId, uint64 titleId, ACPDeviceType deviceType) + { + nnResult r = __ACPUpdateSaveTimeStamp(persistentId, titleId, deviceType); + return ACPStatus::SUCCESS; + } + + void CreateSaveMetaFiles(uint32 persistentId, uint64 titleId) + { + std::string titlePath = CafeSystem::GetMlcStoragePath(CafeSystem::GetForegroundTitleId()); + + sint32 fscStatus; + FSCVirtualFile* fscFile = fsc_open((titlePath + "/meta/meta.xml").c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus); + if (fscFile) + { + sint32 fileSize = fsc_getFileSize(fscFile); + + std::unique_ptr<uint8[]> fileContent = std::make_unique<uint8[]>(fileSize); + fsc_readFile(fscFile, fileContent.get(), fileSize); + fsc_close(fscFile); + + const auto outPath = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/meta/meta.xml", GetTitleIdHigh(titleId), GetTitleIdLow(titleId)); + + std::ofstream myFile(outPath, std::ios::out | std::ios::binary); + myFile.write((char*)fileContent.get(), fileSize); + myFile.close(); + } + + fscFile = fsc_open((titlePath + "/meta/iconTex.tga").c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus); + if (fscFile) + { + sint32 fileSize = fsc_getFileSize(fscFile); + + std::unique_ptr<uint8[]> fileContent = std::make_unique<uint8[]>(fileSize); + fsc_readFile(fscFile, fileContent.get(), fileSize); + fsc_close(fscFile); + + const auto outPath = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/meta/iconTex.tga", GetTitleIdHigh(titleId), GetTitleIdLow(titleId)); + + std::ofstream myFile(outPath, std::ios::out | std::ios::binary); + myFile.write((char*)fileContent.get(), fileSize); + myFile.close(); + } + + ACPUpdateSaveTimeStamp(persistentId, titleId, InternalDeviceType); + } + + nnResult CreateSaveDir(uint32 persistentId, ACPDeviceType type) + { + uint64 titleId = CafeSystem::GetForegroundTitleId(); + uint32 high = GetTitleIdHigh(titleId) & (~0xC); + uint32 low = GetTitleIdLow(titleId); + + sint32 fscStatus = FSC_STATUS_FILE_NOT_FOUND; + char path[256]; + + sprintf(path, "%susr/save/%08x/", "/vol/storage_mlc01/", high); + fsc_createDir(path, &fscStatus); + sprintf(path, "%susr/save/%08x/%08x/", "/vol/storage_mlc01/", high, low); + fsc_createDir(path, &fscStatus); + sprintf(path, "%susr/save/%08x/%08x/meta/", "/vol/storage_mlc01/", high, low); + fsc_createDir(path, &fscStatus); + sprintf(path, "%susr/save/%08x/%08x/user/", "/vol/storage_mlc01/", high, low); + fsc_createDir(path, &fscStatus); + sprintf(path, "%susr/save/%08x/%08x/user/common", "/vol/storage_mlc01/", high, low); + fsc_createDir(path, &fscStatus); + sprintf(path, "%susr/save/%08x/%08x/user/%08x", "/vol/storage_mlc01/", high, low, persistentId == 0 ? 0x80000001 : persistentId); + fsc_createDir(path, &fscStatus); + + // not sure about this + sprintf(path, "%susr/boss/", "/vol/storage_mlc01/"); + fsc_createDir(path, &fscStatus); + sprintf(path, "%susr/boss/%08x/", "/vol/storage_mlc01/", high); + fsc_createDir(path, &fscStatus); + sprintf(path, "%susr/boss/%08x/%08x/", "/vol/storage_mlc01/", high, low); + fsc_createDir(path, &fscStatus); + sprintf(path, "%susr/boss/%08x/%08x/user/", "/vol/storage_mlc01/", high, low); + fsc_createDir(path, &fscStatus); + sprintf(path, "%susr/boss/%08x/%08x/user/common", "/vol/storage_mlc01/", high, low); + fsc_createDir(path, &fscStatus); + sprintf(path, "%susr/boss/%08x/%08x/user/%08x/", "/vol/storage_mlc01/", high, low, persistentId == 0 ? 0x80000001 : persistentId); + fsc_createDir(path, &fscStatus); + + // copy xml meta files + CreateSaveMetaFiles(persistentId, titleId); + + nnResult result = BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_ACP, 0); + return result; + } + + ACPStatus ACPCreateSaveDir(uint32 persistentId, ACPDeviceType type) + { + nnResult result = CreateSaveDir(persistentId, type); + return _ACPConvertResultToACPStatus(&result, "ACPCreateSaveDir", 0x2FA); + } + + ACPStatus ACPCheckApplicationDeviceEmulation(uint32be* isEmulated) + { + *isEmulated = 0; + return ACPStatus::SUCCESS; + } + + nnResult ACPCreateSaveDirEx(uint8 accountSlot, uint64 titleId) + { + acpPrepareRequest(); + acpRequest->requestCode = IOSU_ACP_CREATE_SAVE_DIR_EX; + acpRequest->accountSlot = accountSlot; + acpRequest->titleId = titleId; + + __depr__IOS_Ioctlv(IOS_DEVICE_ACP_MAIN, IOSU_ACP_REQUEST_CEMU, 1, 1, acpBufferVector); + + nnResult result = BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_ACP, 0); + return result; + } + + void nnACPExport_ACPCreateSaveDirEx(PPCInterpreter_t* hCPU) + { + ppcDefineParamU8(accountSlot, 0); + ppcDefineParamU64(titleId, 2); // index 2 because of alignment -> guessed parameters + nnResult result = ACPCreateSaveDirEx(accountSlot, titleId); + osLib_returnFromFunction(hCPU, _ACPConvertResultToACPStatus(&result, "ACPCreateSaveDirEx", 0x300)); + } + + void export_ACPGetSaveDataTitleIdList(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(uknType, 0); + ppcDefineParamStructPtr(titleIdList, acpTitleId_t, 1); + ppcDefineParamU32(maxCount, 2); + ppcDefineParamU32BEPtr(count, 3); + + if (uknType != 3) + assert_dbg(); + + acpPrepareRequest(); + acpRequest->requestCode = IOSU_ACP_GET_SAVE_DATA_TITLE_ID_LIST; + acpRequest->ptr = titleIdList; + acpRequest->maxCount = maxCount; + acpRequest->type = uknType; + + __depr__IOS_Ioctlv(IOS_DEVICE_ACP_MAIN, IOSU_ACP_REQUEST_CEMU, 1, 1, acpBufferVector); + + *count = acpRequest->resultU32.u32; + + osLib_returnFromFunction(hCPU, acpRequest->returnCode); + } + + void export_ACPGetTitleSaveMetaXml(PPCInterpreter_t* hCPU) + { + // r3/r4 = titleId + // r5 = pointer + // r6 = 3 (probably some sort of type? Same as in ACPGetSaveDataTitleIdList?) + ppcDefineParamU64(titleId, 0); + ppcDefineParamStructPtr(acpMetaXml, acpMetaXml_t, 2); + ppcDefineParamU32(uknR6, 3); + + if (uknR6 != 3) + assert_dbg(); + + acpPrepareRequest(); + acpRequest->requestCode = IOSU_ACP_GET_TITLE_SAVE_META_XML; + acpRequest->ptr = acpMetaXml; + acpRequest->titleId = titleId; + acpRequest->type = uknR6; + + __depr__IOS_Ioctlv(IOS_DEVICE_ACP_MAIN, IOSU_ACP_REQUEST_CEMU, 1, 1, acpBufferVector); + + osLib_returnFromFunction(hCPU, acpRequest->returnCode); + } + + static_assert(sizeof(acpSaveDirInfo_t) == 0x80, "acpSaveDirInfo_t has invalid size"); + + void export_ACPGetTitleSaveDirEx(PPCInterpreter_t* hCPU) + { + ppcDefineParamU64(titleId, 0); + ppcDefineParamU32(uknR5, 2); // storage device id? + ppcDefineParamU32(uknR6, 3); // ukn + ppcDefineParamStructPtr(saveDirInfoOut, acpSaveDirInfo_t, 4); + ppcDefineParamU32(uknR8, 5); // max count? + ppcDefineParamU32BEPtr(countOut, 6); + + if (uknR5 != 3) + assert_dbg(); + if (uknR6 != 0) + assert_dbg(); + + acpPrepareRequest(); + acpRequest->requestCode = IOSU_ACP_GET_TITLE_SAVE_DIR; + acpRequest->ptr = saveDirInfoOut; + acpRequest->titleId = titleId; + acpRequest->type = uknR5; + acpRequest->maxCount = uknR8; + + __depr__IOS_Ioctlv(IOS_DEVICE_ACP_MAIN, IOSU_ACP_REQUEST_CEMU, 1, 1, acpBufferVector); + *countOut = acpRequest->resultU32.u32; + + osLib_returnFromFunction(hCPU, acpRequest->returnCode); + } + + void export_ACPCheckTitleNotReferAccountLaunch(PPCInterpreter_t* hCPU) + { + ppcDefineParamU64(titleId, 0); + forceLogDebug_printf("ACPCheckTitleNotReferAccountLaunch(): Placeholder"); + + osLib_returnFromFunction(hCPU, 0); + } + + void export_ACPGetLaunchMetaData(PPCInterpreter_t* hCPU) + { + ppcDefineParamStructPtr(acpMetaData, acpMetaData_t, 0); + + forceLogDebug_printf("ACPGetLaunchMetaData(): Placeholder"); + + acpPrepareRequest(); + acpRequest->requestCode = IOSU_ACP_GET_TITLE_META_DATA; + acpRequest->ptr = acpMetaData; + acpRequest->titleId = CafeSystem::GetForegroundTitleId(); + + __depr__IOS_Ioctlv(IOS_DEVICE_ACP_MAIN, IOSU_ACP_REQUEST_CEMU, 1, 1, acpBufferVector); + + osLib_returnFromFunction(hCPU, acpRequest->returnCode); + } + + void export_ACPGetLaunchMetaXml(PPCInterpreter_t* hCPU) + { + ppcDefineParamStructPtr(acpMetaXml, acpMetaXml_t, 0); + + forceLogDebug_printf("ACPGetLaunchMetaXml(): Placeholder"); + + acpPrepareRequest(); + acpRequest->requestCode = IOSU_ACP_GET_TITLE_META_XML; + acpRequest->ptr = acpMetaXml; + acpRequest->titleId = CafeSystem::GetForegroundTitleId(); + + __depr__IOS_Ioctlv(IOS_DEVICE_ACP_MAIN, IOSU_ACP_REQUEST_CEMU, 1, 1, acpBufferVector); + + osLib_returnFromFunction(hCPU, acpRequest->returnCode); + } + + void export_ACPGetTitleIdOfMainApplication(PPCInterpreter_t* hCPU) + { + ppcDefineParamTypePtr(titleId, uint64be, 0); + + uint64 currentTitleId = CafeSystem::GetForegroundTitleId(); + *titleId = currentTitleId; + + // for applets we return the menu titleId + if (((currentTitleId >> 32) & 0xFF) == 0x30) + { + // get menu titleId + uint64 menuTitleId = _SYSGetSystemApplicationTitleId(0); + *titleId = menuTitleId; + } + + osLib_returnFromFunction(hCPU, 0); + } + + void export_ACPGetTitleMetaDirByDevice(PPCInterpreter_t* hCPU) + { + ppcDefineParamU64(titleId, 0); + ppcDefineParamStr(path, 2); + ppcDefineParamU32(pathSize, 3); + ppcDefineParamU32(deviceId, 4); + + if (deviceId != 3) + assert_dbg(); + + if (((titleId >> 32) & 0xFF) == 0x10 || ((titleId >> 32) & 0xFF) == 0x30) + { + sprintf(path, "/vol/storage_mlc01/sys/title/%08x/%08x/meta", (uint32)(titleId>>32), (uint32)(titleId&0xFFFFFFFF)); + } + else + { + sprintf(path, "/vol/storage_mlc01/usr/title/%08x/%08x/meta", (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF)); + } + + osLib_returnFromFunction(hCPU, 0); + } + + void export_ACPGetTitleMetaXmlByDevice(PPCInterpreter_t* hCPU) + { + ppcDefineParamU64(titleId, 0); + ppcDefineParamStructPtr(acpMetaXml, acpMetaXml_t, 2); + ppcDefineParamU32(deviceId, 3); + + if (deviceId != 3) + assert_dbg(); + + acpPrepareRequest(); + acpRequest->requestCode = IOSU_ACP_GET_TITLE_META_XML; + acpRequest->ptr = acpMetaXml; + acpRequest->titleId = CafeSystem::GetForegroundTitleId(); + + __depr__IOS_Ioctlv(IOS_DEVICE_ACP_MAIN, IOSU_ACP_REQUEST_CEMU, 1, 1, acpBufferVector); + + osLib_returnFromFunction(hCPU, acpRequest->returnCode); + } + + void export_ACPIsOverAgeEx(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(age, 0); + ppcDefineParamU8(slot, 1); + + bool isOverAge = true; + osLib_returnFromFunction(hCPU, isOverAge ? 1 : 0); + } + + void export_ACPGetNetworkTime(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32BEPtr(timestamp64, 0); + ppcDefineParamU32BEPtr(ukn, 1); // probably timezone or offset? Could also be a bool for success/failed + + uint64 t = coreinit::coreinit_getOSTime() + (uint64)((sint64)(ppcCyclesSince2000_UTC - ppcCyclesSince2000) / 20LL); + + timestamp64[0] = (uint32)(t >> 32); + timestamp64[1] = (uint32)(t & 0xFFFFFFFF); + + *ukn = 1; // E-Shop doesnt want this to be zero (games also check for it) + + osLib_returnFromFunction(hCPU, 0); // error code + } + + void export_ACPConvertNetworkTimeToOSCalendarTime(PPCInterpreter_t* hCPU) + { + ppcDefineParamU64(networkTime, 0); + ppcDefineParamStructPtr(calendarTime, coreinit::OSCalendarTime_t, 2); + + coreinit::OSTicksToCalendarTime(networkTime, calendarTime); + + osLib_returnFromFunction(hCPU, 0); + } + + void load() + { + cafeExportRegister("nn_acp", ACPCheckApplicationDeviceEmulation, LogType::Placeholder); + + osLib_addFunction("nn_acp", "ACPCreateSaveDirEx", nnACPExport_ACPCreateSaveDirEx); + cafeExportRegister("nn_acp", ACPUpdateSaveTimeStamp, LogType::Placeholder); + + osLib_addFunction("nn_acp", "ACPGetSaveDataTitleIdList", export_ACPGetSaveDataTitleIdList); + osLib_addFunction("nn_acp", "ACPGetTitleSaveMetaXml", export_ACPGetTitleSaveMetaXml); + osLib_addFunction("nn_acp", "ACPGetTitleSaveDirEx", export_ACPGetTitleSaveDirEx); + + osLib_addFunction("nn_acp", "ACPCheckTitleNotReferAccountLaunch", export_ACPCheckTitleNotReferAccountLaunch); + osLib_addFunction("nn_acp", "ACPGetLaunchMetaData", export_ACPGetLaunchMetaData); + osLib_addFunction("nn_acp", "ACPGetLaunchMetaXml", export_ACPGetLaunchMetaXml); + osLib_addFunction("nn_acp", "ACPGetTitleIdOfMainApplication", export_ACPGetTitleIdOfMainApplication); + + osLib_addFunction("nn_acp", "ACPGetTitleMetaDirByDevice", export_ACPGetTitleMetaDirByDevice); + osLib_addFunction("nn_acp", "ACPGetTitleMetaXmlByDevice", export_ACPGetTitleMetaXmlByDevice); + + cafeExportRegister("nn_acp", ACPGetApplicationBox, LogType::Placeholder); + + osLib_addFunction("nn_acp", "ACPIsOverAgeEx", export_ACPIsOverAgeEx); + + osLib_addFunction("nn_acp", "ACPGetNetworkTime", export_ACPGetNetworkTime); + osLib_addFunction("nn_acp", "ACPConvertNetworkTimeToOSCalendarTime", export_ACPConvertNetworkTimeToOSCalendarTime); + } +} +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_acp/nn_acp.h b/src/Cafe/OS/libs/nn_acp/nn_acp.h new file mode 100644 index 00000000..33a9c487 --- /dev/null +++ b/src/Cafe/OS/libs/nn_acp/nn_acp.h @@ -0,0 +1,28 @@ +#pragma once + +namespace nn +{ +namespace acp +{ + enum ACPStatus : uint32 + { + SUCCESS = 0, + }; + + enum ACPDeviceType + { + UnknownType = 0, + InternalDeviceType = 1, + USBDeviceType = 3, + }; + + void CreateSaveMetaFiles(uint32 persistentId, uint64 titleId); + + ACPStatus ACPGetApplicationBox(uint32be* applicationBox, uint64 titleId); + ACPStatus ACPMountSaveDir(); + ACPStatus ACPCreateSaveDir(uint32 persistentId, ACPDeviceType type); + ACPStatus ACPUpdateSaveTimeStamp(uint32 persistentId, uint64 titleId, ACPDeviceType deviceType);; + + void load(); +} +} diff --git a/src/Cafe/OS/libs/nn_act/nn_act.cpp b/src/Cafe/OS/libs/nn_act/nn_act.cpp new file mode 100644 index 00000000..7da1601d --- /dev/null +++ b/src/Cafe/OS/libs/nn_act/nn_act.cpp @@ -0,0 +1,756 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/libs/coreinit/coreinit_IOS.h" +#include "Cafe/IOSU/legacy/iosu_act.h" +#include "Cafe/IOSU/legacy/iosu_ioctl.h" +#include "nn_act.h" +#include "Cafe/OS/libs/nn_common.h" +#include "Cafe/CafeSystem.h" + +sint32 numAccounts = 1; + +#define actPrepareRequest() \ +StackAllocator<iosuActCemuRequest_t> _buf_actRequest; \ +StackAllocator<ioBufferVector_t> _buf_bufferVector; \ +iosuActCemuRequest_t* actRequest = _buf_actRequest.GetPointer(); \ +ioBufferVector_t* actBufferVector = _buf_bufferVector.GetPointer(); \ +memset(actRequest, 0, sizeof(iosuActCemuRequest_t)); \ +memset(actBufferVector, 0, sizeof(ioBufferVector_t)); \ +actBufferVector->buffer = (uint8*)actRequest; + +#define actPrepareRequest2() \ +StackAllocator<iosuActCemuRequest_t> _buf_actRequest; \ +iosuActCemuRequest_t* actRequest = _buf_actRequest.GetPointer(); \ +memset(actRequest, 0, sizeof(iosuActCemuRequest_t)); + +uint32 getNNReturnCode(uint32 iosError, iosuActCemuRequest_t* actRequest) +{ + if ((iosError & 0x80000000) != 0) + return iosError; + return actRequest->returnCode; +} + +uint32 _doCemuActRequest(iosuActCemuRequest_t* actRequest) +{ + StackAllocator<ioBufferVector_t> _buf_bufferVector; + ioBufferVector_t* actBufferVector = _buf_bufferVector.GetPointer(); + memset(actBufferVector, 0, sizeof(ioBufferVector_t)); + actBufferVector->buffer = (uint8*)actRequest; + + uint32 ioctlResult = __depr__IOS_Ioctlv(IOS_DEVICE_ACT, IOSU_ACT_REQUEST_CEMU, 1, 1, actBufferVector); + return getNNReturnCode(ioctlResult, actRequest); +} + +namespace nn +{ +namespace act +{ + + uint32 GetPersistentIdEx(uint8 slot) + { + actPrepareRequest(); + actRequest->requestCode = IOSU_ARC_PERSISTENTID; + actRequest->accountSlot = slot; + + __depr__IOS_Ioctlv(IOS_DEVICE_ACT, IOSU_ACT_REQUEST_CEMU, 1, 1, actBufferVector); + + return actRequest->resultU32.u32; + } + + uint32 GetMiiEx(void* miiData, uint8 slot) + { + actPrepareRequest2(); + actRequest->requestCode = IOSU_ARC_MIIDATA; + actRequest->accountSlot = slot; + + uint32 result = _doCemuActRequest(actRequest); + memcpy(miiData, actRequest->resultBinary.binBuffer, MII_FFL_STORAGE_SIZE); + + return result; + } + + uint32 GetUuidEx(uint8* uuid, uint8 slot, sint32 name) + { + actPrepareRequest2(); + actRequest->requestCode = IOSU_ARC_UUID; + actRequest->accountSlot = slot; + actRequest->uuidName = name; + + uint32 result = _doCemuActRequest(actRequest); + + memcpy(uuid, actRequest->resultBinary.binBuffer, 16); + + return result; + } + + uint32 GetSimpleAddressIdEx(uint32be* simpleAddressId, uint8 slot) + { + actPrepareRequest2(); + actRequest->requestCode = IOSU_ARC_SIMPLEADDRESS; + actRequest->accountSlot = slot; + + uint32 result = _doCemuActRequest(actRequest); + + *simpleAddressId = actRequest->resultU32.u32; + + return result; + } + + sint32 g_initializeCount = 0; // inc in Initialize and dec in Finalize + uint32 Initialize() + { + // usually uses a mutex which is initialized in -> global constructor keyed to'_17_act_shim_util_cpp + if (g_initializeCount == 0) + { + actPrepareRequest(); + actRequest->requestCode = IOSU_ARC_INIT; + + __depr__IOS_Ioctlv(IOS_DEVICE_ACT, IOSU_ACT_REQUEST_CEMU, 1, 1, actBufferVector); + } + + g_initializeCount++; + + return 0; + } + + NN_ERROR_CODE GetErrorCode(betype<nnResult>* nnResult) + { + NN_ERROR_CODE errCode = NNResultToErrorCode(*nnResult, NN_RESULT_MODULE_NN_ACT); + return errCode; + } + + } +} + + +void nnActExport_CreateConsoleAccount(PPCInterpreter_t* hCPU) +{ + forceLogDebug_printf("CreateConsoleAccount(...) LR %08x", hCPU->spr.LR); + //numAccounts++; + osLib_returnFromFunction(hCPU, 0); +} + +void nnActExport_GetNumOfAccounts(PPCInterpreter_t* hCPU) +{ + forceLogDebug_printf("nn_act.GetNumOfAccounts()\n"); + osLib_returnFromFunction(hCPU, numAccounts); // account count +} + +void nnActExport_IsSlotOccupied(PPCInterpreter_t* hCPU) +{ + forceLogDebug_printf("nn_act.IsSlotOccupied(%d)\n", hCPU->gpr[3]); + sint32 slotIndex = (sint32)hCPU->gpr[3] - 1; // first slot is 1 + bool isOccupied = false; + if( slotIndex == 0 ) + isOccupied = true; + osLib_returnFromFunction(hCPU, isOccupied?1:0); +} + +uint32 GetAccountIdEx(char* accountId, uint8 slot) +{ + // returns zero for non-network accounts? + actPrepareRequest2(); + actRequest->requestCode = IOSU_ARC_ACCOUNT_ID; + actRequest->accountSlot = slot; + + uint32 result = _doCemuActRequest(actRequest); + + strcpy(accountId, actRequest->resultString.strBuffer); + + return result; +} + +uint32 GetPrincipalIdEx(uint32be* principalId, uint8 slot) +{ + actPrepareRequest2(); + actRequest->requestCode = IOSU_ARC_PRINCIPALID; + actRequest->accountSlot = slot; + + uint32 result = _doCemuActRequest(actRequest); + + *principalId = actRequest->resultU32.u32; + + return result; +} + + +uint32 GetTransferableIdEx(uint64* transferableId, uint32 unique, uint8 slot) +{ + actPrepareRequest2(); + actRequest->requestCode = IOSU_ARC_TRANSFERABLEID; + actRequest->accountSlot = slot; + actRequest->unique = unique; + + uint32 result = _doCemuActRequest(actRequest); + + *transferableId = _swapEndianU64(actRequest->resultU64.u64); + + return result; +} + +uint32 GetCountryEx(char* country, uint8 slot) +{ + actPrepareRequest2(); + actRequest->requestCode = IOSU_ARC_COUNTRY; + actRequest->accountSlot = slot; + + uint32 result = _doCemuActRequest(actRequest); + + strcpy(country, actRequest->resultString.strBuffer); + + return result; +} + +uint32 IsNetworkAccount(uint8* isNetworkAccount, uint8 slot) +{ + actPrepareRequest2(); + actRequest->requestCode = IOSU_ARC_ISNETWORKACCOUNT; + actRequest->accountSlot = slot; + + uint32 result = _doCemuActRequest(actRequest); + + *isNetworkAccount = (actRequest->resultU32.u32 != 0) ? 1 : 0; + + return result; +} + +void nnActExport_IsNetworkAccount(PPCInterpreter_t* hCPU) +{ + //forceLogDebug_printf("nn_act.IsNetworkAccount()\n"); + uint8 isNetAcc = 0; + IsNetworkAccount(&isNetAcc, 0xFE); + osLib_returnFromFunction(hCPU, isNetAcc); +} + +void nnActExport_IsNetworkAccountEx(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU8(slot, 0); + forceLogDebug_printf("nn_act.IsNetworkAccountEx(%d)\n", slot); + uint8 isNetAcc = 0; + IsNetworkAccount(&isNetAcc, slot); + osLib_returnFromFunction(hCPU, isNetAcc); +} + +void nnActExport_GetSimpleAddressId(PPCInterpreter_t* hCPU) +{ + forceLogDebug_printf("nn_act.GetSimpleAddressId()\n"); + + uint32be simpleAddressId; + nn::act::GetSimpleAddressIdEx(&simpleAddressId, iosu::act::ACT_SLOT_CURRENT); + + osLib_returnFromFunction(hCPU, (uint32)simpleAddressId); +} + +void nnActExport_GetSimpleAddressIdEx(PPCInterpreter_t* hCPU) +{ + forceLogDebug_printf("nn_act.GetSimpleAddressIdEx(0x%08x, %d)\n", hCPU->gpr[3], hCPU->gpr[4] & 0xFF); + + ppcDefineParamU32BEPtr(simpleAddressId, 0); + ppcDefineParamU8(slot, 1); + + nn::act::GetSimpleAddressIdEx(simpleAddressId, slot); + + osLib_returnFromFunction(hCPU, 0); // ResultSuccess +} + +void nnActExport_GetPrincipalId(PPCInterpreter_t* hCPU) +{ + // return error for non-nnid accounts? + forceLogDebug_printf("nn_act.GetPrincipalId()\n"); + uint32be principalId; + GetPrincipalIdEx(&principalId, iosu::act::ACT_SLOT_CURRENT); + osLib_returnFromFunction(hCPU, (uint32)principalId); +} + +void nnActExport_GetPrincipalIdEx(PPCInterpreter_t* hCPU) +{ + // return error for non-nnid accounts? + forceLogDebug_printf("nn_act.GetPrincipalIdEx(0x%08x, %d)\n", hCPU->gpr[3], hCPU->gpr[4]&0xFF); + ppcDefineParamU32BEPtr(principalId, 0); + ppcDefineParamU8(slot, 1); + GetPrincipalIdEx(principalId, slot); + + osLib_returnFromFunction(hCPU, 0); // ResultSuccess +} + +void nnActExport_GetTransferableIdEx(PPCInterpreter_t* hCPU) +{ + ppcDefineParamStructPtr(transferableId, uint64, 0); + ppcDefineParamU32(unique, 1); + ppcDefineParamU8(slot, 2); + + forceLogDebug_printf("nn_act.GetTransferableIdEx(0x%08x, 0x%08x, %d)", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5] & 0xFF); + + uint32 r = GetTransferableIdEx(transferableId, unique, slot); + + osLib_returnFromFunction(hCPU, 0); // ResultSuccess +} + +void nnActExport_GetPersistentId(PPCInterpreter_t* hCPU) +{ + forceLogDebug_printf("nn_act.GetPersistentId()"); + uint32 persistentId = nn::act::GetPersistentIdEx(iosu::act::ACT_SLOT_CURRENT); + osLib_returnFromFunction(hCPU, persistentId); +} + +void nnActExport_GetPersistentIdEx(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU8(slot, 0); + forceLogDebug_printf("nn_act.GetPersistentIdEx(%d)", slot); + + uint32 persistentId = nn::act::GetPersistentIdEx(slot); + + osLib_returnFromFunction(hCPU, persistentId); +} + +void nnActExport_GetCountry(PPCInterpreter_t* hCPU) +{ + forceLogDebug_printf("nn_act.GetCountry(0x%08x)", hCPU->gpr[3]); + ppcDefineParamStr(country, 0); + uint32 r = GetCountryEx(country, iosu::act::ACT_SLOT_CURRENT); + osLib_returnFromFunction(hCPU, r); +} + +bool g_isParentalControlCheckEnabled = false; +void nnActExport_EnableParentalControlCheck(PPCInterpreter_t* hCPU) +{ + forceLogDebug_printf("nn_act.EnableParentalControlCheck(%d)", hCPU->gpr[3]); + ppcDefineParamU8(isEnabled, 0); + g_isParentalControlCheckEnabled = isEnabled != 0; + osLib_returnFromFunction(hCPU, 0); +} + +void nnActExport_IsParentalControlCheckEnabled(PPCInterpreter_t* hCPU) +{ + forceLogDebug_printf("nn_act.IsParentalControlCheckEnabled() -> %d", g_isParentalControlCheckEnabled); + osLib_returnFromFunction(hCPU, g_isParentalControlCheckEnabled); +} + +void nnActExport_GetHostServerSettings(PPCInterpreter_t* hCPU) +{ + forceLogDebug_printf("GetHostServerSettings() - stub"); + ppcDefineParamStr(ukn, 1); + strcpy(ukn, ""); + osLib_returnFromFunction(hCPU, 0x00000000); +} + +void nnActExport_GetMii(PPCInterpreter_t* hCPU) +{ + forceLogDebug_printf("nn_act.GetMii(...)\n"); + ppcDefineParamUStr(miiData, 0); + uint32 r = nn::act::GetMiiEx(miiData, iosu::act::ACT_SLOT_CURRENT); + osLib_returnFromFunction(hCPU, r); +} + +void nnActExport_GetMiiEx(PPCInterpreter_t* hCPU) +{ + forceLogDebug_printf("nn_act.GetMiiEx(...)\n"); + ppcDefineParamUStr(miiData, 0); + ppcDefineParamU8(slot, 1); + uint32 r = nn::act::GetMiiEx(miiData, slot); + osLib_returnFromFunction(hCPU, r); +} + +void nnActExport_GetMiiImageEx(PPCInterpreter_t* hCPU) +{ + forceLogDebug_printf("GetMiiImageEx unimplemented LR %08x", hCPU->spr.LR); + + osLib_returnFromFunction(hCPU, 0); +} + +void nnActExport_GetMiiName(PPCInterpreter_t* hCPU) +{ + forceLogDebug_printf("GetMiiName(0x%08x)", hCPU->gpr[3]); + ppcDefineParamWStrBE(miiName, 0); + + StackAllocator<FFLData_t> miiData; + + uint32 r = nn::act::GetMiiEx(miiData, iosu::act::ACT_SLOT_CURRENT); + // extract name + sint32 miiNameLength = 0; + for (sint32 i = 0; i < MII_FFL_NAME_LENGTH; i++) + { + miiName[i] = _swapEndianU16(miiData->miiName[i]); + if (miiData->miiName[i] == (const uint16be)'\0') + break; + miiNameLength = i+1; + } + miiName[miiNameLength] = '\0'; + + osLib_returnFromFunction(hCPU, 0); +} + +void nnActExport_GetMiiNameEx(PPCInterpreter_t* hCPU) +{ + forceLogDebug_printf("GetMiiNameEx(0x%08x, %d)", hCPU->gpr[3], hCPU->gpr[4] & 0xFF); + ppcDefineParamWStrBE(miiName, 0); + ppcDefineParamU8(slot, 1); + + StackAllocator<FFLData_t> miiData; + + uint32 r = nn::act::GetMiiEx(miiData, slot); + // extract name + sint32 miiNameLength = 0; + for (sint32 i = 0; i < MII_FFL_NAME_LENGTH; i++) + { + miiName[i] = _swapEndianU16(miiData->miiName[i]); + if (miiData->miiName[i] == (const uint16be)'\0') + break; + miiNameLength = i + 1; + } + miiName[miiNameLength] = '\0'; + + osLib_returnFromFunction(hCPU, 0); +} + +typedef struct +{ + uint32be ukn00; + uint32be ukn04; // transferable id high? + uint32be accountTransferableIdLow; // + uint32be ukn0C; + uint32be ukn10; + uint32be ukn14; + uint32be ukn18; + uint32be ukn1C; + uint32be ukn20; + uint32be ukn24; + uint32be ukn28; + uint32be ukn2C; + uint32be ukn30; + uint32be ukn34; + uint32be ukn38; + uint32be ukn3C; + uint32be ukn40; + uint32be ukn44; + uint32be ukn48; + uint32be ukn4C; + uint32be ukn50; + uint32be ukn54; + uint32be tlsModuleIndex; + uint32be ukn5C; +}FFLStoreDataDepr_t; + +static_assert(sizeof(FFLStoreDataDepr_t) == 96); + +void nnActExport_UpdateMii(PPCInterpreter_t* hCPU) +{ + if (sizeof(FFLStoreDataDepr_t) != MII_FFL_STORAGE_SIZE) + assert_dbg(); + + ppcDefineParamU8(slot, 0); + ppcDefineParamStructPtr(fflStoreData, FFLStoreDataDepr_t, 1); + ppcDefineParamStructPtr(uknName, uint16, 2); // mii name + ppcDefineParamStructPtr(uknNameR6, uint8, 3); + ppcDefineParamStructPtr(uknNameR7, uint8, 4); + ppcDefineParamStructPtr(uknNameR8, uint8, 5); + ppcDefineParamStructPtr(uknNameR9, uint8, 6); + ppcDefineParamStructPtr(uknNameR10, uint8, 7); + ppcDefineParamStructPtr(uknNameSP4, uint8, 8); + + cemu_assert_unimplemented(); + + osLib_returnFromFunction(hCPU, 0); +} + +void nnActExport_GetUuid(PPCInterpreter_t* hCPU) +{ + forceLogDebug_printf("nn_act.GetUuid(0x%08x)", hCPU->gpr[3]); + ppcDefineParamUStr(uuid, 0); + nn::act::GetUuidEx(uuid, iosu::act::ACT_SLOT_CURRENT); + osLib_returnFromFunction(hCPU, 0); // 0 -> result ok +} + +void nnActExport_GetUuidEx(PPCInterpreter_t* hCPU) +{ + forceLogDebug_printf("nn_act.GetUuidEx(0x%08x,0x%02x)", hCPU->gpr[3], hCPU->gpr[3]&0xFF); + ppcDefineParamUStr(uuid, 0); + ppcDefineParamU8(slot, 1); + nn::act::GetUuidEx(uuid, slot); + osLib_returnFromFunction(hCPU, 0); // 0 -> result ok +} + +void nnActExport_GetUuidEx2(PPCInterpreter_t* hCPU) +{ + forceLogDebug_printf("nn_act.GetUuidEx(0x%08x,0x%02x,0x%08x)", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); + ppcDefineParamUStr(uuid, 0); + ppcDefineParamU8(slot, 1); + ppcDefineParamS32(name, 2); + nn::act::GetUuidEx(uuid, iosu::act::ACT_SLOT_CURRENT, name); + osLib_returnFromFunction(hCPU, 0); // 0 -> result ok +} + +void nnActExport_GetAccountId(PPCInterpreter_t* hCPU) +{ + forceLogDebug_printf("nn_act.GetAccountId(0x%08x)", hCPU->gpr[3]); + ppcDefineParamUStr(accId, 0); + GetAccountIdEx((char*)accId, iosu::act::ACT_SLOT_CURRENT); + osLib_returnFromFunction(hCPU, 0); +} + +void nnActExport_GetAccountIdEx(PPCInterpreter_t* hCPU) +{ + forceLogDebug_printf("nn_act.GetAccountIdEx(0x%08x, 0x%02x)", hCPU->gpr[3], hCPU->gpr[3] & 0xFF); + ppcDefineParamUStr(accId, 0); + ppcDefineParamU8(slot, 1); + GetAccountIdEx((char*)accId, slot); + osLib_returnFromFunction(hCPU, 0); +} + +void nnActExport_GetParentalControlSlotNoEx(PPCInterpreter_t* hCPU) +{ + // GetParentalControlSlotNoEx(uint8* output, uint8 slot) + forceLogDebug_printf("nn_act.GetParentalControlSlotNoEx(0x%08x, 0x%02x)", hCPU->gpr[3], hCPU->gpr[4]); + //memory_writeU8(hCPU->gpr[3], 0x01); // returned slot no (slot indices start at 1) + memory_writeU8(hCPU->gpr[3], 1); // 0 -> No parental control for slot? + //memory_writeU8(hCPU->gpr[3], 0); // 0 -> No parental control for slot? + osLib_returnFromFunction(hCPU, 0); +} + +void nnActExport_GetDefaultAccount(PPCInterpreter_t* hCPU) +{ + // todo + forceLogDebug_printf("GetDefaultAccount(): Return 1?"); + osLib_returnFromFunction(hCPU, 1); +} + +void nnActExport_GetSlotNo(PPCInterpreter_t* hCPU) +{ + // id of active account + // uint8 GetSlotNo(void); + forceLogDebug_printf("nn_act.GetSlotNo()"); + osLib_returnFromFunction(hCPU, 1); // 1 is the first slot (0 is invalid) +} + +void nnActExport_GetSlotNoEx(PPCInterpreter_t* hCPU) +{ + forceLogDebug_printf("nn_act.GetSlotNoEx(...)"); + // get slot no by uuid + ppcDefineParamUStr(uuid, 0); + + // get slot no for a specific uuid + for (uint8 i = 1; i < 12; i++) + { + uint8 accUuid[16]{}; + nn::act::GetUuidEx(accUuid, i); + if (memcmp(uuid, accUuid, 16) == 0) + { + osLib_returnFromFunction(hCPU, i); + return; + } + } + + cemu_assert_debug(false); // suspicious behavior? + + osLib_returnFromFunction(hCPU, 0); // account not found +} + +void nnActExport_Initialize(PPCInterpreter_t* hCPU) +{ + forceLogDebug_printf("nn_act.Initialize()"); + + nn::act::Initialize(); + + osLib_returnFromFunction(hCPU, 0); +} + +void nnActExport_HasNfsAccount(PPCInterpreter_t* hCPU) +{ + forceLogDebug_printf("Called nn_act.HasNfsAccount LR %08x", hCPU->spr.LR); + osLib_returnFromFunction(hCPU, 1); // Nfs = Nintendo Friend System? (Splatoon tries to call nn_fp.RegisterAccount if we set this to false) +} + +typedef struct +{ + /* +0x000 */ char token[0x201]; // /nex_token/token + /* +0x201 */ uint8 padding201[3]; + /* +0x204 */ char nexPassword[0x41]; // /nex_token/nex_password + /* +0x245 */ uint8 padding245[3]; + /* +0x248 */ char host[0x10]; // /nex_token/host + /* +0x258 */ uint16be port; // /nex_token/port + /* +0x25A */ uint16be padding25A; +}nexServiceToken_t; + +static_assert(sizeof(nexServiceToken_t) == 0x25C, "nexServiceToken_t has invalid size"); + +void nnActExport_AcquireNexServiceToken(PPCInterpreter_t* hCPU) +{ + ppcDefineParamStructPtr(token, nexServiceToken_t, 0); + ppcDefineParamU32(serverId, 1); + memset(token, 0, sizeof(nexServiceToken_t)); + actPrepareRequest(); + actRequest->requestCode = IOSU_ARC_ACQUIRENEXTOKEN; + actRequest->titleId = CafeSystem::GetForegroundTitleId(); + actRequest->titleVersion = CafeSystem::GetForegroundTitleVersion(); + actRequest->serverId = serverId; + + uint32 resultCode = __depr__IOS_Ioctlv(IOS_DEVICE_ACT, IOSU_ACT_REQUEST_CEMU, 1, 1, actBufferVector); + + memcpy(token, actRequest->resultBinary.binBuffer, sizeof(nexServiceToken_t)); + + forceLogDebug_printf("Called nn_act.AcquireNexServiceToken LR %08x", hCPU->spr.LR); + osLib_returnFromFunction(hCPU, getNNReturnCode(resultCode, actRequest)); +} + +struct independentServiceToken_t +{ + /* +0x000 */ char token[0x201]; +}; +static_assert(sizeof(independentServiceToken_t) == 0x201); // todo - verify size + +uint32 AcquireIndependentServiceToken(independentServiceToken_t* token, const char* clientId, uint32 cacheDurationInSeconds) +{ + memset(token, 0, sizeof(independentServiceToken_t)); + actPrepareRequest(); + actRequest->requestCode = IOSU_ARC_ACQUIREINDEPENDENTTOKEN; + actRequest->titleId = CafeSystem::GetForegroundTitleId(); + actRequest->titleVersion = CafeSystem::GetForegroundTitleVersion(); + actRequest->expiresIn = cacheDurationInSeconds; + strcpy(actRequest->clientId, clientId); + + uint32 resultCode = __depr__IOS_Ioctlv(IOS_DEVICE_ACT, IOSU_ACT_REQUEST_CEMU, 1, 1, actBufferVector); + + memcpy(token, actRequest->resultBinary.binBuffer, sizeof(independentServiceToken_t)); + return getNNReturnCode(resultCode, actRequest); +} + +void nnActExport_AcquireIndependentServiceToken(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(token, independentServiceToken_t, 0); + ppcDefineParamMEMPTR(serviceToken, const char, 1); + uint32 result = AcquireIndependentServiceToken(token.GetPtr(), serviceToken.GetPtr(), 0); + forceLogDebug_printf("nn_act.AcquireIndependentServiceToken(0x%p, %s) -> %x | LR %08x", (void*)token.GetPtr(), serviceToken.GetPtr(), result, hCPU->spr.LR); + forceLogDebug_printf("Token: %s", serviceToken.GetPtr()); + osLib_returnFromFunction(hCPU, result); +} + +void nnActExport_AcquireIndependentServiceToken2(PPCInterpreter_t* hCPU) +{ + ppcDefineParamStructPtr(token, independentServiceToken_t, 0); + ppcDefineParamMEMPTR(clientId, const char, 1); + ppcDefineParamU32(cacheDurationInSeconds, 2); + uint32 result = AcquireIndependentServiceToken(token, clientId.GetPtr(), cacheDurationInSeconds); + forceLogDebug_printf("Called nn_act.AcquireIndependentServiceToken2 LR %08x", hCPU->spr.LR); + osLib_returnFromFunction(hCPU, result); +} + +void nnActExport_AcquireEcServiceToken(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(pEcServiceToken, independentServiceToken_t, 0); + uint32 result = AcquireIndependentServiceToken(pEcServiceToken.GetPtr(), "71a6f5d6430ea0183e3917787d717c46", 0); + forceLogDebug_printf("Called nn_act.AcquireEcServiceToken LR %08x", hCPU->spr.LR); + osLib_returnFromFunction(hCPU, result); +} + +void nnActExport_AcquirePrincipalIdByAccountId(PPCInterpreter_t* hCPU) +{ + ppcDefineParamMEMPTR(principalId, uint32be, 0); + ppcDefineParamMEMPTR(nnid, char, 1); + ppcDefineParamU32(ukn, 2); // some option, can be 0 or 1 ? + forceLogDebug_printf("nn_act.AcquirePrincipalIdByAccountId(0x%08x,\"%s\", %d) - last param unknown", principalId.GetMPTR(), nnid.GetPtr(), ukn); + actPrepareRequest2(); + actRequest->requestCode = IOSU_ARC_ACQUIREPIDBYNNID; + strcpy(actRequest->clientId, nnid.GetPtr()); + + uint32 result = _doCemuActRequest(actRequest); + + *principalId.GetPtr() = actRequest->resultU32.u32; + + osLib_returnFromFunction(hCPU, result); +} + +void nnActExport_GetUtcOffsetEx(PPCInterpreter_t* hCPU) +{ + // GetUtcOffsetEx__Q2_2nn3actFPLUc + ppcDefineParamU32BEPtr(utcOffsetOut, 0); + ppcDefineParamU32(uknParam, 1); + + forceLogDebug_printf("Called nn_act.GetUtcOffsetEx LR %08x", hCPU->spr.LR); + + *utcOffsetOut = 0; + + forceLogDebug_printf("GetUtcOffsetEx stub"); + + osLib_returnFromFunction(hCPU, 0); +} + +void nnActExport_GetUtcOffset(PPCInterpreter_t* hCPU) +{ + uint64 utcOffset = 0; + + uint64 utcDifferenceInSeconds = (ppcCyclesSince2000_UTC / ESPRESSO_CORE_CLOCK) - (ppcCyclesSince2000 / ESPRESSO_CORE_CLOCK); + + osLib_returnFromFunction64(hCPU, utcDifferenceInSeconds * 1000000ULL); +} + +// register account functions +void nnAct_load() +{ + + osLib_addFunction("nn_act", "Initialize__Q2_2nn3actFv", nnActExport_Initialize); + + osLib_addFunction("nn_act", "CreateConsoleAccount__Q2_2nn3actFv", nnActExport_CreateConsoleAccount); + + osLib_addFunction("nn_act", "GetNumOfAccounts__Q2_2nn3actFv", nnActExport_GetNumOfAccounts); + osLib_addFunction("nn_act", "IsSlotOccupied__Q2_2nn3actFUc", nnActExport_IsSlotOccupied); + osLib_addFunction("nn_act", "GetSlotNo__Q2_2nn3actFv", nnActExport_GetSlotNo); + osLib_addFunction("nn_act", "GetSlotNoEx__Q2_2nn3actFRC7ACTUuid", nnActExport_GetSlotNoEx); + osLib_addFunction("nn_act", "IsNetworkAccount__Q2_2nn3actFv", nnActExport_IsNetworkAccount); + osLib_addFunction("nn_act", "IsNetworkAccountEx__Q2_2nn3actFUc", nnActExport_IsNetworkAccountEx); + // account id + osLib_addFunction("nn_act", "GetAccountId__Q2_2nn3actFPc", nnActExport_GetAccountId); + osLib_addFunction("nn_act", "GetAccountIdEx__Q2_2nn3actFPcUc", nnActExport_GetAccountIdEx); + // simple address + osLib_addFunction("nn_act", "GetSimpleAddressId__Q2_2nn3actFv", nnActExport_GetSimpleAddressId); + osLib_addFunction("nn_act", "GetSimpleAddressIdEx__Q2_2nn3actFPUiUc", nnActExport_GetSimpleAddressIdEx); + // principal id + osLib_addFunction("nn_act", "GetPrincipalId__Q2_2nn3actFv", nnActExport_GetPrincipalId); + osLib_addFunction("nn_act", "GetPrincipalIdEx__Q2_2nn3actFPUiUc", nnActExport_GetPrincipalIdEx); + // transferable id + osLib_addFunction("nn_act", "GetTransferableIdEx__Q2_2nn3actFPULUiUc", nnActExport_GetTransferableIdEx); + // persistent id + osLib_addFunction("nn_act", "GetPersistentId__Q2_2nn3actFv", nnActExport_GetPersistentId); + osLib_addFunction("nn_act", "GetPersistentIdEx__Q2_2nn3actFUc", nnActExport_GetPersistentIdEx); + // country + osLib_addFunction("nn_act", "GetCountry__Q2_2nn3actFPc", nnActExport_GetCountry); + + // parental + osLib_addFunction("nn_act", "EnableParentalControlCheck__Q2_2nn3actFb", nnActExport_EnableParentalControlCheck); + osLib_addFunction("nn_act", "IsParentalControlCheckEnabled__Q2_2nn3actFv", nnActExport_IsParentalControlCheckEnabled); + + osLib_addFunction("nn_act", "GetMii__Q2_2nn3actFP12FFLStoreData", nnActExport_GetMii); + osLib_addFunction("nn_act", "GetMiiEx__Q2_2nn3actFP12FFLStoreDataUc", nnActExport_GetMiiEx); + osLib_addFunction("nn_act", "GetMiiImageEx__Q2_2nn3actFPUiPvUi15ACTMiiImageTypeUc", nnActExport_GetMiiImageEx); + osLib_addFunction("nn_act", "GetMiiName__Q2_2nn3actFPw", nnActExport_GetMiiName); + osLib_addFunction("nn_act", "GetMiiNameEx__Q2_2nn3actFPwUc", nnActExport_GetMiiNameEx); + + osLib_addFunction("nn_act", "UpdateMii__Q2_2nn3actFUcRC12FFLStoreDataPCwPCvUiT4T5T4T5T4T5T4T5T4T5T4T5T4T5T4T5", nnActExport_UpdateMii); + + osLib_addFunction("nn_act", "GetUuid__Q2_2nn3actFP7ACTUuid", nnActExport_GetUuid); + osLib_addFunction("nn_act", "GetUuidEx__Q2_2nn3actFP7ACTUuidUc", nnActExport_GetUuidEx); + osLib_addFunction("nn_act", "GetUuidEx__Q2_2nn3actFP7ACTUuidUcUi", nnActExport_GetUuidEx2); + + osLib_addFunction("nn_act", "GetParentalControlSlotNoEx__Q2_2nn3actFPUcUc", nnActExport_GetParentalControlSlotNoEx); + + osLib_addFunction("nn_act", "GetDefaultAccount__Q2_2nn3actFv", nnActExport_GetDefaultAccount); + + osLib_addFunction("nn_act", "AcquireEcServiceToken__Q2_2nn3actFPc", nnActExport_AcquireEcServiceToken); + osLib_addFunction("nn_act", "AcquireNexServiceToken__Q2_2nn3actFP26ACTNexAuthenticationResultUi", nnActExport_AcquireNexServiceToken); + osLib_addFunction("nn_act", "AcquireIndependentServiceToken__Q2_2nn3actFPcPCc", nnActExport_AcquireIndependentServiceToken); + osLib_addFunction("nn_act", "AcquireIndependentServiceToken__Q2_2nn3actFPcPCcUibT4", nnActExport_AcquireIndependentServiceToken2); + osLib_addFunction("nn_act", "AcquireIndependentServiceToken__Q2_2nn3actFPcPCcUi", nnActExport_AcquireIndependentServiceToken2); + + osLib_addFunction("nn_act", "AcquirePrincipalIdByAccountId__Q2_2nn3actFPUiPA17_CcUi", nnActExport_AcquirePrincipalIdByAccountId); + + cafeExportRegisterFunc(nn::act::GetErrorCode, "nn_act", "GetErrorCode__Q2_2nn3actFRCQ2_2nn6Result", LogType::Placeholder); + + // placeholders / incomplete implementations + osLib_addFunction("nn_act", "HasNfsAccount__Q2_2nn3actFv", nnActExport_HasNfsAccount); + osLib_addFunction("nn_act", "GetHostServerSettings__Q2_2nn3actFPcT1Uc", nnActExport_GetHostServerSettings); + osLib_addFunction("nn_act", "GetUtcOffset__Q2_2nn3actFv", nnActExport_GetUtcOffset); + osLib_addFunction("nn_act", "GetUtcOffsetEx__Q2_2nn3actFPLUc", nnActExport_GetUtcOffsetEx); + +} + + + + diff --git a/src/Cafe/OS/libs/nn_act/nn_act.h b/src/Cafe/OS/libs/nn_act/nn_act.h new file mode 100644 index 00000000..06df4af1 --- /dev/null +++ b/src/Cafe/OS/libs/nn_act/nn_act.h @@ -0,0 +1,22 @@ +#pragma once + +namespace nn +{ +namespace act +{ + uint32 Initialize(); + + uint32 GetPersistentIdEx(uint8 slot); + uint32 GetUuidEx(uint8* uuid, uint8 slot, sint32 name = -2); + uint32 GetSimpleAddressIdEx(uint32be* simpleAddressId, uint8 slot); + + static uint32 getCountryCodeFromSimpleAddress(uint32 simpleAddressId) + { + return (simpleAddressId>>24)&0xFF; + } + + const uint8 ACT_SLOT_CURRENT = 0xFE; +} +} + +void nnAct_load(); \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_aoc/nn_aoc.cpp b/src/Cafe/OS/libs/nn_aoc/nn_aoc.cpp new file mode 100644 index 00000000..93564536 --- /dev/null +++ b/src/Cafe/OS/libs/nn_aoc/nn_aoc.cpp @@ -0,0 +1,162 @@ +#include "config/ActiveSettings.h" + +#include "Cafe/OS/libs/nn_aoc/nn_aoc.h" +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/Filesystem/fsc.h" +#include "Cafe/TitleList/TitleId.h" + +#include "Cemu/ncrypto/ncrypto.h" +#include "Common/filestream.h" + +namespace nn +{ + namespace aoc + { + struct AOCTitle + { + uint64be titleId; + uint32be groupId; + uint16be titleVersion; + char path[88]; + uint8 padding[2]; + }; + static_assert(sizeof(AOCTitle) == 0x68); + + enum class AOC_RESULT : uint32 + { + ERROR_OK = 0, + }; + + uint32 AOC_CalculateWorkBufferSize(uint32 count) + { + count = std::min(count, (uint32)256); + uint32 workBufferSize = 0x80 + count * 0x61; + return workBufferSize; + } + + struct AOCCacheEntry + { + AOCCacheEntry(uint64 titleId) : aocTitleId(titleId) {}; + + uint64 aocTitleId; + std::string GetPath() + { + return fmt::format("/vol/aoc{:016x}", aocTitleId); + } + }; + + std::vector<AOCCacheEntry> sAocCache; + bool sAocCacheGenerated = false; + + void generateAOCList() + { + if (sAocCacheGenerated) + return; + sAocCacheGenerated = true; + + sint32 fscStatus; + FSCVirtualFile* volDirIterator = fsc_openDirIterator("/vol", &fscStatus); + cemu_assert_debug(volDirIterator); // for valid titles /vol should always exist + if (volDirIterator) + { + FSCDirEntry dirEntry; + while (fsc_nextDir(volDirIterator, &dirEntry)) + { + std::string_view dirName = dirEntry.GetPath(); + if(!dirEntry.isDirectory) + continue; + // check for pattern: aoc<titleId> + if(dirName.size() != (3+16)) + continue; + if(dirName[0] != 'a' || + dirName[1] != 'o' || + dirName[2] != 'c') + continue; + TitleId aocTitleId; + if( !TitleIdParser::ParseFromStr(dirName.substr(3), aocTitleId) ) + continue; + // add to list of known AOC + sAocCache.emplace_back(aocTitleId); + } + fsc_close(volDirIterator); + } + } + + AOC_RESULT AOC_ListTitle(uint32be* titleCountOut, AOCTitle* titleList, uint32 maxCount, void* workBuffer, uint32 workBufferSize) + { + generateAOCList(); + for (uint32 i = 0; i < std::min(maxCount, (uint32)sAocCache.size()); i++) + { + titleList[i].titleId = sAocCache[i].aocTitleId; + titleList[i].groupId = 0; // todo + titleList[i].titleVersion = 0; // todo + strcpy(titleList[i].path, sAocCache[i].GetPath().c_str()); + } + *titleCountOut = std::min(maxCount, (uint32)sAocCache.size()); + return AOC_RESULT::ERROR_OK; + } + + AOC_RESULT AOC_OpenTitle(char* pathOut, AOCTitle* aocTitleInfo, void* workBuffer, uint32 workBufferSize) + { + strcpy(pathOut, aocTitleInfo->path); + return AOC_RESULT::ERROR_OK; + } + + AOC_RESULT AOC_CloseTitle(void* ukn) + { + return AOC_RESULT::ERROR_OK; + } + + AOC_RESULT AOC_GetPurchaseInfo(uint32be* purchaseBoolArrayOut, uint64 titleId, uint16be* entryIds, uint32 entryCount, void* workBuffer, uint32 workBufferSize) + { + // open ticket file + // on an actual Wii U they get stored to SLC but the download manager places these in the code folder currently + const auto ticketPath = ActiveSettings::GetMlcPath(L"usr/title/{:08x}/{:08x}/code/title.tik", (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF)); + uint32 tikFileSize = 0; + std::unique_ptr<FileStream> fileStream(FileStream::openFile2(ticketPath)); + std::vector<uint8> tikData; + if (fileStream) + fileStream->extract(tikData); + if (tikData.size() > 0) + { + NCrypto::ETicketParser eTicket; + if (eTicket.parse(tikData.data(), tikData.size())) + { + for (uint32 i = 0; i < entryCount; i++) + { + uint16 id = entryIds[i]; + if (eTicket.CheckRight(id)) + purchaseBoolArrayOut[i] = 1; + else + purchaseBoolArrayOut[i] = 0; + } + cemuLog_log(LogType::Force, "Using content rights from AOC title.tik"); + return AOC_RESULT::ERROR_OK; + } + else + { + cemuLog_log(LogType::Force, "Unable to parse AOC title.tik"); + } + } + + // fallback: return true for all contentIds + for (uint32 i = 0; i < entryCount; i++) + { + uint16 id = entryIds[i]; + purchaseBoolArrayOut[i] = 1; + } + + return AOC_RESULT::ERROR_OK; + } + + void Initialize() + { + cafeExportRegister("nn_aoc", AOC_CalculateWorkBufferSize, LogType::NN_AOC); + cafeExportRegister("nn_aoc", AOC_ListTitle, LogType::NN_AOC); + + cafeExportRegister("nn_aoc", AOC_OpenTitle, LogType::NN_AOC); + cafeExportRegister("nn_aoc", AOC_CloseTitle, LogType::NN_AOC); + cafeExportRegister("nn_aoc", AOC_GetPurchaseInfo, LogType::NN_AOC); + } + } +} diff --git a/src/Cafe/OS/libs/nn_aoc/nn_aoc.h b/src/Cafe/OS/libs/nn_aoc/nn_aoc.h new file mode 100644 index 00000000..3853c075 --- /dev/null +++ b/src/Cafe/OS/libs/nn_aoc/nn_aoc.h @@ -0,0 +1,7 @@ +namespace nn +{ + namespace aoc + { + void Initialize(); + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_boss/nn_boss.cpp b/src/Cafe/OS/libs/nn_boss/nn_boss.cpp new file mode 100644 index 00000000..045fa82f --- /dev/null +++ b/src/Cafe/OS/libs/nn_boss/nn_boss.cpp @@ -0,0 +1,1804 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/libs/nn_common.h" +#include "Cafe/OS/libs/nn_act/nn_act.h" +#include "Cafe/OS/libs/coreinit/coreinit_IOS.h" +#include "Cafe/OS/libs/coreinit/coreinit_MEM.h" +#include "Cafe/IOSU/legacy/iosu_boss.h" +#include "Cafe/IOSU/legacy/iosu_act.h" +#include "config/ActiveSettings.h" +#include "Cafe/CafeSystem.h" +#include "Cafe/Filesystem/fsc.h" + +namespace nn +{ + typedef uint32 Result; +namespace boss +{ +#define bossPrepareRequest() \ +StackAllocator<iosuBossCemuRequest_t> _buf_bossRequest; \ +StackAllocator<ioBufferVector_t> _buf_bufferVector; \ +iosuBossCemuRequest_t* bossRequest = _buf_bossRequest.GetPointer(); \ +ioBufferVector_t* bossBufferVector = _buf_bufferVector.GetPointer(); \ +memset(bossRequest, 0, sizeof(iosuBossCemuRequest_t)); \ +memset(bossBufferVector, 0, sizeof(ioBufferVector_t)); \ +bossBufferVector->buffer = (uint8*)bossRequest; + + coreinit::OSMutex g_mutex; + sint32 g_initCounter = 0; + bool g_isInitialized = false; + + void freeMem(void* mem) + { + if(mem) + coreinit::default_MEMFreeToDefaultHeap((uint8*)mem - 8); + } + + struct TaskSetting_t + { + static const uint32 kBossCode = 0x7C0; + static const uint32 kBossCodeLen = 0x20; + + static const uint32 kDirectorySizeLimit = 0x7F0; + static const uint32 kDirectoryName = 0x7E0; + static const uint32 kDirectoryNameLen = 0x8; + + //static const uint32 kFileName = 0x7F8; + static const uint32 kNbdlFileName = 0x7F8; + static const uint32 kFileNameLen = 0x20; + + + + static const uint32 kURL = 0x48; + static const uint32 kURLLen = 0x100; + + static const uint32 kClientCert = 0x41; + static const uint32 kCACert = 0x188; + + static const uint32 kServiceToken = 0x590; + static const uint32 kServiceTokenLen = 0x200; + + + uint8 settings[0x1000]; + uint32be uknExt_vTableProbably; // +0x1000 + }; + static_assert(sizeof(TaskSetting_t) == 0x1004); + static_assert(offsetof(TaskSetting_t, uknExt_vTableProbably) == 0x1000, "offsetof(TaskSetting_t, uknExt)"); + + struct NetTaskSetting_t : TaskSetting_t + { + // 0x188 cert1 + 0x188 cert2 + 0x188 cert3 + // 0x190 AddCaCert (3times) char cert[0x80]; + // SetConnectionSetting + // SetFirstLastModifiedTime + }; + static_assert(sizeof(NetTaskSetting_t) == 0x1004); + + struct NbdlTaskSetting_t : NetTaskSetting_t + { + //char fileName[0x20]; // @0x7F8 + }; + static_assert(sizeof(NbdlTaskSetting_t) == 0x1004); + + struct RawUlTaskSetting_t : NetTaskSetting_t + { + static const uint32 kType = 0x12340000; + uint32be ukRaw1; // 0x1004 + uint32be ukRaw2; // 0x1008 + uint32be ukRaw3; // 0x100C + uint8 rawSpace[0x200]; // 0x1010 + }; + static_assert(sizeof(RawUlTaskSetting_t) == 0x1210); + + struct PlayReportSetting_t : RawUlTaskSetting_t + { + static const uint32 kType = 0x12340001; + MEMPTR<void*> ukPlay1; // 0x1210 + uint32be ukPlay2; // 0x1214 + uint32be ukPlay3; // 0x1218 + uint32be ukPlay4; // 0x121C + }; + static_assert(sizeof(PlayReportSetting_t) == 0x1220); + + struct RawDlTaskSetting_t : NetTaskSetting_t + { + static const uint32 kType = 0x12340002; + //char fileName[0x20]; // 0x7F8 + }; + static_assert(sizeof(RawDlTaskSetting_t) == 0x1004); + + struct TitleId_t + { + uint64be u64{}; + }; + static_assert(sizeof(TitleId_t) == 8); + + struct TaskId_t + { + char id[0x8]{}; + }; + static_assert(sizeof(TaskId_t) == 8); + + struct Title_t + { + uint32be accountId{}; // 0x00 + TitleId_t titleId{}; // 0x8 + uint32be someValue = 0x12341000; // 0x10 + }; + static_assert(sizeof(Title_t) == 0x18); + + struct DirectoryName_t + { + char name[0x8]{}; + }; + static_assert(sizeof(DirectoryName_t) == 8); + + struct Account_t + { + uint32be accountId; + uint32be uk1; // global struct + }; + static_assert(sizeof(Account_t) == 8); + + struct Task_t + { + uint32be accountId; // 0x00 + uint32be uk2; // 0x04 + TaskId_t taskId; // 0x08 + TitleId_t titleId; // 0x10 + uint32be ext; // 0x18 + uint32be padding; // 0x1C + }; + static_assert(sizeof(Task_t) == 0x20, "sizeof(Task_t)"); + + namespace TaskId + { + TaskId_t* ctor(TaskId_t* thisptr) + { + if(!thisptr) + { + // thisptr = new TaskId_t + assert_dbg(); + } + + if(thisptr) + { + thisptr->id[0] = 0; + } + + return thisptr; + } + } + + namespace Account + { + Account_t* ctor(Account_t* thisptr, uint32 accountId) + { + if (!thisptr) + { + // thisptr = new TaskId_t + assert_dbg(); + } + + thisptr->accountId = accountId; + thisptr->uk1 = 0x12340010; + return thisptr; + } + } + + namespace TitleId + { + TitleId_t* ctor(TitleId_t* thisptr, uint64 titleId) + { + if (!thisptr) + { + // thisptr = new TaskId_t + assert_dbg(); + } + + if (thisptr) + { + thisptr->u64 = titleId; + } + + return thisptr; + } + + TitleId_t* ctor(TitleId_t* thisptr) + { + return ctor(thisptr, 0); + } + + bool IsValid(TitleId_t* thisptr) + { + return thisptr->u64 != 0; + } + + TitleId_t* ctor1(TitleId_t* thisptr, uint32 filler, uint64 titleId) + { + return ctor(thisptr); + } + + TitleId_t* ctor2(TitleId_t* thisptr, uint32 filler, uint64 titleId) + { + forceLogDebug_printf("nn_boss_TitleId_ctor2(0x%x)", MEMPTR(thisptr).GetMPTR()); + if (!thisptr) + { + // thisptr = new Task_t + assert_dbg(); + } + + thisptr->u64 = titleId; + return thisptr; + } + + + TitleId_t* cctor(TitleId_t* thisptr, TitleId_t* titleId) + { + forceLogDebug_printf("nn_boss_TitleId_cctor(0x%x)", MEMPTR(thisptr).GetMPTR()); + if (!thisptr) + { + // thisptr = new Task_t + assert_dbg(); + } + + thisptr->u64 = titleId->u64; + + return thisptr; + } + + bool operator_ne(TitleId_t* thisptr, TitleId_t* titleId) + { + forceLogDebug_printf("nn_boss_TitleId_operator_ne(0x%x)", MEMPTR(thisptr).GetMPTR()); + return thisptr->u64 != titleId->u64; + } + } + + namespace TaskSetting + { + bool IsPrivilegedTaskSetting(TaskSetting_t* thisptr) + { + const uint16 value = *(uint16*)&thisptr->settings[0x28]; + return value == 1 || value == 9 || value == 5; + } + + void InitializeSetting(TaskSetting_t* thisptr) + { + memset(thisptr, 0x00, sizeof(TaskSetting_t::settings)); + *(uint32*)&thisptr->settings[0x0C] = 0; + *(uint8*)&thisptr->settings[0x2A] = 0x7D; // timeout? + *(uint32*)&thisptr->settings[0x30] = 0x7080; + *(uint32*)&thisptr->settings[0x8] = 0; + *(uint32*)&thisptr->settings[0x38] = 0; + *(uint32*)&thisptr->settings[0x3C] = 0x76A700; + *(uint32*)&thisptr->settings[0] = 0x76A700; + } + + TaskSetting_t* ctor(TaskSetting_t* thisptr) + { + if(!thisptr) + { + // thisptr = new TaskSetting_t + assert_dbg(); + } + + if (thisptr) + { + thisptr->uknExt_vTableProbably = 0; + InitializeSetting(thisptr); + } + + return thisptr; + } + + + } + + namespace NetTaskSetting + { + Result AddCaCert(NetTaskSetting_t* thisptr, const char* name) + { + if(name == nullptr || strnlen(name, 0x80) == 0x80) + { + forceLogDebug_printf("nn_boss_NetTaskSetting_AddCaCert: name size is invalid"); + return 0xC0203780; + } + + cemu_assert_unimplemented(); + + return 0xA0220D00; + } + + NetTaskSetting_t* ctor(NetTaskSetting_t* thisptr) + { + if (!thisptr) + { + // thisptr = new NetTaskSetting_t + assert_dbg(); + } + + if (thisptr) + { + TaskSetting::ctor(thisptr); + *(uint32*)&thisptr->settings[0x18C] = 0x78; + thisptr->uknExt_vTableProbably = 0; + } + + return thisptr; + } + + Result SetServiceToken(NetTaskSetting_t* thisptr, const uint8* serviceToken) + { + forceLogDebug_printf("nn_boss_NetTaskSetting_SetServiceToken(0x%x, 0x%x)", MEMPTR(thisptr).GetMPTR(), MEMPTR(serviceToken).GetMPTR()); + forceLogDebug_printf("\t->%s", serviceToken); + memcpy(&thisptr->settings[TaskSetting_t::kServiceToken], serviceToken, TaskSetting_t::kServiceTokenLen); + return 0x200080; + } + + Result AddInternalCaCert(NetTaskSetting_t* thisptr, char certId) + { + forceLogDebug_printf("nn_boss_NetTaskSetting_AddInternalCaCert(0x%x, 0x%x)", MEMPTR(thisptr).GetMPTR(), (int)certId); + + uint32 location = TaskSetting_t::kCACert; + for(int i = 0; i < 3; ++i) + { + if(thisptr->settings[location] == 0) + { + thisptr->settings[location] = (uint8)certId; + return 0x200080; + } + + location += TaskSetting_t::kCACert; + } + + forceLogDebug_printf("nn_boss_NetTaskSetting_AddInternalCaCert: can't store certificate"); + return 0xA0220D00; + } + + void SetInternalClientCert(NetTaskSetting_t* thisptr, char certId) + { + forceLogDebug_printf("nn_boss_NetTaskSetting_SetInternalClientCert(0x%x, 0x%x)", MEMPTR(thisptr).GetMPTR(), (int)certId); + thisptr->settings[TaskSetting_t::kClientCert] = (uint8)certId; + } + } + + namespace NbdlTaskSetting // : NetTaskSetting + { + NbdlTaskSetting_t* ctor(NbdlTaskSetting_t* thisptr) + { + if (!thisptr) + { + // thisptr = new NbdlTaskSetting_t + assert_dbg(); + } + + if (thisptr) + { + NetTaskSetting::ctor(thisptr); + thisptr->uknExt_vTableProbably = 0; + } + + return thisptr; + } + + void export_ctor(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(thisptr, NbdlTaskSetting_t, 0); + forceLogDebug_printf("nn_boss_NbdlTaskSetting_ctor"); + ctor(thisptr.GetPtr()); + osLib_returnFromFunction(hCPU, thisptr.GetMPTR()); + } + + void export_Initialize(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(thisptr, NbdlTaskSetting_t, 0); + ppcDefineParamMEMPTR(bossCode, const char, 1); + ppcDefineParamU64(directorySizeLimit, 2); + ppcDefineParamMEMPTR(directoryName, const char, 4); + forceLogDebug_printf("nn_boss_NbdlTaskSetting_Initialize(0x%08x, %s, 0x%llx, 0x%08x)", thisptr.GetMPTR(), bossCode.GetPtr(), directorySizeLimit, directoryName.GetMPTR()); + + if(!bossCode || strnlen(bossCode.GetPtr(), 0x20) == 0x20) + { + osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_BOSS, 0x3780)); + return; + } + + if (directoryName && strnlen(directoryName.GetPtr(), 0x8) == 0x8) + { + osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_BOSS, 0x3780)); + return; + } + + strncpy((char*)&thisptr->settings[TaskSetting_t::kBossCode], bossCode.GetPtr(), TaskSetting_t::kBossCodeLen); + + *(uint64be*)&thisptr->settings[TaskSetting_t::kDirectorySizeLimit] = directorySizeLimit; // uint64be + if(directoryName) + strncpy((char*)&thisptr->settings[TaskSetting_t::kDirectoryName], directoryName.GetPtr(), TaskSetting_t::kDirectoryNameLen); + + osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0x80)); + } + + Result SetFileName(NbdlTaskSetting_t* thisptr, const char* fileName) + { + forceLogDebug_printf("nn_boss_NbdlTaskSetting_t_SetFileName(0x%08x, %s)", MEMPTR(thisptr).GetMPTR(), fileName ? fileName : "\"\""); + + if (!fileName || strnlen(fileName, TaskSetting_t::kFileNameLen) == TaskSetting_t::kFileNameLen) + { + return BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_BOSS, 0x3780); + } + + strncpy((char*)&thisptr->settings[TaskSetting_t::kNbdlFileName], fileName, TaskSetting_t::kFileNameLen); + return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0x80); + } + + } + + namespace RawUlTaskSetting + { + RawUlTaskSetting_t* ctor(RawUlTaskSetting_t* thisptr) + { + forceLogDebug_printf("nn_boss_RawUlTaskSetting_ctor(0x%x) TODO", MEMPTR(thisptr).GetMPTR()); + if (!thisptr) + { + // thisptr = new RawUlTaskSetting_t + assert_dbg(); + } + + if (thisptr) + { + NetTaskSetting::ctor(thisptr); + thisptr->uknExt_vTableProbably = RawUlTaskSetting_t::kType; + thisptr->ukRaw1 = 0; + thisptr->ukRaw2 = 0; + thisptr->ukRaw3 = 0; + memset(thisptr->rawSpace, 0x00, 0x200); + } + + return thisptr; + } + } + + namespace RawDlTaskSetting + { + RawDlTaskSetting_t* ctor(RawDlTaskSetting_t* thisptr) + { + forceLogDebug_printf("nn_boss_RawDlTaskSetting_ctor(0x%x) TODO", MEMPTR(thisptr).GetMPTR()); + if (!thisptr) + { + // thisptr = new RawDlTaskSetting_t + assert_dbg(); + } + + if (thisptr) + { + NetTaskSetting::ctor(thisptr); + thisptr->uknExt_vTableProbably = RawDlTaskSetting_t::kType; + } + + return thisptr; + } + + Result Initialize(RawDlTaskSetting_t* thisptr, const char* url, bool newArrival, bool led, const char* fileName, const char* directoryName) + { + forceLogDebug_printf("nn_boss_RawDlTaskSetting_Initialize(0x%x, 0x%x, %d, %d, 0x%x, 0x%x)", MEMPTR(thisptr).GetMPTR(), MEMPTR(url).GetMPTR(), newArrival, led, MEMPTR(fileName).GetMPTR(), MEMPTR(directoryName).GetMPTR()); + if (!url) + { + return 0xC0203780; + } + + if (strnlen(url, TaskSetting_t::kURLLen) == TaskSetting_t::kURLLen) + { + return 0xC0203780; + } + + forceLogDebug_printf("\t-> url: %s", url); + + if (fileName && strnlen(fileName, TaskSetting_t::kFileNameLen) == TaskSetting_t::kFileNameLen) + { + return 0xC0203780; + } + + if (directoryName && strnlen(directoryName, TaskSetting_t::kDirectoryNameLen) == TaskSetting_t::kDirectoryNameLen) + { + return 0xC0203780; + } + + strncpy((char*)thisptr + TaskSetting_t::kURL, url, TaskSetting_t::kURLLen); + thisptr->settings[0x147] = '\0'; + + if (fileName) + strncpy((char*)thisptr + 0x7D0, fileName, TaskSetting_t::kFileNameLen); + else + strncpy((char*)thisptr + 0x7D0, "rawcontent.dat", TaskSetting_t::kFileNameLen); + thisptr->settings[0x7EF] = '\0'; + + forceLogDebug_printf("\t-> filename: %s", (char*)thisptr + 0x7D0); + + if (directoryName) + { + strncpy((char*)thisptr + 0x7C8, directoryName, TaskSetting_t::kDirectoryNameLen); + thisptr->settings[0x7CF] = '\0'; + forceLogDebug_printf("\t-> directoryName: %s", (char*)thisptr + 0x7C8); + } + + thisptr->settings[0x7C0] = newArrival; + thisptr->settings[0x7C1] = led; + *(uint16be*)&thisptr->settings[0x28] = 0x3; + return 0x200080; + } + } + + namespace PlayReportSetting // : NetTaskSetting + { + void export_ctor(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(thisptr, PlayReportSetting_t, 0); + forceLogDebug_printf("nn_boss_PlayReportSetting_ctor TODO"); + if (!thisptr) + { + assert_dbg(); + } + + if (thisptr) + { + RawUlTaskSetting::ctor(thisptr.GetPtr()); + thisptr->uknExt_vTableProbably = PlayReportSetting_t::kType; + thisptr->ukPlay1 = nullptr; + thisptr->ukPlay2 = 0; + thisptr->ukPlay3 = 0; + thisptr->ukPlay4 = 0; + } + + osLib_returnFromFunction(hCPU, thisptr.GetMPTR()); + } + + void export_Initialize(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(thisptr, PlayReportSetting_t, 0); + ppcDefineParamMEMPTR(ptr, void*, 1); + ppcDefineParamU32(value, 2); + ppcDefineParamMEMPTR(directoryName, const char, 4); + //forceLogDebug_printf("nn_boss_PlayReportSetting_Initialize(0x%08x, %s, 0x%llx, 0x%08x)", thisptr.GetMPTR(), ptr.GetPtr(), directorySizeLimit, directoryName.GetMPTR()); + + if(!ptr || value == 0 || value > 0x19000) + { + forceLogDebug_printf("nn_boss_PlayReportSetting_Initialize: invalid parameter"); + osLib_returnFromFunction(hCPU, 0); + } + + *ptr.GetPtr<uint8>() = 0; + + *(uint16be*)&thisptr->settings[0x28] = 6; + *(uint16be*)&thisptr->settings[0x2B] |= 0x3; + *(uint16be*)&thisptr->settings[0x2C] |= 0xA; + *(uint32be*)&thisptr->settings[0x7C0] |= 2; + + thisptr->ukPlay1 = ptr; + thisptr->ukPlay2 = value; + thisptr->ukPlay3 = 0; + thisptr->ukPlay4 = 0; + + // TODO + osLib_returnFromFunction(hCPU, 0); + } + + void export_Set(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(thisptr, PlayReportSetting_t, 0); + ppcDefineParamMEMPTR(key, const char, 1); + ppcDefineParamU32(value, 2); + + // TODO + forceLogDebug_printf("nn_boss_PlayReportSetting_Set(0x%08x, %s, 0x%x) TODO", thisptr.GetMPTR(), key.GetPtr(), value); + + osLib_returnFromFunction(hCPU, 1); + } + + + } + + namespace Title + { + Title_t* ctor(Title_t* thisptr) + { + forceLogDebug_printf("nn_boss_Title_ctor(0x%x)", MEMPTR(thisptr).GetMPTR()); + if (!thisptr) + { + // thisptr = new Task_t + assert_dbg(); + } + + *thisptr = {}; + + return thisptr; + } + } + + namespace DirectoryName + { + DirectoryName_t* ctor(DirectoryName_t* thisptr) + { + forceLogDebug_printf("nn_boss_DirectoryName_ctor(0x%x)", MEMPTR(thisptr).GetMPTR()); + if (!thisptr) + { + // thisptr = new Task_t + assert_dbg(); + } + + memset(thisptr->name, 0x00, 0x8); + + return thisptr; + } + + const char* operator_const_char(DirectoryName_t* thisptr) + { + forceLogDebug_printf("nn_boss_DirectoryName_operator_const_char(0x%x)", MEMPTR(thisptr).GetMPTR()); + return thisptr->name; + } + } + + namespace Task + { + + Result Initialize(Task_t* thisptr, const char* taskId, uint32 accountId) + { + if(!taskId || strnlen(taskId, 0x8) == 8) + { + return BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_BOSS, 0x3780); + } + + thisptr->accountId = accountId; + strncpy(thisptr->taskId.id, taskId, 0x08); + return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0x80); + } + + Result Initialize(Task_t* thisptr, uint8 slot, const char* taskId) + { + const uint32 accountId = slot == 0 ? 0 : act::GetPersistentIdEx(slot); + return Initialize(thisptr, taskId, accountId); + } + + Result Initialize(Task_t* thisptr, const char* taskId) + { + return Initialize(thisptr, taskId, 0); + } + + void export_Initialize3(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(thisptr, Task_t, 0); + ppcDefineParamMEMPTR(taskId, const char, 1); + ppcDefineParamU32(accountId, 2); + forceLogDebug_printf("nn_boss_Task_Initialize3(0x%08x, %s, 0x%x)", thisptr.GetMPTR(), taskId.GetPtr(), accountId); + const Result result = Initialize(thisptr.GetPtr(), taskId.GetPtr(), accountId); + osLib_returnFromFunction(hCPU, result); + } + + void export_Initialize2(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(thisptr, Task_t, 0); + ppcDefineParamU8(slotId, 1); + ppcDefineParamMEMPTR(taskId, const char, 2); + forceLogDebug_printf("nn_boss_Task_Initialize2(0x%08x, %d, %s)", thisptr.GetMPTR(), slotId, taskId.GetPtr()); + const Result result = Initialize(thisptr.GetPtr(), slotId, taskId.GetPtr()); + osLib_returnFromFunction(hCPU, result); + } + + void export_Initialize1(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(thisptr, Task_t, 0); + ppcDefineParamMEMPTR(taskId, const char, 1); + forceLogDebug_printf("nn_boss_Task_Initialize1(0x%08x, %s)", thisptr.GetMPTR(), taskId.GetPtr()); + const Result result = Initialize(thisptr.GetPtr(), taskId.GetPtr()); + osLib_returnFromFunction(hCPU, result); + } + + + Task_t* ctor(Task_t* thisptr, const char* taskId, uint32 accountId) + { + if (!thisptr) + { + // thisptr = new Task_t + assert_dbg(); + } + + if (thisptr) + { + thisptr->accountId = 0; + thisptr->ext = 0; // dword_10002174 + TaskId::ctor(&thisptr->taskId); + TitleId::ctor(&thisptr->titleId, 0); + cemu_assert_debug(NN_RESULT_IS_SUCCESS(Initialize(thisptr, taskId, accountId))); + } + + return thisptr; + } + + Task_t* ctor(Task_t* thisptr, uint8 slot, const char* taskId) + { + if (!thisptr) + { + // thisptr = new Task_t + assert_dbg(); + } + + if (thisptr) + { + thisptr->accountId = 0; + thisptr->ext = 0; // dword_10002174 + TaskId::ctor(&thisptr->taskId); + TitleId::ctor(&thisptr->titleId, 0); + cemu_assert_debug(NN_RESULT_IS_SUCCESS(Initialize(thisptr, slot, taskId))); + } + + return thisptr; + } + + Task_t* ctor(Task_t* thisptr, const char* taskId) + { + if (!thisptr) + { + // thisptr = new Task_t + assert_dbg(); + } + + if (thisptr) + { + thisptr->accountId = 0; + thisptr->ext = 0; // dword_10002174 + TaskId::ctor(&thisptr->taskId); + TitleId::ctor(&thisptr->titleId, 0); + cemu_assert_debug(NN_RESULT_IS_SUCCESS(Initialize(thisptr, taskId))); + } + + return thisptr; + } + + Task_t* ctor(Task_t* thisptr) + { + if (!thisptr) + { + // thisptr = new Task_t + assert_dbg(); + } + + if (thisptr) + { + thisptr->accountId = 0; + thisptr->ext = 0; // dword_10002174 + TaskId::ctor(&thisptr->taskId); + TitleId::ctor(&thisptr->titleId, 0); + memset(&thisptr->taskId, 0x00, sizeof(TaskId_t)); + } + + return thisptr; + } + + void export_ctor(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(thisptr, Task_t, 0); + forceLogDebug_printf("nn_boss_Task_ctor(0x%08x)", thisptr.GetMPTR()); + ctor(thisptr.GetPtr()); + osLib_returnFromFunction(hCPU, thisptr.GetMPTR()); + } + + void export_Run(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(thisptr, Task_t, 0); + ppcDefineParamU8(isForegroundRun, 1); + forceLogDebug_printf("nn_boss_Task_Run(0x%08x, %d)", thisptr.GetMPTR(), isForegroundRun); + if (isForegroundRun != 0) + { + //peterBreak(); + forceLogDebug_printf("export_Run foreground run"); + } + + bossPrepareRequest(); + bossRequest->requestCode = IOSU_NN_BOSS_TASK_RUN; + bossRequest->accountId = thisptr->accountId; + bossRequest->taskId = thisptr->taskId.id; + bossRequest->titleId = thisptr->titleId.u64; + bossRequest->bool_parameter = isForegroundRun != 0; + + __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); + + osLib_returnFromFunction(hCPU, 0); + } + + void export_StartScheduling(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(thisptr, Task_t, 0); + ppcDefineParamU8(executeImmediately, 1); + forceLogDebug_printf("nn_boss_Task_StartScheduling(0x%08x, %d)", thisptr.GetMPTR(), executeImmediately); + + bossPrepareRequest(); + bossRequest->requestCode = IOSU_NN_BOSS_TASK_START_SCHEDULING; + bossRequest->accountId = thisptr->accountId; + bossRequest->taskId = thisptr->taskId.id; + bossRequest->titleId = thisptr->titleId.u64; + bossRequest->bool_parameter = executeImmediately != 0; + + __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); + + osLib_returnFromFunction(hCPU, 0); + } + + void export_StopScheduling(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(thisptr, Task_t, 0); + forceLogDebug_printf("nn_boss_Task_StopScheduling(0x%08x)", thisptr.GetMPTR()); + + bossPrepareRequest(); + bossRequest->requestCode = IOSU_NN_BOSS_TASK_STOP_SCHEDULING; + bossRequest->accountId = thisptr->accountId; + bossRequest->taskId = thisptr->taskId.id; + bossRequest->titleId = thisptr->titleId.u64; + + __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); + + osLib_returnFromFunction(hCPU, 0); + } + + void export_IsRegistered(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(thisptr, Task_t, 0); + + bossPrepareRequest(); + bossRequest->requestCode = IOSU_NN_BOSS_TASK_IS_REGISTERED; + bossRequest->accountId = thisptr->accountId; + bossRequest->titleId = thisptr->titleId.u64; + bossRequest->taskId = thisptr->taskId.id; + + __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); + + forceLogDebug_printf("nn_boss_Task_IsRegistered(0x%08x) -> %d", thisptr.GetMPTR(), bossRequest->returnCode); + + osLib_returnFromFunction(hCPU, bossRequest->returnCode); + } + + void export_Wait(PPCInterpreter_t* hCPU) + { + // Wait__Q3_2nn4boss4TaskFUiQ3_2nn4boss13TaskWaitState + ppcDefineParamMEMPTR(thisptr, Task_t, 0); + ppcDefineParamU32(timeout, 1); + ppcDefineParamU32(waitState, 2); + forceLogDebug_printf("nn_boss_Task_Wait(0x%08x, 0x%x, %d)", thisptr.GetMPTR(), timeout, waitState); + + bossPrepareRequest(); + bossRequest->requestCode = IOSU_NN_BOSS_TASK_WAIT; + bossRequest->titleId = thisptr->titleId.u64; + bossRequest->taskId = thisptr->taskId.id; + bossRequest->timeout = timeout; + bossRequest->waitState = waitState; + + __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); + + osLib_returnFromFunction(hCPU, bossRequest->returnCode); + + //osLib_returnFromFunction(hCPU, 1); // 0 -> timeout, 1 -> wait condition met + } + + void export_RegisterForImmediateRun(PPCInterpreter_t* hCPU) + { + // RegisterForImmediateRun__Q3_2nn4boss4TaskFRCQ3_2nn4boss11TaskSetting + ppcDefineParamMEMPTR(thisptr, Task_t, 0); + ppcDefineParamMEMPTR(settings, TaskSetting_t, 1); + forceLogDebug_printf("nn_boss_Task_RegisterForImmediateRun(0x%08x, 0x%08x)", thisptr.GetMPTR(), settings.GetMPTR()); + + bossPrepareRequest(); + bossRequest->requestCode = IOSU_NN_BOSS_TASK_REGISTER; + bossRequest->accountId = thisptr->accountId; + bossRequest->taskId = thisptr->taskId.id; + bossRequest->settings = settings.GetPtr(); + bossRequest->uk1 = 0xC00; + + if (TaskSetting::IsPrivilegedTaskSetting(settings.GetPtr())) + bossRequest->titleId = thisptr->titleId.u64; + + const sint32 result = __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); + osLib_returnFromFunction(hCPU, result); + } + + void export_Unregister(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(thisptr, Task_t, 0); + forceLogDebug_printf("nn_boss_Task_Unregister(0x%08x)", thisptr.GetMPTR()); + + bossPrepareRequest(); + bossRequest->requestCode = IOSU_NN_BOSS_TASK_UNREGISTER; + bossRequest->accountId = thisptr->accountId; + bossRequest->taskId = thisptr->taskId.id; + bossRequest->titleId = thisptr->titleId.u64; + + const sint32 result = __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); + osLib_returnFromFunction(hCPU, result); + } + + void export_Register(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(thisptr, Task_t, 0); + ppcDefineParamMEMPTR(settings, TaskSetting_t, 1); + forceLogDebug_printf("nn_boss_Task_Register(0x%08x, 0x%08x)", thisptr.GetMPTR(), settings.GetMPTR()); + + if (hCPU->gpr[4] == 0) + { + forceLogDebug_printf("nn_boss_Task_Register - crash workaround (fix me)"); + osLib_returnFromFunction(hCPU, 0); + return; + } + + bossPrepareRequest(); + bossRequest->requestCode = IOSU_NN_BOSS_TASK_REGISTER_FOR_IMMEDIATE_RUN; + bossRequest->accountId = thisptr->accountId; + bossRequest->taskId = thisptr->taskId.id; + bossRequest->settings = settings.GetPtr(); + bossRequest->uk1 = 0xC00; + + if(TaskSetting::IsPrivilegedTaskSetting(settings.GetPtr())) + bossRequest->titleId = thisptr->titleId.u64; + + __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); + + osLib_returnFromFunction(hCPU, bossRequest->returnCode); + } + + + void export_GetTurnState(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(thisptr, Task_t, 0); + ppcDefineParamMEMPTR(execCount, uint32be, 1); + + bossPrepareRequest(); + bossRequest->requestCode = IOSU_NN_BOSS_TASK_GET_TURN_STATE; + bossRequest->accountId = thisptr->accountId; + bossRequest->taskId = thisptr->taskId.id; + bossRequest->titleId = thisptr->titleId.u64; + + __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); + + if (execCount) + *execCount = bossRequest->u32.exec_count; + + forceLogDebug_printf("nn_boss_Task_GetTurnState(0x%08x, 0x%08x) -> %d", thisptr.GetMPTR(), execCount.GetMPTR(), bossRequest->u32.result); + + osLib_returnFromFunction(hCPU, bossRequest->u32.result); + //osLib_returnFromFunction(hCPU, 7); // 7 -> finished? 0x11 -> Error (Splatoon doesn't like it when we return 0x11 for Nbdl tasks) RETURN FINISHED + } + + void export_GetContentLength(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(thisptr, Task_t, 0); + ppcDefineParamMEMPTR(execCount, uint32be, 1); + + bossPrepareRequest(); + bossRequest->requestCode = IOSU_NN_BOSS_TASK_GET_CONTENT_LENGTH; + bossRequest->accountId = thisptr->accountId; + bossRequest->taskId = thisptr->taskId.id; + bossRequest->titleId = thisptr->titleId.u64; + + __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); + + if (execCount) + *execCount = bossRequest->u64.exec_count; + + forceLogDebug_printf("nn_boss_Task_GetContentLength(0x%08x, 0x%08x) -> 0x%llx", thisptr.GetMPTR(), execCount.GetMPTR(), bossRequest->u64.result); + + osLib_returnFromFunction64(hCPU, bossRequest->u64.result); + } + + void export_GetProcessedLength(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(thisptr, Task_t, 0); + ppcDefineParamMEMPTR(execCount, uint32be, 1); + + bossPrepareRequest(); + bossRequest->requestCode = IOSU_NN_BOSS_TASK_GET_PROCESSED_LENGTH; + bossRequest->accountId = thisptr->accountId; + bossRequest->taskId = thisptr->taskId.id; + bossRequest->titleId = thisptr->titleId.u64; + + __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); + + if (execCount) + *execCount = bossRequest->u64.exec_count; + + forceLogDebug_printf("nn_boss_Task_GetProcessedLength(0x%08x, 0x%08x) -> 0x%llx", thisptr.GetMPTR(), execCount.GetMPTR(), bossRequest->u64.result); + + osLib_returnFromFunction64(hCPU, bossRequest->u64.result); + } + + void export_GetHttpStatusCode(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(thisptr, Task_t, 0); + ppcDefineParamMEMPTR(execCount, uint32be, 1); + + bossPrepareRequest(); + bossRequest->requestCode = IOSU_NN_BOSS_TASK_GET_HTTP_STATUS_CODE; + bossRequest->accountId = thisptr->accountId; + bossRequest->taskId = thisptr->taskId.id; + bossRequest->titleId = thisptr->titleId.u64; + + __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); + + if (execCount) + *execCount = bossRequest->u32.exec_count; + + forceLogDebug_printf("nn_boss_Task_GetHttpStatusCode(0x%08x, 0x%08x) -> %d", thisptr.GetMPTR(), execCount.GetMPTR(), bossRequest->u32.result); + + osLib_returnFromFunction(hCPU, bossRequest->u32.result); + } + } + + struct PrivilegedTask_t : Task_t + { + + }; + static_assert(sizeof(PrivilegedTask_t) == 0x20); + + struct AlmightyTask_t : PrivilegedTask_t + { + + }; + static_assert(sizeof(AlmightyTask_t) == 0x20); + + namespace PrivilegedTask + { + PrivilegedTask_t* ctor(PrivilegedTask_t*thisptr) + { + if (!thisptr) + assert_dbg(); // new + + Task::ctor(thisptr); + thisptr->ext = 0x10003a50; + return thisptr; + } + } + + namespace AlmightyTask + { + AlmightyTask_t* ctor(AlmightyTask_t* thisptr) + { + if (!thisptr) + assert_dbg(); // new + + PrivilegedTask::ctor(thisptr); + thisptr->ext = 0x10002a0c; + return thisptr; + } + void dtor(AlmightyTask_t* thisptr) + { + if (thisptr) + freeMem(thisptr); + } + + uint32 Initialize(AlmightyTask_t* thisptr, TitleId_t* titleId, const char* taskId, uint32 accountId) + { + if (!thisptr) + return 0xc0203780; + + thisptr->accountId = accountId; + thisptr->titleId.u64 = titleId->u64; + strncpy(thisptr->taskId.id, taskId, 8); + thisptr->taskId.id[7] = 0x00; + + return 0x200080; + } + } + + Result InitializeImpl() + { + // if( Initialize(IpcClientCafe*) ) ... + g_isInitialized = true; + return 0; + } + + void export_IsInitialized(PPCInterpreter_t* hCPU) + { + osLib_returnFromFunction(hCPU, (uint32)g_isInitialized); + } + + Result Initialize() + { + Result result; + coreinit::OSLockMutex(&g_mutex); + + if(g_initCounter != 0 || NN_RESULT_IS_SUCCESS((result=InitializeImpl()))) + { + g_initCounter++; + result = 0; + } + + coreinit::OSUnlockMutex(&g_mutex); + return result; + } + + void export_Initialize(PPCInterpreter_t* hCPU) + { + forceLogDebug_printf("nn_boss_Initialize()"); + osLib_returnFromFunction(hCPU, Initialize()); + } + + void export_GetBossState(PPCInterpreter_t* hCPU) + { + forceLogDebug_printf("nn_boss.GetBossState() - stub"); + osLib_returnFromFunction(hCPU, 7); + } + + enum StorageKind + { + kStorageKind_NBDL, + kStorageKind_RawDl, + }; + + namespace Storage + { + struct bossStorage_t + { + /* +0x00 */ uint32be accountId; + /* +0x04 */ uint32be storageKind; + /* +0x08 */ uint8 ukn08Array[3]; + /* +0x0B */ char storageName[8]; + uint8 ukn13; + uint8 ukn14; + uint8 ukn15; + uint8 ukn16; + uint8 ukn17; + /* +0x18 */ + nn::boss::TitleId_t titleId; + uint32be ukn20; // pointer to some global struct + uint32be ukn24; + }; + + static_assert(sizeof(bossStorage_t) == 0x28); + static_assert(offsetof(bossStorage_t, storageKind) == 0x04); + static_assert(offsetof(bossStorage_t, ukn08Array) == 0x08); + static_assert(offsetof(bossStorage_t, storageName) == 0x0B); + static_assert(offsetof(bossStorage_t, titleId) == 0x18); + + bossStorage_t* ctor(bossStorage_t* thisptr) + { + forceLogDebug_printf("nn_boss_Storage_ctor(0x%x)", MEMPTR(thisptr).GetMPTR()); + if (!thisptr) + { + // thisptr = new RawDlTaskSetting_t + assert_dbg(); + } + + if (thisptr) + { + thisptr->titleId.u64 = 0; + thisptr->ukn20 = 0x10000a64; + } + + return thisptr; + } + + void nnBossStorage_prepareTitleId(bossStorage_t* storage) + { + if (storage->titleId.u64 != 0) + return; + storage->titleId.u64 = CafeSystem::GetForegroundTitleId(); + } + + Result Initialize(bossStorage_t* thisptr, const char* dirName, uint32 accountId, StorageKind type) + { + if (!dirName) + return 0xC0203780; + + forceLogDebug_printf("boss::Storage::Initialize(%s, 0x%08x, %d)", dirName, accountId, type); + + thisptr->storageKind = type; + thisptr->titleId.u64 = 0; + + memset(thisptr->storageName, 0, 0x8); + strncpy(thisptr->storageName, dirName, 0x8); + thisptr->storageName[7] = '\0'; + + thisptr->accountId = accountId; + + nnBossStorage_prepareTitleId(thisptr); // usually not done like this + + return 0x200080; + } + + Result Initialize2(bossStorage_t* thisptr, const char* dirName, StorageKind type) + { + return Initialize(thisptr, dirName, 0, type); + } + } + + using Storage_t = Storage::bossStorage_t; + struct AlmightyStorage_t : Storage_t + { + }; + static_assert(sizeof(AlmightyStorage_t) == 0x28); + + namespace AlmightyStorage + { + AlmightyStorage_t* ctor(AlmightyStorage_t* thisptr) + { + forceLogDebug_printf("nn_boss_AlmightyStorage_ctor(0x%x)", MEMPTR(thisptr).GetMPTR()); + if (!thisptr) + { + // thisptr = new RawDlTaskSetting_t + assert_dbg(); + } + + if (thisptr) + { + Storage::ctor(thisptr); + thisptr->ukn20 = 0x100028a4; + } + + return thisptr; + } + + uint32 Initialize(AlmightyStorage_t* thisptr, TitleId_t* titleId, const char* storageName, uint32 accountId, StorageKind storageKind) + { + forceLogDebug_printf("nn_boss_AlmightyStorage_Initialize(0x%x)", MEMPTR(thisptr).GetMPTR()); + if (!thisptr) + return 0xc0203780; + + thisptr->accountId = accountId; + thisptr->storageKind = storageKind; + thisptr->titleId.u64 = titleId->u64; + + strncpy(thisptr->storageName, storageName, 8); + thisptr->storageName[0x7] = 0x00; + + return 0x200080; + } + } +} +} + +// Storage + +struct bossDataName_t +{ + char name[32]; +}; + +static_assert(sizeof(bossDataName_t) == 0x20); + +struct bossStorageFadEntry_t +{ + char name[32]; + uint32be fileNameId; + uint32 ukn24; + uint32 ukn28; + uint32 ukn2C; + uint32 ukn30; + uint32be timestampRelated; // guessed +}; + +// __ct__Q3_2nn4boss8DataNameFv +void nnBossDataNameExport_ct(PPCInterpreter_t* hCPU) +{ + ppcDefineParamStructPtr(dataName, bossDataName_t, 0); + memset(dataName, 0, sizeof(bossDataName_t)); + osLib_returnFromFunction(hCPU, memory_getVirtualOffsetFromPointer(dataName)); +} + +// __opPCc__Q3_2nn4boss8DataNameCFv +void nnBossDataNameExport_opPCc(PPCInterpreter_t* hCPU) +{ + ppcDefineParamStructPtr(dataName, bossDataName_t, 0); + osLib_returnFromFunction(hCPU, memory_getVirtualOffsetFromPointer(dataName->name)); +} + +void nnBossStorageExport_ct(PPCInterpreter_t* hCPU) +{ + ppcDefineParamStructPtr(storage, nn::boss::Storage::bossStorage_t, 0); + forceLogDebug_printf("Constructor for boss storage called"); + // todo + memset(storage, 0, sizeof(nn::boss::Storage::bossStorage_t)); + osLib_returnFromFunction(hCPU, memory_getVirtualOffsetFromPointer(storage)); +} + +void nnBossStorageExport_exist(PPCInterpreter_t* hCPU) +{ + ppcDefineParamStructPtr(storage, nn::boss::Storage::bossStorage_t, 0); + forceLogDebug_printf("nn_boss.Storage_Exist(...) TODO"); + + // todo + osLib_returnFromFunction(hCPU, 1); +} + +#define FAD_ENTRY_MAX_COUNT 512 + +FSCVirtualFile* nnBossStorageFile_open(nn::boss::Storage::bossStorage_t* storage, uint32 fileNameId) +{ + char storageFilePath[1024]; + sprintf(storageFilePath, "/cemuBossStorage/%08x/%08x/user/common/data/%s/%08x", (uint32)(storage->titleId.u64 >> 32), (uint32)(storage->titleId.u64), storage->storageName, fileNameId); + sint32 fscStatus; + FSCVirtualFile* fscStorageFile = fsc_open(storageFilePath, FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION | FSC_ACCESS_FLAG::WRITE_PERMISSION, &fscStatus); + return fscStorageFile; +} + +bossStorageFadEntry_t* nnBossStorageFad_getTable(nn::boss::Storage::bossStorage_t* storage) +{ + const auto accountId = ActiveSettings::GetPersistentId(); + char fadPath[1024]; + sprintf(fadPath, "/cemuBossStorage/%08x/%08x/user/common/%08x/%s/fad.db", (uint32)(storage->titleId.u64 >> 32), (uint32)(storage->titleId.u64), accountId, storage->storageName); + + sint32 fscStatus; + FSCVirtualFile* fscFadFile = fsc_open(fadPath, FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus); + if (!fscFadFile) + { + return nullptr; + } + // skip first 8 bytes + fsc_setFileSeek(fscFadFile, 8); + // read entries + bossStorageFadEntry_t* fadTable = (bossStorageFadEntry_t*)malloc(sizeof(bossStorageFadEntry_t)*FAD_ENTRY_MAX_COUNT); + memset(fadTable, 0, sizeof(bossStorageFadEntry_t)*FAD_ENTRY_MAX_COUNT); + fsc_readFile(fscFadFile, fadTable, sizeof(bossStorageFadEntry_t)*FAD_ENTRY_MAX_COUNT); + fsc_close(fscFadFile); + return fadTable; +} + +// Find index of entry by name. Returns -1 if not found +sint32 nnBossStorageFad_getIndexByName(bossStorageFadEntry_t* fadTable, char* name) +{ + for (sint32 i = 0; i < FAD_ENTRY_MAX_COUNT; i++) + { + if (fadTable[i].name[0] == '\0') + continue; + if (strncmp(name, fadTable[i].name, 0x20) == 0) + { + return i; + } + } + return -1; +} + +bool nnBossStorageFad_getEntryByName(nn::boss::Storage::bossStorage_t* storage, char* name, bossStorageFadEntry_t* fadEntry) +{ + bossStorageFadEntry_t* fadTable = nnBossStorageFad_getTable(storage); + if (fadTable) + { + sint32 entryIndex = nnBossStorageFad_getIndexByName(fadTable, name); + if (entryIndex >= 0) + { + memcpy(fadEntry, fadTable + entryIndex, sizeof(bossStorageFadEntry_t)); + free(fadTable); + return true; + } + free(fadTable); + } + return false; +} + +void nnBossStorageExport_getDataList(PPCInterpreter_t* hCPU) +{ + ppcDefineParamStructPtr(storage, nn::boss::Storage::bossStorage_t, 0); + ppcDefineParamStructPtr(dataList, bossDataName_t, 1); + ppcDefineParamS32(maxEntries, 2); + ppcDefineParamU32BEPtr(outputEntryCount, 3); + forceLogDebug_printf("boss storage getDataList()"); + + // initialize titleId of storage if not already done + nnBossStorage_prepareTitleId(storage); + + // load fad.db + bossStorageFadEntry_t* fadTable = nnBossStorageFad_getTable(storage); + if (fadTable) + { + sint32 validEntryCount = 0; + for (sint32 i = 0; i < FAD_ENTRY_MAX_COUNT; i++) + { + if( fadTable[i].name[0] == '\0' ) + continue; + memcpy(dataList[validEntryCount].name, fadTable[i].name, 0x20); + validEntryCount++; + if (validEntryCount >= maxEntries) + break; + } + *outputEntryCount = validEntryCount; + free(fadTable); + } + else + { + // could not load fad table + *outputEntryCount = 0; + } + osLib_returnFromFunction(hCPU, 0); // error code +} + +// NsData + +typedef struct +{ + /* +0x00 */ char name[0x20]; + /* +0x20 */ nn::boss::Storage::bossStorage_t storage; + /* +0x48 */ uint64 readIndex; + /* +0x50 */ uint32 ukn50; // some pointer to a global struct + /* +0x54 */ uint32 ukn54; +}nsData_t; + +void nnBossNsDataExport_ct(PPCInterpreter_t* hCPU) +{ + forceLogDebug_printf("nnBossNsDataExport_ct"); + ppcDefineParamStructPtr(nsData, nsData_t, 0); + if (!nsData) + assert_dbg(); + + + nsData->ukn50 = 0x10000530; + + memset(nsData->name, 0, 0x20); + + nsData->storage.ukn20 = 0x10000798; + nsData->storage.titleId.u64 = 0; + + nsData->readIndex = 0; + + osLib_returnFromFunction(hCPU, memory_getVirtualOffsetFromPointer(nsData)); +} + +void nnBossNsDataExport_initialize(PPCInterpreter_t* hCPU) +{ + ppcDefineParamStructPtr(nsData, nsData_t, 0); + ppcDefineParamStructPtr(storage, nn::boss::Storage::bossStorage_t, 1); + ppcDefineParamStr(dataName, 2); + + if(dataName == nullptr) + { + if (storage->storageKind != 1) + { + osLib_returnFromFunction(hCPU, 0xC0203780); + return; + } + } + + nsData->storage.accountId = storage->accountId; + nsData->storage.storageKind = storage->storageKind; + + memcpy(nsData->storage.ukn08Array, storage->ukn08Array, 3); + memcpy(nsData->storage.storageName, storage->storageName, 8); + + nsData->storage.titleId.u64 = storage->titleId.u64; + + nsData->storage = *storage; + + if (dataName != nullptr || storage->storageKind != 1) + strncpy(nsData->name, dataName, 0x20); + else + strncpy(nsData->name, "rawcontent.dat", 0x20); + nsData->name[0x1F] = '\0'; + + nsData->readIndex = 0; + + forceLogDebug_printf("nnBossNsDataExport_initialize: %s", nsData->name); + + osLib_returnFromFunction(hCPU, 0x200080); +} + +std::string nnBossNsDataExport_GetPath(nsData_t* nsData) +{ + uint32 accountId = nsData->storage.accountId; + if (accountId == 0) + accountId = iosuAct_getAccountIdOfCurrentAccount(); + + uint64 title_id = nsData->storage.titleId.u64; + if (title_id == 0) + title_id = CafeSystem::GetForegroundTitleId(); + + fs::path path = fmt::format(L"cemuBossStorage/{:08x}/{:08x}/user/{:08x}", (uint32)(title_id >> 32), (uint32)(title_id & 0xFFFFFFFF), accountId); + path /= nsData->storage.storageName; + path /= nsData->name; + return path.string(); +} + +void nnBossNsDataExport_DeleteRealFileWithHistory(PPCInterpreter_t* hCPU) +{ + ppcDefineParamStructPtr(nsData, nsData_t, 0); + forceLogDebug_printf("nn_boss.NsData_DeleteRealFileWithHistory(...)"); + + if (nsData->storage.storageKind == nn::boss::kStorageKind_NBDL) + { + // todo + forceLog_printf("BOSS NBDL: Unsupported delete"); + } + else + { + sint32 fscStatus = FSC_STATUS_OK; + std::string filePath = nnBossNsDataExport_GetPath(nsData).c_str(); + fsc_remove((char*)filePath.c_str(), &fscStatus); + if (fscStatus != 0) + forceLog_printf("Unhandeled FSC status in BOSS DeleteRealFileWithHistory()"); + } + osLib_returnFromFunction(hCPU, 0); +} + +void nnBossNsDataExport_Exist(PPCInterpreter_t* hCPU) +{ + forceLogDebug_printf("nn_boss.NsData_Exist(...)"); + ppcDefineParamStructPtr(nsData, nsData_t, 0); + + bool fileExists = false; + if(nsData->storage.storageKind == nn::boss::kStorageKind_NBDL) + { + // check if name is present in fad table + bossStorageFadEntry_t* fadTable = nnBossStorageFad_getTable(&nsData->storage); + if (fadTable) + { + fileExists = nnBossStorageFad_getIndexByName(fadTable, nsData->name) >= 0; + forceLogDebug_printf("\t(%s) -> %d", nsData->name, fileExists); + free(fadTable); + } + } + else + { + sint32 fscStatus; + auto fscStorageFile = fsc_open((char*)nnBossNsDataExport_GetPath(nsData).c_str(), FSC_ACCESS_FLAG::OPEN_FILE, &fscStatus); + if (fscStorageFile != nullptr) + { + fileExists = true; + fsc_close(fscStorageFile); + } + } + + osLib_returnFromFunction(hCPU, fileExists?1:0); +} + +void nnBossNsDataExport_getSize(PPCInterpreter_t* hCPU) +{ + ppcDefineParamStructPtr(nsData, nsData_t, 0); + + FSCVirtualFile* fscStorageFile = nullptr; + if (nsData->storage.storageKind == nn::boss::kStorageKind_NBDL) + { + bossStorageFadEntry_t fadEntry; + if (nnBossStorageFad_getEntryByName(&nsData->storage, nsData->name, &fadEntry) == false) + { + forceLog_printf("BOSS storage cant find file %s", nsData->name); + osLib_returnFromFunction(hCPU, 0); + return; + } + // open file + fscStorageFile = nnBossStorageFile_open(&nsData->storage, fadEntry.fileNameId); + } + else + { + sint32 fscStatus; + fscStorageFile = fsc_open((char*)nnBossNsDataExport_GetPath(nsData).c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus); + } + + if (fscStorageFile == nullptr) + { + forceLog_printf("BOSS storage cant open file alias %s", nsData->name); + osLib_returnFromFunction(hCPU, 0); + return; + } + + // get size + const sint32 fileSize = fsc_getFileSize(fscStorageFile); + // close file + fsc_close(fscStorageFile); + osLib_returnFromFunction64(hCPU, fileSize); +} + +uint32 nnBossNsData_read(nsData_t* nsData, uint64* sizeOutBE, void* buffer, sint32 length) +{ + FSCVirtualFile* fscStorageFile = nullptr; + if (nsData->storage.storageKind == nn::boss::kStorageKind_NBDL) + { + bossStorageFadEntry_t fadEntry; + if (nnBossStorageFad_getEntryByName(&nsData->storage, nsData->name, &fadEntry) == false) + { + forceLog_printf("BOSS storage cant find file %s for reading", nsData->name); + return 0x80000000; // todo - proper error code + } + // open file + fscStorageFile = nnBossStorageFile_open(&nsData->storage, fadEntry.fileNameId); + } + else + { + sint32 fscStatus; + fscStorageFile = fsc_open((char*)nnBossNsDataExport_GetPath(nsData).c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus); + } + + if (!fscStorageFile) + { + forceLog_printf("BOSS storage cant open file alias %s for reading", nsData->name); + return 0x80000000; // todo - proper error code + } + // get size + sint32 fileSize = fsc_getFileSize(fscStorageFile); + // verify read is within bounds + sint32 readEndOffset = (sint32)_swapEndianU64(nsData->readIndex) + length; + sint32 readBytes = length; + if (readEndOffset > fileSize) + { + readBytes = fileSize - (sint32)_swapEndianU64(nsData->readIndex); + cemu_assert_debug(readBytes != 0); + } + // read + fsc_setFileSeek(fscStorageFile, (uint32)_swapEndianU64(nsData->readIndex)); + fsc_readFile(fscStorageFile, buffer, readBytes); + nsData->readIndex = _swapEndianU64((sint32)_swapEndianU64(nsData->readIndex) + readBytes); + + // close file + fsc_close(fscStorageFile); + if (sizeOutBE) + *sizeOutBE = _swapEndianU64(readBytes); + return 0; +} + +#define NSDATA_SEEK_MODE_BEGINNING (0) + +uint32 nnBossNsData_seek(nsData_t* nsData, uint64 seek, uint32 mode) +{ + FSCVirtualFile* fscStorageFile = nullptr; + if (nsData->storage.storageKind == nn::boss::kStorageKind_NBDL) + { + bossStorageFadEntry_t fadEntry; + if (nnBossStorageFad_getEntryByName(&nsData->storage, nsData->name, &fadEntry) == false) + { + forceLog_printf("BOSS storage cant find file %s for reading", nsData->name); + return 0x80000000; // todo - proper error code + } + // open file + fscStorageFile = nnBossStorageFile_open(&nsData->storage, fadEntry.fileNameId); + } + else + { + sint32 fscStatus; + fscStorageFile = fsc_open((char*)nnBossNsDataExport_GetPath(nsData).c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus); + } + + if (fscStorageFile == nullptr) + { + forceLog_printf("BOSS storage cant open file alias %s for reading", nsData->name); + return 0x80000000; // todo - proper error code + } + // get size + sint32 fileSize = fsc_getFileSize(fscStorageFile); + // handle seek + if (mode == NSDATA_SEEK_MODE_BEGINNING) + { + seek = std::min(seek, (uint64)fileSize); + nsData->readIndex = _swapEndianU64((uint64)seek); + } + else + { + cemu_assert_unimplemented(); + } + fsc_close(fscStorageFile); + return 0; +} + +void nnBossNsDataExport_read(PPCInterpreter_t* hCPU) +{ + ppcDefineParamStructPtr(nsData, nsData_t, 0); + ppcDefineParamStr(buffer, 1); + ppcDefineParamS32(length, 2); + + forceLogDebug_printf("nsData read LR %08x (filename %s)", hCPU->spr.LR, nsData->name); + + uint32 r = nnBossNsData_read(nsData, nullptr, buffer, length); + + osLib_returnFromFunction(hCPU, r); +} + +void nnBossNsDataExport_readWithSizeOut(PPCInterpreter_t* hCPU) +{ + ppcDefineParamStructPtr(nsData, nsData_t, 0); + ppcDefineParamTypePtr(sizeOut, uint64, 1); + ppcDefineParamStr(buffer, 2); + ppcDefineParamS32(length, 3); + + uint32 r = nnBossNsData_read(nsData, sizeOut, buffer, length); + forceLogDebug_printf("nsData readWithSizeOut LR %08x (filename %s length 0x%x) Result: %d Sizeout: %llx", hCPU->spr.LR, nsData->name, length, r, _swapEndianU64(*sizeOut)); + + osLib_returnFromFunction(hCPU, r); +} + +void nnBossNsDataExport_seek(PPCInterpreter_t* hCPU) +{ + ppcDefineParamStructPtr(nsData, nsData_t, 0); + ppcDefineParamU64(seekPos, 2); + ppcDefineParamU32(mode, 4); + + uint32 r = nnBossNsData_seek(nsData, seekPos, mode); + + forceLogDebug_printf("nsData seek LR %08x (filename %s seek 0x%x) Result: %d", hCPU->spr.LR, nsData->name, (uint32)seekPos, r); + + osLib_returnFromFunction(hCPU, r); +} + +void nnBoss_load() +{ + OSInitMutexEx(&nn::boss::g_mutex, nullptr); + + osLib_addFunction("nn_boss", "Initialize__Q2_2nn4bossFv", nn::boss::export_Initialize); + osLib_addFunction("nn_boss", "GetBossState__Q2_2nn4bossFv", nn::boss::export_GetBossState); + + // task + osLib_addFunction("nn_boss", "__ct__Q3_2nn4boss4TaskFv", nn::boss::Task::export_ctor); + osLib_addFunction("nn_boss", "Run__Q3_2nn4boss4TaskFb", nn::boss::Task::export_Run); + osLib_addFunction("nn_boss", "Wait__Q3_2nn4boss4TaskFUiQ3_2nn4boss13TaskWaitState", nn::boss::Task::export_Wait); + osLib_addFunction("nn_boss", "GetTurnState__Q3_2nn4boss4TaskCFPUi", nn::boss::Task::export_GetTurnState); + osLib_addFunction("nn_boss", "GetHttpStatusCode__Q3_2nn4boss4TaskCFPUi", nn::boss::Task::export_GetHttpStatusCode); + osLib_addFunction("nn_boss", "GetContentLength__Q3_2nn4boss4TaskCFPUi", nn::boss::Task::export_GetContentLength); + osLib_addFunction("nn_boss", "GetProcessedLength__Q3_2nn4boss4TaskCFPUi", nn::boss::Task::export_GetProcessedLength); + osLib_addFunction("nn_boss", "Register__Q3_2nn4boss4TaskFRQ3_2nn4boss11TaskSetting", nn::boss::Task::export_Register); + osLib_addFunction("nn_boss", "Unregister__Q3_2nn4boss4TaskFv", nn::boss::Task::export_Unregister); + osLib_addFunction("nn_boss", "Initialize__Q3_2nn4boss4TaskFPCc", nn::boss::Task::export_Initialize1); + osLib_addFunction("nn_boss", "Initialize__Q3_2nn4boss4TaskFUcPCc", nn::boss::Task::export_Initialize2); + osLib_addFunction("nn_boss", "Initialize__Q3_2nn4boss4TaskFPCcUi", nn::boss::Task::export_Initialize3); + osLib_addFunction("nn_boss", "IsRegistered__Q3_2nn4boss4TaskCFv", nn::boss::Task::export_IsRegistered); + osLib_addFunction("nn_boss", "RegisterForImmediateRun__Q3_2nn4boss4TaskFRCQ3_2nn4boss11TaskSetting", nn::boss::Task::export_RegisterForImmediateRun); + osLib_addFunction("nn_boss", "StartScheduling__Q3_2nn4boss4TaskFb", nn::boss::Task::export_StartScheduling); + osLib_addFunction("nn_boss", "StopScheduling__Q3_2nn4boss4TaskFv", nn::boss::Task::export_StopScheduling); + + // Nbdl task setting + osLib_addFunction("nn_boss", "__ct__Q3_2nn4boss15NbdlTaskSettingFv", nn::boss::NbdlTaskSetting::export_ctor); + osLib_addFunction("nn_boss", "Initialize__Q3_2nn4boss15NbdlTaskSettingFPCcLT1", nn::boss::NbdlTaskSetting::export_Initialize); + //osLib_addFunction("nn_boss", "SetFileName__Q3_2nn4boss15NbdlTaskSettingFPCc", nn::boss::NbdlTaskSetting::export_SetFileName); + cafeExportRegisterFunc(nn::boss::NbdlTaskSetting::SetFileName, "nn_boss", "SetFileName__Q3_2nn4boss15NbdlTaskSettingFPCc", LogType::Placeholder); + + // play task setting + osLib_addFunction("nn_boss", "__ct__Q3_2nn4boss17PlayReportSettingFv", nn::boss::PlayReportSetting::export_ctor); + osLib_addFunction("nn_boss", "Set__Q3_2nn4boss17PlayReportSettingFPCcUi", nn::boss::PlayReportSetting::export_Set); + //osLib_addFunction("nn_boss", "Set__Q3_2nn4boss17PlayReportSettingFUiT1", nn::boss::PlayReportSetting::export_Set); + osLib_addFunction("nn_boss", "Initialize__Q3_2nn4boss17PlayReportSettingFPvUi", nn::boss::PlayReportSetting::export_Initialize); + + cafeExportRegisterFunc(nn::boss::RawDlTaskSetting::ctor, "nn_boss", "__ct__Q3_2nn4boss16RawDlTaskSettingFv", LogType::Placeholder); + cafeExportRegisterFunc(nn::boss::RawDlTaskSetting::Initialize, "nn_boss", "Initialize__Q3_2nn4boss16RawDlTaskSettingFPCcbT2N21", LogType::Placeholder); + + cafeExportRegisterFunc(nn::boss::NetTaskSetting::SetServiceToken, "nn_boss", "SetServiceToken__Q3_2nn4boss14NetTaskSettingFPCUc", LogType::Placeholder); + cafeExportRegisterFunc(nn::boss::NetTaskSetting::AddInternalCaCert, "nn_boss", "AddInternalCaCert__Q3_2nn4boss14NetTaskSettingFSc", LogType::Placeholder); + cafeExportRegisterFunc(nn::boss::NetTaskSetting::SetInternalClientCert, "nn_boss", "SetInternalClientCert__Q3_2nn4boss14NetTaskSettingFSc", LogType::Placeholder); + + // Title + cafeExportRegisterFunc(nn::boss::Title::ctor, "nn_boss", "__ct__Q3_2nn4boss5TitleFv", LogType::Placeholder); + // cafeExportMakeWrapper<nn::boss::Title::SetNewArrivalFlagOff>("nn_boss", "SetNewArrivalFlagOff__Q3_2nn4boss5TitleFv"); SMM bookmarks + + // TitleId + cafeExportRegisterFunc(nn::boss::TitleId::ctor1, "nn_boss", "__ct__Q3_2nn4boss7TitleIDFv", LogType::Placeholder); + cafeExportRegisterFunc(nn::boss::TitleId::ctor2, "nn_boss", "__ct__Q3_2nn4boss7TitleIDFUL", LogType::Placeholder); + cafeExportRegisterFunc(nn::boss::TitleId::cctor, "nn_boss", "__ct__Q3_2nn4boss7TitleIDFRCQ3_2nn4boss7TitleID", LogType::Placeholder); + cafeExportRegisterFunc(nn::boss::TitleId::operator_ne, "nn_boss", "__ne__Q3_2nn4boss7TitleIDCFRCQ3_2nn4boss7TitleID", LogType::Placeholder); + + // DataName + osLib_addFunction("nn_boss", "__ct__Q3_2nn4boss8DataNameFv", nnBossDataNameExport_ct); + osLib_addFunction("nn_boss", "__opPCc__Q3_2nn4boss8DataNameCFv", nnBossDataNameExport_opPCc); + + // DirectoryName + cafeExportRegisterFunc(nn::boss::DirectoryName::ctor, "nn_boss", "__ct__Q3_2nn4boss13DirectoryNameFv", LogType::Placeholder); + cafeExportRegisterFunc(nn::boss::DirectoryName::operator_const_char, "nn_boss", "__opPCc__Q3_2nn4boss13DirectoryNameCFv", LogType::Placeholder); + + // Account + cafeExportRegisterFunc(nn::boss::Account::ctor, "nn_boss", "__ct__Q3_2nn4boss7AccountFUi", LogType::Placeholder); + + + // storage + osLib_addFunction("nn_boss", "__ct__Q3_2nn4boss7StorageFv", nnBossStorageExport_ct); + //osLib_addFunction("nn_boss", "Initialize__Q3_2nn4boss7StorageFPCcQ3_2nn4boss11StorageKind", nnBossStorageExport_initialize); + osLib_addFunction("nn_boss", "Exist__Q3_2nn4boss7StorageCFv", nnBossStorageExport_exist); + osLib_addFunction("nn_boss", "GetDataList__Q3_2nn4boss7StorageCFPQ3_2nn4boss8DataNameUiPUiT2", nnBossStorageExport_getDataList); + osLib_addFunction("nn_boss", "GetDataList__Q3_2nn4boss7StorageCFPQ3_2nn4boss8DataNameUiPUiT2", nnBossStorageExport_getDataList); + cafeExportRegisterFunc(nn::boss::Storage::Initialize, "nn_boss", "Initialize__Q3_2nn4boss7StorageFPCcUiQ3_2nn4boss11StorageKind", LogType::Placeholder); + cafeExportRegisterFunc(nn::boss::Storage::Initialize2, "nn_boss", "Initialize__Q3_2nn4boss7StorageFPCcQ3_2nn4boss11StorageKind", LogType::Placeholder); + + // AlmightyStorage + cafeExportRegisterFunc(nn::boss::AlmightyStorage::ctor, "nn_boss", "__ct__Q3_2nn4boss15AlmightyStorageFv", LogType::Placeholder ); + cafeExportRegisterFunc(nn::boss::AlmightyStorage::Initialize, "nn_boss", "Initialize__Q3_2nn4boss15AlmightyStorageFQ3_2nn4boss7TitleIDPCcUiQ3_2nn4boss11StorageKind", LogType::Placeholder ); + + // AlmightyTask + cafeExportRegisterFunc(nn::boss::AlmightyTask::ctor, "nn_boss", "__ct__Q3_2nn4boss12AlmightyTaskFv", LogType::Placeholder); + cafeExportRegisterFunc(nn::boss::AlmightyTask::Initialize, "nn_boss", "Initialize__Q3_2nn4boss12AlmightyTaskFQ3_2nn4boss7TitleIDPCcUi", LogType::Placeholder); + // cafeExportRegisterFunc(nn::boss::AlmightyTask::dtor, "nn_boss", "__dt__Q3_2nn4boss12AlmightyTaskFv", LogType::Placeholder); + + // NsData + osLib_addFunction("nn_boss", "__ct__Q3_2nn4boss6NsDataFv", nnBossNsDataExport_ct); + osLib_addFunction("nn_boss", "Initialize__Q3_2nn4boss6NsDataFRCQ3_2nn4boss7StoragePCc", nnBossNsDataExport_initialize); + osLib_addFunction("nn_boss", "DeleteRealFileWithHistory__Q3_2nn4boss6NsDataFv", nnBossNsDataExport_DeleteRealFileWithHistory); + osLib_addFunction("nn_boss", "Exist__Q3_2nn4boss6NsDataCFv", nnBossNsDataExport_Exist); + osLib_addFunction("nn_boss", "GetSize__Q3_2nn4boss6NsDataCFv", nnBossNsDataExport_getSize); + osLib_addFunction("nn_boss", "Read__Q3_2nn4boss6NsDataFPvUi", nnBossNsDataExport_read); + osLib_addFunction("nn_boss", "Read__Q3_2nn4boss6NsDataFPLPvUi", nnBossNsDataExport_readWithSizeOut); + osLib_addFunction("nn_boss", "Seek__Q3_2nn4boss6NsDataFLQ3_2nn4boss12PositionBase", nnBossNsDataExport_seek); + +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_boss/nn_boss.h b/src/Cafe/OS/libs/nn_boss/nn_boss.h new file mode 100644 index 00000000..f9a434a0 --- /dev/null +++ b/src/Cafe/OS/libs/nn_boss/nn_boss.h @@ -0,0 +1 @@ +void nnBoss_load(); \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_ccr/nn_ccr.cpp b/src/Cafe/OS/libs/nn_ccr/nn_ccr.cpp new file mode 100644 index 00000000..fd87a8a0 --- /dev/null +++ b/src/Cafe/OS/libs/nn_ccr/nn_ccr.cpp @@ -0,0 +1,16 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "nn_ccr.h" + +namespace nn::ccr +{ + sint32 CCRSysCaffeineBootCheck() + { + forceLogDebug_printf("CCRSysCaffeineBootCheck()"); + return -1; + } + + void Initialize() + { + cafeExportRegister("nn_ccr", CCRSysCaffeineBootCheck, LogType::Placeholder); + } +} diff --git a/src/Cafe/OS/libs/nn_ccr/nn_ccr.h b/src/Cafe/OS/libs/nn_ccr/nn_ccr.h new file mode 100644 index 00000000..3893d549 --- /dev/null +++ b/src/Cafe/OS/libs/nn_ccr/nn_ccr.h @@ -0,0 +1,5 @@ + +namespace nn::ccr +{ + void Initialize(); +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_cmpt/nn_cmpt.cpp b/src/Cafe/OS/libs/nn_cmpt/nn_cmpt.cpp new file mode 100644 index 00000000..5566e57e --- /dev/null +++ b/src/Cafe/OS/libs/nn_cmpt/nn_cmpt.cpp @@ -0,0 +1,26 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "nn_cmpt.h" + +namespace nn::cmpt +{ + uint32 CMPTAcctGetPcConf(uint32be* pcConf) + { + forceLogDebug_printf("CMPTAcctGetPcConf() - todo"); + pcConf[0] = 0; + pcConf[1] = 0; + pcConf[2] = 0; + return 0; + } + + uint32 CMPTGetDataSize(uint32be* sizeOut) + { + *sizeOut = 0xC0000; + return 0; + } + + void Initialize() + { + cafeExportRegister("nn_cmpt", CMPTAcctGetPcConf, LogType::Placeholder); + cafeExportRegister("nn_cmpt", CMPTGetDataSize, LogType::Placeholder); + } +} diff --git a/src/Cafe/OS/libs/nn_cmpt/nn_cmpt.h b/src/Cafe/OS/libs/nn_cmpt/nn_cmpt.h new file mode 100644 index 00000000..d2d72494 --- /dev/null +++ b/src/Cafe/OS/libs/nn_cmpt/nn_cmpt.h @@ -0,0 +1,5 @@ + +namespace nn::cmpt +{ + void Initialize(); +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_common.h b/src/Cafe/OS/libs/nn_common.h new file mode 100644 index 00000000..3ec34c08 --- /dev/null +++ b/src/Cafe/OS/libs/nn_common.h @@ -0,0 +1,113 @@ +#pragma once + +using nnResult = uint32; + +inline bool NN_RESULT_IS_SUCCESS(nnResult r) +{ + return (r & 0x80000000) == 0; +} + +inline bool NN_RESULT_IS_FAILURE(nnResult r) +{ + return (r & 0x80000000) != 0; +} + +// any level with MSB set is considered an error +#define NN_RESULT_LEVEL_FATAL (0x7) // 111 +#define NN_RESULT_LEVEL_LVL6 (0x6) // 110 +#define NN_RESULT_LEVEL_STATUS (0x5) // 101 +#define NN_RESULT_LEVEL_SUCCESS (0x0) // 000 + +#define BUILD_NN_RESULT(__level, __module, __detailedErrorCode) ((((uint32)(__level)&0x7)<<29) | ((uint32)(__module)<<20) | ((uint32)(__detailedErrorCode)<<0) ) +#define BUILD_NN_RESULT_STATUS(__module, __detailedErrorCode) ((((uint32)NN_RESULT_LEVEL_STATUS)<<29) | ((uint32)(__module)<<20) | ((uint32)(__detailedErrorCode)<<0) ) + +#define NN_RESULT_SUCCESS ((nnResult)0) +#define NN_RESULT_PLACEHOLDER_ERROR ((nnResult)0x80000000) + +enum NN_RESULT_MODULE : uint32 +{ + NN_RESULT_MODULE_COMMON = 0, + NN_RESULT_MODULE_NN_IPC = 1, + NN_RESULT_MODULE_NN_BOSS = 2, + NN_RESULT_MODULE_NN_ACP = 3, + NN_RESULT_MODULE_NN_IOS = 4, + NN_RESULT_MODULE_NN_NIM = 5, + NN_RESULT_MODULE_NN_PDM = 6, + NN_RESULT_MODULE_NN_ACT = 7, + NN_RESULT_MODULE_NN_NUP = 10, + NN_RESULT_MODULE_NN_NDM = 11, + NN_RESULT_MODULE_NN_FP = 12, + NN_RESULT_MODULE_NN_AC = 13, + NN_RESULT_MODULE_NN_DRMAPP = 15, + NN_RESULT_MODULE_NN_OLV = 17, + NN_RESULT_MODULE_NN_VCTL = 18, + NN_RESULT_MODULE_NN_SPM = 20, + NN_RESULT_MODULE_NN_EC = 22, + NN_RESULT_MODULE_NN_SL = 24, + NN_RESULT_MODULE_NN_ECO = 25, + NN_RESULT_MODULE_NN_NFP = 27, + + NN_RESULT_MODULE_MCP = 511, +}; + +// 0000-9999 +// these are the user-facing error codes you see in ErrEula. Usually combined with a module-specific prefix. E.g. 102-1234 +// in nnResult these are stored in the description field left-shifted by 7 +enum class NN_ERROR_CODE : uint32 +{ + ACT_UNKNOWN_SERVER_ERROR = 2932, +}; + +// takes simplified error code in range 0-9999 +constexpr inline nnResult nnResultStatus(NN_RESULT_MODULE module, NN_ERROR_CODE errorCode) +{ + uint32 description = (uint32)errorCode; + description <<= 7; + return BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, (uint32)module, description); +} + +// nn_act description codes - todo: define these as NN_ERROR_CODE +#define NN_ACT_RESULT_ACCOUNT_DOES_NOT_EXIST 128128 + +// nn_nfp description codes +#define NN_RESULT_NFP_CODE_APPAREAIDMISMATCH 0x11300 +#define NN_RESULT_NFP_CODE_NOAPPAREA 0x10400 + +namespace nn +{ + inline NN_RESULT_MODULE nnResult_GetModule(const nnResult res) { return (NN_RESULT_MODULE)((res >> 20) & 0x1FF); }; + inline uint32 nnResult_GetDescription(const nnResult res) { return (uint32)((res >> 0) & 0xFFFFF); }; + + #define NN_ERRCODE_MODULE_PREFIX_ACT 1020000 + + inline NN_ERROR_CODE NNResultToErrorCode(nnResult result, NN_RESULT_MODULE expectedModule) + { + if (((uint32)result & 0x18000000) == 0x18000000) + { + cemu_assert_unimplemented(); + } + uint32 errorCodeBase = 0; + if (expectedModule == NN_RESULT_MODULE_NN_ACT) + errorCodeBase = NN_ERRCODE_MODULE_PREFIX_ACT; + else + { + cemu_assert_unimplemented(); + } + + NN_RESULT_MODULE module = nnResult_GetModule(result); + if (module != expectedModule) + return (NN_ERROR_CODE)(errorCodeBase + 9999); + uint32 desc = nnResult_GetDescription(result); + uint32 errCode = (desc >> 7); + if (errCode > 9999) + { + cemu_assert_suspicious(); + return (NN_ERROR_CODE)(errorCodeBase + 9999); + } + return (NN_ERROR_CODE)(errorCodeBase + errCode); + } + +} + +// tests +static_assert(BUILD_NN_RESULT_STATUS(NN_RESULT_MODULE_NN_ACT, 317696) == 0xA074D900); diff --git a/src/Cafe/OS/libs/nn_ec/nn_ec.cpp b/src/Cafe/OS/libs/nn_ec/nn_ec.cpp new file mode 100644 index 00000000..67652dfc --- /dev/null +++ b/src/Cafe/OS/libs/nn_ec/nn_ec.cpp @@ -0,0 +1,26 @@ +#include "Cafe/OS/common/OSCommon.h" + +typedef struct +{ + /* +0x00 */ uint8 ukn00[0x10]; + /* +0x10 */ uint8 ukn10[0x0C]; + /* +0x2C */ uint8 ukn2C[0x10]; // currency string? + /* +0x3C */ uint32 ukn3C; // money amount? + // size of struct is 0x40 +}nnEcUknMoneyStruct_t; + +void nnEcExport___ct__Q3_2nn2ec5MoneyFPCcN21(PPCInterpreter_t* hCPU) +{ + debug_printf("nn_ec.__ct__Q3_2nn2ec5MoneyFPCcN21(0x%08x, ...)\n", hCPU->gpr[3]); + nnEcUknMoneyStruct_t* moneyStruct = (nnEcUknMoneyStruct_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); + // todo -> Setup struct + osLib_returnFromFunction(hCPU, memory_getVirtualOffsetFromPointer(moneyStruct)); +} + +/* + * Load E commerce functions (E-Shop stuff) + */ +void nnEc_load() +{ + osLib_addFunction("nn_ec", "__ct__Q3_2nn2ec5MoneyFPCcN21", nnEcExport___ct__Q3_2nn2ec5MoneyFPCcN21); +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_ec/nn_ec.h b/src/Cafe/OS/libs/nn_ec/nn_ec.h new file mode 100644 index 00000000..c39bd428 --- /dev/null +++ b/src/Cafe/OS/libs/nn_ec/nn_ec.h @@ -0,0 +1 @@ +void nnEc_load(); \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_fp/nn_fp.cpp b/src/Cafe/OS/libs/nn_fp/nn_fp.cpp new file mode 100644 index 00000000..2df51a94 --- /dev/null +++ b/src/Cafe/OS/libs/nn_fp/nn_fp.cpp @@ -0,0 +1,796 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/IOSU/legacy/iosu_ioctl.h" +#include "Cafe/IOSU/legacy/iosu_act.h" +#include "Cafe/IOSU/legacy/iosu_fpd.h" +#include "Cafe/OS/libs/coreinit/coreinit_IOS.h" + +#define fpdPrepareRequest() \ +StackAllocator<iosu::fpd::iosuFpdCemuRequest_t> _buf_fpdRequest; \ +StackAllocator<ioBufferVector_t> _buf_bufferVector; \ +iosu::fpd::iosuFpdCemuRequest_t* fpdRequest = _buf_fpdRequest.GetPointer(); \ +ioBufferVector_t* fpdBufferVector = _buf_bufferVector.GetPointer(); \ +memset(fpdRequest, 0, sizeof(iosu::fpd::iosuFpdCemuRequest_t)); \ +memset(fpdBufferVector, 0, sizeof(ioBufferVector_t)); \ +fpdBufferVector->buffer = (uint8*)fpdRequest; + +namespace nn +{ + namespace fp + { + + struct + { + bool isInitialized; + bool isAdminMode; + }g_fp = { }; + + void Initialize() + { + if (g_fp.isInitialized == false) + { + g_fp.isInitialized = true; + fpdPrepareRequest(); + fpdRequest->requestCode = iosu::fpd::IOSU_FPD_INITIALIZE; + __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); + } + } + + + + + void export_IsInitialized(PPCInterpreter_t* hCPU) + { + forceLogDebug_printf("Called nn_fp.IsInitialized LR %08x", hCPU->spr.LR); + osLib_returnFromFunction(hCPU, g_fp.isInitialized ? 1 : 0); + } + + void export_Initialize(PPCInterpreter_t* hCPU) + { + forceLogDebug_printf("Called nn_fp.Initialize LR %08x", hCPU->spr.LR); + + Initialize(); + + osLib_returnFromFunction(hCPU, 0); + } + + void export_InitializeAdmin(PPCInterpreter_t* hCPU) + { + forceLogDebug_printf("Called nn_fp.InitializeAdmin LR %08x", hCPU->spr.LR); + Initialize(); + g_fp.isAdminMode = true; + osLib_returnFromFunction(hCPU, 0); + } + + void export_IsInitializedAdmin(PPCInterpreter_t* hCPU) + { + forceLogDebug_printf("nn_fp.IsInitializedAdmin()"); + osLib_returnFromFunction(hCPU, g_fp.isInitialized ? 1 : 0); + } + + void export_SetNotificationHandler(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(notificationMask, 0); + ppcDefineParamMPTR(funcMPTR, 1); + ppcDefineParamMPTR(customParam, 2); + + forceLogDebug_printf("nn_fp.SetNotificationHandler(0x%08x,0x%08x,0x%08x)", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); + + fpdPrepareRequest(); + fpdRequest->requestCode = iosu::fpd::IOSU_FPD_SET_NOTIFICATION_HANDLER; + fpdRequest->setNotificationHandler.notificationMask = notificationMask; + fpdRequest->setNotificationHandler.funcPtr = funcMPTR; + fpdRequest->setNotificationHandler.custom = customParam; + __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); + + osLib_returnFromFunction(hCPU, 0); + } + + void export_LoginAsync(PPCInterpreter_t* hCPU) + { + ppcDefineParamMPTR(funcPtr, 0); + ppcDefineParamMPTR(custom, 1); + forceLogDebug_printf("nn_fp.LoginAsync(0x%08x,0x%08x)", funcPtr, custom); + if (g_fp.isInitialized == false) + { + osLib_returnFromFunction(hCPU, 0xC0C00580); + return; + } + fpdPrepareRequest(); + fpdRequest->requestCode = iosu::fpd::IOSU_FPD_LOGIN_ASYNC; + fpdRequest->loginAsync.funcPtr = funcPtr; + fpdRequest->loginAsync.custom = custom; + __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); + osLib_returnFromFunction(hCPU, 0); + } + + void export_HasLoggedIn(PPCInterpreter_t* hCPU) + { + // Sonic All Star Racing needs this + forceLogDebug_printf("nn_fp.HasLoggedIn()"); + osLib_returnFromFunction(hCPU, 1); + } + + void export_IsOnline(PPCInterpreter_t* hCPU) + { + //forceLogDebug_printf("nn_fp.IsOnline();\n"); + fpdPrepareRequest(); + fpdRequest->requestCode = iosu::fpd::IOSU_FPD_IS_ONLINE; + __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); + forceLogDebug_printf("nn_fp.IsOnline() -> %d", fpdRequest->resultU32.u32); + + osLib_returnFromFunction(hCPU, fpdRequest->resultU32.u32); + } + + void export_GetFriendList(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(pidList, uint32be, 0); + ppcDefineParamMEMPTR(returnedCount, uint32be, 1); + ppcDefineParamU32(startIndex, 2); + ppcDefineParamU32(maxCount, 3); + + forceLogDebug_printf("nn_fp.GetFriendList(...)"); + //debug_printf("nn_fp.GetFriendList(0x%08x, 0x%08x, %d, %d)\n", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6]); + fpdPrepareRequest(); + fpdRequest->requestCode = iosu::fpd::IOSU_FPD_GET_FRIEND_LIST; + + fpdRequest->getFriendList.pidList = pidList; + fpdRequest->getFriendList.startIndex = startIndex; + fpdRequest->getFriendList.maxCount = maxCount; + + __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); + + *returnedCount = fpdRequest->resultU32.u32; + + osLib_returnFromFunction(hCPU, fpdRequest->returnCode); + } + + void export_GetFriendRequestList(PPCInterpreter_t* hCPU) + { + // GetFriendRequestList__Q2_2nn2fpFPUiT1UiT3 + ppcDefineParamMEMPTR(pidList, uint32be, 0); + ppcDefineParamMEMPTR(returnedCount, uint32be, 1); + ppcDefineParamU32(startIndex, 2); + ppcDefineParamU32(maxCount, 3); + + forceLogDebug_printf("nn_fp.GetFriendRequestList(...)"); + //debug_printf("nn_fp.GetFriendList(0x%08x, 0x%08x, %d, %d)\n", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6]); + fpdPrepareRequest(); + fpdRequest->requestCode = iosu::fpd::IOSU_FPD_GET_FRIENDREQUEST_LIST; + + fpdRequest->getFriendList.pidList = pidList; + fpdRequest->getFriendList.startIndex = startIndex; + fpdRequest->getFriendList.maxCount = maxCount; + + __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); + + *returnedCount = fpdRequest->resultU32.u32; + + osLib_returnFromFunction(hCPU, fpdRequest->returnCode); + } + + void export_GetFriendListAll(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(pidList, uint32be, 0); + ppcDefineParamMEMPTR(returnedCount, uint32be, 1); + ppcDefineParamU32(startIndex, 2); + ppcDefineParamU32(maxCount, 3); + + forceLogDebug_printf("nn_fp.GetFriendListAll(...)"); + + //debug_printf("nn_fp.GetFriendListAll(0x%08x, 0x%08x, %d, %d)\n", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6]); + fpdPrepareRequest(); + fpdRequest->requestCode = iosu::fpd::IOSU_FPD_GET_FRIEND_LIST_ALL; + + fpdRequest->getFriendList.pidList = pidList; + fpdRequest->getFriendList.startIndex = startIndex; + fpdRequest->getFriendList.maxCount = maxCount; + + __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); + + *returnedCount = fpdRequest->resultU32.u32; + + osLib_returnFromFunction(hCPU, fpdRequest->returnCode); + } + + void export_GetFriendListEx(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(friendData, iosu::fpd::friendData_t, 0); + ppcDefineParamMEMPTR(pidList, uint32be, 1); + ppcDefineParamU32(count, 2); + + forceLogDebug_printf("nn_fp.GetFriendListEx(...)"); + + fpdPrepareRequest(); + fpdRequest->requestCode = iosu::fpd::IOSU_FPD_GET_FRIEND_LIST_EX; + + fpdRequest->getFriendListEx.friendData = friendData; + fpdRequest->getFriendListEx.pidList = pidList; + fpdRequest->getFriendListEx.count = count; + + __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); + + osLib_returnFromFunction(hCPU, fpdRequest->returnCode); + } + + void export_GetFriendRequestListEx(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(friendRequest, iosu::fpd::friendRequest_t, 0); + ppcDefineParamMEMPTR(pidList, uint32be, 1); + ppcDefineParamU32(count, 2); + + forceLogDebug_printf("nn_fp.GetFriendRequestListEx(...)"); + + fpdPrepareRequest(); + fpdRequest->requestCode = iosu::fpd::IOSU_FPD_GET_FRIENDREQUEST_LIST_EX; + + fpdRequest->getFriendRequestListEx.friendRequest = friendRequest; + fpdRequest->getFriendRequestListEx.pidList = pidList; + fpdRequest->getFriendRequestListEx.count = count; + + __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); + + osLib_returnFromFunction(hCPU, fpdRequest->returnCode); + } + + void export_GetBasicInfoAsync(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(basicInfo, iosu::fpd::friendBasicInfo_t, 0); + ppcDefineParamTypePtr(pidList, uint32be, 1); + ppcDefineParamU32(count, 2); + ppcDefineParamMPTR(funcMPTR, 3); + ppcDefineParamU32(customParam, 4); + + fpdPrepareRequest(); + fpdRequest->requestCode = iosu::fpd::IOSU_FPD_GET_BASIC_INFO_ASYNC; + fpdRequest->getBasicInfo.basicInfo = basicInfo; + fpdRequest->getBasicInfo.pidList = pidList; + fpdRequest->getBasicInfo.count = count; + fpdRequest->getBasicInfo.funcPtr = funcMPTR; + fpdRequest->getBasicInfo.custom = customParam; + __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector);; + + osLib_returnFromFunction(hCPU, fpdRequest->returnCode); + } + + void export_GetMyPrincipalId(PPCInterpreter_t* hCPU) + { + forceLogDebug_printf("nn_fp.GetMyPrincipalId()"); + + fpdPrepareRequest(); + fpdRequest->requestCode = iosu::fpd::IOSU_FPD_GET_MY_PRINCIPAL_ID; + __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); + + uint32 principalId = fpdRequest->resultU32.u32; + + osLib_returnFromFunction(hCPU, principalId); + } + + void export_GetMyAccountId(PPCInterpreter_t* hCPU) + { + forceLogDebug_printf("nn_fp.GetMyAccountId(0x%08x)", hCPU->gpr[3]); + ppcDefineParamTypePtr(accountId, uint8, 0); + + fpdPrepareRequest(); + fpdRequest->requestCode = iosu::fpd::IOSU_FPD_GET_MY_ACCOUNT_ID; + fpdRequest->common.ptr = (void*)accountId; + __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); + + osLib_returnFromFunction(hCPU, fpdRequest->returnCode); + } + + void export_GetMyScreenName(PPCInterpreter_t* hCPU) + { + forceLogDebug_printf("nn_fp.GetMyScreenName(0x%08x)", hCPU->gpr[3]); + ppcDefineParamTypePtr(screenname, uint16be, 0); + + screenname[0] = '\0'; + fpdPrepareRequest(); + fpdRequest->requestCode = iosu::fpd::IOSU_FPD_GET_MY_SCREENNAME; + fpdRequest->common.ptr = (void*)screenname; + __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); + + osLib_returnFromFunction(hCPU, 0); + } + + typedef struct + { + uint8 showOnline; // show online status to others + uint8 showGame; // show played game to others + uint8 blockFriendRequests; // block friend requests + }fpPerference_t; + + void export_GetMyPreference(PPCInterpreter_t* hCPU) + { + forceLogDebug_printf("nn_fp.GetMyPreference(0x%08x) - placeholder", hCPU->gpr[3]); + ppcDefineParamTypePtr(pref, fpPerference_t, 0); + + pref->showOnline = 1; + pref->showGame = 1; + pref->blockFriendRequests = 0; + + osLib_returnFromFunction(hCPU, 0); + } + + // GetMyPreference__Q2_2nn2fpFPQ3_2nn2fp10Preference + + void export_GetMyMii(PPCInterpreter_t* hCPU) + { + forceLogDebug_printf("nn_fp.GetMyMii(0x%08x)", hCPU->gpr[3]); + ppcDefineParamTypePtr(fflData, FFLData_t, 0); + + fpdPrepareRequest(); + fpdRequest->requestCode = iosu::fpd::IOSU_FPD_GET_MY_MII; + fpdRequest->common.ptr = (void*)fflData; + __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); + + osLib_returnFromFunction(hCPU, fpdRequest->returnCode); + } + + void export_GetFriendAccountId(PPCInterpreter_t* hCPU) + { + forceLogDebug_printf("nn_fp.GetFriendAccountId(0x%08x,0x%08x,0x%08x)", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); + ppcDefineParamMEMPTR(accountIds, char, 0); + ppcDefineParamMEMPTR(pidList, uint32be, 1); + ppcDefineParamU32(count, 2); + + fpdPrepareRequest(); + fpdRequest->requestCode = iosu::fpd::IOSU_FPD_GET_FRIEND_ACCOUNT_ID; + fpdRequest->getFriendAccountId.accountIds = accountIds; + fpdRequest->getFriendAccountId.pidList = pidList; + fpdRequest->getFriendAccountId.count = count; + __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); + + osLib_returnFromFunction(hCPU, fpdRequest->returnCode); + } + + void export_GetFriendScreenName(PPCInterpreter_t* hCPU) + { + // GetFriendScreenName__Q2_2nn2fpFPA11_wPCUiUibPUc + forceLogDebug_printf("nn_fp.GetFriendScreenName(0x%08x,0x%08x,0x%08x,%d,0x%08x)", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7]); + ppcDefineParamMEMPTR(nameList, uint16be, 0); + ppcDefineParamMEMPTR(pidList, uint32be, 1); + ppcDefineParamU32(count, 2); + ppcDefineParamU32(replaceNonAscii, 3); + ppcDefineParamMEMPTR(languageList, uint8, 4); + + fpdPrepareRequest(); + fpdRequest->requestCode = iosu::fpd::IOSU_FPD_GET_FRIEND_SCREENNAME; + fpdRequest->getFriendScreenname.nameList = nameList; + fpdRequest->getFriendScreenname.pidList = pidList; + fpdRequest->getFriendScreenname.count = count; + fpdRequest->getFriendScreenname.replaceNonAscii = replaceNonAscii != 0; + fpdRequest->getFriendScreenname.languageList = languageList; + __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); + + osLib_returnFromFunction(hCPU, fpdRequest->returnCode); + } + + void export_GetFriendMii(PPCInterpreter_t* hCPU) + { + forceLogDebug_printf("nn_fp.GetFriendMii(0x%08x,0x%08x,0x%08x)", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); + ppcDefineParamMEMPTR(miiList, FFLData_t, 0); + ppcDefineParamMEMPTR(pidList, uint32be, 1); + ppcDefineParamU32(count, 2); + + fpdPrepareRequest(); + fpdRequest->requestCode = iosu::fpd::IOSU_FPD_GET_FRIEND_MII; + fpdRequest->getFriendMii.miiList = (uint8*)miiList.GetPtr(); + fpdRequest->getFriendMii.pidList = pidList; + fpdRequest->getFriendMii.count = count; + __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); + + osLib_returnFromFunction(hCPU, fpdRequest->returnCode); + } + + void export_GetFriendPresence(PPCInterpreter_t* hCPU) + { + forceLogDebug_printf("nn_fp.GetFriendPresence(0x%08x,0x%08x,0x%08x)", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); + ppcDefineParamMEMPTR(presenceList, uint8, 0); + ppcDefineParamMEMPTR(pidList, uint32be, 1); + ppcDefineParamU32(count, 2); + + fpdPrepareRequest(); + fpdRequest->requestCode = iosu::fpd::IOSU_FPD_GET_FRIEND_PRESENCE; + fpdRequest->getFriendPresence.presenceList = (uint8*)presenceList.GetPtr(); + fpdRequest->getFriendPresence.pidList = pidList; + fpdRequest->getFriendPresence.count = count; + __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); + + osLib_returnFromFunction(hCPU, fpdRequest->returnCode); + } + + void export_GetFriendRelationship(PPCInterpreter_t* hCPU) + { + forceLogDebug_printf("nn_fp.GetFriendRelationship(0x%08x,0x%08x,0x%08x)", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); + ppcDefineParamMEMPTR(relationshipList, uint8, 0); + ppcDefineParamMEMPTR(pidList, uint32be, 1); + ppcDefineParamU32(count, 2); + + fpdPrepareRequest(); + fpdRequest->requestCode = iosu::fpd::IOSU_FPD_GET_FRIEND_RELATIONSHIP; + fpdRequest->getFriendRelationship.relationshipList = (uint8*)relationshipList.GetPtr(); + fpdRequest->getFriendRelationship.pidList = pidList; + fpdRequest->getFriendRelationship.count = count; + __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); + + osLib_returnFromFunction(hCPU, fpdRequest->returnCode); + } + + void export_IsJoinable(PPCInterpreter_t* hCPU) + { + ppcDefineParamTypePtr(presence, iosu::fpd::friendPresence_t, 0); + ppcDefineParamU64(joinMask, 2); + + if (presence->isValid == 0 || + presence->isOnline == 0 || + presence->gameMode.joinGameId == 0 || + presence->gameMode.matchmakeType == 0 || + presence->gameMode.groupId == 0 || + presence->gameMode.joinGameMode >= 64 ) + { + osLib_returnFromFunction(hCPU, 0); + return; + } + + uint32 joinGameMode = presence->gameMode.joinGameMode; + uint64 joinModeMask = (1ULL<<joinGameMode); + if ((joinModeMask&joinMask) == 0) + { + osLib_returnFromFunction(hCPU, 0); + return; + } + + // check relation ship + uint32 joinFlagMask = presence->gameMode.joinFlagMask; + if (joinFlagMask == 0) + { + osLib_returnFromFunction(hCPU, 0); + return; + } + if (joinFlagMask == 1) + { + osLib_returnFromFunction(hCPU, 1); + return; + } + if (joinFlagMask == 2) + { + // check relationship + uint8 relationship[1] = { 0 }; + StackAllocator<uint32be, 1> pidList; + pidList[0] = presence->gameMode.hostPid; + fpdPrepareRequest(); + fpdRequest->requestCode = iosu::fpd::IOSU_FPD_GET_FRIEND_RELATIONSHIP; + fpdRequest->getFriendRelationship.relationshipList = relationship; + fpdRequest->getFriendRelationship.pidList = pidList.GetPointer(); + fpdRequest->getFriendRelationship.count = 1; + __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); + + if(relationship[0] == iosu::fpd::RELATIONSHIP_FRIEND) + osLib_returnFromFunction(hCPU, 1); + else + osLib_returnFromFunction(hCPU, 0); + return; + } + if (joinFlagMask == 0x65 || joinFlagMask == 0x66) + { + forceLog_printf("Unsupported friend invite"); + } + + osLib_returnFromFunction(hCPU, 0); + } + + void export_CheckSettingStatusAsync(PPCInterpreter_t* hCPU) + { + forceLogDebug_printf("nn_fp.CheckSettingStatusAsync(0x%08x,0x%08x,0x%08x) - placeholder", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); + ppcDefineParamTypePtr(uknR3, uint8, 0); + ppcDefineParamMPTR(funcMPTR, 1); + ppcDefineParamU32(customParam, 2); + + if (g_fp.isAdminMode == false) + { + + osLib_returnFromFunction(hCPU, 0xC0C00800); + return; + } + + *uknR3 = 1; + + StackAllocator<uint32be> callbackResultCode; + + *callbackResultCode.GetPointer() = 0; + + hCPU->gpr[3] = callbackResultCode.GetMPTR(); + hCPU->gpr[4] = customParam; + PPCCore_executeCallbackInternal(funcMPTR); + + osLib_returnFromFunction(hCPU, 0); + } + + void export_IsPreferenceValid(PPCInterpreter_t* hCPU) + { + forceLogDebug_printf("nn_fp.IsPreferenceValid()"); + fpdPrepareRequest(); + fpdRequest->requestCode = iosu::fpd::IOSU_FPD_IS_PREFERENCE_VALID; + __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); + + osLib_returnFromFunction(hCPU, fpdRequest->resultU32.u32); + } + + void export_UpdatePreferenceAsync(PPCInterpreter_t* hCPU) + { + forceLogDebug_printf("nn_fp.UpdatePreferenceAsync(0x%08x,0x%08x,0x%08x) - placeholder", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); + ppcDefineParamTypePtr(uknR3, uint8, 0); + ppcDefineParamMPTR(funcMPTR, 1); + ppcDefineParamU32(customParam, 2); + + if (g_fp.isAdminMode == false) + { + + osLib_returnFromFunction(hCPU, 0xC0C00800); + return; + } + + //*uknR3 = 0; // seems to be 3 bytes (nn::fp::Preference const *) + + StackAllocator<uint32be> callbackResultCode; + + *callbackResultCode.GetPointer() = 0; + + hCPU->gpr[3] = callbackResultCode.GetMPTR(); + hCPU->gpr[4] = customParam; + PPCCore_executeCallbackInternal(funcMPTR); + + osLib_returnFromFunction(hCPU, 0); + } + + void export_UpdateGameMode(PPCInterpreter_t* hCPU) + { + forceLogDebug_printf("nn_fp.UpdateGameMode(0x%08x,0x%08x,%d)", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); + ppcDefineParamMEMPTR(gameMode, iosu::fpd::gameMode_t, 0); + ppcDefineParamMEMPTR(gameModeMessage, uint16be, 1); + ppcDefineParamU32(uknR5, 2); + + fpdPrepareRequest(); + fpdRequest->requestCode = iosu::fpd::IOSU_FPD_UPDATE_GAMEMODE; + fpdRequest->updateGameMode.gameMode = gameMode; + fpdRequest->updateGameMode.gameModeMessage = gameModeMessage; + __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); + + osLib_returnFromFunction(hCPU, 0); + } + + void export_GetRequestBlockSettingAsync(PPCInterpreter_t* hCPU) + { + ppcDefineParamTypePtr(settingList, uint8, 0); + ppcDefineParamTypePtr(pidList, uint32be, 1); + ppcDefineParamU32(pidCount, 2); + ppcDefineParamMPTR(funcMPTR, 3); + ppcDefineParamMPTR(customParam, 4); + + forceLogDebug_printf("GetRequestBlockSettingAsync(...) - todo"); + + for (uint32 i = 0; i < pidCount; i++) + settingList[i] = 0; + // 0 means not blocked. Friend app will continue with GetBasicInformation() + // 1 means blocked. Friend app will continue with AddFriendAsync to add the user as a provisional friend + + StackAllocator<uint32be> callbackResultCode; + + *callbackResultCode.GetPointer() = 0; + + hCPU->gpr[3] = callbackResultCode.GetMPTR(); + hCPU->gpr[4] = customParam; + PPCCore_executeCallbackInternal(funcMPTR); + + osLib_returnFromFunction(hCPU, 0); + } + + void export_AddFriendAsync(PPCInterpreter_t* hCPU) + { + // AddFriendAsync__Q2_2nn2fpFPCcPFQ2_2nn6ResultPv_vPv + ppcDefineParamU32(principalId, 0); + ppcDefineParamMPTR(funcMPTR, 1); + ppcDefineParamMPTR(customParam, 2); + +#ifndef PUBLIC_RELEASE + assert_dbg(); +#endif + fpdPrepareRequest(); + fpdRequest->requestCode = iosu::fpd::IOSU_FPD_ADD_FRIEND; + fpdRequest->addOrRemoveFriend.pid = principalId; + fpdRequest->addOrRemoveFriend.funcPtr = funcMPTR; + fpdRequest->addOrRemoveFriend.custom = customParam; + __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); + + osLib_returnFromFunction(hCPU, fpdRequest->returnCode); + } + + + void export_DeleteFriendFlagsAsync(PPCInterpreter_t* hCPU) + { + forceLogDebug_printf("nn_fp.DeleteFriendFlagsAsync(...) - todo"); + ppcDefineParamU32(uknR3, 0); // example value: pointer + ppcDefineParamU32(uknR4, 1); // example value: 1 + ppcDefineParamU32(uknR5, 2); // example value: 1 + ppcDefineParamMPTR(funcMPTR, 3); + ppcDefineParamU32(customParam, 4); + + if (g_fp.isAdminMode == false) + { + osLib_returnFromFunction(hCPU, 0xC0C00800); + return; + } + + StackAllocator<uint32be> callbackResultCode; + + *callbackResultCode.GetPointer() = 0; + + hCPU->gpr[3] = callbackResultCode.GetMPTR(); + hCPU->gpr[4] = customParam; + PPCCore_executeCallbackInternal(funcMPTR); + + osLib_returnFromFunction(hCPU, 0); + } + + + typedef struct + { + /* +0x00 */ uint32be pid; + /* +0x04 */ uint8 ukn04; + /* +0x05 */ uint8 ukn05; + /* +0x06 */ uint8 ukn06[0x22]; + /* +0x28 */ uint8 ukn28[0x22]; + /* +0x4A */ uint8 _uknOrPadding4A[6]; + /* +0x50 */ uint32be ukn50; + /* +0x54 */ uint32be ukn54; + /* +0x58 */ uint16be ukn58; + /* +0x5C */ uint8 _padding5C[4]; + /* +0x60 */ iosu::fpd::fpdDate_t date; + }RecentPlayRecordEx_t; + + static_assert(sizeof(RecentPlayRecordEx_t) == 0x68, ""); + static_assert(offsetof(RecentPlayRecordEx_t, ukn06) == 0x06, ""); + static_assert(offsetof(RecentPlayRecordEx_t, ukn50) == 0x50, ""); + + void export_AddFriendRequestAsync(PPCInterpreter_t* hCPU) + { + ppcDefineParamTypePtr(playRecord, RecentPlayRecordEx_t, 0); + ppcDefineParamTypePtr(message, uint16be, 1); + ppcDefineParamMPTR(funcMPTR, 2); + ppcDefineParamMPTR(customParam, 3); + + fpdPrepareRequest(); + + uint8* uknData = (uint8*)playRecord; + + fpdRequest->requestCode = iosu::fpd::IOSU_FPD_ADD_FRIEND_REQUEST; + fpdRequest->addFriendRequest.pid = playRecord->pid; + fpdRequest->addFriendRequest.message = message; + fpdRequest->addFriendRequest.funcPtr = funcMPTR; + fpdRequest->addFriendRequest.custom = customParam; + __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); + + osLib_returnFromFunction(hCPU, fpdRequest->returnCode); + } + + void export_RemoveFriendAsync(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(principalId, 0); + ppcDefineParamMPTR(funcMPTR, 1); + ppcDefineParamMPTR(customParam, 2); + + fpdPrepareRequest(); + fpdRequest->requestCode = iosu::fpd::IOSU_FPD_REMOVE_FRIEND_ASYNC; + fpdRequest->addOrRemoveFriend.pid = principalId; + fpdRequest->addOrRemoveFriend.funcPtr = funcMPTR; + fpdRequest->addOrRemoveFriend.custom = customParam; + __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); + + osLib_returnFromFunction(hCPU, fpdRequest->returnCode); + } + + void export_MarkFriendRequestsAsReceivedAsync(PPCInterpreter_t* hCPU) + { + ppcDefineParamTypePtr(messageIdList, uint64, 0); + ppcDefineParamU32(count, 1); + ppcDefineParamMPTR(funcMPTR, 2); + ppcDefineParamMPTR(customParam, 3); + + fpdPrepareRequest(); + fpdRequest->requestCode = iosu::fpd::IOSU_FPD_MARK_FRIEND_REQUEST_AS_RECEIVED_ASYNC; + fpdRequest->markFriendRequest.messageIdList = messageIdList; + fpdRequest->markFriendRequest.count = count; + fpdRequest->markFriendRequest.funcPtr = funcMPTR; + fpdRequest->markFriendRequest.custom = customParam; + __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); + + osLib_returnFromFunction(hCPU, fpdRequest->returnCode); + } + + void export_CancelFriendRequestAsync(PPCInterpreter_t* hCPU) + { + ppcDefineParamU64(frqMessageId, 0); + ppcDefineParamMPTR(funcMPTR, 2); + ppcDefineParamMPTR(customParam, 3); + + fpdPrepareRequest(); + fpdRequest->requestCode = iosu::fpd::IOSU_FPD_CANCEL_FRIEND_REQUEST_ASYNC; + fpdRequest->cancelOrAcceptFriendRequest.messageId = frqMessageId; + fpdRequest->cancelOrAcceptFriendRequest.funcPtr = funcMPTR; + fpdRequest->cancelOrAcceptFriendRequest.custom = customParam; + __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); + + osLib_returnFromFunction(hCPU, fpdRequest->returnCode); + } + + void export_AcceptFriendRequestAsync(PPCInterpreter_t* hCPU) + { + ppcDefineParamU64(frqMessageId, 0); + ppcDefineParamMPTR(funcMPTR, 2); + ppcDefineParamMPTR(customParam, 3); + + fpdPrepareRequest(); + fpdRequest->requestCode = iosu::fpd::IOSU_FPD_ACCEPT_FRIEND_REQUEST_ASYNC; + fpdRequest->cancelOrAcceptFriendRequest.messageId = frqMessageId; + fpdRequest->cancelOrAcceptFriendRequest.funcPtr = funcMPTR; + fpdRequest->cancelOrAcceptFriendRequest.custom = customParam; + __depr__IOS_Ioctlv(IOS_DEVICE_FPD, IOSU_FPD_REQUEST_CEMU, 1, 1, fpdBufferVector); + + osLib_returnFromFunction(hCPU, fpdRequest->returnCode); + } + + void load() + { + osLib_addFunction("nn_fp", "Initialize__Q2_2nn2fpFv", export_Initialize); + osLib_addFunction("nn_fp", "InitializeAdmin__Q2_2nn2fpFv", export_InitializeAdmin); + osLib_addFunction("nn_fp", "IsInitialized__Q2_2nn2fpFv", export_IsInitialized); + osLib_addFunction("nn_fp", "IsInitializedAdmin__Q2_2nn2fpFv", export_IsInitializedAdmin); + + osLib_addFunction("nn_fp", "SetNotificationHandler__Q2_2nn2fpFUiPFQ3_2nn2fp16NotificationTypeUiPv_vPv", export_SetNotificationHandler); + + osLib_addFunction("nn_fp", "LoginAsync__Q2_2nn2fpFPFQ2_2nn6ResultPv_vPv", export_LoginAsync); + osLib_addFunction("nn_fp", "HasLoggedIn__Q2_2nn2fpFv", export_HasLoggedIn); + + osLib_addFunction("nn_fp", "IsOnline__Q2_2nn2fpFv", export_IsOnline); + + osLib_addFunction("nn_fp", "GetFriendList__Q2_2nn2fpFPUiT1UiT3", export_GetFriendList); + osLib_addFunction("nn_fp", "GetFriendRequestList__Q2_2nn2fpFPUiT1UiT3", export_GetFriendRequestList); + osLib_addFunction("nn_fp", "GetFriendListAll__Q2_2nn2fpFPUiT1UiT3", export_GetFriendListAll); + osLib_addFunction("nn_fp", "GetFriendListEx__Q2_2nn2fpFPQ3_2nn2fp10FriendDataPCUiUi", export_GetFriendListEx); + osLib_addFunction("nn_fp", "GetFriendRequestListEx__Q2_2nn2fpFPQ3_2nn2fp13FriendRequestPCUiUi", export_GetFriendRequestListEx); + osLib_addFunction("nn_fp", "GetBasicInfoAsync__Q2_2nn2fpFPQ3_2nn2fp9BasicInfoPCUiUiPFQ2_2nn6ResultPv_vPv", export_GetBasicInfoAsync); + + osLib_addFunction("nn_fp", "GetMyPrincipalId__Q2_2nn2fpFv", export_GetMyPrincipalId); + osLib_addFunction("nn_fp", "GetMyAccountId__Q2_2nn2fpFPc", export_GetMyAccountId); + osLib_addFunction("nn_fp", "GetMyScreenName__Q2_2nn2fpFPw", export_GetMyScreenName); + osLib_addFunction("nn_fp", "GetMyMii__Q2_2nn2fpFP12FFLStoreData", export_GetMyMii); + osLib_addFunction("nn_fp", "GetMyPreference__Q2_2nn2fpFPQ3_2nn2fp10Preference", export_GetMyPreference); + + osLib_addFunction("nn_fp", "GetFriendAccountId__Q2_2nn2fpFPA17_cPCUiUi", export_GetFriendAccountId); + osLib_addFunction("nn_fp", "GetFriendScreenName__Q2_2nn2fpFPA11_wPCUiUibPUc", export_GetFriendScreenName); + osLib_addFunction("nn_fp", "GetFriendMii__Q2_2nn2fpFP12FFLStoreDataPCUiUi", export_GetFriendMii); + osLib_addFunction("nn_fp", "GetFriendPresence__Q2_2nn2fpFPQ3_2nn2fp14FriendPresencePCUiUi", export_GetFriendPresence); + osLib_addFunction("nn_fp", "GetFriendRelationship__Q2_2nn2fpFPUcPCUiUi", export_GetFriendRelationship); + osLib_addFunction("nn_fp", "IsJoinable__Q2_2nn2fpFPCQ3_2nn2fp14FriendPresenceUL", export_IsJoinable); + + osLib_addFunction("nn_fp", "CheckSettingStatusAsync__Q2_2nn2fpFPUcPFQ2_2nn6ResultPv_vPv", export_CheckSettingStatusAsync); + osLib_addFunction("nn_fp", "IsPreferenceValid__Q2_2nn2fpFv", export_IsPreferenceValid); + osLib_addFunction("nn_fp", "UpdatePreferenceAsync__Q2_2nn2fpFPCQ3_2nn2fp10PreferencePFQ2_2nn6ResultPv_vPv", export_UpdatePreferenceAsync); + + osLib_addFunction("nn_fp", "UpdateGameMode__Q2_2nn2fpFPCQ3_2nn2fp8GameModePCwUi", export_UpdateGameMode); + + osLib_addFunction("nn_fp", "GetRequestBlockSettingAsync__Q2_2nn2fpFPUcPCUiUiPFQ2_2nn6ResultPv_vPv", export_GetRequestBlockSettingAsync); + + osLib_addFunction("nn_fp", "AddFriendAsync__Q2_2nn2fpFPCcPFQ2_2nn6ResultPv_vPv", export_AddFriendAsync); + osLib_addFunction("nn_fp", "AddFriendRequestAsync__Q2_2nn2fpFPCQ3_2nn2fp18RecentPlayRecordExPCwPFQ2_2nn6ResultPv_vPv", export_AddFriendRequestAsync); + osLib_addFunction("nn_fp", "DeleteFriendFlagsAsync__Q2_2nn2fpFPCUiUiT2PFQ2_2nn6ResultPv_vPv", export_DeleteFriendFlagsAsync); + + osLib_addFunction("nn_fp", "RemoveFriendAsync__Q2_2nn2fpFUiPFQ2_2nn6ResultPv_vPv", export_RemoveFriendAsync); + osLib_addFunction("nn_fp", "MarkFriendRequestsAsReceivedAsync__Q2_2nn2fpFPCULUiPFQ2_2nn6ResultPv_vPv", export_MarkFriendRequestsAsReceivedAsync); + osLib_addFunction("nn_fp", "CancelFriendRequestAsync__Q2_2nn2fpFULPFQ2_2nn6ResultPv_vPv", export_CancelFriendRequestAsync); + osLib_addFunction("nn_fp", "AcceptFriendRequestAsync__Q2_2nn2fpFULPFQ2_2nn6ResultPv_vPv", export_AcceptFriendRequestAsync); + + } + } +} + diff --git a/src/Cafe/OS/libs/nn_fp/nn_fp.h b/src/Cafe/OS/libs/nn_fp/nn_fp.h new file mode 100644 index 00000000..daab14a0 --- /dev/null +++ b/src/Cafe/OS/libs/nn_fp/nn_fp.h @@ -0,0 +1,8 @@ +#pragma once +namespace nn +{ + namespace fp + { + void load(); + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_idbe/nn_idbe.cpp b/src/Cafe/OS/libs/nn_idbe/nn_idbe.cpp new file mode 100644 index 00000000..55f064ac --- /dev/null +++ b/src/Cafe/OS/libs/nn_idbe/nn_idbe.cpp @@ -0,0 +1,178 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/libs/nn_acp/nn_acp.h" +#include "Cafe/OS/libs/coreinit/coreinit_Thread.h" +#include "Cafe/OS/libs/nn_common.h" +#include "util/crypto/aes128.h" +#include "openssl/sha.h" +#include "Cemu/napi/napi.h" + +namespace nn +{ + namespace idbe + { + struct nnIdbeIconDataV0_t + { + // raw icon data as byte array + // check NAPI::IDBEIconDataV0 for exact data layout + uint8 rawData[0x12060]; + uint8* GetTGAData() + { + return rawData + 0x2030; + } + }; + + static_assert(sizeof(nnIdbeIconDataV0_t) == 0x12060, ""); + + struct nnIdbeHeader_t + { + uint8 formatVersion; + uint8 keyIndex; + }; + + struct nnIdbeEncryptedIcon_t + { + nnIdbeHeader_t header; + uint8 hashSHA256[32]; + nnIdbeIconDataV0_t iconData; + }; + + static_assert(offsetof(nnIdbeEncryptedIcon_t, hashSHA256) == 2, ""); + static_assert(offsetof(nnIdbeEncryptedIcon_t, iconData) == 0x22, ""); + static_assert(sizeof(nnIdbeEncryptedIcon_t) == 0x12082); + + void asyncDownloadIconFile(uint64 titleId, nnIdbeEncryptedIcon_t* iconOut, OSThread_t* thread) + { + std::vector<uint8> idbeData = NAPI::IDBE_RequestRawEncrypted(titleId); + if (idbeData.size() != sizeof(nnIdbeEncryptedIcon_t)) + { + // icon does not exist or has the wrong size + cemuLog_force("IDBE: Failed to retrieve icon for title {:016x}", titleId); + memset(iconOut, 0, sizeof(nnIdbeEncryptedIcon_t)); + coreinit_resumeThread(thread); + return; + } + memcpy(iconOut, idbeData.data(), sizeof(nnIdbeEncryptedIcon_t)); + coreinit_resumeThread(thread); + } + + void export_DownloadIconFile(PPCInterpreter_t* hCPU) + { + ppcDefineParamTypePtr(encryptedIconData, nnIdbeEncryptedIcon_t, 0); + ppcDefineParamU64(titleId, 2); + ppcDefineParamU32(uknR7, 4); + ppcDefineParamU32(uknR8, 5); + + auto asyncTask = std::async(std::launch::async, asyncDownloadIconFile, titleId, encryptedIconData, coreinit::OSGetCurrentThread()); + coreinit::OSSuspendThread(coreinit::OSGetCurrentThread()); + PPCCore_switchToScheduler(); + osLib_returnFromFunction(hCPU, 1); + } + + static_assert(sizeof(nnIdbeHeader_t) == 0x2, ""); + + static uint8 idbeAesKeys[4 * 16] = + { + 0x4A,0xB9,0xA4,0x0E,0x14,0x69,0x75,0xA8,0x4B,0xB1,0xB4,0xF3,0xEC,0xEF,0xC4,0x7B, + 0x90,0xA0,0xBB,0x1E,0x0E,0x86,0x4A,0xE8,0x7D,0x13,0xA6,0xA0,0x3D,0x28,0xC9,0xB8, + 0xFF,0xBB,0x57,0xC1,0x4E,0x98,0xEC,0x69,0x75,0xB3,0x84,0xFC,0xF4,0x07,0x86,0xB5, + 0x80,0x92,0x37,0x99,0xB4,0x1F,0x36,0xA6,0xA7,0x5F,0xB8,0xB4,0x8C,0x95,0xF6,0x6F + }; + + static uint8 idbeAesIv[16] = + { + 0xA4,0x69,0x87,0xAE,0x47,0xD8,0x2B,0xB4,0xFA,0x8A,0xBC,0x04,0x50,0x28,0x5F,0xA4 + }; + + bool decryptIcon(nnIdbeEncryptedIcon_t* iconInput, nnIdbeIconDataV0_t* iconOutput) + { + // check header + nnIdbeHeader_t* idbeHeader = (nnIdbeHeader_t*)iconInput; + if (idbeHeader->formatVersion != 0) + { + forceLog_printf("idbe header version unknown (%d)", (sint32)idbeHeader->formatVersion); + return false; + } + if (idbeHeader->keyIndex >= 4) + { + forceLog_printf("idbe header key count invalid (%d)", (sint32)idbeHeader->keyIndex); + return false; + } + // decrypt data + uint8 iv[16]; + memcpy(iv, idbeAesIv, sizeof(iv)); + uint8 decryptedSHA256[32]; + AES128_CBC_decrypt_updateIV(decryptedSHA256, iconInput->hashSHA256, sizeof(decryptedSHA256), idbeAesKeys + 16 * idbeHeader->keyIndex, iv); + AES128_CBC_decrypt((uint8*)iconOutput, (uint8*)&iconInput->iconData, sizeof(iconInput->iconData), idbeAesKeys + 16 * idbeHeader->keyIndex, iv); + // calculate and compare sha256 + uint8 calculatedSHA256[32]; + SHA256((const unsigned char*)iconOutput, sizeof(nnIdbeIconDataV0_t), (unsigned char*)&calculatedSHA256); + if (memcmp(calculatedSHA256, decryptedSHA256, 32) != 0) + { + forceLogDebug_printf("Idbe icon has incorrect sha256 hash"); + return false; + } + return true; + } + + struct TGAHeader + { + /* +0x00 */ uint8 idLength; + /* +0x01 */ uint8 colorMap; + /* +0x02 */ uint8 imageType; + /* +0x03 */ uint8 colorMap_firstIndex_low; + /* +0x04 */ uint8 colorMap_firstIndex_high; + /* +0x05 */ uint8 colorMap_len_low; + /* +0x06 */ uint8 colorMap_len_high; + /* +0x07 */ uint8 colorMap_bpp; + /* +0x08 */ uint16 image_xOrigin; + /* +0x0A */ uint16 image_yOrigin; + /* +0x0C */ uint16 image_width; + /* +0x0E */ uint16 image_height; + /* +0x10 */ uint8 image_bpp; + /* +0x11 */ uint8 image_desc; + }; + + static_assert(offsetof(TGAHeader, colorMap_firstIndex_low) == 0x03); + static_assert(sizeof(TGAHeader) == 0x12); + + void export_DecryptIconFile(PPCInterpreter_t* hCPU) + { + ppcDefineParamTypePtr(output, nnIdbeIconDataV0_t, 0); + ppcDefineParamTypePtr(input, nnIdbeEncryptedIcon_t, 1); + ppcDefineParamU32(platformMode, 2); + + forceLogDebug_printf("nn_idbe.DecryptIconFile(...)"); + + if (decryptIcon(input, output)) + { + osLib_returnFromFunction(hCPU, 1); + return; + } + forceLogDebug_printf("Unable to decrypt idbe icon file, using default icon"); + + // return default icon + TGAHeader* tgaHeader = (TGAHeader*)(output->GetTGAData()); + memset(tgaHeader, 0, sizeof(TGAHeader)); + tgaHeader->imageType = 2; + + tgaHeader->image_width = 256; + tgaHeader->image_height = 256; + tgaHeader->image_bpp = 32; + tgaHeader->image_desc = (1 << 3); + + osLib_returnFromFunction(hCPU, 1); + } + + void load() + { + // this module is used by: + // Daily Log app + // Download Manager app + // and possibly other system titles? + + osLib_addFunction("nn_idbe", "DownloadIconFile__Q2_2nn4idbeFPvULUsb", export_DownloadIconFile); + osLib_addFunction("nn_idbe", "DecryptIconFile__Q2_2nn4idbeFPvPCv", export_DecryptIconFile); + } + + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_idbe/nn_idbe.h b/src/Cafe/OS/libs/nn_idbe/nn_idbe.h new file mode 100644 index 00000000..5a21b1c0 --- /dev/null +++ b/src/Cafe/OS/libs/nn_idbe/nn_idbe.h @@ -0,0 +1,8 @@ + +namespace nn +{ + namespace idbe + { + void load(); + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_ndm/nn_ndm.cpp b/src/Cafe/OS/libs/nn_ndm/nn_ndm.cpp new file mode 100644 index 00000000..e87e8e69 --- /dev/null +++ b/src/Cafe/OS/libs/nn_ndm/nn_ndm.cpp @@ -0,0 +1,27 @@ +#include "nn_ndm.h" +#include "Cafe/OS/common/OSCommon.h" + +namespace nn +{ + namespace ndm + { + void nnNdmExport_GetDaemonStatus(PPCInterpreter_t* hCPU) + { + // parameters: + // r3 pointer to status integer (out) + // r4 daemon name (integer) + forceLogDebug_printf("nn_ndm.GetDaemonStatus(...) - hack\n"); + // status codes: + // 1 - running? Download Manager (scope.rpx) expects this to return 1 (or zero). Otherwise it will display downloads as disabled + memory_writeU32(hCPU->gpr[3], 1); + // 2 - running? + // 3 - suspended? + osLib_returnFromFunction(hCPU, 0); + } + + void load() + { + osLib_addFunction("nn_ndm", "GetDaemonStatus__Q2_2nn3ndmFPQ4_2nn3ndm7IDaemon6StatusQ4_2nn3ndm4Cafe10DaemonName", nnNdmExport_GetDaemonStatus); + } + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_ndm/nn_ndm.h b/src/Cafe/OS/libs/nn_ndm/nn_ndm.h new file mode 100644 index 00000000..aee24a56 --- /dev/null +++ b/src/Cafe/OS/libs/nn_ndm/nn_ndm.h @@ -0,0 +1,7 @@ +namespace nn +{ + namespace ndm + { + void load(); + } +} diff --git a/src/Cafe/OS/libs/nn_nfp/AmiiboCrypto.h b/src/Cafe/OS/libs/nn_nfp/AmiiboCrypto.h new file mode 100644 index 00000000..1c918e1e --- /dev/null +++ b/src/Cafe/OS/libs/nn_nfp/AmiiboCrypto.h @@ -0,0 +1,267 @@ +#pragma once +#include "util/crypto/aes128.h" +#include "openssl/evp.h" +#include "openssl/hmac.h" + +typedef struct +{ + char typeString[14]; + uint8 magicBytes[16]; + uint8 magicBytesLen; + uint8 xorPad[32]; + uint8 hmacKey[16]; +}amiiboInternalKeys_t; + +void amiiboCalcSeed(AmiiboInternal* internalData, uint8* seed) +{ + memcpy(seed + 0x00, (uint8*)internalData + 0x029, 0x02); + memset(seed + 0x02, 0x00, 0x0E); + memcpy(seed + 0x10, (uint8*)internalData + 0x1D4, 0x08); + memcpy(seed + 0x18, (uint8*)internalData + 0x1D4, 0x08); + memcpy(seed + 0x20, (uint8*)internalData + 0x1E8, 0x20); +} + +void amiiboGenKeyInternalPrepare(amiiboInternalKeys_t* keys, uint8* seed, uint8* output, sint32& outputLen) +{ + sint32 index = 0; + + // concat: + // typeString (including '\0') + sint32 typeStringLen = (sint32)strlen(keys->typeString)+1; + memcpy(output + index, keys->typeString, typeStringLen); + index += typeStringLen; + // seed (16 - magic byte len) + sint32 seedPart1Len = 16 - keys->magicBytesLen; + memcpy(output + index, seed, seedPart1Len); + index += seedPart1Len; + // magic bytes + 16 bytes at +0x10 from input seed + memcpy(output + index, keys->magicBytes, keys->magicBytesLen); + index += keys->magicBytesLen; + // seed 16 bytes at +0x10 + memcpy(output + index, seed + 0x10, 16); + index += 16; + // 32 bytes at +0x20 from input seed xored with xor pad + for (sint32 i = 0; i < 32; i++) + output[index + i] = seed[i + 32] ^ keys->xorPad[i]; + index += 32; + + outputLen = index; +} + +typedef struct +{ + uint8 hmacKey[16]; + uint8 buffer[sizeof(uint16) + 480]; + sint32 bufferSize; + uint16 counter; +}amiiboCryptCtx_t; + +typedef struct +{ + uint8 raw[32]; +}drgbOutput32_t; + +void amiiboCryptInit(amiiboCryptCtx_t* ctx, const uint8* hmacKey, size_t hmacKeySize, const uint8* seed, sint32 seedSize) +{ + ctx->counter = 0; + ctx->bufferSize = (sint32)sizeof(ctx->counter) + seedSize; + memcpy(ctx->hmacKey, hmacKey, 16); + + // set static part of buffer + memcpy(ctx->buffer + sizeof(uint16), seed, seedSize); +} + +void amiiboCryptStep(amiiboCryptCtx_t* ctx, drgbOutput32_t* output) +{ + // update counter + ctx->buffer[0] = ctx->counter >> 8; + ctx->buffer[1] = ctx->counter >> 0; + ctx->counter++; + // generate bytes + uint32 mdLen = (uint32)sizeof(drgbOutput32_t); + HMAC(EVP_sha256(), ctx->hmacKey, sizeof(ctx->hmacKey), (const unsigned char *)ctx->buffer, ctx->bufferSize, output->raw, &mdLen); +} + +typedef struct +{ + uint8 aesKey[16]; + uint8 aesIV[16]; + uint8 hmacKey[16]; +}amiiboDerivedKeys_t; + +static_assert(sizeof(amiiboDerivedKeys_t) == 0x30); + +void amiiboCrypto_genKey(amiiboInternalKeys_t* keys, AmiiboInternal* internalData, amiiboDerivedKeys_t* derivedKeys) +{ + // generate seed + uint8 seed[0x40]; + amiiboCalcSeed(internalData, seed); + + // generate internal seed + uint8 internalKey[512]; + sint32 internalKeyLen = 0; + amiiboGenKeyInternalPrepare(keys, seed, internalKey, internalKeyLen); + + // gen bytes for derived keys + drgbOutput32_t temp[2]; + amiiboCryptCtx_t rngCtx; + amiiboCryptInit(&rngCtx, keys->hmacKey, sizeof(keys->hmacKey), internalKey, internalKeyLen); + amiiboCryptStep(&rngCtx, temp + 0); + amiiboCryptStep(&rngCtx, temp + 1); + memcpy(derivedKeys, temp, sizeof(amiiboDerivedKeys_t)); +} + +void amiiboCrypto_nfcFormatToInternal(AmiiboRawNFCData* nfcData, AmiiboInternal* internalData) +{ + uint8* tag = (uint8*)nfcData; + uint8* intl = (uint8*)internalData; + + memcpy(intl + 0x000, tag + 0x008, 0x008); + memcpy(intl + 0x008, tag + 0x080, 0x020); // tag hmac + memcpy(intl + 0x028, tag + 0x010, 0x024); + memcpy(intl + 0x04C, tag + 0x0A0, 0x168); + memcpy(intl + 0x1B4, tag + 0x034, 0x020); // data hmac + memcpy(intl + 0x1D4, tag + 0x000, 0x008); + memcpy(intl + 0x1DC, tag + 0x054, 0x02C); +} + +void amiiboCrypto_internalToNfcFormat(AmiiboInternal* internalData, AmiiboRawNFCData* nfcData) +{ + uint8* tag = (uint8*)nfcData; + uint8* intl = (uint8*)internalData; + + memcpy(tag + 0x008, intl + 0x000, 0x008); + memcpy(tag + 0x080, intl + 0x008, 0x020); // tag hmac + memcpy(tag + 0x010, intl + 0x028, 0x024); + memcpy(tag + 0x0A0, intl + 0x04C, 0x168); + memcpy(tag + 0x034, intl + 0x1B4, 0x020); // data hmac + memcpy(tag + 0x000, intl + 0x1D4, 0x008); + memcpy(tag + 0x054, intl + 0x1DC, 0x02C); +} + +void amiiboCrypto_transform(const amiiboDerivedKeys_t * keys, uint8 * in, uint8 * out) +{ + uint8 nonce[16]; + + memcpy(nonce, keys->aesIV, sizeof(nonce)); + + AmiiboInternal internalCopy; + memcpy(&internalCopy, in, sizeof(internalCopy)); + + if (out != in) + memcpy(out, in, 0x188 + 0x2C); + AES128CTR_transform(out + 0x2C, 0x188, (uint8*)keys->aesKey, nonce); + + memcpy(out + 0x000, ((uint8*)&internalCopy) + 0x000, 0x008); + memcpy(out + 0x028, ((uint8*)&internalCopy) + 0x028, 0x004); + memcpy(out + 0x1D4, ((uint8*)&internalCopy) + 0x1D4, 0x034); +} + +void AES128_init(); + +void amiiboInitMasterKeys(amiiboInternalKeys_t& kData, amiiboInternalKeys_t& kTag) +{ + uint8 dataKey_hmacKey[16] = { 0x1D, 0x16, 0x4B, 0x37, 0x5B, 0x72, 0xA5, 0x57, 0x28, 0xB9, 0x1D, 0x64, 0xB6, 0xA3, 0xC2, 0x05 }; + uint8 dataKey_typeString[14] = { "unfixed infos" }; + uint8 dataKey_magicBytesSize = 0x0E; + uint8 dataKey_magicBytes[14] = { 0xDB, 0x4B, 0x9E, 0x3F, 0x45, 0x27, 0x8F, 0x39, 0x7E, 0xFF, 0x9B, 0x4F, 0xB9, 0x93 }; + uint8 dataKey_xorPad[32] = { 0x04, 0x49, 0x17, 0xDC, 0x76, 0xB4, 0x96, 0x40, 0xD6, 0xF8, 0x39, 0x39, 0x96, 0x0F, 0xAE, 0xD4, 0xEF, 0x39, 0x2F, 0xAA, 0xB2, 0x14, 0x28, 0xAA, 0x21, 0xFB, 0x54, 0xE5, 0x45, 0x05, 0x47, 0x66 }; + + uint8 tagKey_hmacKey[16] = { 0x7F, 0x75, 0x2D, 0x28, 0x73, 0xA2, 0x00, 0x17, 0xFE, 0xF8, 0x5C, 0x05, 0x75, 0x90, 0x4B, 0x6D }; + uint8 tagKey_typeString[14] = { "locked secret" }; + uint8 tagKey_magicBytesSize = 0x10; + uint8 tagKey_magicBytes[16] = { 0xFD, 0xC8, 0xA0, 0x76, 0x94, 0xB8, 0x9E, 0x4C, 0x47, 0xD3, 0x7D, 0xE8, 0xCE, 0x5C, 0x74, 0xC1 }; + uint8 tagKey_xorPad[32] = { 0x04, 0x49, 0x17, 0xDC, 0x76, 0xB4, 0x96, 0x40, 0xD6, 0xF8, 0x39, 0x39, 0x96, 0x0F, 0xAE, 0xD4, 0xEF, 0x39, 0x2F, 0xAA, 0xB2, 0x14, 0x28, 0xAA, 0x21, 0xFB, 0x54, 0xE5, 0x45, 0x05, 0x47, 0x66 }; + + memcpy(kData.hmacKey, dataKey_hmacKey, 16); + memcpy(kData.typeString, dataKey_typeString, 13); + kData.magicBytesLen = dataKey_magicBytesSize; + memcpy(kData.magicBytes, dataKey_magicBytes, 14); + memcpy(kData.xorPad, dataKey_xorPad, 32); + + memcpy(kTag.hmacKey, tagKey_hmacKey, 16); + memcpy(kTag.typeString, tagKey_typeString, 13); + kTag.magicBytesLen = tagKey_magicBytesSize; + memcpy(kTag.magicBytes, tagKey_magicBytes, 16); + memcpy(kTag.xorPad, tagKey_xorPad, 32); +} + +void amiiboDecrypt() +{ + amiiboInternalKeys_t kData{}; + amiiboInternalKeys_t kTag{}; + amiiboInitMasterKeys(kData, kTag); + + amiiboDerivedKeys_t derivedKeysData{}; + amiiboDerivedKeys_t derivedKeysTag{}; + + amiiboCrypto_nfcFormatToInternal(&nfp_data.amiiboNFCData, &nfp_data.amiiboInternal); + + amiiboCrypto_genKey(&kData, &nfp_data.amiiboInternal, &derivedKeysData); + amiiboCrypto_genKey(&kTag, &nfp_data.amiiboInternal, &derivedKeysTag); + + amiiboCrypto_transform(&derivedKeysData, (uint8*)&nfp_data.amiiboInternal, (uint8*)&nfp_data.amiiboInternal); + + // generate tag hmac + uint32 mdLen = 32; + HMAC(EVP_sha256(), derivedKeysTag.hmacKey, 16, (((uint8*)&nfp_data.amiiboInternal) + 0x1D4), 0x34, + nfp_data.amiiboInternal.tagHMAC, &mdLen); + // generate data hmac + mdLen = 32; + HMAC(EVP_sha256(), derivedKeysData.hmacKey, 16, (((uint8*)&nfp_data.amiiboInternal) + 0x29), 0x1DF, + ((uint8*)&nfp_data.amiiboInternal) + 0x8, &mdLen); + + bool isValidTagHMAC = memcmp(nfp_data.amiiboNFCData.tagHMAC, nfp_data.amiiboInternal.tagHMAC, 32) == 0; + bool isValidDataHMAC = memcmp(nfp_data.amiiboNFCData.dataHMAC, nfp_data.amiiboInternal.dataHMAC, 32) == 0; + if (!isValidTagHMAC) + forceLog_printf("Decrypt amiibo has invalid tag HMAC"); + if (!isValidDataHMAC) + forceLog_printf("Decrypt amiibo has invalid data HMAC"); + nfp_data.hasInvalidHMAC = !isValidTagHMAC || !isValidDataHMAC; +} + +void amiiboEncrypt(AmiiboRawNFCData* nfcOutput) +{ + amiiboInternalKeys_t kData{}; + amiiboInternalKeys_t kTag{}; + amiiboInitMasterKeys(kData, kTag); + + amiiboDerivedKeys_t derivedKeysData{}; + amiiboDerivedKeys_t derivedKeysTag{}; + + // copy internal Amiibo data + AmiiboInternal internalCopy; + memcpy(&internalCopy, &nfp_data.amiiboInternal, sizeof(AmiiboInternal)); + + // gen key + amiiboCrypto_genKey(&kData, &internalCopy, &derivedKeysData); + amiiboCrypto_genKey(&kTag, &internalCopy, &derivedKeysTag); + + // generate new HMACs + uint8 tagHMAC[32]; + uint32 mdLen = 32; + HMAC(EVP_sha256(), derivedKeysTag.hmacKey, 16, (((uint8*)&internalCopy) + 0x1D4), 0x34, + tagHMAC, &mdLen); + mdLen = 32; + uint8 dataHMAC[32]; + HMAC(EVP_sha256(), derivedKeysData.hmacKey, 16, (((uint8*)&internalCopy) + 0x29), 0x1DF, + dataHMAC, &mdLen); + + memset(internalCopy.tagHMAC, 0, 32); + memset(internalCopy.dataHMAC, 0, 32); + + // transform + amiiboCrypto_transform(&derivedKeysData, (uint8*)&internalCopy, (uint8*)&internalCopy); + + // set HMACs + memcpy(internalCopy.tagHMAC, tagHMAC, 32); + memcpy(internalCopy.dataHMAC, dataHMAC, 32); + + // convert back to nfc order + amiiboCrypto_internalToNfcFormat(&internalCopy, nfcOutput); + + // restore NFC values that aren't part of the internal representation + memcpy(nfcOutput->lockBytes, nfp_data.amiiboNFCData.lockBytes, 4); + memcpy(nfcOutput->cfg0, nfp_data.amiiboNFCData.cfg0, 4); + memcpy(nfcOutput->cfg1, nfp_data.amiiboNFCData.cfg1, 4); +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_nfp/nn_nfp.cpp b/src/Cafe/OS/libs/nn_nfp/nn_nfp.cpp new file mode 100644 index 00000000..93e7f7ad --- /dev/null +++ b/src/Cafe/OS/libs/nn_nfp/nn_nfp.cpp @@ -0,0 +1,984 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/HW/Espresso/PPCCallback.h" +#include "Cafe/OS/libs/nn_common.h" +#include "nn_nfp.h" +#include "Cafe/OS/libs/coreinit/coreinit_Thread.h" +#include "Common/filestream.h" +#include "Cafe/CafeSystem.h" + +std::recursive_mutex g_nfpMutex; + +void nnNfpLock() +{ + g_nfpMutex.lock(); +} + +bool nnNfpTryLock() +{ + return g_nfpMutex.try_lock(); +} + +void nnNfpUnlock() +{ + g_nfpMutex.unlock(); +} + +struct AmiiboInternal +{ + /* +0x000 */ uint32 ukn000; + /* +0x004 */ uint32 ukn004; + /* +0x008 */ uint8 dataHMAC[32]; + /* +0x028 */ uint32 ukn028; + /* encrypted region starts here */ + struct + { + /* +0x02C */ uint8 flags; + /* +0x02D */ uint8 countryCode; + /* +0x02E */ uint16be writeCounter; + /* +0x030 */ uint16be date1; + /* +0x032 */ uint16be date2; + /* +0x034 */ uint32be crc; + /* +0x038 */ uint16be nickname[10]; + /* +0x04C */ uint8 mii[0x60]; + /* +0x0AC */ uint32be appDataTitleIdHigh; + /* +0x0B0 */ uint32be appDataTitleIdLow; + /* +0x0B4 */ uint16be writeCounter2; + /* +0x0B6 */ uint16be appDataIdHigh; + /* +0x0B8 */ uint16be appDataIdLow; + /* +0x0BA */ uint16be ukn0BA; + /* +0x0BC */ uint32be ukn0BC; + /* +0x0C0 */ uint32be ukn0C0; + /* +0x0C4 */ uint32be ukn0C4; + /* +0x0C8 */ uint32be ukn0C8; + /* +0x0CC */ uint32be ukn0CC; + /* +0x0D0 */ uint32be ukn0D0; + /* +0x0D4 */ uint32be ukn0D4; + /* +0x0D8 */ uint32be ukn0D8; + uint32 getAppDataAppId() + { + uint32 appId = (uint32)appDataIdHigh << 16; + appId |= (uint32)appDataIdLow; + return appId; + } + + void setAppDataAppId(uint32 appId) + { + appDataIdHigh = (uint16)(appId >> 16); + appDataIdLow = (uint16)(appId & 0xFFFF); + } + }amiiboSettings; + /* +0x0DC */ uint8 applicationData[0xD8]; + /* encrypted region ends here */ + /* +0x1B4 */ uint8 tagHMAC[32]; + /* +0x1D4 */ uint32 ukn1D4; + /* +0x1D8 */ uint32 ukn1D8; + /* +0x1DC */ uint32 ukn1DC; + /* +0x1E0 */ uint8 ukn1E0[0x20]; + /* +0x200 */ uint8 ukn200[0x8]; +}; + +static_assert(sizeof(AmiiboInternal) == 0x208); +static_assert(offsetof(AmiiboInternal, dataHMAC) == 0x8); +static_assert(offsetof(AmiiboInternal, amiiboSettings.appDataIdHigh) == 0xB6); +static_assert(offsetof(AmiiboInternal, amiiboSettings.ukn0D8) == 0xD8); +static_assert(offsetof(AmiiboInternal, tagHMAC) == 0x1B4); + + +union AmiiboRawNFCData +{ + // each page is 4 bytes + struct + { + uint8 page0[4]; + uint8 page1[4]; + uint8 page2[4]; + uint8 page3[4]; + uint8 page4[4]; + uint8 page5[4]; + uint8 page6[4]; + uint8 page7[4]; + uint8 page8[4]; + uint8 page9[4]; + uint8 page10[4]; + uint8 page11[4]; + uint8 page12[4]; + uint8 page13[4]; + uint8 page14[4]; + uint8 page15[4]; + uint8 page16[4]; + }; + struct + { + uint8 rawByte[16*4]; + }; + struct + { + /*+0x000 */ uint8 ntagSerial[9]; + /*+0x009 */ uint8 ukn009; + /*+0x00A */ uint8 lockBytes[2]; + /*+0x00C */ uint8 cc[4]; + /*+0x010 */ uint8 ukn010[4]; + /*+0x014 */ uint8 ukn014[32]; // crypto related? + /*+0x034 */ uint8 tagHMAC[32]; // data hmac + /*+0x054 */ + struct + { + /* +0x54 */ uint8 gameAndCharacterId[2]; + /* +0x56 */ uint8 characterVariation; + /* +0x57 */ uint8 amiiboFigureType; + /* +0x58 */ uint8 amiiboModelNumber[2]; + /* +0x5A */ uint8 amiiboSeries; + /* +0x5B */ uint8 ukn_02; // always 0x02 ? + + /* +0x5C */ uint8 ukn00[0x80-0x5C]; // not part of identification block? + }amiiboIdentificationBlock; + /*+0x080 */ uint8 dataHMAC[32]; + /*+0x0A0 */ uint8 ukn0A0[0x114]; + /*+0x1B4 */ uint8 ukn1B4[0x54]; + /*+0x208 */ uint8 lockBytes208[4]; + /*+0x20C */ uint8 cfg0[4]; + /*+0x210 */ uint8 cfg1[4]; + }; +}; + +static_assert(sizeof(AmiiboRawNFCData) == 532); + +struct AmiiboProcessedData +{ + uint8 uidLength; + uint8 uid[7]; +}; + +struct +{ + bool nfpIsInitialized; + MPTR activateEvent; + MPTR deactivateEvent; + bool isDetecting; + bool isMounted; + bool isReadOnly; + bool hasOpenApplicationArea; // set to true if application area was opened or created + // currently active Amiibo + bool hasActiveAmiibo; + std::wstring amiiboPath; + bool hasInvalidHMAC; + uint32 amiiboTouchTime; + AmiiboRawNFCData amiiboNFCData; // raw data + AmiiboInternal amiiboInternal; // decrypted internal format + AmiiboProcessedData amiiboProcessedData; +}nfp_data = { 0 }; + +bool nnNfp_touchNfcTagFromFile(const wchar_t* filePath, uint32* nfcError); +bool nnNfp_writeCurrentAmiibo(); + +#include "AmiiboCrypto.h" + +void nnNfpExport_SetActivateEvent(PPCInterpreter_t* hCPU) +{ + // parameters: + // r3 OSEvent_t* + ppcDefineParamStructPtr(osEvent, coreinit::OSEvent, 0); + ppcDefineParamMPTR(osEventMPTR, 0); + + debug_printf("nn_nfp.SetActivateEvent(0x%08x)\n", osEventMPTR); + + coreinit::OSInitEvent(osEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO); + + nnNfpLock(); + nfp_data.activateEvent = osEventMPTR; + nnNfpUnlock(); + + osLib_returnFromFunction(hCPU, 0); // return value ukn +} + +void nnNfpExport_SetDeactivateEvent(PPCInterpreter_t* hCPU) +{ + ppcDefineParamStructPtr(osEvent, coreinit::OSEvent, 0); + ppcDefineParamMPTR(osEventMPTR, 0); + + nfpLog_printf("SetDeactivateEvent(0x%08x)", osEventMPTR); + + coreinit::OSInitEvent(osEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO); + + nnNfpLock(); + nfp_data.deactivateEvent = osEventMPTR; + nnNfpUnlock(); + + osLib_returnFromFunction(hCPU, 0); // return value ukn +} + +void nnNfpExport_Initialize(PPCInterpreter_t* hCPU) +{ + debug_printf("Nfp Initialize()\n"); + nfp_data.nfpIsInitialized = true; + nfp_data.isDetecting = false; + nfp_data.hasActiveAmiibo = false; + nfp_data.hasOpenApplicationArea = false; + nfp_data.activateEvent = MPTR_NULL; + nfp_data.deactivateEvent = MPTR_NULL; + osLib_returnFromFunction(hCPU, 0); +} + +void nnNfpExport_StartDetection(PPCInterpreter_t* hCPU) +{ + nfpLog_printf("StartDetection()"); + nnNfpLock(); + nfp_data.isDetecting = true; + nnNfpUnlock(); + osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); +} + +void nnNfpExport_StopDetection(PPCInterpreter_t* hCPU) +{ + nfpLog_printf("StopDetection()"); + nnNfpLock(); + nfp_data.isDetecting = false; + nnNfpUnlock(); + osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); +} + +#define NFP_TAG_MAX_LENGTH (10) + +typedef struct +{ + /* +0x00 */ uint8 uidLength; + /* +0x01 */ uint8 uid[0xA]; + /* +0x0B */ uint8 unused0B[0x15]; + /* +0x20 */ uint32 ukn20[4]; + uint32 ukn30[4]; + uint32 ukn40[4]; + uint32 ukn50; +}nfpTagInfo_t; + +static_assert(sizeof(nfpTagInfo_t) == 0x54, "nfpTagInfo_t has invalid size"); + +void nnNfpExport_GetTagInfo(PPCInterpreter_t* hCPU) +{ + nfpLog_printf("GetTagInfo(0x%08x)", hCPU->gpr[3]); + ppcDefineParamStructPtr(tagInfo, nfpTagInfo_t, 0); + + nnNfpLock(); + if (nfp_data.hasActiveAmiibo == false) + { + nnNfpUnlock(); + osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); // todo: Return correct error code + return; + } + memset(tagInfo, 0x00, sizeof(nfpTagInfo_t)); + + memcpy(tagInfo->uid, nfp_data.amiiboProcessedData.uid, nfp_data.amiiboProcessedData.uidLength); + tagInfo->uidLength = (uint8)nfp_data.amiiboProcessedData.uidLength; + // todo - remaining values + + nnNfpUnlock(); + osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); +} + +typedef struct +{ + /* +0x00 */ uint8 uidLength; + /* +0x01 */ uint8 uid[0xA]; + /* +0x0B */ uint8 ukn0B; + /* +0x0C */ uint8 ukn0C; + /* +0x0D */ uint8 ukn0D; + // more? +}NFCTagInfoCallbackParam_t; + +uint32 NFCGetTagInfo(uint32 index, uint32 timeout, MPTR functionPtr, void* userParam) +{ + nfpLog_printf("NFCGetTagInfo(%d,%d,0x%08x,0x%08x)", index, timeout, functionPtr, userParam?memory_getVirtualOffsetFromPointer(userParam):0); + + + cemu_assert(index == 0); + + nnNfpLock(); + + StackAllocator<NFCTagInfoCallbackParam_t> _callbackParam; + NFCTagInfoCallbackParam_t* callbackParam = _callbackParam.GetPointer(); + + memset(callbackParam, 0x00, sizeof(NFCTagInfoCallbackParam_t)); + + memcpy(callbackParam->uid, nfp_data.amiiboProcessedData.uid, nfp_data.amiiboProcessedData.uidLength); + callbackParam->uidLength = (uint8)nfp_data.amiiboProcessedData.uidLength; + + PPCCoreCallback(functionPtr, index, 0, _callbackParam.GetPointer(), userParam); + + nnNfpUnlock(); + + + return 0; // 0 -> success +} + +void nnNfpExport_Mount(PPCInterpreter_t* hCPU) +{ + nfpLog_printf("Mount()"); + nnNfpLock(); + if (nfp_data.hasActiveAmiibo == false) + { + nnNfpUnlock(); + osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); // todo: Return correct error code + return; + } + nfp_data.isMounted = true; + nfp_data.isReadOnly = false; + nfp_data.isDetecting = false; + nnNfpUnlock(); + osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); +} + +void nnNfpExport_Unmount(PPCInterpreter_t* hCPU) +{ + nfpLog_printf("Unmount()"); + nfp_data.hasOpenApplicationArea = false; + osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); +} + +void nnNfpExport_MountRom(PPCInterpreter_t* hCPU) +{ + nfpLog_printf("MountRom()"); + nnNfpLock(); + if (nfp_data.hasActiveAmiibo == false) + { + nnNfpUnlock(); + osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); // todo: Return correct error code + return; + } + nfp_data.isMounted = true; + nfp_data.isReadOnly = true; + nfp_data.isDetecting = false; + nnNfpUnlock(); + osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); +} + +typedef struct +{ + /* +0x00 */ uint8 characterId[3]; + /* +0x03 */ uint8 amiiboSeries; + /* +0x04 */ uint16be number; + /* +0x06 */ uint8 nfpType; + /* +0x07 */ uint8 unused[0x2F]; +}nfpRomInfo_t; + +static_assert(offsetof(nfpRomInfo_t, amiiboSeries) == 0x3, "nfpRomInfo.seriesId has invalid offset"); +static_assert(offsetof(nfpRomInfo_t, number) == 0x4, "nfpRomInfo.number has invalid offset"); +static_assert(offsetof(nfpRomInfo_t, nfpType) == 0x6, "nfpRomInfo.nfpType has invalid offset"); +static_assert(sizeof(nfpRomInfo_t) == 0x36, "nfpRomInfo_t has invalid size"); + +void nnNfpExport_GetNfpRomInfo(PPCInterpreter_t* hCPU) +{ + nfpLog_printf("GetNfpRomInfo(0x%08x)", hCPU->gpr[3]); + ppcDefineParamStructPtr(romInfo, nfpRomInfo_t, 0); + + nnNfpLock(); + if (nfp_data.hasActiveAmiibo == false) + { + nnNfpUnlock(); + osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); // todo: Return correct error code + return; + } + memset(romInfo, 0x00, sizeof(nfpRomInfo_t)); + + romInfo->characterId[0] = nfp_data.amiiboNFCData.amiiboIdentificationBlock.gameAndCharacterId[0]; + romInfo->characterId[1] = nfp_data.amiiboNFCData.amiiboIdentificationBlock.gameAndCharacterId[1]; + romInfo->characterId[2] = nfp_data.amiiboNFCData.amiiboIdentificationBlock.characterVariation; // guessed + + romInfo->amiiboSeries = nfp_data.amiiboNFCData.amiiboIdentificationBlock.amiiboSeries; // guessed + romInfo->number = *(uint16be*)nfp_data.amiiboNFCData.amiiboIdentificationBlock.amiiboModelNumber; // guessed + romInfo->nfpType = nfp_data.amiiboNFCData.amiiboIdentificationBlock.amiiboFigureType; // guessed + + nnNfpUnlock(); + osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); +} + +typedef struct +{ + uint16be year; + uint8 month; + uint8 day; +}nfpDate_t; + +typedef struct +{ + /* +0x00 */ nfpDate_t date; + /* +0x04 */ uint16be writeCount; + /* +0x06 */ uint8 characterId[3]; + /* +0x09 */ uint8 amiiboSeries; + /* +0x0A */ uint16be number; + /* +0x0C */ uint8 nfpType; + /* +0x0D */ uint8 nfpVersion; + /* +0x0E */ uint16be applicationAreaSize; + /* +0x10 */ uint8 unused[0x30]; +}nfpCommonData_t; + +static_assert(sizeof(nfpCommonData_t) == 0x40, "nfpCommonData_t has invalid size"); +static_assert(offsetof(nfpCommonData_t, writeCount) == 0x4, "nfpCommonData.writeCount has invalid offset"); +static_assert(offsetof(nfpCommonData_t, amiiboSeries) == 0x9, "nfpCommonData.seriesId has invalid offset"); +static_assert(offsetof(nfpCommonData_t, nfpType) == 0xC, "nfpCommonData.nfpType has invalid offset"); +static_assert(offsetof(nfpCommonData_t, applicationAreaSize) == 0xE, "nfpCommonData.applicationAreaSize has invalid offset"); + +void nnNfpExport_GetNfpCommonInfo(PPCInterpreter_t* hCPU) +{ + nfpLog_printf("GetNfpCommonInfo(0x%08x)", hCPU->gpr[3]); + ppcDefineParamStructPtr(commonInfo, nfpCommonData_t, 0); + + nnNfpLock(); + if (nfp_data.hasActiveAmiibo == false) + { + nnNfpUnlock(); + osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); // todo: Return correct error code + return; + } + // tag info format is currently unknown, so we just set it to all zeros for now + if (sizeof(nfpCommonData_t) != 0x40 ) + assert_dbg(); + memset(commonInfo, 0x00, sizeof(nfpCommonData_t)); + + forceLogDebug_printf("GetNfpCommonInfo(0x%08x)"); + + commonInfo->characterId[0] = nfp_data.amiiboNFCData.amiiboIdentificationBlock.gameAndCharacterId[0]; + commonInfo->characterId[1] = nfp_data.amiiboNFCData.amiiboIdentificationBlock.gameAndCharacterId[1]; + commonInfo->characterId[2] = nfp_data.amiiboNFCData.amiiboIdentificationBlock.characterVariation; + + commonInfo->number = *(uint16be*)nfp_data.amiiboNFCData.amiiboIdentificationBlock.amiiboModelNumber; + commonInfo->amiiboSeries = nfp_data.amiiboNFCData.amiiboIdentificationBlock.amiiboSeries; + commonInfo->nfpType = nfp_data.amiiboNFCData.amiiboIdentificationBlock.amiiboFigureType; // guessed + + commonInfo->applicationAreaSize = sizeof(nfp_data.amiiboInternal.applicationData); // not 100% sure if this is always set to the maximum + + nnNfpUnlock(); + osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); +} + +typedef struct +{ + /* +0x00 */ uint8 ownerMii[0x60]; + /* +0x60 */ uint8 nickname[0x14]; + /* +0x74 */ uint16be ukn74; + /* +0x76 */ uint8 ukn76; + /* +0x77 */ uint8 _padding77; + /* +0x78 */ nfpDate_t registerDate; + /* +0x7C */ uint8 ukn7C[0x2C]; +}nfpRegisterInfo_t; + +typedef struct +{ + /* +0x00 */ uint8 ownerMii[0x60]; + /* +0x60 */ uint8 nickname[0x14]; + // maybe has more fields? +}nfpRegisterInfoSet_t; + +void nnNfpExport_GetNfpRegisterInfo(PPCInterpreter_t* hCPU) +{ + nfpLog_printf("GetNfpRegisterInfo(0x%08x)", hCPU->gpr[3]); + ppcDefineParamStructPtr(registerInfo, nfpRegisterInfo_t, 0); + + if(!registerInfo) + { + osLib_returnFromFunction(hCPU, 0xC1B03780); + return; + } + + memset(registerInfo, 0, sizeof(nfpRegisterInfo_t)); + if ((nfp_data.amiiboInternal.amiiboSettings.flags & 0x10) != 0) + { + memcpy(registerInfo->ownerMii, nfp_data.amiiboInternal.amiiboSettings.mii, sizeof(nfp_data.amiiboInternal.amiiboSettings.mii)); + memcpy(registerInfo->nickname, nfp_data.amiiboInternal.amiiboSettings.nickname, sizeof(nfp_data.amiiboInternal.amiiboSettings.nickname)); + } + + // todo - missing fields + + osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); +} + +void nnNfpExport_InitializeRegisterInfoSet(PPCInterpreter_t* hCPU) +{ + nfpLog_printf("InitializeRegisterInfoSet(0x%08x)", hCPU->gpr[3]); + ppcDefineParamStructPtr(registerInfoSet, nfpRegisterInfoSet_t, 0); + + memset(registerInfoSet, 0, sizeof(nfpRegisterInfoSet_t)); + + osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); +} + +void nnNfpExport_SetNfpRegisterInfo(PPCInterpreter_t* hCPU) +{ + nfpLog_printf("SetNfpRegisterInfo(0x%08x)", hCPU->gpr[3]); + ppcDefineParamStructPtr(registerInfoSet, nfpRegisterInfoSet_t, 0); + + memcpy(nfp_data.amiiboInternal.amiiboSettings.mii, registerInfoSet->ownerMii, sizeof(nfp_data.amiiboInternal.amiiboSettings.mii)); + memcpy(nfp_data.amiiboInternal.amiiboSettings.nickname, registerInfoSet->nickname, sizeof(nfp_data.amiiboInternal.amiiboSettings.nickname)); + // todo - set register date and other values + nfp_data.amiiboInternal.amiiboSettings.flags |= 0x10; // set registered bit + + osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); +} + +void nnNfpExport_IsExistApplicationArea(PPCInterpreter_t* hCPU) +{ + nfpLog_printf("IsExistApplicationArea()"); + if (!nfp_data.hasActiveAmiibo || !nfp_data.isMounted) + { + osLib_returnFromFunction(hCPU, 0); + return; + } + bool appAreaExists = (nfp_data.amiiboInternal.amiiboSettings.flags & 0x20) != 0; + osLib_returnFromFunction(hCPU, appAreaExists ? 1 : 0); +} + +void nnNfpExport_OpenApplicationArea(PPCInterpreter_t* hCPU) +{ + nfpLog_printf("OpenApplicationArea(0x%08x)", hCPU->gpr[3]); + ppcDefineParamU32(appAreaId, 0); + + // note - this API doesn't fail if the application area has already been opened? + + if (!(nfp_data.amiiboInternal.amiiboSettings.flags & 0x20)) + { + // no application data set + osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, NN_RESULT_NFP_CODE_NOAPPAREA)); + return; + } + + uint32 nfpAppAreaId = nfp_data.amiiboInternal.amiiboSettings.getAppDataAppId(); + if (nfpAppAreaId != appAreaId) + { + osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, NN_RESULT_NFP_CODE_APPAREAIDMISMATCH)); + return; + } + nfp_data.hasOpenApplicationArea = true; + + osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); +} + +void nnNfpExport_ReadApplicationArea(PPCInterpreter_t* hCPU) +{ + nfpLog_printf("ReadApplicationArea(0x%08x, 0x%x)", hCPU->gpr[3], hCPU->gpr[4]); + ppcDefineParamPtr(bufferPtr, uint8*, 0); + ppcDefineParamU32(len, 1); + + if (!nfp_data.hasOpenApplicationArea) + { + osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); + return; + } + len = std::min(len, (uint32)sizeof(nfp_data.amiiboInternal.applicationData)); + memcpy(bufferPtr, nfp_data.amiiboInternal.applicationData, len); + + osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); +} + +void nnNfpExport_WriteApplicationArea(PPCInterpreter_t* hCPU) +{ + nfpLog_printf("WriteApplicationArea(0x%08x, 0x%x, 0x%08x)", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); + ppcDefineParamPtr(bufferPtr, uint8*, 0); + ppcDefineParamU32(len, 1); + + if (!nfp_data.hasOpenApplicationArea) + { + osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); + return; + } + if (nfp_data.isReadOnly) + { + osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); + return; + } + + len = std::min(len, (uint32)sizeof(nfp_data.amiiboInternal.applicationData)); + memcpy(nfp_data.amiiboInternal.applicationData, bufferPtr, len); + // remaining data is filled with random bytes + for (uint32 i = len; i < sizeof(nfp_data.amiiboInternal.applicationData); i++) + nfp_data.amiiboInternal.applicationData[i] = rand() & 0xFF; + + osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); +} + +typedef struct +{ + uint32be appAreaId; + MEMPTR<uint8> initialData; + uint32be initialLen; + // more? +}NfpCreateInfo_t; + +void nnNfpExport_CreateApplicationArea(PPCInterpreter_t* hCPU) +{ + nfpLog_printf("CreateApplicationArea(0x%08x)", hCPU->gpr[3]); + ppcDefineParamPtr(createInfo, NfpCreateInfo_t, 0); + + if (nfp_data.hasOpenApplicationArea || (nfp_data.amiiboInternal.amiiboSettings.flags&0x20)) + { + // cant create app area if it already exists + osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); + return; + } + if (nfp_data.isReadOnly) + { + osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); + return; + } + + void* writePtr = createInfo->initialData.GetPtr(); + uint32 writeSize = createInfo->initialLen; + if (writeSize > sizeof(nfp_data.amiiboInternal.applicationData)) + { + // requested write size larger than available space + osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); + return; + } + + nfp_data.amiiboInternal.amiiboSettings.setAppDataAppId(createInfo->appAreaId); + nfp_data.amiiboInternal.amiiboSettings.flags |= 0x20; // set application data exists bit + + nfp_data.hasOpenApplicationArea = false; + + // write initial data to application area + memcpy(nfp_data.amiiboInternal.applicationData, writePtr, writeSize); + // remaining data is filled with random bytes + for (uint32 i = writeSize; i < sizeof(nfp_data.amiiboInternal.applicationData); i++) + nfp_data.amiiboInternal.applicationData[i] = rand() & 0xFF; + + // this API forces a flush (unsure, but without this data written by Smash doesn't stick) + if (!nnNfp_writeCurrentAmiibo()) + { + forceLog_printf("Failed to write Amiibo file data when trying to remove appArea"); + osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); + return; + } + + osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); +} + +void nnNfpExport_DeleteApplicationArea(PPCInterpreter_t* hCPU) +{ + nfpLog_printf("DeleteApplicationArea()"); + + if (nfp_data.isReadOnly) + { + osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); + return; + } + if ((nfp_data.amiiboInternal.amiiboSettings.flags & 0x20) == 0) + { + osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); + return; + } + + nfp_data.amiiboInternal.amiiboSettings.setAppDataAppId(0); + nfp_data.amiiboInternal.amiiboSettings.flags &= ~0x20; + + // this API forces a flush + if (!nnNfp_writeCurrentAmiibo()) + { + forceLog_printf("Failed to write Amiibo file data when trying to remove appArea"); + osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); + return; + } + + osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); +} + +void nnNfpExport_Flush(PPCInterpreter_t* hCPU) +{ + nfpLog_printf("Flush()"); + + // write Amiibo data + if (nfp_data.isReadOnly) + { + forceLog_printf("Cannot write to Amiibo when it is mounted in read-only mode"); + osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); + return; + } + + if (!nnNfp_writeCurrentAmiibo()) + { + forceLog_printf("Failed to write Amiibo data"); + osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); + return; + } + + osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); +} + +typedef struct +{ + /* +0x000 */ uint8 ukn00[0x10]; + /* +0x010 */ uint32be mode; + /* +0x014 */ uint8 tagInfo[0x54]; + /* +0x068 */ uint8 isRegistered; // could be uint32be? + /* +0x069 */ uint8 _padding69[3]; + /* +0x06C */ uint8 registerInfo[0xA8]; + /* +0x114 */ uint8 commonInfo[0x40]; + /* +0x154 */ uint8 ukn154[0x20]; +}AmiiboSettingsArgs_t; + +static_assert(sizeof(AmiiboSettingsArgs_t) == 0x174); +static_assert(offsetof(AmiiboSettingsArgs_t, mode) == 0x10); +static_assert(offsetof(AmiiboSettingsArgs_t, tagInfo) == 0x14); +static_assert(offsetof(AmiiboSettingsArgs_t, isRegistered) == 0x68); +static_assert(offsetof(AmiiboSettingsArgs_t, registerInfo) == 0x6C); +static_assert(offsetof(AmiiboSettingsArgs_t, commonInfo) == 0x114); + +void nnNfpExport_GetAmiiboSettingsArgs(PPCInterpreter_t* hCPU) +{ + nfpLog_printf("GetAmiiboSettingsArgs(0x%08x)", hCPU->gpr[3]); + ppcDefineParamStructPtr(settingsArg, AmiiboSettingsArgs_t, 0); + + memset(settingsArg, 0, sizeof(AmiiboSettingsArgs_t)); + + // modes: + // 0 -> Register owner and nickname + // 0x64 -> Launch normally + + settingsArg->mode = 0x64; + + osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); +} + +void nnNfp_unloadAmiibo() +{ + nnNfpLock(); + nfp_data.isMounted = false; + nfp_data.hasActiveAmiibo = false; + nnNfpUnlock(); +} + +bool nnNfp_touchNfcTagFromFile(const wchar_t* filePath, uint32* nfcError) +{ + AmiiboRawNFCData rawData = { 0 }; + auto nfcData = FileStream::LoadIntoMemory(filePath); + if (!nfcData) + { + *nfcError = NFC_ERROR_NO_ACCESS; + return false; + } + if (nfcData->size() < sizeof(AmiiboRawNFCData)) + { + *nfcError = NFC_ERROR_INVALID_FILE_FORMAT; + return false; + } + memcpy(&rawData, nfcData->data(), sizeof(AmiiboRawNFCData)); + + // verify if the file is a valid ntag215/amiibo file + if (rawData.cfg0[0] != 0x00 || rawData.cfg0[1] != 0x00 || rawData.cfg0[2] != 0x00 || rawData.cfg0[3] != 0x04) + { + *nfcError = NFC_ERROR_INVALID_FILE_FORMAT; + return false; + } + if (rawData.cfg1[0] != 0x5F || rawData.cfg1[1] != 0x00 || rawData.cfg1[2] != 0x00 || rawData.cfg1[3] != 0x00) + { + *nfcError = NFC_ERROR_INVALID_FILE_FORMAT; + return false; + } + if (rawData.lockBytes[0] != 0x0F || rawData.lockBytes[1] != 0xE0 ) + { + *nfcError = NFC_ERROR_INVALID_FILE_FORMAT; + return false; + } + if (rawData.cc[0] != 0xF1 || rawData.cc[1] != 0x10 || rawData.cc[2] != 0xFF || rawData.cc[3] != 0xEE) + { + *nfcError = NFC_ERROR_INVALID_FILE_FORMAT; + return false; + } + + // process uid + uint8 serialNumber[7]; + serialNumber[0] = rawData.ntagSerial[0]; + serialNumber[1] = rawData.ntagSerial[1]; + serialNumber[2] = rawData.ntagSerial[2]; + serialNumber[3] = rawData.ntagSerial[4]; + serialNumber[4] = rawData.ntagSerial[5]; + serialNumber[5] = rawData.ntagSerial[6]; + serialNumber[6] = rawData.ntagSerial[7]; + + uint8 serialCheckByte0 = rawData.ntagSerial[3]; + uint8 serialCheckByte1 = rawData.ntagSerial[8]; + + uint8 bcc0 = serialNumber[0] ^ serialNumber[1] ^ serialNumber[2] ^ 0x88; + uint8 bcc1 = serialNumber[3] ^ serialNumber[4] ^ serialNumber[5] ^ serialNumber[6]; + + if (serialCheckByte0 != bcc0 || serialCheckByte1 != bcc1) + { + forceLog_printf("nn_nfp: Mismatch in serial checksum of scanned NFC tag"); + } + nfp_data.amiiboProcessedData.uidLength = 7; + memcpy(nfp_data.amiiboProcessedData.uid, serialNumber, 7); + // signal activation event + nnNfp_unloadAmiibo(); + nnNfpLock(); + memcpy(&nfp_data.amiiboNFCData, &rawData, sizeof(AmiiboRawNFCData)); + // decrypt amiibo + amiiboDecrypt(); + nfp_data.amiiboPath = std::wstring(filePath); + nfp_data.hasActiveAmiibo = true; + if (nfp_data.activateEvent) + { + coreinit::OSEvent* osEvent = (coreinit::OSEvent*)memory_getPointerFromVirtualOffset(nfp_data.activateEvent); + coreinit::OSSignalEvent(osEvent); + } + nfp_data.amiiboTouchTime = GetTickCount(); + nnNfpUnlock(); + *nfcError = NFC_ERROR_NO_ACCESS; + return true; +} + +bool nnNfp_writeCurrentAmiibo() +{ + nnNfpLock(); + if (!nfp_data.hasActiveAmiibo) + { + nnNfpUnlock(); + return false; + } + // open file for writing + FileStream* fs = FileStream::openFile2(nfp_data.amiiboPath, true); + if (!fs) + { + nnNfpUnlock(); + return false; + } + // encrypt Amiibo and convert to NFC format + AmiiboRawNFCData nfcData; + amiiboEncrypt(&nfcData); + // write to file + fs->writeData(&nfcData, sizeof(AmiiboRawNFCData)); + delete fs; + + nnNfpUnlock(); + return true; +} + +void nnNfp_update() +{ + // lock-free check if amiibo is touching + if (nfp_data.hasActiveAmiibo == false) + return; + if (!nnNfpTryLock()) + return; + // make sure amiibo is still touching after acquiring lock + if (nfp_data.hasActiveAmiibo == false) + return; + uint32 amiiboElapsedTouchTime = GetTickCount() - nfp_data.amiiboTouchTime; + if (amiiboElapsedTouchTime >= 1500) + { + nnNfp_unloadAmiibo(); + } + nnNfpUnlock(); + if (nfp_data.deactivateEvent) + { + coreinit::OSEvent* osEvent = (coreinit::OSEvent*)memory_getPointerFromVirtualOffset(nfp_data.deactivateEvent); + coreinit::OSSignalEvent(osEvent); + } +} + +void nnNfpExport_GetNfpState(PPCInterpreter_t* hCPU) +{ + nfpLog_printf("GetNfpState()"); + + // workaround for Mario Party 10 eating CPU cycles in an infinite loop (maybe due to incorrect NFP detection handling?) + uint64 titleId = CafeSystem::GetForegroundTitleId(); + if (titleId == 0x0005000010162d00 || titleId == 0x0005000010162e00) + { + coreinit::OSSleepTicks(ESPRESSO_CORE_CLOCK / 100); // pause for 10ms + } + + uint32 nfpState; + if (nfp_data.nfpIsInitialized == false) + { + nfpState = NFP_STATE_NONE; + } + else + { + if (nfp_data.isMounted && nfp_data.hasActiveAmiibo) + { + if (nfp_data.isReadOnly) + nfpState = NFP_STATE_RW_MOUNT_ROM; + else + nfpState = NFP_STATE_RW_MOUNT; + } + else if (nfp_data.isDetecting) + { + // todo: is this handled correctly? + if (nfp_data.hasActiveAmiibo) + { + nfpState = NFP_STATE_RW_ACTIVE; + } + else + nfpState = NFP_STATE_RW_SEARCH; + } + else + { + nfpState = NFP_STATE_INIT; + } + } + // returns state of nfp library + osLib_returnFromFunction(hCPU, nfpState); +} + +namespace nn::nfp +{ + uint32 GetErrorCode(uint32 result) + { + uint32 level = (result >> 0x1b) & 3; + uint32 mask = 0x7f00000; + if (level != 3) + { + mask = 0x1ff00000; + } + if (((result & mask) == 0x1b00000) && ((result & 0x80000000) != 0)) + { + mask = 0x3ff; + if (level != 3) + { + mask = 0xfffff; + } + return ((result & mask) >> 7) + 1680000; + } + return 1680000; + } + + void nnNfp_load() + { + osLib_addFunction("nn_nfp", "SetActivateEvent__Q2_2nn3nfpFP7OSEvent", nnNfpExport_SetActivateEvent); + osLib_addFunction("nn_nfp", "SetDeactivateEvent__Q2_2nn3nfpFP7OSEvent", nnNfpExport_SetDeactivateEvent); + osLib_addFunction("nn_nfp", "StartDetection__Q2_2nn3nfpFv", nnNfpExport_StartDetection); + osLib_addFunction("nn_nfp", "StopDetection__Q2_2nn3nfpFv", nnNfpExport_StopDetection); + + osLib_addFunction("nn_nfp", "GetTagInfo__Q2_2nn3nfpFPQ3_2nn3nfp7TagInfo", nnNfpExport_GetTagInfo); + osLib_addFunction("nn_nfp", "Mount__Q2_2nn3nfpFv", nnNfpExport_Mount); + osLib_addFunction("nn_nfp", "MountRom__Q2_2nn3nfpFv", nnNfpExport_MountRom); + osLib_addFunction("nn_nfp", "Unmount__Q2_2nn3nfpFv", nnNfpExport_Unmount); + + osLib_addFunction("nn_nfp", "GetNfpRomInfo__Q2_2nn3nfpFPQ3_2nn3nfp7RomInfo", nnNfpExport_GetNfpRomInfo); + osLib_addFunction("nn_nfp", "GetNfpCommonInfo__Q2_2nn3nfpFPQ3_2nn3nfp10CommonInfo", nnNfpExport_GetNfpCommonInfo); + osLib_addFunction("nn_nfp", "GetNfpRegisterInfo__Q2_2nn3nfpFPQ3_2nn3nfp12RegisterInfo", nnNfpExport_GetNfpRegisterInfo); + + osLib_addFunction("nn_nfp", "InitializeRegisterInfoSet__Q2_2nn3nfpFPQ3_2nn3nfp15RegisterInfoSet", nnNfpExport_InitializeRegisterInfoSet); + osLib_addFunction("nn_nfp", "SetNfpRegisterInfo__Q2_2nn3nfpFRCQ3_2nn3nfp15RegisterInfoSet", nnNfpExport_SetNfpRegisterInfo); + + osLib_addFunction("nn_nfp", "IsExistApplicationArea__Q2_2nn3nfpFv", nnNfpExport_IsExistApplicationArea); + osLib_addFunction("nn_nfp", "OpenApplicationArea__Q2_2nn3nfpFUi", nnNfpExport_OpenApplicationArea); + osLib_addFunction("nn_nfp", "CreateApplicationArea__Q2_2nn3nfpFRCQ3_2nn3nfp25ApplicationAreaCreateInfo", nnNfpExport_CreateApplicationArea); + osLib_addFunction("nn_nfp", "DeleteApplicationArea__Q2_2nn3nfpFv", nnNfpExport_DeleteApplicationArea); + osLib_addFunction("nn_nfp", "ReadApplicationArea__Q2_2nn3nfpFPvUi", nnNfpExport_ReadApplicationArea); + osLib_addFunction("nn_nfp", "WriteApplicationArea__Q2_2nn3nfpFPCvUiRCQ3_2nn3nfp5TagId", nnNfpExport_WriteApplicationArea); + + osLib_addFunction("nn_nfp", "Flush__Q2_2nn3nfpFv", nnNfpExport_Flush); + + osLib_addFunction("nn_nfp", "Initialize__Q2_2nn3nfpFv", nnNfpExport_Initialize); + osLib_addFunction("nn_nfp", "GetNfpState__Q2_2nn3nfpFv", nnNfpExport_GetNfpState); + + osLib_addFunction("nn_nfp", "GetAmiiboSettingsArgs__Q2_2nn3nfpFPQ3_2nn3nfp18AmiiboSettingsArgs", nnNfpExport_GetAmiiboSettingsArgs); + } + + void load() + { + nnNfp_load(); // legacy interface, update these to use cafeExportRegister / cafeExportRegisterFunc + + cafeExportRegisterFunc(nn::nfp::GetErrorCode, "nn_nfp", "GetErrorCode__Q2_2nn3nfpFRCQ2_2nn6Result", LogType::Placeholder); + + // NFC API + cafeExportRegister("nn_nfp", NFCGetTagInfo, LogType::Placeholder); + } + +} diff --git a/src/Cafe/OS/libs/nn_nfp/nn_nfp.h b/src/Cafe/OS/libs/nn_nfp/nn_nfp.h new file mode 100644 index 00000000..793b3bc3 --- /dev/null +++ b/src/Cafe/OS/libs/nn_nfp/nn_nfp.h @@ -0,0 +1,25 @@ +#pragma once + +namespace nn::nfp +{ + void load(); +} + +void nnNfp_load(); +void nnNfp_update(); + +bool nnNfp_touchNfcTagFromFile(const wchar_t* filePath, uint32* nfcError); + +#define NFP_STATE_NONE (0) +#define NFP_STATE_INIT (1) +#define NFP_STATE_RW_SEARCH (2) +#define NFP_STATE_RW_ACTIVE (3) +#define NFP_STATE_RW_DEACTIVE (4) +#define NFP_STATE_RW_MOUNT (5) +#define NFP_STATE_UNEXPECTED (6) +#define NFP_STATE_RW_MOUNT_ROM (7) + +// CEMU NFC error codes +#define NFC_ERROR_NONE (0) +#define NFC_ERROR_NO_ACCESS (1) +#define NFC_ERROR_INVALID_FILE_FORMAT (2) diff --git a/src/Cafe/OS/libs/nn_nim/nn_nim.cpp b/src/Cafe/OS/libs/nn_nim/nn_nim.cpp new file mode 100644 index 00000000..565f3fa6 --- /dev/null +++ b/src/Cafe/OS/libs/nn_nim/nn_nim.cpp @@ -0,0 +1,282 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/IOSU/legacy/iosu_ioctl.h" +#include "Cafe/IOSU/legacy/iosu_nim.h" +#include "Cafe/OS/libs/coreinit/coreinit_IOS.h" + +#define nimPrepareRequest() \ +StackAllocator<iosu::nim::iosuNimCemuRequest_t> _buf_nimRequest; \ +StackAllocator<ioBufferVector_t> _buf_bufferVector; \ +iosu::nim::iosuNimCemuRequest_t* nimRequest = _buf_nimRequest.GetPointer(); \ +ioBufferVector_t* nimBufferVector = _buf_bufferVector.GetPointer(); \ +memset(nimRequest, 0, sizeof(iosu::nim::iosuNimCemuRequest_t)); \ +memset(nimBufferVector, 0, sizeof(ioBufferVector_t)); \ +nimBufferVector->buffer = (uint8*)nimRequest; + +namespace nn +{ + namespace nim + { + + void export_NeedsNetworkUpdate(PPCInterpreter_t* hCPU) + { + forceLogDebug_printf("NeedsNetworkUpdate() - placeholder"); + ppcDefineParamTypePtr(needsUpdate, uint8, 0); + + *needsUpdate = 0; + + osLib_returnFromFunction(hCPU, 0); + } + + typedef struct + { + uint32be ukn00; + uint32be ukn04; + uint32be ukn08; + uint32be ukn0C; + uint32be ukn10; + uint32be ukn14; + uint32be ukn18; + }updatePackageProgress_t; + + void export_GetUpdatePackageProgress(PPCInterpreter_t* hCPU) + { + forceLogDebug_printf("GetUpdatePackageProgress() - placeholder"); + ppcDefineParamTypePtr(updatePackageProgress, updatePackageProgress_t, 0); + // status of system update download + // values are unknown + updatePackageProgress->ukn00 = 0; + updatePackageProgress->ukn04 = 0; + updatePackageProgress->ukn18 = 0; + + osLib_returnFromFunction(hCPU, 0); + } + + void export_NeedsNotifyToUsers(PPCInterpreter_t* hCPU) + { + forceLogDebug_printf("NeedsNotifyToUsers() - placeholder"); + ppcDefineParamTypePtr(updatePackageProgress, updatePackageProgress_t, 0); + + osLib_returnFromFunction(hCPU, 0); + } + + void export_GetNumTitlePackages(PPCInterpreter_t* hCPU) + { + forceLogDebug_printf("GetNumTitlePackages() - placeholder"); + + nimPrepareRequest(); + + nimRequest->requestCode = IOSU_NIM_GET_PACKAGE_COUNT; + + __depr__IOS_Ioctlv(IOS_DEVICE_NIM, IOSU_NIM_REQUEST_CEMU, 1, 1, nimBufferVector); + + uint32 numTitlePackages = nimRequest->resultU32.u32; + + osLib_returnFromFunction(hCPU, numTitlePackages); + } + + static_assert(sizeof(iosu::nim::titlePackageInfo_t) == 0x50, "titlePackageInfo_t has invalid size"); + static_assert(offsetof(iosu::nim::titlePackageInfo_t, ukn28DLProgressRelatedMax_u64be) == 0x28, "ukn28_u64be has invalid offset"); + + void export_ListTitlePackagesStatically(PPCInterpreter_t* hCPU) + { + forceLogDebug_printf("ListTitlePackagesStatically() - placeholder"); + ppcDefineParamTypePtr(titleIdList, uint64, 0); + ppcDefineParamS32(maxCount, 1); + + nimPrepareRequest(); + + nimRequest->requestCode = IOSU_NIM_GET_PACKAGES_TITLEID; + nimRequest->maxCount = maxCount; + nimRequest->ptr = (uint8*)(titleIdList); + + __depr__IOS_Ioctlv(IOS_DEVICE_NIM, IOSU_NIM_REQUEST_CEMU, 1, 1, nimBufferVector); + + osLib_returnFromFunction(hCPU, 0); + } + + void export_GetTitlePackageInfos(PPCInterpreter_t* hCPU) + { + forceLogDebug_printf("GetTitlePackageInfos() - placeholder"); + ppcDefineParamTypePtr(titlePackageInfo, iosu::nim::titlePackageInfo_t, 0); + ppcDefineParamTypePtr(titleIdList, uint64, 1); + ppcDefineParamU32(count, 2); + + nimPrepareRequest(); + + nimRequest->requestCode = IOSU_NIM_GET_PACKAGES_INFO; + nimRequest->maxCount = count; + nimRequest->ptr = (uint8*)(titleIdList); + nimRequest->ptr2 = (uint8*)(titlePackageInfo); + + __depr__IOS_Ioctlv(IOS_DEVICE_NIM, IOSU_NIM_REQUEST_CEMU, 1, 1, nimBufferVector); + + osLib_returnFromFunction(hCPU, 0); + } + + void export_NeedsNotifyToUsersTitlePackage(PPCInterpreter_t* hCPU) + { + forceLogDebug_printf("NeedsNotifyToUsers() - placeholder"); + ppcDefineParamTypePtr(titlePackageInfo, iosu::nim::titlePackageInfo_t, 0); + + osLib_returnFromFunction(hCPU, 0); + } + + using IDBE_DATA = uint8[0x12060]; + + void export_GetIconDatabaseEntries(PPCInterpreter_t* hCPU) + { + forceLogDebug_printf("GetIconDatabaseEntries() - placeholder"); + ppcDefineParamTypePtr(iconDatabaseEntries, IDBE_DATA, 0); + ppcDefineParamTypePtr(titleIdList, uint64, 1); + ppcDefineParamS32(count, 2); + + cemu_assert_debug(count == 1); // other count values are untested + + for (sint32 i = 0; i < count; i++) + { + nimPrepareRequest(); + + nimRequest->requestCode = IOSU_NIM_GET_ICON_DATABASE_ENTRY; + nimRequest->titleId = _swapEndianU64(titleIdList[i]); + nimRequest->ptr = (uint8*)(iconDatabaseEntries + i); + + __depr__IOS_Ioctlv(IOS_DEVICE_NIM, IOSU_NIM_REQUEST_CEMU, 1, 1, nimBufferVector); + + } + + + osLib_returnFromFunction(hCPU, 0); + } + + void export_QuerySchedulerStatus(PPCInterpreter_t* hCPU) + { + forceLogDebug_printf("QuerySchedulerStatus() - placeholder"); + + // scheduler status seems to a be a 32bit value? + // scope.rpx only checks the second byte and if it matches 0x01 then the scheduler is considered paused/stopped (displays that downloads are inactive) + + // downloads disabled: + //memory_writeU32(hCPU->gpr[3], (0x00010000)); + // downloads enabled: + memory_writeU32(hCPU->gpr[3], (0x00000000)); + + osLib_returnFromFunction(hCPU, 0); + } + + typedef struct + { + uint32be iosError; + uint32be ukn04; + }nimResultError_t; // size unknown, but probably is 0x8 + + + void export_ConstructResultError(PPCInterpreter_t* hCPU) + { + forceLogDebug_printf("Construct__Q3_2nn3nim11ResultErrorFQ2_2nn6Resulti() - placeholder"); + ppcDefineParamTypePtr(resultError, nimResultError_t, 0); + ppcDefineParamU32BEPtr(nimErrorCodePtr, 1); + ppcDefineParamU32(uknParam, 2); + + resultError->iosError = 0; + resultError->ukn04 = uknParam; + + osLib_returnFromFunction(hCPU, 0); + } + + void export_GetECommerceInfrastructureCountry(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32BEPtr(country, 0); + + forceLogDebug_printf("GetECommerceInfrastructureCountry - todo"); + + *country = 0; + + osLib_returnFromFunction(hCPU, 0); + } + + typedef struct + { + betype<uint32> titleIdHigh; + betype<uint32> titleIdLow; + uint32 regionOrLanguageRelated; + uint8 uknByte0C; + uint8 applicationBoxDevice1; // this is what E-Shop app reads to determine device (0 -> mlc, 1 -> extern storage?) + uint8 uknByte0E; + uint8 applicationBoxDevice2; + uint32 ukn10; + uint8 uknByte14; // set to 0 + uint8 uknByte15; // set to 1 + uint8 postDownloadAction; // 0 -> ?, 1 -> ?, 2 -> Use bg install policy + uint8 uknBytes17; + }TitlePackageTaskConfig_t; + + static_assert(sizeof(TitlePackageTaskConfig_t) == 0x18, ""); + + void export_MakeTitlePackageTaskConfigAutoUsingBgInstallPolicy(PPCInterpreter_t* hCPU) + { + ppcDefineParamPtr(titlePackageTastConfig, TitlePackageTaskConfig_t, 0); + ppcDefineParamU64(titleId, 2); + ppcDefineParamU32(regionOrLanguage, 4); + ppcDefineParamU32(uknR8, 5); // title type? + + forceLogDebug_printf("MakeTitlePackageTaskConfigAutoUsingBgInstallPolicy - placeholder"); + + titlePackageTastConfig->titleIdHigh = (uint32)(titleId >> 32); + titlePackageTastConfig->titleIdLow = (uint32)(titleId & 0xFFFFFFFF); + titlePackageTastConfig->regionOrLanguageRelated = 0; // ? + titlePackageTastConfig->uknByte0C = uknR8; + titlePackageTastConfig->applicationBoxDevice1 = 1; // 1 -> mlc + titlePackageTastConfig->applicationBoxDevice2 = 1; // 1 -> mlc + + titlePackageTastConfig->uknByte0E = 0; // ? + titlePackageTastConfig->ukn10 = 0; // ? + titlePackageTastConfig->uknByte14 = 0; // ? + titlePackageTastConfig->uknByte15 = 1; // ? + + titlePackageTastConfig->postDownloadAction = 0; // ? + + titlePackageTastConfig->uknBytes17 = 0; // ? + + osLib_returnFromFunction(hCPU, 0); + } + + void export_CalculateTitleInstallSize(PPCInterpreter_t* hCPU) + { + ppcDefineParamTypePtr(installSize, betype<uint64>, 0); + ppcDefineParamPtr(titlePackageTastConfig, TitlePackageTaskConfig_t, 1); + + // get install size of currently installed title, otherwise return -1 as size + forceLogDebug_printf("CalculateTitleInstallSize - todo\n"); + + *installSize = 0xFFFFFFFFFFFFFFFF; + + osLib_returnFromFunction(hCPU, 0); + } + + void load() + { + osLib_addFunction("nn_nim", "NeedsNetworkUpdate__Q2_2nn3nimFPb", export_NeedsNetworkUpdate); + osLib_addFunction("nn_nim", "GetUpdatePackageProgress__Q2_2nn3nimFPQ3_2nn3nim21UpdatePackageProgress", export_GetUpdatePackageProgress); + osLib_addFunction("nn_nim", "NeedsNotifyToUsers__Q3_2nn3nim4utilFPCQ3_2nn3nim21UpdatePackageProgress", export_NeedsNotifyToUsers); + osLib_addFunction("nn_nim", "GetNumTitlePackages__Q2_2nn3nimFv", export_GetNumTitlePackages); + + osLib_addFunction("nn_nim", "GetTitlePackageInfos__Q2_2nn3nimFPQ3_2nn3nim16TitlePackageInfoPCULUi", export_GetTitlePackageInfos); + osLib_addFunction("nn_nim", "NeedsNotifyToUsers__Q3_2nn3nim4utilFPCQ3_2nn3nim16TitlePackageInfoPCQ3_2nn3nim11ResultError", export_NeedsNotifyToUsersTitlePackage); + + osLib_addFunction("nn_nim", "ListTitlePackagesStatically__Q2_2nn3nimFPULUi", export_ListTitlePackagesStatically); + + osLib_addFunction("nn_nim", "GetECommerceInfrastructureCountry__Q2_2nn3nimFPQ3_2nn3nim7Country", export_GetECommerceInfrastructureCountry); + + + osLib_addFunction("nn_nim", "QuerySchedulerStatus__Q2_2nn3nimFPQ3_2nn3nim15SchedulerStatus", export_QuerySchedulerStatus); + + osLib_addFunction("nn_nim", "GetIconDatabaseEntries__Q2_2nn3nimFPQ3_2nn3nim17IconDatabaseEntryPCULUi", export_GetIconDatabaseEntries); + + osLib_addFunction("nn_nim", "Construct__Q3_2nn3nim11ResultErrorFQ2_2nn6Resulti", export_ConstructResultError); + + osLib_addFunction("nn_nim", "MakeTitlePackageTaskConfigAutoUsingBgInstallPolicy__Q3_2nn3nim4utilFULiQ3_2nn4Cafe9TitleType", export_MakeTitlePackageTaskConfigAutoUsingBgInstallPolicy); + osLib_addFunction("nn_nim", "CalculateTitleInstallSize__Q2_2nn3nimFPLRCQ3_2nn3nim22TitlePackageTaskConfigPCUsUi", export_CalculateTitleInstallSize); + + } + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_nim/nn_nim.h b/src/Cafe/OS/libs/nn_nim/nn_nim.h new file mode 100644 index 00000000..02907cec --- /dev/null +++ b/src/Cafe/OS/libs/nn_nim/nn_nim.h @@ -0,0 +1,9 @@ +#pragma once + +namespace nn +{ + namespace nim + { + void load(); + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv.cpp b/src/Cafe/OS/libs/nn_olv/nn_olv.cpp new file mode 100644 index 00000000..b2533588 --- /dev/null +++ b/src/Cafe/OS/libs/nn_olv/nn_olv.cpp @@ -0,0 +1,229 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/libs/nn_common.h" +#include "nn_olv.h" + +namespace nn +{ + namespace olv + { + struct DownloadedPostData_t + { + /* +0x0000 */ uint32be flags; + /* +0x0004 */ uint32be userPrincipalId; + /* +0x0008 */ char postId[0x20]; // size guessed + /* +0x0028 */ uint64 postDate; + /* +0x0030 */ uint8 feeling; + /* +0x0031 */ uint8 padding0031[3]; + /* +0x0034 */ uint32be regionId; + /* +0x0038 */ uint8 platformId; + /* +0x0039 */ uint8 languageId; + /* +0x003A */ uint8 countryId; + /* +0x003B */ uint8 padding003B[1]; + /* +0x003C */ uint16be bodyText[0x100]; // actual size is unknown + /* +0x023C */ uint32be bodyTextLength; + /* +0x0240 */ uint8 compressedMemoBody[0xA000]; // 40KB + /* +0xA240 */ uint32be compressedMemoBodyRelated; // size of compressed data? + /* +0xA244 */ uint16be topicTag[0x98]; + // app data + /* +0xA374 */ uint8 appData[0x400]; + /* +0xA774 */ uint32be appDataLength; + // external binary + /* +0xA778 */ uint8 externalBinaryUrl[0x100]; + /* +0xA878 */ uint32be externalBinaryDataSize; + // external image + /* +0xA87C */ uint8 externalImageDataUrl[0x100]; + /* +0xA97C */ uint32be externalImageDataSize; + // external url ? + /* +0xA980 */ char externalUrl[0x100]; + // mii + /* +0xAA80 */ uint8 miiData[0x60]; + /* +0xAAE0 */ uint16be miiNickname[0x20]; + /* +0xAB20 */ uint8 unusedAB20[0x14E0]; + + // everything above is part of DownloadedDataBase + // everything below is part of DownloadedPostData + /* +0xC000 */ uint8 uknDataC000[8]; // ?? + /* +0xC008 */ uint32be communityId; + /* +0xC00C */ uint32be empathyCount; + /* +0xC010 */ uint32be commentCount; + /* +0xC014 */ uint8 unused[0x1F4]; + }; // size: 0xC208 + + static_assert(sizeof(DownloadedPostData_t) == 0xC208, ""); + static_assert(offsetof(DownloadedPostData_t, postDate) == 0x0028, ""); + static_assert(offsetof(DownloadedPostData_t, platformId) == 0x0038, ""); + static_assert(offsetof(DownloadedPostData_t, bodyText) == 0x003C, ""); + static_assert(offsetof(DownloadedPostData_t, compressedMemoBody) == 0x0240, ""); + static_assert(offsetof(DownloadedPostData_t, topicTag) == 0xA244, ""); + static_assert(offsetof(DownloadedPostData_t, appData) == 0xA374, ""); + static_assert(offsetof(DownloadedPostData_t, externalBinaryUrl) == 0xA778, ""); + static_assert(offsetof(DownloadedPostData_t, externalImageDataUrl) == 0xA87C, ""); + static_assert(offsetof(DownloadedPostData_t, externalUrl) == 0xA980, ""); + static_assert(offsetof(DownloadedPostData_t, miiData) == 0xAA80, ""); + static_assert(offsetof(DownloadedPostData_t, miiNickname) == 0xAAE0, ""); + static_assert(offsetof(DownloadedPostData_t, unusedAB20) == 0xAB20, ""); + static_assert(offsetof(DownloadedPostData_t, communityId) == 0xC008, ""); + static_assert(offsetof(DownloadedPostData_t, empathyCount) == 0xC00C, ""); + static_assert(offsetof(DownloadedPostData_t, commentCount) == 0xC010, ""); + + const int POST_DATA_FLAG_HAS_BODY_TEXT = (0x0001); + const int POST_DATA_FLAG_HAS_BODY_MEMO = (0x0002); + + + void export_DownloadPostDataList(PPCInterpreter_t* hCPU) + { + ppcDefineParamTypePtr(downloadedTopicData, void, 0); // DownloadedTopicData + ppcDefineParamTypePtr(downloadedPostData, DownloadedPostData_t, 1); // DownloadedPostData + ppcDefineParamTypePtr(downloadedPostDataSize, uint32be, 2); + ppcDefineParamS32(maxCount, 3); + ppcDefineParamTypePtr(listParam, void, 4); // DownloadPostDataListParam + + maxCount = 0; // DISABLED + + // just some test + for (sint32 i = 0; i < maxCount; i++) + { + DownloadedPostData_t* postData = downloadedPostData + i; + memset(postData, 0, sizeof(DownloadedPostData_t)); + postData->userPrincipalId = 0x1000 + i; + // post id + sprintf(postData->postId, "postid-%04x", i+(GetTickCount()%10000)); + postData->bodyTextLength = 12; + postData->bodyText[0] = 'H'; + postData->bodyText[1] = 'e'; + postData->bodyText[2] = 'l'; + postData->bodyText[3] = 'l'; + postData->bodyText[4] = 'o'; + postData->bodyText[5] = ' '; + postData->bodyText[6] = 'w'; + postData->bodyText[7] = 'o'; + postData->bodyText[8] = 'r'; + postData->bodyText[9] = 'l'; + postData->bodyText[10] = 'd'; + postData->bodyText[11] = '!'; + + postData->miiNickname[0] = 'C'; + postData->miiNickname[1] = 'e'; + postData->miiNickname[2] = 'm'; + postData->miiNickname[3] = 'u'; + postData->miiNickname[4] = '-'; + postData->miiNickname[5] = 'M'; + postData->miiNickname[6] = 'i'; + postData->miiNickname[7] = 'i'; + + postData->topicTag[0] = 't'; + postData->topicTag[1] = 'o'; + postData->topicTag[2] = 'p'; + postData->topicTag[3] = 'i'; + postData->topicTag[4] = 'c'; + + postData->flags = POST_DATA_FLAG_HAS_BODY_TEXT; + } + *downloadedPostDataSize = maxCount; + + osLib_returnFromFunction(hCPU, 0); + } + + void export_DownloadCommunityDataList(PPCInterpreter_t* hCPU) + { + ppcDefineParamTypePtr(communityListSizeOut, uint32be, 1); + + *communityListSizeOut = 0; + osLib_returnFromFunction(hCPU, 0); + } + + void exportDownloadPostData_TestFlags(PPCInterpreter_t* hCPU) + { + ppcDefineParamTypePtr(downloadedPostData, DownloadedPostData_t, 0); + ppcDefineParamU32(testFlags, 1); + + if (((uint32)downloadedPostData->flags) & testFlags) + osLib_returnFromFunction(hCPU, 1); + else + osLib_returnFromFunction(hCPU, 0); + } + + void exportDownloadPostData_GetPostId(PPCInterpreter_t* hCPU) + { + ppcDefineParamTypePtr(downloadedPostData, DownloadedPostData_t, 0); + osLib_returnFromFunction(hCPU, memory_getVirtualOffsetFromPointer(downloadedPostData->postId)); + } + + void exportDownloadPostData_GetMiiNickname(PPCInterpreter_t* hCPU) + { + ppcDefineParamTypePtr(downloadedPostData, DownloadedPostData_t, 0); + if(downloadedPostData->miiNickname[0] == 0 ) + osLib_returnFromFunction(hCPU, MPTR_NULL); + else + osLib_returnFromFunction(hCPU, memory_getVirtualOffsetFromPointer(downloadedPostData->miiNickname)); + } + + void exportDownloadPostData_GetTopicTag(PPCInterpreter_t* hCPU) + { + ppcDefineParamTypePtr(downloadedPostData, DownloadedPostData_t, 0); + osLib_returnFromFunction(hCPU, memory_getVirtualOffsetFromPointer(downloadedPostData->topicTag)); + } + + void exportDownloadPostData_GetBodyText(PPCInterpreter_t* hCPU) + { + ppcDefineParamTypePtr(downloadedPostData, DownloadedPostData_t, 0); + ppcDefineParamWStrBE(strOut, 1); + ppcDefineParamS32(maxLength, 2); + + if (((uint32)downloadedPostData->flags&POST_DATA_FLAG_HAS_BODY_TEXT) == 0) + { + osLib_returnFromFunction(hCPU, 0xC1106800); + return; + } + + memset(strOut, 0, sizeof(uint16be)*maxLength); + sint32 copyLen = std::min(maxLength - 1, (sint32)downloadedPostData->bodyTextLength); + for (sint32 i = 0; i < copyLen; i++) + { + strOut[i] = downloadedPostData->bodyText[i]; + } + strOut[copyLen] = '\0'; + + osLib_returnFromFunction(hCPU, 0); + } + + struct PortalAppParam_t + { + /* +0x1A663B */ char serviceToken[32]; // size is unknown + }; + + void exportPortalAppParam_GetServiceToken(PPCInterpreter_t* hCPU) + { + // r3 = PortalAppParam + ppcDefineParamTypePtr(portalAppParam, PortalAppParam_t, 0); + + strcpy(portalAppParam->serviceToken, "servicetoken"); + // this token is probably just the act IndependentServiceToken for the Miiverse title? + + osLib_returnFromFunction(hCPU, memory_getVirtualOffsetFromPointer(&portalAppParam->serviceToken)); + } + + uint32 UploadPostDataByPostApp(void *postParam) + { + forceLog_printf("UploadPostDataByPostApp() called. Returning error"); + return BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_OLV, 0); // undefined error + } + + void load() + { + osLib_addFunction("nn_olv", "DownloadPostDataList__Q2_2nn3olvFPQ3_2nn3olv19DownloadedTopicDataPQ3_2nn3olv18DownloadedPostDataPUiUiPCQ3_2nn3olv25DownloadPostDataListParam", export_DownloadPostDataList); + osLib_addFunction("nn_olv", "TestFlags__Q3_2nn3olv18DownloadedDataBaseCFUi", exportDownloadPostData_TestFlags); + osLib_addFunction("nn_olv", "GetPostId__Q3_2nn3olv18DownloadedDataBaseCFv", exportDownloadPostData_GetPostId); + osLib_addFunction("nn_olv", "GetMiiNickname__Q3_2nn3olv18DownloadedDataBaseCFv", exportDownloadPostData_GetMiiNickname); + osLib_addFunction("nn_olv", "GetTopicTag__Q3_2nn3olv18DownloadedDataBaseCFv", exportDownloadPostData_GetTopicTag); + osLib_addFunction("nn_olv", "GetBodyText__Q3_2nn3olv18DownloadedDataBaseCFPwUi", exportDownloadPostData_GetBodyText); + + osLib_addFunction("nn_olv", "DownloadCommunityDataList__Q2_2nn3olvFPQ3_2nn3olv23DownloadedCommunityDataPUiUiPCQ3_2nn3olv30DownloadCommunityDataListParam", export_DownloadCommunityDataList); + + osLib_addFunction("nn_olv", "GetServiceToken__Q4_2nn3olv6hidden14PortalAppParamCFv", exportPortalAppParam_GetServiceToken); + + cafeExportRegisterFunc(UploadPostDataByPostApp, "nn_olv", "UploadPostDataByPostApp__Q2_2nn3olvFPCQ3_2nn3olv28UploadPostDataByPostAppParam", LogType::Placeholder); + } + + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv.h b/src/Cafe/OS/libs/nn_olv/nn_olv.h new file mode 100644 index 00000000..e8278136 --- /dev/null +++ b/src/Cafe/OS/libs/nn_olv/nn_olv.h @@ -0,0 +1,9 @@ +#pragma once + +namespace nn +{ + namespace olv + { + void load(); + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_pdm/nn_pdm.cpp b/src/Cafe/OS/libs/nn_pdm/nn_pdm.cpp new file mode 100644 index 00000000..1f0a9390 --- /dev/null +++ b/src/Cafe/OS/libs/nn_pdm/nn_pdm.cpp @@ -0,0 +1,42 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "config/ActiveSettings.h" +#include "Cafe/TitleList/TitleId.h" + +#include "Cafe/IOSU/PDM/iosu_pdm.h" + +#include "nn_pdm.h" + +namespace nn +{ + namespace pdm + { + using PlayDiary = iosu::pdm::PlayDiaryEntry; + + uint32 GetPlayDiaryMaxLength(uint32be* count) + { + *count = iosu::pdm::NUM_PLAY_DIARY_ENTRIES_MAX; + return 0; + } + + uint32 GetPlayStatsMaxLength(uint32be* count) + { + *count = iosu::pdm::NUM_PLAY_STATS_ENTRIES; + return 0; + } + + uint32 GetPlayDiary(uint32be* ukn1, PlayDiary* playDiary, uint32 accountSlot, uint32 maxNumEntries) + { + uint32 numReadEntries = iosu::pdm::GetDiaryEntries(accountSlot, playDiary, maxNumEntries); + *ukn1 = numReadEntries; + return 0; + } + + void Initialize() + { + cafeExportRegisterFunc(GetPlayDiaryMaxLength, "nn_pdm", "GetPlayDiaryMaxLength__Q2_2nn3pdmFPi", LogType::NN_PDM); + cafeExportRegisterFunc(GetPlayStatsMaxLength, "nn_pdm", "GetPlayStatsMaxLength__Q2_2nn3pdmFPi", LogType::NN_PDM); + + cafeExportRegisterFunc(GetPlayDiary, "nn_pdm", "GetPlayDiary__Q2_2nn3pdmFPiPQ3_2nn3pdm9PlayDiaryiT3", LogType::NN_PDM); + } + } +} diff --git a/src/Cafe/OS/libs/nn_pdm/nn_pdm.h b/src/Cafe/OS/libs/nn_pdm/nn_pdm.h new file mode 100644 index 00000000..9034b6c0 --- /dev/null +++ b/src/Cafe/OS/libs/nn_pdm/nn_pdm.h @@ -0,0 +1,7 @@ +namespace nn +{ + namespace pdm + { + void Initialize(); + }; +}; \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_save/nn_save.cpp b/src/Cafe/OS/libs/nn_save/nn_save.cpp new file mode 100644 index 00000000..64bbdc2f --- /dev/null +++ b/src/Cafe/OS/libs/nn_save/nn_save.cpp @@ -0,0 +1,1574 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "gui/wxgui.h" +#include "nn_save.h" + +#include "Cafe/OS/libs/nn_acp/nn_acp.h" +#include "Cafe/OS/libs/nn_act/nn_act.h" + +#include <filesystem> +#include <sstream> +#include "config/ActiveSettings.h" +#include "Cafe/OS/libs/coreinit/coreinit_Thread.h" +#include "Cafe/OS/libs/coreinit/coreinit_FS.h" +#include "Cafe/CafeSystem.h" +#include "Cafe/Filesystem/fsc.h" + +#define SAVE_STATUS_OK ((FSStatus)FS_RESULT::SUCCESS) +#define SAVE_MAX_PATH_SIZE (FSA_CMD_PATH_MAX_LENGTH) + +#define SAVE_ACCOUNT_ID_MIN (1) +#define SAVE_ACCOUNT_ID_MAX (0xC) + +#define SAVE_UNIQUE_TO_TITLE_ID(_unique_) (((((uint64)_unique_ >> 24ULL) | 0x50000) << 32ULL) | ((_unique_ << 8) | 0x10000000)) +#define SAVE_UNIQUE_TO_TITLE_ID_VARIATION(_unique_,_variation_) (((((uint64)_unique_ >> 24ULL) | 0x50000 ) << 32) | ((_unique_ << 8) | 0x10000000 | _variation_)) +#define SAVE_UNIQUE_DEMO_TO_TITLE_ID(_unique_) (((((uint64)_unique_ >> 24ULL) | 0x50002) << 32ULL) | ((_unique_ << 8) | 0x10000000)) +#define SAVE_UNIQUE_DEMO_TO_TITLE_ID_VARIATION(_unique_,_variation_) (((((uint64)_unique_ >> 24ULL) | 0x50002 ) << 32ULL) | ((_unique_ << 8) | 0x10000000 | _variation_)) + +namespace nn +{ +namespace save +{ + typedef FSStatus SAVEStatus; + + typedef struct + { + bool initialized; + coreinit::OSMutex mutex; + coreinit::FSClient_t fsClient; + coreinit::FSCmdBlock_t fsCmdBlock; + uint32 persistentIdCache[0xC]; + }nn_save_t; + + SysAllocator<nn_save_t> g_nn_save; + + uint32 GetPersistentIdFromLocalCache(uint8 accountSlot) + { + accountSlot--; + if (accountSlot >= 0xC) + return 0; + + return g_nn_save->persistentIdCache[accountSlot]; + } + + void SetPersistentIdToLocalCache(uint8 accountSlot, uint32 persistentId) + { + accountSlot--; + if (accountSlot >= 0xC) + return; + + g_nn_save->persistentIdCache[accountSlot] = persistentId; + } + + bool GetPersistentIdEx(uint8 accountSlot, uint32* persistentId) + { + if (accountSlot == 0xFF) + { + *persistentId = 0; + return true; + } + + const uint32 result = GetPersistentIdFromLocalCache(accountSlot); + *persistentId = result; + return result != 0; + } + + bool GetCurrentTitleApplicationBox(acp::ACPDeviceType* deviceType) + { + if (deviceType) + { + *deviceType = acp::InternalDeviceType; + return true; + } + return false; + } + + void UpdateSaveTimeStamp(uint32 persistentId) + { + acp::ACPDeviceType deviceType; + if (GetCurrentTitleApplicationBox(&deviceType)) + ACPUpdateSaveTimeStamp(persistentId, CafeSystem::GetForegroundTitleId(), deviceType); + } + + SAVEStatus ConvertACPToSaveStatus(acp::ACPStatus status) + { + cemu_assert_debug(status == 0); // todo + return 0; + } + + bool GetAbsoluteFullPath(uint32 persistentId, const char* subDir, char* outPath) + { + int size; + if (persistentId != 0) + { + if (subDir) + size = snprintf(outPath, SAVE_MAX_PATH_SIZE - 1, "/vol/save/%08x/%s", persistentId, subDir); + else + size = snprintf(outPath, SAVE_MAX_PATH_SIZE - 1, "/vol/save/%08x/", persistentId); + } + else + { + if (subDir) + size = snprintf(outPath, SAVE_MAX_PATH_SIZE - 1, "/vol/save/common/%s", subDir); + else + size = snprintf(outPath, SAVE_MAX_PATH_SIZE - 1, "/vol/save/common/"); + } + + if (size < SAVE_MAX_PATH_SIZE - 1) + return true; + return false; + } + + SAVEStatus GetAbsoluteFullPathOtherApplication(uint32 persistentId, uint64 titleId, const char* subDir, char* outPath) + { + uint32be applicationBox; + if(acp::ACPGetApplicationBox(&applicationBox, titleId) != acp::ACPStatus::SUCCESS) + return (FSStatus)FS_RESULT::NOT_FOUND; + + sint32 written = 0; + if(applicationBox == 3) + { + if(persistentId != 0) + { + if (subDir) + written = snprintf(outPath, SAVE_MAX_PATH_SIZE - 1, "/vol/storage_mlc01/usr/save/%08x/%08x/user/%08x/%s", + GetTitleIdHigh(titleId), GetTitleIdLow(titleId), persistentId, subDir); + else + written = snprintf(outPath, SAVE_MAX_PATH_SIZE - 1, "/vol/storage_mlc01/usr/save/%08x/%08x/user/%08x/", + GetTitleIdHigh(titleId), GetTitleIdLow(titleId), persistentId); + } + else + { + if (subDir) + written = snprintf(outPath, SAVE_MAX_PATH_SIZE - 1, "/vol/storage_mlc01/usr/save/%08x/%08x/user/common/%s", + GetTitleIdHigh(titleId), GetTitleIdLow(titleId), subDir); + else + written = snprintf(outPath, SAVE_MAX_PATH_SIZE - 1, "/vol/storage_mlc01/usr/save/%08x/%08x/user/common/", + GetTitleIdHigh(titleId), GetTitleIdLow(titleId)); + } + } + else if(applicationBox == 4) + { + cemu_assert_unimplemented(); + } + else + return (FSStatus)FS_RESULT::NOT_FOUND; + + if (written < SAVE_MAX_PATH_SIZE - 1) + return (FSStatus)FS_RESULT::SUCCESS; + + cemu_assert_suspicious(); + return (FSStatus)(FS_RESULT::FATAL_ERROR); + } + + typedef struct + { + coreinit::OSEvent* event; + SAVEStatus returnStatus; + + MEMPTR<OSThread_t> thread; // own stuff until cond + event rewritten + } AsyncCallbackParam_t; + + void AsyncCallback(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 returnStatus, void* p) + { + cemu_assert_debug(p && ((AsyncCallbackParam_t*)p)->event); + + AsyncCallbackParam_t* param = (AsyncCallbackParam_t*)p; + param->returnStatus = returnStatus; + coreinit::OSSignalEvent(param->event); + } + + void AsyncCallback(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(client, coreinit::FSClient_t, 0); + ppcDefineParamMEMPTR(block, coreinit::FSCmdBlock_t, 1); + ppcDefineParamU32(returnStatus, 2); + ppcDefineParamMEMPTR(userContext, void, 3); + + MEMPTR<AsyncCallbackParam_t> param{ userContext }; + + // wait till thread is actually suspended + OSThread_t* thread = param->thread.GetPtr(); + while (thread->suspendCounter == 0 || thread->state == OSThread_t::THREAD_STATE::STATE_RUNNING) + coreinit::OSYieldThread(); + + param->returnStatus = returnStatus; + coreinit_resumeThread(param->thread.GetPtr(), 1000); + + osLib_returnFromFunction(hCPU, 0); + } + + SAVEStatus SAVEMountSaveDir() + { + acp::ACPStatus status = acp::ACPMountSaveDir(); + return ConvertACPToSaveStatus(status); + } + + void _CheckAndMoveLegacySaves() + { + const uint64 titleId = CafeSystem::GetForegroundTitleId(); + + fs::path targetPath, sourcePath; + try + { + bool copiedUser = false, copiedCommon = false; + + const auto sourceSavePath = ActiveSettings::GetMlcPath("emulatorSave/{:08x}", CafeSystem::GetRPXHashBase()); + sourcePath = sourceSavePath; + + if (fs::exists(sourceSavePath) && is_directory(sourceSavePath)) + { + targetPath = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/user/{:08x}", GetTitleIdHigh(titleId), GetTitleIdLow(titleId), 0x80000001); + fs::create_directories(targetPath); + copy(sourceSavePath, targetPath, fs::copy_options::overwrite_existing | fs::copy_options::recursive); + copiedUser = true; + } + + const auto sourceCommonPath = ActiveSettings::GetMlcPath("emulatorSave/{:08x}_255", CafeSystem::GetRPXHashBase()); + sourcePath = sourceCommonPath; + + if (fs::exists(sourceCommonPath) && is_directory(sourceCommonPath)) + { + targetPath = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/user/common", GetTitleIdHigh(titleId), GetTitleIdLow(titleId)); + fs::create_directories(targetPath); + copy(sourceCommonPath, targetPath, fs::copy_options::overwrite_existing | fs::copy_options::recursive); + copiedCommon = true; + } + + if (copiedUser) + fs::remove_all(sourceSavePath); + + if (copiedCommon) + fs::remove_all(sourceCommonPath); + } + catch (const std::exception& ex) + { +#if BOOST_OS_WINDOWS > 0 + std::wstringstream errorMsg; + errorMsg << L"Couldn't move your save files!" << std::endl << std::endl; + errorMsg << L"Error: " << ex.what() << std::endl << std::endl; + errorMsg << L"From:" << std::endl << sourcePath << std::endl << std::endl << "To:" << std::endl << targetPath; + + const DWORD lastError = GetLastError(); + if (lastError != ERROR_SUCCESS) + { + LPTSTR lpMsgBuf = nullptr; + FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, lastError, 0, (LPTSTR)&lpMsgBuf, 0, nullptr); + if (lpMsgBuf) + { + errorMsg << std::endl << std::endl << L"Details: " << lpMsgBuf; + LocalFree(lpMsgBuf); + } + else + { + errorMsg << std::endl << std::endl << L"Error Code: 0x" << std::hex << lastError; + } + } + + errorMsg << std::endl << std::endl << "Continuing will create a new save at the target location." << std::endl << "Do you want to continue?"; + + int result = wxMessageBox(errorMsg.str(), "Save Migration - Error", wxCENTRE | wxYES_NO | wxICON_ERROR); + if (result != wxYES) + { + exit(0); + return; + } +#endif + } + } + + SAVEStatus SAVEInit() + { + const uint64 titleId = CafeSystem::GetForegroundTitleId(); + + if (!g_nn_save->initialized) + { + OSInitMutexEx(&g_nn_save->mutex, nullptr); + act::Initialize(); + coreinit::FSAddClientEx(&g_nn_save->fsClient, 0, 0); + coreinit::FSInitCmdBlock(&g_nn_save->fsCmdBlock); + for(uint8 accountId = SAVE_ACCOUNT_ID_MIN; accountId <= SAVE_ACCOUNT_ID_MAX; ++accountId) + { + uint32 persistentId = act::GetPersistentIdEx(accountId); + SetPersistentIdToLocalCache(accountId, persistentId); + } + + SAVEMountSaveDir(); + g_nn_save->initialized = true; + + _CheckAndMoveLegacySaves(); + + uint32 high = GetTitleIdHigh(titleId) & (~0xC); + uint32 low = GetTitleIdLow(titleId); + + sint32 fscStatus = FSC_STATUS_FILE_NOT_FOUND; + char path[256]; + sprintf(path, "%susr/save/%08x/", "/vol/storage_mlc01/", high); + fsc_createDir(path, &fscStatus); + sprintf(path, "%susr/save/%08x/%08x/", "/vol/storage_mlc01/", high, low); + fsc_createDir(path, &fscStatus); + sprintf(path, "%susr/save/%08x/%08x/meta/", "/vol/storage_mlc01/", high, low); + fsc_createDir(path, &fscStatus); + + acp::CreateSaveMetaFiles(ActiveSettings::GetPersistentId(), titleId); + } + + return SAVE_STATUS_OK; + } + + SAVEStatus SAVERemoveAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + { + SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); + + OSLockMutex(&g_nn_save->mutex); + + uint32 persistentId; + if (GetPersistentIdEx(accountSlot, &persistentId)) + { + char fullPath[SAVE_MAX_PATH_SIZE]; + if (GetAbsoluteFullPath(persistentId, path, fullPath)) + result = coreinit::FSRemoveAsync(client, block, (uint8*)fullPath, errHandling, (FSAsyncParamsNew_t*)asyncParams); + } + else + result = (FSStatus)FS_RESULT::NOT_FOUND; + + OSUnlockMutex(&g_nn_save->mutex); + return result; + } + + SAVEStatus SAVEMakeDirAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + { + SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); + + OSLockMutex(&g_nn_save->mutex); + + uint32 persistentId; + if (GetPersistentIdEx(accountSlot, &persistentId)) + { + char fullPath[SAVE_MAX_PATH_SIZE]; + if (GetAbsoluteFullPath(persistentId, path, fullPath)) + result = coreinit::FSMakeDirAsync(client, block, (uint8*)fullPath, errHandling, (FSAsyncParamsNew_t*)asyncParams); + } + else + result = (FSStatus)FS_RESULT::NOT_FOUND; + + OSUnlockMutex(&g_nn_save->mutex); + + return result; + } + + SAVEStatus SAVEOpenDirAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + { + SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); + + OSLockMutex(&g_nn_save->mutex); + + uint32 persistentId; + if (GetPersistentIdEx(accountSlot, &persistentId)) + { + char fullPath[SAVE_MAX_PATH_SIZE]; + if (GetAbsoluteFullPath(persistentId, path, fullPath)) + result = coreinit::FSOpenDirAsync(client, block, fullPath, hDir, errHandling, (FSAsyncParamsNew_t*)asyncParams); + + } + else + result = (FSStatus)FS_RESULT::NOT_FOUND; + + OSUnlockMutex(&g_nn_save->mutex); + + return result; + } + + SAVEStatus SAVEOpenFileAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, const char* mode, FSFileHandleDepr_t* hFile, FS_ERROR_MASK errHandling, const FSAsyncParamsNew_t* asyncParams) + { + SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); + + OSLockMutex(&g_nn_save->mutex); + + uint32 persistentId; + if (GetPersistentIdEx(accountSlot, &persistentId)) + { + char fullPath[SAVE_MAX_PATH_SIZE]; + if (GetAbsoluteFullPath(persistentId, path, fullPath)) + result = coreinit::FSOpenFileAsync(client, block, fullPath, (char*)mode, hFile, errHandling, (FSAsyncParamsNew_t*)asyncParams); + } + else + result = (FSStatus)FS_RESULT::NOT_FOUND; + + OSUnlockMutex(&g_nn_save->mutex); + + return result; + } + + SAVEStatus SAVEOpenFileOtherApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, const char* mode, FSFileHandleDepr_t* hFile, FS_ERROR_MASK errHandling, const FSAsyncParamsNew_t* asyncParams) + { + if (strcmp(mode, "r") != 0) + return (SAVEStatus)(FS_RESULT::PERMISSION_ERROR); + + SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); + + OSLockMutex(&g_nn_save->mutex); + + uint32 persistentId; + if (GetPersistentIdEx(accountSlot, &persistentId)) + { + char fullPath[SAVE_MAX_PATH_SIZE]; + if (GetAbsoluteFullPathOtherApplication(persistentId, titleId, path, fullPath)) + result = coreinit::FSOpenFileAsync(client, block, fullPath, (char*)mode, hFile, errHandling, (FSAsyncParamsNew_t*)asyncParams); + } + else + result = (FSStatus)FS_RESULT::NOT_FOUND; + + OSUnlockMutex(&g_nn_save->mutex); + + return result; + } + + void export_SAVEOpenFileOtherApplicationAsync(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); + ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); + ppcDefineParamU64(titleId, 2); + ppcDefineParamU8(accountSlot, 4); + ppcDefineParamMEMPTR(path, const char, 5); + ppcDefineParamMEMPTR(mode, const char, 6); + ppcDefineParamMEMPTR(hFile, FSFileHandleDepr_t, 7); + ppcDefineParamU32(errHandling, 8); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParamsNew_t, 9); + + const SAVEStatus result = SAVEOpenFileOtherApplicationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), titleId, accountSlot, path.GetPtr(), mode.GetPtr(), hFile.GetPtr(), errHandling, asyncParams.GetPtr()); + osLib_returnFromFunction(hCPU, result); + } + + SAVEStatus SAVEOpenFileOtherApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, const char* mode, FSFileHandleDepr_t* hFile, FS_ERROR_MASK errHandling) + { + FSAsyncParamsNew_t asyncParams; + asyncParams.ioMsgQueue = nullptr; + asyncParams.userCallback = PPCInterpreter_makeCallableExportDepr(AsyncCallback); + + StackAllocator<AsyncCallbackParam_t> param; + param->thread = coreinitThread_getCurrentThreadMPTRDepr(ppcInterpreterCurrentInstance); + param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; + asyncParams.userContext = param.GetPointer(); + + SAVEStatus status = SAVEOpenFileOtherApplicationAsync(client, block, titleId, accountSlot, path, mode, hFile, errHandling, &asyncParams); + if (status == (FSStatus)FS_RESULT::SUCCESS) + { + coreinit_suspendThread(coreinitThread_getCurrentThreadDepr(ppcInterpreterCurrentInstance), 1000); + PPCCore_switchToScheduler(); + return param->returnStatus; + } + + return status; + } + + void export_SAVEOpenFileOtherApplication(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); + ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); + ppcDefineParamU64(titleId, 2); + ppcDefineParamU8(accountSlot, 4); + ppcDefineParamMEMPTR(path, const char, 5); + ppcDefineParamMEMPTR(mode, const char, 6); + ppcDefineParamMEMPTR(hFile, FSFileHandleDepr_t, 7); + ppcDefineParamU32(errHandling, 8); + + const SAVEStatus result = SAVEOpenFileOtherApplication(fsClient.GetPtr(), fsCmdBlock.GetPtr(), titleId, accountSlot, path.GetPtr(), mode.GetPtr(), hFile.GetPtr(), errHandling); + osLib_returnFromFunction(hCPU, result); + } + + SAVEStatus SAVEOpenFileOtherNormalApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, const char* mode, FSFileHandleDepr_t* hFile, FS_ERROR_MASK errHandling, const FSAsyncParamsNew_t* asyncParams) + { + uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID(uniqueId); + return SAVEOpenFileOtherApplicationAsync(client, block, titleId, accountSlot, path, mode, hFile, errHandling, asyncParams); + } + + void export_SAVEOpenFileOtherNormalApplicationAsync(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); + ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); + ppcDefineParamU32(uniqueId, 2); + ppcDefineParamU8(accountSlot, 3); + ppcDefineParamMEMPTR(path, const char, 4); + ppcDefineParamMEMPTR(mode, const char, 5); + ppcDefineParamMEMPTR(hFile, FSFileHandleDepr_t, 6); + ppcDefineParamU32(errHandling, 7); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParamsNew_t, 8); + + const SAVEStatus result = SAVEOpenFileOtherNormalApplicationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, accountSlot, path.GetPtr(), mode.GetPtr(), hFile.GetPtr(), errHandling, asyncParams.GetPtr()); + osLib_returnFromFunction(hCPU, result); + } + SAVEStatus SAVEOpenFileOtherNormalApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, const char* mode, FSFileHandleDepr_t* hFile, FS_ERROR_MASK errHandling) + { + //peterBreak(); + + uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID(uniqueId); + return SAVEOpenFileOtherApplication(client, block, titleId, accountSlot, path, mode, hFile, errHandling); + } + + void export_SAVEOpenFileOtherNormalApplication(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); + ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); + ppcDefineParamU32(uniqueId, 2); + ppcDefineParamU8(accountSlot, 3); + ppcDefineParamMEMPTR(path, const char, 4); + ppcDefineParamMEMPTR(mode, const char, 5); + ppcDefineParamMEMPTR(hFile, FSFileHandleDepr_t, 6); + ppcDefineParamU32(errHandling, 7); + + const SAVEStatus result = SAVEOpenFileOtherNormalApplication(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, accountSlot, path.GetPtr(), mode.GetPtr(), hFile.GetPtr(), errHandling); + osLib_returnFromFunction(hCPU, result); + } + + SAVEStatus SAVEOpenFileOtherNormalApplicationVariationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, const char* mode, FSFileHandleDepr_t* hFile, FS_ERROR_MASK errHandling, const FSAsyncParamsNew_t* asyncParams) + { + //peterBreak(); + + uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID_VARIATION(uniqueId, variation); + return SAVEOpenFileOtherApplicationAsync(client, block, titleId, accountSlot, path, mode, hFile, errHandling, asyncParams); + } + + void export_SAVEOpenFileOtherNormalApplicationVariationAsync(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); + ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); + ppcDefineParamU32(uniqueId, 2); + ppcDefineParamU8(variation, 3); + ppcDefineParamU8(accountSlot, 4); + ppcDefineParamMEMPTR(path, const char, 5); + ppcDefineParamMEMPTR(mode, const char, 6); + ppcDefineParamMEMPTR(hFile, FSFileHandleDepr_t, 7); + ppcDefineParamU32(errHandling, 8); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParamsNew_t, 9); + + const SAVEStatus result = SAVEOpenFileOtherNormalApplicationVariationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, variation, accountSlot, path.GetPtr(), mode.GetPtr(), hFile.GetPtr(), errHandling, asyncParams.GetPtr()); + osLib_returnFromFunction(hCPU, result); + } + + SAVEStatus SAVEOpenFileOtherNormalApplicationVariation(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, const char* mode, FSFileHandleDepr_t* hFile, FS_ERROR_MASK errHandling) + { + uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID_VARIATION(uniqueId, variation); + return SAVEOpenFileOtherApplication(client, block, titleId, accountSlot, path, mode, hFile, errHandling); + } + + void export_SAVEOpenFileOtherNormalApplicationVariation(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); + ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); + ppcDefineParamU32(uniqueId, 2); + ppcDefineParamU8(variation, 3); + ppcDefineParamU8(accountSlot, 4); + ppcDefineParamMEMPTR(path, const char, 5); + ppcDefineParamMEMPTR(mode, const char, 6); + ppcDefineParamMEMPTR(hFile, FSFileHandleDepr_t, 7); + ppcDefineParamU32(errHandling, 8); + + const SAVEStatus result = SAVEOpenFileOtherNormalApplicationVariation(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, variation, accountSlot, path.GetPtr(), mode.GetPtr(), hFile.GetPtr(), errHandling); + osLib_returnFromFunction(hCPU, result); + } + + SAVEStatus SAVEGetFreeSpaceSizeAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, FSLargeSize* freeSize, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + { + SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); + + OSLockMutex(&g_nn_save->mutex); + + uint32 persistentId; + if (GetPersistentIdEx(accountSlot, &persistentId)) + { + char fullPath[SAVE_MAX_PATH_SIZE]; + // usually a pointer with '\0' instead of nullptr, but it's basically the same + if (GetAbsoluteFullPath(persistentId, nullptr, fullPath)) + result = coreinit::FSGetFreeSpaceSizeAsync(client, block, fullPath, freeSize, errHandling, (FSAsyncParamsNew_t*)asyncParams); + } + else + result = (FSStatus)FS_RESULT::NOT_FOUND; + + OSUnlockMutex(&g_nn_save->mutex); + + return result; + } + + SAVEStatus SAVEGetStatAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + { + SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); + + OSLockMutex(&g_nn_save->mutex); + + uint32 persistentId; + if (GetPersistentIdEx(accountSlot, &persistentId)) + { + char fullPath[SAVE_MAX_PATH_SIZE]; + if (GetAbsoluteFullPath(persistentId, path, fullPath)) + result = coreinit::__FSQueryInfoAsync(client, block, (uint8*)fullPath, FSA_QUERY_TYPE_STAT, stat, errHandling, (FSAsyncParamsNew_t*)asyncParams); // FSGetStatAsync(...) + } + else + result = (FSStatus)FS_RESULT::NOT_FOUND; + + OSUnlockMutex(&g_nn_save->mutex); + + return result; + } + + SAVEStatus SAVEGetStatOtherApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + { + SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); + + OSLockMutex(&g_nn_save->mutex); + + uint32 persistentId; + if (GetPersistentIdEx(accountSlot, &persistentId)) + { + char fullPath[SAVE_MAX_PATH_SIZE]; + if (GetAbsoluteFullPathOtherApplication(persistentId, titleId, path, fullPath) == (FSStatus)FS_RESULT::SUCCESS) + result = coreinit::__FSQueryInfoAsync(client, block, (uint8*)fullPath, FSA_QUERY_TYPE_STAT, stat, errHandling, (FSAsyncParamsNew_t*)asyncParams); // FSGetStatAsync(...) + } + else + result = (FSStatus)FS_RESULT::NOT_FOUND; + + OSUnlockMutex(&g_nn_save->mutex); + + return result; + } + + SAVEStatus SAVEGetStatOtherNormalApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + { + uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID(uniqueId); + return SAVEGetStatOtherApplicationAsync(client, block, titleId, accountSlot, path, stat, errHandling, asyncParams); + } + + SAVEStatus SAVEGetStatOtherDemoApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + { + uint64 titleId = SAVE_UNIQUE_DEMO_TO_TITLE_ID(uniqueId); + return SAVEGetStatOtherApplicationAsync(client, block, titleId, accountSlot, path, stat, errHandling, asyncParams); + } + + SAVEStatus SAVEGetStatOtherNormalApplicationVariationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + { + uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID_VARIATION(uniqueId, variation); + return SAVEGetStatOtherApplicationAsync(client, block, titleId, accountSlot, path, stat, errHandling, asyncParams); + } + + SAVEStatus SAVEGetStatOtherDemoApplicationVariationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + { + uint64 titleId = SAVE_UNIQUE_DEMO_TO_TITLE_ID_VARIATION(uniqueId, variation); + return SAVEGetStatOtherApplicationAsync(client, block, titleId, accountSlot, path, stat, errHandling, asyncParams); + } + + SAVEStatus SAVEInitSaveDir(uint8 accountSlot) + { + SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); + OSLockMutex(&g_nn_save->mutex); + + uint32 persistentId; + if (GetPersistentIdEx(accountSlot, &persistentId)) + { + acp::ACPStatus status = ACPCreateSaveDir(persistentId, acp::InternalDeviceType); + result = ConvertACPToSaveStatus(status); + } + else + result = (FSStatus)FS_RESULT::NOT_FOUND; + + OSUnlockMutex(&g_nn_save->mutex); + return result; + } + + SAVEStatus SAVEGetFreeSpaceSize(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, FSLargeSize* freeSize, FS_ERROR_MASK errHandling) + { + FSAsyncParams_t asyncParams; + asyncParams.ioMsgQueue = MPTR_NULL; + asyncParams.userCallback = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(AsyncCallback)); + + StackAllocator<AsyncCallbackParam_t> param; + param->thread = coreinitThread_getCurrentThreadMPTRDepr(ppcInterpreterCurrentInstance); + param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; + asyncParams.userContext = param.GetMPTRBE(); + + SAVEStatus status = SAVEGetFreeSpaceSizeAsync(client, block, accountSlot, freeSize, errHandling, &asyncParams); + if (status == (FSStatus)FS_RESULT::SUCCESS) + { + coreinit_suspendThread(coreinitThread_getCurrentThreadDepr(ppcInterpreterCurrentInstance), 1000); + PPCCore_switchToScheduler(); + return param->returnStatus; + } + + return status; + } + + void export_SAVEGetFreeSpaceSize(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); + ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); + ppcDefineParamU8(accountSlot, 2); + ppcDefineParamMEMPTR(returnedFreeSize, FSLargeSize, 3); + ppcDefineParamU32(errHandling, 4); + + const SAVEStatus result = SAVEGetFreeSpaceSize(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, returnedFreeSize.GetPtr(), errHandling); + saveLog_printf("SAVEGetFreeSpaceSize(0x%08x, 0x%08x, %x, %x) -> %x", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, errHandling, result); + osLib_returnFromFunction(hCPU, result); + } + + void export_SAVEGetFreeSpaceSizeAsync(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); + ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); + ppcDefineParamU8(accountSlot, 2); + ppcDefineParamMEMPTR(returnedFreeSize, FSLargeSize, 3); + ppcDefineParamU32(errHandling, 4); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParams_t, 5); + + const SAVEStatus result = SAVEGetFreeSpaceSizeAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, returnedFreeSize.GetPtr(), errHandling, asyncParams.GetPtr()); + saveLog_printf("SAVEGetFreeSpaceSizeAsync(0x%08x, 0x%08x, %x, %x) -> %x", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, errHandling, result); + osLib_returnFromFunction(hCPU, result); + } + + void export_SAVEInit(PPCInterpreter_t* hCPU) + { + const SAVEStatus result = SAVEInit(); + saveLog_printf("SAVEInit() -> %x", result); + osLib_returnFromFunction(hCPU, result); + } + + void export_SAVERemoveAsync(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); + ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); + ppcDefineParamU8(accountSlot, 2); + ppcDefineParamMEMPTR(path, const char, 3); + ppcDefineParamU32(errHandling, 4); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParams_t, 5); + + const SAVEStatus result = SAVERemoveAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), errHandling, asyncParams.GetPtr()); + osLib_returnFromFunction(hCPU, result); + } + + SAVEStatus SAVERemove(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling) + { + FSAsyncParams_t asyncParams; + asyncParams.ioMsgQueue = MPTR_NULL; + asyncParams.userCallback = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(AsyncCallback)); + + StackAllocator<AsyncCallbackParam_t> param; + param->thread = coreinitThread_getCurrentThreadMPTRDepr(ppcInterpreterCurrentInstance); + param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; + asyncParams.userContext = param.GetMPTRBE(); + + SAVEStatus status = SAVERemoveAsync(client, block, accountSlot, path, errHandling, &asyncParams); + if (status == (FSStatus)FS_RESULT::SUCCESS) + { + coreinit_suspendThread(coreinitThread_getCurrentThreadDepr(ppcInterpreterCurrentInstance), 1000); + PPCCore_switchToScheduler(); + return param->returnStatus; + } + + return status; + } + + void export_SAVERemove(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); + ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); + ppcDefineParamU8(accountSlot, 2); + ppcDefineParamMEMPTR(path, const char, 3); + ppcDefineParamU32(errHandling, 4); + + const SAVEStatus result = SAVERemove(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), errHandling); + osLib_returnFromFunction(hCPU, result); + } + + SAVEStatus SAVERenameAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* oldPath, const char* newPath, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + { + SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); + + OSLockMutex(&g_nn_save->mutex); + + uint32 persistentId; + if (GetPersistentIdEx(accountSlot, &persistentId)) + { + char fullOldPath[SAVE_MAX_PATH_SIZE]; + if (GetAbsoluteFullPath(persistentId, oldPath, fullOldPath)) + { + char fullNewPath[SAVE_MAX_PATH_SIZE]; + if (GetAbsoluteFullPath(persistentId, newPath, fullNewPath)) + result = coreinit::FSRenameAsync(client, block, fullOldPath, fullNewPath, errHandling, (FSAsyncParamsNew_t*)asyncParams); + } + } + else + result = (FSStatus)FS_RESULT::NOT_FOUND; + + OSUnlockMutex(&g_nn_save->mutex); + + return result; + } + + void export_SAVERenameAsync(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); + ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); + ppcDefineParamU8(accountSlot, 2); + ppcDefineParamMEMPTR(oldPath, const char, 3); + ppcDefineParamMEMPTR(newPath, const char, 4); + ppcDefineParamU32(errHandling, 5); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParams_t, 6); + + const SAVEStatus result = SAVERenameAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, oldPath.GetPtr(), newPath.GetPtr(), errHandling, asyncParams.GetPtr()); + saveLog_printf("SAVERenameAsync(0x%08x, 0x%08x, %x, %s, %s, %x) -> %x", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, oldPath.GetPtr(), newPath.GetPtr(), errHandling, result); + osLib_returnFromFunction(hCPU, result); + } + + SAVEStatus SAVERename(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* oldPath, const char* newPath, FS_ERROR_MASK errHandling) + { + SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); + + OSLockMutex(&g_nn_save->mutex); + uint32 persistentId; + if (GetPersistentIdEx(accountSlot, &persistentId)) + { + char fullOldPath[SAVE_MAX_PATH_SIZE]; + if (GetAbsoluteFullPath(persistentId, oldPath, fullOldPath)) + { + char fullNewPath[SAVE_MAX_PATH_SIZE]; + if (GetAbsoluteFullPath(persistentId, newPath, fullNewPath)) + result = coreinit::FSRename(client, block, fullOldPath, fullNewPath, errHandling); + } + } + else + result = (FSStatus)FS_RESULT::NOT_FOUND; + OSUnlockMutex(&g_nn_save->mutex); + + return result; + } + + void export_SAVEOpenDirAsync(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); + ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); + ppcDefineParamU8(accountSlot, 2); + ppcDefineParamMEMPTR(path, const char, 3); + ppcDefineParamMEMPTR(hDir, betype<FSDirHandle2>, 4); + ppcDefineParamU32(errHandling, 5); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParams_t, 6); + + const SAVEStatus result = SAVEOpenDirAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), hDir, errHandling, asyncParams.GetPtr()); + saveLog_printf("SAVEOpenDirAsync(0x%08x, 0x%08x, %x, %s, 0x%08x (%x), %x) -> %x", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), hDir.GetMPTR(), + (hDir.GetPtr() == nullptr ? 0 : (uint32)*hDir.GetPtr()), errHandling, result); + osLib_returnFromFunction(hCPU, result); + } + + SAVEStatus SAVEOpenDir(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling) + { + FSAsyncParams_t asyncParams; + asyncParams.ioMsgQueue = MPTR_NULL; + asyncParams.userCallback = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(AsyncCallback)); + + StackAllocator<AsyncCallbackParam_t> param; + param->thread = coreinitThread_getCurrentThreadMPTRDepr(ppcInterpreterCurrentInstance); + param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; + asyncParams.userContext = param.GetMPTRBE(); + + SAVEStatus status = SAVEOpenDirAsync(client, block, accountSlot, path, hDir, errHandling, &asyncParams); + if (status == (FSStatus)FS_RESULT::SUCCESS) + { + coreinit_suspendThread(coreinitThread_getCurrentThreadDepr(ppcInterpreterCurrentInstance), 1000); + PPCCore_switchToScheduler(); + return param->returnStatus; + } + + return status; + } + + void export_SAVEOpenDir(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); + ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); + ppcDefineParamU8(accountSlot, 2); + ppcDefineParamMEMPTR(path, const char, 3); + ppcDefineParamMEMPTR(hDir, betype<FSDirHandle2>, 4); + ppcDefineParamU32(errHandling, 5); + + const SAVEStatus result = SAVEOpenDir(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), hDir, errHandling); + saveLog_printf("SAVEOpenDir(0x%08x, 0x%08x, %x, %s, 0x%08x (%x), %x) -> %x", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), hDir.GetMPTR(), + (hDir.GetPtr() == nullptr ? 0 : (uint32)*hDir.GetPtr()), errHandling, result); + osLib_returnFromFunction(hCPU, result); + } + + SAVEStatus SAVEOpenDirOtherApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + { + SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); + + OSLockMutex(&g_nn_save->mutex); + uint32 persistentId; + if (GetPersistentIdEx(accountSlot, &persistentId)) + { + char fullPath[SAVE_MAX_PATH_SIZE]; + if (GetAbsoluteFullPathOtherApplication(persistentId, titleId, path, fullPath)) + result = coreinit::FSOpenDirAsync(client, block, fullPath, hDir, errHandling, (FSAsyncParamsNew_t*)asyncParams); + } + else + result = (FSStatus)FS_RESULT::NOT_FOUND; + OSUnlockMutex(&g_nn_save->mutex); + + return result; + } + + void export_SAVEOpenDirOtherApplicationAsync(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); + ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); + ppcDefineParamU64(titleId, 2); + ppcDefineParamU8(accountSlot, 3); + ppcDefineParamMEMPTR(path, const char, 4); + ppcDefineParamMEMPTR(hDir, betype<FSDirHandle2>, 5); + ppcDefineParamU32(errHandling, 6); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParams_t, 7); + + const SAVEStatus result = SAVEOpenDirOtherApplicationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), titleId, accountSlot, path.GetPtr(), hDir, errHandling, asyncParams.GetPtr()); + saveLog_printf("SAVEOpenDirOtherApplicationAsync(0x%08x, 0x%08x, %llx, %x, %s, 0x%08x (%x), %x) -> %x", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), titleId, accountSlot, path.GetPtr(), hDir.GetMPTR(), + (hDir.GetPtr() == nullptr ? 0 : (uint32)*hDir.GetPtr()), errHandling, result); + osLib_returnFromFunction(hCPU, result); + } + + SAVEStatus SAVEOpenDirOtherApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling) + { + FSAsyncParams_t asyncParams; + asyncParams.ioMsgQueue = MPTR_NULL; + asyncParams.userCallback = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(AsyncCallback)); + + StackAllocator<AsyncCallbackParam_t> param; + param->thread = coreinitThread_getCurrentThreadMPTRDepr(ppcInterpreterCurrentInstance); + param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; + asyncParams.userContext = param.GetMPTRBE(); + + SAVEStatus status = SAVEOpenDirOtherApplicationAsync(client, block, titleId, accountSlot, path, hDir, errHandling, &asyncParams); + if (status == (FSStatus)FS_RESULT::SUCCESS) + { + coreinit_suspendThread(coreinitThread_getCurrentThreadDepr(ppcInterpreterCurrentInstance), 1000); + PPCCore_switchToScheduler(); + return param->returnStatus; + } + + return status; + } + + void export_SAVEOpenDirOtherApplication(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); + ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); + ppcDefineParamU64(titleId, 2); + ppcDefineParamU8(accountSlot, 3); + ppcDefineParamMEMPTR(path, const char, 4); + ppcDefineParamMEMPTR(hDir, betype<FSDirHandle2>, 5); + ppcDefineParamU32(errHandling, 6); + + const SAVEStatus result = SAVEOpenDirOtherApplication(fsClient.GetPtr(), fsCmdBlock.GetPtr(), titleId, accountSlot, path.GetPtr(), hDir, errHandling); + saveLog_printf("SAVEOpenDirOtherApplication(0x%08x, 0x%08x, %llx, %x, %s, 0x%08x (%x), %x) -> %x", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), titleId, accountSlot, path.GetPtr(), hDir.GetMPTR(), + (hDir.GetPtr() == nullptr ? 0 : (uint32)*hDir.GetPtr()), errHandling, result); + osLib_returnFromFunction(hCPU, result); + } + + SAVEStatus SAVEOpenDirOtherNormalApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + { + uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID(uniqueId); + return SAVEOpenDirOtherApplicationAsync(client, block, titleId, accountSlot, path, hDir, errHandling, asyncParams); + } + + void export_SAVEOpenDirOtherNormalApplicationAsync(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); + ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); + ppcDefineParamU32(uniqueId, 2); + ppcDefineParamU8(accountSlot, 3); + ppcDefineParamMEMPTR(path, const char, 4); + ppcDefineParamMEMPTR(hDir, betype<FSDirHandle2>, 5); + ppcDefineParamU32(errHandling, 6); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParams_t, 7); + + const SAVEStatus result = SAVEOpenDirOtherNormalApplicationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, accountSlot, path.GetPtr(), hDir, errHandling, asyncParams.GetPtr()); + osLib_returnFromFunction(hCPU, result); + } + + SAVEStatus SAVEOpenDirOtherNormalApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling) + { + uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID(uniqueId); + return SAVEOpenDirOtherApplication(client, block, titleId, accountSlot, path, hDir, errHandling); + } + + void export_SAVEOpenDirOtherNormalApplication(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); + ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); + ppcDefineParamU32(uniqueId, 2); + ppcDefineParamU8(accountSlot, 3); + ppcDefineParamMEMPTR(path, const char, 4); + ppcDefineParamMEMPTR(hDir, betype<FSDirHandle2>, 5); + ppcDefineParamU32(errHandling, 6); + + const SAVEStatus result = SAVEOpenDirOtherNormalApplication(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, accountSlot, path.GetPtr(), hDir, errHandling); + osLib_returnFromFunction(hCPU, result); + } + + SAVEStatus SAVEOpenDirOtherNormalApplicationVariationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + { + uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID_VARIATION(uniqueId, variation); + return SAVEOpenDirOtherApplicationAsync(client, block, titleId, accountSlot, path, hDir, errHandling, asyncParams); + } + + void export_SAVEOpenDirOtherNormalApplicationVariationAsync(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); + ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); + ppcDefineParamU32(uniqueId, 2); + ppcDefineParamU8(variation, 3); + ppcDefineParamU8(accountSlot, 4); + ppcDefineParamMEMPTR(path, const char, 5); + ppcDefineParamMEMPTR(hDir, betype<FSDirHandle2>, 6); + ppcDefineParamU32(errHandling, 7); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParams_t, 8); + + const SAVEStatus result = SAVEOpenDirOtherNormalApplicationVariationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, variation, accountSlot, path.GetPtr(), hDir, errHandling, asyncParams.GetPtr()); + osLib_returnFromFunction(hCPU, result); + } + + SAVEStatus SAVEOpenDirOtherNormalApplicationVariation(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling) + { + uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID_VARIATION(uniqueId, variation); + return SAVEOpenDirOtherApplication(client, block, titleId, accountSlot, path, hDir, errHandling); + } + + void export_SAVEOpenDirOtherNormalApplicationVariation(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); + ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); + ppcDefineParamU32(uniqueId, 2); + ppcDefineParamU8(variation, 3); + ppcDefineParamU8(accountSlot, 4); + ppcDefineParamMEMPTR(path, const char, 5); + ppcDefineParamMEMPTR(hDir, betype<FSDirHandle2>, 6); + ppcDefineParamU32(errHandling, 7); + + const SAVEStatus result = SAVEOpenDirOtherNormalApplicationVariation(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, variation, accountSlot, path.GetPtr(), hDir, errHandling); + osLib_returnFromFunction(hCPU, result); + } + + void export_SAVEMakeDirAsync(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); + ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); + ppcDefineParamU8(accountSlot, 2); + ppcDefineParamMEMPTR(path, const char, 3); + ppcDefineParamU32(errHandling, 4); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParams_t, 5); + + const SAVEStatus result = SAVEMakeDirAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), errHandling, asyncParams.GetPtr()); + saveLog_printf("SAVEMakeDirAsync(0x%08x, 0x%08x, %x, %s, %x) -> %x", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), errHandling, result); + osLib_returnFromFunction(hCPU, result); + } + + SAVEStatus SAVEMakeDir(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling) + { + FSAsyncParams_t asyncParams; + asyncParams.ioMsgQueue = MPTR_NULL; + asyncParams.userCallback = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(AsyncCallback)); + + StackAllocator<AsyncCallbackParam_t> param; + param->thread = coreinitThread_getCurrentThreadMPTRDepr(ppcInterpreterCurrentInstance); + param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; + asyncParams.userContext = param.GetMPTRBE(); + + SAVEStatus status = SAVEMakeDirAsync(client, block, accountSlot, path, errHandling, &asyncParams); + if (status == (FSStatus)FS_RESULT::SUCCESS) + { + coreinit_suspendThread(coreinitThread_getCurrentThreadDepr(ppcInterpreterCurrentInstance), 1000); + PPCCore_switchToScheduler(); + return param->returnStatus; + } + + return status; + } + + void export_SAVEMakeDir(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); + ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); + ppcDefineParamU8(accountSlot, 2); + ppcDefineParamMEMPTR(path, const char, 3); + ppcDefineParamU32(errHandling, 4); + + const SAVEStatus result = SAVEMakeDir(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), errHandling); + saveLog_printf("SAVEMakeDir(0x%08x, 0x%08x, %x, %s, %x) -> %x", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), errHandling, result); + osLib_returnFromFunction(hCPU, result); + } + + void export_SAVEOpenFileAsync(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); + ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); + ppcDefineParamU8(accountSlot, 2); + ppcDefineParamMEMPTR(path, const char, 3); + ppcDefineParamMEMPTR(mode, const char, 4); + ppcDefineParamMEMPTR(hFile, FSFileHandleDepr_t, 5); + ppcDefineParamU32(errHandling, 6); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParamsNew_t, 7); + + const SAVEStatus result = SAVEOpenFileAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), mode.GetPtr(), hFile.GetPtr(), errHandling, asyncParams.GetPtr()); + saveLog_printf("SAVEOpenFileAsync(0x%08x, 0x%08x, %x, %s, %s, 0x%08x, %x) -> %x", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), mode.GetPtr(), hFile.GetMPTR(), errHandling, result); + osLib_returnFromFunction(hCPU, result); + } + + SAVEStatus SAVEOpenFile(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, const char* mode, FSFileHandleDepr_t* hFile, FS_ERROR_MASK errHandling) + { + FSAsyncParamsNew_t asyncParams; + asyncParams.ioMsgQueue = nullptr; + asyncParams.userCallback = PPCInterpreter_makeCallableExportDepr(AsyncCallback); + + StackAllocator<AsyncCallbackParam_t> param; + param->thread = coreinitThread_getCurrentThreadMPTRDepr(ppcInterpreterCurrentInstance); + param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; + asyncParams.userContext = param.GetPointer(); + + SAVEStatus status = SAVEOpenFileAsync(client, block, accountSlot, path, mode, hFile, errHandling, &asyncParams); + if (status == (FSStatus)FS_RESULT::SUCCESS) + { + coreinit_suspendThread(coreinitThread_getCurrentThreadDepr(ppcInterpreterCurrentInstance), 1000); + PPCCore_switchToScheduler(); + return param->returnStatus; + } + + return status; + } + + void export_SAVEOpenFile(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); + ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); + ppcDefineParamU8(accountSlot, 2); + ppcDefineParamMEMPTR(path, const char, 3); + ppcDefineParamMEMPTR(mode, const char, 4); + ppcDefineParamMEMPTR(hFile, FSFileHandleDepr_t, 5); + ppcDefineParamU32(errHandling, 6); + + const SAVEStatus result = SAVEOpenFile(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), mode.GetPtr(), hFile.GetPtr(), errHandling); + saveLog_printf("SAVEOpenFile(0x%08x, 0x%08x, %x, %s, %s, 0x%08x, %x) -> %x", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), mode.GetPtr(), hFile.GetMPTR(), errHandling, result); + osLib_returnFromFunction(hCPU, result); + } + + void export_SAVEInitSaveDir(PPCInterpreter_t* hCPU) + { + ppcDefineParamU8(accountSlot, 0); + const SAVEStatus result = SAVEInitSaveDir(accountSlot); + saveLog_printf("SAVEInitSaveDir(%x) -> %x", accountSlot, result); + osLib_returnFromFunction(hCPU, result); + } + + void export_SAVEGetStatAsync(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); + ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); + ppcDefineParamU8(accountSlot, 2); + ppcDefineParamMEMPTR(path, const char, 3); + ppcDefineParamMEMPTR(stat, FSStat_t, 4); + ppcDefineParamU32(errHandling, 5); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParams_t, 6); + + const SAVEStatus result = SAVEGetStatAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), stat.GetPtr(), errHandling, asyncParams.GetPtr()); + saveLog_printf("SAVEGetStatAsync(0x%08x, 0x%08x, %x, %s, 0x%08x, %x) -> %x", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), stat.GetMPTR(), errHandling, result); + osLib_returnFromFunction(hCPU, result); + } + + SAVEStatus SAVEGetStat(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling) + { + FSAsyncParams_t asyncParams; + asyncParams.ioMsgQueue = MPTR_NULL; + asyncParams.userCallback = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(AsyncCallback)); + + StackAllocator<AsyncCallbackParam_t> param; + param->thread = coreinitThread_getCurrentThreadMPTRDepr(ppcInterpreterCurrentInstance); + param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; + asyncParams.userContext = param.GetMPTRBE(); + + SAVEStatus status = SAVEGetStatAsync(client, block, accountSlot, path, stat, errHandling, &asyncParams); + if (status == (FSStatus)FS_RESULT::SUCCESS) + { + coreinit_suspendThread(coreinitThread_getCurrentThreadDepr(ppcInterpreterCurrentInstance), 1000); + PPCCore_switchToScheduler(); + return param->returnStatus; + } + + return status; + } + + void export_SAVEGetStat(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); + ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); + ppcDefineParamU8(accountSlot, 2); + ppcDefineParamMEMPTR(path, const char, 3); + ppcDefineParamMEMPTR(stat, FSStat_t, 4); + ppcDefineParamU32(errHandling, 5); + + const SAVEStatus result = SAVEGetStat(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), stat.GetPtr(), errHandling); + saveLog_printf("SAVEGetStat(0x%08x, 0x%08x, %x, %s, 0x%08x, %x) -> %x", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), stat.GetMPTR(), errHandling, result); + osLib_returnFromFunction(hCPU, result); + } + + void export_SAVEGetStatOtherApplicationAsync(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); + ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); + ppcDefineParamU64(titleId, 2); + ppcDefineParamU8(accountSlot, 4); + ppcDefineParamMEMPTR(path, const char, 5); + ppcDefineParamMEMPTR(stat, FSStat_t, 6); + ppcDefineParamU32(errHandling, 7); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParams_t, 8); + + const SAVEStatus result = SAVEGetStatOtherApplicationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), titleId, accountSlot, path.GetPtr(), stat.GetPtr(), errHandling, asyncParams.GetPtr()); + osLib_returnFromFunction(hCPU, result); + } + + SAVEStatus SAVEGetStatOtherApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling) + { + FSAsyncParams_t asyncParams; + asyncParams.ioMsgQueue = MPTR_NULL; + asyncParams.userCallback = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(AsyncCallback)); + + StackAllocator<AsyncCallbackParam_t> param; + param->thread = coreinitThread_getCurrentThreadMPTRDepr(ppcInterpreterCurrentInstance); + param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; + asyncParams.userContext = param.GetMPTRBE(); + + SAVEStatus status = SAVEGetStatOtherApplicationAsync(client, block, titleId, accountSlot, path, stat, errHandling, &asyncParams); + if (status == (FSStatus)FS_RESULT::SUCCESS) + { + coreinit_suspendThread(coreinitThread_getCurrentThreadDepr(ppcInterpreterCurrentInstance), 1000); + PPCCore_switchToScheduler(); + return param->returnStatus; + } + + return status; + } + + void export_SAVEGetStatOtherApplication(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); + ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); + ppcDefineParamU64(titleId, 2); + ppcDefineParamU8(accountSlot, 4); + ppcDefineParamMEMPTR(path, const char, 5); + ppcDefineParamMEMPTR(stat, FSStat_t, 6); + ppcDefineParamU32(errHandling, 7); + + const SAVEStatus result = SAVEGetStatOtherApplication(fsClient.GetPtr(), fsCmdBlock.GetPtr(), titleId, accountSlot, path.GetPtr(), stat.GetPtr(), errHandling); + osLib_returnFromFunction(hCPU, result); + } + + + void export_SAVEGetStatOtherNormalApplicationAsync(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); + ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); + ppcDefineParamU32(uniqueId, 2); + ppcDefineParamU8(accountSlot, 3); + ppcDefineParamMEMPTR(path, const char, 4); + ppcDefineParamMEMPTR(stat, FSStat_t, 5); + ppcDefineParamU32(errHandling, 6); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParams_t, 8); + + const SAVEStatus result = SAVEGetStatOtherNormalApplicationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, accountSlot, path.GetPtr(), stat.GetPtr(), errHandling, asyncParams.GetPtr()); + osLib_returnFromFunction(hCPU, result); + } + + SAVEStatus SAVEGetStatOtherNormalApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling) + { + //peterBreak(); + + uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID(uniqueId); + return SAVEGetStatOtherApplication(client, block, titleId, accountSlot, path, stat, errHandling); + } + + void export_SAVEGetStatOtherNormalApplication(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); + ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); + ppcDefineParamU32(uniqueId, 2); + ppcDefineParamU8(accountSlot, 3); + ppcDefineParamMEMPTR(path, const char, 4); + ppcDefineParamMEMPTR(stat, FSStat_t, 5); + ppcDefineParamU32(errHandling, 6); + + const SAVEStatus result = SAVEGetStatOtherNormalApplication(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, accountSlot, path.GetPtr(), stat.GetPtr(), errHandling); + osLib_returnFromFunction(hCPU, result); + } + + + + void export_SAVEGetStatOtherNormalApplicationVariationAsync(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); + ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); + ppcDefineParamU32(uniqueId, 2); + ppcDefineParamU8(variation, 3); + ppcDefineParamU8(accountSlot, 4); + ppcDefineParamMEMPTR(path, const char, 5); + ppcDefineParamMEMPTR(stat, FSStat_t, 6); + ppcDefineParamU32(errHandling, 7); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParams_t, 8); + + const SAVEStatus result = SAVEGetStatOtherNormalApplicationVariationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, variation, accountSlot, path.GetPtr(), stat.GetPtr(), errHandling, asyncParams.GetPtr()); + osLib_returnFromFunction(hCPU, result); + } + + SAVEStatus SAVEGetStatOtherNormalApplicationVariation(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling) + { + //peterBreak(); + + uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID_VARIATION(uniqueId, variation); + return SAVEGetStatOtherApplication(client, block, titleId, accountSlot, path, stat, errHandling); + } + + void export_SAVEGetStatOtherNormalApplicationVariation(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); + ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); + ppcDefineParamU32(uniqueId, 2); + ppcDefineParamU8(variation, 3); + ppcDefineParamU8(accountSlot, 4); + ppcDefineParamMEMPTR(path, const char, 5); + ppcDefineParamMEMPTR(stat, FSStat_t, 6); + ppcDefineParamU32(errHandling, 7); + + const SAVEStatus result = SAVEGetStatOtherNormalApplicationVariation(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, variation, accountSlot, path.GetPtr(), stat.GetPtr(), errHandling); + osLib_returnFromFunction(hCPU, result); + } + + + SAVEStatus SAVEGetSharedDataTitlePath(uint64 titleId, const char* dataFileName, char* output, sint32 outputLength) + { + SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); + sint32 written = snprintf(output, outputLength, "/vol/storage_mlc01/sys/title/%08x/%08x/content/%s", GetTitleIdHigh(titleId), GetTitleIdLow(titleId), dataFileName); + if (written >= 0 && written < outputLength) + result = (FSStatus)FS_RESULT::SUCCESS; + cemu_assert_debug(result != (FSStatus)(FS_RESULT::FATAL_ERROR)); + return result; + } + + void export_SAVEGetSharedDataTitlePath(PPCInterpreter_t* hCPU) + { + ppcDefineParamU64(titleId, 0); + ppcDefineParamMEMPTR(dataFileName, const char, 2); + ppcDefineParamMEMPTR(output, char, 3); + ppcDefineParamS32(outputLength, 4); + const SAVEStatus result = SAVEGetSharedDataTitlePath(titleId, dataFileName.GetPtr(), output.GetPtr(), outputLength); + saveLog_printf("SAVEGetSharedDataTitlePath(0x%llx, %s, %s, 0x%x) -> %x", titleId, dataFileName.GetPtr(), output.GetPtr(), outputLength, result); + osLib_returnFromFunction(hCPU, result); + } + + SAVEStatus SAVEGetSharedSaveDataPath(uint64 titleId, const char* dataFileName, char* output, uint32 outputLength) + { + SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); + int written = snprintf(output, outputLength, "/vol/storage_mlc01/usr/save/%08x/%08x/user/common/%s", GetTitleIdHigh(titleId), GetTitleIdLow(titleId), dataFileName); + if (written >= 0 && written < (sint32)outputLength) + result = (FSStatus)FS_RESULT::SUCCESS; + cemu_assert_debug(result != (FSStatus)(FS_RESULT::FATAL_ERROR)); + return result; + } + + void export_SAVEGetSharedSaveDataPath(PPCInterpreter_t* hCPU) + { + ppcDefineParamU64(titleId, 0); + ppcDefineParamMEMPTR(dataFileName, const char, 2); + ppcDefineParamMEMPTR(output, char, 3); + ppcDefineParamU32(outputLength, 4); + const SAVEStatus result = SAVEGetSharedSaveDataPath(titleId, dataFileName.GetPtr(), output.GetPtr(), outputLength); + osLib_returnFromFunction(hCPU, result); + } + + SAVEStatus SAVEChangeDirAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + { + SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); + + OSLockMutex(&g_nn_save->mutex); + uint32 persistentId; + if (GetPersistentIdEx(accountSlot, &persistentId)) + { + char fullPath[SAVE_MAX_PATH_SIZE]; + if (GetAbsoluteFullPath(persistentId, path, fullPath)) + result = coreinit::FSChangeDirAsync(client, block, fullPath, errHandling, (FSAsyncParamsNew_t*)asyncParams); + } + else + result = (FSStatus)FS_RESULT::NOT_FOUND; + OSUnlockMutex(&g_nn_save->mutex); + + return result; + } + + void export_SAVEChangeDirAsync(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); + ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); + ppcDefineParamU8(accountSlot, 2); + ppcDefineParamMEMPTR(path, const char, 3); + ppcDefineParamU32(errHandling, 4); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParams_t, 5); + const SAVEStatus result = SAVEChangeDirAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), errHandling, asyncParams.GetPtr()); + saveLog_printf("SAVEChangeDirAsync(0x%08x, 0x%08x, %x, %s, %x) -> %x", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), errHandling, result); + osLib_returnFromFunction(hCPU, result); + } + + SAVEStatus SAVEChangeDir(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling) + { + FSAsyncParams_t asyncParams; + asyncParams.ioMsgQueue = MPTR_NULL; + asyncParams.userCallback = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(AsyncCallback)); + + StackAllocator<AsyncCallbackParam_t> param; + param->thread = coreinitThread_getCurrentThreadMPTRDepr(ppcInterpreterCurrentInstance); + param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; + asyncParams.userContext = param.GetMPTRBE(); + + SAVEStatus status = SAVEChangeDirAsync(client, block, accountSlot, path, errHandling, &asyncParams); + if (status == (FSStatus)FS_RESULT::SUCCESS) + { + coreinit_suspendThread(coreinitThread_getCurrentThreadDepr(ppcInterpreterCurrentInstance), 1000); + PPCCore_switchToScheduler(); + return param->returnStatus; + } + + return status; + } + + void export_SAVEChangeDir(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); + ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); + ppcDefineParamU8(accountSlot, 2); + ppcDefineParamMEMPTR(path, const char, 3); + ppcDefineParamU32(errHandling, 4); + const SAVEStatus result = SAVEChangeDir(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), errHandling); + saveLog_printf("SAVEChangeDir(0x%08x, 0x%08x, %x, %s, %x) -> %x", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), errHandling, result); + osLib_returnFromFunction(hCPU, result); + } + + SAVEStatus SAVEFlushQuotaAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + { + SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); + + OSLockMutex(&g_nn_save->mutex); + uint32 persistentId; + if (GetPersistentIdEx(accountSlot, &persistentId)) + { + char fullPath[SAVE_MAX_PATH_SIZE]; + if (GetAbsoluteFullPath(persistentId, nullptr, fullPath)) + { + result = coreinit::FSFlushQuotaAsync(client, block, fullPath, errHandling, (FSAsyncParamsNew_t*)asyncParams); + // if(OSGetUPID != 0xF) + UpdateSaveTimeStamp(persistentId); + } + } + else + result = (FSStatus)FS_RESULT::NOT_FOUND; + OSUnlockMutex(&g_nn_save->mutex); + + return result; + } + + void export_SAVEFlushQuotaAsync(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); + ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); + ppcDefineParamU8(accountSlot, 2); + ppcDefineParamU32(errHandling, 3); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParams_t, 4); + const SAVEStatus result = SAVEFlushQuotaAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, errHandling, asyncParams.GetPtr()); + saveLog_printf("SAVEFlushQuotaAsync(0x%08x, 0x%08x, %x, %x) -> %x", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, errHandling, result); + osLib_returnFromFunction(hCPU, result); + } + + SAVEStatus SAVEFlushQuota(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, FS_ERROR_MASK errHandling) + { + FSAsyncParams_t asyncParams; + asyncParams.ioMsgQueue = MPTR_NULL; + asyncParams.userCallback = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(AsyncCallback)); + + StackAllocator<AsyncCallbackParam_t> param; + param->thread = coreinitThread_getCurrentThreadMPTRDepr(ppcInterpreterCurrentInstance); + param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; + asyncParams.userContext = param.GetMPTRBE(); + + SAVEStatus status = SAVEFlushQuotaAsync(client, block, accountSlot, errHandling, &asyncParams); + if (status == (FSStatus)FS_RESULT::SUCCESS) + { + coreinit_suspendThread(coreinitThread_getCurrentThreadDepr(ppcInterpreterCurrentInstance), 1000); + PPCCore_switchToScheduler(); + return param->returnStatus; + } + return status; + } + + void export_SAVEFlushQuota(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); + ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); + ppcDefineParamU8(accountSlot, 2); + ppcDefineParamU32(errHandling, 3); + const SAVEStatus result = SAVEFlushQuota(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, errHandling); + saveLog_printf("SAVEFlushQuota(0x%08x, 0x%08x, %x, %x) -> %x", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, errHandling, result); + osLib_returnFromFunction(hCPU, result); + } + + void load() + { + osLib_addFunction("nn_save", "SAVEInit", export_SAVEInit); + osLib_addFunction("nn_save", "SAVEInitSaveDir", export_SAVEInitSaveDir); + osLib_addFunction("nn_save", "SAVEGetSharedDataTitlePath", export_SAVEGetSharedDataTitlePath); + osLib_addFunction("nn_save", "SAVEGetSharedSaveDataPath", export_SAVEGetSharedSaveDataPath); + + // sync functions + osLib_addFunction("nn_save", "SAVEGetFreeSpaceSize", export_SAVEGetFreeSpaceSize); + osLib_addFunction("nn_save", "SAVEMakeDir", export_SAVEMakeDir); + osLib_addFunction("nn_save", "SAVERemove", export_SAVERemove); + osLib_addFunction("nn_save", "SAVEChangeDir", export_SAVEChangeDir); + osLib_addFunction("nn_save", "SAVEFlushQuota", export_SAVEFlushQuota); + + osLib_addFunction("nn_save", "SAVEGetStat", export_SAVEGetStat); + osLib_addFunction("nn_save", "SAVEGetStatOtherApplication", export_SAVEGetStatOtherApplication); + osLib_addFunction("nn_save", "SAVEGetStatOtherNormalApplication", export_SAVEGetStatOtherNormalApplication); + osLib_addFunction("nn_save", "SAVEGetStatOtherNormalApplicationVariation", export_SAVEGetStatOtherNormalApplicationVariation); + + osLib_addFunction("nn_save", "SAVEOpenFile", export_SAVEOpenFile); + osLib_addFunction("nn_save", "SAVEOpenFileOtherApplication", export_SAVEOpenFileOtherApplication); + osLib_addFunction("nn_save", "SAVEOpenFileOtherNormalApplication", export_SAVEOpenFileOtherNormalApplication); + osLib_addFunction("nn_save", "SAVEOpenFileOtherNormalApplicationVariation", export_SAVEOpenFileOtherNormalApplicationVariation); + + osLib_addFunction("nn_save", "SAVEOpenDir", export_SAVEOpenDir); + osLib_addFunction("nn_save", "SAVEOpenDirOtherApplication", export_SAVEOpenDirOtherApplication); + osLib_addFunction("nn_save", "SAVEOpenDirOtherNormalApplication", export_SAVEOpenDirOtherNormalApplication); + osLib_addFunction("nn_save", "SAVEOpenDirOtherNormalApplicationVariation", export_SAVEOpenDirOtherNormalApplicationVariation); + + // async functions + osLib_addFunction("nn_save", "SAVEGetFreeSpaceSizeAsync", export_SAVEGetFreeSpaceSizeAsync); + osLib_addFunction("nn_save", "SAVEMakeDirAsync", export_SAVEMakeDirAsync); + osLib_addFunction("nn_save", "SAVERemoveAsync", export_SAVERemoveAsync); + osLib_addFunction("nn_save", "SAVERenameAsync", export_SAVERenameAsync); + cafeExportRegister("nn_save", SAVERename, LogType::Save); + + osLib_addFunction("nn_save", "SAVEChangeDirAsync", export_SAVEChangeDirAsync); + osLib_addFunction("nn_save", "SAVEFlushQuotaAsync", export_SAVEFlushQuotaAsync); + + osLib_addFunction("nn_save", "SAVEGetStatAsync", export_SAVEGetStatAsync); + osLib_addFunction("nn_save", "SAVEGetStatOtherApplicationAsync", export_SAVEGetStatOtherApplicationAsync); + osLib_addFunction("nn_save", "SAVEGetStatOtherNormalApplicationAsync", export_SAVEGetStatOtherNormalApplicationAsync); + osLib_addFunction("nn_save", "SAVEGetStatOtherNormalApplicationVariationAsync", export_SAVEGetStatOtherNormalApplicationVariationAsync); + + osLib_addFunction("nn_save", "SAVEOpenFileAsync", export_SAVEOpenFileAsync); + osLib_addFunction("nn_save", "SAVEOpenFileOtherApplicationAsync", export_SAVEOpenFileOtherApplicationAsync); + osLib_addFunction("nn_save", "SAVEOpenFileOtherNormalApplicationAsync", export_SAVEOpenFileOtherNormalApplicationAsync); + osLib_addFunction("nn_save", "SAVEOpenFileOtherNormalApplicationVariationAsync", export_SAVEOpenFileOtherNormalApplicationVariationAsync); + + osLib_addFunction("nn_save", "SAVEOpenDirAsync", export_SAVEOpenDirAsync); + osLib_addFunction("nn_save", "SAVEOpenDirOtherApplicationAsync", export_SAVEOpenDirOtherApplicationAsync); + osLib_addFunction("nn_save", "SAVEOpenDirOtherNormalApplicationAsync", export_SAVEOpenDirOtherNormalApplicationAsync); + osLib_addFunction("nn_save", "SAVEOpenDirOtherNormalApplicationVariationAsync", export_SAVEOpenDirOtherNormalApplicationVariationAsync); + } +} +} diff --git a/src/Cafe/OS/libs/nn_save/nn_save.h b/src/Cafe/OS/libs/nn_save/nn_save.h new file mode 100644 index 00000000..088fb1b6 --- /dev/null +++ b/src/Cafe/OS/libs/nn_save/nn_save.h @@ -0,0 +1,11 @@ +#pragma once + +namespace nn +{ +namespace save +{ + void load(); + + bool GetPersistentIdEx(uint8 accountSlot, uint32* persistentId); +} +} diff --git a/src/Cafe/OS/libs/nn_temp/nn_temp.cpp b/src/Cafe/OS/libs/nn_temp/nn_temp.cpp new file mode 100644 index 00000000..7ed619ad --- /dev/null +++ b/src/Cafe/OS/libs/nn_temp/nn_temp.cpp @@ -0,0 +1,24 @@ +#include "Cafe/OS/common/OSCommon.h" + +namespace nn::temp +{ + uint64 tempIdGenerator = 0xdc1b04bd961f2c04ULL; + + void nnTempExport_TEMPCreateAndInitTempDir(PPCInterpreter_t* hCPU) + { + forceLogDebug_printf("TEMPCreateAndInitTempDir(...) - placeholder"); + + // create random temp id + memory_writeU64Slow(hCPU->gpr[5], tempIdGenerator); + tempIdGenerator = (tempIdGenerator << 3) | (tempIdGenerator >> 61); + tempIdGenerator += 0x56e28bd5f4ULL; + + osLib_returnFromFunction(hCPU, 0); + } + + void Initialize() + { + osLib_addFunction("nn_temp", "TEMPCreateAndInitTempDir", nnTempExport_TEMPCreateAndInitTempDir); + } +}; + diff --git a/src/Cafe/OS/libs/nn_temp/nn_temp.h b/src/Cafe/OS/libs/nn_temp/nn_temp.h new file mode 100644 index 00000000..da2b74c0 --- /dev/null +++ b/src/Cafe/OS/libs/nn_temp/nn_temp.h @@ -0,0 +1,6 @@ +#pragma once + +namespace nn::temp +{ + void Initialize(); +}; \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_uds/nn_uds.cpp b/src/Cafe/OS/libs/nn_uds/nn_uds.cpp new file mode 100644 index 00000000..b28e4b64 --- /dev/null +++ b/src/Cafe/OS/libs/nn_uds/nn_uds.cpp @@ -0,0 +1,24 @@ +#include "Cafe/OS/common/OSCommon.h" + +typedef struct +{ + uint32 reserved; +}udsWorkspace_t; + +udsWorkspace_t* udsWorkspace = NULL; + +void nnUdsExport___sti___11_uds_Api_cpp_f5d9abb2(PPCInterpreter_t* hCPU) +{ + debug_printf("__sti___11_uds_Api_cpp_f5d9abb2()\n"); + if( udsWorkspace == NULL ) + udsWorkspace = (udsWorkspace_t*)memory_getPointerFromVirtualOffset(coreinit_allocFromSysArea(32, 32)); + osLib_returnFromFunction(hCPU, memory_getVirtualOffsetFromPointer(udsWorkspace)); +} + +/* + * Load UDS functions + */ +void nnUds_load() +{ + osLib_addFunction("nn_uds", "__sti___11_uds_Api_cpp_f5d9abb2", nnUdsExport___sti___11_uds_Api_cpp_f5d9abb2); +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_uds/nn_uds.h b/src/Cafe/OS/libs/nn_uds/nn_uds.h new file mode 100644 index 00000000..7177ca4b --- /dev/null +++ b/src/Cafe/OS/libs/nn_uds/nn_uds.h @@ -0,0 +1 @@ +void nnUds_load(); \ No newline at end of file diff --git a/src/Cafe/OS/libs/nsyshid/nsyshid.cpp b/src/Cafe/OS/libs/nsyshid/nsyshid.cpp new file mode 100644 index 00000000..4789d3e4 --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/nsyshid.cpp @@ -0,0 +1,844 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/HW/Espresso/PPCCallback.h" +#include <bitset> +#include "nsyshid.h" + +#if BOOST_OS_WINDOWS > 0 + +#include <setupapi.h> +#include <initguid.h> +#include <hidsdi.h> + +#include "Cafe/OS/libs/coreinit/coreinit_Thread.h" + +#pragma comment(lib,"Setupapi.lib") +#pragma comment(lib,"hid.lib") + +DEFINE_GUID(GUID_DEVINTERFACE_HID, 0x4D1E55B2L, 0xF16F, 0x11CF, 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30); + +namespace nsyshid +{ + + typedef struct + { + /* +0x00 */ uint32be handle; + /* +0x04 */ uint32 ukn04; + /* +0x08 */ uint16 vendorId; // little-endian ? + /* +0x0A */ uint16 productId; // little-endian ? + /* +0x0C */ uint8 ifIndex; + /* +0x0D */ uint8 subClass; + /* +0x0E */ uint8 protocol; + /* +0x0F */ uint8 paddingGuessed0F; + /* +0x10 */ uint16be maxPacketSizeRX; + /* +0x12 */ uint16be maxPacketSizeTX; + }HIDDevice_t; + + static_assert(offsetof(HIDDevice_t, vendorId) == 0x8, ""); + static_assert(offsetof(HIDDevice_t, productId) == 0xA, ""); + static_assert(offsetof(HIDDevice_t, ifIndex) == 0xC, ""); + static_assert(offsetof(HIDDevice_t, protocol) == 0xE, ""); + + typedef struct _HIDDeviceInfo_t + { + uint32 handle; + uint32 physicalDeviceInstance; + uint16 vendorId; + uint16 productId; + uint8 interfaceIndex; + uint8 interfaceSubClass; + uint8 protocol; + HIDDevice_t* hidDevice; // this info is passed to applications and must remain intact + wchar_t* devicePath; + _HIDDeviceInfo_t* next; + // host + HANDLE hFile; + }HIDDeviceInfo_t; + + HIDDeviceInfo_t* firstDevice = nullptr; + + + typedef struct _HIDClient_t + { + MEMPTR<_HIDClient_t> next; + uint32be callbackFunc; // attach/detach callback + }HIDClient_t; + + HIDClient_t* firstHIDClient = nullptr; + + HANDLE openDevice(wchar_t* devicePath) + { + return CreateFile(devicePath, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | + FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, + NULL); + } + + void attachClientToList(HIDClient_t* hidClient) + { + // todo - append at the beginning or end of the list? List order matters because it also controls the order in which attach callbacks are called + if (firstHIDClient) + { + hidClient->next = firstHIDClient; + firstHIDClient = hidClient; + } + else + { + hidClient->next = nullptr; + firstHIDClient = hidClient; + } + } + + void attachDeviceToList(HIDDeviceInfo_t* hidDeviceInfo) + { + if (firstDevice) + { + hidDeviceInfo->next = firstDevice; + firstDevice = hidDeviceInfo; + } + else + { + hidDeviceInfo->next = nullptr; + firstDevice = hidDeviceInfo; + } + } + + HIDDeviceInfo_t* getHIDDeviceInfoByHandle(uint32 handle, bool openFileHandle = false) + { + HIDDeviceInfo_t* deviceItr = firstDevice; + while (deviceItr) + { + if (deviceItr->handle == handle) + { + if (openFileHandle && deviceItr->hFile == INVALID_HANDLE_VALUE) + { + deviceItr->hFile = openDevice(deviceItr->devicePath); + if (deviceItr->hFile == INVALID_HANDLE_VALUE) + { + forceLog_printfW(L"HID: Failed to open device \"%s\"", deviceItr->devicePath); + return nullptr; + } + HidD_SetNumInputBuffers(deviceItr->hFile, 2); // dont cache too many reports + } + return deviceItr; + } + deviceItr = deviceItr->next; + } + return nullptr; + } + + uint32 _lastGeneratedHidHandle = 1; + + uint32 generateHIDHandle() + { + _lastGeneratedHidHandle++; + return _lastGeneratedHidHandle; + } + + const int HID_MAX_NUM_DEVICES = 128; + + SysAllocator<HIDDevice_t, HID_MAX_NUM_DEVICES> _devicePool; + std::bitset<HID_MAX_NUM_DEVICES> _devicePoolMask; + + HIDDevice_t* getFreeDevice() + { + for (sint32 i = 0; i < HID_MAX_NUM_DEVICES; i++) + { + if (_devicePoolMask.test(i) == false) + { + _devicePoolMask.set(i); + return _devicePool.GetPtr() + i; + } + } + return nullptr; + } + + void checkAndAddDevice(wchar_t* devicePath, HANDLE hDevice) + { + HIDD_ATTRIBUTES hidAttr; + hidAttr.Size = sizeof(HIDD_ATTRIBUTES); + if (HidD_GetAttributes(hDevice, &hidAttr) == FALSE) + return; + HIDDevice_t* hidDevice = getFreeDevice(); + if (hidDevice == nullptr) + { + forceLog_printf("HID: Maximum number of supported devices exceeded"); + return; + } + + HIDDeviceInfo_t* deviceInfo = (HIDDeviceInfo_t*)malloc(sizeof(HIDDeviceInfo_t)); + memset(deviceInfo, 0, sizeof(HIDDeviceInfo_t)); + deviceInfo->devicePath = _wcsdup(devicePath); + deviceInfo->vendorId = hidAttr.VendorID; + deviceInfo->productId = hidAttr.ProductID; + deviceInfo->hFile = INVALID_HANDLE_VALUE; + // generate handle + deviceInfo->handle = generateHIDHandle(); + // get additional device info + sint32 maxPacketInputLength = -1; + sint32 maxPacketOutputLength = -1; + PHIDP_PREPARSED_DATA ppData = nullptr; + if (HidD_GetPreparsedData(hDevice, &ppData)) + { + HIDP_CAPS caps; + if (HidP_GetCaps(ppData, &caps) == HIDP_STATUS_SUCCESS) + { + // length includes the report id byte + maxPacketInputLength = caps.InputReportByteLength - 1; + maxPacketOutputLength = caps.OutputReportByteLength - 1; + } + HidD_FreePreparsedData(ppData); + } + if (maxPacketInputLength <= 0 || maxPacketInputLength >= 0xF000) + { + forceLog_printf("HID: Input packet length not available or out of range (length = %d)", maxPacketInputLength); + maxPacketInputLength = 0x20; + } + if (maxPacketOutputLength <= 0 || maxPacketOutputLength >= 0xF000) + { + forceLog_printf("HID: Output packet length not available or out of range (length = %d)", maxPacketOutputLength); + maxPacketOutputLength = 0x20; + } + // setup HIDDevice struct + deviceInfo->hidDevice = hidDevice; + memset(hidDevice, 0, sizeof(HIDDevice_t)); + hidDevice->handle = deviceInfo->handle; + hidDevice->vendorId = deviceInfo->vendorId; + hidDevice->productId = deviceInfo->productId; + hidDevice->maxPacketSizeRX = maxPacketInputLength; + hidDevice->maxPacketSizeTX = maxPacketOutputLength; + + hidDevice->ukn04 = 0x11223344; + + hidDevice->ifIndex = 1; + hidDevice->protocol = 0; + hidDevice->subClass = 2; + + // todo - other values + //hidDevice->ifIndex = 1; + + + attachDeviceToList(deviceInfo); + + } + + void initDeviceList() + { + if (firstDevice) + return; + HDEVINFO hDevInfo; + SP_DEVICE_INTERFACE_DATA DevIntfData; + PSP_DEVICE_INTERFACE_DETAIL_DATA DevIntfDetailData; + SP_DEVINFO_DATA DevData; + + DWORD dwSize, dwMemberIdx; + + hDevInfo = SetupDiGetClassDevs(&GUID_DEVINTERFACE_HID, NULL, 0, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT); + + if (hDevInfo != INVALID_HANDLE_VALUE) + { + DevIntfData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + dwMemberIdx = 0; + + SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &GUID_DEVINTERFACE_HID, + dwMemberIdx, &DevIntfData); + + while (GetLastError() != ERROR_NO_MORE_ITEMS) + { + DevData.cbSize = sizeof(DevData); + SetupDiGetDeviceInterfaceDetail( + hDevInfo, &DevIntfData, NULL, 0, &dwSize, NULL); + + DevIntfDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwSize); + DevIntfDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); + + if (SetupDiGetDeviceInterfaceDetail(hDevInfo, &DevIntfData, + DevIntfDetailData, dwSize, &dwSize, &DevData)) + { + HANDLE hHIDDevice = openDevice(DevIntfDetailData->DevicePath); + if (hHIDDevice != INVALID_HANDLE_VALUE) + { + checkAndAddDevice(DevIntfDetailData->DevicePath, hHIDDevice); + CloseHandle(hHIDDevice); + } + } + HeapFree(GetProcessHeap(), 0, DevIntfDetailData); + // next + SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &GUID_DEVINTERFACE_HID, ++dwMemberIdx, &DevIntfData); + } + SetupDiDestroyDeviceInfoList(hDevInfo); + } + } + + const int HID_CALLBACK_DETACH = 0; + const int HID_CALLBACK_ATTACH = 1; + + uint32 doAttachCallback(HIDClient_t* hidClient, HIDDeviceInfo_t* deviceInfo) + { + return PPCCoreCallback(hidClient->callbackFunc, memory_getVirtualOffsetFromPointer(hidClient), memory_getVirtualOffsetFromPointer(deviceInfo->hidDevice), HID_CALLBACK_ATTACH); + } + + void doDetachCallback(HIDClient_t* hidClient, HIDDeviceInfo_t* deviceInfo) + { + PPCCoreCallback(hidClient->callbackFunc, memory_getVirtualOffsetFromPointer(hidClient), memory_getVirtualOffsetFromPointer(deviceInfo->hidDevice), HID_CALLBACK_DETACH); + } + + void export_HIDAddClient(PPCInterpreter_t* hCPU) + { + ppcDefineParamTypePtr(hidClient, HIDClient_t, 0); + ppcDefineParamMPTR(callbackFuncMPTR, 1); + forceLogDebug_printf("nsyshid.HIDAddClient(0x%08x,0x%08x)", hCPU->gpr[3], hCPU->gpr[4]); + hidClient->callbackFunc = callbackFuncMPTR; + attachClientToList(hidClient); + initDeviceList(); + // do attach callbacks + HIDDeviceInfo_t* deviceItr = firstDevice; + while (deviceItr) + { + if (doAttachCallback(hidClient, deviceItr) != 0) + break; + deviceItr = deviceItr->next; + } + + osLib_returnFromFunction(hCPU, 0); + } + + void export_HIDDelClient(PPCInterpreter_t* hCPU) + { + ppcDefineParamTypePtr(hidClient, HIDClient_t, 0); + forceLogDebug_printf("nsyshid.HIDDelClient(0x%08x)", hCPU->gpr[3]); + + // todo + // do detach callbacks + HIDDeviceInfo_t* deviceItr = firstDevice; + while (deviceItr) + { + doDetachCallback(hidClient, deviceItr); + deviceItr = deviceItr->next; + } + + osLib_returnFromFunction(hCPU, 0); + } + + void export_HIDGetDescriptor(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(hidHandle, 0); // r3 + ppcDefineParamU8(descType, 1); // r4 + ppcDefineParamU8(descIndex, 2); // r5 + ppcDefineParamU8(lang, 3); // r6 + ppcDefineParamUStr(output, 4); // r7 + ppcDefineParamU32(outputMaxLength, 5); // r8 + ppcDefineParamMPTR(cbFuncMPTR, 6); // r9 + ppcDefineParamMPTR(cbParamMPTR, 7); // r10 + + HIDDeviceInfo_t* hidDeviceInfo = getHIDDeviceInfoByHandle(hidHandle); + if (hidDeviceInfo) + { + HANDLE hHIDDevice = openDevice(hidDeviceInfo->devicePath); + if (hHIDDevice != INVALID_HANDLE_VALUE) + { + if (descType == 0x02) + { + uint8 configurationDescriptor[0x29]; + + uint8* currentWritePtr; + + // configuration descriptor + currentWritePtr = configurationDescriptor + 0; + *(uint8*)(currentWritePtr + 0) = 9; // bLength + *(uint8*)(currentWritePtr + 1) = 2; // bDescriptorType + *(uint16be*)(currentWritePtr + 2) = 0x0029; // wTotalLength + *(uint8*)(currentWritePtr + 4) = 1; // bNumInterfaces + *(uint8*)(currentWritePtr + 5) = 1; // bConfigurationValue + *(uint8*)(currentWritePtr + 6) = 0; // iConfiguration + *(uint8*)(currentWritePtr + 7) = 0x80; // bmAttributes + *(uint8*)(currentWritePtr + 8) = 0xFA; // MaxPower + currentWritePtr = currentWritePtr + 9; + // configuration descriptor + *(uint8*)(currentWritePtr + 0) = 9; // bLength + *(uint8*)(currentWritePtr + 1) = 0x04; // bDescriptorType + *(uint8*)(currentWritePtr + 2) = 0; // bInterfaceNumber + *(uint8*)(currentWritePtr + 3) = 0; // bAlternateSetting + *(uint8*)(currentWritePtr + 4) = 2; // bNumEndpoints + *(uint8*)(currentWritePtr + 5) = 3; // bInterfaceClass + *(uint8*)(currentWritePtr + 6) = 0; // bInterfaceSubClass + *(uint8*)(currentWritePtr + 7) = 0; // bInterfaceProtocol + *(uint8*)(currentWritePtr + 8) = 0; // iInterface + currentWritePtr = currentWritePtr + 9; + // configuration descriptor + *(uint8*)(currentWritePtr + 0) = 9; // bLength + *(uint8*)(currentWritePtr + 1) = 0x21; // bDescriptorType + *(uint16be*)(currentWritePtr + 2) = 0x0111; // bcdHID + *(uint8*)(currentWritePtr + 4) = 0x00; // bCountryCode + *(uint8*)(currentWritePtr + 5) = 0x01; // bNumDescriptors + *(uint8*)(currentWritePtr + 6) = 0x22; // bDescriptorType + *(uint16be*)(currentWritePtr + 7) = 0x001D; // wDescriptorLength + currentWritePtr = currentWritePtr + 9; + // endpoint descriptor 1 + *(uint8*)(currentWritePtr + 0) = 7; // bLength + *(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType + *(uint8*)(currentWritePtr + 1) = 0x81; // bEndpointAddress + *(uint8*)(currentWritePtr + 2) = 0x03; // bmAttributes + *(uint16be*)(currentWritePtr + 3) = 0x40; // wMaxPacketSize + *(uint8*)(currentWritePtr + 5) = 0x01; // bInterval + currentWritePtr = currentWritePtr + 7; + // endpoint descriptor 2 + *(uint8*)(currentWritePtr + 0) = 7; // bLength + *(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType + *(uint8*)(currentWritePtr + 1) = 0x02; // bEndpointAddress + *(uint8*)(currentWritePtr + 2) = 0x03; // bmAttributes + *(uint16be*)(currentWritePtr + 3) = 0x40; // wMaxPacketSize + *(uint8*)(currentWritePtr + 5) = 0x01; // bInterval + currentWritePtr = currentWritePtr + 7; + + cemu_assert_debug((currentWritePtr - configurationDescriptor) == 0x29); + + memcpy(output, configurationDescriptor, std::min<uint32>(outputMaxLength, sizeof(configurationDescriptor))); + } + else + { + cemu_assert_unimplemented(); + } + CloseHandle(hHIDDevice); + } + else + { + cemu_assert_unimplemented(); + } + } + else + { + cemu_assert_suspicious(); + } + osLib_returnFromFunction(hCPU, 0); + } + + void _debugPrintHex(std::string prefix, uint8* data, size_t len) + { + char debugOutput[1024] = { 0 }; + len = std::min(len, (size_t)100); + for (sint32 i = 0; i < len; i++) + { + sprintf(debugOutput + i * 3, "%02x ", data[i]); + } + forceLogDebug_printf("[%s] Data: %s", prefix.c_str(), debugOutput); + } + + void doHIDTransferCallback(MPTR callbackFuncMPTR, MPTR callbackParamMPTR, uint32 hidHandle, uint32 errorCode, MPTR buffer, sint32 length) + { + coreinitAsyncCallback_add(callbackFuncMPTR, 5, hidHandle, errorCode, buffer, length, callbackParamMPTR); + } + + void export_HIDSetIdle(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(hidHandle, 0); // r3 + ppcDefineParamU32(ifIndex, 1); // r4 + ppcDefineParamU32(ukn, 2); // r5 + ppcDefineParamU32(duration, 3); // r6 + ppcDefineParamMPTR(callbackFuncMPTR, 4); // r7 + ppcDefineParamMPTR(callbackParamMPTR, 5); // r8 + forceLogDebug_printf("nsyshid.HIDSetIdle(...)"); + + // todo + if (callbackFuncMPTR) + { + doHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, hidHandle, 0, MPTR_NULL, 0); + } + else + { + cemu_assert_unimplemented(); + } + osLib_returnFromFunction(hCPU, 0); // for non-async version, return number of bytes transferred + } + + void export_HIDSetProtocol(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(hidHandle, 0); // r3 + ppcDefineParamU32(ifIndex, 1); // r4 + ppcDefineParamU32(protocol, 2); // r5 + ppcDefineParamMPTR(callbackFuncMPTR, 3); // r6 + ppcDefineParamMPTR(callbackParamMPTR, 4); // r7 + forceLogDebug_printf("nsyshid.HIDSetProtocol(...)"); + + if (callbackFuncMPTR) + { + doHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, hidHandle, 0, MPTR_NULL, 0); + } + else + { + cemu_assert_unimplemented(); + } + osLib_returnFromFunction(hCPU, 0); // for non-async version, return number of bytes transferred + } + + // handler for async HIDSetReport transfers + void _hidSetReportAsync(HIDDeviceInfo_t* hidDeviceInfo, uint8* reportData, sint32 length, uint8* originalData, sint32 originalLength, MPTR callbackFuncMPTR, MPTR callbackParamMPTR) + { + sint32 retryCount = 0; + while (true) + { + BOOL r = HidD_SetOutputReport(hidDeviceInfo->hFile, reportData, length); + if (r != FALSE) + break; + Sleep(20); // retry + retryCount++; + if (retryCount >= 40) + { + forceLog_printf("HID async SetReport failed"); + sint32 errorCode = -1; + doHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, hidDeviceInfo->handle, errorCode, memory_getVirtualOffsetFromPointer(originalData), 0); + free(reportData); + return; + } + } + doHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, hidDeviceInfo->handle, 0, memory_getVirtualOffsetFromPointer(originalData), originalLength); + free(reportData); + } + + // handler for synchronous HIDSetReport transfers + sint32 _hidSetReportSync(HIDDeviceInfo_t* hidDeviceInfo, uint8* reportData, sint32 length, uint8* originalData, sint32 originalLength, OSThread_t* osThread) + { + //forceLogDebug_printf("_hidSetReportSync begin"); + _debugPrintHex("_hidSetReportSync Begin", reportData, length); + sint32 retryCount = 0; + sint32 returnCode = 0; + while (true) + { + BOOL r = HidD_SetOutputReport(hidDeviceInfo->hFile, reportData, length); + if (r != FALSE) + { + returnCode = originalLength; + break; + } + Sleep(100); // retry + retryCount++; + if (retryCount >= 10) + assert_dbg(); + } + free(reportData); + forceLogDebug_printf("_hidSetReportSync end. returnCode: %d", returnCode); + coreinit_resumeThread(osThread, 1000); + return returnCode; + } + + void export_HIDSetReport(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(hidHandle, 0); // r3 + ppcDefineParamU32(reportRelatedUkn, 1); // r4 + ppcDefineParamU32(reportId, 2); // r5 + ppcDefineParamUStr(data, 3); // r6 + ppcDefineParamU32(dataLength, 4); // r7 + ppcDefineParamMPTR(callbackFuncMPTR, 5); // r8 + ppcDefineParamMPTR(callbackParamMPTR, 6); // r9 + forceLogDebug_printf("nsyshid.HIDSetReport(%d,0x%02x,0x%02x,...)", hidHandle, reportRelatedUkn, reportId); + + _debugPrintHex("HIDSetReport", data, dataLength); + +#ifndef PUBLIC_RELEASE + if (reportRelatedUkn != 2 || reportId != 0) + assert_dbg(); +#endif + + HIDDeviceInfo_t* hidDeviceInfo = getHIDDeviceInfoByHandle(hidHandle, true); + if (hidDeviceInfo == nullptr) + { + forceLog_printf("nsyshid.HIDSetReport(): Unable to find device with hid handle %d", hidHandle); + osLib_returnFromFunction(hCPU, -1); + return; + } + + // prepare report data + // note: Currently we need to pad the data to 0x20 bytes for it to work (plus one extra byte for HidD_SetOutputReport) + // Does IOSU pad data to 0x20 byte? Also check if this is specific to Skylanders portal + sint32 paddedLength = (dataLength +0x1F)&~0x1F; + uint8* reportData = (uint8*)malloc(paddedLength+1); + memset(reportData, 0, paddedLength+1); + reportData[0] = 0; + memcpy(reportData + 1, data, dataLength); + + + // issue request (synchronous or asynchronous) + sint32 returnCode = 0; + if (callbackFuncMPTR == MPTR_NULL) + { + std::future<sint32> res = std::async(std::launch::async, &_hidSetReportSync, hidDeviceInfo, reportData, paddedLength + 1, data, dataLength, coreinitThread_getCurrentThreadDepr(hCPU)); + coreinit_suspendThread(coreinitThread_getCurrentThreadDepr(hCPU), 1000); + PPCCore_switchToScheduler(); + returnCode = res.get(); + } + else + { + // asynchronous + std::thread(&_hidSetReportAsync, hidDeviceInfo, reportData, paddedLength+1, data, dataLength, callbackFuncMPTR, callbackParamMPTR).detach(); + returnCode = 0; + } + osLib_returnFromFunction(hCPU, returnCode); + } + + sint32 _hidReadInternalSync(HIDDeviceInfo_t* hidDeviceInfo, uint8* data, sint32 maxLength) + { + DWORD bt; + OVERLAPPED ovlp = { 0 }; + ovlp.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + + uint8* tempBuffer = (uint8*)malloc(maxLength + 1); + sint32 transferLength = 0; // minus report byte + + _debugPrintHex("HID_READ_BEFORE", data, maxLength); + + forceLogDebug_printf("HidRead Begin (Length 0x%08x)", maxLength); + BOOL readResult = ReadFile(hidDeviceInfo->hFile, tempBuffer, maxLength + 1, &bt, &ovlp); + if (readResult != FALSE) + { + // sometimes we get the result immediately + if (bt == 0) + transferLength = 0; + else + transferLength = bt - 1; + forceLogDebug_printf("HidRead Result received immediately (error 0x%08x) Length 0x%08x", GetLastError(), transferLength); + } + else + { + // wait for result + forceLogDebug_printf("HidRead WaitForResult (error 0x%08x)", GetLastError()); + // async hid read is never supposed to return unless there is an response? Lego Dimensions stops HIDRead calls as soon as one of them fails with a non-zero error (which includes time out) + DWORD r = WaitForSingleObject(ovlp.hEvent, 2000*100); + if (r == WAIT_TIMEOUT) + { + forceLogDebug_printf("HidRead internal timeout (error 0x%08x)", GetLastError()); + // return -108 in case of timeout + free(tempBuffer); + CloseHandle(ovlp.hEvent); + return -108; + } + + + forceLogDebug_printf("HidRead WaitHalfComplete"); + GetOverlappedResult(hidDeviceInfo->hFile, &ovlp, &bt, false); + if (bt == 0) + transferLength = 0; + else + transferLength = bt - 1; + forceLogDebug_printf("HidRead WaitComplete Length: 0x%08x", transferLength); + } + sint32 returnCode = 0; + if (bt != 0) + { + memcpy(data, tempBuffer + 1, transferLength); + sint32 hidReadLength = transferLength; + + char debugOutput[1024] = { 0 }; + for (sint32 i = 0; i < transferLength; i++) + { + sprintf(debugOutput + i * 3, "%02x ", tempBuffer[1 + i]); + } + forceLogDebug_printf("HIDRead data: %s", debugOutput); + + returnCode = transferLength; + } + else + { + forceLog_printf("Failed HID read"); + returnCode = -1; + } + free(tempBuffer); + CloseHandle(ovlp.hEvent); + return returnCode; + } + + void _hidReadAsync(HIDDeviceInfo_t* hidDeviceInfo, uint8* data, sint32 maxLength, MPTR callbackFuncMPTR, MPTR callbackParamMPTR) + { + sint32 returnCode = _hidReadInternalSync(hidDeviceInfo, data, maxLength); + sint32 errorCode = 0; + if (returnCode < 0) + errorCode = returnCode; // dont return number of bytes in error code + doHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, hidDeviceInfo->handle, errorCode, memory_getVirtualOffsetFromPointer(data), (returnCode>0)?returnCode:0); + } + + sint32 _hidReadSync(HIDDeviceInfo_t* hidDeviceInfo, uint8* data, sint32 maxLength, OSThread_t* osThread) + { + sint32 returnCode = _hidReadInternalSync(hidDeviceInfo, data, maxLength); + coreinit_resumeThread(osThread, 1000); + return returnCode; + } + + void export_HIDRead(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(hidHandle, 0); // r3 + ppcDefineParamUStr(data, 1); // r4 + ppcDefineParamU32(maxLength, 2); // r5 + ppcDefineParamMPTR(callbackFuncMPTR, 3); // r6 + ppcDefineParamMPTR(callbackParamMPTR, 4); // r7 + forceLogDebug_printf("nsyshid.HIDRead(0x%x,0x%08x,0x%08x,0x%08x,0x%08x) LR %08x", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7], hCPU->spr.LR); + + HIDDeviceInfo_t* hidDeviceInfo = getHIDDeviceInfoByHandle(hidHandle, true); + if (hidDeviceInfo == nullptr) + { + forceLog_printf("nsyshid.HIDRead(): Unable to find device with hid handle %d", hidHandle); + osLib_returnFromFunction(hCPU, -1); + return; + } + sint32 returnCode = 0; + if (callbackFuncMPTR != MPTR_NULL) + { + // asynchronous transfer + std::thread(&_hidReadAsync, hidDeviceInfo, data, maxLength, callbackFuncMPTR, callbackParamMPTR).detach(); + returnCode = 0; + } + else + { + // synchronous transfer + std::future<sint32> res = std::async(std::launch::async, &_hidReadSync, hidDeviceInfo, data, maxLength, coreinitThread_getCurrentThreadDepr(hCPU)); + coreinit_suspendThread(coreinitThread_getCurrentThreadDepr(hCPU), 1000); + PPCCore_switchToScheduler(); + returnCode = res.get(); + } + + osLib_returnFromFunction(hCPU, returnCode); + } + + sint32 _hidWriteInternalSync(HIDDeviceInfo_t* hidDeviceInfo, uint8* data, sint32 maxLength) + { + DWORD bt; + OVERLAPPED ovlp = { 0 }; + ovlp.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + + uint8* tempBuffer = (uint8*)malloc(maxLength + 1); + memcpy(tempBuffer + 1, data, maxLength); + tempBuffer[0] = 0; // report byte? + + forceLogDebug_printf("HidWrite Begin (Length 0x%08x)", maxLength); + BOOL WriteResult = WriteFile(hidDeviceInfo->hFile, tempBuffer, maxLength + 1, &bt, &ovlp); + if (WriteResult != FALSE) + { + // sometimes we get the result immediately + forceLogDebug_printf("HidWrite Result received immediately (error 0x%08x) Length 0x%08x", GetLastError()); + } + else + { + // wait for result + forceLogDebug_printf("HidWrite WaitForResult (error 0x%08x)", GetLastError()); + // todo - check for error type + DWORD r = WaitForSingleObject(ovlp.hEvent, 2000); + if (r == WAIT_TIMEOUT) + { + forceLogDebug_printf("HidWrite internal timeout"); + // return -108 in case of timeout + free(tempBuffer); + CloseHandle(ovlp.hEvent); + return -108; + } + + + forceLogDebug_printf("HidWrite WaitHalfComplete"); + GetOverlappedResult(hidDeviceInfo->hFile, &ovlp, &bt, false); + forceLogDebug_printf("HidWrite WaitComplete"); + } + sint32 returnCode = 0; + if (bt != 0) + returnCode = maxLength; + else + returnCode = -1; + + free(tempBuffer); + CloseHandle(ovlp.hEvent); + return returnCode; + } + + void _hidWriteAsync(HIDDeviceInfo_t* hidDeviceInfo, uint8* data, sint32 maxLength, MPTR callbackFuncMPTR, MPTR callbackParamMPTR) + { + sint32 returnCode = _hidWriteInternalSync(hidDeviceInfo, data, maxLength); + sint32 errorCode = 0; + if (returnCode < 0) + errorCode = returnCode; // dont return number of bytes in error code + doHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, hidDeviceInfo->handle, errorCode, memory_getVirtualOffsetFromPointer(data), (returnCode > 0) ? returnCode : 0); + } + + sint32 _hidWriteSync(HIDDeviceInfo_t* hidDeviceInfo, uint8* data, sint32 maxLength, OSThread_t* osThread) + { + sint32 returnCode = _hidWriteInternalSync(hidDeviceInfo, data, maxLength); + coreinit_resumeThread(osThread, 1000); + return returnCode; + } + + void export_HIDWrite(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(hidHandle, 0); // r3 + ppcDefineParamUStr(data, 1); // r4 + ppcDefineParamU32(maxLength, 2); // r5 + ppcDefineParamMPTR(callbackFuncMPTR, 3); // r6 + ppcDefineParamMPTR(callbackParamMPTR, 4); // r7 + forceLogDebug_printf("nsyshid.HIDWrite(0x%x,0x%08x,0x%08x,0x%08x,0x%08x)", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7]); + + HIDDeviceInfo_t* hidDeviceInfo = getHIDDeviceInfoByHandle(hidHandle, true); + if (hidDeviceInfo == nullptr) + { + forceLog_printf("nsyshid.HIDWrite(): Unable to find device with hid handle %d", hidHandle); + osLib_returnFromFunction(hCPU, -1); + return; + } + sint32 returnCode = 0; + if (callbackFuncMPTR != MPTR_NULL) + { + // asynchronous transfer + std::thread(&_hidWriteAsync, hidDeviceInfo, data, maxLength, callbackFuncMPTR, callbackParamMPTR).detach(); + returnCode = 0; + } + else + { + // synchronous transfer + std::future<sint32> res = std::async(std::launch::async, &_hidWriteSync, hidDeviceInfo, data, maxLength, coreinitThread_getCurrentThreadDepr(hCPU)); + coreinit_suspendThread(coreinitThread_getCurrentThreadDepr(hCPU), 1000); + PPCCore_switchToScheduler(); + returnCode = res.get(); + } + + osLib_returnFromFunction(hCPU, returnCode); + } + + void export_HIDDecodeError(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(errorCode, 0); + ppcDefineParamTypePtr(ukn0, uint32be, 1); + ppcDefineParamTypePtr(ukn1, uint32be, 2); + forceLogDebug_printf("nsyshid.HIDDecodeError(0x%08x,0x%08x,0x%08x)", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); + + // todo + *ukn0 = 0x3FF; + *ukn1 = (uint32)-0x7FFF; + + osLib_returnFromFunction(hCPU, 0); + } + + void load() + { + osLib_addFunction("nsyshid", "HIDAddClient", export_HIDAddClient); + osLib_addFunction("nsyshid", "HIDDelClient", export_HIDDelClient); + osLib_addFunction("nsyshid", "HIDGetDescriptor", export_HIDGetDescriptor); + osLib_addFunction("nsyshid", "HIDSetIdle", export_HIDSetIdle); + osLib_addFunction("nsyshid", "HIDSetProtocol", export_HIDSetProtocol); + osLib_addFunction("nsyshid", "HIDSetReport", export_HIDSetReport); + + osLib_addFunction("nsyshid", "HIDRead", export_HIDRead); + osLib_addFunction("nsyshid", "HIDWrite", export_HIDWrite); + + osLib_addFunction("nsyshid", "HIDDecodeError", export_HIDDecodeError); + firstHIDClient = nullptr; + } +} + +#else + +namespace nsyshid +{ + void load() + { + // unimplemented + }; +}; + + +#endif \ No newline at end of file diff --git a/src/Cafe/OS/libs/nsyshid/nsyshid.h b/src/Cafe/OS/libs/nsyshid/nsyshid.h new file mode 100644 index 00000000..051b4e7c --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/nsyshid.h @@ -0,0 +1,5 @@ +#pragma once +namespace nsyshid +{ + void load(); +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nsyskbd/nsyskbd.cpp b/src/Cafe/OS/libs/nsyskbd/nsyskbd.cpp new file mode 100644 index 00000000..fc1d5ad8 --- /dev/null +++ b/src/Cafe/OS/libs/nsyskbd/nsyskbd.cpp @@ -0,0 +1,23 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/libs/nn_common.h" + +namespace nsyskbd +{ + uint32 KBDGetChannelStatus(uint32 channel, uint32be* status) + { + static bool loggedError = false; + if (loggedError == false) + { + forceLogDebug_printf("KBDGetChannelStatus() placeholder"); + loggedError = true; + } + *status = 1; // disconnected + + return 0; + } + + void nsyskbd_load() + { + cafeExportRegister("nsyskbd", KBDGetChannelStatus, LogType::Placeholder); + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nsyskbd/nsyskbd.h b/src/Cafe/OS/libs/nsyskbd/nsyskbd.h new file mode 100644 index 00000000..8e384b59 --- /dev/null +++ b/src/Cafe/OS/libs/nsyskbd/nsyskbd.h @@ -0,0 +1,5 @@ +namespace nsyskbd +{ + void nsyskbd_load(); +} + diff --git a/src/Cafe/OS/libs/nsysnet/nsysnet.cpp b/src/Cafe/OS/libs/nsysnet/nsysnet.cpp new file mode 100644 index 00000000..0a01ca3e --- /dev/null +++ b/src/Cafe/OS/libs/nsysnet/nsysnet.cpp @@ -0,0 +1,2093 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "nsysnet.h" +#include "Cafe/OS/libs/coreinit/coreinit_Thread.h" +#include "Cafe/IOSU/legacy/iosu_crypto.h" +#include "Cafe/OS/libs/coreinit/coreinit_Time.h" + +#include "Common/socket.h" + +#if BOOST_OS_WINDOWS > 0 + +#define WU_AF_INET 2 + +#define WU_SOCK_STREAM 1 +#define WU_SOCK_DGRAM 2 + +#define WU_IPPROTO_IP 0 +#define WU_IPPROTO_TCP 6 +#define WU_IPPROTO_UDP 17 + +#define WU_SO_REUSEADDR 0x0004 +#define WU_SO_KEEPALIVE 0x0008 +#define WU_SO_WINSCALE 0x0400 +#define WU_SO_SNDBUF 0x1001 +#define WU_SO_RCVBUF 0x1002 +#define WU_SO_LASTERROR 0x1007 +#define WU_SO_NBIO 0x1014 +#define WU_SO_NONBLOCK 0x1016 + +#define WU_TCP_NODELAY 0x2004 + +#define WU_SOL_SOCKET -1 // this constant differs from Win32 socket API + +#define WU_MSG_PEEK 0x02 +#define WU_MSG_DONTWAIT 0x20 + +// error codes +#define WU_SO_SUCCESS 0x0000 +#define WU_SO_EWOULDBLOCK 0x0006 +#define WU_SO_ECONNRESET 0x0008 +#define WU_SO_EINVAL 0x000B +#define WU_SO_EINPROGRESS 0x0016 + +#define WU_SO_ESHUTDOWN 0x000F + + +typedef signed int WUSOCKET; + +bool sockLibReady = false; + +void nsysnetExport_socket_lib_init(PPCInterpreter_t* hCPU) +{ + sockLibReady = true; + WSADATA wsa; + WSAStartup(MAKEWORD(2, 2), &wsa); + osLib_returnFromFunction(hCPU, 0); // 0 -> Success +} + +uint32* __gh_errno_ptr() +{ + OSThread_t* osThread = coreinitThread_getCurrentThreadDepr(PPCInterpreter_getCurrentInstance()); + return &osThread->context.error; +} + +void _setSockError(sint32 errCode) +{ + // todo -> Call __gh_errno_ptr and then write 32bit error code + *(uint32*)__gh_errno_ptr() = _swapEndianU32(errCode); + //coreinitData->ghsErrno = _swapEndianU32(errCode); +} + +sint32 _getSockError() +{ + return (sint32)_swapEndianU32(*(uint32*)__gh_errno_ptr()); + //return (sint32)_swapEndianU32(coreinitData->ghsErrno); +} + +// error translation modes for _translateError +#define _ERROR_MODE_DEFAULT 0 +#define _ERROR_MODE_CONNECT 1 +#define _ERROR_MODE_ACCEPT 2 + +sint32 _translateError(sint32 returnCode, sint32 wsaError, sint32 mode = _ERROR_MODE_DEFAULT) +{ + if (mode == _ERROR_MODE_ACCEPT) + { + // accept mode + if (returnCode >= 0) + { + _setSockError(WU_SO_SUCCESS); + return returnCode; + } + } + else + { + // any other mode + if (returnCode == 0) + { + _setSockError(WU_SO_SUCCESS); + return 0; + } + } + // handle WSA error + switch (wsaError) + { + case 0: + _setSockError(WU_SO_SUCCESS); + break; + case WSAEWOULDBLOCK: + if( mode == _ERROR_MODE_CONNECT ) + _setSockError(WU_SO_EINPROGRESS); + else + _setSockError(WU_SO_EWOULDBLOCK); + break; + case WSAEINPROGRESS: + _setSockError(WU_SO_EINPROGRESS); + break; + case WSAECONNABORTED: + debug_printf("WSAECONNABORTED\n"); +#ifndef PUBLIC_RELEASE + assert_dbg(); +#endif + break; + case WSAESHUTDOWN: + _setSockError(WU_SO_ESHUTDOWN); + break; + default: + cemuLog_logDebug(LogType::Force, "Unhandled wsaError {}\n", wsaError); + _setSockError(99999); // unhandled error + } + return -1; +} + +void nsysnetExport_socketlasterr(PPCInterpreter_t* hCPU) +{ + socketLog_printf("socketlasterr() -> %d", _getSockError()); + osLib_returnFromFunction(hCPU, _getSockError()); +} + +typedef struct +{ + uint32 handle; + bool isShutdownRecv; + bool isShutdownSend; + // socket creation info + sint32 family; + sint32 type; + sint32 protocol; + // host side info + SOCKET s; + // socket options + bool isNonBlocking; +}virtualSocket_t; + +typedef struct +{ + uint32 wu_s_addr; +}wu_in_addr; + +struct wu_sockaddr +{ + uint16 sa_family; + uint8 sa_data[14]; // IPv4 +}; + +void sockaddr_guest2host(wu_sockaddr* input, sockaddr* output) +{ + output->sa_family = _swapEndianU16(input->sa_family); + memcpy(output->sa_data, input->sa_data, 14); +} + +void sockaddr_host2guest(sockaddr* input, wu_sockaddr* output) +{ + output->sa_family = _swapEndianU16(input->sa_family); + memcpy(output->sa_data, input->sa_data, 14); +} + +struct wu_addrinfo +{ + sint32 ai_flags; + sint32 ai_family; + sint32 ai_socktype; + sint32 ai_protocol; + sint32 ai_addrlen; + MPTR ai_canonname; + MPTR ai_addr; + MPTR ai_next; +}; + +#define WU_SOCKET_LIMIT (32) // only 32 socket handles are supported per running process + +virtualSocket_t* virtualSocketTable[WU_SOCKET_LIMIT] = { 0 }; + +sint32 _getFreeSocketHandle() +{ + for (sint32 i = 0; i < WU_SOCKET_LIMIT; i++) + { + if (virtualSocketTable[i] == NULL) + return i + 1; + } + return 0; +} + +#define SIO_UDP_CONNRESET _WSAIOW(IOC_VENDOR,12) + +WUSOCKET nsysnet_createVirtualSocket(sint32 family, sint32 type, sint32 protocol) +{ + sint32 s = _getFreeSocketHandle(); + if (s == 0) + { + forceLogDebug_printf("Ran out of socket handles"); + cemu_assert(false); + } + virtualSocket_t* vs = (virtualSocket_t*)malloc(sizeof(virtualSocket_t)); + memset(vs, 0, sizeof(virtualSocket_t)); + vs->family = family; + vs->type = type; + vs->protocol = protocol; + vs->handle = s; + virtualSocketTable[s - 1] = vs; + // init host socket + vs->s = socket(family, type, protocol); + // disable reporting of PORT_UNREACHABLE for UDP sockets + if (protocol == IPPROTO_UDP) + { + BOOL bNewBehavior = FALSE; + DWORD dwBytesReturned = 0; + WSAIoctl(vs->s, SIO_UDP_CONNRESET, &bNewBehavior, sizeof bNewBehavior, NULL, 0, &dwBytesReturned, NULL, NULL); + } + return vs->handle; +} + +WUSOCKET nsysnet_createVirtualSocketFromExistingSocket(SOCKET existingSocket) +{ + forceLogDebug_printf("nsysnet_createVirtualSocketFromExistingSocket - incomplete"); + // SO_TYPE -> type + // SO_BSP_STATE -> protocol + other info + // SO_PROTOCOL_INFO -> protocol + type? + + WSAPROTOCOL_INFO protocolInfo = { 0 }; + int optLen = sizeof(protocolInfo); + getsockopt(existingSocket, SOL_SOCKET, SO_PROTOCOL_INFO, (char*)&protocolInfo, &optLen); + + // todo - translate protocolInfo + + sint32 s = _getFreeSocketHandle(); + if (s == 0) + { + forceLogDebug_printf("Ran out of socket handles"); + cemu_assert(false); + } + virtualSocket_t* vs = (virtualSocket_t*)malloc(sizeof(virtualSocket_t)); + memset(vs, 0, sizeof(virtualSocket_t)); + vs->family = protocolInfo.iAddressFamily; + vs->type = protocolInfo.iSocketType; + vs->protocol = protocolInfo.iSocketType; + vs->handle = s; + virtualSocketTable[s - 1] = vs; + vs->s = existingSocket; + return vs->handle; +} + +void nsysnet_notifyCloseSharedSocket(SOCKET existingSocket) +{ + for (sint32 i = 0; i < WU_SOCKET_LIMIT; i++) + { + if (virtualSocketTable[i] && virtualSocketTable[i]->s == existingSocket) + { + // remove entry + free(virtualSocketTable[i]); + virtualSocketTable[i] = nullptr; + return; + } + } +} + +virtualSocket_t* nsysnet_getVirtualSocketObject(WUSOCKET s) +{ + uint8 handleType = 0; + s--; + if (s < 0 || s >= WU_SOCKET_LIMIT) + return NULL; + return virtualSocketTable[s]; +} + +sint32 nsysnet_getVirtualSocketHandleFromHostHandle(SOCKET s) +{ + for (sint32 i = 0; i < WU_SOCKET_LIMIT; i++) + { + if (virtualSocketTable[i] && virtualSocketTable[i]->s == s) + return i + 1; + } + return -1; +} + +void nsysnetExport_socket(PPCInterpreter_t* hCPU) +{ + socketLog_printf("socket(%d,%d,%d)", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); + ppcDefineParamS32(family, 0); + ppcDefineParamS32(type, 1); + ppcDefineParamS32(protocol, 2); + + if (sockLibReady == false) + { + _setSockError(0x2B); + osLib_returnFromFunction(hCPU, -1); + return; + } + // below here Ioctl code should start + + // check family param + if (family != WU_AF_INET) + { + forceLogDebug_printf("socket(): Unsupported family"); + // todo - error code + osLib_returnFromFunction(hCPU, -1); + return; + } + // check type param + if (type != WU_SOCK_STREAM && type != WU_SOCK_DGRAM) + { + forceLogDebug_printf("socket(): Unsupported family"); + // todo - error code + osLib_returnFromFunction(hCPU, -1); + return; + } + + if (protocol != WU_IPPROTO_TCP && protocol != WU_IPPROTO_UDP && protocol != WU_IPPROTO_IP) + { + forceLogDebug_printf("socket(): Unsupported protocol"); + // todo - error code + osLib_returnFromFunction(hCPU, -1); + return; + } + + WUSOCKET s = nsysnet_createVirtualSocket(family, type, protocol); + socketLog_printf("Created socket handle %d", s); + osLib_returnFromFunction(hCPU, s); +} + +void nsysnetExport_mw_socket(PPCInterpreter_t* hCPU) +{ + socketLog_printf("mw_socket"); + nsysnetExport_socket(hCPU); +} + +void nsysnetExport_shutdown(PPCInterpreter_t* hCPU) +{ + socketLog_printf("shutdown(%d,%d)", hCPU->gpr[3], hCPU->gpr[4]); + ppcDefineParamS32(s, 0); + ppcDefineParamS32(how, 1); + + sint32 r = 0; + virtualSocket_t* vs = nsysnet_getVirtualSocketObject(s); + if (vs == NULL) + { + assert_dbg(); + } + else + { + r = shutdown(vs->s, how); + if (how == 0) + { + // shutdown recv + vs->isShutdownRecv = true; + } + else if (how == 1) + { + // shutdown send + vs->isShutdownSend = true; + } + else if (how == 2) + { + // shutdown recv & send + vs->isShutdownRecv = true; + vs->isShutdownSend = true; + } + else + assert_dbg(); + } + _setSockError(0); // todo + osLib_returnFromFunction(hCPU, r); +} + +void nsysnetExport_socketclose(PPCInterpreter_t* hCPU) +{ + socketLog_printf("socketclose(%d)", hCPU->gpr[3]); + ppcDefineParamS32(s, 0); + + virtualSocket_t* vs = nsysnet_getVirtualSocketObject(s); + if (vs) + { + closesocket(vs->s); + free(vs); + virtualSocketTable[s - 1] = NULL; + } + osLib_returnFromFunction(hCPU, 0); +} + +void nsysnetExport_setsockopt(PPCInterpreter_t* hCPU) +{ + socketLog_printf("setsockopt(%d,0x%x,0x%05x,0x%08x,%d)", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7]); + ppcDefineParamS32(s, 0); + ppcDefineParamS32(level, 1); + ppcDefineParamS32(optname, 2); + ppcDefineParamStr(optval, 3); + ppcDefineParamS32(optlen, 4); + + virtualSocket_t* vs = nsysnet_getVirtualSocketObject(s); + if (!vs) + { + cemu_assert_suspicious(); + } + else + { + // translate level to host API + sint32 hostLevel; + if (level == WU_SOL_SOCKET) + { + hostLevel = SOL_SOCKET; + // handle op + if (optname == WU_SO_REUSEADDR) + { + if (optlen != 4) + cemu_assert_suspicious(); + sint32 optvalLE = _swapEndianU32(*(uint32*)optval); + sint32 r = setsockopt(vs->s, hostLevel, SO_REUSEADDR, (char*)&optvalLE, 4); + if (r != 0) + cemu_assert_suspicious(); + } + else if (optname == WU_SO_NBIO) + { + // similar to WU_SO_NONBLOCK but always sets non-blocking mode regardless of option value + if (optlen == 4) + { + sint32 optvalLE = _swapEndianU32(*(uint32*)optval); + } + else if (optlen == 0) + { + // no opt needed + } + else + cemu_assert_suspicious(); + u_long mode = 1; + ioctlsocket(vs->s, FIONBIO, &mode); + vs->isNonBlocking = true; + } + else if (optname == WU_SO_NONBLOCK) + { + if (optlen != 4) + assert_dbg(); + sint32 optvalLE = _swapEndianU32(*(uint32*)optval); + u_long mode = optvalLE; // 1 -> enable, 0 -> disable + ioctlsocket(vs->s, FIONBIO, &mode); + vs->isNonBlocking = mode != 0; + } + else if (optname == WU_SO_KEEPALIVE) + { + // todo + } + else if (optname == WU_SO_WINSCALE) + { + // todo + } + else if (optname == WU_SO_RCVBUF) + { + socketLog_printf("Set receive buffer size to 0x%08x", _swapEndianU32(*(uint32*)optval)); + if (optlen != 4) + assert_dbg(); + sint32 optvalLE = _swapEndianU32(*(uint32*)optval); + u_long mode = optvalLE; + if (setsockopt(vs->s, SOL_SOCKET, SO_RCVBUF, (const char*)&mode, sizeof(u_long)) != 0) + assert_dbg(); + } + else if (optname == WU_SO_SNDBUF) + { + socketLog_printf("Set send buffer size to 0x%08x", _swapEndianU32(*(uint32*)optval)); + if (optlen != 4) + assert_dbg(); + sint32 optvalLE = _swapEndianU32(*(uint32*)optval); + u_long mode = optvalLE; + if (setsockopt(vs->s, SOL_SOCKET, SO_SNDBUF, (const char*)&mode, sizeof(u_long)) != 0) + assert_dbg(); + } + else + { + forceLogDebug_printf("setsockopt(): Unsupported optname 0x%08x", optname); + } + } + else if (level == WU_IPPROTO_TCP) + { + hostLevel = IPPROTO_TCP; + if (optname == WU_TCP_NODELAY) + { + if (optlen != 4) + assert_dbg(); + sint32 optvalLE = _swapEndianU32(*(uint32*)optval); + u_long mode = optvalLE; // 1 -> enable, 0 -> disable + if (setsockopt(vs->s, IPPROTO_TCP, TCP_NODELAY, (const char*)&mode, sizeof(u_long)) != 0) + assert_dbg(); + } + else + assert_dbg(); + } + else if (level == WU_IPPROTO_IP) + { + hostLevel = IPPROTO_IP; + if (optname == 0xC) + { + // unknown + } + else if( optname == 0x4 ) + { + forceLogDebug_printf("setsockopt with unsupported opname 4 for IPPROTO_IP"); + } + else + assert_dbg(); + + } + else + assert_dbg(); + } + osLib_returnFromFunction(hCPU, 0); +} + +void nsysnetExport_getsockopt(PPCInterpreter_t* hCPU) +{ + socketLog_printf("getsockopt(%d,0x%x,0x%05x,0x%08x,0x%08x)", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7]); + ppcDefineParamS32(s, 0); + ppcDefineParamS32(level, 1); + ppcDefineParamS32(optname, 2); + ppcDefineParamStr(optval, 3); + ppcDefineParamMPTR(optlenMPTR, 4); + + virtualSocket_t* vs = nsysnet_getVirtualSocketObject(s); + if (vs == NULL) + { + cemu_assert_debug(false); // todo + return; + } + + sint32 r = 0; + + // translate level to host API + sint32 hostLevel; + if (level == WU_SOL_SOCKET) + { + hostLevel = SOL_SOCKET; + // handle op + if (optname == WU_SO_LASTERROR) + { + int optvalLE = 0; + int optlenLE = 4; + r = getsockopt(vs->s, hostLevel, SO_ERROR, (char*)&optvalLE, &optlenLE); + r = _translateError(r, WSAGetLastError()); + if (memory_readU32(optlenMPTR) != 4) + assert_dbg(); + memory_writeU32(optlenMPTR, 4); + if (optvalLE != 0) + assert_dbg(); // todo -> Translate error code/status + *(uint32*)optval = _swapEndianU32(optvalLE); + // YouTube app uses this to check if the connection attempt was successful after non-blocking connect() + } + else if (optname == WU_SO_RCVBUF) + { + int optvalLE = 0; + int optlenLE = 4; + r = getsockopt(vs->s, hostLevel, SO_RCVBUF, (char*)&optvalLE, &optlenLE); + r = _translateError(r, WSAGetLastError()); + if (memory_readU32(optlenMPTR) != 4) + assert_dbg(); + memory_writeU32(optlenMPTR, 4); + *(uint32*)optval = _swapEndianU32(optvalLE); + // used by Amazon Video app when a video starts playing + } + else if (optname == WU_SO_SNDBUF) + { + int optvalLE = 0; + int optlenLE = 4; + r = getsockopt(vs->s, hostLevel, SO_SNDBUF, (char*)&optvalLE, &optlenLE); + r = _translateError(r, WSAGetLastError()); + if (memory_readU32(optlenMPTR) != 4) + assert_dbg(); + memory_writeU32(optlenMPTR, 4); + *(uint32*)optval = _swapEndianU32(optvalLE); + // used by Lost Reavers after some loading screens + } + else + { + cemu_assert_debug(false); + } + } + else + { + cemu_assert_debug(false); + } + + osLib_returnFromFunction(hCPU, r); +} + +void nsysnetExport_inet_aton(PPCInterpreter_t* hCPU) +{ + ppcDefineParamStr(ip, 0); + ppcDefineParamStructPtr(addr, wu_in_addr, 1); + socketLog_printf("inet_aton(\"%s\",0x%08x)", ip, hCPU->gpr[4]); + + // _parse_ipad -> todo + sint32 d0, d1, d2, d3; + if (sscanf(ip, "%d.%d.%d.%d", &d0, &d1, &d2, &d3) != 4) + { + cemu_assert_debug(false); + osLib_returnFromFunction(hCPU, 0); // todo - return correct error code + return; + } + + if (d0 < 0 || d0 > 255 || + d1 < 0 || d1 > 255 || + d2 < 0 || d2 > 255 || + d3 < 0 || d3 > 255) + { + cemu_assert_debug(false); + osLib_returnFromFunction(hCPU, 0); + return; + } + + addr->wu_s_addr = _swapEndianU32((d0 << 24) | (d1 << 16) | (d2 << 8) | (d3 << 0)); + + osLib_returnFromFunction(hCPU, 1); // 0 -> error, 1 -> success +} + +void nsysnetExport_inet_pton(PPCInterpreter_t* hCPU) +{ + ppcDefineParamS32(af, 0); + ppcDefineParamStr(ip, 1); + ppcDefineParamStructPtr(addr, wu_in_addr, 2); + + if (af != 2) + { + forceLog_printf("inet_pton() only supports AF_INET"); + osLib_returnFromFunction(hCPU, 0); + return; + } + + sint32 d0, d1, d2, d3; + bool invalidIp = false; + if (sscanf(ip, "%d.%d.%d.%d", &d0, &d1, &d2, &d3) != 4) + invalidIp = true; + if (d0 < 0 || d0 > 255) + invalidIp = true; + if (d1 < 0 || d1 > 255) + invalidIp = true; + if (d2 < 0 || d2 > 255) + invalidIp = true; + if (d3 < 0 || d3 > 255) + invalidIp = true; +#ifndef PUBLIC_RELEASE + if (invalidIp) + assert_dbg(); +#endif + if (invalidIp) + { + socketLog_printf("inet_pton(%d, \"%s\", 0x%08x) -> Invalid ip", af, ip, hCPU->gpr[5]); + osLib_returnFromFunction(hCPU, 0); // 0 -> invalid address + return; + } + + addr->wu_s_addr = _swapEndianU32((d0 << 24) | (d1 << 16) | (d2 << 8) | (d3 << 0)); + socketLog_printf("inet_pton(%d, \"%s\", 0x%08x) -> Ok", af, ip, hCPU->gpr[5]); + + osLib_returnFromFunction(hCPU, 1); // 1 -> success +} + +MEMPTR<char> _ntoa_tempString = nullptr; + +void nsysnetExport_inet_ntoa(PPCInterpreter_t* hCPU) +{ + ppcDefineParamStructPtr(addr, wu_in_addr, 0); + socketLog_printf("inet_ntoa(0x%08x)", hCPU->gpr[3]); + + if (_ntoa_tempString == nullptr) + _ntoa_tempString = (char*)memory_getPointerFromVirtualOffset(OSAllocFromSystem(64, 4)); + + sprintf(_ntoa_tempString.GetPtr(), "%d.%d.%d.%d", ((uint8*)addr)[0], ((uint8*)addr)[1], ((uint8*)addr)[2], ((uint8*)addr)[3]); + + osLib_returnFromFunction(hCPU, _ntoa_tempString.GetMPTR()); +} + +void nsysnetExport_htons(PPCInterpreter_t* hCPU) +{ + socketLog_printf("htons(0x%04x)", hCPU->gpr[3]); + ppcDefineParamU32(v, 0); + osLib_returnFromFunction(hCPU, v); // return value as-is +} + +void nsysnetExport_htonl(PPCInterpreter_t* hCPU) +{ + socketLog_printf("htonl(0x%08x)", hCPU->gpr[3]); + ppcDefineParamU32(v, 0); + osLib_returnFromFunction(hCPU, v); // return value as-is +} + +void nsysnetExport_ntohs(PPCInterpreter_t* hCPU) +{ + socketLog_printf("ntohs(0x%04x)", hCPU->gpr[3]); + ppcDefineParamU32(v, 0); + osLib_returnFromFunction(hCPU, v); // return value as-is +} + +void nsysnetExport_ntohl(PPCInterpreter_t* hCPU) +{ + socketLog_printf("ntohl(0x%08x)", hCPU->gpr[3]); + ppcDefineParamU32(v, 0); + osLib_returnFromFunction(hCPU, v); // return value as-is +} + +void nsysnetExport_bind(PPCInterpreter_t* hCPU) +{ + socketLog_printf("bind(%d,0x%08x,%d)", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); + ppcDefineParamS32(s, 0); + ppcDefineParamStructPtr(addr, struct wu_sockaddr, 1); + ppcDefineParamS32(len, 2); + + + if (len != sizeof(struct wu_sockaddr)) + assert_dbg(); + + virtualSocket_t* vs = nsysnet_getVirtualSocketObject(s); + sint32 r = 0; + if (vs == NULL) + { + assert_dbg(); + } + else + { + if (sizeof(sockaddr) != 16) + assert_dbg(); + sockaddr hostAddr; + hostAddr.sa_family = _swapEndianU16(addr->sa_family); + memcpy(hostAddr.sa_data, addr->sa_data, 14); + sint32 hr = bind(vs->s, &hostAddr, sizeof(sockaddr)); + r = _translateError(hr, WSAGetLastError()); + + + socketLog_printf("bind address: %d.%d.%d.%d:%d result: %d", addr->sa_data[2], addr->sa_data[3], addr->sa_data[4], addr->sa_data[5], _swapEndianU16(*(uint16*)addr->sa_data), hr); + + } + + osLib_returnFromFunction(hCPU, r); +} + +void nsysnetExport_listen(PPCInterpreter_t* hCPU) +{ + socketLog_printf("listen(%d,%d)", hCPU->gpr[3], hCPU->gpr[4]); + ppcDefineParamS32(s, 0); + ppcDefineParamS32(queueSize, 1); + + virtualSocket_t* vs = nsysnet_getVirtualSocketObject(s); + sint32 r = 0; + if (vs == NULL) + { + assert_dbg(); + } + else + { + sint32 hr = listen(vs->s, queueSize); + if (hr != 0) + r = -1; + // todo: Set proper coreinit errno (via _setSockError) + } + + osLib_returnFromFunction(hCPU, r); +} + +void nsysnetExport_accept(PPCInterpreter_t* hCPU) +{ + socketLog_printf("accept(%d,0x%08x,0x%08x)", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); + ppcDefineParamS32(s, 0); + ppcDefineParamStructPtr(addr, struct wu_sockaddr, 1); + ppcDefineParamMPTR(lenMPTR, 2); + + sint32 r = 0; + virtualSocket_t* vs = nsysnet_getVirtualSocketObject(s); + if (vs == NULL) + { + assert_dbg(); + // todo + return; + } + + if (memory_readU32(lenMPTR) != 16) + { + forceLog_printf("invalid sockaddr len in accept()"); + cemu_assert_debug(false); + osLib_returnFromFunction(hCPU, 0); + return; + } + + if (vs->isNonBlocking) + { + sockaddr hostAddr; + int hostLen = sizeof(sockaddr); + SOCKET hr = accept(vs->s, &hostAddr, &hostLen); + if (hr != SOCKET_ERROR) + { + r = nsysnet_createVirtualSocketFromExistingSocket(hr); + _setSockError(WU_SO_SUCCESS); + } + else + { + r = _translateError((sint32)hr, (sint32)WSAGetLastError(), _ERROR_MODE_ACCEPT); + } + sockaddr_host2guest(&hostAddr, addr); + } + else + { + // blocking accept is not supported yet + forceLog_printf("blocking accept() not supported"); + cemu_assert_debug(false); + } + + osLib_returnFromFunction(hCPU, r); +} + + +void nsysnetExport_connect(PPCInterpreter_t* hCPU) +{ + socketLog_printf("connect(%d,0x%08x,%d)", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); + ppcDefineParamS32(s, 0); + ppcDefineParamStructPtr(addr, struct wu_sockaddr, 1); + ppcDefineParamS32(len, 2); + + virtualSocket_t* vs = nsysnet_getVirtualSocketObject(s); + sint32 r = 0; + if (vs == NULL) + { + assert_dbg(); + return; + } + + if (sizeof(sockaddr) != 16) + assert_dbg(); + sockaddr hostAddr; + hostAddr.sa_family = _swapEndianU16(addr->sa_family); + memcpy(hostAddr.sa_data, addr->sa_data, 14); + sint32 hr = connect(vs->s, &hostAddr, sizeof(sockaddr)); + forceLog_printf("Attempt connect to %d.%d.%d.%d:%d", (sint32)(uint8)hostAddr.sa_data[2], (sint32)(uint8)hostAddr.sa_data[3], (sint32)(uint8)hostAddr.sa_data[4], (sint32)(uint8)hostAddr.sa_data[5], _swapEndianU16(*(uint16*)hostAddr.sa_data+0)); + + r = _translateError(hr, WSAGetLastError(), _ERROR_MODE_CONNECT); + + osLib_returnFromFunction(hCPU, r); +} + +void _setSocketSendRecvNonBlockingMode(SOCKET s, bool isNonBlocking) +{ + u_long mode = isNonBlocking ? 1 : 0; + sint32 r = ioctlsocket(s, FIONBIO, &mode); +} + +void nsysnetExport_send(PPCInterpreter_t* hCPU) +{ + socketLog_printf("send(%d,0x%08x,%d,0x%x)", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6]); + ppcDefineParamS32(s, 0); + ppcDefineParamStr(msg, 1); + ppcDefineParamS32(len, 2); + ppcDefineParamS32(flags, 3); + + virtualSocket_t* vs = nsysnet_getVirtualSocketObject(s); + sint32 r = 0; + if (vs == NULL) + { + assert_dbg(); + return; + } + int hostFlags = 0; + bool requestIsNonBlocking = (flags&WU_MSG_DONTWAIT) != 0; + flags &= ~WU_MSG_DONTWAIT; + if (requestIsNonBlocking != vs->isNonBlocking && vs->isNonBlocking == false) + assert_dbg(); + + if (flags) + assert_dbg(); + + sint32 hr = send(vs->s, msg, len, hostFlags); + socketLog_printf("Sent %d bytes", hr); + _translateError(hr <= 0 ? -1 : 0, WSAGetLastError()); + r = hr; + + osLib_returnFromFunction(hCPU, r); +} + +void nsysnetExport_recv(PPCInterpreter_t* hCPU) +{ + socketLog_printf("recv(%d,0x%08x,%d,0x%x) LR: 0x%08x Thread: 0x%08x", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->spr.LR, coreinitThread_getCurrentThreadMPTRDepr(hCPU)); + ppcDefineParamS32(s, 0); + ppcDefineParamStr(msg, 1); + ppcDefineParamS32(len, 2); + ppcDefineParamS32(flags, 3); + + virtualSocket_t* vs = nsysnet_getVirtualSocketObject(s); + sint32 r = 0; + if (vs == NULL) + { + assert_dbg(); + return; + } + int hostFlags = 0; + bool requestIsNonBlocking = (flags&WU_MSG_DONTWAIT) != 0; + flags &= ~WU_MSG_DONTWAIT; + if (flags&WU_MSG_PEEK) + { + hostFlags |= MSG_PEEK; + flags &= ~WU_MSG_PEEK; + } + + if (vs->isNonBlocking) + requestIsNonBlocking = vs->isNonBlocking; // non-blocking sockets always operate in MSG_DONTWAIT mode? + + if (flags) + assert_dbg(); + if (requestIsNonBlocking != vs->isNonBlocking) + _setSocketSendRecvNonBlockingMode(vs->s, requestIsNonBlocking); + // if blocking, yield thread until there is an error or at least 1 byte was received + if (requestIsNonBlocking == false) + { + _setSocketSendRecvNonBlockingMode(vs->s, true); + while (true) + { + char tempBuffer[1]; + sint32 tr = recv(vs->s, tempBuffer, 1, MSG_PEEK); + if (tr == 1) + break; + if (tr == 0) + break; // connection closed + if (tr < 0 && WSAGetLastError() != WSAEWOULDBLOCK) + break; + // yield thread + coreinit::OSSleepTicks(coreinit::EspressoTime::GetTimerClock() / 5000); // let thread wait 0.2ms to give other threads CPU time + // todo - eventually we should find a way to asynchronously signal recv instead of busy-looping here + } + _setSocketSendRecvNonBlockingMode(vs->s, requestIsNonBlocking); + } + // receive + sint32 hr = recv(vs->s, msg, len, hostFlags); + _translateError(hr <= 0 ? -1 : 0, WSAGetLastError()); + if (requestIsNonBlocking != vs->isNonBlocking) + _setSocketSendRecvNonBlockingMode(vs->s, vs->isNonBlocking); + socketLog_printf("Received %d bytes", hr); + r = hr; + + osLib_returnFromFunction(hCPU, r); +} + +struct wu_timeval +{ + uint32 tv_sec; + uint32 tv_usec; +}; + +void _translateFDSet(fd_set* hostSet, struct wu_fd_set* fdset, sint32 nfds) +{ + hostSet->fd_count = 0; + if (fdset == NULL) + return; + uint32 mask = fdset->mask; + for (sint32 i = 0; i < nfds; i++) + { + if( ((mask>>i)&1) == 0 ) + continue; + sint32 socketHandle = i; + virtualSocket_t* vs = nsysnet_getVirtualSocketObject(socketHandle); + if(vs == NULL) + continue; // socket invalid + hostSet->fd_array[hostSet->fd_count] = vs->s; + hostSet->fd_count++; + } +} + +void _translateFDSetRev(struct wu_fd_set* fdset, fd_set* hostSet, sint32 nfds) +{ + if (fdset == NULL) + return; + uint32 mask = _swapEndianU32(0); + for (sint32 i = 0; i < (sint32)hostSet->fd_count; i++) + { + sint32 virtualSocketHandle = nsysnet_getVirtualSocketHandleFromHostHandle(hostSet->fd_array[i]); + if (virtualSocketHandle < 0) + cemu_assert_debug(false); + mask |= (1<<virtualSocketHandle); + } + fdset->mask = mask; +} + +void nsysnetExport_select(PPCInterpreter_t* hCPU) +{ + socketLog_printf("select(%d,0x%08x,0x%08x,0x%08x,0x%08x) LR 0x%08x Thread 0x%08x", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7], hCPU->spr.LR, coreinitThread_getCurrentThreadMPTRDepr(hCPU)); + ppcDefineParamS32(nfds, 0); + ppcDefineParamStructPtr(readfds, struct wu_fd_set, 1); + ppcDefineParamStructPtr(writefds, struct wu_fd_set, 2); + ppcDefineParamStructPtr(exceptfds, struct wu_fd_set, 3); + ppcDefineParamStructPtr(timeOut, struct wu_timeval, 4); + + //socketLog_printf("rm %08x wm %08x em %08x", readfds ? _swapEndianU32(readfds->mask) : 0, writefds ? _swapEndianU32(writefds->mask) : 0, exceptfds ? _swapEndianU32(exceptfds->mask) : 0); + + sint32 r = 0; + + // translate fd_set + fd_set _readfds, _writefds, _exceptfds; + + // handle empty select + if ((readfds == NULL || readfds->mask == 0) && (writefds == NULL || writefds->mask == 0) && (exceptfds == NULL || exceptfds->mask == 0)) + { + if (timeOut == NULL || (timeOut->tv_sec == 0 && timeOut->tv_usec == 0)) + { + // return immediately + socketLog_printf("select returned immediately because of empty fdsets without timeout"); + osLib_returnFromFunction(hCPU, 0); + return; + } + else + { + //// empty select with timeout is not allowed + //_setSockError(WU_SO_EINVAL); + //osLib_returnFromFunction(hCPU, -1); + //socketLog_printf("select returned SO_EINVAL because of empty fdsets with timeout"); + + // when fd sets are empty but timeout is set, then just wait and do nothing? + // Lost Reavers seems to expect this case to return 0 (it hardcodes empty fd sets and timeout comes from curl_multi_timeout) + + //_setSockError(WU_SO_EINVAL); + // todo - sleep here + socketLog_printf("select returned 0 because of empty fdsets with timeout"); + osLib_returnFromFunction(hCPU, 0); + + return; + } + } + + + + timeval tv = { 0 }; + + uint64 msTimeout = (_swapEndianU32(timeOut->tv_usec) / 1000) + (_swapEndianU32(timeOut->tv_sec) * 1000); + uint32 startTime = GetTickCount(); + while (true) + { + _translateFDSet(&_readfds, readfds, nfds); + _translateFDSet(&_writefds, writefds, nfds); + _translateFDSet(&_exceptfds, exceptfds, nfds); + r = select(0, readfds ? &_readfds : NULL, writefds ? &_writefds : NULL, exceptfds ? &_exceptfds : NULL, &tv); + if (r < 0) + { + forceLogDebug_printf("select() failed"); + // timeout + _translateError(r, WSAGetLastError()); + //_setSockError(WU_SO_SUCCESS); + // in case of error, clear all FD sets (?) + if (readfds) + readfds->mask = 0; + if (writefds) + writefds->mask = 0; + if (exceptfds) + exceptfds->mask = 0; + break; + } + else if (r == 0) + { + // check for timeout + if ((GetTickCount() - startTime) >= msTimeout ) + { + // timeout + _setSockError(WU_SO_SUCCESS); + r = 0; + // in case of timeout, clear all FD sets + if (readfds) + readfds->mask = 0; + if (writefds) + writefds->mask = 0; + if (exceptfds) + exceptfds->mask = 0; + break; + } + // yield thread + PPCCore_switchToScheduler(); + } + else + { + socketLog_printf("select returned %d. Read %d Write %d Except %d", r, _readfds.fd_count, _writefds.fd_count, _exceptfds.fd_count); + _translateFDSetRev(readfds, &_readfds, nfds); + _translateFDSetRev(writefds, &_writefds, nfds); + _translateFDSetRev(exceptfds, &_exceptfds, nfds); + break; + } + } + //forceLog_printf("selectEndTime %d", timeGetTime()); + + + //extern int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, + // struct timeval *timeout); + + socketLog_printf("select returned %d", r); + osLib_returnFromFunction(hCPU, r); +} + +void nsysnetExport_getsockname(PPCInterpreter_t* hCPU) +{ + socketLog_printf("getsockname(%d,0x%08x,0x%08x)", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); + + ppcDefineParamS32(s, 0); + ppcDefineParamStructPtr(addr, struct wu_sockaddr, 1); + ppcDefineParamStructPtr(lenPtr, uint32, 2); + sint32 r = 0; + virtualSocket_t* vs = nsysnet_getVirtualSocketObject(s); + if (vs == NULL) + { + assert_dbg(); + } + else + { + struct sockaddr hostAddr; + sint32 hostLen = sizeof(struct sockaddr); + sint32 hr = getsockname(vs->s, &hostAddr, &hostLen); + if (hr == 0) + { + addr->sa_family = _swapEndianU16(hostAddr.sa_family); + memcpy(addr->sa_data, hostAddr.sa_data, 14); + } + else + { + assert_dbg(); + } + //sint32 hr = listen(vs->s, queueSize); + //if (hr != 0) + // r = -1; + //// todo: Set proper coreinit errno (via _setSockError) + } + + osLib_returnFromFunction(hCPU, r); +} + +void nsysnetExport_getpeername(PPCInterpreter_t* hCPU) +{ + ppcDefineParamS32(s, 0); + ppcDefineParamStructPtr(name, struct wu_sockaddr, 1); + ppcDefineParamU32BEPtr(nameLen, 2); + + virtualSocket_t* vs = nsysnet_getVirtualSocketObject(s); + if (vs == NULL) + { + // todo: Return correct error + osLib_returnFromFunction(hCPU, -1); + return; + } + + sockaddr saddr; + int saddrLen = sizeof(sockaddr); + + if (*nameLen < (uint32be)16) + assert_dbg(); + + sint32 r = getpeername(vs->s, &saddr, &saddrLen); + r = _translateError(r, WSAGetLastError()); + + name->sa_family = _swapEndianU16(saddr.sa_family); + memcpy(name->sa_data, saddr.sa_data, 14); + *nameLen = 16; + + osLib_returnFromFunction(hCPU, r); +} + +typedef struct +{ + MPTR h_name; + MPTR h_aliases; + sint32 h_addrType; + sint32 h_length; + MPTR h_addr_list; +}wu_hostent; + +MPTR _allocString(char* str) +{ + sint32 len = (sint32)strlen(str); + MPTR strMPTR = coreinit_allocFromSysArea(len+1, 4); + strcpy((char*)memory_getPointerFromVirtualOffset(strMPTR), str); + return strMPTR; +} + +void nsysnetExport_gethostbyname(PPCInterpreter_t* hCPU) +{ + ppcDefineParamStr(name, 0); + + socketLog_printf("gethostbyname(\"%s\")", name); + + hostent* he = gethostbyname(name); + if (he == NULL) + { + osLib_returnFromFunction(hCPU, MPTR_NULL); + return; + } + + + MPTR hostentMPTR = coreinit_allocFromSysArea(sizeof(wu_hostent)*1, 4); + MPTR hostentAddrListMPTR = coreinit_allocFromSysArea(sizeof(MPTR) * 2, 4); + MPTR hostentAddrListEntriesMPTR = coreinit_allocFromSysArea(sizeof(wu_in_addr) * 1, 4); + + wu_hostent* wuHostent = (wu_hostent*)memory_getPointerFromVirtualOffset(hostentMPTR); + MPTR* addrList = (MPTR*)memory_getPointerFromVirtualOffset(hostentAddrListMPTR); + + wuHostent->h_addrType = _swapEndianU32((uint32)he->h_addrtype); + wuHostent->h_length = _swapEndianU32((uint32)he->h_length); + + wuHostent->h_name = _swapEndianU32(_allocString(he->h_name)); + wuHostent->h_addr_list = _swapEndianU32(hostentAddrListMPTR); + + //memory_writeU32(hostentAddrListEntriesMPTR, _swapEndianU32(*(uint32*)he->h_addr_list[0])); + + wu_in_addr* addrListEntries = (wu_in_addr*)memory_getPointerFromVirtualOffset(hostentAddrListEntriesMPTR); + addrListEntries->wu_s_addr = *(uint32*)he->h_addr_list[0]; // address is already in network (big-endian) order + + memory_writeU32(hostentAddrListMPTR + 4 * 0, hostentAddrListEntriesMPTR); + memory_writeU32(hostentAddrListMPTR + 4 * 1, MPTR_NULL); + + osLib_returnFromFunction(hCPU, hostentMPTR); + return; +} + +SysAllocator<wu_hostent> _staticHostent; +SysAllocator<char, 256> _staticHostentName; +SysAllocator<MPTR, 32> _staticHostentPtrList; +SysAllocator<wu_in_addr> _staticHostentEntries; + +void nsysnetExport_gethostbyaddr(PPCInterpreter_t* hCPU) +{ + ppcDefineParamStr(addr, 0); + ppcDefineParamS32(len, 1); + ppcDefineParamS32(type, 2); + + sint32 maxNumEntries = 31; + + socketLog_printf("gethostbyaddr(\"%s\", %d, %d)", addr, len, type); + + hostent* he = gethostbyaddr(addr, len, type); + if (he == nullptr) + { + socketLog_printf("gethostbyaddr(\"%s\", %d, %d) failed", addr, len, type); + osLib_returnFromFunction(hCPU, MPTR_NULL); + return; + } + +#ifndef PUBLIC_RELEASE + if (he->h_addrtype != AF_INET) + assert_dbg(); + if (he->h_length != sizeof(in_addr)) + assert_dbg(); +#endif + + wu_hostent* wuHostent = _staticHostent.GetPtr(); + // setup wuHostent->h_name + wuHostent->h_name = _swapEndianU32(_staticHostentName.GetMPTR()); + if (he->h_name && strlen(he->h_name) < 255) + { + strcpy(_staticHostentName.GetPtr(), he->h_name); + } + else + { + forceLog_printf("he->h_name not set or name too long"); + strcpy(_staticHostentName.GetPtr(), ""); + } + // setup wuHostent address list + wuHostent->h_addrType = _swapEndianU32(WU_AF_INET); + wuHostent->h_addr_list = _swapEndianU32(_staticHostentPtrList.GetMPTR()); + wuHostent->h_length = _swapEndianU32(sizeof(wu_in_addr)); + for (sint32 i = 0; i < maxNumEntries; i++) + { + if (he->h_addr_list[i] == nullptr) + { + _staticHostentPtrList.GetPtr()[i] = MPTR_NULL; + break; + } + memcpy(_staticHostentEntries.GetPtr() + i, he->h_addr_list[i], sizeof(in_addr)); + _staticHostentPtrList.GetPtr()[i] = _swapEndianU32(memory_getVirtualOffsetFromPointer(_staticHostentEntries.GetPtr() + i)); + } + _staticHostentPtrList.GetPtr()[31] = MPTR_NULL; + // aliases are ignored for now + wuHostent->h_aliases = MPTR_NULL; + + osLib_returnFromFunction(hCPU, _staticHostent.GetMPTR()); + return; +} + +void nsysnetExport_getaddrinfo(PPCInterpreter_t* hCPU) +{ + ppcDefineParamStr(nodeName, 0); + ppcDefineParamStr(serviceName, 1); + ppcDefineParamStructPtr(hints, struct wu_addrinfo, 2); + ppcDefineParamMPTR(results, 3); + + socketLog_printf("getaddrinfo(\"%s\",0x%08x,0x%08x,0x%08x)", nodeName, hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6]); + + sint32 r = 0; + + // todo1: This is really slow. Make it asynchronous + // todo2: Should this set the socket last error code? + + struct addrinfo hint = { 0 }; + if (hints) + { + hint.ai_family = _swapEndianU32(hints->ai_family); + hint.ai_socktype = _swapEndianU32(hints->ai_socktype); + hint.ai_flags = _swapEndianU32(hints->ai_flags); + hint.ai_protocol = _swapEndianU32(hints->ai_protocol); + } + else + { + hint.ai_family = 0; + hint.ai_socktype = 0; + hint.ai_flags = 0; + hint.ai_protocol = 0; + } + struct addrinfo* result; + sint32 hr = getaddrinfo(nodeName, serviceName, &hint, &result); + if (hr != 0) + { + socketLog_printf("getaddrinfo failed with error %d", hr); + switch (hr) + { + case WSAHOST_NOT_FOUND: + r = WU_SO_ECONNRESET; // todo - verify error code + break; + default: + // unhandled error + cemu_assert_debug(false); + socketLog_printf("getaddrinfo unhandled error code"); + r = 1; + break; + } + } + else + { + // count how many results there are + sint32 resultCount = 0; + struct addrinfo* currentAddrInfo = result; + while (currentAddrInfo) + { + resultCount++; + currentAddrInfo = currentAddrInfo->ai_next; + } + if (resultCount == 0) + { + cemu_assert_debug(false); + } + // allocate entries + MPTR addrInfoMPTR = coreinit_allocFromSysArea(sizeof(wu_addrinfo)*resultCount + sizeof(wu_sockaddr)*resultCount, 4); + MPTR addrInfoSockAddrMPTR = addrInfoMPTR + sizeof(wu_addrinfo)*resultCount; + wu_addrinfo* wuAddrInfo = (wu_addrinfo*)memory_getPointerFromVirtualOffset(addrInfoMPTR); + // fill entries + currentAddrInfo = result; + sint32 entryIndex = 0; + wu_addrinfo* previousWuAddrInfo = NULL; + while (currentAddrInfo) + { + if (currentAddrInfo->ai_addrlen != 16) + { + // skip this entry (IPv6) + currentAddrInfo = currentAddrInfo->ai_next; + continue; + } + + memset(&wuAddrInfo[entryIndex], 0, sizeof(wu_addrinfo)); + // setup pointers + wuAddrInfo[entryIndex].ai_addr = _swapEndianU32(addrInfoSockAddrMPTR + sizeof(wu_sockaddr)*entryIndex); + wuAddrInfo[entryIndex].ai_next = MPTR_NULL; + wuAddrInfo[entryIndex].ai_canonname = _swapEndianU32(MPTR_NULL); + // set ai_next for previous element + if (previousWuAddrInfo) + { + previousWuAddrInfo->ai_next = _swapEndianU32(memory_getVirtualOffsetFromPointer(&wuAddrInfo[entryIndex])); + } + previousWuAddrInfo = &wuAddrInfo[entryIndex]; + // fill addrinfo struct + wuAddrInfo[entryIndex].ai_addrlen = _swapEndianU32((uint32)currentAddrInfo->ai_addrlen); + //wuAddrInfo[entryIndex].ai_canonname; todo + wuAddrInfo[entryIndex].ai_family = _swapEndianU32(currentAddrInfo->ai_family); + wuAddrInfo[entryIndex].ai_flags = _swapEndianU32(currentAddrInfo->ai_flags); // todo: These flags might need to be translated + wuAddrInfo[entryIndex].ai_protocol = _swapEndianU32(currentAddrInfo->ai_protocol); + wuAddrInfo[entryIndex].ai_socktype = _swapEndianU32(currentAddrInfo->ai_socktype); + + // fill ai_addr + wu_sockaddr* sockAddr = (wu_sockaddr*)memory_getPointerFromVirtualOffset(_swapEndianU32(wuAddrInfo[entryIndex].ai_addr)); + sockAddr->sa_family = _swapEndianU16(currentAddrInfo->ai_addr->sa_family); + memcpy(sockAddr->sa_data, currentAddrInfo->ai_addr->sa_data, 14); + + // next + entryIndex++; + currentAddrInfo = currentAddrInfo->ai_next; + } + if (entryIndex == 0) + assert_dbg(); + // set results + memory_writeU32(results, addrInfoMPTR); + } + + osLib_returnFromFunction(hCPU, r); +} + +void nsysnetExport_recvfrom(PPCInterpreter_t* hCPU) +{ + socketLog_printf("recvfrom(%d,0x%08x,%d,0x%x) LR: 0x%08x Thread: 0x%08x", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->spr.LR, coreinitThread_getCurrentThreadMPTRDepr(hCPU)); + ppcDefineParamS32(s, 0); + ppcDefineParamStr(msg, 1); + ppcDefineParamS32(len, 2); + ppcDefineParamS32(flags, 3); + ppcDefineParamStructPtr(fromAddr, wu_sockaddr, 4); + ppcDefineParamU32BEPtr(fromLen, 5); + + + virtualSocket_t* vs = nsysnet_getVirtualSocketObject(s); + sint32 r = 0; + if (vs == NULL) + { + assert_dbg(); + return; + } + + int hostFlags = 0; + bool requestIsNonBlocking = (flags&WU_MSG_DONTWAIT) != 0; + flags &= ~WU_MSG_DONTWAIT; + if (flags&WU_MSG_PEEK) + { + assert_dbg(); + hostFlags |= MSG_PEEK; + flags &= ~WU_MSG_PEEK; + } + if (vs->isNonBlocking) + requestIsNonBlocking = vs->isNonBlocking; + + sint32 fromLenHost = *fromLen; + sockaddr fromAddrHost; + sint32 wsaError = 0; + + while( true ) + { + // is socket recv shutdown? + if (vs->isShutdownRecv) + { + // return with error + _setSockError(WU_SO_ESHUTDOWN); + osLib_returnFromFunction(hCPU, -1); + return; + } + // use select to check for exceptions and read data + FD_SET fd_read; + FD_SET fd_exceptions; + FD_ZERO(&fd_read); + FD_ZERO(&fd_exceptions); + FD_SET(vs->s, &fd_read); + FD_SET(vs->s, &fd_exceptions); + TIMEVAL t; + t.tv_sec = 0; + t.tv_usec = 0; + sint32 count = select(0, &fd_read, NULL, &fd_exceptions, &t); + if (count > 0) + { + if (FD_ISSET(vs->s, &fd_exceptions)) + { + assert_dbg(); // exception + } + if (FD_ISSET(vs->s, &fd_read)) + { + // data available + r = recvfrom(vs->s, msg, len, hostFlags, &fromAddrHost, &fromLenHost); + wsaError = WSAGetLastError(); + if (r < 0) + cemu_assert_debug(false); + forceLogDebug_printf("recvfrom returned %d bytes", r); + *fromLen = fromLenHost; + fromAddr->sa_family = _swapEndianU16(fromAddrHost.sa_family); + memcpy(fromAddr->sa_data, fromAddrHost.sa_data, 14); + + _setSockError(0); + osLib_returnFromFunction(hCPU, r); + return; + } + } + // nothing to do + if (requestIsNonBlocking) + { + // return with error + _setSockError(WU_SO_EWOULDBLOCK); + osLib_returnFromFunction(hCPU, -1); + return; + } + else + { + // pause for a while and check again later + coreinit::OSSleepTicks(ESPRESSO_CORE_CLOCK / 1000); // pause for 1ms + PPCCore_switchToScheduler(); + } + } + assert_dbg(); // should no longer be reached + + if (requestIsNonBlocking == false) + { + // blocking + _setSocketSendRecvNonBlockingMode(vs->s, true); + while (true) + { + r = recvfrom(vs->s, msg, len, hostFlags, &fromAddrHost, &fromLenHost); + wsaError = WSAGetLastError(); + if (r < 0) + { + if (wsaError != WSAEWOULDBLOCK) + break; + coreinit::OSSleepTicks(ESPRESSO_CORE_CLOCK/100); // pause for 10ms + PPCCore_switchToScheduler(); + continue; + } + assert_dbg(); + } + _setSocketSendRecvNonBlockingMode(vs->s, vs->isNonBlocking); + } + else + { + // non blocking + assert_dbg(); + } + + *fromLen = fromLenHost; + fromAddr->sa_family = _swapEndianU16(fromAddrHost.sa_family); + memcpy(fromAddr->sa_data, fromAddrHost.sa_data, 14); + + _translateError(r <= 0 ? -1 : 0, wsaError); + + osLib_returnFromFunction(hCPU, r); +} + + +void nsysnetExport_recvfrom_ex(PPCInterpreter_t* hCPU) +{ + socketLog_printf("recvfrom_ex(%d,0x%08x,%d,0x%x,0x%08x,0x%08x,0x%08x,%d) LR: 0x%08x Thread: 0x%08x", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7], hCPU->gpr[8], hCPU->gpr[9], hCPU->gpr[10], hCPU->spr.LR, coreinitThread_getCurrentThreadMPTRDepr(hCPU)); + ppcDefineParamS32(s, 0); + ppcDefineParamStr(msg, 1); + ppcDefineParamS32(len, 2); + ppcDefineParamS32(flags, 3); + ppcDefineParamStructPtr(fromAddr, wu_sockaddr, 4); + ppcDefineParamU32BEPtr(fromLen, 5); + ppcDefineParamUStr(extraData, 6); + ppcDefineParamS32(extraDataLen, 7); + + virtualSocket_t* vs = nsysnet_getVirtualSocketObject(s); + sint32 r = 0; + if (vs == NULL) + { + cemu_assert_debug(false); + return; + } + + int hostFlags = 0; + bool requestIsNonBlocking = (flags&WU_MSG_DONTWAIT) != 0; + flags &= ~WU_MSG_DONTWAIT; + if (flags&WU_MSG_PEEK) + { + cemu_assert_debug(false); + hostFlags |= MSG_PEEK; + flags &= ~WU_MSG_PEEK; + } + if (flags & 0x40) + { + // read TTL + if (extraDataLen != 1) + { + cemu_assert_debug(false); + } + extraData[0] = 0x5; // we currently always return 5 as TTL + flags &= ~0x40; + } + + if (vs->isNonBlocking) + requestIsNonBlocking = vs->isNonBlocking; + + sint32 fromLenHost = *fromLen; + sockaddr fromAddrHost; + sint32 wsaError = 0; + + while (true) + { + // is socket recv shutdown? + if (vs->isShutdownRecv) + { + // return with error + _setSockError(WU_SO_ESHUTDOWN); + osLib_returnFromFunction(hCPU, -1); + return; + } + // use select to check for exceptions and read data + FD_SET fd_read; + FD_SET fd_exceptions; + FD_ZERO(&fd_read); + FD_ZERO(&fd_exceptions); + FD_SET(vs->s, &fd_read); + FD_SET(vs->s, &fd_exceptions); + TIMEVAL t; + t.tv_sec = 0; + t.tv_usec = 0; + sint32 count = select(0, &fd_read, NULL, &fd_exceptions, &t); + if (count > 0) + { + if (FD_ISSET(vs->s, &fd_exceptions)) + { + cemu_assert_debug(false); // exception + } + if (FD_ISSET(vs->s, &fd_read)) + { + // data available + r = recvfrom(vs->s, msg, len, hostFlags, &fromAddrHost, &fromLenHost); + wsaError = WSAGetLastError(); + if (r < 0) + { + cemu_assert_debug(false); + } + *fromLen = fromLenHost; + fromAddr->sa_family = _swapEndianU16(fromAddrHost.sa_family); + memcpy(fromAddr->sa_data, fromAddrHost.sa_data, 14); + + _setSockError(0); + osLib_returnFromFunction(hCPU, r); + return; + } + } + // nothing to do + if (requestIsNonBlocking) + { + // return with error + _setSockError(WU_SO_EWOULDBLOCK); + osLib_returnFromFunction(hCPU, -1); + return; + } + else + { + // pause for a while and check again later + coreinit::OSSleepTicks(ESPRESSO_CORE_CLOCK / 100); // pause for 10ms + PPCCore_switchToScheduler(); + } + } + cemu_assert_debug(false); // should no longer be reached +} + + +void _convertSockaddrToHostFormat(wu_sockaddr* sockaddru, sockaddr* sockaddrHost) +{ + sockaddrHost->sa_family = _swapEndianU16(sockaddru->sa_family); + memcpy(sockaddrHost->sa_data, sockaddru->sa_data, 14); +} + + +void nsysnetExport_sendto(PPCInterpreter_t* hCPU) +{ + socketLog_printf("sendto(%d,0x%08x,%d,0x%x) LR: 0x%08x Thread: 0x%08x", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->spr.LR, coreinitThread_getCurrentThreadMPTRDepr(hCPU)); + ppcDefineParamS32(s, 0); + ppcDefineParamStr(msg, 1); + ppcDefineParamS32(len, 2); + ppcDefineParamS32(flags, 3); + ppcDefineParamStructPtr(toAddr, wu_sockaddr, 4); + ppcDefineParamU32(toLen, 5); + + virtualSocket_t* vs = nsysnet_getVirtualSocketObject(s); + sint32 r = 0; + if (vs == NULL) + { + assert_dbg(); + return; + } + + int hostFlags = 0; + bool requestIsNonBlocking = (flags&WU_MSG_DONTWAIT) != 0; + flags &= ~WU_MSG_DONTWAIT; + if (flags&WU_MSG_PEEK) + { + assert_dbg(); + hostFlags |= MSG_PEEK; + flags &= ~WU_MSG_PEEK; + } + if (vs->isNonBlocking) + requestIsNonBlocking = vs->isNonBlocking; + + sockaddr toAddrHost; + toAddrHost.sa_family = _swapEndianU16(toAddr->sa_family); + memcpy(toAddrHost.sa_data, toAddr->sa_data, 14); + + sint32 wsaError = 0; + if (requestIsNonBlocking == false) + { + // blocking + _setSocketSendRecvNonBlockingMode(vs->s, true); + while (true) + { + r = sendto(vs->s, msg, len, hostFlags, &toAddrHost, toLen); + wsaError = WSAGetLastError(); + if (r < 0) + { + if (wsaError != WSAEWOULDBLOCK) + break; + coreinit::OSSleepTicks(ESPRESSO_CORE_CLOCK / 100); // pause for 10ms + PPCCore_switchToScheduler(); + continue; + } + break; + } + _setSocketSendRecvNonBlockingMode(vs->s, vs->isNonBlocking); + } + else + { + // non blocking + _setSocketSendRecvNonBlockingMode(vs->s, true); + r = sendto(vs->s, msg, len, hostFlags, &toAddrHost, toLen); + wsaError = WSAGetLastError(); + _setSocketSendRecvNonBlockingMode(vs->s, vs->isNonBlocking); + } + + _translateError(r <= 0 ? -1 : 0, wsaError); + + osLib_returnFromFunction(hCPU, r); +} + + +void nsysnetExport_sendto_multi(PPCInterpreter_t* hCPU) +{ + socketLog_printf("sendto_multi(%d,0x%08x,0x%08x,%d) LR: 0x%08x Thread: 0x%08x", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->spr.LR, coreinitThread_getCurrentThreadMPTRDepr(hCPU)); + ppcDefineParamS32(s, 0); + ppcDefineParamStr(data, 1); + ppcDefineParamS32(dataLen, 2); + ppcDefineParamU32(flags, 3); + ppcDefineParamStructPtr(destinationArray, wu_sockaddr, 4); + ppcDefineParamU32(destinationCount, 5); + + if (flags != 0) + assert_dbg(); + + // todo - somehow handle non-blocking + + virtualSocket_t* vs = nsysnet_getVirtualSocketObject(s); + if (vs == NULL) + { + assert_dbg(); + return; + } + + for (uint32 i = 0; i < destinationCount; i++) + { + sockaddr destinationHost; + _convertSockaddrToHostFormat(&destinationArray[i], &destinationHost); + sint32 r = sendto(vs->s, (char*)data, (int)dataLen, 0, &destinationHost, sizeof(sockaddr)); + if (r < dataLen) + assert_dbg(); // todo + } + // success + _setSockError(0); + osLib_returnFromFunction(hCPU, dataLen); +} + +typedef struct +{ + MEMPTR<uint8> data; + uint32be dataLen; + MEMPTR<uint32be> sendLenArray; + uint32be sendLenArraySize; + MEMPTR<wu_sockaddr> destArray; + uint32be destArraySize; + MEMPTR<uint32be> resultArray; + uint32be resultArrayLen; +}sendtomultiBuffer_t; + +void nsysnetExport_sendto_multi_ex(PPCInterpreter_t* hCPU) +{ + socketLog_printf("sendto_multi_ex(%d,0x%08x,0x%08x,%d) LR: 0x%08x Thread: 0x%08x", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->spr.LR, coreinitThread_getCurrentThreadMPTRDepr(hCPU)); + ppcDefineParamS32(s, 0); + ppcDefineParamU32(flags, 1); + ppcDefineParamStructPtr(multiBuf, sendtomultiBuffer_t, 2); + ppcDefineParamU32(num, 3); + + assert_dbg(); // todo - needs testing + + virtualSocket_t* vs = nsysnet_getVirtualSocketObject(s); + if (vs == NULL) + { + assert_dbg(); + return; + } + + // todo - lots of validation for multiBuf parameters (pointers must not be null and must be aligned, lens must be padded to alignment too) + // verify multiBuf + if ((uint32)multiBuf->sendLenArraySize < num || + (uint32)multiBuf->destArraySize < num || + (uint32)multiBuf->resultArrayLen < num ) + { + cemu_assert_debug(false); + } + + for (uint32 i = 0; i < num; i++) + { + multiBuf->resultArray[i] = 0; + } + + uint8* data = multiBuf->data.GetPtr(); + sint32 sendLenSum = 0; + for (uint32 i = 0; i < num; i++) + { + sockaddr destination; + _convertSockaddrToHostFormat(&multiBuf->destArray[i], &destination); + + uint32 sendLen = (uint32)(multiBuf->sendLenArray[i]); + sint32 r = sendto(vs->s, (char*)data, (int)sendLen, 0, &destination, sizeof(sockaddr)); + if (r < (sint32)sendLen) + cemu_assert_debug(false); + else + multiBuf->resultArray[i] = r; + data += sendLen; + sendLenSum += sendLenSum; + } + osLib_returnFromFunction(hCPU, sendLenSum); // return value correct? +} + +#endif + +namespace nsysnet +{ + std::vector<NSSLInternalState_t> g_nsslInternalStates; + + NSSLInternalState_t* GetNSSLContext(sint32 index) + { + if (g_nsslInternalStates.size() <= index) + return nullptr; + + if (g_nsslInternalStates[index].destroyed) + cemu_assert_suspicious(); + + return &g_nsslInternalStates[index]; + } + + void export_NSSLCreateContext(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(version, 0); + + NSSLInternalState_t ssl = {}; + ssl.sslVersion = version; + g_nsslInternalStates.push_back(ssl); + + uint32 nsslHandle = (uint32)g_nsslInternalStates.size() - 1; + + forceLogDebug_printf("NSSLCreateContext(0x%x) -> 0x%x", version, nsslHandle); + + osLib_returnFromFunction(hCPU, nsslHandle); + } + + void export_NSSLSetClientPKI(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(nsslHandle, 0); + ppcDefineParamU32(clientPKI, 1); + forceLogDebug_printf("NSSLSetClientPKI(0x%x, 0x%x)", nsslHandle, clientPKI); + + if (g_nsslInternalStates.size() <= nsslHandle || g_nsslInternalStates[nsslHandle].destroyed) + { + osLib_returnFromFunction(hCPU, NSSL_INVALID_CTX); + return; + } + + g_nsslInternalStates[nsslHandle].clientPKI = clientPKI; + osLib_returnFromFunction(hCPU, NSSL_OK); + } + + void export_NSSLAddServerPKI(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(nsslHandle, 0); + ppcDefineParamU32(serverPKI, 1); + forceLogDebug_printf("NSSLAddServerPKI(0x%x, 0x%x)", nsslHandle, serverPKI); + + if (g_nsslInternalStates.size() <= nsslHandle || g_nsslInternalStates[nsslHandle].destroyed) + { + osLib_returnFromFunction(hCPU, NSSL_INVALID_CTX); + return; + } + + g_nsslInternalStates[nsslHandle].serverPKIs.insert(serverPKI); + osLib_returnFromFunction(hCPU, NSSL_OK); + } + + void export_NSSLAddServerPKIExternal(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(nsslHandle, 0); + ppcDefineParamMEMPTR(certData, uint8, 1); + ppcDefineParamS32(certLen, 2); + ppcDefineParamS32(certType, 3); + + forceLogDebug_printf("NSSLAddServerPKIExternal(0x%x, 0x%08x, 0x%x, %d)", nsslHandle, certData.GetMPTR(), certLen, certType); + if (g_nsslInternalStates.size() <= nsslHandle || g_nsslInternalStates[nsslHandle].destroyed) + { + osLib_returnFromFunction(hCPU, NSSL_INVALID_CTX); + return; + } + + g_nsslInternalStates[nsslHandle].serverCustomPKIs.push_back(std::vector<uint8>(certData.GetPtr(), certData.GetPtr()+certLen)); + + osLib_returnFromFunction(hCPU, NSSL_OK); + } + + void export_NSSLAddServerPKIGroups(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(nsslHandle, 0); + ppcDefineParamU32(groupMask, 1); + ppcDefineParamMEMPTR(validCountOut, sint32, 2); + ppcDefineParamMEMPTR(invalidCountOut, sint32, 3); + forceLogDebug_printf("NSSLAddServerPKIGroups(0x%x, 0x%x, 0x%08x, 0x%08x)", nsslHandle, groupMask, validCountOut.GetMPTR(), invalidCountOut.GetMPTR()); + + if (g_nsslInternalStates.size() <= nsslHandle || g_nsslInternalStates[nsslHandle].destroyed) + { + osLib_returnFromFunction(hCPU, NSSL_INVALID_CTX); + return; + } + + if (HAS_FLAG(groupMask, 1)) + { + g_nsslInternalStates[nsslHandle].serverPKIs.insert({ 100,101,102,103,104,105 }); + } + + if (HAS_FLAG(groupMask, 2)) + { + g_nsslInternalStates[nsslHandle].serverPKIs.insert({ + 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, + 1010, 1011, 1012, 1013, 1014, 1015, 1016, 1017, 1018, 1019, + 1020, 1021, 1022, 1023, 1024, 1025, 1026, 1027, 1028, 1029, + 1030, 1031, 1032, 1033 }); + } + + if (validCountOut) + *validCountOut = (sint32)g_nsslInternalStates[nsslHandle].serverPKIs.size(); + + if (invalidCountOut) + *invalidCountOut = 0; + + osLib_returnFromFunction(hCPU, NSSL_OK); + } + + void export_NSSLDestroyContext(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(nsslHandle, 0); + forceLogDebug_printf("NSSLDestroyContext(0x%x)", nsslHandle); + + if (g_nsslInternalStates.size() <= nsslHandle || g_nsslInternalStates[nsslHandle].destroyed) + { + osLib_returnFromFunction(hCPU, NSSL_INVALID_CTX); + return; + } + + g_nsslInternalStates[nsslHandle].destroyed = true; + osLib_returnFromFunction(hCPU, NSSL_OK); + } + + void export_NSSLExportInternalServerCertificate(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(serverCertId, 0); + ppcDefineParamUStr(output, 1); + ppcDefineParamU32BEPtr(outputSize, 2); + ppcDefineParamU32BEPtr(certType, 3); + + sint32 certificateSize = 0; + uint8* certificateData = iosuCrypto_getCertificateDataById(serverCertId, &certificateSize); + if (certificateData == nullptr) + { + cemu_assert_debug(false); + } + + if( output ) + memcpy(output, certificateData, certificateSize); + *outputSize = (uint32)certificateSize; + *certType = 0; + + osLib_returnFromFunction(hCPU, NSSL_OK); + } + + void export_NSSLExportInternalClientCertificate(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(serverCertId, 0); + ppcDefineParamUStr(output, 1); + ppcDefineParamU32BEPtr(outputSize, 2); + ppcDefineParamU32BEPtr(certType, 3); + ppcDefineParamUStr(pkeyOutput, 4); + ppcDefineParamU32BEPtr(pkeyOutputSize, 5); + ppcDefineParamU32BEPtr(pkeyCertType, 6); + + // certificate + sint32 certificateSize = 0; + uint8* certificateData = iosuCrypto_getCertificateDataById(serverCertId, &certificateSize); + if (certificateData == nullptr) + { + assert_dbg(); // todo + } + if (output) + memcpy(output, certificateData, certificateSize); + *outputSize = (uint32)certificateSize; + *certType = 0; + + // private key + sint32 pkeySize = 0; + uint8* pkeyData = iosuCrypto_getCertificatePrivateKeyById(serverCertId, &pkeySize); + if (pkeyData == nullptr) + { + assert_dbg(); // todo + } + if (pkeyOutput) + memcpy(pkeyOutput, pkeyData, pkeySize); + *pkeyOutputSize = (uint32)pkeySize; + *pkeyCertType = 0; + + osLib_returnFromFunction(hCPU, NSSL_OK); + } + + void wuResetFD(struct wu_fd_set* fdset) + { + fdset->mask = 0; + } + + void wuSetFD(struct wu_fd_set* fdset, sint32 fd) + { + fdset->mask |= (1 << fd); + } + +} + + + +// register nsysnet functions +void nsysnet_load() +{ + + #if BOOST_OS_WINDOWS + osLib_addFunction("nsysnet", "socket_lib_init", nsysnetExport_socket_lib_init); + + // socket API + osLib_addFunction("nsysnet", "socket", nsysnetExport_socket); + osLib_addFunction("nsysnet", "mw_socket", nsysnetExport_mw_socket); + osLib_addFunction("nsysnet", "shutdown", nsysnetExport_shutdown); + osLib_addFunction("nsysnet", "socketclose", nsysnetExport_socketclose); + osLib_addFunction("nsysnet", "setsockopt", nsysnetExport_setsockopt); + osLib_addFunction("nsysnet", "getsockopt", nsysnetExport_getsockopt); + osLib_addFunction("nsysnet", "bind", nsysnetExport_bind); + osLib_addFunction("nsysnet", "listen", nsysnetExport_listen); + osLib_addFunction("nsysnet", "accept", nsysnetExport_accept); + osLib_addFunction("nsysnet", "connect", nsysnetExport_connect); + osLib_addFunction("nsysnet", "send", nsysnetExport_send); + osLib_addFunction("nsysnet", "recv", nsysnetExport_recv); + osLib_addFunction("nsysnet", "select", nsysnetExport_select); + osLib_addFunction("nsysnet", "getsockname", nsysnetExport_getsockname); + osLib_addFunction("nsysnet", "getpeername", nsysnetExport_getpeername); + + osLib_addFunction("nsysnet", "inet_aton", nsysnetExport_inet_aton); + osLib_addFunction("nsysnet", "inet_pton", nsysnetExport_inet_pton); + osLib_addFunction("nsysnet", "inet_ntoa", nsysnetExport_inet_ntoa); + osLib_addFunction("nsysnet", "htons", nsysnetExport_htons); + osLib_addFunction("nsysnet", "htonl", nsysnetExport_htonl); + osLib_addFunction("nsysnet", "ntohs", nsysnetExport_ntohs); + osLib_addFunction("nsysnet", "ntohl", nsysnetExport_ntohl); + osLib_addFunction("nsysnet", "gethostbyname", nsysnetExport_gethostbyname); + osLib_addFunction("nsysnet", "gethostbyaddr", nsysnetExport_gethostbyaddr); + osLib_addFunction("nsysnet", "getaddrinfo", nsysnetExport_getaddrinfo); + + osLib_addFunction("nsysnet", "socketlasterr", nsysnetExport_socketlasterr); + + // unfinished implementations + osLib_addFunction("nsysnet", "recvfrom", nsysnetExport_recvfrom); + osLib_addFunction("nsysnet", "recvfrom_ex", nsysnetExport_recvfrom_ex); + osLib_addFunction("nsysnet", "sendto", nsysnetExport_sendto); + + osLib_addFunction("nsysnet", "sendto_multi", nsysnetExport_sendto_multi); + osLib_addFunction("nsysnet", "sendto_multi_ex", nsysnetExport_sendto_multi_ex); + +#endif + + // NSSL API + osLib_addFunction("nsysnet", "NSSLCreateContext", nsysnet::export_NSSLCreateContext); + osLib_addFunction("nsysnet", "NSSLSetClientPKI", nsysnet::export_NSSLSetClientPKI); + osLib_addFunction("nsysnet", "NSSLAddServerPKI", nsysnet::export_NSSLAddServerPKI); + osLib_addFunction("nsysnet", "NSSLAddServerPKIExternal", nsysnet::export_NSSLAddServerPKIExternal); + osLib_addFunction("nsysnet", "NSSLAddServerPKIGroups", nsysnet::export_NSSLAddServerPKIGroups); + osLib_addFunction("nsysnet", "NSSLDestroyContext", nsysnet::export_NSSLDestroyContext); + + osLib_addFunction("nsysnet", "NSSLExportInternalServerCertificate", nsysnet::export_NSSLExportInternalServerCertificate); + osLib_addFunction("nsysnet", "NSSLExportInternalClientCertificate", nsysnet::export_NSSLExportInternalClientCertificate); +} + +#if BOOST_OS_LINUX +void nsysnet_notifyCloseSharedSocket(SOCKET existingSocket) +{ + +} +#endif \ No newline at end of file diff --git a/src/Cafe/OS/libs/nsysnet/nsysnet.h b/src/Cafe/OS/libs/nsysnet/nsysnet.h new file mode 100644 index 00000000..bb0ad2b0 --- /dev/null +++ b/src/Cafe/OS/libs/nsysnet/nsysnet.h @@ -0,0 +1,48 @@ +#pragma once +#include <set> +#include <vector> + +#if BOOST_OS_WINDOWS > 0 +#include <WinSock2.h> +#else +#include <sys/socket.h> +#define SOCKET int +#define closesocket close +#endif + +typedef signed int WUSOCKET; + +void nsysnet_load(); +WUSOCKET nsysnet_createVirtualSocketFromExistingSocket(SOCKET existingSocket); +void nsysnet_notifyCloseSharedSocket(SOCKET existingSocket); + +struct wu_fd_set +{ + uint32be mask; +}; + +void _translateFDSet(fd_set* hostSet, struct wu_fd_set* fdset, sint32 nfds); +void _translateFDSetRev(struct wu_fd_set* fdset, fd_set* hostSet, sint32 nfds); + +sint32 nsysnet_getVirtualSocketHandleFromHostHandle(SOCKET s); + +namespace nsysnet +{ + +#define NSSL_OK (0) +#define NSSL_INVALID_CTX (0xFFD7FFFF) + + struct NSSLInternalState_t + { + bool destroyed; + uint32 sslVersion; + uint32 clientPKI; + std::set<uint32> serverPKIs; + std::vector<std::vector<uint8>> serverCustomPKIs; + }; + + NSSLInternalState_t* GetNSSLContext(sint32 index); + + void wuResetFD(struct wu_fd_set* fdset); + void wuSetFD(struct wu_fd_set* fdset, sint32 fd); +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/padscore/padscore.cpp b/src/Cafe/OS/libs/padscore/padscore.cpp new file mode 100644 index 00000000..d14ad04b --- /dev/null +++ b/src/Cafe/OS/libs/padscore/padscore.cpp @@ -0,0 +1,790 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/HW/Espresso/PPCCallback.h" +#include "gui/wxgui.h" +#include "Cafe/OS/libs/padscore/padscore.h" +#include "Cafe/OS/libs/coreinit/coreinit_Time.h" +#include "Cafe/OS/libs/coreinit/coreinit_Alarm.h" +#include "Cafe/OS/libs/coreinit/coreinit_SystemInfo.h" +#include "input/InputManager.h" + +// KPAD + +enum class KPAD_ERROR : sint32 +{ + NONE = 0, + NO_CONTROLLER = -2, + NOT_INITIALIZED = -5, +}; + +// WPAD (Wii Controller stuff) + +enum WPADRumble +{ + kStopRumble = 0, + kStartRumble = 1, +}; + +enum class WPADBatteryLevel : uint8 +{ + FULL = 4, +}; + +enum class WPADLed : uint8 +{ + CHAN0 = (1 << 0), + CHAN1 = (1 << 1), + CHAN2 = (1 << 2), + CHAN3 = (1 << 3), +}; + +namespace padscore +{ + enum WPADState_t + { + kWPADStateMaster = 0, + kWPADStateShutdown, + kWPADStateInitializing, + kWPADStateAcquired, + kWPADStateReleased, + }; + KPADUnifiedWpadStatus_t* g_kpad_ringbuffer = nullptr; + uint32 g_kpad_ringbuffer_length = 0; + bool g_wpad_callback_by_kpad = false; + + WPADState_t g_wpad_state = kWPADStateMaster; + + struct { + coreinit::OSAlarm_t alarm; + bool kpad_initialized = false; + + struct WPADData + { + MEMPTR<void> extension_callback; + MEMPTR<void> connectCallback; + MEMPTR<void> sampling_callback; + MEMPTR<void> dpd_callback; + bool dpd_enabled = true; + + bool disconnectCalled = false; + + BtnRepeat btn_repeat{}; + } controller_data[InputManager::kMaxWPADControllers] = {}; + + int max_controllers = kWPADMaxControllers; // max bt controllers? + } g_padscore; +} + + +#pragma region WPAD + +void padscoreExport_WPADGetStatus(PPCInterpreter_t* hCPU) +{ + inputLog_printf("WPADGetStatus()"); + uint32 status = 1; + osLib_returnFromFunction(hCPU, status); +} + +void padscoreExport_WPADGetBatteryLevel(PPCInterpreter_t* hCPU) +{ + inputLog_printf("WPADGetBatteryLevel()"); + osLib_returnFromFunction(hCPU, (uint32)WPADBatteryLevel::FULL); +} + +void padscoreExport_WPADProbe(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + ppcDefineParamPtr(type, uint32be, 1); + + inputLog_printf("WPADProbe(%d)", channel); + + if(const auto controller = InputManager::instance().get_wpad_controller(channel)) + { + if(type) + *type = controller->get_device_type(); + + osLib_returnFromFunction(hCPU, WPAD_ERR_NONE); + } + else + { + osLib_returnFromFunction(hCPU, WPAD_ERR_NO_CONTROLLER); + } +} + +typedef struct +{ + uint32be dpd; + uint32be speaker; + uint32be attach; + uint32be lowBat; + uint32be nearempty; + betype<WPADBatteryLevel> batteryLevel; + betype<WPADLed> led; + uint8 protocol; + uint8 firmware; +}WPADInfo_t; + +static_assert(sizeof(WPADInfo_t) == 0x18); // unsure + +void padscoreExport_WPADGetInfoAsync(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + ppcDefineParamStructPtr(wpadInfo, WPADInfo_t, 1); + ppcDefineParamMPTR(callbackFunc, 2); + inputLog_printf("WPADGetInfoAsync(%d, 0x%08x, 0x%08x)", channel, wpadInfo, callbackFunc); + + if (channel < InputManager::kMaxWPADControllers) + { + if (const auto controller = InputManager::instance().get_wpad_controller(channel)) + { + wpadInfo->dpd = FALSE; + wpadInfo->speaker = FALSE; + wpadInfo->attach = FALSE; + wpadInfo->lowBat = FALSE; + wpadInfo->nearempty = FALSE; + wpadInfo->batteryLevel = WPADBatteryLevel::FULL; + wpadInfo->led = WPADLed::CHAN0; + + if(callbackFunc != MPTR_NULL) + coreinitAsyncCallback_add(callbackFunc, 2, channel, (uint32)KPAD_ERROR::NONE); + + osLib_returnFromFunction(hCPU, WPAD_ERR_NONE); + return; + } + } + else + { + debugBreakpoint(); + } + + if (callbackFunc != MPTR_NULL) + coreinitAsyncCallback_add(callbackFunc, 2, channel, (uint32)KPAD_ERROR::NO_CONTROLLER); + + osLib_returnFromFunction(hCPU, WPAD_ERR_NO_CONTROLLER); +} + +void padscoreExport_WPADRead(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + ppcDefineParamPtr(wpadStatus, WPADStatus_t, 1); + inputLog_printf("WPADRead(%d, %llx)", channel, wpadStatus); + + if (channel < InputManager::kMaxWPADControllers) + { + if(const auto controller = InputManager::instance().get_wpad_controller(channel) ) + { + controller->WPADRead(wpadStatus); + } + } + else + { + debugBreakpoint(); + } + osLib_returnFromFunction(hCPU, WPAD_ERR_NO_CONTROLLER); +} + +void padscoreExport_WPADSetDataFormat(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + ppcDefineParamU32(fmt, 1); + inputLog_printf("WPADSetDataFormat(%d, %d)", channel, fmt); + + if (channel < InputManager::kMaxWPADControllers) + { + if (const auto controller = InputManager::instance().get_wpad_controller(channel)) + controller->set_data_format((WPADDataFormat)fmt); + } + + osLib_returnFromFunction(hCPU, 0); +} + +void padscoreExport_WPADGetDataFormat(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + inputLog_printf("WPADGetDataFormat(%d)", channel); + + sint32 dataFormat = kDataFormat_CORE; + if (channel < InputManager::kMaxWPADControllers) + { + if (const auto controller = InputManager::instance().get_wpad_controller(channel)) + dataFormat = controller->get_data_format(); + } + else + { + debugBreakpoint(); + } + + osLib_returnFromFunction(hCPU, dataFormat); +} + +void padscoreExport_WPADGetInfo(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + ppcDefineParamStructPtr(wpadInfo, WPADInfo_t, 1); + inputLog_printf("WPADGetInfo(%d, 0x%08x)", channel, wpadInfo); + + if (channel < InputManager::kMaxWPADControllers) + { + if (const auto controller = InputManager::instance().get_wpad_controller(channel)) + { + wpadInfo->dpd = FALSE; + wpadInfo->speaker = FALSE; + wpadInfo->attach = FALSE; + wpadInfo->lowBat = FALSE; + wpadInfo->nearempty = FALSE; + wpadInfo->batteryLevel = WPADBatteryLevel::FULL; + wpadInfo->led = WPADLed::CHAN0; + + osLib_returnFromFunction(hCPU, WPAD_ERR_NONE); + return; + } + } + else + { + debugBreakpoint(); + } + + osLib_returnFromFunction(hCPU, WPAD_ERR_NO_CONTROLLER); +} + + +void padscoreExport_WPADIsMotorEnabled(PPCInterpreter_t* hCPU) +{ + inputLog_printf("WPADIsMotorEnabled()"); + osLib_returnFromFunction(hCPU, TRUE); +} + +void padscoreExport_WPADControlMotor(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + ppcDefineParamU32(command, 1); + inputLog_printf("WPADControlMotor(%d, %d)", channel, command); + + if (channel < InputManager::kMaxWPADControllers) + { + if (const auto controller = InputManager::instance().get_wpad_controller(channel)) + { + if( command == kStartRumble ) + controller->start_rumble(); + else + controller->stop_rumble(); + } + } + else + { + debugBreakpoint(); + } + + osLib_returnFromFunction(hCPU, 0); +} +#pragma endregion + + + +#pragma region KPAD +void padscoreExport_KPADGetUnifiedWpadStatus(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + ppcDefineParamPtr(status, KPADUnifiedWpadStatus_t, 1); + ppcDefineParamU32(count, 2); + + inputLog_printf("KPADGetUnifiedWpadStatus(%d, 0x%llx, 0x%x)", channel, status, count); + + if (channel < InputManager::kMaxWPADControllers) + { + memset(status, 0x00, sizeof(KPADUnifiedWpadStatus_t) * count); + + if (const auto controller = InputManager::instance().get_wpad_controller(channel)) + { + switch (controller->get_data_format()) + { + case WPAD_FMT_CORE: + case WPAD_FMT_CORE_ACC: + case WPAD_FMT_CORE_ACC_DPD: + { + status->fmt = controller->get_data_format(); + controller->WPADRead(&status->u.core); + break; + } + default: + debugBreakpoint(); + } + } + else + { + status->u.core.err = WPAD_ERR_NO_CONTROLLER; + } + } + else + { + debugBreakpoint(); + } + + osLib_returnFromFunction(hCPU, 0); +} + +void padscoreExport_KPADSetBtnRepeat(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + float delaySec = hCPU->fpr[1].fpr; + float pulseSec = hCPU->fpr[2].fpr; + inputLog_printf("KPADSetBtnRepeat(%d, %f, %f)", channel, delaySec, pulseSec); + + if (channel < InputManager::kMaxWPADControllers) + { + padscore::g_padscore.controller_data[channel].btn_repeat = { (int)delaySec, (int)pulseSec }; + } + + osLib_returnFromFunction(hCPU, 0); +} + +void padscoreExport_KPADSetSamplingCallback(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + ppcDefineParamMPTR(callback, 1); + + inputLog_printf("KPADSetSamplingCallback(%d, 0x%x)", channel, callback); + + if (channel >= InputManager::kMaxWPADControllers) + { + debugBreakpoint(); + osLib_returnFromFunction(hCPU, MPTR_NULL); + return; + } + + const auto old_callback = padscore::g_padscore.controller_data[channel].sampling_callback; + padscore::g_padscore.controller_data[channel].sampling_callback = callback; + osLib_returnFromFunction(hCPU, old_callback.GetMPTR()); +} + +void padscoreExport_WPADControlDpd(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + ppcDefineParamU32(command, 1); + ppcDefineParamMPTR(callback, 2); + + inputLog_printf("WPADControlDpd(%d, %d, 0x%x)", channel, command, callback); + + if (channel < InputManager::kMaxWPADControllers) + { + if (const auto controller = InputManager::instance().get_wpad_controller(channel)) + { + padscore::g_padscore.controller_data[channel].dpd_callback = callback; + if (callback) + { + coreinitAsyncCallback_add(callback, 2, channel, WPAD_ERR_NONE); + } + + osLib_returnFromFunction(hCPU, WPAD_ERR_NONE); + return; + } + } + + osLib_returnFromFunction(hCPU, WPAD_ERR_NO_CONTROLLER); +} + +void padscoreExport_WPADSetExtensionCallback(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + ppcDefineParamMPTR(callback, 1); + + inputLog_printf("WPADSetExtensionCallback(%d, 0x%x)", channel, callback); + + if (channel >= InputManager::kMaxWPADControllers) + { + debugBreakpoint(); + osLib_returnFromFunction(hCPU, MPTR_NULL); + return; + } + + const auto old_callback = padscore::g_padscore.controller_data[channel].extension_callback; + padscore::g_padscore.controller_data[channel].extension_callback = callback; + osLib_returnFromFunction(hCPU, old_callback.GetMPTR()); +} + +void padscoreExport_KPADSetConnectCallback(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + ppcDefineParamMPTR(callback, 1); + + inputLog_printf("KPADSetConnectCallback(%d, 0x%x)",channel, callback); + + if (channel >= InputManager::kMaxWPADControllers) + { + debugBreakpoint(); + osLib_returnFromFunction(hCPU, MPTR_NULL); + return; + } + + const auto old_callback = padscore::g_padscore.controller_data[channel].connectCallback; + padscore::g_padscore.controller_data[channel].connectCallback = callback; + osLib_returnFromFunction(hCPU, old_callback.GetMPTR()); +} + +bool g_kpadIsInited = true; +sint32 _KPADRead(uint32 channel, KPADStatus_t* samplingBufs, uint32 length, betype<KPAD_ERROR>* errResult) +{ + if (channel >= InputManager::kMaxWPADControllers) + { + debugBreakpoint(); + return 0; + } + + if (g_kpadIsInited == false) + { + if (errResult) + *errResult = KPAD_ERROR::NOT_INITIALIZED; + + return 0; + } + + const auto controller = InputManager::instance().get_wpad_controller(channel); + if (!controller) + { + if (errResult) + *errResult = KPAD_ERROR::NO_CONTROLLER; + + return 0; + } + + memset(samplingBufs, 0x00, sizeof(KPADStatus_t)); + samplingBufs->wpadErr = WPAD_ERR_NONE; + samplingBufs->data_format = controller->get_data_format(); + samplingBufs->devType = controller->get_device_type(); + if(!g_inputConfigWindowHasFocus) + { + const auto btn_repeat = padscore::g_padscore.controller_data[channel].btn_repeat; + controller->KPADRead(*samplingBufs, btn_repeat); + } + + if (errResult) + *errResult = KPAD_ERROR::NONE; + + return 1; +} + +void padscoreExport_KPADReadEx(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + ppcDefineParamPtr(kpadStatus, KPADStatus_t, 1); + ppcDefineParamU32(length, 2); + ppcDefineParamPtr(errResult, betype<KPAD_ERROR>, 3); + inputLog_printf("KPADReadEx(%d, 0x%x)", channel, length); + + sint32 samplesRead = _KPADRead(channel, kpadStatus, length, errResult); + osLib_returnFromFunction(hCPU, samplesRead); +} + +bool debugUseDRC1 = true; +void padscoreExport_KPADRead(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + ppcDefineParamPtr(kpadStatus, KPADStatus_t, 1); + ppcDefineParamU32(length, 2); + inputLog_printf("KPADRead(%d, 0x%x)", channel, length); + + sint32 samplesRead = _KPADRead(channel, kpadStatus, length, nullptr); + osLib_returnFromFunction(hCPU, samplesRead); +} + + + + + + +#pragma endregion + + + +namespace padscore +{ + void export_KPADEnableDPD(PPCInterpreter_t* hCPU) + { + ppcDefineParamS32(channel, 0); + inputLog_printf("KPADEnableDPD(%d)", channel); + cemu_assert_debug(0 <= channel && channel < InputManager::kMaxWPADControllers); + + if(const auto controller = InputManager::instance().get_wpad_controller(channel)) + { + if (controller->get_data_format() != kDataFormat_FREESTYLE && controller->get_data_format() != 0x1F) + g_padscore.controller_data[channel].dpd_enabled = true; + } + + + osLib_returnFromFunction(hCPU, 0); + } + + void export_KPADGetMplsWorkSize(PPCInterpreter_t* hCPU) + { + inputLog_printf("KPADGetMplsWorkSize()"); + osLib_returnFromFunction(hCPU, 0x5FE0); + } + + void KPADInitEx(KPADUnifiedWpadStatus_t ring_buffer[], uint32 length) + { + if (g_padscore.kpad_initialized) + return; + + for (uint32 i = 0; i < InputManager::kMaxWPADControllers; i++) + { + g_padscore.controller_data[i].dpd_enabled = true; + + + } + + g_kpad_ringbuffer = ring_buffer; + g_kpad_ringbuffer_length = length; + g_padscore.kpad_initialized = true; + } + + void export_KPADInit(PPCInterpreter_t* hCPU) + { + inputLog_printf("KPADInit()"); + KPADInitEx(nullptr, 0); + osLib_returnFromFunction(hCPU, 0); + } + + void export_KPADInitEx(PPCInterpreter_t* hCPU) + { + ppcDefineParamMEMPTR(ring_buffer, KPADUnifiedWpadStatus_t, 0); + ppcDefineParamU32(length, 1); + inputLog_printf("KPADInitEx(0x%08x, 0x%x)", ring_buffer.GetMPTR(), length); + KPADInitEx(ring_buffer.GetPtr(), length); + osLib_returnFromFunction(hCPU, 0); + } + + void WPADSetCallbackByKPAD(bool state) + { + g_wpad_callback_by_kpad = state; + } + + void export_WPADSetCallbackByKPAD(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(state, 0); + inputLog_printf("WPADSetCallbackByKPAD(%d)", state); + WPADSetCallbackByKPAD(state); + osLib_returnFromFunction(hCPU, 0); + } + + void export_KPADGetMaxControllers(PPCInterpreter_t* hCPU) + { + inputLog_printf("KPADGetMaxControllers()"); + sint32 max_controllers = g_padscore.max_controllers; + osLib_returnFromFunction(hCPU, max_controllers); + } + + bool WPADIsUsedCallbackByKPAD() + { + return g_wpad_callback_by_kpad == TRUE; + } + + void* WPADSetSamplingCallback(sint32 channel, void* callback) + { + cemu_assert_debug(0 <= channel && channel < kKPADMaxControllers); + + const auto result = g_padscore.controller_data[channel].sampling_callback; + g_padscore.controller_data[channel].sampling_callback = callback; + return result.GetPtr(); + } + + void export_KPADSetMaxControllers(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(new_max_count, 0); + inputLog_printf("KPADSetMaxControllers(%d)", new_max_count); + + if (new_max_count != kKPADMaxControllers && new_max_count != kWPADMaxControllers) + { + debugBreakpoint(); + osLib_returnFromFunction(hCPU, -4); + return; + } + + + const uint32 max_controllers = g_padscore.max_controllers; + if (max_controllers == new_max_count) + { + osLib_returnFromFunction(hCPU, 0); + return; + } + + WPADSetCallbackByKPAD(FALSE); + for (sint32 i = 0; i < kKPADMaxControllers; i++) + { + //WPADSetSamplingCallback(i, 0); TODO + } + + g_padscore.max_controllers = new_max_count; + + WPADSetCallbackByKPAD(true); + osLib_returnFromFunction(hCPU, new_max_count); + } + + void export_WPADInit() + { + if (g_wpad_state != kWPADStateShutdown) + { + if (g_wpad_state == kWPADStateInitializing || g_wpad_state == kWPADStateAcquired) + return; + } + g_wpad_state = kWPADStateInitializing; + } + + + enum class WPADStatus : sint32 + { + Success = 0, + NoController = -1, + Busy = -2, + }; + + WPADStatus WPADIsMplsAttached(sint32 index, uint32be* attached, void* callback) + { + if (index >= kKPADMaxControllers) + return WPADStatus::NoController; + + const auto controller = InputManager::instance().get_wpad_controller(index); + *attached = controller && controller->is_mpls_attached(); + + if (callback) + PPCCoreCallback(MEMPTR(callback), index, controller ? WPADStatus::Success : WPADStatus::NoController); + + return WPADStatus::Success; + } + + struct WPADAcc + { + sint32be x; + sint32be y; + sint32be z; + }; + void WPADGetAccGravityUnit(sint32 index, uint32 type, WPADAcc* acc) + { + if (index >= kKPADMaxControllers) + return; + + // default values for wiimote usually are around 99 + acc->x = 99; + acc->y = 99; + acc->z = 99; + } + + +#pragma endregion + + void TickFunction(PPCInterpreter_t* hCPU) + { + auto& instance = InputManager::instance(); + // test for connected/disconnected controllers + for (auto i = 0; i < InputManager::kMaxWPADControllers; ++i) + { + if (g_padscore.controller_data[i].connectCallback) + { + if(!g_padscore.controller_data[i].disconnectCalled) + { + g_padscore.controller_data[i].disconnectCalled = true; + inputLog_printf("Calling WPADConnectCallback(%d, %d)", i, WPAD_ERR_NO_CONTROLLER); + PPCCoreCallback(g_padscore.controller_data[i].connectCallback, i, WPAD_ERR_NO_CONTROLLER); + continue; + } + + if (const auto controller = instance.get_wpad_controller(i)) + { + if (controller->m_status == WPADController::ConnectCallbackStatus::ReportDisconnect || controller->was_home_button_down()) // fixed! + { + controller->m_status = WPADController::ConnectCallbackStatus::ReportConnect; + + inputLog_printf("Calling WPADConnectCallback(%d, %d)", i, WPAD_ERR_NO_CONTROLLER); + PPCCoreCallback(g_padscore.controller_data[i].connectCallback, i, WPAD_ERR_NO_CONTROLLER); + + } + else if (controller->m_status == WPADController::ConnectCallbackStatus::ReportConnect) + { + controller->m_status = WPADController::ConnectCallbackStatus::None; + inputLog_printf("Calling WPADConnectCallback(%d, %d)", i, WPAD_ERR_NONE); + PPCCoreCallback(g_padscore.controller_data[i].connectCallback, i, WPAD_ERR_NONE); + } + } + } + } + + // test for connected/disconnected extensions + for (auto i = 0; i < InputManager::kMaxWPADControllers; ++i) + { + if (g_padscore.controller_data[i].extension_callback) + { + if (const auto controller = instance.get_wpad_controller(i)) + { + if (controller->m_extension_status == WPADController::ConnectCallbackStatus::ReportConnect) + { + controller->m_extension_status = WPADController::ConnectCallbackStatus::None; + inputLog_printf("Calling WPADextensionCallback(%d)", i); + PPCCoreCallback(g_padscore.controller_data[i].extension_callback, i, controller->get_device_type()); + } + } + } + } + + // call sampling callback + for (auto i = 0; i < InputManager::kMaxWPADControllers; ++i) + { + if (g_padscore.controller_data[i].sampling_callback) { + if (const auto controller = instance.get_wpad_controller(i)) + { + inputLog_printf("Calling WPADsamplingCallback(%d)", i); + PPCCoreCallback(g_padscore.controller_data[i].sampling_callback, i); + } + } + } + + osLib_returnFromFunction(hCPU, 0); + } + void start() + { + OSCreateAlarm(&g_padscore.alarm); + const uint64 start_tick = coreinit::coreinit_getOSTime(); + const uint64 period_tick = coreinit::EspressoTime::GetTimerClock(); // once a second + MPTR handler = PPCInterpreter_makeCallableExportDepr(TickFunction); + OSSetPeriodicAlarm(&g_padscore.alarm, start_tick, period_tick, handler); + } + + void load() + { + cafeExportRegister("padscore", WPADIsMplsAttached, LogType::Input); + cafeExportRegister("padscore", WPADGetAccGravityUnit, LogType::Input); + + // wpad + //osLib_addFunction("padscore", "WPADInit", padscore::export_WPADInit); + + // kpad + osLib_addFunction("padscore", "KPADSetMaxControllers", padscore::export_KPADSetMaxControllers); + osLib_addFunction("padscore", "KPADGetMaxControllers", padscore::export_KPADGetMaxControllers); + osLib_addFunction("padscore", "KPADEnableDPD", padscore::export_KPADEnableDPD); + osLib_addFunction("padscore", "KPADGetMplsWorkSize", padscore::export_KPADGetMplsWorkSize); + osLib_addFunction("padscore", "KPADInit", padscore::export_KPADInit); + osLib_addFunction("padscore", "KPADInitEx", padscore::export_KPADInitEx); + + osLib_addFunction("padscore", "KPADSetConnectCallback", padscoreExport_KPADSetConnectCallback); + osLib_addFunction("padscore", "KPADReadEx", padscoreExport_KPADReadEx); + osLib_addFunction("padscore", "KPADRead", padscoreExport_KPADRead); + osLib_addFunction("padscore", "KPADGetUnifiedWpadStatus", padscoreExport_KPADGetUnifiedWpadStatus); + osLib_addFunction("padscore", "KPADSetSamplingCallback", padscoreExport_KPADSetSamplingCallback); + osLib_addFunction("padscore", "KPADSetBtnRepeat", padscoreExport_KPADSetBtnRepeat); + + osLib_addFunction("padscore", "WPADGetBatteryLevel", padscoreExport_WPADGetBatteryLevel); + osLib_addFunction("padscore", "WPADControlMotor", padscoreExport_WPADControlMotor); + osLib_addFunction("padscore", "WPADIsMotorEnabled", padscoreExport_WPADIsMotorEnabled); + osLib_addFunction("padscore", "WPADGetStatus", padscoreExport_WPADGetStatus); + osLib_addFunction("padscore", "WPADProbe", padscoreExport_WPADProbe); + osLib_addFunction("padscore", "WPADGetInfoAsync", padscoreExport_WPADGetInfoAsync); + osLib_addFunction("padscore", "WPADGetInfo", padscoreExport_WPADGetInfo); + osLib_addFunction("padscore", "WPADSetConnectCallback", padscoreExport_KPADSetConnectCallback); + osLib_addFunction("padscore", "WPADSetDataFormat", padscoreExport_WPADSetDataFormat); + osLib_addFunction("padscore", "WPADGetDataFormat", padscoreExport_WPADGetDataFormat); + osLib_addFunction("padscore", "WPADRead", padscoreExport_WPADRead); + osLib_addFunction("padscore", "WPADSetExtensionCallback", padscoreExport_WPADSetExtensionCallback); + osLib_addFunction("padscore", "WPADSetSamplingCallback", padscoreExport_KPADSetSamplingCallback); + osLib_addFunction("padscore", "WPADControlDpd", padscoreExport_WPADControlDpd); + + osLib_addFunction("padscore", "WPADSetCallbackByKPAD", padscore::export_WPADSetCallbackByKPAD); + } + +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/padscore/padscore.h b/src/Cafe/OS/libs/padscore/padscore.h new file mode 100644 index 00000000..efb1fbca --- /dev/null +++ b/src/Cafe/OS/libs/padscore/padscore.h @@ -0,0 +1,283 @@ +#pragma once + +#include "util/math/vector3.h" + +namespace padscore +{ + void start(); + void load(); +} + +constexpr int kWPADMaxControllers = 4; +constexpr int kKPADMaxControllers = 7; + +#define WPAD_ERR_NONE 0 +#define WPAD_ERR_NO_CONTROLLER -1 +#define WPAD_ERR_BUSY -2 +#define WPAD_ERR_INVALID -4 + +#define WPAD_PRESS_UNITS 4 + +#define WPAD_FMT_CORE 0 +#define WPAD_FMT_CORE_ACC 1 +#define WPAD_FMT_CORE_ACC_DPD 2 + +typedef struct +{ + float x; + float y; + float z; +}padVec3D_t; + +static_assert(sizeof(padVec3D_t) == 0xC); + +typedef struct beVec3D +{ + float32be x; + float32be y; + float32be z; + + beVec3D() = default; + + beVec3D(const Vector3<float>& v) + : x(v.x), y(v.y), z(v.z) {} + + beVec3D(const float32be& x, const float32be& y, const float32be& z) + : x(x), y(y), z(z) {} + +}beVec3D_t; + +static_assert(sizeof(beVec3D_t) == 0xC); + +typedef struct +{ + float x; + float y; +}padVec2D_t; + +static_assert(sizeof(padVec2D_t) == 0x8); + +typedef struct +{ + float32be x; + float32be y; +}beVec2D_t; + +static_assert(sizeof(beVec2D_t) == 0x8); + +union KPADEXStatus_t +{ + struct + { + beVec2D_t stick; + beVec3D_t acc; + float32be accValue; + float32be accSpeed; + }fs; + struct + { + uint32be hold; + uint32be trig; + uint32be release; + beVec2D_t lstick; + beVec2D_t rstick; + float32be ltrigger; + float32be rtrigger; + }cl; + struct + { + uint32be hold; + uint32be trig; + uint32be release; + beVec2D_t lstick; + beVec2D_t rstick; + uint32be charge; + uint32be cable; + }uc; + struct + { + uint32be hold; + uint32be trig; + uint32be release; + }cm; + uint8 _padding[0x50]; +}; + +static_assert(sizeof(KPADEXStatus_t) == 0x50); + +struct KPADMPDir_t +{ + padVec3D_t X; + padVec3D_t Y; + padVec3D_t Z; +}; + +static_assert(sizeof(KPADMPDir_t) == 0x24); + +struct KPADMPStatus_t +{ + padVec3D_t mpls; + padVec3D_t angle; + KPADMPDir_t dir; +}; + +static_assert(sizeof(KPADMPStatus_t) == 0x3C); + +struct KPADStatus_t +{ + uint32be hold; + uint32be trig; + uint32be release; + beVec3D_t acc; + float32be acc_value; + float32be acc_speed; + beVec2D_t pos; + beVec2D_t vec; + float32be speed; + beVec2D_t horizon; + beVec2D_t horiVec; + float32be horiSpeed; + float32be dist; + float32be distVec; + float32be distSpeed; + beVec2D_t accVertical; + uint8 devType; + sint8 wpadErr; + sint8 dpd_valid_fg; + uint8 data_format; + KPADEXStatus_t ex_status; + KPADMPStatus_t mpls; + uint8 _unused[4]; +}; + +static_assert(sizeof(KPADStatus_t) == 0xF0); + +#define WPAD_DPD_MAX_OBJECTS (4) + +struct DPDObject_t +{ + uint16be x; + uint16be y; + uint16be size; + uint8 traceId; + uint8 padding; +}; + +static_assert(sizeof(DPDObject_t) == 0x8); + +struct WPADStatus_t +{ + uint16be button; + uint16be accX; + uint16be accY; + uint16be accZ; + DPDObject_t obj[WPAD_DPD_MAX_OBJECTS]; + uint8 dev; + sint8 err; +}; + +static_assert(sizeof(WPADStatus_t) == 0x2A); + +struct WPADFSStatus_t : WPADStatus_t +{ + uint16be fsAccX; + uint16be fsAccY; + uint16be fsAccZ; + sint8 fsStickX; + sint8 fsStickY; +}; + +struct WPADCLStatus_t : WPADStatus_t +{ + uint16be clButton; + uint16be clLStickX; + uint16be clLStickY; + uint16be clRStickX; + uint16be clRStickY; + uint8 clTriggerL; + uint8 clTriggerR; +}; + +struct WPADUCStatus_t : WPADStatus_t +{ + uint8 padding1[2]; // unsure + uint32be ucButton; + uint16be ucLStickX; + uint16be ucLStickY; + uint16be ucRStickX; + uint16be ucRStickY; + uint32be charge; + uint32be cable; +}; + +static_assert(sizeof(WPADUCStatus_t) == 0x40); + +struct WPADTRStatus_t : WPADStatus_t +{ + uint16 trButton; + uint8 brake; + uint8 mascon; +}; + +struct WPADBLStatus_t : WPADStatus_t +{ + uint16be press[WPAD_PRESS_UNITS]; + sint8 temp; + uint8 battery; +} ; + +static_assert(sizeof(WPADBLStatus_t) == 0x34); + +struct WPADMPStatus_t : WPADStatus_t +{ + union + { + // for Nunchuk + struct + { + uint16be fsAccX; + uint16be fsAccY; + uint16be fsAccZ; + sint8 fsStickX; + sint8 fsStickY; + }fs; + + // for Classic + struct + { + uint16be clButton; + uint16be clLStickX; + uint16be clLStickY; + uint16be clRStickX; + uint16be clRStickY; + uint8 clTriggerL; + uint8 clTriggerR; + }cl; + }status; + uint8 stat; + uint8 _ukn; + uint16be pitch; + uint16be yaw; + uint16be roll; +}; + +static_assert(sizeof(WPADMPStatus_t) == 0x3E); + +typedef struct KPADUnifiedWpadStatus +{ + union + { + WPADStatus_t core; + WPADFSStatus_t fs; + WPADCLStatus_t cl; + WPADTRStatus_t tr; + WPADBLStatus_t bl; + WPADMPStatus_t mp; + }u; + uint8 fmt; + uint8 padding; + uint32 _40; // padding? +}KPADUnifiedWpadStatus_t; + +static_assert(sizeof(KPADUnifiedWpadStatus_t) == 0x44); + diff --git a/src/Cafe/OS/libs/proc_ui/proc_ui.cpp b/src/Cafe/OS/libs/proc_ui/proc_ui.cpp new file mode 100644 index 00000000..caba7237 --- /dev/null +++ b/src/Cafe/OS/libs/proc_ui/proc_ui.cpp @@ -0,0 +1,30 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "proc_ui.h" + +#define PROCUI_STATUS_FOREGROUND 0 +#define PROCUI_STATUS_BACKGROUND 1 +#define PROCUI_STATUS_RELEASING 2 +#define PROCUI_STATUS_EXIT 3 + +uint32 ProcUIProcessMessages() +{ + return PROCUI_STATUS_FOREGROUND; +} + + +uint32 ProcUIInForeground(PPCInterpreter_t* hCPU) +{ + return 1; // true means application is in foreground +} + +uint32 ProcUIRegisterCallback(uint32 message, MPTR callback, void* data, sint32 ukn) +{ + return 0; +} + +void procui_load() +{ + cafeExportRegister("proc_ui", ProcUIRegisterCallback, LogType::ProcUi); + cafeExportRegister("proc_ui", ProcUIProcessMessages, LogType::ProcUi); + cafeExportRegister("proc_ui", ProcUIInForeground, LogType::ProcUi); +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/proc_ui/proc_ui.h b/src/Cafe/OS/libs/proc_ui/proc_ui.h new file mode 100644 index 00000000..4463c5d3 --- /dev/null +++ b/src/Cafe/OS/libs/proc_ui/proc_ui.h @@ -0,0 +1,2 @@ + +void procui_load(); \ No newline at end of file diff --git a/src/Cafe/OS/libs/snd_core/ax.h b/src/Cafe/OS/libs/snd_core/ax.h new file mode 100644 index 00000000..9f7a1e1c --- /dev/null +++ b/src/Cafe/OS/libs/snd_core/ax.h @@ -0,0 +1,391 @@ +#pragma once + +#include "Cafe/OS/libs/coreinit/coreinit.h" // for OSThread* +#include "util/helpers/fspinlock.h" + +struct PPCInterpreter_t; + +namespace snd_core +{ + // sndcore2 - AX init param config + const int AX_RENDERER_FREQ_32KHZ = 0; + const int AX_RENDERER_FREQ_48KHZ = 1; + const int AX_FRAMELENGTH_3MS = 0; + + const int AX_SAMPLES_PER_3MS_48KHZ = (144); // 48000*3/1000 + const int AX_SAMPLES_PER_3MS_32KHZ = (96); // 32000*3/1000 + const int AX_SAMPLES_MAX = AX_SAMPLES_PER_3MS_48KHZ; // the maximum amount of samples in a single frame + + const int AX_DEV_TV = 0; + const int AX_DEV_DRC = 1; + const int AX_DEV_RMT = 2; + const int AX_DEV_COUNT = 3; + + const int AX_UPSAMPLE_STAGE_BEFORE_FINALMIX = 0; + const int AX_UPSAMPLE_STAGE_AFTER_FINALMIX = 1; + + const int AX_PIPELINE_SINGLE = 0; + + struct AXINITPARAM + { + uint32be freq; // AX_RENDERER_FREQ_* + uint32be frameLength; // AX_FRAMELENGTH_* + uint32be pipelineMode; // AX_PIPELINE_* + }; + + // maximum number of supported channels per device + const int AX_TV_CHANNEL_COUNT = 6; + const int AX_DRC_CHANNEL_COUNT = 4; + const int AX_RMT_CHANNEL_COUNT = 1; + + const int AX_APP_FRAME_CALLBACK_MAX = 64; + + const int AX_MODE_STEREO = 0; + const int AX_MODE_SURROUND = 1; + const int AX_MODE_DPL2 = 2; + const int AX_MODE_6CH = 3; + const int AX_MODE_MONO = 5; + + const int AX_PRIORITY_MAX = 32; + const int AX_PRIORITY_FREE = 0; + const int AX_PRIORITY_NODROP = 31; + const int AX_PRIORITY_LOWEST = 1; + const int AX_MAX_VOICES = 96; + + const int AX_AUX_BUS_COUNT = 3; + const int AX_MAX_NUM_BUS = 4; + + const int AX_FORMAT_ADPCM = 0x0; + const int AX_FORMAT_PCM16 = 0xA; + const int AX_FORMAT_PCM8 = 0x19; + + const int AX_LPF_OFF = 0x0; + + const int AX_BIQUAD_OFF = 0x0; + + const int AX_SRC_TYPE_NONE = 0x0; + const int AX_SRC_TYPE_LINEAR = 0x1; + const int AX_SRC_TYPE_LOWPASS1 = 0x2; + const int AX_SRC_TYPE_LOWPASS2 = 0x3; + const int AX_SRC_TYPE_LOWPASS3 = 0x4; + + const int AX_FILTER_MODE_TAP = 0x0; + const int AX_FILTER_MODE_LINEAR = 0x1; + const int AX_FILTER_MODE_NONE = 0x2; + + const int AX_FILTER_LOWPASS_8K = 0x0; + const int AX_FILTER_LOWPASS_12K = 0x1; + const int AX_FILTER_LOWPASS_16K = 0x2; + + void loadExports(); + bool isInitialized(); + void reset(); + + // AX VPB + + struct AXAUXCBCHANNELINFO + { + /* +0x00 */ uint32be numChannels; + /* +0x04 */ uint32be numSamples; + }; + + struct AXPBOFFSET_t + { + /* +0x00 | +0x34 */ uint16 format; + /* +0x02 | +0x36 */ uint16 loopFlag; + /* +0x04 | +0x38 */ uint32 loopOffset; + /* +0x08 | +0x3C */ uint32 endOffset; + /* +0x0C | +0x40 */ uint32 currentOffset; + /* +0x10 | +0x44 */ MPTR samples; + }; + + struct AXPBVE + { + uint16be currentVolume; + sint16be currentDelta; + }; + + struct AXVPBItd + { + uint8 ukn[64]; + }; + + struct AXVPB + { + /* +0x00 */ uint32be index; + /* +0x04 */ uint32be playbackState; + /* +0x08 */ uint32be ukn08; + /* +0x0C */ uint32be mixerSelect; + /* +0x10 */ MEMPTR<AXVPB> next; + /* +0x14 */ MEMPTR<AXVPB> prev; + /* +0x18 */ uint32be ukn18; + /* +0x1C */ uint32be priority; + /* +0x20 */ uint32be callback; + /* +0x24 */ uint32be userParam; + /* +0x28 */ uint32be sync; + /* +0x2C */ uint32be depop; + /* +0x30 */ MEMPTR<AXVPBItd> itd; + /* +0x34 */ AXPBOFFSET_t offsets; + /* +0x48 */ uint32be callbackEx; // AXAcquireVoiceEx + /* +0x4C */ uint32be ukn4C_dropReason; + /* +0x50 */ float32be dspLoad; + /* +0x54 */ float32be ppcLoad; + }; + + struct AXPBLPF_t + { + /* +0x00 */ uint16 on; + /* +0x02 */ sint16 yn1; + /* +0x04 */ sint16 a0; + /* +0x06 */ sint16 b0; + }; + + struct AXPBBIQUAD_t + { + /* +0x00 */ uint16 on; + /* +0x02 */ sint16 xn1; + /* +0x04 */ sint16 xn2; + /* +0x06 */ sint16 yn1; + /* +0x08 */ sint16 yn2; + /* +0x0A */ uint16 b0; + /* +0x0C */ uint16 b1; + /* +0x0E */ uint16 b2; + /* +0x10 */ uint16 a1; + /* +0x12 */ uint16 a2; + }; + + struct AXRemixMatrix_t + { + /* +0x00 */ uint32be channelIn; + /* +0x04 */ uint32be channelOut; + /* +0x08 */ MEMPTR<float32be> matrix; + }; + + struct AXRemixMatrices_t + { + /* +0x00 */ AXRemixMatrix_t deviceEntry[3]; // tv, drc, remote + }; + + struct AXPBADPCM_t + { + uint16 a[16]; + uint16 gain; + uint16 scale; + uint16 yn1; + uint16 yn2; + }; + + struct AXPBADPCMLOOP_t + { + uint16 loopScale; + uint16 loopYn1; + uint16 loopYn2; + }; + + struct AXPBSRC_t + { + /* +0x1B8 */ uint16 ratioHigh; + /* +0x1BA */ uint16 ratioLow; + /* +0x1BC */ uint16 currentFrac; + /* +0x1BE */ uint16 historySamples[4]; + + uint32 GetSrcRatio32() const + { + uint32 offset = (uint32)_swapEndianU16(ratioHigh) << 16; + return offset | (uint32)_swapEndianU16(ratioLow); + } + }; + + struct AXCHMIX_DEPR + { + uint16 vol; + sint16 delta; + }; + + struct AXCHMIX2 + { + uint16be vol; + sint16be delta; + }; + + void AXVPB_Init(); + sint32 AXIsValidDevice(sint32 device, sint32 deviceIndex); + + AXVPB* AXAcquireVoiceEx(uint32 priority, MPTR callbackEx, MPTR userParam); + void AXFreeVoice(AXVPB* vpb); + + bool AXUserIsProtected(); + sint32 AXUserBegin(); + sint32 AXUserEnd(); + + bool AXVoiceProtection_IsProtectedByAnyThread(AXVPB* vpb); + bool AXVoiceProtection_IsProtectedByCurrentThread(AXVPB* vpb); + + sint32 AXVoiceBegin(AXVPB* voice); + sint32 AXVoiceEnd(AXVPB* voice); + sint32 AXSetVoiceDeviceMix(AXVPB* vpb, sint32 device, sint32 deviceIndex, AXCHMIX_DEPR* mix); + void AXSetVoiceState(AXVPB* vpb, sint32 voiceState); + sint32 AXIsVoiceRunning(AXVPB* vpb); + void AXSetVoiceType(AXVPB* vpb, uint16 voiceType); + void AXSetVoiceAdpcm(AXVPB* vpb, AXPBADPCM_t* adpcm); + void AXSetVoiceAdpcmLoop(AXVPB* vpb, AXPBADPCMLOOP_t* adpcmLoop); + void AXSetVoiceSrc(AXVPB* vpb, AXPBSRC_t* src); + void AXSetVoiceSrcType(AXVPB* vpb, uint32 srcType); + sint32 AXSetVoiceSrcRatio(AXVPB* vpb, float ratio); + void AXSetVoiceVe(AXVPB* vpb, AXPBVE* ve); + void AXComputeLpfCoefs(uint32 freq, uint16be* a0, uint16be* b0); + void AXSetVoiceLpf(AXVPB* vpb, AXPBLPF_t* lpf); + void AXSetVoiceLpfCoefs(AXVPB* vpb, uint16 a0, uint16 b0); + void AXSetVoiceBiquad(AXVPB* vpb, AXPBBIQUAD_t* biquad); + void AXSetVoiceBiquadCoefs(AXVPB* vpb, uint16 b0, uint16 b1, uint16 b2, uint16 a1, uint16 a2); + void AXSetVoiceOffsets(AXVPB* vpb, AXPBOFFSET_t* pbOffset); + void AXGetVoiceOffsets(AXVPB* vpb, AXPBOFFSET_t* pbOffset); + void AXGetVoiceOffsetsEx(AXVPB* vpb, AXPBOFFSET_t* pbOffset, MPTR sampleBase); + void AXSetVoiceCurrentOffset(AXVPB* vpb, uint32 currentOffset); + void AXSetVoiceLoopOffset(AXVPB* vpb, uint32 loopOffset); + void AXSetVoiceEndOffset(AXVPB* vpb, uint32 endOffset); + void AXSetVoiceCurrentOffsetEx(AXVPB* vpb, uint32 currentOffset, MPTR sampleBase); + void AXSetVoiceLoopOffsetEx(AXVPB* vpb, uint32 loopOffset, MPTR sampleBase); + void AXSetVoiceEndOffsetEx(AXVPB* vpb, uint32 endOffset, MPTR sampleBase); + uint32 AXGetVoiceCurrentOffsetEx(AXVPB* vpb, MPTR sampleBase); + void AXSetVoiceLoop(AXVPB* vpb, uint16 loopState); + + // AXIst + + void AXIst_Init(); + void AXIst_ThreadEntry(PPCInterpreter_t* hCPU); + void AXIst_QueueFrame(); + + void AXResetCallbacks(); + + bool AXIst_IsFrameBeingProcessed(); + + sint32 AXSetDeviceUpsampleStage(sint32 device, int upsampleStage); + sint32 AXGetDeviceUpsampleStage(sint32 device, uint32be* upsampleStage); + + sint32 AXGetDeviceFinalMixCallback(sint32 device, uint32be* funcAddrPtr); + sint32 AXRegisterDeviceFinalMixCallback(sint32 device, MPTR funcAddr); + + sint32 AXSetDeviceRemixMatrix(sint32 deviceId, uint32 inputChannelCount, uint32 outputChannelCount, const MEMPTR<float32be>& matrix); + sint32 AXGetDeviceRemixMatrix(uint32 deviceId, uint32 inputChannelCount, uint32 outputChannelCount, MEMPTR<MEMPTR<float32be>>& matrix); + + sint32 AXRegisterAppFrameCallback(MPTR funcAddr); + sint32 AXDeregisterAppFrameCallback(MPTR funcAddr); + MPTR AXRegisterFrameCallback(MPTR funcAddr); + + sint32 AXGetInputSamplesPerFrame(); + sint32 AXGetInputSamplesPerSec(); + + // AXOut + + void resetNumProcessedFrames(); + uint32 getNumProcessedFrames(); + + void AXOut_Init(); + + sint32 AIGetSamplesPerChannel(uint32 device); + sint32 AIGetChannelCount(uint32 device); + sint16* AIGetCurrentDMABuffer(uint32 device); + + void AXOut_SubmitTVFrame(sint32 frameIndex); + void AXOut_SubmitDRCFrame(sint32 frameIndex); + + sint32 AXGetDeviceMode(sint32 device); + + extern uint32 numProcessedFrames; + + // AUX + + void AXAux_Init(); + + void AXAux_Process(); + + sint32be* AXAux_GetInputBuffer(sint32 device, sint32 deviceIndex, sint32 auxBus); + sint32be* AXAux_GetOutputBuffer(sint32 device, sint32 deviceIndex, sint32 auxBus); + + sint32 AXGetAuxCallback(sint32 device, sint32 deviceIndex, uint32 auxBusIndex, MEMPTR<uint32be> funcPtrOut, MEMPTR<uint32be> contextPtrOut); + sint32 AXRegisterAuxCallback(sint32 device, sint32 deviceIndex, uint32 auxBusIndex, MPTR funcMPTR, MPTR userParam); + + sint32 AXSetAuxReturnVolume(uint32 device, uint32 deviceIndex, uint32 auxBus, uint16 volume); + + extern uint16 __AXTVAuxReturnVolume[AX_AUX_BUS_COUNT]; + + // AXMix + // mixer select constants (for AXSetDefaultMixerSelect / AXGetDefaultMixerSelect) + const int AX_MIXER_SELECT_DSP = (0); + const int AX_MIXER_SELECT_PPC = (1); + const int AX_MIXER_SELECT_BOTH = (2); + + void AXMix_Init(); + void AXSetDefaultMixerSelect(uint32 mixerSelect); + uint32 AXGetDefaultMixerSelect(); + + void AXMix_DepopVoice(struct AXVPBInternal_t* internalShadowCopy); + + void AXMix_process(struct AXVPBInternal_t* internalShadowCopyHead); + + extern FSpinlock __AXVoiceListSpinlock; + + // AX multi voice + struct AXVPBMULTI + { + /* +0x00 */ betype<uint32> isUsed; + /* +0x04 */ betype<uint32> channelCount; + /* +0x08 */ MEMPTR<AXVPB> voice[6]; + // size: 0x20 + }; + static_assert(sizeof(AXVPBMULTI) == 0x20); + + struct AXMULTIVOICEUKNSTRUCT + { + uint8 ukn[0x4A]; + betype<sint16> channelCount; + }; + + struct AXDSPADPCM + { + /* +0x00 */ uint32be numSamples; + /* +0x04 */ uint32be ukn04; + /* +0x08 */ uint32be sampleRate; + /* +0x0C */ uint16be isLooped; + /* +0x0E */ uint16be format; + /* +0x10 */ uint32be ukn10; + /* +0x14 */ uint32be ukn14; + /* +0x18 */ uint32be ukn18; + /* +0x1C */ uint16be coef[16]; + /* +0x3C */ uint16be gain; + /* +0x3E */ uint16be scale; + /* +0x40 */ uint16be yn1; + /* +0x42 */ uint16be yn2; + /* +0x44 */ uint16be ukn44; + /* +0x46 */ uint16be ukn46; + /* +0x48 */ uint16be ukn48; + /* +0x4A */ uint16be channelCount; + /* +0x4C */ uint16be ukn4C; + /* +0x4E */ uint8 _padding4E[0x12]; + }; + static_assert(sizeof(AXDSPADPCM) == 0x60); + + void AXMultiVoice_Init(); + + sint32 AXAcquireMultiVoice(sint32 voicePriority, void* cbFunc, void* cbData, AXMULTIVOICEUKNSTRUCT* uknR6, MEMPTR<AXVPBMULTI>* multiVoiceOut); + void AXFreeMultiVoice(AXVPBMULTI* multiVoice); + sint32 AXGetMultiVoiceReformatBufferSize(sint32 voiceFormat, uint32 channelCount, uint32 sizeInBytes, uint32be* sizeOutput); + void AXSetMultiVoiceType(AXVPBMULTI* mv, uint16 type); + void AXSetMultiVoiceAdpcm(AXVPBMULTI* mv, AXDSPADPCM* data); + void AXSetMultiVoiceSrcType(AXVPBMULTI* mv, uint32 type); + void AXSetMultiVoiceOffsets(AXVPBMULTI* mv, AXPBOFFSET_t* offsets); + void AXSetMultiVoiceVe(AXVPBMULTI* mv, AXPBVE* ve); + void AXSetMultiVoiceSrcRatio(AXVPBMULTI* mv, float ratio); + void AXSetMultiVoiceSrc(AXVPBMULTI* mv, AXPBSRC_t* src); + void AXSetMultiVoiceLoop(AXVPBMULTI* mv, uint16 loop); + void AXSetMultiVoiceState(AXVPBMULTI* mv, uint16 state); + void AXSetMultiVoiceAdpcmLoop(AXVPBMULTI* mv, AXPBADPCMLOOP_t* loops); + sint32 AXIsMultiVoiceRunning(AXVPBMULTI* mv); + + void AXOut_init(); + void AXOut_reset(); + void AXOut_update(); + + void Initialize(); +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/snd_core/ax_aux.cpp b/src/Cafe/OS/libs/snd_core/ax_aux.cpp new file mode 100644 index 00000000..3da391fe --- /dev/null +++ b/src/Cafe/OS/libs/snd_core/ax_aux.cpp @@ -0,0 +1,295 @@ +#include "Cafe/OS/libs/snd_core/ax.h" +#include "Cafe/OS/libs/snd_core/ax_internal.h" +#include "Cafe/HW/Espresso/PPCState.h" + +namespace snd_core +{ + const int AX_AUX_FRAME_COUNT = 2; + + // old (deprecated) style AUX callbacks + MPTR __AXOldAuxDRCCallbackFunc[AX_AUX_BUS_COUNT * 2]; + MPTR __AXOldAuxDRCCallbackUserParam[AX_AUX_BUS_COUNT * 2]; + + MPTR __AXOldAuxTVCallbackFunc[AX_AUX_BUS_COUNT]; + MPTR __AXOldAuxTVCallbackUserParam[AX_AUX_BUS_COUNT]; + + // new style AUX callbacks + MPTR __AXAuxDRCCallbackFunc[AX_AUX_BUS_COUNT * 2]; // 2 DRCs + MPTR __AXAuxDRCCallbackUserParam[AX_AUX_BUS_COUNT * 2]; + + MPTR __AXAuxTVCallbackFunc[AX_AUX_BUS_COUNT]; + MPTR __AXAuxTVCallbackUserParam[AX_AUX_BUS_COUNT]; + + struct AUXTVBuffer + { + sint32be _buf[AX_AUX_FRAME_COUNT * AX_TV_CHANNEL_COUNT * AX_AUX_BUS_COUNT * AX_SAMPLES_MAX]; + + sint32be* GetBuffer(uint32 auxBus, uint32 auxFrame, uint32 channel = 0) + { + const size_t samplesPerChannel = AXGetInputSamplesPerFrame(); + const size_t samplesPerBus = AX_SAMPLES_PER_3MS_48KHZ * AX_TV_CHANNEL_COUNT; + const size_t samplesPerFrame = samplesPerBus * AX_AUX_BUS_COUNT; + return _buf + auxFrame * samplesPerFrame + auxBus * samplesPerBus + channel * samplesPerChannel; + } + + void ClearBuffer() + { + memset(_buf, 0, sizeof(_buf)); + } + }; + + struct AUXDRCBuffer + { + sint32be _buf[AX_AUX_FRAME_COUNT * AX_DRC_CHANNEL_COUNT * AX_AUX_BUS_COUNT * AX_SAMPLES_MAX]; + + sint32be* GetBuffer(uint32 auxBus, uint32 auxFrame, uint32 channel = 0) + { + const size_t samplesPerChannel = AXGetInputSamplesPerFrame(); + const size_t samplesPerBus = AX_SAMPLES_PER_3MS_48KHZ * AX_DRC_CHANNEL_COUNT; + const size_t samplesPerFrame = samplesPerBus * AX_AUX_BUS_COUNT; + return _buf + auxFrame * samplesPerFrame + auxBus * samplesPerBus + channel * samplesPerChannel; + } + + void ClearBuffer() + { + memset(_buf, 0, sizeof(_buf)); + } + }; + + SysAllocator<AUXTVBuffer> __AXAuxTVBuffer; + SysAllocator<AUXDRCBuffer, 2> __AXAuxDRCBuffer; + + uint32 __AXCurrentAuxInputFrameIndex = 0; + + uint32 AXAux_GetOutputFrameIndex() + { + return 1 - __AXCurrentAuxInputFrameIndex; + } + + sint32be* AXAux_GetInputBuffer(sint32 device, sint32 deviceIndex, sint32 auxBus) + { + if (auxBus < 0 || auxBus >= AX_AUX_BUS_COUNT) + return nullptr; + if (device == AX_DEV_TV) + { + cemu_assert_debug(deviceIndex == 0); + if (__AXOldAuxTVCallbackFunc[auxBus] == MPTR_NULL && __AXAuxTVCallbackFunc[auxBus] == MPTR_NULL) + return nullptr; + return __AXAuxTVBuffer->GetBuffer(auxBus, __AXCurrentAuxInputFrameIndex); + } + else if (device == AX_DEV_DRC) + { + cemu_assert_debug(deviceIndex >= 0 && deviceIndex <= 1); + if (__AXOldAuxDRCCallbackFunc[deviceIndex * AX_AUX_BUS_COUNT + auxBus] == MPTR_NULL && __AXAuxDRCCallbackFunc[deviceIndex * AX_AUX_BUS_COUNT + auxBus] == MPTR_NULL) + return nullptr; + return __AXAuxDRCBuffer[deviceIndex].GetBuffer(auxBus, __AXCurrentAuxInputFrameIndex); + } + else + { + cemu_assert_debug(false); + } + return nullptr; + } + + sint32be* AXAux_GetOutputBuffer(sint32 device, sint32 deviceIndex, sint32 auxBus) + { + uint32 outputFrameIndex = AXAux_GetOutputFrameIndex(); + + if (device == AX_DEV_TV) + { + cemu_assert_debug(deviceIndex == 0); + if (__AXOldAuxTVCallbackFunc[auxBus] == MPTR_NULL && __AXAuxTVCallbackFunc[auxBus] == MPTR_NULL) + return nullptr; + return __AXAuxTVBuffer->GetBuffer(auxBus, outputFrameIndex); + } + else if (device == AX_DEV_DRC) + { + cemu_assert_debug(deviceIndex >= 0 && deviceIndex <= 1); + if (__AXOldAuxDRCCallbackFunc[deviceIndex * AX_AUX_BUS_COUNT + auxBus] == MPTR_NULL && __AXAuxDRCCallbackFunc[deviceIndex * AX_AUX_BUS_COUNT + auxBus] == MPTR_NULL) + return nullptr; + return __AXAuxDRCBuffer[deviceIndex].GetBuffer(auxBus, outputFrameIndex); + } + else + { + cemu_assert_debug(false); + } + return nullptr; + } + + void AXAux_Init() + { + __AXCurrentAuxInputFrameIndex = 0; + __AXAuxTVBuffer->ClearBuffer(); + __AXAuxDRCBuffer[0].ClearBuffer(); + __AXAuxDRCBuffer[1].ClearBuffer(); + + memset(__AXAuxTVCallbackFunc, 0, sizeof(__AXAuxTVCallbackFunc)); + memset(__AXAuxTVCallbackUserParam, 0, sizeof(__AXAuxTVCallbackUserParam)); + memset(__AXOldAuxTVCallbackFunc, 0, sizeof(__AXOldAuxTVCallbackFunc)); + memset(__AXOldAuxTVCallbackUserParam, 0, sizeof(__AXOldAuxTVCallbackUserParam)); + + memset(__AXAuxDRCCallbackFunc, 0, sizeof(__AXAuxDRCCallbackFunc)); + memset(__AXAuxDRCCallbackUserParam, 0, sizeof(__AXAuxDRCCallbackUserParam)); + memset(__AXOldAuxDRCCallbackFunc, 0, sizeof(__AXOldAuxDRCCallbackFunc)); + memset(__AXOldAuxDRCCallbackUserParam, 0, sizeof(__AXOldAuxDRCCallbackUserParam)); + // init aux return volume + __AXTVAuxReturnVolume[0] = 0x8000; + __AXTVAuxReturnVolume[1] = 0x8000; + __AXTVAuxReturnVolume[2] = 0x8000; + } + + sint32 AXRegisterAuxCallback(sint32 device, sint32 deviceIndex, uint32 auxBusIndex, MPTR funcMPTR, MPTR userParam) + { + sint32 r = AXIsValidDevice(device, deviceIndex); + if (r != 0) + return r; + if (auxBusIndex >= AX_AUX_BUS_COUNT) + return -5; + if (device == AX_DEV_TV) + { + __AXAuxTVCallbackFunc[auxBusIndex] = funcMPTR; + __AXAuxTVCallbackUserParam[auxBusIndex] = userParam; + } + else if (device == AX_DEV_DRC) + { + __AXAuxDRCCallbackFunc[auxBusIndex + deviceIndex * 3] = funcMPTR; + __AXAuxDRCCallbackUserParam[auxBusIndex + deviceIndex * 3] = userParam; + } + else if (device == AX_DEV_RMT) + { + cemu_assert_debug(false); + } + return 0; + } + + sint32 AXGetAuxCallback(sint32 device, sint32 deviceIndex, uint32 auxBusIndex, MEMPTR<uint32be> funcPtrOut, MEMPTR<uint32be> contextPtrOut) + { + sint32 r = AXIsValidDevice(device, deviceIndex); + if (r != 0) + return r; + if (auxBusIndex >= AX_AUX_BUS_COUNT) + return -5; + if (device == AX_DEV_TV) + { + *funcPtrOut = __AXAuxTVCallbackFunc[auxBusIndex]; + *contextPtrOut = __AXAuxTVCallbackUserParam[auxBusIndex]; + } + else if (device == AX_DEV_DRC) + { + *funcPtrOut = __AXAuxDRCCallbackFunc[auxBusIndex + deviceIndex * 3]; + *contextPtrOut = __AXAuxDRCCallbackUserParam[auxBusIndex + deviceIndex * 3]; + } + else if (device == AX_DEV_RMT) + { + cemu_assert_debug(false); + *funcPtrOut = MPTR_NULL; + *contextPtrOut = MPTR_NULL; + } + return 0; + } + + SysAllocator<MEMPTR<sint32be>, 6> __AXAuxCB_dataPtrs; + SysAllocator<AXAUXCBCHANNELINFO> __AXAuxCB_auxCBStruct; + + void AXAux_Process() + { + uint32 processedAuxFrameIndex = AXAux_GetOutputFrameIndex(); + uint32 sampleCount = AXGetInputSamplesPerFrame(); + // TV aux callbacks + uint32 tvChannelCount = AX_TV_CHANNEL_COUNT; + for (sint32 auxBusIndex = 0; auxBusIndex < AX_AUX_BUS_COUNT; auxBusIndex++) + { + MPTR auxCBFuncMPTR = MPTR_NULL; + auxCBFuncMPTR = __AXAuxTVCallbackFunc[auxBusIndex]; + if (auxCBFuncMPTR == MPTR_NULL) + auxCBFuncMPTR = __AXOldAuxTVCallbackFunc[auxBusIndex]; + if (auxCBFuncMPTR == MPTR_NULL) + { + void* auxOutput = __AXAuxTVBuffer->GetBuffer(auxBusIndex, processedAuxFrameIndex); + memset(auxOutput, 0, sampleCount * AX_TV_CHANNEL_COUNT * sizeof(sint32)); + continue; + } + for (sint32 channelIndex = 0; channelIndex < AX_TV_CHANNEL_COUNT; channelIndex++) + __AXAuxCB_dataPtrs[channelIndex] = __AXAuxTVBuffer->GetBuffer(auxBusIndex, processedAuxFrameIndex, channelIndex); + // do callback + if (__AXAuxTVCallbackFunc[auxBusIndex] != MPTR_NULL) + { + // new style callback + AXAUXCBCHANNELINFO* cbStruct = __AXAuxCB_auxCBStruct.GetPtr(); + cbStruct->numChannels = tvChannelCount; + cbStruct->numSamples = sampleCount; + ppcInterpreterCurrentInstance->gpr[3] = __AXAuxCB_dataPtrs.GetMPTR(); + ppcInterpreterCurrentInstance->gpr[4] = __AXAuxTVCallbackUserParam[auxBusIndex]; + ppcInterpreterCurrentInstance->gpr[5] = __AXAuxCB_auxCBStruct.GetMPTR(); + PPCCore_executeCallbackInternal(auxCBFuncMPTR); + } + else + { + // old style callback + cemu_assert_debug(false); // todo + } + } + // DRC aux callbacks + for (sint32 drcIndex = 0; drcIndex < 2; drcIndex++) + { + uint32 drcChannelCount = AX_DRC_CHANNEL_COUNT; + for (sint32 auxBusIndex = 0; auxBusIndex < AX_AUX_BUS_COUNT; auxBusIndex++) + { + MPTR auxCBFuncMPTR = MPTR_NULL; + auxCBFuncMPTR = __AXAuxDRCCallbackFunc[auxBusIndex + drcIndex * 3]; + if (auxCBFuncMPTR == MPTR_NULL) + { + auxCBFuncMPTR = __AXOldAuxDRCCallbackFunc[auxBusIndex + drcIndex * 3]; + } + if (auxCBFuncMPTR == MPTR_NULL) + { + void* auxOutput = __AXAuxDRCBuffer[drcIndex].GetBuffer(auxBusIndex, processedAuxFrameIndex); + memset(auxOutput, 0, 96 * 4 * sizeof(sint32)); + continue; + } + if (__AXAuxDRCCallbackFunc[auxBusIndex + drcIndex * 3] != MPTR_NULL) + { + // new style callback + for (sint32 channelIndex = 0; channelIndex < AX_DRC_CHANNEL_COUNT; channelIndex++) + __AXAuxCB_dataPtrs[channelIndex] = __AXAuxDRCBuffer[drcIndex].GetBuffer(auxBusIndex, processedAuxFrameIndex, channelIndex); + AXAUXCBCHANNELINFO* cbStruct = __AXAuxCB_auxCBStruct.GetPtr(); + cbStruct->numChannels = drcChannelCount; + cbStruct->numSamples = sampleCount; + ppcInterpreterCurrentInstance->gpr[3] = __AXAuxCB_dataPtrs.GetMPTR(); + ppcInterpreterCurrentInstance->gpr[4] = __AXAuxDRCCallbackUserParam[auxBusIndex + drcIndex * 3]; + ppcInterpreterCurrentInstance->gpr[5] = __AXAuxCB_auxCBStruct.GetMPTR(); + PPCCore_executeCallbackInternal(auxCBFuncMPTR); + } + else + { + // old style callback + cemu_assert_debug(false); + } + } + } + } + + void AXAux_incrementBufferIndex() + { + __AXCurrentAuxInputFrameIndex = 1 - __AXCurrentAuxInputFrameIndex; + } + + sint32 AXSetAuxReturnVolume(uint32 device, uint32 deviceIndex, uint32 auxBus, uint16 volume) + { + sint32 r = AXIsValidDevice(device, deviceIndex); + if (r) + return r; + if (auxBus >= AX_AUX_BUS_COUNT) + return -5; + if( device == AX_DEV_TV ) + { + __AXTVAuxReturnVolume[auxBus] = volume; + } + else + { + forceLogDebug_printf("sndcore2.AXSetAuxReturnVolume() - unsupported device %d", device); + } + return 0; + } + +} diff --git a/src/Cafe/OS/libs/snd_core/ax_exports.cpp b/src/Cafe/OS/libs/snd_core/ax_exports.cpp new file mode 100644 index 00000000..c84b4ca8 --- /dev/null +++ b/src/Cafe/OS/libs/snd_core/ax_exports.cpp @@ -0,0 +1,519 @@ +#include "Cafe/OS/libs/snd_core/ax.h" +#include "Cafe/OS/libs/snd_core/ax_internal.h" +#include "Cafe/OS/libs/coreinit/coreinit_Thread.h" +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/libs/coreinit/coreinit_MessageQueue.h" + +namespace snd_core +{ + sndGeneric_t sndGeneric; + + void resetToDefaultState() + { + memset(&sndGeneric, 0x00, sizeof(sndGeneric)); + resetNumProcessedFrames(); + } + + bool AXIsInit() + { + return sndGeneric.isInitialized; + } + + void __AXInit(bool isSoundCore2, uint32 frameLength, uint32 rendererFreq, uint32 pipelineMode) + { + cemu_assert(frameLength == AX_FRAMELENGTH_3MS); + cemu_assert(rendererFreq == AX_RENDERER_FREQ_32KHZ || rendererFreq == AX_RENDERER_FREQ_48KHZ); + sndGeneric.isSoundCore2 = isSoundCore2; + sndGeneric.initParam.frameLength = frameLength; + sndGeneric.initParam.rendererFreq = rendererFreq; + sndGeneric.initParam.pipelineMode = pipelineMode; + // init submodules + AXIst_Init(); + AXOut_Init(); + AXVPB_Init(); + AXAux_Init(); + AXMix_Init(); + AXMultiVoice_Init(); + AXIst_InitThread(); + sndGeneric.isInitialized = true; + } + + void sndcore2_AXInitWithParams(AXINITPARAM* initParam) + { + if (sndGeneric.isInitialized) + return; + __AXInit(true, initParam->frameLength, initParam->freq, initParam->pipelineMode); + } + + void sndcore2_AXInit() + { + if (sndGeneric.isInitialized) + return; + __AXInit(true, AX_FRAMELENGTH_3MS, AX_RENDERER_FREQ_32KHZ, AX_PIPELINE_SINGLE); + } + + void sndcore1_AXInit() + { + if (sndGeneric.isInitialized) + return; + __AXInit(false, AX_FRAMELENGTH_3MS, AX_RENDERER_FREQ_32KHZ, AX_PIPELINE_SINGLE); + } + + void sndcore2_AXInitEx(uint32 uknParam) + { + cemu_assert_debug(uknParam == 0); + if (sndGeneric.isInitialized) + return; + __AXInit(true, AX_FRAMELENGTH_3MS, AX_RENDERER_FREQ_32KHZ, AX_PIPELINE_SINGLE); + } + + void sndcore1_AXInitEx(uint32 uknParam) + { + cemu_assert_debug(uknParam == 0); + if (sndGeneric.isInitialized) + return; + __AXInit(false, AX_FRAMELENGTH_3MS, AX_RENDERER_FREQ_32KHZ, AX_PIPELINE_SINGLE); + } + + void AXQuit() + { + forceLogDebug_printf("AXQuit called from 0x%08x", ppcInterpreterCurrentInstance->spr.LR); + // clean up + AXResetCallbacks(); + AXVoiceList_ResetFreeVoiceList(); + // todo - should we wait to make sure any active callbacks are finished with execution before we exit AXQuit? + sndGeneric.isInitialized = false; + // request worker thread stop and wait until complete + AXIst_StopThread(); + } + + sint32 AXGetMaxVoices() + { + return sndGeneric.isInitialized ? AX_MAX_VOICES : 0; + } + + void export_AXGetDeviceFinalMixCallback(PPCInterpreter_t* hCPU) + { + ppcDefineParamS32(device, 0); + ppcDefineParamU32BEPtr(funcAddrPtr, 1); + sint32 r = AXGetDeviceFinalMixCallback(device, funcAddrPtr); + sndApiLog_printf("AXGetDeviceFinalMixCallback(%d,0x%08x)", hCPU->gpr[3], hCPU->gpr[4]); + osLib_returnFromFunction(hCPU, r); + } + + void export_AXRegisterDeviceFinalMixCallback(PPCInterpreter_t* hCPU) + { + ppcDefineParamS32(device, 0); + ppcDefineParamMPTR(funcAddr, 1); + sndApiLog_printf("AXRegisterDeviceFinalMixCallback(%d,0x%08x)", hCPU->gpr[3], hCPU->gpr[4]); + sint32 r = AXRegisterDeviceFinalMixCallback(device, funcAddr); + osLib_returnFromFunction(hCPU, r); + } + + void export_AXRegisterAppFrameCallback(PPCInterpreter_t* hCPU) + { + sndApiLog_printf("AXRegisterAppFrameCallback(0x%08x)", hCPU->gpr[3]); + ppcDefineParamMPTR(funcAddr, 0); + sint32 r = AXRegisterAppFrameCallback(funcAddr); + osLib_returnFromFunction(hCPU, r); + } + + void export_AXDeregisterAppFrameCallback(PPCInterpreter_t* hCPU) + { + sndApiLog_printf("AXDeregisterAppFrameCallback(0x%08x)", hCPU->gpr[3]); + ppcDefineParamMPTR(funcAddr, 0); + sint32 r = AXDeregisterAppFrameCallback(funcAddr); + osLib_returnFromFunction(hCPU, r); + } + + void export_AXRegisterFrameCallback(PPCInterpreter_t* hCPU) + { + sndApiLog_printf("AXRegisterFrameCallback(0x%08x)", hCPU->gpr[3]); + ppcDefineParamMPTR(funcAddr, 0); + sint32 r = AXRegisterFrameCallback(funcAddr); + osLib_returnFromFunction(hCPU, r); + } + + void export_AXRegisterCallback(PPCInterpreter_t* hCPU) + { + sndApiLog_printf("AXRegisterCallback(0x%08x)", hCPU->gpr[3]); + ppcDefineParamMPTR(funcAddr, 0); + sint32 r = AXRegisterFrameCallback(funcAddr); + osLib_returnFromFunction(hCPU, r); + } + + void export_AXRegisterAuxCallback(PPCInterpreter_t* hCPU) + { + sndApiLog_printf("AXRegisterAuxCallback(0x%08x,0x%08x,0x%08x,0x%08x,0x%08x) LR %08x", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7], hCPU->spr.LR); + ppcDefineParamU32(device, 0); + ppcDefineParamU32(deviceIndex, 1); + ppcDefineParamU32(auxBusIndex, 2); + ppcDefineParamMPTR(funcAddr, 3); + ppcDefineParamMPTR(userParam, 4); + sint32 r = AXRegisterAuxCallback(device, deviceIndex, auxBusIndex, funcAddr, userParam); + osLib_returnFromFunction(hCPU, r); + } + + void export_AXGetAuxCallback(PPCInterpreter_t* hCPU) + { + sndApiLog_printf("AXGetAuxCallback(0x%08x,0x%08x,0x%08x,0x%08x,0x%08x)", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7]); + ppcDefineParamU32(device, 0); + ppcDefineParamU32(deviceIndex, 1); + ppcDefineParamU32(auxBusIndex, 2); + ppcDefineParamMEMPTR(funcAddrOut, uint32be, 3); + ppcDefineParamMEMPTR(userParamOut, uint32be, 4); + sint32 r = AXGetAuxCallback(device, deviceIndex, auxBusIndex, funcAddrOut, userParamOut); + osLib_returnFromFunction(hCPU, r); + } + + void export_AXSetAuxReturnVolume(PPCInterpreter_t* hCPU) + { + sndApiLog_printf("AXSetAuxReturnVolume(0x%08x,0x%08x,0x%08x,0x%04x)", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6]); + ppcDefineParamU32(device, 0); + ppcDefineParamU32(deviceIndex, 1); + ppcDefineParamU32(auxBusIndex, 2); + ppcDefineParamU16(volume, 3); + sint32 r = AXSetAuxReturnVolume(device, deviceIndex, auxBusIndex, volume); + osLib_returnFromFunction(hCPU, r); + } + + void export_AXGetDeviceMode(PPCInterpreter_t* hCPU) + { + sndApiLog_printf("AXGetDeviceMode(%d)", hCPU->gpr[3]); + ppcDefineParamS32(device, 0); + ppcDefineParamU32BEPtr(mode, 1); + *mode = AXGetDeviceMode(device); + osLib_returnFromFunction(hCPU, 0); + } + + void export_AXSetDeviceUpsampleStage(PPCInterpreter_t* hCPU) + { + sndApiLog_printf("AXSetDeviceUpsampleStage(%d,%d)", hCPU->gpr[3], hCPU->gpr[4]); + ppcDefineParamS32(device, 0); + ppcDefineParamS32(upsampleStage, 1); + sint32 r = AXSetDeviceUpsampleStage(device, upsampleStage); + osLib_returnFromFunction(hCPU, r); + } + + void export_AXGetDeviceUpsampleStage(PPCInterpreter_t* hCPU) + { + sndApiLog_printf("AXGetDeviceUpsampleStage(%d,0x%08x)", hCPU->gpr[3], hCPU->gpr[4]); + ppcDefineParamS32(device, 0); + ppcDefineParamU32BEPtr(upsampleStagePtr, 1); + sint32 r = AXGetDeviceUpsampleStage(device, upsampleStagePtr); + osLib_returnFromFunction(hCPU, r); + } + + void export_AXAcquireVoiceEx(PPCInterpreter_t* hCPU) + { + ppcDefineParamS32(priority, 0); + ppcDefineParamMPTR(callbackEx, 1); + ppcDefineParamMPTR(userParam, 2); + sndApiLog_printf("AXAcquireVoiceEx(%d,0x%08x,0x%08x)", priority, callbackEx, userParam); + MEMPTR<AXVPB> r = AXAcquireVoiceEx(priority, callbackEx, userParam); + osLib_returnFromFunction(hCPU, r.GetMPTR()); + } + + void export_AXAcquireVoice(PPCInterpreter_t* hCPU) + { + ppcDefineParamS32(priority, 0); + ppcDefineParamMPTR(callback, 1); + ppcDefineParamMPTR(userParam, 2); + sndApiLog_printf("AXAcquireVoice(%d,0x%08x,0x%08x)", priority, callback, userParam); + MEMPTR<AXVPB> r = AXAcquireVoiceEx(priority, MPTR_NULL, MPTR_NULL); + if (r.IsNull() == false) + { + r->callback = (uint32be)callback; + r->userParam = (uint32be)userParam; + } + osLib_returnFromFunction(hCPU, r.GetMPTR()); + } + + void export_AXFreeVoice(PPCInterpreter_t* hCPU) + { + ppcDefineParamStructPtr(vpb, AXVPB, 0); + sndApiLog_printf("AXFreeVoice(0x%08x)", hCPU->gpr[3]); + AXFreeVoice(vpb); + osLib_returnFromFunction(hCPU, 0); + } + + void export_AXUserIsProtected(PPCInterpreter_t* hCPU) + { + sint32 r = AXUserIsProtected(); + sndApiLog_printf("AXUserIsProtected() -> %s", r!=0?"true":"false"); + osLib_returnFromFunction(hCPU, r); + } + + void export_AXUserBegin(PPCInterpreter_t* hCPU) + { + sndApiLog_printf("AXUserBegin()"); + sint32 r = AXUserBegin(); + osLib_returnFromFunction(hCPU, r); + } + + void export_AXUserEnd(PPCInterpreter_t* hCPU) + { + sndApiLog_printf("AXUserEnd()"); + sint32 r = AXUserEnd(); + osLib_returnFromFunction(hCPU, r); + } + + void export_AXVoiceBegin(PPCInterpreter_t* hCPU) + { + ppcDefineParamStructPtr(vpb, AXVPB, 0); + sndApiLog_printf("AXVoiceBegin(0x%08x)", hCPU->gpr[3]); + sint32 r = AXVoiceBegin(vpb); + osLib_returnFromFunction(hCPU, r); + } + + void export_AXVoiceEnd(PPCInterpreter_t* hCPU) + { + ppcDefineParamStructPtr(vpb, AXVPB, 0); + sndApiLog_printf("AXVoiceEnd(0x%08x)", hCPU->gpr[3]); + sint32 r = AXVoiceEnd(vpb); + osLib_returnFromFunction(hCPU, r); + } + + void export_AXVoiceIsProtected(PPCInterpreter_t* hCPU) + { + ppcDefineParamStructPtr(vpb, AXVPB, 0); + sndApiLog_printf("AXVoiceIsProtected(0x%08x)", hCPU->gpr[3]); + sint32 r = AXVoiceProtection_IsProtectedByCurrentThread(vpb)?1:0; + osLib_returnFromFunction(hCPU, r); + } + + uint32 __AXCalculatePointerHighExtension(uint16 format, MPTR sampleBase, uint32 offset) + { + sampleBase = memory_virtualToPhysical(sampleBase); + uint32 ptrHighExtension; + if (format == AX_FORMAT_PCM8) + { + ptrHighExtension = ((sampleBase + offset) >> 29); + } + else if (format == AX_FORMAT_PCM16) + { + ptrHighExtension = ((sampleBase + offset * 2) >> 29); + } + else if (format == AX_FORMAT_ADPCM) + { + ptrHighExtension = ((sampleBase + offset / 2) >> 29); + } + return ptrHighExtension; + } + + void export_AXCheckVoiceOffsets(PPCInterpreter_t* hCPU) + { + sndApiLog_printf("AXCheckVoiceOffsets(0x%08x)\n", hCPU->gpr[3]); + ppcDefineParamStructPtr(pbOffset, AXPBOFFSET_t, 0); + + uint16 format = _swapEndianU16(pbOffset->format); + MPTR sampleBase = _swapEndianU32(pbOffset->samples); + + uint32 highExtLoop = __AXCalculatePointerHighExtension(format, sampleBase, _swapEndianU32(pbOffset->loopOffset)); + uint32 highExtEnd = __AXCalculatePointerHighExtension(format, sampleBase, _swapEndianU32(pbOffset->endOffset)); + uint32 highExtCurrent = __AXCalculatePointerHighExtension(format, sampleBase, _swapEndianU32(pbOffset->currentOffset)); + + bool isSameRange; + if (highExtLoop == highExtEnd && highExtEnd == highExtCurrent) + isSameRange = true; + else + isSameRange = false; + + osLib_returnFromFunction(hCPU, isSameRange ? 1 : 0); + } + + void export_AXSetDeviceRemixMatrix(PPCInterpreter_t* hCPU) + { + ppcDefineParamS32(device, 0); + ppcDefineParamU32(chanIn, 1); + ppcDefineParamU32(chanOut, 2); + ppcDefineParamMEMPTR(matrix, float32be, 3); + sndApiLog_printf("AXSetDeviceRemixMatrix(%d,%d,%d,0x%08x)", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6]); + const auto result = AXSetDeviceRemixMatrix(device, chanIn, chanOut, matrix); + osLib_returnFromFunction(hCPU, result); + } + + void export_AXGetDeviceRemixMatrix(PPCInterpreter_t* hCPU) + { + ppcDefineParamS32(device, 0); + ppcDefineParamU32(chanIn, 1); + ppcDefineParamU32(chanOut, 2); + ppcDefineParamMEMPTR(matrix, MEMPTR<float32be>, 3); + sndApiLog_printf("AXGetDeviceRemixMatrix(%d,%d,%d,0x%08x)", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6]); + const auto result = AXGetDeviceRemixMatrix(device, chanIn, chanOut, matrix); + osLib_returnFromFunction(hCPU, result); + } + + struct AXGetDeviceFinalOutput_t + { + /* +0x00 */ uint32be channelCount; + /* +0x04 */ uint32be uknValue; + /* +0x08 */ uint32be ukn08; + /* +0x0C */ uint32be ukn0C; + /* +0x10 */ uint32be size; + // struct might be bigger? + }; + + sint32 AXGetDeviceFinalOutput(uint32 device, sint16be* sampleBufferOutput, uint32 bufferSize, AXGetDeviceFinalOutput_t* output) + { + if (device != AX_DEV_TV && device != AX_DEV_DRC) + return -1; + sint32 channelCount = AIGetChannelCount(device); + sint32 samplesPerChannel = AIGetSamplesPerChannel(device); + sint32 samplesToCopy = samplesPerChannel * channelCount; + + if (bufferSize < (samplesToCopy * sizeof(sint16be))) + return -11; // buffer not large enough + + // copy samples to buffer + sint16* samplesBuffer = AIGetCurrentDMABuffer(device); + for (sint32 i = 0; i < samplesToCopy; i++) + sampleBufferOutput[i] = samplesBuffer[i]; + + // set output struct + output->size = samplesToCopy * sizeof(sint16be); + output->channelCount = channelCount; + output->uknValue = 1; // always set to 1/true? + + return 0; + } + + void loadExportsSndCore1() + { + cafeExportRegisterFunc(sndcore1_AXInit, "snd_core", "AXInit", LogType::SoundAPI); + cafeExportRegisterFunc(sndcore1_AXInitEx, "snd_core", "AXInitEx", LogType::SoundAPI); + cafeExportRegister("snd_core", AXIsInit, LogType::SoundAPI); + cafeExportRegister("snd_core", AXQuit, LogType::SoundAPI); + + cafeExportRegister("snd_core", AXGetMaxVoices, LogType::SoundAPI); + cafeExportRegister("snd_core", AXGetInputSamplesPerFrame, LogType::SoundAPI); + cafeExportRegister("snd_core", AXGetInputSamplesPerSec, LogType::SoundAPI); + cafeExportRegister("snd_core", AXSetDefaultMixerSelect, LogType::SoundAPI); + cafeExportRegister("snd_core", AXGetDefaultMixerSelect, LogType::SoundAPI); + + osLib_addFunction("snd_core", "AXGetDeviceFinalMixCallback", export_AXGetDeviceFinalMixCallback); + osLib_addFunction("snd_core", "AXRegisterDeviceFinalMixCallback", export_AXRegisterDeviceFinalMixCallback); + + osLib_addFunction("snd_core", "AXRegisterAppFrameCallback", export_AXRegisterAppFrameCallback); + osLib_addFunction("snd_core", "AXDeregisterAppFrameCallback", export_AXDeregisterAppFrameCallback); + + osLib_addFunction("snd_core", "AXRegisterFrameCallback", export_AXRegisterFrameCallback); + osLib_addFunction("snd_core", "AXRegisterCallback", export_AXRegisterCallback); + + osLib_addFunction("snd_core", "AXRegisterAuxCallback", export_AXRegisterAuxCallback); + osLib_addFunction("snd_core", "AXGetAuxCallback", export_AXGetAuxCallback); + + osLib_addFunction("snd_core", "AXSetAuxReturnVolume", export_AXSetAuxReturnVolume); + + osLib_addFunction("snd_core", "AXGetDeviceMode", export_AXGetDeviceMode); + + osLib_addFunction("snd_core", "AXSetDeviceUpsampleStage", export_AXSetDeviceUpsampleStage); + osLib_addFunction("snd_core", "AXGetDeviceUpsampleStage", export_AXGetDeviceUpsampleStage); + + osLib_addFunction("snd_core", "AXAcquireVoiceEx", export_AXAcquireVoiceEx); + osLib_addFunction("snd_core", "AXAcquireVoice", export_AXAcquireVoice); + osLib_addFunction("snd_core", "AXFreeVoice", export_AXFreeVoice); + + osLib_addFunction("snd_core", "AXUserIsProtected", export_AXUserIsProtected); + osLib_addFunction("snd_core", "AXUserBegin", export_AXUserBegin); + osLib_addFunction("snd_core", "AXUserEnd", export_AXUserEnd); + osLib_addFunction("snd_core", "AXVoiceBegin", export_AXVoiceBegin); + osLib_addFunction("snd_core", "AXVoiceEnd", export_AXVoiceEnd); + osLib_addFunction("snd_core", "AXVoiceIsProtected", export_AXVoiceIsProtected); + + osLib_addFunction("snd_core", "AXCheckVoiceOffsets", export_AXCheckVoiceOffsets); + + osLib_addFunction("snd_core", "AXSetDeviceRemixMatrix", export_AXSetDeviceRemixMatrix); + osLib_addFunction("snd_core", "AXGetDeviceRemixMatrix", export_AXGetDeviceRemixMatrix); + + cafeExportRegister("snd_core", AXGetDeviceFinalOutput, LogType::SoundAPI); + } + + void loadExportsSndCore2() + { + cafeExportRegisterFunc(sndcore2_AXInitWithParams, "sndcore2", "AXInitWithParams", LogType::SoundAPI); + cafeExportRegisterFunc(sndcore2_AXInit, "sndcore2", "AXInit", LogType::SoundAPI); + cafeExportRegisterFunc(sndcore2_AXInitEx, "sndcore2", "AXInitEx", LogType::SoundAPI); + cafeExportRegister("sndcore2", AXIsInit, LogType::SoundAPI); + cafeExportRegister("sndcore2", AXQuit, LogType::SoundAPI); + + cafeExportRegister("sndcore2", AXGetMaxVoices, LogType::SoundAPI); + cafeExportRegister("sndcore2", AXGetInputSamplesPerFrame, LogType::SoundAPI); + cafeExportRegister("sndcore2", AXGetInputSamplesPerSec, LogType::SoundAPI); + cafeExportRegister("sndcore2", AXSetDefaultMixerSelect, LogType::SoundAPI); + cafeExportRegister("sndcore2", AXGetDefaultMixerSelect, LogType::SoundAPI); + + osLib_addFunction("sndcore2", "AXGetDeviceFinalMixCallback", export_AXGetDeviceFinalMixCallback); + osLib_addFunction("sndcore2", "AXRegisterDeviceFinalMixCallback", export_AXRegisterDeviceFinalMixCallback); + + osLib_addFunction("sndcore2", "AXRegisterAppFrameCallback", export_AXRegisterAppFrameCallback); + osLib_addFunction("sndcore2", "AXDeregisterAppFrameCallback", export_AXDeregisterAppFrameCallback); + + osLib_addFunction("sndcore2", "AXRegisterFrameCallback", export_AXRegisterFrameCallback); + osLib_addFunction("sndcore2", "AXRegisterCallback", export_AXRegisterCallback); + + osLib_addFunction("sndcore2", "AXRegisterAuxCallback", export_AXRegisterAuxCallback); + osLib_addFunction("sndcore2", "AXGetAuxCallback", export_AXGetAuxCallback); + + osLib_addFunction("sndcore2", "AXSetAuxReturnVolume", export_AXSetAuxReturnVolume); + + osLib_addFunction("sndcore2", "AXGetDeviceMode", export_AXGetDeviceMode); + + osLib_addFunction("sndcore2", "AXSetDeviceUpsampleStage", export_AXSetDeviceUpsampleStage); + osLib_addFunction("sndcore2", "AXGetDeviceUpsampleStage", export_AXGetDeviceUpsampleStage); + + osLib_addFunction("sndcore2", "AXAcquireVoiceEx", export_AXAcquireVoiceEx); + osLib_addFunction("sndcore2", "AXAcquireVoice", export_AXAcquireVoice); + osLib_addFunction("sndcore2", "AXFreeVoice", export_AXFreeVoice); + + osLib_addFunction("sndcore2", "AXUserIsProtected", export_AXUserIsProtected); + osLib_addFunction("sndcore2", "AXUserBegin", export_AXUserBegin); + osLib_addFunction("sndcore2", "AXUserEnd", export_AXUserEnd); + osLib_addFunction("sndcore2", "AXVoiceBegin", export_AXVoiceBegin); + osLib_addFunction("sndcore2", "AXVoiceEnd", export_AXVoiceEnd); + + osLib_addFunction("sndcore2", "AXVoiceIsProtected", export_AXVoiceIsProtected); + + osLib_addFunction("sndcore2", "AXCheckVoiceOffsets", export_AXCheckVoiceOffsets); + + osLib_addFunction("sndcore2", "AXSetDeviceRemixMatrix", export_AXSetDeviceRemixMatrix); + osLib_addFunction("sndcore2", "AXGetDeviceRemixMatrix", export_AXGetDeviceRemixMatrix); + + cafeExportRegister("sndcore2", AXGetDeviceFinalOutput, LogType::SoundAPI); + + // multi voice + cafeExportRegister("sndcore2", AXAcquireMultiVoice, LogType::SoundAPI); + cafeExportRegister("sndcore2", AXFreeMultiVoice, LogType::SoundAPI); + cafeExportRegister("sndcore2", AXGetMultiVoiceReformatBufferSize, LogType::SoundAPI); + cafeExportRegister("sndcore2", AXSetMultiVoiceType, LogType::SoundAPI); + cafeExportRegister("sndcore2", AXSetMultiVoiceAdpcm, LogType::SoundAPI); + cafeExportRegister("sndcore2", AXSetMultiVoiceSrcType, LogType::SoundAPI); + cafeExportRegister("sndcore2", AXSetMultiVoiceOffsets, LogType::SoundAPI); + cafeExportRegister("sndcore2", AXSetMultiVoiceVe, LogType::SoundAPI); + cafeExportRegister("sndcore2", AXSetMultiVoiceSrcRatio, LogType::SoundAPI); + cafeExportRegister("sndcore2", AXSetMultiVoiceSrc, LogType::SoundAPI); + cafeExportRegister("sndcore2", AXSetMultiVoiceLoop, LogType::SoundAPI); + cafeExportRegister("sndcore2", AXSetMultiVoiceState, LogType::SoundAPI); + cafeExportRegister("sndcore2", AXSetMultiVoiceAdpcmLoop, LogType::SoundAPI); + cafeExportRegister("sndcore2", AXIsMultiVoiceRunning, LogType::SoundAPI); + } + + void loadExports() + { + resetToDefaultState(); + + loadExportsSndCore1(); + loadExportsSndCore2(); + } + + bool isInitialized() + { + return sndGeneric.isInitialized; + } + + void reset() + { + sndGeneric.isInitialized = false; + } + +} diff --git a/src/Cafe/OS/libs/snd_core/ax_internal.h b/src/Cafe/OS/libs/snd_core/ax_internal.h new file mode 100644 index 00000000..215b9ca7 --- /dev/null +++ b/src/Cafe/OS/libs/snd_core/ax_internal.h @@ -0,0 +1,239 @@ +#pragma once +#include "Cafe/OS/libs/snd_core/ax.h" + +namespace snd_core +{ + typedef struct + { + bool isInitialized; + bool isSoundCore2; + // init params + struct + { + uint32 rendererFreq; // 32Khz or 48Khz + uint32 frameLength; // 3MS + uint32 pipelineMode; + }initParam; + }sndGeneric_t; + + extern sndGeneric_t sndGeneric; + + const uint32 AX_SYNCFLAG_SRCFILTER = 0x1; // Voice src type (AXSetVoiceSrcType) + const uint32 AX_SYNCFLAG_DEVICEMIXMASK = 0x2; // Voice mix related (AXSetVoiceDeviceMix) + const uint32 AX_SYNCFLAG_PLAYBACKSTATE = 0x4; // Voice play state (AXSetVoiceState) + const uint32 AX_SYNCFLAG_VOICETYPE = 0x8; // Voice type (AXSetVoiceType) + const uint32 AX_SYNCFLAG_DEVICEMIX = 0x10; // Voice mix related (AXSetVoiceDeviceMix) + const uint32 AX_SYNCFLAG_ITD20 = 0x20; // Voice initial time delay (AXSetVoiceItdOn) + const uint32 AX_SYNCFLAG_ITD40 = 0x40; // Voice initial time delay (AXSetVoiceItdOn, AXSetVoiceItdTarget) + const uint32 AX_SYNCFLAG_VE = 0x100; // Voice ve (AXSetVoiceVe) + const uint32 AX_SYNCFLAG_VEDELTA = 0x200; // Voice ve delta (AXSetVoiceVeDelta) + const uint32 AX_SYNCFLAG_OFFSETS = 0x400; // Voice offset data (AXSetVoiceOffsets) + const uint32 AX_SYNCFLAG_LOOPFLAG = 0x800; // Voice loop flag (AXSetVoiceLoop) + const uint32 AX_SYNCFLAG_LOOPOFFSET = 0x1000; // Voice loop offset (AXSetVoiceLoopOffset) + const uint32 AX_SYNCFLAG_ENDOFFSET = 0x2000; // Voice end offset (AXSetVoiceEndOffset) + const uint32 AX_SYNCFLAG_CURRENTOFFSET = 0x4000; // Voice current offset (AXSetVoiceCurrentOffset) + const uint32 AX_SYNCFLAG_ADPCMDATA = 0x8000; // Voice adpcm data (AXSetVoiceAdpcm) + const uint32 AX_SYNCFLAG_SRCDATA = 0x10000; // Voice src + src ratio (AXSetVoiceSrc) + const uint32 AX_SYNCFLAG_SRCRATIO = 0x20000; // Voice src ratio (AXSetVoiceSrcRatio) + const uint32 AX_SYNCFLAG_ADPCMLOOP = 0x40000; // Voice adpcm loop (AXSetVoiceAdpcmLoop) + const uint32 AX_SYNCFLAG_LPFDATA = 0x80000; // Voice lpf (AXSetVoiceLpf) + const uint32 AX_SYNCFLAG_LPFCOEF = 0x100000; // Voice lpf coef (AXSetVoiceLpfCoefs) + const uint32 AX_SYNCFLAG_BIQUADDATA = 0x200000; // Voice biquad (AXSetVoiceBiquad) + const uint32 AX_SYNCFLAG_BIQUADCOEF = 0x400000; // Voice biquad coef (AXSetVoiceBiquadCoefs) + const uint32 AX_SYNCFLAG_VOICEREMOTEON = 0x800000; // ??? (AXSetVoiceRmtOn?) + const uint32 AX_SYNCFLAG_4000000 = 0x4000000; // ??? + const uint32 AX_SYNCFLAG_8000000 = 0x8000000; // ??? + + struct axADPCMInternal_t + { + /* +0x00 | +0x190 */ uint16 coef[16]; + /* +0x20 | +0x1B0 */ uint16 gain; + /* +0x22 | +0x1B2 */ uint16 scale; + /* +0x24 | +0x1B4 */ uint16 yn1; + /* +0x26 | +0x1B6 */ uint16 yn2; + // size: 0x28 + }; + + struct axADPCMLoopInternal_t + { + /* +0x00 | 0x1C6 */ uint16 loopScale; + /* +0x02 | 0x1C8 */ uint16 loopYn1; + /* +0x04 | 0x1CA */ uint16 loopYn2; + // size: 0x6 + }; + + struct axOffsetsInternal_t + { + /* +0x00 / 0x17E */ uint16 loopFlag; + /* +0x02 / 0x180 */ uint16 format; + /* +0x04 / 0x182 */ uint16 ptrHighExtension; // defines 512mb range (highest 3 bit of current offset ptr counted in bytes) + // offsets (relative to NULL, counted in sample words) + // note: All offset ptr variables can only store values up to 512MB (PCM8 mask -> 0x1FFFFFFF, PCM16 mask -> 0x0FFFFFFF, ADPCM mask -> 0x3FFFFFFF) + /* +0x06 / 0x184 */ uint16 loopOffsetPtrHigh; + /* +0x08 / 0x186 */ uint16 loopOffsetPtrLow; + /* +0x0A / 0x188 */ uint16 endOffsetPtrHigh; + /* +0x0C / 0x18A */ uint16 endOffsetPtrLow; + /* +0x0E / 0x18C */ uint16 currentOffsetPtrHigh; + /* +0x10 / 0x18E */ uint16 currentOffsetPtrLow; + + uint32 GetLoopOffset32() const + { + uint32 offset = (uint32)_swapEndianU16(loopOffsetPtrHigh) << 16; + return offset | (uint32)_swapEndianU16(loopOffsetPtrLow); + } + + uint32 GetEndOffset32() const + { + uint32 offset = (uint32)_swapEndianU16(endOffsetPtrHigh) << 16; + return offset | (uint32)_swapEndianU16(endOffsetPtrLow); + } + + uint32 GetCurrentOffset32() const + { + uint32 offset = (uint32)_swapEndianU16(currentOffsetPtrHigh) << 16; + return offset | (uint32)_swapEndianU16(currentOffsetPtrLow); + } + }; + + const int AX_BUS_COUNT = 4; + + struct AXVPBInternal_t + { + /* matches what the DSP expects */ + /* +0x000 */ uint16be nextAddrHigh; // points to next shadow copy (physical pointer, NULL for last voice in list) + /* +0x002 */ uint16be nextAddrLow; + /* +0x004 */ uint16be selfAddrHigh; // points to shadow copy of self (physical pointer) + /* +0x006 */ uint16be selfAddrLow; + /* +0x008 */ uint16 srcFilterMode; // AX_FILTER_MODE_* + /* +0x00A */ uint16 srcTapFilter; // AX_FILTER_TAP_* + /* +0x00C */ uint16be mixerSelect; + /* +0x00E */ uint16 voiceType; + /* +0x010 */ uint16 deviceMixMaskTV[4]; + /* +0x018 */ uint16 deviceMixMaskDRC[4 * 2]; + /* +0x028 */ AXCHMIX_DEPR deviceMixTV[AX_BUS_COUNT * AX_TV_CHANNEL_COUNT]; // TV device mix + /* +0x088 */ AXCHMIX_DEPR deviceMixDRC[AX_BUS_COUNT * AX_DRC_CHANNEL_COUNT * 2]; // DRC device mix + /* +0x108 */ AXCHMIX_DEPR deviceMixRMT[0x40 / 4]; // RMT device mix (size unknown) + /* +0x148 */ uint16 reserved148_voiceRmtOn; + /* +0x14A */ uint16 deviceMixMaskRMT[0x10]; + /* +0x16A */ uint16 playbackState; + // itd (0x16C - 0x1B4?) + /* +0x16C */ uint16 reserved16C; + /* +0x16E */ uint16be itdAddrHigh; // points to AXItd_t (physical pointer) + /* +0x170 */ uint16be itdAddrLow; + /* +0x172 */ uint16 reserved172; + /* +0x174 */ uint16 reserved174; + /* +0x176 */ uint16 reserved176_itdRelated; + /* +0x178 */ uint16 reserved178_itdRelated; + /* +0x17A */ uint16be veVolume; + /* +0x17C */ uint16be veDelta; + /* +0x17E */ axOffsetsInternal_t internalOffsets; + /* +0x190 */ axADPCMInternal_t adpcmData; + /* +0x1B8 */ AXPBSRC_t src; + /* +0x1C6 */ axADPCMLoopInternal_t adpcmLoop; + struct + { + /* +0x1CC */ uint16 on; + /* +0x1CE */ uint16 yn1; + /* +0x1D0 */ uint16 a0; + /* +0x1D2 */ uint16 b0; + }lpf; + struct + { + /* +0x1D4 */ uint16 on; + /* +0x1D6 */ sint16 xn1; + /* +0x1D8 */ sint16 xn2; + /* +0x1DA */ sint16 yn1; + /* +0x1DC */ sint16 yn2; + /* +0x1DE */ uint16 b0; + /* +0x1E0 */ uint16 b1; + /* +0x1E2 */ uint16 b2; + /* +0x1E4 */ uint16 a1; + /* +0x1E6 */ uint16 a2; + }biquad; + uint16 reserved1E8[1]; + uint16 reserved1EA; + uint16 reserved1EC; + uint16 reserved1EE; + uint32 reserved1F0[4]; + uint16 reserved200; + uint16 reserved202; + uint16 reserved204; + uint16 reserved206; + uint16 reserved208; + uint16 reserved20A; + uint16 reserved20C; + uint16 reserved20E; + uint16 reserved210; + uint16 reserved212; + uint16 reserved214; + uint16 reserved216; + uint16 reserved218[0x20]; // not related to device mix? + uint16 reserved258[0x10]; // not related to device mix? + // rmt src related + uint16 reserved278; + uint16 reserved27A; + uint16 reserved27C; + uint16 reserved27E; + uint16 reserved280; + uint16 reserved282_rmtIIRGuessed; + uint32 reserved284; + uint32 reserved288; + uint32 reserved28C; + uint32 reserved290; + uint16 reserved294; + uint16 reserved296; + /* +0x298 */ uint16 reserved298; + /* +0x29A */ uint16 reserved29A; + /* +0x29C */ uint16 reserved29C; + /* +0x29E */ uint16 reserved29E; + /* +0x2A0 */ uint16be index; + /* +0x2A2 */ uint16be ukn2A2; // voice active/valid and being processed? + uint16 reserved2A4; + uint16 reserved2A6; + uint16 reserved2A8; + uint16 reserved2AA; + /* +0x2AC */ MEMPTR<AXVPBInternal_t> nextToProcess; + uint32 reserved2B0; + uint32 reserved2B4; + uint32 reserved2B8; + uint32 reserved2BC; + // size: 0x2C0 + }; + + static_assert(sizeof(AXVPBInternal_t) == 0x2C0); + + extern AXVPBInternal_t* __AXVPBInternalVoiceArray; + extern AXVPBInternal_t* __AXVPBInternalVoiceShadowCopyArrayPtr; + extern AXVPB* __AXVPBArrayPtr; + + void AXResetVoiceLoopCount(AXVPB* vpb); + + std::vector<AXVPB*>& AXVoiceList_GetListByPriority(uint32 priority); + std::vector<AXVPB*>& AXVoiceList_GetFreeVoices(); + void AXVoiceList_ResetFreeVoiceList(); + + inline AXVPBInternal_t* GetInternalVoice(const AXVPB* vpb) + { + return __AXVPBInternalVoiceArray + (size_t)vpb->index; + } + + inline uint32 GetVoiceIndex(const AXVPB* vpb) + { + return (uint32)vpb->index; + } + + // AXIst + void AXIst_InitThread(); + OSThread_t* AXIst_GetThread(); + void AXIst_StopThread(); + + void AXIst_HandleFrameCallbacks(); + + // AXAux + void AXAux_incrementBufferIndex(); + + // internal mix buffers + extern SysAllocator<sint32, AX_SAMPLES_MAX * AX_TV_CHANNEL_COUNT> __AXTVOutputBuffer; + extern SysAllocator<sint32, AX_SAMPLES_MAX * AX_DRC_CHANNEL_COUNT * 2> __AXDRCOutputBuffer; + +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/snd_core/ax_ist.cpp b/src/Cafe/OS/libs/snd_core/ax_ist.cpp new file mode 100644 index 00000000..a47b1974 --- /dev/null +++ b/src/Cafe/OS/libs/snd_core/ax_ist.cpp @@ -0,0 +1,1056 @@ +#include "Cafe/OS/libs/snd_core/ax.h" +#include "Cafe/OS/libs/snd_core/ax_internal.h" +#include "Cafe/HW/Espresso/PPCState.h" +#include "Cafe/HW/Espresso/PPCCallback.h" +#include "Cafe/OS/libs/coreinit/coreinit_Thread.h" + +namespace snd_core +{ + sint32 __AXDeviceUpsampleStage[AX_DEV_COUNT]; // AX_UPSAMPLE_STAGE_* + + sint32 AXSetDeviceUpsampleStage(sint32 device, int upsampleStage) + { + if (device != AX_DEV_TV && device != AX_DEV_DRC) + return -1; + __AXDeviceUpsampleStage[device] = upsampleStage; + return 0; + } + + sint32 AXGetDeviceUpsampleStage(sint32 device, uint32be* upsampleStage) + { + if (device != AX_DEV_TV && device != AX_DEV_DRC) + return -1; + *upsampleStage = __AXDeviceUpsampleStage[device]; + return 0; + } + + sint32 AXGetInputSamplesPerFrame() + { + if (sndGeneric.initParam.rendererFreq == AX_RENDERER_FREQ_48KHZ) + return AX_SAMPLES_PER_3MS_48KHZ; + return AX_SAMPLES_PER_3MS_32KHZ; + } + + sint32 AXGetInputSamplesPerSec() + { + sint32 samplesPerFrame = AXGetInputSamplesPerFrame(); + sint32 samplesPerSecond = (samplesPerFrame * 1000) / 3; + return samplesPerSecond; + } + + struct AXUpsampler + { + sint32 samples[AX_SAMPLES_MAX]; + }; + + struct + { + AXUpsampler upsamplerArray[6]; + bool useLinearUpsampler; // false -> FIR, true -> linear + }__AXTVUpsampler; + + struct + { + AXUpsampler upsamplerArray[4]; + bool useLinearUpsampler; // false -> FIR, true -> linear + }__AXDRCUpsampler[2]; + + void AXUpsampler_Init(AXUpsampler* upsampler) + { + memset(upsampler, 0, sizeof(AXUpsampler)); + } + + struct AXFINALMIXCBPARAM + { + /* +0x00 */ MEMPTR<MEMPTR<sint32>> data; + /* +0x04 */ uint16be numChannelInput; + /* +0x06 */ uint16be numSamples; + /* +0x08 */ uint16be numDevices; + /* +0x0A */ uint16be numChannelOutput; + }; + + static_assert(offsetof(AXFINALMIXCBPARAM, data) == 0x00); + static_assert(offsetof(AXFINALMIXCBPARAM, numChannelInput) == 0x04); + static_assert(offsetof(AXFINALMIXCBPARAM, numSamples) == 0x06); + static_assert(offsetof(AXFINALMIXCBPARAM, numDevices) == 0x08); + static_assert(offsetof(AXFINALMIXCBPARAM, numChannelOutput) == 0x0A); + + SysAllocator<AXFINALMIXCBPARAM> __AXFinalMixCBStructTV; + SysAllocator<AXFINALMIXCBPARAM> __AXFinalMixCBStructDRC; + SysAllocator<AXFINALMIXCBPARAM> __AXFinalMixCBStructRMT; + + SysAllocator<MEMPTR<sint32>, 6> __AXFinalMixCBStructTV_dataPtrArray; + SysAllocator<MEMPTR<sint32>, 4 * 2> __AXFinalMixCBStructDRC_dataPtrArray; + + sint32 __AXFinalMixOutputChannelCount[AX_DEV_COUNT] = { 0 }; // number of output channels returned by final mix callback + + // callbacks + MPTR __AXFrameCallback = MPTR_NULL; // set via AXRegisterFrameCallback() + MPTR __AXAppFrameCallback[AX_APP_FRAME_CALLBACK_MAX]; + MPTR __AXDeviceFinalMixCallback[AX_DEV_COUNT]; + SysAllocator<coreinit::OSMutex, 1> __AXAppFrameCallbackMutex; + + void AXResetCallbacks() + { + __AXFrameCallback = MPTR_NULL; + for (sint32 i = 0; i < AX_APP_FRAME_CALLBACK_MAX; i++) + { + __AXAppFrameCallback[i] = MPTR_NULL; + } + coreinit::OSInitMutexEx(__AXAppFrameCallbackMutex.GetPtr(), NULL); + for (sint32 i = 0; i < AX_DEV_COUNT; i++) + __AXDeviceFinalMixCallback[i] = NULL; + } + + sint32 AXRegisterAppFrameCallback(MPTR funcAddr) + { + if (funcAddr == MPTR_NULL) + return -17; + OSLockMutex(__AXAppFrameCallbackMutex.GetPtr()); + for (sint32 i = 0; i < AX_APP_FRAME_CALLBACK_MAX; i++) + { + if (__AXAppFrameCallback[i] == MPTR_NULL) + { + __AXAppFrameCallback[i] = funcAddr; + OSUnlockMutex(__AXAppFrameCallbackMutex.GetPtr()); + return 0; + } + } + OSUnlockMutex(__AXAppFrameCallbackMutex.GetPtr()); + return -15; + } + + sint32 AXDeregisterAppFrameCallback(MPTR funcAddr) + { + if (funcAddr == MPTR_NULL) + return -17; + OSLockMutex(__AXAppFrameCallbackMutex.GetPtr()); + for (sint32 i = 0; i < AX_APP_FRAME_CALLBACK_MAX; i++) + { + if (__AXAppFrameCallback[i] == funcAddr) + { + __AXAppFrameCallback[i] = MPTR_NULL; + OSUnlockMutex(__AXAppFrameCallbackMutex.GetPtr()); + return 0; + } + } + OSUnlockMutex(__AXAppFrameCallbackMutex.GetPtr()); + return -16; // not found + } + + MPTR AXRegisterFrameCallback(MPTR funcAddr) + { + MPTR prevCallbackFunc = __AXFrameCallback; + __AXFrameCallback = funcAddr; + return prevCallbackFunc; + } + + sint32 __AXTVUpsamplerSampleHistory[AX_TV_CHANNEL_COUNT] = { 0 }; + sint32 __AXDRC0UpsamplerSampleHistory[AX_DRC_CHANNEL_COUNT] = { 0 }; + sint32 __AXDRC1UpsamplerSampleHistory[AX_DRC_CHANNEL_COUNT] = { 0 }; + + void AXIst_Init() + { + // todo - double check defaults + __AXDeviceUpsampleStage[AX_DEV_TV] = AX_UPSAMPLE_STAGE_BEFORE_FINALMIX; + __AXDeviceUpsampleStage[AX_DEV_DRC] = AX_UPSAMPLE_STAGE_BEFORE_FINALMIX; + __AXDeviceUpsampleStage[AX_DEV_RMT] = AX_UPSAMPLE_STAGE_BEFORE_FINALMIX; + for (sint32 i = 0; i < AX_TV_CHANNEL_COUNT; i++) + AXUpsampler_Init(__AXTVUpsampler.upsamplerArray + i); + for (sint32 i = 0; i < AX_DRC_CHANNEL_COUNT; i++) + { + AXUpsampler_Init(__AXDRCUpsampler[0].upsamplerArray + i); + AXUpsampler_Init(__AXDRCUpsampler[1].upsamplerArray + i); + } + __AXTVUpsampler.useLinearUpsampler = false; + __AXDRCUpsampler[0].useLinearUpsampler = false; + __AXDRCUpsampler[1].useLinearUpsampler = false; + memset(__AXTVUpsamplerSampleHistory, 0, sizeof(__AXTVUpsamplerSampleHistory)); + memset(__AXDRC0UpsamplerSampleHistory, 0, sizeof(__AXDRC0UpsamplerSampleHistory)); + memset(__AXDRC1UpsamplerSampleHistory, 0, sizeof(__AXDRC1UpsamplerSampleHistory)); + AXResetCallbacks(); + } + + void AXOut_ResetFinalMixCBData() + { + sint32 inputSamplesPerFrame = AXGetInputSamplesPerFrame(); + // TV + __AXFinalMixCBStructTV->data = nullptr; + __AXFinalMixCBStructTV->numChannelInput = 6; + __AXFinalMixCBStructTV->numChannelOutput = 6; + __AXFinalMixCBStructTV->numSamples = inputSamplesPerFrame; + __AXFinalMixCBStructTV->numDevices = 1; + // DRC + __AXFinalMixCBStructDRC->data = nullptr; + __AXFinalMixCBStructDRC->numChannelInput = 4; + __AXFinalMixCBStructDRC->numChannelOutput = 4; + __AXFinalMixCBStructDRC->numSamples = inputSamplesPerFrame; + __AXFinalMixCBStructDRC->numDevices = 2; + // RMT + __AXFinalMixCBStructRMT->data = nullptr; + __AXFinalMixCBStructRMT->numChannelInput = 1; + __AXFinalMixCBStructRMT->numChannelOutput = 1; + __AXFinalMixCBStructRMT->numSamples = 18; // verify + __AXFinalMixCBStructRMT->numDevices = 4; + } + + sint32 AXGetDeviceFinalMixCallback(sint32 device, uint32be* funcAddrPtr) + { + if (device != AX_DEV_TV && device != AX_DEV_DRC) + return -1; + *funcAddrPtr = __AXDeviceFinalMixCallback[device]; + return 0; + } + + sint32 AXRegisterDeviceFinalMixCallback(sint32 device, MPTR funcAddr) + { + if (device != AX_DEV_TV && device != AX_DEV_DRC) + return -1; + __AXDeviceFinalMixCallback[device] = funcAddr; + return 0; + } + + + SysAllocator<AXRemixMatrices_t, 12> g_remix_matrices; + + sint32 AXSetDeviceRemixMatrix(sint32 deviceId, uint32 inputChannelCount, uint32 outputChannelCount, const MEMPTR<float32be>& matrix) + { + // validate parameters + if (deviceId == AX_DEV_TV) + { + cemu_assert(inputChannelCount <= AX_TV_CHANNEL_COUNT); + cemu_assert(outputChannelCount == 1 || outputChannelCount == 2 || outputChannelCount == 6); + } + else if (deviceId == AX_DEV_DRC) + { + cemu_assert(inputChannelCount <= AX_DRC_CHANNEL_COUNT); + cemu_assert(outputChannelCount == 1 || outputChannelCount == 2 || outputChannelCount == 4); + } + else if (deviceId == AX_DEV_RMT) + { + cemu_assert(false); + } + else + return -1; + + auto matrices = g_remix_matrices.GetPtr(); + + // test if we already have an entry and just need to update the matrix data + for (uint32 i = 0; i < g_remix_matrices.GetCount(); ++i) + { + if (g_remix_matrices[i].deviceEntry[deviceId].channelIn == inputChannelCount && g_remix_matrices[i].deviceEntry[deviceId].channelOut == outputChannelCount) + { + g_remix_matrices[i].deviceEntry[deviceId].matrix = matrix; + return 0; + } + } + + // add new entry + for (uint32 i = 0; i < g_remix_matrices.GetCount(); ++i) + { + if (g_remix_matrices[i].deviceEntry[deviceId].channelIn == 0 && g_remix_matrices[i].deviceEntry[deviceId].channelOut == 0) + { + g_remix_matrices[i].deviceEntry[deviceId].channelIn = inputChannelCount; + g_remix_matrices[i].deviceEntry[deviceId].channelOut = outputChannelCount; + g_remix_matrices[i].deviceEntry[deviceId].matrix = matrix; + return 0; + } + } + + return -9; + } + + sint32 AXGetDeviceRemixMatrix(uint32 deviceId, uint32 inputChannelCount, uint32 outputChannelCount, MEMPTR<MEMPTR<float32be>>& matrix) + { + // validate parameters + if (deviceId == AX_DEV_TV) + { + cemu_assert(inputChannelCount <= AX_TV_CHANNEL_COUNT); + cemu_assert(outputChannelCount == 2 || outputChannelCount == 6); + } + else if (deviceId == AX_DEV_DRC) + { + cemu_assert(inputChannelCount <= AX_DRC_CHANNEL_COUNT); + cemu_assert(outputChannelCount == 1 || outputChannelCount == 2); + } + else if (deviceId == AX_DEV_RMT) + { + cemu_assert(false); + } + else + return -1; + + for (uint32 i = 0; i < g_remix_matrices.GetCount(); ++i) + { + if (g_remix_matrices[i].deviceEntry[deviceId].channelIn == inputChannelCount && g_remix_matrices[i].deviceEntry[deviceId].channelOut == outputChannelCount) + { + *matrix = g_remix_matrices[i].deviceEntry[deviceId].matrix; + return 0; + } + } + return -10; + } + + SysAllocator<sint32, AX_SAMPLES_MAX * AX_TV_CHANNEL_COUNT> __AXTVOutputBuffer; + SysAllocator<sint32, AX_SAMPLES_MAX * AX_DRC_CHANNEL_COUNT * 2> __AXDRCOutputBuffer; + + SysAllocator<sint32, AX_SAMPLES_MAX * AX_TV_CHANNEL_COUNT> __AXTempFinalMixTVBuffer; + SysAllocator<sint32, AX_SAMPLES_MAX * AX_DRC_CHANNEL_COUNT * 2> __AXTempFinalMixDRCBuffer; + + // 48KHz buffers + SysAllocator<sint32, AX_SAMPLES_MAX * AX_TV_CHANNEL_COUNT> __AXTVBuffer48; + SysAllocator<sint32, AX_SAMPLES_MAX * AX_DRC_CHANNEL_COUNT * 2> __AXDRCBuffer48; + + sint32 AXUpsampleLinear32To48(sint32* inputBuffer, sint32* outputBuffer, sint32* sampleHistory, sint32 sampleCount, bool shiftSamples, sint32 channelCount) + { + if (shiftSamples) + { + for (sint32 c = 0; c < channelCount; c++) + { + float samplePrev = (float)sampleHistory[c]; + for (sint32 i = 0; i < sampleCount; i += 2) + { + float sample0 = (float)_swapEndianS32(inputBuffer[0]); + float sample1 = (float)_swapEndianS32(inputBuffer[1]); + + inputBuffer += 2; + float s0 = samplePrev * 0.66666669f + sample0 * 0.33333331f; + float s1 = sample0; + float s2 = sample1 * 0.66666669f + sample0 * 0.33333331f; + outputBuffer[0] = _swapEndianS32(((sint32)s0) >> 8); + outputBuffer[1] = _swapEndianS32(((sint32)s1) >> 8); + outputBuffer[2] = _swapEndianS32(((sint32)s2) >> 8); + outputBuffer += 3; + samplePrev = sample1; + } + sampleHistory[c] = (sint32)samplePrev; + } + } + else + { + for (sint32 c = 0; c < channelCount; c++) + { + float samplePrev = (float)sampleHistory[c]; + for (sint32 i = 0; i < sampleCount; i += 2) + { + float sample0 = (float)_swapEndianS32(inputBuffer[0]); + float sample1 = (float)_swapEndianS32(inputBuffer[1]); + inputBuffer += 2; + float s0 = samplePrev * 0.66666669f + sample0 * 0.33333331f; + float s1 = sample0; + float s2 = sample1 * 0.66666669f + sample0 * 0.33333331f; + outputBuffer[0] = _swapEndianS32(((sint32)s0)); + outputBuffer[1] = _swapEndianS32(((sint32)s1)); + outputBuffer[2] = _swapEndianS32(((sint32)s2)); + outputBuffer += 3; + samplePrev = sample1; + } + sampleHistory[c] = (sint32)samplePrev; + } + } + return (sampleCount / 2) * 3; + } + + void AXTransferSamples(sint32* input, sint32* output, sint32 count, bool shiftSamples) + { + if (shiftSamples) + { + for (sint32 i = 0; i < count; i++) + { + sint32 s = _swapEndianS32(input[i]); + s >>= 8; + output[i] = _swapEndianS32(s); + } + } + else + { + for (sint32 i = 0; i < count; i++) + output[i] = input[i]; + } + } + + void AXIst_ProcessFinalMixCallback() + { + bool forceLinearUpsampler = true; + bool isRenderer48 = sndGeneric.initParam.rendererFreq == AX_RENDERER_FREQ_48KHZ; + sint32 inputSampleCount = AXGetInputSamplesPerFrame(); + // TV + if (isRenderer48 || __AXDeviceUpsampleStage[AX_DEV_TV] != AX_UPSAMPLE_STAGE_BEFORE_FINALMIX) + { + // only copy + sint32* inputBuffer = __AXTVOutputBuffer; + sint32* outputBuffer = __AXTempFinalMixTVBuffer.GetPtr(); + sint32 sampleCount = inputSampleCount * AX_TV_CHANNEL_COUNT; + for (sint32 i = 0; i < sampleCount; i++) + { + sint32 sample0 = _swapEndianS32(inputBuffer[0]); + sample0 >>= 8; + inputBuffer++; + *outputBuffer = _swapEndianS32(sample0); + outputBuffer++; + } + for (sint32 c = 0; c < AX_TV_CHANNEL_COUNT; c++) + { + __AXFinalMixCBStructTV_dataPtrArray[c] = __AXTempFinalMixTVBuffer.GetPtr() + c * inputSampleCount; + } + __AXFinalMixCBStructTV->numSamples = (uint16)inputSampleCount; + __AXFinalMixCBStructTV->data = __AXFinalMixCBStructTV_dataPtrArray.GetPtr(); + } + else + { + // upsample + sint32 upsampledSampleCount; + if (__AXTVUpsampler.useLinearUpsampler || forceLinearUpsampler) + { + upsampledSampleCount = AXUpsampleLinear32To48(__AXTVOutputBuffer, __AXTVBuffer48.GetPtr(), __AXTVUpsamplerSampleHistory, inputSampleCount, true, AX_TV_CHANNEL_COUNT); + } + else + { + cemu_assert(false); // todo + } + for (sint32 c = 0; c < AX_TV_CHANNEL_COUNT; c++) + { + __AXFinalMixCBStructTV_dataPtrArray[c] = __AXTVBuffer48.GetPtr() + c * upsampledSampleCount; + } + __AXFinalMixCBStructTV->numSamples = (uint16)upsampledSampleCount; + __AXFinalMixCBStructTV->data = __AXFinalMixCBStructTV_dataPtrArray.GetPtr(); + } + // DRC + if (isRenderer48 || __AXDeviceUpsampleStage[AX_DEV_DRC] != AX_UPSAMPLE_STAGE_BEFORE_FINALMIX) + { + // only copy + sint32* inputBuffer = __AXDRCOutputBuffer; + sint32* outputBuffer = __AXTempFinalMixDRCBuffer.GetPtr(); + sint32 sampleCount = inputSampleCount * AX_DRC_CHANNEL_COUNT * 2; + for (sint32 i = 0; i < sampleCount; i++) + { + sint32 sample0 = _swapEndianS32(inputBuffer[0]); + sample0 >>= 8; + inputBuffer++; + *outputBuffer = _swapEndianS32(sample0); + outputBuffer++; + } + for (sint32 c = 0; c < AX_DRC_CHANNEL_COUNT*2; c++) + { + __AXFinalMixCBStructDRC_dataPtrArray[c] = __AXTempFinalMixDRCBuffer.GetPtr() + c * inputSampleCount; + } + __AXFinalMixCBStructDRC->numSamples = (uint16)inputSampleCount; + __AXFinalMixCBStructDRC->data = __AXFinalMixCBStructDRC_dataPtrArray.GetPtr(); + } + else + { + // upsample + sint32 upsampledSampleCount; + // DRC0 + if (__AXDRCUpsampler[0].useLinearUpsampler || forceLinearUpsampler) + upsampledSampleCount = AXUpsampleLinear32To48(__AXDRCOutputBuffer, __AXDRCBuffer48.GetPtr(), __AXDRC0UpsamplerSampleHistory, inputSampleCount, true, AX_DRC_CHANNEL_COUNT); + else + { + cemu_assert(false); // todo + } + // DRC1 + if (__AXDRCUpsampler[1].useLinearUpsampler || forceLinearUpsampler) + upsampledSampleCount = AXUpsampleLinear32To48(__AXDRCOutputBuffer + (upsampledSampleCount*AX_DRC_CHANNEL_COUNT), __AXDRCBuffer48.GetPtr()+inputSampleCount*AX_DRC_CHANNEL_COUNT, __AXDRC1UpsamplerSampleHistory, inputSampleCount, true, AX_DRC_CHANNEL_COUNT); + else + { + cemu_assert(false); // todo + } + for (sint32 c = 0; c < AX_DRC_CHANNEL_COUNT * 2; c++) + __AXFinalMixCBStructDRC_dataPtrArray[c] = __AXDRCBuffer48.GetPtr() + c * upsampledSampleCount; + __AXFinalMixCBStructDRC->numSamples = (uint16)upsampledSampleCount; + __AXFinalMixCBStructDRC->data = __AXFinalMixCBStructDRC_dataPtrArray.GetPtr(); + } + + + // do callbacks + __AXFinalMixOutputChannelCount[0] = AX_TV_CHANNEL_COUNT; + __AXFinalMixOutputChannelCount[1] = AX_DRC_CHANNEL_COUNT; + __AXFinalMixOutputChannelCount[2] = AX_RMT_CHANNEL_COUNT; + for (sint32 i = 0; i < AX_DEV_COUNT; i++) + { + if (__AXDeviceFinalMixCallback[i] == MPTR_NULL) + continue; + MEMPTR<AXFINALMIXCBPARAM> cbStruct; + if (i == AX_DEV_TV) + cbStruct = &__AXFinalMixCBStructTV; + else if (i == AX_DEV_DRC) + cbStruct = &__AXFinalMixCBStructDRC; + else if (i == AX_DEV_RMT) + cbStruct = &__AXFinalMixCBStructRMT; + + if (i == 2 && __AXDeviceFinalMixCallback[i]) + { + cemu_assert_debug(false); // RMT callbacks need testing + } + + PPCCoreCallback(__AXDeviceFinalMixCallback[i], cbStruct); + __AXFinalMixOutputChannelCount[i] = (sint32)cbStruct->numChannelOutput; + } + + // handle upsampling + if (isRenderer48) + { + // copy TV + AXTransferSamples(__AXTempFinalMixTVBuffer.GetPtr(), __AXTVBuffer48.GetPtr(), AX_SAMPLES_PER_3MS_48KHZ*AX_TV_CHANNEL_COUNT, false); + // copy DRC 0 + AXTransferSamples(__AXTempFinalMixDRCBuffer.GetPtr(), __AXDRCBuffer48.GetPtr(), AX_SAMPLES_PER_3MS_48KHZ*AX_DRC_CHANNEL_COUNT, false); + } + else + { + if (__AXDeviceUpsampleStage[AX_DEV_TV] == AX_UPSAMPLE_STAGE_BEFORE_FINALMIX) + { + // final mix is 48KHz + // no need to copy since samples are already in right buffer + } + else + { + // final mix is 32KHz -> upsample + // TV + sint32 upsampledSampleCount; + if (__AXTVUpsampler.useLinearUpsampler || forceLinearUpsampler) + { + upsampledSampleCount = AXUpsampleLinear32To48(__AXTempFinalMixTVBuffer.GetPtr(), __AXTVBuffer48.GetPtr(), __AXTVUpsamplerSampleHistory, inputSampleCount, false, AX_TV_CHANNEL_COUNT); + } + else + { + cemu_assert(false); + } + // DRC0 + if (__AXDRCUpsampler[0].useLinearUpsampler || forceLinearUpsampler) + { + AXUpsampleLinear32To48(__AXTempFinalMixDRCBuffer.GetPtr(), __AXDRCBuffer48.GetPtr(), __AXDRC0UpsamplerSampleHistory, inputSampleCount, false, AX_DRC_CHANNEL_COUNT); + } + else + { + cemu_assert(false); + } + } + } + } + + void AXIst_SyncSingleVPB(AXVPB* vpb) + { + uint32 index = vpb->index; + uint32 sync = vpb->sync; + AXVPBInternal_t* internalVPB = __AXVPBInternalVoiceArray + index; + AXVPBInternal_t* internalShadowCopy = __AXVPBInternalVoiceShadowCopyArrayPtr + index; + + // shadow copy -> internal data + internalShadowCopy->nextAddrHigh = internalVPB->nextAddrHigh; + internalShadowCopy->nextAddrLow = internalVPB->nextAddrLow; + if (internalVPB->nextToProcess != nullptr) + internalShadowCopy->nextToProcess = __AXVPBInternalVoiceShadowCopyArrayPtr + (sint32)internalVPB->nextToProcess->index; + else + internalShadowCopy->nextToProcess = nullptr; + + internalShadowCopy->mixerSelect = internalVPB->mixerSelect; + + internalShadowCopy->ukn2A2 = internalVPB->ukn2A2; + + internalShadowCopy->reserved296 = internalVPB->reserved296; + internalShadowCopy->reserved298 = internalVPB->reserved298; + internalShadowCopy->reserved29A = internalVPB->reserved29A; + internalShadowCopy->reserved29C = internalVPB->reserved29C; + internalShadowCopy->reserved29E = internalVPB->reserved29E; + + // sync current playback state + if ((sync&AX_SYNCFLAG_PLAYBACKSTATE) == 0) + { + uint32 playbackState = _swapEndianU16(internalShadowCopy->playbackState); + vpb->playbackState = playbackState; + internalVPB->playbackState = _swapEndianU16(playbackState); + } + // sync current offset + if ((sync&(AX_SYNCFLAG_CURRENTOFFSET | AX_SYNCFLAG_OFFSETS)) == 0) + { + internalVPB->internalOffsets.currentOffsetPtrHigh = internalShadowCopy->internalOffsets.currentOffsetPtrHigh; + internalVPB->internalOffsets.currentOffsetPtrLow = internalShadowCopy->internalOffsets.currentOffsetPtrLow; + } + // sync volume + if ((sync&AX_SYNCFLAG_VE) == 0) + { + internalVPB->veVolume = internalShadowCopy->veVolume; + } + // sync adpcm data + if ((sync&AX_SYNCFLAG_ADPCMDATA) == 0) + { + for (sint32 i = 0; i < 16; i++) + internalVPB->adpcmData.coef[i] = internalShadowCopy->adpcmData.coef[i]; + internalVPB->adpcmData.gain = internalShadowCopy->adpcmData.gain; + internalVPB->adpcmData.scale = internalShadowCopy->adpcmData.scale; + internalVPB->adpcmData.yn1 = internalShadowCopy->adpcmData.yn1; + internalVPB->adpcmData.yn2 = internalShadowCopy->adpcmData.yn2; + } + // sync src data + if ((sync&AX_SYNCFLAG_SRCDATA) == 0) + { + internalVPB->src.currentFrac = internalShadowCopy->src.currentFrac; + internalVPB->src.historySamples[0] = internalShadowCopy->src.historySamples[0]; + internalVPB->src.historySamples[1] = internalShadowCopy->src.historySamples[1]; + internalVPB->src.historySamples[2] = internalShadowCopy->src.historySamples[2]; + internalVPB->src.historySamples[3] = internalShadowCopy->src.historySamples[3]; + } + if (AXVoiceProtection_IsProtectedByAnyThread(vpb)) + { + // if voice is currently protected, dont sync remaining flags + return; + } + // internal data -> shadow copy + // sync src type + if ((sync&AX_SYNCFLAG_SRCFILTER) != 0) + { + internalShadowCopy->srcFilterMode = internalVPB->srcFilterMode; + internalShadowCopy->srcTapFilter = internalVPB->srcTapFilter; + } + // Sync device mix + if ((sync&AX_SYNCFLAG_DEVICEMIXMASK) != 0) + { + memcpy(internalShadowCopy->deviceMixMaskTV, internalVPB->deviceMixMaskTV, 8); + memcpy(internalShadowCopy->deviceMixMaskDRC, internalVPB->deviceMixMaskDRC, 0x10); + memcpy(internalShadowCopy->deviceMixMaskRMT, internalVPB->deviceMixMaskRMT, 0x20); + } + // sync device mix + if ((sync & AX_SYNCFLAG_DEVICEMIX) != 0) + { + memcpy(internalShadowCopy->deviceMixTV, internalVPB->deviceMixTV, 0x60); + memcpy(internalShadowCopy->deviceMixDRC, internalVPB->deviceMixDRC, 0x80); + memcpy(internalShadowCopy->deviceMixRMT, internalVPB->deviceMixRMT, 0x40); + } + // sync playback state + if ((sync&AX_SYNCFLAG_PLAYBACKSTATE) != 0) + { + internalShadowCopy->playbackState = internalVPB->playbackState; + } + // sync voice type + if ((sync&AX_SYNCFLAG_VOICETYPE) != 0) + { + internalShadowCopy->voiceType = internalVPB->voiceType; + } + // itd + if ((sync&AX_SYNCFLAG_ITD40) == 0) + { + if ((sync&AX_SYNCFLAG_ITD20) != 0) + { + //cemu_assert_debug(false); // sync PB itd + } + } + else + { + // sync itd + internalShadowCopy->reserved176_itdRelated = internalVPB->reserved176_itdRelated; + internalShadowCopy->reserved178_itdRelated = internalVPB->reserved178_itdRelated; + } + // sync volume envelope + // the part below could be incorrect (it seems strange that the delta flag overwrites the full ve flag? But PPC code looks like this) + if ((sync&AX_SYNCFLAG_VEDELTA) != 0) + { + internalShadowCopy->veDelta = internalVPB->veDelta; + } + else if ((sync&AX_SYNCFLAG_VE) != 0) + { + internalShadowCopy->veVolume = internalVPB->veVolume; + internalShadowCopy->veDelta = internalVPB->veDelta; + } + // sync offsets + if ((sync&AX_SYNCFLAG_OFFSETS) != 0) + { + // sync entire offsets block + memcpy(&internalShadowCopy->internalOffsets, &internalVPB->internalOffsets, sizeof(axOffsetsInternal_t)); + } + else + { + // sync individual offset fields + if ((sync&AX_SYNCFLAG_LOOPFLAG) != 0) + { + // sync loop flag + internalShadowCopy->internalOffsets.loopFlag = internalVPB->internalOffsets.loopFlag; + } + if ((sync&AX_SYNCFLAG_LOOPOFFSET) != 0) + { + // sync loop offset + internalShadowCopy->internalOffsets.loopOffsetPtrLow = internalVPB->internalOffsets.loopOffsetPtrLow; + internalShadowCopy->internalOffsets.loopOffsetPtrHigh = internalVPB->internalOffsets.loopOffsetPtrHigh; + } + if ((sync&AX_SYNCFLAG_ENDOFFSET) != 0) + { + // sync end offset + internalShadowCopy->internalOffsets.endOffsetPtrLow = internalVPB->internalOffsets.endOffsetPtrLow; + internalShadowCopy->internalOffsets.endOffsetPtrHigh = internalVPB->internalOffsets.endOffsetPtrHigh; + } + if ((sync&AX_SYNCFLAG_CURRENTOFFSET) != 0) + { + // sync current offset + internalShadowCopy->internalOffsets.currentOffsetPtrLow = internalVPB->internalOffsets.currentOffsetPtrLow; + internalShadowCopy->internalOffsets.currentOffsetPtrHigh = internalVPB->internalOffsets.currentOffsetPtrHigh; + } + } + if ((sync&AX_SYNCFLAG_ADPCMDATA) != 0) + { + // sync adpcm data + for (sint32 i = 0; i < 16; i++) + internalShadowCopy->adpcmData.coef[i] = internalVPB->adpcmData.coef[i]; + internalShadowCopy->adpcmData.gain = internalVPB->adpcmData.gain; + internalShadowCopy->adpcmData.scale = internalVPB->adpcmData.scale; + } + if ((sync&AX_SYNCFLAG_SRCDATA) != 0) + { + // sync voice all src data + internalShadowCopy->src.ratioHigh = internalVPB->src.ratioHigh; + internalShadowCopy->src.ratioLow = internalVPB->src.ratioLow; + internalShadowCopy->src.currentFrac = internalVPB->src.currentFrac; + internalShadowCopy->src.historySamples[0] = internalVPB->src.historySamples[0]; + internalShadowCopy->src.historySamples[1] = internalVPB->src.historySamples[1]; + internalShadowCopy->src.historySamples[2] = internalVPB->src.historySamples[2]; + internalShadowCopy->src.historySamples[3] = internalVPB->src.historySamples[3]; + } + else + { + if ((sync&AX_SYNCFLAG_SRCRATIO) != 0) + { + // sync voice src ratio + internalShadowCopy->src.ratioHigh = internalVPB->src.ratioHigh; + internalShadowCopy->src.ratioLow = internalVPB->src.ratioLow; + } + } + if ((sync&AX_SYNCFLAG_ADPCMLOOP) != 0) + { + // sync voice adpcm loop + internalShadowCopy->adpcmLoop.loopScale = internalVPB->adpcmLoop.loopScale; + internalShadowCopy->adpcmLoop.loopYn1 = internalVPB->adpcmLoop.loopYn1; + internalShadowCopy->adpcmLoop.loopYn2 = internalVPB->adpcmLoop.loopYn2; + } + if ((sync&AX_SYNCFLAG_LPFCOEF) != 0) + { + // sync lpf coef + internalShadowCopy->lpf.a0 = internalVPB->lpf.a0; + internalShadowCopy->lpf.b0 = internalVPB->lpf.b0; + } + else + { + if ((sync&AX_SYNCFLAG_LPFDATA) != 0) + { + // sync lpf + internalShadowCopy->lpf.on = internalVPB->lpf.on; + internalShadowCopy->lpf.yn1 = internalVPB->lpf.yn1; + internalShadowCopy->lpf.a0 = internalVPB->lpf.a0; + internalShadowCopy->lpf.b0 = internalVPB->lpf.b0; + } + } + if ((sync&AX_SYNCFLAG_BIQUADCOEF) != 0) + { + // sync biquad coef + internalShadowCopy->biquad.b0 = internalVPB->biquad.b0; + internalShadowCopy->biquad.b1 = internalVPB->biquad.b1; + internalShadowCopy->biquad.b2 = internalVPB->biquad.b2; + internalShadowCopy->biquad.a1 = internalVPB->biquad.a1; + internalShadowCopy->biquad.a2 = internalVPB->biquad.a2; + } + else if ((sync&AX_SYNCFLAG_BIQUADDATA) != 0) + { + // sync biquad + internalShadowCopy->biquad.on = internalVPB->biquad.on; + internalShadowCopy->biquad.xn1 = internalVPB->biquad.xn1; + internalShadowCopy->biquad.xn2 = internalVPB->biquad.xn2; + internalShadowCopy->biquad.yn1 = internalVPB->biquad.yn1; + internalShadowCopy->biquad.yn2 = internalVPB->biquad.yn2; + internalShadowCopy->biquad.b0 = internalVPB->biquad.b0; + internalShadowCopy->biquad.b1 = internalVPB->biquad.b1; + internalShadowCopy->biquad.b2 = internalVPB->biquad.b2; + internalShadowCopy->biquad.a1 = internalVPB->biquad.a1; + internalShadowCopy->biquad.a2 = internalVPB->biquad.a2; + } + if ((sync&AX_SYNCFLAG_VOICEREMOTEON) != 0) + { + // sync VoiceRmtOn (AXSetVoiceRmtOn) + internalShadowCopy->reserved148_voiceRmtOn = internalVPB->reserved148_voiceRmtOn; + } + if ((sync&AX_SYNCFLAG_4000000) != 0) + { + // todo + } + if ((sync&AX_SYNCFLAG_8000000) != 0) + { + // todo + // AXSetVoiceRmtSrc + } + // other flags todo: 0x10000000, 0x20000000, 0x40000000 for RmtIIR + } + + void AXIst_SyncVPB(AXVPBInternal_t** lastProcessedDSPShadowCopy, AXVPBInternal_t** lastProcessedPPCShadowCopy) + { + __AXVoiceListSpinlock.acquire(); + + AXVPBInternal_t* previousInternalDSP = nullptr; + AXVPBInternal_t* previousInternalPPC = nullptr; + for (sint32 priority = AX_PRIORITY_MAX - 1; priority >= AX_PRIORITY_LOWEST; priority--) + { + auto& voiceArray = AXVoiceList_GetListByPriority(priority); + for(auto vpb : voiceArray) + { + sint32 index = vpb->index; + sint32 depop = vpb->depop; + AXVPBInternal_t* internalVPB = __AXVPBInternalVoiceArray + index; + AXVPBInternal_t* internalShadowCopy = __AXVPBInternalVoiceShadowCopyArrayPtr + index; + internalVPB->mixerSelect = vpb->mixerSelect; + AXVPB* nextVpb = vpb->next.GetPtr(); + if (depop) + { + AXMix_DepopVoice(internalShadowCopy); + vpb->depop = 0; + } + if (internalVPB->playbackState != _swapEndianU16(1) && vpb->sync == 0) + { + internalVPB->ukn2A2 = 2; + internalShadowCopy->ukn2A2 = 2; + internalVPB->nextAddrHigh = 0; + internalVPB->nextAddrLow = 0; + } + else + { + internalVPB->ukn2A2 = 1; + if (previousInternalPPC) + { + internalVPB->nextAddrHigh = previousInternalPPC->selfAddrHigh; + internalVPB->nextAddrLow = previousInternalPPC->selfAddrLow; + internalVPB->nextToProcess = previousInternalPPC; + previousInternalPPC = internalVPB; + } + else + { + internalVPB->nextAddrHigh = 0; + internalVPB->nextAddrLow = 0; + internalVPB->nextToProcess = nullptr; + previousInternalPPC = internalVPB; + } + AXIst_SyncSingleVPB(vpb); + if (!AXVoiceProtection_IsProtectedByAnyThread(vpb)) + { + vpb->depop = 0; + vpb->sync = 0; + } + } + } + } + // depop and reset voices which just stopped playing + auto& freeVoicesArray = AXVoiceList_GetFreeVoices(); + for(auto vpb : freeVoicesArray) + { + AXVPBInternal_t* internalVPB = __AXVPBInternalVoiceArray + (sint32)vpb->index; + AXVPBInternal_t* internalShadowCopy = __AXVPBInternalVoiceShadowCopyArrayPtr + (sint32)vpb->index; + if (vpb->depop != (uint32be)0) + { + AXMix_DepopVoice(internalShadowCopy); + vpb->depop = 0; + } + vpb->playbackState = 0; + internalVPB->ukn2A2 = 2; + internalShadowCopy->ukn2A2 = 2; + internalVPB->playbackState = 0; + internalShadowCopy->playbackState = 0; + } + // return last processed DSP/PPC voice internal shadow copy + if (lastProcessedDSPShadowCopy) + { + if (previousInternalDSP) + { + AXVPBInternal_t* internalShadowCopy = __AXVPBInternalVoiceShadowCopyArrayPtr + (sint32)previousInternalDSP->index; + *lastProcessedDSPShadowCopy = internalShadowCopy; + } + else + *lastProcessedDSPShadowCopy = nullptr; + } + if (lastProcessedPPCShadowCopy) + { + if (previousInternalPPC) + { + AXVPBInternal_t* internalShadowCopy = __AXVPBInternalVoiceShadowCopyArrayPtr + (sint32)previousInternalPPC->index; + *lastProcessedPPCShadowCopy = internalShadowCopy; + } + else + *lastProcessedPPCShadowCopy = nullptr; + } + __AXVoiceListSpinlock.release(); + } + + void AXIst_HandleFrameCallbacks() + { + // frame callback + if (__AXFrameCallback != MPTR_NULL) + { + // execute frame callback (no params) + PPCCore_executeCallbackInternal(__AXFrameCallback); + } + // app frame callback + for (sint32 i = 0; i < AX_APP_FRAME_CALLBACK_MAX; i++) + { + if (__AXAppFrameCallback[i] == MPTR_NULL) + continue; + // execute app frame callback (no params) + PPCCore_executeCallbackInternal(__AXAppFrameCallback[i]); + } + } + + void AXIst_ApplyDeviceRemix(sint32be* samples, float32be* matrix, sint32 inputChannelCount, sint32 outputChannelCount, sint32 sampleCount) + { + for (auto i = 0; i < sampleCount; ++i) + { + float tmp[6]{}; + for(auto j = 0; j < inputChannelCount; ++j) + { + tmp[j] = (float)samples[j * sampleCount + i]; + } + + float32be* mtx = matrix; + int tmpOut[10]{}; + for(auto j = 0; j < outputChannelCount; ++j) + { + tmpOut[j] = 0; + for (auto k = 0; k < inputChannelCount; ++k) + { + tmpOut[j] += (int)(tmp[k] * (*mtx)); + mtx++; + } + } + + for (auto j = 0; j < outputChannelCount; ++j) + { + samples[j * sampleCount + i] = tmpOut[j]; + } + } + } + + void AXIst_HandleDeviceRemix() + { + extern SysAllocator<AXRemixMatrices_t, 12> g_remix_matrices; + extern sint32 __AXOutTVOutputChannelCount; + extern sint32 __AXOutDRCOutputChannelCount; + + // tv remix matrix + for(uint32 i = 0; i < g_remix_matrices.GetCount(); ++i) + { + const auto& entry = g_remix_matrices[i]; + if(entry.deviceEntry[0].channelIn == __AXFinalMixCBStructTV->numChannelInput && entry.deviceEntry[0].channelOut == __AXOutTVOutputChannelCount && !entry.deviceEntry[0].matrix.IsNull()) + { + AXIst_ApplyDeviceRemix((sint32be*)__AXTVBuffer48.GetPtr(), entry.deviceEntry[0].matrix.GetPtr(), __AXFinalMixCBStructTV->numChannelInput, __AXOutTVOutputChannelCount, AX_SAMPLES_PER_3MS_48KHZ); + break; + } + } + + // drc remix matrix + for (uint32 i = 0; i < g_remix_matrices.GetCount(); ++i) + { + const auto& entry = g_remix_matrices[i]; + if (entry.deviceEntry[1].channelIn == __AXFinalMixCBStructDRC->numChannelInput && entry.deviceEntry[1].channelOut == __AXOutDRCOutputChannelCount && !entry.deviceEntry[0].matrix.IsNull()) + { + AXIst_ApplyDeviceRemix((sint32be*)__AXDRCBuffer48.GetPtr(), entry.deviceEntry[1].matrix.GetPtr(), __AXFinalMixCBStructDRC->numChannelInput, __AXOutDRCOutputChannelCount, AX_SAMPLES_PER_3MS_48KHZ); + break; + } + } + } + + std::atomic_bool __AXIstIsProcessingFrame = false; + + SysAllocator<OSThread_t> __AXIstThread; + SysAllocator<uint8, 0x4000> __AXIstThreadStack; + + SysAllocator<coreinit::OSMessage, 0x10> __AXIstThreadMsgArray; + SysAllocator<coreinit::OSMessageQueue, 1> __AXIstThreadMsgQueue; + + void AXIst_InitThread() + { + // create ist message queue + OSInitMessageQueue(__AXIstThreadMsgQueue.GetPtr(), __AXIstThreadMsgArray.GetPtr(), 0x10); + // create thread + uint8 istThreadAttr = 0; + coreinit::OSCreateThreadType(__AXIstThread.GetPtr(), PPCInterpreter_makeCallableExportDepr(AXIst_ThreadEntry), 0, &__AXIstThreadMsgQueue, __AXIstThreadStack.GetPtr() + 0x4000, 0x4000, 14, istThreadAttr, OSThread_t::THREAD_TYPE::TYPE_DRIVER); + coreinit::OSResumeThread(__AXIstThread.GetPtr()); + } + + OSThread_t* AXIst_GetThread() + { + return __AXIstThread.GetPtr(); + } + + void AXIst_GenerateFrame() + { + // generate one frame (3MS) of audio + __AXIstIsProcessingFrame.store(true); + + memset(__AXTVOutputBuffer.GetPtr(), 0, AX_SAMPLES_PER_3MS_48KHZ * AX_TV_CHANNEL_COUNT * sizeof(sint32)); + memset(__AXDRCOutputBuffer.GetPtr(), 0, AX_SAMPLES_PER_3MS_48KHZ * AX_DRC_CHANNEL_COUNT * sizeof(sint32)); + + AXVPBInternal_t* internalShadowCopyDSPHead = nullptr; + AXVPBInternal_t* internalShadowCopyPPCHead = nullptr; + + AXIst_SyncVPB(&internalShadowCopyDSPHead, &internalShadowCopyPPCHead); + + if (internalShadowCopyDSPHead) + assert_dbg(); + + AXMix_process(internalShadowCopyPPCHead); + + AXOut_ResetFinalMixCBData(); + AXIst_ProcessFinalMixCallback(); + AXIst_HandleDeviceRemix(); + + // todo - additional phases. See unimplemented API: + // AXSetDRCVSMode + // AXSetDeviceCompressor + // AXRegisterPostFinalMixCallback + + AXOut_SubmitTVFrame(0); + AXOut_SubmitDRCFrame(0); + + __AXIstIsProcessingFrame.store(false); + } + + void AXIst_ThreadEntry(PPCInterpreter_t* hCPU) + { + while (true) + { + StackAllocator<coreinit::OSMessage, 1> msg; + OSReceiveMessage(__AXIstThreadMsgQueue.GetPtr(), msg.GetPointer(), OS_MESSAGE_BLOCK); + if (msg.GetPointer()->message == 2) + { + forceLogDebug_printf("Shut down of AX thread requested"); + coreinit::OSExitThread(0); + break; + } + else if (msg.GetPointer()->message != 1) + assert_dbg(); + AXIst_GenerateFrame(); + numProcessedFrames++; + } + } + + SysAllocator<coreinit::OSMessage> _queueFrameMsg; + + void AXIst_QueueFrame() + { + coreinit::OSMessage* msg = _queueFrameMsg.GetPtr(); + msg->message = 1; + msg->data0 = 0; + msg->data1 = 0; + msg->data2 = 0; + OSSendMessage(__AXIstThreadMsgQueue.GetPtr(), msg, 0); + } + + void AXIst_StopThread() + { + cemu_assert_debug(coreinit::OSIsThreadTerminated(AXIst_GetThread()) == false); + // request thread stop + coreinit::OSMessage* msg = _queueFrameMsg.GetPtr(); + msg->message = 2; + msg->data0 = 0; + msg->data1 = 0; + msg->data2 = 0; + OSSendMessage(__AXIstThreadMsgQueue.GetPtr(), msg, 0); + while (coreinit::OSIsThreadTerminated(AXIst_GetThread()) == false) + PPCCore_switchToScheduler(); + } + + bool AXIst_IsFrameBeingProcessed() + { + return __AXIstIsProcessingFrame.load(); + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/snd_core/ax_mix.cpp b/src/Cafe/OS/libs/snd_core/ax_mix.cpp new file mode 100644 index 00000000..8e959b28 --- /dev/null +++ b/src/Cafe/OS/libs/snd_core/ax_mix.cpp @@ -0,0 +1,987 @@ +#include "Cafe/OS/libs/snd_core/ax.h" +#include "Cafe/OS/libs/snd_core/ax_internal.h" +#include "Cafe/HW/MMU/MMU.h" +#include "config/ActiveSettings.h" + +void mic_updateOnAXFrame(); + +namespace snd_core +{ + uint32 __AXDefaultMixerSelect = AX_MIXER_SELECT_BOTH; + + uint16 __AXTVAuxReturnVolume[AX_AUX_BUS_COUNT]; + + void AXSetDefaultMixerSelect(uint32 mixerSelect) + { + __AXDefaultMixerSelect = mixerSelect; + } + + uint32 AXGetDefaultMixerSelect() + { + return __AXDefaultMixerSelect; + } + + void AXMix_Init() + { + AXSetDefaultMixerSelect(AX_MIXER_SELECT_PPC); + } + + void AXMix_DepopVoice(AXVPBInternal_t* internalShadowCopy) + { + // todo + } + +#define handleAdpcmDecodeLoop() \ + if (internalShadowCopy->internalOffsets.loopFlag != 0) \ + { \ + scale = _swapEndianU16(internalShadowCopy->adpcmLoop.loopScale); \ + delta = 1 << (scale & 0xF); \ + coefIndex = (scale >> 4) & 7; \ + coefA = (sint32)(sint16)_swapEndianU16(internalShadowCopy->adpcmData.coef[coefIndex * 2 + 0]); \ + coefB = (sint32)(sint16)_swapEndianU16(internalShadowCopy->adpcmData.coef[coefIndex * 2 + 1]); \ + playbackNibbleOffset = vpbLoopOffset; \ + } \ + else \ + { \ + /* no loop */ \ + internalShadowCopy->playbackState = 0; \ + memset(outputWriter, 0, sampleCount * sizeof(sint16)); \ + break; \ + } + + uint32 _AX_fixAdpcmEndOffset(uint32 adpcmOffset) + { + // How to Survive uses an end offset of 0x40000 which is not a valid adpcm sample offset and thus can never be reached (the ppc side decoder jumps from 0x3FFFF to 0x40002) + // todo - investigate if the DSP decoder routines can handle invalid ADPCM end offsets + + // for now we use this workaround to force a valid address + if ((adpcmOffset & 0xF) <= 1) + return adpcmOffset + 2; + return adpcmOffset; + } + + void AX_readADPCMSamples(AXVPBInternal_t* internalShadowCopy, sint16* output, sint32 sampleCount) + { + if (internalShadowCopy->playbackState == 0) + { + memset(output, 0, sampleCount * sizeof(sint16)); + return; + } + + AXVPB* vpb = __AXVPBArrayPtr + (sint32)internalShadowCopy->index; + + uint8* sampleBase = (uint8*)memory_getPointerFromVirtualOffset(_swapEndianU32(vpb->offsets.samples)); + + uint32 vpbLoopOffset = _swapEndianU32(*(uint32*)&vpb->offsets.loopOffset); + uint32 vpbEndOffset = _swapEndianU32(*(uint32*)&vpb->offsets.endOffset); + + vpbEndOffset = _AX_fixAdpcmEndOffset(vpbEndOffset); + + uint32 internalCurrentOffset = _swapEndianU32(*(uint32*)&internalShadowCopy->internalOffsets.currentOffsetPtrHigh); + uint32 internalLoopOffset = _swapEndianU32(*(uint32*)&internalShadowCopy->internalOffsets.loopOffsetPtrHigh); + + sint32 scale = (sint32)_swapEndianU16(internalShadowCopy->adpcmData.scale); + sint32 delta = 1 << (scale & 0xF); + sint32 coefIndex = (scale >> 4) & 7; + + sint32 coefA = (sint32)(sint16)_swapEndianU16(internalShadowCopy->adpcmData.coef[coefIndex * 2 + 0]); + sint32 coefB = (sint32)(sint16)_swapEndianU16(internalShadowCopy->adpcmData.coef[coefIndex * 2 + 1]); + + sint32 hist0 = (sint32)_swapEndianS16(internalShadowCopy->adpcmData.yn1); + sint32 hist1 = (sint32)_swapEndianS16(internalShadowCopy->adpcmData.yn2); + + uint32 playbackNibbleOffset = vpbLoopOffset + (internalCurrentOffset - internalLoopOffset); + uint32 playbackNibbleOffsetStart = playbackNibbleOffset; + + sint16* outputWriter = output; + // decode samples from current partial ADPCM block + while (sampleCount && (playbackNibbleOffset & 0xF)) + { + sint8 nibble = (sint8)sampleBase[(sint32)playbackNibbleOffset >> 1]; + nibble <<= (4 * ((playbackNibbleOffset & 1))); + nibble &= 0xF0; + sint32 v = (sint32)nibble; + v <<= (11 - 4); + v *= delta; + v += (hist0 * coefA + hist1 * coefB); + v = (v + 0x400) >> 11; + v = std::clamp(v, -32768, 32767); + + hist1 = hist0; + hist0 = v; + *outputWriter = v; + outputWriter++; + + // check for loop / end offset + if (playbackNibbleOffset == vpbEndOffset) + { + handleAdpcmDecodeLoop(); + } + else + { + playbackNibbleOffset++; + } + sampleCount--; + } + // optimized code to decode whole blocks + if (!(playbackNibbleOffset <= vpbEndOffset && ((uint64)playbackNibbleOffset + (uint64)(sampleCount * 16 / 14)) >= (uint64)vpbEndOffset)) + { + while (sampleCount >= 14) // 14 samples per 16 byte block + { + // decode header + sint8* sampleInputData = (sint8*)sampleBase + ((sint32)playbackNibbleOffset >> 1); + scale = (uint8)sampleInputData[0]; + delta = 1 << (scale & 0xF); + coefIndex = (scale >> 4) & 7; + coefA = (sint32)(sint16)_swapEndianU16(internalShadowCopy->adpcmData.coef[coefIndex * 2 + 0]); + coefB = (sint32)(sint16)_swapEndianU16(internalShadowCopy->adpcmData.coef[coefIndex * 2 + 1]); + playbackNibbleOffset += 2; + // decode samples + sampleInputData++; + for (sint32 i = 0; i < 7; i++) + { + // n0 + sint8 nibble = *sampleInputData; + sampleInputData++; + sint32 v; + // upper nibble + v = (sint32)(sint8)(nibble & 0xF0); + v <<= (11 - 4); + v *= delta; + v += (hist0 * coefA + hist1 * coefB); + v = (v + 0x400) >> 11; + + v = std::min(v, 32767); + v = std::max(v, -32768); + + hist1 = hist0; + hist0 = v; + *outputWriter = v; + outputWriter++; + // lower nibble + v = (sint32)(sint8)(nibble << 4); + v <<= (11 - 4); + v *= delta; + v += (hist0 * coefA + hist1 * coefB); + v = (v + 0x400) >> 11; + + v = std::min(v, 32767); + v = std::max(v, -32768); + + hist1 = hist0; + hist0 = v; + + *outputWriter = v; + outputWriter++; + } + playbackNibbleOffset += 7 * 2; + sampleCount -= 7 * 2; + } + } + // decode remaining samples + while (sampleCount) + { + if ((playbackNibbleOffset & 0xF) == 0) + { + scale = sampleBase[(sint32)playbackNibbleOffset >> 1]; + delta = 1 << (scale & 0xF); + coefIndex = (scale >> 4) & 7; + coefA = (sint32)(sint16)_swapEndianU16(internalShadowCopy->adpcmData.coef[coefIndex * 2 + 0]); + coefB = (sint32)(sint16)_swapEndianU16(internalShadowCopy->adpcmData.coef[coefIndex * 2 + 1]); + playbackNibbleOffset += 2; + } + sint8 nibble = (sint8)sampleBase[(sint32)playbackNibbleOffset >> 1]; + nibble <<= (4 * ((playbackNibbleOffset & 1))); + nibble &= 0xF0; + sint32 v = (sint32)nibble; + v <<= (11 - 4); + v *= delta; + v += (hist0 * coefA + hist1 * coefB); + v = (v + 0x400) >> 11; + + v = std::min(v, 32767); + v = std::max(v, -32768); + + hist1 = hist0; + hist0 = v; + *outputWriter = v; + outputWriter++; + + // check for loop / end offset + if (playbackNibbleOffset == vpbEndOffset) + { + handleAdpcmDecodeLoop(); + } + else + { + playbackNibbleOffset++; + } + sampleCount--; + } + + // write updated values + internalShadowCopy->adpcmData.scale = _swapEndianU16(scale); + internalShadowCopy->adpcmData.yn1 = (uint16)_swapEndianS16(hist0); + internalShadowCopy->adpcmData.yn2 = (uint16)_swapEndianS16(hist1); + + uint32 newInternalCurrentOffset = internalCurrentOffset + (playbackNibbleOffset - playbackNibbleOffsetStart); + *(uint32*)&internalShadowCopy->internalOffsets.currentOffsetPtrHigh = _swapEndianU32(newInternalCurrentOffset); + + } + + void AX_DecodeSamplesADPCM_NoSrc(AXVPBInternal_t* internalShadowCopy, float* output, sint32 sampleCount) + { + sint16 sampleBuffer[1024]; + cemu_assert(sampleCount <= (sizeof(sampleBuffer) / sizeof(sampleBuffer[0]))); + AX_readADPCMSamples(internalShadowCopy, sampleBuffer, sampleCount); + for (sint32 i = 0; i < sampleCount; i++) + { + // decode next sample + sint32 s = sampleBuffer[i]; + s <<= 8; + *output = (float)s; + output++; + } + } + + void AX_DecodeSamplesADPCM_Linear(AXVPBInternal_t* internalShadowCopy, float* output, sint32 sampleCount) + { + uint32 currentFracPos = (uint32)_swapEndianU16(internalShadowCopy->src.currentFrac); + uint32 ratio = _swapEndianU32(*(uint32*)&internalShadowCopy->src.ratioHigh); + + sint16 historySamples[4]; + historySamples[0] = _swapEndianS16(internalShadowCopy->src.historySamples[0]); + historySamples[1] = _swapEndianS16(internalShadowCopy->src.historySamples[1]); + historySamples[2] = _swapEndianS16(internalShadowCopy->src.historySamples[2]); + historySamples[3] = _swapEndianS16(internalShadowCopy->src.historySamples[3]); + + sint32 historyIndex = 0; + + sint32 numberOfDecodedAdpcmSamples = (sint32)((currentFracPos + ratio * sampleCount) >> 16); + sint16 adpcmSampleBuffer[4096]; + if (numberOfDecodedAdpcmSamples >= 4096) + { + memset(output, 0, sizeof(float)*sampleCount); + forceLog_printf("Too many ADPCM samples to decode. ratio = %08x", ratio); + return; + } + AX_readADPCMSamples(internalShadowCopy, adpcmSampleBuffer, numberOfDecodedAdpcmSamples); + + sint32 readSampleCount = 0; + + if (ratio == 0x10000 && currentFracPos == 0) + { + // optimized path when accessing only fully aligned samples + for (sint32 i = 0; i < sampleCount; i++) + { + cemu_assert_debug(readSampleCount < numberOfDecodedAdpcmSamples); + // get next sample + sint16 s = adpcmSampleBuffer[readSampleCount]; + readSampleCount++; + historyIndex = (historyIndex + 1) & 3; + historySamples[historyIndex] = s; + // use previous sample + sint32 previousSample = historySamples[(historyIndex + 3) & 3]; + sint32 p0 = previousSample << 8; + *output = (float)p0; + output++; + } + } + else + { + for (sint32 i = 0; i < sampleCount; i++) + { + // move playback pos + currentFracPos += ratio; + // get more samples if needed + while (currentFracPos >= 0x10000) + { + cemu_assert_debug(readSampleCount < numberOfDecodedAdpcmSamples); + sint16 s = adpcmSampleBuffer[readSampleCount]; + readSampleCount++; + currentFracPos -= 0x10000; + historyIndex = (historyIndex + 1) & 3; + historySamples[historyIndex] = s; + } + // linear interpolation of current sample + sint32 previousSample = historySamples[(historyIndex + 3) & 3]; + sint32 nextSample = historySamples[historyIndex]; + + sint32 p0 = (sint32)previousSample * (sint32)(0x10000 - currentFracPos); + sint32 p1 = (sint32)nextSample * (sint32)(currentFracPos); + + p0 >>= 7; + p1 >>= 7; + + sint32 interpolatedSample = p0 + p1; + interpolatedSample >>= 1; + + *output = (float)interpolatedSample; + output++; + } + } + cemu_assert_debug(readSampleCount == numberOfDecodedAdpcmSamples); + // set variables + internalShadowCopy->src.currentFrac = _swapEndianU16((uint16)(currentFracPos)); + internalShadowCopy->src.historySamples[0] = _swapEndianS16(historySamples[(historyIndex + 0) & 3]); + internalShadowCopy->src.historySamples[1] = _swapEndianS16(historySamples[(historyIndex + 1) & 3]); + internalShadowCopy->src.historySamples[2] = _swapEndianS16(historySamples[(historyIndex + 2) & 3]); + internalShadowCopy->src.historySamples[3] = _swapEndianS16(historySamples[(historyIndex + 3) & 3]); + } + + void AX_DecodeSamplesADPCM_Tap(AXVPBInternal_t* internalShadowCopy, float* output, sint32 sampleCount) + { + // todo - implement this + AX_DecodeSamplesADPCM_Linear(internalShadowCopy, output, sampleCount); + } + + void AX_DecodeSamplesPCM8_Linear(AXVPBInternal_t* internalShadowCopy, float* output, sint32 sampleCount) + { + // get variables + uint32 currentFracPos = (uint32)_swapEndianU16(internalShadowCopy->src.currentFrac); + uint32 ratio = _swapEndianU32(*(uint32*)&internalShadowCopy->src.ratioHigh); + + uint32 endOffsetPtr = _swapEndianU32(*(uint32*)&internalShadowCopy->internalOffsets.endOffsetPtrHigh); + uint32 currentOffsetPtr = _swapEndianU32(*(uint32*)&internalShadowCopy->internalOffsets.currentOffsetPtrHigh); + uint32 loopOffsetPtr = _swapEndianU32(*(uint32*)&internalShadowCopy->internalOffsets.loopOffsetPtrHigh); + uint32 ptrHighExtension = _swapEndianU16(internalShadowCopy->internalOffsets.ptrHighExtension); + + uint8* endOffsetAddr = memory_base + (endOffsetPtr | (ptrHighExtension << 29)); + uint8* currentOffsetAddr = memory_base + (currentOffsetPtr | (ptrHighExtension << 29)); + + sint16 historySamples[4]; + historySamples[0] = _swapEndianS16(internalShadowCopy->src.historySamples[0]); + historySamples[1] = _swapEndianS16(internalShadowCopy->src.historySamples[1]); + historySamples[2] = _swapEndianS16(internalShadowCopy->src.historySamples[2]); + historySamples[3] = _swapEndianS16(internalShadowCopy->src.historySamples[3]); + + sint32 historyIndex = 0; + + for (sint32 i = 0; i<sampleCount; i++) + { + currentFracPos += ratio; + while (currentFracPos >= 0x10000) + { + // read next sample + historyIndex = (historyIndex + 1) & 3; + if (internalShadowCopy->playbackState) + { + sint32 s = (sint32)(sint8)*currentOffsetAddr; + s <<= 8; + historySamples[historyIndex] = s; + if (currentOffsetAddr == endOffsetAddr) + { + if (internalShadowCopy->internalOffsets.loopFlag) + { + // loop + currentOffsetAddr = memory_base + (loopOffsetPtr | (ptrHighExtension << 29)); + } + else + { + // stop playing + internalShadowCopy->playbackState = 0; + } + } + else + { + currentOffsetAddr++; + } + } + else + { + // voice not playing, read sample as 0 + historySamples[historyIndex] = 0; + } + currentFracPos -= 0x10000; + } + // interpolate sample + sint32 previousSample = historySamples[(historyIndex + 3) & 3]; + sint32 nextSample = historySamples[historyIndex]; + sint32 p0 = (sint32)previousSample * (sint32)(0x10000 - currentFracPos); + sint32 p1 = (sint32)nextSample * (sint32)(currentFracPos); + p0 >>= 7; + p1 >>= 7; + sint32 interpolatedSample = p0 + p1; + interpolatedSample >>= 1; + *output = (float)interpolatedSample; + output++; + } + // set variables + internalShadowCopy->src.currentFrac = _swapEndianU16((uint16)(currentFracPos)); + internalShadowCopy->src.historySamples[0] = _swapEndianS16(historySamples[(historyIndex + 0) & 3]); + internalShadowCopy->src.historySamples[1] = _swapEndianS16(historySamples[(historyIndex + 1) & 3]); + internalShadowCopy->src.historySamples[2] = _swapEndianS16(historySamples[(historyIndex + 2) & 3]); + internalShadowCopy->src.historySamples[3] = _swapEndianS16(historySamples[(historyIndex + 3) & 3]); + // store current offset + currentOffsetPtr = (uint32)((uint8*)currentOffsetAddr - memory_base); + currentOffsetPtr &= 0x1FFFFFFF; // is this correct? + *(uint32*)&internalShadowCopy->internalOffsets.currentOffsetPtrHigh = _swapEndianU32(currentOffsetPtr); + } + + void AX_DecodeSamplesPCM8_Tap(AXVPBInternal_t* internalShadowCopy, float* output, sint32 sampleCount) + { + // todo - implement this + AX_DecodeSamplesPCM8_Linear(internalShadowCopy, output, sampleCount); + } + + void AX_DecodeSamplesPCM8_NoSrc(AXVPBInternal_t* internalShadowCopy, float* output, sint32 sampleCount) + { + // get variables + uint32 currentFracPos = (uint32)_swapEndianU16(internalShadowCopy->src.currentFrac); + uint32 ratio = _swapEndianU32(*(uint32*)&internalShadowCopy->src.ratioHigh); + + uint32 endOffsetPtr = _swapEndianU32(*(uint32*)&internalShadowCopy->internalOffsets.endOffsetPtrHigh); + uint32 currentOffsetPtr = _swapEndianU32(*(uint32*)&internalShadowCopy->internalOffsets.currentOffsetPtrHigh); + uint32 ptrHighExtension = _swapEndianU16(internalShadowCopy->internalOffsets.ptrHighExtension); + + uint8* endOffsetAddr = memory_base + (endOffsetPtr | (ptrHighExtension << 29)); + uint8* currentOffsetAddr = memory_base + (currentOffsetPtr | (ptrHighExtension << 29)); + + sint16 historySamples[4]; + historySamples[0] = _swapEndianS16(internalShadowCopy->src.historySamples[0]); + historySamples[1] = _swapEndianS16(internalShadowCopy->src.historySamples[1]); + historySamples[2] = _swapEndianS16(internalShadowCopy->src.historySamples[2]); + historySamples[3] = _swapEndianS16(internalShadowCopy->src.historySamples[3]); + + cemu_assert_debug(false); // todo + } + + void AX_DecodeSamplesPCM16_Linear(AXVPBInternal_t* internalShadowCopy, float* output, sint32 sampleCount) + { + uint32 currentFracPos = (uint32)_swapEndianU16(internalShadowCopy->src.currentFrac); + uint32 ratio = _swapEndianU32(*(uint32*)&internalShadowCopy->src.ratioHigh); + + uint32 endOffsetPtr = _swapEndianU32(*(uint32*)&internalShadowCopy->internalOffsets.endOffsetPtrHigh); + uint32 currentOffsetPtr = _swapEndianU32(*(uint32*)&internalShadowCopy->internalOffsets.currentOffsetPtrHigh); + uint32 loopOffsetPtr = _swapEndianU32(*(uint32*)&internalShadowCopy->internalOffsets.loopOffsetPtrHigh); + uint32 ptrHighExtension = _swapEndianU16(internalShadowCopy->internalOffsets.ptrHighExtension); + + uint16* endOffsetAddr = (uint16*)(memory_base + ((endOffsetPtr * 2) | (ptrHighExtension << 29))); + uint16* currentOffsetAddr = (uint16*)(memory_base + ((currentOffsetPtr * 2) | (ptrHighExtension << 29))); + + uint16* loopOffsetAddrDebug = (uint16*)(memory_base + ((loopOffsetPtr * 2) | (ptrHighExtension << 29))); + + sint16 historySamples[4]; + historySamples[0] = _swapEndianS16(internalShadowCopy->src.historySamples[0]); + historySamples[1] = _swapEndianS16(internalShadowCopy->src.historySamples[1]); + historySamples[2] = _swapEndianS16(internalShadowCopy->src.historySamples[2]); + historySamples[3] = _swapEndianS16(internalShadowCopy->src.historySamples[3]); + + sint32 historyIndex = 0; + + for (sint32 i = 0; i < sampleCount; i++) + { + currentFracPos += ratio; + while (currentFracPos >= 0x10000) + { + // read next sample + historyIndex = (historyIndex + 1) & 3; + if (internalShadowCopy->playbackState) + { + sint32 s = _swapEndianS16(*currentOffsetAddr); + historySamples[historyIndex] = s; + if (currentOffsetAddr == endOffsetAddr) + { + if (internalShadowCopy->internalOffsets.loopFlag) + { + // loop + currentOffsetAddr = (uint16*)(memory_base + ((loopOffsetPtr * 2) | (ptrHighExtension << 29))); + } + else + { + // stop playing + internalShadowCopy->playbackState = 0; + } + } + else + { + currentOffsetAddr++; // increment pointer only if not at end offset + } + } + else + { + // voice not playing -> sample is silent + historySamples[historyIndex] = 0; + } + currentFracPos -= 0x10000; + } + // interpolate sample + sint32 previousSample = historySamples[(historyIndex + 3) & 3]; + sint32 nextSample = historySamples[historyIndex]; + + sint32 p0 = (sint32)previousSample * (sint32)(0x10000 - currentFracPos); + sint32 p1 = (sint32)nextSample * (sint32)(currentFracPos); + p0 >>= 7; + p1 >>= 7; + sint32 interpolatedSample = p0 + p1; + interpolatedSample >>= 1; + + *output = (float)interpolatedSample; + output++; + } + + // set variables + internalShadowCopy->src.currentFrac = _swapEndianU16((uint16)(currentFracPos)); + internalShadowCopy->src.historySamples[0] = _swapEndianS16(historySamples[(historyIndex + 0) & 3]); + internalShadowCopy->src.historySamples[1] = _swapEndianS16(historySamples[(historyIndex + 1) & 3]); + internalShadowCopy->src.historySamples[2] = _swapEndianS16(historySamples[(historyIndex + 2) & 3]); + internalShadowCopy->src.historySamples[3] = _swapEndianS16(historySamples[(historyIndex + 3) & 3]); + // store current offset + currentOffsetPtr = (uint32)((uint8*)currentOffsetAddr - memory_base); + currentOffsetPtr &= 0x1FFFFFFF; + currentOffsetPtr >>= 1; + *(uint32*)&internalShadowCopy->internalOffsets.currentOffsetPtrHigh = _swapEndianU32(currentOffsetPtr); + } + + void AX_DecodeSamplesPCM16_Tap(AXVPBInternal_t* internalShadowCopy, float* output, sint32 sampleCount) + { + // todo - implement this + AX_DecodeSamplesPCM16_Linear(internalShadowCopy, output, sampleCount); + } + + void AX_DecodeSamplesPCM16_NoSrc(AXVPBInternal_t* internalShadowCopy, float* output, sint32 sampleCount) + { + // get variables + uint32 currentFracPos = (uint32)_swapEndianU16(internalShadowCopy->src.currentFrac); + uint32 ratio = _swapEndianU32(*(uint32*)&internalShadowCopy->src.ratioHigh); + + uint32 endOffsetPtr = _swapEndianU32(*(uint32*)&internalShadowCopy->internalOffsets.endOffsetPtrHigh); + uint32 currentOffsetPtr = _swapEndianU32(*(uint32*)&internalShadowCopy->internalOffsets.currentOffsetPtrHigh); + uint32 loopOffsetPtr = _swapEndianU32(*(uint32*)&internalShadowCopy->internalOffsets.loopOffsetPtrHigh); + uint32 ptrHighExtension = _swapEndianU16(internalShadowCopy->internalOffsets.ptrHighExtension); + + uint16* endOffsetAddr = (uint16*)(memory_base + (endOffsetPtr * 2 | (ptrHighExtension << 29))); + uint16* currentOffsetAddr = (uint16*)(memory_base + (currentOffsetPtr * 2 | (ptrHighExtension << 29))); + + if (internalShadowCopy->playbackState == 0) + { + memset(output, 0, sizeof(float)*sampleCount); + return; + } + + for (sint32 i = 0; i < sampleCount; i++) + { + sint32 s = _swapEndianS16(*currentOffsetAddr); + s <<= 8; + output[i] = (float)s; + if (currentOffsetAddr == endOffsetAddr) + { + if (internalShadowCopy->internalOffsets.loopFlag) + { + currentOffsetAddr = (uint16*)(memory_base + (loopOffsetPtr * 2 | (ptrHighExtension << 29))); + } + else + { + internalShadowCopy->playbackState = 0; + for (; i < sampleCount; i++) + { + output[i] = 0.0f; + } + break; + } + } + else + currentOffsetAddr++; + } + // store current offset + currentOffsetPtr = (uint32)((uint8*)currentOffsetAddr - memory_base); + currentOffsetPtr &= 0x1FFFFFFF; + currentOffsetPtr >>= 1; + + *(uint32*)&internalShadowCopy->internalOffsets.currentOffsetPtrHigh = _swapEndianU32(currentOffsetPtr); + } + + void AXVoiceMix_DecodeSamples(AXVPBInternal_t* internalShadowCopy, float* output, sint32 sampleCount) + { + uint32 srcFilterMode = _swapEndianU16(internalShadowCopy->srcFilterMode); + uint16 format = _swapEndianU16(internalShadowCopy->internalOffsets.format); + + if (srcFilterMode == AX_FILTER_MODE_LINEAR || srcFilterMode == AX_FILTER_MODE_TAP) + { + if (format == AX_FORMAT_ADPCM) + AX_DecodeSamplesADPCM_Tap(internalShadowCopy, output, sampleCount); + else if (format == AX_FORMAT_PCM16) + AX_DecodeSamplesPCM16_Tap(internalShadowCopy, output, sampleCount); + else if (format == AX_FORMAT_PCM8) + AX_DecodeSamplesPCM8_Tap(internalShadowCopy, output, sampleCount); + else + cemu_assert_debug(false); + } + else if (srcFilterMode == AX_FILTER_MODE_LINEAR) + { + if (format == AX_FORMAT_ADPCM) + AX_DecodeSamplesADPCM_Linear(internalShadowCopy, output, sampleCount); + else if (format == AX_FORMAT_PCM16) + AX_DecodeSamplesPCM16_Linear(internalShadowCopy, output, sampleCount); + else if (format == AX_FORMAT_PCM8) + AX_DecodeSamplesPCM8_Linear(internalShadowCopy, output, sampleCount); + else + cemu_assert_debug(false); + } + else if (srcFilterMode == AX_FILTER_MODE_NONE) + { + if (format == AX_FORMAT_ADPCM) + AX_DecodeSamplesADPCM_NoSrc(internalShadowCopy, output, sampleCount); + else if (format == AX_FORMAT_PCM16) + AX_DecodeSamplesPCM16_NoSrc(internalShadowCopy, output, sampleCount); + else if (format == AX_FORMAT_PCM8) + AX_DecodeSamplesPCM8_NoSrc(internalShadowCopy, output, sampleCount); + else + cemu_assert_debug(false); + } + } + + sint32 AXVoiceMix_MergeInto(float* inputSamples, float* outputSamples, sint32 sampleCount, AXCHMIX_DEPR* mix, sint16 deltaI) + { + float vol = (float)_swapEndianU16(mix->vol) / (float)0x8000; + if (deltaI != 0) + { + float delta = (float)deltaI / (float)0x8000; + for (sint32 i = 0; i < sampleCount; i++) + { + vol += delta; + outputSamples[i] += inputSamples[i] * vol; + } + } + else + { + // optimized version for delta == 0.0 + for (sint32 i = 0; i < sampleCount; i++) + { + outputSamples[i] += inputSamples[i] * vol; + } + } + uint16 volI = (uint16)(vol * 32768.0f); + mix->vol = _swapEndianU16(volI); + return volI; + } + + float __AXMixBufferTV[AX_SAMPLES_MAX * AX_TV_CHANNEL_COUNT * AX_BUS_COUNT]; + float __AXMixBufferDRC[2 * AX_SAMPLES_MAX * AX_DRC_CHANNEL_COUNT * AX_BUS_COUNT]; + + void AXVoiceMix_ApplyADSR(AXVPBInternal_t* internalShadowCopy, float* sampleData, sint32 sampleCount) + { + uint16 volume = internalShadowCopy->veVolume; + sint16 volumeDelta = (sint16)internalShadowCopy->veDelta; + if (volume == 0x8000 && volumeDelta == 0) + return; + float volumeScaler = (float)volume / 32768.0f; + if (volumeDelta == 0) + { + // without delta + for (sint32 i = 0; i < sampleCount; i++) + sampleData[i] *= volumeScaler; + return; + } + // with delta + double volumeScalerDelta = (double)volumeDelta / 32768.0; + volumeScalerDelta = volumeScalerDelta + volumeScalerDelta; + for (sint32 i = 0; i < sampleCount; i++) + { + volumeScaler += (float)volumeScalerDelta; + sampleData[i] *= volumeScaler; + } + if (volumeDelta != 0) + { + volume = (uint16)(volumeScaler * 32768.0); + internalShadowCopy->veVolume = volume; + } + } + + void AXVoiceMix_ApplyBiquad(AXVPBInternal_t* internalShadowCopy, float* sampleData, sint32 sampleCount) + { + if (internalShadowCopy->biquad.on == AX_BIQUAD_OFF) + return; +#ifndef PUBLIC_RELEASE + if (internalShadowCopy->biquad.on != 0x0200) + { + forceLogDebug_printf("AX_ApplyBiquad() with incorrect biquad.on value 0x%04x", _swapEndianU16(internalShadowCopy->biquad.on)); + } +#endif + + float a1 = (float)(sint16)_swapEndianS16(internalShadowCopy->biquad.a1) / 16384.0f; + float a2 = (float)(sint16)_swapEndianS16(internalShadowCopy->biquad.a2) / 16384.0f; + float b0 = (float)(sint16)_swapEndianS16(internalShadowCopy->biquad.b0) / 16384.0f; + float b1 = (float)(sint16)_swapEndianS16(internalShadowCopy->biquad.b1) / 16384.0f; + float b2 = (float)(sint16)_swapEndianS16(internalShadowCopy->biquad.b2) / 16384.0f; + + float yn1 = (float)_swapEndianS16(internalShadowCopy->biquad.yn1); + float yn2 = (float)_swapEndianS16(internalShadowCopy->biquad.yn2); + float xn1 = (float)_swapEndianS16(internalShadowCopy->biquad.xn1); + float xn2 = (float)_swapEndianS16(internalShadowCopy->biquad.xn2); + if (internalShadowCopy->biquad.b1 != 0) + { + for (sint32 i = 0; i < sampleCount; i++) + { + float inputSample = sampleData[i] / 256.0f; + float temp = b0 * inputSample + b1 * xn1 + b2 * xn2 + a1 * yn1 + a2 * yn2; + sampleData[i] = temp * 256.0f; + temp = std::min(32767.0f, temp); + temp = std::max(-32768.0f, temp); + yn2 = yn1; + xn2 = xn1; + yn1 = temp; + xn1 = inputSample; + } + } + else + { + // optimized variant where voiceInternal->biquad.b1 is hardcoded as zero (used heavily in BotW and Splatoon) + for (sint32 i = 0; i < sampleCount; i++) + { + float inputSample = sampleData[i] / 256.0f; + float temp = b0 * inputSample + b2 * xn2 + a1 * yn1 + a2 * yn2; + sampleData[i] = temp * 256.0f; + temp = std::min(32767.0f, temp); + temp = std::max(-32768.0f, temp); + yn2 = yn1; + xn2 = xn1; + yn1 = temp; + xn1 = inputSample; + } + } + + internalShadowCopy->biquad.yn1 = _swapEndianU16((sint16)(yn1)); + internalShadowCopy->biquad.yn2 = _swapEndianU16((sint16)(yn2)); + internalShadowCopy->biquad.xn1 = _swapEndianU16((sint16)(xn1)); + internalShadowCopy->biquad.xn2 = _swapEndianU16((sint16)(xn2)); + } + + void AXVoiceMix_ApplyLowPass(AXVPBInternal_t* internalShadowCopy, float* sampleData, sint32 sampleCount) + { + if (internalShadowCopy->lpf.on == _swapEndianU16(AX_LPF_OFF)) + return; + float a0 = (float)_swapEndianS16(internalShadowCopy->lpf.a0) / 32767.0f; + float b0 = (float)_swapEndianS16(internalShadowCopy->lpf.b0) / 32767.0f; + float prevSample = (float)_swapEndianS16((sint16)internalShadowCopy->lpf.yn1) * 256.0f / 32767.0f; + for (sint32 i = 0; i < sampleCount; i++) + { + sampleData[i] = a0 * sampleData[i] - b0 * prevSample; + prevSample = sampleData[i]; + } + internalShadowCopy->lpf.yn1 = (uint16)_swapEndianS16((sint16)(prevSample / 256.0f * 32767.0f)); + } + + // mix audio generated from voice into main bus and aux buses + void AXVoiceMix_MixIntoBuses(AXVPBInternal_t* internalShadowCopy, float* sampleData, sint32 sampleCount, sint32 samplesPerFrame) + { + // TV mixing + for (sint32 busIndex = 0; busIndex < AX_BUS_COUNT; busIndex++) + { + for (sint32 channel = 0; channel < 6; channel++) + { + uint32 channelMixMask = (_swapEndianU16(internalShadowCopy->deviceMixMaskTV[busIndex]) >> (channel * 2)) & 3; + if (channelMixMask == 0) + { + internalShadowCopy->reserved1E8[busIndex*AX_TV_CHANNEL_COUNT + channel] = 0; + continue; + } + AXCHMIX_DEPR* mix = internalShadowCopy->deviceMixTV + channel * 4 + busIndex; + float* output = __AXMixBufferTV + (busIndex * 6 + channel) * samplesPerFrame; + AXVoiceMix_MergeInto(sampleData, output, sampleCount, mix, _swapEndianS16(mix->delta)); + internalShadowCopy->reserved1E8[busIndex*AX_TV_CHANNEL_COUNT + channel] = mix->vol; + } + } + // DRC0 mixing + for (sint32 busIndex = 0; busIndex < AX_BUS_COUNT; busIndex++) + { + for (sint32 channel = 0; channel < AX_DRC_CHANNEL_COUNT; channel++) + { + uint32 channelMixMask = (_swapEndianU16(internalShadowCopy->deviceMixMaskDRC[busIndex]) >> (channel * 2)) & 3; + + if (channelMixMask == 0) + { + //internalShadowCopy->reserved1E8[busIndex*AX_DRC_CHANNEL_COUNT + channel] = 0; + continue; + } + AXCHMIX_DEPR* mix = internalShadowCopy->deviceMixDRC + channel * 4 + busIndex; + float* output = __AXMixBufferDRC + (busIndex * AX_DRC_CHANNEL_COUNT + channel) * samplesPerFrame; + AXVoiceMix_MergeInto(sampleData, output, sampleCount, mix, _swapEndianS16(mix->delta)); + } + } + + // DRC1 mixing + RMT mixing + // todo + } + + void AXMix_ProcessVoices(AXVPBInternal_t* firstVoice) + { + if (firstVoice == nullptr) + return; + size_t sampleCount = AXGetInputSamplesPerFrame(); + AXVPBInternal_t* internalVoice = firstVoice; + cemu_assert_debug(sndGeneric.initParam.frameLength == 0); + float tmpSampleBuffer[AX_SAMPLES_MAX]; + while (internalVoice) + { + AXVoiceMix_DecodeSamples(internalVoice, tmpSampleBuffer, sampleCount); + AXVoiceMix_ApplyADSR(internalVoice, tmpSampleBuffer, sampleCount); + AXVoiceMix_ApplyBiquad(internalVoice, tmpSampleBuffer, sampleCount); + AXVoiceMix_ApplyLowPass(internalVoice, tmpSampleBuffer, sampleCount); + AXVoiceMix_MixIntoBuses(internalVoice, tmpSampleBuffer, sampleCount, sampleCount); + // next + internalVoice = internalVoice->nextToProcess.GetPtr(); + } + } + + void AXMix_MergeBusSamples(float* input, sint32* output, sint32 sampleCount, uint16& volume, sint16 delta) + { + float volumeF = (float)volume / 32768.0f; + float deltaF = (float)delta / 32768.0f; + + if (delta) + { + for (sint32 i = 0; i < sampleCount; i++) + { + float s = *input; + input++; + s *= volumeF; + volumeF += deltaF; + *output = _swapEndianS32(_swapEndianS32(*output) + (sint32)s); + output++; + } + volume = (uint16)(volumeF * 32768.0f); + } + else + { + // no delta + for (sint32 i = 0; i < sampleCount; i++) + { + float s = *input; + input++; + s *= volumeF; + *output = _swapEndianS32(_swapEndianS32(*output) + (sint32)s); + output++; + } + } + } + + void AXAuxMix_StoreAuxSamples(float* input, sint32be* output, sint32 sampleCount) + { + // Not 100% sure why but we need to temporarily right shift the aux samples by 8 to get the sample range the games expect for the AUX callback + // without this, Color Splash will apply it's effects incorrectly + // Its probably because AUX mixing always goes through the DSP which uses 16bit arithmetic? + + // no delta + for (sint32 i = 0; i < sampleCount; i++) + { + float s = *input; + input++; + *output = ((sint32)s) >> 8; + output++; + } + } + + void AXAuxMix_MixProcessedAuxSamplesIntoOutput(sint32be* input, float* output, sint32 sampleCount, uint16* volumePtr, sint16 delta) + { + uint16 volume = *volumePtr; + float volumeF = (float)volume / 32768.0f; + float deltaF = (float)delta / 32768.0f; + + cemu_assert_debug(delta == 0); // todo + + for (sint32 i = 0; i < sampleCount; i++) + { + float s = (float)(((sint32)*input)<<8); + input++; + s *= volumeF; + *output += s; + output++; + } + *volumePtr = volume; + } + + uint16 __AXMasterVolume = 0x8000; + uint16 __AXDRCMasterVolume = 0x8000; + + // mix into __AXTVOutputBuffer + void AXMix_mergeTVBuses() + { + size_t sampleCount = AXGetInputSamplesPerFrame(); + + // debug - Erase main bus and only output AUX + if (ActiveSettings::AudioOutputOnlyAux()) + { + memset(__AXMixBufferTV, 0, sizeof(float) * sampleCount * 6); + } + // Mix aux into TV main bus + for (sint32 auxBus = 0; auxBus < AX_AUX_BUS_COUNT; auxBus++) + { + sint32be* auxOutput = AXAux_GetOutputBuffer(AX_DEV_TV, 0, auxBus); + if (auxOutput == nullptr) + continue; + // AUX return from output buffer + uint16 auxReturnVolume = __AXTVAuxReturnVolume[auxBus]; + sint16 auxReturnDelta = 0; + AXAuxMix_MixProcessedAuxSamplesIntoOutput(auxOutput, __AXMixBufferTV, sampleCount * AX_TV_CHANNEL_COUNT, &auxReturnVolume, auxReturnDelta); + } + // mix TV main bus into output + float* input = __AXMixBufferTV; + uint16 masterVolume = __AXMasterVolume; + sint32* output = __AXTVOutputBuffer.GetPtr(); + cemu_assert_debug(masterVolume == 0x8000); // todo -> Calculate delta between old master volume and new volume + sint16 delta = 0; + uint16 volVar; + for (uint16 c = 0; c < AX_TV_CHANNEL_COUNT; c++) + { + volVar = _swapEndianU16(masterVolume); + AXMix_MergeBusSamples(input, output, sampleCount, masterVolume, delta); + output += sampleCount; + input += sampleCount; + } + } + + // mix into __AXDRCOutputBuffer + void AXMix_mergeDRC0Buses() + { + sint32* output = __AXDRCOutputBuffer.GetPtr(); + uint16 masterVolume = __AXDRCMasterVolume; + size_t sampleCount = AXGetInputSamplesPerFrame(); + + // todo - drc0 AUX + + // mix DRC0 main bus into output + float* input = __AXMixBufferDRC; + cemu_assert_debug(masterVolume == 0x8000); // todo -> Calculate delta between old master volume and new volume + sint16 delta = 0; + for (uint16 c = 0; c < AX_DRC_CHANNEL_COUNT; c++) + { + AXMix_MergeBusSamples(input, output, sampleCount, masterVolume, delta); + output += sampleCount; + input += sampleCount; + } + } + + void AXMix_process(AXVPBInternal_t* internalShadowCopyHead) + { + memset(__AXMixBufferTV, 0, sizeof(__AXMixBufferTV)); + memset(__AXMixBufferDRC, 0, sizeof(__AXMixBufferDRC)); + + AXMix_ProcessVoices(internalShadowCopyHead); + AXAux_Process(); // apply AUX effects to previous frame + AXIst_HandleFrameCallbacks(); + + size_t sampleCount = AXGetInputSamplesPerFrame(); + // TV aux store + for (sint32 auxBus = 0; auxBus < AX_AUX_BUS_COUNT; auxBus++) + { + sint32be* auxInput = AXAux_GetInputBuffer(AX_DEV_TV, 0, auxBus); + if (auxInput == nullptr) + continue; + float* tvInput = __AXMixBufferTV + (1 + auxBus) * (sampleCount * AX_TV_CHANNEL_COUNT); + AXAuxMix_StoreAuxSamples(tvInput, auxInput, sampleCount * AX_TV_CHANNEL_COUNT); + } + + // DRC aux store + // todo + + // merge main and aux buses + AXMix_mergeTVBuses(); + AXMix_mergeDRC0Buses(); + + AXAux_incrementBufferIndex(); + // update microphone + mic_updateOnAXFrame(); + } + +} diff --git a/src/Cafe/OS/libs/snd_core/ax_multivoice.cpp b/src/Cafe/OS/libs/snd_core/ax_multivoice.cpp new file mode 100644 index 00000000..0b2b2550 --- /dev/null +++ b/src/Cafe/OS/libs/snd_core/ax_multivoice.cpp @@ -0,0 +1,167 @@ +#include "Cafe/OS/libs/snd_core/ax.h" +#include "Cafe/OS/libs/snd_core/ax_internal.h" +#include "Cafe/HW/MMU/MMU.h" + +namespace snd_core +{ + static_assert(sizeof(AXVPBMULTI) == 0x20, ""); + + SysAllocator<AXVPBMULTI, AX_MAX_VOICES> _buffer__AXVPBMultiVoiceArray; + AXVPBMULTI* __AXVPBMultiVoiceArray; + + void AXMultiVoice_Init() + { + __AXVPBMultiVoiceArray = _buffer__AXVPBMultiVoiceArray.GetPtr(); + for (sint32 i = 0; i < AX_MAX_VOICES; i++) + { + __AXVPBMultiVoiceArray[i].isUsed = 0; + } + } + + sint32 AXAcquireMultiVoice(sint32 voicePriority, void* cbFunc, void* cbData, AXMULTIVOICEUKNSTRUCT* uknR6, MEMPTR<AXVPBMULTI>* multiVoiceOut) + { + for (sint32 i = 0; i < AX_MAX_VOICES; i++) + { + if (__AXVPBMultiVoiceArray[i].isUsed == (uint32)0) + { + sint16 channelCount = uknR6->channelCount; + if (channelCount <= 0 || channelCount > 6) + { + return -0x15; + } + for (sint32 f = 0; f < channelCount; f++) + { + __AXVPBMultiVoiceArray[i].voice[f] = nullptr; + } + __AXVPBMultiVoiceArray[i].isUsed = 1; + for (sint32 f = 0; f < channelCount; f++) + { + AXVPB* vpb = AXAcquireVoiceEx(voicePriority, memory_getVirtualOffsetFromPointer(cbFunc), memory_getVirtualOffsetFromPointer(cbData)); + if (vpb == nullptr) + { + AXFreeMultiVoice(__AXVPBMultiVoiceArray + i); + return -0x16; + } + __AXVPBMultiVoiceArray[i].voice[f] = vpb; + } + __AXVPBMultiVoiceArray[i].channelCount = channelCount; + *multiVoiceOut = (__AXVPBMultiVoiceArray+i); + return 0; + } + } + return -0x14; + } + + void AXFreeMultiVoice(AXVPBMULTI* multiVoice) + { + cemu_assert_debug(multiVoice->isUsed != (uint32)0); + uint16 numChannels = multiVoice->channelCount; + for (uint16 i = 0; i < numChannels; i++) + { + if(multiVoice->voice[i] != nullptr) + AXFreeVoice(multiVoice->voice[i].GetPtr()); + multiVoice->voice[i] = nullptr; + } + multiVoice->isUsed = 0; + } + + sint32 AXGetMultiVoiceReformatBufferSize(sint32 voiceFormat, uint32 channelCount, uint32 sizeInBytes, uint32be* sizeOutput) + { + // used by Axiom Verge + if (voiceFormat == AX_FORMAT_ADPCM) + { + sint32 alignedSize = (sizeInBytes + 7) & ~7; + *sizeOutput = alignedSize * channelCount; + } + else if (voiceFormat == AX_FORMAT_PCM16) + { + *sizeOutput = sizeInBytes; + } + else if (voiceFormat == AX_FORMAT_PCM8) + { + *sizeOutput = sizeInBytes<<1; + } + else + return -23; + return 0; + } + + void AXSetMultiVoiceType(AXVPBMULTI* mv, uint16 type) + { + for(uint32 i = 0; i < mv->channelCount; ++i) + AXSetVoiceType(mv->voice[i].GetPtr(), type); + } + + void AXSetMultiVoiceAdpcm(AXVPBMULTI* mv, AXDSPADPCM* adpcm) + { + static_assert(sizeof(AXDSPADPCM) == 0x60); + for (uint32 i = 0; i < mv->channelCount; ++i) + { + AXPBADPCM_t tmp; + tmp.gain = adpcm[i].gain.bevalue(); + tmp.yn1 = adpcm[i].yn1.bevalue(); + tmp.yn2 = adpcm[i].yn2.bevalue(); + tmp.scale = adpcm[i].scale.bevalue(); + + static_assert(sizeof(tmp.a) == sizeof(adpcm->coef)); + memcpy(tmp.a, adpcm[i].coef, sizeof(tmp.a)); + + AXSetVoiceAdpcm(mv->voice[i].GetPtr(), &tmp); + } + } + + void AXSetMultiVoiceSrcType(AXVPBMULTI* mv, uint32 type) + { + for (uint32 i = 0; i < mv->channelCount; ++i) + AXSetVoiceSrcType(mv->voice[i].GetPtr(), type); + } + + void AXSetMultiVoiceOffsets(AXVPBMULTI* mv, AXPBOFFSET_t* offsets) + { + for (uint32 i = 0; i < mv->channelCount; ++i) + AXSetVoiceOffsets(mv->voice[i].GetPtr(), offsets + i); + } + + void AXSetMultiVoiceVe(AXVPBMULTI* mv, AXPBVE* ve) + { + for (uint32 i = 0; i < mv->channelCount; ++i) + AXSetVoiceVe(mv->voice[i].GetPtr(), ve); + } + + void AXSetMultiVoiceSrcRatio(AXVPBMULTI* mv, float ratio) + { + for (uint32 i = 0; i < mv->channelCount; ++i) + AXSetVoiceSrcRatio(mv->voice[i].GetPtr(), ratio); + } + + void AXSetMultiVoiceSrc(AXVPBMULTI* mv, AXPBSRC_t* src) + { + for (uint32 i = 0; i < mv->channelCount; ++i) + AXSetVoiceSrc(mv->voice[i].GetPtr(), src); + } + + void AXSetMultiVoiceLoop(AXVPBMULTI* mv, uint16 loop) + { + for (uint32 i = 0; i < mv->channelCount; ++i) + AXSetVoiceLoop(mv->voice[i].GetPtr(), loop); + } + + void AXSetMultiVoiceState(AXVPBMULTI* mv, uint16 state) + { + for (uint32 i = 0; i < mv->channelCount; ++i) + AXSetVoiceState(mv->voice[i].GetPtr(), state); + } + + void AXSetMultiVoiceAdpcmLoop(AXVPBMULTI* mv, AXPBADPCMLOOP_t* loops) + { + for (uint32 i = 0; i < mv->channelCount; ++i) + AXSetVoiceAdpcmLoop(mv->voice[i].GetPtr(), loops + i); + } + + sint32 AXIsMultiVoiceRunning(AXVPBMULTI* mv) + { + const sint32 result = AXIsVoiceRunning(mv->voice[0].GetPtr()); + return result; + } + +} diff --git a/src/Cafe/OS/libs/snd_core/ax_out.cpp b/src/Cafe/OS/libs/snd_core/ax_out.cpp new file mode 100644 index 00000000..85a94e52 --- /dev/null +++ b/src/Cafe/OS/libs/snd_core/ax_out.cpp @@ -0,0 +1,577 @@ +#include "Cafe/OS/libs/snd_core/ax.h" +#include "Cafe/OS/libs/snd_core/ax_internal.h" +#include "Cafe/HW/MMU/MMU.h" +#include "audio/IAudioAPI.h" +//#include "ax.h" +#include "config/CemuConfig.h" + +namespace snd_core +{ + uint32 numProcessedFrames = 0; + + void resetNumProcessedFrames() + { + numProcessedFrames = 0; + } + + uint32 getNumProcessedFrames() + { + return numProcessedFrames; + } + + sint32 __AXMode[AX_DEV_COUNT]; // audio mode (AX_MODE_*) per device + + + bool AVMGetTVAudioMode(uint32be* tvAudioMode) + { + // 0 -> mono + // 1,2 -> stereo + // 3 -> surround + // 4 -> unknown mode + switch (GetConfig().tv_channels) + { + case kMono: + *tvAudioMode = 0; + break; + case kSurround: + *tvAudioMode = 3; + break; + default: + *tvAudioMode = 2; + break; + } + + return true; + } + + bool AVMGetDRCSystemAudioMode(uint32be* drcAudioMode) + { + *drcAudioMode = 1; // apparently the default is Stereo(?), MH3U exits if AXGetDeviceMode doesn't return 0 (DRCSystemAudioMode must return 1 to set DRC mode to 0) + return true; + } + + sint32 __AXOutTVOutputChannelCount; + sint32 __AXOutDRCOutputChannelCount; + + void __AXSetTVMode(sint32 mode) + { + cemu_assert(mode == AX_MODE_STEREO || mode == AX_MODE_6CH || mode == AX_MODE_MONO); + __AXMode[AX_DEV_TV] = mode; + } + + void __AXSetDeviceMode(sint32 device, sint32 mode) + { + if (device == AX_DEV_TV) + __AXMode[AX_DEV_TV] = mode; + else if (device == AX_DEV_DRC) + __AXMode[AX_DEV_DRC] = mode; + else if (device == AX_DEV_RMT) + __AXMode[AX_DEV_RMT] = mode; + else + { + cemu_assert_debug(false); + } + } + + sint32 AXGetDeviceMode(sint32 device) + { + if (device == AX_DEV_TV || device == AX_DEV_DRC || device == AX_DEV_RMT) + return __AXMode[device]; + cemu_assert_debug(false); + return 0; + } + + void _AXOutInitDeviceModes() + { + // TV mode + uint32be tvAudioMode; + AVMGetTVAudioMode(&tvAudioMode); + if (tvAudioMode == 0) + { + // mono + __AXSetTVMode(AX_MODE_MONO); + __AXOutTVOutputChannelCount = 1; + } + else if (tvAudioMode == 1 || tvAudioMode == 2) + { + // stereo + __AXSetTVMode(AX_MODE_STEREO); + __AXOutTVOutputChannelCount = 2; + } + else if (tvAudioMode == 3) + { + // surround (6ch) + __AXSetTVMode(AX_MODE_6CH); + __AXOutTVOutputChannelCount = 6; + } + else + { + assert_dbg(); + } + // DRC mode + uint32be drcAudioMode; + AVMGetDRCSystemAudioMode(&drcAudioMode); + if (drcAudioMode == 0) + { + // mono + __AXSetDeviceMode(1, AX_MODE_MONO); + __AXOutDRCOutputChannelCount = 1; + } + else if (drcAudioMode == 2) + { + // surround + __AXSetDeviceMode(1, AX_MODE_SURROUND); + __AXOutDRCOutputChannelCount = 2; // output channel count still 2 for DRC 'surround' + } + else if (drcAudioMode == 1) + { + // stereo + __AXSetDeviceMode(1, AX_MODE_STEREO); + __AXOutDRCOutputChannelCount = 2; + } + else + { + assert_dbg(); + } + } + + void AXOut_Init() + { + _AXOutInitDeviceModes(); + } + + extern SysAllocator<sint32, AX_SAMPLES_MAX * AX_TV_CHANNEL_COUNT> __AXTVBuffer48; + extern SysAllocator<sint32, AX_SAMPLES_MAX* AX_DRC_CHANNEL_COUNT * 2> __AXDRCBuffer48; + + sint16 __buf_AXTVDMABuffers_0[AX_SAMPLES_MAX * AX_TV_CHANNEL_COUNT]; + sint16 __buf_AXTVDMABuffers_1[AX_SAMPLES_MAX * AX_TV_CHANNEL_COUNT]; + sint16 __buf_AXTVDMABuffers_2[AX_SAMPLES_MAX * AX_TV_CHANNEL_COUNT]; + sint16* __AXTVDMABuffers[3] = {__buf_AXTVDMABuffers_0, __buf_AXTVDMABuffers_1, __buf_AXTVDMABuffers_2}; + + #define AX_FRAMES_PER_GROUP (4) + + sint16 tempTVChannelData[AX_SAMPLES_MAX * AX_TV_CHANNEL_COUNT * AX_FRAMES_PER_GROUP] = {}; + sint32 tempAudioBlockCounter = 0; + + + sint16 __buf_AXDRCDMABuffers_0[AX_SAMPLES_MAX * 6]; + sint16 __buf_AXDRCDMABuffers_1[AX_SAMPLES_MAX * 6]; + sint16 __buf_AXDRCDMABuffers_2[AX_SAMPLES_MAX * 6]; + sint16* __AXDRCDMABuffers[3] = { __buf_AXDRCDMABuffers_0, __buf_AXDRCDMABuffers_1, __buf_AXDRCDMABuffers_2 }; + + sint16 tempDRCChannelData[AX_SAMPLES_MAX * 6 * AX_FRAMES_PER_GROUP] = {}; + sint32 tempDRCAudioBlockCounter = 0; + + void AIInitDMA(sint16* sampleData, sint32 size) + { + sint32 sampleCount = size / sizeof(sint16); // sample count in total (summed up for all channels) + + if (sndGeneric.initParam.frameLength != 0) + { + cemu_assert(false); + } + + std::shared_lock lock(g_audioMutex); + + const uint32 channels = g_tvAudio ? g_tvAudio->GetChannels() : AX_TV_CHANNEL_COUNT; + sint16* outputChannel = tempTVChannelData + AX_SAMPLES_PER_3MS_48KHZ * tempAudioBlockCounter * channels; + for (sint32 i = 0; i < sampleCount; ++i) + { + outputChannel[i] = _swapEndianS16(sampleData[i]); + } + + tempAudioBlockCounter++; + if (tempAudioBlockCounter == AX_FRAMES_PER_GROUP) + { + if(g_tvAudio) + g_tvAudio->FeedBlock(tempTVChannelData); + + tempAudioBlockCounter = 0; + } + } + + sint32 AIGetSamplesPerChannel(uint32 device) + { + // TV and DRC output the same number of samples + return AX_SAMPLES_PER_3MS_48KHZ; + } + + sint32 AIGetChannelCount(uint32 device) + { + if (__AXMode[device] == AX_MODE_6CH) + return 6; + if (__AXMode[device] == AX_MODE_STEREO) + return 2; + // default to mono + return 1; + } + + sint16* AIGetCurrentDMABuffer(uint32 device) + { + if (device == AX_DEV_TV) + return __AXTVDMABuffers[0]; + else if (device == AX_DEV_DRC) + return __AXDRCDMABuffers[0]; + cemu_assert_debug(false); + return nullptr; + } + + void AXOut_SubmitTVFrame(sint32 frameIndex) + { + sint32 numSamples = AIGetSamplesPerChannel(AX_DEV_TV); + if (__AXMode[AX_DEV_TV] == AX_MODE_6CH) + { + sint32* inputChannel0 = __AXTVBuffer48.GetPtr() + numSamples * 0; + sint32* inputChannel1 = __AXTVBuffer48.GetPtr() + numSamples * 1; + sint32* inputChannel2 = __AXTVBuffer48.GetPtr() + numSamples * 2; + sint32* inputChannel3 = __AXTVBuffer48.GetPtr() + numSamples * 3; + sint32* inputChannel4 = __AXTVBuffer48.GetPtr() + numSamples * 4; + sint32* inputChannel5 = __AXTVBuffer48.GetPtr() + numSamples * 5; + sint16* dmaOutputBuffer = AIGetCurrentDMABuffer(AX_DEV_TV); + for (sint32 i = 0; i < numSamples; i++) + { + /* + * DirectSound surround order + LEFT 0 + RIGHT 1 + SUR_LEFT 2 + SUR_RIGHT 3 + CH_FC 4 + CH_LFE 5 + => + Front Left - FL 0 + Front Right - FR 1 + Front Center - FC 2 + Low Frequency - LF 3 + Back Left - BL 4 + Back Right - BR 5 + */ + dmaOutputBuffer[0] = _swapEndianS16((sint16)std::min(std::max(_swapEndianS32(*inputChannel0), -32768), 32767)); + dmaOutputBuffer[1] = _swapEndianS16((sint16)std::min(std::max(_swapEndianS32(*inputChannel1), -32768), 32767)); + + dmaOutputBuffer[4] = _swapEndianS16((sint16)std::min(std::max(_swapEndianS32(*inputChannel2), -32768), 32767)); + dmaOutputBuffer[5] = _swapEndianS16((sint16)std::min(std::max(_swapEndianS32(*inputChannel3), -32768), 32767)); + + dmaOutputBuffer[2] = _swapEndianS16((sint16)std::min(std::max(_swapEndianS32(*inputChannel4), -32768), 32767)); + dmaOutputBuffer[3] = _swapEndianS16((sint16)std::min(std::max(_swapEndianS32(*inputChannel5), -32768), 32767)); + dmaOutputBuffer += 6; + // next sample + inputChannel0++; + inputChannel1++; + inputChannel2++; + inputChannel3++; + inputChannel4++; + inputChannel5++; + } + AIInitDMA(__AXTVDMABuffers[frameIndex], numSamples * 6 * sizeof(sint16)); // 6ch output + } + else if (__AXMode[AX_DEV_TV] == AX_MODE_STEREO) + { + sint32* inputChannel0 = __AXTVBuffer48.GetPtr() + numSamples * 0; + sint32* inputChannel1 = __AXTVBuffer48.GetPtr() + numSamples * 1; + sint16* dmaOutputBuffer = __AXTVDMABuffers[frameIndex]; + for (sint32 i = 0; i < numSamples; i++) + { + dmaOutputBuffer[0] = _swapEndianS16((sint16)std::min(std::max(_swapEndianS32(*inputChannel0), -32768), 32767)); + dmaOutputBuffer[1] = _swapEndianS16((sint16)std::min(std::max(_swapEndianS32(*inputChannel1), -32768), 32767)); + dmaOutputBuffer += 2; + // next sample + inputChannel0++; + inputChannel1++; + } + AIInitDMA(__AXTVDMABuffers[frameIndex], numSamples * 2 * sizeof(sint16)); // 2ch output + } + else if (__AXMode[AX_DEV_TV] == AX_MODE_MONO) + { + sint32* inputChannel0 = __AXTVBuffer48.GetPtr() + numSamples * 0; + sint16* dmaOutputBuffer = __AXTVDMABuffers[frameIndex]; + for (sint32 i = 0; i < numSamples; i++) + { + dmaOutputBuffer[0] = _swapEndianS16((sint16)std::min(std::max(_swapEndianS32(*inputChannel0), -32768), 32767)); + dmaOutputBuffer++; + // next sample + inputChannel0++; + } + AIInitDMA(__AXTVDMABuffers[frameIndex], numSamples * 1 * sizeof(sint16)); // 1ch (output as stereo) + } + else + assert_dbg(); + } + + void AIInitDRCDMA(sint16* sampleData, sint32 size) + { + sint32 sampleCount = size / sizeof(sint16); // sample count in total (summed up for all channels) + + if (sndGeneric.initParam.frameLength != 0) + { + cemu_assert(false); + } + + std::shared_lock lock(g_audioMutex); + + const uint32 channels = g_padAudio ? g_padAudio->GetChannels() : AX_DRC_CHANNEL_COUNT; + sint16* outputChannel = tempDRCChannelData + AX_SAMPLES_PER_3MS_48KHZ * tempDRCAudioBlockCounter * channels; + for (sint32 i = 0; i < sampleCount; ++i) + { + outputChannel[i] = _swapEndianS16(sampleData[i]); + } + + tempDRCAudioBlockCounter++; + if (tempDRCAudioBlockCounter == AX_FRAMES_PER_GROUP) + { + if (g_padAudio) + g_padAudio->FeedBlock(tempDRCChannelData); + + tempDRCAudioBlockCounter = 0; + } + } + + void AXOut_SubmitDRCFrame(sint32 frameIndex) + { + sint32 numSamples = AIGetSamplesPerChannel(AX_DEV_DRC); + if (__AXMode[AX_DEV_DRC] == AX_MODE_6CH) + { + sint32* inputChannel0 = __AXDRCBuffer48.GetPtr() + numSamples * 0; + sint32* inputChannel1 = __AXDRCBuffer48.GetPtr() + numSamples * 1; + sint32* inputChannel2 = __AXDRCBuffer48.GetPtr() + numSamples * 2; + sint32* inputChannel3 = __AXDRCBuffer48.GetPtr() + numSamples * 3; + sint16* dmaOutputBuffer = AIGetCurrentDMABuffer(AX_DEV_DRC); + for (sint32 i = 0; i < numSamples; i++) + { + dmaOutputBuffer[0] = _swapEndianS16((sint16)std::min(std::max(_swapEndianS32(*inputChannel0), -32768), 32767)); + dmaOutputBuffer[1] = _swapEndianS16((sint16)std::min(std::max(_swapEndianS32(*inputChannel1), -32768), 32767)); + + dmaOutputBuffer[4] = 0; + dmaOutputBuffer[5] = 0; + + dmaOutputBuffer[2] = 0; + dmaOutputBuffer[3] = 0; + dmaOutputBuffer += 6; + // next sample + inputChannel0++; + inputChannel1++; + inputChannel2++; + inputChannel3++; + } + AIInitDRCDMA(__AXDRCDMABuffers[frameIndex], numSamples * 6 * sizeof(sint16)); // 6ch output + } + else if (__AXMode[AX_DEV_DRC] == AX_MODE_STEREO) + { + sint32* inputChannel0 = __AXDRCBuffer48.GetPtr() + numSamples * 0; + sint32* inputChannel1 = __AXDRCBuffer48.GetPtr() + numSamples * 1; + sint16* dmaOutputBuffer = __AXDRCDMABuffers[frameIndex]; + for (sint32 i = 0; i < numSamples; i++) + { + dmaOutputBuffer[0] = _swapEndianS16((sint16)std::min(std::max(_swapEndianS32(*inputChannel0), -32768), 32767)); + dmaOutputBuffer[1] = _swapEndianS16((sint16)std::min(std::max(_swapEndianS32(*inputChannel1), -32768), 32767)); + dmaOutputBuffer += 2; + // next sample + inputChannel0++; + inputChannel1++; + } + + AIInitDRCDMA(__AXDRCDMABuffers[frameIndex], numSamples * 2 * sizeof(sint16)); // 2ch output + } + else if (__AXMode[AX_DEV_DRC] == AX_MODE_MONO) + { + sint32* inputChannel0 = __AXDRCBuffer48.GetPtr() + numSamples * 0; + sint16* dmaOutputBuffer = __AXDRCDMABuffers[frameIndex]; + for (sint32 i = 0; i < numSamples; i++) + { + // write mono input as stereo output + dmaOutputBuffer[1] = dmaOutputBuffer[0] = _swapEndianS16((sint16)std::min(std::max(_swapEndianS32(*inputChannel0), -32768), 32767)); + dmaOutputBuffer += 2; + // next sample + inputChannel0++; + } + AIInitDRCDMA(__AXDRCDMABuffers[frameIndex], numSamples * 2 * sizeof(sint16)); // 1ch (output as stereo) + } + else + assert_dbg(); + } + + /* AX output */ + + uint32 numQueuedFramesSndGeneric = 0; + + void AXOut_init() + { + auto& config = GetConfig(); + const auto audio_api = (IAudioAPI::AudioAPI)config.audio_api; + + numQueuedFramesSndGeneric = 0; + + std::unique_lock lock(g_audioMutex); + if (!g_tvAudio) + { + sint32 channels; + switch (config.tv_channels) + { + case 0: + channels = 1; // will mix mono sound on both output channels + break; + case 2: + channels = 6; + break; + default: // stereo + channels = 2; + break; + } + + IAudioAPI::DeviceDescriptionPtr device_description; + if (IAudioAPI::IsAudioAPIAvailable(audio_api)) + { + auto devices = IAudioAPI::GetDevices(audio_api); + const auto it = std::find_if(devices.begin(), devices.end(), [&config](const auto& d) {return d->GetIdentifier() == config.tv_device; }); + if (it != devices.end()) + device_description = *it; + } + + if (device_description) + { + try + { + g_tvAudio = IAudioAPI::CreateDevice((IAudioAPI::AudioAPI)config.audio_api, device_description, 48000, channels, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16); + g_tvAudio->SetVolume(config.tv_volume); + } + catch (std::runtime_error& ex) + { + forceLog_printf("can't initialize tv audio: %s", ex.what()); + exit(0); + } + } + } + + if (!g_padAudio) + { + sint32 channels; + switch (config.pad_channels) + { + case 0: + channels = 1; // will mix mono sound on both output channels + break; + case 2: + channels = 6; + break; + default: // stereo + channels = 2; + break; + } + + IAudioAPI::DeviceDescriptionPtr device_description; + if (IAudioAPI::IsAudioAPIAvailable(audio_api)) + { + auto devices = IAudioAPI::GetDevices(audio_api); + const auto it = std::find_if(devices.begin(), devices.end(), [&config](const auto& d) {return d->GetIdentifier() == config.pad_device; }); + if (it != devices.end()) + device_description = *it; + } + + if (device_description) + { + try + { + g_padAudio = IAudioAPI::CreateDevice((IAudioAPI::AudioAPI)config.audio_api, device_description, 48000, channels, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16); + g_padAudio->SetVolume(config.pad_volume); + g_padVolume = config.pad_volume; + } + catch (std::runtime_error& ex) + { + forceLog_printf("can't initialize pad audio: %s", ex.what()); + exit(0); + } + } + } + } + + void AXOut_reset() + { + std::unique_lock lock(g_audioMutex); + if (g_tvAudio) + { + g_tvAudio->Stop(); + g_tvAudio.reset(); + } + if (g_padAudio) + { + g_padAudio->Stop(); + g_padAudio.reset(); + } + } + + void AXOut_updateDevicePlayState(bool isPlaying) + { + std::shared_lock lock(g_audioMutex); + if (g_tvAudio) + { + if (isPlaying) + g_tvAudio->Play(); + else + g_tvAudio->Stop(); + } + + if (g_padAudio) + { + if (isPlaying) + g_padAudio->Play(); + else + g_padAudio->Stop(); + } + } + + // called periodically to check for AX updates + void AXOut_update() + { + constexpr auto kTimeout = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::milliseconds(((IAudioAPI::kBlockCount * 3) / 4) * (AX_FRAMES_PER_GROUP * 3))); + constexpr auto kWaitDuration = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::milliseconds(3)); + constexpr auto kWaitDurationFast = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::microseconds(2900)); + constexpr auto kWaitDurationMinimum = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::microseconds(1700)); + + // if we haven't buffered any blocks, we will wait less time than usual + bool additional_blocks_required = false; + { + const std::shared_lock lock(g_audioMutex, std::try_to_lock); + if (lock) + additional_blocks_required = (g_tvAudio && g_tvAudio->NeedAdditionalBlocks()) || (g_padAudio && g_padAudio->NeedAdditionalBlocks()); + } + + const auto wait_duration = additional_blocks_required ? kWaitDurationFast : kWaitDuration; + + // s_ax_interval_timer increases by the wait period + // it can lag behind by multiple periods (up to kTimeout) if there is minor stutter in the CPU thread + // s_last_check is always set to the timestamp at the time of firing + // it's used to enforce the minimum wait delay (we want to avoid calling AX update in quick succession because other threads may need to do work first) + + static auto s_ax_interval_timer = now_cached() - kWaitDuration; + static auto s_last_check = now_cached(); + + const auto now = now_cached(); + const auto diff = (now - s_ax_interval_timer); + + if (diff < wait_duration) + return; + + // handle minimum wait time (1.7MS) + if ((now - s_last_check) < kWaitDurationMinimum) + return; + s_last_check = now; + + // if we're too far behind, skip forward + if (diff >= kTimeout) + s_ax_interval_timer = (now - wait_duration); + else + s_ax_interval_timer += wait_duration; + + + if (snd_core::isInitialized()) + { + if (numQueuedFramesSndGeneric == snd_core::getNumProcessedFrames()) + { + AXOut_updateDevicePlayState(true); + snd_core::AXIst_QueueFrame(); + numQueuedFramesSndGeneric++; + } + } + } + +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/snd_core/ax_voice.cpp b/src/Cafe/OS/libs/snd_core/ax_voice.cpp new file mode 100644 index 00000000..f60e5853 --- /dev/null +++ b/src/Cafe/OS/libs/snd_core/ax_voice.cpp @@ -0,0 +1,1286 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/HW/Espresso/PPCState.h" +#include "Cafe/HW/Espresso/PPCCallback.h" +#include "Cafe/OS/libs/snd_core/ax.h" +#include "Cafe/OS/libs/snd_core/ax_internal.h" +#include "util/helpers/fspinlock.h" + +namespace snd_core +{ + inline void AXSetSyncFlag(AXVPB* vpb, uint32 flags) + { + vpb->sync = (uint32be)((uint32)vpb->sync | flags); + } + + inline void AXResetSyncFlag(AXVPB* vpb, uint32 flags) + { + vpb->sync = (uint32be)((uint32)vpb->sync & ~flags); + } + + /* voice lists */ + FSpinlock __AXVoiceListSpinlock; + + std::vector<AXVPB*> __AXVoicesPerPriority[AX_PRIORITY_MAX]; + std::vector<AXVPB*> __AXFreeVoices; + + void AXVoiceList_AddFreeVoice(AXVPB* vpb) + { + cemu_assert(vpb->priority != AX_PRIORITY_FREE); + __AXFreeVoices.push_back(vpb); + vpb->prev = nullptr; + vpb->next = nullptr; + } + + AXVPB* AXVoiceList_GetFreeVoice() + { + if (__AXFreeVoices.empty()) + return nullptr; + AXVPB* vpb = __AXFreeVoices.back(); + __AXFreeVoices.pop_back(); + return vpb; + } + + void AXVoiceList_ResetFreeVoiceList() + { + __AXFreeVoices.clear(); + } + + std::vector<AXVPB*>& AXVoiceList_GetFreeVoices() + { + return __AXFreeVoices; + } + + void AXVoiceList_AddVoice(AXVPB* vpb, sint32 priority) + { + cemu_assert(priority != AX_PRIORITY_FREE && priority < AX_PRIORITY_MAX); + __AXVoicesPerPriority[priority].push_back(vpb); + vpb->next = nullptr; + vpb->prev = nullptr; + vpb->priority = priority; + } + + void AXVoiceList_RemoveVoice(AXVPB* vpb) + { + uint32 priority = (uint32)vpb->priority; + cemu_assert(priority != AX_PRIORITY_FREE && priority < AX_PRIORITY_MAX); + vectorRemoveByValue(__AXVoicesPerPriority[priority], vpb); + } + + AXVPB* AXVoiceList_GetLeastRecentVoiceByPriority(uint32 priority) + { + cemu_assert(priority != AX_PRIORITY_FREE && priority < AX_PRIORITY_MAX); + if (__AXVoicesPerPriority[priority].empty()) + return nullptr; + return __AXVoicesPerPriority[priority].front(); + } + + std::vector<AXVPB*>& AXVoiceList_GetListByPriority(uint32 priority) + { + cemu_assert(priority != AX_PRIORITY_FREE && priority < AX_PRIORITY_MAX); + return __AXVoicesPerPriority[priority]; + } + + SysAllocator<AXVPBInternal_t, AX_MAX_VOICES> _buffer__AXVPBInternalVoiceArray; + AXVPBInternal_t* __AXVPBInternalVoiceArray; + + SysAllocator<AXVPBInternal_t, AX_MAX_VOICES> _buffer__AXVPBInternalVoiceShadowCopyArray; + AXVPBInternal_t* __AXVPBInternalVoiceShadowCopyArrayPtr; // this is the array used by audio mixing (it's synced at the beginning of every audio frame with __AXVPBInternalVoiceArray) + + SysAllocator<AXVPBItd, AX_MAX_VOICES> _buffer__AXVPBItdArray; + AXVPBItd* __AXVPBItdArrayPtr; + + SysAllocator<AXVPB, AX_MAX_VOICES> _buffer__AXVPBArray; + AXVPB* __AXVPBArrayPtr; + + struct AXUSERPROTECTION + { + MPTR threadMPTR; + uint32 count; + }; + + uint32 __AXUserProtectionArraySize = 0; + AXUSERPROTECTION __AXUserProtectionArray[AX_MAX_VOICES] = { 0 }; + AXUSERPROTECTION __AXVoiceProtection[AX_MAX_VOICES] = { 0 }; + + bool AXUserIsProtected() + { + return __AXUserProtectionArraySize != 0; + } + + sint32 AXUserBegin() + { + // some games (e.g. Color Splash) can block themselves from calling AXSetVoice* API in time if a thread gets + // rescheduled while inside a AXUserBegin() + AXUserEnd() block + // to prevent this from happening we extend the current thread's quantum while the protection is raised + PPCCore_boostQuantum(10000); + + if (AXIst_IsFrameBeingProcessed()) + { + return -2; + } + MPTR currentThreadMPTR = coreinitThread_getCurrentThreadMPTRDepr(ppcInterpreterCurrentInstance); + for (sint32 i = __AXUserProtectionArraySize - 1; i >= 0; i--) + { + if (__AXUserProtectionArray[i].threadMPTR == currentThreadMPTR) + { + sint32 newCount = __AXUserProtectionArray[i].count + 1; + __AXUserProtectionArray[i].count = newCount; + return newCount; + } + } + // no matching entry found + if (__AXUserProtectionArraySize >= AX_MAX_VOICES) + { + // no entry available + return -4; + } + // create new entry + if (__AXUserProtectionArraySize < 0) + assert_dbg(); + sint32 entryIndex = __AXUserProtectionArraySize; + __AXUserProtectionArray[entryIndex].threadMPTR = currentThreadMPTR; + __AXUserProtectionArray[entryIndex].count = 1; + __AXUserProtectionArraySize++; + return 1; + } + + sint32 AXUserEnd() + { + PPCCore_deboostQuantum(10000); + if (AXIst_IsFrameBeingProcessed()) + return -2; + MPTR currentThreadMPTR = coreinitThread_getCurrentThreadMPTRDepr(ppcInterpreterCurrentInstance); + for (sint32 i = __AXUserProtectionArraySize - 1; i >= 0; i--) + { + if (__AXUserProtectionArray[i].threadMPTR == currentThreadMPTR) + { + sint32 newCount = __AXUserProtectionArray[i].count - 1; + __AXUserProtectionArray[i].count = newCount; + if (__AXUserProtectionArray[i].count == 0) + { + // count == 0 -> remove entry + if (i >= (sint32)(__AXUserProtectionArraySize - 1)) + { + // entry is last in array, can just decrease array size + __AXUserProtectionArraySize--; + __AXUserProtectionArray[i].threadMPTR = MPTR_NULL; + } + else + { + // remove entry by shifting all remaining entries + //for (sint32 f = i; f >= 0; f--) + //{ + // __AXUserProtectionArray[f].threadMPTR = __AXUserProtectionArray[f + 1].threadMPTR; + // __AXUserProtectionArray[f].count = __AXUserProtectionArray[f + 1].count; + //} + cemu_assert_debug(false); + } + // remove entries associated with the current thread from __AXVoiceProtection[] if the count is zero + for (sint32 f = 0; f < AX_MAX_VOICES; f++) + { + if (__AXVoiceProtection[f].threadMPTR == currentThreadMPTR && __AXVoiceProtection[f].count == 0) + { + __AXVoiceProtection[f].threadMPTR = MPTR_NULL; + } + } + } + return newCount; + } + } + cemu_assert_debug(false); // voice not found in list, did the game not call AXUserBegin()? + return -3; + } + + bool AXVoiceProtection_IsProtectedByAnyThread(AXVPB* vpb) + { + sint32 index = vpb->index; + return __AXVoiceProtection[index].threadMPTR != MPTR_NULL; + } + + bool AXVoiceProtection_IsProtectedByCurrentThread(AXVPB* vpb) + { + sint32 index = vpb->index; + bool isProtected = false; + if (AXIst_IsFrameBeingProcessed()) + isProtected = __AXVoiceProtection[index].threadMPTR != MPTR_NULL; + else + isProtected = __AXVoiceProtection[index].threadMPTR != coreinitThread_getCurrentThreadMPTRDepr(ppcInterpreterCurrentInstance); + return isProtected; + } + + void AXVoiceProtection_Acquire(AXVPB* vpb) + { + sint32 index = vpb->index; + if (AXUserIsProtected() == false) + return; + if (AXIst_IsFrameBeingProcessed()) + return; + if (__AXVoiceProtection[index].threadMPTR == MPTR_NULL) + { + __AXVoiceProtection[index].threadMPTR = coreinitThread_getCurrentThreadMPTRDepr(ppcInterpreterCurrentInstance); + // does not set count? + } + } + + void AXVoiceProtection_Release(AXVPB* vpb) + { + sint32 index = vpb->index; + __AXVoiceProtection[index].threadMPTR = MPTR_NULL; + __AXVoiceProtection[index].count = 0; + } + + sint32 AXVoiceBegin(AXVPB* voice) + { + if (voice == nullptr) + { + forceLog_printf("AXVoiceBegin(): Invalid voice"); + return -1; + } + uint32 index = (uint32)voice->index; + if (index >= AX_MAX_VOICES) + { + forceLog_printf("AXVoiceBegin(): Invalid voice index"); + return -1; + } + if (AXIst_IsFrameBeingProcessed()) + return -2; + MPTR currentThreadMPTR = coreinitThread_getCurrentThreadMPTRDepr(ppcInterpreterCurrentInstance); + if (__AXVoiceProtection[index].threadMPTR == MPTR_NULL) + { + __AXVoiceProtection[index].threadMPTR = currentThreadMPTR; + __AXVoiceProtection[index].count = 1; + return 1; + } + else if (__AXVoiceProtection[index].threadMPTR == currentThreadMPTR) + { + __AXVoiceProtection[index].count++; + return __AXVoiceProtection[index].count; + } + return -1; + } + + sint32 __GetThreadProtection(MPTR threadMPTR) + { + for (sint32 i = __AXUserProtectionArraySize - 1; i >= 0; i--) + { + if (__AXUserProtectionArray[i].threadMPTR == threadMPTR) + return i; + } + return -1; + } + + sint32 AXVoiceEnd(AXVPB* voice) + { + if (voice == nullptr) + { + forceLog_printf("AXVoiceBegin(): Invalid voice"); + return -1; + } + uint32 index = (uint32)voice->index; + if (index >= AX_MAX_VOICES) + { + forceLog_printf("AXVoiceBegin(): Invalid voice index"); + return -1; + } + if (AXIst_IsFrameBeingProcessed()) + return -2; + MPTR currentThreadMPTR = coreinitThread_getCurrentThreadMPTRDepr(ppcInterpreterCurrentInstance); + if (__AXVoiceProtection[index].threadMPTR == currentThreadMPTR) + { + if (__AXVoiceProtection[index].count > 0) + __AXVoiceProtection[index].count--; + if (__AXVoiceProtection[index].count == 0) + { + if (__GetThreadProtection(currentThreadMPTR) == -1) + { + __AXVoiceProtection[index].threadMPTR = 0; + } + } + sint32 count = __AXVoiceProtection[index].count; + return count; + } + else + { + if (__AXVoiceProtection[index].threadMPTR == MPTR_NULL) + { + return -3; + } + else + { + return -1; + } + } + } + + void AXVPB_SetVoiceDefault(AXVPB* vpb) + { + AXVPBInternal_t* internal = GetInternalVoice(vpb); + uint32 index = GetVoiceIndex(vpb); + vpb->playbackState = 0; + vpb->sync = 0; + AXSetSyncFlag(vpb, AX_SYNCFLAG_PLAYBACKSTATE); + AXSetSyncFlag(vpb, AX_SYNCFLAG_SRCDATA); + AXSetSyncFlag(vpb, AX_SYNCFLAG_LPFDATA); + AXSetSyncFlag(vpb, AX_SYNCFLAG_BIQUADDATA); + AXSetSyncFlag(vpb, AX_SYNCFLAG_VOICEREMOTEON); + AXSetSyncFlag(vpb, AX_SYNCFLAG_ITD20); + AXSetSyncFlag(vpb, AX_SYNCFLAG_8000000); + AXSetSyncFlag(vpb, 0x10000000); + internal->reserved282_rmtIIRGuessed = 0; + internal->reserved16C = 0; + internal->reserved148_voiceRmtOn = 0; + internal->biquad.on = 0; + internal->lpf.on = 0; + internal->playbackState = 0; + uint32 defaultMixer = AXGetDefaultMixerSelect(); + internal->mixerSelect = defaultMixer; + vpb->mixerSelect = defaultMixer; + AXResetVoiceLoopCount(vpb); + internal->reserved280 = 0; + internal->src.currentFrac = 0; + internal->src.historySamples[0] = 0; + internal->src.historySamples[1] = 0; + internal->src.historySamples[2] = 0; + internal->src.historySamples[3] = 0; + internal->reserved278 = 0; + internal->reserved27A = 0; + internal->reserved27C = 0; + internal->reserved27E = 0; + internal->srcFilterMode = 0; + memset(&internal->deviceMixMaskTV, 0, 8); + memset(&internal->deviceMixMaskDRC, 0, 16); + memset(&internal->deviceMixMaskRMT, 0, 0x20); + memset(&internal->reserved1E8, 0, 0x30); + memset(&internal->reserved218, 0, 0x40); + memset(&internal->reserved258, 0, 0x20); + } + + AXVPB* AXVPB_DropVoice(sint32 priority) + { + for (sint32 i = 1; i < priority; i++) + { + AXVPB* voiceItr = AXVoiceList_GetLeastRecentVoiceByPriority(i); + if (voiceItr) + { + // get last voice in chain + while (voiceItr->next) + voiceItr = voiceItr->next.GetPtr(); + forceLogDebug_printf("Dropped voice %d", (uint32)voiceItr->index); + // drop voice + if (voiceItr->playbackState != 0) + { + voiceItr->depop = 1; + } + // do drop callback + if (voiceItr->callback) + { + PPCCoreCallback(voiceItr->callback, voiceItr); + } + voiceItr->ukn4C_dropReason = 0; // probably drop reason? + if (voiceItr->callbackEx) + { + PPCCoreCallback(voiceItr->callbackEx, voiceItr, voiceItr->userParam, voiceItr->ukn4C_dropReason); + } + // move voice to new stack + AXVoiceList_RemoveVoice(voiceItr); + AXVoiceList_AddVoice(voiceItr, priority); + return voiceItr; + } + } + return nullptr; + } + + AXVPB* AXAcquireVoiceEx(uint32 priority, MPTR callbackEx, MPTR userParam) + { + cemu_assert(priority != AX_PRIORITY_FREE && priority < AX_PRIORITY_MAX); + __AXVoiceListSpinlock.acquire(); + AXVPB* vpb = AXVoiceList_GetFreeVoice(); + if (vpb != nullptr) + { + AXVoiceList_AddVoice(vpb, priority); + vpb->userParam = userParam; + vpb->callback = MPTR_NULL; + vpb->callbackEx = callbackEx; + AXVPB_SetVoiceDefault(vpb); + } + else + { + // no free voice available, drop voice with lower priority + AXVPB* droppedVoice = AXVPB_DropVoice(priority); + if (droppedVoice == nullptr) + { + // no voice available + __AXVoiceListSpinlock.release(); + return nullptr; + } + vpb->userParam = userParam; + vpb->callback = MPTR_NULL; + vpb->callbackEx = callbackEx; + AXVPB_SetVoiceDefault(vpb); + } + __AXVoiceListSpinlock.release(); + return vpb; + } + + void AXFreeVoice(AXVPB* vpb) + { + cemu_assert(vpb != nullptr); + __AXVoiceListSpinlock.acquire(); + if (vpb->priority == (uint32be)AX_PRIORITY_FREE) + { + forceLog_printf("AXFreeVoice() called on free voice\n"); + __AXVoiceListSpinlock.release(); + return; + } + AXVoiceProtection_Release(vpb); + AXVoiceList_RemoveVoice(vpb); + if (vpb->playbackState != (uint32be)0) + { + vpb->depop = (uint32be)1; + } + AXVPB_SetVoiceDefault(vpb); + vpb->callback = MPTR_NULL; + vpb->callbackEx = MPTR_NULL; + AXVoiceList_AddFreeVoice(vpb); + __AXVoiceListSpinlock.release(); + } + + void AXVPBInit() + { + __AXVPBInternalVoiceArray = _buffer__AXVPBInternalVoiceArray.GetPtr(); + + memset(__AXVPBInternalVoiceShadowCopyArrayPtr, 0, sizeof(AXVPBInternal_t)*AX_MAX_VOICES); + memset(__AXVPBInternalVoiceArray, 0, sizeof(AXVPBInternal_t)*AX_MAX_VOICES); + memset(__AXVPBItdArrayPtr, 0, sizeof(AXVPBItd)*AX_MAX_VOICES); + memset(__AXVPBArrayPtr, 0, sizeof(AXVPB)*AX_MAX_VOICES); + for (sint32 i = 0; i < AX_MAX_VOICES; i++) + { + AXVPBItd* itd = __AXVPBItdArrayPtr + i; + AXVPBInternal_t* internalShadowCopy = __AXVPBInternalVoiceShadowCopyArrayPtr + i; + AXVPBInternal_t* internal = __AXVPBInternalVoiceArray + i; + AXVPB* vpb = __AXVPBArrayPtr + i; + + MPTR internalShadowCopyPhys = memory_virtualToPhysical(memory_getVirtualOffsetFromPointer(internalShadowCopy)); + MPTR itdPhys = memory_virtualToPhysical(memory_getVirtualOffsetFromPointer(itd)); + + vpb->callbackEx = MPTR_NULL; + vpb->itd = itd; + vpb->callback = MPTR_NULL; + vpb->index = i; + AXVPB_SetVoiceDefault(vpb); + + if (i == (AX_MAX_VOICES - 1)) + { + internal->nextAddrHigh = 0; + internal->nextAddrLow = 0; + internalShadowCopy->nextAddrHigh = 0; + internalShadowCopy->nextAddrLow = 0; + } + else + { + MPTR nextShadowCopyPhys = internalShadowCopyPhys + sizeof(AXVPBInternal_t); + internalShadowCopy->nextAddrHigh = internal->nextAddrHigh = (nextShadowCopyPhys >> 16); + internalShadowCopy->nextAddrLow = internal->nextAddrLow = (nextShadowCopyPhys & 0xFFFF); + } + internalShadowCopy->index = internal->index = i; + internalShadowCopy->selfAddrHigh = internal->selfAddrHigh = (internalShadowCopyPhys >> 16); + internalShadowCopy->selfAddrLow = internal->selfAddrLow = (internalShadowCopyPhys & 0xFFFF); + internalShadowCopy->itdAddrHigh = internal->itdAddrHigh = (itdPhys >> 16); + internalShadowCopy->itdAddrLow = internal->itdAddrLow = (itdPhys & 0xFFFF); + vpb->priority = 1; + AXVoiceList_AddFreeVoice(vpb); + } + } + + void AXVPB_Init() + { + __AXVPBInternalVoiceShadowCopyArrayPtr = _buffer__AXVPBInternalVoiceShadowCopyArray.GetPtr(); + __AXVPBArrayPtr = _buffer__AXVPBArray.GetPtr(); + __AXVPBItdArrayPtr = _buffer__AXVPBItdArray.GetPtr(); + AXVPBInit(); + } + + sint32 AXIsValidDevice(sint32 device, sint32 deviceIndex) + { + if (device == AX_DEV_TV) + { + if (deviceIndex != 0) + return -2; + } + else if (device == AX_DEV_DRC) + { + if (deviceIndex != 0 && deviceIndex != 1) + return -2; + } + else if (device == AX_DEV_TV) + { + if (deviceIndex < 0 || deviceIndex >= 4) + return -2; + } + else + return -1; + return 0; + } + + + void __AXSetVoiceChannelMix(AXCHMIX_DEPR* mixOut, AXCHMIX_DEPR* mixIn, sint16* mixMask) + { + for (sint32 i = 0; i < AX_BUS_COUNT; i++) + { + mixOut[i].vol = mixIn[i].vol; + mixOut[i].delta = mixIn[i].delta; + if (mixIn[i].delta) + mixMask[i] = 3; + else if (mixIn[i].vol) + mixMask[i] = 1; + else + mixMask[i] = 0; + } + } + + sint32 AXSetVoiceDeviceMix(AXVPB* vpb, sint32 device, sint32 deviceIndex, AXCHMIX_DEPR* mix) + { + if (vpb == nullptr) + return -4; + if (mix == nullptr) + return -3; + sint32 r = AXIsValidDevice(device, deviceIndex); + if (r) + return r; + AXVPBInternal_t* internal = __AXVPBInternalVoiceArray + (sint32)vpb->index; + sint32 channelCount; + + uint16* deviceMixMask; + AXCHMIX_DEPR* voiceMix; + if (device == AX_DEV_TV) + { + channelCount = AX_TV_CHANNEL_COUNT; + voiceMix = internal->deviceMixTV + deviceIndex * 0x60 / 4; + deviceMixMask = internal->deviceMixMaskTV; + } + else if (device == AX_DEV_DRC) + { + channelCount = AX_DRC_CHANNEL_COUNT; + voiceMix = internal->deviceMixDRC + deviceIndex * 16; + deviceMixMask = internal->deviceMixMaskDRC; + } + else if (device == AX_DEV_RMT) + { + assert_dbg(); + channelCount = AX_RMT_CHANNEL_COUNT; + } + sint16 updatedMixMask[AX_BUS_COUNT]; + for (sint32 i = 0; i < AX_BUS_COUNT; i++) + { + updatedMixMask[i] = 0; + } + sint16 channelMixMask[AX_BUS_COUNT]; + for (sint32 c = 0; c < channelCount; c++) + { + __AXSetVoiceChannelMix(voiceMix, mix, channelMixMask); + for (sint32 i = 0; i < AX_BUS_COUNT; i++) + { + updatedMixMask[i] |= (channelMixMask[i] << (c * 2)); + } + // next channel + voiceMix += AX_BUS_COUNT; + mix += AX_BUS_COUNT; + } + for (sint32 i = 0; i < AX_BUS_COUNT; i++) + { + deviceMixMask[i] = _swapEndianU16(updatedMixMask[i]); + } + vpb->sync = (uint32)vpb->sync | (AX_SYNCFLAG_DEVICEMIXMASK | AX_SYNCFLAG_DEVICEMIX); + AXVoiceProtection_Acquire(vpb); + return 0; + } + + void AXSetVoiceState(AXVPB* vpb, sint32 voiceState) + { + if (vpb->playbackState != (uint32be)voiceState) + { + vpb->playbackState = voiceState; + AXVPBInternal_t* internal = __AXVPBInternalVoiceArray + (sint32)vpb->index; + internal->playbackState = _swapEndianU16(voiceState); + AXSetSyncFlag(vpb, AX_SYNCFLAG_PLAYBACKSTATE); + AXVoiceProtection_Acquire(vpb); + if (voiceState == 0) + { + vpb->depop = (uint32be)1; + } + } + } + + sint32 AXIsVoiceRunning(AXVPB* vpb) + { + AXVPBInternal_t* internal = __AXVPBInternalVoiceArray + (sint32)vpb->index; + return (_swapEndianU16(internal->playbackState) == 1) ? 1 : 0; + } + + void AXSetVoiceType(AXVPB* vpb, uint16 voiceType) + { + AXVPBInternal_t* internal = __AXVPBInternalVoiceArray + (sint32)vpb->index; + internal->voiceType = _swapEndianU16(voiceType); + AXSetSyncFlag(vpb, AX_SYNCFLAG_VOICETYPE); + AXVoiceProtection_Acquire(vpb); + } + + void AXSetVoiceAdpcm(AXVPB* vpb, AXPBADPCM_t* adpcm) + { + AXVPBInternal_t* internal = __AXVPBInternalVoiceArray + (sint32)(vpb->index); + for (sint32 i = 0; i < 16; i++) + internal->adpcmData.coef[i] = adpcm->a[i]; + internal->adpcmData.gain = adpcm->gain; + internal->adpcmData.scale = adpcm->scale; + AXSetSyncFlag(vpb, AX_SYNCFLAG_ADPCMDATA); + AXVoiceProtection_Acquire(vpb); + } + + void AXSetVoiceAdpcmLoop(AXVPB* vpb, AXPBADPCMLOOP_t* adpcmLoop) + { + AXVPBInternal_t* internal = __AXVPBInternalVoiceArray + (sint32)vpb->index; + internal->adpcmLoop.loopScale = adpcmLoop->loopScale; + if (internal->voiceType == 0) + { + internal->adpcmLoop.loopYn1 = adpcmLoop->loopYn1; + internal->adpcmLoop.loopYn2 = adpcmLoop->loopYn2; + } + AXSetSyncFlag(vpb, AX_SYNCFLAG_ADPCMLOOP); + AXVoiceProtection_Acquire(vpb); + } + + void AXSetVoiceSrc(AXVPB* vpb, AXPBSRC_t* src) + { + AXVPBInternal_t* internal = __AXVPBInternalVoiceArray + (sint32)vpb->index; + internal->src.ratioHigh = src->ratioHigh; + internal->src.ratioLow = src->ratioLow; + internal->src.currentFrac = src->currentFrac; + internal->src.historySamples[0] = src->historySamples[0]; + internal->src.historySamples[1] = src->historySamples[1]; + internal->src.historySamples[2] = src->historySamples[2]; + internal->src.historySamples[3] = src->historySamples[3]; + AXResetSyncFlag(vpb, AX_SYNCFLAG_SRCRATIO); + AXSetSyncFlag(vpb, AX_SYNCFLAG_SRCDATA); + AXVoiceProtection_Acquire(vpb); + } + + void AXSetVoiceSrcType(AXVPB* vpb, uint32 srcType) + { + AXVPBInternal_t* internal = __AXVPBInternalVoiceArray + (sint32)vpb->index; + if (srcType == AX_SRC_TYPE_NONE) + { + internal->srcFilterMode = _swapEndianU16(AX_FILTER_MODE_NONE); + } + else if (srcType == AX_SRC_TYPE_LINEAR) + { + internal->srcFilterMode = _swapEndianU16(AX_FILTER_MODE_LINEAR); + } + else if (srcType == AX_SRC_TYPE_LOWPASS1) + { + internal->srcFilterMode = _swapEndianU16(AX_FILTER_MODE_TAP); + internal->srcTapFilter = _swapEndianU16(AX_FILTER_LOWPASS_8K); + } + else if (srcType == AX_SRC_TYPE_LOWPASS2) + { + internal->srcFilterMode = _swapEndianU16(AX_FILTER_MODE_TAP); + internal->srcTapFilter = _swapEndianU16(AX_FILTER_LOWPASS_12K); + } + else if (srcType == AX_SRC_TYPE_LOWPASS3) + { + internal->srcFilterMode = _swapEndianU16(AX_FILTER_MODE_TAP); + internal->srcTapFilter = _swapEndianU16(AX_FILTER_LOWPASS_16K); + } + else + { + forceLog_printf("AXSetVoiceSrcType(): Unsupported src type %d", srcType); + } + AXSetSyncFlag(vpb, AX_SYNCFLAG_SRCFILTER); + AXVoiceProtection_Acquire(vpb); + } + + sint32 AXSetVoiceSrcRatio(AXVPB* vpb, float ratio) + { + AXVPBInternal_t* internal = __AXVPBInternalVoiceArray + (sint32)vpb->index; + ratio *= 65536.0f; +#ifndef PUBLIC_RELEASE + if (ratio >= 4294967296.0f) + assert_dbg(); +#endif + sint32 ratioI = (sint32)ratio; + if (ratioI < 0) + ratioI = 0; + else if (ratioI > 0x80000) + ratioI = 0x80000; + + uint16 ratioHigh = (uint16)(ratioI >> 16); + uint16 ratioLow = (uint16)(ratioI & 0xFFFF); + + ratioHigh = _swapEndianU16(ratioHigh); + ratioLow = _swapEndianU16(ratioLow); + + if (internal->src.ratioHigh != ratioHigh || internal->src.ratioLow != ratioLow) + { + internal->src.ratioHigh = ratioHigh; + internal->src.ratioLow = ratioLow; + AXSetSyncFlag(vpb, AX_SYNCFLAG_SRCRATIO); + AXVoiceProtection_Acquire(vpb); + } + return 0; + } + + void AXSetVoiceVe(AXVPB* vpb, AXPBVE* ve) + { + AXVPBInternal_t* internal = __AXVPBInternalVoiceArray + (sint32)vpb->index; + internal->veVolume = ve->currentVolume; + internal->veDelta = ve->currentDelta; + AXSetSyncFlag(vpb, AX_SYNCFLAG_VE); + AXVoiceProtection_Acquire(vpb); + } + + void AXComputeLpfCoefs(uint32 freq, uint16be* a0, uint16be* b0) + { + // todo - verify algorithm + float t1 = cos((float)freq / 32000.0f * 6.2831855f); + float t2 = 2.0f - t1; + t1 = (float)sqrt(t2 * t2 - 1.0f); + t1 = t1 - t2; + t1 = t1 * 32768.0f; + t1 = -t1; + uint32 r = (uint16)t1; + *a0 = 0x7FFF - r; + *b0 = r; + } + + void AXSetVoiceLpf(AXVPB* vpb, AXPBLPF_t* lpf) + { + AXVPBInternal_t* internal = __AXVPBInternalVoiceArray + (sint32)vpb->index; + internal->lpf.on = lpf->on; + internal->lpf.yn1 = lpf->yn1; + internal->lpf.a0 = lpf->a0; + internal->lpf.b0 = lpf->b0; + AXSetSyncFlag(vpb, AX_SYNCFLAG_LPFDATA); + AXVoiceProtection_Acquire(vpb); + } + + void AXSetVoiceLpfCoefs(AXVPB* vpb, uint16 a0, uint16 b0) + { + AXVPBInternal_t* internal = __AXVPBInternalVoiceArray + (sint32)vpb->index; + internal->lpf.a0 = _swapEndianU16(a0); + internal->lpf.b0 = _swapEndianU16(b0); + AXSetSyncFlag(vpb, AX_SYNCFLAG_LPFCOEF); + AXVoiceProtection_Acquire(vpb); + } + + void AXSetVoiceBiquad(AXVPB* vpb, AXPBBIQUAD_t* biquad) + { + AXVPBInternal_t* internal = __AXVPBInternalVoiceArray + (sint32)vpb->index; + internal->biquad.on = biquad->on; + internal->biquad.xn1 = biquad->xn1; + internal->biquad.xn2 = biquad->xn2; + internal->biquad.yn1 = biquad->yn1; + internal->biquad.yn2 = biquad->yn2; + internal->biquad.b0 = biquad->b0; + internal->biquad.b1 = biquad->b1; + internal->biquad.b2 = biquad->b2; + internal->biquad.a1 = biquad->a1; + internal->biquad.a2 = biquad->a2; + AXSetSyncFlag(vpb, AX_SYNCFLAG_BIQUADDATA); + AXVoiceProtection_Acquire(vpb); + } + + void AXSetVoiceBiquadCoefs(AXVPB* vpb, uint16 b0, uint16 b1, uint16 b2, uint16 a1, uint16 a2) + { + AXVPBInternal_t* internal = __AXVPBInternalVoiceArray + (sint32)vpb->index; + internal->biquad.b0 = _swapEndianU16(b0); + internal->biquad.b1 = _swapEndianU16(b1); + internal->biquad.b2 = _swapEndianU16(b2); + internal->biquad.a1 = _swapEndianU16(a1); + internal->biquad.a2 = _swapEndianU16(a2); + AXSetSyncFlag(vpb, AX_SYNCFLAG_BIQUADCOEF); + AXVoiceProtection_Acquire(vpb); + } + + void __AXSetVoiceAddr(AXVPB* vpb, axOffsetsInternal_t* voiceAddr) + { + sint32 voiceIndex = (sint32)(vpb->index); + AXVPBInternal_t* internal = __AXVPBInternalVoiceArray + voiceIndex; + memcpy(&internal->internalOffsets, voiceAddr, sizeof(axOffsetsInternal_t)); + uint16 format = _swapEndianU16(voiceAddr->format); + if (format == AX_FORMAT_PCM8) + { + memset(&internal->adpcmData, 0x00, sizeof(axADPCMInternal_t)); + internal->adpcmData.gain = 0x100; + AXSetSyncFlag(vpb, AX_SYNCFLAG_ADPCMDATA); + AXResetSyncFlag(vpb, AX_SYNCFLAG_LOOPFLAG | AX_SYNCFLAG_LOOPOFFSET | AX_SYNCFLAG_ENDOFFSET | AX_SYNCFLAG_CURRENTOFFSET); + AXSetSyncFlag(vpb, AX_SYNCFLAG_OFFSETS); + AXVoiceProtection_Acquire(vpb); + } + else if (format == AX_FORMAT_PCM16) + { + memset(&internal->adpcmData, 0x00, sizeof(axADPCMInternal_t)); + internal->adpcmData.gain = 0x800; + AXSetSyncFlag(vpb, AX_SYNCFLAG_ADPCMDATA); + AXResetSyncFlag(vpb, AX_SYNCFLAG_LOOPFLAG | AX_SYNCFLAG_LOOPOFFSET | AX_SYNCFLAG_ENDOFFSET | AX_SYNCFLAG_CURRENTOFFSET); + AXSetSyncFlag(vpb, AX_SYNCFLAG_OFFSETS); + AXVoiceProtection_Acquire(vpb); + } + else if (format == AX_FORMAT_ADPCM) + { + // .adpcmData is left intact + AXResetSyncFlag(vpb, AX_SYNCFLAG_LOOPFLAG | AX_SYNCFLAG_LOOPOFFSET | AX_SYNCFLAG_ENDOFFSET | AX_SYNCFLAG_CURRENTOFFSET); + AXSetSyncFlag(vpb, AX_SYNCFLAG_OFFSETS); + AXVoiceProtection_Acquire(vpb); + } + else + { + cemu_assert_debug(false); + } + } + + void AXSetVoiceOffsets(AXVPB* vpb, AXPBOFFSET_t* pbOffset) + { + cemuLog_log(LogType::SoundAPI, fmt::format("AXSetVoiceOffsets() -> Format: {0:04x} Current: {1:08x} End: {2:08x} Loop: {3:08x}", _swapEndianU16(pbOffset->format), _swapEndianU32(pbOffset->currentOffset), _swapEndianU32(pbOffset->endOffset), _swapEndianU32(pbOffset->loopOffset))); + MPTR sampleBase = _swapEndianU32(pbOffset->samples); + if (sampleBase == MPTR_NULL) + { + forceLog_printf("AXSetVoiceOffsets(): Invalid sample address"); + cemu_assert_debug(false); + return; + } + memcpy(&vpb->offsets, pbOffset, sizeof(AXPBOFFSET_t)); + sampleBase = memory_virtualToPhysical(sampleBase); + uint16 format = _swapEndianU16(pbOffset->format); + + axOffsetsInternal_t voiceAddr; + if (format == AX_FORMAT_PCM8) + { + uint32 loopOffsetPtr = sampleBase + _swapEndianU32(pbOffset->loopOffset); + uint32 endOffsetPtr = sampleBase + _swapEndianU32(pbOffset->endOffset); + uint32 currentOffsetPtr = sampleBase + _swapEndianU32(pbOffset->currentOffset); + voiceAddr.format = _swapEndianU16(pbOffset->format); + voiceAddr.loopFlag = _swapEndianU16(pbOffset->loopFlag); + voiceAddr.loopOffsetPtrHigh = (loopOffsetPtr >> 16) & 0x1FFF; + voiceAddr.loopOffsetPtrLow = loopOffsetPtr & 0xFFFF; + voiceAddr.endOffsetPtrHigh = (endOffsetPtr >> 16) & 0x1FFF; + voiceAddr.endOffsetPtrLow = endOffsetPtr & 0xFFFF; + voiceAddr.currentOffsetPtrHigh = (currentOffsetPtr >> 16) & 0x1FFF; + voiceAddr.currentOffsetPtrLow = currentOffsetPtr & 0xFFFF; + voiceAddr.ptrHighExtension = (currentOffsetPtr >> 29); + // convert to big endian + voiceAddr.format = _swapEndianU16(voiceAddr.format); + voiceAddr.loopFlag = _swapEndianU16(voiceAddr.loopFlag); + voiceAddr.loopOffsetPtrHigh = _swapEndianU16(voiceAddr.loopOffsetPtrHigh); + voiceAddr.loopOffsetPtrLow = _swapEndianU16(voiceAddr.loopOffsetPtrLow); + voiceAddr.endOffsetPtrHigh = _swapEndianU16(voiceAddr.endOffsetPtrHigh); + voiceAddr.endOffsetPtrLow = _swapEndianU16(voiceAddr.endOffsetPtrLow); + voiceAddr.currentOffsetPtrHigh = _swapEndianU16(voiceAddr.currentOffsetPtrHigh); + voiceAddr.currentOffsetPtrLow = _swapEndianU16(voiceAddr.currentOffsetPtrLow); + voiceAddr.ptrHighExtension = _swapEndianU16(voiceAddr.ptrHighExtension); + __AXSetVoiceAddr(vpb, &voiceAddr); + } + else if (format == AX_FORMAT_PCM16) + { + uint32 loopOffsetPtr = sampleBase / 2 + _swapEndianU32(pbOffset->loopOffset); + uint32 endOffsetPtr = sampleBase / 2 + _swapEndianU32(pbOffset->endOffset); + uint32 currentOffset = _swapEndianU32(pbOffset->currentOffset); + uint32 currentOffsetPtr = sampleBase / 2 + currentOffset; + voiceAddr.format = _swapEndianU16(pbOffset->format); + voiceAddr.loopFlag = _swapEndianU16(pbOffset->loopFlag); + voiceAddr.loopOffsetPtrHigh = (loopOffsetPtr >> 16) & 0x0FFF; + voiceAddr.loopOffsetPtrLow = loopOffsetPtr & 0xFFFF; + voiceAddr.endOffsetPtrHigh = (endOffsetPtr >> 16) & 0x0FFF; + voiceAddr.endOffsetPtrLow = endOffsetPtr & 0xFFFF; + voiceAddr.currentOffsetPtrHigh = (currentOffsetPtr >> 16) & 0x0FFF; + voiceAddr.currentOffsetPtrLow = currentOffsetPtr & 0xFFFF; + voiceAddr.ptrHighExtension = ((sampleBase + currentOffset * 2) >> 29); + // convert to big endian + voiceAddr.format = _swapEndianU16(voiceAddr.format); + voiceAddr.loopFlag = _swapEndianU16(voiceAddr.loopFlag); + voiceAddr.loopOffsetPtrHigh = _swapEndianU16(voiceAddr.loopOffsetPtrHigh); + voiceAddr.loopOffsetPtrLow = _swapEndianU16(voiceAddr.loopOffsetPtrLow); + voiceAddr.endOffsetPtrHigh = _swapEndianU16(voiceAddr.endOffsetPtrHigh); + voiceAddr.endOffsetPtrLow = _swapEndianU16(voiceAddr.endOffsetPtrLow); + voiceAddr.currentOffsetPtrHigh = _swapEndianU16(voiceAddr.currentOffsetPtrHigh); + voiceAddr.currentOffsetPtrLow = _swapEndianU16(voiceAddr.currentOffsetPtrLow); + voiceAddr.ptrHighExtension = _swapEndianU16(voiceAddr.ptrHighExtension); + __AXSetVoiceAddr(vpb, &voiceAddr); + } + else if (format == AX_FORMAT_ADPCM) + { + uint32 loopOffsetPtr = sampleBase * 2 + _swapEndianU32(pbOffset->loopOffset); + uint32 endOffsetPtr = sampleBase * 2 + _swapEndianU32(pbOffset->endOffset); + uint32 currentOffset = _swapEndianU32(pbOffset->currentOffset); + uint32 currentOffsetPtr = sampleBase * 2 + currentOffset; + voiceAddr.format = _swapEndianU16(pbOffset->format); + voiceAddr.loopFlag = _swapEndianU16(pbOffset->loopFlag); + voiceAddr.loopOffsetPtrHigh = (loopOffsetPtr >> 16) & 0x3FFF; + voiceAddr.loopOffsetPtrLow = loopOffsetPtr & 0xFFFF; + voiceAddr.endOffsetPtrHigh = (endOffsetPtr >> 16) & 0x3FFF; + voiceAddr.endOffsetPtrLow = endOffsetPtr & 0xFFFF; + voiceAddr.currentOffsetPtrHigh = (currentOffsetPtr >> 16) & 0x3FFF; + voiceAddr.currentOffsetPtrLow = currentOffsetPtr & 0xFFFF; + voiceAddr.ptrHighExtension = ((sampleBase + currentOffset / 2) >> 29); + // convert to big endian + voiceAddr.format = _swapEndianU16(voiceAddr.format); + voiceAddr.loopFlag = _swapEndianU16(voiceAddr.loopFlag); + voiceAddr.loopOffsetPtrHigh = _swapEndianU16(voiceAddr.loopOffsetPtrHigh); + voiceAddr.loopOffsetPtrLow = _swapEndianU16(voiceAddr.loopOffsetPtrLow); + voiceAddr.endOffsetPtrHigh = _swapEndianU16(voiceAddr.endOffsetPtrHigh); + voiceAddr.endOffsetPtrLow = _swapEndianU16(voiceAddr.endOffsetPtrLow); + voiceAddr.currentOffsetPtrHigh = _swapEndianU16(voiceAddr.currentOffsetPtrHigh); + voiceAddr.currentOffsetPtrLow = _swapEndianU16(voiceAddr.currentOffsetPtrLow); + voiceAddr.ptrHighExtension = _swapEndianU16(voiceAddr.ptrHighExtension); + __AXSetVoiceAddr(vpb, &voiceAddr); + } + else + { + cemu_assert_debug(false); + } + } + + void AXSetVoiceOffsetsEx(AXVPB* vpb, AXPBOFFSET_t* pbOffset, void* sampleBase) + { + // used by F1 Racing + cemu_assert(vpb != NULL && sampleBase != MPTR_NULL); + + AXPBOFFSET_t tmpOffsets = *pbOffset; + tmpOffsets.samples = _swapEndianU32(memory_getVirtualOffsetFromPointer(sampleBase)); + AXSetVoiceOffsets(vpb, &tmpOffsets); + } + + void AXSetVoiceSamplesAddr(AXVPB* vpb, void* sampleBase) + { + vpb->offsets.samples = _swapEndianU32(memory_getVirtualOffsetFromPointer(sampleBase)); + AXGetVoiceOffsets(vpb, &vpb->offsets); + } + + void AXGetVoiceOffsets(AXVPB* vpb, AXPBOFFSET_t* pbOffset) + { + AXVPBInternal_t* internal = __AXVPBInternalVoiceArray + (sint32)vpb->index; + + memcpy(pbOffset, &vpb->offsets, sizeof(AXPBOFFSET_t)); + MPTR sampleBase = _swapEndianU32(vpb->offsets.samples); + sampleBase = memory_virtualToPhysical(sampleBase); + uint16 format = _swapEndianU16(pbOffset->format); + uint32 loopOffsetPtrLow = _swapEndianU16(internal->internalOffsets.loopOffsetPtrLow); + uint32 loopOffsetPtrHigh = _swapEndianU16(internal->internalOffsets.loopOffsetPtrHigh); + uint32 endOffsetPtrLow = _swapEndianU16(internal->internalOffsets.endOffsetPtrLow); + uint32 endOffsetPtrHigh = _swapEndianU16(internal->internalOffsets.endOffsetPtrHigh); + uint32 currentOffsetPtrLow = _swapEndianU16(internal->internalOffsets.currentOffsetPtrLow); + uint32 currentOffsetPtrHigh = _swapEndianU16(internal->internalOffsets.currentOffsetPtrHigh); + uint32 ptrHighExtension = _swapEndianU16(internal->internalOffsets.ptrHighExtension); + + if (format == AX_FORMAT_PCM8) + { + uint32 loopOffset = (loopOffsetPtrLow | (loopOffsetPtrHigh << 16) | (ptrHighExtension << 29)) - sampleBase; + uint32 endOffset = (endOffsetPtrLow | (endOffsetPtrHigh << 16) | (ptrHighExtension << 29)) - sampleBase; + uint32 currentOffset = (currentOffsetPtrLow | (currentOffsetPtrHigh << 16) | (ptrHighExtension << 29)) - sampleBase; + + pbOffset->loopOffset = _swapEndianU32(loopOffset); + pbOffset->endOffset = _swapEndianU32(endOffset); + pbOffset->currentOffset = _swapEndianU32(currentOffset); + } + else if (format == AX_FORMAT_PCM16) + { + uint32 loopOffset = (loopOffsetPtrLow | (loopOffsetPtrHigh << 16) | ((ptrHighExtension << 29) / 2)) - sampleBase / 2; + uint32 endOffset = (endOffsetPtrLow | (endOffsetPtrHigh << 16) | ((ptrHighExtension << 29) / 2)) - sampleBase / 2; + uint32 currentOffset = (currentOffsetPtrLow | (currentOffsetPtrHigh << 16) | ((ptrHighExtension << 29) / 2)) - sampleBase / 2; + + pbOffset->loopOffset = _swapEndianU32(loopOffset); + pbOffset->endOffset = _swapEndianU32(endOffset); + pbOffset->currentOffset = _swapEndianU32(currentOffset); + } + else if (format == AX_FORMAT_ADPCM) + { + uint32 loopOffset = (loopOffsetPtrLow | (loopOffsetPtrHigh << 16) | ((ptrHighExtension << 29) * 2)) - sampleBase * 2; + uint32 endOffset = (endOffsetPtrLow | (endOffsetPtrHigh << 16) | ((ptrHighExtension << 29) * 2)) - sampleBase * 2; + uint32 currentOffset = (currentOffsetPtrLow | (currentOffsetPtrHigh << 16) | ((ptrHighExtension << 29) * 2)) - sampleBase * 2; + + pbOffset->loopOffset = _swapEndianU32(loopOffset); + pbOffset->endOffset = _swapEndianU32(endOffset); + pbOffset->currentOffset = _swapEndianU32(currentOffset); + } + else + { + cemu_assert_debug(false); + } + sndApiLog_printf("Retrieved voice offsets for voice %08x - base %08x current %08x loopFlag %04x loop %08x end %08x", memory_getVirtualOffsetFromPointer(vpb), _swapEndianU32(pbOffset->samples), _swapEndianU32(pbOffset->currentOffset), _swapEndianU16(pbOffset->loopFlag), _swapEndianU32(pbOffset->loopOffset), _swapEndianU32(pbOffset->endOffset)); + } + + void AXGetVoiceOffsetsEx(AXVPB* vpb, AXPBOFFSET_t* pbOffset, MPTR sampleBase) + { + cemu_assert(vpb != NULL && sampleBase != MPTR_NULL); + + vpb->offsets.samples = _swapEndianU32(sampleBase); + AXGetVoiceOffsets(vpb, pbOffset); + memcpy(&vpb->offsets, pbOffset, sizeof(AXPBOFFSET_t)); + } + + void AXSetVoiceCurrentOffset(AXVPB* vpb, uint32 currentOffset) + { + // untested + AXVPBInternal_t* internal = __AXVPBInternalVoiceArray + (sint32)(vpb->index); + MPTR sampleBase = memory_virtualToPhysical(_swapEndianU32(vpb->offsets.samples)); + vpb->offsets.currentOffset = _swapEndianU32(currentOffset); + uint16 voiceFormat = _swapEndianU16(internal->internalOffsets.format); + uint32 currentOffsetPtr; + sampleBase &= 0x1FFFFFFF; + if (voiceFormat == AX_FORMAT_PCM8) + { + // calculate offset (in bytes) + currentOffsetPtr = sampleBase + currentOffset; + } + else if (voiceFormat == AX_FORMAT_PCM16) + { + // calculate offset (in shorts) + currentOffsetPtr = sampleBase / 2 + currentOffset; + } + else if (voiceFormat == AX_FORMAT_ADPCM) + { + // calculate offset (in nibbles) + currentOffsetPtr = sampleBase * 2 + currentOffset; + } + else + { + cemu_assert_debug(false); + } + internal->internalOffsets.currentOffsetPtrHigh = _swapEndianU16(currentOffsetPtr >> 16); + internal->internalOffsets.currentOffsetPtrLow = _swapEndianU16(currentOffsetPtr & 0xFFFF); + AXSetSyncFlag(vpb, AX_SYNCFLAG_CURRENTOFFSET); + AXVoiceProtection_Acquire(vpb); + } + + void AXSetVoiceLoopOffset(AXVPB* vpb, uint32 loopOffset) + { + AXVPBInternal_t* internal = __AXVPBInternalVoiceArray + (sint32)(vpb->index); + MPTR sampleBase = memory_virtualToPhysical(_swapEndianU32(vpb->offsets.samples)); + vpb->offsets.loopOffset = _swapEndianU32(loopOffset); + uint16 voiceFormat = _swapEndianU16(internal->internalOffsets.format); + uint32 loopOffsetPtr; + sampleBase &= 0x1FFFFFFF; + if (voiceFormat == AX_FORMAT_PCM8) + { + // calculate offset (in bytes) + loopOffsetPtr = sampleBase + loopOffset; + } + else if (voiceFormat == AX_FORMAT_PCM16) + { + // calculate offset (in shorts) + loopOffsetPtr = sampleBase / 2 + loopOffset; + } + else if (voiceFormat == AX_FORMAT_ADPCM) + { + // calculate offset (in nibbles) + loopOffsetPtr = sampleBase * 2 + loopOffset; + } + else + { + cemu_assert_debug(false); + } + internal->internalOffsets.loopOffsetPtrHigh = _swapEndianU16(loopOffsetPtr >> 16); + internal->internalOffsets.loopOffsetPtrLow = _swapEndianU16(loopOffsetPtr & 0xFFFF); + AXSetSyncFlag(vpb, AX_SYNCFLAG_LOOPOFFSET); + AXVoiceProtection_Acquire(vpb); + } + + void AXSetVoiceEndOffset(AXVPB* vpb, uint32 endOffset) + { + // untested + AXVPBInternal_t* internal = __AXVPBInternalVoiceArray + (sint32)(vpb->index); + MPTR sampleBase = memory_virtualToPhysical(_swapEndianU32(vpb->offsets.samples)); + vpb->offsets.endOffset = _swapEndianU32(endOffset); + uint16 voiceFormat = _swapEndianU16(internal->internalOffsets.format); + uint32 endOffsetPtr; + sampleBase &= 0x1FFFFFFF; + if (voiceFormat == AX_FORMAT_PCM8) + { + // calculate offset (in bytes) + endOffsetPtr = sampleBase + endOffset; + } + else if (voiceFormat == AX_FORMAT_PCM16) + { + // calculate offset (in shorts) + endOffsetPtr = sampleBase / 2 + endOffset; + } + else if (voiceFormat == AX_FORMAT_ADPCM) + { + // calculate offset (in nibbles) + endOffsetPtr = sampleBase * 2 + endOffset; + } + else + { + cemu_assert_debug(false); + } + internal->internalOffsets.endOffsetPtrHigh = _swapEndianU16(endOffsetPtr >> 16); + internal->internalOffsets.endOffsetPtrLow = _swapEndianU16(endOffsetPtr & 0xFFFF); + AXSetSyncFlag(vpb, AX_SYNCFLAG_ENDOFFSET); + AXVoiceProtection_Acquire(vpb); + } + + void AXSetVoiceCurrentOffsetEx(AXVPB* vpb, uint32 currentOffset, MPTR sampleBase) + { + cemu_assert(vpb != NULL && sampleBase != MPTR_NULL); + AXPBOFFSET_t pbOffset; + vpb->offsets.samples = _swapEndianU32(sampleBase); + AXGetVoiceOffsets(vpb, &pbOffset); + AXSetVoiceCurrentOffset(vpb, currentOffset); + } + + void AXSetVoiceLoopOffsetEx(AXVPB* vpb, uint32 loopOffset, MPTR sampleBase) + { + cemu_assert(vpb != NULL && sampleBase != MPTR_NULL); + AXPBOFFSET_t pbOffset; + vpb->offsets.samples = _swapEndianU32(sampleBase); + AXGetVoiceOffsets(vpb, &pbOffset); + AXSetVoiceLoopOffset(vpb, loopOffset); + } + + void AXSetVoiceEndOffsetEx(AXVPB* vpb, uint32 endOffset, MPTR sampleBase) + { + cemu_assert(vpb != NULL && sampleBase != MPTR_NULL); + AXPBOFFSET_t pbOffset; + vpb->offsets.samples = _swapEndianU32(sampleBase); + AXGetVoiceOffsets(vpb, &pbOffset); + AXSetVoiceEndOffset(vpb, endOffset); + } + + uint32 AXGetVoiceCurrentOffsetEx(AXVPB* vpb, MPTR sampleBase) + { + cemu_assert(vpb != NULL && sampleBase != MPTR_NULL); + AXPBOFFSET_t pbOffset; + AXGetVoiceOffsetsEx(vpb, &pbOffset, sampleBase); + return _swapEndianU32(pbOffset.currentOffset); + } + + void AXSetVoiceLoop(AXVPB* vpb, uint16 loopState) + { + AXVPBInternal_t* internal = __AXVPBInternalVoiceArray + (sint32)(vpb->index); + vpb->offsets.loopFlag = _swapEndianU16(loopState); + internal->internalOffsets.loopFlag = _swapEndianU16(loopState); + AXSetSyncFlag(vpb, AX_SYNCFLAG_LOOPFLAG); + AXVoiceProtection_Acquire(vpb); + } + + uint32 vpbLoopTracker_loopCount[AX_MAX_VOICES]; + uint32 vpbLoopTracker_prevCurrentOffset[AX_MAX_VOICES]; + + void AXResetVoiceLoopCount(AXVPB* vpb) + { + if (!vpb) + return; + uint32 voiceIndex = vpb->index; + vpbLoopTracker_loopCount[voiceIndex] = 0; + vpbLoopTracker_prevCurrentOffset[voiceIndex] = 0; + } + + sint32 AXGetVoiceLoopCount(AXVPB* vpb) + { + if (!vpb) + return 0; + uint32 voiceIndex = vpb->index; + AXVPBInternal_t* internal = __AXVPBInternalVoiceArray + voiceIndex; + + uint32 loopOffset = internal->internalOffsets.GetLoopOffset32(); + uint32 endOffset = internal->internalOffsets.GetEndOffset32(); + uint32 currentOffset = internal->internalOffsets.GetCurrentOffset32(); + + uint32 srcRatio; + if (internal->srcFilterMode == AX_FILTER_MODE_NONE) + srcRatio = 0x10000; + else + srcRatio = internal->src.GetSrcRatio32(); + + uint32 loopLength = 0; + if (srcRatio != 0) + loopLength = ((endOffset - loopOffset) * 0x10000) / srcRatio; + + uint32 prevCurrentOffset = vpbLoopTracker_prevCurrentOffset[voiceIndex]; + uint32 voiceSamplesPerFrame = AXGetInputSamplesPerFrame(); + + if (loopOffset < endOffset && srcRatio != 0 && loopLength <= voiceSamplesPerFrame) + { + // handle loops shorter than one frame + uint32 loopsInFrame = ((prevCurrentOffset - loopOffset) + voiceSamplesPerFrame) / loopLength; + vpbLoopTracker_loopCount[voiceIndex] += loopsInFrame; + } + else // loopOffset >= endOffset + { + if (prevCurrentOffset <= endOffset) // loop could only happen if playback cursor was before end + { + if (loopOffset > endOffset && currentOffset >= loopOffset) + vpbLoopTracker_loopCount[voiceIndex]++; + else if (currentOffset < prevCurrentOffset && currentOffset >= loopOffset) + vpbLoopTracker_loopCount[voiceIndex]++; + } + } + vpbLoopTracker_prevCurrentOffset[voiceIndex] = currentOffset; + return vpbLoopTracker_loopCount[voiceIndex]; + } + + void Initialize() + { + // snd_core + cafeExportRegister("snd_core", AXSetVoiceDeviceMix, LogType::SoundAPI); + cafeExportRegister("snd_core", AXComputeLpfCoefs, LogType::SoundAPI); + + cafeExportRegister("snd_core", AXSetVoiceState, LogType::SoundAPI); + cafeExportRegister("snd_core", AXSetVoiceType, LogType::SoundAPI); + cafeExportRegister("snd_core", AXSetVoiceAdpcmLoop, LogType::SoundAPI); + cafeExportRegister("snd_core", AXSetVoiceSrc, LogType::SoundAPI); + cafeExportRegister("snd_core", AXSetVoiceSrcType, LogType::SoundAPI); + cafeExportRegister("snd_core", AXSetVoiceSrcRatio, LogType::SoundAPI); + cafeExportRegister("snd_core", AXSetVoiceVe, LogType::SoundAPI); + cafeExportRegister("snd_core", AXSetVoiceAdpcm, LogType::SoundAPI); + cafeExportRegister("snd_core", AXSetVoiceLoop, LogType::SoundAPI); + cafeExportRegister("snd_core", AXSetVoiceLpf, LogType::SoundAPI); + cafeExportRegister("snd_core", AXSetVoiceLpfCoefs, LogType::SoundAPI); + cafeExportRegister("snd_core", AXSetVoiceBiquad, LogType::SoundAPI); + cafeExportRegister("snd_core", AXSetVoiceBiquadCoefs, LogType::SoundAPI); + cafeExportRegister("snd_core", AXSetVoiceOffsets, LogType::SoundAPI); + cafeExportRegister("snd_core", AXSetVoiceOffsetsEx, LogType::SoundAPI); + cafeExportRegister("snd_core", AXSetVoiceCurrentOffset, LogType::SoundAPI); + cafeExportRegister("snd_core", AXSetVoiceCurrentOffsetEx, LogType::SoundAPI); + cafeExportRegister("snd_core", AXSetVoiceLoopOffset, LogType::SoundAPI); + cafeExportRegister("snd_core", AXSetVoiceLoopOffsetEx, LogType::SoundAPI); + cafeExportRegister("snd_core", AXSetVoiceEndOffset, LogType::SoundAPI); + cafeExportRegister("snd_core", AXSetVoiceEndOffsetEx, LogType::SoundAPI); + cafeExportRegister("snd_core", AXSetVoiceSamplesAddr, LogType::SoundAPI); + + cafeExportRegister("snd_core", AXIsVoiceRunning, LogType::SoundAPI); + cafeExportRegister("snd_core", AXGetVoiceLoopCount, LogType::SoundAPI); + cafeExportRegister("snd_core", AXGetVoiceOffsets, LogType::SoundAPI); + cafeExportRegister("snd_core", AXGetVoiceCurrentOffsetEx, LogType::SoundAPI); + + // sndcore2 + cafeExportRegister("sndcore2", AXSetVoiceDeviceMix, LogType::SoundAPI); + cafeExportRegister("sndcore2", AXComputeLpfCoefs, LogType::SoundAPI); + + cafeExportRegister("sndcore2", AXSetVoiceState, LogType::SoundAPI); + cafeExportRegister("sndcore2", AXSetVoiceType, LogType::SoundAPI); + cafeExportRegister("sndcore2", AXSetVoiceAdpcmLoop, LogType::SoundAPI); + cafeExportRegister("sndcore2", AXSetVoiceSrc, LogType::SoundAPI); + cafeExportRegister("sndcore2", AXSetVoiceSrcType, LogType::SoundAPI); + cafeExportRegister("sndcore2", AXSetVoiceSrcRatio, LogType::SoundAPI); + cafeExportRegister("sndcore2", AXSetVoiceVe, LogType::SoundAPI); + cafeExportRegister("sndcore2", AXSetVoiceAdpcm, LogType::SoundAPI); + cafeExportRegister("sndcore2", AXSetVoiceLoop, LogType::SoundAPI); + cafeExportRegister("sndcore2", AXSetVoiceLpf, LogType::SoundAPI); + cafeExportRegister("sndcore2", AXSetVoiceLpfCoefs, LogType::SoundAPI); + cafeExportRegister("sndcore2", AXSetVoiceBiquad, LogType::SoundAPI); + cafeExportRegister("sndcore2", AXSetVoiceBiquadCoefs, LogType::SoundAPI); + cafeExportRegister("sndcore2", AXSetVoiceOffsets, LogType::SoundAPI); + cafeExportRegister("sndcore2", AXSetVoiceOffsetsEx, LogType::SoundAPI); + cafeExportRegister("sndcore2", AXSetVoiceCurrentOffset, LogType::SoundAPI); + cafeExportRegister("sndcore2", AXSetVoiceCurrentOffsetEx, LogType::SoundAPI); + cafeExportRegister("sndcore2", AXSetVoiceLoopOffset, LogType::SoundAPI); + cafeExportRegister("sndcore2", AXSetVoiceLoopOffsetEx, LogType::SoundAPI); + cafeExportRegister("sndcore2", AXSetVoiceEndOffset, LogType::SoundAPI); + cafeExportRegister("sndcore2", AXSetVoiceEndOffsetEx, LogType::SoundAPI); + cafeExportRegister("sndcore2", AXSetVoiceSamplesAddr, LogType::SoundAPI); + + cafeExportRegister("sndcore2", AXIsVoiceRunning, LogType::SoundAPI); + cafeExportRegister("sndcore2", AXGetVoiceLoopCount, LogType::SoundAPI); + cafeExportRegister("sndcore2", AXGetVoiceOffsets, LogType::SoundAPI); + cafeExportRegister("sndcore2", AXGetVoiceCurrentOffsetEx, LogType::SoundAPI); + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/snd_user/snd_user.cpp b/src/Cafe/OS/libs/snd_user/snd_user.cpp new file mode 100644 index 00000000..f8a567ce --- /dev/null +++ b/src/Cafe/OS/libs/snd_user/snd_user.cpp @@ -0,0 +1,1251 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/libs/snd_user/snd_user.h" +#include "Cafe/OS/libs/snd_core/ax.h" +#include "Cafe/OS/libs/coreinit/coreinit_MEM.h" +#include "Cafe/HW/Espresso/PPCCallback.h" +#include "Cafe/OS/RPL/rpl.h" + +using namespace snd_core; + +namespace snd +{ + namespace user + { +#define AX_MAX_NUM_DRC (2) +#define AX_MAX_NUM_RMT (4) + +#define AX_UPDATE_MODE_10000000 (0x10000000) +#define AX_UPDATE_MODE_20000000 (0x20000000) +#define AX_UPDATE_MODE_40000000_VOLUME (0x40000000) +#define AX_UPDATE_MODE_50000000 (0x50000000) +#define AX_UPDATE_MODE_40000007 (0x40000007) +#define AX_UPDATE_MODE_80000000 (0x80000000) + + struct VolumeData + { + sint16 volume; // 0x00 + sint16 volume_target; // 0x02 + }; + static_assert(sizeof(VolumeData) == 0x4, "sizeof(VolumeData)"); + + struct MixControl + { + sint16 aux[AX_AUX_BUS_COUNT]; + sint16 pan; + sint16 span; + sint16 fader; + sint16 lfe; + }; + static_assert(sizeof(MixControl) == 0xE, "sizeof(MixControl)"); + + using MixMode = uint32_t; + + struct MixChannel + { + MEMPTR<AXVPB> voice; + + uint32 update_mode; + sint16 input_level; + VolumeData volume; + + MixControl tv_control; + sint16 tv_channels[AX_TV_CHANNEL_COUNT]; + VolumeData tv_volume[AX_MAX_NUM_BUS][AX_TV_CHANNEL_COUNT]; + MixMode tv_mode; + + MixControl drc_control[AX_MAX_NUM_DRC]; + sint16 drc_channels[AX_MAX_NUM_DRC][AX_DRC_CHANNEL_COUNT]; + VolumeData drc_volume[AX_MAX_NUM_DRC][AX_MAX_NUM_BUS][AX_DRC_CHANNEL_COUNT]; + MixMode drc_mode[AX_MAX_NUM_DRC]; + + MixControl rmt_control[AX_MAX_NUM_RMT]; + sint16 rmt_channels[AX_MAX_NUM_RMT][AX_RMT_CHANNEL_COUNT]; + VolumeData rmt_volume[AX_MAX_NUM_RMT][AX_MAX_NUM_BUS][AX_RMT_CHANNEL_COUNT]; + MixMode rmt_mode[AX_MAX_NUM_RMT]; + + MixControl& GetMixControl(uint32 device, uint32 deviceIndex) + { + if (device == AX_DEV_TV) + { + cemu_assert(deviceIndex == 0); + return tv_control; + } + else if (device == AX_DEV_DRC) + { + cemu_assert(deviceIndex < AX_MAX_NUM_DRC); + return drc_control[deviceIndex]; + } + else if (device == AX_DEV_RMT) + { + cemu_assert(deviceIndex < AX_MAX_NUM_RMT); + return rmt_control[deviceIndex]; + } + cemuLog_log(LogType::Force, "GetMixControl({}, {}): Invalid device/deviceIndex", device, deviceIndex); + cemu_assert(false); + return tv_control; + } + + MixMode& GetMode(uint32 device, uint32 deviceIndex) + { + if (device == AX_DEV_TV) + { + cemu_assert(deviceIndex == 0); + return tv_mode; + } + else if (device == AX_DEV_DRC) + { + cemu_assert(deviceIndex < AX_MAX_NUM_DRC); + return drc_mode[deviceIndex]; + } + else if (device == AX_DEV_RMT) + { + cemu_assert(deviceIndex < AX_MAX_NUM_RMT); + return rmt_mode[deviceIndex]; + } + cemuLog_log(LogType::Force, "GetMode({}, {}): Invalid device/deviceIndex", device, deviceIndex); + cemu_assert(false); + return tv_mode; + } + + sint16* GetChannels(uint32 device, uint32 deviceIndex) + { + if (device == AX_DEV_TV) + { + cemu_assert(deviceIndex == 0); + return tv_channels; + } + else if (device == AX_DEV_DRC) + { + cemu_assert(deviceIndex < AX_MAX_NUM_DRC); + return drc_channels[deviceIndex]; + } + else if (device == AX_DEV_RMT) + { + cemu_assert(deviceIndex < AX_MAX_NUM_RMT); + return rmt_channels[deviceIndex]; + } + cemuLog_log(LogType::Force, "GetChannels({}, {}): Invalid device/deviceIndex", device, deviceIndex); + cemu_assert(false); + return tv_channels; + } + }; + static_assert(sizeof(MixChannel) == 0x1D0, "sizeof(MixChannel)"); + + struct DeviceInfo + { + uint32 tv_sound_mode; // 0x00 + uint32 drc_sound_mode; // 0x04 + uint32 rmt_sound_mode; // 0x08 + }; + + struct snd_user_data_t + { + bool initialized; + + DeviceInfo device_info; + sint32 max_voices; + + MixChannel mix_channel[AX_MAX_VOICES]; + + const uint16 volume[0x388 + 0x3C + 1] = + { + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0xA, 0xA, 0xA, 0xA, 0xA, 0xA, 0xA, 0xA, 0xA, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xC, + 0xC, 0xC, 0xC, 0xC, 0xC, 0xC, 0xD, 0xD, 0xD, 0xD, 0xD, 0xD, 0xD, 0xE, 0xE, 0xE, 0xE, 0xE, 0xE, 0xF, 0xF, 0xF, 0xF, 0xF, 0x10, 0x10, 0x10, 0x10, 0x10, 0x11, 0x11, 0x11, 0x11, 0x11, 0x12, 0x12, 0x12, 0x12, 0x12, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x14, 0x14, 0x14, 0x14, 0x15, 0x15, 0x15, 0x15, 0x16, 0x16, 0x16, 0x16, 0x17, 0x17, 0x17, 0x18, 0x18, 0x18, 0x18, 0x19, 0x19, 0x19, 0x1A, 0x1A, 0x1A, 0x1A, 0x1B, 0x1B, 0x1B, 0x1C, 0x1C, 0x1C, 0x1D, 0x1D, 0x1D, 0x1E, + 0x1E, 0x1E, 0x1F, 0x1F, 0x20, 0x20, 0x20, 0x21, 0x21, 0x21, 0x22, 0x22, 0x23, 0x23, 0x23, 0x24, 0x24, 0x25, 0x25, 0x26, 0x26, 0x26, 0x27, 0x27, 0x28, 0x28, 0x29, 0x29, 0x2A, 0x2A, 0x2B, 0x2B, 0x2C, 0x2C, 0x2D, 0x2D, 0x2E, 0x2E, + 0x2F, 0x2F, 0x30, 0x31, 0x31, 0x32, 0x32, 0x33, 0x33, 0x34, 0x35, 0x35, 0x36, 0x37, 0x37, 0x38, 0x38, 0x39, 0x3A, 0x3A, 0x3B, 0x3C, 0x3D, 0x3D, 0x3E, 0x3F, 0x3F, 0x40, 0x41, 0x42, 0x42, 0x43, 0x44, 0x45, 0x46, 0x46, 0x47, 0x48, + 0x49, 0x4A, 0x4B, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x64, 0x65, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x6D, 0x6F, 0x70, + 0x71, 0x72, 0x74, 0x75, 0x76, 0x78, 0x79, 0x7B, 0x7C, 0x7E, 0x7F, 0x80, 0x82, 0x83, 0x85, 0x87, 0x88, 0x8A, 0x8B, 0x8D, 0x8F, 0x90, 0x92, 0x94, 0x95, 0x97, 0x99, 0x9B, 0x9C, 0x9E, 0xA0, 0xA2, 0xA4, 0xA6, 0xA8, 0xAA, 0xAB, 0xAD, + 0xAF, 0xB2, 0xB4, 0xB6, 0xB8, 0xBA, 0xBC, 0xBE, 0xC0, 0xC3, 0xC5, 0xC7, 0xCA, 0xCC, 0xCE, 0xD1, 0xD3, 0xD6, 0xD8, 0xDB, 0xDD, 0xE0, 0xE2, 0xE5, 0xE7, 0xEA, 0xED, 0xF0, 0xF2, 0xF5, 0xF8, 0xFB, 0xFE, 0x101, 0x104, 0x107, 0x10A, 0x10D, + 0x110, 0x113, 0x116, 0x11A, 0x11D, 0x120, 0x124, 0x127, 0x12A, 0x12E, 0x131, 0x135, 0x138, 0x13C, 0x140, 0x143, 0x147, 0x14B, 0x14F, 0x153, 0x157, 0x15B, 0x15F, 0x163, 0x167, 0x16B, 0x16F, 0x173, 0x178, 0x17C, 0x180, 0x185, 0x189, + 0x18E, 0x193, 0x197, 0x19C, 0x1A1, 0x1A6, 0x1AB, 0x1AF, 0x1B4, 0x1BA, 0x1BF, 0x1C4, 0x1C9, 0x1CE, 0x1D4, 0x1D9, 0x1DF, 0x1E4, 0x1EA, 0x1EF, 0x1F5, 0x1FB, 0x201, 0x207, 0x20D, 0x213, 0x219, 0x21F, 0x226, 0x22C, 0x232, 0x239, 0x240, + 0x246, 0x24D, 0x254, 0x25B, 0x262, 0x269, 0x270, 0x277, 0x27E, 0x286, 0x28D, 0x295, 0x29D, 0x2A4, 0x2AC, 0x2B4, 0x2BC, 0x2C4, 0x2CC, 0x2D5, 0x2DD, 0x2E6, 0x2EE, 0x2F7, 0x300, 0x309, 0x312, 0x31B, 0x324, 0x32D, 0x337, 0x340, 0x34A, + 0x354, 0x35D, 0x367, 0x371, 0x37C, 0x386, 0x390, 0x39B, 0x3A6, 0x3B1, 0x3BB, 0x3C7, 0x3D2, 0x3DD, 0x3E9, 0x3F4, 0x400, 0x40C, 0x418, 0x424, 0x430, 0x43D, 0x449, 0x456, 0x463, 0x470, 0x47D, 0x48A, 0x498, 0x4A5, 0x4B3, 0x4C1, 0x4CF, + 0x4DD, 0x4EC, 0x4FA, 0x509, 0x518, 0x527, 0x536, 0x546, 0x555, 0x565, 0x575, 0x586, 0x596, 0x5A6, 0x5B7, 0x5C8, 0x5D9, 0x5EB, 0x5FC, 0x60E, 0x620, 0x632, 0x644, 0x657, 0x66A, 0x67D, 0x690, 0x6A4, 0x6B7, 0x6CB, 0x6DF, 0x6F4, 0x708, + 0x71D, 0x732, 0x748, 0x75D, 0x773, 0x789, 0x79F, 0x7B6, 0x7CD, 0x7E4, 0x7FB, 0x813, 0x82B, 0x843, 0x85C, 0x874, 0x88E, 0x8A7, 0x8C1, 0x8DA, 0x8F5, 0x90F, 0x92A, 0x945, 0x961, 0x97D, 0x999, 0x9B5, 0x9D2, 0x9EF, 0xA0D, 0xA2A, 0xA48, + 0xA67, 0xA86, 0xAA5, 0xAC5, 0xAE5, 0xB05, 0xB25, 0xB47, 0xB68, 0xB8A, 0xBAC, 0xBCF, 0xBF2, 0xC15, 0xC39, 0xC5D, 0xC82, 0xCA7, 0xCCC, 0xCF2, 0xD19, 0xD3F, 0xD67, 0xD8E, 0xDB7, 0xDDF, 0xE08, 0xE32, 0xE5C, 0xE87, 0xEB2, 0xEDD, 0xF09, + 0xF36, 0xF63, 0xF91, 0xFBF, 0xFEE, 0x101D, 0x104D, 0x107D, 0x10AE, 0x10DF, 0x1111, 0x1144, 0x1177, 0x11AB, 0x11DF, 0x1214, 0x124A, 0x1280, 0x12B7, 0x12EE, 0x1326, 0x135F, 0x1399, 0x13D3, 0x140D, 0x1449, 0x1485, 0x14C2, 0x14FF, 0x153E, + 0x157D, 0x15BC, 0x15FD, 0x163E, 0x1680, 0x16C3, 0x1706, 0x174A, 0x178F, 0x17D5, 0x181C, 0x1863, 0x18AC, 0x18F5, 0x193F, 0x198A, 0x19D5, 0x1A22, 0x1A6F, 0x1ABE, 0x1B0D, 0x1B5D, 0x1BAE, 0x1C00, 0x1C53, 0x1CA7, 0x1CFC, 0x1D52, 0x1DA9, + 0x1E01, 0x1E5A, 0x1EB4, 0x1F0F, 0x1F6B, 0x1FC8, 0x2026, 0x2086, 0x20E6, 0x2148, 0x21AA, 0x220E, 0x2273, 0x22D9, 0x2341, 0x23A9, 0x2413, 0x247E, 0x24EA, 0x2557, 0x25C6, 0x2636, 0x26A7, 0x271A, 0x278E, 0x2803, 0x287A, 0x28F2, 0x296B, + 0x29E6, 0x2A62, 0x2AE0, 0x2B5F, 0x2BDF, 0x2C61, 0x2CE5, 0x2D6A, 0x2DF1, 0x2E79, 0x2F03, 0x2F8E, 0x301B, 0x30AA, 0x313A, 0x31CC, 0x325F, 0x32F5, 0x338C, 0x3425, 0x34BF, 0x355B, 0x35FA, 0x369A, 0x373C, 0x37DF, 0x3885, 0x392C, 0x39D6, + 0x3A81, 0x3B2F, 0x3BDE, 0x3C90, 0x3D43, 0x3DF9, 0x3EB1, 0x3F6A, 0x4026, 0x40E5, 0x41A5, 0x4268, 0x432C, 0x43F4, 0x44BD, 0x4589, 0x4657, 0x4727, 0x47FA, 0x48D0, 0x49A8, 0x4A82, 0x4B5F, 0x4C3E, 0x4D20, 0x4E05, 0x4EEC, 0x4FD6, 0x50C3, + 0x51B2, 0x52A4, 0x5399, 0x5491, 0x558C, 0x5689, 0x578A, 0x588D, 0x5994, 0x5A9D, 0x5BAA, 0x5CBA, 0x5DCD, 0x5EE3, 0x5FFC, 0x6119, 0x6238, 0x635C, 0x6482, 0x65AC, 0x66D9, 0x680A, 0x693F, 0x6A77, 0x6BB2, 0x6CF2, 0x6E35, 0x6F7B, 0x70C6, + 0x7214, 0x7366, 0x74BC, 0x7616, 0x7774, 0x78D6, 0x7A3D, 0x7BA7, 0x7D16, 0x7E88, 0x7FFF, 0x817B, 0x82FB, 0x847F, 0x8608, 0x8795, 0x8927, 0x8ABE, 0x8C59, 0x8DF9, 0x8F9E, 0x9148, 0x92F6, 0x94AA, 0x9663, 0x9820, 0x99E3, 0x9BAB, 0x9D79, + 0x9F4C, 0xA124, 0xA302, 0xA4E5, 0xA6CE, 0xA8BC, 0xAAB0, 0xACAA, 0xAEAA, 0xB0B0, 0xB2BC, 0xB4CE, 0xB6E5, 0xB904, 0xBB28, 0xBD53, 0xBF84, 0xC1BC, 0xC3FA, 0xC63F, 0xC88B, 0xCADD, 0xCD37, 0xCF97, 0xD1FE, 0xD46D, 0xD6E3, 0xD960, 0xDBE4, + 0xDE70, 0xE103, 0xE39E, 0xE641, 0xE8EB, 0xEB9E, 0xEE58, 0xF11B, 0xF3E6, 0xF6B9, 0xF994, 0xFC78, 0xFF64 + }; + + const uint32 pan_values[128] = + { + 00, 00, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFE, 0xFFFFFFFE, 0xFFFFFFFE, 0xFFFFFFFD, 0xFFFFFFFD, 0xFFFFFFFC, 0xFFFFFFFC, 0xFFFFFFFC, 0xFFFFFFFB, 0xFFFFFFFB, 0xFFFFFFFB, + 0xFFFFFFFA, 0xFFFFFFFA, 0xFFFFFFF9, 0xFFFFFFF9, 0xFFFFFFF9, 0xFFFFFFF8, 0xFFFFFFF8, 0xFFFFFFF7, 0xFFFFFFF7, 0xFFFFFFF6, 0xFFFFFFF6, 0xFFFFFFF6, 0xFFFFFFF5, 0xFFFFFFF5, + 0xFFFFFFF4, 0xFFFFFFF4, 0xFFFFFFF3, 0xFFFFFFF3, 0xFFFFFFF2, 0xFFFFFFF2, 0xFFFFFFF2, 0xFFFFFFF1, 0xFFFFFFF1, 0xFFFFFFF0, 0xFFFFFFF0, 0xFFFFFFEF, 0xFFFFFFEF, 0xFFFFFFEE, + 0xFFFFFFEE, 0xFFFFFFED, 0xFFFFFFEC, 0xFFFFFFEC, 0xFFFFFFEB, 0xFFFFFFEB, 0xFFFFFFEA, 0xFFFFFFEA, 0xFFFFFFE9, 0xFFFFFFE9, 0xFFFFFFE8, 0xFFFFFFE7, 0xFFFFFFE7, 0xFFFFFFE6, + 0xFFFFFFE6, 0xFFFFFFE5, 0xFFFFFFE4, 0xFFFFFFE4, 0xFFFFFFE3, 0xFFFFFFE2, 0xFFFFFFE2, 0xFFFFFFE1, 0xFFFFFFE0, 0xFFFFFFDF, 0xFFFFFFDF, 0xFFFFFFDE, 0xFFFFFFDD, 0xFFFFFFDC, + 0xFFFFFFDC, 0xFFFFFFDB, 0xFFFFFFDA, 0xFFFFFFD9, 0xFFFFFFD8, 0xFFFFFFD8, 0xFFFFFFD7, 0xFFFFFFD6, 0xFFFFFFD5, 0xFFFFFFD4, 0xFFFFFFD3, 0xFFFFFFD2, 0xFFFFFFD1, 0xFFFFFFD0, + 0xFFFFFFCF, 0xFFFFFFCE, 0xFFFFFFCD, 0xFFFFFFCC, 0xFFFFFFCA, 0xFFFFFFC9, 0xFFFFFFC8, 0xFFFFFFC7, 0xFFFFFFC5, 0xFFFFFFC4, 0xFFFFFFC3, 0xFFFFFFC1, 0xFFFFFFC0, 0xFFFFFFBE, + 0xFFFFFFBD, 0xFFFFFFBB, 0xFFFFFFB9, 0xFFFFFFB8, 0xFFFFFFB6, 0xFFFFFFB4, 0xFFFFFFB2, 0xFFFFFFB0, 0xFFFFFFAD, 0xFFFFFFAB, 0xFFFFFFA9, 0xFFFFFFA6, 0xFFFFFFA3, 0xFFFFFFA0, + 0xFFFFFF9D, 0xFFFFFF9A, 0xFFFFFF96, 0xFFFFFF92, 0xFFFFFF8D, 0xFFFFFF88, 0xFFFFFF82, 0xFFFFFF7B, 0xFFFFFF74, 0xFFFFFF6A, 0xFFFFFF5D, 0xFFFFFF4C, 0xFFFFFF2E, 0xFFFFFC78 + }; + + const uint16 pan_values_low[128] = + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFE, 0xFFFE, 0xFFFE, 0xFFFE, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFC, 0xFFFC, 0xFFFC, 0xFFFB, 0xFFFB, 0xFFFA, 0xFFFA, 0xFFFA, 0xFFF9, 0xFFF9, 0xFFF8, 0xFFF8, + 0xFFF7, 0xFFF7, 0xFFF6, 0xFFF5, 0xFFF5, 0xFFF4, 0xFFF4, 0xFFF3, 0xFFF2, 0xFFF2, 0xFFF1, 0xFFF0, 0xFFEF, 0xFFEF, 0xFFEE, 0xFFED, 0xFFEC, 0xFFEB, 0xFFEB, 0xFFEA, 0xFFE9, 0xFFE8, 0xFFE7, 0xFFE6, 0xFFE5, 0xFFE4, 0xFFE3, 0xFFE2, 0xFFE1, + 0xFFE0, 0xFFDE, 0xFFDD, 0xFFDC, 0xFFDB, 0xFFDA, 0xFFD8, 0xFFD7, 0xFFD6, 0xFFD4, 0xFFD3, 0xFFD1, 0xFFD0, 0xFFCE, 0xFFCC, 0xFFCB, 0xFFC9, 0xFFC7, 0xFFC6, 0xFFC4, 0xFFC2, 0xFFC0, 0xFFBE, 0xFFBC, 0xFFBA, 0xFFB7, 0xFFB5, 0xFFB3, 0xFFB0, + 0xFFAE, 0xFFAB, 0xFFA8, 0xFFA6, 0xFFA3, 0xFFA0, 0xFF9C, 0xFF99, 0xFF96, 0xFF92, 0xFF8E, 0xFF8A, 0xFF86, 0xFF82, 0xFF7D, 0xFF78, 0xFF73, 0xFF6E, 0xFF68, 0xFF61, 0xFF5A, 0xFF53, 0xFF4B, 0xFF42, 0xFF37, 0xFF2C, 0xFF1F, 0xFF0F, 0xFEFB, + 0xFEE2, 0xFEBF, 0xFE83, 0xFC40 + }; + + const uint16 pan_values_high[128] = + { + 0xFFC3, 0xFFC3, 0xFFC4, 0xFFC5, 0xFFC5, 0xFFC6, 0xFFC6, 0xFFC7, 0xFFC8, 0xFFC8, 0xFFC9, 0xFFC9, 0xFFCA, 0xFFCB, 0xFFCB, 0xFFCC, 0xFFCC, 0xFFCD, 0xFFCE, 0xFFCE, 0xFFCF, 0xFFCF, 0xFFD0, 0xFFD0, 0xFFD1, 0xFFD1, 0xFFD2, 0xFFD2, 0xFFD3, + 0xFFD3, 0xFFD4, 0xFFD4, 0xFFD5, 0xFFD5, 0xFFD6, 0xFFD6, 0xFFD7, 0xFFD7, 0xFFD8, 0xFFD8, 0xFFD9, 0xFFD9, 0xFFDA, 0xFFDA, 0xFFDA, 0xFFDB, 0xFFDB, 0xFFDC, 0xFFDC, 0xFFDD, 0xFFDD, 0xFFDD, 0xFFDE, 0xFFDE, 0xFFDF, 0xFFDF, 0xFFE0, 0xFFE0, + 0xFFE0, 0xFFE1, 0xFFE1, 0xFFE1, 0xFFE2, 0xFFE2, 0xFFE3, 0xFFE3, 0xFFE3, 0xFFE4, 0xFFE4, 0xFFE4, 0xFFE5, 0xFFE5, 0xFFE5, 0xFFE6, 0xFFE6, 0xFFE6, 0xFFE7, 0xFFE7, 0xFFE7, 0xFFE8, 0xFFE8, 0xFFE8, 0xFFE9, 0xFFE9, 0xFFE9, 0xFFEA, 0xFFEA, + 0xFFEA, 0xFFEB, 0xFFEB, 0xFFEB, 0xFFEC, 0xFFEC, 0xFFEC, 0xFFEC, 0xFFED, 0xFFED, 0xFFED, 0xFFEE, 0xFFEE, 0xFFEE, 0xFFEE, 0xFFEF, 0xFFEF, 0xFFEF, 0xFFEF, 0xFFF0, 0xFFF0, 0xFFF0, 0xFFF0, 0xFFF1, 0xFFF1, 0xFFF1, 0xFFF1, 0xFFF2, 0xFFF2, + 0xFFF2, 0xFFF2, 0xFFF3, 0xFFF3, 0xFFF3, 0xFFF3, 0xFFF3, 0xFFF4, 0xFFF4, 0xFFF4, 0xFFF4, 0xFFF5 + }; + + } g_snd_user_data{}; + + void _MIXChannelResetTV(MixChannel* channel, sint32 index) + { + assert(index == 0); + + channel->tv_mode = 0; + + channel->tv_control.pan = 0x40; + channel->tv_control.span = 0x7F; + channel->tv_control.fader = 0; + channel->tv_control.lfe = -960; + + for (size_t i = 0; i < AX_AUX_BUS_COUNT; ++i) + { + channel->tv_control.aux[i] = -960; + } + + for (size_t i = 0; i < AX_MAX_NUM_BUS; ++i) + { + for (size_t j = 0; j < AX_TV_CHANNEL_COUNT; ++j) + { + channel->tv_volume[i][j].volume = 0; + channel->tv_volume[i][j].volume_target = 0; + } + } + } + + void _MIXChannelResetDRC(MixChannel* channel, sint32 index) + { + assert(index < AX_MAX_NUM_DRC); + channel->drc_mode[index] = 0; + + channel->drc_control[index].pan = 0x40; + channel->drc_control[index].span = 0x7F; + channel->drc_control[index].fader = 0; + channel->drc_control[index].lfe = -960; + + for (size_t i = 0; i < AX_MAX_NUM_BUS; ++i) + { + channel->drc_control[index].aux[i] = -960; + } + + for (size_t i = 0; i < AX_MAX_NUM_BUS; ++i) + { + for (size_t j = 0; j < AX_DRC_CHANNEL_COUNT; ++j) + { + channel->drc_volume[index][i][j].volume = 0; + channel->drc_volume[index][i][j].volume_target = 0; + } + } + } + + void _MIXChannelResetRmt(MixChannel* channel, sint32 index) + { + assert(index < AX_MAX_NUM_RMT); + channel->rmt_mode[index] = 0; + + channel->rmt_control[index].pan = 0x40; + channel->rmt_control[index].span = 0x7F; + channel->rmt_control[index].fader = 0; + channel->rmt_control[index].lfe = -960; + + for (size_t i = 0; i < AX_MAX_NUM_BUS; ++i) + { + channel->rmt_control[index].aux[i] = -960; + } + + for (size_t i = 0; i < AX_MAX_NUM_BUS; ++i) + { + for (size_t j = 0; j < AX_RMT_CHANNEL_COUNT; ++j) + { + channel->rmt_volume[index][i][j].volume = 0; + channel->rmt_volume[index][i][j].volume_target = 0; + } + } + } + + void MIXResetChannelData(MixChannel* channel) + { + channel->update_mode = AX_UPDATE_MODE_50000000; + channel->input_level = 0; + channel->volume.volume = 0; + channel->volume.volume_target = 0; + + _MIXChannelResetTV(channel, 0); + + for (int i = 0; i < AX_MAX_NUM_DRC; ++i) + _MIXChannelResetDRC(channel, i); + + for (int i = 0; i < AX_MAX_NUM_RMT; ++i) + _MIXChannelResetRmt(channel, i); + } + + void _MIXControl_SetDevicePan(MixControl* control, int device_type, sint16 channels[]) + { + const auto pandiff = 0x7F - control->pan; + const auto spandiff = 0x7F - control->span; + + if (device_type == AX_DEV_TV) + { + const uint32 sound_mode = g_snd_user_data.device_info.tv_sound_mode; + if (sound_mode == 3) + { + channels[0] = g_snd_user_data.pan_values_low[control->pan]; + channels[1] = g_snd_user_data.pan_values_low[pandiff]; + channels[2] = g_snd_user_data.pan_values_high[pandiff]; + channels[3] = g_snd_user_data.pan_values_high[control->pan]; + channels[4] = g_snd_user_data.pan_values_high[spandiff]; + channels[5] = g_snd_user_data.pan_values_high[control->span]; + } + else if (sound_mode != 4) + { + channels[0] = g_snd_user_data.pan_values[control->pan]; + channels[1] = g_snd_user_data.pan_values[pandiff]; + channels[2] = 0; + channels[3] = 0; + channels[4] = g_snd_user_data.pan_values[spandiff]; + channels[5] = g_snd_user_data.pan_values[control->span]; + } + else + { + uint32 pan = 0x7F; + if (((uint32)control->pan >> 1) < 0x7F) + pan = (uint32)control->pan >> 1; + + channels[0] = g_snd_user_data.pan_values[pan] + g_snd_user_data.pan_values[spandiff]; + + uint32 span = 0x7F; + if (((uint32)pandiff >> 1) < 0x7E) + span = (uint32)pandiff >> 1; + + channels[1] = g_snd_user_data.pan_values[span] + g_snd_user_data.pan_values[spandiff]; + + // TODO + } + } + else if (device_type == AX_DEV_DRC) + { + // TODO + } + } + + sint16 __MIXTranslateVolume(sint16 input) + { + if (input <= -904) + return 0; + + if (input > 0x3C) + return -156; + + return (sint16)g_snd_user_data.volume[input + 903]; + } + + void AXFXInitDefaultHooks(); + + void MIXInit() + { + sndApiLog_printf("MIXInit()"); + + if (g_snd_user_data.initialized) + return; + + g_snd_user_data.max_voices = AX_MAX_VOICES; // AXGetMaxVoices(); + for (sint32 i = 0; i < g_snd_user_data.max_voices; ++i) + { + MIXResetChannelData(&g_snd_user_data.mix_channel[i]); + } + + g_snd_user_data.initialized = true; + g_snd_user_data.device_info.tv_sound_mode = 1; + g_snd_user_data.device_info.drc_sound_mode = 1; + g_snd_user_data.device_info.rmt_sound_mode = 0; + + AXFXInitDefaultHooks(); + } + + void MIXSetSoundMode(uint32 sound_mode) + { + sndApiLog_printf("MIXSetSoundMode(0x%x)", sound_mode); + + if (sound_mode >= 2) + sound_mode = 1; + + g_snd_user_data.device_info.tv_sound_mode = sound_mode; + } + + uint32 MIXGetSoundMode() + { + sndApiLog_printf("MIXGetSoundMode()"); + return g_snd_user_data.device_info.tv_sound_mode; + } + + void _MIXUpdateTV(MixChannel* channel, sint32 index) + { + assert(index == 0); + + bool updated_volume = false; + if ((channel->tv_mode & AX_UPDATE_MODE_80000000) != 0) + { + for (size_t i = 0; i < AX_MAX_NUM_BUS; ++i) + { + for (size_t j = 0; j < AX_TV_CHANNEL_COUNT; ++j) + { + channel->tv_volume[i][j].volume = channel->tv_volume[i][j].volume_target; + } + } + + channel->tv_mode &= ~AX_UPDATE_MODE_80000000; + updated_volume = true; + } + + if ((channel->tv_mode & AX_UPDATE_MODE_40000000_VOLUME) == 0) + { + if (!updated_volume) + return; + } + else + { + if (g_snd_user_data.device_info.tv_sound_mode == 0) + { + sint32 chan4 = channel->tv_channels[4]; + if (chan4 < -0x78) + chan4 = -0x78; + + const sint32 fader = channel->tv_control.fader; + + channel->tv_volume[0][0].volume_target = __MIXTranslateVolume(chan4 + fader); + channel->tv_volume[0][1].volume_target = __MIXTranslateVolume(chan4 + fader); + channel->tv_volume[0][2].volume_target = 0; + channel->tv_volume[0][3].volume_target = 0; + channel->tv_volume[0][4].volume_target = 0; + channel->tv_volume[0][5].volume_target = 0; + + for (int i = 0; i < 3; ++i) + { + const sint32 aux = channel->tv_control.aux[i]; + if ((channel->tv_mode & (1 << i)) == 0) + { + channel->tv_volume[1 + i][0].volume_target = __MIXTranslateVolume(chan4 + fader + aux); + channel->tv_volume[1 + i][1].volume_target = __MIXTranslateVolume(chan4 + fader + aux); + } + else + { + channel->tv_volume[1 + i][0].volume_target = __MIXTranslateVolume(chan4 + aux); + channel->tv_volume[1 + i][1].volume_target = __MIXTranslateVolume(chan4 + aux); + } + + channel->tv_volume[1 + i][2].volume_target = 0; + channel->tv_volume[1 + i][3].volume_target = 0; + channel->tv_volume[1 + i][4].volume_target = 0; + channel->tv_volume[1 + i][5].volume_target = 0; + } + + channel->tv_mode &= ~AX_UPDATE_MODE_40000000_VOLUME; + channel->tv_mode |= AX_UPDATE_MODE_80000000; + } + else if (g_snd_user_data.device_info.tv_sound_mode < 3) + { + sint32 chan4 = channel->tv_channels[4]; + if (chan4 < -0x78) + chan4 = -0x78; + + const sint32 fader = channel->tv_control.fader; + + const sint32 chan0 = channel->tv_channels[0]; + const sint32 chan1 = channel->tv_channels[1]; + channel->tv_volume[0][0].volume_target = __MIXTranslateVolume(chan4 + chan0 + fader); + channel->tv_volume[0][1].volume_target = __MIXTranslateVolume(chan4 + chan1 + fader); + channel->tv_volume[0][2].volume_target = 0; + channel->tv_volume[0][3].volume_target = 0; + channel->tv_volume[0][4].volume_target = 0; + channel->tv_volume[0][5].volume_target = 0; + + for (int i = 0; i < 3; ++i) + { + const sint32 aux = channel->tv_control.aux[i]; + if ((channel->tv_mode & (1 << i)) == 0) + { + channel->tv_volume[1 + i][0].volume_target = __MIXTranslateVolume(chan4 + chan0 + fader + aux); + channel->tv_volume[1 + i][1].volume_target = __MIXTranslateVolume(chan4 + chan1 + fader + aux); + } + else + { + channel->tv_volume[1 + i][0].volume_target = __MIXTranslateVolume(chan4 + chan0 + aux); + channel->tv_volume[1 + i][1].volume_target = __MIXTranslateVolume(chan4 + chan1 + aux); + } + + channel->tv_volume[1 + i][2].volume_target = 0; + channel->tv_volume[1 + i][3].volume_target = 0; + channel->tv_volume[1 + i][4].volume_target = 0; + channel->tv_volume[1 + i][5].volume_target = 0; + } + + channel->tv_mode &= ~AX_UPDATE_MODE_40000000_VOLUME; + channel->tv_mode |= AX_UPDATE_MODE_80000000; + } + else if (g_snd_user_data.device_info.tv_sound_mode == 3) + { + // TODO + } + else if (g_snd_user_data.device_info.tv_sound_mode == 4) + { + // TODO + } + else + { + channel->tv_mode &= ~AX_UPDATE_MODE_40000000_VOLUME; + channel->tv_mode |= AX_UPDATE_MODE_80000000; + } + } + + AXCHMIX2 mix[AX_TV_CHANNEL_COUNT][AX_MAX_NUM_BUS]; + for (size_t i = 0; i < AX_MAX_NUM_BUS; ++i) + { + for (size_t j = 0; j < AX_TV_CHANNEL_COUNT; ++j) + { + const sint16 target = channel->tv_volume[i][j].volume_target; + const sint16 volume = channel->tv_volume[i][j].volume; + + mix[j][i].vol = volume; + mix[j][i].delta = (target - volume) / 96; // 32000HZ SAMPLES_3MS + } + } + AXSetVoiceDeviceMix(channel->voice.GetPtr(), AX_DEV_TV, index, (snd_core::AXCHMIX_DEPR*)&mix[0][0]); + } + + void _MIXUpdateDRC(MixChannel* channel, sint32 index) + { + // todo + } + + void _MIXUpdateRmt(MixChannel* channel, sint32 index) + { + // todo + } + + void MIXInitChannel(AXVPB* voice, uint16 mode, uint16 input, uint16 aux1, uint16 aux2, uint16 aux3, uint16 pan, uint16 span, uint16 fader) + { + sndApiLog_printf("MIXInitChannel(0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x)", MEMPTR(voice).GetMPTR(), mode, input, aux1, aux2, aux3, pan, span, fader); + cemu_assert_debug(voice); + + AXVoiceBegin(voice); + + MIXAssignChannel(voice); + MIXInitInputControl(voice, input, mode); + + const uint32 index = voice->index; + auto& channel = g_snd_user_data.mix_channel[index]; + + channel.tv_control.aux[0] = aux1; + channel.tv_control.aux[1] = aux2; + channel.tv_control.aux[2] = aux3; + + channel.tv_control.pan = pan; + channel.tv_control.span = span; + channel.tv_control.fader = fader; + // channel.tv_control.lfe = lfe; // 0x1A -> not set? + + channel.tv_mode = AX_UPDATE_MODE_40000007 & mode; + _MIXControl_SetDevicePan(&channel.tv_control, AX_DEV_TV, channel.tv_channels); + + channel.tv_mode |= AX_UPDATE_MODE_40000000_VOLUME; + _MIXUpdateTV(&channel, 0); + + AXVoiceEnd(voice); + } + + void MIXAssignChannel(AXVPB* voice) + { + sndApiLog_printf("MIXAssignChannel(0x%x)", MEMPTR(voice).GetMPTR()); + cemu_assert_debug(voice); + + AXVoiceBegin(voice); + + const uint32 voice_index = voice->index; + auto channel = &g_snd_user_data.mix_channel[voice_index]; + MIXResetChannelData(channel); + channel->voice = voice; + + AXVoiceEnd(voice); + } + + void MIXDRCInitChannel(AXVPB* voice, uint16 mode, uint16 vol1, uint16 vol2, uint16 vol3) + { + sndApiLog_printf("MIXDRCInitChannel(0x%x, 0x%x, 0x%x, 0x%x)", MEMPTR(voice).GetMPTR(), mode, vol1, vol2, vol3); + cemu_assert_debug(voice); + + AXVoiceBegin(voice); + + const uint32 index = voice->index; + auto& channel = g_snd_user_data.mix_channel[index]; + + _MIXChannelResetDRC(&channel, 0); + + channel.drc_volume[1][1][1].volume = vol1; + channel.drc_volume[1][1][2].volume_target = vol2; + channel.drc_volume[1][1][3].volume_target = vol3; + + channel.drc_mode[0] = AX_UPDATE_MODE_40000007 & mode; + _MIXControl_SetDevicePan(&channel.drc_control[0], AX_DEV_DRC, &channel.drc_channels[0][0]); + + channel.drc_mode[0] |= AX_UPDATE_MODE_40000000_VOLUME; + _MIXUpdateDRC(&channel, 0); + + AXVoiceEnd(voice); + } + + void MIXSetInput(AXVPB* voice, uint16 input) + { + sndApiLog_printf("MIXSetInput(0x%x, 0x%x)", MEMPTR(voice).GetMPTR(), input); + + const uint32 voice_index = voice->index; + const auto channel = &g_snd_user_data.mix_channel[voice_index]; + + channel->input_level = input; + channel->update_mode |= AX_UPDATE_MODE_10000000; + + } + + void MIXUpdateSettings() + { + sndApiLog_printf("MIXUpdateSettings()"); + + if (!g_snd_user_data.initialized) + return; + + if (g_snd_user_data.max_voices <= 0) + return; + + for (sint32 i = 0; i < g_snd_user_data.max_voices; ++i) + { + auto& channel = g_snd_user_data.mix_channel[i]; + if (!channel.voice) + continue; + + const auto voice = channel.voice.GetPtr(); + + AXVoiceBegin(voice); + + bool volume_updated = false; + if ((channel.update_mode & AX_UPDATE_MODE_20000000) != 0) + { + channel.volume.volume = channel.volume.volume_target; + channel.update_mode &= ~AX_UPDATE_MODE_20000000; + volume_updated = true; + } + + if ((channel.update_mode & AX_UPDATE_MODE_10000000) == 0) + { + if (volume_updated) + { + AXPBVE ve; + ve.currentVolume = channel.volume.volume; + ve.currentDelta = (channel.volume.volume_target - channel.volume.volume) / 96; + AXSetVoiceVe(voice, &ve); + } + } + else + { + sint32 volume = 0; + if ((channel.update_mode & 8) == 0) + volume = __MIXTranslateVolume(channel.input_level); + + channel.volume.volume_target = volume; + + channel.update_mode &= ~AX_UPDATE_MODE_10000000; + channel.update_mode |= AX_UPDATE_MODE_20000000; + + AXPBVE ve; + ve.currentVolume = channel.volume.volume; + ve.currentDelta = (channel.volume.volume_target - channel.volume.volume) / 96; + AXSetVoiceVe(voice, &ve); + } + + _MIXUpdateTV(&channel, 0); + + for (int i = 0; i < 2; ++i) + _MIXUpdateDRC(&channel, i); + + // TODO remote mix + AXCHMIX2 mix[4]; + for (int j = 0; j < 4; ++j) + { + AXSetVoiceDeviceMix(voice, AX_DEV_RMT, i, (snd_core::AXCHMIX_DEPR*)mix); + } + + + AXVoiceEnd(voice); + } + + // TODO + } + + void MIXSetDeviceSoundMode(uint32 device, uint32 mode) + { + sndApiLog_printf("MIXSetDeviceSoundMode(0x%x, 0x%x)", device, mode); + cemu_assert_debug(device < AX_DEV_COUNT); + cemu_assert_debug(mode <= 4); + + bool is_tv_device = false; + bool is_drc_device = false; + if (device == AX_DEV_TV) + { + g_snd_user_data.device_info.tv_sound_mode = mode; + is_tv_device = true; + } + else if (device == AX_DEV_DRC) + { + cemu_assert_debug(mode <= 2); + g_snd_user_data.device_info.drc_sound_mode = mode; + is_drc_device = true; + } + else if (device == AX_DEV_RMT) + { + cemu_assert_debug(mode == 0); + g_snd_user_data.device_info.rmt_sound_mode = mode; + } + else + { + sndApiLog_printf("ERROR: MIXSetDeviceSoundMode(0x%x, 0x%x) -> wrong device", device, mode); + } + + for (sint32 i = 0; i < g_snd_user_data.max_voices; ++i) + { + auto& channel = g_snd_user_data.mix_channel[i]; + if (!channel.voice) + continue; + + const auto voice = channel.voice.GetPtr(); + AXVoiceBegin(voice); + + if (is_tv_device) + { + channel.tv_mode |= AX_UPDATE_MODE_40000000_VOLUME; + _MIXControl_SetDevicePan(&channel.tv_control, AX_DEV_TV, channel.tv_channels); + } + + if (is_drc_device) + { + for (sint32 j = 0; j < AX_MAX_NUM_DRC; ++j) + { + channel.drc_mode[j] |= AX_UPDATE_MODE_40000000_VOLUME; + _MIXControl_SetDevicePan(&channel.drc_control[j], AX_DEV_DRC, channel.drc_channels[j]); + } + } + + AXVoiceEnd(voice); + } + } + + void MIXInitDeviceControl(AXVPB* voice, uint32 device_type, uint32 index, MixControl* control, uint32 mode) + { + sndApiLog_printf("MIXInitDeviceControl(0x%0x, 0x%x, 0x%x, 0x%x, 0x%x )", MEMPTR(voice).GetMPTR(), device_type, index, MEMPTR(control).GetMPTR(), mode); + + cemu_assert_debug(device_type < AX_DEV_COUNT); + cemu_assert_debug(voice); + cemu_assert_debug(control); + + AXVoiceBegin(voice); + + const uint32 voice_index = voice->index; + auto& channel = g_snd_user_data.mix_channel[voice_index]; + + channel.voice = voice; + + if (device_type == AX_DEV_TV) + { + cemu_assert_debug(index == 0); + _MIXChannelResetTV(&channel, index); + + memcpy(&channel.tv_control, control, sizeof(MixControl)); + _MIXControl_SetDevicePan(&channel.tv_control, device_type, channel.tv_channels); + + channel.tv_mode |= AX_UPDATE_MODE_40000000_VOLUME; + _MIXUpdateTV(&channel, index); + } + else if (device_type == AX_DEV_DRC) + { + cemu_assert_debug(index < 2); + _MIXChannelResetDRC(&channel, index); + + memcpy(&channel.drc_control[index], control, sizeof(MixControl)); + _MIXControl_SetDevicePan(&channel.drc_control[index], device_type, channel.drc_channels[index]); + + channel.drc_mode[index] |= AX_UPDATE_MODE_40000000_VOLUME; + _MIXUpdateDRC(&channel, index); + } + else if (device_type == AX_DEV_RMT) + { + cemu_assert_debug(index < 4); + _MIXChannelResetRmt(&channel, index); + + memcpy(&channel.rmt_control[index], control, sizeof(MixControl)); + _MIXControl_SetDevicePan(&channel.rmt_control[index], device_type, channel.rmt_channels[index]); + + channel.rmt_mode[index] = mode & 0xf; + _MIXUpdateRmt(&channel, index); + } + + AXVoiceEnd(voice); + } + + + void MIXInitInputControl(AXVPB* voice, uint16 input, uint32 mode) + { + sndApiLog_printf("MIXInitInputControl(0x%x, 0x%x, 0x%x )", MEMPTR(voice).GetMPTR(), input, mode); + cemu_assert_debug(voice); + + AXVoiceBegin(voice); + + const uint32 voice_index = voice->index; + auto& channel = g_snd_user_data.mix_channel[voice_index]; + + mode &= 8; + mode |= AX_UPDATE_MODE_10000000; + channel.update_mode = mode; + + channel.input_level = input; + + AXVoiceEnd(voice); + } + + void MIXSetDeviceFader(AXVPB* vpb, uint32 device, uint32 deviceIndex, sint16 newFader) + { + // not well tested + cemu_assert(device < AX_DEV_COUNT); + MixChannel& mixChannel = g_snd_user_data.mix_channel[vpb->index]; + + AXVoiceBegin(vpb); + + MixControl& mixControl = mixChannel.GetMixControl(device, deviceIndex); + MixMode& mixMode = mixChannel.GetMode(device, deviceIndex); + + if (mixControl.fader == newFader) + { + AXVoiceEnd(vpb); + return; + } + + mixControl.fader = newFader; + mixMode |= AX_UPDATE_MODE_40000000_VOLUME; + AXVoiceEnd(vpb); + } + + void MIXSetDevicePan(AXVPB* vpb, uint32 device, uint32 deviceIndex, sint16 newPan) + { + // not well tested + cemu_assert(device < AX_DEV_COUNT); + MixChannel& mixChannel = g_snd_user_data.mix_channel[vpb->index]; + + AXVoiceBegin(vpb); + + MixControl& mixControl = mixChannel.GetMixControl(device, deviceIndex); + MixMode& mixMode = mixChannel.GetMode(device, deviceIndex); + sint16* deviceChannels = mixChannel.GetChannels(device, deviceIndex); + + if (mixControl.fader == newPan) + { + AXVoiceEnd(vpb); + return; + } + + mixControl.pan = newPan; + _MIXControl_SetDevicePan(&mixControl, device, deviceChannels); + mixMode |= AX_UPDATE_MODE_40000000_VOLUME; + AXVoiceEnd(vpb); + } + + struct AXPBADPCM + { + uint16be table[8][2]; // 0x00 + uint16be gain; + uint16be predicator; + uint16be yn1; + uint16be yn2; + }; + + struct AXPBADPCMLOOP + { + uint16be predicator; // 0x00 + uint16be yn1; // 0x02 + uint16be yn2; // 0x04 + }; + + struct SPAdpcmEntry + { + AXPBADPCM adpcm; + AXPBADPCMLOOP adpcmLoop; + }; + + struct SPSoundEntry + { + uint32be type; // 0x00 + uint32be sampleRate; // 0x04 + uint32be loopAddress; // 0x08 + uint32be loopEndAddress; // 0x0C + uint32be endOffset; // 0x10 + uint32be currentOffset; // 0x14 + MEMPTR<SPAdpcmEntry> adpcmEntry; // 0x18 + }; + static_assert(sizeof(SPSoundEntry) == 0x1C); + + struct SPSoundTable + { + uint32be count; + SPSoundEntry entries[1]; + }; + + void SPInitSoundTable(SPSoundTable* soundTable, uint8* samples, uint32be* endOffsetPtr) + { + sndApiLog_printf("SPInitSoundTable(0x%x, 0x%x, 0x%x )", MEMPTR(soundTable).GetMPTR(), MEMPTR(samples).GetMPTR(), MEMPTR(endOffsetPtr).GetMPTR()); + if (!soundTable) + return; + + if (!samples) + return; + + uint32be endOffset = 0; + for(uint32 i = 0; i < soundTable->count; ++i) + { + auto& entryPtr = soundTable->entries[i]; + if(entryPtr.type == 0) + { + entryPtr.loopAddress = entryPtr.currentOffset; + entryPtr.loopEndAddress = 0; + uint32be tmp = entryPtr.endOffset >> 1; + if (tmp > endOffset) + endOffset = tmp; + /// ??? + } + else if (entryPtr.type == 1) + { + uint32be tmp = entryPtr.endOffset >> 1; + if (tmp > endOffset) + endOffset = tmp; + /// ??? + } + else if (entryPtr.type == 2) + { + entryPtr.loopAddress = entryPtr.currentOffset; + entryPtr.loopEndAddress = 0; + uint32be tmp = entryPtr.endOffset << 1; + if (tmp > endOffset) + endOffset = tmp; + } + else if (entryPtr.type == 3) + { + uint32be tmp = entryPtr.endOffset << 1; + if (tmp > endOffset) + endOffset = tmp; + } + else if (entryPtr.type == 4) + { + entryPtr.loopAddress = entryPtr.currentOffset; + entryPtr.loopEndAddress = 0; + + if (entryPtr.endOffset > endOffset) + endOffset = entryPtr.endOffset; + } + else if (entryPtr.type == 5) + { + if (entryPtr.endOffset > endOffset) + endOffset = entryPtr.endOffset; + } + } + + + if (endOffsetPtr) + *endOffsetPtr = endOffset; + + } + + SPSoundEntry* SPGetSoundEntry(SPSoundTable* soundTable, uint32 index) + { + sndApiLog_printf("SPGetSoundEntry(0x%x, %d)", MEMPTR(soundTable).GetMPTR(), index); + if (!soundTable) + return nullptr; + + if (soundTable->count <= index) + return nullptr; + + return &soundTable->entries[index]; + } + + MEMPTR<void> s_fxAlloc = nullptr; + MEMPTR<void> s_fxFree = nullptr; + + void _AXDefaultHook_alloc(PPCInterpreter_t* hCPU) + { + uint32 size = hCPU->gpr[3]; + MEMPTR<void> mem = coreinit::_weak_MEMAllocFromDefaultHeap(size); + osLib_returnFromFunction(hCPU, mem.GetMPTR()); + } + + void _AXDefaultHook_free(PPCInterpreter_t* hCPU) + { + MEMPTR<void> mem{ hCPU->gpr[3] }; + return coreinit::_weak_MEMFreeToDefaultHeap(mem.GetPtr()); + } + + void AXFXInitDefaultHooks() + { + // todo - this should only be applied when the library is loaded? MIXInit() does not affect this? + if (!s_fxAlloc) + { + s_fxAlloc = RPLLoader_MakePPCCallable(_AXDefaultHook_alloc); + s_fxFree = RPLLoader_MakePPCCallable(_AXDefaultHook_free); + } + } + + void AXFXSetHooks(void* allocFunc, void* freeFunc) + { + s_fxAlloc = allocFunc; + s_fxFree = freeFunc; + } + + void AXFXGetHooks(MEMPTR<void>* allocFuncOut, MEMPTR<void>* freeFuncOut) + { + *allocFuncOut = s_fxAlloc; + *freeFuncOut = s_fxFree; + } + + void* AXFXInternalAlloc(uint32 size, bool clearToZero) + { + if (s_fxAlloc) + { + MEMPTR<void> mem{ PPCCoreCallback(s_fxAlloc, size) }; + if (clearToZero) + memset(mem.GetPtr(), 0, size); + return mem.GetPtr(); + } + void* mem = coreinit::_weak_MEMAllocFromDefaultHeapEx(size, 4); + if (clearToZero) + memset(mem, 0, size); + return mem; + } + + void AXFXInternalFree(void* mem) + { + if (s_fxFree) + { + PPCCoreCallback(s_fxFree, mem); + return; + } + coreinit::_weak_MEMFreeToDefaultHeap(mem); + } + + /* AUX callback */ + + struct AUXCBSAMPLEDATA + { + MEMPTR<sint32be> channelSamples[6]; + }; + + bool gUnsupportedSoundEffectWarning = false; + void PrintUnsupportedSoundEffectWarning() + { + if (gUnsupportedSoundEffectWarning) + return; + cemuLog_force("The currently running title is trying to utilize an unsupported audio effect"); + cemuLog_force("To emulate these correctly, place snd_user.rpl and snduser2.rpl from the original Wii U firmware in /cafeLibs/ folder"); + gUnsupportedSoundEffectWarning = true; + } + + void __UnimplementedFXCallback(AUXCBSAMPLEDATA* auxSamples, size_t sampleCount, bool clearCh0, bool clearCh1, bool clearCh2, bool clearCh3, bool clearCh4, bool clearCh5) + { + PrintUnsupportedSoundEffectWarning(); + bool clearChannel[6] = { clearCh0, clearCh1, clearCh2, clearCh3, clearCh4, clearCh5 }; + + for (sint32 channel = 0; channel < 6; channel++) + { + if(!clearChannel[channel]) + continue; + sint32be* channelPtr = auxSamples->channelSamples[channel].GetPtr(); + while (sampleCount) + { + *channelPtr = 0; + channelPtr++; + sampleCount--; + } + } + + } + + /* AXFXReverbHi */ + + struct AXFXReverbHiData + { + // todo - implement + uint32be placeholder; + }; + + void AXFXReverbHiInit(AXFXReverbHiData* param) + { + cemuLog_force("AXFXReverbHiInit - stub"); + } + + void AXFXReverbHiSettings(AXFXReverbHiData* param) + { + cemuLog_force("AXFXReverbHiSettings - stub"); + } + + void AXFXReverbHiShutdown(AXFXReverbHiData* param) + { + cemuLog_force("AXFXReverbHiShutdown - stub"); + } + + void AXFXReverbHiCallback(AUXCBSAMPLEDATA* auxSamples, AXFXReverbHiData* reverbHi) + { + // todo - implement me + __UnimplementedFXCallback(auxSamples, 96, true, true, true, false, false, false); + } + + /* AXFXMultiChReverb */ + + struct AXFXMultiChReverbData + { + // todo - implement + uint32 placeholder; + }; + + void AXFXMultiChReverbInit(AXFXMultiChReverbData* param, int ukn2, int ukn3) + { + cemuLog_force("AXFXMultiChReverbInit (Stubbed)"); + } + + void AXFXMultiChReverbSettingsUpdate(AXFXMultiChReverbData* param) + { + // todo + } + + void AXFXMultiChReverbShutdown(AXFXMultiChReverbData* param) + { + // todo + } + + void AXFXMultiChReverbCallback(AUXCBSAMPLEDATA* auxSamples, AXFXMultiChReverbData* reverbHi, AXAUXCBCHANNELINFO* auxInfo) + { + // todo - implement me + __UnimplementedFXCallback(auxSamples, auxInfo->numSamples, true, true, true, true, true, true); + } + + /* AXFXReverbStd */ + + struct AXFXReverbStdData + { + uint32be placeholder; + }; + + uint32 AXFXReverbStdExpGetMemSize(AXFXReverbStdData* reverbParam) + { + return 0xC; // placeholder size + } + + bool AXFXReverbStdExpInit(AXFXReverbStdData* reverbParam) + { + return true; + } + + void AXFXReverbStdExpShutdown(AXFXReverbStdData* reverbParam) + { + + } + + void AXFXReverbStdExpCallback(AUXCBSAMPLEDATA* auxSamples, AXFXReverbStdData* reverbData) + { + // todo - implement me + __UnimplementedFXCallback(auxSamples, 96, true, true, true, false, false, false); + } + + void Initialize() + { + /* snd_user */ + cafeExportRegister("snd_user", MIXInit, LogType::SoundAPI); + cafeExportRegister("snd_user", MIXSetSoundMode, LogType::SoundAPI); + cafeExportRegister("snd_user", MIXGetSoundMode, LogType::SoundAPI); + cafeExportRegister("snd_user", MIXInitChannel, LogType::SoundAPI); + cafeExportRegister("snd_user", MIXAssignChannel, LogType::SoundAPI); + cafeExportRegister("snd_user", MIXDRCInitChannel, LogType::SoundAPI); + cafeExportRegister("snd_user", MIXSetInput, LogType::SoundAPI); + cafeExportRegister("snd_user", MIXUpdateSettings, LogType::SoundAPI); + cafeExportRegister("snd_user", MIXSetDeviceSoundMode, LogType::SoundAPI); + cafeExportRegister("snd_user", MIXSetDeviceFader, LogType::SoundAPI); + cafeExportRegister("snd_user", MIXSetDevicePan, LogType::SoundAPI); + cafeExportRegister("snd_user", MIXInitDeviceControl, LogType::SoundAPI); + cafeExportRegister("snd_user", MIXInitInputControl, LogType::SoundAPI); + + + + cafeExportRegister("snd_user", SPInitSoundTable, LogType::SoundAPI); + cafeExportRegister("snd_user", SPGetSoundEntry, LogType::SoundAPI); + //cafeExportRegister("snd_user", SPPrepareSound); + cafeExportRegister("snd_user", AXFXSetHooks, LogType::SoundAPI); + cafeExportRegister("snd_user", AXFXGetHooks, LogType::SoundAPI); + + cafeExportRegister("snd_user", AXFXReverbHiInit, LogType::SoundAPI); + cafeExportRegister("snd_user", AXFXReverbHiSettings, LogType::SoundAPI); + cafeExportRegister("snd_user", AXFXReverbHiShutdown, LogType::SoundAPI); + cafeExportRegister("snd_user", AXFXReverbHiCallback, LogType::SoundAPI); + + cafeExportRegister("snd_user", AXFXMultiChReverbInit, LogType::SoundAPI); + cafeExportRegister("snd_user", AXFXMultiChReverbSettingsUpdate, LogType::SoundAPI); + cafeExportRegister("snd_user", AXFXMultiChReverbShutdown, LogType::SoundAPI); + cafeExportRegister("snd_user", AXFXMultiChReverbCallback, LogType::SoundAPI); + + /* snduser2 */ + cafeExportRegister("snduser2", MIXInit, LogType::SoundAPI); + cafeExportRegister("snduser2", MIXSetSoundMode, LogType::SoundAPI); + cafeExportRegister("snduser2", MIXGetSoundMode, LogType::SoundAPI); + cafeExportRegister("snduser2", MIXInitChannel, LogType::SoundAPI); + cafeExportRegister("snduser2", MIXAssignChannel, LogType::SoundAPI); + cafeExportRegister("snduser2", MIXDRCInitChannel, LogType::SoundAPI); + cafeExportRegister("snduser2", MIXSetInput, LogType::SoundAPI); + cafeExportRegister("snduser2", MIXUpdateSettings, LogType::SoundAPI); + cafeExportRegister("snduser2", MIXSetDeviceSoundMode, LogType::SoundAPI); + cafeExportRegister("snduser2", MIXSetDeviceFader, LogType::SoundAPI); + cafeExportRegister("snduser2", MIXSetDevicePan, LogType::SoundAPI); + cafeExportRegister("snduser2", MIXInitDeviceControl, LogType::SoundAPI); + cafeExportRegister("snduser2", MIXInitInputControl, LogType::SoundAPI); + + cafeExportRegister("snduser2", AXFXSetHooks, LogType::Placeholder); + cafeExportRegister("snduser2", AXFXGetHooks, LogType::Placeholder); + cafeExportRegister("snduser2", AXFXReverbStdExpGetMemSize, LogType::Placeholder); + cafeExportRegister("snduser2", AXFXReverbStdExpInit, LogType::Placeholder); + cafeExportRegister("snduser2", AXFXReverbStdExpShutdown, LogType::Placeholder); + cafeExportRegister("snduser2", AXFXReverbStdExpCallback, LogType::Placeholder); + + cafeExportRegister("snduser2", AXFXReverbHiInit, LogType::SoundAPI); + cafeExportRegister("snduser2", AXFXReverbHiSettings, LogType::SoundAPI); + cafeExportRegister("snduser2", AXFXReverbHiShutdown, LogType::SoundAPI); + cafeExportRegister("snduser2", AXFXReverbHiCallback, LogType::SoundAPI); + + cafeExportRegister("snduser2", AXFXMultiChReverbInit, LogType::SoundAPI); + cafeExportRegister("snduser2", AXFXMultiChReverbSettingsUpdate, LogType::SoundAPI); + cafeExportRegister("snduser2", AXFXMultiChReverbShutdown, LogType::SoundAPI); + cafeExportRegister("snduser2", AXFXMultiChReverbCallback, LogType::SoundAPI); + } + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/snd_user/snd_user.h b/src/Cafe/OS/libs/snd_user/snd_user.h new file mode 100644 index 00000000..ba2e3511 --- /dev/null +++ b/src/Cafe/OS/libs/snd_user/snd_user.h @@ -0,0 +1,25 @@ +#pragma once + +#include "Cafe/OS/libs/snd_core/ax.h" + +namespace snd +{ + namespace user + { + struct MixControl; + + void MIXInit(); + void MIXInitInputControl(snd_core::AXVPB* voice, uint16 input, uint32 mode); + void MIXInitDeviceControl(snd_core::AXVPB* voice, uint32 device_type, uint32 index, MixControl* control, uint32 mode); + void MIXSetDeviceSoundMode(uint32 device, uint32 mode); + void MIXUpdateSettings(); + void MIXSetInput(snd_core::AXVPB* voice, uint16 input); + void MIXDRCInitChannel(snd_core::AXVPB* voice, uint16 mode, uint16 vol1, uint16 vol2, uint16 vol3); + void MIXAssignChannel(snd_core::AXVPB* voice); + void MIXInitChannel(snd_core::AXVPB* voice, uint16 mode, uint16 input, uint16 aux1, uint16 aux2, uint16 aux3, uint16 pan, uint16 span, uint16 fader); + uint32 MIXGetSoundMode(); + void MIXSetSoundMode(uint32 sound_mode); + + void Initialize(); + } +} diff --git a/src/Cafe/OS/libs/swkbd/swkbd.cpp b/src/Cafe/OS/libs/swkbd/swkbd.cpp new file mode 100644 index 00000000..1a09ec58 --- /dev/null +++ b/src/Cafe/OS/libs/swkbd/swkbd.cpp @@ -0,0 +1,650 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/HW/Latte/ISA/RegDefines.h" +#include "Cafe/OS/libs/gx2/GX2.h" +#include "Cafe/HW/Latte/Core/Latte.h" +#include "Cafe/HW/Latte/Core/LatteDraw.h" + +#include "Cafe/HW/Latte/Renderer/Renderer.h" + +#include <imgui.h> +#include "imgui/imgui_extension.h" +#include "util/helpers/helpers.h" +#include "resource/IconsFontAwesome5.h" + +#define SWKBD_FORM_STRING_MAX_LENGTH (4096) // counted in 16-bit characters + +#define SWKBD_STATE_BLANK (0) // not visible +#define SWKBD_STATE_APPEARING (1) // fade-in ? +#define SWKBD_STATE_DISPLAYED (2) // visible +#define SWKBD_STATE_DISAPPEARING (3) // fade-out ? + +typedef struct +{ + uint32 ukn00; // constructor? + uint32 ukn04; // destructor? + uint32 ukn08; // ? + MEMPTR<void> changeString; // some function address +}SwkbdIEventReceiverVTable_t; + +typedef struct +{ + MEMPTR<SwkbdIEventReceiverVTable_t> vTable; + // todo - more elements? (currently separated from this struct) +}SwkbdIEventReceiver_t; + +struct swkbdReceiverArg_t +{ + MEMPTR<SwkbdIEventReceiver_t> IEventReceiver; + MEMPTR<sint16be> stringBuf; + sint32be stringBufSize; + sint32be fixedCharLimit; + sint32be cursorPos; + sint32be selectFrom; +}; + +typedef struct +{ + uint32 ukn000; + uint32 controllerType; + uint32 keyboardMode; // guessed + uint32 ukn00C; + uint32 ukn010; + uint32 ukn014; + uint32 ukn018; + uint32 ukn01C; // ok string? + uint32 ukn020[4]; + uint32 ukn030[4]; + uint32 ukn040[4]; + uint32 ukn050[4]; + uint32 ukn060[4]; + uint32 ukn070[4]; + uint32 ukn080[4]; + uint32 ukn090[4]; + uint32 ukn0A0; + uint32 ukn0A4; + //uint32 ukn0A8; + //MEMPTR<SwkbdIEventReceiver_t> IEventReceiver; + swkbdReceiverArg_t receiverArg; +}SwkbdKeyboardArg_t; + +typedef struct +{ + // this structure resides in PPC addressable memory space + wchar_t formStringBuffer[SWKBD_FORM_STRING_MAX_LENGTH]; + sint32 formStringLength; + // big endian version of the string buffer (converted whenever GetInputFormString is called) + uint16 formStringBufferBE[SWKBD_FORM_STRING_MAX_LENGTH]; + bool isActive; // set when SwkbdAppearInputForm() is called + //bool isDisplayed; // set when keyboard is rendering + bool decideButtonWasPressed; // set to false when keyboard appears, and set to true when enter is pressed. Remains on true after the keyboard is disappeared (todo: Investigate how this really works) + // keyboard only mode (no input form) + bool keyboardOnlyMode; + SwkbdKeyboardArg_t keyboardArg; + // input form appear args + sint32 maxTextLength; + // imgui keyboard drawing stuff + bool shiftActivated; + bool returnState; + bool cancelState; + +}swkbdInternalState_t; + +swkbdInternalState_t* swkbdInternalState = NULL; + +void swkbdExport_SwkbdCreate(PPCInterpreter_t* hCPU) +{ + forceLogDebug_printf("swkbd.SwkbdCreate(0x%08x,0x%08x,0x%08x,0x%08x)\n", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6]); + if( swkbdInternalState == NULL ) + { + MPTR swkbdInternalStateMPTR = coreinit_allocFromSysArea(sizeof(swkbdInternalState_t), 4); + swkbdInternalState = (swkbdInternalState_t*)memory_getPointerFromVirtualOffset(swkbdInternalStateMPTR); + memset(swkbdInternalState, 0x00, sizeof(swkbdInternalState_t)); + } + osLib_returnFromFunction(hCPU, 0); // should return true? +} + +void swkbdExport_SwkbdGetStateKeyboard(PPCInterpreter_t* hCPU) +{ + uint32 r = SWKBD_STATE_BLANK; + if( swkbdInternalState->isActive ) + r = SWKBD_STATE_DISPLAYED; + osLib_returnFromFunction(hCPU, r); +} + +void swkbdExport_SwkbdGetStateInputForm(PPCInterpreter_t* hCPU) +{ + //debug_printf("SwkbdGetStateInputForm__3RplFv LR: %08x\n", hCPU->sprNew.LR); + uint32 r = SWKBD_STATE_BLANK; + if( swkbdInternalState->isActive ) + r = SWKBD_STATE_DISPLAYED; + osLib_returnFromFunction(hCPU, r); +} + +//ReceiverArg: +//+0x00 IEventReceiver* +//+0x04 stringBuf +//+ 0x08 stringBufSize +//+ 0x0C fixedCharNumLimit(-1) +//+ 0x10 cursorPos +//+ 0x14 selectFrom(-1) +// +//IEventReceiver: +//+0x00 IEventReceiver_vTable* +//+0x04 ? +//+0x08 ? +//+0x0C ? +// +//IEventReceiver_vTable : +// +0x00 ? +// +0x04 ? +// +0x08 ? +// +0x0C functionPtr onDirtyString(const DirtyInfo& info) = 0; ->DirtyInfo is just two DWORDs.From and to ? +// ? + + +typedef struct +{ + MPTR vTable; // guessed +}swkdbIEventReceiver_t; + +typedef struct +{ + uint32 ukn00; + uint32 ukn04; + uint32 ukn08; + MPTR onDirtyString; +}swkdbIEventReceiverVTable_t; + +void swkbdExport_SwkbdSetReceiver(PPCInterpreter_t* hCPU) +{ + debug_printf("SwkbdSetReceiver(0x%08x)\n", hCPU->gpr[3]); + swkbdReceiverArg_t* receiverArg = (swkbdReceiverArg_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); + + if(swkbdInternalState == nullptr) + { + osLib_returnFromFunction(hCPU, 0); + return; + } + + swkbdInternalState->keyboardArg.receiverArg = *receiverArg; + + osLib_returnFromFunction(hCPU, 0); +} + +typedef struct +{ + /* +0x00 */ uint32 ukn00; + /* +0x04 */ uint32 ukn04; + /* +0x08 */ uint32 ukn08; + /* +0x0C */ uint32 ukn0C; + /* +0x10 */ uint32 ukn10; + /* +0x14 */ uint32 ukn14; + /* +0x18 */ uint32 ukn18; + /* +0x1C */ uint32 ukn1C; // pointer to OK string + /* +0x20 */ uint32 ukn20[4]; + /* +0x30 */ uint32 ukn30[4]; + /* +0x40 */ uint32 ukn40[4]; + /* +0x50 */ uint32 ukn50[4]; + /* +0x60 */ uint32 ukn60[4]; + /* +0x70 */ uint32 ukn70[4]; + /* +0x80 */ uint32 ukn80[4]; + /* +0x90 */ uint32 ukn90[4]; + /* +0xA0 */ uint32 uknA0[4]; + /* +0xB0 */ uint32 uknB0[4]; + /* +0xC0 */ uint32 inputFormType; + /* +0xC4 */ uint32be cursorIndex; + /* +0xC8 */ MEMPTR<uint16be> initialText; + /* +0xCC */ MEMPTR<uint16be> infoText; + /* +0xD0 */ uint32be maxTextLength; + +}swkbdAppearArg_t; + +static_assert(offsetof(swkbdAppearArg_t, cursorIndex) == 0xC4, "appearArg.cursorIndex has invalid offset"); + +void swkbdExport_SwkbdAppearInputForm(PPCInterpreter_t* hCPU) +{ + ppcDefineParamStructPtr(appearArg, swkbdAppearArg_t, 0); + forceLogDebug_printf("SwkbdAppearInputForm__3RplFRCQ3_2nn5swkbd9AppearArg LR: %08x\n", hCPU->spr.LR); + swkbdInternalState->formStringLength = 0; + swkbdInternalState->isActive = true; + swkbdInternalState->decideButtonWasPressed = false; + swkbdInternalState->keyboardOnlyMode = false; + + // setup max text length + swkbdInternalState->maxTextLength = (sint32)(uint32)appearArg->maxTextLength; + if (swkbdInternalState->maxTextLength <= 0) + swkbdInternalState->maxTextLength = SWKBD_FORM_STRING_MAX_LENGTH - 1; + else + swkbdInternalState->maxTextLength = std::min(swkbdInternalState->maxTextLength, SWKBD_FORM_STRING_MAX_LENGTH - 1); + // setup initial string + uint16be* initialString = appearArg->initialText.GetPtr(); + if (initialString) + { + swkbdInternalState->formStringLength = 0; + for (sint32 i = 0; i < swkbdInternalState->maxTextLength; i++) + { + wchar_t c = (uint16)initialString[i]; + if( c == '\0' ) + break; + swkbdInternalState->formStringBuffer[i] = c; + swkbdInternalState->formStringLength++; + } + } + else + { + swkbdInternalState->formStringBuffer[0] = '\0'; + swkbdInternalState->formStringLength = 0; + } + osLib_returnFromFunction(hCPU, 1); +} + +void swkbdExport_SwkbdAppearKeyboard(PPCInterpreter_t* hCPU) +{ + // todo: Figure out what the difference between AppearInputForm and AppearKeyboard is? + forceLogDebug_printf("SwkbdAppearKeyboard__3RplFRCQ3_2nn5swkbd11KeyboardArg LR: %08x\n", hCPU->spr.LR); + SwkbdKeyboardArg_t* keyboardArg = (SwkbdKeyboardArg_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); + + uint32 argPtr = hCPU->gpr[3]; + for(sint32 i=0; i<0x180; i += 4) + { + debug_printf("+0x%03x: 0x%08x\n", i, memory_readU32(argPtr+i)); + } + + swkbdInternalState->formStringLength = 0; + swkbdInternalState->isActive = true; + swkbdInternalState->keyboardOnlyMode = true; + swkbdInternalState->decideButtonWasPressed = false; + swkbdInternalState->formStringBuffer[0] = '\0'; + swkbdInternalState->formStringLength = 0; + swkbdInternalState->keyboardArg = *keyboardArg; + osLib_returnFromFunction(hCPU, 1); +} + +void swkbdExport_SwkbdDisappearInputForm(PPCInterpreter_t* hCPU) +{ + debug_printf("SwkbdDisappearInputForm__3RplFv LR: %08x\n", hCPU->spr.LR); + swkbdInternalState->isActive = false; + osLib_returnFromFunction(hCPU, 1); +} + +void swkbdExport_SwkbdDisappearKeyboard(PPCInterpreter_t* hCPU) +{ + debug_printf("SwkbdDisappearKeyboard__3RplFv LR: %08x\n", hCPU->spr.LR); + swkbdInternalState->isActive = false; + osLib_returnFromFunction(hCPU, 1); +} + +void swkbdExport_SwkbdGetInputFormString(PPCInterpreter_t* hCPU) +{ + debug_printf("SwkbdGetInputFormString__3RplFv LR: %08x\n", hCPU->spr.LR); + for(sint32 i=0; i<swkbdInternalState->formStringLength; i++) + { + swkbdInternalState->formStringBufferBE[i] = _swapEndianU16(swkbdInternalState->formStringBuffer[i]); + } + swkbdInternalState->formStringBufferBE[swkbdInternalState->formStringLength] = '\0'; + osLib_returnFromFunction(hCPU, memory_getVirtualOffsetFromPointer(swkbdInternalState->formStringBufferBE)); +} + +void swkbdExport_SwkbdIsDecideOkButton(PPCInterpreter_t* hCPU) +{ + debug_printf("SwkbdIsDecideOkButton__3RplFPb LR: %08x\n", hCPU->spr.LR); + if (swkbdInternalState->decideButtonWasPressed) + osLib_returnFromFunction(hCPU, 1); + else + osLib_returnFromFunction(hCPU, 0); +} + +typedef struct +{ + uint32be ukn00; + uint32be ukn04; + uint32be ukn08; + uint32be ukn0C; + uint32be ukn10; + uint32be ukn14; + uint8 ukn18; + // there might be padding here? +}SwkbdDrawStringInfo_t; + +static_assert(sizeof(SwkbdDrawStringInfo_t) != 0x19, "SwkbdDrawStringInfo_t has invalid size"); + +void swkbdExport_SwkbdGetDrawStringInfo(PPCInterpreter_t* hCPU) +{ + forceLogDebug_printf("SwkbdGetDrawStringInfo(0x%08x) - stub LR: %08x", hCPU->gpr[3], hCPU->spr.LR); + ppcDefineParamStructPtr(drawStringInfo, SwkbdDrawStringInfo_t, 0); + + drawStringInfo->ukn00 = -1; + drawStringInfo->ukn04 = -1; + drawStringInfo->ukn08 = -1; + drawStringInfo->ukn0C = -1; + drawStringInfo->ukn10 = -1; + drawStringInfo->ukn14 = -1; + drawStringInfo->ukn18 = 0; + + osLib_returnFromFunction(hCPU, 0); +} + +void swkbdExport_SwkbdInitLearnDic(PPCInterpreter_t* hCPU) +{ + forceLogDebug_printf("SwkbdInitLearnDic(0x%08x) - stub LR: %08x", hCPU->gpr[3], hCPU->spr.LR); + // todo + + // this has to fail (at least once?) or MH3U will not boot + osLib_returnFromFunction(hCPU, 1); +} + +bool isNeedCalc0 = true; +bool isNeedCalc1 = true; + +void swkbdExport_SwkbdIsNeedCalcSubThreadFont(PPCInterpreter_t* hCPU) +{ + // SwkbdIsNeedCalcSubThreadFont__3RplFv + bool r = false; + osLib_returnFromFunction(hCPU, r?1:0); +} + +void swkbdExport_SwkbdIsNeedCalcSubThreadPredict(PPCInterpreter_t* hCPU) +{ + // SwkbdIsNeedCalcSubThreadPredict__3RplFv + bool r = false; + + osLib_returnFromFunction(hCPU, r?1:0); +} + +void swkbd_keyInput(uint32 keyCode); +void swkbd_render(bool mainWindow) +{ + // only render if active + if( swkbdInternalState == NULL || swkbdInternalState->isActive == false) + return; + + auto& io = ImGui::GetIO(); + const auto font = ImGui_GetFont(48.0f); + const auto textFont = ImGui_GetFont(24.0f); + if (!font || !textFont) + return; + + const auto kPopupFlags = ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings; + + ImGui::PushStyleColor(ImGuiCol_WindowBg, 0); + + ImGui::SetNextWindowPos({ 0,0 }, ImGuiCond_Always); + ImGui::SetNextWindowSize(io.DisplaySize, ImGuiCond_Always); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, { 0,0 }); + ImGui::SetNextWindowBgAlpha(0.8f); + if (ImGui::Begin("Background overlay", nullptr, kPopupFlags | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoFocusOnAppearing)) + ImGui::End(); + ImGui::PopStyleVar(2); + + ImVec2 position = { io.DisplaySize.x / 2.0f, io.DisplaySize.y / 3.0f }; + ImVec2 pivot = { 0.5f, 0.5f }; + + const auto button_len = font->GetCharAdvance('W'); + const float len = button_len * std::max(4, std::max(swkbdInternalState->maxTextLength, (sint32)swkbdInternalState->keyboardArg.receiverArg.stringBufSize)); + + ImVec2 box_size = { std::min(io.DisplaySize.x * 0.9f, len + 90), 0 }; + ImGui::SetNextWindowPos(position, ImGuiCond_Always, pivot); + ImGui::SetNextWindowSize(box_size, ImGuiCond_Always); + ImGui::SetNextWindowBgAlpha(0.9f); + ImGui::PushFont(font); + if (ImGui::Begin("Keyboard Input", nullptr, kPopupFlags)) + { + ImGui::Text(_utf8WrapperPtr(ICON_FA_KEYBOARD)); + ImGui::SameLine(70); + auto text = boost::nowide::narrow(fmt::format(L"{}", swkbdInternalState->formStringBuffer)); + + static std::chrono::steady_clock::time_point s_last_tick = tick_cached(); + static bool s_blink_state = false; + const auto now = tick_cached(); + + if (std::chrono::duration_cast<std::chrono::milliseconds>(now - s_last_tick).count() >= 500) + { + s_blink_state = !s_blink_state; + s_last_tick = now; + } + + if (s_blink_state) + text += "|"; + + ImGui::PushTextWrapPos(); + ImGui::TextUnformatted(text.c_str(), text.c_str() + text.size()); + ImGui::PopTextWrapPos(); + + position.y += ImGui::GetWindowSize().y + 100.0f; + + ImGui::End(); + } + + ImGui::PopFont(); + + ImGui::SetNextWindowPos(position, ImGuiCond_Always, pivot); + //ImGui::SetNextWindowSize({ io.DisplaySize.x * 0.9f , 0.0f}, ImGuiCond_Always); + ImGui::SetNextWindowBgAlpha(0.9f); + ImGui::PushFont(textFont); + + if (ImGui::Begin(fmt::format("Software keyboard##SoftwareKeyboard{}",mainWindow).c_str(), nullptr, kPopupFlags)) + { + if(swkbdInternalState->shiftActivated) + { + const char* keys[] = + { + "#", "[", "]", "$", "%", "^", "&", "*", "(", ")", "_", _utf8WrapperPtr(ICON_FA_ARROW_CIRCLE_LEFT), "\n", + "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "@", "\n", + "A", "S", "D", "F", "G", "H", "J", "K", "L", ";", "\"", "\n", + "Z", "X", "C", "V", "B", "N", "M", "<", ">", "+", "=", "\n", + _utf8WrapperPtr(ICON_FA_ARROW_UP), " ", _utf8WrapperPtr(ICON_FA_CHECK) + }; + for (auto key : keys) + { + if (*key != '\n') + { + if (ImGui::Button(key, { *key == ' ' ? 537 : (button_len + 5), 0})) + { + if (strcmp(key, _utf8WrapperPtr(ICON_FA_ARROW_CIRCLE_LEFT)) == 0) + swkbd_keyInput(8); + else if (strcmp(key, _utf8WrapperPtr(ICON_FA_ARROW_UP)) == 0) + swkbdInternalState->shiftActivated = !swkbdInternalState->shiftActivated; + else if (strcmp(key, _utf8WrapperPtr(ICON_FA_CHECK)) == 0) + swkbd_keyInput(13); + else + swkbd_keyInput(*key); + } + + ImGui::SameLine(); + } + else + ImGui::NewLine(); + } + } + else + { + const char* keys[] = + { + "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", _utf8WrapperPtr(ICON_FA_ARROW_CIRCLE_LEFT), "\n", + "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "/", "\n", + "a", "s", "d", "f", "g", "h", "j", "k", "l", ":", "'", "\n", + "z", "x", "c", "v", "b", "n", "m", ",", ".", "?", "!", "\n", + _utf8WrapperPtr(ICON_FA_ARROW_UP), " ", _utf8WrapperPtr(ICON_FA_CHECK) + }; + for (auto key : keys) + { + if (*key != '\n') + { + if (ImGui::Button(key, { *key == ' ' ? 537 : (button_len + 5), 0 })) + { + if (strcmp(key, _utf8WrapperPtr(ICON_FA_ARROW_CIRCLE_LEFT)) == 0) + swkbd_keyInput(8); + else if (strcmp(key, _utf8WrapperPtr(ICON_FA_ARROW_UP)) == 0) + swkbdInternalState->shiftActivated = !swkbdInternalState->shiftActivated; + else if (strcmp(key, _utf8WrapperPtr(ICON_FA_CHECK)) == 0) + swkbd_keyInput(13); + else + swkbd_keyInput(*key); + } + + ImGui::SameLine(); + } + else + ImGui::NewLine(); + } + } + ImGui::NewLine(); + + + ImGui::End(); + } + + if (io.NavInputs[ImGuiNavInput_Cancel] > 0) + { + if(!swkbdInternalState->cancelState) + swkbd_keyInput(8); // backspace + swkbdInternalState->cancelState = true; + } + else + swkbdInternalState->cancelState = false; + + if (io.NavInputs[ImGuiNavInput_Input] > 0) + { + if (!swkbdInternalState->returnState) + swkbd_keyInput(13); // return + swkbdInternalState->returnState = true; + } + else + swkbdInternalState->returnState = false; + + ImGui::PopFont(); + ImGui::PopStyleColor(); +} + +bool swkbd_hasKeyboardInputHook() +{ + return swkbdInternalState != NULL && swkbdInternalState->isActive; +} + +void swkbd_finishInput() +{ + swkbdInternalState->decideButtonWasPressed = true; // currently we always accept the input +} + +typedef struct +{ + uint32be beginIndex; + uint32be endIndex; +}changeStringParam_t; + +SysAllocator<changeStringParam_t> _changeStringParam; + +void swkbd_inputStringChanged() +{ + if( true )//swkbdInternalState->keyboardOnlyMode ) + { + // write changed string to application's string buffer + uint32 stringBufferSize = swkbdInternalState->keyboardArg.receiverArg.stringBufSize; // in 2-byte words + if( stringBufferSize > 1 ) + { + stringBufferSize--; // don't count the null-termination character + const auto stringBufferBE = swkbdInternalState->keyboardArg.receiverArg.stringBuf.GetPtr(); + sint32 copyLength = std::min((sint32)stringBufferSize, swkbdInternalState->formStringLength); + for(sint32 i=0; i<copyLength; i++) + { + stringBufferBE[i] = swkbdInternalState->formStringBuffer[i]; + } + stringBufferBE[copyLength] = '\0'; + + //swkbdInternalState->keyboardArg.cursorPos = copyLength; + } + // IEventReceiver callback + if (swkbdInternalState->keyboardArg.receiverArg.IEventReceiver) + { + SwkbdIEventReceiver_t* eventReceiver = swkbdInternalState->keyboardArg.receiverArg.IEventReceiver.GetPtr(); + MPTR cbChangeString = eventReceiver->vTable->changeString.GetMPTR(); + if (cbChangeString) + { + changeStringParam_t* changeStringParam = _changeStringParam.GetPtr(); + changeStringParam->beginIndex = 0; + changeStringParam->endIndex = 0; + coreinitAsyncCallback_add(cbChangeString, 2, memory_getVirtualOffsetFromPointer(eventReceiver), _changeStringParam.GetMPTR()); + } + } + } +} + +void swkbd_keyInput(uint32 keyCode) +{ + if (keyCode == 8) // backspace + { + if (swkbdInternalState->formStringLength > 0) + swkbdInternalState->formStringLength--; + swkbdInternalState->formStringBuffer[swkbdInternalState->formStringLength] = '\0'; + swkbd_inputStringChanged(); + return; + } + else if (keyCode == 13) // return + { + swkbd_finishInput(); + return; + } + // check if allowed character + if ((keyCode >= 'a' && keyCode <= 'z') || + (keyCode >= 'A' && keyCode <= 'Z')) + { + // allowed + } + else if ((keyCode >= '0' && keyCode <= '9')) + { + // allowed + } + else if (keyCode == ' ' || keyCode == '.' || keyCode == '/' || keyCode == '?' || keyCode == '=') + { + // allowed + } + else if (keyCode < 32) + { + // control key + return; + } + else + ;// return; + // get max length + sint32 maxLength = swkbdInternalState->maxTextLength; + if (swkbdInternalState->keyboardOnlyMode) + { + uint32 stringBufferSize = swkbdInternalState->keyboardArg.receiverArg.stringBufSize; + if (stringBufferSize > 1) + { + maxLength = stringBufferSize - 1; // don't count the null-termination character + } + else + maxLength = 0; + } + // append character + if (swkbdInternalState->formStringLength < maxLength) + { + swkbdInternalState->formStringBuffer[swkbdInternalState->formStringLength] = keyCode; + swkbdInternalState->formStringLength++; + swkbdInternalState->formStringBuffer[swkbdInternalState->formStringLength] = '\0'; + swkbd_inputStringChanged(); + } +} + +namespace swkbd +{ + void load() + { + osLib_addFunction("swkbd", "SwkbdCreate__3RplFPUcQ3_2nn5swkbd10RegionTypeUiP8FSClient", swkbdExport_SwkbdCreate); + osLib_addFunction("swkbd", "SwkbdGetStateKeyboard__3RplFv", swkbdExport_SwkbdGetStateKeyboard); + osLib_addFunction("swkbd", "SwkbdGetStateInputForm__3RplFv", swkbdExport_SwkbdGetStateInputForm); + osLib_addFunction("swkbd", "SwkbdSetReceiver__3RplFRCQ3_2nn5swkbd11ReceiverArg", swkbdExport_SwkbdSetReceiver); + osLib_addFunction("swkbd", "SwkbdAppearInputForm__3RplFRCQ3_2nn5swkbd9AppearArg", swkbdExport_SwkbdAppearInputForm); + osLib_addFunction("swkbd", "SwkbdDisappearInputForm__3RplFv", swkbdExport_SwkbdDisappearInputForm); + osLib_addFunction("swkbd", "SwkbdDisappearKeyboard__3RplFv", swkbdExport_SwkbdDisappearKeyboard); + osLib_addFunction("swkbd", "SwkbdAppearKeyboard__3RplFRCQ3_2nn5swkbd11KeyboardArg", swkbdExport_SwkbdAppearKeyboard); + osLib_addFunction("swkbd", "SwkbdGetInputFormString__3RplFv", swkbdExport_SwkbdGetInputFormString); + osLib_addFunction("swkbd", "SwkbdIsDecideOkButton__3RplFPb", swkbdExport_SwkbdIsDecideOkButton); + osLib_addFunction("swkbd", "SwkbdInitLearnDic__3RplFPv", swkbdExport_SwkbdInitLearnDic); + osLib_addFunction("swkbd", "SwkbdGetDrawStringInfo__3RplFPQ3_2nn5swkbd14DrawStringInfo", swkbdExport_SwkbdGetDrawStringInfo); + osLib_addFunction("swkbd", "SwkbdIsNeedCalcSubThreadFont__3RplFv", swkbdExport_SwkbdIsNeedCalcSubThreadFont); + osLib_addFunction("swkbd", "SwkbdIsNeedCalcSubThreadPredict__3RplFv", swkbdExport_SwkbdIsNeedCalcSubThreadPredict); + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/swkbd/swkbd.h b/src/Cafe/OS/libs/swkbd/swkbd.h new file mode 100644 index 00000000..2456b25a --- /dev/null +++ b/src/Cafe/OS/libs/swkbd/swkbd.h @@ -0,0 +1,8 @@ +void swkbd_render(bool mainWindow); +bool swkbd_hasKeyboardInputHook(); +void swkbd_keyInput(uint32 keyCode); + +namespace swkbd +{ + void load(); +} diff --git a/src/Cafe/OS/libs/sysapp/sysapp.cpp b/src/Cafe/OS/libs/sysapp/sysapp.cpp new file mode 100644 index 00000000..0085ee19 --- /dev/null +++ b/src/Cafe/OS/libs/sysapp/sysapp.cpp @@ -0,0 +1,597 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "sysapp.h" +#include "Cafe/CafeSystem.h" +#include "Cafe/OS/libs/coreinit/coreinit_FG.h" + +typedef struct +{ + MEMPTR<char> argStr; + uint32be size; +}sysStandardArguments_t; + +typedef struct +{ + // guessed + /* +0x00 */ MEMPTR<uint8> sysAnchor; + /* +0x04 */ uint32be sysAnchorSize; + /* +0x08 */ MEMPTR<uint8> sysResult; + /* +0x0C */ uint32be sysResultSize; +}sysDeserializeStandardArguments_t; + +typedef struct +{ + // used for setting arguments, maybe wrong? + /* +0x00 */ sysStandardArguments_t standardArguments; + /* +0x08 */ uint32be mode; + /* +0x0C */ uint32be slot_id; + /* +0x10 */ MEMPTR<uint8> mii; +}sysMiiStudioArguments_t; + +typedef struct +{ + // used for getting arguments + /* +0x00 */ sysStandardArguments_t standardArguments; // ? + /* +0x08 */ uint32 ukn08; + /* +0x0C */ uint32 ukn0C; + /* +0x10 */ uint32be mode; + /* +0x14 */ uint32be slot_id; + /* +0x18 */ MEMPTR<uint8> mii; + /* +0x1C */ uint32be miiSize; +}sysMiiStudioArguments2_t; + +typedef struct +{ + /* +0x00 */ sysDeserializeStandardArguments_t standardArguments; + /* +0x10 */ uint32be jumpTo; // see below for list of available values + /* +0x14 */ uint32be needsReturn; + /* +0x18 */ uint32be firstBootMode; +}sysSettingsArguments_t; + +static_assert(sizeof(sysSettingsArguments_t) == 0x1C); +static_assert(offsetof(sysSettingsArguments_t, jumpTo) == 0x10); +static_assert(offsetof(sysSettingsArguments_t, needsReturn) == 0x14); +static_assert(offsetof(sysSettingsArguments_t, firstBootMode) == 0x18); + +/* +Sys settings jumpTo values: +0x00 -> Normal launch +0x01 -> Internet settings +0x02 -> Data management +0x03 -> TV remote +0x04 -> Date settings +0x05 -> Country settings +0x06 -> System update + +0x64 -> Initial system configuration (after Wii U GamePad was configured) +0x65 -> Crashes +0x66 -> Delete All Content and Settings (throws erreula after a second) +0x67 -> Quick Start Settings +0x68 -> TV connection type +0x69 -> Data management +0xFF -> Data transfer +*/ + +typedef struct +{ + /* +0x00 */ sysDeserializeStandardArguments_t standardArguments; // guessed + /* +0x10 */ uint32be ukn10; + /* +0x14 */ uint32be ukn14; +}eshopArguments_t; + +static_assert(sizeof(eshopArguments_t) == 0x18); + +#define SYS_STANDARD_ARGS_MAX_LEN 0x1000 + +#define OS_COPY_MAX_DATA_SIZE (0x400000-4) + +void appendDataToBuffer(uint32 currentCopyDataSize, char* str, sint32 size) +{ + cemu_assert_unimplemented(); +} + +sint32 _serializeArgsToBuffer(uint32 currentCopyDataSize, const char* argName, char* str, sint32 size, void (*appendFunc)(uint32 currentCopyDataSize, char* str, sint32 size)) +{ + if (strnlen(argName, 0x41) == 0x40) + return -0x2710; + if (size > 0x200000) + return -0x7530; + char tempStr[0x80]; + if (size != 0 && str != nullptr) + { + sint32 textSize = sprintf(tempStr, "<%s %s=%u>", argName, "size", size); + if ((currentCopyDataSize + textSize + size) >= OS_COPY_MAX_DATA_SIZE) + return 0xFFFF63C0; + appendFunc(currentCopyDataSize, str, size); + appendFunc(currentCopyDataSize, tempStr, textSize); + } + else + { + sint32 textSize = sprintf(tempStr, "<%s>", argName); + appendFunc(currentCopyDataSize, tempStr, textSize); + } + return 0; +} + +sint32 SYSPackArgs() +{ + uint32 currentCopyDataSize = coreinit::__OSGetCopyDataSize(); + char sysPackStr[128]; + sint32 textSize = sprintf(sysPackStr, "<%s %s=%u>", "sys:pack", "size", currentCopyDataSize); + if ((currentCopyDataSize + textSize) > OS_COPY_MAX_DATA_SIZE) + return 0xFFFF4480; + coreinit::__OSAppendCopyData((uint8*)sysPackStr, textSize); + return 0; +} + +sint32 SYSSerializeSysArgs(const char* argName, char* str, sint32 size) +{ + uint32 currentCopyDataSize = coreinit::__OSGetCopyDataSize(); + sint32 r = _serializeArgsToBuffer(currentCopyDataSize, argName, str, size, appendDataToBuffer); + if (r < 0) + return r; + return 0; +} + +sint32 _SYSSerializeStandardArgsIn(sysStandardArguments_t* standardArgs) +{ + if (strnlen(standardArgs->argStr.GetPtr(), SYS_STANDARD_ARGS_MAX_LEN + 4) > SYS_STANDARD_ARGS_MAX_LEN) + { + return 0xFFFF63C0; + } + SYSSerializeSysArgs("sys:anchor", standardArgs->argStr.GetPtr(), standardArgs->size); + SYSPackArgs(); + return 0; +} + +sint32 _SYSAppendCallerInfo() +{ + // todo + return 0; +} + +typedef struct +{ + uint32be id; + uint32be type; + // union data here - todo +}sysArgSlot_t; + +#define SYS_ARG_ID_END 0 // indicates end of sysArgSlot_t array +#define SYS_ARG_ID_ANCHOR 100 + +typedef struct +{ + char* argument; + uint32 size; + uint8* data; +}deserializedArg_t; + +uint32 _sysArg_packSize = 0; + +void clearSysArgs() +{ + _sysArg_packSize = 0; +} + +void deserializeSysArg(deserializedArg_t* deserializedArg) +{ + if (strcmp(deserializedArg->argument, "sys:pack") == 0) + _sysArg_packSize = deserializedArg->size; + // todo +} + +sint32 _deserializeSysArgsEx2(uint8* copyDataPtr, sint32 copyDataSize, void(*cbDeserializeArg)(deserializedArg_t* deserializedArg, void* customParam), void* customParam) +{ + sint32 idx = copyDataSize - 1; + sint32 argSlotIndex = 0; + while (idx >= 0) + { + if (copyDataPtr[idx] == '>') + { + // find matching '<' + sint32 idxStart = idx; + while (true) + { + idxStart--; + if (idxStart < 0) + return 1; + if (copyDataPtr[idxStart] == '<') + break; + } + sint32 idxDataEnd = idxStart; + idxStart++; + // parse argument name + char argumentName[64]; + sint32 idxCurrent = idxStart; + while( idxCurrent <= idx ) + { + argumentName[idxCurrent - idxStart] = copyDataPtr[idxCurrent]; + if ((idxCurrent - idxStart) >= 60) + return 1; + if (copyDataPtr[idxCurrent] == ' ' || copyDataPtr[idxCurrent] == '>') + { + argumentName[idxCurrent - idxStart] = '\0'; + break; + } + idxCurrent++; + } + // parse size (if any) + while (copyDataPtr[idxCurrent] == ' ') + idxCurrent++; + sint32 argumentDataSize; + if (copyDataPtr[idxCurrent] == '>') + { + argumentDataSize = 0; + } + else if ((copyDataSize - idxCurrent) >= 5 && memcmp(copyDataPtr+idxCurrent, "size", 4) == 0) + { + idxCurrent += 4; + // skip whitespaces and '=' + while (copyDataPtr[idxCurrent] == ' ' || copyDataPtr[idxCurrent] == '=') + idxCurrent++; + // parse size value + char tempSizeStr[32]; + sint32 sizeParamLen = idx - idxCurrent; + if (sizeParamLen < 0 || sizeParamLen >= 30) + return 1; + memcpy(tempSizeStr, copyDataPtr+idxCurrent, sizeParamLen); + tempSizeStr[sizeParamLen] = '\0'; + argumentDataSize = atol(tempSizeStr); + } + else + { + assert_dbg(); + return 1; + } + idx = idxStart - argumentDataSize - 1; // beginning of data + deserializedArg_t deserializedArg = { 0 }; + deserializedArg.argument = argumentName; + deserializedArg.size = argumentDataSize; + deserializedArg.data = copyDataPtr + idx; + deserializeSysArg(&deserializedArg); + if (cbDeserializeArg) + cbDeserializeArg(&deserializedArg, customParam); + + idx--; + } + else if (copyDataPtr[idx] == ']') + { + assert_dbg(); + } + else + return 1; + } + return 0; +} + +void _deserializeSysArgsEx(void(*cbDeserializeArg)(deserializedArg_t* deserializedArg, void* customParam), void* customParam) +{ + sint32 copyDataSize = coreinit::__OSGetCopyDataSize(); + uint8* copyDataPtr = coreinit::__OSGetCopyDataPtr(); + _deserializeSysArgsEx2(copyDataPtr, copyDataSize, cbDeserializeArg, customParam); +} + +void SYSDeserializeSysArgs(void(*cbDeserializeArg)(deserializedArg_t* deserializedArg, void* customParam), sysArgSlot_t* argSlots) +{ + _deserializeSysArgsEx(cbDeserializeArg, argSlots); +} + +sint32 _deserializeArgs(void* customParam, sint32 customParamSize, void(*cbDeserializeArg)(deserializedArg_t* deserializedArg, void* customParam)) +{ + memset(customParam, 0, customParamSize); + clearSysArgs(); + _deserializeSysArgsEx(cbDeserializeArg, customParam); + return 0; +} + +void _unpackSysWorkaround() +{ + // unpack sys + clearSysArgs(); + _deserializeSysArgsEx(NULL, NULL); + if (_sysArg_packSize != 0) + { + coreinit::__OSResizeCopyData(_sysArg_packSize); + } +} + +void cbDeserializeArg_MiiMaker(deserializedArg_t* deserializedArg, void* customParam) +{ + sysMiiStudioArguments2_t* miiStudioArgs = (sysMiiStudioArguments2_t*)customParam; + + if (strcmp(deserializedArg->argument, "mode") == 0) + { + miiStudioArgs->mode = *(uint32be*)deserializedArg->data; + } + else if (strcmp(deserializedArg->argument, "mii") == 0) + { + assert_dbg(); + } + else if (strcmp(deserializedArg->argument, "slot_id") == 0) + { +#ifndef PUBLIC_RELEASE + assert_dbg(); +#endif + } + else + { +#ifndef PUBLIC_RELEASE + assert_dbg(); +#endif + } +} + +sint32 _SYSGetMiiStudioArgs(sysMiiStudioArguments2_t* miiStudioArgs) +{ + _unpackSysWorkaround(); + _deserializeArgs(miiStudioArgs, sizeof(sysMiiStudioArguments2_t), cbDeserializeArg_MiiMaker); + return 0; +} + +void sysappExport__SYSGetMiiStudioArgs(PPCInterpreter_t* hCPU) +{ + ppcDefineParamStructPtr(miiStudioArgs, sysMiiStudioArguments2_t, 0); + sint32 r = _SYSGetMiiStudioArgs(miiStudioArgs); + osLib_returnFromFunction(hCPU, r); +} + +void cbDeserializeArg_SysSettings(deserializedArg_t* deserializedArg, void* customParam) +{ + sysSettingsArguments_t* settingsArgs = (sysSettingsArguments_t*)customParam; + if (boost::iequals(deserializedArg->argument, "jump_to")) + { + cemu_assert_unimplemented(); + } + else if (boost::iequals(deserializedArg->argument, "first_boot_kind")) + { + cemu_assert_unimplemented(); + } + else if (boost::iequals(deserializedArg->argument, "needs_return")) + { + settingsArgs->needsReturn = 1; + } + else + cemu_assert_unimplemented(); +} + +sint32 _SYSGetSettingsArgs(sysSettingsArguments_t* settingsArgs) +{ + _unpackSysWorkaround(); + memset(settingsArgs, 0, sizeof(sysSettingsArguments_t)); + return _deserializeArgs(settingsArgs, sizeof(sysSettingsArguments_t), cbDeserializeArg_SysSettings); +} + +void sysappExport__SYSGetSettingsArgs(PPCInterpreter_t* hCPU) +{ + ppcDefineParamStructPtr(settingsArgs, sysSettingsArguments_t, 0); + sint32 r = _SYSGetSettingsArgs(settingsArgs); + + if (settingsArgs->jumpTo == 0) // workaround when launching sys settings directly + settingsArgs->jumpTo = 0x00; + + osLib_returnFromFunction(hCPU, r); +} + +struct +{ + uint64 t0; + uint64 t1; + uint64 t2; +}systemApplicationTitleId[] = { + { 0x5001010040000, 0x5001010040100, 0x5001010040200 }, // Wii U Menu + { 0x5001010047000, 0x5001010047100, 0x5001010047200 }, // System Settings + { 0x5001010048000, 0x5001010048100, 0x5001010048200 }, // Parental Controls + { 0x5001010049000, 0x5001010049100, 0x5001010049200 }, // User Settings + { 0x500101004A000, 0x500101004A100, 0x500101004A200 }, // Mii Maker + { 0x500101004B000, 0x500101004B100, 0x500101004B200 }, // Account Settings + { 0x500101004C000, 0x500101004C100, 0x500101004C200 }, // Daily Log + { 0x500101004D000, 0x500101004D100, 0x500101004D200 }, // Notifications + { 0x500101004E000, 0x500101004E100, 0x500101004E200 }, // Health and Safety Information + { 0x5001B10059000, 0x5001B10059100, 0x5001B10059200 }, // Wii U Electronic Manual + { 0x500101005A000, 0x500101005A100, 0x500101005A200 }, // Wii U Chat + { 0x5001010062000, 0x5001010062100, 0x5001010062200 } // Software/Data Transfer +}; + +uint64 _SYSGetSystemApplicationTitleIdByProdArea(uint32 systemApplicationId, uint32 platformRegion) +{ + if (systemApplicationId >= (sizeof(systemApplicationTitleId) / sizeof(systemApplicationTitleId[0])) ) + assert_dbg(); + + if (platformRegion == 1) + { + return systemApplicationTitleId[systemApplicationId].t0; + } + else if (platformRegion == 4 || platformRegion == 8) + { + return systemApplicationTitleId[systemApplicationId].t2; + } + return systemApplicationTitleId[systemApplicationId].t1; +} + +uint64 _SYSGetSystemApplicationTitleId(sint32 index) +{ + uint32 region = (uint32)CafeSystem::GetPlatformRegion(); + return _SYSGetSystemApplicationTitleIdByProdArea(index, region); +} + +void sysappExport__SYSGetSystemApplicationTitleId(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(systemApplicationId, 0); + forceLogDebug_printf("_SYSGetSystemApplicationTitleId(0x%d)", hCPU->gpr[3]); + + uint64 titleId = _SYSGetSystemApplicationTitleId(systemApplicationId); + osLib_returnFromFunction64(hCPU, titleId); +} + +void __LaunchMiiMaker(sysMiiStudioArguments_t* args, uint32 platformRegion) +{ + coreinit::__OSClearCopyData(); + if (args) + { + _SYSSerializeStandardArgsIn(&args->standardArguments); + SYSSerializeSysArgs("mode", (char*)&args->mode, 4); + SYSSerializeSysArgs("slot_id", (char*)&args->slot_id, 4); + if(args->mii) + SYSSerializeSysArgs("mii", (char*)args->mii.GetPtr(), 0x60); + } + _SYSAppendCallerInfo(); + uint64 titleIdToLaunch = _SYSGetSystemApplicationTitleIdByProdArea(4, platformRegion); + cemu_assert_unimplemented(); +} + +void _SYSLaunchMiiStudio(sysMiiStudioArguments_t* args) +{ + __LaunchMiiMaker(args, (uint32)CafeSystem::GetPlatformRegion()); +} + +void sysappExport__SYSLaunchMiiStudio(PPCInterpreter_t* hCPU) +{ + ppcDefineParamStructPtr(args, sysMiiStudioArguments_t, 0); + _SYSLaunchMiiStudio(args); +} + +void sysappExport__SYSReturnToCallerWithStandardResult(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32BEPtr(resultPtr, 0); + forceLog_printf("_SYSReturnToCallerWithStandardResult(0x%08x) result: 0x%08x", hCPU->gpr[3], (uint32)*resultPtr); + while (true) std::this_thread::sleep_for(std::chrono::milliseconds(10)); +} + +void sysappExport__SYSGetEShopArgs(PPCInterpreter_t* hCPU) +{ + ppcDefineParamStructPtr(args, eshopArguments_t, 0); + forceLogDebug_printf("_SYSGetEShopArgs() - placeholder"); + memset(args, 0, sizeof(eshopArguments_t)); + + osLib_returnFromFunction(hCPU, 0); +} + +void sysappExport_SYSGetUPIDFromTitleID(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU64(titleId, 0); + forceLogDebug_printf("SYSGetUPIDFromTitleID(0x%08x%08x)", hCPU->gpr[3], hCPU->gpr[4]); + uint32 titleIdHigh = (titleId >> 32); + uint32 titleIdLow = (uint32)(titleId & 0xFFFFFFFF); + if ((titleIdHigh & 0xFFFF0000) != 0x50000) + { + osLib_returnFromFunction(hCPU, -1); + return; + } + if ((titleIdLow & 0xF0000000) != 0x10000000) + { + osLib_returnFromFunction(hCPU, -1); + return; + } + uint32 titleId_type = (titleIdHigh & 0xFF); + + if (titleId_type == 0x30) + { + // applet + uint32 ukn = ((titleIdLow >> 12) & 0xFFFF); + if (ukn < 0x10) + { + osLib_returnFromFunction(hCPU, -1); + return; + } + else if (ukn <= 0x19) + { + uint8 appletTable10[10] = { 5,6,8,3,0xA,0xB,9,4,0xC,7 }; + osLib_returnFromFunction(hCPU, appletTable10[ukn - 0x10]); + return; + } + else if (ukn <= 0x20) + { + assert_dbg(); + osLib_returnFromFunction(hCPU, -1); + return; + } + else if (ukn <= 0x29) + { + uint8 appletTable20[10] = {5,6,8,3,0xA,0xB,9,4,0xC,7}; + osLib_returnFromFunction(hCPU, appletTable20[ukn-0x20]); + return; + } + assert_dbg(); + osLib_returnFromFunction(hCPU, -1); + return; + } + else if (titleId_type == 0x10) + { + // system application + if (((titleIdLow >> 12) & 0xFFFF) == 0x40) + { + // Wii U menu + osLib_returnFromFunction(hCPU, 2); + return; + } + osLib_returnFromFunction(hCPU, 0xF); + return; + } + else if (titleId_type == 0x00 || titleId_type == 0x02) + { + osLib_returnFromFunction(hCPU, 0xF); + return; + } + osLib_returnFromFunction(hCPU, -1); +} + +void sysappExport_SYSGetVodArgs(PPCInterpreter_t* hCPU) +{ + forceLogDebug_printf("SYSGetVodArgs() - todo"); + osLib_returnFromFunction(hCPU, 1); +} + +struct SysLauncherArgs28 +{ + uint32 ukn00; + uint32 ukn04; + uint32 ukn08; + uint32 ukn0C; + uint32 ukn10; // caller title id? (8 byte) + uint32 ukn14; + uint32 ukn18; // launched title id? (8 byte) + uint32 ukn1C; + uint32 ukn20; // mode + uint32 ukn24; // slot +}; + +static_assert(sizeof(SysLauncherArgs28) == 0x28); + +void sysappExport__SYSGetLauncherArgs(PPCInterpreter_t* hCPU) +{ + forceLogDebug_printf("_SYSGetLauncherArgs(0x%08x) - todo\n", hCPU->gpr[3]); + + // todo: Handle OS library version. Older versions used a different struct (only 0x18 bytes?) + //ppcDefineParamStructPtr(launcherArgs, SysLauncherArgs, 0); + //memset(launcherArgs, 0, sizeof(SysLauncherArgs)); + + + osLib_returnFromFunction(hCPU, 0); // return argument is todo (probably number of args?) +} + +void sysappExport_SYSGetStandardResult(PPCInterpreter_t* hCPU) +{ + forceLogDebug_printf("SYSGetStandardResult(0x%08x,0x%08x,0x%08x)\n", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); + memset(memory_getPointerFromVirtualOffset(hCPU->gpr[3]), 0, 4); + + osLib_returnFromFunction(hCPU, 0); +} + +// register sysapp functions +void sysapp_load() +{ + osLib_addFunction("sysapp", "_SYSLaunchMiiStudio", sysappExport__SYSLaunchMiiStudio); + osLib_addFunction("sysapp", "_SYSGetMiiStudioArgs", sysappExport__SYSGetMiiStudioArgs); + osLib_addFunction("sysapp", "_SYSGetSettingsArgs", sysappExport__SYSGetSettingsArgs); + osLib_addFunction("sysapp", "_SYSReturnToCallerWithStandardResult", sysappExport__SYSReturnToCallerWithStandardResult); + + osLib_addFunction("sysapp", "_SYSGetSystemApplicationTitleId", sysappExport__SYSGetSystemApplicationTitleId); + osLib_addFunction("sysapp", "SYSGetUPIDFromTitleID", sysappExport_SYSGetUPIDFromTitleID); + + osLib_addFunction("sysapp", "_SYSGetEShopArgs", sysappExport__SYSGetEShopArgs); + + osLib_addFunction("sysapp", "SYSGetVodArgs", sysappExport_SYSGetVodArgs); + + osLib_addFunction("sysapp", "_SYSGetLauncherArgs", sysappExport__SYSGetLauncherArgs); + osLib_addFunction("sysapp", "SYSGetStandardResult", sysappExport_SYSGetStandardResult); +} diff --git a/src/Cafe/OS/libs/sysapp/sysapp.h b/src/Cafe/OS/libs/sysapp/sysapp.h new file mode 100644 index 00000000..dd4eb0b2 --- /dev/null +++ b/src/Cafe/OS/libs/sysapp/sysapp.h @@ -0,0 +1,3 @@ +void sysapp_load(); + +uint64 _SYSGetSystemApplicationTitleId(sint32 index); \ No newline at end of file diff --git a/src/Cafe/OS/libs/vpad/vpad.cpp b/src/Cafe/OS/libs/vpad/vpad.cpp new file mode 100644 index 00000000..0ad97620 --- /dev/null +++ b/src/Cafe/OS/libs/vpad/vpad.cpp @@ -0,0 +1,1209 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/HW/Espresso/PPCCallback.h" +#include "gui/wxgui.h" +#include "Cafe/OS/libs/vpad/vpad.h" +#include "audio/IAudioAPI.h" +#include "Cafe/OS/libs/coreinit/coreinit_Time.h" +#include <boost/range/iterator_range.hpp> +#include "config/ActiveSettings.h" +#include "Cafe/OS/libs/coreinit/coreinit_Alarm.h" +#include "input/InputManager.h" + +#ifdef PUBLIC_RELASE +#define vpadbreak() +#else +#define vpadbreak()// __debugbreak(); +#endif + +#define VPAD_READ_ERR_NONE 0 +#define VPAD_READ_ERR_NO_DATA -1 +#define VPAD_READ_ERR_NO_CONTROLLER -2 +#define VPAD_READ_ERR_SETUP -3 +#define VPAD_READ_ERR_LOCKED -4 +#define VPAD_READ_ERR_INIT -5 + +#define VPAD_TP_VALIDITY_VALID 0 +#define VPAD_TP_VALIDITY_INVALID_X 1 +#define VPAD_TP_VALIDITY_INVALID_Y 2 +#define VPAD_TP_VALIDITY_INVALID_XY (VPAD_TP_VALIDITY_INVALID_X | VPAD_TP_VALIDITY_INVALID_Y) + +#define VPAD_GYRO_ZERODRIFT_LOOSE 0 +#define VPAD_GYRO_ZERODRIFT_STANDARD 1 +#define VPAD_GYRO_ZERODRIFT_TIGHT 2 +#define VPAD_GYRO_ZERODRIFT_NONE 3 + +#define VPAD_PLAY_MODE_LOOSE 0 +#define VPAD_PLAY_MODE_TIGHT 1 + +#define VPAD_BUTTON_PROC_MODE_LOOSE 0 +#define VPAD_BUTTON_PROC_MODE_TIGHT 1 + +#define VPAD_LCD_MODE_MUTE 0 +#define VPAD_LCD_MODE_GAME_CONTROLLER 1 +#define VPAD_LCD_MODE_ON 0xFF + +#define VPAD_MOTOR_PATTERN_SIZE_MAX 15 +#define VPAD_MOTOR_PATTERN_LENGTH_MAX 120 + +#define VPAD_TP_1920x1080 0 +#define VPAD_TP_1280x720 1 +#define VPAD_TP_854x480 2 + +extern bool isLaunchTypeELF; + +bool debugUseDRC = true; +VPADDir g_vpadGyroDirOverwrite[VPAD_MAX_CONTROLLERS] = +{ + {{1.0f,0.0f,0.0f}, {0.0f,1.0f,0.0f}, {0.0f, 0.0f, 0.1f}}, + {{1.0f,0.0f,0.0f}, {0.0f,1.0f,0.0f}, {0.0f, 0.0f, 0.1f}} +}; +uint32 g_vpadGyroZeroDriftMode[VPAD_MAX_CONTROLLERS] = { VPAD_GYRO_ZERODRIFT_STANDARD, VPAD_GYRO_ZERODRIFT_STANDARD }; + +struct VPACGyroDirRevise_t +{ + bool enabled; + VPADDir vpadGyroDirReviseBase; + float weight; +} g_vpadGyroDirRevise[VPAD_MAX_CONTROLLERS] = {}; + +struct VPADAccParam_t +{ // TODO P: use + float playRadius; + float sensitivity; +} g_vpadAccParam[VPAD_MAX_CONTROLLERS] = { {0, 1}, {0, 1} }; + +uint32 g_vpadPlayMode[VPAD_MAX_CONTROLLERS] = { VPAD_PLAY_MODE_TIGHT, VPAD_PLAY_MODE_TIGHT }; // TODO P: use + +#define VPAD_MIN_CLAMP (0x102) +#define VPAD_MAX_CLAMP (0x397) + +struct VPADStickClamp +{ // TODO P: use + bool crossMode; // default is circular mode + + sint32 leftMax; + sint32 leftMin; + + sint32 rightMax; + sint32 rightMin; +} vpadStickClamp[VPAD_MAX_CONTROLLERS] = { {false, VPAD_MAX_CLAMP, VPAD_MIN_CLAMP}, {false, VPAD_MAX_CLAMP, VPAD_MIN_CLAMP} }; + +struct VPADCrossStickEmulationParams +{ // TODO P: use + float leftRotation; + float leftInputRange; + float leftRadius; + + float rightRotation; + float rightInputRange; + float rightRadius; +} vpadCrossStickEmulationParams[VPAD_MAX_CONTROLLERS] = {}; + +uint8 vpadButtonProcMode[VPAD_MAX_CONTROLLERS] = { VPAD_BUTTON_PROC_MODE_TIGHT, VPAD_BUTTON_PROC_MODE_TIGHT }; // TODO P: use +uint32 vpadLcdMode[VPAD_MAX_CONTROLLERS] = { VPAD_LCD_MODE_ON, VPAD_LCD_MODE_ON }; + +struct VPADTPCalibrationParam +{ // TODO P: use + uint16be offsetX; + uint16be offsetY; + float32be scaleX; + float32be scaleY; +} vpadTPCalibrationParam[VPAD_MAX_CONTROLLERS] = { {92, 254, (1280.0f / 3883.0f), (720.0f / 3694.0f)}, {92, 254, (1280.0f / 3883.0f), (720.0f / 3694.0f)} }; + +void _tpRawToResolution(sint32 x, sint32 y, sint32* outX, sint32* outY, sint32 width, sint32 height) +{ + x -= 92; + y = 4095.0 - y - 254; + + x = std::max(x, 0); + y = std::max(y, 0); + + *outX = (sint32)(((double)x / 3883.0) * (double)width); + *outY = (sint32)(((double)y / 3694.0) * (double)height); +} + + +namespace vpad +{ + enum class PlayMode : sint32 + { + Loose = 0, + Tight = 1 + }; + + enum class LcdMode + { + Off = 0, + ControllerOnly = 1, + On = 0xFF, + }; + + struct VPADTPCalibrationParam + { + sint16be x, y; + float32be scale_x, scale_y; + }; + + struct VPADTPData + { + uint16be x, y, touch, valid; + }; + + enum class VPADTPResolution + { + _1920x1080 = 0, + _1280x720 = 1, + _854_480 = 2, + }; + + enum class ButtonProcMode : uint8 + { + Loose = 0, + Tight = 1, + }; + + struct + { + coreinit::OSAlarm_t alarm; + + struct + { + uint64 drcLastCallTime = 0; + + struct AccParam + { + float radius, sensitivity; + } acc_param; + BtnRepeat btn_repeat; + + MEMPTR<void> sampling_callback; + PlayMode acc_play_mode; + + VPADTPCalibrationParam tp_calibration_param{}; + uint16 tp_size = 0xc; + + struct + { + float rotation, range, radius; + }cross_stick_emulation_l{}, cross_stick_emulation_r{}; + + ButtonProcMode button_proc_mode; + + struct + { + bool enabled = false; + struct + { + sint32 min = 0x102, max = 0x397; + } left{}, right{}; + }stick_cross_clamp{}; + + }controller_data[VPAD_MAX_CONTROLLERS]{}; + } g_vpad; + + + + void VPADSetAccParam(sint32 channel, float radius, float sensitivity) + { + inputLog_printf("VPADSetAccParam(%d, %f, %f)", channel, radius, sensitivity); + vpadbreak(); + g_vpad.controller_data[channel].acc_param.radius = radius; + g_vpad.controller_data[channel].acc_param.sensitivity = sensitivity; + } + + void VPADGetAccParam(sint32 channel, float* radius, float* sensitivity) + { + inputLog_printf("VPADGetAccParam(%d, %p, %p)", channel, (void*)radius, (void*)sensitivity); + vpadbreak(); + *radius = g_vpad.controller_data[channel].acc_param.radius; + *sensitivity = g_vpad.controller_data[channel].acc_param.sensitivity; + } + + sint32 VPADRead(sint32 channel, VPADStatus* status, uint32 length, sint32be* error) + { + //printf("VPADRead(%d,0x%08X,%d,0x%08x)\n", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6]); + /*ppcDefineParamU32(channel, 0); + ppcDefineParamStructPtr(status, VPADStatus_t, 1); + ppcDefineParamU32(length, 2); + ppcDefineParamPtr(error, uint32be, 3); + inputLog_printf("VPADRead(%d, _, %d)", channel, length);*/ + + // default init which should be always set + memset(status, 0x00, sizeof(VPADStatus_t)); + + // default misc + status->batteryLevel = 0xC0; // full battery + status->slideVolume = status->slideVolume2 = (uint8)((g_padVolume * 0xFF) / 100); + + // default touch + status->tpData.validity = VPAD_TP_VALIDITY_INVALID_XY; + status->tpProcessed1.validity = VPAD_TP_VALIDITY_INVALID_XY; + status->tpProcessed2.validity = VPAD_TP_VALIDITY_INVALID_XY; + + const auto controller = InputManager::instance().get_vpad_controller(channel); + if (!controller || debugUseDRC == false) + { + // no controller + if (error) + *error = VPAD_READ_ERR_NONE; // VPAD_READ_ERR_NO_DATA; // VPAD_READ_ERR_NO_CONTROLLER; + + return 1; + //osLib_returnFromFunction(hCPU, 1); return; + } + + if (channel != 0) + { + debugBreakpoint(); + } + + const bool vpadDelayEnabled = ActiveSettings::VPADDelayEnabled(); + + if (isLaunchTypeELF) + { + // hacky workaround for homebrew games calling VPADRead in an infinite loop + PPCCore_switchToScheduler(); + } + + if (!g_inputConfigWindowHasFocus) + { + if (channel <= 1 && vpadDelayEnabled) + { + uint64 currentTime = coreinit::coreinit_getOSTime(); + const auto dif = currentTime - vpad::g_vpad.controller_data[channel].drcLastCallTime; + if (dif <= (ESPRESSO_TIMER_CLOCK / 60ull)) + { + // not ready yet + if (error) + *error = VPAD_READ_ERR_NONE; + + return 0; + //osLib_returnFromFunction(hCPU, 0); return; + } + else if (dif <= ESPRESSO_TIMER_CLOCK) + { + vpad::g_vpad.controller_data[channel].drcLastCallTime += (ESPRESSO_TIMER_CLOCK / 60ull); + } + else + { + vpad::g_vpad.controller_data[channel].drcLastCallTime = currentTime; + } + } + controller->VPADRead(*status, vpad::g_vpad.controller_data[channel].btn_repeat); + if (error) + *error = VPAD_READ_ERR_NONE; + return 1; + } + else + { + if (error) + *error = VPAD_READ_ERR_NONE; + + return 1; + } + } + + void VPADSetBtnRepeat(sint32 channel, float delay, float pulse) + { + inputLog_printf("VPADSetBtnRepeat(%d, %f, %f)", channel, delay, pulse); + if(pulse == 0) + { + g_vpad.controller_data[channel].btn_repeat.delay = 40000; + g_vpad.controller_data[channel].btn_repeat.pulse = 0; + } + else + { + g_vpad.controller_data[channel].btn_repeat.delay = (sint32)((delay * 200.0f) + 0.5f); + g_vpad.controller_data[channel].btn_repeat.pulse = (sint32)((pulse * 200.0f) + 0.5f); + } + } + + + void VPADSetAccPlayMode(sint32 channel, PlayMode play_mode) + { + inputLog_printf("VPADSetAccPlayMode(%d, %d)", channel, (int)play_mode); + vpadbreak(); + g_vpad.controller_data[channel].acc_play_mode = play_mode; + } + + PlayMode VPADGetAccPlayMode(sint32 channel) + { + inputLog_printf("VPADGetAccPlayMode(%d)", channel); + vpadbreak(); + return g_vpad.controller_data[channel].acc_play_mode; + } + + void* VPADSetSamplingCallback(sint32 channel, void* callback) + { + inputLog_printf("VPADSetSamplingCallback(%d, 0x%x)", channel, MEMPTR(callback).GetMPTR()); + vpadbreak(); + + void* result = g_vpad.controller_data[channel].sampling_callback; + g_vpad.controller_data[channel].sampling_callback = callback; + return result; + } + + sint32 VPADCalcTPCalibrationParam(VPADTPCalibrationParam* p, uint16 raw_x1, uint16 raw_y1, uint16 x1, uint16 y1, uint16 raw_x2, uint16 raw_y2, uint16 x2, uint16 y2) + { + cemu_assert_unimplemented(); + return 1; + } + + void VPADGetTPCalibrationParam(sint32 channel, VPADTPCalibrationParam* param) + { + inputLog_printf("VPADGetTPCalibrationParam(%d, 0x%x)", channel, MEMPTR(param).GetMPTR()); + vpadbreak(); + + *param = g_vpad.controller_data[channel].tp_calibration_param; + } + + void VPADSetTPCalibrationParam(sint32 channel, VPADTPCalibrationParam* param) + { + inputLog_printf("VPADSetTPCalibrationParam(%d, 0x%x)", channel, MEMPTR(param).GetMPTR()); + vpadbreak(); + + g_vpad.controller_data[channel].tp_calibration_param = *param; + } + + void VPADGetTPCalibratedPoint(sint32 channel, VPADTPData* data, VPADTPData* raw) + { + inputLog_printf("VPADGetTPCalibratedPoint(%d, 0x%x, 0x%x)", channel, MEMPTR(data).GetMPTR(), MEMPTR(raw).GetMPTR()); + vpadbreak(); + + const auto& controller_data = g_vpad.controller_data[channel]; + uint16 x = (uint16)((float)raw->x - ((float)controller_data.tp_calibration_param.x * controller_data.tp_calibration_param.scale_x)); + uint16 y = (uint16)((float)raw->x - ((float)controller_data.tp_calibration_param.y * controller_data.tp_calibration_param.scale_y)); + + const int tp_size = (int)controller_data.tp_size; + + int tmpx = x; + if(x <= (int)controller_data.tp_size) + tmpx = (int)controller_data.tp_size; + + int tmpy = y; + if(y <= (int)controller_data.tp_size) + tmpy = (int)controller_data.tp_size; + + if((0x500 - tp_size) <= tmpx) + x = (0x500 - tp_size); + + if((0x2d0 - tp_size) <= tmpy) + y = (0x2d0 - tp_size); + + data->x = x; + data->y = y; + data->touch = raw->touch; + data->valid = raw->valid; + } + + void VPADGetTPCalibratedPointEx(sint32 channel, VPADTPResolution resolution, VPADTPData* data, VPADTPData* raw) + { + inputLog_printf("VPADGetTPCalibratedPointEx(%d, %d, 0x%x, 0x%x)", channel, (int)resolution, MEMPTR(data).GetMPTR(), MEMPTR(raw).GetMPTR()); + vpadbreak(); + + } + + void VPADSetCrossStickEmulationParamsL(sint32 channel, float rotation, float range, float radius) + { + inputLog_printf("VPADSetCrossStickEmulationParamsL(%d, %f, %f, %f)", channel, rotation, range, radius); + vpadbreak(); + + if (range < 0 || 90.0f < range) + return; + + if (radius < 0 || 1.0f < radius) + return; + + g_vpad.controller_data[channel].cross_stick_emulation_l.rotation = rotation; + g_vpad.controller_data[channel].cross_stick_emulation_l.range = range; + g_vpad.controller_data[channel].cross_stick_emulation_l.radius = radius; + } + + void VPADSetCrossStickEmulationParamsR(sint32 channel, float rotation, float range, float radius) + { + inputLog_printf("VPADSetCrossStickEmulationParamsR(%d, %f, %f, %f)", channel, rotation, range, radius); + vpadbreak(); + + if (range < 0 || 90.0f < range) + return; + + if (radius < 0 || 1.0f < radius) + return; + + g_vpad.controller_data[channel].cross_stick_emulation_r.rotation = rotation; + g_vpad.controller_data[channel].cross_stick_emulation_r.range = range; + g_vpad.controller_data[channel].cross_stick_emulation_r.radius = radius; + } + + void VPADGetCrossStickEmulationParamsL(sint32 channel, float* rotation, float* range, float* radius) + { + inputLog_printf("VPADGetCrossStickEmulationParamsL(%d, 0x%x, 0x%x, 0x%x)", channel, MEMPTR(rotation).GetMPTR(), MEMPTR(range).GetMPTR(), MEMPTR(radius).GetMPTR()); + vpadbreak(); + + *rotation = g_vpad.controller_data[channel].cross_stick_emulation_l.rotation; + *range = g_vpad.controller_data[channel].cross_stick_emulation_l.range; + *radius = g_vpad.controller_data[channel].cross_stick_emulation_l.radius; + } + + void VPADGetCrossStickEmulationParamsR(sint32 channel, float* rotation, float* range, float* radius) + { + inputLog_printf("VPADGetCrossStickEmulationParamsR(%d, 0x%x, 0x%x, 0x%x)", channel, MEMPTR(rotation).GetMPTR(), MEMPTR(range).GetMPTR(), MEMPTR(radius).GetMPTR()); + vpadbreak(); + + *rotation = g_vpad.controller_data[channel].cross_stick_emulation_r.rotation; + *range = g_vpad.controller_data[channel].cross_stick_emulation_r.range; + *radius = g_vpad.controller_data[channel].cross_stick_emulation_r.radius; + } + + ButtonProcMode VPADGetButtonProcMode(sint32 channel) + { + inputLog_printf("VPADGetButtonProcMode(%d)", channel); + vpadbreak(); + + return g_vpad.controller_data[channel].button_proc_mode; + } + + void VPADSetButtonProcMode(sint32 channel, ButtonProcMode mode) + { + inputLog_printf("VPADSetButtonProcMode(%d, %d)", channel, (int)mode); + vpadbreak(); + + g_vpad.controller_data[channel].button_proc_mode = mode; + } + + void VPADEnableStickCrossClamp(sint32 channel) + { + inputLog_printf("VPADEnableStickCrossClamp(%d)", channel); + vpadbreak(); + g_vpad.controller_data[channel].stick_cross_clamp.enabled = true; + } + + void VPADDisableStickCrossClamp(sint32 channel) + { + inputLog_printf("VPADDisableStickCrossClamp(%d)", channel); + vpadbreak(); + g_vpad.controller_data[channel].stick_cross_clamp.enabled = false; + } + + void VPADSetLStickClampThreshold(sint32 channel, sint32 max, sint32 min) + { + inputLog_printf("VPADSetLStickClampThreshold(%d, %d, %d)", channel, max, min); + vpadbreak(); + g_vpad.controller_data[channel].stick_cross_clamp.left.max = std::min(0x397, max); + g_vpad.controller_data[channel].stick_cross_clamp.left.min = std::max(0x102, min); + } + + void VPADSetRStickClampThreshold(sint32 channel, sint32 max, sint32 min) + { + inputLog_printf("VPADSetRStickClampThreshold(%d, %d, %d)", channel, max, min); + vpadbreak(); + g_vpad.controller_data[channel].stick_cross_clamp.right.max = std::min(0x397, max); + g_vpad.controller_data[channel].stick_cross_clamp.right.min = std::max(0x102, min); + } + + void VPADGetLStickClampThreshold(sint32 channel, sint32* max, sint32* min) + { + inputLog_printf("VPADGetLStickClampThreshold(%d, 0x%x, 0x%x)", channel, MEMPTR(max).GetMPTR(), MEMPTR(min).GetMPTR()); + vpadbreak(); + *max = g_vpad.controller_data[channel].stick_cross_clamp.left.max; + *min = g_vpad.controller_data[channel].stick_cross_clamp.left.min; + } + + void VPADGetRStickClampThreshold(sint32 channel, sint32* max, sint32* min) + { + inputLog_printf("VPADGetRStickClampThreshold(%d, 0x%x, 0x%x)", channel, MEMPTR(max).GetMPTR(), MEMPTR(min).GetMPTR()); + vpadbreak(); + *max = g_vpad.controller_data[channel].stick_cross_clamp.right.max; + *min = g_vpad.controller_data[channel].stick_cross_clamp.right.min; + } +} + + +void vpadExport_VPADGetAccParam(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + ppcDefineParamPtr(playRadius, float32be, 1); + ppcDefineParamPtr(sensitivity, float32be, 2); + inputLog_printf("VPADGetAccParam(%d)", channel); + + if (channel < VPAD_MAX_CONTROLLERS) + { + *playRadius = g_vpadAccParam[channel].playRadius; + *sensitivity = g_vpadAccParam[channel].sensitivity; + } + else + { + debugBreakpoint(); + } + + osLib_returnFromFunction(hCPU, 0); +} + +void vpadExport_VPADSetAccParam(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + inputLog_printf("VPADSetAccParam(%d, %f, %f)", channel, hCPU->fpr[1].fpr, hCPU->fpr[2].fpr); + + if (channel < VPAD_MAX_CONTROLLERS) + { + g_vpadAccParam[channel].playRadius = hCPU->fpr[1].fpr; + g_vpadAccParam[channel].sensitivity = hCPU->fpr[2].fpr; + } + else + { + debugBreakpoint(); + } + + osLib_returnFromFunction(hCPU, 0); +} + +void vpadExport_VPADGetAccPlayMode(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + inputLog_printf("VPADGetAccPlayMode(%d)", channel); + + if (channel < VPAD_MAX_CONTROLLERS) + { + osLib_returnFromFunction(hCPU, g_vpadPlayMode[channel]); + } + else + { + debugBreakpoint(); + osLib_returnFromFunction(hCPU, VPAD_PLAY_MODE_TIGHT); + } +} + +void vpadExport_VPADSetAccPlayMode(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + ppcDefineParamU32(playMode, 1); + inputLog_printf("VPADSetAccPlayMode(%d, %d)", channel, playMode); + + if (channel < VPAD_MAX_CONTROLLERS) + { + g_vpadPlayMode[channel] = playMode; + } + else + { + debugBreakpoint(); + } + + osLib_returnFromFunction(hCPU, 0); +} + +void vpadExport_VPADEnableStickCrossClamp(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + inputLog_printf("VPADEnableStickCrossClamp(%d)", channel); + + if (channel < VPAD_MAX_CONTROLLERS) + { + vpadStickClamp[channel].crossMode = true; + } + else + { + debugBreakpoint(); + } + + osLib_returnFromFunction(hCPU, 0); +} + +void vpadExport_VPADDisableStickCrossClamp(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + inputLog_printf("VPADDisableStickCrossClamp(%d)", channel); + + if (channel < VPAD_MAX_CONTROLLERS) + { + vpadStickClamp[channel].crossMode = false; + } + else + { + debugBreakpoint(); + } + + osLib_returnFromFunction(hCPU, 0); +} + +void vpadExport_VPADSetLStickClampThreshold(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + ppcDefineParamS32(maxValue, 1); + ppcDefineParamS32(minValue, 2); + inputLog_printf("VPADSetLStickClampThreshold(%d, %d, %d)", channel, maxValue, minValue); + + if (channel < VPAD_MAX_CONTROLLERS) + { + vpadStickClamp[channel].leftMax = std::min(VPAD_MAX_CLAMP, maxValue); + vpadStickClamp[channel].leftMin = std::max(VPAD_MIN_CLAMP, minValue); + } + else + { + debugBreakpoint(); + } + + osLib_returnFromFunction(hCPU, 0); +} + +void vpadExport_VPADSetRStickClampThreshold(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + ppcDefineParamS32(maxValue, 1); + ppcDefineParamS32(minValue, 2); + inputLog_printf("VPADSetRStickClampThreshold(%d, %d, %d)", channel, maxValue, minValue); + + if (channel < VPAD_MAX_CONTROLLERS) + { + vpadStickClamp[channel].rightMax = std::min(VPAD_MAX_CLAMP, maxValue); + vpadStickClamp[channel].rightMin = std::max(VPAD_MIN_CLAMP, minValue); + } + else + { + debugBreakpoint(); + } + + osLib_returnFromFunction(hCPU, 0); +} + +void vpadExport_VPADGetLStickClampThreshold(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + ppcDefineParamPtr(maxValue, uint32be, 1); + ppcDefineParamPtr(minValue, uint32be, 2); + inputLog_printf("VPADGetLStickClampThreshold(%d)", channel); + + if (channel < VPAD_MAX_CONTROLLERS) + { + *maxValue = vpadStickClamp[channel].leftMax; + *minValue = vpadStickClamp[channel].leftMin; + } + else + { + debugBreakpoint(); + } + + osLib_returnFromFunction(hCPU, 0); +} + +void vpadExport_VPADGetRStickClampThreshold(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + ppcDefineParamPtr(maxValue, uint32be, 1); + ppcDefineParamPtr(minValue, uint32be, 2); + inputLog_printf("VPADGetRStickClampThreshold(%d)", channel); + + if (channel < VPAD_MAX_CONTROLLERS) + { + *maxValue = vpadStickClamp[channel].rightMax; + *minValue = vpadStickClamp[channel].rightMin; + } + else + { + debugBreakpoint(); + } + + osLib_returnFromFunction(hCPU, 0); +} + +void vpadExport_VPADSetCrossStickEmulationParamsL(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + inputLog_printf("VPADSetCrossStickEmulationParamsL(%d, %f, %f, %f)", channel, hCPU->fpr[1].fpr, hCPU->fpr[2].fpr, hCPU->fpr[3].fpr); + + if (channel < VPAD_MAX_CONTROLLERS) + { + vpadCrossStickEmulationParams[channel].leftRotation = hCPU->fpr[1].fpr; + vpadCrossStickEmulationParams[channel].leftInputRange = hCPU->fpr[2].fpr; + vpadCrossStickEmulationParams[channel].leftRadius = hCPU->fpr[3].fpr; + } + else + { + debugBreakpoint(); + } + + osLib_returnFromFunction(hCPU, 0); +} + +void vpadExport_VPADSetCrossStickEmulationParamsR(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + inputLog_printf("VPADSetCrossStickEmulationParamsR(%d, %f, %f, %f)", channel, hCPU->fpr[1].fpr, hCPU->fpr[2].fpr, hCPU->fpr[3].fpr); + + if (channel < VPAD_MAX_CONTROLLERS) + { + vpadCrossStickEmulationParams[channel].rightRotation = hCPU->fpr[1].fpr; + vpadCrossStickEmulationParams[channel].rightInputRange = hCPU->fpr[2].fpr; + vpadCrossStickEmulationParams[channel].rightRadius = hCPU->fpr[3].fpr; + } + else + { + debugBreakpoint(); + } + + osLib_returnFromFunction(hCPU, 0); +} + +void vpadExport_VPADGetCrossStickEmulationParamsL(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + ppcDefineParamPtr(rotation, float32be, 1); + ppcDefineParamPtr(inputRange, float32be, 2); + ppcDefineParamPtr(radius, float32be, 3); + inputLog_printf("VPADGetCrossStickEmulationParamsL(%d)", channel); + + if (channel < VPAD_MAX_CONTROLLERS) + { + *rotation = vpadCrossStickEmulationParams[channel].leftRotation; + *inputRange = vpadCrossStickEmulationParams[channel].leftInputRange; + *radius = vpadCrossStickEmulationParams[channel].leftRadius; + } + else + { + debugBreakpoint(); + } + + osLib_returnFromFunction(hCPU, 0); +} + +void vpadExport_VPADGetCrossStickEmulationParamsR(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + ppcDefineParamPtr(rotation, float32be, 1); + ppcDefineParamPtr(inputRange, float32be, 2); + ppcDefineParamPtr(radius, float32be, 3); + inputLog_printf("VPADGetCrossStickEmulationParamsR(%d)", channel); + + if (channel < VPAD_MAX_CONTROLLERS) + { + *rotation = vpadCrossStickEmulationParams[channel].rightRotation; + *inputRange = vpadCrossStickEmulationParams[channel].rightInputRange; + *radius = vpadCrossStickEmulationParams[channel].rightRadius; + } + else + { + debugBreakpoint(); + } + + osLib_returnFromFunction(hCPU, 0); +} + +void vpadExport_VPADGetButtonProcMode(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + inputLog_printf("VPADGetButtonProcMode(%d)", channel); + + if (channel < VPAD_MAX_CONTROLLERS) + { + osLib_returnFromFunction(hCPU, vpadButtonProcMode[channel]); + } + else + { + debugBreakpoint(); + osLib_returnFromFunction(hCPU, VPAD_BUTTON_PROC_MODE_TIGHT); + } +} + +void vpadExport_VPADSetButtonProcMode(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + ppcDefineParamU8(mode, 1); + inputLog_printf("VPADSetButtonProcMode(%d, %d)", channel, mode); + + if (channel < VPAD_MAX_CONTROLLERS) + { + vpadButtonProcMode[channel] = mode; + } + else + { + debugBreakpoint(); + } + + osLib_returnFromFunction(hCPU, 0); +} + +void vpadExport_VPADSetLcdMode(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + ppcDefineParamU32(mode, 1); + inputLog_printf("VPADSetLcdMode(%d, %d)", channel, mode); + + if (channel < VPAD_MAX_CONTROLLERS) + { + vpadLcdMode[channel] = mode; + } + else + { + debugBreakpoint(); + } + osLib_returnFromFunction(hCPU, 0); +} + +void vpadExport_VPADGetLcdMode(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + ppcDefineParamPtr(mode, uint32be, 1); + inputLog_printf("VPADGetLcdMode(%d)", channel); + + if (channel < VPAD_MAX_CONTROLLERS) + { + *mode = vpadLcdMode[channel]; + } + else + { + debugBreakpoint(); + } + + osLib_returnFromFunction(hCPU, 0); +} + +void vpadExport_VPADControlMotor(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + ppcDefineParamUStr(pattern, 1); + ppcDefineParamU8(length, 2); + inputLog_printf("VPADControlMotor(%d, _, %d)", channel, length); + + if (length > 120) + { + inputLog_printf("VPADControlMotor() - length too high with %d of 120", length); + length = 120; + } + + if (const auto controller = InputManager::instance().get_vpad_controller(channel)) + { + // if length is zero -> stop vibration + if (length == 0) + { + controller->clear_rumble(); + } + else + { + // check for max queue length + if (!controller->push_rumble(pattern, length)) + { + osLib_returnFromFunction(hCPU, -1); // TODO P: not sure about the exact return value + return; + } + } + } + + osLib_returnFromFunction(hCPU, 0); +} + +void vpadExport_VPADStopMotor(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + inputLog_printf("VPADStopMotor(%d)", channel); + + if (const auto controller = InputManager::instance().get_vpad_controller(channel)) + { + controller->clear_rumble(); + } + + osLib_returnFromFunction(hCPU, 0); +} + +void vpadExport_VPADSetTPCalibrationParam(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + ppcDefineParamMEMPTR(params, VPADTPCalibrationParam, 1); + + debugBreakpoint(); + if (channel < VPAD_MAX_CONTROLLERS) + { + VPADTPCalibrationParam* calibrationParam = params.GetPtr(); + + inputLog_printf("VPADSetTPCalibrationParam(%d, %d, %d, %f, %f)", channel, (uint16)calibrationParam->offsetX, (uint16)calibrationParam->offsetX, (float)calibrationParam->scaleX, (float)calibrationParam->scaleY); + + vpadTPCalibrationParam[channel].offsetX = calibrationParam->offsetX; + vpadTPCalibrationParam[channel].offsetX = calibrationParam->offsetY; + vpadTPCalibrationParam[channel].scaleX = calibrationParam->scaleX; + vpadTPCalibrationParam[channel].scaleY = calibrationParam->scaleY; + } + else + { + debugBreakpoint(); + } + + osLib_returnFromFunction(hCPU, 0); +} + +void vpadExport_VPADGetTPCalibrationParam(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + ppcDefineParamStructPtr(calibrationParam, VPADTPCalibrationParam, 1); + inputLog_printf("VPADSetTPCalibrationParam(%d)", channel); + + calibrationParam->offsetX = vpadTPCalibrationParam[channel].offsetX; + calibrationParam->offsetY = vpadTPCalibrationParam[channel].offsetY; + calibrationParam->scaleX = vpadTPCalibrationParam[channel].scaleX; + calibrationParam->scaleY = vpadTPCalibrationParam[channel].scaleY; + + osLib_returnFromFunction(hCPU, 0); +} + +void vpadExport_VPADGetTPCalibratedPoint(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + ppcDefineParamStructPtr(outputDisplay, VPADTPData_t, 1); + ppcDefineParamStructPtr(inputRaw, VPADTPData_t, 2); + inputLog_printf("VPADGetTPCalibratedPoint(%d)", channel); + + memmove(outputDisplay, inputRaw, sizeof(VPADTPData_t)); + + // vpadTPCalibrationParam[channel] + + sint16 x = outputDisplay->x; + sint16 y = outputDisplay->y; + + sint32 outputX; + sint32 outputY; + _tpRawToResolution(x, y, &outputX, &outputY, 1280, 720); + + outputDisplay->x = outputX; + outputDisplay->y = outputY; + outputDisplay->touch = inputRaw->touch; + outputDisplay->validity = inputRaw->validity; + + osLib_returnFromFunction(hCPU, 0); +} + +void vpadExport_VPADGetTPCalibratedPointEx(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + ppcDefineParamS32(tpResolution, 1); + ppcDefineParamStructPtr(outputDisplay, VPADTPData_t, 2); + ppcDefineParamStructPtr(inputRaw, VPADTPData_t, 3); + inputLog_printf("VPADGetTPCalibratedPointEx(%d)", channel); + + //debug_printf("TPInput: %d %d %04x %04x\n", _swapEndianU16(inputRaw->touch), _swapEndianU16(inputRaw->validity), _swapEndianU16(inputRaw->x), _swapEndianU16(inputRaw->y)); + memmove(outputDisplay, inputRaw, sizeof(VPADTPData_t)); + + //debug_printf("VPADGetTPCalibratedPointEx(): Resolution %d\n", hCPU->gpr[4]); + + sint16 x = outputDisplay->x; + sint16 y = outputDisplay->y; + + sint32 outputX = 0; + sint32 outputY = 0; + if (tpResolution == VPAD_TP_1920x1080) + { + _tpRawToResolution(x, y, &outputX, &outputY, 1920, 1080); + } + else if (tpResolution == VPAD_TP_1280x720) + { + _tpRawToResolution(x, y, &outputX, &outputY, 1280, 720); + } + else if (tpResolution == VPAD_TP_854x480) + { + _tpRawToResolution(x, y, &outputX, &outputY, 854, 480); + } + else + { + debugBreakpoint(); + debug_printf("VPADGetTPCalibratedPointEx(): Unsupported tp resolution\n"); + } + outputDisplay->x = outputX; + outputDisplay->y = outputY; + outputDisplay->touch = inputRaw->touch; + outputDisplay->validity = inputRaw->validity; + + //debug_printf("VPADGetTPCalibratedPointEx %d %d\n", _swapEndianU16(outputDisplay->x), _swapEndianU16(outputDisplay->y)); + + osLib_returnFromFunction(hCPU, 0); +} + + +void vpadExport_VPADSetGyroDirection(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + ppcDefineParamStructPtr(dir, VPADDir, 1); + inputLog_printf("VPADSetGyroDirection(%d, <<%f, %f, %f>, <%f, %f, %f>, <%f, %f, %f>>)", channel, (float)dir->x.x, (float)dir->x.y, (float)dir->x.z + , (float)dir->y.x, (float)dir->y.y, (float)dir->y.z + , (float)dir->z.x, (float)dir->z.y, (float)dir->z.z); + + if (channel < VPAD_MAX_CONTROLLERS) + { + g_vpadGyroDirOverwrite[channel] = *dir; + } + else + { + debugBreakpoint(); + } + + osLib_returnFromFunction(hCPU, 0); +} + +void vpadExport_VPADGetGyroZeroDriftMode(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + ppcDefineParamMEMPTR(gyroMode, uint32be, 1); + inputLog_printf("VPADGetGyroZeroDriftMode(%d)", channel); + + if (channel < VPAD_MAX_CONTROLLERS) + { + *gyroMode = g_vpadGyroZeroDriftMode[channel]; + } + else + { + debugBreakpoint(); + *gyroMode = VPAD_GYRO_ZERODRIFT_NONE; + } + + osLib_returnFromFunction(hCPU, 0); +} + +void vpadExport_VPADSetGyroZeroDriftMode(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + ppcDefineParamU32(gyroMode, 1); + inputLog_printf("VPADSetGyroZeroDriftMode(%d, %d)", channel, gyroMode); + + if (channel < VPAD_MAX_CONTROLLERS) + { + if (gyroMode > VPAD_GYRO_ZERODRIFT_NONE) + { + debugBreakpoint(); + } + else + { + g_vpadGyroZeroDriftMode[channel] = gyroMode; + } + } + else + { + debugBreakpoint(); + } + + osLib_returnFromFunction(hCPU, 0); +} + +void vpadExport_VPADSetGyroDirReviseBase(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + ppcDefineParamStructPtr(dir, VPADDir, 1); + + if (channel < VPAD_MAX_CONTROLLERS) + { + g_vpadGyroDirRevise[channel].vpadGyroDirReviseBase = *dir; + } + else + { + debugBreakpoint(); + } + + osLib_returnFromFunction(hCPU, 0); +} + +void vpadExport_VPADDisableGyroDirRevise(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + + if (channel < VPAD_MAX_CONTROLLERS) + { + g_vpadGyroDirRevise[channel].enabled = false; + } + else + { + debugBreakpoint(); + } + + osLib_returnFromFunction(hCPU, 0); +} + +void vpadExport_VPADSetGyroDirReviseParam(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(channel, 0); + + if (channel < VPAD_MAX_CONTROLLERS) + { + g_vpadGyroDirRevise[channel].weight = (float)hCPU->fpr[1].fpr; + } + else + { + debugBreakpoint(); + } + + osLib_returnFromFunction(hCPU, 0); +} + +namespace vpad +{ + void TickFunction(PPCInterpreter_t* hCPU) + { + // check if homebutton is pressed + // check connection to drc + const auto& instance = InputManager::instance(); + for (auto i = 0; i < InputManager::kMaxVPADControllers; ++i) + { + if (!g_vpad.controller_data[i].sampling_callback) + continue; + + if(const auto controller = instance.get_vpad_controller(i)) + { + inputLog_printf("Calling VPADSamplingCallback(%d)", i); + PPCCoreCallback(g_vpad.controller_data[i].sampling_callback, i); + } + } + + osLib_returnFromFunction(hCPU, 0); + } + + void start() + { + coreinit::OSCreateAlarm(&g_vpad.alarm); + const uint64 start_tick = coreinit::coreinit_getOSTime(); + const uint64 period_tick = coreinit::EspressoTime::GetTimerClock() * 5 / 1000; + const MPTR handler = PPCInterpreter_makeCallableExportDepr(TickFunction); + coreinit::OSSetPeriodicAlarm(&g_vpad.alarm, start_tick, period_tick, handler); + } + +void load() +{ + cafeExportRegister("vpad", VPADSetBtnRepeat, LogType::Input); + cafeExportRegister("vpad", VPADSetSamplingCallback, LogType::Input); + cafeExportRegister("vpad", VPADRead, LogType::Input); + + osLib_addFunction("vpad", "VPADGetAccParam", vpadExport_VPADGetAccParam); + osLib_addFunction("vpad", "VPADSetAccParam", vpadExport_VPADSetAccParam); + osLib_addFunction("vpad", "VPADGetAccPlayMode", vpadExport_VPADGetAccPlayMode); + osLib_addFunction("vpad", "VPADSetAccPlayMode", vpadExport_VPADSetAccPlayMode); + + osLib_addFunction("vpad", "VPADEnableStickCrossClamp", vpadExport_VPADEnableStickCrossClamp); + osLib_addFunction("vpad", "VPADDisableStickCrossClamp", vpadExport_VPADDisableStickCrossClamp); + osLib_addFunction("vpad", "VPADSetLStickClampThreshold", vpadExport_VPADSetLStickClampThreshold); + osLib_addFunction("vpad", "VPADSetRStickClampThreshold", vpadExport_VPADSetRStickClampThreshold); + osLib_addFunction("vpad", "VPADGetLStickClampThreshold", vpadExport_VPADGetLStickClampThreshold); + osLib_addFunction("vpad", "VPADGetRStickClampThreshold", vpadExport_VPADGetRStickClampThreshold); + + osLib_addFunction("vpad", "VPADSetCrossStickEmulationParamsL", vpadExport_VPADSetCrossStickEmulationParamsL); + osLib_addFunction("vpad", "VPADSetCrossStickEmulationParamsR", vpadExport_VPADSetCrossStickEmulationParamsR); + osLib_addFunction("vpad", "VPADGetCrossStickEmulationParamsL", vpadExport_VPADGetCrossStickEmulationParamsL); + osLib_addFunction("vpad", "VPADGetCrossStickEmulationParamsR", vpadExport_VPADGetCrossStickEmulationParamsR); + + osLib_addFunction("vpad", "VPADGetButtonProcMode", vpadExport_VPADGetButtonProcMode); + osLib_addFunction("vpad", "VPADSetButtonProcMode", vpadExport_VPADSetButtonProcMode); + osLib_addFunction("vpad", "VPADGetLcdMode", vpadExport_VPADGetLcdMode); + osLib_addFunction("vpad", "VPADSetLcdMode", vpadExport_VPADSetLcdMode); + + osLib_addFunction("vpad", "VPADControlMotor", vpadExport_VPADControlMotor); + osLib_addFunction("vpad", "VPADStopMotor", vpadExport_VPADStopMotor); + + osLib_addFunction("vpad", "VPADGetTPCalibrationParam", vpadExport_VPADGetTPCalibrationParam); + osLib_addFunction("vpad", "VPADSetTPCalibrationParam", vpadExport_VPADSetTPCalibrationParam); + osLib_addFunction("vpad", "VPADGetTPCalibratedPoint", vpadExport_VPADGetTPCalibratedPoint); + osLib_addFunction("vpad", "VPADGetTPCalibratedPointEx", vpadExport_VPADGetTPCalibratedPointEx); + + //osLib_addFunction("vpad", "VPADRead", vpadExport_VPADRead); + //osLib_addFunction("vpad", "VPADSetSamplingCallback", vpadExport_VPADSetSamplingCallback); + //osLib_addFunction("vpad", "VPADSetBtnRepeat", vpadExport_VPADSetBtnRepeat); + + osLib_addFunction("vpad", "VPADGetGyroZeroDriftMode", vpadExport_VPADGetGyroZeroDriftMode); + osLib_addFunction("vpad", "VPADSetGyroDirection", vpadExport_VPADSetGyroDirection); + osLib_addFunction("vpad", "VPADSetGyroZeroDriftMode", vpadExport_VPADSetGyroZeroDriftMode); + + osLib_addFunction("vpad", "VPADSetGyroDirReviseBase", vpadExport_VPADSetGyroDirReviseBase); + osLib_addFunction("vpad", "VPADDisableGyroDirRevise", vpadExport_VPADDisableGyroDirRevise); + osLib_addFunction("vpad", "VPADSetGyroDirReviseParam", vpadExport_VPADSetGyroDirReviseParam); + +} +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/vpad/vpad.h b/src/Cafe/OS/libs/vpad/vpad.h new file mode 100644 index 00000000..1f138523 --- /dev/null +++ b/src/Cafe/OS/libs/vpad/vpad.h @@ -0,0 +1,86 @@ +#pragma once + +#include "Cafe/OS/libs/padscore/padscore.h" + +namespace vpad +{ + void load(); + void start(); +} + +#define VPAD_MAX_CONTROLLERS (2) + +struct BtnRepeat +{ + sint32 delay, pulse; +}; + +enum VPADTouchValidity +{ + kTpValid = 0, + kTpInvalidX = 1, // only x invalid + kTpInvalidY = 2, // only y invalid + kTpInvalid = kTpInvalidX | kTpInvalidY, +}; + +enum VPADTouchState +{ + kTpTouchOff = 0, + kTpTouchOn = 1, +}; + +struct VPADDir +{ + beVec3D_t x; + beVec3D_t y; + beVec3D_t z; + + VPADDir() = default; + VPADDir(const beVec3D_t& x, const beVec3D_t& y, const beVec3D_t& z) + : x(x), y(y), z(z) {} + +}; + +static_assert(sizeof(VPADDir) == 0x24); + +struct VPADTPData_t +{ + uint16be x; + uint16be y; + uint16be touch; + uint16be validity; +}; + +static_assert(sizeof(VPADTPData_t) == 8); + +typedef struct VPADStatus +{ + /* +0x00 */ uint32be hold; + /* +0x04 */ uint32be trig; + /* +0x08 */ uint32be release; + /* +0x0C */ beVec2D_t leftStick; + /* +0x14 */ beVec2D_t rightStick; + /* +0x1C */ beVec3D_t acc; + /* +0x28 */ float32be accMagnitude; + /* +0x2C */ float32be accAcceleration; + /* +0x30 */ beVec2D_t accXY; + /* +0x38 */ beVec3D_t gyroChange; + /* +0x44 */ beVec3D_t gyroOrientation; + /* +0x50 */ sint8 vpadErr; + /* +0x51 */ uint8 padding1[1]; + /* +0x52 */ VPADTPData_t tpData; + /* +0x5A */ VPADTPData_t tpProcessed1; + /* +0x62 */ VPADTPData_t tpProcessed2; + /* +0x6A */ uint8 padding2[2]; + /* +0x6C */ VPADDir dir; + /* +0x90 */ uint8 headphoneStatus; + /* +0x91 */ uint8 padding3[3]; + /* +0x94 */ beVec3D_t magnet; + /* +0xA0 */ uint8 slideVolume; + /* +0xA1 */ uint8 batteryLevel; + /* +0xA2 */ uint8 micStatus; + /* +0xA3 */ uint8 slideVolume2; + /* +0xA4 */ uint8 padding4[8]; +}VPADStatus_t; + +static_assert(sizeof(VPADStatus) == 0xAC); diff --git a/src/Cafe/OS/libs/zlib125/zlib125.cpp b/src/Cafe/OS/libs/zlib125/zlib125.cpp new file mode 100644 index 00000000..72855c61 --- /dev/null +++ b/src/Cafe/OS/libs/zlib125/zlib125.cpp @@ -0,0 +1,360 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "zlib125.h" +#include "zlib.h" + +typedef struct { + /* +0x00 */ MEMPTR<uint8> next_in; /* next input byte */ + /* +0x04 */ uint32be avail_in; /* number of bytes available at next_in */ + /* +0x08 */ uint32be total_in; /* total number of input bytes read so far */ + + /* +0x0C */ MEMPTR<uint8> next_out; /* next output byte should be put there */ + /* +0x10 */ uint32be avail_out; /* remaining free space at next_out */ + /* +0x14 */ uint32be total_out; /* total number of bytes output so far */ + + /* +0x18 */ MEMPTR<char> msg; /* last error message, NULL if no error */ + /* +0x1C */ MEMPTR<void> state; /* not visible by applications */ + + /* +0x20 */ MEMPTR<void> zalloc; /* used to allocate the internal state */ + /* +0x24 */ MEMPTR<void> zfree; /* used to free the internal state */ + /* +0x28 */ MEMPTR<void> opaque; /* private data object passed to zalloc and zfree */ + + /* +0x2C */ uint32be data_type; /* best guess about the data type: binary or text */ + /* +0x30 */ uint32be adler; /* adler32 value of the uncompressed data */ + /* +0x34 */ uint32be reserved; /* reserved for future use */ +}z_stream_ppc2; + +static_assert(sizeof(z_stream_ppc2) == 0x38); + +voidpf zcallocWrapper(voidpf opaque, uInt items, uInt size) +{ + z_stream_ppc2* zstream = (z_stream_ppc2*)opaque; + + PPCInterpreter_t* hCPU = ppcInterpreterCurrentInstance; + hCPU->gpr[3] = zstream->opaque.GetMPTR(); + hCPU->gpr[4] = items; + hCPU->gpr[5] = size; + PPCCore_executeCallbackInternal(zstream->zalloc.GetMPTR()); + memset(memory_getPointerFromVirtualOffset(hCPU->gpr[3]), 0, items*size); + return memory_getPointerFromVirtualOffset(hCPU->gpr[3]); +} + +void zcfreeWrapper(voidpf opaque, voidpf baseIndex) +{ + z_stream_ppc2* zstream = (z_stream_ppc2*)opaque; + PPCInterpreter_t* hCPU = ppcInterpreterCurrentInstance; + hCPU->gpr[3] = zstream->opaque.GetMPTR(); + hCPU->gpr[4] = memory_getVirtualOffsetFromPointer(baseIndex); + PPCCore_executeCallbackInternal(zstream->zfree.GetMPTR()); + return; +} + +void zlib125_zcalloc(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(opaque, 0); + ppcDefineParamU32(items, 1); + ppcDefineParamU32(size, 2); + + hCPU->gpr[3] = items*size; + hCPU->instructionPointer = gCoreinitData->MEMAllocFromDefaultHeap.GetMPTR(); +} + +void zlib125_zcfree(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(opaque, 0); + ppcDefineParamMPTR(baseIndex, 1); + + hCPU->gpr[3] = baseIndex; + hCPU->instructionPointer = gCoreinitData->MEMFreeToDefaultHeap.GetMPTR(); +} + +void zlib125_setupHostZStream(z_stream_ppc2* input, z_stream* output, bool fixInternalStreamPtr = true) +{ + output->next_in = input->next_in.GetPtr(); + output->avail_in = (uint32)input->avail_in; + output->total_in = (uint32)input->total_in; + + output->next_out = input->next_out.GetPtr(); + output->avail_out = (uint32)input->avail_out; + output->total_out = (uint32)input->total_out; + + output->msg = input->msg.GetPtr(); + output->state = (internal_state*)input->state.GetPtr(); + + output->zalloc = zcallocWrapper; + output->zfree = zcfreeWrapper; + output->opaque = (void*)input; + + output->data_type = input->data_type; + output->adler = (uint32)input->adler; + output->reserved = (uint32)input->reserved; + + if (fixInternalStreamPtr && output->state) + { + // in newer zLib versions the internal state has a pointer to the stream at the beginning, we have to update it manually + // todo - find better solution + (*(void**)(output->state)) = output; + } +} + +void zlib125_setupUpdateZStream(z_stream* input, z_stream_ppc2* output) +{ + output->next_in = input->next_in; + output->avail_in = (uint32)input->avail_in; + output->total_in = (uint32)input->total_in; + + output->next_out = input->next_out; + output->avail_out = (uint32)input->avail_out; + output->total_out = (uint32)input->total_out; + + output->msg = input->msg; + output->state = (void*)input->state; + + output->data_type = input->data_type; + output->adler = input->adler; + output->reserved = input->reserved; + +} + +void zlib125Export_inflateInit_(PPCInterpreter_t* hCPU) +{ + ppcDefineParamStructPtr(zstream, z_stream_ppc2, 0); + ppcDefineParamStr(version, 1); + + z_stream hzs; + zlib125_setupHostZStream(zstream, &hzs, false); + + // setup internal memory allocator if requested + if (zstream->zalloc == nullptr) + zstream->zalloc = PPCInterpreter_makeCallableExportDepr(zlib125_zcalloc); + if (zstream->zfree == nullptr) + zstream->zfree = PPCInterpreter_makeCallableExportDepr(zlib125_zcfree); + + sint32 r = inflateInit_(&hzs, version, sizeof(z_stream)); + + zlib125_setupUpdateZStream(&hzs, zstream); + + osLib_returnFromFunction(hCPU, r); +} + +void zlib125Export_inflateInit2_(PPCInterpreter_t* hCPU) +{ + ppcDefineParamStructPtr(zstream, z_stream_ppc2, 0); + ppcDefineParamS32(windowBits, 1); + ppcDefineParamStr(version, 2); + + z_stream hzs; + zlib125_setupHostZStream(zstream, &hzs, false); + + // setup internal memory allocator if requested + if (zstream->zalloc == nullptr) + zstream->zalloc = PPCInterpreter_makeCallableExportDepr(zlib125_zcalloc); + if (zstream->zfree == nullptr) + zstream->zfree = PPCInterpreter_makeCallableExportDepr(zlib125_zcfree); + + sint32 r = inflateInit2_(&hzs, windowBits, version, sizeof(z_stream)); + + zlib125_setupUpdateZStream(&hzs, zstream); + + osLib_returnFromFunction(hCPU, r); +} + +void zlib125Export_inflate(PPCInterpreter_t* hCPU) +{ + ppcDefineParamStructPtr(zstream, z_stream_ppc2, 0); + ppcDefineParamS32(flushParam, 1); + + z_stream hzs; + zlib125_setupHostZStream(zstream, &hzs); + + sint32 r = inflate(&hzs, flushParam); + + zlib125_setupUpdateZStream(&hzs, zstream); + osLib_returnFromFunction(hCPU, r); +} + +void zlib125Export_inflateEnd(PPCInterpreter_t* hCPU) +{ + ppcDefineParamStructPtr(zstream, z_stream_ppc2, 0); + + z_stream hzs; + zlib125_setupHostZStream(zstream, &hzs); + + sint32 r = inflateEnd(&hzs); + + zlib125_setupUpdateZStream(&hzs, zstream); + osLib_returnFromFunction(hCPU, r); +} + +void zlib125Export_inflateReset(PPCInterpreter_t* hCPU) +{ + debug_printf("inflateReset(0x%08x)\n", hCPU->gpr[3]); + ppcDefineParamStructPtr(zstream, z_stream_ppc2, 0); + + z_stream hzs; + zlib125_setupHostZStream(zstream, &hzs); + + sint32 r = inflateReset(&hzs); + + zlib125_setupUpdateZStream(&hzs, zstream); + osLib_returnFromFunction(hCPU, r); +} + +void zlib125Export_inflateReset2(PPCInterpreter_t* hCPU) +{ + debug_printf("inflateReset2(0x%08x,0x%08x)\n", hCPU->gpr[3], hCPU->gpr[4]); + ppcDefineParamStructPtr(zstream, z_stream_ppc2, 0); + ppcDefineParamS32(windowBits, 1); + + z_stream hzs; + zlib125_setupHostZStream(zstream, &hzs); + + sint32 r = inflateReset2(&hzs, windowBits); + + zlib125_setupUpdateZStream(&hzs, zstream); + osLib_returnFromFunction(hCPU, r); +} + +void zlib125Export_deflateInit2_(PPCInterpreter_t* hCPU) +{ + ppcDefineParamStructPtr(zstream, z_stream_ppc2, 0); + ppcDefineParamS32(level, 1); + ppcDefineParamS32(method, 2); + ppcDefineParamS32(windowBits, 3); + ppcDefineParamS32(memLevel, 4); + ppcDefineParamS32(strategy, 5); + ppcDefineParamStr(version, 6); + ppcDefineParamS32(streamsize, 7); + + z_stream hzs; + zlib125_setupHostZStream(zstream, &hzs, false); + + // setup internal memory allocator if requested + if (zstream->zalloc == nullptr) + zstream->zalloc = PPCInterpreter_makeCallableExportDepr(zlib125_zcalloc); + if (zstream->zfree == nullptr) + zstream->zfree = PPCInterpreter_makeCallableExportDepr(zlib125_zcfree); + + // workaround for Splatoon (it allocates a too small buffer for our version of zLib and its zalloc returns NULL) + zstream->zalloc = PPCInterpreter_makeCallableExportDepr(zlib125_zcalloc); + zstream->zfree = PPCInterpreter_makeCallableExportDepr(zlib125_zcfree); + + if (streamsize != sizeof(z_stream_ppc2)) + assert_dbg(); + + sint32 r = deflateInit2_(&hzs, level, method, windowBits, memLevel, strategy, version, sizeof(z_stream)); + + zlib125_setupUpdateZStream(&hzs, zstream); + + osLib_returnFromFunction(hCPU, r); +} + +void zlib125Export_deflateBound(PPCInterpreter_t* hCPU) +{ + debug_printf("deflateBound(0x%08x,0x%08x)\n", hCPU->gpr[3], hCPU->gpr[4]); + ppcDefineParamStructPtr(zstream, z_stream_ppc2, 0); + ppcDefineParamS32(sourceLen, 1); + + z_stream hzs; + zlib125_setupHostZStream(zstream, &hzs); + + sint32 r = deflateBound(&hzs, sourceLen); + + zlib125_setupUpdateZStream(&hzs, zstream); + osLib_returnFromFunction(hCPU, r); +} + +void zlib125Export_deflate(PPCInterpreter_t* hCPU) +{ + ppcDefineParamStructPtr(zstream, z_stream_ppc2, 0); + ppcDefineParamS32(flushParam, 1); + + z_stream hzs; + zlib125_setupHostZStream(zstream, &hzs); + + sint32 r = deflate(&hzs, flushParam); + + zlib125_setupUpdateZStream(&hzs, zstream); + osLib_returnFromFunction(hCPU, r); +} + +void zlib125Export_deflateEnd(PPCInterpreter_t* hCPU) +{ + ppcDefineParamStructPtr(zstream, z_stream_ppc2, 0); + + z_stream hzs; + zlib125_setupHostZStream(zstream, &hzs); + + sint32 r = deflateEnd(&hzs); + + zlib125_setupUpdateZStream(&hzs, zstream); + osLib_returnFromFunction(hCPU, r); +} + +void zlib125Export_uncompress(PPCInterpreter_t* hCPU) +{ + // Bytef * dest, uLongf * destLen, const Bytef * source, uLong sourceLen + uint8* memDst = memory_getPointerFromVirtualOffset(hCPU->gpr[3]); + uint8* memSrc = memory_getPointerFromVirtualOffset(hCPU->gpr[5]); + uint32* pDestLenBE = (uint32*)memory_getPointerFromVirtualOffset(hCPU->gpr[4]); + uint32 sourceLen = hCPU->gpr[6]; + uLong destLen = _swapEndianU32(*pDestLenBE); + sint32 r = uncompress(memDst, &destLen, memSrc, sourceLen); + *pDestLenBE = _swapEndianU32(destLen); + + osLib_returnFromFunction(hCPU, r); +} + +void zlib125Export_compress(PPCInterpreter_t* hCPU) +{ + // Bytef* dest, uLongf* destLen, const Bytef* source, uLong sourceLen + uint8* memDst = memory_getPointerFromVirtualOffset(hCPU->gpr[3]); + uint8* memSrc = memory_getPointerFromVirtualOffset(hCPU->gpr[5]); + uint32* pDestLenBE = (uint32*)memory_getPointerFromVirtualOffset(hCPU->gpr[4]); + uint32 sourceLen = hCPU->gpr[6]; + uLong destLen = _swapEndianU32(*pDestLenBE); + sint32 r = compress(memDst, &destLen, memSrc, sourceLen); + *pDestLenBE = _swapEndianU32(destLen); + osLib_returnFromFunction(hCPU, r); +} + +void zlib125Export_crc32(PPCInterpreter_t* hCPU) +{ + uint32 crc = hCPU->gpr[3]; + uint8* buf = (uint8*)memory_getPointerFromVirtualOffsetAllowNull(hCPU->gpr[4]); + uint32 len = hCPU->gpr[5]; + + uint32 crcResult = crc32(crc, buf, len); + + osLib_returnFromFunction(hCPU, crcResult); +} + +void zlib125Export_compressBound(PPCInterpreter_t* hCPU) +{ + uint32 result = compressBound(hCPU->gpr[3]); + osLib_returnFromFunction(hCPU, result); +} + +namespace zlib +{ + void load() + { + // zlib125 + osLib_addFunction("zlib125", "inflateInit2_", zlib125Export_inflateInit2_); + osLib_addFunction("zlib125", "inflateInit_", zlib125Export_inflateInit_); + osLib_addFunction("zlib125", "inflateEnd", zlib125Export_inflateEnd); + osLib_addFunction("zlib125", "inflate", zlib125Export_inflate); + osLib_addFunction("zlib125", "inflateReset", zlib125Export_inflateReset); + osLib_addFunction("zlib125", "inflateReset2", zlib125Export_inflateReset2); + + osLib_addFunction("zlib125", "deflateInit2_", zlib125Export_deflateInit2_); + osLib_addFunction("zlib125", "deflateBound", zlib125Export_deflateBound); + osLib_addFunction("zlib125", "deflate", zlib125Export_deflate); + osLib_addFunction("zlib125", "deflateEnd", zlib125Export_deflateEnd); + + osLib_addFunction("zlib125", "uncompress", zlib125Export_uncompress); + osLib_addFunction("zlib125", "compress", zlib125Export_compress); + + osLib_addFunction("zlib125", "crc32", zlib125Export_crc32); + osLib_addFunction("zlib125", "compressBound", zlib125Export_compressBound); + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/zlib125/zlib125.h b/src/Cafe/OS/libs/zlib125/zlib125.h new file mode 100644 index 00000000..750c19a3 --- /dev/null +++ b/src/Cafe/OS/libs/zlib125/zlib125.h @@ -0,0 +1,5 @@ + +namespace zlib +{ + void load(); +} \ No newline at end of file diff --git a/src/Cafe/TitleList/BaseInfo.cpp b/src/Cafe/TitleList/BaseInfo.cpp new file mode 100644 index 00000000..528ba948 --- /dev/null +++ b/src/Cafe/TitleList/BaseInfo.cpp @@ -0,0 +1,68 @@ +#include "BaseInfo.h" + +#include "config/CemuConfig.h" +#include "Cafe/Filesystem/fsc.h" +#include "Cafe/Filesystem/FST/FST.h" + +sint32 BaseInfo::GetLanguageIndex(std::string_view language) +{ + if (language == "ja") + return (sint32)CafeConsoleLanguage::JA; + else if (language == "en") + return (sint32)CafeConsoleLanguage::EN; + else if (language == "fr") + return (sint32)CafeConsoleLanguage::FR; + else if (language == "de") + return (sint32)CafeConsoleLanguage::DE; + else if (language == "it") + return (sint32)CafeConsoleLanguage::IT; + else if (language == "es") + return (sint32)CafeConsoleLanguage::ES; + else if (language == "zhs") + return (sint32)CafeConsoleLanguage::ZH; + else if (language == "ko") + return (sint32)CafeConsoleLanguage::KO; + else if (language == "nl") + return (sint32)CafeConsoleLanguage::NL; + else if (language == "pt") + return (sint32)CafeConsoleLanguage::PT; + else if (language == "ru") + return (sint32)CafeConsoleLanguage::RU; + else if (language == "zht") + return (sint32)CafeConsoleLanguage::ZH; + return -1; +} + + +std::unique_ptr<uint8[]> BaseInfo::ReadFSCFile(std::string_view filename, uint32& size) const +{ + size = 0; + sint32 fscStatus = 0; + // load and parse meta.xml + FSCVirtualFile* file = fsc_open(const_cast<char*>(std::string(filename).c_str()), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus); + if (file) + { + size = fsc_getFileSize(file); + auto buffer = std::make_unique<uint8[]>(size); + fsc_readFile(file, buffer.get(), size); + fsc_close(file); + return buffer; + } + + return nullptr; +} + +std::unique_ptr<uint8[]> BaseInfo::ReadVirtualFile(FSTVolume* volume, std::string_view filename, uint32& size) const +{ + size = 0; + FSTFileHandle fileHandle; + if (!volume->OpenFile(filename, fileHandle, true)) + return nullptr; + + size = volume->GetFileSize(fileHandle); + auto buffer = std::make_unique<uint8[]>(size); + volume->ReadFile(fileHandle, 0, size, buffer.get()); + + return buffer; +} + diff --git a/src/Cafe/TitleList/BaseInfo.h b/src/Cafe/TitleList/BaseInfo.h new file mode 100644 index 00000000..27578235 --- /dev/null +++ b/src/Cafe/TitleList/BaseInfo.h @@ -0,0 +1,37 @@ +#pragma once + +namespace pugi +{ + struct xml_parse_result; + class xml_document; +} + +class BaseInfo +{ +public: + enum class GameType + { + FSC, // using fsc API + Directory, // rpx/meta + Image, // wud/wux + }; + + virtual ~BaseInfo() = default; + + [[nodiscard]] const fs::path& GetPath() const { return m_type_path; } + [[nodiscard]] GameType GetGameType() const { return m_type; } + +protected: + + GameType m_type; + fs::path m_type_path; // empty / base dir / wud path + + virtual void ParseDirectory(const fs::path& filename) = 0; + virtual bool ParseFile(const fs::path& filename) = 0; + + + [[nodiscard]] std::unique_ptr<uint8[]> ReadFSCFile(std::string_view filename, uint32& size) const; + [[nodiscard]] std::unique_ptr<uint8[]> ReadVirtualFile(class FSTVolume* volume, std::string_view filename, uint32& size) const; + + [[nodiscard]] static sint32 GetLanguageIndex(std::string_view language); +}; \ No newline at end of file diff --git a/src/Cafe/TitleList/GameInfo.h b/src/Cafe/TitleList/GameInfo.h new file mode 100644 index 00000000..3a2e6121 --- /dev/null +++ b/src/Cafe/TitleList/GameInfo.h @@ -0,0 +1,121 @@ +#pragma once + +#include "config/CemuConfig.h" +#include "MetaInfo.h" +#include "TitleInfo.h" +#include "config/ActiveSettings.h" + +class GameInfo2 +{ +public: + ~GameInfo2() + { + m_base.UnmountAll(); + m_update.UnmountAll(); + for (auto& it : m_aoc) + it.UnmountAll(); + } + + bool IsValid() const + { + return m_base.IsValid(); // at least the base must be valid for this to be a runnable title + } + + void SetBase(const TitleInfo& titleInfo) + { + m_base = titleInfo; + } + + void SetUpdate(const TitleInfo& titleInfo) + { + if (HasUpdate()) + { + if (titleInfo.GetAppTitleVersion() > m_update.GetAppTitleVersion()) + m_update = titleInfo; + } + else + m_update = titleInfo; + } + + bool HasUpdate() const + { + return m_update.IsValid(); + } + + void AddAOC(const TitleInfo& titleInfo) + { + TitleId aocTitleId = titleInfo.GetAppTitleId(); + uint16 aocVersion = titleInfo.GetAppTitleVersion(); + auto it = std::find_if(m_aoc.begin(), m_aoc.end(), [aocTitleId](const TitleInfo& rhs) { return rhs.GetAppTitleId() == aocTitleId; }); + if (it != m_aoc.end()) + { + if(it->GetAppTitleVersion() >= aocVersion) + return; + m_aoc.erase(it); + } + m_aoc.emplace_back(titleInfo); + } + + bool HasAOC() const + { + return !m_aoc.empty(); + } + + TitleInfo& GetBase() + { + return m_base; + } + + TitleInfo& GetUpdate() + { + return m_update; + } + + std::span<TitleInfo> GetAOC() + { + return m_aoc; + } + + TitleId GetBaseTitleId() + { + cemu_assert_debug(m_base.IsValid()); + return m_base.GetAppTitleId(); + } + + std::string GetTitleName() + { + cemu_assert_debug(m_base.IsValid()); + return m_base.GetTitleName(); // long name + } + + uint16 GetVersion() const + { + if (m_update.IsValid()) + return m_update.GetAppTitleVersion(); + return m_base.GetAppTitleVersion(); + } + + CafeConsoleRegion GetRegion() const + { + if (m_update.IsValid()) + return m_update.GetMetaRegion(); + return m_base.GetMetaRegion(); + } + + uint16 GetAOCVersion() const + { + if (m_aoc.empty()) + return 0; + return m_aoc.front().GetAppTitleVersion(); + } + + fs::path GetSaveFolder() + { + return ActiveSettings::GetMlcPath(fmt::format("usr/save/{:08x}/{:08x}", (GetBaseTitleId() >> 32), GetBaseTitleId() & 0xFFFFFFFF)); + } + +private: + TitleInfo m_base; + TitleInfo m_update; + std::vector<TitleInfo> m_aoc; +}; \ No newline at end of file diff --git a/src/Cafe/TitleList/MetaInfo.cpp b/src/Cafe/TitleList/MetaInfo.cpp new file mode 100644 index 00000000..f3e362b0 --- /dev/null +++ b/src/Cafe/TitleList/MetaInfo.cpp @@ -0,0 +1,180 @@ +#include "MetaInfo.h" +#include "Cafe/Filesystem/fsc.h" + +#include "pugixml.hpp" +#include "Cafe/Filesystem/FST/FST.h" + +MetaInfo::MetaInfo() +{ + m_type = GameType::FSC; + + uint32 meta_size; + const auto meta_data = ReadFSCFile("vol/meta/meta.xml", meta_size); + if (meta_size == 0 || !meta_data) + throw std::runtime_error("meta.xml missing"); + + pugi::xml_document meta_doc; + ParseMetaFile(meta_doc, meta_doc.load_buffer_inplace(meta_data.get(), meta_size)); +} + +MetaInfo::MetaInfo(const fs::path& filename) +{ + if (!fs::exists(filename)) + throw std::invalid_argument("filename doesn't exist"); + + if (fs::is_directory(filename)) + { + MetaInfo::ParseDirectory(filename); + m_type = GameType::Directory; + m_type_path = filename; + } + else + MetaInfo::ParseFile(filename); +} + +std::string MetaInfo::GetName(CafeConsoleLanguage language) const +{ + std::string long_name{ GetLongName(language) }; + const auto nl = long_name.find(L'\n'); + if (nl != std::string::npos) + long_name.replace(nl, 1, " - "); + + return long_name; +} + +const std::string& MetaInfo::GetLongName(CafeConsoleLanguage language) const +{ + return m_long_name[(int)language].empty() ? m_long_name[(int)CafeConsoleLanguage::EN] : m_long_name[(int)language]; +} + +const std::string& MetaInfo::GetShortName(CafeConsoleLanguage language) const +{ + return m_short_name[(int)language].empty() ? m_short_name[(int)CafeConsoleLanguage::EN] : m_short_name[(int)language]; +} + +const std::string& MetaInfo::GetPublisher(CafeConsoleLanguage language) const +{ + return m_publisher[(int)language].empty() ? m_publisher[(int)CafeConsoleLanguage::EN] : m_publisher[(int)language]; +} + +void MetaInfo::ParseDirectory(const fs::path& filename) +{ + const auto meta_dir = fs::path(filename).append(L"meta"); + if (!fs::exists(meta_dir) || !fs::is_directory(meta_dir)) + throw std::invalid_argument("meta directory missing"); + + const auto meta_file = meta_dir / L"meta.xml"; + if (!fs::exists(meta_file) || !fs::is_regular_file(meta_file)) + throw std::invalid_argument("meta.xml missing"); + + ParseMetaFile(meta_file.wstring()); +} + +bool MetaInfo::ParseFile(const fs::path& filename) +{ + const auto extension = filename.extension(); + if (filename.filename() != "meta.xml") + return false; + + const auto base_dir = filename.parent_path().parent_path(); + + ParseMetaFile(filename); + m_type = GameType::Directory; + m_type_path = base_dir; + return true; +} + +void MetaInfo::ParseMetaFile(const fs::path& meta_file) +{ + pugi::xml_document doc; + const auto result = doc.load_file(meta_file.wstring().c_str()); + ParseMetaFile(doc, result); +} + +void MetaInfo::ParseMetaFile(const pugi::xml_document& doc, const pugi::xml_parse_result& result) +{ + if (!result) + throw std::invalid_argument(fmt::format("error when parsing the meta.xml: {}", result.description())); + + const auto root = doc.child("menu"); + if (!root) + throw std::invalid_argument("meta.xml invalid"); + + for (const auto& child : root.children()) + { + std::string_view name = child.name(); + if (name == "title_version") + m_version = child.text().as_uint(); + else if (name == "product_code") + m_product_code = child.text().as_string(); + else if (name == "company_code") + m_company_code = child.text().as_string(); + else if (name == "content_platform") + m_content_platform = child.text().as_string(); + else if (name == "title_id") + m_title_id = std::stoull(child.text().as_string(), nullptr, 16); + else if (name == "region") + m_region = (CafeConsoleRegion)child.text().as_uint(); + else if (boost::starts_with(name, "longname_")) + { + const sint32 index = GetLanguageIndex(name.substr(std::size("longname_") - 1)); + if (index != -1) + m_long_name[index] = child.text().as_string(); + } + else if (boost::starts_with(name, L"shortname_")) + { + const sint32 index = GetLanguageIndex(name.substr(std::size("shortname_") - 1)); + if (index != -1) + m_short_name[index] = child.text().as_string(); + } + else if (boost::starts_with(name, L"publisher_")) + { + const sint32 index = GetLanguageIndex(name.substr(std::size("publisher_") - 1)); + if (index != -1) + m_publisher[index] = child.text().as_string(); + } + } +} + +std::unique_ptr<uint8[]> MetaInfo::GetIcon(uint32& size) const +{ + size = 0; + switch (m_type) + { + case GameType::FSC: + return ReadFSCFile("vol/meta/iconTex.tga", size); + case GameType::Directory: + { + cemu_assert_debug(!m_type_path.empty()); + const auto icon = fs::path(m_type_path).append(L"meta").append(L"iconTex.tga"); + std::ifstream file(icon, std::ios::binary | std::ios::ate); + if (file.is_open()) + { + size = file.tellg(); + if (size > 0) + { + file.seekg(0, std::ios::beg); + auto result = std::make_unique<uint8[]>(size); + file.read((char*)result.get(), size); + return result; + } + } + return nullptr; + } + case GameType::Image: + { + cemu_assert_debug(!m_type_path.empty()); + FSTVolume* volume = FSTVolume::OpenFromDiscImage(m_type_path); + if (volume) + { + auto result = ReadVirtualFile(volume, "meta/iconTex.tga", size); + delete volume; + return result; + } + return nullptr; + } + default: + __assume(false); + } + return nullptr; +} diff --git a/src/Cafe/TitleList/MetaInfo.h b/src/Cafe/TitleList/MetaInfo.h new file mode 100644 index 00000000..bcd511c3 --- /dev/null +++ b/src/Cafe/TitleList/MetaInfo.h @@ -0,0 +1,61 @@ +#pragma once + +#include "BaseInfo.h" +#include "config/CemuConfig.h" + +class MetaInfo : public BaseInfo +{ +public: + MetaInfo(); + MetaInfo(const fs::path& filename); + + // returns long name with replaces newlines to ' - ' + [[nodiscard]] std::string GetName(CafeConsoleLanguage language = CafeConsoleLanguage::EN) const; + + [[nodiscard]] uint64 GetTitleId() const { return m_title_id; } + [[nodiscard]] uint32 GetTitleIdHigh() const { return (uint32)(GetTitleId() >> 32); } + [[nodiscard]] uint32 GetTitleIdLow() const { return (uint32)(GetTitleId() & 0xFFFFFFFF); } + + [[nodiscard]] uint64 GetBaseTitleId() const { return m_title_id & ~0xF00000000ULL; } + [[nodiscard]] uint32 GetBaseTitleIdHigh() const { return (uint32)(GetBaseTitleId() >> 32); } + [[nodiscard]] uint32 GetBaseTitleIdLow() const { return (uint32)(GetBaseTitleId() & 0xFFFFFFFF); } + + [[nodiscard]] uint64 GetUpdateTitleId() const { return GetBaseTitleId() | 0xE00000000ULL; } + [[nodiscard]] uint32 GetUpdateTitleIdHigh() const { return (uint32)(GetUpdateTitleId() >> 32); } + [[nodiscard]] uint32 GetUpdateTitleIdLow() const { return (uint32)(GetUpdateTitleId() & 0xFFFFFFFF); } + + [[nodiscard]] uint64 GetDLCTitleId() const { return GetBaseTitleId() | 0xC00000000ULL; } + [[nodiscard]] uint32 GetDLCTitleIdHigh() const { return (uint32)(GetDLCTitleId() >> 32); } + [[nodiscard]] uint32 GetDLCTitleIdLow() const { return (uint32)(GetDLCTitleId() & 0xFFFFFFFF); } + + [[nodiscard]] const std::string& GetLongName(CafeConsoleLanguage language) const; + [[nodiscard]] const std::string& GetShortName(CafeConsoleLanguage language) const; + [[nodiscard]] const std::string& GetPublisher(CafeConsoleLanguage language) const; + [[nodiscard]] const std::string& GetProductCode() const { return m_product_code; } + [[nodiscard]] const std::string& GetCompanyCode() const { return m_company_code; } + [[nodiscard]] const std::string& GetContentPlatform() const { return m_content_platform; } + [[nodiscard]] uint32 GetVersion() const { return m_version; } + [[nodiscard]] CafeConsoleRegion GetRegion() const { return m_region; } + + [[nodiscard]] std::unique_ptr<uint8[]> GetIcon(uint32& size) const; + +protected: + // meta.xml + uint32 m_version; + std::string m_product_code; + std::string m_company_code; + std::string m_content_platform; + uint64 m_title_id; + CafeConsoleRegion m_region; + + std::array<std::string, 12> m_long_name; + std::array<std::string, 12> m_short_name; + std::array<std::string, 12> m_publisher; + + void ParseDirectory(const fs::path& filename) override; + bool ParseFile(const fs::path& filename) override; + + void ParseMetaFile(const fs::path& meta_file); + void ParseMetaFile(const pugi::xml_document& doc, const pugi::xml_parse_result& result); +}; + diff --git a/src/Cafe/TitleList/ParsedMetaXml.h b/src/Cafe/TitleList/ParsedMetaXml.h new file mode 100644 index 00000000..59cdaf87 --- /dev/null +++ b/src/Cafe/TitleList/ParsedMetaXml.h @@ -0,0 +1,140 @@ +#pragma once + +#include <pugixml.hpp> +#include "config/CemuConfig.h" + +struct ParsedMetaXml +{ + uint32 m_version; + std::string m_product_code; + std::string m_company_code; + std::string m_content_platform; + uint64 m_title_id; + CafeConsoleRegion m_region; + + std::array<std::string, 12> m_long_name; + std::array<std::string, 12> m_short_name; + std::array<std::string, 12> m_publisher; + + std::string GetShortName(CafeConsoleLanguage languageId) const + { + return m_short_name[(size_t)languageId]; + } + + std::string GetLongName(CafeConsoleLanguage languageId) const + { + return m_long_name[(size_t)languageId]; + } + + TitleId GetTitleId() const + { + return m_title_id; + } + + uint16 GetTitleVersion() const + { + return (uint16)m_version; + } + + CafeConsoleRegion GetRegion() const + { + return m_region; + } + + std::string GetProductCode() const + { + return m_product_code; + } + + std::string GetCompanyCode() const + { + return m_company_code; + } + + static ParsedMetaXml* Parse(uint8* xmlData, size_t xmlSize) + { + if (xmlSize == 0) + return nullptr; + pugi::xml_document meta_doc; + if (!meta_doc.load_buffer_inplace(xmlData, xmlSize)) + return nullptr; + + const auto root = meta_doc.child("menu"); + if (!root) + return nullptr; + + ParsedMetaXml* parsedMetaXml = new ParsedMetaXml(); + + for (const auto& child : root.children()) + { + std::string_view name = child.name(); + if (name == "title_version") + parsedMetaXml->m_version = child.text().as_uint(); + else if (name == "product_code") + parsedMetaXml->m_product_code = child.text().as_string(); + else if (name == "company_code") + parsedMetaXml->m_company_code = child.text().as_string(); + else if (name == "content_platform") + parsedMetaXml->m_content_platform = child.text().as_string(); + else if (name == "title_id") + parsedMetaXml->m_title_id = std::stoull(child.text().as_string(), nullptr, 16); + else if (name == "region") + parsedMetaXml->m_region = (CafeConsoleRegion)child.text().as_uint(); + else if (boost::starts_with(name, "longname_")) + { + const sint32 index = GetLanguageIndex(name.substr(std::size("longname_") - 1)); + if (index != -1) + parsedMetaXml->m_long_name[index] = child.text().as_string(); + } + else if (boost::starts_with(name, L"shortname_")) + { + const sint32 index = GetLanguageIndex(name.substr(std::size("shortname_") - 1)); + if (index != -1) + parsedMetaXml->m_short_name[index] = child.text().as_string(); + } + else if (boost::starts_with(name, L"publisher_")) + { + const sint32 index = GetLanguageIndex(name.substr(std::size("publisher_") - 1)); + if (index != -1) + parsedMetaXml->m_publisher[index] = child.text().as_string(); + } + } + if (parsedMetaXml->m_title_id == 0) + { + // not valid + delete parsedMetaXml; + return nullptr; + } + return parsedMetaXml; + } + +private: + static sint32 GetLanguageIndex(std::string_view language) // move to NCrypto ? + { + if (language == "ja") + return (sint32)CafeConsoleLanguage::JA; + else if (language == "en") + return (sint32)CafeConsoleLanguage::EN; + else if (language == "fr") + return (sint32)CafeConsoleLanguage::FR; + else if (language == "de") + return (sint32)CafeConsoleLanguage::DE; + else if (language == "it") + return (sint32)CafeConsoleLanguage::IT; + else if (language == "es") + return (sint32)CafeConsoleLanguage::ES; + else if (language == "zhs") + return (sint32)CafeConsoleLanguage::ZH; + else if (language == "ko") + return (sint32)CafeConsoleLanguage::KO; + else if (language == "nl") + return (sint32)CafeConsoleLanguage::NL; + else if (language == "pt") + return (sint32)CafeConsoleLanguage::PT; + else if (language == "ru") + return (sint32)CafeConsoleLanguage::RU; + else if (language == "zht") + return (sint32)CafeConsoleLanguage::ZH; + return -1; + } +}; diff --git a/src/Cafe/TitleList/SaveInfo.cpp b/src/Cafe/TitleList/SaveInfo.cpp new file mode 100644 index 00000000..27411619 --- /dev/null +++ b/src/Cafe/TitleList/SaveInfo.cpp @@ -0,0 +1,34 @@ +#include "SaveInfo.h" +#include "config/ActiveSettings.h" +#include "Common/filestream.h" +#include "ParsedMetaXml.h" + +SaveInfo::SaveInfo(TitleId titleId) : m_titleId(titleId) +{ + m_path = GetSavePath(titleId); + std::error_code ec; + m_isValid = fs::is_directory(m_path, ec); +} + +std::string SaveInfo::GetStorageSubpathByTitleId(TitleId titleId) +{ + // usr/save/<titleIdHigh>/<titleIdLow>/ + return fmt::format("usr/save/{:08x}/{:08x}", ((uint64)titleId) >> 32, (uint64)titleId & 0xFFFFFFFF); +} + +fs::path SaveInfo::GetSavePath(TitleId titleId) +{ + return ActiveSettings::GetMlcPath(GetStorageSubpathByTitleId(titleId)); +} + +bool SaveInfo::ParseMetaData() +{ + if (m_hasMetaLoaded) + return m_parsedMetaXml != nullptr; + m_hasMetaLoaded = true; + auto xmlData = FileStream::LoadIntoMemory(m_path / "meta/meta.xml"); + if (!xmlData) + return false; + m_parsedMetaXml = ParsedMetaXml::Parse(xmlData->data(), xmlData->size()); + return m_parsedMetaXml != nullptr; +} \ No newline at end of file diff --git a/src/Cafe/TitleList/SaveInfo.h b/src/Cafe/TitleList/SaveInfo.h new file mode 100644 index 00000000..2ea66a54 --- /dev/null +++ b/src/Cafe/TitleList/SaveInfo.h @@ -0,0 +1,30 @@ +#pragma once + +#include "TitleId.h" +#include "ParsedMetaXml.h" + +class SaveInfo +{ +public: + SaveInfo() {}; + SaveInfo(TitleId titleId); + + bool IsValid() const { return m_isValid; } + + TitleId GetTitleId() const { return m_titleId; } + fs::path GetPath() const { return m_path; } + + // meta data + bool ParseMetaData(); + ParsedMetaXml* GetMetaInfo() { return m_parsedMetaXml; } + +private: + static std::string GetStorageSubpathByTitleId(TitleId titleId); + static fs::path GetSavePath(TitleId titleId); + + TitleId m_titleId; + fs::path m_path; + bool m_isValid{false}; + bool m_hasMetaLoaded{false}; + ParsedMetaXml* m_parsedMetaXml{nullptr}; +}; \ No newline at end of file diff --git a/src/Cafe/TitleList/SaveList.cpp b/src/Cafe/TitleList/SaveList.cpp new file mode 100644 index 00000000..224d47b0 --- /dev/null +++ b/src/Cafe/TitleList/SaveList.cpp @@ -0,0 +1,177 @@ +#include "SaveList.h" +#include <charconv> + +std::mutex sSLMutex; +fs::path sSLMLCPath; + +std::vector<SaveInfo*> sSLList; + +// callback list +struct SaveListCallbackEntry +{ + SaveListCallbackEntry(void(*cb)(CafeSaveListCallbackEvent* evt, void* ctx), void* ctx, uint64 uniqueId) : + cb(cb), ctx(ctx), uniqueId(uniqueId) {}; + void (*cb)(CafeSaveListCallbackEvent* evt, void* ctx); + void* ctx; + uint64 uniqueId; +}; +std::vector<SaveListCallbackEntry> sSLCallbackList; + +// worker thread +std::atomic_bool sSLWorkerThreadActive{false}; + + +void CafeSaveList::Initialize() +{ + +} + +void CafeSaveList::SetMLCPath(fs::path mlcPath) +{ + std::unique_lock _lock(sSLMutex); + sSLMLCPath = mlcPath; +} + +void CafeSaveList::Refresh() +{ + std::unique_lock _lock(sSLMutex); + if (sSLWorkerThreadActive) + return; + sSLWorkerThreadActive = true; + std::thread t(RefreshThreadWorker); + t.detach(); +} + +void CafeSaveList::RefreshThreadWorker() +{ + // clear save list + for (auto& itSaveInfo : sSLList) + { + for (auto& it : sSLCallbackList) + { + CafeSaveListCallbackEvent evt; + evt.eventType = CafeSaveListCallbackEvent::TYPE::SAVE_REMOVED; + evt.saveInfo = itSaveInfo; + it.cb(&evt, it.ctx); + } + delete itSaveInfo; + } + sSLList.clear(); + + sSLMutex.lock(); + fs::path mlcPath = sSLMLCPath; + sSLMutex.unlock(); + std::error_code ec; + for (auto it_titleHigh : fs::directory_iterator(mlcPath / "usr/save", ec)) + { + if(!it_titleHigh.is_directory(ec)) + continue; + std::string dirName = _utf8Wrapper(it_titleHigh.path().filename()); + if(dirName.empty()) + continue; + uint32 titleIdHigh; + std::from_chars_result r = std::from_chars(dirName.data(), dirName.data() + dirName.size(), titleIdHigh, 16); + if (r.ec != std::errc()) + continue; + fs::path tmp = it_titleHigh.path(); + for (auto it_titleLow : fs::directory_iterator(tmp, ec)) + { + if (!it_titleLow.is_directory(ec)) + continue; + dirName = _utf8Wrapper(it_titleLow.path().filename()); + if (dirName.empty()) + continue; + uint32 titleIdLow; + std::from_chars_result r = std::from_chars(dirName.data(), dirName.data() + dirName.size(), titleIdLow, 16); + if (r.ec != std::errc()) + continue; + // found save + TitleId titleId = (uint64)titleIdHigh << 32 | (uint64)titleIdLow; + SaveInfo* saveInfo = new SaveInfo(titleId); + if (saveInfo->IsValid()) + DiscoveredSave(saveInfo); + else + delete saveInfo; + } + } + sSLMutex.lock(); + sSLWorkerThreadActive = false; + sSLMutex.unlock(); + // send notification about finished scan + for (auto& it : sSLCallbackList) + { + CafeSaveListCallbackEvent evt; + evt.eventType = CafeSaveListCallbackEvent::TYPE::SCAN_FINISHED; + evt.saveInfo = nullptr; + it.cb(&evt, it.ctx); + } +} + +void CafeSaveList::DiscoveredSave(SaveInfo* saveInfo) +{ + if (!saveInfo->ParseMetaData()) + { + delete saveInfo; + return; + } + std::unique_lock _lock(sSLMutex); + auto it = std::find_if(sSLList.begin(), sSLList.end(), [saveInfo](const SaveInfo* rhs) { return saveInfo->GetTitleId() == rhs->GetTitleId(); }); + if (it != sSLList.end()) + { + // save already known + delete saveInfo; + return; + } + sSLList.emplace_back(saveInfo); + // send notification + for (auto& it : sSLCallbackList) + { + CafeSaveListCallbackEvent evt; + evt.eventType = CafeSaveListCallbackEvent::TYPE::SAVE_DISCOVERED; + evt.saveInfo = saveInfo; + it.cb(&evt, it.ctx); + } +} + +uint64 CafeSaveList::RegisterCallback(void(*cb)(CafeSaveListCallbackEvent* evt, void* ctx), void* ctx) +{ + static std::atomic<uint64_t> sCallbackIdGen = 1; + uint64 id = sCallbackIdGen.fetch_add(1); + std::unique_lock _lock(sSLMutex); + sSLCallbackList.emplace_back(cb, ctx, id); + // immediately notify of all known titles + for (auto& it : sSLList) + { + CafeSaveListCallbackEvent evt; + evt.eventType = CafeSaveListCallbackEvent::TYPE::SAVE_DISCOVERED; + evt.saveInfo = it; + cb(&evt, ctx); + } + // if not scanning then send out scan finished notification + if (!sSLWorkerThreadActive) + { + CafeSaveListCallbackEvent evt; + evt.eventType = CafeSaveListCallbackEvent::TYPE::SCAN_FINISHED; + evt.saveInfo = nullptr; + for (auto& it : sSLCallbackList) + it.cb(&evt, it.ctx); + } + return id; +} + +void CafeSaveList::UnregisterCallback(uint64 id) +{ + std::unique_lock _lock(sSLMutex); + auto it = std::find_if(sSLCallbackList.begin(), sSLCallbackList.end(), [id](auto& e) { return e.uniqueId == id; }); + cemu_assert(it != sSLCallbackList.end()); + sSLCallbackList.erase(it); +} + +SaveInfo CafeSaveList::GetSaveByTitleId(TitleId titleId) +{ + std::unique_lock _lock(sSLMutex); + for (auto& it : sSLList) + if (it->GetTitleId() == titleId) + return *it; + return {}; +} \ No newline at end of file diff --git a/src/Cafe/TitleList/SaveList.h b/src/Cafe/TitleList/SaveList.h new file mode 100644 index 00000000..f624adc3 --- /dev/null +++ b/src/Cafe/TitleList/SaveList.h @@ -0,0 +1,33 @@ +#pragma once + +#include "SaveInfo.h" + +struct CafeSaveListCallbackEvent +{ + enum class TYPE + { + SAVE_DISCOVERED, + SAVE_REMOVED, + SCAN_FINISHED, + }; + TYPE eventType; + SaveInfo* saveInfo; +}; + +class CafeSaveList +{ +public: + static void Initialize(); + static void SetMLCPath(fs::path mlcPath); + static void Refresh(); + + static SaveInfo GetSaveByTitleId(TitleId titleId); + + // callback + static uint64 RegisterCallback(void(*cb)(CafeSaveListCallbackEvent* evt, void* ctx), void* ctx); // on register, the callback will be invoked for every already known save + static void UnregisterCallback(uint64 id); + +private: + static void RefreshThreadWorker(); + static void DiscoveredSave(SaveInfo* saveInfo); +}; \ No newline at end of file diff --git a/src/Cafe/TitleList/TitleId.h b/src/Cafe/TitleList/TitleId.h new file mode 100644 index 00000000..b7f63b13 --- /dev/null +++ b/src/Cafe/TitleList/TitleId.h @@ -0,0 +1,125 @@ +#pragma once + +using TitleId = uint64; + +static_assert(sizeof(TitleId) == 8); + +class TitleIdParser +{ +public: + enum class TITLE_TYPE + { + /* XX */ UNKNOWN = 0xFF, // placeholder + /* 00 */ BASE_TITLE = 0x00, // eShop and disc titles + /* 02 */ BASE_TITLE_DEMO = 0x02, + /* 0E */ BASE_TITLE_UPDATE = 0x0E, // update for BASE_TITLE (and maybe BASE_TITLE_DEMO?) + /* 0C */ AOC = 0x0C, // DLC + /* 10 */ SYSTEM_TITLE = 0x10, // eShop etc + /* 1B */ SYSTEM_DATA = 0x1B, + /* 30 */ SYSTEM_OVERLAY_TITLE = 0x30, + }; + + TitleIdParser(uint64 titleId) : m_titleId(titleId) {}; + + // controls whether this title installs to /usr/title or /sys/title + bool IsSystemTitle() const + { + return (GetTypeByte() & 0x10) != 0; + }; + + bool IsBaseTitleUpdate() const + { + return GetType() == TITLE_TYPE::BASE_TITLE_UPDATE; + } + + TITLE_TYPE GetType() const + { + uint8 b = GetTypeByte(); + switch (b) + { + case 0x00: + return TITLE_TYPE::BASE_TITLE; + case 0x02: + return TITLE_TYPE::BASE_TITLE_DEMO; + case 0x0E: + return TITLE_TYPE::BASE_TITLE_UPDATE; + case 0x0C: + return TITLE_TYPE::AOC; + case 0x10: + return TITLE_TYPE::SYSTEM_TITLE; + case 0x1B: + return TITLE_TYPE::SYSTEM_DATA; + case 0x30: + return TITLE_TYPE::SYSTEM_OVERLAY_TITLE; + } + cemuLog_log(LogType::Force, "Unknown title type ({0:016x})", m_titleId); + return TITLE_TYPE::UNKNOWN; + } + + bool IsPlatformCafe() const + { + return GetPlatformWord() == 0x0005; + } + + bool CanHaveSeparateUpdateTitleId() const + { + return GetType() == TITLE_TYPE::BASE_TITLE; + } + + TitleId GetSeparateUpdateTitleId() const + { + cemu_assert_debug(CanHaveSeparateUpdateTitleId()); + return MakeTitleIdWithType(TITLE_TYPE::BASE_TITLE_UPDATE); // e.g. 00050000-11223344 -> 0005000E-11223344 + } + + static TitleId MakeBaseTitleId(TitleId titleId) + { + TitleIdParser titleIdParser(titleId); + if (titleIdParser.GetType() == TITLE_TYPE::BASE_TITLE_UPDATE) + return titleIdParser.MakeTitleIdWithType(TITLE_TYPE::BASE_TITLE); + return titleId; + } + + static bool ParseFromStr(std::string_view strView, TitleId& titleIdOut) + { + if (strView.size() < 16) + return false; + uint64 tmp = 0; + for (size_t i = 0; i < 8*2; i++) + { + tmp <<= 4; + char c = strView[i]; + if (c >= 'A' && c <= 'F') + tmp += (uint64)(c - 'A' + 10); + else if (c >= 'a' && c <= 'f') + tmp += (uint64)(c - 'a' + 10); + else if (c >= '0' && c <= '9') + tmp += (uint64)(c - '0'); + else + return false; + } + titleIdOut = tmp; + return true; + } + +private: + uint8 GetTypeByte() const + { + return (m_titleId >> 32) & 0xFF; + } + + TitleId MakeTitleIdWithType(TITLE_TYPE newType) const + { + TitleId t = m_titleId; + t &= ~(0xFFull << 32); + t |= ((uint64)newType << 32); + return t; + } + + uint16 GetPlatformWord() const // might not be a whole word? + { + return (m_titleId >> 48) & 0xFFFF; + } + + TitleId m_titleId; +}; diff --git a/src/Cafe/TitleList/TitleInfo.cpp b/src/Cafe/TitleList/TitleInfo.cpp new file mode 100644 index 00000000..ec0ed7e5 --- /dev/null +++ b/src/Cafe/TitleList/TitleInfo.cpp @@ -0,0 +1,654 @@ +#include "TitleInfo.h" + +#include "Cafe/Filesystem/fscDeviceHostFS.h" +#include "Cafe/Filesystem/FST/FST.h" + +#include "pugixml.hpp" +#include "Common/filestream.h" + +#include <zarchive/zarchivereader.h> +#include "config/ActiveSettings.h" + +// detect format by reading file header/footer +CafeTitleFileType DetermineCafeSystemFileType(fs::path filePath) +{ + std::unique_ptr<FileStream> fs(FileStream::openFile2(filePath)); + if (!fs) + return CafeTitleFileType::UNKNOWN; + // very small files (<32 bytes) are always considered unknown + uint64 fileSize = fs->GetSize(); + if (fileSize < 32) + return CafeTitleFileType::UNKNOWN; + // read header bytes + uint8 headerRaw[32]{}; + fs->readData(headerRaw, sizeof(headerRaw)); + // check for WUX + uint8 wuxHeaderMagic[8] = { 0x57,0x55,0x58,0x30,0x2E,0xD0,0x99,0x10 }; + if (memcmp(headerRaw, wuxHeaderMagic, sizeof(wuxHeaderMagic)) == 0) + return CafeTitleFileType::WUX; + // check for RPX + uint8 rpxHeaderMagic[9] = { 0x7F,0x45,0x4C,0x46,0x01,0x02,0x01,0xCA,0xFE }; + if (memcmp(headerRaw, rpxHeaderMagic, sizeof(rpxHeaderMagic)) == 0) + return CafeTitleFileType::RPX; + // check for ELF + uint8 elfHeaderMagic[9] = { 0x7F,0x45,0x4C,0x46,0x01,0x02,0x01,0x00,0x00 }; + if (memcmp(headerRaw, elfHeaderMagic, sizeof(elfHeaderMagic)) == 0) + return CafeTitleFileType::ELF; + // check for WUD + uint8 wudMagic1[4] = { 0x57,0x55,0x50,0x2D }; // wud files should always start with "WUP-..." + uint8 wudMagic2[4] = { 0xCC,0x54,0x9E,0xB9 }; + if (fileSize >= 0x10000) + { + uint8 magic1[4]; + fs->SetPosition(0); + fs->readData(magic1, 4); + if (memcmp(magic1, wudMagic1, 4) == 0) + { + uint8 magic2[4]; + fs->SetPosition(0x10000); + fs->readData(magic2, 4); + if (memcmp(magic2, wudMagic2, 4) == 0) + { + return CafeTitleFileType::WUD; + } + } + } + // check for WUA + // todo + return CafeTitleFileType::UNKNOWN; +} + +TitleInfo::TitleInfo(const fs::path& path) +{ + m_isValid = DetectFormat(path, m_fullPath, m_titleFormat); + if (!m_isValid) + m_titleFormat = TitleDataFormat::INVALID_STRUCTURE; + else + { + m_isValid = ParseXmlInfo(); + } + if (m_isValid) + CalcUID(); +} + +TitleInfo::TitleInfo(const fs::path& path, std::string_view subPath) +{ + // path must point to a (wua) file + if (!path.has_filename()) + { + m_isValid = false; + return; + } + m_isValid = true; + m_titleFormat = TitleDataFormat::WIIU_ARCHIVE; + m_fullPath = path; + m_subPath = subPath; + m_isValid = ParseXmlInfo(); + if (m_isValid) + CalcUID(); +} + +TitleInfo::TitleInfo(const TitleInfo::CachedInfo& cachedInfo) +{ + m_cachedInfo = new CachedInfo(cachedInfo); + m_fullPath = cachedInfo.path; + m_subPath = cachedInfo.subPath; + m_titleFormat = cachedInfo.titleDataFormat; + // verify some parameters + m_isValid = false; + if (cachedInfo.titleDataFormat != TitleDataFormat::HOST_FS && + cachedInfo.titleDataFormat != TitleDataFormat::WIIU_ARCHIVE && + cachedInfo.titleDataFormat != TitleDataFormat::WUD && + cachedInfo.titleDataFormat != TitleDataFormat::INVALID_STRUCTURE) + return; + if (cachedInfo.path.empty()) + return; + if (cachedInfo.titleDataFormat == TitleDataFormat::WIIU_ARCHIVE && m_subPath.empty()) + return; // for wua files the subpath must never be empty (title must not be stored in root of archive) + m_isValid = true; + CalcUID(); +} + +TitleInfo::~TitleInfo() +{ + cemu_assert(m_mountpoints.empty()); + delete m_parsedMetaXml; + delete m_parsedAppXml; + delete m_parsedCosXml; + delete m_cachedInfo; +} + +TitleInfo::CachedInfo TitleInfo::MakeCacheEntry() +{ + cemu_assert_debug(IsValid()); + CachedInfo e; + e.titleDataFormat = m_titleFormat; + e.path = m_fullPath; + e.subPath = m_subPath; + e.titleId = GetAppTitleId(); + e.titleVersion = GetAppTitleVersion(); + e.titleName = GetTitleName(); + e.region = GetMetaRegion(); + e.group_id = GetAppGroup(); + e.app_type = GetAppType(); + return e; +} + +// WUA can contain multiple titles. Root directory contains one directory for each title. The name must match: <titleId>_v<version> +bool TitleInfo::ParseWuaTitleFolderName(std::string_view name, TitleId& titleIdOut, uint16& titleVersionOut) +{ + std::string_view sv = name; + if (sv.size() < 16 + 2) + return false; + TitleId parsedId; + if (!TitleIdParser::ParseFromStr(sv, parsedId)) + return false; + sv.remove_prefix(16); + if (sv[0] != '_' || (sv[1] != 'v' && sv[1] != 'v')) + return false; + sv.remove_prefix(2); + if (sv.empty()) + return false; + if (sv[0] == '0' && sv.size() != 1) // leading zero not allowed + return false; + uint32 v = 0; + while (!sv.empty()) + { + uint8 c = sv[0]; + sv.remove_prefix(1); + v *= 10; + if (c >= '0' && c <= '9') + v += (uint32)(c - '0'); + else + { + v = 0xFFFFFFFF; + break; + } + } + if (v > 0xFFFF) + return false; + titleIdOut = parsedId; + titleVersionOut = v; + return true; +} + +bool TitleInfo::DetectFormat(const fs::path& path, fs::path& pathOut, TitleDataFormat& formatOut) +{ + std::error_code ec; + if (path.has_extension() && fs::is_regular_file(path, ec)) + { + std::string filenameStr = _utf8Wrapper(path.filename()); + if (boost::iends_with(filenameStr, ".rpx")) + { + // is in code folder? + fs::path parentPath = path.parent_path(); + if (boost::iequals(_utf8Wrapper(parentPath.filename()), "code")) + { + parentPath = parentPath.parent_path(); + // next to content and meta? + std::error_code ec; + if (fs::exists(parentPath / "content", ec) && fs::exists(parentPath / "meta", ec)) + { + formatOut = TitleDataFormat::HOST_FS; + pathOut = parentPath; + return true; + } + } + } + else if (boost::iends_with(filenameStr, ".wud") || + boost::iends_with(filenameStr, ".wux") || + boost::iends_with(filenameStr, ".iso")) + { + formatOut = TitleDataFormat::WUD; + pathOut = path; + return true; + } + else if (boost::iends_with(filenameStr, ".wua")) + { + formatOut = TitleDataFormat::WIIU_ARCHIVE; + pathOut = path; + // a Wii U archive file can contain multiple titles but TitleInfo only maps to one + // we use the first base title that we find. This is the most intuitive behavior when someone launches "game.wua" + ZArchiveReader* zar = ZArchiveReader::OpenFromFile(path); + if (!zar) + return false; + ZArchiveNodeHandle rootDir = zar->LookUp("", false, true); + bool foundBase = false; + for (uint32 i = 0; i < zar->GetDirEntryCount(rootDir); i++) + { + ZArchiveReader::DirEntry dirEntry; + if (!zar->GetDirEntry(rootDir, i, dirEntry)) + continue; + if (!dirEntry.isDirectory) + continue; + TitleId parsedId; + uint16 parsedVersion; + if (!TitleInfo::ParseWuaTitleFolderName(dirEntry.name, parsedId, parsedVersion)) + continue; + TitleIdParser tip(parsedId); + TitleIdParser::TITLE_TYPE tt = tip.GetType(); + if (tt == TitleIdParser::TITLE_TYPE::BASE_TITLE || tt == TitleIdParser::TITLE_TYPE::BASE_TITLE_DEMO || + tt == TitleIdParser::TITLE_TYPE::SYSTEM_TITLE || tt == TitleIdParser::TITLE_TYPE::SYSTEM_OVERLAY_TITLE) + { + m_subPath = dirEntry.name; + foundBase = true; + break; + } + } + delete zar; + return foundBase; + } + // note: Since a Wii U archive file (.wua) contains multiple titles we shouldn't auto-detect them here + // instead TitleInfo has a second constructor which takes a subpath + // unable to determine type by extension, check contents + CafeTitleFileType fileType = DetermineCafeSystemFileType(path); + if (fileType == CafeTitleFileType::WUD || + fileType == CafeTitleFileType::WUX) + { + formatOut = TitleDataFormat::WUD; + pathOut = path; + return true; + } + } + else + { + // does it point to the root folder of a title? + std::error_code ec; + if (fs::exists(path / "content", ec) && fs::exists(path / "meta", ec) && fs::exists(path / "code", ec)) + { + formatOut = TitleDataFormat::HOST_FS; + pathOut = path; + return true; + } + } + return false; +} + +bool TitleInfo::IsValid() const +{ + return m_isValid; +} + +fs::path TitleInfo::GetPath() const +{ + if (!m_isValid) + { + cemu_assert_suspicious(); + return {}; + } + return m_fullPath; +} + +void TitleInfo::CalcUID() +{ + cemu_assert_debug(m_isValid); + if (!m_isValid) + { + m_uid = 0; + return; + } + std::error_code ec; + // get absolute normalized path + fs::path normalizedPath; + if (m_fullPath.is_relative()) + { + normalizedPath = ActiveSettings::GetPath(); + normalizedPath /= m_fullPath; + } + else + normalizedPath = m_fullPath; + normalizedPath = normalizedPath.lexically_normal(); + uint64 h = fs::hash_value(normalizedPath); + // for WUA files also hash the subpath + if (m_titleFormat == TitleDataFormat::WIIU_ARCHIVE) + { + uint64 subHash = std::hash<std::string_view>{}(m_subPath); + h += subHash; + } + m_uid = h; +} + +uint64 TitleInfo::GetUID() +{ + cemu_assert_debug(m_isValid); + return m_uid; +} + +std::mutex sZArchivePoolMtx; +std::map<fs::path, std::pair<uint32, ZArchiveReader*>> sZArchivePool; + +ZArchiveReader* _ZArchivePool_AcquireInstance(const fs::path& path) +{ + std::unique_lock _lock(sZArchivePoolMtx); + auto it = sZArchivePool.find(path); + if (it != sZArchivePool.end()) + { + it->second.first++; // increment ref count + return it->second.second; + } + _lock.unlock(); + // opening wua files can be expensive, so we do it outside of the lock + ZArchiveReader* zar = ZArchiveReader::OpenFromFile(path); + if (!zar) + return nullptr; + _lock.lock(); + // check if another instance was allocated in the meantime + it = sZArchivePool.find(path); + if (it != sZArchivePool.end()) + { + delete zar; + it->second.first++; // increment ref count + return it->second.second; + } + sZArchivePool.emplace(std::piecewise_construct, + std::forward_as_tuple(path), + std::forward_as_tuple(1, zar) + ); + return zar; +} + +void _ZArchivePool_ReleaseInstance(const fs::path& path, ZArchiveReader* zar) +{ + std::unique_lock _lock(sZArchivePoolMtx); + auto it = sZArchivePool.find(path); + cemu_assert(it != sZArchivePool.end()); + cemu_assert(it->second.second == zar); + it->second.first--; // decrement ref count + if (it->second.first == 0) + { + delete it->second.second; + sZArchivePool.erase(it); + } +} + +bool TitleInfo::Mount(std::string_view virtualPath, std::string_view subfolder, sint32 mountPriority) +{ + cemu_assert_debug(subfolder.empty() || (subfolder.front() != '/' || subfolder.front() != '\\')); // only relative subfolder allowed + + cemu_assert(m_isValid); + if (m_titleFormat == TitleDataFormat::HOST_FS) + { + fs::path hostFSPath = m_fullPath; + hostFSPath.append(subfolder); + bool r = FSCDeviceHostFS_Mount(std::string(virtualPath).c_str(), boost::nowide::widen(_utf8Wrapper(hostFSPath)).c_str(), mountPriority); + cemu_assert_debug(r); + if (!r) + { + cemuLog_log(LogType::Force, "Failed to mount {} to {}", virtualPath, subfolder); + return false; + } + } + else if (m_titleFormat == TitleDataFormat::WUD) + { + if (m_mountpoints.empty()) + { + cemu_assert_debug(!m_wudVolume); + m_wudVolume = FSTVolume::OpenFromDiscImage(m_fullPath); + } + if (!m_wudVolume) + return false; + bool r = FSCDeviceWUD_Mount(std::string(virtualPath).c_str(), subfolder, m_wudVolume, mountPriority); + cemu_assert_debug(r); + if (!r) + { + cemuLog_log(LogType::Force, "Failed to mount {} to {}", virtualPath, subfolder); + delete m_wudVolume; + return false; + } + } + else if (m_titleFormat == TitleDataFormat::WIIU_ARCHIVE) + { + if (!m_zarchive) + { + m_zarchive = _ZArchivePool_AcquireInstance(m_fullPath); + if (!m_zarchive) + return false; + } + bool r = FSCDeviceWUA_Mount(std::string(virtualPath).c_str(), std::string(m_subPath).append("/").append(subfolder), m_zarchive, mountPriority); + if (!r) + { + cemuLog_log(LogType::Force, "Failed to mount {} to {}", virtualPath, subfolder); + _ZArchivePool_ReleaseInstance(m_fullPath, m_zarchive); + return false; + } + } + else + { + cemu_assert_unimplemented(); + } + m_mountpoints.emplace_back(mountPriority, virtualPath); + return true; +} + +void TitleInfo::Unmount(std::string_view virtualPath) +{ + for (auto& itr : m_mountpoints) + { + if (!boost::equals(itr.second, virtualPath)) + continue; + fsc_unmount(itr.second.c_str(), itr.first); + std::erase(m_mountpoints, itr); + // if the last mount point got unmounted, delete any open devices + if (m_mountpoints.empty()) + { + if (m_wudVolume) + { + cemu_assert_debug(m_titleFormat == TitleDataFormat::WUD); + delete m_wudVolume; + m_wudVolume = nullptr; + } + } + // wua files use reference counting + if (m_zarchive) + { + _ZArchivePool_ReleaseInstance(m_fullPath, m_zarchive); + if (m_mountpoints.empty()) + m_zarchive = nullptr; + } + return; + } + cemu_assert_suspicious(); // unmount on unknown path +} + +void TitleInfo::UnmountAll() +{ + while (!m_mountpoints.empty()) + Unmount(m_mountpoints.front().second); +} + +std::atomic_uint64_t sTempMountingPathCounter = 1; + +std::string TitleInfo::GetUniqueTempMountingPath() +{ + uint64_t v = sTempMountingPathCounter.fetch_add(1); + return fmt::format("/internal/tempMount{:016x}/", v); +} + +bool TitleInfo::ParseXmlInfo() +{ + cemu_assert(m_isValid); + if (m_hasParsedXmlFiles) + return m_parsedMetaXml && m_parsedAppXml && m_parsedCosXml; + m_hasParsedXmlFiles = true; + + std::string mountPath = GetUniqueTempMountingPath(); + bool r = Mount(mountPath, "", FSC_PRIORITY_BASE); + if (!r) + return false; + // meta/meta.xml + auto xmlData = fsc_extractFile(fmt::format("{}meta/meta.xml", mountPath).c_str()); + if(xmlData) + m_parsedMetaXml = ParsedMetaXml::Parse(xmlData->data(), xmlData->size()); + // code/app.xml + xmlData = fsc_extractFile(fmt::format("{}code/app.xml", mountPath).c_str()); + if(xmlData) + ParseAppXml(*xmlData); + // code/cos.xml + xmlData = fsc_extractFile(fmt::format("{}code/cos.xml", mountPath).c_str()); + if (xmlData) + m_parsedCosXml = ParsedCosXml::Parse(xmlData->data(), xmlData->size()); + + Unmount(mountPath); + + bool hasAnyXml = m_parsedMetaXml || m_parsedAppXml || m_parsedCosXml; + + if (!m_parsedMetaXml || !m_parsedAppXml || !m_parsedCosXml) + { + if (hasAnyXml) + cemuLog_log(LogType::Force, "Title has missing meta .xml files. Title path: {}", _utf8Wrapper(m_fullPath)); + delete m_parsedMetaXml; + delete m_parsedAppXml; + delete m_parsedCosXml; + m_parsedMetaXml = nullptr; + m_parsedAppXml = nullptr; + m_parsedCosXml = nullptr; + m_isValid = false; + return false; + } + m_isValid = true; + return true; +} + +bool TitleInfo::ParseAppXml(std::vector<uint8>& appXmlData) +{ + pugi::xml_document app_doc; + if (!app_doc.load_buffer_inplace(appXmlData.data(), appXmlData.size())) + return false; + + const auto root = app_doc.child("app"); + if (!root) + return false; + + m_parsedAppXml = new ParsedAppXml(); + + for (const auto& child : root.children()) + { + std::string_view name = child.name(); + if (name == "title_version") + m_parsedAppXml->title_version = (uint16)std::stoull(child.text().as_string(), nullptr, 16); + else if (name == "title_id") + m_parsedAppXml->title_id = std::stoull(child.text().as_string(), nullptr, 16); + else if (name == "app_type") + m_parsedAppXml->app_type = (uint32)std::stoull(child.text().as_string(), nullptr, 16); + else if (name == "group_id") + m_parsedAppXml->group_id = (uint32)std::stoull(child.text().as_string(), nullptr, 16); + } + return true; +} + +TitleId TitleInfo::GetAppTitleId() const +{ + cemu_assert_debug(m_isValid); + if (m_parsedAppXml) + return m_parsedAppXml->title_id; + if (m_cachedInfo) + return m_cachedInfo->titleId; + cemu_assert_suspicious(); + return 0; +} + +uint16 TitleInfo::GetAppTitleVersion() const +{ + cemu_assert_debug(m_isValid); + if (m_parsedAppXml) + return m_parsedAppXml->title_version; + if (m_cachedInfo) + return m_cachedInfo->titleVersion; + cemu_assert_suspicious(); + return 0; +} + +uint32 TitleInfo::GetAppGroup() const +{ + cemu_assert_debug(m_isValid); + if (m_parsedAppXml) + return m_parsedAppXml->group_id; + if (m_cachedInfo) + return m_cachedInfo->group_id; + cemu_assert_suspicious(); + return 0; +} + +uint32 TitleInfo::GetAppType() const +{ + cemu_assert_debug(m_isValid); + if (m_parsedAppXml) + return m_parsedAppXml->app_type; + if (m_cachedInfo) + return m_cachedInfo->app_type; + cemu_assert_suspicious(); + return 0; +} + +TitleIdParser::TITLE_TYPE TitleInfo::GetTitleType() +{ + TitleIdParser tip(GetAppTitleId()); + return tip.GetType(); +} + +std::string TitleInfo::GetTitleName() const +{ + cemu_assert_debug(m_isValid); + if (m_parsedMetaXml) + return m_parsedMetaXml->GetShortName(CafeConsoleLanguage::EN); + if (m_cachedInfo) + return m_cachedInfo->titleName; + cemu_assert_suspicious(); + return ""; +} + +CafeConsoleRegion TitleInfo::GetMetaRegion() const +{ + cemu_assert_debug(m_isValid); + if (m_parsedMetaXml) + return m_parsedMetaXml->GetRegion(); + if (m_cachedInfo) + return m_cachedInfo->region; + cemu_assert_suspicious(); + return CafeConsoleRegion::JPN; +} + +std::string TitleInfo::GetArgStr() const +{ + cemu_assert_debug(m_parsedCosXml); + if (!m_parsedCosXml) + return ""; + return m_parsedCosXml->argstr; +} + +std::string TitleInfo::GetPrintPath() const +{ + if (!m_isValid) + return "invalid"; + std::string tmp; + tmp.append(_utf8Wrapper(m_fullPath)); + switch (m_titleFormat) + { + case TitleDataFormat::HOST_FS: + tmp.append(" [Folder]"); + break; + case TitleDataFormat::WUD: + tmp.append(" [WUD]"); + break; + case TitleDataFormat::WIIU_ARCHIVE: + tmp.append(" [WUA]"); + break; + default: + break; + } + if (m_titleFormat == TitleDataFormat::WIIU_ARCHIVE) + tmp.append(fmt::format(" [{}]", m_subPath)); + return tmp; +} + +std::string TitleInfo::GetInstallPath() const +{ + TitleId titleId = GetAppTitleId(); + TitleIdParser tip(titleId); + std::string tmp; + if (tip.IsSystemTitle()) + tmp = fmt::format("sys\\title\\{:08x}\\{:08x}", GetTitleIdHigh(titleId), GetTitleIdLow(titleId)); + else + tmp = fmt::format("usr\\title\\{:08x}\\{:08x}", GetTitleIdHigh(titleId), GetTitleIdLow(titleId)); + return tmp; +} \ No newline at end of file diff --git a/src/Cafe/TitleList/TitleInfo.h b/src/Cafe/TitleList/TitleInfo.h new file mode 100644 index 00000000..86f89391 --- /dev/null +++ b/src/Cafe/TitleList/TitleInfo.h @@ -0,0 +1,190 @@ +#pragma once + +#include "Cafe/Filesystem/fsc.h" +#include "config/CemuConfig.h" // for CafeConsoleRegion. Move to NCrypto? +#include "TitleId.h" +#include "ParsedMetaXml.h" + +enum class CafeTitleFileType +{ + UNKNOWN, + WUD, + WUX, + RPX, + ELF, +}; + +CafeTitleFileType DetermineCafeSystemFileType(fs::path filePath); + +struct ParsedAppXml +{ + uint64 title_id; + uint16 title_version; + uint32 app_type; + uint32 group_id; +}; + +struct ParsedCosXml +{ + std::string argstr; + + static ParsedCosXml* Parse(uint8* xmlData, size_t xmlLen) + { + pugi::xml_document app_doc; + if (!app_doc.load_buffer_inplace(xmlData, xmlLen)) + return nullptr; + + const auto root = app_doc.child("app"); + if (!root) + return nullptr; + + ParsedCosXml* parsedCos = new ParsedCosXml(); + + for (const auto& child : root.children()) + { + std::string_view name = child.name(); + if (name == "argstr") + parsedCos->argstr = child.text().as_string(); + } + return parsedCos; + } +}; + +class TitleInfo +{ +public: + enum class TitleDataFormat + { + HOST_FS = 1, // host filesystem directory (fullPath points to root with content/code/meta subfolders) + WUD = 2, // WUD or WUX + WIIU_ARCHIVE = 3, // Wii U compressed single-file archive (.wua) + // error + INVALID_STRUCTURE = 0, + }; + + struct CachedInfo + { + TitleDataFormat titleDataFormat; + fs::path path; + std::string subPath; // for WUA + uint64 titleId; + uint16 titleVersion; + std::string titleName; + CafeConsoleRegion region; + uint32 group_id; + uint32 app_type; + }; + + TitleInfo() : m_isValid(false) {}; + TitleInfo(const fs::path& path); + TitleInfo(const fs::path& path, std::string_view subPath); + TitleInfo(const CachedInfo& cachedInfo); + ~TitleInfo(); + + TitleInfo(const TitleInfo& other) + { + Copy(other); + } + + TitleInfo& operator=(TitleInfo other) + { + Copy(other); + return *this; + } + + bool IsCached() { return m_cachedInfo; }; // returns true if this TitleInfo was loaded from cache and has not yet been parsed + + CachedInfo MakeCacheEntry(); + + bool IsValid() const; + uint64 GetUID(); // returns a unique identifier derived from the absolute canonical title location which can be used to identify this title by its location. May not persist across sessions, especially when Cemu is used portable + + fs::path GetPath() const; + TitleDataFormat GetFormat() const { return m_titleFormat; }; + + bool Mount(std::string_view virtualPath, std::string_view subfolder, sint32 mountPriority); + void Unmount(std::string_view virtualPath); + void UnmountAll(); + bool IsMounted() const { return !m_mountpoints.empty(); } + + bool ParseXmlInfo(); + bool HasValidXmlInfo() const { return m_parsedMetaXml && m_parsedAppXml && m_parsedCosXml; }; + + bool IsEqualByLocation(const TitleInfo& rhs) const + { + return m_uid == rhs.m_uid; + } + + // API which requires parsed meta data or cached info + TitleId GetAppTitleId() const; // from app.xml + uint16 GetAppTitleVersion() const; // from app.xml + uint32 GetAppGroup() const; // from app.xml + uint32 GetAppType() const; // from app.xml + std::string GetTitleName() const; // from meta.xml + CafeConsoleRegion GetMetaRegion() const; // from meta.xml + + // cos.xml + std::string GetArgStr() const; + + // meta.xml also contains a version which seems to match the one from app.xml + // the titleId in meta.xml seems to be the title id of the base game for updates specifically. For AOC content it's the AOC's titleId + + TitleIdParser::TITLE_TYPE GetTitleType(); + ParsedMetaXml* GetMetaInfo() + { + return m_parsedMetaXml; + } + + std::string GetPrintPath() const; // formatted path for log writing + std::string GetInstallPath() const; // installation subpath, relative to storage base. E.g. "usr/title/.../..." or "sys/title/.../..." + + static std::string GetUniqueTempMountingPath(); + static bool ParseWuaTitleFolderName(std::string_view name, TitleId& titleIdOut, uint16& titleVersionOut); + +private: + void Copy(const TitleInfo& other) + { + m_isValid = other.m_isValid; + m_titleFormat = other.m_titleFormat; + m_fullPath = other.m_fullPath; + m_subPath = other.m_subPath; + m_hasParsedXmlFiles = other.m_hasParsedXmlFiles; + m_parsedMetaXml = nullptr; + m_parsedAppXml = nullptr; + + if (other.m_parsedMetaXml) + m_parsedMetaXml = new ParsedMetaXml(*other.m_parsedMetaXml); + if (other.m_parsedAppXml) + m_parsedAppXml = new ParsedAppXml(*other.m_parsedAppXml); + if (other.m_parsedCosXml) + m_parsedCosXml = new ParsedCosXml(*other.m_parsedCosXml); + + if (other.m_cachedInfo) + m_cachedInfo = new CachedInfo(*other.m_cachedInfo); + + m_mountpoints.clear(); + m_wudVolume = nullptr; + } + + bool DetectFormat(const fs::path& path, fs::path& pathOut, TitleDataFormat& formatOut); + void CalcUID(); + + bool ParseAppXml(std::vector<uint8>& appXmlData); + + bool m_isValid{ false }; + TitleDataFormat m_titleFormat{ TitleDataFormat::INVALID_STRUCTURE }; + fs::path m_fullPath; + std::string m_subPath; // used for formats where fullPath isn't unique on its own (like WUA) + uint64 m_uid{}; + // mounting info + std::vector<std::pair<sint32, std::string>> m_mountpoints; + class FSTVolume* m_wudVolume{}; + class ZArchiveReader* m_zarchive{}; + // xml info + bool m_hasParsedXmlFiles{ false }; + ParsedMetaXml* m_parsedMetaXml{}; + ParsedAppXml* m_parsedAppXml{}; + ParsedCosXml* m_parsedCosXml{}; + // cached info if called with cache constructor + CachedInfo* m_cachedInfo{nullptr}; +}; \ No newline at end of file diff --git a/src/Cafe/TitleList/TitleList.cpp b/src/Cafe/TitleList/TitleList.cpp new file mode 100644 index 00000000..7e1bf883 --- /dev/null +++ b/src/Cafe/TitleList/TitleList.cpp @@ -0,0 +1,678 @@ +#include "TitleList.h" +#include "Common/filestream.h" + +#include "util/helpers/helpers.h" + +#include <zarchive/zarchivereader.h> + +bool sTLInitialized{ false }; +fs::path sTLCacheFilePath; + +// lists for tracking known titles +// note: The list may only contain titles with valid meta data. Entries loaded from the cache may not have been parsed yet, but they will use a cached value for titleId and titleVersion +std::mutex sTLMutex; +std::vector<TitleInfo*> sTLList; +std::vector<TitleInfo*> sTLListPending; +std::unordered_multimap<uint64, TitleInfo*> sTLMap; +bool sTLCacheDirty{false}; + +// paths +fs::path sTLMLCPath; +std::vector<fs::path> sTLScanPaths; + +// worker +std::thread sTLRefreshWorker; +bool sTLRefreshWorkerActive{false}; +std::atomic_uint32_t sTLRefreshRequests{}; +std::atomic_bool sTLIsScanMandatory{ false }; + +// callback list +struct TitleListCallbackEntry +{ + TitleListCallbackEntry(void(*cb)(CafeTitleListCallbackEvent* evt, void* ctx), void* ctx, uint64 uniqueId) : + cb(cb), ctx(ctx), uniqueId(uniqueId) {}; + void (*cb)(CafeTitleListCallbackEvent* evt, void* ctx); + void* ctx; + uint64 uniqueId; +}; + +std::vector<TitleListCallbackEntry> sTLCallbackList; + +void CafeTitleList::Initialize(const fs::path cacheXmlFile) +{ + std::unique_lock _lock(sTLMutex); + sTLInitialized = true; + sTLCacheFilePath = cacheXmlFile; + LoadCacheFile(); +} + +void CafeTitleList::LoadCacheFile() +{ + sTLIsScanMandatory = true; + cemu_assert_debug(sTLInitialized); + cemu_assert_debug(sTLList.empty()); + auto xmlData = FileStream::LoadIntoMemory(sTLCacheFilePath); + if (!xmlData) + return; + pugi::xml_document doc; + if (!doc.load_buffer_inplace(xmlData->data(), xmlData->size(), pugi::parse_default, pugi::xml_encoding::encoding_utf8)) + return; + auto titleListNode = doc.child("title_list"); + pugi::xml_node itNode = titleListNode.first_child(); + for (const auto& titleInfoNode : doc.child("title_list")) + { + TitleId titleId; + if( !TitleIdParser::ParseFromStr(titleInfoNode.attribute("titleId").as_string(), titleId)) + continue; + uint16 titleVersion = titleInfoNode.attribute("version").as_uint(); + TitleInfo::TitleDataFormat format = (TitleInfo::TitleDataFormat)ConvertString<uint32>(titleInfoNode.child_value("format")); + CafeConsoleRegion region = (CafeConsoleRegion)ConvertString<uint32>(titleInfoNode.child_value("region")); + std::string name = titleInfoNode.child_value("name"); + std::string path = titleInfoNode.child_value("path"); + std::string sub_path = titleInfoNode.child_value("sub_path"); + uint32 group_id = ConvertString<uint32>(titleInfoNode.attribute("group_id").as_string(), 16); + uint32 app_type = ConvertString<uint32>(titleInfoNode.attribute("app_type").as_string(), 16); + + TitleInfo::CachedInfo cacheEntry; + cacheEntry.titleId = titleId; + cacheEntry.titleVersion = titleVersion; + cacheEntry.titleDataFormat = format; + cacheEntry.region = region; + cacheEntry.titleName = name; + cacheEntry.path = _asUtf8(path); + cacheEntry.subPath = sub_path; + cacheEntry.group_id = group_id; + cacheEntry.app_type = app_type; + + TitleInfo* ti = new TitleInfo(cacheEntry); + if (!ti->IsValid()) + { + cemuLog_log(LogType::Force, "Title cache contained invalid title"); + delete ti; + continue; + } + AddTitle(ti); + } + sTLIsScanMandatory = false; +} + +void CafeTitleList::StoreCacheFile() +{ + cemu_assert_debug(sTLInitialized); + if (sTLCacheFilePath.empty()) + return; + std::unique_lock _lock(sTLMutex); + + pugi::xml_document doc; + auto declarationNode = doc.append_child(pugi::node_declaration); + declarationNode.append_attribute("version") = "1.0"; + declarationNode.append_attribute("encoding") = "UTF-8"; + auto title_list_node = doc.append_child("title_list"); + + for (auto& tiIt : sTLList) + { + TitleInfo::CachedInfo info = tiIt->MakeCacheEntry(); + auto titleInfoNode = title_list_node.append_child("title"); + titleInfoNode.append_attribute("titleId").set_value(fmt::format("{:016x}", info.titleId).c_str()); + titleInfoNode.append_attribute("version").set_value(fmt::format("{:}", info.titleVersion).c_str()); + titleInfoNode.append_attribute("group_id").set_value(fmt::format("{:08x}", info.group_id).c_str()); + titleInfoNode.append_attribute("app_type").set_value(fmt::format("{:08x}", info.app_type).c_str()); + titleInfoNode.append_child("region").append_child(pugi::node_pcdata).set_value(fmt::format("{}", (uint32)info.region).c_str()); + titleInfoNode.append_child("name").append_child(pugi::node_pcdata).set_value(info.titleName.c_str()); + titleInfoNode.append_child("format").append_child(pugi::node_pcdata).set_value(fmt::format("{}", (uint32)info.titleDataFormat).c_str()); + titleInfoNode.append_child("path").append_child(pugi::node_pcdata).set_value(_utf8Wrapper(info.path).c_str()); + if(!info.subPath.empty()) + titleInfoNode.append_child("sub_path").append_child(pugi::node_pcdata).set_value(_utf8Wrapper(info.subPath).c_str()); + } + + fs::path tmpPath = fs::path(sTLCacheFilePath.parent_path()).append(fmt::format("{}__tmp", _utf8Wrapper(sTLCacheFilePath.filename()))); + std::ofstream fileOut(tmpPath, std::ios::out | std::ios::binary | std::ios::trunc); + if (!fileOut.is_open()) + { + cemuLog_log(LogType::Force, "Unable to store title list in {}", _utf8Wrapper(tmpPath)); + return; + } + doc.save(fileOut, " ", 1, pugi::xml_encoding::encoding_utf8); + fileOut.flush(); + fileOut.close(); + + std::error_code ec; + fs::rename(tmpPath, sTLCacheFilePath, ec); +} + +void CafeTitleList::ClearScanPaths() +{ + std::unique_lock _lock(sTLMutex); + sTLScanPaths.clear(); +} + +void CafeTitleList::AddScanPath(fs::path path) +{ + std::unique_lock _lock(sTLMutex); + sTLScanPaths.emplace_back(path); +} + +void CafeTitleList::SetMLCPath(fs::path path) +{ + std::unique_lock _lock(sTLMutex); + std::error_code ec; + if (!fs::is_directory(path, ec)) + { + cemuLog_log(LogType::Force, "MLC set to invalid path: {}", _utf8Wrapper(path)); + return; + } + sTLMLCPath = path; +} + +void CafeTitleList::Refresh() +{ + std::unique_lock _lock(sTLMutex); + cemu_assert_debug(sTLInitialized); + sTLRefreshRequests++; + if (!sTLRefreshWorkerActive) + { + if (sTLRefreshWorker.joinable()) + sTLRefreshWorker.join(); + sTLRefreshWorkerActive = true; + sTLRefreshWorker = std::thread(RefreshWorkerThread); + } + sTLIsScanMandatory = false; +} + +bool CafeTitleList::IsScanning() +{ + std::unique_lock _lock(sTLMutex); + return sTLRefreshWorkerActive; +} + +void CafeTitleList::WaitForMandatoryScan() +{ + if (!sTLIsScanMandatory) + return; + while (IsScanning()) + std::this_thread::sleep_for(std::chrono::milliseconds(100)); +} + +void _RemoveTitleFromMultimap(TitleInfo* titleInfo) +{ + auto mapRange = sTLMap.equal_range(titleInfo->GetAppTitleId()); + for (auto mapIt = mapRange.first; mapIt != mapRange.second; ++mapIt) + { + if (mapIt->second == titleInfo) + { + sTLMap.erase(mapIt); + return; + } + } + cemu_assert_suspicious(); +} + +// check if path is a valid title and if it is, permanently add it to the title list +// in the special case that path points to a WUA file, all contained titles will be added +void CafeTitleList::AddTitleFromPath(fs::path path) +{ + if (path.has_extension() && boost::iequals(_utf8Wrapper(path.extension()), ".wua")) + { + ZArchiveReader* zar = ZArchiveReader::OpenFromFile(path); + if (!zar) + { + cemuLog_log(LogType::Force, "Found {} but it is not a valid Wii U archive file", _utf8Wrapper(path)); + return; + } + // enumerate all contained titles + ZArchiveNodeHandle rootDir = zar->LookUp("", false, true); + cemu_assert(rootDir != ZARCHIVE_INVALID_NODE); + for (uint32 i = 0; i < zar->GetDirEntryCount(rootDir); i++) + { + ZArchiveReader::DirEntry dirEntry; + if( !zar->GetDirEntry(rootDir, i, dirEntry) ) + continue; + if(!dirEntry.isDirectory) + continue; + TitleId parsedId; + uint16 parsedVersion; + if (!TitleInfo::ParseWuaTitleFolderName(dirEntry.name, parsedId, parsedVersion)) + { + cemuLog_log(LogType::Force, "Invalid title directory in {}: \"{}\"", _utf8Wrapper(path), dirEntry.name); + continue; + } + // valid subdirectory + TitleInfo* titleInfo = new TitleInfo(path, dirEntry.name); + if (titleInfo->IsValid()) + AddDiscoveredTitle(titleInfo); + else + delete titleInfo; + } + delete zar; + return; + } + TitleInfo* titleInfo = new TitleInfo(path); + if (titleInfo->IsValid()) + AddDiscoveredTitle(titleInfo); + else + delete titleInfo; +} + +bool CafeTitleList::RefreshWorkerThread() +{ + while (sTLRefreshRequests.load()) + { + sTLRefreshRequests.store(0); + // create copies of all the paths + sTLMutex.lock(); + fs::path mlcPath = sTLMLCPath; + std::vector<fs::path> gamePaths = sTLScanPaths; + // remember the current list of known titles + // during the scanning process we will erase matches from the pending list + // at the end of scanning, we can then use this list to identify and remove any titles that are no longer discoverable + sTLListPending = sTLList; + sTLMutex.unlock(); + // scan game paths + for (auto& it : gamePaths) + ScanGamePath(it); + // scan MLC + if (!mlcPath.empty()) + { + std::error_code ec; + for (auto& it : fs::directory_iterator(mlcPath / "usr/title", ec)) + { + if (!it.is_directory(ec)) + continue; + ScanMLCPath(it.path()); + } + ScanMLCPath(mlcPath / "sys/title/00050010"); + ScanMLCPath(mlcPath / "sys/title/00050030"); + } + + // remove any titles that are still pending + for (auto& itPending : sTLListPending) + { + _RemoveTitleFromMultimap(itPending); + std::erase(sTLList, itPending); + } + // send notifications for removed titles, but only if there exists no other title with the same titleId and version + if (!sTLListPending.empty()) + sTLCacheDirty = true; + for (auto& itPending : sTLListPending) + { + CafeTitleListCallbackEvent evt; + evt.eventType = CafeTitleListCallbackEvent::TYPE::TITLE_REMOVED; + evt.titleInfo = itPending; + for (auto& it : sTLCallbackList) + it.cb(&evt, it.ctx); + delete itPending; + } + sTLListPending.clear(); + } + sTLMutex.lock(); + sTLRefreshWorkerActive = false; + // send notification that scanning finished + CafeTitleListCallbackEvent evt; + evt.eventType = CafeTitleListCallbackEvent::TYPE::SCAN_FINISHED; + evt.titleInfo = nullptr; + for (auto& it : sTLCallbackList) + it.cb(&evt, it.ctx); + sTLMutex.unlock(); + if (sTLCacheDirty) + { + StoreCacheFile(); + sTLCacheDirty = false; + } + return true; +} + +bool _IsKnownFileExtension(std::string fileExtension) +{ + for (auto& it : fileExtension) + if (it >= 'A' && it <= 'Z') + it -= ('A' - 'a'); + return + fileExtension == ".wud" || + fileExtension == ".wux" || + fileExtension == ".iso" || + fileExtension == ".wua"; + // note: To detect extracted titles with RPX we use the content/code/meta folder structure +} + +void CafeTitleList::ScanGamePath(const fs::path& path) +{ + // scan the whole directory first to determine if this is a title folder + std::vector<fs::path> filesInDirectory; + std::vector<fs::path> dirsInDirectory; + bool hasContentFolder = false, hasCodeFolder = false, hasMetaFolder = false; + std::error_code ec; + for (auto& it : fs::directory_iterator(path, ec)) + { + if (it.is_regular_file(ec)) + { + filesInDirectory.emplace_back(it.path()); + } + else if (it.is_directory(ec)) + { + dirsInDirectory.emplace_back(it.path()); + + std::string dirName = _utf8Wrapper(it.path().filename()); + if (boost::iequals(dirName, "content")) + hasContentFolder = true; + else if (boost::iequals(dirName, "code")) + hasCodeFolder = true; + else if (boost::iequals(dirName, "meta")) + hasMetaFolder = true; + } + } + // always check individual files + for (auto& it : filesInDirectory) + { + // since checking files is slow, we only do it for known file extensions + if (!it.has_extension()) + continue; + if (!_IsKnownFileExtension(_utf8Wrapper(it.extension()))) + continue; + AddTitleFromPath(it); + } + // is the current directory a title folder? + if (hasContentFolder && hasCodeFolder && hasMetaFolder) + { + // verify if this folder is a valid title + TitleInfo* titleInfo = new TitleInfo(path); + if (titleInfo->IsValid()) + AddDiscoveredTitle(titleInfo); + else + delete titleInfo; + // if there are other folders besides content/code/meta then traverse those + if (dirsInDirectory.size() > 3) + { + for (auto& it : dirsInDirectory) + { + std::string dirName = _utf8Wrapper(it.filename()); + if (!boost::iequals(dirName, "content") && + !boost::iequals(dirName, "code") && + !boost::iequals(dirName, "meta")) + ScanGamePath(it); + } + } + } + else + { + // scan subdirectories + for (auto& it : dirsInDirectory) + ScanGamePath(it); + } +} + +void CafeTitleList::ScanMLCPath(const fs::path& path) +{ + std::error_code ec; + for (auto& it : fs::directory_iterator(path, ec)) + { + if (!it.is_directory()) + continue; + // only scan directories which match the title id naming scheme + std::string dirName = _utf8Wrapper(it.path().filename()); + if(dirName.size() != 8) + continue; + bool containsNoHexCharacter = false; + for (auto& it : dirName) + { + if(it >= 'A' && it <= 'F' || + it >= 'a' && it <= 'f' || + it >= '0' && it <= '9') + continue; + containsNoHexCharacter = true; + break; + } + if(containsNoHexCharacter) + continue; + + if (fs::is_directory(it.path() / "code", ec) && + fs::is_directory(it.path() / "content", ec) && + fs::is_directory(it.path() / "meta", ec)) + { + TitleInfo* titleInfo = new TitleInfo(it); + if (titleInfo->IsValid() && titleInfo->ParseXmlInfo()) + AddDiscoveredTitle(titleInfo); + else + delete titleInfo; + } + } +} + +void CafeTitleList::AddDiscoveredTitle(TitleInfo* titleInfo) +{ + cemu_assert_debug(titleInfo->ParseXmlInfo()); + std::unique_lock _lock(sTLMutex); + // remove from pending list + auto pendingIt = std::find_if(sTLListPending.begin(), sTLListPending.end(), [titleInfo](const TitleInfo* it) { return it->IsEqualByLocation(*titleInfo); }); + if (pendingIt != sTLListPending.end()) + sTLListPending.erase(pendingIt); + AddTitle(titleInfo); +} + +void CafeTitleList::AddTitle(TitleInfo* titleInfo) +{ + // check if title is already known + if (titleInfo->IsCached()) + { + bool isKnown = std::any_of(sTLList.cbegin(), sTLList.cend(), [&titleInfo](const TitleInfo* ti) { return titleInfo->IsEqualByLocation(*ti); }); + if (isKnown) + { + delete titleInfo; + return; + } + } + else + { + auto it = std::find_if(sTLList.begin(), sTLList.end(), [titleInfo](const TitleInfo* it) { return it->IsEqualByLocation(*titleInfo); }); + if (it != sTLList.end()) + { + if ((*it)->IsCached()) + { + // replace cached entry with newly parsed title + TitleInfo* deletedInfo = *it; + sTLList.erase(it); + _RemoveTitleFromMultimap(deletedInfo); + delete deletedInfo; + } + else + { + // title already known + delete titleInfo; + return; + } + } + } + sTLList.emplace_back(titleInfo); + sTLMap.insert(std::pair(titleInfo->GetAppTitleId(), titleInfo)); + // send out notification + CafeTitleListCallbackEvent evt; + evt.eventType = CafeTitleListCallbackEvent::TYPE::TITLE_DISCOVERED; + evt.titleInfo = titleInfo; + for (auto& it : sTLCallbackList) + it.cb(&evt, it.ctx); + sTLCacheDirty = true; +} + +uint64 CafeTitleList::RegisterCallback(void(*cb)(CafeTitleListCallbackEvent* evt, void* ctx), void* ctx) +{ + static std::atomic<uint64_t> sCallbackIdGen = 1; + uint64 id = sCallbackIdGen.fetch_add(1); + std::unique_lock _lock(sTLMutex); + sTLCallbackList.emplace_back(cb, ctx, id); + // immediately notify of all known titles + for (auto& it : sTLList) + { + CafeTitleListCallbackEvent evt; + evt.eventType = CafeTitleListCallbackEvent::TYPE::TITLE_DISCOVERED; + evt.titleInfo = it; + cb(&evt, ctx); + } + // if not scanning then send out scan finished notification + if (!sTLRefreshWorkerActive) + { + CafeTitleListCallbackEvent evt; + evt.eventType = CafeTitleListCallbackEvent::TYPE::SCAN_FINISHED; + evt.titleInfo = nullptr; + for (auto& it : sTLCallbackList) + it.cb(&evt, it.ctx); + } + return id; +} + +void CafeTitleList::UnregisterCallback(uint64 id) +{ + std::unique_lock _lock(sTLMutex); + auto it = std::find_if(sTLCallbackList.begin(), sTLCallbackList.end(), [id](auto& e) { return e.uniqueId == id; }); + cemu_assert(it != sTLCallbackList.end()); // must be a valid callback + sTLCallbackList.erase(it); +} + +bool CafeTitleList::HasTitle(TitleId titleId, uint16& versionOut) +{ + // todo - optimize? + bool matchFound = false; + versionOut = 0; + std::unique_lock _lock(sTLMutex); + for (auto& it : sTLList) + { + if (it->GetAppTitleId() == titleId) + { + uint16 titleVersion = it->GetAppTitleVersion(); + if (titleVersion > versionOut) + versionOut = titleVersion; + matchFound = true; + } + } + return matchFound; +} + +bool CafeTitleList::HasTitleAndVersion(TitleId titleId, uint16 version) +{ + std::unique_lock _lock(sTLMutex); + for (auto& it : sTLList) + { + if (it->GetAppTitleId() == titleId && it->GetAppTitleVersion() == version) + return true; + } + return false; +} + +std::vector<TitleId> CafeTitleList::GetAllTitleIds() +{ + std::unordered_set<TitleId> visitedTitleIds; + std::unique_lock _lock(sTLMutex); + std::vector<TitleId> titleIds; + titleIds.reserve(sTLList.size()); + for (auto& it : sTLList) + { + TitleId tid = it->GetAppTitleId(); + if (visitedTitleIds.find(tid) != visitedTitleIds.end()) + continue; + titleIds.emplace_back(tid); + visitedTitleIds.emplace(tid); + } + return titleIds; +} + +std::span<TitleInfo*> CafeTitleList::AcquireInternalList() +{ + sTLMutex.lock(); + return { sTLList.data(), sTLList.size() }; +} + +void CafeTitleList::ReleaseInternalList() +{ + sTLMutex.unlock(); +} + +bool CafeTitleList::GetFirstByTitleId(TitleId titleId, TitleInfo& titleInfoOut) +{ + std::unique_lock _lock(sTLMutex); + auto it = sTLMap.find(titleId); + if (it != sTLMap.end()) + { + cemu_assert_debug(it->first == titleId); + titleInfoOut = *it->second; + return true; + } + return false; +} + +// takes update or AOC title id and returns the title id of the associated base title +// this can fail if trying to translate an AOC title id without having the base title meta information +bool CafeTitleList::FindBaseTitleId(TitleId titleId, TitleId& titleIdBaseOut) +{ + titleId = TitleIdParser::MakeBaseTitleId(titleId); + + // aoc to base + // todo - this requires scanning all base titles and their updates to see if they reference this title id + // for now we assume there is a direct match of ids + if (((titleId >> 32) & 0xFF) == 0x0C) + { + titleId &= ~0xFF00000000; + titleId |= 0x0000000000; + } + titleIdBaseOut = titleId; + return true; +} + +GameInfo2 CafeTitleList::GetGameInfo(TitleId titleId) +{ + GameInfo2 gameInfo; + + // find base title id + uint64 baseTitleId; + if (!FindBaseTitleId(titleId, baseTitleId)) + { + cemuLog_logDebug(LogType::Force, "Failed to translate title id in GetGameInfo()"); + return gameInfo; + } + // determine if an optional update title id exists + TitleIdParser tip(baseTitleId); + bool hasSeparateUpdateTitleId = tip.CanHaveSeparateUpdateTitleId(); + uint64 updateTitleId = 0; + if (hasSeparateUpdateTitleId) + updateTitleId = tip.GetSeparateUpdateTitleId(); + // scan the title list for base and update + std::unique_lock _lock(sTLMutex); + for (auto& it : sTLList) + { + TitleId appTitleId = it->GetAppTitleId(); + if (appTitleId == baseTitleId) + gameInfo.SetBase(*it); + if (hasSeparateUpdateTitleId && appTitleId == updateTitleId) + { + gameInfo.SetUpdate(*it); + } + } + // if this title can have AOC content then do a second scan + // todo - get a list of all AOC title ids from the base/update meta information + // for now we assume there is a direct match between the base titleId and the aoc titleId + if (tip.CanHaveSeparateUpdateTitleId()) + { + uint64 aocTitleId = baseTitleId | 0xC00000000; + for (auto& it : sTLList) + { + TitleId appTitleId = it->GetAppTitleId(); + if (appTitleId == aocTitleId) + { + gameInfo.AddAOC(*it); // stores the AOC with the highest title version + } + } + } + return gameInfo; +} + +TitleInfo CafeTitleList::GetTitleInfoByUID(uint64 uid) +{ + TitleInfo titleInfo; + std::unique_lock _lock(sTLMutex); + for (auto& it : sTLList) + { + if (it->GetUID() == uid) + { + titleInfo = *it; + break; + } + } + return titleInfo; +} \ No newline at end of file diff --git a/src/Cafe/TitleList/TitleList.h b/src/Cafe/TitleList/TitleList.h new file mode 100644 index 00000000..ff29039e --- /dev/null +++ b/src/Cafe/TitleList/TitleList.h @@ -0,0 +1,57 @@ +#pragma once + +#include "TitleInfo.h" +#include "GameInfo.h" + +struct CafeTitleListCallbackEvent +{ + enum class TYPE + { + TITLE_DISCOVERED, + TITLE_REMOVED, + SCAN_FINISHED, + }; + TYPE eventType; + TitleInfo* titleInfo; +}; + +class CafeTitleList +{ +public: + + static void Initialize(const fs::path cacheXmlFile); + static void LoadCacheFile(); + static void StoreCacheFile(); + + static void ClearScanPaths(); + static void AddScanPath(fs::path path); + static void SetMLCPath(fs::path path); + static void Refresh(); // scan all paths + static bool IsScanning(); // returns true if async refresh is currently active + static void WaitForMandatoryScan(); // wait for current scan result if no cached info is available + static void AddTitleFromPath(fs::path path); + + static uint64 RegisterCallback(void(*cb)(CafeTitleListCallbackEvent* evt, void* ctx), void* ctx); // on register, the callback will be invoked for every already known title + static void UnregisterCallback(uint64 id); + + // utility functions + static bool HasTitle(TitleId titleId, uint16& versionOut); + static bool HasTitleAndVersion(TitleId titleId, uint16 version); + static std::vector<TitleId> GetAllTitleIds(); + static bool GetFirstByTitleId(TitleId titleId, TitleInfo& titleInfoOut); + static bool FindBaseTitleId(TitleId titleId, TitleId& titleIdBaseOut); + + static std::span<TitleInfo*> AcquireInternalList(); + static void ReleaseInternalList(); + + static GameInfo2 GetGameInfo(TitleId titleId); + static TitleInfo GetTitleInfoByUID(uint64 uid); + +private: + static bool RefreshWorkerThread(); + static void ScanGamePath(const fs::path& path); + static void ScanMLCPath(const fs::path& path); + + static void AddDiscoveredTitle(TitleInfo* titleInfo); + static void AddTitle(TitleInfo* titleInfo); +}; \ No newline at end of file diff --git a/src/Cemu/CMakeLists.txt b/src/Cemu/CMakeLists.txt new file mode 100644 index 00000000..8c9136ff --- /dev/null +++ b/src/Cemu/CMakeLists.txt @@ -0,0 +1,15 @@ +project(CemuComponents) + +file(GLOB_RECURSE CPP_FILES *.cpp) +file(GLOB_RECURSE H_FILES *.h) +add_library(CemuComponents ${CPP_FILES} ${H_FILES}) + +set_property(TARGET CemuComponents PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>") + +target_precompile_headers(CemuComponents PRIVATE ../Common/precompiled.h) + +target_include_directories(CemuComponents PRIVATE ../) + +if(ENABLE_DISCORD_RPC) +target_link_libraries(CemuComponents PRIVATE discord-rpc) +endif() diff --git a/src/Cemu/DiscordPresence/DiscordPresence.cpp b/src/Cemu/DiscordPresence/DiscordPresence.cpp new file mode 100644 index 00000000..61734614 --- /dev/null +++ b/src/Cemu/DiscordPresence/DiscordPresence.cpp @@ -0,0 +1,56 @@ +#include "DiscordPresence.h" + +#ifdef ENABLE_DISCORD_RPC + +#include <discord_rpc.h> +#include "Common/version.h" + +DiscordPresence::DiscordPresence() +{ + DiscordEventHandlers handlers{}; + Discord_Initialize("460807638964371468", &handlers, 1, nullptr); + UpdatePresence(Idling); +} + +DiscordPresence::~DiscordPresence() +{ + ClearPresence(); + Discord_Shutdown(); +} + +void DiscordPresence::UpdatePresence(State state, const std::string& text) const +{ + DiscordRichPresence discord_presence{}; + + std::string state_string, details_string; + switch (state) + { + case Idling: + details_string = "Idling"; + break; + case Playing: + details_string = "Ingame"; + state_string = "Playing " + text; + break; + default: + assert(false); + break; + } + + char image_text[128]; + sprintf(image_text, EMULATOR_NAME" %d.%d%s", EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_SUFFIX); + + discord_presence.details = details_string.c_str(); + discord_presence.state = state_string.c_str(); + discord_presence.startTimestamp = time(nullptr); + discord_presence.largeImageText = image_text; + discord_presence.largeImageKey = "logo_icon_big_png"; + Discord_UpdatePresence(&discord_presence); +} + +void DiscordPresence::ClearPresence() const +{ + Discord_ClearPresence(); +} + +#endif \ No newline at end of file diff --git a/src/Cemu/DiscordPresence/DiscordPresence.h b/src/Cemu/DiscordPresence/DiscordPresence.h new file mode 100644 index 00000000..f2491f4b --- /dev/null +++ b/src/Cemu/DiscordPresence/DiscordPresence.h @@ -0,0 +1,21 @@ +#pragma once + +#ifdef ENABLE_DISCORD_RPC + +class DiscordPresence +{ +public: + enum State + { + Idling, + Playing, + }; + + DiscordPresence(); + ~DiscordPresence(); + + void UpdatePresence(State state, const std::string& text = {}) const; + void ClearPresence() const; +}; + +#endif diff --git a/src/Cemu/ExpressionParser/ExpressionParser.cpp b/src/Cemu/ExpressionParser/ExpressionParser.cpp new file mode 100644 index 00000000..74729339 --- /dev/null +++ b/src/Cemu/ExpressionParser/ExpressionParser.cpp @@ -0,0 +1,91 @@ +#include "ExpressionParser.h" + +template<typename T, typename TInternalType = double> +T _testEvaluateToType(const char* expr) +{ + TExpressionParser<TInternalType> ep; + T result = (T)ep.Evaluate(expr); + return result; +} + +void ExpressionParser_test() +{ + ExpressionParser ep; + cemu_assert_debug(_testEvaluateToType<sint64>("0x100005E0") == 0x100005E0); + cemu_assert_debug(_testEvaluateToType<uint64>("0x10+0x20+0x40") == 0x70); + cemu_assert_debug(_testEvaluateToType<sint64>("0+-3") == -3); + cemu_assert_debug(_testEvaluateToType<sint64>("0x0-3") == -3); + cemu_assert_debug(_testEvaluateToType<sint64>("01C+0x10") == 0x2C); + cemu_assert_debug(_testEvaluateToType<sint64>("+0x10") == 0x10); + cemu_assert_debug(_testEvaluateToType<sint64>("01C++0x10") == 0x2C); + cemu_assert_debug(_testEvaluateToType<sint64>("01C+(+0x10)") == 0x2C); + + // arithmetic + cemu_assert_debug(_testEvaluateToType<sint64>("(62156250 / 1000) * 30") == 1864687); // truncated 1864687.5 + + // arithmetic with internal type sint64 + cemu_assert_debug(_testEvaluateToType<sint64, sint64>("(62156250 / 1000) * 30") == 1864680); + + // comparison operators + cemu_assert_debug(_testEvaluateToType<sint64>("5 > 3") == 0x1); + cemu_assert_debug(_testEvaluateToType<sint64>("5 < -10") == 0x0); + cemu_assert_debug(_testEvaluateToType<float>("5 > 3") == 1.0f); + cemu_assert_debug(_testEvaluateToType<float>("-5 > 3") == 0.0f); + cemu_assert_debug(_testEvaluateToType<float>("5 < 10") == 1.0f); + cemu_assert_debug(_testEvaluateToType<float>("5 <= 5") == 1.0f); + cemu_assert_debug(_testEvaluateToType<float>("5 <= 4.999") == 0.0f); + cemu_assert_debug(_testEvaluateToType<float>("5 <= (4+0.999)") == 0.0f); + cemu_assert_debug(_testEvaluateToType<float>("5 >= 3") == 1.0f); + cemu_assert_debug(_testEvaluateToType<float>("5 >= 10") == 0.0f); + cemu_assert_debug(_testEvaluateToType<float>("10 >= 5") == 1.0f); + + // complex operations + cemu_assert_debug(_testEvaluateToType<float>("5 > 4 > 3 > 2") == 0.0f); // this should evaluate the operations from left to right, (5 > 4) -> 0.0, (0.0 > 4) -> 0.0, (0.0 > 3) -> 0.0, (0.0 > 2) -> 0.0 + cemu_assert_debug(_testEvaluateToType<float>("5 > 4 > 3 > -2") == 1.0f); // this should evaluate the operations from left to right, (5 > 4) -> 0.0, (0.0 > 4) -> 0.0, (0.0 > 3) -> 0.0, (0.0 > -2) -> 1.0 + cemu_assert_debug(_testEvaluateToType<float>("(5 == 5) > (5 == 6)") == 1.0f); +} + +// Cemuhook exports + +DLLEXPORT ExpressionParser* ExpressionParser_Create() +{ + return new ExpressionParser(); +} + +DLLEXPORT ExpressionParser* ExpressionParser_CreateCopy(ExpressionParser* ep) +{ + return new ExpressionParser((ExpressionParser&)*ep); +} + +DLLEXPORT void ExpressionParser_Delete(ExpressionParser* ep) +{ + delete ep; +} + +DLLEXPORT void ExpressionParser_AddConstantDouble(ExpressionParser* ep, const char* name, double value) +{ + ep->AddConstant(name, value); +} + +DLLEXPORT void ExpressionParser_AddConstantString(ExpressionParser* ep, const char* name, const char* value) +{ + ep->AddConstant(name, value); +} + +DLLEXPORT bool ExpressionParser_EvaluateToDouble(ExpressionParser* ep, const char* expression, double* result) +{ + try + { + const double temp = ep->Evaluate(std::string(expression)); + if (result) + *result = temp; + + return true; + } + catch (const std::exception& ex) + { + if( result ) + forceLog_printf("Unable to evaluate expression: %s", ex.what()); + return false; + } +} \ No newline at end of file diff --git a/src/Cemu/ExpressionParser/ExpressionParser.h b/src/Cemu/ExpressionParser/ExpressionParser.h new file mode 100644 index 00000000..5f7bf406 --- /dev/null +++ b/src/Cemu/ExpressionParser/ExpressionParser.h @@ -0,0 +1,565 @@ +#pragma once +#include "Common/precompiled.h" + +#include <string> +#include <queue> +#include <stack> +#include <unordered_map> +#include <memory> +#include <charconv> + +#include "boost/functional/hash_fwd.hpp" +#include <fmt/format.h> + +#ifdef __clang__ +#include "Common/linux/fast_float.h" + #define _EP_FROM_CHARS_DBL(...) _convFastFloatResult(fast_float::from_chars(__VA_ARGS__)) + +inline std::from_chars_result _convFastFloatResult(fast_float::from_chars_result r) +{ + std::from_chars_result nr; + nr.ptr = r.ptr; + nr.ec = r.ec; + return nr; +} +#else + #define _EP_FROM_CHARS_DBL(...) std::from_chars(__VA_ARGS__) +#endif + +template<class TType = double> +class TExpressionParser +{ +public: + static_assert(std::is_arithmetic_v<TType>); + using ConstantCallback_t = TType(*)(std::string_view var_name); + using FunctionCallback_t = TType(*)(std::string_view var_name, TType parameter); + + template<typename T> + T Evaluate(std::string_view expression) const + { + static_assert(std::is_arithmetic<T>::value, "type T must be an arithmetic type"); + return (T)Evaluate(expression); + } + + [[nodiscard]] TType Evaluate(std::string_view expression) const + { + std::queue<std::shared_ptr<TokenBase>> output; + std::stack<std::shared_ptr<TokenBase>> operators; + + if (expression.empty()) + { + throw std::runtime_error(fmt::format("empty expression is not allowed")); + } + + bool last_operator_token = true; + + // tokenize and apply shunting-yard + for (size_t i = 0; i < expression.size(); ) + { + const char c = expression[i]; + + // skip whitespaces + if (isblank(c)) + { + ++i; + continue; + } + + // extract numbers + if (isdigit(c) || (last_operator_token && (c == '-' || c == '+'))) + { + const std::string_view view(expression.data() + i, expression.size() - i); + size_t offset = 0; + auto converted = (TType)ConvertString(view, &offset); + output.emplace(std::make_shared<TokenNumber>(converted)); + i += offset; + + last_operator_token = false; + continue; + } + + // check for variables + if (isalpha(c) || c == '_' || c == '$') + { + size_t j; + for (j = i + 1; j < expression.size(); ++j) + { + const char v = expression[j]; + if (!isalpha(v) && !isdigit(v) && v != '_' && v != '@' && v != '.') + { + break; + } + } + + const size_t len = j - i; + const std::string_view view = expression.substr(i, len); + + // check for function + if (m_function_callback) + { + // todo skip whitespaces + if (j < expression.size() && expression[j] == '(') + { + operators.emplace(std::make_shared<TokenUnaryOperator>(TokenUnaryOperator::kFunction, view)); + operators.emplace(std::make_shared<TokenParenthese>()); + i += len + 1; + + last_operator_token = true; + continue; + } + } + + TType value; + const auto it = m_constants.find(std::string{ view }); + if (it == m_constants.cend()) + { + if (m_constant_callback == nullptr) + throw std::runtime_error(fmt::format("unknown constant found \"{}\" in expression: {}", view, expression)); + + value = m_constant_callback(view); + } + else + value = it->second; + + output.emplace(std::make_shared<TokenNumber>(value)); + i += len; + + last_operator_token = false; + continue; + } + + // parenthese + if (c == '(') + { + operators.emplace(std::make_shared<TokenParenthese>()); + ++i; + + last_operator_token = true; + continue; + } + + if (c == ')') + { + bool match = false; + while (!operators.empty()) + { + const auto op = std::dynamic_pointer_cast<TokenParenthese>(operators.top()); + if (!op) + { + output.emplace(operators.top()); + operators.pop(); + continue; + } + + operators.pop(); + match = true; + break; + } + + if (!match && operators.empty()) + { + throw std::runtime_error(fmt::format("parentheses mismatch: {}", expression)); + } + + ++i; + continue; + } + + // supported operations + std::shared_ptr<TokenOperator> operator_token; + switch (c) + { + case '+': + operator_token = std::make_shared<TokenOperator>(TokenOperator::kAddition, 2); + break; + case '-': + operator_token = std::make_shared<TokenOperator>(TokenOperator::kSubtraction, 2); + break; + case '*': + operator_token = std::make_shared<TokenOperator>(TokenOperator::kMultiplication, 3); + break; + case '/': + operator_token = std::make_shared<TokenOperator>(TokenOperator::kDivision, 3); + break; + case '^': + operator_token = std::make_shared<TokenOperator>(TokenOperator::kPow, 4, true); + break; + case '%': + operator_token = std::make_shared<TokenOperator>(TokenOperator::kModulo, 3); + break; + case '=': + if ((i + 1) < expression.size() && expression[i + 1] == '=') + { + operator_token = std::make_shared<TokenOperator>(TokenOperator::kEqual, 1); + ++i; + } + break; + case '!': + if ((i + 1) < expression.size() && expression[i + 1] == '=') + { + operator_token = std::make_shared<TokenOperator>(TokenOperator::kNotEqual, 1); + ++i; + } + break; + case '<': + if ((i + 1) < expression.size() && expression[i + 1] == '=') + { + operator_token = std::make_shared<TokenOperator>(TokenOperator::kLessOrEqual, 1); + ++i; + } + else + { + operator_token = std::make_shared<TokenOperator>(TokenOperator::kLessThan, 1); + } + break; + case '>': + if ((i + 1) < expression.size() && expression[i + 1] == '=') + { + operator_token = std::make_shared<TokenOperator>(TokenOperator::kGreaterOrEqual, 1); + ++i; + } + else + { + operator_token = std::make_shared<TokenOperator>(TokenOperator::kGreaterThan, 1); + } + break; + } + + if (!operator_token) + throw std::runtime_error(fmt::format("invalid operator found in expression: {}", expression)); + + while (!operators.empty()) + { + const auto op = std::dynamic_pointer_cast<TokenOperator>(operators.top()); + if (op && ((!operator_token->right_ass && operator_token->prec <= op->prec) || (operator_token->right_ass && operator_token->prec < op->prec))) + { + output.emplace(operators.top()); + operators.pop(); + continue; + } + + break; + } + + operators.emplace(operator_token); + ++i; + last_operator_token = true; + } + + while (!operators.empty()) + { + const auto parenthese = std::dynamic_pointer_cast<TokenParenthese>(operators.top()); + if (parenthese) + { + throw std::runtime_error(fmt::format("parentheses mismatch in expression: {}", expression)); + } + + output.push(operators.top()); + operators.pop(); + } + + // evaluate tokens in output queue + std::stack<TType> evaluation; + while (!output.empty()) + { + const auto number = std::dynamic_pointer_cast<TokenNumber>(output.front()); + if (number) + { + evaluation.emplace(number->number); + output.pop(); + continue; + } + + if (const auto unop = std::dynamic_pointer_cast<TokenUnaryOperator>(output.front())) + { + if (evaluation.size() < 1) + throw std::runtime_error("not enough parameters for equation"); + + const auto parameter = evaluation.top(); + evaluation.pop(); + + switch (unop->op) + { + case TokenUnaryOperator::kFunction: + evaluation.emplace(m_function_callback(unop->symbol, parameter)); + break; + default: + throw std::runtime_error("unsupported operation constant"); + } + + output.pop(); + continue; + } + else if (const auto op = std::dynamic_pointer_cast<TokenOperator>(output.front())) + { + if (evaluation.size() < 2) + throw std::runtime_error("not enough parameters for equation"); + + const auto rhs = evaluation.top(); + evaluation.pop(); + + const auto lhs = evaluation.top(); + evaluation.pop(); + + switch (op->op) + { + case TokenOperator::kAddition: + evaluation.emplace(lhs + rhs); + break; + case TokenOperator::kSubtraction: + evaluation.emplace(lhs - rhs); + break; + case TokenOperator::kMultiplication: + evaluation.emplace(lhs * rhs); + break; + case TokenOperator::kDivision: + if (rhs == 0.0) evaluation.emplace((TType)0.0); + else evaluation.emplace(lhs / rhs); + break; + case TokenOperator::kPow: + evaluation.emplace((TType)std::pow(lhs, rhs)); + break; + case TokenOperator::kModulo: + if (std::round(rhs) == 0.0) + evaluation.emplace((TType)0.0); + else + evaluation.emplace((TType)((sint64)std::round(lhs) % (sint64)std::round(rhs))); + break; + case TokenOperator::kEqual: + if constexpr (std::is_floating_point_v<TType>) + evaluation.emplace((TType)(std::abs(lhs - rhs) <= 0.0000000001 ? 1 : 0)); + else + evaluation.emplace(lhs == rhs ? 1 : 0); + break; + case TokenOperator::kNotEqual: + if constexpr (std::is_floating_point_v<TType>) + evaluation.emplace((TType)(std::abs(lhs - rhs) > 0.0000000001 ? 1 : 0)); + else + evaluation.emplace(lhs != rhs ? 1 : 0); + break; + case TokenOperator::kLessThan: + evaluation.emplace((TType)(lhs < rhs ? 1 : 0)); + break; + case TokenOperator::kLessOrEqual: + evaluation.emplace((TType)(lhs <= rhs ? 1 : 0)); + break; + case TokenOperator::kGreaterThan: + evaluation.emplace((TType)(lhs > rhs ? 1 : 0)); + break; + case TokenOperator::kGreaterOrEqual: + evaluation.emplace((TType)(lhs >= rhs ? 1 : 0)); + break; + default: + throw std::runtime_error("unsupported operation constant"); + } + + output.pop(); + continue; + } + } + + return evaluation.top(); + } + + [[nodiscard]] bool IsValidExpression(std::string_view expression) const + { + try + { + TExpressionParser<TType> ep; + ep.AddConstantCallback( + [](std::string_view sv) -> TType + { + return (TType)1; + }); + static_cast<void>(ep.Evaluate(expression)); + return true; + } + catch (...) + { + return false; + } + } + + [[nodiscard]] bool IsConstantExpression(std::string_view expression) const + { + try + { + static_cast<void>(this->Evaluate(expression)); + return true; + } + catch (...) + { + return false; + } + } + + void AddConstant(std::string_view name, TType value) + { + m_constants[std::string(name)] = value; + } + + // only adds constant if not added yet + void TryAddConstant(std::string_view name, TType value) + { + m_constants.try_emplace(std::string{ name }, value); + } + + void AddConstant(std::string_view name, std::string_view value) + { + AddConstant(name, ConvertString(value)); + } + + void AddConstantCallback(ConstantCallback_t callback) + { + m_constant_callback = callback; + } + + void SetFunctionCallback(FunctionCallback_t callback) + { + m_function_callback = callback; + } + +private: + std::unordered_map<std::string, TType> m_constants; + ConstantCallback_t m_constant_callback = nullptr; + FunctionCallback_t m_function_callback = nullptr; + + static bool _isNumberWithDecimalPoint(std::string_view str) + { + return str.find('.') != std::string_view::npos; + } + + double ConvertString(std::string_view str, size_t* index_after = nullptr) const + { + const char* strInitial = str.data(); + if (str.empty()) + throw std::runtime_error("can't parse empty number"); + + double result; + std::from_chars_result chars_result; + + const bool is_negative = str[0] == '-'; + if (is_negative || str[0] == '+') + str = str.substr(1); + + if (str.size() >= 3 && str[0] == '0' && tolower(str[1]) == 'x') + { + str = str.substr(2); + // find end of number + const auto end = std::find_if_not(str.cbegin(), str.cend(), isxdigit); + + int64_t tmp; + chars_result = std::from_chars(str.data(), str.data() + std::distance(str.cbegin(), end), tmp, 16); + result = (double)tmp; + } + // a number prefixed with 0 and no decimal point is considered a hex number + else if (str.size() >= 2 && str[0] == '0' && isxdigit(str[1]) && !_isNumberWithDecimalPoint(str)) + { + str = str.substr(1); + // find end of number + const auto end = std::find_if_not(str.cbegin(), str.cend(), isxdigit); + + int64_t tmp; + chars_result = std::from_chars(str.data(), str.data() + std::distance(str.cbegin(), end), tmp, 16); + result = (double)tmp; + } + else + { + if (str[0] == '+') + str = str.substr(1); + + bool has_point = false; + bool has_exponent = false; + const auto end = std::find_if_not(str.cbegin(), str.cend(), + [&has_point, &has_exponent](char c) -> bool + { + if (isdigit(c)) + return true; + + if (c == '.' && !has_point) + { + has_point = true; + return true; + } + + if (tolower(c) == 'e' && !has_exponent) + { + has_exponent = true; + // TODO: after exponent might be + and - allowed? + return true; + } + return false; + }); + chars_result = _EP_FROM_CHARS_DBL(str.data(), str.data() + std::distance(str.cbegin(), end), result); + } + + if (chars_result.ec == std::errc()) + { + if (index_after) + *index_after = chars_result.ptr - strInitial; + + if (is_negative) + result *= -1.0; + + return result; + } + else + throw std::runtime_error(fmt::format("can't parse number: {}", str)); + } + + struct TokenBase + { + virtual ~TokenBase() = default; + }; + + struct TokenUnaryOperator : TokenBase + { + enum Operator + { + kFunction, + }; + + TokenUnaryOperator(Operator op) : op(op) {} + TokenUnaryOperator(Operator op, std::string_view symbol) : op(op), symbol(symbol) {} + + Operator op; + std::string_view symbol; + }; + + struct TokenOperator : TokenBase + { + enum Operator + { + kAddition, + kSubtraction, + kMultiplication, + kDivision, + kPow, + kModulo, + kEqual, + kNotEqual, + kLessThan, + kLessOrEqual, + kGreaterThan, + kGreaterOrEqual, + }; + + TokenOperator(Operator op, int prec, bool right_ass = false) : op(op), prec(prec), right_ass(right_ass) {} + Operator op; + int prec; + bool right_ass; + }; + + struct TokenParenthese : TokenBase {}; + + struct TokenNumber : TokenBase + { + TokenNumber(TType number) : number(number) {} + TType number; + }; +}; + +class ExpressionParser : public TExpressionParser<double> {}; + +void ExpressionParser_test(); + diff --git a/src/Cemu/FileCache/FileCache.cpp b/src/Cemu/FileCache/FileCache.cpp new file mode 100644 index 00000000..8979e416 --- /dev/null +++ b/src/Cemu/FileCache/FileCache.cpp @@ -0,0 +1,657 @@ +#include "FileCache.h" +#include "util/helpers/helpers.h" + +#include <mutex> +#include <condition_variable> +#include "zlib.h" +#include "Common/filestream.h" + +struct FileCacheAsyncJob +{ + FileCache* fileCache; + uint64 name1; + uint64 name2; + std::vector<uint8> fileData; +}; + +struct _FileCacheAsyncWriter +{ + _FileCacheAsyncWriter() + { + m_isRunning.store(true); + m_fileCacheThread = std::thread(&_FileCacheAsyncWriter::FileCacheThread, this); + } + + ~_FileCacheAsyncWriter() + { + if (m_isRunning.load()) + { + m_isRunning.store(false); + m_fileCacheCondVar.notify_one(); + m_fileCacheThread.join(); + } + } + + void AddJob(FileCache* fileCache, const FileCache::FileName& name, const uint8* fileData, sint32 fileSize) + { + FileCacheAsyncJob async; + async.fileCache = fileCache; + async.name1 = name.name1; + async.name2 = name.name2; + async.fileData = { fileData, fileData + fileSize }; + + std::unique_lock lock(m_fileCacheMutex); + m_writeRequests.emplace_back(std::move(async)); + + lock.unlock(); + m_fileCacheCondVar.notify_one(); + } + +private: + void FileCacheThread() + { + SetThreadName("fileCache_thread"); + while (true) + { + std::unique_lock lock(m_fileCacheMutex); + while (m_writeRequests.empty()) + { + m_fileCacheCondVar.wait(lock); + if (!m_isRunning.load(std::memory_order::relaxed)) + return; + } + + std::vector<FileCacheAsyncJob> requestsCopy; + requestsCopy.swap(m_writeRequests); // fast copy & clear + lock.unlock(); + + for (const auto& entry : requestsCopy) + { + entry.fileCache->AddFile({ entry.name1, entry.name2 }, entry.fileData.data(), (sint32)entry.fileData.size()); + } + } + } + + std::thread m_fileCacheThread; + std::mutex m_fileCacheMutex; + std::condition_variable m_fileCacheCondVar; + std::vector<FileCacheAsyncJob> m_writeRequests; + std::atomic_bool m_isRunning; +}FileCacheAsyncWriter; + +#define FILECACHE_MAGIC_V1 0x8371b694 // used prior to Cemu 1.7.4, only supported caches up to 4GB +#define FILECACHE_MAGIC_V2 0x8371b695 // added support for large caches +#define FILECACHE_MAGIC_V3 0x8371b696 // introduced in Cemu 1.16.0 (non-WIP). Adds zlib compression +#define FILECACHE_HEADER_RESV 128 // number of bytes reserved for the header +#define FILECACHE_FILETABLE_NAME1 0xEFEFEFEFEFEFEFEFULL +#define FILECACHE_FILETABLE_NAME2 0xFEFEFEFEFEFEFEFEULL +#define FILECACHE_FILETABLE_FREE_NAME 0ULL + +FileCache* FileCache::Create(wzstring_view path, uint32 extraVersion) +{ + FileStream* fs = FileStream::createFile(std::wstring(path).c_str()); + if (!fs) + { + forceLog_printf("Failed to create cache file \"%ls\"", path); + return nullptr; + } + // init file cache + auto* fileCache = new FileCache(); + fileCache->fileStream = fs; + fileCache->dataOffset = FILECACHE_HEADER_RESV; + fileCache->fileTableEntryCount = 32; + fileCache->fileTableOffset = 0; + fileCache->fileTableSize = sizeof(FileTableEntry) * fileCache->fileTableEntryCount; + fileCache->fileTableEntries = (FileTableEntry*)malloc(fileCache->fileTableSize); + fileCache->extraVersion = extraVersion; + memset(fileCache->fileTableEntries, 0, fileCache->fileTableSize); + // file table stores info about itself + fileCache->fileTableEntries[0].name1 = FILECACHE_FILETABLE_NAME1; + fileCache->fileTableEntries[0].name2 = FILECACHE_FILETABLE_NAME2; + fileCache->fileTableEntries[0].fileOffset = fileCache->fileTableOffset; + fileCache->fileTableEntries[0].fileSize = fileCache->fileTableSize; + // write header + + fs->writeU32(FILECACHE_MAGIC_V3); + fs->writeU32(fileCache->extraVersion); + fs->writeU64(fileCache->dataOffset); + fs->writeU64(fileCache->fileTableOffset); + fs->writeU32(fileCache->fileTableSize); + // write file table + fs->SetPosition(fileCache->dataOffset+fileCache->fileTableOffset); + fs->writeData(fileCache->fileTableEntries, fileCache->fileTableSize); + // done + return fileCache; +} + +FileCache* FileCache::_OpenExisting(wzstring_view path, bool compareExtraVersion, uint32 extraVersion) +{ + FileStream* fs = FileStream::openFile2(path.data(), true); + if (!fs) + return nullptr; + // read header + uint32 headerMagic = 0; + fs->readU32(headerMagic); + bool isV2 = false; + if (headerMagic != FILECACHE_MAGIC_V1 && headerMagic != FILECACHE_MAGIC_V2 && headerMagic != FILECACHE_MAGIC_V3) + { + delete fs; + return nullptr; + } + if (headerMagic == FILECACHE_MAGIC_V1) + { + // support for V1 file format removed with the addition of V3 + delete fs; + return nullptr; + } + if (headerMagic == FILECACHE_MAGIC_V2) + isV2 = true; + uint32 headerExtraVersion = 0xFFFFFFFF; + fs->readU32(headerExtraVersion); + if (compareExtraVersion && headerExtraVersion != extraVersion) + { + delete fs; + return nullptr; + } + if (!compareExtraVersion) + { + extraVersion = headerExtraVersion; + } + + uint64 headerDataOffset = 0; + uint64 headerFileTableOffset = 0; + uint32 headerFileTableSize = 0; + fs->readU64(headerDataOffset); + fs->readU64(headerFileTableOffset); + if (!fs->readU32(headerFileTableSize)) + { + forceLog_printf("\"%ls\" is corrupted", path); + delete fs; + return nullptr; + } + uint32 fileTableEntryCount = 0; + bool invalidFileTableSize = false; + // V2 and V3 + fileTableEntryCount = headerFileTableSize / sizeof(FileTableEntry); + invalidFileTableSize = (headerFileTableSize % sizeof(FileTableEntry)) != 0; + if (invalidFileTableSize) + { + forceLog_printf("\"%ls\" is corrupted", path); + delete fs; + return nullptr; + } + // init struct + auto* fileCache = new FileCache(); + fileCache->fileStream = fs; + fileCache->extraVersion = extraVersion; + fileCache->dataOffset = headerDataOffset; + fileCache->fileTableEntryCount = fileTableEntryCount; + fileCache->fileTableOffset = headerFileTableOffset; + fileCache->fileTableSize = fileTableEntryCount * sizeof(FileTableEntry); + fileCache->fileTableEntries = (FileTableEntry*)malloc(fileTableEntryCount * sizeof(FileTableEntry)); + memset(fileCache->fileTableEntries, 0, fileTableEntryCount * sizeof(FileTableEntry)); + // read file table + fileCache->fileStream->SetPosition(fileCache->dataOffset + fileCache->fileTableOffset); + bool incompleteFileTable = false; + if (isV2) + { + // read file table entries in old format + incompleteFileTable = fileCache->fileStream->readData(fileCache->fileTableEntries, fileCache->fileTableSize) != fileCache->fileTableSize; + // in V2 the extra field wasn't guaranteed to have well defined values + for (uint32 i = 0; i < fileTableEntryCount; i++) + { + fileCache->fileTableEntries[i].flags = FileTableEntry::FLAGS::FLAG_NONE; + fileCache->fileTableEntries[i].extraReserved1 = 0; + fileCache->fileTableEntries[i].extraReserved2 = 0; + fileCache->fileTableEntries[i].extraReserved3 = 0; + } + } + else + { + incompleteFileTable = fileCache->fileStream->readData(fileCache->fileTableEntries, fileCache->fileTableSize) != fileCache->fileTableSize; + } + if (incompleteFileTable) + { + forceLog_printf("\"%ls\" is corrupted (incomplete file table)", path); + delete fileCache; + return nullptr; + } + return fileCache; +} + +FileCache* FileCache::Open(wzstring_view path, bool allowCreate, uint32 extraVersion) +{ + FileCache* fileCache = _OpenExisting(path, true, extraVersion); + if (fileCache) + return fileCache; + if (!allowCreate) + return nullptr; + return Create(path, extraVersion); +} + +FileCache* FileCache::Open(wzstring_view path) +{ + return _OpenExisting(path, false, 0); +} + +FileCache::~FileCache() +{ + free(this->fileTableEntries); + delete fileStream; +} + +void FileCache::fileCache_updateFiletable(sint32 extraEntriesToAllocate) +{ + // recreate file table with bigger size (optional) + this->fileTableEntries[0].name1 = FILECACHE_FILETABLE_FREE_NAME; + this->fileTableEntries[0].name2 = FILECACHE_FILETABLE_FREE_NAME; + sint32 newFileTableEntryCount = this->fileTableEntryCount + extraEntriesToAllocate; + this->fileTableEntries = (FileTableEntry*)realloc(this->fileTableEntries, sizeof(FileTableEntry)*newFileTableEntryCount); + for (sint32 f = this->fileTableEntryCount; f < newFileTableEntryCount; f++) + { + this->fileTableEntries[f].name1 = FILECACHE_FILETABLE_FREE_NAME; + this->fileTableEntries[f].name2 = FILECACHE_FILETABLE_FREE_NAME; + this->fileTableEntries[f].fileOffset = 0; + this->fileTableEntries[f].fileSize = 0; + this->fileTableEntries[f].flags = FileTableEntry::FLAGS::FLAG_NONE; + this->fileTableEntries[f].extraReserved1 = 0; + this->fileTableEntries[f].extraReserved2 = 0; + this->fileTableEntries[f].extraReserved3 = 0; + } + this->fileTableEntryCount = newFileTableEntryCount; + this->_addFileInternal(FILECACHE_FILETABLE_NAME1, FILECACHE_FILETABLE_NAME2, (uint8*)this->fileTableEntries, sizeof(FileTableEntry)*newFileTableEntryCount, true); + // update file table info in struct + if (this->fileTableEntries[0].name1 != FILECACHE_FILETABLE_NAME1 || this->fileTableEntries[0].name2 != FILECACHE_FILETABLE_NAME2) + { + forceLog_printf("Corruption in cache file detected"); + assert_dbg(); + } + this->fileTableOffset = this->fileTableEntries[0].fileOffset; + this->fileTableSize = this->fileTableEntries[0].fileSize; + // update header + fileStream->SetPosition(0); + fileStream->writeU32(FILECACHE_MAGIC_V3); + fileStream->writeU32(this->extraVersion); + fileStream->writeU64(this->dataOffset); + fileStream->writeU64(this->fileTableOffset); + fileStream->writeU32(this->fileTableSize); +} + +uint8* _fileCache_compressFileData(const uint8* fileData, uint32 fileSize, sint32& compressedSize) +{ + // compress data using zlib deflate + // stores the size of the uncompressed file in the first 4 bytes + Bytef* uncompressedInput = (Bytef*)fileData; + uLongf uncompressedLen = fileSize; + uLongf compressedLen = compressBound(fileSize); + Bytef* compressedData = (Bytef*)malloc(4 + compressedLen); + int zret = compress2(compressedData + 4, &compressedLen, uncompressedInput, uncompressedLen, 4); // level 4 has good compression to performance ratio + if (zret != Z_OK) + return nullptr; + compressedData[0] = ((uint32)fileSize >> 24) & 0xFF; + compressedData[1] = ((uint32)fileSize >> 16) & 0xFF; + compressedData[2] = ((uint32)fileSize >> 8) & 0xFF; + compressedData[3] = ((uint32)fileSize >> 0) & 0xFF; + compressedSize = 4 + compressedLen; + return compressedData; +} + +bool _uncompressFileData(const uint8* rawData, size_t rawSize, std::vector<uint8>& dataOut) +{ + if (rawSize < 4) + { + dataOut.clear(); + return false; + } + // get size of uncompressed file + uint32 fileSize = 0; + fileSize |= ((uint32)rawData[0] << 24); + fileSize |= ((uint32)rawData[1] << 16); + fileSize |= ((uint32)rawData[2] << 8); + fileSize |= ((uint32)rawData[3] << 0); + // allocate buffer + Bytef* compressedInput = (Bytef*)rawData + 4; + uLongf compressedLen = (uLongf)(rawSize - 4); + uLongf uncompressedLen = fileSize; + dataOut.resize(fileSize); + int zret = uncompress2(dataOut.data(), &uncompressedLen, compressedInput, &compressedLen); + if (zret != Z_OK) + { + dataOut.clear(); + return false; + } + if (uncompressedLen != fileSize || compressedLen != (rawSize - 4)) + { + // uncompressed size does not match stored size + dataOut.clear(); + return false; + } + return true; +} + +void FileCache::_addFileInternal(uint64 name1, uint64 name2, const uint8* fileData, sint32 fileSize, bool noCompression) +{ + if (fileSize < 0) + return; + if (!enableCompression) + noCompression = true; + // compress data + sint32 rawSize = 0; + uint8* rawData = nullptr; + bool isCompressed = false; + if (noCompression) + { + rawData = (uint8*)fileData; + rawSize = fileSize; + } + else + { + // compress file + rawData = _fileCache_compressFileData(fileData, fileSize, rawSize); + if (rawData) + { + isCompressed = true; + } + else + { + rawData = (uint8*)fileData; + rawSize = fileSize; + } + } + std::unique_lock lock(this->mutex); + // find free entry in file table + sint32 entryIndex = -1; + // scan for already existing entry + for (sint32 i = 0; i < this->fileTableEntryCount; i++) + { + if (this->fileTableEntries[i].name1 == name1 && this->fileTableEntries[i].name2 == name2) + { + entryIndex = i; + break; + } + } + if (entryIndex == -1) + { + while (true) + { + // if no entry exists, search for empty one + for (sint32 i = 0; i < this->fileTableEntryCount; i++) + { + if (this->fileTableEntries[i].name1 == FILECACHE_FILETABLE_FREE_NAME && this->fileTableEntries[i].name2 == FILECACHE_FILETABLE_FREE_NAME) + { + entryIndex = i; + break; + } + } + if (entryIndex == -1) + { + if (name1 == FILECACHE_FILETABLE_NAME1 && name2 == FILECACHE_FILETABLE_NAME2) + { + forceLog_printf("Error in cache file"); + cemu_assert_debug(false); + } + // no free entry, recreate file table with larger size + fileCache_updateFiletable(64); + // try again + continue; + } + else + break; + } + } + // find free space + sint64 currentStartOffset = 0; + while (true) + { + bool hasCollision = false; + sint64 currentEndOffset = currentStartOffset + rawSize; + FileTableEntry* entry = this->fileTableEntries; + FileTableEntry* entryLast = this->fileTableEntries + this->fileTableEntryCount; + while (entry < entryLast) + { + if (entry->name1 == FILECACHE_FILETABLE_FREE_NAME && entry->name2 == FILECACHE_FILETABLE_FREE_NAME) + { + entry++; + continue; + } + if (currentEndOffset >= (sint64)entry->fileOffset && currentStartOffset < (sint64)(entry->fileOffset + entry->fileSize)) + { + currentStartOffset = entry->fileOffset + entry->fileSize; + hasCollision = true; + break; + } + entry++; + } + // optimized logic to speed up scanning for free offsets + // assumes that most of the time entries are stored in direct succession (holds true more often than not) + if (hasCollision && (entry + 1) < entryLast) + { + entry++; + while (entry < entryLast) + { + if (entry->name1 == FILECACHE_FILETABLE_FREE_NAME && entry->name2 == FILECACHE_FILETABLE_FREE_NAME) + { + entry++; + continue; + } + if (entry->fileOffset == currentStartOffset) + { + currentStartOffset = entry->fileOffset + entry->fileSize; + entry++; + continue; + } + break; + } + } + // retry in case of collision + if (hasCollision == false) + break; + } + // update file table entry + this->fileTableEntries[entryIndex].name1 = name1; + this->fileTableEntries[entryIndex].name2 = name2; + this->fileTableEntries[entryIndex].fileOffset = currentStartOffset; + this->fileTableEntries[entryIndex].fileSize = rawSize; + this->fileTableEntries[entryIndex].flags = isCompressed ? FileTableEntry::FLAGS::FLAG_COMPRESSED : FileTableEntry::FLAGS::FLAG_NONE; + this->fileTableEntries[entryIndex].extraReserved1 = 0; + this->fileTableEntries[entryIndex].extraReserved2 = 0; + this->fileTableEntries[entryIndex].extraReserved3 = 0; + // write file data + fileStream->SetPosition(this->dataOffset + currentStartOffset); + fileStream->writeData(rawData, rawSize); + // write file table entry + fileStream->SetPosition(this->dataOffset + this->fileTableOffset + (uint64)(sizeof(FileTableEntry)*entryIndex)); + fileStream->writeData(this->fileTableEntries + entryIndex, sizeof(FileTableEntry)); + if (isCompressed) + free(rawData); +} + +void FileCache::AddFile(const FileName&& name, const uint8* fileData, sint32 fileSize) +{ + this->_addFileInternal(name.name1, name.name2, fileData, fileSize, false); +} + +bool FileCache::DeleteFile(const FileName&& name) +{ + if( name.name1 == FILECACHE_FILETABLE_NAME1 && name.name2 == FILECACHE_FILETABLE_NAME2 ) + return false; // prevent filetable from being deleted + std::unique_lock lock(this->mutex); + FileTableEntry* entry = this->fileTableEntries; + FileTableEntry* entryLast = this->fileTableEntries+this->fileTableEntryCount; + while( entry < entryLast ) + { + if( entry->name1 == name.name1 && entry->name2 == name.name2 ) + { + entry->name1 = FILECACHE_FILETABLE_FREE_NAME; + entry->name2 = FILECACHE_FILETABLE_FREE_NAME; + entry->fileOffset = 0; + entry->fileSize = 0; + // store updated entry to file cache + size_t entryIndex = entry - this->fileTableEntries; + fileStream->SetPosition(this->dataOffset+this->fileTableOffset+(uint64)(sizeof(FileTableEntry)*entryIndex)); + fileStream->writeData(this->fileTableEntries+entryIndex, sizeof(FileTableEntry)); + return true; + } + entry++; + } + return false; +} + +void FileCache::AddFileAsync(const FileName& name, const uint8* fileData, sint32 fileSize) +{ + FileCacheAsyncWriter.AddJob(this, name, fileData, fileSize); +} + +bool FileCache::_getFileDataInternal(const FileTableEntry* entry, std::vector<uint8>& dataOut) +{ + std::vector<uint8> rawData(entry->fileSize); + + fileStream->SetPosition(this->dataOffset + entry->fileOffset); + fileStream->readData(rawData.data(), entry->fileSize); + + if ((entry->flags&FileTableEntry::FLAG_COMPRESSED) == 0) + { + // uncompressed + std::swap(rawData, dataOut); + return true; + } + // decompress + sint32 uncompressedSize = 0; + if (!_uncompressFileData(rawData.data(), rawData.size(), dataOut)) + { + dataOut.clear(); + return false; + } + return true; +} + +bool FileCache::GetFile(const FileName&& name, std::vector<uint8>& dataOut) +{ + std::unique_lock lock(this->mutex); + FileTableEntry* entry = this->fileTableEntries; + FileTableEntry* entryLast = this->fileTableEntries+this->fileTableEntryCount; + while( entry < entryLast ) + { + if( entry->name1 == name.name1 && entry->name2 == name.name2 ) + { + return _getFileDataInternal(entry, dataOut); + } + entry++; + } + dataOut.clear(); + return false; +} + +bool FileCache::GetFileByIndex(sint32 index, uint64* name1, uint64* name2, std::vector<uint8>& dataOut) +{ + if (index < 0 || index >= this->fileTableEntryCount) + return false; + FileTableEntry* entry = this->fileTableEntries + index; + if (this->fileTableEntries == nullptr) + { + forceLog_printf("GetFileByIndex() fileTable is NULL"); + return false; + } + if (entry->name1 == FILECACHE_FILETABLE_FREE_NAME && entry->name2 == FILECACHE_FILETABLE_FREE_NAME) + return false; + if (entry->name1 == FILECACHE_FILETABLE_NAME1 && entry->name2 == FILECACHE_FILETABLE_NAME2) + return false; + + std::unique_lock lock(this->mutex); + if(name1) + *name1 = entry->name1; + if(name2) + *name2 = entry->name2; + return _getFileDataInternal(entry, dataOut); +} + +bool FileCache::HasFile(const FileName&& name) +{ + std::unique_lock lock(this->mutex); + FileTableEntry* entry = this->fileTableEntries; + FileTableEntry* entryLast = this->fileTableEntries + this->fileTableEntryCount; + while (entry < entryLast) + { + if (entry->name1 == name.name1 && entry->name2 == name.name2) + return true; + entry++; + } + return false; +} + +sint32 FileCache::GetMaximumFileIndex() +{ + return this->fileTableEntryCount; +} + +sint32 FileCache::GetFileCount() +{ + std::unique_lock lock(this->mutex); + sint32 fileCount = 0; + FileTableEntry* entry = this->fileTableEntries; + FileTableEntry* entryLast = this->fileTableEntries+this->fileTableEntryCount; + while( entry < entryLast ) + { + if( entry->name1 == FILECACHE_FILETABLE_FREE_NAME && entry->name2 == FILECACHE_FILETABLE_FREE_NAME ) + { + entry++; + continue; + } + if( entry->name1 == FILECACHE_FILETABLE_NAME1 && entry->name2 == FILECACHE_FILETABLE_NAME2 ) + { + entry++; + continue; + } + fileCount++; + entry++; + } + + return fileCount; +} + +void fileCache_test() +{ + FileCache* fc = FileCache::Create(L"testCache.bin", 0); + uint32 time1 = GetTickCount(); + + char* testString1 = (char*)malloc(1024 * 1024 * 8); + char* testString2 = (char*)malloc(1024 * 1024 * 8); + char* testString3 = (char*)malloc(1024 * 1024 * 8); + for (sint32 f = 0; f < 1024 * 1024 * 8; f++) + { + testString1[f] = 'a' + (f & 7); + testString2[f] = 'd' + (f & 3); + testString3[f] = 'f' + (f & 3); + } + + for(sint32 i=0; i<2200; i++) + { + fc->AddFile({ 0x1000001ULL, (uint64)i }, (uint8*)testString1, 1024 * 1024 * 1); + fc->AddFile({ 0x1000002ULL, (uint64)i }, (uint8*)testString2, 1024 * 1024 * 1); + fc->AddFile({ 0x1000003ULL, (uint64)i }, (uint8*)testString3, 1024 * 1024 * 1); + } + uint32 time2 = GetTickCount(); + debug_printf("Writing took %dms\n", time2-time1); + delete fc; + // verify if all entries are still valid + FileCache* fcRead = FileCache::Open(L"testCache.bin", 0); + uint32 time3 = GetTickCount(); + for(sint32 i=0; i<2200; i++) + { + std::vector<uint8> fileData; + bool r = fcRead->GetFile({ 0x1000001ULL, (uint64)i }, fileData); + if (!r || fileData.size() != 1024 * 1024 * 1 || memcmp(fileData.data(), testString1, 1024 * 1024 * 1) != 0) + cemu_assert_debug(false); + r = fcRead->GetFile({ 0x1000002ULL, (uint64)i }, fileData); + if( !r || fileData.size() != 1024 * 1024 * 1 || memcmp(fileData.data(), testString2, 1024 * 1024 * 1) != 0 ) + cemu_assert_debug(false); + r = fcRead->GetFile({ 0x1000003ULL, (uint64)i }, fileData); + if( !r || fileData.size() != 1024 * 1024 * 1 || memcmp(fileData.data(), testString3, 1024 * 1024 * 1) != 0 ) + cemu_assert_debug(false); + } + uint32 time4 = GetTickCount(); + debug_printf("Reading took %dms\n", time4-time3); + delete fcRead; + cemu_assert_debug(false); + exit(0); +} \ No newline at end of file diff --git a/src/Cemu/FileCache/FileCache.h b/src/Cemu/FileCache/FileCache.h new file mode 100644 index 00000000..52972fbd --- /dev/null +++ b/src/Cemu/FileCache/FileCache.h @@ -0,0 +1,97 @@ +#pragma once + +#include "Common/zstring_view.h" +#include <mutex> + +class FileCache +{ +public: + struct FileName + { + FileName(uint64 name1, uint64 name2) : name1(name1), name2(name2) {}; + FileName(std::string_view filePath) + { + // name from string hash + uint64 h1 = 0xa2cc2c49386a75fdull; + uint64 h2 = 0x5182d367734c2ce8ull; + const char* c = filePath.data(); + const char* cEnd = filePath.data() + filePath.size(); + while (c < cEnd) + { + uint64 t = (uint64)*c; + c++; + h1 = (h1 << 7) | (h1 >> (64 - 7)); + h1 += t; + h2 = h2 * 7841u + t; + } + name1 = h1; + name2 = h2; + }; + + FileName(const std::string& filePath) : FileName(std::basic_string_view(filePath.data(), filePath.size())) {}; + + uint64 name1; + uint64 name2; + }; + + ~FileCache(); + + static FileCache* Create(wzstring_view path, uint32 extraVersion = 0); + static FileCache* Open(wzstring_view path, bool allowCreate, uint32 extraVersion = 0); + static FileCache* Open(wzstring_view path); // open without extraVersion check + + void UseCompression(bool enable) { enableCompression = enable; }; + + void AddFile(const FileName&& name, const uint8* fileData, sint32 fileSize); + void AddFileAsync(const FileName& name, const uint8* fileData, sint32 fileSize); + bool DeleteFile(const FileName&& name); + bool GetFile(const FileName&& name, std::vector<uint8>& dataOut); + bool GetFileByIndex(sint32 index, uint64* name1, uint64* name2, std::vector<uint8>& dataOut); + bool HasFile(const FileName&& name); + + sint32 GetFileCount(); + + sint32 GetMaximumFileIndex(); + +private: + struct FileTableEntry + { + enum FLAGS : uint8 + { + FLAG_NONE = 0x00, + FLAG_COMPRESSED = (1 << 0), // zLib compressed + }; + uint64 name1; + uint64 name2; + uint64 fileOffset; + uint32 fileSize; + FLAGS flags; + uint8 extraReserved1; + uint8 extraReserved2; + uint8 extraReserved3; + }; + + static_assert(sizeof(FileTableEntry) == 0x20); + + FileCache() {}; + + static FileCache* _OpenExisting(wzstring_view path, bool compareExtraVersion, uint32 extraVersion = 0); + + void fileCache_updateFiletable(sint32 extraEntriesToAllocate); + void _addFileInternal(uint64 name1, uint64 name2, const uint8* fileData, sint32 fileSize, bool noCompression); + bool _getFileDataInternal(const FileTableEntry* entry, std::vector<uint8>& dataOut); + + class FileStream* fileStream{}; + uint64 dataOffset{}; + uint32 extraVersion{}; + // file table + FileTableEntry* fileTableEntries{}; + sint32 fileTableEntryCount{}; + // file table (as stored in file) + uint64 fileTableOffset{}; + uint32 fileTableSize{}; + // options + bool enableCompression{true}; + + std::recursive_mutex mutex; +}; diff --git a/src/Cemu/Logging/CemuDebugLogging.h b/src/Cemu/Logging/CemuDebugLogging.h new file mode 100644 index 00000000..700ce23c --- /dev/null +++ b/src/Cemu/Logging/CemuDebugLogging.h @@ -0,0 +1,13 @@ +#pragma once + +// printf-style macros that are only active in non-release builds + +#ifdef PUBLIC_RELEASE +#define debug_printf // +#define debug_puts // +static void debugBreakpoint() { } +#else +#define debug_printf printf +#define debug_puts puts +static void debugBreakpoint() {} +#endif \ No newline at end of file diff --git a/src/Cemu/Logging/CemuLogging.cpp b/src/Cemu/Logging/CemuLogging.cpp new file mode 100644 index 00000000..c22ad30e --- /dev/null +++ b/src/Cemu/Logging/CemuLogging.cpp @@ -0,0 +1,274 @@ +#include "CemuLogging.h" +#include "config/CemuConfig.h" +#include "gui/LoggingWindow.h" +#include "config/ActiveSettings.h" +#include "util/helpers/helpers.h" + +#include <mutex> +#include <condition_variable> +#include <chrono> + +#include <fmt/printf.h> + +struct _LogContext +{ + std::condition_variable_any log_condition; + std::recursive_mutex log_mutex; + std::ofstream file_stream; + std::vector<std::string> text_cache; + std::thread log_writer; + std::atomic<bool> threadRunning = false; + + ~_LogContext() + { + // safely shut down the log writer thread + threadRunning.store(false); + if (log_writer.joinable()) + { + log_condition.notify_one(); + log_writer.join(); + } + } +}LogContext; + +const std::map<LogType, std::string> g_logging_window_mapping +{ + {LogType::File, "Coreinit File-Access"}, + {LogType::GX2, "GX2"}, + {LogType::ThreadSync, "Coreinit Thread-Synchronization"}, + {LogType::SoundAPI, "Audio"}, + {LogType::Input, "Input"}, + {LogType::Socket, "Socket"}, + {LogType::Save, "Save"}, + {LogType::CoreinitMem, "Coreinit Memory"}, + {LogType::H264, "H264"}, + {LogType::OpenGL, "OpenGL"}, + {LogType::TextureCache, "Texture Cache"}, + {LogType::nn_nfp, "NFP"}, +}; + +uint64 cemuLog_getFlag(LogType type) +{ + return type <= LogType::Force ? 0 : (1ULL << ((uint64)type - 1)); +} + +bool cemuLog_advancedPPCLoggingEnabled() +{ + return GetConfig().advanced_ppc_logging; +} + +bool cemuLog_isLoggingEnabled(LogType type) +{ + if (type == LogType::Placeholder) + return false; + + if (type == LogType::None) + return false; + + return (type == LogType::Force) || ((GetConfig().log_flag.GetValue() & cemuLog_getFlag(type)) != 0); +} + +void cemuLog_thread() +{ + SetThreadName("cemuLog_thread"); + while (true) + { + std::unique_lock lock(LogContext.log_mutex); + while (LogContext.text_cache.empty()) + { + LogContext.log_condition.wait(lock); + if (!LogContext.threadRunning.load(std::memory_order::relaxed)) + return; + } + + std::vector<std::string> cache_copy; + cache_copy.swap(LogContext.text_cache); // fast copy & clear + lock.unlock(); + + for (const auto& entry : cache_copy) + LogContext.file_stream.write(entry.data(), entry.size()); + + LogContext.file_stream.flush(); + } +} + +void cemuLog_createLogFile(bool triggeredByCrash) +{ + std::unique_lock lock(LogContext.log_mutex); + if (LogContext.file_stream.is_open()) + return; + + const auto path = ActiveSettings::GetPath("log.txt"); + LogContext.file_stream.open(path, std::ios::out); + if (LogContext.file_stream.fail()) + { + cemu_assert_debug(false); + return; + } + + LogContext.threadRunning.store(true); + LogContext.log_writer = std::thread(cemuLog_thread); + lock.unlock(); +} + +void cemuLog_writeLineToLog(std::string_view text, bool date, bool new_line) +{ + std::unique_lock lock(LogContext.log_mutex); + + if (date) + { + const auto now = std::chrono::system_clock::now(); + const auto temp_time = std::chrono::system_clock::to_time_t(now); + const auto& time = *std::localtime(&temp_time); + +#ifdef PUBLIC_RELEASE + auto time_str = fmt::format("[{:02d}:{:02d}:{:02d}] ", time.tm_hour, time.tm_min, time.tm_sec); +#else + auto time_str = fmt::format("[{:02d}:{:02d}:{:02d}.{:03d}] ", time.tm_hour, time.tm_min, time.tm_sec, + std::chrono::duration_cast<std::chrono::milliseconds>(now - std::chrono::time_point_cast<std::chrono::seconds>(now)).count()); +#endif + + LogContext.text_cache.emplace_back(std::move(time_str)); + } + + LogContext.text_cache.emplace_back(text); + + if (new_line) + LogContext.text_cache.emplace_back("\n"); + + lock.unlock(); + LogContext.log_condition.notify_one(); +} + +bool cemuLog_log(LogType type, std::string_view text) +{ + if (!cemuLog_isLoggingEnabled(type)) + return false; + + cemuLog_writeLineToLog(text); + + const auto it = std::find_if(g_logging_window_mapping.cbegin(), g_logging_window_mapping.cend(), + [type](const auto& entry) { return entry.first == type; }); + if (it == g_logging_window_mapping.cend()) + LoggingWindow::Log(text); + else + LoggingWindow::Log(it->second, text); + + return true; +} + +bool cemuLog_log(LogType type, std::u8string_view text) +{ + std::basic_string_view<char> s((char*)text.data(), text.size()); + return cemuLog_log(type, s); +} + +bool cemuLog_log(LogType type, std::wstring_view text) +{ + if (!cemuLog_isLoggingEnabled(type)) + return false; + + return cemuLog_log(type, boost::nowide::narrow(text.data(), text.size())); +} + +void cemuLog_waitForFlush() +{ + cemuLog_createLogFile(false); + std::unique_lock lock(LogContext.log_mutex); + while(!LogContext.text_cache.empty()) + { + lock.unlock(); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + std::this_thread::yield(); + lock.lock(); + } + +} + +// used to atomically write multiple lines to the log +std::unique_lock<decltype(LogContext.log_mutex)> cafeLog_acquire() +{ + return std::unique_lock(LogContext.log_mutex); +} + +void cafeLog_log(uint32 type, const char* format, ...) +{ + const auto logType = (LogType)type; + if (!cemuLog_isLoggingEnabled(logType)) + return; + + char logTempStr[2048]; + va_list(args); + va_start(args, format); +#if BOOST_OS_WINDOWS > 0 + vsprintf_s(logTempStr, format, args); +#else + vsprintf(logTempStr, format, args); +#endif + va_end(args); + + cemuLog_writeLineToLog(logTempStr); + + const auto it = std::find_if(g_logging_window_mapping.cbegin(), g_logging_window_mapping.cend(), + [logType](const auto& entry) { return entry.first == logType; }); + if (it == g_logging_window_mapping.cend()) + LoggingWindow::Log(logTempStr); + else + LoggingWindow::Log(it->second, logTempStr); +} + +void cafeLog_logW(uint32 type, const wchar_t* format, ...) +{ + const auto logType = (LogType)type; + if (!cemuLog_isLoggingEnabled(logType)) + return; + + wchar_t logTempStr[2048]; + va_list(args); + va_start(args, format); +#if BOOST_OS_WINDOWS > 0 + vswprintf_s(logTempStr, format, args); +#else + vswprintf(logTempStr, 2048, format, args); +#endif + va_end(args); + + cemuLog_log(logType, logTempStr); + + const auto it = std::find_if(g_logging_window_mapping.cbegin(), g_logging_window_mapping.cend(), + [logType](const auto& entry) { return entry.first == logType; }); + if (it == g_logging_window_mapping.cend()) + LoggingWindow::Log(logTempStr); + else + LoggingWindow::Log(it->second, logTempStr); +} + +__declspec(dllexport) void cemuLog_log() +{ + typedef void(*VoidFunc)(); + const VoidFunc func = (VoidFunc)cafeLog_log; + func(); +} + +void cafeLog_logLine(uint32 type, const char* line) +{ + if (!cemuLog_isLoggingEnabled((LogType)type)) + return; + + cemuLog_writeLineToLog(line); +} + +void cafeLog_setLoggingFlagEnable(sint32 loggingType, bool isEnable) +{ + if (isEnable) + GetConfig().log_flag = GetConfig().log_flag.GetValue() | cemuLog_getFlag((LogType)loggingType); + else + GetConfig().log_flag = GetConfig().log_flag.GetValue() & ~cemuLog_getFlag((LogType)loggingType); + + g_config.Save(); +} + +bool cafeLog_isLoggingFlagEnabled(sint32 loggingType) +{ + return cemuLog_isLoggingEnabled((LogType)loggingType); +} diff --git a/src/Cemu/Logging/CemuLogging.h b/src/Cemu/Logging/CemuLogging.h new file mode 100644 index 00000000..021055d4 --- /dev/null +++ b/src/Cemu/Logging/CemuLogging.h @@ -0,0 +1,158 @@ +#pragma once + +enum class LogType : sint32 +{ + Placeholder = -2, + None = -1, + Force = 0, // this logging type is always on + File = 1, // coreinit file? + GX2 = 2, + UnsupportedAPI = 3, + ThreadSync = 4, + SoundAPI = 5, // any audio related API + Input = 6, // any input related API + Socket = 7, + Save = 8, + CoreinitMem = 9, // coreinit memory functions + H264 = 10, + OpenGL = 11, // OpenGL debug logging + TextureCache = 12, // texture cache warnings and info + VulkanValidation = 13, // Vulkan validation layer + nn_nfp = 14, // nn_nfp (Amiibo) API + Patches = 15, + CoreinitMP = 16, + CoreinitThread = 17, + CoreinitLogging = 18, // OSReport, OSConsoleWrite etc. + + PPC_IPC = 20, + NN_AOC = 21, + NN_PDM = 22, + + ProcUi = 40, + + APIErrors = 0, // alias for 0. Logs bad parameters or other API errors in OS libs +}; + +template <> +struct fmt::formatter<std::u8string_view> : formatter<string_view> { + template <typename FormatContext> + auto format(std::u8string_view v, FormatContext& ctx) { + string_view s((char*)v.data(), v.size()); + return formatter<string_view>::format(s, ctx); + } +}; + +void cemuLog_writeLineToLog(std::string_view text, bool date = true, bool new_line = true); +inline void cemuLog_writePlainToLog(std::string_view text) { cemuLog_writeLineToLog(text, false, false); } + +bool cemuLog_isLoggingEnabled(LogType type); + +bool cemuLog_log(LogType type, std::string_view text); +bool cemuLog_log(LogType type, std::u8string_view text); +bool cemuLog_log(LogType type, std::wstring_view text); +void cemuLog_waitForFlush(); // wait until all log lines are written + +template<typename TFmt, typename ... TArgs> +bool cemuLog_log(LogType type, TFmt format, TArgs&&... args) +{ + if (!cemuLog_isLoggingEnabled(type)) + return false; + + const auto format_view = fmt::to_string_view(format); + const auto text = fmt::vformat(format_view, fmt::make_args_checked<TArgs...>(format_view, args...)); + cemuLog_log(type, std::basic_string_view(text.data(), text.size())); + return true; +} + +// same as cemuLog_log, but only outputs in debug/release mode +template<typename TFmt, typename ... TArgs> +bool cemuLog_logDebug(LogType type, TFmt format, TArgs&&... args) +{ +#ifndef PUBLIC_RELEASE + return cemuLog_log(type, format, std::forward<TArgs>(args)...); +#else + return false; +#endif +} + +// cafe lib calls +bool cemuLog_advancedPPCLoggingEnabled(); + +uint64 cemuLog_getFlag(LogType type); +void cafeLog_setLoggingFlagEnable(sint32 loggingType, bool isEnable); +bool cafeLog_isLoggingFlagEnabled(sint32 loggingType); + +void cemuLog_createLogFile(bool triggeredByCrash); + +[[nodiscard]] std::unique_lock<std::recursive_mutex> cafeLog_acquire(); + +// legacy methods +void cafeLog_log(uint32 type, const char* format, ...); +void cafeLog_logW(uint32 type, const wchar_t* format, ...); +void cafeLog_logLine(uint32 type, const char* line); + +// legacy log types, deprecated. Use LogType instead +#define LOG_TYPE_FORCE (0) // this logging type is always on +#define LOG_TYPE_FILE (1) +#define LOG_TYPE_GX2 (2) +#define LOG_TYPE_UNSUPPORTED_API (3) +#define LOG_TYPE_THREADSYNC (4) +#define LOG_TYPE_SNDAPI (5) // any audio related API +#define LOG_TYPE_INPUT (6) // any input related API +#define LOG_TYPE_SOCKET (7) // anything from nn_sysnet +#define LOG_TYPE_SAVE (8) // anything from nn_save +#define LOG_TYPE_COREINIT_MEM (9) // coreinit memory functions +#define LOG_TYPE_H264 (10) +#define LOG_TYPE_OPENGL (11) // OpenGL debug logging +#define LOG_TYPE_TEXTURE_CACHE (12) // texture cache warnings and info +#define LOG_TYPE_VULKAN_VALIDATION (13) // Vulkan validation layer +#define LOG_TYPE_NFP (14) // nn_nfp (Amiibo) API +#define LOG_TYPE_PATCHES (15) // logging for graphic pack patches +#define LOG_TYPE_COREINIT_MP (16) +#define LOG_TYPE_COREINIT_THREAD (17) +#define LOG_TYPE_COREINIT_LOGGING (18) +#define LOG_TYPE_LAST (64) // set to 64 so we can easily add more log types in the future without destroying settings file compatibility + +// force log. Redundant -> replace with _log(LogType::Force, ...) call +inline bool cemuLog_force(std::string_view msg) +{ + return cemuLog_log(LogType::Force, msg); +} + +inline bool cemuLog_force(std::u8string_view msg) +{ + return cemuLog_log(LogType::Force, msg); +} + +inline bool cemuLog_force(std::wstring_view msg) +{ + return cemuLog_log(LogType::Force, msg); +} + +template<typename TFmt, typename ... TArgs> +void cemuLog_force(TFmt format, TArgs&&... args) +{ + cemuLog_log(LogType::Force, format, std::forward<TArgs>(args)...); +} + +// these are deprecated. Superseded by cemuLog_log(type, ...) +#define forceLog_printf(...) cafeLog_log(LOG_TYPE_FORCE, __VA_ARGS__); +#define forceLog_printfW(...) cafeLog_logW(LOG_TYPE_FORCE, __VA_ARGS__); +#define gx2Log_printf(...) if( cafeLog_isLoggingFlagEnabled(LOG_TYPE_GX2) ) cafeLog_log(LOG_TYPE_GX2, __VA_ARGS__); +#define coreinitMemLog_printf(...) if( cafeLog_isLoggingFlagEnabled(LOG_TYPE_COREINIT_MEM) ) cafeLog_log(LOG_TYPE_COREINIT_MEM, __VA_ARGS__); +#define sndApiLog_printf(...) if( cafeLog_isLoggingFlagEnabled(LOG_TYPE_SNDAPI) ) cafeLog_log(LOG_TYPE_SNDAPI, __VA_ARGS__); +#define socketLog_printf(...) if( cafeLog_isLoggingFlagEnabled(LOG_TYPE_SOCKET) ) cafeLog_log(LOG_TYPE_SOCKET, __VA_ARGS__); +#define inputLog_printf(...) if( cafeLog_isLoggingFlagEnabled(LOG_TYPE_INPUT) ) cafeLog_log(LOG_TYPE_INPUT, __VA_ARGS__); +#define saveLog_printf(...) if( cafeLog_isLoggingFlagEnabled(LOG_TYPE_SAVE) ) cafeLog_log(LOG_TYPE_SAVE, __VA_ARGS__); +#define nfpLog_printf(...) if( cafeLog_isLoggingFlagEnabled(LOG_TYPE_NFP) ) cafeLog_log(LOG_TYPE_NFP, __VA_ARGS__); + +#ifndef PUBLIC_RELEASE +#define forceLogDebug_printf(...) cafeLog_log(LOG_TYPE_FORCE, __VA_ARGS__); +#define forceLogDebug_printfW(...) cafeLog_logW(LOG_TYPE_FORCE, __VA_ARGS__); + + // use this for temporary debug logging. Will trigger compilation error in release builds +#define forceLogRemoveMe_printf(...) cafeLog_log(LOG_TYPE_FORCE, __VA_ARGS__); +#else +#define forceLogDebug_printf(...) ; +#define forceLogDebug_printfW(...) ; +#endif diff --git a/src/Cemu/PPCAssembler/ppcAssembler.cpp b/src/Cemu/PPCAssembler/ppcAssembler.cpp new file mode 100644 index 00000000..647d683d --- /dev/null +++ b/src/Cemu/PPCAssembler/ppcAssembler.cpp @@ -0,0 +1,3503 @@ +#include "Cemu/PPCAssembler/ppcAssembler.h" +#include "Cafe/HW/Espresso/PPCState.h" +#include "Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h" +#include "Cemu/ExpressionParser/ExpressionParser.h" +#include "util/helpers/helpers.h" + +#include <boost/container/small_vector.hpp> +#include <boost/static_string/static_string.hpp> + +struct ppcAssemblerStr_t +{ + ppcAssemblerStr_t(const char* start, const char* end) : str(start, end - start) {}; + ppcAssemblerStr_t(const char* start, size_t len) : str(start, len) {}; + + std::string_view str; +}; + +struct PPCAssemblerContext +{ + PPCAssemblerInOut* ctx; + boost::container::small_vector<ppcAssemblerStr_t, 8> listOperandStr; + struct PPCInstructionDef* iDef; + uint32 opcode; +}; + +// table based assembler/disassembler + +#define operand0Bit (1<<0) +#define operand1Bit (1<<1) +#define operand2Bit (1<<2) +#define operand3Bit (1<<3) +#define operand4Bit (1<<4) + +#define OPC_NONE (0xFFFF) +#define OPC_EXTENDED_BIT (0x8000) // use extended sub opcode + +enum +{ + OP_FORM_UNUSED, + OP_FORM_XL_CR, // CRXOR, CRAND etc. + OP_FORM_OP3_A_CMP, + OP_FORM_OP3_A_IMM, // rA, rS, rB is imm - has RC bit + OP_FORM_BRANCH_S16, + OP_FORM_BRANCH_S24, + OP_FORM_OP2_D_HSIMM, // rD, signed imm shifted imm (high half) + OP_FORM_RLWINM, + OP_FORM_RLWINM_EXTENDED, // alternative mnemonics of rlwinm + OP_FORM_RLWNM, + OP_FORM_RLWNM_EXTENDED, // alternative mnemonics of rlwnm + OP_FORM_CMP_SIMM, // cr, rD, r + OP_FORM_NO_OPERAND, + // FP + OP_FORM_X_FP_CMP, + + // new generic form with operand encoding stored in the table entry, everything above is deprecated + OP_FORM_DYNAMIC, +}; + +const char* ppcAssembler_getInstructionName(uint32 ppcAsmOp) +{ + switch (ppcAsmOp) + { + case PPCASM_OP_UKN: return "UKN"; + + case PPCASM_OP_ADDI: return "ADDI"; + case PPCASM_OP_SUBI: return "SUBI"; + case PPCASM_OP_ADDIS: return "ADDIS"; + case PPCASM_OP_ADDIC: return "ADDIC"; + case PPCASM_OP_ADDIC_: return "ADDIC."; + + case PPCASM_OP_ADD: return "ADD"; + case PPCASM_OP_ADD_: return "ADD."; + case PPCASM_OP_SUBF: return "SUBF"; + case PPCASM_OP_SUBF_: return "SUBF."; + case PPCASM_OP_SUBFC: return "SUBFC"; + case PPCASM_OP_SUBFC_: return "SUBFC."; + case PPCASM_OP_SUBFE: return "SUBFE"; + case PPCASM_OP_SUBFE_: return "SUBFE."; + case PPCASM_OP_SUBFIC: return "SUBFIC"; + + case PPCASM_OP_SUB: return "SUB"; + case PPCASM_OP_SUB_: return "SUB."; + + case PPCASM_OP_MULLI: return "MULLI"; + case PPCASM_OP_MULLW: return "MULLW"; + case PPCASM_OP_MULLW_: return "MULLW."; + case PPCASM_OP_MULHW: return "MULHW"; + case PPCASM_OP_MULHW_: return "MULHW."; + case PPCASM_OP_MULHWU: return "MULHWU"; + case PPCASM_OP_MULHWU_: return "MULHWU."; + + case PPCASM_OP_DIVW: return "DIVW"; + case PPCASM_OP_DIVW_: return "DIVW."; + case PPCASM_OP_DIVWU: return "DIVWU"; + case PPCASM_OP_DIVWU_: return "DIVWU."; + + case PPCASM_OP_AND: return "AND"; + case PPCASM_OP_AND_: return "AND."; + case PPCASM_OP_ANDC: return "ANDC"; + case PPCASM_OP_ANDC_: return "ANDC."; + + case PPCASM_OP_ANDI_: return "ANDI."; + case PPCASM_OP_ANDIS_: return "ANDIS."; + case PPCASM_OP_OR: return "OR"; + case PPCASM_OP_OR_: return "OR."; + case PPCASM_OP_ORI: return "ORI"; + case PPCASM_OP_ORIS: return "ORIS"; + case PPCASM_OP_ORC: return "ORC"; + case PPCASM_OP_XOR: return "XOR"; + case PPCASM_OP_NOR: return "NOR"; + case PPCASM_OP_NOR_: return "NOR."; + case PPCASM_OP_NOT: return "NOT"; + case PPCASM_OP_NOT_: return "NOT."; + case PPCASM_OP_NEG: return "NEG"; + case PPCASM_OP_NEG_: return "NEG."; + case PPCASM_OP_XORI: return "XORI"; + case PPCASM_OP_XORIS: return "XORIS"; + case PPCASM_OP_SRAW: return "SRAW"; + case PPCASM_OP_SRAW_: return "SRAW."; + case PPCASM_OP_SRAWI: return "SRAWI"; + case PPCASM_OP_SRAWI_: return "SRAWI."; + case PPCASM_OP_SLW: return "SLW"; + case PPCASM_OP_SLW_: return "SLW."; + case PPCASM_OP_SRW: return "SRW"; + case PPCASM_OP_SRW_: return "SRW."; + + case PPCASM_OP_RLWINM: return "RLWINM"; + case PPCASM_OP_RLWINM_: return "RLWINM."; + case PPCASM_OP_RLWIMI: return "RLWIMI"; + case PPCASM_OP_RLWIMI_: return "RLWIMI."; + + case PPCASM_OP_EXTLWI: return "EXTLWI"; + case PPCASM_OP_EXTLWI_: return "EXTLWI."; + case PPCASM_OP_EXTRWI: return "EXTRWI"; + case PPCASM_OP_EXTRWI_: return "EXTRWI."; + + case PPCASM_OP_ROTLWI: return "ROTLWI"; + case PPCASM_OP_ROTLWI_: return "ROTLWI."; + case PPCASM_OP_ROTRWI: return "ROTRWI"; + case PPCASM_OP_ROTRWI_: return "ROTRWI."; + + case PPCASM_OP_SLWI: return "SLWI"; + case PPCASM_OP_SLWI_: return "SLWI."; + case PPCASM_OP_SRWI: return "SRWI"; + case PPCASM_OP_SRWI_: return "SRWI."; + + case PPCASM_OP_CLRLWI: return "CLRLWI"; + case PPCASM_OP_CLRLWI_: return "CLRLWI."; + case PPCASM_OP_CLRRWI: return "CLRRWI"; + case PPCASM_OP_CLRRWI_: return "CLRRWI."; + + case PPCASM_OP_RLWNM: return "RLWNM"; + case PPCASM_OP_RLWNM_: return "RLWNM."; + + case PPCASM_OP_ROTLW: return "ROTLW"; + case PPCASM_OP_ROTLW_: return "ROTLW."; + + case PPCASM_OP_CMPWI: return "CMPWI"; + case PPCASM_OP_CMPLWI: return "CMPLWI"; + case PPCASM_OP_CMPW: return "CMPW"; + case PPCASM_OP_CMPLW: return "CMPLW"; + + case PPCASM_OP_MR: return "MR"; + case PPCASM_OP_MR_: return "MR."; + + case PPCASM_OP_EXTSB: return "EXTSB"; + case PPCASM_OP_EXTSH: return "EXTSH"; + case PPCASM_OP_CNTLZW: return "CNTLZW"; + case PPCASM_OP_EXTSB_: return "EXTSB."; + case PPCASM_OP_EXTSH_: return "EXTSH."; + case PPCASM_OP_CNTLZW_: return "CNTLZW."; + + case PPCASM_OP_MFSPR: return "MFSPR"; + case PPCASM_OP_MTSPR: return "MTSPR"; + + case PPCASM_OP_LMW: return "LMW"; + case PPCASM_OP_LWZ: return "LWZ"; + case PPCASM_OP_LWZU: return "LWZU"; + case PPCASM_OP_LWZX: return "LWZX"; + case PPCASM_OP_LWZUX: return "LWZUX"; + case PPCASM_OP_LHZ: return "LHZ"; + case PPCASM_OP_LHZU: return "LHZU"; + case PPCASM_OP_LHZX: return "LHZX"; + case PPCASM_OP_LHZUX: return "LHZUX"; + case PPCASM_OP_LHA: return "LHA"; + case PPCASM_OP_LHAU: return "LHAU"; + case PPCASM_OP_LHAX: return "LHAX"; + case PPCASM_OP_LHAUX: return "LHAUX"; + case PPCASM_OP_LBZ: return "LBZ"; + case PPCASM_OP_LBZU: return "LBZU"; + case PPCASM_OP_LBZX: return "LBZX"; + case PPCASM_OP_LBZUX: return "LBZUX"; + case PPCASM_OP_STMW: return "STMW"; + case PPCASM_OP_STW: return "STW"; + case PPCASM_OP_STWU: return "STWU"; + case PPCASM_OP_STWX: return "STWX"; + case PPCASM_OP_STWUX: return "STWUX"; + case PPCASM_OP_STH: return "STH"; + case PPCASM_OP_STHU: return "STHU"; + case PPCASM_OP_STHX: return "STHX"; + case PPCASM_OP_STHUX: return "STHUX"; + case PPCASM_OP_STB: return "STB"; + case PPCASM_OP_STBU: return "STBU"; + case PPCASM_OP_STBX: return "STBX"; + case PPCASM_OP_STBUX: return "STBUX"; + case PPCASM_OP_STSWI: return "STSWI"; + case PPCASM_OP_STWBRX: return "STWBRX"; + case PPCASM_OP_STHBRX: return "STHBRX"; + + case PPCASM_OP_LWARX: return "LWARX"; + case PPCASM_OP_STWCX_: return "STWCX."; + + case PPCASM_OP_B: return "B"; + case PPCASM_OP_BL: return "BL"; + case PPCASM_OP_BA: return "BA"; + case PPCASM_OP_BLA: return "BLA"; + + case PPCASM_OP_BC: return "BC"; + case PPCASM_OP_BNE: return "BNE"; + case PPCASM_OP_BEQ: return "BEQ"; + case PPCASM_OP_BGE: return "BGE"; + case PPCASM_OP_BGT: return "BGT"; + case PPCASM_OP_BLT: return "BLT"; + case PPCASM_OP_BLE: return "BLE"; + case PPCASM_OP_BDZ: return "BDZ"; + case PPCASM_OP_BDNZ: return "BDNZ"; + + case PPCASM_OP_BLR: return "BLR"; + case PPCASM_OP_BLTLR: return "BLTLR"; + case PPCASM_OP_BLELR: return "BLELR"; + case PPCASM_OP_BEQLR: return "BEQLR"; + case PPCASM_OP_BGELR: return "BGELR"; + case PPCASM_OP_BGTLR: return "BGTLR"; + case PPCASM_OP_BNELR: return "BNELR"; + + case PPCASM_OP_BCTR: return "BCTR"; + case PPCASM_OP_BCTRL: return "BCTRL"; + + case PPCASM_OP_LFS: return "LFS"; + case PPCASM_OP_LFSU: return "LFSU"; + case PPCASM_OP_LFSX: return "LFSX"; + case PPCASM_OP_LFSUX: return "LFSUX"; + case PPCASM_OP_LFD: return "LFD"; + case PPCASM_OP_LFDU: return "LFDU"; + case PPCASM_OP_LFDX: return "LFDX"; + case PPCASM_OP_LFDUX: return "LFDUX"; + case PPCASM_OP_STFS: return "STFS"; + case PPCASM_OP_STFSU: return "STFSU"; + case PPCASM_OP_STFSX: return "STFSX"; + case PPCASM_OP_STFSUX: return "STFSUX"; + case PPCASM_OP_STFD: return "STFD"; + case PPCASM_OP_STFDU: return "STFDU"; + case PPCASM_OP_STFDX: return "STFDX"; + case PPCASM_OP_STFDUX: return "STFDUX"; + + case PPCASM_OP_STFIWX: return "STFIWX"; + + case PPCASM_OP_PS_MERGE00: return "PS_MERGE00"; + case PPCASM_OP_PS_MERGE01: return "PS_MERGE01"; + case PPCASM_OP_PS_MERGE10: return "PS_MERGE10"; + case PPCASM_OP_PS_MERGE11: return "PS_MERGE11"; + case PPCASM_OP_PS_ADD: return "PS_ADD"; + case PPCASM_OP_PS_SUB: return "PS_SUB"; + case PPCASM_OP_PS_DIV: return "PS_DIV"; + case PPCASM_OP_PS_MUL: return "PS_MUL"; + case PPCASM_OP_PS_MADD: return "PS_MADD"; + case PPCASM_OP_PS_MSUB: return "PS_MSUB"; + case PPCASM_OP_PS_NMADD: return "PS_NMADD"; + case PPCASM_OP_PS_NMSUB: return "PS_NMSUB"; + + case PPCASM_OP_FMR: return "FMR"; + case PPCASM_OP_FNEG: return "FNEG"; + case PPCASM_OP_FRSP: return "FRSP"; + case PPCASM_OP_FRSQRTE: return "FRSQRTE"; + case PPCASM_OP_FADD: return "FADD"; + case PPCASM_OP_FADDS: return "FADDS"; + case PPCASM_OP_FSUB: return "FSUB"; + case PPCASM_OP_FSUBS: return "FSUBS"; + case PPCASM_OP_FMUL: return "FMUL"; + case PPCASM_OP_FMULS: return "FMULS"; + case PPCASM_OP_FDIV: return "FDIV"; + case PPCASM_OP_FDIVS: return "FDIVS"; + + case PPCASM_OP_FMADD: return "FMADD"; + case PPCASM_OP_FMADDS: return "FMADDS"; + case PPCASM_OP_FNMADD: return "FNMADD"; + case PPCASM_OP_FNMADDS: return "FNMADDS"; + case PPCASM_OP_FMSUB: return "FMSUB"; + case PPCASM_OP_FMSUBS: return "FMSUBS"; + case PPCASM_OP_FNMSUB: return "FNMSUB"; + case PPCASM_OP_FNMSUBS: return "FNMSUBS"; + + case PPCASM_OP_FCTIWZ: return "FCTIWZ"; + + case PPCASM_OP_FCMPU: return "FCMPU"; + case PPCASM_OP_FCMPO: return "FCMPO"; + + case PPCASM_OP_ISYNC: return "ISYNC"; + + case PPCASM_OP_NOP: return "NOP"; + case PPCASM_OP_LI: return "LI"; + case PPCASM_OP_LIS: return "LIS"; + + case PPCASM_OP_MFLR: return "MFLR"; + case PPCASM_OP_MTLR: return "MTLR"; + case PPCASM_OP_MFCTR: return "MFCTR"; + case PPCASM_OP_MTCTR: return "MTCTR"; + + case PPCASM_OP_CROR: return "CROR"; + case PPCASM_OP_CRNOR: return "CRNOR"; + case PPCASM_OP_CRORC: return "CRORC"; + case PPCASM_OP_CRXOR: return "CRXOR"; + case PPCASM_OP_CREQV: return "CREQV"; + case PPCASM_OP_CRAND: return "CRAND"; + case PPCASM_OP_CRNAND: return "CRNAND"; + case PPCASM_OP_CRANDC: return "CRANDC"; + + case PPCASM_OP_CRSET: return "CRSET"; + case PPCASM_OP_CRCLR: return "CRCLR"; + case PPCASM_OP_CRMOVE: return "CRMOVE"; + case PPCASM_OP_CRNOT: return "CRNOT"; + + + default: + return "UNDEF"; + } + return ""; +} + +#define C_MASK_RC (PPC_OPC_RC) +#define C_MASK_LK (PPC_OPC_LK) +#define C_MASK_AA (PPC_OPC_AA) +#define C_MASK_OE (1<<10) +#define C_MASK_BO (0x1F<<21) +#define C_MASK_BO_COND (0x1E<<21) // BO mask for true/false conditions. With hint bit excluded +#define C_MASK_BI_CRBIT (0x3<<16) +#define C_MASK_BI_CRIDX (0x7<<18) + +#define C_BIT_RC (PPC_OPC_RC) +#define C_BIT_LK (PPC_OPC_LK) +#define C_BIT_AA (PPC_OPC_AA) +#define C_BIT_BO_FALSE (0x4<<21) // CR bit not set +#define C_BIT_BO_TRUE (0xC<<21) // CR bit set +#define C_BIT_BO_ALWAYS (0x14<<21) +#define C_BIT_BO_DNZ (0x10<<21) +#define C_BIT_BO_DZ (0x12<<21) + +#define C_BITS_BI_LT (0x0<<16) +#define C_BITS_BI_GT (0x1<<16) +#define C_BITS_BI_EQ (0x2<<16) +#define C_BITS_BI_SO (0x3<<16) + +void ppcAssembler_setError(PPCAssemblerInOut* ctx, std::string_view errorMsg); + +bool ppcOp_extraCheck_extlwi(uint32 opcode) +{ + sint32 rS, rA, SH, MB, ME; + PPC_OPC_TEMPL_M(opcode, rS, rA, SH, MB, ME); + + return MB == 0; +} + +bool ppcOp_extraCheck_extrwi(uint32 opcode) +{ + sint32 rS, rA, SH, MB, ME; + PPC_OPC_TEMPL_M(opcode, rS, rA, SH, MB, ME); + if (ME != 31) + return false; + sint8 n = 32 - MB; + sint8 b = SH - n; + return b >= 0; +} + +bool ppcOp_extraCheck_slwi(uint32 opcode) +{ + sint32 rS, rA, SH, MB, ME; + PPC_OPC_TEMPL_M(opcode, rS, rA, SH, MB, ME); + return MB == 0 && SH == (31 - ME); +} + +bool ppcOp_extraCheck_srwi(uint32 opcode) +{ + sint32 rS, rA, SH, MB, ME; + PPC_OPC_TEMPL_M(opcode, rS, rA, SH, MB, ME); + return ME == 31 && (32 - SH) == MB; +} + +bool ppcOp_extraCheck_clrlwi(uint32 opcode) +{ + sint32 rS, rA, SH, MB, ME; + PPC_OPC_TEMPL_M(opcode, rS, rA, SH, MB, ME); + return SH == 0 && ME == 31; +} + +bool ppcOp_extraCheck_clrrwi(uint32 opcode) +{ + sint32 rS, rA, SH, MB, ME; + PPC_OPC_TEMPL_M(opcode, rS, rA, SH, MB, ME); + return SH == 0 && MB == 0; +} + +bool ppcOp_extraCheck_rotlw(uint32 opcode) +{ + sint32 rS, rA, SH, MB, ME; + PPC_OPC_TEMPL_M(opcode, rS, rA, SH, MB, ME); + return MB == 0 && ME == 31; +} + +#define FLG_DEFAULT 0 +#define FLG_SKIP_OP0 operand0Bit +#define FLG_SKIP_OP1 operand1Bit +#define FLG_SKIP_OP2 operand2Bit +#define FLG_SKIP_OP3 operand3Bit +#define FLG_SKIP_OP4 operand4Bit + +#define FLG_SWAP_OP0_OP1 (1<<6) // todo - maybe this should be implemented as a fully configurable matrix of indices instead of predefined constants? +#define FLG_SWAP_OP1_OP2 (1<<7) +#define FLG_SWAP_OP2_OP3 (1<<8) + +#define FLG_UNSIGNED_IMM (1<<10) // always consider immediate unsigned but still allow signed values when assembling + +class EncodedOperand_None +{ +public: + EncodedOperand_None() {} + + bool AssembleOperand(PPCAssemblerContext* assemblerCtx, PPCInstructionDef* iDef, uint32& opcode, size_t index) + { + if (index < assemblerCtx->listOperandStr.size() && !assemblerCtx->listOperandStr[index].str.empty()) + { + ppcAssembler_setError(assemblerCtx->ctx, "Too many operands"); + return false; + } + return true; + } + + void DisassembleOperand(PPCDisassembledInstruction* disInstr, PPCInstructionDef* iDef, const uint32 opcode, size_t index) + { + + } +}; + +static int _parseRegIndex(std::string_view sv, const char* prefix) +{ + while (*prefix) + { + if (sv.empty()) + return -1; + char c = sv[0]; + if (c >= 'A' && c <= 'Z') + c -= ('A' - 'a'); + if (c != *prefix) + return -1; + sv.remove_prefix(1); + prefix++; + } + int r = 0; + const std::from_chars_result result = std::from_chars(sv.data(), sv.data() + sv.size(), r); + if (result.ec == std::errc::invalid_argument || result.ec == std::errc::result_out_of_range) + return -1; + if (result.ptr != sv.data() + sv.size()) + return -1; + return r; +} + +template<bool TAllowEAZero = false> // if true, "r0" can be substituted with "0" +class EncodedOperand_GPR +{ +public: + EncodedOperand_GPR(uint8 bitPos) : m_bitPos(bitPos) {} + + bool AssembleOperand(PPCAssemblerContext* assemblerCtx, PPCInstructionDef* iDef, uint32& opcode, size_t index) + { + if (index >= assemblerCtx->listOperandStr.size()) + { + ppcAssembler_setError(assemblerCtx->ctx, "Missing operand"); + return false; + } + auto operandStr = assemblerCtx->listOperandStr[index].str; + if constexpr (TAllowEAZero) + { + if (operandStr.size() == 1 && operandStr[0] == '0') + { + opcode &= ~((uint32)0x1F << m_bitPos); + opcode |= ((uint32)0 << m_bitPos); + return true; + } + } + sint32 regIndex = _parseRegIndex(operandStr, "r"); + if (regIndex < 0 || regIndex >= 32) + { + ppcAssembler_setError(assemblerCtx->ctx, fmt::format("Operand \"{}\" is not a valid GPR (expected r0 - r31)", assemblerCtx->listOperandStr[index].str)); + return false; + } + opcode &= ~((uint32)0x1F << m_bitPos); + opcode |= ((uint32)regIndex << m_bitPos); + return true; + } + + void DisassembleOperand(PPCDisassembledInstruction* disInstr, PPCInstructionDef* iDef, const uint32 opcode, size_t index) + { + uint32 regIndex = (opcode >> m_bitPos) & 0x1F; + disInstr->operandMask |= (1 << index); + disInstr->operand[index].type = PPCASM_OPERAND_TYPE_GPR; + disInstr->operand[index].registerIndex = regIndex; + } +private: + uint8 m_bitPos; +}; + +class EncodedOperand_FPR +{ +public: + EncodedOperand_FPR(uint8 bitPos) : m_bitPos(bitPos) {} + + bool AssembleOperand(PPCAssemblerContext* assemblerCtx, PPCInstructionDef* iDef, uint32& opcode, size_t index) + { + if (index >= assemblerCtx->listOperandStr.size()) + { + ppcAssembler_setError(assemblerCtx->ctx, "Missing operand"); + return false; + } + sint32 regIndex = _parseRegIndex(assemblerCtx->listOperandStr[index].str, "f"); + if (regIndex < 0 || regIndex >= 32) + { + ppcAssembler_setError(assemblerCtx->ctx, fmt::format("Operand \"{}\" is not a valid FPR (expected f0 - f31)", assemblerCtx->listOperandStr[index].str)); + return false; + } + opcode &= ~((uint32)0x1F << m_bitPos); + opcode |= ((uint32)regIndex << m_bitPos); + return true; + } + + void DisassembleOperand(PPCDisassembledInstruction* disInstr, PPCInstructionDef* iDef, const uint32 opcode, size_t index) + { + uint32 regIndex = (opcode >> m_bitPos) & 0x1F; + disInstr->operandMask |= (1<<index); + disInstr->operand[index].type = PPCASM_OPERAND_TYPE_FPR; + disInstr->operand[index].registerIndex = regIndex; + } +private: + uint8 m_bitPos; +}; + +class EncodedOperand_SPR +{ +public: + EncodedOperand_SPR() {} + + bool AssembleOperand(PPCAssemblerContext* assemblerCtx, PPCInstructionDef* iDef, uint32& opcode, size_t index) + { + if (index >= assemblerCtx->listOperandStr.size()) + { + ppcAssembler_setError(assemblerCtx->ctx, "Missing operand"); + return false; + } + sint32 regIndex = _parseRegIndex(assemblerCtx->listOperandStr[index].str, "spr"); + if (regIndex < 0 || regIndex >= 1024) + { + ppcAssembler_setError(assemblerCtx->ctx, fmt::format("Operand \"{}\" is not a valid GPR (expected spr0 - spr1023)", assemblerCtx->listOperandStr[index].str)); + return false; + } + sint32 sprLow = (regIndex) & 0x1F; + sint32 sprHigh = (regIndex>>5) & 0x1F; + opcode &= ~((uint32)0x1F << 16); + opcode |= ((uint32)sprLow << 16); + opcode &= ~((uint32)0x1F << 11); + opcode |= ((uint32)sprHigh << 11); + return true; + } + + void DisassembleOperand(PPCDisassembledInstruction* disInstr, PPCInstructionDef* iDef, const uint32 opcode, size_t index) + { + uint32 sprLow = (opcode >> 16) & 0x1F; + uint32 sprHigh = (opcode >> 11) & 0x1F; + disInstr->operandMask |= (1 << index); + disInstr->operand[index].type = PPCASM_OPERAND_TYPE_SPR; + disInstr->operand[index].registerIndex = sprLow | (sprHigh << 5); + } +}; + +class EncodedOperand_MemLoc +{ +public: + EncodedOperand_MemLoc() {} + + bool AssembleOperand(PPCAssemblerContext* assemblerCtx, PPCInstructionDef* iDef, uint32& opcode, size_t index) + { + if (index >= assemblerCtx->listOperandStr.size()) + { + ppcAssembler_setError(assemblerCtx->ctx, "Missing operand"); + return false; + } + std::basic_string_view<char> svOpText(assemblerCtx->listOperandStr[index].str); + // first we parse the memory register part at the end + auto startPtr = svOpText.data(); + auto endPtr = startPtr + svOpText.size(); + // trim whitespaces + while (endPtr > startPtr) + { + const char c = endPtr[-1]; + if (c != ' ' && c != '\t') + break; + endPtr--; + } + if (endPtr == startPtr || endPtr[-1] != ')') + { + ppcAssembler_setError(assemblerCtx->ctx, fmt::format("\'{}\' does not end with valid memory register syntax. Memory operand must have the form offset(gpr). Example: 0x20(r3)", svOpText)); + return false; + } + endPtr--; + // find parenthesis open + auto memoryRegEnd = endPtr; + const char* memoryRegBegin = nullptr; + while (endPtr > startPtr) + { + const char c = endPtr[-1]; + if (c == '(') + { + memoryRegBegin = endPtr; + endPtr--; // move end pointer beyond the parenthesis + break; + } + endPtr--; + } + if (memoryRegBegin == nullptr) + { + ppcAssembler_setError(assemblerCtx->ctx, fmt::format("\'{}\' does not end with valid memory register syntax. Memory operand must have the form offset(gpr). Example: 0x20(r3)", svOpText)); + return false; + } + std::string_view svExpressionPart(startPtr, endPtr); + std::string_view svRegPart(memoryRegBegin, memoryRegEnd - memoryRegBegin); + sint32 memGpr = _parseRegIndex(svRegPart, "r"); + //if (_ppcAssembler_parseRegister(svRegPart, "r", memGpr) == false || (memGpr < 0 || memGpr >= 32)) + //{ + // sprintf(_assemblerErrorMessageDepr, "\'%.*s\' is not a valid GPR", (int)(memoryRegEnd - memoryRegBegin), memoryRegBegin); + // ppcAssembler_setError(internalCtx.ctx, _assemblerErrorMessageDepr); + // return false; + //} + if (memGpr < 0 || memGpr >= 32) + { + ppcAssembler_setError(assemblerCtx->ctx, fmt::format("Memory operand register \"{}\" is not a valid GPR (expected r0 - r31)", svRegPart)); + return false; + } + opcode &= ~(0x1F << 16); + opcode |= (memGpr << 16); + // parse expression + ExpressionParser ep; + double immD = 0.0f; + try + { + if (ep.IsConstantExpression(svExpressionPart)) + { + immD = ep.Evaluate(svExpressionPart); + sint32 imm = (sint32)immD; + if (imm < -32768 && imm > 32767) + { + std::string msg = fmt::format("\"{}\" evaluates to offset out of range (Valid range is -32768 to 32767)", svExpressionPart); + ppcAssembler_setError(assemblerCtx->ctx, msg); + return false; + } + opcode &= ~0xFFFF; + opcode |= ((uint32)imm & 0xFFFF); + } + else + { + assemblerCtx->ctx->list_relocs.emplace_back(PPCASM_RELOC::U32_MASKED_IMM, std::string(svExpressionPart), 0, 0, 16); + return true; + } + } + catch (std::exception* e) + { + std::string msg = fmt::format("\"{}\" could not be evaluated. Error: {}", svExpressionPart, e->what()); + ppcAssembler_setError(assemblerCtx->ctx, msg); + return false; + } + return true; + } + + void DisassembleOperand(PPCDisassembledInstruction* disInstr, PPCInstructionDef* iDef, const uint32 opcode, size_t index) + { + uint32 imm = (opcode & 0xFFFF); + if (imm & 0x8000) + imm |= 0xFFFF0000; + uint32 regIndex = (opcode >> 16) & 0x1F; + disInstr->operandMask |= (1 << index); + disInstr->operand[index].type = PPCASM_OPERAND_TYPE_MEM; + disInstr->operand[index].registerIndex = regIndex; + disInstr->operand[index].immS32 = (sint32)imm; + disInstr->operand[index].immWidth = 16; + disInstr->operand[index].isSignedImm = true; + } +}; + +bool _CanStoreInteger(uint32 value, uint32 numBits, bool isSigned) +{ + if (isSigned) + { + sint32 storedValue = (sint32)value; + storedValue <<= (32 - numBits); + storedValue >>= (32 - numBits); + return (uint32)storedValue == value; + } + // unsigned + uint32 storedValue = value; + storedValue <<= (32 - numBits); + storedValue >>= (32 - numBits); + return storedValue == value; +} + +class EncodedOperand_IMM +{ +public: + EncodedOperand_IMM(uint8 bitPos, uint8 bits, bool isSigned, bool negate = false, bool extendedRange = false) : m_bitPos(bitPos), m_bits(bits), m_isSigned(isSigned), m_negate(negate), m_extendedRange(extendedRange) {} + + bool AssembleOperand(PPCAssemblerContext* assemblerCtx, PPCInstructionDef* iDef, uint32& opcode, size_t index) + { + if (index >= assemblerCtx->listOperandStr.size()) + { + ppcAssembler_setError(assemblerCtx->ctx, "Missing operand"); + return false; + } + // parse expression + std::string expressionString(assemblerCtx->listOperandStr[index].str); + if (m_negate) + { + expressionString.insert(0, "0-("); + expressionString.append(")"); + } + ExpressionParser ep; + double immD = 0.0f; + try + { + if (ep.IsConstantExpression(expressionString)) + { + immD = ep.Evaluate(expressionString); + } + else + { + assemblerCtx->ctx->list_relocs.emplace_back(PPCASM_RELOC::U32_MASKED_IMM, expressionString, 0, m_bitPos, m_bits); + return true; + } + } + catch (std::exception* e) + { + // check if expression is invalid or if it contains unknown constants + std::string msg = fmt::format("\"{}\" could not be evaluated. Error: {}", expressionString.c_str(), e->what()); + ppcAssembler_setError(assemblerCtx->ctx, msg); + return false; + } + uint32 imm = (uint32)(sint32)immD; + bool canStore = _CanStoreInteger(imm, m_bits, m_isSigned); + if(!canStore && m_extendedRange) // always allow unsigned + canStore = _CanStoreInteger(imm, m_bits, false); + if (!canStore) + { + std::string msg = fmt::format("Value of operand \"{}\" is out of range", assemblerCtx->listOperandStr[index].str); + ppcAssembler_setError(assemblerCtx->ctx, msg); + return false; + } + uint32 mask = (1<<m_bits)-1; + imm &= mask; + opcode &= ~(mask << m_bitPos); + opcode |= (imm << m_bitPos); + return true; + } + + void DisassembleOperand(PPCDisassembledInstruction* disInstr, PPCInstructionDef* iDef, const uint32 opcode, size_t index) + { + uint32 mask = (1 << m_bits) - 1; + uint32 immValue = (opcode >> m_bitPos) & mask; + if (m_isSigned) + { + sint32 tmpValue = (sint32)immValue; + tmpValue <<= (32 - m_bits); + tmpValue >>= (32 - m_bits); + immValue = (uint32)tmpValue; + } + disInstr->operandMask |= (1 << index); + disInstr->operand[index].type = PPCASM_OPERAND_TYPE_IMM; + disInstr->operand[index].immS32 = immValue; + disInstr->operand[index].immWidth = m_bits; + disInstr->operand[index].isSignedImm = m_isSigned; + } +private: + uint8 m_bitPos; + uint8 m_bits; + bool m_isSigned; + bool m_negate; + bool m_extendedRange; +}; + +class EncodedOperand_U5Reverse +{ +public: + EncodedOperand_U5Reverse(uint8 bitPos, uint8 base) : m_bitPos(bitPos), m_base(base) {} + + bool AssembleOperand(PPCAssemblerContext* assemblerCtx, PPCInstructionDef* iDef, uint32& opcode, size_t index) + { + if (index >= assemblerCtx->listOperandStr.size()) + { + ppcAssembler_setError(assemblerCtx->ctx, "Missing operand"); + return false; + } + // parse expression + std::string expressionString(assemblerCtx->listOperandStr[index].str); + expressionString.insert(0, fmt::format("{}-(", m_base)); + expressionString.append(")"); + ExpressionParser ep; + double immD = 0.0f; + try + { + if (ep.IsConstantExpression(expressionString)) + { + immD = ep.Evaluate(expressionString); + } + else + { + assemblerCtx->ctx->list_relocs.emplace_back(PPCASM_RELOC::U32_MASKED_IMM, expressionString, 0, m_bitPos, 5); + return true; + } + } + catch (std::exception* e) + { + // check if expression is invalid or if it contains unknown constants + std::string msg = fmt::format("\"{}\" could not be evaluated. Error: {}", expressionString.c_str(), e->what()); + ppcAssembler_setError(assemblerCtx->ctx, msg); + return false; + } + uint32 imm = (uint32)(sint32)immD; + bool canStore = _CanStoreInteger(imm, 5, false); + if (!canStore) + { + std::string msg = fmt::format("Value of operand \"{}\" is out of range", assemblerCtx->listOperandStr[index].str); + ppcAssembler_setError(assemblerCtx->ctx, msg); + return false; + } + uint32 mask = (1 << 5) - 1; + imm &= mask; + opcode &= ~(mask << m_bitPos); + opcode |= (imm << m_bitPos); + return true; + } + + void DisassembleOperand(PPCDisassembledInstruction* disInstr, PPCInstructionDef* iDef, const uint32 opcode, size_t index) + { + uint32 mask = (1 << 5) - 1; + uint32 immValue = (opcode >> m_bitPos) & mask; + immValue = m_base - immValue; + disInstr->operandMask |= (1 << index); + disInstr->operand[index].type = PPCASM_OPERAND_TYPE_IMM; + disInstr->operand[index].immS32 = immValue; + disInstr->operand[index].immWidth = 5; + disInstr->operand[index].isSignedImm = false; + } +private: + uint8 m_bitPos; + uint8 m_base; +}; + +class EncodedConstraint_None +{ +public: + EncodedConstraint_None() {} + + void AssembleConstraint(PPCAssemblerContext* assemblerCtx, PPCInstructionDef* iDef, uint32& opcode) + { + } + + bool DisassembleCheckConstraint(PPCDisassembledInstruction* disInstr, PPCInstructionDef* iDef, const uint32 opcode) + { + return true; + } +}; + +class EncodedConstraint_MirrorU5 +{ +public: + EncodedConstraint_MirrorU5(uint8 srcBitPos, uint8 dstBitPos) : m_srcBitPos(srcBitPos), m_dstBitPos(dstBitPos) {} + + void AssembleConstraint(PPCAssemblerContext* assemblerCtx, PPCInstructionDef* iDef, uint32& opcode) + { + uint32 regIndex = (opcode >> m_srcBitPos) & 0x1F; + opcode &= ~((uint32)0x1F << m_dstBitPos); + opcode |= ((uint32)regIndex << m_dstBitPos); + } + + bool DisassembleCheckConstraint(PPCDisassembledInstruction* disInstr, PPCInstructionDef* iDef, const uint32 opcode) + { + uint32 regSrc = (opcode >> m_srcBitPos) & 0x1F; + uint32 regDst = (opcode >> m_dstBitPos) & 0x1F; + return regSrc == regDst; + } + +private: + const uint8 m_srcBitPos, m_dstBitPos; +}; + +// same as _MirrorU5, but the destination must match invBase - src +class EncodedConstraint_MirrorReverseU5 +{ +public: + EncodedConstraint_MirrorReverseU5(uint8 srcBitPos, uint8 dstBitPos, uint8 invBase) : m_srcBitPos(srcBitPos), m_dstBitPos(dstBitPos), m_invBase(invBase) {} + + void AssembleConstraint(PPCAssemblerContext* assemblerCtx, PPCInstructionDef* iDef, uint32& opcode) + { + uint32 regIndex = (opcode >> m_srcBitPos) & 0x1F; + regIndex = m_invBase - regIndex; + opcode &= ~((uint32)0x1F << m_dstBitPos); + opcode |= ((uint32)regIndex << m_dstBitPos); + } + + bool DisassembleCheckConstraint(PPCDisassembledInstruction* disInstr, PPCInstructionDef* iDef, const uint32 opcode) + { + uint32 regSrc = (opcode >> m_srcBitPos) & 0x1F; + uint32 regDst = (opcode >> m_dstBitPos) & 0x1F; + regSrc = m_invBase - regSrc; + if (regSrc >= 32) + return false; + return regSrc == regDst; + } + +private: + const uint8 m_srcBitPos, m_dstBitPos, m_invBase; +}; + +class EncodedConstraint_FixedU5 +{ +public: + EncodedConstraint_FixedU5(uint8 bitPos, uint8 expectedReg) : m_bitPos(bitPos), m_expectedReg(expectedReg) {} + + void AssembleConstraint(PPCAssemblerContext* assemblerCtx, PPCInstructionDef* iDef, uint32& opcode) + { + uint32 regIndex = m_expectedReg; + opcode &= ~((uint32)0x1F << m_bitPos); + opcode |= ((uint32)regIndex << m_bitPos); + } + + bool DisassembleCheckConstraint(PPCDisassembledInstruction* disInstr, PPCInstructionDef* iDef, const uint32 opcode) + { + uint32 reg = (opcode >> m_bitPos) & 0x1F; + return (uint8)reg == m_expectedReg; + } + +private: + const uint8 m_bitPos; + const uint8 m_expectedReg; +}; + +using EncodedConstraint_FixedRegister = EncodedConstraint_FixedU5; +using EncodedConstraint_MirrorRegister = EncodedConstraint_MirrorU5; + +// checks bit value, but does not overwrite it on assemble +class EncodedConstraint_CheckSignBit +{ +public: + EncodedConstraint_CheckSignBit(uint8 bitPos, uint8 expectedValue) : m_bitPos(bitPos), m_expectedValue(expectedValue) {} + + void AssembleConstraint(PPCAssemblerContext* assemblerCtx, PPCInstructionDef* iDef, uint32& opcode) + { + // dont overwrite the existing sign bit + } + + bool DisassembleCheckConstraint(PPCDisassembledInstruction* disInstr, PPCInstructionDef* iDef, const uint32 opcode) + { + return ((opcode >> m_bitPos) & 1) == m_expectedValue; + } + +private: + const uint8 m_bitPos; + const uint8 m_expectedValue; +}; + +//class EncodedConstraint_ExpectBit +//{ +//public: +// EncodedConstraint_ExpectBit(uint8 bitPos, bool val) : m_bitPos(bitPos), m_expectedValue(val ? 1 : 0) {} +// +// void AssembleConstraint(PPCAssemblerContext* assemblerCtx, PPCInstructionDef* iDef, uint32& opcode) +// { +// if (m_expectedValue) +// opcode |= (1 << m_bitPos); +// else +// opcode &= ~(1 << m_bitPos); +// } +// +// bool DisassembleCheckConstraint(PPCDisassembledInstruction* disInstr, PPCInstructionDef* iDef, const uint32 opcode) +// { +// return ((opcode >> m_bitPos) & 1) == m_expectedValue; +// } +// +//private: +// const uint8 m_bitPos; +// const uint8 m_expectedValue; +//}; + +class EncodedConstraint_FixedSPR +{ +public: + EncodedConstraint_FixedSPR(uint16 sprIndex) : m_sprIndex(sprIndex) {} + + void AssembleConstraint(PPCAssemblerContext* assemblerCtx, PPCInstructionDef* iDef, uint32& opcode) + { + sint32 sprLow = (m_sprIndex) & 0x1F; + sint32 sprHigh = (m_sprIndex >> 5) & 0x1F; + opcode &= ~((uint32)0x1F << 16); + opcode |= ((uint32)sprLow << 16); + opcode &= ~((uint32)0x1F << 11); + opcode |= ((uint32)sprHigh << 11); + } + + bool DisassembleCheckConstraint(PPCDisassembledInstruction* disInstr, PPCInstructionDef* iDef, const uint32 opcode) + { + uint32 sprLow = (opcode >> 16) & 0x1F; + uint32 sprHigh = (opcode >> 11) & 0x1F; + uint32 sprIndex = sprLow | (sprHigh << 5); + return (uint16)sprIndex == m_sprIndex; + } + +private: + const uint16 m_sprIndex; +}; + +struct PPCInstructionDef +{ + uint16 ppcAsmOp; // instruction type identifier + uint8 priority; + uint16 opc0; + uint16 opc1; + uint16 opc2; + uint8 instructionForm; // encoding + uint16 flags; + uint32 maskBits; + uint32 compareBits; + bool(*extraCheck)(uint32 opcode); // used for unique criteria (e.g. SRWI checks SH/mask) -> Replaced by constraints + + std::array<std::variant<EncodedOperand_None, EncodedOperand_GPR<false>, EncodedOperand_GPR<true>, EncodedOperand_FPR, EncodedOperand_SPR, EncodedOperand_IMM, EncodedOperand_U5Reverse, EncodedOperand_MemLoc>, 4> encodedOperands{}; // note: The default constructor of std::variant will default-construct the first type (which we want to be EncodedOperand_None) + std::array<std::variant<EncodedConstraint_None, EncodedConstraint_MirrorRegister, EncodedConstraint_MirrorReverseU5, EncodedConstraint_FixedRegister, EncodedConstraint_FixedSPR, EncodedConstraint_CheckSignBit>, 3> constraints{}; +}; + +PPCInstructionDef ppcInstructionTable[] = +{ + {PPCASM_OP_PS_MERGE00, 0, 4, 16, 16, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(11)}}, + {PPCASM_OP_PS_MERGE01, 0, 4, 16, 17, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(11)}}, + {PPCASM_OP_PS_MERGE10, 0, 4, 16, 18, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(11)}}, + {PPCASM_OP_PS_MERGE11, 0, 4, 16, 19, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(11)}}, + + {PPCASM_OP_PS_DIV, 0, 4, 18, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(11)}}, + {PPCASM_OP_PS_SUB, 0, 4, 20, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(11)}}, + {PPCASM_OP_PS_ADD, 0, 4, 21, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(11)}}, + {PPCASM_OP_PS_MUL, 0, 4, 25, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(6)}}, + + {PPCASM_OP_PS_MSUB, 0, 4, 28, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(6), EncodedOperand_FPR(11)}}, + {PPCASM_OP_PS_MADD, 0, 4, 29, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(6), EncodedOperand_FPR(11)}}, + {PPCASM_OP_PS_NMSUB, 0, 4, 30, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(6), EncodedOperand_FPR(11)}}, + {PPCASM_OP_PS_NMADD, 0, 4, 31, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(6), EncodedOperand_FPR(11)}}, + + {PPCASM_OP_MULLI, 0, 7, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_IMM(0, 16, true)}}, + + {PPCASM_OP_SUBFIC, 0, 8, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_IMM(0, 16, true)} }, + + {PPCASM_OP_CMPLWI, 0, 10, OPC_NONE, OPC_NONE, OP_FORM_CMP_SIMM, FLG_DEFAULT, 0, 0, nullptr}, + {PPCASM_OP_CMPWI, 0, 11, OPC_NONE, OPC_NONE, OP_FORM_CMP_SIMM, FLG_DEFAULT, 0, 0, nullptr}, + {PPCASM_OP_ADDIC, 0, 12, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_IMM(0, 16, true)}}, + {PPCASM_OP_ADDIC_, 0, 13, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_IMM(0, 16, true)}}, + {PPCASM_OP_ADDI, 0, 14, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_IMM(0, 16, true)}}, + {PPCASM_OP_SUBI, 1, 14, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_IMM(0, 16, true, true)}, {EncodedConstraint_CheckSignBit(15, 1)}}, // special form of ADDI for negative immediate + {PPCASM_OP_LI, 1, 14, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_IMM(0, 16, true, false, true)}, {EncodedConstraint_FixedRegister(16, 0)}}, + {PPCASM_OP_ADDIS, 0, 15, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_IMM(0, 16, true, false, true)}}, + {PPCASM_OP_LIS, 1, 15, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_IMM(0, 16, true, false, true)}, {EncodedConstraint_FixedRegister(16, 0)}}, + + {PPCASM_OP_BC, 0, 16, OPC_NONE, OPC_NONE, OP_FORM_BRANCH_S16, FLG_DEFAULT, 0, 0, nullptr}, + + {PPCASM_OP_BNE, 1, 16, OPC_NONE, OPC_NONE, OP_FORM_BRANCH_S16, FLG_DEFAULT, C_MASK_BO_COND | C_MASK_BI_CRBIT, C_BIT_BO_FALSE | C_BITS_BI_EQ, nullptr}, + {PPCASM_OP_BEQ, 1, 16, OPC_NONE, OPC_NONE, OP_FORM_BRANCH_S16, FLG_DEFAULT, C_MASK_BO_COND | C_MASK_BI_CRBIT, C_BIT_BO_TRUE | C_BITS_BI_EQ, nullptr}, + {PPCASM_OP_BGE, 1, 16, OPC_NONE, OPC_NONE, OP_FORM_BRANCH_S16, FLG_DEFAULT, C_MASK_BO_COND | C_MASK_BI_CRBIT, C_BIT_BO_FALSE | C_BITS_BI_LT, nullptr}, + {PPCASM_OP_BGT, 1, 16, OPC_NONE, OPC_NONE, OP_FORM_BRANCH_S16, FLG_DEFAULT, C_MASK_BO_COND | C_MASK_BI_CRBIT, C_BIT_BO_TRUE | C_BITS_BI_GT, nullptr}, + {PPCASM_OP_BLT, 1, 16, OPC_NONE, OPC_NONE, OP_FORM_BRANCH_S16, FLG_DEFAULT, C_MASK_BO_COND | C_MASK_BI_CRBIT, C_BIT_BO_TRUE | C_BITS_BI_LT, nullptr}, + {PPCASM_OP_BLE, 1, 16, OPC_NONE, OPC_NONE, OP_FORM_BRANCH_S16, FLG_DEFAULT, C_MASK_BO_COND | C_MASK_BI_CRBIT, C_BIT_BO_FALSE | C_BITS_BI_GT, nullptr}, + + {PPCASM_OP_BDZ, 1, 16, OPC_NONE, OPC_NONE, OP_FORM_BRANCH_S16, FLG_DEFAULT, C_MASK_BO, C_BIT_BO_DZ, nullptr}, + {PPCASM_OP_BDNZ, 1, 16, OPC_NONE, OPC_NONE, OP_FORM_BRANCH_S16, FLG_DEFAULT, C_MASK_BO, C_BIT_BO_DNZ, nullptr}, + + {PPCASM_OP_B, 0, 18, OPC_NONE, OPC_NONE, OP_FORM_BRANCH_S24, FLG_DEFAULT, C_MASK_LK | C_MASK_AA, 0, nullptr}, + {PPCASM_OP_BL, 0, 18, OPC_NONE, OPC_NONE, OP_FORM_BRANCH_S24, FLG_DEFAULT, C_MASK_LK | C_MASK_AA, C_BIT_LK, nullptr}, + {PPCASM_OP_BA, 0, 18, OPC_NONE, OPC_NONE, OP_FORM_BRANCH_S24, FLG_DEFAULT, C_MASK_LK | C_MASK_AA, C_BIT_AA, nullptr}, + {PPCASM_OP_BLA, 0, 18, OPC_NONE, OPC_NONE, OP_FORM_BRANCH_S24, FLG_DEFAULT, C_MASK_LK | C_MASK_AA, C_BIT_LK|C_BIT_AA, nullptr}, + + {PPCASM_OP_BLR, 0, 19, 16, OPC_NONE, OP_FORM_NO_OPERAND, FLG_DEFAULT, C_MASK_BO | C_MASK_LK, C_BIT_BO_ALWAYS, nullptr}, + + {PPCASM_OP_BLTLR, 0, 19, 16, OPC_NONE, OP_FORM_NO_OPERAND, FLG_DEFAULT, C_MASK_BO | C_MASK_BI_CRBIT | C_MASK_LK, C_BIT_BO_TRUE | C_BITS_BI_LT, nullptr}, // less + {PPCASM_OP_BGTLR, 0, 19, 16, OPC_NONE, OP_FORM_NO_OPERAND, FLG_DEFAULT, C_MASK_BO | C_MASK_BI_CRBIT | C_MASK_LK, C_BIT_BO_TRUE | C_BITS_BI_GT, nullptr}, // greater + {PPCASM_OP_BEQLR, 0, 19, 16, OPC_NONE, OP_FORM_NO_OPERAND, FLG_DEFAULT, C_MASK_BO | C_MASK_BI_CRBIT | C_MASK_LK, C_BIT_BO_TRUE | C_BITS_BI_EQ, nullptr}, // equal + {PPCASM_OP_BLELR, 0, 19, 16, OPC_NONE, OP_FORM_NO_OPERAND, FLG_DEFAULT, C_MASK_BO | C_MASK_BI_CRBIT | C_MASK_LK, C_BIT_BO_FALSE | C_BITS_BI_GT, nullptr}, // less or equal (not greater) + {PPCASM_OP_BGELR, 0, 19, 16, OPC_NONE, OP_FORM_NO_OPERAND, FLG_DEFAULT, C_MASK_BO | C_MASK_BI_CRBIT | C_MASK_LK, C_BIT_BO_FALSE | C_BITS_BI_LT, nullptr}, // greater or equal (not less) + {PPCASM_OP_BNELR, 0, 19, 16, OPC_NONE, OP_FORM_NO_OPERAND, FLG_DEFAULT, C_MASK_BO | C_MASK_BI_CRBIT | C_MASK_LK, C_BIT_BO_FALSE | C_BITS_BI_EQ, nullptr}, // not equal + + {PPCASM_OP_ISYNC, 0, 19, 150, OPC_NONE, OP_FORM_NO_OPERAND, FLG_DEFAULT, 0, 0, nullptr}, + + {PPCASM_OP_CRNOR, 0, 19, 33, OPC_NONE, OP_FORM_XL_CR, FLG_DEFAULT, 0, 0, nullptr}, + {PPCASM_OP_CRANDC, 0, 19, 129, OPC_NONE, OP_FORM_XL_CR, FLG_DEFAULT, 0, 0, nullptr}, + {PPCASM_OP_CRXOR, 0, 19, 193, OPC_NONE, OP_FORM_XL_CR, FLG_DEFAULT, 0, 0, nullptr}, + {PPCASM_OP_CRNAND, 0, 19, 255, OPC_NONE, OP_FORM_XL_CR, FLG_DEFAULT, 0, 0, nullptr}, + {PPCASM_OP_CRAND, 0, 19, 257, OPC_NONE, OP_FORM_XL_CR, FLG_DEFAULT, 0, 0, nullptr}, + {PPCASM_OP_CREQV, 0, 19, 289, OPC_NONE, OP_FORM_XL_CR, FLG_DEFAULT, 0, 0, nullptr}, + {PPCASM_OP_CRORC, 0, 19, 417, OPC_NONE, OP_FORM_XL_CR, FLG_DEFAULT, 0, 0, nullptr}, + {PPCASM_OP_CROR, 0, 19, 449, OPC_NONE, OP_FORM_XL_CR, FLG_DEFAULT, 0, 0, nullptr}, + + {PPCASM_OP_BCTR, 0, 19, 528, OPC_NONE, OP_FORM_NO_OPERAND, FLG_DEFAULT, C_MASK_BO | C_MASK_LK, C_BIT_BO_ALWAYS, nullptr}, + {PPCASM_OP_BCTRL, 0, 19, 528, OPC_NONE, OP_FORM_NO_OPERAND, FLG_DEFAULT, C_MASK_BO | C_MASK_LK, C_BIT_BO_ALWAYS | C_BIT_LK, nullptr}, + + {PPCASM_OP_RLWIMI, 0, 20, OPC_NONE, OPC_NONE, OP_FORM_RLWINM, FLG_DEFAULT, C_MASK_RC, 0, nullptr}, + {PPCASM_OP_RLWIMI_, 0, 20, OPC_NONE, OPC_NONE, OP_FORM_RLWINM, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr}, + {PPCASM_OP_RLWINM, 0, 21, OPC_NONE, OPC_NONE, OP_FORM_RLWINM, FLG_DEFAULT, C_MASK_RC, 0, nullptr}, + {PPCASM_OP_RLWINM_, 0, 21, OPC_NONE, OPC_NONE, OP_FORM_RLWINM, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr}, + + {PPCASM_OP_ROTLWI, 2, 21, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_IMM(11, 5, false)}, {EncodedConstraint_FixedU5(6, 0), EncodedConstraint_FixedU5(1, 31)}}, + {PPCASM_OP_ROTLWI_, 2, 21, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_IMM(11, 5, false)}, {EncodedConstraint_FixedU5(6, 0), EncodedConstraint_FixedU5(1, 31)}}, + + // rotrwi RA, RS, n -> rlwinm RA, RS, 32-n, 0, 31 + // only assembler + {PPCASM_OP_ROTRWI, 0, 21, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_U5Reverse(11, 32)}, {EncodedConstraint_FixedU5(6, 0), EncodedConstraint_FixedU5(1, 31)}}, + {PPCASM_OP_ROTRWI_, 0, 21, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_U5Reverse(11, 32)}, {EncodedConstraint_FixedU5(6, 0), EncodedConstraint_FixedU5(1, 31)}}, + + {PPCASM_OP_EXTLWI, 1, 21, OPC_NONE, OPC_NONE, OP_FORM_RLWINM_EXTENDED, FLG_DEFAULT, C_MASK_RC, 0, ppcOp_extraCheck_extlwi}, + {PPCASM_OP_EXTLWI_, 1, 21, OPC_NONE, OPC_NONE, OP_FORM_RLWINM_EXTENDED, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, ppcOp_extraCheck_extlwi}, + {PPCASM_OP_EXTRWI, 1, 21, OPC_NONE, OPC_NONE, OP_FORM_RLWINM_EXTENDED, FLG_DEFAULT, C_MASK_RC, 0, ppcOp_extraCheck_extrwi}, + {PPCASM_OP_EXTRWI_, 1, 21, OPC_NONE, OPC_NONE, OP_FORM_RLWINM_EXTENDED, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, ppcOp_extraCheck_extrwi}, + + {PPCASM_OP_SLWI, 2, 21, OPC_NONE, OPC_NONE, OP_FORM_RLWINM_EXTENDED, FLG_DEFAULT, C_MASK_RC, 0, ppcOp_extraCheck_slwi}, + {PPCASM_OP_SLWI_, 2, 21, OPC_NONE, OPC_NONE, OP_FORM_RLWINM_EXTENDED, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, ppcOp_extraCheck_slwi}, + {PPCASM_OP_SRWI, 2, 21, OPC_NONE, OPC_NONE, OP_FORM_RLWINM_EXTENDED, FLG_DEFAULT, C_MASK_RC, 0, ppcOp_extraCheck_srwi}, + {PPCASM_OP_SRWI_, 2, 21, OPC_NONE, OPC_NONE, OP_FORM_RLWINM_EXTENDED, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, ppcOp_extraCheck_srwi}, + + {PPCASM_OP_CLRLWI, 2, 21, OPC_NONE, OPC_NONE, OP_FORM_RLWINM_EXTENDED, FLG_DEFAULT, C_MASK_RC, 0, ppcOp_extraCheck_clrlwi}, + {PPCASM_OP_CLRLWI_, 2, 21, OPC_NONE, OPC_NONE, OP_FORM_RLWINM_EXTENDED, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, ppcOp_extraCheck_clrlwi}, + {PPCASM_OP_CLRRWI, 2, 21, OPC_NONE, OPC_NONE, OP_FORM_RLWINM_EXTENDED, FLG_DEFAULT, C_MASK_RC, 0, ppcOp_extraCheck_clrrwi}, + {PPCASM_OP_CLRRWI_, 2, 21, OPC_NONE, OPC_NONE, OP_FORM_RLWINM_EXTENDED, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, ppcOp_extraCheck_clrrwi}, + + {PPCASM_OP_RLWNM, 0, 23, OPC_NONE, OPC_NONE, OP_FORM_RLWNM, FLG_DEFAULT, C_MASK_RC, 0, nullptr}, + {PPCASM_OP_RLWNM_, 0, 23, OPC_NONE, OPC_NONE, OP_FORM_RLWNM, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr}, + + {PPCASM_OP_ROTLW, 0, 23, OPC_NONE, OPC_NONE, OP_FORM_RLWNM_EXTENDED, FLG_DEFAULT, C_MASK_RC, 0, ppcOp_extraCheck_rotlw}, + {PPCASM_OP_ROTLW_, 0, 23, OPC_NONE, OPC_NONE, OP_FORM_RLWNM_EXTENDED, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, ppcOp_extraCheck_rotlw}, + + + {PPCASM_OP_ORI, 0, 24, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_IMM(0, 16, false)}}, + {PPCASM_OP_NOP, 1, 24, OPC_NONE, OPC_NONE, OP_FORM_NO_OPERAND, FLG_DEFAULT, 0x3FFFFFF, 0, nullptr}, // ORI r0, r0, 0 -> NOP + {PPCASM_OP_ORIS, 0, 25, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_IMM(0, 16, true, false, true)}}, + + {PPCASM_OP_XORI, 0, 26, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_IMM(0, 16, false)} }, + {PPCASM_OP_XORIS, 0, 27, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_IMM(0, 16, true, false, true)} }, + + {PPCASM_OP_ANDI_, 0, 28, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_IMM(0, 16, false)} }, + {PPCASM_OP_ANDIS_, 0, 29, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_IMM(0, 16, true, false, true)} }, + + // group 31 + {PPCASM_OP_CMPW, 0, 31, 0, OPC_NONE, OP_FORM_OP3_A_CMP, FLG_DEFAULT, 0, 0, nullptr}, + + {PPCASM_OP_SUBFC, 1, 31, 8, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_GPR(11)} }, + {PPCASM_OP_SUBFC_, 1, 31, 8, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_GPR(11)} }, + + {PPCASM_OP_MULHWU, 0, 31, 11, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_GPR(11)} }, + {PPCASM_OP_MULHWU_, 0, 31, 11, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_GPR(11)} }, + + {PPCASM_OP_CMPLW, 0, 31, 32, OPC_NONE, OP_FORM_OP3_A_CMP, FLG_DEFAULT, 0, 0, nullptr}, + + {PPCASM_OP_SUBF, 1, 31, 40, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_GPR(11)} }, + {PPCASM_OP_SUBF_, 1, 31, 40, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_GPR(11)} }, + {PPCASM_OP_SUB, 0, 31, 40, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(11), EncodedOperand_GPR(16)} }, + {PPCASM_OP_SUB_, 0, 31, 40, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(11), EncodedOperand_GPR(16)} }, + + {PPCASM_OP_MULHW, 0, 31, 75, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_GPR(11)} }, + {PPCASM_OP_MULHW_, 0, 31, 75, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_GPR(11)} }, + + {PPCASM_OP_NEG, 0, 31, 104, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC | C_MASK_OE, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16)}}, + {PPCASM_OP_NEG_, 0, 31, 104, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC | C_MASK_OE, C_BIT_RC, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16)} }, + + {PPCASM_OP_NOR, 0, 31, 124, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_GPR(11)} }, + {PPCASM_OP_NOR_, 0, 31, 124, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_GPR(11)} }, + + // alias for NOR where rA == rB + {PPCASM_OP_NOT, 1, 31, 124, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(11)}, {EncodedConstraint_MirrorRegister(11, 21)}}, + {PPCASM_OP_NOT_, 1, 31, 124, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(11)}, {EncodedConstraint_MirrorRegister(11, 21)} }, + + {PPCASM_OP_SUBFE, 1, 31, 136, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_GPR(11)} }, + {PPCASM_OP_SUBFE_, 1, 31, 136, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_GPR(11)} }, + + {PPCASM_OP_MULLW, 0, 31, 235, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_GPR(11)} }, + {PPCASM_OP_MULLW_, 0, 31, 235, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_GPR(11)} }, + + {PPCASM_OP_MFSPR, 0, 31, 339, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_SPR()} }, + {PPCASM_OP_MTSPR, 0, 31, 467, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_SPR(), EncodedOperand_GPR(21)} }, + + {PPCASM_OP_MFLR, 0, 31, 339, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21)}, {EncodedConstraint_FixedSPR(8)}}, + {PPCASM_OP_MTLR, 0, 31, 467, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21)}, {EncodedConstraint_FixedSPR(8)} }, + {PPCASM_OP_MFCTR, 0, 31, 339, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21)}, {EncodedConstraint_FixedSPR(9)} }, + {PPCASM_OP_MTCTR, 0, 31, 467, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21)}, {EncodedConstraint_FixedSPR(9)} }, + + {PPCASM_OP_ADD, 0, 31, 266, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_GPR(11)} }, + {PPCASM_OP_ADD_, 0, 31, 266, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_GPR(11)} }, + + {PPCASM_OP_SLW, 0, 31, 24, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_GPR(11)} }, + {PPCASM_OP_SLW_, 0, 31, 24, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_GPR(11)} }, + {PPCASM_OP_SRW, 0, 31, 536, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_GPR(11)} }, + {PPCASM_OP_SRW_, 0, 31, 536, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_GPR(11)} }, + {PPCASM_OP_SRAW, 0, 31, 792, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_GPR(11)} }, + {PPCASM_OP_SRAW_, 0, 31, 792, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_GPR(11)} }, + + {PPCASM_OP_AND, 0, 31, 28, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_GPR(11)} }, + {PPCASM_OP_AND_, 0, 31, 28, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_GPR(11)} }, + + {PPCASM_OP_ANDC, 0, 31, 60, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_GPR(11)} }, + {PPCASM_OP_ANDC_, 0, 31, 60, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_GPR(11)} }, + + {PPCASM_OP_XOR, 0, 31, 316, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_GPR(11)} }, + + {PPCASM_OP_ORC, 0, 31, 412, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_GPR(11)} }, // has RC? + {PPCASM_OP_OR, 0, 31, 444, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_GPR(11)} }, + {PPCASM_OP_OR_, 0, 31, 444, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21), EncodedOperand_GPR(11)} }, + {PPCASM_OP_MR, 1, 31, 444, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(11)}, {EncodedConstraint_MirrorRegister(11, 21)} }, + {PPCASM_OP_MR_, 1, 31, 444, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(11)}, {EncodedConstraint_MirrorRegister(11, 21)} }, + + {PPCASM_OP_DIVWU, 0, 31, 459, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_GPR(11)} }, + {PPCASM_OP_DIVWU_, 0, 31, 459, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_GPR(11)} }, + + {PPCASM_OP_DIVW, 0, 31, 491, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_GPR(11)} }, + {PPCASM_OP_DIVW_, 0, 31, 491, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_GPR(11)} }, + + {PPCASM_OP_SRAWI, 0, 31, 824, OPC_NONE, OP_FORM_OP3_A_IMM, FLG_DEFAULT, C_MASK_RC, 0, nullptr}, + {PPCASM_OP_SRAWI_, 0, 31, 824, OPC_NONE, OP_FORM_OP3_A_IMM, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr}, + + {PPCASM_OP_CNTLZW, 0, 31, 26, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21)} }, + {PPCASM_OP_EXTSB, 0, 31, 954, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21)} }, + {PPCASM_OP_EXTSH, 0, 31, 922, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, 0, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21)} }, + + {PPCASM_OP_CNTLZW_, 0, 31, 26, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21)} }, + {PPCASM_OP_EXTSB_, 0, 31, 954, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21)} }, + {PPCASM_OP_EXTSH_, 0, 31, 922, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(16), EncodedOperand_GPR(21)} }, + + {PPCASM_OP_LWZX, 0, 31, 23, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, + {PPCASM_OP_LWZUX, 0, 31, 55, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, + {PPCASM_OP_LBZX, 0, 31, 87, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, + {PPCASM_OP_LBZUX, 0, 31, 119, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, + {PPCASM_OP_LHZX, 0, 31, 279, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, + {PPCASM_OP_LHZUX, 0, 31, 311, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, + {PPCASM_OP_LHAX, 0, 31, 343, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, + {PPCASM_OP_LHAUX, 0, 31, 375, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, + {PPCASM_OP_STWX, 0, 31, 151, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, + {PPCASM_OP_STWUX, 0, 31, 183, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, + {PPCASM_OP_STHX, 0, 31, 407, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, + {PPCASM_OP_STHUX, 0, 31, 439, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, + {PPCASM_OP_STBX, 0, 31, 215, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, + {PPCASM_OP_STBUX, 0, 31, 247, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, + {PPCASM_OP_STWBRX, 0, 31, 662, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, + {PPCASM_OP_STHBRX, 0, 31, 918, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, + + {PPCASM_OP_LFSX, 0, 31, 535, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, + {PPCASM_OP_LFSUX, 0, 31, 567, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, + {PPCASM_OP_STFSX, 0, 31, 663, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, + {PPCASM_OP_STFSUX, 0, 31, 695, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, + + {PPCASM_OP_STSWI, 0, 31, 725, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR(16), EncodedOperand_IMM(11, 5, false)} }, + + {PPCASM_OP_LWARX, 0, 31, 20, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, + {PPCASM_OP_STWCX_, 0, 31, 150, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, C_MASK_RC, C_BIT_RC, nullptr, {EncodedOperand_GPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, + + {PPCASM_OP_LFDX, 0, 31, 599, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, + {PPCASM_OP_LFDUX, 0, 31, 631, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, + {PPCASM_OP_STFDX, 0, 31, 727, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, + {PPCASM_OP_STFDUX, 0, 31, 759, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, + + {PPCASM_OP_STFIWX, 0, 31, 983, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_GPR<true>(16), EncodedOperand_GPR(11)} }, + + // load/store + {PPCASM_OP_LWZ, 0, 32, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_MemLoc()} }, + {PPCASM_OP_LWZU, 0, 33, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_MemLoc()} }, + {PPCASM_OP_LBZ, 0, 34, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_MemLoc()} }, + {PPCASM_OP_LBZU, 0, 35, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_MemLoc()} }, + {PPCASM_OP_STW, 0, 36, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_MemLoc()} }, + {PPCASM_OP_STWU, 0, 37, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_MemLoc()} }, + {PPCASM_OP_STB, 0, 38, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_MemLoc()} }, + {PPCASM_OP_STBU, 0, 39, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_MemLoc()} }, + {PPCASM_OP_LHZ, 0, 40, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_MemLoc()} }, + {PPCASM_OP_LHZU, 0, 41, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_MemLoc()} }, + {PPCASM_OP_LHA, 0, 42, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_MemLoc()} }, + {PPCASM_OP_LHAU, 0, 43, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_MemLoc()} }, + {PPCASM_OP_STH, 0, 44, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_MemLoc()} }, + {PPCASM_OP_STHU, 0, 45, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_MemLoc()} }, + {PPCASM_OP_LMW, 0, 46, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_MemLoc()} }, + {PPCASM_OP_STMW, 0, 47, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_GPR(21), EncodedOperand_MemLoc()} }, + {PPCASM_OP_LFS, 0, 48, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_MemLoc()} }, + {PPCASM_OP_LFSU, 0, 49, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_MemLoc()} }, + {PPCASM_OP_LFD, 0, 50, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_MemLoc()} }, + {PPCASM_OP_LFDU, 0, 51, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_MemLoc()} }, + {PPCASM_OP_STFS, 0, 52, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_MemLoc()} }, + {PPCASM_OP_STFSU, 0, 53, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_MemLoc()} }, + {PPCASM_OP_STFD, 0, 54, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_MemLoc()} }, + {PPCASM_OP_STFDU, 0, 55, OPC_NONE, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_MemLoc()} }, + + // FP + {PPCASM_OP_FDIVS, 0, 59, 18, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, { EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(11) } }, + {PPCASM_OP_FSUBS, 0, 59, 20, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, { EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(11) } }, + {PPCASM_OP_FADDS, 0, 59, 21, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, { EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(11) } }, + {PPCASM_OP_FMULS, 0, 59, 25, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, { EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(6) } }, + {PPCASM_OP_FMSUBS, 0, 59, 28, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, { EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(6), EncodedOperand_FPR(11) } }, + {PPCASM_OP_FMADDS, 0, 59, 29, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, { EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(6), EncodedOperand_FPR(11) } }, + {PPCASM_OP_FNMSUBS, 0, 59, 30, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, { EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(6), EncodedOperand_FPR(11) } }, + {PPCASM_OP_FNMADDS, 0, 59, 31, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, { EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(6), EncodedOperand_FPR(11) } }, + + {PPCASM_OP_FCMPU, 0, 63, 0, OPC_NONE, OP_FORM_X_FP_CMP, FLG_DEFAULT, 0, 0, nullptr }, + {PPCASM_OP_FCTIWZ, 0, 63, 15, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, {EncodedOperand_FPR(21), EncodedOperand_FPR(11)}}, + {PPCASM_OP_FDIV, 0, 63, 18, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, { EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(11) } }, + {PPCASM_OP_FSUB, 0, 63, 20, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, { EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(11) } }, + {PPCASM_OP_FADD, 0, 63, 21, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, { EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(11) } }, + {PPCASM_OP_FMUL, 0, 63, 25, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, { EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(6) } }, + {PPCASM_OP_FRSQRTE, 0, 63, 26, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, { EncodedOperand_FPR(21), EncodedOperand_FPR(11) } }, + {PPCASM_OP_FMSUB, 0, 63, 28, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, { EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(6), EncodedOperand_FPR(11) } }, + {PPCASM_OP_FMADD, 0, 63, 29, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, { EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(6), EncodedOperand_FPR(11) } }, + {PPCASM_OP_FNMSUB, 0, 63, 30, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, { EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(6), EncodedOperand_FPR(11) } }, + {PPCASM_OP_FNMADD, 0, 63, 31, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, { EncodedOperand_FPR(21), EncodedOperand_FPR(16), EncodedOperand_FPR(6), EncodedOperand_FPR(11) } }, + {PPCASM_OP_FRSP, 0, 63, 12, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, { EncodedOperand_FPR(21), EncodedOperand_FPR(11) } }, + + // 63 extended opcode + {PPCASM_OP_FCMPO, 0, 63, 32|OPC_EXTENDED_BIT, OPC_NONE, OP_FORM_X_FP_CMP, FLG_DEFAULT, 0, 0, nullptr }, + {PPCASM_OP_FNEG, 0, 63, 40|OPC_EXTENDED_BIT, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, { EncodedOperand_FPR(21), EncodedOperand_FPR(11) } }, + {PPCASM_OP_FMR, 0, 63, 72|OPC_EXTENDED_BIT, OPC_NONE, OP_FORM_DYNAMIC, FLG_DEFAULT, 0, 0, nullptr, { EncodedOperand_FPR(21), EncodedOperand_FPR(11) } }, + +}; + +#define opcMask_setBits(__index, __bitCount, __value) opcodeMask |= (((1<<__bitCount)-1)<<(31-__index)); opcodeBits |= ((__value)<<(31-__index)); + +void ppcAssembler_buildOpcMask(PPCInstructionDef* iDef, uint32& maskOut, uint32& bitsOut) +{ + uint32 opc0 = iDef->opc0; + uint32 opc1 = iDef->opc1; + uint32 opc2 = iDef->opc2; + + uint32 opcodeMask = 0x3F<<26; + uint32 opcodeBits = opc0<<26; + + // handle groups + if (opc0 == 31) + { + // group 31 + opcMask_setBits(30, 10, opc1); + } + else if (opc0 == 19) + { + // group 19 + opcMask_setBits(30, 10, opc1); + } + else if (opc0 == 4) + { + // group 4 (paired single) + opcMask_setBits(30, 5, opc1); + //opc1 = PPC_getBits(opcode, 30, 5); + if (opc1 == 16) + { + // group 4->16 (ps_merge) + opcMask_setBits(25, 5, opc2); + //opc2 = PPC_getBits(opcode, 25, 5); + } + } + else if (opc0 == 59) + { + // group 59 (FP float) + opcMask_setBits(30, 5, opc1); + //opc1 = PPC_getBits(opcode, 30, 5); + } + else if (opc0 == 63) + { + // group 63 (FP double) + if ((opc1&OPC_EXTENDED_BIT) != 0) + { + opcMask_setBits(30, 10, (opc1&~OPC_EXTENDED_BIT)); + } + else + { + opcMask_setBits(30, 5, opc1); + } + } + maskOut = opcodeMask; + bitsOut = opcodeBits; +} + +// given an internal instruction operand index, return the text encoding index +sint32 _getOpIndex(PPCInstructionDef* iDef, sint32 operandIndex) +{ + if ((operandIndex == 0 || operandIndex == 1) && (iDef->flags & FLG_SWAP_OP0_OP1) != 0) + operandIndex ^= 1; + if ((iDef->flags & FLG_SWAP_OP1_OP2) != 0) + { + if (operandIndex == 1) operandIndex = 2; + else if (operandIndex == 2) operandIndex = 1; + } + if ((iDef->flags & FLG_SWAP_OP2_OP3) != 0) + { + if (operandIndex == 2) operandIndex = 3; + else if (operandIndex == 3) operandIndex = 2; + } + return operandIndex; +} + +// given an internal instruction operand index, return the operand index for the text encoding. Returns -1 when operand is not present +// replaces _getOpIndex +//sint32 _operandInternalToTextIndex(ppcInstructionDef_t* iDef, sint32 operandIndex) +//{ +// if ((operandIndex == 0 || operandIndex == 1) && (iDef->flags & FLG_SWAP_OP0_OP1) != 0) +// operandIndex ^= 1; +// if ((operandIndex == 2 || operandIndex == 3) && (iDef->flags & FLG_SWAP_OP2_OP3) != 0) +// operandIndex ^= 1; +// sint32 outputIndex = operandIndex; +// if ((iDef->flags & (1 << outputIndex))) +// return -1; +// for (sint32 i = 0; i < operandIndex; i++) +// { +// if ((iDef->flags & (1 << i))) +// outputIndex--; +// } +// return outputIndex; +//} +// +//int _operandTextToInternalIndex(ppcInstructionDef_t* iDef, sint32 operandIndex) +//{ +// for (sint32 i = 0; i < 8; i++) +// { +// if (_operandInternalToTextIndex(iDef, i) == operandIndex) +// return i; +// } +// return -1; +//} + +void _disasmOpGPR(PPCDisassembledInstruction* disInstr, PPCInstructionDef* iDef, uint32 operandIndex, sint32 regIndex) +{ + if ((iDef->flags & (FLG_SKIP_OP0 << operandIndex)) != 0) + return; + disInstr->operandMask |= (1 << operandIndex); + sint32 opIdx = _getOpIndex(iDef, operandIndex); + disInstr->operand[opIdx].type = PPCASM_OPERAND_TYPE_GPR; + disInstr->operand[opIdx].registerIndex = regIndex; +} + +void ppcAssembler_disassemble(uint32 virtualAddress, uint32 opcode, PPCDisassembledInstruction* disInstr) +{ + auto makeOpCRBit = [&](size_t opIndex, uint8 regIndex) + { + disInstr->operandMask |= (1 << opIndex); + disInstr->operand[opIndex].type = PPCASM_OPERAND_TYPE_CR_BIT; + disInstr->operand[opIndex].registerIndex = regIndex; + }; + + auto makeOpFPR = [&](size_t opIndex, uint8 regIndex) + { + disInstr->operandMask |= (1 << opIndex); + disInstr->operand[opIndex].type = PPCASM_OPERAND_TYPE_FPR; + disInstr->operand[opIndex].registerIndex = regIndex; + }; + + memset(disInstr, 0, sizeof(PPCDisassembledInstruction)); + // find match in table + sint32 bestMatchIndex = -1; + uint8 bestMatchPriority = 0; + for (sint32 i = 0; i < sizeof(ppcInstructionTable) / sizeof(PPCInstructionDef); i++) + { + PPCInstructionDef* iDef = ppcInstructionTable + i; + // check opcode + uint32 opcMask; + uint32 opcBits; + ppcAssembler_buildOpcMask(iDef, opcMask, opcBits); + if ((opcode&opcMask) != opcBits) + continue; + // check bits + if((opcode&iDef->maskBits) != iDef->compareBits) + continue; + // check special condition + if(iDef->extraCheck && iDef->extraCheck(opcode) == false ) + continue; + // check priority + if(iDef->priority < bestMatchPriority) + continue; + // check constraints + bool allConstraintsMatch = true; + for (auto& it : iDef->constraints) + { + if (!std::visit([&](auto&& op) -> bool { + return op.DisassembleCheckConstraint(disInstr, iDef, opcode); + }, it)) + { + allConstraintsMatch = false; + break; + } + } + if (!allConstraintsMatch) + continue; + // select entry + bestMatchIndex = i; + bestMatchPriority = iDef->priority; + } + // if we have found an entry, parse operand data + if (bestMatchIndex >= 0) + { + PPCInstructionDef* iDef = ppcInstructionTable + bestMatchIndex; + // setup general instruction info + disInstr->ppcAsmCode = iDef->ppcAsmOp; + // parse operands + if (iDef->instructionForm == OP_FORM_DYNAMIC) + { + disInstr->operandMask = 0; + for (size_t i = 0; i < iDef->encodedOperands.size(); i++) + { + std::visit([&](auto&& op) { + op.DisassembleOperand(disInstr, iDef, opcode, i); + }, iDef->encodedOperands[i]); + } + } + else if (iDef->instructionForm == OP_FORM_OP3_A_CMP) + { + sint32 rS, rA, rB; + PPC_OPC_TEMPL_X(opcode, rS, rA, rB); + disInstr->operandMask = (rS!=0?operand0Bit:0); + disInstr->operand[0].type = PPCASM_OPERAND_TYPE_CR; + disInstr->operand[0].registerIndex = rS >> 2; + if((rS & 3) != 0) + cemuLog_log(LogType::Force, "[PPC-Disassembler] Unexpected CR encoding for instruction 0x{0:08x}", opcode); + _disasmOpGPR(disInstr, iDef, 1, rA); + _disasmOpGPR(disInstr, iDef, 2, rB); + } + else if (iDef->instructionForm == OP_FORM_OP3_A_IMM) + { + sint32 rS, rA, SH; + PPC_OPC_TEMPL_X(opcode, rS, rA, SH); + disInstr->operandMask = operand0Bit | operand1Bit | operand2Bit; + sint32 op0Idx = _getOpIndex(iDef, 0); + sint32 op1Idx = _getOpIndex(iDef, 1); + sint32 op2Idx = _getOpIndex(iDef, 2); + // operand 0 + disInstr->operand[op0Idx].type = PPCASM_OPERAND_TYPE_GPR; + disInstr->operand[op0Idx].registerIndex = rA; + // operand 1 + disInstr->operand[op1Idx].type = PPCASM_OPERAND_TYPE_GPR; + disInstr->operand[op1Idx].registerIndex = rS; + // operand 2 + disInstr->operand[op2Idx].type = PPCASM_OPERAND_TYPE_IMM; + disInstr->operand[op2Idx].immS32 = SH; + disInstr->operand[op2Idx].immWidth = 5; + disInstr->operand[op2Idx].isSignedImm = false; + } + else if (iDef->instructionForm == OP_FORM_BRANCH_S16) + { + uint32 BO, BI, dest; + PPC_OPC_TEMPL_B(opcode, BO, BI, dest); + uint8 crIndex = BI >> 2; + if ((opcode & PPC_OPC_AA) == 0) + dest += virtualAddress; + if (iDef->ppcAsmOp == PPCASM_OP_BC) + { + // generic conditional branch of form <BO>, CR+LT/GT/EQ/SO, <dst> + disInstr->operandMask = operand0Bit | operand1Bit | operand2Bit; + // operand 0 + disInstr->operand[0].type = PPCASM_OPERAND_TYPE_CIMM; + disInstr->operand[0].registerIndex = BO; + // operand 1 + disInstr->operand[1].type = PPCASM_OPERAND_TYPE_CR_BIT; + disInstr->operand[1].registerIndex = BI; + // operand 2 + disInstr->operand[2].type = PPCASM_OPERAND_TYPE_CIMM; + disInstr->operand[2].immU32 = dest; + // hint bit + disInstr->branchHintBitSet = (BO & 1) != 0; + } + else + { + if (crIndex != 0) + { + disInstr->operandMask = operand0Bit | operand1Bit; + // operand 0 + disInstr->operand[0].type = PPCASM_OPERAND_TYPE_CR; + disInstr->operand[0].registerIndex = crIndex; + // operand 1 + disInstr->operand[1].type = PPCASM_OPERAND_TYPE_CIMM; + disInstr->operand[1].immU32 = dest; + } + else + { + disInstr->operandMask = operand0Bit; + disInstr->operand[0].type = PPCASM_OPERAND_TYPE_CIMM; + disInstr->operand[0].immU32 = dest; + } + // hint bit + disInstr->branchHintBitSet = (BO & 1) != 0; + } + } + else if (iDef->instructionForm == OP_FORM_BRANCH_S24) + { + uint32 dest; + PPC_OPC_TEMPL_I(opcode, dest); + if ((opcode & PPC_OPC_AA) == 0) + dest += virtualAddress; + disInstr->operandMask = operand0Bit; + // operand 0 + disInstr->operand[0].type = PPCASM_OPERAND_TYPE_CIMM; + disInstr->operand[0].immU32 = dest; + } + else if (iDef->instructionForm == OP_FORM_OP2_D_HSIMM) + { + sint32 rD, rA; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); + disInstr->operandMask = operand0Bit | operand1Bit; + // operand 0 + disInstr->operand[0].type = PPCASM_OPERAND_TYPE_GPR; + disInstr->operand[0].registerIndex = rD; + // operand 1 + disInstr->operand[1].type = PPCASM_OPERAND_TYPE_IMM; + disInstr->operand[1].immS32 = imm & 0xFFFF; + disInstr->operand[1].immWidth = 16; + disInstr->operand[1].isSignedImm = true; + } + else if (iDef->instructionForm == OP_FORM_CMP_SIMM) + { + uint32 cr; + sint32 rA; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(opcode, cr, rA, imm); + cr /= 4; + // rS is cr + disInstr->operandMask = operand1Bit | operand2Bit; + if (cr != 0) + disInstr->operandMask |= operand0Bit; + // operand 0 + disInstr->operand[0].type = PPCASM_OPERAND_TYPE_CR; + disInstr->operand[0].registerIndex = cr; + // operand 1 + disInstr->operand[1].type = PPCASM_OPERAND_TYPE_GPR; + disInstr->operand[1].registerIndex = rA; + // operand 2 + disInstr->operand[2].type = PPCASM_OPERAND_TYPE_IMM; + disInstr->operand[2].immS32 = imm; + disInstr->operand[2].immWidth = 16; + disInstr->operand[2].isSignedImm = true; + } + else if (iDef->instructionForm == OP_FORM_NO_OPERAND) + { + disInstr->operandMask = 0; + } + else if (iDef->instructionForm == OP_FORM_X_FP_CMP) + { + sint32 rA, crIndex, rB; + PPC_OPC_TEMPL_X(opcode, crIndex, rA, rB); + cemu_assert_debug((crIndex % 4) == 0); + crIndex /= 4; + disInstr->operandMask = operand0Bit | operand1Bit | operand2Bit; + // operand 0 + disInstr->operand[0].type = PPCASM_OPERAND_TYPE_CR; + disInstr->operand[0].registerIndex = crIndex; + // operand 1 + disInstr->operand[1].type = PPCASM_OPERAND_TYPE_FPR; + disInstr->operand[1].registerIndex = rA; + // operand 2 + disInstr->operand[2].type = PPCASM_OPERAND_TYPE_FPR; + disInstr->operand[2].registerIndex = rB; + } + else if (iDef->instructionForm == OP_FORM_RLWINM) + { + sint32 rS, rA, SH, MB, ME; + PPC_OPC_TEMPL_M(opcode, rS, rA, SH, MB, ME); + disInstr->operandMask = operand0Bit | operand1Bit | operand2Bit | operand3Bit | operand4Bit; + // operand 0 + disInstr->operand[0].type = PPCASM_OPERAND_TYPE_GPR; + disInstr->operand[0].registerIndex = rA; + // operand 1 + disInstr->operand[1].type = PPCASM_OPERAND_TYPE_GPR; + disInstr->operand[1].registerIndex = rS; + // operand 2 + disInstr->operand[2].type = PPCASM_OPERAND_TYPE_IMM; + disInstr->operand[2].immS32 = SH; + disInstr->operand[2].immWidth = 8; + // operand 3 + disInstr->operand[3].type = PPCASM_OPERAND_TYPE_IMM; + disInstr->operand[3].immS32 = MB; + disInstr->operand[3].immWidth = 8; + // operand 4 + disInstr->operand[4].type = PPCASM_OPERAND_TYPE_IMM; + disInstr->operand[4].immS32 = ME; + disInstr->operand[4].immWidth = 8; + } + else if (iDef->instructionForm == OP_FORM_RLWINM_EXTENDED) + { + sint32 rS, rA, SH, MB, ME; + PPC_OPC_TEMPL_M(opcode, rS, rA, SH, MB, ME); + + // operand 0 + disInstr->operand[0].type = PPCASM_OPERAND_TYPE_GPR; + disInstr->operand[0].registerIndex = rA; + // operand 1 + disInstr->operand[1].type = PPCASM_OPERAND_TYPE_GPR; + disInstr->operand[1].registerIndex = rS; + + if (iDef->ppcAsmOp == PPCASM_OP_EXTLWI || iDef->ppcAsmOp == PPCASM_OP_EXTLWI_) + { + disInstr->operandMask = operand0Bit | operand1Bit | operand2Bit | operand3Bit; + // operand 2 + disInstr->operand[2].type = PPCASM_OPERAND_TYPE_IMM; + disInstr->operand[2].immS32 = ME + 1; + disInstr->operand[2].immWidth = 8; + // operand 3 + disInstr->operand[3].type = PPCASM_OPERAND_TYPE_IMM; + disInstr->operand[3].immS32 = SH; + disInstr->operand[3].immWidth = 8; + } + else if (iDef->ppcAsmOp == PPCASM_OP_EXTRWI || iDef->ppcAsmOp == PPCASM_OP_EXTRWI_) + { + disInstr->operandMask = operand0Bit | operand1Bit | operand2Bit | operand3Bit; + sint8 n = 32 - MB; + sint8 b = SH - n; + // operand 2 + disInstr->operand[2].type = PPCASM_OPERAND_TYPE_IMM; + disInstr->operand[2].immS32 = n; + disInstr->operand[2].immWidth = 8; + // operand 3 + disInstr->operand[3].type = PPCASM_OPERAND_TYPE_IMM; + disInstr->operand[3].immS32 = b; + disInstr->operand[3].immWidth = 8; + } + else if (iDef->ppcAsmOp == PPCASM_OP_SLWI || iDef->ppcAsmOp == PPCASM_OP_SLWI_) + { + disInstr->operandMask = operand0Bit | operand1Bit | operand2Bit; + sint8 n = SH; + // operand 2 + disInstr->operand[2].type = PPCASM_OPERAND_TYPE_IMM; + disInstr->operand[2].immS32 = n; + disInstr->operand[2].immWidth = 8; + } + else if (iDef->ppcAsmOp == PPCASM_OP_SRWI || iDef->ppcAsmOp == PPCASM_OP_SRWI_) + { + disInstr->operandMask = operand0Bit | operand1Bit | operand2Bit; + sint8 n = 32 - SH; + // operand 2 + disInstr->operand[2].type = PPCASM_OPERAND_TYPE_IMM; + disInstr->operand[2].immS32 = n; + disInstr->operand[2].immWidth = 8; + } + else if (iDef->ppcAsmOp == PPCASM_OP_CLRLWI || iDef->ppcAsmOp == PPCASM_OP_CLRLWI_) + { + disInstr->operandMask = operand0Bit | operand1Bit | operand2Bit; + sint8 n = MB; + // operand 2 + disInstr->operand[2].type = PPCASM_OPERAND_TYPE_IMM; + disInstr->operand[2].immS32 = n; + disInstr->operand[2].immWidth = 8; + } + else if (iDef->ppcAsmOp == PPCASM_OP_CLRRWI || iDef->ppcAsmOp == PPCASM_OP_CLRRWI_) + { + disInstr->operandMask = operand0Bit | operand1Bit | operand2Bit; + sint8 n = 31 - ME; + // operand 2 + disInstr->operand[2].type = PPCASM_OPERAND_TYPE_IMM; + disInstr->operand[2].immS32 = n; + disInstr->operand[2].immWidth = 8; + } + else + { + cemu_assert_debug(false); + } + } + else if (iDef->instructionForm == OP_FORM_RLWNM) + { + sint32 rS, rA, rB, MB, ME; + PPC_OPC_TEMPL_M(opcode, rS, rA, rB, MB, ME); + disInstr->operandMask = operand0Bit | operand1Bit | operand2Bit | operand3Bit | operand4Bit; + // operand 0 + disInstr->operand[0].type = PPCASM_OPERAND_TYPE_GPR; + disInstr->operand[0].registerIndex = rA; + // operand 1 + disInstr->operand[1].type = PPCASM_OPERAND_TYPE_GPR; + disInstr->operand[1].registerIndex = rS; + // operand 2 + disInstr->operand[2].type = PPCASM_OPERAND_TYPE_GPR; + disInstr->operand[2].registerIndex = rB; + // operand 3 + disInstr->operand[3].type = PPCASM_OPERAND_TYPE_IMM; + disInstr->operand[3].immS32 = MB; + disInstr->operand[3].immWidth = 8; + // operand 4 + disInstr->operand[4].type = PPCASM_OPERAND_TYPE_IMM; + disInstr->operand[4].immS32 = ME; + disInstr->operand[4].immWidth = 8; + } + else if (iDef->instructionForm == OP_FORM_RLWNM_EXTENDED) + { + sint32 rS, rA, rB, MB, ME; + PPC_OPC_TEMPL_M(opcode, rS, rA, rB, MB, ME); + // operand 0 + disInstr->operand[0].type = PPCASM_OPERAND_TYPE_GPR; + disInstr->operand[0].registerIndex = rA; + // operand 1 + disInstr->operand[1].type = PPCASM_OPERAND_TYPE_GPR; + disInstr->operand[1].registerIndex = rS; + // operand 2 + disInstr->operand[2].type = PPCASM_OPERAND_TYPE_GPR; + disInstr->operand[2].registerIndex = rB; + + if (iDef->ppcAsmOp == PPCASM_OP_ROTLW || iDef->ppcAsmOp == PPCASM_OP_ROTLW_) + { + disInstr->operandMask = operand0Bit | operand1Bit | operand2Bit; + // no additional operands displayed + } + } + else if (iDef->instructionForm == OP_FORM_XL_CR) + { + PPC_OPC_TEMPL_X_CR(); + // todo - detect and emit simplified mnemonics (e.g. CRSET) + makeOpCRBit(0, crD); + makeOpCRBit(1, crA); + makeOpCRBit(2, crB); + } + else + { + cemu_assert_debug(false); + } + disInstr->operandMask &= ~(iDef->flags & 0x1F); + } + else + { + // no match + disInstr->ppcAsmCode = PPCASM_OP_UKN; + } +} + +void ppcAssembler_setError(PPCAssemblerInOut* ctx, char const* errorMsg) +{ + ctx->errorMsg = errorMsg; +} + +void ppcAssembler_setError(PPCAssemblerInOut* ctx, std::string_view errorMsg) +{ + ctx->errorMsg = errorMsg; +} + +char _assemblerErrorMessageDepr[1024]; + +bool _ppcAssembler_getOperandTextIndex(PPCAssemblerContext& internalCtx, sint32 operandIndex, sint32& textIndex) +{ + // get swapped index + operandIndex = _getOpIndex(internalCtx.iDef, operandIndex); + // check if operand is optional + if ((internalCtx.iDef->flags&(1 << operandIndex))) + { + // operand not used + textIndex = -1; + return true; + } + // get operand text index + sint32 opTextIdx = 0; + for (sint32 i = 0; i < operandIndex; i++) + { + if ((internalCtx.iDef->flags&(1 << i))) + continue; // skip operand + opTextIdx++; + } + if (opTextIdx >= internalCtx.listOperandStr.size()) + { + ppcAssembler_setError(internalCtx.ctx, "Missing operand"); + return false; + } + textIndex = opTextIdx; + return true; +} + +bool _ppcAssembler_parseRegister(std::string_view str, const char* prefix, sint32& regIndex) +{ + const char* startPtr = str.data(); + const char* endPtr = str.data() + str.size(); + // skip whitespaces + while (startPtr < endPtr) + { + if (*startPtr != ' ') + break; + startPtr++; + } + // trim whitespaces at end + while (endPtr > startPtr) + { + if (endPtr[-1] != ' ') + break; + endPtr--; + } + // parse register name + if (startPtr + 2 > endPtr) + return false; + while (true) + { + if (startPtr >= endPtr) + return false; + if (*prefix == '\0') + break; + if (tolower(*prefix) != tolower(*startPtr)) + return false; + prefix++; + startPtr++; + } + sint32 parsedIndex = 0; + while (startPtr < endPtr) + { + parsedIndex *= 10; + if (*startPtr < '0' || *startPtr > '9') + return false; + parsedIndex += (*startPtr - '0'); + startPtr++; + } + if (parsedIndex >= 32) + return false; + regIndex = parsedIndex; + return true; +} + +bool _ppcAssembler_processRegisterOperand(PPCAssemblerContext& internalCtx, sint32 operandIndex, sint32 bitIndex, const char* prefix, const sint32 registerCount) +{ + sint32 opTextIdx; + if (_ppcAssembler_getOperandTextIndex(internalCtx, operandIndex, opTextIdx) == false) + return false; + if (opTextIdx < 0) + return true; // skipped operand + // parse GPR + sint32 gprIndex; + if (_ppcAssembler_parseRegister(internalCtx.listOperandStr[opTextIdx].str, prefix, gprIndex) == false) + { + ppcAssembler_setError(internalCtx.ctx, fmt::format("\'{0}\' is not a valid register operand (must be {1}0 to {1}{2})", internalCtx.listOperandStr[opTextIdx].str, prefix, registerCount-1)); + return false; + } + if (gprIndex < 0 || gprIndex >= registerCount) + { + ppcAssembler_setError(internalCtx.ctx, fmt::format("\'{0}\' is not a valid register operand (must be {1}0 to {1}{2})", internalCtx.listOperandStr[opTextIdx].str, prefix, registerCount - 1)); + return false; + } + internalCtx.opcode |= (gprIndex << bitIndex); + return true; +} + +bool _ppcAssembler_processGPROperand(PPCAssemblerContext& internalCtx, sint32 operandIndex, sint32 bitIndex) +{ + return _ppcAssembler_processRegisterOperand(internalCtx, operandIndex, bitIndex, "r", 32); +} + +bool _ppcAssembler_processCROperand(PPCAssemblerContext& internalCtx, sint32 operandIndex, sint32 bitIndex, bool isEncodedAsBitIndex) +{ + return _ppcAssembler_processRegisterOperand(internalCtx, operandIndex, bitIndex + (isEncodedAsBitIndex?2:0), "cr", 8); +} + +template<typename TExprResult> +bool _ppcAssembler_evaluateConstantExpression(PPCAssemblerContext& internalCtx, TExpressionParser<TExprResult>& ep, std::string_view expr, TExprResult& result) +{ + if (!ep.IsValidExpression(expr)) + { + ppcAssembler_setError(internalCtx.ctx, fmt::format("'{}' is not a valid expression", expr)); + return false; + } + if (!ep.IsConstantExpression(expr)) + { + ppcAssembler_setError(internalCtx.ctx, fmt::format("'{}' does not evaluate to a constant expression", expr)); + return false; + } + try + { + result = ep.Evaluate(expr); + } + catch (std::exception* e) + { + ppcAssembler_setError(internalCtx.ctx, fmt::format("\'{0}\' could not be evaluated. Error: {1}", expr, e->what())); + return false; + } + return true; +} + +bool _ppcAssembler_processBIOperand(PPCAssemblerContext& internalCtx, sint32 operandIndex, sint32 bitIndex) +{ + sint32 opTextIdx; + if (_ppcAssembler_getOperandTextIndex(internalCtx, operandIndex, opTextIdx) == false) + return false; + if (opTextIdx < 0) + return true; // skipped operand + // parse expression + // syntax examples: + // single condition bit of cr0: lt, gt, eq, so + // condition bit with cr index: 4*cr1+lt + TExpressionParser<sint32> ep; + ep.AddConstant("lt", 0); + ep.AddConstant("gt", 1); + ep.AddConstant("eq", 2); + ep.AddConstant("so", 3); + ep.AddConstant("cr0", 0); + ep.AddConstant("cr1", 1); + ep.AddConstant("cr2", 2); + ep.AddConstant("cr3", 3); + ep.AddConstant("cr4", 4); + ep.AddConstant("cr5", 5); + ep.AddConstant("cr6", 6); + ep.AddConstant("cr7", 7); + std::string_view expr = internalCtx.listOperandStr[opTextIdx].str; + sint32 bi = 0; + if (!_ppcAssembler_evaluateConstantExpression(internalCtx, ep, expr, bi)) + return false; + if (bi < 0 || bi >= (8 * 4)) + { + ppcAssembler_setError(internalCtx.ctx, fmt::format("CR bit operand \'{0}\' evaluated to {1} which is out of range", expr, bi)); + return false; + } + internalCtx.opcode &= ~(0x1F << bitIndex); + internalCtx.opcode |= (bi << bitIndex); + return true; +} + +bool _ppcAssembler_processFPROperand(PPCAssemblerContext& internalCtx, sint32 operandIndex, sint32 bitIndex) +{ + return _ppcAssembler_processRegisterOperand(internalCtx, operandIndex, bitIndex, "f", 32); +} + +bool _ppcAssembler_processImmediateOperandS16(PPCAssemblerContext& internalCtx, sint32 operandIndex, sint32 bitIndex, bool isNegative = false) +{ + sint32 opTextIdx; + if (_ppcAssembler_getOperandTextIndex(internalCtx, operandIndex, opTextIdx) == false) + return false; + if (opTextIdx < 0) + return true; // skipped operand + // parse expression + std::string expressionString(internalCtx.listOperandStr[opTextIdx].str); + + if (isNegative) + { + expressionString.insert(0, "0-("); + expressionString.append(")"); + } + ExpressionParser ep; + double immD = 0.0f; + try + { + if (ep.IsConstantExpression(expressionString)) + { + immD = ep.Evaluate(expressionString); + } + else + { + internalCtx.ctx->list_relocs.emplace_back(PPCASM_RELOC::U32_MASKED_IMM, expressionString, 0, bitIndex, 16); + return true; + } + } + catch (std::exception* e) + { + // check if expression is invalid or if it contains unknown constants + sprintf(_assemblerErrorMessageDepr, "\'%s\' could not be evaluated. Error: %s", expressionString.c_str(), e->what()); + ppcAssembler_setError(internalCtx.ctx, _assemblerErrorMessageDepr); + return false; + } + uint32 imm = (uint32)(sint32)immD; + imm &= 0xFFFF; + internalCtx.opcode |= (imm << bitIndex); + return true; +} + +bool _ppcAssembler_processImmediateOperandU5(PPCAssemblerContext& internalCtx, sint32 operandIndex, sint32 bitIndex) +{ + sint32 opTextIdx; + if (_ppcAssembler_getOperandTextIndex(internalCtx, operandIndex, opTextIdx) == false) + return false; + if (opTextIdx < 0) + return true; // skipped operand + // parse expression + ExpressionParser ep; + double immD = 0.0f; + try + { + immD = ep.Evaluate(internalCtx.listOperandStr[opTextIdx].str); + } + catch (std::exception*) + { + // check if expression is invalid or if it contains unknown constants + ppcAssembler_setError(internalCtx.ctx, fmt::format("\'{}\' is not a valid expression", internalCtx.listOperandStr[opTextIdx].str)); + return false; + } + sint32 immS32 = (sint32)immD; + if (immS32 < 0 || immS32 >= 32) + { + ppcAssembler_setError(internalCtx.ctx, fmt::format("\'{}\' is not in range 0-31", internalCtx.listOperandStr[opTextIdx].str)); + return false; + } + uint32 imm = (uint32)immS32; + imm &= 0x1F; + internalCtx.opcode |= (imm << bitIndex); + return true; +} + +bool _ppcAssembler_processImmediateOperandU5Const(PPCAssemblerContext& internalCtx, sint32 operandIndex, sint8& value) +{ + sint32 opTextIdx; + if (_ppcAssembler_getOperandTextIndex(internalCtx, operandIndex, opTextIdx) == false) + return false; + if (opTextIdx < 0) + return true; // skipped operand + // parse expression + TExpressionParser<sint32> ep; + auto expr = internalCtx.listOperandStr[opTextIdx].str; + sint32 r; + if (!_ppcAssembler_evaluateConstantExpression(internalCtx, ep, expr, r)) + return false; + if (r < 0 || r >= 32) + { + ppcAssembler_setError(internalCtx.ctx, fmt::format("Expression '\'{0}\' which evaluates to {1} is not in range 0-31", expr, r)); + return false; + } + value = (sint8)r; + return true; +} + +bool _ppcAssembler_isConstantBranchTargetExpr(std::string& expressionString, sint32& relativeAddr) +{ + if (expressionString.length() > 0 && expressionString[0] == '.') + { + ExpressionParser ep; + double branchDistance = 0.0f; + try + { + branchDistance = ep.Evaluate(expressionString.substr(1).insert(0, "0")); + } + catch (std::exception&) + { + return false; + } + relativeAddr = (uint32)branchDistance; + return true; + } + return false; +} + +bool _ppcAssembler_processBranchOperandS16(PPCAssemblerContext& internalCtx, sint32 operandIndex) +{ + sint32 opTextIdx; + if (_ppcAssembler_getOperandTextIndex(internalCtx, operandIndex, opTextIdx) == false) + return false; + if (opTextIdx < 0) + return true; // skipped operand + std::string expressionString(internalCtx.listOperandStr[opTextIdx].str); + sint32 relativeAddr; + if (_ppcAssembler_isConstantBranchTargetExpr(expressionString, relativeAddr)) + { + if (relativeAddr < -32768 || relativeAddr > 32767) + { + sprintf(_assemblerErrorMessageDepr, "Branch target out of range"); + ppcAssembler_setError(internalCtx.ctx, _assemblerErrorMessageDepr); + return false; + } + if (relativeAddr&3) + { + sprintf(_assemblerErrorMessageDepr, "Branch target must be aligned to 4"); + ppcAssembler_setError(internalCtx.ctx, _assemblerErrorMessageDepr); + return false; + } + internalCtx.opcode |= (relativeAddr & 0xFFFC); + return true; + } + // create reloc + internalCtx.ctx->list_relocs.emplace_back(PPCASM_RELOC::BRANCH_S16, expressionString, 0, 0, 0); + return true; +} + +bool _ppcAssembler_processBranchOperandS26(PPCAssemblerContext& internalCtx, sint32 operandIndex) +{ + sint32 opTextIdx; + if (_ppcAssembler_getOperandTextIndex(internalCtx, operandIndex, opTextIdx) == false) + return false; + if (opTextIdx < 0) + return true; // skipped operand + std::string expressionString(internalCtx.listOperandStr[opTextIdx].str); + sint32 relativeAddr; + if (_ppcAssembler_isConstantBranchTargetExpr(expressionString, relativeAddr)) + { + if (relativeAddr < -33554432 || relativeAddr > 33554431) + { + ppcAssembler_setError(internalCtx.ctx, "Branch target out of range"); + return false; + } + if (relativeAddr & 3) + { + ppcAssembler_setError(internalCtx.ctx, "Branch target must be aligned to 4"); + return false; + } + internalCtx.opcode |= (relativeAddr & 0x3FFFFFC); + return true; + } + // create reloc + internalCtx.ctx->list_relocs.emplace_back(PPCASM_RELOC::BRANCH_S26, expressionString, 0, 0, 0); + return true; +} + +void _ppcAssembler_setAlignment(PPCAssemblerContext& internalInfo, uint32 alignment) +{ + if (internalInfo.ctx->forceNoAlignment) + { + internalInfo.ctx->alignmentRequirement = 1; + internalInfo.ctx->alignmentPaddingSize = 0; + internalInfo.ctx->virtualAddressAligned = internalInfo.ctx->virtualAddress; + return; + } + internalInfo.ctx->alignmentRequirement = alignment; + if (alignment == 0) + { + internalInfo.ctx->alignmentPaddingSize = 0; + internalInfo.ctx->virtualAddressAligned = internalInfo.ctx->virtualAddress; + return; + } + uint32 alignedVA = (internalInfo.ctx->virtualAddress + alignment - 1) & ~(alignment - 1); + internalInfo.ctx->alignmentPaddingSize = alignedVA - internalInfo.ctx->virtualAddress; + internalInfo.ctx->virtualAddressAligned = alignedVA; +} + +enum class ASM_DATA_DIRECTIVE +{ + NONE, + FLOAT, + DOUBLE, + U32, + U16, + U8, // alias to .string +}; + +bool _ppcAssembler_emitDataDirective(PPCAssemblerContext& internalInfo, ASM_DATA_DIRECTIVE dataDirective) +{ + PPCASM_RELOC relocType; + + switch (dataDirective) + { + case ASM_DATA_DIRECTIVE::FLOAT: + relocType = PPCASM_RELOC::FLOAT; + break; + case ASM_DATA_DIRECTIVE::DOUBLE: + relocType = PPCASM_RELOC::DOUBLE; + break; + case ASM_DATA_DIRECTIVE::U32: + relocType = PPCASM_RELOC::U32; + break; + case ASM_DATA_DIRECTIVE::U16: + relocType = PPCASM_RELOC::U16; + break; + case ASM_DATA_DIRECTIVE::U8: + relocType = PPCASM_RELOC::U8; + break; + default: + cemu_assert_debug(false); + return false; + } + + uint32 elementSize = 0; + + if (relocType == PPCASM_RELOC::FLOAT) + elementSize = 4; + else if (relocType == PPCASM_RELOC::DOUBLE) + elementSize = 8; + else if (relocType == PPCASM_RELOC::U32) + elementSize = 4; + else if (relocType == PPCASM_RELOC::U16) + elementSize = 2; + else if (relocType == PPCASM_RELOC::U8) + elementSize = 1; + else + cemu_assert_debug(false); + + _ppcAssembler_setAlignment(internalInfo, elementSize); + size_t elementCount = internalInfo.listOperandStr.size(); + internalInfo.ctx->outputData.reserve(elementSize * elementCount); + size_t writeIndex = 0; + for (size_t i = 0; i < elementCount; i++) + { + std::string_view expressionStr = internalInfo.listOperandStr[i].str; + // handle string constants + if (internalInfo.listOperandStr[i].str.size() >= 1 && expressionStr[0] == '"') + { + if (dataDirective != ASM_DATA_DIRECTIVE::U8) + { + ppcAssembler_setError(internalInfo.ctx, "Strings constants are only allowed in .byte or .string data directives"); + return false; + } + if (expressionStr.size() < 2 || expressionStr[expressionStr.size()-1] != '"') + { + ppcAssembler_setError(internalInfo.ctx, "String constants must end with a quotation mark. Example: \"text\""); + return false; + } + // write string bytes + null-termination character + size_t strConstantLength = expressionStr.size() - 2; + internalInfo.ctx->outputData.insert(internalInfo.ctx->outputData.end(), expressionStr.data() + 1, expressionStr.data() + 1 + strConstantLength); + internalInfo.ctx->outputData.emplace_back(0); + continue; + } + // numeric constants + internalInfo.ctx->outputData.resize(writeIndex + elementSize); + uint8* elementPtr = internalInfo.ctx->outputData.data() + writeIndex; + for (uint32 b = 0; b < elementSize; b++) + { + internalInfo.ctx->outputData[writeIndex] = 0; + writeIndex++; + } + ExpressionParser ep; + if (ep.IsConstantExpression(expressionStr)) + { + double solution = ep.Evaluate(expressionStr); + switch (relocType) + { + case PPCASM_RELOC::U32: + *(uint32be*)elementPtr = (uint32)solution; + break; + case PPCASM_RELOC::U16: + *(uint16be*)elementPtr = (uint16)solution; + break; + case PPCASM_RELOC::U8: + *(uint8be*)elementPtr = (uint8)solution; + break; + case PPCASM_RELOC::DOUBLE: + *(float64be*)elementPtr = solution; + break; + case PPCASM_RELOC::FLOAT: + *(float32be*)elementPtr = (float)solution; + break; + default: + cemu_assert_debug(false); + } + + } + else + { + // generate reloc if we cant resolve immediately + internalInfo.ctx->list_relocs.emplace_back(relocType, std::string(internalInfo.listOperandStr[i].str), (uint32)(elementPtr - internalInfo.ctx->outputData.data()), 0, 0); + } + } + return true; +} + +void _ppcAssembler_emitAlignDirective(PPCAssemblerContext& internalInfo, sint32 alignmentValue) +{ + _ppcAssembler_setAlignment(internalInfo, 1); + uint32 currentAddr = internalInfo.ctx->virtualAddress; + while ((currentAddr % (uint32)alignmentValue)) + { + internalInfo.ctx->outputData.emplace_back(0); + currentAddr++; + } +} + +void _ppcAssembler_translateAlias(boost::static_string<32>& instructionName) +{ + if (instructionName.compare("BNL") == 0) + instructionName.assign("BGT"); + if (instructionName.compare("BNG") == 0) + instructionName.assign("BLE"); +} + +bool ppcAssembler_assembleSingleInstruction(char const* text, PPCAssemblerInOut* ctx) +{ + PPCAssemblerContext internalInfo; + internalInfo.ctx = ctx; + // tokenize input + char const* currentPtr = text; + char const* startPtr = text; + char const* endPtr = startPtr+strlen(startPtr); + // cut off comments + for (const char* itrPtr = startPtr; itrPtr < endPtr; itrPtr++) + { + if (*itrPtr == '#') + { + endPtr = itrPtr; + break; + } + } + // trim whitespaces at end and beginning + while (endPtr > currentPtr) + { + if (endPtr[-1] != ' ' && endPtr[-1] != '\t') + break; + endPtr--; + } + while (currentPtr < endPtr) + { + if (currentPtr[0] != ' ' && currentPtr[0] != '\t') + break; + currentPtr++; + } + // parse name of instruction + boost::static_string<32> instructionName; + while (*currentPtr != ' ' && *currentPtr != '\t' && currentPtr < endPtr) + { + if (instructionName.size() >= instructionName.capacity()) + { + ppcAssembler_setError(ctx, "Instruction name exceeds maximum allowed length"); + return false; + } + instructionName.push_back((char)toupper(*currentPtr)); + currentPtr++; + } + if (instructionName.empty()) + { + ppcAssembler_setError(ctx, "Instruction name is invalid"); + return false; + } + // handle branch hint suffix + bool hasBranchHint = false; + bool branchHintTaken = false; // '+' -> true, '-' -> false + if (instructionName.back() == '+') + { + hasBranchHint = true; + branchHintTaken = true; + instructionName.pop_back(); + } + if (instructionName.back() == '-') + { + hasBranchHint = true; + branchHintTaken = false; + instructionName.pop_back(); + } + // handle instruction name aliases + _ppcAssembler_translateAlias(instructionName); + // parse operands + internalInfo.listOperandStr.clear(); + while (currentPtr < endPtr) + { + currentPtr++; + startPtr = currentPtr; + // find end of operand + while (currentPtr < endPtr) + { + if (*currentPtr == ',') + break; + currentPtr++; + } + // trim whitespaces at the beginning and end + const char* operandStartPtr = startPtr; + const char* operandEndPtr = currentPtr; + while (operandStartPtr < endPtr) + { + if (*operandStartPtr != ' ' && *operandStartPtr != '\t') + break; + operandStartPtr++; + } + while (operandEndPtr > operandStartPtr) + { + if (operandEndPtr[-1] != ' ' && operandEndPtr[-1] != '\t') + break; + operandEndPtr--; + } + internalInfo.listOperandStr.emplace_back(operandStartPtr, operandEndPtr); + } + // check for data directives + ASM_DATA_DIRECTIVE dataDirective = ASM_DATA_DIRECTIVE::NONE; + if (instructionName.compare(".FLOAT") == 0) + dataDirective = ASM_DATA_DIRECTIVE::FLOAT; + else if (instructionName.compare(".DOUBLE") == 0) + dataDirective = ASM_DATA_DIRECTIVE::DOUBLE; + else if (instructionName.compare(".INT") == 0 || instructionName.compare(".UINT") == 0 || instructionName.compare(".PTR") == 0 || instructionName.compare(".U32") == 0 || instructionName.compare(".LONG") == 0) + dataDirective = ASM_DATA_DIRECTIVE::U32; + else if (instructionName.compare(".WORD") == 0 || instructionName.compare(".U16") == 0 || instructionName.compare(".SHORT") == 0) + dataDirective = ASM_DATA_DIRECTIVE::U16; + else if (instructionName.compare(".BYTE") == 0 || instructionName.compare(".STRING") == 0 || instructionName.compare(".U8") == 0 || instructionName.compare(".CHAR") == 0) + dataDirective = ASM_DATA_DIRECTIVE::U8; + + if (dataDirective != ASM_DATA_DIRECTIVE::NONE) + { + if (internalInfo.listOperandStr.size() < 1) + { + ppcAssembler_setError(ctx, fmt::format("Value expected after data directive {}", instructionName.c_str())); + return false; + } + return _ppcAssembler_emitDataDirective(internalInfo, dataDirective); + } + // handle .align directive + if (instructionName.compare(".ALIGN") == 0) + { + // handle align data directive + if (internalInfo.listOperandStr.size() != 1) + { + ppcAssembler_setError(ctx, ".align directive must have exactly one operand"); + return false; + } + ExpressionParser ep; + try + { + if (ep.IsConstantExpression(internalInfo.listOperandStr[0].str)) + { + sint32 alignmentValue = ep.Evaluate<sint32>(internalInfo.listOperandStr[0].str); + if (alignmentValue <= 0 || alignmentValue >= 256) + { + ppcAssembler_setError(ctx, fmt::format("Alignment value \'{}\' is not within the allowed range (1-256)", alignmentValue)); + return false; + } + _ppcAssembler_emitAlignDirective(internalInfo, alignmentValue); + return true; + } + else + { + ppcAssembler_setError(ctx, fmt::format("Expression \'{}\' for .align directive is not a constant", internalInfo.listOperandStr[0].str)); + return false; + } + } + catch (std::exception* e) + { + ppcAssembler_setError(ctx, fmt::format("Expression \'{}\' for .align directive could not be evaluated. Error: {}", internalInfo.listOperandStr[0].str, e->what())); + return false; + } + } + // find match in instruction definition table + PPCInstructionDef* iDef = nullptr; + for (sint32 i = 0; i < sizeof(ppcInstructionTable) / sizeof(PPCInstructionDef); i++) + { + PPCInstructionDef* instr = ppcInstructionTable + i; + if (instructionName.compare(ppcAssembler_getInstructionName(instr->ppcAsmOp)) == 0) + { + iDef = instr; + internalInfo.iDef = iDef; + break; + } + } + if (iDef == nullptr) + { + ppcAssembler_setError(ctx, fmt::format("Instruction \'{}\' is unknown or not supported", instructionName.c_str())); + return false; + } + // build opcode + _ppcAssembler_setAlignment(internalInfo, 4); + uint32 opcMask; + uint32 opcBits; + ppcAssembler_buildOpcMask(iDef, opcMask, opcBits); + internalInfo.opcode = opcBits & opcMask; + internalInfo.opcode |= (iDef->compareBits&iDef->maskBits); + // handle operands + if (iDef->instructionForm == OP_FORM_DYNAMIC) + { + for (size_t i = 0; i < iDef->encodedOperands.size(); i++) + { + bool r = std::visit([&](auto&& op) -> bool { + return op.AssembleOperand(&internalInfo, iDef, internalInfo.opcode, i); + }, iDef->encodedOperands[i]); + if (!r) + return false; + } + for(auto& it : iDef->constraints) + { + std::visit([&](auto&& op) { + op.AssembleConstraint(&internalInfo, iDef, internalInfo.opcode); + }, it); + } + } + else if (iDef->instructionForm == OP_FORM_NO_OPERAND) + { + // do nothing + } + else if (iDef->instructionForm == OP_FORM_OP3_A_CMP) + { + if (internalInfo.listOperandStr.size() == 2) + { + // implicit cr0 + if (_ppcAssembler_processGPROperand(internalInfo, 0, 16) == false) + return false; + if (_ppcAssembler_processGPROperand(internalInfo, 1, 11) == false) + return false; + } + else + { + if (_ppcAssembler_processCROperand(internalInfo, 0, 21, true) == false) + return false; + if (_ppcAssembler_processGPROperand(internalInfo, 1, 16) == false) + return false; + if (_ppcAssembler_processGPROperand(internalInfo, 2, 11) == false) + return false; + } + } + else if (iDef->instructionForm == OP_FORM_CMP_SIMM) + { + if (internalInfo.listOperandStr.size() == 2) + { + // implicit cr0 + if (_ppcAssembler_processGPROperand(internalInfo, 0, 16) == false) + return false; + if (_ppcAssembler_processImmediateOperandS16(internalInfo, 1, 0) == false) + return false; + } + else + { + if (_ppcAssembler_processCROperand(internalInfo, 0, 21, true) == false) + return false; + if (_ppcAssembler_processGPROperand(internalInfo, 1, 16) == false) + return false; + if (_ppcAssembler_processImmediateOperandS16(internalInfo, 2, 0) == false) + return false; + } + } + else if (iDef->instructionForm == OP_FORM_OP3_A_IMM) + { + if (_ppcAssembler_processGPROperand(internalInfo, 0, 16) == false) + return false; + if (_ppcAssembler_processGPROperand(internalInfo, 1, 21) == false) + return false; + if (_ppcAssembler_processImmediateOperandU5(internalInfo, _getOpIndex(iDef, 2), 11) == false) + return false; + } + else if (iDef->instructionForm == OP_FORM_X_FP_CMP) + { + if (internalInfo.listOperandStr.size() == 2) + { + // implicit cr0 + if (_ppcAssembler_processFPROperand(internalInfo, 0, 16) == false) + return false; + if (_ppcAssembler_processFPROperand(internalInfo, 1, 11) == false) + return false; + } + else + { + if (_ppcAssembler_processCROperand(internalInfo, 0, 21, true) == false) + return false; + if (_ppcAssembler_processFPROperand(internalInfo, 1, 16) == false) + return false; + if (_ppcAssembler_processFPROperand(internalInfo, 2, 11) == false) + return false; + } + } + else if (iDef->instructionForm == OP_FORM_RLWINM) + { + if (_ppcAssembler_processGPROperand(internalInfo, 0, 16) == false) + return false; + if (_ppcAssembler_processGPROperand(internalInfo, 1, 21) == false) + return false; + if (_ppcAssembler_processImmediateOperandU5(internalInfo, 2, 11) == false) + return false; + if (_ppcAssembler_processImmediateOperandU5(internalInfo, 3, 6) == false) + return false; + if (_ppcAssembler_processImmediateOperandU5(internalInfo, 4, 1) == false) + return false; + } + else if (iDef->instructionForm == OP_FORM_RLWINM_EXTENDED) + { + if (_ppcAssembler_processGPROperand(internalInfo, 0, 16) == false) + return false; + if (_ppcAssembler_processGPROperand(internalInfo, 1, 21) == false) + return false; + + uint8 SH = 0; + uint8 MB = 0; + uint8 ME = 0; + + if (internalInfo.iDef->ppcAsmOp == PPCASM_OP_EXTLWI || internalInfo.iDef->ppcAsmOp == PPCASM_OP_EXTLWI_) + { + sint8 n, b; + if (!_ppcAssembler_processImmediateOperandU5Const(internalInfo, 2, n)) + return false; + if (!_ppcAssembler_processImmediateOperandU5Const(internalInfo, 3, b)) + return false; + + SH = b; + MB = 0; + ME = n - 1; + } + else if (internalInfo.iDef->ppcAsmOp == PPCASM_OP_EXTRWI || internalInfo.iDef->ppcAsmOp == PPCASM_OP_EXTRWI_) + { + sint8 n, b; + if (!_ppcAssembler_processImmediateOperandU5Const(internalInfo, 2, n)) + return false; + if (!_ppcAssembler_processImmediateOperandU5Const(internalInfo, 3, b)) + return false; + + SH = b + n; + MB = 32 - n; + ME = 31; + } + else if (internalInfo.iDef->ppcAsmOp == PPCASM_OP_SLWI || internalInfo.iDef->ppcAsmOp == PPCASM_OP_SLWI_) + { + sint8 n; + if (!_ppcAssembler_processImmediateOperandU5Const(internalInfo, 2, n)) + return false; + + SH = n; + MB = 0; + ME = 31 - n; + } + else if (internalInfo.iDef->ppcAsmOp == PPCASM_OP_SRWI || internalInfo.iDef->ppcAsmOp == PPCASM_OP_SRWI_) + { + sint8 n; + if (!_ppcAssembler_processImmediateOperandU5Const(internalInfo, 2, n)) + return false; + SH = 32 - n; + MB = n; + ME = 31; + } + else if (internalInfo.iDef->ppcAsmOp == PPCASM_OP_CLRLWI || internalInfo.iDef->ppcAsmOp == PPCASM_OP_CLRLWI_) + { + sint8 n; + if (!_ppcAssembler_processImmediateOperandU5Const(internalInfo, 2, n)) + return false; + SH = 0; + MB = n; + ME = 31; + } + else if (internalInfo.iDef->ppcAsmOp == PPCASM_OP_CLRRWI || internalInfo.iDef->ppcAsmOp == PPCASM_OP_CLRRWI_) + { + sint8 n; + if (!_ppcAssembler_processImmediateOperandU5Const(internalInfo, 2, n)) + return false; + SH = 0; + MB = 0; + ME = 31 - n; + } + else + { + cemu_assert_debug(false); + } + + internalInfo.opcode |= (SH << 11); + internalInfo.opcode |= (MB << 6); + internalInfo.opcode |= (ME << 1); + } + else if (iDef->instructionForm == OP_FORM_RLWNM) + { + if (_ppcAssembler_processGPROperand(internalInfo, 0, 16) == false) + return false; + if (_ppcAssembler_processGPROperand(internalInfo, 1, 21) == false) + return false; + if (_ppcAssembler_processGPROperand(internalInfo, 2, 11) == false) + return false; + if (_ppcAssembler_processImmediateOperandU5(internalInfo, 3, 6) == false) + return false; + if (_ppcAssembler_processImmediateOperandU5(internalInfo, 4, 1) == false) + return false; + } + else if (iDef->instructionForm == OP_FORM_RLWNM_EXTENDED) + { + if (_ppcAssembler_processGPROperand(internalInfo, 0, 16) == false) + return false; + if (_ppcAssembler_processGPROperand(internalInfo, 1, 21) == false) + return false; + if (_ppcAssembler_processGPROperand(internalInfo, 2, 11) == false) + return false; + uint32 MB = 0, ME = 0; + if (internalInfo.iDef->ppcAsmOp == PPCASM_OP_ROTLW || internalInfo.iDef->ppcAsmOp == PPCASM_OP_ROTLW_) + { + MB = 0; + ME = 31; + } + internalInfo.opcode |= (MB << 6); + internalInfo.opcode |= (ME << 1); + } + else if (iDef->instructionForm == OP_FORM_BRANCH_S16) + { + if (iDef->ppcAsmOp == PPCASM_OP_BC) + { + // generic conditional branch + // BC <BO>, <BI>, <dst> + sint8 bo; + if (_ppcAssembler_processImmediateOperandU5Const(internalInfo, 0, bo) == false) + return false; + if (_ppcAssembler_processBIOperand(internalInfo, 1, 16) == false) + return false; + if (_ppcAssembler_processBranchOperandS16(internalInfo, 2) == false) + return false; + internalInfo.opcode |= (bo << 21); + } + else + { + // BLE, BGT etc. + if (internalInfo.listOperandStr.size() == 2) + { + // explicit CR + if (_ppcAssembler_processCROperand(internalInfo, 0, 16, true) == false) + return false; + if (_ppcAssembler_processBranchOperandS16(internalInfo, 1) == false) + return false; + } + else + { + if (_ppcAssembler_processBranchOperandS16(internalInfo, 0) == false) + return false; + } + if (hasBranchHint) + internalInfo.opcode |= (1 << 21); + } + } + else if (iDef->instructionForm == OP_FORM_BRANCH_S24) + { + if (_ppcAssembler_processBranchOperandS26(internalInfo, 0) == false) + return false; + } + else if (iDef->instructionForm == OP_FORM_XL_CR) + { + if (_ppcAssembler_processBIOperand(internalInfo, 0, 21) == false) + return false; + if (_ppcAssembler_processBIOperand(internalInfo, 1, 16) == false) + return false; + if (_ppcAssembler_processBIOperand(internalInfo, 2, 11) == false) + return false; + } + else + { + cemu_assert_debug(false); // unsupported instruction form + return false; + } + ctx->outputData.resize(4); + ctx->outputData[0] = (uint8)((internalInfo.opcode >> 24) & 0xFF); + ctx->outputData[1] = (uint8)((internalInfo.opcode >> 16) & 0xFF); + ctx->outputData[2] = (uint8)((internalInfo.opcode >> 8) & 0xFF); + ctx->outputData[3] = (uint8)((internalInfo.opcode >> 0) & 0xFF); + return true; +} + +void _testAsm(uint32 expected, const char* iText) +{ + PPCAssemblerInOut ctx{}; + ctx.virtualAddress = 0; + if (!ppcAssembler_assembleSingleInstruction(iText, &ctx)) + { + cemu_assert_debug(false); + return; + } + cemu_assert_debug(ctx.outputData.size() == 4); + uint32 opcode = 0; + opcode |= ((uint32)ctx.outputData[0] << 24); + opcode |= ((uint32)ctx.outputData[1] << 16); + opcode |= ((uint32)ctx.outputData[2] << 8); + opcode |= ((uint32)ctx.outputData[3] << 0); + cemu_assert_debug(expected == opcode); + +} + +void _testAsmFail(const char* iText) +{ + PPCAssemblerInOut ctx{}; + ctx.virtualAddress = 0; + if (!ppcAssembler_assembleSingleInstruction(iText, &ctx)) + return; + cemu_assert_debug(false); // should fail +} + +void _testAsmArray(std::vector<uint8> expectedArray, const char* iText) +{ + PPCAssemblerInOut ctx{}; + ctx.virtualAddress = 0; + if (!ppcAssembler_assembleSingleInstruction(iText, &ctx)) + { + cemu_assert_debug(false); + return; + } + cemu_assert_debug(ctx.outputData.size() == expectedArray.size()); + for (size_t offset = 0; offset < expectedArray.size(); offset++) + { + cemu_assert_debug(ctx.outputData[offset] == expectedArray[offset]); + } +} + +void ppcAsmTestDisassembler() +{ + cemu_assert_debug(_CanStoreInteger(5, 10, false)); + cemu_assert_debug(_CanStoreInteger(1023, 10, false)); + cemu_assert_debug(!_CanStoreInteger(1024, 10, false)); + cemu_assert_debug(_CanStoreInteger(511, 10, true)); + cemu_assert_debug(!_CanStoreInteger(512, 10, true)); + cemu_assert_debug(_CanStoreInteger(-511, 10, true)); + cemu_assert_debug(_CanStoreInteger(-512, 10, true)); + cemu_assert_debug(!_CanStoreInteger(-513, 10, true)); + + PPCDisassembledInstruction disasm; + + auto disassemble = [&](uint32 opcode, PPCASM_OP ppcAsmCode) + { + disasm = { 0 }; + ppcAssembler_disassemble(0x10000000, opcode, &disasm); + cemu_assert_debug(disasm.ppcAsmCode == ppcAsmCode); + }; + + auto checkOperandMask = [&](bool op0 = false, bool op1 = false, bool op2 = false, bool op3 = false) + { + bool hasOp0 = (disasm.operandMask & (1 << 0)); + bool hasOp1 = (disasm.operandMask & (1 << 1)); + bool hasOp2 = (disasm.operandMask & (1 << 2)); + bool hasOp3 = (disasm.operandMask & (1 << 3)); + + cemu_assert_debug(hasOp0 == op0); + cemu_assert_debug(hasOp1 == op1); + cemu_assert_debug(hasOp2 == op2); + cemu_assert_debug(hasOp3 == op3); + }; + + auto checkOpCR = [&](size_t index, uint8 crIndex) + { + cemu_assert_debug(disasm.operand[index].type == PPCASM_OPERAND_TYPE_CR); + cemu_assert_debug(disasm.operand[index].registerIndex == crIndex); + }; + + auto checkOpCRBit = [&](size_t index, uint8 crIndex) + { + cemu_assert_debug(disasm.operand[index].type == PPCASM_OPERAND_TYPE_CR_BIT); + cemu_assert_debug(disasm.operand[index].registerIndex == crIndex); + }; + + auto checkOpFPR = [&](size_t index, uint8 fprIndex) + { + cemu_assert_debug(disasm.operand[index].type == PPCASM_OPERAND_TYPE_FPR); + cemu_assert_debug(disasm.operand[index].registerIndex == fprIndex); + }; + + auto checkOpGPR = [&](size_t index, uint8 gprIndex) + { + cemu_assert_debug(disasm.operand[index].type == PPCASM_OPERAND_TYPE_GPR); + cemu_assert_debug(disasm.operand[index].registerIndex == gprIndex); + }; + + auto checkOpImm = [&](size_t index, sint32 imm) + { + cemu_assert_debug(disasm.operand[index].type == PPCASM_OPERAND_TYPE_IMM); + cemu_assert_debug(disasm.operand[index].immS32 == imm); + }; + + auto checkOpBranchDst = [&](size_t index, sint32 relOffset) + { + cemu_assert_debug(disasm.operand[index].type == PPCASM_OPERAND_TYPE_CIMM); + cemu_assert_debug(disasm.operand[index].immU32 == 0x10000000 + relOffset); + }; + + auto checkBranchHint = [&](bool isSet) + { + cemu_assert_debug(disasm.branchHintBitSet == isSet); + }; + + // addi / subi + _testAsm(0x3863FFFF, "addi r3, r3, -1"); + _testAsm(0x3863FFFF, "subi r3, r3, 1"); + _testAsm(0x387E0134, "addi r3, r30, 0x134"); + _testAsm(0x387E0134, "subi r3, r30, 0-0x134"); + disassemble(0x387E0134, PPCASM_OP_ADDI); + checkOperandMask(true, true, true); + checkOpGPR(0, 3); + checkOpGPR(1, 30); + checkOpImm(2, 0x134); + + // mulli + _testAsm(0x1D1E0005, "mulli r8, r30, 5"); + disassemble(0x1D1E0005, PPCASM_OP_MULLI); + checkOperandMask(true, true, true); + checkOpGPR(0, 8); + checkOpGPR(1, 30); + checkOpImm(2, 5); + + // mulli + _testAsm(0x1CFEFFF8, "mulli r7, r30, 0-(2 + 2 + 2 + 2)"); // -8 + disassemble(0x1CFEFFF8, PPCASM_OP_MULLI); + checkOperandMask(true, true, true); + checkOpGPR(0, 7); + checkOpGPR(1, 30); + checkOpImm(2, -8); + + // subfic + _testAsm(0x2304002F, "subfic r24, r4, 0x2F"); + disassemble(0x2304002F, PPCASM_OP_SUBFIC); + checkOperandMask(true, true, true); + checkOpGPR(0, 24); + checkOpGPR(1, 4); + checkOpImm(2, 0x2F); + + // fcmpu cr7, f1, f0 + _testAsm(0xFF810000, "fcmpu cr7, f1, f0"); + disassemble(0xFF810000, PPCASM_OP_FCMPU); + checkOperandMask(true, true, true); + checkOpCR(0, 7); + checkOpFPR(1, 1); + checkOpFPR(2, 0); + + // fcmpu cr0, f1, f0 + _testAsm(0xFC010000, "fcmpu cr0, f1, f0"); + disassemble(0xFC010000, PPCASM_OP_FCMPU); + checkOperandMask(true, true, true); + checkOpCR(0, 0); + checkOpFPR(1, 1); + checkOpFPR(2, 0); + + // cmpwi r9, -1 + _testAsm(0x2C09FFFF, "cmpwi r9, -1"); + disassemble(0x2C09FFFF, PPCASM_OP_CMPWI); + checkOperandMask(false, true, true); + checkOpGPR(1, 9); + checkOpImm(2, -1); + // cmpwi cr7, r9, 0 + _testAsm(0x2F890000, "cmpwi cr7, r9, 0"); + disassemble(0x2F890000, PPCASM_OP_CMPWI); + checkOperandMask(true, true, true); + checkOpCR(0, 7); + checkOpGPR(1, 9); + checkOpImm(2, 0); + // cmplwi r3, 0xF + _testAsm(0x2803000F, "cmplwi r3, 0xF"); + disassemble(0x2803000F, PPCASM_OP_CMPLWI); + checkOperandMask(false, true, true); + checkOpGPR(1, 3); + checkOpImm(2, 0xF); + // cmpw cr7, r4, r10 + _testAsm(0x7F845000, "cmpw cr7, r4, r10"); + disassemble(0x7F845000, PPCASM_OP_CMPW); + checkOperandMask(true, true, true); + checkOpCR(0, 7); + checkOpGPR(1, 4); + checkOpGPR(2, 10); + // cmplw cr7, r31, r9 + _testAsm(0x7F9F4840, "cmplw cr7, r31, r9"); + disassemble(0x7F9F4840, PPCASM_OP_CMPLW); + checkOperandMask(true, true, true); + checkOpCR(0, 7); + checkOpGPR(1, 31); + checkOpGPR(2, 9); + // cmplw r24, r28 + _testAsm(0x7C18E040, "cmplw r24, r28"); + disassemble(0x7C18E040, PPCASM_OP_CMPLW); + checkOperandMask(false, true, true); + checkOpGPR(1, 24); + checkOpGPR(2, 28); + + // b .+0x18 + _testAsm(0x48000018, "b .+0x18"); + // b 0x10000018 + //_testAsm(0x48000018, "b 0x10000018"); + + // bgt cr7, .+0x40 + _testAsm(0x419D0040, "bgt cr7, .+0x40"); + disassemble(0x419D0040, PPCASM_OP_BGT); + checkOperandMask(true, true); + checkOpCR(0, 7); + checkOpBranchDst(1, 0x40); + checkBranchHint(false); + // bnl cr7, .+0x40 (alias to bgt) + _testAsm(0x419D0040, "bnl cr7, .+0x40"); + // beq cr7, .+0x14 + _testAsm(0x419E0014, "beq cr7, .+0x14"); + disassemble(0x419E0014, PPCASM_OP_BEQ); + checkOperandMask(true, true); + checkOpCR(0, 7); + checkOpBranchDst(1, 0x14); + checkBranchHint(false); + // beq .-0x4C + _testAsm(0x4182FFB4, "beq .-0x4C"); + disassemble(0x4182FFB4, PPCASM_OP_BEQ); + checkOperandMask(true); + checkOpBranchDst(0, -0x4C); + checkBranchHint(false); + // beq+ .+8 + _testAsm(0x41A20008, "beq+ .+8"); + disassemble(0x41A20008, PPCASM_OP_BEQ); + checkOperandMask(true, false); + checkOpBranchDst(0, +0x8); + checkBranchHint(true); + + // cror + _testAsm(0x4F5DF382, "cror 4*cr6+eq, 4*cr7+gt, 4*cr7+eq"); + _testAsm(0x4C411B82, "cror eq, gt, so"); + disassemble(0x4F5DF382, PPCASM_OP_CROR); + checkOperandMask(true, true, true); + checkOpCRBit(0, 6 * 4 + 2); + checkOpCRBit(1, 7 * 4 + 1); + checkOpCRBit(2, 7 * 4 + 2); + + // slw & srw + _testAsm(0x7D202030, "slw r0, r9, r4"); + _testAsm(0x7D80FC31, "srw. r0, r12, r31"); + disassemble(0x7D202030, PPCASM_OP_SLW); + checkOperandMask(true, true, true); + checkOpGPR(0, 0); + checkOpGPR(1, 9); + checkOpGPR(2, 4); + + // FADD + _testAsm(0xFC29502A, "fadd f1, f9, f10"); + disassemble(0xFC29502A, PPCASM_OP_FADD); + checkOperandMask(true, true, true); + checkOpFPR(0, 1); + checkOpFPR(1, 9); + checkOpFPR(2, 10); + + // FSUB + _testAsm(0xFDAB4028, "fsub f13, f11, f8"); + _testAsm(0xFD0D4828, "fsub f8, f13, f9"); + disassemble(0xFD0D4828, PPCASM_OP_FSUB); + checkOperandMask(true, true, true); + checkOpFPR(0, 8); + checkOpFPR(1, 13); + checkOpFPR(2, 9); + + // FMUL + _testAsm(0xFD4B0332, "fmul f10, f11, f12"); + disassemble(0xFD4B0332, PPCASM_OP_FMUL); + checkOperandMask(true, true, true); + checkOpFPR(0, 10); + checkOpFPR(1, 11); + checkOpFPR(2, 12); + + // FDIV + _testAsm(0xFD885824, "fdiv f12, f8, f11"); + disassemble(0xFD885824, PPCASM_OP_FDIV); + checkOperandMask(true, true, true); + checkOpFPR(0, 12); + checkOpFPR(1, 8); + checkOpFPR(2, 11); + + // FMADD + _testAsm(0xFC2A0A3A, "fmadd f1, f10, f8, f1"); + _testAsm(0xFC0A0B7A, "fmadd f0, f10, f13, f1"); + _testAsm(0xFFE0F33A, "fmadd f31, f0, f12, f30"); + disassemble(0xFFE0F33A, PPCASM_OP_FMADD); + checkOperandMask(true, true, true, true); + checkOpFPR(0, 31); + checkOpFPR(1, 0); + checkOpFPR(2, 12); + checkOpFPR(3, 30); + + // FNMADDS + _testAsm(0xED8A627E, "fnmadds f12, f10, f9, f12"); + disassemble(0xED8A627E, PPCASM_OP_FNMADDS); + checkOperandMask(true, true, true, true); + checkOpFPR(0, 12); + checkOpFPR(1, 10); + checkOpFPR(2, 9); + checkOpFPR(3, 12); + + // still missing test cases: FNMADD, FMADDS + + // FMSUB + _testAsm(0xFC1C0338, "fmsub f0, f28, f12, f0"); + _testAsm(0xFD204B38, "fmsub f9, f0, f12, f9"); + disassemble(0xFD204B38, PPCASM_OP_FMSUB); + checkOperandMask(true, true, true, true); + checkOpFPR(0, 9); + checkOpFPR(1, 0); + checkOpFPR(2, 12); + checkOpFPR(3, 9); + + // FNMSUB + _testAsm(0xFD7DCB3C, "fnmsub f11, f29, f12, f25"); + disassemble(0xFD7DCB3C, PPCASM_OP_FNMSUB); + checkOperandMask(true, true, true, true); + checkOpFPR(0, 11); + checkOpFPR(1, 29); + checkOpFPR(2, 12); + checkOpFPR(3, 25); + + // FMSUBS + _testAsm(0xEE3D5838, "fmsubs f17, f29, f0, f11"); + _testAsm(0xEDB84AB8, "fmsubs f13, f24, f10, f9"); + disassemble(0xEDB84AB8, PPCASM_OP_FMSUBS); + checkOperandMask(true, true, true, true); + checkOpFPR(0, 13); + checkOpFPR(1, 24); + checkOpFPR(2, 10); + checkOpFPR(3, 9); + + // FNMSUBS + _testAsm(0xED4951BC, "fnmsubs f10, f9, f6, f10"); + _testAsm(0xED253AFC, "fnmsubs f9, f5, f11, f7"); + disassemble(0xED253AFC, PPCASM_OP_FNMSUBS); + checkOperandMask(true, true, true, true); + checkOpFPR(0, 9); + checkOpFPR(1, 5); + checkOpFPR(2, 11); + checkOpFPR(3, 7); + + // FRSQRTE + _testAsm(0xFCE06034, "frsqrte f7, f12"); + _testAsm(0xFCC06834, "frsqrte f6, f13"); + disassemble(0xFCC06834, PPCASM_OP_FRSQRTE); + checkOperandMask(true, true); + checkOpFPR(0, 6); + checkOpFPR(1, 13); + + // FRSP + _testAsm(0xFFA03018, "frsp f29, f6"); + disassemble(0xFFA03018, PPCASM_OP_FRSP); + checkOperandMask(true, true); + checkOpFPR(0, 29); + checkOpFPR(1, 6); + + // FNEG + _testAsm(0xFDA05850, "fneg f13, f11"); + disassemble(0xFDA05850, PPCASM_OP_FNEG); + checkOperandMask(true, true); + checkOpFPR(0, 13); + checkOpFPR(1, 11); + + // FMR + _testAsm(0xFCE06090, "fmr f7, f12"); + disassemble(0xFCE06090, PPCASM_OP_FMR); + checkOperandMask(true, true); + checkOpFPR(0, 7); + checkOpFPR(1, 12); + + // FCTIWZ + _testAsm(0xFD80401E, "fctiwz f12, f8"); + disassemble(0xFD80401E, PPCASM_OP_FCTIWZ); + checkOperandMask(true, true); + checkOpFPR(0, 12); + checkOpFPR(1, 8); + + // PS_MADD + _testAsm(0x1168637A, "ps_madd f11, f8, f13, f12"); + disassemble(0x1168637A, PPCASM_OP_PS_MADD); + checkOperandMask(true, true, true, true); + checkOpFPR(0, 11); + checkOpFPR(1, 8); + checkOpFPR(2, 13); + checkOpFPR(3, 12); + + // PS_MSUB + _testAsm(0x11A55FF8, "ps_msub f13, f5, f31, f11"); + disassemble(0x11A55FF8, PPCASM_OP_PS_MSUB); + checkOperandMask(true, true, true, true); + checkOpFPR(0, 13); + checkOpFPR(1, 5); + checkOpFPR(2, 31); + checkOpFPR(3, 11); + + // PS_NMADD + _testAsm(0x118850FE, "ps_nmadd f12, f8, f3, f10"); + disassemble(0x118850FE, PPCASM_OP_PS_NMADD); + checkOperandMask(true, true, true, true); + checkOpFPR(0, 12); + checkOpFPR(1, 8); + checkOpFPR(2, 3); + checkOpFPR(3, 10); + + // PS_NMSUB + _testAsm(0x112832FC, "ps_nmsub f9, f8, f11, f6"); + disassemble(0x112832FC, PPCASM_OP_PS_NMSUB); + checkOperandMask(true, true, true, true); + checkOpFPR(0, 9); + checkOpFPR(1, 8); + checkOpFPR(2, 11); + checkOpFPR(3, 6); + + // PS_ADD + _testAsm(0x112A582A, "ps_add f9, f10, f11"); + disassemble(0x112A582A, PPCASM_OP_PS_ADD); + checkOperandMask(true, true, true); + checkOpFPR(0, 9); + checkOpFPR(1, 10); + checkOpFPR(2, 11); + + // PS_SUB + _testAsm(0x11663828, "ps_sub f11, f6, f7"); + disassemble(0x11663828, PPCASM_OP_PS_SUB); + checkOperandMask(true, true, true); + checkOpFPR(0, 11); + checkOpFPR(1, 6); + checkOpFPR(2, 7); + + // PS_MUL + _testAsm(0x11680332, "ps_mul f11, f8, f12"); + disassemble(0x11680332, PPCASM_OP_PS_MUL); + checkOperandMask(true, true, true); + checkOpFPR(0, 11); + checkOpFPR(1, 8); + checkOpFPR(2, 12); + + // PS_DIV + _testAsm(0x10A45824, "ps_div f5, f4, f11"); + disassemble(0x10A45824, PPCASM_OP_PS_DIV); + checkOperandMask(true, true, true); + checkOpFPR(0, 5); + checkOpFPR(1, 4); + checkOpFPR(2, 11); + + // PS_MERGE10 + _testAsm(0x10CC6C20, "ps_merge00 f6, f12, f13"); + disassemble(0x10CC6C20, PPCASM_OP_PS_MERGE00); + checkOperandMask(true, true, true); + checkOpFPR(0, 6); + checkOpFPR(1, 12); + checkOpFPR(2, 13); + + // random extra tests + _testAsm(0x419D0040, "bgt cr7, .+0x40"); + _testAsm(0x50AB042E, "rlwimi r11, r5, 0,16,23"); + _testAsm(0xFF810000, "fcmpu cr7, f1, f0"); + _testAsm(0xFC010000, "fcmpu cr0, f1, f0"); + _testAsm(0x7F845000, "cmpw cr7, r4, r10"); + _testAsm(0x2C090000, "cmpwi r9, 0"); // implicit cr0 + _testAsm(0x2C090000, "cmpwi cr0, r9, 0"); + _testAsm(0x2F9E001F, "cmpwi cr7, r30, 0x1F"); + _testAsm(0x7D573850, "subf r10, r23, r7"); + _testAsm(0x7D573850, "sub r10, r7, r23"); // alias for subf + _testAsm(0x7D862851, "subf. r12, r6, r5"); + _testAsm(0x7D7C1896, "mulhw r11, r28, r3"); + _testAsm(0x7D436016, "mulhwu r10, r3, r12"); + _testAsm(0x7FE318F8, "nor r3, r31, r3"); + _testAsm(0x7D29F8F8, "nor r9, r9, r31"); + _testAsm(0x7F26C8F8, "nor r6, r25, r25"); + _testAsm(0x7F26C8F8, "not r6, r25"); // alias for nor where rA == rB + _testAsm(0x7FE4FB78, "mr r4, r31"); // alias for or where rA == rB + _testAsm(0x7C7EEAA6, "mfspr r3, spr958"); + _testAsm(0x7C78E3A6, "mtspr spr920, r3"); + _testAsm(0x7D6802A6, "mflr r11"); + _testAsm(0x7E6902A6, "mfctr r19"); + _testAsm(0x7D430034, "cntlzw r3, r10"); + _testAsm(0x7C640774, "extsb r4, r3"); + _testAsm(0x1C8A00B8, "mulli r4, r10, 0xB8"); + _testAsm(0x1CFEFFF8, "mulli r7, r30, -8"); + _testAsm(0x1CFE8000, "mulli r7, r30, -0x8000"); + _testAsm(0x1CFE7FFF, "mulli r7, r30, 0x7FFF"); + _testAsmFail("mulli r7, r30, -0x8001"); + _testAsmFail("mulli r7, r30, 0x8000"); + _testAsm(0x38E0FFFF, "li r7, 0xFFFF"); // li/lis allows both signed and unsigned 16-bit range + _testAsm(0x38E0FFFF, "li r7, -1"); + _testAsm(0x38E08000, "li r7, -0x8000"); + _testAsmFail("li r7, -0x8001"); + _testAsmFail("li r7, 0xFFFF+1"); + + // test set 2 + _testAsm(0x7c0903a6, "mtctr r0"); + _testAsm(0x7c6903a6, "mtctr r3"); + _testAsm(0x7d0903a6, "mtctr r8"); + _testAsm(0x7d2903a6, "mtctr r9"); + _testAsm(0x7c0803a6, "mtlr r0"); + _testAsm(0x7c6803a6, "mtlr r3"); + _testAsm(0x7c8803a6, "mtlr r4"); + _testAsm(0x7c065896, "mulhw r0, r6, r11"); + _testAsm(0x7ce85096, "mulhw r7, r8, r10"); + _testAsm(0x7d494096, "mulhw r10, r9, r8"); + _testAsm(0x7ca4c016, "mulhwu r5, r4, r24"); + _testAsm(0x7ce53016, "mulhwu r7, r5, r6"); + _testAsm(0x7d373816, "mulhwu r9, r23, r7"); + _testAsm(0x7d664816, "mulhwu r11, r6, r9"); + _testAsm(0x1d2ae09e, "mulli r9, r10, -0x1F62"); + _testAsm(0x1fe900ff, "mulli r31, r9, 0xFF"); + _testAsm(0x1ffefffe, "mulli r31, r30, -2"); + _testAsm(0x7e4531d6, "mullw r18, r5, r6"); + _testAsm(0x7fe429d6, "mullw r31, r4, r5"); + _testAsm(0x7d0951d7, "mullw. r8, r9, r10"); + _testAsm(0x7f0941d7, "mullw. r24, r9, r8"); + _testAsm(0x7c6600d0, "neg r3, r6"); + _testAsm(0x7df600d0, "neg r15, r22"); + _testAsm(0x7eeb00d0, "neg r23, r11"); + _testAsm(0x7ca428f8, "not r4, r5"); + _testAsm(0x7df578f8, "not r21, r15"); + _testAsm(0x7fe9f8f8, "not r9, r31"); + _testAsm(0x7fc83378, "or r8, r30, r6"); + _testAsm(0x7de6a379, "or. r6, r15, r20"); + _testAsm(0x7f29fb79, "or. r9, r25, r31"); + _testAsm(0x7ceafb38, "orc r10, r7, r31"); + _testAsm(0x7fbe4338, "orc r30, r29, r8"); + _testAsm(0x63a90001, "ori r9, r29, 1"); + _testAsm(0x63c3b46e, "ori r3, r30, 0xB46E"); + _testAsm(0x63e4ffff, "ori r4, r31, 0xFFFF"); + _testAsm(0x650a8000, "oris r10, r8, 0x8000"); + _testAsm(0x676c792e, "oris r12, r27, 0x792E"); + _testAsm(0x6788ffff, "oris r8, r28, 0xFFFF"); + _testAsm(0x67ea3fe0, "oris r10, r31, 0x3FE0"); + _testAsm(0x51231fb8, "rlwimi r3, r9, 3,30,28"); + _testAsm(0x5249fefe, "rlwimi r9, r18, 31,27,31"); + _testAsm(0x52513a20, "rlwimi r17, r18, 7,8,16"); + _testAsm(0x53494420, "rlwimi r9, r26, 8,16,16"); + _testAsm(0x53657269, "rlwimi. r5, r27, 14,9,20"); + _testAsm(0x546906f2, "rlwinm r9, r3, 0,27,25"); + _testAsm(0x55284ad6, "rlwinm r8, r9, 9,11,11"); + _testAsm(0x57c90428, "rlwinm r9, r30, 0,16,20"); + _testAsm(0x57e5d57a, "rlwinm r5, r31, 26,21,29"); + _testAsm(0x57ea34b0, "rlwinm r10, r31, 6,18,24"); + _testAsm(0x54e1bbb1, "rlwinm. r1, r7, 23,14,24"); + _testAsm(0x550707fb, "rlwinm. r7, r8, 0,31,29"); + _testAsm(0x552003f1, "rlwinm. r0, r9, 0,15,24"); + _testAsm(0x5527022f, "rlwinm. r7, r9, 0,8,23"); + _testAsm(0x55270777, "rlwinm. r7, r9, 0,29,27"); + _testAsm(0x552a0777, "rlwinm. r10, r9, 0,29,27"); + _testAsm(0x552a07b9, "rlwinm. r10, r9, 0,30,28"); + _testAsm(0x5d5f583e, "rotlw r31, r10, r11"); + _testAsm(0x5eea703e, "rotlw r10, r23, r14"); + _testAsm(0x5f0ab03e, "rotlw r10, r24, r22"); + _testAsm(0x541a783e, "rotlwi r26, r0, 15"); + _testAsm(0x5488383e, "rotlwi r8, r4, 7"); + _testAsm(0x5779683e, "rotlwi r25, r27, 13"); + _testAsm(0x54e4c83e, "rotrwi r4, r7, 7"); + _testAsm(0x7eeaf830, "slw r10, r23, r31"); + _testAsm(0x7f68c030, "slw r8, r27, r24"); + _testAsm(0x7fe8f030, "slw r8, r31, r30"); + _testAsm(0x7f484831, "slw. r8, r26, r9"); + _testAsm(0x7f844031, "slw. r4, r28, r8"); + _testAsm(0x57e42036, "slwi r4, r31, 4"); + _testAsm(0x57e42834, "slwi r4, r31, 5"); + _testAsm(0x57f37820, "slwi r19, r31, 15"); + _testAsm(0x5488083d, "slwi. r8, r4, 1"); + _testAsm(0x552a3033, "slwi. r10, r9, 6"); + _testAsm(0x5647083d, "slwi. r7, r18, 1"); + _testAsm(0x7c879e30, "sraw r7, r4, r19"); + _testAsm(0x7c891e30, "sraw r9, r4, r3"); + _testAsm(0x7fca4e30, "sraw r10, r30, r9"); + _testAsm(0x7d380e70, "srawi r24, r9, 1"); + _testAsm(0x7c79fe71, "srawi. r25, r3, 0x1F"); + _testAsm(0x7fb91e71, "srawi. r25, r29, 3"); + _testAsm(0x7cc33c30, "srw r3, r6, r7"); + _testAsm(0x7d09e430, "srw r9, r8, r28"); + _testAsm(0x7d3d4430, "srw r29, r9, r8"); + _testAsm(0x5403d97e, "srwi r3, r0, 5"); + _testAsm(0x57dc843e, "srwi r28, r30, 16"); + _testAsm(0x552a463f, "srwi. r10, r9, 24"); + _testAsm(0x5532c9ff, "srwi. r18, r9, 7"); + _testAsm(0x57f8f87f, "srwi. r24, r31, 1"); + _testAsm(0x9be80201, "stb r31, 0x201(r8)"); + _testAsm(0x9bfafffc, "stb r31, -4(r26)"); + _testAsm(0x9bfeffff, "stb r31, -1(r30)"); + _testAsm(0x9dc3ffff, "stbu r14, -1(r3)"); + _testAsm(0x9e55006f, "stbu r18, 0x6F(r21)"); + _testAsm(0x9fdc2008, "stbu r30, 0x2008(r28)"); + _testAsm(0x7ce521ee, "stbux r7, r5, r4"); + _testAsm(0x7d4919ee, "stbux r10, r9, r3"); + _testAsm(0x7f6919ee, "stbux r27, r9, r3"); + _testAsm(0x7c0531ae, "stbx r0, r5, r6"); + _testAsm(0x7d4941ae, "stbx r10, r9, r8"); + _testAsm(0x7fdfe9ae, "stbx r30, r31, r29"); + _testAsm(0xdb690038, "stfd f27, 0x38(r9)"); + _testAsm(0xdb890040, "stfd f28, 0x40(r9)"); + _testAsm(0xde230008, "stfdu f17, 8(r3)"); + _testAsm(0x7c1235ae, "stfdx f0, r18, r6"); + _testAsm(0x7c1f3dae, "stfdx f0, r31, r7"); + _testAsm(0x7c1f45ae, "stfdx f0, r31, r8"); + _testAsm(0x7c001fae, "stfiwx f0, 0, r3"); + _testAsm(0x7c3e4fae, "stfiwx f1, r30, r9"); + _testAsm(0x7fa04fae, "stfiwx f29, 0, r9"); + _testAsm(0xd008fffc, "stfs f0, -4(r8)"); + _testAsm(0xd3ff1604, "stfs f31, 0x1604(r31)"); + _testAsm(0xd41e001c, "stfsu f0, 0x1C(r30)"); + _testAsm(0x7d87356e, "stfsux f12, r7, r6"); + _testAsm(0x7c104d2e, "stfsx f0, r16, r9"); + _testAsm(0x7f5d452e, "stfsx f26, r29, r8"); + _testAsm(0xb3bf000e, "sth r29, 0xE(r31)"); + _testAsm(0xb3f8fffc, "sth r31, -4(r24)"); + _testAsm(0xb3fa0000, "sth r31, 0(r26)"); + _testAsm(0xb3fd0000, "sth r31, 0(r29)"); + _testAsm(0xb528105a, "sthu r9, 0x105A(r8)"); + _testAsm(0xb52afffe, "sthu r9, -2(r10)"); + _testAsm(0xb53f0002, "sthu r9, 2(r31)"); + _testAsm(0x7c69536e, "sthux r3, r9, r10"); + _testAsm(0x7c8a3b6e, "sthux r4, r10, r7"); + _testAsm(0x7c0ca32e, "sthx r0, r12, r20"); + _testAsm(0x7d242b2e, "sthx r9, r4, r5"); + _testAsm(0x7fe93b2e, "sthx r31, r9, r7"); + _testAsm(0xbdd0b4d0, "stmw r14, -0x4B30(r16)"); + _testAsm(0xbed182d1, "stmw r22, -0x7D2F(r17)"); + _testAsm(0x7cbb65aa, "stswi r5, r27, 0xC"); + _testAsm(0x7cbe05aa, "stswi r5, r30, 0"); + _testAsm(0x91348378, "stw r9, -0x7C88(r20)"); + _testAsm(0x93e9fddc, "stw r31, -0x224(r9)"); + _testAsm(0x93fe45c8, "stw r31, 0x45C8(r30)"); + _testAsm(0x7c604d2c, "stwbrx r3, 0, r9"); + _testAsm(0x7d20f52c, "stwbrx r9, 0, r30"); + _testAsm(0x7fa04d2c, "stwbrx r29, 0, r9"); + _testAsm(0x7d20512d, "stwcx. r9, 0, r10"); + _testAsm(0x94650208, "stwu r3, 0x208(r5)"); + _testAsm(0x953ffffc, "stwu r9, -4(r31)"); + _testAsm(0x97fe0208, "stwu r31, 0x208(r30)"); + _testAsm(0x7d21196e, "stwux r9, r1, r3"); + _testAsm(0x7d23516e, "stwux r9, r3, r10"); + _testAsm(0x7d41496e, "stwux r10, r1, r9"); + _testAsm(0x7cbc512e, "stwx r5, r28, r10"); + _testAsm(0x7d5ce92e, "stwx r10, r28, r29"); + _testAsm(0x7ffee92e, "stwx r31, r30, r29"); + _testAsm(0x7c1ec050, "subf r0, r30, r24"); + _testAsm(0x7fbee050, "subf r29, r30, r28"); + _testAsm(0x7fe91850, "subf r31, r9, r3"); + _testAsm(0x7c1e5851, "subf. r0, r30, r11"); + _testAsm(0x7fc42851, "subf. r30, r4, r5"); + _testAsm(0x7fc74851, "subf. r30, r7, r9"); + _testAsm(0x7cffd810, "subfc r7, r31, r27"); + _testAsm(0x7d053010, "subfc r8, r5, r6"); + _testAsm(0x7d093810, "subfc r8, r9, r7"); + _testAsm(0x7f884910, "subfe r28, r8, r9"); + _testAsm(0x7fe95110, "subfe r31, r9, r10"); + _testAsm(0x7fea4910, "subfe r31, r10, r9"); + _testAsm(0x20d0b2d1, "subfic r6, r16, -0x4D2F"); + _testAsm(0x2109fc02, "subfic r8, r9, -0x3FE"); + _testAsm(0x2144001f, "subfic r10, r4, 0x1F"); + _testAsm(0x7c04f278, "xor r4, r0, r30"); + _testAsm(0x7c08fa78, "xor r8, r0, r31"); + _testAsm(0x7c0b4a78, "xor r11, r0, r9"); + _testAsm(0x68640008, "xori r4, r3, 8"); + _testAsm(0x68652064, "xori r5, r3, 0x2064"); + _testAsm(0x692076c3, "xori r0, r9, 0x76C3"); + _testAsm(0x6c0a8000, "xoris r10, r0, 0x8000"); + _testAsm(0x6c68ffff, "xoris r8, r3, 0xFFFF"); + _testAsm(0x6c617267, "xoris r1, r3, 0x7267"); + _testAsm(0x6fe98000, "xoris r9, r31, 0x8000"); + _testAsm(0x6fe9f000, "xoris r9, r31, 0xF000"); + _testAsm(0x6fe9ff00, "xoris r9, r31, 0xFF00"); + _testAsm(0x6fe9ffff, "xoris r9, r31, 0xFFFF"); + + // data directives + _testAsmArray({ 0x00, 0x00, 0x00, 0x01 }, ".int 1"); + _testAsmArray({ 0x00, 0x00, 0x00, 0x01, 0x11, 0x22, 0x33, 0x44 }, ".int 1, 0x11223344"); + _testAsmArray({ 0x42, 0xf6, 0x00, 0x00 }, ".float 123.0"); + _testAsmArray({ 0x7f }, ".byte 0x7f"); + _testAsmArray({ 0x74, 0x65, 0x73, 0x74, 0x00 }, ".byte \"test\""); + _testAsmArray({ 0x41, 0x42, 0x43, 0x00, 0x74, 0x65, 0x73, 0x74, 0x00 }, ".byte \"ABC\", \"test\""); + +} + +void ppcAsmTest() +{ +#ifndef PUBLIC_RELEASE + ppcAsmTestDisassembler(); +#endif +} diff --git a/src/Cemu/PPCAssembler/ppcAssembler.h b/src/Cemu/PPCAssembler/ppcAssembler.h new file mode 100644 index 00000000..c47e7efd --- /dev/null +++ b/src/Cemu/PPCAssembler/ppcAssembler.h @@ -0,0 +1,368 @@ +#pragma once + +#include <boost/container/small_vector.hpp> + +#define PPCASM_OPERAND_COUNT 5 + +#define PPCASM_OPERAND_TYPE_GPR 0 // r0 - r31 +#define PPCASM_OPERAND_TYPE_FPR 1 // f0 - f31 +#define PPCASM_OPERAND_TYPE_SPR 2 // spr0 - spr511 +#define PPCASM_OPERAND_TYPE_IMM 3 // integer constants. E.g. 0x123 +#define PPCASM_OPERAND_TYPE_MEM 4 // [r0 + 1234] +#define PPCASM_OPERAND_TYPE_CIMM 5 // virtual addr of code destination (used for branches) +#define PPCASM_OPERAND_TYPE_CR 6 // cr0-cr7 +#define PPCASM_OPERAND_TYPE_CR_BIT 7 // cr bit 0-31. Example display form: '4*cr1+eq' +#define PPCASM_OPERAND_TYPE_PSQMODE 8 // single or paired mode control for PSQ_L*/PSQ_ST* instructions + +enum PPCASM_OP +{ + PPCASM_OP_UKN, + + PPCASM_OP_ADDI, + PPCASM_OP_SUBI, // special form of ADDI + PPCASM_OP_ADDIS, + PPCASM_OP_ADDIC, + PPCASM_OP_ADDIC_, + + PPCASM_OP_ADD, + PPCASM_OP_ADD_, + PPCASM_OP_SUBF, + PPCASM_OP_SUBF_, + PPCASM_OP_SUBFC, + PPCASM_OP_SUBFC_, + PPCASM_OP_SUBFE, + PPCASM_OP_SUBFE_, + PPCASM_OP_SUBFIC, + + PPCASM_OP_SUB, // alias mnemonic for subf with second and third operand swapped + PPCASM_OP_SUB_, + + PPCASM_OP_MULLI, + PPCASM_OP_MULLW, + PPCASM_OP_MULLW_, + PPCASM_OP_MULHW, + PPCASM_OP_MULHW_, + PPCASM_OP_MULHWU, + PPCASM_OP_MULHWU_, + + PPCASM_OP_DIVW, + PPCASM_OP_DIVW_, + PPCASM_OP_DIVWU, + PPCASM_OP_DIVWU_, + + PPCASM_OP_AND, + PPCASM_OP_AND_, + PPCASM_OP_ANDC, + PPCASM_OP_ANDC_, + PPCASM_OP_OR, + PPCASM_OP_OR_, + PPCASM_OP_ORC, + PPCASM_OP_XOR, + PPCASM_OP_NOR, + PPCASM_OP_NOR_, + PPCASM_OP_NOT, // alias to NOR + PPCASM_OP_NOT_, + PPCASM_OP_NEG, + PPCASM_OP_NEG_, + PPCASM_OP_ANDI_, + PPCASM_OP_ANDIS_, + PPCASM_OP_ORI, + PPCASM_OP_ORIS, + PPCASM_OP_XORI, + PPCASM_OP_XORIS, + PPCASM_OP_CNTLZW, + PPCASM_OP_EXTSB, + PPCASM_OP_EXTSH, + PPCASM_OP_CNTLZW_, + PPCASM_OP_EXTSB_, + PPCASM_OP_EXTSH_, + PPCASM_OP_SRAW, + PPCASM_OP_SRAW_, + PPCASM_OP_SRAWI, + PPCASM_OP_SRAWI_, + PPCASM_OP_SLW, + PPCASM_OP_SLW_, + PPCASM_OP_SRW, + PPCASM_OP_SRW_, + PPCASM_OP_RLWINM, + PPCASM_OP_RLWINM_, + PPCASM_OP_RLWIMI, + PPCASM_OP_RLWIMI_, + + // rlwinm extended mnemonics + PPCASM_OP_EXTLWI, + PPCASM_OP_EXTLWI_, + PPCASM_OP_EXTRWI, + PPCASM_OP_EXTRWI_, + + PPCASM_OP_ROTLWI, + PPCASM_OP_ROTLWI_, + PPCASM_OP_ROTRWI, + PPCASM_OP_ROTRWI_, + + PPCASM_OP_SLWI, + PPCASM_OP_SLWI_, + PPCASM_OP_SRWI, + PPCASM_OP_SRWI_, + PPCASM_OP_CLRLWI, + PPCASM_OP_CLRLWI_, + PPCASM_OP_CLRRWI, + PPCASM_OP_CLRRWI_, + + // rlwimi extended mnemonics + //PPCASM_OP_INSLWI, rlwimi + //PPCASM_OP_INSLWI_, rlwimi + //PPCASM_OP_INSRWI, rlwimi + //PPCASM_OP_INSRWI_, rlwimi + + // rlwnm extended mnemonics + PPCASM_OP_RLWNM, + PPCASM_OP_RLWNM_, + PPCASM_OP_ROTLW, + PPCASM_OP_ROTLW_, + + PPCASM_OP_CMPWI, + PPCASM_OP_CMPLWI, + + PPCASM_OP_CMPW, + PPCASM_OP_CMPLW, + + PPCASM_OP_MR, + PPCASM_OP_MR_, + + PPCASM_OP_MFSPR, + PPCASM_OP_MTSPR, + + PPCASM_OP_LMW, + PPCASM_OP_LWZ, + PPCASM_OP_LWZU, + PPCASM_OP_LWZX, + PPCASM_OP_LWZUX, + PPCASM_OP_LHZ, + PPCASM_OP_LHZU, + PPCASM_OP_LHZX, + PPCASM_OP_LHZUX, + PPCASM_OP_LHA, + PPCASM_OP_LHAU, + PPCASM_OP_LHAX, + PPCASM_OP_LHAUX, + PPCASM_OP_LBZ, + PPCASM_OP_LBZU, + PPCASM_OP_LBZX, + PPCASM_OP_LBZUX, + + PPCASM_OP_STMW, + PPCASM_OP_STW, + PPCASM_OP_STWU, + PPCASM_OP_STWX, + PPCASM_OP_STWUX, + PPCASM_OP_STH, + PPCASM_OP_STHU, + PPCASM_OP_STHX, + PPCASM_OP_STHUX, + PPCASM_OP_STB, + PPCASM_OP_STBU, + PPCASM_OP_STBX, + PPCASM_OP_STBUX, + PPCASM_OP_STWBRX, + PPCASM_OP_STHBRX, + PPCASM_OP_STSWI, + PPCASM_OP_LWARX, + PPCASM_OP_STWCX_, + + PPCASM_OP_B, + PPCASM_OP_BA, + PPCASM_OP_BL, + PPCASM_OP_BLA, + + PPCASM_OP_BC, + PPCASM_OP_BNE, + PPCASM_OP_BEQ, + PPCASM_OP_BGE, + PPCASM_OP_BGT, + PPCASM_OP_BLT, + PPCASM_OP_BLE, + PPCASM_OP_BDZ, + PPCASM_OP_BDNZ, + + PPCASM_OP_BLR, + PPCASM_OP_BLTLR, // less + PPCASM_OP_BLELR, // less or equal + PPCASM_OP_BEQLR, // equal + PPCASM_OP_BGELR, // greater or equal + PPCASM_OP_BGTLR, // greater + PPCASM_OP_BNELR, // not equal + + PPCASM_OP_BCTR, + PPCASM_OP_BCTRL, + + // CR + PPCASM_OP_CROR, + PPCASM_OP_CRORC, + PPCASM_OP_CRXOR, + PPCASM_OP_CREQV, + PPCASM_OP_CRNOR, + PPCASM_OP_CRAND, + PPCASM_OP_CRNAND, + PPCASM_OP_CRANDC, + PPCASM_OP_CRSET, // simplified mnemonic for CREQV + PPCASM_OP_CRCLR, // simplified mnemonic for CRXOR + PPCASM_OP_CRMOVE, // simplified mnemonic for CROR + PPCASM_OP_CRNOT, // simplified mnemonic for CRNOR + + // floating point load/store + PPCASM_OP_LFS, + PPCASM_OP_LFSU, + PPCASM_OP_LFSX, + PPCASM_OP_LFSUX, + PPCASM_OP_LFD, + PPCASM_OP_LFDU, + PPCASM_OP_LFDX, + PPCASM_OP_LFDUX, + + PPCASM_OP_STFS, + PPCASM_OP_STFSU, + PPCASM_OP_STFSX, + PPCASM_OP_STFSUX, + PPCASM_OP_STFD, + PPCASM_OP_STFDU, + PPCASM_OP_STFDX, + PPCASM_OP_STFDUX, + + PPCASM_OP_STFIWX, + + // FP + PPCASM_OP_FMR, + PPCASM_OP_FNEG, + PPCASM_OP_FRSP, + PPCASM_OP_FRSQRTE, + PPCASM_OP_FADD, + PPCASM_OP_FADDS, + PPCASM_OP_FSUB, + PPCASM_OP_FSUBS, + PPCASM_OP_FMUL, + PPCASM_OP_FMULS, + PPCASM_OP_FDIV, + PPCASM_OP_FDIVS, + + PPCASM_OP_FMADD, + PPCASM_OP_FMADDS, + PPCASM_OP_FNMADD, + PPCASM_OP_FNMADDS, + PPCASM_OP_FMSUB, + PPCASM_OP_FMSUBS, + PPCASM_OP_FNMSUB, + PPCASM_OP_FNMSUBS, + + PPCASM_OP_FCTIWZ, + + PPCASM_OP_FCMPU, + PPCASM_OP_FCMPO, + + // PS + PPCASM_OP_PS_MERGE00, + PPCASM_OP_PS_MERGE01, + PPCASM_OP_PS_MERGE10, + PPCASM_OP_PS_MERGE11, + + PPCASM_OP_PS_ADD, + PPCASM_OP_PS_SUB, + PPCASM_OP_PS_DIV, + PPCASM_OP_PS_MUL, + + PPCASM_OP_PS_MADD, + PPCASM_OP_PS_MSUB, + PPCASM_OP_PS_NMADD, + PPCASM_OP_PS_NMSUB, + + // cache & misc + PPCASM_OP_ISYNC, + + // extended mnemonics + PPCASM_OP_NOP, // ORI + PPCASM_OP_LI, // ADDI + PPCASM_OP_LIS, // ADDIS + PPCASM_OP_MFLR, // MFSPR + PPCASM_OP_MTLR, // MTSPR + PPCASM_OP_MFCTR, // MFSPR + PPCASM_OP_MTCTR, // MTSPR +}; + +struct PPCDisassemblerOperand +{ + uint8 type; + uint16 registerIndex; + union + { + sint32 immS32; + uint32 immU32; + }; + bool isSignedImm; + uint8 immWidth; // in bits. E.g. 16 for ADDI +}; + +struct PPCDisassembledInstruction +{ + // output + uint32 ppcAsmCode; + uint8 operandMask; + PPCDisassemblerOperand operand[PPCASM_OPERAND_COUNT]; + // conditional branches + bool branchHintBitSet{false}; +}; + +const char* ppcAssembler_getInstructionName(uint32 ppcAsmOp); + +void ppcAssembler_disassemble(uint32 virtualAddress, uint32 opcode, PPCDisassembledInstruction* disInstr); + +enum class PPCASM_RELOC +{ + // code + U32_MASKED_IMM, + BRANCH_S16, + BRANCH_S26, + // data constants + FLOAT, // 4 byte float data + DOUBLE, // 8 byte double precision float data + U32, // 4 byte unsigned integer + U16, // 2 byte unsigned integer + U8, // 1 byte unsigned integer + +}; + +struct PPCAssemblerReloc +{ + PPCAssemblerReloc(PPCASM_RELOC relocType, std::string expression, uint32 byteOffset, uint8 bitOffset, uint8 bitCount) : m_relocType(relocType), m_expression(expression), m_byteOffset(byteOffset), m_bitOffset(bitOffset), m_bitCount(bitCount) {}; + PPCASM_RELOC m_relocType; + std::string m_expression; + uint32 m_byteOffset; + uint8 m_bitOffset; + uint8 m_bitCount; + bool m_isApplied{}; + bool isApplied() { return m_isApplied; }; + void setApplied() { m_isApplied = true; }; +}; + +struct PPCAssemblerInOut +{ + // in + uint32 virtualAddress; + bool forceNoAlignment{}; // if set, alignment will always be set to 1 (even for .align directive!) + // out + boost::container::small_vector<uint8, 16> outputData; + std::vector<PPCAssemblerReloc> list_relocs; + std::string errorMsg; + uint32 alignmentRequirement{}; // alignment requirement, 0 if none + uint32 alignmentPaddingSize{}; // number of bytes to fill with alignment padding before instruction + uint32 virtualAddressAligned{}; // effective virtualAddress +}; + +bool ppcAssembler_assembleSingleInstruction(char const* text, PPCAssemblerInOut* ctx); + +static uint32 ppcAssembler_generateMaskRLW(int MB, int ME) +{ + uint32 maskMB = 0xFFFFFFFF >> MB; + uint32 maskME = 0xFFFFFFFF << (31 - ME); + uint32 mask = (MB <= ME) ? maskMB & maskME : maskMB | maskME; + return mask; +} \ No newline at end of file diff --git a/src/Cemu/Tools/DownloadManager/DownloadManager.cpp b/src/Cemu/Tools/DownloadManager/DownloadManager.cpp new file mode 100644 index 00000000..8a0aa895 --- /dev/null +++ b/src/Cemu/Tools/DownloadManager/DownloadManager.cpp @@ -0,0 +1,1559 @@ +#include "Cemu/Tools/DownloadManager/DownloadManager.h" + +#include "Cafe/Account/Account.h" +#include "gui/CemuApp.h" +#include "util/crypto/md5.h" +#include "Cafe/TitleList/TitleId.h" +#include "Common/filestream.h" +#include "Cemu/FileCache/FileCache.h" +#include "Cemu/ncrypto/ncrypto.h" +#include "config/ActiveSettings.h" +#include "util/ThreadPool/ThreadPool.h" +#include "util/helpers/enum_array.hpp" +#include "gui/MainWindow.h" + +#include "Cafe/Filesystem/FST/FST.h" +#include "Cafe/TitleList/TitleList.h" + +#include <charconv> +#include <curl/curl.h> +#include <pugixml.hpp> + +#include "gui/helpers/wxHelpers.h" + +#include "Cemu/napi/napi.h" +#include "util/helpers/Serializer.h" + +FileCache* s_nupFileCache = nullptr; + +/* version list */ + +void DownloadManager::downloadTitleVersionList() +{ + if (m_hasTitleVersionList) + return; + NAPI::AuthInfo authInfo; + authInfo.accountId = m_authInfo.nnidAccountName; + authInfo.passwordHash = m_authInfo.passwordHash; + authInfo.deviceId = m_authInfo.deviceId; + authInfo.serial = m_authInfo.serial; + authInfo.country = m_authInfo.country; + authInfo.region = m_authInfo.region; + authInfo.deviceCertBase64 = m_authInfo.deviceCertBase64; + auto versionListVersionResult = NAPI::TAG_GetVersionListVersion(authInfo); + if (!versionListVersionResult.isValid) + return; + auto versionListResult = TAG_GetVersionList(authInfo, versionListVersionResult.fqdnURL, versionListVersionResult.version); + if (!versionListResult.isValid) + return; + m_titleVersionList = versionListResult.titleVersionList; + m_hasTitleVersionList = true; +} + +// grab latest version from TAG version list. Returns false if titleId is not found in the list +bool DownloadManager::getTitleLatestVersion(TitleId titleId, uint16& version) +{ + auto titleVersionAvailability = m_titleVersionList.find(titleId); + if (titleVersionAvailability == m_titleVersionList.end()) + return false; + version = titleVersionAvailability->second; + return true; +} + +/* helper method to generate list of owned base titles */ + +DownloadManager::TitleInstallState::TitleInstallState(DownloadManager* dlMgr, uint64 titleId) : titleId(titleId) +{ + uint16 vers; + if (CafeTitleList::HasTitle(titleId, vers)) + { + isInstalled = true; + installedTitleVersion = vers; + } + else + { + isInstalled = false; + installedTitleVersion = 0; + } + installedTicketVersion = 0; + // for DLC we also retrieve the ticket version from the installed title.tik + // todo - avoid file reads here due to stalling the UI thread + if (TitleIdParser(titleId).GetType() == TitleIdParser::TITLE_TYPE::AOC) + { + const auto ticketPath = ActiveSettings::GetMlcPath(L"usr/title/{:08x}/{:08x}/code/title.tik", (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF)); + uint32 tikFileSize = 0; + FileStream* fileStream = FileStream::openFile2(ticketPath); + if (fileStream) + { + std::vector<uint8> tikData; + fileStream->extract(tikData); + NCrypto::ETicketParser eTicket; + if (eTicket.parse(tikData.data(), tikData.size())) + installedTicketVersion = eTicket.GetTicketVersion(); + delete fileStream; + } + } +} + +DownloadManager::TitleDownloadAvailableState::TitleDownloadAvailableState(DownloadManager* dlMgr, uint64 titleId) : TitleInstallState(dlMgr, titleId) +{ + // get latest available version of this title (from the TAG version list) + uint16 vers; + if (dlMgr->getTitleLatestVersion(titleId, vers)) + { + isUpdateAvailable = true; + availableTitleVersion = vers; + } + else + { + isUpdateAvailable = false; + availableTitleVersion = 0; + } +}; + +std::set<DownloadManager::TitleInstallState> DownloadManager::getOwnedTitleList() +{ + std::set<DownloadManager::TitleInstallState> ownedTitleList; + // add installed games and DLC + std::vector<TitleId> installedTitleIds = CafeTitleList::GetAllTitleIds(); + for (auto& itr : installedTitleIds) + { + TitleIdParser titleIdParser(itr); + auto titleType = titleIdParser.GetType(); + if (titleType == TitleIdParser::TITLE_TYPE::BASE_TITLE || + titleType == TitleIdParser::TITLE_TYPE::BASE_TITLE_DEMO || + titleType == TitleIdParser::TITLE_TYPE::SYSTEM_OVERLAY_TITLE) + { + ownedTitleList.emplace(this, itr); + } + } + // add ticket cache + for (auto& itr : m_ticketCache) + { + TitleIdParser titleIdParser(itr.titleId); + auto titleType = titleIdParser.GetType(); + if (titleType == TitleIdParser::TITLE_TYPE::BASE_TITLE || + titleType == TitleIdParser::TITLE_TYPE::BASE_TITLE_DEMO || + titleType == TitleIdParser::TITLE_TYPE::SYSTEM_OVERLAY_TITLE || + titleType == TitleIdParser::TITLE_TYPE::AOC) + { + ownedTitleList.emplace(this, itr.titleId); + } + } + + return ownedTitleList; +} + +std::set<DownloadManager::TitleDownloadAvailableState> DownloadManager::getFullDownloadList() +{ + std::set<DownloadManager::TitleDownloadAvailableState> fullList; + // get list of owned titles + std::set<DownloadManager::TitleInstallState> ownedTitleList = getOwnedTitleList(); + // add each owned title, but also check for separate updates if available + for (auto& itr : ownedTitleList) + { + TitleIdParser titleIdParser(itr.titleId); + fullList.emplace(this, itr.titleId); + if (titleIdParser.CanHaveSeparateUpdateTitleId()) + { + uint64 updateTitleId = titleIdParser.GetSeparateUpdateTitleId(); + uint16 tempVers; + if (getTitleLatestVersion(updateTitleId, tempVers)) + fullList.emplace(this, updateTitleId); + } + } + return fullList; +} + +/* connect */ + +struct StoredTokenInfo : public SerializerHelper +{ + bool serializeImpl(MemStreamWriter& streamWriter) + { + streamWriter.writeBE<uint8>(0); + streamWriter.writeBE<std::string>(accountId); + streamWriter.writeBE<std::string>(deviceToken); + return true; + } + + bool deserializeImpl(MemStreamReader& streamReader) + { + auto version = streamReader.readBE<uint8>(); + if (version != 0) + return false; + accountId = streamReader.readBE<std::string>(); + deviceToken = streamReader.readBE<std::string>(); + return !streamReader.hasError(); + } + +public: + std::string accountId; + std::string deviceToken; +}; + +bool DownloadManager::_connect_refreshIASAccountIdAndDeviceToken() +{ + NAPI::AuthInfo authInfo; + authInfo.accountId = m_authInfo.nnidAccountName; + authInfo.passwordHash = m_authInfo.passwordHash; + authInfo.deviceId = m_authInfo.deviceId; + authInfo.serial = m_authInfo.serial; + authInfo.country = m_authInfo.country; + authInfo.region = m_authInfo.region; + authInfo.deviceCertBase64 = m_authInfo.deviceCertBase64; + + // query IAS/ECS account id and device token (if not cached) + auto rChallenge = NAPI::IAS_GetChallenge(authInfo); + if (rChallenge.apiError != NAPI_RESULT::SUCCESS) + return false; + auto rRegistrationInfo = NAPI::IAS_GetRegistrationInfo_QueryInfo(authInfo, rChallenge.challenge); + if (rRegistrationInfo.apiError != NAPI_RESULT::SUCCESS) + return false; + + m_iasToken.serviceAccountId = rRegistrationInfo.accountId; + m_iasToken.deviceToken = rRegistrationInfo.deviceToken; + // store to cache + StoredTokenInfo storedTokenInfo; + storedTokenInfo.accountId = rRegistrationInfo.accountId; + storedTokenInfo.deviceToken = rRegistrationInfo.deviceToken; + std::vector<uint8> serializedData; + if (!storedTokenInfo.serialize(serializedData)) + return false; + s_nupFileCache->AddFileAsync({ fmt::format("{}/token_info", m_authInfo.nnidAccountName) }, serializedData.data(), serializedData.size()); + return true; +} + +bool DownloadManager::_connect_queryAccountStatusAndServiceURLs() +{ + NAPI::AuthInfo authInfo; + authInfo.accountId = m_authInfo.nnidAccountName; + authInfo.passwordHash = m_authInfo.passwordHash; + authInfo.deviceId = m_authInfo.deviceId; + authInfo.serial = m_authInfo.serial; + authInfo.country = m_authInfo.country; + authInfo.region = m_authInfo.region; + authInfo.deviceCertBase64 = m_authInfo.deviceCertBase64; + + authInfo.IASToken.accountId = m_iasToken.serviceAccountId; + authInfo.IASToken.deviceToken = m_iasToken.deviceToken; + + NAPI::NAPI_ECSGetAccountStatus_Result accountStatusResult = NAPI::ECS_GetAccountStatus(authInfo); + if (accountStatusResult.apiError != NAPI_RESULT::SUCCESS) + { + cemuLog_log(LogType::Force, fmt::format("ECS - Failed to query account status (error: {0} {1})", accountStatusResult.apiError, accountStatusResult.serviceError)); + return false; + } + if (accountStatusResult.accountStatus == NAPI::NAPI_ECSGetAccountStatus_Result::AccountStatus::UNREGISTERED) + { + cemuLog_log(LogType::Force, fmt::format("ECS - Account is not registered")); + return false; + } + + return true; +} + +// constructor for ticket cache entry +DownloadManager::ETicketInfo::ETicketInfo(SOURCE source, uint64 ticketId, uint32 ticketVersion, std::vector<uint8>& eTicket) + : source(source), ticketId(ticketId), ticketVersion(ticketVersion), + eTicket(eTicket) +{ + NCrypto::ETicketParser eTicketParser; + if (!eTicketParser.parse(eTicket.data(), eTicket.size())) + { + titleId = (uint64)-1; + return; + } + + cemu_assert_debug(!eTicketParser.IsPersonalized()); // ticket should have been depersonalized already + titleId = eTicketParser.GetTitleId(); + ticketVersion = eTicketParser.GetTicketVersion(); + cemu_assert_debug(ticketId == eTicketParser.GetTicketId()); + cemu_assert_debug(ticketVersion == eTicketParser.GetTicketVersion()); +} + +DownloadManager::ETicketInfo::ETicketInfo(SOURCE source, uint64 ticketId, uint32 ticketVersion, std::vector<uint8>& eTicket, std::vector<std::vector<uint8>>& eTicketCerts) + : ETicketInfo(source, ticketId, ticketVersion, eTicket) +{ + this->eTicketCerts = eTicketCerts; +} + +void DownloadManager::ETicketInfo::GetTitleKey(NCrypto::AesKey& key) +{ + NCrypto::ETicketParser eTicketParser; + cemu_assert_debug(eTicketParser.parse(eTicket.data(), eTicket.size())); + eTicketParser.GetTitleKey(key); +} + +void DownloadManager::loadTicketCache() +{ + m_ticketCache.clear(); + cemu_assert_debug(m_ticketCache.empty()); + std::vector<uint8> ticketCacheBlob; + if (!s_nupFileCache->GetFile({ fmt::format("{}/eticket_cache", m_authInfo.nnidAccountName) }, ticketCacheBlob)) + return; + MemStreamReader memReader(ticketCacheBlob.data(), ticketCacheBlob.size()); + uint8 version = memReader.readBE<uint8>(); + if (version != 1) + return; // unsupported version + uint32 numTickets = memReader.readBE<uint32>(); + for (uint32 i = 0; i < numTickets; i++) + { + if (memReader.hasError()) + { + m_ticketCache.clear(); + return; + } + ETicketInfo::SOURCE source = (ETicketInfo::SOURCE)memReader.readBE<uint8>(); + if (source != ETicketInfo::SOURCE::ECS_TICKET && source != ETicketInfo::SOURCE::PUBLIC_TICKET) + { + m_ticketCache.clear(); + return; + } + uint64 ticketId = memReader.readBE<uint64>(); + uint64 ticketVersion = memReader.readBE<uint32>(); + std::vector<uint8> eTicketData = memReader.readPODVector<uint8>(); + std::vector<std::vector<uint8>> eTicketCerts; + uint8 certCount = memReader.readBE<uint8>(); + for (uint32 c = 0; c < certCount; c++) + eTicketCerts.emplace_back(memReader.readPODVector<uint8>()); + if (memReader.hasError()) + { + m_ticketCache.clear(); + return; + } + m_ticketCache.emplace_back(source, ticketId, ticketVersion, eTicketData, eTicketCerts); + } +} + +void DownloadManager::storeTicketCache() +{ + MemStreamWriter memWriter(1024*32); + memWriter.writeBE<uint8>(1); // version + memWriter.writeBE<uint32>((uint32)m_ticketCache.size()); + for (auto& eTicket : m_ticketCache) + { + memWriter.writeBE<uint8>((uint8)eTicket.source); + memWriter.writeBE<uint64>(eTicket.ticketId); + memWriter.writeBE<uint32>(eTicket.ticketVersion); + memWriter.writePODVector(eTicket.eTicket); + memWriter.writeBE<uint8>(eTicket.eTicketCerts.size()); + for (auto& cert : eTicket.eTicketCerts) + memWriter.writePODVector(cert); + } + auto serializedBlob = memWriter.getResult(); + s_nupFileCache->AddFileAsync({ fmt::format("{}/eticket_cache", m_authInfo.nnidAccountName) }, serializedBlob.data(), serializedBlob.size()); +} + +bool DownloadManager::syncAccountTickets() +{ + NAPI::AuthInfo authInfo; + authInfo.accountId = m_authInfo.nnidAccountName; + authInfo.passwordHash = m_authInfo.passwordHash; + authInfo.deviceId = m_authInfo.deviceId; + authInfo.serial = m_authInfo.serial; + authInfo.country = m_authInfo.country; + authInfo.region = m_authInfo.region; + authInfo.deviceCertBase64 = m_authInfo.deviceCertBase64; + + authInfo.IASToken.accountId = m_iasToken.serviceAccountId; + authInfo.IASToken.deviceToken = m_iasToken.deviceToken; + + // query TIV list from server + NAPI::NAPI_ECSAccountListETicketIds_Result resultTicketIds = NAPI::ECS_AccountListETicketIds(authInfo); + if (!resultTicketIds.isValid()) + return false; + + // download uncached tickets + size_t count = resultTicketIds.tivs.size(); + size_t index = 0; + for (auto& tiv : resultTicketIds.tivs) + { + index++; + std::string msg = _("Downloading account ticket").ToStdString(); + msg.append(fmt::format(" {0}/{1}", index, count)); + setStatusMessage(msg, DLMGR_STATUS_CODE::CONNECTING); + // skip if already cached + ETicketInfo* cachedTicket = findTicketByTicketId(tiv.ticketId); + if (cachedTicket) + { + if(cachedTicket->ticketVersion == tiv.ticketVersion) + continue; + // ticket version mismatch, redownload + deleteTicketByTicketId(tiv.ticketId); + } + // get ECS ticket + auto resultETickets = NAPI::ECS_AccountGetETickets(authInfo, tiv.ticketId); + if (!resultETickets.isValid()) + { + cemuLog_log(LogType::Force, "SyncTicketCache: Account ETicket invalid"); + continue; + } + // verify ticket integrity + NCrypto::ETicketParser eTicketParser; + if (!eTicketParser.parse(resultETickets.eTickets.data(), resultETickets.eTickets.size())) + continue; + uint64 titleId = eTicketParser.GetTitleId(); + cemu_assert_debug(eTicketParser.GetTicketId() == tiv.ticketId); + cemu_assert_debug(eTicketParser.GetTicketVersion() == tiv.ticketVersion); + + // depersonalize the ticket + if (eTicketParser.IsPersonalized()) + { + NCrypto::ECCPrivKey privKey = NCrypto::ECCPrivKey::getDeviceCertPrivateKey(); + if (!eTicketParser.Depersonalize(resultETickets.eTickets.data(), resultETickets.eTickets.size(), m_authInfo.deviceId, privKey)) + { + cemuLog_log(LogType::Force, "DownloadManager: Failed to depersonalize ticket"); + continue; + } + // reparse + cemu_assert(eTicketParser.parse(resultETickets.eTickets.data(), resultETickets.eTickets.size())); + } + else + { + cemuLog_log(LogType::Force, "DownloadManager: Unexpected result. ECS ticket not personalized"); + continue; + } + + ETicketInfo eTicket(ETicketInfo::SOURCE::ECS_TICKET, tiv.ticketId, tiv.ticketVersion, resultETickets.eTickets, resultETickets.certs); + m_ticketCache.emplace_back(eTicket); + } + return true; +} + +bool DownloadManager::syncSystemTitleTickets() +{ + setStatusMessage(std::string(_("Downloading system tickets...")), DLMGR_STATUS_CODE::CONNECTING); + // todo - add GetAuth() function + NAPI::AuthInfo authInfo; + authInfo.accountId = m_authInfo.nnidAccountName; + authInfo.passwordHash = m_authInfo.passwordHash; + authInfo.deviceId = m_authInfo.deviceId; + authInfo.serial = m_authInfo.serial; + authInfo.country = m_authInfo.country; + authInfo.region = m_authInfo.region; + authInfo.deviceCertBase64 = m_authInfo.deviceCertBase64; + + authInfo.IASToken.accountId = m_iasToken.serviceAccountId; + authInfo.IASToken.deviceToken = m_iasToken.deviceToken; + + auto querySystemTitleTicket = [&](uint64 titleId) -> void + { + // check if cached already + // todo - how do we know which version to query? System titles seem to use hashes? + if (findFirstTicketByTitleId(titleId)) + return; + // request ticket + auto resultCommonETicket = NAPI::NUS_GetSystemCommonETicket(authInfo, titleId); + if (!resultCommonETicket.isValid()) + return; + // parse and validate ticket + NCrypto::ETicketParser eTicketParser; + if (!eTicketParser.parse(resultCommonETicket.eTicket.data(), resultCommonETicket.eTicket.size())) + return; + if (eTicketParser.GetTitleId() != titleId) + return; + if (eTicketParser.IsPersonalized()) + return; + // add to eTicket cache + ETicketInfo eTicket(ETicketInfo::SOURCE::PUBLIC_TICKET, eTicketParser.GetTicketId(), eTicketParser.GetTicketVersion(), resultCommonETicket.eTicket, resultCommonETicket.certs); + m_ticketCache.emplace_back(eTicket); + }; + + if (m_authInfo.region == CafeConsoleRegion::EUR) + { + querySystemTitleTicket(0x000500301001420A); // eShop + querySystemTitleTicket(0x000500301001520A); // Friend List + querySystemTitleTicket(0x000500301001220A); // Internet browser + } + else if (m_authInfo.region == CafeConsoleRegion::USA) + { + querySystemTitleTicket(0x000500301001410A); // eShop + querySystemTitleTicket(0x000500301001510A); // Friend List + querySystemTitleTicket(0x000500301001210A); // Internet browser + } + else if (m_authInfo.region == CafeConsoleRegion::JPN) + { + querySystemTitleTicket(0x000500301001400A); // eShop + querySystemTitleTicket(0x000500301001500A); // Friend List + querySystemTitleTicket(0x000500301001200A); // Internet browser + } + + return true; +} + +// build list of updates for which either an installed game exists or the base title ticket is cached +bool DownloadManager::syncUpdateTickets() +{ + setStatusMessage(std::string(_("Retrieving update information...")), DLMGR_STATUS_CODE::CONNECTING); + // download update version list + downloadTitleVersionList(); + if (!m_hasTitleVersionList) + return false; + std::set<DownloadManager::TitleDownloadAvailableState> downloadList = getFullDownloadList(); + // count updates + size_t numUpdates = 0; + for (auto& itr : downloadList) + { + TitleIdParser titleIdParser(itr.titleId); + if (titleIdParser.GetType() == TitleIdParser::TITLE_TYPE::BASE_TITLE_UPDATE) + numUpdates++; + } + // get tickets for all the updates + size_t updateIndex = 0; + for (auto& itr : downloadList) + { + TitleIdParser titleIdParser(itr.titleId); + if (titleIdParser.GetType() != TitleIdParser::TITLE_TYPE::BASE_TITLE_UPDATE) + continue; + + std::string msg = _("Downloading ticket").ToStdString(); + msg.append(fmt::format(" {0}/{1}", updateIndex, numUpdates)); + updateIndex++; + setStatusMessage(msg, DLMGR_STATUS_CODE::CONNECTING); + + if (!itr.isUpdateAvailable) + continue; + + // skip if already cached + if (findTicketByTitleIdAndVersion(itr.titleId, itr.availableTitleVersion)) + continue; + + NAPI::AuthInfo dummyAuth; + auto cetkResult = NAPI::CCS_GetCETK(dummyAuth, itr.titleId, itr.availableTitleVersion); + if (!cetkResult.isValid) + continue; + NCrypto::ETicketParser ticketParser; + if (!ticketParser.parse(cetkResult.cetkData.data(), cetkResult.cetkData.size())) + continue; + uint64 ticketId = ticketParser.GetTicketId(); + uint64 ticketTitleId = ticketParser.GetTitleId(); + uint16 ticketTitleVersion = ticketParser.GetTicketVersion(); + uint16 ticketVersion = ticketTitleVersion; + if (ticketTitleId != itr.titleId) + continue; + if (ticketTitleVersion != itr.availableTitleVersion) + { + cemuLog_log(LogType::Force, "Ticket for title update has a mismatching version"); + continue; + } + // add to eTicket cache + ETicketInfo eTicket(ETicketInfo::SOURCE::PUBLIC_TICKET, ticketId, ticketVersion, cetkResult.cetkData); + m_ticketCache.emplace_back(eTicket); + } + return true; +} + +// synchronize ticket cache with server and request uncached ticket data +bool DownloadManager::syncTicketCache() +{ + if (!syncAccountTickets()) + return false; + syncSystemTitleTickets(); + syncUpdateTickets(); + storeTicketCache(); + + // make sure IDBE's are loaded into memory for all eTickets (potential downloads) + // this will only download them if they aren't already in the on-disk cache + size_t count = m_ticketCache.size(); + size_t index = 0; + for (auto& ticketInfo : m_ticketCache) + { + index++; + std::string msg = _("Downloading meta data").ToStdString(); + msg.append(fmt::format(" {0}/{1}", index, count)); + setStatusMessage(msg, DLMGR_STATUS_CODE::CONNECTING); + prepareIDBE(ticketInfo.titleId); + } + setStatusMessage(std::string(_("Connected. Right click entries in the list to start downloading")), DLMGR_STATUS_CODE::CONNECTED); + return true; +} + +void DownloadManager::searchForIncompleteDownloads() +{ + const fs::path packagePath = ActiveSettings::GetMlcPath("usr/packages/title/"); + if (!fs::exists(packagePath)) + return; + + for (auto& p : fs::directory_iterator(packagePath)) + { + uint64 titleId; + uint32 version; + std::string name = p.path().filename().generic_string(); + if( sscanf(name.c_str(), "cemu_%016llx_v%u", &titleId, &version) != 2) + continue; + std::unique_lock<std::recursive_mutex> _l(m_mutex); + for (auto& itr : m_ticketCache) + m_unfinishedDownloads.emplace_back(titleId, version); + } +} + +void DownloadManager::reportAvailableTitles() +{ + if (!m_cbAddDownloadableTitle) + return; + + std::set<DownloadManager::TitleDownloadAvailableState> downloadList = getFullDownloadList(); + + for (auto& itr : downloadList) + { + TitleIdParser titleIdParser(itr.titleId); + TitleIdParser::TITLE_TYPE titleType = titleIdParser.GetType(); + if (titleType == TitleIdParser::TITLE_TYPE::BASE_TITLE || + titleType == TitleIdParser::TITLE_TYPE::BASE_TITLE_DEMO || + titleType == TitleIdParser::TITLE_TYPE::SYSTEM_OVERLAY_TITLE || + titleType == TitleIdParser::TITLE_TYPE::BASE_TITLE_UPDATE || + titleType == TitleIdParser::TITLE_TYPE::AOC) + { + // show entries only if we were able to retrieve the ticket + if (itr.isInstalled) + { + if (!findFirstTicketByTitleId(itr.titleId)) + continue; + } + + DlMgrTitleReport::STATUS status = DlMgrTitleReport::STATUS::INSTALLABLE; + bool aocHasUpdate = false; + if (itr.isInstalled && titleType == TitleIdParser::TITLE_TYPE::AOC) + { + ETicketInfo* eTicketInfo = findFirstTicketByTitleId(itr.titleId); + if (eTicketInfo && eTicketInfo->ticketVersion > itr.installedTicketVersion) + aocHasUpdate = true; + } + + if (itr.isInstalled && itr.installedTitleVersion >= itr.availableTitleVersion && aocHasUpdate == false) + { + status = DlMgrTitleReport::STATUS::INSTALLED; + } + if (status == DlMgrTitleReport::STATUS::INSTALLABLE) + { + if (hasPartialDownload(itr.titleId, itr.availableTitleVersion)) + status = DlMgrTitleReport::STATUS::INSTALLABLE_UNFINISHED; + if (aocHasUpdate) + status = DlMgrTitleReport::STATUS::INSTALLABLE_UPDATE; + } + DlMgrTitleReport titleInfo(status, itr.titleId, itr.availableTitleVersion, getNameFromCachedIDBE(itr.titleId), 0, 0, false); + m_cbAddDownloadableTitle(titleInfo); + } + else + { + cemu_assert_debug(false); // unsupported title type + } + } +} + +/* connection logic */ + +void DownloadManager::_handle_connect() +{ + m_connectState.store(CONNECT_STATE::PROCESSING); + + // reset login state + m_iasToken.serviceAccountId.clear(); + m_iasToken.deviceToken.clear(); + setStatusMessage(std::string(_("Logging in..")), DLMGR_STATUS_CODE::CONNECTING); + // retrieve ECS AccountId + DeviceToken from cache + if (s_nupFileCache) + { + std::vector<uint8> serializationBlob; + if (s_nupFileCache->GetFile({ fmt::format("{}/token_info", m_authInfo.nnidAccountName) }, serializationBlob)) + { + StoredTokenInfo storedTokenInfo; + if (storedTokenInfo.deserialize(serializationBlob)) + { + m_iasToken.serviceAccountId = storedTokenInfo.accountId; + m_iasToken.deviceToken = storedTokenInfo.deviceToken; + } + } + } + // .. or request AccountId and DeviceToken if not cached + if (m_iasToken.serviceAccountId.empty() || m_iasToken.deviceToken.empty()) + { + if (!_connect_refreshIASAccountIdAndDeviceToken()) + { + forceLog_printf("Failed to request IAS token"); + cemu_assert_debug(false); + m_connectState.store(CONNECT_STATE::FAILED); + setStatusMessage(std::string(_("Login failed. Outdated or incomplete online files?")), DLMGR_STATUS_CODE::FAILED); + return; + } + } + // get EC account status and service urls + if (!_connect_queryAccountStatusAndServiceURLs()) + { + m_connectState.store(CONNECT_STATE::FAILED); + setStatusMessage(std::string(_("Failed to query account status. Invalid account information?")), DLMGR_STATUS_CODE::FAILED); + return; + } + // load ticket cache and sync + setStatusMessage(std::string(_("Updating ticket cache")), DLMGR_STATUS_CODE::CONNECTING); + loadTicketCache(); + if (!syncTicketCache()) + { + m_connectState.store(CONNECT_STATE::FAILED); + setStatusMessage(std::string(_("Failed to request tickets (invalid NNID?)")), DLMGR_STATUS_CODE::FAILED); + return; + } + searchForIncompleteDownloads(); + + // notify about all available downloadable titles + reportAvailableTitles(); + + // print ticket info + m_connectState.store(CONNECT_STATE::COMPLETE); +} + +void DownloadManager::connect( + std::string_view nnidAccountName, + const std::array<uint8, 32>& passwordHash, + CafeConsoleRegion region, + std::string_view country, + uint32 deviceId, + std::string_view serial, + std::string_view deviceCertBase64) +{ + if (nnidAccountName.empty()) + { + m_connectState.store(CONNECT_STATE::FAILED); + setStatusMessage(std::string(_("This account is not linked with an NNID")), DLMGR_STATUS_CODE::FAILED); + return; + } + runManager(); + m_authInfo.nnidAccountName = nnidAccountName; + m_authInfo.passwordHash = passwordHash; + if (std::all_of(m_authInfo.passwordHash.begin(), m_authInfo.passwordHash.end(), [](uint8 v) { return v == 0; })) + { + cemuLog_log(LogType::Force, "DLMgr: Invalid password hash"); + m_connectState.store(CONNECT_STATE::FAILED); + setStatusMessage(std::string(_("Failed. Account does not have password set")), DLMGR_STATUS_CODE::FAILED); + return; + } + m_authInfo.region = region; + m_authInfo.country = country; + m_authInfo.deviceCertBase64 = deviceCertBase64; + m_authInfo.deviceId = deviceId; + m_authInfo.serial = serial; + m_connectState.store(CONNECT_STATE::REQUESTED); + notifyManager(); + queueManagerJob([this]() {_handle_connect(); }); +} + +bool DownloadManager::IsConnected() const +{ + return m_connectState.load() != CONNECT_STATE::UNINITIALIZED; +} + +/* package / downloading */ + +// start/resume/retry download +void DownloadManager::initiateDownload(uint64 titleId, uint16 version) +{ + std::unique_lock<std::recursive_mutex> _l(m_mutex); + Package* package = getPackage(titleId, version); + if (package) + { + // remove pause state + package->state.isPaused = false; + // already exists, erase error state + if (package->state.hasError) + { + package->state.hasError = false; + reportPackageStatus(package); + } + checkPackagesState(); + return; + } + // find matching eTicket and get key + std::vector<uint8>* ticketData = nullptr; + for (auto& ticket : m_ticketCache) + { + if (ticket.titleId == titleId )//&& ticket.version == version) + { + ticketData = &ticket.eTicket; + break; + } + } + if (!ticketData) + return; + + package = new Package(titleId, version, *ticketData); + m_packageList.emplace_back(package); + reportPackageStatus(package); + checkPackagesState(); // will start downloading this package if none already active +} + +void DownloadManager::pauseDownload(uint64 titleId, uint16 version) +{ + std::unique_lock<std::recursive_mutex> _l(m_mutex); + Package* package = getPackage(titleId, version); + if (!package || !package->state.isActive) + return; + package->state.isPaused = true; + package->state.isActive = false; + reportPackageStatus(package); + checkPackagesState(); +} + +DownloadManager::Package* DownloadManager::getPackage(uint64 titleId, uint16 version) +{ + std::unique_lock<std::recursive_mutex> _l(m_mutex); + auto itr = std::find_if(m_packageList.begin(), m_packageList.end(), [titleId, version](const Package* v) { return v->titleId == titleId && v->version == version; }); + if (itr == m_packageList.end()) + return nullptr; + return *itr; +} + +fs::path DownloadManager::getPackageDownloadPath(Package* package) +{ + return ActiveSettings::GetMlcPath(fmt::format("usr/packages/title/cemu_{:016x}_v{}/", package->titleId, package->version)); +} + +fs::path DownloadManager::getPackageInstallPath(Package* package) +{ + TitleIdParser tParser(package->titleId); + + const char* titleBasePath = "usr/title/"; + if(tParser.IsSystemTitle()) + titleBasePath = "sys/title/"; + return ActiveSettings::GetMlcPath(fmt::format("{}{:08x}/{:08x}/", titleBasePath, (uint32)(package->titleId>>32), (uint32)package->titleId)); +} + +// called when a package becomes active (queued to downloading) or when any of it's async download operations finishes +// initiates new async download, decrypt or install tasks +void DownloadManager::updatePackage(Package* package) +{ + std::unique_lock<std::recursive_mutex> _l(m_mutex); + if (!package->state.isActive || package->state.isPaused || package->state.hasError) + return; + // do we have the TMD downloaded yet? + if (!package->state.tmd) + { + if (!package->state.isDownloadingTMD) + { + package->state.isDownloadingTMD = true; + ThreadPool::FireAndForget(&DownloadManager::asyncPackageDownloadTMD, this, package); + } + return; + } + + using ContentState = Package::ContentFile::STATE; + // count state totals + struct ContentCountInfo + { + uint32 total{}; + uint32 processing{}; + }; + enum_array<Package::ContentFile::STATE, ContentCountInfo> contentCountTable; + for (auto& itr : package->state.contentFiles) + { + auto state = itr.second.currentState; + contentCountTable[state].total++; + if(itr.second.isBeingProcessed) + contentCountTable[state].processing++; + } + + // utility method to grab next inactive content entry with a specific state + auto getFirstInactiveContentByState = [&](ContentState state) -> Package::ContentFile* + { + auto itr = std::find_if(package->state.contentFiles.begin(), package->state.contentFiles.end(), [state](const auto& contentFile) {return contentFile.second.currentState == state && !contentFile.second.isBeingProcessed; }); + if (itr == package->state.contentFiles.end()) + return nullptr; + return &(itr->second); + }; + + /************* content check phase *************/ + if (package->state.currentState == Package::STATE::INITIAL) + { + package->state.currentState = Package::STATE::CHECKING; + package->state.progress = 0; + package->state.progressMax = (uint32)package->state.contentFiles.size(); + reportPackageStatus(package); + } + while (contentCountTable[ContentState::CHECK].total > 0 && contentCountTable[ContentState::CHECK].processing < 2) + { + Package::ContentFile* contentPtr = getFirstInactiveContentByState(ContentState::CHECK); + if (!contentPtr) + break; + contentPtr->isBeingProcessed = true; + ThreadPool::FireAndForget(&DownloadManager::asyncPackageVerifyFile, this, package, contentPtr->index, true); + contentCountTable[ContentState::CHECK].processing++; + } + if (contentCountTable[ContentState::CHECK].total > 0) + return; // dont proceed to next phase until done + + /************* content download phase *************/ + while (contentCountTable[ContentState::DOWNLOAD].total > 0 && contentCountTable[ContentState::DOWNLOAD].processing < 2) + { + if (package->state.currentState == Package::STATE::CHECKING || package->state.currentState == Package::STATE::VERIFYING) + { + package->state.currentState = Package::STATE::DOWNLOADING; + package->state.progress = 0; + package->state.progressMax = 0; + reportPackageStatus(package); + } + // download next file if there aren't 2 active downloads already + Package::ContentFile* contentPtr = getFirstInactiveContentByState(ContentState::DOWNLOAD); + if (!contentPtr) + break; + contentPtr->isBeingProcessed = true; + ThreadPool::FireAndForget(&DownloadManager::asyncPackageDownloadContentFile, this, package, contentPtr->index); + contentCountTable[ContentState::DOWNLOAD].processing++; + } + if (contentCountTable[ContentState::DOWNLOAD].total > 0) + return; + + /************* content verification phase *************/ + if (package->state.currentState != Package::STATE::VERIFYING) + { + package->state.currentState = Package::STATE::VERIFYING; + package->state.progress = 0; + package->state.progressMax = (uint32)package->state.contentFiles.size(); + reportPackageStatus(package); + } + + while (contentCountTable[ContentState::VERIFY].total > 0 && contentCountTable[ContentState::VERIFY].processing < 2) + { + Package::ContentFile* contentPtr = getFirstInactiveContentByState(ContentState::VERIFY); + if (!contentPtr) + break; + contentPtr->isBeingProcessed = true; + ThreadPool::FireAndForget(&DownloadManager::asyncPackageVerifyFile, this, package, contentPtr->index, true); + contentCountTable[ContentState::VERIFY].processing++; + } + if (contentCountTable[ContentState::VERIFY].total > 0) + return; + + /************* installing phase *************/ + if (!package->state.isInstalling) + { + if (package->state.currentState != Package::STATE::INSTALLING) + { + package->state.currentState = Package::STATE::INSTALLING; + package->state.progress = 0; + package->state.progressMax = 0; + reportPackageStatus(package); + } + package->state.isInstalling = true; + ThreadPool::FireAndForget(&DownloadManager::asyncPackageInstall, this, package); + } +} + +// checks for new packages to download if none are currently active +void DownloadManager::checkPackagesState() +{ + std::unique_lock<std::recursive_mutex> _l(m_mutex); + bool hasActive = false; + hasActive = std::find_if(m_packageList.begin(), m_packageList.end(), + [](const Package* p) { return p->state.isActive; }) != m_packageList.end(); + if (!hasActive) + { + // start new download + auto it = std::find_if(m_packageList.begin(), m_packageList.end(), + [](const Package* p) { return !p->state.isActive && !p->state.hasError && !p->state.isPaused && p->state.currentState != Package::STATE::INSTALLED; }); + if (it != m_packageList.end()) + { + Package* startedPackage = *it; + startedPackage->state.isActive = true; + updatePackage(startedPackage); + reportPackageStatus(startedPackage); + } + } +} + +void DownloadManager::setPackageError(Package* package, std::string errorMsg) +{ + package->state.isActive = false; + if (package->state.hasError) + return; // dont overwrite already set error message + package->state.hasError = true; + package->state.errorMsg = errorMsg; + reportPackageStatus(package); +} + +void DownloadManager::reportPackageStatus(Package* package) +{ + if (!m_cbAddDownloadableTitle) + return; + m_mutex.lock(); + DlMgrTitleReport::STATUS status = DlMgrTitleReport::STATUS::INITIALIZING; + if (package->state.hasError) + { + status = DlMgrTitleReport::STATUS::HAS_ERROR; + } + else if (package->state.currentState == Package::STATE::INSTALLED) + status = DlMgrTitleReport::STATUS::INSTALLED; + else if (!package->state.isActive) + { + if (package->state.tmd) + status = DlMgrTitleReport::STATUS::PAUSED; + else + status = DlMgrTitleReport::STATUS::QUEUED; + } + else if (package->state.tmd) + { + if (package->state.currentState == Package::STATE::CHECKING) + status = DlMgrTitleReport::STATUS::CHECKING; + else if (package->state.currentState == Package::STATE::VERIFYING) + status = DlMgrTitleReport::STATUS::VERIFYING; + else if (package->state.currentState == Package::STATE::INSTALLING) + status = DlMgrTitleReport::STATUS::INSTALLING; + else if (package->state.currentState == Package::STATE::INSTALLED) + status = DlMgrTitleReport::STATUS::INSTALLED; + else + status = DlMgrTitleReport::STATUS::DOWNLOADING; + } + + DlMgrTitleReport reportInfo(status, package->titleId, package->version, getNameFromCachedIDBE(package->titleId), package->state.progress, package->state.progressMax, package->state.isPaused); + if (package->state.hasError) + reportInfo.errorMsg = package->state.errorMsg; + + m_mutex.unlock(); + m_cbAddDownloadableTitle(reportInfo); +} + +void DownloadManager::reportPackageProgress(Package* package, uint32 currentProgress) +{ + // todo - cooldown timer to avoid spamming too many events + + package->state.progress = currentProgress; + reportPackageStatus(package); +} + +void DownloadManager::asyncPackageDownloadTMD(Package* package) +{ + NAPI::AuthInfo authInfo; + authInfo.accountId = m_authInfo.nnidAccountName; + authInfo.passwordHash = m_authInfo.passwordHash; + authInfo.deviceId = m_authInfo.deviceId; + authInfo.serial = m_authInfo.serial; + authInfo.country = m_authInfo.country; + authInfo.region = m_authInfo.region; + authInfo.deviceCertBase64 = m_authInfo.deviceCertBase64; + authInfo.IASToken.accountId = m_iasToken.serviceAccountId; + authInfo.IASToken.deviceToken = m_iasToken.deviceToken; + + TitleIdParser titleIdParser(package->titleId); + NAPI::NAPI_CCSGetTMD_Result tmdResult; + if (titleIdParser.GetType() == TitleIdParser::TITLE_TYPE::AOC) + { + // for AOC we always download the latest TMD + // is there a way to get the version beforehand? It doesn't seem to be stored in either the .tik file or the update version list + tmdResult = CCS_GetTMD(authInfo, package->titleId); + } + else + { + tmdResult = NAPI::CCS_GetTMD(authInfo, package->titleId, package->version); + } + if (!tmdResult.isValid) + { + // failed, try to get latest TMD instead + tmdResult = CCS_GetTMD(authInfo, package->titleId); + } + + std::unique_lock<std::recursive_mutex> _l(m_mutex); + if (!tmdResult.isValid) + { + setPackageError(package, from_wxString(_("TMD download failed"))); + package->state.isDownloadingTMD = false; + return; + } + _l.unlock(); + // parse + NCrypto::TMDParser tmdParser; + if (!tmdParser.parse(tmdResult.tmdData.data(), tmdResult.tmdData.size())) + { + setPackageError(package, from_wxString(_("Invalid TMD"))); + package->state.isDownloadingTMD = false; + return; + } + // set TMD + _l.lock(); + package->state.tmdData = tmdResult.tmdData; + package->state.tmd = new NCrypto::TMDParser(tmdParser); + package->state.isDownloadingTMD = false; + // prepare list of content files + package->state.contentFiles.clear(); + for (auto& itr : package->state.tmd->GetContentList()) + { + if (package->state.contentFiles.find(itr.index) != package->state.contentFiles.end()) + { + cemu_assert_debug(false); + continue; + } + package->state.contentFiles.emplace(std::piecewise_construct, std::forward_as_tuple(itr.index), std::forward_as_tuple(itr.index, itr.contentId, itr.size, itr.contentFlags, itr.hash32)); + } + // create folder + auto dir = getPackageDownloadPath(package); + fs::create_directories(dir); + // continue with downloading + reportPackageStatus(package); + updatePackage(package); +} + +void DownloadManager::calcPackageDownloadProgress(Package* package) +{ + if (package->state.currentState == Package::STATE::DOWNLOADING) + { + uint64 totalSize = 0; + uint64 totalDownloaded = 0; + for (auto& itr : package->state.contentFiles) + { + totalSize += itr.second.paddedSize; + if (itr.second.currentState == Package::ContentFile::STATE::INSTALL || itr.second.currentState == Package::ContentFile::STATE::VERIFY) + totalDownloaded += itr.second.paddedSize; // already downloaded, add full size + else + totalDownloaded += itr.second.amountDownloaded; + } + + uint32 pct10 = (uint32)(totalDownloaded * 1000ull / totalSize); + if (package->state.progress != pct10) + { + package->state.progress = pct10; + package->state.progressMax = 1000; + reportPackageProgress(package, package->state.progress); + } + } +} + +void DownloadManager::asyncPackageDownloadContentFile(Package* package, uint16 index) +{ + // get titleId, contentId and file path + std::unique_lock<std::recursive_mutex> _l(m_mutex); + uint64 titleId = package->titleId; + auto contentFileItr = package->state.contentFiles.find(index); + cemu_assert(contentFileItr != package->state.contentFiles.end()); + uint32 contentId = contentFileItr->second.contentId; + contentFileItr->second.amountDownloaded = 0; + auto packageDownloadPath = getPackageDownloadPath(package); + _l.unlock(); + + // download h3 hash file (.h3) if flag 0x0002 is set (-> we are using the TMD to verify the hash of the content files) + //auto h3Result = NAPI::CCS_GetContentH3File(titleId, contentId); + //auto h3Result = NAPI::CCS_GetContentH3File(titleId, contentId); + + //if (!h3Result.isValid) + //{ + // setPackageError(package, "Download failed (h3)"); + // return; + //} + //filePathStr = (packageDownloadPath / fmt::format("{:08x}.h3", index)).generic_u8string(); + //auto h3File = FileStream::createFile(filePathStr); + //if (!h3File) + //{ + // setPackageError(package, "Cannot create file"); + // return; + //} + //if (h3File->writeData(h3Result.tmdData.data(), h3Result.tmdData.size()) != h3Result.tmdData.size()) + //{ + // setPackageError(package, "Cannot write file (h3). Disk full?"); + // return; + //} + //delete h3File; + + // streamed download of content file (.app) + // prepare callback parameter struct + struct CallbackInfo + { + DownloadManager* downloadMgr; + Package* package; + Package::ContentFile* contentFile; + std::vector<uint8> receiveBuffer; + FileStream* fileOutput; + + static bool writeCallback(void* userData, const void* ptr, size_t len, bool isLast) + { + CallbackInfo* callbackInfo = (CallbackInfo*)userData; + // append bytes to buffer + callbackInfo->receiveBuffer.insert(callbackInfo->receiveBuffer.end(), (const uint8*)ptr, (const uint8*)ptr + len); + // flush cache to file if it exceeds 128KiB or if this is the final callback + if (callbackInfo->receiveBuffer.size() >= (128 * 1024) || (isLast && !callbackInfo->receiveBuffer.empty())) + { + size_t bytesWritten = callbackInfo->receiveBuffer.size(); + if (callbackInfo->fileOutput->writeData(callbackInfo->receiveBuffer.data(), callbackInfo->receiveBuffer.size()) != (uint32)callbackInfo->receiveBuffer.size()) + { + callbackInfo->downloadMgr->setPackageError(callbackInfo->package, from_wxString(_("Cannot write file. Disk full?"))); + return false; + } + callbackInfo->receiveBuffer.clear(); + if (bytesWritten > 0) + { + callbackInfo->downloadMgr->m_mutex.lock(); + callbackInfo->contentFile->amountDownloaded += bytesWritten; + callbackInfo->downloadMgr->calcPackageDownloadProgress(callbackInfo->package); + callbackInfo->downloadMgr->m_mutex.unlock(); + } + } + return true; + } + }callbackInfoData{}; + callbackInfoData.downloadMgr = this; + callbackInfoData.package = package; + callbackInfoData.contentFile = &contentFileItr->second; + callbackInfoData.fileOutput = FileStream::createFile2(packageDownloadPath / fmt::format("{:08x}.app", contentId)); + if (!callbackInfoData.fileOutput) + { + setPackageError(package, from_wxString(_("Cannot create file"))); + return; + } + if (!NAPI::CCS_GetContentFile(titleId, contentId, CallbackInfo::writeCallback, &callbackInfoData)) + { + setPackageError(package, from_wxString(_("Download failed"))); + delete callbackInfoData.fileOutput; + return; + } + delete callbackInfoData.fileOutput; + callbackInfoData.fileOutput = nullptr; + + // mark file as downloaded by requesting verify state + _l.lock(); + contentFileItr->second.finishProcessing(Package::ContentFile::STATE::VERIFY); + _l.unlock(); + // start next task + updatePackage(package); +} + +void DownloadManager::asyncPackageVerifyFile(Package* package, uint16 index, bool isCheckState) +{ + uint8 tmdContentHash[32]; + std::string filePathStr; + // get titleId, contentId and file path + std::unique_lock<std::recursive_mutex> _l(m_mutex); + uint64 titleId = package->titleId; + auto contentFileItr = package->state.contentFiles.find(index); + cemu_assert(contentFileItr != package->state.contentFiles.end()); + uint16 contentIndex = contentFileItr->second.index; + uint32 contentId = contentFileItr->second.contentId; + uint64 contentSize = contentFileItr->second.size; + uint64 contentPaddedSize = contentFileItr->second.paddedSize; + auto contentFlags = contentFileItr->second.contentFlags; + std::memcpy(tmdContentHash, contentFileItr->second.contentHash, 32); + auto packageDownloadPath = getPackageDownloadPath(package); + _l.unlock(); + NCrypto::AesKey ecsTicketKey = package->ticketKey; + + Package::ContentFile::STATE newStateOnError = Package::ContentFile::STATE::DOWNLOAD; + Package::ContentFile::STATE newStateOnSuccess = Package::ContentFile::STATE::INSTALL; + + // verify file + std::unique_ptr<FileStream> fileStream(FileStream::openFile2(packageDownloadPath / fmt::format("{:08x}.app", contentId))); + if (!fileStream) + { + _l.lock(); + contentFileItr->second.finishProcessing(newStateOnError); + if (!isCheckState) + setPackageError(package, "Missing file during verification"); + _l.unlock(); + updatePackage(package); + return; + } + + bool isSHA1 = HAS_FLAG(contentFlags, NCrypto::TMDParser::TMDContentFlags::FLAG_SHA1); + + bool isValid = false; + if (HAS_FLAG(contentFlags, NCrypto::TMDParser::TMDContentFlags::FLAG_HASHED_CONTENT)) + isValid = FSTVerifier::VerifyHashedContentFile(fileStream.get(), &ecsTicketKey, contentIndex, contentSize, contentPaddedSize, isSHA1, tmdContentHash); + else + isValid = FSTVerifier::VerifyContentFile(fileStream.get(), &ecsTicketKey, contentIndex, contentSize, contentPaddedSize, isSHA1, tmdContentHash); + + if (!isValid) + { + _l.lock(); + contentFileItr->second.finishProcessing(newStateOnError); + if (!isCheckState) + setPackageError(package, "Verification failed"); + _l.unlock(); + updatePackage(package); + return; + } + // file verified successfully + _l.lock(); + contentFileItr->second.finishProcessing(newStateOnSuccess); + reportPackageProgress(package, package->state.progress + 1); + _l.unlock(); + // start next task + updatePackage(package); +} + +bool DownloadManager::asyncPackageInstallRecursiveExtractFiles(Package* package, FSTVolume* fstVolume, const std::string& sourcePath, const fs::path& destinationPath) +{ + std::error_code ec; + fs::create_directories(destinationPath, ec); // we dont check the error because it is OS/implementation specific (on Windows this returns ec=0 with false when directory already exists) + cemu_assert_debug(sourcePath.back() == '/'); + FSTDirectoryIterator dirItr; + if (!fstVolume->OpenDirectoryIterator(sourcePath, dirItr)) + { + std::unique_lock<std::recursive_mutex> _l(m_mutex); + setPackageError(package, "Internal error"); + return false; + } + + FSTFileHandle itr; + while (fstVolume->Next(dirItr, itr)) + { + std::string_view nodeName = fstVolume->GetName(itr); + if(nodeName.empty() || boost::equals(nodeName, ".") || boost::equals(nodeName, "..") || boost::contains(nodeName, "/") || boost::contains(nodeName, "\\")) + continue; + std::string sourceFilePath = sourcePath; + sourceFilePath.append(nodeName); + fs::path nodeDestinationPath = destinationPath; + nodeDestinationPath.append(nodeName.data(), nodeName.data() + nodeName.size()); + if (fstVolume->IsDirectory(itr)) + { + if (fstVolume->HasLinkFlag(itr)) + { + // delete link directories + fs::remove_all(nodeDestinationPath, ec); + } + else + { + // iterate + sourceFilePath.push_back('/'); + asyncPackageInstallRecursiveExtractFiles(package, fstVolume, sourceFilePath, nodeDestinationPath); + } + } + else if (fstVolume->IsFile(itr)) + { + if (fstVolume->HasLinkFlag(itr)) + { + // delete link files + fs::remove_all(nodeDestinationPath, ec); + } + else + { + // extract + std::vector<uint8> buffer(64 * 1024); + FileStream* fileOut = FileStream::createFile2(nodeDestinationPath); + if (!fileOut) + { + setPackageError(package, "Failed to create file"); + return false; + } + uint32 fileSize = fstVolume->GetFileSize(itr); + uint32 currentPos = 0; + while (currentPos < fileSize) + { + uint32 numBytesToTransfer = std::min(fileSize - currentPos, (uint32)buffer.size()); + if (fstVolume->ReadFile(itr, currentPos, numBytesToTransfer, buffer.data()) != numBytesToTransfer) + { + setPackageError(package, "Failed to extract data"); + return false; + } + if (fileOut->writeData(buffer.data(), numBytesToTransfer) != numBytesToTransfer) + { + setPackageError(package, "Failed to write to file. Disk full?"); + return false; + } + currentPos += numBytesToTransfer; + } + delete fileOut; + } + // advance progress + std::unique_lock<std::recursive_mutex> _l(m_mutex); + reportPackageProgress(package, package->state.progress + 1); + } + else + { + cemu_assert_debug(false); // unknown node type + } + } + return true; +} + +void DownloadManager::asyncPackageInstall(Package* package) +{ + std::unique_lock<std::recursive_mutex> _l(m_mutex); + auto packageDownloadPath = getPackageDownloadPath(package); + fs::path installPath = getPackageInstallPath(package); + _l.unlock(); + // store title.tmd + std::unique_ptr<FileStream> fileStream(FileStream::createFile2(packageDownloadPath / "title.tmd")); + if (!fileStream || fileStream->writeData(package->state.tmdData.data(), package->state.tmdData.size()) != package->state.tmdData.size()) + { + _l.lock(); + setPackageError(package, "Failed to write title.tmd"); + package->state.isInstalling = false; + return; + } + fileStream.reset(); + // store title.tik + fileStream.reset(FileStream::createFile2(packageDownloadPath / "title.tik")); + if (!fileStream || fileStream->writeData(package->eTicketData.data(), package->eTicketData.size()) != package->eTicketData.size()) + { + _l.lock(); + setPackageError(package, "Failed to write title.tik"); + package->state.isInstalling = false; + return; + } + fileStream.reset(); + // for AOC titles we also 'install' the ticket by copying it next to the code/content/meta folders + // on an actual Wii U the ticket gets installed to SLC but currently we only emulate MLC + if (TitleIdParser(package->titleId).GetType() == TitleIdParser::TITLE_TYPE::AOC) + { + std::error_code ec; + fs::create_directories(installPath, ec); + fs::create_directories(installPath / "code/", ec); + fileStream.reset(FileStream::createFile2(installPath / "code/title.tik")); + if (!fileStream || fileStream->writeData(package->eTicketData.data(), package->eTicketData.size()) != package->eTicketData.size()) + { + _l.lock(); + setPackageError(package, "Failed to install title.tik"); + package->state.isInstalling = false; + return; + } + fileStream.reset(); + } + // open app FST + FSTVolume* fst = FSTVolume::OpenFromContentFolder(packageDownloadPath); + if (!fst) + { + _l.lock(); + setPackageError(package, "Failed to extract content"); + package->state.isInstalling = false; + return; + } + // count number of files for progress tracking + package->state.progressMax = fst->GetFileCount(); + package->state.progress = 0; + // extract code/content/meta folders into installation directory + if (!asyncPackageInstallRecursiveExtractFiles(package, fst, "code/", installPath / "code")) + { + _l.lock(); + setPackageError(package, "Failed to extract code folder"); + package->state.isInstalling = false; + return; + } + if (!asyncPackageInstallRecursiveExtractFiles(package, fst, "content/", installPath / "content")) + { + _l.lock(); + setPackageError(package, "Failed to extract content folder"); + package->state.isInstalling = false; + return; + } + if (!asyncPackageInstallRecursiveExtractFiles(package, fst, "meta/", installPath / "meta")) + { + _l.lock(); + setPackageError(package, "Failed to extract meta folder"); + package->state.isInstalling = false; + return; + } + delete fst; + // delete package folder + std::error_code ec; + fs::remove_all(packageDownloadPath, ec); + // mark as complete + _l.lock(); + package->state.currentState = Package::STATE::INSTALLED; + package->state.isInstalling = false; + package->state.isActive = false; + CafeTitleList::AddTitleFromPath(installPath); + reportPackageStatus(package); + checkPackagesState(); + // lastly request game list to be refreshed + MainWindow::RequestGameListRefresh(); + return; +} + +/* IDBE cache */ + +std::unordered_map<uint64, NAPI::IDBEIconDataV0*> s_idbeCache; +std::mutex s_idbeCacheMutex; + +// load IDBE from disk or server into memory cache +// stalls while reading disk/downloading +void DownloadManager::prepareIDBE(uint64 titleId) +{ + auto hasInCache = [](uint64 titleId) -> bool + { + s_idbeCacheMutex.lock(); + bool hasCached = s_idbeCache.find(titleId) != s_idbeCache.end(); + s_idbeCacheMutex.unlock(); + return hasCached; + }; + + auto addToCache = [](uint64 titleId, NAPI::IDBEIconDataV0* iconData) -> void + { + NAPI::IDBEIconDataV0* iconInstance = new NAPI::IDBEIconDataV0(); + *iconInstance = *iconData; + s_idbeCacheMutex.lock(); + if (!s_idbeCache.try_emplace(titleId, iconInstance).second) + delete iconInstance; + s_idbeCacheMutex.unlock(); + }; + if (hasInCache(titleId)) + return; + // try to load from disk cache + std::vector<uint8> idbeFile; + if (s_nupFileCache->GetFile({ fmt::format("idbe/{0:016x}", titleId) }, idbeFile) && idbeFile.size() == sizeof(NAPI::IDBEIconDataV0)) + return addToCache(titleId, (NAPI::IDBEIconDataV0*)(idbeFile.data())); + // not cached, query from server + std::optional<NAPI::IDBEIconDataV0> iconData = NAPI::IDBE_Request(titleId); + if (!iconData) + return; + s_nupFileCache->AddFileAsync({ fmt::format("idbe/{0:016x}", titleId) }, (uint8*)&(*iconData), sizeof(NAPI::IDBEIconDataV0)); + addToCache(titleId, &*iconData); +} + +std::string DownloadManager::getNameFromCachedIDBE(uint64 titleId) +{ + // workaround for Friend List not having an IDBE + if (titleId == 0x000500301001500A || + titleId == 0x000500301001510A || + titleId == 0x000500301001520A) + { + return "Friend List"; + } + + std::unique_lock<std::mutex> _l(s_idbeCacheMutex); + NAPI::IDBEIconDataV0* iconData = getIDBE(titleId); + if (iconData) + return iconData->GetLanguageStrings(GetConfig().console_language).GetGameNameUTF8(); + return fmt::format("Title {0:016x}", titleId); +} + +// returns IDBE by titleId (or nullptr if not in cache) +// assumes s_idbeCacheMutex is held by caller +NAPI::IDBEIconDataV0* DownloadManager::getIDBE(uint64 titleId) +{ + auto it = s_idbeCache.find(titleId); + if (it == s_idbeCache.end()) + return nullptr; + return it->second; +} + +/* package manager / downloading */ + +void DownloadManager::threadFunc() +{ + while (true) + { + auto cb = m_jobQueue.pop(); + cb(); + } +} + +// init download manager worker thread and cache +void DownloadManager::runManager() +{ + bool prevBool = false; + if (!m_threadLaunched.compare_exchange_weak(prevBool, true)) + return; + // open cache + auto cacheFilePath = ActiveSettings::GetMlcPath("usr/save/system/nim/nup/"); + fs::create_directories(cacheFilePath); + cacheFilePath /= "cemu_cache.dat"; + s_nupFileCache = FileCache::Open(cacheFilePath.generic_wstring(), true); + // launch worker thread + std::thread t(&DownloadManager::threadFunc, this); + t.detach(); +} + +// let manager known there is a new event that needs processing +void DownloadManager::notifyManager() +{ + m_queuedEvents.increment(); +} + +void DownloadManager::queueManagerJob(const std::function<void()>& callback) +{ + m_jobQueue.push(callback); +} diff --git a/src/Cemu/Tools/DownloadManager/DownloadManager.h b/src/Cemu/Tools/DownloadManager/DownloadManager.h new file mode 100644 index 00000000..fa6749b8 --- /dev/null +++ b/src/Cemu/Tools/DownloadManager/DownloadManager.h @@ -0,0 +1,504 @@ +#pragma once +#include "util/helpers/Semaphore.h" +#include "Cemu/ncrypto/ncrypto.h" +#include "Cafe/TitleList/TitleId.h" + +#include "util/helpers/ConcurrentQueue.h" + +#include <functional> +#include <optional> + +#include <future> + +namespace NAPI +{ + struct IDBEIconDataV0; +} + +namespace NCrypto +{ + class TMDParser; +} + +struct DlMgrTitleReport +{ + enum class STATUS + { + INSTALLABLE, // not a package + INSTALLABLE_UNFINISHED, // same as INSTALLABLE, but a previous unfinished package was detected + INSTALLABLE_UPDATE, // same as INSTALLABLE, but an older version is already installed (used for DLC updates after purchasing more content) + // below are packages + QUEUED, + PAUSED, + INITIALIZING, // not active yet, downloading TMD + CHECKING, // checking for previously downloaded files + DOWNLOADING, // downloading content files + VERIFYING, // verifying downloaded files + INSTALLING, + INSTALLED, + HAS_ERROR + }; + + DlMgrTitleReport(STATUS status, uint64 titleId, uint16 version, std::string name, uint32 progress, uint32 progressMax, bool isPaused) : status(status), titleId(titleId), version(version), name(name), progress(progress), progressMax(progressMax), isPaused(isPaused) {} + + uint64 titleId; + uint16 version; + std::string name; // utf8 + STATUS status; + uint32 progress; + uint32 progressMax; + bool isPaused; + std::string errorMsg; +}; + +enum class DLMGR_STATUS_CODE +{ + UNINITIALIZED, + CONNECTING, + FAILED, + CONNECTED +}; + +class DownloadManager +{ +public: + /* singleton */ + + static DownloadManager* GetInstance(bool createIfNotExist = true) + { + static DownloadManager* s_instance = nullptr; + if (s_instance) + return s_instance; + if (createIfNotExist) + s_instance = new DownloadManager(); + return s_instance; + } + + // login + void connect( + std::string_view nnidAccountName, + const std::array<uint8, 32>& passwordHash, + CafeConsoleRegion region, + std::string_view country, + uint32 deviceId, + std::string_view serial, + std::string_view deviceCertBase64); + + bool IsConnected() const; + + +private: + /* connect / login */ + + enum class CONNECT_STATE + { + UNINITIALIZED = 0, // connect() not called + REQUESTED = 1, // connect() requested, but not being processed yet + PROCESSING = 2, // processing login request + COMPLETE = 3, // login complete / succeeded + FAILED = 4, // failed to login + }; + + struct + { + std::string nnidAccountName; + std::array<uint8, 32> passwordHash; + std::string deviceCertBase64; + CafeConsoleRegion region; + std::string country; + uint32 deviceId; // deviceId without platform (0x5<<32 for WiiU) + std::string serial; + }m_authInfo{}; + + struct + { + // auth info we have to request from the server + std::string serviceAccountId; // internal account id (integer) provided by server when registering account (GetRegistrationInfo) + std::string deviceToken; + }m_iasToken{}; + + std::atomic<CONNECT_STATE> m_connectState{ CONNECT_STATE::UNINITIALIZED }; + + void _handle_connect(); + bool _connect_refreshIASAccountIdAndDeviceToken(); + bool _connect_queryAccountStatusAndServiceURLs(); + + /* idbe cache */ +public: + void prepareIDBE(uint64 titleId); + std::string getNameFromCachedIDBE(uint64 titleId); + +private: + NAPI::IDBEIconDataV0* getIDBE(uint64 titleId); + + /* ticket cache */ +public: + + struct ETicketInfo + { + enum class SOURCE : uint8 + { + ECS_TICKET = 0, // personalized ticket of owned title + PUBLIC_TICKET = 1, // public ticket file (available as /CETK from NUS server or via SOAP GetSystemCommonETicket. The former is from Wii era while the latter is how Wii U requests public tickets?) + // note: These ids are baked into the serialized cache format. Do not modify + }; + + ETicketInfo(SOURCE source, uint64 ticketId, uint32 ticketVersion, std::vector<uint8>& eTicket); + ETicketInfo(SOURCE source, uint64 ticketId, uint32 ticketVersion, std::vector<uint8>& eTicket, std::vector<std::vector<uint8>>& eTicketCerts); + void GetTitleKey(NCrypto::AesKey& key); + + SOURCE source; + // tiv + uint64 ticketId; + uint32 ticketVersion; + // titleInfo + uint64 titleId{0}; // parsed from eTicket + + std::vector<uint8> eTicket; + std::vector<std::vector<uint8>> eTicketCerts; + }; + + std::vector<ETicketInfo> m_ticketCache; + + struct UnfinishedDownload + { + UnfinishedDownload(uint64 titleId, uint16 titleVersion) : titleId(titleId), titleVersion(titleVersion) {}; + + uint64 titleId; + uint16 titleVersion; + }; + + std::vector<UnfinishedDownload> m_unfinishedDownloads; + + void loadTicketCache(); + void storeTicketCache(); + + bool syncAccountTickets(); + bool syncSystemTitleTickets(); + bool syncUpdateTickets(); + bool syncTicketCache(); + void searchForIncompleteDownloads(); + + void reportAvailableTitles(); + +private: + ETicketInfo* findTicketByTicketId(uint64 ticketId) + { + for (auto& itr : m_ticketCache) + { + if (itr.ticketId == ticketId) + return &itr; + } + return nullptr; + } + + void deleteTicketByTicketId(uint64 ticketId) + { + auto itr = m_ticketCache.begin(); + while (itr != m_ticketCache.end()) + { + if (itr->ticketId == ticketId) + { + m_ticketCache.erase(itr); + return; + } + itr++; + } + cemu_assert_debug(false); // ticketId not found + } + + ETicketInfo* findTicketByTitleIdAndVersion(uint64 titleId, uint16 version) + { + for (auto& itr : m_ticketCache) + { + if (itr.titleId == titleId && itr.ticketVersion == version) + return &itr; + } + return nullptr; + } + + ETicketInfo* findFirstTicketByTitleId(uint64 titleId) + { + for (auto& itr : m_ticketCache) + { + if (itr.titleId == titleId) + return &itr; + } + return nullptr; + } + + bool hasPartialDownload(uint64 titleId, uint16 titleVersion) + { + for (auto& itr : m_unfinishedDownloads) + { + if (itr.titleId == titleId && itr.titleVersion == titleVersion) + return true; + } + return false; + } + + void deleteUnfinishedDownloadRecord(uint64 titleId) + { + auto itr = m_unfinishedDownloads.begin(); + while (itr != m_unfinishedDownloads.end()) + { + if (itr->titleId == titleId) + { + itr = m_unfinishedDownloads.erase(itr); + continue; + } + itr++; + } + } + + /* packages / downloading */ + struct Package + { + Package(uint64 titleId, uint16 version, std::span<uint8> ticketData) : titleId(titleId), version(version), eTicketData(ticketData.data(), ticketData.data() + ticketData.size()) + { + NCrypto::ETicketParser eTicketParser; + cemu_assert(eTicketParser.parse(ticketData.data(), ticketData.size())); + eTicketParser.GetTitleKey(ticketKey); + }; + + ~Package() + { + delete state.tmd; + } + + enum class STATE + { + INITIAL, + CHECKING, + DOWNLOADING, + VERIFYING, + INSTALLING, + INSTALLED // done + }; + + uint64 titleId; + uint16 version; + std::vector<uint8> eTicketData; + NCrypto::AesKey ticketKey; + + // internal + struct ContentFile + { + ContentFile(uint16 index, uint32 contentId, uint64 size, NCrypto::TMDParser::TMDContentFlags contentFlags, uint8 hash[32]) : index(index), contentId(contentId), size(size), contentFlags(contentFlags) + { + std::memcpy(contentHash, hash, 32); + CalcPaddedSize(); + }; + const uint16 index; + const uint32 contentId; + const uint64 size; + uint64 paddedSize; // includes padding forced by encryption/hashing (e.g. Nintendo Land update has one non-hashed content size of 0x8001, padded to 0x8010) + const NCrypto::TMDParser::TMDContentFlags contentFlags; + uint8 contentHash[32]; + + enum class STATE + { + CHECK, + DOWNLOAD, + VERIFY, + INSTALL, + + ENUM_COUNT, // for enum_array + }; + + STATE currentState{STATE::CHECK}; + bool isBeingProcessed{false}; // worked thread assigned and processing current state + + void finishProcessing(STATE newState) + { + currentState = newState; + isBeingProcessed = false; + }; + + // progress tracking + uint64 amountDownloaded{}; + + private: + void CalcPaddedSize() + { + if (HAS_FLAG(contentFlags, NCrypto::TMDParser::TMDContentFlags::FLAG_HASHED_CONTENT)) + { + // pad to 0x10000 bytes + paddedSize = (size + 0xFFFFull) & ~0xFFFFull; + } + else + { + // pad to 16 bytes + paddedSize = (size + 0xFull) & ~0xFull; + } + } + }; + + struct + { + bool isActive{}; // actively downloading + bool isPaused{}; + STATE currentState{ STATE::INITIAL }; + // tmd + bool isDownloadingTMD{}; + std::vector<uint8> tmdData; + NCrypto::TMDParser* tmd{}; + // app/h3 tracking + std::unordered_map<uint16, ContentFile> contentFiles; + // progress of current operation + uint32 progress{}; // for downloading: in 1/10th of a percent (0-1000), for installing: number of files + uint32 progressMax{}; // maximum (downloading: unused, installing: total number of files) + // installing + bool isInstalling{}; + // error state + bool hasError{}; + std::string errorMsg; + }state; + }; + + std::recursive_mutex m_mutex; + std::vector<Package*> m_packageList; + +public: + void initiateDownload(uint64 titleId, uint16 version); + void pauseDownload(uint64 titleId, uint16 version); + +private: + Package* getPackage(uint64 titleId, uint16 version); + fs::path getPackageDownloadPath(Package* package); + fs::path getPackageInstallPath(Package* package); + void updatePackage(Package* package); + void checkPackagesState(); + + void setPackageError(Package* package, std::string errorMsg); + void reportPackageStatus(Package* package); + void reportPackageProgress(Package* package, uint32 newProgress); + + void asyncPackageDownloadTMD(Package* package); + void calcPackageDownloadProgress(Package* package); + void asyncPackageDownloadContentFile(Package* package, uint16 index); + void asyncPackageVerifyFile(Package* package, uint16 index, bool isCheckState); + void asyncPackageInstall(Package* package); + bool asyncPackageInstallRecursiveExtractFiles(Package* package, class FSTVolume* fstVolume, const std::string& sourcePath, const fs::path& destinationPath); + + /* callback interface */ +public: + void setUserData(void* ptr) + { + m_userData = ptr; + } + + void* getUserData() const + { + return m_userData; + } + + // register/unregister callbacks + // setting valid callbacks will also trigger transfer of the entire title/package state and the current status message + void registerCallbacks( + void(*cbUpdateConnectStatus)(std::string statusText, DLMGR_STATUS_CODE statusCode), + void(*cbAddDownloadableTitle)(const DlMgrTitleReport& titleInfo), + void(*cbRemoveDownloadableTitle)(uint64 titleId, uint16 version) + ) + { + std::unique_lock<std::recursive_mutex> _l(m_mutex); + m_cbUpdateConnectStatus = cbUpdateConnectStatus; + m_cbAddDownloadableTitle = cbAddDownloadableTitle; + m_cbRemoveDownloadableTitle = cbRemoveDownloadableTitle; + // resend data + if (m_cbUpdateConnectStatus || m_cbAddDownloadableTitle) + { + std::unique_lock<std::recursive_mutex> _l(m_mutex); + setStatusMessage(m_statusMessage, m_statusCode); + reportAvailableTitles(); + for (auto& p : m_packageList) + reportPackageStatus(p); + } + } + + void setStatusMessage(std::string_view msg, DLMGR_STATUS_CODE statusCode) + { + m_statusMessage = msg; + m_statusCode = statusCode; + if (m_cbUpdateConnectStatus) + m_cbUpdateConnectStatus(m_statusMessage, statusCode); + } + + std::string m_statusMessage{}; + DLMGR_STATUS_CODE m_statusCode{ DLMGR_STATUS_CODE::UNINITIALIZED }; + + bool hasActiveDownloads() + { + std::unique_lock<std::recursive_mutex> _l(m_mutex); + for (auto& p : m_packageList) + { + if (p->state.isActive && !p->state.hasError && + (p->state.currentState != Package::STATE::INSTALLED)) + { + return true; + } + } + return false; + } + + void reset() + { + std::unique_lock<std::recursive_mutex> _l(m_mutex); + m_packageList.clear(); + m_statusCode = DLMGR_STATUS_CODE::UNINITIALIZED; + m_statusMessage = ""; + } + +private: + void(*m_cbUpdateConnectStatus)(std::string statusText, DLMGR_STATUS_CODE statusCode) { nullptr }; + void(*m_cbAddDownloadableTitle)(const DlMgrTitleReport& titleInfo); + void(*m_cbRemoveDownloadableTitle)(uint64 titleId, uint16 version); + void* m_userData{}; + + /* title version list */ + bool m_hasTitleVersionList{}; + std::unordered_map<uint64, uint16> m_titleVersionList; + + void downloadTitleVersionList(); + bool getTitleLatestVersion(TitleId titleId, uint16& version); + + /* helper for building list of owned titles (is installed or has cached ticket) */ + struct TitleInstallState + { + TitleInstallState(DownloadManager* dlMgr, uint64 titleId); + + uint64 titleId; + bool isInstalled; + uint16 installedTitleVersion; + uint16 installedTicketVersion{}; + + bool operator<(const TitleInstallState& comp) const + { + return this->titleId < comp.titleId; + } + }; + + struct TitleDownloadAvailableState : TitleInstallState + { + TitleDownloadAvailableState(DownloadManager* dlMgr, uint64 titleId); + + bool isUpdateAvailable; // update available for this specific titleId + uint16 availableTitleVersion; + }; + + std::set<TitleInstallState> getOwnedTitleList(); + std::set<TitleDownloadAvailableState> getFullDownloadList(); + + /* thread */ + void threadFunc(); + void runManager(); + void notifyManager(); + void queueManagerJob(const std::function<void()>& callback); + + std::atomic_bool m_threadLaunched{ false }; + CounterSemaphore m_queuedEvents; + + ConcurrentQueue<std::function<void()>> m_jobQueue; + + std::mutex m_updateList; // not needed? +}; diff --git a/src/Cemu/napi/napi.h b/src/Cemu/napi/napi.h new file mode 100644 index 00000000..ab17a7b3 --- /dev/null +++ b/src/Cemu/napi/napi.h @@ -0,0 +1,314 @@ +#pragma once +#include <optional> +#include "config/CemuConfig.h" // for ConsoleLanguage + +enum class NAPI_RESULT +{ + SUCCESS = 0, + FAILED = 1, // general failure + XML_ERROR = 2, // XML response unexpected + DATA_ERROR = 3, // incorrect values + SERVICE_ERROR = 4, // server reply indicates error. Extended error code (serviceError) is set +}; + +namespace NAPI +{ + // common auth info structure shared by ACT, ECS and IAS service + struct AuthInfo + { + // todo - constructor for account name + raw password + + // nnid + std::string accountId; + std::array<uint8, 32> passwordHash; + // console + uint32 deviceId; + std::string serial; + CafeConsoleRegion region; + std::string country; + std::string deviceCertBase64; + + uint64 getDeviceIdWithPlatform() + { + uint64 deviceType = 5; + return (deviceType << 32) | ((uint64)deviceId); + } + + // IAS token (for ECS and other SOAP service requests) + struct + { + std::string accountId; // console account id + std::string deviceToken; + }IASToken; + + // ACT token (for account.nintendo.net requests) + + + }; + + bool NAPI_MakeAuthInfoFromCurrentAccount(AuthInfo& authInfo); // helper function. Returns false if online credentials/dumped files are not available + + /* Shared result */ + + // ErrorCodes IAS: + // 928 -> IAS - Device cert does not verify. + // 954 -> IAS - Invalid Challenge + + enum class EC_ERROR_CODE + { + NONE = 0, + ECS_INVALID_DEVICE_TOKEN = 903, + IAS_DEVICE_CERT_ERROR = 928, // either invalid signature or mismatching incorrect device cert chain + IAS_INVALID_CHALLENGE = 954, + ECS_NO_PERMISSION = 701, // AccountGetETickets + NUS_SOAP_INVALID_VERSION = 1301, // seen when accidentally passing wrong SOAP version to GetSystemCommonETicket + }; + + enum class ACT_ERROR_CODE // are these shared with EC_ERROR_CODE? + { + NONE = 0, + ACT_GAME_SERVER_NOT_FOUND = 1021, // seen when passing wrong serverId to nex token request + }; + + struct _NAPI_CommonResultSOAP + { + NAPI_RESULT apiError{ NAPI_RESULT::FAILED }; + EC_ERROR_CODE serviceError{ EC_ERROR_CODE::NONE }; + + bool isValid() const + { + return apiError == NAPI_RESULT::SUCCESS; + } + }; + + struct _NAPI_CommonResultACT + { + NAPI_RESULT apiError{ NAPI_RESULT::FAILED }; + ACT_ERROR_CODE serviceError{ ACT_ERROR_CODE::NONE }; + + bool isValid() const + { + return apiError == NAPI_RESULT::SUCCESS; + } + }; + + /* account service (account.nintendo.net) */ + + struct ACTGetProfileResult + { + int todo; + }; + + bool ACT_GetProfile(AuthInfo& authInfo, ACTGetProfileResult& result); + + struct ACTNexToken + { + /* +0x000 */ char token[0x201]; + /* +0x201 */ uint8 _padding201[3]; + /* +0x204 */ char nexPassword[0x41]; + /* +0x245 */ uint8 _padding245[3]; + /* +0x248 */ char host[0x10]; + /* +0x258 */ uint16be port; + /* +0x25A */ uint8 _padding25A[2]; + }; + + static_assert(sizeof(ACTNexToken) == 0x25C); + + struct ACTGetNexTokenResult : public _NAPI_CommonResultACT + { + ACTNexToken nexToken; + }; + + ACTGetNexTokenResult ACT_GetNexToken_WithCache(AuthInfo& authInfo, uint64 titleId, uint16 titleVersion, uint32 serverId); + + struct ACTGetIndependentTokenResult : public _NAPI_CommonResultACT + { + std::string token; + sint64 expiresIn{}; + }; + + ACTGetIndependentTokenResult ACT_GetIndependentToken_WithCache(AuthInfo& authInfo, uint64 titleId, uint16 titleVersion, std::string_view clientId); + + struct ACTConvertNnidToPrincipalIdResult : public _NAPI_CommonResultACT + { + bool isFound{false}; + uint32 principalId{}; + }; + + ACTConvertNnidToPrincipalIdResult ACT_ACTConvertNnidToPrincipalId(AuthInfo& authInfo, std::string_view nnid); + + /* NUS */ + + struct NAPI_NUSGetSystemCommonETicket_Result : public _NAPI_CommonResultSOAP + { + std::vector<uint8> eTicket; + std::vector<std::vector<uint8>> certs; + }; + + NAPI_NUSGetSystemCommonETicket_Result NUS_GetSystemCommonETicket(AuthInfo& authInfo, uint64 titleId); + + /* IAS/ECS */ + + struct NAPI_IASGetChallenge_Result : public _NAPI_CommonResultSOAP + { + std::string challenge; + }; + + struct NAPI_IASGetRegistrationInfo_Result : public _NAPI_CommonResultSOAP + { + std::string accountId; + std::string deviceToken; + }; + + struct NAPI_ECSGetAccountStatus_Result : public _NAPI_CommonResultSOAP + { + enum class AccountStatus + { + REGISTERED = 'R', + TRANSFERRED = 'T', + UNREGISTERED = 'U', + UNKNOWN = '\0', + }; + + std::string accountId; + AccountStatus accountStatus{ AccountStatus::UNKNOWN }; + + struct + { + std::string ContentPrefixURL; + std::string UncachedContentPrefixURL; + std::string SystemContentPrefixURL; + std::string SystemUncachedContentPrefixURL; + std::string EcsURL; + std::string IasURL; + std::string CasURL; + std::string NusURL; + }serviceURLs; + }; + + struct NAPI_ECSAccountListETicketIds_Result : public _NAPI_CommonResultSOAP + { + struct TIV + { + sint64 ticketId; + uint32 ticketVersion; + }; + + std::vector<TIV> tivs; + }; + + struct NAPI_ECSAccountGetETickets_Result : public _NAPI_CommonResultSOAP + { + std::vector<uint8> eTickets; + std::vector<std::vector<uint8>> certs; + }; + + NAPI_IASGetChallenge_Result IAS_GetChallenge(AuthInfo& authInfo); + NAPI_IASGetRegistrationInfo_Result IAS_GetRegistrationInfo_QueryInfo(AuthInfo& authInfo, std::string challenge); + + NAPI_ECSGetAccountStatus_Result ECS_GetAccountStatus(AuthInfo& authInfo); + NAPI_ECSAccountListETicketIds_Result ECS_AccountListETicketIds(AuthInfo& authInfo); + NAPI_ECSAccountGetETickets_Result ECS_AccountGetETickets(AuthInfo& authInfo, sint64 ticketId); + + /* CCS */ + + struct NAPI_CCSGetTMD_Result + { + bool isValid{ false }; + std::vector<uint8> tmdData; + }; + + struct NAPI_CCSGetETicket_Result + { + bool isValid{ false }; + std::vector<uint8> cetkData; + }; + + struct NAPI_CCSGetContentH3_Result + { + bool isValid{ false }; + std::vector<uint8> tmdData; + }; + + NAPI_CCSGetTMD_Result CCS_GetTMD(AuthInfo& authInfo, uint64 titleId, uint16 titleVersion); + NAPI_CCSGetTMD_Result CCS_GetTMD(AuthInfo& authInfo, uint64 titleId); + NAPI_CCSGetETicket_Result CCS_GetCETK(AuthInfo& authInfo, uint64 titleId, uint16 titleVersion); + bool CCS_GetContentFile(uint64 titleId, uint32 contentId, bool(*cbWriteCallback)(void* userData, const void* ptr, size_t len, bool isLast), void* userData); + NAPI_CCSGetContentH3_Result CCS_GetContentH3File(uint64 titleId, uint32 contentId); + + /* IDBE */ + + struct IDBEIconDataV0 + { + struct LanguageInfo + { + std::string GetGameNameUTF8(); + std::string GetGameLongNameUTF8(); + std::string GetPublisherNameUTF8(); + private: + uint16be gameName[0x40]{}; + uint16be gameLongName[0x80]{}; + uint16be publisherName[0x40]{}; + }; + + LanguageInfo& GetLanguageStrings(CafeConsoleLanguage index) + { + if ((uint32)index >= 16) + return languageInfo[0]; + return languageInfo[(uint32)index]; + } + + private: + /* +0x00000 */ uint32be titleIdHigh{}; + /* +0x00004 */ uint32be titleIdLow{}; + /* +0x00008 */ uint32 ukn00008; + /* +0x0000C */ uint32 ukn0000C; + /* +0x00010 */ uint32 ukn00010; + /* +0x00014 */ uint32 ukn00014; + /* +0x00018 */ uint32 ukn00018; + /* +0x0001C */ uint32 ukn0001C; + /* +0x00020 */ uint32 ukn00020; + /* +0x00024 */ uint32 ukn00024; + /* +0x00028 */ uint32 ukn00028; + /* +0x0002C */ uint32 ukn0002C; + /* +0x00030 */ LanguageInfo languageInfo[16]; + /* +0x02030 */ uint8 tgaData[0x10030]{}; + }; + + static_assert(sizeof(IDBEIconDataV0) == 0x12060); + + struct IDBEHeader + { + uint8 formatVersion; + uint8 keyIndex; + uint8 hashSHA256[32]; + }; + + static_assert(sizeof(IDBEHeader) == 2+32); + + std::optional<IDBEIconDataV0> IDBE_Request(uint64 titleId); + std::vector<uint8> IDBE_RequestRawEncrypted(uint64 titleId); // same as IDBE_Request but doesn't strip the header and decrypt the IDBE + + /* Version list */ + + struct NAPI_VersionListVersion_Result + { + bool isValid{false}; + uint32 version{0}; + std::string fqdnURL; + }; + + NAPI_VersionListVersion_Result TAG_GetVersionListVersion(AuthInfo& authInfo); + + struct NAPI_VersionList_Result + { + bool isValid{ false }; + std::unordered_map<uint64, uint16> titleVersionList; // key = titleId, value = version + }; + + NAPI_VersionList_Result TAG_GetVersionList(AuthInfo& authInfo, std::string_view fqdnURL, uint32 versionListVersion); + +} + +void NAPI_NUS_GetSystemUpdate(); +void NAPI_NUS_GetSystemTitleHash(); diff --git a/src/Cemu/napi/napi_act.cpp b/src/Cemu/napi/napi_act.cpp new file mode 100644 index 00000000..9638dbba --- /dev/null +++ b/src/Cemu/napi/napi_act.cpp @@ -0,0 +1,612 @@ +#include "Common/precompiled.h" +#include "Cemu/ncrypto/ncrypto.h" +#include "napi.h" +#include "napi_helper.h" + +#include "curl/curl.h" +#include "pugixml.hpp" +#include "Cafe/IOSU/legacy/iosu_crypto.h" + +#include "config/ActiveSettings.h" +#include "util/helpers/StringHelpers.h" +#include "util/highresolutiontimer/HighResolutionTimer.h" +#include "config/LaunchSettings.h" + +namespace NAPI +{ + struct ACTOauthToken : public _NAPI_CommonResultACT + { + std::string token; + std::string refreshToken; + }; + + bool _parseActResponse(CurlRequestHelper& requestHelper, _NAPI_CommonResultACT& result, pugi::xml_document& doc) + { + if (!doc.load_buffer(requestHelper.getReceivedData().data(), requestHelper.getReceivedData().size())) + { + cemuLog_log(LogType::Force, fmt::format("Invalid XML in account service response")); + result.apiError = NAPI_RESULT::XML_ERROR; + return false; + } + // check for error codes + pugi::xml_node errors = doc.child("errors"); + if (errors) + { + pugi::xml_node error = errors.child("error"); + if (error) + { + std::string_view errorCodeStr = error.child_value("code"); + std::string_view errorCodeMsg = error.child_value("message"); + sint32 errorCode = StringHelpers::ToInt(errorCodeStr); + if (errorCode == 0) + { + cemuLog_force("Account response with unexpected error code 0"); + result.apiError = NAPI_RESULT::XML_ERROR; + } + else + { + result.apiError = NAPI_RESULT::SERVICE_ERROR; + result.serviceError = (ACT_ERROR_CODE)errorCode; + cemuLog_force("Account response with error code {}", errorCode); + if(!errorCodeMsg.empty()) + cemuLog_force("Message from server: {}", errorCodeMsg); + } + } + else + { + result.apiError = NAPI_RESULT::XML_ERROR; + } + return false; + } + return true; + } + + void _ACTSetCommonHeaderParameters(CurlRequestHelper& req) + { + req.addHeaderField("X-Nintendo-Platform-ID", "1"); + req.addHeaderField("X-Nintendo-Device-Type", "2"); + + req.addHeaderField("X-Nintendo-Client-ID", "a2efa818a34fa16b8afbc8a74eba3eda"); + req.addHeaderField("X-Nintendo-Client-Secret", "c91cdb5658bd4954ade78533a339cf9a"); + + req.addHeaderField("Accept", "*/*"); + + req.addHeaderField("X-Nintendo-System-Version", "0260"); + } + + void _ACTSetDeviceParameters(CurlRequestHelper& req, AuthInfo& authInfo) + { + req.addHeaderField("X-Nintendo-Device-ID", fmt::format("{}", authInfo.deviceId)); // deviceId without platform field + req.addHeaderField("X-Nintendo-Serial-Number", authInfo.serial); + } + + void _ACTSetRegionAndCountryParameters(CurlRequestHelper& req, AuthInfo& authInfo) + { + req.addHeaderField("X-Nintendo-Region", fmt::format("{}", (uint32)authInfo.region)); + req.addHeaderField("X-Nintendo-Country", authInfo.country); + } + + struct OAuthTokenCacheEntry + { + OAuthTokenCacheEntry(std::string_view accountId, std::array<uint8, 32>& passwordHash, std::string_view token, std::string_view refreshToken, uint64 expiresIn) : accountId(accountId), passwordHash(passwordHash), token(token), refreshToken(refreshToken) + { + expires = HighResolutionTimer::now().getTickInSeconds() + expiresIn; + }; + + bool CheckIfSameAccount(const AuthInfo& authInfo) const + { + return authInfo.accountId == accountId && authInfo.passwordHash == passwordHash; + } + + bool CheckIfExpired() const + { + return HighResolutionTimer::now().getTickInSeconds() >= expires; + } + std::string accountId; + std::array<uint8, 32> passwordHash; + + std::string token; + std::string refreshToken; + uint64 expires; + }; + + std::vector<OAuthTokenCacheEntry> g_oauthTokenCache; + std::mutex g_oauthTokenCacheMtx; + + // look up oauth token in cache, otherwise request from server + ACTOauthToken ACT_GetOauthToken_WithCache(AuthInfo& authInfo, uint64 titleId, uint16 titleVersion) + { + ACTOauthToken result{}; + + // check cache first + g_oauthTokenCacheMtx.lock(); + auto cacheItr = g_oauthTokenCache.begin(); + while (cacheItr != g_oauthTokenCache.end()) + { + if (cacheItr->CheckIfSameAccount(authInfo)) + { + if (cacheItr->CheckIfExpired()) + { + cacheItr = g_oauthTokenCache.erase(cacheItr); + continue; + } + result.token = cacheItr->token; + result.refreshToken = cacheItr->refreshToken; + result.apiError = NAPI_RESULT::SUCCESS; + g_oauthTokenCacheMtx.unlock(); + return result; + } + cacheItr++; + } + g_oauthTokenCacheMtx.unlock(); + // token not cached, request from server via oauth2 + CurlRequestHelper req; + + req.initate(fmt::format("{}/v1/api/oauth20/access_token/generate", LaunchSettings::GetActURLPrefix()), CurlRequestHelper::SERVER_SSL_CONTEXT::ACT); + _ACTSetCommonHeaderParameters(req); + _ACTSetDeviceParameters(req, authInfo); + _ACTSetRegionAndCountryParameters(req, authInfo); + req.addHeaderField("X-Nintendo-Device-Cert", authInfo.deviceCertBase64); + + req.addHeaderField("X-Nintendo-FPD-Version", "0000"); + req.addHeaderField("X-Nintendo-Environment", "L1"); + req.addHeaderField("X-Nintendo-Title-ID", fmt::format("{:016x}", titleId)); + uint32 uniqueId = ((titleId >> 8) & 0xFFFFF); + req.addHeaderField("X-Nintendo-Unique-ID", fmt::format("{:05x}", uniqueId)); + req.addHeaderField("X-Nintendo-Application-Version", fmt::format("{:04x}", titleVersion)); + + // convert password hash to string + char passwordHashString[128]; + for (sint32 i = 0; i < 32; i++) + sprintf(passwordHashString + i * 2, "%02x", authInfo.passwordHash[i]); + req.addPostField("grant_type", "password"); + req.addPostField("user_id", authInfo.accountId); + req.addPostField("password", passwordHashString); + req.addPostField("password_type", "hash"); + + req.addHeaderField("Content-type", "application/x-www-form-urlencoded"); + + if (!req.submitRequest(true)) + { + cemuLog_log(LogType::Force, fmt::format("Failed request /oauth20/access_token/generate")); + result.apiError = NAPI_RESULT::FAILED; + return result; + } + + /* + Response example: + <OAuth20> + <access_token> + <token>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</token> + <refresh_token>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</refresh_token> + <expires_in>3600</expires_in> + </access_token> + </OAuth20> + */ + + // parse result + pugi::xml_document doc; + if (!_parseActResponse(req, result, doc)) + return result; + pugi::xml_node node = doc.child("OAuth20"); + if (!node) + { + cemuLog_log(LogType::Force, fmt::format("Response does not contain OAuth20 node")); + result.apiError = NAPI_RESULT::XML_ERROR; + return result; + } + node = node.child("access_token"); + if (!node) + { + cemuLog_log(LogType::Force, fmt::format("Response does not contain OAuth20/access_token node")); + result.apiError = NAPI_RESULT::XML_ERROR; + return result; + } + + result.token = node.child_value("token"); + result.refreshToken = node.child_value("refresh_token"); + std::string_view expires_in = node.child_value("expires_in"); + result.apiError = NAPI_RESULT::SUCCESS; + + if (result.token.empty()) + cemuLog_force("OAuth20/token is empty"); + sint64 expiration = StringHelpers::ToInt64(expires_in); + expiration = std::max(expiration - 30LL, 0LL); // subtract a few seconds to compensate for the web request delay + + // update cache + if (expiration > 0) + { + g_oauthTokenCacheMtx.lock(); + g_oauthTokenCache.emplace_back(authInfo.accountId, authInfo.passwordHash, result.token, result.refreshToken, expiration); + g_oauthTokenCacheMtx.unlock(); + } + return result; + } + + bool ACT_GetProfile(AuthInfo& authInfo, ACTGetProfileResult& result) + { + CurlRequestHelper req; + + req.initate(fmt::format("{}/v1/api/people/@me/profile", LaunchSettings::GetActURLPrefix()), CurlRequestHelper::SERVER_SSL_CONTEXT::ACT); + + _ACTSetCommonHeaderParameters(req); + _ACTSetDeviceParameters(req, authInfo); + + // get oauth2 token + ACTOauthToken oauthToken = ACT_GetOauthToken_WithCache(authInfo, 0x0005001010001C00, 0x0001C); + + + cemu_assert_unimplemented(); + return true; + } + + struct NexTokenCacheEntry + { + NexTokenCacheEntry(std::string_view accountId, std::array<uint8, 32>& passwordHash, uint32 gameServerId, ACTNexToken& nexToken) : accountId(accountId), passwordHash(passwordHash), nexToken(nexToken), gameServerId(gameServerId) {}; + + bool IsMatch(const AuthInfo& authInfo, const uint32 gameServerId) const + { + return authInfo.accountId == accountId && authInfo.passwordHash == passwordHash && this->gameServerId == gameServerId; + } + + std::string accountId; + std::array<uint8, 32> passwordHash; + uint32 gameServerId; + + ACTNexToken nexToken; + }; + + std::vector<NexTokenCacheEntry> g_nexTokenCache; + std::mutex g_nexTokenCacheMtx; + + ACTGetNexTokenResult ACT_GetNexToken_WithCache(AuthInfo& authInfo, uint64 titleId, uint16 titleVersion, uint32 serverId) + { + ACTGetNexTokenResult result{}; + // check cache + g_nexTokenCacheMtx.lock(); + for (auto& itr : g_nexTokenCache) + { + if (itr.IsMatch(authInfo, serverId)) + { + result.nexToken = itr.nexToken; + result.apiError = NAPI_RESULT::SUCCESS; + g_nexTokenCacheMtx.unlock(); + return result; + + } + } + g_nexTokenCacheMtx.unlock(); + // get Nex token + ACTOauthToken oauthToken = ACT_GetOauthToken_WithCache(authInfo, titleId, titleVersion); + if (!oauthToken.isValid()) + { + cemuLog_force("ACT_GetNexToken(): Failed to retrieve OAuth token"); + if (oauthToken.apiError == NAPI_RESULT::SERVICE_ERROR) + { + result.apiError = NAPI_RESULT::SERVICE_ERROR; + result.serviceError = oauthToken.serviceError; + } + else + { + result.apiError = NAPI_RESULT::DATA_ERROR; + } + return result; + } + // do request + CurlRequestHelper req; + req.initate(fmt::format("{}/v1/api/provider/nex_token/@me?game_server_id={:08X}", LaunchSettings::GetActURLPrefix(), serverId), CurlRequestHelper::SERVER_SSL_CONTEXT::ACT); + _ACTSetCommonHeaderParameters(req); + _ACTSetDeviceParameters(req, authInfo); + _ACTSetRegionAndCountryParameters(req, authInfo); + req.addHeaderField("X-Nintendo-FPD-Version", "0000"); + req.addHeaderField("X-Nintendo-Environment", "L1"); + req.addHeaderField("X-Nintendo-Title-ID", fmt::format("{:016x}", titleId)); + uint32 uniqueId = ((titleId >> 8) & 0xFFFFF); + req.addHeaderField("X-Nintendo-Unique-ID", fmt::format("{:05x}", uniqueId)); + req.addHeaderField("X-Nintendo-Application-Version", fmt::format("{:04x}", titleVersion)); + + req.addHeaderField("Authorization", fmt::format("Bearer {}", oauthToken.token)); + + if (!req.submitRequest(false)) + { + cemuLog_log(LogType::Force, fmt::format("Failed request /provider/nex_token/@me")); + result.apiError = NAPI_RESULT::FAILED; + return result; + } + + /* + Example response (success): + <?xml version="1.0" encoding="UTF-8" standalone="yes"?> + <nex_token> + <host>HOST</host> + <nex_password>xxxxxxxxxxxxxxxx</nex_password> + <pid>123456</pid> + <port>60200</port> + <token>xxxxxxxxxxxxxxx</token> + </nex_token> + + + Example response (error case): + <?xml version="1.0" encoding="UTF-8" standalone="yes"?> + <errors> + <error> + <code>1021</code> + <message>The requested game server was not found.</message> + </error> + </errors> + */ + + // code 0124 -> Version lower than useable registered + + // parse result + pugi::xml_document doc; + if (!_parseActResponse(req, result, doc)) + return result; + pugi::xml_node tokenNode = doc.child("nex_token"); + if (!tokenNode) + { + cemuLog_force("Response does not contain NexToken node"); + result.apiError = NAPI_RESULT::XML_ERROR; + return result; + } + + std::string_view host = tokenNode.child_value("host"); + std::string_view nex_password = tokenNode.child_value("nex_password"); + std::string_view pid = tokenNode.child_value("pid"); + std::string_view port = tokenNode.child_value("port"); + std::string_view token = tokenNode.child_value("token"); + + std::memset(&result.nexToken, 0, sizeof(result.nexToken)); + if (host.size() > 15) + cemuLog_force("NexToken response: host field too long"); + if (nex_password.size() > 64) + cemuLog_force("NexToken response: nex_password field too long"); + if (token.size() > 512) + cemuLog_force("NexToken response: token field too long"); + for (size_t i = 0; i < std::min(host.size(), (size_t)15); i++) + result.nexToken.host[i] = host[i]; + for (size_t i = 0; i < std::min(nex_password.size(), (size_t)64); i++) + result.nexToken.nexPassword[i] = nex_password[i]; + for (size_t i = 0; i < std::min(token.size(), (size_t)512); i++) + result.nexToken.token[i] = token[i]; + result.nexToken.port = (uint16)StringHelpers::ToInt(port); + result.apiError = NAPI_RESULT::SUCCESS; + g_nexTokenCacheMtx.lock(); + g_nexTokenCache.emplace_back(authInfo.accountId, authInfo.passwordHash, serverId, result.nexToken); + g_nexTokenCacheMtx.unlock(); + return result; + } + + struct IndependentTokenCacheEntry + { + IndependentTokenCacheEntry(std::string_view accountId, std::array<uint8, 32>& passwordHash, std::string_view clientId, std::string_view independentToken, sint64 expiresIn) : accountId(accountId), passwordHash(passwordHash), clientId(clientId), independentToken(independentToken) + { + expires = HighResolutionTimer::now().getTickInSeconds() + expiresIn; + }; + + bool IsMatch(const AuthInfo& authInfo, const std::string_view clientId) const + { + return authInfo.accountId == accountId && authInfo.passwordHash == passwordHash && this->clientId == clientId; + } + + bool CheckIfExpired() const + { + return (sint64)HighResolutionTimer::now().getTickInSeconds() >= expires; + } + + std::string accountId; + std::array<uint8, 32> passwordHash; + std::string clientId; + sint64 expires; + + std::string independentToken; + }; + + std::vector<IndependentTokenCacheEntry> g_IndependentTokenCache; + std::mutex g_IndependentTokenCacheMtx; + + ACTGetIndependentTokenResult ACT_GetIndependentToken_WithCache(AuthInfo& authInfo, uint64 titleId, uint16 titleVersion, std::string_view clientId) + { + ACTGetIndependentTokenResult result{}; + // check cache + g_IndependentTokenCacheMtx.lock(); + auto itr = g_IndependentTokenCache.begin(); + while(itr != g_IndependentTokenCache.end()) + { + if (itr->CheckIfExpired()) + { + itr = g_IndependentTokenCache.erase(itr); + continue; + } + else if (itr->IsMatch(authInfo, clientId)) + { + result.token = itr->independentToken; + result.expiresIn = std::max(itr->expires - (sint64)HighResolutionTimer::now().getTickInSeconds(), (sint64)0); + result.apiError = NAPI_RESULT::SUCCESS; + g_IndependentTokenCacheMtx.unlock(); + return result; + } + itr++; + } + g_IndependentTokenCacheMtx.unlock(); + // get Independent token + ACTOauthToken oauthToken = ACT_GetOauthToken_WithCache(authInfo, titleId, titleVersion); + if (!oauthToken.isValid()) + { + cemuLog_force("ACT_GetIndependentToken(): Failed to retrieve OAuth token"); + if (oauthToken.apiError == NAPI_RESULT::SERVICE_ERROR) + { + result.apiError = NAPI_RESULT::SERVICE_ERROR; + result.serviceError = oauthToken.serviceError; + } + else + { + result.apiError = NAPI_RESULT::DATA_ERROR; + } + return result; + } + // do request + CurlRequestHelper req; + req.initate(fmt::format("{}/v1/api/provider/service_token/@me?client_id={}", LaunchSettings::GetActURLPrefix(), clientId), CurlRequestHelper::SERVER_SSL_CONTEXT::ACT); + _ACTSetCommonHeaderParameters(req); + _ACTSetDeviceParameters(req, authInfo); + _ACTSetRegionAndCountryParameters(req, authInfo); + req.addHeaderField("X-Nintendo-FPD-Version", "0000"); + req.addHeaderField("X-Nintendo-Environment", "L1"); + req.addHeaderField("X-Nintendo-Title-ID", fmt::format("{:016x}", titleId)); + uint32 uniqueId = ((titleId >> 8) & 0xFFFFF); + req.addHeaderField("X-Nintendo-Unique-ID", fmt::format("{:05x}", uniqueId)); + req.addHeaderField("X-Nintendo-Application-Version", fmt::format("{:04x}", titleVersion)); + + req.addHeaderField("Authorization", fmt::format("Bearer {}", oauthToken.token)); + + if (!req.submitRequest(false)) + { + cemuLog_log(LogType::Force, fmt::format("Failed request /provider/service_token/@me")); + result.apiError = NAPI_RESULT::FAILED; + return result; + } + + /* + Example response (success): + <?xml version="1.0" encoding="UTF-8" standalone="yes"?> + <service_token> + <token>xxxxxxxxxxxx</token> + </service_token> + */ + + // parse result + pugi::xml_document doc; + if (!_parseActResponse(req, result, doc)) + return result; + pugi::xml_node tokenNode = doc.child("service_token"); + if (!tokenNode) + { + cemuLog_force("Response does not contain service_token node"); + result.apiError = NAPI_RESULT::XML_ERROR; + return result; + } + + std::string_view token = tokenNode.child_value("token"); + result.token = token; + result.apiError = NAPI_RESULT::SUCCESS; + + g_IndependentTokenCacheMtx.lock(); + g_IndependentTokenCache.emplace_back(authInfo.accountId, authInfo.passwordHash, clientId, result.token, 3600); + g_IndependentTokenCacheMtx.unlock(); + return result; + } + + ACTConvertNnidToPrincipalIdResult ACT_ACTConvertNnidToPrincipalId(AuthInfo& authInfo, std::string_view nnid) + { + ACTConvertNnidToPrincipalIdResult result{}; + // get Independent token + ACTOauthToken oauthToken = ACT_GetOauthToken_WithCache(authInfo, 0x0005001010001C00, 0); + if (!oauthToken.isValid()) + { + cemuLog_force("ACT_ACTConvertNnidToPrincipalId(): Failed to retrieve OAuth token"); + if (oauthToken.apiError == NAPI_RESULT::SERVICE_ERROR) + { + result.apiError = NAPI_RESULT::SERVICE_ERROR; + result.serviceError = oauthToken.serviceError; + } + else + { + result.apiError = NAPI_RESULT::DATA_ERROR; + } + return result; + } + // do request + CurlRequestHelper req; + req.initate(fmt::format("{}/v1/api/admin/mapped_ids?input_type=user_id&output_type=pid&input={}", LaunchSettings::GetActURLPrefix(), nnid), CurlRequestHelper::SERVER_SSL_CONTEXT::ACT); + _ACTSetCommonHeaderParameters(req); + _ACTSetDeviceParameters(req, authInfo); + _ACTSetRegionAndCountryParameters(req, authInfo); + req.addHeaderField("X-Nintendo-FPD-Version", "0000"); + req.addHeaderField("X-Nintendo-Environment", "L1"); + req.addHeaderField("X-Nintendo-Title-ID", fmt::format("{:016x}", 0x0005001010001C00)); + uint32 uniqueId = 0x50010; + req.addHeaderField("X-Nintendo-Unique-ID", fmt::format("{:05x}", uniqueId)); + req.addHeaderField("X-Nintendo-Application-Version", fmt::format("{:04x}", 0)); + + req.addHeaderField("Authorization", fmt::format("Bearer {}", oauthToken.token)); + + if (!req.submitRequest(false)) + { + cemuLog_log(LogType::Force, fmt::format("Failed request /admin/mapped_ids")); + result.apiError = NAPI_RESULT::FAILED; + return result; + } + + /* + Example response (success): + <?xml version="1.0" encoding="UTF-8" standalone="yes"?> + <mapped_ids> + <mapped_id> + <in_id>input-nnid</in_id> + <out_id>12345</out_id> + </mapped_id> + </mapped_ids> + */ + + // parse result + pugi::xml_document doc; + if (!_parseActResponse(req, result, doc)) + return result; + pugi::xml_node tokenNode = doc.child("mapped_ids"); + if (!tokenNode) + { + cemuLog_force("Response does not contain mapped_ids node"); + result.apiError = NAPI_RESULT::XML_ERROR; + return result; + } + tokenNode = tokenNode.child("mapped_id"); + if (!tokenNode) + { + cemuLog_force("Response does not contain mapped_id node"); + result.apiError = NAPI_RESULT::XML_ERROR; + return result; + } + std::string_view pidString = tokenNode.child_value("out_id"); + if (!pidString.empty()) + { + result.isFound = true; + result.principalId = StringHelpers::ToInt(pidString); + } + else + { + result.isFound = false; + result.principalId = 0; + } + result.apiError = NAPI_RESULT::SUCCESS; + return result; + } + + bool NAPI_MakeAuthInfoFromCurrentAccount(AuthInfo& authInfo) + { + authInfo = {}; + if (!NCrypto::SEEPROM_IsPresent()) + return false; + const Account& account = Account::GetCurrentAccount(); + authInfo.accountId = account.GetAccountId(); + auto passwordHash = account.GetAccountPasswordCache(); + authInfo.passwordHash = passwordHash; + if (std::all_of(passwordHash.cbegin(), passwordHash.cend(), [](uint8 v) { return v == 0; })) + { + static bool s_showedLoginError = false; + if (!s_showedLoginError) + { + cemuLog_force("Account login is impossible because the cached password hash is not set"); + s_showedLoginError = true; + } + return false; // password hash not set + } + authInfo.deviceId = NCrypto::GetDeviceId(); + authInfo.serial = NCrypto::GetSerial(); + authInfo.region = NCrypto::SEEPROM_GetRegion(); + authInfo.country = NCrypto::GetCountryAsString(account.GetCountry()); + authInfo.deviceCertBase64 = NCrypto::CertECC::GetDeviceCertificate().encodeToBase64(); + + return true; + } +} diff --git a/src/Cemu/napi/napi_ec.cpp b/src/Cemu/napi/napi_ec.cpp new file mode 100644 index 00000000..82ab5be4 --- /dev/null +++ b/src/Cemu/napi/napi_ec.cpp @@ -0,0 +1,531 @@ +#include "Common/precompiled.h" +#include "napi.h" +#include "napi_helper.h" + +#include "curl/curl.h" +#include "Cafe/IOSU/legacy/iosu_crypto.h" + +#include "Cemu/ncrypto/ncrypto.h" +#include "util/crypto/md5.h" +#include "config/LaunchSettings.h" + +#include "pugixml.hpp" +#include <charconv> + +namespace NAPI +{ + /* Service URL manager */ + std::string s_serviceURL_ContentPrefixURL; + std::string s_serviceURL_UncachedContentPrefixURL; + std::string s_serviceURL_EcsURL; + std::string s_serviceURL_IasURL; + std::string s_serviceURL_CasURL; + std::string s_serviceURL_NusURL; + + std::string _getNUSUrl() + { + if (!s_serviceURL_NusURL.empty()) + return s_serviceURL_NusURL; + return "https://nus.wup.shop.nintendo.net/nus/services/NetUpdateSOAP"; + } + + std::string _getIASUrl() + { + if (!s_serviceURL_IasURL.empty()) + return s_serviceURL_IasURL; + return "https://ias.wup.shop.nintendo.net/ias/services/IdentityAuthenticationSOAP"; + } + + std::string _getECSUrl() + { + // this is the first url queried (GetAccountStatus). The others are dynamically set if provided by the server but will fallback to hardcoded defaults otherwise + if (!s_serviceURL_EcsURL.empty()) + return s_serviceURL_EcsURL; + return LaunchSettings::GetServiceURL_ecs(); // by default this is "https://ecs.wup.shop.nintendo.net/ecs/services/ECommerceSOAP" + } + + std::string _getCCSUncachedUrl() // used for TMD requests + { + if (!s_serviceURL_UncachedContentPrefixURL.empty()) + return s_serviceURL_UncachedContentPrefixURL; + return "https://ccs.wup.shop.nintendo.net/ccs/download"; + } + + std::string _getCCSUrl() // used for game data downloads + { + if (!s_serviceURL_ContentPrefixURL.empty()) + return s_serviceURL_ContentPrefixURL; + return "http://ccs.cdn.wup.shop.nintendo.net/ccs/download"; + } + + /* NUS */ + + // request ticket for titles which have a public eTicket (usually updates and system titles) + NAPI_NUSGetSystemCommonETicket_Result NUS_GetSystemCommonETicket(AuthInfo& authInfo, uint64 titleId) + { + NAPI_NUSGetSystemCommonETicket_Result result{}; + + CurlSOAPHelper soapHelper; + soapHelper.SOAP_initate("nus", _getNUSUrl(), "GetSystemCommonETicket", "1.0"); + + soapHelper.SOAP_addRequestField("DeviceId", fmt::format("{}", authInfo.getDeviceIdWithPlatform())); + soapHelper.SOAP_addRequestField("RegionId", NCrypto::GetRegionAsString(authInfo.region)); + soapHelper.SOAP_addRequestField("CountryCode", authInfo.country); + soapHelper.SOAP_addRequestField("SerialNo", authInfo.serial); + + soapHelper.SOAP_addRequestField("TitleId", fmt::format("{:016X}", titleId)); + + if (!soapHelper.submitRequest()) + { + result.apiError = NAPI_RESULT::FAILED; + return result; + } + + // parse result + pugi::xml_document doc; + pugi::xml_node responseNode; + if (!_parseResponseInit(soapHelper, "GetSystemCommonETicketResponse", responseNode, result, doc, responseNode)) + return result; + + const char* eTicketsStr = responseNode.child_value("CommonETicket"); + result.eTicket = NCrypto::base64Decode(eTicketsStr); + if (result.eTicket.empty()) + { + cemuLog_log(LogType::Force, "GetSystemCommonETicketResponse: Invalid eTicket data in response"); + result.apiError = NAPI_RESULT::DATA_ERROR; + return result; + } + + for (pugi::xml_node certNode : responseNode.children("Certs")) + { + const char* certStringValue = certNode.child_value(); + auto certData = NCrypto::base64Decode(certStringValue); + if (certData.empty()) + { + cemuLog_log(LogType::Force, "GetSystemCommonETicketResponse: Invalid cert data in response"); + result.apiError = NAPI_RESULT::DATA_ERROR; + return result; + } + result.certs.emplace_back(NCrypto::base64Decode(certStringValue)); + } + + return result; + } + + /* IAS */ + + NAPI_IASGetChallenge_Result IAS_GetChallenge(AuthInfo& authInfo) + { + NAPI_IASGetChallenge_Result result{}; + + CurlSOAPHelper soapHelper; + soapHelper.SOAP_initate("ias", _getIASUrl(), "GetChallenge", "2.0"); + + soapHelper.SOAP_addRequestField("DeviceId", fmt::format("{}", authInfo.getDeviceIdWithPlatform())); // not validated but the generated Challenge is bound to this DeviceId + soapHelper.SOAP_addRequestField("Region", NCrypto::GetRegionAsString(authInfo.region)); + soapHelper.SOAP_addRequestField("Country", authInfo.country); + + if (!soapHelper.submitRequest()) + { + result.apiError = NAPI_RESULT::FAILED; + return result; + } + + /* parse result */ + pugi::xml_document doc; + pugi::xml_node responseNode; + if (!_parseResponseInit(soapHelper, "GetChallengeResponse", responseNode, result, doc, responseNode)) + return result; + result.challenge = responseNode.child_value("Challenge"); + return result; + } + + NAPI_IASGetRegistrationInfo_Result IAS_GetRegistrationInfo_QueryInfo(AuthInfo& authInfo, std::string challenge) + { + NAPI_IASGetRegistrationInfo_Result result; + CurlSOAPHelper soapHelper; + soapHelper.SOAP_initate("ias", _getIASUrl(), "GetRegistrationInfo", "2.0"); + + soapHelper.SOAP_addRequestField("DeviceId", fmt::format("{}", authInfo.getDeviceIdWithPlatform())); // this must match the DeviceId used to generate Challenge + soapHelper.SOAP_addRequestField("Region", NCrypto::GetRegionAsString(authInfo.region)); + soapHelper.SOAP_addRequestField("Country", authInfo.country); + + // string to sign + // differs depending on the call mode + std::string signString; + signString.reserve(1024); + signString.append(fmt::format("<Challenge>{}</Challenge>", challenge)); + + soapHelper.SOAP_addRequestField("Challenge", challenge); + + uint32 signerTitleIdHigh = 0x00050010; + uint32 signerTitleIdLow = 0x10040300; + + NCrypto::CertECC deviceCert; + if (!deviceCert.decodeFromBase64(authInfo.deviceCertBase64)) + { + result.apiError = NAPI_RESULT::FAILED; + return result; + } + + NCrypto::CHash256 hash; + NCrypto::GenerateHashSHA256(signString.data(), signString.size(), hash); + + NCrypto::CertECC certChain; + NCrypto::ECCSig signature = NCrypto::signHash(signerTitleIdHigh, signerTitleIdLow, hash.b, sizeof(hash.b), certChain); + + auto certChainStr = certChain.encodeToBase64(); + + soapHelper.SOAP_addRequestField("Signature", signature.encodeToBase64()); + soapHelper.SOAP_addRequestField("CertChain", certChainStr); + soapHelper.SOAP_addRequestField("DeviceCert", deviceCert.encodeToBase64()); + + if (!soapHelper.submitRequest()) + { + result.apiError = NAPI_RESULT::FAILED; + return result; + } + + // parse result + pugi::xml_document doc; + pugi::xml_node responseNode; + if (!_parseResponseInit(soapHelper, "GetRegistrationInfoResponse", responseNode, result, doc, responseNode)) + return result; + + result.accountId = responseNode.child_value("AccountId"); + result.deviceToken = responseNode.child_value("DeviceToken"); + + if (boost::iequals(responseNode.child_value("DeviceTokenExpired"), "true")) + forceLog_printf("Unexpected server response: Device token expired"); + + /* + example response: + + <Version>2.0</Version> + <DeviceId>1234567</DeviceId> + <MessageId>EC-1234-1234</MessageId> + <TimeStamp>123456789</TimeStamp> + <ErrorCode>0</ErrorCode> + <ServiceStandbyMode>false/true</ServiceStandbyMode> + <AccountId>123456</AccountId> + <DeviceToken>DEVICE_TOKEN_STR</DeviceToken> + <DeviceTokenExpired>false/true</DeviceTokenExpired> + <Country>COUNTRYCODE</Country> + <ExtAccountId></ExtAccountId> + <DeviceStatus>R</DeviceStatus> + <Currency>EUR</Currency> + */ + + + return result; + } + + /* ECS */ + + std::string _getDeviceTokenWT(std::string_view deviceToken) + { + uint8 wtHash[16]; + MD5_CTX md5Ctx; + MD5_Init(&md5Ctx); + MD5_Update(&md5Ctx, deviceToken.data(), (unsigned long)deviceToken.size()); + MD5_Final(wtHash, &md5Ctx); + + std::string wtString; + wtString.reserve(4 + 32); + wtString.append("WT-"); + for (uint8& b : wtHash) + { + wtString.append(fmt::format("{0:02x}", b)); + } + return wtString; + } + + NAPI_ECSGetAccountStatus_Result ECS_GetAccountStatus(AuthInfo& authInfo) + { + NAPI_ECSGetAccountStatus_Result result{}; + + CurlSOAPHelper soapHelper; + soapHelper.SOAP_initate("ecs", _getECSUrl(), "GetAccountStatus", "2.0"); + + soapHelper.SOAP_addRequestField("DeviceId", fmt::format("{}", authInfo.getDeviceIdWithPlatform())); + soapHelper.SOAP_addRequestField("Region", NCrypto::GetRegionAsString(authInfo.region)); + soapHelper.SOAP_addRequestField("Country", authInfo.country); + + if(!authInfo.IASToken.accountId.empty()) + soapHelper.SOAP_addRequestField("AccountId", authInfo.IASToken.accountId); + if (!authInfo.IASToken.deviceToken.empty()) + soapHelper.SOAP_addRequestField("DeviceToken", _getDeviceTokenWT(authInfo.IASToken.deviceToken)); + + if (!soapHelper.submitRequest()) + { + result.apiError = NAPI_RESULT::FAILED; + return result; + } + + pugi::xml_document doc; + pugi::xml_node responseNode; + if (!_parseResponseInit(soapHelper, "GetAccountStatusResponse", responseNode, result, doc, responseNode)) + return result; + + result.accountId = responseNode.child_value("AccountId"); + const char* accountStatusStr = responseNode.child_value("AccountStatus"); + if (accountStatusStr) + { + if (accountStatusStr[0] == 'R') + result.accountStatus = NAPI_ECSGetAccountStatus_Result::AccountStatus::REGISTERED; + else if (accountStatusStr[0] == 'T') + result.accountStatus = NAPI_ECSGetAccountStatus_Result::AccountStatus::TRANSFERRED; + else if (accountStatusStr[0] == 'U') + result.accountStatus = NAPI_ECSGetAccountStatus_Result::AccountStatus::UNREGISTERED; + else + { + cemuLog_force("ECS_GetAccountStatus: Account has unknown status code {}", accountStatusStr); + } + } + // extract service URLs + for (pugi::xml_node serviceURLNode : responseNode.children("ServiceURLs")) + { + std::string_view serviceType = serviceURLNode.child_value("Name"); + std::string_view url = serviceURLNode.child_value("URI"); + if(serviceType.empty() || url.empty()) + continue; + if (boost::iequals(serviceType, "ContentPrefixURL")) + result.serviceURLs.ContentPrefixURL = url; + else if (boost::iequals(serviceType, "UncachedContentPrefixURL")) + result.serviceURLs.UncachedContentPrefixURL = url; + else if (boost::iequals(serviceType, "SystemContentPrefixURL")) + result.serviceURLs.SystemContentPrefixURL = url; + else if (boost::iequals(serviceType, "SystemUncachedContentPrefixURL")) + result.serviceURLs.SystemUncachedContentPrefixURL = url; + else if (boost::iequals(serviceType, "EcsURL")) + result.serviceURLs.EcsURL = url; + else if (boost::iequals(serviceType, "IasURL")) + result.serviceURLs.IasURL = url; + else if (boost::iequals(serviceType, "CasURL")) + result.serviceURLs.CasURL = url; + else if (boost::iequals(serviceType, "NusURL")) + result.serviceURLs.NusURL = url; + else + forceLog_printf("GetAccountStatus: Unknown service URI type {}", serviceType); + } + + // assign service URLs + if (!result.serviceURLs.ContentPrefixURL.empty()) + s_serviceURL_ContentPrefixURL = result.serviceURLs.ContentPrefixURL; + if (!result.serviceURLs.UncachedContentPrefixURL.empty()) + s_serviceURL_UncachedContentPrefixURL = result.serviceURLs.UncachedContentPrefixURL; + if (!result.serviceURLs.IasURL.empty()) + s_serviceURL_IasURL = result.serviceURLs.IasURL; + if (!result.serviceURLs.CasURL.empty()) + s_serviceURL_CasURL = result.serviceURLs.CasURL; + if (!result.serviceURLs.NusURL.empty()) + s_serviceURL_NusURL = result.serviceURLs.NusURL; + if (!result.serviceURLs.EcsURL.empty()) + s_serviceURL_EcsURL = result.serviceURLs.EcsURL; + + return result; + } + + NAPI_ECSAccountListETicketIds_Result ECS_AccountListETicketIds(AuthInfo& authInfo) + { + NAPI_ECSAccountListETicketIds_Result result{}; + + CurlSOAPHelper soapHelper; + soapHelper.SOAP_initate("ecs", _getECSUrl(), "AccountListETicketIds", "2.0"); + + soapHelper.SOAP_addRequestField("DeviceId", fmt::format("{}", authInfo.getDeviceIdWithPlatform())); + soapHelper.SOAP_addRequestField("Region", NCrypto::GetRegionAsString(authInfo.region)); + soapHelper.SOAP_addRequestField("Country", authInfo.country); + + if (!authInfo.IASToken.accountId.empty()) + soapHelper.SOAP_addRequestField("AccountId", authInfo.IASToken.accountId); + if (!authInfo.IASToken.deviceToken.empty()) + soapHelper.SOAP_addRequestField("DeviceToken", _getDeviceTokenWT(authInfo.IASToken.deviceToken)); + + if (!soapHelper.submitRequest()) + { + result.apiError = NAPI_RESULT::FAILED; + return result; + } + + pugi::xml_document doc; + pugi::xml_node responseNode; + if (!_parseResponseInit(soapHelper, "AccountListETicketIdsResponse", responseNode, result, doc, responseNode)) + return result; + + // extract ticket IVs + for (pugi::xml_node tivNode : responseNode.children("TIV")) + { + // TIV is encoded as <ticketId>.<ticketVersion> + // ticketVersion starts at 0 and increments every time the ticket is updated (e.g. for AOC content, when purchasing additional DLC packages) + const char* tivValue = tivNode.child_value(); + const char* tivValueEnd = tivValue + strlen(tivValue); + const char* tivValueSeparator = std::strstr(tivValue, "."); + if (tivValueSeparator == nullptr) + tivValueSeparator = tivValueEnd; + + // parse ticket id + sint64 ticketId = 0; + std::from_chars_result fcr = std::from_chars(tivValue, tivValueSeparator, ticketId); + if (fcr.ec == std::errc::invalid_argument || fcr.ec == std::errc::result_out_of_range) + { + result.apiError = NAPI_RESULT::XML_ERROR; + return result; + } + // parse ticket version + uint32 ticketVersion = 0; + fcr = std::from_chars(tivValueSeparator+1, tivValueEnd, ticketVersion); + if (fcr.ec == std::errc::invalid_argument || fcr.ec == std::errc::result_out_of_range) + { + result.apiError = NAPI_RESULT::XML_ERROR; + return result; + } + result.tivs.push_back({ ticketId , ticketVersion }); + } + return result; + } + + NAPI_ECSAccountGetETickets_Result ECS_AccountGetETickets(AuthInfo& authInfo, sint64 ticketId) + { + NAPI_ECSAccountGetETickets_Result result{}; + + CurlSOAPHelper soapHelper; + soapHelper.SOAP_initate("ecs", _getECSUrl(), "AccountGetETickets", "2.0"); + + soapHelper.SOAP_addRequestField("DeviceId", fmt::format("{}", authInfo.getDeviceIdWithPlatform())); + soapHelper.SOAP_addRequestField("Region", NCrypto::GetRegionAsString(authInfo.region)); + soapHelper.SOAP_addRequestField("Country", authInfo.country); + + if (!authInfo.IASToken.accountId.empty()) + soapHelper.SOAP_addRequestField("AccountId", authInfo.IASToken.accountId); + if (!authInfo.IASToken.deviceToken.empty()) + soapHelper.SOAP_addRequestField("DeviceToken", _getDeviceTokenWT(authInfo.IASToken.deviceToken)); + + soapHelper.SOAP_addRequestField("DeviceCert", authInfo.deviceCertBase64); + + soapHelper.SOAP_addRequestField("TicketId", fmt::format("{}", ticketId)); + + if (!soapHelper.submitRequest()) + { + result.apiError = NAPI_RESULT::FAILED; + return result; + } + + // parse result + pugi::xml_document doc; + pugi::xml_node responseNode; + if (!_parseResponseInit(soapHelper, "AccountGetETicketsResponse", responseNode, result, doc, responseNode)) + return result; + + const char* eTicketsStr = responseNode.child_value("ETickets"); + result.eTickets = NCrypto::base64Decode(eTicketsStr); + if (result.eTickets.empty()) + { + cemuLog_log(LogType::Force, "AccountGetETickets: Invalid eTickets data in response"); + result.apiError = NAPI_RESULT::DATA_ERROR; + return result; + } + + for (pugi::xml_node certNode : responseNode.children("Certs")) + { + const char* certStringValue = certNode.child_value(); + auto certData = NCrypto::base64Decode(certStringValue); + if (certData.empty()) + { + cemuLog_log(LogType::Force, "AccountGetETickets: Invalid cert data in response"); + result.apiError = NAPI_RESULT::DATA_ERROR; + return result; + } + result.certs.emplace_back(NCrypto::base64Decode(certStringValue)); + } + + /* + example response: + <ETickets>BASE64_TIK?</ETickets> + <Certs>BASE64_CERT</Certs> + <Certs>BASE64_CERT</Certs> + */ + + return result; + } + + /* CCS (content server for raw files, does not use SOAP API) */ + + NAPI_CCSGetTMD_Result CCS_GetTMD(AuthInfo& authInfo, uint64 titleId, uint16 titleVersion) + { + NAPI_CCSGetTMD_Result result{}; + CurlRequestHelper req; + req.initate(fmt::format("{}/{:016x}/tmd.{}?deviceId={}&accountId={}", _getCCSUncachedUrl(), titleId, titleVersion, authInfo.getDeviceIdWithPlatform(), authInfo.IASToken.accountId), CurlRequestHelper::SERVER_SSL_CONTEXT::CCS); + req.setTimeout(180); + if (!req.submitRequest(false)) + { + cemuLog_log(LogType::Force, fmt::format("Failed to request TMD for title {0:016X} v{1}", titleId, titleVersion)); + return result; + } + result.tmdData = req.getReceivedData(); + result.isValid = true; + return result; + } + + NAPI_CCSGetTMD_Result CCS_GetTMD(AuthInfo& authInfo, uint64 titleId) + { + NAPI_CCSGetTMD_Result result{}; + CurlRequestHelper req; + req.initate(fmt::format("{}/{:016x}/tmd?deviceId={}&accountId={}", _getCCSUncachedUrl(), titleId, authInfo.getDeviceIdWithPlatform(), authInfo.IASToken.accountId), CurlRequestHelper::SERVER_SSL_CONTEXT::CCS); + req.setTimeout(180); + if (!req.submitRequest(false)) + { + cemuLog_log(LogType::Force, fmt::format("Failed to request TMD for title {0:016X}", titleId)); + return result; + } + result.tmdData = req.getReceivedData(); + result.isValid = true; + return result; + } + + NAPI_CCSGetETicket_Result CCS_GetCETK(AuthInfo& authInfo, uint64 titleId, uint16 titleVersion) + { + NAPI_CCSGetETicket_Result result{}; + CurlRequestHelper req; + req.initate(fmt::format("{}/{:016x}/cetk", _getCCSUncachedUrl(), titleId), CurlRequestHelper::SERVER_SSL_CONTEXT::CCS); + req.setTimeout(180); + if (!req.submitRequest(false)) + { + cemuLog_log(LogType::Force, fmt::format("Failed to request eTicket for title {0:016X} v{1}", titleId, titleVersion)); + return result; + } + result.cetkData = req.getReceivedData(); + result.isValid = true; + return result; + } + + bool CCS_GetContentFile(uint64 titleId, uint32 contentId, bool(*cbWriteCallback)(void* userData, const void* ptr, size_t len, bool isLast), void* userData) + { + CurlRequestHelper req; + req.initate(fmt::format("{}/{:016x}/{:08x}", _getCCSUrl(), titleId, contentId), CurlRequestHelper::SERVER_SSL_CONTEXT::CCS); + req.setWriteCallback(cbWriteCallback, userData); + req.setTimeout(0); + if (!req.submitRequest(false)) + { + cemuLog_log(LogType::Force, fmt::format("Failed to request content file {:08x} for title {:016X}", contentId, titleId)); + return false; + } + return true; + } + + NAPI_CCSGetContentH3_Result CCS_GetContentH3File(uint64 titleId, uint32 contentId) + { + NAPI_CCSGetContentH3_Result result{}; + CurlRequestHelper req; + req.initate(fmt::format("{}/{:016x}/{:08x}.h3", _getCCSUrl(), titleId, contentId), CurlRequestHelper::SERVER_SSL_CONTEXT::CCS); + if (!req.submitRequest(false)) + { + cemuLog_log(LogType::Force, fmt::format("Failed to request content hash file {:08x}.h3 for title {:016X}", contentId, titleId)); + return result; + } + result.tmdData = req.getReceivedData(); + result.isValid = true; + return result; + } + +}; diff --git a/src/Cemu/napi/napi_helper.cpp b/src/Cemu/napi/napi_helper.cpp new file mode 100644 index 00000000..2f96567d --- /dev/null +++ b/src/Cemu/napi/napi_helper.cpp @@ -0,0 +1,393 @@ +#include "Common/precompiled.h" +#include "napi.h" + +#include "curl/curl.h" +#include "Cafe/IOSU/legacy/iosu_crypto.h" + +#include "Cemu/ncrypto/ncrypto.h" +#include "napi_helper.h" +#include "util/highresolutiontimer/HighResolutionTimer.h" + +#include "pugixml.hpp" +#include <charconv> + +#include"openssl/bn.h" +#include"openssl/x509.h" +#include"openssl/ssl.h" + +CURLcode _sslctx_function_NUS(CURL* curl, void* sslctx, void* param) +{ + if (iosuCrypto_addCACertificate(sslctx, 102) == false) + { + forceLog_printf("Invalid CA certificate (102)"); + } + if (iosuCrypto_addCACertificate(sslctx, 0x69) == false) + { + forceLog_printf("Invalid CA certificate (105)"); + } + + if (iosuCrypto_addClientCertificate(sslctx, 3) == false) + { + forceLog_printf("Certificate error"); + } + SSL_CTX_set_mode((SSL_CTX*)sslctx, SSL_MODE_AUTO_RETRY); + SSL_CTX_set_verify_depth((SSL_CTX*)sslctx, 2); + SSL_CTX_set_verify((SSL_CTX*)sslctx, SSL_VERIFY_PEER, nullptr); + return CURLE_OK; +} + +CURLcode _sslctx_function_IDBE(CURL* curl, void* sslctx, void* param) +{ + if (iosuCrypto_addCACertificate(sslctx, 105) == false) + { + forceLog_printf("Invalid CA certificate (105)"); + } + SSL_CTX_set_mode((SSL_CTX*)sslctx, SSL_MODE_AUTO_RETRY); + SSL_CTX_set_verify_depth((SSL_CTX*)sslctx, 2); + + return CURLE_OK; +} + +CURLcode _sslctx_function_SOAP(CURL* curl, void* sslctx, void* param) +{ + if (iosuCrypto_addCACertificate(sslctx, 102) == false) + { + forceLog_printf("Invalid CA certificate (102)"); + forceLog_printf("Certificate error"); + } + if (iosuCrypto_addClientCertificate(sslctx, 1) == false) + { + forceLog_printf("Certificate error"); + } + SSL_CTX_set_mode((SSL_CTX*)sslctx, SSL_MODE_AUTO_RETRY); + SSL_CTX_set_verify_depth((SSL_CTX*)sslctx, 2); + SSL_CTX_set_verify((SSL_CTX*)sslctx, SSL_VERIFY_PEER, nullptr); + return CURLE_OK; +} + +CurlRequestHelper::CurlRequestHelper() +{ + m_curl = curl_easy_init(); + curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, __curlWriteCallback); + curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, this); + + curl_easy_setopt(m_curl, CURLOPT_FOLLOWLOCATION, 1); + curl_easy_setopt(m_curl, CURLOPT_MAXREDIRS, 2); +} + +CurlRequestHelper::~CurlRequestHelper() +{ + curl_easy_cleanup(m_curl); +} + +void CurlRequestHelper::initate(std::string url, SERVER_SSL_CONTEXT sslContext) +{ + // reset parameters + m_headerExtraFields.clear(); + m_postData.clear(); + m_cbWriteCallback = nullptr; + + curl_easy_setopt(m_curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(m_curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_DEFAULT); + curl_easy_setopt(m_curl, CURLOPT_TIMEOUT, 60); + + // SSL + if (sslContext == SERVER_SSL_CONTEXT::ACT || sslContext == SERVER_SSL_CONTEXT::TAGAYA) + { + curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_FUNCTION, _sslctx_function_NUS); + curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_DATA, NULL); + } + else if (sslContext == SERVER_SSL_CONTEXT::IDBE) + { + curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_FUNCTION, _sslctx_function_IDBE); + curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_DATA, NULL); + } + else if (sslContext == SERVER_SSL_CONTEXT::IAS || sslContext == SERVER_SSL_CONTEXT::ECS) + { + curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_FUNCTION, _sslctx_function_SOAP); + curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_DATA, NULL); + } + else if (sslContext == SERVER_SSL_CONTEXT::CCS) + { + curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_FUNCTION, _sslctx_function_SOAP); + curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_DATA, NULL); + } + else + { + cemu_assert(false); + } +} + +void CurlRequestHelper::addHeaderField(const char* fieldName, std::string_view value) +{ + m_headerExtraFields.emplace_back(fieldName, value); +} + +void CurlRequestHelper::addPostField(const char* fieldName, std::string_view value) +{ + if (!m_postData.empty()) + m_postData.emplace_back('&'); + m_postData.insert(m_postData.end(), (uint8*)fieldName, (uint8*)fieldName + strlen(fieldName)); + m_postData.emplace_back('='); + m_postData.insert(m_postData.end(), (uint8*)value.data(), (uint8*)value.data() + value.size()); +} + +void CurlRequestHelper::setWriteCallback(bool(*cbWriteCallback)(void* userData, const void* ptr, size_t len, bool isLast), void* userData) +{ + m_cbWriteCallback = cbWriteCallback; + m_writeCallbackUserData = userData; +} + +void CurlRequestHelper::setTimeout(sint32 time) +{ + curl_easy_setopt(m_curl, CURLOPT_TIMEOUT, time); +} + +size_t CurlRequestHelper::__curlWriteCallback(char* ptr, size_t size, size_t nmemb, void* userdata) +{ + size_t writeByteSize = (size_t)(size * nmemb); + CurlRequestHelper* curlHelper = (CurlRequestHelper*)userdata; + if (curlHelper->m_cbWriteCallback) + { + if (!curlHelper->m_cbWriteCallback(curlHelper->m_writeCallbackUserData, ptr, writeByteSize, false)) + return 0; + return writeByteSize; + } + curlHelper->m_receiveBuffer.insert(curlHelper->m_receiveBuffer.end(), ptr, ptr + writeByteSize); + return writeByteSize; +} + +bool CurlRequestHelper::submitRequest(bool isPost) +{ + // HTTP headers + struct curl_slist* headers = nullptr; + for (auto& itr : m_headerExtraFields) + headers = curl_slist_append(headers, itr.data.c_str()); + curl_easy_setopt(m_curl, CURLOPT_HTTPHEADER, headers); + + // post + if (isPost) + { + curl_easy_setopt(m_curl, CURLOPT_POST, 1); + curl_easy_setopt(m_curl, CURLOPT_POSTFIELDS, m_postData.data()); + curl_easy_setopt(m_curl, CURLOPT_POSTFIELDSIZE, m_postData.size()); + } + else + curl_easy_setopt(m_curl, CURLOPT_POST, 0); + + // submit + int res = curl_easy_perform(m_curl); + if (res != CURLE_OK) + { + cemuLog_force("CURL web request failed with error {}. Retrying...", res); + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + // retry + res = curl_easy_perform(m_curl); + if (res != CURLE_OK) + return false; + } + + // check response code + long httpCode = 0; + curl_easy_getinfo(m_curl, CURLINFO_RESPONSE_CODE, &httpCode); + if (httpCode != 200) + { + cemuLog_log(LogType::Force, "HTTP request received response {} but expected 200", httpCode); + // error status codes (4xx or 5xx range) are always considered a failed request, except for code 400 which is usually returned as a response to failed logins etc. + if (httpCode >= 400 && httpCode <= 599 && httpCode != 400) + return false; + // for other status codes we assume success if the message is non-empty + if(m_receiveBuffer.empty()) + return false; + } + + if (m_cbWriteCallback) + m_cbWriteCallback(m_writeCallbackUserData, nullptr, 0, true); // flush write + + return true; +} + +CurlSOAPHelper::CurlSOAPHelper() +{ + m_curl = curl_easy_init(); + curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, __curlWriteCallback); + curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, this); + + // SSL + curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_FUNCTION, _sslctx_function_SOAP); + curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_DATA, NULL); +} + +CurlSOAPHelper::~CurlSOAPHelper() +{ + curl_easy_cleanup(m_curl); +} + +void CurlSOAPHelper::SOAP_initate(std::string_view serviceType, std::string url, std::string_view requestMethod, std::string_view requestVersion) +{ + curl_easy_setopt(m_curl, CURLOPT_URL, url.c_str()); + m_serviceType = serviceType; + m_requestMethod = requestMethod; + m_requestVersion = requestVersion; + + m_envelopeExtraParam.reserve(512); + m_envelopeExtraParam.clear(); +} + +void CurlSOAPHelper::SOAP_addRequestField(const char* fieldName, std::string_view value) +{ + m_envelopeExtraParam.append(fmt::format("<{}:{}>{}</{}:{}>", m_serviceType, fieldName, value, m_serviceType, fieldName)); +} + +void CurlSOAPHelper::SOAP_generateEnvelope() +{ + m_envelopeStr.reserve(4096); + m_envelopeStr.clear(); + + m_envelopeStr.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); + + m_envelopeStr.append("<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\"\n"); + m_envelopeStr.append(" xmlns:SOAP-ENC=\"http://schemas.xmlsoap.org/soap/encoding/\"\n"); + m_envelopeStr.append(" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"); + m_envelopeStr.append(" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n"); + m_envelopeStr.append(fmt::format(" xmlns:{}=\"urn:{}.wsapi.broadon.com\">\n", m_serviceType, m_serviceType)); + m_envelopeStr.append("<SOAP-ENV:Body>\n"); + m_envelopeStr.append(fmt::format("<{}:{} xsi:type=\"{}:{}RequestType\">\n", m_serviceType, m_requestMethod, m_serviceType, m_requestMethod)); + m_envelopeStr.append(fmt::format("<{}:Version>{}</{}:Version>\n", m_serviceType, m_requestVersion, m_serviceType)); + + // the server echos the message id + static uint64 s_msgHigh = 1 + (uint64)HighResolutionTimer::now().getTick()/7; + uint64 msgId_high = s_msgHigh; // usually this is set to the deviceId + uint64 msgId_low = (uint64)HighResolutionTimer::now().getTick(); // uptime + m_envelopeStr.append(fmt::format("<{}:MessageId>EC-{}-{}</{}:MessageId>", m_serviceType, msgId_high, msgId_low, m_serviceType)); + + m_envelopeStr.append(m_envelopeExtraParam); + + // some fields are specific to services? + // ECS doesnt seem to like RegionId and CountryCode + + // following fields are shared: + // >>> Region, Country, Language + // following fields are present when NUS: + // >>> RegionId (instead of Region), CountryCode (instead of Country) + // following fields are present when CAS or ECS: + // >>> ApplicationId, TIN, SerialNo + // following fields are present when CAS: + // >>> Age + // following fields are present when ECS: + // >>> SessionHandle, ServiceTicket, ServiceId + // following fields for anything that isn't BGS: + // >>> DeviceId (the serial) + + // DeviceId -> All except BGS (deviceId is the console serial) + // DeviceToken -> Everything except BGS and NUS + + // ECS: + //m_envelopeStr.append(fmt::format("<{}:Region>EUR</{}:Region>", serviceType, serviceType)); + //m_envelopeStr.append(fmt::format("<{}:Country>AT</{}:Country>", serviceType, serviceType)); + + + // device token format: + // <ECS:DeviceToken>WT-<md5hash_in_hex></ECS:DeviceToken> + + // unknown fields: + // VirtualDeviceType (shared but optional?) + + + // device cert not needed for ECS:GetAccountStatus ? (it complains if present) + //char deviceCertStr[1024 * 4]; + //iosuCrypto_getDeviceCertificateBase64Encoded(deviceCertStr); + //m_envelopeStr.append(fmt::format("<{}:DeviceCert>{}</{}:DeviceCert>", serviceType, deviceCertStr, serviceType)); + + // only device token needed + // DeviceToken comes from GetRegistrationInfo and is then stored in ec_account_info.exi + + m_envelopeStr.append(fmt::format("</{}:{}>\n", m_serviceType, m_requestMethod)); + + m_envelopeStr.append("</SOAP-ENV:Body>\n"); + m_envelopeStr.append("</SOAP-ENV:Envelope>\n"); + + +} + +sint32 iosuCrypto_getDeviceCertificateBase64Encoded(char* output); + +bool CurlSOAPHelper::submitRequest() +{ + // generate and set envelope + SOAP_generateEnvelope(); + curl_easy_setopt(m_curl, CURLOPT_POST, 1); + curl_easy_setopt(m_curl, CURLOPT_POSTFIELDS, m_envelopeStr.c_str()); + curl_easy_setopt(m_curl, CURLOPT_POSTFIELDSIZE, m_envelopeStr.size()); + // generate and set headers + struct curl_slist* headers = NULL; + headers = curl_slist_append(headers, "Content-Type: text/xml; charset=utf-8"); + headers = curl_slist_append(headers, "Accept-Charset: UTF-8"); + headers = curl_slist_append(headers, fmt::format("SOAPAction: urn:{}.wsapi.broadon.com/{}", m_serviceType, m_requestMethod).c_str()); + headers = curl_slist_append(headers, "Accept: */*"); + headers = curl_slist_append(headers, "User-Agent: EVL NUP 040800 Sep 18 2012 20:20:02"); + + curl_easy_setopt(m_curl, CURLOPT_HTTPHEADER, headers); + + // send request + auto res = curl_easy_perform(m_curl); + return res == CURLE_OK; +} + +/* helper functions */ + +namespace NAPI +{ + bool _findXmlNode(pugi::xml_node& doc, pugi::xml_node& nodeOut, const char* name) + { + for (auto& itr : doc.children()) + { + if (boost::iequals(itr.name(), name)) + { + nodeOut = itr; + return true; + } + if (_findXmlNode(itr, nodeOut, name)) + return true; + } + return false; + } + + bool _parseResponseInit(const CurlSOAPHelper& soapHelper, const char* responseNodeName, pugi::xml_node& node, _NAPI_CommonResultSOAP& result, pugi::xml_document& doc, pugi::xml_node& responseNode) + { + // parse XML response + if (!doc.load_buffer(soapHelper.getReceivedData().data(), soapHelper.getReceivedData().size())) + { + forceLog_printf("Failed to parse GetRegistrationInfo() response"); + result.apiError = NAPI_RESULT::XML_ERROR; + return false; + } + if (!_findXmlNode(doc, node, responseNodeName)) + { + result.apiError = NAPI_RESULT::XML_ERROR; + return false; + } + // parse error code + auto errorCodeStr = node.child_value("ErrorCode"); + if (!errorCodeStr) + { + result.apiError = NAPI_RESULT::XML_ERROR; + return false; + } + int parsedErrorCode = 0; + std::from_chars_result fcr = std::from_chars(errorCodeStr, errorCodeStr + strlen(errorCodeStr), parsedErrorCode); + if (fcr.ec == std::errc::invalid_argument || fcr.ec == std::errc::result_out_of_range) + { + result.apiError = NAPI_RESULT::XML_ERROR; + return false; + } + if (parsedErrorCode != 0) + { + result.serviceError = (EC_ERROR_CODE)parsedErrorCode; + result.apiError = NAPI_RESULT::SERVICE_ERROR; + return false; + } + result.apiError = NAPI_RESULT::SUCCESS; + return true; + } +}; \ No newline at end of file diff --git a/src/Cemu/napi/napi_helper.h b/src/Cemu/napi/napi_helper.h new file mode 100644 index 00000000..00d57e92 --- /dev/null +++ b/src/Cemu/napi/napi_helper.h @@ -0,0 +1,115 @@ +#pragma once +#include "napi.h" +#include "curl/curl.h" +#include "pugixml.hpp" + +typedef void CURL; + +class CurlRequestHelper +{ + struct HeaderExtraField + { + HeaderExtraField(std::string_view name, std::string_view value) + { + data.assign(name); + data.append(": "); + data.append(value); + }; + + std::string data; + }; +public: + enum class SERVER_SSL_CONTEXT + { + ACT, // account.nintendo.net + ECS, // ecs. + IAS, // ias. + CCS, // ccs. + IDBE, // idbe-wup. + TAGAYA, // tagaya.wup.shop.nintendo.net + }; + + CurlRequestHelper(); + ~CurlRequestHelper(); + + CURL* getCURL() + { + return m_curl; + } + + void initate(std::string url, SERVER_SSL_CONTEXT sslContext); + void addHeaderField(const char* fieldName, std::string_view value); + void addPostField(const char* fieldName, std::string_view value); + void setWriteCallback(bool(*cbWriteCallback)(void* userData, const void* ptr, size_t len, bool isLast), void* userData); + void setTimeout(sint32 timeoutSeconds); // maximum duration of the request after connecting. Set to zero to disable limit + + bool submitRequest(bool isPost = false); + + std::vector<uint8>& getReceivedData() + { + return m_receiveBuffer; + } + +private: + static size_t __curlWriteCallback(char* ptr, size_t size, size_t nmemb, void* userdata); + + CURL* m_curl; + std::vector<uint8> m_receiveBuffer; + // input parameters + std::vector<HeaderExtraField> m_headerExtraFields; + std::vector<uint8> m_postData; + // write callback redirect + bool (*m_cbWriteCallback)(void* userData, const void* ptr, size_t len, bool isLast); + void* m_writeCallbackUserData{}; +}; + +class CurlSOAPHelper // todo - make this use CurlRequestHelper +{ +public: + CurlSOAPHelper(); + ~CurlSOAPHelper(); + + CURL* getCURL() + { + return m_curl; + } + + void SOAP_initate(std::string_view serviceType, std::string url, std::string_view requestMethod, std::string_view requestVersion); + + void SOAP_addRequestField(const char* fieldName, std::string_view value); + + bool submitRequest(); + + const std::vector<uint8>& getReceivedData() const + { + return m_receiveBuffer; + } + +private: + void SOAP_generateEnvelope(); + + static size_t __curlWriteCallback(char* ptr, size_t size, size_t nmemb, void* userdata) + { + CurlSOAPHelper* curlHelper = (CurlSOAPHelper*)userdata; + sint32 writeByteSize = (sint32)(size * nmemb); + curlHelper->m_receiveBuffer.insert(curlHelper->m_receiveBuffer.end(), ptr, ptr + writeByteSize); + return writeByteSize; + } + + CURL* m_curl; + std::vector<uint8> m_receiveBuffer; + // input parameters + std::string m_serviceType; + std::string m_requestMethod; + std::string m_requestVersion; + // generated / internal + std::string m_envelopeStr; + std::string m_envelopeExtraParam; +}; + +namespace NAPI +{ + bool _findXmlNode(pugi::xml_node& doc, pugi::xml_node& nodeOut, const char* name); + bool _parseResponseInit(const CurlSOAPHelper& soapHelper, const char* responseNodeName, pugi::xml_node& node, _NAPI_CommonResultSOAP& result, pugi::xml_document& doc, pugi::xml_node& responseNode); + +}; \ No newline at end of file diff --git a/src/Cemu/napi/napi_idbe.cpp b/src/Cemu/napi/napi_idbe.cpp new file mode 100644 index 00000000..034b94db --- /dev/null +++ b/src/Cemu/napi/napi_idbe.cpp @@ -0,0 +1,139 @@ +#include "Common/precompiled.h" +#include "napi.h" +#include "napi_helper.h" + +#include "curl/curl.h" +#include "pugixml.hpp" +#include "Cafe/IOSU/legacy/iosu_crypto.h" + +#include "Cemu/ncrypto/ncrypto.h" +#include "openssl/sha.h" +#include "util/crypto/aes128.h" +#include "util/helpers/StringHelpers.h" + +namespace NAPI +{ + + std::string IDBEIconDataV0::LanguageInfo::GetGameNameUTF8() + { + return StringHelpers::ToUtf8(gameName); + } + + std::string IDBEIconDataV0::LanguageInfo::GetGameLongNameUTF8() + { + return StringHelpers::ToUtf8(gameLongName); + } + + std::string IDBEIconDataV0::LanguageInfo::GetPublisherNameUTF8() + { + return StringHelpers::ToUtf8(publisherName); + } + + void _decryptIDBEAndHash(IDBEIconDataV0* iconData, uint8* hash, uint8 keyIndex) + { + static uint8 idbeAesKeys[4 * 16] = + { + 0x4A,0xB9,0xA4,0x0E,0x14,0x69,0x75,0xA8,0x4B,0xB1,0xB4,0xF3,0xEC,0xEF,0xC4,0x7B, + 0x90,0xA0,0xBB,0x1E,0x0E,0x86,0x4A,0xE8,0x7D,0x13,0xA6,0xA0,0x3D,0x28,0xC9,0xB8, + 0xFF,0xBB,0x57,0xC1,0x4E,0x98,0xEC,0x69,0x75,0xB3,0x84,0xFC,0xF4,0x07,0x86,0xB5, + 0x80,0x92,0x37,0x99,0xB4,0x1F,0x36,0xA6,0xA7,0x5F,0xB8,0xB4,0x8C,0x95,0xF6,0x6F + }; + + static uint8 idbeAesIv[16] = + { + 0xA4,0x69,0x87,0xAE,0x47,0xD8,0x2B,0xB4,0xFA,0x8A,0xBC,0x04,0x50,0x28,0x5F,0xA4 + }; + + const uint8* aesKey = idbeAesKeys + 16 * keyIndex; + + uint8 iv[16]; + memcpy(iv, hash + 16, sizeof(iv)); + AES128_CBC_decrypt(hash, hash, 32, aesKey, idbeAesIv); + AES128_CBC_decrypt((uint8*)iconData, (uint8*)iconData, sizeof(IDBEIconDataV0), aesKey, iv); + } + + std::vector<uint8> IDBE_RequestRawEncrypted(uint64 titleId) + { + CurlRequestHelper req; + req.initate(fmt::format("https://idbe-wup.cdn.nintendo.net/icondata/{0:02X}/{1:016X}.idbe", (uint32)((titleId >> 8) & 0xFF), titleId), CurlRequestHelper::SERVER_SSL_CONTEXT::IDBE); + if (!req.submitRequest(false)) + { + cemuLog_log(LogType::Force, fmt::format("Failed to request IDBE icon for title {0:016X}", titleId)); + return {}; + } + /* + format: + +0x00 uint8 version (0) + +0x01 uint8 keyIndex + +0x02 uint8[32] hashSHA256 + +0x22 uint8[] EncryptedIconData + */ + auto& receivedData = req.getReceivedData(); + return receivedData; + } + + std::optional<IDBEIconDataV0> IDBE_Request(uint64 titleId) + { + if (titleId == 0x000500301001500A || + titleId == 0x000500301001510A || + titleId == 0x000500301001520A) + { + // friend list has no icon, just fail immediately + forceLogDebug_printf("Requesting IDBE for Friend List. Return none instead"); + return std::nullopt; + } + + CurlRequestHelper req; + req.initate(fmt::format("https://idbe-wup.cdn.nintendo.net/icondata/{0:02X}/{1:016X}.idbe", (uint32)((titleId >> 8) & 0xFF), titleId), CurlRequestHelper::SERVER_SSL_CONTEXT::IDBE); + if (!req.submitRequest(false)) + { + cemuLog_log(LogType::Force, fmt::format("Failed to request IDBE icon for title {0:016X}", titleId)); + return std::nullopt; + } + /* + format: + +0x00 uint8 version (0) + +0x01 uint8 keyIndex + +0x02 uint8[32] hashSHA256 + +0x22 uint8[] EncryptedIconData + */ + auto& receivedData = req.getReceivedData(); + if (receivedData.size() < 0x22) + return std::nullopt; + if (receivedData[0] != 0) + { + cemuLog_log(LogType::Force, "IDBE_Request: File has invalid version"); + return std::nullopt; + } + uint8 keyIndex = receivedData[1]; + if (keyIndex >= 4) + { + cemuLog_log(LogType::Force, "IDBE_Request: Key index out of range"); + return std::nullopt; + } + if (receivedData.size() < (0x22 + sizeof(IDBEIconDataV0))) + { + cemuLog_log(LogType::Force, "IDBE_Request: File size does not match"); + return std::nullopt; + } + // extract hash and encrypted icon data + uint8 hash[32]; + std::memcpy(hash, receivedData.data() + 0x2, 32); + IDBEIconDataV0 iconDataV0; + std::memcpy(&iconDataV0, receivedData.data() + 0x22, sizeof(IDBEIconDataV0)); + // decrypt icon data and hash + _decryptIDBEAndHash(&iconDataV0, hash, keyIndex); + // verify hash of decrypted data + uint8 calcHash[32]; + SHA256_CTX shaCtx; + SHA256_Init(&shaCtx); + SHA256_Update(&shaCtx, &iconDataV0, sizeof(IDBEIconDataV0)); + SHA256_Final(calcHash, &shaCtx); + if (std::memcmp(calcHash, hash, 32) != 0) + { + cemuLog_log(LogType::Force, "IDBE_Request: Hash mismatch"); + return std::nullopt; + } + return std::optional(iconDataV0); + } +}; diff --git a/src/Cemu/napi/napi_version.cpp b/src/Cemu/napi/napi_version.cpp new file mode 100644 index 00000000..f1c1b171 --- /dev/null +++ b/src/Cemu/napi/napi_version.cpp @@ -0,0 +1,84 @@ +#include "Common/precompiled.h" +#include "napi.h" +#include "napi_helper.h" + +#include "curl/curl.h" +#include "pugixml.hpp" + +#include "Cemu/ncrypto/ncrypto.h" +#include <charconv> + +namespace NAPI +{ + NAPI_VersionListVersion_Result TAG_GetVersionListVersion(AuthInfo& authInfo) + { + NAPI_VersionListVersion_Result result; + CurlRequestHelper req; + req.initate(fmt::format("https://tagaya.wup.shop.nintendo.net/tagaya/versionlist/{}/{}/latest_version", NCrypto::GetRegionAsString(authInfo.region), authInfo.country), CurlRequestHelper::SERVER_SSL_CONTEXT::TAGAYA); + if (!req.submitRequest(false)) + { + cemuLog_log(LogType::Force, fmt::format("Failed to request version of update list")); + return result; + } + auto& receivedData = req.getReceivedData(); + + pugi::xml_document doc; + if (!doc.load_buffer(receivedData.data(), receivedData.size())) + { + cemuLog_log(LogType::Force, "Failed to parse title list XML"); + return result; + } + + if (!doc.child("version_list_info").child("version") || !doc.child("version_list_info").child("fqdn")) + { + cemuLog_log(LogType::Force, "Title list XML has missing field"); + return result; + } + result.version = atoi(doc.child("version_list_info").child("version").child_value()); + result.fqdnURL = doc.child("version_list_info").child("fqdn").child_value(); + result.isValid = true; + return result; + } + + NAPI_VersionList_Result TAG_GetVersionList(AuthInfo& authInfo, std::string_view fqdnURL, uint32 versionListVersion) + { + NAPI_VersionList_Result result; + CurlRequestHelper req; + req.initate(fmt::format("https://{}/tagaya/versionlist/{}/{}/list/{}.versionlist", fqdnURL, NCrypto::GetRegionAsString(authInfo.region), authInfo.country, versionListVersion), CurlRequestHelper::SERVER_SSL_CONTEXT::TAGAYA); + if (!req.submitRequest(false)) + { + cemuLog_log(LogType::Force, fmt::format("Failed to request update list")); + return result; + } + auto& receivedData = req.getReceivedData(); + + pugi::xml_document doc; + if (!doc.load_buffer(receivedData.data(), receivedData.size())) + { + forceLog_printf("Failed to parse update list XML"); + return result; + } + // example: + // <?xml version="1.0" encoding="UTF-8" standalone="yes"?> + // <version_list format_version="1.0"><version>1615</version> + // <titles> + // <title><id>0005000E10100600</id><version>16</version> + // <id>0005000E10101B00</id><version>16</version> + // ... + + for (pugi::xml_node title : doc.child("version_list").child("titles").children("title")) + { + uint64 titleId = 0; + uint32 titleVersion = 0; + std::string_view str = title.child_value("id"); + if (const auto res = std::from_chars(str.data(), str.data() + str.size(), titleId, 16); res.ec != std::errc()) + continue; + str = title.child_value("version"); + if (const auto res = std::from_chars(str.data(), str.data() + str.size(), titleVersion, 10); res.ec != std::errc()) + continue; + result.titleVersionList.emplace(titleId, titleVersion); + } + result.isValid = true; + return result; + } +}; \ No newline at end of file diff --git a/src/Cemu/ncrypto/ncrypto.cpp b/src/Cemu/ncrypto/ncrypto.cpp new file mode 100644 index 00000000..7181ab66 --- /dev/null +++ b/src/Cemu/ncrypto/ncrypto.cpp @@ -0,0 +1,977 @@ +#include "Common/precompiled.h" +#include "Cemu/ncrypto/ncrypto.h" +#include "util/helpers/helpers.h" + +#include "openssl/bn.h" +#include "openssl/ec.h" +#include "openssl/x509.h" +#include "openssl/ssl.h" +#include "openssl/sha.h" +#include "openssl/ecdsa.h" + +#include "util/crypto/aes128.h" + +void iosuCrypto_getDeviceCertificate(void* certOut, sint32 len); +void iosuCrypto_getDeviceCertPrivateKey(void* privKeyOut, sint32 len); +bool iosuCrypto_getDeviceId(uint32* deviceId); +void iosuCrypto_getDeviceSerialString(char* serialString); + +void iosuCrypto_readOtpData(void* output, sint32 wordIndex, sint32 size); + +void iosuCrypto_readSeepromData(void* output, sint32 wordIndex, sint32 size); + +extern bool hasSeepromMem; // remove later + +namespace NCrypto +{ + std::string base64Encode(const void* inputMem, size_t inputLen) + { + const uint8* input = (const uint8*)inputMem; + + static const char* base64_charset = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + + std::string strBase64; + strBase64.resize((inputLen * 4) / 3 + 16); + + int i = 0; + int j = 0; + unsigned char charArray_3[3]; + unsigned char charArray_4[4]; + sint32 outputLength = 0; + while (inputLen--) + { + charArray_3[i++] = *(input++); + if (i == 3) + { + charArray_4[0] = (charArray_3[0] & 0xfc) >> 2; + charArray_4[1] = ((charArray_3[0] & 0x03) << 4) + ((charArray_3[1] & 0xf0) >> 4); + charArray_4[2] = ((charArray_3[1] & 0x0f) << 2) + ((charArray_3[2] & 0xc0) >> 6); + charArray_4[3] = charArray_3[2] & 0x3f; + for (i = 0; (i < 4); i++) + { + strBase64[outputLength] = base64_charset[charArray_4[i]]; + outputLength++; + } + i = 0; + } + } + if (i) + { + for (j = i; j < 3; j++) + charArray_3[j] = '\0'; + + charArray_4[0] = (charArray_3[0] & 0xfc) >> 2; + charArray_4[1] = ((charArray_3[0] & 0x03) << 4) + ((charArray_3[1] & 0xf0) >> 4); + charArray_4[2] = ((charArray_3[1] & 0x0f) << 2) + ((charArray_3[2] & 0xc0) >> 6); + charArray_4[3] = charArray_3[2] & 0x3f; + + for (j = 0; j < (i + 1); j++) + { + strBase64[outputLength] = base64_charset[charArray_4[j]]; + outputLength++; + } + while (i++ < 3) + { + strBase64[outputLength] = '='; + outputLength++; + } + } + + cemu_assert(outputLength <= strBase64.size()); + strBase64.resize(outputLength); + return strBase64; + } + + std::vector base64Decode(std::string_view inputStr) + { + static constexpr unsigned char kDecodingTable[] = { + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64, + 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64, + 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64 + }; + + size_t in_len = inputStr.size(); + if (in_len <= 3 || (in_len & 3) != 0) + return std::vector(); // invalid length + + std::vector output; + + size_t out_len = in_len / 4 * 3; + if (inputStr[in_len - 1] == '=') out_len--; + if (inputStr[in_len - 2] == '=') out_len--; + + output.resize(out_len); + + for (size_t i = 0, j = 0; i < in_len;) + { + uint32 a = inputStr[i] == '=' ? 0 & i++ : kDecodingTable[static_cast(inputStr[i++])]; + uint32 b = inputStr[i] == '=' ? 0 & i++ : kDecodingTable[static_cast(inputStr[i++])]; + uint32 c = inputStr[i] == '=' ? 0 & i++ : kDecodingTable[static_cast(inputStr[i++])]; + uint32 d = inputStr[i] == '=' ? 0 & i++ : kDecodingTable[static_cast(inputStr[i++])]; + + uint32 triple = (a << 3 * 6) + (b << 2 * 6) + (c << 1 * 6) + (d << 0 * 6); + + if (j < out_len) output[j++] = (triple >> 2 * 8) & 0xFF; + if (j < out_len) output[j++] = (triple >> 1 * 8) & 0xFF; + if (j < out_len) output[j++] = (triple >> 0 * 8) & 0xFF; + } + + return output; + } + + void base64Tests() + { + std::vector random; + for (sint32 i = 0; i < 100; i++) + { + random.resize(0 + i); + for (size_t x = 0; x < random.size(); x++) + random[x] = (uint8)(i * 21 + x * 133); + + std::string b64 = base64Encode(random.data(), random.size()); + std::vector dec = base64Decode(b64); + + cemu_assert(random == dec); + } + } + + /* Hashing */ + + void GenerateHashSHA256(void* data, size_t len, CHash256& hashOut) + { + SHA256_CTX sha256; + SHA256_Init(&sha256); + SHA256_Update(&sha256, data, len); + SHA256_Final(hashOut.b, &sha256); + } + + void GenerateHashSHA1(void* data, size_t len, CHash160& hashOut) + { + SHA_CTX shaCtx; + SHA1_Init(&shaCtx); + SHA1_Update(&shaCtx, data, len); + SHA1_Final(hashOut.b, &shaCtx); + } + + /* Ticket */ + struct ETicketFileHeaderWiiU + { + /* + Ticket version 0: + SHA1 hash + Ticket version 1: + SHA256 hash + has item rights + + */ + + /* +0x000 */ uint32be signatureType; + /* +0x004 */ uint8 sig[0x100]; + /* +0x104 */ uint8 _ukn104[0x180 - 0x104]; + + /* +0x180 */ ECCPubKey publicKey; + /* +0x1BC */ uint8 ticketFormatVersion; + /* +0x1BD */ uint8 _ukn1BD; + /* +0x1BE */ uint8 _ukn1BE; + + /* +0x1BF */ uint8 encryptedTitleKey[16]; + /* +0x1CF */ uint8 _ukn1CF; // probably padding + + /* +0x1D0 */ uint32be ticketIdHigh; + /* +0x1D4 */ uint32be ticketIdLow; + + /* +0x1D8 */ uint32be deviceId; // ticket personalized to this deviceId. Zero if not personalized + + /* +0x1DC */ uint32be titleIdHigh; + /* +0x1E0 */ uint32be titleIdLow; + /* +0x1E4 */ uint16be ukn1E4; + /* +0x1E6 */ uint16be titleVersion; // also used as ticket version (for AOC content)? + + /* +0x1E8 */ uint8 _ukn1E8[0x21C - 0x1E8]; + + /* +0x21C */ uint32be accountId; + + /* V1 extension header starts at +0x2A4 */ + }; + + struct ETicketFileHeaderExtV1 + { + /* starts at +0x2A4 */ + /* +0x000 */ uint16be headerVersion; + /* +0x002 */ uint16be headerSize; + /* +0x004 */ uint32be ukn008; + /* +0x008 */ uint32be sectionTableOffset; + /* +0x00C */ uint16be sectionTableNumEntries; + /* +0x00E */ uint16be sectionTableEntrySize; + /**/ + }; + + struct ETicketFileHeaderExtV1SectionHeader + { + enum + { + SECTION_TYPE_CONTENT_RIGHTS = 3, // content rights + }; + + /* +0x00 */ uint32be sectionOffset; + /* +0x04 */ uint32be entryCount; + /* +0x08 */ uint32be entrySize; + /* +0x0C */ uint32be sectionSize; + /* +0x10 */ uint16be type; + /* +0x12 */ uint16be ukn00; + }; + + static_assert(sizeof(ETicketFileHeaderWiiU) == 0x220); + + struct ETicketV1_ContentRights + { + uint32be baseIndex; // first index for bitmask? + uint8be rightBitmask[0x80]; + + uint32 GetRightsCount() const + { + return sizeof(rightBitmask) * 8; + } + + bool GetRight(uint32 index) const + { + cemu_assert_debug(index < GetRightsCount()); + if (index >= GetRightsCount()) + return false; + return ((rightBitmask[(index/8)]>>(index & 7)) & 1) != 0; + } + }; + + static_assert(sizeof(ETicketV1_ContentRights) == 0x84); + + bool ETicketParser::parse(const uint8* data, size_t size) + { + auto readStruct = [&](uint32 offset, uint32 readSize) -> void* + { + if ((offset + readSize) > size) + return nullptr; + return (void*)((const uint8*)data + offset); + }; + + if (size < sizeof(ETicketFileHeaderWiiU)) + return false; + ETicketFileHeaderWiiU* header = (ETicketFileHeaderWiiU*)readStruct(0, sizeof(ETicketFileHeaderWiiU)); + if (!header) + return false; + m_titleId = MakeU64(header->titleIdHigh, header->titleIdLow); + m_ticketId = MakeU64(header->ticketIdHigh, header->ticketIdLow); + m_ticketFormatVersion = header->ticketFormatVersion; + m_titleVersion = header->titleVersion; + uint32 titleIdHigh = (m_titleId >> 32); + if ((titleIdHigh >> 16) != 0x5) + return false; // title id should start with 0005... (Wii U platform id?) + + m_isPersonalized = header->deviceId != 0; + m_deviceId = header->deviceId; + m_publicKey = header->publicKey; + + std::memcpy(m_encryptedTitleKey, header->encryptedTitleKey, 16); + + cemu_assert_debug(header->ukn1E4 == 0); + + // read V1 extension + if (m_ticketFormatVersion >= 1) + { + if ((titleIdHigh) == 0x0005000c) + { + ETicketFileHeaderExtV1* extHeader = (ETicketFileHeaderExtV1*)readStruct(0x2A4, sizeof(ETicketFileHeaderExtV1)); // (ETicketFileHeaderExtV1*)((uint8*)header + 0x2A4); + if (!extHeader) + return false; + cemu_assert_debug(extHeader->sectionTableEntrySize == 0x14); + for (uint32 i = 0; i < extHeader->sectionTableNumEntries; i++) + { + ETicketFileHeaderExtV1SectionHeader* sectHeader = (ETicketFileHeaderExtV1SectionHeader*)readStruct(0x2A4 + extHeader->sectionTableOffset, sizeof(ETicketFileHeaderExtV1SectionHeader)); + if (!sectHeader) + return false; + if (sectHeader->type == ETicketFileHeaderExtV1SectionHeader::SECTION_TYPE_CONTENT_RIGHTS) + { + if (sectHeader->entrySize != sizeof(ETicketV1_ContentRights)) + { + cemuLog_log(LogType::Force, "ETicket: Failed to parse ticket with invalid rights size"); + return false; + } + cemu_assert_debug(sectHeader->entryCount == 1); + for (uint32 r = 0; r < sectHeader->entryCount; r++) + { + ETicketV1_ContentRights* rights = (ETicketV1_ContentRights*)readStruct(0x2A4 + sectHeader->sectionOffset + r * sectHeader->entrySize, sizeof(ETicketV1_ContentRights)); + cemu_assert_debug(rights->baseIndex == 0); + if (rights->baseIndex > 0x1000) + { + cemuLog_log(LogType::Force, "ETicket: Invalid content rights index ({})", (uint32)rights->baseIndex); + continue; + } + size_t maxRightsCount = rights->baseIndex + rights->GetRightsCount(); + if (maxRightsCount > m_contentRights.size()) + m_contentRights.resize(maxRightsCount); + for (uint32 x = 0; x < rights->GetRightsCount(); x++) + m_contentRights[x + rights->baseIndex] = rights->GetRight(x); + } + } + else + { + cemu_assert_debug(false); + } + } + } + } + return true; + } + + void ETicketParser::GetTitleKey(AesKey& key) + { + // the key is encrypted using the titleId as IV + 8 zero bytes + uint8 iv[16]{}; + *(uint64be*)iv = m_titleId; + + uint8 commonKey[16] = { 0xD7,0xB0,0x04,0x02,0x65,0x9B,0xA2,0xAB,0xD2,0xCB,0x0D,0xB2,0x7F,0xA2,0xB6,0x56 }; + + AES128_CBC_decrypt(key.b, m_encryptedTitleKey, 16, commonKey, iv); + } + + // personalized tickets have an extra layer of encryption for the title key + bool ETicketParser::Depersonalize(uint8* ticketData, size_t ticketSize, uint32 deviceId, const ECCPrivKey& devicePrivKey) + { + ETicketParser ticketParser; + if (!ticketParser.parse(ticketData, ticketSize)) + return false; + if (!ticketParser.IsPersonalized()) + return false; + if (ticketParser.m_deviceId != deviceId) + { + cemuLog_log(LogType::Force, "Personalized ticket does not match deviceId"); + return false; + } + + // decrypt personalized titlekey + EC_KEY* ec_privKey = devicePrivKey.getPrivateKey(); + EC_POINT* ec_publicKey = ticketParser.m_publicKey.getPublicKeyAsPoint(); + + uint8 sharedKey[128]{}; + int sharedKeyLen = ECDH_compute_key(sharedKey, sizeof(sharedKey), ec_publicKey, ec_privKey, nullptr); + cemu_assert(sharedKeyLen > 16); + EC_KEY_free(ec_privKey); + EC_POINT_free(ec_publicKey); + + NCrypto::CHash160 sharedKeySHA1; + GenerateHashSHA1(sharedKey, sharedKeyLen, sharedKeySHA1); + + uint8 aesSharedKey[16]{}; + std::memcpy(aesSharedKey, sharedKeySHA1.b, 16); + + uint8 iv[16]{}; + *(uint64be*)iv = ticketParser.m_ticketId; + + uint8 ticketKey[16]; + AES128_CBC_decrypt(ticketKey, ticketParser.m_encryptedTitleKey, 16, aesSharedKey, iv); + + // store de-personalized key and remove personal data from ticket + ETicketFileHeaderWiiU* header = (ETicketFileHeaderWiiU*)ticketData; + std::memcpy(header->encryptedTitleKey, ticketKey, 16); + header->deviceId = 0; + header->accountId = 0; + + return true; + } + + /* Title meta data */ + + struct TMDFileHeaderWiiU + { + /* +0x000 */ uint32be signatureType; + + /* +0x004 */ uint8be sig[0x100]; + /* +0x104 */ uint8be _padding104[0x140 - 0x104]; + /* +0x140 */ uint8be _ukn140[0x40]; + + /* +0x180 */ uint8be tmdVersion; + /* +0x181 */ uint8be _ukn181; + /* +0x182 */ uint8be _ukn182; + /* +0x183 */ uint8be isVWii; + /* +0x184 */ uint32be iosTitleIdHigh; + /* +0x188 */ uint32be iosTitleIdLow; + /* +0x18C */ uint32be titleIdHigh; + /* +0x190 */ uint32be titleIdLow; + /* +0x194 */ uint32be titleType; + /* +0x198 */ uint16be group; + /* +0x19A */ uint16be _ukn19A; + /* +0x19C */ uint16be region; + /* +0x19E */ uint8be ratings[16]; + /* +0x1AE */ uint8be _ukn1AE[12]; + /* +0x1BA */ uint8be _ipcMask[12]; + /* +0x1C6 */ uint8be _ukn1C6[18]; + /* +0x1D8 */ uint32be accessRightsMask; + /* +0x1DC */ uint16be titleVersion; + /* +0x1DE */ uint16be numContent; + /* +0x1E0 */ uint32be _ukn1E0; + /* +0x1E4 */ uint8 uknHash[32]; // hash of array at 0x204 + + /* +0x204 */ + struct + { + // pointer to cert data and cert hash? + uint16 ukn00; // index? + uint16 ukn02; + uint8 hash[32]; + }ContentInfo[64]; + }; + + static_assert(sizeof(TMDFileHeaderWiiU) == 0x204 + 64*36); + + struct TMDFileContentEntryWiiU + { + /* +0x00 */ uint32be contentId; + /* +0x04 */ uint16be index; + /* +0x06 */ uint16be type; + /* +0x08 */ uint32be sizeHigh; + /* +0x0C */ uint32be sizeLow; + /* +0x10 */ uint8 hashSHA256[32]; // only the first 20 bytes of the hash seem to be stored? + }; + + static_assert(sizeof(TMDFileContentEntryWiiU) == 0x30); + + bool TMDParser::parse(const uint8* data, size_t size) + { + if (size < sizeof(TMDFileHeaderWiiU)) + { + cemuLog_force("TMD size {} below minimum size of {}", size, sizeof(TMDFileHeaderWiiU)); + return false; + } + TMDFileHeaderWiiU* header = (TMDFileHeaderWiiU*)data; + m_titleId = ((uint64)header->titleIdHigh << 32) | ((uint64)header->titleIdLow); + m_titleVersion = header->titleVersion; + size_t expectedSize = sizeof(TMDFileHeaderWiiU) + sizeof(TMDFileContentEntryWiiU) * header->numContent; + if (size < expectedSize) + { + cemuLog_force("TMD size {} below expected size of {}. Content count: {}", size, expectedSize, (uint16)header->numContent); + return false; + } + + // parse content + TMDFileContentEntryWiiU* contentEntry = (TMDFileContentEntryWiiU*)(header + 1); + for (uint32 i = 0; i < header->numContent; i++) + { + ContentEntry c{}; + c.contentId = contentEntry->contentId; + c.index = contentEntry->index; + c.size = MakeU64(contentEntry->sizeHigh, contentEntry->sizeLow); + c.contentFlags = static_cast((uint16)contentEntry->type); + std::memcpy(c.hash32, contentEntry->hashSHA256, sizeof(c.hash32)); + m_content.emplace_back(c); + contentEntry++; + } + + // todo - parse certificates + return true; + } + + /* ECC PrivateKey helper functions */ + + void ECCPrivKey::setPrivateKey(EC_KEY* key) + { + const BIGNUM* bnPrivKey = EC_KEY_get0_private_key(key); + memset(this->keyData, 0, sizeof(this->keyData)); + BN_bn2binpad(bnPrivKey, this->keyData, sizeof(this->keyData)); + } + + EC_KEY* ECCPrivKey::getPrivateKey() const + { + BIGNUM* bn_privKey = BN_new(); + BN_bin2bn(this->keyData, sizeof(this->keyData), bn_privKey); + EC_KEY* ec_privKey = EC_KEY_new_by_curve_name(NID_sect233r1); + EC_KEY_set_private_key(ec_privKey, bn_privKey); + BN_free(bn_privKey); + return ec_privKey; + } + + ECCPrivKey ECCPrivKey::getDeviceCertPrivateKey() + { + ECCPrivKey key{}; + iosuCrypto_getDeviceCertPrivateKey(key.keyData, sizeof(key.keyData)); + return key; + } + + /* ECC PublicKey helper functions */ + + EC_KEY* ECCPubKey::getPublicKey() + { + BIGNUM* bn_x = BN_new(); + BIGNUM* bn_y = BN_new(); + BN_bin2bn(this->x, sizeof(this->x), bn_x); + BN_bin2bn(this->y, sizeof(this->y), bn_y); + + EC_KEY* ec_pubKey = EC_KEY_new_by_curve_name(NID_sect233r1); + int r = EC_KEY_set_public_key_affine_coordinates(ec_pubKey, bn_x, bn_y); + + BN_free(bn_x); + BN_free(bn_y); + + return ec_pubKey; + } + + EC_POINT* ECCPubKey::getPublicKeyAsPoint() + { + BN_CTX* ctx = BN_CTX_new(); + BIGNUM* bn_x = BN_new(); + BIGNUM* bn_y = BN_new(); + BN_bin2bn(this->x, sizeof(this->x), bn_x); + BN_bin2bn(this->y, sizeof(this->y), bn_y); + EC_GROUP* group = EC_GROUP_new_by_curve_name(NID_sect233r1); + EC_POINT* pubkey = EC_POINT_new(group); + EC_POINT_set_affine_coordinates(group, pubkey, bn_x, bn_y, ctx); + EC_GROUP_free(group); + BN_CTX_free(ctx); + BN_free(bn_x); + BN_free(bn_y); + return pubkey; + } + + ECCPubKey ECCPubKey::generateFromPrivateKey(ECCPrivKey& privKey) + { + BIGNUM* bn_privKey = BN_new(); + BN_bin2bn(privKey.keyData, sizeof(privKey.keyData), bn_privKey); + + // gen public key from private key + EC_GROUP* group = EC_GROUP_new_by_curve_name(NID_sect233r1); + EC_POINT* pubkey = EC_POINT_new(group); + EC_POINT_mul(group, pubkey, bn_privKey, NULL, NULL, NULL); + BIGNUM* bn_x = BN_new(); + BIGNUM* bn_y = BN_new(); + EC_POINT_get_affine_coordinates_GF2m(group, pubkey, bn_x, bn_y, NULL); + + // store public key + ECCPubKey genPubKey; + BN_bn2binpad(bn_x, genPubKey.x, sizeof(genPubKey.x)); + BN_bn2binpad(bn_y, genPubKey.y, sizeof(genPubKey.y)); + + // clean up and return + EC_POINT_free(pubkey); + BN_free(bn_y); + BN_free(bn_x); + BN_free(bn_privKey); + + return genPubKey; + } + + /* Signature helper functions */ + + ECDSA_SIG* ECCSig::getSignature() + { + BIGNUM* bn_r = BN_new(); + BIGNUM* bn_s = BN_new(); + BN_bin2bn(this->r, 30, bn_r); + BN_bin2bn(this->s, 30, bn_s); + + ECDSA_SIG* ec_sig = ECDSA_SIG_new(); + ECDSA_SIG_set0(ec_sig, bn_r, bn_s); // ownership of bn_r and bn_s transferred to SIG as well, do not free manually + + return ec_sig; + } + + void ECCSig::setSignature(ECDSA_SIG* sig) + { + const BIGNUM* sig_r = nullptr, * sig_s = nullptr; + ECDSA_SIG_get0(sig, &sig_r, &sig_s); + + sint32 lenR = BN_num_bytes(sig_r); + sint32 lenS = BN_num_bytes(sig_s); + cemu_assert_debug(lenR <= 30); + cemu_assert_debug(lenS <= 30); + + memset(this->r, 0, sizeof(this->r)); + memset(this->s, 0, sizeof(this->s)); + BN_bn2binpad(sig_r, this->r, 30); + BN_bn2binpad(sig_s, this->s, 30); + } + + /* Certificate */ + + bool CertECC::decodeFromBase64(std::string_view input) + { + auto v = base64Decode(input); + if (v.size() != sizeof(CertECC)) + return false; + memcpy(this, v.data(), sizeof(CertECC)); + return true; + } + + std::string CertECC::encodeToBase64() + { + return base64Encode(this, sizeof(CertECC)); + } + + bool CertECC::verifySignatureViaPubKey(ECCPubKey& signerPubKey) + { + uint8 hash[32]; + SHA256_CTX sha256; + SHA256_Init(&sha256); + SHA256_Update(&sha256, this->issuer, 0x100); + SHA256_Final(hash, &sha256); + + EC_KEY* ecPubKey = signerPubKey.getPublicKey(); + ECDSA_SIG* ecSig = this->signature.getSignature(); + + int r = ECDSA_do_verify(hash, sizeof(hash), ecSig, ecPubKey); + + ECDSA_SIG_free(ecSig); + EC_KEY_free(ecPubKey); + + return r == 1; // true if valid signature + } + + void CertECC::sign(ECCPrivKey& signerPrivKey) + { + uint8 hash[32]; + SHA256_CTX sha256; + SHA256_Init(&sha256); + SHA256_Update(&sha256, this->issuer, 0x100); + SHA256_Final(hash, &sha256); + + // generate signature + EC_KEY* ec_privKey = signerPrivKey.getPrivateKey(); + ECDSA_SIG* sig = ECDSA_do_sign(hash, sizeof(hash), ec_privKey); + EC_KEY_free(ec_privKey); + + // store signature + const BIGNUM* bn_r = nullptr, *bn_s = nullptr; + ECDSA_SIG_get0(sig, &bn_r, &bn_s); + BN_bn2binpad(bn_r, this->signature.r, sizeof(this->signature.r)); + BN_bn2binpad(bn_s, this->signature.s, sizeof(this->signature.s)); + + ECDSA_SIG_free(sig); + } + + CertECC CertECC::GetDeviceCertificate() + { + CertECC deviceCert{}; + iosuCrypto_getDeviceCertificate(&deviceCert, sizeof(CertECC)); + return deviceCert; + } + + // generate a new public key + certificate from privateKey. Certificate is signed with the device key + CertECC CertECC::generateCertificate(uint32 signerTitleIdHigh, uint32 signerTitleIdLow, ECCPrivKey& privKeySigner, ECCPrivKey& privKeyIn, ECCPubKey& pubKeyOut) + { + CertECC deviceCert = GetDeviceCertificate(); + + CertECC newCert = deviceCert; + + cemu_assert(newCert.signatureType == CertECC::SIGTYPE::ECC_SHA256); + // update date + newCert.date = 0; + // update issuer + strcat(newCert.issuer, "-"); + strcat(newCert.issuer, newCert.ngName); + + // update subject + memset(newCert.ngName, 0, sizeof(newCert.ngName)); + sprintf(newCert.ngName, "AP%08x%08x", signerTitleIdHigh, signerTitleIdLow); + + // calculate public key from input private key + newCert.publicKey = ECCPubKey::generateFromPrivateKey(privKeyIn); + pubKeyOut = newCert.publicKey; + + // sign certificate + newCert.sign(privKeySigner); + + return newCert; + } + + ECCSig signHash(uint32 signerTitleIdHigh, uint32 signerTitleIdLow, uint8* hash, sint32 hashLen, CertECC& certChainOut) + { + // generate key pair (we only care about the private key) + EC_KEY* ec_keyPair = EC_KEY_new_by_curve_name(NID_sect233r1); + EC_KEY_generate_key(ec_keyPair); + + ECCPrivKey privKey; + privKey.setPrivateKey(ec_keyPair); + + EC_KEY_free(ec_keyPair); + + // get public key and certificate + ECCPubKey pubKey; + ECCPrivKey signerPrivKey = ECCPrivKey::getDeviceCertPrivateKey(); + certChainOut = CertECC::generateCertificate(signerTitleIdHigh, signerTitleIdLow, signerPrivKey, privKey, pubKey); + + // generate signature + cemu_assert_debug(hashLen == 32); + EC_KEY* ec_privKey = privKey.getPrivateKey(); + ECDSA_SIG* sig = ECDSA_do_sign(hash, hashLen, ec_privKey); + EC_KEY_free(ec_privKey); + + // verify + EC_KEY* ec_pubKey = pubKey.getPublicKey(); + bool isValid = ECDSA_do_verify(hash, hashLen, sig, ec_pubKey) == 1; + EC_KEY_free(ec_pubKey); + cemu_assert(isValid); + + // store signature + ECCSig eccSig; + const BIGNUM* bn_r = nullptr, * bn_s = nullptr; + ECDSA_SIG_get0(sig, &bn_r, &bn_s); + BN_bn2binpad(bn_r, eccSig.r, sizeof(eccSig.r)); + BN_bn2binpad(bn_s, eccSig.s, sizeof(eccSig.s)); + + ECDSA_SIG_free(sig); + + return eccSig; + } + + bool verifyHashSignature(uint8* hash, sint32 hashLen, ECCPubKey& certChainPubKey, ECCSig& sig) + { + EC_KEY* ec_pubKey = certChainPubKey.getPublicKey(); + + ECDSA_SIG* ecdsa_sig = sig.getSignature(); + + bool r = ECDSA_do_verify(hash, hashLen, ecdsa_sig, ec_pubKey) == 1; + + EC_KEY_free(ec_pubKey); + ECDSA_SIG_free(ecdsa_sig); + return r; + } + + bool verifyCert(CertECC& cert, NCrypto::ECCPubKey& signerPubKey) + { + CertECC::SIGTYPE sigType = cert.signatureType; + + if (sigType == CertECC::SIGTYPE::ECC_SHA256) + { + // used for Wii U certs + NCrypto::CHash256 hash; + NCrypto::GenerateHashSHA256(cert.issuer, 0x100, hash); + return NCrypto::verifyHashSignature(hash.b, 32, signerPubKey, cert.signature); + } + else if (sigType == CertECC::SIGTYPE::ECC_SHA1) + { + // from Wii era + NCrypto::CHash160 hash; + NCrypto::GenerateHashSHA1(cert.issuer, 0x100, hash); + return NCrypto::verifyHashSignature(hash.b, 20, signerPubKey, cert.signature); + } + else + { + cemu_assert_unimplemented(); + } + return false; + } + + uint32 GetDeviceId() + { + uint32 deviceId; + if (!iosuCrypto_getDeviceId(&deviceId)) + return 0x11223344; + return deviceId; + } + + std::string GetSerial() + { + char serialBuffer[128]{}; + iosuCrypto_getDeviceSerialString(serialBuffer); + return serialBuffer; + } + + bool SEEPROM_IsPresent() + { + return hasSeepromMem; + } + + CafeConsoleRegion SEEPROM_GetRegion() + { + uint8 seepromRegionU32[4] = {}; + iosuCrypto_readSeepromData(seepromRegionU32, 0xA4, 4); + if (seepromRegionU32[3] == 0) + { + cemuLog_log(LogType::Force, "SEEPROM region is invalid (0)"); + } + return (CafeConsoleRegion)seepromRegionU32[3]; + } + + std::string GetRegionAsString(CafeConsoleRegion regionCode) + { + if (regionCode == CafeConsoleRegion::EUR) + return "EUR"; + else if (regionCode == CafeConsoleRegion::USA) + return "USA"; + else if (regionCode == CafeConsoleRegion::JPN) + return "JPN"; + else if (regionCode == CafeConsoleRegion::CHN) + return "CHN"; + else if (regionCode == CafeConsoleRegion::KOR) + return "KOR"; + else if (regionCode == CafeConsoleRegion::TWN) + return "TWN"; + cemuLog_force("Unknown region code 0x{:x}", (uint32)regionCode); + return "UKN"; + } + + const std::unordered_map g_countryTable = { + {1,"JP"}, + {8,"AI"}, + {9,"AG"}, + {10,"AR"}, + {11,"AW"}, + {12,"BS"}, + {13,"BB"}, + {14,"BZ"}, + {15,"BO"}, + {16,"BR"}, + {17,"VG"}, + {18,"CA"}, + {19,"KY"}, + {20,"CL"}, + {21,"CO"}, + {22,"CR"}, + {23,"DM"}, + {24,"DO"}, + {25,"EC"}, + {26,"SV"}, + {27,"GF"}, + {28,"GD"}, + {29,"GP"}, + {30,"GT"}, + {31,"GY"}, + {32,"HT"}, + {33,"HN"}, + {34,"JM"}, + {35,"MQ"}, + {36,"MX"}, + {37,"MS"}, + {38,"AN"}, + {39,"NI"}, + {40,"PA"}, + {41,"PY"}, + {42,"PE"}, + {43,"KN"}, + {44,"LC"}, + {45,"VC"}, + {46,"SR"}, + {47,"TT"}, + {48,"TC"}, + {49,"US"}, + {50,"UY"}, + {51,"VI"}, + {52,"VE"}, + {64,"AL"}, + {65,"AU"}, + {66,"AT"}, + {67,"BE"}, + {68,"BA"}, + {69,"BW"}, + {70,"BG"}, + {71,"HR"}, + {72,"CY"}, + {73,"CZ"}, + {74,"DK"}, + {75,"EE"}, + {76,"FI"}, + {77,"FR"}, + {78,"DE"}, + {79,"GR"}, + {80,"HU"}, + {81,"IS"}, + {82,"IE"}, + {83,"IT"}, + {84,"LV"}, + {85,"LS"}, + {86,"LI"}, + {87,"LT"}, + {88,"LU"}, + {89,"MK"}, + {90,"MT"}, + {91,"ME"}, + {92,"MZ"}, + {93,"NA"}, + {94,"NL"}, + {95,"NZ"}, + {96,"NO"}, + {97,"PL"}, + {98,"PT"}, + {99,"RO"}, + {100,"RU"}, + {101,"RS"}, + {102,"SK"}, + {103,"SI"}, + {104,"ZA"}, + {105,"ES"}, + {106,"SZ"}, + {107,"SE"}, + {108,"CH"}, + {109,"TR"}, + {110,"GB"}, + {111,"ZM"}, + {112,"ZW"}, + {113,"AZ"}, + {114,"MR"}, + {115,"ML"}, + {116,"NE"}, + {117,"TD"}, + {118,"SD"}, + {119,"ER"}, + {120,"DJ"}, + {121,"SO"}, + {122,"AD"}, + {123,"GI"}, + {124,"GG"}, + {125,"IM"}, + {126,"JE"}, + {127,"MC"}, + {128,"TW"}, + {136,"KR"}, + {144,"HK"}, + {145,"MO"}, + {152,"ID"}, + {153,"SG"}, + {154,"TH"}, + {155,"PH"}, + {156,"MY"}, + {160,"CN"}, + {168,"AE"}, + {170,"EG"}, + {171,"OM"}, + {172,"QA"}, + {173,"KW"}, + {174,"SA"}, + {175,"SY"}, + {176,"BH"}, + {177,"JO"}, + {184,"SM"}, + {185,"VA"}, + {186,"BM"}, + {187,"IN"}, + {192,"NG"}, + {193,"AO"}, + {194,"GH"} + }; + + const char* GetCountryAsString(sint32 index) + { + const auto it = g_countryTable.find(index); + if (it == g_countryTable.cend()) + return "NN"; + return it->second; + } + + void unitTests() + { + base64Tests(); + } + +}; diff --git a/src/Cemu/ncrypto/ncrypto.h b/src/Cemu/ncrypto/ncrypto.h new file mode 100644 index 00000000..e44f7678 --- /dev/null +++ b/src/Cemu/ncrypto/ncrypto.h @@ -0,0 +1,211 @@ +#pragma once +#include "Common/betype.h" +#include "config/CemuConfig.h" // for ConsoleRegion + +/* OpenSSL forward declarations */ +typedef struct ec_key_st EC_KEY; +typedef struct ec_point_st EC_POINT; +typedef struct ECDSA_SIG_st ECDSA_SIG; + +namespace NCrypto +{ + /* Base 64 */ + std::string base64Encode(const void* inputMem, size_t inputLen); + std::vector base64Decode(std::string_view inputStr); + + /* key helper struct */ + struct AesKey + { + uint8 b[16]; + }; + + /* ECC Certificate */ + struct ECCPrivKey + { + uint8 keyData[30]; + + void setPrivateKey(EC_KEY* key); + EC_KEY* getPrivateKey() const; + + static ECCPrivKey getDeviceCertPrivateKey(); + }; + + struct ECCPubKey + { + uint8 x[30]; + uint8 y[30]; + + EC_KEY* getPublicKey(); + EC_POINT* getPublicKeyAsPoint(); + + [[nodiscard]] static ECCPubKey generateFromPrivateKey(ECCPrivKey& privKey); + }; + + struct ECCSig + { + uint8 r[30]; + uint8 s[30]; + + ECDSA_SIG* getSignature(); + void setSignature(ECDSA_SIG* sig); + + bool doesMatch(ECCSig& otherSig) const + { + return memcmp(this->r, otherSig.r, sizeof(this->r)) == 0 && memcmp(this->s, otherSig.s, sizeof(this->s)) == 0; + } + + std::string encodeToBase64() + { + return base64Encode(this, 60); + } + }; + + struct CHash256 // SHA256 + { + uint8 b[32]; + }; + + struct CHash160 // SHA1 + { + uint8 b[20]; + }; + + struct CertECC + { + enum class SIGTYPE : uint32 + { + ECC_SHA1 = 0x00010002, // guessed + ECC_SHA256 = 0x00010005 + }; + + /* +0x000 */ betype signatureType; // 01 00 02 00 + /* +0x004 */ ECCSig signature; //uint8 signature[0x3C]; // from OTP 0xA3*4 + /* +0x040 */ uint8 ukn040[0x40]; // seems to be just padding + /* +0x080 */ char issuer[0x40]; // "Root - CA%08x - MS%08x" + /* +0x0C0 */ char ukn0C0[0x4]; // ??? 00 00 00 02 ? + /* +0x0C4 */ char ngName[0x40]; // "NG%08X" + /* +0x104 */ uint32 date; // big endian? (from OTP 0xA2*4) + /* +0x108 */ ECCPubKey publicKey; //uint8 publicKey[0x3C]; + /* +0x144 */ uint8 padding[0x180 - 0x144]; + + bool decodeFromBase64(std::string_view input); + std::string encodeToBase64(); + + bool verifySignatureViaPubKey(ECCPubKey& signerPubKey); + void sign(ECCPrivKey& signerPrivKey); + + static CertECC GetDeviceCertificate(); + static CertECC generateCertificate(uint32 signerTitleIdHigh, uint32 signerTitleIdLow, ECCPrivKey& privKeySigner, ECCPrivKey& privKeyIn, ECCPubKey& pubKeyOut); + }; + + static_assert(sizeof(CertECC) == 0x180); + + + /* ETicket */ + + class ETicketParser // .tik parser + { + public: + bool parse(const uint8* data, size_t size); + static bool Depersonalize(uint8* ticketData, size_t ticketSize, uint32 deviceId, const ECCPrivKey& devicePrivKey); + + uint64 GetTicketId() const + { + return m_ticketId; + } + + uint64 GetTitleId() const + { + return m_titleId; + } + + uint16 GetTicketVersion() const + { + return m_titleVersion; + } + + void GetTitleKey(AesKey& key); + + bool IsPersonalized() + { + return m_isPersonalized; + } + + bool CheckRight(size_t index) + { + if (index >= m_contentRights.size()) + return false; + return m_contentRights[index]; + } + + private: + uint64 m_titleId; + uint16 m_titleVersion; + uint64 m_ticketId; + uint8 m_ticketFormatVersion; + uint8 m_encryptedTitleKey[16]; + // personalized data + bool m_isPersonalized; + uint32 m_deviceId; + ECCPubKey m_publicKey; + // rights + std::vector m_contentRights; + }; + + /* Title meta data */ + + class TMDParser + { + public: + enum class TMDContentFlags : uint16 + { + // 0x0001 -> Is encrypted? + FLAG_SHA1 = 0x2000, // if not set, use SHA256 + FLAG_HASHED_CONTENT = 0x0002, // .app uses hashed format + }; + + struct ContentEntry + { + uint32 contentId; + uint16 index; + TMDContentFlags contentFlags; + uint64 size; + uint8 hash32[32]; + }; + + uint64 getTitleId() const + { + return m_titleId; + } + + uint16 getTitleVersion() const + { + return m_titleVersion; + } + + bool parse(const uint8* data, size_t size); + std::vector& GetContentList() + { + return m_content; + } + + private: + uint64 m_titleId; + uint16 m_titleVersion; + std::vector m_content; + }; + + DEFINE_ENUM_FLAG_OPERATORS(TMDParser::TMDContentFlags); + + void GenerateHashSHA256(void* data, size_t len, CHash256& hashOut); + ECCSig signHash(uint32 signerTitleIdHigh, uint32 signerTitleIdLow, uint8* hash, sint32 hashLen, CertECC& certChainOut); + + uint32 GetDeviceId(); + std::string GetSerial(); + CafeConsoleRegion SEEPROM_GetRegion(); + std::string GetRegionAsString(CafeConsoleRegion regionCode); + const char* GetCountryAsString(sint32 index); // returns NN if index is not valid or known + bool SEEPROM_IsPresent(); + + void unitTests(); +} \ No newline at end of file diff --git a/src/Cemu/nex/nex.cpp b/src/Cemu/nex/nex.cpp new file mode 100644 index 00000000..8779043c --- /dev/null +++ b/src/Cemu/nex/nex.cpp @@ -0,0 +1,765 @@ +#include "prudp.h" +#include "nex.h" +#include "nexThread.h" + +#include "util/crypto/md5.h" + +// for inet_pton: +#if BOOST_OS_WINDOWS > 0 +#include +#else +#include +#endif + +uint32 _currentCallId = 1; + +sint32 nexService_parseResponse(uint8* data, sint32 length, nexServiceResponse_t* response) +{ + if (length < 4) + return 0; + // get length field + uint32 responseLength = *(uint32*)(data + 0x0); + length -= 4; + if (responseLength > (uint32)length) + return 0; + if (responseLength < 6) + return 0; + uint8 protocolId = *(uint8*)(data + 0x04); + bool isRequest = (protocolId & 0x80) != 0; + protocolId &= 0x7F; + if (isRequest) + { +#ifndef PUBLIC_RELEASE + assert_dbg(); // should never reach since we handle requests before this function is called +#endif + } + uint8 success = *(uint8*)(data + 0x5); + if (success == 0) + { + // error + uint32 errorCode; + if (length < 0xA) + { + return 0; + } + errorCode = *(uint32*)(data + 0x6); + response->errorCode = errorCode; + response->callId = *(uint32*)(data + 0xA); + response->isSuccessful = false; + response->protocolId = protocolId; + response->methodId = 0xFFFFFFFF; + return responseLength + 4; + } + else + { + if (responseLength < 0xA) + return 0; + response->errorCode = 0; + response->isSuccessful = true; + response->protocolId = protocolId; + response->callId = *(uint32*)(data + 0x6); + response->methodId = (*(uint32*)(data + 0xA)) & 0x7FFF; + response->data = nexPacketBuffer(data + 0xE, responseLength - (0xE - 4), false); + return responseLength+4; + } + return 0; +} + +sint32 nexService_parseRequest(uint8* data, sint32 length, nexServiceRequest_t* request) +{ + if (length < 4) + return 0; + // get length field + uint32 requestLength = *(uint32*)(data + 0x0) + 4; + if (requestLength > (uint32)length) + return 0; + if (requestLength < 13) + return 0; + uint8 protocolId = *(uint8*)(data + 0x4); + bool isRequest = (protocolId & 0x80) != 0; + protocolId &= 0x7F; + if(isRequest == false) + assert_dbg(); + uint32 callId = *(uint32*)(data + 0x5); + uint32 methodId = *(uint32*)(data + 0x9); + + uint8* dataPtr = (data + 0xD); + sint32 dataLength = (sint32)(requestLength - 0xD); + + request->callId = callId; + request->methodId = methodId; + request->protocolId = protocolId; + + request->data = nexPacketBuffer(dataPtr, dataLength, false); + + return requestLength; +} + +nexService::nexService() +{ + connectionState = STATE_CONNECTING; + conNexService = nullptr; + isAsync = false; + isDestroyed = false; + isSecureService = false; +} + +nexService::nexService(prudpClient* con) : nexService() +{ + if (con->isConnected() == false) + cemu_assert_suspicious(); + this->conNexService = con; + bufferReceive = std::vector(1024 * 4); +} + +nexService::nexService(uint32 ip, uint16 port, const char* accessKey) : nexService() +{ + // unsecured connection + isSecureService = false; + conNexService = new prudpClient(ip, port, accessKey); + bufferReceive = std::vector(1024 * 4); +} + +nexService::~nexService() +{ + // call error handlers for unfinished method calls + for (auto& it : list_activeRequests) + { + nexServiceResponse_t response = { 0 }; + response.isSuccessful = false; + response.errorCode = ERR_TIMEOUT; + response.custom = it.custom; + if (it.nexServiceResponse) + it.nexServiceResponse(this, &response); + else + { + it.cb2(&response); + } + } + if (conNexService) + delete conNexService; +} + +void nexService::destroy() +{ + if (nexThread_isCurrentThread()) + { + delete this; + return; + } + if (this->isAsync) + isDestroyed = true; + else + delete this; +} + +bool nexService::isMarkedForDestruction() +{ + return isDestroyed; +} + +void nexService::callMethod(uint8 protocolId, uint32 methodId, nexPacketBuffer* parameter, void(*nexServiceResponse)(nexService* nex, nexServiceResponse_t* serviceResponse), void* custom, bool callHandlerIfError) +{ + // add to queue + queuedRequest_t queueRequest = { 0 }; + queueRequest.protocolId = protocolId; + queueRequest.methodId = methodId; + queueRequest.parameterData = std::vector(parameter->getDataPtr(), parameter->getDataPtr() + parameter->getWriteIndex()); + queueRequest.nexServiceResponse = nexServiceResponse; + queueRequest.custom = custom; + queueRequest.callHandlerIfError = callHandlerIfError; + mtx_queuedRequests.lock(); + queuedRequests.push_back(queueRequest); + mtx_queuedRequests.unlock(); +} + +void nexService::callMethod(uint8 protocolId, uint32 methodId, nexPacketBuffer* parameter, std::function cb, bool callHandlerIfError) +{ + // add to queue + queuedRequest_t queueRequest = { 0 }; + queueRequest.protocolId = protocolId; + queueRequest.methodId = methodId; + queueRequest.parameterData = std::vector(parameter->getDataPtr(), parameter->getDataPtr() + parameter->getWriteIndex()); + queueRequest.nexServiceResponse = nullptr; + queueRequest.cb2 = cb; + queueRequest.callHandlerIfError = callHandlerIfError; + mtx_queuedRequests.lock(); + queuedRequests.push_back(queueRequest); + mtx_queuedRequests.unlock(); +} + +void nexService::processQueuedRequest(queuedRequest_t* queuedRequest) +{ + uint32 callId = _currentCallId; + _currentCallId++; + // check state of connection + if (conNexService->getConnectionState() != prudpClient::STATE_CONNECTED) + { + nexServiceResponse_t response = { 0 }; + response.isSuccessful = false; + response.errorCode = ERR_NO_CONNECTION; + response.custom = queuedRequest->custom; + if (queuedRequest->nexServiceResponse) + queuedRequest->nexServiceResponse(this, &response); + else + { + queuedRequest->cb2(&response); + } + return; + } + uint8 packetBuffer[1024 * 8]; + *(uint32*)(packetBuffer + 0x00) = 1 + 4 + 4 + (uint32)queuedRequest->parameterData.size(); // size + *(uint8*)(packetBuffer + 0x04) = queuedRequest->protocolId | PROTOCOL_BIT_REQUEST; + *(uint32*)(packetBuffer + 0x05) = callId; + *(uint32*)(packetBuffer + 0x09) = queuedRequest->methodId; + if (queuedRequest->parameterData.size() >= 1024 * 7) + assert_dbg(); + memcpy((packetBuffer + 0x0D), &queuedRequest->parameterData.front(), queuedRequest->parameterData.size()); + sint32 length = 0xD + (sint32)queuedRequest->parameterData.size(); + conNexService->sendDatagram(packetBuffer, length, true); + // remember request + nexActiveRequestInfo_t requestInfo = { 0 }; + requestInfo.callId = callId; + requestInfo.methodId = queuedRequest->methodId; + requestInfo.protocolId = queuedRequest->protocolId; + if (queuedRequest->nexServiceResponse) + { + requestInfo.nexServiceResponse = queuedRequest->nexServiceResponse; + requestInfo.custom = queuedRequest->custom; + } + else + { + requestInfo.cb2 = queuedRequest->cb2; + } + requestInfo.handleError = queuedRequest->callHandlerIfError; + requestInfo.requestTime = prudpGetMSTimestamp(); + list_activeRequests.push_back(requestInfo); +} + +void nexService::update() +{ + if (connectionState == STATE_CONNECTED) + { + updateNexServiceConnection(); + // process any queued requests + mtx_queuedRequests.lock(); + for (auto& request : queuedRequests) + { + processQueuedRequest(&request); + } + queuedRequests.clear(); + mtx_queuedRequests.unlock(); + } + else if (connectionState == STATE_CONNECTING) + { + updateTemporaryConnections(); + } + // handle request timeouts + uint32 currentTimestamp = prudpGetMSTimestamp(); + sint32 idx = 0; + while (idx < list_activeRequests.size()) + { + auto& it = list_activeRequests[idx]; + if ((uint32)(currentTimestamp - it.requestTime) >= 10000) + { + // time out after 10 seconds + nexServiceResponse_t response = { 0 }; + response.isSuccessful = false; + response.errorCode = ERR_TIMEOUT; + response.custom = it.custom; + if (it.nexServiceResponse) + it.nexServiceResponse(this, &response); + else + { + it.cb2(&response); + } + list_activeRequests.erase(list_activeRequests.cbegin()+idx); + continue; + } + idx++; + } +} + +prudpClient* nexService::getPRUDPConnection() +{ + return conNexService; +} + +sint32 nexService::getState() +{ + return connectionState; +} + +void nexService::registerForAsyncProcessing() +{ + if (isAsync) + return; + nexThread_registerService(this); + isAsync = true; +} + +void nexService::updateTemporaryConnections() +{ + // check for connection + conNexService->update(); + if (conNexService->isConnected()) + { + if (connectionState == STATE_CONNECTING) + connectionState = STATE_CONNECTED; + } + if (conNexService->getConnectionState() == prudpClient::STATE_DISCONNECTED) + connectionState = STATE_DISCONNECTED; +} + +// returns true if the packet is a nex RPC request, in case of error false is returned +bool nexIsRequest(uint8* data, sint32 length) +{ + if (length < 5) + return false; + uint8 protocolId = *(uint8*)(data + 0x04); + bool isRequest = (protocolId & 0x80) != 0; + return isRequest; +} + +void nexService::registerProtocolRequestHandler(uint8 protocol, void(*processRequest)(nexServiceRequest_t* request), void* custom) +{ + protocolHandler_t protocolHandler; + protocolHandler.protocol = protocol; + protocolHandler.processRequest = processRequest; + protocolHandler.custom = custom; + list_requestHandlers.push_back(protocolHandler); +} + +void nexService::sendRequestResponse(nexServiceRequest_t* request, uint32 errorCode, uint8* data, sint32 length) +{ + cemu_assert_debug(length == 0); // non-zero length is todo + + uint8 packetBuffer[256]; + nexPacketBuffer response(packetBuffer, sizeof(packetBuffer), true); + + bool isSuccess = (errorCode == 0); + // write placeholder for response length + response.writeU32(0); + // header fields + response.writeU8(request->protocolId&0x7F); + response.writeU8(isSuccess); + if (isSuccess) + { + response.writeU32(request->callId); + response.writeU32(request->methodId); + // write data + // todo + } + else + { + response.writeU32(errorCode); + response.writeU32(request->callId); + } + // update length field + *(uint32*)response.getDataPtr() = response.getWriteIndex()-4; + if(request->nex->conNexService) + request->nex->conNexService->sendDatagram(response.getDataPtr(), response.getWriteIndex(), true); +} + +void nexService::updateNexServiceConnection() +{ + if (conNexService->getConnectionState() == prudpClient::STATE_DISCONNECTED) + { + this->connectionState = STATE_DISCONNECTED; + return; + } + conNexService->update(); + sint32 datagramLen = conNexService->receiveDatagram(bufferReceive); + if (datagramLen > 0) + { + if (nexIsRequest(&bufferReceive[0], datagramLen)) + { + // request + nexServiceRequest_t nexServiceRequest; + sint32 parsedLength = nexService_parseRequest(&bufferReceive[0], datagramLen, &nexServiceRequest); + if (parsedLength > 0) + { + nexServiceRequest.nex = this; + // call request handler + for (auto& it : list_requestHandlers) + { + if (it.protocol == nexServiceRequest.protocolId) + { + nexServiceRequest.custom = it.custom; + it.processRequest(&nexServiceRequest); + break; + } + } + } + cemu_assert_debug(parsedLength == datagramLen); + } + else + { + // response + nexServiceResponse_t nexServiceResponse; + sint32 parsedLength = nexService_parseResponse(&bufferReceive[0], datagramLen, &nexServiceResponse); + if (parsedLength > 0) + { + nexServiceResponse.nex = this; + // call response handler + for (sint32 i = 0; i < list_activeRequests.size(); i++) + { + if (nexServiceResponse.callId == list_activeRequests[i].callId && + nexServiceResponse.protocolId == list_activeRequests[i].protocolId && + (nexServiceResponse.methodId == list_activeRequests[i].methodId || nexServiceResponse.methodId == 0xFFFFFFFF)) + { + nexServiceResponse.custom = list_activeRequests[i].custom; + if (nexServiceResponse.isSuccessful || list_activeRequests[i].handleError) + { + if (list_activeRequests[i].nexServiceResponse) + list_activeRequests[i].nexServiceResponse(this, &nexServiceResponse); + else + { + list_activeRequests[i].cb2(&nexServiceResponse); + } + } + // remove entry + list_activeRequests.erase(list_activeRequests.begin() + i); + break; + } + } + } + cemu_assert_debug(parsedLength == datagramLen); + } + } +} + +bool _extractStationUrlParamValue(const char* urlStr, const char* paramName, char* output, sint32 maxLength) +{ + size_t paramNameLen = strlen(paramName); + const char* optionPtr = strstr(urlStr, paramName); + while (optionPtr) + { + if (optionPtr[paramNameLen] == '=') + { + output[maxLength - 1] = '\0'; + for (sint32 i = 0; i < maxLength - 1; i++) + { + char c = optionPtr[paramNameLen + 1 + i]; + if (c == ';' || c == '\0') + { + output[i] = '\0'; + break; + } + output[i] = c; + } + return true; + } + // next + optionPtr = strstr(optionPtr+1, paramName); + } + return false; +} + +void nexServiceAuthentication_parseStationURL(char* urlStr, stationUrl_t* stationUrl) +{ + // example: + // prudps:/address=34.210.xxx.xxx;port=60181;CID=1;PID=2;sid=1;stream=10;type=2 + + memset(stationUrl, 0, sizeof(stationUrl_t)); + + char optionValue[128]; + if (_extractStationUrlParamValue(urlStr, "address", optionValue, sizeof(optionValue))) + { + inet_pton(AF_INET, optionValue, &stationUrl->ip); + } + if (_extractStationUrlParamValue(urlStr, "port", optionValue, sizeof(optionValue))) + { + stationUrl->port = atoi(optionValue); + } + if (_extractStationUrlParamValue(urlStr, "CID", optionValue, sizeof(optionValue))) + { + stationUrl->cid = atoi(optionValue); + } + if (_extractStationUrlParamValue(urlStr, "PID", optionValue, sizeof(optionValue))) + { + stationUrl->pid = atoi(optionValue); + } + if (_extractStationUrlParamValue(urlStr, "sid", optionValue, sizeof(optionValue))) + { + stationUrl->sid = atoi(optionValue); + } + if (_extractStationUrlParamValue(urlStr, "stream", optionValue, sizeof(optionValue))) + { + stationUrl->stream = atoi(optionValue); + } + if (_extractStationUrlParamValue(urlStr, "type", optionValue, sizeof(optionValue))) + { + stationUrl->type = atoi(optionValue); + } +} + +typedef struct +{ + uint32 userPid; + uint8 kerberosTicket[1024]; + sint32 kerberosTicketSize; + uint8 kerberosTicket2[4096]; + sint32 kerberosTicket2Size; + stationUrl_t server; + // progress info + bool hasError; + bool done; +}authenticationService_t; + +void nexServiceAuthentication_handleResponse_requestTicket(nexService* nex, nexServiceResponse_t* response) +{ + authenticationService_t* authService = (authenticationService_t*)response->custom; + if (response->isSuccessful == false) + { + forceLog_printf("NEX: RPC error while requesting auth ticket with error code 0x%08x", response->errorCode); + authService->hasError = true; + return; + } + uint32 returnValue = response->data.readU32(); + if (returnValue & 0x80000000) + { + forceLog_printf("NEX: Failed to request auth ticket with error code 0x%08x", returnValue); + authService->hasError = true; + } + authService->kerberosTicket2Size = response->data.readBuffer(authService->kerberosTicket2, sizeof(authService->kerberosTicket2)); + if (response->data.hasReadOutOfBounds()) + { + authService->hasError = true; + forceLog_printf("NEX: Out of bounds error while reading auth ticket"); + return; + } + authService->done = true; +} + +void nexServiceAuthentication_handleResponse_login(nexService* nex, nexServiceResponse_t* response) +{ + authenticationService_t* authService = (authenticationService_t*)response->custom; + if (response->isSuccessful == false) + { + authService->hasError = true; + forceLog_printf("NEX: RPC error in login response 0x%08x", response->errorCode); + return; + } + + uint32 returnValue = response->data.readU32(); + if (returnValue & 0x80000000) + { + authService->hasError = true; + forceLog_printf("NEX: Error 0x%08x in login response (returnCode 0x%08x)", response->errorCode, returnValue); + return; + } + + uint32 userPid = response->data.readU32(); + + // kerberos ticket + authService->kerberosTicketSize = response->data.readBuffer(authService->kerberosTicket, sizeof(authService->kerberosTicket)); + // RVConnection data + // server address (regular protocol) + char serverAddress[1024]; + response->data.readString(serverAddress, sizeof(serverAddress)); + nexServiceAuthentication_parseStationURL(serverAddress, &authService->server); + // special protocols + if (response->data.readU32() != 0) + assert_dbg(); // list not empty + char specialAddress[32]; + response->data.readString(specialAddress, sizeof(specialAddress)); + // V1 has extra info here + // server name + char serverName[256]; + response->data.readString(serverName, sizeof(serverName)); + if (response->data.hasReadOutOfBounds()) + { + authService->hasError = true; + forceLog_printf("NEX: Read out of bounds"); + return; + } + // request ticket data + uint8 tempNexBufferArray[1024]; + nexPacketBuffer packetBuffer(tempNexBufferArray, sizeof(tempNexBufferArray), true); + packetBuffer.writeU32(authService->userPid); + packetBuffer.writeU32(authService->server.pid); + nex->callMethod(NEX_PROTOCOL_AUTHENTICATION, 3, &packetBuffer, nexServiceAuthentication_handleResponse_requestTicket, authService); +} + +typedef struct +{ + bool isComplete; + bool isSuccessful; +}nexServiceSecureRegisterExData_t; + +void nexServiceSecure_handleResponse_RegisterEx(nexService* nex, nexServiceResponse_t* response) +{ + nexServiceSecureRegisterExData_t* info = (nexServiceSecureRegisterExData_t*)response->custom; + info->isComplete = true; + uint32 returnCode = response->data.readU32(); + if (response->isSuccessful == false || response->data.hasReadOutOfBounds()) + { + forceLog_printf("NEX: RPC error in secure register"); + info->isSuccessful = false; + info->isComplete = true; + return; + } + if (returnCode & 0x80000000) + { + forceLog_printf("NEX: Secure register failed with error code 0x%08x", returnCode); + info->isSuccessful = false; + info->isComplete = true; + return; + } + // remaining data is skipped + info->isSuccessful = true; + info->isComplete = true; + return; +} + +nexService* nex_secureLogin(authServerInfo_t* authServerInfo, const char* accessKey, const char* nexToken) +{ + prudpClient* prudpSecureSock = new prudpClient(authServerInfo->server.ip, authServerInfo->server.port, accessKey, authServerInfo); + // wait until connected + while (true) + { + prudpSecureSock->update(); + if (prudpSecureSock->isConnected()) + { + break; + } + if (prudpSecureSock->getConnectionState() == prudpClient::STATE_DISCONNECTED) + { + // timeout or disconnected + forceLog_printf("NEX: Secure login connection time-out"); + delete prudpSecureSock; + return nullptr; + } + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + // create nex service layer + nexService* nex = new nexService(prudpSecureSock); + // secureService: RegisterEx + uint8 tempNexBufferArray[4096]; + nexPacketBuffer packetBuffer(tempNexBufferArray, sizeof(tempNexBufferArray), true); + + char clientStationUrl[256]; + sprintf(clientStationUrl, "prudp:/port=%u;natf=0;natm=0;pmp=0;sid=15;type=2;upnp=0", (uint32)nex->getPRUDPConnection()->getSourcePort()); + // station url list + packetBuffer.writeU32(1); + packetBuffer.writeString(clientStationUrl); + // login data + packetBuffer.writeCustomType(nexNintendoLoginData(nexToken)); + + nexServiceSecureRegisterExData_t secureRegisterExData = { 0 }; + nex->callMethod(NEX_PROTOCOL_SECURE, 4, &packetBuffer, nexServiceSecure_handleResponse_RegisterEx, &secureRegisterExData); + while (true) + { + nex->update(); + if (secureRegisterExData.isComplete) + break; + if (nex->getState() == nexService::STATE_DISCONNECTED) + { + forceLog_printf("NEX: Connection error while registering"); + break; + } + } + if (secureRegisterExData.isSuccessful == false) + { + forceLog_printf("NEX: Failed to register to secure server"); + nex->destroy(); + return nullptr; + } + return nex; +} + +nexService* nex_establishSecureConnection(uint32 authServerIp, uint16 authServerPort, const char* accessKey, uint32 pid, const char* nexPassword, const char* nexToken) +{ + nexService* authConnection = new nexService(authServerIp, authServerPort, accessKey); + // wait until connected + while (authConnection->getState() == nexService::STATE_CONNECTING) + { + authConnection->update(); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + if (authConnection->getState() != nexService::STATE_CONNECTED) + { + // error + authConnection->destroy(); + forceLog_printf("NEX: Failed to connect to the NEX server"); + return nullptr; + } + // send auth login request + uint8 tempNexBufferArray[1024]; + nexPacketBuffer packetBuffer(tempNexBufferArray, sizeof(tempNexBufferArray), true); + char pidStr[32]; + sprintf(pidStr, "%u", pid); + packetBuffer.writeString(pidStr); + authenticationService_t nexAuthService = { 0 }; + nexAuthService.userPid = pid; + authConnection->callMethod(NEX_PROTOCOL_AUTHENTICATION, 1, &packetBuffer, nexServiceAuthentication_handleResponse_login, &nexAuthService); + while (true) + { + if (nexAuthService.hasError || nexAuthService.done || authConnection->getState() != nexService::STATE_CONNECTED) + break; + authConnection->update(); + } + if (nexAuthService.hasError || authConnection->getState() != nexService::STATE_CONNECTED) + { + // error + authConnection->destroy(); + forceLog_printf("NEX: Error during authentication"); + return nullptr; + } + // close connection to auth server + authConnection->destroy(); + // calculate kerberos decryption key + uint32 md5LoopCount = 65000 + (pid % 1024); + md5LoopCount--; + uint8 kerberosKey[16]; + MD5_CTX md5Ctx; + MD5_Init(&md5Ctx); + MD5_Update(&md5Ctx, nexPassword, (unsigned long)strlen(nexPassword)); + MD5_Final(kerberosKey, &md5Ctx); + for (uint32 i = 0; i < md5LoopCount; i++) + { + MD5_Init(&md5Ctx); + MD5_Update(&md5Ctx, kerberosKey, 16); + MD5_Final(kerberosKey, &md5Ctx); + } + if (nexAuthService.kerberosTicket2Size < 16) + { + nexAuthService.hasError = true; + forceLog_printf("NEX: Kerberos ticket too short"); + return nullptr; + } + // check hmac of ticket + uint8 hmacTicket[16]; + hmacMD5(kerberosKey, 16, nexAuthService.kerberosTicket2, nexAuthService.kerberosTicket2Size - 16, hmacTicket); + if (memcmp(hmacTicket, nexAuthService.kerberosTicket2 + nexAuthService.kerberosTicket2Size - 16, 16) != 0) + { + nexAuthService.hasError = true; + forceLog_printf("NEX: Kerberos ticket hmac invalid"); + return nullptr; + } + // auth info + std::unique_ptr authServerInfo(new authServerInfo_t); + // decrypt ticket + RC4Ctx_t rc4Ticket; + RC4_initCtx(&rc4Ticket, kerberosKey, 16); + RC4_transform(&rc4Ticket, nexAuthService.kerberosTicket2, nexAuthService.kerberosTicket2Size - 16, nexAuthService.kerberosTicket2); + nexPacketBuffer packetKerberosTicket(nexAuthService.kerberosTicket2, nexAuthService.kerberosTicket2Size - 16, false); + uint8 secureKey[16]; // for friends server it's 16 bytes, all others use 32 bytes + packetKerberosTicket.readData(secureKey, 16); + uint32 ukn = packetKerberosTicket.readU32(); + authServerInfo->secureTicketLength = packetKerberosTicket.readBuffer(authServerInfo->secureTicket, sizeof(authServerInfo->secureTicket)); + + if (packetKerberosTicket.hasReadOutOfBounds()) + { + forceLog_printf("NEX: Parse error"); + return nullptr; + } + + memcpy(authServerInfo->kerberosKey, kerberosKey, 16); + memcpy(authServerInfo->secureKey, secureKey, 16); + memcpy(&authServerInfo->server, &nexAuthService.server, sizeof(stationUrl_t)); + authServerInfo->userPid = pid; + + return nex_secureLogin(authServerInfo.get(), accessKey, nexToken); +} \ No newline at end of file diff --git a/src/Cemu/nex/nex.h b/src/Cemu/nex/nex.h new file mode 100644 index 00000000..6d513725 --- /dev/null +++ b/src/Cemu/nex/nex.h @@ -0,0 +1,119 @@ +#pragma once +#include "nexTypes.h" +#include + +const int NEX_PROTOCOL_AUTHENTICATION = 0xA; +const int NEX_PROTOCOL_SECURE = 0xB; +const int NEX_PROTOCOL_FRIENDS_WIIU = 0x66; + +class nexService; + +typedef struct +{ + nexService* nex; + bool isSuccessful; + uint32 errorCode; + uint32 callId; + uint32 methodId; + uint8 protocolId; + nexPacketBuffer data; + void* custom; +}nexServiceResponse_t; + +typedef struct +{ + nexService* nex; + uint32 callId; + uint32 methodId; + uint8 protocolId; + nexPacketBuffer data; + void* custom; +}nexServiceRequest_t; + +class prudpClient; + +class nexService +{ +private: + typedef struct + { + uint8 protocolId; + uint32 methodId; + uint32 callId; + void(*nexServiceResponse)(nexService* nex, nexServiceResponse_t* serviceResponse); + void* custom; + bool handleError; // if set to true, call nexServiceResponse with errorCode set (else the callback is skipped when the call is not successful) + uint32 requestTime; // timestamp of when the request was sent + // alternative callback handler + std::function cb2; + }nexActiveRequestInfo_t; + + typedef struct + { + uint8 protocolId; + uint32 methodId; + bool callHandlerIfError; + //nexPacketBuffer* parameter; + std::vector parameterData; + void(*nexServiceResponse)(nexService* nex, nexServiceResponse_t* serviceResponse); + void* custom; + // alternative callback handler + std::function cb2; + }queuedRequest_t; + + typedef struct + { + uint8 protocol; + void(*processRequest)(nexServiceRequest_t* request); + void* custom; + }protocolHandler_t; + +public: + static const int STATE_CONNECTING = 0; + static const int STATE_CONNECTED = 1; + static const int STATE_DISCONNECTED = 2; + + static const unsigned int ERR_TIMEOUT = (0xFFFFFFFF); + static const unsigned int ERR_NO_CONNECTION = (0xFFFFFFFE); + + nexService(prudpClient* con); + nexService(uint32 ip, uint16 port, const char* accessKey); + + const uint8 PROTOCOL_BIT_REQUEST = 0x80; + + void callMethod(uint8 protocolId, uint32 methodId, nexPacketBuffer* parameter, void(*nexServiceResponse)(nexService* nex, nexServiceResponse_t* serviceResponse), void* custom, bool callHandlerIfError = false); + void callMethod(uint8 protocolId, uint32 methodId, nexPacketBuffer* parameter, std::function cb, bool callHandlerIfError); + void registerProtocolRequestHandler(uint8 protocol, void(*processRequest)(nexServiceRequest_t* request), void* custom); + void sendRequestResponse(nexServiceRequest_t* request, uint32 errorCode, uint8* data, sint32 length); + void update(); + prudpClient* getPRUDPConnection(); + sint32 getState(); + void registerForAsyncProcessing(); + void destroy(); + bool isMarkedForDestruction(); + +private: + nexService(); + ~nexService(); + void updateTemporaryConnections(); + void updateNexServiceConnection(); + void processQueuedRequest(queuedRequest_t* queuedRequest); +private: + //bool serviceIsConnected; + uint8 connectionState; + prudpClient* conNexService; + bool isAsync; + bool isDestroyed; // if set, delete object asynchronously + std::vector list_activeRequests; + // protocol request handlers + std::vector list_requestHandlers; + // packet buffer + std::vector bufferReceive; + // auth + bool isSecureService; + // request queue + std::mutex mtx_queuedRequests; + std::vector queuedRequests; +}; + +nexService* nex_establishSecureConnection(uint32 authServerIp, uint16 authServerPort, const char* accessKey, uint32 pid, const char* nexPassword, const char* nexToken); \ No newline at end of file diff --git a/src/Cemu/nex/nexFriends.cpp b/src/Cemu/nex/nexFriends.cpp new file mode 100644 index 00000000..6d902177 --- /dev/null +++ b/src/Cemu/nex/nexFriends.cpp @@ -0,0 +1,993 @@ +#include "prudp.h" +#include "nex.h" +#include "nexFriends.h" + +static const int NOTIFICATION_SRV_FRIEND_OFFLINE = 0x0A; // the opposite event (friend online) is notified via _PRESENCE_CHANGE +static const int NOTIFICATION_SRV_FRIEND_PRESENCE_CHANGE = 0x18; + +static const int NOTIFICATION_SRV_FRIEND_REMOVED = 0x1A; // also used to indicate that an incoming friend request was canceled +static const int NOTIFICATION_SRV_FRIEND_REQUEST_INCOMING = 0x1B; // someone sent a friend request to us +static const int NOTIFICATION_SRV_FRIEND_ADDED = 0x1E; // not sent when friend request is accepted locally + +void NexFriends::processServerNotification_friendOffline(uint32 pid) +{ + std::unique_lock listLock(mtx_lists); + for (auto& it : list_friends) + { + if (it.nnaInfo.principalInfo.principalId == pid) + { + it.presence.isOnline = 0; + generateNotification(NOTIFICATION_TYPE_FRIEND_LOGOFF, pid); + return; + } + } +} + +void NexFriends::processServerNotification_presenceChange(uint32 pid, nexPresenceV2& presence) +{ + std::unique_lock listLock(mtx_lists); + for (auto& it : list_friends) + { + if (it.nnaInfo.principalInfo.principalId == pid) + { + bool isOnlineChange = (presence.isOnline != it.presence.isOnline); + it.presence = presence; + if (isOnlineChange) + generateNotification((presence.isOnline!=0)?NOTIFICATION_TYPE_FRIEND_LOGIN:NOTIFICATION_TYPE_FRIEND_LOGOFF, pid); + generateNotification(NOTIFICATION_TYPE_FRIEND_PRESENCE_CHANGE, pid); + return; + } + } +} + +void NexFriends::processServerNotification_removeFriendOrFriendRequest(uint32 pid) +{ + std::unique_lock listLock(mtx_lists); + for (auto it = list_friends.begin(); it != list_friends.end();) + { + if (it->nnaInfo.principalInfo.principalId == pid) + { + list_friends.erase(it); + generateNotification(NOTIFICATION_TYPE_REMOVED_FRIEND, pid); + return; + } + it++; + } + for (auto it = list_friendReqIncoming.begin(); it != list_friendReqIncoming.end();) + { + if (it->principalInfo.principalId == pid) + { + list_friendReqIncoming.erase(it); + generateNotification(NOTIFICATION_TYPE_REMOVED_INCOMING_REQUEST, pid); + return; + } + it++; + } +} + +void NexFriends::processServerNotification_incomingFriendRequest(uint32 pid, nexFriendRequest& friendRequest) +{ + std::unique_lock listLock(mtx_lists); + // check for duplicate + for (auto& it : list_friendReqIncoming) + { + if (it.principalInfo.principalId == pid) + return; + } + // add friend request and send notification + list_friendReqIncoming.push_back(friendRequest); + generateNotification(NOTIFICATION_TYPE_ADDED_INCOMING_REQUEST, pid); +} + +void NexFriends::processServerNotification_addedFriend(uint32 pid, nexFriend& frd) +{ + std::unique_lock listLock(mtx_lists); + // remove any related incoming friend request + for (auto it = list_friendReqIncoming.begin(); it != list_friendReqIncoming.end();) + { + if (it->principalInfo.principalId == pid) + { + list_friendReqIncoming.erase(it); + generateNotification(NOTIFICATION_TYPE_REMOVED_INCOMING_REQUEST, pid); + break; + } + it++; + } + // remove any related outgoing friend request + for (auto it = list_friendReqOutgoing.begin(); it != list_friendReqOutgoing.end();) + { + if (it->principalInfo.principalId == pid) + { + list_friendReqOutgoing.erase(it); + generateNotification(NOTIFICATION_TYPE_REMOVED_OUTGOING_REQUEST, pid); + break; + } + it++; + } + // check for duplicate + for (auto& it : list_friendReqIncoming) + { + if (it.principalInfo.principalId == pid) + return; + } + // add friend + list_friends.push_back(frd); + generateNotification(NOTIFICATION_TYPE_ADDED_FRIEND, pid); +} + +void NexFriends::processServerNotification(uint32 notificationType, uint32 pid, nexPacketBuffer* notificationData) +{ + nexNotificationEventGeneral notificationGeneral; + if (notificationType == NOTIFICATION_SRV_FRIEND_PRESENCE_CHANGE) + { + nexPresenceV2 presence; + if (notificationData->readPlaceholderType(presence)) + { + this->processServerNotification_presenceChange(pid, presence); + } + } + else if (notificationType == 0x21) + { + // change comment text + if (notificationData->readPlaceholderType(notificationGeneral)) + { + // todo + } + } + else if (notificationType == NOTIFICATION_SRV_FRIEND_OFFLINE) + { + // friend now offline + if (notificationData->readPlaceholderType(notificationGeneral)) + { + this->processServerNotification_friendOffline(pid); + } + } + else if (notificationType == NOTIFICATION_SRV_FRIEND_REMOVED) + { + // friend removed or friend-request removed + if (notificationData->readPlaceholderType(notificationGeneral)) + { + this->processServerNotification_removeFriendOrFriendRequest(pid); + } + } + else if (notificationType == NOTIFICATION_SRV_FRIEND_REQUEST_INCOMING) + { + nexFriendRequest friendRequest; + if (notificationData->readPlaceholderType(friendRequest)) + { + this->processServerNotification_incomingFriendRequest(pid, friendRequest); + } + } + else if (notificationType == NOTIFICATION_SRV_FRIEND_ADDED) + { + nexFriend frd; + if (notificationData->readPlaceholderType(frd)) + { + this->processServerNotification_addedFriend(pid, frd); + } + } + else if (true) + { + forceLogDebug_printf("Unsupported friend server notification type 0x%02x", notificationType); + } +} + +void nexFriends_protocolNotification_processRequest(nexServiceRequest_t* request) +{ + NexFriends* nexFriends = (NexFriends*)request->custom; + if (request->methodId == 0x1 || request->methodId == 0x2) + { + // notification + uint32 notificationType = request->data.readU32(); + uint32 pid = request->data.readU32(); + if (request->data.hasReadOutOfBounds()) + { + request->nex->sendRequestResponse(request, 0, nullptr, 0); + return; + } + forceLogDebug_printf("NN_NOTIFICATION methodId %d type %02x pid %08x", request->methodId, notificationType, pid); + nexFriends->processServerNotification(notificationType, pid, &request->data); + request->nex->sendRequestResponse(request, 0, nullptr, 0); + return; + } + // unknown method + request->nex->sendRequestResponse(request, 0x80000000, nullptr, 0); +} + +NexFriends::NexFriends(uint32 authServerIp, uint16 authServerPort, const char* accessKey, uint32 pid, const char* nexPassword, const char* nexToken, const char* nnid, uint8* miiData, const wchar_t* miiNickname, uint8 countryCode, nexPresenceV2& myPresence) + : nexCon(nullptr) +{ + memcpy(this->miiData, miiData, FFL_SIZE); + strcpy(this->nnid, nnid); + this->pid = pid; + this->countryCode = countryCode; + this->myPresence = myPresence; + this->miiNickname = boost::nowide::narrow(miiNickname); + cemu_assert_debug(this->miiNickname.size() <= 96-1); + auth.serverIp = authServerIp; + auth.port = authServerPort; + auth.accessKey = std::string(accessKey); + auth.nexPassword = std::string(nexPassword); + auth.nexToken = std::string(nexToken); + // login related variables + this->lastLoginAttemptTime = prudpGetMSTimestamp() - 1000*60*60; + this->isCurrentlyConnected = false; + this->hasData = false; + this->loginInProcess = false; + this->numFailedLogins = 0; + this->numSuccessfulLogins = 0; +} + +NexFriends::~NexFriends() +{ + nexCon->destroy(); +} + +void NexFriends::doAsyncLogin() +{ + nexCon = nex_establishSecureConnection(auth.serverIp, auth.port, auth.accessKey.c_str(), pid, auth.nexPassword.c_str(), auth.nexToken.c_str()); + if (nexCon == nullptr) + { + this->numFailedLogins++; + this->loginInProcess = false; + return; + } + + nexCon->registerProtocolRequestHandler(0x64, nexFriends_protocolNotification_processRequest, this); + nexCon->registerForAsyncProcessing(); + this->isCurrentlyConnected = true; + this->firstInformationRequest = true; + this->loginInProcess = false; + this->numSuccessfulLogins++; + requestGetAllInformation(); +} + +void NexFriends::initiateLogin() +{ + if (this->isCurrentlyConnected) + return; + if (this->loginInProcess) + return; + std::unique_lock loginLock(mtx_login); + this->loginInProcess = true; + // reset all data + std::unique_lock listLock(mtx_lists); + list_friends.resize(0); + list_friendReqIncoming.resize(0); + list_friendReqOutgoing.resize(0); + previousState.list_friends.resize(0); + previousState.list_friendReqIncoming.resize(0); + previousState.list_friendReqOutgoing.resize(0); + this->hasData = false; + // start login attempt + forceLogDebug_printf("Attempt to log into friend server..."); + std::thread t(&NexFriends::doAsyncLogin, this); + t.detach(); +} + +void NexFriends::handleResponse_getAllInformation(nexServiceResponse_t* response, NexFriends* nexFriends, std::function cb) +{ + if (response->isSuccessful == false) + { + if (cb) + cb(ERR_RPC_FAILED); + return; + } + NexFriends* session = (NexFriends*)nexFriends; + + nexPrincipalPreference preference(&response->data); + nexComment comment(&response->data); + if (response->data.hasReadOutOfBounds()) + return; + // acquire lock on lists + std::unique_lock listLock(session->mtx_lists); + // remember current state of lists for change tracking + session->previousState.list_friends = session->list_friends; + session->previousState.list_friendReqIncoming = session->list_friendReqIncoming; + session->previousState.list_friendReqOutgoing = session->list_friendReqOutgoing; + // parse response and update lists + // friends + uint32 friendCount = response->data.readU32(); + session->list_friends.resize(friendCount); + for (uint32 i = 0; i < friendCount; i++) + { + session->list_friends[i].readData(&response->data); + } + // friend requests (outgoing) + uint32 friendRequestsOutCount = response->data.readU32(); + if (response->data.hasReadOutOfBounds()) + { + return; + } + session->list_friendReqOutgoing.resize(friendRequestsOutCount); + for (uint32 i = 0; i < friendRequestsOutCount; i++) + { + session->list_friendReqOutgoing[i].readData(&response->data); + } + // friend requests (incoming) + uint32 friendRequestsInCount = response->data.readU32(); + if (response->data.hasReadOutOfBounds()) + return; + session->list_friendReqIncoming.resize(friendRequestsInCount); + for (uint32 i = 0; i < friendRequestsInCount; i++) + { + session->list_friendReqIncoming[i].readData(&response->data); + } + if (response->data.hasReadOutOfBounds()) + return; + // blacklist + uint32 blacklistCount = response->data.readU32(); + for (uint32 i = 0; i < blacklistCount; i++) + { + nexBlacklisted blacklisted(&response->data); + } + // ukn + uint8 uknSetting = response->data.readU8(); // ? (usually zero) + // persistent notifications + uint32 perstNotificationCount = response->data.readU32(); + for (uint32 i = 0; i < perstNotificationCount; i++) + { + nexPersistentNotification notification(&response->data); + //printf("--- Notification ---\n"); + //printf("ID %016llx pid1 %08x pid2 %08x type %08x\n", notification.messageId, notification.pid1, notification.pid2, notification.type); + //printf("Msg %s\n", notification.msg.c_str()); + } + uint8 isPreferenceInvalid = response->data.readU8(); // if not zero, preferences must be setup + if (isPreferenceInvalid) + { + forceLog_printf("NEX: First time login into friend account, setting up default preferences"); + session->updatePreferences(nexPrincipalPreference(1, 1, 0)); + } + + if (session->firstInformationRequest == false) + session->trackNotifications(); + else + { + // first request after login -> send online notification + session->generateNotification(NOTIFICATION_TYPE_ONLINE, session->pid); + } + + session->firstInformationRequest = false; + + session->hasData = true; + + if (cb) + cb(ERR_NONE); +} + +bool NexFriends::requestGetAllInformation() +{ + uint8 tempNexBufferArray[1024]; + nexPacketBuffer packetBuffer(tempNexBufferArray, sizeof(tempNexBufferArray), true); + nexNNAInfo(this->countryCode, 0, nexPrincipalBasicInfo(pid, nnid, nexMiiV2(miiNickname.c_str(), miiData))).writeData(&packetBuffer); + myPresence.writeData(&packetBuffer); + packetBuffer.writeU64(0x1f18420000); // birthday (set to 1990-1-1) + nexCon->callMethod(NEX_PROTOCOL_FRIENDS_WIIU, 1, &packetBuffer, std::bind(handleResponse_getAllInformation, std::placeholders::_1, this, nullptr), true); + return true; +} + +bool NexFriends::requestGetAllInformation(std::function cb) +{ + uint8 tempNexBufferArray[1024]; + nexPacketBuffer packetBuffer(tempNexBufferArray, sizeof(tempNexBufferArray), true); + nexNNAInfo(this->countryCode, 0, nexPrincipalBasicInfo(pid, nnid, nexMiiV2(miiNickname.c_str(), miiData))).writeData(&packetBuffer); + nexPresenceV2(0).writeData(&packetBuffer); + packetBuffer.writeU64(0x1f18420000); // birthday (set to 1990-1-1) + nexCon->callMethod(NEX_PROTOCOL_FRIENDS_WIIU, 1, &packetBuffer, std::bind(handleResponse_getAllInformation, std::placeholders::_1, this, cb), true); + return true; +} + +void NexFriends::handleResponse_updatePreferences(nexServiceResponse_t* response, NexFriends* nexFriends, std::function cb) +{ + // todo +} + +bool NexFriends::updatePreferences(const nexPrincipalPreference& newPreferences) +{ + uint8 tempNexBufferArray[1024]; + nexPacketBuffer packetBuffer(tempNexBufferArray, sizeof(tempNexBufferArray), true); + newPreferences.writeData(&packetBuffer); + nexCon->callMethod(NEX_PROTOCOL_FRIENDS_WIIU, 16, &packetBuffer, std::bind(handleResponse_updatePreferences, std::placeholders::_1, this, nullptr), true); + return true; +} + +bool NexFriends::addProvisionalFriendByPidGuessed(uint32 principalId) +{ + uint8 tempNexBufferArray[512]; + nexPacketBuffer packetBuffer(tempNexBufferArray, sizeof(tempNexBufferArray), true); + packetBuffer.writeU32(principalId); + cemu_assert_unimplemented(); + //nexCon->callMethod(NEX_PROTOCOL_FRIENDS_WIIU, 2, &packetBuffer, handleResponse_test, this); + return true; +} + +bool NexFriends::isOnline() +{ + return isCurrentlyConnected && hasData; +} + +void NexFriends::handleResponse_acceptFriendRequest(nexService* nex, nexServiceResponse_t* response) +{ + NexFriends* session = (NexFriends*)response->custom; + // returns new state of FriendRequest + FriendInfo (if friend request holds no valid data, it means the friend request was successfully accepted?) +} + +void NexFriends::setNotificationHandler(void(*notificationHandler)(NOTIFICATION_TYPE notificationType, uint32 pid)) +{ + this->notificationHandler = notificationHandler; +} + +void NexFriends::generateNotification(NOTIFICATION_TYPE notificationType, uint32 pid) +{ + if (this->notificationHandler == nullptr) + return; + this->notificationHandler(notificationType, pid); +} + +// compare previous and current state of friend(-request) lists and generate change-notifications +void NexFriends::trackNotifications() +{ + // search for changed and added friend entries + for (auto& frNew : list_friends) + { + bool entryFound = false; + for (auto& frPrevious : previousState.list_friends) + { + if (frNew.nnaInfo.principalInfo.principalId == frPrevious.nnaInfo.principalInfo.principalId) + { + // todo - scan for changes + entryFound = true; + break; + } + } + if (entryFound == false) + { + // friend added + generateNotification(NOTIFICATION_TYPE_ADDED_FRIEND, frNew.nnaInfo.principalInfo.principalId); + } + } + // search for removed friends + for (auto& frPrevious : previousState.list_friends) + { + bool entryFound = false; + for (auto& frNew : list_friends) + { + if (frNew.nnaInfo.principalInfo.principalId == frPrevious.nnaInfo.principalInfo.principalId) + { + entryFound = true; + break; + } + } + if (entryFound == false) + { + // friend removed + generateNotification(NOTIFICATION_TYPE_REMOVED_FRIEND, frPrevious.nnaInfo.principalInfo.principalId); + } + } + // search for added friend requests (incoming) + for (auto& frqNew : list_friendReqIncoming) + { + bool entryFound = false; + for (auto& frqPrevious : previousState.list_friendReqIncoming) + { + if (frqNew.principalInfo.principalId == frqPrevious.principalInfo.principalId) + { + entryFound = true; + break; + } + } + if (entryFound == false) + { + // incoming friend request added + generateNotification(NOTIFICATION_TYPE_ADDED_INCOMING_REQUEST, frqNew.principalInfo.principalId); + } + } + // search for removed friend requests (incoming) + for (auto& frqPrevious : previousState.list_friendReqIncoming) + { + bool entryFound = false; + for (auto& frqNew : list_friendReqIncoming) + { + if (frqNew.principalInfo.principalId == frqPrevious.principalInfo.principalId) + { + entryFound = true; + break; + } + } + if (entryFound == false) + { + // incoming friend request removed + generateNotification(NOTIFICATION_TYPE_REMOVED_INCOMING_REQUEST, frqPrevious.principalInfo.principalId); + } + } + // search for added friend requests (outgoing) + for (auto& frqNew : list_friendReqOutgoing) + { + bool entryFound = false; + for (auto& frqPrevious : previousState.list_friendReqOutgoing) + { + if (frqNew.principalInfo.principalId == frqPrevious.principalInfo.principalId) + { + entryFound = true; + break; + } + } + if (entryFound == false) + { + // outgoing friend request added + generateNotification(NOTIFICATION_TYPE_ADDED_OUTGOING_REQUEST, frqNew.principalInfo.principalId); + } + } + // search for removed friend requests (outgoing) + for (auto& frqPrevious : previousState.list_friendReqOutgoing) + { + bool entryFound = false; + for (auto& frqNew : list_friendReqOutgoing) + { + if (frqNew.principalInfo.principalId == frqPrevious.principalInfo.principalId) + { + entryFound = true; + break; + } + } + if (entryFound == false) + { + // outgoing friend request removed + generateNotification(NOTIFICATION_TYPE_REMOVED_OUTGOING_REQUEST, frqPrevious.principalInfo.principalId); + } + } +} + +void addUniquePidToList(std::vector& pidList, uint32 pid) +{ + bool alreadyAdded = false; + for (auto& it2 : pidList) + { + if (it2 == pid) + { + alreadyAdded = true; + break; + } + } + if (alreadyAdded) + return; + pidList.push_back(pid); +} + +void NexFriends::getFriendPIDs(uint32* pidList, uint32* pidCount, sint32 offset, sint32 count, bool includeFriendRequests) +{ + if (count < 0) + { + *pidCount = 0; + return; + } + // parse response and update lists + std::vector allPids; + std::unique_lock listLock(this->mtx_lists); + for (auto& it : this->list_friends) + { + uint32 pid = it.nnaInfo.principalInfo.principalId; + addUniquePidToList(allPids, pid); + } + if (includeFriendRequests) + { + // incoming friend requests are not included + for (auto& it : this->list_friendReqOutgoing) + { + uint32 pid = it.principalInfo.principalId; + addUniquePidToList(allPids, pid); + } + } + sint32 copyCount = std::max((sint32)allPids.size() - offset, 0); + copyCount = std::min(copyCount, count); + if (pidList) + { + for (sint32 i = 0; i < copyCount; i++) + pidList[i] = allPids[offset + i]; + } + *pidCount = copyCount; +} + +void NexFriends::getFriendRequestPIDs(uint32* pidList, uint32* pidCount, sint32 offset, sint32 count, bool includeIncoming, bool includeOutgoing) +{ + if (count < 0) + { + *pidCount = 0; + return; + } + // parse response and update lists + std::vector allPids; + std::unique_lock listLock(this->mtx_lists); + if (includeIncoming) + { + for (auto& it : this->list_friendReqIncoming) + { + uint32 pid = it.principalInfo.principalId; + addUniquePidToList(allPids, pid); + } + } + if (includeOutgoing) + { + for (auto& it : this->list_friendReqOutgoing) + { + uint32 pid = it.principalInfo.principalId; + addUniquePidToList(allPids, pid); + } + } + sint32 copyCount = std::max((sint32)allPids.size() - offset, 0); + copyCount = std::min(copyCount, count); + if (pidList) + { + for (sint32 i = 0; i < copyCount; i++) + pidList[i] = allPids[offset + i]; + } + *pidCount = copyCount; +} + +std::string NexFriends::getAccountNameByPid(uint32 principalId) +{ + if (this->pid == principalId) + return this->nnid; + + nexFriend friendData{}; + if(getFriendByPID(friendData, principalId)) + return friendData.nnaInfo.principalInfo.nnid; + + nexFriendRequest requestData{}; + if (getFriendRequestByPID(requestData, nullptr, principalId)) + return requestData.principalInfo.nnid; + + return {}; +} + +int NexFriends::getPendingFriendRequestCount() +{ + std::unique_lock listLock(this->mtx_lists); + return (int)this->list_friendReqIncoming.size(); +} + + +bool NexFriends::getFriendByPID(nexFriend& friendData, uint32 searchedPid) +{ + std::unique_lock listLock(this->mtx_lists); + for (auto& it : this->list_friends) + { + uint32 pid = it.nnaInfo.principalInfo.principalId; + if (pid == searchedPid) + { + friendData = it; + return true; + } + } + return false; +} + +bool NexFriends::getFriendRequestByPID(nexFriendRequest& friendRequestData, bool* isIncoming, uint32 searchedPid) +{ + std::unique_lock listLock(this->mtx_lists); + for (auto& it : this->list_friendReqIncoming) + { + uint32 pid = it.principalInfo.principalId; + if (pid == searchedPid) + { + friendRequestData = it; + if(isIncoming) + *isIncoming = true; + return true; + } + } + for (auto& it : this->list_friendReqOutgoing) + { + uint32 pid = it.principalInfo.principalId; + if (pid == searchedPid) + { + friendRequestData = it; + if (isIncoming) + *isIncoming = false; + return true; + } + } + return false; +} + +bool NexFriends::getFriendRequestByMessageId(nexFriendRequest& friendRequestData, bool* isIncoming, uint64 messageId) +{ + std::unique_lock listLock(this->mtx_lists); + for (auto& it : this->list_friendReqIncoming) + { + uint64 mid = it.message.messageId; + if(mid == 0) + continue; + if (mid == messageId) + { + friendRequestData = it; + *isIncoming = true; + return true; + } + } + for (auto& it : this->list_friendReqOutgoing) + { + uint64 mid = it.message.messageId; + if (mid == 0) + continue; + if (mid == messageId) + { + friendRequestData = it; + *isIncoming = false; + return true; + } + } + return false; +} + +void addProvisionalFriendHandler(nexServiceResponse_t* nexResponse, std::function cb) +{ + if (nexResponse->isSuccessful) + cb(0); + else + { + // todo: Properly handle returned error code and data + cb(NexFriends::ERR_RPC_FAILED); + } +} + +bool NexFriends::addProvisionalFriend(char* name, std::function cb) +{ + if (nexCon == nullptr || nexCon->getState() != nexService::STATE_CONNECTED) + { + // not connected + cb(ERR_NOT_CONNECTED); + return false; + } + uint8 tempNexBufferArray[128]; + nexPacketBuffer packetBuffer(tempNexBufferArray, sizeof(tempNexBufferArray), true); + packetBuffer.writeString(name); + nexCon->callMethod(NEX_PROTOCOL_FRIENDS_WIIU, 3, &packetBuffer, std::bind(addProvisionalFriendHandler, std::placeholders::_1, cb), true); + return true; +} + +void addFriendRequestHandler(nexServiceResponse_t* nexResponse, NexFriends* nexFriends, std::function cb) +{ + if (nexResponse->isSuccessful) + cb(0); + else + { + // todo: Properly handle returned error code + cb(NexFriends::ERR_RPC_FAILED); + // refresh the list + nexFriends->requestGetAllInformation(); + } +} + +void NexFriends::addFriendRequest(uint32 pid, const char* comment, std::function cb) +{ + if (nexCon == nullptr || nexCon->getState() != nexService::STATE_CONNECTED) + { + // not connected + cb(ERR_NOT_CONNECTED); + return; + } + uint8 tempNexBufferArray[2048]; + nexPacketBuffer packetBuffer(tempNexBufferArray, sizeof(tempNexBufferArray), true); + packetBuffer.writeU32(pid); + packetBuffer.writeU8(0); // ukn (language of comment?) + packetBuffer.writeString(comment); + packetBuffer.writeU8(0); // ukn (language of next string?) + packetBuffer.writeString(""); // ukn + nexGameKey(0, 0).writeData(&packetBuffer); + packetBuffer.writeU64(0); // ukn + nexCon->callMethod(NEX_PROTOCOL_FRIENDS_WIIU, 5, &packetBuffer, std::bind(addFriendRequestHandler, std::placeholders::_1, this, cb), true); +} + +typedef struct +{ + NEXFRIENDS_CALLBACK cb; + void* customParam; + NexFriends* nexFriends; + // command specific + struct + { + nexPrincipalBasicInfo* basicInfo; + sint32 count; + }principalBaseInfo; +}nexFriendsCallInfo_t; + +void NexFriends_handleResponse_requestPrincipleBaseInfoByPID(nexService* nex, nexServiceResponse_t* response) +{ + nexFriendsCallInfo_t* callInfo = (nexFriendsCallInfo_t*)response->custom; + if (response->isSuccessful == false) + { + // handle error case + callInfo->cb(callInfo->nexFriends, NexFriends::ERR_RPC_FAILED, callInfo->customParam); + free(callInfo); + return; + } + // process result + uint32 count = response->data.readU32(); + if (count != callInfo->principalBaseInfo.count) + { + callInfo->cb(callInfo->nexFriends, NexFriends::ERR_UNEXPECTED_RESULT, callInfo->customParam); + free(callInfo); + return; + } + for (uint32 i = 0; i < count; i++) + { + callInfo->principalBaseInfo.basicInfo[i].readData(&response->data); + } + if (response->data.hasReadOutOfBounds()) + { + callInfo->cb(callInfo->nexFriends, NexFriends::ERR_UNEXPECTED_RESULT, callInfo->customParam); + free(callInfo); + return; + } + // callback + callInfo->cb(callInfo->nexFriends, NexFriends::ERR_NONE, callInfo->customParam); + free(callInfo); +} + +void NexFriends::requestPrincipleBaseInfoByPID(nexPrincipalBasicInfo* basicInfo, uint32* pidList, sint32 count, NEXFRIENDS_CALLBACK cb, void* customParam) +{ + if (nexCon == nullptr || nexCon->getState() != nexService::STATE_CONNECTED) + { + // not connected + cb(this, ERR_NOT_CONNECTED, customParam); + return; + } + uint8 tempNexBufferArray[512]; + nexPacketBuffer packetBuffer(tempNexBufferArray, sizeof(tempNexBufferArray), true); + packetBuffer.writeU32(count); + for(sint32 i=0; iprincipalBaseInfo.basicInfo = basicInfo; + callInfo->principalBaseInfo.count = count; + callInfo->cb = cb; + callInfo->customParam = customParam; + callInfo->nexFriends = this; + nexCon->callMethod(NEX_PROTOCOL_FRIENDS_WIIU, 17, &packetBuffer, NexFriends_handleResponse_requestPrincipleBaseInfoByPID, callInfo, true); +} + +void genericFriendServiceNoResponseHandler(nexServiceResponse_t* nexResponse, std::function cb) +{ + if (nexResponse->isSuccessful) + cb(0); + else + { + // todo: Properly handle returned error code + cb(NexFriends::ERR_RPC_FAILED); + } +} + +void NexFriends::removeFriend(uint32 pid, std::function cb) +{ + if (nexCon == nullptr || nexCon->getState() != nexService::STATE_CONNECTED) + { + // not connected + cb(ERR_NOT_CONNECTED); + return; + } + uint8 tempNexBufferArray[512]; + nexPacketBuffer packetBuffer(tempNexBufferArray, sizeof(tempNexBufferArray), true); + packetBuffer.writeU32(pid); + nexCon->callMethod(NEX_PROTOCOL_FRIENDS_WIIU, 4, &packetBuffer, std::bind(genericFriendServiceNoResponseHandler, std::placeholders::_1, cb), true); +} + +void NexFriends::cancelOutgoingProvisionalFriendRequest(uint32 pid, std::function cb) +{ + if (nexCon == nullptr || nexCon->getState() != nexService::STATE_CONNECTED) + { + // not connected + cb(ERR_NOT_CONNECTED); + return; + } + uint8 tempNexBufferArray[512]; + nexPacketBuffer packetBuffer(tempNexBufferArray, sizeof(tempNexBufferArray), true); + packetBuffer.writeU32(pid); + nexCon->callMethod(NEX_PROTOCOL_FRIENDS_WIIU, 4, &packetBuffer, std::bind(genericFriendServiceNoResponseHandler, std::placeholders::_1, cb), true); +} + +void NexFriends::acceptFriendRequest(uint64 messageId, std::function cb) +{ + if (nexCon == nullptr || nexCon->getState() != nexService::STATE_CONNECTED) + { + // not connected + cb(ERR_NOT_CONNECTED); + return; + } + uint8 tempNexBufferArray[128]; + nexPacketBuffer packetBuffer(tempNexBufferArray, sizeof(tempNexBufferArray), true); + packetBuffer.writeU64(messageId); + nexCon->callMethod(NEX_PROTOCOL_FRIENDS_WIIU, 7, &packetBuffer, std::bind(genericFriendServiceNoResponseHandler, std::placeholders::_1, cb), true); +} + +void markFriendRequestsAsReceivedHandler(nexServiceResponse_t* nexResponse, std::function cb) +{ + if (nexResponse->isSuccessful) + cb(0); + else + { + // todo: Properly handle returned error code + cb(NexFriends::ERR_RPC_FAILED); + } +} + +void NexFriends::markFriendRequestsAsReceived(uint64* messageIdList, sint32 count, std::function cb) +{ + if (nexCon == nullptr || nexCon->getState() != nexService::STATE_CONNECTED) + { + // not connected + cb(ERR_NOT_CONNECTED); + return; + } + uint8 tempNexBufferArray[1024]; + nexPacketBuffer packetBuffer(tempNexBufferArray, sizeof(tempNexBufferArray), true); + packetBuffer.writeU32(count); + for(sint32 i=0; icallMethod(NEX_PROTOCOL_FRIENDS_WIIU, 10, &packetBuffer, std::bind(markFriendRequestsAsReceivedHandler, std::placeholders::_1, cb), true); +} + +void genericFriendServiceNoResponseHandlerWithoutCB(nexServiceResponse_t* nexResponse) +{ + +} + +void NexFriends::updateMyPresence(nexPresenceV2& myPresence) +{ + this->myPresence = myPresence; + if (nexCon == nullptr || nexCon->getState() != nexService::STATE_CONNECTED) + { + // not connected + return; + } + uint8 tempNexBufferArray[1024]; + nexPacketBuffer packetBuffer(tempNexBufferArray, sizeof(tempNexBufferArray), true); + myPresence.writeData(&packetBuffer); + nexCon->callMethod(NEX_PROTOCOL_FRIENDS_WIIU, 13, &packetBuffer, std::bind(genericFriendServiceNoResponseHandlerWithoutCB, std::placeholders::_1), false); +} + +void NexFriends::update() +{ + std::unique_lock loginLock(mtx_login); + if (!isCurrentlyConnected) + { + if (loginInProcess) + { + // wait for login attempt to finish + } + else + { + // should we try to reconnect? + uint32 timeSinceLastLoginAttempt = prudpGetMSTimestamp() - this->lastLoginAttemptTime; + uint32 delayTime = 30; // 30 seconds by default + if (this->numFailedLogins < 3) + delayTime = 30; + else + { + if (this->numSuccessfulLogins == 0) + return; // never try again + if (this->numFailedLogins >= 10) + return; // stop after 10 failed attempts + delayTime = 60 + (this->numFailedLogins - 3) * 60; // add one minute each time it fails + } + if (timeSinceLastLoginAttempt < delayTime) + return; + forceLog_printf("NEX: Attempt async friend service login"); + initiateLogin(); + } + } + else + { + if (this->nexCon == nullptr || this->nexCon->getState() != nexService::STATE_CONNECTED) + { + forceLog_printf("NEX: Lost friend server session"); + if (this->nexCon) + { + this->nexCon->destroy(); + this->nexCon = nullptr; + } + + isCurrentlyConnected = false; + } + } +} diff --git a/src/Cemu/nex/nexFriends.h b/src/Cemu/nex/nexFriends.h new file mode 100644 index 00000000..cb935068 --- /dev/null +++ b/src/Cemu/nex/nexFriends.h @@ -0,0 +1,641 @@ +#pragma once + +#ifndef FFL_SIZE +#define FFL_SIZE 0x60 +#endif + +class nexGameKey : public nexType +{ +public: + nexGameKey() + { + + } + + nexGameKey(uint64 titleId, uint16 ukn) + { + this->titleId = titleId; + this->ukn = ukn; + } + + nexGameKey(nexPacketBuffer* pb) + { + readData(pb); + } + + void writeData(nexPacketBuffer* pb) const override + { + pb->writeU64(titleId); + pb->writeU16(ukn); + } + void readData(nexPacketBuffer* pb) override + { + titleId = pb->readU64(); + ukn = pb->readU16(); + } +public: + uint64 titleId; + uint16 ukn; +}; + +class nexMiiV2 : public nexType +{ +public: + nexMiiV2() + { + miiNickname[0] = '\0'; + } + + nexMiiV2(const char* miiNickname, uint8* miiData) + { + strncpy(this->miiNickname, miiNickname, 127); + this->miiNickname[127] = '\0'; + memcpy(this->miiData, miiData, FFL_SIZE); + } + + nexMiiV2(nexPacketBuffer* pb) + { + readData(pb); + } + + void writeData(nexPacketBuffer* pb) const override + { + pb->writeString(this->miiNickname); + pb->writeU8(0); // ukn + pb->writeU8(0); // ukn + pb->writeBuffer(this->miiData, FFL_SIZE); + pb->writeU64(0); // ukn + } + void readData(nexPacketBuffer* pb) override + { + pb->readString(this->miiNickname, sizeof(this->miiNickname)); + this->miiNickname[127] = '\0'; + pb->readU8(); // ukn + pb->readU8(); // ukn + memset(miiData, 0, sizeof(miiData)); + pb->readBuffer(this->miiData, FFL_SIZE); + pb->readU64(); // ukn + } +public: + uint8 miiData[FFL_SIZE]; + char miiNickname[128]; +}; + +class nexPresenceV2 : public nexType +{ +public: + nexPresenceV2() + { + + } + + nexPresenceV2(int ukn) + { + // todo + } + + nexPresenceV2(nexPacketBuffer* pb) + { + readData(pb); + } + + const char* getMetaName() override + { + return "NintendoPresenceV2"; + } + + void writeData(nexPacketBuffer* pb) const override + { + pb->writeU32(1); // isValid? + pb->writeU8(isOnline ? 1 : 0); + // gameKey: + pb->writeU64(gameKey.titleId); + pb->writeU16(gameKey.ukn); + + pb->writeU8(1); // uint8 ukn3(example value : 1) + pb->writeString("");// String msg(current game ? ) + + pb->writeU32(joinFlagMask); + pb->writeU8(joinAvailability); + pb->writeU32(gameId); + pb->writeU32(gameMode); + pb->writeU32(hostPid); + pb->writeU32(groupId); + + pb->writeBuffer(appSpecificData, sizeof(appSpecificData)); + pb->writeU8(3); // ukn + pb->writeU8(1); // ukn + pb->writeU8(3); // ukn + } + void readData(nexPacketBuffer* pb) override + { + ukn0 = pb->readU32(); + isOnline = pb->readU8(); + gameKey.readData(pb); + ukn3 = pb->readU8(); + char msgBuffer[1024]; + pb->readString(msgBuffer, sizeof(msgBuffer)); + msgBuffer[1023] = '\0'; + this->msg = std::string(msgBuffer); + joinFlagMask = pb->readU32(); + joinAvailability = pb->readU8(); + gameId = pb->readU32(); + gameMode = pb->readU32(); + hostPid = pb->readU32(); + groupId = pb->readU32(); + pb->readBuffer(appSpecificData, sizeof(appSpecificData)); + ukn11 = pb->readU8(); + ukn12 = pb->readU8(); + ukn13 = pb->readU8(); + } +public: + uint32 ukn0; // seen values: 0 -> offline, 1 -> online (menu, but not always), 8 and 0x1E6 -> in Splatoon + uint8 isOnline; + nexGameKey gameKey; + uint8 ukn3; // message language? + std::string msg; + uint32 joinFlagMask; + uint8 joinAvailability; + uint32 gameId; + uint32 gameMode; + uint32 hostPid; + uint32 groupId; + uint8 appSpecificData[0x14]; + uint8 ukn11; + uint8 ukn12; + uint8 ukn13; +}; + +class nexPrincipalBasicInfo : public nexType +{ +public: + nexPrincipalBasicInfo() + { + this->nnid[0] = '\0'; + } + + nexPrincipalBasicInfo(uint32 principalId, char* nnid, const nexMiiV2& mii) + { + this->principalId = principalId; + strcpy(this->nnid, nnid); + this->mii = mii; + } + + nexPrincipalBasicInfo(nexPacketBuffer* pb) + { + readData(pb); + } + + void writeData(nexPacketBuffer* pb) const override + { + pb->writeU32(principalId); + pb->writeString(this->nnid); + mii.writeData(pb); + pb->writeU8(2); // todo + } + + void readData(nexPacketBuffer* pb) override + { + principalId = pb->readU32(); + pb->readString(nnid, sizeof(nnid)); + mii.readData(pb); + regionGuessed = pb->readU8(); + } + +public: + uint32 principalId; + char nnid[32]; + nexMiiV2 mii; + uint8 regionGuessed; +}; + +class nexNNAInfo : public nexType +{ +public: + nexNNAInfo() + { + + } + + nexNNAInfo(uint8 countryCode, uint8 countrySubCode, const nexPrincipalBasicInfo& principalBasicInfo) + { + this->countryCode = countryCode; + this->countrySubCode = countrySubCode; + this->principalInfo = principalBasicInfo; + } + + nexNNAInfo(nexPacketBuffer* pb) + { + readData(pb); + } + + void writeData(nexPacketBuffer* pb) const override + { + principalInfo.writeData(pb); + pb->writeU8(countryCode); + pb->writeU8(countrySubCode); + } + void readData(nexPacketBuffer* pb) override + { + principalInfo.readData(pb); + countryCode = pb->readU8(); + countrySubCode = pb->readU8(); + } +public: + uint8 countryCode; + uint8 countrySubCode; + nexPrincipalBasicInfo principalInfo; +}; + +class nexPrincipalPreference : public nexType +{ +public: + nexPrincipalPreference() + { + + } + + nexPrincipalPreference(uint8 ukn0, uint8 ukn1, uint8 ukn2) + { + this->ukn0 = ukn0; + this->ukn1 = ukn1; + this->ukn2 = ukn2; + } + + nexPrincipalPreference(nexPacketBuffer* pb) + { + readData(pb); + } + + void writeData(nexPacketBuffer* pb) const override + { + pb->writeU8(ukn0); + pb->writeU8(ukn1); + pb->writeU8(ukn2); + } + + void readData(nexPacketBuffer* pb) override + { + ukn0 = pb->readU8(); + ukn1 = pb->readU8(); + ukn2 = pb->readU8(); + } +public: + uint8 ukn0; + uint8 ukn1; + uint8 ukn2; +}; + +class nexComment : public nexType +{ +public: + nexComment() + { + + } + + nexComment(uint8 todo) + { + cemu_assert_unimplemented(); + } + + nexComment(nexPacketBuffer* pb) + { + readData(pb); + } + + void writeData(nexPacketBuffer* pb) const override + { + cemu_assert_unimplemented(); + } + + void readData(nexPacketBuffer* pb) override + { + ukn0 = pb->readU8(); + char stringBuffer[1024]; + pb->readString(stringBuffer, 1024); + stringBuffer[1023] = '\0'; + commentString = std::string(stringBuffer); + ukn1 = pb->readU64(); + } +public: + uint8 ukn0; + std::string commentString; + uint64 ukn1; +}; + +class nexFriendRequestMessage : public nexType +{ +public: + nexFriendRequestMessage() + { + + } + + nexFriendRequestMessage(uint8 todo) + { + cemu_assert_unimplemented(); + } + + nexFriendRequestMessage(nexPacketBuffer* pb) + { + readData(pb); + } + + void writeData(nexPacketBuffer* pb) const override + { + cemu_assert_unimplemented(); + } + void readData(nexPacketBuffer* pb) override + { + messageId = pb->readU64(); + isMarkedAsReceived = pb->readU8(); + ukn2 = pb->readU8(); + char stringBuffer[1024]; + pb->readString(stringBuffer, sizeof(stringBuffer)); + stringBuffer[1023] = '\0'; + commentStr = std::string(stringBuffer); + ukn4 = pb->readU8(); + pb->readString(stringBuffer, sizeof(stringBuffer)); + stringBuffer[1023] = '\0'; + ukn5Str = std::string(stringBuffer); + gameKey.readData(pb); + ukn7 = pb->readU64(); + expireTimestamp = pb->readU64(); + } +public: + uint64 messageId; + uint8 isMarkedAsReceived; + uint8 ukn2; + std::string commentStr; + uint8 ukn4; + std::string ukn5Str; + nexGameKey gameKey; + uint64 ukn7; + uint64 expireTimestamp; +}; + +class nexFriendRequest : public nexType +{ +public: + nexFriendRequest() + { + } + + nexFriendRequest(uint64 titleId, uint16 ukn) + { + cemu_assert_unimplemented(); + } + + nexFriendRequest(nexPacketBuffer* pb) + { + readData(pb); + } + + const char* getMetaName() override + { + return "FriendRequest"; + } + + void writeData(nexPacketBuffer* pb) const override + { + cemu_assert_unimplemented(); + } + + void readData(nexPacketBuffer* pb) override + { + principalInfo.readData(pb); + message.readData(pb); + ukn = pb->readU64(); + } +public: + nexPrincipalBasicInfo principalInfo; + nexFriendRequestMessage message; + uint64 ukn; +}; + +class nexFriend : public nexType +{ +public: + nexFriend() + { + } + + nexFriend(nexPacketBuffer* pb) + { + readData(pb); + } + + const char* getMetaName() override + { + return "FriendInfo"; + } + + void writeData(nexPacketBuffer* pb) const override + { + cemu_assert_unimplemented(); + } + + void readData(nexPacketBuffer* pb) override + { + nnaInfo.readData(pb); + presence.readData(pb); + gameModeMessage.readData(pb); + friendsSinceTimestamp = pb->readU64(); + lastOnlineTimestamp = pb->readU64(); + ukn6 = pb->readU64(); + } +public: + nexNNAInfo nnaInfo; + nexPresenceV2 presence; + nexComment gameModeMessage; + uint64 friendsSinceTimestamp; + uint64 lastOnlineTimestamp; + uint64 ukn6; +}; + +class nexBlacklisted : public nexType +{ +public: + nexBlacklisted() + { + } + + nexBlacklisted(nexPacketBuffer* pb) + { + readData(pb); + } + + void writeData(nexPacketBuffer* pb) const override + { + cemu_assert_unimplemented(); + } + void readData(nexPacketBuffer* pb) override + { + basicInfo.readData(pb); + gameKey.readData(pb); + ukn = pb->readU64(); + } +public: + nexPrincipalBasicInfo basicInfo; + nexGameKey gameKey; + uint64 ukn; +}; + + +class nexPersistentNotification : public nexType +{ +public: + nexPersistentNotification() + { + } + + nexPersistentNotification(nexPacketBuffer* pb) + { + readData(pb); + } + + void writeData(nexPacketBuffer* pb) const override + { + cemu_assert_unimplemented(); + } + void readData(nexPacketBuffer* pb) override + { + messageId = pb->readU64(); + pid1 = pb->readU32(); + pid2 = pb->readU32(); + type = pb->readU32(); + pb->readStdString(msg); + } +public: + uint64 messageId; + uint32 pid1; // principal id 1 (might differ depending on type) + uint32 pid2; // principal id 2 (might differ depending on type) + uint32 type; + std::string msg; +}; +class NexFriends; + +typedef void (*NEXFRIENDS_CALLBACK)(NexFriends* nexFriends, uint32 result, void* custom); + +class NexFriends +{ +public: + static const int ERR_NONE = 0; + static const int ERR_RPC_FAILED = 1; + static const int ERR_UNEXPECTED_RESULT = 2; + static const int ERR_NOT_CONNECTED = 3; + + enum NOTIFICATION_TYPE + { + NOTIFICATION_TYPE_ONLINE = 0, + + NOTIFICATION_TYPE_FRIEND_LOGIN = 4, + NOTIFICATION_TYPE_FRIEND_LOGOFF = 5, + NOTIFICATION_TYPE_FRIEND_PRESENCE_CHANGE = 6, + + NOTIFICATION_TYPE_ADDED_FRIEND = 9, + NOTIFICATION_TYPE_REMOVED_FRIEND = 10, + NOTIFICATION_TYPE_ADDED_OUTGOING_REQUEST = 11, + NOTIFICATION_TYPE_REMOVED_OUTGOING_REQUEST = 12, + NOTIFICATION_TYPE_ADDED_INCOMING_REQUEST = 17, + NOTIFICATION_TYPE_REMOVED_INCOMING_REQUEST = 18 + + + }; + +public: + NexFriends(uint32 authServerIp, uint16 authServerPort, const char* accessKey, uint32 pid, const char* nexPassword, const char* nexToken, const char* nnid, uint8* miiData, const wchar_t* miiNickname, uint8 countryCode, nexPresenceV2& myPresence); + + ~NexFriends(); + + std::string getAccountNameByPid(uint32 principalId); + int getPendingFriendRequestCount(); + + bool requestGetAllInformation(); + bool requestGetAllInformation(std::function cb); + bool addProvisionalFriendByPidGuessed(uint32 principalId); + void acceptFriendRequest(uint64 messageId, std::function cb); + bool isOnline(); + + void getFriendPIDs(uint32* pidList, uint32* pidCount, sint32 offset, sint32 count, bool includeFriendRequests); + void getFriendRequestPIDs(uint32* pidList, uint32* pidCount, sint32 offset, sint32 count, bool includeIncoming, bool includeOutgoing); + bool getFriendByPID(nexFriend& friendData, uint32 pid); + bool getFriendRequestByPID(nexFriendRequest& friendRequestData, bool* isIncoming, uint32 searchedPid); + bool getFriendRequestByMessageId(nexFriendRequest& friendRequestData, bool* isIncoming, uint64 messageId); + + bool addProvisionalFriend(char* name, std::function cb); + void addFriendRequest(uint32 pid, const char* comment, std::function cb); + + void requestPrincipleBaseInfoByPID(nexPrincipalBasicInfo* basicInfo, uint32* pidList, sint32 count, NEXFRIENDS_CALLBACK cb, void* customParam); + void removeFriend(uint32 pid, std::function cb); + void cancelOutgoingProvisionalFriendRequest(uint32 pid, std::function cb); + void markFriendRequestsAsReceived(uint64* messageIdList, sint32 count, std::function cb); + + void updateMyPresence(nexPresenceV2& myPresence); + bool updatePreferences(const nexPrincipalPreference& newPreferences); + + void setNotificationHandler(void(*notificationHandler)(NOTIFICATION_TYPE notificationType, uint32 pid)); + + void update(); + + void processServerNotification(uint32 notificationType, uint32 pid, nexPacketBuffer* notificationData); + +private: + void doAsyncLogin(); + void initiateLogin(); + + static void handleResponse_acceptFriendRequest(nexService* nex, nexServiceResponse_t* response); + static void handleResponse_getAllInformation(nexServiceResponse_t* response, NexFriends* nexFriends, std::function cb); + static void handleResponse_updatePreferences(nexServiceResponse_t* response, NexFriends* nexFriends, std::function cb); + + void generateNotification(NOTIFICATION_TYPE notificationType, uint32 pid); + void trackNotifications(); + + // notification-service handlers + void processServerNotification_friendOffline(uint32 pid); + void processServerNotification_presenceChange(uint32 pid, nexPresenceV2& presence); + void processServerNotification_removeFriendOrFriendRequest(uint32 pid); + void processServerNotification_incomingFriendRequest(uint32 pid, nexFriendRequest& friendRequest); + void processServerNotification_addedFriend(uint32 pid, nexFriend& frd); + +private: + void(*notificationHandler)(NOTIFICATION_TYPE notificationType, uint32 pid); + bool isCurrentlyConnected; + bool hasData; // set after connect when information request response was received + bool firstInformationRequest; + nexService* nexCon; + uint8 miiData[FFL_SIZE]; + std::string miiNickname; + char nnid[96]; + uint32 pid; + uint8 countryCode; + // login tracking + std::recursive_mutex mtx_login; + bool loginInProcess; + uint32 lastLoginAttemptTime; + uint32 numFailedLogins; + uint32 numSuccessfulLogins; + // auth + struct + { + uint32 serverIp; + uint16 port; + std::string accessKey; + std::string nexPassword; + std::string nexToken; + }auth; + // local friend state + nexPresenceV2 myPresence; + + std::recursive_mutex mtx_lists; + std::vector list_friends; + std::vector list_friendReqOutgoing; + std::vector list_friendReqIncoming; + struct + { + std::vector list_friends; + std::vector list_friendReqOutgoing; + std::vector list_friendReqIncoming; + }previousState; +}; diff --git a/src/Cemu/nex/nexThread.cpp b/src/Cemu/nex/nexThread.cpp new file mode 100644 index 00000000..fbc5e261 --- /dev/null +++ b/src/Cemu/nex/nexThread.cpp @@ -0,0 +1,81 @@ +#include "prudp.h" +#include "nex.h" +#include "nexThread.h" + +std::mutex mtx_queuedServices; +std::vector list_queuedServices; + +std::vector list_activeNexServices; + +void nexThread_run() +{ + while (true) + { + // check for new services + mtx_queuedServices.lock(); + for(auto& it : list_queuedServices) + list_activeNexServices.push_back(it); + list_queuedServices.clear(); + mtx_queuedServices.unlock(); + // if service list is empty then pause + if (list_activeNexServices.empty()) + { + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + continue; + } + // update + for (auto& it : list_activeNexServices) + { + it->update(); + } + // delete services marked for destruction + sint32 idx = 0; + sint32 listSize = (sint32)list_activeNexServices.size(); + while (idx < listSize) + { + if (list_activeNexServices[idx]->isMarkedForDestruction()) + { + list_activeNexServices[idx]->destroy(); + listSize--; + list_activeNexServices[idx] = list_activeNexServices[listSize]; + } + idx++; + } + if (listSize != list_activeNexServices.size()) + { + list_activeNexServices.resize(listSize); + } + // idle for short time + // todo - find a better way to handle this with lower latency. Maybe using select() with loopback socket for interrupting the select on service change + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } +} + +bool _nexThreadLaunched = false; + +std::thread::id nexThreadId; + +void nexThread_init() +{ + if (_nexThreadLaunched) + return; + std::thread t(nexThread_run); + nexThreadId = t.get_id(); + t.detach(); + _nexThreadLaunched = true; +} + +void nexThread_registerService(nexService* service) +{ + mtx_queuedServices.lock(); + nexThread_init(); + list_queuedServices.push_back(service); + mtx_queuedServices.unlock(); +} + +bool nexThread_isCurrentThread() +{ + if (_nexThreadLaunched == false) + return false; + return std::this_thread::get_id() == nexThreadId; +} \ No newline at end of file diff --git a/src/Cemu/nex/nexThread.h b/src/Cemu/nex/nexThread.h new file mode 100644 index 00000000..4870e920 --- /dev/null +++ b/src/Cemu/nex/nexThread.h @@ -0,0 +1,4 @@ +#pragma once + +void nexThread_registerService(nexService* service); +bool nexThread_isCurrentThread(); \ No newline at end of file diff --git a/src/Cemu/nex/nexTypes.h b/src/Cemu/nex/nexTypes.h new file mode 100644 index 00000000..8147836c --- /dev/null +++ b/src/Cemu/nex/nexTypes.h @@ -0,0 +1,366 @@ +#pragma once +#include + +#ifndef FFL_SIZE +#define FFL_SIZE (0x60) +#endif + +class nexPacketBuffer; + +class nexMetaType +{ +public: + virtual const char* getMetaName() const = 0; + virtual void writeData(nexPacketBuffer* pb) const = 0; +}; + +class nexType +{ +public: + virtual const char* getMetaName() + { + cemu_assert_unimplemented(); + return ""; + } + virtual void writeData(nexPacketBuffer* pb) const = 0; + virtual void readData(nexPacketBuffer* pb) = 0; +}; + +class nexPacketBuffer +{ +public: + nexPacketBuffer(uint8* data, sint32 size, bool isWrite) + { + this->buffer = data; + this->size = size; + this->currentIndex = 0; + this->isWrite = isWrite; + this->readOutOfBounds = false; + } + + nexPacketBuffer() + { + this->buffer = 0; + this->size = 0; + this->currentIndex = 0; + } + + void writeData(const uint8* data, sint32 len) + { + if (this->currentIndex + len > this->size) + return; + memcpy(this->buffer + this->currentIndex, data, len); + this->currentIndex += len; + } + + void writeU8(uint8 v) + { + if (this->currentIndex + sizeof(uint8) > this->size) + return; + *(uint8*)(this->buffer + this->currentIndex) = v; + this->currentIndex += sizeof(uint8); + } + + void writeU16(uint16 v) + { + if (this->currentIndex + sizeof(uint16) > this->size) + return; + *(uint16*)(this->buffer + this->currentIndex) = v; + this->currentIndex += sizeof(uint16); + } + + void writeU32(uint32 v) + { + if (this->currentIndex + sizeof(uint32) > this->size) + return; + *(uint32*)(this->buffer + this->currentIndex) = v; + this->currentIndex += sizeof(uint32); + } + + void writeU64(uint64 v) + { + if (this->currentIndex + sizeof(uint64) > this->size) + return; + *(uint64*)(this->buffer + this->currentIndex) = v; + this->currentIndex += sizeof(uint64); + } + + void writeString(const char* v) + { + sint32 len = (sint32)strlen(v) + 1; + writeU16(len); + writeData((uint8*)v, len); + } + + void writeBuffer(const uint8* v, sint32 len) + { + writeU32(len); + writeData(v, len); + } + + void writeCustomType(const nexMetaType& v) + { + // write meta name + writeString(v.getMetaName()); + // write length 0 placeholder + uint32* lengthPtr0 = (uint32*)(this->buffer + this->currentIndex); + writeU32(0); + // write length 1 placeholder + uint32* lengthPtr1 = (uint32*)(this->buffer + this->currentIndex); + writeU32(0); + // write data + uint32 preWriteIndex = this->currentIndex; + v.writeData(this); + uint32 writeSize = this->currentIndex - preWriteIndex; + // update lengths + *lengthPtr1 = writeSize; + *lengthPtr0 = writeSize + 4; + } + + uint64 readU64() + { + if (this->currentIndex + sizeof(uint64) > this->size) + { + readOutOfBounds = true; + return 0; + } + uint64 v = *(uint64*)(this->buffer + this->currentIndex); + this->currentIndex += sizeof(uint64); + return v; + } + + uint32 readU32() + { + if (this->currentIndex + sizeof(uint32) > this->size) + { + readOutOfBounds = true; + return 0; + } + uint32 v = *(uint32*)(this->buffer + this->currentIndex); + this->currentIndex += sizeof(uint32); + return v; + } + + uint16 readU16() + { + if (this->currentIndex + sizeof(uint16) > this->size) + { + readOutOfBounds = true; + return 0; + } + uint16 v = *(uint16*)(this->buffer + this->currentIndex); + this->currentIndex += sizeof(uint16); + return v; + } + + uint8 readU8() + { + if (this->currentIndex + sizeof(uint8) > this->size) + { + readOutOfBounds = true; + return 0; + } + uint8 v = *(uint8*)(this->buffer + this->currentIndex); + this->currentIndex += sizeof(uint8); + return v; + } + + sint32 readData(void* buffer, sint32 length) + { + if (this->currentIndex + length > this->size) + { + readOutOfBounds = true; + return 0; + } + memcpy(buffer, (this->buffer + this->currentIndex), length); + this->currentIndex += length; + return length; + } + + // reads buffer data from packet and returns amount of bytes copied into buffer + // if buffer is larger than maxLength, the read data is silently truncated + sint32 readBuffer(void* buffer, sint32 maxLength) + { + sint32 bufferLength = (sint32)readU32(); + if (bufferLength < 0 || bufferLength >= 0x10000000) + { + readOutOfBounds = true; + return 0; + } + if (this->currentIndex + bufferLength > this->size) + { + readOutOfBounds = true; + return 0; + } + uint32 copiedLength = std::min(bufferLength, maxLength); + + memcpy(buffer, (this->buffer + this->currentIndex), copiedLength); + this->currentIndex += bufferLength; + return copiedLength; + } + + sint32 readString(char* buffer, sint32 maxLength) + { + sint32 bufferLength = readU16(); + if (this->currentIndex + bufferLength > this->size) + { + readOutOfBounds = true; + buffer[0] = '\0'; + return 0; + } + uint32 copiedLength = std::min(bufferLength, maxLength - 1); + + memcpy(buffer, (this->buffer + this->currentIndex), copiedLength); + buffer[copiedLength] = '\0'; + this->currentIndex += bufferLength; + return copiedLength; + } + + sint32 readStdString(std::string& outputStr) + { + sint32 bufferLength = readU16(); + if (this->currentIndex + bufferLength > this->size) + { + readOutOfBounds = true; + outputStr = std::string(""); + return 0; + } + sint32 copiedLength = bufferLength; + if (bufferLength > 0 && this->buffer[this->currentIndex+bufferLength-1] == '\0') + { + // cut off trailing '\0' + copiedLength--; + } + outputStr = std::string((char*)(this->buffer + this->currentIndex), copiedLength); + this->currentIndex += bufferLength; + return copiedLength; + } + + bool readPlaceholderType(nexType& v) + { + char name[128]; + readString(name, sizeof(name)); + name[sizeof(name)-1] = '\0'; + if (hasReadOutOfBounds()) + return false; + if (strcmp(name, v.getMetaName()) != 0) + return false; + // read sizes + uint32 len0 = readU32(); + uint32 len1 = readU32(); + if (hasReadOutOfBounds()) + return false; + if (len1 != (len0 - 4)) + return false; + // parse type data + v.readData(this); + if (hasReadOutOfBounds()) + return false; + return true; + //// write meta name + //writeString(v.getMetaName()); + //// write length 0 placeholder + //uint32* lengthPtr0 = (uint32*)(this->buffer + this->currentIndex); + //writeU32(0); + //// write length 1 placeholder + //uint32* lengthPtr1 = (uint32*)(this->buffer + this->currentIndex); + //writeU32(0); + //// write data + //uint32 preWriteIndex = this->currentIndex; + //v.writeData(this); + //uint32 writeSize = this->currentIndex - preWriteIndex; + //// update lengths + //*lengthPtr1 = writeSize; + //*lengthPtr0 = writeSize + 4; + } + + bool hasReadOutOfBounds() + { + return readOutOfBounds; + } + + uint8* getDataPtr() + { + return buffer; + } + + sint32 getWriteIndex() + { + return currentIndex; + } + + sint32 getSize() + { + return size; + } + + void setSize(sint32 length) + { + size = length; + } + +private: + uint8* buffer; + sint32 size; + sint32 currentIndex; + bool isWrite; + bool readOutOfBounds; // set if any read operation failed because it exceeded the buffer +}; + +class nexNintendoLoginData : public nexMetaType +{ +public: + nexNintendoLoginData(const char* nexToken) + { + this->nexToken = new std::string(nexToken); + } + + const char* getMetaName() const override + { + return "NintendoLoginData"; + } + + void writeData(nexPacketBuffer* pb) const override + { + pb->writeString(nexToken->c_str()); + } +private: + std::string* nexToken; +}; + +class nexNotificationEventGeneral : public nexType +{ +public: + nexNotificationEventGeneral() + { + + } + + nexNotificationEventGeneral(nexPacketBuffer* pb) + { + readData(pb); + } + + const char* getMetaName() override + { + return "NintendoNotificationEventGeneral"; + } + + void writeData(nexPacketBuffer* pb) const override + { + cemu_assert_unimplemented(); + } + + void readData(nexPacketBuffer* pb) override + { + param1 = pb->readU32(); + param2 = pb->readU64(); + param3 = pb->readU64(); + pb->readStdString(paramStr); + } +public: + uint32 param1; + uint64 param2; + uint64 param3; + std::string paramStr; +}; diff --git a/src/Cemu/nex/prudp.cpp b/src/Cemu/nex/prudp.cpp new file mode 100644 index 00000000..c60e4cc6 --- /dev/null +++ b/src/Cemu/nex/prudp.cpp @@ -0,0 +1,980 @@ +#include "prudp.h" +#include "util/crypto/md5.h" + +#include +#include + +void swap(unsigned char *a, unsigned char *b) +{ + int tmp = *a; + *a = *b; + *b = tmp; +} + +void KSA(unsigned char *key, int keyLen, unsigned char *S) +{ + int j = 0; + + for (int i = 0; i < RC4_N; i++) + S[i] = i; + + for (int i = 0; i < RC4_N; i++) + { + j = (j + S[i] + key[i % keyLen]) % RC4_N; + + swap(&S[i], &S[j]); + } +} + +void PRGA(unsigned char *S, unsigned char* input, int len, unsigned char* output) +{ + int i = 0; + int j = 0; + + for (size_t n = 0; n < len; n++) + { + i = (i + 1) % RC4_N; + j = (j + S[i]) % RC4_N; + + swap(&S[i], &S[j]); + int rnd = S[(S[i] + S[j]) % RC4_N]; + + output[n] = rnd ^ input[n]; + } +} + +void RC4(char* key, unsigned char* input, int len, unsigned char* output) +{ + unsigned char S[RC4_N]; + KSA((unsigned char*)key, (int)strlen(key), S); + PRGA(S, input, len, output); +} + +void RC4_initCtx(RC4Ctx_t* rc4Ctx, const char* key) +{ + rc4Ctx->i = 0; + rc4Ctx->j = 0; + KSA((unsigned char*)key, (int)strlen(key), rc4Ctx->S); +} + +void RC4_initCtx(RC4Ctx_t* rc4Ctx, unsigned char* key, int keyLen) +{ + rc4Ctx->i = 0; + rc4Ctx->j = 0; + KSA(key, keyLen, rc4Ctx->S); +} + +void RC4_transform(RC4Ctx_t* rc4Ctx, unsigned char* input, int len, unsigned char* output) +{ + int i = rc4Ctx->i; + int j = rc4Ctx->j; + + for (size_t n = 0; n < len; n++) + { + i = (i + 1) % RC4_N; + j = (j + rc4Ctx->S[i]) % RC4_N; + + swap(&rc4Ctx->S[i], &rc4Ctx->S[j]); + int rnd = rc4Ctx->S[(rc4Ctx->S[i] + rc4Ctx->S[j]) % RC4_N]; + + output[n] = rnd ^ input[n]; + } + + rc4Ctx->i = i; + rc4Ctx->j = j; +} + +uint32 prudpGetMSTimestamp() +{ + return GetTickCount(); +} + +std::bitset<10000> _portUsageMask; + +uint16 getRandomSrcPRUDPPort() +{ + while (true) + { + sint32 p = rand() % 10000; + if (_portUsageMask.test(p)) + continue; + _portUsageMask.set(p); + return 40000 + p; + } + return 0; +} + +void releasePRUDPPort(uint16 port) +{ + uint32 bitIndex = port - 40000; + _portUsageMask.reset(bitIndex); +} + +std::mt19937_64 prudpRG(GetTickCount()); +std::uniform_int_distribution prudpDis8(0, 0xFF); + +uint8 prudp_generateRandomU8() +{ + return prudpDis8(prudpRG); +} + +uint32 prudp_generateRandomU32() +{ + uint32 v = prudp_generateRandomU8(); + v <<= 8; + v |= prudp_generateRandomU8(); + v <<= 8; + v |= prudp_generateRandomU8(); + v <<= 8; + v |= prudp_generateRandomU8(); + return v; +} + +uint8 prudp_calculateChecksum(uint8 checksumBase, uint8* data, sint32 length) +{ + uint32 checksum32 = 0; + for (sint32 i = 0; i < length / 4; i++) + { + checksum32 += *(uint32*)(data + i * 4); + } + uint8 checksum = checksumBase; + for (sint32 i = length&(~3); i < length; i++) + { + checksum += data[i]; + } + checksum += (uint8)((checksum32 >> 0) & 0xFF); + checksum += (uint8)((checksum32 >> 8) & 0xFF); + checksum += (uint8)((checksum32 >> 16) & 0xFF); + checksum += (uint8)((checksum32 >> 24) & 0xFF); + return checksum; +} + +// calculate the actual size of the packet from the unprocessed raw data +// returns 0 on error +sint32 prudpPacket::calculateSizeFromPacketData(uint8* data, sint32 length) +{ + // check if packet is long enough to hold basic header + if (length < 0xB) + return 0; + // get flags fields + uint16 typeAndFlags = *(uint16*)(data + 0x02); + uint16 type = (typeAndFlags&0xF); + uint16 flags = (typeAndFlags >> 4); + if ((flags&FLAG_HAS_SIZE) == 0) + return length; // without a size field, we cant calculate the length + sint32 calculatedSize; + if (type == TYPE_SYN) + { + if (length < (0xB + 0x4 + 2)) + return 0; + uint16 payloadSize = *(uint16*)(data+0xB+0x4); + calculatedSize = 0xB + 0x4 + 2 + (sint32)payloadSize + 1; // base header + connection signature (SYN param) + payloadSize field + checksum after payload + if (calculatedSize > length) + return 0; + return calculatedSize; + } + else if (type == TYPE_CON) + { + if (length < (0xB + 0x4 + 2)) + return 0; + uint16 payloadSize = *(uint16*)(data + 0xB + 0x4); + calculatedSize = 0xB + 0x4 + 2 + (sint32)payloadSize + 1; // base header + connection signature (CON param) + payloadSize field + checksum after payload + // note: For secure connections the extra data is part of the payload + if (calculatedSize > length) + return 0; + return calculatedSize; + } + else if (type == TYPE_DATA) + { + if (length < (0xB + 1 + 2)) + return 0; + uint16 payloadSize = *(uint16*)(data + 0xB + 1); + calculatedSize = 0xB + 1 + 2 + (sint32)payloadSize + 1; // base header + fragmentIndex (DATA param) + payloadSize field + checksum after payload + if (calculatedSize > length) + return 0; + return calculatedSize; + } + else if (type == TYPE_PING) + { + if (length < (0xB + 2)) + return 0; + uint16 payloadSize = *(uint16*)(data + 0xB); + calculatedSize = 0xB + 2 + (sint32)payloadSize + 1; // base header + payloadSize field + checksum after payload + if (calculatedSize > length) + return 0; + return calculatedSize; + } + else + assert_dbg(); // unknown packet type (todo - add disconnect and ping packet, then make this function return 0 for all unknown types) + return length; +} + +prudpPacket::prudpPacket(prudpStreamSettings_t* streamSettings, uint8 src, uint8 dst, uint8 type, uint16 flags, uint8 sessionId, uint16 sequenceId, uint32 packetSignature) +{ + this->src = src; + this->dst = dst; + this->type = type; + this->flags = flags; + this->sessionId = sessionId; + this->sequenceId = sequenceId; + this->specifiedPacketSignature = packetSignature; + this->streamSettings = streamSettings; + this->fragmentIndex = 0; + this->isEncrypted = false; +} + +bool prudpPacket::requiresAck() +{ + return (flags&FLAG_NEED_ACK) != 0; +} + +sint32 prudpPacket::buildData(uint8* output, sint32 maxLength) +{ + // PRUDP V0 + // encrypt packet data + if (this->type == prudpPacket::TYPE_DATA) + { + // dont save new rc4 state if the packet is not reliable or requires no ack? + if (packetData.size() > 0) + { + if (isEncrypted == false) // only encrypt once + { + RC4_transform(&streamSettings->rc4Client, &packetData.front(), (int)packetData.size(), &packetData.front()); + isEncrypted = true; + } + } + } + // build packet + uint8* packetBuffer = output; + sint32 writeIndex = 0; + // write constant header + *(uint8*)(packetBuffer + 0x00) = src; + *(uint8*)(packetBuffer + 0x01) = dst; + uint16 typeAndFlags = (this->flags << 4) | (this->type); + *(uint16*)(packetBuffer + 0x02) = typeAndFlags; + *(uint8*)(packetBuffer + 0x04) = sessionId; + *(uint32*)(packetBuffer + 0x05) = packetSignature(); + *(uint16*)(packetBuffer + 0x09) = sequenceId; + writeIndex = 0xB; + // variable fields + if (this->type == TYPE_SYN) + { + *(uint32*)(packetBuffer + writeIndex) = 0; // connection signature (always 0 for SYN packet) + writeIndex += 4; + } + else if (this->type == TYPE_CON) + { + // connection signature (+ kerberos data if secure connection) + memcpy(packetBuffer + writeIndex, &packetData.front(), packetData.size()); + writeIndex += (int)packetData.size(); + } + else if (this->type == TYPE_DATA) + { + *(uint8*)(packetBuffer + writeIndex) = fragmentIndex; // fragment index + writeIndex += 1; + if (packetData.empty() == false) + { + memcpy(packetBuffer + writeIndex, &packetData.front(), packetData.size()); + writeIndex += (int)packetData.size(); + } + } + else if (this->type == TYPE_PING) + { + // no data + } + else + assert_dbg(); + // checksum + *(uint8*)(packetBuffer + writeIndex) = calculateChecksum(packetBuffer, writeIndex); + writeIndex++; + + return writeIndex; +} + +uint32 prudpPacket::packetSignature() +{ + if (type == TYPE_SYN) + return 0; + else if (type == TYPE_CON || type == TYPE_PING) + return specifiedPacketSignature; + else if (type == TYPE_DATA) + { + if (packetData.size() == 0) + return 0x12345678; + + HMACMD5Ctx ctx; + hmacMD5_init_limK_to_64(streamSettings->accessKeyDigest, 16, &ctx); + hmacMD5_update(&packetData.front(), (int)packetData.size(), &ctx); + + uint8 digest[16]; + hmacMD5_final(digest, &ctx); + uint32 pSig = *(uint32*)digest; + return pSig; + } + + assert_dbg(); + return 0; +} + +void prudpPacket::setData(uint8* data, sint32 length) +{ + packetData.resize(length); + memcpy(&packetData.front(), data, length); +} + +void prudpPacket::setFragmentIndex(uint8 fragmentIndex) +{ + this->fragmentIndex = fragmentIndex; +} + +uint8 prudpPacket::calculateChecksum(uint8* data, sint32 length) +{ + return prudp_calculateChecksum(streamSettings->checksumBase, data, length); +} + +prudpIncomingPacket::prudpIncomingPacket() +{ + src = 0; + dst = 0; + flags = 0; + type = 0; + sessionId = 0; + packetSignature = 0; + sequenceId = 0; + fragmentIndex = 0; + hasData = false; + isInvalid = false; + streamSettings = nullptr; +} + +prudpIncomingPacket::prudpIncomingPacket(prudpStreamSettings_t* streamSettings, uint8* data, sint32 length) : prudpIncomingPacket() +{ + if (length < 0xB + 1) + { + isInvalid = true; + return; + } + this->streamSettings = streamSettings; + // verify checksum first + uint8 actualChecksum = calculateChecksum(data, length - 1); + uint8 packetChecksum = *(uint8*)(data + length - 1); + if (actualChecksum != packetChecksum) + { + isInvalid = true; + return; + } + length--; // remove checksum from length + // verify constant header + this->src = *(uint8*)(data + 0x00); + this->dst = *(uint8*)(data + 0x01); + uint16 typeAndFlags = *(uint16*)(data + 0x02); + this->flags = (typeAndFlags >> 4) & 0xFFF; + this->type = (typeAndFlags & 0xF); + this->sessionId = *(uint8*)(data + 0x4); + this->packetSignature = *(uint32*)(data + 0x5); + this->sequenceId = *(uint16*)(data + 0x9); + // read dynamic fields + sint32 readIndex = 0xB; + if (this->type == prudpPacket::TYPE_SYN) + { + // SYN packet + if (readIndex < 4) + { + isInvalid = true; + return; + } + packetData.resize(4); + *(uint32*)(&packetData.front()) = *(uint32*)(data + readIndex); + hasData = true; + readIndex += 4; + // ignore FLAG_HAS_SIZE (would come here, usually with a value of 0) + return; + } + else if (this->type == prudpPacket::TYPE_CON) + { + // CON packet + if (readIndex < 4) + { + isInvalid = true; + return; + } + // this packet has another 4 byte signature but it's always zero? (value is ignored for now) + // ignore FLAG_HAS_SIZE + } + else if (this->type == prudpPacket::TYPE_PING) + { + // PING packet + // ignore payload + } + else if (this->type == prudpPacket::TYPE_DATA) + { + // can we assume that reliable data packets always need to have a unique sequence id? (Even if it's a multi-fragment frame) + // unreliable packets must have a sequence id too or how else would we know when to decrypt it? + + bool hasPayloadSize = (this->flags & prudpPacket::FLAG_HAS_SIZE) != 0; + // verify length + if ((length-readIndex) < 1+(hasPayloadSize?2:0)) + { + // too short + isInvalid = true; + return; + } + // read fragment index + this->fragmentIndex = *(uint8*)(data + readIndex); + readIndex += sizeof(uint8); + // read payload size (optional) + if (hasPayloadSize) + { + uint16 payloadSize = *(uint32*)(data + readIndex); + readIndex += sizeof(uint16); + // verify payload size + if ((length - readIndex) != payloadSize) + { + assert_dbg(); // mismatch, ignore packet or use specified payload size? + } + } + // read payload + if (readIndex < length) + { + sint32 dataSize = length - readIndex; + packetData.resize(dataSize); + memcpy(&packetData.front(), data + readIndex, dataSize); + } + return; + } + else if (this->type == prudpPacket::TYPE_DISCONNECT) + { + // DISCONNECT packet + // ignore payload + } + else + { +#ifndef PUBLIC_RELEASE + assert_dbg(); +#endif + } +} + +bool prudpIncomingPacket::hasError() +{ + return isInvalid; +} + +uint8 prudpIncomingPacket::calculateChecksum(uint8* data, sint32 length) +{ + return prudp_calculateChecksum(streamSettings->checksumBase, data, length); +} + +void prudpIncomingPacket::decrypt() +{ + if (packetData.empty()) + return; + RC4_transform(&streamSettings->rc4Server, &packetData.front(), (int)packetData.size(), &packetData.front()); +} + +#define PRUDP_VPORT(__streamType, __port) (((__streamType)<<4) | (__port)) + +prudpClient::prudpClient() +{ + currentConnectionState = STATE_CONNECTING; + serverConnectionSignature = 0; + clientConnectionSignature = 0; + hasSentCon = false; + outgoingSequenceId = 0; + incomingSequenceId = 0; + + clientSessionId = 0; + serverSessionId = 0; + + isSecureConnection = false; +} + +prudpClient::~prudpClient() +{ + if (srcPort != 0) + { + releasePRUDPPort(srcPort); + closesocket(socketUdp); + } +} + +prudpClient::prudpClient(uint32 dstIp, uint16 dstPort, const char* key) : prudpClient() +{ + this->dstIp = dstIp; + this->dstPort = dstPort; + // get unused random source port + for (sint32 tries = 0; tries < 5; tries++) + { + srcPort = getRandomSrcPRUDPPort(); + // create and bind udp socket + socketUdp = socket(AF_INET, SOCK_DGRAM, 0); + struct sockaddr_in udpServer; + udpServer.sin_family = AF_INET; + udpServer.sin_addr.s_addr = INADDR_ANY; + udpServer.sin_port = htons(srcPort); + if (bind(socketUdp, (struct sockaddr *)&udpServer, sizeof(udpServer)) == SOCKET_ERROR) + { + if (tries == 4) + { + forceLog_printf("PRUDP: Failed to bind UDP socket"); + currentConnectionState = STATE_DISCONNECTED; + srcPort = 0; + return; + } + releasePRUDPPort(srcPort); + closesocket(socketUdp); + continue; + } + else + break; + } + // set socket to non-blocking mode +#if BOOST_OS_WINDOWS > 0 + u_long nonBlockingMode = 1; // 1 to enable non-blocking socket + ioctlsocket(socketUdp, FIONBIO, &nonBlockingMode); +#else + int flags = fcntl(socketUdp, F_GETFL); + fcntl(socketUdp, F_SETFL, flags | O_NONBLOCK); +#endif + // generate frequently used parameters + this->vport_src = PRUDP_VPORT(prudpPacket::STREAM_TYPE_SECURE, 0xF); + this->vport_dst = PRUDP_VPORT(prudpPacket::STREAM_TYPE_SECURE, 0x1); + // set stream settings + uint8 checksumBase = 0; + for (sint32 i = 0; key[i] != '\0'; i++) + { + checksumBase += key[i]; + } + streamSettings.checksumBase = checksumBase; + MD5_CTX md5Ctx; + MD5_Init(&md5Ctx); + MD5_Update(&md5Ctx, key, (int)strlen(key)); + MD5_Final(streamSettings.accessKeyDigest, &md5Ctx); + // init stream ciphers + RC4_initCtx(&streamSettings.rc4Server, "CD&ML"); + RC4_initCtx(&streamSettings.rc4Client, "CD&ML"); + // send syn packet + prudpPacket* synPacket = new prudpPacket(&streamSettings, vport_src, vport_dst, prudpPacket::TYPE_SYN, prudpPacket::FLAG_NEED_ACK, 0, 0, 0); + queuePacket(synPacket, dstIp, dstPort); + outgoingSequenceId++; + // set incoming sequence id to 1 + incomingSequenceId = 1; +} + +prudpClient::prudpClient(uint32 dstIp, uint16 dstPort, const char* key, authServerInfo_t* authInfo) : prudpClient(dstIp, dstPort, key) +{ + RC4_initCtx(&streamSettings.rc4Server, authInfo->secureKey, 16); + RC4_initCtx(&streamSettings.rc4Client, authInfo->secureKey, 16); + this->isSecureConnection = true; + memcpy(&this->authInfo, authInfo, sizeof(authServerInfo_t)); +} + +bool prudpClient::isConnected() +{ + return currentConnectionState == STATE_CONNECTED; +} + +uint8 prudpClient::getConnectionState() +{ + return currentConnectionState; +} + +void prudpClient::acknowledgePacket(uint16 sequenceId) +{ + auto it = std::begin(list_packetsWithAckReq); + while (it != std::end(list_packetsWithAckReq)) + { + if (it->packet->sequenceId == sequenceId) + { + delete it->packet; + list_packetsWithAckReq.erase(it); + return; + } + it++; + } +} + +void prudpClient::sortIncomingDataPacket(prudpIncomingPacket* incomingPacket) +{ + uint16 sequenceIdIncomingPacket = incomingPacket->sequenceId; + // find insert index + sint32 insertIndex = 0; + while (insertIndex < queue_incomingPackets.size() ) + { + uint16 seqDif = sequenceIdIncomingPacket - queue_incomingPackets[insertIndex]->sequenceId; + if (seqDif&0x8000) + break; // negative seqDif -> insert before current element +#ifndef PUBLIC_RELEASE + if (seqDif == 0) + assert_dbg(); // same sequence id, sort by fragment index? +#endif + insertIndex++; + } + // insert + sint32 currentSize = (sint32)queue_incomingPackets.size(); + queue_incomingPackets.resize(currentSize+1); + for(sint32 i=currentSize; i>insertIndex; i--) + { + queue_incomingPackets[i] = queue_incomingPackets[i - 1]; + } + queue_incomingPackets[insertIndex] = incomingPacket; +} + +sint32 prudpClient::kerberosEncryptData(uint8* input, sint32 length, uint8* output) +{ + RC4Ctx_t rc4Kerberos; + RC4_initCtx(&rc4Kerberos, this->authInfo.secureKey, 16); + memcpy(output, input, length); + RC4_transform(&rc4Kerberos, output, length, output); + // calculate and append hmac + hmacMD5(this->authInfo.secureKey, 16, output, length, output+length); + return length + 16; +} + +void prudpClient::handleIncomingPacket(prudpIncomingPacket* incomingPacket) +{ + if (incomingPacket->flags&prudpPacket::FLAG_ACK) + { + // ack packet + acknowledgePacket(incomingPacket->sequenceId); + if ((incomingPacket->type == prudpPacket::TYPE_DATA || incomingPacket->type == prudpPacket::TYPE_PING) && incomingPacket->packetData.empty()) + { + // ack packet + delete incomingPacket; + return; + } + } + // special cases + if (incomingPacket->type == prudpPacket::TYPE_SYN) + { + if (hasSentCon == false && incomingPacket->hasData && incomingPacket->packetData.size() == 4) + { + this->serverConnectionSignature = *(uint32*)&incomingPacket->packetData.front(); + this->clientSessionId = prudp_generateRandomU8(); + // generate client session id + this->clientConnectionSignature = prudp_generateRandomU32(); + // send con packet + prudpPacket* conPacket = new prudpPacket(&streamSettings, vport_src, vport_dst, prudpPacket::TYPE_CON, prudpPacket::FLAG_NEED_ACK|prudpPacket::FLAG_RELIABLE, this->clientSessionId, outgoingSequenceId, serverConnectionSignature); + outgoingSequenceId++; + + if (this->isSecureConnection) + { + // set packet specific data (client connection signature) + uint8 tempBuffer[512]; + nexPacketBuffer conData(tempBuffer, sizeof(tempBuffer), true); + conData.writeU32(this->clientConnectionSignature); + conData.writeBuffer(authInfo.secureTicket, authInfo.secureTicketLength); + // encrypted request data + uint8 requestData[4 * 3]; + uint8 requestDataEncrypted[4 * 3 + 0x10]; + *(uint32*)(requestData + 0x0) = authInfo.userPid; + *(uint32*)(requestData + 0x4) = authInfo.server.cid; + *(uint32*)(requestData + 0x8) = prudp_generateRandomU32(); // todo - check value + sint32 encryptedSize = kerberosEncryptData(requestData, sizeof(requestData), requestDataEncrypted); + conData.writeBuffer(requestDataEncrypted, encryptedSize); + conPacket->setData(conData.getDataPtr(), conData.getWriteIndex()); + } + else + { + // set packet specific data (client connection signature) + conPacket->setData((uint8*)&this->clientConnectionSignature, sizeof(uint32)); + } + // sent packet + queuePacket(conPacket, dstIp, dstPort); + // remember con packet as sent + hasSentCon = true; + } + delete incomingPacket; + return; + } + else if (incomingPacket->type == prudpPacket::TYPE_CON) + { + // connected! + if (currentConnectionState == STATE_CONNECTING) + { + lastPingTimestamp = prudpGetMSTimestamp(); + serverSessionId = incomingPacket->sessionId; + currentConnectionState = STATE_CONNECTED; + //printf("Connection established. ClientSession %02x ServerSession %02x\n", clientSessionId, serverSessionId); + } + delete incomingPacket; + return; + } + else if (incomingPacket->type == prudpPacket::TYPE_DATA) + { + // verify some values + uint16 seqDist = incomingPacket->sequenceId - incomingSequenceId; + if (seqDist >= 0xC000) + { + // outdated + delete incomingPacket; + return; + } + // check if packet is already queued + for (auto& it : queue_incomingPackets) + { + if (it->sequenceId == incomingPacket->sequenceId) + { + // already queued (should check other values too, like packet type?) + forceLogDebug_printf("Duplicate PRUDP packet received"); + delete incomingPacket; + return; + } + } + // put into ordered receive queue + sortIncomingDataPacket(incomingPacket); + } + else if (incomingPacket->type == prudpPacket::TYPE_DISCONNECT) + { + currentConnectionState = STATE_DISCONNECTED; + return; + } + else + { + // ignore unknown packet + delete incomingPacket; + return; + } + + if (incomingPacket->flags&prudpPacket::FLAG_NEED_ACK && incomingPacket->type == prudpPacket::TYPE_DATA) + { + // send ack back + prudpPacket* ackPacket = new prudpPacket(&streamSettings, vport_src, vport_dst, prudpPacket::TYPE_DATA, prudpPacket::FLAG_ACK, this->clientSessionId, incomingPacket->sequenceId, 0); + queuePacket(ackPacket, dstIp, dstPort); + } +} + +bool prudpClient::update() +{ + if (currentConnectionState == STATE_DISCONNECTED) + { + return false; + } + uint32 currentTimestamp = prudpGetMSTimestamp(); + // check for incoming packets + uint8 receiveBuffer[4096]; + while (true) + { + sockaddr receiveFrom = { 0 }; + socklen_t receiveFromLen = sizeof(receiveFrom); + sint32 r = recvfrom(socketUdp, (char*)receiveBuffer, sizeof(receiveBuffer), 0, &receiveFrom, &receiveFromLen); + if (r >= 0) + { + //printf("RECV 0x%04x byte\n", r); + // todo: Verify sender (receiveFrom) + // calculate packet size + sint32 pIdx = 0; + while (pIdx < r) + { + sint32 packetLength = prudpPacket::calculateSizeFromPacketData(receiveBuffer + pIdx, r - pIdx); + if (packetLength <= 0 || (pIdx + packetLength) > r) + { + //printf("Invalid packet length\n"); + break; + } + prudpIncomingPacket* incomingPacket = new prudpIncomingPacket(&streamSettings, receiveBuffer + pIdx, packetLength); + pIdx += packetLength; + if (incomingPacket->hasError()) + { + delete incomingPacket; + break; + } + if (incomingPacket->type != prudpPacket::TYPE_CON && incomingPacket->sessionId != serverSessionId) + { + delete incomingPacket; + continue; // different session + } + handleIncomingPacket(incomingPacket); + } + } + else + break; + } + // check for ack timeouts + for (auto &it : list_packetsWithAckReq) + { + if ((currentTimestamp - it.lastRetryTimestamp) >= 2300) + { + if (it.retryCount >= 7) + { + // after too many retries consider the connection dead + currentConnectionState = STATE_DISCONNECTED; + } + // resend + directSendPacket(it.packet, dstIp, dstPort); + it.lastRetryTimestamp = currentTimestamp; + it.retryCount++; + } + } + // check if we need to send another ping + if (currentConnectionState == STATE_CONNECTED && (currentTimestamp - lastPingTimestamp) >= 20000) + { + // send ping + prudpPacket* pingPacket = new prudpPacket(&streamSettings, vport_src, vport_dst, prudpPacket::TYPE_PING, prudpPacket::FLAG_NEED_ACK | prudpPacket::FLAG_RELIABLE, this->clientSessionId, this->outgoingSequenceId, serverConnectionSignature); + this->outgoingSequenceId++; // increase since prudpPacket::FLAG_RELIABLE is set (note: official Wii U friends client sends ping packets without FLAG_RELIABLE) + queuePacket(pingPacket, dstIp, dstPort); + lastPingTimestamp = currentTimestamp; + } + return false; +} + +void prudpClient::directSendPacket(prudpPacket* packet, uint32 dstIp, uint16 dstPort) +{ + uint8 packetBuffer[prudpPacket::PACKET_RAW_SIZE_MAX]; + + sint32 len = packet->buildData(packetBuffer, prudpPacket::PACKET_RAW_SIZE_MAX); + + sockaddr_in destAddr; + destAddr.sin_family = AF_INET; + destAddr.sin_port = htons(dstPort); + destAddr.sin_addr.s_addr = dstIp; + sendto(socketUdp, (const char*)packetBuffer, len, 0, (const sockaddr*)&destAddr, sizeof(destAddr)); +} + +void prudpClient::queuePacket(prudpPacket* packet, uint32 dstIp, uint16 dstPort) +{ + if (packet->requiresAck()) + { + // remember this packet until we receive the ack + prudpAckRequired_t ackRequired = { 0 }; + ackRequired.packet = packet; + ackRequired.initialSendTimestamp = prudpGetMSTimestamp(); + ackRequired.lastRetryTimestamp = ackRequired.initialSendTimestamp; + list_packetsWithAckReq.push_back(ackRequired); + directSendPacket(packet, dstIp, dstPort); + } + else + { + directSendPacket(packet, dstIp, dstPort); + delete packet; + } +} + +void prudpClient::sendDatagram(uint8* input, sint32 length, bool reliable) +{ + if (reliable == false) + { + assert_dbg(); // todo + } + if (length >= 0x300) + assert_dbg(); // too long, need to split into multiple fragments + + // single fragment data packet + prudpPacket* packet = new prudpPacket(&streamSettings, vport_src, vport_dst, prudpPacket::TYPE_DATA, prudpPacket::FLAG_NEED_ACK | prudpPacket::FLAG_RELIABLE, clientSessionId, outgoingSequenceId, 0); + outgoingSequenceId++; + packet->setFragmentIndex(0); + packet->setData(input, length); + queuePacket(packet, dstIp, dstPort); +} + +uint16 prudpClient::getSourcePort() +{ + return this->srcPort; +} + +SOCKET prudpClient::getSocket() +{ + if (currentConnectionState == STATE_DISCONNECTED) + { + return INVALID_SOCKET; + } + return this->socketUdp; +} + +sint32 prudpClient::receiveDatagram(std::vector& outputBuffer) +{ + if (queue_incomingPackets.empty()) + return -1; + prudpIncomingPacket* incomingPacket = queue_incomingPackets[0]; + if (incomingPacket->sequenceId != this->incomingSequenceId) + return -1; + + if (incomingPacket->fragmentIndex == 0) + { + // single-fragment packet + // decrypt + incomingPacket->decrypt(); + // read data + sint32 datagramLen = (sint32)incomingPacket->packetData.size(); + if (datagramLen > 0) + { + // resize buffer if necessary + if (datagramLen > outputBuffer.size()) + outputBuffer.resize(datagramLen); + // to conserve memory we will also shrink the buffer if it was previously extended beyond 64KB + constexpr size_t BUFFER_TARGET_SIZE = 1024 * 64; + if (datagramLen < BUFFER_TARGET_SIZE && outputBuffer.size() > BUFFER_TARGET_SIZE) + outputBuffer.resize(BUFFER_TARGET_SIZE); + // copy datagram to buffer + memcpy(outputBuffer.data(), &incomingPacket->packetData.front(), datagramLen); + } + delete incomingPacket; + // remove packet from queue + sint32 size = (sint32)queue_incomingPackets.size(); + size--; + for (sint32 i = 0; i < size; i++) + { + queue_incomingPackets[i] = queue_incomingPackets[i + 1]; + } + queue_incomingPackets.resize(size); + // advance expected sequence id + this->incomingSequenceId++; + return datagramLen; + } + else + { + // multi-fragment packet + if (incomingPacket->fragmentIndex != 1) + return -1; // first packet of the chain not received yet + // verify chain + sint32 packetIndex = 1; + sint32 chainLength = -1; // if full chain found, set to count of packets + for(sint32 i=1; ifragmentIndex; + // sequence id must increase by 1 for every packet + if (queue_incomingPackets[packetIndex]->sequenceId != (this->incomingSequenceId+i) ) + return -1; // missing packets + // last fragment in chain is marked by fragment index 0 + if (itFragmentIndex == 0) + { + chainLength = i+1; + break; + } + packetIndex++; + } + if (chainLength < 1) + return -1; // chain not complete + // extract data from packet chain + sint32 writeIndex = 0; + for (sint32 i = 0; i < chainLength; i++) + { + incomingPacket = queue_incomingPackets[i]; + // decrypt + incomingPacket->decrypt(); + // extract data + sint32 datagramLen = (sint32)incomingPacket->packetData.size(); + if (datagramLen > 0) + { + // make sure output buffer can fit the data + if ((writeIndex + datagramLen) > outputBuffer.size()) + outputBuffer.resize(writeIndex + datagramLen + 4 * 1024); + memcpy(outputBuffer.data()+writeIndex, &incomingPacket->packetData.front(), datagramLen); + writeIndex += datagramLen; + } + // free packet memory + delete incomingPacket; + } + // remove packets from queue + sint32 size = (sint32)queue_incomingPackets.size(); + size -= chainLength; + for (sint32 i = 0; i < size; i++) + { + queue_incomingPackets[i] = queue_incomingPackets[i + chainLength]; + } + queue_incomingPackets.resize(size); + this->incomingSequenceId += chainLength; + return writeIndex; + } + return -1; +} diff --git a/src/Cemu/nex/prudp.h b/src/Cemu/nex/prudp.h new file mode 100644 index 00000000..aa68f4f6 --- /dev/null +++ b/src/Cemu/nex/prudp.h @@ -0,0 +1,200 @@ +#pragma once +#include "nexTypes.h" +#include "Common/socket.h" + +#define RC4_N 256 + +typedef struct +{ + unsigned char S[RC4_N]; + int i; + int j; +}RC4Ctx_t; + +void RC4_initCtx(RC4Ctx_t* rc4Ctx, char *key); +void RC4_initCtx(RC4Ctx_t* rc4Ctx, unsigned char* key, int keyLen); +void RC4_transform(RC4Ctx_t* rc4Ctx, unsigned char* input, int len, unsigned char* output); + +typedef struct +{ + uint8 checksumBase; // calculated from key + uint8 accessKeyDigest[16]; // MD5 hash of key + RC4Ctx_t rc4Client; + RC4Ctx_t rc4Server; +}prudpStreamSettings_t; + +typedef struct +{ + uint32 ip; + uint16 port; + sint32 cid; + sint32 pid; + sint32 sid; + sint32 stream; + sint32 type; +}stationUrl_t; + +typedef struct +{ + uint32 userPid; + uint8 secureKey[16]; + uint8 kerberosKey[16]; + uint8 secureTicket[1024]; + sint32 secureTicketLength; + stationUrl_t server; +}authServerInfo_t; + +uint8 prudp_calculateChecksum(uint8 checksumBase, uint8* data, sint32 length); + +class prudpPacket +{ +public: + static const int PACKET_RAW_SIZE_MAX = 500; + + static const int TYPE_SYN = 0; + static const int TYPE_CON = 1; + static const int TYPE_DATA = 2; + static const int TYPE_DISCONNECT = 3; + static const int TYPE_PING = 4; + + static const int STREAM_TYPE_SECURE = 0xA; + + static const int FLAG_ACK = 0x1; + static const int FLAG_RELIABLE = 0x2; // if this flag is set, increase sequenceId + static const int FLAG_NEED_ACK = 0x4; + static const int FLAG_HAS_SIZE = 0x8; + + static sint32 calculateSizeFromPacketData(uint8* data, sint32 length); + + prudpPacket(prudpStreamSettings_t* streamSettings, uint8 src, uint8 dst, uint8 type, uint16 flags, uint8 sessionId, uint16 sequenceId, uint32 packetSignature); + bool requiresAck(); + void setData(uint8* data, sint32 length); + void setFragmentIndex(uint8 fragmentIndex); + sint32 buildData(uint8* output, sint32 maxLength); + +private: + uint32 packetSignature(); + + uint8 calculateChecksum(uint8* data, sint32 length); + +public: + uint16 sequenceId; + +private: + uint8 src; + uint8 dst; + uint8 type; + uint8 fragmentIndex; + uint16 flags; + uint8 sessionId; + uint32 specifiedPacketSignature; + prudpStreamSettings_t* streamSettings; + std::vector packetData; + bool isEncrypted; +}; + +class prudpIncomingPacket +{ +public: + prudpIncomingPacket(prudpStreamSettings_t* streamSettings, uint8* data, sint32 length); + + bool hasError(); + + uint8 calculateChecksum(uint8* data, sint32 length); + void decrypt(); + +private: + prudpIncomingPacket(); + +public: + // prudp header + uint8 src; + uint8 dst; + uint16 flags; + uint8 type; + uint8 sessionId; + uint32 packetSignature; + uint16 sequenceId; + uint8 fragmentIndex; + bool hasData = false; + std::vector packetData; + +private: + bool isInvalid = false; + prudpStreamSettings_t* streamSettings = nullptr; + +}; + +typedef struct +{ + prudpPacket* packet; + uint32 initialSendTimestamp; + uint32 lastRetryTimestamp; + sint32 retryCount; +}prudpAckRequired_t; + +class prudpClient +{ +public: + static const int STATE_CONNECTING = 0; + static const int STATE_CONNECTED = 1; + static const int STATE_DISCONNECTED = 2; + +public: + prudpClient(uint32 dstIp, uint16 dstPort, const char* key); + prudpClient(uint32 dstIp, uint16 dstPort, const char* key, authServerInfo_t* authInfo); + ~prudpClient(); + + bool isConnected(); + + uint8 getConnectionState(); + void acknowledgePacket(uint16 sequenceId); + void sortIncomingDataPacket(prudpIncomingPacket* incomingPacket); + void handleIncomingPacket(prudpIncomingPacket* incomingPacket); + bool update(); // check for new incoming packets, returns true if receiveDatagram() should be called + + sint32 receiveDatagram(std::vector& outputBuffer); + void sendDatagram(uint8* input, sint32 length, bool reliable = true); + + uint16 getSourcePort(); + + SOCKET getSocket(); + +private: + prudpClient(); + void directSendPacket(prudpPacket* packet, uint32 dstIp, uint16 dstPort); + sint32 kerberosEncryptData(uint8* input, sint32 length, uint8* output); + void queuePacket(prudpPacket* packet, uint32 dstIp, uint16 dstPort); + +private: + uint16 srcPort; + uint32 dstIp; + uint16 dstPort; + uint8 vport_src; + uint8 vport_dst; + prudpStreamSettings_t streamSettings; + std::vector list_packetsWithAckReq; + std::vector queue_incomingPackets; + + // connection + uint8 currentConnectionState; + uint32 serverConnectionSignature; + uint32 clientConnectionSignature; + bool hasSentCon; + uint32 lastPingTimestamp; + + uint16 outgoingSequenceId; + uint16 incomingSequenceId; + + uint8 clientSessionId; + uint8 serverSessionId; + + // secure + bool isSecureConnection; + authServerInfo_t authInfo; + + // socket + SOCKET socketUdp; +}; + +uint32 prudpGetMSTimestamp(); \ No newline at end of file diff --git a/src/Common/CMakeLists.txt b/src/Common/CMakeLists.txt new file mode 100644 index 00000000..5af5898e --- /dev/null +++ b/src/Common/CMakeLists.txt @@ -0,0 +1,32 @@ +project(CemuCommon) + +#include_directories(".") + +file(GLOB CPP_FILES *.cpp) +file(GLOB H_FILES *.h) +add_library(CemuCommon ${CPP_FILES} ${H_FILES}) + +set_property(TARGET CemuCommon PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + +if(WIN32) +target_sources(CemuCommon +PRIVATE + windows/platform.cpp + windows/platform.h +) +else() +target_sources(CemuCommon +PRIVATE + linux/platform.cpp + linux/platform.h +) +endif() + +target_sources(CemuCommon + PRIVATE + ExceptionHandler/ExceptionHandler.cpp + ExceptionHandler/ExceptionHandler.h + ) + +target_precompile_headers(CemuCommon PUBLIC precompiled.h) +target_include_directories(CemuCommon PRIVATE ../) \ No newline at end of file diff --git a/src/Common/ExceptionHandler/ExceptionHandler.cpp b/src/Common/ExceptionHandler/ExceptionHandler.cpp new file mode 100644 index 00000000..6824a5a7 --- /dev/null +++ b/src/Common/ExceptionHandler/ExceptionHandler.cpp @@ -0,0 +1,446 @@ +#include "Common/precompiled.h" +#include "Cafe/CafeSystem.h" + +#if BOOST_OS_LINUX +#include +#include +#endif + +#if BOOST_OS_WINDOWS +#include +#include +#include + +#include "Config/ActiveSettings.h" +#include "Config/CemuConfig.h" +#include "Cafe/OS/libs/coreinit/coreinit_Thread.h" +#include "Cafe/HW/Espresso/PPCState.h" + +#endif + +extern uint32 currentBaseApplicationHash; +extern uint32 currentUpdatedApplicationHash; + +#if BOOST_OS_WINDOWS + +LONG handleException_SINGLE_STEP(PEXCEPTION_POINTERS pExceptionInfo) +{ + + return EXCEPTION_CONTINUE_SEARCH; +} + +void crashlog_writeHeader(const char* header) +{ + cemuLog_writePlainToLog("-----------------------------------------\n"); + + cemuLog_writePlainToLog(" "); + cemuLog_writePlainToLog(header); + cemuLog_writePlainToLog("\n"); + + cemuLog_writePlainToLog("-----------------------------------------\n"); +} + +bool crashLogCreated = false; +bool IsCemuhookLoaded(); +#include +BOOL CALLBACK MyMiniDumpCallback(PVOID pParam, const PMINIDUMP_CALLBACK_INPUT pInput, PMINIDUMP_CALLBACK_OUTPUT pOutput) +{ + if (!pInput || !pOutput) + return FALSE; + + switch (pInput->CallbackType) + { + case IncludeModuleCallback: + case IncludeThreadCallback: + case ThreadCallback: + case ThreadExCallback: + return TRUE; + + case ModuleCallback: + + if (!(pOutput->ModuleWriteFlags & ModuleReferencedByMemory)) + pOutput->ModuleWriteFlags &= ~ModuleWriteModule; + + return TRUE; + } + + return FALSE; + +} + +bool CreateMiniDump(CrashDump dump, EXCEPTION_POINTERS* pep) +{ + if (dump == CrashDump::Disabled) + return true; + + fs::path p = ActiveSettings::GetPath("crashdump"); + + std::error_code ec; + fs::create_directories(p, ec); + if (ec) + return false; + + const auto now = std::chrono::system_clock::now(); + const auto temp_time = std::chrono::system_clock::to_time_t(now); + const auto& time = *std::gmtime(&temp_time); + + p /= fmt::format("crash_{:04d}{:02d}{:02d}_{:02d}{:02d}{:02d}.dmp", 1900 + time.tm_year, time.tm_mon + 1, time.tm_mday, time.tm_year, time.tm_hour, time.tm_min, time.tm_sec); + + const auto hFile = CreateFileW(p.wstring().c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + if (hFile == INVALID_HANDLE_VALUE) + return false; + + MINIDUMP_EXCEPTION_INFORMATION mdei; + mdei.ThreadId = GetCurrentThreadId(); + mdei.ExceptionPointers = pep; + mdei.ClientPointers = FALSE; + + MINIDUMP_CALLBACK_INFORMATION mci; + mci.CallbackRoutine = (MINIDUMP_CALLBACK_ROUTINE)MyMiniDumpCallback; + mci.CallbackParam = nullptr; + + MINIDUMP_TYPE mdt; + if (dump == CrashDump::Full) + { + mdt = (MINIDUMP_TYPE)(MiniDumpWithPrivateReadWriteMemory | + MiniDumpWithDataSegs | + MiniDumpWithHandleData | + MiniDumpWithFullMemoryInfo | + MiniDumpWithThreadInfo | + MiniDumpWithUnloadedModules); + } + else + { + mdt = (MINIDUMP_TYPE)(MiniDumpWithIndirectlyReferencedMemory | MiniDumpScanMemory); + } + + const auto result = MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, mdt, &mdei, nullptr, &mci); + CloseHandle(hFile); + return result != FALSE; +} + +void DebugLogStackTrace(OSThread_t* thread, MPTR sp); + +void DumpThreadStackTrace() +{ + HANDLE process = GetCurrentProcess(); + SymInitialize(process, NULL, TRUE); + + char dumpLine[1024 * 4]; + void* stack[100]; + + const unsigned short frames = CaptureStackBackTrace(0, 40, stack, NULL); + SYMBOL_INFO* symbol = (SYMBOL_INFO*)calloc(sizeof(SYMBOL_INFO) + 256 * sizeof(char), 1); + symbol->MaxNameLen = 255; + symbol->SizeOfStruct = sizeof(SYMBOL_INFO); + + crashlog_writeHeader("Stack trace"); + for (unsigned int i = 0; i < frames; i++) + { + DWORD64 stackTraceOffset = (DWORD64)stack[i]; + SymFromAddr(process, stackTraceOffset, 0, symbol); + sprintf(dumpLine, "0x%016I64x ", (uint64)(size_t)stack[i]); + cemuLog_writePlainToLog(dumpLine); + // module name + HMODULE stackModule; + if (GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCSTR)stackTraceOffset, &stackModule)) + { + char moduleName[512]; + moduleName[0] = '\0'; + GetModuleFileNameA(stackModule, moduleName, 512); + sint32 moduleNameStartIndex = std::max((sint32)0, (sint32)strlen(moduleName) - 1); + while (moduleNameStartIndex > 0) + { + if (moduleName[moduleNameStartIndex] == '\\' || moduleName[moduleNameStartIndex] == '/') + { + moduleNameStartIndex++; + break; + } + moduleNameStartIndex--; + } + + DWORD64 moduleAddress = (DWORD64)GetModuleHandleA(moduleName); + sint32 relativeOffset = 0; + if (moduleAddress != 0) + relativeOffset = stackTraceOffset - moduleAddress; + + sprintf(dumpLine, "+0x%08x %-16s", relativeOffset, moduleName + moduleNameStartIndex); + cemuLog_writePlainToLog(dumpLine); + } + else + { + sprintf(dumpLine, "+0x00000000 %-16s", "NULL"); + cemuLog_writePlainToLog(dumpLine); + } + // function name + sprintf(dumpLine, " %s\n", symbol->Name); + cemuLog_writePlainToLog(dumpLine); + } + + free(symbol); +} + +void createCrashlog(EXCEPTION_POINTERS* e, PCONTEXT context) +{ + if (crashLogCreated) + return; // only create one crashlog + crashLogCreated = true; + + cemuLog_createLogFile(true); + + const auto crash_dump = GetConfig().crash_dump.GetValue(); + const auto dump_written = CreateMiniDump(crash_dump, e); + if (!dump_written) + cemuLog_writeLineToLog(fmt::format("couldn't write minidump {:#x}", GetLastError()), false, true); + + char dumpLine[1024 * 4]; + + // info about Cemu version + sprintf(dumpLine, "\nCrashlog for Cemu %d.%d%s\n", EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_SUFFIX); + cemuLog_writePlainToLog(dumpLine); + + SYSTEMTIME sysTime; + GetSystemTime(&sysTime); + sprintf(dumpLine, "Date: %02d-%02d-%04d %02d:%02d:%02d\n\n", (sint32)sysTime.wDay, (sint32)sysTime.wMonth, (sint32)sysTime.wYear, (sint32)sysTime.wHour, (sint32)sysTime.wMinute, (sint32)sysTime.wSecond); + cemuLog_writePlainToLog(dumpLine); + + DumpThreadStackTrace(); + // info about exception + if (e->ExceptionRecord) + { + HMODULE exceptionModule; + if (GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCSTR)(e->ExceptionRecord->ExceptionAddress), &exceptionModule)) + { + char moduleName[512]; + moduleName[0] = '\0'; + GetModuleFileNameA(exceptionModule, moduleName, 512); + sint32 moduleNameStartIndex = std::max((sint32)0, (sint32)strlen(moduleName) - 1); + while (moduleNameStartIndex > 0) + { + if (moduleName[moduleNameStartIndex] == '\\' || moduleName[moduleNameStartIndex] == '/') + { + moduleNameStartIndex++; + break; + } + moduleNameStartIndex--; + } + sprintf(dumpLine, "Exception 0x%08x at 0x%I64x(+0x%I64x) in module %s\n", (uint32)e->ExceptionRecord->ExceptionCode, (uint64)e->ExceptionRecord->ExceptionAddress, (uint64)e->ExceptionRecord->ExceptionAddress - (uint64)exceptionModule, moduleName + moduleNameStartIndex); + cemuLog_writePlainToLog(dumpLine); + } + else + { + sprintf(dumpLine, "Exception 0x%08x at 0x%I64x\n", (uint32)e->ExceptionRecord->ExceptionCode, (uint64)e->ExceptionRecord->ExceptionAddress); + cemuLog_writePlainToLog(dumpLine); + } + + } + sprintf(dumpLine, "cemu.exe at 0x%I64x\n", (uint64)GetModuleHandle(NULL)); + cemuLog_writePlainToLog(dumpLine); + // register info + sprintf(dumpLine, "\n"); + cemuLog_writePlainToLog(dumpLine); + sprintf(dumpLine, "RAX=%016I64x RBX=%016I64x RCX=%016I64x RDX=%016I64x\n", context->Rax, context->Rbx, context->Rcx, context->Rdx); + cemuLog_writePlainToLog(dumpLine); + sprintf(dumpLine, "RSP=%016I64x RBP=%016I64x RDI=%016I64x RSI=%016I64x\n", context->Rsp, context->Rbp, context->Rdi, context->Rsi); + cemuLog_writePlainToLog(dumpLine); + sprintf(dumpLine, "R8 =%016I64x R9 =%016I64x R10=%016I64x R11=%016I64x\n", context->R8, context->R9, context->R10, context->R11); + cemuLog_writePlainToLog(dumpLine); + sprintf(dumpLine, "R12=%016I64x R13=%016I64x R14=%016I64x R15=%016I64x\n", context->R12, context->R13, context->R14, context->R15); + cemuLog_writePlainToLog(dumpLine); + // info about game + cemuLog_writePlainToLog("\n"); + crashlog_writeHeader("Game info"); + if (CafeSystem::IsTitleRunning()) + { + cemuLog_writePlainToLog("Game: "); + cemuLog_writePlainToLog(CafeSystem::GetForegroundTitleName().c_str()); + cemuLog_writePlainToLog("\n"); + // title id + sprintf(dumpLine, "TitleId: %I64x\n", CafeSystem::GetForegroundTitleId()); + cemuLog_writePlainToLog(dumpLine); + // rpx hash + sprintf(dumpLine, "RPXHash: %x\n", currentBaseApplicationHash); + cemuLog_writePlainToLog(dumpLine); + } + else + { + cemuLog_writePlainToLog("Not running\n"); + } + // info about active PPC instance: + cemuLog_writePlainToLog("\n"); + crashlog_writeHeader("Active PPC instance"); + if (ppcInterpreterCurrentInstance) + { + OSThread_t* currentThread = coreinit::OSGetCurrentThread(); + uint32 threadPtr = memory_getVirtualOffsetFromPointer(coreinit::OSGetCurrentThread()); + sprintf(dumpLine, "IP 0x%08x LR 0x%08x Thread 0x%08x\n", ppcInterpreterCurrentInstance->instructionPointer, ppcInterpreterCurrentInstance->spr.LR, threadPtr); + cemuLog_writePlainToLog(dumpLine); + + // GPR info + sprintf(dumpLine, "\n"); + cemuLog_writePlainToLog(dumpLine); + auto gprs = ppcInterpreterCurrentInstance->gpr; + sprintf(dumpLine, "r0 =%08x r1 =%08x r2 =%08x r3 =%08x r4 =%08x r5 =%08x r6 =%08x r7 =%08x\n", gprs[0], gprs[1], gprs[2], gprs[3], gprs[4], gprs[5], gprs[6], gprs[7]); + cemuLog_writePlainToLog(dumpLine); + sprintf(dumpLine, "r8 =%08x r9 =%08x r10=%08x r11=%08x r12=%08x r13=%08x r14=%08x r15=%08x\n", gprs[8], gprs[9], gprs[10], gprs[11], gprs[12], gprs[13], gprs[14], gprs[15]); + cemuLog_writePlainToLog(dumpLine); + sprintf(dumpLine, "r16=%08x r17=%08x r18=%08x r19=%08x r20=%08x r21=%08x r22=%08x r23=%08x\n", gprs[16], gprs[17], gprs[18], gprs[19], gprs[20], gprs[21], gprs[22], gprs[23]); + cemuLog_writePlainToLog(dumpLine); + sprintf(dumpLine, "r24=%08x r25=%08x r26=%08x r27=%08x r28=%08x r29=%08x r30=%08x r31=%08x\n", gprs[24], gprs[25], gprs[26], gprs[27], gprs[28], gprs[29], gprs[30], gprs[31]); + cemuLog_writePlainToLog(dumpLine); + + // write line to log + cemuLog_writePlainToLog(dumpLine); + + // stack trace + MPTR currentStackVAddr = ppcInterpreterCurrentInstance->gpr[1]; + cemuLog_writePlainToLog("\n"); + crashlog_writeHeader("PPC stack trace"); + DebugLogStackTrace(currentThread, currentStackVAddr); + + // stack dump + cemuLog_writePlainToLog("\n"); + crashlog_writeHeader("PPC stack dump"); + for (uint32 i = 0; i < 16; i++) + { + MPTR lineAddr = currentStackVAddr + i * 8 * 4; + if (memory_isAddressRangeAccessible(lineAddr, 8 * 4)) + { + sprintf(dumpLine, "[0x%08x] %08x %08x %08x %08x - %08x %08x %08x %08x\n", lineAddr, memory_readU32(lineAddr + 0), memory_readU32(lineAddr + 4), memory_readU32(lineAddr + 8), memory_readU32(lineAddr + 12), memory_readU32(lineAddr + 16), memory_readU32(lineAddr + 20), memory_readU32(lineAddr + 24), memory_readU32(lineAddr + 28)); + cemuLog_writePlainToLog(dumpLine); + } + else + { + sprintf(dumpLine, "[0x%08x] ?\n", lineAddr); + cemuLog_writePlainToLog(dumpLine); + } + } + } + else + { + cemuLog_writePlainToLog("Not active\n"); + } + + // PPC thread log + cemuLog_writePlainToLog("\n"); + crashlog_writeHeader("PPC threads"); + if (activeThreadCount == 0) + { + cemuLog_writePlainToLog("None active\n"); + } + for (sint32 i = 0; i < activeThreadCount; i++) + { + MPTR threadItrMPTR = activeThread[i]; + OSThread_t* threadItrBE = (OSThread_t*)memory_getPointerFromVirtualOffset(threadItrMPTR); + + // get thread state + OSThread_t::THREAD_STATE threadState = threadItrBE->state; + const char* threadStateStr = "UNDEFINED"; + if (threadItrBE->suspendCounter != 0) + threadStateStr = "SUSPENDED"; + else if (threadState == OSThread_t::THREAD_STATE::STATE_NONE) + threadStateStr = "NONE"; + else if (threadState == OSThread_t::THREAD_STATE::STATE_READY) + threadStateStr = "READY"; + else if (threadState == OSThread_t::THREAD_STATE::STATE_RUNNING) + threadStateStr = "RUNNING"; + else if (threadState == OSThread_t::THREAD_STATE::STATE_WAITING) + threadStateStr = "WAITING"; + else if (threadState == OSThread_t::THREAD_STATE::STATE_MORIBUND) + threadStateStr = "MORIBUND"; + // generate log line + uint8 affinity = threadItrBE->attr; + sint32 effectivePriority = threadItrBE->effectivePriority; + const char* threadName = "NULL"; + if (!threadItrBE->threadName.IsNull()) + threadName = threadItrBE->threadName.GetPtr(); + sprintf(dumpLine, "%08x Ent %08x IP %08x LR %08x %-9s Aff %d%d%d Pri %2d Name %s\n", threadItrMPTR, _swapEndianU32(threadItrBE->entrypoint), threadItrBE->context.srr0, _swapEndianU32(threadItrBE->context.lr), threadStateStr, (affinity >> 0) & 1, (affinity >> 1) & 1, (affinity >> 2) & 1, effectivePriority, threadName); + // write line to log + cemuLog_writePlainToLog(dumpLine); + } + + cemuLog_waitForFlush(); + + // save log with the dump + if (dump_written && crash_dump != CrashDump::Disabled) + { + const auto now = std::chrono::system_clock::now(); + const auto temp_time = std::chrono::system_clock::to_time_t(now); + const auto& time = *std::gmtime(&temp_time); + + fs::path p = ActiveSettings::GetPath("crashdump"); + p /= fmt::format("log_{:04d}{:02d}{:02d}_{:02d}{:02d}{:02d}.txt", 1900 + time.tm_year, time.tm_mon + 1, time.tm_mday, time.tm_year, time.tm_hour, time.tm_min, time.tm_sec); + + std::error_code ec; + fs::copy_file(ActiveSettings::GetPath("log.txt"), p, ec); + } + + if (IsCemuhookLoaded()) + TerminateProcess(GetCurrentProcess(), 0); // abort(); + else + exit(0); + + return; +} + +bool logCrashlog; + +int crashlogThread(void* exceptionInfoRawPtr) +{ + PEXCEPTION_POINTERS pExceptionInfo = (PEXCEPTION_POINTERS)exceptionInfoRawPtr; + createCrashlog(pExceptionInfo, pExceptionInfo->ContextRecord); + logCrashlog = true; + return 0; +} + +void debugger_handleSingleStepException(uint32 drMask); + +LONG WINAPI VectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) +{ + if (pExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP) + { + LONG r = handleException_SINGLE_STEP(pExceptionInfo); + if (r != EXCEPTION_CONTINUE_SEARCH) + return r; + debugger_handleSingleStepException(pExceptionInfo->ContextRecord->Dr6 & 0xF); + return EXCEPTION_CONTINUE_EXECUTION; + } + return EXCEPTION_CONTINUE_SEARCH; +} + +LONG WINAPI cemu_unhandledExceptionFilter(EXCEPTION_POINTERS* pExceptionInfo) +{ + createCrashlog(pExceptionInfo, pExceptionInfo->ContextRecord); + return EXCEPTION_NONCONTINUABLE_EXCEPTION; +} + +void ExceptionHandler_init() +{ + SetUnhandledExceptionFilter(cemu_unhandledExceptionFilter); + AddVectoredExceptionHandler(1, VectoredExceptionHandler); + SetErrorMode(SEM_FAILCRITICALERRORS); +} +#else + +void handler_SIGSEGV(int sig) +{ + printf("SIGSEGV!\n"); + + void *array[32]; + size_t size; + + // get void*'s for all entries on the stack + size = backtrace(array, 32); + + // print out all the frames to stderr + fprintf(stderr, "Error: signal %d:\n", sig); + backtrace_symbols_fd(array, size, STDERR_FILENO); + exit(1); +} + +void ExceptionHandler_init() +{ + signal(SIGSEGV, handler_SIGSEGV); +} + +#endif \ No newline at end of file diff --git a/src/Common/ExceptionHandler/ExceptionHandler.h b/src/Common/ExceptionHandler/ExceptionHandler.h new file mode 100644 index 00000000..bb139ca2 --- /dev/null +++ b/src/Common/ExceptionHandler/ExceptionHandler.h @@ -0,0 +1,3 @@ +#pragma once + +void ExceptionHandler_init(); \ No newline at end of file diff --git a/src/Common/GLInclude/GLInclude.h b/src/Common/GLInclude/GLInclude.h new file mode 100644 index 00000000..7fcfa6f0 --- /dev/null +++ b/src/Common/GLInclude/GLInclude.h @@ -0,0 +1,42 @@ +#pragma once + +#include "glext.h" + +#if BOOST_OS_WINDOWS > 0 +#include "wglext.h" +#endif + +#if BOOST_OS_LINUX > 0 + +// from Xlib +#define Bool int +#define Status int +#define True 1 +#define False 0 + +// from system glx.h +typedef XID GLXContextID; +typedef XID GLXPixmap; +typedef XID GLXDrawable; +typedef XID GLXPbuffer; +typedef XID GLXWindow; +typedef XID GLXFBConfigID; +typedef struct __GLXcontextRec *GLXContext; +typedef struct __GLXFBConfigRec *GLXFBConfig; + +#include "glxext.h" + +#undef Bool +#undef Status +#undef True +#undef False + +#endif + +#define GLFUNC(__type, __name) extern __type __name; +#include "glFunctions.h" +#undef GLFUNC + +// this prevents Windows GL.h from being included: +#define __gl_h_ +#define __GL_H__ \ No newline at end of file diff --git a/src/Common/GLInclude/glFunctions.h b/src/Common/GLInclude/glFunctions.h new file mode 100644 index 00000000..e69928d1 --- /dev/null +++ b/src/Common/GLInclude/glFunctions.h @@ -0,0 +1,290 @@ +// OpenGL 1.1 - 2.0 +GLFUNC(PFNGLDRAWBUFFERPROC, glDrawBuffer) +GLFUNC(PFNGLGENTEXTURESPROC, glGenTextures) +GLFUNC(PFNGLDELETETEXTURESPROC, glDeleteTextures) +GLFUNC(PFNGLBINDTEXTUREPROC, glBindTexture) +GLFUNC(PFNGLTEXPARAMETERIPROC, glTexParameteri) +GLFUNC(PFNGLTEXIMAGE2DPROC, glTexImage2D) +GLFUNC(PFNGLTEXSUBIMAGE2DPROC, glTexSubImage2D) +GLFUNC(PFNGLTEXIMAGE1DPROC, glTexImage1D) +GLFUNC(PFNGLTEXSUBIMAGE1DPROC, glTexSubImage1D) +GLFUNC(PFNGLGETTEXIMAGEPROC, glGetTexImage) +GLFUNC(PFNGLENABLEPROC, glEnable) +GLFUNC(PFNGLDISABLEPROC, glDisable) +GLFUNC(PFNGLISENABLEDPROC, glIsEnabled) +GLFUNC(PFNGLCLEARPROC, glClear) +GLFUNC(PFNGLCLEARCOLORPROC, glClearColor) +GLFUNC(PFNGLCLEARDEPTHPROC, glClearDepth) +GLFUNC(PFNGLCLEARSTENCILPROC, glClearStencil) +GLFUNC(PFNGLFLUSHPROC, glFlush) +GLFUNC(PFNGLFINISHPROC, glFinish) +GLFUNC(PFNGLPIXELSTOREIPROC, glPixelStorei) +GLFUNC(PFNGLGETSTRINGPROC, glGetString) +GLFUNC(PFNGLGETINTEGERVPROC, glGetIntegerv) +GLFUNC(PFNGLGETTEXLEVELPARAMETERIVPROC, glGetTexLevelParameteriv) +GLFUNC(PFNGLTEXPARAMETERFPROC, glTexParameterf) +GLFUNC(PFNGLTEXPARAMETERFVPROC, glTexParameterfv) +GLFUNC(PFNGLDEPTHFUNCPROC, glDepthFunc) +GLFUNC(PFNGLDEPTHMASKPROC, glDepthMask) +GLFUNC(PFNGLDEPTHRANGEPROC, glDepthRange) +GLFUNC(PFNGLFRONTFACEPROC, glFrontFace) +GLFUNC(PFNGLCULLFACEPROC, glCullFace) +GLFUNC(PFNGLPOLYGONOFFSETPROC, glPolygonOffset) +GLFUNC(PFNGLPOINTSIZEPROC, glPointSize) +GLFUNC(PFNGLLOGICOPPROC, glLogicOp) +GLFUNC(PFNGLPOLYGONMODEPROC, glPolygonMode) +GLFUNC(PFNGLSCISSORPROC, glScissor) +GLFUNC(PFNGLVIEWPORTPROC, glViewport) +GLFUNC(PFNGLGETERRORPROC, glGetError) +GLFUNC(PFNGLDRAWARRAYSPROC, glDrawArrays) + +// everything else + +GLFUNC(PFNGLUNIFORM1IPROC, glUniform1i) +GLFUNC(PFNGLUNIFORM2IPROC, glUniform2i) +GLFUNC(PFNGLUNIFORM2FPROC, glUniform2f) +GLFUNC(PFNGLUNIFORM4FVPROC, glUniform4fv) +GLFUNC(PFNGLUNIFORMMATRIX4FVPROC, glUniformMatrix4fv) + + +GLFUNC(PFNGLGETSHADERINFOLOGPROC, glGetShaderInfoLog) + +GLFUNC(PFNGLUSEPROGRAMPROC, glUseProgram) + +GLFUNC(PFNGLGETUNIFORMLOCATIONPROC, glGetUniformLocation) + +GLFUNC(PFNGLACTIVETEXTUREPROC, glActiveTexture) +GLFUNC(PFNGLCLIENTACTIVETEXTUREPROC, glClientActiveTexture) + +GLFUNC(PFNGLPRIMITIVERESTARTINDEXPROC, glPrimitiveRestartIndex) + +GLFUNC(PFNGLDRAWRANGEELEMENTSPROC, glDrawRangeElements) +GLFUNC(PFNGLDRAWRANGEELEMENTSBASEVERTEXPROC, glDrawRangeElementsBaseVertex) + +GLFUNC(PFNGLGENBUFFERSPROC, glGenBuffers) +GLFUNC(PFNGLBINDBUFFERPROC, glBindBuffer) +GLFUNC(PFNGLBUFFERDATAPROC, glBufferData) +GLFUNC(PFNGLBUFFERSUBDATAPROC, glBufferSubData) +GLFUNC(PFNGLMAPBUFFERPROC, glMapBuffer) +GLFUNC(PFNGLUNMAPBUFFERPROC, glUnmapBuffer) +GLFUNC(PFNGLDELETEBUFFERSARBPROC, glDeleteBuffers) + +GLFUNC(PFNGLENABLEVERTEXATTRIBARRAYPROC, glEnableVertexAttribArray) +GLFUNC(PFNGLDISABLEVERTEXATTRIBARRAYPROC, glDisableVertexAttribArray) +GLFUNC(PFNGLGETATTRIBLOCATIONPROC, glGetAttribLocation) +GLFUNC(PFNGLBINDATTRIBLOCATIONPROC, glBindAttribLocation) +GLFUNC(PFNGLVERTEXATTRIBPOINTERPROC, glVertexAttribPointer) +GLFUNC(PFNGLVERTEXATTRIBIPOINTERPROC, glVertexAttribIPointer) + +GLFUNC(PFNGLBLENDEQUATIONPROC, glBlendEquation) +GLFUNC(PFNGLBLENDFUNCSEPARATEEXTPROC, glBlendFuncSeparate) +GLFUNC(PFNGLBLENDEQUATIONSEPARATEEXTPROC, glBlendEquationSeparate) +GLFUNC(PFNGLBLENDEQUATIONSEPARATEIPROC, glBlendEquationSeparatei) +GLFUNC(PFNGLBLENDFUNCSEPARATEIPROC, glBlendFuncSeparatei) +GLFUNC(PFNGLBLENDCOLORPROC, glBlendColor) + +GLFUNC(PFNGLDRAWBUFFERSPROC, glDrawBuffers) +GLFUNC(PFNGLCLAMPCOLORARBPROC, glClampColor) + +GLFUNC(PFNGLBINDFRAGDATALOCATIONEXTPROC, glBindFragDataLocation) + +GLFUNC(PFNGLSHADERSOURCEPROC, glShaderSource) +GLFUNC(PFNGLCOMPILESHADERPROC, glCompileShader) +GLFUNC(PFNGLATTACHSHADERPROC, glAttachShader) +GLFUNC(PFNGLLINKPROGRAMPROC, glLinkProgram) +GLFUNC(PFNGLGETSHADERIVPROC, glGetShaderiv) +GLFUNC(PFNGLGETPROGRAMIVPROC, glGetProgramiv) +GLFUNC(PFNGLGETPROGRAMINFOLOGPROC, glGetProgramInfoLog) +GLFUNC(PFNGLGETPROGRAMBINARYPROC, glGetProgramBinary) +GLFUNC(PFNGLPROGRAMBINARYPROC, glProgramBinary) +GLFUNC(PFNGLPROGRAMPARAMETERIPROC, glProgramParameteri) +GLFUNC(PFNGLCREATEPROGRAMPROC, glCreateProgram) +GLFUNC(PFNGLCREATESHADERPROC, glCreateShader) +GLFUNC(PFNGLDELETEPROGRAMPROC, glDeleteProgram) +GLFUNC(PFNGLDELETESHADERPROC, glDeleteShader) +GLFUNC(PFNGLDETACHSHADERPROC, glDetachShader) +GLFUNC(PFNGLGETATTACHEDSHADERSPROC, glGetAttachedShaders) + +GLFUNC(PFNGLCREATESHADERPROGRAMVPROC, glCreateShaderProgramv) +GLFUNC(PFNGLUSEPROGRAMSTAGESPROC, glUseProgramStages) +GLFUNC(PFNGLBINDPROGRAMPIPELINEPROC, glBindProgramPipeline) +GLFUNC(PFNGLGENPROGRAMPIPELINESPROC, glGenProgramPipelines) +GLFUNC(PFNGLACTIVESHADERPROGRAMPROC, glActiveShaderProgram) +GLFUNC(PFNGLDELETEPROGRAMPIPELINESPROC, glDeleteProgramPipelines) + +GLFUNC(PFNGLPROGRAMUNIFORM1IPROC, glProgramUniform1i) +GLFUNC(PFNGLPROGRAMUNIFORM2IPROC, glProgramUniform2i) +GLFUNC(PFNGLPROGRAMUNIFORM1IVPROC, glProgramUniform1iv) +GLFUNC(PFNGLPROGRAMUNIFORM4IVPROC, glProgramUniform4iv) +GLFUNC(PFNGLPROGRAMUNIFORM1FVPROC, glProgramUniform1fv) +GLFUNC(PFNGLPROGRAMUNIFORM2FVPROC, glProgramUniform2fv) + +// FBO + +GLFUNC(PFNGLGENFRAMEBUFFERSPROC, glGenFramebuffers) +GLFUNC(PFNGLBINDFRAMEBUFFERPROC, glBindFramebuffer) +GLFUNC(PFNGLFRAMEBUFFERTEXTURE2DPROC, glFramebufferTexture2D) +GLFUNC(PFNGLFRAMEBUFFERTEXTURELAYERPROC, glFramebufferTextureLayer) +GLFUNC(PFNGLNAMEDFRAMEBUFFERTEXTUREPROC, glNamedFramebufferTexture) +GLFUNC(PFNGLCHECKFRAMEBUFFERSTATUSPROC, glCheckFramebufferStatus) +GLFUNC(PFNGLINVALIDATEFRAMEBUFFERPROC, glInvalidateFramebuffer) +GLFUNC(PFNGLDELETEFRAMEBUFFERSPROC, glDeleteFramebuffers) + +//GLFUNC(PFNGLNAMEDFRAMEBUFFERTEXTUREPROC, glNamedFramebufferTexture) +GLFUNC(PFNGLNAMEDFRAMEBUFFERTEXTURE2DEXTPROC, glNamedFramebufferTexture2DEXT) +GLFUNC(PFNGLNAMEDFRAMEBUFFERTEXTURELAYERPROC, glNamedFramebufferTextureLayer) + +GLFUNC(PFNGLENABLEIPROC, glEnablei) +GLFUNC(PFNGLDISABLEIPROC, glDisablei) + +GLFUNC(PFNGLBEGINQUERYINDEXEDPROC, glBeginQueryIndexed) +GLFUNC(PFNGLENDQUERYINDEXEDPROC, glEndQueryIndexed) +GLFUNC(PFNGLGETQUERYINDEXEDIVPROC, glGetQueryIndexediv) +GLFUNC(PFNGLGETQUERYOBJECTI64VPROC, glGetQueryObjecti64v) + +GLFUNC(PFNGLGENQUERIESPROC, glGenQueries) +GLFUNC(PFNGLDELETEQUERIESPROC, glDeleteQueries) +GLFUNC(PFNGLQUERYCOUNTERPROC, glQueryCounter) +GLFUNC(PFNGLGETQUERYOBJECTIVPROC, glGetQueryObjectiv) + +// FBO DSA + +GLFUNC(PFNGLCREATEFRAMEBUFFERSPROC, glCreateFramebuffers) +GLFUNC(PFNGLINVALIDATENAMEDFRAMEBUFFERDATAPROC, glInvalidateNamedFramebufferData) + + +// misc + +GLFUNC(PFNGLCOLORMASKIPROC, glColorMaski) +GLFUNC(PFNGLTEXTUREBARRIERPROC, glTextureBarrier) +GLFUNC(PFNGLCLIPCONTROLPROC, glClipControl) +GLFUNC(PFNGLVIEWPORTINDEXEDFPROC, glViewportIndexedf) +GLFUNC(PFNGLMAXSHADERCOMPILERTHREADSARBPROC, glMaxShaderCompilerThreadsARB) +GLFUNC(PFNGLDEPTHRANGEDNVPROC, glDepthRangedNV) +GLFUNC(PFNGLPOLYGONOFFSETCLAMPEXTPROC, glPolygonOffsetClampEXT) + +// texture + +GLFUNC(PFNGLTEXTUREVIEWPROC, glTextureView) +GLFUNC(PFNGLTEXSTORAGE1DPROC, glTexStorage1D) +GLFUNC(PFNGLTEXSTORAGE2DPROC, glTexStorage2D) +GLFUNC(PFNGLTEXSTORAGE3DPROC, glTexStorage3D) +GLFUNC(PFNGLTEXIMAGE3DPROC, glTexImage3D) +GLFUNC(PFNGLTEXSUBIMAGE3DPROC, glTexSubImage3D) +GLFUNC(PFNGLCOMPRESSEDTEXIMAGE2DPROC, glCompressedTexImage2D) +GLFUNC(PFNGLCOMPRESSEDTEXIMAGE3DPROC, glCompressedTexImage3D) +GLFUNC(PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC, glCompressedTexSubImage2D) +GLFUNC(PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC, glCompressedTexSubImage3D) +GLFUNC(PFNGLCOMPRESSEDTEXTURESUBIMAGE2DPROC, glCompressedTextureSubImage2D) +GLFUNC(PFNGLCOMPRESSEDTEXTURESUBIMAGE3DPROC, glCompressedTextureSubImage3D) +GLFUNC(PFNGLCOPYIMAGESUBDATAPROC, glCopyImageSubData) +GLFUNC(PFNGLCLEARTEXIMAGEPROC, glClearTexImage) +GLFUNC(PFNGLCLEARTEXSUBIMAGEPROC, glClearTexSubImage) +GLFUNC(PFNGLINVALIDATETEXIMAGEPROC, glInvalidateTexImage) + +// texture DSA + +GLFUNC(PFNGLBINDTEXTUREUNITPROC, glBindTextureUnit) +GLFUNC(PFNGLGETTEXTURELEVELPARAMETERIVPROC, glGetTextureLevelParameteriv) +GLFUNC(PFNGLTEXTUREPARAMETERIPROC, glTextureParameteri) +GLFUNC(PFNGLGETTEXTURESUBIMAGEPROC, glGetTextureSubImage) +GLFUNC(PFNGLTEXTURESUBIMAGE2DPROC, glTextureSubImage2D); +GLFUNC(PFNGLTEXTURESUBIMAGE3DPROC, glTextureSubImage3D) + +// instancing / draw + +GLFUNC(PFNGLDRAWELEMENTSBASEVERTEXPROC, glDrawElementsBaseVertex) +GLFUNC(PFNGLDRAWELEMENTSINSTANCEDBASEVERTEXBASEINSTANCEPROC, glDrawElementsInstancedBaseVertexBaseInstance) +GLFUNC(PFNGLDRAWARRAYSINSTANCEDPROC, glDrawArraysInstanced) + +// vertex array + +GLFUNC(PFNGLGENVERTEXARRAYSPROC, glGenVertexArrays) +GLFUNC(PFNGLBINDVERTEXARRAYPROC, glBindVertexArray) +GLFUNC(PFNGLDELETEVERTEXARRAYSPROC, glDeleteVertexArrays) +GLFUNC(PFNGLBINDVERTEXBUFFERPROC, glBindVertexBuffer) +GLFUNC(PFNGLVERTEXATTRIBFORMATPROC, glVertexAttribFormat) +GLFUNC(PFNGLVERTEXATTRIBIFORMATPROC, glVertexAttribIFormat) +GLFUNC(PFNGLVERTEXATTRIBBINDINGPROC, glVertexAttribBinding) +GLFUNC(PFNGLVERTEXBINDINGDIVISORPROC, glVertexBindingDivisor) +GLFUNC(PFNGLVERTEXATTRIBDIVISORPROC, glVertexAttribDivisor) + +// vertex array DSA + +GLFUNC(PFNGLCREATEVERTEXARRAYSPROC, glCreateVertexArrays) +GLFUNC(PFNGLDISABLEVERTEXARRAYATTRIBPROC, glDisableVertexArrayAttrib) +GLFUNC(PFNGLENABLEVERTEXARRAYATTRIBPROC, glEnableVertexArrayAttrib) +GLFUNC(PFNGLVERTEXARRAYELEMENTBUFFERPROC, glVertexArrayElementBuffer) +GLFUNC(PFNGLVERTEXARRAYVERTEXBUFFERPROC, glVertexArrayVertexBuffer) +GLFUNC(PFNGLVERTEXARRAYATTRIBBINDINGPROC, glVertexArrayAttribBinding) +GLFUNC(PFNGLVERTEXARRAYATTRIBIFORMATPROC, glVertexArrayAttribIFormat) +GLFUNC(PFNGLVERTEXARRAYBINDINGDIVISORPROC, glVertexArrayBindingDivisor) + +// sampler + +GLFUNC(PFNGLGENSAMPLERSPROC, glGenSamplers) +GLFUNC(PFNGLBINDSAMPLERPROC, glBindSampler) +GLFUNC(PFNGLSAMPLERPARAMETERIPROC, glSamplerParameteri) +GLFUNC(PFNGLSAMPLERPARAMETERFPROC, glSamplerParameterf) +GLFUNC(PFNGLSAMPLERPARAMETERIVPROC, glSamplerParameteriv) +GLFUNC(PFNGLSAMPLERPARAMETERFVPROC, glSamplerParameterfv) + +// buffer object + +GLFUNC(PFNGLGETUNIFORMBLOCKINDEXPROC, glGetUniformBlockIndex) +GLFUNC(PFNGLUNIFORMBLOCKBINDINGPROC, glUniformBlockBinding) +GLFUNC(PFNGLBINDBUFFERBASEPROC, glBindBufferBase) +GLFUNC(PFNGLBINDBUFFERRANGEPROC, glBindBufferRange) +GLFUNC(PFNGLGETBUFFERSUBDATAPROC, glGetBufferSubData) + +// uniform storage buffer object + +GLFUNC(PFNGLGETPROGRAMRESOURCEINDEXPROC, glGetProgramResourceIndex) +GLFUNC(PFNGLSHADERSTORAGEBLOCKBINDINGPROC, glShaderStorageBlockBinding) + +// stencil + +GLFUNC(PFNGLSTENCILOPSEPARATEPROC, glStencilOpSeparate) +GLFUNC(PFNGLSTENCILFUNCSEPARATEPROC, glStencilFuncSeparate) +GLFUNC(PFNGLSTENCILMASKSEPARATEPROC, glStencilMaskSeparate) + +// buffer + +GLFUNC(PFNGLCREATEBUFFERSPROC, glCreateBuffers) +GLFUNC(PFNGLBUFFERSTORAGEPROC, glBufferStorage) +GLFUNC(PFNGLNAMEDBUFFERSTORAGEPROC, glNamedBufferStorage) +GLFUNC(PFNGLMAPNAMEDBUFFERPROC, glMapNamedBuffer) +GLFUNC(PFNGLMAPNAMEDBUFFERRANGEPROC, glMapNamedBufferRange) +GLFUNC(PFNGLMAPBUFFERRANGEPROC, glMapBufferRange) +GLFUNC(PFNGLFLUSHMAPPEDBUFFERRANGEPROC, glFlushMappedBufferRange) +GLFUNC(PFNGLMEMORYBARRIERPROC, glMemoryBarrier) +GLFUNC(PFNGLCOPYBUFFERSUBDATAPROC, glCopyBufferSubData) +GLFUNC(PFNGLCOPYNAMEDBUFFERSUBDATAPROC, glCopyNamedBufferSubData) +GLFUNC(PFNGLNAMEDBUFFERSUBDATAPROC, glNamedBufferSubData) +GLFUNC(PFNGLGETNAMEDBUFFERSUBDATAPROC, glGetNamedBufferSubData); + +// transform feedback + +GLFUNC(PFNGLBEGINTRANSFORMFEEDBACKPROC, glBeginTransformFeedback) +GLFUNC(PFNGLENDTRANSFORMFEEDBACKPROC, glEndTransformFeedback) +GLFUNC(PFNGLTRANSFORMFEEDBACKVARYINGSPROC, glTransformFeedbackVaryings) + +// sync / fence + +GLFUNC(PFNGLFENCESYNCPROC, glFenceSync) +GLFUNC(PFNGLCLIENTWAITSYNCPROC, glClientWaitSync) +GLFUNC(PFNGLDELETESYNCPROC, glDeleteSync) + +// debugging + +GLFUNC(PFNGLOBJECTLABELPROC, glObjectLabel) +GLFUNC(PFNGLDEBUGMESSAGECALLBACKPROC, glDebugMessageCallback) +GLFUNC(PFNGLDEBUGMESSAGECONTROLPROC, glDebugMessageControl) + +// wgl + +#if BOOST_OS_WINDOWS > 0 +GLFUNC(PFNWGLSWAPINTERVALEXTPROC, wglSwapIntervalEXT) +#endif + +// x \ No newline at end of file diff --git a/src/Common/GLInclude/glext.h b/src/Common/GLInclude/glext.h new file mode 100644 index 00000000..3c5c24fb --- /dev/null +++ b/src/Common/GLInclude/glext.h @@ -0,0 +1,14121 @@ +#ifndef __gl_glext_custom_h_ +#define __gl_glext_custom_h_ 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/* +** Copyright 2013-2020 The Khronos Group Inc. +** SPDX-License-Identifier: MIT +** +** This header is generated from the Khronos OpenGL / OpenGL ES XML +** API Registry. The current version of the Registry, generator scripts +** used to make the header, and the header can be found at +** https://github.com/KhronosGroup/OpenGL-Registry +*/ + +#if defined(_WIN32) && !defined(APIENTRY) && !defined(__CYGWIN__) && !defined(__SCITECH_SNAP__) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#include +#endif + +#ifndef APIENTRY +#define APIENTRY +#endif +#ifndef APIENTRYP +#define APIENTRYP APIENTRY * +#endif +#ifndef GLAPI +#define GLAPI extern +#endif + +#define GL_GLEXT_VERSION 20220309 + +#include "khrplatform.h" + +/* Generated C header for: + * API: gl + * Profile: compatibility + * Versions considered: .* + * Versions emitted: .* + * Default extensions included: gl + * Additional extensions included: _nomatch_^ + * Extensions removed: _nomatch_^ + */ + +#ifndef GL_VERSION_1_0 +#define GL_VERSION_1_0 1 +typedef void GLvoid; +typedef unsigned int GLenum; +typedef khronos_float_t GLfloat; +typedef int GLint; +typedef int GLsizei; +typedef unsigned int GLbitfield; +typedef double GLdouble; +typedef unsigned int GLuint; +typedef unsigned char GLboolean; +typedef khronos_uint8_t GLubyte; +typedef khronos_int8_t GLbyte; +typedef khronos_int16_t GLshort; +typedef khronos_uint16_t GLushort; +#define GL_DEPTH_BUFFER_BIT 0x00000100 +#define GL_STENCIL_BUFFER_BIT 0x00000400 +#define GL_COLOR_BUFFER_BIT 0x00004000 +#define GL_FALSE 0 +#define GL_TRUE 1 +#define GL_POINTS 0x0000 +#define GL_LINES 0x0001 +#define GL_LINE_LOOP 0x0002 +#define GL_LINE_STRIP 0x0003 +#define GL_TRIANGLES 0x0004 +#define GL_TRIANGLE_STRIP 0x0005 +#define GL_TRIANGLE_FAN 0x0006 +#define GL_QUADS 0x0007 +#define GL_NEVER 0x0200 +#define GL_LESS 0x0201 +#define GL_EQUAL 0x0202 +#define GL_LEQUAL 0x0203 +#define GL_GREATER 0x0204 +#define GL_NOTEQUAL 0x0205 +#define GL_GEQUAL 0x0206 +#define GL_ALWAYS 0x0207 +#define GL_ZERO 0 +#define GL_ONE 1 +#define GL_SRC_COLOR 0x0300 +#define GL_ONE_MINUS_SRC_COLOR 0x0301 +#define GL_SRC_ALPHA 0x0302 +#define GL_ONE_MINUS_SRC_ALPHA 0x0303 +#define GL_DST_ALPHA 0x0304 +#define GL_ONE_MINUS_DST_ALPHA 0x0305 +#define GL_DST_COLOR 0x0306 +#define GL_ONE_MINUS_DST_COLOR 0x0307 +#define GL_SRC_ALPHA_SATURATE 0x0308 +#define GL_NONE 0 +#define GL_FRONT_LEFT 0x0400 +#define GL_FRONT_RIGHT 0x0401 +#define GL_BACK_LEFT 0x0402 +#define GL_BACK_RIGHT 0x0403 +#define GL_FRONT 0x0404 +#define GL_BACK 0x0405 +#define GL_LEFT 0x0406 +#define GL_RIGHT 0x0407 +#define GL_FRONT_AND_BACK 0x0408 +#define GL_NO_ERROR 0 +#define GL_INVALID_ENUM 0x0500 +#define GL_INVALID_VALUE 0x0501 +#define GL_INVALID_OPERATION 0x0502 +#define GL_OUT_OF_MEMORY 0x0505 +#define GL_CW 0x0900 +#define GL_CCW 0x0901 +#define GL_POINT_SIZE 0x0B11 +#define GL_POINT_SIZE_RANGE 0x0B12 +#define GL_POINT_SIZE_GRANULARITY 0x0B13 +#define GL_LINE_SMOOTH 0x0B20 +#define GL_LINE_WIDTH 0x0B21 +#define GL_LINE_WIDTH_RANGE 0x0B22 +#define GL_LINE_WIDTH_GRANULARITY 0x0B23 +#define GL_POLYGON_MODE 0x0B40 +#define GL_POLYGON_SMOOTH 0x0B41 +#define GL_CULL_FACE 0x0B44 +#define GL_CULL_FACE_MODE 0x0B45 +#define GL_FRONT_FACE 0x0B46 +#define GL_DEPTH_RANGE 0x0B70 +#define GL_DEPTH_TEST 0x0B71 +#define GL_DEPTH_WRITEMASK 0x0B72 +#define GL_DEPTH_CLEAR_VALUE 0x0B73 +#define GL_DEPTH_FUNC 0x0B74 +#define GL_STENCIL_TEST 0x0B90 +#define GL_STENCIL_CLEAR_VALUE 0x0B91 +#define GL_STENCIL_FUNC 0x0B92 +#define GL_STENCIL_VALUE_MASK 0x0B93 +#define GL_STENCIL_FAIL 0x0B94 +#define GL_STENCIL_PASS_DEPTH_FAIL 0x0B95 +#define GL_STENCIL_PASS_DEPTH_PASS 0x0B96 +#define GL_STENCIL_REF 0x0B97 +#define GL_STENCIL_WRITEMASK 0x0B98 +#define GL_VIEWPORT 0x0BA2 +#define GL_DITHER 0x0BD0 +#define GL_BLEND_DST 0x0BE0 +#define GL_BLEND_SRC 0x0BE1 +#define GL_BLEND 0x0BE2 +#define GL_LOGIC_OP_MODE 0x0BF0 +#define GL_DRAW_BUFFER 0x0C01 +#define GL_READ_BUFFER 0x0C02 +#define GL_SCISSOR_BOX 0x0C10 +#define GL_SCISSOR_TEST 0x0C11 +#define GL_COLOR_CLEAR_VALUE 0x0C22 +#define GL_COLOR_WRITEMASK 0x0C23 +#define GL_DOUBLEBUFFER 0x0C32 +#define GL_STEREO 0x0C33 +#define GL_LINE_SMOOTH_HINT 0x0C52 +#define GL_POLYGON_SMOOTH_HINT 0x0C53 +#define GL_UNPACK_SWAP_BYTES 0x0CF0 +#define GL_UNPACK_LSB_FIRST 0x0CF1 +#define GL_UNPACK_ROW_LENGTH 0x0CF2 +#define GL_UNPACK_SKIP_ROWS 0x0CF3 +#define GL_UNPACK_SKIP_PIXELS 0x0CF4 +#define GL_UNPACK_ALIGNMENT 0x0CF5 +#define GL_PACK_SWAP_BYTES 0x0D00 +#define GL_PACK_LSB_FIRST 0x0D01 +#define GL_PACK_ROW_LENGTH 0x0D02 +#define GL_PACK_SKIP_ROWS 0x0D03 +#define GL_PACK_SKIP_PIXELS 0x0D04 +#define GL_PACK_ALIGNMENT 0x0D05 +#define GL_MAX_TEXTURE_SIZE 0x0D33 +#define GL_MAX_VIEWPORT_DIMS 0x0D3A +#define GL_SUBPIXEL_BITS 0x0D50 +#define GL_TEXTURE_1D 0x0DE0 +#define GL_TEXTURE_2D 0x0DE1 +#define GL_TEXTURE_WIDTH 0x1000 +#define GL_TEXTURE_HEIGHT 0x1001 +#define GL_TEXTURE_BORDER_COLOR 0x1004 +#define GL_DONT_CARE 0x1100 +#define GL_FASTEST 0x1101 +#define GL_NICEST 0x1102 +#define GL_BYTE 0x1400 +#define GL_UNSIGNED_BYTE 0x1401 +#define GL_SHORT 0x1402 +#define GL_UNSIGNED_SHORT 0x1403 +#define GL_INT 0x1404 +#define GL_UNSIGNED_INT 0x1405 +#define GL_FLOAT 0x1406 +#define GL_STACK_OVERFLOW 0x0503 +#define GL_STACK_UNDERFLOW 0x0504 +#define GL_CLEAR 0x1500 +#define GL_AND 0x1501 +#define GL_AND_REVERSE 0x1502 +#define GL_COPY 0x1503 +#define GL_AND_INVERTED 0x1504 +#define GL_NOOP 0x1505 +#define GL_XOR 0x1506 +#define GL_OR 0x1507 +#define GL_NOR 0x1508 +#define GL_EQUIV 0x1509 +#define GL_INVERT 0x150A +#define GL_OR_REVERSE 0x150B +#define GL_COPY_INVERTED 0x150C +#define GL_OR_INVERTED 0x150D +#define GL_NAND 0x150E +#define GL_SET 0x150F +#define GL_TEXTURE 0x1702 +#define GL_COLOR 0x1800 +#define GL_DEPTH 0x1801 +#define GL_STENCIL 0x1802 +#define GL_STENCIL_INDEX 0x1901 +#define GL_DEPTH_COMPONENT 0x1902 +#define GL_RED 0x1903 +#define GL_GREEN 0x1904 +#define GL_BLUE 0x1905 +#define GL_ALPHA 0x1906 +#define GL_RGB 0x1907 +#define GL_RGBA 0x1908 +#define GL_POINT 0x1B00 +#define GL_LINE 0x1B01 +#define GL_FILL 0x1B02 +#define GL_KEEP 0x1E00 +#define GL_REPLACE 0x1E01 +#define GL_INCR 0x1E02 +#define GL_DECR 0x1E03 +#define GL_VENDOR 0x1F00 +#define GL_RENDERER 0x1F01 +#define GL_VERSION 0x1F02 +#define GL_EXTENSIONS 0x1F03 +#define GL_NEAREST 0x2600 +#define GL_LINEAR 0x2601 +#define GL_NEAREST_MIPMAP_NEAREST 0x2700 +#define GL_LINEAR_MIPMAP_NEAREST 0x2701 +#define GL_NEAREST_MIPMAP_LINEAR 0x2702 +#define GL_LINEAR_MIPMAP_LINEAR 0x2703 +#define GL_TEXTURE_MAG_FILTER 0x2800 +#define GL_TEXTURE_MIN_FILTER 0x2801 +#define GL_TEXTURE_WRAP_S 0x2802 +#define GL_TEXTURE_WRAP_T 0x2803 +#define GL_REPEAT 0x2901 +#define GL_CURRENT_BIT 0x00000001 +#define GL_POINT_BIT 0x00000002 +#define GL_LINE_BIT 0x00000004 +#define GL_POLYGON_BIT 0x00000008 +#define GL_POLYGON_STIPPLE_BIT 0x00000010 +#define GL_PIXEL_MODE_BIT 0x00000020 +#define GL_LIGHTING_BIT 0x00000040 +#define GL_FOG_BIT 0x00000080 +#define GL_ACCUM_BUFFER_BIT 0x00000200 +#define GL_VIEWPORT_BIT 0x00000800 +#define GL_TRANSFORM_BIT 0x00001000 +#define GL_ENABLE_BIT 0x00002000 +#define GL_HINT_BIT 0x00008000 +#define GL_EVAL_BIT 0x00010000 +#define GL_LIST_BIT 0x00020000 +#define GL_TEXTURE_BIT 0x00040000 +#define GL_SCISSOR_BIT 0x00080000 +#define GL_ALL_ATTRIB_BITS 0xFFFFFFFF +#define GL_QUAD_STRIP 0x0008 +#define GL_POLYGON 0x0009 +#define GL_ACCUM 0x0100 +#define GL_LOAD 0x0101 +#define GL_RETURN 0x0102 +#define GL_MULT 0x0103 +#define GL_ADD 0x0104 +#define GL_AUX0 0x0409 +#define GL_AUX1 0x040A +#define GL_AUX2 0x040B +#define GL_AUX3 0x040C +#define GL_2D 0x0600 +#define GL_3D 0x0601 +#define GL_3D_COLOR 0x0602 +#define GL_3D_COLOR_TEXTURE 0x0603 +#define GL_4D_COLOR_TEXTURE 0x0604 +#define GL_PASS_THROUGH_TOKEN 0x0700 +#define GL_POINT_TOKEN 0x0701 +#define GL_LINE_TOKEN 0x0702 +#define GL_POLYGON_TOKEN 0x0703 +#define GL_BITMAP_TOKEN 0x0704 +#define GL_DRAW_PIXEL_TOKEN 0x0705 +#define GL_COPY_PIXEL_TOKEN 0x0706 +#define GL_LINE_RESET_TOKEN 0x0707 +#define GL_EXP 0x0800 +#define GL_EXP2 0x0801 +#define GL_COEFF 0x0A00 +#define GL_ORDER 0x0A01 +#define GL_DOMAIN 0x0A02 +#define GL_PIXEL_MAP_I_TO_I 0x0C70 +#define GL_PIXEL_MAP_S_TO_S 0x0C71 +#define GL_PIXEL_MAP_I_TO_R 0x0C72 +#define GL_PIXEL_MAP_I_TO_G 0x0C73 +#define GL_PIXEL_MAP_I_TO_B 0x0C74 +#define GL_PIXEL_MAP_I_TO_A 0x0C75 +#define GL_PIXEL_MAP_R_TO_R 0x0C76 +#define GL_PIXEL_MAP_G_TO_G 0x0C77 +#define GL_PIXEL_MAP_B_TO_B 0x0C78 +#define GL_PIXEL_MAP_A_TO_A 0x0C79 +#define GL_CURRENT_COLOR 0x0B00 +#define GL_CURRENT_INDEX 0x0B01 +#define GL_CURRENT_NORMAL 0x0B02 +#define GL_CURRENT_TEXTURE_COORDS 0x0B03 +#define GL_CURRENT_RASTER_COLOR 0x0B04 +#define GL_CURRENT_RASTER_INDEX 0x0B05 +#define GL_CURRENT_RASTER_TEXTURE_COORDS 0x0B06 +#define GL_CURRENT_RASTER_POSITION 0x0B07 +#define GL_CURRENT_RASTER_POSITION_VALID 0x0B08 +#define GL_CURRENT_RASTER_DISTANCE 0x0B09 +#define GL_POINT_SMOOTH 0x0B10 +#define GL_LINE_STIPPLE 0x0B24 +#define GL_LINE_STIPPLE_PATTERN 0x0B25 +#define GL_LINE_STIPPLE_REPEAT 0x0B26 +#define GL_LIST_MODE 0x0B30 +#define GL_MAX_LIST_NESTING 0x0B31 +#define GL_LIST_BASE 0x0B32 +#define GL_LIST_INDEX 0x0B33 +#define GL_POLYGON_STIPPLE 0x0B42 +#define GL_EDGE_FLAG 0x0B43 +#define GL_LIGHTING 0x0B50 +#define GL_LIGHT_MODEL_LOCAL_VIEWER 0x0B51 +#define GL_LIGHT_MODEL_TWO_SIDE 0x0B52 +#define GL_LIGHT_MODEL_AMBIENT 0x0B53 +#define GL_SHADE_MODEL 0x0B54 +#define GL_COLOR_MATERIAL_FACE 0x0B55 +#define GL_COLOR_MATERIAL_PARAMETER 0x0B56 +#define GL_COLOR_MATERIAL 0x0B57 +#define GL_FOG 0x0B60 +#define GL_FOG_INDEX 0x0B61 +#define GL_FOG_DENSITY 0x0B62 +#define GL_FOG_START 0x0B63 +#define GL_FOG_END 0x0B64 +#define GL_FOG_MODE 0x0B65 +#define GL_FOG_COLOR 0x0B66 +#define GL_ACCUM_CLEAR_VALUE 0x0B80 +#define GL_MATRIX_MODE 0x0BA0 +#define GL_NORMALIZE 0x0BA1 +#define GL_MODELVIEW_STACK_DEPTH 0x0BA3 +#define GL_PROJECTION_STACK_DEPTH 0x0BA4 +#define GL_TEXTURE_STACK_DEPTH 0x0BA5 +#define GL_MODELVIEW_MATRIX 0x0BA6 +#define GL_PROJECTION_MATRIX 0x0BA7 +#define GL_TEXTURE_MATRIX 0x0BA8 +#define GL_ATTRIB_STACK_DEPTH 0x0BB0 +#define GL_ALPHA_TEST 0x0BC0 +#define GL_ALPHA_TEST_FUNC 0x0BC1 +#define GL_ALPHA_TEST_REF 0x0BC2 +#define GL_LOGIC_OP 0x0BF1 +#define GL_AUX_BUFFERS 0x0C00 +#define GL_INDEX_CLEAR_VALUE 0x0C20 +#define GL_INDEX_WRITEMASK 0x0C21 +#define GL_INDEX_MODE 0x0C30 +#define GL_RGBA_MODE 0x0C31 +#define GL_RENDER_MODE 0x0C40 +#define GL_PERSPECTIVE_CORRECTION_HINT 0x0C50 +#define GL_POINT_SMOOTH_HINT 0x0C51 +#define GL_FOG_HINT 0x0C54 +#define GL_TEXTURE_GEN_S 0x0C60 +#define GL_TEXTURE_GEN_T 0x0C61 +#define GL_TEXTURE_GEN_R 0x0C62 +#define GL_TEXTURE_GEN_Q 0x0C63 +#define GL_PIXEL_MAP_I_TO_I_SIZE 0x0CB0 +#define GL_PIXEL_MAP_S_TO_S_SIZE 0x0CB1 +#define GL_PIXEL_MAP_I_TO_R_SIZE 0x0CB2 +#define GL_PIXEL_MAP_I_TO_G_SIZE 0x0CB3 +#define GL_PIXEL_MAP_I_TO_B_SIZE 0x0CB4 +#define GL_PIXEL_MAP_I_TO_A_SIZE 0x0CB5 +#define GL_PIXEL_MAP_R_TO_R_SIZE 0x0CB6 +#define GL_PIXEL_MAP_G_TO_G_SIZE 0x0CB7 +#define GL_PIXEL_MAP_B_TO_B_SIZE 0x0CB8 +#define GL_PIXEL_MAP_A_TO_A_SIZE 0x0CB9 +#define GL_MAP_COLOR 0x0D10 +#define GL_MAP_STENCIL 0x0D11 +#define GL_INDEX_SHIFT 0x0D12 +#define GL_INDEX_OFFSET 0x0D13 +#define GL_RED_SCALE 0x0D14 +#define GL_RED_BIAS 0x0D15 +#define GL_ZOOM_X 0x0D16 +#define GL_ZOOM_Y 0x0D17 +#define GL_GREEN_SCALE 0x0D18 +#define GL_GREEN_BIAS 0x0D19 +#define GL_BLUE_SCALE 0x0D1A +#define GL_BLUE_BIAS 0x0D1B +#define GL_ALPHA_SCALE 0x0D1C +#define GL_ALPHA_BIAS 0x0D1D +#define GL_DEPTH_SCALE 0x0D1E +#define GL_DEPTH_BIAS 0x0D1F +#define GL_MAX_EVAL_ORDER 0x0D30 +#define GL_MAX_LIGHTS 0x0D31 +#define GL_MAX_CLIP_PLANES 0x0D32 +#define GL_MAX_PIXEL_MAP_TABLE 0x0D34 +#define GL_MAX_ATTRIB_STACK_DEPTH 0x0D35 +#define GL_MAX_MODELVIEW_STACK_DEPTH 0x0D36 +#define GL_MAX_NAME_STACK_DEPTH 0x0D37 +#define GL_MAX_PROJECTION_STACK_DEPTH 0x0D38 +#define GL_MAX_TEXTURE_STACK_DEPTH 0x0D39 +#define GL_INDEX_BITS 0x0D51 +#define GL_RED_BITS 0x0D52 +#define GL_GREEN_BITS 0x0D53 +#define GL_BLUE_BITS 0x0D54 +#define GL_ALPHA_BITS 0x0D55 +#define GL_DEPTH_BITS 0x0D56 +#define GL_STENCIL_BITS 0x0D57 +#define GL_ACCUM_RED_BITS 0x0D58 +#define GL_ACCUM_GREEN_BITS 0x0D59 +#define GL_ACCUM_BLUE_BITS 0x0D5A +#define GL_ACCUM_ALPHA_BITS 0x0D5B +#define GL_NAME_STACK_DEPTH 0x0D70 +#define GL_AUTO_NORMAL 0x0D80 +#define GL_MAP1_COLOR_4 0x0D90 +#define GL_MAP1_INDEX 0x0D91 +#define GL_MAP1_NORMAL 0x0D92 +#define GL_MAP1_TEXTURE_COORD_1 0x0D93 +#define GL_MAP1_TEXTURE_COORD_2 0x0D94 +#define GL_MAP1_TEXTURE_COORD_3 0x0D95 +#define GL_MAP1_TEXTURE_COORD_4 0x0D96 +#define GL_MAP1_VERTEX_3 0x0D97 +#define GL_MAP1_VERTEX_4 0x0D98 +#define GL_MAP2_COLOR_4 0x0DB0 +#define GL_MAP2_INDEX 0x0DB1 +#define GL_MAP2_NORMAL 0x0DB2 +#define GL_MAP2_TEXTURE_COORD_1 0x0DB3 +#define GL_MAP2_TEXTURE_COORD_2 0x0DB4 +#define GL_MAP2_TEXTURE_COORD_3 0x0DB5 +#define GL_MAP2_TEXTURE_COORD_4 0x0DB6 +#define GL_MAP2_VERTEX_3 0x0DB7 +#define GL_MAP2_VERTEX_4 0x0DB8 +#define GL_MAP1_GRID_DOMAIN 0x0DD0 +#define GL_MAP1_GRID_SEGMENTS 0x0DD1 +#define GL_MAP2_GRID_DOMAIN 0x0DD2 +#define GL_MAP2_GRID_SEGMENTS 0x0DD3 +#define GL_TEXTURE_COMPONENTS 0x1003 +#define GL_TEXTURE_BORDER 0x1005 +#define GL_AMBIENT 0x1200 +#define GL_DIFFUSE 0x1201 +#define GL_SPECULAR 0x1202 +#define GL_POSITION 0x1203 +#define GL_SPOT_DIRECTION 0x1204 +#define GL_SPOT_EXPONENT 0x1205 +#define GL_SPOT_CUTOFF 0x1206 +#define GL_CONSTANT_ATTENUATION 0x1207 +#define GL_LINEAR_ATTENUATION 0x1208 +#define GL_QUADRATIC_ATTENUATION 0x1209 +#define GL_COMPILE 0x1300 +#define GL_COMPILE_AND_EXECUTE 0x1301 +#define GL_2_BYTES 0x1407 +#define GL_3_BYTES 0x1408 +#define GL_4_BYTES 0x1409 +#define GL_EMISSION 0x1600 +#define GL_SHININESS 0x1601 +#define GL_AMBIENT_AND_DIFFUSE 0x1602 +#define GL_COLOR_INDEXES 0x1603 +#define GL_MODELVIEW 0x1700 +#define GL_PROJECTION 0x1701 +#define GL_COLOR_INDEX 0x1900 +#define GL_LUMINANCE 0x1909 +#define GL_LUMINANCE_ALPHA 0x190A +#define GL_BITMAP 0x1A00 +#define GL_RENDER 0x1C00 +#define GL_FEEDBACK 0x1C01 +#define GL_SELECT 0x1C02 +#define GL_FLAT 0x1D00 +#define GL_SMOOTH 0x1D01 +#define GL_S 0x2000 +#define GL_T 0x2001 +#define GL_R 0x2002 +#define GL_Q 0x2003 +#define GL_MODULATE 0x2100 +#define GL_DECAL 0x2101 +#define GL_TEXTURE_ENV_MODE 0x2200 +#define GL_TEXTURE_ENV_COLOR 0x2201 +#define GL_TEXTURE_ENV 0x2300 +#define GL_EYE_LINEAR 0x2400 +#define GL_OBJECT_LINEAR 0x2401 +#define GL_SPHERE_MAP 0x2402 +#define GL_TEXTURE_GEN_MODE 0x2500 +#define GL_OBJECT_PLANE 0x2501 +#define GL_EYE_PLANE 0x2502 +#define GL_CLAMP 0x2900 +#define GL_CLIP_PLANE0 0x3000 +#define GL_CLIP_PLANE1 0x3001 +#define GL_CLIP_PLANE2 0x3002 +#define GL_CLIP_PLANE3 0x3003 +#define GL_CLIP_PLANE4 0x3004 +#define GL_CLIP_PLANE5 0x3005 +#define GL_LIGHT0 0x4000 +#define GL_LIGHT1 0x4001 +#define GL_LIGHT2 0x4002 +#define GL_LIGHT3 0x4003 +#define GL_LIGHT4 0x4004 +#define GL_LIGHT5 0x4005 +#define GL_LIGHT6 0x4006 +#define GL_LIGHT7 0x4007 +typedef void (APIENTRYP PFNGLCULLFACEPROC) (GLenum mode); +typedef void (APIENTRYP PFNGLFRONTFACEPROC) (GLenum mode); +typedef void (APIENTRYP PFNGLHINTPROC) (GLenum target, GLenum mode); +typedef void (APIENTRYP PFNGLLINEWIDTHPROC) (GLfloat width); +typedef void (APIENTRYP PFNGLPOINTSIZEPROC) (GLfloat size); +typedef void (APIENTRYP PFNGLPOLYGONMODEPROC) (GLenum face, GLenum mode); +typedef void (APIENTRYP PFNGLSCISSORPROC) (GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLTEXPARAMETERFPROC) (GLenum target, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLTEXPARAMETERFVPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLTEXPARAMETERIPROC) (GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLTEXPARAMETERIVPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLTEXIMAGE1DPROC) (GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLTEXIMAGE2DPROC) (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLDRAWBUFFERPROC) (GLenum buf); +typedef void (APIENTRYP PFNGLCLEARPROC) (GLbitfield mask); +typedef void (APIENTRYP PFNGLCLEARCOLORPROC) (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +typedef void (APIENTRYP PFNGLCLEARSTENCILPROC) (GLint s); +typedef void (APIENTRYP PFNGLCLEARDEPTHPROC) (GLdouble depth); +typedef void (APIENTRYP PFNGLSTENCILMASKPROC) (GLuint mask); +typedef void (APIENTRYP PFNGLCOLORMASKPROC) (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +typedef void (APIENTRYP PFNGLDEPTHMASKPROC) (GLboolean flag); +typedef void (APIENTRYP PFNGLDISABLEPROC) (GLenum cap); +typedef void (APIENTRYP PFNGLENABLEPROC) (GLenum cap); +typedef void (APIENTRYP PFNGLFINISHPROC) (void); +typedef void (APIENTRYP PFNGLFLUSHPROC) (void); +typedef void (APIENTRYP PFNGLBLENDFUNCPROC) (GLenum sfactor, GLenum dfactor); +typedef void (APIENTRYP PFNGLLOGICOPPROC) (GLenum opcode); +typedef void (APIENTRYP PFNGLSTENCILFUNCPROC) (GLenum func, GLint ref, GLuint mask); +typedef void (APIENTRYP PFNGLSTENCILOPPROC) (GLenum fail, GLenum zfail, GLenum zpass); +typedef void (APIENTRYP PFNGLDEPTHFUNCPROC) (GLenum func); +typedef void (APIENTRYP PFNGLPIXELSTOREFPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPIXELSTOREIPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLREADBUFFERPROC) (GLenum src); +typedef void (APIENTRYP PFNGLREADPIXELSPROC) (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void *pixels); +typedef void (APIENTRYP PFNGLGETBOOLEANVPROC) (GLenum pname, GLboolean *data); +typedef void (APIENTRYP PFNGLGETDOUBLEVPROC) (GLenum pname, GLdouble *data); +typedef GLenum (APIENTRYP PFNGLGETERRORPROC) (void); +typedef void (APIENTRYP PFNGLGETFLOATVPROC) (GLenum pname, GLfloat *data); +typedef void (APIENTRYP PFNGLGETINTEGERVPROC) (GLenum pname, GLint *data); +typedef const GLubyte *(APIENTRYP PFNGLGETSTRINGPROC) (GLenum name); +typedef void (APIENTRYP PFNGLGETTEXIMAGEPROC) (GLenum target, GLint level, GLenum format, GLenum type, void *pixels); +typedef void (APIENTRYP PFNGLGETTEXPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETTEXPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETTEXLEVELPARAMETERFVPROC) (GLenum target, GLint level, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETTEXLEVELPARAMETERIVPROC) (GLenum target, GLint level, GLenum pname, GLint *params); +typedef GLboolean (APIENTRYP PFNGLISENABLEDPROC) (GLenum cap); +typedef void (APIENTRYP PFNGLDEPTHRANGEPROC) (GLdouble n, GLdouble f); +typedef void (APIENTRYP PFNGLVIEWPORTPROC) (GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLNEWLISTPROC) (GLuint list, GLenum mode); +typedef void (APIENTRYP PFNGLENDLISTPROC) (void); +typedef void (APIENTRYP PFNGLCALLLISTPROC) (GLuint list); +typedef void (APIENTRYP PFNGLCALLLISTSPROC) (GLsizei n, GLenum type, const void *lists); +typedef void (APIENTRYP PFNGLDELETELISTSPROC) (GLuint list, GLsizei range); +typedef GLuint (APIENTRYP PFNGLGENLISTSPROC) (GLsizei range); +typedef void (APIENTRYP PFNGLLISTBASEPROC) (GLuint base); +typedef void (APIENTRYP PFNGLBEGINPROC) (GLenum mode); +typedef void (APIENTRYP PFNGLBITMAPPROC) (GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap); +typedef void (APIENTRYP PFNGLCOLOR3BPROC) (GLbyte red, GLbyte green, GLbyte blue); +typedef void (APIENTRYP PFNGLCOLOR3BVPROC) (const GLbyte *v); +typedef void (APIENTRYP PFNGLCOLOR3DPROC) (GLdouble red, GLdouble green, GLdouble blue); +typedef void (APIENTRYP PFNGLCOLOR3DVPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLCOLOR3FPROC) (GLfloat red, GLfloat green, GLfloat blue); +typedef void (APIENTRYP PFNGLCOLOR3FVPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLCOLOR3IPROC) (GLint red, GLint green, GLint blue); +typedef void (APIENTRYP PFNGLCOLOR3IVPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLCOLOR3SPROC) (GLshort red, GLshort green, GLshort blue); +typedef void (APIENTRYP PFNGLCOLOR3SVPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLCOLOR3UBPROC) (GLubyte red, GLubyte green, GLubyte blue); +typedef void (APIENTRYP PFNGLCOLOR3UBVPROC) (const GLubyte *v); +typedef void (APIENTRYP PFNGLCOLOR3UIPROC) (GLuint red, GLuint green, GLuint blue); +typedef void (APIENTRYP PFNGLCOLOR3UIVPROC) (const GLuint *v); +typedef void (APIENTRYP PFNGLCOLOR3USPROC) (GLushort red, GLushort green, GLushort blue); +typedef void (APIENTRYP PFNGLCOLOR3USVPROC) (const GLushort *v); +typedef void (APIENTRYP PFNGLCOLOR4BPROC) (GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha); +typedef void (APIENTRYP PFNGLCOLOR4BVPROC) (const GLbyte *v); +typedef void (APIENTRYP PFNGLCOLOR4DPROC) (GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha); +typedef void (APIENTRYP PFNGLCOLOR4DVPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLCOLOR4FPROC) (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +typedef void (APIENTRYP PFNGLCOLOR4FVPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLCOLOR4IPROC) (GLint red, GLint green, GLint blue, GLint alpha); +typedef void (APIENTRYP PFNGLCOLOR4IVPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLCOLOR4SPROC) (GLshort red, GLshort green, GLshort blue, GLshort alpha); +typedef void (APIENTRYP PFNGLCOLOR4SVPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLCOLOR4UBPROC) (GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha); +typedef void (APIENTRYP PFNGLCOLOR4UBVPROC) (const GLubyte *v); +typedef void (APIENTRYP PFNGLCOLOR4UIPROC) (GLuint red, GLuint green, GLuint blue, GLuint alpha); +typedef void (APIENTRYP PFNGLCOLOR4UIVPROC) (const GLuint *v); +typedef void (APIENTRYP PFNGLCOLOR4USPROC) (GLushort red, GLushort green, GLushort blue, GLushort alpha); +typedef void (APIENTRYP PFNGLCOLOR4USVPROC) (const GLushort *v); +typedef void (APIENTRYP PFNGLEDGEFLAGPROC) (GLboolean flag); +typedef void (APIENTRYP PFNGLEDGEFLAGVPROC) (const GLboolean *flag); +typedef void (APIENTRYP PFNGLENDPROC) (void); +typedef void (APIENTRYP PFNGLINDEXDPROC) (GLdouble c); +typedef void (APIENTRYP PFNGLINDEXDVPROC) (const GLdouble *c); +typedef void (APIENTRYP PFNGLINDEXFPROC) (GLfloat c); +typedef void (APIENTRYP PFNGLINDEXFVPROC) (const GLfloat *c); +typedef void (APIENTRYP PFNGLINDEXIPROC) (GLint c); +typedef void (APIENTRYP PFNGLINDEXIVPROC) (const GLint *c); +typedef void (APIENTRYP PFNGLINDEXSPROC) (GLshort c); +typedef void (APIENTRYP PFNGLINDEXSVPROC) (const GLshort *c); +typedef void (APIENTRYP PFNGLNORMAL3BPROC) (GLbyte nx, GLbyte ny, GLbyte nz); +typedef void (APIENTRYP PFNGLNORMAL3BVPROC) (const GLbyte *v); +typedef void (APIENTRYP PFNGLNORMAL3DPROC) (GLdouble nx, GLdouble ny, GLdouble nz); +typedef void (APIENTRYP PFNGLNORMAL3DVPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLNORMAL3FPROC) (GLfloat nx, GLfloat ny, GLfloat nz); +typedef void (APIENTRYP PFNGLNORMAL3FVPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLNORMAL3IPROC) (GLint nx, GLint ny, GLint nz); +typedef void (APIENTRYP PFNGLNORMAL3IVPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLNORMAL3SPROC) (GLshort nx, GLshort ny, GLshort nz); +typedef void (APIENTRYP PFNGLNORMAL3SVPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLRASTERPOS2DPROC) (GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLRASTERPOS2DVPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLRASTERPOS2FPROC) (GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLRASTERPOS2FVPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLRASTERPOS2IPROC) (GLint x, GLint y); +typedef void (APIENTRYP PFNGLRASTERPOS2IVPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLRASTERPOS2SPROC) (GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLRASTERPOS2SVPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLRASTERPOS3DPROC) (GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLRASTERPOS3DVPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLRASTERPOS3FPROC) (GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLRASTERPOS3FVPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLRASTERPOS3IPROC) (GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLRASTERPOS3IVPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLRASTERPOS3SPROC) (GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLRASTERPOS3SVPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLRASTERPOS4DPROC) (GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLRASTERPOS4DVPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLRASTERPOS4FPROC) (GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLRASTERPOS4FVPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLRASTERPOS4IPROC) (GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLRASTERPOS4IVPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLRASTERPOS4SPROC) (GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLRASTERPOS4SVPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLRECTDPROC) (GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2); +typedef void (APIENTRYP PFNGLRECTDVPROC) (const GLdouble *v1, const GLdouble *v2); +typedef void (APIENTRYP PFNGLRECTFPROC) (GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2); +typedef void (APIENTRYP PFNGLRECTFVPROC) (const GLfloat *v1, const GLfloat *v2); +typedef void (APIENTRYP PFNGLRECTIPROC) (GLint x1, GLint y1, GLint x2, GLint y2); +typedef void (APIENTRYP PFNGLRECTIVPROC) (const GLint *v1, const GLint *v2); +typedef void (APIENTRYP PFNGLRECTSPROC) (GLshort x1, GLshort y1, GLshort x2, GLshort y2); +typedef void (APIENTRYP PFNGLRECTSVPROC) (const GLshort *v1, const GLshort *v2); +typedef void (APIENTRYP PFNGLTEXCOORD1DPROC) (GLdouble s); +typedef void (APIENTRYP PFNGLTEXCOORD1DVPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLTEXCOORD1FPROC) (GLfloat s); +typedef void (APIENTRYP PFNGLTEXCOORD1FVPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD1IPROC) (GLint s); +typedef void (APIENTRYP PFNGLTEXCOORD1IVPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLTEXCOORD1SPROC) (GLshort s); +typedef void (APIENTRYP PFNGLTEXCOORD1SVPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLTEXCOORD2DPROC) (GLdouble s, GLdouble t); +typedef void (APIENTRYP PFNGLTEXCOORD2DVPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FPROC) (GLfloat s, GLfloat t); +typedef void (APIENTRYP PFNGLTEXCOORD2FVPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2IPROC) (GLint s, GLint t); +typedef void (APIENTRYP PFNGLTEXCOORD2IVPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLTEXCOORD2SPROC) (GLshort s, GLshort t); +typedef void (APIENTRYP PFNGLTEXCOORD2SVPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLTEXCOORD3DPROC) (GLdouble s, GLdouble t, GLdouble r); +typedef void (APIENTRYP PFNGLTEXCOORD3DVPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLTEXCOORD3FPROC) (GLfloat s, GLfloat t, GLfloat r); +typedef void (APIENTRYP PFNGLTEXCOORD3FVPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD3IPROC) (GLint s, GLint t, GLint r); +typedef void (APIENTRYP PFNGLTEXCOORD3IVPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLTEXCOORD3SPROC) (GLshort s, GLshort t, GLshort r); +typedef void (APIENTRYP PFNGLTEXCOORD3SVPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLTEXCOORD4DPROC) (GLdouble s, GLdouble t, GLdouble r, GLdouble q); +typedef void (APIENTRYP PFNGLTEXCOORD4DVPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLTEXCOORD4FPROC) (GLfloat s, GLfloat t, GLfloat r, GLfloat q); +typedef void (APIENTRYP PFNGLTEXCOORD4FVPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD4IPROC) (GLint s, GLint t, GLint r, GLint q); +typedef void (APIENTRYP PFNGLTEXCOORD4IVPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLTEXCOORD4SPROC) (GLshort s, GLshort t, GLshort r, GLshort q); +typedef void (APIENTRYP PFNGLTEXCOORD4SVPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEX2DPROC) (GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEX2DVPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEX2FPROC) (GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLVERTEX2FVPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEX2IPROC) (GLint x, GLint y); +typedef void (APIENTRYP PFNGLVERTEX2IVPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLVERTEX2SPROC) (GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLVERTEX2SVPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEX3DPROC) (GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEX3DVPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEX3FPROC) (GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLVERTEX3FVPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEX3IPROC) (GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLVERTEX3IVPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLVERTEX3SPROC) (GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLVERTEX3SVPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEX4DPROC) (GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEX4DVPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEX4FPROC) (GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLVERTEX4FVPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEX4IPROC) (GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLVERTEX4IVPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLVERTEX4SPROC) (GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLVERTEX4SVPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLCLIPPLANEPROC) (GLenum plane, const GLdouble *equation); +typedef void (APIENTRYP PFNGLCOLORMATERIALPROC) (GLenum face, GLenum mode); +typedef void (APIENTRYP PFNGLFOGFPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLFOGFVPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLFOGIPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLFOGIVPROC) (GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLLIGHTFPROC) (GLenum light, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLLIGHTFVPROC) (GLenum light, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLLIGHTIPROC) (GLenum light, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLLIGHTIVPROC) (GLenum light, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLLIGHTMODELFPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLLIGHTMODELFVPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLLIGHTMODELIPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLLIGHTMODELIVPROC) (GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLLINESTIPPLEPROC) (GLint factor, GLushort pattern); +typedef void (APIENTRYP PFNGLMATERIALFPROC) (GLenum face, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLMATERIALFVPROC) (GLenum face, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLMATERIALIPROC) (GLenum face, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLMATERIALIVPROC) (GLenum face, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLPOLYGONSTIPPLEPROC) (const GLubyte *mask); +typedef void (APIENTRYP PFNGLSHADEMODELPROC) (GLenum mode); +typedef void (APIENTRYP PFNGLTEXENVFPROC) (GLenum target, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLTEXENVFVPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLTEXENVIPROC) (GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLTEXENVIVPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLTEXGENDPROC) (GLenum coord, GLenum pname, GLdouble param); +typedef void (APIENTRYP PFNGLTEXGENDVPROC) (GLenum coord, GLenum pname, const GLdouble *params); +typedef void (APIENTRYP PFNGLTEXGENFPROC) (GLenum coord, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLTEXGENFVPROC) (GLenum coord, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLTEXGENIPROC) (GLenum coord, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLTEXGENIVPROC) (GLenum coord, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLFEEDBACKBUFFERPROC) (GLsizei size, GLenum type, GLfloat *buffer); +typedef void (APIENTRYP PFNGLSELECTBUFFERPROC) (GLsizei size, GLuint *buffer); +typedef GLint (APIENTRYP PFNGLRENDERMODEPROC) (GLenum mode); +typedef void (APIENTRYP PFNGLINITNAMESPROC) (void); +typedef void (APIENTRYP PFNGLLOADNAMEPROC) (GLuint name); +typedef void (APIENTRYP PFNGLPASSTHROUGHPROC) (GLfloat token); +typedef void (APIENTRYP PFNGLPOPNAMEPROC) (void); +typedef void (APIENTRYP PFNGLPUSHNAMEPROC) (GLuint name); +typedef void (APIENTRYP PFNGLCLEARACCUMPROC) (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +typedef void (APIENTRYP PFNGLCLEARINDEXPROC) (GLfloat c); +typedef void (APIENTRYP PFNGLINDEXMASKPROC) (GLuint mask); +typedef void (APIENTRYP PFNGLACCUMPROC) (GLenum op, GLfloat value); +typedef void (APIENTRYP PFNGLPOPATTRIBPROC) (void); +typedef void (APIENTRYP PFNGLPUSHATTRIBPROC) (GLbitfield mask); +typedef void (APIENTRYP PFNGLMAP1DPROC) (GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points); +typedef void (APIENTRYP PFNGLMAP1FPROC) (GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points); +typedef void (APIENTRYP PFNGLMAP2DPROC) (GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points); +typedef void (APIENTRYP PFNGLMAP2FPROC) (GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points); +typedef void (APIENTRYP PFNGLMAPGRID1DPROC) (GLint un, GLdouble u1, GLdouble u2); +typedef void (APIENTRYP PFNGLMAPGRID1FPROC) (GLint un, GLfloat u1, GLfloat u2); +typedef void (APIENTRYP PFNGLMAPGRID2DPROC) (GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2); +typedef void (APIENTRYP PFNGLMAPGRID2FPROC) (GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2); +typedef void (APIENTRYP PFNGLEVALCOORD1DPROC) (GLdouble u); +typedef void (APIENTRYP PFNGLEVALCOORD1DVPROC) (const GLdouble *u); +typedef void (APIENTRYP PFNGLEVALCOORD1FPROC) (GLfloat u); +typedef void (APIENTRYP PFNGLEVALCOORD1FVPROC) (const GLfloat *u); +typedef void (APIENTRYP PFNGLEVALCOORD2DPROC) (GLdouble u, GLdouble v); +typedef void (APIENTRYP PFNGLEVALCOORD2DVPROC) (const GLdouble *u); +typedef void (APIENTRYP PFNGLEVALCOORD2FPROC) (GLfloat u, GLfloat v); +typedef void (APIENTRYP PFNGLEVALCOORD2FVPROC) (const GLfloat *u); +typedef void (APIENTRYP PFNGLEVALMESH1PROC) (GLenum mode, GLint i1, GLint i2); +typedef void (APIENTRYP PFNGLEVALPOINT1PROC) (GLint i); +typedef void (APIENTRYP PFNGLEVALMESH2PROC) (GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2); +typedef void (APIENTRYP PFNGLEVALPOINT2PROC) (GLint i, GLint j); +typedef void (APIENTRYP PFNGLALPHAFUNCPROC) (GLenum func, GLfloat ref); +typedef void (APIENTRYP PFNGLPIXELZOOMPROC) (GLfloat xfactor, GLfloat yfactor); +typedef void (APIENTRYP PFNGLPIXELTRANSFERFPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPIXELTRANSFERIPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPIXELMAPFVPROC) (GLenum map, GLsizei mapsize, const GLfloat *values); +typedef void (APIENTRYP PFNGLPIXELMAPUIVPROC) (GLenum map, GLsizei mapsize, const GLuint *values); +typedef void (APIENTRYP PFNGLPIXELMAPUSVPROC) (GLenum map, GLsizei mapsize, const GLushort *values); +typedef void (APIENTRYP PFNGLCOPYPIXELSPROC) (GLint x, GLint y, GLsizei width, GLsizei height, GLenum type); +typedef void (APIENTRYP PFNGLDRAWPIXELSPROC) (GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLGETCLIPPLANEPROC) (GLenum plane, GLdouble *equation); +typedef void (APIENTRYP PFNGLGETLIGHTFVPROC) (GLenum light, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETLIGHTIVPROC) (GLenum light, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMAPDVPROC) (GLenum target, GLenum query, GLdouble *v); +typedef void (APIENTRYP PFNGLGETMAPFVPROC) (GLenum target, GLenum query, GLfloat *v); +typedef void (APIENTRYP PFNGLGETMAPIVPROC) (GLenum target, GLenum query, GLint *v); +typedef void (APIENTRYP PFNGLGETMATERIALFVPROC) (GLenum face, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMATERIALIVPROC) (GLenum face, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETPIXELMAPFVPROC) (GLenum map, GLfloat *values); +typedef void (APIENTRYP PFNGLGETPIXELMAPUIVPROC) (GLenum map, GLuint *values); +typedef void (APIENTRYP PFNGLGETPIXELMAPUSVPROC) (GLenum map, GLushort *values); +typedef void (APIENTRYP PFNGLGETPOLYGONSTIPPLEPROC) (GLubyte *mask); +typedef void (APIENTRYP PFNGLGETTEXENVFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETTEXENVIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETTEXGENDVPROC) (GLenum coord, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLGETTEXGENFVPROC) (GLenum coord, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETTEXGENIVPROC) (GLenum coord, GLenum pname, GLint *params); +typedef GLboolean (APIENTRYP PFNGLISLISTPROC) (GLuint list); +typedef void (APIENTRYP PFNGLFRUSTUMPROC) (GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +typedef void (APIENTRYP PFNGLLOADIDENTITYPROC) (void); +typedef void (APIENTRYP PFNGLLOADMATRIXFPROC) (const GLfloat *m); +typedef void (APIENTRYP PFNGLLOADMATRIXDPROC) (const GLdouble *m); +typedef void (APIENTRYP PFNGLMATRIXMODEPROC) (GLenum mode); +typedef void (APIENTRYP PFNGLMULTMATRIXFPROC) (const GLfloat *m); +typedef void (APIENTRYP PFNGLMULTMATRIXDPROC) (const GLdouble *m); +typedef void (APIENTRYP PFNGLORTHOPROC) (GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +typedef void (APIENTRYP PFNGLPOPMATRIXPROC) (void); +typedef void (APIENTRYP PFNGLPUSHMATRIXPROC) (void); +typedef void (APIENTRYP PFNGLROTATEDPROC) (GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLROTATEFPROC) (GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLSCALEDPROC) (GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLSCALEFPROC) (GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTRANSLATEDPROC) (GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLTRANSLATEFPROC) (GLfloat x, GLfloat y, GLfloat z); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCullFace (GLenum mode); +GLAPI void APIENTRY glFrontFace (GLenum mode); +GLAPI void APIENTRY glHint (GLenum target, GLenum mode); +GLAPI void APIENTRY glLineWidth (GLfloat width); +GLAPI void APIENTRY glPointSize (GLfloat size); +GLAPI void APIENTRY glPolygonMode (GLenum face, GLenum mode); +GLAPI void APIENTRY glScissor (GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glTexParameterf (GLenum target, GLenum pname, GLfloat param); +GLAPI void APIENTRY glTexParameterfv (GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glTexParameteri (GLenum target, GLenum pname, GLint param); +GLAPI void APIENTRY glTexParameteriv (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glTexImage1D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glTexImage2D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glDrawBuffer (GLenum buf); +GLAPI void APIENTRY glClear (GLbitfield mask); +GLAPI void APIENTRY glClearColor (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +GLAPI void APIENTRY glClearStencil (GLint s); +GLAPI void APIENTRY glClearDepth (GLdouble depth); +GLAPI void APIENTRY glStencilMask (GLuint mask); +GLAPI void APIENTRY glColorMask (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +GLAPI void APIENTRY glDepthMask (GLboolean flag); +GLAPI void APIENTRY glDisable (GLenum cap); +GLAPI void APIENTRY glEnable (GLenum cap); +GLAPI void APIENTRY glFinish (void); +GLAPI void APIENTRY glFlush (void); +GLAPI void APIENTRY glBlendFunc (GLenum sfactor, GLenum dfactor); +GLAPI void APIENTRY glLogicOp (GLenum opcode); +GLAPI void APIENTRY glStencilFunc (GLenum func, GLint ref, GLuint mask); +GLAPI void APIENTRY glStencilOp (GLenum fail, GLenum zfail, GLenum zpass); +GLAPI void APIENTRY glDepthFunc (GLenum func); +GLAPI void APIENTRY glPixelStoref (GLenum pname, GLfloat param); +GLAPI void APIENTRY glPixelStorei (GLenum pname, GLint param); +GLAPI void APIENTRY glReadBuffer (GLenum src); +GLAPI void APIENTRY glReadPixels (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void *pixels); +GLAPI void APIENTRY glGetBooleanv (GLenum pname, GLboolean *data); +GLAPI void APIENTRY glGetDoublev (GLenum pname, GLdouble *data); +GLAPI GLenum APIENTRY glGetError (void); +GLAPI void APIENTRY glGetFloatv (GLenum pname, GLfloat *data); +GLAPI void APIENTRY glGetIntegerv (GLenum pname, GLint *data); +GLAPI const GLubyte *APIENTRY glGetString (GLenum name); +GLAPI void APIENTRY glGetTexImage (GLenum target, GLint level, GLenum format, GLenum type, void *pixels); +GLAPI void APIENTRY glGetTexParameterfv (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetTexParameteriv (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetTexLevelParameterfv (GLenum target, GLint level, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetTexLevelParameteriv (GLenum target, GLint level, GLenum pname, GLint *params); +GLAPI GLboolean APIENTRY glIsEnabled (GLenum cap); +GLAPI void APIENTRY glDepthRange (GLdouble n, GLdouble f); +GLAPI void APIENTRY glViewport (GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glNewList (GLuint list, GLenum mode); +GLAPI void APIENTRY glEndList (void); +GLAPI void APIENTRY glCallList (GLuint list); +GLAPI void APIENTRY glCallLists (GLsizei n, GLenum type, const void *lists); +GLAPI void APIENTRY glDeleteLists (GLuint list, GLsizei range); +GLAPI GLuint APIENTRY glGenLists (GLsizei range); +GLAPI void APIENTRY glListBase (GLuint base); +GLAPI void APIENTRY glBegin (GLenum mode); +GLAPI void APIENTRY glBitmap (GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap); +GLAPI void APIENTRY glColor3b (GLbyte red, GLbyte green, GLbyte blue); +GLAPI void APIENTRY glColor3bv (const GLbyte *v); +GLAPI void APIENTRY glColor3d (GLdouble red, GLdouble green, GLdouble blue); +GLAPI void APIENTRY glColor3dv (const GLdouble *v); +GLAPI void APIENTRY glColor3f (GLfloat red, GLfloat green, GLfloat blue); +GLAPI void APIENTRY glColor3fv (const GLfloat *v); +GLAPI void APIENTRY glColor3i (GLint red, GLint green, GLint blue); +GLAPI void APIENTRY glColor3iv (const GLint *v); +GLAPI void APIENTRY glColor3s (GLshort red, GLshort green, GLshort blue); +GLAPI void APIENTRY glColor3sv (const GLshort *v); +GLAPI void APIENTRY glColor3ub (GLubyte red, GLubyte green, GLubyte blue); +GLAPI void APIENTRY glColor3ubv (const GLubyte *v); +GLAPI void APIENTRY glColor3ui (GLuint red, GLuint green, GLuint blue); +GLAPI void APIENTRY glColor3uiv (const GLuint *v); +GLAPI void APIENTRY glColor3us (GLushort red, GLushort green, GLushort blue); +GLAPI void APIENTRY glColor3usv (const GLushort *v); +GLAPI void APIENTRY glColor4b (GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha); +GLAPI void APIENTRY glColor4bv (const GLbyte *v); +GLAPI void APIENTRY glColor4d (GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha); +GLAPI void APIENTRY glColor4dv (const GLdouble *v); +GLAPI void APIENTRY glColor4f (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +GLAPI void APIENTRY glColor4fv (const GLfloat *v); +GLAPI void APIENTRY glColor4i (GLint red, GLint green, GLint blue, GLint alpha); +GLAPI void APIENTRY glColor4iv (const GLint *v); +GLAPI void APIENTRY glColor4s (GLshort red, GLshort green, GLshort blue, GLshort alpha); +GLAPI void APIENTRY glColor4sv (const GLshort *v); +GLAPI void APIENTRY glColor4ub (GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha); +GLAPI void APIENTRY glColor4ubv (const GLubyte *v); +GLAPI void APIENTRY glColor4ui (GLuint red, GLuint green, GLuint blue, GLuint alpha); +GLAPI void APIENTRY glColor4uiv (const GLuint *v); +GLAPI void APIENTRY glColor4us (GLushort red, GLushort green, GLushort blue, GLushort alpha); +GLAPI void APIENTRY glColor4usv (const GLushort *v); +GLAPI void APIENTRY glEdgeFlag (GLboolean flag); +GLAPI void APIENTRY glEdgeFlagv (const GLboolean *flag); +GLAPI void APIENTRY glEnd (void); +GLAPI void APIENTRY glIndexd (GLdouble c); +GLAPI void APIENTRY glIndexdv (const GLdouble *c); +GLAPI void APIENTRY glIndexf (GLfloat c); +GLAPI void APIENTRY glIndexfv (const GLfloat *c); +GLAPI void APIENTRY glIndexi (GLint c); +GLAPI void APIENTRY glIndexiv (const GLint *c); +GLAPI void APIENTRY glIndexs (GLshort c); +GLAPI void APIENTRY glIndexsv (const GLshort *c); +GLAPI void APIENTRY glNormal3b (GLbyte nx, GLbyte ny, GLbyte nz); +GLAPI void APIENTRY glNormal3bv (const GLbyte *v); +GLAPI void APIENTRY glNormal3d (GLdouble nx, GLdouble ny, GLdouble nz); +GLAPI void APIENTRY glNormal3dv (const GLdouble *v); +GLAPI void APIENTRY glNormal3f (GLfloat nx, GLfloat ny, GLfloat nz); +GLAPI void APIENTRY glNormal3fv (const GLfloat *v); +GLAPI void APIENTRY glNormal3i (GLint nx, GLint ny, GLint nz); +GLAPI void APIENTRY glNormal3iv (const GLint *v); +GLAPI void APIENTRY glNormal3s (GLshort nx, GLshort ny, GLshort nz); +GLAPI void APIENTRY glNormal3sv (const GLshort *v); +GLAPI void APIENTRY glRasterPos2d (GLdouble x, GLdouble y); +GLAPI void APIENTRY glRasterPos2dv (const GLdouble *v); +GLAPI void APIENTRY glRasterPos2f (GLfloat x, GLfloat y); +GLAPI void APIENTRY glRasterPos2fv (const GLfloat *v); +GLAPI void APIENTRY glRasterPos2i (GLint x, GLint y); +GLAPI void APIENTRY glRasterPos2iv (const GLint *v); +GLAPI void APIENTRY glRasterPos2s (GLshort x, GLshort y); +GLAPI void APIENTRY glRasterPos2sv (const GLshort *v); +GLAPI void APIENTRY glRasterPos3d (GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glRasterPos3dv (const GLdouble *v); +GLAPI void APIENTRY glRasterPos3f (GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glRasterPos3fv (const GLfloat *v); +GLAPI void APIENTRY glRasterPos3i (GLint x, GLint y, GLint z); +GLAPI void APIENTRY glRasterPos3iv (const GLint *v); +GLAPI void APIENTRY glRasterPos3s (GLshort x, GLshort y, GLshort z); +GLAPI void APIENTRY glRasterPos3sv (const GLshort *v); +GLAPI void APIENTRY glRasterPos4d (GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glRasterPos4dv (const GLdouble *v); +GLAPI void APIENTRY glRasterPos4f (GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glRasterPos4fv (const GLfloat *v); +GLAPI void APIENTRY glRasterPos4i (GLint x, GLint y, GLint z, GLint w); +GLAPI void APIENTRY glRasterPos4iv (const GLint *v); +GLAPI void APIENTRY glRasterPos4s (GLshort x, GLshort y, GLshort z, GLshort w); +GLAPI void APIENTRY glRasterPos4sv (const GLshort *v); +GLAPI void APIENTRY glRectd (GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2); +GLAPI void APIENTRY glRectdv (const GLdouble *v1, const GLdouble *v2); +GLAPI void APIENTRY glRectf (GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2); +GLAPI void APIENTRY glRectfv (const GLfloat *v1, const GLfloat *v2); +GLAPI void APIENTRY glRecti (GLint x1, GLint y1, GLint x2, GLint y2); +GLAPI void APIENTRY glRectiv (const GLint *v1, const GLint *v2); +GLAPI void APIENTRY glRects (GLshort x1, GLshort y1, GLshort x2, GLshort y2); +GLAPI void APIENTRY glRectsv (const GLshort *v1, const GLshort *v2); +GLAPI void APIENTRY glTexCoord1d (GLdouble s); +GLAPI void APIENTRY glTexCoord1dv (const GLdouble *v); +GLAPI void APIENTRY glTexCoord1f (GLfloat s); +GLAPI void APIENTRY glTexCoord1fv (const GLfloat *v); +GLAPI void APIENTRY glTexCoord1i (GLint s); +GLAPI void APIENTRY glTexCoord1iv (const GLint *v); +GLAPI void APIENTRY glTexCoord1s (GLshort s); +GLAPI void APIENTRY glTexCoord1sv (const GLshort *v); +GLAPI void APIENTRY glTexCoord2d (GLdouble s, GLdouble t); +GLAPI void APIENTRY glTexCoord2dv (const GLdouble *v); +GLAPI void APIENTRY glTexCoord2f (GLfloat s, GLfloat t); +GLAPI void APIENTRY glTexCoord2fv (const GLfloat *v); +GLAPI void APIENTRY glTexCoord2i (GLint s, GLint t); +GLAPI void APIENTRY glTexCoord2iv (const GLint *v); +GLAPI void APIENTRY glTexCoord2s (GLshort s, GLshort t); +GLAPI void APIENTRY glTexCoord2sv (const GLshort *v); +GLAPI void APIENTRY glTexCoord3d (GLdouble s, GLdouble t, GLdouble r); +GLAPI void APIENTRY glTexCoord3dv (const GLdouble *v); +GLAPI void APIENTRY glTexCoord3f (GLfloat s, GLfloat t, GLfloat r); +GLAPI void APIENTRY glTexCoord3fv (const GLfloat *v); +GLAPI void APIENTRY glTexCoord3i (GLint s, GLint t, GLint r); +GLAPI void APIENTRY glTexCoord3iv (const GLint *v); +GLAPI void APIENTRY glTexCoord3s (GLshort s, GLshort t, GLshort r); +GLAPI void APIENTRY glTexCoord3sv (const GLshort *v); +GLAPI void APIENTRY glTexCoord4d (GLdouble s, GLdouble t, GLdouble r, GLdouble q); +GLAPI void APIENTRY glTexCoord4dv (const GLdouble *v); +GLAPI void APIENTRY glTexCoord4f (GLfloat s, GLfloat t, GLfloat r, GLfloat q); +GLAPI void APIENTRY glTexCoord4fv (const GLfloat *v); +GLAPI void APIENTRY glTexCoord4i (GLint s, GLint t, GLint r, GLint q); +GLAPI void APIENTRY glTexCoord4iv (const GLint *v); +GLAPI void APIENTRY glTexCoord4s (GLshort s, GLshort t, GLshort r, GLshort q); +GLAPI void APIENTRY glTexCoord4sv (const GLshort *v); +GLAPI void APIENTRY glVertex2d (GLdouble x, GLdouble y); +GLAPI void APIENTRY glVertex2dv (const GLdouble *v); +GLAPI void APIENTRY glVertex2f (GLfloat x, GLfloat y); +GLAPI void APIENTRY glVertex2fv (const GLfloat *v); +GLAPI void APIENTRY glVertex2i (GLint x, GLint y); +GLAPI void APIENTRY glVertex2iv (const GLint *v); +GLAPI void APIENTRY glVertex2s (GLshort x, GLshort y); +GLAPI void APIENTRY glVertex2sv (const GLshort *v); +GLAPI void APIENTRY glVertex3d (GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glVertex3dv (const GLdouble *v); +GLAPI void APIENTRY glVertex3f (GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glVertex3fv (const GLfloat *v); +GLAPI void APIENTRY glVertex3i (GLint x, GLint y, GLint z); +GLAPI void APIENTRY glVertex3iv (const GLint *v); +GLAPI void APIENTRY glVertex3s (GLshort x, GLshort y, GLshort z); +GLAPI void APIENTRY glVertex3sv (const GLshort *v); +GLAPI void APIENTRY glVertex4d (GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glVertex4dv (const GLdouble *v); +GLAPI void APIENTRY glVertex4f (GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glVertex4fv (const GLfloat *v); +GLAPI void APIENTRY glVertex4i (GLint x, GLint y, GLint z, GLint w); +GLAPI void APIENTRY glVertex4iv (const GLint *v); +GLAPI void APIENTRY glVertex4s (GLshort x, GLshort y, GLshort z, GLshort w); +GLAPI void APIENTRY glVertex4sv (const GLshort *v); +GLAPI void APIENTRY glClipPlane (GLenum plane, const GLdouble *equation); +GLAPI void APIENTRY glColorMaterial (GLenum face, GLenum mode); +GLAPI void APIENTRY glFogf (GLenum pname, GLfloat param); +GLAPI void APIENTRY glFogfv (GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glFogi (GLenum pname, GLint param); +GLAPI void APIENTRY glFogiv (GLenum pname, const GLint *params); +GLAPI void APIENTRY glLightf (GLenum light, GLenum pname, GLfloat param); +GLAPI void APIENTRY glLightfv (GLenum light, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glLighti (GLenum light, GLenum pname, GLint param); +GLAPI void APIENTRY glLightiv (GLenum light, GLenum pname, const GLint *params); +GLAPI void APIENTRY glLightModelf (GLenum pname, GLfloat param); +GLAPI void APIENTRY glLightModelfv (GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glLightModeli (GLenum pname, GLint param); +GLAPI void APIENTRY glLightModeliv (GLenum pname, const GLint *params); +GLAPI void APIENTRY glLineStipple (GLint factor, GLushort pattern); +GLAPI void APIENTRY glMaterialf (GLenum face, GLenum pname, GLfloat param); +GLAPI void APIENTRY glMaterialfv (GLenum face, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glMateriali (GLenum face, GLenum pname, GLint param); +GLAPI void APIENTRY glMaterialiv (GLenum face, GLenum pname, const GLint *params); +GLAPI void APIENTRY glPolygonStipple (const GLubyte *mask); +GLAPI void APIENTRY glShadeModel (GLenum mode); +GLAPI void APIENTRY glTexEnvf (GLenum target, GLenum pname, GLfloat param); +GLAPI void APIENTRY glTexEnvfv (GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glTexEnvi (GLenum target, GLenum pname, GLint param); +GLAPI void APIENTRY glTexEnviv (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glTexGend (GLenum coord, GLenum pname, GLdouble param); +GLAPI void APIENTRY glTexGendv (GLenum coord, GLenum pname, const GLdouble *params); +GLAPI void APIENTRY glTexGenf (GLenum coord, GLenum pname, GLfloat param); +GLAPI void APIENTRY glTexGenfv (GLenum coord, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glTexGeni (GLenum coord, GLenum pname, GLint param); +GLAPI void APIENTRY glTexGeniv (GLenum coord, GLenum pname, const GLint *params); +GLAPI void APIENTRY glFeedbackBuffer (GLsizei size, GLenum type, GLfloat *buffer); +GLAPI void APIENTRY glSelectBuffer (GLsizei size, GLuint *buffer); +GLAPI GLint APIENTRY glRenderMode (GLenum mode); +GLAPI void APIENTRY glInitNames (void); +GLAPI void APIENTRY glLoadName (GLuint name); +GLAPI void APIENTRY glPassThrough (GLfloat token); +GLAPI void APIENTRY glPopName (void); +GLAPI void APIENTRY glPushName (GLuint name); +GLAPI void APIENTRY glClearAccum (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +GLAPI void APIENTRY glClearIndex (GLfloat c); +GLAPI void APIENTRY glIndexMask (GLuint mask); +GLAPI void APIENTRY glAccum (GLenum op, GLfloat value); +GLAPI void APIENTRY glPopAttrib (void); +GLAPI void APIENTRY glPushAttrib (GLbitfield mask); +GLAPI void APIENTRY glMap1d (GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points); +GLAPI void APIENTRY glMap1f (GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points); +GLAPI void APIENTRY glMap2d (GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points); +GLAPI void APIENTRY glMap2f (GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points); +GLAPI void APIENTRY glMapGrid1d (GLint un, GLdouble u1, GLdouble u2); +GLAPI void APIENTRY glMapGrid1f (GLint un, GLfloat u1, GLfloat u2); +GLAPI void APIENTRY glMapGrid2d (GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2); +GLAPI void APIENTRY glMapGrid2f (GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2); +GLAPI void APIENTRY glEvalCoord1d (GLdouble u); +GLAPI void APIENTRY glEvalCoord1dv (const GLdouble *u); +GLAPI void APIENTRY glEvalCoord1f (GLfloat u); +GLAPI void APIENTRY glEvalCoord1fv (const GLfloat *u); +GLAPI void APIENTRY glEvalCoord2d (GLdouble u, GLdouble v); +GLAPI void APIENTRY glEvalCoord2dv (const GLdouble *u); +GLAPI void APIENTRY glEvalCoord2f (GLfloat u, GLfloat v); +GLAPI void APIENTRY glEvalCoord2fv (const GLfloat *u); +GLAPI void APIENTRY glEvalMesh1 (GLenum mode, GLint i1, GLint i2); +GLAPI void APIENTRY glEvalPoint1 (GLint i); +GLAPI void APIENTRY glEvalMesh2 (GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2); +GLAPI void APIENTRY glEvalPoint2 (GLint i, GLint j); +GLAPI void APIENTRY glAlphaFunc (GLenum func, GLfloat ref); +GLAPI void APIENTRY glPixelZoom (GLfloat xfactor, GLfloat yfactor); +GLAPI void APIENTRY glPixelTransferf (GLenum pname, GLfloat param); +GLAPI void APIENTRY glPixelTransferi (GLenum pname, GLint param); +GLAPI void APIENTRY glPixelMapfv (GLenum map, GLsizei mapsize, const GLfloat *values); +GLAPI void APIENTRY glPixelMapuiv (GLenum map, GLsizei mapsize, const GLuint *values); +GLAPI void APIENTRY glPixelMapusv (GLenum map, GLsizei mapsize, const GLushort *values); +GLAPI void APIENTRY glCopyPixels (GLint x, GLint y, GLsizei width, GLsizei height, GLenum type); +GLAPI void APIENTRY glDrawPixels (GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glGetClipPlane (GLenum plane, GLdouble *equation); +GLAPI void APIENTRY glGetLightfv (GLenum light, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetLightiv (GLenum light, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetMapdv (GLenum target, GLenum query, GLdouble *v); +GLAPI void APIENTRY glGetMapfv (GLenum target, GLenum query, GLfloat *v); +GLAPI void APIENTRY glGetMapiv (GLenum target, GLenum query, GLint *v); +GLAPI void APIENTRY glGetMaterialfv (GLenum face, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetMaterialiv (GLenum face, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetPixelMapfv (GLenum map, GLfloat *values); +GLAPI void APIENTRY glGetPixelMapuiv (GLenum map, GLuint *values); +GLAPI void APIENTRY glGetPixelMapusv (GLenum map, GLushort *values); +GLAPI void APIENTRY glGetPolygonStipple (GLubyte *mask); +GLAPI void APIENTRY glGetTexEnvfv (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetTexEnviv (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetTexGendv (GLenum coord, GLenum pname, GLdouble *params); +GLAPI void APIENTRY glGetTexGenfv (GLenum coord, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetTexGeniv (GLenum coord, GLenum pname, GLint *params); +GLAPI GLboolean APIENTRY glIsList (GLuint list); +GLAPI void APIENTRY glFrustum (GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +GLAPI void APIENTRY glLoadIdentity (void); +GLAPI void APIENTRY glLoadMatrixf (const GLfloat *m); +GLAPI void APIENTRY glLoadMatrixd (const GLdouble *m); +GLAPI void APIENTRY glMatrixMode (GLenum mode); +GLAPI void APIENTRY glMultMatrixf (const GLfloat *m); +GLAPI void APIENTRY glMultMatrixd (const GLdouble *m); +GLAPI void APIENTRY glOrtho (GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +GLAPI void APIENTRY glPopMatrix (void); +GLAPI void APIENTRY glPushMatrix (void); +GLAPI void APIENTRY glRotated (GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glRotatef (GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glScaled (GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glScalef (GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glTranslated (GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glTranslatef (GLfloat x, GLfloat y, GLfloat z); +#endif +#endif /* GL_VERSION_1_0 */ + +#ifndef GL_VERSION_1_1 +#define GL_VERSION_1_1 1 +typedef khronos_float_t GLclampf; +typedef double GLclampd; +#define GL_COLOR_LOGIC_OP 0x0BF2 +#define GL_POLYGON_OFFSET_UNITS 0x2A00 +#define GL_POLYGON_OFFSET_POINT 0x2A01 +#define GL_POLYGON_OFFSET_LINE 0x2A02 +#define GL_POLYGON_OFFSET_FILL 0x8037 +#define GL_POLYGON_OFFSET_FACTOR 0x8038 +#define GL_TEXTURE_BINDING_1D 0x8068 +#define GL_TEXTURE_BINDING_2D 0x8069 +#define GL_TEXTURE_INTERNAL_FORMAT 0x1003 +#define GL_TEXTURE_RED_SIZE 0x805C +#define GL_TEXTURE_GREEN_SIZE 0x805D +#define GL_TEXTURE_BLUE_SIZE 0x805E +#define GL_TEXTURE_ALPHA_SIZE 0x805F +#define GL_DOUBLE 0x140A +#define GL_PROXY_TEXTURE_1D 0x8063 +#define GL_PROXY_TEXTURE_2D 0x8064 +#define GL_R3_G3_B2 0x2A10 +#define GL_RGB4 0x804F +#define GL_RGB5 0x8050 +#define GL_RGB8 0x8051 +#define GL_RGB10 0x8052 +#define GL_RGB12 0x8053 +#define GL_RGB16 0x8054 +#define GL_RGBA2 0x8055 +#define GL_RGBA4 0x8056 +#define GL_RGB5_A1 0x8057 +#define GL_RGBA8 0x8058 +#define GL_RGB10_A2 0x8059 +#define GL_RGBA12 0x805A +#define GL_RGBA16 0x805B +#define GL_CLIENT_PIXEL_STORE_BIT 0x00000001 +#define GL_CLIENT_VERTEX_ARRAY_BIT 0x00000002 +#define GL_CLIENT_ALL_ATTRIB_BITS 0xFFFFFFFF +#define GL_VERTEX_ARRAY_POINTER 0x808E +#define GL_NORMAL_ARRAY_POINTER 0x808F +#define GL_COLOR_ARRAY_POINTER 0x8090 +#define GL_INDEX_ARRAY_POINTER 0x8091 +#define GL_TEXTURE_COORD_ARRAY_POINTER 0x8092 +#define GL_EDGE_FLAG_ARRAY_POINTER 0x8093 +#define GL_FEEDBACK_BUFFER_POINTER 0x0DF0 +#define GL_SELECTION_BUFFER_POINTER 0x0DF3 +#define GL_CLIENT_ATTRIB_STACK_DEPTH 0x0BB1 +#define GL_INDEX_LOGIC_OP 0x0BF1 +#define GL_MAX_CLIENT_ATTRIB_STACK_DEPTH 0x0D3B +#define GL_FEEDBACK_BUFFER_SIZE 0x0DF1 +#define GL_FEEDBACK_BUFFER_TYPE 0x0DF2 +#define GL_SELECTION_BUFFER_SIZE 0x0DF4 +#define GL_VERTEX_ARRAY 0x8074 +#define GL_NORMAL_ARRAY 0x8075 +#define GL_COLOR_ARRAY 0x8076 +#define GL_INDEX_ARRAY 0x8077 +#define GL_TEXTURE_COORD_ARRAY 0x8078 +#define GL_EDGE_FLAG_ARRAY 0x8079 +#define GL_VERTEX_ARRAY_SIZE 0x807A +#define GL_VERTEX_ARRAY_TYPE 0x807B +#define GL_VERTEX_ARRAY_STRIDE 0x807C +#define GL_NORMAL_ARRAY_TYPE 0x807E +#define GL_NORMAL_ARRAY_STRIDE 0x807F +#define GL_COLOR_ARRAY_SIZE 0x8081 +#define GL_COLOR_ARRAY_TYPE 0x8082 +#define GL_COLOR_ARRAY_STRIDE 0x8083 +#define GL_INDEX_ARRAY_TYPE 0x8085 +#define GL_INDEX_ARRAY_STRIDE 0x8086 +#define GL_TEXTURE_COORD_ARRAY_SIZE 0x8088 +#define GL_TEXTURE_COORD_ARRAY_TYPE 0x8089 +#define GL_TEXTURE_COORD_ARRAY_STRIDE 0x808A +#define GL_EDGE_FLAG_ARRAY_STRIDE 0x808C +#define GL_TEXTURE_LUMINANCE_SIZE 0x8060 +#define GL_TEXTURE_INTENSITY_SIZE 0x8061 +#define GL_TEXTURE_PRIORITY 0x8066 +#define GL_TEXTURE_RESIDENT 0x8067 +#define GL_ALPHA4 0x803B +#define GL_ALPHA8 0x803C +#define GL_ALPHA12 0x803D +#define GL_ALPHA16 0x803E +#define GL_LUMINANCE4 0x803F +#define GL_LUMINANCE8 0x8040 +#define GL_LUMINANCE12 0x8041 +#define GL_LUMINANCE16 0x8042 +#define GL_LUMINANCE4_ALPHA4 0x8043 +#define GL_LUMINANCE6_ALPHA2 0x8044 +#define GL_LUMINANCE8_ALPHA8 0x8045 +#define GL_LUMINANCE12_ALPHA4 0x8046 +#define GL_LUMINANCE12_ALPHA12 0x8047 +#define GL_LUMINANCE16_ALPHA16 0x8048 +#define GL_INTENSITY 0x8049 +#define GL_INTENSITY4 0x804A +#define GL_INTENSITY8 0x804B +#define GL_INTENSITY12 0x804C +#define GL_INTENSITY16 0x804D +#define GL_V2F 0x2A20 +#define GL_V3F 0x2A21 +#define GL_C4UB_V2F 0x2A22 +#define GL_C4UB_V3F 0x2A23 +#define GL_C3F_V3F 0x2A24 +#define GL_N3F_V3F 0x2A25 +#define GL_C4F_N3F_V3F 0x2A26 +#define GL_T2F_V3F 0x2A27 +#define GL_T4F_V4F 0x2A28 +#define GL_T2F_C4UB_V3F 0x2A29 +#define GL_T2F_C3F_V3F 0x2A2A +#define GL_T2F_N3F_V3F 0x2A2B +#define GL_T2F_C4F_N3F_V3F 0x2A2C +#define GL_T4F_C4F_N3F_V4F 0x2A2D +typedef void (APIENTRYP PFNGLDRAWARRAYSPROC) (GLenum mode, GLint first, GLsizei count); +typedef void (APIENTRYP PFNGLDRAWELEMENTSPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices); +typedef void (APIENTRYP PFNGLGETPOINTERVPROC) (GLenum pname, void **params); +typedef void (APIENTRYP PFNGLPOLYGONOFFSETPROC) (GLfloat factor, GLfloat units); +typedef void (APIENTRYP PFNGLCOPYTEXIMAGE1DPROC) (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +typedef void (APIENTRYP PFNGLCOPYTEXIMAGE2DPROC) (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE1DPROC) (GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE2DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLTEXSUBIMAGE1DPROC) (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLTEXSUBIMAGE2DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLBINDTEXTUREPROC) (GLenum target, GLuint texture); +typedef void (APIENTRYP PFNGLDELETETEXTURESPROC) (GLsizei n, const GLuint *textures); +typedef void (APIENTRYP PFNGLGENTEXTURESPROC) (GLsizei n, GLuint *textures); +typedef GLboolean (APIENTRYP PFNGLISTEXTUREPROC) (GLuint texture); +typedef void (APIENTRYP PFNGLARRAYELEMENTPROC) (GLint i); +typedef void (APIENTRYP PFNGLCOLORPOINTERPROC) (GLint size, GLenum type, GLsizei stride, const void *pointer); +typedef void (APIENTRYP PFNGLDISABLECLIENTSTATEPROC) (GLenum array); +typedef void (APIENTRYP PFNGLEDGEFLAGPOINTERPROC) (GLsizei stride, const void *pointer); +typedef void (APIENTRYP PFNGLENABLECLIENTSTATEPROC) (GLenum array); +typedef void (APIENTRYP PFNGLINDEXPOINTERPROC) (GLenum type, GLsizei stride, const void *pointer); +typedef void (APIENTRYP PFNGLINTERLEAVEDARRAYSPROC) (GLenum format, GLsizei stride, const void *pointer); +typedef void (APIENTRYP PFNGLNORMALPOINTERPROC) (GLenum type, GLsizei stride, const void *pointer); +typedef void (APIENTRYP PFNGLTEXCOORDPOINTERPROC) (GLint size, GLenum type, GLsizei stride, const void *pointer); +typedef void (APIENTRYP PFNGLVERTEXPOINTERPROC) (GLint size, GLenum type, GLsizei stride, const void *pointer); +typedef GLboolean (APIENTRYP PFNGLARETEXTURESRESIDENTPROC) (GLsizei n, const GLuint *textures, GLboolean *residences); +typedef void (APIENTRYP PFNGLPRIORITIZETEXTURESPROC) (GLsizei n, const GLuint *textures, const GLfloat *priorities); +typedef void (APIENTRYP PFNGLINDEXUBPROC) (GLubyte c); +typedef void (APIENTRYP PFNGLINDEXUBVPROC) (const GLubyte *c); +typedef void (APIENTRYP PFNGLPOPCLIENTATTRIBPROC) (void); +typedef void (APIENTRYP PFNGLPUSHCLIENTATTRIBPROC) (GLbitfield mask); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawArrays (GLenum mode, GLint first, GLsizei count); +GLAPI void APIENTRY glDrawElements (GLenum mode, GLsizei count, GLenum type, const void *indices); +GLAPI void APIENTRY glGetPointerv (GLenum pname, void **params); +GLAPI void APIENTRY glPolygonOffset (GLfloat factor, GLfloat units); +GLAPI void APIENTRY glCopyTexImage1D (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +GLAPI void APIENTRY glCopyTexImage2D (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +GLAPI void APIENTRY glCopyTexSubImage1D (GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +GLAPI void APIENTRY glCopyTexSubImage2D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glTexSubImage1D (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glTexSubImage2D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glBindTexture (GLenum target, GLuint texture); +GLAPI void APIENTRY glDeleteTextures (GLsizei n, const GLuint *textures); +GLAPI void APIENTRY glGenTextures (GLsizei n, GLuint *textures); +GLAPI GLboolean APIENTRY glIsTexture (GLuint texture); +GLAPI void APIENTRY glArrayElement (GLint i); +GLAPI void APIENTRY glColorPointer (GLint size, GLenum type, GLsizei stride, const void *pointer); +GLAPI void APIENTRY glDisableClientState (GLenum array); +GLAPI void APIENTRY glEdgeFlagPointer (GLsizei stride, const void *pointer); +GLAPI void APIENTRY glEnableClientState (GLenum array); +GLAPI void APIENTRY glIndexPointer (GLenum type, GLsizei stride, const void *pointer); +GLAPI void APIENTRY glInterleavedArrays (GLenum format, GLsizei stride, const void *pointer); +GLAPI void APIENTRY glNormalPointer (GLenum type, GLsizei stride, const void *pointer); +GLAPI void APIENTRY glTexCoordPointer (GLint size, GLenum type, GLsizei stride, const void *pointer); +GLAPI void APIENTRY glVertexPointer (GLint size, GLenum type, GLsizei stride, const void *pointer); +GLAPI GLboolean APIENTRY glAreTexturesResident (GLsizei n, const GLuint *textures, GLboolean *residences); +GLAPI void APIENTRY glPrioritizeTextures (GLsizei n, const GLuint *textures, const GLfloat *priorities); +GLAPI void APIENTRY glIndexub (GLubyte c); +GLAPI void APIENTRY glIndexubv (const GLubyte *c); +GLAPI void APIENTRY glPopClientAttrib (void); +GLAPI void APIENTRY glPushClientAttrib (GLbitfield mask); +#endif +#endif /* GL_VERSION_1_1 */ + +#ifndef GL_VERSION_1_2 +#define GL_VERSION_1_2 1 +#define GL_UNSIGNED_BYTE_3_3_2 0x8032 +#define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 +#define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 +#define GL_UNSIGNED_INT_8_8_8_8 0x8035 +#define GL_UNSIGNED_INT_10_10_10_2 0x8036 +#define GL_TEXTURE_BINDING_3D 0x806A +#define GL_PACK_SKIP_IMAGES 0x806B +#define GL_PACK_IMAGE_HEIGHT 0x806C +#define GL_UNPACK_SKIP_IMAGES 0x806D +#define GL_UNPACK_IMAGE_HEIGHT 0x806E +#define GL_TEXTURE_3D 0x806F +#define GL_PROXY_TEXTURE_3D 0x8070 +#define GL_TEXTURE_DEPTH 0x8071 +#define GL_TEXTURE_WRAP_R 0x8072 +#define GL_MAX_3D_TEXTURE_SIZE 0x8073 +#define GL_UNSIGNED_BYTE_2_3_3_REV 0x8362 +#define GL_UNSIGNED_SHORT_5_6_5 0x8363 +#define GL_UNSIGNED_SHORT_5_6_5_REV 0x8364 +#define GL_UNSIGNED_SHORT_4_4_4_4_REV 0x8365 +#define GL_UNSIGNED_SHORT_1_5_5_5_REV 0x8366 +#define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367 +#define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368 +#define GL_BGR 0x80E0 +#define GL_BGRA 0x80E1 +#define GL_MAX_ELEMENTS_VERTICES 0x80E8 +#define GL_MAX_ELEMENTS_INDICES 0x80E9 +#define GL_CLAMP_TO_EDGE 0x812F +#define GL_TEXTURE_MIN_LOD 0x813A +#define GL_TEXTURE_MAX_LOD 0x813B +#define GL_TEXTURE_BASE_LEVEL 0x813C +#define GL_TEXTURE_MAX_LEVEL 0x813D +#define GL_SMOOTH_POINT_SIZE_RANGE 0x0B12 +#define GL_SMOOTH_POINT_SIZE_GRANULARITY 0x0B13 +#define GL_SMOOTH_LINE_WIDTH_RANGE 0x0B22 +#define GL_SMOOTH_LINE_WIDTH_GRANULARITY 0x0B23 +#define GL_ALIASED_LINE_WIDTH_RANGE 0x846E +#define GL_RESCALE_NORMAL 0x803A +#define GL_LIGHT_MODEL_COLOR_CONTROL 0x81F8 +#define GL_SINGLE_COLOR 0x81F9 +#define GL_SEPARATE_SPECULAR_COLOR 0x81FA +#define GL_ALIASED_POINT_SIZE_RANGE 0x846D +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTSPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void *indices); +typedef void (APIENTRYP PFNGLTEXIMAGE3DPROC) (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLTEXSUBIMAGE3DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE3DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawRangeElements (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void *indices); +GLAPI void APIENTRY glTexImage3D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glTexSubImage3D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glCopyTexSubImage3D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +#endif +#endif /* GL_VERSION_1_2 */ + +#ifndef GL_VERSION_1_3 +#define GL_VERSION_1_3 1 +#define GL_TEXTURE0 0x84C0 +#define GL_TEXTURE1 0x84C1 +#define GL_TEXTURE2 0x84C2 +#define GL_TEXTURE3 0x84C3 +#define GL_TEXTURE4 0x84C4 +#define GL_TEXTURE5 0x84C5 +#define GL_TEXTURE6 0x84C6 +#define GL_TEXTURE7 0x84C7 +#define GL_TEXTURE8 0x84C8 +#define GL_TEXTURE9 0x84C9 +#define GL_TEXTURE10 0x84CA +#define GL_TEXTURE11 0x84CB +#define GL_TEXTURE12 0x84CC +#define GL_TEXTURE13 0x84CD +#define GL_TEXTURE14 0x84CE +#define GL_TEXTURE15 0x84CF +#define GL_TEXTURE16 0x84D0 +#define GL_TEXTURE17 0x84D1 +#define GL_TEXTURE18 0x84D2 +#define GL_TEXTURE19 0x84D3 +#define GL_TEXTURE20 0x84D4 +#define GL_TEXTURE21 0x84D5 +#define GL_TEXTURE22 0x84D6 +#define GL_TEXTURE23 0x84D7 +#define GL_TEXTURE24 0x84D8 +#define GL_TEXTURE25 0x84D9 +#define GL_TEXTURE26 0x84DA +#define GL_TEXTURE27 0x84DB +#define GL_TEXTURE28 0x84DC +#define GL_TEXTURE29 0x84DD +#define GL_TEXTURE30 0x84DE +#define GL_TEXTURE31 0x84DF +#define GL_ACTIVE_TEXTURE 0x84E0 +#define GL_MULTISAMPLE 0x809D +#define GL_SAMPLE_ALPHA_TO_COVERAGE 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE 0x809F +#define GL_SAMPLE_COVERAGE 0x80A0 +#define GL_SAMPLE_BUFFERS 0x80A8 +#define GL_SAMPLES 0x80A9 +#define GL_SAMPLE_COVERAGE_VALUE 0x80AA +#define GL_SAMPLE_COVERAGE_INVERT 0x80AB +#define GL_TEXTURE_CUBE_MAP 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE 0x851C +#define GL_COMPRESSED_RGB 0x84ED +#define GL_COMPRESSED_RGBA 0x84EE +#define GL_TEXTURE_COMPRESSION_HINT 0x84EF +#define GL_TEXTURE_COMPRESSED_IMAGE_SIZE 0x86A0 +#define GL_TEXTURE_COMPRESSED 0x86A1 +#define GL_NUM_COMPRESSED_TEXTURE_FORMATS 0x86A2 +#define GL_COMPRESSED_TEXTURE_FORMATS 0x86A3 +#define GL_CLAMP_TO_BORDER 0x812D +#define GL_CLIENT_ACTIVE_TEXTURE 0x84E1 +#define GL_MAX_TEXTURE_UNITS 0x84E2 +#define GL_TRANSPOSE_MODELVIEW_MATRIX 0x84E3 +#define GL_TRANSPOSE_PROJECTION_MATRIX 0x84E4 +#define GL_TRANSPOSE_TEXTURE_MATRIX 0x84E5 +#define GL_TRANSPOSE_COLOR_MATRIX 0x84E6 +#define GL_MULTISAMPLE_BIT 0x20000000 +#define GL_NORMAL_MAP 0x8511 +#define GL_REFLECTION_MAP 0x8512 +#define GL_COMPRESSED_ALPHA 0x84E9 +#define GL_COMPRESSED_LUMINANCE 0x84EA +#define GL_COMPRESSED_LUMINANCE_ALPHA 0x84EB +#define GL_COMPRESSED_INTENSITY 0x84EC +#define GL_COMBINE 0x8570 +#define GL_COMBINE_RGB 0x8571 +#define GL_COMBINE_ALPHA 0x8572 +#define GL_SOURCE0_RGB 0x8580 +#define GL_SOURCE1_RGB 0x8581 +#define GL_SOURCE2_RGB 0x8582 +#define GL_SOURCE0_ALPHA 0x8588 +#define GL_SOURCE1_ALPHA 0x8589 +#define GL_SOURCE2_ALPHA 0x858A +#define GL_OPERAND0_RGB 0x8590 +#define GL_OPERAND1_RGB 0x8591 +#define GL_OPERAND2_RGB 0x8592 +#define GL_OPERAND0_ALPHA 0x8598 +#define GL_OPERAND1_ALPHA 0x8599 +#define GL_OPERAND2_ALPHA 0x859A +#define GL_RGB_SCALE 0x8573 +#define GL_ADD_SIGNED 0x8574 +#define GL_INTERPOLATE 0x8575 +#define GL_SUBTRACT 0x84E7 +#define GL_CONSTANT 0x8576 +#define GL_PRIMARY_COLOR 0x8577 +#define GL_PREVIOUS 0x8578 +#define GL_DOT3_RGB 0x86AE +#define GL_DOT3_RGBA 0x86AF +typedef void (APIENTRYP PFNGLACTIVETEXTUREPROC) (GLenum texture); +typedef void (APIENTRYP PFNGLSAMPLECOVERAGEPROC) (GLfloat value, GLboolean invert); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE3DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE2DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE1DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE1DPROC) (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *data); +typedef void (APIENTRYP PFNGLGETCOMPRESSEDTEXIMAGEPROC) (GLenum target, GLint level, void *img); +typedef void (APIENTRYP PFNGLCLIENTACTIVETEXTUREPROC) (GLenum texture); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1DPROC) (GLenum target, GLdouble s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1DVPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1FPROC) (GLenum target, GLfloat s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1FVPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1IPROC) (GLenum target, GLint s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1IVPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1SPROC) (GLenum target, GLshort s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1SVPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2DPROC) (GLenum target, GLdouble s, GLdouble t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2DVPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2FPROC) (GLenum target, GLfloat s, GLfloat t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2FVPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2IPROC) (GLenum target, GLint s, GLint t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2IVPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2SPROC) (GLenum target, GLshort s, GLshort t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2SVPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3DPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3DVPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3FPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3FVPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3IPROC) (GLenum target, GLint s, GLint t, GLint r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3IVPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3SPROC) (GLenum target, GLshort s, GLshort t, GLshort r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3SVPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4DPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4DVPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4FPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4FVPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4IPROC) (GLenum target, GLint s, GLint t, GLint r, GLint q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4IVPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4SPROC) (GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4SVPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXFPROC) (const GLfloat *m); +typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXDPROC) (const GLdouble *m); +typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXFPROC) (const GLfloat *m); +typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXDPROC) (const GLdouble *m); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glActiveTexture (GLenum texture); +GLAPI void APIENTRY glSampleCoverage (GLfloat value, GLboolean invert); +GLAPI void APIENTRY glCompressedTexImage3D (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *data); +GLAPI void APIENTRY glCompressedTexImage2D (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *data); +GLAPI void APIENTRY glCompressedTexImage1D (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void *data); +GLAPI void APIENTRY glCompressedTexSubImage3D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *data); +GLAPI void APIENTRY glCompressedTexSubImage2D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data); +GLAPI void APIENTRY glCompressedTexSubImage1D (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *data); +GLAPI void APIENTRY glGetCompressedTexImage (GLenum target, GLint level, void *img); +GLAPI void APIENTRY glClientActiveTexture (GLenum texture); +GLAPI void APIENTRY glMultiTexCoord1d (GLenum target, GLdouble s); +GLAPI void APIENTRY glMultiTexCoord1dv (GLenum target, const GLdouble *v); +GLAPI void APIENTRY glMultiTexCoord1f (GLenum target, GLfloat s); +GLAPI void APIENTRY glMultiTexCoord1fv (GLenum target, const GLfloat *v); +GLAPI void APIENTRY glMultiTexCoord1i (GLenum target, GLint s); +GLAPI void APIENTRY glMultiTexCoord1iv (GLenum target, const GLint *v); +GLAPI void APIENTRY glMultiTexCoord1s (GLenum target, GLshort s); +GLAPI void APIENTRY glMultiTexCoord1sv (GLenum target, const GLshort *v); +GLAPI void APIENTRY glMultiTexCoord2d (GLenum target, GLdouble s, GLdouble t); +GLAPI void APIENTRY glMultiTexCoord2dv (GLenum target, const GLdouble *v); +GLAPI void APIENTRY glMultiTexCoord2f (GLenum target, GLfloat s, GLfloat t); +GLAPI void APIENTRY glMultiTexCoord2fv (GLenum target, const GLfloat *v); +GLAPI void APIENTRY glMultiTexCoord2i (GLenum target, GLint s, GLint t); +GLAPI void APIENTRY glMultiTexCoord2iv (GLenum target, const GLint *v); +GLAPI void APIENTRY glMultiTexCoord2s (GLenum target, GLshort s, GLshort t); +GLAPI void APIENTRY glMultiTexCoord2sv (GLenum target, const GLshort *v); +GLAPI void APIENTRY glMultiTexCoord3d (GLenum target, GLdouble s, GLdouble t, GLdouble r); +GLAPI void APIENTRY glMultiTexCoord3dv (GLenum target, const GLdouble *v); +GLAPI void APIENTRY glMultiTexCoord3f (GLenum target, GLfloat s, GLfloat t, GLfloat r); +GLAPI void APIENTRY glMultiTexCoord3fv (GLenum target, const GLfloat *v); +GLAPI void APIENTRY glMultiTexCoord3i (GLenum target, GLint s, GLint t, GLint r); +GLAPI void APIENTRY glMultiTexCoord3iv (GLenum target, const GLint *v); +GLAPI void APIENTRY glMultiTexCoord3s (GLenum target, GLshort s, GLshort t, GLshort r); +GLAPI void APIENTRY glMultiTexCoord3sv (GLenum target, const GLshort *v); +GLAPI void APIENTRY glMultiTexCoord4d (GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); +GLAPI void APIENTRY glMultiTexCoord4dv (GLenum target, const GLdouble *v); +GLAPI void APIENTRY glMultiTexCoord4f (GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); +GLAPI void APIENTRY glMultiTexCoord4fv (GLenum target, const GLfloat *v); +GLAPI void APIENTRY glMultiTexCoord4i (GLenum target, GLint s, GLint t, GLint r, GLint q); +GLAPI void APIENTRY glMultiTexCoord4iv (GLenum target, const GLint *v); +GLAPI void APIENTRY glMultiTexCoord4s (GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); +GLAPI void APIENTRY glMultiTexCoord4sv (GLenum target, const GLshort *v); +GLAPI void APIENTRY glLoadTransposeMatrixf (const GLfloat *m); +GLAPI void APIENTRY glLoadTransposeMatrixd (const GLdouble *m); +GLAPI void APIENTRY glMultTransposeMatrixf (const GLfloat *m); +GLAPI void APIENTRY glMultTransposeMatrixd (const GLdouble *m); +#endif +#endif /* GL_VERSION_1_3 */ + +#ifndef GL_VERSION_1_4 +#define GL_VERSION_1_4 1 +#define GL_BLEND_DST_RGB 0x80C8 +#define GL_BLEND_SRC_RGB 0x80C9 +#define GL_BLEND_DST_ALPHA 0x80CA +#define GL_BLEND_SRC_ALPHA 0x80CB +#define GL_POINT_FADE_THRESHOLD_SIZE 0x8128 +#define GL_DEPTH_COMPONENT16 0x81A5 +#define GL_DEPTH_COMPONENT24 0x81A6 +#define GL_DEPTH_COMPONENT32 0x81A7 +#define GL_MIRRORED_REPEAT 0x8370 +#define GL_MAX_TEXTURE_LOD_BIAS 0x84FD +#define GL_TEXTURE_LOD_BIAS 0x8501 +#define GL_INCR_WRAP 0x8507 +#define GL_DECR_WRAP 0x8508 +#define GL_TEXTURE_DEPTH_SIZE 0x884A +#define GL_TEXTURE_COMPARE_MODE 0x884C +#define GL_TEXTURE_COMPARE_FUNC 0x884D +#define GL_POINT_SIZE_MIN 0x8126 +#define GL_POINT_SIZE_MAX 0x8127 +#define GL_POINT_DISTANCE_ATTENUATION 0x8129 +#define GL_GENERATE_MIPMAP 0x8191 +#define GL_GENERATE_MIPMAP_HINT 0x8192 +#define GL_FOG_COORDINATE_SOURCE 0x8450 +#define GL_FOG_COORDINATE 0x8451 +#define GL_FRAGMENT_DEPTH 0x8452 +#define GL_CURRENT_FOG_COORDINATE 0x8453 +#define GL_FOG_COORDINATE_ARRAY_TYPE 0x8454 +#define GL_FOG_COORDINATE_ARRAY_STRIDE 0x8455 +#define GL_FOG_COORDINATE_ARRAY_POINTER 0x8456 +#define GL_FOG_COORDINATE_ARRAY 0x8457 +#define GL_COLOR_SUM 0x8458 +#define GL_CURRENT_SECONDARY_COLOR 0x8459 +#define GL_SECONDARY_COLOR_ARRAY_SIZE 0x845A +#define GL_SECONDARY_COLOR_ARRAY_TYPE 0x845B +#define GL_SECONDARY_COLOR_ARRAY_STRIDE 0x845C +#define GL_SECONDARY_COLOR_ARRAY_POINTER 0x845D +#define GL_SECONDARY_COLOR_ARRAY 0x845E +#define GL_TEXTURE_FILTER_CONTROL 0x8500 +#define GL_DEPTH_TEXTURE_MODE 0x884B +#define GL_COMPARE_R_TO_TEXTURE 0x884E +#define GL_BLEND_COLOR 0x8005 +#define GL_BLEND_EQUATION 0x8009 +#define GL_CONSTANT_COLOR 0x8001 +#define GL_ONE_MINUS_CONSTANT_COLOR 0x8002 +#define GL_CONSTANT_ALPHA 0x8003 +#define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004 +#define GL_FUNC_ADD 0x8006 +#define GL_FUNC_REVERSE_SUBTRACT 0x800B +#define GL_FUNC_SUBTRACT 0x800A +#define GL_MIN 0x8007 +#define GL_MAX 0x8008 +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEPROC) (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSPROC) (GLenum mode, const GLint *first, const GLsizei *count, GLsizei drawcount); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSPROC) (GLenum mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei drawcount); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFVPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLPOINTPARAMETERIPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERIVPROC) (GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLFOGCOORDFPROC) (GLfloat coord); +typedef void (APIENTRYP PFNGLFOGCOORDFVPROC) (const GLfloat *coord); +typedef void (APIENTRYP PFNGLFOGCOORDDPROC) (GLdouble coord); +typedef void (APIENTRYP PFNGLFOGCOORDDVPROC) (const GLdouble *coord); +typedef void (APIENTRYP PFNGLFOGCOORDPOINTERPROC) (GLenum type, GLsizei stride, const void *pointer); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3BPROC) (GLbyte red, GLbyte green, GLbyte blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3BVPROC) (const GLbyte *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3DPROC) (GLdouble red, GLdouble green, GLdouble blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3DVPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3FPROC) (GLfloat red, GLfloat green, GLfloat blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3FVPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3IPROC) (GLint red, GLint green, GLint blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3IVPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3SPROC) (GLshort red, GLshort green, GLshort blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3SVPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UBPROC) (GLubyte red, GLubyte green, GLubyte blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UBVPROC) (const GLubyte *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UIPROC) (GLuint red, GLuint green, GLuint blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UIVPROC) (const GLuint *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3USPROC) (GLushort red, GLushort green, GLushort blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3USVPROC) (const GLushort *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLORPOINTERPROC) (GLint size, GLenum type, GLsizei stride, const void *pointer); +typedef void (APIENTRYP PFNGLWINDOWPOS2DPROC) (GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLWINDOWPOS2DVPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2FPROC) (GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLWINDOWPOS2FVPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2IPROC) (GLint x, GLint y); +typedef void (APIENTRYP PFNGLWINDOWPOS2IVPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2SPROC) (GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLWINDOWPOS2SVPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3DPROC) (GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLWINDOWPOS3DVPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3FPROC) (GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLWINDOWPOS3FVPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3IPROC) (GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLWINDOWPOS3IVPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3SPROC) (GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLWINDOWPOS3SVPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLBLENDCOLORPROC) (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +typedef void (APIENTRYP PFNGLBLENDEQUATIONPROC) (GLenum mode); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendFuncSeparate (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +GLAPI void APIENTRY glMultiDrawArrays (GLenum mode, const GLint *first, const GLsizei *count, GLsizei drawcount); +GLAPI void APIENTRY glMultiDrawElements (GLenum mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei drawcount); +GLAPI void APIENTRY glPointParameterf (GLenum pname, GLfloat param); +GLAPI void APIENTRY glPointParameterfv (GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glPointParameteri (GLenum pname, GLint param); +GLAPI void APIENTRY glPointParameteriv (GLenum pname, const GLint *params); +GLAPI void APIENTRY glFogCoordf (GLfloat coord); +GLAPI void APIENTRY glFogCoordfv (const GLfloat *coord); +GLAPI void APIENTRY glFogCoordd (GLdouble coord); +GLAPI void APIENTRY glFogCoorddv (const GLdouble *coord); +GLAPI void APIENTRY glFogCoordPointer (GLenum type, GLsizei stride, const void *pointer); +GLAPI void APIENTRY glSecondaryColor3b (GLbyte red, GLbyte green, GLbyte blue); +GLAPI void APIENTRY glSecondaryColor3bv (const GLbyte *v); +GLAPI void APIENTRY glSecondaryColor3d (GLdouble red, GLdouble green, GLdouble blue); +GLAPI void APIENTRY glSecondaryColor3dv (const GLdouble *v); +GLAPI void APIENTRY glSecondaryColor3f (GLfloat red, GLfloat green, GLfloat blue); +GLAPI void APIENTRY glSecondaryColor3fv (const GLfloat *v); +GLAPI void APIENTRY glSecondaryColor3i (GLint red, GLint green, GLint blue); +GLAPI void APIENTRY glSecondaryColor3iv (const GLint *v); +GLAPI void APIENTRY glSecondaryColor3s (GLshort red, GLshort green, GLshort blue); +GLAPI void APIENTRY glSecondaryColor3sv (const GLshort *v); +GLAPI void APIENTRY glSecondaryColor3ub (GLubyte red, GLubyte green, GLubyte blue); +GLAPI void APIENTRY glSecondaryColor3ubv (const GLubyte *v); +GLAPI void APIENTRY glSecondaryColor3ui (GLuint red, GLuint green, GLuint blue); +GLAPI void APIENTRY glSecondaryColor3uiv (const GLuint *v); +GLAPI void APIENTRY glSecondaryColor3us (GLushort red, GLushort green, GLushort blue); +GLAPI void APIENTRY glSecondaryColor3usv (const GLushort *v); +GLAPI void APIENTRY glSecondaryColorPointer (GLint size, GLenum type, GLsizei stride, const void *pointer); +GLAPI void APIENTRY glWindowPos2d (GLdouble x, GLdouble y); +GLAPI void APIENTRY glWindowPos2dv (const GLdouble *v); +GLAPI void APIENTRY glWindowPos2f (GLfloat x, GLfloat y); +GLAPI void APIENTRY glWindowPos2fv (const GLfloat *v); +GLAPI void APIENTRY glWindowPos2i (GLint x, GLint y); +GLAPI void APIENTRY glWindowPos2iv (const GLint *v); +GLAPI void APIENTRY glWindowPos2s (GLshort x, GLshort y); +GLAPI void APIENTRY glWindowPos2sv (const GLshort *v); +GLAPI void APIENTRY glWindowPos3d (GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glWindowPos3dv (const GLdouble *v); +GLAPI void APIENTRY glWindowPos3f (GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glWindowPos3fv (const GLfloat *v); +GLAPI void APIENTRY glWindowPos3i (GLint x, GLint y, GLint z); +GLAPI void APIENTRY glWindowPos3iv (const GLint *v); +GLAPI void APIENTRY glWindowPos3s (GLshort x, GLshort y, GLshort z); +GLAPI void APIENTRY glWindowPos3sv (const GLshort *v); +GLAPI void APIENTRY glBlendColor (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +GLAPI void APIENTRY glBlendEquation (GLenum mode); +#endif +#endif /* GL_VERSION_1_4 */ + +#ifndef GL_VERSION_1_5 +#define GL_VERSION_1_5 1 +typedef khronos_ssize_t GLsizeiptr; +typedef khronos_intptr_t GLintptr; +#define GL_BUFFER_SIZE 0x8764 +#define GL_BUFFER_USAGE 0x8765 +#define GL_QUERY_COUNTER_BITS 0x8864 +#define GL_CURRENT_QUERY 0x8865 +#define GL_QUERY_RESULT 0x8866 +#define GL_QUERY_RESULT_AVAILABLE 0x8867 +#define GL_ARRAY_BUFFER 0x8892 +#define GL_ELEMENT_ARRAY_BUFFER 0x8893 +#define GL_ARRAY_BUFFER_BINDING 0x8894 +#define GL_ELEMENT_ARRAY_BUFFER_BINDING 0x8895 +#define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING 0x889F +#define GL_READ_ONLY 0x88B8 +#define GL_WRITE_ONLY 0x88B9 +#define GL_READ_WRITE 0x88BA +#define GL_BUFFER_ACCESS 0x88BB +#define GL_BUFFER_MAPPED 0x88BC +#define GL_BUFFER_MAP_POINTER 0x88BD +#define GL_STREAM_DRAW 0x88E0 +#define GL_STREAM_READ 0x88E1 +#define GL_STREAM_COPY 0x88E2 +#define GL_STATIC_DRAW 0x88E4 +#define GL_STATIC_READ 0x88E5 +#define GL_STATIC_COPY 0x88E6 +#define GL_DYNAMIC_DRAW 0x88E8 +#define GL_DYNAMIC_READ 0x88E9 +#define GL_DYNAMIC_COPY 0x88EA +#define GL_SAMPLES_PASSED 0x8914 +#define GL_SRC1_ALPHA 0x8589 +#define GL_VERTEX_ARRAY_BUFFER_BINDING 0x8896 +#define GL_NORMAL_ARRAY_BUFFER_BINDING 0x8897 +#define GL_COLOR_ARRAY_BUFFER_BINDING 0x8898 +#define GL_INDEX_ARRAY_BUFFER_BINDING 0x8899 +#define GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING 0x889A +#define GL_EDGE_FLAG_ARRAY_BUFFER_BINDING 0x889B +#define GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING 0x889C +#define GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING 0x889D +#define GL_WEIGHT_ARRAY_BUFFER_BINDING 0x889E +#define GL_FOG_COORD_SRC 0x8450 +#define GL_FOG_COORD 0x8451 +#define GL_CURRENT_FOG_COORD 0x8453 +#define GL_FOG_COORD_ARRAY_TYPE 0x8454 +#define GL_FOG_COORD_ARRAY_STRIDE 0x8455 +#define GL_FOG_COORD_ARRAY_POINTER 0x8456 +#define GL_FOG_COORD_ARRAY 0x8457 +#define GL_FOG_COORD_ARRAY_BUFFER_BINDING 0x889D +#define GL_SRC0_RGB 0x8580 +#define GL_SRC1_RGB 0x8581 +#define GL_SRC2_RGB 0x8582 +#define GL_SRC0_ALPHA 0x8588 +#define GL_SRC2_ALPHA 0x858A +typedef void (APIENTRYP PFNGLGENQUERIESPROC) (GLsizei n, GLuint *ids); +typedef void (APIENTRYP PFNGLDELETEQUERIESPROC) (GLsizei n, const GLuint *ids); +typedef GLboolean (APIENTRYP PFNGLISQUERYPROC) (GLuint id); +typedef void (APIENTRYP PFNGLBEGINQUERYPROC) (GLenum target, GLuint id); +typedef void (APIENTRYP PFNGLENDQUERYPROC) (GLenum target); +typedef void (APIENTRYP PFNGLGETQUERYIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTIVPROC) (GLuint id, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTUIVPROC) (GLuint id, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLBINDBUFFERPROC) (GLenum target, GLuint buffer); +typedef void (APIENTRYP PFNGLDELETEBUFFERSPROC) (GLsizei n, const GLuint *buffers); +typedef void (APIENTRYP PFNGLGENBUFFERSPROC) (GLsizei n, GLuint *buffers); +typedef GLboolean (APIENTRYP PFNGLISBUFFERPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLBUFFERDATAPROC) (GLenum target, GLsizeiptr size, const void *data, GLenum usage); +typedef void (APIENTRYP PFNGLBUFFERSUBDATAPROC) (GLenum target, GLintptr offset, GLsizeiptr size, const void *data); +typedef void (APIENTRYP PFNGLGETBUFFERSUBDATAPROC) (GLenum target, GLintptr offset, GLsizeiptr size, void *data); +typedef void *(APIENTRYP PFNGLMAPBUFFERPROC) (GLenum target, GLenum access); +typedef GLboolean (APIENTRYP PFNGLUNMAPBUFFERPROC) (GLenum target); +typedef void (APIENTRYP PFNGLGETBUFFERPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETBUFFERPOINTERVPROC) (GLenum target, GLenum pname, void **params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenQueries (GLsizei n, GLuint *ids); +GLAPI void APIENTRY glDeleteQueries (GLsizei n, const GLuint *ids); +GLAPI GLboolean APIENTRY glIsQuery (GLuint id); +GLAPI void APIENTRY glBeginQuery (GLenum target, GLuint id); +GLAPI void APIENTRY glEndQuery (GLenum target); +GLAPI void APIENTRY glGetQueryiv (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetQueryObjectiv (GLuint id, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetQueryObjectuiv (GLuint id, GLenum pname, GLuint *params); +GLAPI void APIENTRY glBindBuffer (GLenum target, GLuint buffer); +GLAPI void APIENTRY glDeleteBuffers (GLsizei n, const GLuint *buffers); +GLAPI void APIENTRY glGenBuffers (GLsizei n, GLuint *buffers); +GLAPI GLboolean APIENTRY glIsBuffer (GLuint buffer); +GLAPI void APIENTRY glBufferData (GLenum target, GLsizeiptr size, const void *data, GLenum usage); +GLAPI void APIENTRY glBufferSubData (GLenum target, GLintptr offset, GLsizeiptr size, const void *data); +GLAPI void APIENTRY glGetBufferSubData (GLenum target, GLintptr offset, GLsizeiptr size, void *data); +GLAPI void *APIENTRY glMapBuffer (GLenum target, GLenum access); +GLAPI GLboolean APIENTRY glUnmapBuffer (GLenum target); +GLAPI void APIENTRY glGetBufferParameteriv (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetBufferPointerv (GLenum target, GLenum pname, void **params); +#endif +#endif /* GL_VERSION_1_5 */ + +#ifndef GL_VERSION_2_0 +#define GL_VERSION_2_0 1 +typedef char GLchar; +#define GL_BLEND_EQUATION_RGB 0x8009 +#define GL_VERTEX_ATTRIB_ARRAY_ENABLED 0x8622 +#define GL_VERTEX_ATTRIB_ARRAY_SIZE 0x8623 +#define GL_VERTEX_ATTRIB_ARRAY_STRIDE 0x8624 +#define GL_VERTEX_ATTRIB_ARRAY_TYPE 0x8625 +#define GL_CURRENT_VERTEX_ATTRIB 0x8626 +#define GL_VERTEX_PROGRAM_POINT_SIZE 0x8642 +#define GL_VERTEX_ATTRIB_ARRAY_POINTER 0x8645 +#define GL_STENCIL_BACK_FUNC 0x8800 +#define GL_STENCIL_BACK_FAIL 0x8801 +#define GL_STENCIL_BACK_PASS_DEPTH_FAIL 0x8802 +#define GL_STENCIL_BACK_PASS_DEPTH_PASS 0x8803 +#define GL_MAX_DRAW_BUFFERS 0x8824 +#define GL_DRAW_BUFFER0 0x8825 +#define GL_DRAW_BUFFER1 0x8826 +#define GL_DRAW_BUFFER2 0x8827 +#define GL_DRAW_BUFFER3 0x8828 +#define GL_DRAW_BUFFER4 0x8829 +#define GL_DRAW_BUFFER5 0x882A +#define GL_DRAW_BUFFER6 0x882B +#define GL_DRAW_BUFFER7 0x882C +#define GL_DRAW_BUFFER8 0x882D +#define GL_DRAW_BUFFER9 0x882E +#define GL_DRAW_BUFFER10 0x882F +#define GL_DRAW_BUFFER11 0x8830 +#define GL_DRAW_BUFFER12 0x8831 +#define GL_DRAW_BUFFER13 0x8832 +#define GL_DRAW_BUFFER14 0x8833 +#define GL_DRAW_BUFFER15 0x8834 +#define GL_BLEND_EQUATION_ALPHA 0x883D +#define GL_MAX_VERTEX_ATTRIBS 0x8869 +#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED 0x886A +#define GL_MAX_TEXTURE_IMAGE_UNITS 0x8872 +#define GL_FRAGMENT_SHADER 0x8B30 +#define GL_VERTEX_SHADER 0x8B31 +#define GL_MAX_FRAGMENT_UNIFORM_COMPONENTS 0x8B49 +#define GL_MAX_VERTEX_UNIFORM_COMPONENTS 0x8B4A +#define GL_MAX_VARYING_FLOATS 0x8B4B +#define GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS 0x8B4C +#define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS 0x8B4D +#define GL_SHADER_TYPE 0x8B4F +#define GL_FLOAT_VEC2 0x8B50 +#define GL_FLOAT_VEC3 0x8B51 +#define GL_FLOAT_VEC4 0x8B52 +#define GL_INT_VEC2 0x8B53 +#define GL_INT_VEC3 0x8B54 +#define GL_INT_VEC4 0x8B55 +#define GL_BOOL 0x8B56 +#define GL_BOOL_VEC2 0x8B57 +#define GL_BOOL_VEC3 0x8B58 +#define GL_BOOL_VEC4 0x8B59 +#define GL_FLOAT_MAT2 0x8B5A +#define GL_FLOAT_MAT3 0x8B5B +#define GL_FLOAT_MAT4 0x8B5C +#define GL_SAMPLER_1D 0x8B5D +#define GL_SAMPLER_2D 0x8B5E +#define GL_SAMPLER_3D 0x8B5F +#define GL_SAMPLER_CUBE 0x8B60 +#define GL_SAMPLER_1D_SHADOW 0x8B61 +#define GL_SAMPLER_2D_SHADOW 0x8B62 +#define GL_DELETE_STATUS 0x8B80 +#define GL_COMPILE_STATUS 0x8B81 +#define GL_LINK_STATUS 0x8B82 +#define GL_VALIDATE_STATUS 0x8B83 +#define GL_INFO_LOG_LENGTH 0x8B84 +#define GL_ATTACHED_SHADERS 0x8B85 +#define GL_ACTIVE_UNIFORMS 0x8B86 +#define GL_ACTIVE_UNIFORM_MAX_LENGTH 0x8B87 +#define GL_SHADER_SOURCE_LENGTH 0x8B88 +#define GL_ACTIVE_ATTRIBUTES 0x8B89 +#define GL_ACTIVE_ATTRIBUTE_MAX_LENGTH 0x8B8A +#define GL_FRAGMENT_SHADER_DERIVATIVE_HINT 0x8B8B +#define GL_SHADING_LANGUAGE_VERSION 0x8B8C +#define GL_CURRENT_PROGRAM 0x8B8D +#define GL_POINT_SPRITE_COORD_ORIGIN 0x8CA0 +#define GL_LOWER_LEFT 0x8CA1 +#define GL_UPPER_LEFT 0x8CA2 +#define GL_STENCIL_BACK_REF 0x8CA3 +#define GL_STENCIL_BACK_VALUE_MASK 0x8CA4 +#define GL_STENCIL_BACK_WRITEMASK 0x8CA5 +#define GL_VERTEX_PROGRAM_TWO_SIDE 0x8643 +#define GL_POINT_SPRITE 0x8861 +#define GL_COORD_REPLACE 0x8862 +#define GL_MAX_TEXTURE_COORDS 0x8871 +typedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEPROC) (GLenum modeRGB, GLenum modeAlpha); +typedef void (APIENTRYP PFNGLDRAWBUFFERSPROC) (GLsizei n, const GLenum *bufs); +typedef void (APIENTRYP PFNGLSTENCILOPSEPARATEPROC) (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass); +typedef void (APIENTRYP PFNGLSTENCILFUNCSEPARATEPROC) (GLenum face, GLenum func, GLint ref, GLuint mask); +typedef void (APIENTRYP PFNGLSTENCILMASKSEPARATEPROC) (GLenum face, GLuint mask); +typedef void (APIENTRYP PFNGLATTACHSHADERPROC) (GLuint program, GLuint shader); +typedef void (APIENTRYP PFNGLBINDATTRIBLOCATIONPROC) (GLuint program, GLuint index, const GLchar *name); +typedef void (APIENTRYP PFNGLCOMPILESHADERPROC) (GLuint shader); +typedef GLuint (APIENTRYP PFNGLCREATEPROGRAMPROC) (void); +typedef GLuint (APIENTRYP PFNGLCREATESHADERPROC) (GLenum type); +typedef void (APIENTRYP PFNGLDELETEPROGRAMPROC) (GLuint program); +typedef void (APIENTRYP PFNGLDELETESHADERPROC) (GLuint shader); +typedef void (APIENTRYP PFNGLDETACHSHADERPROC) (GLuint program, GLuint shader); +typedef void (APIENTRYP PFNGLDISABLEVERTEXATTRIBARRAYPROC) (GLuint index); +typedef void (APIENTRYP PFNGLENABLEVERTEXATTRIBARRAYPROC) (GLuint index); +typedef void (APIENTRYP PFNGLGETACTIVEATTRIBPROC) (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, GLchar *name); +typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMPROC) (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, GLchar *name); +typedef void (APIENTRYP PFNGLGETATTACHEDSHADERSPROC) (GLuint program, GLsizei maxCount, GLsizei *count, GLuint *shaders); +typedef GLint (APIENTRYP PFNGLGETATTRIBLOCATIONPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP PFNGLGETPROGRAMIVPROC) (GLuint program, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMINFOLOGPROC) (GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +typedef void (APIENTRYP PFNGLGETSHADERIVPROC) (GLuint shader, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETSHADERINFOLOGPROC) (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +typedef void (APIENTRYP PFNGLGETSHADERSOURCEPROC) (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *source); +typedef GLint (APIENTRYP PFNGLGETUNIFORMLOCATIONPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP PFNGLGETUNIFORMFVPROC) (GLuint program, GLint location, GLfloat *params); +typedef void (APIENTRYP PFNGLGETUNIFORMIVPROC) (GLuint program, GLint location, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBDVPROC) (GLuint index, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBFVPROC) (GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIVPROC) (GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBPOINTERVPROC) (GLuint index, GLenum pname, void **pointer); +typedef GLboolean (APIENTRYP PFNGLISPROGRAMPROC) (GLuint program); +typedef GLboolean (APIENTRYP PFNGLISSHADERPROC) (GLuint shader); +typedef void (APIENTRYP PFNGLLINKPROGRAMPROC) (GLuint program); +typedef void (APIENTRYP PFNGLSHADERSOURCEPROC) (GLuint shader, GLsizei count, const GLchar *const*string, const GLint *length); +typedef void (APIENTRYP PFNGLUSEPROGRAMPROC) (GLuint program); +typedef void (APIENTRYP PFNGLUNIFORM1FPROC) (GLint location, GLfloat v0); +typedef void (APIENTRYP PFNGLUNIFORM2FPROC) (GLint location, GLfloat v0, GLfloat v1); +typedef void (APIENTRYP PFNGLUNIFORM3FPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +typedef void (APIENTRYP PFNGLUNIFORM4FPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +typedef void (APIENTRYP PFNGLUNIFORM1IPROC) (GLint location, GLint v0); +typedef void (APIENTRYP PFNGLUNIFORM2IPROC) (GLint location, GLint v0, GLint v1); +typedef void (APIENTRYP PFNGLUNIFORM3IPROC) (GLint location, GLint v0, GLint v1, GLint v2); +typedef void (APIENTRYP PFNGLUNIFORM4IPROC) (GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +typedef void (APIENTRYP PFNGLUNIFORM1FVPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM2FVPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM3FVPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM4FVPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM1IVPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM2IVPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM3IVPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM4IVPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLVALIDATEPROGRAMPROC) (GLuint program); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DPROC) (GLuint index, GLdouble x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FPROC) (GLuint index, GLfloat x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SPROC) (GLuint index, GLshort x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DPROC) (GLuint index, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FPROC) (GLuint index, GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SPROC) (GLuint index, GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SPROC) (GLuint index, GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NBVPROC) (GLuint index, const GLbyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NIVPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NSVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUBPROC) (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUBVPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUIVPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUSVPROC) (GLuint index, const GLushort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4BVPROC) (GLuint index, const GLbyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4IVPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SPROC) (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UBVPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UIVPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4USVPROC) (GLuint index, const GLushort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBPOINTERPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendEquationSeparate (GLenum modeRGB, GLenum modeAlpha); +GLAPI void APIENTRY glDrawBuffers (GLsizei n, const GLenum *bufs); +GLAPI void APIENTRY glStencilOpSeparate (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass); +GLAPI void APIENTRY glStencilFuncSeparate (GLenum face, GLenum func, GLint ref, GLuint mask); +GLAPI void APIENTRY glStencilMaskSeparate (GLenum face, GLuint mask); +GLAPI void APIENTRY glAttachShader (GLuint program, GLuint shader); +GLAPI void APIENTRY glBindAttribLocation (GLuint program, GLuint index, const GLchar *name); +GLAPI void APIENTRY glCompileShader (GLuint shader); +GLAPI GLuint APIENTRY glCreateProgram (void); +GLAPI GLuint APIENTRY glCreateShader (GLenum type); +GLAPI void APIENTRY glDeleteProgram (GLuint program); +GLAPI void APIENTRY glDeleteShader (GLuint shader); +GLAPI void APIENTRY glDetachShader (GLuint program, GLuint shader); +GLAPI void APIENTRY glDisableVertexAttribArray (GLuint index); +GLAPI void APIENTRY glEnableVertexAttribArray (GLuint index); +GLAPI void APIENTRY glGetActiveAttrib (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, GLchar *name); +GLAPI void APIENTRY glGetActiveUniform (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, GLchar *name); +GLAPI void APIENTRY glGetAttachedShaders (GLuint program, GLsizei maxCount, GLsizei *count, GLuint *shaders); +GLAPI GLint APIENTRY glGetAttribLocation (GLuint program, const GLchar *name); +GLAPI void APIENTRY glGetProgramiv (GLuint program, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetProgramInfoLog (GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +GLAPI void APIENTRY glGetShaderiv (GLuint shader, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetShaderInfoLog (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +GLAPI void APIENTRY glGetShaderSource (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *source); +GLAPI GLint APIENTRY glGetUniformLocation (GLuint program, const GLchar *name); +GLAPI void APIENTRY glGetUniformfv (GLuint program, GLint location, GLfloat *params); +GLAPI void APIENTRY glGetUniformiv (GLuint program, GLint location, GLint *params); +GLAPI void APIENTRY glGetVertexAttribdv (GLuint index, GLenum pname, GLdouble *params); +GLAPI void APIENTRY glGetVertexAttribfv (GLuint index, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetVertexAttribiv (GLuint index, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVertexAttribPointerv (GLuint index, GLenum pname, void **pointer); +GLAPI GLboolean APIENTRY glIsProgram (GLuint program); +GLAPI GLboolean APIENTRY glIsShader (GLuint shader); +GLAPI void APIENTRY glLinkProgram (GLuint program); +GLAPI void APIENTRY glShaderSource (GLuint shader, GLsizei count, const GLchar *const*string, const GLint *length); +GLAPI void APIENTRY glUseProgram (GLuint program); +GLAPI void APIENTRY glUniform1f (GLint location, GLfloat v0); +GLAPI void APIENTRY glUniform2f (GLint location, GLfloat v0, GLfloat v1); +GLAPI void APIENTRY glUniform3f (GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +GLAPI void APIENTRY glUniform4f (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +GLAPI void APIENTRY glUniform1i (GLint location, GLint v0); +GLAPI void APIENTRY glUniform2i (GLint location, GLint v0, GLint v1); +GLAPI void APIENTRY glUniform3i (GLint location, GLint v0, GLint v1, GLint v2); +GLAPI void APIENTRY glUniform4i (GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +GLAPI void APIENTRY glUniform1fv (GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glUniform2fv (GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glUniform3fv (GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glUniform4fv (GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glUniform1iv (GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glUniform2iv (GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glUniform3iv (GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glUniform4iv (GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glUniformMatrix2fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix3fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix4fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glValidateProgram (GLuint program); +GLAPI void APIENTRY glVertexAttrib1d (GLuint index, GLdouble x); +GLAPI void APIENTRY glVertexAttrib1dv (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib1f (GLuint index, GLfloat x); +GLAPI void APIENTRY glVertexAttrib1fv (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib1s (GLuint index, GLshort x); +GLAPI void APIENTRY glVertexAttrib1sv (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib2d (GLuint index, GLdouble x, GLdouble y); +GLAPI void APIENTRY glVertexAttrib2dv (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib2f (GLuint index, GLfloat x, GLfloat y); +GLAPI void APIENTRY glVertexAttrib2fv (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib2s (GLuint index, GLshort x, GLshort y); +GLAPI void APIENTRY glVertexAttrib2sv (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib3d (GLuint index, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glVertexAttrib3dv (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib3f (GLuint index, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glVertexAttrib3fv (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib3s (GLuint index, GLshort x, GLshort y, GLshort z); +GLAPI void APIENTRY glVertexAttrib3sv (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib4Nbv (GLuint index, const GLbyte *v); +GLAPI void APIENTRY glVertexAttrib4Niv (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttrib4Nsv (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib4Nub (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +GLAPI void APIENTRY glVertexAttrib4Nubv (GLuint index, const GLubyte *v); +GLAPI void APIENTRY glVertexAttrib4Nuiv (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttrib4Nusv (GLuint index, const GLushort *v); +GLAPI void APIENTRY glVertexAttrib4bv (GLuint index, const GLbyte *v); +GLAPI void APIENTRY glVertexAttrib4d (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glVertexAttrib4dv (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib4f (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glVertexAttrib4fv (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib4iv (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttrib4s (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +GLAPI void APIENTRY glVertexAttrib4sv (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib4ubv (GLuint index, const GLubyte *v); +GLAPI void APIENTRY glVertexAttrib4uiv (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttrib4usv (GLuint index, const GLushort *v); +GLAPI void APIENTRY glVertexAttribPointer (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer); +#endif +#endif /* GL_VERSION_2_0 */ + +#ifndef GL_VERSION_2_1 +#define GL_VERSION_2_1 1 +#define GL_PIXEL_PACK_BUFFER 0x88EB +#define GL_PIXEL_UNPACK_BUFFER 0x88EC +#define GL_PIXEL_PACK_BUFFER_BINDING 0x88ED +#define GL_PIXEL_UNPACK_BUFFER_BINDING 0x88EF +#define GL_FLOAT_MAT2x3 0x8B65 +#define GL_FLOAT_MAT2x4 0x8B66 +#define GL_FLOAT_MAT3x2 0x8B67 +#define GL_FLOAT_MAT3x4 0x8B68 +#define GL_FLOAT_MAT4x2 0x8B69 +#define GL_FLOAT_MAT4x3 0x8B6A +#define GL_SRGB 0x8C40 +#define GL_SRGB8 0x8C41 +#define GL_SRGB_ALPHA 0x8C42 +#define GL_SRGB8_ALPHA8 0x8C43 +#define GL_COMPRESSED_SRGB 0x8C48 +#define GL_COMPRESSED_SRGB_ALPHA 0x8C49 +#define GL_CURRENT_RASTER_SECONDARY_COLOR 0x845F +#define GL_SLUMINANCE_ALPHA 0x8C44 +#define GL_SLUMINANCE8_ALPHA8 0x8C45 +#define GL_SLUMINANCE 0x8C46 +#define GL_SLUMINANCE8 0x8C47 +#define GL_COMPRESSED_SLUMINANCE 0x8C4A +#define GL_COMPRESSED_SLUMINANCE_ALPHA 0x8C4B +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2X3FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3X2FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2X4FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4X2FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3X4FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4X3FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glUniformMatrix2x3fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix3x2fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix2x4fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix4x2fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix3x4fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix4x3fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +#endif +#endif /* GL_VERSION_2_1 */ + +#ifndef GL_VERSION_3_0 +#define GL_VERSION_3_0 1 +typedef khronos_uint16_t GLhalf; +#define GL_COMPARE_REF_TO_TEXTURE 0x884E +#define GL_CLIP_DISTANCE0 0x3000 +#define GL_CLIP_DISTANCE1 0x3001 +#define GL_CLIP_DISTANCE2 0x3002 +#define GL_CLIP_DISTANCE3 0x3003 +#define GL_CLIP_DISTANCE4 0x3004 +#define GL_CLIP_DISTANCE5 0x3005 +#define GL_CLIP_DISTANCE6 0x3006 +#define GL_CLIP_DISTANCE7 0x3007 +#define GL_MAX_CLIP_DISTANCES 0x0D32 +#define GL_MAJOR_VERSION 0x821B +#define GL_MINOR_VERSION 0x821C +#define GL_NUM_EXTENSIONS 0x821D +#define GL_CONTEXT_FLAGS 0x821E +#define GL_COMPRESSED_RED 0x8225 +#define GL_COMPRESSED_RG 0x8226 +#define GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT 0x00000001 +#define GL_RGBA32F 0x8814 +#define GL_RGB32F 0x8815 +#define GL_RGBA16F 0x881A +#define GL_RGB16F 0x881B +#define GL_VERTEX_ATTRIB_ARRAY_INTEGER 0x88FD +#define GL_MAX_ARRAY_TEXTURE_LAYERS 0x88FF +#define GL_MIN_PROGRAM_TEXEL_OFFSET 0x8904 +#define GL_MAX_PROGRAM_TEXEL_OFFSET 0x8905 +#define GL_CLAMP_READ_COLOR 0x891C +#define GL_FIXED_ONLY 0x891D +#define GL_MAX_VARYING_COMPONENTS 0x8B4B +#define GL_TEXTURE_1D_ARRAY 0x8C18 +#define GL_PROXY_TEXTURE_1D_ARRAY 0x8C19 +#define GL_TEXTURE_2D_ARRAY 0x8C1A +#define GL_PROXY_TEXTURE_2D_ARRAY 0x8C1B +#define GL_TEXTURE_BINDING_1D_ARRAY 0x8C1C +#define GL_TEXTURE_BINDING_2D_ARRAY 0x8C1D +#define GL_R11F_G11F_B10F 0x8C3A +#define GL_UNSIGNED_INT_10F_11F_11F_REV 0x8C3B +#define GL_RGB9_E5 0x8C3D +#define GL_UNSIGNED_INT_5_9_9_9_REV 0x8C3E +#define GL_TEXTURE_SHARED_SIZE 0x8C3F +#define GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH 0x8C76 +#define GL_TRANSFORM_FEEDBACK_BUFFER_MODE 0x8C7F +#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS 0x8C80 +#define GL_TRANSFORM_FEEDBACK_VARYINGS 0x8C83 +#define GL_TRANSFORM_FEEDBACK_BUFFER_START 0x8C84 +#define GL_TRANSFORM_FEEDBACK_BUFFER_SIZE 0x8C85 +#define GL_PRIMITIVES_GENERATED 0x8C87 +#define GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN 0x8C88 +#define GL_RASTERIZER_DISCARD 0x8C89 +#define GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS 0x8C8A +#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS 0x8C8B +#define GL_INTERLEAVED_ATTRIBS 0x8C8C +#define GL_SEPARATE_ATTRIBS 0x8C8D +#define GL_TRANSFORM_FEEDBACK_BUFFER 0x8C8E +#define GL_TRANSFORM_FEEDBACK_BUFFER_BINDING 0x8C8F +#define GL_RGBA32UI 0x8D70 +#define GL_RGB32UI 0x8D71 +#define GL_RGBA16UI 0x8D76 +#define GL_RGB16UI 0x8D77 +#define GL_RGBA8UI 0x8D7C +#define GL_RGB8UI 0x8D7D +#define GL_RGBA32I 0x8D82 +#define GL_RGB32I 0x8D83 +#define GL_RGBA16I 0x8D88 +#define GL_RGB16I 0x8D89 +#define GL_RGBA8I 0x8D8E +#define GL_RGB8I 0x8D8F +#define GL_RED_INTEGER 0x8D94 +#define GL_GREEN_INTEGER 0x8D95 +#define GL_BLUE_INTEGER 0x8D96 +#define GL_RGB_INTEGER 0x8D98 +#define GL_RGBA_INTEGER 0x8D99 +#define GL_BGR_INTEGER 0x8D9A +#define GL_BGRA_INTEGER 0x8D9B +#define GL_SAMPLER_1D_ARRAY 0x8DC0 +#define GL_SAMPLER_2D_ARRAY 0x8DC1 +#define GL_SAMPLER_1D_ARRAY_SHADOW 0x8DC3 +#define GL_SAMPLER_2D_ARRAY_SHADOW 0x8DC4 +#define GL_SAMPLER_CUBE_SHADOW 0x8DC5 +#define GL_UNSIGNED_INT_VEC2 0x8DC6 +#define GL_UNSIGNED_INT_VEC3 0x8DC7 +#define GL_UNSIGNED_INT_VEC4 0x8DC8 +#define GL_INT_SAMPLER_1D 0x8DC9 +#define GL_INT_SAMPLER_2D 0x8DCA +#define GL_INT_SAMPLER_3D 0x8DCB +#define GL_INT_SAMPLER_CUBE 0x8DCC +#define GL_INT_SAMPLER_1D_ARRAY 0x8DCE +#define GL_INT_SAMPLER_2D_ARRAY 0x8DCF +#define GL_UNSIGNED_INT_SAMPLER_1D 0x8DD1 +#define GL_UNSIGNED_INT_SAMPLER_2D 0x8DD2 +#define GL_UNSIGNED_INT_SAMPLER_3D 0x8DD3 +#define GL_UNSIGNED_INT_SAMPLER_CUBE 0x8DD4 +#define GL_UNSIGNED_INT_SAMPLER_1D_ARRAY 0x8DD6 +#define GL_UNSIGNED_INT_SAMPLER_2D_ARRAY 0x8DD7 +#define GL_QUERY_WAIT 0x8E13 +#define GL_QUERY_NO_WAIT 0x8E14 +#define GL_QUERY_BY_REGION_WAIT 0x8E15 +#define GL_QUERY_BY_REGION_NO_WAIT 0x8E16 +#define GL_BUFFER_ACCESS_FLAGS 0x911F +#define GL_BUFFER_MAP_LENGTH 0x9120 +#define GL_BUFFER_MAP_OFFSET 0x9121 +#define GL_DEPTH_COMPONENT32F 0x8CAC +#define GL_DEPTH32F_STENCIL8 0x8CAD +#define GL_FLOAT_32_UNSIGNED_INT_24_8_REV 0x8DAD +#define GL_INVALID_FRAMEBUFFER_OPERATION 0x0506 +#define GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING 0x8210 +#define GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE 0x8211 +#define GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE 0x8212 +#define GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE 0x8213 +#define GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE 0x8214 +#define GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE 0x8215 +#define GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE 0x8216 +#define GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE 0x8217 +#define GL_FRAMEBUFFER_DEFAULT 0x8218 +#define GL_FRAMEBUFFER_UNDEFINED 0x8219 +#define GL_DEPTH_STENCIL_ATTACHMENT 0x821A +#define GL_MAX_RENDERBUFFER_SIZE 0x84E8 +#define GL_DEPTH_STENCIL 0x84F9 +#define GL_UNSIGNED_INT_24_8 0x84FA +#define GL_DEPTH24_STENCIL8 0x88F0 +#define GL_TEXTURE_STENCIL_SIZE 0x88F1 +#define GL_TEXTURE_RED_TYPE 0x8C10 +#define GL_TEXTURE_GREEN_TYPE 0x8C11 +#define GL_TEXTURE_BLUE_TYPE 0x8C12 +#define GL_TEXTURE_ALPHA_TYPE 0x8C13 +#define GL_TEXTURE_DEPTH_TYPE 0x8C16 +#define GL_UNSIGNED_NORMALIZED 0x8C17 +#define GL_FRAMEBUFFER_BINDING 0x8CA6 +#define GL_DRAW_FRAMEBUFFER_BINDING 0x8CA6 +#define GL_RENDERBUFFER_BINDING 0x8CA7 +#define GL_READ_FRAMEBUFFER 0x8CA8 +#define GL_DRAW_FRAMEBUFFER 0x8CA9 +#define GL_READ_FRAMEBUFFER_BINDING 0x8CAA +#define GL_RENDERBUFFER_SAMPLES 0x8CAB +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE 0x8CD0 +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME 0x8CD1 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL 0x8CD2 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE 0x8CD3 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER 0x8CD4 +#define GL_FRAMEBUFFER_COMPLETE 0x8CD5 +#define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT 0x8CD6 +#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT 0x8CD7 +#define GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER 0x8CDB +#define GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER 0x8CDC +#define GL_FRAMEBUFFER_UNSUPPORTED 0x8CDD +#define GL_MAX_COLOR_ATTACHMENTS 0x8CDF +#define GL_COLOR_ATTACHMENT0 0x8CE0 +#define GL_COLOR_ATTACHMENT1 0x8CE1 +#define GL_COLOR_ATTACHMENT2 0x8CE2 +#define GL_COLOR_ATTACHMENT3 0x8CE3 +#define GL_COLOR_ATTACHMENT4 0x8CE4 +#define GL_COLOR_ATTACHMENT5 0x8CE5 +#define GL_COLOR_ATTACHMENT6 0x8CE6 +#define GL_COLOR_ATTACHMENT7 0x8CE7 +#define GL_COLOR_ATTACHMENT8 0x8CE8 +#define GL_COLOR_ATTACHMENT9 0x8CE9 +#define GL_COLOR_ATTACHMENT10 0x8CEA +#define GL_COLOR_ATTACHMENT11 0x8CEB +#define GL_COLOR_ATTACHMENT12 0x8CEC +#define GL_COLOR_ATTACHMENT13 0x8CED +#define GL_COLOR_ATTACHMENT14 0x8CEE +#define GL_COLOR_ATTACHMENT15 0x8CEF +#define GL_COLOR_ATTACHMENT16 0x8CF0 +#define GL_COLOR_ATTACHMENT17 0x8CF1 +#define GL_COLOR_ATTACHMENT18 0x8CF2 +#define GL_COLOR_ATTACHMENT19 0x8CF3 +#define GL_COLOR_ATTACHMENT20 0x8CF4 +#define GL_COLOR_ATTACHMENT21 0x8CF5 +#define GL_COLOR_ATTACHMENT22 0x8CF6 +#define GL_COLOR_ATTACHMENT23 0x8CF7 +#define GL_COLOR_ATTACHMENT24 0x8CF8 +#define GL_COLOR_ATTACHMENT25 0x8CF9 +#define GL_COLOR_ATTACHMENT26 0x8CFA +#define GL_COLOR_ATTACHMENT27 0x8CFB +#define GL_COLOR_ATTACHMENT28 0x8CFC +#define GL_COLOR_ATTACHMENT29 0x8CFD +#define GL_COLOR_ATTACHMENT30 0x8CFE +#define GL_COLOR_ATTACHMENT31 0x8CFF +#define GL_DEPTH_ATTACHMENT 0x8D00 +#define GL_STENCIL_ATTACHMENT 0x8D20 +#define GL_FRAMEBUFFER 0x8D40 +#define GL_RENDERBUFFER 0x8D41 +#define GL_RENDERBUFFER_WIDTH 0x8D42 +#define GL_RENDERBUFFER_HEIGHT 0x8D43 +#define GL_RENDERBUFFER_INTERNAL_FORMAT 0x8D44 +#define GL_STENCIL_INDEX1 0x8D46 +#define GL_STENCIL_INDEX4 0x8D47 +#define GL_STENCIL_INDEX8 0x8D48 +#define GL_STENCIL_INDEX16 0x8D49 +#define GL_RENDERBUFFER_RED_SIZE 0x8D50 +#define GL_RENDERBUFFER_GREEN_SIZE 0x8D51 +#define GL_RENDERBUFFER_BLUE_SIZE 0x8D52 +#define GL_RENDERBUFFER_ALPHA_SIZE 0x8D53 +#define GL_RENDERBUFFER_DEPTH_SIZE 0x8D54 +#define GL_RENDERBUFFER_STENCIL_SIZE 0x8D55 +#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE 0x8D56 +#define GL_MAX_SAMPLES 0x8D57 +#define GL_INDEX 0x8222 +#define GL_TEXTURE_LUMINANCE_TYPE 0x8C14 +#define GL_TEXTURE_INTENSITY_TYPE 0x8C15 +#define GL_FRAMEBUFFER_SRGB 0x8DB9 +#define GL_HALF_FLOAT 0x140B +#define GL_MAP_READ_BIT 0x0001 +#define GL_MAP_WRITE_BIT 0x0002 +#define GL_MAP_INVALIDATE_RANGE_BIT 0x0004 +#define GL_MAP_INVALIDATE_BUFFER_BIT 0x0008 +#define GL_MAP_FLUSH_EXPLICIT_BIT 0x0010 +#define GL_MAP_UNSYNCHRONIZED_BIT 0x0020 +#define GL_COMPRESSED_RED_RGTC1 0x8DBB +#define GL_COMPRESSED_SIGNED_RED_RGTC1 0x8DBC +#define GL_COMPRESSED_RG_RGTC2 0x8DBD +#define GL_COMPRESSED_SIGNED_RG_RGTC2 0x8DBE +#define GL_RG 0x8227 +#define GL_RG_INTEGER 0x8228 +#define GL_R8 0x8229 +#define GL_R16 0x822A +#define GL_RG8 0x822B +#define GL_RG16 0x822C +#define GL_R16F 0x822D +#define GL_R32F 0x822E +#define GL_RG16F 0x822F +#define GL_RG32F 0x8230 +#define GL_R8I 0x8231 +#define GL_R8UI 0x8232 +#define GL_R16I 0x8233 +#define GL_R16UI 0x8234 +#define GL_R32I 0x8235 +#define GL_R32UI 0x8236 +#define GL_RG8I 0x8237 +#define GL_RG8UI 0x8238 +#define GL_RG16I 0x8239 +#define GL_RG16UI 0x823A +#define GL_RG32I 0x823B +#define GL_RG32UI 0x823C +#define GL_VERTEX_ARRAY_BINDING 0x85B5 +#define GL_CLAMP_VERTEX_COLOR 0x891A +#define GL_CLAMP_FRAGMENT_COLOR 0x891B +#define GL_ALPHA_INTEGER 0x8D97 +typedef void (APIENTRYP PFNGLCOLORMASKIPROC) (GLuint index, GLboolean r, GLboolean g, GLboolean b, GLboolean a); +typedef void (APIENTRYP PFNGLGETBOOLEANI_VPROC) (GLenum target, GLuint index, GLboolean *data); +typedef void (APIENTRYP PFNGLGETINTEGERI_VPROC) (GLenum target, GLuint index, GLint *data); +typedef void (APIENTRYP PFNGLENABLEIPROC) (GLenum target, GLuint index); +typedef void (APIENTRYP PFNGLDISABLEIPROC) (GLenum target, GLuint index); +typedef GLboolean (APIENTRYP PFNGLISENABLEDIPROC) (GLenum target, GLuint index); +typedef void (APIENTRYP PFNGLBEGINTRANSFORMFEEDBACKPROC) (GLenum primitiveMode); +typedef void (APIENTRYP PFNGLENDTRANSFORMFEEDBACKPROC) (void); +typedef void (APIENTRYP PFNGLBINDBUFFERRANGEPROC) (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); +typedef void (APIENTRYP PFNGLBINDBUFFERBASEPROC) (GLenum target, GLuint index, GLuint buffer); +typedef void (APIENTRYP PFNGLTRANSFORMFEEDBACKVARYINGSPROC) (GLuint program, GLsizei count, const GLchar *const*varyings, GLenum bufferMode); +typedef void (APIENTRYP PFNGLGETTRANSFORMFEEDBACKVARYINGPROC) (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLsizei *size, GLenum *type, GLchar *name); +typedef void (APIENTRYP PFNGLCLAMPCOLORPROC) (GLenum target, GLenum clamp); +typedef void (APIENTRYP PFNGLBEGINCONDITIONALRENDERPROC) (GLuint id, GLenum mode); +typedef void (APIENTRYP PFNGLENDCONDITIONALRENDERPROC) (void); +typedef void (APIENTRYP PFNGLVERTEXATTRIBIPOINTERPROC) (GLuint index, GLint size, GLenum type, GLsizei stride, const void *pointer); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIIVPROC) (GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIUIVPROC) (GLuint index, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI1IPROC) (GLuint index, GLint x); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI2IPROC) (GLuint index, GLint x, GLint y); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI3IPROC) (GLuint index, GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4IPROC) (GLuint index, GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI1UIPROC) (GLuint index, GLuint x); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI2UIPROC) (GLuint index, GLuint x, GLuint y); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI3UIPROC) (GLuint index, GLuint x, GLuint y, GLuint z); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4UIPROC) (GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI1IVPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI2IVPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI3IVPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4IVPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI1UIVPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI2UIVPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI3UIVPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4UIVPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4BVPROC) (GLuint index, const GLbyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4SVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4UBVPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4USVPROC) (GLuint index, const GLushort *v); +typedef void (APIENTRYP PFNGLGETUNIFORMUIVPROC) (GLuint program, GLint location, GLuint *params); +typedef void (APIENTRYP PFNGLBINDFRAGDATALOCATIONPROC) (GLuint program, GLuint color, const GLchar *name); +typedef GLint (APIENTRYP PFNGLGETFRAGDATALOCATIONPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP PFNGLUNIFORM1UIPROC) (GLint location, GLuint v0); +typedef void (APIENTRYP PFNGLUNIFORM2UIPROC) (GLint location, GLuint v0, GLuint v1); +typedef void (APIENTRYP PFNGLUNIFORM3UIPROC) (GLint location, GLuint v0, GLuint v1, GLuint v2); +typedef void (APIENTRYP PFNGLUNIFORM4UIPROC) (GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +typedef void (APIENTRYP PFNGLUNIFORM1UIVPROC) (GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLUNIFORM2UIVPROC) (GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLUNIFORM3UIVPROC) (GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLUNIFORM4UIVPROC) (GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLTEXPARAMETERIIVPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLTEXPARAMETERIUIVPROC) (GLenum target, GLenum pname, const GLuint *params); +typedef void (APIENTRYP PFNGLGETTEXPARAMETERIIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETTEXPARAMETERIUIVPROC) (GLenum target, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLCLEARBUFFERIVPROC) (GLenum buffer, GLint drawbuffer, const GLint *value); +typedef void (APIENTRYP PFNGLCLEARBUFFERUIVPROC) (GLenum buffer, GLint drawbuffer, const GLuint *value); +typedef void (APIENTRYP PFNGLCLEARBUFFERFVPROC) (GLenum buffer, GLint drawbuffer, const GLfloat *value); +typedef void (APIENTRYP PFNGLCLEARBUFFERFIPROC) (GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil); +typedef const GLubyte *(APIENTRYP PFNGLGETSTRINGIPROC) (GLenum name, GLuint index); +typedef GLboolean (APIENTRYP PFNGLISRENDERBUFFERPROC) (GLuint renderbuffer); +typedef void (APIENTRYP PFNGLBINDRENDERBUFFERPROC) (GLenum target, GLuint renderbuffer); +typedef void (APIENTRYP PFNGLDELETERENDERBUFFERSPROC) (GLsizei n, const GLuint *renderbuffers); +typedef void (APIENTRYP PFNGLGENRENDERBUFFERSPROC) (GLsizei n, GLuint *renderbuffers); +typedef void (APIENTRYP PFNGLRENDERBUFFERSTORAGEPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETRENDERBUFFERPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef GLboolean (APIENTRYP PFNGLISFRAMEBUFFERPROC) (GLuint framebuffer); +typedef void (APIENTRYP PFNGLBINDFRAMEBUFFERPROC) (GLenum target, GLuint framebuffer); +typedef void (APIENTRYP PFNGLDELETEFRAMEBUFFERSPROC) (GLsizei n, const GLuint *framebuffers); +typedef void (APIENTRYP PFNGLGENFRAMEBUFFERSPROC) (GLsizei n, GLuint *framebuffers); +typedef GLenum (APIENTRYP PFNGLCHECKFRAMEBUFFERSTATUSPROC) (GLenum target); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE1DPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE2DPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE3DPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); +typedef void (APIENTRYP PFNGLFRAMEBUFFERRENDERBUFFERPROC) (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +typedef void (APIENTRYP PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC) (GLenum target, GLenum attachment, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGENERATEMIPMAPPROC) (GLenum target); +typedef void (APIENTRYP PFNGLBLITFRAMEBUFFERPROC) (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +typedef void (APIENTRYP PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURELAYERPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); +typedef void *(APIENTRYP PFNGLMAPBUFFERRANGEPROC) (GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access); +typedef void (APIENTRYP PFNGLFLUSHMAPPEDBUFFERRANGEPROC) (GLenum target, GLintptr offset, GLsizeiptr length); +typedef void (APIENTRYP PFNGLBINDVERTEXARRAYPROC) (GLuint array); +typedef void (APIENTRYP PFNGLDELETEVERTEXARRAYSPROC) (GLsizei n, const GLuint *arrays); +typedef void (APIENTRYP PFNGLGENVERTEXARRAYSPROC) (GLsizei n, GLuint *arrays); +typedef GLboolean (APIENTRYP PFNGLISVERTEXARRAYPROC) (GLuint array); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorMaski (GLuint index, GLboolean r, GLboolean g, GLboolean b, GLboolean a); +GLAPI void APIENTRY glGetBooleani_v (GLenum target, GLuint index, GLboolean *data); +GLAPI void APIENTRY glGetIntegeri_v (GLenum target, GLuint index, GLint *data); +GLAPI void APIENTRY glEnablei (GLenum target, GLuint index); +GLAPI void APIENTRY glDisablei (GLenum target, GLuint index); +GLAPI GLboolean APIENTRY glIsEnabledi (GLenum target, GLuint index); +GLAPI void APIENTRY glBeginTransformFeedback (GLenum primitiveMode); +GLAPI void APIENTRY glEndTransformFeedback (void); +GLAPI void APIENTRY glBindBufferRange (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); +GLAPI void APIENTRY glBindBufferBase (GLenum target, GLuint index, GLuint buffer); +GLAPI void APIENTRY glTransformFeedbackVaryings (GLuint program, GLsizei count, const GLchar *const*varyings, GLenum bufferMode); +GLAPI void APIENTRY glGetTransformFeedbackVarying (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLsizei *size, GLenum *type, GLchar *name); +GLAPI void APIENTRY glClampColor (GLenum target, GLenum clamp); +GLAPI void APIENTRY glBeginConditionalRender (GLuint id, GLenum mode); +GLAPI void APIENTRY glEndConditionalRender (void); +GLAPI void APIENTRY glVertexAttribIPointer (GLuint index, GLint size, GLenum type, GLsizei stride, const void *pointer); +GLAPI void APIENTRY glGetVertexAttribIiv (GLuint index, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVertexAttribIuiv (GLuint index, GLenum pname, GLuint *params); +GLAPI void APIENTRY glVertexAttribI1i (GLuint index, GLint x); +GLAPI void APIENTRY glVertexAttribI2i (GLuint index, GLint x, GLint y); +GLAPI void APIENTRY glVertexAttribI3i (GLuint index, GLint x, GLint y, GLint z); +GLAPI void APIENTRY glVertexAttribI4i (GLuint index, GLint x, GLint y, GLint z, GLint w); +GLAPI void APIENTRY glVertexAttribI1ui (GLuint index, GLuint x); +GLAPI void APIENTRY glVertexAttribI2ui (GLuint index, GLuint x, GLuint y); +GLAPI void APIENTRY glVertexAttribI3ui (GLuint index, GLuint x, GLuint y, GLuint z); +GLAPI void APIENTRY glVertexAttribI4ui (GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +GLAPI void APIENTRY glVertexAttribI1iv (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttribI2iv (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttribI3iv (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttribI4iv (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttribI1uiv (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttribI2uiv (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttribI3uiv (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttribI4uiv (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttribI4bv (GLuint index, const GLbyte *v); +GLAPI void APIENTRY glVertexAttribI4sv (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttribI4ubv (GLuint index, const GLubyte *v); +GLAPI void APIENTRY glVertexAttribI4usv (GLuint index, const GLushort *v); +GLAPI void APIENTRY glGetUniformuiv (GLuint program, GLint location, GLuint *params); +GLAPI void APIENTRY glBindFragDataLocation (GLuint program, GLuint color, const GLchar *name); +GLAPI GLint APIENTRY glGetFragDataLocation (GLuint program, const GLchar *name); +GLAPI void APIENTRY glUniform1ui (GLint location, GLuint v0); +GLAPI void APIENTRY glUniform2ui (GLint location, GLuint v0, GLuint v1); +GLAPI void APIENTRY glUniform3ui (GLint location, GLuint v0, GLuint v1, GLuint v2); +GLAPI void APIENTRY glUniform4ui (GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +GLAPI void APIENTRY glUniform1uiv (GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glUniform2uiv (GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glUniform3uiv (GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glUniform4uiv (GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glTexParameterIiv (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glTexParameterIuiv (GLenum target, GLenum pname, const GLuint *params); +GLAPI void APIENTRY glGetTexParameterIiv (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetTexParameterIuiv (GLenum target, GLenum pname, GLuint *params); +GLAPI void APIENTRY glClearBufferiv (GLenum buffer, GLint drawbuffer, const GLint *value); +GLAPI void APIENTRY glClearBufferuiv (GLenum buffer, GLint drawbuffer, const GLuint *value); +GLAPI void APIENTRY glClearBufferfv (GLenum buffer, GLint drawbuffer, const GLfloat *value); +GLAPI void APIENTRY glClearBufferfi (GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil); +GLAPI const GLubyte *APIENTRY glGetStringi (GLenum name, GLuint index); +GLAPI GLboolean APIENTRY glIsRenderbuffer (GLuint renderbuffer); +GLAPI void APIENTRY glBindRenderbuffer (GLenum target, GLuint renderbuffer); +GLAPI void APIENTRY glDeleteRenderbuffers (GLsizei n, const GLuint *renderbuffers); +GLAPI void APIENTRY glGenRenderbuffers (GLsizei n, GLuint *renderbuffers); +GLAPI void APIENTRY glRenderbufferStorage (GLenum target, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glGetRenderbufferParameteriv (GLenum target, GLenum pname, GLint *params); +GLAPI GLboolean APIENTRY glIsFramebuffer (GLuint framebuffer); +GLAPI void APIENTRY glBindFramebuffer (GLenum target, GLuint framebuffer); +GLAPI void APIENTRY glDeleteFramebuffers (GLsizei n, const GLuint *framebuffers); +GLAPI void APIENTRY glGenFramebuffers (GLsizei n, GLuint *framebuffers); +GLAPI GLenum APIENTRY glCheckFramebufferStatus (GLenum target); +GLAPI void APIENTRY glFramebufferTexture1D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +GLAPI void APIENTRY glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +GLAPI void APIENTRY glFramebufferTexture3D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); +GLAPI void APIENTRY glFramebufferRenderbuffer (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +GLAPI void APIENTRY glGetFramebufferAttachmentParameteriv (GLenum target, GLenum attachment, GLenum pname, GLint *params); +GLAPI void APIENTRY glGenerateMipmap (GLenum target); +GLAPI void APIENTRY glBlitFramebuffer (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +GLAPI void APIENTRY glRenderbufferStorageMultisample (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glFramebufferTextureLayer (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); +GLAPI void *APIENTRY glMapBufferRange (GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access); +GLAPI void APIENTRY glFlushMappedBufferRange (GLenum target, GLintptr offset, GLsizeiptr length); +GLAPI void APIENTRY glBindVertexArray (GLuint array); +GLAPI void APIENTRY glDeleteVertexArrays (GLsizei n, const GLuint *arrays); +GLAPI void APIENTRY glGenVertexArrays (GLsizei n, GLuint *arrays); +GLAPI GLboolean APIENTRY glIsVertexArray (GLuint array); +#endif +#endif /* GL_VERSION_3_0 */ + +#ifndef GL_VERSION_3_1 +#define GL_VERSION_3_1 1 +#define GL_SAMPLER_2D_RECT 0x8B63 +#define GL_SAMPLER_2D_RECT_SHADOW 0x8B64 +#define GL_SAMPLER_BUFFER 0x8DC2 +#define GL_INT_SAMPLER_2D_RECT 0x8DCD +#define GL_INT_SAMPLER_BUFFER 0x8DD0 +#define GL_UNSIGNED_INT_SAMPLER_2D_RECT 0x8DD5 +#define GL_UNSIGNED_INT_SAMPLER_BUFFER 0x8DD8 +#define GL_TEXTURE_BUFFER 0x8C2A +#define GL_MAX_TEXTURE_BUFFER_SIZE 0x8C2B +#define GL_TEXTURE_BINDING_BUFFER 0x8C2C +#define GL_TEXTURE_BUFFER_DATA_STORE_BINDING 0x8C2D +#define GL_TEXTURE_RECTANGLE 0x84F5 +#define GL_TEXTURE_BINDING_RECTANGLE 0x84F6 +#define GL_PROXY_TEXTURE_RECTANGLE 0x84F7 +#define GL_MAX_RECTANGLE_TEXTURE_SIZE 0x84F8 +#define GL_R8_SNORM 0x8F94 +#define GL_RG8_SNORM 0x8F95 +#define GL_RGB8_SNORM 0x8F96 +#define GL_RGBA8_SNORM 0x8F97 +#define GL_R16_SNORM 0x8F98 +#define GL_RG16_SNORM 0x8F99 +#define GL_RGB16_SNORM 0x8F9A +#define GL_RGBA16_SNORM 0x8F9B +#define GL_SIGNED_NORMALIZED 0x8F9C +#define GL_PRIMITIVE_RESTART 0x8F9D +#define GL_PRIMITIVE_RESTART_INDEX 0x8F9E +#define GL_COPY_READ_BUFFER 0x8F36 +#define GL_COPY_WRITE_BUFFER 0x8F37 +#define GL_UNIFORM_BUFFER 0x8A11 +#define GL_UNIFORM_BUFFER_BINDING 0x8A28 +#define GL_UNIFORM_BUFFER_START 0x8A29 +#define GL_UNIFORM_BUFFER_SIZE 0x8A2A +#define GL_MAX_VERTEX_UNIFORM_BLOCKS 0x8A2B +#define GL_MAX_GEOMETRY_UNIFORM_BLOCKS 0x8A2C +#define GL_MAX_FRAGMENT_UNIFORM_BLOCKS 0x8A2D +#define GL_MAX_COMBINED_UNIFORM_BLOCKS 0x8A2E +#define GL_MAX_UNIFORM_BUFFER_BINDINGS 0x8A2F +#define GL_MAX_UNIFORM_BLOCK_SIZE 0x8A30 +#define GL_MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS 0x8A31 +#define GL_MAX_COMBINED_GEOMETRY_UNIFORM_COMPONENTS 0x8A32 +#define GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS 0x8A33 +#define GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT 0x8A34 +#define GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH 0x8A35 +#define GL_ACTIVE_UNIFORM_BLOCKS 0x8A36 +#define GL_UNIFORM_TYPE 0x8A37 +#define GL_UNIFORM_SIZE 0x8A38 +#define GL_UNIFORM_NAME_LENGTH 0x8A39 +#define GL_UNIFORM_BLOCK_INDEX 0x8A3A +#define GL_UNIFORM_OFFSET 0x8A3B +#define GL_UNIFORM_ARRAY_STRIDE 0x8A3C +#define GL_UNIFORM_MATRIX_STRIDE 0x8A3D +#define GL_UNIFORM_IS_ROW_MAJOR 0x8A3E +#define GL_UNIFORM_BLOCK_BINDING 0x8A3F +#define GL_UNIFORM_BLOCK_DATA_SIZE 0x8A40 +#define GL_UNIFORM_BLOCK_NAME_LENGTH 0x8A41 +#define GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS 0x8A42 +#define GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES 0x8A43 +#define GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER 0x8A44 +#define GL_UNIFORM_BLOCK_REFERENCED_BY_GEOMETRY_SHADER 0x8A45 +#define GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER 0x8A46 +#define GL_INVALID_INDEX 0xFFFFFFFFu +typedef void (APIENTRYP PFNGLDRAWARRAYSINSTANCEDPROC) (GLenum mode, GLint first, GLsizei count, GLsizei instancecount); +typedef void (APIENTRYP PFNGLDRAWELEMENTSINSTANCEDPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount); +typedef void (APIENTRYP PFNGLTEXBUFFERPROC) (GLenum target, GLenum internalformat, GLuint buffer); +typedef void (APIENTRYP PFNGLPRIMITIVERESTARTINDEXPROC) (GLuint index); +typedef void (APIENTRYP PFNGLCOPYBUFFERSUBDATAPROC) (GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); +typedef void (APIENTRYP PFNGLGETUNIFORMINDICESPROC) (GLuint program, GLsizei uniformCount, const GLchar *const*uniformNames, GLuint *uniformIndices); +typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMSIVPROC) (GLuint program, GLsizei uniformCount, const GLuint *uniformIndices, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMNAMEPROC) (GLuint program, GLuint uniformIndex, GLsizei bufSize, GLsizei *length, GLchar *uniformName); +typedef GLuint (APIENTRYP PFNGLGETUNIFORMBLOCKINDEXPROC) (GLuint program, const GLchar *uniformBlockName); +typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMBLOCKIVPROC) (GLuint program, GLuint uniformBlockIndex, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMBLOCKNAMEPROC) (GLuint program, GLuint uniformBlockIndex, GLsizei bufSize, GLsizei *length, GLchar *uniformBlockName); +typedef void (APIENTRYP PFNGLUNIFORMBLOCKBINDINGPROC) (GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawArraysInstanced (GLenum mode, GLint first, GLsizei count, GLsizei instancecount); +GLAPI void APIENTRY glDrawElementsInstanced (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount); +GLAPI void APIENTRY glTexBuffer (GLenum target, GLenum internalformat, GLuint buffer); +GLAPI void APIENTRY glPrimitiveRestartIndex (GLuint index); +GLAPI void APIENTRY glCopyBufferSubData (GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); +GLAPI void APIENTRY glGetUniformIndices (GLuint program, GLsizei uniformCount, const GLchar *const*uniformNames, GLuint *uniformIndices); +GLAPI void APIENTRY glGetActiveUniformsiv (GLuint program, GLsizei uniformCount, const GLuint *uniformIndices, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetActiveUniformName (GLuint program, GLuint uniformIndex, GLsizei bufSize, GLsizei *length, GLchar *uniformName); +GLAPI GLuint APIENTRY glGetUniformBlockIndex (GLuint program, const GLchar *uniformBlockName); +GLAPI void APIENTRY glGetActiveUniformBlockiv (GLuint program, GLuint uniformBlockIndex, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetActiveUniformBlockName (GLuint program, GLuint uniformBlockIndex, GLsizei bufSize, GLsizei *length, GLchar *uniformBlockName); +GLAPI void APIENTRY glUniformBlockBinding (GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding); +#endif +#endif /* GL_VERSION_3_1 */ + +#ifndef GL_VERSION_3_2 +#define GL_VERSION_3_2 1 +typedef struct __GLsync *GLsync; +typedef khronos_uint64_t GLuint64; +typedef khronos_int64_t GLint64; +#define GL_CONTEXT_CORE_PROFILE_BIT 0x00000001 +#define GL_CONTEXT_COMPATIBILITY_PROFILE_BIT 0x00000002 +#define GL_LINES_ADJACENCY 0x000A +#define GL_LINE_STRIP_ADJACENCY 0x000B +#define GL_TRIANGLES_ADJACENCY 0x000C +#define GL_TRIANGLE_STRIP_ADJACENCY 0x000D +#define GL_PROGRAM_POINT_SIZE 0x8642 +#define GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS 0x8C29 +#define GL_FRAMEBUFFER_ATTACHMENT_LAYERED 0x8DA7 +#define GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS 0x8DA8 +#define GL_GEOMETRY_SHADER 0x8DD9 +#define GL_GEOMETRY_VERTICES_OUT 0x8916 +#define GL_GEOMETRY_INPUT_TYPE 0x8917 +#define GL_GEOMETRY_OUTPUT_TYPE 0x8918 +#define GL_MAX_GEOMETRY_UNIFORM_COMPONENTS 0x8DDF +#define GL_MAX_GEOMETRY_OUTPUT_VERTICES 0x8DE0 +#define GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS 0x8DE1 +#define GL_MAX_VERTEX_OUTPUT_COMPONENTS 0x9122 +#define GL_MAX_GEOMETRY_INPUT_COMPONENTS 0x9123 +#define GL_MAX_GEOMETRY_OUTPUT_COMPONENTS 0x9124 +#define GL_MAX_FRAGMENT_INPUT_COMPONENTS 0x9125 +#define GL_CONTEXT_PROFILE_MASK 0x9126 +#define GL_DEPTH_CLAMP 0x864F +#define GL_QUADS_FOLLOW_PROVOKING_VERTEX_CONVENTION 0x8E4C +#define GL_FIRST_VERTEX_CONVENTION 0x8E4D +#define GL_LAST_VERTEX_CONVENTION 0x8E4E +#define GL_PROVOKING_VERTEX 0x8E4F +#define GL_TEXTURE_CUBE_MAP_SEAMLESS 0x884F +#define GL_MAX_SERVER_WAIT_TIMEOUT 0x9111 +#define GL_OBJECT_TYPE 0x9112 +#define GL_SYNC_CONDITION 0x9113 +#define GL_SYNC_STATUS 0x9114 +#define GL_SYNC_FLAGS 0x9115 +#define GL_SYNC_FENCE 0x9116 +#define GL_SYNC_GPU_COMMANDS_COMPLETE 0x9117 +#define GL_UNSIGNALED 0x9118 +#define GL_SIGNALED 0x9119 +#define GL_ALREADY_SIGNALED 0x911A +#define GL_TIMEOUT_EXPIRED 0x911B +#define GL_CONDITION_SATISFIED 0x911C +#define GL_WAIT_FAILED 0x911D +#define GL_TIMEOUT_IGNORED 0xFFFFFFFFFFFFFFFFull +#define GL_SYNC_FLUSH_COMMANDS_BIT 0x00000001 +#define GL_SAMPLE_POSITION 0x8E50 +#define GL_SAMPLE_MASK 0x8E51 +#define GL_SAMPLE_MASK_VALUE 0x8E52 +#define GL_MAX_SAMPLE_MASK_WORDS 0x8E59 +#define GL_TEXTURE_2D_MULTISAMPLE 0x9100 +#define GL_PROXY_TEXTURE_2D_MULTISAMPLE 0x9101 +#define GL_TEXTURE_2D_MULTISAMPLE_ARRAY 0x9102 +#define GL_PROXY_TEXTURE_2D_MULTISAMPLE_ARRAY 0x9103 +#define GL_TEXTURE_BINDING_2D_MULTISAMPLE 0x9104 +#define GL_TEXTURE_BINDING_2D_MULTISAMPLE_ARRAY 0x9105 +#define GL_TEXTURE_SAMPLES 0x9106 +#define GL_TEXTURE_FIXED_SAMPLE_LOCATIONS 0x9107 +#define GL_SAMPLER_2D_MULTISAMPLE 0x9108 +#define GL_INT_SAMPLER_2D_MULTISAMPLE 0x9109 +#define GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE 0x910A +#define GL_SAMPLER_2D_MULTISAMPLE_ARRAY 0x910B +#define GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY 0x910C +#define GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY 0x910D +#define GL_MAX_COLOR_TEXTURE_SAMPLES 0x910E +#define GL_MAX_DEPTH_TEXTURE_SAMPLES 0x910F +#define GL_MAX_INTEGER_SAMPLES 0x9110 +typedef void (APIENTRYP PFNGLDRAWELEMENTSBASEVERTEXPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLint basevertex); +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTSBASEVERTEXPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void *indices, GLint basevertex); +typedef void (APIENTRYP PFNGLDRAWELEMENTSINSTANCEDBASEVERTEXPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount, GLint basevertex); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSBASEVERTEXPROC) (GLenum mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei drawcount, const GLint *basevertex); +typedef void (APIENTRYP PFNGLPROVOKINGVERTEXPROC) (GLenum mode); +typedef GLsync (APIENTRYP PFNGLFENCESYNCPROC) (GLenum condition, GLbitfield flags); +typedef GLboolean (APIENTRYP PFNGLISSYNCPROC) (GLsync sync); +typedef void (APIENTRYP PFNGLDELETESYNCPROC) (GLsync sync); +typedef GLenum (APIENTRYP PFNGLCLIENTWAITSYNCPROC) (GLsync sync, GLbitfield flags, GLuint64 timeout); +typedef void (APIENTRYP PFNGLWAITSYNCPROC) (GLsync sync, GLbitfield flags, GLuint64 timeout); +typedef void (APIENTRYP PFNGLGETINTEGER64VPROC) (GLenum pname, GLint64 *data); +typedef void (APIENTRYP PFNGLGETSYNCIVPROC) (GLsync sync, GLenum pname, GLsizei count, GLsizei *length, GLint *values); +typedef void (APIENTRYP PFNGLGETINTEGER64I_VPROC) (GLenum target, GLuint index, GLint64 *data); +typedef void (APIENTRYP PFNGLGETBUFFERPARAMETERI64VPROC) (GLenum target, GLenum pname, GLint64 *params); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTUREPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLTEXIMAGE2DMULTISAMPLEPROC) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations); +typedef void (APIENTRYP PFNGLTEXIMAGE3DMULTISAMPLEPROC) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations); +typedef void (APIENTRYP PFNGLGETMULTISAMPLEFVPROC) (GLenum pname, GLuint index, GLfloat *val); +typedef void (APIENTRYP PFNGLSAMPLEMASKIPROC) (GLuint maskNumber, GLbitfield mask); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawElementsBaseVertex (GLenum mode, GLsizei count, GLenum type, const void *indices, GLint basevertex); +GLAPI void APIENTRY glDrawRangeElementsBaseVertex (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void *indices, GLint basevertex); +GLAPI void APIENTRY glDrawElementsInstancedBaseVertex (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount, GLint basevertex); +GLAPI void APIENTRY glMultiDrawElementsBaseVertex (GLenum mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei drawcount, const GLint *basevertex); +GLAPI void APIENTRY glProvokingVertex (GLenum mode); +GLAPI GLsync APIENTRY glFenceSync (GLenum condition, GLbitfield flags); +GLAPI GLboolean APIENTRY glIsSync (GLsync sync); +GLAPI void APIENTRY glDeleteSync (GLsync sync); +GLAPI GLenum APIENTRY glClientWaitSync (GLsync sync, GLbitfield flags, GLuint64 timeout); +GLAPI void APIENTRY glWaitSync (GLsync sync, GLbitfield flags, GLuint64 timeout); +GLAPI void APIENTRY glGetInteger64v (GLenum pname, GLint64 *data); +GLAPI void APIENTRY glGetSynciv (GLsync sync, GLenum pname, GLsizei count, GLsizei *length, GLint *values); +GLAPI void APIENTRY glGetInteger64i_v (GLenum target, GLuint index, GLint64 *data); +GLAPI void APIENTRY glGetBufferParameteri64v (GLenum target, GLenum pname, GLint64 *params); +GLAPI void APIENTRY glFramebufferTexture (GLenum target, GLenum attachment, GLuint texture, GLint level); +GLAPI void APIENTRY glTexImage2DMultisample (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations); +GLAPI void APIENTRY glTexImage3DMultisample (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations); +GLAPI void APIENTRY glGetMultisamplefv (GLenum pname, GLuint index, GLfloat *val); +GLAPI void APIENTRY glSampleMaski (GLuint maskNumber, GLbitfield mask); +#endif +#endif /* GL_VERSION_3_2 */ + +#ifndef GL_VERSION_3_3 +#define GL_VERSION_3_3 1 +#define GL_VERTEX_ATTRIB_ARRAY_DIVISOR 0x88FE +#define GL_SRC1_COLOR 0x88F9 +#define GL_ONE_MINUS_SRC1_COLOR 0x88FA +#define GL_ONE_MINUS_SRC1_ALPHA 0x88FB +#define GL_MAX_DUAL_SOURCE_DRAW_BUFFERS 0x88FC +#define GL_ANY_SAMPLES_PASSED 0x8C2F +#define GL_SAMPLER_BINDING 0x8919 +#define GL_RGB10_A2UI 0x906F +#define GL_TEXTURE_SWIZZLE_R 0x8E42 +#define GL_TEXTURE_SWIZZLE_G 0x8E43 +#define GL_TEXTURE_SWIZZLE_B 0x8E44 +#define GL_TEXTURE_SWIZZLE_A 0x8E45 +#define GL_TEXTURE_SWIZZLE_RGBA 0x8E46 +#define GL_TIME_ELAPSED 0x88BF +#define GL_TIMESTAMP 0x8E28 +#define GL_INT_2_10_10_10_REV 0x8D9F +typedef void (APIENTRYP PFNGLBINDFRAGDATALOCATIONINDEXEDPROC) (GLuint program, GLuint colorNumber, GLuint index, const GLchar *name); +typedef GLint (APIENTRYP PFNGLGETFRAGDATAINDEXPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP PFNGLGENSAMPLERSPROC) (GLsizei count, GLuint *samplers); +typedef void (APIENTRYP PFNGLDELETESAMPLERSPROC) (GLsizei count, const GLuint *samplers); +typedef GLboolean (APIENTRYP PFNGLISSAMPLERPROC) (GLuint sampler); +typedef void (APIENTRYP PFNGLBINDSAMPLERPROC) (GLuint unit, GLuint sampler); +typedef void (APIENTRYP PFNGLSAMPLERPARAMETERIPROC) (GLuint sampler, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLSAMPLERPARAMETERIVPROC) (GLuint sampler, GLenum pname, const GLint *param); +typedef void (APIENTRYP PFNGLSAMPLERPARAMETERFPROC) (GLuint sampler, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLSAMPLERPARAMETERFVPROC) (GLuint sampler, GLenum pname, const GLfloat *param); +typedef void (APIENTRYP PFNGLSAMPLERPARAMETERIIVPROC) (GLuint sampler, GLenum pname, const GLint *param); +typedef void (APIENTRYP PFNGLSAMPLERPARAMETERIUIVPROC) (GLuint sampler, GLenum pname, const GLuint *param); +typedef void (APIENTRYP PFNGLGETSAMPLERPARAMETERIVPROC) (GLuint sampler, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETSAMPLERPARAMETERIIVPROC) (GLuint sampler, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETSAMPLERPARAMETERFVPROC) (GLuint sampler, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETSAMPLERPARAMETERIUIVPROC) (GLuint sampler, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLQUERYCOUNTERPROC) (GLuint id, GLenum target); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTI64VPROC) (GLuint id, GLenum pname, GLint64 *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTUI64VPROC) (GLuint id, GLenum pname, GLuint64 *params); +typedef void (APIENTRYP PFNGLVERTEXATTRIBDIVISORPROC) (GLuint index, GLuint divisor); +typedef void (APIENTRYP PFNGLVERTEXATTRIBP1UIPROC) (GLuint index, GLenum type, GLboolean normalized, GLuint value); +typedef void (APIENTRYP PFNGLVERTEXATTRIBP1UIVPROC) (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); +typedef void (APIENTRYP PFNGLVERTEXATTRIBP2UIPROC) (GLuint index, GLenum type, GLboolean normalized, GLuint value); +typedef void (APIENTRYP PFNGLVERTEXATTRIBP2UIVPROC) (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); +typedef void (APIENTRYP PFNGLVERTEXATTRIBP3UIPROC) (GLuint index, GLenum type, GLboolean normalized, GLuint value); +typedef void (APIENTRYP PFNGLVERTEXATTRIBP3UIVPROC) (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); +typedef void (APIENTRYP PFNGLVERTEXATTRIBP4UIPROC) (GLuint index, GLenum type, GLboolean normalized, GLuint value); +typedef void (APIENTRYP PFNGLVERTEXATTRIBP4UIVPROC) (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); +typedef void (APIENTRYP PFNGLVERTEXP2UIPROC) (GLenum type, GLuint value); +typedef void (APIENTRYP PFNGLVERTEXP2UIVPROC) (GLenum type, const GLuint *value); +typedef void (APIENTRYP PFNGLVERTEXP3UIPROC) (GLenum type, GLuint value); +typedef void (APIENTRYP PFNGLVERTEXP3UIVPROC) (GLenum type, const GLuint *value); +typedef void (APIENTRYP PFNGLVERTEXP4UIPROC) (GLenum type, GLuint value); +typedef void (APIENTRYP PFNGLVERTEXP4UIVPROC) (GLenum type, const GLuint *value); +typedef void (APIENTRYP PFNGLTEXCOORDP1UIPROC) (GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLTEXCOORDP1UIVPROC) (GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLTEXCOORDP2UIPROC) (GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLTEXCOORDP2UIVPROC) (GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLTEXCOORDP3UIPROC) (GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLTEXCOORDP3UIVPROC) (GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLTEXCOORDP4UIPROC) (GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLTEXCOORDP4UIVPROC) (GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORDP1UIPROC) (GLenum texture, GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORDP1UIVPROC) (GLenum texture, GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORDP2UIPROC) (GLenum texture, GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORDP2UIVPROC) (GLenum texture, GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORDP3UIPROC) (GLenum texture, GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORDP3UIVPROC) (GLenum texture, GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORDP4UIPROC) (GLenum texture, GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORDP4UIVPROC) (GLenum texture, GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLNORMALP3UIPROC) (GLenum type, GLuint coords); +typedef void (APIENTRYP PFNGLNORMALP3UIVPROC) (GLenum type, const GLuint *coords); +typedef void (APIENTRYP PFNGLCOLORP3UIPROC) (GLenum type, GLuint color); +typedef void (APIENTRYP PFNGLCOLORP3UIVPROC) (GLenum type, const GLuint *color); +typedef void (APIENTRYP PFNGLCOLORP4UIPROC) (GLenum type, GLuint color); +typedef void (APIENTRYP PFNGLCOLORP4UIVPROC) (GLenum type, const GLuint *color); +typedef void (APIENTRYP PFNGLSECONDARYCOLORP3UIPROC) (GLenum type, GLuint color); +typedef void (APIENTRYP PFNGLSECONDARYCOLORP3UIVPROC) (GLenum type, const GLuint *color); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindFragDataLocationIndexed (GLuint program, GLuint colorNumber, GLuint index, const GLchar *name); +GLAPI GLint APIENTRY glGetFragDataIndex (GLuint program, const GLchar *name); +GLAPI void APIENTRY glGenSamplers (GLsizei count, GLuint *samplers); +GLAPI void APIENTRY glDeleteSamplers (GLsizei count, const GLuint *samplers); +GLAPI GLboolean APIENTRY glIsSampler (GLuint sampler); +GLAPI void APIENTRY glBindSampler (GLuint unit, GLuint sampler); +GLAPI void APIENTRY glSamplerParameteri (GLuint sampler, GLenum pname, GLint param); +GLAPI void APIENTRY glSamplerParameteriv (GLuint sampler, GLenum pname, const GLint *param); +GLAPI void APIENTRY glSamplerParameterf (GLuint sampler, GLenum pname, GLfloat param); +GLAPI void APIENTRY glSamplerParameterfv (GLuint sampler, GLenum pname, const GLfloat *param); +GLAPI void APIENTRY glSamplerParameterIiv (GLuint sampler, GLenum pname, const GLint *param); +GLAPI void APIENTRY glSamplerParameterIuiv (GLuint sampler, GLenum pname, const GLuint *param); +GLAPI void APIENTRY glGetSamplerParameteriv (GLuint sampler, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetSamplerParameterIiv (GLuint sampler, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetSamplerParameterfv (GLuint sampler, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetSamplerParameterIuiv (GLuint sampler, GLenum pname, GLuint *params); +GLAPI void APIENTRY glQueryCounter (GLuint id, GLenum target); +GLAPI void APIENTRY glGetQueryObjecti64v (GLuint id, GLenum pname, GLint64 *params); +GLAPI void APIENTRY glGetQueryObjectui64v (GLuint id, GLenum pname, GLuint64 *params); +GLAPI void APIENTRY glVertexAttribDivisor (GLuint index, GLuint divisor); +GLAPI void APIENTRY glVertexAttribP1ui (GLuint index, GLenum type, GLboolean normalized, GLuint value); +GLAPI void APIENTRY glVertexAttribP1uiv (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); +GLAPI void APIENTRY glVertexAttribP2ui (GLuint index, GLenum type, GLboolean normalized, GLuint value); +GLAPI void APIENTRY glVertexAttribP2uiv (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); +GLAPI void APIENTRY glVertexAttribP3ui (GLuint index, GLenum type, GLboolean normalized, GLuint value); +GLAPI void APIENTRY glVertexAttribP3uiv (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); +GLAPI void APIENTRY glVertexAttribP4ui (GLuint index, GLenum type, GLboolean normalized, GLuint value); +GLAPI void APIENTRY glVertexAttribP4uiv (GLuint index, GLenum type, GLboolean normalized, const GLuint *value); +GLAPI void APIENTRY glVertexP2ui (GLenum type, GLuint value); +GLAPI void APIENTRY glVertexP2uiv (GLenum type, const GLuint *value); +GLAPI void APIENTRY glVertexP3ui (GLenum type, GLuint value); +GLAPI void APIENTRY glVertexP3uiv (GLenum type, const GLuint *value); +GLAPI void APIENTRY glVertexP4ui (GLenum type, GLuint value); +GLAPI void APIENTRY glVertexP4uiv (GLenum type, const GLuint *value); +GLAPI void APIENTRY glTexCoordP1ui (GLenum type, GLuint coords); +GLAPI void APIENTRY glTexCoordP1uiv (GLenum type, const GLuint *coords); +GLAPI void APIENTRY glTexCoordP2ui (GLenum type, GLuint coords); +GLAPI void APIENTRY glTexCoordP2uiv (GLenum type, const GLuint *coords); +GLAPI void APIENTRY glTexCoordP3ui (GLenum type, GLuint coords); +GLAPI void APIENTRY glTexCoordP3uiv (GLenum type, const GLuint *coords); +GLAPI void APIENTRY glTexCoordP4ui (GLenum type, GLuint coords); +GLAPI void APIENTRY glTexCoordP4uiv (GLenum type, const GLuint *coords); +GLAPI void APIENTRY glMultiTexCoordP1ui (GLenum texture, GLenum type, GLuint coords); +GLAPI void APIENTRY glMultiTexCoordP1uiv (GLenum texture, GLenum type, const GLuint *coords); +GLAPI void APIENTRY glMultiTexCoordP2ui (GLenum texture, GLenum type, GLuint coords); +GLAPI void APIENTRY glMultiTexCoordP2uiv (GLenum texture, GLenum type, const GLuint *coords); +GLAPI void APIENTRY glMultiTexCoordP3ui (GLenum texture, GLenum type, GLuint coords); +GLAPI void APIENTRY glMultiTexCoordP3uiv (GLenum texture, GLenum type, const GLuint *coords); +GLAPI void APIENTRY glMultiTexCoordP4ui (GLenum texture, GLenum type, GLuint coords); +GLAPI void APIENTRY glMultiTexCoordP4uiv (GLenum texture, GLenum type, const GLuint *coords); +GLAPI void APIENTRY glNormalP3ui (GLenum type, GLuint coords); +GLAPI void APIENTRY glNormalP3uiv (GLenum type, const GLuint *coords); +GLAPI void APIENTRY glColorP3ui (GLenum type, GLuint color); +GLAPI void APIENTRY glColorP3uiv (GLenum type, const GLuint *color); +GLAPI void APIENTRY glColorP4ui (GLenum type, GLuint color); +GLAPI void APIENTRY glColorP4uiv (GLenum type, const GLuint *color); +GLAPI void APIENTRY glSecondaryColorP3ui (GLenum type, GLuint color); +GLAPI void APIENTRY glSecondaryColorP3uiv (GLenum type, const GLuint *color); +#endif +#endif /* GL_VERSION_3_3 */ + +#ifndef GL_VERSION_4_0 +#define GL_VERSION_4_0 1 +#define GL_SAMPLE_SHADING 0x8C36 +#define GL_MIN_SAMPLE_SHADING_VALUE 0x8C37 +#define GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET 0x8E5E +#define GL_MAX_PROGRAM_TEXTURE_GATHER_OFFSET 0x8E5F +#define GL_TEXTURE_CUBE_MAP_ARRAY 0x9009 +#define GL_TEXTURE_BINDING_CUBE_MAP_ARRAY 0x900A +#define GL_PROXY_TEXTURE_CUBE_MAP_ARRAY 0x900B +#define GL_SAMPLER_CUBE_MAP_ARRAY 0x900C +#define GL_SAMPLER_CUBE_MAP_ARRAY_SHADOW 0x900D +#define GL_INT_SAMPLER_CUBE_MAP_ARRAY 0x900E +#define GL_UNSIGNED_INT_SAMPLER_CUBE_MAP_ARRAY 0x900F +#define GL_DRAW_INDIRECT_BUFFER 0x8F3F +#define GL_DRAW_INDIRECT_BUFFER_BINDING 0x8F43 +#define GL_GEOMETRY_SHADER_INVOCATIONS 0x887F +#define GL_MAX_GEOMETRY_SHADER_INVOCATIONS 0x8E5A +#define GL_MIN_FRAGMENT_INTERPOLATION_OFFSET 0x8E5B +#define GL_MAX_FRAGMENT_INTERPOLATION_OFFSET 0x8E5C +#define GL_FRAGMENT_INTERPOLATION_OFFSET_BITS 0x8E5D +#define GL_MAX_VERTEX_STREAMS 0x8E71 +#define GL_DOUBLE_VEC2 0x8FFC +#define GL_DOUBLE_VEC3 0x8FFD +#define GL_DOUBLE_VEC4 0x8FFE +#define GL_DOUBLE_MAT2 0x8F46 +#define GL_DOUBLE_MAT3 0x8F47 +#define GL_DOUBLE_MAT4 0x8F48 +#define GL_DOUBLE_MAT2x3 0x8F49 +#define GL_DOUBLE_MAT2x4 0x8F4A +#define GL_DOUBLE_MAT3x2 0x8F4B +#define GL_DOUBLE_MAT3x4 0x8F4C +#define GL_DOUBLE_MAT4x2 0x8F4D +#define GL_DOUBLE_MAT4x3 0x8F4E +#define GL_ACTIVE_SUBROUTINES 0x8DE5 +#define GL_ACTIVE_SUBROUTINE_UNIFORMS 0x8DE6 +#define GL_ACTIVE_SUBROUTINE_UNIFORM_LOCATIONS 0x8E47 +#define GL_ACTIVE_SUBROUTINE_MAX_LENGTH 0x8E48 +#define GL_ACTIVE_SUBROUTINE_UNIFORM_MAX_LENGTH 0x8E49 +#define GL_MAX_SUBROUTINES 0x8DE7 +#define GL_MAX_SUBROUTINE_UNIFORM_LOCATIONS 0x8DE8 +#define GL_NUM_COMPATIBLE_SUBROUTINES 0x8E4A +#define GL_COMPATIBLE_SUBROUTINES 0x8E4B +#define GL_PATCHES 0x000E +#define GL_PATCH_VERTICES 0x8E72 +#define GL_PATCH_DEFAULT_INNER_LEVEL 0x8E73 +#define GL_PATCH_DEFAULT_OUTER_LEVEL 0x8E74 +#define GL_TESS_CONTROL_OUTPUT_VERTICES 0x8E75 +#define GL_TESS_GEN_MODE 0x8E76 +#define GL_TESS_GEN_SPACING 0x8E77 +#define GL_TESS_GEN_VERTEX_ORDER 0x8E78 +#define GL_TESS_GEN_POINT_MODE 0x8E79 +#define GL_ISOLINES 0x8E7A +#define GL_FRACTIONAL_ODD 0x8E7B +#define GL_FRACTIONAL_EVEN 0x8E7C +#define GL_MAX_PATCH_VERTICES 0x8E7D +#define GL_MAX_TESS_GEN_LEVEL 0x8E7E +#define GL_MAX_TESS_CONTROL_UNIFORM_COMPONENTS 0x8E7F +#define GL_MAX_TESS_EVALUATION_UNIFORM_COMPONENTS 0x8E80 +#define GL_MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS 0x8E81 +#define GL_MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS 0x8E82 +#define GL_MAX_TESS_CONTROL_OUTPUT_COMPONENTS 0x8E83 +#define GL_MAX_TESS_PATCH_COMPONENTS 0x8E84 +#define GL_MAX_TESS_CONTROL_TOTAL_OUTPUT_COMPONENTS 0x8E85 +#define GL_MAX_TESS_EVALUATION_OUTPUT_COMPONENTS 0x8E86 +#define GL_MAX_TESS_CONTROL_UNIFORM_BLOCKS 0x8E89 +#define GL_MAX_TESS_EVALUATION_UNIFORM_BLOCKS 0x8E8A +#define GL_MAX_TESS_CONTROL_INPUT_COMPONENTS 0x886C +#define GL_MAX_TESS_EVALUATION_INPUT_COMPONENTS 0x886D +#define GL_MAX_COMBINED_TESS_CONTROL_UNIFORM_COMPONENTS 0x8E1E +#define GL_MAX_COMBINED_TESS_EVALUATION_UNIFORM_COMPONENTS 0x8E1F +#define GL_UNIFORM_BLOCK_REFERENCED_BY_TESS_CONTROL_SHADER 0x84F0 +#define GL_UNIFORM_BLOCK_REFERENCED_BY_TESS_EVALUATION_SHADER 0x84F1 +#define GL_TESS_EVALUATION_SHADER 0x8E87 +#define GL_TESS_CONTROL_SHADER 0x8E88 +#define GL_TRANSFORM_FEEDBACK 0x8E22 +#define GL_TRANSFORM_FEEDBACK_BUFFER_PAUSED 0x8E23 +#define GL_TRANSFORM_FEEDBACK_BUFFER_ACTIVE 0x8E24 +#define GL_TRANSFORM_FEEDBACK_BINDING 0x8E25 +#define GL_MAX_TRANSFORM_FEEDBACK_BUFFERS 0x8E70 +typedef void (APIENTRYP PFNGLMINSAMPLESHADINGPROC) (GLfloat value); +typedef void (APIENTRYP PFNGLBLENDEQUATIONIPROC) (GLuint buf, GLenum mode); +typedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEIPROC) (GLuint buf, GLenum modeRGB, GLenum modeAlpha); +typedef void (APIENTRYP PFNGLBLENDFUNCIPROC) (GLuint buf, GLenum src, GLenum dst); +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEIPROC) (GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); +typedef void (APIENTRYP PFNGLDRAWARRAYSINDIRECTPROC) (GLenum mode, const void *indirect); +typedef void (APIENTRYP PFNGLDRAWELEMENTSINDIRECTPROC) (GLenum mode, GLenum type, const void *indirect); +typedef void (APIENTRYP PFNGLUNIFORM1DPROC) (GLint location, GLdouble x); +typedef void (APIENTRYP PFNGLUNIFORM2DPROC) (GLint location, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLUNIFORM3DPROC) (GLint location, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLUNIFORM4DPROC) (GLint location, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLUNIFORM1DVPROC) (GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORM2DVPROC) (GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORM3DVPROC) (GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORM4DVPROC) (GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2X3DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2X4DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3X2DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3X4DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4X2DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4X3DVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLGETUNIFORMDVPROC) (GLuint program, GLint location, GLdouble *params); +typedef GLint (APIENTRYP PFNGLGETSUBROUTINEUNIFORMLOCATIONPROC) (GLuint program, GLenum shadertype, const GLchar *name); +typedef GLuint (APIENTRYP PFNGLGETSUBROUTINEINDEXPROC) (GLuint program, GLenum shadertype, const GLchar *name); +typedef void (APIENTRYP PFNGLGETACTIVESUBROUTINEUNIFORMIVPROC) (GLuint program, GLenum shadertype, GLuint index, GLenum pname, GLint *values); +typedef void (APIENTRYP PFNGLGETACTIVESUBROUTINEUNIFORMNAMEPROC) (GLuint program, GLenum shadertype, GLuint index, GLsizei bufSize, GLsizei *length, GLchar *name); +typedef void (APIENTRYP PFNGLGETACTIVESUBROUTINENAMEPROC) (GLuint program, GLenum shadertype, GLuint index, GLsizei bufSize, GLsizei *length, GLchar *name); +typedef void (APIENTRYP PFNGLUNIFORMSUBROUTINESUIVPROC) (GLenum shadertype, GLsizei count, const GLuint *indices); +typedef void (APIENTRYP PFNGLGETUNIFORMSUBROUTINEUIVPROC) (GLenum shadertype, GLint location, GLuint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMSTAGEIVPROC) (GLuint program, GLenum shadertype, GLenum pname, GLint *values); +typedef void (APIENTRYP PFNGLPATCHPARAMETERIPROC) (GLenum pname, GLint value); +typedef void (APIENTRYP PFNGLPATCHPARAMETERFVPROC) (GLenum pname, const GLfloat *values); +typedef void (APIENTRYP PFNGLBINDTRANSFORMFEEDBACKPROC) (GLenum target, GLuint id); +typedef void (APIENTRYP PFNGLDELETETRANSFORMFEEDBACKSPROC) (GLsizei n, const GLuint *ids); +typedef void (APIENTRYP PFNGLGENTRANSFORMFEEDBACKSPROC) (GLsizei n, GLuint *ids); +typedef GLboolean (APIENTRYP PFNGLISTRANSFORMFEEDBACKPROC) (GLuint id); +typedef void (APIENTRYP PFNGLPAUSETRANSFORMFEEDBACKPROC) (void); +typedef void (APIENTRYP PFNGLRESUMETRANSFORMFEEDBACKPROC) (void); +typedef void (APIENTRYP PFNGLDRAWTRANSFORMFEEDBACKPROC) (GLenum mode, GLuint id); +typedef void (APIENTRYP PFNGLDRAWTRANSFORMFEEDBACKSTREAMPROC) (GLenum mode, GLuint id, GLuint stream); +typedef void (APIENTRYP PFNGLBEGINQUERYINDEXEDPROC) (GLenum target, GLuint index, GLuint id); +typedef void (APIENTRYP PFNGLENDQUERYINDEXEDPROC) (GLenum target, GLuint index); +typedef void (APIENTRYP PFNGLGETQUERYINDEXEDIVPROC) (GLenum target, GLuint index, GLenum pname, GLint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMinSampleShading (GLfloat value); +GLAPI void APIENTRY glBlendEquationi (GLuint buf, GLenum mode); +GLAPI void APIENTRY glBlendEquationSeparatei (GLuint buf, GLenum modeRGB, GLenum modeAlpha); +GLAPI void APIENTRY glBlendFunci (GLuint buf, GLenum src, GLenum dst); +GLAPI void APIENTRY glBlendFuncSeparatei (GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); +GLAPI void APIENTRY glDrawArraysIndirect (GLenum mode, const void *indirect); +GLAPI void APIENTRY glDrawElementsIndirect (GLenum mode, GLenum type, const void *indirect); +GLAPI void APIENTRY glUniform1d (GLint location, GLdouble x); +GLAPI void APIENTRY glUniform2d (GLint location, GLdouble x, GLdouble y); +GLAPI void APIENTRY glUniform3d (GLint location, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glUniform4d (GLint location, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glUniform1dv (GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glUniform2dv (GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glUniform3dv (GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glUniform4dv (GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix2dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix3dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix4dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix2x3dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix2x4dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix3x2dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix3x4dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix4x2dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glUniformMatrix4x3dv (GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glGetUniformdv (GLuint program, GLint location, GLdouble *params); +GLAPI GLint APIENTRY glGetSubroutineUniformLocation (GLuint program, GLenum shadertype, const GLchar *name); +GLAPI GLuint APIENTRY glGetSubroutineIndex (GLuint program, GLenum shadertype, const GLchar *name); +GLAPI void APIENTRY glGetActiveSubroutineUniformiv (GLuint program, GLenum shadertype, GLuint index, GLenum pname, GLint *values); +GLAPI void APIENTRY glGetActiveSubroutineUniformName (GLuint program, GLenum shadertype, GLuint index, GLsizei bufSize, GLsizei *length, GLchar *name); +GLAPI void APIENTRY glGetActiveSubroutineName (GLuint program, GLenum shadertype, GLuint index, GLsizei bufSize, GLsizei *length, GLchar *name); +GLAPI void APIENTRY glUniformSubroutinesuiv (GLenum shadertype, GLsizei count, const GLuint *indices); +GLAPI void APIENTRY glGetUniformSubroutineuiv (GLenum shadertype, GLint location, GLuint *params); +GLAPI void APIENTRY glGetProgramStageiv (GLuint program, GLenum shadertype, GLenum pname, GLint *values); +GLAPI void APIENTRY glPatchParameteri (GLenum pname, GLint value); +GLAPI void APIENTRY glPatchParameterfv (GLenum pname, const GLfloat *values); +GLAPI void APIENTRY glBindTransformFeedback (GLenum target, GLuint id); +GLAPI void APIENTRY glDeleteTransformFeedbacks (GLsizei n, const GLuint *ids); +GLAPI void APIENTRY glGenTransformFeedbacks (GLsizei n, GLuint *ids); +GLAPI GLboolean APIENTRY glIsTransformFeedback (GLuint id); +GLAPI void APIENTRY glPauseTransformFeedback (void); +GLAPI void APIENTRY glResumeTransformFeedback (void); +GLAPI void APIENTRY glDrawTransformFeedback (GLenum mode, GLuint id); +GLAPI void APIENTRY glDrawTransformFeedbackStream (GLenum mode, GLuint id, GLuint stream); +GLAPI void APIENTRY glBeginQueryIndexed (GLenum target, GLuint index, GLuint id); +GLAPI void APIENTRY glEndQueryIndexed (GLenum target, GLuint index); +GLAPI void APIENTRY glGetQueryIndexediv (GLenum target, GLuint index, GLenum pname, GLint *params); +#endif +#endif /* GL_VERSION_4_0 */ + +#ifndef GL_VERSION_4_1 +#define GL_VERSION_4_1 1 +#define GL_FIXED 0x140C +#define GL_IMPLEMENTATION_COLOR_READ_TYPE 0x8B9A +#define GL_IMPLEMENTATION_COLOR_READ_FORMAT 0x8B9B +#define GL_LOW_FLOAT 0x8DF0 +#define GL_MEDIUM_FLOAT 0x8DF1 +#define GL_HIGH_FLOAT 0x8DF2 +#define GL_LOW_INT 0x8DF3 +#define GL_MEDIUM_INT 0x8DF4 +#define GL_HIGH_INT 0x8DF5 +#define GL_SHADER_COMPILER 0x8DFA +#define GL_SHADER_BINARY_FORMATS 0x8DF8 +#define GL_NUM_SHADER_BINARY_FORMATS 0x8DF9 +#define GL_MAX_VERTEX_UNIFORM_VECTORS 0x8DFB +#define GL_MAX_VARYING_VECTORS 0x8DFC +#define GL_MAX_FRAGMENT_UNIFORM_VECTORS 0x8DFD +#define GL_RGB565 0x8D62 +#define GL_PROGRAM_BINARY_RETRIEVABLE_HINT 0x8257 +#define GL_PROGRAM_BINARY_LENGTH 0x8741 +#define GL_NUM_PROGRAM_BINARY_FORMATS 0x87FE +#define GL_PROGRAM_BINARY_FORMATS 0x87FF +#define GL_VERTEX_SHADER_BIT 0x00000001 +#define GL_FRAGMENT_SHADER_BIT 0x00000002 +#define GL_GEOMETRY_SHADER_BIT 0x00000004 +#define GL_TESS_CONTROL_SHADER_BIT 0x00000008 +#define GL_TESS_EVALUATION_SHADER_BIT 0x00000010 +#define GL_ALL_SHADER_BITS 0xFFFFFFFF +#define GL_PROGRAM_SEPARABLE 0x8258 +#define GL_ACTIVE_PROGRAM 0x8259 +#define GL_PROGRAM_PIPELINE_BINDING 0x825A +#define GL_MAX_VIEWPORTS 0x825B +#define GL_VIEWPORT_SUBPIXEL_BITS 0x825C +#define GL_VIEWPORT_BOUNDS_RANGE 0x825D +#define GL_LAYER_PROVOKING_VERTEX 0x825E +#define GL_VIEWPORT_INDEX_PROVOKING_VERTEX 0x825F +#define GL_UNDEFINED_VERTEX 0x8260 +typedef void (APIENTRYP PFNGLRELEASESHADERCOMPILERPROC) (void); +typedef void (APIENTRYP PFNGLSHADERBINARYPROC) (GLsizei count, const GLuint *shaders, GLenum binaryFormat, const void *binary, GLsizei length); +typedef void (APIENTRYP PFNGLGETSHADERPRECISIONFORMATPROC) (GLenum shadertype, GLenum precisiontype, GLint *range, GLint *precision); +typedef void (APIENTRYP PFNGLDEPTHRANGEFPROC) (GLfloat n, GLfloat f); +typedef void (APIENTRYP PFNGLCLEARDEPTHFPROC) (GLfloat d); +typedef void (APIENTRYP PFNGLGETPROGRAMBINARYPROC) (GLuint program, GLsizei bufSize, GLsizei *length, GLenum *binaryFormat, void *binary); +typedef void (APIENTRYP PFNGLPROGRAMBINARYPROC) (GLuint program, GLenum binaryFormat, const void *binary, GLsizei length); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETERIPROC) (GLuint program, GLenum pname, GLint value); +typedef void (APIENTRYP PFNGLUSEPROGRAMSTAGESPROC) (GLuint pipeline, GLbitfield stages, GLuint program); +typedef void (APIENTRYP PFNGLACTIVESHADERPROGRAMPROC) (GLuint pipeline, GLuint program); +typedef GLuint (APIENTRYP PFNGLCREATESHADERPROGRAMVPROC) (GLenum type, GLsizei count, const GLchar *const*strings); +typedef void (APIENTRYP PFNGLBINDPROGRAMPIPELINEPROC) (GLuint pipeline); +typedef void (APIENTRYP PFNGLDELETEPROGRAMPIPELINESPROC) (GLsizei n, const GLuint *pipelines); +typedef void (APIENTRYP PFNGLGENPROGRAMPIPELINESPROC) (GLsizei n, GLuint *pipelines); +typedef GLboolean (APIENTRYP PFNGLISPROGRAMPIPELINEPROC) (GLuint pipeline); +typedef void (APIENTRYP PFNGLGETPROGRAMPIPELINEIVPROC) (GLuint pipeline, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1IPROC) (GLuint program, GLint location, GLint v0); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1IVPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1FPROC) (GLuint program, GLint location, GLfloat v0); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1FVPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1DPROC) (GLuint program, GLint location, GLdouble v0); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1DVPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1UIPROC) (GLuint program, GLint location, GLuint v0); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1UIVPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2IPROC) (GLuint program, GLint location, GLint v0, GLint v1); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2IVPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2FPROC) (GLuint program, GLint location, GLfloat v0, GLfloat v1); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2FVPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2DPROC) (GLuint program, GLint location, GLdouble v0, GLdouble v1); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2DVPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2UIPROC) (GLuint program, GLint location, GLuint v0, GLuint v1); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2UIVPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3IPROC) (GLuint program, GLint location, GLint v0, GLint v1, GLint v2); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3IVPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3FPROC) (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3FVPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3DPROC) (GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3DVPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3UIPROC) (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3UIVPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4IPROC) (GLuint program, GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4IVPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4FPROC) (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4FVPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4DPROC) (GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2, GLdouble v3); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4DVPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4UIPROC) (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4UIVPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X3FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X2FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X4FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X2FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X4FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X3FVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X3DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X2DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X4DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X2DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X4DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X3DVPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLVALIDATEPROGRAMPIPELINEPROC) (GLuint pipeline); +typedef void (APIENTRYP PFNGLGETPROGRAMPIPELINEINFOLOGPROC) (GLuint pipeline, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1DPROC) (GLuint index, GLdouble x); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL2DPROC) (GLuint index, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL3DPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL4DPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL2DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL3DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL4DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBLPOINTERPROC) (GLuint index, GLint size, GLenum type, GLsizei stride, const void *pointer); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBLDVPROC) (GLuint index, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLVIEWPORTARRAYVPROC) (GLuint first, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLVIEWPORTINDEXEDFPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat w, GLfloat h); +typedef void (APIENTRYP PFNGLVIEWPORTINDEXEDFVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLSCISSORARRAYVPROC) (GLuint first, GLsizei count, const GLint *v); +typedef void (APIENTRYP PFNGLSCISSORINDEXEDPROC) (GLuint index, GLint left, GLint bottom, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLSCISSORINDEXEDVPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLDEPTHRANGEARRAYVPROC) (GLuint first, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLDEPTHRANGEINDEXEDPROC) (GLuint index, GLdouble n, GLdouble f); +typedef void (APIENTRYP PFNGLGETFLOATI_VPROC) (GLenum target, GLuint index, GLfloat *data); +typedef void (APIENTRYP PFNGLGETDOUBLEI_VPROC) (GLenum target, GLuint index, GLdouble *data); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glReleaseShaderCompiler (void); +GLAPI void APIENTRY glShaderBinary (GLsizei count, const GLuint *shaders, GLenum binaryFormat, const void *binary, GLsizei length); +GLAPI void APIENTRY glGetShaderPrecisionFormat (GLenum shadertype, GLenum precisiontype, GLint *range, GLint *precision); +GLAPI void APIENTRY glDepthRangef (GLfloat n, GLfloat f); +GLAPI void APIENTRY glClearDepthf (GLfloat d); +GLAPI void APIENTRY glGetProgramBinary (GLuint program, GLsizei bufSize, GLsizei *length, GLenum *binaryFormat, void *binary); +GLAPI void APIENTRY glProgramBinary (GLuint program, GLenum binaryFormat, const void *binary, GLsizei length); +GLAPI void APIENTRY glProgramParameteri (GLuint program, GLenum pname, GLint value); +GLAPI void APIENTRY glUseProgramStages (GLuint pipeline, GLbitfield stages, GLuint program); +GLAPI void APIENTRY glActiveShaderProgram (GLuint pipeline, GLuint program); +GLAPI GLuint APIENTRY glCreateShaderProgramv (GLenum type, GLsizei count, const GLchar *const*strings); +GLAPI void APIENTRY glBindProgramPipeline (GLuint pipeline); +GLAPI void APIENTRY glDeleteProgramPipelines (GLsizei n, const GLuint *pipelines); +GLAPI void APIENTRY glGenProgramPipelines (GLsizei n, GLuint *pipelines); +GLAPI GLboolean APIENTRY glIsProgramPipeline (GLuint pipeline); +GLAPI void APIENTRY glGetProgramPipelineiv (GLuint pipeline, GLenum pname, GLint *params); +GLAPI void APIENTRY glProgramUniform1i (GLuint program, GLint location, GLint v0); +GLAPI void APIENTRY glProgramUniform1iv (GLuint program, GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glProgramUniform1f (GLuint program, GLint location, GLfloat v0); +GLAPI void APIENTRY glProgramUniform1fv (GLuint program, GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glProgramUniform1d (GLuint program, GLint location, GLdouble v0); +GLAPI void APIENTRY glProgramUniform1dv (GLuint program, GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glProgramUniform1ui (GLuint program, GLint location, GLuint v0); +GLAPI void APIENTRY glProgramUniform1uiv (GLuint program, GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glProgramUniform2i (GLuint program, GLint location, GLint v0, GLint v1); +GLAPI void APIENTRY glProgramUniform2iv (GLuint program, GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glProgramUniform2f (GLuint program, GLint location, GLfloat v0, GLfloat v1); +GLAPI void APIENTRY glProgramUniform2fv (GLuint program, GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glProgramUniform2d (GLuint program, GLint location, GLdouble v0, GLdouble v1); +GLAPI void APIENTRY glProgramUniform2dv (GLuint program, GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glProgramUniform2ui (GLuint program, GLint location, GLuint v0, GLuint v1); +GLAPI void APIENTRY glProgramUniform2uiv (GLuint program, GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glProgramUniform3i (GLuint program, GLint location, GLint v0, GLint v1, GLint v2); +GLAPI void APIENTRY glProgramUniform3iv (GLuint program, GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glProgramUniform3f (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +GLAPI void APIENTRY glProgramUniform3fv (GLuint program, GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glProgramUniform3d (GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2); +GLAPI void APIENTRY glProgramUniform3dv (GLuint program, GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glProgramUniform3ui (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2); +GLAPI void APIENTRY glProgramUniform3uiv (GLuint program, GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glProgramUniform4i (GLuint program, GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +GLAPI void APIENTRY glProgramUniform4iv (GLuint program, GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glProgramUniform4f (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +GLAPI void APIENTRY glProgramUniform4fv (GLuint program, GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glProgramUniform4d (GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2, GLdouble v3); +GLAPI void APIENTRY glProgramUniform4dv (GLuint program, GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glProgramUniform4ui (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +GLAPI void APIENTRY glProgramUniform4uiv (GLuint program, GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glProgramUniformMatrix2fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix3fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix4fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix2dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix3dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix4dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix2x3fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix3x2fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix2x4fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix4x2fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix3x4fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix4x3fv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix2x3dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix3x2dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix2x4dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix4x2dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix3x4dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix4x3dv (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glValidateProgramPipeline (GLuint pipeline); +GLAPI void APIENTRY glGetProgramPipelineInfoLog (GLuint pipeline, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +GLAPI void APIENTRY glVertexAttribL1d (GLuint index, GLdouble x); +GLAPI void APIENTRY glVertexAttribL2d (GLuint index, GLdouble x, GLdouble y); +GLAPI void APIENTRY glVertexAttribL3d (GLuint index, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glVertexAttribL4d (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glVertexAttribL1dv (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribL2dv (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribL3dv (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribL4dv (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribLPointer (GLuint index, GLint size, GLenum type, GLsizei stride, const void *pointer); +GLAPI void APIENTRY glGetVertexAttribLdv (GLuint index, GLenum pname, GLdouble *params); +GLAPI void APIENTRY glViewportArrayv (GLuint first, GLsizei count, const GLfloat *v); +GLAPI void APIENTRY glViewportIndexedf (GLuint index, GLfloat x, GLfloat y, GLfloat w, GLfloat h); +GLAPI void APIENTRY glViewportIndexedfv (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glScissorArrayv (GLuint first, GLsizei count, const GLint *v); +GLAPI void APIENTRY glScissorIndexed (GLuint index, GLint left, GLint bottom, GLsizei width, GLsizei height); +GLAPI void APIENTRY glScissorIndexedv (GLuint index, const GLint *v); +GLAPI void APIENTRY glDepthRangeArrayv (GLuint first, GLsizei count, const GLdouble *v); +GLAPI void APIENTRY glDepthRangeIndexed (GLuint index, GLdouble n, GLdouble f); +GLAPI void APIENTRY glGetFloati_v (GLenum target, GLuint index, GLfloat *data); +GLAPI void APIENTRY glGetDoublei_v (GLenum target, GLuint index, GLdouble *data); +#endif +#endif /* GL_VERSION_4_1 */ + +#ifndef GL_VERSION_4_2 +#define GL_VERSION_4_2 1 +#define GL_COPY_READ_BUFFER_BINDING 0x8F36 +#define GL_COPY_WRITE_BUFFER_BINDING 0x8F37 +#define GL_TRANSFORM_FEEDBACK_ACTIVE 0x8E24 +#define GL_TRANSFORM_FEEDBACK_PAUSED 0x8E23 +#define GL_UNPACK_COMPRESSED_BLOCK_WIDTH 0x9127 +#define GL_UNPACK_COMPRESSED_BLOCK_HEIGHT 0x9128 +#define GL_UNPACK_COMPRESSED_BLOCK_DEPTH 0x9129 +#define GL_UNPACK_COMPRESSED_BLOCK_SIZE 0x912A +#define GL_PACK_COMPRESSED_BLOCK_WIDTH 0x912B +#define GL_PACK_COMPRESSED_BLOCK_HEIGHT 0x912C +#define GL_PACK_COMPRESSED_BLOCK_DEPTH 0x912D +#define GL_PACK_COMPRESSED_BLOCK_SIZE 0x912E +#define GL_NUM_SAMPLE_COUNTS 0x9380 +#define GL_MIN_MAP_BUFFER_ALIGNMENT 0x90BC +#define GL_ATOMIC_COUNTER_BUFFER 0x92C0 +#define GL_ATOMIC_COUNTER_BUFFER_BINDING 0x92C1 +#define GL_ATOMIC_COUNTER_BUFFER_START 0x92C2 +#define GL_ATOMIC_COUNTER_BUFFER_SIZE 0x92C3 +#define GL_ATOMIC_COUNTER_BUFFER_DATA_SIZE 0x92C4 +#define GL_ATOMIC_COUNTER_BUFFER_ACTIVE_ATOMIC_COUNTERS 0x92C5 +#define GL_ATOMIC_COUNTER_BUFFER_ACTIVE_ATOMIC_COUNTER_INDICES 0x92C6 +#define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_VERTEX_SHADER 0x92C7 +#define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_TESS_CONTROL_SHADER 0x92C8 +#define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_TESS_EVALUATION_SHADER 0x92C9 +#define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_GEOMETRY_SHADER 0x92CA +#define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_FRAGMENT_SHADER 0x92CB +#define GL_MAX_VERTEX_ATOMIC_COUNTER_BUFFERS 0x92CC +#define GL_MAX_TESS_CONTROL_ATOMIC_COUNTER_BUFFERS 0x92CD +#define GL_MAX_TESS_EVALUATION_ATOMIC_COUNTER_BUFFERS 0x92CE +#define GL_MAX_GEOMETRY_ATOMIC_COUNTER_BUFFERS 0x92CF +#define GL_MAX_FRAGMENT_ATOMIC_COUNTER_BUFFERS 0x92D0 +#define GL_MAX_COMBINED_ATOMIC_COUNTER_BUFFERS 0x92D1 +#define GL_MAX_VERTEX_ATOMIC_COUNTERS 0x92D2 +#define GL_MAX_TESS_CONTROL_ATOMIC_COUNTERS 0x92D3 +#define GL_MAX_TESS_EVALUATION_ATOMIC_COUNTERS 0x92D4 +#define GL_MAX_GEOMETRY_ATOMIC_COUNTERS 0x92D5 +#define GL_MAX_FRAGMENT_ATOMIC_COUNTERS 0x92D6 +#define GL_MAX_COMBINED_ATOMIC_COUNTERS 0x92D7 +#define GL_MAX_ATOMIC_COUNTER_BUFFER_SIZE 0x92D8 +#define GL_MAX_ATOMIC_COUNTER_BUFFER_BINDINGS 0x92DC +#define GL_ACTIVE_ATOMIC_COUNTER_BUFFERS 0x92D9 +#define GL_UNIFORM_ATOMIC_COUNTER_BUFFER_INDEX 0x92DA +#define GL_UNSIGNED_INT_ATOMIC_COUNTER 0x92DB +#define GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT 0x00000001 +#define GL_ELEMENT_ARRAY_BARRIER_BIT 0x00000002 +#define GL_UNIFORM_BARRIER_BIT 0x00000004 +#define GL_TEXTURE_FETCH_BARRIER_BIT 0x00000008 +#define GL_SHADER_IMAGE_ACCESS_BARRIER_BIT 0x00000020 +#define GL_COMMAND_BARRIER_BIT 0x00000040 +#define GL_PIXEL_BUFFER_BARRIER_BIT 0x00000080 +#define GL_TEXTURE_UPDATE_BARRIER_BIT 0x00000100 +#define GL_BUFFER_UPDATE_BARRIER_BIT 0x00000200 +#define GL_FRAMEBUFFER_BARRIER_BIT 0x00000400 +#define GL_TRANSFORM_FEEDBACK_BARRIER_BIT 0x00000800 +#define GL_ATOMIC_COUNTER_BARRIER_BIT 0x00001000 +#define GL_ALL_BARRIER_BITS 0xFFFFFFFF +#define GL_MAX_IMAGE_UNITS 0x8F38 +#define GL_MAX_COMBINED_IMAGE_UNITS_AND_FRAGMENT_OUTPUTS 0x8F39 +#define GL_IMAGE_BINDING_NAME 0x8F3A +#define GL_IMAGE_BINDING_LEVEL 0x8F3B +#define GL_IMAGE_BINDING_LAYERED 0x8F3C +#define GL_IMAGE_BINDING_LAYER 0x8F3D +#define GL_IMAGE_BINDING_ACCESS 0x8F3E +#define GL_IMAGE_1D 0x904C +#define GL_IMAGE_2D 0x904D +#define GL_IMAGE_3D 0x904E +#define GL_IMAGE_2D_RECT 0x904F +#define GL_IMAGE_CUBE 0x9050 +#define GL_IMAGE_BUFFER 0x9051 +#define GL_IMAGE_1D_ARRAY 0x9052 +#define GL_IMAGE_2D_ARRAY 0x9053 +#define GL_IMAGE_CUBE_MAP_ARRAY 0x9054 +#define GL_IMAGE_2D_MULTISAMPLE 0x9055 +#define GL_IMAGE_2D_MULTISAMPLE_ARRAY 0x9056 +#define GL_INT_IMAGE_1D 0x9057 +#define GL_INT_IMAGE_2D 0x9058 +#define GL_INT_IMAGE_3D 0x9059 +#define GL_INT_IMAGE_2D_RECT 0x905A +#define GL_INT_IMAGE_CUBE 0x905B +#define GL_INT_IMAGE_BUFFER 0x905C +#define GL_INT_IMAGE_1D_ARRAY 0x905D +#define GL_INT_IMAGE_2D_ARRAY 0x905E +#define GL_INT_IMAGE_CUBE_MAP_ARRAY 0x905F +#define GL_INT_IMAGE_2D_MULTISAMPLE 0x9060 +#define GL_INT_IMAGE_2D_MULTISAMPLE_ARRAY 0x9061 +#define GL_UNSIGNED_INT_IMAGE_1D 0x9062 +#define GL_UNSIGNED_INT_IMAGE_2D 0x9063 +#define GL_UNSIGNED_INT_IMAGE_3D 0x9064 +#define GL_UNSIGNED_INT_IMAGE_2D_RECT 0x9065 +#define GL_UNSIGNED_INT_IMAGE_CUBE 0x9066 +#define GL_UNSIGNED_INT_IMAGE_BUFFER 0x9067 +#define GL_UNSIGNED_INT_IMAGE_1D_ARRAY 0x9068 +#define GL_UNSIGNED_INT_IMAGE_2D_ARRAY 0x9069 +#define GL_UNSIGNED_INT_IMAGE_CUBE_MAP_ARRAY 0x906A +#define GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE 0x906B +#define GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_ARRAY 0x906C +#define GL_MAX_IMAGE_SAMPLES 0x906D +#define GL_IMAGE_BINDING_FORMAT 0x906E +#define GL_IMAGE_FORMAT_COMPATIBILITY_TYPE 0x90C7 +#define GL_IMAGE_FORMAT_COMPATIBILITY_BY_SIZE 0x90C8 +#define GL_IMAGE_FORMAT_COMPATIBILITY_BY_CLASS 0x90C9 +#define GL_MAX_VERTEX_IMAGE_UNIFORMS 0x90CA +#define GL_MAX_TESS_CONTROL_IMAGE_UNIFORMS 0x90CB +#define GL_MAX_TESS_EVALUATION_IMAGE_UNIFORMS 0x90CC +#define GL_MAX_GEOMETRY_IMAGE_UNIFORMS 0x90CD +#define GL_MAX_FRAGMENT_IMAGE_UNIFORMS 0x90CE +#define GL_MAX_COMBINED_IMAGE_UNIFORMS 0x90CF +#define GL_COMPRESSED_RGBA_BPTC_UNORM 0x8E8C +#define GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM 0x8E8D +#define GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT 0x8E8E +#define GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT 0x8E8F +#define GL_TEXTURE_IMMUTABLE_FORMAT 0x912F +typedef void (APIENTRYP PFNGLDRAWARRAYSINSTANCEDBASEINSTANCEPROC) (GLenum mode, GLint first, GLsizei count, GLsizei instancecount, GLuint baseinstance); +typedef void (APIENTRYP PFNGLDRAWELEMENTSINSTANCEDBASEINSTANCEPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount, GLuint baseinstance); +typedef void (APIENTRYP PFNGLDRAWELEMENTSINSTANCEDBASEVERTEXBASEINSTANCEPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount, GLint basevertex, GLuint baseinstance); +typedef void (APIENTRYP PFNGLGETINTERNALFORMATIVPROC) (GLenum target, GLenum internalformat, GLenum pname, GLsizei count, GLint *params); +typedef void (APIENTRYP PFNGLGETACTIVEATOMICCOUNTERBUFFERIVPROC) (GLuint program, GLuint bufferIndex, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLBINDIMAGETEXTUREPROC) (GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLenum format); +typedef void (APIENTRYP PFNGLMEMORYBARRIERPROC) (GLbitfield barriers); +typedef void (APIENTRYP PFNGLTEXSTORAGE1DPROC) (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width); +typedef void (APIENTRYP PFNGLTEXSTORAGE2DPROC) (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLTEXSTORAGE3DPROC) (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth); +typedef void (APIENTRYP PFNGLDRAWTRANSFORMFEEDBACKINSTANCEDPROC) (GLenum mode, GLuint id, GLsizei instancecount); +typedef void (APIENTRYP PFNGLDRAWTRANSFORMFEEDBACKSTREAMINSTANCEDPROC) (GLenum mode, GLuint id, GLuint stream, GLsizei instancecount); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawArraysInstancedBaseInstance (GLenum mode, GLint first, GLsizei count, GLsizei instancecount, GLuint baseinstance); +GLAPI void APIENTRY glDrawElementsInstancedBaseInstance (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount, GLuint baseinstance); +GLAPI void APIENTRY glDrawElementsInstancedBaseVertexBaseInstance (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount, GLint basevertex, GLuint baseinstance); +GLAPI void APIENTRY glGetInternalformativ (GLenum target, GLenum internalformat, GLenum pname, GLsizei count, GLint *params); +GLAPI void APIENTRY glGetActiveAtomicCounterBufferiv (GLuint program, GLuint bufferIndex, GLenum pname, GLint *params); +GLAPI void APIENTRY glBindImageTexture (GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLenum format); +GLAPI void APIENTRY glMemoryBarrier (GLbitfield barriers); +GLAPI void APIENTRY glTexStorage1D (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width); +GLAPI void APIENTRY glTexStorage2D (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glTexStorage3D (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth); +GLAPI void APIENTRY glDrawTransformFeedbackInstanced (GLenum mode, GLuint id, GLsizei instancecount); +GLAPI void APIENTRY glDrawTransformFeedbackStreamInstanced (GLenum mode, GLuint id, GLuint stream, GLsizei instancecount); +#endif +#endif /* GL_VERSION_4_2 */ + +#ifndef GL_VERSION_4_3 +#define GL_VERSION_4_3 1 +typedef void (APIENTRY *GLDEBUGPROC)(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,const void *userParam); +#define GL_NUM_SHADING_LANGUAGE_VERSIONS 0x82E9 +#define GL_VERTEX_ATTRIB_ARRAY_LONG 0x874E +#define GL_COMPRESSED_RGB8_ETC2 0x9274 +#define GL_COMPRESSED_SRGB8_ETC2 0x9275 +#define GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 0x9276 +#define GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 0x9277 +#define GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278 +#define GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC 0x9279 +#define GL_COMPRESSED_R11_EAC 0x9270 +#define GL_COMPRESSED_SIGNED_R11_EAC 0x9271 +#define GL_COMPRESSED_RG11_EAC 0x9272 +#define GL_COMPRESSED_SIGNED_RG11_EAC 0x9273 +#define GL_PRIMITIVE_RESTART_FIXED_INDEX 0x8D69 +#define GL_ANY_SAMPLES_PASSED_CONSERVATIVE 0x8D6A +#define GL_MAX_ELEMENT_INDEX 0x8D6B +#define GL_COMPUTE_SHADER 0x91B9 +#define GL_MAX_COMPUTE_UNIFORM_BLOCKS 0x91BB +#define GL_MAX_COMPUTE_TEXTURE_IMAGE_UNITS 0x91BC +#define GL_MAX_COMPUTE_IMAGE_UNIFORMS 0x91BD +#define GL_MAX_COMPUTE_SHARED_MEMORY_SIZE 0x8262 +#define GL_MAX_COMPUTE_UNIFORM_COMPONENTS 0x8263 +#define GL_MAX_COMPUTE_ATOMIC_COUNTER_BUFFERS 0x8264 +#define GL_MAX_COMPUTE_ATOMIC_COUNTERS 0x8265 +#define GL_MAX_COMBINED_COMPUTE_UNIFORM_COMPONENTS 0x8266 +#define GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS 0x90EB +#define GL_MAX_COMPUTE_WORK_GROUP_COUNT 0x91BE +#define GL_MAX_COMPUTE_WORK_GROUP_SIZE 0x91BF +#define GL_COMPUTE_WORK_GROUP_SIZE 0x8267 +#define GL_UNIFORM_BLOCK_REFERENCED_BY_COMPUTE_SHADER 0x90EC +#define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_COMPUTE_SHADER 0x90ED +#define GL_DISPATCH_INDIRECT_BUFFER 0x90EE +#define GL_DISPATCH_INDIRECT_BUFFER_BINDING 0x90EF +#define GL_COMPUTE_SHADER_BIT 0x00000020 +#define GL_DEBUG_OUTPUT_SYNCHRONOUS 0x8242 +#define GL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH 0x8243 +#define GL_DEBUG_CALLBACK_FUNCTION 0x8244 +#define GL_DEBUG_CALLBACK_USER_PARAM 0x8245 +#define GL_DEBUG_SOURCE_API 0x8246 +#define GL_DEBUG_SOURCE_WINDOW_SYSTEM 0x8247 +#define GL_DEBUG_SOURCE_SHADER_COMPILER 0x8248 +#define GL_DEBUG_SOURCE_THIRD_PARTY 0x8249 +#define GL_DEBUG_SOURCE_APPLICATION 0x824A +#define GL_DEBUG_SOURCE_OTHER 0x824B +#define GL_DEBUG_TYPE_ERROR 0x824C +#define GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR 0x824D +#define GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR 0x824E +#define GL_DEBUG_TYPE_PORTABILITY 0x824F +#define GL_DEBUG_TYPE_PERFORMANCE 0x8250 +#define GL_DEBUG_TYPE_OTHER 0x8251 +#define GL_MAX_DEBUG_MESSAGE_LENGTH 0x9143 +#define GL_MAX_DEBUG_LOGGED_MESSAGES 0x9144 +#define GL_DEBUG_LOGGED_MESSAGES 0x9145 +#define GL_DEBUG_SEVERITY_HIGH 0x9146 +#define GL_DEBUG_SEVERITY_MEDIUM 0x9147 +#define GL_DEBUG_SEVERITY_LOW 0x9148 +#define GL_DEBUG_TYPE_MARKER 0x8268 +#define GL_DEBUG_TYPE_PUSH_GROUP 0x8269 +#define GL_DEBUG_TYPE_POP_GROUP 0x826A +#define GL_DEBUG_SEVERITY_NOTIFICATION 0x826B +#define GL_MAX_DEBUG_GROUP_STACK_DEPTH 0x826C +#define GL_DEBUG_GROUP_STACK_DEPTH 0x826D +#define GL_BUFFER 0x82E0 +#define GL_SHADER 0x82E1 +#define GL_PROGRAM 0x82E2 +#define GL_QUERY 0x82E3 +#define GL_PROGRAM_PIPELINE 0x82E4 +#define GL_SAMPLER 0x82E6 +#define GL_MAX_LABEL_LENGTH 0x82E8 +#define GL_DEBUG_OUTPUT 0x92E0 +#define GL_CONTEXT_FLAG_DEBUG_BIT 0x00000002 +#define GL_MAX_UNIFORM_LOCATIONS 0x826E +#define GL_FRAMEBUFFER_DEFAULT_WIDTH 0x9310 +#define GL_FRAMEBUFFER_DEFAULT_HEIGHT 0x9311 +#define GL_FRAMEBUFFER_DEFAULT_LAYERS 0x9312 +#define GL_FRAMEBUFFER_DEFAULT_SAMPLES 0x9313 +#define GL_FRAMEBUFFER_DEFAULT_FIXED_SAMPLE_LOCATIONS 0x9314 +#define GL_MAX_FRAMEBUFFER_WIDTH 0x9315 +#define GL_MAX_FRAMEBUFFER_HEIGHT 0x9316 +#define GL_MAX_FRAMEBUFFER_LAYERS 0x9317 +#define GL_MAX_FRAMEBUFFER_SAMPLES 0x9318 +#define GL_INTERNALFORMAT_SUPPORTED 0x826F +#define GL_INTERNALFORMAT_PREFERRED 0x8270 +#define GL_INTERNALFORMAT_RED_SIZE 0x8271 +#define GL_INTERNALFORMAT_GREEN_SIZE 0x8272 +#define GL_INTERNALFORMAT_BLUE_SIZE 0x8273 +#define GL_INTERNALFORMAT_ALPHA_SIZE 0x8274 +#define GL_INTERNALFORMAT_DEPTH_SIZE 0x8275 +#define GL_INTERNALFORMAT_STENCIL_SIZE 0x8276 +#define GL_INTERNALFORMAT_SHARED_SIZE 0x8277 +#define GL_INTERNALFORMAT_RED_TYPE 0x8278 +#define GL_INTERNALFORMAT_GREEN_TYPE 0x8279 +#define GL_INTERNALFORMAT_BLUE_TYPE 0x827A +#define GL_INTERNALFORMAT_ALPHA_TYPE 0x827B +#define GL_INTERNALFORMAT_DEPTH_TYPE 0x827C +#define GL_INTERNALFORMAT_STENCIL_TYPE 0x827D +#define GL_MAX_WIDTH 0x827E +#define GL_MAX_HEIGHT 0x827F +#define GL_MAX_DEPTH 0x8280 +#define GL_MAX_LAYERS 0x8281 +#define GL_MAX_COMBINED_DIMENSIONS 0x8282 +#define GL_COLOR_COMPONENTS 0x8283 +#define GL_DEPTH_COMPONENTS 0x8284 +#define GL_STENCIL_COMPONENTS 0x8285 +#define GL_COLOR_RENDERABLE 0x8286 +#define GL_DEPTH_RENDERABLE 0x8287 +#define GL_STENCIL_RENDERABLE 0x8288 +#define GL_FRAMEBUFFER_RENDERABLE 0x8289 +#define GL_FRAMEBUFFER_RENDERABLE_LAYERED 0x828A +#define GL_FRAMEBUFFER_BLEND 0x828B +#define GL_READ_PIXELS 0x828C +#define GL_READ_PIXELS_FORMAT 0x828D +#define GL_READ_PIXELS_TYPE 0x828E +#define GL_TEXTURE_IMAGE_FORMAT 0x828F +#define GL_TEXTURE_IMAGE_TYPE 0x8290 +#define GL_GET_TEXTURE_IMAGE_FORMAT 0x8291 +#define GL_GET_TEXTURE_IMAGE_TYPE 0x8292 +#define GL_MIPMAP 0x8293 +#define GL_MANUAL_GENERATE_MIPMAP 0x8294 +#define GL_AUTO_GENERATE_MIPMAP 0x8295 +#define GL_COLOR_ENCODING 0x8296 +#define GL_SRGB_READ 0x8297 +#define GL_SRGB_WRITE 0x8298 +#define GL_FILTER 0x829A +#define GL_VERTEX_TEXTURE 0x829B +#define GL_TESS_CONTROL_TEXTURE 0x829C +#define GL_TESS_EVALUATION_TEXTURE 0x829D +#define GL_GEOMETRY_TEXTURE 0x829E +#define GL_FRAGMENT_TEXTURE 0x829F +#define GL_COMPUTE_TEXTURE 0x82A0 +#define GL_TEXTURE_SHADOW 0x82A1 +#define GL_TEXTURE_GATHER 0x82A2 +#define GL_TEXTURE_GATHER_SHADOW 0x82A3 +#define GL_SHADER_IMAGE_LOAD 0x82A4 +#define GL_SHADER_IMAGE_STORE 0x82A5 +#define GL_SHADER_IMAGE_ATOMIC 0x82A6 +#define GL_IMAGE_TEXEL_SIZE 0x82A7 +#define GL_IMAGE_COMPATIBILITY_CLASS 0x82A8 +#define GL_IMAGE_PIXEL_FORMAT 0x82A9 +#define GL_IMAGE_PIXEL_TYPE 0x82AA +#define GL_SIMULTANEOUS_TEXTURE_AND_DEPTH_TEST 0x82AC +#define GL_SIMULTANEOUS_TEXTURE_AND_STENCIL_TEST 0x82AD +#define GL_SIMULTANEOUS_TEXTURE_AND_DEPTH_WRITE 0x82AE +#define GL_SIMULTANEOUS_TEXTURE_AND_STENCIL_WRITE 0x82AF +#define GL_TEXTURE_COMPRESSED_BLOCK_WIDTH 0x82B1 +#define GL_TEXTURE_COMPRESSED_BLOCK_HEIGHT 0x82B2 +#define GL_TEXTURE_COMPRESSED_BLOCK_SIZE 0x82B3 +#define GL_CLEAR_BUFFER 0x82B4 +#define GL_TEXTURE_VIEW 0x82B5 +#define GL_VIEW_COMPATIBILITY_CLASS 0x82B6 +#define GL_FULL_SUPPORT 0x82B7 +#define GL_CAVEAT_SUPPORT 0x82B8 +#define GL_IMAGE_CLASS_4_X_32 0x82B9 +#define GL_IMAGE_CLASS_2_X_32 0x82BA +#define GL_IMAGE_CLASS_1_X_32 0x82BB +#define GL_IMAGE_CLASS_4_X_16 0x82BC +#define GL_IMAGE_CLASS_2_X_16 0x82BD +#define GL_IMAGE_CLASS_1_X_16 0x82BE +#define GL_IMAGE_CLASS_4_X_8 0x82BF +#define GL_IMAGE_CLASS_2_X_8 0x82C0 +#define GL_IMAGE_CLASS_1_X_8 0x82C1 +#define GL_IMAGE_CLASS_11_11_10 0x82C2 +#define GL_IMAGE_CLASS_10_10_10_2 0x82C3 +#define GL_VIEW_CLASS_128_BITS 0x82C4 +#define GL_VIEW_CLASS_96_BITS 0x82C5 +#define GL_VIEW_CLASS_64_BITS 0x82C6 +#define GL_VIEW_CLASS_48_BITS 0x82C7 +#define GL_VIEW_CLASS_32_BITS 0x82C8 +#define GL_VIEW_CLASS_24_BITS 0x82C9 +#define GL_VIEW_CLASS_16_BITS 0x82CA +#define GL_VIEW_CLASS_8_BITS 0x82CB +#define GL_VIEW_CLASS_S3TC_DXT1_RGB 0x82CC +#define GL_VIEW_CLASS_S3TC_DXT1_RGBA 0x82CD +#define GL_VIEW_CLASS_S3TC_DXT3_RGBA 0x82CE +#define GL_VIEW_CLASS_S3TC_DXT5_RGBA 0x82CF +#define GL_VIEW_CLASS_RGTC1_RED 0x82D0 +#define GL_VIEW_CLASS_RGTC2_RG 0x82D1 +#define GL_VIEW_CLASS_BPTC_UNORM 0x82D2 +#define GL_VIEW_CLASS_BPTC_FLOAT 0x82D3 +#define GL_UNIFORM 0x92E1 +#define GL_UNIFORM_BLOCK 0x92E2 +#define GL_PROGRAM_INPUT 0x92E3 +#define GL_PROGRAM_OUTPUT 0x92E4 +#define GL_BUFFER_VARIABLE 0x92E5 +#define GL_SHADER_STORAGE_BLOCK 0x92E6 +#define GL_VERTEX_SUBROUTINE 0x92E8 +#define GL_TESS_CONTROL_SUBROUTINE 0x92E9 +#define GL_TESS_EVALUATION_SUBROUTINE 0x92EA +#define GL_GEOMETRY_SUBROUTINE 0x92EB +#define GL_FRAGMENT_SUBROUTINE 0x92EC +#define GL_COMPUTE_SUBROUTINE 0x92ED +#define GL_VERTEX_SUBROUTINE_UNIFORM 0x92EE +#define GL_TESS_CONTROL_SUBROUTINE_UNIFORM 0x92EF +#define GL_TESS_EVALUATION_SUBROUTINE_UNIFORM 0x92F0 +#define GL_GEOMETRY_SUBROUTINE_UNIFORM 0x92F1 +#define GL_FRAGMENT_SUBROUTINE_UNIFORM 0x92F2 +#define GL_COMPUTE_SUBROUTINE_UNIFORM 0x92F3 +#define GL_TRANSFORM_FEEDBACK_VARYING 0x92F4 +#define GL_ACTIVE_RESOURCES 0x92F5 +#define GL_MAX_NAME_LENGTH 0x92F6 +#define GL_MAX_NUM_ACTIVE_VARIABLES 0x92F7 +#define GL_MAX_NUM_COMPATIBLE_SUBROUTINES 0x92F8 +#define GL_NAME_LENGTH 0x92F9 +#define GL_TYPE 0x92FA +#define GL_ARRAY_SIZE 0x92FB +#define GL_OFFSET 0x92FC +#define GL_BLOCK_INDEX 0x92FD +#define GL_ARRAY_STRIDE 0x92FE +#define GL_MATRIX_STRIDE 0x92FF +#define GL_IS_ROW_MAJOR 0x9300 +#define GL_ATOMIC_COUNTER_BUFFER_INDEX 0x9301 +#define GL_BUFFER_BINDING 0x9302 +#define GL_BUFFER_DATA_SIZE 0x9303 +#define GL_NUM_ACTIVE_VARIABLES 0x9304 +#define GL_ACTIVE_VARIABLES 0x9305 +#define GL_REFERENCED_BY_VERTEX_SHADER 0x9306 +#define GL_REFERENCED_BY_TESS_CONTROL_SHADER 0x9307 +#define GL_REFERENCED_BY_TESS_EVALUATION_SHADER 0x9308 +#define GL_REFERENCED_BY_GEOMETRY_SHADER 0x9309 +#define GL_REFERENCED_BY_FRAGMENT_SHADER 0x930A +#define GL_REFERENCED_BY_COMPUTE_SHADER 0x930B +#define GL_TOP_LEVEL_ARRAY_SIZE 0x930C +#define GL_TOP_LEVEL_ARRAY_STRIDE 0x930D +#define GL_LOCATION 0x930E +#define GL_LOCATION_INDEX 0x930F +#define GL_IS_PER_PATCH 0x92E7 +#define GL_SHADER_STORAGE_BUFFER 0x90D2 +#define GL_SHADER_STORAGE_BUFFER_BINDING 0x90D3 +#define GL_SHADER_STORAGE_BUFFER_START 0x90D4 +#define GL_SHADER_STORAGE_BUFFER_SIZE 0x90D5 +#define GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS 0x90D6 +#define GL_MAX_GEOMETRY_SHADER_STORAGE_BLOCKS 0x90D7 +#define GL_MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS 0x90D8 +#define GL_MAX_TESS_EVALUATION_SHADER_STORAGE_BLOCKS 0x90D9 +#define GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS 0x90DA +#define GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS 0x90DB +#define GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS 0x90DC +#define GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS 0x90DD +#define GL_MAX_SHADER_STORAGE_BLOCK_SIZE 0x90DE +#define GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT 0x90DF +#define GL_SHADER_STORAGE_BARRIER_BIT 0x00002000 +#define GL_MAX_COMBINED_SHADER_OUTPUT_RESOURCES 0x8F39 +#define GL_DEPTH_STENCIL_TEXTURE_MODE 0x90EA +#define GL_TEXTURE_BUFFER_OFFSET 0x919D +#define GL_TEXTURE_BUFFER_SIZE 0x919E +#define GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT 0x919F +#define GL_TEXTURE_VIEW_MIN_LEVEL 0x82DB +#define GL_TEXTURE_VIEW_NUM_LEVELS 0x82DC +#define GL_TEXTURE_VIEW_MIN_LAYER 0x82DD +#define GL_TEXTURE_VIEW_NUM_LAYERS 0x82DE +#define GL_TEXTURE_IMMUTABLE_LEVELS 0x82DF +#define GL_VERTEX_ATTRIB_BINDING 0x82D4 +#define GL_VERTEX_ATTRIB_RELATIVE_OFFSET 0x82D5 +#define GL_VERTEX_BINDING_DIVISOR 0x82D6 +#define GL_VERTEX_BINDING_OFFSET 0x82D7 +#define GL_VERTEX_BINDING_STRIDE 0x82D8 +#define GL_MAX_VERTEX_ATTRIB_RELATIVE_OFFSET 0x82D9 +#define GL_MAX_VERTEX_ATTRIB_BINDINGS 0x82DA +#define GL_VERTEX_BINDING_BUFFER 0x8F4F +#define GL_DISPLAY_LIST 0x82E7 +typedef void (APIENTRYP PFNGLCLEARBUFFERDATAPROC) (GLenum target, GLenum internalformat, GLenum format, GLenum type, const void *data); +typedef void (APIENTRYP PFNGLCLEARBUFFERSUBDATAPROC) (GLenum target, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void *data); +typedef void (APIENTRYP PFNGLDISPATCHCOMPUTEPROC) (GLuint num_groups_x, GLuint num_groups_y, GLuint num_groups_z); +typedef void (APIENTRYP PFNGLDISPATCHCOMPUTEINDIRECTPROC) (GLintptr indirect); +typedef void (APIENTRYP PFNGLCOPYIMAGESUBDATAPROC) (GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth); +typedef void (APIENTRYP PFNGLFRAMEBUFFERPARAMETERIPROC) (GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLGETFRAMEBUFFERPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETINTERNALFORMATI64VPROC) (GLenum target, GLenum internalformat, GLenum pname, GLsizei count, GLint64 *params); +typedef void (APIENTRYP PFNGLINVALIDATETEXSUBIMAGEPROC) (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth); +typedef void (APIENTRYP PFNGLINVALIDATETEXIMAGEPROC) (GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLINVALIDATEBUFFERSUBDATAPROC) (GLuint buffer, GLintptr offset, GLsizeiptr length); +typedef void (APIENTRYP PFNGLINVALIDATEBUFFERDATAPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLINVALIDATEFRAMEBUFFERPROC) (GLenum target, GLsizei numAttachments, const GLenum *attachments); +typedef void (APIENTRYP PFNGLINVALIDATESUBFRAMEBUFFERPROC) (GLenum target, GLsizei numAttachments, const GLenum *attachments, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSINDIRECTPROC) (GLenum mode, const void *indirect, GLsizei drawcount, GLsizei stride); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSINDIRECTPROC) (GLenum mode, GLenum type, const void *indirect, GLsizei drawcount, GLsizei stride); +typedef void (APIENTRYP PFNGLGETPROGRAMINTERFACEIVPROC) (GLuint program, GLenum programInterface, GLenum pname, GLint *params); +typedef GLuint (APIENTRYP PFNGLGETPROGRAMRESOURCEINDEXPROC) (GLuint program, GLenum programInterface, const GLchar *name); +typedef void (APIENTRYP PFNGLGETPROGRAMRESOURCENAMEPROC) (GLuint program, GLenum programInterface, GLuint index, GLsizei bufSize, GLsizei *length, GLchar *name); +typedef void (APIENTRYP PFNGLGETPROGRAMRESOURCEIVPROC) (GLuint program, GLenum programInterface, GLuint index, GLsizei propCount, const GLenum *props, GLsizei count, GLsizei *length, GLint *params); +typedef GLint (APIENTRYP PFNGLGETPROGRAMRESOURCELOCATIONPROC) (GLuint program, GLenum programInterface, const GLchar *name); +typedef GLint (APIENTRYP PFNGLGETPROGRAMRESOURCELOCATIONINDEXPROC) (GLuint program, GLenum programInterface, const GLchar *name); +typedef void (APIENTRYP PFNGLSHADERSTORAGEBLOCKBINDINGPROC) (GLuint program, GLuint storageBlockIndex, GLuint storageBlockBinding); +typedef void (APIENTRYP PFNGLTEXBUFFERRANGEPROC) (GLenum target, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size); +typedef void (APIENTRYP PFNGLTEXSTORAGE2DMULTISAMPLEPROC) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations); +typedef void (APIENTRYP PFNGLTEXSTORAGE3DMULTISAMPLEPROC) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations); +typedef void (APIENTRYP PFNGLTEXTUREVIEWPROC) (GLuint texture, GLenum target, GLuint origtexture, GLenum internalformat, GLuint minlevel, GLuint numlevels, GLuint minlayer, GLuint numlayers); +typedef void (APIENTRYP PFNGLBINDVERTEXBUFFERPROC) (GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride); +typedef void (APIENTRYP PFNGLVERTEXATTRIBFORMATPROC) (GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset); +typedef void (APIENTRYP PFNGLVERTEXATTRIBIFORMATPROC) (GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); +typedef void (APIENTRYP PFNGLVERTEXATTRIBLFORMATPROC) (GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); +typedef void (APIENTRYP PFNGLVERTEXATTRIBBINDINGPROC) (GLuint attribindex, GLuint bindingindex); +typedef void (APIENTRYP PFNGLVERTEXBINDINGDIVISORPROC) (GLuint bindingindex, GLuint divisor); +typedef void (APIENTRYP PFNGLDEBUGMESSAGECONTROLPROC) (GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled); +typedef void (APIENTRYP PFNGLDEBUGMESSAGEINSERTPROC) (GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *buf); +typedef void (APIENTRYP PFNGLDEBUGMESSAGECALLBACKPROC) (GLDEBUGPROC callback, const void *userParam); +typedef GLuint (APIENTRYP PFNGLGETDEBUGMESSAGELOGPROC) (GLuint count, GLsizei bufSize, GLenum *sources, GLenum *types, GLuint *ids, GLenum *severities, GLsizei *lengths, GLchar *messageLog); +typedef void (APIENTRYP PFNGLPUSHDEBUGGROUPPROC) (GLenum source, GLuint id, GLsizei length, const GLchar *message); +typedef void (APIENTRYP PFNGLPOPDEBUGGROUPPROC) (void); +typedef void (APIENTRYP PFNGLOBJECTLABELPROC) (GLenum identifier, GLuint name, GLsizei length, const GLchar *label); +typedef void (APIENTRYP PFNGLGETOBJECTLABELPROC) (GLenum identifier, GLuint name, GLsizei bufSize, GLsizei *length, GLchar *label); +typedef void (APIENTRYP PFNGLOBJECTPTRLABELPROC) (const void *ptr, GLsizei length, const GLchar *label); +typedef void (APIENTRYP PFNGLGETOBJECTPTRLABELPROC) (const void *ptr, GLsizei bufSize, GLsizei *length, GLchar *label); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glClearBufferData (GLenum target, GLenum internalformat, GLenum format, GLenum type, const void *data); +GLAPI void APIENTRY glClearBufferSubData (GLenum target, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void *data); +GLAPI void APIENTRY glDispatchCompute (GLuint num_groups_x, GLuint num_groups_y, GLuint num_groups_z); +GLAPI void APIENTRY glDispatchComputeIndirect (GLintptr indirect); +GLAPI void APIENTRY glCopyImageSubData (GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth); +GLAPI void APIENTRY glFramebufferParameteri (GLenum target, GLenum pname, GLint param); +GLAPI void APIENTRY glGetFramebufferParameteriv (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetInternalformati64v (GLenum target, GLenum internalformat, GLenum pname, GLsizei count, GLint64 *params); +GLAPI void APIENTRY glInvalidateTexSubImage (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth); +GLAPI void APIENTRY glInvalidateTexImage (GLuint texture, GLint level); +GLAPI void APIENTRY glInvalidateBufferSubData (GLuint buffer, GLintptr offset, GLsizeiptr length); +GLAPI void APIENTRY glInvalidateBufferData (GLuint buffer); +GLAPI void APIENTRY glInvalidateFramebuffer (GLenum target, GLsizei numAttachments, const GLenum *attachments); +GLAPI void APIENTRY glInvalidateSubFramebuffer (GLenum target, GLsizei numAttachments, const GLenum *attachments, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glMultiDrawArraysIndirect (GLenum mode, const void *indirect, GLsizei drawcount, GLsizei stride); +GLAPI void APIENTRY glMultiDrawElementsIndirect (GLenum mode, GLenum type, const void *indirect, GLsizei drawcount, GLsizei stride); +GLAPI void APIENTRY glGetProgramInterfaceiv (GLuint program, GLenum programInterface, GLenum pname, GLint *params); +GLAPI GLuint APIENTRY glGetProgramResourceIndex (GLuint program, GLenum programInterface, const GLchar *name); +GLAPI void APIENTRY glGetProgramResourceName (GLuint program, GLenum programInterface, GLuint index, GLsizei bufSize, GLsizei *length, GLchar *name); +GLAPI void APIENTRY glGetProgramResourceiv (GLuint program, GLenum programInterface, GLuint index, GLsizei propCount, const GLenum *props, GLsizei count, GLsizei *length, GLint *params); +GLAPI GLint APIENTRY glGetProgramResourceLocation (GLuint program, GLenum programInterface, const GLchar *name); +GLAPI GLint APIENTRY glGetProgramResourceLocationIndex (GLuint program, GLenum programInterface, const GLchar *name); +GLAPI void APIENTRY glShaderStorageBlockBinding (GLuint program, GLuint storageBlockIndex, GLuint storageBlockBinding); +GLAPI void APIENTRY glTexBufferRange (GLenum target, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size); +GLAPI void APIENTRY glTexStorage2DMultisample (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations); +GLAPI void APIENTRY glTexStorage3DMultisample (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations); +GLAPI void APIENTRY glTextureView (GLuint texture, GLenum target, GLuint origtexture, GLenum internalformat, GLuint minlevel, GLuint numlevels, GLuint minlayer, GLuint numlayers); +GLAPI void APIENTRY glBindVertexBuffer (GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride); +GLAPI void APIENTRY glVertexAttribFormat (GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset); +GLAPI void APIENTRY glVertexAttribIFormat (GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); +GLAPI void APIENTRY glVertexAttribLFormat (GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); +GLAPI void APIENTRY glVertexAttribBinding (GLuint attribindex, GLuint bindingindex); +GLAPI void APIENTRY glVertexBindingDivisor (GLuint bindingindex, GLuint divisor); +GLAPI void APIENTRY glDebugMessageControl (GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled); +GLAPI void APIENTRY glDebugMessageInsert (GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *buf); +GLAPI void APIENTRY glDebugMessageCallback (GLDEBUGPROC callback, const void *userParam); +GLAPI GLuint APIENTRY glGetDebugMessageLog (GLuint count, GLsizei bufSize, GLenum *sources, GLenum *types, GLuint *ids, GLenum *severities, GLsizei *lengths, GLchar *messageLog); +GLAPI void APIENTRY glPushDebugGroup (GLenum source, GLuint id, GLsizei length, const GLchar *message); +GLAPI void APIENTRY glPopDebugGroup (void); +GLAPI void APIENTRY glObjectLabel (GLenum identifier, GLuint name, GLsizei length, const GLchar *label); +GLAPI void APIENTRY glGetObjectLabel (GLenum identifier, GLuint name, GLsizei bufSize, GLsizei *length, GLchar *label); +GLAPI void APIENTRY glObjectPtrLabel (const void *ptr, GLsizei length, const GLchar *label); +GLAPI void APIENTRY glGetObjectPtrLabel (const void *ptr, GLsizei bufSize, GLsizei *length, GLchar *label); +#endif +#endif /* GL_VERSION_4_3 */ + +#ifndef GL_VERSION_4_4 +#define GL_VERSION_4_4 1 +#define GL_MAX_VERTEX_ATTRIB_STRIDE 0x82E5 +#define GL_PRIMITIVE_RESTART_FOR_PATCHES_SUPPORTED 0x8221 +#define GL_TEXTURE_BUFFER_BINDING 0x8C2A +#define GL_MAP_PERSISTENT_BIT 0x0040 +#define GL_MAP_COHERENT_BIT 0x0080 +#define GL_DYNAMIC_STORAGE_BIT 0x0100 +#define GL_CLIENT_STORAGE_BIT 0x0200 +#define GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT 0x00004000 +#define GL_BUFFER_IMMUTABLE_STORAGE 0x821F +#define GL_BUFFER_STORAGE_FLAGS 0x8220 +#define GL_CLEAR_TEXTURE 0x9365 +#define GL_LOCATION_COMPONENT 0x934A +#define GL_TRANSFORM_FEEDBACK_BUFFER_INDEX 0x934B +#define GL_TRANSFORM_FEEDBACK_BUFFER_STRIDE 0x934C +#define GL_QUERY_BUFFER 0x9192 +#define GL_QUERY_BUFFER_BARRIER_BIT 0x00008000 +#define GL_QUERY_BUFFER_BINDING 0x9193 +#define GL_QUERY_RESULT_NO_WAIT 0x9194 +#define GL_MIRROR_CLAMP_TO_EDGE 0x8743 +typedef void (APIENTRYP PFNGLBUFFERSTORAGEPROC) (GLenum target, GLsizeiptr size, const void *data, GLbitfield flags); +typedef void (APIENTRYP PFNGLCLEARTEXIMAGEPROC) (GLuint texture, GLint level, GLenum format, GLenum type, const void *data); +typedef void (APIENTRYP PFNGLCLEARTEXSUBIMAGEPROC) (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *data); +typedef void (APIENTRYP PFNGLBINDBUFFERSBASEPROC) (GLenum target, GLuint first, GLsizei count, const GLuint *buffers); +typedef void (APIENTRYP PFNGLBINDBUFFERSRANGEPROC) (GLenum target, GLuint first, GLsizei count, const GLuint *buffers, const GLintptr *offsets, const GLsizeiptr *sizes); +typedef void (APIENTRYP PFNGLBINDTEXTURESPROC) (GLuint first, GLsizei count, const GLuint *textures); +typedef void (APIENTRYP PFNGLBINDSAMPLERSPROC) (GLuint first, GLsizei count, const GLuint *samplers); +typedef void (APIENTRYP PFNGLBINDIMAGETEXTURESPROC) (GLuint first, GLsizei count, const GLuint *textures); +typedef void (APIENTRYP PFNGLBINDVERTEXBUFFERSPROC) (GLuint first, GLsizei count, const GLuint *buffers, const GLintptr *offsets, const GLsizei *strides); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBufferStorage (GLenum target, GLsizeiptr size, const void *data, GLbitfield flags); +GLAPI void APIENTRY glClearTexImage (GLuint texture, GLint level, GLenum format, GLenum type, const void *data); +GLAPI void APIENTRY glClearTexSubImage (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *data); +GLAPI void APIENTRY glBindBuffersBase (GLenum target, GLuint first, GLsizei count, const GLuint *buffers); +GLAPI void APIENTRY glBindBuffersRange (GLenum target, GLuint first, GLsizei count, const GLuint *buffers, const GLintptr *offsets, const GLsizeiptr *sizes); +GLAPI void APIENTRY glBindTextures (GLuint first, GLsizei count, const GLuint *textures); +GLAPI void APIENTRY glBindSamplers (GLuint first, GLsizei count, const GLuint *samplers); +GLAPI void APIENTRY glBindImageTextures (GLuint first, GLsizei count, const GLuint *textures); +GLAPI void APIENTRY glBindVertexBuffers (GLuint first, GLsizei count, const GLuint *buffers, const GLintptr *offsets, const GLsizei *strides); +#endif +#endif /* GL_VERSION_4_4 */ + +#ifndef GL_VERSION_4_5 +#define GL_VERSION_4_5 1 +#define GL_CONTEXT_LOST 0x0507 +#define GL_NEGATIVE_ONE_TO_ONE 0x935E +#define GL_ZERO_TO_ONE 0x935F +#define GL_CLIP_ORIGIN 0x935C +#define GL_CLIP_DEPTH_MODE 0x935D +#define GL_QUERY_WAIT_INVERTED 0x8E17 +#define GL_QUERY_NO_WAIT_INVERTED 0x8E18 +#define GL_QUERY_BY_REGION_WAIT_INVERTED 0x8E19 +#define GL_QUERY_BY_REGION_NO_WAIT_INVERTED 0x8E1A +#define GL_MAX_CULL_DISTANCES 0x82F9 +#define GL_MAX_COMBINED_CLIP_AND_CULL_DISTANCES 0x82FA +#define GL_TEXTURE_TARGET 0x1006 +#define GL_QUERY_TARGET 0x82EA +#define GL_GUILTY_CONTEXT_RESET 0x8253 +#define GL_INNOCENT_CONTEXT_RESET 0x8254 +#define GL_UNKNOWN_CONTEXT_RESET 0x8255 +#define GL_RESET_NOTIFICATION_STRATEGY 0x8256 +#define GL_LOSE_CONTEXT_ON_RESET 0x8252 +#define GL_NO_RESET_NOTIFICATION 0x8261 +#define GL_CONTEXT_FLAG_ROBUST_ACCESS_BIT 0x00000004 +#define GL_COLOR_TABLE 0x80D0 +#define GL_POST_CONVOLUTION_COLOR_TABLE 0x80D1 +#define GL_POST_COLOR_MATRIX_COLOR_TABLE 0x80D2 +#define GL_PROXY_COLOR_TABLE 0x80D3 +#define GL_PROXY_POST_CONVOLUTION_COLOR_TABLE 0x80D4 +#define GL_PROXY_POST_COLOR_MATRIX_COLOR_TABLE 0x80D5 +#define GL_CONVOLUTION_1D 0x8010 +#define GL_CONVOLUTION_2D 0x8011 +#define GL_SEPARABLE_2D 0x8012 +#define GL_HISTOGRAM 0x8024 +#define GL_PROXY_HISTOGRAM 0x8025 +#define GL_MINMAX 0x802E +#define GL_CONTEXT_RELEASE_BEHAVIOR 0x82FB +#define GL_CONTEXT_RELEASE_BEHAVIOR_FLUSH 0x82FC +typedef void (APIENTRYP PFNGLCLIPCONTROLPROC) (GLenum origin, GLenum depth); +typedef void (APIENTRYP PFNGLCREATETRANSFORMFEEDBACKSPROC) (GLsizei n, GLuint *ids); +typedef void (APIENTRYP PFNGLTRANSFORMFEEDBACKBUFFERBASEPROC) (GLuint xfb, GLuint index, GLuint buffer); +typedef void (APIENTRYP PFNGLTRANSFORMFEEDBACKBUFFERRANGEPROC) (GLuint xfb, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); +typedef void (APIENTRYP PFNGLGETTRANSFORMFEEDBACKIVPROC) (GLuint xfb, GLenum pname, GLint *param); +typedef void (APIENTRYP PFNGLGETTRANSFORMFEEDBACKI_VPROC) (GLuint xfb, GLenum pname, GLuint index, GLint *param); +typedef void (APIENTRYP PFNGLGETTRANSFORMFEEDBACKI64_VPROC) (GLuint xfb, GLenum pname, GLuint index, GLint64 *param); +typedef void (APIENTRYP PFNGLCREATEBUFFERSPROC) (GLsizei n, GLuint *buffers); +typedef void (APIENTRYP PFNGLNAMEDBUFFERSTORAGEPROC) (GLuint buffer, GLsizeiptr size, const void *data, GLbitfield flags); +typedef void (APIENTRYP PFNGLNAMEDBUFFERDATAPROC) (GLuint buffer, GLsizeiptr size, const void *data, GLenum usage); +typedef void (APIENTRYP PFNGLNAMEDBUFFERSUBDATAPROC) (GLuint buffer, GLintptr offset, GLsizeiptr size, const void *data); +typedef void (APIENTRYP PFNGLCOPYNAMEDBUFFERSUBDATAPROC) (GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); +typedef void (APIENTRYP PFNGLCLEARNAMEDBUFFERDATAPROC) (GLuint buffer, GLenum internalformat, GLenum format, GLenum type, const void *data); +typedef void (APIENTRYP PFNGLCLEARNAMEDBUFFERSUBDATAPROC) (GLuint buffer, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void *data); +typedef void *(APIENTRYP PFNGLMAPNAMEDBUFFERPROC) (GLuint buffer, GLenum access); +typedef void *(APIENTRYP PFNGLMAPNAMEDBUFFERRANGEPROC) (GLuint buffer, GLintptr offset, GLsizeiptr length, GLbitfield access); +typedef GLboolean (APIENTRYP PFNGLUNMAPNAMEDBUFFERPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLFLUSHMAPPEDNAMEDBUFFERRANGEPROC) (GLuint buffer, GLintptr offset, GLsizeiptr length); +typedef void (APIENTRYP PFNGLGETNAMEDBUFFERPARAMETERIVPROC) (GLuint buffer, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETNAMEDBUFFERPARAMETERI64VPROC) (GLuint buffer, GLenum pname, GLint64 *params); +typedef void (APIENTRYP PFNGLGETNAMEDBUFFERPOINTERVPROC) (GLuint buffer, GLenum pname, void **params); +typedef void (APIENTRYP PFNGLGETNAMEDBUFFERSUBDATAPROC) (GLuint buffer, GLintptr offset, GLsizeiptr size, void *data); +typedef void (APIENTRYP PFNGLCREATEFRAMEBUFFERSPROC) (GLsizei n, GLuint *framebuffers); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERRENDERBUFFERPROC) (GLuint framebuffer, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERPARAMETERIPROC) (GLuint framebuffer, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTUREPROC) (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTURELAYERPROC) (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLint layer); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERDRAWBUFFERPROC) (GLuint framebuffer, GLenum buf); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERDRAWBUFFERSPROC) (GLuint framebuffer, GLsizei n, const GLenum *bufs); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERREADBUFFERPROC) (GLuint framebuffer, GLenum src); +typedef void (APIENTRYP PFNGLINVALIDATENAMEDFRAMEBUFFERDATAPROC) (GLuint framebuffer, GLsizei numAttachments, const GLenum *attachments); +typedef void (APIENTRYP PFNGLINVALIDATENAMEDFRAMEBUFFERSUBDATAPROC) (GLuint framebuffer, GLsizei numAttachments, const GLenum *attachments, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLCLEARNAMEDFRAMEBUFFERIVPROC) (GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLint *value); +typedef void (APIENTRYP PFNGLCLEARNAMEDFRAMEBUFFERUIVPROC) (GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLuint *value); +typedef void (APIENTRYP PFNGLCLEARNAMEDFRAMEBUFFERFVPROC) (GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLfloat *value); +typedef void (APIENTRYP PFNGLCLEARNAMEDFRAMEBUFFERFIPROC) (GLuint framebuffer, GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil); +typedef void (APIENTRYP PFNGLBLITNAMEDFRAMEBUFFERPROC) (GLuint readFramebuffer, GLuint drawFramebuffer, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +typedef GLenum (APIENTRYP PFNGLCHECKNAMEDFRAMEBUFFERSTATUSPROC) (GLuint framebuffer, GLenum target); +typedef void (APIENTRYP PFNGLGETNAMEDFRAMEBUFFERPARAMETERIVPROC) (GLuint framebuffer, GLenum pname, GLint *param); +typedef void (APIENTRYP PFNGLGETNAMEDFRAMEBUFFERATTACHMENTPARAMETERIVPROC) (GLuint framebuffer, GLenum attachment, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLCREATERENDERBUFFERSPROC) (GLsizei n, GLuint *renderbuffers); +typedef void (APIENTRYP PFNGLNAMEDRENDERBUFFERSTORAGEPROC) (GLuint renderbuffer, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLNAMEDRENDERBUFFERSTORAGEMULTISAMPLEPROC) (GLuint renderbuffer, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETNAMEDRENDERBUFFERPARAMETERIVPROC) (GLuint renderbuffer, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLCREATETEXTURESPROC) (GLenum target, GLsizei n, GLuint *textures); +typedef void (APIENTRYP PFNGLTEXTUREBUFFERPROC) (GLuint texture, GLenum internalformat, GLuint buffer); +typedef void (APIENTRYP PFNGLTEXTUREBUFFERRANGEPROC) (GLuint texture, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size); +typedef void (APIENTRYP PFNGLTEXTURESTORAGE1DPROC) (GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width); +typedef void (APIENTRYP PFNGLTEXTURESTORAGE2DPROC) (GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLTEXTURESTORAGE3DPROC) (GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth); +typedef void (APIENTRYP PFNGLTEXTURESTORAGE2DMULTISAMPLEPROC) (GLuint texture, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations); +typedef void (APIENTRYP PFNGLTEXTURESTORAGE3DMULTISAMPLEPROC) (GLuint texture, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations); +typedef void (APIENTRYP PFNGLTEXTURESUBIMAGE1DPROC) (GLuint texture, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLTEXTURESUBIMAGE2DPROC) (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLTEXTURESUBIMAGE3DPROC) (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTURESUBIMAGE1DPROC) (GLuint texture, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTURESUBIMAGE2DPROC) (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTURESUBIMAGE3DPROC) (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *data); +typedef void (APIENTRYP PFNGLCOPYTEXTURESUBIMAGE1DPROC) (GLuint texture, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCOPYTEXTURESUBIMAGE2DPROC) (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLCOPYTEXTURESUBIMAGE3DPROC) (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLTEXTUREPARAMETERFPROC) (GLuint texture, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLTEXTUREPARAMETERFVPROC) (GLuint texture, GLenum pname, const GLfloat *param); +typedef void (APIENTRYP PFNGLTEXTUREPARAMETERIPROC) (GLuint texture, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLTEXTUREPARAMETERIIVPROC) (GLuint texture, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLTEXTUREPARAMETERIUIVPROC) (GLuint texture, GLenum pname, const GLuint *params); +typedef void (APIENTRYP PFNGLTEXTUREPARAMETERIVPROC) (GLuint texture, GLenum pname, const GLint *param); +typedef void (APIENTRYP PFNGLGENERATETEXTUREMIPMAPPROC) (GLuint texture); +typedef void (APIENTRYP PFNGLBINDTEXTUREUNITPROC) (GLuint unit, GLuint texture); +typedef void (APIENTRYP PFNGLGETTEXTUREIMAGEPROC) (GLuint texture, GLint level, GLenum format, GLenum type, GLsizei bufSize, void *pixels); +typedef void (APIENTRYP PFNGLGETCOMPRESSEDTEXTUREIMAGEPROC) (GLuint texture, GLint level, GLsizei bufSize, void *pixels); +typedef void (APIENTRYP PFNGLGETTEXTURELEVELPARAMETERFVPROC) (GLuint texture, GLint level, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETTEXTURELEVELPARAMETERIVPROC) (GLuint texture, GLint level, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETTEXTUREPARAMETERFVPROC) (GLuint texture, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETTEXTUREPARAMETERIIVPROC) (GLuint texture, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETTEXTUREPARAMETERIUIVPROC) (GLuint texture, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLGETTEXTUREPARAMETERIVPROC) (GLuint texture, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLCREATEVERTEXARRAYSPROC) (GLsizei n, GLuint *arrays); +typedef void (APIENTRYP PFNGLDISABLEVERTEXARRAYATTRIBPROC) (GLuint vaobj, GLuint index); +typedef void (APIENTRYP PFNGLENABLEVERTEXARRAYATTRIBPROC) (GLuint vaobj, GLuint index); +typedef void (APIENTRYP PFNGLVERTEXARRAYELEMENTBUFFERPROC) (GLuint vaobj, GLuint buffer); +typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXBUFFERPROC) (GLuint vaobj, GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride); +typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXBUFFERSPROC) (GLuint vaobj, GLuint first, GLsizei count, const GLuint *buffers, const GLintptr *offsets, const GLsizei *strides); +typedef void (APIENTRYP PFNGLVERTEXARRAYATTRIBBINDINGPROC) (GLuint vaobj, GLuint attribindex, GLuint bindingindex); +typedef void (APIENTRYP PFNGLVERTEXARRAYATTRIBFORMATPROC) (GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset); +typedef void (APIENTRYP PFNGLVERTEXARRAYATTRIBIFORMATPROC) (GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); +typedef void (APIENTRYP PFNGLVERTEXARRAYATTRIBLFORMATPROC) (GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); +typedef void (APIENTRYP PFNGLVERTEXARRAYBINDINGDIVISORPROC) (GLuint vaobj, GLuint bindingindex, GLuint divisor); +typedef void (APIENTRYP PFNGLGETVERTEXARRAYIVPROC) (GLuint vaobj, GLenum pname, GLint *param); +typedef void (APIENTRYP PFNGLGETVERTEXARRAYINDEXEDIVPROC) (GLuint vaobj, GLuint index, GLenum pname, GLint *param); +typedef void (APIENTRYP PFNGLGETVERTEXARRAYINDEXED64IVPROC) (GLuint vaobj, GLuint index, GLenum pname, GLint64 *param); +typedef void (APIENTRYP PFNGLCREATESAMPLERSPROC) (GLsizei n, GLuint *samplers); +typedef void (APIENTRYP PFNGLCREATEPROGRAMPIPELINESPROC) (GLsizei n, GLuint *pipelines); +typedef void (APIENTRYP PFNGLCREATEQUERIESPROC) (GLenum target, GLsizei n, GLuint *ids); +typedef void (APIENTRYP PFNGLGETQUERYBUFFEROBJECTI64VPROC) (GLuint id, GLuint buffer, GLenum pname, GLintptr offset); +typedef void (APIENTRYP PFNGLGETQUERYBUFFEROBJECTIVPROC) (GLuint id, GLuint buffer, GLenum pname, GLintptr offset); +typedef void (APIENTRYP PFNGLGETQUERYBUFFEROBJECTUI64VPROC) (GLuint id, GLuint buffer, GLenum pname, GLintptr offset); +typedef void (APIENTRYP PFNGLGETQUERYBUFFEROBJECTUIVPROC) (GLuint id, GLuint buffer, GLenum pname, GLintptr offset); +typedef void (APIENTRYP PFNGLMEMORYBARRIERBYREGIONPROC) (GLbitfield barriers); +typedef void (APIENTRYP PFNGLGETTEXTURESUBIMAGEPROC) (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, GLsizei bufSize, void *pixels); +typedef void (APIENTRYP PFNGLGETCOMPRESSEDTEXTURESUBIMAGEPROC) (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLsizei bufSize, void *pixels); +typedef GLenum (APIENTRYP PFNGLGETGRAPHICSRESETSTATUSPROC) (void); +typedef void (APIENTRYP PFNGLGETNCOMPRESSEDTEXIMAGEPROC) (GLenum target, GLint lod, GLsizei bufSize, void *pixels); +typedef void (APIENTRYP PFNGLGETNTEXIMAGEPROC) (GLenum target, GLint level, GLenum format, GLenum type, GLsizei bufSize, void *pixels); +typedef void (APIENTRYP PFNGLGETNUNIFORMDVPROC) (GLuint program, GLint location, GLsizei bufSize, GLdouble *params); +typedef void (APIENTRYP PFNGLGETNUNIFORMFVPROC) (GLuint program, GLint location, GLsizei bufSize, GLfloat *params); +typedef void (APIENTRYP PFNGLGETNUNIFORMIVPROC) (GLuint program, GLint location, GLsizei bufSize, GLint *params); +typedef void (APIENTRYP PFNGLGETNUNIFORMUIVPROC) (GLuint program, GLint location, GLsizei bufSize, GLuint *params); +typedef void (APIENTRYP PFNGLREADNPIXELSPROC) (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei bufSize, void *data); +typedef void (APIENTRYP PFNGLGETNMAPDVPROC) (GLenum target, GLenum query, GLsizei bufSize, GLdouble *v); +typedef void (APIENTRYP PFNGLGETNMAPFVPROC) (GLenum target, GLenum query, GLsizei bufSize, GLfloat *v); +typedef void (APIENTRYP PFNGLGETNMAPIVPROC) (GLenum target, GLenum query, GLsizei bufSize, GLint *v); +typedef void (APIENTRYP PFNGLGETNPIXELMAPFVPROC) (GLenum map, GLsizei bufSize, GLfloat *values); +typedef void (APIENTRYP PFNGLGETNPIXELMAPUIVPROC) (GLenum map, GLsizei bufSize, GLuint *values); +typedef void (APIENTRYP PFNGLGETNPIXELMAPUSVPROC) (GLenum map, GLsizei bufSize, GLushort *values); +typedef void (APIENTRYP PFNGLGETNPOLYGONSTIPPLEPROC) (GLsizei bufSize, GLubyte *pattern); +typedef void (APIENTRYP PFNGLGETNCOLORTABLEPROC) (GLenum target, GLenum format, GLenum type, GLsizei bufSize, void *table); +typedef void (APIENTRYP PFNGLGETNCONVOLUTIONFILTERPROC) (GLenum target, GLenum format, GLenum type, GLsizei bufSize, void *image); +typedef void (APIENTRYP PFNGLGETNSEPARABLEFILTERPROC) (GLenum target, GLenum format, GLenum type, GLsizei rowBufSize, void *row, GLsizei columnBufSize, void *column, void *span); +typedef void (APIENTRYP PFNGLGETNHISTOGRAMPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLsizei bufSize, void *values); +typedef void (APIENTRYP PFNGLGETNMINMAXPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLsizei bufSize, void *values); +typedef void (APIENTRYP PFNGLTEXTUREBARRIERPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glClipControl (GLenum origin, GLenum depth); +GLAPI void APIENTRY glCreateTransformFeedbacks (GLsizei n, GLuint *ids); +GLAPI void APIENTRY glTransformFeedbackBufferBase (GLuint xfb, GLuint index, GLuint buffer); +GLAPI void APIENTRY glTransformFeedbackBufferRange (GLuint xfb, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); +GLAPI void APIENTRY glGetTransformFeedbackiv (GLuint xfb, GLenum pname, GLint *param); +GLAPI void APIENTRY glGetTransformFeedbacki_v (GLuint xfb, GLenum pname, GLuint index, GLint *param); +GLAPI void APIENTRY glGetTransformFeedbacki64_v (GLuint xfb, GLenum pname, GLuint index, GLint64 *param); +GLAPI void APIENTRY glCreateBuffers (GLsizei n, GLuint *buffers); +GLAPI void APIENTRY glNamedBufferStorage (GLuint buffer, GLsizeiptr size, const void *data, GLbitfield flags); +GLAPI void APIENTRY glNamedBufferData (GLuint buffer, GLsizeiptr size, const void *data, GLenum usage); +GLAPI void APIENTRY glNamedBufferSubData (GLuint buffer, GLintptr offset, GLsizeiptr size, const void *data); +GLAPI void APIENTRY glCopyNamedBufferSubData (GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); +GLAPI void APIENTRY glClearNamedBufferData (GLuint buffer, GLenum internalformat, GLenum format, GLenum type, const void *data); +GLAPI void APIENTRY glClearNamedBufferSubData (GLuint buffer, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void *data); +GLAPI void *APIENTRY glMapNamedBuffer (GLuint buffer, GLenum access); +GLAPI void *APIENTRY glMapNamedBufferRange (GLuint buffer, GLintptr offset, GLsizeiptr length, GLbitfield access); +GLAPI GLboolean APIENTRY glUnmapNamedBuffer (GLuint buffer); +GLAPI void APIENTRY glFlushMappedNamedBufferRange (GLuint buffer, GLintptr offset, GLsizeiptr length); +GLAPI void APIENTRY glGetNamedBufferParameteriv (GLuint buffer, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetNamedBufferParameteri64v (GLuint buffer, GLenum pname, GLint64 *params); +GLAPI void APIENTRY glGetNamedBufferPointerv (GLuint buffer, GLenum pname, void **params); +GLAPI void APIENTRY glGetNamedBufferSubData (GLuint buffer, GLintptr offset, GLsizeiptr size, void *data); +GLAPI void APIENTRY glCreateFramebuffers (GLsizei n, GLuint *framebuffers); +GLAPI void APIENTRY glNamedFramebufferRenderbuffer (GLuint framebuffer, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +GLAPI void APIENTRY glNamedFramebufferParameteri (GLuint framebuffer, GLenum pname, GLint param); +GLAPI void APIENTRY glNamedFramebufferTexture (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level); +GLAPI void APIENTRY glNamedFramebufferTextureLayer (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLint layer); +GLAPI void APIENTRY glNamedFramebufferDrawBuffer (GLuint framebuffer, GLenum buf); +GLAPI void APIENTRY glNamedFramebufferDrawBuffers (GLuint framebuffer, GLsizei n, const GLenum *bufs); +GLAPI void APIENTRY glNamedFramebufferReadBuffer (GLuint framebuffer, GLenum src); +GLAPI void APIENTRY glInvalidateNamedFramebufferData (GLuint framebuffer, GLsizei numAttachments, const GLenum *attachments); +GLAPI void APIENTRY glInvalidateNamedFramebufferSubData (GLuint framebuffer, GLsizei numAttachments, const GLenum *attachments, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glClearNamedFramebufferiv (GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLint *value); +GLAPI void APIENTRY glClearNamedFramebufferuiv (GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLuint *value); +GLAPI void APIENTRY glClearNamedFramebufferfv (GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLfloat *value); +GLAPI void APIENTRY glClearNamedFramebufferfi (GLuint framebuffer, GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil); +GLAPI void APIENTRY glBlitNamedFramebuffer (GLuint readFramebuffer, GLuint drawFramebuffer, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +GLAPI GLenum APIENTRY glCheckNamedFramebufferStatus (GLuint framebuffer, GLenum target); +GLAPI void APIENTRY glGetNamedFramebufferParameteriv (GLuint framebuffer, GLenum pname, GLint *param); +GLAPI void APIENTRY glGetNamedFramebufferAttachmentParameteriv (GLuint framebuffer, GLenum attachment, GLenum pname, GLint *params); +GLAPI void APIENTRY glCreateRenderbuffers (GLsizei n, GLuint *renderbuffers); +GLAPI void APIENTRY glNamedRenderbufferStorage (GLuint renderbuffer, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glNamedRenderbufferStorageMultisample (GLuint renderbuffer, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glGetNamedRenderbufferParameteriv (GLuint renderbuffer, GLenum pname, GLint *params); +GLAPI void APIENTRY glCreateTextures (GLenum target, GLsizei n, GLuint *textures); +GLAPI void APIENTRY glTextureBuffer (GLuint texture, GLenum internalformat, GLuint buffer); +GLAPI void APIENTRY glTextureBufferRange (GLuint texture, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size); +GLAPI void APIENTRY glTextureStorage1D (GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width); +GLAPI void APIENTRY glTextureStorage2D (GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glTextureStorage3D (GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth); +GLAPI void APIENTRY glTextureStorage2DMultisample (GLuint texture, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations); +GLAPI void APIENTRY glTextureStorage3DMultisample (GLuint texture, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations); +GLAPI void APIENTRY glTextureSubImage1D (GLuint texture, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glTextureSubImage2D (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glTextureSubImage3D (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glCompressedTextureSubImage1D (GLuint texture, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *data); +GLAPI void APIENTRY glCompressedTextureSubImage2D (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data); +GLAPI void APIENTRY glCompressedTextureSubImage3D (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *data); +GLAPI void APIENTRY glCopyTextureSubImage1D (GLuint texture, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +GLAPI void APIENTRY glCopyTextureSubImage2D (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glCopyTextureSubImage3D (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glTextureParameterf (GLuint texture, GLenum pname, GLfloat param); +GLAPI void APIENTRY glTextureParameterfv (GLuint texture, GLenum pname, const GLfloat *param); +GLAPI void APIENTRY glTextureParameteri (GLuint texture, GLenum pname, GLint param); +GLAPI void APIENTRY glTextureParameterIiv (GLuint texture, GLenum pname, const GLint *params); +GLAPI void APIENTRY glTextureParameterIuiv (GLuint texture, GLenum pname, const GLuint *params); +GLAPI void APIENTRY glTextureParameteriv (GLuint texture, GLenum pname, const GLint *param); +GLAPI void APIENTRY glGenerateTextureMipmap (GLuint texture); +GLAPI void APIENTRY glBindTextureUnit (GLuint unit, GLuint texture); +GLAPI void APIENTRY glGetTextureImage (GLuint texture, GLint level, GLenum format, GLenum type, GLsizei bufSize, void *pixels); +GLAPI void APIENTRY glGetCompressedTextureImage (GLuint texture, GLint level, GLsizei bufSize, void *pixels); +GLAPI void APIENTRY glGetTextureLevelParameterfv (GLuint texture, GLint level, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetTextureLevelParameteriv (GLuint texture, GLint level, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetTextureParameterfv (GLuint texture, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetTextureParameterIiv (GLuint texture, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetTextureParameterIuiv (GLuint texture, GLenum pname, GLuint *params); +GLAPI void APIENTRY glGetTextureParameteriv (GLuint texture, GLenum pname, GLint *params); +GLAPI void APIENTRY glCreateVertexArrays (GLsizei n, GLuint *arrays); +GLAPI void APIENTRY glDisableVertexArrayAttrib (GLuint vaobj, GLuint index); +GLAPI void APIENTRY glEnableVertexArrayAttrib (GLuint vaobj, GLuint index); +GLAPI void APIENTRY glVertexArrayElementBuffer (GLuint vaobj, GLuint buffer); +GLAPI void APIENTRY glVertexArrayVertexBuffer (GLuint vaobj, GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride); +GLAPI void APIENTRY glVertexArrayVertexBuffers (GLuint vaobj, GLuint first, GLsizei count, const GLuint *buffers, const GLintptr *offsets, const GLsizei *strides); +GLAPI void APIENTRY glVertexArrayAttribBinding (GLuint vaobj, GLuint attribindex, GLuint bindingindex); +GLAPI void APIENTRY glVertexArrayAttribFormat (GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset); +GLAPI void APIENTRY glVertexArrayAttribIFormat (GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); +GLAPI void APIENTRY glVertexArrayAttribLFormat (GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); +GLAPI void APIENTRY glVertexArrayBindingDivisor (GLuint vaobj, GLuint bindingindex, GLuint divisor); +GLAPI void APIENTRY glGetVertexArrayiv (GLuint vaobj, GLenum pname, GLint *param); +GLAPI void APIENTRY glGetVertexArrayIndexediv (GLuint vaobj, GLuint index, GLenum pname, GLint *param); +GLAPI void APIENTRY glGetVertexArrayIndexed64iv (GLuint vaobj, GLuint index, GLenum pname, GLint64 *param); +GLAPI void APIENTRY glCreateSamplers (GLsizei n, GLuint *samplers); +GLAPI void APIENTRY glCreateProgramPipelines (GLsizei n, GLuint *pipelines); +GLAPI void APIENTRY glCreateQueries (GLenum target, GLsizei n, GLuint *ids); +GLAPI void APIENTRY glGetQueryBufferObjecti64v (GLuint id, GLuint buffer, GLenum pname, GLintptr offset); +GLAPI void APIENTRY glGetQueryBufferObjectiv (GLuint id, GLuint buffer, GLenum pname, GLintptr offset); +GLAPI void APIENTRY glGetQueryBufferObjectui64v (GLuint id, GLuint buffer, GLenum pname, GLintptr offset); +GLAPI void APIENTRY glGetQueryBufferObjectuiv (GLuint id, GLuint buffer, GLenum pname, GLintptr offset); +GLAPI void APIENTRY glMemoryBarrierByRegion (GLbitfield barriers); +GLAPI void APIENTRY glGetTextureSubImage (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, GLsizei bufSize, void *pixels); +GLAPI void APIENTRY glGetCompressedTextureSubImage (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLsizei bufSize, void *pixels); +GLAPI GLenum APIENTRY glGetGraphicsResetStatus (void); +GLAPI void APIENTRY glGetnCompressedTexImage (GLenum target, GLint lod, GLsizei bufSize, void *pixels); +GLAPI void APIENTRY glGetnTexImage (GLenum target, GLint level, GLenum format, GLenum type, GLsizei bufSize, void *pixels); +GLAPI void APIENTRY glGetnUniformdv (GLuint program, GLint location, GLsizei bufSize, GLdouble *params); +GLAPI void APIENTRY glGetnUniformfv (GLuint program, GLint location, GLsizei bufSize, GLfloat *params); +GLAPI void APIENTRY glGetnUniformiv (GLuint program, GLint location, GLsizei bufSize, GLint *params); +GLAPI void APIENTRY glGetnUniformuiv (GLuint program, GLint location, GLsizei bufSize, GLuint *params); +GLAPI void APIENTRY glReadnPixels (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei bufSize, void *data); +GLAPI void APIENTRY glGetnMapdv (GLenum target, GLenum query, GLsizei bufSize, GLdouble *v); +GLAPI void APIENTRY glGetnMapfv (GLenum target, GLenum query, GLsizei bufSize, GLfloat *v); +GLAPI void APIENTRY glGetnMapiv (GLenum target, GLenum query, GLsizei bufSize, GLint *v); +GLAPI void APIENTRY glGetnPixelMapfv (GLenum map, GLsizei bufSize, GLfloat *values); +GLAPI void APIENTRY glGetnPixelMapuiv (GLenum map, GLsizei bufSize, GLuint *values); +GLAPI void APIENTRY glGetnPixelMapusv (GLenum map, GLsizei bufSize, GLushort *values); +GLAPI void APIENTRY glGetnPolygonStipple (GLsizei bufSize, GLubyte *pattern); +GLAPI void APIENTRY glGetnColorTable (GLenum target, GLenum format, GLenum type, GLsizei bufSize, void *table); +GLAPI void APIENTRY glGetnConvolutionFilter (GLenum target, GLenum format, GLenum type, GLsizei bufSize, void *image); +GLAPI void APIENTRY glGetnSeparableFilter (GLenum target, GLenum format, GLenum type, GLsizei rowBufSize, void *row, GLsizei columnBufSize, void *column, void *span); +GLAPI void APIENTRY glGetnHistogram (GLenum target, GLboolean reset, GLenum format, GLenum type, GLsizei bufSize, void *values); +GLAPI void APIENTRY glGetnMinmax (GLenum target, GLboolean reset, GLenum format, GLenum type, GLsizei bufSize, void *values); +GLAPI void APIENTRY glTextureBarrier (void); +#endif +#endif /* GL_VERSION_4_5 */ + +#ifndef GL_VERSION_4_6 +#define GL_VERSION_4_6 1 +#define GL_SHADER_BINARY_FORMAT_SPIR_V 0x9551 +#define GL_SPIR_V_BINARY 0x9552 +#define GL_PARAMETER_BUFFER 0x80EE +#define GL_PARAMETER_BUFFER_BINDING 0x80EF +#define GL_CONTEXT_FLAG_NO_ERROR_BIT 0x00000008 +#define GL_VERTICES_SUBMITTED 0x82EE +#define GL_PRIMITIVES_SUBMITTED 0x82EF +#define GL_VERTEX_SHADER_INVOCATIONS 0x82F0 +#define GL_TESS_CONTROL_SHADER_PATCHES 0x82F1 +#define GL_TESS_EVALUATION_SHADER_INVOCATIONS 0x82F2 +#define GL_GEOMETRY_SHADER_PRIMITIVES_EMITTED 0x82F3 +#define GL_FRAGMENT_SHADER_INVOCATIONS 0x82F4 +#define GL_COMPUTE_SHADER_INVOCATIONS 0x82F5 +#define GL_CLIPPING_INPUT_PRIMITIVES 0x82F6 +#define GL_CLIPPING_OUTPUT_PRIMITIVES 0x82F7 +#define GL_POLYGON_OFFSET_CLAMP 0x8E1B +#define GL_SPIR_V_EXTENSIONS 0x9553 +#define GL_NUM_SPIR_V_EXTENSIONS 0x9554 +#define GL_TEXTURE_MAX_ANISOTROPY 0x84FE +#define GL_MAX_TEXTURE_MAX_ANISOTROPY 0x84FF +#define GL_TRANSFORM_FEEDBACK_OVERFLOW 0x82EC +#define GL_TRANSFORM_FEEDBACK_STREAM_OVERFLOW 0x82ED +typedef void (APIENTRYP PFNGLSPECIALIZESHADERPROC) (GLuint shader, const GLchar *pEntryPoint, GLuint numSpecializationConstants, const GLuint *pConstantIndex, const GLuint *pConstantValue); +typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSINDIRECTCOUNTPROC) (GLenum mode, const void *indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSINDIRECTCOUNTPROC) (GLenum mode, GLenum type, const void *indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride); +typedef void (APIENTRYP PFNGLPOLYGONOFFSETCLAMPPROC) (GLfloat factor, GLfloat units, GLfloat clamp); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSpecializeShader (GLuint shader, const GLchar *pEntryPoint, GLuint numSpecializationConstants, const GLuint *pConstantIndex, const GLuint *pConstantValue); +GLAPI void APIENTRY glMultiDrawArraysIndirectCount (GLenum mode, const void *indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride); +GLAPI void APIENTRY glMultiDrawElementsIndirectCount (GLenum mode, GLenum type, const void *indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride); +GLAPI void APIENTRY glPolygonOffsetClamp (GLfloat factor, GLfloat units, GLfloat clamp); +#endif +#endif /* GL_VERSION_4_6 */ + +#ifndef GL_ARB_ES2_compatibility +#define GL_ARB_ES2_compatibility 1 +#endif /* GL_ARB_ES2_compatibility */ + +#ifndef GL_ARB_ES3_1_compatibility +#define GL_ARB_ES3_1_compatibility 1 +#endif /* GL_ARB_ES3_1_compatibility */ + +#ifndef GL_ARB_ES3_2_compatibility +#define GL_ARB_ES3_2_compatibility 1 +#define GL_PRIMITIVE_BOUNDING_BOX_ARB 0x92BE +#define GL_MULTISAMPLE_LINE_WIDTH_RANGE_ARB 0x9381 +#define GL_MULTISAMPLE_LINE_WIDTH_GRANULARITY_ARB 0x9382 +typedef void (APIENTRYP PFNGLPRIMITIVEBOUNDINGBOXARBPROC) (GLfloat minX, GLfloat minY, GLfloat minZ, GLfloat minW, GLfloat maxX, GLfloat maxY, GLfloat maxZ, GLfloat maxW); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPrimitiveBoundingBoxARB (GLfloat minX, GLfloat minY, GLfloat minZ, GLfloat minW, GLfloat maxX, GLfloat maxY, GLfloat maxZ, GLfloat maxW); +#endif +#endif /* GL_ARB_ES3_2_compatibility */ + +#ifndef GL_ARB_ES3_compatibility +#define GL_ARB_ES3_compatibility 1 +#endif /* GL_ARB_ES3_compatibility */ + +#ifndef GL_ARB_arrays_of_arrays +#define GL_ARB_arrays_of_arrays 1 +#endif /* GL_ARB_arrays_of_arrays */ + +#ifndef GL_ARB_base_instance +#define GL_ARB_base_instance 1 +#endif /* GL_ARB_base_instance */ + +#ifndef GL_ARB_bindless_texture +#define GL_ARB_bindless_texture 1 +typedef khronos_uint64_t GLuint64EXT; +#define GL_UNSIGNED_INT64_ARB 0x140F +typedef GLuint64 (APIENTRYP PFNGLGETTEXTUREHANDLEARBPROC) (GLuint texture); +typedef GLuint64 (APIENTRYP PFNGLGETTEXTURESAMPLERHANDLEARBPROC) (GLuint texture, GLuint sampler); +typedef void (APIENTRYP PFNGLMAKETEXTUREHANDLERESIDENTARBPROC) (GLuint64 handle); +typedef void (APIENTRYP PFNGLMAKETEXTUREHANDLENONRESIDENTARBPROC) (GLuint64 handle); +typedef GLuint64 (APIENTRYP PFNGLGETIMAGEHANDLEARBPROC) (GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum format); +typedef void (APIENTRYP PFNGLMAKEIMAGEHANDLERESIDENTARBPROC) (GLuint64 handle, GLenum access); +typedef void (APIENTRYP PFNGLMAKEIMAGEHANDLENONRESIDENTARBPROC) (GLuint64 handle); +typedef void (APIENTRYP PFNGLUNIFORMHANDLEUI64ARBPROC) (GLint location, GLuint64 value); +typedef void (APIENTRYP PFNGLUNIFORMHANDLEUI64VARBPROC) (GLint location, GLsizei count, const GLuint64 *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMHANDLEUI64ARBPROC) (GLuint program, GLint location, GLuint64 value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMHANDLEUI64VARBPROC) (GLuint program, GLint location, GLsizei count, const GLuint64 *values); +typedef GLboolean (APIENTRYP PFNGLISTEXTUREHANDLERESIDENTARBPROC) (GLuint64 handle); +typedef GLboolean (APIENTRYP PFNGLISIMAGEHANDLERESIDENTARBPROC) (GLuint64 handle); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1UI64ARBPROC) (GLuint index, GLuint64EXT x); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1UI64VARBPROC) (GLuint index, const GLuint64EXT *v); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBLUI64VARBPROC) (GLuint index, GLenum pname, GLuint64EXT *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLuint64 APIENTRY glGetTextureHandleARB (GLuint texture); +GLAPI GLuint64 APIENTRY glGetTextureSamplerHandleARB (GLuint texture, GLuint sampler); +GLAPI void APIENTRY glMakeTextureHandleResidentARB (GLuint64 handle); +GLAPI void APIENTRY glMakeTextureHandleNonResidentARB (GLuint64 handle); +GLAPI GLuint64 APIENTRY glGetImageHandleARB (GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum format); +GLAPI void APIENTRY glMakeImageHandleResidentARB (GLuint64 handle, GLenum access); +GLAPI void APIENTRY glMakeImageHandleNonResidentARB (GLuint64 handle); +GLAPI void APIENTRY glUniformHandleui64ARB (GLint location, GLuint64 value); +GLAPI void APIENTRY glUniformHandleui64vARB (GLint location, GLsizei count, const GLuint64 *value); +GLAPI void APIENTRY glProgramUniformHandleui64ARB (GLuint program, GLint location, GLuint64 value); +GLAPI void APIENTRY glProgramUniformHandleui64vARB (GLuint program, GLint location, GLsizei count, const GLuint64 *values); +GLAPI GLboolean APIENTRY glIsTextureHandleResidentARB (GLuint64 handle); +GLAPI GLboolean APIENTRY glIsImageHandleResidentARB (GLuint64 handle); +GLAPI void APIENTRY glVertexAttribL1ui64ARB (GLuint index, GLuint64EXT x); +GLAPI void APIENTRY glVertexAttribL1ui64vARB (GLuint index, const GLuint64EXT *v); +GLAPI void APIENTRY glGetVertexAttribLui64vARB (GLuint index, GLenum pname, GLuint64EXT *params); +#endif +#endif /* GL_ARB_bindless_texture */ + +#ifndef GL_ARB_blend_func_extended +#define GL_ARB_blend_func_extended 1 +#endif /* GL_ARB_blend_func_extended */ + +#ifndef GL_ARB_buffer_storage +#define GL_ARB_buffer_storage 1 +#endif /* GL_ARB_buffer_storage */ + +#ifndef GL_ARB_cl_event +#define GL_ARB_cl_event 1 +struct _cl_context; +struct _cl_event; +#define GL_SYNC_CL_EVENT_ARB 0x8240 +#define GL_SYNC_CL_EVENT_COMPLETE_ARB 0x8241 +typedef GLsync (APIENTRYP PFNGLCREATESYNCFROMCLEVENTARBPROC) (struct _cl_context *context, struct _cl_event *event, GLbitfield flags); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLsync APIENTRY glCreateSyncFromCLeventARB (struct _cl_context *context, struct _cl_event *event, GLbitfield flags); +#endif +#endif /* GL_ARB_cl_event */ + +#ifndef GL_ARB_clear_buffer_object +#define GL_ARB_clear_buffer_object 1 +#endif /* GL_ARB_clear_buffer_object */ + +#ifndef GL_ARB_clear_texture +#define GL_ARB_clear_texture 1 +#endif /* GL_ARB_clear_texture */ + +#ifndef GL_ARB_clip_control +#define GL_ARB_clip_control 1 +#endif /* GL_ARB_clip_control */ + +#ifndef GL_ARB_color_buffer_float +#define GL_ARB_color_buffer_float 1 +#define GL_RGBA_FLOAT_MODE_ARB 0x8820 +#define GL_CLAMP_VERTEX_COLOR_ARB 0x891A +#define GL_CLAMP_FRAGMENT_COLOR_ARB 0x891B +#define GL_CLAMP_READ_COLOR_ARB 0x891C +#define GL_FIXED_ONLY_ARB 0x891D +typedef void (APIENTRYP PFNGLCLAMPCOLORARBPROC) (GLenum target, GLenum clamp); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glClampColorARB (GLenum target, GLenum clamp); +#endif +#endif /* GL_ARB_color_buffer_float */ + +#ifndef GL_ARB_compatibility +#define GL_ARB_compatibility 1 +#endif /* GL_ARB_compatibility */ + +#ifndef GL_ARB_compressed_texture_pixel_storage +#define GL_ARB_compressed_texture_pixel_storage 1 +#endif /* GL_ARB_compressed_texture_pixel_storage */ + +#ifndef GL_ARB_compute_shader +#define GL_ARB_compute_shader 1 +#endif /* GL_ARB_compute_shader */ + +#ifndef GL_ARB_compute_variable_group_size +#define GL_ARB_compute_variable_group_size 1 +#define GL_MAX_COMPUTE_VARIABLE_GROUP_INVOCATIONS_ARB 0x9344 +#define GL_MAX_COMPUTE_FIXED_GROUP_INVOCATIONS_ARB 0x90EB +#define GL_MAX_COMPUTE_VARIABLE_GROUP_SIZE_ARB 0x9345 +#define GL_MAX_COMPUTE_FIXED_GROUP_SIZE_ARB 0x91BF +typedef void (APIENTRYP PFNGLDISPATCHCOMPUTEGROUPSIZEARBPROC) (GLuint num_groups_x, GLuint num_groups_y, GLuint num_groups_z, GLuint group_size_x, GLuint group_size_y, GLuint group_size_z); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDispatchComputeGroupSizeARB (GLuint num_groups_x, GLuint num_groups_y, GLuint num_groups_z, GLuint group_size_x, GLuint group_size_y, GLuint group_size_z); +#endif +#endif /* GL_ARB_compute_variable_group_size */ + +#ifndef GL_ARB_conditional_render_inverted +#define GL_ARB_conditional_render_inverted 1 +#endif /* GL_ARB_conditional_render_inverted */ + +#ifndef GL_ARB_conservative_depth +#define GL_ARB_conservative_depth 1 +#endif /* GL_ARB_conservative_depth */ + +#ifndef GL_ARB_copy_buffer +#define GL_ARB_copy_buffer 1 +#endif /* GL_ARB_copy_buffer */ + +#ifndef GL_ARB_copy_image +#define GL_ARB_copy_image 1 +#endif /* GL_ARB_copy_image */ + +#ifndef GL_ARB_cull_distance +#define GL_ARB_cull_distance 1 +#endif /* GL_ARB_cull_distance */ + +#ifndef GL_ARB_debug_output +#define GL_ARB_debug_output 1 +typedef void (APIENTRY *GLDEBUGPROCARB)(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,const void *userParam); +#define GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB 0x8242 +#define GL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH_ARB 0x8243 +#define GL_DEBUG_CALLBACK_FUNCTION_ARB 0x8244 +#define GL_DEBUG_CALLBACK_USER_PARAM_ARB 0x8245 +#define GL_DEBUG_SOURCE_API_ARB 0x8246 +#define GL_DEBUG_SOURCE_WINDOW_SYSTEM_ARB 0x8247 +#define GL_DEBUG_SOURCE_SHADER_COMPILER_ARB 0x8248 +#define GL_DEBUG_SOURCE_THIRD_PARTY_ARB 0x8249 +#define GL_DEBUG_SOURCE_APPLICATION_ARB 0x824A +#define GL_DEBUG_SOURCE_OTHER_ARB 0x824B +#define GL_DEBUG_TYPE_ERROR_ARB 0x824C +#define GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB 0x824D +#define GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB 0x824E +#define GL_DEBUG_TYPE_PORTABILITY_ARB 0x824F +#define GL_DEBUG_TYPE_PERFORMANCE_ARB 0x8250 +#define GL_DEBUG_TYPE_OTHER_ARB 0x8251 +#define GL_MAX_DEBUG_MESSAGE_LENGTH_ARB 0x9143 +#define GL_MAX_DEBUG_LOGGED_MESSAGES_ARB 0x9144 +#define GL_DEBUG_LOGGED_MESSAGES_ARB 0x9145 +#define GL_DEBUG_SEVERITY_HIGH_ARB 0x9146 +#define GL_DEBUG_SEVERITY_MEDIUM_ARB 0x9147 +#define GL_DEBUG_SEVERITY_LOW_ARB 0x9148 +typedef void (APIENTRYP PFNGLDEBUGMESSAGECONTROLARBPROC) (GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled); +typedef void (APIENTRYP PFNGLDEBUGMESSAGEINSERTARBPROC) (GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *buf); +typedef void (APIENTRYP PFNGLDEBUGMESSAGECALLBACKARBPROC) (GLDEBUGPROCARB callback, const void *userParam); +typedef GLuint (APIENTRYP PFNGLGETDEBUGMESSAGELOGARBPROC) (GLuint count, GLsizei bufSize, GLenum *sources, GLenum *types, GLuint *ids, GLenum *severities, GLsizei *lengths, GLchar *messageLog); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDebugMessageControlARB (GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled); +GLAPI void APIENTRY glDebugMessageInsertARB (GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *buf); +GLAPI void APIENTRY glDebugMessageCallbackARB (GLDEBUGPROCARB callback, const void *userParam); +GLAPI GLuint APIENTRY glGetDebugMessageLogARB (GLuint count, GLsizei bufSize, GLenum *sources, GLenum *types, GLuint *ids, GLenum *severities, GLsizei *lengths, GLchar *messageLog); +#endif +#endif /* GL_ARB_debug_output */ + +#ifndef GL_ARB_depth_buffer_float +#define GL_ARB_depth_buffer_float 1 +#endif /* GL_ARB_depth_buffer_float */ + +#ifndef GL_ARB_depth_clamp +#define GL_ARB_depth_clamp 1 +#endif /* GL_ARB_depth_clamp */ + +#ifndef GL_ARB_depth_texture +#define GL_ARB_depth_texture 1 +#define GL_DEPTH_COMPONENT16_ARB 0x81A5 +#define GL_DEPTH_COMPONENT24_ARB 0x81A6 +#define GL_DEPTH_COMPONENT32_ARB 0x81A7 +#define GL_TEXTURE_DEPTH_SIZE_ARB 0x884A +#define GL_DEPTH_TEXTURE_MODE_ARB 0x884B +#endif /* GL_ARB_depth_texture */ + +#ifndef GL_ARB_derivative_control +#define GL_ARB_derivative_control 1 +#endif /* GL_ARB_derivative_control */ + +#ifndef GL_ARB_direct_state_access +#define GL_ARB_direct_state_access 1 +#endif /* GL_ARB_direct_state_access */ + +#ifndef GL_ARB_draw_buffers +#define GL_ARB_draw_buffers 1 +#define GL_MAX_DRAW_BUFFERS_ARB 0x8824 +#define GL_DRAW_BUFFER0_ARB 0x8825 +#define GL_DRAW_BUFFER1_ARB 0x8826 +#define GL_DRAW_BUFFER2_ARB 0x8827 +#define GL_DRAW_BUFFER3_ARB 0x8828 +#define GL_DRAW_BUFFER4_ARB 0x8829 +#define GL_DRAW_BUFFER5_ARB 0x882A +#define GL_DRAW_BUFFER6_ARB 0x882B +#define GL_DRAW_BUFFER7_ARB 0x882C +#define GL_DRAW_BUFFER8_ARB 0x882D +#define GL_DRAW_BUFFER9_ARB 0x882E +#define GL_DRAW_BUFFER10_ARB 0x882F +#define GL_DRAW_BUFFER11_ARB 0x8830 +#define GL_DRAW_BUFFER12_ARB 0x8831 +#define GL_DRAW_BUFFER13_ARB 0x8832 +#define GL_DRAW_BUFFER14_ARB 0x8833 +#define GL_DRAW_BUFFER15_ARB 0x8834 +typedef void (APIENTRYP PFNGLDRAWBUFFERSARBPROC) (GLsizei n, const GLenum *bufs); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawBuffersARB (GLsizei n, const GLenum *bufs); +#endif +#endif /* GL_ARB_draw_buffers */ + +#ifndef GL_ARB_draw_buffers_blend +#define GL_ARB_draw_buffers_blend 1 +typedef void (APIENTRYP PFNGLBLENDEQUATIONIARBPROC) (GLuint buf, GLenum mode); +typedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEIARBPROC) (GLuint buf, GLenum modeRGB, GLenum modeAlpha); +typedef void (APIENTRYP PFNGLBLENDFUNCIARBPROC) (GLuint buf, GLenum src, GLenum dst); +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEIARBPROC) (GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendEquationiARB (GLuint buf, GLenum mode); +GLAPI void APIENTRY glBlendEquationSeparateiARB (GLuint buf, GLenum modeRGB, GLenum modeAlpha); +GLAPI void APIENTRY glBlendFunciARB (GLuint buf, GLenum src, GLenum dst); +GLAPI void APIENTRY glBlendFuncSeparateiARB (GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); +#endif +#endif /* GL_ARB_draw_buffers_blend */ + +#ifndef GL_ARB_draw_elements_base_vertex +#define GL_ARB_draw_elements_base_vertex 1 +#endif /* GL_ARB_draw_elements_base_vertex */ + +#ifndef GL_ARB_draw_indirect +#define GL_ARB_draw_indirect 1 +#endif /* GL_ARB_draw_indirect */ + +#ifndef GL_ARB_draw_instanced +#define GL_ARB_draw_instanced 1 +typedef void (APIENTRYP PFNGLDRAWARRAYSINSTANCEDARBPROC) (GLenum mode, GLint first, GLsizei count, GLsizei primcount); +typedef void (APIENTRYP PFNGLDRAWELEMENTSINSTANCEDARBPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawArraysInstancedARB (GLenum mode, GLint first, GLsizei count, GLsizei primcount); +GLAPI void APIENTRY glDrawElementsInstancedARB (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount); +#endif +#endif /* GL_ARB_draw_instanced */ + +#ifndef GL_ARB_enhanced_layouts +#define GL_ARB_enhanced_layouts 1 +#endif /* GL_ARB_enhanced_layouts */ + +#ifndef GL_ARB_explicit_attrib_location +#define GL_ARB_explicit_attrib_location 1 +#endif /* GL_ARB_explicit_attrib_location */ + +#ifndef GL_ARB_explicit_uniform_location +#define GL_ARB_explicit_uniform_location 1 +#endif /* GL_ARB_explicit_uniform_location */ + +#ifndef GL_ARB_fragment_coord_conventions +#define GL_ARB_fragment_coord_conventions 1 +#endif /* GL_ARB_fragment_coord_conventions */ + +#ifndef GL_ARB_fragment_layer_viewport +#define GL_ARB_fragment_layer_viewport 1 +#endif /* GL_ARB_fragment_layer_viewport */ + +#ifndef GL_ARB_fragment_program +#define GL_ARB_fragment_program 1 +#define GL_FRAGMENT_PROGRAM_ARB 0x8804 +#define GL_PROGRAM_FORMAT_ASCII_ARB 0x8875 +#define GL_PROGRAM_LENGTH_ARB 0x8627 +#define GL_PROGRAM_FORMAT_ARB 0x8876 +#define GL_PROGRAM_BINDING_ARB 0x8677 +#define GL_PROGRAM_INSTRUCTIONS_ARB 0x88A0 +#define GL_MAX_PROGRAM_INSTRUCTIONS_ARB 0x88A1 +#define GL_PROGRAM_NATIVE_INSTRUCTIONS_ARB 0x88A2 +#define GL_MAX_PROGRAM_NATIVE_INSTRUCTIONS_ARB 0x88A3 +#define GL_PROGRAM_TEMPORARIES_ARB 0x88A4 +#define GL_MAX_PROGRAM_TEMPORARIES_ARB 0x88A5 +#define GL_PROGRAM_NATIVE_TEMPORARIES_ARB 0x88A6 +#define GL_MAX_PROGRAM_NATIVE_TEMPORARIES_ARB 0x88A7 +#define GL_PROGRAM_PARAMETERS_ARB 0x88A8 +#define GL_MAX_PROGRAM_PARAMETERS_ARB 0x88A9 +#define GL_PROGRAM_NATIVE_PARAMETERS_ARB 0x88AA +#define GL_MAX_PROGRAM_NATIVE_PARAMETERS_ARB 0x88AB +#define GL_PROGRAM_ATTRIBS_ARB 0x88AC +#define GL_MAX_PROGRAM_ATTRIBS_ARB 0x88AD +#define GL_PROGRAM_NATIVE_ATTRIBS_ARB 0x88AE +#define GL_MAX_PROGRAM_NATIVE_ATTRIBS_ARB 0x88AF +#define GL_MAX_PROGRAM_LOCAL_PARAMETERS_ARB 0x88B4 +#define GL_MAX_PROGRAM_ENV_PARAMETERS_ARB 0x88B5 +#define GL_PROGRAM_UNDER_NATIVE_LIMITS_ARB 0x88B6 +#define GL_PROGRAM_ALU_INSTRUCTIONS_ARB 0x8805 +#define GL_PROGRAM_TEX_INSTRUCTIONS_ARB 0x8806 +#define GL_PROGRAM_TEX_INDIRECTIONS_ARB 0x8807 +#define GL_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB 0x8808 +#define GL_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB 0x8809 +#define GL_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB 0x880A +#define GL_MAX_PROGRAM_ALU_INSTRUCTIONS_ARB 0x880B +#define GL_MAX_PROGRAM_TEX_INSTRUCTIONS_ARB 0x880C +#define GL_MAX_PROGRAM_TEX_INDIRECTIONS_ARB 0x880D +#define GL_MAX_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB 0x880E +#define GL_MAX_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB 0x880F +#define GL_MAX_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB 0x8810 +#define GL_PROGRAM_STRING_ARB 0x8628 +#define GL_PROGRAM_ERROR_POSITION_ARB 0x864B +#define GL_CURRENT_MATRIX_ARB 0x8641 +#define GL_TRANSPOSE_CURRENT_MATRIX_ARB 0x88B7 +#define GL_CURRENT_MATRIX_STACK_DEPTH_ARB 0x8640 +#define GL_MAX_PROGRAM_MATRICES_ARB 0x862F +#define GL_MAX_PROGRAM_MATRIX_STACK_DEPTH_ARB 0x862E +#define GL_MAX_TEXTURE_COORDS_ARB 0x8871 +#define GL_MAX_TEXTURE_IMAGE_UNITS_ARB 0x8872 +#define GL_PROGRAM_ERROR_STRING_ARB 0x8874 +#define GL_MATRIX0_ARB 0x88C0 +#define GL_MATRIX1_ARB 0x88C1 +#define GL_MATRIX2_ARB 0x88C2 +#define GL_MATRIX3_ARB 0x88C3 +#define GL_MATRIX4_ARB 0x88C4 +#define GL_MATRIX5_ARB 0x88C5 +#define GL_MATRIX6_ARB 0x88C6 +#define GL_MATRIX7_ARB 0x88C7 +#define GL_MATRIX8_ARB 0x88C8 +#define GL_MATRIX9_ARB 0x88C9 +#define GL_MATRIX10_ARB 0x88CA +#define GL_MATRIX11_ARB 0x88CB +#define GL_MATRIX12_ARB 0x88CC +#define GL_MATRIX13_ARB 0x88CD +#define GL_MATRIX14_ARB 0x88CE +#define GL_MATRIX15_ARB 0x88CF +#define GL_MATRIX16_ARB 0x88D0 +#define GL_MATRIX17_ARB 0x88D1 +#define GL_MATRIX18_ARB 0x88D2 +#define GL_MATRIX19_ARB 0x88D3 +#define GL_MATRIX20_ARB 0x88D4 +#define GL_MATRIX21_ARB 0x88D5 +#define GL_MATRIX22_ARB 0x88D6 +#define GL_MATRIX23_ARB 0x88D7 +#define GL_MATRIX24_ARB 0x88D8 +#define GL_MATRIX25_ARB 0x88D9 +#define GL_MATRIX26_ARB 0x88DA +#define GL_MATRIX27_ARB 0x88DB +#define GL_MATRIX28_ARB 0x88DC +#define GL_MATRIX29_ARB 0x88DD +#define GL_MATRIX30_ARB 0x88DE +#define GL_MATRIX31_ARB 0x88DF +typedef void (APIENTRYP PFNGLPROGRAMSTRINGARBPROC) (GLenum target, GLenum format, GLsizei len, const void *string); +typedef void (APIENTRYP PFNGLBINDPROGRAMARBPROC) (GLenum target, GLuint program); +typedef void (APIENTRYP PFNGLDELETEPROGRAMSARBPROC) (GLsizei n, const GLuint *programs); +typedef void (APIENTRYP PFNGLGENPROGRAMSARBPROC) (GLsizei n, GLuint *programs); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETER4DARBPROC) (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETER4DVARBPROC) (GLenum target, GLuint index, const GLdouble *params); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETER4FARBPROC) (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETER4FVARBPROC) (GLenum target, GLuint index, const GLfloat *params); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETER4DARBPROC) (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETER4DVARBPROC) (GLenum target, GLuint index, const GLdouble *params); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETER4FARBPROC) (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETER4FVARBPROC) (GLenum target, GLuint index, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMENVPARAMETERDVARBPROC) (GLenum target, GLuint index, GLdouble *params); +typedef void (APIENTRYP PFNGLGETPROGRAMENVPARAMETERFVARBPROC) (GLenum target, GLuint index, GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMLOCALPARAMETERDVARBPROC) (GLenum target, GLuint index, GLdouble *params); +typedef void (APIENTRYP PFNGLGETPROGRAMLOCALPARAMETERFVARBPROC) (GLenum target, GLuint index, GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMIVARBPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMSTRINGARBPROC) (GLenum target, GLenum pname, void *string); +typedef GLboolean (APIENTRYP PFNGLISPROGRAMARBPROC) (GLuint program); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramStringARB (GLenum target, GLenum format, GLsizei len, const void *string); +GLAPI void APIENTRY glBindProgramARB (GLenum target, GLuint program); +GLAPI void APIENTRY glDeleteProgramsARB (GLsizei n, const GLuint *programs); +GLAPI void APIENTRY glGenProgramsARB (GLsizei n, GLuint *programs); +GLAPI void APIENTRY glProgramEnvParameter4dARB (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glProgramEnvParameter4dvARB (GLenum target, GLuint index, const GLdouble *params); +GLAPI void APIENTRY glProgramEnvParameter4fARB (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glProgramEnvParameter4fvARB (GLenum target, GLuint index, const GLfloat *params); +GLAPI void APIENTRY glProgramLocalParameter4dARB (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glProgramLocalParameter4dvARB (GLenum target, GLuint index, const GLdouble *params); +GLAPI void APIENTRY glProgramLocalParameter4fARB (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glProgramLocalParameter4fvARB (GLenum target, GLuint index, const GLfloat *params); +GLAPI void APIENTRY glGetProgramEnvParameterdvARB (GLenum target, GLuint index, GLdouble *params); +GLAPI void APIENTRY glGetProgramEnvParameterfvARB (GLenum target, GLuint index, GLfloat *params); +GLAPI void APIENTRY glGetProgramLocalParameterdvARB (GLenum target, GLuint index, GLdouble *params); +GLAPI void APIENTRY glGetProgramLocalParameterfvARB (GLenum target, GLuint index, GLfloat *params); +GLAPI void APIENTRY glGetProgramivARB (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetProgramStringARB (GLenum target, GLenum pname, void *string); +GLAPI GLboolean APIENTRY glIsProgramARB (GLuint program); +#endif +#endif /* GL_ARB_fragment_program */ + +#ifndef GL_ARB_fragment_program_shadow +#define GL_ARB_fragment_program_shadow 1 +#endif /* GL_ARB_fragment_program_shadow */ + +#ifndef GL_ARB_fragment_shader +#define GL_ARB_fragment_shader 1 +#define GL_FRAGMENT_SHADER_ARB 0x8B30 +#define GL_MAX_FRAGMENT_UNIFORM_COMPONENTS_ARB 0x8B49 +#define GL_FRAGMENT_SHADER_DERIVATIVE_HINT_ARB 0x8B8B +#endif /* GL_ARB_fragment_shader */ + +#ifndef GL_ARB_fragment_shader_interlock +#define GL_ARB_fragment_shader_interlock 1 +#endif /* GL_ARB_fragment_shader_interlock */ + +#ifndef GL_ARB_framebuffer_no_attachments +#define GL_ARB_framebuffer_no_attachments 1 +#endif /* GL_ARB_framebuffer_no_attachments */ + +#ifndef GL_ARB_framebuffer_object +#define GL_ARB_framebuffer_object 1 +#endif /* GL_ARB_framebuffer_object */ + +#ifndef GL_ARB_framebuffer_sRGB +#define GL_ARB_framebuffer_sRGB 1 +#endif /* GL_ARB_framebuffer_sRGB */ + +#ifndef GL_ARB_geometry_shader4 +#define GL_ARB_geometry_shader4 1 +#define GL_LINES_ADJACENCY_ARB 0x000A +#define GL_LINE_STRIP_ADJACENCY_ARB 0x000B +#define GL_TRIANGLES_ADJACENCY_ARB 0x000C +#define GL_TRIANGLE_STRIP_ADJACENCY_ARB 0x000D +#define GL_PROGRAM_POINT_SIZE_ARB 0x8642 +#define GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS_ARB 0x8C29 +#define GL_FRAMEBUFFER_ATTACHMENT_LAYERED_ARB 0x8DA7 +#define GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS_ARB 0x8DA8 +#define GL_FRAMEBUFFER_INCOMPLETE_LAYER_COUNT_ARB 0x8DA9 +#define GL_GEOMETRY_SHADER_ARB 0x8DD9 +#define GL_GEOMETRY_VERTICES_OUT_ARB 0x8DDA +#define GL_GEOMETRY_INPUT_TYPE_ARB 0x8DDB +#define GL_GEOMETRY_OUTPUT_TYPE_ARB 0x8DDC +#define GL_MAX_GEOMETRY_VARYING_COMPONENTS_ARB 0x8DDD +#define GL_MAX_VERTEX_VARYING_COMPONENTS_ARB 0x8DDE +#define GL_MAX_GEOMETRY_UNIFORM_COMPONENTS_ARB 0x8DDF +#define GL_MAX_GEOMETRY_OUTPUT_VERTICES_ARB 0x8DE0 +#define GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS_ARB 0x8DE1 +typedef void (APIENTRYP PFNGLPROGRAMPARAMETERIARBPROC) (GLuint program, GLenum pname, GLint value); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTUREARBPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURELAYERARBPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTUREFACEARBPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level, GLenum face); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramParameteriARB (GLuint program, GLenum pname, GLint value); +GLAPI void APIENTRY glFramebufferTextureARB (GLenum target, GLenum attachment, GLuint texture, GLint level); +GLAPI void APIENTRY glFramebufferTextureLayerARB (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); +GLAPI void APIENTRY glFramebufferTextureFaceARB (GLenum target, GLenum attachment, GLuint texture, GLint level, GLenum face); +#endif +#endif /* GL_ARB_geometry_shader4 */ + +#ifndef GL_ARB_get_program_binary +#define GL_ARB_get_program_binary 1 +#endif /* GL_ARB_get_program_binary */ + +#ifndef GL_ARB_get_texture_sub_image +#define GL_ARB_get_texture_sub_image 1 +#endif /* GL_ARB_get_texture_sub_image */ + +#ifndef GL_ARB_gl_spirv +#define GL_ARB_gl_spirv 1 +#define GL_SHADER_BINARY_FORMAT_SPIR_V_ARB 0x9551 +#define GL_SPIR_V_BINARY_ARB 0x9552 +typedef void (APIENTRYP PFNGLSPECIALIZESHADERARBPROC) (GLuint shader, const GLchar *pEntryPoint, GLuint numSpecializationConstants, const GLuint *pConstantIndex, const GLuint *pConstantValue); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSpecializeShaderARB (GLuint shader, const GLchar *pEntryPoint, GLuint numSpecializationConstants, const GLuint *pConstantIndex, const GLuint *pConstantValue); +#endif +#endif /* GL_ARB_gl_spirv */ + +#ifndef GL_ARB_gpu_shader5 +#define GL_ARB_gpu_shader5 1 +#endif /* GL_ARB_gpu_shader5 */ + +#ifndef GL_ARB_gpu_shader_fp64 +#define GL_ARB_gpu_shader_fp64 1 +#endif /* GL_ARB_gpu_shader_fp64 */ + +#ifndef GL_ARB_gpu_shader_int64 +#define GL_ARB_gpu_shader_int64 1 +#define GL_INT64_ARB 0x140E +#define GL_INT64_VEC2_ARB 0x8FE9 +#define GL_INT64_VEC3_ARB 0x8FEA +#define GL_INT64_VEC4_ARB 0x8FEB +#define GL_UNSIGNED_INT64_VEC2_ARB 0x8FF5 +#define GL_UNSIGNED_INT64_VEC3_ARB 0x8FF6 +#define GL_UNSIGNED_INT64_VEC4_ARB 0x8FF7 +typedef void (APIENTRYP PFNGLUNIFORM1I64ARBPROC) (GLint location, GLint64 x); +typedef void (APIENTRYP PFNGLUNIFORM2I64ARBPROC) (GLint location, GLint64 x, GLint64 y); +typedef void (APIENTRYP PFNGLUNIFORM3I64ARBPROC) (GLint location, GLint64 x, GLint64 y, GLint64 z); +typedef void (APIENTRYP PFNGLUNIFORM4I64ARBPROC) (GLint location, GLint64 x, GLint64 y, GLint64 z, GLint64 w); +typedef void (APIENTRYP PFNGLUNIFORM1I64VARBPROC) (GLint location, GLsizei count, const GLint64 *value); +typedef void (APIENTRYP PFNGLUNIFORM2I64VARBPROC) (GLint location, GLsizei count, const GLint64 *value); +typedef void (APIENTRYP PFNGLUNIFORM3I64VARBPROC) (GLint location, GLsizei count, const GLint64 *value); +typedef void (APIENTRYP PFNGLUNIFORM4I64VARBPROC) (GLint location, GLsizei count, const GLint64 *value); +typedef void (APIENTRYP PFNGLUNIFORM1UI64ARBPROC) (GLint location, GLuint64 x); +typedef void (APIENTRYP PFNGLUNIFORM2UI64ARBPROC) (GLint location, GLuint64 x, GLuint64 y); +typedef void (APIENTRYP PFNGLUNIFORM3UI64ARBPROC) (GLint location, GLuint64 x, GLuint64 y, GLuint64 z); +typedef void (APIENTRYP PFNGLUNIFORM4UI64ARBPROC) (GLint location, GLuint64 x, GLuint64 y, GLuint64 z, GLuint64 w); +typedef void (APIENTRYP PFNGLUNIFORM1UI64VARBPROC) (GLint location, GLsizei count, const GLuint64 *value); +typedef void (APIENTRYP PFNGLUNIFORM2UI64VARBPROC) (GLint location, GLsizei count, const GLuint64 *value); +typedef void (APIENTRYP PFNGLUNIFORM3UI64VARBPROC) (GLint location, GLsizei count, const GLuint64 *value); +typedef void (APIENTRYP PFNGLUNIFORM4UI64VARBPROC) (GLint location, GLsizei count, const GLuint64 *value); +typedef void (APIENTRYP PFNGLGETUNIFORMI64VARBPROC) (GLuint program, GLint location, GLint64 *params); +typedef void (APIENTRYP PFNGLGETUNIFORMUI64VARBPROC) (GLuint program, GLint location, GLuint64 *params); +typedef void (APIENTRYP PFNGLGETNUNIFORMI64VARBPROC) (GLuint program, GLint location, GLsizei bufSize, GLint64 *params); +typedef void (APIENTRYP PFNGLGETNUNIFORMUI64VARBPROC) (GLuint program, GLint location, GLsizei bufSize, GLuint64 *params); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1I64ARBPROC) (GLuint program, GLint location, GLint64 x); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2I64ARBPROC) (GLuint program, GLint location, GLint64 x, GLint64 y); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3I64ARBPROC) (GLuint program, GLint location, GLint64 x, GLint64 y, GLint64 z); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4I64ARBPROC) (GLuint program, GLint location, GLint64 x, GLint64 y, GLint64 z, GLint64 w); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1I64VARBPROC) (GLuint program, GLint location, GLsizei count, const GLint64 *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2I64VARBPROC) (GLuint program, GLint location, GLsizei count, const GLint64 *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3I64VARBPROC) (GLuint program, GLint location, GLsizei count, const GLint64 *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4I64VARBPROC) (GLuint program, GLint location, GLsizei count, const GLint64 *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1UI64ARBPROC) (GLuint program, GLint location, GLuint64 x); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2UI64ARBPROC) (GLuint program, GLint location, GLuint64 x, GLuint64 y); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3UI64ARBPROC) (GLuint program, GLint location, GLuint64 x, GLuint64 y, GLuint64 z); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4UI64ARBPROC) (GLuint program, GLint location, GLuint64 x, GLuint64 y, GLuint64 z, GLuint64 w); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1UI64VARBPROC) (GLuint program, GLint location, GLsizei count, const GLuint64 *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2UI64VARBPROC) (GLuint program, GLint location, GLsizei count, const GLuint64 *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3UI64VARBPROC) (GLuint program, GLint location, GLsizei count, const GLuint64 *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4UI64VARBPROC) (GLuint program, GLint location, GLsizei count, const GLuint64 *value); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glUniform1i64ARB (GLint location, GLint64 x); +GLAPI void APIENTRY glUniform2i64ARB (GLint location, GLint64 x, GLint64 y); +GLAPI void APIENTRY glUniform3i64ARB (GLint location, GLint64 x, GLint64 y, GLint64 z); +GLAPI void APIENTRY glUniform4i64ARB (GLint location, GLint64 x, GLint64 y, GLint64 z, GLint64 w); +GLAPI void APIENTRY glUniform1i64vARB (GLint location, GLsizei count, const GLint64 *value); +GLAPI void APIENTRY glUniform2i64vARB (GLint location, GLsizei count, const GLint64 *value); +GLAPI void APIENTRY glUniform3i64vARB (GLint location, GLsizei count, const GLint64 *value); +GLAPI void APIENTRY glUniform4i64vARB (GLint location, GLsizei count, const GLint64 *value); +GLAPI void APIENTRY glUniform1ui64ARB (GLint location, GLuint64 x); +GLAPI void APIENTRY glUniform2ui64ARB (GLint location, GLuint64 x, GLuint64 y); +GLAPI void APIENTRY glUniform3ui64ARB (GLint location, GLuint64 x, GLuint64 y, GLuint64 z); +GLAPI void APIENTRY glUniform4ui64ARB (GLint location, GLuint64 x, GLuint64 y, GLuint64 z, GLuint64 w); +GLAPI void APIENTRY glUniform1ui64vARB (GLint location, GLsizei count, const GLuint64 *value); +GLAPI void APIENTRY glUniform2ui64vARB (GLint location, GLsizei count, const GLuint64 *value); +GLAPI void APIENTRY glUniform3ui64vARB (GLint location, GLsizei count, const GLuint64 *value); +GLAPI void APIENTRY glUniform4ui64vARB (GLint location, GLsizei count, const GLuint64 *value); +GLAPI void APIENTRY glGetUniformi64vARB (GLuint program, GLint location, GLint64 *params); +GLAPI void APIENTRY glGetUniformui64vARB (GLuint program, GLint location, GLuint64 *params); +GLAPI void APIENTRY glGetnUniformi64vARB (GLuint program, GLint location, GLsizei bufSize, GLint64 *params); +GLAPI void APIENTRY glGetnUniformui64vARB (GLuint program, GLint location, GLsizei bufSize, GLuint64 *params); +GLAPI void APIENTRY glProgramUniform1i64ARB (GLuint program, GLint location, GLint64 x); +GLAPI void APIENTRY glProgramUniform2i64ARB (GLuint program, GLint location, GLint64 x, GLint64 y); +GLAPI void APIENTRY glProgramUniform3i64ARB (GLuint program, GLint location, GLint64 x, GLint64 y, GLint64 z); +GLAPI void APIENTRY glProgramUniform4i64ARB (GLuint program, GLint location, GLint64 x, GLint64 y, GLint64 z, GLint64 w); +GLAPI void APIENTRY glProgramUniform1i64vARB (GLuint program, GLint location, GLsizei count, const GLint64 *value); +GLAPI void APIENTRY glProgramUniform2i64vARB (GLuint program, GLint location, GLsizei count, const GLint64 *value); +GLAPI void APIENTRY glProgramUniform3i64vARB (GLuint program, GLint location, GLsizei count, const GLint64 *value); +GLAPI void APIENTRY glProgramUniform4i64vARB (GLuint program, GLint location, GLsizei count, const GLint64 *value); +GLAPI void APIENTRY glProgramUniform1ui64ARB (GLuint program, GLint location, GLuint64 x); +GLAPI void APIENTRY glProgramUniform2ui64ARB (GLuint program, GLint location, GLuint64 x, GLuint64 y); +GLAPI void APIENTRY glProgramUniform3ui64ARB (GLuint program, GLint location, GLuint64 x, GLuint64 y, GLuint64 z); +GLAPI void APIENTRY glProgramUniform4ui64ARB (GLuint program, GLint location, GLuint64 x, GLuint64 y, GLuint64 z, GLuint64 w); +GLAPI void APIENTRY glProgramUniform1ui64vARB (GLuint program, GLint location, GLsizei count, const GLuint64 *value); +GLAPI void APIENTRY glProgramUniform2ui64vARB (GLuint program, GLint location, GLsizei count, const GLuint64 *value); +GLAPI void APIENTRY glProgramUniform3ui64vARB (GLuint program, GLint location, GLsizei count, const GLuint64 *value); +GLAPI void APIENTRY glProgramUniform4ui64vARB (GLuint program, GLint location, GLsizei count, const GLuint64 *value); +#endif +#endif /* GL_ARB_gpu_shader_int64 */ + +#ifndef GL_ARB_half_float_pixel +#define GL_ARB_half_float_pixel 1 +typedef khronos_uint16_t GLhalfARB; +#define GL_HALF_FLOAT_ARB 0x140B +#endif /* GL_ARB_half_float_pixel */ + +#ifndef GL_ARB_half_float_vertex +#define GL_ARB_half_float_vertex 1 +#endif /* GL_ARB_half_float_vertex */ + +#ifndef GL_ARB_imaging +#define GL_ARB_imaging 1 +#define GL_CONVOLUTION_BORDER_MODE 0x8013 +#define GL_CONVOLUTION_FILTER_SCALE 0x8014 +#define GL_CONVOLUTION_FILTER_BIAS 0x8015 +#define GL_REDUCE 0x8016 +#define GL_CONVOLUTION_FORMAT 0x8017 +#define GL_CONVOLUTION_WIDTH 0x8018 +#define GL_CONVOLUTION_HEIGHT 0x8019 +#define GL_MAX_CONVOLUTION_WIDTH 0x801A +#define GL_MAX_CONVOLUTION_HEIGHT 0x801B +#define GL_POST_CONVOLUTION_RED_SCALE 0x801C +#define GL_POST_CONVOLUTION_GREEN_SCALE 0x801D +#define GL_POST_CONVOLUTION_BLUE_SCALE 0x801E +#define GL_POST_CONVOLUTION_ALPHA_SCALE 0x801F +#define GL_POST_CONVOLUTION_RED_BIAS 0x8020 +#define GL_POST_CONVOLUTION_GREEN_BIAS 0x8021 +#define GL_POST_CONVOLUTION_BLUE_BIAS 0x8022 +#define GL_POST_CONVOLUTION_ALPHA_BIAS 0x8023 +#define GL_HISTOGRAM_WIDTH 0x8026 +#define GL_HISTOGRAM_FORMAT 0x8027 +#define GL_HISTOGRAM_RED_SIZE 0x8028 +#define GL_HISTOGRAM_GREEN_SIZE 0x8029 +#define GL_HISTOGRAM_BLUE_SIZE 0x802A +#define GL_HISTOGRAM_ALPHA_SIZE 0x802B +#define GL_HISTOGRAM_LUMINANCE_SIZE 0x802C +#define GL_HISTOGRAM_SINK 0x802D +#define GL_MINMAX_FORMAT 0x802F +#define GL_MINMAX_SINK 0x8030 +#define GL_TABLE_TOO_LARGE 0x8031 +#define GL_COLOR_MATRIX 0x80B1 +#define GL_COLOR_MATRIX_STACK_DEPTH 0x80B2 +#define GL_MAX_COLOR_MATRIX_STACK_DEPTH 0x80B3 +#define GL_POST_COLOR_MATRIX_RED_SCALE 0x80B4 +#define GL_POST_COLOR_MATRIX_GREEN_SCALE 0x80B5 +#define GL_POST_COLOR_MATRIX_BLUE_SCALE 0x80B6 +#define GL_POST_COLOR_MATRIX_ALPHA_SCALE 0x80B7 +#define GL_POST_COLOR_MATRIX_RED_BIAS 0x80B8 +#define GL_POST_COLOR_MATRIX_GREEN_BIAS 0x80B9 +#define GL_POST_COLOR_MATRIX_BLUE_BIAS 0x80BA +#define GL_POST_COLOR_MATRIX_ALPHA_BIAS 0x80BB +#define GL_COLOR_TABLE_SCALE 0x80D6 +#define GL_COLOR_TABLE_BIAS 0x80D7 +#define GL_COLOR_TABLE_FORMAT 0x80D8 +#define GL_COLOR_TABLE_WIDTH 0x80D9 +#define GL_COLOR_TABLE_RED_SIZE 0x80DA +#define GL_COLOR_TABLE_GREEN_SIZE 0x80DB +#define GL_COLOR_TABLE_BLUE_SIZE 0x80DC +#define GL_COLOR_TABLE_ALPHA_SIZE 0x80DD +#define GL_COLOR_TABLE_LUMINANCE_SIZE 0x80DE +#define GL_COLOR_TABLE_INTENSITY_SIZE 0x80DF +#define GL_CONSTANT_BORDER 0x8151 +#define GL_REPLICATE_BORDER 0x8153 +#define GL_CONVOLUTION_BORDER_COLOR 0x8154 +typedef void (APIENTRYP PFNGLCOLORTABLEPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const void *table); +typedef void (APIENTRYP PFNGLCOLORTABLEPARAMETERFVPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCOLORTABLEPARAMETERIVPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOPYCOLORTABLEPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPROC) (GLenum target, GLenum format, GLenum type, void *table); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLCOLORSUBTABLEPROC) (GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const void *data); +typedef void (APIENTRYP PFNGLCOPYCOLORSUBTABLEPROC) (GLenum target, GLsizei start, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCONVOLUTIONFILTER1DPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const void *image); +typedef void (APIENTRYP PFNGLCONVOLUTIONFILTER2DPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *image); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERFPROC) (GLenum target, GLenum pname, GLfloat params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERFVPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERIPROC) (GLenum target, GLenum pname, GLint params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERIVPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOPYCONVOLUTIONFILTER1DPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCOPYCONVOLUTIONFILTER2DPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONFILTERPROC) (GLenum target, GLenum format, GLenum type, void *image); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETSEPARABLEFILTERPROC) (GLenum target, GLenum format, GLenum type, void *row, void *column, void *span); +typedef void (APIENTRYP PFNGLSEPARABLEFILTER2DPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *row, const void *column); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, void *values); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMINMAXPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, void *values); +typedef void (APIENTRYP PFNGLGETMINMAXPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMINMAXPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLHISTOGRAMPROC) (GLenum target, GLsizei width, GLenum internalformat, GLboolean sink); +typedef void (APIENTRYP PFNGLMINMAXPROC) (GLenum target, GLenum internalformat, GLboolean sink); +typedef void (APIENTRYP PFNGLRESETHISTOGRAMPROC) (GLenum target); +typedef void (APIENTRYP PFNGLRESETMINMAXPROC) (GLenum target); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorTable (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const void *table); +GLAPI void APIENTRY glColorTableParameterfv (GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glColorTableParameteriv (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glCopyColorTable (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +GLAPI void APIENTRY glGetColorTable (GLenum target, GLenum format, GLenum type, void *table); +GLAPI void APIENTRY glGetColorTableParameterfv (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetColorTableParameteriv (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glColorSubTable (GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const void *data); +GLAPI void APIENTRY glCopyColorSubTable (GLenum target, GLsizei start, GLint x, GLint y, GLsizei width); +GLAPI void APIENTRY glConvolutionFilter1D (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const void *image); +GLAPI void APIENTRY glConvolutionFilter2D (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *image); +GLAPI void APIENTRY glConvolutionParameterf (GLenum target, GLenum pname, GLfloat params); +GLAPI void APIENTRY glConvolutionParameterfv (GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glConvolutionParameteri (GLenum target, GLenum pname, GLint params); +GLAPI void APIENTRY glConvolutionParameteriv (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glCopyConvolutionFilter1D (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +GLAPI void APIENTRY glCopyConvolutionFilter2D (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glGetConvolutionFilter (GLenum target, GLenum format, GLenum type, void *image); +GLAPI void APIENTRY glGetConvolutionParameterfv (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetConvolutionParameteriv (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetSeparableFilter (GLenum target, GLenum format, GLenum type, void *row, void *column, void *span); +GLAPI void APIENTRY glSeparableFilter2D (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *row, const void *column); +GLAPI void APIENTRY glGetHistogram (GLenum target, GLboolean reset, GLenum format, GLenum type, void *values); +GLAPI void APIENTRY glGetHistogramParameterfv (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetHistogramParameteriv (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetMinmax (GLenum target, GLboolean reset, GLenum format, GLenum type, void *values); +GLAPI void APIENTRY glGetMinmaxParameterfv (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetMinmaxParameteriv (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glHistogram (GLenum target, GLsizei width, GLenum internalformat, GLboolean sink); +GLAPI void APIENTRY glMinmax (GLenum target, GLenum internalformat, GLboolean sink); +GLAPI void APIENTRY glResetHistogram (GLenum target); +GLAPI void APIENTRY glResetMinmax (GLenum target); +#endif +#endif /* GL_ARB_imaging */ + +#ifndef GL_ARB_indirect_parameters +#define GL_ARB_indirect_parameters 1 +#define GL_PARAMETER_BUFFER_ARB 0x80EE +#define GL_PARAMETER_BUFFER_BINDING_ARB 0x80EF +typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSINDIRECTCOUNTARBPROC) (GLenum mode, const void *indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSINDIRECTCOUNTARBPROC) (GLenum mode, GLenum type, const void *indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMultiDrawArraysIndirectCountARB (GLenum mode, const void *indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride); +GLAPI void APIENTRY glMultiDrawElementsIndirectCountARB (GLenum mode, GLenum type, const void *indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride); +#endif +#endif /* GL_ARB_indirect_parameters */ + +#ifndef GL_ARB_instanced_arrays +#define GL_ARB_instanced_arrays 1 +#define GL_VERTEX_ATTRIB_ARRAY_DIVISOR_ARB 0x88FE +typedef void (APIENTRYP PFNGLVERTEXATTRIBDIVISORARBPROC) (GLuint index, GLuint divisor); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexAttribDivisorARB (GLuint index, GLuint divisor); +#endif +#endif /* GL_ARB_instanced_arrays */ + +#ifndef GL_ARB_internalformat_query +#define GL_ARB_internalformat_query 1 +#endif /* GL_ARB_internalformat_query */ + +#ifndef GL_ARB_internalformat_query2 +#define GL_ARB_internalformat_query2 1 +#define GL_SRGB_DECODE_ARB 0x8299 +#define GL_VIEW_CLASS_EAC_R11 0x9383 +#define GL_VIEW_CLASS_EAC_RG11 0x9384 +#define GL_VIEW_CLASS_ETC2_RGB 0x9385 +#define GL_VIEW_CLASS_ETC2_RGBA 0x9386 +#define GL_VIEW_CLASS_ETC2_EAC_RGBA 0x9387 +#define GL_VIEW_CLASS_ASTC_4x4_RGBA 0x9388 +#define GL_VIEW_CLASS_ASTC_5x4_RGBA 0x9389 +#define GL_VIEW_CLASS_ASTC_5x5_RGBA 0x938A +#define GL_VIEW_CLASS_ASTC_6x5_RGBA 0x938B +#define GL_VIEW_CLASS_ASTC_6x6_RGBA 0x938C +#define GL_VIEW_CLASS_ASTC_8x5_RGBA 0x938D +#define GL_VIEW_CLASS_ASTC_8x6_RGBA 0x938E +#define GL_VIEW_CLASS_ASTC_8x8_RGBA 0x938F +#define GL_VIEW_CLASS_ASTC_10x5_RGBA 0x9390 +#define GL_VIEW_CLASS_ASTC_10x6_RGBA 0x9391 +#define GL_VIEW_CLASS_ASTC_10x8_RGBA 0x9392 +#define GL_VIEW_CLASS_ASTC_10x10_RGBA 0x9393 +#define GL_VIEW_CLASS_ASTC_12x10_RGBA 0x9394 +#define GL_VIEW_CLASS_ASTC_12x12_RGBA 0x9395 +#endif /* GL_ARB_internalformat_query2 */ + +#ifndef GL_ARB_invalidate_subdata +#define GL_ARB_invalidate_subdata 1 +#endif /* GL_ARB_invalidate_subdata */ + +#ifndef GL_ARB_map_buffer_alignment +#define GL_ARB_map_buffer_alignment 1 +#endif /* GL_ARB_map_buffer_alignment */ + +#ifndef GL_ARB_map_buffer_range +#define GL_ARB_map_buffer_range 1 +#endif /* GL_ARB_map_buffer_range */ + +#ifndef GL_ARB_matrix_palette +#define GL_ARB_matrix_palette 1 +#define GL_MATRIX_PALETTE_ARB 0x8840 +#define GL_MAX_MATRIX_PALETTE_STACK_DEPTH_ARB 0x8841 +#define GL_MAX_PALETTE_MATRICES_ARB 0x8842 +#define GL_CURRENT_PALETTE_MATRIX_ARB 0x8843 +#define GL_MATRIX_INDEX_ARRAY_ARB 0x8844 +#define GL_CURRENT_MATRIX_INDEX_ARB 0x8845 +#define GL_MATRIX_INDEX_ARRAY_SIZE_ARB 0x8846 +#define GL_MATRIX_INDEX_ARRAY_TYPE_ARB 0x8847 +#define GL_MATRIX_INDEX_ARRAY_STRIDE_ARB 0x8848 +#define GL_MATRIX_INDEX_ARRAY_POINTER_ARB 0x8849 +typedef void (APIENTRYP PFNGLCURRENTPALETTEMATRIXARBPROC) (GLint index); +typedef void (APIENTRYP PFNGLMATRIXINDEXUBVARBPROC) (GLint size, const GLubyte *indices); +typedef void (APIENTRYP PFNGLMATRIXINDEXUSVARBPROC) (GLint size, const GLushort *indices); +typedef void (APIENTRYP PFNGLMATRIXINDEXUIVARBPROC) (GLint size, const GLuint *indices); +typedef void (APIENTRYP PFNGLMATRIXINDEXPOINTERARBPROC) (GLint size, GLenum type, GLsizei stride, const void *pointer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCurrentPaletteMatrixARB (GLint index); +GLAPI void APIENTRY glMatrixIndexubvARB (GLint size, const GLubyte *indices); +GLAPI void APIENTRY glMatrixIndexusvARB (GLint size, const GLushort *indices); +GLAPI void APIENTRY glMatrixIndexuivARB (GLint size, const GLuint *indices); +GLAPI void APIENTRY glMatrixIndexPointerARB (GLint size, GLenum type, GLsizei stride, const void *pointer); +#endif +#endif /* GL_ARB_matrix_palette */ + +#ifndef GL_ARB_multi_bind +#define GL_ARB_multi_bind 1 +#endif /* GL_ARB_multi_bind */ + +#ifndef GL_ARB_multi_draw_indirect +#define GL_ARB_multi_draw_indirect 1 +#endif /* GL_ARB_multi_draw_indirect */ + +#ifndef GL_ARB_multisample +#define GL_ARB_multisample 1 +#define GL_MULTISAMPLE_ARB 0x809D +#define GL_SAMPLE_ALPHA_TO_COVERAGE_ARB 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_ARB 0x809F +#define GL_SAMPLE_COVERAGE_ARB 0x80A0 +#define GL_SAMPLE_BUFFERS_ARB 0x80A8 +#define GL_SAMPLES_ARB 0x80A9 +#define GL_SAMPLE_COVERAGE_VALUE_ARB 0x80AA +#define GL_SAMPLE_COVERAGE_INVERT_ARB 0x80AB +#define GL_MULTISAMPLE_BIT_ARB 0x20000000 +typedef void (APIENTRYP PFNGLSAMPLECOVERAGEARBPROC) (GLfloat value, GLboolean invert); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSampleCoverageARB (GLfloat value, GLboolean invert); +#endif +#endif /* GL_ARB_multisample */ + +#ifndef GL_ARB_multitexture +#define GL_ARB_multitexture 1 +#define GL_TEXTURE0_ARB 0x84C0 +#define GL_TEXTURE1_ARB 0x84C1 +#define GL_TEXTURE2_ARB 0x84C2 +#define GL_TEXTURE3_ARB 0x84C3 +#define GL_TEXTURE4_ARB 0x84C4 +#define GL_TEXTURE5_ARB 0x84C5 +#define GL_TEXTURE6_ARB 0x84C6 +#define GL_TEXTURE7_ARB 0x84C7 +#define GL_TEXTURE8_ARB 0x84C8 +#define GL_TEXTURE9_ARB 0x84C9 +#define GL_TEXTURE10_ARB 0x84CA +#define GL_TEXTURE11_ARB 0x84CB +#define GL_TEXTURE12_ARB 0x84CC +#define GL_TEXTURE13_ARB 0x84CD +#define GL_TEXTURE14_ARB 0x84CE +#define GL_TEXTURE15_ARB 0x84CF +#define GL_TEXTURE16_ARB 0x84D0 +#define GL_TEXTURE17_ARB 0x84D1 +#define GL_TEXTURE18_ARB 0x84D2 +#define GL_TEXTURE19_ARB 0x84D3 +#define GL_TEXTURE20_ARB 0x84D4 +#define GL_TEXTURE21_ARB 0x84D5 +#define GL_TEXTURE22_ARB 0x84D6 +#define GL_TEXTURE23_ARB 0x84D7 +#define GL_TEXTURE24_ARB 0x84D8 +#define GL_TEXTURE25_ARB 0x84D9 +#define GL_TEXTURE26_ARB 0x84DA +#define GL_TEXTURE27_ARB 0x84DB +#define GL_TEXTURE28_ARB 0x84DC +#define GL_TEXTURE29_ARB 0x84DD +#define GL_TEXTURE30_ARB 0x84DE +#define GL_TEXTURE31_ARB 0x84DF +#define GL_ACTIVE_TEXTURE_ARB 0x84E0 +#define GL_CLIENT_ACTIVE_TEXTURE_ARB 0x84E1 +#define GL_MAX_TEXTURE_UNITS_ARB 0x84E2 +typedef void (APIENTRYP PFNGLACTIVETEXTUREARBPROC) (GLenum texture); +typedef void (APIENTRYP PFNGLCLIENTACTIVETEXTUREARBPROC) (GLenum texture); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1DARBPROC) (GLenum target, GLdouble s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1FARBPROC) (GLenum target, GLfloat s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1IARBPROC) (GLenum target, GLint s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1SARBPROC) (GLenum target, GLshort s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2DARBPROC) (GLenum target, GLdouble s, GLdouble t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2FARBPROC) (GLenum target, GLfloat s, GLfloat t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2IARBPROC) (GLenum target, GLint s, GLint t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2SARBPROC) (GLenum target, GLshort s, GLshort t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3DARBPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3FARBPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3IARBPROC) (GLenum target, GLint s, GLint t, GLint r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3SARBPROC) (GLenum target, GLshort s, GLshort t, GLshort r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4DARBPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4FARBPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4IARBPROC) (GLenum target, GLint s, GLint t, GLint r, GLint q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4SARBPROC) (GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4SVARBPROC) (GLenum target, const GLshort *v); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glActiveTextureARB (GLenum texture); +GLAPI void APIENTRY glClientActiveTextureARB (GLenum texture); +GLAPI void APIENTRY glMultiTexCoord1dARB (GLenum target, GLdouble s); +GLAPI void APIENTRY glMultiTexCoord1dvARB (GLenum target, const GLdouble *v); +GLAPI void APIENTRY glMultiTexCoord1fARB (GLenum target, GLfloat s); +GLAPI void APIENTRY glMultiTexCoord1fvARB (GLenum target, const GLfloat *v); +GLAPI void APIENTRY glMultiTexCoord1iARB (GLenum target, GLint s); +GLAPI void APIENTRY glMultiTexCoord1ivARB (GLenum target, const GLint *v); +GLAPI void APIENTRY glMultiTexCoord1sARB (GLenum target, GLshort s); +GLAPI void APIENTRY glMultiTexCoord1svARB (GLenum target, const GLshort *v); +GLAPI void APIENTRY glMultiTexCoord2dARB (GLenum target, GLdouble s, GLdouble t); +GLAPI void APIENTRY glMultiTexCoord2dvARB (GLenum target, const GLdouble *v); +GLAPI void APIENTRY glMultiTexCoord2fARB (GLenum target, GLfloat s, GLfloat t); +GLAPI void APIENTRY glMultiTexCoord2fvARB (GLenum target, const GLfloat *v); +GLAPI void APIENTRY glMultiTexCoord2iARB (GLenum target, GLint s, GLint t); +GLAPI void APIENTRY glMultiTexCoord2ivARB (GLenum target, const GLint *v); +GLAPI void APIENTRY glMultiTexCoord2sARB (GLenum target, GLshort s, GLshort t); +GLAPI void APIENTRY glMultiTexCoord2svARB (GLenum target, const GLshort *v); +GLAPI void APIENTRY glMultiTexCoord3dARB (GLenum target, GLdouble s, GLdouble t, GLdouble r); +GLAPI void APIENTRY glMultiTexCoord3dvARB (GLenum target, const GLdouble *v); +GLAPI void APIENTRY glMultiTexCoord3fARB (GLenum target, GLfloat s, GLfloat t, GLfloat r); +GLAPI void APIENTRY glMultiTexCoord3fvARB (GLenum target, const GLfloat *v); +GLAPI void APIENTRY glMultiTexCoord3iARB (GLenum target, GLint s, GLint t, GLint r); +GLAPI void APIENTRY glMultiTexCoord3ivARB (GLenum target, const GLint *v); +GLAPI void APIENTRY glMultiTexCoord3sARB (GLenum target, GLshort s, GLshort t, GLshort r); +GLAPI void APIENTRY glMultiTexCoord3svARB (GLenum target, const GLshort *v); +GLAPI void APIENTRY glMultiTexCoord4dARB (GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); +GLAPI void APIENTRY glMultiTexCoord4dvARB (GLenum target, const GLdouble *v); +GLAPI void APIENTRY glMultiTexCoord4fARB (GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); +GLAPI void APIENTRY glMultiTexCoord4fvARB (GLenum target, const GLfloat *v); +GLAPI void APIENTRY glMultiTexCoord4iARB (GLenum target, GLint s, GLint t, GLint r, GLint q); +GLAPI void APIENTRY glMultiTexCoord4ivARB (GLenum target, const GLint *v); +GLAPI void APIENTRY glMultiTexCoord4sARB (GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); +GLAPI void APIENTRY glMultiTexCoord4svARB (GLenum target, const GLshort *v); +#endif +#endif /* GL_ARB_multitexture */ + +#ifndef GL_ARB_occlusion_query +#define GL_ARB_occlusion_query 1 +#define GL_QUERY_COUNTER_BITS_ARB 0x8864 +#define GL_CURRENT_QUERY_ARB 0x8865 +#define GL_QUERY_RESULT_ARB 0x8866 +#define GL_QUERY_RESULT_AVAILABLE_ARB 0x8867 +#define GL_SAMPLES_PASSED_ARB 0x8914 +typedef void (APIENTRYP PFNGLGENQUERIESARBPROC) (GLsizei n, GLuint *ids); +typedef void (APIENTRYP PFNGLDELETEQUERIESARBPROC) (GLsizei n, const GLuint *ids); +typedef GLboolean (APIENTRYP PFNGLISQUERYARBPROC) (GLuint id); +typedef void (APIENTRYP PFNGLBEGINQUERYARBPROC) (GLenum target, GLuint id); +typedef void (APIENTRYP PFNGLENDQUERYARBPROC) (GLenum target); +typedef void (APIENTRYP PFNGLGETQUERYIVARBPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTIVARBPROC) (GLuint id, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTUIVARBPROC) (GLuint id, GLenum pname, GLuint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenQueriesARB (GLsizei n, GLuint *ids); +GLAPI void APIENTRY glDeleteQueriesARB (GLsizei n, const GLuint *ids); +GLAPI GLboolean APIENTRY glIsQueryARB (GLuint id); +GLAPI void APIENTRY glBeginQueryARB (GLenum target, GLuint id); +GLAPI void APIENTRY glEndQueryARB (GLenum target); +GLAPI void APIENTRY glGetQueryivARB (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetQueryObjectivARB (GLuint id, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetQueryObjectuivARB (GLuint id, GLenum pname, GLuint *params); +#endif +#endif /* GL_ARB_occlusion_query */ + +#ifndef GL_ARB_occlusion_query2 +#define GL_ARB_occlusion_query2 1 +#endif /* GL_ARB_occlusion_query2 */ + +#ifndef GL_ARB_parallel_shader_compile +#define GL_ARB_parallel_shader_compile 1 +#define GL_MAX_SHADER_COMPILER_THREADS_ARB 0x91B0 +#define GL_COMPLETION_STATUS_ARB 0x91B1 +typedef void (APIENTRYP PFNGLMAXSHADERCOMPILERTHREADSARBPROC) (GLuint count); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMaxShaderCompilerThreadsARB (GLuint count); +#endif +#endif /* GL_ARB_parallel_shader_compile */ + +#ifndef GL_ARB_pipeline_statistics_query +#define GL_ARB_pipeline_statistics_query 1 +#define GL_VERTICES_SUBMITTED_ARB 0x82EE +#define GL_PRIMITIVES_SUBMITTED_ARB 0x82EF +#define GL_VERTEX_SHADER_INVOCATIONS_ARB 0x82F0 +#define GL_TESS_CONTROL_SHADER_PATCHES_ARB 0x82F1 +#define GL_TESS_EVALUATION_SHADER_INVOCATIONS_ARB 0x82F2 +#define GL_GEOMETRY_SHADER_PRIMITIVES_EMITTED_ARB 0x82F3 +#define GL_FRAGMENT_SHADER_INVOCATIONS_ARB 0x82F4 +#define GL_COMPUTE_SHADER_INVOCATIONS_ARB 0x82F5 +#define GL_CLIPPING_INPUT_PRIMITIVES_ARB 0x82F6 +#define GL_CLIPPING_OUTPUT_PRIMITIVES_ARB 0x82F7 +#endif /* GL_ARB_pipeline_statistics_query */ + +#ifndef GL_ARB_pixel_buffer_object +#define GL_ARB_pixel_buffer_object 1 +#define GL_PIXEL_PACK_BUFFER_ARB 0x88EB +#define GL_PIXEL_UNPACK_BUFFER_ARB 0x88EC +#define GL_PIXEL_PACK_BUFFER_BINDING_ARB 0x88ED +#define GL_PIXEL_UNPACK_BUFFER_BINDING_ARB 0x88EF +#endif /* GL_ARB_pixel_buffer_object */ + +#ifndef GL_ARB_point_parameters +#define GL_ARB_point_parameters 1 +#define GL_POINT_SIZE_MIN_ARB 0x8126 +#define GL_POINT_SIZE_MAX_ARB 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE_ARB 0x8128 +#define GL_POINT_DISTANCE_ATTENUATION_ARB 0x8129 +typedef void (APIENTRYP PFNGLPOINTPARAMETERFARBPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFVARBPROC) (GLenum pname, const GLfloat *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPointParameterfARB (GLenum pname, GLfloat param); +GLAPI void APIENTRY glPointParameterfvARB (GLenum pname, const GLfloat *params); +#endif +#endif /* GL_ARB_point_parameters */ + +#ifndef GL_ARB_point_sprite +#define GL_ARB_point_sprite 1 +#define GL_POINT_SPRITE_ARB 0x8861 +#define GL_COORD_REPLACE_ARB 0x8862 +#endif /* GL_ARB_point_sprite */ + +#ifndef GL_ARB_polygon_offset_clamp +#define GL_ARB_polygon_offset_clamp 1 +#endif /* GL_ARB_polygon_offset_clamp */ + +#ifndef GL_ARB_post_depth_coverage +#define GL_ARB_post_depth_coverage 1 +#endif /* GL_ARB_post_depth_coverage */ + +#ifndef GL_ARB_program_interface_query +#define GL_ARB_program_interface_query 1 +#endif /* GL_ARB_program_interface_query */ + +#ifndef GL_ARB_provoking_vertex +#define GL_ARB_provoking_vertex 1 +#endif /* GL_ARB_provoking_vertex */ + +#ifndef GL_ARB_query_buffer_object +#define GL_ARB_query_buffer_object 1 +#endif /* GL_ARB_query_buffer_object */ + +#ifndef GL_ARB_robust_buffer_access_behavior +#define GL_ARB_robust_buffer_access_behavior 1 +#endif /* GL_ARB_robust_buffer_access_behavior */ + +#ifndef GL_ARB_robustness +#define GL_ARB_robustness 1 +#define GL_CONTEXT_FLAG_ROBUST_ACCESS_BIT_ARB 0x00000004 +#define GL_LOSE_CONTEXT_ON_RESET_ARB 0x8252 +#define GL_GUILTY_CONTEXT_RESET_ARB 0x8253 +#define GL_INNOCENT_CONTEXT_RESET_ARB 0x8254 +#define GL_UNKNOWN_CONTEXT_RESET_ARB 0x8255 +#define GL_RESET_NOTIFICATION_STRATEGY_ARB 0x8256 +#define GL_NO_RESET_NOTIFICATION_ARB 0x8261 +typedef GLenum (APIENTRYP PFNGLGETGRAPHICSRESETSTATUSARBPROC) (void); +typedef void (APIENTRYP PFNGLGETNTEXIMAGEARBPROC) (GLenum target, GLint level, GLenum format, GLenum type, GLsizei bufSize, void *img); +typedef void (APIENTRYP PFNGLREADNPIXELSARBPROC) (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei bufSize, void *data); +typedef void (APIENTRYP PFNGLGETNCOMPRESSEDTEXIMAGEARBPROC) (GLenum target, GLint lod, GLsizei bufSize, void *img); +typedef void (APIENTRYP PFNGLGETNUNIFORMFVARBPROC) (GLuint program, GLint location, GLsizei bufSize, GLfloat *params); +typedef void (APIENTRYP PFNGLGETNUNIFORMIVARBPROC) (GLuint program, GLint location, GLsizei bufSize, GLint *params); +typedef void (APIENTRYP PFNGLGETNUNIFORMUIVARBPROC) (GLuint program, GLint location, GLsizei bufSize, GLuint *params); +typedef void (APIENTRYP PFNGLGETNUNIFORMDVARBPROC) (GLuint program, GLint location, GLsizei bufSize, GLdouble *params); +typedef void (APIENTRYP PFNGLGETNMAPDVARBPROC) (GLenum target, GLenum query, GLsizei bufSize, GLdouble *v); +typedef void (APIENTRYP PFNGLGETNMAPFVARBPROC) (GLenum target, GLenum query, GLsizei bufSize, GLfloat *v); +typedef void (APIENTRYP PFNGLGETNMAPIVARBPROC) (GLenum target, GLenum query, GLsizei bufSize, GLint *v); +typedef void (APIENTRYP PFNGLGETNPIXELMAPFVARBPROC) (GLenum map, GLsizei bufSize, GLfloat *values); +typedef void (APIENTRYP PFNGLGETNPIXELMAPUIVARBPROC) (GLenum map, GLsizei bufSize, GLuint *values); +typedef void (APIENTRYP PFNGLGETNPIXELMAPUSVARBPROC) (GLenum map, GLsizei bufSize, GLushort *values); +typedef void (APIENTRYP PFNGLGETNPOLYGONSTIPPLEARBPROC) (GLsizei bufSize, GLubyte *pattern); +typedef void (APIENTRYP PFNGLGETNCOLORTABLEARBPROC) (GLenum target, GLenum format, GLenum type, GLsizei bufSize, void *table); +typedef void (APIENTRYP PFNGLGETNCONVOLUTIONFILTERARBPROC) (GLenum target, GLenum format, GLenum type, GLsizei bufSize, void *image); +typedef void (APIENTRYP PFNGLGETNSEPARABLEFILTERARBPROC) (GLenum target, GLenum format, GLenum type, GLsizei rowBufSize, void *row, GLsizei columnBufSize, void *column, void *span); +typedef void (APIENTRYP PFNGLGETNHISTOGRAMARBPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLsizei bufSize, void *values); +typedef void (APIENTRYP PFNGLGETNMINMAXARBPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLsizei bufSize, void *values); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLenum APIENTRY glGetGraphicsResetStatusARB (void); +GLAPI void APIENTRY glGetnTexImageARB (GLenum target, GLint level, GLenum format, GLenum type, GLsizei bufSize, void *img); +GLAPI void APIENTRY glReadnPixelsARB (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei bufSize, void *data); +GLAPI void APIENTRY glGetnCompressedTexImageARB (GLenum target, GLint lod, GLsizei bufSize, void *img); +GLAPI void APIENTRY glGetnUniformfvARB (GLuint program, GLint location, GLsizei bufSize, GLfloat *params); +GLAPI void APIENTRY glGetnUniformivARB (GLuint program, GLint location, GLsizei bufSize, GLint *params); +GLAPI void APIENTRY glGetnUniformuivARB (GLuint program, GLint location, GLsizei bufSize, GLuint *params); +GLAPI void APIENTRY glGetnUniformdvARB (GLuint program, GLint location, GLsizei bufSize, GLdouble *params); +GLAPI void APIENTRY glGetnMapdvARB (GLenum target, GLenum query, GLsizei bufSize, GLdouble *v); +GLAPI void APIENTRY glGetnMapfvARB (GLenum target, GLenum query, GLsizei bufSize, GLfloat *v); +GLAPI void APIENTRY glGetnMapivARB (GLenum target, GLenum query, GLsizei bufSize, GLint *v); +GLAPI void APIENTRY glGetnPixelMapfvARB (GLenum map, GLsizei bufSize, GLfloat *values); +GLAPI void APIENTRY glGetnPixelMapuivARB (GLenum map, GLsizei bufSize, GLuint *values); +GLAPI void APIENTRY glGetnPixelMapusvARB (GLenum map, GLsizei bufSize, GLushort *values); +GLAPI void APIENTRY glGetnPolygonStippleARB (GLsizei bufSize, GLubyte *pattern); +GLAPI void APIENTRY glGetnColorTableARB (GLenum target, GLenum format, GLenum type, GLsizei bufSize, void *table); +GLAPI void APIENTRY glGetnConvolutionFilterARB (GLenum target, GLenum format, GLenum type, GLsizei bufSize, void *image); +GLAPI void APIENTRY glGetnSeparableFilterARB (GLenum target, GLenum format, GLenum type, GLsizei rowBufSize, void *row, GLsizei columnBufSize, void *column, void *span); +GLAPI void APIENTRY glGetnHistogramARB (GLenum target, GLboolean reset, GLenum format, GLenum type, GLsizei bufSize, void *values); +GLAPI void APIENTRY glGetnMinmaxARB (GLenum target, GLboolean reset, GLenum format, GLenum type, GLsizei bufSize, void *values); +#endif +#endif /* GL_ARB_robustness */ + +#ifndef GL_ARB_robustness_isolation +#define GL_ARB_robustness_isolation 1 +#endif /* GL_ARB_robustness_isolation */ + +#ifndef GL_ARB_sample_locations +#define GL_ARB_sample_locations 1 +#define GL_SAMPLE_LOCATION_SUBPIXEL_BITS_ARB 0x933D +#define GL_SAMPLE_LOCATION_PIXEL_GRID_WIDTH_ARB 0x933E +#define GL_SAMPLE_LOCATION_PIXEL_GRID_HEIGHT_ARB 0x933F +#define GL_PROGRAMMABLE_SAMPLE_LOCATION_TABLE_SIZE_ARB 0x9340 +#define GL_SAMPLE_LOCATION_ARB 0x8E50 +#define GL_PROGRAMMABLE_SAMPLE_LOCATION_ARB 0x9341 +#define GL_FRAMEBUFFER_PROGRAMMABLE_SAMPLE_LOCATIONS_ARB 0x9342 +#define GL_FRAMEBUFFER_SAMPLE_LOCATION_PIXEL_GRID_ARB 0x9343 +typedef void (APIENTRYP PFNGLFRAMEBUFFERSAMPLELOCATIONSFVARBPROC) (GLenum target, GLuint start, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERSAMPLELOCATIONSFVARBPROC) (GLuint framebuffer, GLuint start, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLEVALUATEDEPTHVALUESARBPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFramebufferSampleLocationsfvARB (GLenum target, GLuint start, GLsizei count, const GLfloat *v); +GLAPI void APIENTRY glNamedFramebufferSampleLocationsfvARB (GLuint framebuffer, GLuint start, GLsizei count, const GLfloat *v); +GLAPI void APIENTRY glEvaluateDepthValuesARB (void); +#endif +#endif /* GL_ARB_sample_locations */ + +#ifndef GL_ARB_sample_shading +#define GL_ARB_sample_shading 1 +#define GL_SAMPLE_SHADING_ARB 0x8C36 +#define GL_MIN_SAMPLE_SHADING_VALUE_ARB 0x8C37 +typedef void (APIENTRYP PFNGLMINSAMPLESHADINGARBPROC) (GLfloat value); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMinSampleShadingARB (GLfloat value); +#endif +#endif /* GL_ARB_sample_shading */ + +#ifndef GL_ARB_sampler_objects +#define GL_ARB_sampler_objects 1 +#endif /* GL_ARB_sampler_objects */ + +#ifndef GL_ARB_seamless_cube_map +#define GL_ARB_seamless_cube_map 1 +#endif /* GL_ARB_seamless_cube_map */ + +#ifndef GL_ARB_seamless_cubemap_per_texture +#define GL_ARB_seamless_cubemap_per_texture 1 +#endif /* GL_ARB_seamless_cubemap_per_texture */ + +#ifndef GL_ARB_separate_shader_objects +#define GL_ARB_separate_shader_objects 1 +#endif /* GL_ARB_separate_shader_objects */ + +#ifndef GL_ARB_shader_atomic_counter_ops +#define GL_ARB_shader_atomic_counter_ops 1 +#endif /* GL_ARB_shader_atomic_counter_ops */ + +#ifndef GL_ARB_shader_atomic_counters +#define GL_ARB_shader_atomic_counters 1 +#endif /* GL_ARB_shader_atomic_counters */ + +#ifndef GL_ARB_shader_ballot +#define GL_ARB_shader_ballot 1 +#endif /* GL_ARB_shader_ballot */ + +#ifndef GL_ARB_shader_bit_encoding +#define GL_ARB_shader_bit_encoding 1 +#endif /* GL_ARB_shader_bit_encoding */ + +#ifndef GL_ARB_shader_clock +#define GL_ARB_shader_clock 1 +#endif /* GL_ARB_shader_clock */ + +#ifndef GL_ARB_shader_draw_parameters +#define GL_ARB_shader_draw_parameters 1 +#endif /* GL_ARB_shader_draw_parameters */ + +#ifndef GL_ARB_shader_group_vote +#define GL_ARB_shader_group_vote 1 +#endif /* GL_ARB_shader_group_vote */ + +#ifndef GL_ARB_shader_image_load_store +#define GL_ARB_shader_image_load_store 1 +#endif /* GL_ARB_shader_image_load_store */ + +#ifndef GL_ARB_shader_image_size +#define GL_ARB_shader_image_size 1 +#endif /* GL_ARB_shader_image_size */ + +#ifndef GL_ARB_shader_objects +#define GL_ARB_shader_objects 1 +#ifdef __APPLE__ +typedef void *GLhandleARB; +#else +typedef unsigned int GLhandleARB; +#endif +typedef char GLcharARB; +#define GL_PROGRAM_OBJECT_ARB 0x8B40 +#define GL_SHADER_OBJECT_ARB 0x8B48 +#define GL_OBJECT_TYPE_ARB 0x8B4E +#define GL_OBJECT_SUBTYPE_ARB 0x8B4F +#define GL_FLOAT_VEC2_ARB 0x8B50 +#define GL_FLOAT_VEC3_ARB 0x8B51 +#define GL_FLOAT_VEC4_ARB 0x8B52 +#define GL_INT_VEC2_ARB 0x8B53 +#define GL_INT_VEC3_ARB 0x8B54 +#define GL_INT_VEC4_ARB 0x8B55 +#define GL_BOOL_ARB 0x8B56 +#define GL_BOOL_VEC2_ARB 0x8B57 +#define GL_BOOL_VEC3_ARB 0x8B58 +#define GL_BOOL_VEC4_ARB 0x8B59 +#define GL_FLOAT_MAT2_ARB 0x8B5A +#define GL_FLOAT_MAT3_ARB 0x8B5B +#define GL_FLOAT_MAT4_ARB 0x8B5C +#define GL_SAMPLER_1D_ARB 0x8B5D +#define GL_SAMPLER_2D_ARB 0x8B5E +#define GL_SAMPLER_3D_ARB 0x8B5F +#define GL_SAMPLER_CUBE_ARB 0x8B60 +#define GL_SAMPLER_1D_SHADOW_ARB 0x8B61 +#define GL_SAMPLER_2D_SHADOW_ARB 0x8B62 +#define GL_SAMPLER_2D_RECT_ARB 0x8B63 +#define GL_SAMPLER_2D_RECT_SHADOW_ARB 0x8B64 +#define GL_OBJECT_DELETE_STATUS_ARB 0x8B80 +#define GL_OBJECT_COMPILE_STATUS_ARB 0x8B81 +#define GL_OBJECT_LINK_STATUS_ARB 0x8B82 +#define GL_OBJECT_VALIDATE_STATUS_ARB 0x8B83 +#define GL_OBJECT_INFO_LOG_LENGTH_ARB 0x8B84 +#define GL_OBJECT_ATTACHED_OBJECTS_ARB 0x8B85 +#define GL_OBJECT_ACTIVE_UNIFORMS_ARB 0x8B86 +#define GL_OBJECT_ACTIVE_UNIFORM_MAX_LENGTH_ARB 0x8B87 +#define GL_OBJECT_SHADER_SOURCE_LENGTH_ARB 0x8B88 +typedef void (APIENTRYP PFNGLDELETEOBJECTARBPROC) (GLhandleARB obj); +typedef GLhandleARB (APIENTRYP PFNGLGETHANDLEARBPROC) (GLenum pname); +typedef void (APIENTRYP PFNGLDETACHOBJECTARBPROC) (GLhandleARB containerObj, GLhandleARB attachedObj); +typedef GLhandleARB (APIENTRYP PFNGLCREATESHADEROBJECTARBPROC) (GLenum shaderType); +typedef void (APIENTRYP PFNGLSHADERSOURCEARBPROC) (GLhandleARB shaderObj, GLsizei count, const GLcharARB **string, const GLint *length); +typedef void (APIENTRYP PFNGLCOMPILESHADERARBPROC) (GLhandleARB shaderObj); +typedef GLhandleARB (APIENTRYP PFNGLCREATEPROGRAMOBJECTARBPROC) (void); +typedef void (APIENTRYP PFNGLATTACHOBJECTARBPROC) (GLhandleARB containerObj, GLhandleARB obj); +typedef void (APIENTRYP PFNGLLINKPROGRAMARBPROC) (GLhandleARB programObj); +typedef void (APIENTRYP PFNGLUSEPROGRAMOBJECTARBPROC) (GLhandleARB programObj); +typedef void (APIENTRYP PFNGLVALIDATEPROGRAMARBPROC) (GLhandleARB programObj); +typedef void (APIENTRYP PFNGLUNIFORM1FARBPROC) (GLint location, GLfloat v0); +typedef void (APIENTRYP PFNGLUNIFORM2FARBPROC) (GLint location, GLfloat v0, GLfloat v1); +typedef void (APIENTRYP PFNGLUNIFORM3FARBPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +typedef void (APIENTRYP PFNGLUNIFORM4FARBPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +typedef void (APIENTRYP PFNGLUNIFORM1IARBPROC) (GLint location, GLint v0); +typedef void (APIENTRYP PFNGLUNIFORM2IARBPROC) (GLint location, GLint v0, GLint v1); +typedef void (APIENTRYP PFNGLUNIFORM3IARBPROC) (GLint location, GLint v0, GLint v1, GLint v2); +typedef void (APIENTRYP PFNGLUNIFORM4IARBPROC) (GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +typedef void (APIENTRYP PFNGLUNIFORM1FVARBPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM2FVARBPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM3FVARBPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM4FVARBPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM1IVARBPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM2IVARBPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM3IVARBPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM4IVARBPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2FVARBPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3FVARBPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4FVARBPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLGETOBJECTPARAMETERFVARBPROC) (GLhandleARB obj, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETOBJECTPARAMETERIVARBPROC) (GLhandleARB obj, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETINFOLOGARBPROC) (GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *infoLog); +typedef void (APIENTRYP PFNGLGETATTACHEDOBJECTSARBPROC) (GLhandleARB containerObj, GLsizei maxCount, GLsizei *count, GLhandleARB *obj); +typedef GLint (APIENTRYP PFNGLGETUNIFORMLOCATIONARBPROC) (GLhandleARB programObj, const GLcharARB *name); +typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMARBPROC) (GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLcharARB *name); +typedef void (APIENTRYP PFNGLGETUNIFORMFVARBPROC) (GLhandleARB programObj, GLint location, GLfloat *params); +typedef void (APIENTRYP PFNGLGETUNIFORMIVARBPROC) (GLhandleARB programObj, GLint location, GLint *params); +typedef void (APIENTRYP PFNGLGETSHADERSOURCEARBPROC) (GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *source); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDeleteObjectARB (GLhandleARB obj); +GLAPI GLhandleARB APIENTRY glGetHandleARB (GLenum pname); +GLAPI void APIENTRY glDetachObjectARB (GLhandleARB containerObj, GLhandleARB attachedObj); +GLAPI GLhandleARB APIENTRY glCreateShaderObjectARB (GLenum shaderType); +GLAPI void APIENTRY glShaderSourceARB (GLhandleARB shaderObj, GLsizei count, const GLcharARB **string, const GLint *length); +GLAPI void APIENTRY glCompileShaderARB (GLhandleARB shaderObj); +GLAPI GLhandleARB APIENTRY glCreateProgramObjectARB (void); +GLAPI void APIENTRY glAttachObjectARB (GLhandleARB containerObj, GLhandleARB obj); +GLAPI void APIENTRY glLinkProgramARB (GLhandleARB programObj); +GLAPI void APIENTRY glUseProgramObjectARB (GLhandleARB programObj); +GLAPI void APIENTRY glValidateProgramARB (GLhandleARB programObj); +GLAPI void APIENTRY glUniform1fARB (GLint location, GLfloat v0); +GLAPI void APIENTRY glUniform2fARB (GLint location, GLfloat v0, GLfloat v1); +GLAPI void APIENTRY glUniform3fARB (GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +GLAPI void APIENTRY glUniform4fARB (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +GLAPI void APIENTRY glUniform1iARB (GLint location, GLint v0); +GLAPI void APIENTRY glUniform2iARB (GLint location, GLint v0, GLint v1); +GLAPI void APIENTRY glUniform3iARB (GLint location, GLint v0, GLint v1, GLint v2); +GLAPI void APIENTRY glUniform4iARB (GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +GLAPI void APIENTRY glUniform1fvARB (GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glUniform2fvARB (GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glUniform3fvARB (GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glUniform4fvARB (GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glUniform1ivARB (GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glUniform2ivARB (GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glUniform3ivARB (GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glUniform4ivARB (GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glUniformMatrix2fvARB (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix3fvARB (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glUniformMatrix4fvARB (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glGetObjectParameterfvARB (GLhandleARB obj, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetObjectParameterivARB (GLhandleARB obj, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetInfoLogARB (GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *infoLog); +GLAPI void APIENTRY glGetAttachedObjectsARB (GLhandleARB containerObj, GLsizei maxCount, GLsizei *count, GLhandleARB *obj); +GLAPI GLint APIENTRY glGetUniformLocationARB (GLhandleARB programObj, const GLcharARB *name); +GLAPI void APIENTRY glGetActiveUniformARB (GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLcharARB *name); +GLAPI void APIENTRY glGetUniformfvARB (GLhandleARB programObj, GLint location, GLfloat *params); +GLAPI void APIENTRY glGetUniformivARB (GLhandleARB programObj, GLint location, GLint *params); +GLAPI void APIENTRY glGetShaderSourceARB (GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *source); +#endif +#endif /* GL_ARB_shader_objects */ + +#ifndef GL_ARB_shader_precision +#define GL_ARB_shader_precision 1 +#endif /* GL_ARB_shader_precision */ + +#ifndef GL_ARB_shader_stencil_export +#define GL_ARB_shader_stencil_export 1 +#endif /* GL_ARB_shader_stencil_export */ + +#ifndef GL_ARB_shader_storage_buffer_object +#define GL_ARB_shader_storage_buffer_object 1 +#endif /* GL_ARB_shader_storage_buffer_object */ + +#ifndef GL_ARB_shader_subroutine +#define GL_ARB_shader_subroutine 1 +#endif /* GL_ARB_shader_subroutine */ + +#ifndef GL_ARB_shader_texture_image_samples +#define GL_ARB_shader_texture_image_samples 1 +#endif /* GL_ARB_shader_texture_image_samples */ + +#ifndef GL_ARB_shader_texture_lod +#define GL_ARB_shader_texture_lod 1 +#endif /* GL_ARB_shader_texture_lod */ + +#ifndef GL_ARB_shader_viewport_layer_array +#define GL_ARB_shader_viewport_layer_array 1 +#endif /* GL_ARB_shader_viewport_layer_array */ + +#ifndef GL_ARB_shading_language_100 +#define GL_ARB_shading_language_100 1 +#define GL_SHADING_LANGUAGE_VERSION_ARB 0x8B8C +#endif /* GL_ARB_shading_language_100 */ + +#ifndef GL_ARB_shading_language_420pack +#define GL_ARB_shading_language_420pack 1 +#endif /* GL_ARB_shading_language_420pack */ + +#ifndef GL_ARB_shading_language_include +#define GL_ARB_shading_language_include 1 +#define GL_SHADER_INCLUDE_ARB 0x8DAE +#define GL_NAMED_STRING_LENGTH_ARB 0x8DE9 +#define GL_NAMED_STRING_TYPE_ARB 0x8DEA +typedef void (APIENTRYP PFNGLNAMEDSTRINGARBPROC) (GLenum type, GLint namelen, const GLchar *name, GLint stringlen, const GLchar *string); +typedef void (APIENTRYP PFNGLDELETENAMEDSTRINGARBPROC) (GLint namelen, const GLchar *name); +typedef void (APIENTRYP PFNGLCOMPILESHADERINCLUDEARBPROC) (GLuint shader, GLsizei count, const GLchar *const*path, const GLint *length); +typedef GLboolean (APIENTRYP PFNGLISNAMEDSTRINGARBPROC) (GLint namelen, const GLchar *name); +typedef void (APIENTRYP PFNGLGETNAMEDSTRINGARBPROC) (GLint namelen, const GLchar *name, GLsizei bufSize, GLint *stringlen, GLchar *string); +typedef void (APIENTRYP PFNGLGETNAMEDSTRINGIVARBPROC) (GLint namelen, const GLchar *name, GLenum pname, GLint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glNamedStringARB (GLenum type, GLint namelen, const GLchar *name, GLint stringlen, const GLchar *string); +GLAPI void APIENTRY glDeleteNamedStringARB (GLint namelen, const GLchar *name); +GLAPI void APIENTRY glCompileShaderIncludeARB (GLuint shader, GLsizei count, const GLchar *const*path, const GLint *length); +GLAPI GLboolean APIENTRY glIsNamedStringARB (GLint namelen, const GLchar *name); +GLAPI void APIENTRY glGetNamedStringARB (GLint namelen, const GLchar *name, GLsizei bufSize, GLint *stringlen, GLchar *string); +GLAPI void APIENTRY glGetNamedStringivARB (GLint namelen, const GLchar *name, GLenum pname, GLint *params); +#endif +#endif /* GL_ARB_shading_language_include */ + +#ifndef GL_ARB_shading_language_packing +#define GL_ARB_shading_language_packing 1 +#endif /* GL_ARB_shading_language_packing */ + +#ifndef GL_ARB_shadow +#define GL_ARB_shadow 1 +#define GL_TEXTURE_COMPARE_MODE_ARB 0x884C +#define GL_TEXTURE_COMPARE_FUNC_ARB 0x884D +#define GL_COMPARE_R_TO_TEXTURE_ARB 0x884E +#endif /* GL_ARB_shadow */ + +#ifndef GL_ARB_shadow_ambient +#define GL_ARB_shadow_ambient 1 +#define GL_TEXTURE_COMPARE_FAIL_VALUE_ARB 0x80BF +#endif /* GL_ARB_shadow_ambient */ + +#ifndef GL_ARB_sparse_buffer +#define GL_ARB_sparse_buffer 1 +#define GL_SPARSE_STORAGE_BIT_ARB 0x0400 +#define GL_SPARSE_BUFFER_PAGE_SIZE_ARB 0x82F8 +typedef void (APIENTRYP PFNGLBUFFERPAGECOMMITMENTARBPROC) (GLenum target, GLintptr offset, GLsizeiptr size, GLboolean commit); +typedef void (APIENTRYP PFNGLNAMEDBUFFERPAGECOMMITMENTEXTPROC) (GLuint buffer, GLintptr offset, GLsizeiptr size, GLboolean commit); +typedef void (APIENTRYP PFNGLNAMEDBUFFERPAGECOMMITMENTARBPROC) (GLuint buffer, GLintptr offset, GLsizeiptr size, GLboolean commit); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBufferPageCommitmentARB (GLenum target, GLintptr offset, GLsizeiptr size, GLboolean commit); +GLAPI void APIENTRY glNamedBufferPageCommitmentEXT (GLuint buffer, GLintptr offset, GLsizeiptr size, GLboolean commit); +GLAPI void APIENTRY glNamedBufferPageCommitmentARB (GLuint buffer, GLintptr offset, GLsizeiptr size, GLboolean commit); +#endif +#endif /* GL_ARB_sparse_buffer */ + +#ifndef GL_ARB_sparse_texture +#define GL_ARB_sparse_texture 1 +#define GL_TEXTURE_SPARSE_ARB 0x91A6 +#define GL_VIRTUAL_PAGE_SIZE_INDEX_ARB 0x91A7 +#define GL_NUM_SPARSE_LEVELS_ARB 0x91AA +#define GL_NUM_VIRTUAL_PAGE_SIZES_ARB 0x91A8 +#define GL_VIRTUAL_PAGE_SIZE_X_ARB 0x9195 +#define GL_VIRTUAL_PAGE_SIZE_Y_ARB 0x9196 +#define GL_VIRTUAL_PAGE_SIZE_Z_ARB 0x9197 +#define GL_MAX_SPARSE_TEXTURE_SIZE_ARB 0x9198 +#define GL_MAX_SPARSE_3D_TEXTURE_SIZE_ARB 0x9199 +#define GL_MAX_SPARSE_ARRAY_TEXTURE_LAYERS_ARB 0x919A +#define GL_SPARSE_TEXTURE_FULL_ARRAY_CUBE_MIPMAPS_ARB 0x91A9 +typedef void (APIENTRYP PFNGLTEXPAGECOMMITMENTARBPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLboolean commit); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexPageCommitmentARB (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLboolean commit); +#endif +#endif /* GL_ARB_sparse_texture */ + +#ifndef GL_ARB_sparse_texture2 +#define GL_ARB_sparse_texture2 1 +#endif /* GL_ARB_sparse_texture2 */ + +#ifndef GL_ARB_sparse_texture_clamp +#define GL_ARB_sparse_texture_clamp 1 +#endif /* GL_ARB_sparse_texture_clamp */ + +#ifndef GL_ARB_spirv_extensions +#define GL_ARB_spirv_extensions 1 +#endif /* GL_ARB_spirv_extensions */ + +#ifndef GL_ARB_stencil_texturing +#define GL_ARB_stencil_texturing 1 +#endif /* GL_ARB_stencil_texturing */ + +#ifndef GL_ARB_sync +#define GL_ARB_sync 1 +#endif /* GL_ARB_sync */ + +#ifndef GL_ARB_tessellation_shader +#define GL_ARB_tessellation_shader 1 +#endif /* GL_ARB_tessellation_shader */ + +#ifndef GL_ARB_texture_barrier +#define GL_ARB_texture_barrier 1 +#endif /* GL_ARB_texture_barrier */ + +#ifndef GL_ARB_texture_border_clamp +#define GL_ARB_texture_border_clamp 1 +#define GL_CLAMP_TO_BORDER_ARB 0x812D +#endif /* GL_ARB_texture_border_clamp */ + +#ifndef GL_ARB_texture_buffer_object +#define GL_ARB_texture_buffer_object 1 +#define GL_TEXTURE_BUFFER_ARB 0x8C2A +#define GL_MAX_TEXTURE_BUFFER_SIZE_ARB 0x8C2B +#define GL_TEXTURE_BINDING_BUFFER_ARB 0x8C2C +#define GL_TEXTURE_BUFFER_DATA_STORE_BINDING_ARB 0x8C2D +#define GL_TEXTURE_BUFFER_FORMAT_ARB 0x8C2E +typedef void (APIENTRYP PFNGLTEXBUFFERARBPROC) (GLenum target, GLenum internalformat, GLuint buffer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexBufferARB (GLenum target, GLenum internalformat, GLuint buffer); +#endif +#endif /* GL_ARB_texture_buffer_object */ + +#ifndef GL_ARB_texture_buffer_object_rgb32 +#define GL_ARB_texture_buffer_object_rgb32 1 +#endif /* GL_ARB_texture_buffer_object_rgb32 */ + +#ifndef GL_ARB_texture_buffer_range +#define GL_ARB_texture_buffer_range 1 +#endif /* GL_ARB_texture_buffer_range */ + +#ifndef GL_ARB_texture_compression +#define GL_ARB_texture_compression 1 +#define GL_COMPRESSED_ALPHA_ARB 0x84E9 +#define GL_COMPRESSED_LUMINANCE_ARB 0x84EA +#define GL_COMPRESSED_LUMINANCE_ALPHA_ARB 0x84EB +#define GL_COMPRESSED_INTENSITY_ARB 0x84EC +#define GL_COMPRESSED_RGB_ARB 0x84ED +#define GL_COMPRESSED_RGBA_ARB 0x84EE +#define GL_TEXTURE_COMPRESSION_HINT_ARB 0x84EF +#define GL_TEXTURE_COMPRESSED_IMAGE_SIZE_ARB 0x86A0 +#define GL_TEXTURE_COMPRESSED_ARB 0x86A1 +#define GL_NUM_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A2 +#define GL_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A3 +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE3DARBPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE2DARBPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE1DARBPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE3DARBPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE2DARBPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE1DARBPROC) (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *data); +typedef void (APIENTRYP PFNGLGETCOMPRESSEDTEXIMAGEARBPROC) (GLenum target, GLint level, void *img); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCompressedTexImage3DARB (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *data); +GLAPI void APIENTRY glCompressedTexImage2DARB (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *data); +GLAPI void APIENTRY glCompressedTexImage1DARB (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void *data); +GLAPI void APIENTRY glCompressedTexSubImage3DARB (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *data); +GLAPI void APIENTRY glCompressedTexSubImage2DARB (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data); +GLAPI void APIENTRY glCompressedTexSubImage1DARB (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *data); +GLAPI void APIENTRY glGetCompressedTexImageARB (GLenum target, GLint level, void *img); +#endif +#endif /* GL_ARB_texture_compression */ + +#ifndef GL_ARB_texture_compression_bptc +#define GL_ARB_texture_compression_bptc 1 +#define GL_COMPRESSED_RGBA_BPTC_UNORM_ARB 0x8E8C +#define GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB 0x8E8D +#define GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB 0x8E8E +#define GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB 0x8E8F +#endif /* GL_ARB_texture_compression_bptc */ + +#ifndef GL_ARB_texture_compression_rgtc +#define GL_ARB_texture_compression_rgtc 1 +#endif /* GL_ARB_texture_compression_rgtc */ + +#ifndef GL_ARB_texture_cube_map +#define GL_ARB_texture_cube_map 1 +#define GL_NORMAL_MAP_ARB 0x8511 +#define GL_REFLECTION_MAP_ARB 0x8512 +#define GL_TEXTURE_CUBE_MAP_ARB 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP_ARB 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP_ARB 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE_ARB 0x851C +#endif /* GL_ARB_texture_cube_map */ + +#ifndef GL_ARB_texture_cube_map_array +#define GL_ARB_texture_cube_map_array 1 +#define GL_TEXTURE_CUBE_MAP_ARRAY_ARB 0x9009 +#define GL_TEXTURE_BINDING_CUBE_MAP_ARRAY_ARB 0x900A +#define GL_PROXY_TEXTURE_CUBE_MAP_ARRAY_ARB 0x900B +#define GL_SAMPLER_CUBE_MAP_ARRAY_ARB 0x900C +#define GL_SAMPLER_CUBE_MAP_ARRAY_SHADOW_ARB 0x900D +#define GL_INT_SAMPLER_CUBE_MAP_ARRAY_ARB 0x900E +#define GL_UNSIGNED_INT_SAMPLER_CUBE_MAP_ARRAY_ARB 0x900F +#endif /* GL_ARB_texture_cube_map_array */ + +#ifndef GL_ARB_texture_env_add +#define GL_ARB_texture_env_add 1 +#endif /* GL_ARB_texture_env_add */ + +#ifndef GL_ARB_texture_env_combine +#define GL_ARB_texture_env_combine 1 +#define GL_COMBINE_ARB 0x8570 +#define GL_COMBINE_RGB_ARB 0x8571 +#define GL_COMBINE_ALPHA_ARB 0x8572 +#define GL_SOURCE0_RGB_ARB 0x8580 +#define GL_SOURCE1_RGB_ARB 0x8581 +#define GL_SOURCE2_RGB_ARB 0x8582 +#define GL_SOURCE0_ALPHA_ARB 0x8588 +#define GL_SOURCE1_ALPHA_ARB 0x8589 +#define GL_SOURCE2_ALPHA_ARB 0x858A +#define GL_OPERAND0_RGB_ARB 0x8590 +#define GL_OPERAND1_RGB_ARB 0x8591 +#define GL_OPERAND2_RGB_ARB 0x8592 +#define GL_OPERAND0_ALPHA_ARB 0x8598 +#define GL_OPERAND1_ALPHA_ARB 0x8599 +#define GL_OPERAND2_ALPHA_ARB 0x859A +#define GL_RGB_SCALE_ARB 0x8573 +#define GL_ADD_SIGNED_ARB 0x8574 +#define GL_INTERPOLATE_ARB 0x8575 +#define GL_SUBTRACT_ARB 0x84E7 +#define GL_CONSTANT_ARB 0x8576 +#define GL_PRIMARY_COLOR_ARB 0x8577 +#define GL_PREVIOUS_ARB 0x8578 +#endif /* GL_ARB_texture_env_combine */ + +#ifndef GL_ARB_texture_env_crossbar +#define GL_ARB_texture_env_crossbar 1 +#endif /* GL_ARB_texture_env_crossbar */ + +#ifndef GL_ARB_texture_env_dot3 +#define GL_ARB_texture_env_dot3 1 +#define GL_DOT3_RGB_ARB 0x86AE +#define GL_DOT3_RGBA_ARB 0x86AF +#endif /* GL_ARB_texture_env_dot3 */ + +#ifndef GL_ARB_texture_filter_anisotropic +#define GL_ARB_texture_filter_anisotropic 1 +#endif /* GL_ARB_texture_filter_anisotropic */ + +#ifndef GL_ARB_texture_filter_minmax +#define GL_ARB_texture_filter_minmax 1 +#define GL_TEXTURE_REDUCTION_MODE_ARB 0x9366 +#define GL_WEIGHTED_AVERAGE_ARB 0x9367 +#endif /* GL_ARB_texture_filter_minmax */ + +#ifndef GL_ARB_texture_float +#define GL_ARB_texture_float 1 +#define GL_TEXTURE_RED_TYPE_ARB 0x8C10 +#define GL_TEXTURE_GREEN_TYPE_ARB 0x8C11 +#define GL_TEXTURE_BLUE_TYPE_ARB 0x8C12 +#define GL_TEXTURE_ALPHA_TYPE_ARB 0x8C13 +#define GL_TEXTURE_LUMINANCE_TYPE_ARB 0x8C14 +#define GL_TEXTURE_INTENSITY_TYPE_ARB 0x8C15 +#define GL_TEXTURE_DEPTH_TYPE_ARB 0x8C16 +#define GL_UNSIGNED_NORMALIZED_ARB 0x8C17 +#define GL_RGBA32F_ARB 0x8814 +#define GL_RGB32F_ARB 0x8815 +#define GL_ALPHA32F_ARB 0x8816 +#define GL_INTENSITY32F_ARB 0x8817 +#define GL_LUMINANCE32F_ARB 0x8818 +#define GL_LUMINANCE_ALPHA32F_ARB 0x8819 +#define GL_RGBA16F_ARB 0x881A +#define GL_RGB16F_ARB 0x881B +#define GL_ALPHA16F_ARB 0x881C +#define GL_INTENSITY16F_ARB 0x881D +#define GL_LUMINANCE16F_ARB 0x881E +#define GL_LUMINANCE_ALPHA16F_ARB 0x881F +#endif /* GL_ARB_texture_float */ + +#ifndef GL_ARB_texture_gather +#define GL_ARB_texture_gather 1 +#define GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET_ARB 0x8E5E +#define GL_MAX_PROGRAM_TEXTURE_GATHER_OFFSET_ARB 0x8E5F +#define GL_MAX_PROGRAM_TEXTURE_GATHER_COMPONENTS_ARB 0x8F9F +#endif /* GL_ARB_texture_gather */ + +#ifndef GL_ARB_texture_mirror_clamp_to_edge +#define GL_ARB_texture_mirror_clamp_to_edge 1 +#endif /* GL_ARB_texture_mirror_clamp_to_edge */ + +#ifndef GL_ARB_texture_mirrored_repeat +#define GL_ARB_texture_mirrored_repeat 1 +#define GL_MIRRORED_REPEAT_ARB 0x8370 +#endif /* GL_ARB_texture_mirrored_repeat */ + +#ifndef GL_ARB_texture_multisample +#define GL_ARB_texture_multisample 1 +#endif /* GL_ARB_texture_multisample */ + +#ifndef GL_ARB_texture_non_power_of_two +#define GL_ARB_texture_non_power_of_two 1 +#endif /* GL_ARB_texture_non_power_of_two */ + +#ifndef GL_ARB_texture_query_levels +#define GL_ARB_texture_query_levels 1 +#endif /* GL_ARB_texture_query_levels */ + +#ifndef GL_ARB_texture_query_lod +#define GL_ARB_texture_query_lod 1 +#endif /* GL_ARB_texture_query_lod */ + +#ifndef GL_ARB_texture_rectangle +#define GL_ARB_texture_rectangle 1 +#define GL_TEXTURE_RECTANGLE_ARB 0x84F5 +#define GL_TEXTURE_BINDING_RECTANGLE_ARB 0x84F6 +#define GL_PROXY_TEXTURE_RECTANGLE_ARB 0x84F7 +#define GL_MAX_RECTANGLE_TEXTURE_SIZE_ARB 0x84F8 +#endif /* GL_ARB_texture_rectangle */ + +#ifndef GL_ARB_texture_rg +#define GL_ARB_texture_rg 1 +#endif /* GL_ARB_texture_rg */ + +#ifndef GL_ARB_texture_rgb10_a2ui +#define GL_ARB_texture_rgb10_a2ui 1 +#endif /* GL_ARB_texture_rgb10_a2ui */ + +#ifndef GL_ARB_texture_stencil8 +#define GL_ARB_texture_stencil8 1 +#endif /* GL_ARB_texture_stencil8 */ + +#ifndef GL_ARB_texture_storage +#define GL_ARB_texture_storage 1 +#endif /* GL_ARB_texture_storage */ + +#ifndef GL_ARB_texture_storage_multisample +#define GL_ARB_texture_storage_multisample 1 +#endif /* GL_ARB_texture_storage_multisample */ + +#ifndef GL_ARB_texture_swizzle +#define GL_ARB_texture_swizzle 1 +#endif /* GL_ARB_texture_swizzle */ + +#ifndef GL_ARB_texture_view +#define GL_ARB_texture_view 1 +#endif /* GL_ARB_texture_view */ + +#ifndef GL_ARB_timer_query +#define GL_ARB_timer_query 1 +#endif /* GL_ARB_timer_query */ + +#ifndef GL_ARB_transform_feedback2 +#define GL_ARB_transform_feedback2 1 +#endif /* GL_ARB_transform_feedback2 */ + +#ifndef GL_ARB_transform_feedback3 +#define GL_ARB_transform_feedback3 1 +#endif /* GL_ARB_transform_feedback3 */ + +#ifndef GL_ARB_transform_feedback_instanced +#define GL_ARB_transform_feedback_instanced 1 +#endif /* GL_ARB_transform_feedback_instanced */ + +#ifndef GL_ARB_transform_feedback_overflow_query +#define GL_ARB_transform_feedback_overflow_query 1 +#define GL_TRANSFORM_FEEDBACK_OVERFLOW_ARB 0x82EC +#define GL_TRANSFORM_FEEDBACK_STREAM_OVERFLOW_ARB 0x82ED +#endif /* GL_ARB_transform_feedback_overflow_query */ + +#ifndef GL_ARB_transpose_matrix +#define GL_ARB_transpose_matrix 1 +#define GL_TRANSPOSE_MODELVIEW_MATRIX_ARB 0x84E3 +#define GL_TRANSPOSE_PROJECTION_MATRIX_ARB 0x84E4 +#define GL_TRANSPOSE_TEXTURE_MATRIX_ARB 0x84E5 +#define GL_TRANSPOSE_COLOR_MATRIX_ARB 0x84E6 +typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXFARBPROC) (const GLfloat *m); +typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXDARBPROC) (const GLdouble *m); +typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXFARBPROC) (const GLfloat *m); +typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXDARBPROC) (const GLdouble *m); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glLoadTransposeMatrixfARB (const GLfloat *m); +GLAPI void APIENTRY glLoadTransposeMatrixdARB (const GLdouble *m); +GLAPI void APIENTRY glMultTransposeMatrixfARB (const GLfloat *m); +GLAPI void APIENTRY glMultTransposeMatrixdARB (const GLdouble *m); +#endif +#endif /* GL_ARB_transpose_matrix */ + +#ifndef GL_ARB_uniform_buffer_object +#define GL_ARB_uniform_buffer_object 1 +#endif /* GL_ARB_uniform_buffer_object */ + +#ifndef GL_ARB_vertex_array_bgra +#define GL_ARB_vertex_array_bgra 1 +#endif /* GL_ARB_vertex_array_bgra */ + +#ifndef GL_ARB_vertex_array_object +#define GL_ARB_vertex_array_object 1 +#endif /* GL_ARB_vertex_array_object */ + +#ifndef GL_ARB_vertex_attrib_64bit +#define GL_ARB_vertex_attrib_64bit 1 +#endif /* GL_ARB_vertex_attrib_64bit */ + +#ifndef GL_ARB_vertex_attrib_binding +#define GL_ARB_vertex_attrib_binding 1 +#endif /* GL_ARB_vertex_attrib_binding */ + +#ifndef GL_ARB_vertex_blend +#define GL_ARB_vertex_blend 1 +#define GL_MAX_VERTEX_UNITS_ARB 0x86A4 +#define GL_ACTIVE_VERTEX_UNITS_ARB 0x86A5 +#define GL_WEIGHT_SUM_UNITY_ARB 0x86A6 +#define GL_VERTEX_BLEND_ARB 0x86A7 +#define GL_CURRENT_WEIGHT_ARB 0x86A8 +#define GL_WEIGHT_ARRAY_TYPE_ARB 0x86A9 +#define GL_WEIGHT_ARRAY_STRIDE_ARB 0x86AA +#define GL_WEIGHT_ARRAY_SIZE_ARB 0x86AB +#define GL_WEIGHT_ARRAY_POINTER_ARB 0x86AC +#define GL_WEIGHT_ARRAY_ARB 0x86AD +#define GL_MODELVIEW0_ARB 0x1700 +#define GL_MODELVIEW1_ARB 0x850A +#define GL_MODELVIEW2_ARB 0x8722 +#define GL_MODELVIEW3_ARB 0x8723 +#define GL_MODELVIEW4_ARB 0x8724 +#define GL_MODELVIEW5_ARB 0x8725 +#define GL_MODELVIEW6_ARB 0x8726 +#define GL_MODELVIEW7_ARB 0x8727 +#define GL_MODELVIEW8_ARB 0x8728 +#define GL_MODELVIEW9_ARB 0x8729 +#define GL_MODELVIEW10_ARB 0x872A +#define GL_MODELVIEW11_ARB 0x872B +#define GL_MODELVIEW12_ARB 0x872C +#define GL_MODELVIEW13_ARB 0x872D +#define GL_MODELVIEW14_ARB 0x872E +#define GL_MODELVIEW15_ARB 0x872F +#define GL_MODELVIEW16_ARB 0x8730 +#define GL_MODELVIEW17_ARB 0x8731 +#define GL_MODELVIEW18_ARB 0x8732 +#define GL_MODELVIEW19_ARB 0x8733 +#define GL_MODELVIEW20_ARB 0x8734 +#define GL_MODELVIEW21_ARB 0x8735 +#define GL_MODELVIEW22_ARB 0x8736 +#define GL_MODELVIEW23_ARB 0x8737 +#define GL_MODELVIEW24_ARB 0x8738 +#define GL_MODELVIEW25_ARB 0x8739 +#define GL_MODELVIEW26_ARB 0x873A +#define GL_MODELVIEW27_ARB 0x873B +#define GL_MODELVIEW28_ARB 0x873C +#define GL_MODELVIEW29_ARB 0x873D +#define GL_MODELVIEW30_ARB 0x873E +#define GL_MODELVIEW31_ARB 0x873F +typedef void (APIENTRYP PFNGLWEIGHTBVARBPROC) (GLint size, const GLbyte *weights); +typedef void (APIENTRYP PFNGLWEIGHTSVARBPROC) (GLint size, const GLshort *weights); +typedef void (APIENTRYP PFNGLWEIGHTIVARBPROC) (GLint size, const GLint *weights); +typedef void (APIENTRYP PFNGLWEIGHTFVARBPROC) (GLint size, const GLfloat *weights); +typedef void (APIENTRYP PFNGLWEIGHTDVARBPROC) (GLint size, const GLdouble *weights); +typedef void (APIENTRYP PFNGLWEIGHTUBVARBPROC) (GLint size, const GLubyte *weights); +typedef void (APIENTRYP PFNGLWEIGHTUSVARBPROC) (GLint size, const GLushort *weights); +typedef void (APIENTRYP PFNGLWEIGHTUIVARBPROC) (GLint size, const GLuint *weights); +typedef void (APIENTRYP PFNGLWEIGHTPOINTERARBPROC) (GLint size, GLenum type, GLsizei stride, const void *pointer); +typedef void (APIENTRYP PFNGLVERTEXBLENDARBPROC) (GLint count); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glWeightbvARB (GLint size, const GLbyte *weights); +GLAPI void APIENTRY glWeightsvARB (GLint size, const GLshort *weights); +GLAPI void APIENTRY glWeightivARB (GLint size, const GLint *weights); +GLAPI void APIENTRY glWeightfvARB (GLint size, const GLfloat *weights); +GLAPI void APIENTRY glWeightdvARB (GLint size, const GLdouble *weights); +GLAPI void APIENTRY glWeightubvARB (GLint size, const GLubyte *weights); +GLAPI void APIENTRY glWeightusvARB (GLint size, const GLushort *weights); +GLAPI void APIENTRY glWeightuivARB (GLint size, const GLuint *weights); +GLAPI void APIENTRY glWeightPointerARB (GLint size, GLenum type, GLsizei stride, const void *pointer); +GLAPI void APIENTRY glVertexBlendARB (GLint count); +#endif +#endif /* GL_ARB_vertex_blend */ + +#ifndef GL_ARB_vertex_buffer_object +#define GL_ARB_vertex_buffer_object 1 +typedef khronos_ssize_t GLsizeiptrARB; +typedef khronos_intptr_t GLintptrARB; +#define GL_BUFFER_SIZE_ARB 0x8764 +#define GL_BUFFER_USAGE_ARB 0x8765 +#define GL_ARRAY_BUFFER_ARB 0x8892 +#define GL_ELEMENT_ARRAY_BUFFER_ARB 0x8893 +#define GL_ARRAY_BUFFER_BINDING_ARB 0x8894 +#define GL_ELEMENT_ARRAY_BUFFER_BINDING_ARB 0x8895 +#define GL_VERTEX_ARRAY_BUFFER_BINDING_ARB 0x8896 +#define GL_NORMAL_ARRAY_BUFFER_BINDING_ARB 0x8897 +#define GL_COLOR_ARRAY_BUFFER_BINDING_ARB 0x8898 +#define GL_INDEX_ARRAY_BUFFER_BINDING_ARB 0x8899 +#define GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING_ARB 0x889A +#define GL_EDGE_FLAG_ARRAY_BUFFER_BINDING_ARB 0x889B +#define GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING_ARB 0x889C +#define GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING_ARB 0x889D +#define GL_WEIGHT_ARRAY_BUFFER_BINDING_ARB 0x889E +#define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING_ARB 0x889F +#define GL_READ_ONLY_ARB 0x88B8 +#define GL_WRITE_ONLY_ARB 0x88B9 +#define GL_READ_WRITE_ARB 0x88BA +#define GL_BUFFER_ACCESS_ARB 0x88BB +#define GL_BUFFER_MAPPED_ARB 0x88BC +#define GL_BUFFER_MAP_POINTER_ARB 0x88BD +#define GL_STREAM_DRAW_ARB 0x88E0 +#define GL_STREAM_READ_ARB 0x88E1 +#define GL_STREAM_COPY_ARB 0x88E2 +#define GL_STATIC_DRAW_ARB 0x88E4 +#define GL_STATIC_READ_ARB 0x88E5 +#define GL_STATIC_COPY_ARB 0x88E6 +#define GL_DYNAMIC_DRAW_ARB 0x88E8 +#define GL_DYNAMIC_READ_ARB 0x88E9 +#define GL_DYNAMIC_COPY_ARB 0x88EA +typedef void (APIENTRYP PFNGLBINDBUFFERARBPROC) (GLenum target, GLuint buffer); +typedef void (APIENTRYP PFNGLDELETEBUFFERSARBPROC) (GLsizei n, const GLuint *buffers); +typedef void (APIENTRYP PFNGLGENBUFFERSARBPROC) (GLsizei n, GLuint *buffers); +typedef GLboolean (APIENTRYP PFNGLISBUFFERARBPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLBUFFERDATAARBPROC) (GLenum target, GLsizeiptrARB size, const void *data, GLenum usage); +typedef void (APIENTRYP PFNGLBUFFERSUBDATAARBPROC) (GLenum target, GLintptrARB offset, GLsizeiptrARB size, const void *data); +typedef void (APIENTRYP PFNGLGETBUFFERSUBDATAARBPROC) (GLenum target, GLintptrARB offset, GLsizeiptrARB size, void *data); +typedef void *(APIENTRYP PFNGLMAPBUFFERARBPROC) (GLenum target, GLenum access); +typedef GLboolean (APIENTRYP PFNGLUNMAPBUFFERARBPROC) (GLenum target); +typedef void (APIENTRYP PFNGLGETBUFFERPARAMETERIVARBPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETBUFFERPOINTERVARBPROC) (GLenum target, GLenum pname, void **params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindBufferARB (GLenum target, GLuint buffer); +GLAPI void APIENTRY glDeleteBuffersARB (GLsizei n, const GLuint *buffers); +GLAPI void APIENTRY glGenBuffersARB (GLsizei n, GLuint *buffers); +GLAPI GLboolean APIENTRY glIsBufferARB (GLuint buffer); +GLAPI void APIENTRY glBufferDataARB (GLenum target, GLsizeiptrARB size, const void *data, GLenum usage); +GLAPI void APIENTRY glBufferSubDataARB (GLenum target, GLintptrARB offset, GLsizeiptrARB size, const void *data); +GLAPI void APIENTRY glGetBufferSubDataARB (GLenum target, GLintptrARB offset, GLsizeiptrARB size, void *data); +GLAPI void *APIENTRY glMapBufferARB (GLenum target, GLenum access); +GLAPI GLboolean APIENTRY glUnmapBufferARB (GLenum target); +GLAPI void APIENTRY glGetBufferParameterivARB (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetBufferPointervARB (GLenum target, GLenum pname, void **params); +#endif +#endif /* GL_ARB_vertex_buffer_object */ + +#ifndef GL_ARB_vertex_program +#define GL_ARB_vertex_program 1 +#define GL_COLOR_SUM_ARB 0x8458 +#define GL_VERTEX_PROGRAM_ARB 0x8620 +#define GL_VERTEX_ATTRIB_ARRAY_ENABLED_ARB 0x8622 +#define GL_VERTEX_ATTRIB_ARRAY_SIZE_ARB 0x8623 +#define GL_VERTEX_ATTRIB_ARRAY_STRIDE_ARB 0x8624 +#define GL_VERTEX_ATTRIB_ARRAY_TYPE_ARB 0x8625 +#define GL_CURRENT_VERTEX_ATTRIB_ARB 0x8626 +#define GL_VERTEX_PROGRAM_POINT_SIZE_ARB 0x8642 +#define GL_VERTEX_PROGRAM_TWO_SIDE_ARB 0x8643 +#define GL_VERTEX_ATTRIB_ARRAY_POINTER_ARB 0x8645 +#define GL_MAX_VERTEX_ATTRIBS_ARB 0x8869 +#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED_ARB 0x886A +#define GL_PROGRAM_ADDRESS_REGISTERS_ARB 0x88B0 +#define GL_MAX_PROGRAM_ADDRESS_REGISTERS_ARB 0x88B1 +#define GL_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB 0x88B2 +#define GL_MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB 0x88B3 +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DARBPROC) (GLuint index, GLdouble x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DVARBPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FARBPROC) (GLuint index, GLfloat x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FVARBPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SARBPROC) (GLuint index, GLshort x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DARBPROC) (GLuint index, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DVARBPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FARBPROC) (GLuint index, GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FVARBPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SARBPROC) (GLuint index, GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DARBPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DVARBPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FARBPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FVARBPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SARBPROC) (GLuint index, GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NBVARBPROC) (GLuint index, const GLbyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NIVARBPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NSVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUBARBPROC) (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUBVARBPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUIVARBPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUSVARBPROC) (GLuint index, const GLushort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4BVARBPROC) (GLuint index, const GLbyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DARBPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DVARBPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FARBPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FVARBPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4IVARBPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SARBPROC) (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UBVARBPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UIVARBPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4USVARBPROC) (GLuint index, const GLushort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBPOINTERARBPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer); +typedef void (APIENTRYP PFNGLENABLEVERTEXATTRIBARRAYARBPROC) (GLuint index); +typedef void (APIENTRYP PFNGLDISABLEVERTEXATTRIBARRAYARBPROC) (GLuint index); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBDVARBPROC) (GLuint index, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBFVARBPROC) (GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIVARBPROC) (GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBPOINTERVARBPROC) (GLuint index, GLenum pname, void **pointer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexAttrib1dARB (GLuint index, GLdouble x); +GLAPI void APIENTRY glVertexAttrib1dvARB (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib1fARB (GLuint index, GLfloat x); +GLAPI void APIENTRY glVertexAttrib1fvARB (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib1sARB (GLuint index, GLshort x); +GLAPI void APIENTRY glVertexAttrib1svARB (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib2dARB (GLuint index, GLdouble x, GLdouble y); +GLAPI void APIENTRY glVertexAttrib2dvARB (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib2fARB (GLuint index, GLfloat x, GLfloat y); +GLAPI void APIENTRY glVertexAttrib2fvARB (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib2sARB (GLuint index, GLshort x, GLshort y); +GLAPI void APIENTRY glVertexAttrib2svARB (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib3dARB (GLuint index, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glVertexAttrib3dvARB (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib3fARB (GLuint index, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glVertexAttrib3fvARB (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib3sARB (GLuint index, GLshort x, GLshort y, GLshort z); +GLAPI void APIENTRY glVertexAttrib3svARB (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib4NbvARB (GLuint index, const GLbyte *v); +GLAPI void APIENTRY glVertexAttrib4NivARB (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttrib4NsvARB (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib4NubARB (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +GLAPI void APIENTRY glVertexAttrib4NubvARB (GLuint index, const GLubyte *v); +GLAPI void APIENTRY glVertexAttrib4NuivARB (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttrib4NusvARB (GLuint index, const GLushort *v); +GLAPI void APIENTRY glVertexAttrib4bvARB (GLuint index, const GLbyte *v); +GLAPI void APIENTRY glVertexAttrib4dARB (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glVertexAttrib4dvARB (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib4fARB (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glVertexAttrib4fvARB (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib4ivARB (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttrib4sARB (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +GLAPI void APIENTRY glVertexAttrib4svARB (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib4ubvARB (GLuint index, const GLubyte *v); +GLAPI void APIENTRY glVertexAttrib4uivARB (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttrib4usvARB (GLuint index, const GLushort *v); +GLAPI void APIENTRY glVertexAttribPointerARB (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer); +GLAPI void APIENTRY glEnableVertexAttribArrayARB (GLuint index); +GLAPI void APIENTRY glDisableVertexAttribArrayARB (GLuint index); +GLAPI void APIENTRY glGetVertexAttribdvARB (GLuint index, GLenum pname, GLdouble *params); +GLAPI void APIENTRY glGetVertexAttribfvARB (GLuint index, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetVertexAttribivARB (GLuint index, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVertexAttribPointervARB (GLuint index, GLenum pname, void **pointer); +#endif +#endif /* GL_ARB_vertex_program */ + +#ifndef GL_ARB_vertex_shader +#define GL_ARB_vertex_shader 1 +#define GL_VERTEX_SHADER_ARB 0x8B31 +#define GL_MAX_VERTEX_UNIFORM_COMPONENTS_ARB 0x8B4A +#define GL_MAX_VARYING_FLOATS_ARB 0x8B4B +#define GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS_ARB 0x8B4C +#define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS_ARB 0x8B4D +#define GL_OBJECT_ACTIVE_ATTRIBUTES_ARB 0x8B89 +#define GL_OBJECT_ACTIVE_ATTRIBUTE_MAX_LENGTH_ARB 0x8B8A +typedef void (APIENTRYP PFNGLBINDATTRIBLOCATIONARBPROC) (GLhandleARB programObj, GLuint index, const GLcharARB *name); +typedef void (APIENTRYP PFNGLGETACTIVEATTRIBARBPROC) (GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLcharARB *name); +typedef GLint (APIENTRYP PFNGLGETATTRIBLOCATIONARBPROC) (GLhandleARB programObj, const GLcharARB *name); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindAttribLocationARB (GLhandleARB programObj, GLuint index, const GLcharARB *name); +GLAPI void APIENTRY glGetActiveAttribARB (GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLcharARB *name); +GLAPI GLint APIENTRY glGetAttribLocationARB (GLhandleARB programObj, const GLcharARB *name); +#endif +#endif /* GL_ARB_vertex_shader */ + +#ifndef GL_ARB_vertex_type_10f_11f_11f_rev +#define GL_ARB_vertex_type_10f_11f_11f_rev 1 +#endif /* GL_ARB_vertex_type_10f_11f_11f_rev */ + +#ifndef GL_ARB_vertex_type_2_10_10_10_rev +#define GL_ARB_vertex_type_2_10_10_10_rev 1 +#endif /* GL_ARB_vertex_type_2_10_10_10_rev */ + +#ifndef GL_ARB_viewport_array +#define GL_ARB_viewport_array 1 +typedef void (APIENTRYP PFNGLDEPTHRANGEARRAYDVNVPROC) (GLuint first, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLDEPTHRANGEINDEXEDDNVPROC) (GLuint index, GLdouble n, GLdouble f); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDepthRangeArraydvNV (GLuint first, GLsizei count, const GLdouble *v); +GLAPI void APIENTRY glDepthRangeIndexeddNV (GLuint index, GLdouble n, GLdouble f); +#endif +#endif /* GL_ARB_viewport_array */ + +#ifndef GL_ARB_window_pos +#define GL_ARB_window_pos 1 +typedef void (APIENTRYP PFNGLWINDOWPOS2DARBPROC) (GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLWINDOWPOS2DVARBPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2FARBPROC) (GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLWINDOWPOS2FVARBPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2IARBPROC) (GLint x, GLint y); +typedef void (APIENTRYP PFNGLWINDOWPOS2IVARBPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2SARBPROC) (GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLWINDOWPOS2SVARBPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3DARBPROC) (GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLWINDOWPOS3DVARBPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3FARBPROC) (GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLWINDOWPOS3FVARBPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3IARBPROC) (GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLWINDOWPOS3IVARBPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3SARBPROC) (GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLWINDOWPOS3SVARBPROC) (const GLshort *v); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glWindowPos2dARB (GLdouble x, GLdouble y); +GLAPI void APIENTRY glWindowPos2dvARB (const GLdouble *v); +GLAPI void APIENTRY glWindowPos2fARB (GLfloat x, GLfloat y); +GLAPI void APIENTRY glWindowPos2fvARB (const GLfloat *v); +GLAPI void APIENTRY glWindowPos2iARB (GLint x, GLint y); +GLAPI void APIENTRY glWindowPos2ivARB (const GLint *v); +GLAPI void APIENTRY glWindowPos2sARB (GLshort x, GLshort y); +GLAPI void APIENTRY glWindowPos2svARB (const GLshort *v); +GLAPI void APIENTRY glWindowPos3dARB (GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glWindowPos3dvARB (const GLdouble *v); +GLAPI void APIENTRY glWindowPos3fARB (GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glWindowPos3fvARB (const GLfloat *v); +GLAPI void APIENTRY glWindowPos3iARB (GLint x, GLint y, GLint z); +GLAPI void APIENTRY glWindowPos3ivARB (const GLint *v); +GLAPI void APIENTRY glWindowPos3sARB (GLshort x, GLshort y, GLshort z); +GLAPI void APIENTRY glWindowPos3svARB (const GLshort *v); +#endif +#endif /* GL_ARB_window_pos */ + +#ifndef GL_KHR_blend_equation_advanced +#define GL_KHR_blend_equation_advanced 1 +#define GL_MULTIPLY_KHR 0x9294 +#define GL_SCREEN_KHR 0x9295 +#define GL_OVERLAY_KHR 0x9296 +#define GL_DARKEN_KHR 0x9297 +#define GL_LIGHTEN_KHR 0x9298 +#define GL_COLORDODGE_KHR 0x9299 +#define GL_COLORBURN_KHR 0x929A +#define GL_HARDLIGHT_KHR 0x929B +#define GL_SOFTLIGHT_KHR 0x929C +#define GL_DIFFERENCE_KHR 0x929E +#define GL_EXCLUSION_KHR 0x92A0 +#define GL_HSL_HUE_KHR 0x92AD +#define GL_HSL_SATURATION_KHR 0x92AE +#define GL_HSL_COLOR_KHR 0x92AF +#define GL_HSL_LUMINOSITY_KHR 0x92B0 +typedef void (APIENTRYP PFNGLBLENDBARRIERKHRPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendBarrierKHR (void); +#endif +#endif /* GL_KHR_blend_equation_advanced */ + +#ifndef GL_KHR_blend_equation_advanced_coherent +#define GL_KHR_blend_equation_advanced_coherent 1 +#define GL_BLEND_ADVANCED_COHERENT_KHR 0x9285 +#endif /* GL_KHR_blend_equation_advanced_coherent */ + +#ifndef GL_KHR_context_flush_control +#define GL_KHR_context_flush_control 1 +#endif /* GL_KHR_context_flush_control */ + +#ifndef GL_KHR_debug +#define GL_KHR_debug 1 +#endif /* GL_KHR_debug */ + +#ifndef GL_KHR_no_error +#define GL_KHR_no_error 1 +#define GL_CONTEXT_FLAG_NO_ERROR_BIT_KHR 0x00000008 +#endif /* GL_KHR_no_error */ + +#ifndef GL_KHR_parallel_shader_compile +#define GL_KHR_parallel_shader_compile 1 +#define GL_MAX_SHADER_COMPILER_THREADS_KHR 0x91B0 +#define GL_COMPLETION_STATUS_KHR 0x91B1 +typedef void (APIENTRYP PFNGLMAXSHADERCOMPILERTHREADSKHRPROC) (GLuint count); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMaxShaderCompilerThreadsKHR (GLuint count); +#endif +#endif /* GL_KHR_parallel_shader_compile */ + +#ifndef GL_KHR_robust_buffer_access_behavior +#define GL_KHR_robust_buffer_access_behavior 1 +#endif /* GL_KHR_robust_buffer_access_behavior */ + +#ifndef GL_KHR_robustness +#define GL_KHR_robustness 1 +#define GL_CONTEXT_ROBUST_ACCESS 0x90F3 +#endif /* GL_KHR_robustness */ + +#ifndef GL_KHR_shader_subgroup +#define GL_KHR_shader_subgroup 1 +#define GL_SUBGROUP_SIZE_KHR 0x9532 +#define GL_SUBGROUP_SUPPORTED_STAGES_KHR 0x9533 +#define GL_SUBGROUP_SUPPORTED_FEATURES_KHR 0x9534 +#define GL_SUBGROUP_QUAD_ALL_STAGES_KHR 0x9535 +#define GL_SUBGROUP_FEATURE_BASIC_BIT_KHR 0x00000001 +#define GL_SUBGROUP_FEATURE_VOTE_BIT_KHR 0x00000002 +#define GL_SUBGROUP_FEATURE_ARITHMETIC_BIT_KHR 0x00000004 +#define GL_SUBGROUP_FEATURE_BALLOT_BIT_KHR 0x00000008 +#define GL_SUBGROUP_FEATURE_SHUFFLE_BIT_KHR 0x00000010 +#define GL_SUBGROUP_FEATURE_SHUFFLE_RELATIVE_BIT_KHR 0x00000020 +#define GL_SUBGROUP_FEATURE_CLUSTERED_BIT_KHR 0x00000040 +#define GL_SUBGROUP_FEATURE_QUAD_BIT_KHR 0x00000080 +#endif /* GL_KHR_shader_subgroup */ + +#ifndef GL_KHR_texture_compression_astc_hdr +#define GL_KHR_texture_compression_astc_hdr 1 +#define GL_COMPRESSED_RGBA_ASTC_4x4_KHR 0x93B0 +#define GL_COMPRESSED_RGBA_ASTC_5x4_KHR 0x93B1 +#define GL_COMPRESSED_RGBA_ASTC_5x5_KHR 0x93B2 +#define GL_COMPRESSED_RGBA_ASTC_6x5_KHR 0x93B3 +#define GL_COMPRESSED_RGBA_ASTC_6x6_KHR 0x93B4 +#define GL_COMPRESSED_RGBA_ASTC_8x5_KHR 0x93B5 +#define GL_COMPRESSED_RGBA_ASTC_8x6_KHR 0x93B6 +#define GL_COMPRESSED_RGBA_ASTC_8x8_KHR 0x93B7 +#define GL_COMPRESSED_RGBA_ASTC_10x5_KHR 0x93B8 +#define GL_COMPRESSED_RGBA_ASTC_10x6_KHR 0x93B9 +#define GL_COMPRESSED_RGBA_ASTC_10x8_KHR 0x93BA +#define GL_COMPRESSED_RGBA_ASTC_10x10_KHR 0x93BB +#define GL_COMPRESSED_RGBA_ASTC_12x10_KHR 0x93BC +#define GL_COMPRESSED_RGBA_ASTC_12x12_KHR 0x93BD +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR 0x93D0 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR 0x93D1 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR 0x93D2 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR 0x93D3 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR 0x93D4 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR 0x93D5 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR 0x93D6 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR 0x93D7 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR 0x93D8 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR 0x93D9 +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR 0x93DA +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR 0x93DB +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR 0x93DC +#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR 0x93DD +#endif /* GL_KHR_texture_compression_astc_hdr */ + +#ifndef GL_KHR_texture_compression_astc_ldr +#define GL_KHR_texture_compression_astc_ldr 1 +#endif /* GL_KHR_texture_compression_astc_ldr */ + +#ifndef GL_KHR_texture_compression_astc_sliced_3d +#define GL_KHR_texture_compression_astc_sliced_3d 1 +#endif /* GL_KHR_texture_compression_astc_sliced_3d */ + +#ifndef GL_OES_byte_coordinates +#define GL_OES_byte_coordinates 1 +typedef void (APIENTRYP PFNGLMULTITEXCOORD1BOESPROC) (GLenum texture, GLbyte s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1BVOESPROC) (GLenum texture, const GLbyte *coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2BOESPROC) (GLenum texture, GLbyte s, GLbyte t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2BVOESPROC) (GLenum texture, const GLbyte *coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3BOESPROC) (GLenum texture, GLbyte s, GLbyte t, GLbyte r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3BVOESPROC) (GLenum texture, const GLbyte *coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4BOESPROC) (GLenum texture, GLbyte s, GLbyte t, GLbyte r, GLbyte q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4BVOESPROC) (GLenum texture, const GLbyte *coords); +typedef void (APIENTRYP PFNGLTEXCOORD1BOESPROC) (GLbyte s); +typedef void (APIENTRYP PFNGLTEXCOORD1BVOESPROC) (const GLbyte *coords); +typedef void (APIENTRYP PFNGLTEXCOORD2BOESPROC) (GLbyte s, GLbyte t); +typedef void (APIENTRYP PFNGLTEXCOORD2BVOESPROC) (const GLbyte *coords); +typedef void (APIENTRYP PFNGLTEXCOORD3BOESPROC) (GLbyte s, GLbyte t, GLbyte r); +typedef void (APIENTRYP PFNGLTEXCOORD3BVOESPROC) (const GLbyte *coords); +typedef void (APIENTRYP PFNGLTEXCOORD4BOESPROC) (GLbyte s, GLbyte t, GLbyte r, GLbyte q); +typedef void (APIENTRYP PFNGLTEXCOORD4BVOESPROC) (const GLbyte *coords); +typedef void (APIENTRYP PFNGLVERTEX2BOESPROC) (GLbyte x, GLbyte y); +typedef void (APIENTRYP PFNGLVERTEX2BVOESPROC) (const GLbyte *coords); +typedef void (APIENTRYP PFNGLVERTEX3BOESPROC) (GLbyte x, GLbyte y, GLbyte z); +typedef void (APIENTRYP PFNGLVERTEX3BVOESPROC) (const GLbyte *coords); +typedef void (APIENTRYP PFNGLVERTEX4BOESPROC) (GLbyte x, GLbyte y, GLbyte z, GLbyte w); +typedef void (APIENTRYP PFNGLVERTEX4BVOESPROC) (const GLbyte *coords); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMultiTexCoord1bOES (GLenum texture, GLbyte s); +GLAPI void APIENTRY glMultiTexCoord1bvOES (GLenum texture, const GLbyte *coords); +GLAPI void APIENTRY glMultiTexCoord2bOES (GLenum texture, GLbyte s, GLbyte t); +GLAPI void APIENTRY glMultiTexCoord2bvOES (GLenum texture, const GLbyte *coords); +GLAPI void APIENTRY glMultiTexCoord3bOES (GLenum texture, GLbyte s, GLbyte t, GLbyte r); +GLAPI void APIENTRY glMultiTexCoord3bvOES (GLenum texture, const GLbyte *coords); +GLAPI void APIENTRY glMultiTexCoord4bOES (GLenum texture, GLbyte s, GLbyte t, GLbyte r, GLbyte q); +GLAPI void APIENTRY glMultiTexCoord4bvOES (GLenum texture, const GLbyte *coords); +GLAPI void APIENTRY glTexCoord1bOES (GLbyte s); +GLAPI void APIENTRY glTexCoord1bvOES (const GLbyte *coords); +GLAPI void APIENTRY glTexCoord2bOES (GLbyte s, GLbyte t); +GLAPI void APIENTRY glTexCoord2bvOES (const GLbyte *coords); +GLAPI void APIENTRY glTexCoord3bOES (GLbyte s, GLbyte t, GLbyte r); +GLAPI void APIENTRY glTexCoord3bvOES (const GLbyte *coords); +GLAPI void APIENTRY glTexCoord4bOES (GLbyte s, GLbyte t, GLbyte r, GLbyte q); +GLAPI void APIENTRY glTexCoord4bvOES (const GLbyte *coords); +GLAPI void APIENTRY glVertex2bOES (GLbyte x, GLbyte y); +GLAPI void APIENTRY glVertex2bvOES (const GLbyte *coords); +GLAPI void APIENTRY glVertex3bOES (GLbyte x, GLbyte y, GLbyte z); +GLAPI void APIENTRY glVertex3bvOES (const GLbyte *coords); +GLAPI void APIENTRY glVertex4bOES (GLbyte x, GLbyte y, GLbyte z, GLbyte w); +GLAPI void APIENTRY glVertex4bvOES (const GLbyte *coords); +#endif +#endif /* GL_OES_byte_coordinates */ + +#ifndef GL_OES_compressed_paletted_texture +#define GL_OES_compressed_paletted_texture 1 +#define GL_PALETTE4_RGB8_OES 0x8B90 +#define GL_PALETTE4_RGBA8_OES 0x8B91 +#define GL_PALETTE4_R5_G6_B5_OES 0x8B92 +#define GL_PALETTE4_RGBA4_OES 0x8B93 +#define GL_PALETTE4_RGB5_A1_OES 0x8B94 +#define GL_PALETTE8_RGB8_OES 0x8B95 +#define GL_PALETTE8_RGBA8_OES 0x8B96 +#define GL_PALETTE8_R5_G6_B5_OES 0x8B97 +#define GL_PALETTE8_RGBA4_OES 0x8B98 +#define GL_PALETTE8_RGB5_A1_OES 0x8B99 +#endif /* GL_OES_compressed_paletted_texture */ + +#ifndef GL_OES_fixed_point +#define GL_OES_fixed_point 1 +typedef khronos_int32_t GLfixed; +#define GL_FIXED_OES 0x140C +typedef void (APIENTRYP PFNGLALPHAFUNCXOESPROC) (GLenum func, GLfixed ref); +typedef void (APIENTRYP PFNGLCLEARCOLORXOESPROC) (GLfixed red, GLfixed green, GLfixed blue, GLfixed alpha); +typedef void (APIENTRYP PFNGLCLEARDEPTHXOESPROC) (GLfixed depth); +typedef void (APIENTRYP PFNGLCLIPPLANEXOESPROC) (GLenum plane, const GLfixed *equation); +typedef void (APIENTRYP PFNGLCOLOR4XOESPROC) (GLfixed red, GLfixed green, GLfixed blue, GLfixed alpha); +typedef void (APIENTRYP PFNGLDEPTHRANGEXOESPROC) (GLfixed n, GLfixed f); +typedef void (APIENTRYP PFNGLFOGXOESPROC) (GLenum pname, GLfixed param); +typedef void (APIENTRYP PFNGLFOGXVOESPROC) (GLenum pname, const GLfixed *param); +typedef void (APIENTRYP PFNGLFRUSTUMXOESPROC) (GLfixed l, GLfixed r, GLfixed b, GLfixed t, GLfixed n, GLfixed f); +typedef void (APIENTRYP PFNGLGETCLIPPLANEXOESPROC) (GLenum plane, GLfixed *equation); +typedef void (APIENTRYP PFNGLGETFIXEDVOESPROC) (GLenum pname, GLfixed *params); +typedef void (APIENTRYP PFNGLGETTEXENVXVOESPROC) (GLenum target, GLenum pname, GLfixed *params); +typedef void (APIENTRYP PFNGLGETTEXPARAMETERXVOESPROC) (GLenum target, GLenum pname, GLfixed *params); +typedef void (APIENTRYP PFNGLLIGHTMODELXOESPROC) (GLenum pname, GLfixed param); +typedef void (APIENTRYP PFNGLLIGHTMODELXVOESPROC) (GLenum pname, const GLfixed *param); +typedef void (APIENTRYP PFNGLLIGHTXOESPROC) (GLenum light, GLenum pname, GLfixed param); +typedef void (APIENTRYP PFNGLLIGHTXVOESPROC) (GLenum light, GLenum pname, const GLfixed *params); +typedef void (APIENTRYP PFNGLLINEWIDTHXOESPROC) (GLfixed width); +typedef void (APIENTRYP PFNGLLOADMATRIXXOESPROC) (const GLfixed *m); +typedef void (APIENTRYP PFNGLMATERIALXOESPROC) (GLenum face, GLenum pname, GLfixed param); +typedef void (APIENTRYP PFNGLMATERIALXVOESPROC) (GLenum face, GLenum pname, const GLfixed *param); +typedef void (APIENTRYP PFNGLMULTMATRIXXOESPROC) (const GLfixed *m); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4XOESPROC) (GLenum texture, GLfixed s, GLfixed t, GLfixed r, GLfixed q); +typedef void (APIENTRYP PFNGLNORMAL3XOESPROC) (GLfixed nx, GLfixed ny, GLfixed nz); +typedef void (APIENTRYP PFNGLORTHOXOESPROC) (GLfixed l, GLfixed r, GLfixed b, GLfixed t, GLfixed n, GLfixed f); +typedef void (APIENTRYP PFNGLPOINTPARAMETERXVOESPROC) (GLenum pname, const GLfixed *params); +typedef void (APIENTRYP PFNGLPOINTSIZEXOESPROC) (GLfixed size); +typedef void (APIENTRYP PFNGLPOLYGONOFFSETXOESPROC) (GLfixed factor, GLfixed units); +typedef void (APIENTRYP PFNGLROTATEXOESPROC) (GLfixed angle, GLfixed x, GLfixed y, GLfixed z); +typedef void (APIENTRYP PFNGLSCALEXOESPROC) (GLfixed x, GLfixed y, GLfixed z); +typedef void (APIENTRYP PFNGLTEXENVXOESPROC) (GLenum target, GLenum pname, GLfixed param); +typedef void (APIENTRYP PFNGLTEXENVXVOESPROC) (GLenum target, GLenum pname, const GLfixed *params); +typedef void (APIENTRYP PFNGLTEXPARAMETERXOESPROC) (GLenum target, GLenum pname, GLfixed param); +typedef void (APIENTRYP PFNGLTEXPARAMETERXVOESPROC) (GLenum target, GLenum pname, const GLfixed *params); +typedef void (APIENTRYP PFNGLTRANSLATEXOESPROC) (GLfixed x, GLfixed y, GLfixed z); +typedef void (APIENTRYP PFNGLACCUMXOESPROC) (GLenum op, GLfixed value); +typedef void (APIENTRYP PFNGLBITMAPXOESPROC) (GLsizei width, GLsizei height, GLfixed xorig, GLfixed yorig, GLfixed xmove, GLfixed ymove, const GLubyte *bitmap); +typedef void (APIENTRYP PFNGLBLENDCOLORXOESPROC) (GLfixed red, GLfixed green, GLfixed blue, GLfixed alpha); +typedef void (APIENTRYP PFNGLCLEARACCUMXOESPROC) (GLfixed red, GLfixed green, GLfixed blue, GLfixed alpha); +typedef void (APIENTRYP PFNGLCOLOR3XOESPROC) (GLfixed red, GLfixed green, GLfixed blue); +typedef void (APIENTRYP PFNGLCOLOR3XVOESPROC) (const GLfixed *components); +typedef void (APIENTRYP PFNGLCOLOR4XVOESPROC) (const GLfixed *components); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERXOESPROC) (GLenum target, GLenum pname, GLfixed param); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERXVOESPROC) (GLenum target, GLenum pname, const GLfixed *params); +typedef void (APIENTRYP PFNGLEVALCOORD1XOESPROC) (GLfixed u); +typedef void (APIENTRYP PFNGLEVALCOORD1XVOESPROC) (const GLfixed *coords); +typedef void (APIENTRYP PFNGLEVALCOORD2XOESPROC) (GLfixed u, GLfixed v); +typedef void (APIENTRYP PFNGLEVALCOORD2XVOESPROC) (const GLfixed *coords); +typedef void (APIENTRYP PFNGLFEEDBACKBUFFERXOESPROC) (GLsizei n, GLenum type, const GLfixed *buffer); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONPARAMETERXVOESPROC) (GLenum target, GLenum pname, GLfixed *params); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPARAMETERXVOESPROC) (GLenum target, GLenum pname, GLfixed *params); +typedef void (APIENTRYP PFNGLGETLIGHTXOESPROC) (GLenum light, GLenum pname, GLfixed *params); +typedef void (APIENTRYP PFNGLGETMAPXVOESPROC) (GLenum target, GLenum query, GLfixed *v); +typedef void (APIENTRYP PFNGLGETMATERIALXOESPROC) (GLenum face, GLenum pname, GLfixed param); +typedef void (APIENTRYP PFNGLGETPIXELMAPXVPROC) (GLenum map, GLint size, GLfixed *values); +typedef void (APIENTRYP PFNGLGETTEXGENXVOESPROC) (GLenum coord, GLenum pname, GLfixed *params); +typedef void (APIENTRYP PFNGLGETTEXLEVELPARAMETERXVOESPROC) (GLenum target, GLint level, GLenum pname, GLfixed *params); +typedef void (APIENTRYP PFNGLINDEXXOESPROC) (GLfixed component); +typedef void (APIENTRYP PFNGLINDEXXVOESPROC) (const GLfixed *component); +typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXXOESPROC) (const GLfixed *m); +typedef void (APIENTRYP PFNGLMAP1XOESPROC) (GLenum target, GLfixed u1, GLfixed u2, GLint stride, GLint order, GLfixed points); +typedef void (APIENTRYP PFNGLMAP2XOESPROC) (GLenum target, GLfixed u1, GLfixed u2, GLint ustride, GLint uorder, GLfixed v1, GLfixed v2, GLint vstride, GLint vorder, GLfixed points); +typedef void (APIENTRYP PFNGLMAPGRID1XOESPROC) (GLint n, GLfixed u1, GLfixed u2); +typedef void (APIENTRYP PFNGLMAPGRID2XOESPROC) (GLint n, GLfixed u1, GLfixed u2, GLfixed v1, GLfixed v2); +typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXXOESPROC) (const GLfixed *m); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1XOESPROC) (GLenum texture, GLfixed s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1XVOESPROC) (GLenum texture, const GLfixed *coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2XOESPROC) (GLenum texture, GLfixed s, GLfixed t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2XVOESPROC) (GLenum texture, const GLfixed *coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3XOESPROC) (GLenum texture, GLfixed s, GLfixed t, GLfixed r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3XVOESPROC) (GLenum texture, const GLfixed *coords); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4XVOESPROC) (GLenum texture, const GLfixed *coords); +typedef void (APIENTRYP PFNGLNORMAL3XVOESPROC) (const GLfixed *coords); +typedef void (APIENTRYP PFNGLPASSTHROUGHXOESPROC) (GLfixed token); +typedef void (APIENTRYP PFNGLPIXELMAPXPROC) (GLenum map, GLint size, const GLfixed *values); +typedef void (APIENTRYP PFNGLPIXELSTOREXPROC) (GLenum pname, GLfixed param); +typedef void (APIENTRYP PFNGLPIXELTRANSFERXOESPROC) (GLenum pname, GLfixed param); +typedef void (APIENTRYP PFNGLPIXELZOOMXOESPROC) (GLfixed xfactor, GLfixed yfactor); +typedef void (APIENTRYP PFNGLPRIORITIZETEXTURESXOESPROC) (GLsizei n, const GLuint *textures, const GLfixed *priorities); +typedef void (APIENTRYP PFNGLRASTERPOS2XOESPROC) (GLfixed x, GLfixed y); +typedef void (APIENTRYP PFNGLRASTERPOS2XVOESPROC) (const GLfixed *coords); +typedef void (APIENTRYP PFNGLRASTERPOS3XOESPROC) (GLfixed x, GLfixed y, GLfixed z); +typedef void (APIENTRYP PFNGLRASTERPOS3XVOESPROC) (const GLfixed *coords); +typedef void (APIENTRYP PFNGLRASTERPOS4XOESPROC) (GLfixed x, GLfixed y, GLfixed z, GLfixed w); +typedef void (APIENTRYP PFNGLRASTERPOS4XVOESPROC) (const GLfixed *coords); +typedef void (APIENTRYP PFNGLRECTXOESPROC) (GLfixed x1, GLfixed y1, GLfixed x2, GLfixed y2); +typedef void (APIENTRYP PFNGLRECTXVOESPROC) (const GLfixed *v1, const GLfixed *v2); +typedef void (APIENTRYP PFNGLTEXCOORD1XOESPROC) (GLfixed s); +typedef void (APIENTRYP PFNGLTEXCOORD1XVOESPROC) (const GLfixed *coords); +typedef void (APIENTRYP PFNGLTEXCOORD2XOESPROC) (GLfixed s, GLfixed t); +typedef void (APIENTRYP PFNGLTEXCOORD2XVOESPROC) (const GLfixed *coords); +typedef void (APIENTRYP PFNGLTEXCOORD3XOESPROC) (GLfixed s, GLfixed t, GLfixed r); +typedef void (APIENTRYP PFNGLTEXCOORD3XVOESPROC) (const GLfixed *coords); +typedef void (APIENTRYP PFNGLTEXCOORD4XOESPROC) (GLfixed s, GLfixed t, GLfixed r, GLfixed q); +typedef void (APIENTRYP PFNGLTEXCOORD4XVOESPROC) (const GLfixed *coords); +typedef void (APIENTRYP PFNGLTEXGENXOESPROC) (GLenum coord, GLenum pname, GLfixed param); +typedef void (APIENTRYP PFNGLTEXGENXVOESPROC) (GLenum coord, GLenum pname, const GLfixed *params); +typedef void (APIENTRYP PFNGLVERTEX2XOESPROC) (GLfixed x); +typedef void (APIENTRYP PFNGLVERTEX2XVOESPROC) (const GLfixed *coords); +typedef void (APIENTRYP PFNGLVERTEX3XOESPROC) (GLfixed x, GLfixed y); +typedef void (APIENTRYP PFNGLVERTEX3XVOESPROC) (const GLfixed *coords); +typedef void (APIENTRYP PFNGLVERTEX4XOESPROC) (GLfixed x, GLfixed y, GLfixed z); +typedef void (APIENTRYP PFNGLVERTEX4XVOESPROC) (const GLfixed *coords); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glAlphaFuncxOES (GLenum func, GLfixed ref); +GLAPI void APIENTRY glClearColorxOES (GLfixed red, GLfixed green, GLfixed blue, GLfixed alpha); +GLAPI void APIENTRY glClearDepthxOES (GLfixed depth); +GLAPI void APIENTRY glClipPlanexOES (GLenum plane, const GLfixed *equation); +GLAPI void APIENTRY glColor4xOES (GLfixed red, GLfixed green, GLfixed blue, GLfixed alpha); +GLAPI void APIENTRY glDepthRangexOES (GLfixed n, GLfixed f); +GLAPI void APIENTRY glFogxOES (GLenum pname, GLfixed param); +GLAPI void APIENTRY glFogxvOES (GLenum pname, const GLfixed *param); +GLAPI void APIENTRY glFrustumxOES (GLfixed l, GLfixed r, GLfixed b, GLfixed t, GLfixed n, GLfixed f); +GLAPI void APIENTRY glGetClipPlanexOES (GLenum plane, GLfixed *equation); +GLAPI void APIENTRY glGetFixedvOES (GLenum pname, GLfixed *params); +GLAPI void APIENTRY glGetTexEnvxvOES (GLenum target, GLenum pname, GLfixed *params); +GLAPI void APIENTRY glGetTexParameterxvOES (GLenum target, GLenum pname, GLfixed *params); +GLAPI void APIENTRY glLightModelxOES (GLenum pname, GLfixed param); +GLAPI void APIENTRY glLightModelxvOES (GLenum pname, const GLfixed *param); +GLAPI void APIENTRY glLightxOES (GLenum light, GLenum pname, GLfixed param); +GLAPI void APIENTRY glLightxvOES (GLenum light, GLenum pname, const GLfixed *params); +GLAPI void APIENTRY glLineWidthxOES (GLfixed width); +GLAPI void APIENTRY glLoadMatrixxOES (const GLfixed *m); +GLAPI void APIENTRY glMaterialxOES (GLenum face, GLenum pname, GLfixed param); +GLAPI void APIENTRY glMaterialxvOES (GLenum face, GLenum pname, const GLfixed *param); +GLAPI void APIENTRY glMultMatrixxOES (const GLfixed *m); +GLAPI void APIENTRY glMultiTexCoord4xOES (GLenum texture, GLfixed s, GLfixed t, GLfixed r, GLfixed q); +GLAPI void APIENTRY glNormal3xOES (GLfixed nx, GLfixed ny, GLfixed nz); +GLAPI void APIENTRY glOrthoxOES (GLfixed l, GLfixed r, GLfixed b, GLfixed t, GLfixed n, GLfixed f); +GLAPI void APIENTRY glPointParameterxvOES (GLenum pname, const GLfixed *params); +GLAPI void APIENTRY glPointSizexOES (GLfixed size); +GLAPI void APIENTRY glPolygonOffsetxOES (GLfixed factor, GLfixed units); +GLAPI void APIENTRY glRotatexOES (GLfixed angle, GLfixed x, GLfixed y, GLfixed z); +GLAPI void APIENTRY glScalexOES (GLfixed x, GLfixed y, GLfixed z); +GLAPI void APIENTRY glTexEnvxOES (GLenum target, GLenum pname, GLfixed param); +GLAPI void APIENTRY glTexEnvxvOES (GLenum target, GLenum pname, const GLfixed *params); +GLAPI void APIENTRY glTexParameterxOES (GLenum target, GLenum pname, GLfixed param); +GLAPI void APIENTRY glTexParameterxvOES (GLenum target, GLenum pname, const GLfixed *params); +GLAPI void APIENTRY glTranslatexOES (GLfixed x, GLfixed y, GLfixed z); +GLAPI void APIENTRY glAccumxOES (GLenum op, GLfixed value); +GLAPI void APIENTRY glBitmapxOES (GLsizei width, GLsizei height, GLfixed xorig, GLfixed yorig, GLfixed xmove, GLfixed ymove, const GLubyte *bitmap); +GLAPI void APIENTRY glBlendColorxOES (GLfixed red, GLfixed green, GLfixed blue, GLfixed alpha); +GLAPI void APIENTRY glClearAccumxOES (GLfixed red, GLfixed green, GLfixed blue, GLfixed alpha); +GLAPI void APIENTRY glColor3xOES (GLfixed red, GLfixed green, GLfixed blue); +GLAPI void APIENTRY glColor3xvOES (const GLfixed *components); +GLAPI void APIENTRY glColor4xvOES (const GLfixed *components); +GLAPI void APIENTRY glConvolutionParameterxOES (GLenum target, GLenum pname, GLfixed param); +GLAPI void APIENTRY glConvolutionParameterxvOES (GLenum target, GLenum pname, const GLfixed *params); +GLAPI void APIENTRY glEvalCoord1xOES (GLfixed u); +GLAPI void APIENTRY glEvalCoord1xvOES (const GLfixed *coords); +GLAPI void APIENTRY glEvalCoord2xOES (GLfixed u, GLfixed v); +GLAPI void APIENTRY glEvalCoord2xvOES (const GLfixed *coords); +GLAPI void APIENTRY glFeedbackBufferxOES (GLsizei n, GLenum type, const GLfixed *buffer); +GLAPI void APIENTRY glGetConvolutionParameterxvOES (GLenum target, GLenum pname, GLfixed *params); +GLAPI void APIENTRY glGetHistogramParameterxvOES (GLenum target, GLenum pname, GLfixed *params); +GLAPI void APIENTRY glGetLightxOES (GLenum light, GLenum pname, GLfixed *params); +GLAPI void APIENTRY glGetMapxvOES (GLenum target, GLenum query, GLfixed *v); +GLAPI void APIENTRY glGetMaterialxOES (GLenum face, GLenum pname, GLfixed param); +GLAPI void APIENTRY glGetPixelMapxv (GLenum map, GLint size, GLfixed *values); +GLAPI void APIENTRY glGetTexGenxvOES (GLenum coord, GLenum pname, GLfixed *params); +GLAPI void APIENTRY glGetTexLevelParameterxvOES (GLenum target, GLint level, GLenum pname, GLfixed *params); +GLAPI void APIENTRY glIndexxOES (GLfixed component); +GLAPI void APIENTRY glIndexxvOES (const GLfixed *component); +GLAPI void APIENTRY glLoadTransposeMatrixxOES (const GLfixed *m); +GLAPI void APIENTRY glMap1xOES (GLenum target, GLfixed u1, GLfixed u2, GLint stride, GLint order, GLfixed points); +GLAPI void APIENTRY glMap2xOES (GLenum target, GLfixed u1, GLfixed u2, GLint ustride, GLint uorder, GLfixed v1, GLfixed v2, GLint vstride, GLint vorder, GLfixed points); +GLAPI void APIENTRY glMapGrid1xOES (GLint n, GLfixed u1, GLfixed u2); +GLAPI void APIENTRY glMapGrid2xOES (GLint n, GLfixed u1, GLfixed u2, GLfixed v1, GLfixed v2); +GLAPI void APIENTRY glMultTransposeMatrixxOES (const GLfixed *m); +GLAPI void APIENTRY glMultiTexCoord1xOES (GLenum texture, GLfixed s); +GLAPI void APIENTRY glMultiTexCoord1xvOES (GLenum texture, const GLfixed *coords); +GLAPI void APIENTRY glMultiTexCoord2xOES (GLenum texture, GLfixed s, GLfixed t); +GLAPI void APIENTRY glMultiTexCoord2xvOES (GLenum texture, const GLfixed *coords); +GLAPI void APIENTRY glMultiTexCoord3xOES (GLenum texture, GLfixed s, GLfixed t, GLfixed r); +GLAPI void APIENTRY glMultiTexCoord3xvOES (GLenum texture, const GLfixed *coords); +GLAPI void APIENTRY glMultiTexCoord4xvOES (GLenum texture, const GLfixed *coords); +GLAPI void APIENTRY glNormal3xvOES (const GLfixed *coords); +GLAPI void APIENTRY glPassThroughxOES (GLfixed token); +GLAPI void APIENTRY glPixelMapx (GLenum map, GLint size, const GLfixed *values); +GLAPI void APIENTRY glPixelStorex (GLenum pname, GLfixed param); +GLAPI void APIENTRY glPixelTransferxOES (GLenum pname, GLfixed param); +GLAPI void APIENTRY glPixelZoomxOES (GLfixed xfactor, GLfixed yfactor); +GLAPI void APIENTRY glPrioritizeTexturesxOES (GLsizei n, const GLuint *textures, const GLfixed *priorities); +GLAPI void APIENTRY glRasterPos2xOES (GLfixed x, GLfixed y); +GLAPI void APIENTRY glRasterPos2xvOES (const GLfixed *coords); +GLAPI void APIENTRY glRasterPos3xOES (GLfixed x, GLfixed y, GLfixed z); +GLAPI void APIENTRY glRasterPos3xvOES (const GLfixed *coords); +GLAPI void APIENTRY glRasterPos4xOES (GLfixed x, GLfixed y, GLfixed z, GLfixed w); +GLAPI void APIENTRY glRasterPos4xvOES (const GLfixed *coords); +GLAPI void APIENTRY glRectxOES (GLfixed x1, GLfixed y1, GLfixed x2, GLfixed y2); +GLAPI void APIENTRY glRectxvOES (const GLfixed *v1, const GLfixed *v2); +GLAPI void APIENTRY glTexCoord1xOES (GLfixed s); +GLAPI void APIENTRY glTexCoord1xvOES (const GLfixed *coords); +GLAPI void APIENTRY glTexCoord2xOES (GLfixed s, GLfixed t); +GLAPI void APIENTRY glTexCoord2xvOES (const GLfixed *coords); +GLAPI void APIENTRY glTexCoord3xOES (GLfixed s, GLfixed t, GLfixed r); +GLAPI void APIENTRY glTexCoord3xvOES (const GLfixed *coords); +GLAPI void APIENTRY glTexCoord4xOES (GLfixed s, GLfixed t, GLfixed r, GLfixed q); +GLAPI void APIENTRY glTexCoord4xvOES (const GLfixed *coords); +GLAPI void APIENTRY glTexGenxOES (GLenum coord, GLenum pname, GLfixed param); +GLAPI void APIENTRY glTexGenxvOES (GLenum coord, GLenum pname, const GLfixed *params); +GLAPI void APIENTRY glVertex2xOES (GLfixed x); +GLAPI void APIENTRY glVertex2xvOES (const GLfixed *coords); +GLAPI void APIENTRY glVertex3xOES (GLfixed x, GLfixed y); +GLAPI void APIENTRY glVertex3xvOES (const GLfixed *coords); +GLAPI void APIENTRY glVertex4xOES (GLfixed x, GLfixed y, GLfixed z); +GLAPI void APIENTRY glVertex4xvOES (const GLfixed *coords); +#endif +#endif /* GL_OES_fixed_point */ + +#ifndef GL_OES_query_matrix +#define GL_OES_query_matrix 1 +typedef GLbitfield (APIENTRYP PFNGLQUERYMATRIXXOESPROC) (GLfixed *mantissa, GLint *exponent); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLbitfield APIENTRY glQueryMatrixxOES (GLfixed *mantissa, GLint *exponent); +#endif +#endif /* GL_OES_query_matrix */ + +#ifndef GL_OES_read_format +#define GL_OES_read_format 1 +#define GL_IMPLEMENTATION_COLOR_READ_TYPE_OES 0x8B9A +#define GL_IMPLEMENTATION_COLOR_READ_FORMAT_OES 0x8B9B +#endif /* GL_OES_read_format */ + +#ifndef GL_OES_single_precision +#define GL_OES_single_precision 1 +typedef void (APIENTRYP PFNGLCLEARDEPTHFOESPROC) (GLclampf depth); +typedef void (APIENTRYP PFNGLCLIPPLANEFOESPROC) (GLenum plane, const GLfloat *equation); +typedef void (APIENTRYP PFNGLDEPTHRANGEFOESPROC) (GLclampf n, GLclampf f); +typedef void (APIENTRYP PFNGLFRUSTUMFOESPROC) (GLfloat l, GLfloat r, GLfloat b, GLfloat t, GLfloat n, GLfloat f); +typedef void (APIENTRYP PFNGLGETCLIPPLANEFOESPROC) (GLenum plane, GLfloat *equation); +typedef void (APIENTRYP PFNGLORTHOFOESPROC) (GLfloat l, GLfloat r, GLfloat b, GLfloat t, GLfloat n, GLfloat f); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glClearDepthfOES (GLclampf depth); +GLAPI void APIENTRY glClipPlanefOES (GLenum plane, const GLfloat *equation); +GLAPI void APIENTRY glDepthRangefOES (GLclampf n, GLclampf f); +GLAPI void APIENTRY glFrustumfOES (GLfloat l, GLfloat r, GLfloat b, GLfloat t, GLfloat n, GLfloat f); +GLAPI void APIENTRY glGetClipPlanefOES (GLenum plane, GLfloat *equation); +GLAPI void APIENTRY glOrthofOES (GLfloat l, GLfloat r, GLfloat b, GLfloat t, GLfloat n, GLfloat f); +#endif +#endif /* GL_OES_single_precision */ + +#ifndef GL_3DFX_multisample +#define GL_3DFX_multisample 1 +#define GL_MULTISAMPLE_3DFX 0x86B2 +#define GL_SAMPLE_BUFFERS_3DFX 0x86B3 +#define GL_SAMPLES_3DFX 0x86B4 +#define GL_MULTISAMPLE_BIT_3DFX 0x20000000 +#endif /* GL_3DFX_multisample */ + +#ifndef GL_3DFX_tbuffer +#define GL_3DFX_tbuffer 1 +typedef void (APIENTRYP PFNGLTBUFFERMASK3DFXPROC) (GLuint mask); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTbufferMask3DFX (GLuint mask); +#endif +#endif /* GL_3DFX_tbuffer */ + +#ifndef GL_3DFX_texture_compression_FXT1 +#define GL_3DFX_texture_compression_FXT1 1 +#define GL_COMPRESSED_RGB_FXT1_3DFX 0x86B0 +#define GL_COMPRESSED_RGBA_FXT1_3DFX 0x86B1 +#endif /* GL_3DFX_texture_compression_FXT1 */ + +#ifndef GL_AMD_blend_minmax_factor +#define GL_AMD_blend_minmax_factor 1 +#define GL_FACTOR_MIN_AMD 0x901C +#define GL_FACTOR_MAX_AMD 0x901D +#endif /* GL_AMD_blend_minmax_factor */ + +#ifndef GL_AMD_conservative_depth +#define GL_AMD_conservative_depth 1 +#endif /* GL_AMD_conservative_depth */ + +#ifndef GL_AMD_debug_output +#define GL_AMD_debug_output 1 +typedef void (APIENTRY *GLDEBUGPROCAMD)(GLuint id,GLenum category,GLenum severity,GLsizei length,const GLchar *message,void *userParam); +#define GL_MAX_DEBUG_MESSAGE_LENGTH_AMD 0x9143 +#define GL_MAX_DEBUG_LOGGED_MESSAGES_AMD 0x9144 +#define GL_DEBUG_LOGGED_MESSAGES_AMD 0x9145 +#define GL_DEBUG_SEVERITY_HIGH_AMD 0x9146 +#define GL_DEBUG_SEVERITY_MEDIUM_AMD 0x9147 +#define GL_DEBUG_SEVERITY_LOW_AMD 0x9148 +#define GL_DEBUG_CATEGORY_API_ERROR_AMD 0x9149 +#define GL_DEBUG_CATEGORY_WINDOW_SYSTEM_AMD 0x914A +#define GL_DEBUG_CATEGORY_DEPRECATION_AMD 0x914B +#define GL_DEBUG_CATEGORY_UNDEFINED_BEHAVIOR_AMD 0x914C +#define GL_DEBUG_CATEGORY_PERFORMANCE_AMD 0x914D +#define GL_DEBUG_CATEGORY_SHADER_COMPILER_AMD 0x914E +#define GL_DEBUG_CATEGORY_APPLICATION_AMD 0x914F +#define GL_DEBUG_CATEGORY_OTHER_AMD 0x9150 +typedef void (APIENTRYP PFNGLDEBUGMESSAGEENABLEAMDPROC) (GLenum category, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled); +typedef void (APIENTRYP PFNGLDEBUGMESSAGEINSERTAMDPROC) (GLenum category, GLenum severity, GLuint id, GLsizei length, const GLchar *buf); +typedef void (APIENTRYP PFNGLDEBUGMESSAGECALLBACKAMDPROC) (GLDEBUGPROCAMD callback, void *userParam); +typedef GLuint (APIENTRYP PFNGLGETDEBUGMESSAGELOGAMDPROC) (GLuint count, GLsizei bufSize, GLenum *categories, GLuint *severities, GLuint *ids, GLsizei *lengths, GLchar *message); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDebugMessageEnableAMD (GLenum category, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled); +GLAPI void APIENTRY glDebugMessageInsertAMD (GLenum category, GLenum severity, GLuint id, GLsizei length, const GLchar *buf); +GLAPI void APIENTRY glDebugMessageCallbackAMD (GLDEBUGPROCAMD callback, void *userParam); +GLAPI GLuint APIENTRY glGetDebugMessageLogAMD (GLuint count, GLsizei bufSize, GLenum *categories, GLuint *severities, GLuint *ids, GLsizei *lengths, GLchar *message); +#endif +#endif /* GL_AMD_debug_output */ + +#ifndef GL_AMD_depth_clamp_separate +#define GL_AMD_depth_clamp_separate 1 +#define GL_DEPTH_CLAMP_NEAR_AMD 0x901E +#define GL_DEPTH_CLAMP_FAR_AMD 0x901F +#endif /* GL_AMD_depth_clamp_separate */ + +#ifndef GL_AMD_draw_buffers_blend +#define GL_AMD_draw_buffers_blend 1 +typedef void (APIENTRYP PFNGLBLENDFUNCINDEXEDAMDPROC) (GLuint buf, GLenum src, GLenum dst); +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEINDEXEDAMDPROC) (GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); +typedef void (APIENTRYP PFNGLBLENDEQUATIONINDEXEDAMDPROC) (GLuint buf, GLenum mode); +typedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEINDEXEDAMDPROC) (GLuint buf, GLenum modeRGB, GLenum modeAlpha); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendFuncIndexedAMD (GLuint buf, GLenum src, GLenum dst); +GLAPI void APIENTRY glBlendFuncSeparateIndexedAMD (GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); +GLAPI void APIENTRY glBlendEquationIndexedAMD (GLuint buf, GLenum mode); +GLAPI void APIENTRY glBlendEquationSeparateIndexedAMD (GLuint buf, GLenum modeRGB, GLenum modeAlpha); +#endif +#endif /* GL_AMD_draw_buffers_blend */ + +#ifndef GL_AMD_framebuffer_multisample_advanced +#define GL_AMD_framebuffer_multisample_advanced 1 +#define GL_RENDERBUFFER_STORAGE_SAMPLES_AMD 0x91B2 +#define GL_MAX_COLOR_FRAMEBUFFER_SAMPLES_AMD 0x91B3 +#define GL_MAX_COLOR_FRAMEBUFFER_STORAGE_SAMPLES_AMD 0x91B4 +#define GL_MAX_DEPTH_STENCIL_FRAMEBUFFER_SAMPLES_AMD 0x91B5 +#define GL_NUM_SUPPORTED_MULTISAMPLE_MODES_AMD 0x91B6 +#define GL_SUPPORTED_MULTISAMPLE_MODES_AMD 0x91B7 +typedef void (APIENTRYP PFNGLRENDERBUFFERSTORAGEMULTISAMPLEADVANCEDAMDPROC) (GLenum target, GLsizei samples, GLsizei storageSamples, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLNAMEDRENDERBUFFERSTORAGEMULTISAMPLEADVANCEDAMDPROC) (GLuint renderbuffer, GLsizei samples, GLsizei storageSamples, GLenum internalformat, GLsizei width, GLsizei height); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glRenderbufferStorageMultisampleAdvancedAMD (GLenum target, GLsizei samples, GLsizei storageSamples, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glNamedRenderbufferStorageMultisampleAdvancedAMD (GLuint renderbuffer, GLsizei samples, GLsizei storageSamples, GLenum internalformat, GLsizei width, GLsizei height); +#endif +#endif /* GL_AMD_framebuffer_multisample_advanced */ + +#ifndef GL_AMD_framebuffer_sample_positions +#define GL_AMD_framebuffer_sample_positions 1 +#define GL_SUBSAMPLE_DISTANCE_AMD 0x883F +#define GL_PIXELS_PER_SAMPLE_PATTERN_X_AMD 0x91AE +#define GL_PIXELS_PER_SAMPLE_PATTERN_Y_AMD 0x91AF +#define GL_ALL_PIXELS_AMD 0xFFFFFFFF +typedef void (APIENTRYP PFNGLFRAMEBUFFERSAMPLEPOSITIONSFVAMDPROC) (GLenum target, GLuint numsamples, GLuint pixelindex, const GLfloat *values); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERSAMPLEPOSITIONSFVAMDPROC) (GLuint framebuffer, GLuint numsamples, GLuint pixelindex, const GLfloat *values); +typedef void (APIENTRYP PFNGLGETFRAMEBUFFERPARAMETERFVAMDPROC) (GLenum target, GLenum pname, GLuint numsamples, GLuint pixelindex, GLsizei size, GLfloat *values); +typedef void (APIENTRYP PFNGLGETNAMEDFRAMEBUFFERPARAMETERFVAMDPROC) (GLuint framebuffer, GLenum pname, GLuint numsamples, GLuint pixelindex, GLsizei size, GLfloat *values); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFramebufferSamplePositionsfvAMD (GLenum target, GLuint numsamples, GLuint pixelindex, const GLfloat *values); +GLAPI void APIENTRY glNamedFramebufferSamplePositionsfvAMD (GLuint framebuffer, GLuint numsamples, GLuint pixelindex, const GLfloat *values); +GLAPI void APIENTRY glGetFramebufferParameterfvAMD (GLenum target, GLenum pname, GLuint numsamples, GLuint pixelindex, GLsizei size, GLfloat *values); +GLAPI void APIENTRY glGetNamedFramebufferParameterfvAMD (GLuint framebuffer, GLenum pname, GLuint numsamples, GLuint pixelindex, GLsizei size, GLfloat *values); +#endif +#endif /* GL_AMD_framebuffer_sample_positions */ + +#ifndef GL_AMD_gcn_shader +#define GL_AMD_gcn_shader 1 +#endif /* GL_AMD_gcn_shader */ + +#ifndef GL_AMD_gpu_shader_half_float +#define GL_AMD_gpu_shader_half_float 1 +#define GL_FLOAT16_NV 0x8FF8 +#define GL_FLOAT16_VEC2_NV 0x8FF9 +#define GL_FLOAT16_VEC3_NV 0x8FFA +#define GL_FLOAT16_VEC4_NV 0x8FFB +#define GL_FLOAT16_MAT2_AMD 0x91C5 +#define GL_FLOAT16_MAT3_AMD 0x91C6 +#define GL_FLOAT16_MAT4_AMD 0x91C7 +#define GL_FLOAT16_MAT2x3_AMD 0x91C8 +#define GL_FLOAT16_MAT2x4_AMD 0x91C9 +#define GL_FLOAT16_MAT3x2_AMD 0x91CA +#define GL_FLOAT16_MAT3x4_AMD 0x91CB +#define GL_FLOAT16_MAT4x2_AMD 0x91CC +#define GL_FLOAT16_MAT4x3_AMD 0x91CD +#endif /* GL_AMD_gpu_shader_half_float */ + +#ifndef GL_AMD_gpu_shader_int16 +#define GL_AMD_gpu_shader_int16 1 +#endif /* GL_AMD_gpu_shader_int16 */ + +#ifndef GL_AMD_gpu_shader_int64 +#define GL_AMD_gpu_shader_int64 1 +typedef khronos_int64_t GLint64EXT; +#define GL_INT64_NV 0x140E +#define GL_UNSIGNED_INT64_NV 0x140F +#define GL_INT8_NV 0x8FE0 +#define GL_INT8_VEC2_NV 0x8FE1 +#define GL_INT8_VEC3_NV 0x8FE2 +#define GL_INT8_VEC4_NV 0x8FE3 +#define GL_INT16_NV 0x8FE4 +#define GL_INT16_VEC2_NV 0x8FE5 +#define GL_INT16_VEC3_NV 0x8FE6 +#define GL_INT16_VEC4_NV 0x8FE7 +#define GL_INT64_VEC2_NV 0x8FE9 +#define GL_INT64_VEC3_NV 0x8FEA +#define GL_INT64_VEC4_NV 0x8FEB +#define GL_UNSIGNED_INT8_NV 0x8FEC +#define GL_UNSIGNED_INT8_VEC2_NV 0x8FED +#define GL_UNSIGNED_INT8_VEC3_NV 0x8FEE +#define GL_UNSIGNED_INT8_VEC4_NV 0x8FEF +#define GL_UNSIGNED_INT16_NV 0x8FF0 +#define GL_UNSIGNED_INT16_VEC2_NV 0x8FF1 +#define GL_UNSIGNED_INT16_VEC3_NV 0x8FF2 +#define GL_UNSIGNED_INT16_VEC4_NV 0x8FF3 +#define GL_UNSIGNED_INT64_VEC2_NV 0x8FF5 +#define GL_UNSIGNED_INT64_VEC3_NV 0x8FF6 +#define GL_UNSIGNED_INT64_VEC4_NV 0x8FF7 +typedef void (APIENTRYP PFNGLUNIFORM1I64NVPROC) (GLint location, GLint64EXT x); +typedef void (APIENTRYP PFNGLUNIFORM2I64NVPROC) (GLint location, GLint64EXT x, GLint64EXT y); +typedef void (APIENTRYP PFNGLUNIFORM3I64NVPROC) (GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z); +typedef void (APIENTRYP PFNGLUNIFORM4I64NVPROC) (GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z, GLint64EXT w); +typedef void (APIENTRYP PFNGLUNIFORM1I64VNVPROC) (GLint location, GLsizei count, const GLint64EXT *value); +typedef void (APIENTRYP PFNGLUNIFORM2I64VNVPROC) (GLint location, GLsizei count, const GLint64EXT *value); +typedef void (APIENTRYP PFNGLUNIFORM3I64VNVPROC) (GLint location, GLsizei count, const GLint64EXT *value); +typedef void (APIENTRYP PFNGLUNIFORM4I64VNVPROC) (GLint location, GLsizei count, const GLint64EXT *value); +typedef void (APIENTRYP PFNGLUNIFORM1UI64NVPROC) (GLint location, GLuint64EXT x); +typedef void (APIENTRYP PFNGLUNIFORM2UI64NVPROC) (GLint location, GLuint64EXT x, GLuint64EXT y); +typedef void (APIENTRYP PFNGLUNIFORM3UI64NVPROC) (GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z); +typedef void (APIENTRYP PFNGLUNIFORM4UI64NVPROC) (GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, GLuint64EXT w); +typedef void (APIENTRYP PFNGLUNIFORM1UI64VNVPROC) (GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (APIENTRYP PFNGLUNIFORM2UI64VNVPROC) (GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (APIENTRYP PFNGLUNIFORM3UI64VNVPROC) (GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (APIENTRYP PFNGLUNIFORM4UI64VNVPROC) (GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (APIENTRYP PFNGLGETUNIFORMI64VNVPROC) (GLuint program, GLint location, GLint64EXT *params); +typedef void (APIENTRYP PFNGLGETUNIFORMUI64VNVPROC) (GLuint program, GLint location, GLuint64EXT *params); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1I64NVPROC) (GLuint program, GLint location, GLint64EXT x); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2I64NVPROC) (GLuint program, GLint location, GLint64EXT x, GLint64EXT y); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3I64NVPROC) (GLuint program, GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4I64NVPROC) (GLuint program, GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z, GLint64EXT w); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1I64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2I64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3I64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4I64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1UI64NVPROC) (GLuint program, GLint location, GLuint64EXT x); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2UI64NVPROC) (GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3UI64NVPROC) (GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4UI64NVPROC) (GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, GLuint64EXT w); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1UI64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2UI64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3UI64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4UI64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glUniform1i64NV (GLint location, GLint64EXT x); +GLAPI void APIENTRY glUniform2i64NV (GLint location, GLint64EXT x, GLint64EXT y); +GLAPI void APIENTRY glUniform3i64NV (GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z); +GLAPI void APIENTRY glUniform4i64NV (GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z, GLint64EXT w); +GLAPI void APIENTRY glUniform1i64vNV (GLint location, GLsizei count, const GLint64EXT *value); +GLAPI void APIENTRY glUniform2i64vNV (GLint location, GLsizei count, const GLint64EXT *value); +GLAPI void APIENTRY glUniform3i64vNV (GLint location, GLsizei count, const GLint64EXT *value); +GLAPI void APIENTRY glUniform4i64vNV (GLint location, GLsizei count, const GLint64EXT *value); +GLAPI void APIENTRY glUniform1ui64NV (GLint location, GLuint64EXT x); +GLAPI void APIENTRY glUniform2ui64NV (GLint location, GLuint64EXT x, GLuint64EXT y); +GLAPI void APIENTRY glUniform3ui64NV (GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z); +GLAPI void APIENTRY glUniform4ui64NV (GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, GLuint64EXT w); +GLAPI void APIENTRY glUniform1ui64vNV (GLint location, GLsizei count, const GLuint64EXT *value); +GLAPI void APIENTRY glUniform2ui64vNV (GLint location, GLsizei count, const GLuint64EXT *value); +GLAPI void APIENTRY glUniform3ui64vNV (GLint location, GLsizei count, const GLuint64EXT *value); +GLAPI void APIENTRY glUniform4ui64vNV (GLint location, GLsizei count, const GLuint64EXT *value); +GLAPI void APIENTRY glGetUniformi64vNV (GLuint program, GLint location, GLint64EXT *params); +GLAPI void APIENTRY glGetUniformui64vNV (GLuint program, GLint location, GLuint64EXT *params); +GLAPI void APIENTRY glProgramUniform1i64NV (GLuint program, GLint location, GLint64EXT x); +GLAPI void APIENTRY glProgramUniform2i64NV (GLuint program, GLint location, GLint64EXT x, GLint64EXT y); +GLAPI void APIENTRY glProgramUniform3i64NV (GLuint program, GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z); +GLAPI void APIENTRY glProgramUniform4i64NV (GLuint program, GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z, GLint64EXT w); +GLAPI void APIENTRY glProgramUniform1i64vNV (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +GLAPI void APIENTRY glProgramUniform2i64vNV (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +GLAPI void APIENTRY glProgramUniform3i64vNV (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +GLAPI void APIENTRY glProgramUniform4i64vNV (GLuint program, GLint location, GLsizei count, const GLint64EXT *value); +GLAPI void APIENTRY glProgramUniform1ui64NV (GLuint program, GLint location, GLuint64EXT x); +GLAPI void APIENTRY glProgramUniform2ui64NV (GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y); +GLAPI void APIENTRY glProgramUniform3ui64NV (GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z); +GLAPI void APIENTRY glProgramUniform4ui64NV (GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, GLuint64EXT w); +GLAPI void APIENTRY glProgramUniform1ui64vNV (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +GLAPI void APIENTRY glProgramUniform2ui64vNV (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +GLAPI void APIENTRY glProgramUniform3ui64vNV (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +GLAPI void APIENTRY glProgramUniform4ui64vNV (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +#endif +#endif /* GL_AMD_gpu_shader_int64 */ + +#ifndef GL_AMD_interleaved_elements +#define GL_AMD_interleaved_elements 1 +#define GL_VERTEX_ELEMENT_SWIZZLE_AMD 0x91A4 +#define GL_VERTEX_ID_SWIZZLE_AMD 0x91A5 +typedef void (APIENTRYP PFNGLVERTEXATTRIBPARAMETERIAMDPROC) (GLuint index, GLenum pname, GLint param); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexAttribParameteriAMD (GLuint index, GLenum pname, GLint param); +#endif +#endif /* GL_AMD_interleaved_elements */ + +#ifndef GL_AMD_multi_draw_indirect +#define GL_AMD_multi_draw_indirect 1 +typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSINDIRECTAMDPROC) (GLenum mode, const void *indirect, GLsizei primcount, GLsizei stride); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSINDIRECTAMDPROC) (GLenum mode, GLenum type, const void *indirect, GLsizei primcount, GLsizei stride); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMultiDrawArraysIndirectAMD (GLenum mode, const void *indirect, GLsizei primcount, GLsizei stride); +GLAPI void APIENTRY glMultiDrawElementsIndirectAMD (GLenum mode, GLenum type, const void *indirect, GLsizei primcount, GLsizei stride); +#endif +#endif /* GL_AMD_multi_draw_indirect */ + +#ifndef GL_AMD_name_gen_delete +#define GL_AMD_name_gen_delete 1 +#define GL_DATA_BUFFER_AMD 0x9151 +#define GL_PERFORMANCE_MONITOR_AMD 0x9152 +#define GL_QUERY_OBJECT_AMD 0x9153 +#define GL_VERTEX_ARRAY_OBJECT_AMD 0x9154 +#define GL_SAMPLER_OBJECT_AMD 0x9155 +typedef void (APIENTRYP PFNGLGENNAMESAMDPROC) (GLenum identifier, GLuint num, GLuint *names); +typedef void (APIENTRYP PFNGLDELETENAMESAMDPROC) (GLenum identifier, GLuint num, const GLuint *names); +typedef GLboolean (APIENTRYP PFNGLISNAMEAMDPROC) (GLenum identifier, GLuint name); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenNamesAMD (GLenum identifier, GLuint num, GLuint *names); +GLAPI void APIENTRY glDeleteNamesAMD (GLenum identifier, GLuint num, const GLuint *names); +GLAPI GLboolean APIENTRY glIsNameAMD (GLenum identifier, GLuint name); +#endif +#endif /* GL_AMD_name_gen_delete */ + +#ifndef GL_AMD_occlusion_query_event +#define GL_AMD_occlusion_query_event 1 +#define GL_OCCLUSION_QUERY_EVENT_MASK_AMD 0x874F +#define GL_QUERY_DEPTH_PASS_EVENT_BIT_AMD 0x00000001 +#define GL_QUERY_DEPTH_FAIL_EVENT_BIT_AMD 0x00000002 +#define GL_QUERY_STENCIL_FAIL_EVENT_BIT_AMD 0x00000004 +#define GL_QUERY_DEPTH_BOUNDS_FAIL_EVENT_BIT_AMD 0x00000008 +#define GL_QUERY_ALL_EVENT_BITS_AMD 0xFFFFFFFF +typedef void (APIENTRYP PFNGLQUERYOBJECTPARAMETERUIAMDPROC) (GLenum target, GLuint id, GLenum pname, GLuint param); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glQueryObjectParameteruiAMD (GLenum target, GLuint id, GLenum pname, GLuint param); +#endif +#endif /* GL_AMD_occlusion_query_event */ + +#ifndef GL_AMD_performance_monitor +#define GL_AMD_performance_monitor 1 +#define GL_COUNTER_TYPE_AMD 0x8BC0 +#define GL_COUNTER_RANGE_AMD 0x8BC1 +#define GL_UNSIGNED_INT64_AMD 0x8BC2 +#define GL_PERCENTAGE_AMD 0x8BC3 +#define GL_PERFMON_RESULT_AVAILABLE_AMD 0x8BC4 +#define GL_PERFMON_RESULT_SIZE_AMD 0x8BC5 +#define GL_PERFMON_RESULT_AMD 0x8BC6 +typedef void (APIENTRYP PFNGLGETPERFMONITORGROUPSAMDPROC) (GLint *numGroups, GLsizei groupsSize, GLuint *groups); +typedef void (APIENTRYP PFNGLGETPERFMONITORCOUNTERSAMDPROC) (GLuint group, GLint *numCounters, GLint *maxActiveCounters, GLsizei counterSize, GLuint *counters); +typedef void (APIENTRYP PFNGLGETPERFMONITORGROUPSTRINGAMDPROC) (GLuint group, GLsizei bufSize, GLsizei *length, GLchar *groupString); +typedef void (APIENTRYP PFNGLGETPERFMONITORCOUNTERSTRINGAMDPROC) (GLuint group, GLuint counter, GLsizei bufSize, GLsizei *length, GLchar *counterString); +typedef void (APIENTRYP PFNGLGETPERFMONITORCOUNTERINFOAMDPROC) (GLuint group, GLuint counter, GLenum pname, void *data); +typedef void (APIENTRYP PFNGLGENPERFMONITORSAMDPROC) (GLsizei n, GLuint *monitors); +typedef void (APIENTRYP PFNGLDELETEPERFMONITORSAMDPROC) (GLsizei n, GLuint *monitors); +typedef void (APIENTRYP PFNGLSELECTPERFMONITORCOUNTERSAMDPROC) (GLuint monitor, GLboolean enable, GLuint group, GLint numCounters, GLuint *counterList); +typedef void (APIENTRYP PFNGLBEGINPERFMONITORAMDPROC) (GLuint monitor); +typedef void (APIENTRYP PFNGLENDPERFMONITORAMDPROC) (GLuint monitor); +typedef void (APIENTRYP PFNGLGETPERFMONITORCOUNTERDATAAMDPROC) (GLuint monitor, GLenum pname, GLsizei dataSize, GLuint *data, GLint *bytesWritten); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetPerfMonitorGroupsAMD (GLint *numGroups, GLsizei groupsSize, GLuint *groups); +GLAPI void APIENTRY glGetPerfMonitorCountersAMD (GLuint group, GLint *numCounters, GLint *maxActiveCounters, GLsizei counterSize, GLuint *counters); +GLAPI void APIENTRY glGetPerfMonitorGroupStringAMD (GLuint group, GLsizei bufSize, GLsizei *length, GLchar *groupString); +GLAPI void APIENTRY glGetPerfMonitorCounterStringAMD (GLuint group, GLuint counter, GLsizei bufSize, GLsizei *length, GLchar *counterString); +GLAPI void APIENTRY glGetPerfMonitorCounterInfoAMD (GLuint group, GLuint counter, GLenum pname, void *data); +GLAPI void APIENTRY glGenPerfMonitorsAMD (GLsizei n, GLuint *monitors); +GLAPI void APIENTRY glDeletePerfMonitorsAMD (GLsizei n, GLuint *monitors); +GLAPI void APIENTRY glSelectPerfMonitorCountersAMD (GLuint monitor, GLboolean enable, GLuint group, GLint numCounters, GLuint *counterList); +GLAPI void APIENTRY glBeginPerfMonitorAMD (GLuint monitor); +GLAPI void APIENTRY glEndPerfMonitorAMD (GLuint monitor); +GLAPI void APIENTRY glGetPerfMonitorCounterDataAMD (GLuint monitor, GLenum pname, GLsizei dataSize, GLuint *data, GLint *bytesWritten); +#endif +#endif /* GL_AMD_performance_monitor */ + +#ifndef GL_AMD_pinned_memory +#define GL_AMD_pinned_memory 1 +#define GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD 0x9160 +#endif /* GL_AMD_pinned_memory */ + +#ifndef GL_AMD_query_buffer_object +#define GL_AMD_query_buffer_object 1 +#define GL_QUERY_BUFFER_AMD 0x9192 +#define GL_QUERY_BUFFER_BINDING_AMD 0x9193 +#define GL_QUERY_RESULT_NO_WAIT_AMD 0x9194 +#endif /* GL_AMD_query_buffer_object */ + +#ifndef GL_AMD_sample_positions +#define GL_AMD_sample_positions 1 +typedef void (APIENTRYP PFNGLSETMULTISAMPLEFVAMDPROC) (GLenum pname, GLuint index, const GLfloat *val); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSetMultisamplefvAMD (GLenum pname, GLuint index, const GLfloat *val); +#endif +#endif /* GL_AMD_sample_positions */ + +#ifndef GL_AMD_seamless_cubemap_per_texture +#define GL_AMD_seamless_cubemap_per_texture 1 +#endif /* GL_AMD_seamless_cubemap_per_texture */ + +#ifndef GL_AMD_shader_atomic_counter_ops +#define GL_AMD_shader_atomic_counter_ops 1 +#endif /* GL_AMD_shader_atomic_counter_ops */ + +#ifndef GL_AMD_shader_ballot +#define GL_AMD_shader_ballot 1 +#endif /* GL_AMD_shader_ballot */ + +#ifndef GL_AMD_shader_explicit_vertex_parameter +#define GL_AMD_shader_explicit_vertex_parameter 1 +#endif /* GL_AMD_shader_explicit_vertex_parameter */ + +#ifndef GL_AMD_shader_gpu_shader_half_float_fetch +#define GL_AMD_shader_gpu_shader_half_float_fetch 1 +#endif /* GL_AMD_shader_gpu_shader_half_float_fetch */ + +#ifndef GL_AMD_shader_image_load_store_lod +#define GL_AMD_shader_image_load_store_lod 1 +#endif /* GL_AMD_shader_image_load_store_lod */ + +#ifndef GL_AMD_shader_stencil_export +#define GL_AMD_shader_stencil_export 1 +#endif /* GL_AMD_shader_stencil_export */ + +#ifndef GL_AMD_shader_trinary_minmax +#define GL_AMD_shader_trinary_minmax 1 +#endif /* GL_AMD_shader_trinary_minmax */ + +#ifndef GL_AMD_sparse_texture +#define GL_AMD_sparse_texture 1 +#define GL_VIRTUAL_PAGE_SIZE_X_AMD 0x9195 +#define GL_VIRTUAL_PAGE_SIZE_Y_AMD 0x9196 +#define GL_VIRTUAL_PAGE_SIZE_Z_AMD 0x9197 +#define GL_MAX_SPARSE_TEXTURE_SIZE_AMD 0x9198 +#define GL_MAX_SPARSE_3D_TEXTURE_SIZE_AMD 0x9199 +#define GL_MAX_SPARSE_ARRAY_TEXTURE_LAYERS 0x919A +#define GL_MIN_SPARSE_LEVEL_AMD 0x919B +#define GL_MIN_LOD_WARNING_AMD 0x919C +#define GL_TEXTURE_STORAGE_SPARSE_BIT_AMD 0x00000001 +typedef void (APIENTRYP PFNGLTEXSTORAGESPARSEAMDPROC) (GLenum target, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLsizei layers, GLbitfield flags); +typedef void (APIENTRYP PFNGLTEXTURESTORAGESPARSEAMDPROC) (GLuint texture, GLenum target, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLsizei layers, GLbitfield flags); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexStorageSparseAMD (GLenum target, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLsizei layers, GLbitfield flags); +GLAPI void APIENTRY glTextureStorageSparseAMD (GLuint texture, GLenum target, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLsizei layers, GLbitfield flags); +#endif +#endif /* GL_AMD_sparse_texture */ + +#ifndef GL_AMD_stencil_operation_extended +#define GL_AMD_stencil_operation_extended 1 +#define GL_SET_AMD 0x874A +#define GL_REPLACE_VALUE_AMD 0x874B +#define GL_STENCIL_OP_VALUE_AMD 0x874C +#define GL_STENCIL_BACK_OP_VALUE_AMD 0x874D +typedef void (APIENTRYP PFNGLSTENCILOPVALUEAMDPROC) (GLenum face, GLuint value); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glStencilOpValueAMD (GLenum face, GLuint value); +#endif +#endif /* GL_AMD_stencil_operation_extended */ + +#ifndef GL_AMD_texture_gather_bias_lod +#define GL_AMD_texture_gather_bias_lod 1 +#endif /* GL_AMD_texture_gather_bias_lod */ + +#ifndef GL_AMD_texture_texture4 +#define GL_AMD_texture_texture4 1 +#endif /* GL_AMD_texture_texture4 */ + +#ifndef GL_AMD_transform_feedback3_lines_triangles +#define GL_AMD_transform_feedback3_lines_triangles 1 +#endif /* GL_AMD_transform_feedback3_lines_triangles */ + +#ifndef GL_AMD_transform_feedback4 +#define GL_AMD_transform_feedback4 1 +#define GL_STREAM_RASTERIZATION_AMD 0x91A0 +#endif /* GL_AMD_transform_feedback4 */ + +#ifndef GL_AMD_vertex_shader_layer +#define GL_AMD_vertex_shader_layer 1 +#endif /* GL_AMD_vertex_shader_layer */ + +#ifndef GL_AMD_vertex_shader_tessellator +#define GL_AMD_vertex_shader_tessellator 1 +#define GL_SAMPLER_BUFFER_AMD 0x9001 +#define GL_INT_SAMPLER_BUFFER_AMD 0x9002 +#define GL_UNSIGNED_INT_SAMPLER_BUFFER_AMD 0x9003 +#define GL_TESSELLATION_MODE_AMD 0x9004 +#define GL_TESSELLATION_FACTOR_AMD 0x9005 +#define GL_DISCRETE_AMD 0x9006 +#define GL_CONTINUOUS_AMD 0x9007 +typedef void (APIENTRYP PFNGLTESSELLATIONFACTORAMDPROC) (GLfloat factor); +typedef void (APIENTRYP PFNGLTESSELLATIONMODEAMDPROC) (GLenum mode); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTessellationFactorAMD (GLfloat factor); +GLAPI void APIENTRY glTessellationModeAMD (GLenum mode); +#endif +#endif /* GL_AMD_vertex_shader_tessellator */ + +#ifndef GL_AMD_vertex_shader_viewport_index +#define GL_AMD_vertex_shader_viewport_index 1 +#endif /* GL_AMD_vertex_shader_viewport_index */ + +#ifndef GL_APPLE_aux_depth_stencil +#define GL_APPLE_aux_depth_stencil 1 +#define GL_AUX_DEPTH_STENCIL_APPLE 0x8A14 +#endif /* GL_APPLE_aux_depth_stencil */ + +#ifndef GL_APPLE_client_storage +#define GL_APPLE_client_storage 1 +#define GL_UNPACK_CLIENT_STORAGE_APPLE 0x85B2 +#endif /* GL_APPLE_client_storage */ + +#ifndef GL_APPLE_element_array +#define GL_APPLE_element_array 1 +#define GL_ELEMENT_ARRAY_APPLE 0x8A0C +#define GL_ELEMENT_ARRAY_TYPE_APPLE 0x8A0D +#define GL_ELEMENT_ARRAY_POINTER_APPLE 0x8A0E +typedef void (APIENTRYP PFNGLELEMENTPOINTERAPPLEPROC) (GLenum type, const void *pointer); +typedef void (APIENTRYP PFNGLDRAWELEMENTARRAYAPPLEPROC) (GLenum mode, GLint first, GLsizei count); +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTARRAYAPPLEPROC) (GLenum mode, GLuint start, GLuint end, GLint first, GLsizei count); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTARRAYAPPLEPROC) (GLenum mode, const GLint *first, const GLsizei *count, GLsizei primcount); +typedef void (APIENTRYP PFNGLMULTIDRAWRANGEELEMENTARRAYAPPLEPROC) (GLenum mode, GLuint start, GLuint end, const GLint *first, const GLsizei *count, GLsizei primcount); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glElementPointerAPPLE (GLenum type, const void *pointer); +GLAPI void APIENTRY glDrawElementArrayAPPLE (GLenum mode, GLint first, GLsizei count); +GLAPI void APIENTRY glDrawRangeElementArrayAPPLE (GLenum mode, GLuint start, GLuint end, GLint first, GLsizei count); +GLAPI void APIENTRY glMultiDrawElementArrayAPPLE (GLenum mode, const GLint *first, const GLsizei *count, GLsizei primcount); +GLAPI void APIENTRY glMultiDrawRangeElementArrayAPPLE (GLenum mode, GLuint start, GLuint end, const GLint *first, const GLsizei *count, GLsizei primcount); +#endif +#endif /* GL_APPLE_element_array */ + +#ifndef GL_APPLE_fence +#define GL_APPLE_fence 1 +#define GL_DRAW_PIXELS_APPLE 0x8A0A +#define GL_FENCE_APPLE 0x8A0B +typedef void (APIENTRYP PFNGLGENFENCESAPPLEPROC) (GLsizei n, GLuint *fences); +typedef void (APIENTRYP PFNGLDELETEFENCESAPPLEPROC) (GLsizei n, const GLuint *fences); +typedef void (APIENTRYP PFNGLSETFENCEAPPLEPROC) (GLuint fence); +typedef GLboolean (APIENTRYP PFNGLISFENCEAPPLEPROC) (GLuint fence); +typedef GLboolean (APIENTRYP PFNGLTESTFENCEAPPLEPROC) (GLuint fence); +typedef void (APIENTRYP PFNGLFINISHFENCEAPPLEPROC) (GLuint fence); +typedef GLboolean (APIENTRYP PFNGLTESTOBJECTAPPLEPROC) (GLenum object, GLuint name); +typedef void (APIENTRYP PFNGLFINISHOBJECTAPPLEPROC) (GLenum object, GLint name); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenFencesAPPLE (GLsizei n, GLuint *fences); +GLAPI void APIENTRY glDeleteFencesAPPLE (GLsizei n, const GLuint *fences); +GLAPI void APIENTRY glSetFenceAPPLE (GLuint fence); +GLAPI GLboolean APIENTRY glIsFenceAPPLE (GLuint fence); +GLAPI GLboolean APIENTRY glTestFenceAPPLE (GLuint fence); +GLAPI void APIENTRY glFinishFenceAPPLE (GLuint fence); +GLAPI GLboolean APIENTRY glTestObjectAPPLE (GLenum object, GLuint name); +GLAPI void APIENTRY glFinishObjectAPPLE (GLenum object, GLint name); +#endif +#endif /* GL_APPLE_fence */ + +#ifndef GL_APPLE_float_pixels +#define GL_APPLE_float_pixels 1 +#define GL_HALF_APPLE 0x140B +#define GL_RGBA_FLOAT32_APPLE 0x8814 +#define GL_RGB_FLOAT32_APPLE 0x8815 +#define GL_ALPHA_FLOAT32_APPLE 0x8816 +#define GL_INTENSITY_FLOAT32_APPLE 0x8817 +#define GL_LUMINANCE_FLOAT32_APPLE 0x8818 +#define GL_LUMINANCE_ALPHA_FLOAT32_APPLE 0x8819 +#define GL_RGBA_FLOAT16_APPLE 0x881A +#define GL_RGB_FLOAT16_APPLE 0x881B +#define GL_ALPHA_FLOAT16_APPLE 0x881C +#define GL_INTENSITY_FLOAT16_APPLE 0x881D +#define GL_LUMINANCE_FLOAT16_APPLE 0x881E +#define GL_LUMINANCE_ALPHA_FLOAT16_APPLE 0x881F +#define GL_COLOR_FLOAT_APPLE 0x8A0F +#endif /* GL_APPLE_float_pixels */ + +#ifndef GL_APPLE_flush_buffer_range +#define GL_APPLE_flush_buffer_range 1 +#define GL_BUFFER_SERIALIZED_MODIFY_APPLE 0x8A12 +#define GL_BUFFER_FLUSHING_UNMAP_APPLE 0x8A13 +typedef void (APIENTRYP PFNGLBUFFERPARAMETERIAPPLEPROC) (GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLFLUSHMAPPEDBUFFERRANGEAPPLEPROC) (GLenum target, GLintptr offset, GLsizeiptr size); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBufferParameteriAPPLE (GLenum target, GLenum pname, GLint param); +GLAPI void APIENTRY glFlushMappedBufferRangeAPPLE (GLenum target, GLintptr offset, GLsizeiptr size); +#endif +#endif /* GL_APPLE_flush_buffer_range */ + +#ifndef GL_APPLE_object_purgeable +#define GL_APPLE_object_purgeable 1 +#define GL_BUFFER_OBJECT_APPLE 0x85B3 +#define GL_RELEASED_APPLE 0x8A19 +#define GL_VOLATILE_APPLE 0x8A1A +#define GL_RETAINED_APPLE 0x8A1B +#define GL_UNDEFINED_APPLE 0x8A1C +#define GL_PURGEABLE_APPLE 0x8A1D +typedef GLenum (APIENTRYP PFNGLOBJECTPURGEABLEAPPLEPROC) (GLenum objectType, GLuint name, GLenum option); +typedef GLenum (APIENTRYP PFNGLOBJECTUNPURGEABLEAPPLEPROC) (GLenum objectType, GLuint name, GLenum option); +typedef void (APIENTRYP PFNGLGETOBJECTPARAMETERIVAPPLEPROC) (GLenum objectType, GLuint name, GLenum pname, GLint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLenum APIENTRY glObjectPurgeableAPPLE (GLenum objectType, GLuint name, GLenum option); +GLAPI GLenum APIENTRY glObjectUnpurgeableAPPLE (GLenum objectType, GLuint name, GLenum option); +GLAPI void APIENTRY glGetObjectParameterivAPPLE (GLenum objectType, GLuint name, GLenum pname, GLint *params); +#endif +#endif /* GL_APPLE_object_purgeable */ + +#ifndef GL_APPLE_rgb_422 +#define GL_APPLE_rgb_422 1 +#define GL_RGB_422_APPLE 0x8A1F +#define GL_UNSIGNED_SHORT_8_8_APPLE 0x85BA +#define GL_UNSIGNED_SHORT_8_8_REV_APPLE 0x85BB +#define GL_RGB_RAW_422_APPLE 0x8A51 +#endif /* GL_APPLE_rgb_422 */ + +#ifndef GL_APPLE_row_bytes +#define GL_APPLE_row_bytes 1 +#define GL_PACK_ROW_BYTES_APPLE 0x8A15 +#define GL_UNPACK_ROW_BYTES_APPLE 0x8A16 +#endif /* GL_APPLE_row_bytes */ + +#ifndef GL_APPLE_specular_vector +#define GL_APPLE_specular_vector 1 +#define GL_LIGHT_MODEL_SPECULAR_VECTOR_APPLE 0x85B0 +#endif /* GL_APPLE_specular_vector */ + +#ifndef GL_APPLE_texture_range +#define GL_APPLE_texture_range 1 +#define GL_TEXTURE_RANGE_LENGTH_APPLE 0x85B7 +#define GL_TEXTURE_RANGE_POINTER_APPLE 0x85B8 +#define GL_TEXTURE_STORAGE_HINT_APPLE 0x85BC +#define GL_STORAGE_PRIVATE_APPLE 0x85BD +#define GL_STORAGE_CACHED_APPLE 0x85BE +#define GL_STORAGE_SHARED_APPLE 0x85BF +typedef void (APIENTRYP PFNGLTEXTURERANGEAPPLEPROC) (GLenum target, GLsizei length, const void *pointer); +typedef void (APIENTRYP PFNGLGETTEXPARAMETERPOINTERVAPPLEPROC) (GLenum target, GLenum pname, void **params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTextureRangeAPPLE (GLenum target, GLsizei length, const void *pointer); +GLAPI void APIENTRY glGetTexParameterPointervAPPLE (GLenum target, GLenum pname, void **params); +#endif +#endif /* GL_APPLE_texture_range */ + +#ifndef GL_APPLE_transform_hint +#define GL_APPLE_transform_hint 1 +#define GL_TRANSFORM_HINT_APPLE 0x85B1 +#endif /* GL_APPLE_transform_hint */ + +#ifndef GL_APPLE_vertex_array_object +#define GL_APPLE_vertex_array_object 1 +#define GL_VERTEX_ARRAY_BINDING_APPLE 0x85B5 +typedef void (APIENTRYP PFNGLBINDVERTEXARRAYAPPLEPROC) (GLuint array); +typedef void (APIENTRYP PFNGLDELETEVERTEXARRAYSAPPLEPROC) (GLsizei n, const GLuint *arrays); +typedef void (APIENTRYP PFNGLGENVERTEXARRAYSAPPLEPROC) (GLsizei n, GLuint *arrays); +typedef GLboolean (APIENTRYP PFNGLISVERTEXARRAYAPPLEPROC) (GLuint array); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindVertexArrayAPPLE (GLuint array); +GLAPI void APIENTRY glDeleteVertexArraysAPPLE (GLsizei n, const GLuint *arrays); +GLAPI void APIENTRY glGenVertexArraysAPPLE (GLsizei n, GLuint *arrays); +GLAPI GLboolean APIENTRY glIsVertexArrayAPPLE (GLuint array); +#endif +#endif /* GL_APPLE_vertex_array_object */ + +#ifndef GL_APPLE_vertex_array_range +#define GL_APPLE_vertex_array_range 1 +#define GL_VERTEX_ARRAY_RANGE_APPLE 0x851D +#define GL_VERTEX_ARRAY_RANGE_LENGTH_APPLE 0x851E +#define GL_VERTEX_ARRAY_STORAGE_HINT_APPLE 0x851F +#define GL_VERTEX_ARRAY_RANGE_POINTER_APPLE 0x8521 +#define GL_STORAGE_CLIENT_APPLE 0x85B4 +typedef void (APIENTRYP PFNGLVERTEXARRAYRANGEAPPLEPROC) (GLsizei length, void *pointer); +typedef void (APIENTRYP PFNGLFLUSHVERTEXARRAYRANGEAPPLEPROC) (GLsizei length, void *pointer); +typedef void (APIENTRYP PFNGLVERTEXARRAYPARAMETERIAPPLEPROC) (GLenum pname, GLint param); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexArrayRangeAPPLE (GLsizei length, void *pointer); +GLAPI void APIENTRY glFlushVertexArrayRangeAPPLE (GLsizei length, void *pointer); +GLAPI void APIENTRY glVertexArrayParameteriAPPLE (GLenum pname, GLint param); +#endif +#endif /* GL_APPLE_vertex_array_range */ + +#ifndef GL_APPLE_vertex_program_evaluators +#define GL_APPLE_vertex_program_evaluators 1 +#define GL_VERTEX_ATTRIB_MAP1_APPLE 0x8A00 +#define GL_VERTEX_ATTRIB_MAP2_APPLE 0x8A01 +#define GL_VERTEX_ATTRIB_MAP1_SIZE_APPLE 0x8A02 +#define GL_VERTEX_ATTRIB_MAP1_COEFF_APPLE 0x8A03 +#define GL_VERTEX_ATTRIB_MAP1_ORDER_APPLE 0x8A04 +#define GL_VERTEX_ATTRIB_MAP1_DOMAIN_APPLE 0x8A05 +#define GL_VERTEX_ATTRIB_MAP2_SIZE_APPLE 0x8A06 +#define GL_VERTEX_ATTRIB_MAP2_COEFF_APPLE 0x8A07 +#define GL_VERTEX_ATTRIB_MAP2_ORDER_APPLE 0x8A08 +#define GL_VERTEX_ATTRIB_MAP2_DOMAIN_APPLE 0x8A09 +typedef void (APIENTRYP PFNGLENABLEVERTEXATTRIBAPPLEPROC) (GLuint index, GLenum pname); +typedef void (APIENTRYP PFNGLDISABLEVERTEXATTRIBAPPLEPROC) (GLuint index, GLenum pname); +typedef GLboolean (APIENTRYP PFNGLISVERTEXATTRIBENABLEDAPPLEPROC) (GLuint index, GLenum pname); +typedef void (APIENTRYP PFNGLMAPVERTEXATTRIB1DAPPLEPROC) (GLuint index, GLuint size, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points); +typedef void (APIENTRYP PFNGLMAPVERTEXATTRIB1FAPPLEPROC) (GLuint index, GLuint size, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points); +typedef void (APIENTRYP PFNGLMAPVERTEXATTRIB2DAPPLEPROC) (GLuint index, GLuint size, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points); +typedef void (APIENTRYP PFNGLMAPVERTEXATTRIB2FAPPLEPROC) (GLuint index, GLuint size, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glEnableVertexAttribAPPLE (GLuint index, GLenum pname); +GLAPI void APIENTRY glDisableVertexAttribAPPLE (GLuint index, GLenum pname); +GLAPI GLboolean APIENTRY glIsVertexAttribEnabledAPPLE (GLuint index, GLenum pname); +GLAPI void APIENTRY glMapVertexAttrib1dAPPLE (GLuint index, GLuint size, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points); +GLAPI void APIENTRY glMapVertexAttrib1fAPPLE (GLuint index, GLuint size, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points); +GLAPI void APIENTRY glMapVertexAttrib2dAPPLE (GLuint index, GLuint size, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points); +GLAPI void APIENTRY glMapVertexAttrib2fAPPLE (GLuint index, GLuint size, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points); +#endif +#endif /* GL_APPLE_vertex_program_evaluators */ + +#ifndef GL_APPLE_ycbcr_422 +#define GL_APPLE_ycbcr_422 1 +#define GL_YCBCR_422_APPLE 0x85B9 +#endif /* GL_APPLE_ycbcr_422 */ + +#ifndef GL_ATI_draw_buffers +#define GL_ATI_draw_buffers 1 +#define GL_MAX_DRAW_BUFFERS_ATI 0x8824 +#define GL_DRAW_BUFFER0_ATI 0x8825 +#define GL_DRAW_BUFFER1_ATI 0x8826 +#define GL_DRAW_BUFFER2_ATI 0x8827 +#define GL_DRAW_BUFFER3_ATI 0x8828 +#define GL_DRAW_BUFFER4_ATI 0x8829 +#define GL_DRAW_BUFFER5_ATI 0x882A +#define GL_DRAW_BUFFER6_ATI 0x882B +#define GL_DRAW_BUFFER7_ATI 0x882C +#define GL_DRAW_BUFFER8_ATI 0x882D +#define GL_DRAW_BUFFER9_ATI 0x882E +#define GL_DRAW_BUFFER10_ATI 0x882F +#define GL_DRAW_BUFFER11_ATI 0x8830 +#define GL_DRAW_BUFFER12_ATI 0x8831 +#define GL_DRAW_BUFFER13_ATI 0x8832 +#define GL_DRAW_BUFFER14_ATI 0x8833 +#define GL_DRAW_BUFFER15_ATI 0x8834 +typedef void (APIENTRYP PFNGLDRAWBUFFERSATIPROC) (GLsizei n, const GLenum *bufs); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawBuffersATI (GLsizei n, const GLenum *bufs); +#endif +#endif /* GL_ATI_draw_buffers */ + +#ifndef GL_ATI_element_array +#define GL_ATI_element_array 1 +#define GL_ELEMENT_ARRAY_ATI 0x8768 +#define GL_ELEMENT_ARRAY_TYPE_ATI 0x8769 +#define GL_ELEMENT_ARRAY_POINTER_ATI 0x876A +typedef void (APIENTRYP PFNGLELEMENTPOINTERATIPROC) (GLenum type, const void *pointer); +typedef void (APIENTRYP PFNGLDRAWELEMENTARRAYATIPROC) (GLenum mode, GLsizei count); +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTARRAYATIPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glElementPointerATI (GLenum type, const void *pointer); +GLAPI void APIENTRY glDrawElementArrayATI (GLenum mode, GLsizei count); +GLAPI void APIENTRY glDrawRangeElementArrayATI (GLenum mode, GLuint start, GLuint end, GLsizei count); +#endif +#endif /* GL_ATI_element_array */ + +#ifndef GL_ATI_envmap_bumpmap +#define GL_ATI_envmap_bumpmap 1 +#define GL_BUMP_ROT_MATRIX_ATI 0x8775 +#define GL_BUMP_ROT_MATRIX_SIZE_ATI 0x8776 +#define GL_BUMP_NUM_TEX_UNITS_ATI 0x8777 +#define GL_BUMP_TEX_UNITS_ATI 0x8778 +#define GL_DUDV_ATI 0x8779 +#define GL_DU8DV8_ATI 0x877A +#define GL_BUMP_ENVMAP_ATI 0x877B +#define GL_BUMP_TARGET_ATI 0x877C +typedef void (APIENTRYP PFNGLTEXBUMPPARAMETERIVATIPROC) (GLenum pname, const GLint *param); +typedef void (APIENTRYP PFNGLTEXBUMPPARAMETERFVATIPROC) (GLenum pname, const GLfloat *param); +typedef void (APIENTRYP PFNGLGETTEXBUMPPARAMETERIVATIPROC) (GLenum pname, GLint *param); +typedef void (APIENTRYP PFNGLGETTEXBUMPPARAMETERFVATIPROC) (GLenum pname, GLfloat *param); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexBumpParameterivATI (GLenum pname, const GLint *param); +GLAPI void APIENTRY glTexBumpParameterfvATI (GLenum pname, const GLfloat *param); +GLAPI void APIENTRY glGetTexBumpParameterivATI (GLenum pname, GLint *param); +GLAPI void APIENTRY glGetTexBumpParameterfvATI (GLenum pname, GLfloat *param); +#endif +#endif /* GL_ATI_envmap_bumpmap */ + +#ifndef GL_ATI_fragment_shader +#define GL_ATI_fragment_shader 1 +#define GL_FRAGMENT_SHADER_ATI 0x8920 +#define GL_REG_0_ATI 0x8921 +#define GL_REG_1_ATI 0x8922 +#define GL_REG_2_ATI 0x8923 +#define GL_REG_3_ATI 0x8924 +#define GL_REG_4_ATI 0x8925 +#define GL_REG_5_ATI 0x8926 +#define GL_REG_6_ATI 0x8927 +#define GL_REG_7_ATI 0x8928 +#define GL_REG_8_ATI 0x8929 +#define GL_REG_9_ATI 0x892A +#define GL_REG_10_ATI 0x892B +#define GL_REG_11_ATI 0x892C +#define GL_REG_12_ATI 0x892D +#define GL_REG_13_ATI 0x892E +#define GL_REG_14_ATI 0x892F +#define GL_REG_15_ATI 0x8930 +#define GL_REG_16_ATI 0x8931 +#define GL_REG_17_ATI 0x8932 +#define GL_REG_18_ATI 0x8933 +#define GL_REG_19_ATI 0x8934 +#define GL_REG_20_ATI 0x8935 +#define GL_REG_21_ATI 0x8936 +#define GL_REG_22_ATI 0x8937 +#define GL_REG_23_ATI 0x8938 +#define GL_REG_24_ATI 0x8939 +#define GL_REG_25_ATI 0x893A +#define GL_REG_26_ATI 0x893B +#define GL_REG_27_ATI 0x893C +#define GL_REG_28_ATI 0x893D +#define GL_REG_29_ATI 0x893E +#define GL_REG_30_ATI 0x893F +#define GL_REG_31_ATI 0x8940 +#define GL_CON_0_ATI 0x8941 +#define GL_CON_1_ATI 0x8942 +#define GL_CON_2_ATI 0x8943 +#define GL_CON_3_ATI 0x8944 +#define GL_CON_4_ATI 0x8945 +#define GL_CON_5_ATI 0x8946 +#define GL_CON_6_ATI 0x8947 +#define GL_CON_7_ATI 0x8948 +#define GL_CON_8_ATI 0x8949 +#define GL_CON_9_ATI 0x894A +#define GL_CON_10_ATI 0x894B +#define GL_CON_11_ATI 0x894C +#define GL_CON_12_ATI 0x894D +#define GL_CON_13_ATI 0x894E +#define GL_CON_14_ATI 0x894F +#define GL_CON_15_ATI 0x8950 +#define GL_CON_16_ATI 0x8951 +#define GL_CON_17_ATI 0x8952 +#define GL_CON_18_ATI 0x8953 +#define GL_CON_19_ATI 0x8954 +#define GL_CON_20_ATI 0x8955 +#define GL_CON_21_ATI 0x8956 +#define GL_CON_22_ATI 0x8957 +#define GL_CON_23_ATI 0x8958 +#define GL_CON_24_ATI 0x8959 +#define GL_CON_25_ATI 0x895A +#define GL_CON_26_ATI 0x895B +#define GL_CON_27_ATI 0x895C +#define GL_CON_28_ATI 0x895D +#define GL_CON_29_ATI 0x895E +#define GL_CON_30_ATI 0x895F +#define GL_CON_31_ATI 0x8960 +#define GL_MOV_ATI 0x8961 +#define GL_ADD_ATI 0x8963 +#define GL_MUL_ATI 0x8964 +#define GL_SUB_ATI 0x8965 +#define GL_DOT3_ATI 0x8966 +#define GL_DOT4_ATI 0x8967 +#define GL_MAD_ATI 0x8968 +#define GL_LERP_ATI 0x8969 +#define GL_CND_ATI 0x896A +#define GL_CND0_ATI 0x896B +#define GL_DOT2_ADD_ATI 0x896C +#define GL_SECONDARY_INTERPOLATOR_ATI 0x896D +#define GL_NUM_FRAGMENT_REGISTERS_ATI 0x896E +#define GL_NUM_FRAGMENT_CONSTANTS_ATI 0x896F +#define GL_NUM_PASSES_ATI 0x8970 +#define GL_NUM_INSTRUCTIONS_PER_PASS_ATI 0x8971 +#define GL_NUM_INSTRUCTIONS_TOTAL_ATI 0x8972 +#define GL_NUM_INPUT_INTERPOLATOR_COMPONENTS_ATI 0x8973 +#define GL_NUM_LOOPBACK_COMPONENTS_ATI 0x8974 +#define GL_COLOR_ALPHA_PAIRING_ATI 0x8975 +#define GL_SWIZZLE_STR_ATI 0x8976 +#define GL_SWIZZLE_STQ_ATI 0x8977 +#define GL_SWIZZLE_STR_DR_ATI 0x8978 +#define GL_SWIZZLE_STQ_DQ_ATI 0x8979 +#define GL_SWIZZLE_STRQ_ATI 0x897A +#define GL_SWIZZLE_STRQ_DQ_ATI 0x897B +#define GL_RED_BIT_ATI 0x00000001 +#define GL_GREEN_BIT_ATI 0x00000002 +#define GL_BLUE_BIT_ATI 0x00000004 +#define GL_2X_BIT_ATI 0x00000001 +#define GL_4X_BIT_ATI 0x00000002 +#define GL_8X_BIT_ATI 0x00000004 +#define GL_HALF_BIT_ATI 0x00000008 +#define GL_QUARTER_BIT_ATI 0x00000010 +#define GL_EIGHTH_BIT_ATI 0x00000020 +#define GL_SATURATE_BIT_ATI 0x00000040 +#define GL_COMP_BIT_ATI 0x00000002 +#define GL_NEGATE_BIT_ATI 0x00000004 +#define GL_BIAS_BIT_ATI 0x00000008 +typedef GLuint (APIENTRYP PFNGLGENFRAGMENTSHADERSATIPROC) (GLuint range); +typedef void (APIENTRYP PFNGLBINDFRAGMENTSHADERATIPROC) (GLuint id); +typedef void (APIENTRYP PFNGLDELETEFRAGMENTSHADERATIPROC) (GLuint id); +typedef void (APIENTRYP PFNGLBEGINFRAGMENTSHADERATIPROC) (void); +typedef void (APIENTRYP PFNGLENDFRAGMENTSHADERATIPROC) (void); +typedef void (APIENTRYP PFNGLPASSTEXCOORDATIPROC) (GLuint dst, GLuint coord, GLenum swizzle); +typedef void (APIENTRYP PFNGLSAMPLEMAPATIPROC) (GLuint dst, GLuint interp, GLenum swizzle); +typedef void (APIENTRYP PFNGLCOLORFRAGMENTOP1ATIPROC) (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod); +typedef void (APIENTRYP PFNGLCOLORFRAGMENTOP2ATIPROC) (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod); +typedef void (APIENTRYP PFNGLCOLORFRAGMENTOP3ATIPROC) (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod, GLuint arg3, GLuint arg3Rep, GLuint arg3Mod); +typedef void (APIENTRYP PFNGLALPHAFRAGMENTOP1ATIPROC) (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod); +typedef void (APIENTRYP PFNGLALPHAFRAGMENTOP2ATIPROC) (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod); +typedef void (APIENTRYP PFNGLALPHAFRAGMENTOP3ATIPROC) (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod, GLuint arg3, GLuint arg3Rep, GLuint arg3Mod); +typedef void (APIENTRYP PFNGLSETFRAGMENTSHADERCONSTANTATIPROC) (GLuint dst, const GLfloat *value); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLuint APIENTRY glGenFragmentShadersATI (GLuint range); +GLAPI void APIENTRY glBindFragmentShaderATI (GLuint id); +GLAPI void APIENTRY glDeleteFragmentShaderATI (GLuint id); +GLAPI void APIENTRY glBeginFragmentShaderATI (void); +GLAPI void APIENTRY glEndFragmentShaderATI (void); +GLAPI void APIENTRY glPassTexCoordATI (GLuint dst, GLuint coord, GLenum swizzle); +GLAPI void APIENTRY glSampleMapATI (GLuint dst, GLuint interp, GLenum swizzle); +GLAPI void APIENTRY glColorFragmentOp1ATI (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod); +GLAPI void APIENTRY glColorFragmentOp2ATI (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod); +GLAPI void APIENTRY glColorFragmentOp3ATI (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod, GLuint arg3, GLuint arg3Rep, GLuint arg3Mod); +GLAPI void APIENTRY glAlphaFragmentOp1ATI (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod); +GLAPI void APIENTRY glAlphaFragmentOp2ATI (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod); +GLAPI void APIENTRY glAlphaFragmentOp3ATI (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod, GLuint arg3, GLuint arg3Rep, GLuint arg3Mod); +GLAPI void APIENTRY glSetFragmentShaderConstantATI (GLuint dst, const GLfloat *value); +#endif +#endif /* GL_ATI_fragment_shader */ + +#ifndef GL_ATI_map_object_buffer +#define GL_ATI_map_object_buffer 1 +typedef void *(APIENTRYP PFNGLMAPOBJECTBUFFERATIPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLUNMAPOBJECTBUFFERATIPROC) (GLuint buffer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void *APIENTRY glMapObjectBufferATI (GLuint buffer); +GLAPI void APIENTRY glUnmapObjectBufferATI (GLuint buffer); +#endif +#endif /* GL_ATI_map_object_buffer */ + +#ifndef GL_ATI_meminfo +#define GL_ATI_meminfo 1 +#define GL_VBO_FREE_MEMORY_ATI 0x87FB +#define GL_TEXTURE_FREE_MEMORY_ATI 0x87FC +#define GL_RENDERBUFFER_FREE_MEMORY_ATI 0x87FD +#endif /* GL_ATI_meminfo */ + +#ifndef GL_ATI_pixel_format_float +#define GL_ATI_pixel_format_float 1 +#define GL_RGBA_FLOAT_MODE_ATI 0x8820 +#define GL_COLOR_CLEAR_UNCLAMPED_VALUE_ATI 0x8835 +#endif /* GL_ATI_pixel_format_float */ + +#ifndef GL_ATI_pn_triangles +#define GL_ATI_pn_triangles 1 +#define GL_PN_TRIANGLES_ATI 0x87F0 +#define GL_MAX_PN_TRIANGLES_TESSELATION_LEVEL_ATI 0x87F1 +#define GL_PN_TRIANGLES_POINT_MODE_ATI 0x87F2 +#define GL_PN_TRIANGLES_NORMAL_MODE_ATI 0x87F3 +#define GL_PN_TRIANGLES_TESSELATION_LEVEL_ATI 0x87F4 +#define GL_PN_TRIANGLES_POINT_MODE_LINEAR_ATI 0x87F5 +#define GL_PN_TRIANGLES_POINT_MODE_CUBIC_ATI 0x87F6 +#define GL_PN_TRIANGLES_NORMAL_MODE_LINEAR_ATI 0x87F7 +#define GL_PN_TRIANGLES_NORMAL_MODE_QUADRATIC_ATI 0x87F8 +typedef void (APIENTRYP PFNGLPNTRIANGLESIATIPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPNTRIANGLESFATIPROC) (GLenum pname, GLfloat param); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPNTrianglesiATI (GLenum pname, GLint param); +GLAPI void APIENTRY glPNTrianglesfATI (GLenum pname, GLfloat param); +#endif +#endif /* GL_ATI_pn_triangles */ + +#ifndef GL_ATI_separate_stencil +#define GL_ATI_separate_stencil 1 +#define GL_STENCIL_BACK_FUNC_ATI 0x8800 +#define GL_STENCIL_BACK_FAIL_ATI 0x8801 +#define GL_STENCIL_BACK_PASS_DEPTH_FAIL_ATI 0x8802 +#define GL_STENCIL_BACK_PASS_DEPTH_PASS_ATI 0x8803 +typedef void (APIENTRYP PFNGLSTENCILOPSEPARATEATIPROC) (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass); +typedef void (APIENTRYP PFNGLSTENCILFUNCSEPARATEATIPROC) (GLenum frontfunc, GLenum backfunc, GLint ref, GLuint mask); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glStencilOpSeparateATI (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass); +GLAPI void APIENTRY glStencilFuncSeparateATI (GLenum frontfunc, GLenum backfunc, GLint ref, GLuint mask); +#endif +#endif /* GL_ATI_separate_stencil */ + +#ifndef GL_ATI_text_fragment_shader +#define GL_ATI_text_fragment_shader 1 +#define GL_TEXT_FRAGMENT_SHADER_ATI 0x8200 +#endif /* GL_ATI_text_fragment_shader */ + +#ifndef GL_ATI_texture_env_combine3 +#define GL_ATI_texture_env_combine3 1 +#define GL_MODULATE_ADD_ATI 0x8744 +#define GL_MODULATE_SIGNED_ADD_ATI 0x8745 +#define GL_MODULATE_SUBTRACT_ATI 0x8746 +#endif /* GL_ATI_texture_env_combine3 */ + +#ifndef GL_ATI_texture_float +#define GL_ATI_texture_float 1 +#define GL_RGBA_FLOAT32_ATI 0x8814 +#define GL_RGB_FLOAT32_ATI 0x8815 +#define GL_ALPHA_FLOAT32_ATI 0x8816 +#define GL_INTENSITY_FLOAT32_ATI 0x8817 +#define GL_LUMINANCE_FLOAT32_ATI 0x8818 +#define GL_LUMINANCE_ALPHA_FLOAT32_ATI 0x8819 +#define GL_RGBA_FLOAT16_ATI 0x881A +#define GL_RGB_FLOAT16_ATI 0x881B +#define GL_ALPHA_FLOAT16_ATI 0x881C +#define GL_INTENSITY_FLOAT16_ATI 0x881D +#define GL_LUMINANCE_FLOAT16_ATI 0x881E +#define GL_LUMINANCE_ALPHA_FLOAT16_ATI 0x881F +#endif /* GL_ATI_texture_float */ + +#ifndef GL_ATI_texture_mirror_once +#define GL_ATI_texture_mirror_once 1 +#define GL_MIRROR_CLAMP_ATI 0x8742 +#define GL_MIRROR_CLAMP_TO_EDGE_ATI 0x8743 +#endif /* GL_ATI_texture_mirror_once */ + +#ifndef GL_ATI_vertex_array_object +#define GL_ATI_vertex_array_object 1 +#define GL_STATIC_ATI 0x8760 +#define GL_DYNAMIC_ATI 0x8761 +#define GL_PRESERVE_ATI 0x8762 +#define GL_DISCARD_ATI 0x8763 +#define GL_OBJECT_BUFFER_SIZE_ATI 0x8764 +#define GL_OBJECT_BUFFER_USAGE_ATI 0x8765 +#define GL_ARRAY_OBJECT_BUFFER_ATI 0x8766 +#define GL_ARRAY_OBJECT_OFFSET_ATI 0x8767 +typedef GLuint (APIENTRYP PFNGLNEWOBJECTBUFFERATIPROC) (GLsizei size, const void *pointer, GLenum usage); +typedef GLboolean (APIENTRYP PFNGLISOBJECTBUFFERATIPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLUPDATEOBJECTBUFFERATIPROC) (GLuint buffer, GLuint offset, GLsizei size, const void *pointer, GLenum preserve); +typedef void (APIENTRYP PFNGLGETOBJECTBUFFERFVATIPROC) (GLuint buffer, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETOBJECTBUFFERIVATIPROC) (GLuint buffer, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLFREEOBJECTBUFFERATIPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLARRAYOBJECTATIPROC) (GLenum array, GLint size, GLenum type, GLsizei stride, GLuint buffer, GLuint offset); +typedef void (APIENTRYP PFNGLGETARRAYOBJECTFVATIPROC) (GLenum array, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETARRAYOBJECTIVATIPROC) (GLenum array, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLVARIANTARRAYOBJECTATIPROC) (GLuint id, GLenum type, GLsizei stride, GLuint buffer, GLuint offset); +typedef void (APIENTRYP PFNGLGETVARIANTARRAYOBJECTFVATIPROC) (GLuint id, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVARIANTARRAYOBJECTIVATIPROC) (GLuint id, GLenum pname, GLint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLuint APIENTRY glNewObjectBufferATI (GLsizei size, const void *pointer, GLenum usage); +GLAPI GLboolean APIENTRY glIsObjectBufferATI (GLuint buffer); +GLAPI void APIENTRY glUpdateObjectBufferATI (GLuint buffer, GLuint offset, GLsizei size, const void *pointer, GLenum preserve); +GLAPI void APIENTRY glGetObjectBufferfvATI (GLuint buffer, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetObjectBufferivATI (GLuint buffer, GLenum pname, GLint *params); +GLAPI void APIENTRY glFreeObjectBufferATI (GLuint buffer); +GLAPI void APIENTRY glArrayObjectATI (GLenum array, GLint size, GLenum type, GLsizei stride, GLuint buffer, GLuint offset); +GLAPI void APIENTRY glGetArrayObjectfvATI (GLenum array, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetArrayObjectivATI (GLenum array, GLenum pname, GLint *params); +GLAPI void APIENTRY glVariantArrayObjectATI (GLuint id, GLenum type, GLsizei stride, GLuint buffer, GLuint offset); +GLAPI void APIENTRY glGetVariantArrayObjectfvATI (GLuint id, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetVariantArrayObjectivATI (GLuint id, GLenum pname, GLint *params); +#endif +#endif /* GL_ATI_vertex_array_object */ + +#ifndef GL_ATI_vertex_attrib_array_object +#define GL_ATI_vertex_attrib_array_object 1 +typedef void (APIENTRYP PFNGLVERTEXATTRIBARRAYOBJECTATIPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, GLuint buffer, GLuint offset); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBARRAYOBJECTFVATIPROC) (GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBARRAYOBJECTIVATIPROC) (GLuint index, GLenum pname, GLint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexAttribArrayObjectATI (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, GLuint buffer, GLuint offset); +GLAPI void APIENTRY glGetVertexAttribArrayObjectfvATI (GLuint index, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetVertexAttribArrayObjectivATI (GLuint index, GLenum pname, GLint *params); +#endif +#endif /* GL_ATI_vertex_attrib_array_object */ + +#ifndef GL_ATI_vertex_streams +#define GL_ATI_vertex_streams 1 +#define GL_MAX_VERTEX_STREAMS_ATI 0x876B +#define GL_VERTEX_STREAM0_ATI 0x876C +#define GL_VERTEX_STREAM1_ATI 0x876D +#define GL_VERTEX_STREAM2_ATI 0x876E +#define GL_VERTEX_STREAM3_ATI 0x876F +#define GL_VERTEX_STREAM4_ATI 0x8770 +#define GL_VERTEX_STREAM5_ATI 0x8771 +#define GL_VERTEX_STREAM6_ATI 0x8772 +#define GL_VERTEX_STREAM7_ATI 0x8773 +#define GL_VERTEX_SOURCE_ATI 0x8774 +typedef void (APIENTRYP PFNGLVERTEXSTREAM1SATIPROC) (GLenum stream, GLshort x); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1IATIPROC) (GLenum stream, GLint x); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1FATIPROC) (GLenum stream, GLfloat x); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1DATIPROC) (GLenum stream, GLdouble x); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2SATIPROC) (GLenum stream, GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2IATIPROC) (GLenum stream, GLint x, GLint y); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2FATIPROC) (GLenum stream, GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2DATIPROC) (GLenum stream, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3SATIPROC) (GLenum stream, GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3IATIPROC) (GLenum stream, GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3FATIPROC) (GLenum stream, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3DATIPROC) (GLenum stream, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4SATIPROC) (GLenum stream, GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4IATIPROC) (GLenum stream, GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4FATIPROC) (GLenum stream, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4DATIPROC) (GLenum stream, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3BATIPROC) (GLenum stream, GLbyte nx, GLbyte ny, GLbyte nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3BVATIPROC) (GLenum stream, const GLbyte *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3SATIPROC) (GLenum stream, GLshort nx, GLshort ny, GLshort nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3IATIPROC) (GLenum stream, GLint nx, GLint ny, GLint nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3FATIPROC) (GLenum stream, GLfloat nx, GLfloat ny, GLfloat nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3DATIPROC) (GLenum stream, GLdouble nx, GLdouble ny, GLdouble nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLCLIENTACTIVEVERTEXSTREAMATIPROC) (GLenum stream); +typedef void (APIENTRYP PFNGLVERTEXBLENDENVIATIPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLVERTEXBLENDENVFATIPROC) (GLenum pname, GLfloat param); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexStream1sATI (GLenum stream, GLshort x); +GLAPI void APIENTRY glVertexStream1svATI (GLenum stream, const GLshort *coords); +GLAPI void APIENTRY glVertexStream1iATI (GLenum stream, GLint x); +GLAPI void APIENTRY glVertexStream1ivATI (GLenum stream, const GLint *coords); +GLAPI void APIENTRY glVertexStream1fATI (GLenum stream, GLfloat x); +GLAPI void APIENTRY glVertexStream1fvATI (GLenum stream, const GLfloat *coords); +GLAPI void APIENTRY glVertexStream1dATI (GLenum stream, GLdouble x); +GLAPI void APIENTRY glVertexStream1dvATI (GLenum stream, const GLdouble *coords); +GLAPI void APIENTRY glVertexStream2sATI (GLenum stream, GLshort x, GLshort y); +GLAPI void APIENTRY glVertexStream2svATI (GLenum stream, const GLshort *coords); +GLAPI void APIENTRY glVertexStream2iATI (GLenum stream, GLint x, GLint y); +GLAPI void APIENTRY glVertexStream2ivATI (GLenum stream, const GLint *coords); +GLAPI void APIENTRY glVertexStream2fATI (GLenum stream, GLfloat x, GLfloat y); +GLAPI void APIENTRY glVertexStream2fvATI (GLenum stream, const GLfloat *coords); +GLAPI void APIENTRY glVertexStream2dATI (GLenum stream, GLdouble x, GLdouble y); +GLAPI void APIENTRY glVertexStream2dvATI (GLenum stream, const GLdouble *coords); +GLAPI void APIENTRY glVertexStream3sATI (GLenum stream, GLshort x, GLshort y, GLshort z); +GLAPI void APIENTRY glVertexStream3svATI (GLenum stream, const GLshort *coords); +GLAPI void APIENTRY glVertexStream3iATI (GLenum stream, GLint x, GLint y, GLint z); +GLAPI void APIENTRY glVertexStream3ivATI (GLenum stream, const GLint *coords); +GLAPI void APIENTRY glVertexStream3fATI (GLenum stream, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glVertexStream3fvATI (GLenum stream, const GLfloat *coords); +GLAPI void APIENTRY glVertexStream3dATI (GLenum stream, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glVertexStream3dvATI (GLenum stream, const GLdouble *coords); +GLAPI void APIENTRY glVertexStream4sATI (GLenum stream, GLshort x, GLshort y, GLshort z, GLshort w); +GLAPI void APIENTRY glVertexStream4svATI (GLenum stream, const GLshort *coords); +GLAPI void APIENTRY glVertexStream4iATI (GLenum stream, GLint x, GLint y, GLint z, GLint w); +GLAPI void APIENTRY glVertexStream4ivATI (GLenum stream, const GLint *coords); +GLAPI void APIENTRY glVertexStream4fATI (GLenum stream, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glVertexStream4fvATI (GLenum stream, const GLfloat *coords); +GLAPI void APIENTRY glVertexStream4dATI (GLenum stream, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glVertexStream4dvATI (GLenum stream, const GLdouble *coords); +GLAPI void APIENTRY glNormalStream3bATI (GLenum stream, GLbyte nx, GLbyte ny, GLbyte nz); +GLAPI void APIENTRY glNormalStream3bvATI (GLenum stream, const GLbyte *coords); +GLAPI void APIENTRY glNormalStream3sATI (GLenum stream, GLshort nx, GLshort ny, GLshort nz); +GLAPI void APIENTRY glNormalStream3svATI (GLenum stream, const GLshort *coords); +GLAPI void APIENTRY glNormalStream3iATI (GLenum stream, GLint nx, GLint ny, GLint nz); +GLAPI void APIENTRY glNormalStream3ivATI (GLenum stream, const GLint *coords); +GLAPI void APIENTRY glNormalStream3fATI (GLenum stream, GLfloat nx, GLfloat ny, GLfloat nz); +GLAPI void APIENTRY glNormalStream3fvATI (GLenum stream, const GLfloat *coords); +GLAPI void APIENTRY glNormalStream3dATI (GLenum stream, GLdouble nx, GLdouble ny, GLdouble nz); +GLAPI void APIENTRY glNormalStream3dvATI (GLenum stream, const GLdouble *coords); +GLAPI void APIENTRY glClientActiveVertexStreamATI (GLenum stream); +GLAPI void APIENTRY glVertexBlendEnviATI (GLenum pname, GLint param); +GLAPI void APIENTRY glVertexBlendEnvfATI (GLenum pname, GLfloat param); +#endif +#endif /* GL_ATI_vertex_streams */ + +#ifndef GL_EXT_422_pixels +#define GL_EXT_422_pixels 1 +#define GL_422_EXT 0x80CC +#define GL_422_REV_EXT 0x80CD +#define GL_422_AVERAGE_EXT 0x80CE +#define GL_422_REV_AVERAGE_EXT 0x80CF +#endif /* GL_EXT_422_pixels */ + +#ifndef GL_EXT_EGL_image_storage +#define GL_EXT_EGL_image_storage 1 +typedef void *GLeglImageOES; +typedef void (APIENTRYP PFNGLEGLIMAGETARGETTEXSTORAGEEXTPROC) (GLenum target, GLeglImageOES image, const GLint* attrib_list); +typedef void (APIENTRYP PFNGLEGLIMAGETARGETTEXTURESTORAGEEXTPROC) (GLuint texture, GLeglImageOES image, const GLint* attrib_list); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glEGLImageTargetTexStorageEXT (GLenum target, GLeglImageOES image, const GLint* attrib_list); +GLAPI void APIENTRY glEGLImageTargetTextureStorageEXT (GLuint texture, GLeglImageOES image, const GLint* attrib_list); +#endif +#endif /* GL_EXT_EGL_image_storage */ + +#ifndef GL_EXT_EGL_sync +#define GL_EXT_EGL_sync 1 +#endif /* GL_EXT_EGL_sync */ + +#ifndef GL_EXT_abgr +#define GL_EXT_abgr 1 +#define GL_ABGR_EXT 0x8000 +#endif /* GL_EXT_abgr */ + +#ifndef GL_EXT_bgra +#define GL_EXT_bgra 1 +#define GL_BGR_EXT 0x80E0 +#define GL_BGRA_EXT 0x80E1 +#endif /* GL_EXT_bgra */ + +#ifndef GL_EXT_bindable_uniform +#define GL_EXT_bindable_uniform 1 +#define GL_MAX_VERTEX_BINDABLE_UNIFORMS_EXT 0x8DE2 +#define GL_MAX_FRAGMENT_BINDABLE_UNIFORMS_EXT 0x8DE3 +#define GL_MAX_GEOMETRY_BINDABLE_UNIFORMS_EXT 0x8DE4 +#define GL_MAX_BINDABLE_UNIFORM_SIZE_EXT 0x8DED +#define GL_UNIFORM_BUFFER_EXT 0x8DEE +#define GL_UNIFORM_BUFFER_BINDING_EXT 0x8DEF +typedef void (APIENTRYP PFNGLUNIFORMBUFFEREXTPROC) (GLuint program, GLint location, GLuint buffer); +typedef GLint (APIENTRYP PFNGLGETUNIFORMBUFFERSIZEEXTPROC) (GLuint program, GLint location); +typedef GLintptr (APIENTRYP PFNGLGETUNIFORMOFFSETEXTPROC) (GLuint program, GLint location); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glUniformBufferEXT (GLuint program, GLint location, GLuint buffer); +GLAPI GLint APIENTRY glGetUniformBufferSizeEXT (GLuint program, GLint location); +GLAPI GLintptr APIENTRY glGetUniformOffsetEXT (GLuint program, GLint location); +#endif +#endif /* GL_EXT_bindable_uniform */ + +#ifndef GL_EXT_blend_color +#define GL_EXT_blend_color 1 +#define GL_CONSTANT_COLOR_EXT 0x8001 +#define GL_ONE_MINUS_CONSTANT_COLOR_EXT 0x8002 +#define GL_CONSTANT_ALPHA_EXT 0x8003 +#define GL_ONE_MINUS_CONSTANT_ALPHA_EXT 0x8004 +#define GL_BLEND_COLOR_EXT 0x8005 +typedef void (APIENTRYP PFNGLBLENDCOLOREXTPROC) (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendColorEXT (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +#endif +#endif /* GL_EXT_blend_color */ + +#ifndef GL_EXT_blend_equation_separate +#define GL_EXT_blend_equation_separate 1 +#define GL_BLEND_EQUATION_RGB_EXT 0x8009 +#define GL_BLEND_EQUATION_ALPHA_EXT 0x883D +typedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEEXTPROC) (GLenum modeRGB, GLenum modeAlpha); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendEquationSeparateEXT (GLenum modeRGB, GLenum modeAlpha); +#endif +#endif /* GL_EXT_blend_equation_separate */ + +#ifndef GL_EXT_blend_func_separate +#define GL_EXT_blend_func_separate 1 +#define GL_BLEND_DST_RGB_EXT 0x80C8 +#define GL_BLEND_SRC_RGB_EXT 0x80C9 +#define GL_BLEND_DST_ALPHA_EXT 0x80CA +#define GL_BLEND_SRC_ALPHA_EXT 0x80CB +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEEXTPROC) (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendFuncSeparateEXT (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +#endif +#endif /* GL_EXT_blend_func_separate */ + +#ifndef GL_EXT_blend_logic_op +#define GL_EXT_blend_logic_op 1 +#endif /* GL_EXT_blend_logic_op */ + +#ifndef GL_EXT_blend_minmax +#define GL_EXT_blend_minmax 1 +#define GL_MIN_EXT 0x8007 +#define GL_MAX_EXT 0x8008 +#define GL_FUNC_ADD_EXT 0x8006 +#define GL_BLEND_EQUATION_EXT 0x8009 +typedef void (APIENTRYP PFNGLBLENDEQUATIONEXTPROC) (GLenum mode); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendEquationEXT (GLenum mode); +#endif +#endif /* GL_EXT_blend_minmax */ + +#ifndef GL_EXT_blend_subtract +#define GL_EXT_blend_subtract 1 +#define GL_FUNC_SUBTRACT_EXT 0x800A +#define GL_FUNC_REVERSE_SUBTRACT_EXT 0x800B +#endif /* GL_EXT_blend_subtract */ + +#ifndef GL_EXT_clip_volume_hint +#define GL_EXT_clip_volume_hint 1 +#define GL_CLIP_VOLUME_CLIPPING_HINT_EXT 0x80F0 +#endif /* GL_EXT_clip_volume_hint */ + +#ifndef GL_EXT_cmyka +#define GL_EXT_cmyka 1 +#define GL_CMYK_EXT 0x800C +#define GL_CMYKA_EXT 0x800D +#define GL_PACK_CMYK_HINT_EXT 0x800E +#define GL_UNPACK_CMYK_HINT_EXT 0x800F +#endif /* GL_EXT_cmyka */ + +#ifndef GL_EXT_color_subtable +#define GL_EXT_color_subtable 1 +typedef void (APIENTRYP PFNGLCOLORSUBTABLEEXTPROC) (GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const void *data); +typedef void (APIENTRYP PFNGLCOPYCOLORSUBTABLEEXTPROC) (GLenum target, GLsizei start, GLint x, GLint y, GLsizei width); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorSubTableEXT (GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const void *data); +GLAPI void APIENTRY glCopyColorSubTableEXT (GLenum target, GLsizei start, GLint x, GLint y, GLsizei width); +#endif +#endif /* GL_EXT_color_subtable */ + +#ifndef GL_EXT_compiled_vertex_array +#define GL_EXT_compiled_vertex_array 1 +#define GL_ARRAY_ELEMENT_LOCK_FIRST_EXT 0x81A8 +#define GL_ARRAY_ELEMENT_LOCK_COUNT_EXT 0x81A9 +typedef void (APIENTRYP PFNGLLOCKARRAYSEXTPROC) (GLint first, GLsizei count); +typedef void (APIENTRYP PFNGLUNLOCKARRAYSEXTPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glLockArraysEXT (GLint first, GLsizei count); +GLAPI void APIENTRY glUnlockArraysEXT (void); +#endif +#endif /* GL_EXT_compiled_vertex_array */ + +#ifndef GL_EXT_convolution +#define GL_EXT_convolution 1 +#define GL_CONVOLUTION_1D_EXT 0x8010 +#define GL_CONVOLUTION_2D_EXT 0x8011 +#define GL_SEPARABLE_2D_EXT 0x8012 +#define GL_CONVOLUTION_BORDER_MODE_EXT 0x8013 +#define GL_CONVOLUTION_FILTER_SCALE_EXT 0x8014 +#define GL_CONVOLUTION_FILTER_BIAS_EXT 0x8015 +#define GL_REDUCE_EXT 0x8016 +#define GL_CONVOLUTION_FORMAT_EXT 0x8017 +#define GL_CONVOLUTION_WIDTH_EXT 0x8018 +#define GL_CONVOLUTION_HEIGHT_EXT 0x8019 +#define GL_MAX_CONVOLUTION_WIDTH_EXT 0x801A +#define GL_MAX_CONVOLUTION_HEIGHT_EXT 0x801B +#define GL_POST_CONVOLUTION_RED_SCALE_EXT 0x801C +#define GL_POST_CONVOLUTION_GREEN_SCALE_EXT 0x801D +#define GL_POST_CONVOLUTION_BLUE_SCALE_EXT 0x801E +#define GL_POST_CONVOLUTION_ALPHA_SCALE_EXT 0x801F +#define GL_POST_CONVOLUTION_RED_BIAS_EXT 0x8020 +#define GL_POST_CONVOLUTION_GREEN_BIAS_EXT 0x8021 +#define GL_POST_CONVOLUTION_BLUE_BIAS_EXT 0x8022 +#define GL_POST_CONVOLUTION_ALPHA_BIAS_EXT 0x8023 +typedef void (APIENTRYP PFNGLCONVOLUTIONFILTER1DEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const void *image); +typedef void (APIENTRYP PFNGLCONVOLUTIONFILTER2DEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *image); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERFEXTPROC) (GLenum target, GLenum pname, GLfloat params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERFVEXTPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERIEXTPROC) (GLenum target, GLenum pname, GLint params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERIVEXTPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOPYCONVOLUTIONFILTER1DEXTPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCOPYCONVOLUTIONFILTER2DEXTPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONFILTEREXTPROC) (GLenum target, GLenum format, GLenum type, void *image); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETSEPARABLEFILTEREXTPROC) (GLenum target, GLenum format, GLenum type, void *row, void *column, void *span); +typedef void (APIENTRYP PFNGLSEPARABLEFILTER2DEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *row, const void *column); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glConvolutionFilter1DEXT (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const void *image); +GLAPI void APIENTRY glConvolutionFilter2DEXT (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *image); +GLAPI void APIENTRY glConvolutionParameterfEXT (GLenum target, GLenum pname, GLfloat params); +GLAPI void APIENTRY glConvolutionParameterfvEXT (GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glConvolutionParameteriEXT (GLenum target, GLenum pname, GLint params); +GLAPI void APIENTRY glConvolutionParameterivEXT (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glCopyConvolutionFilter1DEXT (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +GLAPI void APIENTRY glCopyConvolutionFilter2DEXT (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glGetConvolutionFilterEXT (GLenum target, GLenum format, GLenum type, void *image); +GLAPI void APIENTRY glGetConvolutionParameterfvEXT (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetConvolutionParameterivEXT (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetSeparableFilterEXT (GLenum target, GLenum format, GLenum type, void *row, void *column, void *span); +GLAPI void APIENTRY glSeparableFilter2DEXT (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *row, const void *column); +#endif +#endif /* GL_EXT_convolution */ + +#ifndef GL_EXT_coordinate_frame +#define GL_EXT_coordinate_frame 1 +#define GL_TANGENT_ARRAY_EXT 0x8439 +#define GL_BINORMAL_ARRAY_EXT 0x843A +#define GL_CURRENT_TANGENT_EXT 0x843B +#define GL_CURRENT_BINORMAL_EXT 0x843C +#define GL_TANGENT_ARRAY_TYPE_EXT 0x843E +#define GL_TANGENT_ARRAY_STRIDE_EXT 0x843F +#define GL_BINORMAL_ARRAY_TYPE_EXT 0x8440 +#define GL_BINORMAL_ARRAY_STRIDE_EXT 0x8441 +#define GL_TANGENT_ARRAY_POINTER_EXT 0x8442 +#define GL_BINORMAL_ARRAY_POINTER_EXT 0x8443 +#define GL_MAP1_TANGENT_EXT 0x8444 +#define GL_MAP2_TANGENT_EXT 0x8445 +#define GL_MAP1_BINORMAL_EXT 0x8446 +#define GL_MAP2_BINORMAL_EXT 0x8447 +typedef void (APIENTRYP PFNGLTANGENT3BEXTPROC) (GLbyte tx, GLbyte ty, GLbyte tz); +typedef void (APIENTRYP PFNGLTANGENT3BVEXTPROC) (const GLbyte *v); +typedef void (APIENTRYP PFNGLTANGENT3DEXTPROC) (GLdouble tx, GLdouble ty, GLdouble tz); +typedef void (APIENTRYP PFNGLTANGENT3DVEXTPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLTANGENT3FEXTPROC) (GLfloat tx, GLfloat ty, GLfloat tz); +typedef void (APIENTRYP PFNGLTANGENT3FVEXTPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLTANGENT3IEXTPROC) (GLint tx, GLint ty, GLint tz); +typedef void (APIENTRYP PFNGLTANGENT3IVEXTPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLTANGENT3SEXTPROC) (GLshort tx, GLshort ty, GLshort tz); +typedef void (APIENTRYP PFNGLTANGENT3SVEXTPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLBINORMAL3BEXTPROC) (GLbyte bx, GLbyte by, GLbyte bz); +typedef void (APIENTRYP PFNGLBINORMAL3BVEXTPROC) (const GLbyte *v); +typedef void (APIENTRYP PFNGLBINORMAL3DEXTPROC) (GLdouble bx, GLdouble by, GLdouble bz); +typedef void (APIENTRYP PFNGLBINORMAL3DVEXTPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLBINORMAL3FEXTPROC) (GLfloat bx, GLfloat by, GLfloat bz); +typedef void (APIENTRYP PFNGLBINORMAL3FVEXTPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLBINORMAL3IEXTPROC) (GLint bx, GLint by, GLint bz); +typedef void (APIENTRYP PFNGLBINORMAL3IVEXTPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLBINORMAL3SEXTPROC) (GLshort bx, GLshort by, GLshort bz); +typedef void (APIENTRYP PFNGLBINORMAL3SVEXTPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLTANGENTPOINTEREXTPROC) (GLenum type, GLsizei stride, const void *pointer); +typedef void (APIENTRYP PFNGLBINORMALPOINTEREXTPROC) (GLenum type, GLsizei stride, const void *pointer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTangent3bEXT (GLbyte tx, GLbyte ty, GLbyte tz); +GLAPI void APIENTRY glTangent3bvEXT (const GLbyte *v); +GLAPI void APIENTRY glTangent3dEXT (GLdouble tx, GLdouble ty, GLdouble tz); +GLAPI void APIENTRY glTangent3dvEXT (const GLdouble *v); +GLAPI void APIENTRY glTangent3fEXT (GLfloat tx, GLfloat ty, GLfloat tz); +GLAPI void APIENTRY glTangent3fvEXT (const GLfloat *v); +GLAPI void APIENTRY glTangent3iEXT (GLint tx, GLint ty, GLint tz); +GLAPI void APIENTRY glTangent3ivEXT (const GLint *v); +GLAPI void APIENTRY glTangent3sEXT (GLshort tx, GLshort ty, GLshort tz); +GLAPI void APIENTRY glTangent3svEXT (const GLshort *v); +GLAPI void APIENTRY glBinormal3bEXT (GLbyte bx, GLbyte by, GLbyte bz); +GLAPI void APIENTRY glBinormal3bvEXT (const GLbyte *v); +GLAPI void APIENTRY glBinormal3dEXT (GLdouble bx, GLdouble by, GLdouble bz); +GLAPI void APIENTRY glBinormal3dvEXT (const GLdouble *v); +GLAPI void APIENTRY glBinormal3fEXT (GLfloat bx, GLfloat by, GLfloat bz); +GLAPI void APIENTRY glBinormal3fvEXT (const GLfloat *v); +GLAPI void APIENTRY glBinormal3iEXT (GLint bx, GLint by, GLint bz); +GLAPI void APIENTRY glBinormal3ivEXT (const GLint *v); +GLAPI void APIENTRY glBinormal3sEXT (GLshort bx, GLshort by, GLshort bz); +GLAPI void APIENTRY glBinormal3svEXT (const GLshort *v); +GLAPI void APIENTRY glTangentPointerEXT (GLenum type, GLsizei stride, const void *pointer); +GLAPI void APIENTRY glBinormalPointerEXT (GLenum type, GLsizei stride, const void *pointer); +#endif +#endif /* GL_EXT_coordinate_frame */ + +#ifndef GL_EXT_copy_texture +#define GL_EXT_copy_texture 1 +typedef void (APIENTRYP PFNGLCOPYTEXIMAGE1DEXTPROC) (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +typedef void (APIENTRYP PFNGLCOPYTEXIMAGE2DEXTPROC) (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE1DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE2DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE3DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCopyTexImage1DEXT (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +GLAPI void APIENTRY glCopyTexImage2DEXT (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +GLAPI void APIENTRY glCopyTexSubImage1DEXT (GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +GLAPI void APIENTRY glCopyTexSubImage2DEXT (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glCopyTexSubImage3DEXT (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +#endif +#endif /* GL_EXT_copy_texture */ + +#ifndef GL_EXT_cull_vertex +#define GL_EXT_cull_vertex 1 +#define GL_CULL_VERTEX_EXT 0x81AA +#define GL_CULL_VERTEX_EYE_POSITION_EXT 0x81AB +#define GL_CULL_VERTEX_OBJECT_POSITION_EXT 0x81AC +typedef void (APIENTRYP PFNGLCULLPARAMETERDVEXTPROC) (GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLCULLPARAMETERFVEXTPROC) (GLenum pname, GLfloat *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCullParameterdvEXT (GLenum pname, GLdouble *params); +GLAPI void APIENTRY glCullParameterfvEXT (GLenum pname, GLfloat *params); +#endif +#endif /* GL_EXT_cull_vertex */ + +#ifndef GL_EXT_debug_label +#define GL_EXT_debug_label 1 +#define GL_PROGRAM_PIPELINE_OBJECT_EXT 0x8A4F +#define GL_PROGRAM_OBJECT_EXT 0x8B40 +#define GL_SHADER_OBJECT_EXT 0x8B48 +#define GL_BUFFER_OBJECT_EXT 0x9151 +#define GL_QUERY_OBJECT_EXT 0x9153 +#define GL_VERTEX_ARRAY_OBJECT_EXT 0x9154 +typedef void (APIENTRYP PFNGLLABELOBJECTEXTPROC) (GLenum type, GLuint object, GLsizei length, const GLchar *label); +typedef void (APIENTRYP PFNGLGETOBJECTLABELEXTPROC) (GLenum type, GLuint object, GLsizei bufSize, GLsizei *length, GLchar *label); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glLabelObjectEXT (GLenum type, GLuint object, GLsizei length, const GLchar *label); +GLAPI void APIENTRY glGetObjectLabelEXT (GLenum type, GLuint object, GLsizei bufSize, GLsizei *length, GLchar *label); +#endif +#endif /* GL_EXT_debug_label */ + +#ifndef GL_EXT_debug_marker +#define GL_EXT_debug_marker 1 +typedef void (APIENTRYP PFNGLINSERTEVENTMARKEREXTPROC) (GLsizei length, const GLchar *marker); +typedef void (APIENTRYP PFNGLPUSHGROUPMARKEREXTPROC) (GLsizei length, const GLchar *marker); +typedef void (APIENTRYP PFNGLPOPGROUPMARKEREXTPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glInsertEventMarkerEXT (GLsizei length, const GLchar *marker); +GLAPI void APIENTRY glPushGroupMarkerEXT (GLsizei length, const GLchar *marker); +GLAPI void APIENTRY glPopGroupMarkerEXT (void); +#endif +#endif /* GL_EXT_debug_marker */ + +#ifndef GL_EXT_depth_bounds_test +#define GL_EXT_depth_bounds_test 1 +#define GL_DEPTH_BOUNDS_TEST_EXT 0x8890 +#define GL_DEPTH_BOUNDS_EXT 0x8891 +typedef void (APIENTRYP PFNGLDEPTHBOUNDSEXTPROC) (GLclampd zmin, GLclampd zmax); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDepthBoundsEXT (GLclampd zmin, GLclampd zmax); +#endif +#endif /* GL_EXT_depth_bounds_test */ + +#ifndef GL_EXT_direct_state_access +#define GL_EXT_direct_state_access 1 +#define GL_PROGRAM_MATRIX_EXT 0x8E2D +#define GL_TRANSPOSE_PROGRAM_MATRIX_EXT 0x8E2E +#define GL_PROGRAM_MATRIX_STACK_DEPTH_EXT 0x8E2F +typedef void (APIENTRYP PFNGLMATRIXLOADFEXTPROC) (GLenum mode, const GLfloat *m); +typedef void (APIENTRYP PFNGLMATRIXLOADDEXTPROC) (GLenum mode, const GLdouble *m); +typedef void (APIENTRYP PFNGLMATRIXMULTFEXTPROC) (GLenum mode, const GLfloat *m); +typedef void (APIENTRYP PFNGLMATRIXMULTDEXTPROC) (GLenum mode, const GLdouble *m); +typedef void (APIENTRYP PFNGLMATRIXLOADIDENTITYEXTPROC) (GLenum mode); +typedef void (APIENTRYP PFNGLMATRIXROTATEFEXTPROC) (GLenum mode, GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLMATRIXROTATEDEXTPROC) (GLenum mode, GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLMATRIXSCALEFEXTPROC) (GLenum mode, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLMATRIXSCALEDEXTPROC) (GLenum mode, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLMATRIXTRANSLATEFEXTPROC) (GLenum mode, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLMATRIXTRANSLATEDEXTPROC) (GLenum mode, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLMATRIXFRUSTUMEXTPROC) (GLenum mode, GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +typedef void (APIENTRYP PFNGLMATRIXORTHOEXTPROC) (GLenum mode, GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +typedef void (APIENTRYP PFNGLMATRIXPOPEXTPROC) (GLenum mode); +typedef void (APIENTRYP PFNGLMATRIXPUSHEXTPROC) (GLenum mode); +typedef void (APIENTRYP PFNGLCLIENTATTRIBDEFAULTEXTPROC) (GLbitfield mask); +typedef void (APIENTRYP PFNGLPUSHCLIENTATTRIBDEFAULTEXTPROC) (GLbitfield mask); +typedef void (APIENTRYP PFNGLTEXTUREPARAMETERFEXTPROC) (GLuint texture, GLenum target, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLTEXTUREPARAMETERFVEXTPROC) (GLuint texture, GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLTEXTUREPARAMETERIEXTPROC) (GLuint texture, GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLTEXTUREPARAMETERIVEXTPROC) (GLuint texture, GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLTEXTUREIMAGE1DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLTEXTUREIMAGE2DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLTEXTURESUBIMAGE1DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLTEXTURESUBIMAGE2DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLCOPYTEXTUREIMAGE1DEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +typedef void (APIENTRYP PFNGLCOPYTEXTUREIMAGE2DEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +typedef void (APIENTRYP PFNGLCOPYTEXTURESUBIMAGE1DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCOPYTEXTURESUBIMAGE2DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETTEXTUREIMAGEEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum format, GLenum type, void *pixels); +typedef void (APIENTRYP PFNGLGETTEXTUREPARAMETERFVEXTPROC) (GLuint texture, GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETTEXTUREPARAMETERIVEXTPROC) (GLuint texture, GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETTEXTURELEVELPARAMETERFVEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETTEXTURELEVELPARAMETERIVEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLTEXTUREIMAGE3DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLTEXTURESUBIMAGE3DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLCOPYTEXTURESUBIMAGE3DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLBINDMULTITEXTUREEXTPROC) (GLenum texunit, GLenum target, GLuint texture); +typedef void (APIENTRYP PFNGLMULTITEXCOORDPOINTEREXTPROC) (GLenum texunit, GLint size, GLenum type, GLsizei stride, const void *pointer); +typedef void (APIENTRYP PFNGLMULTITEXENVFEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLMULTITEXENVFVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLMULTITEXENVIEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLMULTITEXENVIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLMULTITEXGENDEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, GLdouble param); +typedef void (APIENTRYP PFNGLMULTITEXGENDVEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, const GLdouble *params); +typedef void (APIENTRYP PFNGLMULTITEXGENFEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLMULTITEXGENFVEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLMULTITEXGENIEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLMULTITEXGENIVEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLGETMULTITEXENVFVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMULTITEXENVIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMULTITEXGENDVEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLGETMULTITEXGENFVEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMULTITEXGENIVEXTPROC) (GLenum texunit, GLenum coord, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLMULTITEXPARAMETERIEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLMULTITEXPARAMETERIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLMULTITEXPARAMETERFEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLMULTITEXPARAMETERFVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLMULTITEXIMAGE1DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLMULTITEXIMAGE2DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLMULTITEXSUBIMAGE1DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLMULTITEXSUBIMAGE2DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLCOPYMULTITEXIMAGE1DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +typedef void (APIENTRYP PFNGLCOPYMULTITEXIMAGE2DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +typedef void (APIENTRYP PFNGLCOPYMULTITEXSUBIMAGE1DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCOPYMULTITEXSUBIMAGE2DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETMULTITEXIMAGEEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum format, GLenum type, void *pixels); +typedef void (APIENTRYP PFNGLGETMULTITEXPARAMETERFVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMULTITEXPARAMETERIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMULTITEXLEVELPARAMETERFVEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMULTITEXLEVELPARAMETERIVEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLMULTITEXIMAGE3DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLMULTITEXSUBIMAGE3DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLCOPYMULTITEXSUBIMAGE3DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLENABLECLIENTSTATEINDEXEDEXTPROC) (GLenum array, GLuint index); +typedef void (APIENTRYP PFNGLDISABLECLIENTSTATEINDEXEDEXTPROC) (GLenum array, GLuint index); +typedef void (APIENTRYP PFNGLGETFLOATINDEXEDVEXTPROC) (GLenum target, GLuint index, GLfloat *data); +typedef void (APIENTRYP PFNGLGETDOUBLEINDEXEDVEXTPROC) (GLenum target, GLuint index, GLdouble *data); +typedef void (APIENTRYP PFNGLGETPOINTERINDEXEDVEXTPROC) (GLenum target, GLuint index, void **data); +typedef void (APIENTRYP PFNGLENABLEINDEXEDEXTPROC) (GLenum target, GLuint index); +typedef void (APIENTRYP PFNGLDISABLEINDEXEDEXTPROC) (GLenum target, GLuint index); +typedef GLboolean (APIENTRYP PFNGLISENABLEDINDEXEDEXTPROC) (GLenum target, GLuint index); +typedef void (APIENTRYP PFNGLGETINTEGERINDEXEDVEXTPROC) (GLenum target, GLuint index, GLint *data); +typedef void (APIENTRYP PFNGLGETBOOLEANINDEXEDVEXTPROC) (GLenum target, GLuint index, GLboolean *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTUREIMAGE3DEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTUREIMAGE2DEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTUREIMAGE1DEXTPROC) (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTURESUBIMAGE3DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTURESUBIMAGE2DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTURESUBIMAGE1DEXTPROC) (GLuint texture, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *bits); +typedef void (APIENTRYP PFNGLGETCOMPRESSEDTEXTUREIMAGEEXTPROC) (GLuint texture, GLenum target, GLint lod, void *img); +typedef void (APIENTRYP PFNGLCOMPRESSEDMULTITEXIMAGE3DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDMULTITEXIMAGE2DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDMULTITEXIMAGE1DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDMULTITEXSUBIMAGE3DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDMULTITEXSUBIMAGE2DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *bits); +typedef void (APIENTRYP PFNGLCOMPRESSEDMULTITEXSUBIMAGE1DEXTPROC) (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *bits); +typedef void (APIENTRYP PFNGLGETCOMPRESSEDMULTITEXIMAGEEXTPROC) (GLenum texunit, GLenum target, GLint lod, void *img); +typedef void (APIENTRYP PFNGLMATRIXLOADTRANSPOSEFEXTPROC) (GLenum mode, const GLfloat *m); +typedef void (APIENTRYP PFNGLMATRIXLOADTRANSPOSEDEXTPROC) (GLenum mode, const GLdouble *m); +typedef void (APIENTRYP PFNGLMATRIXMULTTRANSPOSEFEXTPROC) (GLenum mode, const GLfloat *m); +typedef void (APIENTRYP PFNGLMATRIXMULTTRANSPOSEDEXTPROC) (GLenum mode, const GLdouble *m); +typedef void (APIENTRYP PFNGLNAMEDBUFFERDATAEXTPROC) (GLuint buffer, GLsizeiptr size, const void *data, GLenum usage); +typedef void (APIENTRYP PFNGLNAMEDBUFFERSUBDATAEXTPROC) (GLuint buffer, GLintptr offset, GLsizeiptr size, const void *data); +typedef void *(APIENTRYP PFNGLMAPNAMEDBUFFEREXTPROC) (GLuint buffer, GLenum access); +typedef GLboolean (APIENTRYP PFNGLUNMAPNAMEDBUFFEREXTPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLGETNAMEDBUFFERPARAMETERIVEXTPROC) (GLuint buffer, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETNAMEDBUFFERPOINTERVEXTPROC) (GLuint buffer, GLenum pname, void **params); +typedef void (APIENTRYP PFNGLGETNAMEDBUFFERSUBDATAEXTPROC) (GLuint buffer, GLintptr offset, GLsizeiptr size, void *data); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1FEXTPROC) (GLuint program, GLint location, GLfloat v0); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2FEXTPROC) (GLuint program, GLint location, GLfloat v0, GLfloat v1); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3FEXTPROC) (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4FEXTPROC) (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1IEXTPROC) (GLuint program, GLint location, GLint v0); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2IEXTPROC) (GLuint program, GLint location, GLint v0, GLint v1); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3IEXTPROC) (GLuint program, GLint location, GLint v0, GLint v1, GLint v2); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4IEXTPROC) (GLuint program, GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1FVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2FVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3FVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4FVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1IVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2IVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3IVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4IVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X3FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X2FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X4FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X2FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X4FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X3FVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLTEXTUREBUFFEREXTPROC) (GLuint texture, GLenum target, GLenum internalformat, GLuint buffer); +typedef void (APIENTRYP PFNGLMULTITEXBUFFEREXTPROC) (GLenum texunit, GLenum target, GLenum internalformat, GLuint buffer); +typedef void (APIENTRYP PFNGLTEXTUREPARAMETERIIVEXTPROC) (GLuint texture, GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLTEXTUREPARAMETERIUIVEXTPROC) (GLuint texture, GLenum target, GLenum pname, const GLuint *params); +typedef void (APIENTRYP PFNGLGETTEXTUREPARAMETERIIVEXTPROC) (GLuint texture, GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETTEXTUREPARAMETERIUIVEXTPROC) (GLuint texture, GLenum target, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLMULTITEXPARAMETERIIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLMULTITEXPARAMETERIUIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, const GLuint *params); +typedef void (APIENTRYP PFNGLGETMULTITEXPARAMETERIIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMULTITEXPARAMETERIUIVEXTPROC) (GLenum texunit, GLenum target, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1UIEXTPROC) (GLuint program, GLint location, GLuint v0); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2UIEXTPROC) (GLuint program, GLint location, GLuint v0, GLuint v1); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3UIEXTPROC) (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4UIEXTPROC) (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1UIVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2UIVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3UIVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4UIVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERS4FVEXTPROC) (GLuint program, GLenum target, GLuint index, GLsizei count, const GLfloat *params); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERI4IEXTPROC) (GLuint program, GLenum target, GLuint index, GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERI4IVEXTPROC) (GLuint program, GLenum target, GLuint index, const GLint *params); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERSI4IVEXTPROC) (GLuint program, GLenum target, GLuint index, GLsizei count, const GLint *params); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERI4UIEXTPROC) (GLuint program, GLenum target, GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERI4UIVEXTPROC) (GLuint program, GLenum target, GLuint index, const GLuint *params); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERSI4UIVEXTPROC) (GLuint program, GLenum target, GLuint index, GLsizei count, const GLuint *params); +typedef void (APIENTRYP PFNGLGETNAMEDPROGRAMLOCALPARAMETERIIVEXTPROC) (GLuint program, GLenum target, GLuint index, GLint *params); +typedef void (APIENTRYP PFNGLGETNAMEDPROGRAMLOCALPARAMETERIUIVEXTPROC) (GLuint program, GLenum target, GLuint index, GLuint *params); +typedef void (APIENTRYP PFNGLENABLECLIENTSTATEIEXTPROC) (GLenum array, GLuint index); +typedef void (APIENTRYP PFNGLDISABLECLIENTSTATEIEXTPROC) (GLenum array, GLuint index); +typedef void (APIENTRYP PFNGLGETFLOATI_VEXTPROC) (GLenum pname, GLuint index, GLfloat *params); +typedef void (APIENTRYP PFNGLGETDOUBLEI_VEXTPROC) (GLenum pname, GLuint index, GLdouble *params); +typedef void (APIENTRYP PFNGLGETPOINTERI_VEXTPROC) (GLenum pname, GLuint index, void **params); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMSTRINGEXTPROC) (GLuint program, GLenum target, GLenum format, GLsizei len, const void *string); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETER4DEXTPROC) (GLuint program, GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETER4DVEXTPROC) (GLuint program, GLenum target, GLuint index, const GLdouble *params); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETER4FEXTPROC) (GLuint program, GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETER4FVEXTPROC) (GLuint program, GLenum target, GLuint index, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETNAMEDPROGRAMLOCALPARAMETERDVEXTPROC) (GLuint program, GLenum target, GLuint index, GLdouble *params); +typedef void (APIENTRYP PFNGLGETNAMEDPROGRAMLOCALPARAMETERFVEXTPROC) (GLuint program, GLenum target, GLuint index, GLfloat *params); +typedef void (APIENTRYP PFNGLGETNAMEDPROGRAMIVEXTPROC) (GLuint program, GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETNAMEDPROGRAMSTRINGEXTPROC) (GLuint program, GLenum target, GLenum pname, void *string); +typedef void (APIENTRYP PFNGLNAMEDRENDERBUFFERSTORAGEEXTPROC) (GLuint renderbuffer, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETNAMEDRENDERBUFFERPARAMETERIVEXTPROC) (GLuint renderbuffer, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLNAMEDRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC) (GLuint renderbuffer, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLNAMEDRENDERBUFFERSTORAGEMULTISAMPLECOVERAGEEXTPROC) (GLuint renderbuffer, GLsizei coverageSamples, GLsizei colorSamples, GLenum internalformat, GLsizei width, GLsizei height); +typedef GLenum (APIENTRYP PFNGLCHECKNAMEDFRAMEBUFFERSTATUSEXTPROC) (GLuint framebuffer, GLenum target); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTURE1DEXTPROC) (GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTURE2DEXTPROC) (GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTURE3DEXTPROC) (GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERRENDERBUFFEREXTPROC) (GLuint framebuffer, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +typedef void (APIENTRYP PFNGLGETNAMEDFRAMEBUFFERATTACHMENTPARAMETERIVEXTPROC) (GLuint framebuffer, GLenum attachment, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGENERATETEXTUREMIPMAPEXTPROC) (GLuint texture, GLenum target); +typedef void (APIENTRYP PFNGLGENERATEMULTITEXMIPMAPEXTPROC) (GLenum texunit, GLenum target); +typedef void (APIENTRYP PFNGLFRAMEBUFFERDRAWBUFFEREXTPROC) (GLuint framebuffer, GLenum mode); +typedef void (APIENTRYP PFNGLFRAMEBUFFERDRAWBUFFERSEXTPROC) (GLuint framebuffer, GLsizei n, const GLenum *bufs); +typedef void (APIENTRYP PFNGLFRAMEBUFFERREADBUFFEREXTPROC) (GLuint framebuffer, GLenum mode); +typedef void (APIENTRYP PFNGLGETFRAMEBUFFERPARAMETERIVEXTPROC) (GLuint framebuffer, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLNAMEDCOPYBUFFERSUBDATAEXTPROC) (GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTUREEXTPROC) (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTURELAYEREXTPROC) (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLint layer); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTUREFACEEXTPROC) (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLenum face); +typedef void (APIENTRYP PFNGLTEXTURERENDERBUFFEREXTPROC) (GLuint texture, GLenum target, GLuint renderbuffer); +typedef void (APIENTRYP PFNGLMULTITEXRENDERBUFFEREXTPROC) (GLenum texunit, GLenum target, GLuint renderbuffer); +typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXOFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLint size, GLenum type, GLsizei stride, GLintptr offset); +typedef void (APIENTRYP PFNGLVERTEXARRAYCOLOROFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLint size, GLenum type, GLsizei stride, GLintptr offset); +typedef void (APIENTRYP PFNGLVERTEXARRAYEDGEFLAGOFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLsizei stride, GLintptr offset); +typedef void (APIENTRYP PFNGLVERTEXARRAYINDEXOFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLenum type, GLsizei stride, GLintptr offset); +typedef void (APIENTRYP PFNGLVERTEXARRAYNORMALOFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLenum type, GLsizei stride, GLintptr offset); +typedef void (APIENTRYP PFNGLVERTEXARRAYTEXCOORDOFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLint size, GLenum type, GLsizei stride, GLintptr offset); +typedef void (APIENTRYP PFNGLVERTEXARRAYMULTITEXCOORDOFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLenum texunit, GLint size, GLenum type, GLsizei stride, GLintptr offset); +typedef void (APIENTRYP PFNGLVERTEXARRAYFOGCOORDOFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLenum type, GLsizei stride, GLintptr offset); +typedef void (APIENTRYP PFNGLVERTEXARRAYSECONDARYCOLOROFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLint size, GLenum type, GLsizei stride, GLintptr offset); +typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXATTRIBOFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, GLintptr offset); +typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXATTRIBIOFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLuint index, GLint size, GLenum type, GLsizei stride, GLintptr offset); +typedef void (APIENTRYP PFNGLENABLEVERTEXARRAYEXTPROC) (GLuint vaobj, GLenum array); +typedef void (APIENTRYP PFNGLDISABLEVERTEXARRAYEXTPROC) (GLuint vaobj, GLenum array); +typedef void (APIENTRYP PFNGLENABLEVERTEXARRAYATTRIBEXTPROC) (GLuint vaobj, GLuint index); +typedef void (APIENTRYP PFNGLDISABLEVERTEXARRAYATTRIBEXTPROC) (GLuint vaobj, GLuint index); +typedef void (APIENTRYP PFNGLGETVERTEXARRAYINTEGERVEXTPROC) (GLuint vaobj, GLenum pname, GLint *param); +typedef void (APIENTRYP PFNGLGETVERTEXARRAYPOINTERVEXTPROC) (GLuint vaobj, GLenum pname, void **param); +typedef void (APIENTRYP PFNGLGETVERTEXARRAYINTEGERI_VEXTPROC) (GLuint vaobj, GLuint index, GLenum pname, GLint *param); +typedef void (APIENTRYP PFNGLGETVERTEXARRAYPOINTERI_VEXTPROC) (GLuint vaobj, GLuint index, GLenum pname, void **param); +typedef void *(APIENTRYP PFNGLMAPNAMEDBUFFERRANGEEXTPROC) (GLuint buffer, GLintptr offset, GLsizeiptr length, GLbitfield access); +typedef void (APIENTRYP PFNGLFLUSHMAPPEDNAMEDBUFFERRANGEEXTPROC) (GLuint buffer, GLintptr offset, GLsizeiptr length); +typedef void (APIENTRYP PFNGLNAMEDBUFFERSTORAGEEXTPROC) (GLuint buffer, GLsizeiptr size, const void *data, GLbitfield flags); +typedef void (APIENTRYP PFNGLCLEARNAMEDBUFFERDATAEXTPROC) (GLuint buffer, GLenum internalformat, GLenum format, GLenum type, const void *data); +typedef void (APIENTRYP PFNGLCLEARNAMEDBUFFERSUBDATAEXTPROC) (GLuint buffer, GLenum internalformat, GLsizeiptr offset, GLsizeiptr size, GLenum format, GLenum type, const void *data); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERPARAMETERIEXTPROC) (GLuint framebuffer, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLGETNAMEDFRAMEBUFFERPARAMETERIVEXTPROC) (GLuint framebuffer, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1DEXTPROC) (GLuint program, GLint location, GLdouble x); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2DEXTPROC) (GLuint program, GLint location, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3DEXTPROC) (GLuint program, GLint location, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4DEXTPROC) (GLuint program, GLint location, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM1DVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM2DVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM3DVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORM4DVEXTPROC) (GLuint program, GLint location, GLsizei count, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X3DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X4DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X2DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X4DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X2DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X3DVEXTPROC) (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +typedef void (APIENTRYP PFNGLTEXTUREBUFFERRANGEEXTPROC) (GLuint texture, GLenum target, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size); +typedef void (APIENTRYP PFNGLTEXTURESTORAGE1DEXTPROC) (GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width); +typedef void (APIENTRYP PFNGLTEXTURESTORAGE2DEXTPROC) (GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLTEXTURESTORAGE3DEXTPROC) (GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth); +typedef void (APIENTRYP PFNGLTEXTURESTORAGE2DMULTISAMPLEEXTPROC) (GLuint texture, GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations); +typedef void (APIENTRYP PFNGLTEXTURESTORAGE3DMULTISAMPLEEXTPROC) (GLuint texture, GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations); +typedef void (APIENTRYP PFNGLVERTEXARRAYBINDVERTEXBUFFEREXTPROC) (GLuint vaobj, GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride); +typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXATTRIBFORMATEXTPROC) (GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset); +typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXATTRIBIFORMATEXTPROC) (GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); +typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXATTRIBLFORMATEXTPROC) (GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); +typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXATTRIBBINDINGEXTPROC) (GLuint vaobj, GLuint attribindex, GLuint bindingindex); +typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXBINDINGDIVISOREXTPROC) (GLuint vaobj, GLuint bindingindex, GLuint divisor); +typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXATTRIBLOFFSETEXTPROC) (GLuint vaobj, GLuint buffer, GLuint index, GLint size, GLenum type, GLsizei stride, GLintptr offset); +typedef void (APIENTRYP PFNGLTEXTUREPAGECOMMITMENTEXTPROC) (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLboolean commit); +typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXATTRIBDIVISOREXTPROC) (GLuint vaobj, GLuint index, GLuint divisor); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMatrixLoadfEXT (GLenum mode, const GLfloat *m); +GLAPI void APIENTRY glMatrixLoaddEXT (GLenum mode, const GLdouble *m); +GLAPI void APIENTRY glMatrixMultfEXT (GLenum mode, const GLfloat *m); +GLAPI void APIENTRY glMatrixMultdEXT (GLenum mode, const GLdouble *m); +GLAPI void APIENTRY glMatrixLoadIdentityEXT (GLenum mode); +GLAPI void APIENTRY glMatrixRotatefEXT (GLenum mode, GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glMatrixRotatedEXT (GLenum mode, GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glMatrixScalefEXT (GLenum mode, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glMatrixScaledEXT (GLenum mode, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glMatrixTranslatefEXT (GLenum mode, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glMatrixTranslatedEXT (GLenum mode, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glMatrixFrustumEXT (GLenum mode, GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +GLAPI void APIENTRY glMatrixOrthoEXT (GLenum mode, GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +GLAPI void APIENTRY glMatrixPopEXT (GLenum mode); +GLAPI void APIENTRY glMatrixPushEXT (GLenum mode); +GLAPI void APIENTRY glClientAttribDefaultEXT (GLbitfield mask); +GLAPI void APIENTRY glPushClientAttribDefaultEXT (GLbitfield mask); +GLAPI void APIENTRY glTextureParameterfEXT (GLuint texture, GLenum target, GLenum pname, GLfloat param); +GLAPI void APIENTRY glTextureParameterfvEXT (GLuint texture, GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glTextureParameteriEXT (GLuint texture, GLenum target, GLenum pname, GLint param); +GLAPI void APIENTRY glTextureParameterivEXT (GLuint texture, GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glTextureImage1DEXT (GLuint texture, GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glTextureImage2DEXT (GLuint texture, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glTextureSubImage1DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glTextureSubImage2DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glCopyTextureImage1DEXT (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +GLAPI void APIENTRY glCopyTextureImage2DEXT (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +GLAPI void APIENTRY glCopyTextureSubImage1DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +GLAPI void APIENTRY glCopyTextureSubImage2DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glGetTextureImageEXT (GLuint texture, GLenum target, GLint level, GLenum format, GLenum type, void *pixels); +GLAPI void APIENTRY glGetTextureParameterfvEXT (GLuint texture, GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetTextureParameterivEXT (GLuint texture, GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetTextureLevelParameterfvEXT (GLuint texture, GLenum target, GLint level, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetTextureLevelParameterivEXT (GLuint texture, GLenum target, GLint level, GLenum pname, GLint *params); +GLAPI void APIENTRY glTextureImage3DEXT (GLuint texture, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glTextureSubImage3DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glCopyTextureSubImage3DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glBindMultiTextureEXT (GLenum texunit, GLenum target, GLuint texture); +GLAPI void APIENTRY glMultiTexCoordPointerEXT (GLenum texunit, GLint size, GLenum type, GLsizei stride, const void *pointer); +GLAPI void APIENTRY glMultiTexEnvfEXT (GLenum texunit, GLenum target, GLenum pname, GLfloat param); +GLAPI void APIENTRY glMultiTexEnvfvEXT (GLenum texunit, GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glMultiTexEnviEXT (GLenum texunit, GLenum target, GLenum pname, GLint param); +GLAPI void APIENTRY glMultiTexEnvivEXT (GLenum texunit, GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glMultiTexGendEXT (GLenum texunit, GLenum coord, GLenum pname, GLdouble param); +GLAPI void APIENTRY glMultiTexGendvEXT (GLenum texunit, GLenum coord, GLenum pname, const GLdouble *params); +GLAPI void APIENTRY glMultiTexGenfEXT (GLenum texunit, GLenum coord, GLenum pname, GLfloat param); +GLAPI void APIENTRY glMultiTexGenfvEXT (GLenum texunit, GLenum coord, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glMultiTexGeniEXT (GLenum texunit, GLenum coord, GLenum pname, GLint param); +GLAPI void APIENTRY glMultiTexGenivEXT (GLenum texunit, GLenum coord, GLenum pname, const GLint *params); +GLAPI void APIENTRY glGetMultiTexEnvfvEXT (GLenum texunit, GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetMultiTexEnvivEXT (GLenum texunit, GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetMultiTexGendvEXT (GLenum texunit, GLenum coord, GLenum pname, GLdouble *params); +GLAPI void APIENTRY glGetMultiTexGenfvEXT (GLenum texunit, GLenum coord, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetMultiTexGenivEXT (GLenum texunit, GLenum coord, GLenum pname, GLint *params); +GLAPI void APIENTRY glMultiTexParameteriEXT (GLenum texunit, GLenum target, GLenum pname, GLint param); +GLAPI void APIENTRY glMultiTexParameterivEXT (GLenum texunit, GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glMultiTexParameterfEXT (GLenum texunit, GLenum target, GLenum pname, GLfloat param); +GLAPI void APIENTRY glMultiTexParameterfvEXT (GLenum texunit, GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glMultiTexImage1DEXT (GLenum texunit, GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glMultiTexImage2DEXT (GLenum texunit, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glMultiTexSubImage1DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glMultiTexSubImage2DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glCopyMultiTexImage1DEXT (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +GLAPI void APIENTRY glCopyMultiTexImage2DEXT (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +GLAPI void APIENTRY glCopyMultiTexSubImage1DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +GLAPI void APIENTRY glCopyMultiTexSubImage2DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glGetMultiTexImageEXT (GLenum texunit, GLenum target, GLint level, GLenum format, GLenum type, void *pixels); +GLAPI void APIENTRY glGetMultiTexParameterfvEXT (GLenum texunit, GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetMultiTexParameterivEXT (GLenum texunit, GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetMultiTexLevelParameterfvEXT (GLenum texunit, GLenum target, GLint level, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetMultiTexLevelParameterivEXT (GLenum texunit, GLenum target, GLint level, GLenum pname, GLint *params); +GLAPI void APIENTRY glMultiTexImage3DEXT (GLenum texunit, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glMultiTexSubImage3DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glCopyMultiTexSubImage3DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glEnableClientStateIndexedEXT (GLenum array, GLuint index); +GLAPI void APIENTRY glDisableClientStateIndexedEXT (GLenum array, GLuint index); +GLAPI void APIENTRY glGetFloatIndexedvEXT (GLenum target, GLuint index, GLfloat *data); +GLAPI void APIENTRY glGetDoubleIndexedvEXT (GLenum target, GLuint index, GLdouble *data); +GLAPI void APIENTRY glGetPointerIndexedvEXT (GLenum target, GLuint index, void **data); +GLAPI void APIENTRY glEnableIndexedEXT (GLenum target, GLuint index); +GLAPI void APIENTRY glDisableIndexedEXT (GLenum target, GLuint index); +GLAPI GLboolean APIENTRY glIsEnabledIndexedEXT (GLenum target, GLuint index); +GLAPI void APIENTRY glGetIntegerIndexedvEXT (GLenum target, GLuint index, GLint *data); +GLAPI void APIENTRY glGetBooleanIndexedvEXT (GLenum target, GLuint index, GLboolean *data); +GLAPI void APIENTRY glCompressedTextureImage3DEXT (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *bits); +GLAPI void APIENTRY glCompressedTextureImage2DEXT (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *bits); +GLAPI void APIENTRY glCompressedTextureImage1DEXT (GLuint texture, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void *bits); +GLAPI void APIENTRY glCompressedTextureSubImage3DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *bits); +GLAPI void APIENTRY glCompressedTextureSubImage2DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *bits); +GLAPI void APIENTRY glCompressedTextureSubImage1DEXT (GLuint texture, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *bits); +GLAPI void APIENTRY glGetCompressedTextureImageEXT (GLuint texture, GLenum target, GLint lod, void *img); +GLAPI void APIENTRY glCompressedMultiTexImage3DEXT (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *bits); +GLAPI void APIENTRY glCompressedMultiTexImage2DEXT (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *bits); +GLAPI void APIENTRY glCompressedMultiTexImage1DEXT (GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void *bits); +GLAPI void APIENTRY glCompressedMultiTexSubImage3DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *bits); +GLAPI void APIENTRY glCompressedMultiTexSubImage2DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *bits); +GLAPI void APIENTRY glCompressedMultiTexSubImage1DEXT (GLenum texunit, GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *bits); +GLAPI void APIENTRY glGetCompressedMultiTexImageEXT (GLenum texunit, GLenum target, GLint lod, void *img); +GLAPI void APIENTRY glMatrixLoadTransposefEXT (GLenum mode, const GLfloat *m); +GLAPI void APIENTRY glMatrixLoadTransposedEXT (GLenum mode, const GLdouble *m); +GLAPI void APIENTRY glMatrixMultTransposefEXT (GLenum mode, const GLfloat *m); +GLAPI void APIENTRY glMatrixMultTransposedEXT (GLenum mode, const GLdouble *m); +GLAPI void APIENTRY glNamedBufferDataEXT (GLuint buffer, GLsizeiptr size, const void *data, GLenum usage); +GLAPI void APIENTRY glNamedBufferSubDataEXT (GLuint buffer, GLintptr offset, GLsizeiptr size, const void *data); +GLAPI void *APIENTRY glMapNamedBufferEXT (GLuint buffer, GLenum access); +GLAPI GLboolean APIENTRY glUnmapNamedBufferEXT (GLuint buffer); +GLAPI void APIENTRY glGetNamedBufferParameterivEXT (GLuint buffer, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetNamedBufferPointervEXT (GLuint buffer, GLenum pname, void **params); +GLAPI void APIENTRY glGetNamedBufferSubDataEXT (GLuint buffer, GLintptr offset, GLsizeiptr size, void *data); +GLAPI void APIENTRY glProgramUniform1fEXT (GLuint program, GLint location, GLfloat v0); +GLAPI void APIENTRY glProgramUniform2fEXT (GLuint program, GLint location, GLfloat v0, GLfloat v1); +GLAPI void APIENTRY glProgramUniform3fEXT (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +GLAPI void APIENTRY glProgramUniform4fEXT (GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +GLAPI void APIENTRY glProgramUniform1iEXT (GLuint program, GLint location, GLint v0); +GLAPI void APIENTRY glProgramUniform2iEXT (GLuint program, GLint location, GLint v0, GLint v1); +GLAPI void APIENTRY glProgramUniform3iEXT (GLuint program, GLint location, GLint v0, GLint v1, GLint v2); +GLAPI void APIENTRY glProgramUniform4iEXT (GLuint program, GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +GLAPI void APIENTRY glProgramUniform1fvEXT (GLuint program, GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glProgramUniform2fvEXT (GLuint program, GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glProgramUniform3fvEXT (GLuint program, GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glProgramUniform4fvEXT (GLuint program, GLint location, GLsizei count, const GLfloat *value); +GLAPI void APIENTRY glProgramUniform1ivEXT (GLuint program, GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glProgramUniform2ivEXT (GLuint program, GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glProgramUniform3ivEXT (GLuint program, GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glProgramUniform4ivEXT (GLuint program, GLint location, GLsizei count, const GLint *value); +GLAPI void APIENTRY glProgramUniformMatrix2fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix3fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix4fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix2x3fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix3x2fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix2x4fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix4x2fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix3x4fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glProgramUniformMatrix4x3fvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +GLAPI void APIENTRY glTextureBufferEXT (GLuint texture, GLenum target, GLenum internalformat, GLuint buffer); +GLAPI void APIENTRY glMultiTexBufferEXT (GLenum texunit, GLenum target, GLenum internalformat, GLuint buffer); +GLAPI void APIENTRY glTextureParameterIivEXT (GLuint texture, GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glTextureParameterIuivEXT (GLuint texture, GLenum target, GLenum pname, const GLuint *params); +GLAPI void APIENTRY glGetTextureParameterIivEXT (GLuint texture, GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetTextureParameterIuivEXT (GLuint texture, GLenum target, GLenum pname, GLuint *params); +GLAPI void APIENTRY glMultiTexParameterIivEXT (GLenum texunit, GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glMultiTexParameterIuivEXT (GLenum texunit, GLenum target, GLenum pname, const GLuint *params); +GLAPI void APIENTRY glGetMultiTexParameterIivEXT (GLenum texunit, GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetMultiTexParameterIuivEXT (GLenum texunit, GLenum target, GLenum pname, GLuint *params); +GLAPI void APIENTRY glProgramUniform1uiEXT (GLuint program, GLint location, GLuint v0); +GLAPI void APIENTRY glProgramUniform2uiEXT (GLuint program, GLint location, GLuint v0, GLuint v1); +GLAPI void APIENTRY glProgramUniform3uiEXT (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2); +GLAPI void APIENTRY glProgramUniform4uiEXT (GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +GLAPI void APIENTRY glProgramUniform1uivEXT (GLuint program, GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glProgramUniform2uivEXT (GLuint program, GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glProgramUniform3uivEXT (GLuint program, GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glProgramUniform4uivEXT (GLuint program, GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glNamedProgramLocalParameters4fvEXT (GLuint program, GLenum target, GLuint index, GLsizei count, const GLfloat *params); +GLAPI void APIENTRY glNamedProgramLocalParameterI4iEXT (GLuint program, GLenum target, GLuint index, GLint x, GLint y, GLint z, GLint w); +GLAPI void APIENTRY glNamedProgramLocalParameterI4ivEXT (GLuint program, GLenum target, GLuint index, const GLint *params); +GLAPI void APIENTRY glNamedProgramLocalParametersI4ivEXT (GLuint program, GLenum target, GLuint index, GLsizei count, const GLint *params); +GLAPI void APIENTRY glNamedProgramLocalParameterI4uiEXT (GLuint program, GLenum target, GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +GLAPI void APIENTRY glNamedProgramLocalParameterI4uivEXT (GLuint program, GLenum target, GLuint index, const GLuint *params); +GLAPI void APIENTRY glNamedProgramLocalParametersI4uivEXT (GLuint program, GLenum target, GLuint index, GLsizei count, const GLuint *params); +GLAPI void APIENTRY glGetNamedProgramLocalParameterIivEXT (GLuint program, GLenum target, GLuint index, GLint *params); +GLAPI void APIENTRY glGetNamedProgramLocalParameterIuivEXT (GLuint program, GLenum target, GLuint index, GLuint *params); +GLAPI void APIENTRY glEnableClientStateiEXT (GLenum array, GLuint index); +GLAPI void APIENTRY glDisableClientStateiEXT (GLenum array, GLuint index); +GLAPI void APIENTRY glGetFloati_vEXT (GLenum pname, GLuint index, GLfloat *params); +GLAPI void APIENTRY glGetDoublei_vEXT (GLenum pname, GLuint index, GLdouble *params); +GLAPI void APIENTRY glGetPointeri_vEXT (GLenum pname, GLuint index, void **params); +GLAPI void APIENTRY glNamedProgramStringEXT (GLuint program, GLenum target, GLenum format, GLsizei len, const void *string); +GLAPI void APIENTRY glNamedProgramLocalParameter4dEXT (GLuint program, GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glNamedProgramLocalParameter4dvEXT (GLuint program, GLenum target, GLuint index, const GLdouble *params); +GLAPI void APIENTRY glNamedProgramLocalParameter4fEXT (GLuint program, GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glNamedProgramLocalParameter4fvEXT (GLuint program, GLenum target, GLuint index, const GLfloat *params); +GLAPI void APIENTRY glGetNamedProgramLocalParameterdvEXT (GLuint program, GLenum target, GLuint index, GLdouble *params); +GLAPI void APIENTRY glGetNamedProgramLocalParameterfvEXT (GLuint program, GLenum target, GLuint index, GLfloat *params); +GLAPI void APIENTRY glGetNamedProgramivEXT (GLuint program, GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetNamedProgramStringEXT (GLuint program, GLenum target, GLenum pname, void *string); +GLAPI void APIENTRY glNamedRenderbufferStorageEXT (GLuint renderbuffer, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glGetNamedRenderbufferParameterivEXT (GLuint renderbuffer, GLenum pname, GLint *params); +GLAPI void APIENTRY glNamedRenderbufferStorageMultisampleEXT (GLuint renderbuffer, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glNamedRenderbufferStorageMultisampleCoverageEXT (GLuint renderbuffer, GLsizei coverageSamples, GLsizei colorSamples, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI GLenum APIENTRY glCheckNamedFramebufferStatusEXT (GLuint framebuffer, GLenum target); +GLAPI void APIENTRY glNamedFramebufferTexture1DEXT (GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +GLAPI void APIENTRY glNamedFramebufferTexture2DEXT (GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +GLAPI void APIENTRY glNamedFramebufferTexture3DEXT (GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); +GLAPI void APIENTRY glNamedFramebufferRenderbufferEXT (GLuint framebuffer, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +GLAPI void APIENTRY glGetNamedFramebufferAttachmentParameterivEXT (GLuint framebuffer, GLenum attachment, GLenum pname, GLint *params); +GLAPI void APIENTRY glGenerateTextureMipmapEXT (GLuint texture, GLenum target); +GLAPI void APIENTRY glGenerateMultiTexMipmapEXT (GLenum texunit, GLenum target); +GLAPI void APIENTRY glFramebufferDrawBufferEXT (GLuint framebuffer, GLenum mode); +GLAPI void APIENTRY glFramebufferDrawBuffersEXT (GLuint framebuffer, GLsizei n, const GLenum *bufs); +GLAPI void APIENTRY glFramebufferReadBufferEXT (GLuint framebuffer, GLenum mode); +GLAPI void APIENTRY glGetFramebufferParameterivEXT (GLuint framebuffer, GLenum pname, GLint *params); +GLAPI void APIENTRY glNamedCopyBufferSubDataEXT (GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); +GLAPI void APIENTRY glNamedFramebufferTextureEXT (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level); +GLAPI void APIENTRY glNamedFramebufferTextureLayerEXT (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLint layer); +GLAPI void APIENTRY glNamedFramebufferTextureFaceEXT (GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLenum face); +GLAPI void APIENTRY glTextureRenderbufferEXT (GLuint texture, GLenum target, GLuint renderbuffer); +GLAPI void APIENTRY glMultiTexRenderbufferEXT (GLenum texunit, GLenum target, GLuint renderbuffer); +GLAPI void APIENTRY glVertexArrayVertexOffsetEXT (GLuint vaobj, GLuint buffer, GLint size, GLenum type, GLsizei stride, GLintptr offset); +GLAPI void APIENTRY glVertexArrayColorOffsetEXT (GLuint vaobj, GLuint buffer, GLint size, GLenum type, GLsizei stride, GLintptr offset); +GLAPI void APIENTRY glVertexArrayEdgeFlagOffsetEXT (GLuint vaobj, GLuint buffer, GLsizei stride, GLintptr offset); +GLAPI void APIENTRY glVertexArrayIndexOffsetEXT (GLuint vaobj, GLuint buffer, GLenum type, GLsizei stride, GLintptr offset); +GLAPI void APIENTRY glVertexArrayNormalOffsetEXT (GLuint vaobj, GLuint buffer, GLenum type, GLsizei stride, GLintptr offset); +GLAPI void APIENTRY glVertexArrayTexCoordOffsetEXT (GLuint vaobj, GLuint buffer, GLint size, GLenum type, GLsizei stride, GLintptr offset); +GLAPI void APIENTRY glVertexArrayMultiTexCoordOffsetEXT (GLuint vaobj, GLuint buffer, GLenum texunit, GLint size, GLenum type, GLsizei stride, GLintptr offset); +GLAPI void APIENTRY glVertexArrayFogCoordOffsetEXT (GLuint vaobj, GLuint buffer, GLenum type, GLsizei stride, GLintptr offset); +GLAPI void APIENTRY glVertexArraySecondaryColorOffsetEXT (GLuint vaobj, GLuint buffer, GLint size, GLenum type, GLsizei stride, GLintptr offset); +GLAPI void APIENTRY glVertexArrayVertexAttribOffsetEXT (GLuint vaobj, GLuint buffer, GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, GLintptr offset); +GLAPI void APIENTRY glVertexArrayVertexAttribIOffsetEXT (GLuint vaobj, GLuint buffer, GLuint index, GLint size, GLenum type, GLsizei stride, GLintptr offset); +GLAPI void APIENTRY glEnableVertexArrayEXT (GLuint vaobj, GLenum array); +GLAPI void APIENTRY glDisableVertexArrayEXT (GLuint vaobj, GLenum array); +GLAPI void APIENTRY glEnableVertexArrayAttribEXT (GLuint vaobj, GLuint index); +GLAPI void APIENTRY glDisableVertexArrayAttribEXT (GLuint vaobj, GLuint index); +GLAPI void APIENTRY glGetVertexArrayIntegervEXT (GLuint vaobj, GLenum pname, GLint *param); +GLAPI void APIENTRY glGetVertexArrayPointervEXT (GLuint vaobj, GLenum pname, void **param); +GLAPI void APIENTRY glGetVertexArrayIntegeri_vEXT (GLuint vaobj, GLuint index, GLenum pname, GLint *param); +GLAPI void APIENTRY glGetVertexArrayPointeri_vEXT (GLuint vaobj, GLuint index, GLenum pname, void **param); +GLAPI void *APIENTRY glMapNamedBufferRangeEXT (GLuint buffer, GLintptr offset, GLsizeiptr length, GLbitfield access); +GLAPI void APIENTRY glFlushMappedNamedBufferRangeEXT (GLuint buffer, GLintptr offset, GLsizeiptr length); +GLAPI void APIENTRY glNamedBufferStorageEXT (GLuint buffer, GLsizeiptr size, const void *data, GLbitfield flags); +GLAPI void APIENTRY glClearNamedBufferDataEXT (GLuint buffer, GLenum internalformat, GLenum format, GLenum type, const void *data); +GLAPI void APIENTRY glClearNamedBufferSubDataEXT (GLuint buffer, GLenum internalformat, GLsizeiptr offset, GLsizeiptr size, GLenum format, GLenum type, const void *data); +GLAPI void APIENTRY glNamedFramebufferParameteriEXT (GLuint framebuffer, GLenum pname, GLint param); +GLAPI void APIENTRY glGetNamedFramebufferParameterivEXT (GLuint framebuffer, GLenum pname, GLint *params); +GLAPI void APIENTRY glProgramUniform1dEXT (GLuint program, GLint location, GLdouble x); +GLAPI void APIENTRY glProgramUniform2dEXT (GLuint program, GLint location, GLdouble x, GLdouble y); +GLAPI void APIENTRY glProgramUniform3dEXT (GLuint program, GLint location, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glProgramUniform4dEXT (GLuint program, GLint location, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glProgramUniform1dvEXT (GLuint program, GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glProgramUniform2dvEXT (GLuint program, GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glProgramUniform3dvEXT (GLuint program, GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glProgramUniform4dvEXT (GLuint program, GLint location, GLsizei count, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix2dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix3dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix4dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix2x3dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix2x4dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix3x2dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix3x4dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix4x2dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glProgramUniformMatrix4x3dvEXT (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble *value); +GLAPI void APIENTRY glTextureBufferRangeEXT (GLuint texture, GLenum target, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size); +GLAPI void APIENTRY glTextureStorage1DEXT (GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width); +GLAPI void APIENTRY glTextureStorage2DEXT (GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glTextureStorage3DEXT (GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth); +GLAPI void APIENTRY glTextureStorage2DMultisampleEXT (GLuint texture, GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations); +GLAPI void APIENTRY glTextureStorage3DMultisampleEXT (GLuint texture, GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations); +GLAPI void APIENTRY glVertexArrayBindVertexBufferEXT (GLuint vaobj, GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride); +GLAPI void APIENTRY glVertexArrayVertexAttribFormatEXT (GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset); +GLAPI void APIENTRY glVertexArrayVertexAttribIFormatEXT (GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); +GLAPI void APIENTRY glVertexArrayVertexAttribLFormatEXT (GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); +GLAPI void APIENTRY glVertexArrayVertexAttribBindingEXT (GLuint vaobj, GLuint attribindex, GLuint bindingindex); +GLAPI void APIENTRY glVertexArrayVertexBindingDivisorEXT (GLuint vaobj, GLuint bindingindex, GLuint divisor); +GLAPI void APIENTRY glVertexArrayVertexAttribLOffsetEXT (GLuint vaobj, GLuint buffer, GLuint index, GLint size, GLenum type, GLsizei stride, GLintptr offset); +GLAPI void APIENTRY glTexturePageCommitmentEXT (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLboolean commit); +GLAPI void APIENTRY glVertexArrayVertexAttribDivisorEXT (GLuint vaobj, GLuint index, GLuint divisor); +#endif +#endif /* GL_EXT_direct_state_access */ + +#ifndef GL_EXT_draw_buffers2 +#define GL_EXT_draw_buffers2 1 +typedef void (APIENTRYP PFNGLCOLORMASKINDEXEDEXTPROC) (GLuint index, GLboolean r, GLboolean g, GLboolean b, GLboolean a); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorMaskIndexedEXT (GLuint index, GLboolean r, GLboolean g, GLboolean b, GLboolean a); +#endif +#endif /* GL_EXT_draw_buffers2 */ + +#ifndef GL_EXT_draw_instanced +#define GL_EXT_draw_instanced 1 +typedef void (APIENTRYP PFNGLDRAWARRAYSINSTANCEDEXTPROC) (GLenum mode, GLint start, GLsizei count, GLsizei primcount); +typedef void (APIENTRYP PFNGLDRAWELEMENTSINSTANCEDEXTPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawArraysInstancedEXT (GLenum mode, GLint start, GLsizei count, GLsizei primcount); +GLAPI void APIENTRY glDrawElementsInstancedEXT (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount); +#endif +#endif /* GL_EXT_draw_instanced */ + +#ifndef GL_EXT_draw_range_elements +#define GL_EXT_draw_range_elements 1 +#define GL_MAX_ELEMENTS_VERTICES_EXT 0x80E8 +#define GL_MAX_ELEMENTS_INDICES_EXT 0x80E9 +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTSEXTPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void *indices); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawRangeElementsEXT (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void *indices); +#endif +#endif /* GL_EXT_draw_range_elements */ + +#ifndef GL_EXT_external_buffer +#define GL_EXT_external_buffer 1 +typedef void *GLeglClientBufferEXT; +typedef void (APIENTRYP PFNGLBUFFERSTORAGEEXTERNALEXTPROC) (GLenum target, GLintptr offset, GLsizeiptr size, GLeglClientBufferEXT clientBuffer, GLbitfield flags); +typedef void (APIENTRYP PFNGLNAMEDBUFFERSTORAGEEXTERNALEXTPROC) (GLuint buffer, GLintptr offset, GLsizeiptr size, GLeglClientBufferEXT clientBuffer, GLbitfield flags); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBufferStorageExternalEXT (GLenum target, GLintptr offset, GLsizeiptr size, GLeglClientBufferEXT clientBuffer, GLbitfield flags); +GLAPI void APIENTRY glNamedBufferStorageExternalEXT (GLuint buffer, GLintptr offset, GLsizeiptr size, GLeglClientBufferEXT clientBuffer, GLbitfield flags); +#endif +#endif /* GL_EXT_external_buffer */ + +#ifndef GL_EXT_fog_coord +#define GL_EXT_fog_coord 1 +#define GL_FOG_COORDINATE_SOURCE_EXT 0x8450 +#define GL_FOG_COORDINATE_EXT 0x8451 +#define GL_FRAGMENT_DEPTH_EXT 0x8452 +#define GL_CURRENT_FOG_COORDINATE_EXT 0x8453 +#define GL_FOG_COORDINATE_ARRAY_TYPE_EXT 0x8454 +#define GL_FOG_COORDINATE_ARRAY_STRIDE_EXT 0x8455 +#define GL_FOG_COORDINATE_ARRAY_POINTER_EXT 0x8456 +#define GL_FOG_COORDINATE_ARRAY_EXT 0x8457 +typedef void (APIENTRYP PFNGLFOGCOORDFEXTPROC) (GLfloat coord); +typedef void (APIENTRYP PFNGLFOGCOORDFVEXTPROC) (const GLfloat *coord); +typedef void (APIENTRYP PFNGLFOGCOORDDEXTPROC) (GLdouble coord); +typedef void (APIENTRYP PFNGLFOGCOORDDVEXTPROC) (const GLdouble *coord); +typedef void (APIENTRYP PFNGLFOGCOORDPOINTEREXTPROC) (GLenum type, GLsizei stride, const void *pointer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFogCoordfEXT (GLfloat coord); +GLAPI void APIENTRY glFogCoordfvEXT (const GLfloat *coord); +GLAPI void APIENTRY glFogCoorddEXT (GLdouble coord); +GLAPI void APIENTRY glFogCoorddvEXT (const GLdouble *coord); +GLAPI void APIENTRY glFogCoordPointerEXT (GLenum type, GLsizei stride, const void *pointer); +#endif +#endif /* GL_EXT_fog_coord */ + +#ifndef GL_EXT_framebuffer_blit +#define GL_EXT_framebuffer_blit 1 +#define GL_READ_FRAMEBUFFER_EXT 0x8CA8 +#define GL_DRAW_FRAMEBUFFER_EXT 0x8CA9 +#define GL_DRAW_FRAMEBUFFER_BINDING_EXT 0x8CA6 +#define GL_READ_FRAMEBUFFER_BINDING_EXT 0x8CAA +typedef void (APIENTRYP PFNGLBLITFRAMEBUFFEREXTPROC) (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlitFramebufferEXT (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +#endif +#endif /* GL_EXT_framebuffer_blit */ + +#ifndef GL_EXT_framebuffer_multisample +#define GL_EXT_framebuffer_multisample 1 +#define GL_RENDERBUFFER_SAMPLES_EXT 0x8CAB +#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT 0x8D56 +#define GL_MAX_SAMPLES_EXT 0x8D57 +typedef void (APIENTRYP PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC) (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glRenderbufferStorageMultisampleEXT (GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +#endif +#endif /* GL_EXT_framebuffer_multisample */ + +#ifndef GL_EXT_framebuffer_multisample_blit_scaled +#define GL_EXT_framebuffer_multisample_blit_scaled 1 +#define GL_SCALED_RESOLVE_FASTEST_EXT 0x90BA +#define GL_SCALED_RESOLVE_NICEST_EXT 0x90BB +#endif /* GL_EXT_framebuffer_multisample_blit_scaled */ + +#ifndef GL_EXT_framebuffer_object +#define GL_EXT_framebuffer_object 1 +#define GL_INVALID_FRAMEBUFFER_OPERATION_EXT 0x0506 +#define GL_MAX_RENDERBUFFER_SIZE_EXT 0x84E8 +#define GL_FRAMEBUFFER_BINDING_EXT 0x8CA6 +#define GL_RENDERBUFFER_BINDING_EXT 0x8CA7 +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE_EXT 0x8CD0 +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME_EXT 0x8CD1 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL_EXT 0x8CD2 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE_EXT 0x8CD3 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_3D_ZOFFSET_EXT 0x8CD4 +#define GL_FRAMEBUFFER_COMPLETE_EXT 0x8CD5 +#define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT 0x8CD6 +#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT 0x8CD7 +#define GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT 0x8CD9 +#define GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT 0x8CDA +#define GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT 0x8CDB +#define GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT 0x8CDC +#define GL_FRAMEBUFFER_UNSUPPORTED_EXT 0x8CDD +#define GL_MAX_COLOR_ATTACHMENTS_EXT 0x8CDF +#define GL_COLOR_ATTACHMENT0_EXT 0x8CE0 +#define GL_COLOR_ATTACHMENT1_EXT 0x8CE1 +#define GL_COLOR_ATTACHMENT2_EXT 0x8CE2 +#define GL_COLOR_ATTACHMENT3_EXT 0x8CE3 +#define GL_COLOR_ATTACHMENT4_EXT 0x8CE4 +#define GL_COLOR_ATTACHMENT5_EXT 0x8CE5 +#define GL_COLOR_ATTACHMENT6_EXT 0x8CE6 +#define GL_COLOR_ATTACHMENT7_EXT 0x8CE7 +#define GL_COLOR_ATTACHMENT8_EXT 0x8CE8 +#define GL_COLOR_ATTACHMENT9_EXT 0x8CE9 +#define GL_COLOR_ATTACHMENT10_EXT 0x8CEA +#define GL_COLOR_ATTACHMENT11_EXT 0x8CEB +#define GL_COLOR_ATTACHMENT12_EXT 0x8CEC +#define GL_COLOR_ATTACHMENT13_EXT 0x8CED +#define GL_COLOR_ATTACHMENT14_EXT 0x8CEE +#define GL_COLOR_ATTACHMENT15_EXT 0x8CEF +#define GL_DEPTH_ATTACHMENT_EXT 0x8D00 +#define GL_STENCIL_ATTACHMENT_EXT 0x8D20 +#define GL_FRAMEBUFFER_EXT 0x8D40 +#define GL_RENDERBUFFER_EXT 0x8D41 +#define GL_RENDERBUFFER_WIDTH_EXT 0x8D42 +#define GL_RENDERBUFFER_HEIGHT_EXT 0x8D43 +#define GL_RENDERBUFFER_INTERNAL_FORMAT_EXT 0x8D44 +#define GL_STENCIL_INDEX1_EXT 0x8D46 +#define GL_STENCIL_INDEX4_EXT 0x8D47 +#define GL_STENCIL_INDEX8_EXT 0x8D48 +#define GL_STENCIL_INDEX16_EXT 0x8D49 +#define GL_RENDERBUFFER_RED_SIZE_EXT 0x8D50 +#define GL_RENDERBUFFER_GREEN_SIZE_EXT 0x8D51 +#define GL_RENDERBUFFER_BLUE_SIZE_EXT 0x8D52 +#define GL_RENDERBUFFER_ALPHA_SIZE_EXT 0x8D53 +#define GL_RENDERBUFFER_DEPTH_SIZE_EXT 0x8D54 +#define GL_RENDERBUFFER_STENCIL_SIZE_EXT 0x8D55 +typedef GLboolean (APIENTRYP PFNGLISRENDERBUFFEREXTPROC) (GLuint renderbuffer); +typedef void (APIENTRYP PFNGLBINDRENDERBUFFEREXTPROC) (GLenum target, GLuint renderbuffer); +typedef void (APIENTRYP PFNGLDELETERENDERBUFFERSEXTPROC) (GLsizei n, const GLuint *renderbuffers); +typedef void (APIENTRYP PFNGLGENRENDERBUFFERSEXTPROC) (GLsizei n, GLuint *renderbuffers); +typedef void (APIENTRYP PFNGLRENDERBUFFERSTORAGEEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETRENDERBUFFERPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef GLboolean (APIENTRYP PFNGLISFRAMEBUFFEREXTPROC) (GLuint framebuffer); +typedef void (APIENTRYP PFNGLBINDFRAMEBUFFEREXTPROC) (GLenum target, GLuint framebuffer); +typedef void (APIENTRYP PFNGLDELETEFRAMEBUFFERSEXTPROC) (GLsizei n, const GLuint *framebuffers); +typedef void (APIENTRYP PFNGLGENFRAMEBUFFERSEXTPROC) (GLsizei n, GLuint *framebuffers); +typedef GLenum (APIENTRYP PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC) (GLenum target); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE1DEXTPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE2DEXTPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE3DEXTPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); +typedef void (APIENTRYP PFNGLFRAMEBUFFERRENDERBUFFEREXTPROC) (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +typedef void (APIENTRYP PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVEXTPROC) (GLenum target, GLenum attachment, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGENERATEMIPMAPEXTPROC) (GLenum target); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLboolean APIENTRY glIsRenderbufferEXT (GLuint renderbuffer); +GLAPI void APIENTRY glBindRenderbufferEXT (GLenum target, GLuint renderbuffer); +GLAPI void APIENTRY glDeleteRenderbuffersEXT (GLsizei n, const GLuint *renderbuffers); +GLAPI void APIENTRY glGenRenderbuffersEXT (GLsizei n, GLuint *renderbuffers); +GLAPI void APIENTRY glRenderbufferStorageEXT (GLenum target, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glGetRenderbufferParameterivEXT (GLenum target, GLenum pname, GLint *params); +GLAPI GLboolean APIENTRY glIsFramebufferEXT (GLuint framebuffer); +GLAPI void APIENTRY glBindFramebufferEXT (GLenum target, GLuint framebuffer); +GLAPI void APIENTRY glDeleteFramebuffersEXT (GLsizei n, const GLuint *framebuffers); +GLAPI void APIENTRY glGenFramebuffersEXT (GLsizei n, GLuint *framebuffers); +GLAPI GLenum APIENTRY glCheckFramebufferStatusEXT (GLenum target); +GLAPI void APIENTRY glFramebufferTexture1DEXT (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +GLAPI void APIENTRY glFramebufferTexture2DEXT (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +GLAPI void APIENTRY glFramebufferTexture3DEXT (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); +GLAPI void APIENTRY glFramebufferRenderbufferEXT (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +GLAPI void APIENTRY glGetFramebufferAttachmentParameterivEXT (GLenum target, GLenum attachment, GLenum pname, GLint *params); +GLAPI void APIENTRY glGenerateMipmapEXT (GLenum target); +#endif +#endif /* GL_EXT_framebuffer_object */ + +#ifndef GL_EXT_framebuffer_sRGB +#define GL_EXT_framebuffer_sRGB 1 +#define GL_FRAMEBUFFER_SRGB_EXT 0x8DB9 +#define GL_FRAMEBUFFER_SRGB_CAPABLE_EXT 0x8DBA +#endif /* GL_EXT_framebuffer_sRGB */ + +#ifndef GL_EXT_geometry_shader4 +#define GL_EXT_geometry_shader4 1 +#define GL_GEOMETRY_SHADER_EXT 0x8DD9 +#define GL_GEOMETRY_VERTICES_OUT_EXT 0x8DDA +#define GL_GEOMETRY_INPUT_TYPE_EXT 0x8DDB +#define GL_GEOMETRY_OUTPUT_TYPE_EXT 0x8DDC +#define GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS_EXT 0x8C29 +#define GL_MAX_GEOMETRY_VARYING_COMPONENTS_EXT 0x8DDD +#define GL_MAX_VERTEX_VARYING_COMPONENTS_EXT 0x8DDE +#define GL_MAX_VARYING_COMPONENTS_EXT 0x8B4B +#define GL_MAX_GEOMETRY_UNIFORM_COMPONENTS_EXT 0x8DDF +#define GL_MAX_GEOMETRY_OUTPUT_VERTICES_EXT 0x8DE0 +#define GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS_EXT 0x8DE1 +#define GL_LINES_ADJACENCY_EXT 0x000A +#define GL_LINE_STRIP_ADJACENCY_EXT 0x000B +#define GL_TRIANGLES_ADJACENCY_EXT 0x000C +#define GL_TRIANGLE_STRIP_ADJACENCY_EXT 0x000D +#define GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS_EXT 0x8DA8 +#define GL_FRAMEBUFFER_INCOMPLETE_LAYER_COUNT_EXT 0x8DA9 +#define GL_FRAMEBUFFER_ATTACHMENT_LAYERED_EXT 0x8DA7 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER_EXT 0x8CD4 +#define GL_PROGRAM_POINT_SIZE_EXT 0x8642 +typedef void (APIENTRYP PFNGLPROGRAMPARAMETERIEXTPROC) (GLuint program, GLenum pname, GLint value); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramParameteriEXT (GLuint program, GLenum pname, GLint value); +#endif +#endif /* GL_EXT_geometry_shader4 */ + +#ifndef GL_EXT_gpu_program_parameters +#define GL_EXT_gpu_program_parameters 1 +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETERS4FVEXTPROC) (GLenum target, GLuint index, GLsizei count, const GLfloat *params); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETERS4FVEXTPROC) (GLenum target, GLuint index, GLsizei count, const GLfloat *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramEnvParameters4fvEXT (GLenum target, GLuint index, GLsizei count, const GLfloat *params); +GLAPI void APIENTRY glProgramLocalParameters4fvEXT (GLenum target, GLuint index, GLsizei count, const GLfloat *params); +#endif +#endif /* GL_EXT_gpu_program_parameters */ + +#ifndef GL_EXT_gpu_shader4 +#define GL_EXT_gpu_shader4 1 +#define GL_SAMPLER_1D_ARRAY_EXT 0x8DC0 +#define GL_SAMPLER_2D_ARRAY_EXT 0x8DC1 +#define GL_SAMPLER_BUFFER_EXT 0x8DC2 +#define GL_SAMPLER_1D_ARRAY_SHADOW_EXT 0x8DC3 +#define GL_SAMPLER_2D_ARRAY_SHADOW_EXT 0x8DC4 +#define GL_SAMPLER_CUBE_SHADOW_EXT 0x8DC5 +#define GL_UNSIGNED_INT_VEC2_EXT 0x8DC6 +#define GL_UNSIGNED_INT_VEC3_EXT 0x8DC7 +#define GL_UNSIGNED_INT_VEC4_EXT 0x8DC8 +#define GL_INT_SAMPLER_1D_EXT 0x8DC9 +#define GL_INT_SAMPLER_2D_EXT 0x8DCA +#define GL_INT_SAMPLER_3D_EXT 0x8DCB +#define GL_INT_SAMPLER_CUBE_EXT 0x8DCC +#define GL_INT_SAMPLER_2D_RECT_EXT 0x8DCD +#define GL_INT_SAMPLER_1D_ARRAY_EXT 0x8DCE +#define GL_INT_SAMPLER_2D_ARRAY_EXT 0x8DCF +#define GL_INT_SAMPLER_BUFFER_EXT 0x8DD0 +#define GL_UNSIGNED_INT_SAMPLER_1D_EXT 0x8DD1 +#define GL_UNSIGNED_INT_SAMPLER_2D_EXT 0x8DD2 +#define GL_UNSIGNED_INT_SAMPLER_3D_EXT 0x8DD3 +#define GL_UNSIGNED_INT_SAMPLER_CUBE_EXT 0x8DD4 +#define GL_UNSIGNED_INT_SAMPLER_2D_RECT_EXT 0x8DD5 +#define GL_UNSIGNED_INT_SAMPLER_1D_ARRAY_EXT 0x8DD6 +#define GL_UNSIGNED_INT_SAMPLER_2D_ARRAY_EXT 0x8DD7 +#define GL_UNSIGNED_INT_SAMPLER_BUFFER_EXT 0x8DD8 +#define GL_MIN_PROGRAM_TEXEL_OFFSET_EXT 0x8904 +#define GL_MAX_PROGRAM_TEXEL_OFFSET_EXT 0x8905 +#define GL_VERTEX_ATTRIB_ARRAY_INTEGER_EXT 0x88FD +typedef void (APIENTRYP PFNGLGETUNIFORMUIVEXTPROC) (GLuint program, GLint location, GLuint *params); +typedef void (APIENTRYP PFNGLBINDFRAGDATALOCATIONEXTPROC) (GLuint program, GLuint color, const GLchar *name); +typedef GLint (APIENTRYP PFNGLGETFRAGDATALOCATIONEXTPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP PFNGLUNIFORM1UIEXTPROC) (GLint location, GLuint v0); +typedef void (APIENTRYP PFNGLUNIFORM2UIEXTPROC) (GLint location, GLuint v0, GLuint v1); +typedef void (APIENTRYP PFNGLUNIFORM3UIEXTPROC) (GLint location, GLuint v0, GLuint v1, GLuint v2); +typedef void (APIENTRYP PFNGLUNIFORM4UIEXTPROC) (GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +typedef void (APIENTRYP PFNGLUNIFORM1UIVEXTPROC) (GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLUNIFORM2UIVEXTPROC) (GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLUNIFORM3UIVEXTPROC) (GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLUNIFORM4UIVEXTPROC) (GLint location, GLsizei count, const GLuint *value); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI1IEXTPROC) (GLuint index, GLint x); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI2IEXTPROC) (GLuint index, GLint x, GLint y); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI3IEXTPROC) (GLuint index, GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4IEXTPROC) (GLuint index, GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI1UIEXTPROC) (GLuint index, GLuint x); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI2UIEXTPROC) (GLuint index, GLuint x, GLuint y); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI3UIEXTPROC) (GLuint index, GLuint x, GLuint y, GLuint z); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4UIEXTPROC) (GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI1IVEXTPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI2IVEXTPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI3IVEXTPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4IVEXTPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI1UIVEXTPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI2UIVEXTPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI3UIVEXTPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4UIVEXTPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4BVEXTPROC) (GLuint index, const GLbyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4SVEXTPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4UBVEXTPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBI4USVEXTPROC) (GLuint index, const GLushort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBIPOINTEREXTPROC) (GLuint index, GLint size, GLenum type, GLsizei stride, const void *pointer); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIIVEXTPROC) (GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIUIVEXTPROC) (GLuint index, GLenum pname, GLuint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetUniformuivEXT (GLuint program, GLint location, GLuint *params); +GLAPI void APIENTRY glBindFragDataLocationEXT (GLuint program, GLuint color, const GLchar *name); +GLAPI GLint APIENTRY glGetFragDataLocationEXT (GLuint program, const GLchar *name); +GLAPI void APIENTRY glUniform1uiEXT (GLint location, GLuint v0); +GLAPI void APIENTRY glUniform2uiEXT (GLint location, GLuint v0, GLuint v1); +GLAPI void APIENTRY glUniform3uiEXT (GLint location, GLuint v0, GLuint v1, GLuint v2); +GLAPI void APIENTRY glUniform4uiEXT (GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); +GLAPI void APIENTRY glUniform1uivEXT (GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glUniform2uivEXT (GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glUniform3uivEXT (GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glUniform4uivEXT (GLint location, GLsizei count, const GLuint *value); +GLAPI void APIENTRY glVertexAttribI1iEXT (GLuint index, GLint x); +GLAPI void APIENTRY glVertexAttribI2iEXT (GLuint index, GLint x, GLint y); +GLAPI void APIENTRY glVertexAttribI3iEXT (GLuint index, GLint x, GLint y, GLint z); +GLAPI void APIENTRY glVertexAttribI4iEXT (GLuint index, GLint x, GLint y, GLint z, GLint w); +GLAPI void APIENTRY glVertexAttribI1uiEXT (GLuint index, GLuint x); +GLAPI void APIENTRY glVertexAttribI2uiEXT (GLuint index, GLuint x, GLuint y); +GLAPI void APIENTRY glVertexAttribI3uiEXT (GLuint index, GLuint x, GLuint y, GLuint z); +GLAPI void APIENTRY glVertexAttribI4uiEXT (GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +GLAPI void APIENTRY glVertexAttribI1ivEXT (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttribI2ivEXT (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttribI3ivEXT (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttribI4ivEXT (GLuint index, const GLint *v); +GLAPI void APIENTRY glVertexAttribI1uivEXT (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttribI2uivEXT (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttribI3uivEXT (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttribI4uivEXT (GLuint index, const GLuint *v); +GLAPI void APIENTRY glVertexAttribI4bvEXT (GLuint index, const GLbyte *v); +GLAPI void APIENTRY glVertexAttribI4svEXT (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttribI4ubvEXT (GLuint index, const GLubyte *v); +GLAPI void APIENTRY glVertexAttribI4usvEXT (GLuint index, const GLushort *v); +GLAPI void APIENTRY glVertexAttribIPointerEXT (GLuint index, GLint size, GLenum type, GLsizei stride, const void *pointer); +GLAPI void APIENTRY glGetVertexAttribIivEXT (GLuint index, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVertexAttribIuivEXT (GLuint index, GLenum pname, GLuint *params); +#endif +#endif /* GL_EXT_gpu_shader4 */ + +#ifndef GL_EXT_histogram +#define GL_EXT_histogram 1 +#define GL_HISTOGRAM_EXT 0x8024 +#define GL_PROXY_HISTOGRAM_EXT 0x8025 +#define GL_HISTOGRAM_WIDTH_EXT 0x8026 +#define GL_HISTOGRAM_FORMAT_EXT 0x8027 +#define GL_HISTOGRAM_RED_SIZE_EXT 0x8028 +#define GL_HISTOGRAM_GREEN_SIZE_EXT 0x8029 +#define GL_HISTOGRAM_BLUE_SIZE_EXT 0x802A +#define GL_HISTOGRAM_ALPHA_SIZE_EXT 0x802B +#define GL_HISTOGRAM_LUMINANCE_SIZE_EXT 0x802C +#define GL_HISTOGRAM_SINK_EXT 0x802D +#define GL_MINMAX_EXT 0x802E +#define GL_MINMAX_FORMAT_EXT 0x802F +#define GL_MINMAX_SINK_EXT 0x8030 +#define GL_TABLE_TOO_LARGE_EXT 0x8031 +typedef void (APIENTRYP PFNGLGETHISTOGRAMEXTPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, void *values); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMINMAXEXTPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, void *values); +typedef void (APIENTRYP PFNGLGETMINMAXPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMINMAXPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLHISTOGRAMEXTPROC) (GLenum target, GLsizei width, GLenum internalformat, GLboolean sink); +typedef void (APIENTRYP PFNGLMINMAXEXTPROC) (GLenum target, GLenum internalformat, GLboolean sink); +typedef void (APIENTRYP PFNGLRESETHISTOGRAMEXTPROC) (GLenum target); +typedef void (APIENTRYP PFNGLRESETMINMAXEXTPROC) (GLenum target); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetHistogramEXT (GLenum target, GLboolean reset, GLenum format, GLenum type, void *values); +GLAPI void APIENTRY glGetHistogramParameterfvEXT (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetHistogramParameterivEXT (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetMinmaxEXT (GLenum target, GLboolean reset, GLenum format, GLenum type, void *values); +GLAPI void APIENTRY glGetMinmaxParameterfvEXT (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetMinmaxParameterivEXT (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glHistogramEXT (GLenum target, GLsizei width, GLenum internalformat, GLboolean sink); +GLAPI void APIENTRY glMinmaxEXT (GLenum target, GLenum internalformat, GLboolean sink); +GLAPI void APIENTRY glResetHistogramEXT (GLenum target); +GLAPI void APIENTRY glResetMinmaxEXT (GLenum target); +#endif +#endif /* GL_EXT_histogram */ + +#ifndef GL_EXT_index_array_formats +#define GL_EXT_index_array_formats 1 +#define GL_IUI_V2F_EXT 0x81AD +#define GL_IUI_V3F_EXT 0x81AE +#define GL_IUI_N3F_V2F_EXT 0x81AF +#define GL_IUI_N3F_V3F_EXT 0x81B0 +#define GL_T2F_IUI_V2F_EXT 0x81B1 +#define GL_T2F_IUI_V3F_EXT 0x81B2 +#define GL_T2F_IUI_N3F_V2F_EXT 0x81B3 +#define GL_T2F_IUI_N3F_V3F_EXT 0x81B4 +#endif /* GL_EXT_index_array_formats */ + +#ifndef GL_EXT_index_func +#define GL_EXT_index_func 1 +#define GL_INDEX_TEST_EXT 0x81B5 +#define GL_INDEX_TEST_FUNC_EXT 0x81B6 +#define GL_INDEX_TEST_REF_EXT 0x81B7 +typedef void (APIENTRYP PFNGLINDEXFUNCEXTPROC) (GLenum func, GLclampf ref); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glIndexFuncEXT (GLenum func, GLclampf ref); +#endif +#endif /* GL_EXT_index_func */ + +#ifndef GL_EXT_index_material +#define GL_EXT_index_material 1 +#define GL_INDEX_MATERIAL_EXT 0x81B8 +#define GL_INDEX_MATERIAL_PARAMETER_EXT 0x81B9 +#define GL_INDEX_MATERIAL_FACE_EXT 0x81BA +typedef void (APIENTRYP PFNGLINDEXMATERIALEXTPROC) (GLenum face, GLenum mode); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glIndexMaterialEXT (GLenum face, GLenum mode); +#endif +#endif /* GL_EXT_index_material */ + +#ifndef GL_EXT_index_texture +#define GL_EXT_index_texture 1 +#endif /* GL_EXT_index_texture */ + +#ifndef GL_EXT_light_texture +#define GL_EXT_light_texture 1 +#define GL_FRAGMENT_MATERIAL_EXT 0x8349 +#define GL_FRAGMENT_NORMAL_EXT 0x834A +#define GL_FRAGMENT_COLOR_EXT 0x834C +#define GL_ATTENUATION_EXT 0x834D +#define GL_SHADOW_ATTENUATION_EXT 0x834E +#define GL_TEXTURE_APPLICATION_MODE_EXT 0x834F +#define GL_TEXTURE_LIGHT_EXT 0x8350 +#define GL_TEXTURE_MATERIAL_FACE_EXT 0x8351 +#define GL_TEXTURE_MATERIAL_PARAMETER_EXT 0x8352 +typedef void (APIENTRYP PFNGLAPPLYTEXTUREEXTPROC) (GLenum mode); +typedef void (APIENTRYP PFNGLTEXTURELIGHTEXTPROC) (GLenum pname); +typedef void (APIENTRYP PFNGLTEXTUREMATERIALEXTPROC) (GLenum face, GLenum mode); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glApplyTextureEXT (GLenum mode); +GLAPI void APIENTRY glTextureLightEXT (GLenum pname); +GLAPI void APIENTRY glTextureMaterialEXT (GLenum face, GLenum mode); +#endif +#endif /* GL_EXT_light_texture */ + +#ifndef GL_EXT_memory_object +#define GL_EXT_memory_object 1 +#define GL_TEXTURE_TILING_EXT 0x9580 +#define GL_DEDICATED_MEMORY_OBJECT_EXT 0x9581 +#define GL_PROTECTED_MEMORY_OBJECT_EXT 0x959B +#define GL_NUM_TILING_TYPES_EXT 0x9582 +#define GL_TILING_TYPES_EXT 0x9583 +#define GL_OPTIMAL_TILING_EXT 0x9584 +#define GL_LINEAR_TILING_EXT 0x9585 +#define GL_NUM_DEVICE_UUIDS_EXT 0x9596 +#define GL_DEVICE_UUID_EXT 0x9597 +#define GL_DRIVER_UUID_EXT 0x9598 +#define GL_UUID_SIZE_EXT 16 +typedef void (APIENTRYP PFNGLGETUNSIGNEDBYTEVEXTPROC) (GLenum pname, GLubyte *data); +typedef void (APIENTRYP PFNGLGETUNSIGNEDBYTEI_VEXTPROC) (GLenum target, GLuint index, GLubyte *data); +typedef void (APIENTRYP PFNGLDELETEMEMORYOBJECTSEXTPROC) (GLsizei n, const GLuint *memoryObjects); +typedef GLboolean (APIENTRYP PFNGLISMEMORYOBJECTEXTPROC) (GLuint memoryObject); +typedef void (APIENTRYP PFNGLCREATEMEMORYOBJECTSEXTPROC) (GLsizei n, GLuint *memoryObjects); +typedef void (APIENTRYP PFNGLMEMORYOBJECTPARAMETERIVEXTPROC) (GLuint memoryObject, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLGETMEMORYOBJECTPARAMETERIVEXTPROC) (GLuint memoryObject, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLTEXSTORAGEMEM2DEXTPROC) (GLenum target, GLsizei levels, GLenum internalFormat, GLsizei width, GLsizei height, GLuint memory, GLuint64 offset); +typedef void (APIENTRYP PFNGLTEXSTORAGEMEM2DMULTISAMPLEEXTPROC) (GLenum target, GLsizei samples, GLenum internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations, GLuint memory, GLuint64 offset); +typedef void (APIENTRYP PFNGLTEXSTORAGEMEM3DEXTPROC) (GLenum target, GLsizei levels, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLuint memory, GLuint64 offset); +typedef void (APIENTRYP PFNGLTEXSTORAGEMEM3DMULTISAMPLEEXTPROC) (GLenum target, GLsizei samples, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations, GLuint memory, GLuint64 offset); +typedef void (APIENTRYP PFNGLBUFFERSTORAGEMEMEXTPROC) (GLenum target, GLsizeiptr size, GLuint memory, GLuint64 offset); +typedef void (APIENTRYP PFNGLTEXTURESTORAGEMEM2DEXTPROC) (GLuint texture, GLsizei levels, GLenum internalFormat, GLsizei width, GLsizei height, GLuint memory, GLuint64 offset); +typedef void (APIENTRYP PFNGLTEXTURESTORAGEMEM2DMULTISAMPLEEXTPROC) (GLuint texture, GLsizei samples, GLenum internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations, GLuint memory, GLuint64 offset); +typedef void (APIENTRYP PFNGLTEXTURESTORAGEMEM3DEXTPROC) (GLuint texture, GLsizei levels, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLuint memory, GLuint64 offset); +typedef void (APIENTRYP PFNGLTEXTURESTORAGEMEM3DMULTISAMPLEEXTPROC) (GLuint texture, GLsizei samples, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations, GLuint memory, GLuint64 offset); +typedef void (APIENTRYP PFNGLNAMEDBUFFERSTORAGEMEMEXTPROC) (GLuint buffer, GLsizeiptr size, GLuint memory, GLuint64 offset); +typedef void (APIENTRYP PFNGLTEXSTORAGEMEM1DEXTPROC) (GLenum target, GLsizei levels, GLenum internalFormat, GLsizei width, GLuint memory, GLuint64 offset); +typedef void (APIENTRYP PFNGLTEXTURESTORAGEMEM1DEXTPROC) (GLuint texture, GLsizei levels, GLenum internalFormat, GLsizei width, GLuint memory, GLuint64 offset); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetUnsignedBytevEXT (GLenum pname, GLubyte *data); +GLAPI void APIENTRY glGetUnsignedBytei_vEXT (GLenum target, GLuint index, GLubyte *data); +GLAPI void APIENTRY glDeleteMemoryObjectsEXT (GLsizei n, const GLuint *memoryObjects); +GLAPI GLboolean APIENTRY glIsMemoryObjectEXT (GLuint memoryObject); +GLAPI void APIENTRY glCreateMemoryObjectsEXT (GLsizei n, GLuint *memoryObjects); +GLAPI void APIENTRY glMemoryObjectParameterivEXT (GLuint memoryObject, GLenum pname, const GLint *params); +GLAPI void APIENTRY glGetMemoryObjectParameterivEXT (GLuint memoryObject, GLenum pname, GLint *params); +GLAPI void APIENTRY glTexStorageMem2DEXT (GLenum target, GLsizei levels, GLenum internalFormat, GLsizei width, GLsizei height, GLuint memory, GLuint64 offset); +GLAPI void APIENTRY glTexStorageMem2DMultisampleEXT (GLenum target, GLsizei samples, GLenum internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations, GLuint memory, GLuint64 offset); +GLAPI void APIENTRY glTexStorageMem3DEXT (GLenum target, GLsizei levels, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLuint memory, GLuint64 offset); +GLAPI void APIENTRY glTexStorageMem3DMultisampleEXT (GLenum target, GLsizei samples, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations, GLuint memory, GLuint64 offset); +GLAPI void APIENTRY glBufferStorageMemEXT (GLenum target, GLsizeiptr size, GLuint memory, GLuint64 offset); +GLAPI void APIENTRY glTextureStorageMem2DEXT (GLuint texture, GLsizei levels, GLenum internalFormat, GLsizei width, GLsizei height, GLuint memory, GLuint64 offset); +GLAPI void APIENTRY glTextureStorageMem2DMultisampleEXT (GLuint texture, GLsizei samples, GLenum internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations, GLuint memory, GLuint64 offset); +GLAPI void APIENTRY glTextureStorageMem3DEXT (GLuint texture, GLsizei levels, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLuint memory, GLuint64 offset); +GLAPI void APIENTRY glTextureStorageMem3DMultisampleEXT (GLuint texture, GLsizei samples, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations, GLuint memory, GLuint64 offset); +GLAPI void APIENTRY glNamedBufferStorageMemEXT (GLuint buffer, GLsizeiptr size, GLuint memory, GLuint64 offset); +GLAPI void APIENTRY glTexStorageMem1DEXT (GLenum target, GLsizei levels, GLenum internalFormat, GLsizei width, GLuint memory, GLuint64 offset); +GLAPI void APIENTRY glTextureStorageMem1DEXT (GLuint texture, GLsizei levels, GLenum internalFormat, GLsizei width, GLuint memory, GLuint64 offset); +#endif +#endif /* GL_EXT_memory_object */ + +#ifndef GL_EXT_memory_object_fd +#define GL_EXT_memory_object_fd 1 +#define GL_HANDLE_TYPE_OPAQUE_FD_EXT 0x9586 +typedef void (APIENTRYP PFNGLIMPORTMEMORYFDEXTPROC) (GLuint memory, GLuint64 size, GLenum handleType, GLint fd); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glImportMemoryFdEXT (GLuint memory, GLuint64 size, GLenum handleType, GLint fd); +#endif +#endif /* GL_EXT_memory_object_fd */ + +#ifndef GL_EXT_memory_object_win32 +#define GL_EXT_memory_object_win32 1 +#define GL_HANDLE_TYPE_OPAQUE_WIN32_EXT 0x9587 +#define GL_HANDLE_TYPE_OPAQUE_WIN32_KMT_EXT 0x9588 +#define GL_DEVICE_LUID_EXT 0x9599 +#define GL_DEVICE_NODE_MASK_EXT 0x959A +#define GL_LUID_SIZE_EXT 8 +#define GL_HANDLE_TYPE_D3D12_TILEPOOL_EXT 0x9589 +#define GL_HANDLE_TYPE_D3D12_RESOURCE_EXT 0x958A +#define GL_HANDLE_TYPE_D3D11_IMAGE_EXT 0x958B +#define GL_HANDLE_TYPE_D3D11_IMAGE_KMT_EXT 0x958C +typedef void (APIENTRYP PFNGLIMPORTMEMORYWIN32HANDLEEXTPROC) (GLuint memory, GLuint64 size, GLenum handleType, void *handle); +typedef void (APIENTRYP PFNGLIMPORTMEMORYWIN32NAMEEXTPROC) (GLuint memory, GLuint64 size, GLenum handleType, const void *name); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glImportMemoryWin32HandleEXT (GLuint memory, GLuint64 size, GLenum handleType, void *handle); +GLAPI void APIENTRY glImportMemoryWin32NameEXT (GLuint memory, GLuint64 size, GLenum handleType, const void *name); +#endif +#endif /* GL_EXT_memory_object_win32 */ + +#ifndef GL_EXT_misc_attribute +#define GL_EXT_misc_attribute 1 +#endif /* GL_EXT_misc_attribute */ + +#ifndef GL_EXT_multi_draw_arrays +#define GL_EXT_multi_draw_arrays 1 +typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSEXTPROC) (GLenum mode, const GLint *first, const GLsizei *count, GLsizei primcount); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSEXTPROC) (GLenum mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei primcount); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMultiDrawArraysEXT (GLenum mode, const GLint *first, const GLsizei *count, GLsizei primcount); +GLAPI void APIENTRY glMultiDrawElementsEXT (GLenum mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei primcount); +#endif +#endif /* GL_EXT_multi_draw_arrays */ + +#ifndef GL_EXT_multisample +#define GL_EXT_multisample 1 +#define GL_MULTISAMPLE_EXT 0x809D +#define GL_SAMPLE_ALPHA_TO_MASK_EXT 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_EXT 0x809F +#define GL_SAMPLE_MASK_EXT 0x80A0 +#define GL_1PASS_EXT 0x80A1 +#define GL_2PASS_0_EXT 0x80A2 +#define GL_2PASS_1_EXT 0x80A3 +#define GL_4PASS_0_EXT 0x80A4 +#define GL_4PASS_1_EXT 0x80A5 +#define GL_4PASS_2_EXT 0x80A6 +#define GL_4PASS_3_EXT 0x80A7 +#define GL_SAMPLE_BUFFERS_EXT 0x80A8 +#define GL_SAMPLES_EXT 0x80A9 +#define GL_SAMPLE_MASK_VALUE_EXT 0x80AA +#define GL_SAMPLE_MASK_INVERT_EXT 0x80AB +#define GL_SAMPLE_PATTERN_EXT 0x80AC +#define GL_MULTISAMPLE_BIT_EXT 0x20000000 +typedef void (APIENTRYP PFNGLSAMPLEMASKEXTPROC) (GLclampf value, GLboolean invert); +typedef void (APIENTRYP PFNGLSAMPLEPATTERNEXTPROC) (GLenum pattern); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSampleMaskEXT (GLclampf value, GLboolean invert); +GLAPI void APIENTRY glSamplePatternEXT (GLenum pattern); +#endif +#endif /* GL_EXT_multisample */ + +#ifndef GL_EXT_multiview_tessellation_geometry_shader +#define GL_EXT_multiview_tessellation_geometry_shader 1 +#endif /* GL_EXT_multiview_tessellation_geometry_shader */ + +#ifndef GL_EXT_multiview_texture_multisample +#define GL_EXT_multiview_texture_multisample 1 +#endif /* GL_EXT_multiview_texture_multisample */ + +#ifndef GL_EXT_multiview_timer_query +#define GL_EXT_multiview_timer_query 1 +#endif /* GL_EXT_multiview_timer_query */ + +#ifndef GL_EXT_packed_depth_stencil +#define GL_EXT_packed_depth_stencil 1 +#define GL_DEPTH_STENCIL_EXT 0x84F9 +#define GL_UNSIGNED_INT_24_8_EXT 0x84FA +#define GL_DEPTH24_STENCIL8_EXT 0x88F0 +#define GL_TEXTURE_STENCIL_SIZE_EXT 0x88F1 +#endif /* GL_EXT_packed_depth_stencil */ + +#ifndef GL_EXT_packed_float +#define GL_EXT_packed_float 1 +#define GL_R11F_G11F_B10F_EXT 0x8C3A +#define GL_UNSIGNED_INT_10F_11F_11F_REV_EXT 0x8C3B +#define GL_RGBA_SIGNED_COMPONENTS_EXT 0x8C3C +#endif /* GL_EXT_packed_float */ + +#ifndef GL_EXT_packed_pixels +#define GL_EXT_packed_pixels 1 +#define GL_UNSIGNED_BYTE_3_3_2_EXT 0x8032 +#define GL_UNSIGNED_SHORT_4_4_4_4_EXT 0x8033 +#define GL_UNSIGNED_SHORT_5_5_5_1_EXT 0x8034 +#define GL_UNSIGNED_INT_8_8_8_8_EXT 0x8035 +#define GL_UNSIGNED_INT_10_10_10_2_EXT 0x8036 +#endif /* GL_EXT_packed_pixels */ + +#ifndef GL_EXT_paletted_texture +#define GL_EXT_paletted_texture 1 +#define GL_COLOR_INDEX1_EXT 0x80E2 +#define GL_COLOR_INDEX2_EXT 0x80E3 +#define GL_COLOR_INDEX4_EXT 0x80E4 +#define GL_COLOR_INDEX8_EXT 0x80E5 +#define GL_COLOR_INDEX12_EXT 0x80E6 +#define GL_COLOR_INDEX16_EXT 0x80E7 +#define GL_TEXTURE_INDEX_SIZE_EXT 0x80ED +typedef void (APIENTRYP PFNGLCOLORTABLEEXTPROC) (GLenum target, GLenum internalFormat, GLsizei width, GLenum format, GLenum type, const void *table); +typedef void (APIENTRYP PFNGLGETCOLORTABLEEXTPROC) (GLenum target, GLenum format, GLenum type, void *data); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorTableEXT (GLenum target, GLenum internalFormat, GLsizei width, GLenum format, GLenum type, const void *table); +GLAPI void APIENTRY glGetColorTableEXT (GLenum target, GLenum format, GLenum type, void *data); +GLAPI void APIENTRY glGetColorTableParameterivEXT (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetColorTableParameterfvEXT (GLenum target, GLenum pname, GLfloat *params); +#endif +#endif /* GL_EXT_paletted_texture */ + +#ifndef GL_EXT_pixel_buffer_object +#define GL_EXT_pixel_buffer_object 1 +#define GL_PIXEL_PACK_BUFFER_EXT 0x88EB +#define GL_PIXEL_UNPACK_BUFFER_EXT 0x88EC +#define GL_PIXEL_PACK_BUFFER_BINDING_EXT 0x88ED +#define GL_PIXEL_UNPACK_BUFFER_BINDING_EXT 0x88EF +#endif /* GL_EXT_pixel_buffer_object */ + +#ifndef GL_EXT_pixel_transform +#define GL_EXT_pixel_transform 1 +#define GL_PIXEL_TRANSFORM_2D_EXT 0x8330 +#define GL_PIXEL_MAG_FILTER_EXT 0x8331 +#define GL_PIXEL_MIN_FILTER_EXT 0x8332 +#define GL_PIXEL_CUBIC_WEIGHT_EXT 0x8333 +#define GL_CUBIC_EXT 0x8334 +#define GL_AVERAGE_EXT 0x8335 +#define GL_PIXEL_TRANSFORM_2D_STACK_DEPTH_EXT 0x8336 +#define GL_MAX_PIXEL_TRANSFORM_2D_STACK_DEPTH_EXT 0x8337 +#define GL_PIXEL_TRANSFORM_2D_MATRIX_EXT 0x8338 +typedef void (APIENTRYP PFNGLPIXELTRANSFORMPARAMETERIEXTPROC) (GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPIXELTRANSFORMPARAMETERFEXTPROC) (GLenum target, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPIXELTRANSFORMPARAMETERIVEXTPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLPIXELTRANSFORMPARAMETERFVEXTPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETPIXELTRANSFORMPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETPIXELTRANSFORMPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPixelTransformParameteriEXT (GLenum target, GLenum pname, GLint param); +GLAPI void APIENTRY glPixelTransformParameterfEXT (GLenum target, GLenum pname, GLfloat param); +GLAPI void APIENTRY glPixelTransformParameterivEXT (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glPixelTransformParameterfvEXT (GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glGetPixelTransformParameterivEXT (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetPixelTransformParameterfvEXT (GLenum target, GLenum pname, GLfloat *params); +#endif +#endif /* GL_EXT_pixel_transform */ + +#ifndef GL_EXT_pixel_transform_color_table +#define GL_EXT_pixel_transform_color_table 1 +#endif /* GL_EXT_pixel_transform_color_table */ + +#ifndef GL_EXT_point_parameters +#define GL_EXT_point_parameters 1 +#define GL_POINT_SIZE_MIN_EXT 0x8126 +#define GL_POINT_SIZE_MAX_EXT 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE_EXT 0x8128 +#define GL_DISTANCE_ATTENUATION_EXT 0x8129 +typedef void (APIENTRYP PFNGLPOINTPARAMETERFEXTPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFVEXTPROC) (GLenum pname, const GLfloat *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPointParameterfEXT (GLenum pname, GLfloat param); +GLAPI void APIENTRY glPointParameterfvEXT (GLenum pname, const GLfloat *params); +#endif +#endif /* GL_EXT_point_parameters */ + +#ifndef GL_EXT_polygon_offset +#define GL_EXT_polygon_offset 1 +#define GL_POLYGON_OFFSET_EXT 0x8037 +#define GL_POLYGON_OFFSET_FACTOR_EXT 0x8038 +#define GL_POLYGON_OFFSET_BIAS_EXT 0x8039 +typedef void (APIENTRYP PFNGLPOLYGONOFFSETEXTPROC) (GLfloat factor, GLfloat bias); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPolygonOffsetEXT (GLfloat factor, GLfloat bias); +#endif +#endif /* GL_EXT_polygon_offset */ + +#ifndef GL_EXT_polygon_offset_clamp +#define GL_EXT_polygon_offset_clamp 1 +#define GL_POLYGON_OFFSET_CLAMP_EXT 0x8E1B +typedef void (APIENTRYP PFNGLPOLYGONOFFSETCLAMPEXTPROC) (GLfloat factor, GLfloat units, GLfloat clamp); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPolygonOffsetClampEXT (GLfloat factor, GLfloat units, GLfloat clamp); +#endif +#endif /* GL_EXT_polygon_offset_clamp */ + +#ifndef GL_EXT_post_depth_coverage +#define GL_EXT_post_depth_coverage 1 +#endif /* GL_EXT_post_depth_coverage */ + +#ifndef GL_EXT_provoking_vertex +#define GL_EXT_provoking_vertex 1 +#define GL_QUADS_FOLLOW_PROVOKING_VERTEX_CONVENTION_EXT 0x8E4C +#define GL_FIRST_VERTEX_CONVENTION_EXT 0x8E4D +#define GL_LAST_VERTEX_CONVENTION_EXT 0x8E4E +#define GL_PROVOKING_VERTEX_EXT 0x8E4F +typedef void (APIENTRYP PFNGLPROVOKINGVERTEXEXTPROC) (GLenum mode); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProvokingVertexEXT (GLenum mode); +#endif +#endif /* GL_EXT_provoking_vertex */ + +#ifndef GL_EXT_raster_multisample +#define GL_EXT_raster_multisample 1 +#define GL_RASTER_MULTISAMPLE_EXT 0x9327 +#define GL_RASTER_SAMPLES_EXT 0x9328 +#define GL_MAX_RASTER_SAMPLES_EXT 0x9329 +#define GL_RASTER_FIXED_SAMPLE_LOCATIONS_EXT 0x932A +#define GL_MULTISAMPLE_RASTERIZATION_ALLOWED_EXT 0x932B +#define GL_EFFECTIVE_RASTER_SAMPLES_EXT 0x932C +typedef void (APIENTRYP PFNGLRASTERSAMPLESEXTPROC) (GLuint samples, GLboolean fixedsamplelocations); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glRasterSamplesEXT (GLuint samples, GLboolean fixedsamplelocations); +#endif +#endif /* GL_EXT_raster_multisample */ + +#ifndef GL_EXT_rescale_normal +#define GL_EXT_rescale_normal 1 +#define GL_RESCALE_NORMAL_EXT 0x803A +#endif /* GL_EXT_rescale_normal */ + +#ifndef GL_EXT_secondary_color +#define GL_EXT_secondary_color 1 +#define GL_COLOR_SUM_EXT 0x8458 +#define GL_CURRENT_SECONDARY_COLOR_EXT 0x8459 +#define GL_SECONDARY_COLOR_ARRAY_SIZE_EXT 0x845A +#define GL_SECONDARY_COLOR_ARRAY_TYPE_EXT 0x845B +#define GL_SECONDARY_COLOR_ARRAY_STRIDE_EXT 0x845C +#define GL_SECONDARY_COLOR_ARRAY_POINTER_EXT 0x845D +#define GL_SECONDARY_COLOR_ARRAY_EXT 0x845E +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3BEXTPROC) (GLbyte red, GLbyte green, GLbyte blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3BVEXTPROC) (const GLbyte *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3DEXTPROC) (GLdouble red, GLdouble green, GLdouble blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3DVEXTPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3FEXTPROC) (GLfloat red, GLfloat green, GLfloat blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3FVEXTPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3IEXTPROC) (GLint red, GLint green, GLint blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3IVEXTPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3SEXTPROC) (GLshort red, GLshort green, GLshort blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3SVEXTPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UBEXTPROC) (GLubyte red, GLubyte green, GLubyte blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UBVEXTPROC) (const GLubyte *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UIEXTPROC) (GLuint red, GLuint green, GLuint blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UIVEXTPROC) (const GLuint *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3USEXTPROC) (GLushort red, GLushort green, GLushort blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3USVEXTPROC) (const GLushort *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLORPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, const void *pointer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSecondaryColor3bEXT (GLbyte red, GLbyte green, GLbyte blue); +GLAPI void APIENTRY glSecondaryColor3bvEXT (const GLbyte *v); +GLAPI void APIENTRY glSecondaryColor3dEXT (GLdouble red, GLdouble green, GLdouble blue); +GLAPI void APIENTRY glSecondaryColor3dvEXT (const GLdouble *v); +GLAPI void APIENTRY glSecondaryColor3fEXT (GLfloat red, GLfloat green, GLfloat blue); +GLAPI void APIENTRY glSecondaryColor3fvEXT (const GLfloat *v); +GLAPI void APIENTRY glSecondaryColor3iEXT (GLint red, GLint green, GLint blue); +GLAPI void APIENTRY glSecondaryColor3ivEXT (const GLint *v); +GLAPI void APIENTRY glSecondaryColor3sEXT (GLshort red, GLshort green, GLshort blue); +GLAPI void APIENTRY glSecondaryColor3svEXT (const GLshort *v); +GLAPI void APIENTRY glSecondaryColor3ubEXT (GLubyte red, GLubyte green, GLubyte blue); +GLAPI void APIENTRY glSecondaryColor3ubvEXT (const GLubyte *v); +GLAPI void APIENTRY glSecondaryColor3uiEXT (GLuint red, GLuint green, GLuint blue); +GLAPI void APIENTRY glSecondaryColor3uivEXT (const GLuint *v); +GLAPI void APIENTRY glSecondaryColor3usEXT (GLushort red, GLushort green, GLushort blue); +GLAPI void APIENTRY glSecondaryColor3usvEXT (const GLushort *v); +GLAPI void APIENTRY glSecondaryColorPointerEXT (GLint size, GLenum type, GLsizei stride, const void *pointer); +#endif +#endif /* GL_EXT_secondary_color */ + +#ifndef GL_EXT_semaphore +#define GL_EXT_semaphore 1 +#define GL_LAYOUT_GENERAL_EXT 0x958D +#define GL_LAYOUT_COLOR_ATTACHMENT_EXT 0x958E +#define GL_LAYOUT_DEPTH_STENCIL_ATTACHMENT_EXT 0x958F +#define GL_LAYOUT_DEPTH_STENCIL_READ_ONLY_EXT 0x9590 +#define GL_LAYOUT_SHADER_READ_ONLY_EXT 0x9591 +#define GL_LAYOUT_TRANSFER_SRC_EXT 0x9592 +#define GL_LAYOUT_TRANSFER_DST_EXT 0x9593 +#define GL_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_EXT 0x9530 +#define GL_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_EXT 0x9531 +typedef void (APIENTRYP PFNGLGENSEMAPHORESEXTPROC) (GLsizei n, GLuint *semaphores); +typedef void (APIENTRYP PFNGLDELETESEMAPHORESEXTPROC) (GLsizei n, const GLuint *semaphores); +typedef GLboolean (APIENTRYP PFNGLISSEMAPHOREEXTPROC) (GLuint semaphore); +typedef void (APIENTRYP PFNGLSEMAPHOREPARAMETERUI64VEXTPROC) (GLuint semaphore, GLenum pname, const GLuint64 *params); +typedef void (APIENTRYP PFNGLGETSEMAPHOREPARAMETERUI64VEXTPROC) (GLuint semaphore, GLenum pname, GLuint64 *params); +typedef void (APIENTRYP PFNGLWAITSEMAPHOREEXTPROC) (GLuint semaphore, GLuint numBufferBarriers, const GLuint *buffers, GLuint numTextureBarriers, const GLuint *textures, const GLenum *srcLayouts); +typedef void (APIENTRYP PFNGLSIGNALSEMAPHOREEXTPROC) (GLuint semaphore, GLuint numBufferBarriers, const GLuint *buffers, GLuint numTextureBarriers, const GLuint *textures, const GLenum *dstLayouts); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenSemaphoresEXT (GLsizei n, GLuint *semaphores); +GLAPI void APIENTRY glDeleteSemaphoresEXT (GLsizei n, const GLuint *semaphores); +GLAPI GLboolean APIENTRY glIsSemaphoreEXT (GLuint semaphore); +GLAPI void APIENTRY glSemaphoreParameterui64vEXT (GLuint semaphore, GLenum pname, const GLuint64 *params); +GLAPI void APIENTRY glGetSemaphoreParameterui64vEXT (GLuint semaphore, GLenum pname, GLuint64 *params); +GLAPI void APIENTRY glWaitSemaphoreEXT (GLuint semaphore, GLuint numBufferBarriers, const GLuint *buffers, GLuint numTextureBarriers, const GLuint *textures, const GLenum *srcLayouts); +GLAPI void APIENTRY glSignalSemaphoreEXT (GLuint semaphore, GLuint numBufferBarriers, const GLuint *buffers, GLuint numTextureBarriers, const GLuint *textures, const GLenum *dstLayouts); +#endif +#endif /* GL_EXT_semaphore */ + +#ifndef GL_EXT_semaphore_fd +#define GL_EXT_semaphore_fd 1 +typedef void (APIENTRYP PFNGLIMPORTSEMAPHOREFDEXTPROC) (GLuint semaphore, GLenum handleType, GLint fd); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glImportSemaphoreFdEXT (GLuint semaphore, GLenum handleType, GLint fd); +#endif +#endif /* GL_EXT_semaphore_fd */ + +#ifndef GL_EXT_semaphore_win32 +#define GL_EXT_semaphore_win32 1 +#define GL_HANDLE_TYPE_D3D12_FENCE_EXT 0x9594 +#define GL_D3D12_FENCE_VALUE_EXT 0x9595 +typedef void (APIENTRYP PFNGLIMPORTSEMAPHOREWIN32HANDLEEXTPROC) (GLuint semaphore, GLenum handleType, void *handle); +typedef void (APIENTRYP PFNGLIMPORTSEMAPHOREWIN32NAMEEXTPROC) (GLuint semaphore, GLenum handleType, const void *name); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glImportSemaphoreWin32HandleEXT (GLuint semaphore, GLenum handleType, void *handle); +GLAPI void APIENTRY glImportSemaphoreWin32NameEXT (GLuint semaphore, GLenum handleType, const void *name); +#endif +#endif /* GL_EXT_semaphore_win32 */ + +#ifndef GL_EXT_separate_shader_objects +#define GL_EXT_separate_shader_objects 1 +#define GL_ACTIVE_PROGRAM_EXT 0x8B8D +typedef void (APIENTRYP PFNGLUSESHADERPROGRAMEXTPROC) (GLenum type, GLuint program); +typedef void (APIENTRYP PFNGLACTIVEPROGRAMEXTPROC) (GLuint program); +typedef GLuint (APIENTRYP PFNGLCREATESHADERPROGRAMEXTPROC) (GLenum type, const GLchar *string); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glUseShaderProgramEXT (GLenum type, GLuint program); +GLAPI void APIENTRY glActiveProgramEXT (GLuint program); +GLAPI GLuint APIENTRY glCreateShaderProgramEXT (GLenum type, const GLchar *string); +#endif +#endif /* GL_EXT_separate_shader_objects */ + +#ifndef GL_EXT_separate_specular_color +#define GL_EXT_separate_specular_color 1 +#define GL_LIGHT_MODEL_COLOR_CONTROL_EXT 0x81F8 +#define GL_SINGLE_COLOR_EXT 0x81F9 +#define GL_SEPARATE_SPECULAR_COLOR_EXT 0x81FA +#endif /* GL_EXT_separate_specular_color */ + +#ifndef GL_EXT_shader_framebuffer_fetch +#define GL_EXT_shader_framebuffer_fetch 1 +#define GL_FRAGMENT_SHADER_DISCARDS_SAMPLES_EXT 0x8A52 +#endif /* GL_EXT_shader_framebuffer_fetch */ + +#ifndef GL_EXT_shader_framebuffer_fetch_non_coherent +#define GL_EXT_shader_framebuffer_fetch_non_coherent 1 +typedef void (APIENTRYP PFNGLFRAMEBUFFERFETCHBARRIEREXTPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFramebufferFetchBarrierEXT (void); +#endif +#endif /* GL_EXT_shader_framebuffer_fetch_non_coherent */ + +#ifndef GL_EXT_shader_image_load_formatted +#define GL_EXT_shader_image_load_formatted 1 +#endif /* GL_EXT_shader_image_load_formatted */ + +#ifndef GL_EXT_shader_image_load_store +#define GL_EXT_shader_image_load_store 1 +#define GL_MAX_IMAGE_UNITS_EXT 0x8F38 +#define GL_MAX_COMBINED_IMAGE_UNITS_AND_FRAGMENT_OUTPUTS_EXT 0x8F39 +#define GL_IMAGE_BINDING_NAME_EXT 0x8F3A +#define GL_IMAGE_BINDING_LEVEL_EXT 0x8F3B +#define GL_IMAGE_BINDING_LAYERED_EXT 0x8F3C +#define GL_IMAGE_BINDING_LAYER_EXT 0x8F3D +#define GL_IMAGE_BINDING_ACCESS_EXT 0x8F3E +#define GL_IMAGE_1D_EXT 0x904C +#define GL_IMAGE_2D_EXT 0x904D +#define GL_IMAGE_3D_EXT 0x904E +#define GL_IMAGE_2D_RECT_EXT 0x904F +#define GL_IMAGE_CUBE_EXT 0x9050 +#define GL_IMAGE_BUFFER_EXT 0x9051 +#define GL_IMAGE_1D_ARRAY_EXT 0x9052 +#define GL_IMAGE_2D_ARRAY_EXT 0x9053 +#define GL_IMAGE_CUBE_MAP_ARRAY_EXT 0x9054 +#define GL_IMAGE_2D_MULTISAMPLE_EXT 0x9055 +#define GL_IMAGE_2D_MULTISAMPLE_ARRAY_EXT 0x9056 +#define GL_INT_IMAGE_1D_EXT 0x9057 +#define GL_INT_IMAGE_2D_EXT 0x9058 +#define GL_INT_IMAGE_3D_EXT 0x9059 +#define GL_INT_IMAGE_2D_RECT_EXT 0x905A +#define GL_INT_IMAGE_CUBE_EXT 0x905B +#define GL_INT_IMAGE_BUFFER_EXT 0x905C +#define GL_INT_IMAGE_1D_ARRAY_EXT 0x905D +#define GL_INT_IMAGE_2D_ARRAY_EXT 0x905E +#define GL_INT_IMAGE_CUBE_MAP_ARRAY_EXT 0x905F +#define GL_INT_IMAGE_2D_MULTISAMPLE_EXT 0x9060 +#define GL_INT_IMAGE_2D_MULTISAMPLE_ARRAY_EXT 0x9061 +#define GL_UNSIGNED_INT_IMAGE_1D_EXT 0x9062 +#define GL_UNSIGNED_INT_IMAGE_2D_EXT 0x9063 +#define GL_UNSIGNED_INT_IMAGE_3D_EXT 0x9064 +#define GL_UNSIGNED_INT_IMAGE_2D_RECT_EXT 0x9065 +#define GL_UNSIGNED_INT_IMAGE_CUBE_EXT 0x9066 +#define GL_UNSIGNED_INT_IMAGE_BUFFER_EXT 0x9067 +#define GL_UNSIGNED_INT_IMAGE_1D_ARRAY_EXT 0x9068 +#define GL_UNSIGNED_INT_IMAGE_2D_ARRAY_EXT 0x9069 +#define GL_UNSIGNED_INT_IMAGE_CUBE_MAP_ARRAY_EXT 0x906A +#define GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_EXT 0x906B +#define GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_ARRAY_EXT 0x906C +#define GL_MAX_IMAGE_SAMPLES_EXT 0x906D +#define GL_IMAGE_BINDING_FORMAT_EXT 0x906E +#define GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT_EXT 0x00000001 +#define GL_ELEMENT_ARRAY_BARRIER_BIT_EXT 0x00000002 +#define GL_UNIFORM_BARRIER_BIT_EXT 0x00000004 +#define GL_TEXTURE_FETCH_BARRIER_BIT_EXT 0x00000008 +#define GL_SHADER_IMAGE_ACCESS_BARRIER_BIT_EXT 0x00000020 +#define GL_COMMAND_BARRIER_BIT_EXT 0x00000040 +#define GL_PIXEL_BUFFER_BARRIER_BIT_EXT 0x00000080 +#define GL_TEXTURE_UPDATE_BARRIER_BIT_EXT 0x00000100 +#define GL_BUFFER_UPDATE_BARRIER_BIT_EXT 0x00000200 +#define GL_FRAMEBUFFER_BARRIER_BIT_EXT 0x00000400 +#define GL_TRANSFORM_FEEDBACK_BARRIER_BIT_EXT 0x00000800 +#define GL_ATOMIC_COUNTER_BARRIER_BIT_EXT 0x00001000 +#define GL_ALL_BARRIER_BITS_EXT 0xFFFFFFFF +typedef void (APIENTRYP PFNGLBINDIMAGETEXTUREEXTPROC) (GLuint index, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLint format); +typedef void (APIENTRYP PFNGLMEMORYBARRIEREXTPROC) (GLbitfield barriers); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindImageTextureEXT (GLuint index, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLint format); +GLAPI void APIENTRY glMemoryBarrierEXT (GLbitfield barriers); +#endif +#endif /* GL_EXT_shader_image_load_store */ + +#ifndef GL_EXT_shader_integer_mix +#define GL_EXT_shader_integer_mix 1 +#endif /* GL_EXT_shader_integer_mix */ + +#ifndef GL_EXT_shadow_funcs +#define GL_EXT_shadow_funcs 1 +#endif /* GL_EXT_shadow_funcs */ + +#ifndef GL_EXT_shared_texture_palette +#define GL_EXT_shared_texture_palette 1 +#define GL_SHARED_TEXTURE_PALETTE_EXT 0x81FB +#endif /* GL_EXT_shared_texture_palette */ + +#ifndef GL_EXT_sparse_texture2 +#define GL_EXT_sparse_texture2 1 +#endif /* GL_EXT_sparse_texture2 */ + +#ifndef GL_EXT_stencil_clear_tag +#define GL_EXT_stencil_clear_tag 1 +#define GL_STENCIL_TAG_BITS_EXT 0x88F2 +#define GL_STENCIL_CLEAR_TAG_VALUE_EXT 0x88F3 +typedef void (APIENTRYP PFNGLSTENCILCLEARTAGEXTPROC) (GLsizei stencilTagBits, GLuint stencilClearTag); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glStencilClearTagEXT (GLsizei stencilTagBits, GLuint stencilClearTag); +#endif +#endif /* GL_EXT_stencil_clear_tag */ + +#ifndef GL_EXT_stencil_two_side +#define GL_EXT_stencil_two_side 1 +#define GL_STENCIL_TEST_TWO_SIDE_EXT 0x8910 +#define GL_ACTIVE_STENCIL_FACE_EXT 0x8911 +typedef void (APIENTRYP PFNGLACTIVESTENCILFACEEXTPROC) (GLenum face); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glActiveStencilFaceEXT (GLenum face); +#endif +#endif /* GL_EXT_stencil_two_side */ + +#ifndef GL_EXT_stencil_wrap +#define GL_EXT_stencil_wrap 1 +#define GL_INCR_WRAP_EXT 0x8507 +#define GL_DECR_WRAP_EXT 0x8508 +#endif /* GL_EXT_stencil_wrap */ + +#ifndef GL_EXT_subtexture +#define GL_EXT_subtexture 1 +typedef void (APIENTRYP PFNGLTEXSUBIMAGE1DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLTEXSUBIMAGE2DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexSubImage1DEXT (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glTexSubImage2DEXT (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); +#endif +#endif /* GL_EXT_subtexture */ + +#ifndef GL_EXT_texture +#define GL_EXT_texture 1 +#define GL_ALPHA4_EXT 0x803B +#define GL_ALPHA8_EXT 0x803C +#define GL_ALPHA12_EXT 0x803D +#define GL_ALPHA16_EXT 0x803E +#define GL_LUMINANCE4_EXT 0x803F +#define GL_LUMINANCE8_EXT 0x8040 +#define GL_LUMINANCE12_EXT 0x8041 +#define GL_LUMINANCE16_EXT 0x8042 +#define GL_LUMINANCE4_ALPHA4_EXT 0x8043 +#define GL_LUMINANCE6_ALPHA2_EXT 0x8044 +#define GL_LUMINANCE8_ALPHA8_EXT 0x8045 +#define GL_LUMINANCE12_ALPHA4_EXT 0x8046 +#define GL_LUMINANCE12_ALPHA12_EXT 0x8047 +#define GL_LUMINANCE16_ALPHA16_EXT 0x8048 +#define GL_INTENSITY_EXT 0x8049 +#define GL_INTENSITY4_EXT 0x804A +#define GL_INTENSITY8_EXT 0x804B +#define GL_INTENSITY12_EXT 0x804C +#define GL_INTENSITY16_EXT 0x804D +#define GL_RGB2_EXT 0x804E +#define GL_RGB4_EXT 0x804F +#define GL_RGB5_EXT 0x8050 +#define GL_RGB8_EXT 0x8051 +#define GL_RGB10_EXT 0x8052 +#define GL_RGB12_EXT 0x8053 +#define GL_RGB16_EXT 0x8054 +#define GL_RGBA2_EXT 0x8055 +#define GL_RGBA4_EXT 0x8056 +#define GL_RGB5_A1_EXT 0x8057 +#define GL_RGBA8_EXT 0x8058 +#define GL_RGB10_A2_EXT 0x8059 +#define GL_RGBA12_EXT 0x805A +#define GL_RGBA16_EXT 0x805B +#define GL_TEXTURE_RED_SIZE_EXT 0x805C +#define GL_TEXTURE_GREEN_SIZE_EXT 0x805D +#define GL_TEXTURE_BLUE_SIZE_EXT 0x805E +#define GL_TEXTURE_ALPHA_SIZE_EXT 0x805F +#define GL_TEXTURE_LUMINANCE_SIZE_EXT 0x8060 +#define GL_TEXTURE_INTENSITY_SIZE_EXT 0x8061 +#define GL_REPLACE_EXT 0x8062 +#define GL_PROXY_TEXTURE_1D_EXT 0x8063 +#define GL_PROXY_TEXTURE_2D_EXT 0x8064 +#define GL_TEXTURE_TOO_LARGE_EXT 0x8065 +#endif /* GL_EXT_texture */ + +#ifndef GL_EXT_texture3D +#define GL_EXT_texture3D 1 +#define GL_PACK_SKIP_IMAGES_EXT 0x806B +#define GL_PACK_IMAGE_HEIGHT_EXT 0x806C +#define GL_UNPACK_SKIP_IMAGES_EXT 0x806D +#define GL_UNPACK_IMAGE_HEIGHT_EXT 0x806E +#define GL_TEXTURE_3D_EXT 0x806F +#define GL_PROXY_TEXTURE_3D_EXT 0x8070 +#define GL_TEXTURE_DEPTH_EXT 0x8071 +#define GL_TEXTURE_WRAP_R_EXT 0x8072 +#define GL_MAX_3D_TEXTURE_SIZE_EXT 0x8073 +typedef void (APIENTRYP PFNGLTEXIMAGE3DEXTPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLTEXSUBIMAGE3DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexImage3DEXT (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glTexSubImage3DEXT (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels); +#endif +#endif /* GL_EXT_texture3D */ + +#ifndef GL_EXT_texture_array +#define GL_EXT_texture_array 1 +#define GL_TEXTURE_1D_ARRAY_EXT 0x8C18 +#define GL_PROXY_TEXTURE_1D_ARRAY_EXT 0x8C19 +#define GL_TEXTURE_2D_ARRAY_EXT 0x8C1A +#define GL_PROXY_TEXTURE_2D_ARRAY_EXT 0x8C1B +#define GL_TEXTURE_BINDING_1D_ARRAY_EXT 0x8C1C +#define GL_TEXTURE_BINDING_2D_ARRAY_EXT 0x8C1D +#define GL_MAX_ARRAY_TEXTURE_LAYERS_EXT 0x88FF +#define GL_COMPARE_REF_DEPTH_TO_TEXTURE_EXT 0x884E +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURELAYEREXTPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFramebufferTextureLayerEXT (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); +#endif +#endif /* GL_EXT_texture_array */ + +#ifndef GL_EXT_texture_buffer_object +#define GL_EXT_texture_buffer_object 1 +#define GL_TEXTURE_BUFFER_EXT 0x8C2A +#define GL_MAX_TEXTURE_BUFFER_SIZE_EXT 0x8C2B +#define GL_TEXTURE_BINDING_BUFFER_EXT 0x8C2C +#define GL_TEXTURE_BUFFER_DATA_STORE_BINDING_EXT 0x8C2D +#define GL_TEXTURE_BUFFER_FORMAT_EXT 0x8C2E +typedef void (APIENTRYP PFNGLTEXBUFFEREXTPROC) (GLenum target, GLenum internalformat, GLuint buffer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexBufferEXT (GLenum target, GLenum internalformat, GLuint buffer); +#endif +#endif /* GL_EXT_texture_buffer_object */ + +#ifndef GL_EXT_texture_compression_latc +#define GL_EXT_texture_compression_latc 1 +#define GL_COMPRESSED_LUMINANCE_LATC1_EXT 0x8C70 +#define GL_COMPRESSED_SIGNED_LUMINANCE_LATC1_EXT 0x8C71 +#define GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT 0x8C72 +#define GL_COMPRESSED_SIGNED_LUMINANCE_ALPHA_LATC2_EXT 0x8C73 +#endif /* GL_EXT_texture_compression_latc */ + +#ifndef GL_EXT_texture_compression_rgtc +#define GL_EXT_texture_compression_rgtc 1 +#define GL_COMPRESSED_RED_RGTC1_EXT 0x8DBB +#define GL_COMPRESSED_SIGNED_RED_RGTC1_EXT 0x8DBC +#define GL_COMPRESSED_RED_GREEN_RGTC2_EXT 0x8DBD +#define GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT 0x8DBE +#endif /* GL_EXT_texture_compression_rgtc */ + +#ifndef GL_EXT_texture_compression_s3tc +#define GL_EXT_texture_compression_s3tc 1 +#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 +#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 +#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 +#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 +#endif /* GL_EXT_texture_compression_s3tc */ + +#ifndef GL_EXT_texture_cube_map +#define GL_EXT_texture_cube_map 1 +#define GL_NORMAL_MAP_EXT 0x8511 +#define GL_REFLECTION_MAP_EXT 0x8512 +#define GL_TEXTURE_CUBE_MAP_EXT 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP_EXT 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X_EXT 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y_EXT 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_EXT 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z_EXT 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_EXT 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP_EXT 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE_EXT 0x851C +#endif /* GL_EXT_texture_cube_map */ + +#ifndef GL_EXT_texture_env_add +#define GL_EXT_texture_env_add 1 +#endif /* GL_EXT_texture_env_add */ + +#ifndef GL_EXT_texture_env_combine +#define GL_EXT_texture_env_combine 1 +#define GL_COMBINE_EXT 0x8570 +#define GL_COMBINE_RGB_EXT 0x8571 +#define GL_COMBINE_ALPHA_EXT 0x8572 +#define GL_RGB_SCALE_EXT 0x8573 +#define GL_ADD_SIGNED_EXT 0x8574 +#define GL_INTERPOLATE_EXT 0x8575 +#define GL_CONSTANT_EXT 0x8576 +#define GL_PRIMARY_COLOR_EXT 0x8577 +#define GL_PREVIOUS_EXT 0x8578 +#define GL_SOURCE0_RGB_EXT 0x8580 +#define GL_SOURCE1_RGB_EXT 0x8581 +#define GL_SOURCE2_RGB_EXT 0x8582 +#define GL_SOURCE0_ALPHA_EXT 0x8588 +#define GL_SOURCE1_ALPHA_EXT 0x8589 +#define GL_SOURCE2_ALPHA_EXT 0x858A +#define GL_OPERAND0_RGB_EXT 0x8590 +#define GL_OPERAND1_RGB_EXT 0x8591 +#define GL_OPERAND2_RGB_EXT 0x8592 +#define GL_OPERAND0_ALPHA_EXT 0x8598 +#define GL_OPERAND1_ALPHA_EXT 0x8599 +#define GL_OPERAND2_ALPHA_EXT 0x859A +#endif /* GL_EXT_texture_env_combine */ + +#ifndef GL_EXT_texture_env_dot3 +#define GL_EXT_texture_env_dot3 1 +#define GL_DOT3_RGB_EXT 0x8740 +#define GL_DOT3_RGBA_EXT 0x8741 +#endif /* GL_EXT_texture_env_dot3 */ + +#ifndef GL_EXT_texture_filter_anisotropic +#define GL_EXT_texture_filter_anisotropic 1 +#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE +#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF +#endif /* GL_EXT_texture_filter_anisotropic */ + +#ifndef GL_EXT_texture_filter_minmax +#define GL_EXT_texture_filter_minmax 1 +#define GL_TEXTURE_REDUCTION_MODE_EXT 0x9366 +#define GL_WEIGHTED_AVERAGE_EXT 0x9367 +#endif /* GL_EXT_texture_filter_minmax */ + +#ifndef GL_EXT_texture_integer +#define GL_EXT_texture_integer 1 +#define GL_RGBA32UI_EXT 0x8D70 +#define GL_RGB32UI_EXT 0x8D71 +#define GL_ALPHA32UI_EXT 0x8D72 +#define GL_INTENSITY32UI_EXT 0x8D73 +#define GL_LUMINANCE32UI_EXT 0x8D74 +#define GL_LUMINANCE_ALPHA32UI_EXT 0x8D75 +#define GL_RGBA16UI_EXT 0x8D76 +#define GL_RGB16UI_EXT 0x8D77 +#define GL_ALPHA16UI_EXT 0x8D78 +#define GL_INTENSITY16UI_EXT 0x8D79 +#define GL_LUMINANCE16UI_EXT 0x8D7A +#define GL_LUMINANCE_ALPHA16UI_EXT 0x8D7B +#define GL_RGBA8UI_EXT 0x8D7C +#define GL_RGB8UI_EXT 0x8D7D +#define GL_ALPHA8UI_EXT 0x8D7E +#define GL_INTENSITY8UI_EXT 0x8D7F +#define GL_LUMINANCE8UI_EXT 0x8D80 +#define GL_LUMINANCE_ALPHA8UI_EXT 0x8D81 +#define GL_RGBA32I_EXT 0x8D82 +#define GL_RGB32I_EXT 0x8D83 +#define GL_ALPHA32I_EXT 0x8D84 +#define GL_INTENSITY32I_EXT 0x8D85 +#define GL_LUMINANCE32I_EXT 0x8D86 +#define GL_LUMINANCE_ALPHA32I_EXT 0x8D87 +#define GL_RGBA16I_EXT 0x8D88 +#define GL_RGB16I_EXT 0x8D89 +#define GL_ALPHA16I_EXT 0x8D8A +#define GL_INTENSITY16I_EXT 0x8D8B +#define GL_LUMINANCE16I_EXT 0x8D8C +#define GL_LUMINANCE_ALPHA16I_EXT 0x8D8D +#define GL_RGBA8I_EXT 0x8D8E +#define GL_RGB8I_EXT 0x8D8F +#define GL_ALPHA8I_EXT 0x8D90 +#define GL_INTENSITY8I_EXT 0x8D91 +#define GL_LUMINANCE8I_EXT 0x8D92 +#define GL_LUMINANCE_ALPHA8I_EXT 0x8D93 +#define GL_RED_INTEGER_EXT 0x8D94 +#define GL_GREEN_INTEGER_EXT 0x8D95 +#define GL_BLUE_INTEGER_EXT 0x8D96 +#define GL_ALPHA_INTEGER_EXT 0x8D97 +#define GL_RGB_INTEGER_EXT 0x8D98 +#define GL_RGBA_INTEGER_EXT 0x8D99 +#define GL_BGR_INTEGER_EXT 0x8D9A +#define GL_BGRA_INTEGER_EXT 0x8D9B +#define GL_LUMINANCE_INTEGER_EXT 0x8D9C +#define GL_LUMINANCE_ALPHA_INTEGER_EXT 0x8D9D +#define GL_RGBA_INTEGER_MODE_EXT 0x8D9E +typedef void (APIENTRYP PFNGLTEXPARAMETERIIVEXTPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLTEXPARAMETERIUIVEXTPROC) (GLenum target, GLenum pname, const GLuint *params); +typedef void (APIENTRYP PFNGLGETTEXPARAMETERIIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETTEXPARAMETERIUIVEXTPROC) (GLenum target, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLCLEARCOLORIIEXTPROC) (GLint red, GLint green, GLint blue, GLint alpha); +typedef void (APIENTRYP PFNGLCLEARCOLORIUIEXTPROC) (GLuint red, GLuint green, GLuint blue, GLuint alpha); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexParameterIivEXT (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glTexParameterIuivEXT (GLenum target, GLenum pname, const GLuint *params); +GLAPI void APIENTRY glGetTexParameterIivEXT (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetTexParameterIuivEXT (GLenum target, GLenum pname, GLuint *params); +GLAPI void APIENTRY glClearColorIiEXT (GLint red, GLint green, GLint blue, GLint alpha); +GLAPI void APIENTRY glClearColorIuiEXT (GLuint red, GLuint green, GLuint blue, GLuint alpha); +#endif +#endif /* GL_EXT_texture_integer */ + +#ifndef GL_EXT_texture_lod_bias +#define GL_EXT_texture_lod_bias 1 +#define GL_MAX_TEXTURE_LOD_BIAS_EXT 0x84FD +#define GL_TEXTURE_FILTER_CONTROL_EXT 0x8500 +#define GL_TEXTURE_LOD_BIAS_EXT 0x8501 +#endif /* GL_EXT_texture_lod_bias */ + +#ifndef GL_EXT_texture_mirror_clamp +#define GL_EXT_texture_mirror_clamp 1 +#define GL_MIRROR_CLAMP_EXT 0x8742 +#define GL_MIRROR_CLAMP_TO_EDGE_EXT 0x8743 +#define GL_MIRROR_CLAMP_TO_BORDER_EXT 0x8912 +#endif /* GL_EXT_texture_mirror_clamp */ + +#ifndef GL_EXT_texture_object +#define GL_EXT_texture_object 1 +#define GL_TEXTURE_PRIORITY_EXT 0x8066 +#define GL_TEXTURE_RESIDENT_EXT 0x8067 +#define GL_TEXTURE_1D_BINDING_EXT 0x8068 +#define GL_TEXTURE_2D_BINDING_EXT 0x8069 +#define GL_TEXTURE_3D_BINDING_EXT 0x806A +typedef GLboolean (APIENTRYP PFNGLARETEXTURESRESIDENTEXTPROC) (GLsizei n, const GLuint *textures, GLboolean *residences); +typedef void (APIENTRYP PFNGLBINDTEXTUREEXTPROC) (GLenum target, GLuint texture); +typedef void (APIENTRYP PFNGLDELETETEXTURESEXTPROC) (GLsizei n, const GLuint *textures); +typedef void (APIENTRYP PFNGLGENTEXTURESEXTPROC) (GLsizei n, GLuint *textures); +typedef GLboolean (APIENTRYP PFNGLISTEXTUREEXTPROC) (GLuint texture); +typedef void (APIENTRYP PFNGLPRIORITIZETEXTURESEXTPROC) (GLsizei n, const GLuint *textures, const GLclampf *priorities); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLboolean APIENTRY glAreTexturesResidentEXT (GLsizei n, const GLuint *textures, GLboolean *residences); +GLAPI void APIENTRY glBindTextureEXT (GLenum target, GLuint texture); +GLAPI void APIENTRY glDeleteTexturesEXT (GLsizei n, const GLuint *textures); +GLAPI void APIENTRY glGenTexturesEXT (GLsizei n, GLuint *textures); +GLAPI GLboolean APIENTRY glIsTextureEXT (GLuint texture); +GLAPI void APIENTRY glPrioritizeTexturesEXT (GLsizei n, const GLuint *textures, const GLclampf *priorities); +#endif +#endif /* GL_EXT_texture_object */ + +#ifndef GL_EXT_texture_perturb_normal +#define GL_EXT_texture_perturb_normal 1 +#define GL_PERTURB_EXT 0x85AE +#define GL_TEXTURE_NORMAL_EXT 0x85AF +typedef void (APIENTRYP PFNGLTEXTURENORMALEXTPROC) (GLenum mode); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTextureNormalEXT (GLenum mode); +#endif +#endif /* GL_EXT_texture_perturb_normal */ + +#ifndef GL_EXT_texture_sRGB +#define GL_EXT_texture_sRGB 1 +#define GL_SRGB_EXT 0x8C40 +#define GL_SRGB8_EXT 0x8C41 +#define GL_SRGB_ALPHA_EXT 0x8C42 +#define GL_SRGB8_ALPHA8_EXT 0x8C43 +#define GL_SLUMINANCE_ALPHA_EXT 0x8C44 +#define GL_SLUMINANCE8_ALPHA8_EXT 0x8C45 +#define GL_SLUMINANCE_EXT 0x8C46 +#define GL_SLUMINANCE8_EXT 0x8C47 +#define GL_COMPRESSED_SRGB_EXT 0x8C48 +#define GL_COMPRESSED_SRGB_ALPHA_EXT 0x8C49 +#define GL_COMPRESSED_SLUMINANCE_EXT 0x8C4A +#define GL_COMPRESSED_SLUMINANCE_ALPHA_EXT 0x8C4B +#define GL_COMPRESSED_SRGB_S3TC_DXT1_EXT 0x8C4C +#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT 0x8C4D +#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT 0x8C4E +#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT 0x8C4F +#endif /* GL_EXT_texture_sRGB */ + +#ifndef GL_EXT_texture_sRGB_R8 +#define GL_EXT_texture_sRGB_R8 1 +#define GL_SR8_EXT 0x8FBD +#endif /* GL_EXT_texture_sRGB_R8 */ + +#ifndef GL_EXT_texture_sRGB_RG8 +#define GL_EXT_texture_sRGB_RG8 1 +#define GL_SRG8_EXT 0x8FBE +#endif /* GL_EXT_texture_sRGB_RG8 */ + +#ifndef GL_EXT_texture_sRGB_decode +#define GL_EXT_texture_sRGB_decode 1 +#define GL_TEXTURE_SRGB_DECODE_EXT 0x8A48 +#define GL_DECODE_EXT 0x8A49 +#define GL_SKIP_DECODE_EXT 0x8A4A +#endif /* GL_EXT_texture_sRGB_decode */ + +#ifndef GL_EXT_texture_shadow_lod +#define GL_EXT_texture_shadow_lod 1 +#endif /* GL_EXT_texture_shadow_lod */ + +#ifndef GL_EXT_texture_shared_exponent +#define GL_EXT_texture_shared_exponent 1 +#define GL_RGB9_E5_EXT 0x8C3D +#define GL_UNSIGNED_INT_5_9_9_9_REV_EXT 0x8C3E +#define GL_TEXTURE_SHARED_SIZE_EXT 0x8C3F +#endif /* GL_EXT_texture_shared_exponent */ + +#ifndef GL_EXT_texture_snorm +#define GL_EXT_texture_snorm 1 +#define GL_ALPHA_SNORM 0x9010 +#define GL_LUMINANCE_SNORM 0x9011 +#define GL_LUMINANCE_ALPHA_SNORM 0x9012 +#define GL_INTENSITY_SNORM 0x9013 +#define GL_ALPHA8_SNORM 0x9014 +#define GL_LUMINANCE8_SNORM 0x9015 +#define GL_LUMINANCE8_ALPHA8_SNORM 0x9016 +#define GL_INTENSITY8_SNORM 0x9017 +#define GL_ALPHA16_SNORM 0x9018 +#define GL_LUMINANCE16_SNORM 0x9019 +#define GL_LUMINANCE16_ALPHA16_SNORM 0x901A +#define GL_INTENSITY16_SNORM 0x901B +#define GL_RED_SNORM 0x8F90 +#define GL_RG_SNORM 0x8F91 +#define GL_RGB_SNORM 0x8F92 +#define GL_RGBA_SNORM 0x8F93 +#endif /* GL_EXT_texture_snorm */ + +#ifndef GL_EXT_texture_storage +#define GL_EXT_texture_storage 1 +#define GL_TEXTURE_IMMUTABLE_FORMAT_EXT 0x912F +#define GL_RGBA32F_EXT 0x8814 +#define GL_RGB32F_EXT 0x8815 +#define GL_ALPHA32F_EXT 0x8816 +#define GL_LUMINANCE32F_EXT 0x8818 +#define GL_LUMINANCE_ALPHA32F_EXT 0x8819 +#define GL_RGBA16F_EXT 0x881A +#define GL_RGB16F_EXT 0x881B +#define GL_ALPHA16F_EXT 0x881C +#define GL_LUMINANCE16F_EXT 0x881E +#define GL_LUMINANCE_ALPHA16F_EXT 0x881F +#define GL_BGRA8_EXT 0x93A1 +#define GL_R8_EXT 0x8229 +#define GL_RG8_EXT 0x822B +#define GL_R32F_EXT 0x822E +#define GL_RG32F_EXT 0x8230 +#define GL_R16F_EXT 0x822D +#define GL_RG16F_EXT 0x822F +typedef void (APIENTRYP PFNGLTEXSTORAGE1DEXTPROC) (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width); +typedef void (APIENTRYP PFNGLTEXSTORAGE2DEXTPROC) (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLTEXSTORAGE3DEXTPROC) (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexStorage1DEXT (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width); +GLAPI void APIENTRY glTexStorage2DEXT (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); +GLAPI void APIENTRY glTexStorage3DEXT (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth); +#endif +#endif /* GL_EXT_texture_storage */ + +#ifndef GL_EXT_texture_swizzle +#define GL_EXT_texture_swizzle 1 +#define GL_TEXTURE_SWIZZLE_R_EXT 0x8E42 +#define GL_TEXTURE_SWIZZLE_G_EXT 0x8E43 +#define GL_TEXTURE_SWIZZLE_B_EXT 0x8E44 +#define GL_TEXTURE_SWIZZLE_A_EXT 0x8E45 +#define GL_TEXTURE_SWIZZLE_RGBA_EXT 0x8E46 +#endif /* GL_EXT_texture_swizzle */ + +#ifndef GL_EXT_timer_query +#define GL_EXT_timer_query 1 +#define GL_TIME_ELAPSED_EXT 0x88BF +typedef void (APIENTRYP PFNGLGETQUERYOBJECTI64VEXTPROC) (GLuint id, GLenum pname, GLint64 *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTUI64VEXTPROC) (GLuint id, GLenum pname, GLuint64 *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetQueryObjecti64vEXT (GLuint id, GLenum pname, GLint64 *params); +GLAPI void APIENTRY glGetQueryObjectui64vEXT (GLuint id, GLenum pname, GLuint64 *params); +#endif +#endif /* GL_EXT_timer_query */ + +#ifndef GL_EXT_transform_feedback +#define GL_EXT_transform_feedback 1 +#define GL_TRANSFORM_FEEDBACK_BUFFER_EXT 0x8C8E +#define GL_TRANSFORM_FEEDBACK_BUFFER_START_EXT 0x8C84 +#define GL_TRANSFORM_FEEDBACK_BUFFER_SIZE_EXT 0x8C85 +#define GL_TRANSFORM_FEEDBACK_BUFFER_BINDING_EXT 0x8C8F +#define GL_INTERLEAVED_ATTRIBS_EXT 0x8C8C +#define GL_SEPARATE_ATTRIBS_EXT 0x8C8D +#define GL_PRIMITIVES_GENERATED_EXT 0x8C87 +#define GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN_EXT 0x8C88 +#define GL_RASTERIZER_DISCARD_EXT 0x8C89 +#define GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS_EXT 0x8C8A +#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS_EXT 0x8C8B +#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS_EXT 0x8C80 +#define GL_TRANSFORM_FEEDBACK_VARYINGS_EXT 0x8C83 +#define GL_TRANSFORM_FEEDBACK_BUFFER_MODE_EXT 0x8C7F +#define GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH_EXT 0x8C76 +typedef void (APIENTRYP PFNGLBEGINTRANSFORMFEEDBACKEXTPROC) (GLenum primitiveMode); +typedef void (APIENTRYP PFNGLENDTRANSFORMFEEDBACKEXTPROC) (void); +typedef void (APIENTRYP PFNGLBINDBUFFERRANGEEXTPROC) (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); +typedef void (APIENTRYP PFNGLBINDBUFFEROFFSETEXTPROC) (GLenum target, GLuint index, GLuint buffer, GLintptr offset); +typedef void (APIENTRYP PFNGLBINDBUFFERBASEEXTPROC) (GLenum target, GLuint index, GLuint buffer); +typedef void (APIENTRYP PFNGLTRANSFORMFEEDBACKVARYINGSEXTPROC) (GLuint program, GLsizei count, const GLchar *const*varyings, GLenum bufferMode); +typedef void (APIENTRYP PFNGLGETTRANSFORMFEEDBACKVARYINGEXTPROC) (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLsizei *size, GLenum *type, GLchar *name); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBeginTransformFeedbackEXT (GLenum primitiveMode); +GLAPI void APIENTRY glEndTransformFeedbackEXT (void); +GLAPI void APIENTRY glBindBufferRangeEXT (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); +GLAPI void APIENTRY glBindBufferOffsetEXT (GLenum target, GLuint index, GLuint buffer, GLintptr offset); +GLAPI void APIENTRY glBindBufferBaseEXT (GLenum target, GLuint index, GLuint buffer); +GLAPI void APIENTRY glTransformFeedbackVaryingsEXT (GLuint program, GLsizei count, const GLchar *const*varyings, GLenum bufferMode); +GLAPI void APIENTRY glGetTransformFeedbackVaryingEXT (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLsizei *size, GLenum *type, GLchar *name); +#endif +#endif /* GL_EXT_transform_feedback */ + +#ifndef GL_EXT_vertex_array +#define GL_EXT_vertex_array 1 +#define GL_VERTEX_ARRAY_EXT 0x8074 +#define GL_NORMAL_ARRAY_EXT 0x8075 +#define GL_COLOR_ARRAY_EXT 0x8076 +#define GL_INDEX_ARRAY_EXT 0x8077 +#define GL_TEXTURE_COORD_ARRAY_EXT 0x8078 +#define GL_EDGE_FLAG_ARRAY_EXT 0x8079 +#define GL_VERTEX_ARRAY_SIZE_EXT 0x807A +#define GL_VERTEX_ARRAY_TYPE_EXT 0x807B +#define GL_VERTEX_ARRAY_STRIDE_EXT 0x807C +#define GL_VERTEX_ARRAY_COUNT_EXT 0x807D +#define GL_NORMAL_ARRAY_TYPE_EXT 0x807E +#define GL_NORMAL_ARRAY_STRIDE_EXT 0x807F +#define GL_NORMAL_ARRAY_COUNT_EXT 0x8080 +#define GL_COLOR_ARRAY_SIZE_EXT 0x8081 +#define GL_COLOR_ARRAY_TYPE_EXT 0x8082 +#define GL_COLOR_ARRAY_STRIDE_EXT 0x8083 +#define GL_COLOR_ARRAY_COUNT_EXT 0x8084 +#define GL_INDEX_ARRAY_TYPE_EXT 0x8085 +#define GL_INDEX_ARRAY_STRIDE_EXT 0x8086 +#define GL_INDEX_ARRAY_COUNT_EXT 0x8087 +#define GL_TEXTURE_COORD_ARRAY_SIZE_EXT 0x8088 +#define GL_TEXTURE_COORD_ARRAY_TYPE_EXT 0x8089 +#define GL_TEXTURE_COORD_ARRAY_STRIDE_EXT 0x808A +#define GL_TEXTURE_COORD_ARRAY_COUNT_EXT 0x808B +#define GL_EDGE_FLAG_ARRAY_STRIDE_EXT 0x808C +#define GL_EDGE_FLAG_ARRAY_COUNT_EXT 0x808D +#define GL_VERTEX_ARRAY_POINTER_EXT 0x808E +#define GL_NORMAL_ARRAY_POINTER_EXT 0x808F +#define GL_COLOR_ARRAY_POINTER_EXT 0x8090 +#define GL_INDEX_ARRAY_POINTER_EXT 0x8091 +#define GL_TEXTURE_COORD_ARRAY_POINTER_EXT 0x8092 +#define GL_EDGE_FLAG_ARRAY_POINTER_EXT 0x8093 +typedef void (APIENTRYP PFNGLARRAYELEMENTEXTPROC) (GLint i); +typedef void (APIENTRYP PFNGLCOLORPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLsizei count, const void *pointer); +typedef void (APIENTRYP PFNGLDRAWARRAYSEXTPROC) (GLenum mode, GLint first, GLsizei count); +typedef void (APIENTRYP PFNGLEDGEFLAGPOINTEREXTPROC) (GLsizei stride, GLsizei count, const GLboolean *pointer); +typedef void (APIENTRYP PFNGLGETPOINTERVEXTPROC) (GLenum pname, void **params); +typedef void (APIENTRYP PFNGLINDEXPOINTEREXTPROC) (GLenum type, GLsizei stride, GLsizei count, const void *pointer); +typedef void (APIENTRYP PFNGLNORMALPOINTEREXTPROC) (GLenum type, GLsizei stride, GLsizei count, const void *pointer); +typedef void (APIENTRYP PFNGLTEXCOORDPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLsizei count, const void *pointer); +typedef void (APIENTRYP PFNGLVERTEXPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLsizei count, const void *pointer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glArrayElementEXT (GLint i); +GLAPI void APIENTRY glColorPointerEXT (GLint size, GLenum type, GLsizei stride, GLsizei count, const void *pointer); +GLAPI void APIENTRY glDrawArraysEXT (GLenum mode, GLint first, GLsizei count); +GLAPI void APIENTRY glEdgeFlagPointerEXT (GLsizei stride, GLsizei count, const GLboolean *pointer); +GLAPI void APIENTRY glGetPointervEXT (GLenum pname, void **params); +GLAPI void APIENTRY glIndexPointerEXT (GLenum type, GLsizei stride, GLsizei count, const void *pointer); +GLAPI void APIENTRY glNormalPointerEXT (GLenum type, GLsizei stride, GLsizei count, const void *pointer); +GLAPI void APIENTRY glTexCoordPointerEXT (GLint size, GLenum type, GLsizei stride, GLsizei count, const void *pointer); +GLAPI void APIENTRY glVertexPointerEXT (GLint size, GLenum type, GLsizei stride, GLsizei count, const void *pointer); +#endif +#endif /* GL_EXT_vertex_array */ + +#ifndef GL_EXT_vertex_array_bgra +#define GL_EXT_vertex_array_bgra 1 +#endif /* GL_EXT_vertex_array_bgra */ + +#ifndef GL_EXT_vertex_attrib_64bit +#define GL_EXT_vertex_attrib_64bit 1 +#define GL_DOUBLE_VEC2_EXT 0x8FFC +#define GL_DOUBLE_VEC3_EXT 0x8FFD +#define GL_DOUBLE_VEC4_EXT 0x8FFE +#define GL_DOUBLE_MAT2_EXT 0x8F46 +#define GL_DOUBLE_MAT3_EXT 0x8F47 +#define GL_DOUBLE_MAT4_EXT 0x8F48 +#define GL_DOUBLE_MAT2x3_EXT 0x8F49 +#define GL_DOUBLE_MAT2x4_EXT 0x8F4A +#define GL_DOUBLE_MAT3x2_EXT 0x8F4B +#define GL_DOUBLE_MAT3x4_EXT 0x8F4C +#define GL_DOUBLE_MAT4x2_EXT 0x8F4D +#define GL_DOUBLE_MAT4x3_EXT 0x8F4E +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1DEXTPROC) (GLuint index, GLdouble x); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL2DEXTPROC) (GLuint index, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL3DEXTPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL4DEXTPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1DVEXTPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL2DVEXTPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL3DVEXTPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL4DVEXTPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBLPOINTEREXTPROC) (GLuint index, GLint size, GLenum type, GLsizei stride, const void *pointer); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBLDVEXTPROC) (GLuint index, GLenum pname, GLdouble *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexAttribL1dEXT (GLuint index, GLdouble x); +GLAPI void APIENTRY glVertexAttribL2dEXT (GLuint index, GLdouble x, GLdouble y); +GLAPI void APIENTRY glVertexAttribL3dEXT (GLuint index, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glVertexAttribL4dEXT (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glVertexAttribL1dvEXT (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribL2dvEXT (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribL3dvEXT (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribL4dvEXT (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribLPointerEXT (GLuint index, GLint size, GLenum type, GLsizei stride, const void *pointer); +GLAPI void APIENTRY glGetVertexAttribLdvEXT (GLuint index, GLenum pname, GLdouble *params); +#endif +#endif /* GL_EXT_vertex_attrib_64bit */ + +#ifndef GL_EXT_vertex_shader +#define GL_EXT_vertex_shader 1 +#define GL_VERTEX_SHADER_EXT 0x8780 +#define GL_VERTEX_SHADER_BINDING_EXT 0x8781 +#define GL_OP_INDEX_EXT 0x8782 +#define GL_OP_NEGATE_EXT 0x8783 +#define GL_OP_DOT3_EXT 0x8784 +#define GL_OP_DOT4_EXT 0x8785 +#define GL_OP_MUL_EXT 0x8786 +#define GL_OP_ADD_EXT 0x8787 +#define GL_OP_MADD_EXT 0x8788 +#define GL_OP_FRAC_EXT 0x8789 +#define GL_OP_MAX_EXT 0x878A +#define GL_OP_MIN_EXT 0x878B +#define GL_OP_SET_GE_EXT 0x878C +#define GL_OP_SET_LT_EXT 0x878D +#define GL_OP_CLAMP_EXT 0x878E +#define GL_OP_FLOOR_EXT 0x878F +#define GL_OP_ROUND_EXT 0x8790 +#define GL_OP_EXP_BASE_2_EXT 0x8791 +#define GL_OP_LOG_BASE_2_EXT 0x8792 +#define GL_OP_POWER_EXT 0x8793 +#define GL_OP_RECIP_EXT 0x8794 +#define GL_OP_RECIP_SQRT_EXT 0x8795 +#define GL_OP_SUB_EXT 0x8796 +#define GL_OP_CROSS_PRODUCT_EXT 0x8797 +#define GL_OP_MULTIPLY_MATRIX_EXT 0x8798 +#define GL_OP_MOV_EXT 0x8799 +#define GL_OUTPUT_VERTEX_EXT 0x879A +#define GL_OUTPUT_COLOR0_EXT 0x879B +#define GL_OUTPUT_COLOR1_EXT 0x879C +#define GL_OUTPUT_TEXTURE_COORD0_EXT 0x879D +#define GL_OUTPUT_TEXTURE_COORD1_EXT 0x879E +#define GL_OUTPUT_TEXTURE_COORD2_EXT 0x879F +#define GL_OUTPUT_TEXTURE_COORD3_EXT 0x87A0 +#define GL_OUTPUT_TEXTURE_COORD4_EXT 0x87A1 +#define GL_OUTPUT_TEXTURE_COORD5_EXT 0x87A2 +#define GL_OUTPUT_TEXTURE_COORD6_EXT 0x87A3 +#define GL_OUTPUT_TEXTURE_COORD7_EXT 0x87A4 +#define GL_OUTPUT_TEXTURE_COORD8_EXT 0x87A5 +#define GL_OUTPUT_TEXTURE_COORD9_EXT 0x87A6 +#define GL_OUTPUT_TEXTURE_COORD10_EXT 0x87A7 +#define GL_OUTPUT_TEXTURE_COORD11_EXT 0x87A8 +#define GL_OUTPUT_TEXTURE_COORD12_EXT 0x87A9 +#define GL_OUTPUT_TEXTURE_COORD13_EXT 0x87AA +#define GL_OUTPUT_TEXTURE_COORD14_EXT 0x87AB +#define GL_OUTPUT_TEXTURE_COORD15_EXT 0x87AC +#define GL_OUTPUT_TEXTURE_COORD16_EXT 0x87AD +#define GL_OUTPUT_TEXTURE_COORD17_EXT 0x87AE +#define GL_OUTPUT_TEXTURE_COORD18_EXT 0x87AF +#define GL_OUTPUT_TEXTURE_COORD19_EXT 0x87B0 +#define GL_OUTPUT_TEXTURE_COORD20_EXT 0x87B1 +#define GL_OUTPUT_TEXTURE_COORD21_EXT 0x87B2 +#define GL_OUTPUT_TEXTURE_COORD22_EXT 0x87B3 +#define GL_OUTPUT_TEXTURE_COORD23_EXT 0x87B4 +#define GL_OUTPUT_TEXTURE_COORD24_EXT 0x87B5 +#define GL_OUTPUT_TEXTURE_COORD25_EXT 0x87B6 +#define GL_OUTPUT_TEXTURE_COORD26_EXT 0x87B7 +#define GL_OUTPUT_TEXTURE_COORD27_EXT 0x87B8 +#define GL_OUTPUT_TEXTURE_COORD28_EXT 0x87B9 +#define GL_OUTPUT_TEXTURE_COORD29_EXT 0x87BA +#define GL_OUTPUT_TEXTURE_COORD30_EXT 0x87BB +#define GL_OUTPUT_TEXTURE_COORD31_EXT 0x87BC +#define GL_OUTPUT_FOG_EXT 0x87BD +#define GL_SCALAR_EXT 0x87BE +#define GL_VECTOR_EXT 0x87BF +#define GL_MATRIX_EXT 0x87C0 +#define GL_VARIANT_EXT 0x87C1 +#define GL_INVARIANT_EXT 0x87C2 +#define GL_LOCAL_CONSTANT_EXT 0x87C3 +#define GL_LOCAL_EXT 0x87C4 +#define GL_MAX_VERTEX_SHADER_INSTRUCTIONS_EXT 0x87C5 +#define GL_MAX_VERTEX_SHADER_VARIANTS_EXT 0x87C6 +#define GL_MAX_VERTEX_SHADER_INVARIANTS_EXT 0x87C7 +#define GL_MAX_VERTEX_SHADER_LOCAL_CONSTANTS_EXT 0x87C8 +#define GL_MAX_VERTEX_SHADER_LOCALS_EXT 0x87C9 +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_INSTRUCTIONS_EXT 0x87CA +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_VARIANTS_EXT 0x87CB +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_LOCAL_CONSTANTS_EXT 0x87CC +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_INVARIANTS_EXT 0x87CD +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_LOCALS_EXT 0x87CE +#define GL_VERTEX_SHADER_INSTRUCTIONS_EXT 0x87CF +#define GL_VERTEX_SHADER_VARIANTS_EXT 0x87D0 +#define GL_VERTEX_SHADER_INVARIANTS_EXT 0x87D1 +#define GL_VERTEX_SHADER_LOCAL_CONSTANTS_EXT 0x87D2 +#define GL_VERTEX_SHADER_LOCALS_EXT 0x87D3 +#define GL_VERTEX_SHADER_OPTIMIZED_EXT 0x87D4 +#define GL_X_EXT 0x87D5 +#define GL_Y_EXT 0x87D6 +#define GL_Z_EXT 0x87D7 +#define GL_W_EXT 0x87D8 +#define GL_NEGATIVE_X_EXT 0x87D9 +#define GL_NEGATIVE_Y_EXT 0x87DA +#define GL_NEGATIVE_Z_EXT 0x87DB +#define GL_NEGATIVE_W_EXT 0x87DC +#define GL_ZERO_EXT 0x87DD +#define GL_ONE_EXT 0x87DE +#define GL_NEGATIVE_ONE_EXT 0x87DF +#define GL_NORMALIZED_RANGE_EXT 0x87E0 +#define GL_FULL_RANGE_EXT 0x87E1 +#define GL_CURRENT_VERTEX_EXT 0x87E2 +#define GL_MVP_MATRIX_EXT 0x87E3 +#define GL_VARIANT_VALUE_EXT 0x87E4 +#define GL_VARIANT_DATATYPE_EXT 0x87E5 +#define GL_VARIANT_ARRAY_STRIDE_EXT 0x87E6 +#define GL_VARIANT_ARRAY_TYPE_EXT 0x87E7 +#define GL_VARIANT_ARRAY_EXT 0x87E8 +#define GL_VARIANT_ARRAY_POINTER_EXT 0x87E9 +#define GL_INVARIANT_VALUE_EXT 0x87EA +#define GL_INVARIANT_DATATYPE_EXT 0x87EB +#define GL_LOCAL_CONSTANT_VALUE_EXT 0x87EC +#define GL_LOCAL_CONSTANT_DATATYPE_EXT 0x87ED +typedef void (APIENTRYP PFNGLBEGINVERTEXSHADEREXTPROC) (void); +typedef void (APIENTRYP PFNGLENDVERTEXSHADEREXTPROC) (void); +typedef void (APIENTRYP PFNGLBINDVERTEXSHADEREXTPROC) (GLuint id); +typedef GLuint (APIENTRYP PFNGLGENVERTEXSHADERSEXTPROC) (GLuint range); +typedef void (APIENTRYP PFNGLDELETEVERTEXSHADEREXTPROC) (GLuint id); +typedef void (APIENTRYP PFNGLSHADEROP1EXTPROC) (GLenum op, GLuint res, GLuint arg1); +typedef void (APIENTRYP PFNGLSHADEROP2EXTPROC) (GLenum op, GLuint res, GLuint arg1, GLuint arg2); +typedef void (APIENTRYP PFNGLSHADEROP3EXTPROC) (GLenum op, GLuint res, GLuint arg1, GLuint arg2, GLuint arg3); +typedef void (APIENTRYP PFNGLSWIZZLEEXTPROC) (GLuint res, GLuint in, GLenum outX, GLenum outY, GLenum outZ, GLenum outW); +typedef void (APIENTRYP PFNGLWRITEMASKEXTPROC) (GLuint res, GLuint in, GLenum outX, GLenum outY, GLenum outZ, GLenum outW); +typedef void (APIENTRYP PFNGLINSERTCOMPONENTEXTPROC) (GLuint res, GLuint src, GLuint num); +typedef void (APIENTRYP PFNGLEXTRACTCOMPONENTEXTPROC) (GLuint res, GLuint src, GLuint num); +typedef GLuint (APIENTRYP PFNGLGENSYMBOLSEXTPROC) (GLenum datatype, GLenum storagetype, GLenum range, GLuint components); +typedef void (APIENTRYP PFNGLSETINVARIANTEXTPROC) (GLuint id, GLenum type, const void *addr); +typedef void (APIENTRYP PFNGLSETLOCALCONSTANTEXTPROC) (GLuint id, GLenum type, const void *addr); +typedef void (APIENTRYP PFNGLVARIANTBVEXTPROC) (GLuint id, const GLbyte *addr); +typedef void (APIENTRYP PFNGLVARIANTSVEXTPROC) (GLuint id, const GLshort *addr); +typedef void (APIENTRYP PFNGLVARIANTIVEXTPROC) (GLuint id, const GLint *addr); +typedef void (APIENTRYP PFNGLVARIANTFVEXTPROC) (GLuint id, const GLfloat *addr); +typedef void (APIENTRYP PFNGLVARIANTDVEXTPROC) (GLuint id, const GLdouble *addr); +typedef void (APIENTRYP PFNGLVARIANTUBVEXTPROC) (GLuint id, const GLubyte *addr); +typedef void (APIENTRYP PFNGLVARIANTUSVEXTPROC) (GLuint id, const GLushort *addr); +typedef void (APIENTRYP PFNGLVARIANTUIVEXTPROC) (GLuint id, const GLuint *addr); +typedef void (APIENTRYP PFNGLVARIANTPOINTEREXTPROC) (GLuint id, GLenum type, GLuint stride, const void *addr); +typedef void (APIENTRYP PFNGLENABLEVARIANTCLIENTSTATEEXTPROC) (GLuint id); +typedef void (APIENTRYP PFNGLDISABLEVARIANTCLIENTSTATEEXTPROC) (GLuint id); +typedef GLuint (APIENTRYP PFNGLBINDLIGHTPARAMETEREXTPROC) (GLenum light, GLenum value); +typedef GLuint (APIENTRYP PFNGLBINDMATERIALPARAMETEREXTPROC) (GLenum face, GLenum value); +typedef GLuint (APIENTRYP PFNGLBINDTEXGENPARAMETEREXTPROC) (GLenum unit, GLenum coord, GLenum value); +typedef GLuint (APIENTRYP PFNGLBINDTEXTUREUNITPARAMETEREXTPROC) (GLenum unit, GLenum value); +typedef GLuint (APIENTRYP PFNGLBINDPARAMETEREXTPROC) (GLenum value); +typedef GLboolean (APIENTRYP PFNGLISVARIANTENABLEDEXTPROC) (GLuint id, GLenum cap); +typedef void (APIENTRYP PFNGLGETVARIANTBOOLEANVEXTPROC) (GLuint id, GLenum value, GLboolean *data); +typedef void (APIENTRYP PFNGLGETVARIANTINTEGERVEXTPROC) (GLuint id, GLenum value, GLint *data); +typedef void (APIENTRYP PFNGLGETVARIANTFLOATVEXTPROC) (GLuint id, GLenum value, GLfloat *data); +typedef void (APIENTRYP PFNGLGETVARIANTPOINTERVEXTPROC) (GLuint id, GLenum value, void **data); +typedef void (APIENTRYP PFNGLGETINVARIANTBOOLEANVEXTPROC) (GLuint id, GLenum value, GLboolean *data); +typedef void (APIENTRYP PFNGLGETINVARIANTINTEGERVEXTPROC) (GLuint id, GLenum value, GLint *data); +typedef void (APIENTRYP PFNGLGETINVARIANTFLOATVEXTPROC) (GLuint id, GLenum value, GLfloat *data); +typedef void (APIENTRYP PFNGLGETLOCALCONSTANTBOOLEANVEXTPROC) (GLuint id, GLenum value, GLboolean *data); +typedef void (APIENTRYP PFNGLGETLOCALCONSTANTINTEGERVEXTPROC) (GLuint id, GLenum value, GLint *data); +typedef void (APIENTRYP PFNGLGETLOCALCONSTANTFLOATVEXTPROC) (GLuint id, GLenum value, GLfloat *data); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBeginVertexShaderEXT (void); +GLAPI void APIENTRY glEndVertexShaderEXT (void); +GLAPI void APIENTRY glBindVertexShaderEXT (GLuint id); +GLAPI GLuint APIENTRY glGenVertexShadersEXT (GLuint range); +GLAPI void APIENTRY glDeleteVertexShaderEXT (GLuint id); +GLAPI void APIENTRY glShaderOp1EXT (GLenum op, GLuint res, GLuint arg1); +GLAPI void APIENTRY glShaderOp2EXT (GLenum op, GLuint res, GLuint arg1, GLuint arg2); +GLAPI void APIENTRY glShaderOp3EXT (GLenum op, GLuint res, GLuint arg1, GLuint arg2, GLuint arg3); +GLAPI void APIENTRY glSwizzleEXT (GLuint res, GLuint in, GLenum outX, GLenum outY, GLenum outZ, GLenum outW); +GLAPI void APIENTRY glWriteMaskEXT (GLuint res, GLuint in, GLenum outX, GLenum outY, GLenum outZ, GLenum outW); +GLAPI void APIENTRY glInsertComponentEXT (GLuint res, GLuint src, GLuint num); +GLAPI void APIENTRY glExtractComponentEXT (GLuint res, GLuint src, GLuint num); +GLAPI GLuint APIENTRY glGenSymbolsEXT (GLenum datatype, GLenum storagetype, GLenum range, GLuint components); +GLAPI void APIENTRY glSetInvariantEXT (GLuint id, GLenum type, const void *addr); +GLAPI void APIENTRY glSetLocalConstantEXT (GLuint id, GLenum type, const void *addr); +GLAPI void APIENTRY glVariantbvEXT (GLuint id, const GLbyte *addr); +GLAPI void APIENTRY glVariantsvEXT (GLuint id, const GLshort *addr); +GLAPI void APIENTRY glVariantivEXT (GLuint id, const GLint *addr); +GLAPI void APIENTRY glVariantfvEXT (GLuint id, const GLfloat *addr); +GLAPI void APIENTRY glVariantdvEXT (GLuint id, const GLdouble *addr); +GLAPI void APIENTRY glVariantubvEXT (GLuint id, const GLubyte *addr); +GLAPI void APIENTRY glVariantusvEXT (GLuint id, const GLushort *addr); +GLAPI void APIENTRY glVariantuivEXT (GLuint id, const GLuint *addr); +GLAPI void APIENTRY glVariantPointerEXT (GLuint id, GLenum type, GLuint stride, const void *addr); +GLAPI void APIENTRY glEnableVariantClientStateEXT (GLuint id); +GLAPI void APIENTRY glDisableVariantClientStateEXT (GLuint id); +GLAPI GLuint APIENTRY glBindLightParameterEXT (GLenum light, GLenum value); +GLAPI GLuint APIENTRY glBindMaterialParameterEXT (GLenum face, GLenum value); +GLAPI GLuint APIENTRY glBindTexGenParameterEXT (GLenum unit, GLenum coord, GLenum value); +GLAPI GLuint APIENTRY glBindTextureUnitParameterEXT (GLenum unit, GLenum value); +GLAPI GLuint APIENTRY glBindParameterEXT (GLenum value); +GLAPI GLboolean APIENTRY glIsVariantEnabledEXT (GLuint id, GLenum cap); +GLAPI void APIENTRY glGetVariantBooleanvEXT (GLuint id, GLenum value, GLboolean *data); +GLAPI void APIENTRY glGetVariantIntegervEXT (GLuint id, GLenum value, GLint *data); +GLAPI void APIENTRY glGetVariantFloatvEXT (GLuint id, GLenum value, GLfloat *data); +GLAPI void APIENTRY glGetVariantPointervEXT (GLuint id, GLenum value, void **data); +GLAPI void APIENTRY glGetInvariantBooleanvEXT (GLuint id, GLenum value, GLboolean *data); +GLAPI void APIENTRY glGetInvariantIntegervEXT (GLuint id, GLenum value, GLint *data); +GLAPI void APIENTRY glGetInvariantFloatvEXT (GLuint id, GLenum value, GLfloat *data); +GLAPI void APIENTRY glGetLocalConstantBooleanvEXT (GLuint id, GLenum value, GLboolean *data); +GLAPI void APIENTRY glGetLocalConstantIntegervEXT (GLuint id, GLenum value, GLint *data); +GLAPI void APIENTRY glGetLocalConstantFloatvEXT (GLuint id, GLenum value, GLfloat *data); +#endif +#endif /* GL_EXT_vertex_shader */ + +#ifndef GL_EXT_vertex_weighting +#define GL_EXT_vertex_weighting 1 +#define GL_MODELVIEW0_STACK_DEPTH_EXT 0x0BA3 +#define GL_MODELVIEW1_STACK_DEPTH_EXT 0x8502 +#define GL_MODELVIEW0_MATRIX_EXT 0x0BA6 +#define GL_MODELVIEW1_MATRIX_EXT 0x8506 +#define GL_VERTEX_WEIGHTING_EXT 0x8509 +#define GL_MODELVIEW0_EXT 0x1700 +#define GL_MODELVIEW1_EXT 0x850A +#define GL_CURRENT_VERTEX_WEIGHT_EXT 0x850B +#define GL_VERTEX_WEIGHT_ARRAY_EXT 0x850C +#define GL_VERTEX_WEIGHT_ARRAY_SIZE_EXT 0x850D +#define GL_VERTEX_WEIGHT_ARRAY_TYPE_EXT 0x850E +#define GL_VERTEX_WEIGHT_ARRAY_STRIDE_EXT 0x850F +#define GL_VERTEX_WEIGHT_ARRAY_POINTER_EXT 0x8510 +typedef void (APIENTRYP PFNGLVERTEXWEIGHTFEXTPROC) (GLfloat weight); +typedef void (APIENTRYP PFNGLVERTEXWEIGHTFVEXTPROC) (const GLfloat *weight); +typedef void (APIENTRYP PFNGLVERTEXWEIGHTPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, const void *pointer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexWeightfEXT (GLfloat weight); +GLAPI void APIENTRY glVertexWeightfvEXT (const GLfloat *weight); +GLAPI void APIENTRY glVertexWeightPointerEXT (GLint size, GLenum type, GLsizei stride, const void *pointer); +#endif +#endif /* GL_EXT_vertex_weighting */ + +#ifndef GL_EXT_win32_keyed_mutex +#define GL_EXT_win32_keyed_mutex 1 +typedef GLboolean (APIENTRYP PFNGLACQUIREKEYEDMUTEXWIN32EXTPROC) (GLuint memory, GLuint64 key, GLuint timeout); +typedef GLboolean (APIENTRYP PFNGLRELEASEKEYEDMUTEXWIN32EXTPROC) (GLuint memory, GLuint64 key); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLboolean APIENTRY glAcquireKeyedMutexWin32EXT (GLuint memory, GLuint64 key, GLuint timeout); +GLAPI GLboolean APIENTRY glReleaseKeyedMutexWin32EXT (GLuint memory, GLuint64 key); +#endif +#endif /* GL_EXT_win32_keyed_mutex */ + +#ifndef GL_EXT_window_rectangles +#define GL_EXT_window_rectangles 1 +#define GL_INCLUSIVE_EXT 0x8F10 +#define GL_EXCLUSIVE_EXT 0x8F11 +#define GL_WINDOW_RECTANGLE_EXT 0x8F12 +#define GL_WINDOW_RECTANGLE_MODE_EXT 0x8F13 +#define GL_MAX_WINDOW_RECTANGLES_EXT 0x8F14 +#define GL_NUM_WINDOW_RECTANGLES_EXT 0x8F15 +typedef void (APIENTRYP PFNGLWINDOWRECTANGLESEXTPROC) (GLenum mode, GLsizei count, const GLint *box); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glWindowRectanglesEXT (GLenum mode, GLsizei count, const GLint *box); +#endif +#endif /* GL_EXT_window_rectangles */ + +#ifndef GL_EXT_x11_sync_object +#define GL_EXT_x11_sync_object 1 +#define GL_SYNC_X11_FENCE_EXT 0x90E1 +typedef GLsync (APIENTRYP PFNGLIMPORTSYNCEXTPROC) (GLenum external_sync_type, GLintptr external_sync, GLbitfield flags); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLsync APIENTRY glImportSyncEXT (GLenum external_sync_type, GLintptr external_sync, GLbitfield flags); +#endif +#endif /* GL_EXT_x11_sync_object */ + +#ifndef GL_GREMEDY_frame_terminator +#define GL_GREMEDY_frame_terminator 1 +typedef void (APIENTRYP PFNGLFRAMETERMINATORGREMEDYPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFrameTerminatorGREMEDY (void); +#endif +#endif /* GL_GREMEDY_frame_terminator */ + +#ifndef GL_GREMEDY_string_marker +#define GL_GREMEDY_string_marker 1 +typedef void (APIENTRYP PFNGLSTRINGMARKERGREMEDYPROC) (GLsizei len, const void *string); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glStringMarkerGREMEDY (GLsizei len, const void *string); +#endif +#endif /* GL_GREMEDY_string_marker */ + +#ifndef GL_HP_convolution_border_modes +#define GL_HP_convolution_border_modes 1 +#define GL_IGNORE_BORDER_HP 0x8150 +#define GL_CONSTANT_BORDER_HP 0x8151 +#define GL_REPLICATE_BORDER_HP 0x8153 +#define GL_CONVOLUTION_BORDER_COLOR_HP 0x8154 +#endif /* GL_HP_convolution_border_modes */ + +#ifndef GL_HP_image_transform +#define GL_HP_image_transform 1 +#define GL_IMAGE_SCALE_X_HP 0x8155 +#define GL_IMAGE_SCALE_Y_HP 0x8156 +#define GL_IMAGE_TRANSLATE_X_HP 0x8157 +#define GL_IMAGE_TRANSLATE_Y_HP 0x8158 +#define GL_IMAGE_ROTATE_ANGLE_HP 0x8159 +#define GL_IMAGE_ROTATE_ORIGIN_X_HP 0x815A +#define GL_IMAGE_ROTATE_ORIGIN_Y_HP 0x815B +#define GL_IMAGE_MAG_FILTER_HP 0x815C +#define GL_IMAGE_MIN_FILTER_HP 0x815D +#define GL_IMAGE_CUBIC_WEIGHT_HP 0x815E +#define GL_CUBIC_HP 0x815F +#define GL_AVERAGE_HP 0x8160 +#define GL_IMAGE_TRANSFORM_2D_HP 0x8161 +#define GL_POST_IMAGE_TRANSFORM_COLOR_TABLE_HP 0x8162 +#define GL_PROXY_POST_IMAGE_TRANSFORM_COLOR_TABLE_HP 0x8163 +typedef void (APIENTRYP PFNGLIMAGETRANSFORMPARAMETERIHPPROC) (GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLIMAGETRANSFORMPARAMETERFHPPROC) (GLenum target, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLIMAGETRANSFORMPARAMETERIVHPPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLIMAGETRANSFORMPARAMETERFVHPPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETIMAGETRANSFORMPARAMETERIVHPPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETIMAGETRANSFORMPARAMETERFVHPPROC) (GLenum target, GLenum pname, GLfloat *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glImageTransformParameteriHP (GLenum target, GLenum pname, GLint param); +GLAPI void APIENTRY glImageTransformParameterfHP (GLenum target, GLenum pname, GLfloat param); +GLAPI void APIENTRY glImageTransformParameterivHP (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glImageTransformParameterfvHP (GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glGetImageTransformParameterivHP (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetImageTransformParameterfvHP (GLenum target, GLenum pname, GLfloat *params); +#endif +#endif /* GL_HP_image_transform */ + +#ifndef GL_HP_occlusion_test +#define GL_HP_occlusion_test 1 +#define GL_OCCLUSION_TEST_HP 0x8165 +#define GL_OCCLUSION_TEST_RESULT_HP 0x8166 +#endif /* GL_HP_occlusion_test */ + +#ifndef GL_HP_texture_lighting +#define GL_HP_texture_lighting 1 +#define GL_TEXTURE_LIGHTING_MODE_HP 0x8167 +#define GL_TEXTURE_POST_SPECULAR_HP 0x8168 +#define GL_TEXTURE_PRE_SPECULAR_HP 0x8169 +#endif /* GL_HP_texture_lighting */ + +#ifndef GL_IBM_cull_vertex +#define GL_IBM_cull_vertex 1 +#define GL_CULL_VERTEX_IBM 103050 +#endif /* GL_IBM_cull_vertex */ + +#ifndef GL_IBM_multimode_draw_arrays +#define GL_IBM_multimode_draw_arrays 1 +typedef void (APIENTRYP PFNGLMULTIMODEDRAWARRAYSIBMPROC) (const GLenum *mode, const GLint *first, const GLsizei *count, GLsizei primcount, GLint modestride); +typedef void (APIENTRYP PFNGLMULTIMODEDRAWELEMENTSIBMPROC) (const GLenum *mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei primcount, GLint modestride); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMultiModeDrawArraysIBM (const GLenum *mode, const GLint *first, const GLsizei *count, GLsizei primcount, GLint modestride); +GLAPI void APIENTRY glMultiModeDrawElementsIBM (const GLenum *mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei primcount, GLint modestride); +#endif +#endif /* GL_IBM_multimode_draw_arrays */ + +#ifndef GL_IBM_rasterpos_clip +#define GL_IBM_rasterpos_clip 1 +#define GL_RASTER_POSITION_UNCLIPPED_IBM 0x19262 +#endif /* GL_IBM_rasterpos_clip */ + +#ifndef GL_IBM_static_data +#define GL_IBM_static_data 1 +#define GL_ALL_STATIC_DATA_IBM 103060 +#define GL_STATIC_VERTEX_ARRAY_IBM 103061 +typedef void (APIENTRYP PFNGLFLUSHSTATICDATAIBMPROC) (GLenum target); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFlushStaticDataIBM (GLenum target); +#endif +#endif /* GL_IBM_static_data */ + +#ifndef GL_IBM_texture_mirrored_repeat +#define GL_IBM_texture_mirrored_repeat 1 +#define GL_MIRRORED_REPEAT_IBM 0x8370 +#endif /* GL_IBM_texture_mirrored_repeat */ + +#ifndef GL_IBM_vertex_array_lists +#define GL_IBM_vertex_array_lists 1 +#define GL_VERTEX_ARRAY_LIST_IBM 103070 +#define GL_NORMAL_ARRAY_LIST_IBM 103071 +#define GL_COLOR_ARRAY_LIST_IBM 103072 +#define GL_INDEX_ARRAY_LIST_IBM 103073 +#define GL_TEXTURE_COORD_ARRAY_LIST_IBM 103074 +#define GL_EDGE_FLAG_ARRAY_LIST_IBM 103075 +#define GL_FOG_COORDINATE_ARRAY_LIST_IBM 103076 +#define GL_SECONDARY_COLOR_ARRAY_LIST_IBM 103077 +#define GL_VERTEX_ARRAY_LIST_STRIDE_IBM 103080 +#define GL_NORMAL_ARRAY_LIST_STRIDE_IBM 103081 +#define GL_COLOR_ARRAY_LIST_STRIDE_IBM 103082 +#define GL_INDEX_ARRAY_LIST_STRIDE_IBM 103083 +#define GL_TEXTURE_COORD_ARRAY_LIST_STRIDE_IBM 103084 +#define GL_EDGE_FLAG_ARRAY_LIST_STRIDE_IBM 103085 +#define GL_FOG_COORDINATE_ARRAY_LIST_STRIDE_IBM 103086 +#define GL_SECONDARY_COLOR_ARRAY_LIST_STRIDE_IBM 103087 +typedef void (APIENTRYP PFNGLCOLORPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const void **pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLSECONDARYCOLORPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const void **pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLEDGEFLAGPOINTERLISTIBMPROC) (GLint stride, const GLboolean **pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLFOGCOORDPOINTERLISTIBMPROC) (GLenum type, GLint stride, const void **pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLINDEXPOINTERLISTIBMPROC) (GLenum type, GLint stride, const void **pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLNORMALPOINTERLISTIBMPROC) (GLenum type, GLint stride, const void **pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLTEXCOORDPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const void **pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLVERTEXPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const void **pointer, GLint ptrstride); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorPointerListIBM (GLint size, GLenum type, GLint stride, const void **pointer, GLint ptrstride); +GLAPI void APIENTRY glSecondaryColorPointerListIBM (GLint size, GLenum type, GLint stride, const void **pointer, GLint ptrstride); +GLAPI void APIENTRY glEdgeFlagPointerListIBM (GLint stride, const GLboolean **pointer, GLint ptrstride); +GLAPI void APIENTRY glFogCoordPointerListIBM (GLenum type, GLint stride, const void **pointer, GLint ptrstride); +GLAPI void APIENTRY glIndexPointerListIBM (GLenum type, GLint stride, const void **pointer, GLint ptrstride); +GLAPI void APIENTRY glNormalPointerListIBM (GLenum type, GLint stride, const void **pointer, GLint ptrstride); +GLAPI void APIENTRY glTexCoordPointerListIBM (GLint size, GLenum type, GLint stride, const void **pointer, GLint ptrstride); +GLAPI void APIENTRY glVertexPointerListIBM (GLint size, GLenum type, GLint stride, const void **pointer, GLint ptrstride); +#endif +#endif /* GL_IBM_vertex_array_lists */ + +#ifndef GL_INGR_blend_func_separate +#define GL_INGR_blend_func_separate 1 +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEINGRPROC) (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendFuncSeparateINGR (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +#endif +#endif /* GL_INGR_blend_func_separate */ + +#ifndef GL_INGR_color_clamp +#define GL_INGR_color_clamp 1 +#define GL_RED_MIN_CLAMP_INGR 0x8560 +#define GL_GREEN_MIN_CLAMP_INGR 0x8561 +#define GL_BLUE_MIN_CLAMP_INGR 0x8562 +#define GL_ALPHA_MIN_CLAMP_INGR 0x8563 +#define GL_RED_MAX_CLAMP_INGR 0x8564 +#define GL_GREEN_MAX_CLAMP_INGR 0x8565 +#define GL_BLUE_MAX_CLAMP_INGR 0x8566 +#define GL_ALPHA_MAX_CLAMP_INGR 0x8567 +#endif /* GL_INGR_color_clamp */ + +#ifndef GL_INGR_interlace_read +#define GL_INGR_interlace_read 1 +#define GL_INTERLACE_READ_INGR 0x8568 +#endif /* GL_INGR_interlace_read */ + +#ifndef GL_INTEL_blackhole_render +#define GL_INTEL_blackhole_render 1 +#define GL_BLACKHOLE_RENDER_INTEL 0x83FC +#endif /* GL_INTEL_blackhole_render */ + +#ifndef GL_INTEL_conservative_rasterization +#define GL_INTEL_conservative_rasterization 1 +#define GL_CONSERVATIVE_RASTERIZATION_INTEL 0x83FE +#endif /* GL_INTEL_conservative_rasterization */ + +#ifndef GL_INTEL_fragment_shader_ordering +#define GL_INTEL_fragment_shader_ordering 1 +#endif /* GL_INTEL_fragment_shader_ordering */ + +#ifndef GL_INTEL_framebuffer_CMAA +#define GL_INTEL_framebuffer_CMAA 1 +typedef void (APIENTRYP PFNGLAPPLYFRAMEBUFFERATTACHMENTCMAAINTELPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glApplyFramebufferAttachmentCMAAINTEL (void); +#endif +#endif /* GL_INTEL_framebuffer_CMAA */ + +#ifndef GL_INTEL_map_texture +#define GL_INTEL_map_texture 1 +#define GL_TEXTURE_MEMORY_LAYOUT_INTEL 0x83FF +#define GL_LAYOUT_DEFAULT_INTEL 0 +#define GL_LAYOUT_LINEAR_INTEL 1 +#define GL_LAYOUT_LINEAR_CPU_CACHED_INTEL 2 +typedef void (APIENTRYP PFNGLSYNCTEXTUREINTELPROC) (GLuint texture); +typedef void (APIENTRYP PFNGLUNMAPTEXTURE2DINTELPROC) (GLuint texture, GLint level); +typedef void *(APIENTRYP PFNGLMAPTEXTURE2DINTELPROC) (GLuint texture, GLint level, GLbitfield access, GLint *stride, GLenum *layout); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSyncTextureINTEL (GLuint texture); +GLAPI void APIENTRY glUnmapTexture2DINTEL (GLuint texture, GLint level); +GLAPI void *APIENTRY glMapTexture2DINTEL (GLuint texture, GLint level, GLbitfield access, GLint *stride, GLenum *layout); +#endif +#endif /* GL_INTEL_map_texture */ + +#ifndef GL_INTEL_parallel_arrays +#define GL_INTEL_parallel_arrays 1 +#define GL_PARALLEL_ARRAYS_INTEL 0x83F4 +#define GL_VERTEX_ARRAY_PARALLEL_POINTERS_INTEL 0x83F5 +#define GL_NORMAL_ARRAY_PARALLEL_POINTERS_INTEL 0x83F6 +#define GL_COLOR_ARRAY_PARALLEL_POINTERS_INTEL 0x83F7 +#define GL_TEXTURE_COORD_ARRAY_PARALLEL_POINTERS_INTEL 0x83F8 +typedef void (APIENTRYP PFNGLVERTEXPOINTERVINTELPROC) (GLint size, GLenum type, const void **pointer); +typedef void (APIENTRYP PFNGLNORMALPOINTERVINTELPROC) (GLenum type, const void **pointer); +typedef void (APIENTRYP PFNGLCOLORPOINTERVINTELPROC) (GLint size, GLenum type, const void **pointer); +typedef void (APIENTRYP PFNGLTEXCOORDPOINTERVINTELPROC) (GLint size, GLenum type, const void **pointer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexPointervINTEL (GLint size, GLenum type, const void **pointer); +GLAPI void APIENTRY glNormalPointervINTEL (GLenum type, const void **pointer); +GLAPI void APIENTRY glColorPointervINTEL (GLint size, GLenum type, const void **pointer); +GLAPI void APIENTRY glTexCoordPointervINTEL (GLint size, GLenum type, const void **pointer); +#endif +#endif /* GL_INTEL_parallel_arrays */ + +#ifndef GL_INTEL_performance_query +#define GL_INTEL_performance_query 1 +#define GL_PERFQUERY_SINGLE_CONTEXT_INTEL 0x00000000 +#define GL_PERFQUERY_GLOBAL_CONTEXT_INTEL 0x00000001 +#define GL_PERFQUERY_WAIT_INTEL 0x83FB +#define GL_PERFQUERY_FLUSH_INTEL 0x83FA +#define GL_PERFQUERY_DONOT_FLUSH_INTEL 0x83F9 +#define GL_PERFQUERY_COUNTER_EVENT_INTEL 0x94F0 +#define GL_PERFQUERY_COUNTER_DURATION_NORM_INTEL 0x94F1 +#define GL_PERFQUERY_COUNTER_DURATION_RAW_INTEL 0x94F2 +#define GL_PERFQUERY_COUNTER_THROUGHPUT_INTEL 0x94F3 +#define GL_PERFQUERY_COUNTER_RAW_INTEL 0x94F4 +#define GL_PERFQUERY_COUNTER_TIMESTAMP_INTEL 0x94F5 +#define GL_PERFQUERY_COUNTER_DATA_UINT32_INTEL 0x94F8 +#define GL_PERFQUERY_COUNTER_DATA_UINT64_INTEL 0x94F9 +#define GL_PERFQUERY_COUNTER_DATA_FLOAT_INTEL 0x94FA +#define GL_PERFQUERY_COUNTER_DATA_DOUBLE_INTEL 0x94FB +#define GL_PERFQUERY_COUNTER_DATA_BOOL32_INTEL 0x94FC +#define GL_PERFQUERY_QUERY_NAME_LENGTH_MAX_INTEL 0x94FD +#define GL_PERFQUERY_COUNTER_NAME_LENGTH_MAX_INTEL 0x94FE +#define GL_PERFQUERY_COUNTER_DESC_LENGTH_MAX_INTEL 0x94FF +#define GL_PERFQUERY_GPA_EXTENDED_COUNTERS_INTEL 0x9500 +typedef void (APIENTRYP PFNGLBEGINPERFQUERYINTELPROC) (GLuint queryHandle); +typedef void (APIENTRYP PFNGLCREATEPERFQUERYINTELPROC) (GLuint queryId, GLuint *queryHandle); +typedef void (APIENTRYP PFNGLDELETEPERFQUERYINTELPROC) (GLuint queryHandle); +typedef void (APIENTRYP PFNGLENDPERFQUERYINTELPROC) (GLuint queryHandle); +typedef void (APIENTRYP PFNGLGETFIRSTPERFQUERYIDINTELPROC) (GLuint *queryId); +typedef void (APIENTRYP PFNGLGETNEXTPERFQUERYIDINTELPROC) (GLuint queryId, GLuint *nextQueryId); +typedef void (APIENTRYP PFNGLGETPERFCOUNTERINFOINTELPROC) (GLuint queryId, GLuint counterId, GLuint counterNameLength, GLchar *counterName, GLuint counterDescLength, GLchar *counterDesc, GLuint *counterOffset, GLuint *counterDataSize, GLuint *counterTypeEnum, GLuint *counterDataTypeEnum, GLuint64 *rawCounterMaxValue); +typedef void (APIENTRYP PFNGLGETPERFQUERYDATAINTELPROC) (GLuint queryHandle, GLuint flags, GLsizei dataSize, void *data, GLuint *bytesWritten); +typedef void (APIENTRYP PFNGLGETPERFQUERYIDBYNAMEINTELPROC) (GLchar *queryName, GLuint *queryId); +typedef void (APIENTRYP PFNGLGETPERFQUERYINFOINTELPROC) (GLuint queryId, GLuint queryNameLength, GLchar *queryName, GLuint *dataSize, GLuint *noCounters, GLuint *noInstances, GLuint *capsMask); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBeginPerfQueryINTEL (GLuint queryHandle); +GLAPI void APIENTRY glCreatePerfQueryINTEL (GLuint queryId, GLuint *queryHandle); +GLAPI void APIENTRY glDeletePerfQueryINTEL (GLuint queryHandle); +GLAPI void APIENTRY glEndPerfQueryINTEL (GLuint queryHandle); +GLAPI void APIENTRY glGetFirstPerfQueryIdINTEL (GLuint *queryId); +GLAPI void APIENTRY glGetNextPerfQueryIdINTEL (GLuint queryId, GLuint *nextQueryId); +GLAPI void APIENTRY glGetPerfCounterInfoINTEL (GLuint queryId, GLuint counterId, GLuint counterNameLength, GLchar *counterName, GLuint counterDescLength, GLchar *counterDesc, GLuint *counterOffset, GLuint *counterDataSize, GLuint *counterTypeEnum, GLuint *counterDataTypeEnum, GLuint64 *rawCounterMaxValue); +GLAPI void APIENTRY glGetPerfQueryDataINTEL (GLuint queryHandle, GLuint flags, GLsizei dataSize, void *data, GLuint *bytesWritten); +GLAPI void APIENTRY glGetPerfQueryIdByNameINTEL (GLchar *queryName, GLuint *queryId); +GLAPI void APIENTRY glGetPerfQueryInfoINTEL (GLuint queryId, GLuint queryNameLength, GLchar *queryName, GLuint *dataSize, GLuint *noCounters, GLuint *noInstances, GLuint *capsMask); +#endif +#endif /* GL_INTEL_performance_query */ + +#ifndef GL_MESAX_texture_stack +#define GL_MESAX_texture_stack 1 +#define GL_TEXTURE_1D_STACK_MESAX 0x8759 +#define GL_TEXTURE_2D_STACK_MESAX 0x875A +#define GL_PROXY_TEXTURE_1D_STACK_MESAX 0x875B +#define GL_PROXY_TEXTURE_2D_STACK_MESAX 0x875C +#define GL_TEXTURE_1D_STACK_BINDING_MESAX 0x875D +#define GL_TEXTURE_2D_STACK_BINDING_MESAX 0x875E +#endif /* GL_MESAX_texture_stack */ + +#ifndef GL_MESA_framebuffer_flip_x +#define GL_MESA_framebuffer_flip_x 1 +#define GL_FRAMEBUFFER_FLIP_X_MESA 0x8BBC +#endif /* GL_MESA_framebuffer_flip_x */ + +#ifndef GL_MESA_framebuffer_flip_y +#define GL_MESA_framebuffer_flip_y 1 +#define GL_FRAMEBUFFER_FLIP_Y_MESA 0x8BBB +typedef void (APIENTRYP PFNGLFRAMEBUFFERPARAMETERIMESAPROC) (GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLGETFRAMEBUFFERPARAMETERIVMESAPROC) (GLenum target, GLenum pname, GLint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFramebufferParameteriMESA (GLenum target, GLenum pname, GLint param); +GLAPI void APIENTRY glGetFramebufferParameterivMESA (GLenum target, GLenum pname, GLint *params); +#endif +#endif /* GL_MESA_framebuffer_flip_y */ + +#ifndef GL_MESA_framebuffer_swap_xy +#define GL_MESA_framebuffer_swap_xy 1 +#define GL_FRAMEBUFFER_SWAP_XY_MESA 0x8BBD +#endif /* GL_MESA_framebuffer_swap_xy */ + +#ifndef GL_MESA_pack_invert +#define GL_MESA_pack_invert 1 +#define GL_PACK_INVERT_MESA 0x8758 +#endif /* GL_MESA_pack_invert */ + +#ifndef GL_MESA_program_binary_formats +#define GL_MESA_program_binary_formats 1 +#define GL_PROGRAM_BINARY_FORMAT_MESA 0x875F +#endif /* GL_MESA_program_binary_formats */ + +#ifndef GL_MESA_resize_buffers +#define GL_MESA_resize_buffers 1 +typedef void (APIENTRYP PFNGLRESIZEBUFFERSMESAPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glResizeBuffersMESA (void); +#endif +#endif /* GL_MESA_resize_buffers */ + +#ifndef GL_MESA_shader_integer_functions +#define GL_MESA_shader_integer_functions 1 +#endif /* GL_MESA_shader_integer_functions */ + +#ifndef GL_MESA_tile_raster_order +#define GL_MESA_tile_raster_order 1 +#define GL_TILE_RASTER_ORDER_FIXED_MESA 0x8BB8 +#define GL_TILE_RASTER_ORDER_INCREASING_X_MESA 0x8BB9 +#define GL_TILE_RASTER_ORDER_INCREASING_Y_MESA 0x8BBA +#endif /* GL_MESA_tile_raster_order */ + +#ifndef GL_MESA_window_pos +#define GL_MESA_window_pos 1 +typedef void (APIENTRYP PFNGLWINDOWPOS2DMESAPROC) (GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLWINDOWPOS2DVMESAPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2FMESAPROC) (GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLWINDOWPOS2FVMESAPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2IMESAPROC) (GLint x, GLint y); +typedef void (APIENTRYP PFNGLWINDOWPOS2IVMESAPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2SMESAPROC) (GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLWINDOWPOS2SVMESAPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3DMESAPROC) (GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLWINDOWPOS3DVMESAPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3FMESAPROC) (GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLWINDOWPOS3FVMESAPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3IMESAPROC) (GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLWINDOWPOS3IVMESAPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3SMESAPROC) (GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLWINDOWPOS3SVMESAPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLWINDOWPOS4DMESAPROC) (GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLWINDOWPOS4DVMESAPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS4FMESAPROC) (GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLWINDOWPOS4FVMESAPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS4IMESAPROC) (GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLWINDOWPOS4IVMESAPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS4SMESAPROC) (GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLWINDOWPOS4SVMESAPROC) (const GLshort *v); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glWindowPos2dMESA (GLdouble x, GLdouble y); +GLAPI void APIENTRY glWindowPos2dvMESA (const GLdouble *v); +GLAPI void APIENTRY glWindowPos2fMESA (GLfloat x, GLfloat y); +GLAPI void APIENTRY glWindowPos2fvMESA (const GLfloat *v); +GLAPI void APIENTRY glWindowPos2iMESA (GLint x, GLint y); +GLAPI void APIENTRY glWindowPos2ivMESA (const GLint *v); +GLAPI void APIENTRY glWindowPos2sMESA (GLshort x, GLshort y); +GLAPI void APIENTRY glWindowPos2svMESA (const GLshort *v); +GLAPI void APIENTRY glWindowPos3dMESA (GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glWindowPos3dvMESA (const GLdouble *v); +GLAPI void APIENTRY glWindowPos3fMESA (GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glWindowPos3fvMESA (const GLfloat *v); +GLAPI void APIENTRY glWindowPos3iMESA (GLint x, GLint y, GLint z); +GLAPI void APIENTRY glWindowPos3ivMESA (const GLint *v); +GLAPI void APIENTRY glWindowPos3sMESA (GLshort x, GLshort y, GLshort z); +GLAPI void APIENTRY glWindowPos3svMESA (const GLshort *v); +GLAPI void APIENTRY glWindowPos4dMESA (GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glWindowPos4dvMESA (const GLdouble *v); +GLAPI void APIENTRY glWindowPos4fMESA (GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glWindowPos4fvMESA (const GLfloat *v); +GLAPI void APIENTRY glWindowPos4iMESA (GLint x, GLint y, GLint z, GLint w); +GLAPI void APIENTRY glWindowPos4ivMESA (const GLint *v); +GLAPI void APIENTRY glWindowPos4sMESA (GLshort x, GLshort y, GLshort z, GLshort w); +GLAPI void APIENTRY glWindowPos4svMESA (const GLshort *v); +#endif +#endif /* GL_MESA_window_pos */ + +#ifndef GL_MESA_ycbcr_texture +#define GL_MESA_ycbcr_texture 1 +#define GL_UNSIGNED_SHORT_8_8_MESA 0x85BA +#define GL_UNSIGNED_SHORT_8_8_REV_MESA 0x85BB +#define GL_YCBCR_MESA 0x8757 +#endif /* GL_MESA_ycbcr_texture */ + +#ifndef GL_NVX_blend_equation_advanced_multi_draw_buffers +#define GL_NVX_blend_equation_advanced_multi_draw_buffers 1 +#endif /* GL_NVX_blend_equation_advanced_multi_draw_buffers */ + +#ifndef GL_NVX_conditional_render +#define GL_NVX_conditional_render 1 +typedef void (APIENTRYP PFNGLBEGINCONDITIONALRENDERNVXPROC) (GLuint id); +typedef void (APIENTRYP PFNGLENDCONDITIONALRENDERNVXPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBeginConditionalRenderNVX (GLuint id); +GLAPI void APIENTRY glEndConditionalRenderNVX (void); +#endif +#endif /* GL_NVX_conditional_render */ + +#ifndef GL_NVX_gpu_memory_info +#define GL_NVX_gpu_memory_info 1 +#define GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX 0x9047 +#define GL_GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX 0x9048 +#define GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX 0x9049 +#define GL_GPU_MEMORY_INFO_EVICTION_COUNT_NVX 0x904A +#define GL_GPU_MEMORY_INFO_EVICTED_MEMORY_NVX 0x904B +#endif /* GL_NVX_gpu_memory_info */ + +#ifndef GL_NVX_gpu_multicast2 +#define GL_NVX_gpu_multicast2 1 +#define GL_UPLOAD_GPU_MASK_NVX 0x954A +typedef void (APIENTRYP PFNGLUPLOADGPUMASKNVXPROC) (GLbitfield mask); +typedef void (APIENTRYP PFNGLMULTICASTVIEWPORTARRAYVNVXPROC) (GLuint gpu, GLuint first, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTICASTVIEWPORTPOSITIONWSCALENVXPROC) (GLuint gpu, GLuint index, GLfloat xcoeff, GLfloat ycoeff); +typedef void (APIENTRYP PFNGLMULTICASTSCISSORARRAYVNVXPROC) (GLuint gpu, GLuint first, GLsizei count, const GLint *v); +typedef GLuint (APIENTRYP PFNGLASYNCCOPYBUFFERSUBDATANVXPROC) (GLsizei waitSemaphoreCount, const GLuint *waitSemaphoreArray, const GLuint64 *fenceValueArray, GLuint readGpu, GLbitfield writeGpuMask, GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size, GLsizei signalSemaphoreCount, const GLuint *signalSemaphoreArray, const GLuint64 *signalValueArray); +typedef GLuint (APIENTRYP PFNGLASYNCCOPYIMAGESUBDATANVXPROC) (GLsizei waitSemaphoreCount, const GLuint *waitSemaphoreArray, const GLuint64 *waitValueArray, GLuint srcGpu, GLbitfield dstGpuMask, GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth, GLsizei signalSemaphoreCount, const GLuint *signalSemaphoreArray, const GLuint64 *signalValueArray); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glUploadGpuMaskNVX (GLbitfield mask); +GLAPI void APIENTRY glMulticastViewportArrayvNVX (GLuint gpu, GLuint first, GLsizei count, const GLfloat *v); +GLAPI void APIENTRY glMulticastViewportPositionWScaleNVX (GLuint gpu, GLuint index, GLfloat xcoeff, GLfloat ycoeff); +GLAPI void APIENTRY glMulticastScissorArrayvNVX (GLuint gpu, GLuint first, GLsizei count, const GLint *v); +GLAPI GLuint APIENTRY glAsyncCopyBufferSubDataNVX (GLsizei waitSemaphoreCount, const GLuint *waitSemaphoreArray, const GLuint64 *fenceValueArray, GLuint readGpu, GLbitfield writeGpuMask, GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size, GLsizei signalSemaphoreCount, const GLuint *signalSemaphoreArray, const GLuint64 *signalValueArray); +GLAPI GLuint APIENTRY glAsyncCopyImageSubDataNVX (GLsizei waitSemaphoreCount, const GLuint *waitSemaphoreArray, const GLuint64 *waitValueArray, GLuint srcGpu, GLbitfield dstGpuMask, GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth, GLsizei signalSemaphoreCount, const GLuint *signalSemaphoreArray, const GLuint64 *signalValueArray); +#endif +#endif /* GL_NVX_gpu_multicast2 */ + +#ifndef GL_NVX_linked_gpu_multicast +#define GL_NVX_linked_gpu_multicast 1 +#define GL_LGPU_SEPARATE_STORAGE_BIT_NVX 0x0800 +#define GL_MAX_LGPU_GPUS_NVX 0x92BA +typedef void (APIENTRYP PFNGLLGPUNAMEDBUFFERSUBDATANVXPROC) (GLbitfield gpuMask, GLuint buffer, GLintptr offset, GLsizeiptr size, const void *data); +typedef void (APIENTRYP PFNGLLGPUCOPYIMAGESUBDATANVXPROC) (GLuint sourceGpu, GLbitfield destinationGpuMask, GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srxY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei width, GLsizei height, GLsizei depth); +typedef void (APIENTRYP PFNGLLGPUINTERLOCKNVXPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glLGPUNamedBufferSubDataNVX (GLbitfield gpuMask, GLuint buffer, GLintptr offset, GLsizeiptr size, const void *data); +GLAPI void APIENTRY glLGPUCopyImageSubDataNVX (GLuint sourceGpu, GLbitfield destinationGpuMask, GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srxY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei width, GLsizei height, GLsizei depth); +GLAPI void APIENTRY glLGPUInterlockNVX (void); +#endif +#endif /* GL_NVX_linked_gpu_multicast */ + +#ifndef GL_NVX_progress_fence +#define GL_NVX_progress_fence 1 +typedef GLuint (APIENTRYP PFNGLCREATEPROGRESSFENCENVXPROC) (void); +typedef void (APIENTRYP PFNGLSIGNALSEMAPHOREUI64NVXPROC) (GLuint signalGpu, GLsizei fenceObjectCount, const GLuint *semaphoreArray, const GLuint64 *fenceValueArray); +typedef void (APIENTRYP PFNGLWAITSEMAPHOREUI64NVXPROC) (GLuint waitGpu, GLsizei fenceObjectCount, const GLuint *semaphoreArray, const GLuint64 *fenceValueArray); +typedef void (APIENTRYP PFNGLCLIENTWAITSEMAPHOREUI64NVXPROC) (GLsizei fenceObjectCount, const GLuint *semaphoreArray, const GLuint64 *fenceValueArray); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLuint APIENTRY glCreateProgressFenceNVX (void); +GLAPI void APIENTRY glSignalSemaphoreui64NVX (GLuint signalGpu, GLsizei fenceObjectCount, const GLuint *semaphoreArray, const GLuint64 *fenceValueArray); +GLAPI void APIENTRY glWaitSemaphoreui64NVX (GLuint waitGpu, GLsizei fenceObjectCount, const GLuint *semaphoreArray, const GLuint64 *fenceValueArray); +GLAPI void APIENTRY glClientWaitSemaphoreui64NVX (GLsizei fenceObjectCount, const GLuint *semaphoreArray, const GLuint64 *fenceValueArray); +#endif +#endif /* GL_NVX_progress_fence */ + +#ifndef GL_NV_alpha_to_coverage_dither_control +#define GL_NV_alpha_to_coverage_dither_control 1 +#define GL_ALPHA_TO_COVERAGE_DITHER_DEFAULT_NV 0x934D +#define GL_ALPHA_TO_COVERAGE_DITHER_ENABLE_NV 0x934E +#define GL_ALPHA_TO_COVERAGE_DITHER_DISABLE_NV 0x934F +#define GL_ALPHA_TO_COVERAGE_DITHER_MODE_NV 0x92BF +typedef void (APIENTRYP PFNGLALPHATOCOVERAGEDITHERCONTROLNVPROC) (GLenum mode); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glAlphaToCoverageDitherControlNV (GLenum mode); +#endif +#endif /* GL_NV_alpha_to_coverage_dither_control */ + +#ifndef GL_NV_bindless_multi_draw_indirect +#define GL_NV_bindless_multi_draw_indirect 1 +typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSINDIRECTBINDLESSNVPROC) (GLenum mode, const void *indirect, GLsizei drawCount, GLsizei stride, GLint vertexBufferCount); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSINDIRECTBINDLESSNVPROC) (GLenum mode, GLenum type, const void *indirect, GLsizei drawCount, GLsizei stride, GLint vertexBufferCount); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMultiDrawArraysIndirectBindlessNV (GLenum mode, const void *indirect, GLsizei drawCount, GLsizei stride, GLint vertexBufferCount); +GLAPI void APIENTRY glMultiDrawElementsIndirectBindlessNV (GLenum mode, GLenum type, const void *indirect, GLsizei drawCount, GLsizei stride, GLint vertexBufferCount); +#endif +#endif /* GL_NV_bindless_multi_draw_indirect */ + +#ifndef GL_NV_bindless_multi_draw_indirect_count +#define GL_NV_bindless_multi_draw_indirect_count 1 +typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSINDIRECTBINDLESSCOUNTNVPROC) (GLenum mode, const void *indirect, GLsizei drawCount, GLsizei maxDrawCount, GLsizei stride, GLint vertexBufferCount); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSINDIRECTBINDLESSCOUNTNVPROC) (GLenum mode, GLenum type, const void *indirect, GLsizei drawCount, GLsizei maxDrawCount, GLsizei stride, GLint vertexBufferCount); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMultiDrawArraysIndirectBindlessCountNV (GLenum mode, const void *indirect, GLsizei drawCount, GLsizei maxDrawCount, GLsizei stride, GLint vertexBufferCount); +GLAPI void APIENTRY glMultiDrawElementsIndirectBindlessCountNV (GLenum mode, GLenum type, const void *indirect, GLsizei drawCount, GLsizei maxDrawCount, GLsizei stride, GLint vertexBufferCount); +#endif +#endif /* GL_NV_bindless_multi_draw_indirect_count */ + +#ifndef GL_NV_bindless_texture +#define GL_NV_bindless_texture 1 +typedef GLuint64 (APIENTRYP PFNGLGETTEXTUREHANDLENVPROC) (GLuint texture); +typedef GLuint64 (APIENTRYP PFNGLGETTEXTURESAMPLERHANDLENVPROC) (GLuint texture, GLuint sampler); +typedef void (APIENTRYP PFNGLMAKETEXTUREHANDLERESIDENTNVPROC) (GLuint64 handle); +typedef void (APIENTRYP PFNGLMAKETEXTUREHANDLENONRESIDENTNVPROC) (GLuint64 handle); +typedef GLuint64 (APIENTRYP PFNGLGETIMAGEHANDLENVPROC) (GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum format); +typedef void (APIENTRYP PFNGLMAKEIMAGEHANDLERESIDENTNVPROC) (GLuint64 handle, GLenum access); +typedef void (APIENTRYP PFNGLMAKEIMAGEHANDLENONRESIDENTNVPROC) (GLuint64 handle); +typedef void (APIENTRYP PFNGLUNIFORMHANDLEUI64NVPROC) (GLint location, GLuint64 value); +typedef void (APIENTRYP PFNGLUNIFORMHANDLEUI64VNVPROC) (GLint location, GLsizei count, const GLuint64 *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMHANDLEUI64NVPROC) (GLuint program, GLint location, GLuint64 value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMHANDLEUI64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLuint64 *values); +typedef GLboolean (APIENTRYP PFNGLISTEXTUREHANDLERESIDENTNVPROC) (GLuint64 handle); +typedef GLboolean (APIENTRYP PFNGLISIMAGEHANDLERESIDENTNVPROC) (GLuint64 handle); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLuint64 APIENTRY glGetTextureHandleNV (GLuint texture); +GLAPI GLuint64 APIENTRY glGetTextureSamplerHandleNV (GLuint texture, GLuint sampler); +GLAPI void APIENTRY glMakeTextureHandleResidentNV (GLuint64 handle); +GLAPI void APIENTRY glMakeTextureHandleNonResidentNV (GLuint64 handle); +GLAPI GLuint64 APIENTRY glGetImageHandleNV (GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum format); +GLAPI void APIENTRY glMakeImageHandleResidentNV (GLuint64 handle, GLenum access); +GLAPI void APIENTRY glMakeImageHandleNonResidentNV (GLuint64 handle); +GLAPI void APIENTRY glUniformHandleui64NV (GLint location, GLuint64 value); +GLAPI void APIENTRY glUniformHandleui64vNV (GLint location, GLsizei count, const GLuint64 *value); +GLAPI void APIENTRY glProgramUniformHandleui64NV (GLuint program, GLint location, GLuint64 value); +GLAPI void APIENTRY glProgramUniformHandleui64vNV (GLuint program, GLint location, GLsizei count, const GLuint64 *values); +GLAPI GLboolean APIENTRY glIsTextureHandleResidentNV (GLuint64 handle); +GLAPI GLboolean APIENTRY glIsImageHandleResidentNV (GLuint64 handle); +#endif +#endif /* GL_NV_bindless_texture */ + +#ifndef GL_NV_blend_equation_advanced +#define GL_NV_blend_equation_advanced 1 +#define GL_BLEND_OVERLAP_NV 0x9281 +#define GL_BLEND_PREMULTIPLIED_SRC_NV 0x9280 +#define GL_BLUE_NV 0x1905 +#define GL_COLORBURN_NV 0x929A +#define GL_COLORDODGE_NV 0x9299 +#define GL_CONJOINT_NV 0x9284 +#define GL_CONTRAST_NV 0x92A1 +#define GL_DARKEN_NV 0x9297 +#define GL_DIFFERENCE_NV 0x929E +#define GL_DISJOINT_NV 0x9283 +#define GL_DST_ATOP_NV 0x928F +#define GL_DST_IN_NV 0x928B +#define GL_DST_NV 0x9287 +#define GL_DST_OUT_NV 0x928D +#define GL_DST_OVER_NV 0x9289 +#define GL_EXCLUSION_NV 0x92A0 +#define GL_GREEN_NV 0x1904 +#define GL_HARDLIGHT_NV 0x929B +#define GL_HARDMIX_NV 0x92A9 +#define GL_HSL_COLOR_NV 0x92AF +#define GL_HSL_HUE_NV 0x92AD +#define GL_HSL_LUMINOSITY_NV 0x92B0 +#define GL_HSL_SATURATION_NV 0x92AE +#define GL_INVERT_OVG_NV 0x92B4 +#define GL_INVERT_RGB_NV 0x92A3 +#define GL_LIGHTEN_NV 0x9298 +#define GL_LINEARBURN_NV 0x92A5 +#define GL_LINEARDODGE_NV 0x92A4 +#define GL_LINEARLIGHT_NV 0x92A7 +#define GL_MINUS_CLAMPED_NV 0x92B3 +#define GL_MINUS_NV 0x929F +#define GL_MULTIPLY_NV 0x9294 +#define GL_OVERLAY_NV 0x9296 +#define GL_PINLIGHT_NV 0x92A8 +#define GL_PLUS_CLAMPED_ALPHA_NV 0x92B2 +#define GL_PLUS_CLAMPED_NV 0x92B1 +#define GL_PLUS_DARKER_NV 0x9292 +#define GL_PLUS_NV 0x9291 +#define GL_RED_NV 0x1903 +#define GL_SCREEN_NV 0x9295 +#define GL_SOFTLIGHT_NV 0x929C +#define GL_SRC_ATOP_NV 0x928E +#define GL_SRC_IN_NV 0x928A +#define GL_SRC_NV 0x9286 +#define GL_SRC_OUT_NV 0x928C +#define GL_SRC_OVER_NV 0x9288 +#define GL_UNCORRELATED_NV 0x9282 +#define GL_VIVIDLIGHT_NV 0x92A6 +#define GL_XOR_NV 0x1506 +typedef void (APIENTRYP PFNGLBLENDPARAMETERINVPROC) (GLenum pname, GLint value); +typedef void (APIENTRYP PFNGLBLENDBARRIERNVPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendParameteriNV (GLenum pname, GLint value); +GLAPI void APIENTRY glBlendBarrierNV (void); +#endif +#endif /* GL_NV_blend_equation_advanced */ + +#ifndef GL_NV_blend_equation_advanced_coherent +#define GL_NV_blend_equation_advanced_coherent 1 +#define GL_BLEND_ADVANCED_COHERENT_NV 0x9285 +#endif /* GL_NV_blend_equation_advanced_coherent */ + +#ifndef GL_NV_blend_minmax_factor +#define GL_NV_blend_minmax_factor 1 +#endif /* GL_NV_blend_minmax_factor */ + +#ifndef GL_NV_blend_square +#define GL_NV_blend_square 1 +#endif /* GL_NV_blend_square */ + +#ifndef GL_NV_clip_space_w_scaling +#define GL_NV_clip_space_w_scaling 1 +#define GL_VIEWPORT_POSITION_W_SCALE_NV 0x937C +#define GL_VIEWPORT_POSITION_W_SCALE_X_COEFF_NV 0x937D +#define GL_VIEWPORT_POSITION_W_SCALE_Y_COEFF_NV 0x937E +typedef void (APIENTRYP PFNGLVIEWPORTPOSITIONWSCALENVPROC) (GLuint index, GLfloat xcoeff, GLfloat ycoeff); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glViewportPositionWScaleNV (GLuint index, GLfloat xcoeff, GLfloat ycoeff); +#endif +#endif /* GL_NV_clip_space_w_scaling */ + +#ifndef GL_NV_command_list +#define GL_NV_command_list 1 +#define GL_TERMINATE_SEQUENCE_COMMAND_NV 0x0000 +#define GL_NOP_COMMAND_NV 0x0001 +#define GL_DRAW_ELEMENTS_COMMAND_NV 0x0002 +#define GL_DRAW_ARRAYS_COMMAND_NV 0x0003 +#define GL_DRAW_ELEMENTS_STRIP_COMMAND_NV 0x0004 +#define GL_DRAW_ARRAYS_STRIP_COMMAND_NV 0x0005 +#define GL_DRAW_ELEMENTS_INSTANCED_COMMAND_NV 0x0006 +#define GL_DRAW_ARRAYS_INSTANCED_COMMAND_NV 0x0007 +#define GL_ELEMENT_ADDRESS_COMMAND_NV 0x0008 +#define GL_ATTRIBUTE_ADDRESS_COMMAND_NV 0x0009 +#define GL_UNIFORM_ADDRESS_COMMAND_NV 0x000A +#define GL_BLEND_COLOR_COMMAND_NV 0x000B +#define GL_STENCIL_REF_COMMAND_NV 0x000C +#define GL_LINE_WIDTH_COMMAND_NV 0x000D +#define GL_POLYGON_OFFSET_COMMAND_NV 0x000E +#define GL_ALPHA_REF_COMMAND_NV 0x000F +#define GL_VIEWPORT_COMMAND_NV 0x0010 +#define GL_SCISSOR_COMMAND_NV 0x0011 +#define GL_FRONT_FACE_COMMAND_NV 0x0012 +typedef void (APIENTRYP PFNGLCREATESTATESNVPROC) (GLsizei n, GLuint *states); +typedef void (APIENTRYP PFNGLDELETESTATESNVPROC) (GLsizei n, const GLuint *states); +typedef GLboolean (APIENTRYP PFNGLISSTATENVPROC) (GLuint state); +typedef void (APIENTRYP PFNGLSTATECAPTURENVPROC) (GLuint state, GLenum mode); +typedef GLuint (APIENTRYP PFNGLGETCOMMANDHEADERNVPROC) (GLenum tokenID, GLuint size); +typedef GLushort (APIENTRYP PFNGLGETSTAGEINDEXNVPROC) (GLenum shadertype); +typedef void (APIENTRYP PFNGLDRAWCOMMANDSNVPROC) (GLenum primitiveMode, GLuint buffer, const GLintptr *indirects, const GLsizei *sizes, GLuint count); +typedef void (APIENTRYP PFNGLDRAWCOMMANDSADDRESSNVPROC) (GLenum primitiveMode, const GLuint64 *indirects, const GLsizei *sizes, GLuint count); +typedef void (APIENTRYP PFNGLDRAWCOMMANDSSTATESNVPROC) (GLuint buffer, const GLintptr *indirects, const GLsizei *sizes, const GLuint *states, const GLuint *fbos, GLuint count); +typedef void (APIENTRYP PFNGLDRAWCOMMANDSSTATESADDRESSNVPROC) (const GLuint64 *indirects, const GLsizei *sizes, const GLuint *states, const GLuint *fbos, GLuint count); +typedef void (APIENTRYP PFNGLCREATECOMMANDLISTSNVPROC) (GLsizei n, GLuint *lists); +typedef void (APIENTRYP PFNGLDELETECOMMANDLISTSNVPROC) (GLsizei n, const GLuint *lists); +typedef GLboolean (APIENTRYP PFNGLISCOMMANDLISTNVPROC) (GLuint list); +typedef void (APIENTRYP PFNGLLISTDRAWCOMMANDSSTATESCLIENTNVPROC) (GLuint list, GLuint segment, const void **indirects, const GLsizei *sizes, const GLuint *states, const GLuint *fbos, GLuint count); +typedef void (APIENTRYP PFNGLCOMMANDLISTSEGMENTSNVPROC) (GLuint list, GLuint segments); +typedef void (APIENTRYP PFNGLCOMPILECOMMANDLISTNVPROC) (GLuint list); +typedef void (APIENTRYP PFNGLCALLCOMMANDLISTNVPROC) (GLuint list); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCreateStatesNV (GLsizei n, GLuint *states); +GLAPI void APIENTRY glDeleteStatesNV (GLsizei n, const GLuint *states); +GLAPI GLboolean APIENTRY glIsStateNV (GLuint state); +GLAPI void APIENTRY glStateCaptureNV (GLuint state, GLenum mode); +GLAPI GLuint APIENTRY glGetCommandHeaderNV (GLenum tokenID, GLuint size); +GLAPI GLushort APIENTRY glGetStageIndexNV (GLenum shadertype); +GLAPI void APIENTRY glDrawCommandsNV (GLenum primitiveMode, GLuint buffer, const GLintptr *indirects, const GLsizei *sizes, GLuint count); +GLAPI void APIENTRY glDrawCommandsAddressNV (GLenum primitiveMode, const GLuint64 *indirects, const GLsizei *sizes, GLuint count); +GLAPI void APIENTRY glDrawCommandsStatesNV (GLuint buffer, const GLintptr *indirects, const GLsizei *sizes, const GLuint *states, const GLuint *fbos, GLuint count); +GLAPI void APIENTRY glDrawCommandsStatesAddressNV (const GLuint64 *indirects, const GLsizei *sizes, const GLuint *states, const GLuint *fbos, GLuint count); +GLAPI void APIENTRY glCreateCommandListsNV (GLsizei n, GLuint *lists); +GLAPI void APIENTRY glDeleteCommandListsNV (GLsizei n, const GLuint *lists); +GLAPI GLboolean APIENTRY glIsCommandListNV (GLuint list); +GLAPI void APIENTRY glListDrawCommandsStatesClientNV (GLuint list, GLuint segment, const void **indirects, const GLsizei *sizes, const GLuint *states, const GLuint *fbos, GLuint count); +GLAPI void APIENTRY glCommandListSegmentsNV (GLuint list, GLuint segments); +GLAPI void APIENTRY glCompileCommandListNV (GLuint list); +GLAPI void APIENTRY glCallCommandListNV (GLuint list); +#endif +#endif /* GL_NV_command_list */ + +#ifndef GL_NV_compute_program5 +#define GL_NV_compute_program5 1 +#define GL_COMPUTE_PROGRAM_NV 0x90FB +#define GL_COMPUTE_PROGRAM_PARAMETER_BUFFER_NV 0x90FC +#endif /* GL_NV_compute_program5 */ + +#ifndef GL_NV_compute_shader_derivatives +#define GL_NV_compute_shader_derivatives 1 +#endif /* GL_NV_compute_shader_derivatives */ + +#ifndef GL_NV_conditional_render +#define GL_NV_conditional_render 1 +#define GL_QUERY_WAIT_NV 0x8E13 +#define GL_QUERY_NO_WAIT_NV 0x8E14 +#define GL_QUERY_BY_REGION_WAIT_NV 0x8E15 +#define GL_QUERY_BY_REGION_NO_WAIT_NV 0x8E16 +typedef void (APIENTRYP PFNGLBEGINCONDITIONALRENDERNVPROC) (GLuint id, GLenum mode); +typedef void (APIENTRYP PFNGLENDCONDITIONALRENDERNVPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBeginConditionalRenderNV (GLuint id, GLenum mode); +GLAPI void APIENTRY glEndConditionalRenderNV (void); +#endif +#endif /* GL_NV_conditional_render */ + +#ifndef GL_NV_conservative_raster +#define GL_NV_conservative_raster 1 +#define GL_CONSERVATIVE_RASTERIZATION_NV 0x9346 +#define GL_SUBPIXEL_PRECISION_BIAS_X_BITS_NV 0x9347 +#define GL_SUBPIXEL_PRECISION_BIAS_Y_BITS_NV 0x9348 +#define GL_MAX_SUBPIXEL_PRECISION_BIAS_BITS_NV 0x9349 +typedef void (APIENTRYP PFNGLSUBPIXELPRECISIONBIASNVPROC) (GLuint xbits, GLuint ybits); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSubpixelPrecisionBiasNV (GLuint xbits, GLuint ybits); +#endif +#endif /* GL_NV_conservative_raster */ + +#ifndef GL_NV_conservative_raster_dilate +#define GL_NV_conservative_raster_dilate 1 +#define GL_CONSERVATIVE_RASTER_DILATE_NV 0x9379 +#define GL_CONSERVATIVE_RASTER_DILATE_RANGE_NV 0x937A +#define GL_CONSERVATIVE_RASTER_DILATE_GRANULARITY_NV 0x937B +typedef void (APIENTRYP PFNGLCONSERVATIVERASTERPARAMETERFNVPROC) (GLenum pname, GLfloat value); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glConservativeRasterParameterfNV (GLenum pname, GLfloat value); +#endif +#endif /* GL_NV_conservative_raster_dilate */ + +#ifndef GL_NV_conservative_raster_pre_snap +#define GL_NV_conservative_raster_pre_snap 1 +#define GL_CONSERVATIVE_RASTER_MODE_PRE_SNAP_NV 0x9550 +#endif /* GL_NV_conservative_raster_pre_snap */ + +#ifndef GL_NV_conservative_raster_pre_snap_triangles +#define GL_NV_conservative_raster_pre_snap_triangles 1 +#define GL_CONSERVATIVE_RASTER_MODE_NV 0x954D +#define GL_CONSERVATIVE_RASTER_MODE_POST_SNAP_NV 0x954E +#define GL_CONSERVATIVE_RASTER_MODE_PRE_SNAP_TRIANGLES_NV 0x954F +typedef void (APIENTRYP PFNGLCONSERVATIVERASTERPARAMETERINVPROC) (GLenum pname, GLint param); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glConservativeRasterParameteriNV (GLenum pname, GLint param); +#endif +#endif /* GL_NV_conservative_raster_pre_snap_triangles */ + +#ifndef GL_NV_conservative_raster_underestimation +#define GL_NV_conservative_raster_underestimation 1 +#endif /* GL_NV_conservative_raster_underestimation */ + +#ifndef GL_NV_copy_depth_to_color +#define GL_NV_copy_depth_to_color 1 +#define GL_DEPTH_STENCIL_TO_RGBA_NV 0x886E +#define GL_DEPTH_STENCIL_TO_BGRA_NV 0x886F +#endif /* GL_NV_copy_depth_to_color */ + +#ifndef GL_NV_copy_image +#define GL_NV_copy_image 1 +typedef void (APIENTRYP PFNGLCOPYIMAGESUBDATANVPROC) (GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei width, GLsizei height, GLsizei depth); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCopyImageSubDataNV (GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei width, GLsizei height, GLsizei depth); +#endif +#endif /* GL_NV_copy_image */ + +#ifndef GL_NV_deep_texture3D +#define GL_NV_deep_texture3D 1 +#define GL_MAX_DEEP_3D_TEXTURE_WIDTH_HEIGHT_NV 0x90D0 +#define GL_MAX_DEEP_3D_TEXTURE_DEPTH_NV 0x90D1 +#endif /* GL_NV_deep_texture3D */ + +#ifndef GL_NV_depth_buffer_float +#define GL_NV_depth_buffer_float 1 +#define GL_DEPTH_COMPONENT32F_NV 0x8DAB +#define GL_DEPTH32F_STENCIL8_NV 0x8DAC +#define GL_FLOAT_32_UNSIGNED_INT_24_8_REV_NV 0x8DAD +#define GL_DEPTH_BUFFER_FLOAT_MODE_NV 0x8DAF +typedef void (APIENTRYP PFNGLDEPTHRANGEDNVPROC) (GLdouble zNear, GLdouble zFar); +typedef void (APIENTRYP PFNGLCLEARDEPTHDNVPROC) (GLdouble depth); +typedef void (APIENTRYP PFNGLDEPTHBOUNDSDNVPROC) (GLdouble zmin, GLdouble zmax); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDepthRangedNV (GLdouble zNear, GLdouble zFar); +GLAPI void APIENTRY glClearDepthdNV (GLdouble depth); +GLAPI void APIENTRY glDepthBoundsdNV (GLdouble zmin, GLdouble zmax); +#endif +#endif /* GL_NV_depth_buffer_float */ + +#ifndef GL_NV_depth_clamp +#define GL_NV_depth_clamp 1 +#define GL_DEPTH_CLAMP_NV 0x864F +#endif /* GL_NV_depth_clamp */ + +#ifndef GL_NV_draw_texture +#define GL_NV_draw_texture 1 +typedef void (APIENTRYP PFNGLDRAWTEXTURENVPROC) (GLuint texture, GLuint sampler, GLfloat x0, GLfloat y0, GLfloat x1, GLfloat y1, GLfloat z, GLfloat s0, GLfloat t0, GLfloat s1, GLfloat t1); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawTextureNV (GLuint texture, GLuint sampler, GLfloat x0, GLfloat y0, GLfloat x1, GLfloat y1, GLfloat z, GLfloat s0, GLfloat t0, GLfloat s1, GLfloat t1); +#endif +#endif /* GL_NV_draw_texture */ + +#ifndef GL_NV_draw_vulkan_image +#define GL_NV_draw_vulkan_image 1 +typedef void (APIENTRY *GLVULKANPROCNV)(void); +typedef void (APIENTRYP PFNGLDRAWVKIMAGENVPROC) (GLuint64 vkImage, GLuint sampler, GLfloat x0, GLfloat y0, GLfloat x1, GLfloat y1, GLfloat z, GLfloat s0, GLfloat t0, GLfloat s1, GLfloat t1); +typedef GLVULKANPROCNV (APIENTRYP PFNGLGETVKPROCADDRNVPROC) (const GLchar *name); +typedef void (APIENTRYP PFNGLWAITVKSEMAPHORENVPROC) (GLuint64 vkSemaphore); +typedef void (APIENTRYP PFNGLSIGNALVKSEMAPHORENVPROC) (GLuint64 vkSemaphore); +typedef void (APIENTRYP PFNGLSIGNALVKFENCENVPROC) (GLuint64 vkFence); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawVkImageNV (GLuint64 vkImage, GLuint sampler, GLfloat x0, GLfloat y0, GLfloat x1, GLfloat y1, GLfloat z, GLfloat s0, GLfloat t0, GLfloat s1, GLfloat t1); +GLAPI GLVULKANPROCNV APIENTRY glGetVkProcAddrNV (const GLchar *name); +GLAPI void APIENTRY glWaitVkSemaphoreNV (GLuint64 vkSemaphore); +GLAPI void APIENTRY glSignalVkSemaphoreNV (GLuint64 vkSemaphore); +GLAPI void APIENTRY glSignalVkFenceNV (GLuint64 vkFence); +#endif +#endif /* GL_NV_draw_vulkan_image */ + +#ifndef GL_NV_evaluators +#define GL_NV_evaluators 1 +#define GL_EVAL_2D_NV 0x86C0 +#define GL_EVAL_TRIANGULAR_2D_NV 0x86C1 +#define GL_MAP_TESSELLATION_NV 0x86C2 +#define GL_MAP_ATTRIB_U_ORDER_NV 0x86C3 +#define GL_MAP_ATTRIB_V_ORDER_NV 0x86C4 +#define GL_EVAL_FRACTIONAL_TESSELLATION_NV 0x86C5 +#define GL_EVAL_VERTEX_ATTRIB0_NV 0x86C6 +#define GL_EVAL_VERTEX_ATTRIB1_NV 0x86C7 +#define GL_EVAL_VERTEX_ATTRIB2_NV 0x86C8 +#define GL_EVAL_VERTEX_ATTRIB3_NV 0x86C9 +#define GL_EVAL_VERTEX_ATTRIB4_NV 0x86CA +#define GL_EVAL_VERTEX_ATTRIB5_NV 0x86CB +#define GL_EVAL_VERTEX_ATTRIB6_NV 0x86CC +#define GL_EVAL_VERTEX_ATTRIB7_NV 0x86CD +#define GL_EVAL_VERTEX_ATTRIB8_NV 0x86CE +#define GL_EVAL_VERTEX_ATTRIB9_NV 0x86CF +#define GL_EVAL_VERTEX_ATTRIB10_NV 0x86D0 +#define GL_EVAL_VERTEX_ATTRIB11_NV 0x86D1 +#define GL_EVAL_VERTEX_ATTRIB12_NV 0x86D2 +#define GL_EVAL_VERTEX_ATTRIB13_NV 0x86D3 +#define GL_EVAL_VERTEX_ATTRIB14_NV 0x86D4 +#define GL_EVAL_VERTEX_ATTRIB15_NV 0x86D5 +#define GL_MAX_MAP_TESSELLATION_NV 0x86D6 +#define GL_MAX_RATIONAL_EVAL_ORDER_NV 0x86D7 +typedef void (APIENTRYP PFNGLMAPCONTROLPOINTSNVPROC) (GLenum target, GLuint index, GLenum type, GLsizei ustride, GLsizei vstride, GLint uorder, GLint vorder, GLboolean packed, const void *points); +typedef void (APIENTRYP PFNGLMAPPARAMETERIVNVPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLMAPPARAMETERFVNVPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETMAPCONTROLPOINTSNVPROC) (GLenum target, GLuint index, GLenum type, GLsizei ustride, GLsizei vstride, GLboolean packed, void *points); +typedef void (APIENTRYP PFNGLGETMAPPARAMETERIVNVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMAPPARAMETERFVNVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMAPATTRIBPARAMETERIVNVPROC) (GLenum target, GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMAPATTRIBPARAMETERFVNVPROC) (GLenum target, GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLEVALMAPSNVPROC) (GLenum target, GLenum mode); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMapControlPointsNV (GLenum target, GLuint index, GLenum type, GLsizei ustride, GLsizei vstride, GLint uorder, GLint vorder, GLboolean packed, const void *points); +GLAPI void APIENTRY glMapParameterivNV (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glMapParameterfvNV (GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glGetMapControlPointsNV (GLenum target, GLuint index, GLenum type, GLsizei ustride, GLsizei vstride, GLboolean packed, void *points); +GLAPI void APIENTRY glGetMapParameterivNV (GLenum target, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetMapParameterfvNV (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetMapAttribParameterivNV (GLenum target, GLuint index, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetMapAttribParameterfvNV (GLenum target, GLuint index, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glEvalMapsNV (GLenum target, GLenum mode); +#endif +#endif /* GL_NV_evaluators */ + +#ifndef GL_NV_explicit_multisample +#define GL_NV_explicit_multisample 1 +#define GL_SAMPLE_POSITION_NV 0x8E50 +#define GL_SAMPLE_MASK_NV 0x8E51 +#define GL_SAMPLE_MASK_VALUE_NV 0x8E52 +#define GL_TEXTURE_BINDING_RENDERBUFFER_NV 0x8E53 +#define GL_TEXTURE_RENDERBUFFER_DATA_STORE_BINDING_NV 0x8E54 +#define GL_TEXTURE_RENDERBUFFER_NV 0x8E55 +#define GL_SAMPLER_RENDERBUFFER_NV 0x8E56 +#define GL_INT_SAMPLER_RENDERBUFFER_NV 0x8E57 +#define GL_UNSIGNED_INT_SAMPLER_RENDERBUFFER_NV 0x8E58 +#define GL_MAX_SAMPLE_MASK_WORDS_NV 0x8E59 +typedef void (APIENTRYP PFNGLGETMULTISAMPLEFVNVPROC) (GLenum pname, GLuint index, GLfloat *val); +typedef void (APIENTRYP PFNGLSAMPLEMASKINDEXEDNVPROC) (GLuint index, GLbitfield mask); +typedef void (APIENTRYP PFNGLTEXRENDERBUFFERNVPROC) (GLenum target, GLuint renderbuffer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetMultisamplefvNV (GLenum pname, GLuint index, GLfloat *val); +GLAPI void APIENTRY glSampleMaskIndexedNV (GLuint index, GLbitfield mask); +GLAPI void APIENTRY glTexRenderbufferNV (GLenum target, GLuint renderbuffer); +#endif +#endif /* GL_NV_explicit_multisample */ + +#ifndef GL_NV_fence +#define GL_NV_fence 1 +#define GL_ALL_COMPLETED_NV 0x84F2 +#define GL_FENCE_STATUS_NV 0x84F3 +#define GL_FENCE_CONDITION_NV 0x84F4 +typedef void (APIENTRYP PFNGLDELETEFENCESNVPROC) (GLsizei n, const GLuint *fences); +typedef void (APIENTRYP PFNGLGENFENCESNVPROC) (GLsizei n, GLuint *fences); +typedef GLboolean (APIENTRYP PFNGLISFENCENVPROC) (GLuint fence); +typedef GLboolean (APIENTRYP PFNGLTESTFENCENVPROC) (GLuint fence); +typedef void (APIENTRYP PFNGLGETFENCEIVNVPROC) (GLuint fence, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLFINISHFENCENVPROC) (GLuint fence); +typedef void (APIENTRYP PFNGLSETFENCENVPROC) (GLuint fence, GLenum condition); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDeleteFencesNV (GLsizei n, const GLuint *fences); +GLAPI void APIENTRY glGenFencesNV (GLsizei n, GLuint *fences); +GLAPI GLboolean APIENTRY glIsFenceNV (GLuint fence); +GLAPI GLboolean APIENTRY glTestFenceNV (GLuint fence); +GLAPI void APIENTRY glGetFenceivNV (GLuint fence, GLenum pname, GLint *params); +GLAPI void APIENTRY glFinishFenceNV (GLuint fence); +GLAPI void APIENTRY glSetFenceNV (GLuint fence, GLenum condition); +#endif +#endif /* GL_NV_fence */ + +#ifndef GL_NV_fill_rectangle +#define GL_NV_fill_rectangle 1 +#define GL_FILL_RECTANGLE_NV 0x933C +#endif /* GL_NV_fill_rectangle */ + +#ifndef GL_NV_float_buffer +#define GL_NV_float_buffer 1 +#define GL_FLOAT_R_NV 0x8880 +#define GL_FLOAT_RG_NV 0x8881 +#define GL_FLOAT_RGB_NV 0x8882 +#define GL_FLOAT_RGBA_NV 0x8883 +#define GL_FLOAT_R16_NV 0x8884 +#define GL_FLOAT_R32_NV 0x8885 +#define GL_FLOAT_RG16_NV 0x8886 +#define GL_FLOAT_RG32_NV 0x8887 +#define GL_FLOAT_RGB16_NV 0x8888 +#define GL_FLOAT_RGB32_NV 0x8889 +#define GL_FLOAT_RGBA16_NV 0x888A +#define GL_FLOAT_RGBA32_NV 0x888B +#define GL_TEXTURE_FLOAT_COMPONENTS_NV 0x888C +#define GL_FLOAT_CLEAR_COLOR_VALUE_NV 0x888D +#define GL_FLOAT_RGBA_MODE_NV 0x888E +#endif /* GL_NV_float_buffer */ + +#ifndef GL_NV_fog_distance +#define GL_NV_fog_distance 1 +#define GL_FOG_DISTANCE_MODE_NV 0x855A +#define GL_EYE_RADIAL_NV 0x855B +#define GL_EYE_PLANE_ABSOLUTE_NV 0x855C +#endif /* GL_NV_fog_distance */ + +#ifndef GL_NV_fragment_coverage_to_color +#define GL_NV_fragment_coverage_to_color 1 +#define GL_FRAGMENT_COVERAGE_TO_COLOR_NV 0x92DD +#define GL_FRAGMENT_COVERAGE_COLOR_NV 0x92DE +typedef void (APIENTRYP PFNGLFRAGMENTCOVERAGECOLORNVPROC) (GLuint color); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFragmentCoverageColorNV (GLuint color); +#endif +#endif /* GL_NV_fragment_coverage_to_color */ + +#ifndef GL_NV_fragment_program +#define GL_NV_fragment_program 1 +#define GL_MAX_FRAGMENT_PROGRAM_LOCAL_PARAMETERS_NV 0x8868 +#define GL_FRAGMENT_PROGRAM_NV 0x8870 +#define GL_MAX_TEXTURE_COORDS_NV 0x8871 +#define GL_MAX_TEXTURE_IMAGE_UNITS_NV 0x8872 +#define GL_FRAGMENT_PROGRAM_BINDING_NV 0x8873 +#define GL_PROGRAM_ERROR_STRING_NV 0x8874 +typedef void (APIENTRYP PFNGLPROGRAMNAMEDPARAMETER4FNVPROC) (GLuint id, GLsizei len, const GLubyte *name, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLPROGRAMNAMEDPARAMETER4FVNVPROC) (GLuint id, GLsizei len, const GLubyte *name, const GLfloat *v); +typedef void (APIENTRYP PFNGLPROGRAMNAMEDPARAMETER4DNVPROC) (GLuint id, GLsizei len, const GLubyte *name, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLPROGRAMNAMEDPARAMETER4DVNVPROC) (GLuint id, GLsizei len, const GLubyte *name, const GLdouble *v); +typedef void (APIENTRYP PFNGLGETPROGRAMNAMEDPARAMETERFVNVPROC) (GLuint id, GLsizei len, const GLubyte *name, GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMNAMEDPARAMETERDVNVPROC) (GLuint id, GLsizei len, const GLubyte *name, GLdouble *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramNamedParameter4fNV (GLuint id, GLsizei len, const GLubyte *name, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glProgramNamedParameter4fvNV (GLuint id, GLsizei len, const GLubyte *name, const GLfloat *v); +GLAPI void APIENTRY glProgramNamedParameter4dNV (GLuint id, GLsizei len, const GLubyte *name, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glProgramNamedParameter4dvNV (GLuint id, GLsizei len, const GLubyte *name, const GLdouble *v); +GLAPI void APIENTRY glGetProgramNamedParameterfvNV (GLuint id, GLsizei len, const GLubyte *name, GLfloat *params); +GLAPI void APIENTRY glGetProgramNamedParameterdvNV (GLuint id, GLsizei len, const GLubyte *name, GLdouble *params); +#endif +#endif /* GL_NV_fragment_program */ + +#ifndef GL_NV_fragment_program2 +#define GL_NV_fragment_program2 1 +#define GL_MAX_PROGRAM_EXEC_INSTRUCTIONS_NV 0x88F4 +#define GL_MAX_PROGRAM_CALL_DEPTH_NV 0x88F5 +#define GL_MAX_PROGRAM_IF_DEPTH_NV 0x88F6 +#define GL_MAX_PROGRAM_LOOP_DEPTH_NV 0x88F7 +#define GL_MAX_PROGRAM_LOOP_COUNT_NV 0x88F8 +#endif /* GL_NV_fragment_program2 */ + +#ifndef GL_NV_fragment_program4 +#define GL_NV_fragment_program4 1 +#endif /* GL_NV_fragment_program4 */ + +#ifndef GL_NV_fragment_program_option +#define GL_NV_fragment_program_option 1 +#endif /* GL_NV_fragment_program_option */ + +#ifndef GL_NV_fragment_shader_barycentric +#define GL_NV_fragment_shader_barycentric 1 +#endif /* GL_NV_fragment_shader_barycentric */ + +#ifndef GL_NV_fragment_shader_interlock +#define GL_NV_fragment_shader_interlock 1 +#endif /* GL_NV_fragment_shader_interlock */ + +#ifndef GL_NV_framebuffer_mixed_samples +#define GL_NV_framebuffer_mixed_samples 1 +#define GL_COVERAGE_MODULATION_TABLE_NV 0x9331 +#define GL_COLOR_SAMPLES_NV 0x8E20 +#define GL_DEPTH_SAMPLES_NV 0x932D +#define GL_STENCIL_SAMPLES_NV 0x932E +#define GL_MIXED_DEPTH_SAMPLES_SUPPORTED_NV 0x932F +#define GL_MIXED_STENCIL_SAMPLES_SUPPORTED_NV 0x9330 +#define GL_COVERAGE_MODULATION_NV 0x9332 +#define GL_COVERAGE_MODULATION_TABLE_SIZE_NV 0x9333 +typedef void (APIENTRYP PFNGLCOVERAGEMODULATIONTABLENVPROC) (GLsizei n, const GLfloat *v); +typedef void (APIENTRYP PFNGLGETCOVERAGEMODULATIONTABLENVPROC) (GLsizei bufSize, GLfloat *v); +typedef void (APIENTRYP PFNGLCOVERAGEMODULATIONNVPROC) (GLenum components); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCoverageModulationTableNV (GLsizei n, const GLfloat *v); +GLAPI void APIENTRY glGetCoverageModulationTableNV (GLsizei bufSize, GLfloat *v); +GLAPI void APIENTRY glCoverageModulationNV (GLenum components); +#endif +#endif /* GL_NV_framebuffer_mixed_samples */ + +#ifndef GL_NV_framebuffer_multisample_coverage +#define GL_NV_framebuffer_multisample_coverage 1 +#define GL_RENDERBUFFER_COVERAGE_SAMPLES_NV 0x8CAB +#define GL_RENDERBUFFER_COLOR_SAMPLES_NV 0x8E10 +#define GL_MAX_MULTISAMPLE_COVERAGE_MODES_NV 0x8E11 +#define GL_MULTISAMPLE_COVERAGE_MODES_NV 0x8E12 +typedef void (APIENTRYP PFNGLRENDERBUFFERSTORAGEMULTISAMPLECOVERAGENVPROC) (GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLenum internalformat, GLsizei width, GLsizei height); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glRenderbufferStorageMultisampleCoverageNV (GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLenum internalformat, GLsizei width, GLsizei height); +#endif +#endif /* GL_NV_framebuffer_multisample_coverage */ + +#ifndef GL_NV_geometry_program4 +#define GL_NV_geometry_program4 1 +#define GL_GEOMETRY_PROGRAM_NV 0x8C26 +#define GL_MAX_PROGRAM_OUTPUT_VERTICES_NV 0x8C27 +#define GL_MAX_PROGRAM_TOTAL_OUTPUT_COMPONENTS_NV 0x8C28 +typedef void (APIENTRYP PFNGLPROGRAMVERTEXLIMITNVPROC) (GLenum target, GLint limit); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTUREEXTPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTUREFACEEXTPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level, GLenum face); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramVertexLimitNV (GLenum target, GLint limit); +GLAPI void APIENTRY glFramebufferTextureEXT (GLenum target, GLenum attachment, GLuint texture, GLint level); +GLAPI void APIENTRY glFramebufferTextureFaceEXT (GLenum target, GLenum attachment, GLuint texture, GLint level, GLenum face); +#endif +#endif /* GL_NV_geometry_program4 */ + +#ifndef GL_NV_geometry_shader4 +#define GL_NV_geometry_shader4 1 +#endif /* GL_NV_geometry_shader4 */ + +#ifndef GL_NV_geometry_shader_passthrough +#define GL_NV_geometry_shader_passthrough 1 +#endif /* GL_NV_geometry_shader_passthrough */ + +#ifndef GL_NV_gpu_multicast +#define GL_NV_gpu_multicast 1 +#define GL_PER_GPU_STORAGE_BIT_NV 0x0800 +#define GL_MULTICAST_GPUS_NV 0x92BA +#define GL_RENDER_GPU_MASK_NV 0x9558 +#define GL_PER_GPU_STORAGE_NV 0x9548 +#define GL_MULTICAST_PROGRAMMABLE_SAMPLE_LOCATION_NV 0x9549 +typedef void (APIENTRYP PFNGLRENDERGPUMASKNVPROC) (GLbitfield mask); +typedef void (APIENTRYP PFNGLMULTICASTBUFFERSUBDATANVPROC) (GLbitfield gpuMask, GLuint buffer, GLintptr offset, GLsizeiptr size, const void *data); +typedef void (APIENTRYP PFNGLMULTICASTCOPYBUFFERSUBDATANVPROC) (GLuint readGpu, GLbitfield writeGpuMask, GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); +typedef void (APIENTRYP PFNGLMULTICASTCOPYIMAGESUBDATANVPROC) (GLuint srcGpu, GLbitfield dstGpuMask, GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth); +typedef void (APIENTRYP PFNGLMULTICASTBLITFRAMEBUFFERNVPROC) (GLuint srcGpu, GLuint dstGpu, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +typedef void (APIENTRYP PFNGLMULTICASTFRAMEBUFFERSAMPLELOCATIONSFVNVPROC) (GLuint gpu, GLuint framebuffer, GLuint start, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTICASTBARRIERNVPROC) (void); +typedef void (APIENTRYP PFNGLMULTICASTWAITSYNCNVPROC) (GLuint signalGpu, GLbitfield waitGpuMask); +typedef void (APIENTRYP PFNGLMULTICASTGETQUERYOBJECTIVNVPROC) (GLuint gpu, GLuint id, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLMULTICASTGETQUERYOBJECTUIVNVPROC) (GLuint gpu, GLuint id, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLMULTICASTGETQUERYOBJECTI64VNVPROC) (GLuint gpu, GLuint id, GLenum pname, GLint64 *params); +typedef void (APIENTRYP PFNGLMULTICASTGETQUERYOBJECTUI64VNVPROC) (GLuint gpu, GLuint id, GLenum pname, GLuint64 *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glRenderGpuMaskNV (GLbitfield mask); +GLAPI void APIENTRY glMulticastBufferSubDataNV (GLbitfield gpuMask, GLuint buffer, GLintptr offset, GLsizeiptr size, const void *data); +GLAPI void APIENTRY glMulticastCopyBufferSubDataNV (GLuint readGpu, GLbitfield writeGpuMask, GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); +GLAPI void APIENTRY glMulticastCopyImageSubDataNV (GLuint srcGpu, GLbitfield dstGpuMask, GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth); +GLAPI void APIENTRY glMulticastBlitFramebufferNV (GLuint srcGpu, GLuint dstGpu, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +GLAPI void APIENTRY glMulticastFramebufferSampleLocationsfvNV (GLuint gpu, GLuint framebuffer, GLuint start, GLsizei count, const GLfloat *v); +GLAPI void APIENTRY glMulticastBarrierNV (void); +GLAPI void APIENTRY glMulticastWaitSyncNV (GLuint signalGpu, GLbitfield waitGpuMask); +GLAPI void APIENTRY glMulticastGetQueryObjectivNV (GLuint gpu, GLuint id, GLenum pname, GLint *params); +GLAPI void APIENTRY glMulticastGetQueryObjectuivNV (GLuint gpu, GLuint id, GLenum pname, GLuint *params); +GLAPI void APIENTRY glMulticastGetQueryObjecti64vNV (GLuint gpu, GLuint id, GLenum pname, GLint64 *params); +GLAPI void APIENTRY glMulticastGetQueryObjectui64vNV (GLuint gpu, GLuint id, GLenum pname, GLuint64 *params); +#endif +#endif /* GL_NV_gpu_multicast */ + +#ifndef GL_NV_gpu_program4 +#define GL_NV_gpu_program4 1 +#define GL_MIN_PROGRAM_TEXEL_OFFSET_NV 0x8904 +#define GL_MAX_PROGRAM_TEXEL_OFFSET_NV 0x8905 +#define GL_PROGRAM_ATTRIB_COMPONENTS_NV 0x8906 +#define GL_PROGRAM_RESULT_COMPONENTS_NV 0x8907 +#define GL_MAX_PROGRAM_ATTRIB_COMPONENTS_NV 0x8908 +#define GL_MAX_PROGRAM_RESULT_COMPONENTS_NV 0x8909 +#define GL_MAX_PROGRAM_GENERIC_ATTRIBS_NV 0x8DA5 +#define GL_MAX_PROGRAM_GENERIC_RESULTS_NV 0x8DA6 +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETERI4INVPROC) (GLenum target, GLuint index, GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETERI4IVNVPROC) (GLenum target, GLuint index, const GLint *params); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETERSI4IVNVPROC) (GLenum target, GLuint index, GLsizei count, const GLint *params); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETERI4UINVPROC) (GLenum target, GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETERI4UIVNVPROC) (GLenum target, GLuint index, const GLuint *params); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETERSI4UIVNVPROC) (GLenum target, GLuint index, GLsizei count, const GLuint *params); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETERI4INVPROC) (GLenum target, GLuint index, GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETERI4IVNVPROC) (GLenum target, GLuint index, const GLint *params); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETERSI4IVNVPROC) (GLenum target, GLuint index, GLsizei count, const GLint *params); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETERI4UINVPROC) (GLenum target, GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETERI4UIVNVPROC) (GLenum target, GLuint index, const GLuint *params); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETERSI4UIVNVPROC) (GLenum target, GLuint index, GLsizei count, const GLuint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMLOCALPARAMETERIIVNVPROC) (GLenum target, GLuint index, GLint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMLOCALPARAMETERIUIVNVPROC) (GLenum target, GLuint index, GLuint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMENVPARAMETERIIVNVPROC) (GLenum target, GLuint index, GLint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMENVPARAMETERIUIVNVPROC) (GLenum target, GLuint index, GLuint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramLocalParameterI4iNV (GLenum target, GLuint index, GLint x, GLint y, GLint z, GLint w); +GLAPI void APIENTRY glProgramLocalParameterI4ivNV (GLenum target, GLuint index, const GLint *params); +GLAPI void APIENTRY glProgramLocalParametersI4ivNV (GLenum target, GLuint index, GLsizei count, const GLint *params); +GLAPI void APIENTRY glProgramLocalParameterI4uiNV (GLenum target, GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +GLAPI void APIENTRY glProgramLocalParameterI4uivNV (GLenum target, GLuint index, const GLuint *params); +GLAPI void APIENTRY glProgramLocalParametersI4uivNV (GLenum target, GLuint index, GLsizei count, const GLuint *params); +GLAPI void APIENTRY glProgramEnvParameterI4iNV (GLenum target, GLuint index, GLint x, GLint y, GLint z, GLint w); +GLAPI void APIENTRY glProgramEnvParameterI4ivNV (GLenum target, GLuint index, const GLint *params); +GLAPI void APIENTRY glProgramEnvParametersI4ivNV (GLenum target, GLuint index, GLsizei count, const GLint *params); +GLAPI void APIENTRY glProgramEnvParameterI4uiNV (GLenum target, GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); +GLAPI void APIENTRY glProgramEnvParameterI4uivNV (GLenum target, GLuint index, const GLuint *params); +GLAPI void APIENTRY glProgramEnvParametersI4uivNV (GLenum target, GLuint index, GLsizei count, const GLuint *params); +GLAPI void APIENTRY glGetProgramLocalParameterIivNV (GLenum target, GLuint index, GLint *params); +GLAPI void APIENTRY glGetProgramLocalParameterIuivNV (GLenum target, GLuint index, GLuint *params); +GLAPI void APIENTRY glGetProgramEnvParameterIivNV (GLenum target, GLuint index, GLint *params); +GLAPI void APIENTRY glGetProgramEnvParameterIuivNV (GLenum target, GLuint index, GLuint *params); +#endif +#endif /* GL_NV_gpu_program4 */ + +#ifndef GL_NV_gpu_program5 +#define GL_NV_gpu_program5 1 +#define GL_MAX_GEOMETRY_PROGRAM_INVOCATIONS_NV 0x8E5A +#define GL_MIN_FRAGMENT_INTERPOLATION_OFFSET_NV 0x8E5B +#define GL_MAX_FRAGMENT_INTERPOLATION_OFFSET_NV 0x8E5C +#define GL_FRAGMENT_PROGRAM_INTERPOLATION_OFFSET_BITS_NV 0x8E5D +#define GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET_NV 0x8E5E +#define GL_MAX_PROGRAM_TEXTURE_GATHER_OFFSET_NV 0x8E5F +#define GL_MAX_PROGRAM_SUBROUTINE_PARAMETERS_NV 0x8F44 +#define GL_MAX_PROGRAM_SUBROUTINE_NUM_NV 0x8F45 +typedef void (APIENTRYP PFNGLPROGRAMSUBROUTINEPARAMETERSUIVNVPROC) (GLenum target, GLsizei count, const GLuint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMSUBROUTINEPARAMETERUIVNVPROC) (GLenum target, GLuint index, GLuint *param); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramSubroutineParametersuivNV (GLenum target, GLsizei count, const GLuint *params); +GLAPI void APIENTRY glGetProgramSubroutineParameteruivNV (GLenum target, GLuint index, GLuint *param); +#endif +#endif /* GL_NV_gpu_program5 */ + +#ifndef GL_NV_gpu_program5_mem_extended +#define GL_NV_gpu_program5_mem_extended 1 +#endif /* GL_NV_gpu_program5_mem_extended */ + +#ifndef GL_NV_gpu_shader5 +#define GL_NV_gpu_shader5 1 +#endif /* GL_NV_gpu_shader5 */ + +#ifndef GL_NV_half_float +#define GL_NV_half_float 1 +typedef unsigned short GLhalfNV; +#define GL_HALF_FLOAT_NV 0x140B +typedef void (APIENTRYP PFNGLVERTEX2HNVPROC) (GLhalfNV x, GLhalfNV y); +typedef void (APIENTRYP PFNGLVERTEX2HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEX3HNVPROC) (GLhalfNV x, GLhalfNV y, GLhalfNV z); +typedef void (APIENTRYP PFNGLVERTEX3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEX4HNVPROC) (GLhalfNV x, GLhalfNV y, GLhalfNV z, GLhalfNV w); +typedef void (APIENTRYP PFNGLVERTEX4HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLNORMAL3HNVPROC) (GLhalfNV nx, GLhalfNV ny, GLhalfNV nz); +typedef void (APIENTRYP PFNGLNORMAL3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLCOLOR3HNVPROC) (GLhalfNV red, GLhalfNV green, GLhalfNV blue); +typedef void (APIENTRYP PFNGLCOLOR3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLCOLOR4HNVPROC) (GLhalfNV red, GLhalfNV green, GLhalfNV blue, GLhalfNV alpha); +typedef void (APIENTRYP PFNGLCOLOR4HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLTEXCOORD1HNVPROC) (GLhalfNV s); +typedef void (APIENTRYP PFNGLTEXCOORD1HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLTEXCOORD2HNVPROC) (GLhalfNV s, GLhalfNV t); +typedef void (APIENTRYP PFNGLTEXCOORD2HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLTEXCOORD3HNVPROC) (GLhalfNV s, GLhalfNV t, GLhalfNV r); +typedef void (APIENTRYP PFNGLTEXCOORD3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLTEXCOORD4HNVPROC) (GLhalfNV s, GLhalfNV t, GLhalfNV r, GLhalfNV q); +typedef void (APIENTRYP PFNGLTEXCOORD4HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1HNVPROC) (GLenum target, GLhalfNV s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1HVNVPROC) (GLenum target, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2HNVPROC) (GLenum target, GLhalfNV s, GLhalfNV t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2HVNVPROC) (GLenum target, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3HNVPROC) (GLenum target, GLhalfNV s, GLhalfNV t, GLhalfNV r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3HVNVPROC) (GLenum target, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4HNVPROC) (GLenum target, GLhalfNV s, GLhalfNV t, GLhalfNV r, GLhalfNV q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4HVNVPROC) (GLenum target, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLFOGCOORDHNVPROC) (GLhalfNV fog); +typedef void (APIENTRYP PFNGLFOGCOORDHVNVPROC) (const GLhalfNV *fog); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3HNVPROC) (GLhalfNV red, GLhalfNV green, GLhalfNV blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXWEIGHTHNVPROC) (GLhalfNV weight); +typedef void (APIENTRYP PFNGLVERTEXWEIGHTHVNVPROC) (const GLhalfNV *weight); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1HNVPROC) (GLuint index, GLhalfNV x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1HVNVPROC) (GLuint index, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2HNVPROC) (GLuint index, GLhalfNV x, GLhalfNV y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2HVNVPROC) (GLuint index, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3HNVPROC) (GLuint index, GLhalfNV x, GLhalfNV y, GLhalfNV z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3HVNVPROC) (GLuint index, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4HNVPROC) (GLuint index, GLhalfNV x, GLhalfNV y, GLhalfNV z, GLhalfNV w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4HVNVPROC) (GLuint index, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS1HVNVPROC) (GLuint index, GLsizei n, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS2HVNVPROC) (GLuint index, GLsizei n, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS3HVNVPROC) (GLuint index, GLsizei n, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4HVNVPROC) (GLuint index, GLsizei n, const GLhalfNV *v); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertex2hNV (GLhalfNV x, GLhalfNV y); +GLAPI void APIENTRY glVertex2hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glVertex3hNV (GLhalfNV x, GLhalfNV y, GLhalfNV z); +GLAPI void APIENTRY glVertex3hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glVertex4hNV (GLhalfNV x, GLhalfNV y, GLhalfNV z, GLhalfNV w); +GLAPI void APIENTRY glVertex4hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glNormal3hNV (GLhalfNV nx, GLhalfNV ny, GLhalfNV nz); +GLAPI void APIENTRY glNormal3hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glColor3hNV (GLhalfNV red, GLhalfNV green, GLhalfNV blue); +GLAPI void APIENTRY glColor3hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glColor4hNV (GLhalfNV red, GLhalfNV green, GLhalfNV blue, GLhalfNV alpha); +GLAPI void APIENTRY glColor4hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glTexCoord1hNV (GLhalfNV s); +GLAPI void APIENTRY glTexCoord1hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glTexCoord2hNV (GLhalfNV s, GLhalfNV t); +GLAPI void APIENTRY glTexCoord2hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glTexCoord3hNV (GLhalfNV s, GLhalfNV t, GLhalfNV r); +GLAPI void APIENTRY glTexCoord3hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glTexCoord4hNV (GLhalfNV s, GLhalfNV t, GLhalfNV r, GLhalfNV q); +GLAPI void APIENTRY glTexCoord4hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glMultiTexCoord1hNV (GLenum target, GLhalfNV s); +GLAPI void APIENTRY glMultiTexCoord1hvNV (GLenum target, const GLhalfNV *v); +GLAPI void APIENTRY glMultiTexCoord2hNV (GLenum target, GLhalfNV s, GLhalfNV t); +GLAPI void APIENTRY glMultiTexCoord2hvNV (GLenum target, const GLhalfNV *v); +GLAPI void APIENTRY glMultiTexCoord3hNV (GLenum target, GLhalfNV s, GLhalfNV t, GLhalfNV r); +GLAPI void APIENTRY glMultiTexCoord3hvNV (GLenum target, const GLhalfNV *v); +GLAPI void APIENTRY glMultiTexCoord4hNV (GLenum target, GLhalfNV s, GLhalfNV t, GLhalfNV r, GLhalfNV q); +GLAPI void APIENTRY glMultiTexCoord4hvNV (GLenum target, const GLhalfNV *v); +GLAPI void APIENTRY glFogCoordhNV (GLhalfNV fog); +GLAPI void APIENTRY glFogCoordhvNV (const GLhalfNV *fog); +GLAPI void APIENTRY glSecondaryColor3hNV (GLhalfNV red, GLhalfNV green, GLhalfNV blue); +GLAPI void APIENTRY glSecondaryColor3hvNV (const GLhalfNV *v); +GLAPI void APIENTRY glVertexWeighthNV (GLhalfNV weight); +GLAPI void APIENTRY glVertexWeighthvNV (const GLhalfNV *weight); +GLAPI void APIENTRY glVertexAttrib1hNV (GLuint index, GLhalfNV x); +GLAPI void APIENTRY glVertexAttrib1hvNV (GLuint index, const GLhalfNV *v); +GLAPI void APIENTRY glVertexAttrib2hNV (GLuint index, GLhalfNV x, GLhalfNV y); +GLAPI void APIENTRY glVertexAttrib2hvNV (GLuint index, const GLhalfNV *v); +GLAPI void APIENTRY glVertexAttrib3hNV (GLuint index, GLhalfNV x, GLhalfNV y, GLhalfNV z); +GLAPI void APIENTRY glVertexAttrib3hvNV (GLuint index, const GLhalfNV *v); +GLAPI void APIENTRY glVertexAttrib4hNV (GLuint index, GLhalfNV x, GLhalfNV y, GLhalfNV z, GLhalfNV w); +GLAPI void APIENTRY glVertexAttrib4hvNV (GLuint index, const GLhalfNV *v); +GLAPI void APIENTRY glVertexAttribs1hvNV (GLuint index, GLsizei n, const GLhalfNV *v); +GLAPI void APIENTRY glVertexAttribs2hvNV (GLuint index, GLsizei n, const GLhalfNV *v); +GLAPI void APIENTRY glVertexAttribs3hvNV (GLuint index, GLsizei n, const GLhalfNV *v); +GLAPI void APIENTRY glVertexAttribs4hvNV (GLuint index, GLsizei n, const GLhalfNV *v); +#endif +#endif /* GL_NV_half_float */ + +#ifndef GL_NV_internalformat_sample_query +#define GL_NV_internalformat_sample_query 1 +#define GL_MULTISAMPLES_NV 0x9371 +#define GL_SUPERSAMPLE_SCALE_X_NV 0x9372 +#define GL_SUPERSAMPLE_SCALE_Y_NV 0x9373 +#define GL_CONFORMANT_NV 0x9374 +typedef void (APIENTRYP PFNGLGETINTERNALFORMATSAMPLEIVNVPROC) (GLenum target, GLenum internalformat, GLsizei samples, GLenum pname, GLsizei count, GLint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetInternalformatSampleivNV (GLenum target, GLenum internalformat, GLsizei samples, GLenum pname, GLsizei count, GLint *params); +#endif +#endif /* GL_NV_internalformat_sample_query */ + +#ifndef GL_NV_light_max_exponent +#define GL_NV_light_max_exponent 1 +#define GL_MAX_SHININESS_NV 0x8504 +#define GL_MAX_SPOT_EXPONENT_NV 0x8505 +#endif /* GL_NV_light_max_exponent */ + +#ifndef GL_NV_memory_attachment +#define GL_NV_memory_attachment 1 +#define GL_ATTACHED_MEMORY_OBJECT_NV 0x95A4 +#define GL_ATTACHED_MEMORY_OFFSET_NV 0x95A5 +#define GL_MEMORY_ATTACHABLE_ALIGNMENT_NV 0x95A6 +#define GL_MEMORY_ATTACHABLE_SIZE_NV 0x95A7 +#define GL_MEMORY_ATTACHABLE_NV 0x95A8 +#define GL_DETACHED_MEMORY_INCARNATION_NV 0x95A9 +#define GL_DETACHED_TEXTURES_NV 0x95AA +#define GL_DETACHED_BUFFERS_NV 0x95AB +#define GL_MAX_DETACHED_TEXTURES_NV 0x95AC +#define GL_MAX_DETACHED_BUFFERS_NV 0x95AD +typedef void (APIENTRYP PFNGLGETMEMORYOBJECTDETACHEDRESOURCESUIVNVPROC) (GLuint memory, GLenum pname, GLint first, GLsizei count, GLuint *params); +typedef void (APIENTRYP PFNGLRESETMEMORYOBJECTPARAMETERNVPROC) (GLuint memory, GLenum pname); +typedef void (APIENTRYP PFNGLTEXATTACHMEMORYNVPROC) (GLenum target, GLuint memory, GLuint64 offset); +typedef void (APIENTRYP PFNGLBUFFERATTACHMEMORYNVPROC) (GLenum target, GLuint memory, GLuint64 offset); +typedef void (APIENTRYP PFNGLTEXTUREATTACHMEMORYNVPROC) (GLuint texture, GLuint memory, GLuint64 offset); +typedef void (APIENTRYP PFNGLNAMEDBUFFERATTACHMEMORYNVPROC) (GLuint buffer, GLuint memory, GLuint64 offset); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetMemoryObjectDetachedResourcesuivNV (GLuint memory, GLenum pname, GLint first, GLsizei count, GLuint *params); +GLAPI void APIENTRY glResetMemoryObjectParameterNV (GLuint memory, GLenum pname); +GLAPI void APIENTRY glTexAttachMemoryNV (GLenum target, GLuint memory, GLuint64 offset); +GLAPI void APIENTRY glBufferAttachMemoryNV (GLenum target, GLuint memory, GLuint64 offset); +GLAPI void APIENTRY glTextureAttachMemoryNV (GLuint texture, GLuint memory, GLuint64 offset); +GLAPI void APIENTRY glNamedBufferAttachMemoryNV (GLuint buffer, GLuint memory, GLuint64 offset); +#endif +#endif /* GL_NV_memory_attachment */ + +#ifndef GL_NV_memory_object_sparse +#define GL_NV_memory_object_sparse 1 +typedef void (APIENTRYP PFNGLBUFFERPAGECOMMITMENTMEMNVPROC) (GLenum target, GLintptr offset, GLsizeiptr size, GLuint memory, GLuint64 memOffset, GLboolean commit); +typedef void (APIENTRYP PFNGLTEXPAGECOMMITMENTMEMNVPROC) (GLenum target, GLint layer, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLuint memory, GLuint64 offset, GLboolean commit); +typedef void (APIENTRYP PFNGLNAMEDBUFFERPAGECOMMITMENTMEMNVPROC) (GLuint buffer, GLintptr offset, GLsizeiptr size, GLuint memory, GLuint64 memOffset, GLboolean commit); +typedef void (APIENTRYP PFNGLTEXTUREPAGECOMMITMENTMEMNVPROC) (GLuint texture, GLint layer, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLuint memory, GLuint64 offset, GLboolean commit); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBufferPageCommitmentMemNV (GLenum target, GLintptr offset, GLsizeiptr size, GLuint memory, GLuint64 memOffset, GLboolean commit); +GLAPI void APIENTRY glTexPageCommitmentMemNV (GLenum target, GLint layer, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLuint memory, GLuint64 offset, GLboolean commit); +GLAPI void APIENTRY glNamedBufferPageCommitmentMemNV (GLuint buffer, GLintptr offset, GLsizeiptr size, GLuint memory, GLuint64 memOffset, GLboolean commit); +GLAPI void APIENTRY glTexturePageCommitmentMemNV (GLuint texture, GLint layer, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLuint memory, GLuint64 offset, GLboolean commit); +#endif +#endif /* GL_NV_memory_object_sparse */ + +#ifndef GL_NV_mesh_shader +#define GL_NV_mesh_shader 1 +#define GL_MESH_SHADER_NV 0x9559 +#define GL_TASK_SHADER_NV 0x955A +#define GL_MAX_MESH_UNIFORM_BLOCKS_NV 0x8E60 +#define GL_MAX_MESH_TEXTURE_IMAGE_UNITS_NV 0x8E61 +#define GL_MAX_MESH_IMAGE_UNIFORMS_NV 0x8E62 +#define GL_MAX_MESH_UNIFORM_COMPONENTS_NV 0x8E63 +#define GL_MAX_MESH_ATOMIC_COUNTER_BUFFERS_NV 0x8E64 +#define GL_MAX_MESH_ATOMIC_COUNTERS_NV 0x8E65 +#define GL_MAX_MESH_SHADER_STORAGE_BLOCKS_NV 0x8E66 +#define GL_MAX_COMBINED_MESH_UNIFORM_COMPONENTS_NV 0x8E67 +#define GL_MAX_TASK_UNIFORM_BLOCKS_NV 0x8E68 +#define GL_MAX_TASK_TEXTURE_IMAGE_UNITS_NV 0x8E69 +#define GL_MAX_TASK_IMAGE_UNIFORMS_NV 0x8E6A +#define GL_MAX_TASK_UNIFORM_COMPONENTS_NV 0x8E6B +#define GL_MAX_TASK_ATOMIC_COUNTER_BUFFERS_NV 0x8E6C +#define GL_MAX_TASK_ATOMIC_COUNTERS_NV 0x8E6D +#define GL_MAX_TASK_SHADER_STORAGE_BLOCKS_NV 0x8E6E +#define GL_MAX_COMBINED_TASK_UNIFORM_COMPONENTS_NV 0x8E6F +#define GL_MAX_MESH_WORK_GROUP_INVOCATIONS_NV 0x95A2 +#define GL_MAX_TASK_WORK_GROUP_INVOCATIONS_NV 0x95A3 +#define GL_MAX_MESH_TOTAL_MEMORY_SIZE_NV 0x9536 +#define GL_MAX_TASK_TOTAL_MEMORY_SIZE_NV 0x9537 +#define GL_MAX_MESH_OUTPUT_VERTICES_NV 0x9538 +#define GL_MAX_MESH_OUTPUT_PRIMITIVES_NV 0x9539 +#define GL_MAX_TASK_OUTPUT_COUNT_NV 0x953A +#define GL_MAX_DRAW_MESH_TASKS_COUNT_NV 0x953D +#define GL_MAX_MESH_VIEWS_NV 0x9557 +#define GL_MESH_OUTPUT_PER_VERTEX_GRANULARITY_NV 0x92DF +#define GL_MESH_OUTPUT_PER_PRIMITIVE_GRANULARITY_NV 0x9543 +#define GL_MAX_MESH_WORK_GROUP_SIZE_NV 0x953B +#define GL_MAX_TASK_WORK_GROUP_SIZE_NV 0x953C +#define GL_MESH_WORK_GROUP_SIZE_NV 0x953E +#define GL_TASK_WORK_GROUP_SIZE_NV 0x953F +#define GL_MESH_VERTICES_OUT_NV 0x9579 +#define GL_MESH_PRIMITIVES_OUT_NV 0x957A +#define GL_MESH_OUTPUT_TYPE_NV 0x957B +#define GL_UNIFORM_BLOCK_REFERENCED_BY_MESH_SHADER_NV 0x959C +#define GL_UNIFORM_BLOCK_REFERENCED_BY_TASK_SHADER_NV 0x959D +#define GL_REFERENCED_BY_MESH_SHADER_NV 0x95A0 +#define GL_REFERENCED_BY_TASK_SHADER_NV 0x95A1 +#define GL_MESH_SHADER_BIT_NV 0x00000040 +#define GL_TASK_SHADER_BIT_NV 0x00000080 +#define GL_MESH_SUBROUTINE_NV 0x957C +#define GL_TASK_SUBROUTINE_NV 0x957D +#define GL_MESH_SUBROUTINE_UNIFORM_NV 0x957E +#define GL_TASK_SUBROUTINE_UNIFORM_NV 0x957F +#define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_MESH_SHADER_NV 0x959E +#define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_TASK_SHADER_NV 0x959F +typedef void (APIENTRYP PFNGLDRAWMESHTASKSNVPROC) (GLuint first, GLuint count); +typedef void (APIENTRYP PFNGLDRAWMESHTASKSINDIRECTNVPROC) (GLintptr indirect); +typedef void (APIENTRYP PFNGLMULTIDRAWMESHTASKSINDIRECTNVPROC) (GLintptr indirect, GLsizei drawcount, GLsizei stride); +typedef void (APIENTRYP PFNGLMULTIDRAWMESHTASKSINDIRECTCOUNTNVPROC) (GLintptr indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawMeshTasksNV (GLuint first, GLuint count); +GLAPI void APIENTRY glDrawMeshTasksIndirectNV (GLintptr indirect); +GLAPI void APIENTRY glMultiDrawMeshTasksIndirectNV (GLintptr indirect, GLsizei drawcount, GLsizei stride); +GLAPI void APIENTRY glMultiDrawMeshTasksIndirectCountNV (GLintptr indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride); +#endif +#endif /* GL_NV_mesh_shader */ + +#ifndef GL_NV_multisample_coverage +#define GL_NV_multisample_coverage 1 +#endif /* GL_NV_multisample_coverage */ + +#ifndef GL_NV_multisample_filter_hint +#define GL_NV_multisample_filter_hint 1 +#define GL_MULTISAMPLE_FILTER_HINT_NV 0x8534 +#endif /* GL_NV_multisample_filter_hint */ + +#ifndef GL_NV_occlusion_query +#define GL_NV_occlusion_query 1 +#define GL_PIXEL_COUNTER_BITS_NV 0x8864 +#define GL_CURRENT_OCCLUSION_QUERY_ID_NV 0x8865 +#define GL_PIXEL_COUNT_NV 0x8866 +#define GL_PIXEL_COUNT_AVAILABLE_NV 0x8867 +typedef void (APIENTRYP PFNGLGENOCCLUSIONQUERIESNVPROC) (GLsizei n, GLuint *ids); +typedef void (APIENTRYP PFNGLDELETEOCCLUSIONQUERIESNVPROC) (GLsizei n, const GLuint *ids); +typedef GLboolean (APIENTRYP PFNGLISOCCLUSIONQUERYNVPROC) (GLuint id); +typedef void (APIENTRYP PFNGLBEGINOCCLUSIONQUERYNVPROC) (GLuint id); +typedef void (APIENTRYP PFNGLENDOCCLUSIONQUERYNVPROC) (void); +typedef void (APIENTRYP PFNGLGETOCCLUSIONQUERYIVNVPROC) (GLuint id, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETOCCLUSIONQUERYUIVNVPROC) (GLuint id, GLenum pname, GLuint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenOcclusionQueriesNV (GLsizei n, GLuint *ids); +GLAPI void APIENTRY glDeleteOcclusionQueriesNV (GLsizei n, const GLuint *ids); +GLAPI GLboolean APIENTRY glIsOcclusionQueryNV (GLuint id); +GLAPI void APIENTRY glBeginOcclusionQueryNV (GLuint id); +GLAPI void APIENTRY glEndOcclusionQueryNV (void); +GLAPI void APIENTRY glGetOcclusionQueryivNV (GLuint id, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetOcclusionQueryuivNV (GLuint id, GLenum pname, GLuint *params); +#endif +#endif /* GL_NV_occlusion_query */ + +#ifndef GL_NV_packed_depth_stencil +#define GL_NV_packed_depth_stencil 1 +#define GL_DEPTH_STENCIL_NV 0x84F9 +#define GL_UNSIGNED_INT_24_8_NV 0x84FA +#endif /* GL_NV_packed_depth_stencil */ + +#ifndef GL_NV_parameter_buffer_object +#define GL_NV_parameter_buffer_object 1 +#define GL_MAX_PROGRAM_PARAMETER_BUFFER_BINDINGS_NV 0x8DA0 +#define GL_MAX_PROGRAM_PARAMETER_BUFFER_SIZE_NV 0x8DA1 +#define GL_VERTEX_PROGRAM_PARAMETER_BUFFER_NV 0x8DA2 +#define GL_GEOMETRY_PROGRAM_PARAMETER_BUFFER_NV 0x8DA3 +#define GL_FRAGMENT_PROGRAM_PARAMETER_BUFFER_NV 0x8DA4 +typedef void (APIENTRYP PFNGLPROGRAMBUFFERPARAMETERSFVNVPROC) (GLenum target, GLuint bindingIndex, GLuint wordIndex, GLsizei count, const GLfloat *params); +typedef void (APIENTRYP PFNGLPROGRAMBUFFERPARAMETERSIIVNVPROC) (GLenum target, GLuint bindingIndex, GLuint wordIndex, GLsizei count, const GLint *params); +typedef void (APIENTRYP PFNGLPROGRAMBUFFERPARAMETERSIUIVNVPROC) (GLenum target, GLuint bindingIndex, GLuint wordIndex, GLsizei count, const GLuint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramBufferParametersfvNV (GLenum target, GLuint bindingIndex, GLuint wordIndex, GLsizei count, const GLfloat *params); +GLAPI void APIENTRY glProgramBufferParametersIivNV (GLenum target, GLuint bindingIndex, GLuint wordIndex, GLsizei count, const GLint *params); +GLAPI void APIENTRY glProgramBufferParametersIuivNV (GLenum target, GLuint bindingIndex, GLuint wordIndex, GLsizei count, const GLuint *params); +#endif +#endif /* GL_NV_parameter_buffer_object */ + +#ifndef GL_NV_parameter_buffer_object2 +#define GL_NV_parameter_buffer_object2 1 +#endif /* GL_NV_parameter_buffer_object2 */ + +#ifndef GL_NV_path_rendering +#define GL_NV_path_rendering 1 +#define GL_PATH_FORMAT_SVG_NV 0x9070 +#define GL_PATH_FORMAT_PS_NV 0x9071 +#define GL_STANDARD_FONT_NAME_NV 0x9072 +#define GL_SYSTEM_FONT_NAME_NV 0x9073 +#define GL_FILE_NAME_NV 0x9074 +#define GL_PATH_STROKE_WIDTH_NV 0x9075 +#define GL_PATH_END_CAPS_NV 0x9076 +#define GL_PATH_INITIAL_END_CAP_NV 0x9077 +#define GL_PATH_TERMINAL_END_CAP_NV 0x9078 +#define GL_PATH_JOIN_STYLE_NV 0x9079 +#define GL_PATH_MITER_LIMIT_NV 0x907A +#define GL_PATH_DASH_CAPS_NV 0x907B +#define GL_PATH_INITIAL_DASH_CAP_NV 0x907C +#define GL_PATH_TERMINAL_DASH_CAP_NV 0x907D +#define GL_PATH_DASH_OFFSET_NV 0x907E +#define GL_PATH_CLIENT_LENGTH_NV 0x907F +#define GL_PATH_FILL_MODE_NV 0x9080 +#define GL_PATH_FILL_MASK_NV 0x9081 +#define GL_PATH_FILL_COVER_MODE_NV 0x9082 +#define GL_PATH_STROKE_COVER_MODE_NV 0x9083 +#define GL_PATH_STROKE_MASK_NV 0x9084 +#define GL_COUNT_UP_NV 0x9088 +#define GL_COUNT_DOWN_NV 0x9089 +#define GL_PATH_OBJECT_BOUNDING_BOX_NV 0x908A +#define GL_CONVEX_HULL_NV 0x908B +#define GL_BOUNDING_BOX_NV 0x908D +#define GL_TRANSLATE_X_NV 0x908E +#define GL_TRANSLATE_Y_NV 0x908F +#define GL_TRANSLATE_2D_NV 0x9090 +#define GL_TRANSLATE_3D_NV 0x9091 +#define GL_AFFINE_2D_NV 0x9092 +#define GL_AFFINE_3D_NV 0x9094 +#define GL_TRANSPOSE_AFFINE_2D_NV 0x9096 +#define GL_TRANSPOSE_AFFINE_3D_NV 0x9098 +#define GL_UTF8_NV 0x909A +#define GL_UTF16_NV 0x909B +#define GL_BOUNDING_BOX_OF_BOUNDING_BOXES_NV 0x909C +#define GL_PATH_COMMAND_COUNT_NV 0x909D +#define GL_PATH_COORD_COUNT_NV 0x909E +#define GL_PATH_DASH_ARRAY_COUNT_NV 0x909F +#define GL_PATH_COMPUTED_LENGTH_NV 0x90A0 +#define GL_PATH_FILL_BOUNDING_BOX_NV 0x90A1 +#define GL_PATH_STROKE_BOUNDING_BOX_NV 0x90A2 +#define GL_SQUARE_NV 0x90A3 +#define GL_ROUND_NV 0x90A4 +#define GL_TRIANGULAR_NV 0x90A5 +#define GL_BEVEL_NV 0x90A6 +#define GL_MITER_REVERT_NV 0x90A7 +#define GL_MITER_TRUNCATE_NV 0x90A8 +#define GL_SKIP_MISSING_GLYPH_NV 0x90A9 +#define GL_USE_MISSING_GLYPH_NV 0x90AA +#define GL_PATH_ERROR_POSITION_NV 0x90AB +#define GL_ACCUM_ADJACENT_PAIRS_NV 0x90AD +#define GL_ADJACENT_PAIRS_NV 0x90AE +#define GL_FIRST_TO_REST_NV 0x90AF +#define GL_PATH_GEN_MODE_NV 0x90B0 +#define GL_PATH_GEN_COEFF_NV 0x90B1 +#define GL_PATH_GEN_COMPONENTS_NV 0x90B3 +#define GL_PATH_STENCIL_FUNC_NV 0x90B7 +#define GL_PATH_STENCIL_REF_NV 0x90B8 +#define GL_PATH_STENCIL_VALUE_MASK_NV 0x90B9 +#define GL_PATH_STENCIL_DEPTH_OFFSET_FACTOR_NV 0x90BD +#define GL_PATH_STENCIL_DEPTH_OFFSET_UNITS_NV 0x90BE +#define GL_PATH_COVER_DEPTH_FUNC_NV 0x90BF +#define GL_PATH_DASH_OFFSET_RESET_NV 0x90B4 +#define GL_MOVE_TO_RESETS_NV 0x90B5 +#define GL_MOVE_TO_CONTINUES_NV 0x90B6 +#define GL_CLOSE_PATH_NV 0x00 +#define GL_MOVE_TO_NV 0x02 +#define GL_RELATIVE_MOVE_TO_NV 0x03 +#define GL_LINE_TO_NV 0x04 +#define GL_RELATIVE_LINE_TO_NV 0x05 +#define GL_HORIZONTAL_LINE_TO_NV 0x06 +#define GL_RELATIVE_HORIZONTAL_LINE_TO_NV 0x07 +#define GL_VERTICAL_LINE_TO_NV 0x08 +#define GL_RELATIVE_VERTICAL_LINE_TO_NV 0x09 +#define GL_QUADRATIC_CURVE_TO_NV 0x0A +#define GL_RELATIVE_QUADRATIC_CURVE_TO_NV 0x0B +#define GL_CUBIC_CURVE_TO_NV 0x0C +#define GL_RELATIVE_CUBIC_CURVE_TO_NV 0x0D +#define GL_SMOOTH_QUADRATIC_CURVE_TO_NV 0x0E +#define GL_RELATIVE_SMOOTH_QUADRATIC_CURVE_TO_NV 0x0F +#define GL_SMOOTH_CUBIC_CURVE_TO_NV 0x10 +#define GL_RELATIVE_SMOOTH_CUBIC_CURVE_TO_NV 0x11 +#define GL_SMALL_CCW_ARC_TO_NV 0x12 +#define GL_RELATIVE_SMALL_CCW_ARC_TO_NV 0x13 +#define GL_SMALL_CW_ARC_TO_NV 0x14 +#define GL_RELATIVE_SMALL_CW_ARC_TO_NV 0x15 +#define GL_LARGE_CCW_ARC_TO_NV 0x16 +#define GL_RELATIVE_LARGE_CCW_ARC_TO_NV 0x17 +#define GL_LARGE_CW_ARC_TO_NV 0x18 +#define GL_RELATIVE_LARGE_CW_ARC_TO_NV 0x19 +#define GL_RESTART_PATH_NV 0xF0 +#define GL_DUP_FIRST_CUBIC_CURVE_TO_NV 0xF2 +#define GL_DUP_LAST_CUBIC_CURVE_TO_NV 0xF4 +#define GL_RECT_NV 0xF6 +#define GL_CIRCULAR_CCW_ARC_TO_NV 0xF8 +#define GL_CIRCULAR_CW_ARC_TO_NV 0xFA +#define GL_CIRCULAR_TANGENT_ARC_TO_NV 0xFC +#define GL_ARC_TO_NV 0xFE +#define GL_RELATIVE_ARC_TO_NV 0xFF +#define GL_BOLD_BIT_NV 0x01 +#define GL_ITALIC_BIT_NV 0x02 +#define GL_GLYPH_WIDTH_BIT_NV 0x01 +#define GL_GLYPH_HEIGHT_BIT_NV 0x02 +#define GL_GLYPH_HORIZONTAL_BEARING_X_BIT_NV 0x04 +#define GL_GLYPH_HORIZONTAL_BEARING_Y_BIT_NV 0x08 +#define GL_GLYPH_HORIZONTAL_BEARING_ADVANCE_BIT_NV 0x10 +#define GL_GLYPH_VERTICAL_BEARING_X_BIT_NV 0x20 +#define GL_GLYPH_VERTICAL_BEARING_Y_BIT_NV 0x40 +#define GL_GLYPH_VERTICAL_BEARING_ADVANCE_BIT_NV 0x80 +#define GL_GLYPH_HAS_KERNING_BIT_NV 0x100 +#define GL_FONT_X_MIN_BOUNDS_BIT_NV 0x00010000 +#define GL_FONT_Y_MIN_BOUNDS_BIT_NV 0x00020000 +#define GL_FONT_X_MAX_BOUNDS_BIT_NV 0x00040000 +#define GL_FONT_Y_MAX_BOUNDS_BIT_NV 0x00080000 +#define GL_FONT_UNITS_PER_EM_BIT_NV 0x00100000 +#define GL_FONT_ASCENDER_BIT_NV 0x00200000 +#define GL_FONT_DESCENDER_BIT_NV 0x00400000 +#define GL_FONT_HEIGHT_BIT_NV 0x00800000 +#define GL_FONT_MAX_ADVANCE_WIDTH_BIT_NV 0x01000000 +#define GL_FONT_MAX_ADVANCE_HEIGHT_BIT_NV 0x02000000 +#define GL_FONT_UNDERLINE_POSITION_BIT_NV 0x04000000 +#define GL_FONT_UNDERLINE_THICKNESS_BIT_NV 0x08000000 +#define GL_FONT_HAS_KERNING_BIT_NV 0x10000000 +#define GL_ROUNDED_RECT_NV 0xE8 +#define GL_RELATIVE_ROUNDED_RECT_NV 0xE9 +#define GL_ROUNDED_RECT2_NV 0xEA +#define GL_RELATIVE_ROUNDED_RECT2_NV 0xEB +#define GL_ROUNDED_RECT4_NV 0xEC +#define GL_RELATIVE_ROUNDED_RECT4_NV 0xED +#define GL_ROUNDED_RECT8_NV 0xEE +#define GL_RELATIVE_ROUNDED_RECT8_NV 0xEF +#define GL_RELATIVE_RECT_NV 0xF7 +#define GL_FONT_GLYPHS_AVAILABLE_NV 0x9368 +#define GL_FONT_TARGET_UNAVAILABLE_NV 0x9369 +#define GL_FONT_UNAVAILABLE_NV 0x936A +#define GL_FONT_UNINTELLIGIBLE_NV 0x936B +#define GL_CONIC_CURVE_TO_NV 0x1A +#define GL_RELATIVE_CONIC_CURVE_TO_NV 0x1B +#define GL_FONT_NUM_GLYPH_INDICES_BIT_NV 0x20000000 +#define GL_STANDARD_FONT_FORMAT_NV 0x936C +#define GL_2_BYTES_NV 0x1407 +#define GL_3_BYTES_NV 0x1408 +#define GL_4_BYTES_NV 0x1409 +#define GL_EYE_LINEAR_NV 0x2400 +#define GL_OBJECT_LINEAR_NV 0x2401 +#define GL_CONSTANT_NV 0x8576 +#define GL_PATH_FOG_GEN_MODE_NV 0x90AC +#define GL_PRIMARY_COLOR_NV 0x852C +#define GL_SECONDARY_COLOR_NV 0x852D +#define GL_PATH_GEN_COLOR_FORMAT_NV 0x90B2 +#define GL_PATH_PROJECTION_NV 0x1701 +#define GL_PATH_MODELVIEW_NV 0x1700 +#define GL_PATH_MODELVIEW_STACK_DEPTH_NV 0x0BA3 +#define GL_PATH_MODELVIEW_MATRIX_NV 0x0BA6 +#define GL_PATH_MAX_MODELVIEW_STACK_DEPTH_NV 0x0D36 +#define GL_PATH_TRANSPOSE_MODELVIEW_MATRIX_NV 0x84E3 +#define GL_PATH_PROJECTION_STACK_DEPTH_NV 0x0BA4 +#define GL_PATH_PROJECTION_MATRIX_NV 0x0BA7 +#define GL_PATH_MAX_PROJECTION_STACK_DEPTH_NV 0x0D38 +#define GL_PATH_TRANSPOSE_PROJECTION_MATRIX_NV 0x84E4 +#define GL_FRAGMENT_INPUT_NV 0x936D +typedef GLuint (APIENTRYP PFNGLGENPATHSNVPROC) (GLsizei range); +typedef void (APIENTRYP PFNGLDELETEPATHSNVPROC) (GLuint path, GLsizei range); +typedef GLboolean (APIENTRYP PFNGLISPATHNVPROC) (GLuint path); +typedef void (APIENTRYP PFNGLPATHCOMMANDSNVPROC) (GLuint path, GLsizei numCommands, const GLubyte *commands, GLsizei numCoords, GLenum coordType, const void *coords); +typedef void (APIENTRYP PFNGLPATHCOORDSNVPROC) (GLuint path, GLsizei numCoords, GLenum coordType, const void *coords); +typedef void (APIENTRYP PFNGLPATHSUBCOMMANDSNVPROC) (GLuint path, GLsizei commandStart, GLsizei commandsToDelete, GLsizei numCommands, const GLubyte *commands, GLsizei numCoords, GLenum coordType, const void *coords); +typedef void (APIENTRYP PFNGLPATHSUBCOORDSNVPROC) (GLuint path, GLsizei coordStart, GLsizei numCoords, GLenum coordType, const void *coords); +typedef void (APIENTRYP PFNGLPATHSTRINGNVPROC) (GLuint path, GLenum format, GLsizei length, const void *pathString); +typedef void (APIENTRYP PFNGLPATHGLYPHSNVPROC) (GLuint firstPathName, GLenum fontTarget, const void *fontName, GLbitfield fontStyle, GLsizei numGlyphs, GLenum type, const void *charcodes, GLenum handleMissingGlyphs, GLuint pathParameterTemplate, GLfloat emScale); +typedef void (APIENTRYP PFNGLPATHGLYPHRANGENVPROC) (GLuint firstPathName, GLenum fontTarget, const void *fontName, GLbitfield fontStyle, GLuint firstGlyph, GLsizei numGlyphs, GLenum handleMissingGlyphs, GLuint pathParameterTemplate, GLfloat emScale); +typedef void (APIENTRYP PFNGLWEIGHTPATHSNVPROC) (GLuint resultPath, GLsizei numPaths, const GLuint *paths, const GLfloat *weights); +typedef void (APIENTRYP PFNGLCOPYPATHNVPROC) (GLuint resultPath, GLuint srcPath); +typedef void (APIENTRYP PFNGLINTERPOLATEPATHSNVPROC) (GLuint resultPath, GLuint pathA, GLuint pathB, GLfloat weight); +typedef void (APIENTRYP PFNGLTRANSFORMPATHNVPROC) (GLuint resultPath, GLuint srcPath, GLenum transformType, const GLfloat *transformValues); +typedef void (APIENTRYP PFNGLPATHPARAMETERIVNVPROC) (GLuint path, GLenum pname, const GLint *value); +typedef void (APIENTRYP PFNGLPATHPARAMETERINVPROC) (GLuint path, GLenum pname, GLint value); +typedef void (APIENTRYP PFNGLPATHPARAMETERFVNVPROC) (GLuint path, GLenum pname, const GLfloat *value); +typedef void (APIENTRYP PFNGLPATHPARAMETERFNVPROC) (GLuint path, GLenum pname, GLfloat value); +typedef void (APIENTRYP PFNGLPATHDASHARRAYNVPROC) (GLuint path, GLsizei dashCount, const GLfloat *dashArray); +typedef void (APIENTRYP PFNGLPATHSTENCILFUNCNVPROC) (GLenum func, GLint ref, GLuint mask); +typedef void (APIENTRYP PFNGLPATHSTENCILDEPTHOFFSETNVPROC) (GLfloat factor, GLfloat units); +typedef void (APIENTRYP PFNGLSTENCILFILLPATHNVPROC) (GLuint path, GLenum fillMode, GLuint mask); +typedef void (APIENTRYP PFNGLSTENCILSTROKEPATHNVPROC) (GLuint path, GLint reference, GLuint mask); +typedef void (APIENTRYP PFNGLSTENCILFILLPATHINSTANCEDNVPROC) (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLenum fillMode, GLuint mask, GLenum transformType, const GLfloat *transformValues); +typedef void (APIENTRYP PFNGLSTENCILSTROKEPATHINSTANCEDNVPROC) (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLint reference, GLuint mask, GLenum transformType, const GLfloat *transformValues); +typedef void (APIENTRYP PFNGLPATHCOVERDEPTHFUNCNVPROC) (GLenum func); +typedef void (APIENTRYP PFNGLCOVERFILLPATHNVPROC) (GLuint path, GLenum coverMode); +typedef void (APIENTRYP PFNGLCOVERSTROKEPATHNVPROC) (GLuint path, GLenum coverMode); +typedef void (APIENTRYP PFNGLCOVERFILLPATHINSTANCEDNVPROC) (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLenum coverMode, GLenum transformType, const GLfloat *transformValues); +typedef void (APIENTRYP PFNGLCOVERSTROKEPATHINSTANCEDNVPROC) (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLenum coverMode, GLenum transformType, const GLfloat *transformValues); +typedef void (APIENTRYP PFNGLGETPATHPARAMETERIVNVPROC) (GLuint path, GLenum pname, GLint *value); +typedef void (APIENTRYP PFNGLGETPATHPARAMETERFVNVPROC) (GLuint path, GLenum pname, GLfloat *value); +typedef void (APIENTRYP PFNGLGETPATHCOMMANDSNVPROC) (GLuint path, GLubyte *commands); +typedef void (APIENTRYP PFNGLGETPATHCOORDSNVPROC) (GLuint path, GLfloat *coords); +typedef void (APIENTRYP PFNGLGETPATHDASHARRAYNVPROC) (GLuint path, GLfloat *dashArray); +typedef void (APIENTRYP PFNGLGETPATHMETRICSNVPROC) (GLbitfield metricQueryMask, GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLsizei stride, GLfloat *metrics); +typedef void (APIENTRYP PFNGLGETPATHMETRICRANGENVPROC) (GLbitfield metricQueryMask, GLuint firstPathName, GLsizei numPaths, GLsizei stride, GLfloat *metrics); +typedef void (APIENTRYP PFNGLGETPATHSPACINGNVPROC) (GLenum pathListMode, GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLfloat advanceScale, GLfloat kerningScale, GLenum transformType, GLfloat *returnedSpacing); +typedef GLboolean (APIENTRYP PFNGLISPOINTINFILLPATHNVPROC) (GLuint path, GLuint mask, GLfloat x, GLfloat y); +typedef GLboolean (APIENTRYP PFNGLISPOINTINSTROKEPATHNVPROC) (GLuint path, GLfloat x, GLfloat y); +typedef GLfloat (APIENTRYP PFNGLGETPATHLENGTHNVPROC) (GLuint path, GLsizei startSegment, GLsizei numSegments); +typedef GLboolean (APIENTRYP PFNGLPOINTALONGPATHNVPROC) (GLuint path, GLsizei startSegment, GLsizei numSegments, GLfloat distance, GLfloat *x, GLfloat *y, GLfloat *tangentX, GLfloat *tangentY); +typedef void (APIENTRYP PFNGLMATRIXLOAD3X2FNVPROC) (GLenum matrixMode, const GLfloat *m); +typedef void (APIENTRYP PFNGLMATRIXLOAD3X3FNVPROC) (GLenum matrixMode, const GLfloat *m); +typedef void (APIENTRYP PFNGLMATRIXLOADTRANSPOSE3X3FNVPROC) (GLenum matrixMode, const GLfloat *m); +typedef void (APIENTRYP PFNGLMATRIXMULT3X2FNVPROC) (GLenum matrixMode, const GLfloat *m); +typedef void (APIENTRYP PFNGLMATRIXMULT3X3FNVPROC) (GLenum matrixMode, const GLfloat *m); +typedef void (APIENTRYP PFNGLMATRIXMULTTRANSPOSE3X3FNVPROC) (GLenum matrixMode, const GLfloat *m); +typedef void (APIENTRYP PFNGLSTENCILTHENCOVERFILLPATHNVPROC) (GLuint path, GLenum fillMode, GLuint mask, GLenum coverMode); +typedef void (APIENTRYP PFNGLSTENCILTHENCOVERSTROKEPATHNVPROC) (GLuint path, GLint reference, GLuint mask, GLenum coverMode); +typedef void (APIENTRYP PFNGLSTENCILTHENCOVERFILLPATHINSTANCEDNVPROC) (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLenum fillMode, GLuint mask, GLenum coverMode, GLenum transformType, const GLfloat *transformValues); +typedef void (APIENTRYP PFNGLSTENCILTHENCOVERSTROKEPATHINSTANCEDNVPROC) (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLint reference, GLuint mask, GLenum coverMode, GLenum transformType, const GLfloat *transformValues); +typedef GLenum (APIENTRYP PFNGLPATHGLYPHINDEXRANGENVPROC) (GLenum fontTarget, const void *fontName, GLbitfield fontStyle, GLuint pathParameterTemplate, GLfloat emScale, GLuint *baseAndCount); +typedef GLenum (APIENTRYP PFNGLPATHGLYPHINDEXARRAYNVPROC) (GLuint firstPathName, GLenum fontTarget, const void *fontName, GLbitfield fontStyle, GLuint firstGlyphIndex, GLsizei numGlyphs, GLuint pathParameterTemplate, GLfloat emScale); +typedef GLenum (APIENTRYP PFNGLPATHMEMORYGLYPHINDEXARRAYNVPROC) (GLuint firstPathName, GLenum fontTarget, GLsizeiptr fontSize, const void *fontData, GLsizei faceIndex, GLuint firstGlyphIndex, GLsizei numGlyphs, GLuint pathParameterTemplate, GLfloat emScale); +typedef void (APIENTRYP PFNGLPROGRAMPATHFRAGMENTINPUTGENNVPROC) (GLuint program, GLint location, GLenum genMode, GLint components, const GLfloat *coeffs); +typedef void (APIENTRYP PFNGLGETPROGRAMRESOURCEFVNVPROC) (GLuint program, GLenum programInterface, GLuint index, GLsizei propCount, const GLenum *props, GLsizei count, GLsizei *length, GLfloat *params); +typedef void (APIENTRYP PFNGLPATHCOLORGENNVPROC) (GLenum color, GLenum genMode, GLenum colorFormat, const GLfloat *coeffs); +typedef void (APIENTRYP PFNGLPATHTEXGENNVPROC) (GLenum texCoordSet, GLenum genMode, GLint components, const GLfloat *coeffs); +typedef void (APIENTRYP PFNGLPATHFOGGENNVPROC) (GLenum genMode); +typedef void (APIENTRYP PFNGLGETPATHCOLORGENIVNVPROC) (GLenum color, GLenum pname, GLint *value); +typedef void (APIENTRYP PFNGLGETPATHCOLORGENFVNVPROC) (GLenum color, GLenum pname, GLfloat *value); +typedef void (APIENTRYP PFNGLGETPATHTEXGENIVNVPROC) (GLenum texCoordSet, GLenum pname, GLint *value); +typedef void (APIENTRYP PFNGLGETPATHTEXGENFVNVPROC) (GLenum texCoordSet, GLenum pname, GLfloat *value); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLuint APIENTRY glGenPathsNV (GLsizei range); +GLAPI void APIENTRY glDeletePathsNV (GLuint path, GLsizei range); +GLAPI GLboolean APIENTRY glIsPathNV (GLuint path); +GLAPI void APIENTRY glPathCommandsNV (GLuint path, GLsizei numCommands, const GLubyte *commands, GLsizei numCoords, GLenum coordType, const void *coords); +GLAPI void APIENTRY glPathCoordsNV (GLuint path, GLsizei numCoords, GLenum coordType, const void *coords); +GLAPI void APIENTRY glPathSubCommandsNV (GLuint path, GLsizei commandStart, GLsizei commandsToDelete, GLsizei numCommands, const GLubyte *commands, GLsizei numCoords, GLenum coordType, const void *coords); +GLAPI void APIENTRY glPathSubCoordsNV (GLuint path, GLsizei coordStart, GLsizei numCoords, GLenum coordType, const void *coords); +GLAPI void APIENTRY glPathStringNV (GLuint path, GLenum format, GLsizei length, const void *pathString); +GLAPI void APIENTRY glPathGlyphsNV (GLuint firstPathName, GLenum fontTarget, const void *fontName, GLbitfield fontStyle, GLsizei numGlyphs, GLenum type, const void *charcodes, GLenum handleMissingGlyphs, GLuint pathParameterTemplate, GLfloat emScale); +GLAPI void APIENTRY glPathGlyphRangeNV (GLuint firstPathName, GLenum fontTarget, const void *fontName, GLbitfield fontStyle, GLuint firstGlyph, GLsizei numGlyphs, GLenum handleMissingGlyphs, GLuint pathParameterTemplate, GLfloat emScale); +GLAPI void APIENTRY glWeightPathsNV (GLuint resultPath, GLsizei numPaths, const GLuint *paths, const GLfloat *weights); +GLAPI void APIENTRY glCopyPathNV (GLuint resultPath, GLuint srcPath); +GLAPI void APIENTRY glInterpolatePathsNV (GLuint resultPath, GLuint pathA, GLuint pathB, GLfloat weight); +GLAPI void APIENTRY glTransformPathNV (GLuint resultPath, GLuint srcPath, GLenum transformType, const GLfloat *transformValues); +GLAPI void APIENTRY glPathParameterivNV (GLuint path, GLenum pname, const GLint *value); +GLAPI void APIENTRY glPathParameteriNV (GLuint path, GLenum pname, GLint value); +GLAPI void APIENTRY glPathParameterfvNV (GLuint path, GLenum pname, const GLfloat *value); +GLAPI void APIENTRY glPathParameterfNV (GLuint path, GLenum pname, GLfloat value); +GLAPI void APIENTRY glPathDashArrayNV (GLuint path, GLsizei dashCount, const GLfloat *dashArray); +GLAPI void APIENTRY glPathStencilFuncNV (GLenum func, GLint ref, GLuint mask); +GLAPI void APIENTRY glPathStencilDepthOffsetNV (GLfloat factor, GLfloat units); +GLAPI void APIENTRY glStencilFillPathNV (GLuint path, GLenum fillMode, GLuint mask); +GLAPI void APIENTRY glStencilStrokePathNV (GLuint path, GLint reference, GLuint mask); +GLAPI void APIENTRY glStencilFillPathInstancedNV (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLenum fillMode, GLuint mask, GLenum transformType, const GLfloat *transformValues); +GLAPI void APIENTRY glStencilStrokePathInstancedNV (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLint reference, GLuint mask, GLenum transformType, const GLfloat *transformValues); +GLAPI void APIENTRY glPathCoverDepthFuncNV (GLenum func); +GLAPI void APIENTRY glCoverFillPathNV (GLuint path, GLenum coverMode); +GLAPI void APIENTRY glCoverStrokePathNV (GLuint path, GLenum coverMode); +GLAPI void APIENTRY glCoverFillPathInstancedNV (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLenum coverMode, GLenum transformType, const GLfloat *transformValues); +GLAPI void APIENTRY glCoverStrokePathInstancedNV (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLenum coverMode, GLenum transformType, const GLfloat *transformValues); +GLAPI void APIENTRY glGetPathParameterivNV (GLuint path, GLenum pname, GLint *value); +GLAPI void APIENTRY glGetPathParameterfvNV (GLuint path, GLenum pname, GLfloat *value); +GLAPI void APIENTRY glGetPathCommandsNV (GLuint path, GLubyte *commands); +GLAPI void APIENTRY glGetPathCoordsNV (GLuint path, GLfloat *coords); +GLAPI void APIENTRY glGetPathDashArrayNV (GLuint path, GLfloat *dashArray); +GLAPI void APIENTRY glGetPathMetricsNV (GLbitfield metricQueryMask, GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLsizei stride, GLfloat *metrics); +GLAPI void APIENTRY glGetPathMetricRangeNV (GLbitfield metricQueryMask, GLuint firstPathName, GLsizei numPaths, GLsizei stride, GLfloat *metrics); +GLAPI void APIENTRY glGetPathSpacingNV (GLenum pathListMode, GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLfloat advanceScale, GLfloat kerningScale, GLenum transformType, GLfloat *returnedSpacing); +GLAPI GLboolean APIENTRY glIsPointInFillPathNV (GLuint path, GLuint mask, GLfloat x, GLfloat y); +GLAPI GLboolean APIENTRY glIsPointInStrokePathNV (GLuint path, GLfloat x, GLfloat y); +GLAPI GLfloat APIENTRY glGetPathLengthNV (GLuint path, GLsizei startSegment, GLsizei numSegments); +GLAPI GLboolean APIENTRY glPointAlongPathNV (GLuint path, GLsizei startSegment, GLsizei numSegments, GLfloat distance, GLfloat *x, GLfloat *y, GLfloat *tangentX, GLfloat *tangentY); +GLAPI void APIENTRY glMatrixLoad3x2fNV (GLenum matrixMode, const GLfloat *m); +GLAPI void APIENTRY glMatrixLoad3x3fNV (GLenum matrixMode, const GLfloat *m); +GLAPI void APIENTRY glMatrixLoadTranspose3x3fNV (GLenum matrixMode, const GLfloat *m); +GLAPI void APIENTRY glMatrixMult3x2fNV (GLenum matrixMode, const GLfloat *m); +GLAPI void APIENTRY glMatrixMult3x3fNV (GLenum matrixMode, const GLfloat *m); +GLAPI void APIENTRY glMatrixMultTranspose3x3fNV (GLenum matrixMode, const GLfloat *m); +GLAPI void APIENTRY glStencilThenCoverFillPathNV (GLuint path, GLenum fillMode, GLuint mask, GLenum coverMode); +GLAPI void APIENTRY glStencilThenCoverStrokePathNV (GLuint path, GLint reference, GLuint mask, GLenum coverMode); +GLAPI void APIENTRY glStencilThenCoverFillPathInstancedNV (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLenum fillMode, GLuint mask, GLenum coverMode, GLenum transformType, const GLfloat *transformValues); +GLAPI void APIENTRY glStencilThenCoverStrokePathInstancedNV (GLsizei numPaths, GLenum pathNameType, const void *paths, GLuint pathBase, GLint reference, GLuint mask, GLenum coverMode, GLenum transformType, const GLfloat *transformValues); +GLAPI GLenum APIENTRY glPathGlyphIndexRangeNV (GLenum fontTarget, const void *fontName, GLbitfield fontStyle, GLuint pathParameterTemplate, GLfloat emScale, GLuint *baseAndCount); +GLAPI GLenum APIENTRY glPathGlyphIndexArrayNV (GLuint firstPathName, GLenum fontTarget, const void *fontName, GLbitfield fontStyle, GLuint firstGlyphIndex, GLsizei numGlyphs, GLuint pathParameterTemplate, GLfloat emScale); +GLAPI GLenum APIENTRY glPathMemoryGlyphIndexArrayNV (GLuint firstPathName, GLenum fontTarget, GLsizeiptr fontSize, const void *fontData, GLsizei faceIndex, GLuint firstGlyphIndex, GLsizei numGlyphs, GLuint pathParameterTemplate, GLfloat emScale); +GLAPI void APIENTRY glProgramPathFragmentInputGenNV (GLuint program, GLint location, GLenum genMode, GLint components, const GLfloat *coeffs); +GLAPI void APIENTRY glGetProgramResourcefvNV (GLuint program, GLenum programInterface, GLuint index, GLsizei propCount, const GLenum *props, GLsizei count, GLsizei *length, GLfloat *params); +GLAPI void APIENTRY glPathColorGenNV (GLenum color, GLenum genMode, GLenum colorFormat, const GLfloat *coeffs); +GLAPI void APIENTRY glPathTexGenNV (GLenum texCoordSet, GLenum genMode, GLint components, const GLfloat *coeffs); +GLAPI void APIENTRY glPathFogGenNV (GLenum genMode); +GLAPI void APIENTRY glGetPathColorGenivNV (GLenum color, GLenum pname, GLint *value); +GLAPI void APIENTRY glGetPathColorGenfvNV (GLenum color, GLenum pname, GLfloat *value); +GLAPI void APIENTRY glGetPathTexGenivNV (GLenum texCoordSet, GLenum pname, GLint *value); +GLAPI void APIENTRY glGetPathTexGenfvNV (GLenum texCoordSet, GLenum pname, GLfloat *value); +#endif +#endif /* GL_NV_path_rendering */ + +#ifndef GL_NV_path_rendering_shared_edge +#define GL_NV_path_rendering_shared_edge 1 +#define GL_SHARED_EDGE_NV 0xC0 +#endif /* GL_NV_path_rendering_shared_edge */ + +#ifndef GL_NV_pixel_data_range +#define GL_NV_pixel_data_range 1 +#define GL_WRITE_PIXEL_DATA_RANGE_NV 0x8878 +#define GL_READ_PIXEL_DATA_RANGE_NV 0x8879 +#define GL_WRITE_PIXEL_DATA_RANGE_LENGTH_NV 0x887A +#define GL_READ_PIXEL_DATA_RANGE_LENGTH_NV 0x887B +#define GL_WRITE_PIXEL_DATA_RANGE_POINTER_NV 0x887C +#define GL_READ_PIXEL_DATA_RANGE_POINTER_NV 0x887D +typedef void (APIENTRYP PFNGLPIXELDATARANGENVPROC) (GLenum target, GLsizei length, const void *pointer); +typedef void (APIENTRYP PFNGLFLUSHPIXELDATARANGENVPROC) (GLenum target); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPixelDataRangeNV (GLenum target, GLsizei length, const void *pointer); +GLAPI void APIENTRY glFlushPixelDataRangeNV (GLenum target); +#endif +#endif /* GL_NV_pixel_data_range */ + +#ifndef GL_NV_point_sprite +#define GL_NV_point_sprite 1 +#define GL_POINT_SPRITE_NV 0x8861 +#define GL_COORD_REPLACE_NV 0x8862 +#define GL_POINT_SPRITE_R_MODE_NV 0x8863 +typedef void (APIENTRYP PFNGLPOINTPARAMETERINVPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERIVNVPROC) (GLenum pname, const GLint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPointParameteriNV (GLenum pname, GLint param); +GLAPI void APIENTRY glPointParameterivNV (GLenum pname, const GLint *params); +#endif +#endif /* GL_NV_point_sprite */ + +#ifndef GL_NV_present_video +#define GL_NV_present_video 1 +#define GL_FRAME_NV 0x8E26 +#define GL_FIELDS_NV 0x8E27 +#define GL_CURRENT_TIME_NV 0x8E28 +#define GL_NUM_FILL_STREAMS_NV 0x8E29 +#define GL_PRESENT_TIME_NV 0x8E2A +#define GL_PRESENT_DURATION_NV 0x8E2B +typedef void (APIENTRYP PFNGLPRESENTFRAMEKEYEDNVPROC) (GLuint video_slot, GLuint64EXT minPresentTime, GLuint beginPresentTimeId, GLuint presentDurationId, GLenum type, GLenum target0, GLuint fill0, GLuint key0, GLenum target1, GLuint fill1, GLuint key1); +typedef void (APIENTRYP PFNGLPRESENTFRAMEDUALFILLNVPROC) (GLuint video_slot, GLuint64EXT minPresentTime, GLuint beginPresentTimeId, GLuint presentDurationId, GLenum type, GLenum target0, GLuint fill0, GLenum target1, GLuint fill1, GLenum target2, GLuint fill2, GLenum target3, GLuint fill3); +typedef void (APIENTRYP PFNGLGETVIDEOIVNVPROC) (GLuint video_slot, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVIDEOUIVNVPROC) (GLuint video_slot, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLGETVIDEOI64VNVPROC) (GLuint video_slot, GLenum pname, GLint64EXT *params); +typedef void (APIENTRYP PFNGLGETVIDEOUI64VNVPROC) (GLuint video_slot, GLenum pname, GLuint64EXT *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPresentFrameKeyedNV (GLuint video_slot, GLuint64EXT minPresentTime, GLuint beginPresentTimeId, GLuint presentDurationId, GLenum type, GLenum target0, GLuint fill0, GLuint key0, GLenum target1, GLuint fill1, GLuint key1); +GLAPI void APIENTRY glPresentFrameDualFillNV (GLuint video_slot, GLuint64EXT minPresentTime, GLuint beginPresentTimeId, GLuint presentDurationId, GLenum type, GLenum target0, GLuint fill0, GLenum target1, GLuint fill1, GLenum target2, GLuint fill2, GLenum target3, GLuint fill3); +GLAPI void APIENTRY glGetVideoivNV (GLuint video_slot, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVideouivNV (GLuint video_slot, GLenum pname, GLuint *params); +GLAPI void APIENTRY glGetVideoi64vNV (GLuint video_slot, GLenum pname, GLint64EXT *params); +GLAPI void APIENTRY glGetVideoui64vNV (GLuint video_slot, GLenum pname, GLuint64EXT *params); +#endif +#endif /* GL_NV_present_video */ + +#ifndef GL_NV_primitive_restart +#define GL_NV_primitive_restart 1 +#define GL_PRIMITIVE_RESTART_NV 0x8558 +#define GL_PRIMITIVE_RESTART_INDEX_NV 0x8559 +typedef void (APIENTRYP PFNGLPRIMITIVERESTARTNVPROC) (void); +typedef void (APIENTRYP PFNGLPRIMITIVERESTARTINDEXNVPROC) (GLuint index); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPrimitiveRestartNV (void); +GLAPI void APIENTRY glPrimitiveRestartIndexNV (GLuint index); +#endif +#endif /* GL_NV_primitive_restart */ + +#ifndef GL_NV_primitive_shading_rate +#define GL_NV_primitive_shading_rate 1 +#define GL_SHADING_RATE_IMAGE_PER_PRIMITIVE_NV 0x95B1 +#define GL_SHADING_RATE_IMAGE_PALETTE_COUNT_NV 0x95B2 +#endif /* GL_NV_primitive_shading_rate */ + +#ifndef GL_NV_query_resource +#define GL_NV_query_resource 1 +#define GL_QUERY_RESOURCE_TYPE_VIDMEM_ALLOC_NV 0x9540 +#define GL_QUERY_RESOURCE_MEMTYPE_VIDMEM_NV 0x9542 +#define GL_QUERY_RESOURCE_SYS_RESERVED_NV 0x9544 +#define GL_QUERY_RESOURCE_TEXTURE_NV 0x9545 +#define GL_QUERY_RESOURCE_RENDERBUFFER_NV 0x9546 +#define GL_QUERY_RESOURCE_BUFFEROBJECT_NV 0x9547 +typedef GLint (APIENTRYP PFNGLQUERYRESOURCENVPROC) (GLenum queryType, GLint tagId, GLuint count, GLint *buffer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLint APIENTRY glQueryResourceNV (GLenum queryType, GLint tagId, GLuint count, GLint *buffer); +#endif +#endif /* GL_NV_query_resource */ + +#ifndef GL_NV_query_resource_tag +#define GL_NV_query_resource_tag 1 +typedef void (APIENTRYP PFNGLGENQUERYRESOURCETAGNVPROC) (GLsizei n, GLint *tagIds); +typedef void (APIENTRYP PFNGLDELETEQUERYRESOURCETAGNVPROC) (GLsizei n, const GLint *tagIds); +typedef void (APIENTRYP PFNGLQUERYRESOURCETAGNVPROC) (GLint tagId, const GLchar *tagString); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenQueryResourceTagNV (GLsizei n, GLint *tagIds); +GLAPI void APIENTRY glDeleteQueryResourceTagNV (GLsizei n, const GLint *tagIds); +GLAPI void APIENTRY glQueryResourceTagNV (GLint tagId, const GLchar *tagString); +#endif +#endif /* GL_NV_query_resource_tag */ + +#ifndef GL_NV_register_combiners +#define GL_NV_register_combiners 1 +#define GL_REGISTER_COMBINERS_NV 0x8522 +#define GL_VARIABLE_A_NV 0x8523 +#define GL_VARIABLE_B_NV 0x8524 +#define GL_VARIABLE_C_NV 0x8525 +#define GL_VARIABLE_D_NV 0x8526 +#define GL_VARIABLE_E_NV 0x8527 +#define GL_VARIABLE_F_NV 0x8528 +#define GL_VARIABLE_G_NV 0x8529 +#define GL_CONSTANT_COLOR0_NV 0x852A +#define GL_CONSTANT_COLOR1_NV 0x852B +#define GL_SPARE0_NV 0x852E +#define GL_SPARE1_NV 0x852F +#define GL_DISCARD_NV 0x8530 +#define GL_E_TIMES_F_NV 0x8531 +#define GL_SPARE0_PLUS_SECONDARY_COLOR_NV 0x8532 +#define GL_UNSIGNED_IDENTITY_NV 0x8536 +#define GL_UNSIGNED_INVERT_NV 0x8537 +#define GL_EXPAND_NORMAL_NV 0x8538 +#define GL_EXPAND_NEGATE_NV 0x8539 +#define GL_HALF_BIAS_NORMAL_NV 0x853A +#define GL_HALF_BIAS_NEGATE_NV 0x853B +#define GL_SIGNED_IDENTITY_NV 0x853C +#define GL_SIGNED_NEGATE_NV 0x853D +#define GL_SCALE_BY_TWO_NV 0x853E +#define GL_SCALE_BY_FOUR_NV 0x853F +#define GL_SCALE_BY_ONE_HALF_NV 0x8540 +#define GL_BIAS_BY_NEGATIVE_ONE_HALF_NV 0x8541 +#define GL_COMBINER_INPUT_NV 0x8542 +#define GL_COMBINER_MAPPING_NV 0x8543 +#define GL_COMBINER_COMPONENT_USAGE_NV 0x8544 +#define GL_COMBINER_AB_DOT_PRODUCT_NV 0x8545 +#define GL_COMBINER_CD_DOT_PRODUCT_NV 0x8546 +#define GL_COMBINER_MUX_SUM_NV 0x8547 +#define GL_COMBINER_SCALE_NV 0x8548 +#define GL_COMBINER_BIAS_NV 0x8549 +#define GL_COMBINER_AB_OUTPUT_NV 0x854A +#define GL_COMBINER_CD_OUTPUT_NV 0x854B +#define GL_COMBINER_SUM_OUTPUT_NV 0x854C +#define GL_MAX_GENERAL_COMBINERS_NV 0x854D +#define GL_NUM_GENERAL_COMBINERS_NV 0x854E +#define GL_COLOR_SUM_CLAMP_NV 0x854F +#define GL_COMBINER0_NV 0x8550 +#define GL_COMBINER1_NV 0x8551 +#define GL_COMBINER2_NV 0x8552 +#define GL_COMBINER3_NV 0x8553 +#define GL_COMBINER4_NV 0x8554 +#define GL_COMBINER5_NV 0x8555 +#define GL_COMBINER6_NV 0x8556 +#define GL_COMBINER7_NV 0x8557 +typedef void (APIENTRYP PFNGLCOMBINERPARAMETERFVNVPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCOMBINERPARAMETERFNVPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLCOMBINERPARAMETERIVNVPROC) (GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOMBINERPARAMETERINVPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLCOMBINERINPUTNVPROC) (GLenum stage, GLenum portion, GLenum variable, GLenum input, GLenum mapping, GLenum componentUsage); +typedef void (APIENTRYP PFNGLCOMBINEROUTPUTNVPROC) (GLenum stage, GLenum portion, GLenum abOutput, GLenum cdOutput, GLenum sumOutput, GLenum scale, GLenum bias, GLboolean abDotProduct, GLboolean cdDotProduct, GLboolean muxSum); +typedef void (APIENTRYP PFNGLFINALCOMBINERINPUTNVPROC) (GLenum variable, GLenum input, GLenum mapping, GLenum componentUsage); +typedef void (APIENTRYP PFNGLGETCOMBINERINPUTPARAMETERFVNVPROC) (GLenum stage, GLenum portion, GLenum variable, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOMBINERINPUTPARAMETERIVNVPROC) (GLenum stage, GLenum portion, GLenum variable, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETCOMBINEROUTPUTPARAMETERFVNVPROC) (GLenum stage, GLenum portion, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOMBINEROUTPUTPARAMETERIVNVPROC) (GLenum stage, GLenum portion, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETFINALCOMBINERINPUTPARAMETERFVNVPROC) (GLenum variable, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETFINALCOMBINERINPUTPARAMETERIVNVPROC) (GLenum variable, GLenum pname, GLint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCombinerParameterfvNV (GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glCombinerParameterfNV (GLenum pname, GLfloat param); +GLAPI void APIENTRY glCombinerParameterivNV (GLenum pname, const GLint *params); +GLAPI void APIENTRY glCombinerParameteriNV (GLenum pname, GLint param); +GLAPI void APIENTRY glCombinerInputNV (GLenum stage, GLenum portion, GLenum variable, GLenum input, GLenum mapping, GLenum componentUsage); +GLAPI void APIENTRY glCombinerOutputNV (GLenum stage, GLenum portion, GLenum abOutput, GLenum cdOutput, GLenum sumOutput, GLenum scale, GLenum bias, GLboolean abDotProduct, GLboolean cdDotProduct, GLboolean muxSum); +GLAPI void APIENTRY glFinalCombinerInputNV (GLenum variable, GLenum input, GLenum mapping, GLenum componentUsage); +GLAPI void APIENTRY glGetCombinerInputParameterfvNV (GLenum stage, GLenum portion, GLenum variable, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetCombinerInputParameterivNV (GLenum stage, GLenum portion, GLenum variable, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetCombinerOutputParameterfvNV (GLenum stage, GLenum portion, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetCombinerOutputParameterivNV (GLenum stage, GLenum portion, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetFinalCombinerInputParameterfvNV (GLenum variable, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetFinalCombinerInputParameterivNV (GLenum variable, GLenum pname, GLint *params); +#endif +#endif /* GL_NV_register_combiners */ + +#ifndef GL_NV_register_combiners2 +#define GL_NV_register_combiners2 1 +#define GL_PER_STAGE_CONSTANTS_NV 0x8535 +typedef void (APIENTRYP PFNGLCOMBINERSTAGEPARAMETERFVNVPROC) (GLenum stage, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOMBINERSTAGEPARAMETERFVNVPROC) (GLenum stage, GLenum pname, GLfloat *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCombinerStageParameterfvNV (GLenum stage, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glGetCombinerStageParameterfvNV (GLenum stage, GLenum pname, GLfloat *params); +#endif +#endif /* GL_NV_register_combiners2 */ + +#ifndef GL_NV_representative_fragment_test +#define GL_NV_representative_fragment_test 1 +#define GL_REPRESENTATIVE_FRAGMENT_TEST_NV 0x937F +#endif /* GL_NV_representative_fragment_test */ + +#ifndef GL_NV_robustness_video_memory_purge +#define GL_NV_robustness_video_memory_purge 1 +#define GL_PURGED_CONTEXT_RESET_NV 0x92BB +#endif /* GL_NV_robustness_video_memory_purge */ + +#ifndef GL_NV_sample_locations +#define GL_NV_sample_locations 1 +#define GL_SAMPLE_LOCATION_SUBPIXEL_BITS_NV 0x933D +#define GL_SAMPLE_LOCATION_PIXEL_GRID_WIDTH_NV 0x933E +#define GL_SAMPLE_LOCATION_PIXEL_GRID_HEIGHT_NV 0x933F +#define GL_PROGRAMMABLE_SAMPLE_LOCATION_TABLE_SIZE_NV 0x9340 +#define GL_SAMPLE_LOCATION_NV 0x8E50 +#define GL_PROGRAMMABLE_SAMPLE_LOCATION_NV 0x9341 +#define GL_FRAMEBUFFER_PROGRAMMABLE_SAMPLE_LOCATIONS_NV 0x9342 +#define GL_FRAMEBUFFER_SAMPLE_LOCATION_PIXEL_GRID_NV 0x9343 +typedef void (APIENTRYP PFNGLFRAMEBUFFERSAMPLELOCATIONSFVNVPROC) (GLenum target, GLuint start, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERSAMPLELOCATIONSFVNVPROC) (GLuint framebuffer, GLuint start, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLRESOLVEDEPTHVALUESNVPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFramebufferSampleLocationsfvNV (GLenum target, GLuint start, GLsizei count, const GLfloat *v); +GLAPI void APIENTRY glNamedFramebufferSampleLocationsfvNV (GLuint framebuffer, GLuint start, GLsizei count, const GLfloat *v); +GLAPI void APIENTRY glResolveDepthValuesNV (void); +#endif +#endif /* GL_NV_sample_locations */ + +#ifndef GL_NV_sample_mask_override_coverage +#define GL_NV_sample_mask_override_coverage 1 +#endif /* GL_NV_sample_mask_override_coverage */ + +#ifndef GL_NV_scissor_exclusive +#define GL_NV_scissor_exclusive 1 +#define GL_SCISSOR_TEST_EXCLUSIVE_NV 0x9555 +#define GL_SCISSOR_BOX_EXCLUSIVE_NV 0x9556 +typedef void (APIENTRYP PFNGLSCISSOREXCLUSIVENVPROC) (GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLSCISSOREXCLUSIVEARRAYVNVPROC) (GLuint first, GLsizei count, const GLint *v); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glScissorExclusiveNV (GLint x, GLint y, GLsizei width, GLsizei height); +GLAPI void APIENTRY glScissorExclusiveArrayvNV (GLuint first, GLsizei count, const GLint *v); +#endif +#endif /* GL_NV_scissor_exclusive */ + +#ifndef GL_NV_shader_atomic_counters +#define GL_NV_shader_atomic_counters 1 +#endif /* GL_NV_shader_atomic_counters */ + +#ifndef GL_NV_shader_atomic_float +#define GL_NV_shader_atomic_float 1 +#endif /* GL_NV_shader_atomic_float */ + +#ifndef GL_NV_shader_atomic_float64 +#define GL_NV_shader_atomic_float64 1 +#endif /* GL_NV_shader_atomic_float64 */ + +#ifndef GL_NV_shader_atomic_fp16_vector +#define GL_NV_shader_atomic_fp16_vector 1 +#endif /* GL_NV_shader_atomic_fp16_vector */ + +#ifndef GL_NV_shader_atomic_int64 +#define GL_NV_shader_atomic_int64 1 +#endif /* GL_NV_shader_atomic_int64 */ + +#ifndef GL_NV_shader_buffer_load +#define GL_NV_shader_buffer_load 1 +#define GL_BUFFER_GPU_ADDRESS_NV 0x8F1D +#define GL_GPU_ADDRESS_NV 0x8F34 +#define GL_MAX_SHADER_BUFFER_ADDRESS_NV 0x8F35 +typedef void (APIENTRYP PFNGLMAKEBUFFERRESIDENTNVPROC) (GLenum target, GLenum access); +typedef void (APIENTRYP PFNGLMAKEBUFFERNONRESIDENTNVPROC) (GLenum target); +typedef GLboolean (APIENTRYP PFNGLISBUFFERRESIDENTNVPROC) (GLenum target); +typedef void (APIENTRYP PFNGLMAKENAMEDBUFFERRESIDENTNVPROC) (GLuint buffer, GLenum access); +typedef void (APIENTRYP PFNGLMAKENAMEDBUFFERNONRESIDENTNVPROC) (GLuint buffer); +typedef GLboolean (APIENTRYP PFNGLISNAMEDBUFFERRESIDENTNVPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLGETBUFFERPARAMETERUI64VNVPROC) (GLenum target, GLenum pname, GLuint64EXT *params); +typedef void (APIENTRYP PFNGLGETNAMEDBUFFERPARAMETERUI64VNVPROC) (GLuint buffer, GLenum pname, GLuint64EXT *params); +typedef void (APIENTRYP PFNGLGETINTEGERUI64VNVPROC) (GLenum value, GLuint64EXT *result); +typedef void (APIENTRYP PFNGLUNIFORMUI64NVPROC) (GLint location, GLuint64EXT value); +typedef void (APIENTRYP PFNGLUNIFORMUI64VNVPROC) (GLint location, GLsizei count, const GLuint64EXT *value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMUI64NVPROC) (GLuint program, GLint location, GLuint64EXT value); +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMUI64VNVPROC) (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMakeBufferResidentNV (GLenum target, GLenum access); +GLAPI void APIENTRY glMakeBufferNonResidentNV (GLenum target); +GLAPI GLboolean APIENTRY glIsBufferResidentNV (GLenum target); +GLAPI void APIENTRY glMakeNamedBufferResidentNV (GLuint buffer, GLenum access); +GLAPI void APIENTRY glMakeNamedBufferNonResidentNV (GLuint buffer); +GLAPI GLboolean APIENTRY glIsNamedBufferResidentNV (GLuint buffer); +GLAPI void APIENTRY glGetBufferParameterui64vNV (GLenum target, GLenum pname, GLuint64EXT *params); +GLAPI void APIENTRY glGetNamedBufferParameterui64vNV (GLuint buffer, GLenum pname, GLuint64EXT *params); +GLAPI void APIENTRY glGetIntegerui64vNV (GLenum value, GLuint64EXT *result); +GLAPI void APIENTRY glUniformui64NV (GLint location, GLuint64EXT value); +GLAPI void APIENTRY glUniformui64vNV (GLint location, GLsizei count, const GLuint64EXT *value); +GLAPI void APIENTRY glProgramUniformui64NV (GLuint program, GLint location, GLuint64EXT value); +GLAPI void APIENTRY glProgramUniformui64vNV (GLuint program, GLint location, GLsizei count, const GLuint64EXT *value); +#endif +#endif /* GL_NV_shader_buffer_load */ + +#ifndef GL_NV_shader_buffer_store +#define GL_NV_shader_buffer_store 1 +#define GL_SHADER_GLOBAL_ACCESS_BARRIER_BIT_NV 0x00000010 +#endif /* GL_NV_shader_buffer_store */ + +#ifndef GL_NV_shader_storage_buffer_object +#define GL_NV_shader_storage_buffer_object 1 +#endif /* GL_NV_shader_storage_buffer_object */ + +#ifndef GL_NV_shader_subgroup_partitioned +#define GL_NV_shader_subgroup_partitioned 1 +#define GL_SUBGROUP_FEATURE_PARTITIONED_BIT_NV 0x00000100 +#endif /* GL_NV_shader_subgroup_partitioned */ + +#ifndef GL_NV_shader_texture_footprint +#define GL_NV_shader_texture_footprint 1 +#endif /* GL_NV_shader_texture_footprint */ + +#ifndef GL_NV_shader_thread_group +#define GL_NV_shader_thread_group 1 +#define GL_WARP_SIZE_NV 0x9339 +#define GL_WARPS_PER_SM_NV 0x933A +#define GL_SM_COUNT_NV 0x933B +#endif /* GL_NV_shader_thread_group */ + +#ifndef GL_NV_shader_thread_shuffle +#define GL_NV_shader_thread_shuffle 1 +#endif /* GL_NV_shader_thread_shuffle */ + +#ifndef GL_NV_shading_rate_image +#define GL_NV_shading_rate_image 1 +#define GL_SHADING_RATE_IMAGE_NV 0x9563 +#define GL_SHADING_RATE_NO_INVOCATIONS_NV 0x9564 +#define GL_SHADING_RATE_1_INVOCATION_PER_PIXEL_NV 0x9565 +#define GL_SHADING_RATE_1_INVOCATION_PER_1X2_PIXELS_NV 0x9566 +#define GL_SHADING_RATE_1_INVOCATION_PER_2X1_PIXELS_NV 0x9567 +#define GL_SHADING_RATE_1_INVOCATION_PER_2X2_PIXELS_NV 0x9568 +#define GL_SHADING_RATE_1_INVOCATION_PER_2X4_PIXELS_NV 0x9569 +#define GL_SHADING_RATE_1_INVOCATION_PER_4X2_PIXELS_NV 0x956A +#define GL_SHADING_RATE_1_INVOCATION_PER_4X4_PIXELS_NV 0x956B +#define GL_SHADING_RATE_2_INVOCATIONS_PER_PIXEL_NV 0x956C +#define GL_SHADING_RATE_4_INVOCATIONS_PER_PIXEL_NV 0x956D +#define GL_SHADING_RATE_8_INVOCATIONS_PER_PIXEL_NV 0x956E +#define GL_SHADING_RATE_16_INVOCATIONS_PER_PIXEL_NV 0x956F +#define GL_SHADING_RATE_IMAGE_BINDING_NV 0x955B +#define GL_SHADING_RATE_IMAGE_TEXEL_WIDTH_NV 0x955C +#define GL_SHADING_RATE_IMAGE_TEXEL_HEIGHT_NV 0x955D +#define GL_SHADING_RATE_IMAGE_PALETTE_SIZE_NV 0x955E +#define GL_MAX_COARSE_FRAGMENT_SAMPLES_NV 0x955F +#define GL_SHADING_RATE_SAMPLE_ORDER_DEFAULT_NV 0x95AE +#define GL_SHADING_RATE_SAMPLE_ORDER_PIXEL_MAJOR_NV 0x95AF +#define GL_SHADING_RATE_SAMPLE_ORDER_SAMPLE_MAJOR_NV 0x95B0 +typedef void (APIENTRYP PFNGLBINDSHADINGRATEIMAGENVPROC) (GLuint texture); +typedef void (APIENTRYP PFNGLGETSHADINGRATEIMAGEPALETTENVPROC) (GLuint viewport, GLuint entry, GLenum *rate); +typedef void (APIENTRYP PFNGLGETSHADINGRATESAMPLELOCATIONIVNVPROC) (GLenum rate, GLuint samples, GLuint index, GLint *location); +typedef void (APIENTRYP PFNGLSHADINGRATEIMAGEBARRIERNVPROC) (GLboolean synchronize); +typedef void (APIENTRYP PFNGLSHADINGRATEIMAGEPALETTENVPROC) (GLuint viewport, GLuint first, GLsizei count, const GLenum *rates); +typedef void (APIENTRYP PFNGLSHADINGRATESAMPLEORDERNVPROC) (GLenum order); +typedef void (APIENTRYP PFNGLSHADINGRATESAMPLEORDERCUSTOMNVPROC) (GLenum rate, GLuint samples, const GLint *locations); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindShadingRateImageNV (GLuint texture); +GLAPI void APIENTRY glGetShadingRateImagePaletteNV (GLuint viewport, GLuint entry, GLenum *rate); +GLAPI void APIENTRY glGetShadingRateSampleLocationivNV (GLenum rate, GLuint samples, GLuint index, GLint *location); +GLAPI void APIENTRY glShadingRateImageBarrierNV (GLboolean synchronize); +GLAPI void APIENTRY glShadingRateImagePaletteNV (GLuint viewport, GLuint first, GLsizei count, const GLenum *rates); +GLAPI void APIENTRY glShadingRateSampleOrderNV (GLenum order); +GLAPI void APIENTRY glShadingRateSampleOrderCustomNV (GLenum rate, GLuint samples, const GLint *locations); +#endif +#endif /* GL_NV_shading_rate_image */ + +#ifndef GL_NV_stereo_view_rendering +#define GL_NV_stereo_view_rendering 1 +#endif /* GL_NV_stereo_view_rendering */ + +#ifndef GL_NV_tessellation_program5 +#define GL_NV_tessellation_program5 1 +#define GL_MAX_PROGRAM_PATCH_ATTRIBS_NV 0x86D8 +#define GL_TESS_CONTROL_PROGRAM_NV 0x891E +#define GL_TESS_EVALUATION_PROGRAM_NV 0x891F +#define GL_TESS_CONTROL_PROGRAM_PARAMETER_BUFFER_NV 0x8C74 +#define GL_TESS_EVALUATION_PROGRAM_PARAMETER_BUFFER_NV 0x8C75 +#endif /* GL_NV_tessellation_program5 */ + +#ifndef GL_NV_texgen_emboss +#define GL_NV_texgen_emboss 1 +#define GL_EMBOSS_LIGHT_NV 0x855D +#define GL_EMBOSS_CONSTANT_NV 0x855E +#define GL_EMBOSS_MAP_NV 0x855F +#endif /* GL_NV_texgen_emboss */ + +#ifndef GL_NV_texgen_reflection +#define GL_NV_texgen_reflection 1 +#define GL_NORMAL_MAP_NV 0x8511 +#define GL_REFLECTION_MAP_NV 0x8512 +#endif /* GL_NV_texgen_reflection */ + +#ifndef GL_NV_texture_barrier +#define GL_NV_texture_barrier 1 +typedef void (APIENTRYP PFNGLTEXTUREBARRIERNVPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTextureBarrierNV (void); +#endif +#endif /* GL_NV_texture_barrier */ + +#ifndef GL_NV_texture_compression_vtc +#define GL_NV_texture_compression_vtc 1 +#endif /* GL_NV_texture_compression_vtc */ + +#ifndef GL_NV_texture_env_combine4 +#define GL_NV_texture_env_combine4 1 +#define GL_COMBINE4_NV 0x8503 +#define GL_SOURCE3_RGB_NV 0x8583 +#define GL_SOURCE3_ALPHA_NV 0x858B +#define GL_OPERAND3_RGB_NV 0x8593 +#define GL_OPERAND3_ALPHA_NV 0x859B +#endif /* GL_NV_texture_env_combine4 */ + +#ifndef GL_NV_texture_expand_normal +#define GL_NV_texture_expand_normal 1 +#define GL_TEXTURE_UNSIGNED_REMAP_MODE_NV 0x888F +#endif /* GL_NV_texture_expand_normal */ + +#ifndef GL_NV_texture_multisample +#define GL_NV_texture_multisample 1 +#define GL_TEXTURE_COVERAGE_SAMPLES_NV 0x9045 +#define GL_TEXTURE_COLOR_SAMPLES_NV 0x9046 +typedef void (APIENTRYP PFNGLTEXIMAGE2DMULTISAMPLECOVERAGENVPROC) (GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLint internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations); +typedef void (APIENTRYP PFNGLTEXIMAGE3DMULTISAMPLECOVERAGENVPROC) (GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations); +typedef void (APIENTRYP PFNGLTEXTUREIMAGE2DMULTISAMPLENVPROC) (GLuint texture, GLenum target, GLsizei samples, GLint internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations); +typedef void (APIENTRYP PFNGLTEXTUREIMAGE3DMULTISAMPLENVPROC) (GLuint texture, GLenum target, GLsizei samples, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations); +typedef void (APIENTRYP PFNGLTEXTUREIMAGE2DMULTISAMPLECOVERAGENVPROC) (GLuint texture, GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLint internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations); +typedef void (APIENTRYP PFNGLTEXTUREIMAGE3DMULTISAMPLECOVERAGENVPROC) (GLuint texture, GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexImage2DMultisampleCoverageNV (GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLint internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations); +GLAPI void APIENTRY glTexImage3DMultisampleCoverageNV (GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations); +GLAPI void APIENTRY glTextureImage2DMultisampleNV (GLuint texture, GLenum target, GLsizei samples, GLint internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations); +GLAPI void APIENTRY glTextureImage3DMultisampleNV (GLuint texture, GLenum target, GLsizei samples, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations); +GLAPI void APIENTRY glTextureImage2DMultisampleCoverageNV (GLuint texture, GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLint internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations); +GLAPI void APIENTRY glTextureImage3DMultisampleCoverageNV (GLuint texture, GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations); +#endif +#endif /* GL_NV_texture_multisample */ + +#ifndef GL_NV_texture_rectangle +#define GL_NV_texture_rectangle 1 +#define GL_TEXTURE_RECTANGLE_NV 0x84F5 +#define GL_TEXTURE_BINDING_RECTANGLE_NV 0x84F6 +#define GL_PROXY_TEXTURE_RECTANGLE_NV 0x84F7 +#define GL_MAX_RECTANGLE_TEXTURE_SIZE_NV 0x84F8 +#endif /* GL_NV_texture_rectangle */ + +#ifndef GL_NV_texture_rectangle_compressed +#define GL_NV_texture_rectangle_compressed 1 +#endif /* GL_NV_texture_rectangle_compressed */ + +#ifndef GL_NV_texture_shader +#define GL_NV_texture_shader 1 +#define GL_OFFSET_TEXTURE_RECTANGLE_NV 0x864C +#define GL_OFFSET_TEXTURE_RECTANGLE_SCALE_NV 0x864D +#define GL_DOT_PRODUCT_TEXTURE_RECTANGLE_NV 0x864E +#define GL_RGBA_UNSIGNED_DOT_PRODUCT_MAPPING_NV 0x86D9 +#define GL_UNSIGNED_INT_S8_S8_8_8_NV 0x86DA +#define GL_UNSIGNED_INT_8_8_S8_S8_REV_NV 0x86DB +#define GL_DSDT_MAG_INTENSITY_NV 0x86DC +#define GL_SHADER_CONSISTENT_NV 0x86DD +#define GL_TEXTURE_SHADER_NV 0x86DE +#define GL_SHADER_OPERATION_NV 0x86DF +#define GL_CULL_MODES_NV 0x86E0 +#define GL_OFFSET_TEXTURE_MATRIX_NV 0x86E1 +#define GL_OFFSET_TEXTURE_SCALE_NV 0x86E2 +#define GL_OFFSET_TEXTURE_BIAS_NV 0x86E3 +#define GL_OFFSET_TEXTURE_2D_MATRIX_NV 0x86E1 +#define GL_OFFSET_TEXTURE_2D_SCALE_NV 0x86E2 +#define GL_OFFSET_TEXTURE_2D_BIAS_NV 0x86E3 +#define GL_PREVIOUS_TEXTURE_INPUT_NV 0x86E4 +#define GL_CONST_EYE_NV 0x86E5 +#define GL_PASS_THROUGH_NV 0x86E6 +#define GL_CULL_FRAGMENT_NV 0x86E7 +#define GL_OFFSET_TEXTURE_2D_NV 0x86E8 +#define GL_DEPENDENT_AR_TEXTURE_2D_NV 0x86E9 +#define GL_DEPENDENT_GB_TEXTURE_2D_NV 0x86EA +#define GL_DOT_PRODUCT_NV 0x86EC +#define GL_DOT_PRODUCT_DEPTH_REPLACE_NV 0x86ED +#define GL_DOT_PRODUCT_TEXTURE_2D_NV 0x86EE +#define GL_DOT_PRODUCT_TEXTURE_CUBE_MAP_NV 0x86F0 +#define GL_DOT_PRODUCT_DIFFUSE_CUBE_MAP_NV 0x86F1 +#define GL_DOT_PRODUCT_REFLECT_CUBE_MAP_NV 0x86F2 +#define GL_DOT_PRODUCT_CONST_EYE_REFLECT_CUBE_MAP_NV 0x86F3 +#define GL_HILO_NV 0x86F4 +#define GL_DSDT_NV 0x86F5 +#define GL_DSDT_MAG_NV 0x86F6 +#define GL_DSDT_MAG_VIB_NV 0x86F7 +#define GL_HILO16_NV 0x86F8 +#define GL_SIGNED_HILO_NV 0x86F9 +#define GL_SIGNED_HILO16_NV 0x86FA +#define GL_SIGNED_RGBA_NV 0x86FB +#define GL_SIGNED_RGBA8_NV 0x86FC +#define GL_SIGNED_RGB_NV 0x86FE +#define GL_SIGNED_RGB8_NV 0x86FF +#define GL_SIGNED_LUMINANCE_NV 0x8701 +#define GL_SIGNED_LUMINANCE8_NV 0x8702 +#define GL_SIGNED_LUMINANCE_ALPHA_NV 0x8703 +#define GL_SIGNED_LUMINANCE8_ALPHA8_NV 0x8704 +#define GL_SIGNED_ALPHA_NV 0x8705 +#define GL_SIGNED_ALPHA8_NV 0x8706 +#define GL_SIGNED_INTENSITY_NV 0x8707 +#define GL_SIGNED_INTENSITY8_NV 0x8708 +#define GL_DSDT8_NV 0x8709 +#define GL_DSDT8_MAG8_NV 0x870A +#define GL_DSDT8_MAG8_INTENSITY8_NV 0x870B +#define GL_SIGNED_RGB_UNSIGNED_ALPHA_NV 0x870C +#define GL_SIGNED_RGB8_UNSIGNED_ALPHA8_NV 0x870D +#define GL_HI_SCALE_NV 0x870E +#define GL_LO_SCALE_NV 0x870F +#define GL_DS_SCALE_NV 0x8710 +#define GL_DT_SCALE_NV 0x8711 +#define GL_MAGNITUDE_SCALE_NV 0x8712 +#define GL_VIBRANCE_SCALE_NV 0x8713 +#define GL_HI_BIAS_NV 0x8714 +#define GL_LO_BIAS_NV 0x8715 +#define GL_DS_BIAS_NV 0x8716 +#define GL_DT_BIAS_NV 0x8717 +#define GL_MAGNITUDE_BIAS_NV 0x8718 +#define GL_VIBRANCE_BIAS_NV 0x8719 +#define GL_TEXTURE_BORDER_VALUES_NV 0x871A +#define GL_TEXTURE_HI_SIZE_NV 0x871B +#define GL_TEXTURE_LO_SIZE_NV 0x871C +#define GL_TEXTURE_DS_SIZE_NV 0x871D +#define GL_TEXTURE_DT_SIZE_NV 0x871E +#define GL_TEXTURE_MAG_SIZE_NV 0x871F +#endif /* GL_NV_texture_shader */ + +#ifndef GL_NV_texture_shader2 +#define GL_NV_texture_shader2 1 +#define GL_DOT_PRODUCT_TEXTURE_3D_NV 0x86EF +#endif /* GL_NV_texture_shader2 */ + +#ifndef GL_NV_texture_shader3 +#define GL_NV_texture_shader3 1 +#define GL_OFFSET_PROJECTIVE_TEXTURE_2D_NV 0x8850 +#define GL_OFFSET_PROJECTIVE_TEXTURE_2D_SCALE_NV 0x8851 +#define GL_OFFSET_PROJECTIVE_TEXTURE_RECTANGLE_NV 0x8852 +#define GL_OFFSET_PROJECTIVE_TEXTURE_RECTANGLE_SCALE_NV 0x8853 +#define GL_OFFSET_HILO_TEXTURE_2D_NV 0x8854 +#define GL_OFFSET_HILO_TEXTURE_RECTANGLE_NV 0x8855 +#define GL_OFFSET_HILO_PROJECTIVE_TEXTURE_2D_NV 0x8856 +#define GL_OFFSET_HILO_PROJECTIVE_TEXTURE_RECTANGLE_NV 0x8857 +#define GL_DEPENDENT_HILO_TEXTURE_2D_NV 0x8858 +#define GL_DEPENDENT_RGB_TEXTURE_3D_NV 0x8859 +#define GL_DEPENDENT_RGB_TEXTURE_CUBE_MAP_NV 0x885A +#define GL_DOT_PRODUCT_PASS_THROUGH_NV 0x885B +#define GL_DOT_PRODUCT_TEXTURE_1D_NV 0x885C +#define GL_DOT_PRODUCT_AFFINE_DEPTH_REPLACE_NV 0x885D +#define GL_HILO8_NV 0x885E +#define GL_SIGNED_HILO8_NV 0x885F +#define GL_FORCE_BLUE_TO_ONE_NV 0x8860 +#endif /* GL_NV_texture_shader3 */ + +#ifndef GL_NV_timeline_semaphore +#define GL_NV_timeline_semaphore 1 +#define GL_TIMELINE_SEMAPHORE_VALUE_NV 0x9595 +#define GL_SEMAPHORE_TYPE_NV 0x95B3 +#define GL_SEMAPHORE_TYPE_BINARY_NV 0x95B4 +#define GL_SEMAPHORE_TYPE_TIMELINE_NV 0x95B5 +#define GL_MAX_TIMELINE_SEMAPHORE_VALUE_DIFFERENCE_NV 0x95B6 +typedef void (APIENTRYP PFNGLCREATESEMAPHORESNVPROC) (GLsizei n, GLuint *semaphores); +typedef void (APIENTRYP PFNGLSEMAPHOREPARAMETERIVNVPROC) (GLuint semaphore, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLGETSEMAPHOREPARAMETERIVNVPROC) (GLuint semaphore, GLenum pname, GLint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCreateSemaphoresNV (GLsizei n, GLuint *semaphores); +GLAPI void APIENTRY glSemaphoreParameterivNV (GLuint semaphore, GLenum pname, const GLint *params); +GLAPI void APIENTRY glGetSemaphoreParameterivNV (GLuint semaphore, GLenum pname, GLint *params); +#endif +#endif /* GL_NV_timeline_semaphore */ + +#ifndef GL_NV_transform_feedback +#define GL_NV_transform_feedback 1 +#define GL_BACK_PRIMARY_COLOR_NV 0x8C77 +#define GL_BACK_SECONDARY_COLOR_NV 0x8C78 +#define GL_TEXTURE_COORD_NV 0x8C79 +#define GL_CLIP_DISTANCE_NV 0x8C7A +#define GL_VERTEX_ID_NV 0x8C7B +#define GL_PRIMITIVE_ID_NV 0x8C7C +#define GL_GENERIC_ATTRIB_NV 0x8C7D +#define GL_TRANSFORM_FEEDBACK_ATTRIBS_NV 0x8C7E +#define GL_TRANSFORM_FEEDBACK_BUFFER_MODE_NV 0x8C7F +#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS_NV 0x8C80 +#define GL_ACTIVE_VARYINGS_NV 0x8C81 +#define GL_ACTIVE_VARYING_MAX_LENGTH_NV 0x8C82 +#define GL_TRANSFORM_FEEDBACK_VARYINGS_NV 0x8C83 +#define GL_TRANSFORM_FEEDBACK_BUFFER_START_NV 0x8C84 +#define GL_TRANSFORM_FEEDBACK_BUFFER_SIZE_NV 0x8C85 +#define GL_TRANSFORM_FEEDBACK_RECORD_NV 0x8C86 +#define GL_PRIMITIVES_GENERATED_NV 0x8C87 +#define GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN_NV 0x8C88 +#define GL_RASTERIZER_DISCARD_NV 0x8C89 +#define GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS_NV 0x8C8A +#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS_NV 0x8C8B +#define GL_INTERLEAVED_ATTRIBS_NV 0x8C8C +#define GL_SEPARATE_ATTRIBS_NV 0x8C8D +#define GL_TRANSFORM_FEEDBACK_BUFFER_NV 0x8C8E +#define GL_TRANSFORM_FEEDBACK_BUFFER_BINDING_NV 0x8C8F +#define GL_LAYER_NV 0x8DAA +#define GL_NEXT_BUFFER_NV -2 +#define GL_SKIP_COMPONENTS4_NV -3 +#define GL_SKIP_COMPONENTS3_NV -4 +#define GL_SKIP_COMPONENTS2_NV -5 +#define GL_SKIP_COMPONENTS1_NV -6 +typedef void (APIENTRYP PFNGLBEGINTRANSFORMFEEDBACKNVPROC) (GLenum primitiveMode); +typedef void (APIENTRYP PFNGLENDTRANSFORMFEEDBACKNVPROC) (void); +typedef void (APIENTRYP PFNGLTRANSFORMFEEDBACKATTRIBSNVPROC) (GLsizei count, const GLint *attribs, GLenum bufferMode); +typedef void (APIENTRYP PFNGLBINDBUFFERRANGENVPROC) (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); +typedef void (APIENTRYP PFNGLBINDBUFFEROFFSETNVPROC) (GLenum target, GLuint index, GLuint buffer, GLintptr offset); +typedef void (APIENTRYP PFNGLBINDBUFFERBASENVPROC) (GLenum target, GLuint index, GLuint buffer); +typedef void (APIENTRYP PFNGLTRANSFORMFEEDBACKVARYINGSNVPROC) (GLuint program, GLsizei count, const GLint *locations, GLenum bufferMode); +typedef void (APIENTRYP PFNGLACTIVEVARYINGNVPROC) (GLuint program, const GLchar *name); +typedef GLint (APIENTRYP PFNGLGETVARYINGLOCATIONNVPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP PFNGLGETACTIVEVARYINGNVPROC) (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLsizei *size, GLenum *type, GLchar *name); +typedef void (APIENTRYP PFNGLGETTRANSFORMFEEDBACKVARYINGNVPROC) (GLuint program, GLuint index, GLint *location); +typedef void (APIENTRYP PFNGLTRANSFORMFEEDBACKSTREAMATTRIBSNVPROC) (GLsizei count, const GLint *attribs, GLsizei nbuffers, const GLint *bufstreams, GLenum bufferMode); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBeginTransformFeedbackNV (GLenum primitiveMode); +GLAPI void APIENTRY glEndTransformFeedbackNV (void); +GLAPI void APIENTRY glTransformFeedbackAttribsNV (GLsizei count, const GLint *attribs, GLenum bufferMode); +GLAPI void APIENTRY glBindBufferRangeNV (GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); +GLAPI void APIENTRY glBindBufferOffsetNV (GLenum target, GLuint index, GLuint buffer, GLintptr offset); +GLAPI void APIENTRY glBindBufferBaseNV (GLenum target, GLuint index, GLuint buffer); +GLAPI void APIENTRY glTransformFeedbackVaryingsNV (GLuint program, GLsizei count, const GLint *locations, GLenum bufferMode); +GLAPI void APIENTRY glActiveVaryingNV (GLuint program, const GLchar *name); +GLAPI GLint APIENTRY glGetVaryingLocationNV (GLuint program, const GLchar *name); +GLAPI void APIENTRY glGetActiveVaryingNV (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLsizei *size, GLenum *type, GLchar *name); +GLAPI void APIENTRY glGetTransformFeedbackVaryingNV (GLuint program, GLuint index, GLint *location); +GLAPI void APIENTRY glTransformFeedbackStreamAttribsNV (GLsizei count, const GLint *attribs, GLsizei nbuffers, const GLint *bufstreams, GLenum bufferMode); +#endif +#endif /* GL_NV_transform_feedback */ + +#ifndef GL_NV_transform_feedback2 +#define GL_NV_transform_feedback2 1 +#define GL_TRANSFORM_FEEDBACK_NV 0x8E22 +#define GL_TRANSFORM_FEEDBACK_BUFFER_PAUSED_NV 0x8E23 +#define GL_TRANSFORM_FEEDBACK_BUFFER_ACTIVE_NV 0x8E24 +#define GL_TRANSFORM_FEEDBACK_BINDING_NV 0x8E25 +typedef void (APIENTRYP PFNGLBINDTRANSFORMFEEDBACKNVPROC) (GLenum target, GLuint id); +typedef void (APIENTRYP PFNGLDELETETRANSFORMFEEDBACKSNVPROC) (GLsizei n, const GLuint *ids); +typedef void (APIENTRYP PFNGLGENTRANSFORMFEEDBACKSNVPROC) (GLsizei n, GLuint *ids); +typedef GLboolean (APIENTRYP PFNGLISTRANSFORMFEEDBACKNVPROC) (GLuint id); +typedef void (APIENTRYP PFNGLPAUSETRANSFORMFEEDBACKNVPROC) (void); +typedef void (APIENTRYP PFNGLRESUMETRANSFORMFEEDBACKNVPROC) (void); +typedef void (APIENTRYP PFNGLDRAWTRANSFORMFEEDBACKNVPROC) (GLenum mode, GLuint id); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindTransformFeedbackNV (GLenum target, GLuint id); +GLAPI void APIENTRY glDeleteTransformFeedbacksNV (GLsizei n, const GLuint *ids); +GLAPI void APIENTRY glGenTransformFeedbacksNV (GLsizei n, GLuint *ids); +GLAPI GLboolean APIENTRY glIsTransformFeedbackNV (GLuint id); +GLAPI void APIENTRY glPauseTransformFeedbackNV (void); +GLAPI void APIENTRY glResumeTransformFeedbackNV (void); +GLAPI void APIENTRY glDrawTransformFeedbackNV (GLenum mode, GLuint id); +#endif +#endif /* GL_NV_transform_feedback2 */ + +#ifndef GL_NV_uniform_buffer_unified_memory +#define GL_NV_uniform_buffer_unified_memory 1 +#define GL_UNIFORM_BUFFER_UNIFIED_NV 0x936E +#define GL_UNIFORM_BUFFER_ADDRESS_NV 0x936F +#define GL_UNIFORM_BUFFER_LENGTH_NV 0x9370 +#endif /* GL_NV_uniform_buffer_unified_memory */ + +#ifndef GL_NV_vdpau_interop +#define GL_NV_vdpau_interop 1 +typedef GLintptr GLvdpauSurfaceNV; +#define GL_SURFACE_STATE_NV 0x86EB +#define GL_SURFACE_REGISTERED_NV 0x86FD +#define GL_SURFACE_MAPPED_NV 0x8700 +#define GL_WRITE_DISCARD_NV 0x88BE +typedef void (APIENTRYP PFNGLVDPAUINITNVPROC) (const void *vdpDevice, const void *getProcAddress); +typedef void (APIENTRYP PFNGLVDPAUFININVPROC) (void); +typedef GLvdpauSurfaceNV (APIENTRYP PFNGLVDPAUREGISTERVIDEOSURFACENVPROC) (const void *vdpSurface, GLenum target, GLsizei numTextureNames, const GLuint *textureNames); +typedef GLvdpauSurfaceNV (APIENTRYP PFNGLVDPAUREGISTEROUTPUTSURFACENVPROC) (const void *vdpSurface, GLenum target, GLsizei numTextureNames, const GLuint *textureNames); +typedef GLboolean (APIENTRYP PFNGLVDPAUISSURFACENVPROC) (GLvdpauSurfaceNV surface); +typedef void (APIENTRYP PFNGLVDPAUUNREGISTERSURFACENVPROC) (GLvdpauSurfaceNV surface); +typedef void (APIENTRYP PFNGLVDPAUGETSURFACEIVNVPROC) (GLvdpauSurfaceNV surface, GLenum pname, GLsizei count, GLsizei *length, GLint *values); +typedef void (APIENTRYP PFNGLVDPAUSURFACEACCESSNVPROC) (GLvdpauSurfaceNV surface, GLenum access); +typedef void (APIENTRYP PFNGLVDPAUMAPSURFACESNVPROC) (GLsizei numSurfaces, const GLvdpauSurfaceNV *surfaces); +typedef void (APIENTRYP PFNGLVDPAUUNMAPSURFACESNVPROC) (GLsizei numSurface, const GLvdpauSurfaceNV *surfaces); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVDPAUInitNV (const void *vdpDevice, const void *getProcAddress); +GLAPI void APIENTRY glVDPAUFiniNV (void); +GLAPI GLvdpauSurfaceNV APIENTRY glVDPAURegisterVideoSurfaceNV (const void *vdpSurface, GLenum target, GLsizei numTextureNames, const GLuint *textureNames); +GLAPI GLvdpauSurfaceNV APIENTRY glVDPAURegisterOutputSurfaceNV (const void *vdpSurface, GLenum target, GLsizei numTextureNames, const GLuint *textureNames); +GLAPI GLboolean APIENTRY glVDPAUIsSurfaceNV (GLvdpauSurfaceNV surface); +GLAPI void APIENTRY glVDPAUUnregisterSurfaceNV (GLvdpauSurfaceNV surface); +GLAPI void APIENTRY glVDPAUGetSurfaceivNV (GLvdpauSurfaceNV surface, GLenum pname, GLsizei count, GLsizei *length, GLint *values); +GLAPI void APIENTRY glVDPAUSurfaceAccessNV (GLvdpauSurfaceNV surface, GLenum access); +GLAPI void APIENTRY glVDPAUMapSurfacesNV (GLsizei numSurfaces, const GLvdpauSurfaceNV *surfaces); +GLAPI void APIENTRY glVDPAUUnmapSurfacesNV (GLsizei numSurface, const GLvdpauSurfaceNV *surfaces); +#endif +#endif /* GL_NV_vdpau_interop */ + +#ifndef GL_NV_vdpau_interop2 +#define GL_NV_vdpau_interop2 1 +typedef GLvdpauSurfaceNV (APIENTRYP PFNGLVDPAUREGISTERVIDEOSURFACEWITHPICTURESTRUCTURENVPROC) (const void *vdpSurface, GLenum target, GLsizei numTextureNames, const GLuint *textureNames, GLboolean isFrameStructure); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLvdpauSurfaceNV APIENTRY glVDPAURegisterVideoSurfaceWithPictureStructureNV (const void *vdpSurface, GLenum target, GLsizei numTextureNames, const GLuint *textureNames, GLboolean isFrameStructure); +#endif +#endif /* GL_NV_vdpau_interop2 */ + +#ifndef GL_NV_vertex_array_range +#define GL_NV_vertex_array_range 1 +#define GL_VERTEX_ARRAY_RANGE_NV 0x851D +#define GL_VERTEX_ARRAY_RANGE_LENGTH_NV 0x851E +#define GL_VERTEX_ARRAY_RANGE_VALID_NV 0x851F +#define GL_MAX_VERTEX_ARRAY_RANGE_ELEMENT_NV 0x8520 +#define GL_VERTEX_ARRAY_RANGE_POINTER_NV 0x8521 +typedef void (APIENTRYP PFNGLFLUSHVERTEXARRAYRANGENVPROC) (void); +typedef void (APIENTRYP PFNGLVERTEXARRAYRANGENVPROC) (GLsizei length, const void *pointer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFlushVertexArrayRangeNV (void); +GLAPI void APIENTRY glVertexArrayRangeNV (GLsizei length, const void *pointer); +#endif +#endif /* GL_NV_vertex_array_range */ + +#ifndef GL_NV_vertex_array_range2 +#define GL_NV_vertex_array_range2 1 +#define GL_VERTEX_ARRAY_RANGE_WITHOUT_FLUSH_NV 0x8533 +#endif /* GL_NV_vertex_array_range2 */ + +#ifndef GL_NV_vertex_attrib_integer_64bit +#define GL_NV_vertex_attrib_integer_64bit 1 +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1I64NVPROC) (GLuint index, GLint64EXT x); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL2I64NVPROC) (GLuint index, GLint64EXT x, GLint64EXT y); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL3I64NVPROC) (GLuint index, GLint64EXT x, GLint64EXT y, GLint64EXT z); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL4I64NVPROC) (GLuint index, GLint64EXT x, GLint64EXT y, GLint64EXT z, GLint64EXT w); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1I64VNVPROC) (GLuint index, const GLint64EXT *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL2I64VNVPROC) (GLuint index, const GLint64EXT *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL3I64VNVPROC) (GLuint index, const GLint64EXT *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL4I64VNVPROC) (GLuint index, const GLint64EXT *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1UI64NVPROC) (GLuint index, GLuint64EXT x); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL2UI64NVPROC) (GLuint index, GLuint64EXT x, GLuint64EXT y); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL3UI64NVPROC) (GLuint index, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL4UI64NVPROC) (GLuint index, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, GLuint64EXT w); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1UI64VNVPROC) (GLuint index, const GLuint64EXT *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL2UI64VNVPROC) (GLuint index, const GLuint64EXT *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL3UI64VNVPROC) (GLuint index, const GLuint64EXT *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBL4UI64VNVPROC) (GLuint index, const GLuint64EXT *v); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBLI64VNVPROC) (GLuint index, GLenum pname, GLint64EXT *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBLUI64VNVPROC) (GLuint index, GLenum pname, GLuint64EXT *params); +typedef void (APIENTRYP PFNGLVERTEXATTRIBLFORMATNVPROC) (GLuint index, GLint size, GLenum type, GLsizei stride); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexAttribL1i64NV (GLuint index, GLint64EXT x); +GLAPI void APIENTRY glVertexAttribL2i64NV (GLuint index, GLint64EXT x, GLint64EXT y); +GLAPI void APIENTRY glVertexAttribL3i64NV (GLuint index, GLint64EXT x, GLint64EXT y, GLint64EXT z); +GLAPI void APIENTRY glVertexAttribL4i64NV (GLuint index, GLint64EXT x, GLint64EXT y, GLint64EXT z, GLint64EXT w); +GLAPI void APIENTRY glVertexAttribL1i64vNV (GLuint index, const GLint64EXT *v); +GLAPI void APIENTRY glVertexAttribL2i64vNV (GLuint index, const GLint64EXT *v); +GLAPI void APIENTRY glVertexAttribL3i64vNV (GLuint index, const GLint64EXT *v); +GLAPI void APIENTRY glVertexAttribL4i64vNV (GLuint index, const GLint64EXT *v); +GLAPI void APIENTRY glVertexAttribL1ui64NV (GLuint index, GLuint64EXT x); +GLAPI void APIENTRY glVertexAttribL2ui64NV (GLuint index, GLuint64EXT x, GLuint64EXT y); +GLAPI void APIENTRY glVertexAttribL3ui64NV (GLuint index, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z); +GLAPI void APIENTRY glVertexAttribL4ui64NV (GLuint index, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, GLuint64EXT w); +GLAPI void APIENTRY glVertexAttribL1ui64vNV (GLuint index, const GLuint64EXT *v); +GLAPI void APIENTRY glVertexAttribL2ui64vNV (GLuint index, const GLuint64EXT *v); +GLAPI void APIENTRY glVertexAttribL3ui64vNV (GLuint index, const GLuint64EXT *v); +GLAPI void APIENTRY glVertexAttribL4ui64vNV (GLuint index, const GLuint64EXT *v); +GLAPI void APIENTRY glGetVertexAttribLi64vNV (GLuint index, GLenum pname, GLint64EXT *params); +GLAPI void APIENTRY glGetVertexAttribLui64vNV (GLuint index, GLenum pname, GLuint64EXT *params); +GLAPI void APIENTRY glVertexAttribLFormatNV (GLuint index, GLint size, GLenum type, GLsizei stride); +#endif +#endif /* GL_NV_vertex_attrib_integer_64bit */ + +#ifndef GL_NV_vertex_buffer_unified_memory +#define GL_NV_vertex_buffer_unified_memory 1 +#define GL_VERTEX_ATTRIB_ARRAY_UNIFIED_NV 0x8F1E +#define GL_ELEMENT_ARRAY_UNIFIED_NV 0x8F1F +#define GL_VERTEX_ATTRIB_ARRAY_ADDRESS_NV 0x8F20 +#define GL_VERTEX_ARRAY_ADDRESS_NV 0x8F21 +#define GL_NORMAL_ARRAY_ADDRESS_NV 0x8F22 +#define GL_COLOR_ARRAY_ADDRESS_NV 0x8F23 +#define GL_INDEX_ARRAY_ADDRESS_NV 0x8F24 +#define GL_TEXTURE_COORD_ARRAY_ADDRESS_NV 0x8F25 +#define GL_EDGE_FLAG_ARRAY_ADDRESS_NV 0x8F26 +#define GL_SECONDARY_COLOR_ARRAY_ADDRESS_NV 0x8F27 +#define GL_FOG_COORD_ARRAY_ADDRESS_NV 0x8F28 +#define GL_ELEMENT_ARRAY_ADDRESS_NV 0x8F29 +#define GL_VERTEX_ATTRIB_ARRAY_LENGTH_NV 0x8F2A +#define GL_VERTEX_ARRAY_LENGTH_NV 0x8F2B +#define GL_NORMAL_ARRAY_LENGTH_NV 0x8F2C +#define GL_COLOR_ARRAY_LENGTH_NV 0x8F2D +#define GL_INDEX_ARRAY_LENGTH_NV 0x8F2E +#define GL_TEXTURE_COORD_ARRAY_LENGTH_NV 0x8F2F +#define GL_EDGE_FLAG_ARRAY_LENGTH_NV 0x8F30 +#define GL_SECONDARY_COLOR_ARRAY_LENGTH_NV 0x8F31 +#define GL_FOG_COORD_ARRAY_LENGTH_NV 0x8F32 +#define GL_ELEMENT_ARRAY_LENGTH_NV 0x8F33 +#define GL_DRAW_INDIRECT_UNIFIED_NV 0x8F40 +#define GL_DRAW_INDIRECT_ADDRESS_NV 0x8F41 +#define GL_DRAW_INDIRECT_LENGTH_NV 0x8F42 +typedef void (APIENTRYP PFNGLBUFFERADDRESSRANGENVPROC) (GLenum pname, GLuint index, GLuint64EXT address, GLsizeiptr length); +typedef void (APIENTRYP PFNGLVERTEXFORMATNVPROC) (GLint size, GLenum type, GLsizei stride); +typedef void (APIENTRYP PFNGLNORMALFORMATNVPROC) (GLenum type, GLsizei stride); +typedef void (APIENTRYP PFNGLCOLORFORMATNVPROC) (GLint size, GLenum type, GLsizei stride); +typedef void (APIENTRYP PFNGLINDEXFORMATNVPROC) (GLenum type, GLsizei stride); +typedef void (APIENTRYP PFNGLTEXCOORDFORMATNVPROC) (GLint size, GLenum type, GLsizei stride); +typedef void (APIENTRYP PFNGLEDGEFLAGFORMATNVPROC) (GLsizei stride); +typedef void (APIENTRYP PFNGLSECONDARYCOLORFORMATNVPROC) (GLint size, GLenum type, GLsizei stride); +typedef void (APIENTRYP PFNGLFOGCOORDFORMATNVPROC) (GLenum type, GLsizei stride); +typedef void (APIENTRYP PFNGLVERTEXATTRIBFORMATNVPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride); +typedef void (APIENTRYP PFNGLVERTEXATTRIBIFORMATNVPROC) (GLuint index, GLint size, GLenum type, GLsizei stride); +typedef void (APIENTRYP PFNGLGETINTEGERUI64I_VNVPROC) (GLenum value, GLuint index, GLuint64EXT *result); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBufferAddressRangeNV (GLenum pname, GLuint index, GLuint64EXT address, GLsizeiptr length); +GLAPI void APIENTRY glVertexFormatNV (GLint size, GLenum type, GLsizei stride); +GLAPI void APIENTRY glNormalFormatNV (GLenum type, GLsizei stride); +GLAPI void APIENTRY glColorFormatNV (GLint size, GLenum type, GLsizei stride); +GLAPI void APIENTRY glIndexFormatNV (GLenum type, GLsizei stride); +GLAPI void APIENTRY glTexCoordFormatNV (GLint size, GLenum type, GLsizei stride); +GLAPI void APIENTRY glEdgeFlagFormatNV (GLsizei stride); +GLAPI void APIENTRY glSecondaryColorFormatNV (GLint size, GLenum type, GLsizei stride); +GLAPI void APIENTRY glFogCoordFormatNV (GLenum type, GLsizei stride); +GLAPI void APIENTRY glVertexAttribFormatNV (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride); +GLAPI void APIENTRY glVertexAttribIFormatNV (GLuint index, GLint size, GLenum type, GLsizei stride); +GLAPI void APIENTRY glGetIntegerui64i_vNV (GLenum value, GLuint index, GLuint64EXT *result); +#endif +#endif /* GL_NV_vertex_buffer_unified_memory */ + +#ifndef GL_NV_vertex_program +#define GL_NV_vertex_program 1 +#define GL_VERTEX_PROGRAM_NV 0x8620 +#define GL_VERTEX_STATE_PROGRAM_NV 0x8621 +#define GL_ATTRIB_ARRAY_SIZE_NV 0x8623 +#define GL_ATTRIB_ARRAY_STRIDE_NV 0x8624 +#define GL_ATTRIB_ARRAY_TYPE_NV 0x8625 +#define GL_CURRENT_ATTRIB_NV 0x8626 +#define GL_PROGRAM_LENGTH_NV 0x8627 +#define GL_PROGRAM_STRING_NV 0x8628 +#define GL_MODELVIEW_PROJECTION_NV 0x8629 +#define GL_IDENTITY_NV 0x862A +#define GL_INVERSE_NV 0x862B +#define GL_TRANSPOSE_NV 0x862C +#define GL_INVERSE_TRANSPOSE_NV 0x862D +#define GL_MAX_TRACK_MATRIX_STACK_DEPTH_NV 0x862E +#define GL_MAX_TRACK_MATRICES_NV 0x862F +#define GL_MATRIX0_NV 0x8630 +#define GL_MATRIX1_NV 0x8631 +#define GL_MATRIX2_NV 0x8632 +#define GL_MATRIX3_NV 0x8633 +#define GL_MATRIX4_NV 0x8634 +#define GL_MATRIX5_NV 0x8635 +#define GL_MATRIX6_NV 0x8636 +#define GL_MATRIX7_NV 0x8637 +#define GL_CURRENT_MATRIX_STACK_DEPTH_NV 0x8640 +#define GL_CURRENT_MATRIX_NV 0x8641 +#define GL_VERTEX_PROGRAM_POINT_SIZE_NV 0x8642 +#define GL_VERTEX_PROGRAM_TWO_SIDE_NV 0x8643 +#define GL_PROGRAM_PARAMETER_NV 0x8644 +#define GL_ATTRIB_ARRAY_POINTER_NV 0x8645 +#define GL_PROGRAM_TARGET_NV 0x8646 +#define GL_PROGRAM_RESIDENT_NV 0x8647 +#define GL_TRACK_MATRIX_NV 0x8648 +#define GL_TRACK_MATRIX_TRANSFORM_NV 0x8649 +#define GL_VERTEX_PROGRAM_BINDING_NV 0x864A +#define GL_PROGRAM_ERROR_POSITION_NV 0x864B +#define GL_VERTEX_ATTRIB_ARRAY0_NV 0x8650 +#define GL_VERTEX_ATTRIB_ARRAY1_NV 0x8651 +#define GL_VERTEX_ATTRIB_ARRAY2_NV 0x8652 +#define GL_VERTEX_ATTRIB_ARRAY3_NV 0x8653 +#define GL_VERTEX_ATTRIB_ARRAY4_NV 0x8654 +#define GL_VERTEX_ATTRIB_ARRAY5_NV 0x8655 +#define GL_VERTEX_ATTRIB_ARRAY6_NV 0x8656 +#define GL_VERTEX_ATTRIB_ARRAY7_NV 0x8657 +#define GL_VERTEX_ATTRIB_ARRAY8_NV 0x8658 +#define GL_VERTEX_ATTRIB_ARRAY9_NV 0x8659 +#define GL_VERTEX_ATTRIB_ARRAY10_NV 0x865A +#define GL_VERTEX_ATTRIB_ARRAY11_NV 0x865B +#define GL_VERTEX_ATTRIB_ARRAY12_NV 0x865C +#define GL_VERTEX_ATTRIB_ARRAY13_NV 0x865D +#define GL_VERTEX_ATTRIB_ARRAY14_NV 0x865E +#define GL_VERTEX_ATTRIB_ARRAY15_NV 0x865F +#define GL_MAP1_VERTEX_ATTRIB0_4_NV 0x8660 +#define GL_MAP1_VERTEX_ATTRIB1_4_NV 0x8661 +#define GL_MAP1_VERTEX_ATTRIB2_4_NV 0x8662 +#define GL_MAP1_VERTEX_ATTRIB3_4_NV 0x8663 +#define GL_MAP1_VERTEX_ATTRIB4_4_NV 0x8664 +#define GL_MAP1_VERTEX_ATTRIB5_4_NV 0x8665 +#define GL_MAP1_VERTEX_ATTRIB6_4_NV 0x8666 +#define GL_MAP1_VERTEX_ATTRIB7_4_NV 0x8667 +#define GL_MAP1_VERTEX_ATTRIB8_4_NV 0x8668 +#define GL_MAP1_VERTEX_ATTRIB9_4_NV 0x8669 +#define GL_MAP1_VERTEX_ATTRIB10_4_NV 0x866A +#define GL_MAP1_VERTEX_ATTRIB11_4_NV 0x866B +#define GL_MAP1_VERTEX_ATTRIB12_4_NV 0x866C +#define GL_MAP1_VERTEX_ATTRIB13_4_NV 0x866D +#define GL_MAP1_VERTEX_ATTRIB14_4_NV 0x866E +#define GL_MAP1_VERTEX_ATTRIB15_4_NV 0x866F +#define GL_MAP2_VERTEX_ATTRIB0_4_NV 0x8670 +#define GL_MAP2_VERTEX_ATTRIB1_4_NV 0x8671 +#define GL_MAP2_VERTEX_ATTRIB2_4_NV 0x8672 +#define GL_MAP2_VERTEX_ATTRIB3_4_NV 0x8673 +#define GL_MAP2_VERTEX_ATTRIB4_4_NV 0x8674 +#define GL_MAP2_VERTEX_ATTRIB5_4_NV 0x8675 +#define GL_MAP2_VERTEX_ATTRIB6_4_NV 0x8676 +#define GL_MAP2_VERTEX_ATTRIB7_4_NV 0x8677 +#define GL_MAP2_VERTEX_ATTRIB8_4_NV 0x8678 +#define GL_MAP2_VERTEX_ATTRIB9_4_NV 0x8679 +#define GL_MAP2_VERTEX_ATTRIB10_4_NV 0x867A +#define GL_MAP2_VERTEX_ATTRIB11_4_NV 0x867B +#define GL_MAP2_VERTEX_ATTRIB12_4_NV 0x867C +#define GL_MAP2_VERTEX_ATTRIB13_4_NV 0x867D +#define GL_MAP2_VERTEX_ATTRIB14_4_NV 0x867E +#define GL_MAP2_VERTEX_ATTRIB15_4_NV 0x867F +typedef GLboolean (APIENTRYP PFNGLAREPROGRAMSRESIDENTNVPROC) (GLsizei n, const GLuint *programs, GLboolean *residences); +typedef void (APIENTRYP PFNGLBINDPROGRAMNVPROC) (GLenum target, GLuint id); +typedef void (APIENTRYP PFNGLDELETEPROGRAMSNVPROC) (GLsizei n, const GLuint *programs); +typedef void (APIENTRYP PFNGLEXECUTEPROGRAMNVPROC) (GLenum target, GLuint id, const GLfloat *params); +typedef void (APIENTRYP PFNGLGENPROGRAMSNVPROC) (GLsizei n, GLuint *programs); +typedef void (APIENTRYP PFNGLGETPROGRAMPARAMETERDVNVPROC) (GLenum target, GLuint index, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLGETPROGRAMPARAMETERFVNVPROC) (GLenum target, GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMIVNVPROC) (GLuint id, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMSTRINGNVPROC) (GLuint id, GLenum pname, GLubyte *program); +typedef void (APIENTRYP PFNGLGETTRACKMATRIXIVNVPROC) (GLenum target, GLuint address, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBDVNVPROC) (GLuint index, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBFVNVPROC) (GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIVNVPROC) (GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBPOINTERVNVPROC) (GLuint index, GLenum pname, void **pointer); +typedef GLboolean (APIENTRYP PFNGLISPROGRAMNVPROC) (GLuint id); +typedef void (APIENTRYP PFNGLLOADPROGRAMNVPROC) (GLenum target, GLuint id, GLsizei len, const GLubyte *program); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETER4DNVPROC) (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETER4DVNVPROC) (GLenum target, GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETER4FNVPROC) (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETER4FVNVPROC) (GLenum target, GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETERS4DVNVPROC) (GLenum target, GLuint index, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETERS4FVNVPROC) (GLenum target, GLuint index, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLREQUESTRESIDENTPROGRAMSNVPROC) (GLsizei n, const GLuint *programs); +typedef void (APIENTRYP PFNGLTRACKMATRIXNVPROC) (GLenum target, GLuint address, GLenum matrix, GLenum transform); +typedef void (APIENTRYP PFNGLVERTEXATTRIBPOINTERNVPROC) (GLuint index, GLint fsize, GLenum type, GLsizei stride, const void *pointer); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DNVPROC) (GLuint index, GLdouble x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DVNVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FNVPROC) (GLuint index, GLfloat x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FVNVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SNVPROC) (GLuint index, GLshort x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SVNVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DNVPROC) (GLuint index, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DVNVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FNVPROC) (GLuint index, GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FVNVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SNVPROC) (GLuint index, GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SVNVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DNVPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DVNVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FNVPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FVNVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SNVPROC) (GLuint index, GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SVNVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DNVPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DVNVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FNVPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FVNVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SNVPROC) (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SVNVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UBNVPROC) (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UBVNVPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS1DVNVPROC) (GLuint index, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS1FVNVPROC) (GLuint index, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS1SVNVPROC) (GLuint index, GLsizei count, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS2DVNVPROC) (GLuint index, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS2FVNVPROC) (GLuint index, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS2SVNVPROC) (GLuint index, GLsizei count, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS3DVNVPROC) (GLuint index, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS3FVNVPROC) (GLuint index, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS3SVNVPROC) (GLuint index, GLsizei count, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4DVNVPROC) (GLuint index, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4FVNVPROC) (GLuint index, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4SVNVPROC) (GLuint index, GLsizei count, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4UBVNVPROC) (GLuint index, GLsizei count, const GLubyte *v); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLboolean APIENTRY glAreProgramsResidentNV (GLsizei n, const GLuint *programs, GLboolean *residences); +GLAPI void APIENTRY glBindProgramNV (GLenum target, GLuint id); +GLAPI void APIENTRY glDeleteProgramsNV (GLsizei n, const GLuint *programs); +GLAPI void APIENTRY glExecuteProgramNV (GLenum target, GLuint id, const GLfloat *params); +GLAPI void APIENTRY glGenProgramsNV (GLsizei n, GLuint *programs); +GLAPI void APIENTRY glGetProgramParameterdvNV (GLenum target, GLuint index, GLenum pname, GLdouble *params); +GLAPI void APIENTRY glGetProgramParameterfvNV (GLenum target, GLuint index, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetProgramivNV (GLuint id, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetProgramStringNV (GLuint id, GLenum pname, GLubyte *program); +GLAPI void APIENTRY glGetTrackMatrixivNV (GLenum target, GLuint address, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVertexAttribdvNV (GLuint index, GLenum pname, GLdouble *params); +GLAPI void APIENTRY glGetVertexAttribfvNV (GLuint index, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetVertexAttribivNV (GLuint index, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVertexAttribPointervNV (GLuint index, GLenum pname, void **pointer); +GLAPI GLboolean APIENTRY glIsProgramNV (GLuint id); +GLAPI void APIENTRY glLoadProgramNV (GLenum target, GLuint id, GLsizei len, const GLubyte *program); +GLAPI void APIENTRY glProgramParameter4dNV (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glProgramParameter4dvNV (GLenum target, GLuint index, const GLdouble *v); +GLAPI void APIENTRY glProgramParameter4fNV (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glProgramParameter4fvNV (GLenum target, GLuint index, const GLfloat *v); +GLAPI void APIENTRY glProgramParameters4dvNV (GLenum target, GLuint index, GLsizei count, const GLdouble *v); +GLAPI void APIENTRY glProgramParameters4fvNV (GLenum target, GLuint index, GLsizei count, const GLfloat *v); +GLAPI void APIENTRY glRequestResidentProgramsNV (GLsizei n, const GLuint *programs); +GLAPI void APIENTRY glTrackMatrixNV (GLenum target, GLuint address, GLenum matrix, GLenum transform); +GLAPI void APIENTRY glVertexAttribPointerNV (GLuint index, GLint fsize, GLenum type, GLsizei stride, const void *pointer); +GLAPI void APIENTRY glVertexAttrib1dNV (GLuint index, GLdouble x); +GLAPI void APIENTRY glVertexAttrib1dvNV (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib1fNV (GLuint index, GLfloat x); +GLAPI void APIENTRY glVertexAttrib1fvNV (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib1sNV (GLuint index, GLshort x); +GLAPI void APIENTRY glVertexAttrib1svNV (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib2dNV (GLuint index, GLdouble x, GLdouble y); +GLAPI void APIENTRY glVertexAttrib2dvNV (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib2fNV (GLuint index, GLfloat x, GLfloat y); +GLAPI void APIENTRY glVertexAttrib2fvNV (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib2sNV (GLuint index, GLshort x, GLshort y); +GLAPI void APIENTRY glVertexAttrib2svNV (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib3dNV (GLuint index, GLdouble x, GLdouble y, GLdouble z); +GLAPI void APIENTRY glVertexAttrib3dvNV (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib3fNV (GLuint index, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glVertexAttrib3fvNV (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib3sNV (GLuint index, GLshort x, GLshort y, GLshort z); +GLAPI void APIENTRY glVertexAttrib3svNV (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib4dNV (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +GLAPI void APIENTRY glVertexAttrib4dvNV (GLuint index, const GLdouble *v); +GLAPI void APIENTRY glVertexAttrib4fNV (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glVertexAttrib4fvNV (GLuint index, const GLfloat *v); +GLAPI void APIENTRY glVertexAttrib4sNV (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +GLAPI void APIENTRY glVertexAttrib4svNV (GLuint index, const GLshort *v); +GLAPI void APIENTRY glVertexAttrib4ubNV (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +GLAPI void APIENTRY glVertexAttrib4ubvNV (GLuint index, const GLubyte *v); +GLAPI void APIENTRY glVertexAttribs1dvNV (GLuint index, GLsizei count, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribs1fvNV (GLuint index, GLsizei count, const GLfloat *v); +GLAPI void APIENTRY glVertexAttribs1svNV (GLuint index, GLsizei count, const GLshort *v); +GLAPI void APIENTRY glVertexAttribs2dvNV (GLuint index, GLsizei count, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribs2fvNV (GLuint index, GLsizei count, const GLfloat *v); +GLAPI void APIENTRY glVertexAttribs2svNV (GLuint index, GLsizei count, const GLshort *v); +GLAPI void APIENTRY glVertexAttribs3dvNV (GLuint index, GLsizei count, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribs3fvNV (GLuint index, GLsizei count, const GLfloat *v); +GLAPI void APIENTRY glVertexAttribs3svNV (GLuint index, GLsizei count, const GLshort *v); +GLAPI void APIENTRY glVertexAttribs4dvNV (GLuint index, GLsizei count, const GLdouble *v); +GLAPI void APIENTRY glVertexAttribs4fvNV (GLuint index, GLsizei count, const GLfloat *v); +GLAPI void APIENTRY glVertexAttribs4svNV (GLuint index, GLsizei count, const GLshort *v); +GLAPI void APIENTRY glVertexAttribs4ubvNV (GLuint index, GLsizei count, const GLubyte *v); +#endif +#endif /* GL_NV_vertex_program */ + +#ifndef GL_NV_vertex_program1_1 +#define GL_NV_vertex_program1_1 1 +#endif /* GL_NV_vertex_program1_1 */ + +#ifndef GL_NV_vertex_program2 +#define GL_NV_vertex_program2 1 +#endif /* GL_NV_vertex_program2 */ + +#ifndef GL_NV_vertex_program2_option +#define GL_NV_vertex_program2_option 1 +#endif /* GL_NV_vertex_program2_option */ + +#ifndef GL_NV_vertex_program3 +#define GL_NV_vertex_program3 1 +#endif /* GL_NV_vertex_program3 */ + +#ifndef GL_NV_vertex_program4 +#define GL_NV_vertex_program4 1 +#define GL_VERTEX_ATTRIB_ARRAY_INTEGER_NV 0x88FD +#endif /* GL_NV_vertex_program4 */ + +#ifndef GL_NV_video_capture +#define GL_NV_video_capture 1 +#define GL_VIDEO_BUFFER_NV 0x9020 +#define GL_VIDEO_BUFFER_BINDING_NV 0x9021 +#define GL_FIELD_UPPER_NV 0x9022 +#define GL_FIELD_LOWER_NV 0x9023 +#define GL_NUM_VIDEO_CAPTURE_STREAMS_NV 0x9024 +#define GL_NEXT_VIDEO_CAPTURE_BUFFER_STATUS_NV 0x9025 +#define GL_VIDEO_CAPTURE_TO_422_SUPPORTED_NV 0x9026 +#define GL_LAST_VIDEO_CAPTURE_STATUS_NV 0x9027 +#define GL_VIDEO_BUFFER_PITCH_NV 0x9028 +#define GL_VIDEO_COLOR_CONVERSION_MATRIX_NV 0x9029 +#define GL_VIDEO_COLOR_CONVERSION_MAX_NV 0x902A +#define GL_VIDEO_COLOR_CONVERSION_MIN_NV 0x902B +#define GL_VIDEO_COLOR_CONVERSION_OFFSET_NV 0x902C +#define GL_VIDEO_BUFFER_INTERNAL_FORMAT_NV 0x902D +#define GL_PARTIAL_SUCCESS_NV 0x902E +#define GL_SUCCESS_NV 0x902F +#define GL_FAILURE_NV 0x9030 +#define GL_YCBYCR8_422_NV 0x9031 +#define GL_YCBAYCR8A_4224_NV 0x9032 +#define GL_Z6Y10Z6CB10Z6Y10Z6CR10_422_NV 0x9033 +#define GL_Z6Y10Z6CB10Z6A10Z6Y10Z6CR10Z6A10_4224_NV 0x9034 +#define GL_Z4Y12Z4CB12Z4Y12Z4CR12_422_NV 0x9035 +#define GL_Z4Y12Z4CB12Z4A12Z4Y12Z4CR12Z4A12_4224_NV 0x9036 +#define GL_Z4Y12Z4CB12Z4CR12_444_NV 0x9037 +#define GL_VIDEO_CAPTURE_FRAME_WIDTH_NV 0x9038 +#define GL_VIDEO_CAPTURE_FRAME_HEIGHT_NV 0x9039 +#define GL_VIDEO_CAPTURE_FIELD_UPPER_HEIGHT_NV 0x903A +#define GL_VIDEO_CAPTURE_FIELD_LOWER_HEIGHT_NV 0x903B +#define GL_VIDEO_CAPTURE_SURFACE_ORIGIN_NV 0x903C +typedef void (APIENTRYP PFNGLBEGINVIDEOCAPTURENVPROC) (GLuint video_capture_slot); +typedef void (APIENTRYP PFNGLBINDVIDEOCAPTURESTREAMBUFFERNVPROC) (GLuint video_capture_slot, GLuint stream, GLenum frame_region, GLintptrARB offset); +typedef void (APIENTRYP PFNGLBINDVIDEOCAPTURESTREAMTEXTURENVPROC) (GLuint video_capture_slot, GLuint stream, GLenum frame_region, GLenum target, GLuint texture); +typedef void (APIENTRYP PFNGLENDVIDEOCAPTURENVPROC) (GLuint video_capture_slot); +typedef void (APIENTRYP PFNGLGETVIDEOCAPTUREIVNVPROC) (GLuint video_capture_slot, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVIDEOCAPTURESTREAMIVNVPROC) (GLuint video_capture_slot, GLuint stream, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVIDEOCAPTURESTREAMFVNVPROC) (GLuint video_capture_slot, GLuint stream, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVIDEOCAPTURESTREAMDVNVPROC) (GLuint video_capture_slot, GLuint stream, GLenum pname, GLdouble *params); +typedef GLenum (APIENTRYP PFNGLVIDEOCAPTURENVPROC) (GLuint video_capture_slot, GLuint *sequence_num, GLuint64EXT *capture_time); +typedef void (APIENTRYP PFNGLVIDEOCAPTURESTREAMPARAMETERIVNVPROC) (GLuint video_capture_slot, GLuint stream, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLVIDEOCAPTURESTREAMPARAMETERFVNVPROC) (GLuint video_capture_slot, GLuint stream, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLVIDEOCAPTURESTREAMPARAMETERDVNVPROC) (GLuint video_capture_slot, GLuint stream, GLenum pname, const GLdouble *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBeginVideoCaptureNV (GLuint video_capture_slot); +GLAPI void APIENTRY glBindVideoCaptureStreamBufferNV (GLuint video_capture_slot, GLuint stream, GLenum frame_region, GLintptrARB offset); +GLAPI void APIENTRY glBindVideoCaptureStreamTextureNV (GLuint video_capture_slot, GLuint stream, GLenum frame_region, GLenum target, GLuint texture); +GLAPI void APIENTRY glEndVideoCaptureNV (GLuint video_capture_slot); +GLAPI void APIENTRY glGetVideoCaptureivNV (GLuint video_capture_slot, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVideoCaptureStreamivNV (GLuint video_capture_slot, GLuint stream, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetVideoCaptureStreamfvNV (GLuint video_capture_slot, GLuint stream, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetVideoCaptureStreamdvNV (GLuint video_capture_slot, GLuint stream, GLenum pname, GLdouble *params); +GLAPI GLenum APIENTRY glVideoCaptureNV (GLuint video_capture_slot, GLuint *sequence_num, GLuint64EXT *capture_time); +GLAPI void APIENTRY glVideoCaptureStreamParameterivNV (GLuint video_capture_slot, GLuint stream, GLenum pname, const GLint *params); +GLAPI void APIENTRY glVideoCaptureStreamParameterfvNV (GLuint video_capture_slot, GLuint stream, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glVideoCaptureStreamParameterdvNV (GLuint video_capture_slot, GLuint stream, GLenum pname, const GLdouble *params); +#endif +#endif /* GL_NV_video_capture */ + +#ifndef GL_NV_viewport_array2 +#define GL_NV_viewport_array2 1 +#endif /* GL_NV_viewport_array2 */ + +#ifndef GL_NV_viewport_swizzle +#define GL_NV_viewport_swizzle 1 +#define GL_VIEWPORT_SWIZZLE_POSITIVE_X_NV 0x9350 +#define GL_VIEWPORT_SWIZZLE_NEGATIVE_X_NV 0x9351 +#define GL_VIEWPORT_SWIZZLE_POSITIVE_Y_NV 0x9352 +#define GL_VIEWPORT_SWIZZLE_NEGATIVE_Y_NV 0x9353 +#define GL_VIEWPORT_SWIZZLE_POSITIVE_Z_NV 0x9354 +#define GL_VIEWPORT_SWIZZLE_NEGATIVE_Z_NV 0x9355 +#define GL_VIEWPORT_SWIZZLE_POSITIVE_W_NV 0x9356 +#define GL_VIEWPORT_SWIZZLE_NEGATIVE_W_NV 0x9357 +#define GL_VIEWPORT_SWIZZLE_X_NV 0x9358 +#define GL_VIEWPORT_SWIZZLE_Y_NV 0x9359 +#define GL_VIEWPORT_SWIZZLE_Z_NV 0x935A +#define GL_VIEWPORT_SWIZZLE_W_NV 0x935B +typedef void (APIENTRYP PFNGLVIEWPORTSWIZZLENVPROC) (GLuint index, GLenum swizzlex, GLenum swizzley, GLenum swizzlez, GLenum swizzlew); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glViewportSwizzleNV (GLuint index, GLenum swizzlex, GLenum swizzley, GLenum swizzlez, GLenum swizzlew); +#endif +#endif /* GL_NV_viewport_swizzle */ + +#ifndef GL_OML_interlace +#define GL_OML_interlace 1 +#define GL_INTERLACE_OML 0x8980 +#define GL_INTERLACE_READ_OML 0x8981 +#endif /* GL_OML_interlace */ + +#ifndef GL_OML_resample +#define GL_OML_resample 1 +#define GL_PACK_RESAMPLE_OML 0x8984 +#define GL_UNPACK_RESAMPLE_OML 0x8985 +#define GL_RESAMPLE_REPLICATE_OML 0x8986 +#define GL_RESAMPLE_ZERO_FILL_OML 0x8987 +#define GL_RESAMPLE_AVERAGE_OML 0x8988 +#define GL_RESAMPLE_DECIMATE_OML 0x8989 +#endif /* GL_OML_resample */ + +#ifndef GL_OML_subsample +#define GL_OML_subsample 1 +#define GL_FORMAT_SUBSAMPLE_24_24_OML 0x8982 +#define GL_FORMAT_SUBSAMPLE_244_244_OML 0x8983 +#endif /* GL_OML_subsample */ + +#ifndef GL_OVR_multiview +#define GL_OVR_multiview 1 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_NUM_VIEWS_OVR 0x9630 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_BASE_VIEW_INDEX_OVR 0x9632 +#define GL_MAX_VIEWS_OVR 0x9631 +#define GL_FRAMEBUFFER_INCOMPLETE_VIEW_TARGETS_OVR 0x9633 +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVRPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint baseViewIndex, GLsizei numViews); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFramebufferTextureMultiviewOVR (GLenum target, GLenum attachment, GLuint texture, GLint level, GLint baseViewIndex, GLsizei numViews); +#endif +#endif /* GL_OVR_multiview */ + +#ifndef GL_OVR_multiview2 +#define GL_OVR_multiview2 1 +#endif /* GL_OVR_multiview2 */ + +#ifndef GL_PGI_misc_hints +#define GL_PGI_misc_hints 1 +#define GL_PREFER_DOUBLEBUFFER_HINT_PGI 0x1A1F8 +#define GL_CONSERVE_MEMORY_HINT_PGI 0x1A1FD +#define GL_RECLAIM_MEMORY_HINT_PGI 0x1A1FE +#define GL_NATIVE_GRAPHICS_HANDLE_PGI 0x1A202 +#define GL_NATIVE_GRAPHICS_BEGIN_HINT_PGI 0x1A203 +#define GL_NATIVE_GRAPHICS_END_HINT_PGI 0x1A204 +#define GL_ALWAYS_FAST_HINT_PGI 0x1A20C +#define GL_ALWAYS_SOFT_HINT_PGI 0x1A20D +#define GL_ALLOW_DRAW_OBJ_HINT_PGI 0x1A20E +#define GL_ALLOW_DRAW_WIN_HINT_PGI 0x1A20F +#define GL_ALLOW_DRAW_FRG_HINT_PGI 0x1A210 +#define GL_ALLOW_DRAW_MEM_HINT_PGI 0x1A211 +#define GL_STRICT_DEPTHFUNC_HINT_PGI 0x1A216 +#define GL_STRICT_LIGHTING_HINT_PGI 0x1A217 +#define GL_STRICT_SCISSOR_HINT_PGI 0x1A218 +#define GL_FULL_STIPPLE_HINT_PGI 0x1A219 +#define GL_CLIP_NEAR_HINT_PGI 0x1A220 +#define GL_CLIP_FAR_HINT_PGI 0x1A221 +#define GL_WIDE_LINE_HINT_PGI 0x1A222 +#define GL_BACK_NORMALS_HINT_PGI 0x1A223 +typedef void (APIENTRYP PFNGLHINTPGIPROC) (GLenum target, GLint mode); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glHintPGI (GLenum target, GLint mode); +#endif +#endif /* GL_PGI_misc_hints */ + +#ifndef GL_PGI_vertex_hints +#define GL_PGI_vertex_hints 1 +#define GL_VERTEX_DATA_HINT_PGI 0x1A22A +#define GL_VERTEX_CONSISTENT_HINT_PGI 0x1A22B +#define GL_MATERIAL_SIDE_HINT_PGI 0x1A22C +#define GL_MAX_VERTEX_HINT_PGI 0x1A22D +#define GL_COLOR3_BIT_PGI 0x00010000 +#define GL_COLOR4_BIT_PGI 0x00020000 +#define GL_EDGEFLAG_BIT_PGI 0x00040000 +#define GL_INDEX_BIT_PGI 0x00080000 +#define GL_MAT_AMBIENT_BIT_PGI 0x00100000 +#define GL_MAT_AMBIENT_AND_DIFFUSE_BIT_PGI 0x00200000 +#define GL_MAT_DIFFUSE_BIT_PGI 0x00400000 +#define GL_MAT_EMISSION_BIT_PGI 0x00800000 +#define GL_MAT_COLOR_INDEXES_BIT_PGI 0x01000000 +#define GL_MAT_SHININESS_BIT_PGI 0x02000000 +#define GL_MAT_SPECULAR_BIT_PGI 0x04000000 +#define GL_NORMAL_BIT_PGI 0x08000000 +#define GL_TEXCOORD1_BIT_PGI 0x10000000 +#define GL_TEXCOORD2_BIT_PGI 0x20000000 +#define GL_TEXCOORD3_BIT_PGI 0x40000000 +#define GL_TEXCOORD4_BIT_PGI 0x80000000 +#define GL_VERTEX23_BIT_PGI 0x00000004 +#define GL_VERTEX4_BIT_PGI 0x00000008 +#endif /* GL_PGI_vertex_hints */ + +#ifndef GL_REND_screen_coordinates +#define GL_REND_screen_coordinates 1 +#define GL_SCREEN_COORDINATES_REND 0x8490 +#define GL_INVERTED_SCREEN_W_REND 0x8491 +#endif /* GL_REND_screen_coordinates */ + +#ifndef GL_S3_s3tc +#define GL_S3_s3tc 1 +#define GL_RGB_S3TC 0x83A0 +#define GL_RGB4_S3TC 0x83A1 +#define GL_RGBA_S3TC 0x83A2 +#define GL_RGBA4_S3TC 0x83A3 +#define GL_RGBA_DXT5_S3TC 0x83A4 +#define GL_RGBA4_DXT5_S3TC 0x83A5 +#endif /* GL_S3_s3tc */ + +#ifndef GL_SGIS_detail_texture +#define GL_SGIS_detail_texture 1 +#define GL_DETAIL_TEXTURE_2D_SGIS 0x8095 +#define GL_DETAIL_TEXTURE_2D_BINDING_SGIS 0x8096 +#define GL_LINEAR_DETAIL_SGIS 0x8097 +#define GL_LINEAR_DETAIL_ALPHA_SGIS 0x8098 +#define GL_LINEAR_DETAIL_COLOR_SGIS 0x8099 +#define GL_DETAIL_TEXTURE_LEVEL_SGIS 0x809A +#define GL_DETAIL_TEXTURE_MODE_SGIS 0x809B +#define GL_DETAIL_TEXTURE_FUNC_POINTS_SGIS 0x809C +typedef void (APIENTRYP PFNGLDETAILTEXFUNCSGISPROC) (GLenum target, GLsizei n, const GLfloat *points); +typedef void (APIENTRYP PFNGLGETDETAILTEXFUNCSGISPROC) (GLenum target, GLfloat *points); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDetailTexFuncSGIS (GLenum target, GLsizei n, const GLfloat *points); +GLAPI void APIENTRY glGetDetailTexFuncSGIS (GLenum target, GLfloat *points); +#endif +#endif /* GL_SGIS_detail_texture */ + +#ifndef GL_SGIS_fog_function +#define GL_SGIS_fog_function 1 +#define GL_FOG_FUNC_SGIS 0x812A +#define GL_FOG_FUNC_POINTS_SGIS 0x812B +#define GL_MAX_FOG_FUNC_POINTS_SGIS 0x812C +typedef void (APIENTRYP PFNGLFOGFUNCSGISPROC) (GLsizei n, const GLfloat *points); +typedef void (APIENTRYP PFNGLGETFOGFUNCSGISPROC) (GLfloat *points); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFogFuncSGIS (GLsizei n, const GLfloat *points); +GLAPI void APIENTRY glGetFogFuncSGIS (GLfloat *points); +#endif +#endif /* GL_SGIS_fog_function */ + +#ifndef GL_SGIS_generate_mipmap +#define GL_SGIS_generate_mipmap 1 +#define GL_GENERATE_MIPMAP_SGIS 0x8191 +#define GL_GENERATE_MIPMAP_HINT_SGIS 0x8192 +#endif /* GL_SGIS_generate_mipmap */ + +#ifndef GL_SGIS_multisample +#define GL_SGIS_multisample 1 +#define GL_MULTISAMPLE_SGIS 0x809D +#define GL_SAMPLE_ALPHA_TO_MASK_SGIS 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_SGIS 0x809F +#define GL_SAMPLE_MASK_SGIS 0x80A0 +#define GL_1PASS_SGIS 0x80A1 +#define GL_2PASS_0_SGIS 0x80A2 +#define GL_2PASS_1_SGIS 0x80A3 +#define GL_4PASS_0_SGIS 0x80A4 +#define GL_4PASS_1_SGIS 0x80A5 +#define GL_4PASS_2_SGIS 0x80A6 +#define GL_4PASS_3_SGIS 0x80A7 +#define GL_SAMPLE_BUFFERS_SGIS 0x80A8 +#define GL_SAMPLES_SGIS 0x80A9 +#define GL_SAMPLE_MASK_VALUE_SGIS 0x80AA +#define GL_SAMPLE_MASK_INVERT_SGIS 0x80AB +#define GL_SAMPLE_PATTERN_SGIS 0x80AC +typedef void (APIENTRYP PFNGLSAMPLEMASKSGISPROC) (GLclampf value, GLboolean invert); +typedef void (APIENTRYP PFNGLSAMPLEPATTERNSGISPROC) (GLenum pattern); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSampleMaskSGIS (GLclampf value, GLboolean invert); +GLAPI void APIENTRY glSamplePatternSGIS (GLenum pattern); +#endif +#endif /* GL_SGIS_multisample */ + +#ifndef GL_SGIS_pixel_texture +#define GL_SGIS_pixel_texture 1 +#define GL_PIXEL_TEXTURE_SGIS 0x8353 +#define GL_PIXEL_FRAGMENT_RGB_SOURCE_SGIS 0x8354 +#define GL_PIXEL_FRAGMENT_ALPHA_SOURCE_SGIS 0x8355 +#define GL_PIXEL_GROUP_COLOR_SGIS 0x8356 +typedef void (APIENTRYP PFNGLPIXELTEXGENPARAMETERISGISPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPIXELTEXGENPARAMETERIVSGISPROC) (GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLPIXELTEXGENPARAMETERFSGISPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPIXELTEXGENPARAMETERFVSGISPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETPIXELTEXGENPARAMETERIVSGISPROC) (GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETPIXELTEXGENPARAMETERFVSGISPROC) (GLenum pname, GLfloat *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPixelTexGenParameteriSGIS (GLenum pname, GLint param); +GLAPI void APIENTRY glPixelTexGenParameterivSGIS (GLenum pname, const GLint *params); +GLAPI void APIENTRY glPixelTexGenParameterfSGIS (GLenum pname, GLfloat param); +GLAPI void APIENTRY glPixelTexGenParameterfvSGIS (GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glGetPixelTexGenParameterivSGIS (GLenum pname, GLint *params); +GLAPI void APIENTRY glGetPixelTexGenParameterfvSGIS (GLenum pname, GLfloat *params); +#endif +#endif /* GL_SGIS_pixel_texture */ + +#ifndef GL_SGIS_point_line_texgen +#define GL_SGIS_point_line_texgen 1 +#define GL_EYE_DISTANCE_TO_POINT_SGIS 0x81F0 +#define GL_OBJECT_DISTANCE_TO_POINT_SGIS 0x81F1 +#define GL_EYE_DISTANCE_TO_LINE_SGIS 0x81F2 +#define GL_OBJECT_DISTANCE_TO_LINE_SGIS 0x81F3 +#define GL_EYE_POINT_SGIS 0x81F4 +#define GL_OBJECT_POINT_SGIS 0x81F5 +#define GL_EYE_LINE_SGIS 0x81F6 +#define GL_OBJECT_LINE_SGIS 0x81F7 +#endif /* GL_SGIS_point_line_texgen */ + +#ifndef GL_SGIS_point_parameters +#define GL_SGIS_point_parameters 1 +#define GL_POINT_SIZE_MIN_SGIS 0x8126 +#define GL_POINT_SIZE_MAX_SGIS 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE_SGIS 0x8128 +#define GL_DISTANCE_ATTENUATION_SGIS 0x8129 +typedef void (APIENTRYP PFNGLPOINTPARAMETERFSGISPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFVSGISPROC) (GLenum pname, const GLfloat *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPointParameterfSGIS (GLenum pname, GLfloat param); +GLAPI void APIENTRY glPointParameterfvSGIS (GLenum pname, const GLfloat *params); +#endif +#endif /* GL_SGIS_point_parameters */ + +#ifndef GL_SGIS_sharpen_texture +#define GL_SGIS_sharpen_texture 1 +#define GL_LINEAR_SHARPEN_SGIS 0x80AD +#define GL_LINEAR_SHARPEN_ALPHA_SGIS 0x80AE +#define GL_LINEAR_SHARPEN_COLOR_SGIS 0x80AF +#define GL_SHARPEN_TEXTURE_FUNC_POINTS_SGIS 0x80B0 +typedef void (APIENTRYP PFNGLSHARPENTEXFUNCSGISPROC) (GLenum target, GLsizei n, const GLfloat *points); +typedef void (APIENTRYP PFNGLGETSHARPENTEXFUNCSGISPROC) (GLenum target, GLfloat *points); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSharpenTexFuncSGIS (GLenum target, GLsizei n, const GLfloat *points); +GLAPI void APIENTRY glGetSharpenTexFuncSGIS (GLenum target, GLfloat *points); +#endif +#endif /* GL_SGIS_sharpen_texture */ + +#ifndef GL_SGIS_texture4D +#define GL_SGIS_texture4D 1 +#define GL_PACK_SKIP_VOLUMES_SGIS 0x8130 +#define GL_PACK_IMAGE_DEPTH_SGIS 0x8131 +#define GL_UNPACK_SKIP_VOLUMES_SGIS 0x8132 +#define GL_UNPACK_IMAGE_DEPTH_SGIS 0x8133 +#define GL_TEXTURE_4D_SGIS 0x8134 +#define GL_PROXY_TEXTURE_4D_SGIS 0x8135 +#define GL_TEXTURE_4DSIZE_SGIS 0x8136 +#define GL_TEXTURE_WRAP_Q_SGIS 0x8137 +#define GL_MAX_4D_TEXTURE_SIZE_SGIS 0x8138 +#define GL_TEXTURE_4D_BINDING_SGIS 0x814F +typedef void (APIENTRYP PFNGLTEXIMAGE4DSGISPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLsizei size4d, GLint border, GLenum format, GLenum type, const void *pixels); +typedef void (APIENTRYP PFNGLTEXSUBIMAGE4DSGISPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint woffset, GLsizei width, GLsizei height, GLsizei depth, GLsizei size4d, GLenum format, GLenum type, const void *pixels); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexImage4DSGIS (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLsizei size4d, GLint border, GLenum format, GLenum type, const void *pixels); +GLAPI void APIENTRY glTexSubImage4DSGIS (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint woffset, GLsizei width, GLsizei height, GLsizei depth, GLsizei size4d, GLenum format, GLenum type, const void *pixels); +#endif +#endif /* GL_SGIS_texture4D */ + +#ifndef GL_SGIS_texture_border_clamp +#define GL_SGIS_texture_border_clamp 1 +#define GL_CLAMP_TO_BORDER_SGIS 0x812D +#endif /* GL_SGIS_texture_border_clamp */ + +#ifndef GL_SGIS_texture_color_mask +#define GL_SGIS_texture_color_mask 1 +#define GL_TEXTURE_COLOR_WRITEMASK_SGIS 0x81EF +typedef void (APIENTRYP PFNGLTEXTURECOLORMASKSGISPROC) (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTextureColorMaskSGIS (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +#endif +#endif /* GL_SGIS_texture_color_mask */ + +#ifndef GL_SGIS_texture_edge_clamp +#define GL_SGIS_texture_edge_clamp 1 +#define GL_CLAMP_TO_EDGE_SGIS 0x812F +#endif /* GL_SGIS_texture_edge_clamp */ + +#ifndef GL_SGIS_texture_filter4 +#define GL_SGIS_texture_filter4 1 +#define GL_FILTER4_SGIS 0x8146 +#define GL_TEXTURE_FILTER4_SIZE_SGIS 0x8147 +typedef void (APIENTRYP PFNGLGETTEXFILTERFUNCSGISPROC) (GLenum target, GLenum filter, GLfloat *weights); +typedef void (APIENTRYP PFNGLTEXFILTERFUNCSGISPROC) (GLenum target, GLenum filter, GLsizei n, const GLfloat *weights); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetTexFilterFuncSGIS (GLenum target, GLenum filter, GLfloat *weights); +GLAPI void APIENTRY glTexFilterFuncSGIS (GLenum target, GLenum filter, GLsizei n, const GLfloat *weights); +#endif +#endif /* GL_SGIS_texture_filter4 */ + +#ifndef GL_SGIS_texture_lod +#define GL_SGIS_texture_lod 1 +#define GL_TEXTURE_MIN_LOD_SGIS 0x813A +#define GL_TEXTURE_MAX_LOD_SGIS 0x813B +#define GL_TEXTURE_BASE_LEVEL_SGIS 0x813C +#define GL_TEXTURE_MAX_LEVEL_SGIS 0x813D +#endif /* GL_SGIS_texture_lod */ + +#ifndef GL_SGIS_texture_select +#define GL_SGIS_texture_select 1 +#define GL_DUAL_ALPHA4_SGIS 0x8110 +#define GL_DUAL_ALPHA8_SGIS 0x8111 +#define GL_DUAL_ALPHA12_SGIS 0x8112 +#define GL_DUAL_ALPHA16_SGIS 0x8113 +#define GL_DUAL_LUMINANCE4_SGIS 0x8114 +#define GL_DUAL_LUMINANCE8_SGIS 0x8115 +#define GL_DUAL_LUMINANCE12_SGIS 0x8116 +#define GL_DUAL_LUMINANCE16_SGIS 0x8117 +#define GL_DUAL_INTENSITY4_SGIS 0x8118 +#define GL_DUAL_INTENSITY8_SGIS 0x8119 +#define GL_DUAL_INTENSITY12_SGIS 0x811A +#define GL_DUAL_INTENSITY16_SGIS 0x811B +#define GL_DUAL_LUMINANCE_ALPHA4_SGIS 0x811C +#define GL_DUAL_LUMINANCE_ALPHA8_SGIS 0x811D +#define GL_QUAD_ALPHA4_SGIS 0x811E +#define GL_QUAD_ALPHA8_SGIS 0x811F +#define GL_QUAD_LUMINANCE4_SGIS 0x8120 +#define GL_QUAD_LUMINANCE8_SGIS 0x8121 +#define GL_QUAD_INTENSITY4_SGIS 0x8122 +#define GL_QUAD_INTENSITY8_SGIS 0x8123 +#define GL_DUAL_TEXTURE_SELECT_SGIS 0x8124 +#define GL_QUAD_TEXTURE_SELECT_SGIS 0x8125 +#endif /* GL_SGIS_texture_select */ + +#ifndef GL_SGIX_async +#define GL_SGIX_async 1 +#define GL_ASYNC_MARKER_SGIX 0x8329 +typedef void (APIENTRYP PFNGLASYNCMARKERSGIXPROC) (GLuint marker); +typedef GLint (APIENTRYP PFNGLFINISHASYNCSGIXPROC) (GLuint *markerp); +typedef GLint (APIENTRYP PFNGLPOLLASYNCSGIXPROC) (GLuint *markerp); +typedef GLuint (APIENTRYP PFNGLGENASYNCMARKERSSGIXPROC) (GLsizei range); +typedef void (APIENTRYP PFNGLDELETEASYNCMARKERSSGIXPROC) (GLuint marker, GLsizei range); +typedef GLboolean (APIENTRYP PFNGLISASYNCMARKERSGIXPROC) (GLuint marker); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glAsyncMarkerSGIX (GLuint marker); +GLAPI GLint APIENTRY glFinishAsyncSGIX (GLuint *markerp); +GLAPI GLint APIENTRY glPollAsyncSGIX (GLuint *markerp); +GLAPI GLuint APIENTRY glGenAsyncMarkersSGIX (GLsizei range); +GLAPI void APIENTRY glDeleteAsyncMarkersSGIX (GLuint marker, GLsizei range); +GLAPI GLboolean APIENTRY glIsAsyncMarkerSGIX (GLuint marker); +#endif +#endif /* GL_SGIX_async */ + +#ifndef GL_SGIX_async_histogram +#define GL_SGIX_async_histogram 1 +#define GL_ASYNC_HISTOGRAM_SGIX 0x832C +#define GL_MAX_ASYNC_HISTOGRAM_SGIX 0x832D +#endif /* GL_SGIX_async_histogram */ + +#ifndef GL_SGIX_async_pixel +#define GL_SGIX_async_pixel 1 +#define GL_ASYNC_TEX_IMAGE_SGIX 0x835C +#define GL_ASYNC_DRAW_PIXELS_SGIX 0x835D +#define GL_ASYNC_READ_PIXELS_SGIX 0x835E +#define GL_MAX_ASYNC_TEX_IMAGE_SGIX 0x835F +#define GL_MAX_ASYNC_DRAW_PIXELS_SGIX 0x8360 +#define GL_MAX_ASYNC_READ_PIXELS_SGIX 0x8361 +#endif /* GL_SGIX_async_pixel */ + +#ifndef GL_SGIX_blend_alpha_minmax +#define GL_SGIX_blend_alpha_minmax 1 +#define GL_ALPHA_MIN_SGIX 0x8320 +#define GL_ALPHA_MAX_SGIX 0x8321 +#endif /* GL_SGIX_blend_alpha_minmax */ + +#ifndef GL_SGIX_calligraphic_fragment +#define GL_SGIX_calligraphic_fragment 1 +#define GL_CALLIGRAPHIC_FRAGMENT_SGIX 0x8183 +#endif /* GL_SGIX_calligraphic_fragment */ + +#ifndef GL_SGIX_clipmap +#define GL_SGIX_clipmap 1 +#define GL_LINEAR_CLIPMAP_LINEAR_SGIX 0x8170 +#define GL_TEXTURE_CLIPMAP_CENTER_SGIX 0x8171 +#define GL_TEXTURE_CLIPMAP_FRAME_SGIX 0x8172 +#define GL_TEXTURE_CLIPMAP_OFFSET_SGIX 0x8173 +#define GL_TEXTURE_CLIPMAP_VIRTUAL_DEPTH_SGIX 0x8174 +#define GL_TEXTURE_CLIPMAP_LOD_OFFSET_SGIX 0x8175 +#define GL_TEXTURE_CLIPMAP_DEPTH_SGIX 0x8176 +#define GL_MAX_CLIPMAP_DEPTH_SGIX 0x8177 +#define GL_MAX_CLIPMAP_VIRTUAL_DEPTH_SGIX 0x8178 +#define GL_NEAREST_CLIPMAP_NEAREST_SGIX 0x844D +#define GL_NEAREST_CLIPMAP_LINEAR_SGIX 0x844E +#define GL_LINEAR_CLIPMAP_NEAREST_SGIX 0x844F +#endif /* GL_SGIX_clipmap */ + +#ifndef GL_SGIX_convolution_accuracy +#define GL_SGIX_convolution_accuracy 1 +#define GL_CONVOLUTION_HINT_SGIX 0x8316 +#endif /* GL_SGIX_convolution_accuracy */ + +#ifndef GL_SGIX_depth_pass_instrument +#define GL_SGIX_depth_pass_instrument 1 +#endif /* GL_SGIX_depth_pass_instrument */ + +#ifndef GL_SGIX_depth_texture +#define GL_SGIX_depth_texture 1 +#define GL_DEPTH_COMPONENT16_SGIX 0x81A5 +#define GL_DEPTH_COMPONENT24_SGIX 0x81A6 +#define GL_DEPTH_COMPONENT32_SGIX 0x81A7 +#endif /* GL_SGIX_depth_texture */ + +#ifndef GL_SGIX_flush_raster +#define GL_SGIX_flush_raster 1 +typedef void (APIENTRYP PFNGLFLUSHRASTERSGIXPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFlushRasterSGIX (void); +#endif +#endif /* GL_SGIX_flush_raster */ + +#ifndef GL_SGIX_fog_offset +#define GL_SGIX_fog_offset 1 +#define GL_FOG_OFFSET_SGIX 0x8198 +#define GL_FOG_OFFSET_VALUE_SGIX 0x8199 +#endif /* GL_SGIX_fog_offset */ + +#ifndef GL_SGIX_fragment_lighting +#define GL_SGIX_fragment_lighting 1 +#define GL_FRAGMENT_LIGHTING_SGIX 0x8400 +#define GL_FRAGMENT_COLOR_MATERIAL_SGIX 0x8401 +#define GL_FRAGMENT_COLOR_MATERIAL_FACE_SGIX 0x8402 +#define GL_FRAGMENT_COLOR_MATERIAL_PARAMETER_SGIX 0x8403 +#define GL_MAX_FRAGMENT_LIGHTS_SGIX 0x8404 +#define GL_MAX_ACTIVE_LIGHTS_SGIX 0x8405 +#define GL_CURRENT_RASTER_NORMAL_SGIX 0x8406 +#define GL_LIGHT_ENV_MODE_SGIX 0x8407 +#define GL_FRAGMENT_LIGHT_MODEL_LOCAL_VIEWER_SGIX 0x8408 +#define GL_FRAGMENT_LIGHT_MODEL_TWO_SIDE_SGIX 0x8409 +#define GL_FRAGMENT_LIGHT_MODEL_AMBIENT_SGIX 0x840A +#define GL_FRAGMENT_LIGHT_MODEL_NORMAL_INTERPOLATION_SGIX 0x840B +#define GL_FRAGMENT_LIGHT0_SGIX 0x840C +#define GL_FRAGMENT_LIGHT1_SGIX 0x840D +#define GL_FRAGMENT_LIGHT2_SGIX 0x840E +#define GL_FRAGMENT_LIGHT3_SGIX 0x840F +#define GL_FRAGMENT_LIGHT4_SGIX 0x8410 +#define GL_FRAGMENT_LIGHT5_SGIX 0x8411 +#define GL_FRAGMENT_LIGHT6_SGIX 0x8412 +#define GL_FRAGMENT_LIGHT7_SGIX 0x8413 +typedef void (APIENTRYP PFNGLFRAGMENTCOLORMATERIALSGIXPROC) (GLenum face, GLenum mode); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTFSGIXPROC) (GLenum light, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTFVSGIXPROC) (GLenum light, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTISGIXPROC) (GLenum light, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTIVSGIXPROC) (GLenum light, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTMODELFSGIXPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTMODELFVSGIXPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTMODELISGIXPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTMODELIVSGIXPROC) (GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLFRAGMENTMATERIALFSGIXPROC) (GLenum face, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLFRAGMENTMATERIALFVSGIXPROC) (GLenum face, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLFRAGMENTMATERIALISGIXPROC) (GLenum face, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLFRAGMENTMATERIALIVSGIXPROC) (GLenum face, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLGETFRAGMENTLIGHTFVSGIXPROC) (GLenum light, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETFRAGMENTLIGHTIVSGIXPROC) (GLenum light, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETFRAGMENTMATERIALFVSGIXPROC) (GLenum face, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETFRAGMENTMATERIALIVSGIXPROC) (GLenum face, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLLIGHTENVISGIXPROC) (GLenum pname, GLint param); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFragmentColorMaterialSGIX (GLenum face, GLenum mode); +GLAPI void APIENTRY glFragmentLightfSGIX (GLenum light, GLenum pname, GLfloat param); +GLAPI void APIENTRY glFragmentLightfvSGIX (GLenum light, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glFragmentLightiSGIX (GLenum light, GLenum pname, GLint param); +GLAPI void APIENTRY glFragmentLightivSGIX (GLenum light, GLenum pname, const GLint *params); +GLAPI void APIENTRY glFragmentLightModelfSGIX (GLenum pname, GLfloat param); +GLAPI void APIENTRY glFragmentLightModelfvSGIX (GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glFragmentLightModeliSGIX (GLenum pname, GLint param); +GLAPI void APIENTRY glFragmentLightModelivSGIX (GLenum pname, const GLint *params); +GLAPI void APIENTRY glFragmentMaterialfSGIX (GLenum face, GLenum pname, GLfloat param); +GLAPI void APIENTRY glFragmentMaterialfvSGIX (GLenum face, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glFragmentMaterialiSGIX (GLenum face, GLenum pname, GLint param); +GLAPI void APIENTRY glFragmentMaterialivSGIX (GLenum face, GLenum pname, const GLint *params); +GLAPI void APIENTRY glGetFragmentLightfvSGIX (GLenum light, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetFragmentLightivSGIX (GLenum light, GLenum pname, GLint *params); +GLAPI void APIENTRY glGetFragmentMaterialfvSGIX (GLenum face, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetFragmentMaterialivSGIX (GLenum face, GLenum pname, GLint *params); +GLAPI void APIENTRY glLightEnviSGIX (GLenum pname, GLint param); +#endif +#endif /* GL_SGIX_fragment_lighting */ + +#ifndef GL_SGIX_framezoom +#define GL_SGIX_framezoom 1 +#define GL_FRAMEZOOM_SGIX 0x818B +#define GL_FRAMEZOOM_FACTOR_SGIX 0x818C +#define GL_MAX_FRAMEZOOM_FACTOR_SGIX 0x818D +typedef void (APIENTRYP PFNGLFRAMEZOOMSGIXPROC) (GLint factor); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFrameZoomSGIX (GLint factor); +#endif +#endif /* GL_SGIX_framezoom */ + +#ifndef GL_SGIX_igloo_interface +#define GL_SGIX_igloo_interface 1 +typedef void (APIENTRYP PFNGLIGLOOINTERFACESGIXPROC) (GLenum pname, const void *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glIglooInterfaceSGIX (GLenum pname, const void *params); +#endif +#endif /* GL_SGIX_igloo_interface */ + +#ifndef GL_SGIX_instruments +#define GL_SGIX_instruments 1 +#define GL_INSTRUMENT_BUFFER_POINTER_SGIX 0x8180 +#define GL_INSTRUMENT_MEASUREMENTS_SGIX 0x8181 +typedef GLint (APIENTRYP PFNGLGETINSTRUMENTSSGIXPROC) (void); +typedef void (APIENTRYP PFNGLINSTRUMENTSBUFFERSGIXPROC) (GLsizei size, GLint *buffer); +typedef GLint (APIENTRYP PFNGLPOLLINSTRUMENTSSGIXPROC) (GLint *marker_p); +typedef void (APIENTRYP PFNGLREADINSTRUMENTSSGIXPROC) (GLint marker); +typedef void (APIENTRYP PFNGLSTARTINSTRUMENTSSGIXPROC) (void); +typedef void (APIENTRYP PFNGLSTOPINSTRUMENTSSGIXPROC) (GLint marker); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLint APIENTRY glGetInstrumentsSGIX (void); +GLAPI void APIENTRY glInstrumentsBufferSGIX (GLsizei size, GLint *buffer); +GLAPI GLint APIENTRY glPollInstrumentsSGIX (GLint *marker_p); +GLAPI void APIENTRY glReadInstrumentsSGIX (GLint marker); +GLAPI void APIENTRY glStartInstrumentsSGIX (void); +GLAPI void APIENTRY glStopInstrumentsSGIX (GLint marker); +#endif +#endif /* GL_SGIX_instruments */ + +#ifndef GL_SGIX_interlace +#define GL_SGIX_interlace 1 +#define GL_INTERLACE_SGIX 0x8094 +#endif /* GL_SGIX_interlace */ + +#ifndef GL_SGIX_ir_instrument1 +#define GL_SGIX_ir_instrument1 1 +#define GL_IR_INSTRUMENT1_SGIX 0x817F +#endif /* GL_SGIX_ir_instrument1 */ + +#ifndef GL_SGIX_list_priority +#define GL_SGIX_list_priority 1 +#define GL_LIST_PRIORITY_SGIX 0x8182 +typedef void (APIENTRYP PFNGLGETLISTPARAMETERFVSGIXPROC) (GLuint list, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETLISTPARAMETERIVSGIXPROC) (GLuint list, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLLISTPARAMETERFSGIXPROC) (GLuint list, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLLISTPARAMETERFVSGIXPROC) (GLuint list, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLLISTPARAMETERISGIXPROC) (GLuint list, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLLISTPARAMETERIVSGIXPROC) (GLuint list, GLenum pname, const GLint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetListParameterfvSGIX (GLuint list, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetListParameterivSGIX (GLuint list, GLenum pname, GLint *params); +GLAPI void APIENTRY glListParameterfSGIX (GLuint list, GLenum pname, GLfloat param); +GLAPI void APIENTRY glListParameterfvSGIX (GLuint list, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glListParameteriSGIX (GLuint list, GLenum pname, GLint param); +GLAPI void APIENTRY glListParameterivSGIX (GLuint list, GLenum pname, const GLint *params); +#endif +#endif /* GL_SGIX_list_priority */ + +#ifndef GL_SGIX_pixel_texture +#define GL_SGIX_pixel_texture 1 +#define GL_PIXEL_TEX_GEN_SGIX 0x8139 +#define GL_PIXEL_TEX_GEN_MODE_SGIX 0x832B +typedef void (APIENTRYP PFNGLPIXELTEXGENSGIXPROC) (GLenum mode); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPixelTexGenSGIX (GLenum mode); +#endif +#endif /* GL_SGIX_pixel_texture */ + +#ifndef GL_SGIX_pixel_tiles +#define GL_SGIX_pixel_tiles 1 +#define GL_PIXEL_TILE_BEST_ALIGNMENT_SGIX 0x813E +#define GL_PIXEL_TILE_CACHE_INCREMENT_SGIX 0x813F +#define GL_PIXEL_TILE_WIDTH_SGIX 0x8140 +#define GL_PIXEL_TILE_HEIGHT_SGIX 0x8141 +#define GL_PIXEL_TILE_GRID_WIDTH_SGIX 0x8142 +#define GL_PIXEL_TILE_GRID_HEIGHT_SGIX 0x8143 +#define GL_PIXEL_TILE_GRID_DEPTH_SGIX 0x8144 +#define GL_PIXEL_TILE_CACHE_SIZE_SGIX 0x8145 +#endif /* GL_SGIX_pixel_tiles */ + +#ifndef GL_SGIX_polynomial_ffd +#define GL_SGIX_polynomial_ffd 1 +#define GL_TEXTURE_DEFORMATION_BIT_SGIX 0x00000001 +#define GL_GEOMETRY_DEFORMATION_BIT_SGIX 0x00000002 +#define GL_GEOMETRY_DEFORMATION_SGIX 0x8194 +#define GL_TEXTURE_DEFORMATION_SGIX 0x8195 +#define GL_DEFORMATIONS_MASK_SGIX 0x8196 +#define GL_MAX_DEFORMATION_ORDER_SGIX 0x8197 +typedef void (APIENTRYP PFNGLDEFORMATIONMAP3DSGIXPROC) (GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, GLdouble w1, GLdouble w2, GLint wstride, GLint worder, const GLdouble *points); +typedef void (APIENTRYP PFNGLDEFORMATIONMAP3FSGIXPROC) (GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, GLfloat w1, GLfloat w2, GLint wstride, GLint worder, const GLfloat *points); +typedef void (APIENTRYP PFNGLDEFORMSGIXPROC) (GLbitfield mask); +typedef void (APIENTRYP PFNGLLOADIDENTITYDEFORMATIONMAPSGIXPROC) (GLbitfield mask); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDeformationMap3dSGIX (GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, GLdouble w1, GLdouble w2, GLint wstride, GLint worder, const GLdouble *points); +GLAPI void APIENTRY glDeformationMap3fSGIX (GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, GLfloat w1, GLfloat w2, GLint wstride, GLint worder, const GLfloat *points); +GLAPI void APIENTRY glDeformSGIX (GLbitfield mask); +GLAPI void APIENTRY glLoadIdentityDeformationMapSGIX (GLbitfield mask); +#endif +#endif /* GL_SGIX_polynomial_ffd */ + +#ifndef GL_SGIX_reference_plane +#define GL_SGIX_reference_plane 1 +#define GL_REFERENCE_PLANE_SGIX 0x817D +#define GL_REFERENCE_PLANE_EQUATION_SGIX 0x817E +typedef void (APIENTRYP PFNGLREFERENCEPLANESGIXPROC) (const GLdouble *equation); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glReferencePlaneSGIX (const GLdouble *equation); +#endif +#endif /* GL_SGIX_reference_plane */ + +#ifndef GL_SGIX_resample +#define GL_SGIX_resample 1 +#define GL_PACK_RESAMPLE_SGIX 0x842E +#define GL_UNPACK_RESAMPLE_SGIX 0x842F +#define GL_RESAMPLE_REPLICATE_SGIX 0x8433 +#define GL_RESAMPLE_ZERO_FILL_SGIX 0x8434 +#define GL_RESAMPLE_DECIMATE_SGIX 0x8430 +#endif /* GL_SGIX_resample */ + +#ifndef GL_SGIX_scalebias_hint +#define GL_SGIX_scalebias_hint 1 +#define GL_SCALEBIAS_HINT_SGIX 0x8322 +#endif /* GL_SGIX_scalebias_hint */ + +#ifndef GL_SGIX_shadow +#define GL_SGIX_shadow 1 +#define GL_TEXTURE_COMPARE_SGIX 0x819A +#define GL_TEXTURE_COMPARE_OPERATOR_SGIX 0x819B +#define GL_TEXTURE_LEQUAL_R_SGIX 0x819C +#define GL_TEXTURE_GEQUAL_R_SGIX 0x819D +#endif /* GL_SGIX_shadow */ + +#ifndef GL_SGIX_shadow_ambient +#define GL_SGIX_shadow_ambient 1 +#define GL_SHADOW_AMBIENT_SGIX 0x80BF +#endif /* GL_SGIX_shadow_ambient */ + +#ifndef GL_SGIX_sprite +#define GL_SGIX_sprite 1 +#define GL_SPRITE_SGIX 0x8148 +#define GL_SPRITE_MODE_SGIX 0x8149 +#define GL_SPRITE_AXIS_SGIX 0x814A +#define GL_SPRITE_TRANSLATION_SGIX 0x814B +#define GL_SPRITE_AXIAL_SGIX 0x814C +#define GL_SPRITE_OBJECT_ALIGNED_SGIX 0x814D +#define GL_SPRITE_EYE_ALIGNED_SGIX 0x814E +typedef void (APIENTRYP PFNGLSPRITEPARAMETERFSGIXPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLSPRITEPARAMETERFVSGIXPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLSPRITEPARAMETERISGIXPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLSPRITEPARAMETERIVSGIXPROC) (GLenum pname, const GLint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSpriteParameterfSGIX (GLenum pname, GLfloat param); +GLAPI void APIENTRY glSpriteParameterfvSGIX (GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glSpriteParameteriSGIX (GLenum pname, GLint param); +GLAPI void APIENTRY glSpriteParameterivSGIX (GLenum pname, const GLint *params); +#endif +#endif /* GL_SGIX_sprite */ + +#ifndef GL_SGIX_subsample +#define GL_SGIX_subsample 1 +#define GL_PACK_SUBSAMPLE_RATE_SGIX 0x85A0 +#define GL_UNPACK_SUBSAMPLE_RATE_SGIX 0x85A1 +#define GL_PIXEL_SUBSAMPLE_4444_SGIX 0x85A2 +#define GL_PIXEL_SUBSAMPLE_2424_SGIX 0x85A3 +#define GL_PIXEL_SUBSAMPLE_4242_SGIX 0x85A4 +#endif /* GL_SGIX_subsample */ + +#ifndef GL_SGIX_tag_sample_buffer +#define GL_SGIX_tag_sample_buffer 1 +typedef void (APIENTRYP PFNGLTAGSAMPLEBUFFERSGIXPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTagSampleBufferSGIX (void); +#endif +#endif /* GL_SGIX_tag_sample_buffer */ + +#ifndef GL_SGIX_texture_add_env +#define GL_SGIX_texture_add_env 1 +#define GL_TEXTURE_ENV_BIAS_SGIX 0x80BE +#endif /* GL_SGIX_texture_add_env */ + +#ifndef GL_SGIX_texture_coordinate_clamp +#define GL_SGIX_texture_coordinate_clamp 1 +#define GL_TEXTURE_MAX_CLAMP_S_SGIX 0x8369 +#define GL_TEXTURE_MAX_CLAMP_T_SGIX 0x836A +#define GL_TEXTURE_MAX_CLAMP_R_SGIX 0x836B +#endif /* GL_SGIX_texture_coordinate_clamp */ + +#ifndef GL_SGIX_texture_lod_bias +#define GL_SGIX_texture_lod_bias 1 +#define GL_TEXTURE_LOD_BIAS_S_SGIX 0x818E +#define GL_TEXTURE_LOD_BIAS_T_SGIX 0x818F +#define GL_TEXTURE_LOD_BIAS_R_SGIX 0x8190 +#endif /* GL_SGIX_texture_lod_bias */ + +#ifndef GL_SGIX_texture_multi_buffer +#define GL_SGIX_texture_multi_buffer 1 +#define GL_TEXTURE_MULTI_BUFFER_HINT_SGIX 0x812E +#endif /* GL_SGIX_texture_multi_buffer */ + +#ifndef GL_SGIX_texture_scale_bias +#define GL_SGIX_texture_scale_bias 1 +#define GL_POST_TEXTURE_FILTER_BIAS_SGIX 0x8179 +#define GL_POST_TEXTURE_FILTER_SCALE_SGIX 0x817A +#define GL_POST_TEXTURE_FILTER_BIAS_RANGE_SGIX 0x817B +#define GL_POST_TEXTURE_FILTER_SCALE_RANGE_SGIX 0x817C +#endif /* GL_SGIX_texture_scale_bias */ + +#ifndef GL_SGIX_vertex_preclip +#define GL_SGIX_vertex_preclip 1 +#define GL_VERTEX_PRECLIP_SGIX 0x83EE +#define GL_VERTEX_PRECLIP_HINT_SGIX 0x83EF +#endif /* GL_SGIX_vertex_preclip */ + +#ifndef GL_SGIX_ycrcb +#define GL_SGIX_ycrcb 1 +#define GL_YCRCB_422_SGIX 0x81BB +#define GL_YCRCB_444_SGIX 0x81BC +#endif /* GL_SGIX_ycrcb */ + +#ifndef GL_SGIX_ycrcb_subsample +#define GL_SGIX_ycrcb_subsample 1 +#endif /* GL_SGIX_ycrcb_subsample */ + +#ifndef GL_SGIX_ycrcba +#define GL_SGIX_ycrcba 1 +#define GL_YCRCB_SGIX 0x8318 +#define GL_YCRCBA_SGIX 0x8319 +#endif /* GL_SGIX_ycrcba */ + +#ifndef GL_SGI_color_matrix +#define GL_SGI_color_matrix 1 +#define GL_COLOR_MATRIX_SGI 0x80B1 +#define GL_COLOR_MATRIX_STACK_DEPTH_SGI 0x80B2 +#define GL_MAX_COLOR_MATRIX_STACK_DEPTH_SGI 0x80B3 +#define GL_POST_COLOR_MATRIX_RED_SCALE_SGI 0x80B4 +#define GL_POST_COLOR_MATRIX_GREEN_SCALE_SGI 0x80B5 +#define GL_POST_COLOR_MATRIX_BLUE_SCALE_SGI 0x80B6 +#define GL_POST_COLOR_MATRIX_ALPHA_SCALE_SGI 0x80B7 +#define GL_POST_COLOR_MATRIX_RED_BIAS_SGI 0x80B8 +#define GL_POST_COLOR_MATRIX_GREEN_BIAS_SGI 0x80B9 +#define GL_POST_COLOR_MATRIX_BLUE_BIAS_SGI 0x80BA +#define GL_POST_COLOR_MATRIX_ALPHA_BIAS_SGI 0x80BB +#endif /* GL_SGI_color_matrix */ + +#ifndef GL_SGI_color_table +#define GL_SGI_color_table 1 +#define GL_COLOR_TABLE_SGI 0x80D0 +#define GL_POST_CONVOLUTION_COLOR_TABLE_SGI 0x80D1 +#define GL_POST_COLOR_MATRIX_COLOR_TABLE_SGI 0x80D2 +#define GL_PROXY_COLOR_TABLE_SGI 0x80D3 +#define GL_PROXY_POST_CONVOLUTION_COLOR_TABLE_SGI 0x80D4 +#define GL_PROXY_POST_COLOR_MATRIX_COLOR_TABLE_SGI 0x80D5 +#define GL_COLOR_TABLE_SCALE_SGI 0x80D6 +#define GL_COLOR_TABLE_BIAS_SGI 0x80D7 +#define GL_COLOR_TABLE_FORMAT_SGI 0x80D8 +#define GL_COLOR_TABLE_WIDTH_SGI 0x80D9 +#define GL_COLOR_TABLE_RED_SIZE_SGI 0x80DA +#define GL_COLOR_TABLE_GREEN_SIZE_SGI 0x80DB +#define GL_COLOR_TABLE_BLUE_SIZE_SGI 0x80DC +#define GL_COLOR_TABLE_ALPHA_SIZE_SGI 0x80DD +#define GL_COLOR_TABLE_LUMINANCE_SIZE_SGI 0x80DE +#define GL_COLOR_TABLE_INTENSITY_SIZE_SGI 0x80DF +typedef void (APIENTRYP PFNGLCOLORTABLESGIPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const void *table); +typedef void (APIENTRYP PFNGLCOLORTABLEPARAMETERFVSGIPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCOLORTABLEPARAMETERIVSGIPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOPYCOLORTABLESGIPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLGETCOLORTABLESGIPROC) (GLenum target, GLenum format, GLenum type, void *table); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERFVSGIPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERIVSGIPROC) (GLenum target, GLenum pname, GLint *params); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorTableSGI (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const void *table); +GLAPI void APIENTRY glColorTableParameterfvSGI (GLenum target, GLenum pname, const GLfloat *params); +GLAPI void APIENTRY glColorTableParameterivSGI (GLenum target, GLenum pname, const GLint *params); +GLAPI void APIENTRY glCopyColorTableSGI (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +GLAPI void APIENTRY glGetColorTableSGI (GLenum target, GLenum format, GLenum type, void *table); +GLAPI void APIENTRY glGetColorTableParameterfvSGI (GLenum target, GLenum pname, GLfloat *params); +GLAPI void APIENTRY glGetColorTableParameterivSGI (GLenum target, GLenum pname, GLint *params); +#endif +#endif /* GL_SGI_color_table */ + +#ifndef GL_SGI_texture_color_table +#define GL_SGI_texture_color_table 1 +#define GL_TEXTURE_COLOR_TABLE_SGI 0x80BC +#define GL_PROXY_TEXTURE_COLOR_TABLE_SGI 0x80BD +#endif /* GL_SGI_texture_color_table */ + +#ifndef GL_SUNX_constant_data +#define GL_SUNX_constant_data 1 +#define GL_UNPACK_CONSTANT_DATA_SUNX 0x81D5 +#define GL_TEXTURE_CONSTANT_DATA_SUNX 0x81D6 +typedef void (APIENTRYP PFNGLFINISHTEXTURESUNXPROC) (void); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFinishTextureSUNX (void); +#endif +#endif /* GL_SUNX_constant_data */ + +#ifndef GL_SUN_convolution_border_modes +#define GL_SUN_convolution_border_modes 1 +#define GL_WRAP_BORDER_SUN 0x81D4 +#endif /* GL_SUN_convolution_border_modes */ + +#ifndef GL_SUN_global_alpha +#define GL_SUN_global_alpha 1 +#define GL_GLOBAL_ALPHA_SUN 0x81D9 +#define GL_GLOBAL_ALPHA_FACTOR_SUN 0x81DA +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORBSUNPROC) (GLbyte factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORSSUNPROC) (GLshort factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORISUNPROC) (GLint factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORFSUNPROC) (GLfloat factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORDSUNPROC) (GLdouble factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORUBSUNPROC) (GLubyte factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORUSSUNPROC) (GLushort factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORUISUNPROC) (GLuint factor); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGlobalAlphaFactorbSUN (GLbyte factor); +GLAPI void APIENTRY glGlobalAlphaFactorsSUN (GLshort factor); +GLAPI void APIENTRY glGlobalAlphaFactoriSUN (GLint factor); +GLAPI void APIENTRY glGlobalAlphaFactorfSUN (GLfloat factor); +GLAPI void APIENTRY glGlobalAlphaFactordSUN (GLdouble factor); +GLAPI void APIENTRY glGlobalAlphaFactorubSUN (GLubyte factor); +GLAPI void APIENTRY glGlobalAlphaFactorusSUN (GLushort factor); +GLAPI void APIENTRY glGlobalAlphaFactoruiSUN (GLuint factor); +#endif +#endif /* GL_SUN_global_alpha */ + +#ifndef GL_SUN_mesh_array +#define GL_SUN_mesh_array 1 +#define GL_QUAD_MESH_SUN 0x8614 +#define GL_TRIANGLE_MESH_SUN 0x8615 +typedef void (APIENTRYP PFNGLDRAWMESHARRAYSSUNPROC) (GLenum mode, GLint first, GLsizei count, GLsizei width); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawMeshArraysSUN (GLenum mode, GLint first, GLsizei count, GLsizei width); +#endif +#endif /* GL_SUN_mesh_array */ + +#ifndef GL_SUN_slice_accum +#define GL_SUN_slice_accum 1 +#define GL_SLICE_ACCUM_SUN 0x85CC +#endif /* GL_SUN_slice_accum */ + +#ifndef GL_SUN_triangle_list +#define GL_SUN_triangle_list 1 +#define GL_RESTART_SUN 0x0001 +#define GL_REPLACE_MIDDLE_SUN 0x0002 +#define GL_REPLACE_OLDEST_SUN 0x0003 +#define GL_TRIANGLE_LIST_SUN 0x81D7 +#define GL_REPLACEMENT_CODE_SUN 0x81D8 +#define GL_REPLACEMENT_CODE_ARRAY_SUN 0x85C0 +#define GL_REPLACEMENT_CODE_ARRAY_TYPE_SUN 0x85C1 +#define GL_REPLACEMENT_CODE_ARRAY_STRIDE_SUN 0x85C2 +#define GL_REPLACEMENT_CODE_ARRAY_POINTER_SUN 0x85C3 +#define GL_R1UI_V3F_SUN 0x85C4 +#define GL_R1UI_C4UB_V3F_SUN 0x85C5 +#define GL_R1UI_C3F_V3F_SUN 0x85C6 +#define GL_R1UI_N3F_V3F_SUN 0x85C7 +#define GL_R1UI_C4F_N3F_V3F_SUN 0x85C8 +#define GL_R1UI_T2F_V3F_SUN 0x85C9 +#define GL_R1UI_T2F_N3F_V3F_SUN 0x85CA +#define GL_R1UI_T2F_C4F_N3F_V3F_SUN 0x85CB +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUISUNPROC) (GLuint code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUSSUNPROC) (GLushort code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUBSUNPROC) (GLubyte code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUIVSUNPROC) (const GLuint *code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUSVSUNPROC) (const GLushort *code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUBVSUNPROC) (const GLubyte *code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEPOINTERSUNPROC) (GLenum type, GLsizei stride, const void **pointer); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glReplacementCodeuiSUN (GLuint code); +GLAPI void APIENTRY glReplacementCodeusSUN (GLushort code); +GLAPI void APIENTRY glReplacementCodeubSUN (GLubyte code); +GLAPI void APIENTRY glReplacementCodeuivSUN (const GLuint *code); +GLAPI void APIENTRY glReplacementCodeusvSUN (const GLushort *code); +GLAPI void APIENTRY glReplacementCodeubvSUN (const GLubyte *code); +GLAPI void APIENTRY glReplacementCodePointerSUN (GLenum type, GLsizei stride, const void **pointer); +#endif +#endif /* GL_SUN_triangle_list */ + +#ifndef GL_SUN_vertex +#define GL_SUN_vertex 1 +typedef void (APIENTRYP PFNGLCOLOR4UBVERTEX2FSUNPROC) (GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLCOLOR4UBVERTEX2FVSUNPROC) (const GLubyte *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLCOLOR4UBVERTEX3FSUNPROC) (GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLCOLOR4UBVERTEX3FVSUNPROC) (const GLubyte *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLCOLOR3FVERTEX3FSUNPROC) (GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLCOLOR3FVERTEX3FVSUNPROC) (const GLfloat *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLNORMAL3FVERTEX3FSUNPROC) (GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLCOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLCOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD4FVERTEX4FSUNPROC) (GLfloat s, GLfloat t, GLfloat p, GLfloat q, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLTEXCOORD4FVERTEX4FVSUNPROC) (const GLfloat *tc, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR4UBVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR4UBVERTEX3FVSUNPROC) (const GLfloat *tc, const GLubyte *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR3FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR3FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FNORMAL3FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD4FCOLOR4FNORMAL3FVERTEX4FSUNPROC) (GLfloat s, GLfloat t, GLfloat p, GLfloat q, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLTEXCOORD4FCOLOR4FNORMAL3FVERTEX4FVSUNPROC) (const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUIVERTEX3FSUNPROC) (GLuint rc, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUIVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR4UBVERTEX3FSUNPROC) (GLuint rc, GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR4UBVERTEX3FVSUNPROC) (const GLuint *rc, const GLubyte *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR3FVERTEX3FSUNPROC) (GLuint rc, GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUINORMAL3FVERTEX3FSUNPROC) (GLuint rc, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUINORMAL3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLuint rc, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FVERTEX3FSUNPROC) (GLuint rc, GLfloat s, GLfloat t, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *tc, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FNORMAL3FVERTEX3FSUNPROC) (GLuint rc, GLfloat s, GLfloat t, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FNORMAL3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *tc, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FCOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLuint rc, GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FCOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColor4ubVertex2fSUN (GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y); +GLAPI void APIENTRY glColor4ubVertex2fvSUN (const GLubyte *c, const GLfloat *v); +GLAPI void APIENTRY glColor4ubVertex3fSUN (GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glColor4ubVertex3fvSUN (const GLubyte *c, const GLfloat *v); +GLAPI void APIENTRY glColor3fVertex3fSUN (GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glColor3fVertex3fvSUN (const GLfloat *c, const GLfloat *v); +GLAPI void APIENTRY glNormal3fVertex3fSUN (GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glNormal3fVertex3fvSUN (const GLfloat *n, const GLfloat *v); +GLAPI void APIENTRY glColor4fNormal3fVertex3fSUN (GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glColor4fNormal3fVertex3fvSUN (const GLfloat *c, const GLfloat *n, const GLfloat *v); +GLAPI void APIENTRY glTexCoord2fVertex3fSUN (GLfloat s, GLfloat t, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glTexCoord2fVertex3fvSUN (const GLfloat *tc, const GLfloat *v); +GLAPI void APIENTRY glTexCoord4fVertex4fSUN (GLfloat s, GLfloat t, GLfloat p, GLfloat q, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glTexCoord4fVertex4fvSUN (const GLfloat *tc, const GLfloat *v); +GLAPI void APIENTRY glTexCoord2fColor4ubVertex3fSUN (GLfloat s, GLfloat t, GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glTexCoord2fColor4ubVertex3fvSUN (const GLfloat *tc, const GLubyte *c, const GLfloat *v); +GLAPI void APIENTRY glTexCoord2fColor3fVertex3fSUN (GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glTexCoord2fColor3fVertex3fvSUN (const GLfloat *tc, const GLfloat *c, const GLfloat *v); +GLAPI void APIENTRY glTexCoord2fNormal3fVertex3fSUN (GLfloat s, GLfloat t, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glTexCoord2fNormal3fVertex3fvSUN (const GLfloat *tc, const GLfloat *n, const GLfloat *v); +GLAPI void APIENTRY glTexCoord2fColor4fNormal3fVertex3fSUN (GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glTexCoord2fColor4fNormal3fVertex3fvSUN (const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +GLAPI void APIENTRY glTexCoord4fColor4fNormal3fVertex4fSUN (GLfloat s, GLfloat t, GLfloat p, GLfloat q, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +GLAPI void APIENTRY glTexCoord4fColor4fNormal3fVertex4fvSUN (const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +GLAPI void APIENTRY glReplacementCodeuiVertex3fSUN (GLuint rc, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glReplacementCodeuiVertex3fvSUN (const GLuint *rc, const GLfloat *v); +GLAPI void APIENTRY glReplacementCodeuiColor4ubVertex3fSUN (GLuint rc, GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glReplacementCodeuiColor4ubVertex3fvSUN (const GLuint *rc, const GLubyte *c, const GLfloat *v); +GLAPI void APIENTRY glReplacementCodeuiColor3fVertex3fSUN (GLuint rc, GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glReplacementCodeuiColor3fVertex3fvSUN (const GLuint *rc, const GLfloat *c, const GLfloat *v); +GLAPI void APIENTRY glReplacementCodeuiNormal3fVertex3fSUN (GLuint rc, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glReplacementCodeuiNormal3fVertex3fvSUN (const GLuint *rc, const GLfloat *n, const GLfloat *v); +GLAPI void APIENTRY glReplacementCodeuiColor4fNormal3fVertex3fSUN (GLuint rc, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glReplacementCodeuiColor4fNormal3fVertex3fvSUN (const GLuint *rc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fVertex3fSUN (GLuint rc, GLfloat s, GLfloat t, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fVertex3fvSUN (const GLuint *rc, const GLfloat *tc, const GLfloat *v); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fNormal3fVertex3fSUN (GLuint rc, GLfloat s, GLfloat t, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fNormal3fVertex3fvSUN (const GLuint *rc, const GLfloat *tc, const GLfloat *n, const GLfloat *v); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fSUN (GLuint rc, GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fvSUN (const GLuint *rc, const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +#endif +#endif /* GL_SUN_vertex */ + +#ifndef GL_WIN_phong_shading +#define GL_WIN_phong_shading 1 +#define GL_PHONG_WIN 0x80EA +#define GL_PHONG_HINT_WIN 0x80EB +#endif /* GL_WIN_phong_shading */ + +#ifndef GL_WIN_specular_fog +#define GL_WIN_specular_fog 1 +#define GL_FOG_SPECULAR_TEXTURE_WIN 0x80EC +#endif /* GL_WIN_specular_fog */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/Common/GLInclude/glxext.h b/src/Common/GLInclude/glxext.h new file mode 100644 index 00000000..05a697c8 --- /dev/null +++ b/src/Common/GLInclude/glxext.h @@ -0,0 +1,954 @@ +#ifndef __glx_glxext_h_ +#define __glx_glxext_h_ 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/* +** Copyright 2013-2020 The Khronos Group Inc. +** SPDX-License-Identifier: MIT +** +** This header is generated from the Khronos OpenGL / OpenGL ES XML +** API Registry. The current version of the Registry, generator scripts +** used to make the header, and the header can be found at +** https://github.com/KhronosGroup/OpenGL-Registry +*/ + +#define GLX_GLXEXT_VERSION 20211115 + +/* Generated C header for: + * API: glx + * Versions considered: .* + * Versions emitted: 1\.[3-9] + * Default extensions included: glx + * Additional extensions included: _nomatch_^ + * Extensions removed: _nomatch_^ + */ + +#ifndef GLX_VERSION_1_3 +#define GLX_VERSION_1_3 1 +typedef XID GLXContextID; +typedef struct __GLXFBConfigRec *GLXFBConfig; +typedef XID GLXWindow; +typedef XID GLXPbuffer; +#define GLX_WINDOW_BIT 0x00000001 +#define GLX_PIXMAP_BIT 0x00000002 +#define GLX_PBUFFER_BIT 0x00000004 +#define GLX_RGBA_BIT 0x00000001 +#define GLX_COLOR_INDEX_BIT 0x00000002 +#define GLX_PBUFFER_CLOBBER_MASK 0x08000000 +#define GLX_FRONT_LEFT_BUFFER_BIT 0x00000001 +#define GLX_FRONT_RIGHT_BUFFER_BIT 0x00000002 +#define GLX_BACK_LEFT_BUFFER_BIT 0x00000004 +#define GLX_BACK_RIGHT_BUFFER_BIT 0x00000008 +#define GLX_AUX_BUFFERS_BIT 0x00000010 +#define GLX_DEPTH_BUFFER_BIT 0x00000020 +#define GLX_STENCIL_BUFFER_BIT 0x00000040 +#define GLX_ACCUM_BUFFER_BIT 0x00000080 +#define GLX_CONFIG_CAVEAT 0x20 +#define GLX_X_VISUAL_TYPE 0x22 +#define GLX_TRANSPARENT_TYPE 0x23 +#define GLX_TRANSPARENT_INDEX_VALUE 0x24 +#define GLX_TRANSPARENT_RED_VALUE 0x25 +#define GLX_TRANSPARENT_GREEN_VALUE 0x26 +#define GLX_TRANSPARENT_BLUE_VALUE 0x27 +#define GLX_TRANSPARENT_ALPHA_VALUE 0x28 +#define GLX_DONT_CARE 0xFFFFFFFF +#define GLX_NONE 0x8000 +#define GLX_SLOW_CONFIG 0x8001 +#define GLX_TRUE_COLOR 0x8002 +#define GLX_DIRECT_COLOR 0x8003 +#define GLX_PSEUDO_COLOR 0x8004 +#define GLX_STATIC_COLOR 0x8005 +#define GLX_GRAY_SCALE 0x8006 +#define GLX_STATIC_GRAY 0x8007 +#define GLX_TRANSPARENT_RGB 0x8008 +#define GLX_TRANSPARENT_INDEX 0x8009 +#define GLX_VISUAL_ID 0x800B +#define GLX_SCREEN 0x800C +#define GLX_NON_CONFORMANT_CONFIG 0x800D +#define GLX_DRAWABLE_TYPE 0x8010 +#define GLX_RENDER_TYPE 0x8011 +#define GLX_X_RENDERABLE 0x8012 +#define GLX_FBCONFIG_ID 0x8013 +#define GLX_RGBA_TYPE 0x8014 +#define GLX_COLOR_INDEX_TYPE 0x8015 +#define GLX_MAX_PBUFFER_WIDTH 0x8016 +#define GLX_MAX_PBUFFER_HEIGHT 0x8017 +#define GLX_MAX_PBUFFER_PIXELS 0x8018 +#define GLX_PRESERVED_CONTENTS 0x801B +#define GLX_LARGEST_PBUFFER 0x801C +#define GLX_WIDTH 0x801D +#define GLX_HEIGHT 0x801E +#define GLX_EVENT_MASK 0x801F +#define GLX_DAMAGED 0x8020 +#define GLX_SAVED 0x8021 +#define GLX_WINDOW 0x8022 +#define GLX_PBUFFER 0x8023 +#define GLX_PBUFFER_HEIGHT 0x8040 +#define GLX_PBUFFER_WIDTH 0x8041 +typedef GLXFBConfig *( *PFNGLXGETFBCONFIGSPROC) (Display *dpy, int screen, int *nelements); +typedef GLXFBConfig *( *PFNGLXCHOOSEFBCONFIGPROC) (Display *dpy, int screen, const int *attrib_list, int *nelements); +typedef int ( *PFNGLXGETFBCONFIGATTRIBPROC) (Display *dpy, GLXFBConfig config, int attribute, int *value); +typedef XVisualInfo *( *PFNGLXGETVISUALFROMFBCONFIGPROC) (Display *dpy, GLXFBConfig config); +typedef GLXWindow ( *PFNGLXCREATEWINDOWPROC) (Display *dpy, GLXFBConfig config, Window win, const int *attrib_list); +typedef void ( *PFNGLXDESTROYWINDOWPROC) (Display *dpy, GLXWindow win); +typedef GLXPixmap ( *PFNGLXCREATEPIXMAPPROC) (Display *dpy, GLXFBConfig config, Pixmap pixmap, const int *attrib_list); +typedef void ( *PFNGLXDESTROYPIXMAPPROC) (Display *dpy, GLXPixmap pixmap); +typedef GLXPbuffer ( *PFNGLXCREATEPBUFFERPROC) (Display *dpy, GLXFBConfig config, const int *attrib_list); +typedef void ( *PFNGLXDESTROYPBUFFERPROC) (Display *dpy, GLXPbuffer pbuf); +typedef void ( *PFNGLXQUERYDRAWABLEPROC) (Display *dpy, GLXDrawable draw, int attribute, unsigned int *value); +typedef GLXContext ( *PFNGLXCREATENEWCONTEXTPROC) (Display *dpy, GLXFBConfig config, int render_type, GLXContext share_list, Bool direct); +typedef Bool ( *PFNGLXMAKECONTEXTCURRENTPROC) (Display *dpy, GLXDrawable draw, GLXDrawable read, GLXContext ctx); +typedef GLXDrawable ( *PFNGLXGETCURRENTREADDRAWABLEPROC) (void); +typedef int ( *PFNGLXQUERYCONTEXTPROC) (Display *dpy, GLXContext ctx, int attribute, int *value); +typedef void ( *PFNGLXSELECTEVENTPROC) (Display *dpy, GLXDrawable draw, unsigned long event_mask); +typedef void ( *PFNGLXGETSELECTEDEVENTPROC) (Display *dpy, GLXDrawable draw, unsigned long *event_mask); +#ifdef GLX_GLXEXT_PROTOTYPES +GLXFBConfig *glXGetFBConfigs (Display *dpy, int screen, int *nelements); +GLXFBConfig *glXChooseFBConfig (Display *dpy, int screen, const int *attrib_list, int *nelements); +int glXGetFBConfigAttrib (Display *dpy, GLXFBConfig config, int attribute, int *value); +XVisualInfo *glXGetVisualFromFBConfig (Display *dpy, GLXFBConfig config); +GLXWindow glXCreateWindow (Display *dpy, GLXFBConfig config, Window win, const int *attrib_list); +void glXDestroyWindow (Display *dpy, GLXWindow win); +GLXPixmap glXCreatePixmap (Display *dpy, GLXFBConfig config, Pixmap pixmap, const int *attrib_list); +void glXDestroyPixmap (Display *dpy, GLXPixmap pixmap); +GLXPbuffer glXCreatePbuffer (Display *dpy, GLXFBConfig config, const int *attrib_list); +void glXDestroyPbuffer (Display *dpy, GLXPbuffer pbuf); +void glXQueryDrawable (Display *dpy, GLXDrawable draw, int attribute, unsigned int *value); +GLXContext glXCreateNewContext (Display *dpy, GLXFBConfig config, int render_type, GLXContext share_list, Bool direct); +Bool glXMakeContextCurrent (Display *dpy, GLXDrawable draw, GLXDrawable read, GLXContext ctx); +GLXDrawable glXGetCurrentReadDrawable (void); +int glXQueryContext (Display *dpy, GLXContext ctx, int attribute, int *value); +void glXSelectEvent (Display *dpy, GLXDrawable draw, unsigned long event_mask); +void glXGetSelectedEvent (Display *dpy, GLXDrawable draw, unsigned long *event_mask); +#endif +#endif /* GLX_VERSION_1_3 */ + +#ifndef GLX_VERSION_1_4 +#define GLX_VERSION_1_4 1 +typedef void ( *__GLXextFuncPtr)(void); +#define GLX_SAMPLE_BUFFERS 100000 +#define GLX_SAMPLES 100001 +typedef __GLXextFuncPtr ( *PFNGLXGETPROCADDRESSPROC) (const GLubyte *procName); +#ifdef GLX_GLXEXT_PROTOTYPES +__GLXextFuncPtr glXGetProcAddress (const GLubyte *procName); +#endif +#endif /* GLX_VERSION_1_4 */ + +#ifndef GLX_ARB_context_flush_control +#define GLX_ARB_context_flush_control 1 +#define GLX_CONTEXT_RELEASE_BEHAVIOR_ARB 0x2097 +#define GLX_CONTEXT_RELEASE_BEHAVIOR_NONE_ARB 0 +#define GLX_CONTEXT_RELEASE_BEHAVIOR_FLUSH_ARB 0x2098 +#endif /* GLX_ARB_context_flush_control */ + +#ifndef GLX_ARB_create_context +#define GLX_ARB_create_context 1 +#define GLX_CONTEXT_DEBUG_BIT_ARB 0x00000001 +#define GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x00000002 +#define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091 +#define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092 +#define GLX_CONTEXT_FLAGS_ARB 0x2094 +typedef GLXContext ( *PFNGLXCREATECONTEXTATTRIBSARBPROC) (Display *dpy, GLXFBConfig config, GLXContext share_context, Bool direct, const int *attrib_list); +#ifdef GLX_GLXEXT_PROTOTYPES +GLXContext glXCreateContextAttribsARB (Display *dpy, GLXFBConfig config, GLXContext share_context, Bool direct, const int *attrib_list); +#endif +#endif /* GLX_ARB_create_context */ + +#ifndef GLX_ARB_create_context_no_error +#define GLX_ARB_create_context_no_error 1 +#define GLX_CONTEXT_OPENGL_NO_ERROR_ARB 0x31B3 +#endif /* GLX_ARB_create_context_no_error */ + +#ifndef GLX_ARB_create_context_profile +#define GLX_ARB_create_context_profile 1 +#define GLX_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 +#define GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002 +#define GLX_CONTEXT_PROFILE_MASK_ARB 0x9126 +#endif /* GLX_ARB_create_context_profile */ + +#ifndef GLX_ARB_create_context_robustness +#define GLX_ARB_create_context_robustness 1 +#define GLX_CONTEXT_ROBUST_ACCESS_BIT_ARB 0x00000004 +#define GLX_LOSE_CONTEXT_ON_RESET_ARB 0x8252 +#define GLX_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB 0x8256 +#define GLX_NO_RESET_NOTIFICATION_ARB 0x8261 +#endif /* GLX_ARB_create_context_robustness */ + +#ifndef GLX_ARB_fbconfig_float +#define GLX_ARB_fbconfig_float 1 +#define GLX_RGBA_FLOAT_TYPE_ARB 0x20B9 +#define GLX_RGBA_FLOAT_BIT_ARB 0x00000004 +#endif /* GLX_ARB_fbconfig_float */ + +#ifndef GLX_ARB_framebuffer_sRGB +#define GLX_ARB_framebuffer_sRGB 1 +#define GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB 0x20B2 +#endif /* GLX_ARB_framebuffer_sRGB */ + +#ifndef GLX_ARB_get_proc_address +#define GLX_ARB_get_proc_address 1 +typedef __GLXextFuncPtr ( *PFNGLXGETPROCADDRESSARBPROC) (const GLubyte *procName); +#ifdef GLX_GLXEXT_PROTOTYPES +__GLXextFuncPtr glXGetProcAddressARB (const GLubyte *procName); +#endif +#endif /* GLX_ARB_get_proc_address */ + +#ifndef GLX_ARB_multisample +#define GLX_ARB_multisample 1 +#define GLX_SAMPLE_BUFFERS_ARB 100000 +#define GLX_SAMPLES_ARB 100001 +#endif /* GLX_ARB_multisample */ + +#ifndef GLX_ARB_robustness_application_isolation +#define GLX_ARB_robustness_application_isolation 1 +#define GLX_CONTEXT_RESET_ISOLATION_BIT_ARB 0x00000008 +#endif /* GLX_ARB_robustness_application_isolation */ + +#ifndef GLX_ARB_robustness_share_group_isolation +#define GLX_ARB_robustness_share_group_isolation 1 +#endif /* GLX_ARB_robustness_share_group_isolation */ + +#ifndef GLX_ARB_vertex_buffer_object +#define GLX_ARB_vertex_buffer_object 1 +#define GLX_CONTEXT_ALLOW_BUFFER_BYTE_ORDER_MISMATCH_ARB 0x2095 +#endif /* GLX_ARB_vertex_buffer_object */ + +#ifndef GLX_3DFX_multisample +#define GLX_3DFX_multisample 1 +#define GLX_SAMPLE_BUFFERS_3DFX 0x8050 +#define GLX_SAMPLES_3DFX 0x8051 +#endif /* GLX_3DFX_multisample */ + +#ifndef GLX_AMD_gpu_association +#define GLX_AMD_gpu_association 1 +#define GLX_GPU_VENDOR_AMD 0x1F00 +#define GLX_GPU_RENDERER_STRING_AMD 0x1F01 +#define GLX_GPU_OPENGL_VERSION_STRING_AMD 0x1F02 +#define GLX_GPU_FASTEST_TARGET_GPUS_AMD 0x21A2 +#define GLX_GPU_RAM_AMD 0x21A3 +#define GLX_GPU_CLOCK_AMD 0x21A4 +#define GLX_GPU_NUM_PIPES_AMD 0x21A5 +#define GLX_GPU_NUM_SIMD_AMD 0x21A6 +#define GLX_GPU_NUM_RB_AMD 0x21A7 +#define GLX_GPU_NUM_SPI_AMD 0x21A8 +typedef unsigned int ( *PFNGLXGETGPUIDSAMDPROC) (unsigned int maxCount, unsigned int *ids); +typedef int ( *PFNGLXGETGPUINFOAMDPROC) (unsigned int id, int property, GLenum dataType, unsigned int size, void *data); +typedef unsigned int ( *PFNGLXGETCONTEXTGPUIDAMDPROC) (GLXContext ctx); +typedef GLXContext ( *PFNGLXCREATEASSOCIATEDCONTEXTAMDPROC) (unsigned int id, GLXContext share_list); +typedef GLXContext ( *PFNGLXCREATEASSOCIATEDCONTEXTATTRIBSAMDPROC) (unsigned int id, GLXContext share_context, const int *attribList); +typedef Bool ( *PFNGLXDELETEASSOCIATEDCONTEXTAMDPROC) (GLXContext ctx); +typedef Bool ( *PFNGLXMAKEASSOCIATEDCONTEXTCURRENTAMDPROC) (GLXContext ctx); +typedef GLXContext ( *PFNGLXGETCURRENTASSOCIATEDCONTEXTAMDPROC) (void); +typedef void ( *PFNGLXBLITCONTEXTFRAMEBUFFERAMDPROC) (GLXContext dstCtx, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +#ifdef GLX_GLXEXT_PROTOTYPES +unsigned int glXGetGPUIDsAMD (unsigned int maxCount, unsigned int *ids); +int glXGetGPUInfoAMD (unsigned int id, int property, GLenum dataType, unsigned int size, void *data); +unsigned int glXGetContextGPUIDAMD (GLXContext ctx); +GLXContext glXCreateAssociatedContextAMD (unsigned int id, GLXContext share_list); +GLXContext glXCreateAssociatedContextAttribsAMD (unsigned int id, GLXContext share_context, const int *attribList); +Bool glXDeleteAssociatedContextAMD (GLXContext ctx); +Bool glXMakeAssociatedContextCurrentAMD (GLXContext ctx); +GLXContext glXGetCurrentAssociatedContextAMD (void); +void glXBlitContextFramebufferAMD (GLXContext dstCtx, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +#endif +#endif /* GLX_AMD_gpu_association */ + +#ifndef GLX_EXT_buffer_age +#define GLX_EXT_buffer_age 1 +#define GLX_BACK_BUFFER_AGE_EXT 0x20F4 +#endif /* GLX_EXT_buffer_age */ + +#ifndef GLX_EXT_context_priority +#define GLX_EXT_context_priority 1 +#define GLX_CONTEXT_PRIORITY_LEVEL_EXT 0x3100 +#define GLX_CONTEXT_PRIORITY_HIGH_EXT 0x3101 +#define GLX_CONTEXT_PRIORITY_MEDIUM_EXT 0x3102 +#define GLX_CONTEXT_PRIORITY_LOW_EXT 0x3103 +#endif /* GLX_EXT_context_priority */ + +#ifndef GLX_EXT_create_context_es2_profile +#define GLX_EXT_create_context_es2_profile 1 +#define GLX_CONTEXT_ES2_PROFILE_BIT_EXT 0x00000004 +#endif /* GLX_EXT_create_context_es2_profile */ + +#ifndef GLX_EXT_create_context_es_profile +#define GLX_EXT_create_context_es_profile 1 +#define GLX_CONTEXT_ES_PROFILE_BIT_EXT 0x00000004 +#endif /* GLX_EXT_create_context_es_profile */ + +#ifndef GLX_EXT_fbconfig_packed_float +#define GLX_EXT_fbconfig_packed_float 1 +#define GLX_RGBA_UNSIGNED_FLOAT_TYPE_EXT 0x20B1 +#define GLX_RGBA_UNSIGNED_FLOAT_BIT_EXT 0x00000008 +#endif /* GLX_EXT_fbconfig_packed_float */ + +#ifndef GLX_EXT_framebuffer_sRGB +#define GLX_EXT_framebuffer_sRGB 1 +#define GLX_FRAMEBUFFER_SRGB_CAPABLE_EXT 0x20B2 +#endif /* GLX_EXT_framebuffer_sRGB */ + +#ifndef GLX_EXT_get_drawable_type +#define GLX_EXT_get_drawable_type 1 +#endif /* GLX_EXT_get_drawable_type */ + +#ifndef GLX_EXT_import_context +#define GLX_EXT_import_context 1 +#define GLX_SHARE_CONTEXT_EXT 0x800A +#define GLX_VISUAL_ID_EXT 0x800B +#define GLX_SCREEN_EXT 0x800C +typedef Display *( *PFNGLXGETCURRENTDISPLAYEXTPROC) (void); +typedef int ( *PFNGLXQUERYCONTEXTINFOEXTPROC) (Display *dpy, GLXContext context, int attribute, int *value); +typedef GLXContextID ( *PFNGLXGETCONTEXTIDEXTPROC) (const GLXContext context); +typedef GLXContext ( *PFNGLXIMPORTCONTEXTEXTPROC) (Display *dpy, GLXContextID contextID); +typedef void ( *PFNGLXFREECONTEXTEXTPROC) (Display *dpy, GLXContext context); +#ifdef GLX_GLXEXT_PROTOTYPES +Display *glXGetCurrentDisplayEXT (void); +int glXQueryContextInfoEXT (Display *dpy, GLXContext context, int attribute, int *value); +GLXContextID glXGetContextIDEXT (const GLXContext context); +GLXContext glXImportContextEXT (Display *dpy, GLXContextID contextID); +void glXFreeContextEXT (Display *dpy, GLXContext context); +#endif +#endif /* GLX_EXT_import_context */ + +#ifndef GLX_EXT_libglvnd +#define GLX_EXT_libglvnd 1 +#define GLX_VENDOR_NAMES_EXT 0x20F6 +#endif /* GLX_EXT_libglvnd */ + +#ifndef GLX_EXT_no_config_context +#define GLX_EXT_no_config_context 1 +#endif /* GLX_EXT_no_config_context */ + +#ifndef GLX_EXT_stereo_tree +#define GLX_EXT_stereo_tree 1 +typedef struct { + int type; + unsigned long serial; + Bool send_event; + Display *display; + int extension; + int evtype; + GLXDrawable window; + Bool stereo_tree; +} GLXStereoNotifyEventEXT; +#define GLX_STEREO_TREE_EXT 0x20F5 +#define GLX_STEREO_NOTIFY_MASK_EXT 0x00000001 +#define GLX_STEREO_NOTIFY_EXT 0x00000000 +#endif /* GLX_EXT_stereo_tree */ + +#ifndef GLX_EXT_swap_control +#define GLX_EXT_swap_control 1 +#define GLX_SWAP_INTERVAL_EXT 0x20F1 +#define GLX_MAX_SWAP_INTERVAL_EXT 0x20F2 +typedef void ( *PFNGLXSWAPINTERVALEXTPROC) (Display *dpy, GLXDrawable drawable, int interval); +#ifdef GLX_GLXEXT_PROTOTYPES +void glXSwapIntervalEXT (Display *dpy, GLXDrawable drawable, int interval); +#endif +#endif /* GLX_EXT_swap_control */ + +#ifndef GLX_EXT_swap_control_tear +#define GLX_EXT_swap_control_tear 1 +#define GLX_LATE_SWAPS_TEAR_EXT 0x20F3 +#endif /* GLX_EXT_swap_control_tear */ + +#ifndef GLX_EXT_texture_from_pixmap +#define GLX_EXT_texture_from_pixmap 1 +#define GLX_TEXTURE_1D_BIT_EXT 0x00000001 +#define GLX_TEXTURE_2D_BIT_EXT 0x00000002 +#define GLX_TEXTURE_RECTANGLE_BIT_EXT 0x00000004 +#define GLX_BIND_TO_TEXTURE_RGB_EXT 0x20D0 +#define GLX_BIND_TO_TEXTURE_RGBA_EXT 0x20D1 +#define GLX_BIND_TO_MIPMAP_TEXTURE_EXT 0x20D2 +#define GLX_BIND_TO_TEXTURE_TARGETS_EXT 0x20D3 +#define GLX_Y_INVERTED_EXT 0x20D4 +#define GLX_TEXTURE_FORMAT_EXT 0x20D5 +#define GLX_TEXTURE_TARGET_EXT 0x20D6 +#define GLX_MIPMAP_TEXTURE_EXT 0x20D7 +#define GLX_TEXTURE_FORMAT_NONE_EXT 0x20D8 +#define GLX_TEXTURE_FORMAT_RGB_EXT 0x20D9 +#define GLX_TEXTURE_FORMAT_RGBA_EXT 0x20DA +#define GLX_TEXTURE_1D_EXT 0x20DB +#define GLX_TEXTURE_2D_EXT 0x20DC +#define GLX_TEXTURE_RECTANGLE_EXT 0x20DD +#define GLX_FRONT_LEFT_EXT 0x20DE +#define GLX_FRONT_RIGHT_EXT 0x20DF +#define GLX_BACK_LEFT_EXT 0x20E0 +#define GLX_BACK_RIGHT_EXT 0x20E1 +#define GLX_FRONT_EXT 0x20DE +#define GLX_BACK_EXT 0x20E0 +#define GLX_AUX0_EXT 0x20E2 +#define GLX_AUX1_EXT 0x20E3 +#define GLX_AUX2_EXT 0x20E4 +#define GLX_AUX3_EXT 0x20E5 +#define GLX_AUX4_EXT 0x20E6 +#define GLX_AUX5_EXT 0x20E7 +#define GLX_AUX6_EXT 0x20E8 +#define GLX_AUX7_EXT 0x20E9 +#define GLX_AUX8_EXT 0x20EA +#define GLX_AUX9_EXT 0x20EB +typedef void ( *PFNGLXBINDTEXIMAGEEXTPROC) (Display *dpy, GLXDrawable drawable, int buffer, const int *attrib_list); +typedef void ( *PFNGLXRELEASETEXIMAGEEXTPROC) (Display *dpy, GLXDrawable drawable, int buffer); +#ifdef GLX_GLXEXT_PROTOTYPES +void glXBindTexImageEXT (Display *dpy, GLXDrawable drawable, int buffer, const int *attrib_list); +void glXReleaseTexImageEXT (Display *dpy, GLXDrawable drawable, int buffer); +#endif +#endif /* GLX_EXT_texture_from_pixmap */ + +#ifndef GLX_EXT_visual_info +#define GLX_EXT_visual_info 1 +#define GLX_X_VISUAL_TYPE_EXT 0x22 +#define GLX_TRANSPARENT_TYPE_EXT 0x23 +#define GLX_TRANSPARENT_INDEX_VALUE_EXT 0x24 +#define GLX_TRANSPARENT_RED_VALUE_EXT 0x25 +#define GLX_TRANSPARENT_GREEN_VALUE_EXT 0x26 +#define GLX_TRANSPARENT_BLUE_VALUE_EXT 0x27 +#define GLX_TRANSPARENT_ALPHA_VALUE_EXT 0x28 +#define GLX_NONE_EXT 0x8000 +#define GLX_TRUE_COLOR_EXT 0x8002 +#define GLX_DIRECT_COLOR_EXT 0x8003 +#define GLX_PSEUDO_COLOR_EXT 0x8004 +#define GLX_STATIC_COLOR_EXT 0x8005 +#define GLX_GRAY_SCALE_EXT 0x8006 +#define GLX_STATIC_GRAY_EXT 0x8007 +#define GLX_TRANSPARENT_RGB_EXT 0x8008 +#define GLX_TRANSPARENT_INDEX_EXT 0x8009 +#endif /* GLX_EXT_visual_info */ + +#ifndef GLX_EXT_visual_rating +#define GLX_EXT_visual_rating 1 +#define GLX_VISUAL_CAVEAT_EXT 0x20 +#define GLX_SLOW_VISUAL_EXT 0x8001 +#define GLX_NON_CONFORMANT_VISUAL_EXT 0x800D +#endif /* GLX_EXT_visual_rating */ + +#ifndef GLX_INTEL_swap_event +#define GLX_INTEL_swap_event 1 +#define GLX_BUFFER_SWAP_COMPLETE_INTEL_MASK 0x04000000 +#define GLX_EXCHANGE_COMPLETE_INTEL 0x8180 +#define GLX_COPY_COMPLETE_INTEL 0x8181 +#define GLX_FLIP_COMPLETE_INTEL 0x8182 +#endif /* GLX_INTEL_swap_event */ + +#ifndef GLX_MESA_agp_offset +#define GLX_MESA_agp_offset 1 +typedef unsigned int ( *PFNGLXGETAGPOFFSETMESAPROC) (const void *pointer); +#ifdef GLX_GLXEXT_PROTOTYPES +unsigned int glXGetAGPOffsetMESA (const void *pointer); +#endif +#endif /* GLX_MESA_agp_offset */ + +#ifndef GLX_MESA_copy_sub_buffer +#define GLX_MESA_copy_sub_buffer 1 +typedef void ( *PFNGLXCOPYSUBBUFFERMESAPROC) (Display *dpy, GLXDrawable drawable, int x, int y, int width, int height); +#ifdef GLX_GLXEXT_PROTOTYPES +void glXCopySubBufferMESA (Display *dpy, GLXDrawable drawable, int x, int y, int width, int height); +#endif +#endif /* GLX_MESA_copy_sub_buffer */ + +#ifndef GLX_MESA_pixmap_colormap +#define GLX_MESA_pixmap_colormap 1 +typedef GLXPixmap ( *PFNGLXCREATEGLXPIXMAPMESAPROC) (Display *dpy, XVisualInfo *visual, Pixmap pixmap, Colormap cmap); +#ifdef GLX_GLXEXT_PROTOTYPES +GLXPixmap glXCreateGLXPixmapMESA (Display *dpy, XVisualInfo *visual, Pixmap pixmap, Colormap cmap); +#endif +#endif /* GLX_MESA_pixmap_colormap */ + +#ifndef GLX_MESA_query_renderer +#define GLX_MESA_query_renderer 1 +#define GLX_RENDERER_VENDOR_ID_MESA 0x8183 +#define GLX_RENDERER_DEVICE_ID_MESA 0x8184 +#define GLX_RENDERER_VERSION_MESA 0x8185 +#define GLX_RENDERER_ACCELERATED_MESA 0x8186 +#define GLX_RENDERER_VIDEO_MEMORY_MESA 0x8187 +#define GLX_RENDERER_UNIFIED_MEMORY_ARCHITECTURE_MESA 0x8188 +#define GLX_RENDERER_PREFERRED_PROFILE_MESA 0x8189 +#define GLX_RENDERER_OPENGL_CORE_PROFILE_VERSION_MESA 0x818A +#define GLX_RENDERER_OPENGL_COMPATIBILITY_PROFILE_VERSION_MESA 0x818B +#define GLX_RENDERER_OPENGL_ES_PROFILE_VERSION_MESA 0x818C +#define GLX_RENDERER_OPENGL_ES2_PROFILE_VERSION_MESA 0x818D +typedef Bool ( *PFNGLXQUERYCURRENTRENDERERINTEGERMESAPROC) (int attribute, unsigned int *value); +typedef const char *( *PFNGLXQUERYCURRENTRENDERERSTRINGMESAPROC) (int attribute); +typedef Bool ( *PFNGLXQUERYRENDERERINTEGERMESAPROC) (Display *dpy, int screen, int renderer, int attribute, unsigned int *value); +typedef const char *( *PFNGLXQUERYRENDERERSTRINGMESAPROC) (Display *dpy, int screen, int renderer, int attribute); +#ifdef GLX_GLXEXT_PROTOTYPES +Bool glXQueryCurrentRendererIntegerMESA (int attribute, unsigned int *value); +const char *glXQueryCurrentRendererStringMESA (int attribute); +Bool glXQueryRendererIntegerMESA (Display *dpy, int screen, int renderer, int attribute, unsigned int *value); +const char *glXQueryRendererStringMESA (Display *dpy, int screen, int renderer, int attribute); +#endif +#endif /* GLX_MESA_query_renderer */ + +#ifndef GLX_MESA_release_buffers +#define GLX_MESA_release_buffers 1 +typedef Bool ( *PFNGLXRELEASEBUFFERSMESAPROC) (Display *dpy, GLXDrawable drawable); +#ifdef GLX_GLXEXT_PROTOTYPES +Bool glXReleaseBuffersMESA (Display *dpy, GLXDrawable drawable); +#endif +#endif /* GLX_MESA_release_buffers */ + +#ifndef GLX_MESA_set_3dfx_mode +#define GLX_MESA_set_3dfx_mode 1 +#define GLX_3DFX_WINDOW_MODE_MESA 0x1 +#define GLX_3DFX_FULLSCREEN_MODE_MESA 0x2 +typedef GLboolean ( *PFNGLXSET3DFXMODEMESAPROC) (GLint mode); +#ifdef GLX_GLXEXT_PROTOTYPES +GLboolean glXSet3DfxModeMESA (GLint mode); +#endif +#endif /* GLX_MESA_set_3dfx_mode */ + +#ifndef GLX_MESA_swap_control +#define GLX_MESA_swap_control 1 +typedef int ( *PFNGLXGETSWAPINTERVALMESAPROC) (void); +typedef int ( *PFNGLXSWAPINTERVALMESAPROC) (unsigned int interval); +#ifdef GLX_GLXEXT_PROTOTYPES +int glXGetSwapIntervalMESA (void); +int glXSwapIntervalMESA (unsigned int interval); +#endif +#endif /* GLX_MESA_swap_control */ + +#ifndef GLX_NV_copy_buffer +#define GLX_NV_copy_buffer 1 +typedef void ( *PFNGLXCOPYBUFFERSUBDATANVPROC) (Display *dpy, GLXContext readCtx, GLXContext writeCtx, GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); +typedef void ( *PFNGLXNAMEDCOPYBUFFERSUBDATANVPROC) (Display *dpy, GLXContext readCtx, GLXContext writeCtx, GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); +#ifdef GLX_GLXEXT_PROTOTYPES +void glXCopyBufferSubDataNV (Display *dpy, GLXContext readCtx, GLXContext writeCtx, GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); +void glXNamedCopyBufferSubDataNV (Display *dpy, GLXContext readCtx, GLXContext writeCtx, GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size); +#endif +#endif /* GLX_NV_copy_buffer */ + +#ifndef GLX_NV_copy_image +#define GLX_NV_copy_image 1 +typedef void ( *PFNGLXCOPYIMAGESUBDATANVPROC) (Display *dpy, GLXContext srcCtx, GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLXContext dstCtx, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei width, GLsizei height, GLsizei depth); +#ifdef GLX_GLXEXT_PROTOTYPES +void glXCopyImageSubDataNV (Display *dpy, GLXContext srcCtx, GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLXContext dstCtx, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei width, GLsizei height, GLsizei depth); +#endif +#endif /* GLX_NV_copy_image */ + +#ifndef GLX_NV_delay_before_swap +#define GLX_NV_delay_before_swap 1 +typedef Bool ( *PFNGLXDELAYBEFORESWAPNVPROC) (Display *dpy, GLXDrawable drawable, GLfloat seconds); +#ifdef GLX_GLXEXT_PROTOTYPES +Bool glXDelayBeforeSwapNV (Display *dpy, GLXDrawable drawable, GLfloat seconds); +#endif +#endif /* GLX_NV_delay_before_swap */ + +#ifndef GLX_NV_float_buffer +#define GLX_NV_float_buffer 1 +#define GLX_FLOAT_COMPONENTS_NV 0x20B0 +#endif /* GLX_NV_float_buffer */ + +#ifndef GLX_NV_multigpu_context +#define GLX_NV_multigpu_context 1 +#define GLX_CONTEXT_MULTIGPU_ATTRIB_NV 0x20AA +#define GLX_CONTEXT_MULTIGPU_ATTRIB_SINGLE_NV 0x20AB +#define GLX_CONTEXT_MULTIGPU_ATTRIB_AFR_NV 0x20AC +#define GLX_CONTEXT_MULTIGPU_ATTRIB_MULTICAST_NV 0x20AD +#define GLX_CONTEXT_MULTIGPU_ATTRIB_MULTI_DISPLAY_MULTICAST_NV 0x20AE +#endif /* GLX_NV_multigpu_context */ + +#ifndef GLX_NV_multisample_coverage +#define GLX_NV_multisample_coverage 1 +#define GLX_COVERAGE_SAMPLES_NV 100001 +#define GLX_COLOR_SAMPLES_NV 0x20B3 +#endif /* GLX_NV_multisample_coverage */ + +#ifndef GLX_NV_present_video +#define GLX_NV_present_video 1 +#define GLX_NUM_VIDEO_SLOTS_NV 0x20F0 +typedef unsigned int *( *PFNGLXENUMERATEVIDEODEVICESNVPROC) (Display *dpy, int screen, int *nelements); +typedef int ( *PFNGLXBINDVIDEODEVICENVPROC) (Display *dpy, unsigned int video_slot, unsigned int video_device, const int *attrib_list); +#ifdef GLX_GLXEXT_PROTOTYPES +unsigned int *glXEnumerateVideoDevicesNV (Display *dpy, int screen, int *nelements); +int glXBindVideoDeviceNV (Display *dpy, unsigned int video_slot, unsigned int video_device, const int *attrib_list); +#endif +#endif /* GLX_NV_present_video */ + +#ifndef GLX_NV_robustness_video_memory_purge +#define GLX_NV_robustness_video_memory_purge 1 +#define GLX_GENERATE_RESET_ON_VIDEO_MEMORY_PURGE_NV 0x20F7 +#endif /* GLX_NV_robustness_video_memory_purge */ + +#ifndef GLX_NV_swap_group +#define GLX_NV_swap_group 1 +typedef Bool ( *PFNGLXJOINSWAPGROUPNVPROC) (Display *dpy, GLXDrawable drawable, GLuint group); +typedef Bool ( *PFNGLXBINDSWAPBARRIERNVPROC) (Display *dpy, GLuint group, GLuint barrier); +typedef Bool ( *PFNGLXQUERYSWAPGROUPNVPROC) (Display *dpy, GLXDrawable drawable, GLuint *group, GLuint *barrier); +typedef Bool ( *PFNGLXQUERYMAXSWAPGROUPSNVPROC) (Display *dpy, int screen, GLuint *maxGroups, GLuint *maxBarriers); +typedef Bool ( *PFNGLXQUERYFRAMECOUNTNVPROC) (Display *dpy, int screen, GLuint *count); +typedef Bool ( *PFNGLXRESETFRAMECOUNTNVPROC) (Display *dpy, int screen); +#ifdef GLX_GLXEXT_PROTOTYPES +Bool glXJoinSwapGroupNV (Display *dpy, GLXDrawable drawable, GLuint group); +Bool glXBindSwapBarrierNV (Display *dpy, GLuint group, GLuint barrier); +Bool glXQuerySwapGroupNV (Display *dpy, GLXDrawable drawable, GLuint *group, GLuint *barrier); +Bool glXQueryMaxSwapGroupsNV (Display *dpy, int screen, GLuint *maxGroups, GLuint *maxBarriers); +Bool glXQueryFrameCountNV (Display *dpy, int screen, GLuint *count); +Bool glXResetFrameCountNV (Display *dpy, int screen); +#endif +#endif /* GLX_NV_swap_group */ + +#ifndef GLX_NV_video_capture +#define GLX_NV_video_capture 1 +typedef XID GLXVideoCaptureDeviceNV; +#define GLX_DEVICE_ID_NV 0x20CD +#define GLX_UNIQUE_ID_NV 0x20CE +#define GLX_NUM_VIDEO_CAPTURE_SLOTS_NV 0x20CF +typedef int ( *PFNGLXBINDVIDEOCAPTUREDEVICENVPROC) (Display *dpy, unsigned int video_capture_slot, GLXVideoCaptureDeviceNV device); +typedef GLXVideoCaptureDeviceNV *( *PFNGLXENUMERATEVIDEOCAPTUREDEVICESNVPROC) (Display *dpy, int screen, int *nelements); +typedef void ( *PFNGLXLOCKVIDEOCAPTUREDEVICENVPROC) (Display *dpy, GLXVideoCaptureDeviceNV device); +typedef int ( *PFNGLXQUERYVIDEOCAPTUREDEVICENVPROC) (Display *dpy, GLXVideoCaptureDeviceNV device, int attribute, int *value); +typedef void ( *PFNGLXRELEASEVIDEOCAPTUREDEVICENVPROC) (Display *dpy, GLXVideoCaptureDeviceNV device); +#ifdef GLX_GLXEXT_PROTOTYPES +int glXBindVideoCaptureDeviceNV (Display *dpy, unsigned int video_capture_slot, GLXVideoCaptureDeviceNV device); +GLXVideoCaptureDeviceNV *glXEnumerateVideoCaptureDevicesNV (Display *dpy, int screen, int *nelements); +void glXLockVideoCaptureDeviceNV (Display *dpy, GLXVideoCaptureDeviceNV device); +int glXQueryVideoCaptureDeviceNV (Display *dpy, GLXVideoCaptureDeviceNV device, int attribute, int *value); +void glXReleaseVideoCaptureDeviceNV (Display *dpy, GLXVideoCaptureDeviceNV device); +#endif +#endif /* GLX_NV_video_capture */ + +#ifndef GLX_NV_video_out +#define GLX_NV_video_out 1 +typedef unsigned int GLXVideoDeviceNV; +#define GLX_VIDEO_OUT_COLOR_NV 0x20C3 +#define GLX_VIDEO_OUT_ALPHA_NV 0x20C4 +#define GLX_VIDEO_OUT_DEPTH_NV 0x20C5 +#define GLX_VIDEO_OUT_COLOR_AND_ALPHA_NV 0x20C6 +#define GLX_VIDEO_OUT_COLOR_AND_DEPTH_NV 0x20C7 +#define GLX_VIDEO_OUT_FRAME_NV 0x20C8 +#define GLX_VIDEO_OUT_FIELD_1_NV 0x20C9 +#define GLX_VIDEO_OUT_FIELD_2_NV 0x20CA +#define GLX_VIDEO_OUT_STACKED_FIELDS_1_2_NV 0x20CB +#define GLX_VIDEO_OUT_STACKED_FIELDS_2_1_NV 0x20CC +typedef int ( *PFNGLXGETVIDEODEVICENVPROC) (Display *dpy, int screen, int numVideoDevices, GLXVideoDeviceNV *pVideoDevice); +typedef int ( *PFNGLXRELEASEVIDEODEVICENVPROC) (Display *dpy, int screen, GLXVideoDeviceNV VideoDevice); +typedef int ( *PFNGLXBINDVIDEOIMAGENVPROC) (Display *dpy, GLXVideoDeviceNV VideoDevice, GLXPbuffer pbuf, int iVideoBuffer); +typedef int ( *PFNGLXRELEASEVIDEOIMAGENVPROC) (Display *dpy, GLXPbuffer pbuf); +typedef int ( *PFNGLXSENDPBUFFERTOVIDEONVPROC) (Display *dpy, GLXPbuffer pbuf, int iBufferType, unsigned long *pulCounterPbuffer, GLboolean bBlock); +typedef int ( *PFNGLXGETVIDEOINFONVPROC) (Display *dpy, int screen, GLXVideoDeviceNV VideoDevice, unsigned long *pulCounterOutputPbuffer, unsigned long *pulCounterOutputVideo); +#ifdef GLX_GLXEXT_PROTOTYPES +int glXGetVideoDeviceNV (Display *dpy, int screen, int numVideoDevices, GLXVideoDeviceNV *pVideoDevice); +int glXReleaseVideoDeviceNV (Display *dpy, int screen, GLXVideoDeviceNV VideoDevice); +int glXBindVideoImageNV (Display *dpy, GLXVideoDeviceNV VideoDevice, GLXPbuffer pbuf, int iVideoBuffer); +int glXReleaseVideoImageNV (Display *dpy, GLXPbuffer pbuf); +int glXSendPbufferToVideoNV (Display *dpy, GLXPbuffer pbuf, int iBufferType, unsigned long *pulCounterPbuffer, GLboolean bBlock); +int glXGetVideoInfoNV (Display *dpy, int screen, GLXVideoDeviceNV VideoDevice, unsigned long *pulCounterOutputPbuffer, unsigned long *pulCounterOutputVideo); +#endif +#endif /* GLX_NV_video_out */ + +#ifndef GLX_OML_swap_method +#define GLX_OML_swap_method 1 +#define GLX_SWAP_METHOD_OML 0x8060 +#define GLX_SWAP_EXCHANGE_OML 0x8061 +#define GLX_SWAP_COPY_OML 0x8062 +#define GLX_SWAP_UNDEFINED_OML 0x8063 +#endif /* GLX_OML_swap_method */ + +#ifndef GLX_OML_sync_control +#define GLX_OML_sync_control 1 +#ifndef GLEXT_64_TYPES_DEFINED +/* This code block is duplicated in glext.h, so must be protected */ +#define GLEXT_64_TYPES_DEFINED +/* Define int32_t, int64_t, and uint64_t types for UST/MSC */ +/* (as used in the GLX_OML_sync_control extension). */ +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +#include +#elif defined(__sun__) || defined(__digital__) +#include +#if defined(__STDC__) +#if defined(__arch64__) || defined(_LP64) +typedef long int int64_t; +typedef unsigned long int uint64_t; +#else +typedef long long int int64_t; +typedef unsigned long long int uint64_t; +#endif /* __arch64__ */ +#endif /* __STDC__ */ +#elif defined( __VMS ) || defined(__sgi) +#include +#elif defined(__SCO__) || defined(__USLC__) +#include +#elif defined(__UNIXOS2__) || defined(__SOL64__) +typedef long int int32_t; +typedef long long int int64_t; +typedef unsigned long long int uint64_t; +#elif defined(_WIN32) && defined(__GNUC__) +#include +#elif defined(_WIN32) +typedef __int32 int32_t; +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; +#else +/* Fallback if nothing above works */ +#include +#endif +#endif +typedef Bool ( *PFNGLXGETSYNCVALUESOMLPROC) (Display *dpy, GLXDrawable drawable, int64_t *ust, int64_t *msc, int64_t *sbc); +typedef Bool ( *PFNGLXGETMSCRATEOMLPROC) (Display *dpy, GLXDrawable drawable, int32_t *numerator, int32_t *denominator); +typedef int64_t ( *PFNGLXSWAPBUFFERSMSCOMLPROC) (Display *dpy, GLXDrawable drawable, int64_t target_msc, int64_t divisor, int64_t remainder); +typedef Bool ( *PFNGLXWAITFORMSCOMLPROC) (Display *dpy, GLXDrawable drawable, int64_t target_msc, int64_t divisor, int64_t remainder, int64_t *ust, int64_t *msc, int64_t *sbc); +typedef Bool ( *PFNGLXWAITFORSBCOMLPROC) (Display *dpy, GLXDrawable drawable, int64_t target_sbc, int64_t *ust, int64_t *msc, int64_t *sbc); +#ifdef GLX_GLXEXT_PROTOTYPES +Bool glXGetSyncValuesOML (Display *dpy, GLXDrawable drawable, int64_t *ust, int64_t *msc, int64_t *sbc); +Bool glXGetMscRateOML (Display *dpy, GLXDrawable drawable, int32_t *numerator, int32_t *denominator); +int64_t glXSwapBuffersMscOML (Display *dpy, GLXDrawable drawable, int64_t target_msc, int64_t divisor, int64_t remainder); +Bool glXWaitForMscOML (Display *dpy, GLXDrawable drawable, int64_t target_msc, int64_t divisor, int64_t remainder, int64_t *ust, int64_t *msc, int64_t *sbc); +Bool glXWaitForSbcOML (Display *dpy, GLXDrawable drawable, int64_t target_sbc, int64_t *ust, int64_t *msc, int64_t *sbc); +#endif +#endif /* GLX_OML_sync_control */ + +#ifndef GLX_SGIS_blended_overlay +#define GLX_SGIS_blended_overlay 1 +#define GLX_BLENDED_RGBA_SGIS 0x8025 +#endif /* GLX_SGIS_blended_overlay */ + +#ifndef GLX_SGIS_multisample +#define GLX_SGIS_multisample 1 +#define GLX_SAMPLE_BUFFERS_SGIS 100000 +#define GLX_SAMPLES_SGIS 100001 +#endif /* GLX_SGIS_multisample */ + +#ifndef GLX_SGIS_shared_multisample +#define GLX_SGIS_shared_multisample 1 +#define GLX_MULTISAMPLE_SUB_RECT_WIDTH_SGIS 0x8026 +#define GLX_MULTISAMPLE_SUB_RECT_HEIGHT_SGIS 0x8027 +#endif /* GLX_SGIS_shared_multisample */ + +#ifndef GLX_SGIX_dmbuffer +#define GLX_SGIX_dmbuffer 1 +typedef XID GLXPbufferSGIX; +#ifdef _DM_BUFFER_H_ +#define GLX_DIGITAL_MEDIA_PBUFFER_SGIX 0x8024 +typedef Bool ( *PFNGLXASSOCIATEDMPBUFFERSGIXPROC) (Display *dpy, GLXPbufferSGIX pbuffer, DMparams *params, DMbuffer dmbuffer); +#ifdef GLX_GLXEXT_PROTOTYPES +Bool glXAssociateDMPbufferSGIX (Display *dpy, GLXPbufferSGIX pbuffer, DMparams *params, DMbuffer dmbuffer); +#endif +#endif /* _DM_BUFFER_H_ */ +#endif /* GLX_SGIX_dmbuffer */ + +#ifndef GLX_SGIX_fbconfig +#define GLX_SGIX_fbconfig 1 +typedef struct __GLXFBConfigRec *GLXFBConfigSGIX; +#define GLX_WINDOW_BIT_SGIX 0x00000001 +#define GLX_PIXMAP_BIT_SGIX 0x00000002 +#define GLX_RGBA_BIT_SGIX 0x00000001 +#define GLX_COLOR_INDEX_BIT_SGIX 0x00000002 +#define GLX_DRAWABLE_TYPE_SGIX 0x8010 +#define GLX_RENDER_TYPE_SGIX 0x8011 +#define GLX_X_RENDERABLE_SGIX 0x8012 +#define GLX_FBCONFIG_ID_SGIX 0x8013 +#define GLX_RGBA_TYPE_SGIX 0x8014 +#define GLX_COLOR_INDEX_TYPE_SGIX 0x8015 +typedef int ( *PFNGLXGETFBCONFIGATTRIBSGIXPROC) (Display *dpy, GLXFBConfigSGIX config, int attribute, int *value); +typedef GLXFBConfigSGIX *( *PFNGLXCHOOSEFBCONFIGSGIXPROC) (Display *dpy, int screen, int *attrib_list, int *nelements); +typedef GLXPixmap ( *PFNGLXCREATEGLXPIXMAPWITHCONFIGSGIXPROC) (Display *dpy, GLXFBConfigSGIX config, Pixmap pixmap); +typedef GLXContext ( *PFNGLXCREATECONTEXTWITHCONFIGSGIXPROC) (Display *dpy, GLXFBConfigSGIX config, int render_type, GLXContext share_list, Bool direct); +typedef XVisualInfo *( *PFNGLXGETVISUALFROMFBCONFIGSGIXPROC) (Display *dpy, GLXFBConfigSGIX config); +typedef GLXFBConfigSGIX ( *PFNGLXGETFBCONFIGFROMVISUALSGIXPROC) (Display *dpy, XVisualInfo *vis); +#ifdef GLX_GLXEXT_PROTOTYPES +int glXGetFBConfigAttribSGIX (Display *dpy, GLXFBConfigSGIX config, int attribute, int *value); +GLXFBConfigSGIX *glXChooseFBConfigSGIX (Display *dpy, int screen, int *attrib_list, int *nelements); +GLXPixmap glXCreateGLXPixmapWithConfigSGIX (Display *dpy, GLXFBConfigSGIX config, Pixmap pixmap); +GLXContext glXCreateContextWithConfigSGIX (Display *dpy, GLXFBConfigSGIX config, int render_type, GLXContext share_list, Bool direct); +XVisualInfo *glXGetVisualFromFBConfigSGIX (Display *dpy, GLXFBConfigSGIX config); +GLXFBConfigSGIX glXGetFBConfigFromVisualSGIX (Display *dpy, XVisualInfo *vis); +#endif +#endif /* GLX_SGIX_fbconfig */ + +#ifndef GLX_SGIX_hyperpipe +#define GLX_SGIX_hyperpipe 1 +typedef struct { + char pipeName[80]; /* Should be [GLX_HYPERPIPE_PIPE_NAME_LENGTH_SGIX] */ + int networkId; +} GLXHyperpipeNetworkSGIX; +typedef struct { + char pipeName[80]; /* Should be [GLX_HYPERPIPE_PIPE_NAME_LENGTH_SGIX] */ + int channel; + unsigned int participationType; + int timeSlice; +} GLXHyperpipeConfigSGIX; +typedef struct { + char pipeName[80]; /* Should be [GLX_HYPERPIPE_PIPE_NAME_LENGTH_SGIX] */ + int srcXOrigin, srcYOrigin, srcWidth, srcHeight; + int destXOrigin, destYOrigin, destWidth, destHeight; +} GLXPipeRect; +typedef struct { + char pipeName[80]; /* Should be [GLX_HYPERPIPE_PIPE_NAME_LENGTH_SGIX] */ + int XOrigin, YOrigin, maxHeight, maxWidth; +} GLXPipeRectLimits; +#define GLX_HYPERPIPE_PIPE_NAME_LENGTH_SGIX 80 +#define GLX_BAD_HYPERPIPE_CONFIG_SGIX 91 +#define GLX_BAD_HYPERPIPE_SGIX 92 +#define GLX_HYPERPIPE_DISPLAY_PIPE_SGIX 0x00000001 +#define GLX_HYPERPIPE_RENDER_PIPE_SGIX 0x00000002 +#define GLX_PIPE_RECT_SGIX 0x00000001 +#define GLX_PIPE_RECT_LIMITS_SGIX 0x00000002 +#define GLX_HYPERPIPE_STEREO_SGIX 0x00000003 +#define GLX_HYPERPIPE_PIXEL_AVERAGE_SGIX 0x00000004 +#define GLX_HYPERPIPE_ID_SGIX 0x8030 +typedef GLXHyperpipeNetworkSGIX *( *PFNGLXQUERYHYPERPIPENETWORKSGIXPROC) (Display *dpy, int *npipes); +typedef int ( *PFNGLXHYPERPIPECONFIGSGIXPROC) (Display *dpy, int networkId, int npipes, GLXHyperpipeConfigSGIX *cfg, int *hpId); +typedef GLXHyperpipeConfigSGIX *( *PFNGLXQUERYHYPERPIPECONFIGSGIXPROC) (Display *dpy, int hpId, int *npipes); +typedef int ( *PFNGLXDESTROYHYPERPIPECONFIGSGIXPROC) (Display *dpy, int hpId); +typedef int ( *PFNGLXBINDHYPERPIPESGIXPROC) (Display *dpy, int hpId); +typedef int ( *PFNGLXQUERYHYPERPIPEBESTATTRIBSGIXPROC) (Display *dpy, int timeSlice, int attrib, int size, void *attribList, void *returnAttribList); +typedef int ( *PFNGLXHYPERPIPEATTRIBSGIXPROC) (Display *dpy, int timeSlice, int attrib, int size, void *attribList); +typedef int ( *PFNGLXQUERYHYPERPIPEATTRIBSGIXPROC) (Display *dpy, int timeSlice, int attrib, int size, void *returnAttribList); +#ifdef GLX_GLXEXT_PROTOTYPES +GLXHyperpipeNetworkSGIX *glXQueryHyperpipeNetworkSGIX (Display *dpy, int *npipes); +int glXHyperpipeConfigSGIX (Display *dpy, int networkId, int npipes, GLXHyperpipeConfigSGIX *cfg, int *hpId); +GLXHyperpipeConfigSGIX *glXQueryHyperpipeConfigSGIX (Display *dpy, int hpId, int *npipes); +int glXDestroyHyperpipeConfigSGIX (Display *dpy, int hpId); +int glXBindHyperpipeSGIX (Display *dpy, int hpId); +int glXQueryHyperpipeBestAttribSGIX (Display *dpy, int timeSlice, int attrib, int size, void *attribList, void *returnAttribList); +int glXHyperpipeAttribSGIX (Display *dpy, int timeSlice, int attrib, int size, void *attribList); +int glXQueryHyperpipeAttribSGIX (Display *dpy, int timeSlice, int attrib, int size, void *returnAttribList); +#endif +#endif /* GLX_SGIX_hyperpipe */ + +#ifndef GLX_SGIX_pbuffer +#define GLX_SGIX_pbuffer 1 +#define GLX_PBUFFER_BIT_SGIX 0x00000004 +#define GLX_BUFFER_CLOBBER_MASK_SGIX 0x08000000 +#define GLX_FRONT_LEFT_BUFFER_BIT_SGIX 0x00000001 +#define GLX_FRONT_RIGHT_BUFFER_BIT_SGIX 0x00000002 +#define GLX_BACK_LEFT_BUFFER_BIT_SGIX 0x00000004 +#define GLX_BACK_RIGHT_BUFFER_BIT_SGIX 0x00000008 +#define GLX_AUX_BUFFERS_BIT_SGIX 0x00000010 +#define GLX_DEPTH_BUFFER_BIT_SGIX 0x00000020 +#define GLX_STENCIL_BUFFER_BIT_SGIX 0x00000040 +#define GLX_ACCUM_BUFFER_BIT_SGIX 0x00000080 +#define GLX_SAMPLE_BUFFERS_BIT_SGIX 0x00000100 +#define GLX_MAX_PBUFFER_WIDTH_SGIX 0x8016 +#define GLX_MAX_PBUFFER_HEIGHT_SGIX 0x8017 +#define GLX_MAX_PBUFFER_PIXELS_SGIX 0x8018 +#define GLX_OPTIMAL_PBUFFER_WIDTH_SGIX 0x8019 +#define GLX_OPTIMAL_PBUFFER_HEIGHT_SGIX 0x801A +#define GLX_PRESERVED_CONTENTS_SGIX 0x801B +#define GLX_LARGEST_PBUFFER_SGIX 0x801C +#define GLX_WIDTH_SGIX 0x801D +#define GLX_HEIGHT_SGIX 0x801E +#define GLX_EVENT_MASK_SGIX 0x801F +#define GLX_DAMAGED_SGIX 0x8020 +#define GLX_SAVED_SGIX 0x8021 +#define GLX_WINDOW_SGIX 0x8022 +#define GLX_PBUFFER_SGIX 0x8023 +typedef GLXPbufferSGIX ( *PFNGLXCREATEGLXPBUFFERSGIXPROC) (Display *dpy, GLXFBConfigSGIX config, unsigned int width, unsigned int height, int *attrib_list); +typedef void ( *PFNGLXDESTROYGLXPBUFFERSGIXPROC) (Display *dpy, GLXPbufferSGIX pbuf); +typedef void ( *PFNGLXQUERYGLXPBUFFERSGIXPROC) (Display *dpy, GLXPbufferSGIX pbuf, int attribute, unsigned int *value); +typedef void ( *PFNGLXSELECTEVENTSGIXPROC) (Display *dpy, GLXDrawable drawable, unsigned long mask); +typedef void ( *PFNGLXGETSELECTEDEVENTSGIXPROC) (Display *dpy, GLXDrawable drawable, unsigned long *mask); +#ifdef GLX_GLXEXT_PROTOTYPES +GLXPbufferSGIX glXCreateGLXPbufferSGIX (Display *dpy, GLXFBConfigSGIX config, unsigned int width, unsigned int height, int *attrib_list); +void glXDestroyGLXPbufferSGIX (Display *dpy, GLXPbufferSGIX pbuf); +void glXQueryGLXPbufferSGIX (Display *dpy, GLXPbufferSGIX pbuf, int attribute, unsigned int *value); +void glXSelectEventSGIX (Display *dpy, GLXDrawable drawable, unsigned long mask); +void glXGetSelectedEventSGIX (Display *dpy, GLXDrawable drawable, unsigned long *mask); +#endif +#endif /* GLX_SGIX_pbuffer */ + +#ifndef GLX_SGIX_swap_barrier +#define GLX_SGIX_swap_barrier 1 +typedef void ( *PFNGLXBINDSWAPBARRIERSGIXPROC) (Display *dpy, GLXDrawable drawable, int barrier); +typedef Bool ( *PFNGLXQUERYMAXSWAPBARRIERSSGIXPROC) (Display *dpy, int screen, int *max); +#ifdef GLX_GLXEXT_PROTOTYPES +void glXBindSwapBarrierSGIX (Display *dpy, GLXDrawable drawable, int barrier); +Bool glXQueryMaxSwapBarriersSGIX (Display *dpy, int screen, int *max); +#endif +#endif /* GLX_SGIX_swap_barrier */ + +#ifndef GLX_SGIX_swap_group +#define GLX_SGIX_swap_group 1 +typedef void ( *PFNGLXJOINSWAPGROUPSGIXPROC) (Display *dpy, GLXDrawable drawable, GLXDrawable member); +#ifdef GLX_GLXEXT_PROTOTYPES +void glXJoinSwapGroupSGIX (Display *dpy, GLXDrawable drawable, GLXDrawable member); +#endif +#endif /* GLX_SGIX_swap_group */ + +#ifndef GLX_SGIX_video_resize +#define GLX_SGIX_video_resize 1 +#define GLX_SYNC_FRAME_SGIX 0x00000000 +#define GLX_SYNC_SWAP_SGIX 0x00000001 +typedef int ( *PFNGLXBINDCHANNELTOWINDOWSGIXPROC) (Display *display, int screen, int channel, Window window); +typedef int ( *PFNGLXCHANNELRECTSGIXPROC) (Display *display, int screen, int channel, int x, int y, int w, int h); +typedef int ( *PFNGLXQUERYCHANNELRECTSGIXPROC) (Display *display, int screen, int channel, int *dx, int *dy, int *dw, int *dh); +typedef int ( *PFNGLXQUERYCHANNELDELTASSGIXPROC) (Display *display, int screen, int channel, int *x, int *y, int *w, int *h); +typedef int ( *PFNGLXCHANNELRECTSYNCSGIXPROC) (Display *display, int screen, int channel, GLenum synctype); +#ifdef GLX_GLXEXT_PROTOTYPES +int glXBindChannelToWindowSGIX (Display *display, int screen, int channel, Window window); +int glXChannelRectSGIX (Display *display, int screen, int channel, int x, int y, int w, int h); +int glXQueryChannelRectSGIX (Display *display, int screen, int channel, int *dx, int *dy, int *dw, int *dh); +int glXQueryChannelDeltasSGIX (Display *display, int screen, int channel, int *x, int *y, int *w, int *h); +int glXChannelRectSyncSGIX (Display *display, int screen, int channel, GLenum synctype); +#endif +#endif /* GLX_SGIX_video_resize */ + +#ifndef GLX_SGIX_video_source +#define GLX_SGIX_video_source 1 +typedef XID GLXVideoSourceSGIX; +#ifdef _VL_H +typedef GLXVideoSourceSGIX ( *PFNGLXCREATEGLXVIDEOSOURCESGIXPROC) (Display *display, int screen, VLServer server, VLPath path, int nodeClass, VLNode drainNode); +typedef void ( *PFNGLXDESTROYGLXVIDEOSOURCESGIXPROC) (Display *dpy, GLXVideoSourceSGIX glxvideosource); +#ifdef GLX_GLXEXT_PROTOTYPES +GLXVideoSourceSGIX glXCreateGLXVideoSourceSGIX (Display *display, int screen, VLServer server, VLPath path, int nodeClass, VLNode drainNode); +void glXDestroyGLXVideoSourceSGIX (Display *dpy, GLXVideoSourceSGIX glxvideosource); +#endif +#endif /* _VL_H */ +#endif /* GLX_SGIX_video_source */ + +#ifndef GLX_SGIX_visual_select_group +#define GLX_SGIX_visual_select_group 1 +#define GLX_VISUAL_SELECT_GROUP_SGIX 0x8028 +#endif /* GLX_SGIX_visual_select_group */ + +#ifndef GLX_SGI_cushion +#define GLX_SGI_cushion 1 +typedef void ( *PFNGLXCUSHIONSGIPROC) (Display *dpy, Window window, float cushion); +#ifdef GLX_GLXEXT_PROTOTYPES +void glXCushionSGI (Display *dpy, Window window, float cushion); +#endif +#endif /* GLX_SGI_cushion */ + +#ifndef GLX_SGI_make_current_read +#define GLX_SGI_make_current_read 1 +typedef Bool ( *PFNGLXMAKECURRENTREADSGIPROC) (Display *dpy, GLXDrawable draw, GLXDrawable read, GLXContext ctx); +typedef GLXDrawable ( *PFNGLXGETCURRENTREADDRAWABLESGIPROC) (void); +#ifdef GLX_GLXEXT_PROTOTYPES +Bool glXMakeCurrentReadSGI (Display *dpy, GLXDrawable draw, GLXDrawable read, GLXContext ctx); +GLXDrawable glXGetCurrentReadDrawableSGI (void); +#endif +#endif /* GLX_SGI_make_current_read */ + +#ifndef GLX_SGI_swap_control +#define GLX_SGI_swap_control 1 +typedef int ( *PFNGLXSWAPINTERVALSGIPROC) (int interval); +#ifdef GLX_GLXEXT_PROTOTYPES +int glXSwapIntervalSGI (int interval); +#endif +#endif /* GLX_SGI_swap_control */ + +#ifndef GLX_SGI_video_sync +#define GLX_SGI_video_sync 1 +typedef int ( *PFNGLXGETVIDEOSYNCSGIPROC) (unsigned int *count); +typedef int ( *PFNGLXWAITVIDEOSYNCSGIPROC) (int divisor, int remainder, unsigned int *count); +#ifdef GLX_GLXEXT_PROTOTYPES +int glXGetVideoSyncSGI (unsigned int *count); +int glXWaitVideoSyncSGI (int divisor, int remainder, unsigned int *count); +#endif +#endif /* GLX_SGI_video_sync */ + +#ifndef GLX_SUN_get_transparent_index +#define GLX_SUN_get_transparent_index 1 +typedef Status ( *PFNGLXGETTRANSPARENTINDEXSUNPROC) (Display *dpy, Window overlay, Window underlay, unsigned long *pTransparentIndex); +#ifdef GLX_GLXEXT_PROTOTYPES +Status glXGetTransparentIndexSUN (Display *dpy, Window overlay, Window underlay, unsigned long *pTransparentIndex); +#endif +#endif /* GLX_SUN_get_transparent_index */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/Common/GLInclude/khrplatform.h b/src/Common/GLInclude/khrplatform.h new file mode 100644 index 00000000..01646449 --- /dev/null +++ b/src/Common/GLInclude/khrplatform.h @@ -0,0 +1,311 @@ +#ifndef __khrplatform_h_ +#define __khrplatform_h_ + +/* +** Copyright (c) 2008-2018 The Khronos Group Inc. +** +** Permission is hereby granted, free of charge, to any person obtaining a +** copy of this software and/or associated documentation files (the +** "Materials"), to deal in the Materials without restriction, including +** without limitation the rights to use, copy, modify, merge, publish, +** distribute, sublicense, and/or sell copies of the Materials, and to +** permit persons to whom the Materials are furnished to do so, subject to +** the following conditions: +** +** The above copyright notice and this permission notice shall be included +** in all copies or substantial portions of the Materials. +** +** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. +*/ + +/* Khronos platform-specific types and definitions. + * + * The master copy of khrplatform.h is maintained in the Khronos EGL + * Registry repository at https://github.com/KhronosGroup/EGL-Registry + * The last semantic modification to khrplatform.h was at commit ID: + * 67a3e0864c2d75ea5287b9f3d2eb74a745936692 + * + * Adopters may modify this file to suit their platform. Adopters are + * encouraged to submit platform specific modifications to the Khronos + * group so that they can be included in future versions of this file. + * Please submit changes by filing pull requests or issues on + * the EGL Registry repository linked above. + * + * + * See the Implementer's Guidelines for information about where this file + * should be located on your system and for more details of its use: + * http://www.khronos.org/registry/implementers_guide.pdf + * + * This file should be included as + * #include + * by Khronos client API header files that use its types and defines. + * + * The types in khrplatform.h should only be used to define API-specific types. + * + * Types defined in khrplatform.h: + * khronos_int8_t signed 8 bit + * khronos_uint8_t unsigned 8 bit + * khronos_int16_t signed 16 bit + * khronos_uint16_t unsigned 16 bit + * khronos_int32_t signed 32 bit + * khronos_uint32_t unsigned 32 bit + * khronos_int64_t signed 64 bit + * khronos_uint64_t unsigned 64 bit + * khronos_intptr_t signed same number of bits as a pointer + * khronos_uintptr_t unsigned same number of bits as a pointer + * khronos_ssize_t signed size + * khronos_usize_t unsigned size + * khronos_float_t signed 32 bit floating point + * khronos_time_ns_t unsigned 64 bit time in nanoseconds + * khronos_utime_nanoseconds_t unsigned time interval or absolute time in + * nanoseconds + * khronos_stime_nanoseconds_t signed time interval in nanoseconds + * khronos_boolean_enum_t enumerated boolean type. This should + * only be used as a base type when a client API's boolean type is + * an enum. Client APIs which use an integer or other type for + * booleans cannot use this as the base type for their boolean. + * + * Tokens defined in khrplatform.h: + * + * KHRONOS_FALSE, KHRONOS_TRUE Enumerated boolean false/true values. + * + * KHRONOS_SUPPORT_INT64 is 1 if 64 bit integers are supported; otherwise 0. + * KHRONOS_SUPPORT_FLOAT is 1 if floats are supported; otherwise 0. + * + * Calling convention macros defined in this file: + * KHRONOS_APICALL + * KHRONOS_APIENTRY + * KHRONOS_APIATTRIBUTES + * + * These may be used in function prototypes as: + * + * KHRONOS_APICALL void KHRONOS_APIENTRY funcname( + * int arg1, + * int arg2) KHRONOS_APIATTRIBUTES; + */ + +#if defined(__SCITECH_SNAP__) && !defined(KHRONOS_STATIC) +# define KHRONOS_STATIC 1 +#endif + +/*------------------------------------------------------------------------- + * Definition of KHRONOS_APICALL + *------------------------------------------------------------------------- + * This precedes the return type of the function in the function prototype. + */ +#if defined(KHRONOS_STATIC) + /* If the preprocessor constant KHRONOS_STATIC is defined, make the + * header compatible with static linking. */ +# define KHRONOS_APICALL +#elif defined(_WIN32) +# define KHRONOS_APICALL __declspec(dllimport) +#elif defined (__SYMBIAN32__) +# define KHRONOS_APICALL IMPORT_C +#elif defined(__ANDROID__) +# define KHRONOS_APICALL __attribute__((visibility("default"))) +#else +# define KHRONOS_APICALL +#endif + +/*------------------------------------------------------------------------- + * Definition of KHRONOS_APIENTRY + *------------------------------------------------------------------------- + * This follows the return type of the function and precedes the function + * name in the function prototype. + */ +#if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(__SCITECH_SNAP__) + /* Win32 but not WinCE */ +# define KHRONOS_APIENTRY __stdcall +#else +# define KHRONOS_APIENTRY +#endif + +/*------------------------------------------------------------------------- + * Definition of KHRONOS_APIATTRIBUTES + *------------------------------------------------------------------------- + * This follows the closing parenthesis of the function prototype arguments. + */ +#if defined (__ARMCC_2__) +#define KHRONOS_APIATTRIBUTES __softfp +#else +#define KHRONOS_APIATTRIBUTES +#endif + +/*------------------------------------------------------------------------- + * basic type definitions + *-----------------------------------------------------------------------*/ +#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__GNUC__) || defined(__SCO__) || defined(__USLC__) + + +/* + * Using + */ +#include +typedef int32_t khronos_int32_t; +typedef uint32_t khronos_uint32_t; +typedef int64_t khronos_int64_t; +typedef uint64_t khronos_uint64_t; +#define KHRONOS_SUPPORT_INT64 1 +#define KHRONOS_SUPPORT_FLOAT 1 +/* + * To support platform where unsigned long cannot be used interchangeably with + * inptr_t (e.g. CHERI-extended ISAs), we can use the stdint.h intptr_t. + * Ideally, we could just use (u)intptr_t everywhere, but this could result in + * ABI breakage if khronos_uintptr_t is changed from unsigned long to + * unsigned long long or similar (this results in different C++ name mangling). + * To avoid changes for existing platforms, we restrict usage of intptr_t to + * platforms where the size of a pointer is larger than the size of long. + */ +#if defined(__SIZEOF_LONG__) && defined(__SIZEOF_POINTER__) +#if __SIZEOF_POINTER__ > __SIZEOF_LONG__ +#define KHRONOS_USE_INTPTR_T +#endif +#endif + +#elif defined(__VMS ) || defined(__sgi) + +/* + * Using + */ +#include +typedef int32_t khronos_int32_t; +typedef uint32_t khronos_uint32_t; +typedef int64_t khronos_int64_t; +typedef uint64_t khronos_uint64_t; +#define KHRONOS_SUPPORT_INT64 1 +#define KHRONOS_SUPPORT_FLOAT 1 + +#elif defined(_WIN32) && !defined(__SCITECH_SNAP__) + +/* + * Win32 + */ +typedef __int32 khronos_int32_t; +typedef unsigned __int32 khronos_uint32_t; +typedef __int64 khronos_int64_t; +typedef unsigned __int64 khronos_uint64_t; +#define KHRONOS_SUPPORT_INT64 1 +#define KHRONOS_SUPPORT_FLOAT 1 + +#elif defined(__sun__) || defined(__digital__) + +/* + * Sun or Digital + */ +typedef int khronos_int32_t; +typedef unsigned int khronos_uint32_t; +#if defined(__arch64__) || defined(_LP64) +typedef long int khronos_int64_t; +typedef unsigned long int khronos_uint64_t; +#else +typedef long long int khronos_int64_t; +typedef unsigned long long int khronos_uint64_t; +#endif /* __arch64__ */ +#define KHRONOS_SUPPORT_INT64 1 +#define KHRONOS_SUPPORT_FLOAT 1 + +#elif 0 + +/* + * Hypothetical platform with no float or int64 support + */ +typedef int khronos_int32_t; +typedef unsigned int khronos_uint32_t; +#define KHRONOS_SUPPORT_INT64 0 +#define KHRONOS_SUPPORT_FLOAT 0 + +#else + +/* + * Generic fallback + */ +#include +typedef int32_t khronos_int32_t; +typedef uint32_t khronos_uint32_t; +typedef int64_t khronos_int64_t; +typedef uint64_t khronos_uint64_t; +#define KHRONOS_SUPPORT_INT64 1 +#define KHRONOS_SUPPORT_FLOAT 1 + +#endif + + +/* + * Types that are (so far) the same on all platforms + */ +typedef signed char khronos_int8_t; +typedef unsigned char khronos_uint8_t; +typedef signed short int khronos_int16_t; +typedef unsigned short int khronos_uint16_t; + +/* + * Types that differ between LLP64 and LP64 architectures - in LLP64, + * pointers are 64 bits, but 'long' is still 32 bits. Win64 appears + * to be the only LLP64 architecture in current use. + */ +#ifdef KHRONOS_USE_INTPTR_T +typedef intptr_t khronos_intptr_t; +typedef uintptr_t khronos_uintptr_t; +#elif defined(_WIN64) +typedef signed long long int khronos_intptr_t; +typedef unsigned long long int khronos_uintptr_t; +#else +typedef signed long int khronos_intptr_t; +typedef unsigned long int khronos_uintptr_t; +#endif + +#if defined(_WIN64) +typedef signed long long int khronos_ssize_t; +typedef unsigned long long int khronos_usize_t; +#else +typedef signed long int khronos_ssize_t; +typedef unsigned long int khronos_usize_t; +#endif + +#if KHRONOS_SUPPORT_FLOAT +/* + * Float type + */ +typedef float khronos_float_t; +#endif + +#if KHRONOS_SUPPORT_INT64 +/* Time types + * + * These types can be used to represent a time interval in nanoseconds or + * an absolute Unadjusted System Time. Unadjusted System Time is the number + * of nanoseconds since some arbitrary system event (e.g. since the last + * time the system booted). The Unadjusted System Time is an unsigned + * 64 bit value that wraps back to 0 every 584 years. Time intervals + * may be either signed or unsigned. + */ +typedef khronos_uint64_t khronos_utime_nanoseconds_t; +typedef khronos_int64_t khronos_stime_nanoseconds_t; +#endif + +/* + * Dummy value used to pad enum types to 32 bits. + */ +#ifndef KHRONOS_MAX_ENUM +#define KHRONOS_MAX_ENUM 0x7FFFFFFF +#endif + +/* + * Enumerated boolean type + * + * Values other than zero should be considered to be true. Therefore + * comparisons should not be made against KHRONOS_TRUE. + */ +typedef enum { + KHRONOS_FALSE = 0, + KHRONOS_TRUE = 1, + KHRONOS_BOOLEAN_ENUM_FORCE_SIZE = KHRONOS_MAX_ENUM +} khronos_boolean_enum_t; + +#endif /* __khrplatform_h_ */ diff --git a/src/Common/GLInclude/wglext.h b/src/Common/GLInclude/wglext.h new file mode 100644 index 00000000..70a885b4 --- /dev/null +++ b/src/Common/GLInclude/wglext.h @@ -0,0 +1,845 @@ +#ifndef __wgl_wglext_h_ +#define __wgl_wglext_h_ 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/* +** Copyright 2013-2020 The Khronos Group Inc. +** SPDX-License-Identifier: MIT +** +** This header is generated from the Khronos OpenGL / OpenGL ES XML +** API Registry. The current version of the Registry, generator scripts +** used to make the header, and the header can be found at +** https://github.com/KhronosGroup/OpenGL-Registry +*/ + +#if defined(_WIN32) && !defined(APIENTRY) && !defined(__CYGWIN__) && !defined(__SCITECH_SNAP__) +#define WIN32_LEAN_AND_MEAN 1 +#include +#endif + +#define WGL_WGLEXT_VERSION 20211115 + +/* Generated C header for: + * API: wgl + * Versions considered: .* + * Versions emitted: _nomatch_^ + * Default extensions included: wgl + * Additional extensions included: _nomatch_^ + * Extensions removed: _nomatch_^ + */ + +#ifndef WGL_ARB_buffer_region +#define WGL_ARB_buffer_region 1 +#define WGL_FRONT_COLOR_BUFFER_BIT_ARB 0x00000001 +#define WGL_BACK_COLOR_BUFFER_BIT_ARB 0x00000002 +#define WGL_DEPTH_BUFFER_BIT_ARB 0x00000004 +#define WGL_STENCIL_BUFFER_BIT_ARB 0x00000008 +typedef HANDLE (WINAPI * PFNWGLCREATEBUFFERREGIONARBPROC) (HDC hDC, int iLayerPlane, UINT uType); +typedef VOID (WINAPI * PFNWGLDELETEBUFFERREGIONARBPROC) (HANDLE hRegion); +typedef BOOL (WINAPI * PFNWGLSAVEBUFFERREGIONARBPROC) (HANDLE hRegion, int x, int y, int width, int height); +typedef BOOL (WINAPI * PFNWGLRESTOREBUFFERREGIONARBPROC) (HANDLE hRegion, int x, int y, int width, int height, int xSrc, int ySrc); +#ifdef WGL_WGLEXT_PROTOTYPES +HANDLE WINAPI wglCreateBufferRegionARB (HDC hDC, int iLayerPlane, UINT uType); +VOID WINAPI wglDeleteBufferRegionARB (HANDLE hRegion); +BOOL WINAPI wglSaveBufferRegionARB (HANDLE hRegion, int x, int y, int width, int height); +BOOL WINAPI wglRestoreBufferRegionARB (HANDLE hRegion, int x, int y, int width, int height, int xSrc, int ySrc); +#endif +#endif /* WGL_ARB_buffer_region */ + +#ifndef WGL_ARB_context_flush_control +#define WGL_ARB_context_flush_control 1 +#define WGL_CONTEXT_RELEASE_BEHAVIOR_ARB 0x2097 +#define WGL_CONTEXT_RELEASE_BEHAVIOR_NONE_ARB 0 +#define WGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_ARB 0x2098 +#endif /* WGL_ARB_context_flush_control */ + +#ifndef WGL_ARB_create_context +#define WGL_ARB_create_context 1 +#define WGL_CONTEXT_DEBUG_BIT_ARB 0x00000001 +#define WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x00000002 +#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 +#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 +#define WGL_CONTEXT_LAYER_PLANE_ARB 0x2093 +#define WGL_CONTEXT_FLAGS_ARB 0x2094 +#define ERROR_INVALID_VERSION_ARB 0x2095 +typedef HGLRC (WINAPI * PFNWGLCREATECONTEXTATTRIBSARBPROC) (HDC hDC, HGLRC hShareContext, const int *attribList); +#ifdef WGL_WGLEXT_PROTOTYPES +HGLRC WINAPI wglCreateContextAttribsARB (HDC hDC, HGLRC hShareContext, const int *attribList); +#endif +#endif /* WGL_ARB_create_context */ + +#ifndef WGL_ARB_create_context_no_error +#define WGL_ARB_create_context_no_error 1 +#define WGL_CONTEXT_OPENGL_NO_ERROR_ARB 0x31B3 +#endif /* WGL_ARB_create_context_no_error */ + +#ifndef WGL_ARB_create_context_profile +#define WGL_ARB_create_context_profile 1 +#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 +#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 +#define WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002 +#define ERROR_INVALID_PROFILE_ARB 0x2096 +#endif /* WGL_ARB_create_context_profile */ + +#ifndef WGL_ARB_create_context_robustness +#define WGL_ARB_create_context_robustness 1 +#define WGL_CONTEXT_ROBUST_ACCESS_BIT_ARB 0x00000004 +#define WGL_LOSE_CONTEXT_ON_RESET_ARB 0x8252 +#define WGL_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB 0x8256 +#define WGL_NO_RESET_NOTIFICATION_ARB 0x8261 +#endif /* WGL_ARB_create_context_robustness */ + +#ifndef WGL_ARB_extensions_string +#define WGL_ARB_extensions_string 1 +typedef const char *(WINAPI * PFNWGLGETEXTENSIONSSTRINGARBPROC) (HDC hdc); +#ifdef WGL_WGLEXT_PROTOTYPES +const char *WINAPI wglGetExtensionsStringARB (HDC hdc); +#endif +#endif /* WGL_ARB_extensions_string */ + +#ifndef WGL_ARB_framebuffer_sRGB +#define WGL_ARB_framebuffer_sRGB 1 +#define WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB 0x20A9 +#endif /* WGL_ARB_framebuffer_sRGB */ + +#ifndef WGL_ARB_make_current_read +#define WGL_ARB_make_current_read 1 +#define ERROR_INVALID_PIXEL_TYPE_ARB 0x2043 +#define ERROR_INCOMPATIBLE_DEVICE_CONTEXTS_ARB 0x2054 +typedef BOOL (WINAPI * PFNWGLMAKECONTEXTCURRENTARBPROC) (HDC hDrawDC, HDC hReadDC, HGLRC hglrc); +typedef HDC (WINAPI * PFNWGLGETCURRENTREADDCARBPROC) (void); +#ifdef WGL_WGLEXT_PROTOTYPES +BOOL WINAPI wglMakeContextCurrentARB (HDC hDrawDC, HDC hReadDC, HGLRC hglrc); +HDC WINAPI wglGetCurrentReadDCARB (void); +#endif +#endif /* WGL_ARB_make_current_read */ + +#ifndef WGL_ARB_multisample +#define WGL_ARB_multisample 1 +#define WGL_SAMPLE_BUFFERS_ARB 0x2041 +#define WGL_SAMPLES_ARB 0x2042 +#endif /* WGL_ARB_multisample */ + +#ifndef WGL_ARB_pbuffer +#define WGL_ARB_pbuffer 1 +DECLARE_HANDLE(HPBUFFERARB); +#define WGL_DRAW_TO_PBUFFER_ARB 0x202D +#define WGL_MAX_PBUFFER_PIXELS_ARB 0x202E +#define WGL_MAX_PBUFFER_WIDTH_ARB 0x202F +#define WGL_MAX_PBUFFER_HEIGHT_ARB 0x2030 +#define WGL_PBUFFER_LARGEST_ARB 0x2033 +#define WGL_PBUFFER_WIDTH_ARB 0x2034 +#define WGL_PBUFFER_HEIGHT_ARB 0x2035 +#define WGL_PBUFFER_LOST_ARB 0x2036 +typedef HPBUFFERARB (WINAPI * PFNWGLCREATEPBUFFERARBPROC) (HDC hDC, int iPixelFormat, int iWidth, int iHeight, const int *piAttribList); +typedef HDC (WINAPI * PFNWGLGETPBUFFERDCARBPROC) (HPBUFFERARB hPbuffer); +typedef int (WINAPI * PFNWGLRELEASEPBUFFERDCARBPROC) (HPBUFFERARB hPbuffer, HDC hDC); +typedef BOOL (WINAPI * PFNWGLDESTROYPBUFFERARBPROC) (HPBUFFERARB hPbuffer); +typedef BOOL (WINAPI * PFNWGLQUERYPBUFFERARBPROC) (HPBUFFERARB hPbuffer, int iAttribute, int *piValue); +#ifdef WGL_WGLEXT_PROTOTYPES +HPBUFFERARB WINAPI wglCreatePbufferARB (HDC hDC, int iPixelFormat, int iWidth, int iHeight, const int *piAttribList); +HDC WINAPI wglGetPbufferDCARB (HPBUFFERARB hPbuffer); +int WINAPI wglReleasePbufferDCARB (HPBUFFERARB hPbuffer, HDC hDC); +BOOL WINAPI wglDestroyPbufferARB (HPBUFFERARB hPbuffer); +BOOL WINAPI wglQueryPbufferARB (HPBUFFERARB hPbuffer, int iAttribute, int *piValue); +#endif +#endif /* WGL_ARB_pbuffer */ + +#ifndef WGL_ARB_pixel_format +#define WGL_ARB_pixel_format 1 +#define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000 +#define WGL_DRAW_TO_WINDOW_ARB 0x2001 +#define WGL_DRAW_TO_BITMAP_ARB 0x2002 +#define WGL_ACCELERATION_ARB 0x2003 +#define WGL_NEED_PALETTE_ARB 0x2004 +#define WGL_NEED_SYSTEM_PALETTE_ARB 0x2005 +#define WGL_SWAP_LAYER_BUFFERS_ARB 0x2006 +#define WGL_SWAP_METHOD_ARB 0x2007 +#define WGL_NUMBER_OVERLAYS_ARB 0x2008 +#define WGL_NUMBER_UNDERLAYS_ARB 0x2009 +#define WGL_TRANSPARENT_ARB 0x200A +#define WGL_TRANSPARENT_RED_VALUE_ARB 0x2037 +#define WGL_TRANSPARENT_GREEN_VALUE_ARB 0x2038 +#define WGL_TRANSPARENT_BLUE_VALUE_ARB 0x2039 +#define WGL_TRANSPARENT_ALPHA_VALUE_ARB 0x203A +#define WGL_TRANSPARENT_INDEX_VALUE_ARB 0x203B +#define WGL_SHARE_DEPTH_ARB 0x200C +#define WGL_SHARE_STENCIL_ARB 0x200D +#define WGL_SHARE_ACCUM_ARB 0x200E +#define WGL_SUPPORT_GDI_ARB 0x200F +#define WGL_SUPPORT_OPENGL_ARB 0x2010 +#define WGL_DOUBLE_BUFFER_ARB 0x2011 +#define WGL_STEREO_ARB 0x2012 +#define WGL_PIXEL_TYPE_ARB 0x2013 +#define WGL_COLOR_BITS_ARB 0x2014 +#define WGL_RED_BITS_ARB 0x2015 +#define WGL_RED_SHIFT_ARB 0x2016 +#define WGL_GREEN_BITS_ARB 0x2017 +#define WGL_GREEN_SHIFT_ARB 0x2018 +#define WGL_BLUE_BITS_ARB 0x2019 +#define WGL_BLUE_SHIFT_ARB 0x201A +#define WGL_ALPHA_BITS_ARB 0x201B +#define WGL_ALPHA_SHIFT_ARB 0x201C +#define WGL_ACCUM_BITS_ARB 0x201D +#define WGL_ACCUM_RED_BITS_ARB 0x201E +#define WGL_ACCUM_GREEN_BITS_ARB 0x201F +#define WGL_ACCUM_BLUE_BITS_ARB 0x2020 +#define WGL_ACCUM_ALPHA_BITS_ARB 0x2021 +#define WGL_DEPTH_BITS_ARB 0x2022 +#define WGL_STENCIL_BITS_ARB 0x2023 +#define WGL_AUX_BUFFERS_ARB 0x2024 +#define WGL_NO_ACCELERATION_ARB 0x2025 +#define WGL_GENERIC_ACCELERATION_ARB 0x2026 +#define WGL_FULL_ACCELERATION_ARB 0x2027 +#define WGL_SWAP_EXCHANGE_ARB 0x2028 +#define WGL_SWAP_COPY_ARB 0x2029 +#define WGL_SWAP_UNDEFINED_ARB 0x202A +#define WGL_TYPE_RGBA_ARB 0x202B +#define WGL_TYPE_COLORINDEX_ARB 0x202C +typedef BOOL (WINAPI * PFNWGLGETPIXELFORMATATTRIBIVARBPROC) (HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, const int *piAttributes, int *piValues); +typedef BOOL (WINAPI * PFNWGLGETPIXELFORMATATTRIBFVARBPROC) (HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, const int *piAttributes, FLOAT *pfValues); +typedef BOOL (WINAPI * PFNWGLCHOOSEPIXELFORMATARBPROC) (HDC hdc, const int *piAttribIList, const FLOAT *pfAttribFList, UINT nMaxFormats, int *piFormats, UINT *nNumFormats); +#ifdef WGL_WGLEXT_PROTOTYPES +BOOL WINAPI wglGetPixelFormatAttribivARB (HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, const int *piAttributes, int *piValues); +BOOL WINAPI wglGetPixelFormatAttribfvARB (HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, const int *piAttributes, FLOAT *pfValues); +BOOL WINAPI wglChoosePixelFormatARB (HDC hdc, const int *piAttribIList, const FLOAT *pfAttribFList, UINT nMaxFormats, int *piFormats, UINT *nNumFormats); +#endif +#endif /* WGL_ARB_pixel_format */ + +#ifndef WGL_ARB_pixel_format_float +#define WGL_ARB_pixel_format_float 1 +#define WGL_TYPE_RGBA_FLOAT_ARB 0x21A0 +#endif /* WGL_ARB_pixel_format_float */ + +#ifndef WGL_ARB_render_texture +#define WGL_ARB_render_texture 1 +#define WGL_BIND_TO_TEXTURE_RGB_ARB 0x2070 +#define WGL_BIND_TO_TEXTURE_RGBA_ARB 0x2071 +#define WGL_TEXTURE_FORMAT_ARB 0x2072 +#define WGL_TEXTURE_TARGET_ARB 0x2073 +#define WGL_MIPMAP_TEXTURE_ARB 0x2074 +#define WGL_TEXTURE_RGB_ARB 0x2075 +#define WGL_TEXTURE_RGBA_ARB 0x2076 +#define WGL_NO_TEXTURE_ARB 0x2077 +#define WGL_TEXTURE_CUBE_MAP_ARB 0x2078 +#define WGL_TEXTURE_1D_ARB 0x2079 +#define WGL_TEXTURE_2D_ARB 0x207A +#define WGL_MIPMAP_LEVEL_ARB 0x207B +#define WGL_CUBE_MAP_FACE_ARB 0x207C +#define WGL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB 0x207D +#define WGL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB 0x207E +#define WGL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB 0x207F +#define WGL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB 0x2080 +#define WGL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB 0x2081 +#define WGL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB 0x2082 +#define WGL_FRONT_LEFT_ARB 0x2083 +#define WGL_FRONT_RIGHT_ARB 0x2084 +#define WGL_BACK_LEFT_ARB 0x2085 +#define WGL_BACK_RIGHT_ARB 0x2086 +#define WGL_AUX0_ARB 0x2087 +#define WGL_AUX1_ARB 0x2088 +#define WGL_AUX2_ARB 0x2089 +#define WGL_AUX3_ARB 0x208A +#define WGL_AUX4_ARB 0x208B +#define WGL_AUX5_ARB 0x208C +#define WGL_AUX6_ARB 0x208D +#define WGL_AUX7_ARB 0x208E +#define WGL_AUX8_ARB 0x208F +#define WGL_AUX9_ARB 0x2090 +typedef BOOL (WINAPI * PFNWGLBINDTEXIMAGEARBPROC) (HPBUFFERARB hPbuffer, int iBuffer); +typedef BOOL (WINAPI * PFNWGLRELEASETEXIMAGEARBPROC) (HPBUFFERARB hPbuffer, int iBuffer); +typedef BOOL (WINAPI * PFNWGLSETPBUFFERATTRIBARBPROC) (HPBUFFERARB hPbuffer, const int *piAttribList); +#ifdef WGL_WGLEXT_PROTOTYPES +BOOL WINAPI wglBindTexImageARB (HPBUFFERARB hPbuffer, int iBuffer); +BOOL WINAPI wglReleaseTexImageARB (HPBUFFERARB hPbuffer, int iBuffer); +BOOL WINAPI wglSetPbufferAttribARB (HPBUFFERARB hPbuffer, const int *piAttribList); +#endif +#endif /* WGL_ARB_render_texture */ + +#ifndef WGL_ARB_robustness_application_isolation +#define WGL_ARB_robustness_application_isolation 1 +#define WGL_CONTEXT_RESET_ISOLATION_BIT_ARB 0x00000008 +#endif /* WGL_ARB_robustness_application_isolation */ + +#ifndef WGL_ARB_robustness_share_group_isolation +#define WGL_ARB_robustness_share_group_isolation 1 +#endif /* WGL_ARB_robustness_share_group_isolation */ + +#ifndef WGL_3DFX_multisample +#define WGL_3DFX_multisample 1 +#define WGL_SAMPLE_BUFFERS_3DFX 0x2060 +#define WGL_SAMPLES_3DFX 0x2061 +#endif /* WGL_3DFX_multisample */ + +#ifndef WGL_3DL_stereo_control +#define WGL_3DL_stereo_control 1 +#define WGL_STEREO_EMITTER_ENABLE_3DL 0x2055 +#define WGL_STEREO_EMITTER_DISABLE_3DL 0x2056 +#define WGL_STEREO_POLARITY_NORMAL_3DL 0x2057 +#define WGL_STEREO_POLARITY_INVERT_3DL 0x2058 +typedef BOOL (WINAPI * PFNWGLSETSTEREOEMITTERSTATE3DLPROC) (HDC hDC, UINT uState); +#ifdef WGL_WGLEXT_PROTOTYPES +BOOL WINAPI wglSetStereoEmitterState3DL (HDC hDC, UINT uState); +#endif +#endif /* WGL_3DL_stereo_control */ + +#ifndef WGL_AMD_gpu_association +#define WGL_AMD_gpu_association 1 +#define WGL_GPU_VENDOR_AMD 0x1F00 +#define WGL_GPU_RENDERER_STRING_AMD 0x1F01 +#define WGL_GPU_OPENGL_VERSION_STRING_AMD 0x1F02 +#define WGL_GPU_FASTEST_TARGET_GPUS_AMD 0x21A2 +#define WGL_GPU_RAM_AMD 0x21A3 +#define WGL_GPU_CLOCK_AMD 0x21A4 +#define WGL_GPU_NUM_PIPES_AMD 0x21A5 +#define WGL_GPU_NUM_SIMD_AMD 0x21A6 +#define WGL_GPU_NUM_RB_AMD 0x21A7 +#define WGL_GPU_NUM_SPI_AMD 0x21A8 +typedef UINT (WINAPI * PFNWGLGETGPUIDSAMDPROC) (UINT maxCount, UINT *ids); +typedef INT (WINAPI * PFNWGLGETGPUINFOAMDPROC) (UINT id, INT property, GLenum dataType, UINT size, void *data); +typedef UINT (WINAPI * PFNWGLGETCONTEXTGPUIDAMDPROC) (HGLRC hglrc); +typedef HGLRC (WINAPI * PFNWGLCREATEASSOCIATEDCONTEXTAMDPROC) (UINT id); +typedef HGLRC (WINAPI * PFNWGLCREATEASSOCIATEDCONTEXTATTRIBSAMDPROC) (UINT id, HGLRC hShareContext, const int *attribList); +typedef BOOL (WINAPI * PFNWGLDELETEASSOCIATEDCONTEXTAMDPROC) (HGLRC hglrc); +typedef BOOL (WINAPI * PFNWGLMAKEASSOCIATEDCONTEXTCURRENTAMDPROC) (HGLRC hglrc); +typedef HGLRC (WINAPI * PFNWGLGETCURRENTASSOCIATEDCONTEXTAMDPROC) (void); +typedef VOID (WINAPI * PFNWGLBLITCONTEXTFRAMEBUFFERAMDPROC) (HGLRC dstCtx, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +#ifdef WGL_WGLEXT_PROTOTYPES +UINT WINAPI wglGetGPUIDsAMD (UINT maxCount, UINT *ids); +INT WINAPI wglGetGPUInfoAMD (UINT id, INT property, GLenum dataType, UINT size, void *data); +UINT WINAPI wglGetContextGPUIDAMD (HGLRC hglrc); +HGLRC WINAPI wglCreateAssociatedContextAMD (UINT id); +HGLRC WINAPI wglCreateAssociatedContextAttribsAMD (UINT id, HGLRC hShareContext, const int *attribList); +BOOL WINAPI wglDeleteAssociatedContextAMD (HGLRC hglrc); +BOOL WINAPI wglMakeAssociatedContextCurrentAMD (HGLRC hglrc); +HGLRC WINAPI wglGetCurrentAssociatedContextAMD (void); +VOID WINAPI wglBlitContextFramebufferAMD (HGLRC dstCtx, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +#endif +#endif /* WGL_AMD_gpu_association */ + +#ifndef WGL_ATI_pixel_format_float +#define WGL_ATI_pixel_format_float 1 +#define WGL_TYPE_RGBA_FLOAT_ATI 0x21A0 +#endif /* WGL_ATI_pixel_format_float */ + +#ifndef WGL_ATI_render_texture_rectangle +#define WGL_ATI_render_texture_rectangle 1 +#define WGL_TEXTURE_RECTANGLE_ATI 0x21A5 +#endif /* WGL_ATI_render_texture_rectangle */ + +#ifndef WGL_EXT_colorspace +#define WGL_EXT_colorspace 1 +#define WGL_COLORSPACE_EXT 0x309D +#define WGL_COLORSPACE_SRGB_EXT 0x3089 +#define WGL_COLORSPACE_LINEAR_EXT 0x308A +#endif /* WGL_EXT_colorspace */ + +#ifndef WGL_EXT_create_context_es2_profile +#define WGL_EXT_create_context_es2_profile 1 +#define WGL_CONTEXT_ES2_PROFILE_BIT_EXT 0x00000004 +#endif /* WGL_EXT_create_context_es2_profile */ + +#ifndef WGL_EXT_create_context_es_profile +#define WGL_EXT_create_context_es_profile 1 +#define WGL_CONTEXT_ES_PROFILE_BIT_EXT 0x00000004 +#endif /* WGL_EXT_create_context_es_profile */ + +#ifndef WGL_EXT_depth_float +#define WGL_EXT_depth_float 1 +#define WGL_DEPTH_FLOAT_EXT 0x2040 +#endif /* WGL_EXT_depth_float */ + +#ifndef WGL_EXT_display_color_table +#define WGL_EXT_display_color_table 1 +typedef GLboolean (WINAPI * PFNWGLCREATEDISPLAYCOLORTABLEEXTPROC) (GLushort id); +typedef GLboolean (WINAPI * PFNWGLLOADDISPLAYCOLORTABLEEXTPROC) (const GLushort *table, GLuint length); +typedef GLboolean (WINAPI * PFNWGLBINDDISPLAYCOLORTABLEEXTPROC) (GLushort id); +typedef VOID (WINAPI * PFNWGLDESTROYDISPLAYCOLORTABLEEXTPROC) (GLushort id); +#ifdef WGL_WGLEXT_PROTOTYPES +GLboolean WINAPI wglCreateDisplayColorTableEXT (GLushort id); +GLboolean WINAPI wglLoadDisplayColorTableEXT (const GLushort *table, GLuint length); +GLboolean WINAPI wglBindDisplayColorTableEXT (GLushort id); +VOID WINAPI wglDestroyDisplayColorTableEXT (GLushort id); +#endif +#endif /* WGL_EXT_display_color_table */ + +#ifndef WGL_EXT_extensions_string +#define WGL_EXT_extensions_string 1 +typedef const char *(WINAPI * PFNWGLGETEXTENSIONSSTRINGEXTPROC) (void); +#ifdef WGL_WGLEXT_PROTOTYPES +const char *WINAPI wglGetExtensionsStringEXT (void); +#endif +#endif /* WGL_EXT_extensions_string */ + +#ifndef WGL_EXT_framebuffer_sRGB +#define WGL_EXT_framebuffer_sRGB 1 +#define WGL_FRAMEBUFFER_SRGB_CAPABLE_EXT 0x20A9 +#endif /* WGL_EXT_framebuffer_sRGB */ + +#ifndef WGL_EXT_make_current_read +#define WGL_EXT_make_current_read 1 +#define ERROR_INVALID_PIXEL_TYPE_EXT 0x2043 +typedef BOOL (WINAPI * PFNWGLMAKECONTEXTCURRENTEXTPROC) (HDC hDrawDC, HDC hReadDC, HGLRC hglrc); +typedef HDC (WINAPI * PFNWGLGETCURRENTREADDCEXTPROC) (void); +#ifdef WGL_WGLEXT_PROTOTYPES +BOOL WINAPI wglMakeContextCurrentEXT (HDC hDrawDC, HDC hReadDC, HGLRC hglrc); +HDC WINAPI wglGetCurrentReadDCEXT (void); +#endif +#endif /* WGL_EXT_make_current_read */ + +#ifndef WGL_EXT_multisample +#define WGL_EXT_multisample 1 +#define WGL_SAMPLE_BUFFERS_EXT 0x2041 +#define WGL_SAMPLES_EXT 0x2042 +#endif /* WGL_EXT_multisample */ + +#ifndef WGL_EXT_pbuffer +#define WGL_EXT_pbuffer 1 +DECLARE_HANDLE(HPBUFFEREXT); +#define WGL_DRAW_TO_PBUFFER_EXT 0x202D +#define WGL_MAX_PBUFFER_PIXELS_EXT 0x202E +#define WGL_MAX_PBUFFER_WIDTH_EXT 0x202F +#define WGL_MAX_PBUFFER_HEIGHT_EXT 0x2030 +#define WGL_OPTIMAL_PBUFFER_WIDTH_EXT 0x2031 +#define WGL_OPTIMAL_PBUFFER_HEIGHT_EXT 0x2032 +#define WGL_PBUFFER_LARGEST_EXT 0x2033 +#define WGL_PBUFFER_WIDTH_EXT 0x2034 +#define WGL_PBUFFER_HEIGHT_EXT 0x2035 +typedef HPBUFFEREXT (WINAPI * PFNWGLCREATEPBUFFEREXTPROC) (HDC hDC, int iPixelFormat, int iWidth, int iHeight, const int *piAttribList); +typedef HDC (WINAPI * PFNWGLGETPBUFFERDCEXTPROC) (HPBUFFEREXT hPbuffer); +typedef int (WINAPI * PFNWGLRELEASEPBUFFERDCEXTPROC) (HPBUFFEREXT hPbuffer, HDC hDC); +typedef BOOL (WINAPI * PFNWGLDESTROYPBUFFEREXTPROC) (HPBUFFEREXT hPbuffer); +typedef BOOL (WINAPI * PFNWGLQUERYPBUFFEREXTPROC) (HPBUFFEREXT hPbuffer, int iAttribute, int *piValue); +#ifdef WGL_WGLEXT_PROTOTYPES +HPBUFFEREXT WINAPI wglCreatePbufferEXT (HDC hDC, int iPixelFormat, int iWidth, int iHeight, const int *piAttribList); +HDC WINAPI wglGetPbufferDCEXT (HPBUFFEREXT hPbuffer); +int WINAPI wglReleasePbufferDCEXT (HPBUFFEREXT hPbuffer, HDC hDC); +BOOL WINAPI wglDestroyPbufferEXT (HPBUFFEREXT hPbuffer); +BOOL WINAPI wglQueryPbufferEXT (HPBUFFEREXT hPbuffer, int iAttribute, int *piValue); +#endif +#endif /* WGL_EXT_pbuffer */ + +#ifndef WGL_EXT_pixel_format +#define WGL_EXT_pixel_format 1 +#define WGL_NUMBER_PIXEL_FORMATS_EXT 0x2000 +#define WGL_DRAW_TO_WINDOW_EXT 0x2001 +#define WGL_DRAW_TO_BITMAP_EXT 0x2002 +#define WGL_ACCELERATION_EXT 0x2003 +#define WGL_NEED_PALETTE_EXT 0x2004 +#define WGL_NEED_SYSTEM_PALETTE_EXT 0x2005 +#define WGL_SWAP_LAYER_BUFFERS_EXT 0x2006 +#define WGL_SWAP_METHOD_EXT 0x2007 +#define WGL_NUMBER_OVERLAYS_EXT 0x2008 +#define WGL_NUMBER_UNDERLAYS_EXT 0x2009 +#define WGL_TRANSPARENT_EXT 0x200A +#define WGL_TRANSPARENT_VALUE_EXT 0x200B +#define WGL_SHARE_DEPTH_EXT 0x200C +#define WGL_SHARE_STENCIL_EXT 0x200D +#define WGL_SHARE_ACCUM_EXT 0x200E +#define WGL_SUPPORT_GDI_EXT 0x200F +#define WGL_SUPPORT_OPENGL_EXT 0x2010 +#define WGL_DOUBLE_BUFFER_EXT 0x2011 +#define WGL_STEREO_EXT 0x2012 +#define WGL_PIXEL_TYPE_EXT 0x2013 +#define WGL_COLOR_BITS_EXT 0x2014 +#define WGL_RED_BITS_EXT 0x2015 +#define WGL_RED_SHIFT_EXT 0x2016 +#define WGL_GREEN_BITS_EXT 0x2017 +#define WGL_GREEN_SHIFT_EXT 0x2018 +#define WGL_BLUE_BITS_EXT 0x2019 +#define WGL_BLUE_SHIFT_EXT 0x201A +#define WGL_ALPHA_BITS_EXT 0x201B +#define WGL_ALPHA_SHIFT_EXT 0x201C +#define WGL_ACCUM_BITS_EXT 0x201D +#define WGL_ACCUM_RED_BITS_EXT 0x201E +#define WGL_ACCUM_GREEN_BITS_EXT 0x201F +#define WGL_ACCUM_BLUE_BITS_EXT 0x2020 +#define WGL_ACCUM_ALPHA_BITS_EXT 0x2021 +#define WGL_DEPTH_BITS_EXT 0x2022 +#define WGL_STENCIL_BITS_EXT 0x2023 +#define WGL_AUX_BUFFERS_EXT 0x2024 +#define WGL_NO_ACCELERATION_EXT 0x2025 +#define WGL_GENERIC_ACCELERATION_EXT 0x2026 +#define WGL_FULL_ACCELERATION_EXT 0x2027 +#define WGL_SWAP_EXCHANGE_EXT 0x2028 +#define WGL_SWAP_COPY_EXT 0x2029 +#define WGL_SWAP_UNDEFINED_EXT 0x202A +#define WGL_TYPE_RGBA_EXT 0x202B +#define WGL_TYPE_COLORINDEX_EXT 0x202C +typedef BOOL (WINAPI * PFNWGLGETPIXELFORMATATTRIBIVEXTPROC) (HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, int *piAttributes, int *piValues); +typedef BOOL (WINAPI * PFNWGLGETPIXELFORMATATTRIBFVEXTPROC) (HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, int *piAttributes, FLOAT *pfValues); +typedef BOOL (WINAPI * PFNWGLCHOOSEPIXELFORMATEXTPROC) (HDC hdc, const int *piAttribIList, const FLOAT *pfAttribFList, UINT nMaxFormats, int *piFormats, UINT *nNumFormats); +#ifdef WGL_WGLEXT_PROTOTYPES +BOOL WINAPI wglGetPixelFormatAttribivEXT (HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, int *piAttributes, int *piValues); +BOOL WINAPI wglGetPixelFormatAttribfvEXT (HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, int *piAttributes, FLOAT *pfValues); +BOOL WINAPI wglChoosePixelFormatEXT (HDC hdc, const int *piAttribIList, const FLOAT *pfAttribFList, UINT nMaxFormats, int *piFormats, UINT *nNumFormats); +#endif +#endif /* WGL_EXT_pixel_format */ + +#ifndef WGL_EXT_pixel_format_packed_float +#define WGL_EXT_pixel_format_packed_float 1 +#define WGL_TYPE_RGBA_UNSIGNED_FLOAT_EXT 0x20A8 +#endif /* WGL_EXT_pixel_format_packed_float */ + +#ifndef WGL_EXT_swap_control +#define WGL_EXT_swap_control 1 +typedef BOOL (WINAPI * PFNWGLSWAPINTERVALEXTPROC) (int interval); +typedef int (WINAPI * PFNWGLGETSWAPINTERVALEXTPROC) (void); +#ifdef WGL_WGLEXT_PROTOTYPES +BOOL WINAPI wglSwapIntervalEXT (int interval); +int WINAPI wglGetSwapIntervalEXT (void); +#endif +#endif /* WGL_EXT_swap_control */ + +#ifndef WGL_EXT_swap_control_tear +#define WGL_EXT_swap_control_tear 1 +#endif /* WGL_EXT_swap_control_tear */ + +#ifndef WGL_I3D_digital_video_control +#define WGL_I3D_digital_video_control 1 +#define WGL_DIGITAL_VIDEO_CURSOR_ALPHA_FRAMEBUFFER_I3D 0x2050 +#define WGL_DIGITAL_VIDEO_CURSOR_ALPHA_VALUE_I3D 0x2051 +#define WGL_DIGITAL_VIDEO_CURSOR_INCLUDED_I3D 0x2052 +#define WGL_DIGITAL_VIDEO_GAMMA_CORRECTED_I3D 0x2053 +typedef BOOL (WINAPI * PFNWGLGETDIGITALVIDEOPARAMETERSI3DPROC) (HDC hDC, int iAttribute, int *piValue); +typedef BOOL (WINAPI * PFNWGLSETDIGITALVIDEOPARAMETERSI3DPROC) (HDC hDC, int iAttribute, const int *piValue); +#ifdef WGL_WGLEXT_PROTOTYPES +BOOL WINAPI wglGetDigitalVideoParametersI3D (HDC hDC, int iAttribute, int *piValue); +BOOL WINAPI wglSetDigitalVideoParametersI3D (HDC hDC, int iAttribute, const int *piValue); +#endif +#endif /* WGL_I3D_digital_video_control */ + +#ifndef WGL_I3D_gamma +#define WGL_I3D_gamma 1 +#define WGL_GAMMA_TABLE_SIZE_I3D 0x204E +#define WGL_GAMMA_EXCLUDE_DESKTOP_I3D 0x204F +typedef BOOL (WINAPI * PFNWGLGETGAMMATABLEPARAMETERSI3DPROC) (HDC hDC, int iAttribute, int *piValue); +typedef BOOL (WINAPI * PFNWGLSETGAMMATABLEPARAMETERSI3DPROC) (HDC hDC, int iAttribute, const int *piValue); +typedef BOOL (WINAPI * PFNWGLGETGAMMATABLEI3DPROC) (HDC hDC, int iEntries, USHORT *puRed, USHORT *puGreen, USHORT *puBlue); +typedef BOOL (WINAPI * PFNWGLSETGAMMATABLEI3DPROC) (HDC hDC, int iEntries, const USHORT *puRed, const USHORT *puGreen, const USHORT *puBlue); +#ifdef WGL_WGLEXT_PROTOTYPES +BOOL WINAPI wglGetGammaTableParametersI3D (HDC hDC, int iAttribute, int *piValue); +BOOL WINAPI wglSetGammaTableParametersI3D (HDC hDC, int iAttribute, const int *piValue); +BOOL WINAPI wglGetGammaTableI3D (HDC hDC, int iEntries, USHORT *puRed, USHORT *puGreen, USHORT *puBlue); +BOOL WINAPI wglSetGammaTableI3D (HDC hDC, int iEntries, const USHORT *puRed, const USHORT *puGreen, const USHORT *puBlue); +#endif +#endif /* WGL_I3D_gamma */ + +#ifndef WGL_I3D_genlock +#define WGL_I3D_genlock 1 +#define WGL_GENLOCK_SOURCE_MULTIVIEW_I3D 0x2044 +#define WGL_GENLOCK_SOURCE_EXTERNAL_SYNC_I3D 0x2045 +#define WGL_GENLOCK_SOURCE_EXTERNAL_FIELD_I3D 0x2046 +#define WGL_GENLOCK_SOURCE_EXTERNAL_TTL_I3D 0x2047 +#define WGL_GENLOCK_SOURCE_DIGITAL_SYNC_I3D 0x2048 +#define WGL_GENLOCK_SOURCE_DIGITAL_FIELD_I3D 0x2049 +#define WGL_GENLOCK_SOURCE_EDGE_FALLING_I3D 0x204A +#define WGL_GENLOCK_SOURCE_EDGE_RISING_I3D 0x204B +#define WGL_GENLOCK_SOURCE_EDGE_BOTH_I3D 0x204C +typedef BOOL (WINAPI * PFNWGLENABLEGENLOCKI3DPROC) (HDC hDC); +typedef BOOL (WINAPI * PFNWGLDISABLEGENLOCKI3DPROC) (HDC hDC); +typedef BOOL (WINAPI * PFNWGLISENABLEDGENLOCKI3DPROC) (HDC hDC, BOOL *pFlag); +typedef BOOL (WINAPI * PFNWGLGENLOCKSOURCEI3DPROC) (HDC hDC, UINT uSource); +typedef BOOL (WINAPI * PFNWGLGETGENLOCKSOURCEI3DPROC) (HDC hDC, UINT *uSource); +typedef BOOL (WINAPI * PFNWGLGENLOCKSOURCEEDGEI3DPROC) (HDC hDC, UINT uEdge); +typedef BOOL (WINAPI * PFNWGLGETGENLOCKSOURCEEDGEI3DPROC) (HDC hDC, UINT *uEdge); +typedef BOOL (WINAPI * PFNWGLGENLOCKSAMPLERATEI3DPROC) (HDC hDC, UINT uRate); +typedef BOOL (WINAPI * PFNWGLGETGENLOCKSAMPLERATEI3DPROC) (HDC hDC, UINT *uRate); +typedef BOOL (WINAPI * PFNWGLGENLOCKSOURCEDELAYI3DPROC) (HDC hDC, UINT uDelay); +typedef BOOL (WINAPI * PFNWGLGETGENLOCKSOURCEDELAYI3DPROC) (HDC hDC, UINT *uDelay); +typedef BOOL (WINAPI * PFNWGLQUERYGENLOCKMAXSOURCEDELAYI3DPROC) (HDC hDC, UINT *uMaxLineDelay, UINT *uMaxPixelDelay); +#ifdef WGL_WGLEXT_PROTOTYPES +BOOL WINAPI wglEnableGenlockI3D (HDC hDC); +BOOL WINAPI wglDisableGenlockI3D (HDC hDC); +BOOL WINAPI wglIsEnabledGenlockI3D (HDC hDC, BOOL *pFlag); +BOOL WINAPI wglGenlockSourceI3D (HDC hDC, UINT uSource); +BOOL WINAPI wglGetGenlockSourceI3D (HDC hDC, UINT *uSource); +BOOL WINAPI wglGenlockSourceEdgeI3D (HDC hDC, UINT uEdge); +BOOL WINAPI wglGetGenlockSourceEdgeI3D (HDC hDC, UINT *uEdge); +BOOL WINAPI wglGenlockSampleRateI3D (HDC hDC, UINT uRate); +BOOL WINAPI wglGetGenlockSampleRateI3D (HDC hDC, UINT *uRate); +BOOL WINAPI wglGenlockSourceDelayI3D (HDC hDC, UINT uDelay); +BOOL WINAPI wglGetGenlockSourceDelayI3D (HDC hDC, UINT *uDelay); +BOOL WINAPI wglQueryGenlockMaxSourceDelayI3D (HDC hDC, UINT *uMaxLineDelay, UINT *uMaxPixelDelay); +#endif +#endif /* WGL_I3D_genlock */ + +#ifndef WGL_I3D_image_buffer +#define WGL_I3D_image_buffer 1 +#define WGL_IMAGE_BUFFER_MIN_ACCESS_I3D 0x00000001 +#define WGL_IMAGE_BUFFER_LOCK_I3D 0x00000002 +typedef LPVOID (WINAPI * PFNWGLCREATEIMAGEBUFFERI3DPROC) (HDC hDC, DWORD dwSize, UINT uFlags); +typedef BOOL (WINAPI * PFNWGLDESTROYIMAGEBUFFERI3DPROC) (HDC hDC, LPVOID pAddress); +typedef BOOL (WINAPI * PFNWGLASSOCIATEIMAGEBUFFEREVENTSI3DPROC) (HDC hDC, const HANDLE *pEvent, const LPVOID *pAddress, const DWORD *pSize, UINT count); +typedef BOOL (WINAPI * PFNWGLRELEASEIMAGEBUFFEREVENTSI3DPROC) (HDC hDC, const LPVOID *pAddress, UINT count); +#ifdef WGL_WGLEXT_PROTOTYPES +LPVOID WINAPI wglCreateImageBufferI3D (HDC hDC, DWORD dwSize, UINT uFlags); +BOOL WINAPI wglDestroyImageBufferI3D (HDC hDC, LPVOID pAddress); +BOOL WINAPI wglAssociateImageBufferEventsI3D (HDC hDC, const HANDLE *pEvent, const LPVOID *pAddress, const DWORD *pSize, UINT count); +BOOL WINAPI wglReleaseImageBufferEventsI3D (HDC hDC, const LPVOID *pAddress, UINT count); +#endif +#endif /* WGL_I3D_image_buffer */ + +#ifndef WGL_I3D_swap_frame_lock +#define WGL_I3D_swap_frame_lock 1 +typedef BOOL (WINAPI * PFNWGLENABLEFRAMELOCKI3DPROC) (void); +typedef BOOL (WINAPI * PFNWGLDISABLEFRAMELOCKI3DPROC) (void); +typedef BOOL (WINAPI * PFNWGLISENABLEDFRAMELOCKI3DPROC) (BOOL *pFlag); +typedef BOOL (WINAPI * PFNWGLQUERYFRAMELOCKMASTERI3DPROC) (BOOL *pFlag); +#ifdef WGL_WGLEXT_PROTOTYPES +BOOL WINAPI wglEnableFrameLockI3D (void); +BOOL WINAPI wglDisableFrameLockI3D (void); +BOOL WINAPI wglIsEnabledFrameLockI3D (BOOL *pFlag); +BOOL WINAPI wglQueryFrameLockMasterI3D (BOOL *pFlag); +#endif +#endif /* WGL_I3D_swap_frame_lock */ + +#ifndef WGL_I3D_swap_frame_usage +#define WGL_I3D_swap_frame_usage 1 +typedef BOOL (WINAPI * PFNWGLGETFRAMEUSAGEI3DPROC) (float *pUsage); +typedef BOOL (WINAPI * PFNWGLBEGINFRAMETRACKINGI3DPROC) (void); +typedef BOOL (WINAPI * PFNWGLENDFRAMETRACKINGI3DPROC) (void); +typedef BOOL (WINAPI * PFNWGLQUERYFRAMETRACKINGI3DPROC) (DWORD *pFrameCount, DWORD *pMissedFrames, float *pLastMissedUsage); +#ifdef WGL_WGLEXT_PROTOTYPES +BOOL WINAPI wglGetFrameUsageI3D (float *pUsage); +BOOL WINAPI wglBeginFrameTrackingI3D (void); +BOOL WINAPI wglEndFrameTrackingI3D (void); +BOOL WINAPI wglQueryFrameTrackingI3D (DWORD *pFrameCount, DWORD *pMissedFrames, float *pLastMissedUsage); +#endif +#endif /* WGL_I3D_swap_frame_usage */ + +#ifndef WGL_NV_DX_interop +#define WGL_NV_DX_interop 1 +#define WGL_ACCESS_READ_ONLY_NV 0x00000000 +#define WGL_ACCESS_READ_WRITE_NV 0x00000001 +#define WGL_ACCESS_WRITE_DISCARD_NV 0x00000002 +typedef BOOL (WINAPI * PFNWGLDXSETRESOURCESHAREHANDLENVPROC) (void *dxObject, HANDLE shareHandle); +typedef HANDLE (WINAPI * PFNWGLDXOPENDEVICENVPROC) (void *dxDevice); +typedef BOOL (WINAPI * PFNWGLDXCLOSEDEVICENVPROC) (HANDLE hDevice); +typedef HANDLE (WINAPI * PFNWGLDXREGISTEROBJECTNVPROC) (HANDLE hDevice, void *dxObject, GLuint name, GLenum type, GLenum access); +typedef BOOL (WINAPI * PFNWGLDXUNREGISTEROBJECTNVPROC) (HANDLE hDevice, HANDLE hObject); +typedef BOOL (WINAPI * PFNWGLDXOBJECTACCESSNVPROC) (HANDLE hObject, GLenum access); +typedef BOOL (WINAPI * PFNWGLDXLOCKOBJECTSNVPROC) (HANDLE hDevice, GLint count, HANDLE *hObjects); +typedef BOOL (WINAPI * PFNWGLDXUNLOCKOBJECTSNVPROC) (HANDLE hDevice, GLint count, HANDLE *hObjects); +#ifdef WGL_WGLEXT_PROTOTYPES +BOOL WINAPI wglDXSetResourceShareHandleNV (void *dxObject, HANDLE shareHandle); +HANDLE WINAPI wglDXOpenDeviceNV (void *dxDevice); +BOOL WINAPI wglDXCloseDeviceNV (HANDLE hDevice); +HANDLE WINAPI wglDXRegisterObjectNV (HANDLE hDevice, void *dxObject, GLuint name, GLenum type, GLenum access); +BOOL WINAPI wglDXUnregisterObjectNV (HANDLE hDevice, HANDLE hObject); +BOOL WINAPI wglDXObjectAccessNV (HANDLE hObject, GLenum access); +BOOL WINAPI wglDXLockObjectsNV (HANDLE hDevice, GLint count, HANDLE *hObjects); +BOOL WINAPI wglDXUnlockObjectsNV (HANDLE hDevice, GLint count, HANDLE *hObjects); +#endif +#endif /* WGL_NV_DX_interop */ + +#ifndef WGL_NV_DX_interop2 +#define WGL_NV_DX_interop2 1 +#endif /* WGL_NV_DX_interop2 */ + +#ifndef WGL_NV_copy_image +#define WGL_NV_copy_image 1 +typedef BOOL (WINAPI * PFNWGLCOPYIMAGESUBDATANVPROC) (HGLRC hSrcRC, GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, HGLRC hDstRC, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei width, GLsizei height, GLsizei depth); +#ifdef WGL_WGLEXT_PROTOTYPES +BOOL WINAPI wglCopyImageSubDataNV (HGLRC hSrcRC, GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, HGLRC hDstRC, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei width, GLsizei height, GLsizei depth); +#endif +#endif /* WGL_NV_copy_image */ + +#ifndef WGL_NV_delay_before_swap +#define WGL_NV_delay_before_swap 1 +typedef BOOL (WINAPI * PFNWGLDELAYBEFORESWAPNVPROC) (HDC hDC, GLfloat seconds); +#ifdef WGL_WGLEXT_PROTOTYPES +BOOL WINAPI wglDelayBeforeSwapNV (HDC hDC, GLfloat seconds); +#endif +#endif /* WGL_NV_delay_before_swap */ + +#ifndef WGL_NV_float_buffer +#define WGL_NV_float_buffer 1 +#define WGL_FLOAT_COMPONENTS_NV 0x20B0 +#define WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_R_NV 0x20B1 +#define WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_RG_NV 0x20B2 +#define WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_RGB_NV 0x20B3 +#define WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_RGBA_NV 0x20B4 +#define WGL_TEXTURE_FLOAT_R_NV 0x20B5 +#define WGL_TEXTURE_FLOAT_RG_NV 0x20B6 +#define WGL_TEXTURE_FLOAT_RGB_NV 0x20B7 +#define WGL_TEXTURE_FLOAT_RGBA_NV 0x20B8 +#endif /* WGL_NV_float_buffer */ + +#ifndef WGL_NV_gpu_affinity +#define WGL_NV_gpu_affinity 1 +DECLARE_HANDLE(HGPUNV); +struct _GPU_DEVICE { + DWORD cb; + CHAR DeviceName[32]; + CHAR DeviceString[128]; + DWORD Flags; + RECT rcVirtualScreen; +}; +typedef struct _GPU_DEVICE *PGPU_DEVICE; +#define ERROR_INCOMPATIBLE_AFFINITY_MASKS_NV 0x20D0 +#define ERROR_MISSING_AFFINITY_MASK_NV 0x20D1 +typedef BOOL (WINAPI * PFNWGLENUMGPUSNVPROC) (UINT iGpuIndex, HGPUNV *phGpu); +typedef BOOL (WINAPI * PFNWGLENUMGPUDEVICESNVPROC) (HGPUNV hGpu, UINT iDeviceIndex, PGPU_DEVICE lpGpuDevice); +typedef HDC (WINAPI * PFNWGLCREATEAFFINITYDCNVPROC) (const HGPUNV *phGpuList); +typedef BOOL (WINAPI * PFNWGLENUMGPUSFROMAFFINITYDCNVPROC) (HDC hAffinityDC, UINT iGpuIndex, HGPUNV *hGpu); +typedef BOOL (WINAPI * PFNWGLDELETEDCNVPROC) (HDC hdc); +#ifdef WGL_WGLEXT_PROTOTYPES +BOOL WINAPI wglEnumGpusNV (UINT iGpuIndex, HGPUNV *phGpu); +BOOL WINAPI wglEnumGpuDevicesNV (HGPUNV hGpu, UINT iDeviceIndex, PGPU_DEVICE lpGpuDevice); +HDC WINAPI wglCreateAffinityDCNV (const HGPUNV *phGpuList); +BOOL WINAPI wglEnumGpusFromAffinityDCNV (HDC hAffinityDC, UINT iGpuIndex, HGPUNV *hGpu); +BOOL WINAPI wglDeleteDCNV (HDC hdc); +#endif +#endif /* WGL_NV_gpu_affinity */ + +#ifndef WGL_NV_multigpu_context +#define WGL_NV_multigpu_context 1 +#define WGL_CONTEXT_MULTIGPU_ATTRIB_NV 0x20AA +#define WGL_CONTEXT_MULTIGPU_ATTRIB_SINGLE_NV 0x20AB +#define WGL_CONTEXT_MULTIGPU_ATTRIB_AFR_NV 0x20AC +#define WGL_CONTEXT_MULTIGPU_ATTRIB_MULTICAST_NV 0x20AD +#define WGL_CONTEXT_MULTIGPU_ATTRIB_MULTI_DISPLAY_MULTICAST_NV 0x20AE +#endif /* WGL_NV_multigpu_context */ + +#ifndef WGL_NV_multisample_coverage +#define WGL_NV_multisample_coverage 1 +#define WGL_COVERAGE_SAMPLES_NV 0x2042 +#define WGL_COLOR_SAMPLES_NV 0x20B9 +#endif /* WGL_NV_multisample_coverage */ + +#ifndef WGL_NV_present_video +#define WGL_NV_present_video 1 +DECLARE_HANDLE(HVIDEOOUTPUTDEVICENV); +#define WGL_NUM_VIDEO_SLOTS_NV 0x20F0 +typedef int (WINAPI * PFNWGLENUMERATEVIDEODEVICESNVPROC) (HDC hDc, HVIDEOOUTPUTDEVICENV *phDeviceList); +typedef BOOL (WINAPI * PFNWGLBINDVIDEODEVICENVPROC) (HDC hDc, unsigned int uVideoSlot, HVIDEOOUTPUTDEVICENV hVideoDevice, const int *piAttribList); +typedef BOOL (WINAPI * PFNWGLQUERYCURRENTCONTEXTNVPROC) (int iAttribute, int *piValue); +#ifdef WGL_WGLEXT_PROTOTYPES +int WINAPI wglEnumerateVideoDevicesNV (HDC hDc, HVIDEOOUTPUTDEVICENV *phDeviceList); +BOOL WINAPI wglBindVideoDeviceNV (HDC hDc, unsigned int uVideoSlot, HVIDEOOUTPUTDEVICENV hVideoDevice, const int *piAttribList); +BOOL WINAPI wglQueryCurrentContextNV (int iAttribute, int *piValue); +#endif +#endif /* WGL_NV_present_video */ + +#ifndef WGL_NV_render_depth_texture +#define WGL_NV_render_depth_texture 1 +#define WGL_BIND_TO_TEXTURE_DEPTH_NV 0x20A3 +#define WGL_BIND_TO_TEXTURE_RECTANGLE_DEPTH_NV 0x20A4 +#define WGL_DEPTH_TEXTURE_FORMAT_NV 0x20A5 +#define WGL_TEXTURE_DEPTH_COMPONENT_NV 0x20A6 +#define WGL_DEPTH_COMPONENT_NV 0x20A7 +#endif /* WGL_NV_render_depth_texture */ + +#ifndef WGL_NV_render_texture_rectangle +#define WGL_NV_render_texture_rectangle 1 +#define WGL_BIND_TO_TEXTURE_RECTANGLE_RGB_NV 0x20A0 +#define WGL_BIND_TO_TEXTURE_RECTANGLE_RGBA_NV 0x20A1 +#define WGL_TEXTURE_RECTANGLE_NV 0x20A2 +#endif /* WGL_NV_render_texture_rectangle */ + +#ifndef WGL_NV_swap_group +#define WGL_NV_swap_group 1 +typedef BOOL (WINAPI * PFNWGLJOINSWAPGROUPNVPROC) (HDC hDC, GLuint group); +typedef BOOL (WINAPI * PFNWGLBINDSWAPBARRIERNVPROC) (GLuint group, GLuint barrier); +typedef BOOL (WINAPI * PFNWGLQUERYSWAPGROUPNVPROC) (HDC hDC, GLuint *group, GLuint *barrier); +typedef BOOL (WINAPI * PFNWGLQUERYMAXSWAPGROUPSNVPROC) (HDC hDC, GLuint *maxGroups, GLuint *maxBarriers); +typedef BOOL (WINAPI * PFNWGLQUERYFRAMECOUNTNVPROC) (HDC hDC, GLuint *count); +typedef BOOL (WINAPI * PFNWGLRESETFRAMECOUNTNVPROC) (HDC hDC); +#ifdef WGL_WGLEXT_PROTOTYPES +BOOL WINAPI wglJoinSwapGroupNV (HDC hDC, GLuint group); +BOOL WINAPI wglBindSwapBarrierNV (GLuint group, GLuint barrier); +BOOL WINAPI wglQuerySwapGroupNV (HDC hDC, GLuint *group, GLuint *barrier); +BOOL WINAPI wglQueryMaxSwapGroupsNV (HDC hDC, GLuint *maxGroups, GLuint *maxBarriers); +BOOL WINAPI wglQueryFrameCountNV (HDC hDC, GLuint *count); +BOOL WINAPI wglResetFrameCountNV (HDC hDC); +#endif +#endif /* WGL_NV_swap_group */ + +#ifndef WGL_NV_vertex_array_range +#define WGL_NV_vertex_array_range 1 +typedef void *(WINAPI * PFNWGLALLOCATEMEMORYNVPROC) (GLsizei size, GLfloat readfreq, GLfloat writefreq, GLfloat priority); +typedef void (WINAPI * PFNWGLFREEMEMORYNVPROC) (void *pointer); +#ifdef WGL_WGLEXT_PROTOTYPES +void *WINAPI wglAllocateMemoryNV (GLsizei size, GLfloat readfreq, GLfloat writefreq, GLfloat priority); +void WINAPI wglFreeMemoryNV (void *pointer); +#endif +#endif /* WGL_NV_vertex_array_range */ + +#ifndef WGL_NV_video_capture +#define WGL_NV_video_capture 1 +DECLARE_HANDLE(HVIDEOINPUTDEVICENV); +#define WGL_UNIQUE_ID_NV 0x20CE +#define WGL_NUM_VIDEO_CAPTURE_SLOTS_NV 0x20CF +typedef BOOL (WINAPI * PFNWGLBINDVIDEOCAPTUREDEVICENVPROC) (UINT uVideoSlot, HVIDEOINPUTDEVICENV hDevice); +typedef UINT (WINAPI * PFNWGLENUMERATEVIDEOCAPTUREDEVICESNVPROC) (HDC hDc, HVIDEOINPUTDEVICENV *phDeviceList); +typedef BOOL (WINAPI * PFNWGLLOCKVIDEOCAPTUREDEVICENVPROC) (HDC hDc, HVIDEOINPUTDEVICENV hDevice); +typedef BOOL (WINAPI * PFNWGLQUERYVIDEOCAPTUREDEVICENVPROC) (HDC hDc, HVIDEOINPUTDEVICENV hDevice, int iAttribute, int *piValue); +typedef BOOL (WINAPI * PFNWGLRELEASEVIDEOCAPTUREDEVICENVPROC) (HDC hDc, HVIDEOINPUTDEVICENV hDevice); +#ifdef WGL_WGLEXT_PROTOTYPES +BOOL WINAPI wglBindVideoCaptureDeviceNV (UINT uVideoSlot, HVIDEOINPUTDEVICENV hDevice); +UINT WINAPI wglEnumerateVideoCaptureDevicesNV (HDC hDc, HVIDEOINPUTDEVICENV *phDeviceList); +BOOL WINAPI wglLockVideoCaptureDeviceNV (HDC hDc, HVIDEOINPUTDEVICENV hDevice); +BOOL WINAPI wglQueryVideoCaptureDeviceNV (HDC hDc, HVIDEOINPUTDEVICENV hDevice, int iAttribute, int *piValue); +BOOL WINAPI wglReleaseVideoCaptureDeviceNV (HDC hDc, HVIDEOINPUTDEVICENV hDevice); +#endif +#endif /* WGL_NV_video_capture */ + +#ifndef WGL_NV_video_output +#define WGL_NV_video_output 1 +DECLARE_HANDLE(HPVIDEODEV); +#define WGL_BIND_TO_VIDEO_RGB_NV 0x20C0 +#define WGL_BIND_TO_VIDEO_RGBA_NV 0x20C1 +#define WGL_BIND_TO_VIDEO_RGB_AND_DEPTH_NV 0x20C2 +#define WGL_VIDEO_OUT_COLOR_NV 0x20C3 +#define WGL_VIDEO_OUT_ALPHA_NV 0x20C4 +#define WGL_VIDEO_OUT_DEPTH_NV 0x20C5 +#define WGL_VIDEO_OUT_COLOR_AND_ALPHA_NV 0x20C6 +#define WGL_VIDEO_OUT_COLOR_AND_DEPTH_NV 0x20C7 +#define WGL_VIDEO_OUT_FRAME 0x20C8 +#define WGL_VIDEO_OUT_FIELD_1 0x20C9 +#define WGL_VIDEO_OUT_FIELD_2 0x20CA +#define WGL_VIDEO_OUT_STACKED_FIELDS_1_2 0x20CB +#define WGL_VIDEO_OUT_STACKED_FIELDS_2_1 0x20CC +typedef BOOL (WINAPI * PFNWGLGETVIDEODEVICENVPROC) (HDC hDC, int numDevices, HPVIDEODEV *hVideoDevice); +typedef BOOL (WINAPI * PFNWGLRELEASEVIDEODEVICENVPROC) (HPVIDEODEV hVideoDevice); +typedef BOOL (WINAPI * PFNWGLBINDVIDEOIMAGENVPROC) (HPVIDEODEV hVideoDevice, HPBUFFERARB hPbuffer, int iVideoBuffer); +typedef BOOL (WINAPI * PFNWGLRELEASEVIDEOIMAGENVPROC) (HPBUFFERARB hPbuffer, int iVideoBuffer); +typedef BOOL (WINAPI * PFNWGLSENDPBUFFERTOVIDEONVPROC) (HPBUFFERARB hPbuffer, int iBufferType, unsigned long *pulCounterPbuffer, BOOL bBlock); +typedef BOOL (WINAPI * PFNWGLGETVIDEOINFONVPROC) (HPVIDEODEV hpVideoDevice, unsigned long *pulCounterOutputPbuffer, unsigned long *pulCounterOutputVideo); +#ifdef WGL_WGLEXT_PROTOTYPES +BOOL WINAPI wglGetVideoDeviceNV (HDC hDC, int numDevices, HPVIDEODEV *hVideoDevice); +BOOL WINAPI wglReleaseVideoDeviceNV (HPVIDEODEV hVideoDevice); +BOOL WINAPI wglBindVideoImageNV (HPVIDEODEV hVideoDevice, HPBUFFERARB hPbuffer, int iVideoBuffer); +BOOL WINAPI wglReleaseVideoImageNV (HPBUFFERARB hPbuffer, int iVideoBuffer); +BOOL WINAPI wglSendPbufferToVideoNV (HPBUFFERARB hPbuffer, int iBufferType, unsigned long *pulCounterPbuffer, BOOL bBlock); +BOOL WINAPI wglGetVideoInfoNV (HPVIDEODEV hpVideoDevice, unsigned long *pulCounterOutputPbuffer, unsigned long *pulCounterOutputVideo); +#endif +#endif /* WGL_NV_video_output */ + +#ifndef WGL_OML_sync_control +#define WGL_OML_sync_control 1 +typedef BOOL (WINAPI * PFNWGLGETSYNCVALUESOMLPROC) (HDC hdc, INT64 *ust, INT64 *msc, INT64 *sbc); +typedef BOOL (WINAPI * PFNWGLGETMSCRATEOMLPROC) (HDC hdc, INT32 *numerator, INT32 *denominator); +typedef INT64 (WINAPI * PFNWGLSWAPBUFFERSMSCOMLPROC) (HDC hdc, INT64 target_msc, INT64 divisor, INT64 remainder); +typedef INT64 (WINAPI * PFNWGLSWAPLAYERBUFFERSMSCOMLPROC) (HDC hdc, INT fuPlanes, INT64 target_msc, INT64 divisor, INT64 remainder); +typedef BOOL (WINAPI * PFNWGLWAITFORMSCOMLPROC) (HDC hdc, INT64 target_msc, INT64 divisor, INT64 remainder, INT64 *ust, INT64 *msc, INT64 *sbc); +typedef BOOL (WINAPI * PFNWGLWAITFORSBCOMLPROC) (HDC hdc, INT64 target_sbc, INT64 *ust, INT64 *msc, INT64 *sbc); +#ifdef WGL_WGLEXT_PROTOTYPES +BOOL WINAPI wglGetSyncValuesOML (HDC hdc, INT64 *ust, INT64 *msc, INT64 *sbc); +BOOL WINAPI wglGetMscRateOML (HDC hdc, INT32 *numerator, INT32 *denominator); +INT64 WINAPI wglSwapBuffersMscOML (HDC hdc, INT64 target_msc, INT64 divisor, INT64 remainder); +INT64 WINAPI wglSwapLayerBuffersMscOML (HDC hdc, INT fuPlanes, INT64 target_msc, INT64 divisor, INT64 remainder); +BOOL WINAPI wglWaitForMscOML (HDC hdc, INT64 target_msc, INT64 divisor, INT64 remainder, INT64 *ust, INT64 *msc, INT64 *sbc); +BOOL WINAPI wglWaitForSbcOML (HDC hdc, INT64 target_sbc, INT64 *ust, INT64 *msc, INT64 *sbc); +#endif +#endif /* WGL_OML_sync_control */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/Common/MemPtr.h b/src/Common/MemPtr.h new file mode 100644 index 00000000..7ad5b76f --- /dev/null +++ b/src/Common/MemPtr.h @@ -0,0 +1,150 @@ +#pragma once +#include +#include "betype.h" + +extern uint8* memory_base; +extern uint8* PPCInterpreterGetStackPointer(); +extern uint8* PPCInterpreterGetAndModifyStackPointer(sint32 offset); +extern void PPCInterpreterModifyStackPointer(sint32 offset); + +class MEMPTRBase {}; + +template +class MEMPTR : MEMPTRBase +{ +public: + constexpr MEMPTR() + : m_value(0) { } + + explicit constexpr MEMPTR(uint32 offset) + : m_value(offset) { } + + explicit constexpr MEMPTR(const uint32be& offset) + : m_value(offset) { } + + constexpr MEMPTR(std::nullptr_t) + : m_value(0) { } + + MEMPTR(T* ptr) + { + if (ptr == nullptr) + m_value = 0; + else + m_value = (uint32)((uintptr_t)ptr - (uintptr_t)memory_base); + } + + constexpr MEMPTR(const MEMPTR& memptr) + : m_value(memptr.m_value) { } + + constexpr MEMPTR& operator=(const MEMPTR& memptr) + { + m_value = memptr.m_value; + return *this; + } + + constexpr MEMPTR& operator=(const uint32& offset) + { + m_value = offset; + return *this; + } + + constexpr MEMPTR& operator=(const std::nullptr_t rhs) + { + m_value = 0; + return *this; + } + + MEMPTR& operator=(T* ptr) + { + if (ptr == nullptr) + m_value = 0; + else + m_value = (uint32)((uintptr_t)ptr - (uintptr_t)memory_base); + return *this; + } + + bool atomic_compare_exchange(T* comparePtr, T* newPtr) + { + MEMPTR mp_compare = comparePtr; + MEMPTR mp_new = newPtr; + std::atomic* thisValueAtomic = (std::atomic*)&m_value; + return thisValueAtomic->compare_exchange_strong(mp_compare.m_value, mp_new.m_value); + } + + explicit constexpr operator bool() const noexcept { return m_value != 0; } + + constexpr operator T*() const noexcept { return GetPtr(); } // allow implicit cast to wrapped pointer type + + + template + explicit operator MEMPTR() const { return MEMPTR(this->m_value); } + + //bool operator==(const MEMPTR& v) const { return m_value == v.m_value; } + //bool operator==(const T* rhs) const { return (T*)(m_value == 0 ? nullptr : memory_base + (uint32)m_value) == rhs; } -> ambigious (implicit cast to T* allows for T* == T*) + //bool operator==(std::nullptr_t rhs) const { return m_value == 0; } + + //bool operator!=(const MEMPTR& v) const { return !(*this == v); } + //bool operator!=(const void* rhs) const { return !(*this == rhs); } + //bool operator!=(int rhs) const { return !(*this == rhs); } + + //bool operator==(const void* rhs) const { return (void*)(m_value == 0 ? nullptr : memory_base + (uint32)m_value) == rhs; } + + //explicit bool operator==(int rhs) const { return *this == (const void*)(size_t)rhs; } + + + MEMPTR operator+(const MEMPTR& ptr) { return MEMPTR(this->GetMPTR() + ptr.GetMPTR()); } + MEMPTR operator-(const MEMPTR& ptr) { return MEMPTR(this->GetMPTR() - ptr.GetMPTR()); } + + MEMPTR operator+(sint32 v) + { + // pointer arithmetic + return MEMPTR(this->GetMPTR() + v * 4); + } + + MEMPTR operator-(sint32 v) + { + // pointer arithmetic + return MEMPTR(this->GetMPTR() - v * 4); + } + + template + typename std::enable_if::value, Q>::type& + operator*() const { return *GetPtr(); } + + T* operator->() const { return GetPtr(); } + + template + typename std::enable_if::value, Q>::type& + operator[](int index) { return GetPtr()[index]; } + + T* GetPtr() const { return (T*)(m_value == 0 ? nullptr : memory_base + (uint32)m_value); } + + template + C* GetPtr() const { return (C*)(GetPtr()); } + + constexpr uint32 GetMPTR() const { return m_value.value(); } + constexpr uint32 GetRawValue() const { return m_value.bevalue(); } // accesses value using host-endianness + + constexpr const uint32be& GetBEValue() const { return m_value; } + + constexpr bool IsNull() const { return m_value == 0; } + + constexpr uint32 GetMPTRBE() const { return m_value.bevalue(); } + + uint32be* GetBEPtr() { return &m_value; } + +private: + uint32be m_value; +}; + +static_assert(sizeof(MEMPTR) == sizeof(uint32be)); + +#include "StackAllocator.h" +#include "SysAllocator.h" + +template +struct fmt::formatter> : formatter +{ + template + auto format(const MEMPTR& v, FormatContext& ctx) { return formatter::format(fmt::format("{:#x}", v.GetMPTR()), ctx); } +}; diff --git a/src/Common/StackAllocator.h b/src/Common/StackAllocator.h new file mode 100644 index 00000000..750db13f --- /dev/null +++ b/src/Common/StackAllocator.h @@ -0,0 +1,41 @@ +#pragma once + +template +class StackAllocator +{ +public: + StackAllocator() + : StackAllocator(1) + {} + + explicit StackAllocator(const uint32 items) + { + m_modified_size = count * sizeof(T) * items + kStaticMemOffset * 2; + + auto tmp = PPCInterpreterGetStackPointer(); + m_ptr = (T*)(PPCInterpreterGetAndModifyStackPointer(m_modified_size) + kStaticMemOffset); + } + + ~StackAllocator() + { + PPCInterpreterModifyStackPointer(-m_modified_size); + } + + T* operator->() const + { + return GetPointer(); + } + + T* GetPointer() const { return m_ptr; } + uint32 GetMPTR() const { return MEMPTR(m_ptr).GetMPTR(); } + uint32 GetMPTRBE() const { return MEMPTR(m_ptr).GetMPTRBE(); } + + operator T*() const { return GetPointer(); } + explicit operator uint32() const { return GetMPTR(); } + +private: + static const uint32 kStaticMemOffset = 64; + + T* m_ptr; + sint32 m_modified_size; +}; \ No newline at end of file diff --git a/src/Common/SysAllocator.cpp b/src/Common/SysAllocator.cpp new file mode 100644 index 00000000..6632f6f0 --- /dev/null +++ b/src/Common/SysAllocator.cpp @@ -0,0 +1,25 @@ +#include "SysAllocator.h" + +void SysAllocatorContainer::Initialize() +{ + for (SysAllocatorBase* sysAlloc : m_sysAllocList) + { + sysAlloc->Initialize(); + } +} + +void SysAllocatorContainer::PushSysAllocator(SysAllocatorBase* base) +{ + m_sysAllocList.push_back(base); +} + +SysAllocatorContainer& SysAllocatorContainer::GetInstance() +{ + static SysAllocatorContainer s_instance; + return s_instance; +} + +SysAllocatorBase::SysAllocatorBase() +{ + SysAllocatorContainer::GetInstance().PushSysAllocator(this); +} diff --git a/src/Common/SysAllocator.h b/src/Common/SysAllocator.h new file mode 100644 index 00000000..1807fb24 --- /dev/null +++ b/src/Common/SysAllocator.h @@ -0,0 +1,198 @@ +#pragma once + +#include +uint32 coreinit_allocFromSysArea(uint32 size, uint32 alignment); +class SysAllocatorBase; + +class SysAllocatorContainer +{ +public: + void Initialize(); + void PushSysAllocator(SysAllocatorBase* base); + + static SysAllocatorContainer& GetInstance(); + +private: + std::vector m_sysAllocList; +}; + + +class SysAllocatorBase +{ + friend class SysAllocatorContainer; +public: + SysAllocatorBase(); + virtual ~SysAllocatorBase() = default; + +private: + virtual void Initialize() = 0; +}; + + + +template +class SysAllocator : public SysAllocatorBase +{ +public: + SysAllocator() + { + m_tempData.resize(count); + } + + SysAllocator(std::initializer_list l) + { + m_tempData.reserve(count); + + m_tempData.insert(m_tempData.begin(), l); + if (count > l.size()) + m_tempData.insert(m_tempData.end(), count - l.size(), T()); + } + + constexpr uint32 GetCount() const + { + return count; + } + + constexpr size_t GetByteSize() const + { + return count * sizeof(T); + } + + T* GetPtr() const + { + return m_sysMem.GetPtr(); + } + + uint32 GetMPTR() const + { + return m_sysMem.GetMPTR(); + } + + T& operator*() + { + return *GetPtr(); + } + + operator T*() + { + return GetPtr(); + } + + SysAllocator operator+(const SysAllocator& ptr) + { + return SysAllocator(this->GetMPTR() + ptr.GetMPTR()); + } + + SysAllocator operator-(const SysAllocator& ptr) + { + return SysAllocator(this->GetMPTR() - ptr.GetMPTR()); + } + + T* operator+(sint32 v) + { + return this->GetPtr() + v; + } + + T* operator-(sint32 v) + { + return this->GetPtr() - v; + } + + operator void*() { return m_sysMem->GetPtr(); } + + // for all arrays except bool + template + typename std::enable_if< count != 1 && !std::is_same::value, Q >::type& + operator[](int index) + { + // return tmp data until we allocated in sys mem + if (m_sysMem.GetMPTR() == 0) + { + return m_tempData[index]; + } + + return m_sysMem[index]; + } +private: + SysAllocator(uint32 memptr) + : m_sysMem(memptr) + {} + + void Initialize() override + { + if (m_sysMem.GetMPTR() != 0) + return; + + // alloc mem + m_sysMem = { coreinit_allocFromSysArea(sizeof(T) * count, alignment) }; + // copy temp buffer to mem and clear it + memcpy(m_sysMem.GetPtr(), m_tempData.data(), sizeof(T)*count); + m_tempData.clear(); + } + + MEMPTR m_sysMem; + std::vector m_tempData; +}; + +template +class SysAllocator : public SysAllocatorBase +{ +public: + SysAllocator() + { + m_tempData = {}; + } + + SysAllocator(const T& value) + { + m_tempData = value; + } + + SysAllocator& operator=(const T& value) + { + *m_sysMem = value; + return *this; + } + + operator T() + { + return *GetPtr(); + } + + operator T*() + { + return GetPtr(); + } + + T* GetPtr() const + { + return m_sysMem.GetPtr(); + } + + uint32 GetMPTR() const + { + return m_sysMem.GetMPTR(); + } + + T* operator&() { return (T*)m_sysMem.GetPtr(); } + + T* operator->() const + { + return this->GetPtr(); + } + +private: + void Initialize() override + { + if (m_sysMem.GetMPTR() != 0) + return; + + // alloc mem + m_sysMem = { coreinit_allocFromSysArea(sizeof(T), 8) }; + // copy temp buffer to mem and clear it + *m_sysMem = m_tempData; + } + + MEMPTR m_sysMem; + T m_tempData; +}; \ No newline at end of file diff --git a/src/Common/betype.h b/src/Common/betype.h new file mode 100644 index 00000000..62762da4 --- /dev/null +++ b/src/Common/betype.h @@ -0,0 +1,232 @@ +#pragma once + +#include + +namespace Latte +{ + class LATTEREG; +}; + +template +constexpr T bswap_impl(T i, std::index_sequence) +{ + return ((((i >> (N * CHAR_BIT)) & (T)(uint8_t)(-1)) << ((sizeof(T) - 1 - N) * CHAR_BIT)) | ...); +} + +template> +constexpr T bswap(T i) +{ + return (T)bswap_impl((U)i, std::make_index_sequence{}); +} + +template +constexpr T SwapEndian(T value) +{ + if constexpr (boost::is_integral::value) + { +#ifdef _MSC_VER + if constexpr (sizeof(T) == sizeof(uint32_t)) + { + return (T)_byteswap_ulong(value); + } +#endif + + return (T)bswap((std::make_unsigned_t)value); + } + else if constexpr (boost::is_floating_point::value) + { + if constexpr (sizeof(T) == sizeof(uint32_t)) + { + const auto tmp = bswap(*(uint32_t*)&value); + return *(T*)&tmp; + } + if constexpr (sizeof(T) == sizeof(uint64_t)) + { + const auto tmp = bswap(*(uint64_t*)&value); + return *(T*)&tmp; + } + } + else if constexpr (boost::is_enum::value) + { + return (T)SwapEndian((std::underlying_type_t)value); + } + else if constexpr (boost::is_base_of::value) + { + const auto tmp = bswap(*(uint32_t*)&value); + return *(T*)&tmp; + } + else + { + static_assert(boost::is_integral::value, "unsupported betype specialization!"); + } + + return value; +} + +#ifndef WIN32 +//static_assert(false, "_BE and _LE need to be adjusted"); +#endif + +// swap if native isn't big endian +template +constexpr T _BE(T value) +{ + return SwapEndian(value); +} + +// swap if native isn't little endian +template +constexpr T _LE(T value) +{ + return value; +} + +template +class betype +{ +public: + constexpr betype() = default; + + // copy + constexpr betype(T value) + : m_value(SwapEndian(value)) {} + + constexpr betype(const betype& value) = default; // required for trivially_copyable + //constexpr betype(const betype& value) + // : m_value(value.m_value) {} + + template + constexpr betype(const betype& value) + : betype((T)value.value()) {} + + // assigns + static betype from_bevalue(T value) + { + betype result; + result.m_value = value; + return result; + } + + // returns LE value + constexpr T value() const { return SwapEndian(m_value); } + + // returns BE value + constexpr T bevalue() const { return m_value; } + + constexpr operator T() const { return value(); } + + betype& operator+=(const betype& v) + { + m_value = SwapEndian(T(value() + v.value())); + return *this; + } + + betype& operator-=(const betype& v) + { + m_value = SwapEndian(T(value() - v.value())); + return *this; + } + + betype& operator*=(const betype& v) + { + m_value = SwapEndian(T(value() * v.value())); + return *this; + } + + betype& operator/=(const betype& v) + { + m_value = SwapEndian(T(value() / v.value())); + return *this; + } + + betype& operator&=(const betype& v) requires (requires (T& x, const T& y) { x &= y; }) + { + m_value &= v.m_value; + return *this; + } + + betype& operator|=(const betype& v) requires (requires (T& x, const T& y) { x |= y; }) + { + m_value |= v.m_value; + return *this; + } + + betype& operator|(const betype& v) const requires (requires (T& x, const T& y) { x | y; }) + { + betype tmp(*this); + tmp.m_value = tmp.m_value | v.m_value; + return tmp; + } + + betype operator|(const T& v) const requires (requires (T& x, const T& y) { x | y; }) + { + betype tmp(*this); + tmp.m_value = tmp.m_value | SwapEndian(v); + return tmp; + } + + betype& operator^=(const betype& v) requires std::integral + { + m_value ^= v.m_value; + return *this; + } + + betype& operator>>=(std::size_t idx) requires std::integral + { + m_value = SwapEndian(T(value() >> idx)); + return *this; + } + + betype& operator<<=(std::size_t idx) requires std::integral + { + m_value = SwapEndian(T(value() << idx)); + return *this; + } + + betype operator~() const requires std::integral + { + return from_bevalue(T(~m_value)); + } + + betype& operator++() requires std::integral + { + m_value = SwapEndian(T(value() + 1)); + return *this; + } + + betype& operator--() requires std::integral + { + m_value = SwapEndian(T(value() - 1)); + return *this; + } +private: + //T m_value{}; // before 1.26.2 + T m_value; +}; + +using uint64be = betype; +using uint32be = betype; +using uint16be = betype; +using uint8be = betype; + +using sint64be = betype; +using sint32be = betype; +using sint16be = betype; +using sint8be = betype; + +using float32be = betype; +using float64be = betype; + +static_assert(sizeof(betype) == sizeof(uint64)); +static_assert(sizeof(betype) == sizeof(uint32)); +static_assert(sizeof(betype) == sizeof(uint16)); +static_assert(sizeof(betype) == sizeof(uint8)); +static_assert(sizeof(betype) == sizeof(float)); +static_assert(sizeof(betype) == sizeof(double)); + +static_assert(std::is_trivially_copyable_v); +static_assert(std::is_trivially_constructible_v); +static_assert(std::is_copy_constructible_v); +static_assert(std::is_move_constructible_v); +static_assert(std::is_copy_assignable_v); +static_assert(std::is_move_assignable_v); diff --git a/src/Common/enumFlags.h b/src/Common/enumFlags.h new file mode 100644 index 00000000..ac63f4c6 --- /dev/null +++ b/src/Common/enumFlags.h @@ -0,0 +1,132 @@ +#pragma once + +#include + +// enum flag helpers +template +struct EnableBitMaskOperators +{ + static const bool enable = false; +}; + +template +typename std::enable_if::enable, TEnum>::type +operator &(TEnum lhs, TEnum rhs) +{ + return static_cast (static_cast::type>(lhs) & static_cast::type>(rhs)); +} + +template +typename std::enable_if::enable, TEnum>::type +operator |(TEnum lhs, TEnum rhs) +{ + return static_cast (static_cast::type>(lhs) | static_cast::type>(rhs)); +} + +template +typename std::enable_if::enable, TEnum>::type +operator ^(TEnum lhs, TEnum rhs) +{ + return static_cast (static_cast::type>(lhs) ^ static_cast::type>(rhs)); +} + +template +typename std::enable_if::enable, TEnum>::type +operator ~(TEnum rhs) +{ + return static_cast (~static_cast::type>(rhs)); +} + +template +typename std::enable_if::enable, TEnum>::type& +operator &=(TEnum& lhs, TEnum rhs) +{ + lhs = static_cast (static_cast::type>(lhs) & static_cast::type>(rhs)); + return lhs; +} + +template +typename std::enable_if::enable, TEnum>::type& +operator |=(TEnum& lhs, TEnum rhs) +{ + lhs = static_cast (static_cast::type>(lhs) | static_cast::type>(rhs)); + return lhs; +} + +template +typename std::enable_if::enable, TEnum>::type& +operator ^=(TEnum& lhs, TEnum rhs) +{ + lhs = static_cast (static_cast::type>(lhs) ^ static_cast::type>(rhs)); + return lhs; +} + +template::enable>> +constexpr bool operator==(TEnum lhs, std::underlying_type_t rhs) +{ + return static_cast>(lhs) == rhs; +} +template::enable>> +constexpr bool operator!=(TEnum lhs, std::underlying_type_t rhs) +{ + return static_cast>(lhs) != rhs; +} + +#define ENABLE_BITMASK_OPERATORS(x) template<> struct EnableBitMaskOperators { static const bool enable = true; }; + + +template +struct EnableEnumIterators +{ + static const bool enable = false; +}; + +template +typename std::enable_if::enable, TEnum>::type& +operator++(TEnum& lhs) +{ + lhs = static_cast(static_cast::type>(lhs) + 1); + return lhs; +} + +template +typename std::enable_if::enable, TEnum>::type +operator*(TEnum rhs) +{ + return rhs; +} + +template +typename std::enable_if::enable, TEnum>::type +begin(TEnum value) +{ + return EnableEnumIterators::begin; +} + +template +typename std::enable_if::enable, TEnum>::type +rbegin(TEnum value) +{ + return EnableEnumIterators::rbegin; +} + +template +typename std::enable_if::enable, TEnum>::type +end(TEnum r) { + return EnableEnumIterators::end; +} + +template +typename std::enable_if::enable, TEnum>::type +rend(TEnum r) { + return EnableEnumIterators::rend; +} + +#define ENABLE_ENUM_ITERATORS(x, first_value, last_value) template<> struct EnableEnumIterators {\ + static const bool enable = true;\ + static const x begin = first_value;\ + static const x rbegin = last_value;\ + static const x end = static_cast(static_cast::type>(last_value) + 1);\ + static const x rend = static_cast(static_cast::type>(first_value) - 1);\ +}; +// todo: rend type must be signed? \ No newline at end of file diff --git a/src/Common/filestream.h b/src/Common/filestream.h new file mode 100644 index 00000000..64b04c70 --- /dev/null +++ b/src/Common/filestream.h @@ -0,0 +1,441 @@ +#pragma once +#include "Common/precompiled.h" +#include + +#ifdef _WIN32 + +class FileStream +{ +public: + static FileStream* openFile(std::string_view path) + { + HANDLE hFile = CreateFileW(boost::nowide::widen(path.data(), path.size()).c_str(), FILE_GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0); + if (hFile == INVALID_HANDLE_VALUE) + return nullptr; + return new FileStream(hFile); + } + + static FileStream* openFile(const wchar_t* path, bool allowWrite = false) + { + HANDLE hFile = CreateFileW(path, allowWrite ? (FILE_GENERIC_READ | FILE_GENERIC_WRITE) : FILE_GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0); + if (hFile == INVALID_HANDLE_VALUE) + return nullptr; + return new FileStream(hFile); + } + + static FileStream* openFile2(const fs::path& path, bool allowWrite = false) + { + return openFile(path.generic_wstring().c_str(), allowWrite); + } + + static FileStream* createFile(const wchar_t* path) + { + HANDLE hFile = CreateFileW(path, FILE_GENERIC_READ | FILE_GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, 0); + if (hFile == INVALID_HANDLE_VALUE) + return nullptr; + return new FileStream(hFile); + } + + static FileStream* createFile(std::string_view path) + { + auto w = boost::nowide::widen(path.data(), path.size()); + HANDLE hFile = CreateFileW(w.c_str(), FILE_GENERIC_READ | FILE_GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, 0); + if (hFile == INVALID_HANDLE_VALUE) + return nullptr; + return new FileStream(hFile); + } + + static FileStream* createFile2(const fs::path& path) + { + return createFile(path.generic_wstring().c_str()); + } + + // helper function to load a file into memory + static std::optional> LoadIntoMemory(const fs::path& path) + { + FileStream* fs = openFile2(path); + if (!fs) + return std::nullopt; + uint64 fileSize = fs->GetSize(); + if(fileSize > 0xFFFFFFFFull) + { + delete fs; + return std::nullopt; + } + std::optional> v(fileSize); + if (fs->readData(v->data(), (uint32)fileSize) != (uint32)fileSize) + { + delete fs; + return std::nullopt; + } + delete fs; + return v; + } + + // size and seek + void SetPosition(uint64 pos) + { + LONG posHigh = (LONG)(pos >> 32); + LONG posLow = (LONG)(pos); + SetFilePointer(m_hFile, posLow, &posHigh, FILE_BEGIN); + } + + uint64 GetSize() + { + DWORD fileSizeHigh = 0; + DWORD fileSizeLow = 0; + fileSizeLow = GetFileSize(m_hFile, &fileSizeHigh); + return ((uint64)fileSizeHigh << 32) | (uint64)fileSizeLow; + } + + bool SetEndOfFile() + { + return ::SetEndOfFile(m_hFile) != 0; + } + + // reading + void extract(std::vector& data) + { + DWORD fileSize = GetFileSize(m_hFile, nullptr); + data.resize(fileSize); + SetFilePointer(m_hFile, 0, 0, FILE_BEGIN); + DWORD bt; + ReadFile(m_hFile, data.data(), fileSize, &bt, nullptr); + } + + uint32 readData(void* data, uint32 length) + { + DWORD bytesRead = 0; + ReadFile(m_hFile, data, length, &bytesRead, NULL); + return bytesRead; + } + + bool readU64(uint64& v) + { + return readData(&v, sizeof(uint64)) == sizeof(uint64); + } + + bool readU32(uint32& v) + { + return readData(&v, sizeof(uint32)) == sizeof(uint32); + } + + bool readU8(uint8& v) + { + return readData(&v, sizeof(uint8)) == sizeof(uint8); + } + + bool readLine(std::string& line) + { + line.clear(); + uint8 c; + bool isEOF = true; + while (readU8(c)) + { + isEOF = false; + if(c == '\r') + continue; + if (c == '\n') + break; + line.push_back((char)c); + } + return !isEOF; + } + + // writing (binary) + sint32 writeData(const void* data, sint32 length) + { + DWORD bytesWritten = 0; + WriteFile(m_hFile, data, length, &bytesWritten, NULL); + return bytesWritten; + } + + void writeU64(uint64 v) + { + writeData(&v, sizeof(uint64)); + } + + void writeU32(uint32 v) + { + writeData(&v, sizeof(uint32)); + } + + void writeU8(uint8 v) + { + writeData(&v, sizeof(uint8)); + } + + // writing (strings) + void writeStringFmt(const char* format, ...) + { + char buffer[2048]; + va_list args; + va_start(args, format); + vsnprintf(buffer, sizeof(buffer), format, args); + writeData(buffer, (sint32)strlen(buffer)); + } + + void writeString(const char* str) + { + writeData(str, (sint32)strlen(str)); + } + + void writeLine(const char* str) + { + writeData(str, (sint32)strlen(str)); + writeData("\r\n", 2); + } + + ~FileStream() + { + if(m_isValid) + CloseHandle(m_hFile); + } + + FileStream() {}; + +private: + FileStream(HANDLE hFile) + { + m_hFile = hFile; + m_isValid = true; + } + + bool m_isValid{}; + HANDLE m_hFile; +}; + +#else + +#include + +class FileStream +{ +public: + static FileStream* openFile(std::string_view path) + { + return openFile2(path, false); + } + + static FileStream* openFile(const wchar_t* path, bool allowWrite = false) + { + return openFile2(path, allowWrite); + } + + static FileStream* openFile2(const fs::path& path, bool allowWrite = false) + { + //return openFile(path.generic_wstring().c_str(), allowWrite); + FileStream* fs = new FileStream(path, true, allowWrite); + if (fs->m_isValid) + return fs; + delete fs; + return nullptr; + } + + static FileStream* createFile(const wchar_t* path) + { + return createFile2(path); + } + + static FileStream* createFile(std::string_view path) + { + return createFile2(path); + } + + static FileStream* createFile2(const fs::path& path) + { + FileStream* fs = new FileStream(path, false, false); + if (fs->m_isValid) + return fs; + delete fs; + return nullptr; + } + + // helper function to load a file into memory + static std::optional> LoadIntoMemory(const fs::path& path) + { + FileStream* fs = openFile2(path); + if (!fs) + return std::nullopt; + uint64 fileSize = fs->GetSize(); + if (fileSize > 0xFFFFFFFFull) + { + delete fs; + return std::nullopt; + } + std::optional> v(fileSize); + if (fs->readData(v->data(), (uint32)fileSize) != (uint32)fileSize) + { + delete fs; + return std::nullopt; + } + delete fs; + return v; + } + + // size and seek + void SetPosition(uint64 pos) + { + cemu_assert(m_isValid); + if (m_prevOperationWasWrite) + m_fileStream.seekp((std::streampos)pos); + else + m_fileStream.seekg((std::streampos)pos); + } + + uint64 GetSize() + { + cemu_assert(m_isValid); + auto currentPos = m_fileStream.tellg(); + m_fileStream.seekg(0, std::ios::end); + auto fileSize = m_fileStream.tellg(); + m_fileStream.seekg(currentPos, std::ios::beg); + uint64 fs = (uint64)fileSize; + return fs; + } + + bool SetEndOfFile() + { + assert_dbg(); + return true; + //return ::SetEndOfFile(m_hFile) != 0; + } + + // reading + void extract(std::vector& data) + { + uint64 fileSize = GetSize(); + SetPosition(0); + data.resize(fileSize); + readData(data.data(), fileSize); + } + + uint32 readData(void* data, uint32 length) + { + SyncReadWriteSeek(false); + m_fileStream.read((char*)data, length); + size_t bytesRead = m_fileStream.gcount(); + return (uint32)bytesRead; + } + + bool readU64(uint64& v) + { + return readData(&v, sizeof(uint64)) == sizeof(uint64); + } + + bool readU32(uint32& v) + { + return readData(&v, sizeof(uint32)) == sizeof(uint32); + } + + bool readU8(uint8& v) + { + return readData(&v, sizeof(uint8)) == sizeof(uint8); + } + + bool readLine(std::string& line) + { + line.clear(); + uint8 c; + bool isEOF = true; + while (readU8(c)) + { + isEOF = false; + if (c == '\r') + continue; + if (c == '\n') + break; + line.push_back((char)c); + } + return !isEOF; + } + + // writing (binary) + sint32 writeData(const void* data, sint32 length) + { + SyncReadWriteSeek(true); + m_fileStream.write((const char*)data, length); + return length; + } + + void writeU64(uint64 v) + { + writeData(&v, sizeof(uint64)); + } + + void writeU32(uint32 v) + { + writeData(&v, sizeof(uint32)); + } + + void writeU8(uint8 v) + { + writeData(&v, sizeof(uint8)); + } + + // writing (strings) + void writeStringFmt(const char* format, ...) + { + char buffer[2048]; + va_list args; + va_start(args, format); + vsnprintf(buffer, sizeof(buffer), format, args); + writeData(buffer, (sint32)strlen(buffer)); + } + + void writeString(const char* str) + { + writeData(str, (sint32)strlen(str)); + } + + void writeLine(const char* str) + { + writeData(str, (sint32)strlen(str)); + writeData("\r\n", 2); + } + + ~FileStream() + { + if (m_isValid) + { + m_fileStream.close(); + } + // CloseHandle(m_hFile); + } + + FileStream() {}; + +private: + FileStream(const fs::path& path, bool isOpen, bool isWriteable) + { + if (isOpen) + { + m_fileStream.open(path, isWriteable ? (std::ios_base::in | std::ios_base::out | std::ios_base::binary) : (std::ios_base::in | std::ios_base::binary)); + m_isValid = m_fileStream.is_open(); + } + else + { + m_fileStream.open(path, std::ios_base::in | std::ios_base::out | std::ios_base::binary | std::ios_base::trunc); + m_isValid = m_fileStream.is_open(); + } + } + + void SyncReadWriteSeek(bool nextOpIsWrite) + { + // nextOpIsWrite == false -> read. Otherwise write + if (nextOpIsWrite == m_prevOperationWasWrite) + return; + if (nextOpIsWrite) + m_fileStream.seekp(m_fileStream.tellg(), std::ios::beg); + else + m_fileStream.seekg(m_fileStream.tellp(), std::ios::beg); + + m_prevOperationWasWrite = nextOpIsWrite; + } + + bool m_isValid{}; + std::fstream m_fileStream; + bool m_prevOperationWasWrite{false}; +}; + +#endif \ No newline at end of file diff --git a/src/Common/linux/date.h b/src/Common/linux/date.h new file mode 100644 index 00000000..6960e8cd --- /dev/null +++ b/src/Common/linux/date.h @@ -0,0 +1,8234 @@ +#ifndef DATE_H +#define DATE_H + +// The MIT License (MIT) +// +// Copyright (c) 2015, 2016, 2017 Howard Hinnant +// Copyright (c) 2016 Adrian Colomitchi +// Copyright (c) 2017 Florian Dang +// Copyright (c) 2017 Paul Thompson +// Copyright (c) 2018, 2019 Tomasz KamiÅ„ski +// Copyright (c) 2019 Jiangang Zhuang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// Our apologies. When the previous paragraph was written, lowercase had not yet +// been invented (that would involve another several millennia of evolution). +// We did not mean to shout. + +#ifndef HAS_STRING_VIEW +# if __cplusplus >= 201703 || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +# define HAS_STRING_VIEW 1 +# else +# define HAS_STRING_VIEW 0 +# endif +#endif // HAS_STRING_VIEW + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if HAS_STRING_VIEW +# include +#endif +#include +#include + +#ifdef __GNUC__ +# pragma GCC diagnostic push +# if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 7) +# pragma GCC diagnostic ignored "-Wpedantic" +# endif +# if __GNUC__ < 5 + // GCC 4.9 Bug 61489 Wrong warning with -Wmissing-field-initializers +# pragma GCC diagnostic ignored "-Wmissing-field-initializers" +# endif +#endif + +#ifdef _MSC_VER +# pragma warning(push) +// warning C4127: conditional expression is constant +# pragma warning(disable : 4127) +#endif + +namespace date +{ + +//---------------+ +// Configuration | +//---------------+ + +#ifndef ONLY_C_LOCALE +# define ONLY_C_LOCALE 0 +#endif + +#if defined(_MSC_VER) && (!defined(__clang__) || (_MSC_VER < 1910)) +// MSVC +# ifndef _SILENCE_CXX17_UNCAUGHT_EXCEPTION_DEPRECATION_WARNING +# define _SILENCE_CXX17_UNCAUGHT_EXCEPTION_DEPRECATION_WARNING +# endif +# if _MSC_VER < 1910 +// before VS2017 +# define CONSTDATA const +# define CONSTCD11 +# define CONSTCD14 +# define NOEXCEPT _NOEXCEPT +# else +// VS2017 and later +# define CONSTDATA constexpr const +# define CONSTCD11 constexpr +# define CONSTCD14 constexpr +# define NOEXCEPT noexcept +# endif + +#elif defined(__SUNPRO_CC) && __SUNPRO_CC <= 0x5150 +// Oracle Developer Studio 12.6 and earlier +# define CONSTDATA constexpr const +# define CONSTCD11 constexpr +# define CONSTCD14 +# define NOEXCEPT noexcept + +#elif __cplusplus >= 201402 +// C++14 +# define CONSTDATA constexpr const +# define CONSTCD11 constexpr +# define CONSTCD14 constexpr +# define NOEXCEPT noexcept +#else +// C++11 +# define CONSTDATA constexpr const +# define CONSTCD11 constexpr +# define CONSTCD14 +# define NOEXCEPT noexcept +#endif + +#ifndef HAS_UNCAUGHT_EXCEPTIONS +# if __cplusplus >= 201703 || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +# define HAS_UNCAUGHT_EXCEPTIONS 1 +# else +# define HAS_UNCAUGHT_EXCEPTIONS 0 +# endif +#endif // HAS_UNCAUGHT_EXCEPTIONS + +#ifndef HAS_VOID_T +# if __cplusplus >= 201703 || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +# define HAS_VOID_T 1 +# else +# define HAS_VOID_T 0 +# endif +#endif // HAS_VOID_T + +// Protect from Oracle sun macro +#ifdef sun +# undef sun +#endif + +// Work around for a NVCC compiler bug which causes it to fail +// to compile std::ratio_{multiply,divide} when used directly +// in the std::chrono::duration template instantiations below +namespace detail { +template +using ratio_multiply = decltype(std::ratio_multiply{}); + +template +using ratio_divide = decltype(std::ratio_divide{}); +} // namespace detail + +//-----------+ +// Interface | +//-----------+ + +// durations + +using days = std::chrono::duration + , std::chrono::hours::period>>; + +using weeks = std::chrono::duration + , days::period>>; + +using years = std::chrono::duration + , days::period>>; + +using months = std::chrono::duration + >>; + +// time_point + +template + using sys_time = std::chrono::time_point; + +using sys_days = sys_time; +using sys_seconds = sys_time; + +struct local_t {}; + +template + using local_time = std::chrono::time_point; + +using local_seconds = local_time; +using local_days = local_time; + +// types + +struct last_spec +{ + explicit last_spec() = default; +}; + +class day; +class month; +class year; + +class weekday; +class weekday_indexed; +class weekday_last; + +class month_day; +class month_day_last; +class month_weekday; +class month_weekday_last; + +class year_month; + +class year_month_day; +class year_month_day_last; +class year_month_weekday; +class year_month_weekday_last; + +// date composition operators + +CONSTCD11 year_month operator/(const year& y, const month& m) NOEXCEPT; +CONSTCD11 year_month operator/(const year& y, int m) NOEXCEPT; + +CONSTCD11 month_day operator/(const day& d, const month& m) NOEXCEPT; +CONSTCD11 month_day operator/(const day& d, int m) NOEXCEPT; +CONSTCD11 month_day operator/(const month& m, const day& d) NOEXCEPT; +CONSTCD11 month_day operator/(const month& m, int d) NOEXCEPT; +CONSTCD11 month_day operator/(int m, const day& d) NOEXCEPT; + +CONSTCD11 month_day_last operator/(const month& m, last_spec) NOEXCEPT; +CONSTCD11 month_day_last operator/(int m, last_spec) NOEXCEPT; +CONSTCD11 month_day_last operator/(last_spec, const month& m) NOEXCEPT; +CONSTCD11 month_day_last operator/(last_spec, int m) NOEXCEPT; + +CONSTCD11 month_weekday operator/(const month& m, const weekday_indexed& wdi) NOEXCEPT; +CONSTCD11 month_weekday operator/(int m, const weekday_indexed& wdi) NOEXCEPT; +CONSTCD11 month_weekday operator/(const weekday_indexed& wdi, const month& m) NOEXCEPT; +CONSTCD11 month_weekday operator/(const weekday_indexed& wdi, int m) NOEXCEPT; + +CONSTCD11 month_weekday_last operator/(const month& m, const weekday_last& wdl) NOEXCEPT; +CONSTCD11 month_weekday_last operator/(int m, const weekday_last& wdl) NOEXCEPT; +CONSTCD11 month_weekday_last operator/(const weekday_last& wdl, const month& m) NOEXCEPT; +CONSTCD11 month_weekday_last operator/(const weekday_last& wdl, int m) NOEXCEPT; + +CONSTCD11 year_month_day operator/(const year_month& ym, const day& d) NOEXCEPT; +CONSTCD11 year_month_day operator/(const year_month& ym, int d) NOEXCEPT; +CONSTCD11 year_month_day operator/(const year& y, const month_day& md) NOEXCEPT; +CONSTCD11 year_month_day operator/(int y, const month_day& md) NOEXCEPT; +CONSTCD11 year_month_day operator/(const month_day& md, const year& y) NOEXCEPT; +CONSTCD11 year_month_day operator/(const month_day& md, int y) NOEXCEPT; + +CONSTCD11 + year_month_day_last operator/(const year_month& ym, last_spec) NOEXCEPT; +CONSTCD11 + year_month_day_last operator/(const year& y, const month_day_last& mdl) NOEXCEPT; +CONSTCD11 + year_month_day_last operator/(int y, const month_day_last& mdl) NOEXCEPT; +CONSTCD11 + year_month_day_last operator/(const month_day_last& mdl, const year& y) NOEXCEPT; +CONSTCD11 + year_month_day_last operator/(const month_day_last& mdl, int y) NOEXCEPT; + +CONSTCD11 +year_month_weekday +operator/(const year_month& ym, const weekday_indexed& wdi) NOEXCEPT; + +CONSTCD11 +year_month_weekday +operator/(const year& y, const month_weekday& mwd) NOEXCEPT; + +CONSTCD11 +year_month_weekday +operator/(int y, const month_weekday& mwd) NOEXCEPT; + +CONSTCD11 +year_month_weekday +operator/(const month_weekday& mwd, const year& y) NOEXCEPT; + +CONSTCD11 +year_month_weekday +operator/(const month_weekday& mwd, int y) NOEXCEPT; + +CONSTCD11 +year_month_weekday_last +operator/(const year_month& ym, const weekday_last& wdl) NOEXCEPT; + +CONSTCD11 +year_month_weekday_last +operator/(const year& y, const month_weekday_last& mwdl) NOEXCEPT; + +CONSTCD11 +year_month_weekday_last +operator/(int y, const month_weekday_last& mwdl) NOEXCEPT; + +CONSTCD11 +year_month_weekday_last +operator/(const month_weekday_last& mwdl, const year& y) NOEXCEPT; + +CONSTCD11 +year_month_weekday_last +operator/(const month_weekday_last& mwdl, int y) NOEXCEPT; + +// Detailed interface + +// day + +class day +{ + unsigned char d_; + +public: + day() = default; + explicit CONSTCD11 day(unsigned d) NOEXCEPT; + + CONSTCD14 day& operator++() NOEXCEPT; + CONSTCD14 day operator++(int) NOEXCEPT; + CONSTCD14 day& operator--() NOEXCEPT; + CONSTCD14 day operator--(int) NOEXCEPT; + + CONSTCD14 day& operator+=(const days& d) NOEXCEPT; + CONSTCD14 day& operator-=(const days& d) NOEXCEPT; + + CONSTCD11 explicit operator unsigned() const NOEXCEPT; + CONSTCD11 bool ok() const NOEXCEPT; +}; + +CONSTCD11 bool operator==(const day& x, const day& y) NOEXCEPT; +CONSTCD11 bool operator!=(const day& x, const day& y) NOEXCEPT; +CONSTCD11 bool operator< (const day& x, const day& y) NOEXCEPT; +CONSTCD11 bool operator> (const day& x, const day& y) NOEXCEPT; +CONSTCD11 bool operator<=(const day& x, const day& y) NOEXCEPT; +CONSTCD11 bool operator>=(const day& x, const day& y) NOEXCEPT; + +CONSTCD11 day operator+(const day& x, const days& y) NOEXCEPT; +CONSTCD11 day operator+(const days& x, const day& y) NOEXCEPT; +CONSTCD11 day operator-(const day& x, const days& y) NOEXCEPT; +CONSTCD11 days operator-(const day& x, const day& y) NOEXCEPT; + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const day& d); + +// month + +class month +{ + unsigned char m_; + +public: + month() = default; + explicit CONSTCD11 month(unsigned m) NOEXCEPT; + + CONSTCD14 month& operator++() NOEXCEPT; + CONSTCD14 month operator++(int) NOEXCEPT; + CONSTCD14 month& operator--() NOEXCEPT; + CONSTCD14 month operator--(int) NOEXCEPT; + + CONSTCD14 month& operator+=(const months& m) NOEXCEPT; + CONSTCD14 month& operator-=(const months& m) NOEXCEPT; + + CONSTCD11 explicit operator unsigned() const NOEXCEPT; + CONSTCD11 bool ok() const NOEXCEPT; +}; + +CONSTCD11 bool operator==(const month& x, const month& y) NOEXCEPT; +CONSTCD11 bool operator!=(const month& x, const month& y) NOEXCEPT; +CONSTCD11 bool operator< (const month& x, const month& y) NOEXCEPT; +CONSTCD11 bool operator> (const month& x, const month& y) NOEXCEPT; +CONSTCD11 bool operator<=(const month& x, const month& y) NOEXCEPT; +CONSTCD11 bool operator>=(const month& x, const month& y) NOEXCEPT; + +CONSTCD14 month operator+(const month& x, const months& y) NOEXCEPT; +CONSTCD14 month operator+(const months& x, const month& y) NOEXCEPT; +CONSTCD14 month operator-(const month& x, const months& y) NOEXCEPT; +CONSTCD14 months operator-(const month& x, const month& y) NOEXCEPT; + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const month& m); + +// year + +class year +{ + short y_; + +public: + year() = default; + explicit CONSTCD11 year(int y) NOEXCEPT; + + CONSTCD14 year& operator++() NOEXCEPT; + CONSTCD14 year operator++(int) NOEXCEPT; + CONSTCD14 year& operator--() NOEXCEPT; + CONSTCD14 year operator--(int) NOEXCEPT; + + CONSTCD14 year& operator+=(const years& y) NOEXCEPT; + CONSTCD14 year& operator-=(const years& y) NOEXCEPT; + + CONSTCD11 year operator-() const NOEXCEPT; + CONSTCD11 year operator+() const NOEXCEPT; + + CONSTCD11 bool is_leap() const NOEXCEPT; + + CONSTCD11 explicit operator int() const NOEXCEPT; + CONSTCD11 bool ok() const NOEXCEPT; + + static CONSTCD11 year min() NOEXCEPT { return year{-32767}; } + static CONSTCD11 year max() NOEXCEPT { return year{32767}; } +}; + +CONSTCD11 bool operator==(const year& x, const year& y) NOEXCEPT; +CONSTCD11 bool operator!=(const year& x, const year& y) NOEXCEPT; +CONSTCD11 bool operator< (const year& x, const year& y) NOEXCEPT; +CONSTCD11 bool operator> (const year& x, const year& y) NOEXCEPT; +CONSTCD11 bool operator<=(const year& x, const year& y) NOEXCEPT; +CONSTCD11 bool operator>=(const year& x, const year& y) NOEXCEPT; + +CONSTCD11 year operator+(const year& x, const years& y) NOEXCEPT; +CONSTCD11 year operator+(const years& x, const year& y) NOEXCEPT; +CONSTCD11 year operator-(const year& x, const years& y) NOEXCEPT; +CONSTCD11 years operator-(const year& x, const year& y) NOEXCEPT; + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const year& y); + +// weekday + +class weekday +{ + unsigned char wd_; +public: + weekday() = default; + explicit CONSTCD11 weekday(unsigned wd) NOEXCEPT; + CONSTCD14 weekday(const sys_days& dp) NOEXCEPT; + CONSTCD14 explicit weekday(const local_days& dp) NOEXCEPT; + + CONSTCD14 weekday& operator++() NOEXCEPT; + CONSTCD14 weekday operator++(int) NOEXCEPT; + CONSTCD14 weekday& operator--() NOEXCEPT; + CONSTCD14 weekday operator--(int) NOEXCEPT; + + CONSTCD14 weekday& operator+=(const days& d) NOEXCEPT; + CONSTCD14 weekday& operator-=(const days& d) NOEXCEPT; + + CONSTCD11 bool ok() const NOEXCEPT; + + CONSTCD11 unsigned c_encoding() const NOEXCEPT; + CONSTCD11 unsigned iso_encoding() const NOEXCEPT; + + CONSTCD11 weekday_indexed operator[](unsigned index) const NOEXCEPT; + CONSTCD11 weekday_last operator[](last_spec) const NOEXCEPT; + +private: + static CONSTCD14 unsigned char weekday_from_days(int z) NOEXCEPT; + + friend CONSTCD11 bool operator==(const weekday& x, const weekday& y) NOEXCEPT; + friend CONSTCD14 days operator-(const weekday& x, const weekday& y) NOEXCEPT; + friend CONSTCD14 weekday operator+(const weekday& x, const days& y) NOEXCEPT; + template + friend std::basic_ostream& + operator<<(std::basic_ostream& os, const weekday& wd); + friend class weekday_indexed; +}; + +CONSTCD11 bool operator==(const weekday& x, const weekday& y) NOEXCEPT; +CONSTCD11 bool operator!=(const weekday& x, const weekday& y) NOEXCEPT; + +CONSTCD14 weekday operator+(const weekday& x, const days& y) NOEXCEPT; +CONSTCD14 weekday operator+(const days& x, const weekday& y) NOEXCEPT; +CONSTCD14 weekday operator-(const weekday& x, const days& y) NOEXCEPT; +CONSTCD14 days operator-(const weekday& x, const weekday& y) NOEXCEPT; + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const weekday& wd); + +// weekday_indexed + +class weekday_indexed +{ + unsigned char wd_ : 4; + unsigned char index_ : 4; + +public: + weekday_indexed() = default; + CONSTCD11 weekday_indexed(const date::weekday& wd, unsigned index) NOEXCEPT; + + CONSTCD11 date::weekday weekday() const NOEXCEPT; + CONSTCD11 unsigned index() const NOEXCEPT; + CONSTCD11 bool ok() const NOEXCEPT; +}; + +CONSTCD11 bool operator==(const weekday_indexed& x, const weekday_indexed& y) NOEXCEPT; +CONSTCD11 bool operator!=(const weekday_indexed& x, const weekday_indexed& y) NOEXCEPT; + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const weekday_indexed& wdi); + +// weekday_last + +class weekday_last +{ + date::weekday wd_; + +public: + explicit CONSTCD11 weekday_last(const date::weekday& wd) NOEXCEPT; + + CONSTCD11 date::weekday weekday() const NOEXCEPT; + CONSTCD11 bool ok() const NOEXCEPT; +}; + +CONSTCD11 bool operator==(const weekday_last& x, const weekday_last& y) NOEXCEPT; +CONSTCD11 bool operator!=(const weekday_last& x, const weekday_last& y) NOEXCEPT; + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const weekday_last& wdl); + +namespace detail +{ + +struct unspecified_month_disambiguator {}; + +} // namespace detail + +// year_month + +class year_month +{ + date::year y_; + date::month m_; + +public: + year_month() = default; + CONSTCD11 year_month(const date::year& y, const date::month& m) NOEXCEPT; + + CONSTCD11 date::year year() const NOEXCEPT; + CONSTCD11 date::month month() const NOEXCEPT; + + template + CONSTCD14 year_month& operator+=(const months& dm) NOEXCEPT; + template + CONSTCD14 year_month& operator-=(const months& dm) NOEXCEPT; + CONSTCD14 year_month& operator+=(const years& dy) NOEXCEPT; + CONSTCD14 year_month& operator-=(const years& dy) NOEXCEPT; + + CONSTCD11 bool ok() const NOEXCEPT; +}; + +CONSTCD11 bool operator==(const year_month& x, const year_month& y) NOEXCEPT; +CONSTCD11 bool operator!=(const year_month& x, const year_month& y) NOEXCEPT; +CONSTCD11 bool operator< (const year_month& x, const year_month& y) NOEXCEPT; +CONSTCD11 bool operator> (const year_month& x, const year_month& y) NOEXCEPT; +CONSTCD11 bool operator<=(const year_month& x, const year_month& y) NOEXCEPT; +CONSTCD11 bool operator>=(const year_month& x, const year_month& y) NOEXCEPT; + +template +CONSTCD14 year_month operator+(const year_month& ym, const months& dm) NOEXCEPT; +template +CONSTCD14 year_month operator+(const months& dm, const year_month& ym) NOEXCEPT; +template +CONSTCD14 year_month operator-(const year_month& ym, const months& dm) NOEXCEPT; + +CONSTCD11 months operator-(const year_month& x, const year_month& y) NOEXCEPT; +CONSTCD11 year_month operator+(const year_month& ym, const years& dy) NOEXCEPT; +CONSTCD11 year_month operator+(const years& dy, const year_month& ym) NOEXCEPT; +CONSTCD11 year_month operator-(const year_month& ym, const years& dy) NOEXCEPT; + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const year_month& ym); + +// month_day + +class month_day +{ + date::month m_; + date::day d_; + +public: + month_day() = default; + CONSTCD11 month_day(const date::month& m, const date::day& d) NOEXCEPT; + + CONSTCD11 date::month month() const NOEXCEPT; + CONSTCD11 date::day day() const NOEXCEPT; + + CONSTCD14 bool ok() const NOEXCEPT; +}; + +CONSTCD11 bool operator==(const month_day& x, const month_day& y) NOEXCEPT; +CONSTCD11 bool operator!=(const month_day& x, const month_day& y) NOEXCEPT; +CONSTCD11 bool operator< (const month_day& x, const month_day& y) NOEXCEPT; +CONSTCD11 bool operator> (const month_day& x, const month_day& y) NOEXCEPT; +CONSTCD11 bool operator<=(const month_day& x, const month_day& y) NOEXCEPT; +CONSTCD11 bool operator>=(const month_day& x, const month_day& y) NOEXCEPT; + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const month_day& md); + +// month_day_last + +class month_day_last +{ + date::month m_; + +public: + CONSTCD11 explicit month_day_last(const date::month& m) NOEXCEPT; + + CONSTCD11 date::month month() const NOEXCEPT; + CONSTCD11 bool ok() const NOEXCEPT; +}; + +CONSTCD11 bool operator==(const month_day_last& x, const month_day_last& y) NOEXCEPT; +CONSTCD11 bool operator!=(const month_day_last& x, const month_day_last& y) NOEXCEPT; +CONSTCD11 bool operator< (const month_day_last& x, const month_day_last& y) NOEXCEPT; +CONSTCD11 bool operator> (const month_day_last& x, const month_day_last& y) NOEXCEPT; +CONSTCD11 bool operator<=(const month_day_last& x, const month_day_last& y) NOEXCEPT; +CONSTCD11 bool operator>=(const month_day_last& x, const month_day_last& y) NOEXCEPT; + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const month_day_last& mdl); + +// month_weekday + +class month_weekday +{ + date::month m_; + date::weekday_indexed wdi_; +public: + CONSTCD11 month_weekday(const date::month& m, + const date::weekday_indexed& wdi) NOEXCEPT; + + CONSTCD11 date::month month() const NOEXCEPT; + CONSTCD11 date::weekday_indexed weekday_indexed() const NOEXCEPT; + + CONSTCD11 bool ok() const NOEXCEPT; +}; + +CONSTCD11 bool operator==(const month_weekday& x, const month_weekday& y) NOEXCEPT; +CONSTCD11 bool operator!=(const month_weekday& x, const month_weekday& y) NOEXCEPT; + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const month_weekday& mwd); + +// month_weekday_last + +class month_weekday_last +{ + date::month m_; + date::weekday_last wdl_; + +public: + CONSTCD11 month_weekday_last(const date::month& m, + const date::weekday_last& wd) NOEXCEPT; + + CONSTCD11 date::month month() const NOEXCEPT; + CONSTCD11 date::weekday_last weekday_last() const NOEXCEPT; + + CONSTCD11 bool ok() const NOEXCEPT; +}; + +CONSTCD11 + bool operator==(const month_weekday_last& x, const month_weekday_last& y) NOEXCEPT; +CONSTCD11 + bool operator!=(const month_weekday_last& x, const month_weekday_last& y) NOEXCEPT; + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const month_weekday_last& mwdl); + +// class year_month_day + +class year_month_day +{ + date::year y_; + date::month m_; + date::day d_; + +public: + year_month_day() = default; + CONSTCD11 year_month_day(const date::year& y, const date::month& m, + const date::day& d) NOEXCEPT; + CONSTCD14 year_month_day(const year_month_day_last& ymdl) NOEXCEPT; + + CONSTCD14 year_month_day(sys_days dp) NOEXCEPT; + CONSTCD14 explicit year_month_day(local_days dp) NOEXCEPT; + + template + CONSTCD14 year_month_day& operator+=(const months& m) NOEXCEPT; + template + CONSTCD14 year_month_day& operator-=(const months& m) NOEXCEPT; + CONSTCD14 year_month_day& operator+=(const years& y) NOEXCEPT; + CONSTCD14 year_month_day& operator-=(const years& y) NOEXCEPT; + + CONSTCD11 date::year year() const NOEXCEPT; + CONSTCD11 date::month month() const NOEXCEPT; + CONSTCD11 date::day day() const NOEXCEPT; + + CONSTCD14 operator sys_days() const NOEXCEPT; + CONSTCD14 explicit operator local_days() const NOEXCEPT; + CONSTCD14 bool ok() const NOEXCEPT; + +private: + static CONSTCD14 year_month_day from_days(days dp) NOEXCEPT; + CONSTCD14 days to_days() const NOEXCEPT; +}; + +CONSTCD11 bool operator==(const year_month_day& x, const year_month_day& y) NOEXCEPT; +CONSTCD11 bool operator!=(const year_month_day& x, const year_month_day& y) NOEXCEPT; +CONSTCD11 bool operator< (const year_month_day& x, const year_month_day& y) NOEXCEPT; +CONSTCD11 bool operator> (const year_month_day& x, const year_month_day& y) NOEXCEPT; +CONSTCD11 bool operator<=(const year_month_day& x, const year_month_day& y) NOEXCEPT; +CONSTCD11 bool operator>=(const year_month_day& x, const year_month_day& y) NOEXCEPT; + +template +CONSTCD14 year_month_day operator+(const year_month_day& ymd, const months& dm) NOEXCEPT; +template +CONSTCD14 year_month_day operator+(const months& dm, const year_month_day& ymd) NOEXCEPT; +template +CONSTCD14 year_month_day operator-(const year_month_day& ymd, const months& dm) NOEXCEPT; +CONSTCD11 year_month_day operator+(const year_month_day& ymd, const years& dy) NOEXCEPT; +CONSTCD11 year_month_day operator+(const years& dy, const year_month_day& ymd) NOEXCEPT; +CONSTCD11 year_month_day operator-(const year_month_day& ymd, const years& dy) NOEXCEPT; + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const year_month_day& ymd); + +// year_month_day_last + +class year_month_day_last +{ + date::year y_; + date::month_day_last mdl_; + +public: + CONSTCD11 year_month_day_last(const date::year& y, + const date::month_day_last& mdl) NOEXCEPT; + + template + CONSTCD14 year_month_day_last& operator+=(const months& m) NOEXCEPT; + template + CONSTCD14 year_month_day_last& operator-=(const months& m) NOEXCEPT; + CONSTCD14 year_month_day_last& operator+=(const years& y) NOEXCEPT; + CONSTCD14 year_month_day_last& operator-=(const years& y) NOEXCEPT; + + CONSTCD11 date::year year() const NOEXCEPT; + CONSTCD11 date::month month() const NOEXCEPT; + CONSTCD11 date::month_day_last month_day_last() const NOEXCEPT; + CONSTCD14 date::day day() const NOEXCEPT; + + CONSTCD14 operator sys_days() const NOEXCEPT; + CONSTCD14 explicit operator local_days() const NOEXCEPT; + CONSTCD11 bool ok() const NOEXCEPT; +}; + +CONSTCD11 + bool operator==(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT; +CONSTCD11 + bool operator!=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT; +CONSTCD11 + bool operator< (const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT; +CONSTCD11 + bool operator> (const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT; +CONSTCD11 + bool operator<=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT; +CONSTCD11 + bool operator>=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT; + +template +CONSTCD14 +year_month_day_last +operator+(const year_month_day_last& ymdl, const months& dm) NOEXCEPT; + +template +CONSTCD14 +year_month_day_last +operator+(const months& dm, const year_month_day_last& ymdl) NOEXCEPT; + +CONSTCD11 +year_month_day_last +operator+(const year_month_day_last& ymdl, const years& dy) NOEXCEPT; + +CONSTCD11 +year_month_day_last +operator+(const years& dy, const year_month_day_last& ymdl) NOEXCEPT; + +template +CONSTCD14 +year_month_day_last +operator-(const year_month_day_last& ymdl, const months& dm) NOEXCEPT; + +CONSTCD11 +year_month_day_last +operator-(const year_month_day_last& ymdl, const years& dy) NOEXCEPT; + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const year_month_day_last& ymdl); + +// year_month_weekday + +class year_month_weekday +{ + date::year y_; + date::month m_; + date::weekday_indexed wdi_; + +public: + year_month_weekday() = default; + CONSTCD11 year_month_weekday(const date::year& y, const date::month& m, + const date::weekday_indexed& wdi) NOEXCEPT; + CONSTCD14 year_month_weekday(const sys_days& dp) NOEXCEPT; + CONSTCD14 explicit year_month_weekday(const local_days& dp) NOEXCEPT; + + template + CONSTCD14 year_month_weekday& operator+=(const months& m) NOEXCEPT; + template + CONSTCD14 year_month_weekday& operator-=(const months& m) NOEXCEPT; + CONSTCD14 year_month_weekday& operator+=(const years& y) NOEXCEPT; + CONSTCD14 year_month_weekday& operator-=(const years& y) NOEXCEPT; + + CONSTCD11 date::year year() const NOEXCEPT; + CONSTCD11 date::month month() const NOEXCEPT; + CONSTCD11 date::weekday weekday() const NOEXCEPT; + CONSTCD11 unsigned index() const NOEXCEPT; + CONSTCD11 date::weekday_indexed weekday_indexed() const NOEXCEPT; + + CONSTCD14 operator sys_days() const NOEXCEPT; + CONSTCD14 explicit operator local_days() const NOEXCEPT; + CONSTCD14 bool ok() const NOEXCEPT; + +private: + static CONSTCD14 year_month_weekday from_days(days dp) NOEXCEPT; + CONSTCD14 days to_days() const NOEXCEPT; +}; + +CONSTCD11 + bool operator==(const year_month_weekday& x, const year_month_weekday& y) NOEXCEPT; +CONSTCD11 + bool operator!=(const year_month_weekday& x, const year_month_weekday& y) NOEXCEPT; + +template +CONSTCD14 +year_month_weekday +operator+(const year_month_weekday& ymwd, const months& dm) NOEXCEPT; + +template +CONSTCD14 +year_month_weekday +operator+(const months& dm, const year_month_weekday& ymwd) NOEXCEPT; + +CONSTCD11 +year_month_weekday +operator+(const year_month_weekday& ymwd, const years& dy) NOEXCEPT; + +CONSTCD11 +year_month_weekday +operator+(const years& dy, const year_month_weekday& ymwd) NOEXCEPT; + +template +CONSTCD14 +year_month_weekday +operator-(const year_month_weekday& ymwd, const months& dm) NOEXCEPT; + +CONSTCD11 +year_month_weekday +operator-(const year_month_weekday& ymwd, const years& dy) NOEXCEPT; + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const year_month_weekday& ymwdi); + +// year_month_weekday_last + +class year_month_weekday_last +{ + date::year y_; + date::month m_; + date::weekday_last wdl_; + +public: + CONSTCD11 year_month_weekday_last(const date::year& y, const date::month& m, + const date::weekday_last& wdl) NOEXCEPT; + + template + CONSTCD14 year_month_weekday_last& operator+=(const months& m) NOEXCEPT; + template + CONSTCD14 year_month_weekday_last& operator-=(const months& m) NOEXCEPT; + CONSTCD14 year_month_weekday_last& operator+=(const years& y) NOEXCEPT; + CONSTCD14 year_month_weekday_last& operator-=(const years& y) NOEXCEPT; + + CONSTCD11 date::year year() const NOEXCEPT; + CONSTCD11 date::month month() const NOEXCEPT; + CONSTCD11 date::weekday weekday() const NOEXCEPT; + CONSTCD11 date::weekday_last weekday_last() const NOEXCEPT; + + CONSTCD14 operator sys_days() const NOEXCEPT; + CONSTCD14 explicit operator local_days() const NOEXCEPT; + CONSTCD11 bool ok() const NOEXCEPT; + +private: + CONSTCD14 days to_days() const NOEXCEPT; +}; + +CONSTCD11 +bool +operator==(const year_month_weekday_last& x, const year_month_weekday_last& y) NOEXCEPT; + +CONSTCD11 +bool +operator!=(const year_month_weekday_last& x, const year_month_weekday_last& y) NOEXCEPT; + +template +CONSTCD14 +year_month_weekday_last +operator+(const year_month_weekday_last& ymwdl, const months& dm) NOEXCEPT; + +template +CONSTCD14 +year_month_weekday_last +operator+(const months& dm, const year_month_weekday_last& ymwdl) NOEXCEPT; + +CONSTCD11 +year_month_weekday_last +operator+(const year_month_weekday_last& ymwdl, const years& dy) NOEXCEPT; + +CONSTCD11 +year_month_weekday_last +operator+(const years& dy, const year_month_weekday_last& ymwdl) NOEXCEPT; + +template +CONSTCD14 +year_month_weekday_last +operator-(const year_month_weekday_last& ymwdl, const months& dm) NOEXCEPT; + +CONSTCD11 +year_month_weekday_last +operator-(const year_month_weekday_last& ymwdl, const years& dy) NOEXCEPT; + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const year_month_weekday_last& ymwdl); + +#if !defined(_MSC_VER) || (_MSC_VER >= 1900) +inline namespace literals +{ + +CONSTCD11 date::day operator "" _d(unsigned long long d) NOEXCEPT; +CONSTCD11 date::year operator "" _y(unsigned long long y) NOEXCEPT; + +} // inline namespace literals +#endif // !defined(_MSC_VER) || (_MSC_VER >= 1900) + +// CONSTDATA date::month January{1}; +// CONSTDATA date::month February{2}; +// CONSTDATA date::month March{3}; +// CONSTDATA date::month April{4}; +// CONSTDATA date::month May{5}; +// CONSTDATA date::month June{6}; +// CONSTDATA date::month July{7}; +// CONSTDATA date::month August{8}; +// CONSTDATA date::month September{9}; +// CONSTDATA date::month October{10}; +// CONSTDATA date::month November{11}; +// CONSTDATA date::month December{12}; +// +// CONSTDATA date::weekday Sunday{0u}; +// CONSTDATA date::weekday Monday{1u}; +// CONSTDATA date::weekday Tuesday{2u}; +// CONSTDATA date::weekday Wednesday{3u}; +// CONSTDATA date::weekday Thursday{4u}; +// CONSTDATA date::weekday Friday{5u}; +// CONSTDATA date::weekday Saturday{6u}; + +#if HAS_VOID_T + +template > +struct is_clock + : std::false_type +{}; + +template +struct is_clock> + : std::true_type +{}; + +template inline constexpr bool is_clock_v = is_clock::value; + +#endif // HAS_VOID_T + +//----------------+ +// Implementation | +//----------------+ + +// utilities +namespace detail { + +template> +class save_istream +{ +protected: + std::basic_ios& is_; + CharT fill_; + std::ios::fmtflags flags_; + std::streamsize precision_; + std::streamsize width_; + std::basic_ostream* tie_; + std::locale loc_; + +public: + ~save_istream() + { + is_.fill(fill_); + is_.flags(flags_); + is_.precision(precision_); + is_.width(width_); + is_.imbue(loc_); + is_.tie(tie_); + } + + save_istream(const save_istream&) = delete; + save_istream& operator=(const save_istream&) = delete; + + explicit save_istream(std::basic_ios& is) + : is_(is) + , fill_(is.fill()) + , flags_(is.flags()) + , precision_(is.precision()) + , width_(is.width(0)) + , tie_(is.tie(nullptr)) + , loc_(is.getloc()) + { + if (tie_ != nullptr) + tie_->flush(); + } +}; + +template> +class save_ostream + : private save_istream +{ +public: + ~save_ostream() + { + if ((this->flags_ & std::ios::unitbuf) && +#if HAS_UNCAUGHT_EXCEPTIONS + std::uncaught_exceptions() == 0 && +#else + !std::uncaught_exception() && +#endif + this->is_.good()) + this->is_.rdbuf()->pubsync(); + } + + save_ostream(const save_ostream&) = delete; + save_ostream& operator=(const save_ostream&) = delete; + + explicit save_ostream(std::basic_ios& os) + : save_istream(os) + { + } +}; + +template +struct choose_trunc_type +{ + static const int digits = std::numeric_limits::digits; + using type = typename std::conditional + < + digits < 32, + std::int32_t, + typename std::conditional + < + digits < 64, + std::int64_t, +#ifdef __SIZEOF_INT128__ + __int128 +#else + std::int64_t +#endif + >::type + >::type; +}; + +template +CONSTCD11 +inline +typename std::enable_if +< + !std::chrono::treat_as_floating_point::value, + T +>::type +trunc(T t) NOEXCEPT +{ + return t; +} + +template +CONSTCD14 +inline +typename std::enable_if +< + std::chrono::treat_as_floating_point::value, + T +>::type +trunc(T t) NOEXCEPT +{ + using std::numeric_limits; + using I = typename choose_trunc_type::type; + CONSTDATA auto digits = numeric_limits::digits; + static_assert(digits < numeric_limits::digits, ""); + CONSTDATA auto max = I{1} << (digits-1); + CONSTDATA auto min = -max; + const auto negative = t < T{0}; + if (min <= t && t <= max && t != 0 && t == t) + { + t = static_cast(static_cast(t)); + if (t == 0 && negative) + t = -t; + } + return t; +} + +template +struct static_gcd +{ + static const std::intmax_t value = static_gcd::value; +}; + +template +struct static_gcd +{ + static const std::intmax_t value = Xp; +}; + +template <> +struct static_gcd<0, 0> +{ + static const std::intmax_t value = 1; +}; + +template +struct no_overflow +{ +private: + static const std::intmax_t gcd_n1_n2 = static_gcd::value; + static const std::intmax_t gcd_d1_d2 = static_gcd::value; + static const std::intmax_t n1 = R1::num / gcd_n1_n2; + static const std::intmax_t d1 = R1::den / gcd_d1_d2; + static const std::intmax_t n2 = R2::num / gcd_n1_n2; + static const std::intmax_t d2 = R2::den / gcd_d1_d2; +#ifdef __cpp_constexpr + static const std::intmax_t max = std::numeric_limits::max(); +#else + static const std::intmax_t max = LLONG_MAX; +#endif + + template + struct mul // overflow == false + { + static const std::intmax_t value = Xp * Yp; + }; + + template + struct mul + { + static const std::intmax_t value = 1; + }; + +public: + static const bool value = (n1 <= max / d2) && (n2 <= max / d1); + typedef std::ratio::value, + mul::value> type; +}; + +} // detail + +// trunc towards zero +template +CONSTCD11 +inline +typename std::enable_if +< + detail::no_overflow::value, + To +>::type +trunc(const std::chrono::duration& d) +{ + return To{detail::trunc(std::chrono::duration_cast(d).count())}; +} + +template +CONSTCD11 +inline +typename std::enable_if +< + !detail::no_overflow::value, + To +>::type +trunc(const std::chrono::duration& d) +{ + using std::chrono::duration_cast; + using std::chrono::duration; + using rep = typename std::common_type::type; + return To{detail::trunc(duration_cast(duration_cast>(d)).count())}; +} + +#ifndef HAS_CHRONO_ROUNDING +# if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 190023918 || (_MSC_FULL_VER >= 190000000 && defined (__clang__))) +# define HAS_CHRONO_ROUNDING 1 +# elif defined(__cpp_lib_chrono) && __cplusplus > 201402 && __cpp_lib_chrono >= 201510 +# define HAS_CHRONO_ROUNDING 1 +# elif defined(_LIBCPP_VERSION) && __cplusplus > 201402 && _LIBCPP_VERSION >= 3800 +# define HAS_CHRONO_ROUNDING 1 +# else +# define HAS_CHRONO_ROUNDING 0 +# endif +#endif // HAS_CHRONO_ROUNDING + +#if HAS_CHRONO_ROUNDING == 0 + +// round down +template +CONSTCD14 +inline +typename std::enable_if +< + detail::no_overflow::value, + To +>::type +floor(const std::chrono::duration& d) +{ + auto t = trunc(d); + if (t > d) + return t - To{1}; + return t; +} + +template +CONSTCD14 +inline +typename std::enable_if +< + !detail::no_overflow::value, + To +>::type +floor(const std::chrono::duration& d) +{ + using rep = typename std::common_type::type; + return floor(floor>(d)); +} + +// round to nearest, to even on tie +template +CONSTCD14 +inline +To +round(const std::chrono::duration& d) +{ + auto t0 = floor(d); + auto t1 = t0 + To{1}; + if (t1 == To{0} && t0 < To{0}) + t1 = -t1; + auto diff0 = d - t0; + auto diff1 = t1 - d; + if (diff0 == diff1) + { + if (t0 - trunc(t0/2)*2 == To{0}) + return t0; + return t1; + } + if (diff0 < diff1) + return t0; + return t1; +} + +// round up +template +CONSTCD14 +inline +To +ceil(const std::chrono::duration& d) +{ + auto t = trunc(d); + if (t < d) + return t + To{1}; + return t; +} + +template ::is_signed + >::type> +CONSTCD11 +std::chrono::duration +abs(std::chrono::duration d) +{ + return d >= d.zero() ? d : static_cast(-d); +} + +// round down +template +CONSTCD11 +inline +std::chrono::time_point +floor(const std::chrono::time_point& tp) +{ + using std::chrono::time_point; + return time_point{date::floor(tp.time_since_epoch())}; +} + +// round to nearest, to even on tie +template +CONSTCD11 +inline +std::chrono::time_point +round(const std::chrono::time_point& tp) +{ + using std::chrono::time_point; + return time_point{round(tp.time_since_epoch())}; +} + +// round up +template +CONSTCD11 +inline +std::chrono::time_point +ceil(const std::chrono::time_point& tp) +{ + using std::chrono::time_point; + return time_point{ceil(tp.time_since_epoch())}; +} + +#else // HAS_CHRONO_ROUNDING == 1 + +using std::chrono::floor; +using std::chrono::ceil; +using std::chrono::round; +using std::chrono::abs; + +#endif // HAS_CHRONO_ROUNDING + +namespace detail +{ + +template +CONSTCD14 +inline +typename std::enable_if +< + !std::chrono::treat_as_floating_point::value, + To +>::type +round_i(const std::chrono::duration& d) +{ + return round(d); +} + +template +CONSTCD14 +inline +typename std::enable_if +< + std::chrono::treat_as_floating_point::value, + To +>::type +round_i(const std::chrono::duration& d) +{ + return d; +} + +template +CONSTCD11 +inline +std::chrono::time_point +round_i(const std::chrono::time_point& tp) +{ + using std::chrono::time_point; + return time_point{round_i(tp.time_since_epoch())}; +} + +} // detail + +// trunc towards zero +template +CONSTCD11 +inline +std::chrono::time_point +trunc(const std::chrono::time_point& tp) +{ + using std::chrono::time_point; + return time_point{trunc(tp.time_since_epoch())}; +} + +// day + +CONSTCD11 inline day::day(unsigned d) NOEXCEPT : d_(static_cast(d)) {} +CONSTCD14 inline day& day::operator++() NOEXCEPT {++d_; return *this;} +CONSTCD14 inline day day::operator++(int) NOEXCEPT {auto tmp(*this); ++(*this); return tmp;} +CONSTCD14 inline day& day::operator--() NOEXCEPT {--d_; return *this;} +CONSTCD14 inline day day::operator--(int) NOEXCEPT {auto tmp(*this); --(*this); return tmp;} +CONSTCD14 inline day& day::operator+=(const days& d) NOEXCEPT {*this = *this + d; return *this;} +CONSTCD14 inline day& day::operator-=(const days& d) NOEXCEPT {*this = *this - d; return *this;} +CONSTCD11 inline day::operator unsigned() const NOEXCEPT {return d_;} +CONSTCD11 inline bool day::ok() const NOEXCEPT {return 1 <= d_ && d_ <= 31;} + +CONSTCD11 +inline +bool +operator==(const day& x, const day& y) NOEXCEPT +{ + return static_cast(x) == static_cast(y); +} + +CONSTCD11 +inline +bool +operator!=(const day& x, const day& y) NOEXCEPT +{ + return !(x == y); +} + +CONSTCD11 +inline +bool +operator<(const day& x, const day& y) NOEXCEPT +{ + return static_cast(x) < static_cast(y); +} + +CONSTCD11 +inline +bool +operator>(const day& x, const day& y) NOEXCEPT +{ + return y < x; +} + +CONSTCD11 +inline +bool +operator<=(const day& x, const day& y) NOEXCEPT +{ + return !(y < x); +} + +CONSTCD11 +inline +bool +operator>=(const day& x, const day& y) NOEXCEPT +{ + return !(x < y); +} + +CONSTCD11 +inline +days +operator-(const day& x, const day& y) NOEXCEPT +{ + return days{static_cast(static_cast(x) + - static_cast(y))}; +} + +CONSTCD11 +inline +day +operator+(const day& x, const days& y) NOEXCEPT +{ + return day{static_cast(x) + static_cast(y.count())}; +} + +CONSTCD11 +inline +day +operator+(const days& x, const day& y) NOEXCEPT +{ + return y + x; +} + +CONSTCD11 +inline +day +operator-(const day& x, const days& y) NOEXCEPT +{ + return x + -y; +} + +namespace detail +{ + +template +std::basic_ostream& +low_level_fmt(std::basic_ostream& os, const day& d) +{ + detail::save_ostream _(os); + os.fill('0'); + os.flags(std::ios::dec | std::ios::right); + os.width(2); + os << static_cast(d); + return os; +} + +} // namespace detail + +template +inline +std::basic_ostream& +operator<<(std::basic_ostream& os, const day& d) +{ + detail::low_level_fmt(os, d); + if (!d.ok()) + os << " is not a valid day"; + return os; +} + +// month + +CONSTCD11 inline month::month(unsigned m) NOEXCEPT : m_(static_cast(m)) {} +CONSTCD14 inline month& month::operator++() NOEXCEPT {*this += months{1}; return *this;} +CONSTCD14 inline month month::operator++(int) NOEXCEPT {auto tmp(*this); ++(*this); return tmp;} +CONSTCD14 inline month& month::operator--() NOEXCEPT {*this -= months{1}; return *this;} +CONSTCD14 inline month month::operator--(int) NOEXCEPT {auto tmp(*this); --(*this); return tmp;} + +CONSTCD14 +inline +month& +month::operator+=(const months& m) NOEXCEPT +{ + *this = *this + m; + return *this; +} + +CONSTCD14 +inline +month& +month::operator-=(const months& m) NOEXCEPT +{ + *this = *this - m; + return *this; +} + +CONSTCD11 inline month::operator unsigned() const NOEXCEPT {return m_;} +CONSTCD11 inline bool month::ok() const NOEXCEPT {return 1 <= m_ && m_ <= 12;} + +CONSTCD11 +inline +bool +operator==(const month& x, const month& y) NOEXCEPT +{ + return static_cast(x) == static_cast(y); +} + +CONSTCD11 +inline +bool +operator!=(const month& x, const month& y) NOEXCEPT +{ + return !(x == y); +} + +CONSTCD11 +inline +bool +operator<(const month& x, const month& y) NOEXCEPT +{ + return static_cast(x) < static_cast(y); +} + +CONSTCD11 +inline +bool +operator>(const month& x, const month& y) NOEXCEPT +{ + return y < x; +} + +CONSTCD11 +inline +bool +operator<=(const month& x, const month& y) NOEXCEPT +{ + return !(y < x); +} + +CONSTCD11 +inline +bool +operator>=(const month& x, const month& y) NOEXCEPT +{ + return !(x < y); +} + +CONSTCD14 +inline +months +operator-(const month& x, const month& y) NOEXCEPT +{ + auto const d = static_cast(x) - static_cast(y); + return months(d <= 11 ? d : d + 12); +} + +CONSTCD14 +inline +month +operator+(const month& x, const months& y) NOEXCEPT +{ + auto const mu = static_cast(static_cast(x)) + y.count() - 1; + auto const yr = (mu >= 0 ? mu : mu-11) / 12; + return month{static_cast(mu - yr * 12 + 1)}; +} + +CONSTCD14 +inline +month +operator+(const months& x, const month& y) NOEXCEPT +{ + return y + x; +} + +CONSTCD14 +inline +month +operator-(const month& x, const months& y) NOEXCEPT +{ + return x + -y; +} + +namespace detail +{ + +template +std::basic_ostream& +low_level_fmt(std::basic_ostream& os, const month& m) +{ + if (m.ok()) + { + CharT fmt[] = {'%', 'b', 0}; + os << format(os.getloc(), fmt, m); + } + else + os << static_cast(m); + return os; +} + +} // namespace detail + +template +inline +std::basic_ostream& +operator<<(std::basic_ostream& os, const month& m) +{ + detail::low_level_fmt(os, m); + if (!m.ok()) + os << " is not a valid month"; + return os; +} + +// year + +CONSTCD11 inline year::year(int y) NOEXCEPT : y_(static_cast(y)) {} +CONSTCD14 inline year& year::operator++() NOEXCEPT {++y_; return *this;} +CONSTCD14 inline year year::operator++(int) NOEXCEPT {auto tmp(*this); ++(*this); return tmp;} +CONSTCD14 inline year& year::operator--() NOEXCEPT {--y_; return *this;} +CONSTCD14 inline year year::operator--(int) NOEXCEPT {auto tmp(*this); --(*this); return tmp;} +CONSTCD14 inline year& year::operator+=(const years& y) NOEXCEPT {*this = *this + y; return *this;} +CONSTCD14 inline year& year::operator-=(const years& y) NOEXCEPT {*this = *this - y; return *this;} +CONSTCD11 inline year year::operator-() const NOEXCEPT {return year{-y_};} +CONSTCD11 inline year year::operator+() const NOEXCEPT {return *this;} + +CONSTCD11 +inline +bool +year::is_leap() const NOEXCEPT +{ + return y_ % 4 == 0 && (y_ % 100 != 0 || y_ % 400 == 0); +} + +CONSTCD11 inline year::operator int() const NOEXCEPT {return y_;} + +CONSTCD11 +inline +bool +year::ok() const NOEXCEPT +{ + return y_ != std::numeric_limits::min(); +} + +CONSTCD11 +inline +bool +operator==(const year& x, const year& y) NOEXCEPT +{ + return static_cast(x) == static_cast(y); +} + +CONSTCD11 +inline +bool +operator!=(const year& x, const year& y) NOEXCEPT +{ + return !(x == y); +} + +CONSTCD11 +inline +bool +operator<(const year& x, const year& y) NOEXCEPT +{ + return static_cast(x) < static_cast(y); +} + +CONSTCD11 +inline +bool +operator>(const year& x, const year& y) NOEXCEPT +{ + return y < x; +} + +CONSTCD11 +inline +bool +operator<=(const year& x, const year& y) NOEXCEPT +{ + return !(y < x); +} + +CONSTCD11 +inline +bool +operator>=(const year& x, const year& y) NOEXCEPT +{ + return !(x < y); +} + +CONSTCD11 +inline +years +operator-(const year& x, const year& y) NOEXCEPT +{ + return years{static_cast(x) - static_cast(y)}; +} + +CONSTCD11 +inline +year +operator+(const year& x, const years& y) NOEXCEPT +{ + return year{static_cast(x) + y.count()}; +} + +CONSTCD11 +inline +year +operator+(const years& x, const year& y) NOEXCEPT +{ + return y + x; +} + +CONSTCD11 +inline +year +operator-(const year& x, const years& y) NOEXCEPT +{ + return year{static_cast(x) - y.count()}; +} + +namespace detail +{ + +template +std::basic_ostream& +low_level_fmt(std::basic_ostream& os, const year& y) +{ + detail::save_ostream _(os); + os.fill('0'); + os.flags(std::ios::dec | std::ios::internal); + os.width(4 + (y < year{0})); + os.imbue(std::locale::classic()); + os << static_cast(y); + return os; +} + +} // namespace detail + +template +inline +std::basic_ostream& +operator<<(std::basic_ostream& os, const year& y) +{ + detail::low_level_fmt(os, y); + if (!y.ok()) + os << " is not a valid year"; + return os; +} + +// weekday + +CONSTCD14 +inline +unsigned char +weekday::weekday_from_days(int z) NOEXCEPT +{ + auto u = static_cast(z); + return static_cast(z >= -4 ? (u+4) % 7 : u % 7); +} + +CONSTCD11 +inline +weekday::weekday(unsigned wd) NOEXCEPT + : wd_(static_cast(wd != 7 ? wd : 0)) + {} + +CONSTCD14 +inline +weekday::weekday(const sys_days& dp) NOEXCEPT + : wd_(weekday_from_days(dp.time_since_epoch().count())) + {} + +CONSTCD14 +inline +weekday::weekday(const local_days& dp) NOEXCEPT + : wd_(weekday_from_days(dp.time_since_epoch().count())) + {} + +CONSTCD14 inline weekday& weekday::operator++() NOEXCEPT {*this += days{1}; return *this;} +CONSTCD14 inline weekday weekday::operator++(int) NOEXCEPT {auto tmp(*this); ++(*this); return tmp;} +CONSTCD14 inline weekday& weekday::operator--() NOEXCEPT {*this -= days{1}; return *this;} +CONSTCD14 inline weekday weekday::operator--(int) NOEXCEPT {auto tmp(*this); --(*this); return tmp;} + +CONSTCD14 +inline +weekday& +weekday::operator+=(const days& d) NOEXCEPT +{ + *this = *this + d; + return *this; +} + +CONSTCD14 +inline +weekday& +weekday::operator-=(const days& d) NOEXCEPT +{ + *this = *this - d; + return *this; +} + +CONSTCD11 inline bool weekday::ok() const NOEXCEPT {return wd_ <= 6;} + +CONSTCD11 +inline +unsigned weekday::c_encoding() const NOEXCEPT +{ + return unsigned{wd_}; +} + +CONSTCD11 +inline +unsigned weekday::iso_encoding() const NOEXCEPT +{ + return unsigned{((wd_ == 0u) ? 7u : wd_)}; +} + +CONSTCD11 +inline +bool +operator==(const weekday& x, const weekday& y) NOEXCEPT +{ + return x.wd_ == y.wd_; +} + +CONSTCD11 +inline +bool +operator!=(const weekday& x, const weekday& y) NOEXCEPT +{ + return !(x == y); +} + +CONSTCD14 +inline +days +operator-(const weekday& x, const weekday& y) NOEXCEPT +{ + auto const wdu = x.wd_ - y.wd_; + auto const wk = (wdu >= 0 ? wdu : wdu-6) / 7; + return days{wdu - wk * 7}; +} + +CONSTCD14 +inline +weekday +operator+(const weekday& x, const days& y) NOEXCEPT +{ + auto const wdu = static_cast(static_cast(x.wd_)) + y.count(); + auto const wk = (wdu >= 0 ? wdu : wdu-6) / 7; + return weekday{static_cast(wdu - wk * 7)}; +} + +CONSTCD14 +inline +weekday +operator+(const days& x, const weekday& y) NOEXCEPT +{ + return y + x; +} + +CONSTCD14 +inline +weekday +operator-(const weekday& x, const days& y) NOEXCEPT +{ + return x + -y; +} + +namespace detail +{ + +template +std::basic_ostream& +low_level_fmt(std::basic_ostream& os, const weekday& wd) +{ + if (wd.ok()) + { + CharT fmt[] = {'%', 'a', 0}; + os << format(fmt, wd); + } + else + os << wd.c_encoding(); + return os; +} + +} // namespace detail + +template +inline +std::basic_ostream& +operator<<(std::basic_ostream& os, const weekday& wd) +{ + detail::low_level_fmt(os, wd); + if (!wd.ok()) + os << " is not a valid weekday"; + return os; +} + +#if !defined(_MSC_VER) || (_MSC_VER >= 1900) +inline namespace literals +{ + +CONSTCD11 +inline +date::day +operator "" _d(unsigned long long d) NOEXCEPT +{ + return date::day{static_cast(d)}; +} + +CONSTCD11 +inline +date::year +operator "" _y(unsigned long long y) NOEXCEPT +{ + return date::year(static_cast(y)); +} +#endif // !defined(_MSC_VER) || (_MSC_VER >= 1900) + +CONSTDATA date::last_spec last{}; + +CONSTDATA date::month jan{1}; +CONSTDATA date::month feb{2}; +CONSTDATA date::month mar{3}; +CONSTDATA date::month apr{4}; +CONSTDATA date::month may{5}; +CONSTDATA date::month jun{6}; +CONSTDATA date::month jul{7}; +CONSTDATA date::month aug{8}; +CONSTDATA date::month sep{9}; +CONSTDATA date::month oct{10}; +CONSTDATA date::month nov{11}; +CONSTDATA date::month dec{12}; + +CONSTDATA date::weekday sun{0u}; +CONSTDATA date::weekday mon{1u}; +CONSTDATA date::weekday tue{2u}; +CONSTDATA date::weekday wed{3u}; +CONSTDATA date::weekday thu{4u}; +CONSTDATA date::weekday fri{5u}; +CONSTDATA date::weekday sat{6u}; + +#if !defined(_MSC_VER) || (_MSC_VER >= 1900) +} // inline namespace literals +#endif + +CONSTDATA date::month January{1}; +CONSTDATA date::month February{2}; +CONSTDATA date::month March{3}; +CONSTDATA date::month April{4}; +CONSTDATA date::month May{5}; +CONSTDATA date::month June{6}; +CONSTDATA date::month July{7}; +CONSTDATA date::month August{8}; +CONSTDATA date::month September{9}; +CONSTDATA date::month October{10}; +CONSTDATA date::month November{11}; +CONSTDATA date::month December{12}; + +CONSTDATA date::weekday Monday{1}; +CONSTDATA date::weekday Tuesday{2}; +CONSTDATA date::weekday Wednesday{3}; +CONSTDATA date::weekday Thursday{4}; +CONSTDATA date::weekday Friday{5}; +CONSTDATA date::weekday Saturday{6}; +CONSTDATA date::weekday Sunday{7}; + +// weekday_indexed + +CONSTCD11 +inline +weekday +weekday_indexed::weekday() const NOEXCEPT +{ + return date::weekday{static_cast(wd_)}; +} + +CONSTCD11 inline unsigned weekday_indexed::index() const NOEXCEPT {return index_;} + +CONSTCD11 +inline +bool +weekday_indexed::ok() const NOEXCEPT +{ + return weekday().ok() && 1 <= index_ && index_ <= 5; +} + +#ifdef __GNUC__ +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wconversion" +#endif // __GNUC__ + +CONSTCD11 +inline +weekday_indexed::weekday_indexed(const date::weekday& wd, unsigned index) NOEXCEPT + : wd_(static_cast(static_cast(wd.wd_))) + , index_(static_cast(index)) + {} + +#ifdef __GNUC__ +# pragma GCC diagnostic pop +#endif // __GNUC__ + +namespace detail +{ + +template +std::basic_ostream& +low_level_fmt(std::basic_ostream& os, const weekday_indexed& wdi) +{ + return low_level_fmt(os, wdi.weekday()) << '[' << wdi.index() << ']'; +} + +} // namespace detail + +template +inline +std::basic_ostream& +operator<<(std::basic_ostream& os, const weekday_indexed& wdi) +{ + detail::low_level_fmt(os, wdi); + if (!wdi.ok()) + os << " is not a valid weekday_indexed"; + return os; +} + +CONSTCD11 +inline +weekday_indexed +weekday::operator[](unsigned index) const NOEXCEPT +{ + return {*this, index}; +} + +CONSTCD11 +inline +bool +operator==(const weekday_indexed& x, const weekday_indexed& y) NOEXCEPT +{ + return x.weekday() == y.weekday() && x.index() == y.index(); +} + +CONSTCD11 +inline +bool +operator!=(const weekday_indexed& x, const weekday_indexed& y) NOEXCEPT +{ + return !(x == y); +} + +// weekday_last + +CONSTCD11 inline date::weekday weekday_last::weekday() const NOEXCEPT {return wd_;} +CONSTCD11 inline bool weekday_last::ok() const NOEXCEPT {return wd_.ok();} +CONSTCD11 inline weekday_last::weekday_last(const date::weekday& wd) NOEXCEPT : wd_(wd) {} + +CONSTCD11 +inline +bool +operator==(const weekday_last& x, const weekday_last& y) NOEXCEPT +{ + return x.weekday() == y.weekday(); +} + +CONSTCD11 +inline +bool +operator!=(const weekday_last& x, const weekday_last& y) NOEXCEPT +{ + return !(x == y); +} + +namespace detail +{ + +template +std::basic_ostream& +low_level_fmt(std::basic_ostream& os, const weekday_last& wdl) +{ + return low_level_fmt(os, wdl.weekday()) << "[last]"; +} + +} // namespace detail + +template +inline +std::basic_ostream& +operator<<(std::basic_ostream& os, const weekday_last& wdl) +{ + detail::low_level_fmt(os, wdl); + if (!wdl.ok()) + os << " is not a valid weekday_last"; + return os; +} + +CONSTCD11 +inline +weekday_last +weekday::operator[](last_spec) const NOEXCEPT +{ + return weekday_last{*this}; +} + +// year_month + +CONSTCD11 +inline +year_month::year_month(const date::year& y, const date::month& m) NOEXCEPT + : y_(y) + , m_(m) + {} + +CONSTCD11 inline year year_month::year() const NOEXCEPT {return y_;} +CONSTCD11 inline month year_month::month() const NOEXCEPT {return m_;} +CONSTCD11 inline bool year_month::ok() const NOEXCEPT {return y_.ok() && m_.ok();} + +template +CONSTCD14 +inline +year_month& +year_month::operator+=(const months& dm) NOEXCEPT +{ + *this = *this + dm; + return *this; +} + +template +CONSTCD14 +inline +year_month& +year_month::operator-=(const months& dm) NOEXCEPT +{ + *this = *this - dm; + return *this; +} + +CONSTCD14 +inline +year_month& +year_month::operator+=(const years& dy) NOEXCEPT +{ + *this = *this + dy; + return *this; +} + +CONSTCD14 +inline +year_month& +year_month::operator-=(const years& dy) NOEXCEPT +{ + *this = *this - dy; + return *this; +} + +CONSTCD11 +inline +bool +operator==(const year_month& x, const year_month& y) NOEXCEPT +{ + return x.year() == y.year() && x.month() == y.month(); +} + +CONSTCD11 +inline +bool +operator!=(const year_month& x, const year_month& y) NOEXCEPT +{ + return !(x == y); +} + +CONSTCD11 +inline +bool +operator<(const year_month& x, const year_month& y) NOEXCEPT +{ + return x.year() < y.year() ? true + : (x.year() > y.year() ? false + : (x.month() < y.month())); +} + +CONSTCD11 +inline +bool +operator>(const year_month& x, const year_month& y) NOEXCEPT +{ + return y < x; +} + +CONSTCD11 +inline +bool +operator<=(const year_month& x, const year_month& y) NOEXCEPT +{ + return !(y < x); +} + +CONSTCD11 +inline +bool +operator>=(const year_month& x, const year_month& y) NOEXCEPT +{ + return !(x < y); +} + +template +CONSTCD14 +inline +year_month +operator+(const year_month& ym, const months& dm) NOEXCEPT +{ + auto dmi = static_cast(static_cast(ym.month())) - 1 + dm.count(); + auto dy = (dmi >= 0 ? dmi : dmi-11) / 12; + dmi = dmi - dy * 12 + 1; + return (ym.year() + years(dy)) / month(static_cast(dmi)); +} + +template +CONSTCD14 +inline +year_month +operator+(const months& dm, const year_month& ym) NOEXCEPT +{ + return ym + dm; +} + +template +CONSTCD14 +inline +year_month +operator-(const year_month& ym, const months& dm) NOEXCEPT +{ + return ym + -dm; +} + +CONSTCD11 +inline +months +operator-(const year_month& x, const year_month& y) NOEXCEPT +{ + return (x.year() - y.year()) + + months(static_cast(x.month()) - static_cast(y.month())); +} + +CONSTCD11 +inline +year_month +operator+(const year_month& ym, const years& dy) NOEXCEPT +{ + return (ym.year() + dy) / ym.month(); +} + +CONSTCD11 +inline +year_month +operator+(const years& dy, const year_month& ym) NOEXCEPT +{ + return ym + dy; +} + +CONSTCD11 +inline +year_month +operator-(const year_month& ym, const years& dy) NOEXCEPT +{ + return ym + -dy; +} + +namespace detail +{ + +template +std::basic_ostream& +low_level_fmt(std::basic_ostream& os, const year_month& ym) +{ + low_level_fmt(os, ym.year()) << '/'; + return low_level_fmt(os, ym.month()); +} + +} // namespace detail + +template +inline +std::basic_ostream& +operator<<(std::basic_ostream& os, const year_month& ym) +{ + detail::low_level_fmt(os, ym); + if (!ym.ok()) + os << " is not a valid year_month"; + return os; +} + +// month_day + +CONSTCD11 +inline +month_day::month_day(const date::month& m, const date::day& d) NOEXCEPT + : m_(m) + , d_(d) + {} + +CONSTCD11 inline date::month month_day::month() const NOEXCEPT {return m_;} +CONSTCD11 inline date::day month_day::day() const NOEXCEPT {return d_;} + +CONSTCD14 +inline +bool +month_day::ok() const NOEXCEPT +{ + CONSTDATA date::day d[] = + { + date::day(31), date::day(29), date::day(31), + date::day(30), date::day(31), date::day(30), + date::day(31), date::day(31), date::day(30), + date::day(31), date::day(30), date::day(31) + }; + return m_.ok() && date::day{1} <= d_ && d_ <= d[static_cast(m_)-1]; +} + +CONSTCD11 +inline +bool +operator==(const month_day& x, const month_day& y) NOEXCEPT +{ + return x.month() == y.month() && x.day() == y.day(); +} + +CONSTCD11 +inline +bool +operator!=(const month_day& x, const month_day& y) NOEXCEPT +{ + return !(x == y); +} + +CONSTCD11 +inline +bool +operator<(const month_day& x, const month_day& y) NOEXCEPT +{ + return x.month() < y.month() ? true + : (x.month() > y.month() ? false + : (x.day() < y.day())); +} + +CONSTCD11 +inline +bool +operator>(const month_day& x, const month_day& y) NOEXCEPT +{ + return y < x; +} + +CONSTCD11 +inline +bool +operator<=(const month_day& x, const month_day& y) NOEXCEPT +{ + return !(y < x); +} + +CONSTCD11 +inline +bool +operator>=(const month_day& x, const month_day& y) NOEXCEPT +{ + return !(x < y); +} + +namespace detail +{ + +template +std::basic_ostream& +low_level_fmt(std::basic_ostream& os, const month_day& md) +{ + low_level_fmt(os, md.month()) << '/'; + return low_level_fmt(os, md.day()); +} + +} // namespace detail + +template +inline +std::basic_ostream& +operator<<(std::basic_ostream& os, const month_day& md) +{ + detail::low_level_fmt(os, md); + if (!md.ok()) + os << " is not a valid month_day"; + return os; +} + +// month_day_last + +CONSTCD11 inline month month_day_last::month() const NOEXCEPT {return m_;} +CONSTCD11 inline bool month_day_last::ok() const NOEXCEPT {return m_.ok();} +CONSTCD11 inline month_day_last::month_day_last(const date::month& m) NOEXCEPT : m_(m) {} + +CONSTCD11 +inline +bool +operator==(const month_day_last& x, const month_day_last& y) NOEXCEPT +{ + return x.month() == y.month(); +} + +CONSTCD11 +inline +bool +operator!=(const month_day_last& x, const month_day_last& y) NOEXCEPT +{ + return !(x == y); +} + +CONSTCD11 +inline +bool +operator<(const month_day_last& x, const month_day_last& y) NOEXCEPT +{ + return x.month() < y.month(); +} + +CONSTCD11 +inline +bool +operator>(const month_day_last& x, const month_day_last& y) NOEXCEPT +{ + return y < x; +} + +CONSTCD11 +inline +bool +operator<=(const month_day_last& x, const month_day_last& y) NOEXCEPT +{ + return !(y < x); +} + +CONSTCD11 +inline +bool +operator>=(const month_day_last& x, const month_day_last& y) NOEXCEPT +{ + return !(x < y); +} + +namespace detail +{ + +template +std::basic_ostream& +low_level_fmt(std::basic_ostream& os, const month_day_last& mdl) +{ + return low_level_fmt(os, mdl.month()) << "/last"; +} + +} // namespace detail + +template +inline +std::basic_ostream& +operator<<(std::basic_ostream& os, const month_day_last& mdl) +{ + detail::low_level_fmt(os, mdl); + if (!mdl.ok()) + os << " is not a valid month_day_last"; + return os; +} + +// month_weekday + +CONSTCD11 +inline +month_weekday::month_weekday(const date::month& m, + const date::weekday_indexed& wdi) NOEXCEPT + : m_(m) + , wdi_(wdi) + {} + +CONSTCD11 inline month month_weekday::month() const NOEXCEPT {return m_;} + +CONSTCD11 +inline +weekday_indexed +month_weekday::weekday_indexed() const NOEXCEPT +{ + return wdi_; +} + +CONSTCD11 +inline +bool +month_weekday::ok() const NOEXCEPT +{ + return m_.ok() && wdi_.ok(); +} + +CONSTCD11 +inline +bool +operator==(const month_weekday& x, const month_weekday& y) NOEXCEPT +{ + return x.month() == y.month() && x.weekday_indexed() == y.weekday_indexed(); +} + +CONSTCD11 +inline +bool +operator!=(const month_weekday& x, const month_weekday& y) NOEXCEPT +{ + return !(x == y); +} + +namespace detail +{ + +template +std::basic_ostream& +low_level_fmt(std::basic_ostream& os, const month_weekday& mwd) +{ + low_level_fmt(os, mwd.month()) << '/'; + return low_level_fmt(os, mwd.weekday_indexed()); +} + +} // namespace detail + +template +inline +std::basic_ostream& +operator<<(std::basic_ostream& os, const month_weekday& mwd) +{ + detail::low_level_fmt(os, mwd); + if (!mwd.ok()) + os << " is not a valid month_weekday"; + return os; +} + +// month_weekday_last + +CONSTCD11 +inline +month_weekday_last::month_weekday_last(const date::month& m, + const date::weekday_last& wdl) NOEXCEPT + : m_(m) + , wdl_(wdl) + {} + +CONSTCD11 inline month month_weekday_last::month() const NOEXCEPT {return m_;} + +CONSTCD11 +inline +weekday_last +month_weekday_last::weekday_last() const NOEXCEPT +{ + return wdl_; +} + +CONSTCD11 +inline +bool +month_weekday_last::ok() const NOEXCEPT +{ + return m_.ok() && wdl_.ok(); +} + +CONSTCD11 +inline +bool +operator==(const month_weekday_last& x, const month_weekday_last& y) NOEXCEPT +{ + return x.month() == y.month() && x.weekday_last() == y.weekday_last(); +} + +CONSTCD11 +inline +bool +operator!=(const month_weekday_last& x, const month_weekday_last& y) NOEXCEPT +{ + return !(x == y); +} + +namespace detail +{ + +template +std::basic_ostream& +low_level_fmt(std::basic_ostream& os, const month_weekday_last& mwdl) +{ + low_level_fmt(os, mwdl.month()) << '/'; + return low_level_fmt(os, mwdl.weekday_last()); +} + +} // namespace detail + +template +inline +std::basic_ostream& +operator<<(std::basic_ostream& os, const month_weekday_last& mwdl) +{ + detail::low_level_fmt(os, mwdl); + if (!mwdl.ok()) + os << " is not a valid month_weekday_last"; + return os; +} + +// year_month_day_last + +CONSTCD11 +inline +year_month_day_last::year_month_day_last(const date::year& y, + const date::month_day_last& mdl) NOEXCEPT + : y_(y) + , mdl_(mdl) + {} + +template +CONSTCD14 +inline +year_month_day_last& +year_month_day_last::operator+=(const months& m) NOEXCEPT +{ + *this = *this + m; + return *this; +} + +template +CONSTCD14 +inline +year_month_day_last& +year_month_day_last::operator-=(const months& m) NOEXCEPT +{ + *this = *this - m; + return *this; +} + +CONSTCD14 +inline +year_month_day_last& +year_month_day_last::operator+=(const years& y) NOEXCEPT +{ + *this = *this + y; + return *this; +} + +CONSTCD14 +inline +year_month_day_last& +year_month_day_last::operator-=(const years& y) NOEXCEPT +{ + *this = *this - y; + return *this; +} + +CONSTCD11 inline year year_month_day_last::year() const NOEXCEPT {return y_;} +CONSTCD11 inline month year_month_day_last::month() const NOEXCEPT {return mdl_.month();} + +CONSTCD11 +inline +month_day_last +year_month_day_last::month_day_last() const NOEXCEPT +{ + return mdl_; +} + +CONSTCD14 +inline +day +year_month_day_last::day() const NOEXCEPT +{ + CONSTDATA date::day d[] = + { + date::day(31), date::day(28), date::day(31), + date::day(30), date::day(31), date::day(30), + date::day(31), date::day(31), date::day(30), + date::day(31), date::day(30), date::day(31) + }; + return (month() != February || !y_.is_leap()) && mdl_.ok() ? + d[static_cast(month()) - 1] : date::day{29}; +} + +CONSTCD14 +inline +year_month_day_last::operator sys_days() const NOEXCEPT +{ + return sys_days(year()/month()/day()); +} + +CONSTCD14 +inline +year_month_day_last::operator local_days() const NOEXCEPT +{ + return local_days(year()/month()/day()); +} + +CONSTCD11 +inline +bool +year_month_day_last::ok() const NOEXCEPT +{ + return y_.ok() && mdl_.ok(); +} + +CONSTCD11 +inline +bool +operator==(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT +{ + return x.year() == y.year() && x.month_day_last() == y.month_day_last(); +} + +CONSTCD11 +inline +bool +operator!=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT +{ + return !(x == y); +} + +CONSTCD11 +inline +bool +operator<(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT +{ + return x.year() < y.year() ? true + : (x.year() > y.year() ? false + : (x.month_day_last() < y.month_day_last())); +} + +CONSTCD11 +inline +bool +operator>(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT +{ + return y < x; +} + +CONSTCD11 +inline +bool +operator<=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT +{ + return !(y < x); +} + +CONSTCD11 +inline +bool +operator>=(const year_month_day_last& x, const year_month_day_last& y) NOEXCEPT +{ + return !(x < y); +} + +namespace detail +{ + +template +std::basic_ostream& +low_level_fmt(std::basic_ostream& os, const year_month_day_last& ymdl) +{ + low_level_fmt(os, ymdl.year()) << '/'; + return low_level_fmt(os, ymdl.month_day_last()); +} + +} // namespace detail + +template +inline +std::basic_ostream& +operator<<(std::basic_ostream& os, const year_month_day_last& ymdl) +{ + detail::low_level_fmt(os, ymdl); + if (!ymdl.ok()) + os << " is not a valid year_month_day_last"; + return os; +} + +template +CONSTCD14 +inline +year_month_day_last +operator+(const year_month_day_last& ymdl, const months& dm) NOEXCEPT +{ + return (ymdl.year() / ymdl.month() + dm) / last; +} + +template +CONSTCD14 +inline +year_month_day_last +operator+(const months& dm, const year_month_day_last& ymdl) NOEXCEPT +{ + return ymdl + dm; +} + +template +CONSTCD14 +inline +year_month_day_last +operator-(const year_month_day_last& ymdl, const months& dm) NOEXCEPT +{ + return ymdl + (-dm); +} + +CONSTCD11 +inline +year_month_day_last +operator+(const year_month_day_last& ymdl, const years& dy) NOEXCEPT +{ + return {ymdl.year()+dy, ymdl.month_day_last()}; +} + +CONSTCD11 +inline +year_month_day_last +operator+(const years& dy, const year_month_day_last& ymdl) NOEXCEPT +{ + return ymdl + dy; +} + +CONSTCD11 +inline +year_month_day_last +operator-(const year_month_day_last& ymdl, const years& dy) NOEXCEPT +{ + return ymdl + (-dy); +} + +// year_month_day + +CONSTCD11 +inline +year_month_day::year_month_day(const date::year& y, const date::month& m, + const date::day& d) NOEXCEPT + : y_(y) + , m_(m) + , d_(d) + {} + +CONSTCD14 +inline +year_month_day::year_month_day(const year_month_day_last& ymdl) NOEXCEPT + : y_(ymdl.year()) + , m_(ymdl.month()) + , d_(ymdl.day()) + {} + +CONSTCD14 +inline +year_month_day::year_month_day(sys_days dp) NOEXCEPT + : year_month_day(from_days(dp.time_since_epoch())) + {} + +CONSTCD14 +inline +year_month_day::year_month_day(local_days dp) NOEXCEPT + : year_month_day(from_days(dp.time_since_epoch())) + {} + +CONSTCD11 inline year year_month_day::year() const NOEXCEPT {return y_;} +CONSTCD11 inline month year_month_day::month() const NOEXCEPT {return m_;} +CONSTCD11 inline day year_month_day::day() const NOEXCEPT {return d_;} + +template +CONSTCD14 +inline +year_month_day& +year_month_day::operator+=(const months& m) NOEXCEPT +{ + *this = *this + m; + return *this; +} + +template +CONSTCD14 +inline +year_month_day& +year_month_day::operator-=(const months& m) NOEXCEPT +{ + *this = *this - m; + return *this; +} + +CONSTCD14 +inline +year_month_day& +year_month_day::operator+=(const years& y) NOEXCEPT +{ + *this = *this + y; + return *this; +} + +CONSTCD14 +inline +year_month_day& +year_month_day::operator-=(const years& y) NOEXCEPT +{ + *this = *this - y; + return *this; +} + +CONSTCD14 +inline +days +year_month_day::to_days() const NOEXCEPT +{ + static_assert(std::numeric_limits::digits >= 18, + "This algorithm has not been ported to a 16 bit unsigned integer"); + static_assert(std::numeric_limits::digits >= 20, + "This algorithm has not been ported to a 16 bit signed integer"); + auto const y = static_cast(y_) - (m_ <= February); + auto const m = static_cast(m_); + auto const d = static_cast(d_); + auto const era = (y >= 0 ? y : y-399) / 400; + auto const yoe = static_cast(y - era * 400); // [0, 399] + auto const doy = (153*(m > 2 ? m-3 : m+9) + 2)/5 + d-1; // [0, 365] + auto const doe = yoe * 365 + yoe/4 - yoe/100 + doy; // [0, 146096] + return days{era * 146097 + static_cast(doe) - 719468}; +} + +CONSTCD14 +inline +year_month_day::operator sys_days() const NOEXCEPT +{ + return sys_days{to_days()}; +} + +CONSTCD14 +inline +year_month_day::operator local_days() const NOEXCEPT +{ + return local_days{to_days()}; +} + +CONSTCD14 +inline +bool +year_month_day::ok() const NOEXCEPT +{ + if (!(y_.ok() && m_.ok())) + return false; + return date::day{1} <= d_ && d_ <= (y_ / m_ / last).day(); +} + +CONSTCD11 +inline +bool +operator==(const year_month_day& x, const year_month_day& y) NOEXCEPT +{ + return x.year() == y.year() && x.month() == y.month() && x.day() == y.day(); +} + +CONSTCD11 +inline +bool +operator!=(const year_month_day& x, const year_month_day& y) NOEXCEPT +{ + return !(x == y); +} + +CONSTCD11 +inline +bool +operator<(const year_month_day& x, const year_month_day& y) NOEXCEPT +{ + return x.year() < y.year() ? true + : (x.year() > y.year() ? false + : (x.month() < y.month() ? true + : (x.month() > y.month() ? false + : (x.day() < y.day())))); +} + +CONSTCD11 +inline +bool +operator>(const year_month_day& x, const year_month_day& y) NOEXCEPT +{ + return y < x; +} + +CONSTCD11 +inline +bool +operator<=(const year_month_day& x, const year_month_day& y) NOEXCEPT +{ + return !(y < x); +} + +CONSTCD11 +inline +bool +operator>=(const year_month_day& x, const year_month_day& y) NOEXCEPT +{ + return !(x < y); +} + +template +inline +std::basic_ostream& +operator<<(std::basic_ostream& os, const year_month_day& ymd) +{ + detail::save_ostream _(os); + os.fill('0'); + os.flags(std::ios::dec | std::ios::right); + os.imbue(std::locale::classic()); + os << static_cast(ymd.year()) << '-'; + os.width(2); + os << static_cast(ymd.month()) << '-'; + os.width(2); + os << static_cast(ymd.day()); + if (!ymd.ok()) + os << " is not a valid year_month_day"; + return os; +} + +CONSTCD14 +inline +year_month_day +year_month_day::from_days(days dp) NOEXCEPT +{ + static_assert(std::numeric_limits::digits >= 18, + "This algorithm has not been ported to a 16 bit unsigned integer"); + static_assert(std::numeric_limits::digits >= 20, + "This algorithm has not been ported to a 16 bit signed integer"); + auto const z = dp.count() + 719468; + auto const era = (z >= 0 ? z : z - 146096) / 146097; + auto const doe = static_cast(z - era * 146097); // [0, 146096] + auto const yoe = (doe - doe/1460 + doe/36524 - doe/146096) / 365; // [0, 399] + auto const y = static_cast(yoe) + era * 400; + auto const doy = doe - (365*yoe + yoe/4 - yoe/100); // [0, 365] + auto const mp = (5*doy + 2)/153; // [0, 11] + auto const d = doy - (153*mp+2)/5 + 1; // [1, 31] + auto const m = mp < 10 ? mp+3 : mp-9; // [1, 12] + return year_month_day{date::year{y + (m <= 2)}, date::month(m), date::day(d)}; +} + +template +CONSTCD14 +inline +year_month_day +operator+(const year_month_day& ymd, const months& dm) NOEXCEPT +{ + return (ymd.year() / ymd.month() + dm) / ymd.day(); +} + +template +CONSTCD14 +inline +year_month_day +operator+(const months& dm, const year_month_day& ymd) NOEXCEPT +{ + return ymd + dm; +} + +template +CONSTCD14 +inline +year_month_day +operator-(const year_month_day& ymd, const months& dm) NOEXCEPT +{ + return ymd + (-dm); +} + +CONSTCD11 +inline +year_month_day +operator+(const year_month_day& ymd, const years& dy) NOEXCEPT +{ + return (ymd.year() + dy) / ymd.month() / ymd.day(); +} + +CONSTCD11 +inline +year_month_day +operator+(const years& dy, const year_month_day& ymd) NOEXCEPT +{ + return ymd + dy; +} + +CONSTCD11 +inline +year_month_day +operator-(const year_month_day& ymd, const years& dy) NOEXCEPT +{ + return ymd + (-dy); +} + +// year_month_weekday + +CONSTCD11 +inline +year_month_weekday::year_month_weekday(const date::year& y, const date::month& m, + const date::weekday_indexed& wdi) + NOEXCEPT + : y_(y) + , m_(m) + , wdi_(wdi) + {} + +CONSTCD14 +inline +year_month_weekday::year_month_weekday(const sys_days& dp) NOEXCEPT + : year_month_weekday(from_days(dp.time_since_epoch())) + {} + +CONSTCD14 +inline +year_month_weekday::year_month_weekday(const local_days& dp) NOEXCEPT + : year_month_weekday(from_days(dp.time_since_epoch())) + {} + +template +CONSTCD14 +inline +year_month_weekday& +year_month_weekday::operator+=(const months& m) NOEXCEPT +{ + *this = *this + m; + return *this; +} + +template +CONSTCD14 +inline +year_month_weekday& +year_month_weekday::operator-=(const months& m) NOEXCEPT +{ + *this = *this - m; + return *this; +} + +CONSTCD14 +inline +year_month_weekday& +year_month_weekday::operator+=(const years& y) NOEXCEPT +{ + *this = *this + y; + return *this; +} + +CONSTCD14 +inline +year_month_weekday& +year_month_weekday::operator-=(const years& y) NOEXCEPT +{ + *this = *this - y; + return *this; +} + +CONSTCD11 inline year year_month_weekday::year() const NOEXCEPT {return y_;} +CONSTCD11 inline month year_month_weekday::month() const NOEXCEPT {return m_;} + +CONSTCD11 +inline +weekday +year_month_weekday::weekday() const NOEXCEPT +{ + return wdi_.weekday(); +} + +CONSTCD11 +inline +unsigned +year_month_weekday::index() const NOEXCEPT +{ + return wdi_.index(); +} + +CONSTCD11 +inline +weekday_indexed +year_month_weekday::weekday_indexed() const NOEXCEPT +{ + return wdi_; +} + +CONSTCD14 +inline +year_month_weekday::operator sys_days() const NOEXCEPT +{ + return sys_days{to_days()}; +} + +CONSTCD14 +inline +year_month_weekday::operator local_days() const NOEXCEPT +{ + return local_days{to_days()}; +} + +CONSTCD14 +inline +bool +year_month_weekday::ok() const NOEXCEPT +{ + if (!y_.ok() || !m_.ok() || !wdi_.weekday().ok() || wdi_.index() < 1) + return false; + if (wdi_.index() <= 4) + return true; + auto d2 = wdi_.weekday() - date::weekday(static_cast(y_/m_/1)) + + days((wdi_.index()-1)*7 + 1); + return static_cast(d2.count()) <= static_cast((y_/m_/last).day()); +} + +CONSTCD14 +inline +year_month_weekday +year_month_weekday::from_days(days d) NOEXCEPT +{ + sys_days dp{d}; + auto const wd = date::weekday(dp); + auto const ymd = year_month_day(dp); + return {ymd.year(), ymd.month(), wd[(static_cast(ymd.day())-1)/7+1]}; +} + +CONSTCD14 +inline +days +year_month_weekday::to_days() const NOEXCEPT +{ + auto d = sys_days(y_/m_/1); + return (d + (wdi_.weekday() - date::weekday(d) + days{(wdi_.index()-1)*7}) + ).time_since_epoch(); +} + +CONSTCD11 +inline +bool +operator==(const year_month_weekday& x, const year_month_weekday& y) NOEXCEPT +{ + return x.year() == y.year() && x.month() == y.month() && + x.weekday_indexed() == y.weekday_indexed(); +} + +CONSTCD11 +inline +bool +operator!=(const year_month_weekday& x, const year_month_weekday& y) NOEXCEPT +{ + return !(x == y); +} + +template +inline +std::basic_ostream& +operator<<(std::basic_ostream& os, const year_month_weekday& ymwdi) +{ + detail::low_level_fmt(os, ymwdi.year()) << '/'; + detail::low_level_fmt(os, ymwdi.month()) << '/'; + detail::low_level_fmt(os, ymwdi.weekday_indexed()); + if (!ymwdi.ok()) + os << " is not a valid year_month_weekday"; + return os; +} + +template +CONSTCD14 +inline +year_month_weekday +operator+(const year_month_weekday& ymwd, const months& dm) NOEXCEPT +{ + return (ymwd.year() / ymwd.month() + dm) / ymwd.weekday_indexed(); +} + +template +CONSTCD14 +inline +year_month_weekday +operator+(const months& dm, const year_month_weekday& ymwd) NOEXCEPT +{ + return ymwd + dm; +} + +template +CONSTCD14 +inline +year_month_weekday +operator-(const year_month_weekday& ymwd, const months& dm) NOEXCEPT +{ + return ymwd + (-dm); +} + +CONSTCD11 +inline +year_month_weekday +operator+(const year_month_weekday& ymwd, const years& dy) NOEXCEPT +{ + return {ymwd.year()+dy, ymwd.month(), ymwd.weekday_indexed()}; +} + +CONSTCD11 +inline +year_month_weekday +operator+(const years& dy, const year_month_weekday& ymwd) NOEXCEPT +{ + return ymwd + dy; +} + +CONSTCD11 +inline +year_month_weekday +operator-(const year_month_weekday& ymwd, const years& dy) NOEXCEPT +{ + return ymwd + (-dy); +} + +// year_month_weekday_last + +CONSTCD11 +inline +year_month_weekday_last::year_month_weekday_last(const date::year& y, + const date::month& m, + const date::weekday_last& wdl) NOEXCEPT + : y_(y) + , m_(m) + , wdl_(wdl) + {} + +template +CONSTCD14 +inline +year_month_weekday_last& +year_month_weekday_last::operator+=(const months& m) NOEXCEPT +{ + *this = *this + m; + return *this; +} + +template +CONSTCD14 +inline +year_month_weekday_last& +year_month_weekday_last::operator-=(const months& m) NOEXCEPT +{ + *this = *this - m; + return *this; +} + +CONSTCD14 +inline +year_month_weekday_last& +year_month_weekday_last::operator+=(const years& y) NOEXCEPT +{ + *this = *this + y; + return *this; +} + +CONSTCD14 +inline +year_month_weekday_last& +year_month_weekday_last::operator-=(const years& y) NOEXCEPT +{ + *this = *this - y; + return *this; +} + +CONSTCD11 inline year year_month_weekday_last::year() const NOEXCEPT {return y_;} +CONSTCD11 inline month year_month_weekday_last::month() const NOEXCEPT {return m_;} + +CONSTCD11 +inline +weekday +year_month_weekday_last::weekday() const NOEXCEPT +{ + return wdl_.weekday(); +} + +CONSTCD11 +inline +weekday_last +year_month_weekday_last::weekday_last() const NOEXCEPT +{ + return wdl_; +} + +CONSTCD14 +inline +year_month_weekday_last::operator sys_days() const NOEXCEPT +{ + return sys_days{to_days()}; +} + +CONSTCD14 +inline +year_month_weekday_last::operator local_days() const NOEXCEPT +{ + return local_days{to_days()}; +} + +CONSTCD11 +inline +bool +year_month_weekday_last::ok() const NOEXCEPT +{ + return y_.ok() && m_.ok() && wdl_.ok(); +} + +CONSTCD14 +inline +days +year_month_weekday_last::to_days() const NOEXCEPT +{ + auto const d = sys_days(y_/m_/last); + return (d - (date::weekday{d} - wdl_.weekday())).time_since_epoch(); +} + +CONSTCD11 +inline +bool +operator==(const year_month_weekday_last& x, const year_month_weekday_last& y) NOEXCEPT +{ + return x.year() == y.year() && x.month() == y.month() && + x.weekday_last() == y.weekday_last(); +} + +CONSTCD11 +inline +bool +operator!=(const year_month_weekday_last& x, const year_month_weekday_last& y) NOEXCEPT +{ + return !(x == y); +} + +template +inline +std::basic_ostream& +operator<<(std::basic_ostream& os, const year_month_weekday_last& ymwdl) +{ + detail::low_level_fmt(os, ymwdl.year()) << '/'; + detail::low_level_fmt(os, ymwdl.month()) << '/'; + detail::low_level_fmt(os, ymwdl.weekday_last()); + if (!ymwdl.ok()) + os << " is not a valid year_month_weekday_last"; + return os; +} + +template +CONSTCD14 +inline +year_month_weekday_last +operator+(const year_month_weekday_last& ymwdl, const months& dm) NOEXCEPT +{ + return (ymwdl.year() / ymwdl.month() + dm) / ymwdl.weekday_last(); +} + +template +CONSTCD14 +inline +year_month_weekday_last +operator+(const months& dm, const year_month_weekday_last& ymwdl) NOEXCEPT +{ + return ymwdl + dm; +} + +template +CONSTCD14 +inline +year_month_weekday_last +operator-(const year_month_weekday_last& ymwdl, const months& dm) NOEXCEPT +{ + return ymwdl + (-dm); +} + +CONSTCD11 +inline +year_month_weekday_last +operator+(const year_month_weekday_last& ymwdl, const years& dy) NOEXCEPT +{ + return {ymwdl.year()+dy, ymwdl.month(), ymwdl.weekday_last()}; +} + +CONSTCD11 +inline +year_month_weekday_last +operator+(const years& dy, const year_month_weekday_last& ymwdl) NOEXCEPT +{ + return ymwdl + dy; +} + +CONSTCD11 +inline +year_month_weekday_last +operator-(const year_month_weekday_last& ymwdl, const years& dy) NOEXCEPT +{ + return ymwdl + (-dy); +} + +// year_month from operator/() + +CONSTCD11 +inline +year_month +operator/(const year& y, const month& m) NOEXCEPT +{ + return {y, m}; +} + +CONSTCD11 +inline +year_month +operator/(const year& y, int m) NOEXCEPT +{ + return y / month(static_cast(m)); +} + +// month_day from operator/() + +CONSTCD11 +inline +month_day +operator/(const month& m, const day& d) NOEXCEPT +{ + return {m, d}; +} + +CONSTCD11 +inline +month_day +operator/(const day& d, const month& m) NOEXCEPT +{ + return m / d; +} + +CONSTCD11 +inline +month_day +operator/(const month& m, int d) NOEXCEPT +{ + return m / day(static_cast(d)); +} + +CONSTCD11 +inline +month_day +operator/(int m, const day& d) NOEXCEPT +{ + return month(static_cast(m)) / d; +} + +CONSTCD11 inline month_day operator/(const day& d, int m) NOEXCEPT {return m / d;} + +// month_day_last from operator/() + +CONSTCD11 +inline +month_day_last +operator/(const month& m, last_spec) NOEXCEPT +{ + return month_day_last{m}; +} + +CONSTCD11 +inline +month_day_last +operator/(last_spec, const month& m) NOEXCEPT +{ + return m/last; +} + +CONSTCD11 +inline +month_day_last +operator/(int m, last_spec) NOEXCEPT +{ + return month(static_cast(m))/last; +} + +CONSTCD11 +inline +month_day_last +operator/(last_spec, int m) NOEXCEPT +{ + return m/last; +} + +// month_weekday from operator/() + +CONSTCD11 +inline +month_weekday +operator/(const month& m, const weekday_indexed& wdi) NOEXCEPT +{ + return {m, wdi}; +} + +CONSTCD11 +inline +month_weekday +operator/(const weekday_indexed& wdi, const month& m) NOEXCEPT +{ + return m / wdi; +} + +CONSTCD11 +inline +month_weekday +operator/(int m, const weekday_indexed& wdi) NOEXCEPT +{ + return month(static_cast(m)) / wdi; +} + +CONSTCD11 +inline +month_weekday +operator/(const weekday_indexed& wdi, int m) NOEXCEPT +{ + return m / wdi; +} + +// month_weekday_last from operator/() + +CONSTCD11 +inline +month_weekday_last +operator/(const month& m, const weekday_last& wdl) NOEXCEPT +{ + return {m, wdl}; +} + +CONSTCD11 +inline +month_weekday_last +operator/(const weekday_last& wdl, const month& m) NOEXCEPT +{ + return m / wdl; +} + +CONSTCD11 +inline +month_weekday_last +operator/(int m, const weekday_last& wdl) NOEXCEPT +{ + return month(static_cast(m)) / wdl; +} + +CONSTCD11 +inline +month_weekday_last +operator/(const weekday_last& wdl, int m) NOEXCEPT +{ + return m / wdl; +} + +// year_month_day from operator/() + +CONSTCD11 +inline +year_month_day +operator/(const year_month& ym, const day& d) NOEXCEPT +{ + return {ym.year(), ym.month(), d}; +} + +CONSTCD11 +inline +year_month_day +operator/(const year_month& ym, int d) NOEXCEPT +{ + return ym / day(static_cast(d)); +} + +CONSTCD11 +inline +year_month_day +operator/(const year& y, const month_day& md) NOEXCEPT +{ + return y / md.month() / md.day(); +} + +CONSTCD11 +inline +year_month_day +operator/(int y, const month_day& md) NOEXCEPT +{ + return year(y) / md; +} + +CONSTCD11 +inline +year_month_day +operator/(const month_day& md, const year& y) NOEXCEPT +{ + return y / md; +} + +CONSTCD11 +inline +year_month_day +operator/(const month_day& md, int y) NOEXCEPT +{ + return year(y) / md; +} + +// year_month_day_last from operator/() + +CONSTCD11 +inline +year_month_day_last +operator/(const year_month& ym, last_spec) NOEXCEPT +{ + return {ym.year(), month_day_last{ym.month()}}; +} + +CONSTCD11 +inline +year_month_day_last +operator/(const year& y, const month_day_last& mdl) NOEXCEPT +{ + return {y, mdl}; +} + +CONSTCD11 +inline +year_month_day_last +operator/(int y, const month_day_last& mdl) NOEXCEPT +{ + return year(y) / mdl; +} + +CONSTCD11 +inline +year_month_day_last +operator/(const month_day_last& mdl, const year& y) NOEXCEPT +{ + return y / mdl; +} + +CONSTCD11 +inline +year_month_day_last +operator/(const month_day_last& mdl, int y) NOEXCEPT +{ + return year(y) / mdl; +} + +// year_month_weekday from operator/() + +CONSTCD11 +inline +year_month_weekday +operator/(const year_month& ym, const weekday_indexed& wdi) NOEXCEPT +{ + return {ym.year(), ym.month(), wdi}; +} + +CONSTCD11 +inline +year_month_weekday +operator/(const year& y, const month_weekday& mwd) NOEXCEPT +{ + return {y, mwd.month(), mwd.weekday_indexed()}; +} + +CONSTCD11 +inline +year_month_weekday +operator/(int y, const month_weekday& mwd) NOEXCEPT +{ + return year(y) / mwd; +} + +CONSTCD11 +inline +year_month_weekday +operator/(const month_weekday& mwd, const year& y) NOEXCEPT +{ + return y / mwd; +} + +CONSTCD11 +inline +year_month_weekday +operator/(const month_weekday& mwd, int y) NOEXCEPT +{ + return year(y) / mwd; +} + +// year_month_weekday_last from operator/() + +CONSTCD11 +inline +year_month_weekday_last +operator/(const year_month& ym, const weekday_last& wdl) NOEXCEPT +{ + return {ym.year(), ym.month(), wdl}; +} + +CONSTCD11 +inline +year_month_weekday_last +operator/(const year& y, const month_weekday_last& mwdl) NOEXCEPT +{ + return {y, mwdl.month(), mwdl.weekday_last()}; +} + +CONSTCD11 +inline +year_month_weekday_last +operator/(int y, const month_weekday_last& mwdl) NOEXCEPT +{ + return year(y) / mwdl; +} + +CONSTCD11 +inline +year_month_weekday_last +operator/(const month_weekday_last& mwdl, const year& y) NOEXCEPT +{ + return y / mwdl; +} + +CONSTCD11 +inline +year_month_weekday_last +operator/(const month_weekday_last& mwdl, int y) NOEXCEPT +{ + return year(y) / mwdl; +} + +template +struct fields; + +template +std::basic_ostream& +to_stream(std::basic_ostream& os, const CharT* fmt, + const fields& fds, const std::string* abbrev = nullptr, + const std::chrono::seconds* offset_sec = nullptr); + +template +std::basic_istream& +from_stream(std::basic_istream& is, const CharT* fmt, + fields& fds, std::basic_string* abbrev = nullptr, + std::chrono::minutes* offset = nullptr); + +// hh_mm_ss + +namespace detail +{ + +struct undocumented {explicit undocumented() = default;}; + +// width::value is the number of fractional decimal digits in 1/n +// width<0>::value and width<1>::value are defined to be 0 +// If 1/n takes more than 18 fractional decimal digits, +// the result is truncated to 19. +// Example: width<2>::value == 1 +// Example: width<3>::value == 19 +// Example: width<4>::value == 2 +// Example: width<10>::value == 1 +// Example: width<1000>::value == 3 +template +struct width +{ + static_assert(d > 0, "width called with zero denominator"); + static CONSTDATA unsigned value = 1 + width::value; +}; + +template +struct width +{ + static CONSTDATA unsigned value = 0; +}; + +template +struct static_pow10 +{ +private: + static CONSTDATA std::uint64_t h = static_pow10::value; +public: + static CONSTDATA std::uint64_t value = h * h * (exp % 2 ? 10 : 1); +}; + +template <> +struct static_pow10<0> +{ + static CONSTDATA std::uint64_t value = 1; +}; + +template +class decimal_format_seconds +{ + using CT = typename std::common_type::type; + using rep = typename CT::rep; + static unsigned CONSTDATA trial_width = + detail::width::value; +public: + static unsigned CONSTDATA width = trial_width < 19 ? trial_width : 6u; + using precision = std::chrono::duration::value>>; + +private: + std::chrono::seconds s_; + precision sub_s_; + +public: + CONSTCD11 decimal_format_seconds() + : s_() + , sub_s_() + {} + + CONSTCD11 explicit decimal_format_seconds(const Duration& d) NOEXCEPT + : s_(std::chrono::duration_cast(d)) + , sub_s_(std::chrono::duration_cast(d - s_)) + {} + + CONSTCD14 std::chrono::seconds& seconds() NOEXCEPT {return s_;} + CONSTCD11 std::chrono::seconds seconds() const NOEXCEPT {return s_;} + CONSTCD11 precision subseconds() const NOEXCEPT {return sub_s_;} + + CONSTCD14 precision to_duration() const NOEXCEPT + { + return s_ + sub_s_; + } + + CONSTCD11 bool in_conventional_range() const NOEXCEPT + { + return sub_s_ < std::chrono::seconds{1} && s_ < std::chrono::minutes{1}; + } + + template + friend + std::basic_ostream& + operator<<(std::basic_ostream& os, const decimal_format_seconds& x) + { + return x.print(os, std::chrono::treat_as_floating_point{}); + } + + template + std::basic_ostream& + print(std::basic_ostream& os, std::true_type) const + { + date::detail::save_ostream _(os); + std::chrono::duration d = s_ + sub_s_; + if (d < std::chrono::seconds{10}) + os << '0'; + os.precision(width+6); + os << std::fixed << d.count(); + return os; + } + + template + std::basic_ostream& + print(std::basic_ostream& os, std::false_type) const + { + date::detail::save_ostream _(os); + os.fill('0'); + os.flags(std::ios::dec | std::ios::right); + os.width(2); + os << s_.count(); + if (width > 0) + { +#if !ONLY_C_LOCALE + os << std::use_facet>(os.getloc()).decimal_point(); +#else + os << '.'; +#endif + date::detail::save_ostream _s(os); + os.imbue(std::locale::classic()); + os.width(width); + os << sub_s_.count(); + } + return os; + } +}; + +template +inline +CONSTCD11 +typename std::enable_if + < + std::numeric_limits::is_signed, + std::chrono::duration + >::type +abs(std::chrono::duration d) +{ + return d >= d.zero() ? +d : -d; +} + +template +inline +CONSTCD11 +typename std::enable_if + < + !std::numeric_limits::is_signed, + std::chrono::duration + >::type +abs(std::chrono::duration d) +{ + return d; +} + +} // namespace detail + +template +class hh_mm_ss +{ + using dfs = detail::decimal_format_seconds::type>; + + std::chrono::hours h_; + std::chrono::minutes m_; + dfs s_; + bool neg_; + +public: + static unsigned CONSTDATA fractional_width = dfs::width; + using precision = typename dfs::precision; + + CONSTCD11 hh_mm_ss() NOEXCEPT + : hh_mm_ss(Duration::zero()) + {} + + CONSTCD11 explicit hh_mm_ss(Duration d) NOEXCEPT + : h_(std::chrono::duration_cast(detail::abs(d))) + , m_(std::chrono::duration_cast(detail::abs(d)) - h_) + , s_(detail::abs(d) - h_ - m_) + , neg_(d < Duration::zero()) + {} + + CONSTCD11 std::chrono::hours hours() const NOEXCEPT {return h_;} + CONSTCD11 std::chrono::minutes minutes() const NOEXCEPT {return m_;} + CONSTCD11 std::chrono::seconds seconds() const NOEXCEPT {return s_.seconds();} + CONSTCD14 std::chrono::seconds& + seconds(detail::undocumented) NOEXCEPT {return s_.seconds();} + CONSTCD11 precision subseconds() const NOEXCEPT {return s_.subseconds();} + CONSTCD11 bool is_negative() const NOEXCEPT {return neg_;} + + CONSTCD11 explicit operator precision() const NOEXCEPT {return to_duration();} + CONSTCD11 precision to_duration() const NOEXCEPT + {return (s_.to_duration() + m_ + h_) * (1-2*neg_);} + + CONSTCD11 bool in_conventional_range() const NOEXCEPT + { + return !neg_ && h_ < days{1} && m_ < std::chrono::hours{1} && + s_.in_conventional_range(); + } + +private: + + template + friend + std::basic_ostream& + operator<<(std::basic_ostream& os, hh_mm_ss const& tod) + { + if (tod.is_negative()) + os << '-'; + if (tod.h_ < std::chrono::hours{10}) + os << '0'; + os << tod.h_.count() << ':'; + if (tod.m_ < std::chrono::minutes{10}) + os << '0'; + os << tod.m_.count() << ':' << tod.s_; + return os; + } + + template + friend + std::basic_ostream& + date::to_stream(std::basic_ostream& os, const CharT* fmt, + const fields& fds, const std::string* abbrev, + const std::chrono::seconds* offset_sec); + + template + friend + std::basic_istream& + date::from_stream(std::basic_istream& is, const CharT* fmt, + fields& fds, + std::basic_string* abbrev, std::chrono::minutes* offset); +}; + +inline +CONSTCD14 +bool +is_am(std::chrono::hours const& h) NOEXCEPT +{ + using std::chrono::hours; + return hours{0} <= h && h < hours{12}; +} + +inline +CONSTCD14 +bool +is_pm(std::chrono::hours const& h) NOEXCEPT +{ + using std::chrono::hours; + return hours{12} <= h && h < hours{24}; +} + +inline +CONSTCD14 +std::chrono::hours +make12(std::chrono::hours h) NOEXCEPT +{ + using std::chrono::hours; + if (h < hours{12}) + { + if (h == hours{0}) + h = hours{12}; + } + else + { + if (h != hours{12}) + h = h - hours{12}; + } + return h; +} + +inline +CONSTCD14 +std::chrono::hours +make24(std::chrono::hours h, bool is_pm) NOEXCEPT +{ + using std::chrono::hours; + if (is_pm) + { + if (h != hours{12}) + h = h + hours{12}; + } + else if (h == hours{12}) + h = hours{0}; + return h; +} + +template +using time_of_day = hh_mm_ss; + +template +CONSTCD11 +inline +hh_mm_ss> +make_time(const std::chrono::duration& d) +{ + return hh_mm_ss>(d); +} + +template +inline +typename std::enable_if +< + !std::is_convertible::value, + std::basic_ostream& +>::type +operator<<(std::basic_ostream& os, const sys_time& tp) +{ + auto const dp = date::floor(tp); + return os << year_month_day(dp) << ' ' << make_time(tp-dp); +} + +template +inline +std::basic_ostream& +operator<<(std::basic_ostream& os, const sys_days& dp) +{ + return os << year_month_day(dp); +} + +template +inline +std::basic_ostream& +operator<<(std::basic_ostream& os, const local_time& ut) +{ + return (os << sys_time{ut.time_since_epoch()}); +} + +namespace detail +{ + +template +class string_literal; + +template +inline +CONSTCD14 +string_literal::type, + N1 + N2 - 1> +operator+(const string_literal& x, const string_literal& y) NOEXCEPT; + +template +class string_literal +{ + CharT p_[N]; + + CONSTCD11 string_literal() NOEXCEPT + : p_{} + {} + +public: + using const_iterator = const CharT*; + + string_literal(string_literal const&) = default; + string_literal& operator=(string_literal const&) = delete; + + template ::type> + CONSTCD11 string_literal(CharT c) NOEXCEPT + : p_{c} + { + } + + template ::type> + CONSTCD11 string_literal(CharT c1, CharT c2) NOEXCEPT + : p_{c1, c2} + { + } + + template ::type> + CONSTCD11 string_literal(CharT c1, CharT c2, CharT c3) NOEXCEPT + : p_{c1, c2, c3} + { + } + + CONSTCD14 string_literal(const CharT(&a)[N]) NOEXCEPT + : p_{} + { + for (std::size_t i = 0; i < N; ++i) + p_[i] = a[i]; + } + + template ::type> + CONSTCD14 string_literal(const char(&a)[N]) NOEXCEPT + : p_{} + { + for (std::size_t i = 0; i < N; ++i) + p_[i] = a[i]; + } + + template ::value>::type> + CONSTCD14 string_literal(string_literal const& a) NOEXCEPT + : p_{} + { + for (std::size_t i = 0; i < N; ++i) + p_[i] = a[i]; + } + + CONSTCD11 const CharT* data() const NOEXCEPT {return p_;} + CONSTCD11 std::size_t size() const NOEXCEPT {return N-1;} + + CONSTCD11 const_iterator begin() const NOEXCEPT {return p_;} + CONSTCD11 const_iterator end() const NOEXCEPT {return p_ + N-1;} + + CONSTCD11 CharT const& operator[](std::size_t n) const NOEXCEPT + { + return p_[n]; + } + + template + friend + std::basic_ostream& + operator<<(std::basic_ostream& os, const string_literal& s) + { + return os << s.p_; + } + + template + friend + CONSTCD14 + string_literal::type, + N1 + N2 - 1> + operator+(const string_literal& x, const string_literal& y) NOEXCEPT; +}; + +template +CONSTCD11 +inline +string_literal +operator+(const string_literal& x, const string_literal& y) NOEXCEPT +{ + return string_literal(x[0], y[0]); +} + +template +CONSTCD11 +inline +string_literal +operator+(const string_literal& x, const string_literal& y) NOEXCEPT +{ + return string_literal(x[0], x[1], y[0]); +} + +template +CONSTCD14 +inline +string_literal::type, + N1 + N2 - 1> +operator+(const string_literal& x, const string_literal& y) NOEXCEPT +{ + using CT = typename std::conditional::type; + + string_literal r; + std::size_t i = 0; + for (; i < N1-1; ++i) + r.p_[i] = CT(x.p_[i]); + for (std::size_t j = 0; j < N2; ++j, ++i) + r.p_[i] = CT(y.p_[j]); + + return r; +} + + +template +inline +std::basic_string +operator+(std::basic_string x, const string_literal& y) +{ + x.append(y.data(), y.size()); + return x; +} + +#if __cplusplus >= 201402 && (!defined(__EDG_VERSION__) || __EDG_VERSION__ > 411) \ + && (!defined(__SUNPRO_CC) || __SUNPRO_CC > 0x5150) + +template ::value || + std::is_same::value || + std::is_same::value || + std::is_same::value>> +CONSTCD14 +inline +string_literal +msl(CharT c) NOEXCEPT +{ + return string_literal{c}; +} + +CONSTCD14 +inline +std::size_t +to_string_len(std::intmax_t i) +{ + std::size_t r = 0; + do + { + i /= 10; + ++r; + } while (i > 0); + return r; +} + +template +CONSTCD14 +inline +std::enable_if_t +< + N < 10, + string_literal +> +msl() NOEXCEPT +{ + return msl(char(N % 10 + '0')); +} + +template +CONSTCD14 +inline +std::enable_if_t +< + 10 <= N, + string_literal +> +msl() NOEXCEPT +{ + return msl() + msl(char(N % 10 + '0')); +} + +template +CONSTCD14 +inline +std::enable_if_t +< + std::ratio::type::den != 1, + string_literal::type::num) + + to_string_len(std::ratio::type::den) + 4> +> +msl(std::ratio) NOEXCEPT +{ + using R = typename std::ratio::type; + return msl(CharT{'['}) + msl() + msl(CharT{'/'}) + + msl() + msl(CharT{']'}); +} + +template +CONSTCD14 +inline +std::enable_if_t +< + std::ratio::type::den == 1, + string_literal::type::num) + 3> +> +msl(std::ratio) NOEXCEPT +{ + using R = typename std::ratio::type; + return msl(CharT{'['}) + msl() + msl(CharT{']'}); +} + + +#else // __cplusplus < 201402 || (defined(__EDG_VERSION__) && __EDG_VERSION__ <= 411) + +inline +std::string +to_string(std::uint64_t x) +{ + return std::to_string(x); +} + +template +inline +std::basic_string +to_string(std::uint64_t x) +{ + auto y = std::to_string(x); + return std::basic_string(y.begin(), y.end()); +} + +template +inline +typename std::enable_if +< + std::ratio::type::den != 1, + std::basic_string +>::type +msl(std::ratio) +{ + using R = typename std::ratio::type; + return std::basic_string(1, '[') + to_string(R::num) + CharT{'/'} + + to_string(R::den) + CharT{']'}; +} + +template +inline +typename std::enable_if +< + std::ratio::type::den == 1, + std::basic_string +>::type +msl(std::ratio) +{ + using R = typename std::ratio::type; + return std::basic_string(1, '[') + to_string(R::num) + CharT{']'}; +} + +#endif // __cplusplus < 201402 || (defined(__EDG_VERSION__) && __EDG_VERSION__ <= 411) + +template +CONSTCD11 +inline +string_literal +msl(std::atto) NOEXCEPT +{ + return string_literal{'a'}; +} + +template +CONSTCD11 +inline +string_literal +msl(std::femto) NOEXCEPT +{ + return string_literal{'f'}; +} + +template +CONSTCD11 +inline +string_literal +msl(std::pico) NOEXCEPT +{ + return string_literal{'p'}; +} + +template +CONSTCD11 +inline +string_literal +msl(std::nano) NOEXCEPT +{ + return string_literal{'n'}; +} + +template +CONSTCD11 +inline +typename std::enable_if +< + std::is_same::value, + string_literal +>::type +msl(std::micro) NOEXCEPT +{ + return string_literal{'\xC2', '\xB5'}; +} + +template +CONSTCD11 +inline +typename std::enable_if +< + !std::is_same::value, + string_literal +>::type +msl(std::micro) NOEXCEPT +{ + return string_literal{CharT{static_cast('\xB5')}}; +} + +template +CONSTCD11 +inline +string_literal +msl(std::milli) NOEXCEPT +{ + return string_literal{'m'}; +} + +template +CONSTCD11 +inline +string_literal +msl(std::centi) NOEXCEPT +{ + return string_literal{'c'}; +} + +template +CONSTCD11 +inline +string_literal +msl(std::deca) NOEXCEPT +{ + return string_literal{'d', 'a'}; +} + +template +CONSTCD11 +inline +string_literal +msl(std::deci) NOEXCEPT +{ + return string_literal{'d'}; +} + +template +CONSTCD11 +inline +string_literal +msl(std::hecto) NOEXCEPT +{ + return string_literal{'h'}; +} + +template +CONSTCD11 +inline +string_literal +msl(std::kilo) NOEXCEPT +{ + return string_literal{'k'}; +} + +template +CONSTCD11 +inline +string_literal +msl(std::mega) NOEXCEPT +{ + return string_literal{'M'}; +} + +template +CONSTCD11 +inline +string_literal +msl(std::giga) NOEXCEPT +{ + return string_literal{'G'}; +} + +template +CONSTCD11 +inline +string_literal +msl(std::tera) NOEXCEPT +{ + return string_literal{'T'}; +} + +template +CONSTCD11 +inline +string_literal +msl(std::peta) NOEXCEPT +{ + return string_literal{'P'}; +} + +template +CONSTCD11 +inline +string_literal +msl(std::exa) NOEXCEPT +{ + return string_literal{'E'}; +} + +template +CONSTCD11 +inline +auto +get_units(Period p) + -> decltype(msl(p) + string_literal{'s'}) +{ + return msl(p) + string_literal{'s'}; +} + +template +CONSTCD11 +inline +string_literal +get_units(std::ratio<1>) +{ + return string_literal{'s'}; +} + +template +CONSTCD11 +inline +string_literal +get_units(std::ratio<3600>) +{ + return string_literal{'h'}; +} + +template +CONSTCD11 +inline +string_literal +get_units(std::ratio<60>) +{ + return string_literal{'m', 'i', 'n'}; +} + +template +CONSTCD11 +inline +string_literal +get_units(std::ratio<86400>) +{ + return string_literal{'d'}; +} + +template > +struct make_string; + +template <> +struct make_string +{ + template + static + std::string + from(Rep n) + { + return std::to_string(n); + } +}; + +template +struct make_string +{ + template + static + std::basic_string + from(Rep n) + { + auto s = std::to_string(n); + return std::basic_string(s.begin(), s.end()); + } +}; + +template <> +struct make_string +{ + template + static + std::wstring + from(Rep n) + { + return std::to_wstring(n); + } +}; + +template +struct make_string +{ + template + static + std::basic_string + from(Rep n) + { + auto s = std::to_wstring(n); + return std::basic_string(s.begin(), s.end()); + } +}; + +} // namespace detail + +// to_stream + +CONSTDATA year nanyear{-32768}; + +template +struct fields +{ + year_month_day ymd{nanyear/0/0}; + weekday wd{8u}; + hh_mm_ss tod{}; + bool has_tod = false; + +#if !defined(__clang__) && defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ <= 409) + fields() : ymd{nanyear/0/0}, wd{8u}, tod{}, has_tod{false} {} +#else + fields() = default; +#endif + + fields(year_month_day ymd_) : ymd(ymd_) {} + fields(weekday wd_) : wd(wd_) {} + fields(hh_mm_ss tod_) : tod(tod_), has_tod(true) {} + + fields(year_month_day ymd_, weekday wd_) : ymd(ymd_), wd(wd_) {} + fields(year_month_day ymd_, hh_mm_ss tod_) : ymd(ymd_), tod(tod_), + has_tod(true) {} + + fields(weekday wd_, hh_mm_ss tod_) : wd(wd_), tod(tod_), has_tod(true) {} + + fields(year_month_day ymd_, weekday wd_, hh_mm_ss tod_) + : ymd(ymd_) + , wd(wd_) + , tod(tod_) + , has_tod(true) + {} +}; + +namespace detail +{ + +template +unsigned +extract_weekday(std::basic_ostream& os, const fields& fds) +{ + if (!fds.ymd.ok() && !fds.wd.ok()) + { + // fds does not contain a valid weekday + os.setstate(std::ios::failbit); + return 8; + } + weekday wd; + if (fds.ymd.ok()) + { + wd = weekday{sys_days(fds.ymd)}; + if (fds.wd.ok() && wd != fds.wd) + { + // fds.ymd and fds.wd are inconsistent + os.setstate(std::ios::failbit); + return 8; + } + } + else + wd = fds.wd; + return static_cast((wd - Sunday).count()); +} + +template +unsigned +extract_month(std::basic_ostream& os, const fields& fds) +{ + if (!fds.ymd.month().ok()) + { + // fds does not contain a valid month + os.setstate(std::ios::failbit); + return 0; + } + return static_cast(fds.ymd.month()); +} + +} // namespace detail + +#if ONLY_C_LOCALE + +namespace detail +{ + +inline +std::pair +weekday_names() +{ + static const std::string nm[] = + { + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + "Sun", + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat" + }; + return std::make_pair(nm, nm+sizeof(nm)/sizeof(nm[0])); +} + +inline +std::pair +month_names() +{ + static const std::string nm[] = + { + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec" + }; + return std::make_pair(nm, nm+sizeof(nm)/sizeof(nm[0])); +} + +inline +std::pair +ampm_names() +{ + static const std::string nm[] = + { + "AM", + "PM" + }; + return std::make_pair(nm, nm+sizeof(nm)/sizeof(nm[0])); +} + +template +FwdIter +scan_keyword(std::basic_istream& is, FwdIter kb, FwdIter ke) +{ + size_t nkw = static_cast(std::distance(kb, ke)); + const unsigned char doesnt_match = '\0'; + const unsigned char might_match = '\1'; + const unsigned char does_match = '\2'; + unsigned char statbuf[100]; + unsigned char* status = statbuf; + std::unique_ptr stat_hold(0, free); + if (nkw > sizeof(statbuf)) + { + status = (unsigned char*)std::malloc(nkw); + if (status == nullptr) + throw std::bad_alloc(); + stat_hold.reset(status); + } + size_t n_might_match = nkw; // At this point, any keyword might match + size_t n_does_match = 0; // but none of them definitely do + // Initialize all statuses to might_match, except for "" keywords are does_match + unsigned char* st = status; + for (auto ky = kb; ky != ke; ++ky, ++st) + { + if (!ky->empty()) + *st = might_match; + else + { + *st = does_match; + --n_might_match; + ++n_does_match; + } + } + // While there might be a match, test keywords against the next CharT + for (size_t indx = 0; is && n_might_match > 0; ++indx) + { + // Peek at the next CharT but don't consume it + auto ic = is.peek(); + if (ic == EOF) + { + is.setstate(std::ios::eofbit); + break; + } + auto c = static_cast(toupper(static_cast(ic))); + bool consume = false; + // For each keyword which might match, see if the indx character is c + // If a match if found, consume c + // If a match is found, and that is the last character in the keyword, + // then that keyword matches. + // If the keyword doesn't match this character, then change the keyword + // to doesn't match + st = status; + for (auto ky = kb; ky != ke; ++ky, ++st) + { + if (*st == might_match) + { + if (c == static_cast(toupper(static_cast((*ky)[indx])))) + { + consume = true; + if (ky->size() == indx+1) + { + *st = does_match; + --n_might_match; + ++n_does_match; + } + } + else + { + *st = doesnt_match; + --n_might_match; + } + } + } + // consume if we matched a character + if (consume) + { + (void)is.get(); + // If we consumed a character and there might be a matched keyword that + // was marked matched on a previous iteration, then such keywords + // are now marked as not matching. + if (n_might_match + n_does_match > 1) + { + st = status; + for (auto ky = kb; ky != ke; ++ky, ++st) + { + if (*st == does_match && ky->size() != indx+1) + { + *st = doesnt_match; + --n_does_match; + } + } + } + } + } + // We've exited the loop because we hit eof and/or we have no more "might matches". + // Return the first matching result + for (st = status; kb != ke; ++kb, ++st) + if (*st == does_match) + break; + if (kb == ke) + is.setstate(std::ios::failbit); + return kb; +} + +} // namespace detail + +#endif // ONLY_C_LOCALE + +template +std::basic_ostream& +to_stream(std::basic_ostream& os, const CharT* fmt, + const fields& fds, const std::string* abbrev, + const std::chrono::seconds* offset_sec) +{ +#if ONLY_C_LOCALE + using detail::weekday_names; + using detail::month_names; + using detail::ampm_names; +#endif + using detail::save_ostream; + using detail::get_units; + using detail::extract_weekday; + using detail::extract_month; + using std::ios; + using std::chrono::duration_cast; + using std::chrono::seconds; + using std::chrono::minutes; + using std::chrono::hours; + date::detail::save_ostream ss(os); + os.fill(' '); + os.flags(std::ios::skipws | std::ios::dec); + os.width(0); + tm tm{}; + bool insert_negative = fds.has_tod && fds.tod.to_duration() < Duration::zero(); +#if !ONLY_C_LOCALE + auto& facet = std::use_facet>(os.getloc()); +#endif + const CharT* command = nullptr; + CharT modified = CharT{}; + for (; *fmt; ++fmt) + { + switch (*fmt) + { + case 'a': + case 'A': + if (command) + { + if (modified == CharT{}) + { + tm.tm_wday = static_cast(extract_weekday(os, fds)); + if (os.fail()) + return os; +#if !ONLY_C_LOCALE + const CharT f[] = {'%', *fmt}; + facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); +#else // ONLY_C_LOCALE + os << weekday_names().first[tm.tm_wday+7*(*fmt == 'a')]; +#endif // ONLY_C_LOCALE + } + else + { + os << CharT{'%'} << modified << *fmt; + modified = CharT{}; + } + command = nullptr; + } + else + os << *fmt; + break; + case 'b': + case 'B': + case 'h': + if (command) + { + if (modified == CharT{}) + { + tm.tm_mon = static_cast(extract_month(os, fds)) - 1; +#if !ONLY_C_LOCALE + const CharT f[] = {'%', *fmt}; + facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); +#else // ONLY_C_LOCALE + os << month_names().first[tm.tm_mon+12*(*fmt != 'B')]; +#endif // ONLY_C_LOCALE + } + else + { + os << CharT{'%'} << modified << *fmt; + modified = CharT{}; + } + command = nullptr; + } + else + os << *fmt; + break; + case 'c': + case 'x': + if (command) + { + if (modified == CharT{'O'}) + os << CharT{'%'} << modified << *fmt; + else + { + if (!fds.ymd.ok()) + os.setstate(std::ios::failbit); + if (*fmt == 'c' && !fds.has_tod) + os.setstate(std::ios::failbit); +#if !ONLY_C_LOCALE + tm = std::tm{}; + auto const& ymd = fds.ymd; + auto ld = local_days(ymd); + if (*fmt == 'c') + { + tm.tm_sec = static_cast(fds.tod.seconds().count()); + tm.tm_min = static_cast(fds.tod.minutes().count()); + tm.tm_hour = static_cast(fds.tod.hours().count()); + } + tm.tm_mday = static_cast(static_cast(ymd.day())); + tm.tm_mon = static_cast(extract_month(os, fds) - 1); + tm.tm_year = static_cast(ymd.year()) - 1900; + tm.tm_wday = static_cast(extract_weekday(os, fds)); + if (os.fail()) + return os; + tm.tm_yday = static_cast((ld - local_days(ymd.year()/1/1)).count()); + CharT f[3] = {'%'}; + auto fe = std::begin(f) + 1; + if (modified == CharT{'E'}) + *fe++ = modified; + *fe++ = *fmt; + facet.put(os, os, os.fill(), &tm, std::begin(f), fe); +#else // ONLY_C_LOCALE + if (*fmt == 'c') + { + auto wd = static_cast(extract_weekday(os, fds)); + os << weekday_names().first[static_cast(wd)+7] + << ' '; + os << month_names().first[extract_month(os, fds)-1+12] << ' '; + auto d = static_cast(static_cast(fds.ymd.day())); + if (d < 10) + os << ' '; + os << d << ' ' + << make_time(duration_cast(fds.tod.to_duration())) + << ' ' << fds.ymd.year(); + + } + else // *fmt == 'x' + { + auto const& ymd = fds.ymd; + save_ostream _(os); + os.fill('0'); + os.flags(std::ios::dec | std::ios::right); + os.width(2); + os << static_cast(ymd.month()) << CharT{'/'}; + os.width(2); + os << static_cast(ymd.day()) << CharT{'/'}; + os.width(2); + os << static_cast(ymd.year()) % 100; + } +#endif // ONLY_C_LOCALE + } + command = nullptr; + modified = CharT{}; + } + else + os << *fmt; + break; + case 'C': + if (command) + { + if (modified == CharT{'O'}) + os << CharT{'%'} << modified << *fmt; + else + { + if (!fds.ymd.year().ok()) + os.setstate(std::ios::failbit); + auto y = static_cast(fds.ymd.year()); +#if !ONLY_C_LOCALE + if (modified == CharT{}) +#endif + { + save_ostream _(os); + os.fill('0'); + os.flags(std::ios::dec | std::ios::right); + if (y >= 0) + { + os.width(2); + os << y/100; + } + else + { + os << CharT{'-'}; + os.width(2); + os << -(y-99)/100; + } + } +#if !ONLY_C_LOCALE + else if (modified == CharT{'E'}) + { + tm.tm_year = y - 1900; + CharT f[3] = {'%', 'E', 'C'}; + facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); + } +#endif + } + command = nullptr; + modified = CharT{}; + } + else + os << *fmt; + break; + case 'd': + case 'e': + if (command) + { + if (modified == CharT{'E'}) + os << CharT{'%'} << modified << *fmt; + else + { + if (!fds.ymd.day().ok()) + os.setstate(std::ios::failbit); + auto d = static_cast(static_cast(fds.ymd.day())); +#if !ONLY_C_LOCALE + if (modified == CharT{}) +#endif + { + save_ostream _(os); + if (*fmt == CharT{'d'}) + os.fill('0'); + else + os.fill(' '); + os.flags(std::ios::dec | std::ios::right); + os.width(2); + os << d; + } +#if !ONLY_C_LOCALE + else if (modified == CharT{'O'}) + { + tm.tm_mday = d; + CharT f[3] = {'%', 'O', *fmt}; + facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); + } +#endif + } + command = nullptr; + modified = CharT{}; + } + else + os << *fmt; + break; + case 'D': + if (command) + { + if (modified == CharT{}) + { + if (!fds.ymd.ok()) + os.setstate(std::ios::failbit); + auto const& ymd = fds.ymd; + save_ostream _(os); + os.fill('0'); + os.flags(std::ios::dec | std::ios::right); + os.width(2); + os << static_cast(ymd.month()) << CharT{'/'}; + os.width(2); + os << static_cast(ymd.day()) << CharT{'/'}; + os.width(2); + os << static_cast(ymd.year()) % 100; + } + else + { + os << CharT{'%'} << modified << *fmt; + modified = CharT{}; + } + command = nullptr; + } + else + os << *fmt; + break; + case 'F': + if (command) + { + if (modified == CharT{}) + { + if (!fds.ymd.ok()) + os.setstate(std::ios::failbit); + auto const& ymd = fds.ymd; + save_ostream _(os); + os.imbue(std::locale::classic()); + os.fill('0'); + os.flags(std::ios::dec | std::ios::right); + os.width(4); + os << static_cast(ymd.year()) << CharT{'-'}; + os.width(2); + os << static_cast(ymd.month()) << CharT{'-'}; + os.width(2); + os << static_cast(ymd.day()); + } + else + { + os << CharT{'%'} << modified << *fmt; + modified = CharT{}; + } + command = nullptr; + } + else + os << *fmt; + break; + case 'g': + case 'G': + if (command) + { + if (modified == CharT{}) + { + if (!fds.ymd.ok()) + os.setstate(std::ios::failbit); + auto ld = local_days(fds.ymd); + auto y = year_month_day{ld + days{3}}.year(); + auto start = local_days((y-years{1})/December/Thursday[last]) + + (Monday-Thursday); + if (ld < start) + --y; + if (*fmt == CharT{'G'}) + os << y; + else + { + save_ostream _(os); + os.fill('0'); + os.flags(std::ios::dec | std::ios::right); + os.width(2); + os << std::abs(static_cast(y)) % 100; + } + } + else + { + os << CharT{'%'} << modified << *fmt; + modified = CharT{}; + } + command = nullptr; + } + else + os << *fmt; + break; + case 'H': + case 'I': + if (command) + { + if (modified == CharT{'E'}) + os << CharT{'%'} << modified << *fmt; + else + { + if (!fds.has_tod) + os.setstate(std::ios::failbit); + if (insert_negative) + { + os << '-'; + insert_negative = false; + } + auto hms = fds.tod; +#if !ONLY_C_LOCALE + if (modified == CharT{}) +#endif + { + auto h = *fmt == CharT{'I'} ? date::make12(hms.hours()) : hms.hours(); + if (h < hours{10}) + os << CharT{'0'}; + os << h.count(); + } +#if !ONLY_C_LOCALE + else if (modified == CharT{'O'}) + { + const CharT f[] = {'%', modified, *fmt}; + tm.tm_hour = static_cast(hms.hours().count()); + facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); + } +#endif + } + modified = CharT{}; + command = nullptr; + } + else + os << *fmt; + break; + case 'j': + if (command) + { + if (modified == CharT{}) + { + if (fds.ymd.ok() || fds.has_tod) + { + days doy; + if (fds.ymd.ok()) + { + auto ld = local_days(fds.ymd); + auto y = fds.ymd.year(); + doy = ld - local_days(y/January/1) + days{1}; + } + else + { + doy = duration_cast(fds.tod.to_duration()); + } + save_ostream _(os); + os.fill('0'); + os.flags(std::ios::dec | std::ios::right); + os.width(3); + os << doy.count(); + } + else + { + os.setstate(std::ios::failbit); + } + } + else + { + os << CharT{'%'} << modified << *fmt; + modified = CharT{}; + } + command = nullptr; + } + else + os << *fmt; + break; + case 'm': + if (command) + { + if (modified == CharT{'E'}) + os << CharT{'%'} << modified << *fmt; + else + { + if (!fds.ymd.month().ok()) + os.setstate(std::ios::failbit); + auto m = static_cast(fds.ymd.month()); +#if !ONLY_C_LOCALE + if (modified == CharT{}) +#endif + { + if (m < 10) + os << CharT{'0'}; + os << m; + } +#if !ONLY_C_LOCALE + else if (modified == CharT{'O'}) + { + const CharT f[] = {'%', modified, *fmt}; + tm.tm_mon = static_cast(m-1); + facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); + } +#endif + } + modified = CharT{}; + command = nullptr; + } + else + os << *fmt; + break; + case 'M': + if (command) + { + if (modified == CharT{'E'}) + os << CharT{'%'} << modified << *fmt; + else + { + if (!fds.has_tod) + os.setstate(std::ios::failbit); + if (insert_negative) + { + os << '-'; + insert_negative = false; + } +#if !ONLY_C_LOCALE + if (modified == CharT{}) +#endif + { + if (fds.tod.minutes() < minutes{10}) + os << CharT{'0'}; + os << fds.tod.minutes().count(); + } +#if !ONLY_C_LOCALE + else if (modified == CharT{'O'}) + { + const CharT f[] = {'%', modified, *fmt}; + tm.tm_min = static_cast(fds.tod.minutes().count()); + facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); + } +#endif + } + modified = CharT{}; + command = nullptr; + } + else + os << *fmt; + break; + case 'n': + if (command) + { + if (modified == CharT{}) + os << CharT{'\n'}; + else + { + os << CharT{'%'} << modified << *fmt; + modified = CharT{}; + } + command = nullptr; + } + else + os << *fmt; + break; + case 'p': + if (command) + { + if (modified == CharT{}) + { + if (!fds.has_tod) + os.setstate(std::ios::failbit); +#if !ONLY_C_LOCALE + const CharT f[] = {'%', *fmt}; + tm.tm_hour = static_cast(fds.tod.hours().count()); + facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); +#else + if (date::is_am(fds.tod.hours())) + os << ampm_names().first[0]; + else + os << ampm_names().first[1]; +#endif + } + else + { + os << CharT{'%'} << modified << *fmt; + } + modified = CharT{}; + command = nullptr; + } + else + os << *fmt; + break; + case 'Q': + case 'q': + if (command) + { + if (modified == CharT{}) + { + if (!fds.has_tod) + os.setstate(std::ios::failbit); + auto d = fds.tod.to_duration(); + if (*fmt == 'q') + os << get_units(typename decltype(d)::period::type{}); + else + os << d.count(); + } + else + { + os << CharT{'%'} << modified << *fmt; + } + modified = CharT{}; + command = nullptr; + } + else + os << *fmt; + break; + case 'r': + if (command) + { + if (modified == CharT{}) + { + if (!fds.has_tod) + os.setstate(std::ios::failbit); +#if !ONLY_C_LOCALE + const CharT f[] = {'%', *fmt}; + tm.tm_hour = static_cast(fds.tod.hours().count()); + tm.tm_min = static_cast(fds.tod.minutes().count()); + tm.tm_sec = static_cast(fds.tod.seconds().count()); + facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); +#else + hh_mm_ss tod(duration_cast(fds.tod.to_duration())); + save_ostream _(os); + os.fill('0'); + os.width(2); + os << date::make12(tod.hours()).count() << CharT{':'}; + os.width(2); + os << tod.minutes().count() << CharT{':'}; + os.width(2); + os << tod.seconds().count() << CharT{' '}; + if (date::is_am(tod.hours())) + os << ampm_names().first[0]; + else + os << ampm_names().first[1]; +#endif + } + else + { + os << CharT{'%'} << modified << *fmt; + } + modified = CharT{}; + command = nullptr; + } + else + os << *fmt; + break; + case 'R': + if (command) + { + if (modified == CharT{}) + { + if (!fds.has_tod) + os.setstate(std::ios::failbit); + if (fds.tod.hours() < hours{10}) + os << CharT{'0'}; + os << fds.tod.hours().count() << CharT{':'}; + if (fds.tod.minutes() < minutes{10}) + os << CharT{'0'}; + os << fds.tod.minutes().count(); + } + else + { + os << CharT{'%'} << modified << *fmt; + modified = CharT{}; + } + command = nullptr; + } + else + os << *fmt; + break; + case 'S': + if (command) + { + if (modified == CharT{'E'}) + os << CharT{'%'} << modified << *fmt; + else + { + if (!fds.has_tod) + os.setstate(std::ios::failbit); + if (insert_negative) + { + os << '-'; + insert_negative = false; + } +#if !ONLY_C_LOCALE + if (modified == CharT{}) +#endif + { + os << fds.tod.s_; + } +#if !ONLY_C_LOCALE + else if (modified == CharT{'O'}) + { + const CharT f[] = {'%', modified, *fmt}; + tm.tm_sec = static_cast(fds.tod.s_.seconds().count()); + facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); + } +#endif + } + modified = CharT{}; + command = nullptr; + } + else + os << *fmt; + break; + case 't': + if (command) + { + if (modified == CharT{}) + os << CharT{'\t'}; + else + { + os << CharT{'%'} << modified << *fmt; + modified = CharT{}; + } + command = nullptr; + } + else + os << *fmt; + break; + case 'T': + if (command) + { + if (modified == CharT{}) + { + if (!fds.has_tod) + os.setstate(std::ios::failbit); + os << fds.tod; + } + else + { + os << CharT{'%'} << modified << *fmt; + modified = CharT{}; + } + command = nullptr; + } + else + os << *fmt; + break; + case 'u': + if (command) + { + if (modified == CharT{'E'}) + os << CharT{'%'} << modified << *fmt; + else + { + auto wd = extract_weekday(os, fds); +#if !ONLY_C_LOCALE + if (modified == CharT{}) +#endif + { + os << (wd != 0 ? wd : 7u); + } +#if !ONLY_C_LOCALE + else if (modified == CharT{'O'}) + { + const CharT f[] = {'%', modified, *fmt}; + tm.tm_wday = static_cast(wd); + facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); + } +#endif + } + modified = CharT{}; + command = nullptr; + } + else + os << *fmt; + break; + case 'U': + if (command) + { + if (modified == CharT{'E'}) + os << CharT{'%'} << modified << *fmt; + else + { + auto const& ymd = fds.ymd; + if (!ymd.ok()) + os.setstate(std::ios::failbit); + auto ld = local_days(ymd); +#if !ONLY_C_LOCALE + if (modified == CharT{}) +#endif + { + auto st = local_days(Sunday[1]/January/ymd.year()); + if (ld < st) + os << CharT{'0'} << CharT{'0'}; + else + { + auto wn = duration_cast(ld - st).count() + 1; + if (wn < 10) + os << CharT{'0'}; + os << wn; + } + } + #if !ONLY_C_LOCALE + else if (modified == CharT{'O'}) + { + const CharT f[] = {'%', modified, *fmt}; + tm.tm_year = static_cast(ymd.year()) - 1900; + tm.tm_wday = static_cast(extract_weekday(os, fds)); + if (os.fail()) + return os; + tm.tm_yday = static_cast((ld - local_days(ymd.year()/1/1)).count()); + facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); + } +#endif + } + modified = CharT{}; + command = nullptr; + } + else + os << *fmt; + break; + case 'V': + if (command) + { + if (modified == CharT{'E'}) + os << CharT{'%'} << modified << *fmt; + else + { + if (!fds.ymd.ok()) + os.setstate(std::ios::failbit); + auto ld = local_days(fds.ymd); +#if !ONLY_C_LOCALE + if (modified == CharT{}) +#endif + { + auto y = year_month_day{ld + days{3}}.year(); + auto st = local_days((y-years{1})/12/Thursday[last]) + + (Monday-Thursday); + if (ld < st) + { + --y; + st = local_days((y - years{1})/12/Thursday[last]) + + (Monday-Thursday); + } + auto wn = duration_cast(ld - st).count() + 1; + if (wn < 10) + os << CharT{'0'}; + os << wn; + } +#if !ONLY_C_LOCALE + else if (modified == CharT{'O'}) + { + const CharT f[] = {'%', modified, *fmt}; + auto const& ymd = fds.ymd; + tm.tm_year = static_cast(ymd.year()) - 1900; + tm.tm_wday = static_cast(extract_weekday(os, fds)); + if (os.fail()) + return os; + tm.tm_yday = static_cast((ld - local_days(ymd.year()/1/1)).count()); + facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); + } +#endif + } + modified = CharT{}; + command = nullptr; + } + else + os << *fmt; + break; + case 'w': + if (command) + { + auto wd = extract_weekday(os, fds); + if (os.fail()) + return os; +#if !ONLY_C_LOCALE + if (modified == CharT{}) +#else + if (modified != CharT{'E'}) +#endif + { + os << wd; + } +#if !ONLY_C_LOCALE + else if (modified == CharT{'O'}) + { + const CharT f[] = {'%', modified, *fmt}; + tm.tm_wday = static_cast(wd); + facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); + } +#endif + else + { + os << CharT{'%'} << modified << *fmt; + } + modified = CharT{}; + command = nullptr; + } + else + os << *fmt; + break; + case 'W': + if (command) + { + if (modified == CharT{'E'}) + os << CharT{'%'} << modified << *fmt; + else + { + auto const& ymd = fds.ymd; + if (!ymd.ok()) + os.setstate(std::ios::failbit); + auto ld = local_days(ymd); +#if !ONLY_C_LOCALE + if (modified == CharT{}) +#endif + { + auto st = local_days(Monday[1]/January/ymd.year()); + if (ld < st) + os << CharT{'0'} << CharT{'0'}; + else + { + auto wn = duration_cast(ld - st).count() + 1; + if (wn < 10) + os << CharT{'0'}; + os << wn; + } + } +#if !ONLY_C_LOCALE + else if (modified == CharT{'O'}) + { + const CharT f[] = {'%', modified, *fmt}; + tm.tm_year = static_cast(ymd.year()) - 1900; + tm.tm_wday = static_cast(extract_weekday(os, fds)); + if (os.fail()) + return os; + tm.tm_yday = static_cast((ld - local_days(ymd.year()/1/1)).count()); + facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); + } +#endif + } + modified = CharT{}; + command = nullptr; + } + else + os << *fmt; + break; + case 'X': + if (command) + { + if (modified == CharT{'O'}) + os << CharT{'%'} << modified << *fmt; + else + { + if (!fds.has_tod) + os.setstate(std::ios::failbit); +#if !ONLY_C_LOCALE + tm = std::tm{}; + tm.tm_sec = static_cast(fds.tod.seconds().count()); + tm.tm_min = static_cast(fds.tod.minutes().count()); + tm.tm_hour = static_cast(fds.tod.hours().count()); + CharT f[3] = {'%'}; + auto fe = std::begin(f) + 1; + if (modified == CharT{'E'}) + *fe++ = modified; + *fe++ = *fmt; + facet.put(os, os, os.fill(), &tm, std::begin(f), fe); +#else + os << fds.tod; +#endif + } + command = nullptr; + modified = CharT{}; + } + else + os << *fmt; + break; + case 'y': + if (command) + { + if (!fds.ymd.year().ok()) + os.setstate(std::ios::failbit); + auto y = static_cast(fds.ymd.year()); +#if !ONLY_C_LOCALE + if (modified == CharT{}) + { +#endif + y = std::abs(y) % 100; + if (y < 10) + os << CharT{'0'}; + os << y; +#if !ONLY_C_LOCALE + } + else + { + const CharT f[] = {'%', modified, *fmt}; + tm.tm_year = y - 1900; + facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); + } +#endif + modified = CharT{}; + command = nullptr; + } + else + os << *fmt; + break; + case 'Y': + if (command) + { + if (modified == CharT{'O'}) + os << CharT{'%'} << modified << *fmt; + else + { + if (!fds.ymd.year().ok()) + os.setstate(std::ios::failbit); + auto y = fds.ymd.year(); +#if !ONLY_C_LOCALE + if (modified == CharT{}) +#endif + { + save_ostream _(os); + os.imbue(std::locale::classic()); + os << y; + } +#if !ONLY_C_LOCALE + else if (modified == CharT{'E'}) + { + const CharT f[] = {'%', modified, *fmt}; + tm.tm_year = static_cast(y) - 1900; + facet.put(os, os, os.fill(), &tm, std::begin(f), std::end(f)); + } +#endif + } + modified = CharT{}; + command = nullptr; + } + else + os << *fmt; + break; + case 'z': + if (command) + { + if (offset_sec == nullptr) + { + // Can not format %z with unknown offset + os.setstate(ios::failbit); + return os; + } + auto m = duration_cast(*offset_sec); + auto neg = m < minutes{0}; + m = date::abs(m); + auto h = duration_cast(m); + m -= h; + if (neg) + os << CharT{'-'}; + else + os << CharT{'+'}; + if (h < hours{10}) + os << CharT{'0'}; + os << h.count(); + if (modified != CharT{}) + os << CharT{':'}; + if (m < minutes{10}) + os << CharT{'0'}; + os << m.count(); + command = nullptr; + modified = CharT{}; + } + else + os << *fmt; + break; + case 'Z': + if (command) + { + if (modified == CharT{}) + { + if (abbrev == nullptr) + { + // Can not format %Z with unknown time_zone + os.setstate(ios::failbit); + return os; + } + for (auto c : *abbrev) + os << CharT(c); + } + else + { + os << CharT{'%'} << modified << *fmt; + modified = CharT{}; + } + command = nullptr; + } + else + os << *fmt; + break; + case 'E': + case 'O': + if (command) + { + if (modified == CharT{}) + { + modified = *fmt; + } + else + { + os << CharT{'%'} << modified << *fmt; + command = nullptr; + modified = CharT{}; + } + } + else + os << *fmt; + break; + case '%': + if (command) + { + if (modified == CharT{}) + { + os << CharT{'%'}; + command = nullptr; + } + else + { + os << CharT{'%'} << modified << CharT{'%'}; + command = nullptr; + modified = CharT{}; + } + } + else + command = fmt; + break; + default: + if (command) + { + os << CharT{'%'}; + command = nullptr; + } + if (modified != CharT{}) + { + os << modified; + modified = CharT{}; + } + os << *fmt; + break; + } + } + if (command) + os << CharT{'%'}; + if (modified != CharT{}) + os << modified; + return os; +} + +template +inline +std::basic_ostream& +to_stream(std::basic_ostream& os, const CharT* fmt, const year& y) +{ + using CT = std::chrono::seconds; + fields fds{y/0/0}; + return to_stream(os, fmt, fds); +} + +template +inline +std::basic_ostream& +to_stream(std::basic_ostream& os, const CharT* fmt, const month& m) +{ + using CT = std::chrono::seconds; + fields fds{m/0/nanyear}; + return to_stream(os, fmt, fds); +} + +template +inline +std::basic_ostream& +to_stream(std::basic_ostream& os, const CharT* fmt, const day& d) +{ + using CT = std::chrono::seconds; + fields fds{d/0/nanyear}; + return to_stream(os, fmt, fds); +} + +template +inline +std::basic_ostream& +to_stream(std::basic_ostream& os, const CharT* fmt, const weekday& wd) +{ + using CT = std::chrono::seconds; + fields fds{wd}; + return to_stream(os, fmt, fds); +} + +template +inline +std::basic_ostream& +to_stream(std::basic_ostream& os, const CharT* fmt, const year_month& ym) +{ + using CT = std::chrono::seconds; + fields fds{ym/0}; + return to_stream(os, fmt, fds); +} + +template +inline +std::basic_ostream& +to_stream(std::basic_ostream& os, const CharT* fmt, const month_day& md) +{ + using CT = std::chrono::seconds; + fields fds{md/nanyear}; + return to_stream(os, fmt, fds); +} + +template +inline +std::basic_ostream& +to_stream(std::basic_ostream& os, const CharT* fmt, + const year_month_day& ymd) +{ + using CT = std::chrono::seconds; + fields fds{ymd}; + return to_stream(os, fmt, fds); +} + +template +inline +std::basic_ostream& +to_stream(std::basic_ostream& os, const CharT* fmt, + const std::chrono::duration& d) +{ + using Duration = std::chrono::duration; + using CT = typename std::common_type::type; + fields fds{hh_mm_ss{d}}; + return to_stream(os, fmt, fds); +} + +template +std::basic_ostream& +to_stream(std::basic_ostream& os, const CharT* fmt, + const local_time& tp, const std::string* abbrev = nullptr, + const std::chrono::seconds* offset_sec = nullptr) +{ + using CT = typename std::common_type::type; + auto ld = std::chrono::time_point_cast(tp); + fields fds; + if (ld <= tp) + fds = fields{year_month_day{ld}, hh_mm_ss{tp-local_seconds{ld}}}; + else + fds = fields{year_month_day{ld - days{1}}, + hh_mm_ss{days{1} - (local_seconds{ld} - tp)}}; + return to_stream(os, fmt, fds, abbrev, offset_sec); +} + +template +std::basic_ostream& +to_stream(std::basic_ostream& os, const CharT* fmt, + const sys_time& tp) +{ + using std::chrono::seconds; + using CT = typename std::common_type::type; + const std::string abbrev("UTC"); + CONSTDATA seconds offset{0}; + auto sd = std::chrono::time_point_cast(tp); + fields fds; + if (sd <= tp) + fds = fields{year_month_day{sd}, hh_mm_ss{tp-sys_seconds{sd}}}; + else + fds = fields{year_month_day{sd - days{1}}, + hh_mm_ss{days{1} - (sys_seconds{sd} - tp)}}; + return to_stream(os, fmt, fds, &abbrev, &offset); +} + +// format + +template +auto +format(const std::locale& loc, const CharT* fmt, const Streamable& tp) + -> decltype(to_stream(std::declval&>(), fmt, tp), + std::basic_string{}) +{ + std::basic_ostringstream os; + os.exceptions(std::ios::failbit | std::ios::badbit); + os.imbue(loc); + to_stream(os, fmt, tp); + return os.str(); +} + +template +auto +format(const CharT* fmt, const Streamable& tp) + -> decltype(to_stream(std::declval&>(), fmt, tp), + std::basic_string{}) +{ + std::basic_ostringstream os; + os.exceptions(std::ios::failbit | std::ios::badbit); + to_stream(os, fmt, tp); + return os.str(); +} + +template +auto +format(const std::locale& loc, const std::basic_string& fmt, + const Streamable& tp) + -> decltype(to_stream(std::declval&>(), fmt.c_str(), tp), + std::basic_string{}) +{ + std::basic_ostringstream os; + os.exceptions(std::ios::failbit | std::ios::badbit); + os.imbue(loc); + to_stream(os, fmt.c_str(), tp); + return os.str(); +} + +template +auto +format(const std::basic_string& fmt, const Streamable& tp) + -> decltype(to_stream(std::declval&>(), fmt.c_str(), tp), + std::basic_string{}) +{ + std::basic_ostringstream os; + os.exceptions(std::ios::failbit | std::ios::badbit); + to_stream(os, fmt.c_str(), tp); + return os.str(); +} + +// parse + +namespace detail +{ + +template +bool +read_char(std::basic_istream& is, CharT fmt, std::ios::iostate& err) +{ + auto ic = is.get(); + if (Traits::eq_int_type(ic, Traits::eof()) || + !Traits::eq(Traits::to_char_type(ic), fmt)) + { + err |= std::ios::failbit; + is.setstate(std::ios::failbit); + return false; + } + return true; +} + +template +unsigned +read_unsigned(std::basic_istream& is, unsigned m = 1, unsigned M = 10) +{ + unsigned x = 0; + unsigned count = 0; + while (true) + { + auto ic = is.peek(); + if (Traits::eq_int_type(ic, Traits::eof())) + break; + auto c = static_cast(Traits::to_char_type(ic)); + if (!('0' <= c && c <= '9')) + break; + (void)is.get(); + ++count; + x = 10*x + static_cast(c - '0'); + if (count == M) + break; + } + if (count < m) + is.setstate(std::ios::failbit); + return x; +} + +template +int +read_signed(std::basic_istream& is, unsigned m = 1, unsigned M = 10) +{ + auto ic = is.peek(); + if (!Traits::eq_int_type(ic, Traits::eof())) + { + auto c = static_cast(Traits::to_char_type(ic)); + if (('0' <= c && c <= '9') || c == '-' || c == '+') + { + if (c == '-' || c == '+') + (void)is.get(); + auto x = static_cast(read_unsigned(is, std::max(m, 1u), M)); + if (!is.fail()) + { + if (c == '-') + x = -x; + return x; + } + } + } + if (m > 0) + is.setstate(std::ios::failbit); + return 0; +} + +template +long double +read_long_double(std::basic_istream& is, unsigned m = 1, unsigned M = 10) +{ + unsigned count = 0; + unsigned fcount = 0; + unsigned long long i = 0; + unsigned long long f = 0; + bool parsing_fraction = false; +#if ONLY_C_LOCALE + typename Traits::int_type decimal_point = '.'; +#else + auto decimal_point = Traits::to_int_type( + std::use_facet>(is.getloc()).decimal_point()); +#endif + while (true) + { + auto ic = is.peek(); + if (Traits::eq_int_type(ic, Traits::eof())) + break; + if (Traits::eq_int_type(ic, decimal_point)) + { + decimal_point = Traits::eof(); + parsing_fraction = true; + } + else + { + auto c = static_cast(Traits::to_char_type(ic)); + if (!('0' <= c && c <= '9')) + break; + if (!parsing_fraction) + { + i = 10*i + static_cast(c - '0'); + } + else + { + f = 10*f + static_cast(c - '0'); + ++fcount; + } + } + (void)is.get(); + if (++count == M) + break; + } + if (count < m) + { + is.setstate(std::ios::failbit); + return 0; + } + return static_cast(i) + static_cast(f)/std::pow(10.L, fcount); +} + +struct rs +{ + int& i; + unsigned m; + unsigned M; +}; + +struct ru +{ + int& i; + unsigned m; + unsigned M; +}; + +struct rld +{ + long double& i; + unsigned m; + unsigned M; +}; + +template +void +read(std::basic_istream&) +{ +} + +template +void +read(std::basic_istream& is, CharT a0, Args&& ...args); + +template +void +read(std::basic_istream& is, rs a0, Args&& ...args); + +template +void +read(std::basic_istream& is, ru a0, Args&& ...args); + +template +void +read(std::basic_istream& is, int a0, Args&& ...args); + +template +void +read(std::basic_istream& is, rld a0, Args&& ...args); + +template +void +read(std::basic_istream& is, CharT a0, Args&& ...args) +{ + // No-op if a0 == CharT{} + if (a0 != CharT{}) + { + auto ic = is.peek(); + if (Traits::eq_int_type(ic, Traits::eof())) + { + is.setstate(std::ios::failbit | std::ios::eofbit); + return; + } + if (!Traits::eq(Traits::to_char_type(ic), a0)) + { + is.setstate(std::ios::failbit); + return; + } + (void)is.get(); + } + read(is, std::forward(args)...); +} + +template +void +read(std::basic_istream& is, rs a0, Args&& ...args) +{ + auto x = read_signed(is, a0.m, a0.M); + if (is.fail()) + return; + a0.i = x; + read(is, std::forward(args)...); +} + +template +void +read(std::basic_istream& is, ru a0, Args&& ...args) +{ + auto x = read_unsigned(is, a0.m, a0.M); + if (is.fail()) + return; + a0.i = static_cast(x); + read(is, std::forward(args)...); +} + +template +void +read(std::basic_istream& is, int a0, Args&& ...args) +{ + if (a0 != -1) + { + auto u = static_cast(a0); + CharT buf[std::numeric_limits::digits10+2u] = {}; + auto e = buf; + do + { + *e++ = static_cast(CharT(u % 10) + CharT{'0'}); + u /= 10; + } while (u > 0); + std::reverse(buf, e); + for (auto p = buf; p != e && is.rdstate() == std::ios::goodbit; ++p) + read(is, *p); + } + if (is.rdstate() == std::ios::goodbit) + read(is, std::forward(args)...); +} + +template +void +read(std::basic_istream& is, rld a0, Args&& ...args) +{ + auto x = read_long_double(is, a0.m, a0.M); + if (is.fail()) + return; + a0.i = x; + read(is, std::forward(args)...); +} + +template +inline +void +checked_set(T& value, T from, T not_a_value, std::basic_ios& is) +{ + if (!is.fail()) + { + if (value == not_a_value) + value = std::move(from); + else if (value != from) + is.setstate(std::ios::failbit); + } +} + +} // namespace detail; + +template > +std::basic_istream& +from_stream(std::basic_istream& is, const CharT* fmt, + fields& fds, std::basic_string* abbrev, + std::chrono::minutes* offset) +{ + using std::numeric_limits; + using std::ios; + using std::chrono::duration; + using std::chrono::duration_cast; + using std::chrono::seconds; + using std::chrono::minutes; + using std::chrono::hours; + using detail::round_i; + typename std::basic_istream::sentry ok{is, true}; + if (ok) + { + date::detail::save_istream ss(is); + is.fill(' '); + is.flags(std::ios::skipws | std::ios::dec); + is.width(0); +#if !ONLY_C_LOCALE + auto& f = std::use_facet>(is.getloc()); + std::tm tm{}; +#endif + const CharT* command = nullptr; + auto modified = CharT{}; + auto width = -1; + + CONSTDATA int not_a_year = numeric_limits::min(); + CONSTDATA int not_a_2digit_year = 100; + CONSTDATA int not_a_century = not_a_year / 100; + CONSTDATA int not_a_month = 0; + CONSTDATA int not_a_day = 0; + CONSTDATA int not_a_hour = numeric_limits::min(); + CONSTDATA int not_a_hour_12_value = 0; + CONSTDATA int not_a_minute = not_a_hour; + CONSTDATA Duration not_a_second = Duration::min(); + CONSTDATA int not_a_doy = -1; + CONSTDATA int not_a_weekday = 8; + CONSTDATA int not_a_week_num = 100; + CONSTDATA int not_a_ampm = -1; + CONSTDATA minutes not_a_offset = minutes::min(); + + int Y = not_a_year; // c, F, Y * + int y = not_a_2digit_year; // D, x, y * + int g = not_a_2digit_year; // g * + int G = not_a_year; // G * + int C = not_a_century; // C * + int m = not_a_month; // b, B, h, m, c, D, F, x * + int d = not_a_day; // c, d, D, e, F, x * + int j = not_a_doy; // j * + int wd = not_a_weekday; // a, A, u, w * + int H = not_a_hour; // c, H, R, T, X * + int I = not_a_hour_12_value; // I, r * + int p = not_a_ampm; // p, r * + int M = not_a_minute; // c, M, r, R, T, X * + Duration s = not_a_second; // c, r, S, T, X * + int U = not_a_week_num; // U * + int V = not_a_week_num; // V * + int W = not_a_week_num; // W * + std::basic_string temp_abbrev; // Z * + minutes temp_offset = not_a_offset; // z * + + using detail::read; + using detail::rs; + using detail::ru; + using detail::rld; + using detail::checked_set; + for (; *fmt != CharT{} && !is.fail(); ++fmt) + { + switch (*fmt) + { + case 'a': + case 'A': + case 'u': + case 'w': // wd: a, A, u, w + if (command) + { + int trial_wd = not_a_weekday; + if (*fmt == 'a' || *fmt == 'A') + { + if (modified == CharT{}) + { +#if !ONLY_C_LOCALE + ios::iostate err = ios::goodbit; + f.get(is, nullptr, is, err, &tm, command, fmt+1); + is.setstate(err); + if (!is.fail()) + trial_wd = tm.tm_wday; +#else + auto nm = detail::weekday_names(); + auto i = detail::scan_keyword(is, nm.first, nm.second) - nm.first; + if (!is.fail()) + trial_wd = i % 7; +#endif + } + else + read(is, CharT{'%'}, width, modified, *fmt); + } + else // *fmt == 'u' || *fmt == 'w' + { +#if !ONLY_C_LOCALE + if (modified == CharT{}) +#else + if (modified != CharT{'E'}) +#endif + { + read(is, ru{trial_wd, 1, width == -1 ? + 1u : static_cast(width)}); + if (!is.fail()) + { + if (*fmt == 'u') + { + if (!(1 <= trial_wd && trial_wd <= 7)) + { + trial_wd = not_a_weekday; + is.setstate(ios::failbit); + } + else if (trial_wd == 7) + trial_wd = 0; + } + else // *fmt == 'w' + { + if (!(0 <= trial_wd && trial_wd <= 6)) + { + trial_wd = not_a_weekday; + is.setstate(ios::failbit); + } + } + } + } +#if !ONLY_C_LOCALE + else if (modified == CharT{'O'}) + { + ios::iostate err = ios::goodbit; + f.get(is, nullptr, is, err, &tm, command, fmt+1); + is.setstate(err); + if (!is.fail()) + trial_wd = tm.tm_wday; + } +#endif + else + read(is, CharT{'%'}, width, modified, *fmt); + } + if (trial_wd != not_a_weekday) + checked_set(wd, trial_wd, not_a_weekday, is); + } + else // !command + read(is, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + break; + case 'b': + case 'B': + case 'h': + if (command) + { + if (modified == CharT{}) + { + int ttm = not_a_month; +#if !ONLY_C_LOCALE + ios::iostate err = ios::goodbit; + f.get(is, nullptr, is, err, &tm, command, fmt+1); + if ((err & ios::failbit) == 0) + ttm = tm.tm_mon + 1; + is.setstate(err); +#else + auto nm = detail::month_names(); + auto i = detail::scan_keyword(is, nm.first, nm.second) - nm.first; + if (!is.fail()) + ttm = i % 12 + 1; +#endif + checked_set(m, ttm, not_a_month, is); + } + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'c': + if (command) + { + if (modified != CharT{'O'}) + { +#if !ONLY_C_LOCALE + ios::iostate err = ios::goodbit; + f.get(is, nullptr, is, err, &tm, command, fmt+1); + if ((err & ios::failbit) == 0) + { + checked_set(Y, tm.tm_year + 1900, not_a_year, is); + checked_set(m, tm.tm_mon + 1, not_a_month, is); + checked_set(d, tm.tm_mday, not_a_day, is); + checked_set(H, tm.tm_hour, not_a_hour, is); + checked_set(M, tm.tm_min, not_a_minute, is); + checked_set(s, duration_cast(seconds{tm.tm_sec}), + not_a_second, is); + } + is.setstate(err); +#else + // "%a %b %e %T %Y" + auto nm = detail::weekday_names(); + auto i = detail::scan_keyword(is, nm.first, nm.second) - nm.first; + checked_set(wd, static_cast(i % 7), not_a_weekday, is); + ws(is); + nm = detail::month_names(); + i = detail::scan_keyword(is, nm.first, nm.second) - nm.first; + checked_set(m, static_cast(i % 12 + 1), not_a_month, is); + ws(is); + int td = not_a_day; + read(is, rs{td, 1, 2}); + checked_set(d, td, not_a_day, is); + ws(is); + using dfs = detail::decimal_format_seconds; + CONSTDATA auto w = Duration::period::den == 1 ? 2 : 3 + dfs::width; + int tH; + int tM; + long double S{}; + read(is, ru{tH, 1, 2}, CharT{':'}, ru{tM, 1, 2}, + CharT{':'}, rld{S, 1, w}); + checked_set(H, tH, not_a_hour, is); + checked_set(M, tM, not_a_minute, is); + checked_set(s, round_i(duration{S}), + not_a_second, is); + ws(is); + int tY = not_a_year; + read(is, rs{tY, 1, 4u}); + checked_set(Y, tY, not_a_year, is); +#endif + } + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'x': + if (command) + { + if (modified != CharT{'O'}) + { +#if !ONLY_C_LOCALE + ios::iostate err = ios::goodbit; + f.get(is, nullptr, is, err, &tm, command, fmt+1); + if ((err & ios::failbit) == 0) + { + checked_set(Y, tm.tm_year + 1900, not_a_year, is); + checked_set(m, tm.tm_mon + 1, not_a_month, is); + checked_set(d, tm.tm_mday, not_a_day, is); + } + is.setstate(err); +#else + // "%m/%d/%y" + int ty = not_a_2digit_year; + int tm = not_a_month; + int td = not_a_day; + read(is, ru{tm, 1, 2}, CharT{'/'}, ru{td, 1, 2}, CharT{'/'}, + rs{ty, 1, 2}); + checked_set(y, ty, not_a_2digit_year, is); + checked_set(m, tm, not_a_month, is); + checked_set(d, td, not_a_day, is); +#endif + } + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'X': + if (command) + { + if (modified != CharT{'O'}) + { +#if !ONLY_C_LOCALE + ios::iostate err = ios::goodbit; + f.get(is, nullptr, is, err, &tm, command, fmt+1); + if ((err & ios::failbit) == 0) + { + checked_set(H, tm.tm_hour, not_a_hour, is); + checked_set(M, tm.tm_min, not_a_minute, is); + checked_set(s, duration_cast(seconds{tm.tm_sec}), + not_a_second, is); + } + is.setstate(err); +#else + // "%T" + using dfs = detail::decimal_format_seconds; + CONSTDATA auto w = Duration::period::den == 1 ? 2 : 3 + dfs::width; + int tH = not_a_hour; + int tM = not_a_minute; + long double S{}; + read(is, ru{tH, 1, 2}, CharT{':'}, ru{tM, 1, 2}, + CharT{':'}, rld{S, 1, w}); + checked_set(H, tH, not_a_hour, is); + checked_set(M, tM, not_a_minute, is); + checked_set(s, round_i(duration{S}), + not_a_second, is); +#endif + } + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'C': + if (command) + { + int tC = not_a_century; +#if !ONLY_C_LOCALE + if (modified == CharT{}) + { +#endif + read(is, rs{tC, 1, width == -1 ? 2u : static_cast(width)}); +#if !ONLY_C_LOCALE + } + else + { + ios::iostate err = ios::goodbit; + f.get(is, nullptr, is, err, &tm, command, fmt+1); + if ((err & ios::failbit) == 0) + { + auto tY = tm.tm_year + 1900; + tC = (tY >= 0 ? tY : tY-99) / 100; + } + is.setstate(err); + } +#endif + checked_set(C, tC, not_a_century, is); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'D': + if (command) + { + if (modified == CharT{}) + { + int tn = not_a_month; + int td = not_a_day; + int ty = not_a_2digit_year; + read(is, ru{tn, 1, 2}, CharT{'\0'}, CharT{'/'}, CharT{'\0'}, + ru{td, 1, 2}, CharT{'\0'}, CharT{'/'}, CharT{'\0'}, + rs{ty, 1, 2}); + checked_set(y, ty, not_a_2digit_year, is); + checked_set(m, tn, not_a_month, is); + checked_set(d, td, not_a_day, is); + } + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'F': + if (command) + { + if (modified == CharT{}) + { + int tY = not_a_year; + int tn = not_a_month; + int td = not_a_day; + read(is, rs{tY, 1, width == -1 ? 4u : static_cast(width)}, + CharT{'-'}, ru{tn, 1, 2}, CharT{'-'}, ru{td, 1, 2}); + checked_set(Y, tY, not_a_year, is); + checked_set(m, tn, not_a_month, is); + checked_set(d, td, not_a_day, is); + } + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'd': + case 'e': + if (command) + { +#if !ONLY_C_LOCALE + if (modified == CharT{}) +#else + if (modified != CharT{'E'}) +#endif + { + int td = not_a_day; + read(is, rs{td, 1, width == -1 ? 2u : static_cast(width)}); + checked_set(d, td, not_a_day, is); + } +#if !ONLY_C_LOCALE + else if (modified == CharT{'O'}) + { + ios::iostate err = ios::goodbit; + f.get(is, nullptr, is, err, &tm, command, fmt+1); + command = nullptr; + width = -1; + modified = CharT{}; + if ((err & ios::failbit) == 0) + checked_set(d, tm.tm_mday, not_a_day, is); + is.setstate(err); + } +#endif + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'H': + if (command) + { +#if !ONLY_C_LOCALE + if (modified == CharT{}) +#else + if (modified != CharT{'E'}) +#endif + { + int tH = not_a_hour; + read(is, ru{tH, 1, width == -1 ? 2u : static_cast(width)}); + checked_set(H, tH, not_a_hour, is); + } +#if !ONLY_C_LOCALE + else if (modified == CharT{'O'}) + { + ios::iostate err = ios::goodbit; + f.get(is, nullptr, is, err, &tm, command, fmt+1); + if ((err & ios::failbit) == 0) + checked_set(H, tm.tm_hour, not_a_hour, is); + is.setstate(err); + } +#endif + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'I': + if (command) + { + if (modified == CharT{}) + { + int tI = not_a_hour_12_value; + // reads in an hour into I, but most be in [1, 12] + read(is, rs{tI, 1, width == -1 ? 2u : static_cast(width)}); + if (!(1 <= tI && tI <= 12)) + is.setstate(ios::failbit); + checked_set(I, tI, not_a_hour_12_value, is); + } + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'j': + if (command) + { + if (modified == CharT{}) + { + int tj = not_a_doy; + read(is, ru{tj, 1, width == -1 ? 3u : static_cast(width)}); + checked_set(j, tj, not_a_doy, is); + } + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'M': + if (command) + { +#if !ONLY_C_LOCALE + if (modified == CharT{}) +#else + if (modified != CharT{'E'}) +#endif + { + int tM = not_a_minute; + read(is, ru{tM, 1, width == -1 ? 2u : static_cast(width)}); + checked_set(M, tM, not_a_minute, is); + } +#if !ONLY_C_LOCALE + else if (modified == CharT{'O'}) + { + ios::iostate err = ios::goodbit; + f.get(is, nullptr, is, err, &tm, command, fmt+1); + if ((err & ios::failbit) == 0) + checked_set(M, tm.tm_min, not_a_minute, is); + is.setstate(err); + } +#endif + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'm': + if (command) + { +#if !ONLY_C_LOCALE + if (modified == CharT{}) +#else + if (modified != CharT{'E'}) +#endif + { + int tn = not_a_month; + read(is, rs{tn, 1, width == -1 ? 2u : static_cast(width)}); + checked_set(m, tn, not_a_month, is); + } +#if !ONLY_C_LOCALE + else if (modified == CharT{'O'}) + { + ios::iostate err = ios::goodbit; + f.get(is, nullptr, is, err, &tm, command, fmt+1); + if ((err & ios::failbit) == 0) + checked_set(m, tm.tm_mon + 1, not_a_month, is); + is.setstate(err); + } +#endif + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'n': + case 't': + if (command) + { + if (modified == CharT{}) + { + // %n matches a single white space character + // %t matches 0 or 1 white space characters + auto ic = is.peek(); + if (Traits::eq_int_type(ic, Traits::eof())) + { + ios::iostate err = ios::eofbit; + if (*fmt == 'n') + err |= ios::failbit; + is.setstate(err); + break; + } + if (isspace(ic)) + { + (void)is.get(); + } + else if (*fmt == 'n') + is.setstate(ios::failbit); + } + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'p': + if (command) + { + if (modified == CharT{}) + { + int tp = not_a_ampm; +#if !ONLY_C_LOCALE + tm = std::tm{}; + tm.tm_hour = 1; + ios::iostate err = ios::goodbit; + f.get(is, nullptr, is, err, &tm, command, fmt+1); + is.setstate(err); + if (tm.tm_hour == 1) + tp = 0; + else if (tm.tm_hour == 13) + tp = 1; + else + is.setstate(err); +#else + auto nm = detail::ampm_names(); + auto i = detail::scan_keyword(is, nm.first, nm.second) - nm.first; + tp = static_cast(i); +#endif + checked_set(p, tp, not_a_ampm, is); + } + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + + break; + case 'r': + if (command) + { + if (modified == CharT{}) + { +#if !ONLY_C_LOCALE + ios::iostate err = ios::goodbit; + f.get(is, nullptr, is, err, &tm, command, fmt+1); + if ((err & ios::failbit) == 0) + { + checked_set(H, tm.tm_hour, not_a_hour, is); + checked_set(M, tm.tm_min, not_a_hour, is); + checked_set(s, duration_cast(seconds{tm.tm_sec}), + not_a_second, is); + } + is.setstate(err); +#else + // "%I:%M:%S %p" + using dfs = detail::decimal_format_seconds; + CONSTDATA auto w = Duration::period::den == 1 ? 2 : 3 + dfs::width; + long double S{}; + int tI = not_a_hour_12_value; + int tM = not_a_minute; + read(is, ru{tI, 1, 2}, CharT{':'}, ru{tM, 1, 2}, + CharT{':'}, rld{S, 1, w}); + checked_set(I, tI, not_a_hour_12_value, is); + checked_set(M, tM, not_a_minute, is); + checked_set(s, round_i(duration{S}), + not_a_second, is); + ws(is); + auto nm = detail::ampm_names(); + auto i = detail::scan_keyword(is, nm.first, nm.second) - nm.first; + checked_set(p, static_cast(i), not_a_ampm, is); +#endif + } + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'R': + if (command) + { + if (modified == CharT{}) + { + int tH = not_a_hour; + int tM = not_a_minute; + read(is, ru{tH, 1, 2}, CharT{'\0'}, CharT{':'}, CharT{'\0'}, + ru{tM, 1, 2}, CharT{'\0'}); + checked_set(H, tH, not_a_hour, is); + checked_set(M, tM, not_a_minute, is); + } + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'S': + if (command) + { + #if !ONLY_C_LOCALE + if (modified == CharT{}) +#else + if (modified != CharT{'E'}) +#endif + { + using dfs = detail::decimal_format_seconds; + CONSTDATA auto w = Duration::period::den == 1 ? 2 : 3 + dfs::width; + long double S{}; + read(is, rld{S, 1, width == -1 ? w : static_cast(width)}); + checked_set(s, round_i(duration{S}), + not_a_second, is); + } +#if !ONLY_C_LOCALE + else if (modified == CharT{'O'}) + { + ios::iostate err = ios::goodbit; + f.get(is, nullptr, is, err, &tm, command, fmt+1); + if ((err & ios::failbit) == 0) + checked_set(s, duration_cast(seconds{tm.tm_sec}), + not_a_second, is); + is.setstate(err); + } +#endif + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'T': + if (command) + { + if (modified == CharT{}) + { + using dfs = detail::decimal_format_seconds; + CONSTDATA auto w = Duration::period::den == 1 ? 2 : 3 + dfs::width; + int tH = not_a_hour; + int tM = not_a_minute; + long double S{}; + read(is, ru{tH, 1, 2}, CharT{':'}, ru{tM, 1, 2}, + CharT{':'}, rld{S, 1, w}); + checked_set(H, tH, not_a_hour, is); + checked_set(M, tM, not_a_minute, is); + checked_set(s, round_i(duration{S}), + not_a_second, is); + } + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'Y': + if (command) + { +#if !ONLY_C_LOCALE + if (modified == CharT{}) +#else + if (modified != CharT{'O'}) +#endif + { + int tY = not_a_year; + read(is, rs{tY, 1, width == -1 ? 4u : static_cast(width)}); + checked_set(Y, tY, not_a_year, is); + } +#if !ONLY_C_LOCALE + else if (modified == CharT{'E'}) + { + ios::iostate err = ios::goodbit; + f.get(is, nullptr, is, err, &tm, command, fmt+1); + if ((err & ios::failbit) == 0) + checked_set(Y, tm.tm_year + 1900, not_a_year, is); + is.setstate(err); + } +#endif + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'y': + if (command) + { +#if !ONLY_C_LOCALE + if (modified == CharT{}) +#endif + { + int ty = not_a_2digit_year; + read(is, ru{ty, 1, width == -1 ? 2u : static_cast(width)}); + checked_set(y, ty, not_a_2digit_year, is); + } +#if !ONLY_C_LOCALE + else + { + ios::iostate err = ios::goodbit; + f.get(is, nullptr, is, err, &tm, command, fmt+1); + if ((err & ios::failbit) == 0) + checked_set(Y, tm.tm_year + 1900, not_a_year, is); + is.setstate(err); + } +#endif + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'g': + if (command) + { + if (modified == CharT{}) + { + int tg = not_a_2digit_year; + read(is, ru{tg, 1, width == -1 ? 2u : static_cast(width)}); + checked_set(g, tg, not_a_2digit_year, is); + } + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'G': + if (command) + { + if (modified == CharT{}) + { + int tG = not_a_year; + read(is, rs{tG, 1, width == -1 ? 4u : static_cast(width)}); + checked_set(G, tG, not_a_year, is); + } + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'U': + if (command) + { + if (modified == CharT{}) + { + int tU = not_a_week_num; + read(is, ru{tU, 1, width == -1 ? 2u : static_cast(width)}); + checked_set(U, tU, not_a_week_num, is); + } + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'V': + if (command) + { + if (modified == CharT{}) + { + int tV = not_a_week_num; + read(is, ru{tV, 1, width == -1 ? 2u : static_cast(width)}); + checked_set(V, tV, not_a_week_num, is); + } + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'W': + if (command) + { + if (modified == CharT{}) + { + int tW = not_a_week_num; + read(is, ru{tW, 1, width == -1 ? 2u : static_cast(width)}); + checked_set(W, tW, not_a_week_num, is); + } + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'E': + case 'O': + if (command) + { + if (modified == CharT{}) + { + modified = *fmt; + } + else + { + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + } + else + read(is, *fmt); + break; + case '%': + if (command) + { + if (modified == CharT{}) + read(is, *fmt); + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + command = fmt; + break; + case 'z': + if (command) + { + int tH, tM; + minutes toff = not_a_offset; + bool neg = false; + auto ic = is.peek(); + if (!Traits::eq_int_type(ic, Traits::eof())) + { + auto c = static_cast(Traits::to_char_type(ic)); + if (c == '-') + neg = true; + } + if (modified == CharT{}) + { + read(is, rs{tH, 2, 2}); + if (!is.fail()) + toff = hours{std::abs(tH)}; + if (is.good()) + { + ic = is.peek(); + if (!Traits::eq_int_type(ic, Traits::eof())) + { + auto c = static_cast(Traits::to_char_type(ic)); + if ('0' <= c && c <= '9') + { + read(is, ru{tM, 2, 2}); + if (!is.fail()) + toff += minutes{tM}; + } + } + } + } + else + { + read(is, rs{tH, 1, 2}); + if (!is.fail()) + toff = hours{std::abs(tH)}; + if (is.good()) + { + ic = is.peek(); + if (!Traits::eq_int_type(ic, Traits::eof())) + { + auto c = static_cast(Traits::to_char_type(ic)); + if (c == ':') + { + (void)is.get(); + read(is, ru{tM, 2, 2}); + if (!is.fail()) + toff += minutes{tM}; + } + } + } + } + if (neg) + toff = -toff; + checked_set(temp_offset, toff, not_a_offset, is); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + case 'Z': + if (command) + { + if (modified == CharT{}) + { + std::basic_string buf; + while (is.rdstate() == std::ios::goodbit) + { + auto i = is.rdbuf()->sgetc(); + if (Traits::eq_int_type(i, Traits::eof())) + { + is.setstate(ios::eofbit); + break; + } + auto wc = Traits::to_char_type(i); + auto c = static_cast(wc); + // is c a valid time zone name or abbreviation character? + if (!(CharT{1} < wc && wc < CharT{127}) || !(isalnum(c) || + c == '_' || c == '/' || c == '-' || c == '+')) + break; + buf.push_back(c); + is.rdbuf()->sbumpc(); + } + if (buf.empty()) + is.setstate(ios::failbit); + checked_set(temp_abbrev, buf, {}, is); + } + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + else + read(is, *fmt); + break; + default: + if (command) + { + if (width == -1 && modified == CharT{} && '0' <= *fmt && *fmt <= '9') + { + width = static_cast(*fmt) - '0'; + while ('0' <= fmt[1] && fmt[1] <= '9') + width = 10*width + static_cast(*++fmt) - '0'; + } + else + { + if (modified == CharT{}) + read(is, CharT{'%'}, width, *fmt); + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + } + else // !command + { + if (isspace(static_cast(*fmt))) + { + // space matches 0 or more white space characters + if (is.good()) + ws(is); + } + else + read(is, *fmt); + } + break; + } + } + // is.fail() || *fmt == CharT{} + if (is.rdstate() == ios::goodbit && command) + { + if (modified == CharT{}) + read(is, CharT{'%'}, width); + else + read(is, CharT{'%'}, width, modified); + } + if (!is.fail()) + { + if (y != not_a_2digit_year) + { + // Convert y and an optional C to Y + if (!(0 <= y && y <= 99)) + goto broken; + if (C == not_a_century) + { + if (Y == not_a_year) + { + if (y >= 69) + C = 19; + else + C = 20; + } + else + { + C = (Y >= 0 ? Y : Y-100) / 100; + } + } + int tY; + if (C >= 0) + tY = 100*C + y; + else + tY = 100*(C+1) - (y == 0 ? 100 : y); + if (Y != not_a_year && Y != tY) + goto broken; + Y = tY; + } + if (g != not_a_2digit_year) + { + // Convert g and an optional C to G + if (!(0 <= g && g <= 99)) + goto broken; + if (C == not_a_century) + { + if (G == not_a_year) + { + if (g >= 69) + C = 19; + else + C = 20; + } + else + { + C = (G >= 0 ? G : G-100) / 100; + } + } + int tG; + if (C >= 0) + tG = 100*C + g; + else + tG = 100*(C+1) - (g == 0 ? 100 : g); + if (G != not_a_year && G != tG) + goto broken; + G = tG; + } + if (Y < static_cast(year::min()) || Y > static_cast(year::max())) + Y = not_a_year; + bool computed = false; + if (G != not_a_year && V != not_a_week_num && wd != not_a_weekday) + { + year_month_day ymd_trial = sys_days(year{G-1}/December/Thursday[last]) + + (Monday-Thursday) + weeks{V-1} + + (weekday{static_cast(wd)}-Monday); + if (Y == not_a_year) + Y = static_cast(ymd_trial.year()); + else if (year{Y} != ymd_trial.year()) + goto broken; + if (m == not_a_month) + m = static_cast(static_cast(ymd_trial.month())); + else if (month(static_cast(m)) != ymd_trial.month()) + goto broken; + if (d == not_a_day) + d = static_cast(static_cast(ymd_trial.day())); + else if (day(static_cast(d)) != ymd_trial.day()) + goto broken; + computed = true; + } + if (Y != not_a_year && U != not_a_week_num && wd != not_a_weekday) + { + year_month_day ymd_trial = sys_days(year{Y}/January/Sunday[1]) + + weeks{U-1} + + (weekday{static_cast(wd)} - Sunday); + if (Y == not_a_year) + Y = static_cast(ymd_trial.year()); + else if (year{Y} != ymd_trial.year()) + goto broken; + if (m == not_a_month) + m = static_cast(static_cast(ymd_trial.month())); + else if (month(static_cast(m)) != ymd_trial.month()) + goto broken; + if (d == not_a_day) + d = static_cast(static_cast(ymd_trial.day())); + else if (day(static_cast(d)) != ymd_trial.day()) + goto broken; + computed = true; + } + if (Y != not_a_year && W != not_a_week_num && wd != not_a_weekday) + { + year_month_day ymd_trial = sys_days(year{Y}/January/Monday[1]) + + weeks{W-1} + + (weekday{static_cast(wd)} - Monday); + if (Y == not_a_year) + Y = static_cast(ymd_trial.year()); + else if (year{Y} != ymd_trial.year()) + goto broken; + if (m == not_a_month) + m = static_cast(static_cast(ymd_trial.month())); + else if (month(static_cast(m)) != ymd_trial.month()) + goto broken; + if (d == not_a_day) + d = static_cast(static_cast(ymd_trial.day())); + else if (day(static_cast(d)) != ymd_trial.day()) + goto broken; + computed = true; + } + if (j != not_a_doy && Y != not_a_year) + { + auto ymd_trial = year_month_day{local_days(year{Y}/1/1) + days{j-1}}; + if (m == not_a_month) + m = static_cast(static_cast(ymd_trial.month())); + else if (month(static_cast(m)) != ymd_trial.month()) + goto broken; + if (d == not_a_day) + d = static_cast(static_cast(ymd_trial.day())); + else if (day(static_cast(d)) != ymd_trial.day()) + goto broken; + j = not_a_doy; + } + auto ymd = year{Y}/m/d; + if (ymd.ok()) + { + if (wd == not_a_weekday) + wd = static_cast((weekday(sys_days(ymd)) - Sunday).count()); + else if (wd != static_cast((weekday(sys_days(ymd)) - Sunday).count())) + goto broken; + if (!computed) + { + if (G != not_a_year || V != not_a_week_num) + { + sys_days sd = ymd; + auto G_trial = year_month_day{sd + days{3}}.year(); + auto start = sys_days((G_trial - years{1})/December/Thursday[last]) + + (Monday - Thursday); + if (sd < start) + { + --G_trial; + if (V != not_a_week_num) + start = sys_days((G_trial - years{1})/December/Thursday[last]) + + (Monday - Thursday); + } + if (G != not_a_year && G != static_cast(G_trial)) + goto broken; + if (V != not_a_week_num) + { + auto V_trial = duration_cast(sd - start).count() + 1; + if (V != V_trial) + goto broken; + } + } + if (U != not_a_week_num) + { + auto start = sys_days(Sunday[1]/January/ymd.year()); + auto U_trial = floor(sys_days(ymd) - start).count() + 1; + if (U != U_trial) + goto broken; + } + if (W != not_a_week_num) + { + auto start = sys_days(Monday[1]/January/ymd.year()); + auto W_trial = floor(sys_days(ymd) - start).count() + 1; + if (W != W_trial) + goto broken; + } + } + } + fds.ymd = ymd; + if (I != not_a_hour_12_value) + { + if (!(1 <= I && I <= 12)) + goto broken; + if (p != not_a_ampm) + { + // p is in [0, 1] == [AM, PM] + // Store trial H in I + if (I == 12) + --p; + I += p*12; + // Either set H from I or make sure H and I are consistent + if (H == not_a_hour) + H = I; + else if (I != H) + goto broken; + } + else // p == not_a_ampm + { + // if H, make sure H and I could be consistent + if (H != not_a_hour) + { + if (I == 12) + { + if (H != 0 && H != 12) + goto broken; + } + else if (!(I == H || I == H+12)) + { + goto broken; + } + } + else // I is ambiguous, AM or PM? + goto broken; + } + } + if (H != not_a_hour) + { + fds.has_tod = true; + fds.tod = hh_mm_ss{hours{H}}; + } + if (M != not_a_minute) + { + fds.has_tod = true; + fds.tod.m_ = minutes{M}; + } + if (s != not_a_second) + { + fds.has_tod = true; + fds.tod.s_ = detail::decimal_format_seconds{s}; + } + if (j != not_a_doy) + { + fds.has_tod = true; + fds.tod.h_ += hours{days{j}}; + } + if (wd != not_a_weekday) + fds.wd = weekday{static_cast(wd)}; + if (abbrev != nullptr) + *abbrev = std::move(temp_abbrev); + if (offset != nullptr && temp_offset != not_a_offset) + *offset = temp_offset; + } + return is; + } +broken: + is.setstate(ios::failbit); + return is; +} + +template > +std::basic_istream& +from_stream(std::basic_istream& is, const CharT* fmt, year& y, + std::basic_string* abbrev = nullptr, + std::chrono::minutes* offset = nullptr) +{ + using CT = std::chrono::seconds; + fields fds{}; + date::from_stream(is, fmt, fds, abbrev, offset); + if (!fds.ymd.year().ok()) + is.setstate(std::ios::failbit); + if (!is.fail()) + y = fds.ymd.year(); + return is; +} + +template > +std::basic_istream& +from_stream(std::basic_istream& is, const CharT* fmt, month& m, + std::basic_string* abbrev = nullptr, + std::chrono::minutes* offset = nullptr) +{ + using CT = std::chrono::seconds; + fields fds{}; + date::from_stream(is, fmt, fds, abbrev, offset); + if (!fds.ymd.month().ok()) + is.setstate(std::ios::failbit); + if (!is.fail()) + m = fds.ymd.month(); + return is; +} + +template > +std::basic_istream& +from_stream(std::basic_istream& is, const CharT* fmt, day& d, + std::basic_string* abbrev = nullptr, + std::chrono::minutes* offset = nullptr) +{ + using CT = std::chrono::seconds; + fields fds{}; + date::from_stream(is, fmt, fds, abbrev, offset); + if (!fds.ymd.day().ok()) + is.setstate(std::ios::failbit); + if (!is.fail()) + d = fds.ymd.day(); + return is; +} + +template > +std::basic_istream& +from_stream(std::basic_istream& is, const CharT* fmt, weekday& wd, + std::basic_string* abbrev = nullptr, + std::chrono::minutes* offset = nullptr) +{ + using CT = std::chrono::seconds; + fields fds{}; + date::from_stream(is, fmt, fds, abbrev, offset); + if (!fds.wd.ok()) + is.setstate(std::ios::failbit); + if (!is.fail()) + wd = fds.wd; + return is; +} + +template > +std::basic_istream& +from_stream(std::basic_istream& is, const CharT* fmt, year_month& ym, + std::basic_string* abbrev = nullptr, + std::chrono::minutes* offset = nullptr) +{ + using CT = std::chrono::seconds; + fields fds{}; + date::from_stream(is, fmt, fds, abbrev, offset); + if (!fds.ymd.month().ok()) + is.setstate(std::ios::failbit); + if (!is.fail()) + ym = fds.ymd.year()/fds.ymd.month(); + return is; +} + +template > +std::basic_istream& +from_stream(std::basic_istream& is, const CharT* fmt, month_day& md, + std::basic_string* abbrev = nullptr, + std::chrono::minutes* offset = nullptr) +{ + using CT = std::chrono::seconds; + fields fds{}; + date::from_stream(is, fmt, fds, abbrev, offset); + if (!fds.ymd.month().ok() || !fds.ymd.day().ok()) + is.setstate(std::ios::failbit); + if (!is.fail()) + md = fds.ymd.month()/fds.ymd.day(); + return is; +} + +template > +std::basic_istream& +from_stream(std::basic_istream& is, const CharT* fmt, + year_month_day& ymd, std::basic_string* abbrev = nullptr, + std::chrono::minutes* offset = nullptr) +{ + using CT = std::chrono::seconds; + fields fds{}; + date::from_stream(is, fmt, fds, abbrev, offset); + if (!fds.ymd.ok()) + is.setstate(std::ios::failbit); + if (!is.fail()) + ymd = fds.ymd; + return is; +} + +template > +std::basic_istream& +from_stream(std::basic_istream& is, const CharT* fmt, + sys_time& tp, std::basic_string* abbrev = nullptr, + std::chrono::minutes* offset = nullptr) +{ + using CT = typename std::common_type::type; + using detail::round_i; + std::chrono::minutes offset_local{}; + auto offptr = offset ? offset : &offset_local; + fields fds{}; + fds.has_tod = true; + date::from_stream(is, fmt, fds, abbrev, offptr); + if (!fds.ymd.ok() || !fds.tod.in_conventional_range()) + is.setstate(std::ios::failbit); + if (!is.fail()) + tp = round_i(sys_days(fds.ymd) - *offptr + fds.tod.to_duration()); + return is; +} + +template > +std::basic_istream& +from_stream(std::basic_istream& is, const CharT* fmt, + local_time& tp, std::basic_string* abbrev = nullptr, + std::chrono::minutes* offset = nullptr) +{ + using CT = typename std::common_type::type; + using detail::round_i; + fields fds{}; + fds.has_tod = true; + date::from_stream(is, fmt, fds, abbrev, offset); + if (!fds.ymd.ok() || !fds.tod.in_conventional_range()) + is.setstate(std::ios::failbit); + if (!is.fail()) + tp = round_i(local_seconds{local_days(fds.ymd)} + fds.tod.to_duration()); + return is; +} + +template > +std::basic_istream& +from_stream(std::basic_istream& is, const CharT* fmt, + std::chrono::duration& d, + std::basic_string* abbrev = nullptr, + std::chrono::minutes* offset = nullptr) +{ + using Duration = std::chrono::duration; + using CT = typename std::common_type::type; + using detail::round_i; + fields fds{}; + date::from_stream(is, fmt, fds, abbrev, offset); + if (!fds.has_tod) + is.setstate(std::ios::failbit); + if (!is.fail()) + d = round_i(fds.tod.to_duration()); + return is; +} + +template , + class Alloc = std::allocator> +struct parse_manip +{ + const std::basic_string format_; + Parsable& tp_; + std::basic_string* abbrev_; + std::chrono::minutes* offset_; + +public: + parse_manip(std::basic_string format, Parsable& tp, + std::basic_string* abbrev = nullptr, + std::chrono::minutes* offset = nullptr) + : format_(std::move(format)) + , tp_(tp) + , abbrev_(abbrev) + , offset_(offset) + {} + +#if HAS_STRING_VIEW + parse_manip(const CharT* format, Parsable& tp, + std::basic_string* abbrev = nullptr, + std::chrono::minutes* offset = nullptr) + : format_(format) + , tp_(tp) + , abbrev_(abbrev) + , offset_(offset) + {} + + parse_manip(std::basic_string_view format, Parsable& tp, + std::basic_string* abbrev = nullptr, + std::chrono::minutes* offset = nullptr) + : format_(format) + , tp_(tp) + , abbrev_(abbrev) + , offset_(offset) + {} +#endif // HAS_STRING_VIEW +}; + +template +std::basic_istream& +operator>>(std::basic_istream& is, + const parse_manip& x) +{ + return date::from_stream(is, x.format_.c_str(), x.tp_, x.abbrev_, x.offset_); +} + +template +inline +auto +parse(const std::basic_string& format, Parsable& tp) + -> decltype(date::from_stream(std::declval&>(), + format.c_str(), tp), + parse_manip{format, tp}) +{ + return {format, tp}; +} + +template +inline +auto +parse(const std::basic_string& format, Parsable& tp, + std::basic_string& abbrev) + -> decltype(date::from_stream(std::declval&>(), + format.c_str(), tp, &abbrev), + parse_manip{format, tp, &abbrev}) +{ + return {format, tp, &abbrev}; +} + +template +inline +auto +parse(const std::basic_string& format, Parsable& tp, + std::chrono::minutes& offset) + -> decltype(date::from_stream(std::declval&>(), + format.c_str(), tp, + std::declval*>(), + &offset), + parse_manip{format, tp, nullptr, &offset}) +{ + return {format, tp, nullptr, &offset}; +} + +template +inline +auto +parse(const std::basic_string& format, Parsable& tp, + std::basic_string& abbrev, std::chrono::minutes& offset) + -> decltype(date::from_stream(std::declval&>(), + format.c_str(), tp, &abbrev, &offset), + parse_manip{format, tp, &abbrev, &offset}) +{ + return {format, tp, &abbrev, &offset}; +} + +// const CharT* formats + +template +inline +auto +parse(const CharT* format, Parsable& tp) + -> decltype(date::from_stream(std::declval&>(), format, tp), + parse_manip{format, tp}) +{ + return {format, tp}; +} + +template +inline +auto +parse(const CharT* format, Parsable& tp, std::basic_string& abbrev) + -> decltype(date::from_stream(std::declval&>(), format, + tp, &abbrev), + parse_manip{format, tp, &abbrev}) +{ + return {format, tp, &abbrev}; +} + +template +inline +auto +parse(const CharT* format, Parsable& tp, std::chrono::minutes& offset) + -> decltype(date::from_stream(std::declval&>(), format, + tp, std::declval*>(), &offset), + parse_manip{format, tp, nullptr, &offset}) +{ + return {format, tp, nullptr, &offset}; +} + +template +inline +auto +parse(const CharT* format, Parsable& tp, + std::basic_string& abbrev, std::chrono::minutes& offset) + -> decltype(date::from_stream(std::declval&>(), format, + tp, &abbrev, &offset), + parse_manip{format, tp, &abbrev, &offset}) +{ + return {format, tp, &abbrev, &offset}; +} + +// duration streaming + +template +inline +std::basic_ostream& +operator<<(std::basic_ostream& os, + const std::chrono::duration& d) +{ + return os << detail::make_string::from(d.count()) + + detail::get_units(typename Period::type{}); +} + +} // namespace date + +#ifdef _MSC_VER +# pragma warning(pop) +#endif + +#ifdef __GNUC__ +# pragma GCC diagnostic pop +#endif + +#endif // DATE_H diff --git a/src/Common/linux/fast_float.h b/src/Common/linux/fast_float.h new file mode 100644 index 00000000..9faa9cff --- /dev/null +++ b/src/Common/linux/fast_float.h @@ -0,0 +1,2980 @@ +// fast_float by Daniel Lemire +// fast_float by João Paulo Magalhaes +// +// with contributions from Eugene Golushkov +// with contributions from Maksim Kita +// with contributions from Marcin Wojdyr +// with contributions from Neal Richardson +// with contributions from Tim Paine +// with contributions from Fabio Pellacini +// +// Licensed under the Apache License, Version 2.0, or the +// MIT License at your option. This file may not be copied, +// modified, or distributed except according to those terms. +// +// MIT License Notice +// +// MIT License +// +// Copyright (c) 2021 The fast_float authors +// +// Permission is hereby granted, free of charge, to any +// person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the +// Software without restriction, including without +// limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice +// shall be included in all copies or substantial portions +// of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// +// Apache License (Version 2.0) Notice +// +// Copyright 2021 The fast_float authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// + +#ifndef FASTFLOAT_FAST_FLOAT_H +#define FASTFLOAT_FAST_FLOAT_H + +#include + +namespace fast_float { +enum chars_format { + scientific = 1<<0, + fixed = 1<<2, + hex = 1<<3, + general = fixed | scientific +}; + + +struct from_chars_result { + const char *ptr; + std::errc ec; +}; + +struct parse_options { + constexpr explicit parse_options(chars_format fmt = chars_format::general, + char dot = '.') + : format(fmt), decimal_point(dot) {} + + /** Which number formats are accepted */ + chars_format format; + /** The character used as decimal point */ + char decimal_point; +}; + +/** + * This function parses the character sequence [first,last) for a number. It parses floating-point numbers expecting + * a locale-indepent format equivalent to what is used by std::strtod in the default ("C") locale. + * The resulting floating-point value is the closest floating-point values (using either float or double), + * using the "round to even" convention for values that would otherwise fall right in-between two values. + * That is, we provide exact parsing according to the IEEE standard. + * + * Given a successful parse, the pointer (`ptr`) in the returned value is set to point right after the + * parsed number, and the `value` referenced is set to the parsed value. In case of error, the returned + * `ec` contains a representative error, otherwise the default (`std::errc()`) value is stored. + * + * The implementation does not throw and does not allocate memory (e.g., with `new` or `malloc`). + * + * Like the C++17 standard, the `fast_float::from_chars` functions take an optional last argument of + * the type `fast_float::chars_format`. It is a bitset value: we check whether + * `fmt & fast_float::chars_format::fixed` and `fmt & fast_float::chars_format::scientific` are set + * to determine whether we allowe the fixed point and scientific notation respectively. + * The default is `fast_float::chars_format::general` which allows both `fixed` and `scientific`. + */ +template +from_chars_result from_chars(const char *first, const char *last, + T &value, chars_format fmt = chars_format::general) noexcept; + +/** + * Like from_chars, but accepts an `options` argument to govern number parsing. + */ +template +from_chars_result from_chars_advanced(const char *first, const char *last, + T &value, parse_options options) noexcept; + +} +#endif // FASTFLOAT_FAST_FLOAT_H + +#ifndef FASTFLOAT_FLOAT_COMMON_H +#define FASTFLOAT_FLOAT_COMMON_H + +#include +#include +#include +#include +#include + +#if (defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) \ + || defined(__amd64) || defined(__aarch64__) || defined(_M_ARM64) \ + || defined(__MINGW64__) \ + || defined(__s390x__) \ + || (defined(__ppc64__) || defined(__PPC64__) || defined(__ppc64le__) || defined(__PPC64LE__)) \ + || defined(__EMSCRIPTEN__)) +#define FASTFLOAT_64BIT +#elif (defined(__i386) || defined(__i386__) || defined(_M_IX86) \ + || defined(__arm__) || defined(_M_ARM) \ + || defined(__MINGW32__)) +#define FASTFLOAT_32BIT +#else + // Need to check incrementally, since SIZE_MAX is a size_t, avoid overflow. + // We can never tell the register width, but the SIZE_MAX is a good approximation. + // UINTPTR_MAX and INTPTR_MAX are optional, so avoid them for max portability. + #if SIZE_MAX == 0xffff + #error Unknown platform (16-bit, unsupported) + #elif SIZE_MAX == 0xffffffff + #define FASTFLOAT_32BIT + #elif SIZE_MAX == 0xffffffffffffffff + #define FASTFLOAT_64BIT + #else + #error Unknown platform (not 32-bit, not 64-bit?) + #endif +#endif + +#if ((defined(_WIN32) || defined(_WIN64)) && !defined(__clang__)) +#include +#endif + +#if defined(_MSC_VER) && !defined(__clang__) +#define FASTFLOAT_VISUAL_STUDIO 1 +#endif + +#ifdef _WIN32 +#define FASTFLOAT_IS_BIG_ENDIAN 0 +#else +#if defined(__APPLE__) || defined(__FreeBSD__) +#include +#elif defined(sun) || defined(__sun) +#include +#else +#include +#endif +# +#ifndef __BYTE_ORDER__ +// safe choice +#define FASTFLOAT_IS_BIG_ENDIAN 0 +#endif +# +#ifndef __ORDER_LITTLE_ENDIAN__ +// safe choice +#define FASTFLOAT_IS_BIG_ENDIAN 0 +#endif +# +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define FASTFLOAT_IS_BIG_ENDIAN 0 +#else +#define FASTFLOAT_IS_BIG_ENDIAN 1 +#endif +#endif + +#ifdef FASTFLOAT_VISUAL_STUDIO +#define fastfloat_really_inline __forceinline +#else +#define fastfloat_really_inline inline __attribute__((always_inline)) +#endif + +#ifndef FASTFLOAT_ASSERT +#define FASTFLOAT_ASSERT(x) { if (!(x)) abort(); } +#endif + +#ifndef FASTFLOAT_DEBUG_ASSERT +#include +#define FASTFLOAT_DEBUG_ASSERT(x) assert(x) +#endif + +// rust style `try!()` macro, or `?` operator +#define FASTFLOAT_TRY(x) { if (!(x)) return false; } + +namespace fast_float { + +// Compares two ASCII strings in a case insensitive manner. +inline bool fastfloat_strncasecmp(const char *input1, const char *input2, + size_t length) { + char running_diff{0}; + for (size_t i = 0; i < length; i++) { + running_diff |= (input1[i] ^ input2[i]); + } + return (running_diff == 0) || (running_diff == 32); +} + +#ifndef FLT_EVAL_METHOD +#error "FLT_EVAL_METHOD should be defined, please include cfloat." +#endif + +// a pointer and a length to a contiguous block of memory +template +struct span { + const T* ptr; + size_t length; + span(const T* _ptr, size_t _length) : ptr(_ptr), length(_length) {} + span() : ptr(nullptr), length(0) {} + + constexpr size_t len() const noexcept { + return length; + } + + const T& operator[](size_t index) const noexcept { + FASTFLOAT_DEBUG_ASSERT(index < length); + return ptr[index]; + } +}; + +struct value128 { + uint64_t low; + uint64_t high; + value128(uint64_t _low, uint64_t _high) : low(_low), high(_high) {} + value128() : low(0), high(0) {} +}; + +/* result might be undefined when input_num is zero */ +fastfloat_really_inline int leading_zeroes(uint64_t input_num) { + assert(input_num > 0); +#ifdef FASTFLOAT_VISUAL_STUDIO + #if defined(_M_X64) || defined(_M_ARM64) + unsigned long leading_zero = 0; + // Search the mask data from most significant bit (MSB) + // to least significant bit (LSB) for a set bit (1). + _BitScanReverse64(&leading_zero, input_num); + return (int)(63 - leading_zero); + #else + int last_bit = 0; + if(input_num & uint64_t(0xffffffff00000000)) input_num >>= 32, last_bit |= 32; + if(input_num & uint64_t( 0xffff0000)) input_num >>= 16, last_bit |= 16; + if(input_num & uint64_t( 0xff00)) input_num >>= 8, last_bit |= 8; + if(input_num & uint64_t( 0xf0)) input_num >>= 4, last_bit |= 4; + if(input_num & uint64_t( 0xc)) input_num >>= 2, last_bit |= 2; + if(input_num & uint64_t( 0x2)) input_num >>= 1, last_bit |= 1; + return 63 - last_bit; + #endif +#else + return __builtin_clzll(input_num); +#endif +} + +#ifdef FASTFLOAT_32BIT + +// slow emulation routine for 32-bit +fastfloat_really_inline uint64_t emulu(uint32_t x, uint32_t y) { + return x * (uint64_t)y; +} + +// slow emulation routine for 32-bit +#if !defined(__MINGW64__) +fastfloat_really_inline uint64_t _umul128(uint64_t ab, uint64_t cd, + uint64_t *hi) { + uint64_t ad = emulu((uint32_t)(ab >> 32), (uint32_t)cd); + uint64_t bd = emulu((uint32_t)ab, (uint32_t)cd); + uint64_t adbc = ad + emulu((uint32_t)ab, (uint32_t)(cd >> 32)); + uint64_t adbc_carry = !!(adbc < ad); + uint64_t lo = bd + (adbc << 32); + *hi = emulu((uint32_t)(ab >> 32), (uint32_t)(cd >> 32)) + (adbc >> 32) + + (adbc_carry << 32) + !!(lo < bd); + return lo; +} +#endif // !__MINGW64__ + +#endif // FASTFLOAT_32BIT + + +// compute 64-bit a*b +fastfloat_really_inline value128 full_multiplication(uint64_t a, + uint64_t b) { + value128 answer; +#ifdef _M_ARM64 + // ARM64 has native support for 64-bit multiplications, no need to emulate + answer.high = __umulh(a, b); + answer.low = a * b; +#elif defined(FASTFLOAT_32BIT) || (defined(_WIN64) && !defined(__clang__)) + answer.low = _umul128(a, b, &answer.high); // _umul128 not available on ARM64 +#elif defined(FASTFLOAT_64BIT) + __uint128_t r = ((__uint128_t)a) * b; + answer.low = uint64_t(r); + answer.high = uint64_t(r >> 64); +#else + #error Not implemented +#endif + return answer; +} + +struct adjusted_mantissa { + uint64_t mantissa{0}; + int32_t power2{0}; // a negative value indicates an invalid result + adjusted_mantissa() = default; + bool operator==(const adjusted_mantissa &o) const { + return mantissa == o.mantissa && power2 == o.power2; + } + bool operator!=(const adjusted_mantissa &o) const { + return mantissa != o.mantissa || power2 != o.power2; + } +}; + +// Bias so we can get the real exponent with an invalid adjusted_mantissa. +constexpr static int32_t invalid_am_bias = -0x8000; + +constexpr static double powers_of_ten_double[] = { + 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, + 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22}; +constexpr static float powers_of_ten_float[] = {1e0, 1e1, 1e2, 1e3, 1e4, 1e5, + 1e6, 1e7, 1e8, 1e9, 1e10}; + +template struct binary_format { + using equiv_uint = typename std::conditional::type; + + static inline constexpr int mantissa_explicit_bits(); + static inline constexpr int minimum_exponent(); + static inline constexpr int infinite_power(); + static inline constexpr int sign_index(); + static inline constexpr int min_exponent_fast_path(); + static inline constexpr int max_exponent_fast_path(); + static inline constexpr int max_exponent_round_to_even(); + static inline constexpr int min_exponent_round_to_even(); + static inline constexpr uint64_t max_mantissa_fast_path(); + static inline constexpr int largest_power_of_ten(); + static inline constexpr int smallest_power_of_ten(); + static inline constexpr T exact_power_of_ten(int64_t power); + static inline constexpr size_t max_digits(); + static inline constexpr equiv_uint exponent_mask(); + static inline constexpr equiv_uint mantissa_mask(); + static inline constexpr equiv_uint hidden_bit_mask(); +}; + +template <> inline constexpr int binary_format::mantissa_explicit_bits() { + return 52; +} +template <> inline constexpr int binary_format::mantissa_explicit_bits() { + return 23; +} + +template <> inline constexpr int binary_format::max_exponent_round_to_even() { + return 23; +} + +template <> inline constexpr int binary_format::max_exponent_round_to_even() { + return 10; +} + +template <> inline constexpr int binary_format::min_exponent_round_to_even() { + return -4; +} + +template <> inline constexpr int binary_format::min_exponent_round_to_even() { + return -17; +} + +template <> inline constexpr int binary_format::minimum_exponent() { + return -1023; +} +template <> inline constexpr int binary_format::minimum_exponent() { + return -127; +} + +template <> inline constexpr int binary_format::infinite_power() { + return 0x7FF; +} +template <> inline constexpr int binary_format::infinite_power() { + return 0xFF; +} + +template <> inline constexpr int binary_format::sign_index() { return 63; } +template <> inline constexpr int binary_format::sign_index() { return 31; } + +template <> inline constexpr int binary_format::min_exponent_fast_path() { +#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) + return 0; +#else + return -22; +#endif +} +template <> inline constexpr int binary_format::min_exponent_fast_path() { +#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) + return 0; +#else + return -10; +#endif +} + +template <> inline constexpr int binary_format::max_exponent_fast_path() { + return 22; +} +template <> inline constexpr int binary_format::max_exponent_fast_path() { + return 10; +} + +template <> inline constexpr uint64_t binary_format::max_mantissa_fast_path() { + return uint64_t(2) << mantissa_explicit_bits(); +} +template <> inline constexpr uint64_t binary_format::max_mantissa_fast_path() { + return uint64_t(2) << mantissa_explicit_bits(); +} + +template <> +inline constexpr double binary_format::exact_power_of_ten(int64_t power) { + return powers_of_ten_double[power]; +} +template <> +inline constexpr float binary_format::exact_power_of_ten(int64_t power) { + + return powers_of_ten_float[power]; +} + + +template <> +inline constexpr int binary_format::largest_power_of_ten() { + return 308; +} +template <> +inline constexpr int binary_format::largest_power_of_ten() { + return 38; +} + +template <> +inline constexpr int binary_format::smallest_power_of_ten() { + return -342; +} +template <> +inline constexpr int binary_format::smallest_power_of_ten() { + return -65; +} + +template <> inline constexpr size_t binary_format::max_digits() { + return 769; +} +template <> inline constexpr size_t binary_format::max_digits() { + return 114; +} + +template <> inline constexpr binary_format::equiv_uint + binary_format::exponent_mask() { + return 0x7F800000; +} +template <> inline constexpr binary_format::equiv_uint + binary_format::exponent_mask() { + return 0x7FF0000000000000; +} + +template <> inline constexpr binary_format::equiv_uint + binary_format::mantissa_mask() { + return 0x007FFFFF; +} +template <> inline constexpr binary_format::equiv_uint + binary_format::mantissa_mask() { + return 0x000FFFFFFFFFFFFF; +} + +template <> inline constexpr binary_format::equiv_uint + binary_format::hidden_bit_mask() { + return 0x00800000; +} +template <> inline constexpr binary_format::equiv_uint + binary_format::hidden_bit_mask() { + return 0x0010000000000000; +} + +template +fastfloat_really_inline void to_float(bool negative, adjusted_mantissa am, T &value) { + uint64_t word = am.mantissa; + word |= uint64_t(am.power2) << binary_format::mantissa_explicit_bits(); + word = negative + ? word | (uint64_t(1) << binary_format::sign_index()) : word; +#if FASTFLOAT_IS_BIG_ENDIAN == 1 + if (std::is_same::value) { + ::memcpy(&value, (char *)&word + 4, sizeof(T)); // extract value at offset 4-7 if float on big-endian + } else { + ::memcpy(&value, &word, sizeof(T)); + } +#else + // For little-endian systems: + ::memcpy(&value, &word, sizeof(T)); +#endif +} + +} // namespace fast_float + +#endif + +#ifndef FASTFLOAT_ASCII_NUMBER_H +#define FASTFLOAT_ASCII_NUMBER_H + +#include +#include +#include +#include + + +namespace fast_float { + +// Next function can be micro-optimized, but compilers are entirely +// able to optimize it well. +fastfloat_really_inline bool is_integer(char c) noexcept { return c >= '0' && c <= '9'; } + +fastfloat_really_inline uint64_t byteswap(uint64_t val) { + return (val & 0xFF00000000000000) >> 56 + | (val & 0x00FF000000000000) >> 40 + | (val & 0x0000FF0000000000) >> 24 + | (val & 0x000000FF00000000) >> 8 + | (val & 0x00000000FF000000) << 8 + | (val & 0x0000000000FF0000) << 24 + | (val & 0x000000000000FF00) << 40 + | (val & 0x00000000000000FF) << 56; +} + +fastfloat_really_inline uint64_t read_u64(const char *chars) { + uint64_t val; + ::memcpy(&val, chars, sizeof(uint64_t)); +#if FASTFLOAT_IS_BIG_ENDIAN == 1 + // Need to read as-if the number was in little-endian order. + val = byteswap(val); +#endif + return val; +} + +fastfloat_really_inline void write_u64(uint8_t *chars, uint64_t val) { +#if FASTFLOAT_IS_BIG_ENDIAN == 1 + // Need to read as-if the number was in little-endian order. + val = byteswap(val); +#endif + ::memcpy(chars, &val, sizeof(uint64_t)); +} + +// credit @aqrit +fastfloat_really_inline uint32_t parse_eight_digits_unrolled(uint64_t val) { + const uint64_t mask = 0x000000FF000000FF; + const uint64_t mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32) + const uint64_t mul2 = 0x0000271000000001; // 1 + (10000ULL << 32) + val -= 0x3030303030303030; + val = (val * 10) + (val >> 8); // val = (val * 2561) >> 8; + val = (((val & mask) * mul1) + (((val >> 16) & mask) * mul2)) >> 32; + return uint32_t(val); +} + +fastfloat_really_inline uint32_t parse_eight_digits_unrolled(const char *chars) noexcept { + return parse_eight_digits_unrolled(read_u64(chars)); +} + +// credit @aqrit +fastfloat_really_inline bool is_made_of_eight_digits_fast(uint64_t val) noexcept { + return !((((val + 0x4646464646464646) | (val - 0x3030303030303030)) & + 0x8080808080808080)); +} + +fastfloat_really_inline bool is_made_of_eight_digits_fast(const char *chars) noexcept { + return is_made_of_eight_digits_fast(read_u64(chars)); +} + +typedef span byte_span; + +struct parsed_number_string { + int64_t exponent{0}; + uint64_t mantissa{0}; + const char *lastmatch{nullptr}; + bool negative{false}; + bool valid{false}; + bool too_many_digits{false}; + // contains the range of the significant digits + byte_span integer{}; // non-nullable + byte_span fraction{}; // nullable +}; + +// Assuming that you use no more than 19 digits, this will +// parse an ASCII string. +fastfloat_really_inline +parsed_number_string parse_number_string(const char *p, const char *pend, parse_options options) noexcept { + const chars_format fmt = options.format; + const char decimal_point = options.decimal_point; + + parsed_number_string answer; + answer.valid = false; + answer.too_many_digits = false; + answer.negative = (*p == '-'); + if (*p == '-') { // C++17 20.19.3.(7.1) explicitly forbids '+' sign here + ++p; + if (p == pend) { + return answer; + } + if (!is_integer(*p) && (*p != decimal_point)) { // a sign must be followed by an integer or the dot + return answer; + } + } + const char *const start_digits = p; + + uint64_t i = 0; // an unsigned int avoids signed overflows (which are bad) + + while ((std::distance(p, pend) >= 8) && is_made_of_eight_digits_fast(p)) { + i = i * 100000000 + parse_eight_digits_unrolled(p); // in rare cases, this will overflow, but that's ok + p += 8; + } + while ((p != pend) && is_integer(*p)) { + // a multiplication by 10 is cheaper than an arbitrary integer + // multiplication + i = 10 * i + + uint64_t(*p - '0'); // might overflow, we will handle the overflow later + ++p; + } + const char *const end_of_integer_part = p; + int64_t digit_count = int64_t(end_of_integer_part - start_digits); + answer.integer = byte_span(start_digits, size_t(digit_count)); + int64_t exponent = 0; + if ((p != pend) && (*p == decimal_point)) { + ++p; + const char* before = p; + // can occur at most twice without overflowing, but let it occur more, since + // for integers with many digits, digit parsing is the primary bottleneck. + while ((std::distance(p, pend) >= 8) && is_made_of_eight_digits_fast(p)) { + i = i * 100000000 + parse_eight_digits_unrolled(p); // in rare cases, this will overflow, but that's ok + p += 8; + } + while ((p != pend) && is_integer(*p)) { + uint8_t digit = uint8_t(*p - '0'); + ++p; + i = i * 10 + digit; // in rare cases, this will overflow, but that's ok + } + exponent = before - p; + answer.fraction = byte_span(before, size_t(p - before)); + digit_count -= exponent; + } + // we must have encountered at least one integer! + if (digit_count == 0) { + return answer; + } + int64_t exp_number = 0; // explicit exponential part + if ((fmt & chars_format::scientific) && (p != pend) && (('e' == *p) || ('E' == *p))) { + const char * location_of_e = p; + ++p; + bool neg_exp = false; + if ((p != pend) && ('-' == *p)) { + neg_exp = true; + ++p; + } else if ((p != pend) && ('+' == *p)) { // '+' on exponent is allowed by C++17 20.19.3.(7.1) + ++p; + } + if ((p == pend) || !is_integer(*p)) { + if(!(fmt & chars_format::fixed)) { + // We are in error. + return answer; + } + // Otherwise, we will be ignoring the 'e'. + p = location_of_e; + } else { + while ((p != pend) && is_integer(*p)) { + uint8_t digit = uint8_t(*p - '0'); + if (exp_number < 0x10000000) { + exp_number = 10 * exp_number + digit; + } + ++p; + } + if(neg_exp) { exp_number = - exp_number; } + exponent += exp_number; + } + } else { + // If it scientific and not fixed, we have to bail out. + if((fmt & chars_format::scientific) && !(fmt & chars_format::fixed)) { return answer; } + } + answer.lastmatch = p; + answer.valid = true; + + // If we frequently had to deal with long strings of digits, + // we could extend our code by using a 128-bit integer instead + // of a 64-bit integer. However, this is uncommon. + // + // We can deal with up to 19 digits. + if (digit_count > 19) { // this is uncommon + // It is possible that the integer had an overflow. + // We have to handle the case where we have 0.0000somenumber. + // We need to be mindful of the case where we only have zeroes... + // E.g., 0.000000000...000. + const char *start = start_digits; + while ((start != pend) && (*start == '0' || *start == decimal_point)) { + if(*start == '0') { digit_count --; } + start++; + } + if (digit_count > 19) { + answer.too_many_digits = true; + // Let us start again, this time, avoiding overflows. + // We don't need to check if is_integer, since we use the + // pre-tokenized spans from above. + i = 0; + p = answer.integer.ptr; + const char* int_end = p + answer.integer.len(); + const uint64_t minimal_nineteen_digit_integer{1000000000000000000}; + while((i < minimal_nineteen_digit_integer) && (p != int_end)) { + i = i * 10 + uint64_t(*p - '0'); + ++p; + } + if (i >= minimal_nineteen_digit_integer) { // We have a big integers + exponent = end_of_integer_part - p + exp_number; + } else { // We have a value with a fractional component. + p = answer.fraction.ptr; + const char* frac_end = p + answer.fraction.len(); + while((i < minimal_nineteen_digit_integer) && (p != frac_end)) { + i = i * 10 + uint64_t(*p - '0'); + ++p; + } + exponent = answer.fraction.ptr - p + exp_number; + } + // We have now corrected both exponent and i, to a truncated value + } + } + answer.exponent = exponent; + answer.mantissa = i; + return answer; +} + +} // namespace fast_float + +#endif + +#ifndef FASTFLOAT_FAST_TABLE_H +#define FASTFLOAT_FAST_TABLE_H + +#include + +namespace fast_float { + +/** + * When mapping numbers from decimal to binary, + * we go from w * 10^q to m * 2^p but we have + * 10^q = 5^q * 2^q, so effectively + * we are trying to match + * w * 2^q * 5^q to m * 2^p. Thus the powers of two + * are not a concern since they can be represented + * exactly using the binary notation, only the powers of five + * affect the binary significand. + */ + +/** + * The smallest non-zero float (binary64) is 2^−1074. + * We take as input numbers of the form w x 10^q where w < 2^64. + * We have that w * 10^-343 < 2^(64-344) 5^-343 < 2^-1076. + * However, we have that + * (2^64-1) * 10^-342 = (2^64-1) * 2^-342 * 5^-342 > 2^−1074. + * Thus it is possible for a number of the form w * 10^-342 where + * w is a 64-bit value to be a non-zero floating-point number. + ********* + * Any number of form w * 10^309 where w>= 1 is going to be + * infinite in binary64 so we never need to worry about powers + * of 5 greater than 308. + */ +template +struct powers_template { + +constexpr static int smallest_power_of_five = binary_format::smallest_power_of_ten(); +constexpr static int largest_power_of_five = binary_format::largest_power_of_ten(); +constexpr static int number_of_entries = 2 * (largest_power_of_five - smallest_power_of_five + 1); +// Powers of five from 5^-342 all the way to 5^308 rounded toward one. +static const uint64_t power_of_five_128[number_of_entries]; +}; + +template +const uint64_t powers_template::power_of_five_128[number_of_entries] = { + 0xeef453d6923bd65a,0x113faa2906a13b3f, + 0x9558b4661b6565f8,0x4ac7ca59a424c507, + 0xbaaee17fa23ebf76,0x5d79bcf00d2df649, + 0xe95a99df8ace6f53,0xf4d82c2c107973dc, + 0x91d8a02bb6c10594,0x79071b9b8a4be869, + 0xb64ec836a47146f9,0x9748e2826cdee284, + 0xe3e27a444d8d98b7,0xfd1b1b2308169b25, + 0x8e6d8c6ab0787f72,0xfe30f0f5e50e20f7, + 0xb208ef855c969f4f,0xbdbd2d335e51a935, + 0xde8b2b66b3bc4723,0xad2c788035e61382, + 0x8b16fb203055ac76,0x4c3bcb5021afcc31, + 0xaddcb9e83c6b1793,0xdf4abe242a1bbf3d, + 0xd953e8624b85dd78,0xd71d6dad34a2af0d, + 0x87d4713d6f33aa6b,0x8672648c40e5ad68, + 0xa9c98d8ccb009506,0x680efdaf511f18c2, + 0xd43bf0effdc0ba48,0x212bd1b2566def2, + 0x84a57695fe98746d,0x14bb630f7604b57, + 0xa5ced43b7e3e9188,0x419ea3bd35385e2d, + 0xcf42894a5dce35ea,0x52064cac828675b9, + 0x818995ce7aa0e1b2,0x7343efebd1940993, + 0xa1ebfb4219491a1f,0x1014ebe6c5f90bf8, + 0xca66fa129f9b60a6,0xd41a26e077774ef6, + 0xfd00b897478238d0,0x8920b098955522b4, + 0x9e20735e8cb16382,0x55b46e5f5d5535b0, + 0xc5a890362fddbc62,0xeb2189f734aa831d, + 0xf712b443bbd52b7b,0xa5e9ec7501d523e4, + 0x9a6bb0aa55653b2d,0x47b233c92125366e, + 0xc1069cd4eabe89f8,0x999ec0bb696e840a, + 0xf148440a256e2c76,0xc00670ea43ca250d, + 0x96cd2a865764dbca,0x380406926a5e5728, + 0xbc807527ed3e12bc,0xc605083704f5ecf2, + 0xeba09271e88d976b,0xf7864a44c633682e, + 0x93445b8731587ea3,0x7ab3ee6afbe0211d, + 0xb8157268fdae9e4c,0x5960ea05bad82964, + 0xe61acf033d1a45df,0x6fb92487298e33bd, + 0x8fd0c16206306bab,0xa5d3b6d479f8e056, + 0xb3c4f1ba87bc8696,0x8f48a4899877186c, + 0xe0b62e2929aba83c,0x331acdabfe94de87, + 0x8c71dcd9ba0b4925,0x9ff0c08b7f1d0b14, + 0xaf8e5410288e1b6f,0x7ecf0ae5ee44dd9, + 0xdb71e91432b1a24a,0xc9e82cd9f69d6150, + 0x892731ac9faf056e,0xbe311c083a225cd2, + 0xab70fe17c79ac6ca,0x6dbd630a48aaf406, + 0xd64d3d9db981787d,0x92cbbccdad5b108, + 0x85f0468293f0eb4e,0x25bbf56008c58ea5, + 0xa76c582338ed2621,0xaf2af2b80af6f24e, + 0xd1476e2c07286faa,0x1af5af660db4aee1, + 0x82cca4db847945ca,0x50d98d9fc890ed4d, + 0xa37fce126597973c,0xe50ff107bab528a0, + 0xcc5fc196fefd7d0c,0x1e53ed49a96272c8, + 0xff77b1fcbebcdc4f,0x25e8e89c13bb0f7a, + 0x9faacf3df73609b1,0x77b191618c54e9ac, + 0xc795830d75038c1d,0xd59df5b9ef6a2417, + 0xf97ae3d0d2446f25,0x4b0573286b44ad1d, + 0x9becce62836ac577,0x4ee367f9430aec32, + 0xc2e801fb244576d5,0x229c41f793cda73f, + 0xf3a20279ed56d48a,0x6b43527578c1110f, + 0x9845418c345644d6,0x830a13896b78aaa9, + 0xbe5691ef416bd60c,0x23cc986bc656d553, + 0xedec366b11c6cb8f,0x2cbfbe86b7ec8aa8, + 0x94b3a202eb1c3f39,0x7bf7d71432f3d6a9, + 0xb9e08a83a5e34f07,0xdaf5ccd93fb0cc53, + 0xe858ad248f5c22c9,0xd1b3400f8f9cff68, + 0x91376c36d99995be,0x23100809b9c21fa1, + 0xb58547448ffffb2d,0xabd40a0c2832a78a, + 0xe2e69915b3fff9f9,0x16c90c8f323f516c, + 0x8dd01fad907ffc3b,0xae3da7d97f6792e3, + 0xb1442798f49ffb4a,0x99cd11cfdf41779c, + 0xdd95317f31c7fa1d,0x40405643d711d583, + 0x8a7d3eef7f1cfc52,0x482835ea666b2572, + 0xad1c8eab5ee43b66,0xda3243650005eecf, + 0xd863b256369d4a40,0x90bed43e40076a82, + 0x873e4f75e2224e68,0x5a7744a6e804a291, + 0xa90de3535aaae202,0x711515d0a205cb36, + 0xd3515c2831559a83,0xd5a5b44ca873e03, + 0x8412d9991ed58091,0xe858790afe9486c2, + 0xa5178fff668ae0b6,0x626e974dbe39a872, + 0xce5d73ff402d98e3,0xfb0a3d212dc8128f, + 0x80fa687f881c7f8e,0x7ce66634bc9d0b99, + 0xa139029f6a239f72,0x1c1fffc1ebc44e80, + 0xc987434744ac874e,0xa327ffb266b56220, + 0xfbe9141915d7a922,0x4bf1ff9f0062baa8, + 0x9d71ac8fada6c9b5,0x6f773fc3603db4a9, + 0xc4ce17b399107c22,0xcb550fb4384d21d3, + 0xf6019da07f549b2b,0x7e2a53a146606a48, + 0x99c102844f94e0fb,0x2eda7444cbfc426d, + 0xc0314325637a1939,0xfa911155fefb5308, + 0xf03d93eebc589f88,0x793555ab7eba27ca, + 0x96267c7535b763b5,0x4bc1558b2f3458de, + 0xbbb01b9283253ca2,0x9eb1aaedfb016f16, + 0xea9c227723ee8bcb,0x465e15a979c1cadc, + 0x92a1958a7675175f,0xbfacd89ec191ec9, + 0xb749faed14125d36,0xcef980ec671f667b, + 0xe51c79a85916f484,0x82b7e12780e7401a, + 0x8f31cc0937ae58d2,0xd1b2ecb8b0908810, + 0xb2fe3f0b8599ef07,0x861fa7e6dcb4aa15, + 0xdfbdcece67006ac9,0x67a791e093e1d49a, + 0x8bd6a141006042bd,0xe0c8bb2c5c6d24e0, + 0xaecc49914078536d,0x58fae9f773886e18, + 0xda7f5bf590966848,0xaf39a475506a899e, + 0x888f99797a5e012d,0x6d8406c952429603, + 0xaab37fd7d8f58178,0xc8e5087ba6d33b83, + 0xd5605fcdcf32e1d6,0xfb1e4a9a90880a64, + 0x855c3be0a17fcd26,0x5cf2eea09a55067f, + 0xa6b34ad8c9dfc06f,0xf42faa48c0ea481e, + 0xd0601d8efc57b08b,0xf13b94daf124da26, + 0x823c12795db6ce57,0x76c53d08d6b70858, + 0xa2cb1717b52481ed,0x54768c4b0c64ca6e, + 0xcb7ddcdda26da268,0xa9942f5dcf7dfd09, + 0xfe5d54150b090b02,0xd3f93b35435d7c4c, + 0x9efa548d26e5a6e1,0xc47bc5014a1a6daf, + 0xc6b8e9b0709f109a,0x359ab6419ca1091b, + 0xf867241c8cc6d4c0,0xc30163d203c94b62, + 0x9b407691d7fc44f8,0x79e0de63425dcf1d, + 0xc21094364dfb5636,0x985915fc12f542e4, + 0xf294b943e17a2bc4,0x3e6f5b7b17b2939d, + 0x979cf3ca6cec5b5a,0xa705992ceecf9c42, + 0xbd8430bd08277231,0x50c6ff782a838353, + 0xece53cec4a314ebd,0xa4f8bf5635246428, + 0x940f4613ae5ed136,0x871b7795e136be99, + 0xb913179899f68584,0x28e2557b59846e3f, + 0xe757dd7ec07426e5,0x331aeada2fe589cf, + 0x9096ea6f3848984f,0x3ff0d2c85def7621, + 0xb4bca50b065abe63,0xfed077a756b53a9, + 0xe1ebce4dc7f16dfb,0xd3e8495912c62894, + 0x8d3360f09cf6e4bd,0x64712dd7abbbd95c, + 0xb080392cc4349dec,0xbd8d794d96aacfb3, + 0xdca04777f541c567,0xecf0d7a0fc5583a0, + 0x89e42caaf9491b60,0xf41686c49db57244, + 0xac5d37d5b79b6239,0x311c2875c522ced5, + 0xd77485cb25823ac7,0x7d633293366b828b, + 0x86a8d39ef77164bc,0xae5dff9c02033197, + 0xa8530886b54dbdeb,0xd9f57f830283fdfc, + 0xd267caa862a12d66,0xd072df63c324fd7b, + 0x8380dea93da4bc60,0x4247cb9e59f71e6d, + 0xa46116538d0deb78,0x52d9be85f074e608, + 0xcd795be870516656,0x67902e276c921f8b, + 0x806bd9714632dff6,0xba1cd8a3db53b6, + 0xa086cfcd97bf97f3,0x80e8a40eccd228a4, + 0xc8a883c0fdaf7df0,0x6122cd128006b2cd, + 0xfad2a4b13d1b5d6c,0x796b805720085f81, + 0x9cc3a6eec6311a63,0xcbe3303674053bb0, + 0xc3f490aa77bd60fc,0xbedbfc4411068a9c, + 0xf4f1b4d515acb93b,0xee92fb5515482d44, + 0x991711052d8bf3c5,0x751bdd152d4d1c4a, + 0xbf5cd54678eef0b6,0xd262d45a78a0635d, + 0xef340a98172aace4,0x86fb897116c87c34, + 0x9580869f0e7aac0e,0xd45d35e6ae3d4da0, + 0xbae0a846d2195712,0x8974836059cca109, + 0xe998d258869facd7,0x2bd1a438703fc94b, + 0x91ff83775423cc06,0x7b6306a34627ddcf, + 0xb67f6455292cbf08,0x1a3bc84c17b1d542, + 0xe41f3d6a7377eeca,0x20caba5f1d9e4a93, + 0x8e938662882af53e,0x547eb47b7282ee9c, + 0xb23867fb2a35b28d,0xe99e619a4f23aa43, + 0xdec681f9f4c31f31,0x6405fa00e2ec94d4, + 0x8b3c113c38f9f37e,0xde83bc408dd3dd04, + 0xae0b158b4738705e,0x9624ab50b148d445, + 0xd98ddaee19068c76,0x3badd624dd9b0957, + 0x87f8a8d4cfa417c9,0xe54ca5d70a80e5d6, + 0xa9f6d30a038d1dbc,0x5e9fcf4ccd211f4c, + 0xd47487cc8470652b,0x7647c3200069671f, + 0x84c8d4dfd2c63f3b,0x29ecd9f40041e073, + 0xa5fb0a17c777cf09,0xf468107100525890, + 0xcf79cc9db955c2cc,0x7182148d4066eeb4, + 0x81ac1fe293d599bf,0xc6f14cd848405530, + 0xa21727db38cb002f,0xb8ada00e5a506a7c, + 0xca9cf1d206fdc03b,0xa6d90811f0e4851c, + 0xfd442e4688bd304a,0x908f4a166d1da663, + 0x9e4a9cec15763e2e,0x9a598e4e043287fe, + 0xc5dd44271ad3cdba,0x40eff1e1853f29fd, + 0xf7549530e188c128,0xd12bee59e68ef47c, + 0x9a94dd3e8cf578b9,0x82bb74f8301958ce, + 0xc13a148e3032d6e7,0xe36a52363c1faf01, + 0xf18899b1bc3f8ca1,0xdc44e6c3cb279ac1, + 0x96f5600f15a7b7e5,0x29ab103a5ef8c0b9, + 0xbcb2b812db11a5de,0x7415d448f6b6f0e7, + 0xebdf661791d60f56,0x111b495b3464ad21, + 0x936b9fcebb25c995,0xcab10dd900beec34, + 0xb84687c269ef3bfb,0x3d5d514f40eea742, + 0xe65829b3046b0afa,0xcb4a5a3112a5112, + 0x8ff71a0fe2c2e6dc,0x47f0e785eaba72ab, + 0xb3f4e093db73a093,0x59ed216765690f56, + 0xe0f218b8d25088b8,0x306869c13ec3532c, + 0x8c974f7383725573,0x1e414218c73a13fb, + 0xafbd2350644eeacf,0xe5d1929ef90898fa, + 0xdbac6c247d62a583,0xdf45f746b74abf39, + 0x894bc396ce5da772,0x6b8bba8c328eb783, + 0xab9eb47c81f5114f,0x66ea92f3f326564, + 0xd686619ba27255a2,0xc80a537b0efefebd, + 0x8613fd0145877585,0xbd06742ce95f5f36, + 0xa798fc4196e952e7,0x2c48113823b73704, + 0xd17f3b51fca3a7a0,0xf75a15862ca504c5, + 0x82ef85133de648c4,0x9a984d73dbe722fb, + 0xa3ab66580d5fdaf5,0xc13e60d0d2e0ebba, + 0xcc963fee10b7d1b3,0x318df905079926a8, + 0xffbbcfe994e5c61f,0xfdf17746497f7052, + 0x9fd561f1fd0f9bd3,0xfeb6ea8bedefa633, + 0xc7caba6e7c5382c8,0xfe64a52ee96b8fc0, + 0xf9bd690a1b68637b,0x3dfdce7aa3c673b0, + 0x9c1661a651213e2d,0x6bea10ca65c084e, + 0xc31bfa0fe5698db8,0x486e494fcff30a62, + 0xf3e2f893dec3f126,0x5a89dba3c3efccfa, + 0x986ddb5c6b3a76b7,0xf89629465a75e01c, + 0xbe89523386091465,0xf6bbb397f1135823, + 0xee2ba6c0678b597f,0x746aa07ded582e2c, + 0x94db483840b717ef,0xa8c2a44eb4571cdc, + 0xba121a4650e4ddeb,0x92f34d62616ce413, + 0xe896a0d7e51e1566,0x77b020baf9c81d17, + 0x915e2486ef32cd60,0xace1474dc1d122e, + 0xb5b5ada8aaff80b8,0xd819992132456ba, + 0xe3231912d5bf60e6,0x10e1fff697ed6c69, + 0x8df5efabc5979c8f,0xca8d3ffa1ef463c1, + 0xb1736b96b6fd83b3,0xbd308ff8a6b17cb2, + 0xddd0467c64bce4a0,0xac7cb3f6d05ddbde, + 0x8aa22c0dbef60ee4,0x6bcdf07a423aa96b, + 0xad4ab7112eb3929d,0x86c16c98d2c953c6, + 0xd89d64d57a607744,0xe871c7bf077ba8b7, + 0x87625f056c7c4a8b,0x11471cd764ad4972, + 0xa93af6c6c79b5d2d,0xd598e40d3dd89bcf, + 0xd389b47879823479,0x4aff1d108d4ec2c3, + 0x843610cb4bf160cb,0xcedf722a585139ba, + 0xa54394fe1eedb8fe,0xc2974eb4ee658828, + 0xce947a3da6a9273e,0x733d226229feea32, + 0x811ccc668829b887,0x806357d5a3f525f, + 0xa163ff802a3426a8,0xca07c2dcb0cf26f7, + 0xc9bcff6034c13052,0xfc89b393dd02f0b5, + 0xfc2c3f3841f17c67,0xbbac2078d443ace2, + 0x9d9ba7832936edc0,0xd54b944b84aa4c0d, + 0xc5029163f384a931,0xa9e795e65d4df11, + 0xf64335bcf065d37d,0x4d4617b5ff4a16d5, + 0x99ea0196163fa42e,0x504bced1bf8e4e45, + 0xc06481fb9bcf8d39,0xe45ec2862f71e1d6, + 0xf07da27a82c37088,0x5d767327bb4e5a4c, + 0x964e858c91ba2655,0x3a6a07f8d510f86f, + 0xbbe226efb628afea,0x890489f70a55368b, + 0xeadab0aba3b2dbe5,0x2b45ac74ccea842e, + 0x92c8ae6b464fc96f,0x3b0b8bc90012929d, + 0xb77ada0617e3bbcb,0x9ce6ebb40173744, + 0xe55990879ddcaabd,0xcc420a6a101d0515, + 0x8f57fa54c2a9eab6,0x9fa946824a12232d, + 0xb32df8e9f3546564,0x47939822dc96abf9, + 0xdff9772470297ebd,0x59787e2b93bc56f7, + 0x8bfbea76c619ef36,0x57eb4edb3c55b65a, + 0xaefae51477a06b03,0xede622920b6b23f1, + 0xdab99e59958885c4,0xe95fab368e45eced, + 0x88b402f7fd75539b,0x11dbcb0218ebb414, + 0xaae103b5fcd2a881,0xd652bdc29f26a119, + 0xd59944a37c0752a2,0x4be76d3346f0495f, + 0x857fcae62d8493a5,0x6f70a4400c562ddb, + 0xa6dfbd9fb8e5b88e,0xcb4ccd500f6bb952, + 0xd097ad07a71f26b2,0x7e2000a41346a7a7, + 0x825ecc24c873782f,0x8ed400668c0c28c8, + 0xa2f67f2dfa90563b,0x728900802f0f32fa, + 0xcbb41ef979346bca,0x4f2b40a03ad2ffb9, + 0xfea126b7d78186bc,0xe2f610c84987bfa8, + 0x9f24b832e6b0f436,0xdd9ca7d2df4d7c9, + 0xc6ede63fa05d3143,0x91503d1c79720dbb, + 0xf8a95fcf88747d94,0x75a44c6397ce912a, + 0x9b69dbe1b548ce7c,0xc986afbe3ee11aba, + 0xc24452da229b021b,0xfbe85badce996168, + 0xf2d56790ab41c2a2,0xfae27299423fb9c3, + 0x97c560ba6b0919a5,0xdccd879fc967d41a, + 0xbdb6b8e905cb600f,0x5400e987bbc1c920, + 0xed246723473e3813,0x290123e9aab23b68, + 0x9436c0760c86e30b,0xf9a0b6720aaf6521, + 0xb94470938fa89bce,0xf808e40e8d5b3e69, + 0xe7958cb87392c2c2,0xb60b1d1230b20e04, + 0x90bd77f3483bb9b9,0xb1c6f22b5e6f48c2, + 0xb4ecd5f01a4aa828,0x1e38aeb6360b1af3, + 0xe2280b6c20dd5232,0x25c6da63c38de1b0, + 0x8d590723948a535f,0x579c487e5a38ad0e, + 0xb0af48ec79ace837,0x2d835a9df0c6d851, + 0xdcdb1b2798182244,0xf8e431456cf88e65, + 0x8a08f0f8bf0f156b,0x1b8e9ecb641b58ff, + 0xac8b2d36eed2dac5,0xe272467e3d222f3f, + 0xd7adf884aa879177,0x5b0ed81dcc6abb0f, + 0x86ccbb52ea94baea,0x98e947129fc2b4e9, + 0xa87fea27a539e9a5,0x3f2398d747b36224, + 0xd29fe4b18e88640e,0x8eec7f0d19a03aad, + 0x83a3eeeef9153e89,0x1953cf68300424ac, + 0xa48ceaaab75a8e2b,0x5fa8c3423c052dd7, + 0xcdb02555653131b6,0x3792f412cb06794d, + 0x808e17555f3ebf11,0xe2bbd88bbee40bd0, + 0xa0b19d2ab70e6ed6,0x5b6aceaeae9d0ec4, + 0xc8de047564d20a8b,0xf245825a5a445275, + 0xfb158592be068d2e,0xeed6e2f0f0d56712, + 0x9ced737bb6c4183d,0x55464dd69685606b, + 0xc428d05aa4751e4c,0xaa97e14c3c26b886, + 0xf53304714d9265df,0xd53dd99f4b3066a8, + 0x993fe2c6d07b7fab,0xe546a8038efe4029, + 0xbf8fdb78849a5f96,0xde98520472bdd033, + 0xef73d256a5c0f77c,0x963e66858f6d4440, + 0x95a8637627989aad,0xdde7001379a44aa8, + 0xbb127c53b17ec159,0x5560c018580d5d52, + 0xe9d71b689dde71af,0xaab8f01e6e10b4a6, + 0x9226712162ab070d,0xcab3961304ca70e8, + 0xb6b00d69bb55c8d1,0x3d607b97c5fd0d22, + 0xe45c10c42a2b3b05,0x8cb89a7db77c506a, + 0x8eb98a7a9a5b04e3,0x77f3608e92adb242, + 0xb267ed1940f1c61c,0x55f038b237591ed3, + 0xdf01e85f912e37a3,0x6b6c46dec52f6688, + 0x8b61313bbabce2c6,0x2323ac4b3b3da015, + 0xae397d8aa96c1b77,0xabec975e0a0d081a, + 0xd9c7dced53c72255,0x96e7bd358c904a21, + 0x881cea14545c7575,0x7e50d64177da2e54, + 0xaa242499697392d2,0xdde50bd1d5d0b9e9, + 0xd4ad2dbfc3d07787,0x955e4ec64b44e864, + 0x84ec3c97da624ab4,0xbd5af13bef0b113e, + 0xa6274bbdd0fadd61,0xecb1ad8aeacdd58e, + 0xcfb11ead453994ba,0x67de18eda5814af2, + 0x81ceb32c4b43fcf4,0x80eacf948770ced7, + 0xa2425ff75e14fc31,0xa1258379a94d028d, + 0xcad2f7f5359a3b3e,0x96ee45813a04330, + 0xfd87b5f28300ca0d,0x8bca9d6e188853fc, + 0x9e74d1b791e07e48,0x775ea264cf55347e, + 0xc612062576589dda,0x95364afe032a819e, + 0xf79687aed3eec551,0x3a83ddbd83f52205, + 0x9abe14cd44753b52,0xc4926a9672793543, + 0xc16d9a0095928a27,0x75b7053c0f178294, + 0xf1c90080baf72cb1,0x5324c68b12dd6339, + 0x971da05074da7bee,0xd3f6fc16ebca5e04, + 0xbce5086492111aea,0x88f4bb1ca6bcf585, + 0xec1e4a7db69561a5,0x2b31e9e3d06c32e6, + 0x9392ee8e921d5d07,0x3aff322e62439fd0, + 0xb877aa3236a4b449,0x9befeb9fad487c3, + 0xe69594bec44de15b,0x4c2ebe687989a9b4, + 0x901d7cf73ab0acd9,0xf9d37014bf60a11, + 0xb424dc35095cd80f,0x538484c19ef38c95, + 0xe12e13424bb40e13,0x2865a5f206b06fba, + 0x8cbccc096f5088cb,0xf93f87b7442e45d4, + 0xafebff0bcb24aafe,0xf78f69a51539d749, + 0xdbe6fecebdedd5be,0xb573440e5a884d1c, + 0x89705f4136b4a597,0x31680a88f8953031, + 0xabcc77118461cefc,0xfdc20d2b36ba7c3e, + 0xd6bf94d5e57a42bc,0x3d32907604691b4d, + 0x8637bd05af6c69b5,0xa63f9a49c2c1b110, + 0xa7c5ac471b478423,0xfcf80dc33721d54, + 0xd1b71758e219652b,0xd3c36113404ea4a9, + 0x83126e978d4fdf3b,0x645a1cac083126ea, + 0xa3d70a3d70a3d70a,0x3d70a3d70a3d70a4, + 0xcccccccccccccccc,0xcccccccccccccccd, + 0x8000000000000000,0x0, + 0xa000000000000000,0x0, + 0xc800000000000000,0x0, + 0xfa00000000000000,0x0, + 0x9c40000000000000,0x0, + 0xc350000000000000,0x0, + 0xf424000000000000,0x0, + 0x9896800000000000,0x0, + 0xbebc200000000000,0x0, + 0xee6b280000000000,0x0, + 0x9502f90000000000,0x0, + 0xba43b74000000000,0x0, + 0xe8d4a51000000000,0x0, + 0x9184e72a00000000,0x0, + 0xb5e620f480000000,0x0, + 0xe35fa931a0000000,0x0, + 0x8e1bc9bf04000000,0x0, + 0xb1a2bc2ec5000000,0x0, + 0xde0b6b3a76400000,0x0, + 0x8ac7230489e80000,0x0, + 0xad78ebc5ac620000,0x0, + 0xd8d726b7177a8000,0x0, + 0x878678326eac9000,0x0, + 0xa968163f0a57b400,0x0, + 0xd3c21bcecceda100,0x0, + 0x84595161401484a0,0x0, + 0xa56fa5b99019a5c8,0x0, + 0xcecb8f27f4200f3a,0x0, + 0x813f3978f8940984,0x4000000000000000, + 0xa18f07d736b90be5,0x5000000000000000, + 0xc9f2c9cd04674ede,0xa400000000000000, + 0xfc6f7c4045812296,0x4d00000000000000, + 0x9dc5ada82b70b59d,0xf020000000000000, + 0xc5371912364ce305,0x6c28000000000000, + 0xf684df56c3e01bc6,0xc732000000000000, + 0x9a130b963a6c115c,0x3c7f400000000000, + 0xc097ce7bc90715b3,0x4b9f100000000000, + 0xf0bdc21abb48db20,0x1e86d40000000000, + 0x96769950b50d88f4,0x1314448000000000, + 0xbc143fa4e250eb31,0x17d955a000000000, + 0xeb194f8e1ae525fd,0x5dcfab0800000000, + 0x92efd1b8d0cf37be,0x5aa1cae500000000, + 0xb7abc627050305ad,0xf14a3d9e40000000, + 0xe596b7b0c643c719,0x6d9ccd05d0000000, + 0x8f7e32ce7bea5c6f,0xe4820023a2000000, + 0xb35dbf821ae4f38b,0xdda2802c8a800000, + 0xe0352f62a19e306e,0xd50b2037ad200000, + 0x8c213d9da502de45,0x4526f422cc340000, + 0xaf298d050e4395d6,0x9670b12b7f410000, + 0xdaf3f04651d47b4c,0x3c0cdd765f114000, + 0x88d8762bf324cd0f,0xa5880a69fb6ac800, + 0xab0e93b6efee0053,0x8eea0d047a457a00, + 0xd5d238a4abe98068,0x72a4904598d6d880, + 0x85a36366eb71f041,0x47a6da2b7f864750, + 0xa70c3c40a64e6c51,0x999090b65f67d924, + 0xd0cf4b50cfe20765,0xfff4b4e3f741cf6d, + 0x82818f1281ed449f,0xbff8f10e7a8921a4, + 0xa321f2d7226895c7,0xaff72d52192b6a0d, + 0xcbea6f8ceb02bb39,0x9bf4f8a69f764490, + 0xfee50b7025c36a08,0x2f236d04753d5b4, + 0x9f4f2726179a2245,0x1d762422c946590, + 0xc722f0ef9d80aad6,0x424d3ad2b7b97ef5, + 0xf8ebad2b84e0d58b,0xd2e0898765a7deb2, + 0x9b934c3b330c8577,0x63cc55f49f88eb2f, + 0xc2781f49ffcfa6d5,0x3cbf6b71c76b25fb, + 0xf316271c7fc3908a,0x8bef464e3945ef7a, + 0x97edd871cfda3a56,0x97758bf0e3cbb5ac, + 0xbde94e8e43d0c8ec,0x3d52eeed1cbea317, + 0xed63a231d4c4fb27,0x4ca7aaa863ee4bdd, + 0x945e455f24fb1cf8,0x8fe8caa93e74ef6a, + 0xb975d6b6ee39e436,0xb3e2fd538e122b44, + 0xe7d34c64a9c85d44,0x60dbbca87196b616, + 0x90e40fbeea1d3a4a,0xbc8955e946fe31cd, + 0xb51d13aea4a488dd,0x6babab6398bdbe41, + 0xe264589a4dcdab14,0xc696963c7eed2dd1, + 0x8d7eb76070a08aec,0xfc1e1de5cf543ca2, + 0xb0de65388cc8ada8,0x3b25a55f43294bcb, + 0xdd15fe86affad912,0x49ef0eb713f39ebe, + 0x8a2dbf142dfcc7ab,0x6e3569326c784337, + 0xacb92ed9397bf996,0x49c2c37f07965404, + 0xd7e77a8f87daf7fb,0xdc33745ec97be906, + 0x86f0ac99b4e8dafd,0x69a028bb3ded71a3, + 0xa8acd7c0222311bc,0xc40832ea0d68ce0c, + 0xd2d80db02aabd62b,0xf50a3fa490c30190, + 0x83c7088e1aab65db,0x792667c6da79e0fa, + 0xa4b8cab1a1563f52,0x577001b891185938, + 0xcde6fd5e09abcf26,0xed4c0226b55e6f86, + 0x80b05e5ac60b6178,0x544f8158315b05b4, + 0xa0dc75f1778e39d6,0x696361ae3db1c721, + 0xc913936dd571c84c,0x3bc3a19cd1e38e9, + 0xfb5878494ace3a5f,0x4ab48a04065c723, + 0x9d174b2dcec0e47b,0x62eb0d64283f9c76, + 0xc45d1df942711d9a,0x3ba5d0bd324f8394, + 0xf5746577930d6500,0xca8f44ec7ee36479, + 0x9968bf6abbe85f20,0x7e998b13cf4e1ecb, + 0xbfc2ef456ae276e8,0x9e3fedd8c321a67e, + 0xefb3ab16c59b14a2,0xc5cfe94ef3ea101e, + 0x95d04aee3b80ece5,0xbba1f1d158724a12, + 0xbb445da9ca61281f,0x2a8a6e45ae8edc97, + 0xea1575143cf97226,0xf52d09d71a3293bd, + 0x924d692ca61be758,0x593c2626705f9c56, + 0xb6e0c377cfa2e12e,0x6f8b2fb00c77836c, + 0xe498f455c38b997a,0xb6dfb9c0f956447, + 0x8edf98b59a373fec,0x4724bd4189bd5eac, + 0xb2977ee300c50fe7,0x58edec91ec2cb657, + 0xdf3d5e9bc0f653e1,0x2f2967b66737e3ed, + 0x8b865b215899f46c,0xbd79e0d20082ee74, + 0xae67f1e9aec07187,0xecd8590680a3aa11, + 0xda01ee641a708de9,0xe80e6f4820cc9495, + 0x884134fe908658b2,0x3109058d147fdcdd, + 0xaa51823e34a7eede,0xbd4b46f0599fd415, + 0xd4e5e2cdc1d1ea96,0x6c9e18ac7007c91a, + 0x850fadc09923329e,0x3e2cf6bc604ddb0, + 0xa6539930bf6bff45,0x84db8346b786151c, + 0xcfe87f7cef46ff16,0xe612641865679a63, + 0x81f14fae158c5f6e,0x4fcb7e8f3f60c07e, + 0xa26da3999aef7749,0xe3be5e330f38f09d, + 0xcb090c8001ab551c,0x5cadf5bfd3072cc5, + 0xfdcb4fa002162a63,0x73d9732fc7c8f7f6, + 0x9e9f11c4014dda7e,0x2867e7fddcdd9afa, + 0xc646d63501a1511d,0xb281e1fd541501b8, + 0xf7d88bc24209a565,0x1f225a7ca91a4226, + 0x9ae757596946075f,0x3375788de9b06958, + 0xc1a12d2fc3978937,0x52d6b1641c83ae, + 0xf209787bb47d6b84,0xc0678c5dbd23a49a, + 0x9745eb4d50ce6332,0xf840b7ba963646e0, + 0xbd176620a501fbff,0xb650e5a93bc3d898, + 0xec5d3fa8ce427aff,0xa3e51f138ab4cebe, + 0x93ba47c980e98cdf,0xc66f336c36b10137, + 0xb8a8d9bbe123f017,0xb80b0047445d4184, + 0xe6d3102ad96cec1d,0xa60dc059157491e5, + 0x9043ea1ac7e41392,0x87c89837ad68db2f, + 0xb454e4a179dd1877,0x29babe4598c311fb, + 0xe16a1dc9d8545e94,0xf4296dd6fef3d67a, + 0x8ce2529e2734bb1d,0x1899e4a65f58660c, + 0xb01ae745b101e9e4,0x5ec05dcff72e7f8f, + 0xdc21a1171d42645d,0x76707543f4fa1f73, + 0x899504ae72497eba,0x6a06494a791c53a8, + 0xabfa45da0edbde69,0x487db9d17636892, + 0xd6f8d7509292d603,0x45a9d2845d3c42b6, + 0x865b86925b9bc5c2,0xb8a2392ba45a9b2, + 0xa7f26836f282b732,0x8e6cac7768d7141e, + 0xd1ef0244af2364ff,0x3207d795430cd926, + 0x8335616aed761f1f,0x7f44e6bd49e807b8, + 0xa402b9c5a8d3a6e7,0x5f16206c9c6209a6, + 0xcd036837130890a1,0x36dba887c37a8c0f, + 0x802221226be55a64,0xc2494954da2c9789, + 0xa02aa96b06deb0fd,0xf2db9baa10b7bd6c, + 0xc83553c5c8965d3d,0x6f92829494e5acc7, + 0xfa42a8b73abbf48c,0xcb772339ba1f17f9, + 0x9c69a97284b578d7,0xff2a760414536efb, + 0xc38413cf25e2d70d,0xfef5138519684aba, + 0xf46518c2ef5b8cd1,0x7eb258665fc25d69, + 0x98bf2f79d5993802,0xef2f773ffbd97a61, + 0xbeeefb584aff8603,0xaafb550ffacfd8fa, + 0xeeaaba2e5dbf6784,0x95ba2a53f983cf38, + 0x952ab45cfa97a0b2,0xdd945a747bf26183, + 0xba756174393d88df,0x94f971119aeef9e4, + 0xe912b9d1478ceb17,0x7a37cd5601aab85d, + 0x91abb422ccb812ee,0xac62e055c10ab33a, + 0xb616a12b7fe617aa,0x577b986b314d6009, + 0xe39c49765fdf9d94,0xed5a7e85fda0b80b, + 0x8e41ade9fbebc27d,0x14588f13be847307, + 0xb1d219647ae6b31c,0x596eb2d8ae258fc8, + 0xde469fbd99a05fe3,0x6fca5f8ed9aef3bb, + 0x8aec23d680043bee,0x25de7bb9480d5854, + 0xada72ccc20054ae9,0xaf561aa79a10ae6a, + 0xd910f7ff28069da4,0x1b2ba1518094da04, + 0x87aa9aff79042286,0x90fb44d2f05d0842, + 0xa99541bf57452b28,0x353a1607ac744a53, + 0xd3fa922f2d1675f2,0x42889b8997915ce8, + 0x847c9b5d7c2e09b7,0x69956135febada11, + 0xa59bc234db398c25,0x43fab9837e699095, + 0xcf02b2c21207ef2e,0x94f967e45e03f4bb, + 0x8161afb94b44f57d,0x1d1be0eebac278f5, + 0xa1ba1ba79e1632dc,0x6462d92a69731732, + 0xca28a291859bbf93,0x7d7b8f7503cfdcfe, + 0xfcb2cb35e702af78,0x5cda735244c3d43e, + 0x9defbf01b061adab,0x3a0888136afa64a7, + 0xc56baec21c7a1916,0x88aaa1845b8fdd0, + 0xf6c69a72a3989f5b,0x8aad549e57273d45, + 0x9a3c2087a63f6399,0x36ac54e2f678864b, + 0xc0cb28a98fcf3c7f,0x84576a1bb416a7dd, + 0xf0fdf2d3f3c30b9f,0x656d44a2a11c51d5, + 0x969eb7c47859e743,0x9f644ae5a4b1b325, + 0xbc4665b596706114,0x873d5d9f0dde1fee, + 0xeb57ff22fc0c7959,0xa90cb506d155a7ea, + 0x9316ff75dd87cbd8,0x9a7f12442d588f2, + 0xb7dcbf5354e9bece,0xc11ed6d538aeb2f, + 0xe5d3ef282a242e81,0x8f1668c8a86da5fa, + 0x8fa475791a569d10,0xf96e017d694487bc, + 0xb38d92d760ec4455,0x37c981dcc395a9ac, + 0xe070f78d3927556a,0x85bbe253f47b1417, + 0x8c469ab843b89562,0x93956d7478ccec8e, + 0xaf58416654a6babb,0x387ac8d1970027b2, + 0xdb2e51bfe9d0696a,0x6997b05fcc0319e, + 0x88fcf317f22241e2,0x441fece3bdf81f03, + 0xab3c2fddeeaad25a,0xd527e81cad7626c3, + 0xd60b3bd56a5586f1,0x8a71e223d8d3b074, + 0x85c7056562757456,0xf6872d5667844e49, + 0xa738c6bebb12d16c,0xb428f8ac016561db, + 0xd106f86e69d785c7,0xe13336d701beba52, + 0x82a45b450226b39c,0xecc0024661173473, + 0xa34d721642b06084,0x27f002d7f95d0190, + 0xcc20ce9bd35c78a5,0x31ec038df7b441f4, + 0xff290242c83396ce,0x7e67047175a15271, + 0x9f79a169bd203e41,0xf0062c6e984d386, + 0xc75809c42c684dd1,0x52c07b78a3e60868, + 0xf92e0c3537826145,0xa7709a56ccdf8a82, + 0x9bbcc7a142b17ccb,0x88a66076400bb691, + 0xc2abf989935ddbfe,0x6acff893d00ea435, + 0xf356f7ebf83552fe,0x583f6b8c4124d43, + 0x98165af37b2153de,0xc3727a337a8b704a, + 0xbe1bf1b059e9a8d6,0x744f18c0592e4c5c, + 0xeda2ee1c7064130c,0x1162def06f79df73, + 0x9485d4d1c63e8be7,0x8addcb5645ac2ba8, + 0xb9a74a0637ce2ee1,0x6d953e2bd7173692, + 0xe8111c87c5c1ba99,0xc8fa8db6ccdd0437, + 0x910ab1d4db9914a0,0x1d9c9892400a22a2, + 0xb54d5e4a127f59c8,0x2503beb6d00cab4b, + 0xe2a0b5dc971f303a,0x2e44ae64840fd61d, + 0x8da471a9de737e24,0x5ceaecfed289e5d2, + 0xb10d8e1456105dad,0x7425a83e872c5f47, + 0xdd50f1996b947518,0xd12f124e28f77719, + 0x8a5296ffe33cc92f,0x82bd6b70d99aaa6f, + 0xace73cbfdc0bfb7b,0x636cc64d1001550b, + 0xd8210befd30efa5a,0x3c47f7e05401aa4e, + 0x8714a775e3e95c78,0x65acfaec34810a71, + 0xa8d9d1535ce3b396,0x7f1839a741a14d0d, + 0xd31045a8341ca07c,0x1ede48111209a050, + 0x83ea2b892091e44d,0x934aed0aab460432, + 0xa4e4b66b68b65d60,0xf81da84d5617853f, + 0xce1de40642e3f4b9,0x36251260ab9d668e, + 0x80d2ae83e9ce78f3,0xc1d72b7c6b426019, + 0xa1075a24e4421730,0xb24cf65b8612f81f, + 0xc94930ae1d529cfc,0xdee033f26797b627, + 0xfb9b7cd9a4a7443c,0x169840ef017da3b1, + 0x9d412e0806e88aa5,0x8e1f289560ee864e, + 0xc491798a08a2ad4e,0xf1a6f2bab92a27e2, + 0xf5b5d7ec8acb58a2,0xae10af696774b1db, + 0x9991a6f3d6bf1765,0xacca6da1e0a8ef29, + 0xbff610b0cc6edd3f,0x17fd090a58d32af3, + 0xeff394dcff8a948e,0xddfc4b4cef07f5b0, + 0x95f83d0a1fb69cd9,0x4abdaf101564f98e, + 0xbb764c4ca7a4440f,0x9d6d1ad41abe37f1, + 0xea53df5fd18d5513,0x84c86189216dc5ed, + 0x92746b9be2f8552c,0x32fd3cf5b4e49bb4, + 0xb7118682dbb66a77,0x3fbc8c33221dc2a1, + 0xe4d5e82392a40515,0xfabaf3feaa5334a, + 0x8f05b1163ba6832d,0x29cb4d87f2a7400e, + 0xb2c71d5bca9023f8,0x743e20e9ef511012, + 0xdf78e4b2bd342cf6,0x914da9246b255416, + 0x8bab8eefb6409c1a,0x1ad089b6c2f7548e, + 0xae9672aba3d0c320,0xa184ac2473b529b1, + 0xda3c0f568cc4f3e8,0xc9e5d72d90a2741e, + 0x8865899617fb1871,0x7e2fa67c7a658892, + 0xaa7eebfb9df9de8d,0xddbb901b98feeab7, + 0xd51ea6fa85785631,0x552a74227f3ea565, + 0x8533285c936b35de,0xd53a88958f87275f, + 0xa67ff273b8460356,0x8a892abaf368f137, + 0xd01fef10a657842c,0x2d2b7569b0432d85, + 0x8213f56a67f6b29b,0x9c3b29620e29fc73, + 0xa298f2c501f45f42,0x8349f3ba91b47b8f, + 0xcb3f2f7642717713,0x241c70a936219a73, + 0xfe0efb53d30dd4d7,0xed238cd383aa0110, + 0x9ec95d1463e8a506,0xf4363804324a40aa, + 0xc67bb4597ce2ce48,0xb143c6053edcd0d5, + 0xf81aa16fdc1b81da,0xdd94b7868e94050a, + 0x9b10a4e5e9913128,0xca7cf2b4191c8326, + 0xc1d4ce1f63f57d72,0xfd1c2f611f63a3f0, + 0xf24a01a73cf2dccf,0xbc633b39673c8cec, + 0x976e41088617ca01,0xd5be0503e085d813, + 0xbd49d14aa79dbc82,0x4b2d8644d8a74e18, + 0xec9c459d51852ba2,0xddf8e7d60ed1219e, + 0x93e1ab8252f33b45,0xcabb90e5c942b503, + 0xb8da1662e7b00a17,0x3d6a751f3b936243, + 0xe7109bfba19c0c9d,0xcc512670a783ad4, + 0x906a617d450187e2,0x27fb2b80668b24c5, + 0xb484f9dc9641e9da,0xb1f9f660802dedf6, + 0xe1a63853bbd26451,0x5e7873f8a0396973, + 0x8d07e33455637eb2,0xdb0b487b6423e1e8, + 0xb049dc016abc5e5f,0x91ce1a9a3d2cda62, + 0xdc5c5301c56b75f7,0x7641a140cc7810fb, + 0x89b9b3e11b6329ba,0xa9e904c87fcb0a9d, + 0xac2820d9623bf429,0x546345fa9fbdcd44, + 0xd732290fbacaf133,0xa97c177947ad4095, + 0x867f59a9d4bed6c0,0x49ed8eabcccc485d, + 0xa81f301449ee8c70,0x5c68f256bfff5a74, + 0xd226fc195c6a2f8c,0x73832eec6fff3111, + 0x83585d8fd9c25db7,0xc831fd53c5ff7eab, + 0xa42e74f3d032f525,0xba3e7ca8b77f5e55, + 0xcd3a1230c43fb26f,0x28ce1bd2e55f35eb, + 0x80444b5e7aa7cf85,0x7980d163cf5b81b3, + 0xa0555e361951c366,0xd7e105bcc332621f, + 0xc86ab5c39fa63440,0x8dd9472bf3fefaa7, + 0xfa856334878fc150,0xb14f98f6f0feb951, + 0x9c935e00d4b9d8d2,0x6ed1bf9a569f33d3, + 0xc3b8358109e84f07,0xa862f80ec4700c8, + 0xf4a642e14c6262c8,0xcd27bb612758c0fa, + 0x98e7e9cccfbd7dbd,0x8038d51cb897789c, + 0xbf21e44003acdd2c,0xe0470a63e6bd56c3, + 0xeeea5d5004981478,0x1858ccfce06cac74, + 0x95527a5202df0ccb,0xf37801e0c43ebc8, + 0xbaa718e68396cffd,0xd30560258f54e6ba, + 0xe950df20247c83fd,0x47c6b82ef32a2069, + 0x91d28b7416cdd27e,0x4cdc331d57fa5441, + 0xb6472e511c81471d,0xe0133fe4adf8e952, + 0xe3d8f9e563a198e5,0x58180fddd97723a6, + 0x8e679c2f5e44ff8f,0x570f09eaa7ea7648,}; +using powers = powers_template<>; + +} + +#endif + +#ifndef FASTFLOAT_DECIMAL_TO_BINARY_H +#define FASTFLOAT_DECIMAL_TO_BINARY_H + +#include +#include +#include +#include +#include +#include + +namespace fast_float { + +// This will compute or rather approximate w * 5**q and return a pair of 64-bit words approximating +// the result, with the "high" part corresponding to the most significant bits and the +// low part corresponding to the least significant bits. +// +template +fastfloat_really_inline +value128 compute_product_approximation(int64_t q, uint64_t w) { + const int index = 2 * int(q - powers::smallest_power_of_five); + // For small values of q, e.g., q in [0,27], the answer is always exact because + // The line value128 firstproduct = full_multiplication(w, power_of_five_128[index]); + // gives the exact answer. + value128 firstproduct = full_multiplication(w, powers::power_of_five_128[index]); + static_assert((bit_precision >= 0) && (bit_precision <= 64), " precision should be in (0,64]"); + constexpr uint64_t precision_mask = (bit_precision < 64) ? + (uint64_t(0xFFFFFFFFFFFFFFFF) >> bit_precision) + : uint64_t(0xFFFFFFFFFFFFFFFF); + if((firstproduct.high & precision_mask) == precision_mask) { // could further guard with (lower + w < lower) + // regarding the second product, we only need secondproduct.high, but our expectation is that the compiler will optimize this extra work away if needed. + value128 secondproduct = full_multiplication(w, powers::power_of_five_128[index + 1]); + firstproduct.low += secondproduct.high; + if(secondproduct.high > firstproduct.low) { + firstproduct.high++; + } + } + return firstproduct; +} + +namespace detail { +/** + * For q in (0,350), we have that + * f = (((152170 + 65536) * q ) >> 16); + * is equal to + * floor(p) + q + * where + * p = log(5**q)/log(2) = q * log(5)/log(2) + * + * For negative values of q in (-400,0), we have that + * f = (((152170 + 65536) * q ) >> 16); + * is equal to + * -ceil(p) + q + * where + * p = log(5**-q)/log(2) = -q * log(5)/log(2) + */ + constexpr fastfloat_really_inline int32_t power(int32_t q) noexcept { + return (((152170 + 65536) * q) >> 16) + 63; + } +} // namespace detail + +// create an adjusted mantissa, biased by the invalid power2 +// for significant digits already multiplied by 10 ** q. +template +fastfloat_really_inline +adjusted_mantissa compute_error_scaled(int64_t q, uint64_t w, int lz) noexcept { + int hilz = int(w >> 63) ^ 1; + adjusted_mantissa answer; + answer.mantissa = w << hilz; + int bias = binary::mantissa_explicit_bits() - binary::minimum_exponent(); + answer.power2 = int32_t(detail::power(int32_t(q)) + bias - hilz - lz - 62 + invalid_am_bias); + return answer; +} + +// w * 10 ** q, without rounding the representation up. +// the power2 in the exponent will be adjusted by invalid_am_bias. +template +fastfloat_really_inline +adjusted_mantissa compute_error(int64_t q, uint64_t w) noexcept { + int lz = leading_zeroes(w); + w <<= lz; + value128 product = compute_product_approximation(q, w); + return compute_error_scaled(q, product.high, lz); +} + +// w * 10 ** q +// The returned value should be a valid ieee64 number that simply need to be packed. +// However, in some very rare cases, the computation will fail. In such cases, we +// return an adjusted_mantissa with a negative power of 2: the caller should recompute +// in such cases. +template +fastfloat_really_inline +adjusted_mantissa compute_float(int64_t q, uint64_t w) noexcept { + adjusted_mantissa answer; + if ((w == 0) || (q < binary::smallest_power_of_ten())) { + answer.power2 = 0; + answer.mantissa = 0; + // result should be zero + return answer; + } + if (q > binary::largest_power_of_ten()) { + // we want to get infinity: + answer.power2 = binary::infinite_power(); + answer.mantissa = 0; + return answer; + } + // At this point in time q is in [powers::smallest_power_of_five, powers::largest_power_of_five]. + + // We want the most significant bit of i to be 1. Shift if needed. + int lz = leading_zeroes(w); + w <<= lz; + + // The required precision is binary::mantissa_explicit_bits() + 3 because + // 1. We need the implicit bit + // 2. We need an extra bit for rounding purposes + // 3. We might lose a bit due to the "upperbit" routine (result too small, requiring a shift) + + value128 product = compute_product_approximation(q, w); + if(product.low == 0xFFFFFFFFFFFFFFFF) { // could guard it further + // In some very rare cases, this could happen, in which case we might need a more accurate + // computation that what we can provide cheaply. This is very, very unlikely. + // + const bool inside_safe_exponent = (q >= -27) && (q <= 55); // always good because 5**q <2**128 when q>=0, + // and otherwise, for q<0, we have 5**-q<2**64 and the 128-bit reciprocal allows for exact computation. + if(!inside_safe_exponent) { + return compute_error_scaled(q, product.high, lz); + } + } + // The "compute_product_approximation" function can be slightly slower than a branchless approach: + // value128 product = compute_product(q, w); + // but in practice, we can win big with the compute_product_approximation if its additional branch + // is easily predicted. Which is best is data specific. + int upperbit = int(product.high >> 63); + + answer.mantissa = product.high >> (upperbit + 64 - binary::mantissa_explicit_bits() - 3); + + answer.power2 = int32_t(detail::power(int32_t(q)) + upperbit - lz - binary::minimum_exponent()); + if (answer.power2 <= 0) { // we have a subnormal? + // Here have that answer.power2 <= 0 so -answer.power2 >= 0 + if(-answer.power2 + 1 >= 64) { // if we have more than 64 bits below the minimum exponent, you have a zero for sure. + answer.power2 = 0; + answer.mantissa = 0; + // result should be zero + return answer; + } + // next line is safe because -answer.power2 + 1 < 64 + answer.mantissa >>= -answer.power2 + 1; + // Thankfully, we can't have both "round-to-even" and subnormals because + // "round-to-even" only occurs for powers close to 0. + answer.mantissa += (answer.mantissa & 1); // round up + answer.mantissa >>= 1; + // There is a weird scenario where we don't have a subnormal but just. + // Suppose we start with 2.2250738585072013e-308, we end up + // with 0x3fffffffffffff x 2^-1023-53 which is technically subnormal + // whereas 0x40000000000000 x 2^-1023-53 is normal. Now, we need to round + // up 0x3fffffffffffff x 2^-1023-53 and once we do, we are no longer + // subnormal, but we can only know this after rounding. + // So we only declare a subnormal if we are smaller than the threshold. + answer.power2 = (answer.mantissa < (uint64_t(1) << binary::mantissa_explicit_bits())) ? 0 : 1; + return answer; + } + + // usually, we round *up*, but if we fall right in between and and we have an + // even basis, we need to round down + // We are only concerned with the cases where 5**q fits in single 64-bit word. + if ((product.low <= 1) && (q >= binary::min_exponent_round_to_even()) && (q <= binary::max_exponent_round_to_even()) && + ((answer.mantissa & 3) == 1) ) { // we may fall between two floats! + // To be in-between two floats we need that in doing + // answer.mantissa = product.high >> (upperbit + 64 - binary::mantissa_explicit_bits() - 3); + // ... we dropped out only zeroes. But if this happened, then we can go back!!! + if((answer.mantissa << (upperbit + 64 - binary::mantissa_explicit_bits() - 3)) == product.high) { + answer.mantissa &= ~uint64_t(1); // flip it so that we do not round up + } + } + + answer.mantissa += (answer.mantissa & 1); // round up + answer.mantissa >>= 1; + if (answer.mantissa >= (uint64_t(2) << binary::mantissa_explicit_bits())) { + answer.mantissa = (uint64_t(1) << binary::mantissa_explicit_bits()); + answer.power2++; // undo previous addition + } + + answer.mantissa &= ~(uint64_t(1) << binary::mantissa_explicit_bits()); + if (answer.power2 >= binary::infinite_power()) { // infinity + answer.power2 = binary::infinite_power(); + answer.mantissa = 0; + } + return answer; +} + +} // namespace fast_float + +#endif + +#ifndef FASTFLOAT_BIGINT_H +#define FASTFLOAT_BIGINT_H + +#include +#include +#include +#include + + +namespace fast_float { + +// the limb width: we want efficient multiplication of double the bits in +// limb, or for 64-bit limbs, at least 64-bit multiplication where we can +// extract the high and low parts efficiently. this is every 64-bit +// architecture except for sparc, which emulates 128-bit multiplication. +// we might have platforms where `CHAR_BIT` is not 8, so let's avoid +// doing `8 * sizeof(limb)`. +#if defined(FASTFLOAT_64BIT) && !defined(__sparc) +#define FASTFLOAT_64BIT_LIMB +typedef uint64_t limb; +constexpr size_t limb_bits = 64; +#else +#define FASTFLOAT_32BIT_LIMB +typedef uint32_t limb; +constexpr size_t limb_bits = 32; +#endif + +typedef span limb_span; + +// number of bits in a bigint. this needs to be at least the number +// of bits required to store the largest bigint, which is +// `log2(10**(digits + max_exp))`, or `log2(10**(767 + 342))`, or +// ~3600 bits, so we round to 4000. +constexpr size_t bigint_bits = 4000; +constexpr size_t bigint_limbs = bigint_bits / limb_bits; + +// vector-like type that is allocated on the stack. the entire +// buffer is pre-allocated, and only the length changes. +template +struct stackvec { + limb data[size]; + // we never need more than 150 limbs + uint16_t length{0}; + + stackvec() = default; + stackvec(const stackvec &) = delete; + stackvec &operator=(const stackvec &) = delete; + stackvec(stackvec &&) = delete; + stackvec &operator=(stackvec &&other) = delete; + + // create stack vector from existing limb span. + stackvec(limb_span s) { + FASTFLOAT_ASSERT(try_extend(s)); + } + + limb& operator[](size_t index) noexcept { + FASTFLOAT_DEBUG_ASSERT(index < length); + return data[index]; + } + const limb& operator[](size_t index) const noexcept { + FASTFLOAT_DEBUG_ASSERT(index < length); + return data[index]; + } + // index from the end of the container + const limb& rindex(size_t index) const noexcept { + FASTFLOAT_DEBUG_ASSERT(index < length); + size_t rindex = length - index - 1; + return data[rindex]; + } + + // set the length, without bounds checking. + void set_len(size_t len) noexcept { + length = uint16_t(len); + } + constexpr size_t len() const noexcept { + return length; + } + constexpr bool is_empty() const noexcept { + return length == 0; + } + constexpr size_t capacity() const noexcept { + return size; + } + // append item to vector, without bounds checking + void push_unchecked(limb value) noexcept { + data[length] = value; + length++; + } + // append item to vector, returning if item was added + bool try_push(limb value) noexcept { + if (len() < capacity()) { + push_unchecked(value); + return true; + } else { + return false; + } + } + // add items to the vector, from a span, without bounds checking + void extend_unchecked(limb_span s) noexcept { + limb* ptr = data + length; + ::memcpy((void*)ptr, (const void*)s.ptr, sizeof(limb) * s.len()); + set_len(len() + s.len()); + } + // try to add items to the vector, returning if items were added + bool try_extend(limb_span s) noexcept { + if (len() + s.len() <= capacity()) { + extend_unchecked(s); + return true; + } else { + return false; + } + } + // resize the vector, without bounds checking + // if the new size is longer than the vector, assign value to each + // appended item. + void resize_unchecked(size_t new_len, limb value) noexcept { + if (new_len > len()) { + size_t count = new_len - len(); + limb* first = data + len(); + limb* last = first + count; + ::std::fill(first, last, value); + set_len(new_len); + } else { + set_len(new_len); + } + } + // try to resize the vector, returning if the vector was resized. + bool try_resize(size_t new_len, limb value) noexcept { + if (new_len > capacity()) { + return false; + } else { + resize_unchecked(new_len, value); + return true; + } + } + // check if any limbs are non-zero after the given index. + // this needs to be done in reverse order, since the index + // is relative to the most significant limbs. + bool nonzero(size_t index) const noexcept { + while (index < len()) { + if (rindex(index) != 0) { + return true; + } + index++; + } + return false; + } + // normalize the big integer, so most-significant zero limbs are removed. + void normalize() noexcept { + while (len() > 0 && rindex(0) == 0) { + length--; + } + } +}; + +fastfloat_really_inline +uint64_t empty_hi64(bool& truncated) noexcept { + truncated = false; + return 0; +} + +fastfloat_really_inline +uint64_t uint64_hi64(uint64_t r0, bool& truncated) noexcept { + truncated = false; + int shl = leading_zeroes(r0); + return r0 << shl; +} + +fastfloat_really_inline +uint64_t uint64_hi64(uint64_t r0, uint64_t r1, bool& truncated) noexcept { + int shl = leading_zeroes(r0); + if (shl == 0) { + truncated = r1 != 0; + return r0; + } else { + int shr = 64 - shl; + truncated = (r1 << shl) != 0; + return (r0 << shl) | (r1 >> shr); + } +} + +fastfloat_really_inline +uint64_t uint32_hi64(uint32_t r0, bool& truncated) noexcept { + return uint64_hi64(r0, truncated); +} + +fastfloat_really_inline +uint64_t uint32_hi64(uint32_t r0, uint32_t r1, bool& truncated) noexcept { + uint64_t x0 = r0; + uint64_t x1 = r1; + return uint64_hi64((x0 << 32) | x1, truncated); +} + +fastfloat_really_inline +uint64_t uint32_hi64(uint32_t r0, uint32_t r1, uint32_t r2, bool& truncated) noexcept { + uint64_t x0 = r0; + uint64_t x1 = r1; + uint64_t x2 = r2; + return uint64_hi64(x0, (x1 << 32) | x2, truncated); +} + +// add two small integers, checking for overflow. +// we want an efficient operation. for msvc, where +// we don't have built-in intrinsics, this is still +// pretty fast. +fastfloat_really_inline +limb scalar_add(limb x, limb y, bool& overflow) noexcept { + limb z; + +// gcc and clang +#if defined(__has_builtin) + #if __has_builtin(__builtin_add_overflow) + overflow = __builtin_add_overflow(x, y, &z); + return z; + #endif +#endif + + // generic, this still optimizes correctly on MSVC. + z = x + y; + overflow = z < x; + return z; +} + +// multiply two small integers, getting both the high and low bits. +fastfloat_really_inline +limb scalar_mul(limb x, limb y, limb& carry) noexcept { +#ifdef FASTFLOAT_64BIT_LIMB + #if defined(__SIZEOF_INT128__) + // GCC and clang both define it as an extension. + __uint128_t z = __uint128_t(x) * __uint128_t(y) + __uint128_t(carry); + carry = limb(z >> limb_bits); + return limb(z); + #else + // fallback, no native 128-bit integer multiplication with carry. + // on msvc, this optimizes identically, somehow. + value128 z = full_multiplication(x, y); + bool overflow; + z.low = scalar_add(z.low, carry, overflow); + z.high += uint64_t(overflow); // cannot overflow + carry = z.high; + return z.low; + #endif +#else + uint64_t z = uint64_t(x) * uint64_t(y) + uint64_t(carry); + carry = limb(z >> limb_bits); + return limb(z); +#endif +} + +// add scalar value to bigint starting from offset. +// used in grade school multiplication +template +inline bool small_add_from(stackvec& vec, limb y, size_t start) noexcept { + size_t index = start; + limb carry = y; + bool overflow; + while (carry != 0 && index < vec.len()) { + vec[index] = scalar_add(vec[index], carry, overflow); + carry = limb(overflow); + index += 1; + } + if (carry != 0) { + FASTFLOAT_TRY(vec.try_push(carry)); + } + return true; +} + +// add scalar value to bigint. +template +fastfloat_really_inline bool small_add(stackvec& vec, limb y) noexcept { + return small_add_from(vec, y, 0); +} + +// multiply bigint by scalar value. +template +inline bool small_mul(stackvec& vec, limb y) noexcept { + limb carry = 0; + for (size_t index = 0; index < vec.len(); index++) { + vec[index] = scalar_mul(vec[index], y, carry); + } + if (carry != 0) { + FASTFLOAT_TRY(vec.try_push(carry)); + } + return true; +} + +// add bigint to bigint starting from index. +// used in grade school multiplication +template +bool large_add_from(stackvec& x, limb_span y, size_t start) noexcept { + // the effective x buffer is from `xstart..x.len()`, so exit early + // if we can't get that current range. + if (x.len() < start || y.len() > x.len() - start) { + FASTFLOAT_TRY(x.try_resize(y.len() + start, 0)); + } + + bool carry = false; + for (size_t index = 0; index < y.len(); index++) { + limb xi = x[index + start]; + limb yi = y[index]; + bool c1 = false; + bool c2 = false; + xi = scalar_add(xi, yi, c1); + if (carry) { + xi = scalar_add(xi, 1, c2); + } + x[index + start] = xi; + carry = c1 | c2; + } + + // handle overflow + if (carry) { + FASTFLOAT_TRY(small_add_from(x, 1, y.len() + start)); + } + return true; +} + +// add bigint to bigint. +template +fastfloat_really_inline bool large_add_from(stackvec& x, limb_span y) noexcept { + return large_add_from(x, y, 0); +} + +// grade-school multiplication algorithm +template +bool long_mul(stackvec& x, limb_span y) noexcept { + limb_span xs = limb_span(x.data, x.len()); + stackvec z(xs); + limb_span zs = limb_span(z.data, z.len()); + + if (y.len() != 0) { + limb y0 = y[0]; + FASTFLOAT_TRY(small_mul(x, y0)); + for (size_t index = 1; index < y.len(); index++) { + limb yi = y[index]; + stackvec zi; + if (yi != 0) { + // re-use the same buffer throughout + zi.set_len(0); + FASTFLOAT_TRY(zi.try_extend(zs)); + FASTFLOAT_TRY(small_mul(zi, yi)); + limb_span zis = limb_span(zi.data, zi.len()); + FASTFLOAT_TRY(large_add_from(x, zis, index)); + } + } + } + + x.normalize(); + return true; +} + +// grade-school multiplication algorithm +template +bool large_mul(stackvec& x, limb_span y) noexcept { + if (y.len() == 1) { + FASTFLOAT_TRY(small_mul(x, y[0])); + } else { + FASTFLOAT_TRY(long_mul(x, y)); + } + return true; +} + +// big integer type. implements a small subset of big integer +// arithmetic, using simple algorithms since asymptotically +// faster algorithms are slower for a small number of limbs. +// all operations assume the big-integer is normalized. +struct bigint { + // storage of the limbs, in little-endian order. + stackvec vec; + + bigint(): vec() {} + bigint(const bigint &) = delete; + bigint &operator=(const bigint &) = delete; + bigint(bigint &&) = delete; + bigint &operator=(bigint &&other) = delete; + + bigint(uint64_t value): vec() { +#ifdef FASTFLOAT_64BIT_LIMB + vec.push_unchecked(value); +#else + vec.push_unchecked(uint32_t(value)); + vec.push_unchecked(uint32_t(value >> 32)); +#endif + vec.normalize(); + } + + // get the high 64 bits from the vector, and if bits were truncated. + // this is to get the significant digits for the float. + uint64_t hi64(bool& truncated) const noexcept { +#ifdef FASTFLOAT_64BIT_LIMB + if (vec.len() == 0) { + return empty_hi64(truncated); + } else if (vec.len() == 1) { + return uint64_hi64(vec.rindex(0), truncated); + } else { + uint64_t result = uint64_hi64(vec.rindex(0), vec.rindex(1), truncated); + truncated |= vec.nonzero(2); + return result; + } +#else + if (vec.len() == 0) { + return empty_hi64(truncated); + } else if (vec.len() == 1) { + return uint32_hi64(vec.rindex(0), truncated); + } else if (vec.len() == 2) { + return uint32_hi64(vec.rindex(0), vec.rindex(1), truncated); + } else { + uint64_t result = uint32_hi64(vec.rindex(0), vec.rindex(1), vec.rindex(2), truncated); + truncated |= vec.nonzero(3); + return result; + } +#endif + } + + // compare two big integers, returning the large value. + // assumes both are normalized. if the return value is + // negative, other is larger, if the return value is + // positive, this is larger, otherwise they are equal. + // the limbs are stored in little-endian order, so we + // must compare the limbs in ever order. + int compare(const bigint& other) const noexcept { + if (vec.len() > other.vec.len()) { + return 1; + } else if (vec.len() < other.vec.len()) { + return -1; + } else { + for (size_t index = vec.len(); index > 0; index--) { + limb xi = vec[index - 1]; + limb yi = other.vec[index - 1]; + if (xi > yi) { + return 1; + } else if (xi < yi) { + return -1; + } + } + return 0; + } + } + + // shift left each limb n bits, carrying over to the new limb + // returns true if we were able to shift all the digits. + bool shl_bits(size_t n) noexcept { + // Internally, for each item, we shift left by n, and add the previous + // right shifted limb-bits. + // For example, we transform (for u8) shifted left 2, to: + // b10100100 b01000010 + // b10 b10010001 b00001000 + FASTFLOAT_DEBUG_ASSERT(n != 0); + FASTFLOAT_DEBUG_ASSERT(n < sizeof(limb) * 8); + + size_t shl = n; + size_t shr = limb_bits - shl; + limb prev = 0; + for (size_t index = 0; index < vec.len(); index++) { + limb xi = vec[index]; + vec[index] = (xi << shl) | (prev >> shr); + prev = xi; + } + + limb carry = prev >> shr; + if (carry != 0) { + return vec.try_push(carry); + } + return true; + } + + // move the limbs left by `n` limbs. + bool shl_limbs(size_t n) noexcept { + FASTFLOAT_DEBUG_ASSERT(n != 0); + if (n + vec.len() > vec.capacity()) { + return false; + } else if (!vec.is_empty()) { + // move limbs + limb* dst = vec.data + n; + const limb* src = vec.data; + ::memmove(dst, src, sizeof(limb) * vec.len()); + // fill in empty limbs + limb* first = vec.data; + limb* last = first + n; + ::std::fill(first, last, 0); + vec.set_len(n + vec.len()); + return true; + } else { + return true; + } + } + + // move the limbs left by `n` bits. + bool shl(size_t n) noexcept { + size_t rem = n % limb_bits; + size_t div = n / limb_bits; + if (rem != 0) { + FASTFLOAT_TRY(shl_bits(rem)); + } + if (div != 0) { + FASTFLOAT_TRY(shl_limbs(div)); + } + return true; + } + + // get the number of leading zeros in the bigint. + int ctlz() const noexcept { + if (vec.is_empty()) { + return 0; + } else { +#ifdef FASTFLOAT_64BIT_LIMB + return leading_zeroes(vec.rindex(0)); +#else + // no use defining a specialized leading_zeroes for a 32-bit type. + uint64_t r0 = vec.rindex(0); + return leading_zeroes(r0 << 32); +#endif + } + } + + // get the number of bits in the bigint. + int bit_length() const noexcept { + int lz = ctlz(); + return int(limb_bits * vec.len()) - lz; + } + + bool mul(limb y) noexcept { + return small_mul(vec, y); + } + + bool add(limb y) noexcept { + return small_add(vec, y); + } + + // multiply as if by 2 raised to a power. + bool pow2(uint32_t exp) noexcept { + return shl(exp); + } + + // multiply as if by 5 raised to a power. + bool pow5(uint32_t exp) noexcept { + // multiply by a power of 5 + static constexpr uint32_t large_step = 135; + static constexpr uint64_t small_power_of_5[] = { + 1UL, 5UL, 25UL, 125UL, 625UL, 3125UL, 15625UL, 78125UL, 390625UL, + 1953125UL, 9765625UL, 48828125UL, 244140625UL, 1220703125UL, + 6103515625UL, 30517578125UL, 152587890625UL, 762939453125UL, + 3814697265625UL, 19073486328125UL, 95367431640625UL, 476837158203125UL, + 2384185791015625UL, 11920928955078125UL, 59604644775390625UL, + 298023223876953125UL, 1490116119384765625UL, 7450580596923828125UL, + }; +#ifdef FASTFLOAT_64BIT_LIMB + constexpr static limb large_power_of_5[] = { + 1414648277510068013UL, 9180637584431281687UL, 4539964771860779200UL, + 10482974169319127550UL, 198276706040285095UL}; +#else + constexpr static limb large_power_of_5[] = { + 4279965485U, 329373468U, 4020270615U, 2137533757U, 4287402176U, + 1057042919U, 1071430142U, 2440757623U, 381945767U, 46164893U}; +#endif + size_t large_length = sizeof(large_power_of_5) / sizeof(limb); + limb_span large = limb_span(large_power_of_5, large_length); + while (exp >= large_step) { + FASTFLOAT_TRY(large_mul(vec, large)); + exp -= large_step; + } +#ifdef FASTFLOAT_64BIT_LIMB + uint32_t small_step = 27; + limb max_native = 7450580596923828125UL; +#else + uint32_t small_step = 13; + limb max_native = 1220703125U; +#endif + while (exp >= small_step) { + FASTFLOAT_TRY(small_mul(vec, max_native)); + exp -= small_step; + } + if (exp != 0) { + FASTFLOAT_TRY(small_mul(vec, limb(small_power_of_5[exp]))); + } + + return true; + } + + // multiply as if by 10 raised to a power. + bool pow10(uint32_t exp) noexcept { + FASTFLOAT_TRY(pow5(exp)); + return pow2(exp); + } +}; + +} // namespace fast_float + +#endif + +#ifndef FASTFLOAT_ASCII_NUMBER_H +#define FASTFLOAT_ASCII_NUMBER_H + +#include +#include +#include +#include + + +namespace fast_float { + +// Next function can be micro-optimized, but compilers are entirely +// able to optimize it well. +fastfloat_really_inline bool is_integer(char c) noexcept { return c >= '0' && c <= '9'; } + +fastfloat_really_inline uint64_t byteswap(uint64_t val) { + return (val & 0xFF00000000000000) >> 56 + | (val & 0x00FF000000000000) >> 40 + | (val & 0x0000FF0000000000) >> 24 + | (val & 0x000000FF00000000) >> 8 + | (val & 0x00000000FF000000) << 8 + | (val & 0x0000000000FF0000) << 24 + | (val & 0x000000000000FF00) << 40 + | (val & 0x00000000000000FF) << 56; +} + +fastfloat_really_inline uint64_t read_u64(const char *chars) { + uint64_t val; + ::memcpy(&val, chars, sizeof(uint64_t)); +#if FASTFLOAT_IS_BIG_ENDIAN == 1 + // Need to read as-if the number was in little-endian order. + val = byteswap(val); +#endif + return val; +} + +fastfloat_really_inline void write_u64(uint8_t *chars, uint64_t val) { +#if FASTFLOAT_IS_BIG_ENDIAN == 1 + // Need to read as-if the number was in little-endian order. + val = byteswap(val); +#endif + ::memcpy(chars, &val, sizeof(uint64_t)); +} + +// credit @aqrit +fastfloat_really_inline uint32_t parse_eight_digits_unrolled(uint64_t val) { + const uint64_t mask = 0x000000FF000000FF; + const uint64_t mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32) + const uint64_t mul2 = 0x0000271000000001; // 1 + (10000ULL << 32) + val -= 0x3030303030303030; + val = (val * 10) + (val >> 8); // val = (val * 2561) >> 8; + val = (((val & mask) * mul1) + (((val >> 16) & mask) * mul2)) >> 32; + return uint32_t(val); +} + +fastfloat_really_inline uint32_t parse_eight_digits_unrolled(const char *chars) noexcept { + return parse_eight_digits_unrolled(read_u64(chars)); +} + +// credit @aqrit +fastfloat_really_inline bool is_made_of_eight_digits_fast(uint64_t val) noexcept { + return !((((val + 0x4646464646464646) | (val - 0x3030303030303030)) & + 0x8080808080808080)); +} + +fastfloat_really_inline bool is_made_of_eight_digits_fast(const char *chars) noexcept { + return is_made_of_eight_digits_fast(read_u64(chars)); +} + +typedef span byte_span; + +struct parsed_number_string { + int64_t exponent{0}; + uint64_t mantissa{0}; + const char *lastmatch{nullptr}; + bool negative{false}; + bool valid{false}; + bool too_many_digits{false}; + // contains the range of the significant digits + byte_span integer{}; // non-nullable + byte_span fraction{}; // nullable +}; + +// Assuming that you use no more than 19 digits, this will +// parse an ASCII string. +fastfloat_really_inline +parsed_number_string parse_number_string(const char *p, const char *pend, parse_options options) noexcept { + const chars_format fmt = options.format; + const char decimal_point = options.decimal_point; + + parsed_number_string answer; + answer.valid = false; + answer.too_many_digits = false; + answer.negative = (*p == '-'); + if (*p == '-') { // C++17 20.19.3.(7.1) explicitly forbids '+' sign here + ++p; + if (p == pend) { + return answer; + } + if (!is_integer(*p) && (*p != decimal_point)) { // a sign must be followed by an integer or the dot + return answer; + } + } + const char *const start_digits = p; + + uint64_t i = 0; // an unsigned int avoids signed overflows (which are bad) + + while ((std::distance(p, pend) >= 8) && is_made_of_eight_digits_fast(p)) { + i = i * 100000000 + parse_eight_digits_unrolled(p); // in rare cases, this will overflow, but that's ok + p += 8; + } + while ((p != pend) && is_integer(*p)) { + // a multiplication by 10 is cheaper than an arbitrary integer + // multiplication + i = 10 * i + + uint64_t(*p - '0'); // might overflow, we will handle the overflow later + ++p; + } + const char *const end_of_integer_part = p; + int64_t digit_count = int64_t(end_of_integer_part - start_digits); + answer.integer = byte_span(start_digits, size_t(digit_count)); + int64_t exponent = 0; + if ((p != pend) && (*p == decimal_point)) { + ++p; + const char* before = p; + // can occur at most twice without overflowing, but let it occur more, since + // for integers with many digits, digit parsing is the primary bottleneck. + while ((std::distance(p, pend) >= 8) && is_made_of_eight_digits_fast(p)) { + i = i * 100000000 + parse_eight_digits_unrolled(p); // in rare cases, this will overflow, but that's ok + p += 8; + } + while ((p != pend) && is_integer(*p)) { + uint8_t digit = uint8_t(*p - '0'); + ++p; + i = i * 10 + digit; // in rare cases, this will overflow, but that's ok + } + exponent = before - p; + answer.fraction = byte_span(before, size_t(p - before)); + digit_count -= exponent; + } + // we must have encountered at least one integer! + if (digit_count == 0) { + return answer; + } + int64_t exp_number = 0; // explicit exponential part + if ((fmt & chars_format::scientific) && (p != pend) && (('e' == *p) || ('E' == *p))) { + const char * location_of_e = p; + ++p; + bool neg_exp = false; + if ((p != pend) && ('-' == *p)) { + neg_exp = true; + ++p; + } else if ((p != pend) && ('+' == *p)) { // '+' on exponent is allowed by C++17 20.19.3.(7.1) + ++p; + } + if ((p == pend) || !is_integer(*p)) { + if(!(fmt & chars_format::fixed)) { + // We are in error. + return answer; + } + // Otherwise, we will be ignoring the 'e'. + p = location_of_e; + } else { + while ((p != pend) && is_integer(*p)) { + uint8_t digit = uint8_t(*p - '0'); + if (exp_number < 0x10000000) { + exp_number = 10 * exp_number + digit; + } + ++p; + } + if(neg_exp) { exp_number = - exp_number; } + exponent += exp_number; + } + } else { + // If it scientific and not fixed, we have to bail out. + if((fmt & chars_format::scientific) && !(fmt & chars_format::fixed)) { return answer; } + } + answer.lastmatch = p; + answer.valid = true; + + // If we frequently had to deal with long strings of digits, + // we could extend our code by using a 128-bit integer instead + // of a 64-bit integer. However, this is uncommon. + // + // We can deal with up to 19 digits. + if (digit_count > 19) { // this is uncommon + // It is possible that the integer had an overflow. + // We have to handle the case where we have 0.0000somenumber. + // We need to be mindful of the case where we only have zeroes... + // E.g., 0.000000000...000. + const char *start = start_digits; + while ((start != pend) && (*start == '0' || *start == decimal_point)) { + if(*start == '0') { digit_count --; } + start++; + } + if (digit_count > 19) { + answer.too_many_digits = true; + // Let us start again, this time, avoiding overflows. + // We don't need to check if is_integer, since we use the + // pre-tokenized spans from above. + i = 0; + p = answer.integer.ptr; + const char* int_end = p + answer.integer.len(); + const uint64_t minimal_nineteen_digit_integer{1000000000000000000}; + while((i < minimal_nineteen_digit_integer) && (p != int_end)) { + i = i * 10 + uint64_t(*p - '0'); + ++p; + } + if (i >= minimal_nineteen_digit_integer) { // We have a big integers + exponent = end_of_integer_part - p + exp_number; + } else { // We have a value with a fractional component. + p = answer.fraction.ptr; + const char* frac_end = p + answer.fraction.len(); + while((i < minimal_nineteen_digit_integer) && (p != frac_end)) { + i = i * 10 + uint64_t(*p - '0'); + ++p; + } + exponent = answer.fraction.ptr - p + exp_number; + } + // We have now corrected both exponent and i, to a truncated value + } + } + answer.exponent = exponent; + answer.mantissa = i; + return answer; +} + +} // namespace fast_float + +#endif + +#ifndef FASTFLOAT_DIGIT_COMPARISON_H +#define FASTFLOAT_DIGIT_COMPARISON_H + +#include +#include +#include +#include + + +namespace fast_float { + +// 1e0 to 1e19 +constexpr static uint64_t powers_of_ten_uint64[] = { + 1UL, 10UL, 100UL, 1000UL, 10000UL, 100000UL, 1000000UL, 10000000UL, 100000000UL, + 1000000000UL, 10000000000UL, 100000000000UL, 1000000000000UL, 10000000000000UL, + 100000000000000UL, 1000000000000000UL, 10000000000000000UL, 100000000000000000UL, + 1000000000000000000UL, 10000000000000000000UL}; + +// calculate the exponent, in scientific notation, of the number. +// this algorithm is not even close to optimized, but it has no practical +// effect on performance: in order to have a faster algorithm, we'd need +// to slow down performance for faster algorithms, and this is still fast. +fastfloat_really_inline int32_t scientific_exponent(parsed_number_string& num) noexcept { + uint64_t mantissa = num.mantissa; + int32_t exponent = int32_t(num.exponent); + while (mantissa >= 10000) { + mantissa /= 10000; + exponent += 4; + } + while (mantissa >= 100) { + mantissa /= 100; + exponent += 2; + } + while (mantissa >= 10) { + mantissa /= 10; + exponent += 1; + } + return exponent; +} + +// this converts a native floating-point number to an extended-precision float. +template +fastfloat_really_inline adjusted_mantissa to_extended(T value) noexcept { + using equiv_uint = typename binary_format::equiv_uint; + constexpr equiv_uint exponent_mask = binary_format::exponent_mask(); + constexpr equiv_uint mantissa_mask = binary_format::mantissa_mask(); + constexpr equiv_uint hidden_bit_mask = binary_format::hidden_bit_mask(); + + adjusted_mantissa am; + int32_t bias = binary_format::mantissa_explicit_bits() - binary_format::minimum_exponent(); + equiv_uint bits; + ::memcpy(&bits, &value, sizeof(T)); + if ((bits & exponent_mask) == 0) { + // denormal + am.power2 = 1 - bias; + am.mantissa = bits & mantissa_mask; + } else { + // normal + am.power2 = int32_t((bits & exponent_mask) >> binary_format::mantissa_explicit_bits()); + am.power2 -= bias; + am.mantissa = (bits & mantissa_mask) | hidden_bit_mask; + } + + return am; +} + +// get the extended precision value of the halfway point between b and b+u. +// we are given a native float that represents b, so we need to adjust it +// halfway between b and b+u. +template +fastfloat_really_inline adjusted_mantissa to_extended_halfway(T value) noexcept { + adjusted_mantissa am = to_extended(value); + am.mantissa <<= 1; + am.mantissa += 1; + am.power2 -= 1; + return am; +} + +// round an extended-precision float to the nearest machine float. +template +fastfloat_really_inline void round(adjusted_mantissa& am, callback cb) noexcept { + int32_t mantissa_shift = 64 - binary_format::mantissa_explicit_bits() - 1; + if (-am.power2 >= mantissa_shift) { + // have a denormal float + int32_t shift = -am.power2 + 1; + cb(am, std::min(shift, 64)); + // check for round-up: if rounding-nearest carried us to the hidden bit. + am.power2 = (am.mantissa < (uint64_t(1) << binary_format::mantissa_explicit_bits())) ? 0 : 1; + return; + } + + // have a normal float, use the default shift. + cb(am, mantissa_shift); + + // check for carry + if (am.mantissa >= (uint64_t(2) << binary_format::mantissa_explicit_bits())) { + am.mantissa = (uint64_t(1) << binary_format::mantissa_explicit_bits()); + am.power2++; + } + + // check for infinite: we could have carried to an infinite power + am.mantissa &= ~(uint64_t(1) << binary_format::mantissa_explicit_bits()); + if (am.power2 >= binary_format::infinite_power()) { + am.power2 = binary_format::infinite_power(); + am.mantissa = 0; + } +} + +template +fastfloat_really_inline +void round_nearest_tie_even(adjusted_mantissa& am, int32_t shift, callback cb) noexcept { + uint64_t mask; + uint64_t halfway; + if (shift == 64) { + mask = UINT64_MAX; + } else { + mask = (uint64_t(1) << shift) - 1; + } + if (shift == 0) { + halfway = 0; + } else { + halfway = uint64_t(1) << (shift - 1); + } + uint64_t truncated_bits = am.mantissa & mask; + uint64_t is_above = truncated_bits > halfway; + uint64_t is_halfway = truncated_bits == halfway; + + // shift digits into position + if (shift == 64) { + am.mantissa = 0; + } else { + am.mantissa >>= shift; + } + am.power2 += shift; + + bool is_odd = (am.mantissa & 1) == 1; + am.mantissa += uint64_t(cb(is_odd, is_halfway, is_above)); +} + +fastfloat_really_inline void round_down(adjusted_mantissa& am, int32_t shift) noexcept { + if (shift == 64) { + am.mantissa = 0; + } else { + am.mantissa >>= shift; + } + am.power2 += shift; +} + +fastfloat_really_inline void skip_zeros(const char*& first, const char* last) noexcept { + uint64_t val; + while (std::distance(first, last) >= 8) { + ::memcpy(&val, first, sizeof(uint64_t)); + if (val != 0x3030303030303030) { + break; + } + first += 8; + } + while (first != last) { + if (*first != '0') { + break; + } + first++; + } +} + +// determine if any non-zero digits were truncated. +// all characters must be valid digits. +fastfloat_really_inline bool is_truncated(const char* first, const char* last) noexcept { + // do 8-bit optimizations, can just compare to 8 literal 0s. + uint64_t val; + while (std::distance(first, last) >= 8) { + ::memcpy(&val, first, sizeof(uint64_t)); + if (val != 0x3030303030303030) { + return true; + } + first += 8; + } + while (first != last) { + if (*first != '0') { + return true; + } + first++; + } + return false; +} + +fastfloat_really_inline bool is_truncated(byte_span s) noexcept { + return is_truncated(s.ptr, s.ptr + s.len()); +} + +fastfloat_really_inline +void parse_eight_digits(const char*& p, limb& value, size_t& counter, size_t& count) noexcept { + value = value * 100000000 + parse_eight_digits_unrolled(p); + p += 8; + counter += 8; + count += 8; +} + +fastfloat_really_inline +void parse_one_digit(const char*& p, limb& value, size_t& counter, size_t& count) noexcept { + value = value * 10 + limb(*p - '0'); + p++; + counter++; + count++; +} + +fastfloat_really_inline +void add_native(bigint& big, limb power, limb value) noexcept { + big.mul(power); + big.add(value); +} + +fastfloat_really_inline void round_up_bigint(bigint& big, size_t& count) noexcept { + // need to round-up the digits, but need to avoid rounding + // ....9999 to ...10000, which could cause a false halfway point. + add_native(big, 10, 1); + count++; +} + +// parse the significant digits into a big integer +inline void parse_mantissa(bigint& result, parsed_number_string& num, size_t max_digits, size_t& digits) noexcept { + // try to minimize the number of big integer and scalar multiplication. + // therefore, try to parse 8 digits at a time, and multiply by the largest + // scalar value (9 or 19 digits) for each step. + size_t counter = 0; + digits = 0; + limb value = 0; +#ifdef FASTFLOAT_64BIT_LIMB + size_t step = 19; +#else + size_t step = 9; +#endif + + // process all integer digits. + const char* p = num.integer.ptr; + const char* pend = p + num.integer.len(); + skip_zeros(p, pend); + // process all digits, in increments of step per loop + while (p != pend) { + while ((std::distance(p, pend) >= 8) && (step - counter >= 8) && (max_digits - digits >= 8)) { + parse_eight_digits(p, value, counter, digits); + } + while (counter < step && p != pend && digits < max_digits) { + parse_one_digit(p, value, counter, digits); + } + if (digits == max_digits) { + // add the temporary value, then check if we've truncated any digits + add_native(result, limb(powers_of_ten_uint64[counter]), value); + bool truncated = is_truncated(p, pend); + if (num.fraction.ptr != nullptr) { + truncated |= is_truncated(num.fraction); + } + if (truncated) { + round_up_bigint(result, digits); + } + return; + } else { + add_native(result, limb(powers_of_ten_uint64[counter]), value); + counter = 0; + value = 0; + } + } + + // add our fraction digits, if they're available. + if (num.fraction.ptr != nullptr) { + p = num.fraction.ptr; + pend = p + num.fraction.len(); + if (digits == 0) { + skip_zeros(p, pend); + } + // process all digits, in increments of step per loop + while (p != pend) { + while ((std::distance(p, pend) >= 8) && (step - counter >= 8) && (max_digits - digits >= 8)) { + parse_eight_digits(p, value, counter, digits); + } + while (counter < step && p != pend && digits < max_digits) { + parse_one_digit(p, value, counter, digits); + } + if (digits == max_digits) { + // add the temporary value, then check if we've truncated any digits + add_native(result, limb(powers_of_ten_uint64[counter]), value); + bool truncated = is_truncated(p, pend); + if (truncated) { + round_up_bigint(result, digits); + } + return; + } else { + add_native(result, limb(powers_of_ten_uint64[counter]), value); + counter = 0; + value = 0; + } + } + } + + if (counter != 0) { + add_native(result, limb(powers_of_ten_uint64[counter]), value); + } +} + +template +inline adjusted_mantissa positive_digit_comp(bigint& bigmant, int32_t exponent) noexcept { + FASTFLOAT_ASSERT(bigmant.pow10(uint32_t(exponent))); + adjusted_mantissa answer; + bool truncated; + answer.mantissa = bigmant.hi64(truncated); + int bias = binary_format::mantissa_explicit_bits() - binary_format::minimum_exponent(); + answer.power2 = bigmant.bit_length() - 64 + bias; + + round(answer, [truncated](adjusted_mantissa& a, int32_t shift) { + round_nearest_tie_even(a, shift, [truncated](bool is_odd, bool is_halfway, bool is_above) -> bool { + return is_above || (is_halfway && truncated) || (is_odd && is_halfway); + }); + }); + + return answer; +} + +// the scaling here is quite simple: we have, for the real digits `m * 10^e`, +// and for the theoretical digits `n * 2^f`. Since `e` is always negative, +// to scale them identically, we do `n * 2^f * 5^-f`, so we now have `m * 2^e`. +// we then need to scale by `2^(f- e)`, and then the two significant digits +// are of the same magnitude. +template +inline adjusted_mantissa negative_digit_comp(bigint& bigmant, adjusted_mantissa am, int32_t exponent) noexcept { + bigint& real_digits = bigmant; + int32_t real_exp = exponent; + + // get the value of `b`, rounded down, and get a bigint representation of b+h + adjusted_mantissa am_b = am; + // gcc7 buf: use a lambda to remove the noexcept qualifier bug with -Wnoexcept-type. + round(am_b, [](adjusted_mantissa&a, int32_t shift) { round_down(a, shift); }); + T b; + to_float(false, am_b, b); + adjusted_mantissa theor = to_extended_halfway(b); + bigint theor_digits(theor.mantissa); + int32_t theor_exp = theor.power2; + + // scale real digits and theor digits to be same power. + int32_t pow2_exp = theor_exp - real_exp; + uint32_t pow5_exp = uint32_t(-real_exp); + if (pow5_exp != 0) { + FASTFLOAT_ASSERT(theor_digits.pow5(pow5_exp)); + } + if (pow2_exp > 0) { + FASTFLOAT_ASSERT(theor_digits.pow2(uint32_t(pow2_exp))); + } else if (pow2_exp < 0) { + FASTFLOAT_ASSERT(real_digits.pow2(uint32_t(-pow2_exp))); + } + + // compare digits, and use it to director rounding + int ord = real_digits.compare(theor_digits); + adjusted_mantissa answer = am; + round(answer, [ord](adjusted_mantissa& a, int32_t shift) { + round_nearest_tie_even(a, shift, [ord](bool is_odd, bool _, bool __) -> bool { + (void)_; // not needed, since we've done our comparison + (void)__; // not needed, since we've done our comparison + if (ord > 0) { + return true; + } else if (ord < 0) { + return false; + } else { + return is_odd; + } + }); + }); + + return answer; +} + +// parse the significant digits as a big integer to unambiguously round the +// the significant digits. here, we are trying to determine how to round +// an extended float representation close to `b+h`, halfway between `b` +// (the float rounded-down) and `b+u`, the next positive float. this +// algorithm is always correct, and uses one of two approaches. when +// the exponent is positive relative to the significant digits (such as +// 1234), we create a big-integer representation, get the high 64-bits, +// determine if any lower bits are truncated, and use that to direct +// rounding. in case of a negative exponent relative to the significant +// digits (such as 1.2345), we create a theoretical representation of +// `b` as a big-integer type, scaled to the same binary exponent as +// the actual digits. we then compare the big integer representations +// of both, and use that to direct rounding. +template +inline adjusted_mantissa digit_comp(parsed_number_string& num, adjusted_mantissa am) noexcept { + // remove the invalid exponent bias + am.power2 -= invalid_am_bias; + + int32_t sci_exp = scientific_exponent(num); + size_t max_digits = binary_format::max_digits(); + size_t digits = 0; + bigint bigmant; + parse_mantissa(bigmant, num, max_digits, digits); + // can't underflow, since digits is at most max_digits. + int32_t exponent = sci_exp + 1 - int32_t(digits); + if (exponent >= 0) { + return positive_digit_comp(bigmant, exponent); + } else { + return negative_digit_comp(bigmant, am, exponent); + } +} + +} // namespace fast_float + +#endif + +#ifndef FASTFLOAT_PARSE_NUMBER_H +#define FASTFLOAT_PARSE_NUMBER_H + + +#include +#include +#include +#include + +namespace fast_float { + + +namespace detail { +/** + * Special case +inf, -inf, nan, infinity, -infinity. + * The case comparisons could be made much faster given that we know that the + * strings a null-free and fixed. + **/ +template +from_chars_result parse_infnan(const char *first, const char *last, T &value) noexcept { + from_chars_result answer; + answer.ptr = first; + answer.ec = std::errc(); // be optimistic + bool minusSign = false; + if (*first == '-') { // assume first < last, so dereference without checks; C++17 20.19.3.(7.1) explicitly forbids '+' here + minusSign = true; + ++first; + } + if (last - first >= 3) { + if (fastfloat_strncasecmp(first, "nan", 3)) { + answer.ptr = (first += 3); + value = minusSign ? -std::numeric_limits::quiet_NaN() : std::numeric_limits::quiet_NaN(); + // Check for possible nan(n-char-seq-opt), C++17 20.19.3.7, C11 7.20.1.3.3. At least MSVC produces nan(ind) and nan(snan). + if(first != last && *first == '(') { + for(const char* ptr = first + 1; ptr != last; ++ptr) { + if (*ptr == ')') { + answer.ptr = ptr + 1; // valid nan(n-char-seq-opt) + break; + } + else if(!(('a' <= *ptr && *ptr <= 'z') || ('A' <= *ptr && *ptr <= 'Z') || ('0' <= *ptr && *ptr <= '9') || *ptr == '_')) + break; // forbidden char, not nan(n-char-seq-opt) + } + } + return answer; + } + if (fastfloat_strncasecmp(first, "inf", 3)) { + if ((last - first >= 8) && fastfloat_strncasecmp(first + 3, "inity", 5)) { + answer.ptr = first + 8; + } else { + answer.ptr = first + 3; + } + value = minusSign ? -std::numeric_limits::infinity() : std::numeric_limits::infinity(); + return answer; + } + } + answer.ec = std::errc::invalid_argument; + return answer; +} + +} // namespace detail + +template +from_chars_result from_chars(const char *first, const char *last, + T &value, chars_format fmt /*= chars_format::general*/) noexcept { + return from_chars_advanced(first, last, value, parse_options{fmt}); +} + +template +from_chars_result from_chars_advanced(const char *first, const char *last, + T &value, parse_options options) noexcept { + + static_assert (std::is_same::value || std::is_same::value, "only float and double are supported"); + + + from_chars_result answer; + if (first == last) { + answer.ec = std::errc::invalid_argument; + answer.ptr = first; + return answer; + } + parsed_number_string pns = parse_number_string(first, last, options); + if (!pns.valid) { + return detail::parse_infnan(first, last, value); + } + answer.ec = std::errc(); // be optimistic + answer.ptr = pns.lastmatch; + // Next is Clinger's fast path. + if (binary_format::min_exponent_fast_path() <= pns.exponent && pns.exponent <= binary_format::max_exponent_fast_path() && pns.mantissa <=binary_format::max_mantissa_fast_path() && !pns.too_many_digits) { + value = T(pns.mantissa); + if (pns.exponent < 0) { value = value / binary_format::exact_power_of_ten(-pns.exponent); } + else { value = value * binary_format::exact_power_of_ten(pns.exponent); } + if (pns.negative) { value = -value; } + return answer; + } + adjusted_mantissa am = compute_float>(pns.exponent, pns.mantissa); + if(pns.too_many_digits && am.power2 >= 0) { + if(am != compute_float>(pns.exponent, pns.mantissa + 1)) { + am = compute_error>(pns.exponent, pns.mantissa); + } + } + // If we called compute_float>(pns.exponent, pns.mantissa) and we have an invalid power (am.power2 < 0), + // then we need to go the long way around again. This is very uncommon. + if(am.power2 < 0) { am = digit_comp(pns, am); } + to_float(pns.negative, am, value); + return answer; +} + +} // namespace fast_float + +#endif + + diff --git a/src/Common/linux/platform.cpp b/src/Common/linux/platform.cpp new file mode 100644 index 00000000..592a665f --- /dev/null +++ b/src/Common/linux/platform.cpp @@ -0,0 +1,23 @@ +#include +#include + +uint32_t GetTickCount() +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (1000 * ts.tv_sec + ts.tv_nsec / 1000000); +} + +#include + +void (__cpuid)(int __cpuVal[4], unsigned int __leaf) +{ + __cpuid(__cpuVal[0], __cpuVal[1], __cpuVal[2], __cpuVal[3], __leaf); +} +#undef __cpuid + +void __cpuidex (int __cpuid_info[4], int __leaf, int __subleaf) +{ + __cpuid_count (__leaf, __subleaf, __cpuid_info[0], __cpuid_info[1], + __cpuid_info[2], __cpuid_info[3]); +} \ No newline at end of file diff --git a/src/Common/linux/platform.h b/src/Common/linux/platform.h new file mode 100644 index 00000000..f8aeff56 --- /dev/null +++ b/src/Common/linux/platform.h @@ -0,0 +1,96 @@ +#include + +class SlimRWLock +{ +public: + void LockRead() + { + m_sm.lock_shared(); + } + + void UnlockRead() + { + m_sm.unlock_shared(); + } + + void LockWrite() + { + m_sm.lock(); + } + + void UnlockWrite() + { + m_sm.unlock(); + } + +private: + std::shared_mutex m_sm; +}; + +inline uint32_t GetExceptionError() +{ + return errno; +} + +#include +#include +#include +#undef False +#undef True +#undef None +#undef Bool +#undef Status +#undef Success +#undef ClientMessage + +// cpu id (somewhat hacky, reorganize later) +void (__cpuid)(int __cpuVal[4], unsigned int __leaf); +void __cpuidex (int __cpuid_info[4], int __leaf, int __subleaf); + +// placeholder +uint32_t GetTickCount(); + +// strcpy_s and strcat_s implementations +template +void strcpy_s(char (&dst)[N], const char* src) +{ + if(N == 0) + return; + char* dstP = dst; + const char* end = src + N - 1; + while(src < end) + { + char c = *src; + *dstP = c; + if(c == '\0') + return; + dstP++; + src++; + c++; + } + *dstP = '\0'; + return; +} + +template +void strcat_s(char (&dst)[N], const char* src) +{ + if(N == 0) + return; + char* dstP = dst; + const char* end = dstP + N - 1; + while(dstP < end && *dstP != '\0') + dstP++; + while(dstP < end) + { + char c = *src; + *dstP = c; + if(c == '\0') + return; + dstP++; + src++; + c++; + } + *dstP = '\0'; + return; +} diff --git a/src/Common/platform.h b/src/Common/platform.h new file mode 100644 index 00000000..d47bcf74 --- /dev/null +++ b/src/Common/platform.h @@ -0,0 +1,16 @@ +#pragma once + +#include "boost/predef/os.h" +#include + +#if BOOST_OS_WINDOWS +#include "Common/windows/platform.h" +#elif BOOST_OS_LINUX +#include "byteswap.h" +//#include +// #include +#include "Common/linux/platform.h" + +#elif BOOST_OS_MACOS + +#endif \ No newline at end of file diff --git a/src/Common/precompiled.cpp b/src/Common/precompiled.cpp new file mode 100644 index 00000000..c08ca1f1 --- /dev/null +++ b/src/Common/precompiled.cpp @@ -0,0 +1 @@ +#include "precompiled.h" \ No newline at end of file diff --git a/src/Common/precompiled.h b/src/Common/precompiled.h new file mode 100644 index 00000000..0ec725a6 --- /dev/null +++ b/src/Common/precompiled.h @@ -0,0 +1,434 @@ +#pragma once + +#include // for size_t + +#include "version.h" +#include "platform.h" + +#define FMT_HEADER_ONLY +#define FMT_USE_GRISU 1 +#include +#include +#include +#if FMT_VERSION > 80000 +#include // needed for wchar_t support +#endif + +#define SDL_MAIN_HANDLED + +// #if FMT_VERSION > 80102 +// // restore generic formatter for enum (class) to underlying. We currently utilize this for HLE call logging +// template ::value, int> = 0> +// constexpr auto format_as(Enum e) noexcept -> std::underlying_type { +// return static_cast>(e); +// } +// #endif + +// c includes +#include +#include +#include +#include +#include +#include + +// c++ includes +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +#include "enumFlags.h" +#include "zstring_view.h" + +// base types +using uint64 = uint64_t; +using uint32 = uint32_t; +using uint16 = uint16_t; +using uint8 = uint8_t; + +using sint64 = int64_t; +using sint32 = int32_t; +using sint16 = int16_t; +using sint8 = int8_t; + +using MPTR = uint32; +using MPTR_UINT8 = uint32; +using MPTR_UINT16 = uint32; +using MPTR_UINT32 = uint32; + +// types with explicit big endian order +#include "betype.h" + +// types with explicit little endian order (on a big-endian system these would be implemented like betype.h) +using uint64le = uint64_t; +using uint32le = uint32_t; +using uint16le = uint16_t; +using uint8le = uint8_t; + +// logging +#include "Cemu/Logging/CemuDebugLogging.h" +#include "Cemu/Logging/CemuLogging.h" + +// CPU extensions +extern bool _cpuExtension_SSSE3; +extern bool _cpuExtension_SSE4_1; +extern bool _cpuExtension_AVX2; + +// manual endian-swapping + +#if _MSC_VER +inline uint64 _swapEndianU64(uint64 v) +{ + return _byteswap_uint64(v); +} + +inline uint32 _swapEndianU32(uint32 v) +{ + return _byteswap_ulong(v); +} + +inline sint32 _swapEndianS32(sint32 v) +{ + return (sint32)_byteswap_ulong((uint32)v); +} + +inline uint16 _swapEndianU16(uint16 v) +{ + return (v >> 8) | (v << 8); +} + +inline sint16 _swapEndianS16(sint16 v) +{ + return (sint16)(((uint16)v >> 8) | ((uint16)v << 8)); +} +#endif + +#if BOOST_OS_LINUX +inline uint64 _swapEndianU64(uint64 v) +{ + return bswap_64(v); +} + +inline uint32 _swapEndianU32(uint32 v) +{ + return bswap_32(v); +} + +inline sint32 _swapEndianS32(sint32 v) +{ + return (sint32)bswap_32((uint32)v); +} + +inline uint16 _swapEndianU16(uint16 v) +{ + return (v >> 8) | (v << 8); +} + +inline sint16 _swapEndianS16(sint16 v) +{ + return (sint16)(((uint16)v >> 8) | ((uint16)v << 8)); +} + +typedef uint8_t BYTE; +typedef uint32_t DWORD; +typedef int32_t LONG; +typedef int64_t LONGLONG; + +typedef union _LARGE_INTEGER { + struct { + DWORD LowPart; + LONG HighPart; + }; + struct { + DWORD LowPart; + LONG HighPart; + } u; + LONGLONG QuadPart; +} LARGE_INTEGER, *PLARGE_INTEGER; + +#define DEFINE_ENUM_FLAG_OPERATORS(T) \ + inline T operator~ (T a) { return static_cast( ~static_cast::type>(a) ); } \ + inline T operator| (T a, T b) { return static_cast( static_cast::type>(a) | static_cast::type>(b) ); } \ + inline T operator& (T a, T b) { return static_cast( static_cast::type>(a) & static_cast::type>(b) ); } \ + inline T operator^ (T a, T b) { return static_cast( static_cast::type>(a) ^ static_cast::type>(b) ); } \ + inline T& operator|= (T& a, T b) { return reinterpret_cast( reinterpret_cast::type&>(a) |= static_cast::type>(b) ); } \ + inline T& operator&= (T& a, T b) { return reinterpret_cast( reinterpret_cast::type&>(a) &= static_cast::type>(b) ); } \ + inline T& operator^= (T& a, T b) { return reinterpret_cast( reinterpret_cast::type&>(a) ^= static_cast::type>(b) ); } +#endif + +// MEMPTR +#include "Common/MemPtr.h" + +#define MPTR_NULL (0) + +// macros +#if BOOST_OS_WINDOWS +#define DLLEXPORT __declspec(dllexport) +#else +#define DLLEXPORT __declspec(dllexport) +#endif + +template +constexpr bool HAS_FLAG(T1 flags, T2 test_flag) { return (flags & (T1)test_flag) == (T1)test_flag; } +template +constexpr bool HAS_BIT(T1 value, T2 index) { return (value & ((T1)1 << index)) != 0; } +template +constexpr void SAFE_RELEASE(T& p) { if (p) { p->Release(); p = nullptr; } } + +template +constexpr uint32_t ppcsizeof() { return (uint32_t) sizeof(T); } + +// common utility functions + +template +void vectorAppendUnique(std::vector& vec, const T& val) // for cases where a small vector is more efficient than a set +{ + if (std::find(vec.begin(), vec.end(), val) != vec.end()) + return; + vec.emplace_back(val); +} + +template +void vectorRemoveByValue(std::vector& vec, const T& val) +{ + vec.erase(std::remove(vec.begin(), vec.end(), val), vec.end()); +} + +template +void vectorRemoveByIndex(std::vector& vec, const size_t index) +{ + vec.erase(vec.begin() + index); +} + +template +int match_any_of(T1 value, T2 compareTo) +{ + return value == compareTo; +} + +template +bool match_any_of(T1 value, T2 compareTo, Types&&... others) +{ + return value == compareTo || match_any_of(value, others...); +} + +// we cache the frequency in a static variable +[[nodiscard]] static std::chrono::high_resolution_clock::time_point now_cached() noexcept +{ +#ifdef _WIN32 + // get current time + static const long long _Freq = _Query_perf_frequency(); // doesn't change after system boot + const long long _Ctr = _Query_perf_counter(); + static_assert(std::nano::num == 1, "This assumes period::num == 1."); + const long long _Whole = (_Ctr / _Freq) * std::nano::den; + const long long _Part = (_Ctr % _Freq) * std::nano::den / _Freq; + return (std::chrono::high_resolution_clock::time_point(std::chrono::nanoseconds(_Whole + _Part))); +#else + return std::chrono::high_resolution_clock::now(); +#endif +} + + +[[nodiscard]] static std::chrono::steady_clock::time_point tick_cached() noexcept +{ +#ifdef _WIN32 + // get current time + static const long long _Freq = _Query_perf_frequency(); // doesn't change after system boot + const long long _Ctr = _Query_perf_counter(); + static_assert(std::nano::num == 1, "This assumes period::num == 1."); + const long long _Whole = (_Ctr / _Freq) * std::nano::den; + const long long _Part = (_Ctr % _Freq) * std::nano::den / _Freq; + return (std::chrono::steady_clock::time_point(std::chrono::nanoseconds(_Whole + _Part))); +#else + // todo: Add faster implementation for linux + return std::chrono::steady_clock::now(); +#endif +} + +inline void cemu_assert(bool _condition) +{ + if ((_condition) == false) + { + __debugbreak(); + } +} + +#ifdef PUBLIC_RELEASE +//#define cemu_assert_debug(__cond) -> Forcing __cond not to be evaluated currently has unexpected side-effects + +inline void cemu_assert_debug(bool _condition) +{ +} + +inline void cemu_assert_unimplemented() +{ +} + +inline void cemu_assert_suspicious() +{ +} + +inline void cemu_assert_error() +{ + __debugbreak(); +} +#else +inline void cemu_assert_debug(bool _condition) +{ + if ((_condition) == false) + __debugbreak(); +} + +inline void cemu_assert_unimplemented() +{ + __debugbreak(); +} + +inline void cemu_assert_suspicious() +{ + __debugbreak(); +} + +inline void cemu_assert_error() +{ + __debugbreak(); +} +#endif + +#define assert_dbg() __debugbreak() // old style unconditional generic assert + +// Some string conversion helpers because C++20 std::u8string is too cumbersome to use in practice +// mixing string types generally causes loads of issues and many of the libraries we use dont expose interfaces for u8string + +inline const char* _utf8WrapperPtr(const char8_t* input) +{ + // use with care + return (const char*)input; +} + +inline std::string_view _utf8Wrapper(std::u8string_view input) +{ + std::basic_string_view v((char*)input.data(), input.size()); + return v; +} + +// returns a std::u8string as std::string, the contents are left as-is +inline std::string _utf8Wrapper(const std::u8string& u8str) +{ + std::string v; + v.resize(u8str.size()); + memcpy(v.data(), u8str.data(), u8str.size()); + return v; +} + +// get utf8 generic path string directly from std::filesystem::path +inline std::string _utf8Wrapper(const fs::path& path) +{ + return _utf8Wrapper(path.generic_u8string()); +} + +inline std::u8string_view _asUtf8(std::string_view input) +{ + std::basic_string_view v((char8_t*)input.data(), input.size()); + return v; +} + +class RunAtCemuBoot // -> replaces this with direct function calls. Linkers other than MSVC may optimize way object files entirely if they are not referenced from outside. So a source file self-registering using this would be causing issues +{ +public: + RunAtCemuBoot(void(*f)()) + { + f(); + } +}; + +// temporary wrapper until Concurrency TS gives us std::future .is_ready() +template +bool future_is_ready(std::future& f) +{ +#ifdef __clang__ + return f.wait_for(std::chrono::nanoseconds(0)) == std::future_status::ready; +#else + return f._Is_ready(); +#endif +} + +// helper function to cast raw pointers to std::atomic +// this is technically not legal but works on most platforms as long as alignment restrictions are met and the implementation of atomic doesnt come with additional members + +template +std::atomic* _rawPtrToAtomic(T* ptr) +{ + return reinterpret_cast*>(ptr); +} + +#if __clang__ +#define ATTR_MS_ABI __attribute__((ms_abi)) +#else +#define ATTR_MS_ABI +#endif + +#if !defined(TRUE) or !defined(FALSE) +#define TRUE (1) +#define FALSE (0) +#endif + +inline uint32 GetTitleIdHigh(uint64 titleId) +{ + return titleId >> 32; +} + +inline uint32 GetTitleIdLow(uint64 titleId) +{ + return titleId & 0xFFFFFFFF; +} + +#ifdef __clang__ +#define memcpy_dwords(__dest, __src, __numDwords) memcpy((__dest), (__src), (__numDwords) * sizeof(uint32)) +#define memcpy_qwords(__dest, __src, __numQwords) memcpy((__dest), (__src), (__numQwords) * sizeof(uint64)) +#else +#define memcpy_dwords(__dest, __src, __numDwords) __movsd((unsigned long*)(__dest), (const unsigned long*)(__src), __numDwords) +#define memcpy_qwords(__dest, __src, __numQwords) __movsq((unsigned long long*)(__dest), (const unsigned long long*)(__src), __numQwords) +#endif + +// PPC context and memory functions +#include "Cafe/HW/MMU/MMU.h" +#include "Cafe/HW/Espresso/PPCState.h" +#include "Cafe/HW/Espresso/PPCCallback.h" diff --git a/src/Common/socket.h b/src/Common/socket.h new file mode 100644 index 00000000..2a33d737 --- /dev/null +++ b/src/Common/socket.h @@ -0,0 +1,16 @@ +#pragma once + +#if BOOST_OS_WINDOWS > 0 + +#include +typedef int socklen_t; + +#else + +#include +#define SOCKET int +#define closesocket close +#define SOCKET_ERROR -1 +#define INVALID_SOCKET -1 + +#endif diff --git a/src/Common/version.h b/src/Common/version.h new file mode 100644 index 00000000..2352878b --- /dev/null +++ b/src/Common/version.h @@ -0,0 +1,8 @@ +#define EMULATOR_NAME "Cemu" +#define EMULATOR_VERSION_LEAD 2 +#define EMULATOR_VERSION_MAJOR 0 +#define EMULATOR_VERSION_MINOR 0 // kept internally for backwards compatibility purposes, but isn't displayed anymore + +#define EMULATOR_VERSION_SUFFIX " (experimental)" + +#define EMULATOR_SERVER_VERSION 4000 // used for auto-updater, increment this with every release diff --git a/src/Common/windows/platform.cpp b/src/Common/windows/platform.cpp new file mode 100644 index 00000000..9e991b24 --- /dev/null +++ b/src/Common/windows/platform.cpp @@ -0,0 +1,34 @@ +#include + +SlimRWLock::SlimRWLock() +{ + static_assert(sizeof(m_lock) == sizeof(SRWLOCK)); + RTL_SRWLOCK* srwLock = (RTL_SRWLOCK*)&m_lock; + *srwLock = SRWLOCK_INIT; + //m_lock = { SRWLOCK_INIT }; +} + +void SlimRWLock::LockRead() +{ + AcquireSRWLockShared((SRWLOCK*)&m_lock); +} + +void SlimRWLock::UnlockRead() +{ + ReleaseSRWLockShared((SRWLOCK*)&m_lock); +} + +void SlimRWLock::LockWrite() +{ + AcquireSRWLockExclusive((SRWLOCK*)&m_lock); +} + +void SlimRWLock::UnlockWrite() +{ + ReleaseSRWLockExclusive((SRWLOCK*)&m_lock); +} + +uint32_t GetExceptionError() +{ + return (uint32_t)GetLastError(); +} \ No newline at end of file diff --git a/src/Common/windows/platform.h b/src/Common/windows/platform.h new file mode 100644 index 00000000..3d034a35 --- /dev/null +++ b/src/Common/windows/platform.h @@ -0,0 +1,28 @@ +#pragma once + +#ifndef NOMINMAX +#define NOMINMAX +#endif +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include + +#define AF_BLUETOOTH AF_BTH +#define BTPROTO_RFCOMM BT_PORT_ANY + +class SlimRWLock +{ +public: + SlimRWLock(); + + void LockRead(); + void UnlockRead(); + void LockWrite(); + void UnlockWrite(); + +private: + /*SRWLOCK*/ void* m_lock; +}; + +uint32_t GetExceptionError(); \ No newline at end of file diff --git a/src/Common/zstring_view.h b/src/Common/zstring_view.h new file mode 100644 index 00000000..2afd605c --- /dev/null +++ b/src/Common/zstring_view.h @@ -0,0 +1,143 @@ +#ifndef ASZ_ZSTRING_VIEW_HPP +#define ASZ_ZSTRING_VIEW_HPP + +// https://github.com/Aszarsha/zstring_view/blob/master/zstring_view.hpp + +#include + +// Copy interface from cppreference's std::basic_string_view page +// http://en.cppreference.com/w/cpp/string/basic_string_view +// and add conversion from zstring_view to basic_string_view and CharT const* + +template< class CharT + , class Traits = std::char_traits< CharT > +> +class basic_zstring_view + : private std::basic_string_view< CharT, Traits > { +private: + using string_view_base = std::basic_string_view< CharT, Traits >; + +public: /// Types /// + using typename string_view_base::traits_type; + using typename string_view_base::value_type; + using typename string_view_base::pointer; + using typename string_view_base::const_pointer; + using typename string_view_base::reference; + using typename string_view_base::const_reference; + using typename string_view_base::iterator; + using typename string_view_base::const_iterator; + using typename string_view_base::reverse_iterator; + using typename string_view_base::const_reverse_iterator; + using typename string_view_base::size_type; + using typename string_view_base::difference_type; + +public: /// Static members /// + using string_view_base::npos; + +public: /// Special member functions /// + constexpr basic_zstring_view() noexcept = default; + + constexpr basic_zstring_view(basic_zstring_view const&) noexcept = default; + constexpr basic_zstring_view& operator=(basic_zstring_view const&) noexcept = default; + + constexpr basic_zstring_view(basic_zstring_view&&) noexcept = default; + constexpr basic_zstring_view& operator=(basic_zstring_view&&) noexcept = default; + + basic_zstring_view(CharT const* s) : string_view_base{ s } { } + basic_zstring_view(CharT const* s, size_type sz) : string_view_base{ s, sz } { } + + // Explicitly no convertion from string_view, since null-termination is not guaranteed + basic_zstring_view(std::basic_string, std::allocator> const& s) : string_view_base{ s } { } + +public: /// Member functions /// + //== Iterators ==// + using string_view_base::begin; + using string_view_base::cbegin; + + using string_view_base::end; + using string_view_base::cend; + + using string_view_base::rbegin; + using string_view_base::crbegin; + + using string_view_base::rend; + using string_view_base::crend; + + //== Element access ==// + using string_view_base::operator[]; + + using string_view_base::at; + + using string_view_base::front; + + using string_view_base::back; + + using string_view_base::data; + + //== Capacity ==// + using string_view_base::size; + using string_view_base::length; + + using string_view_base::max_size; + + using string_view_base::empty; + + //== Modifiers ==// + using string_view_base::remove_prefix; + + //Explicitly NOT + //using string_view_base::remove_suffix; + + using string_view_base::swap; + + //== Operations ==// + using string_view_base::copy; + + //Explicitly NOT + //using::string_view_base::substr; + + using string_view_base::compare; + + // C++20 + //using string_view_base::starts_with; + + // C++20 + //using string_view_base::ends_with; + + using string_view_base::find; + + using string_view_base::rfind; + + using string_view_base::find_first_of; + + using string_view_base::find_last_of; + + using string_view_base::find_first_not_of; + + using string_view_base::find_last_not_of; + +public: /// Convertions /// + constexpr operator const_pointer() const noexcept { + return data(); + } + + // Ideally operator string_view_base() + // but by language definition (C++17) will never be used: + // From [class.conv.fct]: A conversion function is never used to convert a (possibly cv-qualified) object to the (possibly cv-qualified) same object type (or a reference to it), to a (possibly cv-qualified) base class of that type (or a reference to it), or to (possibly cv-qualified) void. + constexpr string_view_base to_string_view() const noexcept { + return { data(), size() }; + } + +public: /// Misc /// + friend inline std::ostream& operator<<(std::ostream& o, basic_zstring_view v) { + o << v.data(); + return o; + } +}; + +using zstring_view = basic_zstring_view< char >; +using wzstring_view = basic_zstring_view< wchar_t >; +using u16zstring_view = basic_zstring_view< char16_t >; +using u32zstring_view = basic_zstring_view< char32_t >; + +#endif \ No newline at end of file diff --git a/src/asm/CMakeLists.txt b/src/asm/CMakeLists.txt new file mode 100644 index 00000000..ab9e701b --- /dev/null +++ b/src/asm/CMakeLists.txt @@ -0,0 +1,30 @@ +project(CemuAsm C) + +IF (WIN32) + +enable_language(C ASM_MASM) + +add_library(CemuAsm +x64util_masm.asm +) +set_source_files_properties(x64util_masm.asm PROPERTIES LANGUAGE ASM_MASM) + +ELSE() + +# NASM +set(CMAKE_ASM_NASM_COMPILE_OBJECT " -g -Fdwarf -f elf64 -o ") +set(CMAKE_ASM_NASM_LINK_EXECUTABLE "ld -fPIC -o ") + +enable_language(C ASM_NASM) + +add_library(CemuAsm +x64util_nasm.asm +) +set_source_files_properties(x64util_nasm.asm PROPERTIES LANGUAGE ASM_NASM) + +set_target_properties(CemuAsm PROPERTIES NASM_OBJ_FORMAT elf64) +set_target_properties(CemuAsm PROPERTIES LINKER_LANGUAGE C) + +ENDIF() + +set_property(TARGET CemuAsm PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") \ No newline at end of file diff --git a/src/asm/x64util.h b/src/asm/x64util.h new file mode 100644 index 00000000..654c8f4a --- /dev/null +++ b/src/asm/x64util.h @@ -0,0 +1,5 @@ +#pragma once + +extern "C" uint64 ATTR_MS_ABI udiv128(uint64 low, uint64 hi, uint64 divisor, uint64 *remainder); +extern "C" void recompiler_fres(); +extern "C" void recompiler_frsqrte(); diff --git a/src/asm/x64util_masm.asm b/src/asm/x64util_masm.asm new file mode 100644 index 00000000..14aa1ec0 --- /dev/null +++ b/src/asm/x64util_masm.asm @@ -0,0 +1,240 @@ +.code + +udiv128 PROC + mov rax, rcx + div r8 + mov [r9], rdx + ret +udiv128 ENDP + +recompiler_fres PROC + ; store all modified registers +push rdx +push rcx +push rax +push r8 +lea r8,[asmFresLookupTable] +movq rdx, xmm15 +mov rcx,rdx +shr rcx,2Fh +mov rax,rdx +and ecx,1Fh +shr rax,25h +and eax,3FFh +imul eax,dword ptr [r8+rcx*8+4] +mov r8d,dword ptr [r8+rcx*8] +mov rcx,rdx +shr rcx,34h +inc eax +shr eax,1 +sub r8d,eax +and ecx,7FFh +jne fres_espresso_label3 +mov rax,7FF0000000000000h +or rdx,rax +movq xmm15, rdx +pop r8 +pop rax +pop rcx +pop rdx +ret +fres_espresso_label3: +cmp ecx,7FFh +jne fres_espresso_label4 +mov rax,0FFFFFFFFFFFFFh +test rax,rdx +jne fres_espresso_label1 +test rdx,rdx +jns fres_espresso_label2 +mov rax,8000000000000000h +movq xmm15, rax +pop r8 +pop rax +pop rcx +pop rdx +ret +fres_espresso_label2: +xorps xmm15,xmm15 +pop r8 +pop rax +pop rcx +pop rdx +ret +fres_espresso_label4: +mov eax,7FDh +sub eax,ecx +mov ecx,eax +mov rax,8000000000000000h +and rdx,rax +shl rcx,34h +mov eax,r8d +or rcx,rdx +shl rax,1Dh +add rcx,rax +movq xmm15, rcx +fres_espresso_label1: +pop r8 +pop rax +pop rcx +pop rdx +ret + +recompiler_fres ENDP + +asmFresLookupTable: +DD 07ff800h, 03e1h +DD 0783800h, 03a7h +DD 070ea00h, 0371h +DD 06a0800h, 0340h +DD 0638800h, 0313h +DD 05d6200h, 02eah +DD 0579000h, 02c4h +DD 0520800h, 02a0h +DD 04cc800h, 027fh +DD 047ca00h, 0261h +DD 0430800h, 0245h +DD 03e8000h, 022ah +DD 03a2c00h, 0212h +DD 0360800h, 01fbh +DD 0321400h, 01e5h +DD 02e4a00h, 01d1h +DD 02aa800h, 01beh +DD 0272c00h, 01ach +DD 023d600h, 019bh +DD 0209e00h, 018bh +DD 01d8800h, 017ch +DD 01a9000h, 016eh +DD 017ae00h, 015bh +DD 014f800h, 015bh +DD 0124400h, 0143h +DD 0fbe00h, 0143h +DD 0d3800h, 012dh +DD 0ade00h, 012dh +DD 088400h, 011ah +DD 065000h, 011ah +DD 041c00h, 0108h +DD 020c00h, 0106h + +recompiler_frsqrte PROC + ; store all modified registers +push rdx +push rcx +push rax +push r8 +push r9 +movq r8, xmm15 +mov rax,7FFFFFFFFFFFFFFFh +test rax,r8 +jne frsqrte_espresso_label1 +mov rax,0FFF0000000000000h +and r8,rax +mov rax,7FF0000000000000h +or r8,rax +movq xmm15, r8 +pop r9 +pop r8 +pop rax +pop rcx +pop rdx +ret +frsqrte_espresso_label1: +mov r9,r8 +shr r9,34h +and r9d,7FFh +cmp r9d,7FFh +jne frsqrte_espresso_label2 +mov rax,0FFFFFFFFFFFFFh +test rax,r8 +jne frsqrte_espresso_label3 +test r8,r8 +js frsqrte_espresso_label4 +xorps xmm15,xmm15 +pop r9 +pop r8 +pop rax +pop rcx +pop rdx +ret +frsqrte_espresso_label2: +test r8,r8 +jns frsqrte_espresso_label5 +frsqrte_espresso_label4: +mov rax,7FF8000000000000h +movq xmm15, rax +pop r9 +pop r8 +pop rax +pop rcx +pop rdx +ret +frsqrte_espresso_label5: +lea rdx,[asmFrsqrteLookupTable] +mov rax,r8 +shr rax,30h +mov rcx,r8 +shr rcx,25h +and eax,1Fh +and ecx,7FFh +imul ecx,dword ptr [rdx+rax*8+4] +mov eax,dword ptr [rdx+rax*8] +sub eax,ecx +lea ecx,[r9-3FDh] +shr ecx,1 +movsxd rdx,eax +mov eax,3FFh +sub eax,ecx +shl rdx,1Ah +mov ecx,eax +mov rax,8000000000000000h +and r8,rax +shl rcx,34h +or rcx,r8 +add rdx,rcx +movq xmm15, rdx +frsqrte_espresso_label3: +pop r9 +pop r8 +pop rax +pop rcx +pop rdx +ret + +recompiler_frsqrte ENDP + +asmFrsqrteLookupTable: +DD 01a7e800h, 0568h +DD 017cb800h, 04f3h +DD 01552800h, 048dh +DD 0130c000h, 0435h +DD 010f2000h, 03e7h +DD 0eff000h, 03a2h +DD 0d2e000h, 0365h +DD 0b7c000h, 032eh +DD 09e5000h, 02fch +DD 0867000h, 02d0h +DD 06ff000h, 02a8h +DD 05ab800h, 0283h +DD 046a000h, 0261h +DD 0339800h, 0243h +DD 0218800h, 0226h +DD 0105800h, 020bh +DD 03ffa000h, 07a4h +DD 03c29000h, 0700h +DD 038aa000h, 0670h +DD 03572000h, 05f2h +DD 03279000h, 0584h +DD 02fb7000h, 0524h +DD 02d26000h, 04cch +DD 02ac0000h, 047eh +DD 02881000h, 043ah +DD 02665000h, 03fah +DD 02468000h, 03c2h +DD 02287000h, 038eh +DD 020c1000h, 035eh +DD 01f12000h, 0332h +DD 01d79000h, 030ah +DD 01bf4000h, 02e6h + + + +END \ No newline at end of file diff --git a/src/asm/x64util_nasm.asm b/src/asm/x64util_nasm.asm new file mode 100644 index 00000000..89878f6e --- /dev/null +++ b/src/asm/x64util_nasm.asm @@ -0,0 +1,237 @@ +DEFAULT REL + +SECTION .text + +global udiv128 +global recompiler_fres +global recompiler_frsqrte + +udiv128: + mov rax, rcx + div r8 + mov [r9], rdx + ret + +recompiler_fres: + ; store all modified registers +push rdx +push rcx +push rax +push r8 +lea r8,[asmFresLookupTable] +movq rdx, xmm15 +mov rcx,rdx +shr rcx,2Fh +mov rax,rdx +and ecx,1Fh +shr rax,25h +and eax,3FFh +imul eax,dword [r8+rcx*8+4] +mov r8d,dword [r8+rcx*8] +mov rcx,rdx +shr rcx,34h +inc eax +shr eax,1 +sub r8d,eax +and ecx,7FFh +jne fres_espresso_label3 +mov rax,7FF0000000000000h +or rdx,rax +movq xmm15, rdx +pop r8 +pop rax +pop rcx +pop rdx +ret +fres_espresso_label3: +cmp ecx,7FFh +jne fres_espresso_label4 +mov rax,0FFFFFFFFFFFFFh +test rax,rdx +jne fres_espresso_label1 +test rdx,rdx +jns fres_espresso_label2 +mov rax,8000000000000000h +movq xmm15, rax +pop r8 +pop rax +pop rcx +pop rdx +ret +fres_espresso_label2: +xorps xmm15,xmm15 +pop r8 +pop rax +pop rcx +pop rdx +ret +fres_espresso_label4: +mov eax,7FDh +sub eax,ecx +mov ecx,eax +mov rax,8000000000000000h +and rdx,rax +shl rcx,34h +mov eax,r8d +or rcx,rdx +shl rax,1Dh +add rcx,rax +movq xmm15, rcx +fres_espresso_label1: +pop r8 +pop rax +pop rcx +pop rdx +ret + +asmFresLookupTable: +DD 07ff800h, 03e1h +DD 0783800h, 03a7h +DD 070ea00h, 0371h +DD 06a0800h, 0340h +DD 0638800h, 0313h +DD 05d6200h, 02eah +DD 0579000h, 02c4h +DD 0520800h, 02a0h +DD 04cc800h, 027fh +DD 047ca00h, 0261h +DD 0430800h, 0245h +DD 03e8000h, 022ah +DD 03a2c00h, 0212h +DD 0360800h, 01fbh +DD 0321400h, 01e5h +DD 02e4a00h, 01d1h +DD 02aa800h, 01beh +DD 0272c00h, 01ach +DD 023d600h, 019bh +DD 0209e00h, 018bh +DD 01d8800h, 017ch +DD 01a9000h, 016eh +DD 017ae00h, 015bh +DD 014f800h, 015bh +DD 0124400h, 0143h +DD 0fbe00h, 0143h +DD 0d3800h, 012dh +DD 0ade00h, 012dh +DD 088400h, 011ah +DD 065000h, 011ah +DD 041c00h, 0108h +DD 020c00h, 0106h + +recompiler_frsqrte: + ; store all modified registers +push rdx +push rcx +push rax +push r8 +push r9 +movq r8, xmm15 +mov rax,7FFFFFFFFFFFFFFFh +test rax,r8 +jne frsqrte_espresso_label1 +mov rax,0FFF0000000000000h +and r8,rax +mov rax,7FF0000000000000h +or r8,rax +movq xmm15, r8 +pop r9 +pop r8 +pop rax +pop rcx +pop rdx +ret +frsqrte_espresso_label1: +mov r9,r8 +shr r9,34h +and r9d,7FFh +cmp r9d,7FFh +jne frsqrte_espresso_label2 +mov rax,0FFFFFFFFFFFFFh +test rax,r8 +jne frsqrte_espresso_label3 +test r8,r8 +js frsqrte_espresso_label4 +xorps xmm15,xmm15 +pop r9 +pop r8 +pop rax +pop rcx +pop rdx +ret +frsqrte_espresso_label2: +test r8,r8 +jns frsqrte_espresso_label5 +frsqrte_espresso_label4: +mov rax,7FF8000000000000h +movq xmm15, rax +pop r9 +pop r8 +pop rax +pop rcx +pop rdx +ret +frsqrte_espresso_label5: +lea rdx,[asmFrsqrteLookupTable] +mov rax,r8 +shr rax,30h +mov rcx,r8 +shr rcx,25h +and eax,1Fh +and ecx,7FFh +imul ecx,dword [rdx+rax*8+4] +mov eax,dword [rdx+rax*8] +sub eax,ecx +lea ecx,[r9-3FDh] +shr ecx,1 +movsxd rdx,eax +mov eax,3FFh +sub eax,ecx +shl rdx,1Ah +mov ecx,eax +mov rax,8000000000000000h +and r8,rax +shl rcx,34h +or rcx,r8 +add rdx,rcx +movq xmm15, rdx +frsqrte_espresso_label3: +pop r9 +pop r8 +pop rax +pop rcx +pop rdx +ret + +asmFrsqrteLookupTable: +DD 01a7e800h, 0568h +DD 017cb800h, 04f3h +DD 01552800h, 048dh +DD 0130c000h, 0435h +DD 010f2000h, 03e7h +DD 0eff000h, 03a2h +DD 0d2e000h, 0365h +DD 0b7c000h, 032eh +DD 09e5000h, 02fch +DD 0867000h, 02d0h +DD 06ff000h, 02a8h +DD 05ab800h, 0283h +DD 046a000h, 0261h +DD 0339800h, 0243h +DD 0218800h, 0226h +DD 0105800h, 020bh +DD 03ffa000h, 07a4h +DD 03c29000h, 0700h +DD 038aa000h, 0670h +DD 03572000h, 05f2h +DD 03279000h, 0584h +DD 02fb7000h, 0524h +DD 02d26000h, 04cch +DD 02ac0000h, 047eh +DD 02881000h, 043ah +DD 02665000h, 03fah +DD 02468000h, 03c2h +DD 02287000h, 038eh +DD 020c1000h, 035eh +DD 01f12000h, 0332h +DD 01d79000h, 030ah +DD 01bf4000h, 02e6h diff --git a/src/audio/CMakeLists.txt b/src/audio/CMakeLists.txt new file mode 100644 index 00000000..6a11033d --- /dev/null +++ b/src/audio/CMakeLists.txt @@ -0,0 +1,42 @@ +project(CemuAudio) + +add_library(CemuAudio +IAudioAPI.cpp +IAudioAPI.h +) + +set_property(TARGET CemuAudio PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + +# move these to UI folder +target_sources(CemuAudio PRIVATE + audioDebuggerWindow.cpp + audioDebuggerWindow.h +) + +if(WIN32) + target_sources(CemuAudio PRIVATE + DirectSoundAPI.cpp + DirectSoundAPI.h + XAudio2API.cpp + XAudio2API.h + XAudio27API.cpp + XAudio27API.h + ) +endif() + +if(ENABLE_CUBEB) + target_sources(CemuAudio PRIVATE + CubebAPI.cpp + CubebAPI.h + ) + #add_definitions(HAS_CUBEB) +endif() + +target_precompile_headers(CemuAudio PRIVATE ../Common/precompiled.h) + +#target_link_libraries(CemuAudio CemuCommon CemuGui CemuPlatform) +target_include_directories(CemuAudio PRIVATE ../) + +if(ENABLE_CUBEB) + target_link_libraries(CemuAudio cubeb) +endif() diff --git a/src/audio/CubebAPI.cpp b/src/audio/CubebAPI.cpp new file mode 100644 index 00000000..c40e0f55 --- /dev/null +++ b/src/audio/CubebAPI.cpp @@ -0,0 +1,225 @@ +#include "CubebAPI.h" + +#if BOOST_OS_WINDOWS +#include +#include +#include +#pragma comment(lib, "Avrt.lib") +#pragma comment(lib, "ksuser.lib") +#endif + + +void state_cb(cubeb_stream* stream, void* user, cubeb_state state) +{ + if (!stream) + return; + + /*switch (state) + { + case CUBEB_STATE_STARTED: + fprintf(stderr, "stream started\n"); + break; + case CUBEB_STATE_STOPPED: + fprintf(stderr, "stream stopped\n"); + break; + case CUBEB_STATE_DRAINED: + fprintf(stderr, "stream drained\n"); + break; + default: + fprintf(stderr, "unknown stream state %d\n", state); + }*/ +} + +long CubebAPI::data_cb(cubeb_stream* stream, void* user, const void* inputbuffer, void* outputbuffer, long nframes) +{ + auto* thisptr = (CubebAPI*)user; + //const auto size = (size_t)thisptr->m_bytesPerBlock; // (size_t)nframes* thisptr->m_channels; + + // m_bytesPerBlock = samples_per_block * channels * (bits_per_sample / 8); + const auto size = (size_t)nframes * thisptr->m_channels * (thisptr->m_bitsPerSample/8); + + std::unique_lock lock(thisptr->m_mutex); + if (thisptr->m_buffer.empty()) + { + // we got no data, just write silence + memset(outputbuffer, 0x00, size); + } + else + { + const auto copied = std::min(thisptr->m_buffer.size(), size); + memcpy(outputbuffer, thisptr->m_buffer.data(), copied); + thisptr->m_buffer.erase(thisptr->m_buffer.begin(), std::next(thisptr->m_buffer.begin(), copied)); + lock.unlock(); + // fill rest with silence + if (copied != size) + memset((uint8*)outputbuffer + copied, 0x00, size - copied); + } + + return nframes; +} + +CubebAPI::CubebAPI(cubeb_devid devid, uint32 samplerate, uint32 channels, uint32 samples_per_block, + uint32 bits_per_sample) + : IAudioAPI(samplerate, channels, samples_per_block, bits_per_sample) +{ + cubeb_stream_params output_params; + + output_params.format = CUBEB_SAMPLE_S16LE; + output_params.rate = samplerate; + output_params.channels = channels; + output_params.prefs = CUBEB_STREAM_PREF_NONE; + + switch (channels) + { + case 8: + output_params.layout = CUBEB_LAYOUT_3F4_LFE; + break; + case 6: + output_params.layout = CUBEB_LAYOUT_QUAD_LFE | CHANNEL_FRONT_CENTER; + break; + case 4: + output_params.layout = CUBEB_LAYOUT_QUAD; + break; + case 2: + output_params.layout = CUBEB_LAYOUT_STEREO; + break; + default: + output_params.layout = CUBEB_LAYOUT_MONO; + break; + } + + uint32 latency = 1; + cubeb_get_min_latency(s_context, &output_params, &latency); + + m_buffer.reserve((size_t)m_bytesPerBlock * kBlockCount); + + if (cubeb_stream_init(s_context, &m_stream, "Cemu Cubeb output", + nullptr, nullptr, + devid, &output_params, + latency, data_cb, state_cb, this) != CUBEB_OK) + { + throw std::runtime_error("can't initialize cubeb device"); + } +} + +CubebAPI::~CubebAPI() +{ + if (m_stream) + { + Stop(); + cubeb_stream_destroy(m_stream); + } +} + +bool CubebAPI::NeedAdditionalBlocks() const +{ + std::shared_lock lock(m_mutex); + return m_buffer.size() < s_audioDelay * m_bytesPerBlock; +} + +bool CubebAPI::FeedBlock(sint16* data) +{ + std::unique_lock lock(m_mutex); + if (m_buffer.capacity() <= m_buffer.size() + m_bytesPerBlock) + { + forceLogDebug_printf("dropped direct sound block since too many buffers are queued"); + return false; + } + + m_buffer.insert(m_buffer.end(), (uint8*)data, (uint8*)data + m_bytesPerBlock); + return true; +} + +bool CubebAPI::Play() +{ + if (m_is_playing) + return true; + + if (cubeb_stream_start(m_stream) == CUBEB_OK) + { + m_is_playing = true; + return true; + } + + return false; +} + +bool CubebAPI::Stop() +{ + if (!m_is_playing) + return true; + + if (cubeb_stream_stop(m_stream) == CUBEB_OK) + { + m_is_playing = false; + return true; + } + + return false; +} + +void CubebAPI::SetVolume(sint32 volume) +{ + IAudioAPI::SetVolume(volume); + cubeb_stream_set_volume(m_stream, (float)volume / 100.0f); +} + + +bool CubebAPI::InitializeStatic() +{ +#if BOOST_OS_WINDOWS + s_com_initialized = (SUCCEEDED(CoInitializeEx(nullptr, COINIT_MULTITHREADED))); +#endif + + if (cubeb_init(&s_context, "Cemu Cubeb", nullptr)) + { + cemuLog_force("can't create cubeb audio api"); + +#if BOOST_OS_WINDOWS + if (s_com_initialized) + { + CoUninitialize(); + s_com_initialized = false; + } +#endif + + return false; + } + + return true; +} + +void CubebAPI::Destroy() +{ + if (s_context) + cubeb_destroy(s_context); +#if BOOST_OS_WINDOWS + if (s_com_initialized) + CoUninitialize(); +#endif +} + +std::vector CubebAPI::GetDevices() +{ + cubeb_device_collection devices; + if (cubeb_enumerate_devices(s_context, CUBEB_DEVICE_TYPE_OUTPUT, &devices) != CUBEB_OK) + return {}; + + std::vector result; + result.reserve(devices.count); + for (size_t i = 0; i < devices.count; ++i) + { + //const auto& device = devices.device[i]; + if (devices.device[i].state == CUBEB_DEVICE_STATE_ENABLED) + { + auto device = std::make_shared(devices.device[i].devid, devices.device[i].device_id, + boost::nowide::widen( + devices.device[i].friendly_name)); + result.emplace_back(device); + } + } + + cubeb_device_collection_destroy(s_context, &devices); + + return result; +} diff --git a/src/audio/CubebAPI.h b/src/audio/CubebAPI.h new file mode 100644 index 00000000..a828ce0d --- /dev/null +++ b/src/audio/CubebAPI.h @@ -0,0 +1,53 @@ +#pragma once + +#include "IAudioAPI.h" + +#include + +#include + +class CubebAPI : public IAudioAPI +{ +public: + class CubebDeviceDescription : public DeviceDescription + { + public: + CubebDeviceDescription(cubeb_devid devid, std::string device_id, const std::wstring& name) + : DeviceDescription(name), m_devid(devid), m_device_id(std::move(device_id)) { } + + std::wstring GetIdentifier() const override { return boost::nowide::widen(m_device_id); } + cubeb_devid GetDeviceId() const { return m_devid; } + + private: + cubeb_devid m_devid; + std::string m_device_id; + }; + + using CubebDeviceDescriptionPtr = std::shared_ptr; + + CubebAPI(cubeb_devid devid, uint32 samplerate, uint32 channels, uint32 samples_per_block, uint32 bits_per_sample); + ~CubebAPI(); + + AudioAPI GetType() const override { return Cubeb; } + bool NeedAdditionalBlocks() const override; + bool FeedBlock(sint16* data) override; + bool Play() override; + bool Stop() override; + void SetVolume(sint32 volume) override; + + static std::vector GetDevices(); + + static bool InitializeStatic(); + static void Destroy(); + +private: + inline static bool s_com_initialized = false; + inline static cubeb* s_context = nullptr; + + cubeb_stream* m_stream = nullptr; + bool m_is_playing = false; + + mutable std::shared_mutex m_mutex; + std::vector m_buffer; + static long data_cb(cubeb_stream* stream, void* user, const void* inputbuffer, void* outputbuffer, long nframes); +}; diff --git a/src/audio/DirectSoundAPI.cpp b/src/audio/DirectSoundAPI.cpp new file mode 100644 index 00000000..8f85fd1a --- /dev/null +++ b/src/audio/DirectSoundAPI.cpp @@ -0,0 +1,246 @@ +#include "DirectSoundAPI.h" + +#include "gui/wxgui.h" + +#include "util/helpers/helpers.h" +#include "gui/guiWrapper.h" + +#pragma comment(lib, "Dsound.lib") + +std::wstring DirectSoundAPI::DirectSoundDeviceDescription::GetIdentifier() const +{ + return m_guid ? WStringFromGUID(*m_guid) : L"default"; +} + +DirectSoundAPI::DirectSoundAPI(GUID* guid, sint32 samplerate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample) + : IAudioAPI(samplerate, channels, samples_per_block, bits_per_sample) +{ + LPDIRECTSOUND8 direct_sound; + if (DirectSoundCreate8(guid, &direct_sound, nullptr) != DS_OK) + throw std::runtime_error("can't create directsound device"); + + m_direct_sound = decltype(m_direct_sound)(direct_sound); + + if (FAILED(m_direct_sound->SetCooperativeLevel(gui_getWindowInfo().window_main.hwnd, DSSCL_PRIORITY))) + throw std::runtime_error("can't set directsound priority"); + + DSBUFFERDESC bd{}; + bd.dwSize = sizeof(DSBUFFERDESC); + bd.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_CTRLVOLUME | DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLPOSITIONNOTIFY; + bd.dwBufferBytes = kBufferCount * m_bytesPerBlock; // kBlockCount * (samples_per_block * channels * (bits_per_sample / 8)); + bd.lpwfxFormat = (LPWAVEFORMATEX)&m_wfx; + + LPDIRECTSOUNDBUFFER sound_buffer; + if (FAILED(m_direct_sound->CreateSoundBuffer(&bd, &sound_buffer, nullptr))) + throw std::runtime_error("can't create directsound soundbuffer"); + + DSBCAPS caps{}; + caps.dwSize = sizeof(DSBCAPS); + if (FAILED(sound_buffer->GetCaps(&caps))) + throw std::runtime_error("can't get directsound soundbuffer size"); + + m_sound_buffer_size = caps.dwBufferBytes; + + LPDIRECTSOUNDBUFFER8 sound_buffer8; + LPDIRECTSOUNDNOTIFY8 notify8; + sound_buffer->QueryInterface(IID_IDirectSoundBuffer8, (void**)&sound_buffer8); + + if (!sound_buffer8) + { + sound_buffer->Release(); + throw std::runtime_error("can't get directsound buffer interface"); + } + + m_sound_buffer = decltype(m_sound_buffer)(sound_buffer8); + + sound_buffer->QueryInterface(IID_IDirectSoundNotify8, (void**)¬ify8); + if (!notify8) + { + sound_buffer->Release(); + throw std::runtime_error("can't get directsound notify interface"); + } + m_notify = decltype(m_notify)(notify8); + + sound_buffer->Release(); + + { // initialize sound buffer + void *ptr1, *ptr2; + DWORD bytes1, bytes2; + m_sound_buffer->Lock(0, m_sound_buffer_size, &ptr1, &bytes1, &ptr2, &bytes2, 0); + memset(ptr1, 0x00, bytes1); + if (ptr2 && bytes2 > 0) + memset(ptr2, 0x00, bytes2); + m_sound_buffer->Unlock(ptr1, bytes1, ptr2, bytes2); + } + + m_sound_buffer->SetCurrentPosition(0); + + DSBPOSITIONNOTIFY notify[kBufferCount]{}; + for (size_t i = 0; i < kBufferCount; ++i) + { + m_notify_event[i] = CreateEvent(nullptr, FALSE, FALSE, nullptr); + + notify[i].hEventNotify = m_notify_event[i]; + //notify[i].dwOffset = ((i*2) + 1) * (m_bytes_per_block / 2); + notify[i].dwOffset = (i * m_bytesPerBlock); + } + + if (FAILED(m_notify->SetNotificationPositions(kBufferCount, notify))) + throw std::runtime_error("can't set directsound notify positions"); + + m_running = true; + m_thread = std::thread(&DirectSoundAPI::AudioThread, this); +#if BOOST_OS_WINDOWS + SetThreadPriority(m_thread.native_handle(), THREAD_PRIORITY_TIME_CRITICAL); +#endif +} + +void DirectSoundAPI::AudioThread() +{ + while (m_running) + { + HRESULT hr = WaitForMultipleObjects(m_notify_event.size(), m_notify_event.data(), FALSE, 10); + if (WAIT_OBJECT_0 <= hr && hr <= WAIT_OBJECT_0 + kBufferCount) + { + // write to the following buffer + const sint32 position = (hr - WAIT_OBJECT_0 + 1) % kBufferCount; + + void *ptr1, *ptr2; + DWORD bytes1, bytes2; + hr = m_sound_buffer->Lock(position * m_bytesPerBlock, m_bytesPerBlock, &ptr1, &bytes1, &ptr2, &bytes2, 0); + if (hr == DSERR_BUFFERLOST) + { + m_sound_buffer->Restore(); + hr = m_sound_buffer->Lock(position * m_bytesPerBlock, m_bytesPerBlock, &ptr1, &bytes1, &ptr2, &bytes2, 0); + } + + if (FAILED(hr)) + { + forceLog_printf("DirectSound: Dropped audio block due to locking failure"); + continue; + } + + { + std::unique_lock lock(m_mutex); + if (m_buffer.empty()) + { + //forceLogDebug_printf("DirectSound: writing silence"); + + // we got no data, just write silence + memset(ptr1, 0x00, bytes1); + if (ptr2) + memset(ptr2, 0x00, bytes2); + } + else + { + const auto& buffer = m_buffer.front(); + memcpy(ptr1, buffer.get(), bytes1); + if (ptr2) + memcpy(ptr2, buffer.get() + bytes1, bytes2); + + m_buffer.pop(); + } + } + + m_sound_buffer->Unlock(ptr1, bytes1, ptr2, bytes2); + } + } +} + +DirectSoundAPI::~DirectSoundAPI() +{ + m_running = false; + DirectSoundAPI::Stop(); + + if(m_thread.joinable()) + m_thread.join(); + + m_notify.reset(); + m_sound_buffer.reset(); + m_direct_sound.reset(); + + for(auto entry : m_notify_event) + { + if (entry) + CloseHandle(entry); + } +} + +bool DirectSoundAPI::Play() +{ + if (m_playing) + return true; + + m_playing = SUCCEEDED(m_sound_buffer->Play(0, 0, DSBPLAY_LOOPING)); + return m_playing; +} + +bool DirectSoundAPI::Stop() +{ + if (!m_playing) + return true; + + m_playing = FAILED(m_sound_buffer->Stop()); + return m_playing; +} + +bool DirectSoundAPI::FeedBlock(sint16* data) +{ + std::unique_lock lock(m_mutex); + if (m_buffer.size() > kBlockCount) + { + forceLogDebug_printf("dropped direct sound block since too many buffers are queued"); + return false; + } + + auto tmp = std::make_unique(m_bytesPerBlock); + memcpy(tmp.get(), data, m_bytesPerBlock); + m_buffer.emplace(std::move(tmp)); + return true; +} + +void DirectSoundAPI::SetVolume(sint32 volume) +{ + IAudioAPI::SetVolume(volume); + + const LONG value = pow((float)volume / 100.0f, 0.20f) * (DSBVOLUME_MAX - DSBVOLUME_MIN) + DSBVOLUME_MIN; + m_sound_buffer->SetVolume(value); +} + +bool DirectSoundAPI::NeedAdditionalBlocks() const +{ + std::shared_lock lock(m_mutex); + return m_buffer.size() < s_audioDelay; +} + +std::vector DirectSoundAPI::GetDevices() +{ + std::vector result; + + DirectSoundEnumerateW( + [](LPGUID lpGuid, LPCWSTR lpcstrDescription, LPCWSTR lpcstrModule, LPVOID lpContext) -> BOOL + { + auto results = (std::vector*)lpContext; + auto obj = std::make_shared(lpcstrDescription, lpGuid); + results->emplace_back(obj); + return TRUE; + }, &result); + + return result; +} + +std::vector DirectSoundAPI::GetInputDevices() +{ + std::vector result; + + DirectSoundCaptureEnumerateW( + [](LPGUID lpGuid, LPCWSTR lpcstrDescription, LPCWSTR lpcstrModule, LPVOID lpContext) -> BOOL + { + auto results = (std::vector*)lpContext; + auto obj = std::make_shared(lpcstrDescription, lpGuid); + results->emplace_back(obj); + return TRUE; + }, &result); + + return result; +} diff --git a/src/audio/DirectSoundAPI.h b/src/audio/DirectSoundAPI.h new file mode 100644 index 00000000..c5ad0d6f --- /dev/null +++ b/src/audio/DirectSoundAPI.h @@ -0,0 +1,66 @@ +#pragma once + +#define DIRECTSOUND_VERSION 0x0800 +#include +//#include +#include + +#include "IAudioAPI.h" + +class DirectSoundAPI : public IAudioAPI +{ +public: + class DirectSoundDeviceDescription : public DeviceDescription + { + public: + DirectSoundDeviceDescription(const std::wstring& name, GUID* guid) + : DeviceDescription(name), m_guid(guid) { } + + std::wstring GetIdentifier() const override; + GUID* GetGUID() const { return m_guid; } + + private: + GUID* m_guid; + }; + + using DirectSoundDeviceDescriptionPtr = std::shared_ptr; + + // output + DirectSoundAPI(GUID* guid, sint32 samplerate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample); + ~DirectSoundAPI(); + + AudioAPI GetType() const override { return DirectSound; } + + bool Play() override; + bool Stop() override; + bool FeedBlock(sint16* data) override; + void SetVolume(sint32 volume) override; + bool NeedAdditionalBlocks() const override; + + static std::vector GetDevices(); + static std::vector GetInputDevices(); + +private: + struct DirectSoundDeleter + { + void operator()(IUnknown* ptr) const { if (ptr) ptr->Release(); } + }; + + std::unique_ptr m_direct_sound; + //std::unique_ptr m_direct_sound_capture; + std::unique_ptr m_sound_buffer; + std::unique_ptr m_notify; + + DWORD m_sound_buffer_size = 0; + uint32_t m_offset = 0; + bool m_data_written = false; + + static const uint32 kBufferCount = 4; + std::array m_notify_event{}; + mutable std::shared_mutex m_mutex; + + std::queue> m_buffer; + std::thread m_thread; + std::atomic_bool m_running = false; + void AudioThread(); +}; diff --git a/src/audio/IAudioAPI.cpp b/src/audio/IAudioAPI.cpp new file mode 100644 index 00000000..1bbeac6d --- /dev/null +++ b/src/audio/IAudioAPI.cpp @@ -0,0 +1,160 @@ +#include "IAudioAPI.h" + +#if BOOST_OS_WINDOWS > 0 +#include "XAudio2API.h" +#include "XAudio27API.h" +#include "DirectSoundAPI.h" +#endif +#include "config/CemuConfig.h" +#include "CubebAPI.h" + +std::shared_mutex g_audioMutex; +AudioAPIPtr g_tvAudio; +AudioAPIPtr g_padAudio; +std::atomic_int32_t g_padVolume = 0; + +uint32 IAudioAPI::s_audioDelay = 2; +std::array IAudioAPI::s_availableApis{}; + +IAudioAPI::IAudioAPI(uint32 samplerate, uint32 channels, uint32 samples_per_block, uint32 bits_per_sample) + : m_samplerate(samplerate), m_channels(channels), m_samplesPerBlock(samples_per_block), m_bitsPerSample(bits_per_sample) +{ + m_bytesPerBlock = samples_per_block * channels * (bits_per_sample / 8); + InitWFX(m_samplerate, m_channels, m_bitsPerSample); +} + +void IAudioAPI::PrintLogging() +{ + forceLog_printf("------- Init Audio backend -------"); + forceLog_printf("DirectSound: %s", s_availableApis[DirectSound] ? "available" : "not supported"); + forceLog_printf("XAudio 2.8: %s", s_availableApis[XAudio2] ? "available" : "not supported"); + if (!s_availableApis[XAudio2]) + { + forceLog_printf("XAudio 2.7: %s", s_availableApis[XAudio27] ? "available" : "not supported") + } + forceLog_printf("Cubeb: %s", s_availableApis[Cubeb] ? "available" : "not supported"); +} + +void IAudioAPI::InitWFX(sint32 samplerate, sint32 channels, sint32 bits_per_sample) +{ +#if BOOST_OS_WINDOWS > 0 + // move this to Windows-specific audio API implementations and use a cross-platform format here + m_wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + m_wfx.Format.nChannels = channels; + m_wfx.Format.nSamplesPerSec = samplerate; + m_wfx.Format.wBitsPerSample = bits_per_sample; + m_wfx.Format.nBlockAlign = (m_wfx.Format.nChannels * m_wfx.Format.wBitsPerSample) / 8; // must equal (nChannels � wBitsPerSample) / 8 + m_wfx.Format.nAvgBytesPerSec = m_wfx.Format.nSamplesPerSec * m_wfx.Format.nBlockAlign; // must equal nSamplesPerSec � nBlockAlign. + m_wfx.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); + + m_wfx.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + m_wfx.Samples.wValidBitsPerSample = bits_per_sample; + switch (channels) + { + case 8: + m_wfx.dwChannelMask |= (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_FRONT_LEFT_OF_CENTER | SPEAKER_FRONT_RIGHT_OF_CENTER); + break; + case 6: + m_wfx.dwChannelMask |= (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT); + break; + case 4: + m_wfx.dwChannelMask |= (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT); + break; + case 2: + m_wfx.dwChannelMask |= (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT); + break; + default: + m_wfx.dwChannelMask = 0; + break; + } +#endif +} + +void IAudioAPI::InitializeStatic() +{ + s_audioDelay = GetConfig().audio_delay; + +#if BOOST_OS_WINDOWS + s_availableApis[DirectSound] = true; + s_availableApis[XAudio2] = XAudio2API::InitializeStatic(); + if(!s_availableApis[XAudio2]) // don't try to initialize the older lib if the newer version is available + s_availableApis[XAudio27] = XAudio27API::InitializeStatic(); +#endif + s_availableApis[Cubeb] = CubebAPI::InitializeStatic(); +} + +bool IAudioAPI::IsAudioAPIAvailable(AudioAPI api) +{ + if ((size_t)api < s_availableApis.size()) + return s_availableApis[api]; + + cemu_assert_debug(false); + return false; +} + + + +AudioAPIPtr IAudioAPI::CreateDevice(AudioAPI api, const DeviceDescriptionPtr& device, sint32 samplerate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample) +{ + if (!IsAudioAPIAvailable(api)) + return {}; + + switch(api) + { +#if BOOST_OS_WINDOWS + case DirectSound: + { + const auto tmp = std::dynamic_pointer_cast(device); + return std::make_unique(tmp->GetGUID(), samplerate, channels, samples_per_block, bits_per_sample); + } + case XAudio27: + { + const auto tmp = std::dynamic_pointer_cast(device); + return std::make_unique(tmp->GetDeviceId(), samplerate, channels, samples_per_block, bits_per_sample); + } + case XAudio2: + { + const auto tmp = std::dynamic_pointer_cast(device); + return std::make_unique(tmp->GetDeviceId(), samplerate, channels, samples_per_block, bits_per_sample); + } +#endif + case Cubeb: + { + const auto tmp = std::dynamic_pointer_cast(device); + return std::make_unique(tmp->GetDeviceId(), samplerate, channels, samples_per_block, bits_per_sample); + } + default: + throw std::runtime_error(fmt::format("invalid audio api: {}", api)); + } +} + +std::vector IAudioAPI::GetDevices(AudioAPI api) +{ + if (!IsAudioAPIAvailable(api)) + return {}; + + switch(api) + { +#if BOOST_OS_WINDOWS + case DirectSound: + { + return DirectSoundAPI::GetDevices(); + } + case XAudio27: + { + return XAudio27API::GetDevices(); + } + case XAudio2: + { + return XAudio2API::GetDevices(); + } +#endif + case Cubeb: + { + return CubebAPI::GetDevices(); + } + default: + throw std::runtime_error(fmt::format("invalid audio api: {}", api)); + } +} + diff --git a/src/audio/IAudioAPI.h b/src/audio/IAudioAPI.h new file mode 100644 index 00000000..25d416ba --- /dev/null +++ b/src/audio/IAudioAPI.h @@ -0,0 +1,90 @@ +#pragma once + +#if BOOST_OS_WINDOWS > 0 +#include +#endif + +class IAudioAPI +{ + friend class GeneralSettings2; + +public: + class DeviceDescription + { + public: + explicit DeviceDescription(std::wstring name) + : m_name(std::move(name)) { } + + virtual ~DeviceDescription() = default; + virtual std::wstring GetIdentifier() const = 0; + const std::wstring& GetName() const { return m_name; } + + bool operator==(const DeviceDescription& o) const + { + return GetIdentifier() == o.GetIdentifier(); + } + + private: + std::wstring m_name; + }; + + using DeviceDescriptionPtr = std::shared_ptr; + + enum AudioAPI + { + DirectSound = 0, + XAudio27, + XAudio2, + Cubeb, + + AudioAPIEnd, + }; + static constexpr uint32 kBlockCount = 24; + + IAudioAPI(uint32 samplerate, uint32 channels, uint32 samples_per_block, uint32 bits_per_sample); + virtual ~IAudioAPI() = default; + virtual AudioAPI GetType() const = 0; + + sint32 GetChannels() const { return m_channels; } + + virtual sint32 GetVolume() const { return m_volume; } + virtual void SetVolume(sint32 volume) { m_volume = volume; } + virtual void SetInputVolume(sint32 volume) { m_inputVolume = volume; } + + virtual bool NeedAdditionalBlocks() const = 0; + virtual bool FeedBlock(sint16* data) = 0; + virtual bool Play() = 0; + virtual bool Stop() = 0; + + static void PrintLogging(); + static void InitializeStatic(); + static bool IsAudioAPIAvailable(AudioAPI api); + + static std::unique_ptr CreateDevice(AudioAPI api, const DeviceDescriptionPtr& device, sint32 samplerate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample); + static std::vector GetDevices(AudioAPI api); + +protected: +#if BOOST_OS_WINDOWS > 0 + WAVEFORMATEXTENSIBLE m_wfx{}; +#endif + + uint32 m_samplerate, m_channels, m_samplesPerBlock, m_bitsPerSample; + uint32 m_bytesPerBlock; + + sint32 m_volume = 0, m_inputVolume = 0; + bool m_playing = false; + + static std::array s_availableApis; + static uint32 s_audioDelay; + +private: + void InitWFX(sint32 samplerate, sint32 channels, sint32 bits_per_sample); + +}; + +using AudioAPIPtr = std::unique_ptr; +extern std::shared_mutex g_audioMutex; +extern AudioAPIPtr g_tvAudio; + +extern AudioAPIPtr g_padAudio; +extern std::atomic_int32_t g_padVolume; diff --git a/src/audio/XAudio27API.cpp b/src/audio/XAudio27API.cpp new file mode 100644 index 00000000..4a99bfa2 --- /dev/null +++ b/src/audio/XAudio27API.cpp @@ -0,0 +1,237 @@ +#include "XAudio27API.h" + +#include "../dependencies/DirectX_2010/XAudio2.h" + +static_assert(IAudioAPI::kBlockCount < XAUDIO2_MAX_QUEUED_BUFFERS, "too many xaudio2 buffers"); + +HMODULE XAudio27API::s_xaudio_dll = nullptr; +bool XAudio27API::s_com_initialized = false; +std::unique_ptr XAudio27API::s_xaudio; + +XAudio27API::XAudio27API(uint32 device_id, uint32 samplerate, uint32 channels, uint32 samples_per_block, uint32 bits_per_sample) + : IAudioAPI(samplerate, channels, samples_per_block, bits_per_sample) +{ + if (!s_xaudio) + throw std::runtime_error("xaudio 2.7 not initialized!"); + + // we use -1 for always selecting the primary device, which is the first one + if (device_id == -1) + device_id = 0; + + HRESULT hres; + IXAudio2* xaudio; + if (FAILED((hres = XAudio2Create(&xaudio, 0, XAUDIO2_DEFAULT_PROCESSOR)))) + throw std::runtime_error(fmt::format("can't create xaudio device (hres: {:#x})", hres)); + m_xaudio = decltype(m_xaudio)(xaudio); + + IXAudio2MasteringVoice* mastering_voice; + if (FAILED((hres = m_xaudio->CreateMasteringVoice(&mastering_voice, channels, samplerate, 0, device_id)))) + throw std::runtime_error(fmt::format("can't create xaudio mastering voice (hres: {:#x})", hres)); + + m_mastering_voice = decltype(m_mastering_voice)(mastering_voice); + + m_wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + m_wfx.Format.nChannels = channels; + m_wfx.Format.nSamplesPerSec = samplerate; + m_wfx.Format.wBitsPerSample = bits_per_sample; + m_wfx.Format.nBlockAlign = (m_wfx.Format.nChannels * m_wfx.Format.wBitsPerSample) / 8; // must equal (nChannels × wBitsPerSample) / 8 + m_wfx.Format.nAvgBytesPerSec = m_wfx.Format.nSamplesPerSec * m_wfx.Format.nBlockAlign; // must equal nSamplesPerSec × nBlockAlign. + m_wfx.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); + + m_wfx.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + m_wfx.Samples.wValidBitsPerSample = bits_per_sample; + switch (channels) + { + case 8: + m_wfx.dwChannelMask |= (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_FRONT_LEFT_OF_CENTER | SPEAKER_FRONT_RIGHT_OF_CENTER); + break; + case 6: + m_wfx.dwChannelMask |= (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT); + break; + case 4: + m_wfx.dwChannelMask |= (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT); + break; + case 2: + m_wfx.dwChannelMask |= (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT); + break; + default: + m_wfx.dwChannelMask = 0; + break; + } + + IXAudio2SourceVoice* source_voice; + if (FAILED((hres = m_xaudio->CreateSourceVoice(&source_voice, &m_wfx.Format, 0, 1.0f)))) + throw std::runtime_error(fmt::format("can't create xaudio source voice (hres: {:#x})", hres)); + m_source_voice = decltype(m_source_voice)(source_voice); + + m_sound_buffer_size = kBlockCount * (samples_per_block * channels * (bits_per_sample / 8)); + + for (uint32 i = 0; i < kBlockCount; ++i) + m_audio_buffer[i] = std::make_unique(m_bytesPerBlock); + + m_xaudio->StartEngine(); +} + +XAudio27API::~XAudio27API() +{ + if(m_xaudio) + m_xaudio->StopEngine(); + + XAudio27API::Stop(); + + m_source_voice.reset(); + m_mastering_voice.reset(); + m_xaudio.reset(); +} + +void XAudio27API::SetVolume(sint32 volume) +{ + IAudioAPI::SetVolume(volume); + m_mastering_voice->SetVolume((float)volume / 100.0f); +} + +bool XAudio27API::Play() +{ + if (m_playing) + return true; + + m_playing = SUCCEEDED(m_source_voice->Start()); + return m_playing; +} + +bool XAudio27API::Stop() +{ + if (!m_playing) + return true; + + m_playing = FAILED(m_source_voice->Stop()); + m_source_voice->FlushSourceBuffers(); + + return m_playing; +} + +bool XAudio27API::InitializeStatic() +{ + if (s_xaudio) + return true; + + s_com_initialized = (SUCCEEDED(CoInitializeEx(nullptr, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE))); + +#ifdef _DEBUG + s_xaudio_dll = LoadLibraryExW(L"XAudioD2_7.DLL", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); + if(!s_xaudio_dll) + s_xaudio_dll = LoadLibraryExW(L"XAudio2_7.DLL", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); +#else + s_xaudio_dll = LoadLibraryExW(L"XAudio2_7.DLL", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); +#endif + + try + { + if (!s_xaudio_dll) + throw std::exception(); + + IXAudio2* xaudio; + if (FAILED(XAudio2Create(&xaudio, 0, XAUDIO2_DEFAULT_PROCESSOR))) + throw std::exception(); + + s_xaudio = decltype(s_xaudio)(xaudio); + return true; + } + catch (const std::exception&) + { + if (s_xaudio_dll) + FreeLibrary(s_xaudio_dll); + + if (s_com_initialized) + CoUninitialize(); + + return false; + } +} + +void XAudio27API::Destroy() +{ + s_xaudio.reset(); + + if (s_xaudio_dll) + FreeLibrary(s_xaudio_dll); + + if (s_com_initialized) + CoUninitialize(); +} + +std::vector XAudio27API::GetDevices() +{ + std::vector result; + + // always add the default device + auto default_device = std::make_shared(L"Primary Sound Driver", L"", -1); + result.emplace_back(default_device); + + uint32 count = 0; + if (FAILED(s_xaudio->GetDeviceCount(&count))) + return result; + + if (!count) + return result; + + result.reserve(count + 1); + + // first device is always the primary device + for (uint32 id = 0; id < count; ++id) + { + XAUDIO2_DEVICE_DETAILS details; + if (SUCCEEDED(s_xaudio->GetDeviceDetails(id, &details))) + { + auto device = std::make_shared(details.DisplayName, details.DeviceID, id); + result.emplace_back(device); + } + } + + return result; +} + +void XAudio27API::XAudioDeleter::operator()(IXAudio2* ptr) const +{ + if (ptr) + ptr->Release(); +} + +void XAudio27API::VoiceDeleter::operator()(IXAudio2Voice* ptr) const +{ + if (ptr) + ptr->DestroyVoice(); +} + +bool XAudio27API::FeedBlock(sint16* data) +{ + // check if we queued too many blocks + if(m_blocks_queued >= kBlockCount) + { + XAUDIO2_VOICE_STATE state{}; + m_source_voice->GetState(&state); + m_blocks_queued = state.BuffersQueued; + + if (m_blocks_queued >= kBlockCount) + { + forceLogDebug_printf("dropped xaudio2 block since too many buffers are queued"); + return false; + } + } + + memcpy(m_audio_buffer[m_offset].get(), data, m_bytesPerBlock); + + XAUDIO2_BUFFER buffer{}; + buffer.AudioBytes = m_bytesPerBlock; + buffer.pAudioData = m_audio_buffer[m_offset].get(); + m_source_voice->SubmitSourceBuffer(&buffer); + + m_offset = (m_offset + 1) % kBlockCount; + m_blocks_queued++; + return true; +} + +bool XAudio27API::NeedAdditionalBlocks() const +{ + return m_blocks_queued < s_audioDelay; +} diff --git a/src/audio/XAudio27API.h b/src/audio/XAudio27API.h new file mode 100644 index 00000000..80e423a3 --- /dev/null +++ b/src/audio/XAudio27API.h @@ -0,0 +1,73 @@ +#pragma once + +#define DIRECTSOUND_VERSION 0x0800 +#include +#include +#include + +#include "IAudioAPI.h" + +struct IXAudio2; +struct IXAudio2Voice; +struct IXAudio2MasteringVoice; +struct IXAudio2SourceVoice; + +class XAudio27API : public IAudioAPI +{ +public: + class XAudio27DeviceDescription : public DeviceDescription + { + public: + XAudio27DeviceDescription(const std::wstring& name, std::wstring device_id, uint32 id) + : DeviceDescription(name), m_device_id(std::move(device_id)), m_id(id) { } + + std::wstring GetIdentifier() const override { return m_device_id.empty() ? L"default" : m_device_id; } + uint32 GetDeviceId() const { return m_id; } + + private: + std::wstring m_device_id; + const uint32 m_id; + }; + + using XAudio2DeviceDescriptionPtr = std::shared_ptr; + + AudioAPI GetType() const override { return XAudio27; } + + XAudio27API(uint32 device_id, uint32 samplerate, uint32 channels, uint32 samples_per_block, uint32 bits_per_sample); + ~XAudio27API(); + void SetVolume(sint32 volume) override; + + bool Play() override; + bool Stop() override; + bool FeedBlock(sint16* data) override; + bool NeedAdditionalBlocks() const override; + + static bool InitializeStatic(); + static void Destroy(); + static std::vector GetDevices(); + +private: + struct XAudioDeleter + { + void operator()(IXAudio2* ptr) const; + }; + + struct VoiceDeleter + { + void operator()(IXAudio2Voice* ptr) const; + }; + + static HMODULE s_xaudio_dll; + static bool s_com_initialized; + static std::unique_ptr s_xaudio; + + std::unique_ptr m_xaudio; + std::wstring m_device_id; + std::unique_ptr m_mastering_voice; + std::unique_ptr m_source_voice; + + std::unique_ptr m_audio_buffer[kBlockCount]; + DWORD m_sound_buffer_size = 0; + uint32_t m_offset = 0; + uint32_t m_blocks_queued = 0; +}; diff --git a/src/audio/XAudio2API.cpp b/src/audio/XAudio2API.cpp new file mode 100644 index 00000000..0cc54651 --- /dev/null +++ b/src/audio/XAudio2API.cpp @@ -0,0 +1,310 @@ + + +//#if (_WIN32_WINNT >= 0x0602 /*_WIN32_WINNT_WIN8*/) + +#include + +#ifndef XAUDIO2_DLL +#error wrong included! +#endif + +#include "XAudio2API.h" +#include "util/helpers/helpers.h" + +#include +#include +#include + +// guid from mmdeviceapi.h +static const GUID DEVINTERFACE_AUDIO_RENDER_GUID = { 0xe6327cad, 0xdcec, 0x4949, 0xae, 0x8a, 0x99, 0x1e, 0x97, 0x6a, 0x79, 0xd2 }; + +#pragma comment(lib, "wbemuuid.lib") + +static_assert(IAudioAPI::kBlockCount < XAUDIO2_MAX_QUEUED_BUFFERS, "too many xaudio2 buffers"); + +HMODULE XAudio2API::s_xaudio_dll = nullptr; +bool XAudio2API::s_com_initialized = false; +std::vector XAudio2API::s_devices; + +XAudio2API::XAudio2API(std::wstring device_id, uint32 samplerate, uint32 channels, uint32 samples_per_block, uint32 bits_per_sample) + : IAudioAPI(samplerate, channels, samples_per_block, bits_per_sample), m_device_id(std::move(device_id)) +{ + const auto _XAudio2Create = (decltype(&XAudio2Create))GetProcAddress(s_xaudio_dll, "XAudio2Create"); + if (!_XAudio2Create) + throw std::runtime_error("can't find XAudio2Create import"); + + HRESULT hres; + IXAudio2* xaudio; + if (FAILED((hres = _XAudio2Create(&xaudio, 0, XAUDIO2_DEFAULT_PROCESSOR)))) + throw std::runtime_error(fmt::format("can't create xaudio device (hres: {:#x})", hres)); + + m_xaudio = decltype(m_xaudio)(xaudio); + + IXAudio2MasteringVoice* mastering_voice; + if (FAILED((hres = m_xaudio->CreateMasteringVoice(&mastering_voice, channels, samplerate, 0, m_device_id.empty() ? nullptr : m_device_id.c_str())))) + throw std::runtime_error(fmt::format("can't create xaudio mastering voice (hres: {:#x})", hres)); + + m_mastering_voice = decltype(m_mastering_voice)(mastering_voice); + + m_wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + m_wfx.Format.nChannels = channels; + m_wfx.Format.nSamplesPerSec = samplerate; + m_wfx.Format.wBitsPerSample = bits_per_sample; + m_wfx.Format.nBlockAlign = (m_wfx.Format.nChannels * m_wfx.Format.wBitsPerSample) / 8; // must equal (nChannels × wBitsPerSample) / 8 + m_wfx.Format.nAvgBytesPerSec = m_wfx.Format.nSamplesPerSec * m_wfx.Format.nBlockAlign; // must equal nSamplesPerSec × nBlockAlign. + m_wfx.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); + + m_wfx.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + m_wfx.Samples.wValidBitsPerSample = bits_per_sample; + switch(channels) + { + case 8: + m_wfx.dwChannelMask |= (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_FRONT_LEFT_OF_CENTER | SPEAKER_FRONT_RIGHT_OF_CENTER); + break; + case 6: + m_wfx.dwChannelMask |= (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT); + break; + case 4: + m_wfx.dwChannelMask |= (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT); + break; + case 2: + m_wfx.dwChannelMask |= (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT); + break; + default: + m_wfx.dwChannelMask = 0; + break; + } + + IXAudio2SourceVoice* source_voice; + if (FAILED((hres = m_xaudio->CreateSourceVoice(&source_voice, &m_wfx.Format, 0, 1.0f)))) + throw std::runtime_error(fmt::format("can't create xaudio source voice (hres: {:#x})", hres)); + + m_source_voice = decltype(m_source_voice)(source_voice); + + m_sound_buffer_size = kBlockCount * (samples_per_block * channels * (bits_per_sample / 8)); + + for (uint32 i = 0; i < kBlockCount; ++i) + m_audio_buffer[i] = std::make_unique(m_bytesPerBlock); + + m_xaudio->StartEngine(); +} + +void XAudio2API::XAudioDeleter::operator()(IXAudio2* ptr) const +{ + if (ptr) + ptr->Release(); +} + +void XAudio2API::VoiceDeleter::operator()(IXAudio2Voice* ptr) const +{ + if (ptr) + ptr->DestroyVoice(); +} + +XAudio2API::~XAudio2API() +{ + if(m_xaudio) + m_xaudio->StopEngine(); + + XAudio2API::Stop(); + + m_source_voice.reset(); + m_mastering_voice.reset(); + m_xaudio.reset(); +} + +void XAudio2API::SetVolume(sint32 volume) +{ + IAudioAPI::SetVolume(volume); + m_mastering_voice->SetVolume((float)volume / 100.0f); +} + +bool XAudio2API::Play() +{ + if (m_playing) + return true; + + m_playing = SUCCEEDED(m_source_voice->Start()); + return m_playing; +} + +bool XAudio2API::Stop() +{ + if (!m_playing) + return true; + + m_playing = FAILED(m_source_voice->Stop()); + m_source_voice->FlushSourceBuffers(); + + return m_playing; +} + +bool XAudio2API::InitializeStatic() +{ + if (s_xaudio_dll) + return true; + + s_com_initialized = (SUCCEEDED(CoInitializeEx(nullptr, COINIT_MULTITHREADED))); + + // win 10 + s_xaudio_dll = LoadLibraryEx(XAUDIO2_DLL, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); + + try + { + if (!s_xaudio_dll) + throw std::exception(); + + const auto _XAudio2Create = (decltype(&XAudio2Create))GetProcAddress(s_xaudio_dll, "XAudio2Create"); + if (!_XAudio2Create) + throw std::exception(); + + RefreshDevices(); + return true; + } + catch (const std::exception&) + { + if (s_xaudio_dll) + FreeLibrary(s_xaudio_dll); + + if (s_com_initialized) + CoUninitialize(); + + return false; + } +} + +void XAudio2API::Destroy() +{ + if (s_xaudio_dll) + FreeLibrary(s_xaudio_dll); + + if (s_com_initialized) + CoUninitialize(); +} + +const std::vector& XAudio2API::RefreshDevices() +{ + // this function must be called from the same thread as we called CoInitializeEx + s_devices.clear(); + + // always add the default device + auto default_device = std::make_shared(L"Primary Sound Driver", L""); + s_devices.emplace_back(default_device); + + if (FAILED(CoInitializeEx(nullptr, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE))) + return s_devices; + + try + { + struct IWbemLocator *wbem_locator = nullptr; + + HRESULT hres = CoCreateInstance(__uuidof(WbemLocator), nullptr, CLSCTX_INPROC_SERVER, __uuidof(IWbemLocator), (LPVOID*)&wbem_locator); + if (FAILED(hres) || !wbem_locator) + throw std::system_error(hres, std::system_category()); + + std::shared_ptr path(SysAllocString(LR"(\\.\root\cimv2)"), SysFreeString); + std::shared_ptr language(SysAllocString(L"WQL"), SysFreeString); + std::shared_ptr query(SysAllocString(LR"(SELECT Name,DeviceID FROM Win32_PNPEntity WHERE PNPClass = "AudioEndpoint")"), SysFreeString); + std::shared_ptr name_row(SysAllocString(L"Name"), SysFreeString); + std::shared_ptr device_id_row(SysAllocString(L"DeviceID"), SysFreeString); + + IWbemServices *wbem_services = nullptr; + hres = wbem_locator->ConnectServer(path.get(), nullptr, nullptr, nullptr, 0, nullptr, nullptr, &wbem_services); + wbem_locator->Release(); // Free memory resources. + + if (FAILED(hres) || !wbem_services) + throw std::system_error(hres, std::system_category()); + + hres = CoSetProxyBlanket(wbem_services, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, nullptr, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, EOAC_NONE); + if (FAILED(hres)) + throw std::system_error(hres, std::system_category()); + + IEnumWbemClassObject* wbem_enum = nullptr; + hres = wbem_services->ExecQuery(language.get(), query.get(), WBEM_FLAG_RETURN_WBEM_COMPLETE | WBEM_FLAG_FORWARD_ONLY, nullptr, &wbem_enum); + if (FAILED(hres) || !wbem_enum) + throw std::system_error(hres, std::system_category()); + + ULONG returned; + IWbemClassObject* object[20]; + // WBEM_S_TIMEDOUT + while (SUCCEEDED(hres = wbem_enum->Next(100, 20, object, &returned)) && returned > 0) + { + for (ULONG i = 0; i < returned; ++i) + { + std::wstring name, device_id; + + VARIANT var; + if (SUCCEEDED(object[i]->Get(name_row.get(), 0L, &var, NULL, NULL)) && var.vt == VT_BSTR && var.bstrVal) + { + name = var.bstrVal; + if (SUCCEEDED(object[i]->Get(device_id_row.get(), 0L, &var, NULL, NULL)) && var.vt == VT_BSTR && var.bstrVal) + { + std::wstring id = var.bstrVal; + + if(id.find(L"{0.0.0.00000000}") == std::wstring::npos) + { + object[i]->Release(); + continue; + } + + std::replace(id.begin(), id.end(), L'\\', L'#'); // xaudio devices have "#" instead of backslashes + + std::wstringstream tmp; + tmp << L"\\\\?\\" << id << L"#{" << WStringFromGUID(DEVINTERFACE_AUDIO_RENDER_GUID) << L"}"; + device_id = tmp.str(); + } + } + + auto device = std::make_shared(name,device_id); + s_devices.emplace_back(device); + + object[i]->Release(); + } + } + + wbem_enum->Release(); + + // Clean up + wbem_services->Release(); + } + catch (const std::system_error& ex) + { + forceLog_printf("XAudio2API::RefreshDevices: error while refreshing device list (%s - code: 0x%08x)", ex.what(), ex.code().value()); + } + + CoUninitialize(); + return s_devices; +} + +bool XAudio2API::FeedBlock(sint16* data) +{ + // check if we queued too many blocks + if (m_blocks_queued >= kBlockCount) + { + XAUDIO2_VOICE_STATE state{}; + m_source_voice->GetState(&state); + m_blocks_queued = state.BuffersQueued; + + if (m_blocks_queued >= kBlockCount) + { + forceLogDebug_printf("dropped xaudio2 block since too many buffers are queued"); + return false; + } + } + + memcpy(m_audio_buffer[m_offset].get(), data, m_bytesPerBlock); + + XAUDIO2_BUFFER buffer{}; + buffer.AudioBytes = m_bytesPerBlock; + buffer.pAudioData = m_audio_buffer[m_offset].get(); + m_source_voice->SubmitSourceBuffer(&buffer); + + m_offset = (m_offset + 1) % kBlockCount; + m_blocks_queued++; + return true; +} + +bool XAudio2API::NeedAdditionalBlocks() const +{ + return m_blocks_queued < s_audioDelay; +} diff --git a/src/audio/XAudio2API.h b/src/audio/XAudio2API.h new file mode 100644 index 00000000..2b23e1cc --- /dev/null +++ b/src/audio/XAudio2API.h @@ -0,0 +1,74 @@ +#pragma once + +#define DIRECTSOUND_VERSION 0x0800 +#include +#include +#include + +#include "IAudioAPI.h" + +struct IXAudio2; +struct IXAudio2Voice; +struct IXAudio2MasteringVoice; +struct IXAudio2SourceVoice; + +class XAudio2API : public IAudioAPI +{ +public: + class XAudio2DeviceDescription : public DeviceDescription + { + public: + XAudio2DeviceDescription(const std::wstring& name, std::wstring device_id) + : DeviceDescription(name), m_device_id(std::move(device_id)) { } + + std::wstring GetIdentifier() const override { return m_device_id.empty() ? L"default" : m_device_id; } + const std::wstring& GetDeviceId() const { return m_device_id; } + + private: + std::wstring m_device_id; + }; + + using XAudio2DeviceDescriptionPtr = std::shared_ptr; + + AudioAPI GetType() const override { return XAudio27; } + + XAudio2API(std::wstring device_id, uint32 samplerate, uint32 channels, uint32 samples_per_block, uint32 bits_per_sample); + ~XAudio2API(); + void SetVolume(sint32 volume) override; + + bool Play() override; + bool Stop() override; + bool FeedBlock(sint16* data) override; + bool NeedAdditionalBlocks() const override; + + static bool InitializeStatic(); + static void Destroy(); + static const std::vector& GetDevices() { return s_devices; } + +private: + static const std::vector& RefreshDevices(); + + struct XAudioDeleter + { + void operator()(IXAudio2* ptr) const; + }; + + struct VoiceDeleter + { + void operator()(IXAudio2Voice* ptr) const; + }; + + static HMODULE s_xaudio_dll; + static bool s_com_initialized; + static std::vector s_devices; + + std::unique_ptr m_xaudio; + std::wstring m_device_id; + std::unique_ptr m_mastering_voice; + std::unique_ptr m_source_voice; + + std::unique_ptr m_audio_buffer[kBlockCount]; + DWORD m_sound_buffer_size = 0; + uint32_t m_offset = 0; + uint32_t m_blocks_queued = 0; +}; diff --git a/src/audio/audioDebuggerWindow.cpp b/src/audio/audioDebuggerWindow.cpp new file mode 100644 index 00000000..9cdb78d8 --- /dev/null +++ b/src/audio/audioDebuggerWindow.cpp @@ -0,0 +1,331 @@ +#include "gui/wxgui.h" +#include "audioDebuggerWindow.h" + +#include "Cafe/OS/libs/snd_core/ax.h" +#include "Cafe/OS/libs/snd_core/ax_internal.h" + +enum +{ + // options + REFRESH_ID, + CLOSE_ID, + VOICELIST_ID, + REFRESH_TIMER_ID, +}; + +wxBEGIN_EVENT_TABLE(AudioDebuggerWindow, wxFrame) +EVT_BUTTON(CLOSE_ID, AudioDebuggerWindow::OnCloseButton) +EVT_BUTTON(REFRESH_ID, AudioDebuggerWindow::OnRefreshButton) +EVT_TIMER(REFRESH_TIMER_ID, AudioDebuggerWindow::OnRefreshTimer) + +EVT_CLOSE(AudioDebuggerWindow::OnClose) +wxEND_EVENT_TABLE() + +AudioDebuggerWindow::AudioDebuggerWindow(wxFrame& parent) + : wxFrame(&parent, wxID_ANY, wxT("AX voice viewer"), wxDefaultPosition, wxSize(1126, 580), wxCLOSE_BOX | wxCLIP_CHILDREN | wxCAPTION | wxRESIZE_BORDER) +{ + + wxPanel* mainPane = new wxPanel(this); + wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL); + + voiceListbox = new wxListCtrl(mainPane, VOICELIST_ID, wxPoint(0, 0), wxSize(1126, 570), wxLC_REPORT); + voiceListbox->SetFont(wxFont(8, wxFONTFAMILY_MODERN, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, "Courier New")); + // add columns + wxListItem col0; + col0.SetId(0); + col0.SetText(wxT("idx")); + col0.SetWidth(40); + voiceListbox->InsertColumn(0, col0); + wxListItem col1; + col1.SetId(1); + col1.SetText(wxT("state")); + col1.SetWidth(48); + voiceListbox->InsertColumn(1, col1); + //wxListItem col2; + // format + col1.SetId(2); + col1.SetText(wxT("fmt")); + col1.SetWidth(52); + voiceListbox->InsertColumn(2, col1); + // sample base addr + col1.SetId(3); + col1.SetText(wxT("base")); + col1.SetWidth(70); + voiceListbox->InsertColumn(3, col1); + // current offset + col1.SetId(4); + col1.SetText(wxT("current")); + col1.SetWidth(70); + voiceListbox->InsertColumn(4, col1); + // loop offset + col1.SetId(5); + col1.SetText(wxT("loop")); + col1.SetWidth(70); + voiceListbox->InsertColumn(5, col1); + // end offset + col1.SetId(6); + col1.SetText(wxT("end")); + col1.SetWidth(70); + voiceListbox->InsertColumn(6, col1); + // volume + col1.SetId(7); + col1.SetText(wxT("vol")); + col1.SetWidth(46); + voiceListbox->InsertColumn(7, col1); + // volume delta + col1.SetId(8); + col1.SetText(wxT("volD")); + col1.SetWidth(46); + voiceListbox->InsertColumn(8, col1); + // src + col1.SetId(9); + col1.SetText(wxT("src")); + col1.SetWidth(70); + voiceListbox->InsertColumn(9, col1); + // low-pass filter coef a0 + col1.SetId(10); + col1.SetText(wxT("lpa0")); + col1.SetWidth(46); + voiceListbox->InsertColumn(10, col1); + // low-pass filter coef b0 + col1.SetId(11); + col1.SetText(wxT("lpb0")); + col1.SetWidth(46); + voiceListbox->InsertColumn(11, col1); + // biquad filter coef b0 + col1.SetId(12); + col1.SetText(wxT("bqb0")); + col1.SetWidth(46); + voiceListbox->InsertColumn(12, col1); + // biquad filter coef b0 + col1.SetId(13); + col1.SetText(wxT("bqb1")); + col1.SetWidth(46); + voiceListbox->InsertColumn(13, col1); + // biquad filter coef b0 + col1.SetId(14); + col1.SetText(wxT("bqb2")); + col1.SetWidth(46); + voiceListbox->InsertColumn(14, col1); + // biquad filter coef a0 + col1.SetId(15); + col1.SetText(wxT("bqa1")); + col1.SetWidth(46); + voiceListbox->InsertColumn(15, col1); + // biquad filter coef a1 + col1.SetId(16); + col1.SetText(wxT("bqa2")); + col1.SetWidth(46); + voiceListbox->InsertColumn(16, col1); + // device mix + col1.SetId(17); + col1.SetText(wxT("deviceMix")); + col1.SetWidth(186); + voiceListbox->InsertColumn(17, col1); + + sizer->Add(voiceListbox, 1, wxEXPAND | wxBOTTOM, 0); + + voiceListbox->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(AudioDebuggerWindow::OnVoiceListRightClick), NULL, this); + + mainPane->SetSizer(sizer); + + // add empty entries + for (sint32 i = 0; i < snd_core::AX_MAX_VOICES; i++) + { + wxListItem item; + item.SetId(i); + char tempStr[32]; + sprintf(tempStr, "%d", snd_core::AX_MAX_VOICES-i-1); + item.SetText(wxString(tempStr)); + voiceListbox->InsertItem(item); + } + RefreshVoiceList(); + + wxFrame::SetBackgroundColour(*wxWHITE); + + // start refresh timer + static const int INTERVAL = 100; // milliseconds + refreshTimer = new wxTimer(this, REFRESH_TIMER_ID); + refreshTimer->Start(INTERVAL); +} + +void AudioDebuggerWindow::OnRefreshTimer(wxTimerEvent& event) +{ + RefreshVoiceList(); +} + +void AudioDebuggerWindow::OnCloseButton(wxCommandEvent& event) +{ + Close(); +} + +void AudioDebuggerWindow::OnRefreshButton(wxCommandEvent& event) +{ + RefreshVoiceList(); +} + +void AudioDebuggerWindow::OnClose(wxCloseEvent& event) +{ + Close(); +} + +#define _r(__idx) _swapEndianU32(threadItrBE->context.gpr[__idx]) + +void AudioDebuggerWindow::RefreshVoiceList_sndgeneric() +{ + if (snd_core::__AXVPBInternalVoiceArray == nullptr || snd_core::__AXVPBArrayPtr == nullptr) + return; + + snd_core::AXVPB tempVoiceArray[snd_core::AX_MAX_VOICES]; + memcpy(tempVoiceArray, snd_core::__AXVPBArrayPtr, sizeof(snd_core::AXVPB)*snd_core::AX_MAX_VOICES); + + voiceListbox->Freeze(); + + char tempStr[64]; + for (sint32 i = 0; i < snd_core::AX_MAX_VOICES; i++) + { + sint32 voiceIndex = snd_core::AX_MAX_VOICES - 1 - i; + snd_core::AXVPBInternal_t* internal = snd_core::__AXVPBInternalVoiceArray + voiceIndex; + // index + sprintf(tempStr, "%d", (sint32)tempVoiceArray[voiceIndex].index); + voiceListbox->SetItem(i, 0, tempStr); + // state + uint16 playbackState = _swapEndianU16(internal->playbackState); + if (playbackState) + strcpy(tempStr, "on"); + else + strcpy(tempStr, "off"); + voiceListbox->SetItem(i, 1, tempStr); + // if voice index is invalid then stop updating here to prevent crashes + if (voiceIndex < 0 || playbackState == 0) + { + voiceListbox->SetItem(i, 0, ""); + voiceListbox->SetItem(i, 1, ""); + voiceListbox->SetItem(i, 2, ""); + voiceListbox->SetItem(i, 3, ""); + voiceListbox->SetItem(i, 4, ""); + voiceListbox->SetItem(i, 5, ""); + voiceListbox->SetItem(i, 6, ""); + voiceListbox->SetItem(i, 7, ""); + voiceListbox->SetItem(i, 8, ""); + voiceListbox->SetItem(i, 9, ""); + voiceListbox->SetItem(i, 10, ""); + voiceListbox->SetItem(i, 11, ""); + voiceListbox->SetItem(i, 12, ""); + voiceListbox->SetItem(i, 13, ""); + voiceListbox->SetItem(i, 14, ""); + voiceListbox->SetItem(i, 15, ""); + voiceListbox->SetItem(i, 16, ""); + voiceListbox->SetItem(i, 17, ""); + continue; + } + // format + if (tempVoiceArray[voiceIndex].offsets.format == _swapEndianU16(snd_core::AX_FORMAT_ADPCM)) + strcpy(tempStr, "adpcm"); + else if (tempVoiceArray[voiceIndex].offsets.format == _swapEndianU16(snd_core::AX_FORMAT_PCM16)) + strcpy(tempStr, "pcm16"); + else if (tempVoiceArray[voiceIndex].offsets.format == _swapEndianU16(snd_core::AX_FORMAT_PCM8)) + strcpy(tempStr, "pcm8"); + else + strcpy(tempStr, "ukn"); + voiceListbox->SetItem(i, 2, tempStr); + // update offsets + snd_core::AXPBOFFSET_t tempOffsets; + snd_core::AXGetVoiceOffsets(tempVoiceArray + voiceIndex, &tempOffsets); + // sample base + sprintf(tempStr, "%08x", _swapEndianU32(tempOffsets.samples)); + voiceListbox->SetItem(i, 3, tempStr); + // current offset + sprintf(tempStr, "%08x", _swapEndianU32(tempOffsets.currentOffset)); + voiceListbox->SetItem(i, 4, tempStr); + // loop offset + if (tempOffsets.loopFlag) + sprintf(tempStr, "%08x", _swapEndianU32(tempOffsets.loopOffset)); + else + sprintf(tempStr, ""); + voiceListbox->SetItem(i, 5, tempStr); + // end offset + sprintf(tempStr, "%08x", _swapEndianU32(tempOffsets.endOffset)); + voiceListbox->SetItem(i, 6, tempStr); + // volume + sprintf(tempStr, "%04x", (uint16)internal->veVolume); + voiceListbox->SetItem(i, 7, tempStr); + // volume delta + sprintf(tempStr, "%04x", (uint16)internal->veDelta); + voiceListbox->SetItem(i, 8, tempStr); + // src + sprintf(tempStr, "%04x%04x", _swapEndianU16(internal->src.ratioHigh), _swapEndianU16(internal->src.ratioLow)); + voiceListbox->SetItem(i, 9, tempStr); + // lpf + if (internal->lpf.on) + { + sprintf(tempStr, "%04x", _swapEndianU16(internal->lpf.a0)); + voiceListbox->SetItem(i, 10, tempStr); + sprintf(tempStr, "%04x", _swapEndianU16(internal->lpf.b0)); + voiceListbox->SetItem(i, 11, tempStr); + } + else + { + voiceListbox->SetItem(i, 10, ""); + voiceListbox->SetItem(i, 11, ""); + } + // biquad + if (internal->biquad.on) + { + sprintf(tempStr, "%04x", _swapEndianU16(internal->biquad.b0)); + voiceListbox->SetItem(i, 12, tempStr); + sprintf(tempStr, "%04x", _swapEndianU16(internal->biquad.b1)); + voiceListbox->SetItem(i, 13, tempStr); + sprintf(tempStr, "%04x", _swapEndianU16(internal->biquad.b2)); + voiceListbox->SetItem(i, 14, tempStr); + sprintf(tempStr, "%04x", _swapEndianU16(internal->biquad.a1)); + voiceListbox->SetItem(i, 15, tempStr); + sprintf(tempStr, "%04x", _swapEndianU16(internal->biquad.a2)); + voiceListbox->SetItem(i, 16, tempStr); + } + else + { + voiceListbox->SetItem(i, 12, ""); + voiceListbox->SetItem(i, 13, ""); + voiceListbox->SetItem(i, 14, ""); + voiceListbox->SetItem(i, 15, ""); + voiceListbox->SetItem(i, 16, ""); + } + // device mix + for (uint32 f = 0; f < snd_core::AX_TV_CHANNEL_COUNT*snd_core::AX_MAX_NUM_BUS; f++) + { + sint32 busIndex = f% snd_core::AX_MAX_NUM_BUS; + sint32 channelIndex = f / snd_core::AX_MAX_NUM_BUS; + + //debug_printf("DeviceMix TV Voice %08x b%02d/c%02d vol %04x delta %04x\n", hCPU->gpr[3], busIndex, channelIndex, _swapEndianU16(mixArrayBE[f].vol), _swapEndianU16(mixArrayBE[f].volDelta)); + uint32 mixVol = internal->deviceMixTV[channelIndex * 4 + busIndex].vol; + mixVol = (mixVol + 0x0FFF) >> (12); + sprintf(tempStr + f, "%x", mixVol); + //ax.voiceInternal[voiceIndex].deviceMixTVChannel[channelIndex].bus[busIndex].vol = _swapEndianU16(mixArrayBE[f].vol); + } + voiceListbox->SetItem(i, 17, tempStr); + } + voiceListbox->Thaw(); +} + +void AudioDebuggerWindow::RefreshVoiceList() +{ + RefreshVoiceList_sndgeneric(); +} + +void AudioDebuggerWindow::OnVoiceListPopupClick(wxCommandEvent &evt) +{ + +} + +void AudioDebuggerWindow::OnVoiceListRightClick(wxMouseEvent& event) +{ + +} + +void AudioDebuggerWindow::Close() +{ + // this->MakeModal(false); + refreshTimer->Stop(); + this->Destroy(); +} diff --git a/src/audio/audioDebuggerWindow.h b/src/audio/audioDebuggerWindow.h new file mode 100644 index 00000000..5a6f1242 --- /dev/null +++ b/src/audio/audioDebuggerWindow.h @@ -0,0 +1,28 @@ +#pragma once + +#include + +class AudioDebuggerWindow : public wxFrame +{ +public: + AudioDebuggerWindow(wxFrame& parent); + + void OnCloseButton(wxCommandEvent& event); + void OnRefreshButton(wxCommandEvent& event); + void OnClose(wxCloseEvent& event); + void RefreshVoiceList_sndgeneric(); + void RefreshVoiceList(); + void OnRefreshTimer(wxTimerEvent& event); + void OnVoiceListPopupClick(wxCommandEvent &evt); + void OnVoiceListRightClick(wxMouseEvent& event); + + void Close(); + +private: + wxListCtrl* voiceListbox; + wxTimer* refreshTimer; + + wxDECLARE_EVENT_TABLE(); + + +}; \ No newline at end of file diff --git a/src/audio/xaudio2_7/audiodefs.h b/src/audio/xaudio2_7/audiodefs.h new file mode 100644 index 00000000..ff995ecc --- /dev/null +++ b/src/audio/xaudio2_7/audiodefs.h @@ -0,0 +1,263 @@ +/*************************************************************************** + * + * Copyright (c) Microsoft Corporation. All rights reserved. + * + * File: audiodefs.h + * Content: Basic constants and data types for audio work. + * + * Remarks: This header file defines all of the audio format constants and + * structures required for XAudio2 and XACT work. Providing these + * in a single location avoids certain dependency problems in the + * legacy audio headers (mmreg.h, mmsystem.h, ksmedia.h). + * + * NOTE: Including the legacy headers after this one may cause a + * compilation error, because they define some of the same types + * defined here without preprocessor guards to avoid multiple + * definitions. If a source file needs one of the old headers, + * it must include it before including audiodefs.h. + * + ***************************************************************************/ + +#ifndef __AUDIODEFS_INCLUDED__ +#define __AUDIODEFS_INCLUDED__ + +#include // For WORD, DWORD, etc. + +#pragma pack(push, 1) // Pack structures to 1-byte boundaries + + +/************************************************************************** + * + * WAVEFORMATEX: Base structure for many audio formats. Format-specific + * extensions can be defined for particular formats by using a non-zero + * cbSize value and adding extra fields to the end of this structure. + * + ***************************************************************************/ + +#ifndef _WAVEFORMATEX_ + + #define _WAVEFORMATEX_ + typedef struct tWAVEFORMATEX + { + WORD wFormatTag; // Integer identifier of the format + WORD nChannels; // Number of audio channels + DWORD nSamplesPerSec; // Audio sample rate + DWORD nAvgBytesPerSec; // Bytes per second (possibly approximate) + WORD nBlockAlign; // Size in bytes of a sample block (all channels) + WORD wBitsPerSample; // Size in bits of a single per-channel sample + WORD cbSize; // Bytes of extra data appended to this struct + } WAVEFORMATEX; + +#endif + +// Defining pointer types outside of the #if block to make sure they are +// defined even if mmreg.h or mmsystem.h is #included before this file + +typedef WAVEFORMATEX *PWAVEFORMATEX, *NPWAVEFORMATEX, *LPWAVEFORMATEX; +typedef const WAVEFORMATEX *PCWAVEFORMATEX, *LPCWAVEFORMATEX; + + +/************************************************************************** + * + * WAVEFORMATEXTENSIBLE: Extended version of WAVEFORMATEX that should be + * used as a basis for all new audio formats. The format tag is replaced + * with a GUID, allowing new formats to be defined without registering a + * format tag with Microsoft. There are also new fields that can be used + * to specify the spatial positions for each channel and the bit packing + * used for wide samples (e.g. 24-bit PCM samples in 32-bit containers). + * + ***************************************************************************/ + +#ifndef _WAVEFORMATEXTENSIBLE_ + + #define _WAVEFORMATEXTENSIBLE_ + typedef struct + { + WAVEFORMATEX Format; // Base WAVEFORMATEX data + union + { + WORD wValidBitsPerSample; // Valid bits in each sample container + WORD wSamplesPerBlock; // Samples per block of audio data; valid + // if wBitsPerSample=0 (but rarely used). + WORD wReserved; // Zero if neither case above applies. + } Samples; + DWORD dwChannelMask; // Positions of the audio channels + GUID SubFormat; // Format identifier GUID + } WAVEFORMATEXTENSIBLE; + +#endif + +typedef WAVEFORMATEXTENSIBLE *PWAVEFORMATEXTENSIBLE, *LPWAVEFORMATEXTENSIBLE; +typedef const WAVEFORMATEXTENSIBLE *PCWAVEFORMATEXTENSIBLE, *LPCWAVEFORMATEXTENSIBLE; + + + +/************************************************************************** + * + * Define the most common wave format tags used in WAVEFORMATEX formats. + * + ***************************************************************************/ + +#ifndef WAVE_FORMAT_PCM // Pulse Code Modulation + + // If WAVE_FORMAT_PCM is not defined, we need to define some legacy types + // for compatibility with the Windows mmreg.h / mmsystem.h header files. + + // Old general format structure (information common to all formats) + typedef struct waveformat_tag + { + WORD wFormatTag; + WORD nChannels; + DWORD nSamplesPerSec; + DWORD nAvgBytesPerSec; + WORD nBlockAlign; + } WAVEFORMAT, *PWAVEFORMAT, NEAR *NPWAVEFORMAT, FAR *LPWAVEFORMAT; + + // Specific format structure for PCM data + typedef struct pcmwaveformat_tag + { + WAVEFORMAT wf; + WORD wBitsPerSample; + } PCMWAVEFORMAT, *PPCMWAVEFORMAT, NEAR *NPPCMWAVEFORMAT, FAR *LPPCMWAVEFORMAT; + + #define WAVE_FORMAT_PCM 0x0001 + +#endif + +#ifndef WAVE_FORMAT_ADPCM // Microsoft Adaptive Differental PCM + + // Replicate the Microsoft ADPCM type definitions from mmreg.h. + + typedef struct adpcmcoef_tag + { + short iCoef1; + short iCoef2; + } ADPCMCOEFSET; + + #pragma warning(push) + #pragma warning(disable:4200) // Disable zero-sized array warnings + + typedef struct adpcmwaveformat_tag { + WAVEFORMATEX wfx; + WORD wSamplesPerBlock; + WORD wNumCoef; + ADPCMCOEFSET aCoef[]; // Always 7 coefficient pairs for MS ADPCM + } ADPCMWAVEFORMAT; + + #pragma warning(pop) + + #define WAVE_FORMAT_ADPCM 0x0002 + +#endif + +// Other frequently used format tags + +#ifndef WAVE_FORMAT_UNKNOWN + #define WAVE_FORMAT_UNKNOWN 0x0000 // Unknown or invalid format tag +#endif + +#ifndef WAVE_FORMAT_IEEE_FLOAT + #define WAVE_FORMAT_IEEE_FLOAT 0x0003 // 32-bit floating-point +#endif + +#ifndef WAVE_FORMAT_MPEGLAYER3 + #define WAVE_FORMAT_MPEGLAYER3 0x0055 // ISO/MPEG Layer3 +#endif + +#ifndef WAVE_FORMAT_DOLBY_AC3_SPDIF + #define WAVE_FORMAT_DOLBY_AC3_SPDIF 0x0092 // Dolby Audio Codec 3 over S/PDIF +#endif + +#ifndef WAVE_FORMAT_WMAUDIO2 + #define WAVE_FORMAT_WMAUDIO2 0x0161 // Windows Media Audio +#endif + +#ifndef WAVE_FORMAT_WMAUDIO3 + #define WAVE_FORMAT_WMAUDIO3 0x0162 // Windows Media Audio Pro +#endif + +#ifndef WAVE_FORMAT_WMASPDIF + #define WAVE_FORMAT_WMASPDIF 0x0164 // Windows Media Audio over S/PDIF +#endif + +#ifndef WAVE_FORMAT_EXTENSIBLE + #define WAVE_FORMAT_EXTENSIBLE 0xFFFE // All WAVEFORMATEXTENSIBLE formats +#endif + + +/************************************************************************** + * + * Define the most common wave format GUIDs used in WAVEFORMATEXTENSIBLE + * formats. Note that including the Windows ksmedia.h header after this + * one will cause build problems; this cannot be avoided, since ksmedia.h + * defines these macros without preprocessor guards. + * + ***************************************************************************/ + +#ifdef __cplusplus // uuid() and __uuidof() are only available in C++ + + #ifndef KSDATAFORMAT_SUBTYPE_PCM + struct __declspec(uuid("00000001-0000-0010-8000-00aa00389b71")) KSDATAFORMAT_SUBTYPE_PCM_STRUCT; + #define KSDATAFORMAT_SUBTYPE_PCM __uuidof(KSDATAFORMAT_SUBTYPE_PCM_STRUCT) + #endif + + #ifndef KSDATAFORMAT_SUBTYPE_ADPCM + struct __declspec(uuid("00000002-0000-0010-8000-00aa00389b71")) KSDATAFORMAT_SUBTYPE_ADPCM_STRUCT; + #define KSDATAFORMAT_SUBTYPE_ADPCM __uuidof(KSDATAFORMAT_SUBTYPE_ADPCM_STRUCT) + #endif + + #ifndef KSDATAFORMAT_SUBTYPE_IEEE_FLOAT + struct __declspec(uuid("00000003-0000-0010-8000-00aa00389b71")) KSDATAFORMAT_SUBTYPE_IEEE_FLOAT_STRUCT; + #define KSDATAFORMAT_SUBTYPE_IEEE_FLOAT __uuidof(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT_STRUCT) + #endif + +#endif + + +/************************************************************************** + * + * Speaker positions used in the WAVEFORMATEXTENSIBLE dwChannelMask field. + * + ***************************************************************************/ + +#ifndef SPEAKER_FRONT_LEFT + #define SPEAKER_FRONT_LEFT 0x00000001 + #define SPEAKER_FRONT_RIGHT 0x00000002 + #define SPEAKER_FRONT_CENTER 0x00000004 + #define SPEAKER_LOW_FREQUENCY 0x00000008 + #define SPEAKER_BACK_LEFT 0x00000010 + #define SPEAKER_BACK_RIGHT 0x00000020 + #define SPEAKER_FRONT_LEFT_OF_CENTER 0x00000040 + #define SPEAKER_FRONT_RIGHT_OF_CENTER 0x00000080 + #define SPEAKER_BACK_CENTER 0x00000100 + #define SPEAKER_SIDE_LEFT 0x00000200 + #define SPEAKER_SIDE_RIGHT 0x00000400 + #define SPEAKER_TOP_CENTER 0x00000800 + #define SPEAKER_TOP_FRONT_LEFT 0x00001000 + #define SPEAKER_TOP_FRONT_CENTER 0x00002000 + #define SPEAKER_TOP_FRONT_RIGHT 0x00004000 + #define SPEAKER_TOP_BACK_LEFT 0x00008000 + #define SPEAKER_TOP_BACK_CENTER 0x00010000 + #define SPEAKER_TOP_BACK_RIGHT 0x00020000 + #define SPEAKER_RESERVED 0x7FFC0000 + #define SPEAKER_ALL 0x80000000 + #define _SPEAKER_POSITIONS_ +#endif + +#ifndef SPEAKER_STEREO + #define SPEAKER_MONO (SPEAKER_FRONT_CENTER) + #define SPEAKER_STEREO (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT) + #define SPEAKER_2POINT1 (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_LOW_FREQUENCY) + #define SPEAKER_SURROUND (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_BACK_CENTER) + #define SPEAKER_QUAD (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT) + #define SPEAKER_4POINT1 (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT) + #define SPEAKER_5POINT1 (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT) + #define SPEAKER_7POINT1 (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_FRONT_LEFT_OF_CENTER | SPEAKER_FRONT_RIGHT_OF_CENTER) + #define SPEAKER_5POINT1_SURROUND (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT) + #define SPEAKER_7POINT1_SURROUND (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT) +#endif + + +#pragma pack(pop) + +#endif // #ifndef __AUDIODEFS_INCLUDED__ diff --git a/src/audio/xaudio2_7/comdecl.h b/src/audio/xaudio2_7/comdecl.h new file mode 100644 index 00000000..2ae9a961 --- /dev/null +++ b/src/audio/xaudio2_7/comdecl.h @@ -0,0 +1,59 @@ +// comdecl.h: Macros to facilitate COM interface and GUID declarations. +// Copyright (c) Microsoft Corporation. All rights reserved. + +#ifndef _COMDECL_H_ +#define _COMDECL_H_ + +#ifndef _XBOX + #include // For standard COM interface macros +#else + #pragma warning(push) + #pragma warning(disable:4061) + #include // Required by xobjbase.h + #include // Special definitions for Xbox build + #pragma warning(pop) +#endif + +// The DEFINE_CLSID() and DEFINE_IID() macros defined below allow COM GUIDs to +// be declared and defined in such a way that clients can obtain the GUIDs using +// either the __uuidof() extension or the old-style CLSID_Foo / IID_IFoo names. +// If using the latter approach, the client can also choose whether to get the +// GUID definitions by defining the INITGUID preprocessor constant or by linking +// to a GUID library. This works in either C or C++. + +#ifdef __cplusplus + + #define DECLSPEC_UUID_WRAPPER(x) __declspec(uuid(#x)) + #ifdef INITGUID + + #define DEFINE_CLSID(className, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + class DECLSPEC_UUID_WRAPPER(l##-##w1##-##w2##-##b1##b2##-##b3##b4##b5##b6##b7##b8) className; \ + EXTERN_C const GUID DECLSPEC_SELECTANY CLSID_##className = __uuidof(className) + + #define DEFINE_IID(interfaceName, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + interface DECLSPEC_UUID_WRAPPER(l##-##w1##-##w2##-##b1##b2##-##b3##b4##b5##b6##b7##b8) interfaceName; \ + EXTERN_C const GUID DECLSPEC_SELECTANY IID_##interfaceName = __uuidof(interfaceName) + + #else // INITGUID + + #define DEFINE_CLSID(className, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + class DECLSPEC_UUID_WRAPPER(l##-##w1##-##w2##-##b1##b2##-##b3##b4##b5##b6##b7##b8) className; \ + EXTERN_C const GUID CLSID_##className + + #define DEFINE_IID(interfaceName, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + interface DECLSPEC_UUID_WRAPPER(l##-##w1##-##w2##-##b1##b2##-##b3##b4##b5##b6##b7##b8) interfaceName; \ + EXTERN_C const GUID IID_##interfaceName + + #endif // INITGUID + +#else // __cplusplus + + #define DEFINE_CLSID(className, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + DEFINE_GUID(CLSID_##className, 0x##l, 0x##w1, 0x##w2, 0x##b1, 0x##b2, 0x##b3, 0x##b4, 0x##b5, 0x##b6, 0x##b7, 0x##b8) + + #define DEFINE_IID(interfaceName, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + DEFINE_GUID(IID_##interfaceName, 0x##l, 0x##w1, 0x##w2, 0x##b1, 0x##b2, 0x##b3, 0x##b4, 0x##b5, 0x##b6, 0x##b7, 0x##b8) + +#endif // __cplusplus + +#endif // #ifndef _COMDECL_H_ diff --git a/src/audio/xaudio2_7/dxsdkver.h b/src/audio/xaudio2_7/dxsdkver.h new file mode 100644 index 00000000..7d88bbbb --- /dev/null +++ b/src/audio/xaudio2_7/dxsdkver.h @@ -0,0 +1,18 @@ +/*==========================================================================; + * + * + * File: dxsdkver.h + * Content: DirectX SDK Version Include File + * + ****************************************************************************/ + +#ifndef _DXSDKVER_H_ +#define _DXSDKVER_H_ + +#define _DXSDK_PRODUCT_MAJOR 9 +#define _DXSDK_PRODUCT_MINOR 29 +#define _DXSDK_BUILD_MAJOR 1962 +#define _DXSDK_BUILD_MINOR 0 + +#endif // _DXSDKVER_H_ + diff --git a/src/audio/xaudio2_7/xma2defs.h b/src/audio/xaudio2_7/xma2defs.h new file mode 100644 index 00000000..ce64a61a --- /dev/null +++ b/src/audio/xaudio2_7/xma2defs.h @@ -0,0 +1,718 @@ +/*************************************************************************** + * + * Copyright (c) Microsoft Corporation. All rights reserved. + * + * File: xma2defs.h + * Content: Constants, data types and functions for XMA2 compressed audio. + * + ***************************************************************************/ + +#ifndef __XMA2DEFS_INCLUDED__ +#define __XMA2DEFS_INCLUDED__ + +#include // Markers for documenting API semantics +#include // For S_OK, E_FAIL +#include "audiodefs.h" // Basic data types and constants for audio work + + +/*************************************************************************** + * Overview + ***************************************************************************/ + +// A typical XMA2 file contains these RIFF chunks: +// +// 'fmt' or 'XMA2' chunk (or both): A description of the XMA data's structure +// and characteristics (length, channels, sample rate, loops, block size, etc). +// +// 'seek' chunk: A seek table to help navigate the XMA data. +// +// 'data' chunk: The encoded XMA2 data. +// +// The encoded XMA2 data is structured as a set of BLOCKS, which contain PACKETS, +// which contain FRAMES, which contain SUBFRAMES (roughly speaking). The frames +// in a file may also be divided into several subsets, called STREAMS. +// +// FRAME: A variable-sized segment of XMA data that decodes to exactly 512 mono +// or stereo PCM samples. This is the smallest unit of XMA data that can +// be decoded in isolation. Frames are an arbitrary number of bits in +// length, and need not be byte-aligned. See "XMA frame structure" below. +// +// SUBFRAME: A region of bits in an XMA frame that decodes to 128 mono or stereo +// samples. The XMA decoder cannot decode a subframe in isolation; it needs +// a whole frame to work with. However, it can begin emitting the frame's +// decoded samples at any one of the four subframe boundaries. Subframes +// can be addressed for seeking and looping purposes. +// +// PACKET: A 2Kb region containing a 32-bit header and some XMA frames. Frames +// can (and usually do) span packets. A packet's header includes the offset +// in bits of the first frame that begins within that packet. All of the +// frames that begin in a given packet belong to the same "stream" (see the +// Multichannel Audio section below). +// +// STREAM: A set of packets within an XMA file that all contain data for the +// same mono or stereo component of a PCM file with more than two channels. +// The packets comprising a given stream may be interleaved with each other +// more or less arbitrarily; see Multichannel Audio. +// +// BLOCK: An array of XMA packets; or, to break it down differently, a series of +// consecutive XMA frames, padded at the end with reserved data. A block +// must contain at least one 2Kb packet per stream, and it can hold up to +// 4095 packets (8190Kb), but its size is typically in the 32Kb-128Kb range. +// (The size chosen involves a trade-off between memory use and efficiency +// of reading from permanent storage.) +// +// XMA frames do not span blocks, so a block is guaranteed to begin with a +// set of complete frames, one per stream. Also, a block in a multi-stream +// XMA2 file always contains the same number of samples for each stream; +// see Multichannel Audio. +// +// The 'data' chunk in an XMA2 file is an array of XMA2WAVEFORMAT.BlockCount XMA +// blocks, all the same size (as specified in XMA2WAVEFORMAT.BlockSizeInBytes) +// except for the last one, which may be shorter. + + +// MULTICHANNEL AUDIO: the XMA decoder can only decode raw XMA data into either +// mono or stereo PCM data. In order to encode a 6-channel file (say), the file +// must be deinterleaved into 3 stereo streams that are encoded independently, +// producing 3 encoded XMA data streams. Then the packets in these 3 streams +// are interleaved to produce a single XMA2 file, and some information is added +// to the file so that the original 6-channel audio can be reconstructed at +// decode time. This works using the concept of an XMA stream (see above). +// +// The frames for all the streams in an XMA file are interleaved in an arbitrary +// order. To locate a frame that belongs to a given stream in a given XMA block, +// you must examine the first few packets in the block. Here (and only here) the +// packets are guaranteed to be presented in stream order, so that all frames +// beginning in packet 0 belong to stream 0 (the first stereo pair), etc. +// +// (This means that when decoding multi-stream XMA files, only entire XMA blocks +// should be submitted to the decoder; otherwise it cannot know which frames +// belong to which stream.) +// +// Once you have one frame that belongs to a given stream, you can find the next +// one by looking at the frame's 'NextFrameOffsetBits' value (which is stored in +// its first 15 bits; see XMAFRAME below). The GetXmaFrameBitPosition function +// uses this technique. + + +// SEEKING IN XMA2 FILES: Here is some pseudocode to find the byte position and +// subframe in an XMA2 file which will contain sample S when decoded. +// +// 1. Traverse the seek table to find the XMA2 block containing sample S. The +// seek table is an array of big-endian DWORDs, one per block in the file. +// The Nth DWORD is the total number of PCM samples that would be obtained +// by decoding the entire XMA file up to the end of block N. Hence, the +// block we want is the first one whose seek table entry is greater than S. +// (See the GetXmaBlockContainingSample helper function.) +// +// 2. Calculate which frame F within the block found above contains sample S. +// Since each frame decodes to 512 samples, this is straightforward. The +// first frame in the block produces samples X to X + 512, where X is the +// seek table entry for the prior block. So F is (S - X) / 512. +// +// 3. Find the bit offset within the block where frame F starts. Since frames +// are variable-sized, this can only be done by traversing all the frames in +// the block until we reach frame F. (See GetXmaFrameBitPosition.) +// +// 4. Frame F has four 128-sample subframes. To find the subframe containing S, +// we can use the formula (S % 512) / 128. +// +// In the case of multi-stream XMA files, sample S is a multichannel sample with +// parts coming from several frames, one per stream. To find all these frames, +// steps 2-4 need to be repeated for each stream N, using the knowledge that the +// first packets in a block are presented in stream order. The frame traversal +// in step 3 must be started at the first frame in the Nth packet of the block, +// which will be the first frame for stream N. (And the packet header will tell +// you the first frame's start position within the packet.) +// +// Step 1 can be performed using the GetXmaBlockContainingSample function below, +// and steps 2-4 by calling GetXmaDecodePositionForSample once for each stream. + + + +/*************************************************************************** + * XMA constants + ***************************************************************************/ + +// Size of the PCM samples produced by the XMA decoder +#define XMA_OUTPUT_SAMPLE_BYTES 2u +#define XMA_OUTPUT_SAMPLE_BITS (XMA_OUTPUT_SAMPLE_BYTES * 8u) + +// Size of an XMA packet +#define XMA_BYTES_PER_PACKET 2048u +#define XMA_BITS_PER_PACKET (XMA_BYTES_PER_PACKET * 8u) + +// Size of an XMA packet header +#define XMA_PACKET_HEADER_BYTES 4u +#define XMA_PACKET_HEADER_BITS (XMA_PACKET_HEADER_BYTES * 8u) + +// Sample blocks in a decoded XMA frame +#define XMA_SAMPLES_PER_FRAME 512u + +// Sample blocks in a decoded XMA subframe +#define XMA_SAMPLES_PER_SUBFRAME 128u + +// Maximum encoded data that can be submitted to the XMA decoder at a time +#define XMA_READBUFFER_MAX_PACKETS 4095u +#define XMA_READBUFFER_MAX_BYTES (XMA_READBUFFER_MAX_PACKETS * XMA_BYTES_PER_PACKET) + +// Maximum size allowed for the XMA decoder's output buffers +#define XMA_WRITEBUFFER_MAX_BYTES (31u * 256u) + +// Required byte alignment of the XMA decoder's output buffers +#define XMA_WRITEBUFFER_BYTE_ALIGNMENT 256u + +// Decode chunk sizes for the XMA_PLAYBACK_INIT.subframesToDecode field +#define XMA_MIN_SUBFRAMES_TO_DECODE 1u +#define XMA_MAX_SUBFRAMES_TO_DECODE 8u +#define XMA_OPTIMAL_SUBFRAMES_TO_DECODE 4u + +// LoopCount<255 means finite repetitions; LoopCount=255 means infinite looping +#define XMA_MAX_LOOPCOUNT 254u +#define XMA_INFINITE_LOOP 255u + + + +/*************************************************************************** + * XMA format structures + ***************************************************************************/ + +// The currently recommended way to express format information for XMA2 files +// is the XMA2WAVEFORMATEX structure. This structure is fully compliant with +// the WAVEFORMATEX standard and contains all the information needed to parse +// and manage XMA2 files in a compact way. + +#define WAVE_FORMAT_XMA2 0x166 + +typedef struct XMA2WAVEFORMATEX +{ + WAVEFORMATEX wfx; + // Meaning of the WAVEFORMATEX fields here: + // wFormatTag; // Audio format type; always WAVE_FORMAT_XMA2 + // nChannels; // Channel count of the decoded audio + // nSamplesPerSec; // Sample rate of the decoded audio + // nAvgBytesPerSec; // Used internally by the XMA encoder + // nBlockAlign; // Decoded sample size; channels * wBitsPerSample / 8 + // wBitsPerSample; // Bits per decoded mono sample; always 16 for XMA + // cbSize; // Size in bytes of the rest of this structure (34) + + WORD NumStreams; // Number of audio streams (1 or 2 channels each) + DWORD ChannelMask; // Spatial positions of the channels in this file, + // stored as SPEAKER_xxx values (see audiodefs.h) + DWORD SamplesEncoded; // Total number of PCM samples the file decodes to + DWORD BytesPerBlock; // XMA block size (but the last one may be shorter) + DWORD PlayBegin; // First valid sample in the decoded audio + DWORD PlayLength; // Length of the valid part of the decoded audio + DWORD LoopBegin; // Beginning of the loop region in decoded sample terms + DWORD LoopLength; // Length of the loop region in decoded sample terms + BYTE LoopCount; // Number of loop repetitions; 255 = infinite + BYTE EncoderVersion; // Version of XMA encoder that generated the file + WORD BlockCount; // XMA blocks in file (and entries in its seek table) +} XMA2WAVEFORMATEX, *PXMA2WAVEFORMATEX; + + +// The legacy XMA format structures are described here for reference, but they +// should not be used in new content. XMAWAVEFORMAT was the structure used in +// XMA version 1 files. XMA2WAVEFORMAT was used in early XMA2 files; it is not +// placed in the usual 'fmt' RIFF chunk but in its own 'XMA2' chunk. + +#ifndef WAVE_FORMAT_XMA +#define WAVE_FORMAT_XMA 0x0165 + +// Values used in the ChannelMask fields below. Similar to the SPEAKER_xxx +// values defined in audiodefs.h, but modified to fit in a single byte. +#ifndef XMA_SPEAKER_LEFT + #define XMA_SPEAKER_LEFT 0x01 + #define XMA_SPEAKER_RIGHT 0x02 + #define XMA_SPEAKER_CENTER 0x04 + #define XMA_SPEAKER_LFE 0x08 + #define XMA_SPEAKER_LEFT_SURROUND 0x10 + #define XMA_SPEAKER_RIGHT_SURROUND 0x20 + #define XMA_SPEAKER_LEFT_BACK 0x40 + #define XMA_SPEAKER_RIGHT_BACK 0x80 +#endif + + +// Used in XMAWAVEFORMAT for per-stream data +typedef struct XMASTREAMFORMAT +{ + DWORD PsuedoBytesPerSec; // Used by the XMA encoder (typo preserved for legacy reasons) + DWORD SampleRate; // The stream's decoded sample rate (in XMA2 files, + // this is the same for all streams in the file). + DWORD LoopStart; // Bit offset of the frame containing the loop start + // point, relative to the beginning of the stream. + DWORD LoopEnd; // Bit offset of the frame containing the loop end. + BYTE SubframeData; // Two 4-bit numbers specifying the exact location of + // the loop points within the frames that contain them. + // SubframeEnd: Subframe of the loop end frame where + // the loop ends. Ranges from 0 to 3. + // SubframeSkip: Subframes to skip in the start frame to + // reach the loop. Ranges from 0 to 4. + BYTE Channels; // Number of channels in the stream (1 or 2) + WORD ChannelMask; // Spatial positions of the channels in the stream +} XMASTREAMFORMAT; + +// Legacy XMA1 format structure +typedef struct XMAWAVEFORMAT +{ + WORD FormatTag; // Audio format type (always WAVE_FORMAT_XMA) + WORD BitsPerSample; // Bit depth (currently required to be 16) + WORD EncodeOptions; // Options for XMA encoder/decoder + WORD LargestSkip; // Largest skip used in interleaving streams + WORD NumStreams; // Number of interleaved audio streams + BYTE LoopCount; // Number of loop repetitions; 255 = infinite + BYTE Version; // XMA encoder version that generated the file. + // Always 3 or higher for XMA2 files. + XMASTREAMFORMAT XmaStreams[1]; // Per-stream format information; the actual + // array length is in the NumStreams field. +} XMAWAVEFORMAT; + + +// Used in XMA2WAVEFORMAT for per-stream data +typedef struct XMA2STREAMFORMAT +{ + BYTE Channels; // Number of channels in the stream (1 or 2) + BYTE RESERVED; // Reserved for future use + WORD ChannelMask; // Spatial positions of the channels in the stream +} XMA2STREAMFORMAT; + +// Legacy XMA2 format structure (big-endian byte ordering) +typedef struct XMA2WAVEFORMAT +{ + BYTE Version; // XMA encoder version that generated the file. + // Always 3 or higher for XMA2 files. + BYTE NumStreams; // Number of interleaved audio streams + BYTE RESERVED; // Reserved for future use + BYTE LoopCount; // Number of loop repetitions; 255 = infinite + DWORD LoopBegin; // Loop begin point, in samples + DWORD LoopEnd; // Loop end point, in samples + DWORD SampleRate; // The file's decoded sample rate + DWORD EncodeOptions; // Options for the XMA encoder/decoder + DWORD PsuedoBytesPerSec; // Used internally by the XMA encoder + DWORD BlockSizeInBytes; // Size in bytes of this file's XMA blocks (except + // possibly the last one). Always a multiple of + // 2Kb, since XMA blocks are arrays of 2Kb packets. + DWORD SamplesEncoded; // Total number of PCM samples encoded in this file + DWORD SamplesInSource; // Actual number of PCM samples in the source + // material used to generate this file + DWORD BlockCount; // Number of XMA blocks in this file (and hence + // also the number of entries in its seek table) + XMA2STREAMFORMAT Streams[1]; // Per-stream format information; the actual + // array length is in the NumStreams field. +} XMA2WAVEFORMAT; + +#endif // #ifndef WAVE_FORMAT_XMA + + + +/*************************************************************************** + * XMA packet structure (in big-endian form) + ***************************************************************************/ + +typedef struct XMA2PACKET +{ + int FrameCount : 6; // Number of XMA frames that begin in this packet + int FrameOffsetInBits : 15; // Bit of XmaData where the first complete frame begins + int PacketMetaData : 3; // Metadata stored in the packet (always 1 for XMA2) + int PacketSkipCount : 8; // How many packets belonging to other streams must be + // skipped to find the next packet belonging to this one + BYTE XmaData[XMA_BYTES_PER_PACKET - sizeof(DWORD)]; // XMA encoded data +} XMA2PACKET; + +// E.g. if the first DWORD of a packet is 0x30107902: +// +// 001100 000001000001111 001 00000010 +// | | | |____ Skip 2 packets to find the next one for this stream +// | | |___________ XMA2 signature (always 001) +// | |_____________________ First frame starts 527 bits into packet +// |________________________________ Packet contains 12 frames + + +// Helper functions to extract the fields above from an XMA packet. (Note that +// the bitfields cannot be read directly on little-endian architectures such as +// the Intel x86, as they are laid out in big-endian form.) + +__inline DWORD GetXmaPacketFrameCount(__in_bcount(1) const BYTE* pPacket) +{ + return (DWORD)(pPacket[0] >> 2); +} + +__inline DWORD GetXmaPacketFirstFrameOffsetInBits(__in_bcount(3) const BYTE* pPacket) +{ + return ((DWORD)(pPacket[0] & 0x3) << 13) | + ((DWORD)(pPacket[1]) << 5) | + ((DWORD)(pPacket[2]) >> 3); +} + +__inline DWORD GetXmaPacketMetadata(__in_bcount(3) const BYTE* pPacket) +{ + return (DWORD)(pPacket[2] & 0x7); +} + +__inline DWORD GetXmaPacketSkipCount(__in_bcount(4) const BYTE* pPacket) +{ + return (DWORD)(pPacket[3]); +} + + + +/*************************************************************************** + * XMA frame structure + ***************************************************************************/ + +// There is no way to represent the XMA frame as a C struct, since it is a +// variable-sized string of bits that need not be stored at a byte-aligned +// position in memory. This is the layout: +// +// XMAFRAME +// { +// LengthInBits: A 15-bit number representing the length of this frame. +// XmaData: Encoded XMA data; its size in bits is (LengthInBits - 15). +// } + +// Size in bits of the frame's initial LengthInBits field +#define XMA_BITS_IN_FRAME_LENGTH_FIELD 15 + +// Special LengthInBits value that marks an invalid final frame +#define XMA_FINAL_FRAME_MARKER 0x7FFF + + + +/*************************************************************************** + * XMA helper functions + ***************************************************************************/ + +// We define a local ASSERT macro to equal the global one if it exists. +// You can define XMA2DEFS_ASSERT in advance to override this default. +#ifndef XMA2DEFS_ASSERT + #ifdef ASSERT + #define XMA2DEFS_ASSERT ASSERT + #else + #define XMA2DEFS_ASSERT(a) /* No-op by default */ + #endif +#endif + + +// GetXmaBlockContainingSample: Use a given seek table to find the XMA block +// containing a given decoded sample. Note that the seek table entries in an +// XMA file are stored in big-endian form and may need to be converted prior +// to calling this function. + +__inline HRESULT GetXmaBlockContainingSample +( + DWORD nBlockCount, // Blocks in the file (= seek table entries) + __in_ecount(nBlockCount) const DWORD* pSeekTable, // Pointer to the seek table data + DWORD nDesiredSample, // Decoded sample to locate + __out DWORD* pnBlockContainingSample, // Index of the block containing the sample + __out DWORD* pnSampleOffsetWithinBlock // Position of the sample in this block +) +{ + DWORD nPreviousTotalSamples = 0; + DWORD nBlock; + DWORD nTotalSamplesSoFar; + + XMA2DEFS_ASSERT(pSeekTable); + XMA2DEFS_ASSERT(pnBlockContainingSample); + XMA2DEFS_ASSERT(pnSampleOffsetWithinBlock); + + for (nBlock = 0; nBlock < nBlockCount; ++nBlock) + { + nTotalSamplesSoFar = pSeekTable[nBlock]; + if (nTotalSamplesSoFar > nDesiredSample) + { + *pnBlockContainingSample = nBlock; + *pnSampleOffsetWithinBlock = nDesiredSample - nPreviousTotalSamples; + return S_OK; + } + nPreviousTotalSamples = nTotalSamplesSoFar; + } + + return E_FAIL; +} + + +// GetXmaFrameLengthInBits: Reads a given frame's LengthInBits field. + +__inline DWORD GetXmaFrameLengthInBits +( + __in_bcount(nBitPosition / 8 + 3) + __in const BYTE* pPacket, // Pointer to XMA packet[s] containing the frame + DWORD nBitPosition // Bit offset of the frame within this packet +) +{ + DWORD nRegion; + DWORD nBytePosition = nBitPosition / 8; + DWORD nBitOffset = nBitPosition % 8; + + if (nBitOffset < 2) // Only need to read 2 bytes (and might not be safe to read more) + { + nRegion = (DWORD)(pPacket[nBytePosition+0]) << 8 | + (DWORD)(pPacket[nBytePosition+1]); + return (nRegion >> (1 - nBitOffset)) & 0x7FFF; // Last 15 bits + } + else // Need to read 3 bytes + { + nRegion = (DWORD)(pPacket[nBytePosition+0]) << 16 | + (DWORD)(pPacket[nBytePosition+1]) << 8 | + (DWORD)(pPacket[nBytePosition+2]); + return (nRegion >> (9 - nBitOffset)) & 0x7FFF; // Last 15 bits + } +} + + +// GetXmaFrameBitPosition: Calculates the bit offset of a given frame within +// an XMA block or set of blocks. Returns 0 on failure. + +__inline DWORD GetXmaFrameBitPosition +( + __in_bcount(nXmaDataBytes) const BYTE* pXmaData, // Pointer to XMA block[s] + DWORD nXmaDataBytes, // Size of pXmaData in bytes + DWORD nStreamIndex, // Stream within which to seek + DWORD nDesiredFrame // Frame sought +) +{ + const BYTE* pCurrentPacket; + DWORD nPacketsExamined = 0; + DWORD nFrameCountSoFar = 0; + DWORD nFramesToSkip; + DWORD nFrameBitOffset; + + XMA2DEFS_ASSERT(pXmaData); + XMA2DEFS_ASSERT(nXmaDataBytes % XMA_BYTES_PER_PACKET == 0); + + // Get the first XMA packet belonging to the desired stream, relying on the + // fact that the first packets for each stream are in consecutive order at + // the beginning of an XMA block. + + pCurrentPacket = pXmaData + nStreamIndex * XMA_BYTES_PER_PACKET; + for (;;) + { + // If we have exceeded the size of the XMA data, return failure + if (pCurrentPacket + XMA_BYTES_PER_PACKET > pXmaData + nXmaDataBytes) + { + return 0; + } + + // If the current packet contains the frame we are looking for... + if (nFrameCountSoFar + GetXmaPacketFrameCount(pCurrentPacket) > nDesiredFrame) + { + // See how many frames in this packet we need to skip to get to it + XMA2DEFS_ASSERT(nDesiredFrame >= nFrameCountSoFar); + nFramesToSkip = nDesiredFrame - nFrameCountSoFar; + + // Get the bit offset of the first frame in this packet + nFrameBitOffset = XMA_PACKET_HEADER_BITS + GetXmaPacketFirstFrameOffsetInBits(pCurrentPacket); + + // Advance nFrameBitOffset to the frame of interest + while (nFramesToSkip--) + { + nFrameBitOffset += GetXmaFrameLengthInBits(pCurrentPacket, nFrameBitOffset); + } + + // The bit offset to return is the number of bits from pXmaData to + // pCurrentPacket plus the bit offset of the frame of interest + return (DWORD)(pCurrentPacket - pXmaData) * 8 + nFrameBitOffset; + } + + // If we haven't found the right packet yet, advance our counters + ++nPacketsExamined; + nFrameCountSoFar += GetXmaPacketFrameCount(pCurrentPacket); + + // And skip to the next packet belonging to the same stream + pCurrentPacket += XMA_BYTES_PER_PACKET * (GetXmaPacketSkipCount(pCurrentPacket) + 1); + } +} + + +// GetLastXmaFrameBitPosition: Calculates the bit offset of the last complete +// frame in an XMA block or set of blocks. + +__inline DWORD GetLastXmaFrameBitPosition +( + __in_bcount(nXmaDataBytes) const BYTE* pXmaData, // Pointer to XMA block[s] + DWORD nXmaDataBytes, // Size of pXmaData in bytes + DWORD nStreamIndex // Stream within which to seek +) +{ + const BYTE* pLastPacket; + DWORD nBytesToNextPacket; + DWORD nFrameBitOffset; + DWORD nFramesInLastPacket; + + XMA2DEFS_ASSERT(pXmaData); + XMA2DEFS_ASSERT(nXmaDataBytes % XMA_BYTES_PER_PACKET == 0); + XMA2DEFS_ASSERT(nXmaDataBytes >= XMA_BYTES_PER_PACKET * (nStreamIndex + 1)); + + // Get the first XMA packet belonging to the desired stream, relying on the + // fact that the first packets for each stream are in consecutive order at + // the beginning of an XMA block. + pLastPacket = pXmaData + nStreamIndex * XMA_BYTES_PER_PACKET; + + // Search for the last packet belonging to the desired stream + for (;;) + { + nBytesToNextPacket = XMA_BYTES_PER_PACKET * (GetXmaPacketSkipCount(pLastPacket) + 1); + XMA2DEFS_ASSERT(nBytesToNextPacket); + if (pLastPacket + nBytesToNextPacket + XMA_BYTES_PER_PACKET > pXmaData + nXmaDataBytes) + { + break; // The next packet would extend beyond the end of pXmaData + } + pLastPacket += nBytesToNextPacket; + } + + // The last packet can sometimes have no seekable frames, in which case we + // have to use the previous one + if (GetXmaPacketFrameCount(pLastPacket) == 0) + { + pLastPacket -= nBytesToNextPacket; + } + + // Found the last packet. Get the bit offset of its first frame. + nFrameBitOffset = XMA_PACKET_HEADER_BITS + GetXmaPacketFirstFrameOffsetInBits(pLastPacket); + + // Traverse frames until we reach the last one + nFramesInLastPacket = GetXmaPacketFrameCount(pLastPacket); + while (--nFramesInLastPacket) + { + nFrameBitOffset += GetXmaFrameLengthInBits(pLastPacket, nFrameBitOffset); + } + + // The bit offset to return is the number of bits from pXmaData to + // pLastPacket plus the offset of the last frame in this packet. + return (DWORD)(pLastPacket - pXmaData) * 8 + nFrameBitOffset; +} + + +// GetXmaDecodePositionForSample: Obtains the information needed to make the +// decoder generate audio starting at a given sample position relative to the +// beginning of the given XMA block: the bit offset of the appropriate frame, +// and the right subframe within that frame. This data can be passed directly +// to the XMAPlaybackSetDecodePosition function. + +__inline HRESULT GetXmaDecodePositionForSample +( + __in_bcount(nXmaDataBytes) const BYTE* pXmaData, // Pointer to XMA block[s] + DWORD nXmaDataBytes, // Size of pXmaData in bytes + DWORD nStreamIndex, // Stream within which to seek + DWORD nDesiredSample, // Sample sought + __out DWORD* pnBitOffset, // Returns the bit offset within pXmaData of + // the frame containing the sample sought + __out DWORD* pnSubFrame // Returns the subframe containing the sample +) +{ + DWORD nDesiredFrame = nDesiredSample / XMA_SAMPLES_PER_FRAME; + DWORD nSubFrame = (nDesiredSample % XMA_SAMPLES_PER_FRAME) / XMA_SAMPLES_PER_SUBFRAME; + DWORD nBitOffset = GetXmaFrameBitPosition(pXmaData, nXmaDataBytes, nStreamIndex, nDesiredFrame); + + XMA2DEFS_ASSERT(pnBitOffset); + XMA2DEFS_ASSERT(pnSubFrame); + + if (nBitOffset) + { + *pnBitOffset = nBitOffset; + *pnSubFrame = nSubFrame; + return S_OK; + } + else + { + return E_FAIL; + } +} + + +// GetXmaSampleRate: Obtains the legal XMA sample rate (24, 32, 44.1 or 48Khz) +// corresponding to a generic sample rate. + +__inline DWORD GetXmaSampleRate(DWORD dwGeneralRate) +{ + DWORD dwXmaRate = 48000; // Default XMA rate for all rates above 44100Hz + + if (dwGeneralRate <= 24000) dwXmaRate = 24000; + else if (dwGeneralRate <= 32000) dwXmaRate = 32000; + else if (dwGeneralRate <= 44100) dwXmaRate = 44100; + + return dwXmaRate; +} + + +// Functions to convert between WAVEFORMATEXTENSIBLE channel masks (combinations +// of the SPEAKER_xxx flags defined in audiodefs.h) and XMA channel masks (which +// are limited to eight possible speaker positions: left, right, center, low +// frequency, side left, side right, back left and back right). + +__inline DWORD GetStandardChannelMaskFromXmaMask(BYTE bXmaMask) +{ + DWORD dwStandardMask = 0; + + if (bXmaMask & XMA_SPEAKER_LEFT) dwStandardMask |= SPEAKER_FRONT_LEFT; + if (bXmaMask & XMA_SPEAKER_RIGHT) dwStandardMask |= SPEAKER_FRONT_RIGHT; + if (bXmaMask & XMA_SPEAKER_CENTER) dwStandardMask |= SPEAKER_FRONT_CENTER; + if (bXmaMask & XMA_SPEAKER_LFE) dwStandardMask |= SPEAKER_LOW_FREQUENCY; + if (bXmaMask & XMA_SPEAKER_LEFT_SURROUND) dwStandardMask |= SPEAKER_SIDE_LEFT; + if (bXmaMask & XMA_SPEAKER_RIGHT_SURROUND) dwStandardMask |= SPEAKER_SIDE_RIGHT; + if (bXmaMask & XMA_SPEAKER_LEFT_BACK) dwStandardMask |= SPEAKER_BACK_LEFT; + if (bXmaMask & XMA_SPEAKER_RIGHT_BACK) dwStandardMask |= SPEAKER_BACK_RIGHT; + + return dwStandardMask; +} + +__inline BYTE GetXmaChannelMaskFromStandardMask(DWORD dwStandardMask) +{ + BYTE bXmaMask = 0; + + if (dwStandardMask & SPEAKER_FRONT_LEFT) bXmaMask |= XMA_SPEAKER_LEFT; + if (dwStandardMask & SPEAKER_FRONT_RIGHT) bXmaMask |= XMA_SPEAKER_RIGHT; + if (dwStandardMask & SPEAKER_FRONT_CENTER) bXmaMask |= XMA_SPEAKER_CENTER; + if (dwStandardMask & SPEAKER_LOW_FREQUENCY) bXmaMask |= XMA_SPEAKER_LFE; + if (dwStandardMask & SPEAKER_SIDE_LEFT) bXmaMask |= XMA_SPEAKER_LEFT_SURROUND; + if (dwStandardMask & SPEAKER_SIDE_RIGHT) bXmaMask |= XMA_SPEAKER_RIGHT_SURROUND; + if (dwStandardMask & SPEAKER_BACK_LEFT) bXmaMask |= XMA_SPEAKER_LEFT_BACK; + if (dwStandardMask & SPEAKER_BACK_RIGHT) bXmaMask |= XMA_SPEAKER_RIGHT_BACK; + + return bXmaMask; +} + + +// LocalizeXma2Format: Modifies a XMA2WAVEFORMATEX structure in place to comply +// with the current platform's byte-ordering rules (little- or big-endian). + +__inline HRESULT LocalizeXma2Format(__inout XMA2WAVEFORMATEX* pXma2Format) +{ + #define XMASWAP2BYTES(n) ((WORD)(((n) >> 8) | (((n) & 0xff) << 8))) + #define XMASWAP4BYTES(n) ((DWORD)((n) >> 24 | (n) << 24 | ((n) & 0xff00) << 8 | ((n) & 0xff0000) >> 8)) + + if (pXma2Format->wfx.wFormatTag == WAVE_FORMAT_XMA2) + { + return S_OK; + } + else if (XMASWAP2BYTES(pXma2Format->wfx.wFormatTag) == WAVE_FORMAT_XMA2) + { + pXma2Format->wfx.wFormatTag = XMASWAP2BYTES(pXma2Format->wfx.wFormatTag); + pXma2Format->wfx.nChannels = XMASWAP2BYTES(pXma2Format->wfx.nChannels); + pXma2Format->wfx.nSamplesPerSec = XMASWAP4BYTES(pXma2Format->wfx.nSamplesPerSec); + pXma2Format->wfx.nAvgBytesPerSec = XMASWAP4BYTES(pXma2Format->wfx.nAvgBytesPerSec); + pXma2Format->wfx.nBlockAlign = XMASWAP2BYTES(pXma2Format->wfx.nBlockAlign); + pXma2Format->wfx.wBitsPerSample = XMASWAP2BYTES(pXma2Format->wfx.wBitsPerSample); + pXma2Format->wfx.cbSize = XMASWAP2BYTES(pXma2Format->wfx.cbSize); + pXma2Format->NumStreams = XMASWAP2BYTES(pXma2Format->NumStreams); + pXma2Format->ChannelMask = XMASWAP4BYTES(pXma2Format->ChannelMask); + pXma2Format->SamplesEncoded = XMASWAP4BYTES(pXma2Format->SamplesEncoded); + pXma2Format->BytesPerBlock = XMASWAP4BYTES(pXma2Format->BytesPerBlock); + pXma2Format->PlayBegin = XMASWAP4BYTES(pXma2Format->PlayBegin); + pXma2Format->PlayLength = XMASWAP4BYTES(pXma2Format->PlayLength); + pXma2Format->LoopBegin = XMASWAP4BYTES(pXma2Format->LoopBegin); + pXma2Format->LoopLength = XMASWAP4BYTES(pXma2Format->LoopLength); + pXma2Format->BlockCount = XMASWAP2BYTES(pXma2Format->BlockCount); + return S_OK; + } + else + { + return E_FAIL; // Not a recognizable XMA2 format + } + + #undef XMASWAP2BYTES + #undef XMASWAP4BYTES +} + + +#endif // #ifndef __XMA2DEFS_INCLUDED__ diff --git a/src/cemuhook/CMakeLists.txt b/src/cemuhook/CMakeLists.txt new file mode 100644 index 00000000..b7f6b5a1 --- /dev/null +++ b/src/cemuhook/CMakeLists.txt @@ -0,0 +1,12 @@ +project(CemuCemuhook) + +add_library(CemuCemuhook +wxEvtHook.inl +wxCemuhookExports.cpp +) + +set_property(TARGET CemuCemuhook PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + +target_precompile_headers(CemuCemuhook PRIVATE ../Common/precompiled.h) + +target_include_directories(CemuCemuhook PRIVATE ../) \ No newline at end of file diff --git a/src/cemuhook/wxCemuhookExports.cpp b/src/cemuhook/wxCemuhookExports.cpp new file mode 100644 index 00000000..d39e22dc --- /dev/null +++ b/src/cemuhook/wxCemuhookExports.cpp @@ -0,0 +1,196 @@ + +#ifdef USE_CEMUHOOK + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CHECK_FOR_WX_EVT_STRING(strVar, strConst) if (strcmp(strVar, #strConst) == 0){ return static_cast(strConst); } + + +__declspec(dllexport) wxEvtHandler* wxEvtHandler_Initialize(uint8_t* allocMemory) +{ + wxEvtHandler* handler = new (allocMemory) wxEvtHandler(); + return handler; +} + +__declspec(dllexport) void wxEvtHandler_Connect(wxEvtHandler* eventSource, int id, int lastId, int eventType, wxObjectEventFunction func, wxObject* userData, wxEvtHandler* eventSink) +{ + eventSource->Connect(id, lastId, eventType, func, userData, eventSink); +} + +__declspec(dllexport) void wxEvtHandler_Disconnect(wxEvtHandler* eventSource, int id, int lastId, int eventType, wxObjectEventFunction func, wxObject* userData, wxEvtHandler* eventSink) +{ + eventSource->Disconnect(id, lastId, eventType, func, userData, eventSink); +} + +__declspec(dllexport) const wchar_t* GetTranslationWChar(const wchar_t* text) +{ + return wxGetTranslation(text).wc_str(); +} + +__declspec(dllexport) int wxGetEventByName(const char* eventName) +{ +#define PROCESS_OWN_WXEVT(EventVarName,EventHookId) if (!strcmp(eventName,#EventVarName)){ return static_cast(EventVarName); } +#include "wxEvtHook.inl" +#undef PROCESS_OWN_WXEVT + + return -1; +} + +#pragma optimize( "", off ) + +static bool FixupWxEvtId(const wxEventType& outObj, const int newId) +{ + const int oldVal = static_cast(outObj); + if (oldVal == newId) + return false; + + wxEventType* dstObj = const_cast(&outObj); + memcpy(dstObj, &newId, sizeof(newId)); + + // check value again + if (static_cast(outObj) != newId) + assert_dbg(); + + return true; +} + +void FixupWxEvtIdsToMatchCemuHook() +{ + // instantiate all the events +#define PROCESS_OWN_WXEVT(EventVarName,EventHookId) static_cast(EventVarName); +#include "cemuhook/wxEvtHook.inl" +#undef PROCESS_OWN_WXEVT + // fix them +#define PROCESS_OWN_WXEVT(EventVarName,EventHookId) FixupWxEvtId(EventVarName,EventHookId) +#include "cemuhook/wxEvtHook.inl" +#undef PROCESS_OWN_WXEVT +} + +#pragma optimize( "", on ) + +// these I added on my own since they might be useful + +__declspec(dllexport) void coreinitAPI_OSYieldThread() +{ + PPCCore_switchToScheduler(); +} + +#define xstr(a) str(a) +#define str(a) #a + +#define PRINT_EVENT(__evtName) printf(xstr(__evtName) " = %d\n", (int)(wxEventType)__evtName); + +void PrintEvents() +{ + PRINT_EVENT(wxEVT_IDLE) + PRINT_EVENT(wxEVT_THREAD) + PRINT_EVENT(wxEVT_ASYNC_METHOD_CALL) + + PRINT_EVENT(wxEVT_BUTTON) + PRINT_EVENT(wxEVT_CHECKBOX) + PRINT_EVENT(wxEVT_CHOICE) + PRINT_EVENT(wxEVT_LISTBOX) + PRINT_EVENT(wxEVT_LISTBOX_DCLICK) + PRINT_EVENT(wxEVT_CHECKLISTBOX) + PRINT_EVENT(wxEVT_MENU) + PRINT_EVENT(wxEVT_SLIDER) + PRINT_EVENT(wxEVT_RADIOBOX) + PRINT_EVENT(wxEVT_RADIOBUTTON) + PRINT_EVENT(wxEVT_SCROLLBAR) + + PRINT_EVENT(wxEVT_LEFT_DOWN) + PRINT_EVENT(wxEVT_LEFT_UP) + PRINT_EVENT(wxEVT_MIDDLE_DOWN) + PRINT_EVENT(wxEVT_MIDDLE_UP) + PRINT_EVENT(wxEVT_RIGHT_DOWN) + PRINT_EVENT(wxEVT_RIGHT_UP) + PRINT_EVENT(wxEVT_MOTION) + PRINT_EVENT(wxEVT_ENTER_WINDOW) + PRINT_EVENT(wxEVT_LEAVE_WINDOW) + + PRINT_EVENT(wxEVT_CHAR) + PRINT_EVENT(wxEVT_SET_CURSOR) + PRINT_EVENT(wxEVT_SCROLL_TOP) + PRINT_EVENT(wxEVT_SCROLL_BOTTOM) + + PRINT_EVENT(wxEVT_SIZE) + PRINT_EVENT(wxEVT_MOVE) + PRINT_EVENT(wxEVT_CLOSE_WINDOW) + PRINT_EVENT(wxEVT_END_SESSION) + PRINT_EVENT(wxEVT_ACTIVATE_APP) + PRINT_EVENT(wxEVT_ACTIVATE) + PRINT_EVENT(wxEVT_CREATE) + PRINT_EVENT(wxEVT_DESTROY) + PRINT_EVENT(wxEVT_SHOW) + PRINT_EVENT(wxEVT_ICONIZE) + PRINT_EVENT(wxEVT_MAXIMIZE) + + PRINT_EVENT(wxEVT_PAINT) + PRINT_EVENT(wxEVT_MENU_OPEN) + PRINT_EVENT(wxEVT_MENU_CLOSE) + PRINT_EVENT(wxEVT_MENU_HIGHLIGHT) + PRINT_EVENT(wxEVT_CONTEXT_MENU) + + PRINT_EVENT(wxEVT_UPDATE_UI) + PRINT_EVENT(wxEVT_SIZING) + PRINT_EVENT(wxEVT_MOVING) + + PRINT_EVENT(wxEVT_TEXT_COPY) + PRINT_EVENT(wxEVT_TEXT_CUT) + PRINT_EVENT(wxEVT_TEXT_PASTE) + +} + +void wxMatchCemuhookEventIds() +{ + + FixupWxEvtIdsToMatchCemuHook(); + + //PrintEvents(); + // check if key eventIds match with Cemuhook + cemu_assert((wxEventType)wxEVT_SIZE == 10078); + cemu_assert((wxEventType)wxEVT_HYPERLINK == 10156); + cemu_assert((wxEventType)wxEVT_IDLE == 10001); + cemu_assert((wxEventType)wxEVT_UPDATE_UI == 10116); +} + +/* This code reserves the first 300 wxWidgets event ids early, so that they cant be grabbed by wxWidgets constructors for the regular events. We then assign fixed IDs that match Cemuhook's later */ +#pragma init_seg(lib) + +int wxNewEventType(); + +bool wxReserveEventIds() +{ + for (int i = 0; i < 300; i++) + wxNewEventType(); + return true; +} + +bool s_placeholderResult = wxReserveEventIds(); + +#endif \ No newline at end of file diff --git a/src/cemuhook/wxEvtHook.inl b/src/cemuhook/wxEvtHook.inl new file mode 100644 index 00000000..a8ae33c0 --- /dev/null +++ b/src/cemuhook/wxEvtHook.inl @@ -0,0 +1,171 @@ +PROCESS_OWN_WXEVT(wxEVT_NULL,10000); +PROCESS_OWN_WXEVT(wxEVT_IDLE,10001); +PROCESS_OWN_WXEVT(wxEVT_THREAD,10002); +PROCESS_OWN_WXEVT(wxEVT_ASYNC_METHOD_CALL,10003); +PROCESS_OWN_WXEVT(wxEVT_END_PROCESS,10004); +PROCESS_OWN_WXEVT(wxEVT_TIMER,10005); +PROCESS_OWN_WXEVT(wxEVT_BUTTON,10006); +PROCESS_OWN_WXEVT(wxEVT_CHECKBOX,10007); +PROCESS_OWN_WXEVT(wxEVT_CHOICE,10008); +PROCESS_OWN_WXEVT(wxEVT_LISTBOX,10009); +PROCESS_OWN_WXEVT(wxEVT_LISTBOX_DCLICK,10010); +PROCESS_OWN_WXEVT(wxEVT_CHECKLISTBOX,10011); +PROCESS_OWN_WXEVT(wxEVT_MENU,10012); +PROCESS_OWN_WXEVT(wxEVT_SLIDER,10013); +PROCESS_OWN_WXEVT(wxEVT_RADIOBOX,10014); +PROCESS_OWN_WXEVT(wxEVT_RADIOBUTTON,10015); +PROCESS_OWN_WXEVT(wxEVT_SCROLLBAR,10016); +PROCESS_OWN_WXEVT(wxEVT_VLBOX,10017); +PROCESS_OWN_WXEVT(wxEVT_COMBOBOX,10018); +PROCESS_OWN_WXEVT(wxEVT_TOOL_RCLICKED,10019); +PROCESS_OWN_WXEVT(wxEVT_TOOL_ENTER,10020); +PROCESS_OWN_WXEVT(wxEVT_TOOL_DROPDOWN,10021); +PROCESS_OWN_WXEVT(wxEVT_COMBOBOX_DROPDOWN,10022); +PROCESS_OWN_WXEVT(wxEVT_COMBOBOX_CLOSEUP,10023); +PROCESS_OWN_WXEVT(wxEVT_LEFT_DOWN,10024); +PROCESS_OWN_WXEVT(wxEVT_LEFT_UP,10025); +PROCESS_OWN_WXEVT(wxEVT_MIDDLE_DOWN,10026); +PROCESS_OWN_WXEVT(wxEVT_MIDDLE_UP,10027); +PROCESS_OWN_WXEVT(wxEVT_RIGHT_DOWN,10028); +PROCESS_OWN_WXEVT(wxEVT_RIGHT_UP,10029); +PROCESS_OWN_WXEVT(wxEVT_MOTION,10030); +PROCESS_OWN_WXEVT(wxEVT_ENTER_WINDOW,10031); +PROCESS_OWN_WXEVT(wxEVT_LEAVE_WINDOW,10032); +PROCESS_OWN_WXEVT(wxEVT_LEFT_DCLICK,10033); +PROCESS_OWN_WXEVT(wxEVT_MIDDLE_DCLICK,10034); +PROCESS_OWN_WXEVT(wxEVT_RIGHT_DCLICK,10035); +PROCESS_OWN_WXEVT(wxEVT_SET_FOCUS,10036); +PROCESS_OWN_WXEVT(wxEVT_KILL_FOCUS,10037); +PROCESS_OWN_WXEVT(wxEVT_CHILD_FOCUS,10038); +PROCESS_OWN_WXEVT(wxEVT_MOUSEWHEEL,10039); +PROCESS_OWN_WXEVT(wxEVT_AUX1_DOWN,10040); +PROCESS_OWN_WXEVT(wxEVT_AUX1_UP,10041); +PROCESS_OWN_WXEVT(wxEVT_AUX1_DCLICK,10042); +PROCESS_OWN_WXEVT(wxEVT_AUX2_DOWN,10043); +PROCESS_OWN_WXEVT(wxEVT_AUX2_UP,10044); +PROCESS_OWN_WXEVT(wxEVT_AUX2_DCLICK,10045); +PROCESS_OWN_WXEVT(wxEVT_MAGNIFY,10046); +PROCESS_OWN_WXEVT(wxEVT_CHAR,10047); +PROCESS_OWN_WXEVT(wxEVT_AFTER_CHAR,10048); +PROCESS_OWN_WXEVT(wxEVT_CHAR_HOOK,10049); +PROCESS_OWN_WXEVT(wxEVT_NAVIGATION_KEY,10050); +PROCESS_OWN_WXEVT(wxEVT_KEY_DOWN,10051); +PROCESS_OWN_WXEVT(wxEVT_KEY_UP,10052); +PROCESS_OWN_WXEVT(wxEVT_HOTKEY,10053); +PROCESS_OWN_WXEVT(wxEVT_SET_CURSOR,10054); +PROCESS_OWN_WXEVT(wxEVT_SCROLL_TOP,10055); +PROCESS_OWN_WXEVT(wxEVT_SCROLL_BOTTOM,10056); +PROCESS_OWN_WXEVT(wxEVT_SCROLL_LINEUP,10057); +PROCESS_OWN_WXEVT(wxEVT_SCROLL_LINEDOWN,10058); +PROCESS_OWN_WXEVT(wxEVT_SCROLL_PAGEUP,10059); +PROCESS_OWN_WXEVT(wxEVT_SCROLL_PAGEDOWN,10060); +PROCESS_OWN_WXEVT(wxEVT_SCROLL_THUMBTRACK,10061); +PROCESS_OWN_WXEVT(wxEVT_SCROLL_THUMBRELEASE,10062); +PROCESS_OWN_WXEVT(wxEVT_SCROLL_CHANGED,10063); +PROCESS_OWN_WXEVT(wxEVT_SPIN_UP,10057); +PROCESS_OWN_WXEVT(wxEVT_SPIN_DOWN,10058); +PROCESS_OWN_WXEVT(wxEVT_SPIN,10061); +PROCESS_OWN_WXEVT(wxEVT_SCROLLWIN_TOP,10064); +PROCESS_OWN_WXEVT(wxEVT_SCROLLWIN_BOTTOM,10065); +PROCESS_OWN_WXEVT(wxEVT_SCROLLWIN_LINEUP,10066); +PROCESS_OWN_WXEVT(wxEVT_SCROLLWIN_LINEDOWN,10067); +PROCESS_OWN_WXEVT(wxEVT_SCROLLWIN_PAGEUP,10068); +PROCESS_OWN_WXEVT(wxEVT_SCROLLWIN_PAGEDOWN,10069); +PROCESS_OWN_WXEVT(wxEVT_SCROLLWIN_THUMBTRACK,10070); +PROCESS_OWN_WXEVT(wxEVT_SCROLLWIN_THUMBRELEASE,10071); +PROCESS_OWN_WXEVT(wxEVT_GESTURE_PAN,10072); +PROCESS_OWN_WXEVT(wxEVT_GESTURE_ZOOM,10073); +PROCESS_OWN_WXEVT(wxEVT_GESTURE_ROTATE,10074); +PROCESS_OWN_WXEVT(wxEVT_TWO_FINGER_TAP,10075); +PROCESS_OWN_WXEVT(wxEVT_LONG_PRESS,10076); +PROCESS_OWN_WXEVT(wxEVT_PRESS_AND_TAP,10077); +PROCESS_OWN_WXEVT(wxEVT_SIZE,10078); +PROCESS_OWN_WXEVT(wxEVT_SIZING,10079); +PROCESS_OWN_WXEVT(wxEVT_MOVE,10080); +PROCESS_OWN_WXEVT(wxEVT_MOVING,10081); +PROCESS_OWN_WXEVT(wxEVT_MOVE_START,10082); +PROCESS_OWN_WXEVT(wxEVT_MOVE_END,10083); +PROCESS_OWN_WXEVT(wxEVT_CLOSE_WINDOW,10084); +PROCESS_OWN_WXEVT(wxEVT_END_SESSION,10085); +PROCESS_OWN_WXEVT(wxEVT_QUERY_END_SESSION,10086); +PROCESS_OWN_WXEVT(wxEVT_HIBERNATE,10087); +PROCESS_OWN_WXEVT(wxEVT_ACTIVATE_APP,10088); +PROCESS_OWN_WXEVT(wxEVT_ACTIVATE,10089); +PROCESS_OWN_WXEVT(wxEVT_CREATE,10090); +PROCESS_OWN_WXEVT(wxEVT_DESTROY,10091); +PROCESS_OWN_WXEVT(wxEVT_SHOW,10092); +PROCESS_OWN_WXEVT(wxEVT_ICONIZE,10093); +PROCESS_OWN_WXEVT(wxEVT_MAXIMIZE,10094); +PROCESS_OWN_WXEVT(wxEVT_FULLSCREEN,10095); +PROCESS_OWN_WXEVT(wxEVT_MOUSE_CAPTURE_CHANGED,10096); +PROCESS_OWN_WXEVT(wxEVT_MOUSE_CAPTURE_LOST,10097); +PROCESS_OWN_WXEVT(wxEVT_PAINT,10098); +PROCESS_OWN_WXEVT(wxEVT_ERASE_BACKGROUND,10099); +PROCESS_OWN_WXEVT(wxEVT_NC_PAINT,10100); +PROCESS_OWN_WXEVT(wxEVT_MENU_OPEN,10101); +PROCESS_OWN_WXEVT(wxEVT_MENU_CLOSE,10102); +PROCESS_OWN_WXEVT(wxEVT_MENU_HIGHLIGHT,10103); +PROCESS_OWN_WXEVT(wxEVT_CONTEXT_MENU,10104); +PROCESS_OWN_WXEVT(wxEVT_SYS_COLOUR_CHANGED,10105); +PROCESS_OWN_WXEVT(wxEVT_DISPLAY_CHANGED,10106); +PROCESS_OWN_WXEVT(wxEVT_DPI_CHANGED,10107); +PROCESS_OWN_WXEVT(wxEVT_QUERY_NEW_PALETTE,10108); +PROCESS_OWN_WXEVT(wxEVT_PALETTE_CHANGED,10109); +PROCESS_OWN_WXEVT(wxEVT_JOY_BUTTON_DOWN,10110); +PROCESS_OWN_WXEVT(wxEVT_JOY_BUTTON_UP,10111); +PROCESS_OWN_WXEVT(wxEVT_JOY_MOVE,10112); +PROCESS_OWN_WXEVT(wxEVT_JOY_ZMOVE,10113); +PROCESS_OWN_WXEVT(wxEVT_DROP_FILES,10114); +PROCESS_OWN_WXEVT(wxEVT_INIT_DIALOG,10115); +PROCESS_OWN_WXEVT(wxEVT_UPDATE_UI,10116); +PROCESS_OWN_WXEVT(wxEVT_TEXT_COPY,10117); +PROCESS_OWN_WXEVT(wxEVT_TEXT_CUT,10118); +PROCESS_OWN_WXEVT(wxEVT_TEXT_PASTE,10119); +PROCESS_OWN_WXEVT(wxEVT_COMMAND_LEFT_CLICK,10120); +PROCESS_OWN_WXEVT(wxEVT_COMMAND_LEFT_DCLICK,10121); +PROCESS_OWN_WXEVT(wxEVT_COMMAND_RIGHT_CLICK,10122); +PROCESS_OWN_WXEVT(wxEVT_COMMAND_RIGHT_DCLICK,10123); +PROCESS_OWN_WXEVT(wxEVT_COMMAND_SET_FOCUS,10124); +PROCESS_OWN_WXEVT(wxEVT_COMMAND_KILL_FOCUS,10125); +PROCESS_OWN_WXEVT(wxEVT_COMMAND_ENTER,10126); +PROCESS_OWN_WXEVT(wxEVT_HELP,10127); +PROCESS_OWN_WXEVT(wxEVT_DETAILED_HELP,10128); +PROCESS_OWN_WXEVT(wxEVT_LIST_BEGIN_DRAG,10129); +PROCESS_OWN_WXEVT(wxEVT_LIST_BEGIN_RDRAG,10130); +PROCESS_OWN_WXEVT(wxEVT_LIST_BEGIN_LABEL_EDIT,10131); +PROCESS_OWN_WXEVT(wxEVT_LIST_END_LABEL_EDIT,10132); +PROCESS_OWN_WXEVT(wxEVT_LIST_DELETE_ITEM,10133); +PROCESS_OWN_WXEVT(wxEVT_LIST_DELETE_ALL_ITEMS,10134); +PROCESS_OWN_WXEVT(wxEVT_LIST_ITEM_SELECTED,10135); +PROCESS_OWN_WXEVT(wxEVT_LIST_ITEM_DESELECTED,10136); +PROCESS_OWN_WXEVT(wxEVT_LIST_KEY_DOWN,10137); +PROCESS_OWN_WXEVT(wxEVT_LIST_INSERT_ITEM,10138); +PROCESS_OWN_WXEVT(wxEVT_LIST_COL_CLICK,10139); +PROCESS_OWN_WXEVT(wxEVT_LIST_COL_RIGHT_CLICK,10140); +PROCESS_OWN_WXEVT(wxEVT_LIST_COL_BEGIN_DRAG,10141); +PROCESS_OWN_WXEVT(wxEVT_LIST_COL_DRAGGING,10142); +PROCESS_OWN_WXEVT(wxEVT_LIST_COL_END_DRAG,10143); +PROCESS_OWN_WXEVT(wxEVT_LIST_ITEM_RIGHT_CLICK,10144); +PROCESS_OWN_WXEVT(wxEVT_LIST_ITEM_MIDDLE_CLICK,10145); +PROCESS_OWN_WXEVT(wxEVT_LIST_ITEM_ACTIVATED,10146); +PROCESS_OWN_WXEVT(wxEVT_LIST_ITEM_FOCUSED,10147); +PROCESS_OWN_WXEVT(wxEVT_LIST_ITEM_CHECKED,10148); +PROCESS_OWN_WXEVT(wxEVT_LIST_ITEM_UNCHECKED,10149); +PROCESS_OWN_WXEVT(wxEVT_LIST_CACHE_HINT,10150); +PROCESS_OWN_WXEVT(wxEVT_TEXT,10151); +PROCESS_OWN_WXEVT(wxEVT_TEXT_ENTER,10152); +PROCESS_OWN_WXEVT(wxEVT_TEXT_URL,10153); +PROCESS_OWN_WXEVT(wxEVT_TEXT_MAXLEN,10154); +PROCESS_OWN_WXEVT(wxEVT_WINDOW_MODAL_DIALOG_CLOSED,10155); +PROCESS_OWN_WXEVT(wxEVT_HYPERLINK,10156); +PROCESS_OWN_WXEVT(wxEVT_CLIPBOARD_CHANGED,10157); +PROCESS_OWN_WXEVT(wxEVT_NOTEBOOK_PAGE_CHANGED,10158); +PROCESS_OWN_WXEVT(wxEVT_NOTEBOOK_PAGE_CHANGING,10159); +PROCESS_OWN_WXEVT(wxEVT_SPINCTRL,10160); +PROCESS_OWN_WXEVT(wxEVT_SPINCTRLDOUBLE,10161); +PROCESS_OWN_WXEVT(wxEVT_COLLAPSIBLEPANE_CHANGED,10162); +PROCESS_OWN_WXEVT(wxEVT_COLLAPSIBLEHEADER_CHANGED,10163); +PROCESS_OWN_WXEVT(wxEVT_POWER_SUSPENDING,10164); +PROCESS_OWN_WXEVT(wxEVT_POWER_SUSPENDED,10165); +PROCESS_OWN_WXEVT(wxEVT_POWER_SUSPEND_CANCEL,10166); +PROCESS_OWN_WXEVT(wxEVT_POWER_RESUME,10167); \ No newline at end of file diff --git a/src/config/ActiveSettings.cpp b/src/config/ActiveSettings.cpp new file mode 100644 index 00000000..9e8a429e --- /dev/null +++ b/src/config/ActiveSettings.cpp @@ -0,0 +1,227 @@ +#include "config/ActiveSettings.h" + +#include "Cafe/GameProfile/GameProfile.h" +#include "LaunchSettings.h" +#include "util/helpers/helpers.h" + +#include + +#include "Cafe/IOSU/legacy/iosu_crypto.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" +#include "Cafe/CafeSystem.h" + +extern bool DLLEXPORT alwaysDisplayDRC; + +void ActiveSettings::LoadOnce() +{ + s_full_path = boost::dll::program_location().generic_wstring(); + s_path = s_full_path.parent_path(); + s_filename = s_full_path.filename(); + + g_config.SetFilename(GetPath("settings.xml").generic_wstring()); + g_config.Load(); + + std::wstring additionalErrorInfo; + s_has_required_online_files = iosuCrypt_checkRequirementsForOnlineMode(additionalErrorInfo) == IOS_CRYPTO_ONLINE_REQ_OK; +} + +bool ActiveSettings::LoadSharedLibrariesEnabled() +{ + return g_current_game_profile->ShouldLoadSharedLibraries().value_or(true); +} + +bool ActiveSettings::DisplayDRCEnabled() +{ + alwaysDisplayDRC = g_current_game_profile->StartWithGamepadView(); + return g_current_game_profile->StartWithGamepadView(); +} + +bool ActiveSettings::FullscreenEnabled() +{ + if (LaunchSettings::FullscreenEnabled().has_value()) + return LaunchSettings::FullscreenEnabled().value(); + + return GetConfig().fullscreen; +} + +CPUMode ActiveSettings::GetCPUMode() +{ + auto mode = g_current_game_profile->GetCPUMode().value_or(CPUMode::Auto); + + if (mode == CPUMode::Auto) + { + if (GetPhysicalCoreCount() >= 4) + mode = CPUMode::MulticoreRecompiler; + else + mode = CPUMode::SinglecoreRecompiler; + } + else if (mode == CPUMode::DualcoreRecompiler) // dualcore is disabled now + mode = CPUMode::MulticoreRecompiler; + + return mode; +} + +uint8 ActiveSettings::GetTimerShiftFactor() +{ + return s_timer_shift; +} + +void ActiveSettings::SetTimerShiftFactor(uint8 shiftFactor) +{ + s_timer_shift = shiftFactor; +} + +PrecompiledShaderOption ActiveSettings::GetPrecompiledShadersOption() +{ + return PrecompiledShaderOption::Auto; // g_current_game_profile->GetPrecompiledShadersState().value_or(GetConfig().precompiled_shaders); +} + +bool ActiveSettings::RenderUpsideDownEnabled() +{ + return LaunchSettings::RenderUpsideDownEnabled().value_or(GetConfig().render_upside_down); +} + +bool ActiveSettings::WaitForGX2DrawDoneEnabled() +{ + return GetConfig().gx2drawdone_sync; +} + +GraphicAPI ActiveSettings::GetGraphicsAPI() +{ + GraphicAPI api = g_current_game_profile->GetGraphicsAPI().value_or(GetConfig().graphic_api); + // check if vulkan even available + if (api == kVulkan && !g_vulkan_available) + api = kOpenGL; + + return api; +} + +bool ActiveSettings::AudioOutputOnlyAux() +{ + return s_audio_aux_only; +} + +void ActiveSettings::EnableAudioOnlyAux(bool state) +{ + s_audio_aux_only = state; +} + +uint32 ActiveSettings::GetPersistentId() +{ + return LaunchSettings::GetPersistentId().value_or(GetConfig().account.m_persistent_id); +} + +bool ActiveSettings::IsOnlineEnabled() +{ + return GetConfig().account.online_enabled && Account::GetAccount(GetPersistentId()).IsValidOnlineAccount() && HasRequiredOnlineFiles(); +} + +bool ActiveSettings::HasRequiredOnlineFiles() +{ + return s_has_required_online_files; +} + +bool ActiveSettings::DumpShadersEnabled() +{ + return s_dump_shaders; +} + +bool ActiveSettings::DumpTexturesEnabled() +{ + return s_dump_textures; +} + +bool ActiveSettings::DumpLibcurlRequestsEnabled() +{ + return s_dump_libcurl_requests; +} + +void ActiveSettings::EnableDumpShaders(bool state) +{ + s_dump_shaders = state; +} + +void ActiveSettings::EnableDumpTextures(bool state) +{ + s_dump_textures = state; +} + +void ActiveSettings::EnableDumpLibcurlRequests(bool state) +{ + s_dump_libcurl_requests = state; +} + +bool ActiveSettings::FrameProfilerEnabled() +{ + return s_frame_profiler_enabled; +} + +void ActiveSettings::EnableFrameProfiler(bool state) +{ + s_frame_profiler_enabled = state; +} + +bool ActiveSettings::VPADDelayEnabled() +{ + const uint64 titleId = CafeSystem::GetForegroundTitleId(); + // workaround for Art Academy spamming VPADRead + return /* Art Academy: Home Studio (US) */ titleId == 0x000500001017BF00 || + /* Art Academy: Home Studio (JP) */ titleId == 0x000500001017BE00 || + /* Art Academy: Atelier (EU) */ titleId == 0x000500001017B500; +} + +bool ActiveSettings::ShaderPreventInfiniteLoopsEnabled() +{ + const uint64 titleId = CafeSystem::GetForegroundTitleId(); + // workaround for NSMBU (and variants) having a bug where shaders can get stuck in infinite loops + // update: As of Cemu 1.20.0 this should no longer be required + return /* NSMBU JP */ titleId == 0x0005000010101C00 || + /* NSMBU US */ titleId == 0x0005000010101D00 || + /* NSMBU EU */ titleId == 0x0005000010101E00 || + /* NSMBU+L US */ titleId == 0x000500001014B700 || + /* NSMBU+L EU */ titleId == 0x000500001014B800 || + /* NSLU US */ titleId == 0x0005000010142300 || + /* NSLU EU */ titleId == 0x0005000010142400; +} + +bool ActiveSettings::FlushGPUCacheOnSwap() +{ + const uint64 titleId = CafeSystem::GetForegroundTitleId(); + // games that require flushing the cache between frames + return + /* PAC-MAN and the Ghostly Adventures (EU) */ titleId == 0x0005000010147900 || + /* PAC-MAN and the Ghostly Adventures (US) */ titleId == 0x0005000010146300 || + /* PAC-MAN and the Ghostly Adventures 2 (EU) */ titleId == 0x000500001017E500 || + /* PAC-MAN and the Ghostly Adventures 2 (US) */ titleId == 0x000500001017E600; +} + +bool ActiveSettings::ForceSamplerRoundToPrecision() +{ + // some Wayforward games (Duck Tales, Adventure Time Explore The Dungeon) sample textures exactly on the texel edge. On Wii U this is fine because the rounding mode can be controlled + // on OpenGL/Vulkan when uv coordinates are converted from (un)normalized to integer the implementation always truncates which causes an off-by-one error in edge cases + // In the future we should look into optionally correctly emulating sampler behavior (its quite complex, Latte supports flexible precision levels) + const uint64 titleId = CafeSystem::GetForegroundTitleId(); + return + /* Adventure Time ETDBIDK (EU) */ titleId == 0x000500001014E100 || + /* Adventure Time ETDBIDK (US) */ titleId == 0x0005000010144000 || + /* DuckTales Remastered (EU) */ titleId == 0x0005000010129200 || + /* DuckTales Remastered (US) */ titleId == 0x0005000010129000; + return false; +} + +fs::path ActiveSettings::GetMlcPath() +{ + if(const auto launch_mlc = LaunchSettings::GetMLCPath(); launch_mlc.has_value()) + return launch_mlc.value(); + + if(const auto config_mlc = GetConfig().mlc_path.GetValue(); !config_mlc.empty()) + return config_mlc; + + return GetDefaultMLCPath(); +} + +fs::path ActiveSettings::GetDefaultMLCPath() +{ + return GetPath("mlc01"); +} + diff --git a/src/config/ActiveSettings.h b/src/config/ActiveSettings.h new file mode 100644 index 00000000..44b5f509 --- /dev/null +++ b/src/config/ActiveSettings.h @@ -0,0 +1,129 @@ +#pragma once + +#include "config/CemuConfig.h" + +// global active settings for fast access (reflects settings from command line and game profile) +class ActiveSettings +{ +public: + static void LoadOnce(); + + [[nodiscard]] static fs::path GetFullPath() { return s_full_path; } + [[nodiscard]] static fs::path GetPath() { return s_path; } + [[nodiscard]] static fs::path GetFilename() { return s_filename; } + + [[nodiscard]] static fs::path GetMlcPath(); + + [[nodiscard]] static fs::path GetPath(std::string_view p) + { + std::basic_string_view s((const char8_t*)p.data(), p.size()); + return s_path / fs::path(s); + } + + [[nodiscard]] static fs::path GetMlcPath(std::string_view p) + { + std::basic_string_view s((const char8_t*)p.data(), p.size()); + return GetMlcPath() / fs::path(s); + } + + template + [[nodiscard]] static fs::path GetPath(std::string_view format, TArgs&&... args) + { + cemu_assert_debug(format.empty() || (format[0] != '/' && format[0] != '\\')); + std::string tmpPathStr = fmt::format(format, std::forward(args)...); + std::basic_string_view s((const char8_t*)tmpPathStr.data(), tmpPathStr.size()); + return s_path / fs::path(s); + } + + template + [[nodiscard]] static fs::path GetPath(std::wstring_view format, TArgs&&... args) + { + cemu_assert_debug(format.empty() || (format[0] != L'/' && format[0] != L'\\')); + return s_path / fmt::format(format, std::forward(args)...); + } + + template + [[nodiscard]] static fs::path GetMlcPath(std::string_view format, TArgs&&... args) + { + cemu_assert_debug(format.empty() || (format[0] != '/' && format[0] != '\\')); + auto tmp = fmt::format(format, std::forward(args)...); + return GetMlcPath() / fs::path(_asUtf8(tmp)); + } + + template + [[nodiscard]] static fs::path GetMlcPath(std::wstring_view format, TArgs&&... args) + { + cemu_assert_debug(format.empty() || (format[0] != L'/' && format[0] != L'\\')); + return GetMlcPath() / fmt::format(format, std::forward(args)...); + } + + // get mlc path to default cemu root dir/mlc01 + [[nodiscard]] static fs::path GetDefaultMLCPath(); + +private: + inline static fs::path s_full_path; // full filename + inline static fs::path s_path; // path + inline static fs::path s_filename; // cemu.exe + inline static fs::path s_mlc_path; + +public: + // general + [[nodiscard]] static bool LoadSharedLibrariesEnabled(); + [[nodiscard]] static bool DisplayDRCEnabled(); + [[nodiscard]] static bool FullscreenEnabled(); + + // cpu + [[nodiscard]] static CPUMode GetCPUMode(); + [[nodiscard]] static uint8 GetTimerShiftFactor(); + + static void SetTimerShiftFactor(uint8 shiftFactor); + + // gpu + [[nodiscard]] static PrecompiledShaderOption GetPrecompiledShadersOption(); + [[nodiscard]] static bool RenderUpsideDownEnabled(); + [[nodiscard]] static bool WaitForGX2DrawDoneEnabled(); + [[nodiscard]] static GraphicAPI GetGraphicsAPI(); + + // audio + [[nodiscard]] static bool AudioOutputOnlyAux(); + static void EnableAudioOnlyAux(bool state); + + // account + [[nodiscard]] static uint32 GetPersistentId(); + [[nodiscard]] static bool IsOnlineEnabled(); + [[nodiscard]] static bool HasRequiredOnlineFiles(); + + // dump options + [[nodiscard]] static bool DumpShadersEnabled(); + [[nodiscard]] static bool DumpTexturesEnabled(); + [[nodiscard]] static bool DumpLibcurlRequestsEnabled(); + static void EnableDumpShaders(bool state); + static void EnableDumpTextures(bool state); + static void EnableDumpLibcurlRequests(bool state); + + // debug + [[nodiscard]] static bool FrameProfilerEnabled(); + static void EnableFrameProfiler(bool state); + + // hacks + [[nodiscard]] static bool VPADDelayEnabled(); + [[nodiscard]] static bool ShaderPreventInfiniteLoopsEnabled(); + [[nodiscard]] static bool FlushGPUCacheOnSwap(); + [[nodiscard]] static bool ForceSamplerRoundToPrecision(); + +private: + // dump options + inline static bool s_dump_shaders = false; + inline static bool s_dump_textures = false; + inline static bool s_dump_libcurl_requests = false; + + // timer speed + inline static uint8 s_timer_shift = 3; // right shift factor, 0 -> 8x, 3 -> 1x, 4 -> 0.5x + + // debug + inline static bool s_frame_profiler_enabled = false; + inline static bool s_audio_aux_only = false; + + inline static bool s_has_required_online_files = false; +}; + diff --git a/src/config/CMakeLists.txt b/src/config/CMakeLists.txt new file mode 100644 index 00000000..4710b5c4 --- /dev/null +++ b/src/config/CMakeLists.txt @@ -0,0 +1,11 @@ +project(CemuConfig) + +file(GLOB CPP_FILES *.cpp) +file(GLOB H_FILES *.h) +add_library(CemuConfig ${CPP_FILES} ${H_FILES}) + +set_property(TARGET CemuConfig PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + +target_precompile_headers(CemuConfig PRIVATE ../Common/precompiled.h) + +target_include_directories(CemuConfig PRIVATE ../) \ No newline at end of file diff --git a/src/config/CemuConfig.cpp b/src/config/CemuConfig.cpp new file mode 100644 index 00000000..c4280893 --- /dev/null +++ b/src/config/CemuConfig.cpp @@ -0,0 +1,579 @@ +#include "config/CemuConfig.h" + +#include "util/helpers/helpers.h" +#include "config/ActiveSettings.h" + +#include + +#include "PermanentConfig.h" +#include "ActiveSettings.h" + +XMLCemuConfig_t g_config(L"settings.xml"); + +void CemuConfig::SetMLCPath(std::wstring_view path, bool save) +{ + mlc_path.SetValue(path); + if(save) + g_config.Save(); + + // if custom mlc path has been selected, store it in permanent config + if (!boost::starts_with(path, ActiveSettings::GetDefaultMLCPath().generic_wstring())) + { + try + { + auto pconfig = PermanentConfig::Load(); + pconfig.custom_mlc_path = boost::nowide::narrow(path.data(), path.size()); + pconfig.Store(); + } + catch (const PSDisabledException&) {} + catch (const std::exception& ex) + { + forceLog_printf("can't store custom mlc path in permanent storage: %s", ex.what()); + } + } + + Account::RefreshAccounts(); +} + +void CemuConfig::Load(XMLConfigParser& parser) +{ + auto new_parser = parser.get("content"); + if (new_parser.valid()) + parser = new_parser; + + // general settings + log_flag = parser.get("logflag", log_flag.GetInitValue()); + advanced_ppc_logging = parser.get("advanced_ppc_logging", advanced_ppc_logging.GetInitValue()); + + const char* mlc = parser.get("mlc_path", ""); + try + { + mlc_path = boost::nowide::widen(mlc); + } + catch (const std::exception&) + { + forceLog_printf("config load error: can't load mlc path: %s", mlc); + } + + permanent_storage = parser.get("permanent_storage", permanent_storage); + + language = parser.get("language", wxLANGUAGE_DEFAULT); + use_discord_presence = parser.get("use_discord_presence", true); + fullscreen_menubar = parser.get("fullscreen_menubar", false); + check_update = parser.get("check_update", check_update); + save_screenshot = parser.get("save_screenshot", save_screenshot); + did_show_vulkan_warning = parser.get("vk_warning", did_show_vulkan_warning); + did_show_graphic_pack_download = parser.get("gp_download", did_show_graphic_pack_download); + fullscreen = parser.get("fullscreen", fullscreen); + + // cpu_mode = parser.get("cpu_mode", cpu_mode.GetInitValue()); + //console_region = parser.get("console_region", console_region.GetInitValue()); + console_language = parser.get("console_language", console_language.GetInitValue()); + + window_position.x = parser.get("window_position").get("x", -1); + window_position.y = parser.get("window_position").get("y", -1); + + window_size.x = parser.get("window_size").get("x", -1); + window_size.y = parser.get("window_size").get("y", -1); + window_maximized = parser.get("window_maximized", false); + + pad_open = parser.get("open_pad", false); + pad_position.x = parser.get("pad_position").get("x", -1); + pad_position.y = parser.get("pad_position").get("y", -1); + + pad_size.x = parser.get("pad_size").get("x", -1); + pad_size.y = parser.get("pad_size").get("y", -1); + pad_maximized = parser.get("pad_maximized", false); + + auto gamelist = parser.get("GameList"); + game_list_style = gamelist.get("style", 0); + game_list_column_order = gamelist.get("order", ""); + column_width.name = gamelist.get("name_width", -3); + column_width.version = gamelist.get("version_width", -3); + column_width.dlc = gamelist.get("dlc_width", -3); + column_width.game_time = gamelist.get("game_time_width", -3); + column_width.game_started = gamelist.get("game_started_width", -3); + column_width.region = gamelist.get("region_width", -3); + + recent_launch_files.clear(); + auto launch_parser = parser.get("RecentLaunchFiles"); + for (auto element = launch_parser.get("Entry"); element.valid(); element = launch_parser.get("Entry", element)) + { + const std::string path = element.value(""); + if (path.empty()) + continue; + + try + { + recent_launch_files.emplace_back(boost::nowide::widen(path)); + } + catch (const std::exception&) + { + forceLog_printf("config load error: can't load recently launched game file: %s", path.c_str()); + } + } + + recent_nfc_files.clear(); + auto nfc_parser = parser.get("RecentNFCFiles"); + for (auto element = nfc_parser.get("Entry"); element.valid(); element = nfc_parser.get("Entry", element)) + { + const std::string path = element.value(""); + if (path.empty()) + continue; + + try + { + recent_nfc_files.emplace_back(boost::nowide::widen(path)); + } + catch (const std::exception&) + { + forceLog_printf("config load error: can't load recently launched nfc file: %s", path.c_str()); + } + } + + game_paths.clear(); + auto game_path_parser = parser.get("GamePaths"); + for (auto element = game_path_parser.get("Entry"); element.valid(); element = game_path_parser.get("Entry", element)) + { + const std::string path = element.value(""); + if (path.empty()) + continue; + + try + { + game_paths.emplace_back(boost::nowide::widen(path)); + } + catch (const std::exception&) + { + forceLog_printf("config load error: can't load game path: %s", path.c_str()); + } + } + + std::unique_lock _lock(game_cache_entries_mutex); + game_cache_entries.clear(); + auto game_cache_parser = parser.get("GameCache"); + for (auto element = game_cache_parser.get("Entry"); element.valid(); element = game_cache_parser.get("Entry", element)) + { + const char* rpx = element.get("path", ""); + try + { + GameEntry entry{}; + entry.rpx_file = boost::nowide::widen(rpx); + entry.title_id = element.get("title_id", 0LL); + entry.legacy_name = boost::nowide::widen(element.get("name", "")); + entry.custom_name = element.get("custom_name", ""); + entry.legacy_region = element.get("region", 0); + entry.legacy_version = element.get("version", 0); + entry.legacy_update_version = element.get("version", 0); + entry.legacy_dlc_version = element.get("dlc_version", 0); + entry.legacy_time_played = element.get("time_played", 0ULL); + entry.legacy_last_played = element.get("last_played", 0ULL); + entry.favorite = element.get("favorite", false); + game_cache_entries.emplace_back(entry); + + if (entry.title_id != 0) + { + if (entry.favorite) + game_cache_favorites.emplace(entry.title_id); + else + game_cache_favorites.erase(entry.title_id); + } + } + catch (const std::exception&) + { + forceLog_printf("config load error: can't load game cache entry: %s", rpx); + } + } + _lock.unlock(); + + graphic_pack_entries.clear(); + auto graphic_pack_parser = parser.get("GraphicPack"); + for (auto element = graphic_pack_parser.get("Entry"); element.valid(); element = graphic_pack_parser.get("Entry", element)) + { + std::string filename = element.get_attribute("filename", ""); + if(filename.empty()) // legacy loading + { + filename = element.get("filename", ""); + fs::path path = fs::path(filename).lexically_normal(); + graphic_pack_entries.try_emplace(path); + const std::string category = element.get("category", ""); + const std::string preset = element.get("preset", ""); + graphic_pack_entries[filename].try_emplace(category, preset); + } + else + { + fs::path path = fs::path(filename).lexically_normal(); + graphic_pack_entries.try_emplace(path); + + const bool disabled = element.get_attribute("disabled", false); + if (disabled) + { + graphic_pack_entries[path].try_emplace("_disabled", "true"); + } + + for (auto preset = element.get("Preset"); preset.valid(); preset = element.get("Preset", preset)) + { + const std::string category = preset.get("category", ""); + const std::string active_preset = preset.get("preset", ""); + graphic_pack_entries[path].try_emplace(category, active_preset); + } + } + + } + + // graphics + auto graphic = parser.get("Graphic"); + graphic_api = graphic.get("api", kOpenGL); + graphic.get("device", graphic_device_uuid); + vsync = graphic.get("VSync", 0); + gx2drawdone_sync = graphic.get("GX2DrawdoneSync", true); + upscale_filter = graphic.get("UpscaleFilter", kBicubicHermiteFilter); + downscale_filter = graphic.get("DownscaleFilter", kLinearFilter); + fullscreen_scaling = graphic.get("FullscreenScaling", kKeepAspectRatio); + async_compile = graphic.get("AsyncCompile", async_compile); + vk_accurate_barriers = graphic.get("vkAccurateBarriers", true); // this used to be "VulkanAccurateBarriers" but because we changed the default to true in 1.27.1 the option name had to be changed + + auto overlay_node = graphic.get("Overlay"); + if(overlay_node.valid()) + { + overlay.position = overlay_node.get("Position", ScreenPosition::kDisabled); + overlay.text_color = overlay_node.get("TextColor", 0xFFFFFFFF); + overlay.text_scale = overlay_node.get("TextScale", 100); + overlay.fps = overlay_node.get("FPS", true); + overlay.drawcalls = overlay_node.get("DrawCalls", false); + overlay.cpu_usage = overlay_node.get("CPUUsage", false); + overlay.cpu_per_core_usage = overlay_node.get("CPUPerCoreUsage", false); + overlay.ram_usage = overlay_node.get("RAMUsage", false); + overlay.vram_usage = overlay_node.get("VRAMUsage", false); + overlay.debug = overlay_node.get("Debug", false); + + notification.controller_profiles = overlay_node.get("ControllerProfiles", true); + notification.controller_battery = overlay_node.get("ControllerBattery", true); + notification.shader_compiling = overlay_node.get("ShaderCompiling", true); + } + else + { + // legacy support + overlay.position = graphic.get("OverlayPosition", ScreenPosition::kDisabled); + overlay.text_color = graphic.get("OverlayTextColor", 0xFFFFFFFF); + overlay.fps = graphic.get("OverlayFPS", true); + overlay.drawcalls = graphic.get("OverlayDrawCalls", false); + overlay.cpu_usage = graphic.get("OverlayCPUUsage", false); + overlay.cpu_per_core_usage = graphic.get("OverlayCPUPerCoreUsage", false); + overlay.ram_usage = graphic.get("OverlayRAMUsage", false); + + notification.controller_profiles = graphic.get("OverlayControllerProfiles", true); + notification.controller_battery = graphic.get("OverlayControllerBattery", true); + notification.shader_compiling = graphic.get("ShaderCompiling", true); + } + + auto notification_node = graphic.get("Notification"); + if (notification_node.valid()) + { + notification.position = notification_node.get("Position", ScreenPosition::kTopLeft); + notification.text_color = notification_node.get("TextColor", 0xFFFFFFFF); + notification.text_scale = notification_node.get("TextScale", 100); + notification.controller_profiles = notification_node.get("ControllerProfiles", true); + notification.controller_battery = notification_node.get("ControllerBattery", false); + notification.shader_compiling = notification_node.get("ShaderCompiling", true); + notification.friends = notification_node.get("FriendService", true); + } + + // audio + auto audio = parser.get("Audio"); + audio_api = audio.get("api", 0); + audio_delay = audio.get("delay", 2); + tv_channels = audio.get("TVChannels", kStereo); + pad_channels = audio.get("PadChannels", kStereo); + tv_volume = audio.get("TVVolume", 20); + pad_volume = audio.get("PadVolume", 0); + + const auto tv = audio.get("TVDevice", ""); + try + { + tv_device = boost::nowide::widen(tv); + } + catch (const std::exception&) + { + forceLog_printf("config load error: can't load tv device: %s", tv); + } + + const auto pad = audio.get("PadDevice", ""); + try + { + pad_device = boost::nowide::widen(pad); + } + catch (const std::exception&) + { + forceLog_printf("config load error: can't load pad device: %s", pad); + } + + // account + auto acc = parser.get("Account"); + account.m_persistent_id = acc.get("PersistentId", account.m_persistent_id); + account.online_enabled = acc.get("OnlineEnabled", account.online_enabled); + + // debug + auto debug = parser.get("Debug"); + crash_dump = debug.get("CrashDump", crash_dump); + + // input + auto input = parser.get("Input"); + auto dsuc = input.get("DSUC"); + dsu_client.host = dsuc.get_attribute("host", dsu_client.host); + dsu_client.port = dsuc.get_attribute("port", dsu_client.port); +} + +void CemuConfig::Save(XMLConfigParser& parser) +{ + auto config = parser.set("content"); + // general settings + config.set("logflag", log_flag.GetValue()); + config.set("advanced_ppc_logging", advanced_ppc_logging.GetValue()); + config.set("mlc_path", boost::nowide::narrow(mlc_path.GetValue()).c_str()); + config.set("permanent_storage", permanent_storage); + config.set("language", language); + config.set("use_discord_presence", use_discord_presence); + config.set("fullscreen_menubar", fullscreen_menubar); + config.set("check_update", check_update); + config.set("save_screenshot", save_screenshot); + config.set("vk_warning", did_show_vulkan_warning); + config.set("gp_download", did_show_graphic_pack_download); + config.set("fullscreen", fullscreen); + + // config.set("cpu_mode", cpu_mode.GetValue()); + //config.set("console_region", console_region.GetValue()); + config.set("console_language", console_language.GetValue()); + + auto wpos = config.set("window_position"); + wpos.set("x", window_position.x); + wpos.set("y", window_position.y); + auto wsize = config.set("window_size"); + wsize.set("x", window_size.x); + wsize.set("y", window_size.y); + config.set("window_maximized", window_maximized); + + config.set("open_pad", pad_open); + auto ppos = config.set("pad_position"); + ppos.set("x", pad_position.x); + ppos.set("y", pad_position.y); + auto psize = config.set("pad_size"); + psize.set("x", pad_size.x); + psize.set("y", pad_size.y); + config.set("pad_maximized", pad_maximized); + + auto gamelist = config.set("GameList"); + gamelist.set("style", game_list_style); + gamelist.set("order", game_list_column_order); + gamelist.set("name_width", column_width.name); + gamelist.set("version_width", column_width.version); + gamelist.set("dlc_width", column_width.dlc); + gamelist.set("game_time_width", column_width.game_time); + gamelist.set("game_started_width", column_width.game_started); + gamelist.set("region_width", column_width.region); + + auto launch_files_parser = config.set("RecentLaunchFiles"); + for (const auto& entry : recent_launch_files) + { + launch_files_parser.set("Entry", boost::nowide::narrow(entry).c_str()); + } + + auto nfc_files_parser = config.set("RecentNFCFiles"); + for (const auto& entry : recent_nfc_files) + { + nfc_files_parser.set("Entry", boost::nowide::narrow(entry).c_str()); + } + + // game paths + auto game_path_parser = config.set("GamePaths"); + for (const auto& entry : game_paths) + { + game_path_parser.set("Entry", boost::nowide::narrow(entry).c_str()); + } + + // game list cache + std::unique_lock _lock(game_cache_entries_mutex); + auto game_cache_parser = config.set("GameCache"); + for (const auto& game : game_cache_entries) + { + auto entry = game_cache_parser.set("Entry"); + + entry.set("title_id", (sint64)game.title_id); + entry.set("name", boost::nowide::narrow(game.legacy_name).c_str()); + entry.set("custom_name", game.custom_name.c_str()); + entry.set("region", (sint32)game.legacy_region); + entry.set("version", (sint32)game.legacy_update_version); + entry.set("dlc_version", (sint32)game.legacy_dlc_version); + entry.set("path", boost::nowide::narrow(game.rpx_file).c_str()); + entry.set("time_played", game.legacy_time_played); + entry.set("last_played", game.legacy_last_played); + entry.set("favorite", game.favorite); + } + _lock.unlock(); + + auto graphic_pack_parser = config.set("GraphicPack"); + for (const auto& game : graphic_pack_entries) + { + auto entry = graphic_pack_parser.set("Entry"); + entry.set_attribute("filename",_utf8Wrapper(game.first).c_str()); + for(const auto& kv : game.second) + { + // TODO: less hacky pls + if(boost::iequals(kv.first, "_disabled")) + { + entry.set_attribute("disabled", true); + continue; + } + + auto preset = entry.set("Preset"); + if(!kv.first.empty()) + preset.set("category", kv.first.c_str()); + + preset.set("preset", kv.second.c_str()); + } + } + + // graphics + auto graphic = config.set("Graphic"); + graphic.set("api", graphic_api); + graphic.set("device", graphic_device_uuid); + graphic.set("VSync", vsync); + graphic.set("GX2DrawdoneSync", gx2drawdone_sync); + //graphic.set("PrecompiledShaders", precompiled_shaders.GetValue()); + graphic.set("UpscaleFilter", upscale_filter); + graphic.set("DownscaleFilter", downscale_filter); + graphic.set("FullscreenScaling", fullscreen_scaling); + graphic.set("AsyncCompile", async_compile.GetValue()); + graphic.set("vkAccurateBarriers", vk_accurate_barriers); + + auto overlay_node = graphic.set("Overlay"); + overlay_node.set("Position", overlay.position); + overlay_node.set("TextColor", overlay.text_color); + overlay_node.set("TextScale", overlay.text_scale); + overlay_node.set("FPS", overlay.fps); + overlay_node.set("DrawCalls", overlay.drawcalls); + overlay_node.set("CPUUsage", overlay.cpu_usage); + overlay_node.set("CPUPerCoreUsage", overlay.cpu_per_core_usage); + overlay_node.set("RAMUsage", overlay.ram_usage); + overlay_node.set("VRAMUsage", overlay.vram_usage); + overlay_node.set("Debug", overlay.debug); + + auto notification_node = graphic.set("Notification"); + notification_node.set("Position", notification.position); + notification_node.set("TextColor", notification.text_color); + notification_node.set("TextScale", notification.text_scale); + notification_node.set("ControllerProfiles", notification.controller_profiles); + notification_node.set("ControllerBattery", notification.controller_battery); + notification_node.set("ShaderCompiling", notification.shader_compiling); + notification_node.set("FriendService", notification.friends); + + // audio + auto audio = config.set("Audio"); + audio.set("api", audio_api); + audio.set("delay", audio_delay); + audio.set("TVChannels", tv_channels); + audio.set("PadChannels", pad_channels); + audio.set("TVVolume", tv_volume); + audio.set("PadVolume", pad_volume); + audio.set("TVDevice", boost::nowide::narrow(tv_device).c_str()); + audio.set("PadDevice", boost::nowide::narrow(pad_device).c_str()); + + // account + auto acc = config.set("Account"); + acc.set("PersistentId", account.m_persistent_id.GetValue()); + acc.set("OnlineEnabled", account.online_enabled.GetValue()); + + // debug + auto debug = config.set("Debug"); + debug.set("CrashDump", crash_dump.GetValue()); + + // input + auto input = config.set("Input"); + auto dsuc = input.set("DSUC"); + dsuc.set_attribute("host", dsu_client.host); + dsuc.set_attribute("port", dsu_client.port); +} + +GameEntry* CemuConfig::GetGameEntryByTitleId(uint64 titleId) +{ + // assumes game_cache_entries_mutex is already held + for (auto& it : game_cache_entries) + { + if (it.title_id == titleId) + return ⁢ + } + return nullptr; +} + +GameEntry* CemuConfig::CreateGameEntry(uint64 titleId) +{ + // assumes game_cache_entries_mutex is already held + GameEntry gameEntry; + gameEntry.title_id = titleId; + game_cache_entries.emplace_back(gameEntry); + return &game_cache_entries.back(); +} + +bool CemuConfig::IsGameListFavorite(uint64 titleId) +{ + std::unique_lock _lock(game_cache_entries_mutex); + return game_cache_favorites.find(titleId) != game_cache_favorites.end(); +} + +void CemuConfig::SetGameListFavorite(uint64 titleId, bool isFavorite) +{ + std::unique_lock _lock(game_cache_entries_mutex); + GameEntry* gameEntry = GetGameEntryByTitleId(titleId); + if (!gameEntry) + gameEntry = CreateGameEntry(titleId); + gameEntry->favorite = isFavorite; + if (isFavorite) + game_cache_favorites.emplace(titleId); + else + game_cache_favorites.erase(titleId); +} + +bool CemuConfig::GetGameListCustomName(uint64 titleId, std::string& customName) +{ + std::unique_lock _lock(game_cache_entries_mutex); + GameEntry* gameEntry = GetGameEntryByTitleId(titleId); + if (gameEntry && !gameEntry->custom_name.empty()) + { + customName = gameEntry->custom_name; + return true; + } + return false; +} + +void CemuConfig::SetGameListCustomName(uint64 titleId, std::string customName) +{ + std::unique_lock _lock(game_cache_entries_mutex); + GameEntry* gameEntry = GetGameEntryByTitleId(titleId); + if (!gameEntry) + { + if (customName.empty()) + return; + gameEntry = CreateGameEntry(titleId); + } + gameEntry->custom_name = customName; +} + +void CemuConfig::AddRecentlyLaunchedFile(std::wstring_view file) +{ + // insert into front + recent_launch_files.insert(recent_launch_files.begin(), std::wstring{ file }); + RemoveDuplicatesKeepOrder(recent_launch_files); + // keep maximum of entries + while(recent_launch_files.size() > kMaxRecentEntries) + recent_launch_files.pop_back(); +} + +void CemuConfig::AddRecentNfcFile(std::wstring_view file) +{ + // insert into front + recent_nfc_files.insert(recent_nfc_files.begin(), std::wstring{ file }); + RemoveDuplicatesKeepOrder(recent_nfc_files); + // keep maximum of entries + while (recent_nfc_files.size() > kMaxRecentEntries) + recent_nfc_files.pop_back(); +} \ No newline at end of file diff --git a/src/config/CemuConfig.h b/src/config/CemuConfig.h new file mode 100644 index 00000000..247a0a83 --- /dev/null +++ b/src/config/CemuConfig.h @@ -0,0 +1,463 @@ +#pragma once + +#include "ConfigValue.h" +#include "XMLConfig.h" +#include "util/math/vector2.h" +#include "Cafe/Account/Account.h" + +#include + +struct GameEntry +{ + GameEntry() = default; + + std::wstring rpx_file; + std::wstring legacy_name; + std::string product_code; + std::string company_code; + + std::string custom_name; + + sint32 icon = -1; + sint32 icon_small = -1; + sint32 legacy_version = 0; + sint32 legacy_update_version = -1; + sint32 legacy_dlc_version = -1; + uint64 title_id = 0; + uint32 legacy_region = 0; + + std::wstring save_folder; + std::wstring update_folder; + std::wstring dlc_folder; + + uint64 legacy_time_played = 0; + uint64 legacy_last_played = 0; + + bool favorite = false; + + bool is_update = false; +}; + +struct GraphicPackEntry +{ + GraphicPackEntry(std::wstring_view filename,std::string_view category, std::string_view preset) + : filename(filename) + { + presets[std::string{category}] = preset; + } + + GraphicPackEntry(std::wstring_view filename, std::string_view preset) + : filename(filename) + { + presets[""] = preset; + } + + GraphicPackEntry(std::wstring filename) + : filename(std::move(filename)) + {} + + struct Preset + { + std::string name; + std::string category; + }; + std::unordered_map presets; // category, active_preset + + std::wstring filename; + bool enabled = true; +}; + +enum GraphicAPI +{ + kOpenGL = 0, + kVulkan, +}; + +enum AudioChannels +{ + kMono = 0, + kStereo, + kSurround, +}; + +enum UpscalingFilter +{ + kLinearFilter, + kBicubicFilter, + kBicubicHermiteFilter, + kNearestNeighborFilter, +}; + +enum FullscreenScaling +{ + kKeepAspectRatio, + kStretch, +}; + +enum class ScreenPosition +{ + kDisabled = 0, + kTopLeft, + kTopCenter, + kTopRight, + kBottomLeft, + kBottomCenter, + kBottomRight, +}; + +enum class PrecompiledShaderOption +{ + Auto, + Enable, + Disable, +}; +ENABLE_ENUM_ITERATORS(PrecompiledShaderOption, PrecompiledShaderOption::Auto, PrecompiledShaderOption::Disable); + +enum class AccurateShaderMulOption +{ + False = 0, // ignore non-ieee MUL special cases + True = 1, // fully emulate non-ieee MUL special cases + Min = 2, // similar to true, but avoids conditionals (instead relies on min() and abs()) +}; +ENABLE_ENUM_ITERATORS(AccurateShaderMulOption, AccurateShaderMulOption::False, AccurateShaderMulOption::Min); + +enum class CPUMode +{ + SinglecoreInterpreter = 0, + SinglecoreRecompiler = 1, + DualcoreRecompiler = 2, // deprecated and not used anymore + MulticoreRecompiler = 3, + Auto = 4, +}; +ENABLE_ENUM_ITERATORS(CPUMode, CPUMode::SinglecoreInterpreter, CPUMode::Auto); + + +enum class CPUModeLegacy +{ + SinglecoreInterpreter = 0, + SinglecoreRecompiler = 1, + DualcoreRecompiler = 2, + TriplecoreRecompiler = 3, + Auto = 4 +}; +ENABLE_ENUM_ITERATORS(CPUModeLegacy, CPUModeLegacy::SinglecoreInterpreter, CPUModeLegacy::Auto); + +enum class CafeConsoleRegion +{ + JPN = 0x1, + USA = 0x2, + EUR = 0x4, + AUS_DEPR = 0x8, + CHN = 0x10, + KOR = 0x20, + TWN = 0x40, + Auto = 0xFF, +}; +ENABLE_BITMASK_OPERATORS(CafeConsoleRegion); + +enum class CafeConsoleLanguage +{ + JA = 0, + EN = 1, + FR = 2, + DE = 3, + IT = 4, + ES = 5, + ZH = 6, + KO = 7, + NL = 8, + PT = 9, + RU = 10, + TW = 11, +}; +ENABLE_ENUM_ITERATORS(CafeConsoleLanguage, CafeConsoleLanguage::JA, CafeConsoleLanguage::TW); + +enum class CrashDump +{ + Disabled, + Lite, + Full +}; +ENABLE_ENUM_ITERATORS(CrashDump, CrashDump::Disabled, CrashDump::Full); + +template <> +struct fmt::formatter : formatter { + template + auto format(const PrecompiledShaderOption c, FormatContext &ctx) { + string_view name; + switch (c) + { + case PrecompiledShaderOption::Auto: name = "auto"; break; + case PrecompiledShaderOption::Enable: name = "true"; break; + case PrecompiledShaderOption::Disable: name = "false"; break; + default: name = "unknown"; break; + } + return formatter::format(name, ctx); + } +}; +template <> +struct fmt::formatter : formatter { + template + auto format(const AccurateShaderMulOption c, FormatContext &ctx) { + string_view name; + switch (c) + { + case AccurateShaderMulOption::True: name = "true"; break; + case AccurateShaderMulOption::False: name = "false"; break; + case AccurateShaderMulOption::Min: name = "min"; break; + default: name = "unknown"; break; + } + return formatter::format(name, ctx); + } +}; +template <> +struct fmt::formatter : formatter { + template + auto format(const CPUMode c, FormatContext &ctx) { + string_view name; + switch (c) + { + case CPUMode::SinglecoreInterpreter: name = "Single-core interpreter"; break; + case CPUMode::SinglecoreRecompiler: name = "Single-core recompiler"; break; + case CPUMode::DualcoreRecompiler: name = "Dual-core recompiler"; break; + case CPUMode::MulticoreRecompiler: name = "Multi-core recompiler"; break; + case CPUMode::Auto: name = "Auto"; break; + default: name = "unknown"; break; + } + return formatter::format(name, ctx); + } +}; +template <> +struct fmt::formatter : formatter { + template + auto format(const CPUModeLegacy c, FormatContext &ctx) { + string_view name; + switch (c) + { + case CPUModeLegacy::SinglecoreInterpreter: name = "Singlecore-Interpreter"; break; + case CPUModeLegacy::SinglecoreRecompiler: name = "Singlecore-Recompiler"; break; + case CPUModeLegacy::DualcoreRecompiler: name = "Dualcore-Recompiler"; break; + case CPUModeLegacy::TriplecoreRecompiler: name = "Triplecore-Recompiler"; break; + case CPUModeLegacy::Auto: name = "Auto"; break; + default: name = "unknown"; break; + } + return formatter::format(name, ctx); + } +}; +template <> +struct fmt::formatter : formatter { + template + auto format(const CafeConsoleRegion v, FormatContext &ctx) { + string_view name; + switch (v) + { + case CafeConsoleRegion::JPN: name = "Japan"; break; + case CafeConsoleRegion::USA: name = "USA"; break; + case CafeConsoleRegion::EUR: name = "Europe"; break; + case CafeConsoleRegion::AUS_DEPR: name = "Australia"; break; + case CafeConsoleRegion::CHN: name = "China"; break; + case CafeConsoleRegion::KOR: name = "Korea"; break; + case CafeConsoleRegion::TWN: name = "Taiwan"; break; + case CafeConsoleRegion::Auto: name = "Auto"; break; + default: name = "many"; break; + + } + return formatter::format(name, ctx); + } +}; +template <> +struct fmt::formatter : formatter { + template + auto format(const CafeConsoleLanguage v, FormatContext &ctx) { + string_view name; + switch (v) + { + case CafeConsoleLanguage::JA: name = "Japanese"; break; + case CafeConsoleLanguage::EN: name = "English"; break; + case CafeConsoleLanguage::FR: name = "French"; break; + case CafeConsoleLanguage::DE: name = "German"; break; + case CafeConsoleLanguage::IT: name = "Italian"; break; + case CafeConsoleLanguage::ES: name = "Spanish"; break; + case CafeConsoleLanguage::ZH: name = "Chinese"; break; + case CafeConsoleLanguage::KO: name = "Korean"; break; + case CafeConsoleLanguage::NL: name = "Dutch"; break; + case CafeConsoleLanguage::PT: name = "Portugese"; break; + case CafeConsoleLanguage::RU: name = "Russian"; break; + case CafeConsoleLanguage::TW: name = "Taiwanese"; break; + default: name = "unknown"; break; + } + return formatter::format(name, ctx); + } +}; + +template <> +struct fmt::formatter : formatter { + template + auto format(const CrashDump v, FormatContext &ctx) { + string_view name; + switch (v) + { + case CrashDump::Disabled: name = "Disabled"; break; + case CrashDump::Lite: name = "Lite"; break; + case CrashDump::Full: name = "Full"; break; + default: name = "unknown"; break; + + } + return formatter::format(name, ctx); + } +}; + + +struct CemuConfig +{ + CemuConfig() + { + + }; + + CemuConfig(const CemuConfig&) = delete; + // + + // sets mlc path, updates permanent config value, saves config + void SetMLCPath(std::wstring_view path, bool save = true); + + ConfigValue log_flag{ 0 }; + ConfigValue advanced_ppc_logging{ false }; + + ConfigValue permanent_storage{ true }; + + ConfigValue language{ wxLANGUAGE_DEFAULT }; + ConfigValue use_discord_presence{ true }; + ConfigValue mlc_path {}; + ConfigValue fullscreen_menubar{ false }; + ConfigValue fullscreen{ false }; + + std::vector game_paths; + std::mutex game_cache_entries_mutex; + std::vector game_cache_entries; + + // optimized access + std::set game_cache_favorites; // per titleId + + struct _path_hash { + std::size_t operator()(const fs::path& path) const { + return fs::hash_value(path); + } + }; + // > + std::unordered_map, _path_hash> graphic_pack_entries; + + ConfigValueBounds cpu_mode{ CPUMode::Auto }; + ConfigValueBounds console_language{ CafeConsoleLanguage::EN }; + + // max 15 entries + static constexpr size_t kMaxRecentEntries = 15; + std::vector recent_launch_files; + std::vector recent_nfc_files; + + Vector2i window_position{-1,-1}; + Vector2i window_size{ -1,-1 }; + ConfigValue window_maximized; + + ConfigValue pad_open; + Vector2i pad_position{ -1,-1 }; + Vector2i pad_size{ -1,-1 }; + ConfigValue pad_maximized; + + ConfigValue check_update{false}; + ConfigValue save_screenshot{true}; + + ConfigValue did_show_vulkan_warning{false}; + ConfigValue did_show_graphic_pack_download{false}; + + int game_list_style = 0; + std::string game_list_column_order; + struct + { + int name = -3, version = -3, dlc = -3, game_time = -3, game_started = -3, region = -3; + } column_width{}; + + // graphics + ConfigValue graphic_api{ kVulkan }; + std::array graphic_device_uuid; + ConfigValue vsync{ 0 }; // 0 = off, 1+ = on depending on render backend + ConfigValue gx2drawdone_sync {true}; + ConfigValue render_upside_down{ false }; + ConfigValue async_compile{ false }; + + ConfigValue vk_accurate_barriers{ true }; + + struct + { + ScreenPosition position = ScreenPosition::kDisabled; + uint32 text_color = 0xFFFFFFFF; + sint32 text_scale = 100; + bool fps = true; + bool drawcalls = false; + bool cpu_usage = false; + bool cpu_per_core_usage = false; + bool ram_usage = false; + bool vram_usage = false; + bool debug = false; + } overlay{}; + + struct + { + ScreenPosition position = ScreenPosition::kTopLeft; + uint32 text_color = 0xFFFFFFFF; + sint32 text_scale = 100; + bool controller_profiles = true; + bool controller_battery = false; + bool shader_compiling = true; + bool friends = true; + } notification{}; + + ConfigValue upscale_filter{kBicubicFilter}; + ConfigValue downscale_filter{kLinearFilter}; + ConfigValue fullscreen_scaling{kKeepAspectRatio}; + + // audio + sint32 audio_api = 0; + sint32 audio_delay = 2; + AudioChannels tv_channels = kStereo, pad_channels = kStereo; + sint32 tv_volume = 50, pad_volume = 0; + std::wstring tv_device{ L"default" }, pad_device; + + // account + struct + { + ConfigValueBounds m_persistent_id{ Account::kMinPersistendId, Account::kMinPersistendId, 0xFFFFFFFF }; + ConfigValue online_enabled{false}; + }account{}; + + // input + struct + { + ConfigValue host{"127.0.0.1"}; + ConfigValue port{ 26760 }; + }dsu_client{}; + + // debug + ConfigValueBounds crash_dump{ CrashDump::Disabled }; + + void Load(XMLConfigParser& parser); + void Save(XMLConfigParser& parser); + + void AddRecentlyLaunchedFile(std::wstring_view file); + void AddRecentNfcFile(std::wstring_view file); + + bool IsGameListFavorite(uint64 titleId); + void SetGameListFavorite(uint64 titleId, bool isFavorite); + bool GetGameListCustomName(uint64 titleId, std::string& customName); + void SetGameListCustomName(uint64 titleId, std::string customName); + + private: + GameEntry* GetGameEntryByTitleId(uint64 titleId); + GameEntry* CreateGameEntry(uint64 titleId); +}; + +typedef XMLDataConfig XMLCemuConfig_t; +extern XMLCemuConfig_t g_config; +inline CemuConfig& GetConfig() { return g_config.data(); } + + diff --git a/src/config/ConfigValue.h b/src/config/ConfigValue.h new file mode 100644 index 00000000..b53992de --- /dev/null +++ b/src/config/ConfigValue.h @@ -0,0 +1,250 @@ +#pragma once + +#include "Common/enumFlags.h" + +#include +#include + +template +class ConfigValueAtomic +{ +public: + constexpr ConfigValueAtomic() + : m_value(TType()), m_init_value(TType()) {} + + constexpr ConfigValueAtomic(const TType& init_value) + : m_value(init_value), m_init_value(init_value) {} + + constexpr ConfigValueAtomic(TType&& init_value) + : m_value(init_value), m_init_value(init_value) {} + + ConfigValueAtomic(const ConfigValueAtomic& v) + : m_value(v.GetValue()), m_init_value(v.m_init_value) {} + + ConfigValueAtomic& operator=(const ConfigValueAtomic& v) + { + SetValue(v.GetValue()); + return *this; + } + + ConfigValueAtomic& operator=(const TType& v) + { + SetValue(v); + return *this; + } + + ConfigValueAtomic& operator=(TType&& v) + { + SetValue(v); + return *this; + } + + [[nodiscard]] TType GetValue() const { return m_value.load(); } + void SetValue(const TType& v) { m_value = v; } + + [[nodiscard]] const TType& GetInitValue() const { return m_init_value; } + operator TType() const { return m_value.load(); } + +private: + std::atomic m_value; + const TType m_init_value; +}; + +template +class ConfigValueNoneAtomic +{ +public: + constexpr ConfigValueNoneAtomic() + : m_value({}), m_init_value({}) {} + + constexpr ConfigValueNoneAtomic(const TType& init_value) + : m_value(init_value), m_init_value(init_value) {} + + constexpr ConfigValueNoneAtomic(TType&& init_value) + : m_value(init_value), m_init_value(init_value) {} + + ConfigValueNoneAtomic(const ConfigValueNoneAtomic& v) + : m_value(v.GetValue()), m_init_value(v.GetInitValue()) {} + + ConfigValueNoneAtomic& operator=(const ConfigValueNoneAtomic& v) + { + SetValue(v.GetValue()); + return *this; + } + + ConfigValueNoneAtomic& operator=(const TType& v) + { + SetValue(v); + return *this; + } + + ConfigValueNoneAtomic& operator=(TType&& v) + { + SetValue(v); + return *this; + } + + [[nodiscard]] TType GetValue() const + { + std::shared_lock lock(m_mutex); + return m_value; + } + + void SetValue(const TType& v) + { + std::lock_guard lock(m_mutex); + m_value = v; + } + + template >> + void SetValue(std::string_view v) + { + std::lock_guard lock(m_mutex); + m_value = v; + } + + template >> + void SetValue(std::wstring_view v) + { + std::lock_guard lock(m_mutex); + m_value = v; + } + + [[nodiscard]] const TType& GetInitValue() const { return m_init_value; } + + [[nodiscard]] operator TType() const + { + return GetValue(); + } + +protected: + mutable std::shared_mutex m_mutex; + TType m_value; + const TType m_init_value; +}; + +template +constexpr bool is_atomic_type_v = std::is_trivially_copyable_v && std::is_copy_constructible_v && std::is_move_constructible_v && std::is_copy_assignable_v && std::is_move_assignable_v; + +template +class ConfigValue : public std::conditional, ConfigValueAtomic, ConfigValueNoneAtomic>::type +{ +public: + using base_type = typename std::conditional, ConfigValueAtomic, ConfigValueNoneAtomic>::type; + using base_type::base_type; + + ConfigValue& operator=(const ConfigValue& v) + { + base_type::operator=(v.GetValue()); + return *this; + } + + ConfigValue& operator=(const TType& v) + { + base_type::operator=(v); + return *this; + } + + ConfigValue& operator=(TType&& v) + { + base_type::operator=(v); + return *this; + } +}; + +template +class ConfigValueBounds : public ConfigValue +{ +public: + using base_type = ConfigValue; + + constexpr ConfigValueBounds(const TType& min, const TType& init_value, const TType& max) + :base_type(init_value), m_min_value(min), m_max_value(max) + { + assert(m_min_value <= init_value && init_value <= m_max_value); + } + + constexpr ConfigValueBounds(TType&& min, TType&& init_value, TType&& max) + : base_type(std::forward(init_value)), m_min_value(min), m_max_value(max) + { + assert(m_min_value <= init_value && init_value <= m_max_value); + } + + // init from enum with iterators + template::value && EnableEnumIterators::enable, TType>> + constexpr ConfigValueBounds() + : base_type(), m_min_value(begin(TEnum{})), m_max_value(rbegin(TEnum{})) + { + assert(m_min_value <= this->GetInitValue() && this->GetInitValue() <= m_max_value); + } + + template::value && EnableEnumIterators::enable, TType>> + constexpr ConfigValueBounds(const TType& init_value) + : base_type(std::forward(init_value)), m_min_value(begin(init_value)), m_max_value(rbegin(init_value)) + { + assert(m_min_value <= init_value && init_value <= m_max_value); + } + + template::value && EnableEnumIterators::enable, TType>> + constexpr ConfigValueBounds(TType&& init_value) + : base_type(std::forward(init_value)), m_min_value(begin(init_value)), m_max_value(rbegin(init_value)) + { + assert(m_min_value <= init_value && init_value <= m_max_value); + } + + ConfigValueBounds& operator=(const ConfigValueBounds& v) + { + base_type::operator=(v.GetValue()); + + const auto& value = this->GetValue(); + if (value < GetMin() || value > GetMax()) + base_type::SetValue(this->GetInitValue()); + + return *this; + }; + + ConfigValueBounds& operator=(const TType& v) + { + base_type::operator=(v); + + const auto& value = this->GetValue(); + if (value < GetMin() || value > GetMax()) + base_type::SetValue(this->GetInitValue()); + + return *this; + }; + + ConfigValueBounds& operator=(TType&& v) + { + base_type::operator=(v); + + const auto& value = this->GetValue(); + if (value < GetMin() || value > GetMax()) + base_type::SetValue(this->GetInitValue()); + + return *this; + }; + + [[nodiscard]] const TType& GetMax() const { return m_max_value; } + [[nodiscard]] const TType& GetMin() const { return m_min_value; } + +private: + const TType m_min_value; + const TType m_max_value; +}; + +template +struct fmt::formatter< ConfigValue > : formatter { + template + auto format(const ConfigValue& v, FormatContext& ctx) { + return formatter::format(v.GetValue(), ctx); + } +}; + +template +struct fmt::formatter< ConfigValueBounds > : formatter { + template + auto format(const ConfigValueBounds& v, FormatContext& ctx) { + return formatter::format(v.GetValue(), ctx); + } +}; \ No newline at end of file diff --git a/src/config/LaunchSettings.cpp b/src/config/LaunchSettings.cpp new file mode 100644 index 00000000..322999fd --- /dev/null +++ b/src/config/LaunchSettings.cpp @@ -0,0 +1,262 @@ +#include "LaunchSettings.h" + +#include "util/helpers/helpers.h" +#include "Cafe/Account/Account.h" + +#include "Cafe/OS/libs/coreinit/coreinit.h" + +#include "boost/program_options.hpp" +#include + +#include "config/ActiveSettings.h" +#include "util/crypto/aes128.h" + +#include "Cafe/Filesystem/FST/FST.h" + +void requireConsole(); + +bool LaunchSettings::HandleCommandline(const wchar_t* lpCmdLine) +{ + #if BOOST_OS_WINDOWS + const std::vector args = boost::program_options::split_winmain(lpCmdLine); + return HandleCommandline(args); + #else + cemu_assert_unimplemented(); + return false; + #endif +} + +bool LaunchSettings::HandleCommandline(int argc, wchar_t* argv[]) +{ + std::vector args; + args.reserve(argc); + for(int i = 0; i < argc; ++i) + { + args.emplace_back(argv[i]); + } + + return HandleCommandline(args); +} + +bool LaunchSettings::HandleCommandline(int argc, char* argv[]) +{ + std::vector args; + args.reserve(argc); + for(int i = 0; i < argc; ++i) + { + args.emplace_back(boost::nowide::widen(argv[i])); + } + + return HandleCommandline(args); +} + +bool LaunchSettings::HandleCommandline(const std::vector& args) +{ + namespace po = boost::program_options; + po::options_description desc{ "Launch options" }; + desc.add_options() + ("help,h", "This help screen") + ("version,v", "Displays the version of Cemu") + + ("game,g", po::wvalue(), "Path of game to launch") + ("mlc,m", po::wvalue(), "Custom mlc folder location") + + ("fullscreen,f", po::value()->implicit_value(true), "Launch games in fullscreen mode") + ("ud,u", po::value()->implicit_value(true), "Render output upside-down") + + ("account,a", po::value(), "Persistent id of account") + + ("force-interpreter", po::value()->implicit_value(true), "Force interpreter CPU emulation, disables recompiler") + + ("act-url", po::value(), "URL prefix for account server") + ("ecs-url", po::value(), "URL for ECS service"); + + + po::options_description hidden{ "Hidden options" }; + hidden.add_options() + ("nsight", po::value()->implicit_value(true), "NSight debugging options") + ("legacy", po::value()->implicit_value(true), "Intel legacy graphic mode"); + + po::options_description extractor{ "Extractor tool" }; + extractor.add_options() + ("extract,e", po::wvalue(), "Path to WUD or WUX file for extraction") + ("path,p", po::value(), "Path of file to extract (for example meta/meta.xml)") + ("output,o", po::wvalue(), "Output path for extracted file."); + + po::options_description all; + all.add(desc).add(hidden).add(extractor); + + po::options_description visible; + visible.add(desc).add(extractor); + + try + { + po::wcommand_line_parser parser{ args }; + parser.allow_unregistered().options(all).style(po::command_line_style::allow_long | po::command_line_style::allow_short | po::command_line_style::allow_dash_for_short | po::command_line_style::case_insensitive | + po::command_line_style::long_allow_next | + po::command_line_style::short_allow_next | + po::command_line_style::allow_long_disguise); + + const auto parsed_options = parser.run(); + + po::variables_map vm; + po::store(parsed_options, vm); + notify(vm); + + if (vm.count("help")) + { + requireConsole(); + std::cout << visible << std::endl; + return false; // exit in main + } + if (vm.count("version")) + { + requireConsole(); + std::string versionStr = fmt::format("{}.{}{}", EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_SUFFIX); + std::cout << versionStr << std::endl; + return false; // exit in main + } + + if (vm.count("game")) + { + std::wstring tmp = vm["game"].as(); + // workaround for boost command_line_parser not trimming token for short name parameters despite short_allow_adjacent + if (tmp.size() > 0 && tmp.front() == '=') + tmp.erase(tmp.begin()+0); + + s_load_game_file = tmp; + } + + if (vm.count("mlc")) + { + std::wstring tmp = vm["mlc"].as(); + // workaround for boost command_line_parser not trimming token for short name parameters despite short_allow_adjacent + if (tmp.size() > 0 && tmp.front() == '=') + tmp.erase(tmp.begin() + 0); + + s_mlc_path = tmp; + } + + if (vm.count("account")) + { + const auto id = ConvertString(vm["account"].as(), 16); + if (id >= Account::kMinPersistendId) + s_persistent_id = id; + } + + if (vm.count("fullscreen")) + s_fullscreen = vm["fullscreen"].as(); + if (vm.count("ud")) + s_render_upside_down = vm["ud"].as(); + + if (vm.count("nsight")) + s_nsight_mode = vm["nsight"].as(); + if (vm.count("legacy")) + s_force_intel_legacy = vm["legacy"].as(); + + if(vm.count("force-interpreter")) + s_force_interpreter = vm["force-interpreter"].as(); + + std::wstring extract_path, log_path; + std::string output_path; + if (vm.count("extract")) + extract_path = vm["extract"].as(); + if (vm.count("path")) + output_path = vm["path"].as(); + if (vm.count("output")) + log_path = vm["output"].as(); + + // urls + if (vm.count("act-url")) + { + serviceURL_ACT = vm["act-url"].as(); + if (serviceURL_ACT.size() > 0 && serviceURL_ACT.back() == '/') + serviceURL_ACT.pop_back(); + } + if (vm.count("ecs-url")) + serviceURL_ECS = vm["ecs-url"].as(); + + if(!extract_path.empty()) + { + ExtractorTool(extract_path, output_path, log_path); + return false; + } + + return true; + } + catch (const std::exception& ex) + { + std::string errorMsg; + errorMsg.append("Error while trying to parse command line parameter:\n"); + errorMsg.append(ex.what()); + wxMessageBox(errorMsg, wxT("Parameter error"), wxICON_ERROR); + //cemuLog_log(LogType::Force, ex.what()); + //std::cout << "Command line parameter error: " << ex.what() << std::endl; + return false; + } + +} + +bool LaunchSettings::ExtractorTool(std::wstring_view wud_path, std::string_view output_path, std::wstring_view log_path) +{ + // extracting requires path of file + if (output_path.empty()) + { + requireConsole(); + puts("Cannot extract files because no source path was specified (-p)\n"); + return false; + } + // mount wud + AES128_init(); + FSTVolume* srcVolume = FSTVolume::OpenFromDiscImage(fs::path(wud_path)); + if (!srcVolume) + { + requireConsole(); + puts(fmt::format("Unable to open \"%s\"\n", fs::path(wud_path).generic_string()).c_str()); + return false; + } + bool fileFound = false; + std::vector fileData = srcVolume->ExtractFile(output_path, &fileFound); + delete srcVolume; + if (!fileFound) + { + requireConsole(); + puts(fmt::format("Unable to read file \"%s\"\n", output_path).c_str()); + return false; + } + // output on console (if no output path was specified) + if (!log_path.empty()) + { + try + { + fs::path filename(std::wstring{ log_path }); + + filename /= boost::nowide::widen(std::string(output_path)); + + fs::path p = filename; + p.remove_filename(); + + fs::create_directories(p); + std::ofstream file(filename, std::ios::out | std::ios::binary); + file.write((const char*)fileData.data(), fileData.size()); + file.flush(); + file.close(); + } + catch (const std::exception& ex) + { + forceLog_printf("can't write file: %s", ex.what()); + puts(fmt::format("can't write file: %s\n", ex.what()).c_str()); + } + } + else + { + // output to console + requireConsole(); + printf("%.*s", (int)fileData.size(), fileData.data()); + fflush(stdout); + } + + return true; +} + + diff --git a/src/config/LaunchSettings.h b/src/config/LaunchSettings.h new file mode 100644 index 00000000..299dd72c --- /dev/null +++ b/src/config/LaunchSettings.h @@ -0,0 +1,57 @@ +#pragma once + +#include +#include + +class LaunchSettings +{ +public: + // winmain + static bool HandleCommandline(const wchar_t* lpCmdLine); + // wmain + static bool HandleCommandline(int argc, wchar_t* argv[]); + // main (unix) + static bool HandleCommandline(int argc, char* argv[]); + + static bool HandleCommandline(const std::vector& args); + + static std::optional GetLoadFile() { return s_load_game_file; } + static std::optional GetMLCPath() { return s_mlc_path; } + + static std::optional RenderUpsideDownEnabled() { return s_render_upside_down; } + static std::optional FullscreenEnabled() { return s_fullscreen; } + + static bool NSightModeEnabled() { return s_nsight_mode; } + static bool ForceIntelLegacyEnabled() { return s_force_intel_legacy; } + + static bool ForceInterpreter() { return s_force_interpreter; }; + + static std::optional GetPersistentId() { return s_persistent_id; } + + static std::string GetActURLPrefix() { return serviceURL_ACT; } + static std::string GetServiceURL_ecs() { return serviceURL_ECS; } + +private: + inline static std::optional s_load_game_file{}; + inline static std::optional s_mlc_path{}; + + inline static std::optional s_render_upside_down{}; + inline static std::optional s_fullscreen{}; + + inline static bool s_nsight_mode = false; + inline static bool s_force_intel_legacy = false; + + inline static bool s_force_interpreter = false; + + inline static std::optional s_persistent_id{}; + + // service URLS + inline static std::string serviceURL_ACT = "https://account.nintendo.net"; + inline static std::string serviceURL_ECS = "https://ecs.wup.shop.nintendo.net/ecs/services/ECommerceSOAP"; + // todo - npts and other boss urls + + + static bool ExtractorTool(std::wstring_view wud_path, std::string_view output_path, std::wstring_view log_path); +}; + + diff --git a/src/config/PermanentConfig.cpp b/src/config/PermanentConfig.cpp new file mode 100644 index 00000000..20a44d28 --- /dev/null +++ b/src/config/PermanentConfig.cpp @@ -0,0 +1,65 @@ +#include "PermanentConfig.h" + +#include "pugixml.hpp" + +#include "PermanentStorage.h" + +struct xml_string_writer : pugi::xml_writer +{ + std::string result; + + void write(const void* data, size_t size) override + { + result.append(static_cast(data), size); + } +}; + +std::string PermanentConfig::ToXMLString() const noexcept +{ + pugi::xml_document doc; + doc.append_child(pugi::node_declaration).append_attribute("encoding") = "UTF-8"; + auto root = doc.append_child("config"); + root.append_child("MlcPath").text().set(this->custom_mlc_path.c_str()); + + xml_string_writer writer; + doc.save(writer); + return writer.result; +} + +PermanentConfig PermanentConfig::FromXMLString(std::string_view str) noexcept +{ + PermanentConfig result{}; + + pugi::xml_document doc; + if(doc.load_buffer(str.data(), str.size())) + { + result.custom_mlc_path = doc.select_node("/config/MlcPath").node().text().as_string(); + } + + return result; +} + +PermanentConfig PermanentConfig::Load() +{ + PermanentStorage storage; + + const auto str = storage.ReadFile(kFileName); + if (!str.empty()) + return FromXMLString(str); + + return {}; +} + +bool PermanentConfig::Store() noexcept +{ + try + { + PermanentStorage storage; + storage.WriteStringToFile(kFileName, ToXMLString()); + } + catch (...) + { + return false; + } + return true; +} diff --git a/src/config/PermanentConfig.h b/src/config/PermanentConfig.h new file mode 100644 index 00000000..8c134747 --- /dev/null +++ b/src/config/PermanentConfig.h @@ -0,0 +1,18 @@ +#pragma once + +#include "PermanentStorage.h" + +struct PermanentConfig +{ + static constexpr const char* kFileName = "perm_setting.xml"; + + std::string custom_mlc_path; + + [[nodiscard]] std::string ToXMLString() const noexcept; + static PermanentConfig FromXMLString(std::string_view str) noexcept; + + // gets from permanent storage + static PermanentConfig Load(); + // saves to permanent storage + bool Store() noexcept; +}; diff --git a/src/config/PermanentStorage.cpp b/src/config/PermanentStorage.cpp new file mode 100644 index 00000000..597e210c --- /dev/null +++ b/src/config/PermanentStorage.cpp @@ -0,0 +1,76 @@ +#include "PermanentStorage.h" +#include "config/CemuConfig.h" +#include "util/helpers/SystemException.h" + +PermanentStorage::PermanentStorage() +{ + if (!GetConfig().permanent_storage) + throw PSDisabledException(); + + const char* appdata = std::getenv("LOCALAPPDATA"); + if (!appdata) + throw std::runtime_error("can't get LOCALAPPDATA"); + m_storage_path = appdata; + m_storage_path /= "Cemu"; + + fs::create_directories(m_storage_path); +} + +PermanentStorage::~PermanentStorage() +{ + if (m_remove_storage) + { + std::error_code ec; + fs::remove_all(m_storage_path, ec); + if (ec) + { + SystemException ex(ec); + forceLog_printf("can't remove permanent storage: %s", ex.what()); + } + } +} + +void PermanentStorage::ClearAllFiles() const +{ + fs::remove_all(m_storage_path); + fs::create_directories(m_storage_path); +} + +void PermanentStorage::RemoveStorage() +{ + m_remove_storage = true; +} + +void PermanentStorage::WriteStringToFile(std::string_view filename, std::string_view content) +{ + const auto name = m_storage_path.append(filename.data(), filename.data() + filename.size()); + std::ofstream file(name.string()); + file.write(content.data(), (uint32_t)content.size()); +} + +std::string PermanentStorage::ReadFile(std::string_view filename) noexcept +{ + try + { + const auto name = m_storage_path.append(filename.data(), filename.data() + filename.size()); + std::ifstream file(name, std::ios::in | std::ios::ate); + if (!file.is_open()) + return {}; + + const auto end = file.tellg(); + file.seekg(0, std::ios::beg); + const auto file_size = end - file.tellg(); + if (file_size == 0) + return {}; + + std::string result; + result.resize(file_size); + file.read(result.data(), file_size); + return result; + } + catch (...) + { + return {}; + } + +} diff --git a/src/config/PermanentStorage.h b/src/config/PermanentStorage.h new file mode 100644 index 00000000..3cda3d6d --- /dev/null +++ b/src/config/PermanentStorage.h @@ -0,0 +1,27 @@ +#pragma once + +// disabled by config +class PSDisabledException : public std::runtime_error +{ +public: + PSDisabledException() + : std::runtime_error("permanent storage is disabled by user") {} +}; + +class PermanentStorage +{ +public: + PermanentStorage(); + ~PermanentStorage(); + + void ClearAllFiles() const; + // flags storage to be removed on destruction + void RemoveStorage(); + + void WriteStringToFile(std::string_view filename, std::string_view content); + std::string ReadFile(std::string_view filename) noexcept; + +private: + fs::path m_storage_path; + bool m_remove_storage = false; +}; \ No newline at end of file diff --git a/src/config/XMLConfig.h b/src/config/XMLConfig.h new file mode 100644 index 00000000..d626484d --- /dev/null +++ b/src/config/XMLConfig.h @@ -0,0 +1,476 @@ +#pragma once + +#include "util/tinyxml2/tinyxml2.h" +#include "Common/filestream.h" +#include "config/ConfigValue.h" + +#include +#include + +class XMLConfigParser +{ +public: + XMLConfigParser(tinyxml2::XMLDocument* document) + : m_document(document), m_current_element(nullptr), m_is_root(true) {} + +private: + XMLConfigParser(tinyxml2::XMLDocument* document, tinyxml2::XMLElement* element) + : m_document(document), m_current_element(element), m_is_root(false) {} + +public: + template + auto get(const char* name, T default_value = {}) + { + if constexpr(std::is_enum_v) + return static_cast(get(name, static_cast>(default_value))); + + const auto* element = m_current_element + ? m_current_element->FirstChildElement(name) + : m_document->FirstChildElement(name); + + if (element == nullptr) + return default_value; + + if constexpr (std::is_same_v) + return element->BoolText(default_value); + else if constexpr (std::is_same_v) + return element->FloatText(default_value); + else if constexpr (std::is_same_v) + return element->IntText(default_value); + else if constexpr (std::is_same_v) + return element->UnsignedText(default_value); + else if constexpr (std::is_same_v) + return element->Int64Text(default_value); + else if constexpr (std::is_same_v) // doesnt support real uint64... + return (uint64)element->Int64Text((sint64)default_value); + else if constexpr (std::is_same_v || std::is_same_v) + { + const char* text = element->GetText(); + return text ? text : default_value; + } + + return default_value; + } + + template + T get(const char* name, const ConfigValue& default_value) + { + return get(name, default_value.GetInitValue()); + } + + template + T get(const char* name, const ConfigValueBounds& default_value) + { + return get(name, default_value.GetInitValue()); + } + + template + auto get_attribute(const char* name, T default_value = {}) + { + if constexpr (std::is_enum_v) + return static_cast(get_attribute(name, static_cast>(default_value))); + + if (m_current_element == nullptr) + return default_value; + + if constexpr (std::is_same_v) + return m_current_element->BoolAttribute(name, default_value); + else if constexpr (std::is_same_v) + return m_current_element->FloatAttribute(name, default_value); + else if constexpr (std::is_same_v) + return m_current_element->IntAttribute(name, default_value); + else if constexpr (std::is_same_v) + return m_current_element->UnsignedAttribute(name, default_value); + else if constexpr (std::is_same_v) + return m_current_element->Int64Attribute(name, default_value); + else if constexpr (std::is_same_v) // doesnt support real uint64... + return (uint64)m_current_element->Int64Attribute(name, (sint64)default_value); + else if constexpr (std::is_same_v || std::is_same_v) + { + const char* text = m_current_element->Attribute(name); + return text ? text : default_value; + } + + return default_value; + } + + template + T get_attribute(const char* name, const ConfigValue& default_value) + { + return get_attribute(name, default_value.GetInitValue()); + } + + template + T get_attribute(const char* name, const ConfigValueBounds& default_value) + { + return get_attribute(name, default_value.GetInitValue()); + } + + int char2int(char input) + { + if(input >= '0' && input <= '9') + return input - '0'; + if(input >= 'A' && input <= 'F') + return input - 'A' + 10; + if(input >= 'a' && input <= 'f') + return input - 'a' + 10; + throw std::invalid_argument("Invalid input string"); + } + + template + std::array& get(const char* name, std::array& arr) + { + arr = {}; + const auto element = m_current_element + ? m_current_element->FirstChildElement(name) + : m_document->FirstChildElement(name); + if (element == nullptr) + return arr; + + const char* text = element->GetText(); + if(text) + { + assert(strlen(text) == (arr.size() * 2)); + std::istringstream iss(text); + for(int i = 0; i < arr.size(); ++i) + { + arr[i] = (char2int(text[i*2]) << 4) + char2int(text[i * 2 + 1]); + } + } + + return arr; + } + + template + T value(T default_value = T()) + { + //peterBreak(); + return default_value; + } + + template <> + bool value(bool default_value) + { + return m_current_element ? m_current_element->BoolText(default_value) : default_value; + } + + template <> + float value(float default_value) + { + return m_current_element ? m_current_element->FloatText(default_value) : default_value; + } + + template <> + double value(double default_value) + { + return m_current_element ? m_current_element->DoubleText(default_value) : default_value; + } + + template <> + uint32 value(uint32 default_value) + { + return m_current_element ? m_current_element->UnsignedText(default_value) : default_value; + } + + template <> + sint32 value(sint32 default_value) + { + return m_current_element ? m_current_element->IntText(default_value) : default_value; + } + + template <> + uint64 value(uint64 default_value) + { + return m_current_element ? (uint64)m_current_element->Int64Text(default_value) : default_value; + } + + template <> + sint64 value(sint64 default_value) + { + return m_current_element ? m_current_element->Int64Text(default_value) : default_value; + } + + template <> + const char* value(const char* default_value) + { + if (m_current_element) + { + if (m_current_element->GetText()) + return m_current_element->GetText(); + } + + return default_value; + } + + template + void set(const char* name, const std::array& value) + { + auto element = m_document->NewElement(name); + + std::stringstream str; + for(const auto& v : value) + { + str << fmt::format("{:02x}", v); + } + + element->SetText(str.str().c_str()); + + if (m_current_element) + m_current_element->InsertEndChild(element); + else + m_document->InsertEndChild(element); + } + + template + void set(const char* name, T value) + { + auto* element = m_document->NewElement(name); + + if constexpr (std::is_enum::value) + element->SetText(fmt::format("{}", static_cast::type>(value)).c_str()); + else + element->SetText(fmt::format("{}", value).c_str()); + + if (m_current_element) + m_current_element->InsertEndChild(element); + else + m_document->InsertEndChild(element); + } + + template + void set(const char* name, const std::atomic& value) + { + set(name, value.load()); + } + + template <> + void set(const char* name, uint64 value) + { + set(name, (sint64)value); + } + + tinyxml2::XMLElement* GetCurrentElement() const { return m_current_element; } + + XMLConfigParser get(const char* name) const + { + const auto element = m_current_element + ? m_current_element->FirstChildElement(name) + : m_document->FirstChildElement(name); + return {m_document, element}; + } + + XMLConfigParser get(const char* name, const XMLConfigParser& parser) + { + const auto element = parser.m_current_element + ? parser.m_current_element->NextSiblingElement(name) + : parser.m_document->NextSiblingElement(name); + return { m_document, element }; + } + + XMLConfigParser set(const char* name) const + { + const auto element = m_document->NewElement(name); + if (m_current_element) + m_current_element->InsertEndChild(element); + else + m_document->InsertEndChild(element); + + return {m_document, element}; + } + + template + XMLConfigParser& set_attribute(const char* name, const T& value) + { + cemu_assert_debug(m_current_element != nullptr); + if (m_current_element) + m_current_element->SetAttribute(name, value); + + return *this; + } + + template + XMLConfigParser& set_attribute(const char* name, const ConfigValue& value) + { + return set_attribute(name, value.GetValue()); + } + + template + XMLConfigParser& set_attribute(const char* name, const ConfigValueBounds& value) + { + return set_attribute(name, value.GetValue()); + } + + template <> + XMLConfigParser& set_attribute(const char* name, const std::string& value) + { + return set_attribute(name, value.c_str()); + } + + + bool valid() const + { + if (m_is_root) + return m_document != nullptr; + + return m_document != nullptr && m_current_element != nullptr; + } + +private: + tinyxml2::XMLDocument* m_document; + tinyxml2::XMLElement* m_current_element; + bool m_is_root; +}; + +template +class XMLConfig +{ +public: + XMLConfig() = delete; + + XMLConfig(T& instance) + : m_instance(instance) + {} + + XMLConfig(T& instance, std::wstring_view filename) + : m_instance(instance), m_filename(filename) {} + + XMLConfig(const XMLConfig&) = delete; + + virtual ~XMLConfig() = default; + + bool Load() + { + if (L == nullptr) + return false; + + if (m_filename.empty()) + return false; + + return Load(m_filename); + } + + bool Load(const std::wstring& filename) + { + if (L == nullptr) + return false; + FileStream* fs = FileStream::openFile(filename.c_str()); + + if (fs == nullptr) + { + forceLogDebug_printfW(L"XMLConfig::Load > failed \"%s\" with error %d", filename.c_str(), errno); + return false; + } + std::vector xmlData; + xmlData.resize(fs->GetSize()); + fs->readData(xmlData.data(), xmlData.size()); + delete fs; + + tinyxml2::XMLDocument doc; + const tinyxml2::XMLError error = doc.Parse((const char*)xmlData.data(), xmlData.size()); + const bool success = error == tinyxml2::XML_SUCCESS; + if (error != 0) + { + forceLogDebug_printf("XMLConfig::Load > LoadFile %d", error); + } + if (success) + { + auto parser = XMLConfigParser(&doc); + (m_instance.*L)(parser); + } + return true; + } + + bool Save() + { + if (S == nullptr) + return false; + + if (m_filename.empty()) + return false; + + return Save(m_filename); + } + + bool Save(const std::wstring& filename) + { + if (S == nullptr) + return false; + + std::wstring tmp_name = fmt::format(L"{}_{}.tmp", filename,rand() % 1000); + std::error_code err; + fs::create_directories(fs::path(filename).parent_path(), err); + if (err) + { + forceLog_printf("can't create parent path for save file: %s", err.message().c_str()); + return false; + } + + FILE* file = nullptr; +#if BOOST_OS_WINDOWS > 0 + file = _wfopen(tmp_name.c_str(), L"wb"); +#else + file = fopen(boost::nowide::narrow(tmp_name).c_str(), "wb"); +#endif + if (file == nullptr) + { + forceLogDebug_printfW(L"XMLConfig::Save > failed \"%s\" with error %d", filename.c_str(), errno); + return false; + } + + tinyxml2::XMLDocument doc; + const auto declaration = doc.NewDeclaration(); + doc.InsertFirstChild(declaration); + + auto parser = XMLConfigParser(&doc); + (m_instance.*S)(parser); + + const tinyxml2::XMLError error = doc.SaveFile(file); + const bool success = error == tinyxml2::XML_SUCCESS; + if(error != 0) + forceLogDebug_printfW(L"XMLConfig::Save > SaveFile %d", error); + + fflush(file); + fclose(file); + + fs::rename(tmp_name, filename, err); + if(err) + { + forceLog_printf("can't save settings to file: %s", err.message().c_str()); + fs::remove(tmp_name, err); + } + + return success; + } + + [[nodiscard]] const std::wstring& GetFilename() const { return m_filename; } + void SetFilename(const std::wstring& filename) { m_filename = filename; } + + std::unique_lock Lock() { return std::unique_lock(m_mutex); } + +private: + T& m_instance; + std::wstring m_filename; + std::mutex m_mutex; +}; + +template +class XMLDataConfig : public XMLConfig +{ +public: + XMLDataConfig() + : XMLConfig::XMLConfig(m_data), m_data() {} + + XMLDataConfig(std::wstring_view filename) + : XMLConfig::XMLConfig(m_data, filename), m_data() {} + + XMLDataConfig(std::wstring_view filename, T init_data) + : XMLConfig::XMLConfig(m_data, filename), m_data(std::move(init_data)) {} + + XMLDataConfig(const XMLDataConfig& o) = delete; + + T& data() { return m_data; } + +private: + T m_data; +}; + diff --git a/src/exports.def b/src/exports.def new file mode 100644 index 00000000..fd824c4c --- /dev/null +++ b/src/exports.def @@ -0,0 +1,57 @@ +EXPORTS + cemuLog_log=?cemuLog_log@@YAXXZ + PPCCore_executeCallback=?PPCCore_executeCallback@@YAXI@Z + osLib_registerHLEFunction=?osLib_registerHLEFunction@@YAXPEBD0P6AXPEAUPPCInterpreter_t@@@Z@Z + gameProfile_load=?gameProfile_load@@YAXXZ + gameProfile_categoryBegin=?gameProfile_categoryBegin@@YAXPEAVIniParser@@@Z + gameProfile_getCurrentCategoryName=?gameProfile_getCurrentCategoryName@@YAPEADPEAVIniParser@@@Z + gameProfile_loadStringOption=?gameProfile_loadStringOption@@YAPEADPEAVIniParser@@PEAD@Z + gameProfile_loadBooleanOption=?gameProfile_loadBooleanOption@@YA_NPEAVIniParser@@PEADPEAUgameProfileBooleanOption_t@@@Z + gameProfile_loadIntegerNamedOption=?gameProfile_loadIntegerNamedOption@@YA_NPEAVIniParser@@PEADPEAUgameProfileIntegerOption_t@@HPEBUgpNamedOptionEntry_t@@H@Z + gameProfile_loadIntegerOption=?gameProfile_loadIntegerOption@@YA_NPEAVIniParser@@PEBDPEAUgameProfileIntegerOption_t@@HHH@Z + memory_init=?memory_init@@YAXXZ + memory_getBase=?memory_getBase@@YAPEAXXZ + wxMainWindowCreated=?wxMainWindowCreated@@YAPEAVwxTopLevelWindow@@PEAV1@IPEAVCemuApp@@@Z + wxEvtHandler_Initialize=?wxEvtHandler_Initialize@@YAPEAVwxEvtHandler@@PEAE@Z + wxEvtHandler_Connect=?wxEvtHandler_Connect@@YAXPEAVwxEvtHandler@@HHHP81@EAAXAEAVwxEvent@@@ZPEAVwxObject@@0@Z + wxEvtHandler_Disconnect=?wxEvtHandler_Disconnect@@YAXPEAVwxEvtHandler@@HHHP81@EAAXAEAVwxEvent@@@ZPEAVwxObject@@0@Z + wxGetEventByName=?wxGetEventByName@@YAHPEBD@Z + gameMeta_loadForCurrent=?gameMeta_loadForCurrent@@YAXXZ + gameMeta_getTitleId=?gameMeta_getTitleId@@YA_KXZ + PPCRecompiler_init=?PPCRecompiler_init@@YAXXZ + hasMOVBESupport=?hasMOVBESupport@@3_NA + hasLZCNTSupport=?hasLZCNTSupport@@3_NA + hasAVXSupport=?hasAVXSupport@@3_NA + ppcRecompilerInstanceData=?ppcRecompilerInstanceData@@3PEAUPPCRecompilerInstanceData_t@@EA + currentTLSModuleIndex=?_currentTLSModuleIndex@@3IA + rplModuleCount=?rplModuleCount@@3HA + rplModuleList=?rplModuleList@@3PAPEAURPLModule@@A + rpl_loadFromMem=?rpl_loadFromMem@@YAPEAURPLModule@@PEAEHPEAD@Z + loadSharedData=?loadSharedData@@YAIXZ + graphicPack_loadGraphicPackShaders=?graphicPack_loadGraphicPackShaders@@YAXPEAUgraphicPack_t@@PEA_W@Z + config_isGraphicPackEnabled=?config_isGraphicPackEnabled@@YA_N_K@Z + alwaysDisplayDRC=?alwaysDisplayDRC@@3_NA + ppcCyclesSince2000=?ppcCyclesSince2000@@3_KA + ppcMainThreadCycleCounter=?ppcMainThreadCycleCounter@@3_KC + GetTranslationWChar=?GetTranslationWChar@@YAPEB_WPEB_W@Z + ActivateGraphicPack=?Activate@GraphicPack2@@AEAA_NXZ + DeactivateGraphicPack=?Deactivate@GraphicPack2@@AEAA_NXZ + coreinitAPI_OSYieldThread=?coreinitAPI_OSYieldThread@@YAXXZ + GraphicPack2_GetFilename=?GraphicPack2_GetFilename@@YAPEB_WPEAVGraphicPack2@@@Z + GraphicPack2_GetName=?GraphicPack2_GetName@@YAPEBDPEAVGraphicPack2@@@Z + GraphicPack2_GetPath=?GraphicPack2_GetPath@@YAPEBDPEAVGraphicPack2@@@Z + GraphicPack2_GetDescription=?GraphicPack2_GetDescription@@YAPEBDPEAVGraphicPack2@@@Z + GraphicPack2_GetTitleIdCount=?GraphicPack2_GetTitleIdCount@@YA?BHPEAVGraphicPack2@@@Z + GraphicPack2_GetTitleIdList=?GraphicPack2_GetTitleIdList@@YAPEB_KPEAVGraphicPack2@@@Z + GraphicPack2_notifyActivate=?GraphicPack2_notifyActivate@@YAXPEAVGraphicPack2@@PEAVExpressionParser@@@Z + GraphicPack2_notifyDeactivate=?GraphicPack2_notifyDeactivate@@YAXPEAVGraphicPack2@@@Z + GraphicPack2_CreateExpressionParser=?GraphicPack2_CreateExpressionParser@@YAPEAVExpressionParser@@PEAVGraphicPack2@@@Z + ExpressionParser_Create=?ExpressionParser_Create@@YAPEAVExpressionParser@@XZ + ExpressionParser_CreateCopy=?ExpressionParser_CreateCopy@@YAPEAVExpressionParser@@PEAV1@@Z + ExpressionParser_Delete=?ExpressionParser_Delete@@YAXPEAVExpressionParser@@@Z + ExpressionParser_AddConstantDouble=?ExpressionParser_AddConstantDouble@@YAXPEAVExpressionParser@@PEBDN@Z + ExpressionParser_AddConstantString=?ExpressionParser_AddConstantString@@YAXPEAVExpressionParser@@PEBD1@Z + ExpressionParser_EvaluateToDouble=?ExpressionParser_EvaluateToDouble@@YA_NPEAVExpressionParser@@PEBDPEAN@Z + PPCRecompiler_findFuncRanges=?PPCRecompiler_findFuncRanges@@YA_NIPEAUppcRecompilerFuncRange_t@@PEA_K@Z + PPCRecompiler_getJumpTableBase=?PPCRecompiler_getJumpTableBase@@YAPEA_KXZ + PPCRecompiler_invalidateRange=?PPCRecompiler_invalidateRange@@YAXII@Z \ No newline at end of file diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt new file mode 100644 index 00000000..7d62bc65 --- /dev/null +++ b/src/gui/CMakeLists.txt @@ -0,0 +1,29 @@ +project(CemuGui) + +include_directories(".") + +file(GLOB_RECURSE CPP_FILES *.cpp) +file(GLOB_RECURSE H_FILES *.h) +add_library(CemuGui ${CPP_FILES} ${H_FILES}) + +set_property(TARGET CemuGui PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + +target_sources(CemuGui PRIVATE +wxcomponents/checkedlistctrl.cpp +wxcomponents/checkedlistctrl.h +wxcomponents/checktree.cpp +wxcomponents/checktree.h +) + +target_precompile_headers(CemuGui PRIVATE ../Common/precompiled.h) + +target_include_directories(CemuGui PRIVATE ../) + +target_link_libraries(CemuGui ${wxWidgets_LIBRARIES}) +target_link_libraries(CemuGui libzip::zip) +target_link_libraries(CemuGui CemuCafe CemuComponents CemuResource) +target_link_libraries(CemuGui zarchive) + +if(ENABLE_CUBEB) + target_link_libraries(CemuGui cubeb) +endif() diff --git a/src/gui/CemuApp.cpp b/src/gui/CemuApp.cpp new file mode 100644 index 00000000..a13ba419 --- /dev/null +++ b/src/gui/CemuApp.cpp @@ -0,0 +1,420 @@ +#include "gui/CemuApp.h" +#include "gui/MainWindow.h" +#include "gui/wxgui.h" +#include "config/CemuConfig.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" +#include "gui/guiWrapper.h" +#include "config/ActiveSettings.h" +#include "gui/GettingStartedDialog.h" +#include "config/PermanentConfig.h" +#include "config/PermanentStorage.h" +#include "input/InputManager.h" +#include "gui/helpers/wxHelpers.h" +#include "Cemu/ncrypto/ncrypto.h" + +#include +#include + +#include "Cafe/TitleList/TitleList.h" +#include "Cafe/TitleList/SaveList.h" + +wxIMPLEMENT_APP_NO_MAIN(CemuApp); + +// defined in guiWrapper.cpp +extern WindowInfo g_window_info; +extern std::shared_mutex g_mutex; + +// Translation strings to extract for gettext: +void unused_translation_dummy() +{ + void(_("Browse")); + void(_("Select a file")); + void(_("Select a directory")); + + void(_("base")); + void(_("update")); + void(_("dlc")); + void(_("save")); + + void(_("Japan")); + void(_("USA")); + void(_("Europe")); + void(_("Australia")); + void(_("China")); + void(_("Korea")); + void(_("Taiwan")); + void(_("Auto")); + void(_("many")); + + void(_("Japanese")); + void(_("English")); + void(_("French")); + void(_("German")); + void(_("Italian")); + void(_("Spanish")); + void(_("Chinese")); + void(_("Korean")); + void(_("Dutch")); + void(_("Portugese")); + void(_("Russian")); + void(_("Taiwanese")); + void(_("unknown")); + + + // account.h + void(_("AccountId missing (The account is not connected to a NNID)")); + void(_("IsPasswordCacheEnabled is set to false (The remember password option on your Wii U must be enabled for this account before dumping it)")); + void(_("AccountPasswordCache is empty (The remember password option on your Wii U must be enabled for this account before dumping it)")); + void(_("PrincipalId missing")); +} + + +#pragma optimize( "", off ) +DLLEXPORT _declspec(noinline) wxTopLevelWindow* wxMainWindowCreated(wxTopLevelWindow* wndPtr, uint32 magicConstant, CemuApp* appPointer) +{ + return wndPtr; +} +#pragma optimize( "", on ) + +bool CemuApp::OnInit() +{ + wxInitAllImageHandlers(); + + g_config.Load(); + m_languages = GetAvailableLanguages(); + + const sint32 language = GetConfig().language; + if (language != wxLANGUAGE_ENGLISH) + { + const auto it = std::find_if(m_languages.begin(), m_languages.end(), [language](const wxLanguageInfo* info) { return info->Language == language; }); + if (it != m_languages.end() && wxLocale::IsAvailable(language)) + { + if (m_locale.Init(language)) + { + m_locale.AddCatalogLookupPathPrefix("./resources"); + m_locale.AddCatalog("cemu"); + } + } + } + + if (!m_locale.IsOk()) + { + m_locale.Init(wxLANGUAGE_DEFAULT); + } + + // fill colour db + wxTheColourDatabase->AddColour("ERROR", wxColour(0xCC, 0, 0)); + wxTheColourDatabase->AddColour("SUCCESS", wxColour(0, 0xbb, 0)); + +#if BOOST_OS_WINDOWS + const auto parent_path = GetParentProcess(); + if(parent_path.has_filename()) + { + const auto filename = parent_path.filename().generic_string(); + if (boost::icontains(filename, "WiiU_USB_Helper")) + __fastfail(0); + } +#endif + + // init input + InputManager::instance().load(); + + InitializeGlobalVulkan(); + + Bind(wxEVT_ACTIVATE_APP, &CemuApp::ActivateApp, this); + + if (!TestWriteAccess(ActiveSettings::GetPath())) + wxMessageBox(_("Cemu can't write to its directory.\nPlease move it to a different location or run Cemu as administrator!"), _("Warning"), wxOK | wxCENTRE | wxICON_EXCLAMATION, nullptr); + + auto& config = GetConfig(); + const bool first_start = !config.did_show_graphic_pack_download; + + CreateDefaultFiles(first_start); + + m_mainFrame = new MainWindow(); + + if (first_start) + m_mainFrame->ShowGettingStartedDialog(); + + std::unique_lock lock(g_mutex); + g_window_info.app_active = true; + + SetTopWindow(m_mainFrame); + + // Cemuhook callback + wxMainWindowCreated(m_mainFrame, 0xDABABE, this); + m_mainFrame->Show(); + return true; +} + +int CemuApp::OnExit() +{ + wxApp::OnExit(); +#if BOOST_OS_WINDOWS > 0 + ExitProcess(0); +#else + exit(0); +#endif +} + +#if BOOST_OS_WINDOWS +void DumpThreadStackTrace(); +#endif + +void CemuApp::OnAssertFailure(const wxChar* file, int line, const wxChar* func, const wxChar* cond, const wxChar* msg) +{ + cemuLog_createLogFile(false); + cemuLog_log(LogType::Force, "Encountered wxWidgets assert!"); + cemuLog_log(LogType::Force, fmt::format(L"File: {0} Line: {1}", std::wstring_view(file), line)); + cemuLog_log(LogType::Force, fmt::format(L"Func: {0} Cond: {1}", func, std::wstring_view(cond))); + cemuLog_log(LogType::Force, fmt::format(L"Message: {}", std::wstring_view(msg))); + +#if BOOST_OS_WINDOWS + DumpThreadStackTrace(); +#endif + cemu_assert_debug(false); +} + +int CemuApp::FilterEvent(wxEvent& event) +{ + if(event.GetEventType() == wxEVT_KEY_DOWN) + { + const auto& key_event = (wxKeyEvent&)event; + wxGetKeyState(wxKeyCode::WXK_F17); + g_window_info.keydown[fix_raw_keycode(key_event.GetRawKeyCode(), key_event.GetRawKeyFlags())] = true; + } + else if(event.GetEventType() == wxEVT_KEY_UP) + { + const auto& key_event = (wxKeyEvent&)event; + g_window_info.keydown[fix_raw_keycode(key_event.GetRawKeyCode(), key_event.GetRawKeyFlags())] = false; + } + + return wxApp::FilterEvent(event); +} + +std::vector CemuApp::GetAvailableLanguages() +{ + const auto path = ActiveSettings::GetPath("resources"); + if (!exists(path)) + return {}; + + std::vector result; + for (const auto& p : fs::directory_iterator(path)) + { + if (!fs::is_directory(p)) + continue; + + const auto& path = p.path(); + auto filename = path.filename(); + + const auto* lang_info = wxLocale::FindLanguageInfo(filename.c_str()); + if (!lang_info) + continue; + + const auto language_file = path / "cemu.mo"; + if (!fs::exists(language_file)) + continue; + + result.emplace_back(lang_info); + } + + return result; +} + +void CemuApp::CreateDefaultFiles(bool first_start) +{ + std::wstring mlc = GetMLCPath().ToStdWstring(); + + // check for mlc01 folder missing if custom path has been set + if (!fs::exists(mlc) && !first_start) + { + const std::wstring message = fmt::format(_(L"Your mlc01 folder seems to be missing.\n\nThis is where Cemu stores save files, game updates and other Wii U files.\n\nThe expected path is:\n{}\n\nDo you want to create the folder at the expected path?").ToStdWstring(), mlc); + + wxMessageDialog dialog(nullptr, message, "Error", wxCENTRE | wxYES_NO | wxCANCEL| wxICON_WARNING); + dialog.SetYesNoCancelLabels(_("Yes"), _("No"), _("Select a custom path")); + const auto dialogResult = dialog.ShowModal(); + if (dialogResult == wxID_NO) + exit(0); + else if(dialogResult == wxID_CANCEL) + { + if (!SelectMLCPath()) + return; + + mlc = GetMLCPath(); + } + else + { + GetConfig().mlc_path = L""; + g_config.Save(); + } + } + + // create sys/usr folder in mlc01 + try + { + const auto sysFolder = fs::path(mlc).append(L"sys"); + fs::create_directories(sysFolder); + + const auto usrFolder = fs::path(mlc).append(L"usr"); + fs::create_directories(usrFolder); + fs::create_directories(fs::path(usrFolder).append("title/00050000")); // base + fs::create_directories(fs::path(usrFolder).append("title/0005000c")); // dlc + fs::create_directories(fs::path(usrFolder).append("title/0005000e")); // update + + // Mii Maker save folders {0x500101004A000, 0x500101004A100, 0x500101004A200}, + fs::create_directories(fs::path(mlc).append(L"usr/save/00050010/1004a000/user/common/db")); + fs::create_directories(fs::path(mlc).append(L"usr/save/00050010/1004a100/user/common/db")); + fs::create_directories(fs::path(mlc).append(L"usr/save/00050010/1004a200/user/common/db")); + + // lang files + auto langDir = fs::path(mlc).append(L"sys/title/0005001b/1005c000/content"); + fs::create_directories(langDir); + + auto langFile = fs::path(langDir).append("language.txt"); + if (!fs::exists(langFile)) + { + std::ofstream file(langFile); + if (file.is_open()) + { + const char* langStrings[] = { "ja","en","fr","de","it","es","zh","ko","nl","pt","ru","zh" }; + for (const char* lang : langStrings) + file << fmt::format(R"("{}",)", lang) << std::endl; + + file.flush(); + file.close(); + } + } + + auto countryFile = fs::path(langDir).append("country.txt"); + if (!fs::exists(countryFile)) + { + std::ofstream file(countryFile); + for (sint32 i = 0; i < 201; i++) + { + const char* countryCode = NCrypto::GetCountryAsString(i); + if (boost::iequals(countryCode, "NN")) + file << "NULL," << std::endl; + else + file << fmt::format(R"("{}",)", countryCode) << std::endl; + } + file.flush(); + file.close(); + } + } + catch (const std::exception& ex) + { + std::stringstream errorMsg; + errorMsg << fmt::format(_("Couldn't create a required mlc01 subfolder or file!\n\nError: {0}\nTarget path:\n{1}").ToStdString(), ex.what(), boost::nowide::narrow(mlc)); + +#if BOOST_OS_WINDOWS > 0 + const DWORD lastError = GetLastError(); + if (lastError != ERROR_SUCCESS) + errorMsg << fmt::format("\n\n{}", GetSystemErrorMessage(lastError)); + + wxMessageBox(errorMsg.str(), "Error", wxOK | wxCENTRE | wxICON_ERROR); +#endif + exit(0); + } + + // cemu directories + try + { + const auto controllerProfileFolder = GetCemuPath(L"controllerProfiles").ToStdWstring(); + if (!fs::exists(controllerProfileFolder)) + fs::create_directories(controllerProfileFolder); + + const auto memorySearcherFolder = GetCemuPath(L"memorySearcher").ToStdWstring(); + if (!fs::exists(memorySearcherFolder)) + fs::create_directories(memorySearcherFolder); + } + catch (const std::exception& ex) + { + std::stringstream errorMsg; + errorMsg << fmt::format(_("Couldn't create a required cemu directory or file!\n\nError: {0}").ToStdString(), ex.what()); + +#if BOOST_OS_WINDOWS > 0 + const DWORD lastError = GetLastError(); + if (lastError != ERROR_SUCCESS) + errorMsg << fmt::format("\n\n{}", GetSystemErrorMessage(lastError)); + + + wxMessageBox(errorMsg.str(), "Error", wxOK | wxCENTRE | wxICON_ERROR); +#endif + exit(0); + } +} + + + +bool CemuApp::SelectMLCPath(wxWindow* parent) +{ + auto& config = GetConfig(); + + std::wstring default_path; + if (fs::exists(config.mlc_path.GetValue())) + default_path = config.mlc_path.GetValue(); + + // try until users selects a valid path or aborts + while(true) + { + wxDirDialog path_dialog(parent, _("Select a mlc directory"), default_path, wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST); + if (path_dialog.ShowModal() != wxID_OK || path_dialog.GetPath().empty()) + return false; + + const auto path = path_dialog.GetPath().ToStdWstring(); + + if (!TestWriteAccess(fs::path{ path })) + { + const auto result = wxMessageBox(_("Cemu can't write to the selected mlc path!\nDo you want to select another path?"), _("Error"), wxYES_NO | wxCENTRE | wxICON_ERROR); + if (result == wxYES) + continue; + + break; + } + + config.SetMLCPath(path); + // update TitleList and SaveList scanner with new MLC path + CafeTitleList::SetMLCPath(path); + CafeTitleList::Refresh(); + CafeSaveList::SetMLCPath(path); + CafeSaveList::Refresh(); + return true; + } + + return false; +} + +wxString CemuApp::GetCemuPath() +{ + return ActiveSettings::GetPath().generic_wstring(); +} + +wxString CemuApp::GetCemuPath(const wxString& cat) +{ + return ActiveSettings::GetPath(cat.ToStdString()).generic_wstring(); +} + +wxString CemuApp::GetMLCPath() +{ + return ActiveSettings::GetMlcPath().generic_wstring(); +} + +wxString CemuApp::GetMLCPath(const wxString& cat) +{ + return ActiveSettings::GetMlcPath(cat.ToStdString()).generic_wstring(); +} + +void CemuApp::ActivateApp(wxActivateEvent& event) +{ + g_window_info.app_active = event.GetActive(); + event.Skip(); +} + +extern "C" +{ + CemuApp& wxGetAppWrapper() + { + return *static_cast(wxApp::GetInstance()); + }; +} + diff --git a/src/gui/CemuApp.h b/src/gui/CemuApp.h new file mode 100644 index 00000000..888293eb --- /dev/null +++ b/src/gui/CemuApp.h @@ -0,0 +1,35 @@ +#pragma once + +#include + +class MainWindow; + +class CemuApp : public wxApp +{ +public: + bool OnInit() override; + int OnExit() override; + + void OnAssertFailure(const wxChar* file, int line, const wxChar* func, const wxChar* cond, const wxChar* msg) override; + int FilterEvent(wxEvent& event) override; + + const std::vector& GetLanguages() const { return m_languages; } + static std::vector GetAvailableLanguages(); + + static void CreateDefaultFiles(bool first_start = false); + static bool SelectMLCPath(wxWindow* parent = nullptr); + + static wxString GetCemuPath(); + static wxString GetCemuPath(const wxString& cat); + static wxString GetMLCPath(); + static wxString GetMLCPath(const wxString& cat); +private: + void ActivateApp(wxActivateEvent& event); + + MainWindow* m_mainFrame = nullptr; + + wxLocale m_locale; + std::vector m_languages; +}; + +wxDECLARE_APP(CemuApp); diff --git a/src/gui/CemuUpdateWindow.cpp b/src/gui/CemuUpdateWindow.cpp new file mode 100644 index 00000000..f9aa3a88 --- /dev/null +++ b/src/gui/CemuUpdateWindow.cpp @@ -0,0 +1,621 @@ +#include "gui/CemuUpdateWindow.h" + +#include "Common/version.h" +#include "util/helpers/helpers.h" +#include "util/helpers/SystemException.h" +#include "config/ActiveSettings.h" +#include "Common/filestream.h" + +#include +#include +#include +#include + +#include +#include +#include + + +wxDECLARE_EVENT(wxEVT_RESULT, wxCommandEvent); +wxDEFINE_EVENT(wxEVT_RESULT, wxCommandEvent); + +wxDECLARE_EVENT(wxEVT_PROGRESS, wxCommandEvent); +wxDEFINE_EVENT(wxEVT_PROGRESS, wxCommandEvent); + +CemuUpdateWindow::CemuUpdateWindow(wxWindow* parent) + : wxDialog(parent, wxID_ANY, "Cemu update", wxDefaultPosition, wxDefaultSize, + wxCAPTION | wxMINIMIZE_BOX | wxSYSTEM_MENU | wxTAB_TRAVERSAL | wxCLOSE_BOX) +{ + auto* sizer = new wxBoxSizer(wxVERTICAL); + m_gauge = new wxGauge(this, wxID_ANY, 100, wxDefaultPosition, wxSize(500, 20), wxGA_HORIZONTAL); + m_gauge->SetValue(0); + sizer->Add(m_gauge, 0, wxALL | wxEXPAND, 5); + + auto* rows = new wxFlexGridSizer(0, 2, 0, 0); + rows->AddGrowableCol(1); + + m_text = new wxStaticText(this, wxID_ANY, "Checking for latest version..."); + rows->Add(m_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + + { + auto* right_side = new wxBoxSizer(wxHORIZONTAL); + + m_update_button = new wxButton(this, wxID_ANY, _("Update")); + m_update_button->Bind(wxEVT_BUTTON, &CemuUpdateWindow::OnUpdateButton, this); + right_side->Add(m_update_button, 0, wxALL, 5); + + m_cancel_button = new wxButton(this, wxID_ANY, _("Cancel")); + m_cancel_button->Bind(wxEVT_BUTTON, &CemuUpdateWindow::OnCancelButton, this); + right_side->Add(m_cancel_button, 0, wxALL, 5); + + rows->Add(right_side, 1, wxALIGN_RIGHT, 5); + } + + m_changelog = new wxHyperlinkCtrl(this, wxID_ANY, _("Changelog"), wxEmptyString); + rows->Add(m_changelog, 0, wxLEFT | wxBOTTOM | wxRIGHT | wxEXPAND, 5); + + sizer->Add(rows, 0, wxALL | wxEXPAND, 5); + + SetSizerAndFit(sizer); + Centre(wxBOTH); + + Bind(wxEVT_CLOSE_WINDOW, &CemuUpdateWindow::OnClose, this); + Bind(wxEVT_RESULT, &CemuUpdateWindow::OnResult, this); + Bind(wxEVT_PROGRESS, &CemuUpdateWindow::OnGaugeUpdate, this); + m_thread = std::thread(&CemuUpdateWindow::WorkerThread, this); + + m_update_button->Hide(); + m_changelog->Hide(); +} + +CemuUpdateWindow::~CemuUpdateWindow() +{ + m_order = WorkerOrder::Exit; + if (m_thread.joinable()) + m_thread.join(); +} + +size_t CemuUpdateWindow::WriteStringCallback(char* ptr, size_t size, size_t nmemb, void* userdata) +{ + ((std::string*)userdata)->append(ptr, size * nmemb); + return size * nmemb; +}; + +std::string _curlUrlEscape(CURL* curl, const std::string& input) +{ + char* escapedStr = curl_easy_escape(curl, input.c_str(), input.size()); + std::string r(escapedStr); + curl_free(escapedStr); + return r; +} + +bool CemuUpdateWindow::GetServerVersion(uint64& version, std::string& filename, std::string& changelog_filename) +{ + std::string buffer; + std::string urlStr("https://cemu.info/api/cemu_version3.php?version2="); + auto* curl = curl_easy_init(); + urlStr.append(_curlUrlEscape(curl, fmt::format("{}.{}.{}{}", EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, EMULATOR_VERSION_SUFFIX))); + + curl_easy_setopt(curl, CURLOPT_URL, urlStr.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteStringCallback); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); + + bool result = false; + CURLcode cr = curl_easy_perform(curl); + if (cr == CURLE_OK) + { + long http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + if (http_code != 0 && http_code != 200) + { + forceLog_printf("Update check failed (http code: %d)", http_code); + cemu_assert_debug(false); + return false; + } + + std::vector tokens; + const boost::char_separator sep{ "|" }; + for (const auto& token : boost::tokenizer(buffer, sep)) + { + tokens.emplace_back(token); + } + + if (tokens.size() >= 2) + { + const auto latest_version = ConvertString(tokens[0]); + result = latest_version > 0 && !tokens[1].empty(); + if (result) + { + version = latest_version; + filename = tokens[1]; + + if(tokens.size() >= 3) + changelog_filename = tokens[2]; + } + } + } + else + { + forceLog_printf("Update check failed with CURL error %d", (int)cr); + cemu_assert_debug(false); + } + + curl_easy_cleanup(curl); + return result; +} + +std::future CemuUpdateWindow::IsUpdateAvailable() +{ + return std::async(std::launch::async, CheckVersion); +} + +bool CemuUpdateWindow::CheckVersion() +{ + uint64 latest_version; + std::string filename, changelog; + if (!GetServerVersion(latest_version, filename, changelog)) + return false; + + return IsUpdateAvailable(latest_version); +} + +bool CemuUpdateWindow::IsUpdateAvailable(uint64 latest_version) +{ + uint64 version = EMULATOR_SERVER_VERSION; + + return latest_version > version; +} + +int CemuUpdateWindow::ProgressCallback(void* clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, + curl_off_t ulnow) +{ + auto* thisptr = (CemuUpdateWindow*)clientp; + auto* event = new wxCommandEvent(wxEVT_PROGRESS); + event->SetInt((int)dlnow); + wxQueueEvent(thisptr, event); + return 0; +} + +bool CemuUpdateWindow::DownloadCemuZip(const std::string& url, const fs::path& filename) +{ + FileStream* fsUpdateFile = FileStream::createFile2(filename); + if (!fsUpdateFile) + return false; + + bool result = false; + auto* curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_NOBODY, 1); + if (curl_easy_perform(curl) == CURLE_OK) + { + long http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + if (http_code != 0 && http_code != 200) + { + cemuLog_log(LogType::Force, "Unable to download cemu update zip file from {} (http error: {})", url, http_code); + curl_easy_cleanup(curl); + return false; + } + + curl_off_t update_size; + if (curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &update_size) == CURLE_OK) + m_gauge_max_value = (int)update_size; + + + auto _curlWriteData = +[](void* ptr, size_t size, size_t nmemb, void* ctx) -> size_t + { + FileStream* fs = (FileStream*)ctx; + const size_t writeSize = size * nmemb; + fs->writeData(ptr, writeSize); + return writeSize; + }; + + curl_easy_setopt(curl, CURLOPT_NOBODY, 0); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, _curlWriteData); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, fsUpdateFile); + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); + curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, ProgressCallback); + curl_easy_setopt(curl, CURLOPT_XFERINFODATA, this); + + auto curl_result = std::async(std::launch::async, [](CURL* curl, long* http_code) + { + const auto r = curl_easy_perform(curl); + curl_easy_cleanup(curl); + return r; + }, curl, &http_code); + while (!curl_result.valid()) + { + if (m_order == WorkerOrder::Exit) + return false; + + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + + result = curl_result.get() == CURLE_OK; + + delete fsUpdateFile; + } + else + curl_easy_cleanup(curl); + + if (!result && fs::exists(filename)) + { + try + { + fs::remove(filename); + } + catch (const std::exception& ex) + { + forceLog_printf("can't remove update.zip on error: %s", ex.what()); + } + } + return result; +} + +bool CemuUpdateWindow::ExtractUpdate(const fs::path& zipname, const fs::path& targetpath) +{ + // open downloaded zip + int err; + auto* za = zip_open(zipname.string().c_str(), ZIP_RDONLY, &err); + if (za == nullptr) + { + cemuLog_log(LogType::Force, "Cannot open zip file: {}", zipname.string()); + return false; + } + + const auto count = zip_get_num_entries(za, 0); + m_gauge_max_value = count; + for (auto i = 0; i < count; i++) + { + if (m_order == WorkerOrder::Exit) + return false; + + zip_stat_t sb{}; + if (zip_stat_index(za, i, 0, &sb) == 0) + { + fs::path fname = targetpath; + fname /= sb.name; + + const auto len = strlen(sb.name); + if (strcmp(sb.name, ".") == 0 || strcmp(sb.name, "..") == 0) + { + // protection + continue; + } + if (sb.name[len - 1] == '/' || sb.name[len - 1] == '\\') + { + // directory + try + { + if (!exists(fname)) + create_directory(fname); + } + catch (const std::exception& ex) + { + SystemException sys(ex); + forceLog_printf("can't create folder \"%s\" for update: %s", sb.name, sys.what()); + } + continue; + } + + // file + auto* zf = zip_fopen_index(za, i, 0); + if (!zf) + { + forceLog_printf("can't open zip file \"%s\"", sb.name); + zip_close(za); + return false; + } + + std::vector buffer(sb.size); + const auto read = zip_fread(zf, buffer.data(), sb.size); + if (read != (sint64)sb.size) + { + forceLog_printf("could only read 0x%x of 0x%x bytes from zip file \"%s\"", read, sb.size, sb.name); + zip_close(za); + return false; + } + + auto* file = fopen(fname.string().c_str(), "wb"); + if (file == nullptr) + { + forceLog_printf("can't create update file \"%s\"", sb.name); + zip_close(za); + return false; + } + + fwrite(buffer.data(), 1, buffer.size(), file); + fflush(file); + fclose(file); + + zip_fclose(zf); + + if ((i / 10) * 10 == i) + { + auto* event = new wxCommandEvent(wxEVT_PROGRESS); + event->SetInt(i); + wxQueueEvent(this, event); + } + } + } + + auto* event = new wxCommandEvent(wxEVT_PROGRESS); + event->SetInt(m_gauge_max_value); + wxQueueEvent(this, event); + + zip_close(za); + + return true; +} + +void CemuUpdateWindow::WorkerThread() +{ + const auto tmppath = fs::temp_directory_path() / L"cemu_update"; + std::error_code ec; + // clean leftovers + if (exists(tmppath)) + remove_all(tmppath, ec); + + while (true) + { + std::unique_lock lock(m_mutex); + while (m_order == WorkerOrder::Idle) + m_condition.wait_for(lock, std::chrono::milliseconds(125)); + + if (m_order == WorkerOrder::Exit) + break; + + try + { + if (m_order == WorkerOrder::CheckVersion) + { + auto* event = new wxCommandEvent(wxEVT_RESULT); + if (GetServerVersion(m_version, m_filename, m_changelog_filename) && IsUpdateAvailable(m_version)) + event->SetInt((int)Result::UpdateAvailable); + else + event->SetInt((int)Result::NoUpdateAvailable); + + wxQueueEvent(this, event); + } + else if (m_order == WorkerOrder::UpdateVersion) + { + // download update + const std::string url = fmt::format("http://cemu.info/releases/{}", m_filename); + if (!exists(tmppath)) + create_directory(tmppath); + + const auto update_file = tmppath / L"update.zip"; + if (DownloadCemuZip(url, update_file)) + { + auto* event = new wxCommandEvent(wxEVT_RESULT); + event->SetInt((int)Result::UpdateDownloaded); + wxQueueEvent(this, event); + } + else + { + auto* event = new wxCommandEvent(wxEVT_RESULT); + event->SetInt((int)Result::UpdateDownloadError); + wxQueueEvent(this, event); + m_order = WorkerOrder::Idle; + continue; + } + if (m_order == WorkerOrder::Exit) + break; + + // extract + const auto expected_path = (tmppath / m_filename).replace_extension(""); + if (ExtractUpdate(update_file, tmppath) && exists(expected_path)) + { + auto* event = new wxCommandEvent(wxEVT_RESULT); + event->SetInt((int)Result::ExtractSuccess); + wxQueueEvent(this, event); + } + else + { + auto* event = new wxCommandEvent(wxEVT_RESULT); + event->SetInt((int)Result::ExtractError); + wxQueueEvent(this, event); + + if (exists(tmppath)) + { + try + { + fs::remove(tmppath); + } + catch (const std::exception& ex) + { + SystemException sys(ex); + forceLog_printf("can't remove extracted tmp files: %s", sys.what()); + } + } + + continue; + } + + if (m_order == WorkerOrder::Exit) + break; + + // apply update + std::wstring target_directory = ActiveSettings::GetPath().generic_wstring(); + if (target_directory[target_directory.size() - 1] == '/') + target_directory = target_directory.substr(0, target_directory.size() - 1); // remove trailing / + + // get exe name + const auto exec = ActiveSettings::GetFullPath(); + const auto target_exe = fs::path(exec).replace_extension("exe.backup"); + fs::rename(exec, target_exe); + m_restart_file = exec; + + const auto index = expected_path.wstring().size(); + int counter = 0; + for (const auto& it : fs::recursive_directory_iterator(expected_path)) + { + const auto filename = it.path().wstring().substr(index); + auto target_file = target_directory + filename; + try + { + if (is_directory(it)) + { + if (!fs::exists(target_file)) + fs::create_directory(target_file); + } + else + { + if(it.path().filename() == L"Cemu.exe") + fs::rename(it.path(), fs::path(target_file).replace_filename(exec.filename())); + else + fs::rename(it.path(), target_file); + } + } + catch (const std::exception& ex) + { + SystemException sys(ex); + forceLog_printf("applying update error: %s", sys.what()); + } + + if ((counter++ / 10) * 10 == counter) + { + auto* event = new wxCommandEvent(wxEVT_PROGRESS); + event->SetInt(counter); + wxQueueEvent(this, event); + } + } + + auto* event = new wxCommandEvent(wxEVT_PROGRESS); + event->SetInt(m_gauge_max_value); + wxQueueEvent(this, event); + + auto* result_event = new wxCommandEvent(wxEVT_RESULT); + result_event->SetInt((int)Result::Success); + wxQueueEvent(this, result_event); + } + } + catch (const std::exception& ex) + { + SystemException sys(ex); + forceLog_printf("update error: %s", sys.what()); + + // clean leftovers + if (exists(tmppath)) + remove_all(tmppath, ec); + + auto* result_event = new wxCommandEvent(wxEVT_RESULT); + result_event->SetInt((int)Result::Error); + wxQueueEvent(this, result_event); + } + + m_order = WorkerOrder::Idle; + } +} + +bool IsCemuhookLoaded(); +void CemuUpdateWindow::OnClose(wxCloseEvent& event) +{ + event.Skip(); + +#if BOOST_OS_WINDOWS > 0 + if (m_restart_required && !m_restart_file.empty() && fs::exists(m_restart_file)) + { + PROCESS_INFORMATION pi{}; + STARTUPINFO si{}; + si.cb = sizeof(si); + + std::wstring cmdline = GetCommandLineW(); + const auto index = cmdline.find('"', 1); + cemu_assert_debug(index != std::wstring::npos); + cmdline = L"\"" + m_restart_file.wstring() + L"\"" + cmdline.substr(index + 1); + + HANDLE lock = CreateMutex(nullptr, TRUE, L"Global\\cemu_update_lock"); + CreateProcess(nullptr, (wchar_t*)cmdline.c_str(), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi); + + if (IsCemuhookLoaded()) + TerminateProcess(GetCurrentProcess(), 0); + else + exit(0); + } +#else + cemuLog_log(LogType::Force, "unimplemented - restart on update"); +#endif +} + + +void CemuUpdateWindow::OnResult(wxCommandEvent& event) +{ + switch ((Result)event.GetInt()) + { + case Result::NoUpdateAvailable: + m_cancel_button->SetLabel(_("Exit")); + m_text->SetLabel(_("No update available!")); + m_gauge->SetValue(100); + break; + case Result::UpdateAvailable: + { + if (!m_changelog_filename.empty()) + { + m_changelog->SetURL(fmt::format("https://cemu.info/changelog/{}", m_changelog_filename)); + m_changelog->Show(); + } + else + m_changelog->Hide(); + + m_update_button->Show(); + + + m_text->SetLabel(_("Update available!")); + m_cancel_button->SetLabel(_("Exit")); + break; + } + case Result::UpdateDownloaded: + m_text->SetLabel(_("Extracting update...")); + m_gauge->SetValue(0); + break; + case Result::UpdateDownloadError: + m_update_button->Enable(); + m_text->SetLabel(_("Couldn't download the update!")); + break; + case Result::ExtractSuccess: + m_text->SetLabel(_("Applying update...")); + m_gauge->SetValue(0); + m_cancel_button->Disable(); + break; + case Result::ExtractError: + m_update_button->Enable(); + m_cancel_button->Enable(); + m_text->SetLabel(_("Extracting failed!")); + break; + case Result::Success: + m_cancel_button->Enable(); + m_update_button->Hide(); + + m_text->SetLabel(_("Success")); + m_cancel_button->SetLabel(_("Restart")); + m_restart_required = true; + break; + default: ; + } +} + +void CemuUpdateWindow::OnGaugeUpdate(wxCommandEvent& event) +{ + const int total_size = m_gauge_max_value > 0 ? m_gauge_max_value : 10000000; + m_gauge->SetValue((event.GetInt() * 100) / total_size); +} + +void CemuUpdateWindow::OnUpdateButton(const wxCommandEvent& event) +{ + std::unique_lock lock(m_mutex); + m_order = WorkerOrder::UpdateVersion; + + m_condition.notify_all(); + + m_update_button->Disable(); + + m_text->SetLabel(_("Downloading update...")); +} + +void CemuUpdateWindow::OnCancelButton(const wxCommandEvent& event) +{ + Close(); +} diff --git a/src/gui/CemuUpdateWindow.h b/src/gui/CemuUpdateWindow.h new file mode 100644 index 00000000..5df8aecd --- /dev/null +++ b/src/gui/CemuUpdateWindow.h @@ -0,0 +1,71 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +class CemuUpdateWindow : public wxDialog +{ +public: + CemuUpdateWindow(wxWindow* parent); + ~CemuUpdateWindow(); + + static std::future IsUpdateAvailable(); + +private: + wxStaticText* m_text; + wxGauge* m_gauge; + wxButton* m_cancel_button, *m_update_button; + wxHyperlinkCtrl* m_changelog; + + void OnUpdateButton(const wxCommandEvent& event); + void OnCancelButton(const wxCommandEvent& event); + void OnClose(wxCloseEvent& event); + void OnResult(wxCommandEvent& event); + void OnGaugeUpdate(wxCommandEvent& event); + + static size_t WriteStringCallback(char* ptr, size_t size, size_t nmemb, void* userdata); + static bool GetServerVersion(uint64& version, std::string& filename, std::string& changelog_filename); + static bool CheckVersion(); + static bool IsUpdateAvailable(uint64 latest_version); + + static int ProgressCallback(void* clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow); + bool DownloadCemuZip(const std::string& url, const fs::path& filename); + bool ExtractUpdate(const fs::path& zipname, const fs::path& targetpath); + + enum class WorkerOrder + { + Idle, + Exit, + CheckVersion, + UpdateVersion, + }; + enum class Result + { + NoUpdateAvailable, + UpdateAvailable, + UpdateDownloaded, + UpdateDownloadError, + ExtractSuccess, + ExtractError, + Success, + Error + }; + std::mutex m_mutex; + std::condition_variable m_condition; + WorkerOrder m_order = WorkerOrder::CheckVersion; + void WorkerThread(); + + uint64 m_version = 0; + std::string m_filename, m_changelog_filename; + int m_gauge_max_value = 0; + + std::thread m_thread; + fs::path m_restart_file; + bool m_restart_required = false; +}; diff --git a/src/gui/ChecksumTool.cpp b/src/gui/ChecksumTool.cpp new file mode 100644 index 00000000..f13e8fa7 --- /dev/null +++ b/src/gui/ChecksumTool.cpp @@ -0,0 +1,788 @@ +#include "gui/ChecksumTool.h" + +#ifdef _WIN32 +#include +#endif + +#include "Cafe/TitleList/GameInfo.h" +#include "gui/helpers/wxCustomEvents.h" +#include "util/helpers/helpers.h" +#include "gui/helpers/wxHelpers.h" +#include "Cafe/Filesystem/WUD/wud.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config/ActiveSettings.h" +#include "Cafe/TitleList/TitleList.h" + +const char kSchema[] = R"( +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "title_id": { + "type": "string" + }, + "region": { + "type": "integer" + }, + "version": { + "type": "integer" + }, + "wud_hash": { + "type": "string" + }, + "files": { + "type": "array", + "items": { + "type": "object", + "properties": { + "file": { + "type": "string" + }, + "hash": { + "type": "string" + } + }, + "required": [ + "file", + "hash" + ] + } + } + }, + "required": [ + "title_id", + "region", + "version", + "files" + ] +})"; + + +ChecksumTool::ChecksumTool(wxWindow* parent, wxTitleManagerList::TitleEntry& entry) + : wxDialog(parent, wxID_ANY, + wxStringFormat2(_("Title checksum of {:08x}-{:08x}"), (uint32)(entry.title_id >> 32), (uint32)(entry.title_id & 0xFFFFFFFF)), + wxDefaultPosition, wxDefaultSize, wxCAPTION | wxFRAME_TOOL_WINDOW | wxSYSTEM_MENU | wxTAB_TRAVERSAL | wxCLOSE_BOX), m_entry(entry) +{ + + m_info = CafeTitleList::GetTitleInfoByUID(m_entry.location_uid); + if (!m_info.IsValid()) + throw std::runtime_error("Invalid title"); + + // only request online update once + static bool s_once = false; + if (!s_once) + { + s_once = true; + m_online_ready = std::async(std::launch::async, &ChecksumTool::LoadOnlineData, this); + } + else + m_enable_verify_button = 1; + + auto* sizer = new wxBoxSizer(wxVERTICAL); + { + auto* box_sizer = new wxStaticBoxSizer(new wxStaticBox(this, wxID_ANY, _("Verifying integrity of game files...")), wxVERTICAL); + auto* box = box_sizer->GetStaticBox(); + + m_progress = new wxGauge(box, wxID_ANY, 100, wxDefaultPosition, wxDefaultSize, wxGA_HORIZONTAL | wxGA_SMOOTH); + m_progress->SetMinSize({ 400, -1 }); + m_progress->SetValue(0); + box_sizer->Add(m_progress, 0, wxALL | wxEXPAND, 5); + + m_status = new wxStaticText(box, wxID_ANY, wxEmptyString); + m_status->Wrap(-1); + box_sizer->Add(m_status, 0, wxLEFT | wxRIGHT | wxBOTTOM | wxEXPAND, 5); + + sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); + } + + { + auto* box_sizer = new wxStaticBoxSizer(new wxStaticBox(this, wxID_ANY, _("Control")), wxHORIZONTAL); + auto* box = box_sizer->GetStaticBox(); + + m_verify_online = new wxButton(box, wxID_ANY, _("Verify online")); + m_verify_online->SetToolTip(_("Verifies the checksum online")); + m_verify_online->Disable(); + m_verify_online->Bind(wxEVT_BUTTON, &ChecksumTool::OnVerifyOnline, this); + m_verify_online->Bind(wxEVT_ENABLE, [this](wxCommandEvent&) + { + ++m_enable_verify_button; + if (m_enable_verify_button >= 2) + { + // only enable if we have a file for it + const auto title_id_str = fmt::format("{:016x}", m_json_entry.title_id); + const auto default_file = fmt::format("{}_v{}.json", title_id_str, m_info.GetAppTitleVersion()); + + const auto checksum_path = ActiveSettings::GetPath("resources/checksums/{}", default_file); + if (exists(checksum_path)) + m_verify_online->Enable(); + } + }); + box_sizer->Add(m_verify_online, 0, wxALL | wxEXPAND, 5); + + m_verify_local = new wxButton(box, wxID_ANY, _("Verify with local file")); + m_verify_online->SetToolTip(_("Verifies the checksum with a local json file you can select")); + m_verify_local->Disable(); + m_verify_local->Bind(wxEVT_BUTTON, &ChecksumTool::OnVerifyLocal, this); + box_sizer->Add(m_verify_local, 0, wxALL | wxEXPAND, 5); + + m_export_button = new wxButton(box, wxID_ANY, _("Export")); + m_verify_online->SetToolTip(_("Export the title checksum data to a local json file")); + m_export_button->Disable(); + m_export_button->Bind(wxEVT_BUTTON, &ChecksumTool::OnExportChecksums, this); + box_sizer->Add(m_export_button, 0, wxALL | wxEXPAND, 5); + + sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); + } + + this->Bind(wxEVT_SET_GAUGE_VALUE, &ChecksumTool::OnSetGaugevalue, this); + + m_worker = std::thread(&ChecksumTool::DoWork, this); + + this->SetSizerAndFit(sizer); + this->Centre(wxBOTH); +} + +ChecksumTool::~ChecksumTool() +{ + m_running = false; + if (m_worker.joinable()) + m_worker.join(); +} + +std::size_t WriteCallback(const char* in, std::size_t size, std::size_t num, std::string* out) +{ + const std::size_t totalBytes(size * num); + out->append(in, totalBytes); + return totalBytes; +} + +void ChecksumTool::LoadOnlineData() const +{ + try + { + bool updated_required = true; + + std::string latest_commit; + + const auto checksum_path = ActiveSettings::GetPath("resources/checksums"); + if (exists(checksum_path)) + { + std::string current_commit; + // check for current version + std::ifstream file(checksum_path / "commit.txt"); + if (file.is_open()) + { + std::getline(file, current_commit); + file.close(); + } + + // check latest version + /* + https://api.github.com/repos/teamcemu/title-checksums/branches/master + https://api.github.com/repos/teamcemu/title-checksums/commits?per_page=1 + */ + std::string data; + auto* curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, "https://api.github.com/repos/teamcemu/title-checksums/commits/master"); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &data); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); + curl_easy_setopt(curl, CURLOPT_USERAGENT, fmt::format("Cemu_{}.{}{}", EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_SUFFIX).c_str()); + + curl_easy_perform(curl); + + long http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + curl_easy_cleanup(curl); + if (http_code == 200 && !data.empty()) + { + rapidjson::Document doc; + doc.Parse(data.c_str(), data.size()); + if (!doc.HasParseError() && doc.HasMember("sha")) + { + latest_commit = doc["sha"].GetString(); + if (boost::iequals(current_commit, latest_commit)) + { + updated_required = false; + } + } + } + } + else + { + // create directory since not available yet + fs::create_directories(checksum_path); + } + + if (updated_required) + { + std::string data; + auto* curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, "https://github.com/TeamCemu/title-checksums/archive/master.zip"); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &data); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); + curl_easy_setopt(curl, CURLOPT_USERAGENT, fmt::format("Cemu_{}.{}{}", EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_SUFFIX).c_str()); + + curl_easy_perform(curl); + + long http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + curl_easy_cleanup(curl); + if (http_code == 200 && !data.empty()) + { + // init zip source + zip_error_t error; + zip_error_init(&error); + zip_source_t* src; + if ((src = zip_source_buffer_create(data.data(), data.size(), 1, &error)) == nullptr) + { + zip_error_fini(&error); + return; + } + + auto* za = zip_open_from_source(src, ZIP_RDONLY, &error); + if (!za) + { + wxQueueEvent(m_verify_online, new wxCommandEvent(wxEVT_ENABLE)); + return; + } + + const auto numEntries = zip_get_num_entries(za, 0); + for (sint64 i = 0; i < numEntries; i++) + { + zip_stat_t sb = { 0 }; + if (zip_stat_index(za, i, 0, &sb) != 0) + continue; + + if (std::strstr(sb.name, "../") != nullptr || + std::strstr(sb.name, "..\\") != nullptr) + continue; // bad path + + if (boost::equals(sb.name, "title-checksums-master/")) + continue; + + // title-checksums-master/ + const auto path = checksum_path / &sb.name[sizeof("title-checksums-master")]; + + size_t sbNameLen = strlen(sb.name); + if (sbNameLen == 0) + continue; + + if (sb.name[sbNameLen - 1] == '/') + { + std::error_code ec; + fs::create_directories(path, ec); + continue; + } + + if (sb.size == 0) + continue; + + if (sb.size > (1024 * 1024 * 128)) + continue; // skip unusually huge files + + zip_file_t* zipFile = zip_fopen_index(za, i, 0); + if (zipFile == nullptr) + continue; + + std::vector buffer(sb.size); + if (zip_fread(zipFile, buffer.data(), sb.size) == sb.size) + { + std::ofstream file(path); + if (file.is_open()) + { + file.write(buffer.data(), sb.size); + file.flush(); + file.close(); + } + } + + zip_fclose(zipFile); + } + + std::ofstream file(checksum_path / "commit.txt"); + if (file.is_open()) + { + file << latest_commit; + file.close(); + } + } + } + } + catch(const std::exception& ex) + { + forceLog_printf("error on updating json checksum data: %s", ex.what()); + } + + wxQueueEvent(m_verify_online, new wxCommandEvent(wxEVT_ENABLE)); +} + +void ChecksumTool::OnSetGaugevalue(wxSetGaugeValue& event) +{ + event.GetGauge()->SetValue(event.GetValue()); + event.GetTextCtrl()->SetLabelText(event.GetText()); + + // no error + if(event.GetInt() == 0 && event.GetValue() == 100) + { + m_export_button->Enable(); + m_verify_local->Enable(); + wxPostEvent(m_verify_online, wxCommandEvent(wxEVT_ENABLE)); + } +} + +void ChecksumTool::OnExportChecksums(wxCommandEvent& event) +{ + // TODO: merge if json already exists + wxDirDialog dialog(this, _("Export checksum entry"), "", wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST); + if (dialog.ShowModal() != wxID_OK || dialog.GetPath().IsEmpty()) + return; + + rapidjson::Document doc; + doc.SetObject(); + auto& a = doc.GetAllocator(); + /* + title_id + region + version + wud_hash + files: + [ + {file, hash} + ] + */ + + auto title_id_str = fmt::format("{:016x}", m_json_entry.title_id); + doc.AddMember("title_id", rapidjson::StringRef(title_id_str.c_str(), title_id_str.size()), a); + doc.AddMember("region", (int)m_info.GetMetaRegion(), a); + doc.AddMember("version", m_info.GetAppTitleVersion(), a); + if (!m_json_entry.wud_hash.empty()) + doc.AddMember("wud_hash", rapidjson::StringRef(m_json_entry.wud_hash.c_str(), m_json_entry.wud_hash.size()), a); + + rapidjson::Value entry_array(rapidjson::kArrayType); + + rapidjson::Value file_array(rapidjson::kArrayType); + for(const auto& file : m_json_entry.file_hashes) + { + rapidjson::Value file_entry; + file_entry.SetObject(); + + file_entry.AddMember("file", rapidjson::StringRef(file.first.c_str(), file.first.size()), a); + file_entry.AddMember("hash", rapidjson::StringRef(file.second.c_str(), file.second.size()), a); + + file_array.PushBack(file_entry, a); + } + + doc.AddMember("files", file_array, a); + + std::filesystem::path target_file{ dialog.GetPath().c_str().AsInternal() }; + target_file /= fmt::format("{}_v{}.json", title_id_str, m_info.GetAppTitleVersion()); + + std::ofstream file(target_file); + if(file.is_open()) + { + rapidjson::OStreamWrapper osw(file); + rapidjson::PrettyWriter writer(osw); + //rapidjson::GenericSchemaValidator > validator(schema, writer); + doc.Accept(writer); + wxMessageBox(_("Export successful"), wxMessageBoxCaptionStr, wxOK | wxCENTRE, this); + } + else + { + wxMessageBox(wxStringFormat2(_("Can't write to file: {}"), target_file.string()), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + } +} + +void ChecksumTool::VerifyJsonEntry(const rapidjson::Document& doc) +{ + rapidjson::Document sdoc; + sdoc.Parse(kSchema, std::size(kSchema)); + wxASSERT(!sdoc.HasParseError()); + rapidjson::SchemaDocument schema(sdoc); + rapidjson::SchemaValidator validator(schema); + if (!doc.Accept(validator)) + { + //// validation error: + //rapidjson::StringBuffer sb; + //validator.GetInvalidSchemaPointer().StringifyUriFragment(sb); + //printf("Invalid schema: %s\n", sb.GetString()); + //printf("Invalid keyword: %s\n", validator.GetInvalidSchemaKeyword()); + //sb.Clear(); + //validator.GetInvalidDocumentPointer().StringifyUriFragment(sb); + //printf("Invalid document: %s\n", sb.GetString()); + ///* + //Invalid schema: # + //Invalid keyword: required + //Invalid document: # + // */ + + wxMessageBox(_("JSON file doesn't satisfy needed schema"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + return; + } + + try + { + JsonEntry test_entry{}; + test_entry.title_id = ConvertString(doc["title_id"].GetString(), 16); + test_entry.region = (CafeConsoleRegion)doc["region"].GetInt(); + test_entry.version = doc["version"].GetInt(); + if (doc.HasMember("wud_hash")) + test_entry.wud_hash = doc["wud_hash"].GetString(); + + for (const auto& v : doc["files"].GetArray()) + { + std::filesystem::path genericFilePath(v["file"].GetString(), std::filesystem::path::generic_format); // convert path to generic form (forward slashes) + test_entry.file_hashes[genericFilePath.generic_string()] = v["hash"].GetString(); + } + + if (m_json_entry.title_id != test_entry.title_id) + { + wxMessageBox(wxStringFormat2(_("The file you are comparing with is for a different title.")), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + return; + } + if (m_json_entry.version != test_entry.version) + { + wxMessageBox(wxStringFormat2(_("Wrong version: {}"), test_entry.version), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + return; + } + if (m_json_entry.region != test_entry.region) + { + wxMessageBox(wxStringFormat2(_("Wrong region: {}"), test_entry.region), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + return; + } + if (!m_json_entry.wud_hash.empty()) + { + if (test_entry.wud_hash.empty()) + { + wxMessageBox(_("The verification data doesn't include a wud hash!"), _("Error"), wxOK | wxCENTRE | wxICON_WARNING, this); + return; + } + if(!boost::iequals(test_entry.wud_hash, m_json_entry.wud_hash)) + { + wxMessageBox(wxStringFormat2(_("Your game image is invalid!\n\nYour hash:\n{}\n\nExpected hash:\n{}"), m_json_entry.wud_hash, test_entry.wud_hash), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + return; + } + } + else + { + std::map> invalid_hashes; + std::vector missing_files; + const auto writeMismatchInfoToLog = [this, &missing_files, &invalid_hashes]() + { + wxFileDialog dialog(this, _("Select a file to export the errors"), wxEmptyString, wxEmptyString, "Error list (*.txt)|*.txt", wxFD_SAVE | wxFD_OVERWRITE_PROMPT); + if (dialog.ShowModal() != wxID_OK || dialog.GetPath().IsEmpty()) + return; + + const std::string path = dialog.GetPath().ToUTF8().data(); + std::ofstream file(path); + if (file.is_open()) + { + if (!missing_files.empty()) + { + file << "The following files are missing:\n"; + for (const auto& f : missing_files) + file << "\t" << f << "\n"; + + file << "\n"; + } + + if (!invalid_hashes.empty()) + { + file << "The following files have an invalid hash (name | current hash | expected hash):\n"; + for (const auto& f : invalid_hashes) + file << "\t" << f.first << " | " << f.second.first << " | " << f.second.second << "\n"; + } + file.flush(); + file.close(); +#ifdef _WIN32 + ShellExecuteA(GetHWND(), "open", path.c_str(), nullptr, nullptr, SW_SHOWNORMAL); +#else + assert_dbg(); +#endif + } + else + wxMessageBox(_("Can't open file to write!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + }; + + for (const auto& f : test_entry.file_hashes) + { + const auto it = m_json_entry.file_hashes.find(f.first); + if (it == m_json_entry.file_hashes.cend()) + { + missing_files.emplace_back(f.first); + } + else if (!boost::iequals(f.second, it->second)) + { + invalid_hashes[f.first] = std::make_pair(it->second, f.second); + } + } + + // files are missing but rest is okay + if ((!missing_files.empty() || !invalid_hashes.empty()) && (missing_files.size() + invalid_hashes.size()) < 30) + { + // the list of missing + invalid hashes is short enough that we can print it to the message box + std::stringstream str; + if (missing_files.size() > 0) + { + str << _("The following files are missing:").ToUTF8().data() << "\n"; + for (const auto& v : missing_files) + str << v << "\n"; + if(invalid_hashes.size() > 0) + str << "\n"; + } + if (invalid_hashes.size() > 0) + { + str << _("The following files are damaged:").ToUTF8().data() << "\n"; + for (const auto& v : invalid_hashes) + str << v.first << "\n"; + } + + wxMessageBox(str.str(), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + return; + } + else if (missing_files.empty() && !invalid_hashes.empty()) + { + const int result = wxMessageBox(wxStringFormat2(_("{} files have an invalid hash!\nDo you want to export a list of them to a file?"), invalid_hashes.size()), _("Error"), wxYES_NO | wxCENTRE | wxICON_ERROR, this); + if (result == wxYES) + { + writeMismatchInfoToLog(); + } + return; + } + else if (!missing_files.empty() && !invalid_hashes.empty()) + { + const int result = wxMessageBox(wxStringFormat2(_("Multiple issues with your game files have been found!\nDo you want to export them to a file?"), invalid_hashes.size()), _("Error"), wxYES_NO | wxCENTRE | wxICON_ERROR, this); + if (result == wxYES) + { + writeMismatchInfoToLog(); + } + return; + } + } + wxMessageBox(_("Your game files are valid"), _("Success"), wxOK | wxCENTRE, this); + } + catch (const std::exception& ex) + { + wxMessageBox(wxStringFormat2(_("JSON parse error: {}"), ex.what()), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + } + +} + +void ChecksumTool::OnVerifyOnline(wxCommandEvent& event) +{ + const auto title_id_str = fmt::format("{:016x}", m_json_entry.title_id); + const auto default_file = fmt::format("{}_v{}.json", title_id_str, m_info.GetAppTitleVersion()); + + const auto checksum_path = ActiveSettings::GetPath("resources/checksums/{}", default_file); + if(!exists(checksum_path)) + return; + + std::ifstream file(checksum_path); + if (!file.is_open()) + { + wxMessageBox(_("Can't open file!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + return; + } + + rapidjson::IStreamWrapper str(file); + rapidjson::Document d; + d.ParseStream(str); + if (d.HasParseError()) + { + wxMessageBox(_("Can't parse json file!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + return; + } + + VerifyJsonEntry(d); +} + +void ChecksumTool::OnVerifyLocal(wxCommandEvent& event) +{ + const auto title_id_str = fmt::format("{:016x}", m_json_entry.title_id); + const auto default_file = fmt::format("{}_v{}.json", title_id_str, m_info.GetAppTitleVersion()); + wxFileDialog file_dialog(this, _("Open checksum entry"), "", default_file.c_str(),"JSON files (*.json)|*.json", wxFD_OPEN | wxFD_FILE_MUST_EXIST); + if (file_dialog.ShowModal() != wxID_OK || file_dialog.GetPath().IsEmpty()) + return; + + std::filesystem::path filename{ file_dialog.GetPath().c_str().AsInternal() }; + std::ifstream file(filename); + if(!file.is_open()) + { + wxMessageBox(_("Can't open file!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + return; + } + + rapidjson::IStreamWrapper str(file); + rapidjson::Document d; + d.ParseStream(str); + if (d.HasParseError()) + { + wxMessageBox(_("Can't parse json file!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + return; + } + + VerifyJsonEntry(d); +} + +static void _fscGetAllFiles(std::set& allFilesOut, const std::string& fscBasePath, const std::string& relativePath) +{ + sint32 fscStatus; + FSCVirtualFile* fsc = fsc_openDirIterator((fscBasePath + relativePath).c_str(), &fscStatus); + cemu_assert(fsc); + FSCDirEntry dirEntry; + while (fsc_nextDir(fsc, &dirEntry)) + { + if (dirEntry.isDirectory) + { + _fscGetAllFiles(allFilesOut, fscBasePath, std::string(relativePath).append(dirEntry.GetPath()).append("/")); + } + else + { + allFilesOut.emplace(std::string(relativePath).append(dirEntry.GetPath())); + } + } + delete fsc; +} + +void ChecksumTool::DoWork() +{ + m_json_entry.title_id = m_info.GetAppTitleId(); + m_json_entry.region = m_info.GetMetaRegion(); + m_json_entry.version = m_info.GetAppTitleVersion(); + + static_assert(SHA256_DIGEST_LENGTH == 32); + + std::array checksum{}; + + switch (m_info.GetFormat()) + { + case TitleInfo::TitleDataFormat::WUD: + { + const auto path = m_entry.path.string(); + wxQueueEvent(this, new wxSetGaugeValue(1, m_progress, m_status, wxStringFormat2(_("Reading game image: {}"), path))); + + wud_t* wud = wud_open(m_info.GetPath()); + if (!wud) + throw std::runtime_error("can't open game image"); + + const auto wud_size = wud_getWUDSize(wud); + std::vector buffer(1024 * 1024 * 8); + + SHA256_CTX sha256; + SHA256_Init(&sha256); + + uint32 read = 0; + size_t offset = 0; + auto size = wud_size; + do + { + if (!m_running.load(std::memory_order_relaxed)) + { + wud_close(wud); + return; + } + + read = wud_readData(wud, buffer.data(), std::min(buffer.size(), (size_t)wud_size - offset), offset); + offset += read; + size -= read; + + SHA256_Update(&sha256, buffer.data(), read); + + wxQueueEvent(this, new wxSetGaugeValue((int)((offset * 90) / wud_size), m_progress, m_status, wxStringFormat2(_("Reading game image: {}/{}kb"), offset / 1024, wud_size / 1024))); + } while (read != 0 && size > 0); + wud_close(wud); + + wxQueueEvent(this, new wxSetGaugeValue(90, m_progress, m_status, wxStringFormat2(_("Generating checksum of game image: {}"), path))); + + if (!m_running.load(std::memory_order_relaxed)) + return; + + SHA256_Final(checksum.data(), &sha256); + + std::stringstream str; + for (const auto& b : checksum) + { + str << fmt::format("{:02X}", b); + } + + m_json_entry.wud_hash = str.str(); + + wxQueueEvent(this, new wxSetGaugeValue(100, m_progress, m_status, wxStringFormat2(_("Generated checksum of game image: {}"), path))); + break; + } + default: + // we hash the individual files for all formats except WUD/WUX + std::string temporaryMountPath = TitleInfo::GetUniqueTempMountingPath(); + m_info.Mount(temporaryMountPath.c_str(), "", FSC_PRIORITY_BASE); + wxQueueEvent(this, new wxSetGaugeValue(1, m_progress, m_status, _("Grabbing game files"))); + + // get list of all files + std::set files; + _fscGetAllFiles(files, temporaryMountPath, ""); + + const size_t file_count = files.size(); + size_t counter = 0; + for (const auto& filename : files) + { + auto fileData = fsc_extractFile((temporaryMountPath + "/" + filename).c_str()); + if (!fileData) + { + cemuLog_log(LogType::Force, "Failed to open {}", filename); + continue; + } + + SHA256_CTX sha256; + SHA256_Init(&sha256); + SHA256_Update(&sha256, fileData->data(), fileData->size()); + SHA256_Final(checksum.data(), &sha256); + + std::stringstream str; + for (const auto& b : checksum) + { + str << fmt::format("{:02X}", b); + } + + // store relative path and hash + m_json_entry.file_hashes[filename] = str.str(); + + ++counter; + wxQueueEvent(this, new wxSetGaugeValue((int)((counter * 100) / file_count), m_progress, m_status, wxStringFormat2(_("Hashing game file: {}/{}"), counter, file_count))); + + if (!m_running.load(std::memory_order_relaxed)) + { + m_info.Unmount(temporaryMountPath.c_str()); + return; + } + } + m_info.Unmount(temporaryMountPath.c_str()); + + wxQueueEvent(this, new wxSetGaugeValue(100, m_progress, m_status, wxStringFormat2(_("Generated checksum of {} game files"), file_count))); + break; + } +} \ No newline at end of file diff --git a/src/gui/ChecksumTool.h b/src/gui/ChecksumTool.h new file mode 100644 index 00000000..1b54733b --- /dev/null +++ b/src/gui/ChecksumTool.h @@ -0,0 +1,51 @@ +#pragma once +#include +#include "gui/components/wxTitleManagerList.h" +#include "Cafe/TitleList/TitleInfo.h" +#include + +class wxSetGaugeValue; + +class ChecksumTool : public wxDialog +{ +public: + ChecksumTool(wxWindow* parent, wxTitleManagerList::TitleEntry& entry); + ~ChecksumTool(); + +private: + std::future m_online_ready; + void LoadOnlineData() const; + void VerifyJsonEntry(const rapidjson::Document& doc); + + void OnSetGaugevalue(wxSetGaugeValue& event); + void OnExportChecksums(wxCommandEvent& event); + void OnVerifyOnline(wxCommandEvent& event); + void OnVerifyLocal(wxCommandEvent& event); + + void DoWork(); + std::atomic_bool m_running = true; + std::thread m_worker; + + class wxGauge* m_progress; + class wxStaticText* m_status; + class wxButton *m_verify_online, *m_verify_local, *m_export_button; + int m_enable_verify_button = 0; + + TitleInfo m_info; + wxTitleManagerList::TitleEntry m_entry; + wxColour m_default_color; + + struct JsonEntry + { + uint64 title_id; + uint32 version; + CafeConsoleRegion region; + + std::string wud_hash; + std::map file_hashes; + }; + JsonEntry m_json_entry; + inline static std::mutex s_mutex{}; + inline static std::vector s_entries{}; + +}; diff --git a/src/gui/DownloadGraphicPacksWindow.cpp b/src/gui/DownloadGraphicPacksWindow.cpp new file mode 100644 index 00000000..8835b9fc --- /dev/null +++ b/src/gui/DownloadGraphicPacksWindow.cpp @@ -0,0 +1,408 @@ +#include "gui/wxgui.h" +#include "gui/DownloadGraphicPacksWindow.h" + +#include +#include +#include +#include +#include + +#include "config/ActiveSettings.h" +#include "Common/filestream.h" + +#include "Cafe/CafeSystem.h" + +struct DownloadGraphicPacksWindow::curlDownloadFileState_t +{ + std::vector fileData; + double progress; +}; + +size_t DownloadGraphicPacksWindow::curlDownloadFile_writeData(void *ptr, size_t size, size_t nmemb, curlDownloadFileState_t* downloadState) +{ + const size_t writeSize = size * nmemb; + const size_t currentSize = downloadState->fileData.size(); + const size_t newSize = currentSize + writeSize; + downloadState->fileData.resize(newSize); + memcpy(downloadState->fileData.data() + currentSize, ptr, writeSize); + return writeSize; +} + +int DownloadGraphicPacksWindow::progress_callback(curlDownloadFileState_t* downloadState, double dltotal, double dlnow, double ultotal, double ulnow) +{ + if (dltotal > 1.0) + downloadState->progress = dlnow / dltotal; + else + downloadState->progress = 0.0; + return 0; +} + +bool DownloadGraphicPacksWindow::curlDownloadFile(const char *url, curlDownloadFileState_t* downloadState) +{ + CURL* curl = curl_easy_init(); + if (curl == nullptr) + return false; + + downloadState->progress = 0.0; + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlDownloadFile_writeData); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, downloadState); + curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, progress_callback); + curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, downloadState); + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, true); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); + + char temp[128]; + sprintf(temp, "Cemu_%d.%d%s", EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_SUFFIX); + curl_easy_setopt(curl, CURLOPT_USERAGENT, temp); + downloadState->fileData.resize(0); + const CURLcode res = curl_easy_perform(curl); + curl_easy_cleanup(curl); + return res == CURLE_OK; +} + +// returns true if the version matches +bool checkGraphicPackDownloadedVersion(const char* nameVersion, bool& hasVersionFile) +{ + hasVersionFile = false; + const auto path = ActiveSettings::GetPath("graphicPacks/downloadedGraphicPacks/version.txt"); + std::unique_ptr file(FileStream::openFile2(path)); + + std::string versionInFile; + if (file && file->readLine(versionInFile)) + { + return boost::iequals(versionInFile, nameVersion); + } + return false; +} + +void createGraphicPackDownloadedVersionFile(const char* nameVersion) +{ + const auto path = ActiveSettings::GetPath("graphicPacks/downloadedGraphicPacks/version.txt"); + std::unique_ptr file(FileStream::createFile2(path)); + if (file) + file->writeString(nameVersion); + else + { + cemuLog_force("Failed to write graphic pack version.txt"); + } +} + +void deleteDownloadedGraphicPacks() +{ + const auto path = ActiveSettings::GetPath("graphicPacks/downloadedGraphicPacks"); + std::error_code er; + if (!fs::exists(path)) + return; + try + { + for (auto& p : fs::directory_iterator(path)) + { + fs::remove_all(p.path(), er); + } + } + catch (std::filesystem::filesystem_error& e) + { + cemuLog_log(LogType::Force, "Error in deleteDownloadedGraphicPacks():"); + cemuLog_log(LogType::Force, e.what()); + } +} + +void DownloadGraphicPacksWindow::UpdateThread() +{ + if (CafeSystem::IsTitleRunning()) + { + wxMessageBox(_("Graphic packs cannot be updated while a game is running."), _("Graphic packs"), 5, this->GetParent()); + // cancel update + m_threadState = ThreadFinished; + return; + } + + // get github url + std::string githubAPIUrl; + curlDownloadFileState_t tempDownloadState; + std::string queryUrl("http://cemu.info/api/query_graphicpack_url_1_17_0.php?"); + char temp[64]; + sprintf(temp, "version=%d.%d.%d%s", EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, EMULATOR_VERSION_SUFFIX); + queryUrl.append(temp); + queryUrl.append("&"); + sprintf(temp, "t=%u", (uint32)std::chrono::seconds(std::time(NULL)).count()); // add a dynamic part to the url to bypass overly aggressive caching (like some proxies do) + queryUrl.append(temp); + if (curlDownloadFile(queryUrl.c_str(), &tempDownloadState) && boost::starts_with((const char*)tempDownloadState.fileData.data(), "http")) + { + // convert downloaded data to url string + githubAPIUrl.assign(tempDownloadState.fileData.cbegin(), tempDownloadState.fileData.cend()); + } + else + { + // cemu api request failed, use hardcoded github url + forceLog_printf("Graphic pack update request failed or returned invalid URL. Using default repository URL instead"); + githubAPIUrl = "https://api.github.com/repos/slashiee/cemu_graphic_packs/releases/latest"; + } + // github API request + if (curlDownloadFile(githubAPIUrl.c_str(), &tempDownloadState) == false) + { + wxMessageBox( _("Error"), _(L"Failed to connect to server"), wxOK | wxCENTRE | wxICON_ERROR, this); + m_threadState = ThreadError; + return; + } + // parse json result + rapidjson::Document d; + d.Parse((const char*)tempDownloadState.fileData.data(), tempDownloadState.fileData.size()); + if (d.HasParseError()) + { + m_threadState = ThreadError; + return; + } + auto& jsonName = d["name"]; + if (jsonName.IsString() == false) + { + m_threadState = ThreadError; + return; + } + const char* assetName = jsonName.GetString(); // name includes version + if( d.IsObject() == false) + { + m_threadState = ThreadError; + return; + } + auto& jsonAssets = d["assets"]; + if (jsonAssets.IsArray() == false || jsonAssets.GetArray().Size() == 0) + { + m_threadState = ThreadError; + return; + } + auto& jsonAsset0 = jsonAssets.GetArray()[0]; + if (jsonAsset0.IsObject() == false) + { + m_threadState = ThreadError; + return; + } + auto& jsonDownloadUrl = jsonAsset0["browser_download_url"]; + if (jsonDownloadUrl.IsString() == false) + { + m_threadState = ThreadError; + return; + } + const char* browserDownloadUrl = jsonDownloadUrl.GetString(); + // check version + bool hasVersionFile = false; + if (checkGraphicPackDownloadedVersion(assetName, hasVersionFile)) + { + // already up to date + wxMessageBox(_("No updates available."), _("Graphic packs"), wxOK | wxCENTRE, this->GetParent()); + m_threadState = ThreadFinished; + return; + } + if (hasVersionFile) + { + // if a version file already exists (and graphic packs are installed) ask the user if he really wants to update + if (wxMessageBox(_("Updated graphic packs are available. Do you want to download and install them?"), _("Graphic packs"), wxYES_NO, this->GetParent()) != wxYES) + { + // cancel update + m_threadState = ThreadFinished; + return; + } + } + // download zip + m_stage = StageDownloading; + if (curlDownloadFile(browserDownloadUrl, m_downloadState.get()) == false) + { + wxMessageBox(_("Error"), _(L"Failed to connect to server"), wxOK | wxCENTRE | wxICON_ERROR, this); + m_threadState = ThreadError; + return; + } + + m_extractionProgress = 0.0; + m_stage = StageExtracting; + + zip_source_t *src; + zip_t *za; + zip_error_t error; + + // init zip source + zip_error_init(&error); + if ((src = zip_source_buffer_create(m_downloadState->fileData.data(), m_downloadState->fileData.size(), 1, &error)) == NULL) + { + zip_error_fini(&error); + m_threadState = ThreadError; + return; + } + + // open zip from source + if ((za = zip_open_from_source(src, 0, &error)) == NULL) + { + zip_source_free(src); + zip_error_fini(&error); + m_threadState = ThreadError; + return; + } + + auto path = ActiveSettings::GetPath("graphicPacks/downloadedGraphicPacks"); + std::error_code er; + //fs::remove_all(path, er); -> Don't delete the whole folder and recreate it immediately afterwards because sometimes it just fails + deleteDownloadedGraphicPacks(); + fs::create_directories(path, er); // make sure downloadedGraphicPacks folder exists + + sint32 numEntries = zip_get_num_entries(za, 0); + for (sint32 i = 0; i < numEntries; i++) + { + m_extractionProgress = (double)i / (double)numEntries; + zip_stat_t sb = { 0 }; + if (zip_stat_index(za, i, 0, &sb) != 0) + { + assert_dbg(); + } + + if(std::strstr(sb.name, "../") != nullptr || + std::strstr(sb.name, "..\\") != nullptr) + continue; // bad path + + path = ActiveSettings::GetPath("graphicPacks/downloadedGraphicPacks/{}", sb.name); + + size_t sbNameLen = strlen(sb.name); + if(sbNameLen == 0) + continue; + if (sb.name[sbNameLen - 1] == '/') + { + fs::create_directories(path, er); + continue; + } + if(sb.size == 0) + continue; + if (sb.size > (1024 * 1024 * 128)) + continue; // skip unusually huge files + + zip_file_t* zipFile = zip_fopen_index(za, i, 0); + if (zipFile == nullptr) + continue; + + uint8* fileBuffer = new uint8[sb.size]; + + if (zip_fread(zipFile, fileBuffer, sb.size) == sb.size) + { + FileStream* fs = FileStream::createFile2(path); + if (fs) + { + fs->writeData(fileBuffer, sb.size); + delete fs; + } + } + + delete [] fileBuffer; + zip_fclose(zipFile); + } + + zip_error_fini(&error); + createGraphicPackDownloadedVersionFile(assetName); + m_threadState = ThreadFinished; +} + +DownloadGraphicPacksWindow::DownloadGraphicPacksWindow(wxWindow* parent) + : wxDialog(parent, wxID_ANY, _("Checking version..."), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxMINIMIZE_BOX | wxSYSTEM_MENU | wxTAB_TRAVERSAL | wxCLOSE_BOX), + m_threadState(ThreadRunning), m_stage(StageCheckVersion), m_currentStage(StageCheckVersion) +{ + auto* sizer = new wxBoxSizer(wxVERTICAL); + + m_processBar = new wxGauge(this, wxID_ANY, 100, wxDefaultPosition, wxSize(500, 20), wxGA_HORIZONTAL); + m_processBar->SetValue(0); + m_processBar->SetRange(100); + sizer->Add(m_processBar, 0, wxALL | wxEXPAND, 5); + + auto* m_cancelButton = new wxButton(this, wxID_ANY, _("Cancel"), wxDefaultPosition, wxDefaultSize, 0); + m_cancelButton->Bind(wxEVT_BUTTON, &DownloadGraphicPacksWindow::OnCancelButton, this); + sizer->Add(m_cancelButton, 0, wxALIGN_RIGHT | wxALL, 5); + + this->SetSizer(sizer); + this->Centre(wxBOTH); + + wxWindowBase::Layout(); + wxWindowBase::Fit(); + + m_timer = new wxTimer(this); + this->Bind(wxEVT_TIMER, &DownloadGraphicPacksWindow::OnUpdate, this); + this->Bind(wxEVT_CLOSE_WINDOW, &DownloadGraphicPacksWindow::OnClose, this); + m_timer->Start(250); + + + m_downloadState = std::make_unique(); + + m_thread = std::thread(&DownloadGraphicPacksWindow::UpdateThread, this); +} + +DownloadGraphicPacksWindow::~DownloadGraphicPacksWindow() +{ + m_timer->Stop(); + if (m_thread.joinable()) + m_thread.join(); +} + +const std::string& DownloadGraphicPacksWindow::GetException() const +{ + return m_threadException; +} + +int DownloadGraphicPacksWindow::ShowModal() +{ + wxDialog::ShowModal(); + return m_threadState == ThreadCanceled ? wxID_CANCEL : wxID_OK; +} + +void DownloadGraphicPacksWindow::OnClose(wxCloseEvent& event) +{ + if (m_threadState == ThreadRunning) + { + //wxMessageDialog dialog(this, _("Do you really want to cancel the update process?\n\nCanceling the process will delete the applied update."), _("Info"), wxCENTRE | wxYES_NO); + //if (dialog.ShowModal() != wxID_YES) + // return; + + m_threadState = ThreadCanceled; + } + + m_timer->Stop(); + if (m_thread.joinable()) + m_thread.join(); + + event.Skip(); +} + +void DownloadGraphicPacksWindow::OnUpdate(const wxTimerEvent& event) +{ + if (m_threadState != ThreadRunning) + { + Close(); + return; + } + if (m_currentStage != m_stage) + { + if (m_stage == StageDownloading) + { + this->SetTitle(_("Downloading graphic packs...")); + } + else if (m_stage == StageExtracting) + { + this->SetTitle(_("Extracting...")); + } + m_currentStage = m_stage; + } + + if (m_currentStage == StageDownloading) + { + const sint32 processedSize = (sint32)(m_downloadState->progress * 100.0f); + if (m_processBar->GetValue() != processedSize) + m_processBar->SetValue(processedSize); + } + else if (m_currentStage == StageExtracting) + { + const sint32 processedSize = (sint32)(m_extractionProgress * 100.0f); + if (m_processBar->GetValue() != processedSize) + m_processBar->SetValue(processedSize); + } +} + +void DownloadGraphicPacksWindow::OnCancelButton(const wxCommandEvent& event) +{ + Close(); +} diff --git a/src/gui/DownloadGraphicPacksWindow.h b/src/gui/DownloadGraphicPacksWindow.h new file mode 100644 index 00000000..860bb267 --- /dev/null +++ b/src/gui/DownloadGraphicPacksWindow.h @@ -0,0 +1,60 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include + + +class DownloadGraphicPacksWindow : public wxDialog +{ +public: + DownloadGraphicPacksWindow(wxWindow* parent); + ~DownloadGraphicPacksWindow(); + + const std::string& GetException() const; + + int ShowModal() override; + void OnClose(wxCloseEvent& event); + + void OnUpdate(const wxTimerEvent& event); + void OnCancelButton(const wxCommandEvent& event); + +private: + void UpdateThread(); + + enum ThreadState_t + { + ThreadRunning, + ThreadCanceled, + ThreadError, + ThreadFinished, + }; + + enum DownloadStage_t + { + StageCheckVersion, + StageDownloading, + StageExtracting + }; + + std::atomic m_threadState; + std::atomic m_stage; + std::atomic m_extractionProgress; + std::string m_threadException; + std::thread m_thread; + + DownloadStage_t m_currentStage; + wxGauge* m_processBar; + wxTimer* m_timer; + + struct curlDownloadFileState_t; + std::unique_ptr m_downloadState; + + static size_t curlDownloadFile_writeData(void* ptr, size_t size, size_t nmemb, curlDownloadFileState_t* downloadState); + static int progress_callback(curlDownloadFileState_t* downloadState, double dltotal, double dlnow, double ultotal, double ulnow); + static bool curlDownloadFile(const char* url, curlDownloadFileState_t* downloadState); +}; diff --git a/src/gui/GameProfileWindow.cpp b/src/gui/GameProfileWindow.cpp new file mode 100644 index 00000000..a807e6cc --- /dev/null +++ b/src/gui/GameProfileWindow.cpp @@ -0,0 +1,369 @@ +#include "gui/GameProfileWindow.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "gui/helpers/wxHelpers.h" +#include "input/InputManager.h" + +#if BOOST_OS_LINUX +#include "resource/linux/resources.h" +#endif + +GameProfileWindow::GameProfileWindow(wxWindow* parent, uint64_t title_id) + : wxFrame(parent, wxID_ANY, _("Edit game profile"), wxDefaultPosition, wxSize{ 390, 350 }, wxCLOSE_BOX | wxCLIP_CHILDREN | wxCAPTION | wxRESIZE_BORDER | wxTAB_TRAVERSAL | wxSYSTEM_MENU), m_title_id(title_id) +{ + SetIcon(wxICON(X_GAME_PROFILE)); + + m_game_profile.Reset(); + m_game_profile.Load(title_id, false); + + this->SetSizeHints(wxDefaultSize, wxDefaultSize); + + auto* main_sizer = new wxBoxSizer(wxVERTICAL); + + auto* m_notebook = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0); + // general + { + auto* panel = new wxPanel(m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + auto* sizer = new wxBoxSizer(wxVERTICAL); + { + auto* box_sizer = new wxStaticBoxSizer(new wxStaticBox(panel, wxID_ANY, _("General")), wxVERTICAL); + auto* box = box_sizer->GetStaticBox(); + + m_load_libs = new wxCheckBox(box, wxID_ANY, _("Load shared libraries")); + m_load_libs->SetToolTip(_("EXPERT OPTION\nThis option will load libraries from the cafeLibs directory")); + box_sizer->Add(m_load_libs, 0, wxALL, 5); + + m_start_with_padview = new wxCheckBox(box, wxID_ANY, _("Launch with gamepad view")); + m_start_with_padview->SetToolTip(_("Games will be launched with gamepad view toggled as default. The view can be toggled with CTRL + TAB")); + box_sizer->Add(m_start_with_padview, 0, wxALL, 5); + + sizer->Add(box_sizer, 0, wxEXPAND, 5); + } + // cpu + { + auto* box_sizer = new wxStaticBoxSizer(new wxStaticBox(panel, wxID_ANY, _("CPU")), wxVERTICAL); + auto* box = box_sizer->GetStaticBox(); + + auto* first_row = new wxFlexGridSizer(0, 2, 0, 0); + first_row->SetFlexibleDirection(wxBOTH); + first_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); + + first_row->Add(new wxStaticText(box, wxID_ANY, _("Mode")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + + wxString cpu_modes[] = { _("Single-core interpreter"), _("Single-core recompiler"), _("Multi-core recompiler"), _("Auto (recommended)") }; + const sint32 m_cpu_modeNChoices = std::size(cpu_modes); + m_cpu_mode = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, m_cpu_modeNChoices, cpu_modes, 0); + m_cpu_mode->SetToolTip(_("Set the CPU emulation mode")); + first_row->Add(m_cpu_mode, 0, wxALL, 5); + + first_row->Add(new wxStaticText(box, wxID_ANY, _("Thread quantum")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + + auto* quantum_sizer = new wxFlexGridSizer(0, 2, 0, 0); + quantum_sizer->SetFlexibleDirection(wxBOTH); + quantum_sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); + + wxString quantum_values[] = { "20000", "45000", "60000", "80000" ,"100000" }; + m_thread_quantum = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(quantum_values), quantum_values); + m_thread_quantum->SetMinSize(wxSize(85, -1)); + m_thread_quantum->SetToolTip(_("EXPERT OPTION\nSet the maximum thread slice runtime (in virtual cycles)")); + quantum_sizer->Add(m_thread_quantum, 0, wxALL, 5); + + quantum_sizer->Add(new wxStaticText(box, wxID_ANY, _("cycles")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + + first_row->Add(quantum_sizer, 0, wxEXPAND, 5); + + box_sizer->Add(first_row, 0, wxEXPAND, 5); + + + sizer->Add(box_sizer, 0, wxEXPAND, 5); + } + + panel->SetSizer(sizer); + panel->Layout(); + sizer->Fit(panel); + m_notebook->AddPage(panel, _("General"), true); + } + + // graphic + { + auto* panel = new wxPanel(m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + auto* sizer = new wxBoxSizer(wxVERTICAL); + + //m_extended_texture_readback = new wxCheckBox(panel, wxID_ANY, _("Extended texture readback")); + //m_extended_texture_readback->SetToolTip(_("Improves emulation accuracy of CPU to GPU memory access at the cost of performance. Required for some games.")); + //sizer->Add(m_extended_texture_readback, 0, wxALL, 5); + + auto* first_row = new wxFlexGridSizer(0, 2, 0, 0); + first_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); + + /*first_row->Add(new wxStaticText(panel, wxID_ANY, _("Precompiled shaders")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + wxString precompiled_modes[] = { _("auto"), _("enable"), _("disable") }; + const sint32 precompiled_count = std::size(precompiled_modes); + m_precompiled = new wxChoice(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, precompiled_count, precompiled_modes, 0); + m_precompiled->SetToolTip(_("Precompiled shaders can speed up the load time on the shader loading screen.\nAuto will enable it for AMD/Intel but disable it for NVIDIA GPUs as a workaround for a driver bug.\n\nRecommended: Auto")); + first_row->Add(m_precompiled, 0, wxALL, 5);*/ + + first_row->Add(new wxStaticText(panel, wxID_ANY, _("Graphics API")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + + wxString gapi_values[] = { "", "OpenGL", "Vulkan" }; + m_graphic_api = new wxChoice(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, (int)std::size(gapi_values), gapi_values); + first_row->Add(m_graphic_api, 0, wxALL, 5); + + first_row->Add(new wxStaticText(panel, wxID_ANY, _("Shader mul accuracy")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + + wxString mul_values[] = { _("false"), _("true"), _("minimal") }; + m_shader_mul_accuracy = new wxChoice(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, (int)std::size(mul_values), mul_values); + m_shader_mul_accuracy->SetToolTip(_("EXPERT OPTION\nControls the accuracy of floating point multiplication in shaders.\n\nRecommended: true")); + first_row->Add(m_shader_mul_accuracy, 0, wxALL, 5); + + /*first_row->Add(new wxStaticText(panel, wxID_ANY, _("GPU buffer cache accuracy")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + wxString accuarcy_values[] = { _("high"), _("medium"), _("low") }; + m_cache_accuracy = new wxChoice(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, (int)std::size(accuarcy_values), accuarcy_values); + m_cache_accuracy->SetToolTip(_("Lower value results in higher performance, but may cause graphical issues")); + first_row->Add(m_cache_accuracy, 0, wxALL, 5);*/ + + sizer->Add(first_row, 0, wxEXPAND, 5); + + + panel->SetSizer(sizer); + panel->Layout(); + sizer->Fit(panel); + m_notebook->AddPage(panel, _("Graphic"), false); + } + + //// audio + //{ + // auto panel = new wxPanel(m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + // wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL); + + // m_disable_audio = new wxCheckBox(panel, wxID_ANY, _("Disable Audio"), wxDefaultPosition, wxDefaultSize, wxCHK_3STATE | wxCHK_ALLOW_3RD_STATE_FOR_USER); + // sizer->Add(m_disable_audio, 0, wxALL, 5); + + + // panel->SetSizer(sizer); + // panel->Layout(); + // sizer->Fit(panel); + // m_notebook->AddPage(panel, _("Audio"), false); + //} + + // controller + { + auto panel = new wxPanel(m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + wxBoxSizer* sizer; + sizer = new wxBoxSizer(wxVERTICAL); + + wxFlexGridSizer* profile_sizer; + profile_sizer = new wxFlexGridSizer(0, 2, 0, 0); + profile_sizer->SetFlexibleDirection(wxBOTH); + profile_sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); + + for (int i = 0; i < 8; ++i) + { + profile_sizer->Add(new wxStaticText(panel, wxID_ANY, fmt::format("{} {}", _("Controller").ToStdString(), (i + 1))), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + + wxArrayString m_controller_profileChoices; + m_controller_profile[i] = new wxComboBox(panel, wxID_ANY,"", wxDefaultPosition, wxDefaultSize, 0, nullptr, wxCB_DROPDOWN| wxCB_READONLY); + m_controller_profile[i]->SetMinSize(wxSize(250, -1)); + m_controller_profile[i]->Bind(wxEVT_COMBOBOX_DROPDOWN, &GameProfileWindow::OnControllerProfileDropdown, this); + m_controller_profile[i]->SetToolTip(_("Forces a given controller profile")); + profile_sizer->Add(m_controller_profile[i], 0, wxALL, 5); + } + + sizer->Add(profile_sizer, 0, wxEXPAND, 5); + + panel->SetSizer(sizer); + panel->Layout(); + sizer->Fit(panel); + m_notebook->AddPage(panel, _("Controller"), false); + } + + main_sizer->Add(m_notebook, 1, wxEXPAND | wxALL, 5); + + + this->SetSizer(main_sizer); + this->Layout(); + + this->Centre(wxBOTH); + + ApplyProfile(); +} + +GameProfileWindow::~GameProfileWindow() +{ + SaveProfile(); +} + +void GameProfileWindow::OnStreamoutSizeChange(wxCommandEvent& event) +{ + wxSlider* slider = wxDynamicCast(event.GetEventObject(), wxSlider); + wxASSERT(slider); + wxStaticText* text = wxDynamicCast(slider->GetClientData(), wxStaticText); + wxASSERT(text); + text->SetLabelText(fmt::format("{} MB", slider->GetValue())); + event.Skip(); +} + +void GameProfileWindow::OnControllerProfileDropdown(wxCommandEvent& event) +{ + wxComboBox* cb = wxDynamicCast(event.GetEventObject(), wxComboBox); + wxASSERT(cb); + + wxWindowUpdateLocker lock(cb); + + const auto selected_value = cb->GetStringSelection(); + cb->Clear(); + cb->Append(wxEmptyString); + + auto profiles = InputManager::get_profiles(); + for (const auto& profile : profiles) + { + cb->Append(to_wxString(profile)); + } + + cb->SetStringSelection(selected_value); +} + +void GameProfileWindow::SetProfileInt(gameProfileIntegerOption_t& option, wxCheckBox* checkbox, sint32 value) const +{ + const auto state = checkbox->GetValue(); + if (state) + { + option.isPresent = true; + option.value = value; + } + else + option.isPresent = false; +} + +void GameProfileWindow::ApplyProfile() +{ + if(m_game_profile.m_gameName) + this->SetTitle(fmt::format("{} - {}", _("Edit game profile").ToStdString(), m_game_profile.m_gameName.value())); + + // general + m_load_libs->SetValue(m_game_profile.m_loadSharedLibraries.value()); + m_start_with_padview->SetValue(m_game_profile.m_startWithPadView); + + // cpu + // wxString cpu_modes[] = { _("Singlecore-Interpreter"), _("Singlecore-Recompiler"), _("Triplecore-Recompiler"), _("Auto (recommended)") }; + switch(m_game_profile.m_cpuMode.value()) + { + case CPUMode::SinglecoreInterpreter: m_cpu_mode->SetSelection(0); break; + case CPUMode::SinglecoreRecompiler: m_cpu_mode->SetSelection(1); break; + case CPUMode::DualcoreRecompiler: m_cpu_mode->SetSelection(2); break; + case CPUMode::MulticoreRecompiler: m_cpu_mode->SetSelection(2); break; + default: m_cpu_mode->SetSelection(3); + } + + m_thread_quantum->SetStringSelection(fmt::format("{}", m_game_profile.m_threadQuantum)); + + // gpu + if (!m_game_profile.m_graphics_api.has_value()) + m_graphic_api->SetSelection(0); // selecting "" + else + m_graphic_api->SetSelection(1 + m_game_profile.m_graphics_api.value()); // "", OpenGL, Vulkan + //m_extended_texture_readback->SetValue(m_game_profile.m_extendedTextureReadback); + //m_precompiled->SetSelection((int)m_game_profile.m_precompiledShaders.value()); + m_shader_mul_accuracy->SetSelection((int)m_game_profile.m_accurateShaderMul); + //m_cache_accuracy->SetSelection((int)m_game_profile.m_gpuBufferCacheAccuracy.value()); + + //// audio + //m_disable_audio->Set3StateValue(GetCheckboxState(m_game_profile.disableAudio)); + + // controller + auto profiles = InputManager::get_profiles(); + + for (const auto& cb : m_controller_profile) + { + cb->Clear(); + for (const auto& profile : profiles) + { + cb->Append(to_wxString(profile)); + } + } + + for (int i = 0; i < InputManager::kMaxController; ++i) + { + const bool has_value = m_game_profile.m_controllerProfile[i].has_value(); + if (has_value) + { + const auto& v = m_game_profile.m_controllerProfile[i].value(); + m_controller_profile[i]->SetStringSelection(wxString::FromUTF8(v)); + } + + else + m_controller_profile[i]->SetSelection(wxNOT_FOUND); + } +} + +void GameProfileWindow::SaveProfile() +{ + // update game profile struct + m_game_profile.Reset(); + // general + m_game_profile.m_loadSharedLibraries = m_load_libs->GetValue(); + m_game_profile.m_startWithPadView = m_start_with_padview->GetValue(); + + // cpu + switch(m_cpu_mode->GetSelection()) + { + case 0: m_game_profile.m_cpuMode = CPUMode::SinglecoreInterpreter; break; + case 1: m_game_profile.m_cpuMode = CPUMode::SinglecoreRecompiler; break; + case 2: m_game_profile.m_cpuMode = CPUMode::MulticoreRecompiler; break; + default: + m_game_profile.m_cpuMode = CPUMode::Auto; + } + + + const wxString thread_quantum = m_thread_quantum->GetStringSelection(); + if (!thread_quantum.empty()) + { + m_game_profile.m_threadQuantum = ConvertString(thread_quantum.ToStdString()); + m_game_profile.m_threadQuantum = std::min(m_game_profile.m_threadQuantum, 536870912); + m_game_profile.m_threadQuantum = std::max(m_game_profile.m_threadQuantum, 5000); + } + + // gpu + m_game_profile.m_accurateShaderMul = (AccurateShaderMulOption)m_shader_mul_accuracy->GetSelection(); + if (m_graphic_api->GetSelection() == 0) + m_game_profile.m_graphics_api = {}; + else + m_game_profile.m_graphics_api = (GraphicAPI)(m_graphic_api->GetSelection() - 1); // "", OpenGL, Vulkan + + // controller + for (int i = 0; i < 8; ++i) + { + if(m_controller_profile[i]->GetSelection() == wxNOT_FOUND) + { + m_game_profile.m_controllerProfile[i].reset(); + continue; + } + + const wxString profile_name = m_controller_profile[i]->GetStringSelection(); + if (profile_name.empty()) + m_game_profile.m_controllerProfile[i].reset(); + else + m_game_profile.m_controllerProfile[i] = profile_name.ToUTF8(); + } + + // update game profile file + m_game_profile.Save(m_title_id); +} + +void GameProfileWindow::SetSliderValue(wxSlider* slider, sint32 new_value) const +{ + wxASSERT(slider); + slider->SetValue(new_value); + + wxCommandEvent slider_event(wxEVT_SLIDER, slider->GetId()); + slider_event.SetEventObject(slider); + slider_event.SetClientData((void*)IsFrozen()); + wxPostEvent(slider->GetEventHandler(), slider_event); +} \ No newline at end of file diff --git a/src/gui/GameProfileWindow.h b/src/gui/GameProfileWindow.h new file mode 100644 index 00000000..6ca36de6 --- /dev/null +++ b/src/gui/GameProfileWindow.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "Cafe/GameProfile/GameProfile.h" + +class GameProfileWindow : public wxFrame +{ +public: + GameProfileWindow(wxWindow* parent, uint64_t title_id); + ~GameProfileWindow(); + +private: + uint64_t m_title_id; + GameProfile m_game_profile; + + void OnStreamoutSizeChange(wxCommandEvent& event); + void OnControllerProfileDropdown(wxCommandEvent& event); + + void SetSliderValue(wxSlider* slider, sint32 new_value) const; + void SetProfileInt(gameProfileIntegerOption_t& option, wxCheckBox* checkbox, sint32 value) const; + + void ApplyProfile(); + void SaveProfile(); + + // general + wxCheckBox* m_load_libs, *m_start_with_padview; + + // cpu + wxChoice *m_cpu_mode; + wxChoice* m_thread_quantum; + + // gpu + //wxCheckBox* m_extended_texture_readback; + //wxChoice* m_precompiled; + wxChoice* m_graphic_api; + + wxChoice* m_shader_mul_accuracy; + //wxChoice* m_cache_accuracy; + + // audio + //wxCheckBox* m_disable_audio; + + // controller + wxComboBox* m_controller_profile[8]; +}; \ No newline at end of file diff --git a/src/gui/GameUpdateWindow.cpp b/src/gui/GameUpdateWindow.cpp new file mode 100644 index 00000000..f8d9bf61 --- /dev/null +++ b/src/gui/GameUpdateWindow.cpp @@ -0,0 +1,350 @@ +#include "gui/wxgui.h" +#include "gui/GameUpdateWindow.h" +#include "util/helpers/helpers.h" + +#include +#include +#include "util/helpers/SystemException.h" +#include "gui/CemuApp.h" +#include "Cafe/TitleList/GameInfo.h" +#include "gui/helpers/wxHelpers.h" +#include "wxHelper.h" + +std::string _GetTitleIdTypeStr(TitleId titleId) +{ + TitleIdParser tip(titleId); + switch (tip.GetType()) + { + case TitleIdParser::TITLE_TYPE::AOC: + return _("DLC").ToStdString(); + case TitleIdParser::TITLE_TYPE::BASE_TITLE: + return _("Base game").ToStdString(); + case TitleIdParser::TITLE_TYPE::BASE_TITLE_DEMO: + return _("Demo").ToStdString(); + case TitleIdParser::TITLE_TYPE::SYSTEM_TITLE: + case TitleIdParser::TITLE_TYPE::SYSTEM_OVERLAY_TITLE: + return _("System title").ToStdString(); + case TitleIdParser::TITLE_TYPE::SYSTEM_DATA: + return _("System data title").ToStdString(); + case TitleIdParser::TITLE_TYPE::BASE_TITLE_UPDATE: + return _("Update").ToStdString(); + default: + break; + } + return "Unknown"; +} + +bool IsRunningInWine(); + +bool GameUpdateWindow::ParseUpdate(const fs::path& metaPath) +{ + m_title_info = TitleInfo(metaPath); + if (!m_title_info.IsValid()) + return false; + fs::path target_location = ActiveSettings::GetMlcPath(m_title_info.GetInstallPath()); + std::error_code ec; + if (fs::exists(target_location, ec)) + { + try + { + const TitleInfo tmp(target_location); + if (!tmp.IsValid()) + { + // does not exist / is not valid. We allow to overwrite it + } + else + { + TitleIdParser tip(m_title_info.GetAppTitleId()); + TitleIdParser tipOther(tmp.GetAppTitleId()); + + if (tip.GetType() != tipOther.GetType()) + { + std::string typeStrToInstall = _GetTitleIdTypeStr(m_title_info.GetAppTitleId()); + std::string typeStrCurrentlyInstalled = _GetTitleIdTypeStr(tmp.GetAppTitleId()); + + std::string wxMsg = wxHelper::MakeUTF8(_("It seems that there is already a title installed at the target location but it has a different type.\nCurrently installed: \'{}\' Installing: \'{}\'\n\nThis can happen for titles which were installed with very old Cemu versions.\nDo you still want to continue with the installation? It will replace the currently installed title.")); + wxMessageDialog dialog(this, fmt::format(wxMsg, typeStrCurrentlyInstalled, typeStrToInstall), _("Warning"), wxCENTRE | wxYES_NO | wxICON_EXCLAMATION); + if (dialog.ShowModal() != wxID_YES) + return false; + } + else if (tmp.GetAppTitleVersion() == m_title_info.GetAppTitleVersion()) + { + wxMessageDialog dialog(this, _("It seems that the selected title is already installed, do you want to reinstall it?"), _("Warning"), wxCENTRE | wxYES_NO); + if (dialog.ShowModal() != wxID_YES) + return false; + } + else if (tmp.GetAppTitleVersion() > m_title_info.GetAppTitleVersion()) + { + wxMessageDialog dialog(this, _("It seems that a newer version is already installed, do you still want to install the older version?"), _("Warning"), wxCENTRE | wxYES_NO); + if (dialog.ShowModal() != wxID_YES) + return false; + } + } + + // temp rename until done + m_backup_folder = target_location; + m_backup_folder.replace_extension(".backup"); + + std::error_code ec; + while (fs::exists(m_backup_folder, ec) || ec) + { + fs::remove_all(m_backup_folder, ec); + + if (ec) + { + const auto error_msg = wxStringFormat2(_("Error when trying to move former title installation:\n{}"), GetSystemErrorMessage(ec)); + wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE, this); + return false; + } + + // wait so filesystem doesnt + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + fs::rename(target_location, m_backup_folder); + } + catch (const std::exception& ex) + { + forceLog_printf("GameUpdateWindow::ParseUpdate exist-error: %s at %s", ex.what(), target_location.generic_u8string().c_str()); + } + } + + m_target_path = target_location; + + fs::path source(metaPath); + + m_source_paths = + { + fs::path(source).append("content"), + fs::path(source).append("code"), + fs::path(source).append("meta") + }; + + m_required_size = 0; + for (auto& path : m_source_paths) + { + for (const fs::directory_entry& f : fs::recursive_directory_iterator(path)) + { + if (is_regular_file(f.path())) + m_required_size += file_size(f.path()); + } + } + + // checking size is buggy on Wine (on Steam Deck this would return values too small to install bigger updates) - we therefore skip this step + if(!IsRunningInWine()) + { + const fs::space_info targetSpace = fs::space(target_location.root_path()); + if (targetSpace.free <= m_required_size) + { + auto string = wxStringFormat(_("Not enough space available.\nRequired: {0} MB\nAvailable: {1} MB"), L"%lld %lld", (m_required_size / 1024 / 1024), (targetSpace.free / 1024 / 1024)); + throw std::runtime_error(string); + } + } + + return true; +} + +GameUpdateWindow::GameUpdateWindow(wxWindow& parent, const fs::path& filePath) + : wxDialog(&parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxCAPTION | wxMINIMIZE_BOX | wxSYSTEM_MENU | wxTAB_TRAVERSAL | wxCLOSE_BOX), + m_thread_state(ThreadRunning) +{ + try + { + #if BOOST_OS_WINDOWS + SetLastError(0); + #endif + if(!ParseUpdate(filePath)) + throw AbortException(); + } + catch (const std::runtime_error& ex) + { + throw SystemException(ex); + } + + auto sizer = new wxBoxSizer(wxVERTICAL); + + TitleIdParser tip(GetTitleId()); + + if (tip.GetType() == TitleIdParser::TITLE_TYPE::AOC) + SetTitle(_("Installing DLC ...")); + else if (tip.GetType() == TitleIdParser::TITLE_TYPE::BASE_TITLE_UPDATE) + SetTitle(_("Installing update ...")); + else if (tip.IsSystemTitle()) + SetTitle(_("Installing system title ...")); + else + SetTitle(_("Installing title ...")); + + m_processBar = new wxGauge(this, wxID_ANY, 100, wxDefaultPosition, wxSize(500, 20), wxGA_HORIZONTAL); + m_processBar->SetValue(0); + m_processBar->SetRange((sint32)(m_required_size / 1000)); + sizer->Add(m_processBar, 0, wxALL | wxEXPAND, 5); + + wxButton* m_cancelButton = new wxButton(this, wxID_ANY, _("Cancel"), wxDefaultPosition, wxDefaultSize, 0); + m_cancelButton->Bind(wxEVT_BUTTON, &GameUpdateWindow::OnCancelButton, this); + sizer->Add(m_cancelButton, 0, wxALIGN_RIGHT | wxALL, 5); + + this->SetSizer(sizer); + this->Centre(wxBOTH); + + wxWindowBase::Layout(); + wxWindowBase::Fit(); + + m_timer = new wxTimer(this); + this->Bind(wxEVT_TIMER, &GameUpdateWindow::OnUpdate, this); + this->Bind(wxEVT_CLOSE_WINDOW, &GameUpdateWindow::OnClose, this); + m_timer->Start(250); + + m_thread_state = ThreadRunning; + m_thread = std::thread(&GameUpdateWindow::ThreadWork, this); +} + +void GameUpdateWindow::ThreadWork() +{ + fs::directory_entry currentDirEntry; + try + { + // create base directories + for (auto& path : m_source_paths) + { + if (!path.has_stem()) + continue; + + fs::path targetDir = fs::path(m_target_path) / path.stem(); + create_directories(targetDir); + } + + const auto target_path = fs::path(m_target_path); + for (auto& path : m_source_paths) + { + if (m_thread_state == ThreadCanceled) + break; + + if (!path.has_parent_path()) + continue; + + const auto len = path.parent_path().string().size() + 1; + for (const fs::directory_entry& f : fs::recursive_directory_iterator {path}) + { + if (m_thread_state == ThreadCanceled) + break; + + currentDirEntry = f; + fs::path relative(f.path().string().substr(len)); + fs::path target = fs::path(m_target_path) / relative; + if (is_directory(f)) + { + create_directories(target); + continue; + } + + copy(f, target, fs::copy_options::overwrite_existing); + if (is_regular_file(f.path())) + { + m_processed_size += file_size(f.path()); + } + } + } + } + catch (const std::exception& ex) + { + std::stringstream error_msg; + error_msg << GetSystemErrorMessage(ex); + + if(currentDirEntry != fs::directory_entry{}) + error_msg << fmt::format("\n{}\n{}",_("Current file:").ToStdString(), _utf8Wrapper(currentDirEntry.path())); + + m_thread_exception = error_msg.str(); + m_thread_state = ThreadCanceled; + } + + if (m_thread_state == ThreadCanceled) + { + if(fs::exists(m_target_path)) + fs::remove_all(m_target_path); + } + else + m_thread_state = ThreadFinished; +} + +GameUpdateWindow::~GameUpdateWindow() +{ + m_timer->Stop(); + if (m_thread.joinable()) + m_thread.join(); +} + +int GameUpdateWindow::ShowModal() +{ + wxDialog::ShowModal(); + return m_thread_state == ThreadCanceled ? wxID_CANCEL : wxID_OK; +} + +void GameUpdateWindow::OnClose(wxCloseEvent& event) +{ + if (m_thread_state == ThreadRunning) + { + wxMessageDialog dialog(this, _("Do you really want to cancel the installation process?\n\nCanceling the process will delete the applied files."), _("Info"), wxCENTRE | wxYES_NO); + if (dialog.ShowModal() != wxID_YES) + return; + + m_thread_state = ThreadCanceled; + } + + m_timer->Stop(); + if (m_thread.joinable()) + m_thread.join(); + + if(!m_backup_folder.empty()) + { + if(m_thread_state == ThreadCanceled) + { + // restore backup + try + { + if(fs::exists(m_target_path)) + fs::remove_all(m_target_path); + + fs::rename(m_backup_folder, m_target_path); + } + catch (const std::exception& ex) + { + forceLogDebug_printf("can't restore update backup: %s",ex.what()); + } + } + else + { + // delete backup + try + { + if(fs::exists(m_backup_folder)) + fs::remove_all(m_backup_folder); + } + catch (const std::exception& ex) + { + forceLogDebug_printf("can't delete update backup: %s",ex.what()); + } + } + + m_backup_folder.clear(); + } + + event.Skip(); +} + +void GameUpdateWindow::OnUpdate(const wxTimerEvent& event) +{ + if (m_thread_state != ThreadRunning) + { + Close(); + return; + } + + const auto processedSize = (sint32)(m_processed_size / 1000); + if (m_processBar->GetValue() != processedSize) + m_processBar->SetValue(processedSize); +} + +void GameUpdateWindow::OnCancelButton(const wxCommandEvent& event) +{ + Close(); +} diff --git a/src/gui/GameUpdateWindow.h b/src/gui/GameUpdateWindow.h new file mode 100644 index 00000000..60ec5d63 --- /dev/null +++ b/src/gui/GameUpdateWindow.h @@ -0,0 +1,67 @@ +#pragma once + +#include "Cafe/TitleList/GameInfo.h" + +#include +#include +#include + +#include +#include +#include +#include + +// thrown if users doesn't wish to reinstall update/dlc +class AbortException : public std::exception {}; + +class GameUpdateWindow : public wxDialog +{ +public: + + GameUpdateWindow(wxWindow& parent, const fs::path& metaPath); + ~GameUpdateWindow(); + + uint64 GetTitleId() const { return m_title_info.GetAppTitleId(); } + bool HasException() const { return !m_thread_exception.empty(); } + //bool IsDLC() const { return m_game_info->IsDLC(); } + //bool IsUpdate() const { return m_game_info->IsUpdate(); } + const std::string& GetExceptionMessage() const { return m_thread_exception; } + const std::string GetGameName() const { return m_title_info.GetTitleName(); } + uint32 GetTargetVersion() const { return m_title_info.GetAppTitleVersion(); } + fs::path GetTargetPath() const { return fs::path(m_target_path); } + + int ShowModal() override; + void OnClose(wxCloseEvent& event); + + void OnUpdate(const wxTimerEvent& event); + void OnCancelButton(const wxCommandEvent& event); + + //uint64 GetUpdateTitleId() const { return m_title_info->GetUpdateTitleId(); } + //uint64 GetDLCTitleId() const { return m_game_info->GetDLCTitleId(); } + +private: + //std::unique_ptr m_game_info; + TitleInfo m_title_info; + enum ThreadState_t + { + ThreadRunning, + ThreadCanceled, + ThreadFinished, + }; + + uint64_t m_required_size; + fs::path m_target_path; + std::array m_source_paths; + bool ParseUpdate(const fs::path& metaPath); + + std::atomic m_processed_size = 0; + std::atomic m_thread_state; + std::string m_thread_exception; + std::thread m_thread; + void ThreadWork(); + + fs::path m_backup_folder; // for prev update data + + wxGauge* m_processBar; + wxTimer* m_timer; +}; diff --git a/src/gui/GeneralSettings2.cpp b/src/gui/GeneralSettings2.cpp new file mode 100644 index 00000000..367fc902 --- /dev/null +++ b/src/gui/GeneralSettings2.cpp @@ -0,0 +1,1802 @@ +#include "gui/wxgui.h" +#include "gui/GeneralSettings2.h" +#include "gui/CemuApp.h" +#include "gui/helpers/wxControlObject.h" + +#include "util/helpers/helpers.h" + +#include "Cafe/OS/libs/snd_core/ax.h" + +#include +#include +#include +#include +#include + +#include "config/CemuConfig.h" + +#include "audio/IAudioAPI.h" +#if BOOST_OS_WINDOWS > 0 +#include "audio/DirectSoundAPI.h" +#include "audio/XAudio27API.h" +#endif +#include "audio/CubebAPI.h" + +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" +#include "Cafe/Account/Account.h" + +#include +#include "util/helpers/SystemException.h" +#include "gui/dialogs/CreateAccount/wxCreateAccountDialog.h" +#include "config/PermanentStorage.h" + +#if BOOST_OS_WINDOWS > 0 +#include +#endif + +#include "config/LaunchSettings.h" +#include "config/ActiveSettings.h" +#include "gui/helpers/wxHelpers.h" + +#if BOOST_OS_LINUX > 0 +#include "resource/linux/resources.h" +#endif + +#include "Cafe/CafeSystem.h" +#include "Cemu/ncrypto/ncrypto.h" +#include "Cafe/TitleList/TitleList.h" +#include "wxHelper.h" + +const wxString kDirectSound(wxT("DirectSound")); +const wxString kXAudio27(wxT("XAudio2.7")); +const wxString kXAudio2(wxT("XAudio2")); +const wxString kCubeb(wxT("Cubeb")); + +const wxString kPropertyPersistentId(wxT("PersistentId")); +const wxString kPropertyMiiName(wxT("MiiName")); +const wxString kPropertyBirthday(wxT("Birthday")); +const wxString kPropertyGender(wxT("Gender")); +const wxString kPropertyEmail(wxT("Email")); +const wxString kPropertyCountry(wxT("Country")); + +wxDEFINE_EVENT(wxEVT_ACCOUNTLIST_REFRESH, wxCommandEvent); + +class wxDeviceDescription : public wxClientData +{ +public: + wxDeviceDescription(const IAudioAPI::DeviceDescriptionPtr& description) : m_description(description) {} + const IAudioAPI::DeviceDescriptionPtr& GetDescription() const { return m_description; } +private: + IAudioAPI::DeviceDescriptionPtr m_description; +}; + +class wxVulkanUUID : public wxClientData +{ +public: + wxVulkanUUID(const VulkanRenderer::DeviceInfo& info) + : m_device_info(info) {} + const VulkanRenderer::DeviceInfo& GetDeviceInfo() const { return m_device_info; } + +private: + VulkanRenderer::DeviceInfo m_device_info; +}; + +class wxAccountData : public wxClientData +{ +public: + wxAccountData(const Account& account) + : m_account(account) {} + + Account& GetAccount() { return m_account; } + const Account& GetAccount() const { return m_account; } + +private: + Account m_account; +}; + +wxPanel* GeneralSettings2::AddGeneralPage(wxNotebook* notebook) +{ + auto* panel = new wxPanel(notebook); + auto* general_panel_sizer = new wxBoxSizer(wxVERTICAL); + + { + auto* box = new wxStaticBox(panel, wxID_ANY, _("Interface")); + auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); + + { + auto* first_row = new wxFlexGridSizer(0, 2, 0, 0); + first_row->SetFlexibleDirection(wxBOTH); + first_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); + + first_row->Add(new wxStaticText(box, wxID_ANY, _("Language"), wxDefaultPosition, wxDefaultSize, 0), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + + wxString language_choices[] = { _("Default"), _("English") }; + m_language = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(language_choices), language_choices); + m_language->SetSelection(0); + m_language->SetToolTip(_("Changes the interface language of Cemu\nAvailable languages are stored in the translation directory\nA restart will be required after changing the language")); + for (const auto& language : wxGetApp().GetLanguages()) + { + m_language->Append(language->Description); + } + + first_row->Add(m_language, 0, wxALL | wxEXPAND, 5); + + box_sizer->Add(first_row, 1, wxEXPAND, 5); + } + + { + auto* second_row = new wxFlexGridSizer(0, 3, 0, 0); + second_row->SetFlexibleDirection(wxBOTH); + second_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); + + const int topflag = wxALIGN_CENTER_VERTICAL | wxALL; + m_save_window_position_size = new wxCheckBox(box, wxID_ANY, _("Remember main window position")); + m_save_window_position_size->SetToolTip(_("Restores the last known window position and size when starting Cemu")); + second_row->Add(m_save_window_position_size, 0, topflag, 5); + second_row->AddSpacer(10); + m_save_padwindow_position_size = new wxCheckBox(box, wxID_ANY, _("Remember pad window position")); + m_save_padwindow_position_size->SetToolTip(_("Restores the last known pad window position and size when opening it")); + second_row->Add(m_save_padwindow_position_size, 0, topflag, 5); + + const int botflag = wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT | wxBOTTOM; + m_discord_presence = new wxCheckBox(box, wxID_ANY, _("Discord Presence")); + m_discord_presence->SetToolTip(_("Enables the Discord Rich Presence feature\nYou will also need to enable it in the Discord settings itself!")); + second_row->Add(m_discord_presence, 0, botflag, 5); + second_row->AddSpacer(10); + m_fullscreen_menubar = new wxCheckBox(box, wxID_ANY, _("Fullscreen menu bar")); + m_fullscreen_menubar->SetToolTip(_("Displays the menu bar when Cemu is running in fullscreen mode and the mouse cursor is moved to the top")); + second_row->Add(m_fullscreen_menubar, 0, botflag, 5); + + m_auto_update = new wxCheckBox(box, wxID_ANY, _("Automatically check for updates")); + m_auto_update->SetToolTip(_("Automatically checks for new cemu versions on startup")); + second_row->Add(m_auto_update, 0, botflag, 5); + second_row->AddSpacer(10); + m_save_screenshot = new wxCheckBox(box, wxID_ANY, _("Save screenshot")); + m_save_screenshot->SetToolTip(_("Pressing the screenshot key (F12) will save a screenshot directly to the screenshots folder")); + second_row->Add(m_save_screenshot, 0, botflag, 5); + + m_permanent_storage = new wxCheckBox(box, wxID_ANY, _("Use permanent storage")); + m_permanent_storage->SetToolTip(_("Cemu will remember your custom mlc path in %LOCALAPPDATA%/Cemu for new installations.")); + second_row->Add(m_permanent_storage, 0, botflag, 5); + + box_sizer->Add(second_row, 0, wxEXPAND, 5); + } + + general_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); + } + + { + auto* box = new wxStaticBox(panel, wxID_ANY, _("MLC Path")); + auto* box_sizer = new wxStaticBoxSizer(box, wxHORIZONTAL); + + m_mlc_path = new wxTextCtrl(box, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_READONLY); + m_mlc_path->SetMinSize(wxSize(150, -1)); + m_mlc_path->Bind(wxEVT_CHAR, &GeneralSettings2::OnMLCPathChar, this); + m_mlc_path->SetToolTip(_("The mlc directory contains your save games and installed game update/dlc data")); + + box_sizer->Add(m_mlc_path, 1, wxALL | wxEXPAND, 5); + + auto* change_path = new wxButton(box, wxID_ANY, wxT("...")); + change_path->Bind(wxEVT_BUTTON, &GeneralSettings2::OnMLCPathSelect, this); + change_path->SetToolTip(_("Select a custom mlc path\nThe mlc path is used to store Wii U related files like save games, game updates and dlc data")); + box_sizer->Add(change_path, 0, wxALL, 5); + if (LaunchSettings::GetMLCPath().has_value()) + change_path->Disable(); + general_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); + } + + { + auto* general_gamepath_box = new wxStaticBox(panel, wxID_ANY, _("Game Paths")); + auto* general_gamepath_sizer = new wxStaticBoxSizer(general_gamepath_box, wxVERTICAL); + + m_game_paths = new wxListBox(general_gamepath_box, wxID_ANY); + m_game_paths->SetMinSize(wxSize(150, 100)); + m_game_paths->SetToolTip(_("Add the root directory of your game(s). It will scan all directories in it for games")); + general_gamepath_sizer->Add(m_game_paths, 1, wxALL | wxEXPAND, 5); + + auto* general_gamepath_buttons = new wxFlexGridSizer(0, 2, 0, 0); + general_gamepath_buttons->SetFlexibleDirection(wxBOTH); + general_gamepath_buttons->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); + + auto* general_gamepath_add_button = new wxButton(general_gamepath_box, wxID_ANY, _("Add")); + general_gamepath_add_button->Bind(wxEVT_BUTTON, &GeneralSettings2::OnAddPathClicked, this); + general_gamepath_add_button->SetToolTip(_("Adds a game path to scan for games displayed in the game list\nIf you have unpacked games, make sure to select the root folder of a game")); + general_gamepath_buttons->Add(general_gamepath_add_button, 0, wxALL, 5); + + auto* general_gamepath_remove_button = new wxButton(general_gamepath_box, wxID_ANY, _("Remove")); + general_gamepath_remove_button->Bind(wxEVT_BUTTON, &GeneralSettings2::OnRemovePathClicked, this); + general_gamepath_remove_button->SetToolTip(_("Removes the currently selected game path from the game list")); + general_gamepath_buttons->Add(general_gamepath_remove_button, 0, wxALL, 5); + + general_gamepath_sizer->Add(general_gamepath_buttons, 0, wxEXPAND, 5); + general_panel_sizer->Add(general_gamepath_sizer, 1, wxEXPAND | wxALL, 5); + } + + panel->SetSizerAndFit(general_panel_sizer); + + return panel; +} + +wxPanel* GeneralSettings2::AddGraphicsPage(wxNotebook* notebook) +{ + // Graphics page + auto graphics_panel = new wxPanel(notebook); + auto graphics_panel_sizer = new wxBoxSizer(wxVERTICAL); + + { + auto box = new wxStaticBox(graphics_panel, wxID_ANY, _("General")); + auto box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); + + auto row = new wxFlexGridSizer(0, 2, 0, 0); + row->SetFlexibleDirection(wxBOTH); + row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); + + row->Add(new wxStaticText(box, wxID_ANY, _("Graphics API")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + + sint32 api_size = 1; + wxString choices[2] = { "OpenGL" }; + if (g_vulkan_available) + { + choices[1] = "Vulkan"; + api_size = 2; + } + + m_graphic_api = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, api_size, choices); + m_graphic_api->SetSelection(0); + if (api_size > 1) + m_graphic_api->SetToolTip(_("Select one of the available graphic back ends")); + row->Add(m_graphic_api, 0, wxALL, 5); + m_graphic_api->Bind(wxEVT_CHOICE, &GeneralSettings2::OnGraphicAPISelected, this); + + row->Add(new wxStaticText(box, wxID_ANY, _("Graphics Device")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + m_graphic_device = new wxChoice(box, wxID_ANY, wxDefaultPosition, { 230, -1 }, api_size, choices); + m_graphic_device->SetSelection(0); + m_graphic_device->SetToolTip(_("Select the used graphic device")); + row->Add(m_graphic_device, 0, wxALL, 5); + + row->Add(new wxStaticText(box, wxID_ANY, _("VSync")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + m_vsync = new wxChoice(box, wxID_ANY, wxDefaultPosition, { 230, -1 }); + m_vsync->SetToolTip(_("Controls the vsync state")); + row->Add(m_vsync, 0, wxALL, 5); + + box_sizer->Add(row, 0, wxEXPAND, 5); + + auto* graphic_misc_row = new wxFlexGridSizer(0, 2, 0, 0); + + m_async_compile = new wxCheckBox(box, wxID_ANY, _("Async shader compile")); + m_async_compile->SetToolTip(_("Enables async shader and pipeline compilation. Reduces stutter at the cost of objects not rendering for a short time.\nVulkan only")); + graphic_misc_row->Add(m_async_compile, 0, wxALL, 5); + + m_gx2drawdone_sync = new wxCheckBox(box, wxID_ANY, _("Full sync at GX2DrawDone()")); + m_gx2drawdone_sync->SetToolTip(_("If synchronization is requested by the game, the emulated CPU will wait for the GPU to finish all operations.\nThis is more accurate behavior, but may cause lower performance")); + graphic_misc_row->Add(m_gx2drawdone_sync, 0, wxALL, 5); + + box_sizer->Add(graphic_misc_row, 1, wxEXPAND, 5); + graphics_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); + } + + { + wxString choices[] = { _("Bilinear"), _("Bicubic"), _("Hermite"), _("Nearest Neighbor") }; + m_upscale_filter = new wxRadioBox(graphics_panel, wxID_ANY, _("Upscale filter"), wxDefaultPosition, wxDefaultSize, std::size(choices), choices, 5, wxRA_SPECIFY_COLS); + m_upscale_filter->SetToolTip(_("Upscaling filters are used when the game resolution is smaller than the window size")); + graphics_panel_sizer->Add(m_upscale_filter, 0, wxALL | wxEXPAND, 5); + + m_downscale_filter = new wxRadioBox(graphics_panel, wxID_ANY, _("Downscale filter"), wxDefaultPosition, wxDefaultSize, std::size(choices), choices, 5, wxRA_SPECIFY_COLS); + m_downscale_filter->SetToolTip(_("Downscaling filters are used when the game resolution is bigger than the window size")); + graphics_panel_sizer->Add(m_downscale_filter, 0, wxALL | wxEXPAND, 5); + } + + { + wxString choices[] = { _("Keep aspect ratio"), _("Stretch") }; + m_fullscreen_scaling = new wxRadioBox(graphics_panel, wxID_ANY, _("Fullscreen scaling"), wxDefaultPosition, wxDefaultSize, std::size(choices), choices, 5, wxRA_SPECIFY_COLS); + m_fullscreen_scaling->SetToolTip(_("Controls the output aspect ratio when it doesn't match the ratio of the game")); + graphics_panel_sizer->Add(m_fullscreen_scaling, 0, wxALL | wxEXPAND, 5); + } + + graphics_panel->SetSizerAndFit(graphics_panel_sizer); + return graphics_panel; +} + +wxPanel* GeneralSettings2::AddAudioPage(wxNotebook* notebook) +{ + auto audio_panel = new wxPanel(notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + auto audio_panel_sizer = new wxBoxSizer(wxVERTICAL); + + { + auto box = new wxStaticBox(audio_panel, wxID_ANY, _("General")); + auto box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); + + auto audio_general_row = new wxFlexGridSizer(0, 3, 0, 0); + audio_general_row->SetFlexibleDirection(wxBOTH); + audio_general_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); + + audio_general_row->Add(new wxStaticText(box, wxID_ANY, _("API")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + + m_audio_api = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, nullptr); + if (IAudioAPI::IsAudioAPIAvailable(IAudioAPI::DirectSound)) + m_audio_api->Append(kDirectSound); + if (IAudioAPI::IsAudioAPIAvailable(IAudioAPI::XAudio27)) + m_audio_api->Append(kXAudio27); + if (IAudioAPI::IsAudioAPIAvailable(IAudioAPI::XAudio2)) + m_audio_api->Append(kXAudio2); + if (IAudioAPI::IsAudioAPIAvailable(IAudioAPI::Cubeb)) + m_audio_api->Append(kCubeb); + + m_audio_api->SetSelection(0); + m_audio_api->SetToolTip(_("Select one of the available audio back ends")); + audio_general_row->Add(m_audio_api, 0, wxALL, 5); + + m_audio_api->Bind(wxEVT_CHOICE, &GeneralSettings2::OnAudioAPISelected, this); + + audio_general_row->AddSpacer(0); + + audio_general_row->Add(new wxStaticText(box, wxID_ANY, _("Latency")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + m_audio_latency = new wxSlider(box, wxID_ANY, 2, 0, IAudioAPI::kBlockCount - 1, wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL); + m_audio_latency->SetToolTip(_("Controls the amount of buffered audio data\nHigher values will create a delay in audio playback, but may avoid audio problems when emulation is too slow")); + audio_general_row->Add(m_audio_latency, 0, wxEXPAND | wxALL, 5); + auto latency_text = new wxStaticText(box, wxID_ANY, wxT("24ms")); + audio_general_row->Add(latency_text, 0, wxALIGN_CENTER_VERTICAL | wxALL | wxALIGN_RIGHT, 5); + m_audio_latency->Bind(wxEVT_SLIDER, &GeneralSettings2::OnLatencySliderChanged, this, wxID_ANY, wxID_ANY, new wxControlObject(latency_text)); + m_audio_latency->Bind(wxEVT_SLIDER, &GeneralSettings2::OnAudioLatencyChanged, this); + + box_sizer->Add(audio_general_row, 1, wxEXPAND, 5); + audio_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); + } + + const wxString audio_channel_choices[] = { _("Mono"), _("Stereo") , _("Surround") }; + { + auto box = new wxStaticBox(audio_panel, wxID_ANY, _("TV")); + auto box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); + + auto audio_tv_row = new wxFlexGridSizer(0, 3, 0, 0); + audio_tv_row->SetFlexibleDirection(wxBOTH); + audio_tv_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); + + audio_tv_row->Add(new wxStaticText(box, wxID_ANY, _("Device")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + m_tv_device = new wxChoice(box, wxID_ANY); + m_tv_device->SetMinSize(wxSize(300, -1)); + m_tv_device->SetToolTip(_("Select the active audio output device for Wii U TV")); + audio_tv_row->Add(m_tv_device, 0, wxEXPAND | wxALL, 5); + audio_tv_row->AddSpacer(0); + + m_tv_device->Bind(wxEVT_CHOICE, &GeneralSettings2::OnAudioDeviceSelected, this); + + audio_tv_row->Add(new wxStaticText(box, wxID_ANY, _("Channels")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + + m_tv_channels = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(audio_channel_choices), audio_channel_choices); + + m_tv_channels->SetSelection(1); // set default to stereo + m_tv_channels->Bind(wxEVT_CHOICE, &GeneralSettings2::OnAudioChannelsSelected, this); + audio_tv_row->Add(m_tv_channels, 0, wxEXPAND | wxALL, 5); + audio_tv_row->AddSpacer(0); + + audio_tv_row->Add(new wxStaticText(box, wxID_ANY, _("Volume")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + m_tv_volume = new wxSlider(box, wxID_ANY, 100, 0, 100, wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL); + audio_tv_row->Add(m_tv_volume, 0, wxEXPAND | wxALL, 5); + auto audio_tv_volume_text = new wxStaticText(box, wxID_ANY, wxT("100%")); + audio_tv_row->Add(audio_tv_volume_text, 0, wxALIGN_CENTER_VERTICAL | wxALL | wxALIGN_RIGHT, 5); + + m_tv_volume->Bind(wxEVT_SLIDER, &GeneralSettings2::OnSliderChangedPercent, this, wxID_ANY, wxID_ANY, new wxControlObject(audio_tv_volume_text)); + m_tv_volume->Bind(wxEVT_SLIDER, &GeneralSettings2::OnVolumeChanged, this); + + box_sizer->Add(audio_tv_row, 1, wxEXPAND, 5); + audio_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); + } + + { + auto box = new wxStaticBox(audio_panel, wxID_ANY, _("Gamepad")); + auto box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); + + auto audio_pad_row = new wxFlexGridSizer(0, 3, 0, 0); + audio_pad_row->SetFlexibleDirection(wxBOTH); + audio_pad_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); + + audio_pad_row->Add(new wxStaticText(box, wxID_ANY, _("Device")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + m_pad_device = new wxChoice(box, wxID_ANY, wxDefaultPosition); + m_pad_device->SetMinSize(wxSize(300, -1)); + m_pad_device->SetToolTip(_("Select the active audio output device for Wii U GamePad")); + audio_pad_row->Add(m_pad_device, 0, wxEXPAND | wxALL, 5); + audio_pad_row->AddSpacer(0); + + m_pad_device->Bind(wxEVT_CHOICE, &GeneralSettings2::OnAudioDeviceSelected, this); + + const wxString audio_channel_drc_choices[] = { _("Stereo") }; // stereo for now only + + audio_pad_row->Add(new wxStaticText(box, wxID_ANY, _("Channels")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + m_pad_channels = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(audio_channel_drc_choices), audio_channel_drc_choices); + + m_pad_channels->SetSelection(0); // set default to stereo + + m_pad_channels->Bind(wxEVT_CHOICE, &GeneralSettings2::OnAudioChannelsSelected, this); + audio_pad_row->Add(m_pad_channels, 0, wxEXPAND | wxALL, 5); + audio_pad_row->AddSpacer(0); + + audio_pad_row->Add(new wxStaticText(box, wxID_ANY, _("Volume")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + m_pad_volume = new wxSlider(box, wxID_ANY, 100, 0, 100); + audio_pad_row->Add(m_pad_volume, 0, wxEXPAND | wxALL, 5); + auto audio_pad_volume_text = new wxStaticText(box, wxID_ANY, wxT("100%")); + audio_pad_row->Add(audio_pad_volume_text, 0, wxALIGN_CENTER_VERTICAL | wxALL | wxALIGN_RIGHT, 5); + + m_pad_volume->Bind(wxEVT_SLIDER, &GeneralSettings2::OnSliderChangedPercent, this, wxID_ANY, wxID_ANY, new wxControlObject(audio_pad_volume_text)); + m_pad_volume->Bind(wxEVT_SLIDER, &GeneralSettings2::OnVolumeChanged, this); + + box_sizer->Add(audio_pad_row, 1, wxEXPAND, 5); + audio_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); + } + + audio_panel->SetSizerAndFit(audio_panel_sizer); + return audio_panel; +} + +wxPanel* GeneralSettings2::AddOverlayPage(wxNotebook* notebook) +{ + auto* panel = new wxPanel(notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + auto* panel_sizer = new wxBoxSizer(wxVERTICAL); + + const wxString positions[]{ _("Disabled"), _("Top left"), _("Top center"), _("Top right"), _("Bottom left"), _("Bottom center"), _("Bottom right") }; + const wxString text_scale[]{ "50%", "75%", "100%", "125%", "150%", "175%", "200%", "225%", "250%", "275%", "300%" }; + { + auto box = new wxStaticBox(panel, wxID_ANY, _("Overlay")); + auto box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); + + auto position_row = new wxFlexGridSizer(1, 0, 0, 0); + position_row->SetFlexibleDirection(wxBOTH); + position_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); + { + position_row->Add(new wxStaticText(box, wxID_ANY, _("Position")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + m_overlay_position = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(positions), positions); + m_overlay_position->SetSelection(0); + m_overlay_position->SetToolTip(_("Controls the overlay which displays technical information while playing")); + position_row->Add(m_overlay_position, 0, wxALL, 5); + + position_row->AddSpacer(25); + + position_row->Add(new wxStaticText(box, wxID_ANY, _("Text Color")), 0, wxALL | wxALIGN_CENTRE_VERTICAL, 5); + m_overlay_font_color = new wxColourPickerCtrl(box, wxID_ANY, *wxWHITE, wxDefaultPosition, wxDefaultSize, wxCLRP_SHOW_ALPHA); + m_overlay_font_color->SetToolTip(_("Sets the text color of the overlay")); + position_row->Add(m_overlay_font_color, 0, wxEXPAND | wxALL, 5); + + position_row->AddSpacer(25); + + position_row->Add(new wxStaticText(box, wxID_ANY, _("Scale")), 0, wxALL | wxALIGN_CENTRE_VERTICAL, 5); + m_overlay_scale = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(text_scale), text_scale); + m_overlay_scale->SetToolTip(_("Sets the scale of the overlay text")); + position_row->Add(m_overlay_scale, 0, wxEXPAND | wxALL, 5); + } + box_sizer->Add(position_row, 0, wxEXPAND, 5); + + auto settings2_row = new wxFlexGridSizer(0, 4, 2, 0); + { + m_overlay_fps = new wxCheckBox(box, wxID_ANY, _("FPS")); + m_overlay_fps->SetToolTip(_("The number of frames per second. Average over last 5 seconds")); + settings2_row->Add(m_overlay_fps, 0, wxALL, 5); + + m_overlay_drawcalls = new wxCheckBox(box, wxID_ANY, _("Draw calls per frame")); + m_overlay_drawcalls->SetToolTip(_("The number of draw calls per frame. Average over last 5 seconds")); + settings2_row->Add(m_overlay_drawcalls, 0, wxALL, 5); + + m_overlay_cpu = new wxCheckBox(box, wxID_ANY, _("CPU usage")); + m_overlay_cpu->SetToolTip(_("CPU usage of Cemu in percent")); + settings2_row->Add(m_overlay_cpu, 0, wxALL, 5); + + m_overlay_cpu_per_core = new wxCheckBox(box, wxID_ANY, _("CPU per core usage")); + m_overlay_cpu_per_core->SetToolTip(_("Total cpu usage in percent for each core")); + settings2_row->Add(m_overlay_cpu_per_core, 0, wxALL, 5); + + m_overlay_ram = new wxCheckBox(box, wxID_ANY, _("RAM usage")); + m_overlay_ram->SetToolTip(_("Cemu RAM usage in MB")); + settings2_row->Add(m_overlay_ram, 0, wxALL, 5); + + m_overlay_vram = new wxCheckBox(box, wxID_ANY, _("VRAM usage")); +#if BOOST_OS_WINDOWS + using RtlGetVersion_t = LONG(WINAPI*)(PRTL_OSVERSIONINFOW lpVersionInformation); + const auto pRtlGetVersion = (RtlGetVersion_t)GetProcAddress(GetModuleHandleA("ntdll.dll"), "RtlGetVersion"); + //if(IsWindows8Point1OrGreater()) requires manifest + RTL_OSVERSIONINFOW info{}; + // Windows 8.1 6.3* + if (pRtlGetVersion && pRtlGetVersion(&info) == 0 && ((info.dwMajorVersion == 6 && info.dwMinorVersion >= 3) || info.dwMajorVersion > 6)) + m_overlay_vram->SetToolTip(_("The VRAM usage of Cemu in MB")); + else + { + m_overlay_vram->SetToolTip(_("This option requires Win8.1+")); + m_overlay_vram->Disable(); + } +#else + m_overlay_vram->SetToolTip(_("The VRAM usage of Cemu in MB")); +#endif + + settings2_row->Add(m_overlay_vram, 0, wxALL, 5); + + m_overlay_debug = new wxCheckBox(box, wxID_ANY, _("Debug")); + m_overlay_debug->SetToolTip(_("Displays internal debug information (Vulkan only)")); + settings2_row->Add(m_overlay_debug, 0, wxALL, 5); + } + box_sizer->Add(settings2_row, 0, wxEXPAND, 5); + + panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); + } + + { + auto box = new wxStaticBox(panel, wxID_ANY, _("Notifications")); + auto box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); + + auto position_row = new wxFlexGridSizer(1, 0, 0, 0); + position_row->SetFlexibleDirection(wxBOTH); + position_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); + { + position_row->Add(new wxStaticText(box, wxID_ANY, _("Position")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + + m_notification_position = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(positions), positions); + m_notification_position->SetSelection(0); + m_notification_position->SetToolTip(_("Controls the notification position while playing")); + position_row->Add(m_notification_position, 0, wxALL, 5); + + position_row->AddSpacer(25); + + position_row->Add(new wxStaticText(box, wxID_ANY, _("Text Color")), 0, wxALL | wxALIGN_CENTRE_VERTICAL, 5); + m_notification_font_color = new wxColourPickerCtrl(box, wxID_ANY, *wxWHITE, wxDefaultPosition, wxDefaultSize, wxCLRP_SHOW_ALPHA); + m_notification_font_color->SetToolTip(_("Sets the text color of notifications")); + position_row->Add(m_notification_font_color, 0, wxEXPAND | wxALL, 5); + + position_row->Add(new wxStaticText(box, wxID_ANY, _("Scale")), 0, wxALL | wxALIGN_CENTRE_VERTICAL, 5); + m_notification_scale = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(text_scale), text_scale); + m_notification_scale->SetToolTip(_("Sets the scale of the notification text")); + position_row->Add(m_notification_scale, 0, wxEXPAND | wxALL, 5); + } + box_sizer->Add(position_row, 0, wxEXPAND, 5); + + auto settings1_row = new wxFlexGridSizer(1, 0, 2, 0); + { + m_controller_profile_name = new wxCheckBox(box, wxID_ANY, _("Controller profiles")); + m_controller_profile_name->SetToolTip(_("Displays the active controller profile when starting a game")); + settings1_row->Add(m_controller_profile_name, 0, wxALL, 5); + + m_controller_low_battery = new wxCheckBox(box, wxID_ANY, _("Low battery")); + m_controller_low_battery->SetToolTip(_("Shows a notification when a low controller battery has been detected")); + settings1_row->Add(m_controller_low_battery, 0, wxALL, 5); + + m_shader_compiling = new wxCheckBox(box, wxID_ANY, _("Shader compiler")); + m_shader_compiling->SetToolTip(_("Shows a notification after shaders have been compiled")); + settings1_row->Add(m_shader_compiling, 0, wxALL, 5); + + m_friends_data = new wxCheckBox(box, wxID_ANY, _("Friend list")); + m_friends_data->SetToolTip(_("Shows friend list related data if online")); + settings1_row->Add(m_friends_data, 0, wxALL, 5); + } + box_sizer->Add(settings1_row, 0, wxEXPAND, 5); + + + panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); + } + + panel->SetSizerAndFit(panel_sizer); + + return panel; +} + +wxPanel* GeneralSettings2::AddAccountPage(wxNotebook* notebook) +{ + auto* online_panel = new wxPanel(notebook); + auto* online_panel_sizer = new wxBoxSizer(wxVERTICAL); + + { + auto* box = new wxStaticBox(online_panel, wxID_ANY, _("Account settings")); + auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); + + auto* content = new wxFlexGridSizer(0, 4, 0, 0); + content->SetFlexibleDirection(wxBOTH); + content->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); + content->AddGrowableCol(1, 1); + content->AddGrowableCol(2, 0); + content->AddGrowableCol(3, 0); + + content->Add(new wxStaticText(box, wxID_ANY, _("Active account")), 1, wxALIGN_CENTER_VERTICAL | wxALL, 5); + + m_active_account = new wxChoice(box, wxID_ANY); + m_active_account->SetMinSize({ 250, -1 }); + content->Add(m_active_account, 0, wxEXPAND | wxALL, 5); + m_active_account->Bind(wxEVT_CHOICE, &GeneralSettings2::OnActiveAccountChanged, this); + + m_create_account = new wxButton(box, wxID_ANY, _("Create")); + content->Add(m_create_account, 0, wxEXPAND | wxALL | wxALIGN_RIGHT, 5); + m_create_account->Bind(wxEVT_BUTTON, &GeneralSettings2::OnAccountCreate, this); + + m_delete_account = new wxButton(box, wxID_ANY, _("Delete")); + content->Add(m_delete_account, 0, wxEXPAND | wxALL | wxALIGN_RIGHT, 5); + m_delete_account->Bind(wxEVT_BUTTON, &GeneralSettings2::OnAccountDelete, this); + + box_sizer->Add(content, 1, wxEXPAND, 5); + + online_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); + + if (CafeSystem::IsTitleRunning()) + { + m_active_account->Enable(false); + m_create_account->Enable(false); + m_delete_account->Enable(false); + } + } + + { + auto* box = new wxStaticBox(online_panel, wxID_ANY, _("Online settings")); + auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); + + m_online_enabled = new wxCheckBox(box, wxID_ANY, _("Enable online mode")); + m_online_enabled->Bind(wxEVT_CHECKBOX, &GeneralSettings2::OnOnlineEnable, this); + box_sizer->Add(m_online_enabled, 0, wxEXPAND | wxALL, 5); + + auto* row = new wxFlexGridSizer(0, 2, 0, 0); + row->SetFlexibleDirection(wxBOTH); + row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); + + const wxImage tmp = wxBITMAP_PNG(PNG_ERROR).ConvertToImage(); + m_validate_online = new wxBitmapButton(box, wxID_ANY, tmp.Scale(16, 16)); + m_validate_online->Bind(wxEVT_BUTTON, &GeneralSettings2::OnShowOnlineValidator, this); + row->Add(m_validate_online, 0, wxEXPAND | wxALL, 5); + + m_online_status = new wxStaticText(box, wxID_ANY, _("No account selected")); + row->Add(m_online_status, 1, wxALL | wxALIGN_CENTRE_VERTICAL, 5); + + box_sizer->Add(row, 1, wxEXPAND, 5); + + auto* tutorial_link = new wxHyperlinkCtrl(box, wxID_ANY, _("Online play tutorial"), "https://cemu.info/online-guide"); + box_sizer->Add(tutorial_link, 0, wxALL, 5); + + + online_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); + } + + { + m_account_information = new wxCollapsiblePane(online_panel, wxID_ANY, _("Account information")); + #if BOOST_OS_WINDOWS > 0 + m_account_information->GetControlWidget()->SetBackgroundColour(*wxWHITE); + #endif + auto win = m_account_information->GetPane(); + + auto content = new wxBoxSizer(wxVERTICAL); + + m_account_grid = new wxPropertyGrid(win, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxPG_HIDE_MARGIN | wxPG_STATIC_SPLITTER); + m_account_grid->SetExtraStyle(wxPG_EX_HELP_AS_TOOLTIPS); + m_account_grid->SetMinSize({ 300, -1 }); + //m_account_grid->Append(new wxPropertyCategory("Main")); + + auto* persistent_id_gprop = m_account_grid->Append(new wxStringProperty(wxT("PersistentId"), kPropertyPersistentId)); + persistent_id_gprop->SetHelpString(_("The persistent id is the internal folder name used for your saves")); + m_account_grid->SetPropertyReadOnly(persistent_id_gprop); + + m_account_grid->Append(new wxStringProperty(_("Mii name"), kPropertyMiiName))->SetHelpString(_("The mii name is the profile name")); + m_account_grid->Append(new wxStringProperty(_("Birthday"), kPropertyBirthday)); + + wxPGChoices gender; + gender.Add(_("Female"), 0); + gender.Add(_("Male"), 1); + m_account_grid->Append(new wxEnumProperty("Gender", kPropertyGender, gender)); + + m_account_grid->Append(new wxStringProperty(_("Email"), kPropertyEmail)); + + wxPGChoices countries; + for (int i = 0; i < 195; ++i) + { + const auto country = NCrypto::GetCountryAsString(i); + if (country && (i == 0 || !boost::equals(country, "NN"))) + { + countries.Add(country, i); + } + } + m_account_grid->Append(new wxEnumProperty(_("Country"), kPropertyCountry, countries)); + + m_account_grid->Bind(wxEVT_PG_CHANGED, &GeneralSettings2::OnAccountSettingsChanged, this); + + content->Add(m_account_grid, 1, wxEXPAND | wxALL, 5); + + win->SetSizer(content); + content->SetSizeHints(win); + + online_panel_sizer->Add(m_account_information, 0, wxEXPAND | wxALL, 5); + } + + online_panel->SetSizerAndFit(online_panel_sizer); + return online_panel; +} + +wxPanel* GeneralSettings2::AddDebugPage(wxNotebook* notebook) +{ + auto* panel = new wxPanel(notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + auto* debug_panel_sizer = new wxBoxSizer(wxVERTICAL); + + auto* debug_row = new wxFlexGridSizer(0, 2, 0, 0); + debug_row->SetFlexibleDirection(wxBOTH); + debug_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); + + debug_row->Add(new wxStaticText(panel, wxID_ANY, _("Crash dump"), wxDefaultPosition, wxDefaultSize, 0), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + + wxString dump_choices[] = { _("Disabled"), _("Lite"), _("Full") }; + m_crash_dump = new wxChoice(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(dump_choices), dump_choices); + m_crash_dump->SetSelection(0); + m_crash_dump->SetToolTip(_("Creates a dump when Cemu crashes\nOnly enable when requested by a developer!\nThe Full option will create a very large dump file (includes a full RAM dump of the Cemu process)")); + debug_row->Add(m_crash_dump, 0, wxALL | wxEXPAND, 5); + + debug_panel_sizer->Add(debug_row, 0, wxALL | wxEXPAND, 5); + + panel->SetSizerAndFit(debug_panel_sizer); + + return panel; +} + +GeneralSettings2::GeneralSettings2(wxWindow* parent, bool game_launched) + : wxDialog(parent, wxID_ANY, _("General settings"), wxDefaultPosition, wxDefaultSize, wxCLOSE_BOX | wxCLIP_CHILDREN | wxCAPTION | wxRESIZE_BORDER), m_game_launched(game_launched) +{ + SetIcon(wxICON(X_SETTINGS)); + + auto* sizer = new wxBoxSizer(wxVERTICAL); + auto* notebook = new wxNotebook(this, wxID_ANY); + + notebook->AddPage(AddGeneralPage(notebook), _("General")); + notebook->AddPage(AddGraphicsPage(notebook), _("Graphics")); + notebook->AddPage(AddAudioPage(notebook), _("Audio")); + notebook->AddPage(AddOverlayPage(notebook), _("Overlay")); + notebook->AddPage(AddAccountPage(notebook), _("Account")); + notebook->AddPage(AddDebugPage(notebook), _("Debug")); + + Bind(wxEVT_CLOSE_WINDOW, &GeneralSettings2::OnClose, this); + + // + + sizer->Add(notebook, 1, wxEXPAND | wxALL, 5); + + SetSizerAndFit(sizer); + Layout(); + Centre(wxBOTH); + + // + + UpdateOnlineAccounts(); + UpdateAudioDeviceList(); + + ApplyConfig(); + HandleGraphicsApiSelection(); + + DisableSettings(game_launched); +} + +void GeneralSettings2::StoreConfig() +{ + auto* app = (CemuApp*)wxTheApp; + auto& config = GetConfig(); + + config.use_discord_presence = m_discord_presence->IsChecked(); + config.fullscreen_menubar = m_fullscreen_menubar->IsChecked(); + config.check_update = m_auto_update->IsChecked(); + config.save_screenshot = m_save_screenshot->IsChecked(); + + const bool use_ps = m_permanent_storage->IsChecked(); + if(use_ps) + { + config.permanent_storage = use_ps; + try + { + + PermanentStorage storage; + storage.RemoveStorage(); + } + catch (...) {} + } + else + { + try + { + // delete permanent storage + PermanentStorage storage; + storage.RemoveStorage(); + } + catch (...) {} + config.permanent_storage = use_ps; + } + + if (!LaunchSettings::GetMLCPath().has_value()) + config.SetMLCPath(m_mlc_path->GetValue().ToStdWstring(), false); + + // -1 is default wx widget value -> set to dummy 0 so mainwindow and padwindow will update it + config.window_position = m_save_window_position_size->IsChecked() ? Vector2i{ 0,0 } : Vector2i{-1,-1}; + config.window_size = m_save_window_position_size->IsChecked() ? Vector2i{ 0,0 } : Vector2i{-1,-1}; + config.pad_position = m_save_padwindow_position_size->IsChecked() ? Vector2i{ 0,0 } : Vector2i{-1,-1}; + config.pad_size = m_save_padwindow_position_size->IsChecked() ? Vector2i{ 0,0 } : Vector2i{-1,-1}; + + config.game_paths.clear(); + for (auto& path : m_game_paths->GetStrings()) + config.game_paths.emplace_back(path); + + auto selection = m_language->GetSelection(); + if (selection == 0) + GetConfig().language = wxLANGUAGE_DEFAULT; + else if (selection == 1) + GetConfig().language = wxLANGUAGE_ENGLISH; + else + { + const auto language = m_language->GetStringSelection(); + for (const auto& lang : app->GetLanguages()) + { + if (lang->Description == language) + { + GetConfig().language = lang->Language; + break; + } + } + } + + // audio + if (m_audio_api->GetStringSelection() == kDirectSound) + config.audio_api = IAudioAPI::DirectSound; + else if (m_audio_api->GetStringSelection() == kXAudio27) + config.audio_api = IAudioAPI::XAudio27; + else if (m_audio_api->GetStringSelection() == kXAudio2) + config.audio_api = IAudioAPI::XAudio2; + else if (m_audio_api->GetStringSelection() == kCubeb) + config.audio_api = IAudioAPI::Cubeb; + + config.audio_delay = m_audio_latency->GetValue(); + config.tv_channels = (AudioChannels)m_tv_channels->GetSelection(); + //config.pad_channels = (AudioChannels)m_pad_channels->GetSelection(); + config.pad_channels = kStereo; // (AudioChannels)m_pad_channels->GetSelection(); + + config.tv_volume = m_tv_volume->GetValue(); + config.pad_volume = m_pad_volume->GetValue(); + + config.tv_device = L""; + const auto tv_device = m_tv_device->GetSelection(); + if (tv_device != wxNOT_FOUND && tv_device != 0 && m_tv_device->HasClientObjectData()) + { + const auto* device_description = (wxDeviceDescription*)m_tv_device->GetClientObject(tv_device); + if(device_description) + config.tv_device = device_description->GetDescription()->GetIdentifier(); + } + + config.pad_device = L""; + const auto pad_device = m_pad_device->GetSelection(); + if (pad_device != wxNOT_FOUND && pad_device != 0 && m_pad_device->HasClientObjectData()) + { + const auto* device_description = (wxDeviceDescription*)m_pad_device->GetClientObject(pad_device); + if (device_description) + config.pad_device = device_description->GetDescription()->GetIdentifier(); + } + + // graphics + config.graphic_api = (GraphicAPI)m_graphic_api->GetSelection(); + + selection = m_graphic_device->GetSelection(); + if(selection != wxNOT_FOUND) + { + const auto* info = (wxVulkanUUID*)m_graphic_device->GetClientObject(selection); + if(info) + config.graphic_device_uuid = info->GetDeviceInfo().uuid; + else + config.graphic_device_uuid = {}; + } + else + config.graphic_device_uuid = {}; + + + config.vsync = m_vsync->GetSelection(); + config.gx2drawdone_sync = m_gx2drawdone_sync->IsChecked(); + config.async_compile = m_async_compile->IsChecked(); + + config.upscale_filter = m_upscale_filter->GetSelection(); + config.downscale_filter = m_downscale_filter->GetSelection(); + config.fullscreen_scaling = m_fullscreen_scaling->GetSelection(); + + config.overlay.position = (ScreenPosition)m_overlay_position->GetSelection(); wxASSERT((int)config.overlay.position <= (int)ScreenPosition::kBottomRight); + config.overlay.text_color = m_overlay_font_color->GetColour().GetRGBA(); + config.overlay.text_scale = m_overlay_scale->GetSelection() * 25 + 50; + + config.overlay.fps = m_overlay_fps->GetValue(); + config.overlay.drawcalls = m_overlay_drawcalls->GetValue(); + config.overlay.cpu_usage = m_overlay_cpu->GetValue(); + config.overlay.cpu_per_core_usage = m_overlay_cpu_per_core->GetValue(); + config.overlay.ram_usage = m_overlay_ram->GetValue(); + config.overlay.vram_usage = m_overlay_vram->GetValue(); + config.overlay.debug = m_overlay_debug->GetValue(); + + config.notification.position = (ScreenPosition)m_notification_position->GetSelection(); wxASSERT((int)config.notification.position <= (int)ScreenPosition::kBottomRight); + config.notification.text_color = m_notification_font_color->GetColour().GetRGBA(); + config.notification.text_scale = m_notification_scale->GetSelection() * 25 + 50; + config.notification.controller_profiles = m_controller_profile_name->GetValue(); + config.notification.controller_battery = m_controller_low_battery->GetValue(); + config.notification.shader_compiling = m_shader_compiling->GetValue(); + config.notification.friends = m_friends_data->GetValue(); + + // account + const auto active_account = m_active_account->GetSelection(); + if (active_account == wxNOT_FOUND) + config.account.m_persistent_id = config.account.m_persistent_id.GetInitValue(); + else + config.account.m_persistent_id = dynamic_cast(m_active_account->GetClientObject(active_account))->GetAccount().GetPersistentId(); + + config.account.online_enabled = m_online_enabled->GetValue(); + + // debug + config.crash_dump = (CrashDump)m_crash_dump->GetSelection(); + + g_config.Save(); +} + +GeneralSettings2::~GeneralSettings2() +{ + Unbind(wxEVT_CLOSE_WINDOW, &GeneralSettings2::OnClose, this); +} + +void GeneralSettings2::OnClose(wxCloseEvent& event) +{ + StoreConfig(); + if (m_has_account_change) + { + wxCommandEvent refresh_event(wxEVT_ACCOUNTLIST_REFRESH); + GetParent()->ProcessWindowEvent(refresh_event); + } + event.Skip(); +} + +void GeneralSettings2::ValidateConfig() +{ + g_config.Load(); + + auto& data = g_config.data(); + // todo + //data.fullscreen_scaling = min(max(data.fullscreen_scaling,)) +} + +void GeneralSettings2::DisableSettings(bool game_launched) +{ + +} + +void GeneralSettings2::OnAudioLatencyChanged(wxCommandEvent& event) +{ + IAudioAPI::s_audioDelay = event.GetInt(); + event.Skip(); +} + +void GeneralSettings2::OnVolumeChanged(wxCommandEvent& event) +{ + std::shared_lock lock(g_audioMutex); + if(event.GetEventObject() == m_pad_volume) + { + if (g_padAudio) + { + g_padAudio->SetVolume(event.GetInt()); + g_padVolume = event.GetInt(); + } + } + else + { + if (g_tvAudio) + g_tvAudio->SetVolume(event.GetInt()); + } + + + event.Skip(); +} + +void GeneralSettings2::OnInputVolumeChanged(wxCommandEvent& event) +{ + std::shared_lock lock(g_audioMutex); + if (g_padAudio) + { + g_padAudio->SetInputVolume(event.GetInt()); + g_padVolume = event.GetInt(); + } + + event.Skip(); +} + +void GeneralSettings2::OnSliderChangedPercent(wxCommandEvent& event) +{ + const auto slider = dynamic_cast(event.GetEventObject()); + wxASSERT(slider); + + const auto control = dynamic_cast(event.GetEventUserData()); + wxASSERT(control); + + auto slider_text = control->GetControl(); + wxASSERT(slider_text); + + const auto value = event.GetInt(); + slider->SetValue(value); + slider_text->SetLabel(wxString::Format("%d%%", value)); + + event.Skip(); +} + +void GeneralSettings2::OnLatencySliderChanged(wxCommandEvent& event) +{ + const auto slider = dynamic_cast(event.GetEventObject()); + wxASSERT(slider); + + const auto control = dynamic_cast(event.GetEventUserData()); + wxASSERT(control); + + auto slider_text = control->GetControl(); + wxASSERT(slider_text); + + const auto value = event.GetInt(); + slider->SetValue(value); + slider_text->SetLabel(wxString::Format("%dms", value * 12)); + + event.Skip(); +} + +void GeneralSettings2::UpdateAudioDeviceList() +{ + m_tv_device->Clear(); + m_pad_device->Clear(); + + m_tv_device->Append(_("Disabled")); + m_pad_device->Append(_("Disabled")); + + const auto audio_api = (IAudioAPI::AudioAPI)GetConfig().audio_api; + const auto devices = IAudioAPI::GetDevices(audio_api); + for (auto& device : devices) + { + m_tv_device->Append(device->GetName(), new wxDeviceDescription(device)); + m_pad_device->Append(device->GetName(), new wxDeviceDescription(device)); + } + + if(m_tv_device->GetCount() > 1) + m_tv_device->SetSelection(1); + else + m_tv_device->SetSelection(0); + + m_pad_device->SetSelection(0); + + // todo reset global instance of audio device +} + +void GeneralSettings2::ResetAccountInformation() +{ + m_account_grid->SetSplitterPosition(100); + m_active_account->SetSelection(0); + + for(auto it = m_account_grid->GetIterator(); !it.AtEnd(); ++it) + { + (*it)->SetValueToUnspecified(); + } + + // refresh pane size + m_account_information->InvalidateBestSize(); + #if BOOST_OS_WINDOWS > 0 + m_account_information->OnStateChange(GetBestSize()); + #endif +} + +void GeneralSettings2::OnAccountCreate(wxCommandEvent& event) +{ + wxASSERT(Account::HasFreeAccountSlots()); + + wxCreateAccountDialog dialog(this); + if (dialog.ShowModal() == wxID_CANCEL) + return; + + Account account(dialog.GetPersistentId(), dialog.GetMiiName().ToStdWstring()); + account.Save(); + Account::RefreshAccounts(); + + const int index = m_active_account->Append(account.ToString(), new wxAccountData(account)); + + // update ui + m_active_account->SetSelection(index); + UpdateAccountInformation(); + + m_create_account->Enable(m_active_account->GetCount() < 0xC); + m_delete_account->Enable(m_active_account->GetCount() > 1); + + // send main window event + wxASSERT(GetParent()); + wxCommandEvent refresh_event(wxEVT_ACCOUNTLIST_REFRESH); + GetParent()->ProcessWindowEvent(refresh_event); +} + +void GeneralSettings2::OnAccountDelete(wxCommandEvent& event) +{ + if(m_active_account->GetCount() == 1) + { + wxMessageBox(_("Can't delete the only account!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + return; + } + + const auto selection = m_active_account->GetSelection(); + wxASSERT(selection != wxNOT_FOUND); + auto* obj = dynamic_cast(m_active_account->GetClientObject(selection)); + wxASSERT(obj); + auto& account = obj->GetAccount(); + + const std::wstring format_str = _("Are you sure you want to delete the account {} with id {:x}?").ToStdWstring(); + const std::wstring msg = fmt::format(format_str, + std::wstring{ account.GetMiiName() }, account.GetPersistentId()); + + const int answer = wxMessageBox(msg, _("Confirmation"), wxYES_NO | wxCENTRE | wxICON_QUESTION, this); + if (answer == wxNO) + return; + + // todo: ask if saves should be deleted too? + + const fs::path path = account.GetFileName(); + try + { + fs::remove_all(path.parent_path()); + m_active_account->Delete(selection); + m_active_account->SetSelection(0); + Account::RefreshAccounts(); + UpdateAccountInformation(); + + m_create_account->Enable(m_active_account->GetCount() < 0xC); + m_delete_account->Enable(m_active_account->GetCount() > 1); + } + catch(const std::exception& ex) + { + SystemException sys(ex); + forceLog_printf((char*)sys.what()); + } + +} + +void GeneralSettings2::OnAccountSettingsChanged(wxPropertyGridEvent& event) +{ + wxPGProperty* property = event.GetProperty(); + if (!property) + return; + + const wxAny value = property->GetValue(); + if (value.IsNull()) + return; + + const auto selection = m_active_account->GetSelection(); + wxASSERT(selection != wxNOT_FOUND); + auto* obj = dynamic_cast(m_active_account->GetClientObject(selection)); + wxASSERT(obj); + auto& account = obj->GetAccount(); + + // TODO make id changeable to free ids + current it? + bool refresh_accounts = false; + if (property->GetName() == kPropertyMiiName) + { + std::wstring new_name = value.As().ToStdWstring(); + if (new_name.empty()) + new_name = L"default"; + + account.SetMiiName(new_name); + refresh_accounts = true; + } + else if (property->GetName() == kPropertyBirthday) + { + const std::string birthday = value.As().ToStdString(); + const boost::char_separator sep{ "-" }; + + std::vector tokens; + for (const auto& token : boost::tokenizer(birthday, sep)) + { + tokens.emplace_back(token); + } + + if (tokens.size() == 3) + { + account.SetBirthYear(ConvertString(tokens[0])); + account.SetBirthMonth(ConvertString(tokens[1])); + account.SetBirthDay(ConvertString(tokens[2])); + } + } + else if (property->GetName() == kPropertyGender) + { + account.SetGender(value.As()); + } + else if (property->GetName() == kPropertyEmail) + { + account.SetEmail(value.As().ToStdString()); + + } + else if (property->GetName() == kPropertyCountry) + { + account.SetCountry(value.As()); + } + else + cemu_assert_debug(false); + + account.Save(); + Account::RefreshAccounts(); // refresh internal account list + UpdateAccountInformation(); // refresh on invalid values + + if(refresh_accounts) + { + wxCommandEvent refresh_event(wxEVT_ACCOUNTLIST_REFRESH); + GetParent()->ProcessWindowEvent(refresh_event); + } +} + +void GeneralSettings2::UpdateAccountInformation() +{ + m_account_grid->SetSplitterPosition(100); + + m_online_status->SetLabel(_("At least one issue has been found")); + + const auto selection = m_active_account->GetSelection(); + if(selection == wxNOT_FOUND) + { + m_validate_online->SetBitmap(wxBITMAP_PNG(PNG_ERROR).ConvertToImage().Scale(16, 16)); + m_validate_online->SetWindowStyleFlag(m_validate_online->GetWindowStyleFlag() & ~wxBORDER_NONE); + ResetAccountInformation(); + return; + } + + const auto* obj = dynamic_cast(m_active_account->GetClientObject(selection)); + wxASSERT(obj); + const auto& account = obj->GetAccount(); + + m_active_account->SetString(selection, account.ToString()); + + m_account_grid->GetProperty(kPropertyPersistentId)->SetValueFromString(fmt::format("{:x}", account.GetPersistentId())); + m_account_grid->GetProperty(kPropertyMiiName)->SetValueFromString(std::wstring{ account.GetMiiName() }); + m_account_grid->GetProperty(kPropertyBirthday)->SetValueFromString(fmt::format("{:04d}-{:02d}-{:02d}", account.GetBirthYear(), account.GetBirthMonth(), account.GetBirthDay())); + + const auto gender_property = m_account_grid->GetProperty(kPropertyGender); // gender 2 can be also female? + gender_property->SetChoiceSelection(std::min(gender_property->GetChoices().GetCount() - 1, (uint32)account.GetGender())); + + m_account_grid->GetProperty(kPropertyEmail)->SetValueFromString(std::string{ account.GetEmail() }); + + auto* country_property = dynamic_cast(m_account_grid->GetProperty(kPropertyCountry)); + wxASSERT(country_property); + int index = (country_property)->GetIndexForValue(account.GetCountry()); + if (index == wxNOT_FOUND) + index = 0; + country_property->SetChoiceSelection(index); + + const bool online_valid = account.IsValidOnlineAccount() && ActiveSettings::HasRequiredOnlineFiles(); + if (online_valid) + { + + m_online_status->SetLabel(_("Your account is a valid online account")); + m_validate_online->SetBitmap(wxBITMAP_PNG(PNG_CHECK_YES).ConvertToImage().Scale(16, 16)); + m_validate_online->SetWindowStyleFlag(m_validate_online->GetWindowStyleFlag() | wxBORDER_NONE); + } + else + { + m_validate_online->SetBitmap(wxBITMAP_PNG(PNG_ERROR).ConvertToImage().Scale(16, 16)); + m_validate_online->SetWindowStyleFlag(m_validate_online->GetWindowStyleFlag() & ~wxBORDER_NONE); + } + + // refresh pane size + m_account_grid->InvalidateBestSize(); + //m_account_grid->GetParent()->FitInside(); + //m_account_information->OnStateChange(GetBestSize()); idk.. +} + +void GeneralSettings2::UpdateOnlineAccounts() +{ + m_active_account->Clear(); + for(const auto& account : Account::GetAccounts()) + { + m_active_account->Append(fmt::format(L"{} ({:x})", std::wstring{ account.GetMiiName() }, account.GetPersistentId()), + new wxAccountData(account)); + } + + m_active_account->SetSelection(0); + m_create_account->Enable(m_active_account->GetCount() < 0xC); + m_delete_account->Enable(m_active_account->GetCount() > 1); + UpdateAccountInformation(); +} + +void GeneralSettings2::HandleGraphicsApiSelection() +{ + int selection = m_vsync->GetSelection(); + if(selection == wxNOT_FOUND) + selection = GetConfig().vsync; + + m_vsync->Clear(); + if(m_graphic_api->GetSelection() == 0) + { + // OpenGL + m_vsync->AppendString(_("Off")); + m_vsync->AppendString(_("On")); + if (selection == 0) + m_vsync->Select(0); + else + m_vsync->Select(1); + + m_graphic_device->Clear(); + m_graphic_device->Disable(); + + m_gx2drawdone_sync->Enable(); + m_async_compile->Disable(); + } + else + { + // Vulkan + m_gx2drawdone_sync->Disable(); + m_async_compile->Enable(); + + m_vsync->AppendString(_("Off")); + m_vsync->AppendString(_("Double buffering")); + m_vsync->AppendString(_("Triple buffering")); + + m_vsync->AppendString(_("Match emulated display (Experimental)")); + + m_vsync->Select(selection); + + m_graphic_device->Enable(); + auto devices = VulkanRenderer::GetDevices(); + m_graphic_device->Clear(); + if(!devices.empty()) + { + for(const auto& device : devices) + { + m_graphic_device->Append(device.name, new wxVulkanUUID(device)); + } + m_graphic_device->SetSelection(0); + + const auto& config = GetConfig(); + for(size_t i = 0; i < devices.size(); ++i) + { + if(config.graphic_device_uuid == devices[i].uuid) + { + m_graphic_device->SetSelection(i); + break; + } + } + } + } +} + +void GeneralSettings2::ApplyConfig() +{ + ValidateConfig(); + auto& config = GetConfig(); + + if (LaunchSettings::GetMLCPath().has_value()) + m_mlc_path->SetValue(wxString{ LaunchSettings::GetMLCPath().value().generic_wstring() }); + else + m_mlc_path->SetValue(config.mlc_path.GetValue()); + + m_save_window_position_size->SetValue(config.window_position != Vector2i{-1,-1}); + m_save_padwindow_position_size->SetValue(config.pad_position != Vector2i{-1,-1}); + + m_discord_presence->SetValue(config.use_discord_presence); + m_fullscreen_menubar->SetValue(config.fullscreen_menubar); + + m_auto_update->SetValue(config.check_update); + m_save_screenshot->SetValue(config.save_screenshot); + + m_permanent_storage->SetValue(config.permanent_storage); + + for (auto& path : config.game_paths) + { + m_game_paths->Append(path); + } + + const auto app = (CemuApp*)wxTheApp; + for (const auto& language : app->GetLanguages()) + { + if (config.language == language->Language) + { + m_language->SetStringSelection(language->Description); + break; + } + } + + // graphics + m_graphic_api->SetSelection(config.graphic_api); + m_vsync->SetSelection(config.vsync); + m_async_compile->SetValue(config.async_compile); + m_gx2drawdone_sync->SetValue(config.gx2drawdone_sync); + m_upscale_filter->SetSelection(config.upscale_filter); + m_downscale_filter->SetSelection(config.downscale_filter); + m_fullscreen_scaling->SetSelection(config.fullscreen_scaling); + + wxASSERT((uint32)config.overlay.position < m_overlay_position->GetCount()); + m_overlay_position->SetSelection((int)config.overlay.position); + m_overlay_font_color->SetColour(wxColour((unsigned long)config.overlay.text_color)); + + uint32 selection = (config.overlay.text_scale - 50) / 25; + wxASSERT(selection < m_overlay_scale->GetCount()); + m_overlay_scale->SetSelection(selection); + + m_overlay_fps->SetValue(config.overlay.fps); + m_overlay_drawcalls->SetValue(config.overlay.drawcalls); + m_overlay_cpu->SetValue(config.overlay.cpu_usage); + m_overlay_cpu_per_core->SetValue(config.overlay.cpu_per_core_usage); + m_overlay_ram->SetValue(config.overlay.ram_usage); + m_overlay_vram->SetValue(config.overlay.vram_usage); + m_overlay_debug->SetValue(config.overlay.debug); + + wxASSERT((uint32)config.notification.position < m_notification_position->GetCount()); + m_notification_position->SetSelection((int)config.notification.position); + m_notification_font_color->SetColour(wxColour((unsigned long)config.notification.text_color)); + + selection = (config.notification.text_scale - 50) / 25; + wxASSERT(selection < m_notification_scale->GetCount()); + m_notification_scale->SetSelection(selection); + + m_controller_profile_name->SetValue(config.notification.controller_profiles); + m_controller_low_battery->SetValue(config.notification.controller_battery); + m_shader_compiling->SetValue(config.notification.shader_compiling); + m_friends_data->SetValue(config.notification.friends); + + // audio + if(config.audio_api == IAudioAPI::DirectSound) + m_audio_api->SetStringSelection(kDirectSound); + else if(config.audio_api == IAudioAPI::XAudio27) + m_audio_api->SetStringSelection(kXAudio27); + else if(config.audio_api == IAudioAPI::XAudio2) + m_audio_api->SetStringSelection(kXAudio2); + else if(config.audio_api == IAudioAPI::Cubeb) + m_audio_api->SetStringSelection(kCubeb); + + SendSliderEvent(m_audio_latency, config.audio_delay); + + m_tv_channels->SetSelection(config.tv_channels); + //m_pad_channels->SetSelection(config.pad_channels); + m_pad_channels->SetSelection(0); + + SendSliderEvent(m_tv_volume, config.tv_volume); + + if (!config.tv_device.empty() && m_tv_device->HasClientObjectData()) + { + for(uint32 i = 0; i < m_tv_device->GetCount(); ++i) + { + const auto device_description = (wxDeviceDescription*)m_tv_device->GetClientObject(i); + if (device_description && config.tv_device == device_description->GetDescription()->GetIdentifier()) + { + m_tv_device->SetSelection(i); + break; + } + } + } + else + m_tv_device->SetSelection(0); + + SendSliderEvent(m_pad_volume, config.pad_volume); + if (!config.pad_device.empty() && m_pad_device->HasClientObjectData()) + { + for (uint32 i = 0; i < m_pad_device->GetCount(); ++i) + { + const auto device_description = (wxDeviceDescription*)m_pad_device->GetClientObject(i); + if (device_description && config.pad_device == device_description->GetDescription()->GetIdentifier()) + { + m_pad_device->SetSelection(i); + break; + } + } + } + else + m_pad_device->SetSelection(0); + + // account + UpdateOnlineAccounts(); + m_active_account->SetSelection(0); + for(uint32 i = 0; i < m_active_account->GetCount(); ++i) + { + const auto* obj = dynamic_cast(m_active_account->GetClientObject(i)); + wxASSERT(obj); + if(obj->GetAccount().GetPersistentId() == ActiveSettings::GetPersistentId()) + { + m_active_account->SetSelection(i); + break; + } + } + + m_online_enabled->SetValue(config.account.online_enabled); + UpdateAccountInformation(); + + // debug + m_crash_dump->SetSelection((int)config.crash_dump.GetValue()); +} + +void GeneralSettings2::OnOnlineEnable(wxCommandEvent& event) +{ + event.Skip(); + if (!m_online_enabled->GetValue()) + return; + + // show warning if player enables online mode + const auto result = wxMessageBox(_("Please be aware that online mode lets you connect to OFFICIAL servers and therefore there is a risk of getting banned.\nOnly proceed if you are willing to risk losing online access with your Wii U and/or NNID."), + _("Warning"), wxYES_NO | wxCENTRE | wxICON_EXCLAMATION, this); + if (result == wxNO) + m_online_enabled->SetValue(false); +} + + +void GeneralSettings2::OnAudioAPISelected(wxCommandEvent& event) +{ + IAudioAPI::AudioAPI api; + if (m_audio_api->GetStringSelection() == kDirectSound) + api = IAudioAPI::DirectSound; + else if (m_audio_api->GetStringSelection() == kXAudio27) + api = IAudioAPI::XAudio27; + else if (m_audio_api->GetStringSelection() == kXAudio2) + api = IAudioAPI::XAudio2; + else if (m_audio_api->GetStringSelection() == kCubeb) + api = IAudioAPI::Cubeb; + else + { + wxFAIL_MSG("invalid audio api selected!"); + return; + } + + GetConfig().audio_api = api; + UpdateAudioDeviceList(); + OnAudioDeviceSelected(event); +} + +#define AX_FRAMES_PER_GROUP 4 + +void GeneralSettings2::UpdateAudioDevice() +{ + auto& config = GetConfig(); + + // tv audio device + { + const auto selection = m_tv_device->GetSelection(); + if (selection == wxNOT_FOUND) + { + cemu_assert_debug(false); + return; + } + + if (m_tv_device->HasClientObjectData()) + { + const auto description = (wxDeviceDescription*)m_tv_device->GetClientObject(selection); + if (description) + { + std::unique_lock lock(g_audioMutex); + + sint32 channels; + if (m_game_launched && g_tvAudio) + channels = g_tvAudio->GetChannels(); + else + { + switch (config.tv_channels) + { + case 0: + channels = 1; + break; + case 2: + channels = 6; + break; + default: // stereo + channels = 2; + break; + } + } + + try + { + g_tvAudio.reset(); + g_tvAudio = IAudioAPI::CreateDevice((IAudioAPI::AudioAPI)config.audio_api, description->GetDescription(), 48000, channels, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16); + g_tvAudio->SetVolume(m_tv_volume->GetValue()); + } + catch (std::runtime_error& ex) + { + forceLog_printf("can't initialize tv audio: %s", ex.what()); + } + } + } + } + + // pad audio device + { + const auto selection = m_pad_device->GetSelection(); + if (selection == wxNOT_FOUND) + { + cemu_assert_debug(false); + return; + } + + if (m_pad_device->HasClientObjectData()) + { + const auto description = (wxDeviceDescription*)m_pad_device->GetClientObject(selection); + if (description) + { + std::unique_lock lock(g_audioMutex); + + sint32 channels; + if (m_game_launched && g_padAudio) + channels = g_padAudio->GetChannels(); + else + { + switch (config.pad_channels) + { + case 0: + channels = 1; + break; + case 2: + channels = 6; + break; + default: // stereo + channels = 2; + break; + } + } + + try + { + g_padAudio.reset(); + g_padAudio = IAudioAPI::CreateDevice((IAudioAPI::AudioAPI)config.audio_api, description->GetDescription(), 48000, channels, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16); + g_padAudio->SetVolume(m_pad_volume->GetValue()); + g_padVolume = m_pad_volume->GetValue(); + } + catch (std::runtime_error& ex) + { + forceLog_printf("can't initialize pad audio: %s", ex.what()); + } + } + } + } +} + +void GeneralSettings2::OnAudioDeviceSelected(wxCommandEvent& event) +{ + UpdateAudioDevice(); +} + +void GeneralSettings2::OnAudioChannelsSelected(wxCommandEvent& event) +{ + const auto obj = wxDynamicCast(event.GetEventObject(), wxChoice); + wxASSERT(obj); + if (obj->GetSelection() == wxNOT_FOUND) + return; + + auto& config = GetConfig(); + if (obj == m_tv_channels) + { + if (config.tv_channels == (AudioChannels)obj->GetSelection()) + return; + + config.tv_channels = (AudioChannels)obj->GetSelection(); + } + else if (obj == m_pad_channels) + { + if (config.pad_channels == (AudioChannels)obj->GetSelection()) + return; + + config.pad_channels = (AudioChannels)obj->GetSelection(); + } + else + cemu_assert_debug(false); + + if(m_game_launched) + wxMessageBox(_("You have to restart the game in order to apply the new settings."), _("Information"), wxOK | wxCENTRE, this); + else + UpdateAudioDevice(); +} + +void GeneralSettings2::OnGraphicAPISelected(wxCommandEvent& event) +{ + HandleGraphicsApiSelection(); +} + +void GeneralSettings2::OnAddPathClicked(wxCommandEvent& event) +{ + wxDirDialog path_dialog(this, _("Select a directory containing games."), wxEmptyString, wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST); + if (path_dialog.ShowModal() != wxID_OK || path_dialog.GetPath().empty()) + return; + + const auto path = path_dialog.GetPath(); + // test if already included + for (auto& s : m_game_paths->GetStrings()) + { + if (s == path) + return; + } + + m_game_paths->Append(path); + m_reload_gamelist = true; + + // trigger title list rescan with new path configuration + CafeTitleList::ClearScanPaths(); + for (auto& it : m_game_paths->GetStrings()) + CafeTitleList::AddScanPath(wxHelper::MakeFSPath(it)); + CafeTitleList::Refresh(); +} + +void GeneralSettings2::OnRemovePathClicked(wxCommandEvent& event) +{ + const auto selection = m_game_paths->GetSelection(); + if (selection == wxNOT_FOUND) + return; + + m_game_paths->Delete(selection); + m_reload_gamelist = true; + // trigger title list rescan with new path configuration + CafeTitleList::ClearScanPaths(); + for (auto& it : m_game_paths->GetStrings()) + CafeTitleList::AddScanPath(wxHelper::MakeFSPath(it)); + CafeTitleList::Refresh(); +} + +void GeneralSettings2::OnActiveAccountChanged(wxCommandEvent& event) +{ + UpdateAccountInformation(); + m_has_account_change = true; +} + +void GeneralSettings2::OnMLCPathSelect(wxCommandEvent& event) +{ + if (!CemuApp::SelectMLCPath(this)) + return; + + m_mlc_path->SetValue(ActiveSettings::GetMlcPath().generic_string()); + m_reload_gamelist = true; + m_mlc_modified = true; + CemuApp::CreateDefaultFiles(); +} + +void GeneralSettings2::OnMLCPathChar(wxKeyEvent& event) +{ + if (LaunchSettings::GetMLCPath().has_value()) + return; + + if(event.GetKeyCode() == WXK_DELETE || event.GetKeyCode() == WXK_BACK) + { + m_mlc_path->SetValue(wxEmptyString); + m_reload_gamelist = true; + + GetConfig().mlc_path = L""; + g_config.Save(); + m_mlc_modified = true; + + CemuApp::CreateDefaultFiles(); + } +} + +void GeneralSettings2::OnShowOnlineValidator(wxCommandEvent& event) +{ + const auto selection = m_active_account->GetSelection(); + if (selection == wxNOT_FOUND) + return; + + const auto* obj = dynamic_cast(m_active_account->GetClientObject(selection)); + wxASSERT(obj); + const auto& account = obj->GetAccount(); + + const auto validator = account.ValidateOnlineFiles(); + if (validator) // everything valid? shouldn't happen + return; + + std::wstringstream err; + err << L"The following error(s) have been found:" << std::endl; + + if (validator.otp == OnlineValidator::FileState::Missing) + err << L"otp.bin missing in cemu root directory" << std::endl; + else if(validator.otp == OnlineValidator::FileState::Corrupted) + err << L"otp.bin is invalid" << std::endl; + + if (validator.seeprom == OnlineValidator::FileState::Missing) + err << L"seeprom.bin missing in cemu root directory" << std::endl; + else if(validator.seeprom == OnlineValidator::FileState::Corrupted) + err << L"seeprom.bin is invalid" << std::endl; + + if(!validator.missing_files.empty()) + { + err << L"Missing certificate and key files:" << std::endl; + + int counter = 0; + for (const auto& f : validator.missing_files) + { + err << f << std::endl; + + ++counter; + if(counter > 10) + { + err << L"..." << std::endl; + break; + } + } + + err << std::endl; + } + + if (!validator.valid_account) + { + err << L"The currently selected account is not a valid or dumped online account:\n" << boost::nowide::widen(fmt::format("{}", validator.account_error)); + } + + + wxMessageBox(err.str(), _("Online Status"), wxOK | wxCENTRE | wxICON_INFORMATION); +} diff --git a/src/gui/GeneralSettings2.h b/src/gui/GeneralSettings2.h new file mode 100644 index 00000000..ed326d5a --- /dev/null +++ b/src/gui/GeneralSettings2.h @@ -0,0 +1,108 @@ +#pragma once +#include +#include + +class wxColourPickerCtrl; + +wxDECLARE_EVENT(wxEVT_ACCOUNTLIST_REFRESH, wxCommandEvent); + +class GeneralSettings2 : public wxDialog +{ +public: + GeneralSettings2(wxWindow* parent, bool game_launched); + ~GeneralSettings2(); + + [[nodiscard]] bool ShouldReloadGamelist() const { return m_reload_gamelist; } + [[nodiscard]] bool MLCModified() const { return m_mlc_modified; } + void OnClose(wxCloseEvent& event); + +private: + void ValidateConfig(); + void StoreConfig(); + void DisableSettings(bool game_launched); + + bool m_reload_gamelist = false; + bool m_mlc_modified = false; + bool m_game_launched; + + bool m_has_account_change = false; // keep track of dirty state of accounts + + + wxPanel* AddGeneralPage(wxNotebook* notebook); + wxPanel* AddGraphicsPage(wxNotebook* notebook); + wxPanel* AddAudioPage(wxNotebook* notebook); + wxPanel* AddOverlayPage(wxNotebook* notebook); + wxPanel* AddAccountPage(wxNotebook* notebook); + wxPanel* AddDebugPage(wxNotebook* notebook); + + // General + wxChoice * m_language; + wxCheckBox* m_save_window_position_size; + wxCheckBox* m_save_padwindow_position_size; + wxCheckBox* m_discord_presence, *m_fullscreen_menubar; + wxCheckBox* m_auto_update, *m_save_screenshot; + wxCheckBox* m_permanent_storage; + wxListBox* m_game_paths; + wxTextCtrl* m_mlc_path; + + // Graphics + wxChoice* m_graphic_api, * m_graphic_device; + wxChoice* m_vsync; + wxCheckBox *m_async_compile, *m_gx2drawdone_sync; + wxRadioBox* m_upscale_filter, *m_downscale_filter, *m_fullscreen_scaling; + wxChoice* m_overlay_position, *m_notification_position, *m_overlay_scale, *m_notification_scale; + wxCheckBox* m_controller_profile_name, *m_controller_low_battery, *m_shader_compiling, *m_friends_data; + wxCheckBox *m_overlay_fps, *m_overlay_drawcalls, *m_overlay_cpu, *m_overlay_cpu_per_core,*m_overlay_ram, *m_overlay_vram, *m_overlay_debug; + wxColourPickerCtrl *m_overlay_font_color, *m_notification_font_color; + + // Audio + wxChoice* m_audio_api; + wxSlider *m_audio_latency; + wxSlider *m_tv_volume, *m_pad_volume; + wxChoice *m_tv_channels, *m_pad_channels; + wxChoice *m_tv_device, *m_pad_device; + + // Account + wxButton* m_create_account, * m_delete_account; + wxChoice* m_active_account; + wxCheckBox* m_online_enabled; + wxCollapsiblePane* m_account_information; + wxPropertyGrid* m_account_grid; + wxBitmapButton* m_validate_online; + wxStaticText* m_online_status; + + // Debug + wxChoice* m_crash_dump; + + void OnAccountCreate(wxCommandEvent& event); + void OnAccountDelete(wxCommandEvent& event); + void OnAccountSettingsChanged(wxPropertyGridEvent& event); + void OnAudioLatencyChanged(wxCommandEvent& event); + void OnVolumeChanged(wxCommandEvent& event); + void OnInputVolumeChanged(wxCommandEvent& event); + void OnSliderChangedPercent(wxCommandEvent& event); + void OnLatencySliderChanged(wxCommandEvent& event); + void OnAudioAPISelected(wxCommandEvent& event); + void OnAudioDeviceSelected(wxCommandEvent& event); + void OnAudioChannelsSelected(wxCommandEvent& event); + void OnGraphicAPISelected(wxCommandEvent& event); + void OnAddPathClicked(wxCommandEvent& event); + void OnRemovePathClicked(wxCommandEvent& event); + void OnActiveAccountChanged(wxCommandEvent& event); + void OnMLCPathSelect(wxCommandEvent& event); + void OnMLCPathChar(wxKeyEvent& event); + void OnShowOnlineValidator(wxCommandEvent& event); + void OnOnlineEnable(wxCommandEvent& event); + + // updates cemu audio devices + void UpdateAudioDevice(); + // refreshes audio device list for dropdown + void UpdateAudioDeviceList(); + + void ResetAccountInformation(); + void UpdateAccountInformation(); + void UpdateOnlineAccounts(); + void HandleGraphicsApiSelection(); + void ApplyConfig(); +}; + diff --git a/src/gui/GettingStartedDialog.cpp b/src/gui/GettingStartedDialog.cpp new file mode 100644 index 00000000..13fbfa94 --- /dev/null +++ b/src/gui/GettingStartedDialog.cpp @@ -0,0 +1,314 @@ +#include "gui/GettingStartedDialog.h" + +#include +#include +#include +#include + +#include "config/ActiveSettings.h" +#include "gui/CemuApp.h" +#include "gui/DownloadGraphicPacksWindow.h" +#include "gui/GraphicPacksWindow2.h" +#include "gui/input/InputSettings2.h" +#include "config/CemuConfig.h" +#include "config/PermanentConfig.h" + +#include "Cafe/TitleList/TitleList.h" + +#if BOOST_OS_LINUX > 0 +#include "resource/linux/resources.h" +#endif + +#include "wxHelper.h" + +wxPanel* GettingStartedDialog::CreatePage1() +{ + auto* result = new wxPanel(m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + auto* page1_sizer = new wxBoxSizer(wxVERTICAL); + { + auto* sizer = new wxBoxSizer(wxHORIZONTAL); + + sizer->Add(new wxStaticBitmap(result, wxID_ANY, wxICON(M_WND_ICON128)), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + + auto* m_staticText11 = new wxStaticText(result, wxID_ANY, _("It looks like you're starting Cemu for the first time.\nThis quick setup assistant will help you get the best experience"), wxDefaultPosition, wxDefaultSize, 0); + m_staticText11->Wrap(-1); + sizer->Add(m_staticText11, 0, wxALL, 5); + + page1_sizer->Add(sizer, 0, wxALL | wxEXPAND, 5); + } + + { + m_mlc_box_sizer = new wxStaticBoxSizer(wxVERTICAL, result, _("mlc01 path")); + m_mlc_box_sizer->Add(new wxStaticText(m_mlc_box_sizer->GetStaticBox(), wxID_ANY, _("The mlc path is the root folder of the emulated Wii U internal flash storage. It contains all your saves, installed updates and DLCs.\nIt is strongly recommend that you create a dedicated folder for it (example: C:\\wiiu\\mlc\\) \nIf left empty, the mlc folder will be created inside the Cemu folder.")), 0, wxALL, 5); + + m_prev_mlc_warning = new wxStaticText(m_mlc_box_sizer->GetStaticBox(), wxID_ANY, _("A custom mlc path from a previous Cemu installation has been found and filled in.")); + m_prev_mlc_warning->SetForegroundColour(*wxRED); + m_prev_mlc_warning->Show(false); + m_mlc_box_sizer->Add(m_prev_mlc_warning, 0, wxALL, 5); + + auto* mlc_path_sizer = new wxBoxSizer(wxHORIZONTAL); + mlc_path_sizer->Add(new wxStaticText(m_mlc_box_sizer->GetStaticBox(), wxID_ANY, _("Custom mlc01 path")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + + // workaround since we can't specify our own browse label? >> _("Browse") + m_mlc_folder = new wxDirPickerCtrl(m_mlc_box_sizer->GetStaticBox(), wxID_ANY, wxEmptyString, _("Select a folder"), wxDefaultPosition, wxDefaultSize, wxDIRP_DEFAULT_STYLE); + auto tTest1 = m_mlc_folder->GetTextCtrl(); + if(m_mlc_folder->HasTextCtrl()) + { + m_mlc_folder->GetTextCtrl()->SetEditable(false); + m_mlc_folder->GetTextCtrl()->Bind(wxEVT_CHAR, &GettingStartedDialog::OnMLCPathChar, this); + } + mlc_path_sizer->Add(m_mlc_folder, 1, wxALL, 5); + + mlc_path_sizer->Add(new wxStaticText(m_mlc_box_sizer->GetStaticBox(), wxID_ANY, _("(optional)")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + + m_mlc_box_sizer->Add(mlc_path_sizer, 0, wxEXPAND, 5); + + page1_sizer->Add(m_mlc_box_sizer, 0, wxALL | wxEXPAND, 5); + } + + { + auto* sizer = new wxStaticBoxSizer(wxVERTICAL, result, _("Game paths")); + + sizer->Add(new wxStaticText(sizer->GetStaticBox(), wxID_ANY, _("The game path is scanned by Cemu to locate your games. We recommend creating a dedicated directory in which\nyou place all your Wii U games. (example: C:\\wiiu\\games\\)\n\nYou can also set additional paths in the general settings of Cemu.")), 0, wxALL, 5); + + auto* game_path_sizer = new wxBoxSizer(wxHORIZONTAL); + + game_path_sizer->Add(new wxStaticText(sizer->GetStaticBox(), wxID_ANY, _("Game path")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + + m_game_path = new wxDirPickerCtrl(sizer->GetStaticBox(), wxID_ANY, wxEmptyString, _("Select a folder")); + game_path_sizer->Add(m_game_path, 1, wxALL, 5); + + sizer->Add(game_path_sizer, 0, wxEXPAND, 5); + + page1_sizer->Add(sizer, 0, wxALL | wxEXPAND, 5); + } + + { + auto* sizer = new wxStaticBoxSizer(wxVERTICAL, result, _("Graphic packs")); + + sizer->Add(new wxStaticText(sizer->GetStaticBox(), wxID_ANY, _("Graphic packs improve games by offering the possibility to change resolution, tweak FPS or add other visual or gameplay modifications.\nDownload the community graphic packs to get started.\n")), 0, wxALL, 5); + + auto* download_gp = new wxButton(sizer->GetStaticBox(), wxID_ANY, _("Download community graphic packs")); + download_gp->Bind(wxEVT_BUTTON, &GettingStartedDialog::OnDownloadGPs, this); + sizer->Add(download_gp, 0, wxALIGN_CENTER | wxALL, 5); + + page1_sizer->Add(sizer, 0, wxALL | wxEXPAND, 5); + } + + { + auto* sizer = new wxFlexGridSizer(0, 1, 0, 0); + sizer->AddGrowableCol(0); + sizer->AddGrowableRow(0); + sizer->SetFlexibleDirection(wxBOTH); + sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_ALL); + + auto* next = new wxButton(result, wxID_ANY, _("Next"), wxDefaultPosition, wxDefaultSize, 0); + next->Bind(wxEVT_BUTTON, [this](const auto&){m_notebook->SetSelection(1); }); + sizer->Add(next, 0, wxALIGN_BOTTOM | wxALIGN_RIGHT | wxALL, 5); + + page1_sizer->Add(sizer, 1, wxEXPAND, 5); + } + + + result->SetSizer(page1_sizer); + return result; +} + +wxPanel* GettingStartedDialog::CreatePage2() +{ + auto* result = new wxPanel(m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + auto* page2_sizer = new wxBoxSizer(wxVERTICAL); + + { + auto* sizer = new wxStaticBoxSizer(new wxStaticBox(result, wxID_ANY, _("Input settings")), wxVERTICAL); + + sizer->Add(new wxStaticText(sizer->GetStaticBox(), wxID_ANY, _("You can configure one controller for each player.\nWe advise you to always use GamePad as emulated input for the first player, since many games expect the GamePad to be present.\nIt is also required for touch functionality.\nThe default global hotkeys are:\nCTRL - show pad screen\nCTRL + TAB - toggle pad screen\nALT + ENTER - toggle fullscreen\nESC - leave fullscreen\n\nIf you're having trouble configuring your controller, make sure to have it in idle state and press calibrate.\nAlso don't set the axis deadzone too low.")), 0, wxALL, 5); + + auto* input_button = new wxButton(sizer->GetStaticBox(), wxID_ANY, _("Configure input")); + input_button->Bind(wxEVT_BUTTON, &GettingStartedDialog::OnInputSettings, this); + sizer->Add(input_button, 0, wxALIGN_CENTER | wxALL, 5); + + page2_sizer->Add(sizer, 0, wxALL | wxEXPAND, 5); + } + + { + auto* sizer = new wxStaticBoxSizer(new wxStaticBox(result, wxID_ANY, _("Additional options")), wxVERTICAL); + + auto* option_sizer = new wxFlexGridSizer(0, 2, 0, 0); + option_sizer->SetFlexibleDirection(wxBOTH); + option_sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); + + m_fullscreen = new wxCheckBox(sizer->GetStaticBox(), wxID_ANY, _("Start games with fullscreen")); + option_sizer->Add(m_fullscreen, 0, wxALL, 5); + + m_separate = new wxCheckBox(sizer->GetStaticBox(), wxID_ANY, _("Open separate pad screen")); + option_sizer->Add(m_separate, 0, wxALL, 5); + + m_update = new wxCheckBox(sizer->GetStaticBox(), wxID_ANY, _("Automatically check for updates")); + option_sizer->Add(m_update, 0, wxALL, 5); + + sizer->Add(option_sizer, 1, wxEXPAND, 5); + page2_sizer->Add(sizer, 0, wxALL | wxEXPAND, 5); + } + + { + auto* sizer = new wxFlexGridSizer(0, 3, 0, 0); + sizer->AddGrowableCol(1); + sizer->AddGrowableRow(0); + sizer->SetFlexibleDirection(wxBOTH); + sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_ALL); + + m_dont_show = new wxCheckBox(result, wxID_ANY, _("Don't show this again")); + m_dont_show->SetValue(true); + sizer->Add(m_dont_show, 0, wxALIGN_BOTTOM | wxALL, 5); + + auto* previous = new wxButton(result, wxID_ANY, _("Previous")); + previous->Bind(wxEVT_BUTTON, [this](const auto&) {m_notebook->SetSelection(0); }); + sizer->Add(previous, 0, wxALIGN_BOTTOM | wxALIGN_RIGHT | wxALL, 5); + + auto* close = new wxButton(result, wxID_ANY, _("Close")); + close->Bind(wxEVT_BUTTON, [this](const auto&){ Close(); }); + sizer->Add(close, 1, wxALIGN_BOTTOM | wxALIGN_RIGHT | wxALL, 5); + + page2_sizer->Add(sizer, 1, wxEXPAND | wxLEFT, 5); + } + + result->SetSizer(page2_sizer); + return result; +} + +void GettingStartedDialog::ApplySettings() +{ + auto& config = GetConfig(); + m_fullscreen->SetValue(config.fullscreen.GetValue()); + m_update->SetValue(config.check_update.GetValue()); + m_separate->SetValue(config.pad_open.GetValue()); + m_dont_show->SetValue(true); // we want it always enabled by default + m_mlc_folder->SetPath(config.mlc_path.GetValue()); + + try + { + const auto pconfig = PermanentConfig::Load(); + if(!pconfig.custom_mlc_path.empty()) + { + m_mlc_folder->SetPath(wxString::FromUTF8(pconfig.custom_mlc_path)); + m_prev_mlc_warning->Show(true); + } + } + catch (const PSDisabledException&) {} + catch (...) {} +} + +void GettingStartedDialog::UpdateWindowSize() +{ + for (auto it = m_notebook->GetChildren().GetFirst(); it; it = it->GetNext()) + { + it->GetData()->Layout(); + } + m_notebook->Layout(); + Layout(); + Fit(); +} + +void GettingStartedDialog::OnClose(wxCloseEvent& event) +{ + event.Skip(); + + auto& config = GetConfig(); + config.fullscreen = m_fullscreen->GetValue(); + config.check_update = m_update->GetValue(); + config.pad_open = m_separate->GetValue(); + config.did_show_graphic_pack_download = m_dont_show->GetValue(); + + const fs::path gamePath = wxHelper::MakeFSPath(m_game_path->GetPath()); + if (!gamePath.empty() && fs::exists(gamePath)) + { + const auto it = std::find(config.game_paths.cbegin(), config.game_paths.cend(), gamePath); + if (it == config.game_paths.cend()) + { + config.game_paths.emplace_back(gamePath.generic_wstring()); + m_game_path_changed = true; + } + } + + const fs::path mlcPath = wxHelper::MakeFSPath(m_mlc_folder->GetPath()); + if(config.mlc_path.GetValue() != mlcPath && (mlcPath.empty() || fs::exists(mlcPath))) + { + config.SetMLCPath(mlcPath.generic_wstring(), false); + m_mlc_changed = true; + } + + g_config.Save(); + + if(m_mlc_changed) + CemuApp::CreateDefaultFiles(); + + CafeTitleList::ClearScanPaths(); + for (auto& it : GetConfig().game_paths) + CafeTitleList::AddScanPath(it); + CafeTitleList::Refresh(); +} + + +GettingStartedDialog::GettingStartedDialog(wxWindow* parent) + : wxDialog(parent, wxID_ANY, _("Getting started"), wxDefaultPosition, { 740,530 }, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) +{ + //this->SetSizeHints(wxDefaultSize, { 740,530 }); + + auto* sizer = new wxBoxSizer(wxVERTICAL); + + m_notebook = new wxSimplebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0); + + auto* m_page1 = CreatePage1(); + m_notebook->AddPage(m_page1, wxEmptyString, false); + + auto* m_page2 = CreatePage2(); + m_notebook->AddPage(m_page2, wxEmptyString, false); + + sizer->Add(m_notebook, 1, wxEXPAND | wxALL, 5); + + this->SetSizer(sizer); + this->Centre(wxBOTH); + this->Bind(wxEVT_CLOSE_WINDOW, &GettingStartedDialog::OnClose, this); + + ApplySettings(); + UpdateWindowSize(); +} + +void GettingStartedDialog::OnDownloadGPs(wxCommandEvent& event) +{ + DownloadGraphicPacksWindow dialog(this); + dialog.ShowModal(); + + GraphicPacksWindow2::RefreshGraphicPacks(); + + wxMessageDialog ask_dialog(this, _("Do you want to view the downloaded graphic packs?"), _("Graphic packs"), wxCENTRE | wxYES_NO); + if (ask_dialog.ShowModal() == wxID_YES) + { + GraphicPacksWindow2 window(this, 0); + window.ShowModal(); + } +} + +void GettingStartedDialog::OnInputSettings(wxCommandEvent& event) +{ + InputSettings2 dialog(this); + dialog.ShowModal(); +} + +void GettingStartedDialog::OnMLCPathChar(wxKeyEvent& event) +{ + //if (LaunchSettings::GetMLCPath().has_value()) + // return; + + if (event.GetKeyCode() == WXK_DELETE || event.GetKeyCode() == WXK_BACK) + { + m_mlc_folder->GetTextCtrl()->SetValue(wxEmptyString); + if(m_prev_mlc_warning->IsShown()) + { + m_prev_mlc_warning->Show(false); + UpdateWindowSize(); + } + } +} + diff --git a/src/gui/GettingStartedDialog.h b/src/gui/GettingStartedDialog.h new file mode 100644 index 00000000..ec122eab --- /dev/null +++ b/src/gui/GettingStartedDialog.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +class GettingStartedDialog : public wxDialog +{ +public: + GettingStartedDialog(wxWindow* parent = nullptr); + + [[nodiscard]] bool HasGamePathChanged() const { return m_game_path_changed; } + [[nodiscard]] bool HasMLCChanged() const { return m_mlc_changed; } + +private: + wxPanel* CreatePage1(); + wxPanel* CreatePage2(); + void ApplySettings(); + void UpdateWindowSize(); + + void OnClose(wxCloseEvent& event); + void OnDownloadGPs(wxCommandEvent& event); + void OnInputSettings(wxCommandEvent& event); + void OnMLCPathChar(wxKeyEvent& event); + + wxSimplebook* m_notebook; + wxCheckBox* m_fullscreen; + wxCheckBox* m_separate; + wxCheckBox* m_update; + wxCheckBox* m_dont_show; + + wxStaticBoxSizer* m_mlc_box_sizer; + wxStaticText* m_prev_mlc_warning; + wxDirPickerCtrl* m_mlc_folder; + wxDirPickerCtrl* m_game_path; + + bool m_game_path_changed = false; + bool m_mlc_changed = false; +}; + diff --git a/src/gui/GraphicPacksWindow2.cpp b/src/gui/GraphicPacksWindow2.cpp new file mode 100644 index 00000000..53a65b69 --- /dev/null +++ b/src/gui/GraphicPacksWindow2.cpp @@ -0,0 +1,675 @@ +#include "gui/wxgui.h" +#include "gui/GraphicPacksWindow2.h" +#include "gui/DownloadGraphicPacksWindow.h" +#include "Cafe/GraphicPack/GraphicPack2.h" +#include "config/CemuConfig.h" +#include "Cafe/GraphicPack/GraphicPack.h" + +#include "Cafe/HW/Latte/Core/LatteAsyncCommands.h" + +#include "Cafe/CafeSystem.h" +#include "Cafe/TitleList/TitleList.h" + +#if BOOST_OS_LINUX > 0 +#include "resource/linux/resources.h" +#endif + +// main.cpp +bool IsCemuhookLoaded(); + +class wxGraphicPackData : public wxTreeItemData +{ +public: + wxGraphicPackData(GraphicPackPtr pack) + : m_pack(std::move(pack)) { } + + const GraphicPackPtr& GetGraphicPack() const { return m_pack; } + +private: + GraphicPackPtr m_pack; +}; + +void GraphicPacksWindow2::FillGraphicPackList() const +{ + wxWindowUpdateLocker lock(m_graphic_pack_tree); + + m_graphic_pack_tree->DeleteAllItems(); + auto graphic_packs = GraphicPack2::GetGraphicPacks(); + + const auto root = m_graphic_pack_tree->AddRoot("Root"); + + const bool has_filter = !m_filter.empty(); + + for(auto& p : graphic_packs) + { + // filter graphic packs by given title id + if (m_filter_installed_games && !m_installed_games.empty()) + { + bool found = false; + for (uint64 titleId : p->GetTitleIds()) + { + if (std::find(m_installed_games.cbegin(), m_installed_games.cend(), titleId) != m_installed_games.cend()) + { + found = true; + break; + } + } + + if (!found) + continue; + } + + // filter graphic packs by given title id + if(has_filter) + { + bool found = false; + + if (boost::icontains(p->GetPath(), m_filter)) + found = true; + else + { + for (uint64 titleId : p->GetTitleIds()) + { + if (boost::icontains(fmt::format("{:x}", titleId), m_filter)) + { + found = true; + break; + } + } + } + + if (!found) + continue; + } + + const auto& path = p->GetPath(); + auto tokens = TokenizeView(path, '/'); + auto node = root; + for(size_t i=0; iAppendItem(parent_node, wxString(token.data(), token.length())); + } + } + else + { + // last token + // if a node with same name already exists, add a number suffix + for (sint32 s = 0; s < 999; s++) + { + wxString nodeName(token.data(), token.length()); + if (s > 0) + nodeName.append(fmt::format(" #{}", s + 1)); + + node = FindTreeItem(parent_node, nodeName); + if (!node.IsOk()) + { + node = m_graphic_pack_tree->AppendItem(parent_node, nodeName); + break; + } + } + } + } + + if(node.IsOk() && node != root) + { + m_graphic_pack_tree->SetItemData(node, new wxGraphicPackData(p)); + bool canEnable = true; + + if (p->GetVersion() == 3) + { + auto tmp_text = m_graphic_pack_tree->GetItemText(node); + m_graphic_pack_tree->SetItemText(node, tmp_text + " (may not be compatible with Vulkan)"); + } + else if (p->GetVersion() != 3 && p->GetVersion() != 4 && p->GetVersion() != 5 && p->GetVersion() != 6 && p->GetVersion() != GraphicPack2::GFXPACK_VERSION_7) + { + auto tmp_text = m_graphic_pack_tree->GetItemText(node); + m_graphic_pack_tree->SetItemText(node, tmp_text + " (Unsupported version)"); + m_graphic_pack_tree->SetItemTextColour(node, 0x0000CC); + canEnable = false; + } + else if (p->IsActivated()) + m_graphic_pack_tree->SetItemTextColour(node, 0x009900); + + m_graphic_pack_tree->MakeCheckable(node, p->IsEnabled()); + if (!canEnable) + m_graphic_pack_tree->DisableCheckBox(node); + } + } + + m_graphic_pack_tree->Sort(root, true); + + if (!m_filter.empty()) + { + size_t counter = 0; + ExpandChildren({ root }, counter); + } +} + + +void GraphicPacksWindow2::GetChildren(const wxTreeItemId& id, std::vector& children) const +{ + wxTreeItemIdValue cookie; + wxTreeItemId child = m_graphic_pack_tree->GetFirstChild(id, cookie); + while (child.IsOk()) + { + children.emplace_back(child); + child = m_graphic_pack_tree->GetNextChild(child, cookie); + } +} + +void GraphicPacksWindow2::ExpandChildren(const std::vector& ids, size_t& counter) const +{ + std::vector children; + for (const auto& id : ids) + GetChildren(id, children); + + counter += children.size(); + if (counter >= 30 || children.empty()) + return; + + for (const auto& id : ids) + { + if(id != m_graphic_pack_tree->GetRootItem() && m_graphic_pack_tree->HasChildren(id)) + m_graphic_pack_tree->Expand(id); + } + + ExpandChildren(children, counter); +} + +void GraphicPacksWindow2::RefreshGraphicPacks() +{ + GraphicPack2::ClearGraphicPacks(); + graphicPack_loadAll(); +} + +GraphicPacksWindow2::GraphicPacksWindow2(wxWindow* parent, uint64_t title_id_filter) + : wxDialog(parent, wxID_ANY, _("Graphic packs"), wxDefaultPosition, wxSize(1000,670), wxCLOSE_BOX | wxCLIP_CHILDREN | wxCAPTION | wxRESIZE_BORDER), + m_installed_games(CafeTitleList::GetAllTitleIds()) +{ + if (title_id_filter != 0) + m_filter = fmt::format("{:x}", title_id_filter); + + m_filter_installed_games = !m_installed_games.empty(); + + SetIcon(wxICON(X_BOX)); + SetMinSize(wxSize(500, 400)); + auto main_sizer = new wxBoxSizer(wxVERTICAL); + + m_splitter_window = new wxSplitterWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSP_3D); + m_splitter_window->Bind(wxEVT_SIZE, &GraphicPacksWindow2::OnSizeChanged, this); + m_splitter_window->Bind(wxEVT_SPLITTER_SASH_POS_CHANGED, &GraphicPacksWindow2::SashPositionChanged, this); + + // left side + auto left_panel = new wxPanel(m_splitter_window); + { + auto sizer = new wxBoxSizer(wxVERTICAL); + + wxFlexGridSizer* filter_row = new wxFlexGridSizer(0, 3, 0, 0); + filter_row->AddGrowableCol(1); + filter_row->SetFlexibleDirection(wxBOTH); + filter_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); + + const auto text = new wxStaticText(left_panel, wxID_ANY, _("Filter"), wxDefaultPosition, wxDefaultSize, 0); + text->Wrap(-1); + filter_row->Add(text, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + + m_filter_text = new wxTextCtrl(left_panel, wxID_ANY, m_filter, wxDefaultPosition, wxDefaultSize, 0); + filter_row->Add(m_filter_text, 0, wxALL | wxEXPAND, 5); + m_filter_text->Bind(wxEVT_COMMAND_TEXT_UPDATED, &GraphicPacksWindow2::OnFilterUpdate, this); + + m_installed_games_only = new wxCheckBox(left_panel, wxID_ANY, _("Installed games"), wxDefaultPosition, wxDefaultSize, 0); + m_installed_games_only->SetValue(m_filter_installed_games); + filter_row->Add(m_installed_games_only, 0, wxALL | wxEXPAND, 5); + m_installed_games_only->Bind(wxEVT_CHECKBOX, &GraphicPacksWindow2::OnInstalledGamesChanged, this); + if (m_installed_games.empty()) + m_installed_games_only->Disable(); + + sizer->Add(filter_row, 0, wxEXPAND, 5); + + m_graphic_pack_tree = new wxCheckTree(left_panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTR_DEFAULT_STYLE | wxTR_HIDE_ROOT); + m_graphic_pack_tree->Bind(wxEVT_TREE_SEL_CHANGED, &GraphicPacksWindow2::OnTreeSelectionChanged, this); + m_graphic_pack_tree->Bind(wxEVT_CHECKTREE_CHOICE, &GraphicPacksWindow2::OnTreeChoiceChanged, this); + //m_graphic_pack_tree->SetMinSize(wxSize(600, 400)); + sizer->Add(m_graphic_pack_tree, 1, wxEXPAND | wxALL, 5); + + left_panel->SetSizerAndFit(sizer); + } + + // right side + m_right_panel = new wxPanel(m_splitter_window, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxNO_BORDER | wxFULL_REPAINT_ON_RESIZE); + { + auto* sizer = new wxBoxSizer(wxVERTICAL); + { + m_gp_options = new wxScrolled(m_right_panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxNO_BORDER | wxVSCROLL); + m_gp_options->SetScrollRate(-1, 10); + + auto* inner_sizer = new wxBoxSizer(wxVERTICAL); + { + auto* box = new wxStaticBox(m_gp_options, wxID_ANY, _("Graphic pack")); + auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); + + m_graphic_pack_name = new wxStaticText(box, wxID_ANY, wxEmptyString); + box_sizer->Add(m_graphic_pack_name, 1, wxEXPAND | wxALL, 5); + + inner_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); + } + + { + auto* box = new wxStaticBox(m_gp_options, wxID_ANY, _("Description")); + auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); + + m_graphic_pack_description = new wxStaticText(box, wxID_ANY, wxEmptyString); + box_sizer->Add(m_graphic_pack_description, 1, wxEXPAND | wxALL, 5); + + inner_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); + } + + m_preset_sizer = new wxBoxSizer(wxVERTICAL); + inner_sizer->Add(m_preset_sizer, 0, wxEXPAND, 0); + + { + auto* box = new wxStaticBox(m_gp_options, wxID_ANY, _("Control")); + auto* box_sizer = new wxStaticBoxSizer(box, wxHORIZONTAL); + + m_reload_shaders = new wxButton(box, wxID_ANY, _("Reload edited shaders")); + m_reload_shaders->Bind(wxEVT_BUTTON, &GraphicPacksWindow2::OnReloadShaders, this); + m_reload_shaders->Disable(); + box_sizer->Add(m_reload_shaders, 0, wxEXPAND | wxALL, 5); + + inner_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); + } + + inner_sizer->Add(new wxStaticText(m_gp_options, wxID_ANY, wxEmptyString), 1, wxALL | wxEXPAND, 5); + + m_gp_options->SetSizerAndFit(inner_sizer); + m_gp_options->Hide(); + + sizer->Add(m_gp_options, 1, wxEXPAND | wxRESERVE_SPACE_EVEN_IF_HIDDEN); + } + + + sizer->Add(new wxStaticLine(m_right_panel, wxID_ANY), 0, wxLEFT|wxRIGHT | wxEXPAND, 3); + + auto* row = new wxBoxSizer(wxHORIZONTAL); + m_update_graphicPacks = new wxButton(m_right_panel, wxID_ANY, _("Download latest community graphic packs")); + m_update_graphicPacks->Bind(wxEVT_BUTTON, &GraphicPacksWindow2::OnCheckForUpdates, this); + row->Add(m_update_graphicPacks, 0, wxALL, 5); + + sizer->Add(row, 0, wxALL | wxEXPAND, 5); + + m_right_panel->SetSizerAndFit(sizer); + } + + m_splitter_window->SetMinimumPaneSize(50); + m_splitter_window->SplitVertically(left_panel, m_right_panel, (sint32)(m_ratio * m_splitter_window->GetParent()->GetSize().GetWidth())); + main_sizer->Add(m_splitter_window, 1, wxEXPAND, 5); + + + m_info_bar = new wxInfoBar(this); + m_info_bar->SetShowHideEffects(wxSHOW_EFFECT_BLEND, wxSHOW_EFFECT_BLEND); + m_info_bar->SetEffectDuration(500); + main_sizer->Add(m_info_bar, 0, wxALL | wxEXPAND, 5); + + SetSizer(main_sizer); + + FillGraphicPackList(); +} + +void GraphicPacksWindow2::SaveStateToConfig() +{ + auto& data = g_config.data(); + data.graphic_pack_entries.clear(); + + for (const auto& gp : GraphicPack2::GetGraphicPacks()) + { + auto filename = MakeRelativePath(gp->GetFilename()).lexically_normal(); + if (gp->IsEnabled()) + { + data.graphic_pack_entries.try_emplace(filename); + auto& it = data.graphic_pack_entries[filename]; + // otherwise store all selected presets + for (const auto& preset : gp->GetActivePresets()) + it.try_emplace(preset->category, preset->name); + } + else if(gp->IsDefaultEnabled()) + { + // save that its disabled + data.graphic_pack_entries.try_emplace(filename); + auto& it = data.graphic_pack_entries[filename]; + it.try_emplace("_disabled", "false"); + } + } + + g_config.Save(); +} + +GraphicPacksWindow2::~GraphicPacksWindow2() +{ + m_graphic_pack_tree->Unbind(wxEVT_CHECKTREE_CHOICE, &GraphicPacksWindow2::OnTreeSelectionChanged, this); + m_graphic_pack_tree->Unbind(wxEVT_CHECKTREE_CHOICE, &GraphicPacksWindow2::OnTreeChoiceChanged, this); + // m_active_preset->Unbind(wxEVT_CHOICE, &GraphicPacksWindow2::OnActivePresetChanged, this); + m_reload_shaders->Unbind(wxEVT_BUTTON, &GraphicPacksWindow2::OnReloadShaders, this); + + SaveStateToConfig(); +} + +wxTreeItemId GraphicPacksWindow2::FindTreeItem(const wxTreeItemId& root, const wxString& text) const +{ + wxTreeItemIdValue cookie; + for(auto item = m_graphic_pack_tree->GetFirstChild(root, cookie); item.IsOk(); item = m_graphic_pack_tree->GetNextSibling(item)) + { + if (m_graphic_pack_tree->GetItemText(item) == text) + return item; + } + + return {}; +} + +void GraphicPacksWindow2::LoadPresetSelections(const GraphicPackPtr& gp) +{ + std::vector order; + auto presets = gp->GetCategorizedPresets(order); + for(const auto& category : order) + { + const auto& entry = presets[category]; + + // test if any preset is visible and update its status + if (std::none_of(entry.cbegin(), entry.cend(), [gp](const auto& p) + { + return p->visible; + })) + { + continue; + } + + wxString label(category.empty() ? _("Active preset") : wxString(category)); + auto* box = new wxStaticBox(m_preset_sizer->GetContainingWindow(), wxID_ANY, label); + auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); + + auto* preset = new wxChoice(box, wxID_ANY); + preset->SetClientObject(new wxStringClientData(category)); + preset->Bind(wxEVT_CHOICE, &GraphicPacksWindow2::OnActivePresetChanged, this); + + std::optional active_preset; + for (auto& pentry : entry) + { + if (!pentry->visible) + continue; + + preset->AppendString(pentry->name); + if (pentry->active) + active_preset = pentry->name; + } + + if (active_preset) + preset->SetStringSelection(active_preset.value()); + else if (preset->GetCount() > 0) + preset->SetSelection(0); + + box_sizer->Add(preset, 1, wxEXPAND | wxALL, 5); + + m_preset_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); + } +} + +void GraphicPacksWindow2::OnTreeSelectionChanged(wxTreeEvent& event) +{ + wxWindowUpdateLocker lock(this); + + const auto selection = m_graphic_pack_tree->GetSelection(); + if (selection.IsOk()) + { + const auto data = dynamic_cast(m_graphic_pack_tree->GetItemData(selection)); + if (data) + { + if(!m_gp_options->IsShown()) + m_gp_options->Show(); + + const auto& gp = data->GetGraphicPack(); + if (gp != m_shown_graphic_pack) + { + m_preset_sizer->Clear(true); + m_gp_name = gp->GetName(); + m_graphic_pack_name->SetLabel(m_gp_name); + + if (gp->GetDescription().empty()) + m_gp_description = _("This graphic pack has no description"); + else + m_gp_description = gp->GetDescription(); + + m_graphic_pack_description->SetLabel(m_gp_description); + + LoadPresetSelections(gp); + + m_reload_shaders->Enable(gp->HasShaders()); + + m_shown_graphic_pack = gp; + + m_graphic_pack_name->Wrap(m_graphic_pack_name->GetParent()->GetClientSize().GetWidth() - 10); + m_graphic_pack_name->GetGrandParent()->Layout(); + + m_graphic_pack_description->Wrap(m_graphic_pack_description->GetParent()->GetClientSize().GetWidth() - 10); + m_graphic_pack_description->GetGrandParent()->Layout(); + + m_right_panel->FitInside(); + m_right_panel->Layout(); + } + return; + } + } + + m_preset_sizer->Clear(true); + m_graphic_pack_name->SetLabel(wxEmptyString); + m_graphic_pack_description->SetLabel(wxEmptyString); + + m_reload_shaders->Disable(); + + m_shown_graphic_pack.reset(); + + m_gp_options->Hide(); + m_right_panel->FitInside(); + m_right_panel->Layout(); +} + +void GraphicPacksWindow2::OnTreeChoiceChanged(wxTreeEvent& event) +{ + auto item = event.GetItem(); + if (!item.IsOk()) + return; + + const bool state = event.GetExtraLong() != 0; + + const auto data = dynamic_cast(m_graphic_pack_tree->GetItemData(item)); + if (!data) + return; + + auto& graphic_pack = data->GetGraphicPack(); + graphic_pack->SetEnabled(state); + + bool has_texture_rules = false; + if (CafeSystem::IsTitleRunning() && graphic_pack->ContainsTitleId(CafeSystem::GetForegroundTitleId())) + { + if (state) + { + GraphicPack2::ActivateGraphicPack(graphic_pack); + has_texture_rules = !graphic_pack->GetTextureRules().empty(); + + if (!has_texture_rules) + { + ReloadPack(graphic_pack); + m_graphic_pack_tree->SetItemTextColour(item, 0x009900); + } + } + else + { + has_texture_rules = !graphic_pack->GetTextureRules().empty(); + + if (!has_texture_rules) + { + DeleteShadersFromRuntimeCache(graphic_pack); + m_graphic_pack_tree->SetItemTextColour(item, *wxBLACK); + } + + GraphicPack2::DeactivateGraphicPack(graphic_pack); + } + } + + if (!m_info_bar->IsShown() && has_texture_rules) + m_info_bar->ShowMessage(_("Restart of Cemu required for changes to take effect")); + + // also change selection to activated gp + m_graphic_pack_tree->SelectItem(item); +} + +void GraphicPacksWindow2::OnActivePresetChanged(wxCommandEvent& event) +{ + if (!m_shown_graphic_pack) + return; + + const auto obj = wxDynamicCast(event.GetEventObject(), wxChoice); + wxASSERT(obj); + const auto string_data = dynamic_cast(obj->GetClientObject()); + wxASSERT(string_data); + const auto preset = obj->GetStringSelection().ToStdString(); + if(m_shown_graphic_pack->SetActivePreset(string_data->GetData().c_str().AsChar(), preset)) + { + wxWindowUpdateLocker lock(this); + m_preset_sizer->Clear(true); + LoadPresetSelections(m_shown_graphic_pack); + //m_preset_sizer->GetContainingWindow()->Layout(); + //m_right_panel->FitInside(); + m_right_panel->FitInside(); + m_right_panel->Layout(); + } + + if (m_shown_graphic_pack->GetTextureRules().empty()) + ReloadPack(m_shown_graphic_pack); + else if (!m_info_bar->IsShown()) + m_info_bar->ShowMessage(_("Restart of Cemu required for changes to take effect")); +} + +void GraphicPacksWindow2::OnReloadShaders(wxCommandEvent& event) +{ + if (m_shown_graphic_pack) + ReloadPack(m_shown_graphic_pack); +} + +void GraphicPacksWindow2::OnCheckForUpdates(wxCommandEvent& event) +{ + DownloadGraphicPacksWindow frame(this); + SaveStateToConfig(); + const int updateResult = frame.ShowModal(); + if (updateResult == wxID_OK) + { + if (!CafeSystem::IsTitleRunning()) + { + std::vector old_packs = GraphicPack2::GetGraphicPacks(); + RefreshGraphicPacks(); + FillGraphicPackList(); + + // check if enabled graphic packs are lost: + const auto& new_packs = GraphicPack2::GetGraphicPacks(); + std::stringstream str; + for(const auto& p : old_packs) + { + if (!p->IsEnabled()) + continue; + + const auto it = std::find_if(new_packs.cbegin(), new_packs.cend(), [&p](const auto& gp) + { + return gp->GetFilename() == p->GetFilename(); + }); + + if(it == new_packs.cend()) + { + str << p->GetPath() << std::endl; + } + } + + const auto packs = str.str(); + if(!packs.empty()) + { + wxMessageBox(fmt::format("{}\n \n{} \n{}", _("This update removed or renamed the following graphic packs:").ToStdString(), packs, _("You may need to set them up again.").ToStdString()), + _("Warning"), wxOK | wxCENTRE | wxICON_INFORMATION, this); + } + } + } +} + +void GraphicPacksWindow2::OnSizeChanged(wxSizeEvent& event) +{ + const auto obj = (wxSplitterWindow*)event.GetEventObject(); + wxASSERT(obj); + + const auto width = std::max(obj->GetMinimumPaneSize(), obj->GetParent()->GetClientSize().GetWidth()); + obj->SetSashPosition((sint32)(m_ratio * width)); + + if (!m_gp_name.empty()) + m_graphic_pack_name->SetLabel(m_gp_name); + + if (!m_gp_description.empty()) + m_graphic_pack_description->SetLabel(m_gp_description); + + m_graphic_pack_name->Wrap(m_graphic_pack_name->GetParent()->GetClientSize().GetWidth() - 10); + m_graphic_pack_description->Wrap(m_graphic_pack_description->GetParent()->GetClientSize().GetWidth() - 10); + + event.Skip(); +} + +void GraphicPacksWindow2::SashPositionChanged(wxEvent& event) +{ + const auto obj = (wxSplitterWindow*)event.GetEventObject(); + wxASSERT(obj); + + const auto width = std::max(obj->GetMinimumPaneSize(), obj->GetParent()->GetClientSize().GetWidth()); + m_ratio = (float)obj->GetSashPosition() / width; + event.Skip(); +} + +void GraphicPacksWindow2::OnFilterUpdate(wxEvent& event) +{ + m_filter = m_filter_text->GetValue(); + FillGraphicPackList(); + event.Skip(); +} + +void GraphicPacksWindow2::OnInstalledGamesChanged(wxCommandEvent& event) +{ + m_filter_installed_games = m_installed_games_only->GetValue(); + FillGraphicPackList(); + event.Skip(); +} + +void GraphicPacksWindow2::ReloadPack(const GraphicPackPtr& graphic_pack) const +{ + if (graphic_pack->HasShaders() || graphic_pack->HasPatches() || graphic_pack->HasCustomVSyncFrequency()) + { + if (graphic_pack->Reload()) + { + DeleteShadersFromRuntimeCache(graphic_pack); + } + } +} + +void GraphicPacksWindow2::DeleteShadersFromRuntimeCache(const GraphicPackPtr& graphic_pack) const +{ + for (const auto& shader : graphic_pack->GetCustomShaders()) + { + LatteConst::ShaderType shaderType; + if (shader.type == GraphicPack2::GP_SHADER_TYPE::VERTEX) + shaderType = LatteConst::ShaderType::Vertex; + else if (shader.type == GraphicPack2::GP_SHADER_TYPE::GEOMETRY) + shaderType = LatteConst::ShaderType::Geometry; + else if (shader.type == GraphicPack2::GP_SHADER_TYPE::PIXEL) + shaderType = LatteConst::ShaderType::Pixel; + LatteAsyncCommands_queueDeleteShader(shader.shader_base_hash, shader.shader_aux_hash, shaderType); + } +} + diff --git a/src/gui/GraphicPacksWindow2.h b/src/gui/GraphicPacksWindow2.h new file mode 100644 index 00000000..82b13c72 --- /dev/null +++ b/src/gui/GraphicPacksWindow2.h @@ -0,0 +1,73 @@ +#pragma once + +#include +#include +#include +#include + +#include "wxcomponents/checktree.h" + +#include "Cafe/GraphicPack/GraphicPack2.h" + +class wxSplitterWindow; +class wxPanel; +class wxButton; +class wxChoice; + +class GraphicPacksWindow2 : public wxDialog +{ +public: + GraphicPacksWindow2(wxWindow* parent, uint64_t title_id_filter); + ~GraphicPacksWindow2(); + + static void RefreshGraphicPacks(); + +private: + std::string m_filter; + bool m_filter_installed_games; + std::vector m_installed_games; + + void FillGraphicPackList() const; + void GetChildren(const wxTreeItemId& id, std::vector& children) const; + void ExpandChildren(const std::vector& ids, size_t& counter) const; + + wxSplitterWindow * m_splitter_window; + + wxPanel* m_right_panel; + wxScrolled* m_gp_options; + + wxCheckTree * m_graphic_pack_tree; + wxTextCtrl* m_filter_text; + wxCheckBox* m_installed_games_only; + + wxStaticText* m_graphic_pack_name, *m_graphic_pack_description; + wxBoxSizer* m_preset_sizer; + std::vector m_active_preset; + wxButton* m_reload_shaders; + wxButton* m_update_graphicPacks; + wxInfoBar* m_info_bar; + + GraphicPackPtr m_shown_graphic_pack; + std::string m_gp_name, m_gp_description; + + float m_ratio = 0.55f; + + wxTreeItemId FindTreeItem(const wxTreeItemId& root, const wxString& text) const; + void LoadPresetSelections(const GraphicPackPtr& gp); + + void OnTreeSelectionChanged(wxTreeEvent& event); + void OnTreeChoiceChanged(wxTreeEvent& event); + void OnActivePresetChanged(wxCommandEvent& event); + void OnReloadShaders(wxCommandEvent& event); + void OnCheckForUpdates(wxCommandEvent& event); + void OnSizeChanged(wxSizeEvent& event); + void SashPositionChanged(wxEvent& event); + void OnFilterUpdate(wxEvent& event); + void OnInstalledGamesChanged(wxCommandEvent& event); + + void SaveStateToConfig(); + + void ReloadPack(const GraphicPackPtr& graphic_pack) const; + void DeleteShadersFromRuntimeCache(const GraphicPackPtr& graphic_pack) const; + +}; \ No newline at end of file diff --git a/src/gui/LoggingWindow.cpp b/src/gui/LoggingWindow.cpp new file mode 100644 index 00000000..6672bec9 --- /dev/null +++ b/src/gui/LoggingWindow.cpp @@ -0,0 +1,100 @@ +#include "gui/LoggingWindow.h" + +#include "gui/helpers/wxLogEvent.h" + +#include +#include +#include + +#include "gui/helpers/wxHelpers.h" + +wxDEFINE_EVENT(EVT_LOG, wxLogEvent); + +LoggingWindow* s_instance; + +LoggingWindow::LoggingWindow(wxFrame* parent) + : wxFrame(parent, wxID_ANY, _("Logging window"), wxDefaultPosition, wxSize(800, 600), wxDEFAULT_FRAME_STYLE | wxTAB_TRAVERSAL) +{ + auto* sizer = new wxBoxSizer( wxVERTICAL ); + { + auto filter_row = new wxBoxSizer( wxHORIZONTAL ); + + filter_row->Add(new wxStaticText( this, wxID_ANY, _("Filter")), 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); + + wxString choices[] = {"Coreinit File-Access", "Coreinit Thread-Synchronization", "Coreinit Memory", "GX2", "Audio", "Input", "Socket", "Save", "H264", "Texture Cache", "OpenGL"}; + m_filter = new wxComboBox( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, std::size(choices), choices, 0 ); + m_filter->Bind(wxEVT_COMBOBOX, &LoggingWindow::OnFilterChange, this); + m_filter->Bind(wxEVT_TEXT, &LoggingWindow::OnFilterChange, this); + filter_row->Add(m_filter, 1, wxALL, 5 ); + + m_filter_message = new wxCheckBox(this, wxID_ANY, _("Filter messages")); + m_filter_message->Bind(wxEVT_CHECKBOX, &LoggingWindow::OnFilterMessageChange, this); + filter_row->Add(m_filter_message, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); + + sizer->Add( filter_row, 0, wxEXPAND, 5 ); + } + + m_log_list = new wxLogCtrl(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxScrolledWindowStyle|wxVSCROLL);//( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, NULL, wxLB_HSCROLL ); + sizer->Add( m_log_list, 1, wxALL | wxEXPAND, 5 ); + + this->SetSizer( sizer ); + this->Layout(); + + this->Bind(EVT_LOG, &LoggingWindow::OnLogMessage, this); + + std::unique_lock lock(s_mutex); + cemu_assert_debug(s_instance == nullptr); + s_instance = this; +} + +LoggingWindow::~LoggingWindow() +{ + this->Unbind(EVT_LOG, &LoggingWindow::OnLogMessage, this); + + std::unique_lock lock(s_mutex); + s_instance = nullptr; +} + +void LoggingWindow::Log(std::string_view filter, std::string_view message) +{ + std::shared_lock lock(s_mutex); + if(!s_instance) + return; + + wxLogEvent event(std::string {filter}, std::string{ message }); + s_instance->OnLogMessage(event); + + //const auto log_event = new wxLogEvent(filter, message); + //wxQueueEvent(s_instance, log_event); +} + +void LoggingWindow::Log(std::string_view filter, std::wstring_view message) +{ + std::shared_lock lock(s_mutex); + if(!s_instance) + return; + + wxLogEvent event(std::string {filter}, std::wstring{ message }); + s_instance->OnLogMessage(event); + + //const auto log_event = new wxLogEvent(filter, message); + //wxQueueEvent(s_instance, log_event); +} + +void LoggingWindow::OnLogMessage(wxLogEvent& event) +{ + m_log_list->PushEntry(event.GetFilter(), event.GetMessage()); +} + +void LoggingWindow::OnFilterChange(wxCommandEvent& event) +{ + m_log_list->SetActiveFilter(from_wxString(m_filter->GetValue())); + event.Skip(); +} + +void LoggingWindow::OnFilterMessageChange(wxCommandEvent& event) +{ + m_log_list->SetFilterMessage(m_filter_message->GetValue()); + event.Skip(); +} + diff --git a/src/gui/LoggingWindow.h b/src/gui/LoggingWindow.h new file mode 100644 index 00000000..28ca30dd --- /dev/null +++ b/src/gui/LoggingWindow.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include +#include +#include "gui/components/wxLogCtrl.h" + +class wxLogEvent; + +class LoggingWindow : public wxFrame +{ +public: + LoggingWindow(wxFrame* parent); + ~LoggingWindow(); + + static void Log(std::string_view filter, std::string_view message); + static void Log(std::string_view message) { Log("", message); } + static void Log(std::string_view filter, std::wstring_view message); + static void Log(std::wstring_view message){ Log("", message); } + + template + static void Log(std::string_view filter, std::string_view format, TArgs&&... args) + { + Log(filter, fmt::format(format, std::forward(args)...)); + } + + template + static void Log(std::string_view filter, std::wstring_view format, TArgs&&... args) + { + Log(filter, fmt::format(format, std::forward(args)...)); + } +private: + void OnLogMessage(wxLogEvent& event); + void OnFilterChange(wxCommandEvent& event); + void OnFilterMessageChange(wxCommandEvent& event); + + wxComboBox* m_filter; + wxLogCtrl* m_log_list; + wxCheckBox* m_filter_message; + + inline static std::shared_mutex s_mutex; + inline static LoggingWindow* s_instance = nullptr; +}; + diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp new file mode 100644 index 00000000..d84a12e7 --- /dev/null +++ b/src/gui/MainWindow.cpp @@ -0,0 +1,2400 @@ +#include "gui/wxgui.h" +#include "gui/MainWindow.h" +#include "gui/guiWrapper.h" + +#include + +#include "gui/GameUpdateWindow.h" +#include "gui/PadViewFrame.h" +#include "gui/windows/TextureRelationViewer/TextureRelationWindow.h" +#include "gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.h" +#include "audio/audioDebuggerWindow.h" +#include "gui/canvas/OpenGLCanvas.h" +#include "gui/canvas/VulkanCanvas.h" +#include "Cafe/OS/libs/nn_nfp/nn_nfp.h" +#include "Cafe/OS/libs/swkbd/swkbd.h" +#include "Cafe/IOSU/legacy/iosu_crypto.h" +#include "Cafe/GameProfile/GameProfile.h" +#include "gui/debugger/DebuggerWindow2.h" +#include "util/helpers/helpers.h" +#include "config/CemuConfig.h" +#include "Cemu/DiscordPresence/DiscordPresence.h" +#include "gui/GeneralSettings2.h" +#include "gui/GraphicPacksWindow2.h" +#include "gui/GameProfileWindow.h" +#include "gui/CemuApp.h" +#include "gui/CemuUpdateWindow.h" +#include "gui/helpers/wxCustomData.h" +#include "gui/LoggingWindow.h" +#include "config/ActiveSettings.h" +#include "config/LaunchSettings.h" + +#include "Cafe/Filesystem/FST/FST.h" + +#include "gui/TitleManager.h" + +#include "Cafe/CafeSystem.h" +#include "Cafe/GraphicPack/GraphicPack.h" +#include "Cafe/TitleList/GameInfo.h" + +#ifdef _WIN32 +#include +#endif + +#include +#include "util/helpers/SystemException.h" +#include "gui/DownloadGraphicPacksWindow.h" +#include "gui/GettingStartedDialog.h" +#include "gui/helpers/wxHelpers.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/VsyncDriver.h" +#include "gui/input/InputSettings2.h" +#include "input/InputManager.h" + +#if BOOST_OS_WINDOWS > 0 +#define exit(__c) ExitProcess(__c) +#endif + +#if BOOST_OS_LINUX > 0 +#include "resource/linux/resources.h" +#endif + +#include "Cafe/TitleList/TitleInfo.h" +#include "Cafe/TitleList/TitleList.h" +#include "wxHelper.h" + +extern WindowInfo g_window_info; +extern std::shared_mutex g_mutex; + +bool IsCemuhookLoaded(); + +wxDEFINE_EVENT(wxEVT_SET_WINDOW_TITLE, wxCommandEvent); + +enum +{ + // note - Cemuhook mirrors these ids, be careful about changing them + + // ui elements + MAINFRAME_GAMELIST_ID = 20000, //wxID_HIGHEST + 1, + // file + MAINFRAME_MENU_ID_FILE_LOAD = 20100, + MAINFRAME_MENU_ID_FILE_INSTALL_UPDATE, + MAINFRAME_MENU_ID_FILE_EXIT, + MAINFRAME_MENU_ID_FILE_END_EMULATION, + MAINFRAME_MENU_ID_FILE_RECENT_0, + MAINFRAME_MENU_ID_FILE_RECENT_LAST = MAINFRAME_MENU_ID_FILE_RECENT_0 + 15, + // options + MAINFRAME_MENU_ID_OPTIONS_FULLSCREEN = 20200, + MAINFRAME_MENU_ID_OPTIONS_VSYNC, + MAINFRAME_MENU_ID_OPTIONS_SECOND_WINDOW_PADVIEW, + MAINFRAME_MENU_ID_OPTIONS_GRAPHIC, + MAINFRAME_MENU_ID_OPTIONS_GRAPHIC_PACKS2, + MAINFRAME_MENU_ID_OPTIONS_GENERAL, + MAINFRAME_MENU_ID_OPTIONS_GENERAL2, + MAINFRAME_MENU_ID_OPTIONS_AUDIO, + MAINFRAME_MENU_ID_OPTIONS_INPUT, + // options -> experimental + MAINFRAME_MENU_ID_EXPERIMENTAL_BOTW_WORKAROUND = 20300, + MAINFRAME_MENU_ID_EXPERIMENTAL_SYNC_TO_GX2DRAWDONE, + MAINFRAME_MENU_ID_EXPERIMENTAL_DISABLE_PRECOMPILED, + // options -> account + MAINFRAME_MENU_ID_OPTIONS_ACCOUNT_1 = 20350, + MAINFRAME_MENU_ID_OPTIONS_ACCOUNT_12 = 20350 + 11, + + // options -> system language + MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_JAPANESE = 20500, + MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_ENGLISH, + MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_FRENCH, + MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_GERMAN, + MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_ITALIAN, + MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_SPANISH, + MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_CHINESE, + MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_KOREAN, + MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_DUTCH, + MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_PORTUGUESE, + MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_RUSSIAN, + MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_TAIWANESE, + // tools + MAINFRAME_MENU_ID_TOOLS_MEMORY_SEARCHER = 20600, + MAINFRAME_MENU_ID_TOOLS_TITLE_MANAGER, + MAINFRAME_MENU_ID_TOOLS_DOWNLOAD_MANAGER, + // cpu + // cpu->timer speed + MAINFRAME_MENU_ID_TIMER_SPEED_1X = 20700, + MAINFRAME_MENU_ID_TIMER_SPEED_2X = 20701, + MAINFRAME_MENU_ID_TIMER_SPEED_4X = 20702, + MAINFRAME_MENU_ID_TIMER_SPEED_8X = 20703, + MAINFRAME_MENU_ID_TIMER_SPEED_05X = 20704, + MAINFRAME_MENU_ID_TIMER_SPEED_025X = 20705, + MAINFRAME_MENU_ID_TIMER_SPEED_0125X = 20706, + + // nfc->Touch NFC file + MAINFRAME_MENU_ID_NFC_TOUCH_NFC_FILE = 21000, + MAINFRAME_MENU_ID_NFC_RECENT_0, + MAINFRAME_MENU_ID_NFC_RECENT_LAST = MAINFRAME_MENU_ID_NFC_RECENT_0 + 15, + // debug + MAINFRAME_MENU_ID_DEBUG_RESERVED = 21100, + MAINFRAME_MENU_ID_DEBUG_RENDER_UPSIDE_DOWN, + MAINFRAME_MENU_ID_DEBUG_VIEW_LOGGING_WINDOW, + MAINFRAME_MENU_ID_DEBUG_VIEW_PPC_THREADS, + MAINFRAME_MENU_ID_DEBUG_VIEW_PPC_DEBUGGER, + MAINFRAME_MENU_ID_DEBUG_VIEW_AUDIO_DEBUGGER, + MAINFRAME_MENU_ID_DEBUG_VIEW_TEXTURE_RELATIONS, + MAINFRAME_MENU_ID_DEBUG_SHOW_FRAME_PROFILER, + MAINFRAME_MENU_ID_DEBUG_AUDIO_AUX_ONLY, + MAINFRAME_MENU_ID_DEBUG_VK_ACCURATE_BARRIERS, + + // debug->logging + MAINFRAME_MENU_ID_DEBUG_LOGGING_DISABLE_ALL = 21500, + MAINFRAME_MENU_ID_DEBUG_LOGGING0, + MAINFRAME_MENU_ID_DEBUG_LOGGING20 = MAINFRAME_MENU_ID_DEBUG_LOGGING0 + 20, + MAINFRAME_MENU_ID_DEBUG_ADVANCED_PPC_INFO, + // debug->dump + MAINFRAME_MENU_ID_DEBUG_DUMP_TEXTURES = 21600, + MAINFRAME_MENU_ID_DEBUG_DUMP_SHADERS, + MAINFRAME_MENU_ID_DEBUG_DUMP_RAM, + MAINFRAME_MENU_ID_DEBUG_DUMP_FST, + MAINFRAME_MENU_ID_DEBUG_DUMP_CURL_REQUESTS, + // help + MAINFRAME_MENU_ID_HELP_WEB = 21700, + MAINFRAME_MENU_ID_HELP_ABOUT, + MAINFRAME_MENU_ID_HELP_UPDATE, + MAINFRAME_MENU_ID_HELP_GETTING_STARTED, + + // custom + MAINFRAME_ID_TIMER1 = 21800, +}; + +wxDEFINE_EVENT(wxEVT_REQUEST_GAMELIST_REFRESH, wxCommandEvent); +wxDEFINE_EVENT(wxEVT_LAUNCH_GAME, wxLaunchGameEvent); + +wxBEGIN_EVENT_TABLE(MainWindow, wxFrame) +EVT_TIMER(MAINFRAME_ID_TIMER1, MainWindow::OnTimer) +EVT_CLOSE(MainWindow::OnClose) +EVT_SIZE(MainWindow::OnSizeEvent) +EVT_MOVE(MainWindow::OnMove) +// file menu +EVT_MENU(MAINFRAME_MENU_ID_FILE_LOAD, MainWindow::OnFileMenu) +EVT_MENU(MAINFRAME_MENU_ID_FILE_INSTALL_UPDATE, MainWindow::OnInstallUpdate) +EVT_MENU(MAINFRAME_MENU_ID_FILE_EXIT, MainWindow::OnFileExit) +EVT_MENU(MAINFRAME_MENU_ID_FILE_END_EMULATION, MainWindow::OnFileMenu) +EVT_MENU_RANGE(MAINFRAME_MENU_ID_FILE_RECENT_0 + 0, MAINFRAME_MENU_ID_FILE_RECENT_LAST, MainWindow::OnFileMenu) +// options -> region menu +EVT_MENU_RANGE(MAINFRAME_MENU_ID_OPTIONS_ACCOUNT_1, MAINFRAME_MENU_ID_OPTIONS_ACCOUNT_12, MainWindow::OnAccountSelect) +EVT_MENU_RANGE(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_JAPANESE, MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_TAIWANESE, MainWindow::OnConsoleLanguage) +// options menu +EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_FULLSCREEN, MainWindow::OnOptionsInput) +EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_SECOND_WINDOW_PADVIEW, MainWindow::OnOptionsInput) +EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_GRAPHIC, MainWindow::OnOptionsInput) +EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_GRAPHIC_PACKS2, MainWindow::OnOptionsInput) +EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_GENERAL, MainWindow::OnOptionsInput) +EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_GENERAL2, MainWindow::OnOptionsInput) +EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_AUDIO, MainWindow::OnOptionsInput) +EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_INPUT, MainWindow::OnOptionsInput) +// tools menu +EVT_MENU(MAINFRAME_MENU_ID_TOOLS_MEMORY_SEARCHER, MainWindow::OnToolsInput) +EVT_MENU(MAINFRAME_MENU_ID_TOOLS_TITLE_MANAGER, MainWindow::OnToolsInput) +EVT_MENU(MAINFRAME_MENU_ID_TOOLS_DOWNLOAD_MANAGER, MainWindow::OnToolsInput) +//// cpu menu +EVT_MENU(MAINFRAME_MENU_ID_TIMER_SPEED_8X, MainWindow::OnDebugSetting) +EVT_MENU(MAINFRAME_MENU_ID_TIMER_SPEED_4X, MainWindow::OnDebugSetting) +EVT_MENU(MAINFRAME_MENU_ID_TIMER_SPEED_2X, MainWindow::OnDebugSetting) +EVT_MENU(MAINFRAME_MENU_ID_TIMER_SPEED_1X, MainWindow::OnDebugSetting) +EVT_MENU(MAINFRAME_MENU_ID_TIMER_SPEED_05X, MainWindow::OnDebugSetting) +EVT_MENU(MAINFRAME_MENU_ID_TIMER_SPEED_025X, MainWindow::OnDebugSetting) +EVT_MENU(MAINFRAME_MENU_ID_TIMER_SPEED_0125X, MainWindow::OnDebugSetting) +// nfc menu +EVT_MENU(MAINFRAME_MENU_ID_NFC_TOUCH_NFC_FILE, MainWindow::OnNFCMenu) +EVT_MENU_RANGE(MAINFRAME_MENU_ID_NFC_RECENT_0 + 0, MAINFRAME_MENU_ID_NFC_RECENT_LAST, MainWindow::OnNFCMenu) +// debug -> logging menu +EVT_MENU_RANGE(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + 0, MAINFRAME_MENU_ID_DEBUG_LOGGING0 + 19, MainWindow::OnDebugLoggingToggleFlagGeneric) +EVT_MENU(MAINFRAME_MENU_ID_DEBUG_ADVANCED_PPC_INFO, MainWindow::OnPPCInfoToggle) +// debug -> dump menu +EVT_MENU(MAINFRAME_MENU_ID_DEBUG_DUMP_TEXTURES, MainWindow::OnDebugDumpUsedTextures) +EVT_MENU(MAINFRAME_MENU_ID_DEBUG_DUMP_SHADERS, MainWindow::OnDebugDumpUsedShaders) +EVT_MENU(MAINFRAME_MENU_ID_DEBUG_DUMP_CURL_REQUESTS, MainWindow::OnDebugSetting) +// debug -> Other options +EVT_MENU(MAINFRAME_MENU_ID_DEBUG_RENDER_UPSIDE_DOWN, MainWindow::OnDebugSetting) +EVT_MENU(MAINFRAME_MENU_ID_DEBUG_AUDIO_AUX_ONLY, MainWindow::OnDebugSetting) +EVT_MENU(MAINFRAME_MENU_ID_DEBUG_VK_ACCURATE_BARRIERS, MainWindow::OnDebugSetting) +EVT_MENU(MAINFRAME_MENU_ID_DEBUG_DUMP_RAM, MainWindow::OnDebugSetting) +EVT_MENU(MAINFRAME_MENU_ID_DEBUG_DUMP_FST, MainWindow::OnDebugSetting) +// debug -> View ... +EVT_MENU(MAINFRAME_MENU_ID_DEBUG_VIEW_LOGGING_WINDOW, MainWindow::OnLoggingWindow) +EVT_MENU(MAINFRAME_MENU_ID_DEBUG_VIEW_PPC_THREADS, MainWindow::OnDebugViewPPCThreads) +EVT_MENU(MAINFRAME_MENU_ID_DEBUG_VIEW_PPC_DEBUGGER, MainWindow::OnDebugViewPPCDebugger) +EVT_MENU(MAINFRAME_MENU_ID_DEBUG_VIEW_AUDIO_DEBUGGER, MainWindow::OnDebugViewAudioDebugger) +EVT_MENU(MAINFRAME_MENU_ID_DEBUG_VIEW_TEXTURE_RELATIONS, MainWindow::OnDebugViewTextureRelations) +EVT_MENU(MAINFRAME_MENU_ID_DEBUG_SHOW_FRAME_PROFILER, MainWindow::OnDebugSetting) +// help menu +EVT_MENU(MAINFRAME_MENU_ID_HELP_WEB, MainWindow::OnHelpVistWebpage) +EVT_MENU(MAINFRAME_MENU_ID_HELP_ABOUT, MainWindow::OnHelpAbout) +EVT_MENU(MAINFRAME_MENU_ID_HELP_UPDATE, MainWindow::OnHelpUpdate) +EVT_MENU(MAINFRAME_MENU_ID_HELP_GETTING_STARTED, MainWindow::OnHelpGettingStarted) +// misc +EVT_COMMAND(wxID_ANY, wxEVT_REQUEST_GAMELIST_REFRESH, MainWindow::OnRequestGameListRefresh) + +EVT_COMMAND(wxID_ANY, wxEVT_GAMELIST_BEGIN_UPDATE, MainWindow::OnGameListBeginUpdate) +EVT_COMMAND(wxID_ANY, wxEVT_GAMELIST_END_UPDATE, MainWindow::OnGameListEndUpdate) +EVT_COMMAND(wxID_ANY, wxEVT_ACCOUNTLIST_REFRESH, MainWindow::OnAccountListRefresh) +EVT_COMMAND(wxID_ANY, wxEVT_SET_WINDOW_TITLE, MainWindow::OnSetWindowTitle) + +wxEND_EVENT_TABLE() + +class wxGameDropTarget : public wxFileDropTarget +{ +public: + wxGameDropTarget(MainWindow* window) : m_window(window) {} + bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& filenames) override + { + if(!m_window->IsGameLaunched() && filenames.GetCount() == 1) + return m_window->FileLoad(filenames[0].wc_str(), wxLaunchGameEvent::INITIATED_BY::DRAG_AND_DROP); + + return false; + } + +private: + MainWindow* m_window; +}; + +class wxAmiiboDropTarget : public wxFileDropTarget +{ +public: + wxAmiiboDropTarget(MainWindow* window) : m_window(window) {} + bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& filenames) override + { + if (!m_window->IsGameLaunched() || filenames.GetCount() != 1) + return false; + + uint32 nfcError; + if (nnNfp_touchNfcTagFromFile(filenames[0].wc_str(), &nfcError)) + { + GetConfig().AddRecentNfcFile((wchar_t*)filenames[0].wc_str()); + m_window->UpdateNFCMenu(); + return true; + } + else + { + if (nfcError == NFC_ERROR_NO_ACCESS) + wxMessageBox(_("Cannot open file"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR); + else if (nfcError == NFC_ERROR_INVALID_FILE_FORMAT) + wxMessageBox(_("Not a valid NFC NTAG215 file"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR); + return false; + } + } + +private: + MainWindow* m_window; +}; + +MainWindow::MainWindow() + : wxFrame(nullptr, -1, GetInitialWindowTitle(), wxDefaultPosition, wxSize(1280, 720), wxMINIMIZE_BOX | wxMAXIMIZE_BOX | wxSYSTEM_MENU | wxCAPTION | wxCLOSE_BOX | wxCLIP_CHILDREN | wxRESIZE_BORDER) +{ + gui_initHandleContextFromWxWidgetsWindow(g_window_info.window_main, this); + g_mainFrame = this; + + RecreateMenu(); + SetClientSize(1280, 720); + SetIcon(wxICON(M_WND_ICON128)); + +#if BOOST_OS_WINDOWS > 0 + HICON hWindowIcon = (HICON)LoadImageA(NULL, "M_WND_ICON16", IMAGE_ICON, 16, 16, LR_LOADFROMFILE); + SendMessage(this->GetHWND(), WM_SETICON, ICON_SMALL, (LPARAM)hWindowIcon); +#endif + + auto* main_sizer = new wxBoxSizer(wxVERTICAL); + if (!LaunchSettings::GetLoadFile().has_value()) + { + { + m_main_panel = new wxPanel(this); + auto* sizer = new wxBoxSizer(wxVERTICAL); + // game list + m_game_list = new wxGameList(m_main_panel, MAINFRAME_GAMELIST_ID); + m_game_list->Bind(wxEVT_OPEN_SETTINGS, [this](auto&) {OpenSettings(); }); + m_game_list->SetDropTarget(new wxGameDropTarget(this)); + sizer->Add(m_game_list, 1, wxEXPAND); + + // info, warning bar + m_info_bar = new wxInfoBar(m_main_panel); + m_info_bar->SetShowHideEffects(wxSHOW_EFFECT_BLEND, wxSHOW_EFFECT_BLEND); + m_info_bar->SetEffectDuration(500); + sizer->Add(m_info_bar, 0, wxALL | wxEXPAND, 5); + + m_main_panel->SetSizer(sizer); + main_sizer->Add(m_main_panel, 1, wxEXPAND, 0, nullptr); + } + } + else + { + // launching game via -g option. Dont setup or load game list + m_game_list = nullptr; + m_info_bar = nullptr; + } + SetSizer(main_sizer); + + m_last_mouse_move_time = std::chrono::steady_clock::now(); + + m_timer = new wxTimer(this, MAINFRAME_ID_TIMER1); + m_timer->Start(500); + + LoadSettings(); + +#ifdef ENABLE_DISCORD_RPC + auto& config = GetConfig(); + if (config.use_discord_presence) + m_discord = std::make_unique(); +#endif + + Bind(wxEVT_OPEN_GRAPHIC_PACK, &MainWindow::OnGraphicWindowOpen, this); + Bind(wxEVT_LAUNCH_GAME, &MainWindow::OnLaunchFromFile, this); + + if (fs::exists(ActiveSettings::GetPath("dbghelp.dll")) && !fs::exists(ActiveSettings::GetPath("cemuhook.dll"))) + { + m_statusBar = CreateStatusBar(1); + wxStaticText* statusBarText = new wxStaticText(m_statusBar, wxID_ANY, wxT("The installed version of Cemuhook is not compatible. To get the latest version visit: ")); + wxHyperlinkCtrl* cemuhookUrl = new wxHyperlinkCtrl(m_statusBar, wxID_ANY, "https://cemuhook.sshnuke.net/", "https://cemuhook.sshnuke.net/"); + // align elements + auto textSize = statusBarText->GetSize(); + statusBarText->SetPosition(wxPoint(6, (m_statusBar->GetSize().GetHeight() - textSize.GetHeight()) / 2)); + if(cemuhookUrl) + cemuhookUrl->SetPosition(wxPoint(textSize.GetWidth() + 12, (m_statusBar->GetSize().GetHeight() - textSize.GetHeight()) / 2)); + } + + if (LaunchSettings::GetLoadFile().has_value()) + { + MainWindow::RequestLaunchGame(LaunchSettings::GetLoadFile().value(), wxLaunchGameEvent::INITIATED_BY::COMMAND_LINE); + } +} + +MainWindow::~MainWindow() +{ + if (m_padView) + { + //delete m_padView; + m_padView->Destroy(); + m_padView = nullptr; + } + + m_timer->Stop(); + + std::unique_lock lock(g_mutex); + g_mainFrame = nullptr; +} + +wxString MainWindow::GetInitialWindowTitle() +{ + return wxStringFormat2(EMULATOR_NAME" {}.{}{}", EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_SUFFIX); +} + +void MainWindow::ShowGettingStartedDialog() +{ + GettingStartedDialog dia(this); + dia.ShowModal(); + if (dia.HasGamePathChanged() || dia.HasMLCChanged()) + m_game_list->ReloadGameEntries(); + + TogglePadView(); + + auto& config = GetConfig(); + m_padViewMenuItem->Check(config.pad_open.GetValue()); + m_fullscreenMenuItem->Check(config.fullscreen.GetValue()); +} + +namespace coreinit +{ + void OSSchedulerEnd(); +}; + +void MainWindow::OnClose(wxCloseEvent& event) +{ + if(m_game_list) + m_game_list->OnClose(event); + + if (!IsMaximized() && !gui_isFullScreen()) + m_restored_size = GetSize(); + + SaveSettings(); + m_timer->Stop(); + + event.Skip(); + + CafeSystem::ShutdownTitle(); + DestroyCanvas(); +} + +bool MainWindow::InstallUpdate(const fs::path& metaFilePath) +{ + try + { + GameUpdateWindow frame(*this, metaFilePath); + const int updateResult = frame.ShowModal(); + + if (updateResult == wxID_OK) + { + CafeTitleList::AddTitleFromPath(frame.GetTargetPath()); // this will also send a notification to the game list which will update the entry + wxMessageBox(_("Title installed!"), _("Success")); + return true; + } + else + { + if (frame.GetExceptionMessage().empty()) + wxMessageBox(_("Title installation has been canceled!")); + else + { + throw std::runtime_error(frame.GetExceptionMessage()); + } + } + } + catch(const AbortException&) + { + // ignored + } + catch (const std::exception& ex) + { + wxMessageBox(ex.what(), _("Update error")); + } + return false; +} + +bool MainWindow::FileLoad(std::wstring fileName, wxLaunchGameEvent::INITIATED_BY initiatedBy) +{ + const fs::path launchPath = fs::path(fileName); + TitleInfo launchTitle{ launchPath }; + if (launchTitle.IsValid()) + { + // the title might not be in the TitleList, so we add it as a temporary entry + CafeTitleList::AddTitleFromPath(launchPath); + // title is valid, launch from TitleId + TitleId baseTitleId; + if (!CafeTitleList::FindBaseTitleId(launchTitle.GetAppTitleId(), baseTitleId)) + { + wxString t = _("Unable to launch game because the base files were not found."); + wxMessageBox(t, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); + return false; + } + CafeSystem::STATUS_CODE r = CafeSystem::PrepareForegroundTitle(baseTitleId); + if (r == CafeSystem::STATUS_CODE::INVALID_RPX) + { + cemu_assert_debug(false); + return false; + } + else if (r == CafeSystem::STATUS_CODE::UNABLE_TO_MOUNT) + { + wxString t = _("Unable to mount title.\nMake sure the configured game paths are still valid and refresh the game list.\n\nFile which failed to load:\n"); + t.append(fileName); + wxMessageBox(t, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); + return false; + } + else if (r != CafeSystem::STATUS_CODE::SUCCESS) + { + wxString t = _("Failed to launch game."); + t.append(fileName); + wxMessageBox(t, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); + return false; + } + } + else //if (launchTitle.GetFormat() == TitleInfo::TitleDataFormat::INVALID_STRUCTURE ) + { + // title is invalid, if its an RPX/ELF we can launch it directly + // otherwise its an error + CafeTitleFileType fileType = DetermineCafeSystemFileType(launchPath); + if (fileType == CafeTitleFileType::RPX || fileType == CafeTitleFileType::ELF) + { + CafeSystem::STATUS_CODE r = CafeSystem::PrepareForegroundTitleFromStandaloneRPX(launchPath); + if (r != CafeSystem::STATUS_CODE::SUCCESS) + { + cemu_assert_debug(false); // todo + wxString t = _("Failed to launch executable. Path: "); + t.append(fileName); + wxMessageBox(t, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); + return false; + } + } + else if (initiatedBy == wxLaunchGameEvent::INITIATED_BY::GAME_LIST) + { + wxString t = _("Unable to launch title.\nMake sure the configured game paths are still valid and refresh the game list.\n\nPath which failed to load:\n"); + t.append(fileName); + wxMessageBox(t, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); + return false; + } + else if (initiatedBy == wxLaunchGameEvent::INITIATED_BY::MENU || + initiatedBy == wxLaunchGameEvent::INITIATED_BY::COMMAND_LINE) + { + wxString t = _("Unable to launch game\nPath:\n"); + t.append(fileName); + wxMessageBox(t, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); + return false; + } + else + { + wxString t = _("Unable to launch game\nPath:\n"); + t.append(fileName); + wxMessageBox(t, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); + return false; + } + } + + if(launchTitle.IsValid()) + GetConfig().AddRecentlyLaunchedFile(launchTitle.GetPath().generic_wstring()); + else + GetConfig().AddRecentlyLaunchedFile(fileName); + + wxWindowUpdateLocker lock(this); + + auto* main_sizer = GetSizer(); + // remove old gamelist panel + if (m_main_panel) + { + m_main_panel->Hide(); + main_sizer->Detach(m_main_panel); + } + + // create render canvas rendering + m_game_panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxNO_BORDER | wxWANTS_CHARS); + auto* sizer = new wxBoxSizer(wxVERTICAL); + + // shouldn't be needed, but who knows + m_game_panel->Bind(wxEVT_KEY_UP, &MainWindow::OnKeyUp, this); + m_game_panel->Bind(wxEVT_CHAR, &MainWindow::OnChar, this); + + m_game_panel->SetSizer(sizer); + main_sizer->Add(m_game_panel, 1, wxEXPAND, 0, nullptr); + + m_game_launched = true; + m_loadMenuItem->Enable(false); + m_installUpdateMenuItem->Enable(false); + m_memorySearcherMenuItem->Enable(true); + + if (m_game_list) + { + delete m_game_list; + m_game_list = nullptr; + } + + const auto game_name = GetGameName(fileName); + m_launched_game_name = boost::nowide::narrow(game_name); + #ifdef ENABLE_DISCORD_RPC + if (m_discord) + m_discord->UpdatePresence(DiscordPresence::Playing, m_launched_game_name); + #endif + + if (ActiveSettings::FullscreenEnabled()) + SetFullScreen(true); + + CreateCanvas(); + CafeSystem::LaunchForegroundTitle(); + RecreateMenu(); + + return true; +} + +void MainWindow::OnLaunchFromFile(wxLaunchGameEvent& event) +{ + if (event.GetPath().empty()) + return; + FileLoad(event.GetPath().generic_wstring(), event.GetInitiatedBy()); +} + +void MainWindow::OnFileMenu(wxCommandEvent& event) +{ + const auto menuId = event.GetId(); + if (menuId == MAINFRAME_MENU_ID_FILE_LOAD) + { + const auto wildcard = wxStringFormat2( + "{}|*.wud;*.wux;*.wua;*.iso;*.rpx;*.elf" + "|{}|*.wud;*.wux;*.iso" + "|{}|*.wua" + "|{}|*.rpx;*.elf" + "|{}|*", + _("All Wii U files (wud, wux, wua, iso, rpx, elf)"), + _("Wii U image (wud, wux, iso, wad)"), + _("Wii U archive (wua)"), + _("Wii U executable (rpx, elf)"), + _("All files (*.*)") + ); + + wxFileDialog openFileDialog(this, _("Open file to launch"), wxEmptyString, wxEmptyString, wildcard, wxFD_OPEN | wxFD_FILE_MUST_EXIST); + + if (openFileDialog.ShowModal() == wxID_CANCEL) + return; + + const wxString wxStrFilePath = openFileDialog.GetPath(); + FileLoad(wxStrFilePath.wc_str(), wxLaunchGameEvent::INITIATED_BY::MENU); + } + else if (menuId >= MAINFRAME_MENU_ID_FILE_RECENT_0 && menuId <= MAINFRAME_MENU_ID_FILE_RECENT_LAST) + { + const auto& config = GetConfig(); + const size_t index = menuId - MAINFRAME_MENU_ID_FILE_RECENT_0; + if (index < config.recent_launch_files.size()) + { + const auto& path = config.recent_launch_files[index]; + if (!path.empty()) + FileLoad(path, wxLaunchGameEvent::INITIATED_BY::MENU); + } + } + else if (menuId == MAINFRAME_MENU_ID_FILE_END_EMULATION) + { + CafeSystem::ShutdownTitle(); + DestroyCanvas(); + m_game_launched = false; + RecreateMenu(); + } +} + +void MainWindow::OnInstallUpdate(wxCommandEvent& event) +{ + while (true) + { + wxDirDialog openDirDialog(nullptr, _("Select folder of title to install"), "", wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST, wxDefaultPosition, wxDefaultSize, _("Select the folder that stores your update, DLC or base game files")); + int modalChoice = openDirDialog.ShowModal(); + if (modalChoice == wxID_CANCEL) + break; + if (modalChoice == wxID_OK) + { + #if BOOST_OS_LINUX + fs::path dirPath((const char*)(openDirDialog.GetPath().fn_str())); + #else + fs::path dirPath(openDirDialog.GetPath().fn_str()); + #endif + + if ((dirPath.filename() == "code" || dirPath.filename() == "content" || dirPath.filename() == "meta") && dirPath.has_parent_path()) + { + if (!fs::exists(dirPath.parent_path() / "code") || !fs::exists(dirPath.parent_path() / "content") || !fs::exists(dirPath.parent_path() / "meta")) + { + wxMessageBox(wxStringFormat2(_("The (parent) folder of the title you selected is missing at least one of the required subfolders (\"code\", \"content\" and \"meta\")\nMake sure that the files are complete."), dirPath.filename().string())); + continue; + } + else + dirPath = dirPath.parent_path(); + } + + if (!fs::exists(dirPath)) + wxMessageBox(_("The folder you have selected cannot be found on your system.")); + else if (!fs::exists(dirPath / "meta" / "meta.xml")) + wxMessageBox(_("Unable to find the /meta/meta.xml file inside the selected folder.")); + else + { + InstallUpdate(dirPath); + return; + } + } + } +} + +void MainWindow::OnNFCMenu(wxCommandEvent& event) +{ + if (event.GetId() == MAINFRAME_MENU_ID_NFC_TOUCH_NFC_FILE) + { + wxFileDialog + openFileDialog(this, _("Open file to load"), "", "", + "All NFC files (bin, dat, nfc)|*.bin;*.dat;*.nfc|All files (*.*)|*", wxFD_OPEN | wxFD_FILE_MUST_EXIST); // TRANSLATE + if (openFileDialog.ShowModal() == wxID_CANCEL) + return; + wxString wxStrFilePath = openFileDialog.GetPath(); + uint32 nfcError; + if (nnNfp_touchNfcTagFromFile(wxStrFilePath.wc_str(), &nfcError) == false) + { + if (nfcError == NFC_ERROR_NO_ACCESS) + wxMessageBox(_("Cannot open file")); + else if (nfcError == NFC_ERROR_INVALID_FILE_FORMAT) + wxMessageBox(_("Not a valid NFC NTAG215 file")); + } + else + { + GetConfig().AddRecentNfcFile((wchar_t*)wxStrFilePath.wc_str()); + UpdateNFCMenu(); + } + } + else if (event.GetId() >= MAINFRAME_MENU_ID_NFC_RECENT_0 && event.GetId() <= MAINFRAME_MENU_ID_NFC_RECENT_LAST) + { + const size_t index = event.GetId() - MAINFRAME_MENU_ID_NFC_RECENT_0; + auto& config = GetConfig(); + if (index < config.recent_nfc_files.size()) + { + const auto& path = config.recent_nfc_files[index]; + if (!path.empty()) + { + uint32 nfcError = 0; + if (nnNfp_touchNfcTagFromFile(path.c_str(), &nfcError) == false) + { + if (nfcError == NFC_ERROR_NO_ACCESS) + wxMessageBox(_("Cannot open file")); + else if (nfcError == NFC_ERROR_INVALID_FILE_FORMAT) + wxMessageBox(_("Not a valid NFC NTAG215 file")); + } + else + { + config.AddRecentNfcFile(path); + UpdateNFCMenu(); + } + } + } + } +} + +void MainWindow::OnFileExit(wxCommandEvent& event) +{ + // todo: Safely clean up everything + SaveSettings(); + exit(0); +} + +void MainWindow::TogglePadView() +{ + const auto& config = GetConfig(); + if (config.pad_open) + { + if (m_padView) + return; + + m_padView = new PadViewFrame(this); + + m_padView->Bind(wxEVT_CLOSE_WINDOW, &MainWindow::OnPadClose, this); + + m_padView->Show(true); + m_padView->Initialize(); + if (m_game_launched) + m_padView->InitializeRenderCanvas(); + } + else if (m_padView) + { + m_padView->Destroy(); + m_padView = nullptr; + } +} + +#if BOOST_OS_WINDOWS + +#ifndef DBT_DEVNODES_CHANGED +#define DBT_DEVNODES_CHANGED (0x0007) +#endif +WXLRESULT MainWindow::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) +{ + if (nMsg == WM_DEVICECHANGE) + { + if (wParam == DBT_DEVNODES_CHANGED) + { + InputManager::instance().on_device_changed(); + } + } + + return wxFrame::MSWWindowProc(nMsg, wParam, lParam); +} +#endif + +void MainWindow::OpenSettings() +{ + auto& config = GetConfig(); + const auto language = config.language; + + GeneralSettings2 frame(this, m_game_launched); + frame.ShowModal(); + const bool paths_modified = frame.ShouldReloadGamelist(); + const bool mlc_modified = frame.MLCModified(); + frame.Destroy(); + + if (paths_modified) + m_game_list->ReloadGameEntries(false); + else + SaveSettings(); + + #ifdef ENABLE_DISCORD_RPC + if (config.use_discord_presence) + { + if (!m_discord) + { + m_discord = std::make_unique(); + if (!m_launched_game_name.empty()) + m_discord->UpdatePresence(DiscordPresence::Playing, m_launched_game_name); + } + } + else + m_discord.reset(); + #endif + + if(config.check_update && !m_game_launched) + m_update_available = CemuUpdateWindow::IsUpdateAvailable(); + + if (mlc_modified) + RecreateMenu(); + + if (!config.fullscreen_menubar && IsFullScreen()) + SetMenuVisible(false); + + if (language != config.language) + wxMessageBox(_("Cemu must be restarted to apply the selected UI language."), _("Information"), wxOK | wxCENTRE, this); // TODO: change language to newly selected one +} + +void MainWindow::OnOptionsInput(wxCommandEvent& event) +{ + switch (event.GetId()) + { + case MAINFRAME_MENU_ID_OPTIONS_FULLSCREEN: + { + const bool state = m_fullscreenMenuItem->IsChecked(); + SetFullScreen(state); + break; + } + case MAINFRAME_MENU_ID_OPTIONS_SECOND_WINDOW_PADVIEW: + { + GetConfig().pad_open = !GetConfig().pad_open; + g_config.Save(); + + TogglePadView(); + break; + } + case MAINFRAME_MENU_ID_OPTIONS_GRAPHIC_PACKS2: + { + if (m_graphic_pack_window) + return; + + uint64 titleId = 0; + if (CafeSystem::IsTitleRunning()) + titleId = CafeSystem::GetForegroundTitleId(); + + m_graphic_pack_window = new GraphicPacksWindow2(this, titleId); + m_graphic_pack_window->Bind(wxEVT_CLOSE_WINDOW, &MainWindow::OnGraphicWindowClose, this); + m_graphic_pack_window->Show(true); + + break; + } + + case MAINFRAME_MENU_ID_OPTIONS_GENERAL2: + { + OpenSettings(); + break; + } + case MAINFRAME_MENU_ID_OPTIONS_INPUT: + { + auto* frame = new InputSettings2(this); + frame->ShowModal(); + frame->Destroy(); + break; + } + + } +} + +void MainWindow::OnAccountSelect(wxCommandEvent& event) +{ + const int index = event.GetId() - MAINFRAME_MENU_ID_OPTIONS_ACCOUNT_1; + const auto& accounts = Account::GetAccounts(); + wxASSERT(index >= 0 && index < (int)accounts.size()); + auto& config = GetConfig(); + config.account.m_persistent_id = accounts[index].GetPersistentId(); + // config.account.online_enabled.value = false; // reset online for safety + g_config.Save(); +} + +//void MainWindow::OnConsoleRegion(wxCommandEvent& event) +//{ +// switch (event.GetId()) +// { +// case MAINFRAME_MENU_ID_OPTIONS_REGION_AUTO: +// GetConfig().console_region = ConsoleRegion::Auto; +// break; +// case MAINFRAME_MENU_ID_OPTIONS_REGION_JPN: +// GetConfig().console_region = ConsoleRegion::JPN; +// break; +// case MAINFRAME_MENU_ID_OPTIONS_REGION_USA: +// GetConfig().console_region = ConsoleRegion::USA; +// break; +// case MAINFRAME_MENU_ID_OPTIONS_REGION_EUR: +// GetConfig().console_region = ConsoleRegion::EUR; +// break; +// case MAINFRAME_MENU_ID_OPTIONS_REGION_CHN: +// GetConfig().console_region = ConsoleRegion::CHN; +// break; +// case MAINFRAME_MENU_ID_OPTIONS_REGION_KOR: +// GetConfig().console_region = ConsoleRegion::KOR; +// break; +// case MAINFRAME_MENU_ID_OPTIONS_REGION_TWN: +// GetConfig().console_region = ConsoleRegion::TWN; +// break; +// default: +// cemu_assert_debug(false); +// } +// +// g_config.Save(); +//} + +void MainWindow::OnConsoleLanguage(wxCommandEvent& event) +{ + switch (event.GetId()) + { + case MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_JAPANESE: + GetConfig().console_language = CafeConsoleLanguage::JA; + break; + case MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_ENGLISH: + GetConfig().console_language = CafeConsoleLanguage::EN; + break; + case MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_FRENCH: + GetConfig().console_language = CafeConsoleLanguage::FR; + break; + case MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_GERMAN: + GetConfig().console_language = CafeConsoleLanguage::DE; + break; + case MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_ITALIAN: + GetConfig().console_language = CafeConsoleLanguage::IT; + break; + case MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_SPANISH: + GetConfig().console_language = CafeConsoleLanguage::ES; + break; + case MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_CHINESE: + GetConfig().console_language = CafeConsoleLanguage::ZH; + break; + case MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_KOREAN: + GetConfig().console_language = CafeConsoleLanguage::KO; + break; + case MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_DUTCH: + GetConfig().console_language = CafeConsoleLanguage::NL; + break; + case MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_PORTUGUESE: + GetConfig().console_language = CafeConsoleLanguage::PT; + break; + case MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_RUSSIAN: + GetConfig().console_language = CafeConsoleLanguage::RU; + break; + case MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_TAIWANESE: + GetConfig().console_language = CafeConsoleLanguage::TW; + break; + default: + cemu_assert_debug(false); + } + g_config.Save(); +} + +//void MainWindow::OnCPUMode(wxCommandEvent& event) +//{ +// if (event.GetId() == MAINFRAME_MENU_ID_CPU_MODE_SINGLECORE_INTERPRETER) +// GetConfig().cpu_mode = CPUMode::SinglecoreInterpreter; +// else if (event.GetId() == MAINFRAME_MENU_ID_CPU_MODE_SINGLECORE_RECOMPILER) +// GetConfig().cpu_mode = CPUMode::SinglecoreRecompiler; +// else if (event.GetId() == MAINFRAME_MENU_ID_CPU_MODE_DUALCORE_RECOMPILER) +// GetConfig().cpu_mode = CPUMode::DualcoreRecompiler; +// else if (event.GetId() == MAINFRAME_MENU_ID_CPU_MODE_TRIPLECORE_RECOMPILER) +// GetConfig().cpu_mode = CPUMode::TriplecoreRecompiler; +// else +// cemu_assert_debug(false); +// +// g_config.Save(); +//} + +void MainWindow::OnDebugSetting(wxCommandEvent& event) +{ + if (event.GetId() == MAINFRAME_MENU_ID_DEBUG_RENDER_UPSIDE_DOWN) + GetConfig().render_upside_down = event.IsChecked(); + else if (event.GetId() == MAINFRAME_MENU_ID_DEBUG_VK_ACCURATE_BARRIERS) + { + GetConfig().vk_accurate_barriers = event.IsChecked(); + if(!GetConfig().vk_accurate_barriers) + wxMessageBox(_("Warning: Disabling the accurate barriers option will lead to flickering graphics but may improve performance. It is highly recommended to leave it turned on."), _("Accurate barriers are off"), wxOK); + } + else if (event.GetId() == MAINFRAME_MENU_ID_DEBUG_AUDIO_AUX_ONLY) + ActiveSettings::EnableAudioOnlyAux(event.IsChecked()); + else if (event.GetId() == MAINFRAME_MENU_ID_DEBUG_DUMP_RAM) + memory_createDump(); + else if (event.GetId() == MAINFRAME_MENU_ID_DEBUG_DUMP_FST) + { + /* int msgBoxAnswer = wxMessageBox(_("All files from the currently running game will be dumped to /dump/. This process can take a few minutes."), + _("Dump WUD"), wxOK | wxCANCEL | wxICON_WARNING); + if (msgBoxAnswer == wxOK) + { + volumeFST_dump(bootGame_getMountedWUD()); + wxMessageBox(_("Dump complete")); + }*/ + } + else if (event.GetId() == MAINFRAME_MENU_ID_DEBUG_SHOW_FRAME_PROFILER) + { + ActiveSettings::EnableFrameProfiler(event.IsChecked()); + } + else if (event.GetId() == MAINFRAME_MENU_ID_DEBUG_DUMP_CURL_REQUESTS) + { + // toggle debug -> dump -> curl requests + const bool state = event.IsChecked(); + ActiveSettings::EnableDumpLibcurlRequests(state); + if (state) + { + try + { + const auto path = CemuApp::GetCemuPath(L"dump\\curl").ToStdWstring(); + fs::create_directories(path); + } + catch (const std::exception& ex) + { + SystemException sys(ex); + forceLog_printf("error when creating dump curl folder: %s", sys.what()); + ActiveSettings::EnableDumpLibcurlRequests(false); + } + } + } + else if (event.GetId() == MAINFRAME_MENU_ID_TIMER_SPEED_8X) + ActiveSettings::SetTimerShiftFactor(0); + else if (event.GetId() == MAINFRAME_MENU_ID_TIMER_SPEED_4X) + ActiveSettings::SetTimerShiftFactor(1); + else if (event.GetId() == MAINFRAME_MENU_ID_TIMER_SPEED_2X) + ActiveSettings::SetTimerShiftFactor(2); + else if (event.GetId() == MAINFRAME_MENU_ID_TIMER_SPEED_1X) + ActiveSettings::SetTimerShiftFactor(3); + else if (event.GetId() == MAINFRAME_MENU_ID_TIMER_SPEED_05X) + ActiveSettings::SetTimerShiftFactor(4); + else if (event.GetId() == MAINFRAME_MENU_ID_TIMER_SPEED_025X) + ActiveSettings::SetTimerShiftFactor(5); + else if (event.GetId() == MAINFRAME_MENU_ID_TIMER_SPEED_0125X) + ActiveSettings::SetTimerShiftFactor(6); + else + cemu_assert_debug(false); + + g_config.Save(); +} + +void MainWindow::OnDebugLoggingToggleFlagGeneric(wxCommandEvent& event) +{ + sint32 loggingIdBase = MAINFRAME_MENU_ID_DEBUG_LOGGING0; + + sint32 id = event.GetId(); + if (id >= loggingIdBase && id < (MAINFRAME_MENU_ID_DEBUG_LOGGING0 + 20)) + { + cafeLog_setLoggingFlagEnable(id - loggingIdBase, event.IsChecked()); + } +} + +void MainWindow::OnPPCInfoToggle(wxCommandEvent& event) +{ + GetConfig().advanced_ppc_logging = !GetConfig().advanced_ppc_logging.GetValue(); + g_config.Save(); +} + +void MainWindow::OnDebugDumpUsedTextures(wxCommandEvent& event) +{ + const bool value = event.IsChecked(); + ActiveSettings::EnableDumpTextures(value); + if (value) + { + try + { + // create directory + const auto path = CemuApp::GetCemuPath(L"dump\\textures"); + fs::create_directories(path.ToStdWstring()); + } + catch (const std::exception& ex) + { + SystemException sys(ex); + forceLog_printf("can't create texture dump folder: %s", ex.what()); + ActiveSettings::EnableDumpTextures(false); + } + } +} + +void MainWindow::OnDebugDumpUsedShaders(wxCommandEvent& event) +{ + const bool value = event.IsChecked(); + ActiveSettings::EnableDumpShaders(value); + if (value) + { + try + { + // create directory + const auto path = CemuApp::GetCemuPath(L"dump\\shaders"); + fs::create_directories(path.ToStdWstring()); + } + catch (const std::exception & ex) + { + SystemException sys(ex); + forceLog_printf("can't create shaders dump folder: %s", ex.what()); + ActiveSettings::EnableDumpShaders(false); + } + } +} + +void MainWindow::OnLoggingWindow(wxCommandEvent& event) +{ + if(m_logging_window) + return; + + m_logging_window = new LoggingWindow(this); + m_logging_window->Bind(wxEVT_CLOSE_WINDOW, + [this](wxCloseEvent& event) { + m_logging_window = nullptr; + event.Skip(); + }); + m_logging_window->Show(true); +} + +void MainWindow::OnDebugViewPPCThreads(wxCommandEvent& event) +{ + auto frame = new DebugPPCThreadsWindow(*this); + frame->Show(true); +} + +void MainWindow::OnDebugViewPPCDebugger(wxCommandEvent& event) +{ + if (m_debugger_window && m_debugger_window->IsShown()) + { + m_debugger_window->Close(); + m_debugger_window = nullptr; + return; + } + + auto rect = GetDesktopRect(); + /* + sint32 new_width = max(rect.GetWidth() * 0.70, rect.GetWidth() - 850); + this->SetSize(new_width, 480);*/ + + this->SetSize(800, 450 + 50); + this->CenterOnScreen(); + + auto pos = this->GetPosition(); + pos.y = std::min(pos.y + 200, rect.GetHeight() - 400); + this->SetPosition(pos); + + m_debugger_window = new DebuggerWindow2(*this, rect); + m_debugger_window->Bind(wxEVT_CLOSE_WINDOW, &MainWindow::OnDebuggerClose, this); + m_debugger_window->Show(true); +} + +void MainWindow::OnDebugViewAudioDebugger(wxCommandEvent& event) +{ + auto frame = new AudioDebuggerWindow(*this); + frame->Show(true); +} + +void MainWindow::OnDebugViewTextureRelations(wxCommandEvent& event) +{ + openTextureViewer(*this); +} + +void MainWindow::ShowCursor(bool state) +{ + #if BOOST_OS_WINDOWS + CURSORINFO info{}; + info.cbSize = sizeof(CURSORINFO); + GetCursorInfo(&info); + const bool visible = info.flags == CURSOR_SHOWING; + + if (state == visible) + return; + + int counter = 0; + if(state) + { + do + { + counter = ::ShowCursor(TRUE); + } while (counter < 0); + } + else + { + do + { + counter = ::ShowCursor(FALSE); + } while (counter >= 0); + } + #else + cemuLog_log(LogType::Force, "MainWindow::ShowCursor - todo"); + #endif +} + +uintptr_t MainWindow::GetRenderCanvasHWND() +{ + // deprecated. We can use the global cross-platform window info structs now + #if BOOST_OS_WINDOWS + if (!m_render_canvas) + return 0; + return (uintptr_t)m_render_canvas->GetHWND(); + #else + return 0; + #endif +} + +wxRect MainWindow::GetDesktopRect() +{ + const auto pos = GetPosition(); + const auto middle = pos.x + GetSize().GetWidth() / 2; + + const auto displayCount = wxDisplay::GetCount(); + for (uint32 i = 0; i < displayCount; ++i) + { + wxDisplay display(i); + if (!display.IsOk()) + continue; + + const auto geo = display.GetGeometry(); + if (geo.x <= middle && middle <= geo.x + geo.width) + return geo; + } + return { 0,0,800,600 }; +} + +void MainWindow::LoadSettings() +{ + g_config.Load(); + const auto& config = GetConfig(); + + if(config.check_update) + m_update_available = CemuUpdateWindow::IsUpdateAvailable(); + + if (config.window_position != Vector2i{ -1,-1 }) + this->SetPosition({ config.window_position.x, config.window_position.y }); + + if (config.window_size != Vector2i{ -1,-1 }) + { + this->SetSize({ config.window_size.x, config.window_size.y }); + + if (config.window_maximized) + this->Maximize(); + } + + if (config.pad_position != Vector2i{ -1,-1 }) + { + g_window_info.restored_pad_x = config.pad_position.x; + g_window_info.restored_pad_y = config.pad_position.y; + } + + if (config.pad_size != Vector2i{ -1,-1 }) + { + g_window_info.restored_pad_width = config.pad_size.x; + g_window_info.restored_pad_height = config.pad_size.y; + + g_window_info.pad_maximized = config.pad_maximized; + } + + this->TogglePadView(); + + if(m_game_list) + m_game_list->LoadConfig(); +} + +void MainWindow::SaveSettings() +{ + auto lock = g_config.Lock(); + auto& config = GetConfig(); + + if (config.window_position != Vector2i{ -1,-1 }) + { + config.window_position.x = m_restored_position.x; + config.window_position.y = m_restored_position.y; + } + if (config.window_size != Vector2i{ -1,-1 }) + { + config.window_size.x = m_restored_size.x; + config.window_size.y = m_restored_size.y; + config.window_maximized = IsMaximized(); + } + else + { + config.window_maximized = false; + } + + config.pad_open = m_padView != nullptr; + + if (config.pad_position != Vector2i{ -1,-1 } && g_window_info.restored_pad_x != -1) + { + config.pad_position.x = g_window_info.restored_pad_x; + config.pad_position.y = g_window_info.restored_pad_y; + } + if (config.pad_size != Vector2i{ -1,-1 } && g_window_info.restored_pad_width != -1) + { + config.pad_size.x = g_window_info.restored_pad_width; + config.pad_size.y = g_window_info.restored_pad_height; + config.pad_maximized = g_window_info.pad_maximized; + } + else + { + config.pad_maximized = false; + } + + if(m_game_list) + m_game_list->SaveConfig(); + + g_config.Save(); +} + +void MainWindow::OnMouseMove(wxMouseEvent& event) +{ + event.Skip(); + + m_last_mouse_move_time = std::chrono::steady_clock::now(); + m_mouse_position = wxGetMousePosition(); + ShowCursor(true); + + auto& instance = InputManager::instance(); + std::unique_lock lock(instance.m_main_mouse.m_mutex); + instance.m_main_mouse.position = { event.GetPosition().x, event.GetPosition().y }; + lock.unlock(); + + if (!IsFullScreen()) + return; + + const auto& config = GetConfig(); + // if mouse goes to upper screen then show our menu in fullscreen mode + if (config.fullscreen_menubar) + SetMenuVisible(event.GetPosition().y < 50); +} + +void MainWindow::OnMouseLeft(wxMouseEvent& event) +{ + auto& instance = InputManager::instance(); + + std::scoped_lock lock(instance.m_main_mouse.m_mutex); + instance.m_main_mouse.left_down = event.ButtonDown(wxMOUSE_BTN_LEFT); + instance.m_main_mouse.position = { event.GetPosition().x, event.GetPosition().y }; + if (event.ButtonDown(wxMOUSE_BTN_LEFT)) + instance.m_main_mouse.left_down_toggle = true; + + event.Skip(); +} + +void MainWindow::OnMouseRight(wxMouseEvent& event) +{ + auto& instance = InputManager::instance(); + + std::scoped_lock lock(instance.m_main_mouse.m_mutex); + instance.m_main_mouse.right_down = event.ButtonDown(wxMOUSE_BTN_RIGHT); + instance.m_main_mouse.position = { event.GetPosition().x, event.GetPosition().y }; + if(event.ButtonDown(wxMOUSE_BTN_RIGHT)) + instance.m_main_mouse.right_down_toggle = true; + + event.Skip(); +} + +void MainWindow::OnGameListBeginUpdate(wxCommandEvent& event) +{ + if (m_game_list->IsShown()) + m_info_bar->ShowMessage(_("Updating game list...")); +} + +void MainWindow::OnGameListEndUpdate(wxCommandEvent& event) +{ + m_info_bar->Dismiss(); +} + +void MainWindow::OnAccountListRefresh(wxCommandEvent& event) +{ + RecreateMenu(); +} + +void MainWindow::OnRequestGameListRefresh(wxCommandEvent& event) +{ + m_game_list->ReloadGameEntries(false); +} + +void MainWindow::OnSetWindowTitle(wxCommandEvent& event) +{ + this->SetTitle(event.GetString()); +} + +void MainWindow::OnKeyUp(wxKeyEvent& event) +{ + event.Skip(); + + if (swkbd_hasKeyboardInputHook()) + return; + + const auto code = event.GetKeyCode(); + if (code == WXK_ESCAPE) + SetFullScreen(false); + else if (code == WXK_RETURN && event.AltDown()) + SetFullScreen(!IsFullScreen()); +#ifdef PUBLIC_RELEASE + else if (code == WXK_F12) +#else + else if (code == WXK_F11) +#endif + g_window_info.has_screenshot_request = true; // async screenshot request +} + +void MainWindow::OnChar(wxKeyEvent& event) +{ + if (swkbd_hasKeyboardInputHook()) + swkbd_keyInput(event.GetUnicodeKey()); + + event.Skip(); +} + +void MainWindow::OnToolsInput(wxCommandEvent& event) +{ + const auto id = event.GetId(); + switch (id) + { + case MAINFRAME_MENU_ID_TOOLS_MEMORY_SEARCHER: + { + if (m_toolWindow) + m_toolWindow->SetFocus(); + else + { + m_toolWindow = new MemorySearcherTool(this); + m_toolWindow->Bind(wxEVT_CLOSE_WINDOW, &MainWindow::OnMemorySearcherClose, this); + m_toolWindow->Show(true); + } + break; + } + case MAINFRAME_MENU_ID_TOOLS_TITLE_MANAGER: + case MAINFRAME_MENU_ID_TOOLS_DOWNLOAD_MANAGER: + { + const auto default_tab = id == MAINFRAME_MENU_ID_TOOLS_TITLE_MANAGER ? TitleManagerPage::TitleManager : TitleManagerPage::DownloadManager; + + if (m_title_manager) + m_title_manager->SetFocusAndTab(default_tab); + else + { + m_title_manager = new TitleManager(this, default_tab); + m_title_manager->Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& event) + { + m_title_manager = nullptr; + event.Skip(); + }); + m_title_manager->Show(); + } + } + break; + } +} + +void MainWindow::OnGesturePan(wxPanGestureEvent& event) +{ + auto& instance = InputManager::instance(); + std::scoped_lock lock(instance.m_main_touch.m_mutex); + instance.m_main_touch.position = { event.GetPosition().x, event.GetPosition().y }; + instance.m_main_touch.left_down = event.IsGestureStart() || !event.IsGestureEnd(); + if (event.IsGestureStart() || !event.IsGestureEnd()) + instance.m_main_touch.left_down_toggle = true; + + + event.Skip(); +} + +void MainWindow::OnGameLoaded() +{ + if (m_debugger_window) + m_debugger_window->OnGameLoaded(); +} + +void MainWindow::AsyncSetTitle(std::string_view windowTitle) +{ + wxCommandEvent set_title_event(wxEVT_SET_WINDOW_TITLE); + set_title_event.SetString(wxHelper::FromUtf8(windowTitle)); + g_mainFrame->QueueEvent(set_title_event.Clone()); +} + +void MainWindow::CreateCanvas() +{ + if (ActiveSettings::GetGraphicsAPI() == kVulkan) + m_render_canvas = new VulkanCanvas(m_game_panel, wxSize(1280, 720), true); + else + m_render_canvas = GLCanvas_Create(m_game_panel, wxSize(1280, 720), true); + + // mouse events + m_render_canvas->Bind(wxEVT_MOTION, &MainWindow::OnMouseMove, this); + m_render_canvas->Bind(wxEVT_MOUSEWHEEL, &MainWindow::OnMouseWheel, this); + m_render_canvas->Bind(wxEVT_LEFT_DOWN, &MainWindow::OnMouseLeft, this); + m_render_canvas->Bind(wxEVT_LEFT_UP, &MainWindow::OnMouseLeft, this); + m_render_canvas->Bind(wxEVT_RIGHT_DOWN, &MainWindow::OnMouseRight, this); + m_render_canvas->Bind(wxEVT_RIGHT_UP, &MainWindow::OnMouseRight, this); + + m_render_canvas->Bind(wxEVT_GESTURE_PAN, &MainWindow::OnGesturePan, this); + + // key events + m_render_canvas->Bind(wxEVT_KEY_UP, &MainWindow::OnKeyUp, this); + m_render_canvas->Bind(wxEVT_CHAR, &MainWindow::OnChar, this); + + m_render_canvas->SetDropTarget(new wxAmiiboDropTarget(this)); + m_game_panel->GetSizer()->Add(m_render_canvas, 1, wxEXPAND, 0, nullptr); + + GetSizer()->Layout(); + m_render_canvas->SetFocus(); + + if (m_padView) + m_padView->InitializeRenderCanvas(); +} + +void MainWindow::DestroyCanvas() +{ + if (m_padView) + { + m_padView->Destroy(); + m_padView = nullptr; + } + if (m_render_canvas) + { + m_render_canvas->Destroy(); + m_render_canvas = nullptr; + } +} + + +std::wstring MainWindow::GetGameName(std::wstring_view file_name) +{ + fs::path path{ std::wstring{file_name} }; + const auto extension = path.extension(); + if (extension == ".wud" || extension == ".wux") + { + std::unique_ptr volume(FSTVolume::OpenFromDiscImage(path)); + if (!volume) + return path.filename().generic_wstring(); + + bool foundFile = false; + std::vector metaContent = volume->ExtractFile("meta/meta.xml", &foundFile); + if (!foundFile) + return path.filename().generic_wstring(); + + namespace xml = tinyxml2; + xml::XMLDocument doc; + doc.Parse((const char*)metaContent.data(), metaContent.size()); + + // parse meta.xml + xml::XMLElement* root = doc.FirstChildElement("menu"); + if (root) + { + xml::XMLElement* element = root->FirstChildElement("longname_en"); + if (element) + { + + auto game_name = boost::nowide::widen(element->GetText()); + const auto it = game_name.find(L'\n'); + if (it != std::wstring::npos) + game_name.replace(it, 1, L" - "); + + return game_name; + } + } + return path.filename().generic_wstring(); + } + else if (extension == ".rpx") + { + if (path.has_parent_path() && path.parent_path().has_parent_path()) + { + auto meta_xml = path.parent_path().parent_path() / "meta/meta.xml"; + auto metaXmlData = FileStream::LoadIntoMemory(meta_xml); + if (metaXmlData) + { + namespace xml = tinyxml2; + xml::XMLDocument doc; + if (doc.Parse((const char*)metaXmlData->data(), metaXmlData->size()) == xml::XML_SUCCESS) + { + xml::XMLElement* root = doc.FirstChildElement("menu"); + if (root) + { + xml::XMLElement* element = root->FirstChildElement("longname_en"); + if (element) + { + + auto game_name = boost::nowide::widen(element->GetText()); + const auto it = game_name.find(L'\n'); + if (it != std::wstring::npos) + game_name.replace(it, 1, L" - "); + + return game_name; + } + } + } + } + } + } + + return path.filename().generic_wstring(); +} + +void MainWindow::OnSizeEvent(wxSizeEvent& event) +{ + if (!IsMaximized() && !gui_isFullScreen()) + m_restored_size = GetSize(); + + const wxSize client_size = GetClientSize(); + g_window_info.width = client_size.GetWidth(); + g_window_info.height = client_size.GetHeight(); + + if (m_debugger_window && m_debugger_window->IsShown()) + m_debugger_window->OnParentMove(GetPosition(), event.GetSize()); + + event.Skip(); + + VsyncDriver_notifyWindowPosChanged(); +} + +void MainWindow::OnMove(wxMoveEvent& event) +{ + if (!IsMaximized() && !gui_isFullScreen()) + m_restored_position = GetPosition(); + + if (m_debugger_window && m_debugger_window->IsShown()) + m_debugger_window->OnParentMove(GetPosition(), GetSize()); + VsyncDriver_notifyWindowPosChanged(); +} + +void MainWindow::OnDebuggerClose(wxCloseEvent& event) +{ + m_debugger_window = nullptr; + event.Skip(); +} + +void MainWindow::OnPadClose(wxCloseEvent& event) +{ + auto& config = GetConfig(); + config.pad_open = false; + if (config.pad_position != Vector2i{ -1,-1 }) + m_padView->GetPosition(&config.pad_position.x, &config.pad_position.y); + + if (config.pad_size != Vector2i{ -1,-1 }) + m_padView->GetSize(&config.pad_size.x, &config.pad_size.y); + + g_config.Save(); + + // already deleted by wxwidget + m_padView = nullptr; + + if (m_padViewMenuItem) + m_padViewMenuItem->Check(false); + + event.Skip(); +} + +void MainWindow::OnMemorySearcherClose(wxCloseEvent& event) +{ + m_toolWindow = nullptr; + event.Skip(); +} + +void MainWindow::OnMouseWheel(wxMouseEvent& event) +{ + const float delta = event.GetWheelRotation(); // in 120 steps -> max reached ~480 (?) + auto& instance = InputManager::instance(); + instance.m_mouse_wheel = (delta / 120.0f); + + event.Skip(); +} + +void MainWindow::SetFullScreen(bool state) +{ + // only update config entry if we dont't have launch parameters + if (!LaunchSettings::FullscreenEnabled().has_value()) + { + GetConfig().fullscreen = state; + g_config.Save(); + } + if (state && !m_game_launched) + return; + g_window_info.is_fullscreen = state; + m_fullscreenMenuItem->Check(state); + + this->ShowFullScreen(state); + + if (state) + m_menu_visible = false; // menu gets always disabled by wxFULLSCREEN_NOMENUBAR + else + SetMenuVisible(true); +} + +void MainWindow::SetMenuVisible(bool state) +{ + if (m_menu_visible == state) + return; + + SetMenuBar(state ? m_menuBar : nullptr); + m_menu_visible = state; +} + +void MainWindow::UpdateNFCMenu() +{ + if (m_nfcMenuSeparator0) + { + m_nfcMenu->Remove(m_nfcMenuSeparator0->GetId()); + m_nfcMenuSeparator0 = nullptr; + } + // remove recent files list + for (sint32 i = 0; i < CemuConfig::kMaxRecentEntries; i++) + { + if (m_nfcMenu->FindChildItem(MAINFRAME_MENU_ID_NFC_RECENT_0 + i) == nullptr) + continue; + m_nfcMenu->Remove(MAINFRAME_MENU_ID_NFC_RECENT_0 + i); + } + // add entries + const auto& config = GetConfig(); + sint32 recentFileIndex = 0; + for (size_t i = 0; i < config.recent_nfc_files.size(); i++) + { + const auto& entry = config.recent_nfc_files[i]; + if (entry.empty()) + continue; + + if (!fs::exists(entry)) + continue; + + if (recentFileIndex == 0) + m_nfcMenuSeparator0 = m_nfcMenu->AppendSeparator(); + + m_nfcMenu->Append(MAINFRAME_MENU_ID_NFC_RECENT_0 + i, fmt::format(L"{}. {}", recentFileIndex, entry )); + + recentFileIndex++; + if (recentFileIndex >= 12) + break; + } +} + +bool MainWindow::IsMenuHidden() const +{ + return m_menu_visible; +} + +void MainWindow::OnTimer(wxTimerEvent& event) +{ + if(m_update_available.valid() && future_is_ready(m_update_available)) + { + if(m_update_available.get()) + { + wxMessageDialog dialog(this, _("There's a new update available.\nDo you want to update?"), _("Update notification"), wxCENTRE | wxYES_NO); + if(dialog.ShowModal() == wxID_YES) + { + CemuUpdateWindow update_window(this); + update_window.ShowModal(); + update_window.Destroy(); + } + } + + m_update_available = {}; + } + + if (!IsFullScreen() || m_menu_visible) + return; + + const auto mouse_position = wxGetMousePosition(); + if(m_mouse_position != mouse_position) + { + m_last_mouse_move_time = std::chrono::steady_clock::now(); + m_mouse_position = mouse_position; + ShowCursor(true); + return; + } + + auto diff = std::chrono::duration_cast(std::chrono::steady_clock::now() - m_last_mouse_move_time); + if (diff.count() > 3000) + { + ShowCursor(false); + } + +} + +void MainWindow::OnHelpVistWebpage(wxCommandEvent& event) {} + +#define BUILD_DATE __DATE__ " " __TIME__ + +class CemuAboutDialog : public wxDialog +{ +public: + CemuAboutDialog(wxWindow* parent = NULL) + : wxDialog(NULL, wxID_ANY, "About Cemu", wxDefaultPosition, wxSize(500, 700)) + { + Create(parent); + } + + void Create(wxWindow* parent = NULL) + { + SetIcon(wxICON(M_WND_ICON128)); + + this->SetBackgroundColour(wxColour(0xFFFFFFFF)); + + wxScrolledWindow* scrolledWindow = new wxScrolledWindow(this); + + wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL); + + m_scrolledSizer = new wxBoxSizer(wxVERTICAL); + + AddHeaderInfo(scrolledWindow, m_scrolledSizer); + m_scrolledSizer->AddSpacer(5); + AddLibInfo(scrolledWindow, m_scrolledSizer); + m_scrolledSizer->AddSpacer(5); + AddThanks(scrolledWindow, m_scrolledSizer); + + scrolledWindow->SetSizer(m_scrolledSizer); + scrolledWindow->FitInside(); + scrolledWindow->SetScrollRate(25, 25); + mainSizer->Add(scrolledWindow, wxSizerFlags(1).Expand().Border(wxLEFT | wxRIGHT, 10)); + + SetSizer(mainSizer); + CentreOnParent(); + } + + void AddHeaderInfo(wxWindow* parent, wxSizer* sizer) + { + char versionString[512]; + sprintf(versionString, "Cemu\nVersion %d.%d\nCompiled " BUILD_DATE "\nOriginal authors: Exzap, Petergov", EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR); + + sizer->Add(new wxStaticText(parent, wxID_ANY, versionString), wxSizerFlags().Border(wxALL, 3).Border(wxTOP, 10)); + sizer->Add(new wxHyperlinkCtrl(parent, -1, "https://cemu.info", "https://cemu.info"), wxSizerFlags().Expand().Border(wxTOP | wxBOTTOM, 3)); + + sizer->AddSpacer(3); + sizer->Add(new wxStaticLine(parent), wxSizerFlags().Expand().Border(wxRIGHT, 4)); + sizer->AddSpacer(5); + + wxString extraInfo("" EMULATOR_NAME " is a Wii U emulator.\n\nWii and Wii U are trademarks of Nintendo.\n" EMULATOR_NAME " is not affiliated with Nintendo."); + sizer->Add(new wxStaticText(parent, wxID_ANY, extraInfo), wxSizerFlags()); + } + + void AddLibInfo(wxWindow* parent, wxSizer* sizer) + { + sizer->AddSpacer(3); + sizer->Add(new wxStaticLine(parent), wxSizerFlags().Expand().Border(wxRIGHT, 4)); + sizer->AddSpacer(3); + + sizer->Add(new wxStaticText(parent, wxID_ANY, "Used libraries and utilities:"), wxSizerFlags().Expand().Border(wxTOP | wxBOTTOM, 2)); + // zLib + { + wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL); + lineSizer->Add(new wxStaticText(parent, -1, "zLib ("), 0); + lineSizer->Add(new wxHyperlinkCtrl(parent, -1, "http://www.zlib.net", "http://www.zlib.net"), 0); + lineSizer->Add(new wxStaticText(parent, -1, ")"), 0); + sizer->Add(lineSizer); + } + // wxWidgets + { + wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL); + lineSizer->Add(new wxStaticText(parent, -1, "wxWidgets ("), 0); + lineSizer->Add(new wxHyperlinkCtrl(parent, -1, "https://www.wxwidgets.org/", "https://www.wxwidgets.org/"), 0); + lineSizer->Add(new wxStaticText(parent, -1, ")"), 0); + sizer->Add(lineSizer); + } + // OpenSSL + { + wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL); + lineSizer->Add(new wxStaticText(parent, -1, "OpenSSL ("), 0); + lineSizer->Add(new wxHyperlinkCtrl(parent, -1, "https://www.openssl.org/", "https://www.openssl.org/"), 0); + lineSizer->Add(new wxStaticText(parent, -1, ")"), 0); + sizer->Add(lineSizer); + } + // libcurl + { + wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL); + lineSizer->Add(new wxStaticText(parent, -1, "libcurl ("), 0); + lineSizer->Add(new wxHyperlinkCtrl(parent, -1, "https://curl.haxx.se/libcurl/", "https://curl.haxx.se/libcurl/"), 0); + lineSizer->Add(new wxStaticText(parent, -1, ")"), 0); + sizer->Add(lineSizer); + } + // imgui + { + wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL); + lineSizer->Add(new wxStaticText(parent, -1, "imgui ("), 0); + lineSizer->Add(new wxHyperlinkCtrl(parent, -1, "https://github.com/ocornut/imgui", "https://github.com/ocornut/imgui"), 0); + lineSizer->Add(new wxStaticText(parent, -1, ")"), 0); + sizer->Add(lineSizer); + } + // fontawesome + { + wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL); + lineSizer->Add(new wxStaticText(parent, -1, "fontawesome ("), 0); + lineSizer->Add(new wxHyperlinkCtrl(parent, -1, "https://github.com/FortAwesome/Font-Awesome", "https://github.com/FortAwesome/Font-Awesome"), 0); + lineSizer->Add(new wxStaticText(parent, -1, ")"), 0); + sizer->Add(lineSizer); + } + // boost + { + wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL); + lineSizer->Add(new wxStaticText(parent, -1, "boost ("), 0); + lineSizer->Add(new wxHyperlinkCtrl(parent, -1, "https://www.boost.org", "https://www.boost.org"), 0); + lineSizer->Add(new wxStaticText(parent, -1, ")"), 0); + sizer->Add(lineSizer); + } + // libusb + { + wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL); + lineSizer->Add(new wxStaticText(parent, -1, "libusb ("), 0); + lineSizer->Add(new wxHyperlinkCtrl(parent, -1, "https://libusb.info", "https://libusb.info"), 0); + lineSizer->Add(new wxStaticText(parent, -1, ")"), 0); + sizer->Add(lineSizer); + } + // icons + { + wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL); + lineSizer->Add(new wxStaticText(parent, -1, "icons from "), 0); + lineSizer->Add(new wxHyperlinkCtrl(parent, -1, "https://icons8.com", "https://icons8.com"), 0); + sizer->Add(lineSizer); + } + // Lato font (are we still using it?) + { + wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL); + lineSizer->Add(new wxStaticText(parent, -1, "\"Lato\" font by tyPoland Lukasz Dziedzic (OFL, V1.1)"), 0); + sizer->Add(lineSizer); + } + // SDL + { + wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL); + lineSizer->Add(new wxStaticText(parent, -1, "SDL ("), 0); + lineSizer->Add(new wxHyperlinkCtrl(parent, -1, "https://github.com/libsdl-org/SDL", "https://github.com/libsdl-org/SDL"), 0); + lineSizer->Add(new wxStaticText(parent, -1, ")"), 0); + sizer->Add(lineSizer); + } + // IH264 + { + wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL); + lineSizer->Add(new wxStaticText(parent, -1, "Modified ih264 from Android project ("), 0); + lineSizer->Add(new wxHyperlinkCtrl(parent, -1, "Source", "https://cemu.info/oss/ih264d.zip"), 0); + lineSizer->Add(new wxStaticText(parent, -1, " "), 0); + wxHyperlinkCtrl* noticeLink = new wxHyperlinkCtrl(parent, -1, "NOTICE", ""); + noticeLink->Bind(wxEVT_LEFT_DOWN, [](wxMouseEvent& event) + { + fs::path tempPath = fs::temp_directory_path(); + tempPath.append("NOTICE_IH264.txt"); + FileStream* fs = FileStream::createFile2(tempPath); + if (!fs) + return; + fs->writeString( + "/******************************************************************************\r\n" + " *\r\n" + " * Copyright (C) 2015 The Android Open Source Project\r\n" + " *\r\n" + " * Licensed under the Apache License, Version 2.0 (the \"License\");\r\n" + " * you may not use this file except in compliance with the License.\r\n" + " * You may obtain a copy of the License at:" + " *\r\n" + " * http://www.apache.org/licenses/LICENSE-2.0\r\n" + " *\r\n" + " * Unless required by applicable law or agreed to in writing, software\r\n" + " * distributed under the License is distributed on an \"AS IS\" BASIS,\r\n" + " * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n" + " * See the License for the specific language governing permissions and\r\n" + " * limitations under the License.\r\n" + " *\r\n" + " *****************************************************************************\r\n" + " * Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore\r\n" + "*/\r\n" + "/*****************************************************************************/\r\n" + ); + delete fs; +#ifdef _WIN32 + ShellExecute(0, 0, tempPath.generic_wstring().c_str(), 0, 0, SW_SHOW); +#else + assert_dbg(); +#endif + }); + lineSizer->Add(noticeLink, 0); + lineSizer->Add(new wxStaticText(parent, -1, ")"), 0); + sizer->Add(lineSizer); + } + } + + void AddThanks(wxWindow* parent, wxSizer* sizer) + { + sizer->AddSpacer(3); + sizer->Add(new wxStaticLine(parent), wxSizerFlags().Expand().Border(wxRIGHT, 4)); + sizer->AddSpacer(3); + + wxGridSizer* gridSizer = new wxGridSizer(1, 2, 0, 0); + + sizer->AddSpacer(2); + + sizer->Add(new wxStaticText(parent, wxID_ANY, "Thanks to our Patreon supporters:"), wxSizerFlags().Expand().Border(wxTOP | wxBOTTOM, 2)); + + std::vector patreonSupporterNames{ "Maufeat", "lvlv", "F34R", "John Godgames", "Jameel Lewis", "skooks", "Cheesy", "Barrowsx", "Mored1984", "madmat007" + , "Kuhnnl", "Owen M", "lucianobugalu", "KimoMaka", "nick palma aka renaissance18", "TheGiantBros", "SpiGAndromeda" + , "Chimech0", "Nicolás Pino", "Pezzatti", "Barry Wallace", "REGNR8 Productions", "Lagia", "Freestyler316", "Dentora" + , "tactics", "Merola.C", "Ceigyx", "Mata", "BobSchneeder45", "fenixDG", "jjalapeno55", "FissionMetroid101", "Jetta88" + , "nesxdie", "Mikah", "PornfoxVR.com", "Hunter4everosa", "Bbzx", "Salim Sanehi", "FalloutpunkX", "NashOH-CL", "RaheemWala" + , "Faris Leonhart", "MahvZero", "PlaguedGuardian", "Stuffie", "CaptainLester", "Qtech", "Zaurexus", "Leonidas", "Artifesto" + , "Alca259", "SirWestofAsh", "Loli Co.", "The Technical Revolutionary", "MegaYama", "mitori", "Seymordius", "Adrian Josh Cruz", "Manuel Hoenings", "Just A Jabb" + , "pgantonio", "CannonXIII", "Lonewolf00708", "AlexsDesign.com", "NoskLo", "MrSirHaku", "xElite_V AKA William H. Johnson", "Zalnor", "Pig", "James \"SE4LS\"", "DairyOrange", "Horoko Lawrence", "bloodmc", "Officer Jenny", "Quasar", "Postposterous", "Jake Jackson", "Kaydax", "CthePredatorG" + , "Hengi", "Pyrochaser"}; + + wxString nameListLeft, nameListRight; + for (size_t i = 0; i < patreonSupporterNames.size(); i++) + { + const char* name = patreonSupporterNames[i]; + wxString& nameList = ((i % 2) == 0) ? nameListLeft : nameListRight; + if (i >= 2) + nameList.append("\n"); + nameList.append(name); + } + + gridSizer->Add(new wxStaticText(parent, wxID_ANY, nameListLeft), wxSizerFlags()); + gridSizer->Add(new wxStaticText(parent, wxID_ANY, nameListRight), wxSizerFlags()); + + sizer->AddSpacer(4); + + sizer->Add(gridSizer, 1, wxEXPAND); + + sizer->AddSpacer(2); + sizer->Add(new wxStaticText(parent, wxID_ANY, "Special thanks:"), wxSizerFlags().Expand().Border(wxTOP, 2)); + sizer->Add(new wxStaticText(parent, wxID_ANY, "espes - Also try XQEMU!\nWaltzz92"), wxSizerFlags().Expand().Border(wxTOP, 1)); + } + +protected: + wxSizer* m_scrolledSizer; +}; + +void MainWindow::OnHelpAbout(wxCommandEvent& event) +{ + CemuAboutDialog dlgAbout(this); + dlgAbout.ShowModal(); +} + +void MainWindow::OnHelpUpdate(wxCommandEvent& event) +{ + CemuUpdateWindow test(this); + test.ShowModal(); + test.Destroy(); +} + +void MainWindow::OnHelpGettingStarted(wxCommandEvent& event) +{ + ShowGettingStartedDialog(); +} + +void MainWindow::RecreateMenu() +{ + if (m_menuBar) + { + SetMenuBar(nullptr); + m_menuBar->Destroy(); + m_menuBar = nullptr; + } + + auto& config = GetConfig(); + + m_menuBar = new wxMenuBar; + // file submenu + m_fileMenu = new wxMenu; + + if (!m_game_launched) + { + m_loadMenuItem = m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_LOAD, _("&Load")); + m_installUpdateMenuItem = m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_INSTALL_UPDATE, _("&Install game title, update or DLC")); + + sint32 recentFileIndex = 0; + m_fileMenuSeparator0 = nullptr; + m_fileMenuSeparator1 = nullptr; + for (size_t i = 0; i < config.recent_launch_files.size(); i++) + { + const auto& entry = config.recent_launch_files[i]; + if (entry.empty()) + continue; + + if (!fs::exists(entry)) + continue; + + if (recentFileIndex == 0) + m_fileMenuSeparator0 = m_fileMenu->AppendSeparator(); + + m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_RECENT_0 + i, fmt::format(L"{}. {}", recentFileIndex, entry)); + recentFileIndex++; + + if (recentFileIndex >= 8) + break; + } + m_fileMenuSeparator1 = m_fileMenu->AppendSeparator(); + + } + else + { + // add 'Stop emulation' menu entry to file menu +#ifndef PUBLIC_RELEASE + m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_END_EMULATION, _("End emulation")); +#endif + // destroy Cemuhook update indicator status bar + if (m_statusBar) + { + delete m_statusBar; + m_statusBar = nullptr; + } + } + + m_exitMenuItem = m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_EXIT, _("&Exit")); + m_menuBar->Append(m_fileMenu, _("&File")); + // options->account submenu + m_optionsAccountMenu = new wxMenu; + const auto account_id = ActiveSettings::GetPersistentId(); + int index = 0; + for(const auto& account : Account::GetAccounts()) + { + wxMenuItem* item = m_optionsAccountMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_ACCOUNT_1 + index, account.ToString()); + item->Check(account_id == account.GetPersistentId()); + if (m_game_launched || LaunchSettings::GetPersistentId().has_value()) + item->Enable(false); + + ++index; + } + //optionsAccountMenu->AppendSeparator(); TODO + //optionsAccountMenu->AppendCheckItem(MAINFRAME_MENU_ID_OPTIONS_ACCOUNT_1 + index, _("Online enabled"))->Check(config.account.online_enabled); + + // options->region submenu + //wxMenu* optionsRegionMenu = new wxMenu; + //optionsRegionMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_REGION_AUTO, _("&Auto"), wxEmptyString)->Check(config.console_region == ConsoleRegion::Auto); + ////optionsRegionMenu->AppendSeparator(); + //optionsRegionMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_REGION_USA, _("&USA"), wxEmptyString)->Check(config.console_region == ConsoleRegion::USA); + //optionsRegionMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_REGION_EUR, _("&Europe"), wxEmptyString)->Check(config.console_region == ConsoleRegion::EUR); + //optionsRegionMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_REGION_JPN, _("&Japan"), wxEmptyString)->Check(config.console_region == ConsoleRegion::JPN); + //// optionsRegionMenu->Append(MAINFRAME_MENU_ID_OPTIONS_REGION_AUS, wxT("&Australia"), wxEmptyString, wxITEM_RADIO)->Check(config_get()->region==3); -> Was merged into Europe? + //optionsRegionMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_REGION_CHN, _("&China"), wxEmptyString)->Check(config.console_region == ConsoleRegion::CHN); + //optionsRegionMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_REGION_KOR, _("&Korea"), wxEmptyString)->Check(config.console_region == ConsoleRegion::KOR); + //optionsRegionMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_REGION_TWN, _("&Taiwan"), wxEmptyString)->Check(config.console_region == ConsoleRegion::TWN); + + // options->console language submenu + wxMenu* optionsConsoleLanguageMenu = new wxMenu; + optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_ENGLISH, _("&English"), wxEmptyString)->Check(config.console_language == CafeConsoleLanguage::EN); + optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_JAPANESE, _("&Japanese"), wxEmptyString)->Check(config.console_language == CafeConsoleLanguage::JA); + optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_FRENCH, _("&French"), wxEmptyString)->Check(config.console_language == CafeConsoleLanguage::FR); + optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_GERMAN, _("&German"), wxEmptyString)->Check(config.console_language == CafeConsoleLanguage::DE); + optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_ITALIAN, _("&Italian"), wxEmptyString)->Check(config.console_language == CafeConsoleLanguage::IT); + optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_SPANISH, _("&Spanish"), wxEmptyString)->Check(config.console_language == CafeConsoleLanguage::ES); + optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_CHINESE, _("&Chinese"), wxEmptyString)->Check(config.console_language == CafeConsoleLanguage::ZH); + optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_KOREAN, _("&Korean"), wxEmptyString)->Check(config.console_language == CafeConsoleLanguage::KO); + optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_DUTCH, _("&Dutch"), wxEmptyString)->Check(config.console_language == CafeConsoleLanguage::NL); + optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_PORTUGUESE, _("&Portuguese"), wxEmptyString)->Check(config.console_language == CafeConsoleLanguage::PT); + optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_RUSSIAN, _("&Russian"), wxEmptyString)->Check(config.console_language == CafeConsoleLanguage::RU); + optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_TAIWANESE, _("&Taiwanese"), wxEmptyString)->Check(config.console_language == CafeConsoleLanguage::TW); + + // options submenu + wxMenu* optionsMenu = new wxMenu; + m_fullscreenMenuItem = optionsMenu->AppendCheckItem(MAINFRAME_MENU_ID_OPTIONS_FULLSCREEN, _("&Fullscreen"), wxEmptyString); + m_fullscreenMenuItem->Check(ActiveSettings::FullscreenEnabled()); + + optionsMenu->Append(MAINFRAME_MENU_ID_OPTIONS_GRAPHIC_PACKS2, _("&Graphic packs")); + //optionsMenu->AppendSubMenu(optionsVCAMenu, _("&GPU buffer cache accuracy")); + m_padViewMenuItem = optionsMenu->AppendCheckItem(MAINFRAME_MENU_ID_OPTIONS_SECOND_WINDOW_PADVIEW, _("&Separate GamePad view"), wxEmptyString); + m_padViewMenuItem->Check(GetConfig().pad_open); + optionsMenu->AppendSeparator(); + optionsMenu->Append(MAINFRAME_MENU_ID_OPTIONS_GENERAL2, _("&General settings")); + optionsMenu->Append(MAINFRAME_MENU_ID_OPTIONS_INPUT, _("&Input settings")); + + optionsMenu->AppendSeparator(); + optionsMenu->AppendSubMenu(m_optionsAccountMenu, _("&Active account")); + //optionsMenu->AppendSubMenu(optionsRegionMenu, _("&Console region")); + optionsMenu->AppendSubMenu(optionsConsoleLanguageMenu, _("&Console language")); + m_menuBar->Append(optionsMenu, _("&Options")); + + // tools submenu + wxMenu* toolsMenu = new wxMenu; + m_memorySearcherMenuItem = toolsMenu->Append(MAINFRAME_MENU_ID_TOOLS_MEMORY_SEARCHER, _("&Memory searcher")); + m_memorySearcherMenuItem->Enable(false); + toolsMenu->Append(MAINFRAME_MENU_ID_TOOLS_TITLE_MANAGER, _("&Title Manager")); + toolsMenu->Append(MAINFRAME_MENU_ID_TOOLS_DOWNLOAD_MANAGER, _("&Download Manager")); + m_menuBar->Append(toolsMenu, _("&Tools")); + + // cpu timer speed menu + wxMenu* timerSpeedMenu = new wxMenu; + timerSpeedMenu->AppendRadioItem(MAINFRAME_MENU_ID_TIMER_SPEED_1X, _("&1x speed"), wxEmptyString)->Check(ActiveSettings::GetTimerShiftFactor() == 3); + timerSpeedMenu->AppendRadioItem(MAINFRAME_MENU_ID_TIMER_SPEED_2X, _("&2x speed"), wxEmptyString)->Check(ActiveSettings::GetTimerShiftFactor() == 2); + timerSpeedMenu->AppendRadioItem(MAINFRAME_MENU_ID_TIMER_SPEED_4X, _("&4x speed"), wxEmptyString)->Check(ActiveSettings::GetTimerShiftFactor() == 1); + timerSpeedMenu->AppendRadioItem(MAINFRAME_MENU_ID_TIMER_SPEED_8X, _("&8x speed"), wxEmptyString)->Check(ActiveSettings::GetTimerShiftFactor() == 0); + timerSpeedMenu->AppendRadioItem(MAINFRAME_MENU_ID_TIMER_SPEED_05X, _("&0.5x speed"), wxEmptyString)->Check(ActiveSettings::GetTimerShiftFactor() == 4); + timerSpeedMenu->AppendRadioItem(MAINFRAME_MENU_ID_TIMER_SPEED_025X, _("&0.25x speed"), wxEmptyString)->Check(ActiveSettings::GetTimerShiftFactor() == 5); + timerSpeedMenu->AppendRadioItem(MAINFRAME_MENU_ID_TIMER_SPEED_0125X, _("&0.125x speed"), wxEmptyString)->Check(ActiveSettings::GetTimerShiftFactor() == 6); + + // cpu submenu + wxMenu* cpuMenu = new wxMenu; + cpuMenu->AppendSubMenu(timerSpeedMenu, _("&Timer speed")); + m_menuBar->Append(cpuMenu, _("&CPU")); + + // nfc submenu + wxMenu* nfcMenu = new wxMenu; + m_nfcMenu = nfcMenu; + nfcMenu->Append(MAINFRAME_MENU_ID_NFC_TOUCH_NFC_FILE, _("&Scan NFC tag from file"))->Enable(false); + m_menuBar->Append(nfcMenu, _("&NFC")); + m_nfcMenuSeparator0 = nullptr; + // debug->logging submenu + wxMenu* debugLoggingMenu = new wxMenu; + + debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + LOG_TYPE_UNSUPPORTED_API, _("&Unsupported API calls"), wxEmptyString)->Check(cafeLog_isLoggingFlagEnabled(LOG_TYPE_UNSUPPORTED_API)); + debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + LOG_TYPE_COREINIT_LOGGING, _("&Coreinit Logging (OSReport/OSConsole)"), wxEmptyString)->Check(cafeLog_isLoggingFlagEnabled(LOG_TYPE_COREINIT_LOGGING)); + debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + LOG_TYPE_FILE, _("&Coreinit File-Access"), wxEmptyString)->Check(cafeLog_isLoggingFlagEnabled(LOG_TYPE_FILE)); + debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + LOG_TYPE_THREADSYNC, _("&Coreinit Thread-Synchronization API"), wxEmptyString)->Check(cafeLog_isLoggingFlagEnabled(LOG_TYPE_THREADSYNC)); + debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + LOG_TYPE_COREINIT_MEM, _("&Coreinit Memory API"), wxEmptyString)->Check(cafeLog_isLoggingFlagEnabled(LOG_TYPE_COREINIT_MEM)); + debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + LOG_TYPE_COREINIT_MP, _("&Coreinit MP API"), wxEmptyString)->Check(cafeLog_isLoggingFlagEnabled(LOG_TYPE_COREINIT_MP)); + debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + LOG_TYPE_COREINIT_THREAD, _("&Coreinit Thread API"), wxEmptyString)->Check(cafeLog_isLoggingFlagEnabled(LOG_TYPE_COREINIT_THREAD)); + debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + LOG_TYPE_NFP, _("&NN NFP"), wxEmptyString)->Check(cafeLog_isLoggingFlagEnabled(LOG_TYPE_NFP)); + debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + LOG_TYPE_GX2, _("&GX2 API"), wxEmptyString)->Check(cafeLog_isLoggingFlagEnabled(LOG_TYPE_GX2)); + debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + LOG_TYPE_SNDAPI, _("&Audio API"), wxEmptyString)->Check(cafeLog_isLoggingFlagEnabled(LOG_TYPE_SNDAPI)); + debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + LOG_TYPE_INPUT, _("&Input API"), wxEmptyString)->Check(cafeLog_isLoggingFlagEnabled(LOG_TYPE_INPUT)); + debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + LOG_TYPE_SOCKET, _("&Socket API"), wxEmptyString)->Check(cafeLog_isLoggingFlagEnabled(LOG_TYPE_SOCKET)); + debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + LOG_TYPE_SAVE, _("&Save API"), wxEmptyString)->Check(cafeLog_isLoggingFlagEnabled(LOG_TYPE_SAVE)); + debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + LOG_TYPE_H264, _("&H264 API"), wxEmptyString)->Check(cafeLog_isLoggingFlagEnabled(LOG_TYPE_H264)); + debugLoggingMenu->AppendSeparator(); + debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + LOG_TYPE_PATCHES, _("&Graphic pack patches"), wxEmptyString)->Check(cafeLog_isLoggingFlagEnabled(LOG_TYPE_PATCHES)); + debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + LOG_TYPE_TEXTURE_CACHE, _("&Texture cache warnings"), wxEmptyString)->Check(cafeLog_isLoggingFlagEnabled(LOG_TYPE_TEXTURE_CACHE)); + debugLoggingMenu->AppendSeparator(); + debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + LOG_TYPE_OPENGL, _("&OpenGL debug output"), wxEmptyString)->Check(cafeLog_isLoggingFlagEnabled(LOG_TYPE_OPENGL)); + debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + LOG_TYPE_VULKAN_VALIDATION, _("&Vulkan validation layer (slow)"), wxEmptyString)->Check(cafeLog_isLoggingFlagEnabled(LOG_TYPE_VULKAN_VALIDATION)); +#ifndef PUBLIC_RELEASE + debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_ADVANCED_PPC_INFO, _("&Log PPC context for API"), wxEmptyString)->Check(cemuLog_advancedPPCLoggingEnabled()); +#endif + m_loggingSubmenu = debugLoggingMenu; + // debug->dump submenu + wxMenu* debugDumpMenu = new wxMenu; + debugDumpMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_DUMP_TEXTURES, _("&Textures"), wxEmptyString)->Check(ActiveSettings::DumpTexturesEnabled()); + debugDumpMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_DUMP_SHADERS, _("&Shaders"), wxEmptyString)->Check(ActiveSettings::DumpShadersEnabled()); + debugDumpMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_DUMP_CURL_REQUESTS, _("&nlibcurl HTTP/HTTPS requests"), wxEmptyString); + // debug submenu + wxMenu* debugMenu = new wxMenu; + m_debugMenu = debugMenu; + debugMenu->AppendSubMenu(debugLoggingMenu, _("&Logging")); + debugMenu->AppendSubMenu(debugDumpMenu, _("&Dump")); + debugMenu->AppendSeparator(); + + auto upsidedownItem = debugMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_RENDER_UPSIDE_DOWN, _("&Render upside-down"), wxEmptyString); + upsidedownItem->Check(ActiveSettings::RenderUpsideDownEnabled()); + if(LaunchSettings::RenderUpsideDownEnabled().has_value()) + upsidedownItem->Enable(false); + + auto accurateBarriers = debugMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_VK_ACCURATE_BARRIERS, _("&Accurate barriers (Vulkan)"), wxEmptyString); + accurateBarriers->Check(GetConfig().vk_accurate_barriers); + + debugMenu->AppendSeparator(); + +#ifndef PUBLIC_RELEASE + auto audioAuxOnly = debugMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_AUDIO_AUX_ONLY, _("&Audio AUX only"), wxEmptyString); + audioAuxOnly->Check(ActiveSettings::AudioOutputOnlyAux()); +#endif + +#ifndef PUBLIC_RELEASE + debugMenu->Append(MAINFRAME_MENU_ID_DEBUG_VIEW_LOGGING_WINDOW, _("&Open logging window")); +#endif + debugMenu->Append(MAINFRAME_MENU_ID_DEBUG_VIEW_PPC_THREADS, _("&View PPC threads")); + debugMenu->Append(MAINFRAME_MENU_ID_DEBUG_VIEW_PPC_DEBUGGER, _("&View PPC debugger")); + debugMenu->Append(MAINFRAME_MENU_ID_DEBUG_VIEW_AUDIO_DEBUGGER, _("&View audio debugger")); + debugMenu->Append(MAINFRAME_MENU_ID_DEBUG_VIEW_TEXTURE_RELATIONS, _("&View texture cache info")); + debugMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_SHOW_FRAME_PROFILER, _("&Show frame profiler"), wxEmptyString); + debugMenu->Append(MAINFRAME_MENU_ID_DEBUG_DUMP_RAM, _("&Dump current RAM")); + // debugMenu->Append(MAINFRAME_MENU_ID_DEBUG_DUMP_FST, _("&Dump WUD filesystem"))->Enable(false); + + m_menuBar->Append(debugMenu, _("&Debug")); + // help menu + wxMenu* helpMenu = new wxMenu; + //helpMenu->Append(MAINFRAME_MENU_ID_HELP_WEB, wxT("&Visit website")); + //helpMenu->AppendSeparator(); + m_check_update_menu = helpMenu->Append(MAINFRAME_MENU_ID_HELP_UPDATE, _("&Check for updates")); + helpMenu->Append(MAINFRAME_MENU_ID_HELP_GETTING_STARTED, _("&Getting started")); + helpMenu->AppendSeparator(); + helpMenu->Append(MAINFRAME_MENU_ID_HELP_ABOUT, _("&About Cemu")); + + m_menuBar->Append(helpMenu, _("&Help")); + + SetMenuBar(m_menuBar); + m_menu_visible = true; + + if (m_game_launched) + { + if (m_check_update_menu) + m_check_update_menu->Enable(false); + + m_memorySearcherMenuItem->Enable(true); + m_nfcMenu->Enable(MAINFRAME_MENU_ID_NFC_TOUCH_NFC_FILE, true); + + // disable OpenGL logging (currently cant be toggled after OpenGL backend is initialized) + m_loggingSubmenu->Enable(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + LOG_TYPE_OPENGL, false); + m_loggingSubmenu->Enable(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + LOG_TYPE_VULKAN_VALIDATION, false); + + UpdateNFCMenu(); + } + + // hide new menu in fullscreen + if (IsFullScreen()) + SetMenuVisible(false); +} + +void MainWindow::OnAfterCallShowErrorDialog() +{ + //wxMessageBox((const wxString&)dialogText, (const wxString&)dialogTitle, wxICON_INFORMATION); + //wxDialog* dialog = new wxDialog(NULL,wxID_ANY,(const wxString&)dialogTitle,wxDefaultPosition,wxSize(310,170)); + //dialog->ShowModal(); + //dialogState = 1; +} + +bool MainWindow::EnableOnlineMode() const +{ + // TODO: not used anymore + // + // if enabling online mode, check if all requirements are met + std::wstring additionalErrorInfo; + const sint32 onlineReqError = iosuCrypt_checkRequirementsForOnlineMode(additionalErrorInfo); + + bool enableOnline = false; + if (onlineReqError == IOS_CRYPTO_ONLINE_REQ_OTP_MISSING) + { + wxMessageBox(_("otp.bin could not be found"), _("Error"), wxICON_ERROR); + } + else if (onlineReqError == IOS_CRYPTO_ONLINE_REQ_OTP_CORRUPTED) + { + wxMessageBox(_("otp.bin is corrupted or has invalid size"), _("Error"), wxICON_ERROR); + } + else if (onlineReqError == IOS_CRYPTO_ONLINE_REQ_SEEPROM_MISSING) + { + wxMessageBox(_("seeprom.bin could not be found"), _("Error"), wxICON_ERROR); + } + else if (onlineReqError == IOS_CRYPTO_ONLINE_REQ_SEEPROM_CORRUPTED) + { + wxMessageBox(_("seeprom.bin is corrupted or has invalid size"), _("Error"), wxICON_ERROR); + } + else if (onlineReqError == IOS_CRYPTO_ONLINE_REQ_MISSING_FILE) + { + std::wstring errorMessage = fmt::format(L"Unable to load a necessary file:\n{}", additionalErrorInfo); + wxMessageBox(errorMessage.c_str(), _("Error"), wxICON_ERROR); + } + else if (onlineReqError == IOS_CRYPTO_ONLINE_REQ_OK) + { + enableOnline = true; + } + else + { + wxMessageBox(_("Unknown error occured"), _("Error"), wxICON_ERROR); + } + + //config_get()->enableOnlineMode = enableOnline; + return enableOnline; +} + +void MainWindow::RestoreSettingsAfterGameExited() +{ + RecreateMenu(); +} + +void MainWindow::UpdateSettingsAfterGameLaunch() +{ + m_update_available = {}; + //m_game_launched = true; + RecreateMenu(); +} + +std::string MainWindow::GetRegionString(uint32 region) const +{ + switch (region) + { + case 0x1: + return "JPN"; + case 0x2: + return "USA"; + case 0x4: + return "EUR"; + case 0x10: + return "CHN"; + case 0x20: + return "KOR"; + case 0x40: + return "TWN"; + default: + return std::to_string(region); + } +} + +void MainWindow::OnGraphicWindowClose(wxCloseEvent& event) +{ + m_graphic_pack_window->Destroy(); + m_graphic_pack_window = nullptr; +} + +void MainWindow::OnGraphicWindowOpen(wxTitleIdEvent& event) +{ + if (m_graphic_pack_window) + return; + + m_graphic_pack_window = new GraphicPacksWindow2(this, event.GetTitleId()); + m_graphic_pack_window->Bind(wxEVT_CLOSE_WINDOW, &MainWindow::OnGraphicWindowClose, this); + m_graphic_pack_window->Show(true); +} + +void MainWindow::RequestGameListRefresh() +{ + auto* evt = new wxCommandEvent(wxEVT_REQUEST_GAMELIST_REFRESH); + wxQueueEvent(g_mainFrame, evt); +} + +void MainWindow::RequestLaunchGame(fs::path filePath, wxLaunchGameEvent::INITIATED_BY initiatedBy) +{ + wxLaunchGameEvent evt(filePath, initiatedBy); + wxPostEvent(g_mainFrame, evt); +} diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h new file mode 100644 index 00000000..5b8458cb --- /dev/null +++ b/src/gui/MainWindow.h @@ -0,0 +1,236 @@ +#pragma once + +#include +#include +#include +#include "wxcomponents/checkedlistctrl.h" + +#include "gui/PadViewFrame.h" +#include "gui/MemorySearcherTool.h" + +#include "config/XMLConfig.h" + +#include "gui/LoggingWindow.h" +#include "gui/components/wxGameList.h" + +#include + +class DebuggerWindow2; +struct GameEntry; +class DiscordPresence; +class TitleManager; +class wxLaunchGameEvent; + +wxDECLARE_EVENT(wxEVT_LAUNCH_GAME, wxLaunchGameEvent); +wxDECLARE_EVENT(wxEVT_SET_WINDOW_TITLE, wxCommandEvent); + +class wxLaunchGameEvent : public wxCommandEvent +{ +public: + enum class INITIATED_BY + { + MENU, // via file menu + DRAG_AND_DROP, + GAME_LIST, + TITLE_MANAGER, + COMMAND_LINE, // -g parameter + }; + + wxLaunchGameEvent(fs::path path, INITIATED_BY initiatedBy) + : wxCommandEvent(wxEVT_LAUNCH_GAME), m_launchPath(path), m_initiatedBy(initiatedBy) {} + + [[nodiscard]] fs::path GetPath() const { return m_launchPath; } + [[nodiscard]] INITIATED_BY GetInitiatedBy() const { return m_initiatedBy; } + + wxEvent* Clone() const { return new wxLaunchGameEvent(*this); } + +private: + fs::path m_launchPath; + INITIATED_BY m_initiatedBy; +}; + +class MainWindow : public wxFrame +{ + friend class CemuApp; + +public: + MainWindow(); + ~MainWindow(); + + void UpdateSettingsAfterGameLaunch(); + void RestoreSettingsAfterGameExited(); + + bool FileLoad(std::wstring fileName, wxLaunchGameEvent::INITIATED_BY initiatedBy); + + [[nodiscard]] bool IsGameLaunched() const { return m_game_launched; } + + void SetFullScreen(bool state); + void SetMenuVisible(bool state); + void UpdateNFCMenu(); + bool IsMenuHidden() const; + void TogglePadView(); + +#if BOOST_OS_WINDOWS + WXLRESULT MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) override; +#endif + void OpenSettings(); + + PadViewFrame* GetPadView() const { return m_padView; } + + void OnSizeEvent(wxSizeEvent& event); + void OnMove(wxMoveEvent& event); + + void OnDebuggerClose(wxCloseEvent& event); + void OnPadClose(wxCloseEvent& event); + void OnMemorySearcherClose(wxCloseEvent& event); + void OnMouseWheel(wxMouseEvent& event); + void OnClose(wxCloseEvent& event); + void OnFileMenu(wxCommandEvent& event); + void OnLaunchFromFile(wxLaunchGameEvent& event); + void OnInstallUpdate(wxCommandEvent& event); + void OnFileExit(wxCommandEvent& event); + void OnNFCMenu(wxCommandEvent& event); + void OnOptionsInput(wxCommandEvent& event); + void OnAccountSelect(wxCommandEvent& event); + void OnConsoleLanguage(wxCommandEvent& event); + void OnHelpVistWebpage(wxCommandEvent& event); + void OnHelpAbout(wxCommandEvent& event); + void OnHelpGettingStarted(wxCommandEvent& event); + void OnHelpUpdate(wxCommandEvent& event); + void OnAfterCallShowErrorDialog(); + void OnDebugSetting(wxCommandEvent& event); + void OnDebugLoggingToggleFlagGeneric(wxCommandEvent& event); + void OnPPCInfoToggle(wxCommandEvent& event); + void OnDebugDumpUsedTextures(wxCommandEvent& event); + void OnDebugDumpUsedShaders(wxCommandEvent& event); + void OnLoggingWindow(wxCommandEvent& event); + void OnDebugViewPPCThreads(wxCommandEvent& event); + void OnDebugViewPPCDebugger(wxCommandEvent& event); + void OnDebugViewAudioDebugger(wxCommandEvent& event); + void OnDebugViewTextureRelations(wxCommandEvent& event); + void OnMouseMove(wxMouseEvent& event); + void OnMouseLeft(wxMouseEvent& event); + void OnMouseRight(wxMouseEvent& event); + void OnGameListBeginUpdate(wxCommandEvent& event); + void OnGameListEndUpdate(wxCommandEvent& event); + void OnAccountListRefresh(wxCommandEvent& event); + void OnRequestGameListRefresh(wxCommandEvent& event); + void OnSetWindowTitle(wxCommandEvent& event); + + void OnKeyUp(wxKeyEvent& event); + void OnChar(wxKeyEvent& event); + + void OnToolsInput(wxCommandEvent& event); + void OnGesturePan(wxPanGestureEvent& event); + + void OnGameLoaded(); + + void AsyncSetTitle(std::string_view windowTitle); + + void CreateCanvas(); + void DestroyCanvas(); + + std::wstring GetGameName(std::wstring_view file_name); + static void ShowCursor(bool state); + + uintptr_t GetRenderCanvasHWND(); + + static void RequestGameListRefresh(); + static void RequestLaunchGame(fs::path filePath, wxLaunchGameEvent::INITIATED_BY initiatedBy); + +private: + void RecreateMenu(); + static wxString GetInitialWindowTitle(); + void ShowGettingStartedDialog(); + bool EnableOnlineMode() const; + + bool InstallUpdate(const fs::path& metaFilePath); + + void OnTimer(wxTimerEvent& event); + + wxRect GetDesktopRect(); + + MemorySearcherTool* m_toolWindow = nullptr; + TitleManager* m_title_manager = nullptr; + PadViewFrame* m_padView = nullptr; + wxWindow* m_graphic_pack_window = nullptr; + + wxTimer* m_timer; + wxPoint m_mouse_position{}; + std::chrono::steady_clock::time_point m_last_mouse_move_time; + wxSize m_restored_size; + wxPoint m_restored_position; + + wxStatusBar* m_statusBar{}; + + bool m_menu_visible = false; + bool m_game_launched = false; + + #ifdef ENABLE_DISCORD_RPC + std::unique_ptr m_discord; + #endif + + std::string m_launched_game_name; + + DebuggerWindow2* m_debugger_window = nullptr; + LoggingWindow* m_logging_window = nullptr; + + std::future m_update_available; + + wxMenuItem* m_check_update_menu{}; + void LoadSettings(); + void SaveSettings(); + + std::string GetRegionString(uint32 region) const; + + void OnGraphicWindowClose(wxCloseEvent& event); + void OnGraphicWindowOpen(wxTitleIdEvent& event); + + // panels + wxPanel* m_main_panel{}, * m_game_panel{}; + + // rendering + wxWindow* m_render_canvas{}; + + // gamelist + wxGameList* m_game_list; + wxInfoBar* m_info_bar; + + // menu + wxMenuBar* m_menuBar = nullptr; + + // file + wxMenu* m_fileMenu; + wxMenuItem* m_fileMenuSeparator0; + wxMenuItem* m_fileMenuSeparator1; + wxMenuItem* m_loadMenuItem; + wxMenuItem* m_installUpdateMenuItem; + wxMenuItem* m_exitMenuItem; + + // options + //wxMenu* m_gpuBufferCacheAccuracySubmenu; + wxMenu* m_optionsAccountMenu; + + wxMenuItem* m_fullscreenMenuItem; + wxMenuItem* m_padViewMenuItem; + + // tools + wxMenuItem* m_memorySearcherMenuItem; + + // cpu + //wxMenu* m_cpuModeSubmenu; + wxMenu* m_cpuTimerSubmenu; + + // nfc + wxMenu* m_nfcMenu; + wxMenuItem* m_nfcMenuSeparator0; + + // debug + wxMenu* m_debugMenu; + wxMenu* m_loggingSubmenu; + wxMenuItem* m_asyncCompile; + +wxDECLARE_EVENT_TABLE(); +}; + +extern MainWindow* g_mainFrame; \ No newline at end of file diff --git a/src/gui/MemorySearcherTool.cpp b/src/gui/MemorySearcherTool.cpp new file mode 100644 index 00000000..9fd55dcd --- /dev/null +++ b/src/gui/MemorySearcherTool.cpp @@ -0,0 +1,777 @@ +#include "gui/wxgui.h" + +#include "gui/MemorySearcherTool.h" + +#include +#include +#include + +#include "config/ActiveSettings.h" +#include "gui/helpers/wxHelpers.h" +#include "Common/filestream.h" +#include "util/IniParser/IniParser.h" +#include "util/helpers/StringHelpers.h" +#include "Cafe/CafeSystem.h" + +enum +{ + COMBOBOX_DATATYPE = wxID_HIGHEST + 1, + TEXT_VALUE, + BUTTON_START, + BUTTON_FILTER, + LIST_RESULTS, + LIST_ENTRYTABLE, + TIMER_REFRESH, + LIST_ENTRY_ADD, + LIST_ENTRY_REMOVE, +}; + +wxDEFINE_EVENT(wxEVT_SEARCH_FINISHED, wxCommandEvent); + +wxBEGIN_EVENT_TABLE(MemorySearcherTool, wxFrame) +EVT_CLOSE(MemorySearcherTool::OnClose) +EVT_BUTTON(BUTTON_START, MemorySearcherTool::OnSearch) +EVT_BUTTON(BUTTON_FILTER, MemorySearcherTool::OnFilter) +EVT_TIMER(TIMER_REFRESH, MemorySearcherTool::OnTimerTick) +wxEND_EVENT_TABLE() + +constexpr auto kMaxResultCount = 5000; + +const wxString kDatatypeFloat = "float"; +const wxString kDatatypeDouble = "double"; +const wxString kDatatypeString = "string"; +const wxString kDatatypeInt8 = "int8"; +const wxString kDatatypeInt16 = "int16"; +const wxString kDatatypeInt32 = "int32"; +const wxString kDatatypeInt64 = "int64"; +const wxString kDataTypeNames[] = {kDatatypeFloat,kDatatypeDouble,/*DATATYPE_STRING,*/kDatatypeInt8,kDatatypeInt16,kDatatypeInt32,kDatatypeInt64}; + +MemorySearcherTool::MemorySearcherTool(wxFrame* parent) + : wxFrame(parent, wxID_ANY, _("Memory Searcher"), wxDefaultPosition, wxSize(600, 540), wxDEFAULT_FRAME_STYLE | wxTAB_TRAVERSAL) +{ + this->SetSizeHints(wxDefaultSize, wxDefaultSize); + this->wxWindowBase::SetBackgroundColour(*wxWHITE); + this->wxTopLevelWindowBase::SetMinSize(wxSize(600, 540)); + + auto* sizer = new wxBoxSizer(wxVERTICAL); + + auto* row1 = new wxFlexGridSizer(0, 4, 0, 0); + row1->AddGrowableCol(1); + + m_cbDataType = new wxComboBox(this, COMBOBOX_DATATYPE, kDatatypeFloat, wxDefaultPosition, wxDefaultSize, std::size(kDataTypeNames), kDataTypeNames, wxCB_READONLY); + m_textValue = new wxTextCtrl(this, TEXT_VALUE); + m_buttonStart = new wxButton(this, BUTTON_START, _("Search")); + m_buttonFilter = new wxButton(this, BUTTON_FILTER, _("Filter")); + m_buttonFilter->Disable(); + + row1->Add(m_cbDataType, 0, wxALL, 5); + row1->Add(m_textValue, 0, wxALL | wxEXPAND, 5); + row1->Add(m_buttonStart, 0, wxALL, 5); + row1->Add(m_buttonFilter, 0, wxALL, 5); + + sizer->Add(row1, 0, wxEXPAND, 5); + + auto* row2 = new wxFlexGridSizer(0, 1, 0, 0); + row2->AddGrowableCol(0); + + m_gauge = new wxGauge(this, wxID_ANY, 100, wxDefaultPosition, wxDefaultSize, wxGA_HORIZONTAL); + m_gauge->SetValue(0); + m_gauge->Enable(false); + + m_textEntryTable = new wxStaticText(this, wxID_ANY, _("Results")); + m_listResults = new wxListCtrl(this, LIST_RESULTS, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_SORT_ASCENDING); + m_listResults->Bind(wxEVT_LEFT_DCLICK, &MemorySearcherTool::OnResultListClick, this); + { + wxListItem col0; + col0.SetId(0); + col0.SetText(_("address")); + col0.SetWidth(100); + m_listResults->InsertColumn(0, col0); + wxListItem col1; + col1.SetId(1); + col1.SetText(_("value")); + col1.SetWidth(250); + m_listResults->InsertColumn(1, col1); + } + + auto textEntryTable = new wxStaticText(this, wxID_ANY, _("Stored Entries")); + m_listEntryTable = new wxDataViewListCtrl(this, LIST_ENTRYTABLE, wxDefaultPosition, wxSize(420, 200), wxDV_HORIZ_RULES); + m_listEntryTable->Bind(wxEVT_DATAVIEW_ITEM_CONTEXT_MENU, &MemorySearcherTool::OnEntryListRightClick, this); + m_listEntryTable->Bind(wxEVT_COMMAND_DATAVIEW_ITEM_EDITING_DONE, &MemorySearcherTool::OnItemEdited, this); + { + m_listEntryTable->AppendTextColumn(_("description"), wxDATAVIEW_CELL_EDITABLE, 150, wxALIGN_LEFT, wxDATAVIEW_COL_SORTABLE); + m_listEntryTable->AppendTextColumn(_("address"), wxDATAVIEW_CELL_INERT, 100, wxALIGN_LEFT, wxDATAVIEW_COL_SORTABLE); + m_listEntryTable->AppendTextColumn(_("type")); + m_listEntryTable->AppendTextColumn(_("value"), wxDATAVIEW_CELL_EDITABLE); + m_listEntryTable->AppendToggleColumn(_("freeze"), wxDATAVIEW_CELL_ACTIVATABLE, 50, wxALIGN_LEFT, 0); + } + + row2->AddGrowableRow(3); + row2->AddGrowableRow(5); + + row2->Add(m_gauge, 0, wxALL | wxEXPAND, 5); + row2->Add(0, 10, 1, wxEXPAND, 5); + row2->Add(m_textEntryTable, 0, wxALL, 5); + row2->Add(m_listResults, 1, wxALL | wxEXPAND, 5); + row2->Add(textEntryTable, 0, wxALL, 5); + row2->Add(m_listEntryTable, 1, wxALL | wxEXPAND, 5); + + sizer->Add(row2, 1, wxEXPAND, 5); + + // load stored entries + Load(); + + this->Bind(wxEVT_SEARCH_FINISHED, &MemorySearcherTool::OnSearchFinished, this); + this->Bind(wxEVT_SET_GAUGE_VALUE, &MemorySearcherTool::OnUpdateGauge, this); + + m_refresh_timer = new wxTimer(this, TIMER_REFRESH); + m_refresh_timer->Start(250); + + this->SetSizer(sizer); + this->wxWindowBase::Layout(); + + this->Centre(wxBOTH); +} + +MemorySearcherTool::~MemorySearcherTool() +{ + m_refresh_timer->Stop(); + + m_running = false; + if (m_worker.joinable()) + m_worker.join(); +} + +void MemorySearcherTool::OnTimerTick(wxTimerEvent& event) +{ + RefreshResultList(); + RefreshStashList(); +} + +void MemorySearcherTool::OnClose(wxCloseEvent& event) +{ + Save(); + event.Skip(); +} + +void MemorySearcherTool::OnSearchFinished(wxCommandEvent&) +{ + FillResultList(); + m_search_running = false; + m_buttonStart->Enable(); + m_buttonFilter->Enable(); +} + +void MemorySearcherTool::OnUpdateGauge(wxSetGaugeValue& event) +{ + auto* gauge = event.GetGauge(); + const auto value = event.GetValue(); + if (event.GetRange() != 0) + { + gauge->SetRange(event.GetRange()); + gauge->SetValue(value); + return; + } + + debug_printf("update gauge: %d + %d = %d (/%d)\n", gauge->GetValue(), value, gauge->GetValue() + value, gauge->GetRange()); + gauge->SetValue(gauge->GetValue() + value); +} + +void MemorySearcherTool::OnSearch(wxCommandEvent&) +{ + if (m_clear_state) + { + Reset(); + return; + } + + if (m_search_running) + return; + + if (m_textValue->IsEmpty()) + return; + + SetSearchDataType(); + + if (!VerifySearchValue()) + { + wxMessageBox(_("Your entered value is not valid for the selected datatype."), _("Error"), wxICON_ERROR); + return; + } + m_buttonStart->Disable(); + m_buttonFilter->Disable(); + m_cbDataType->Disable(); + m_buttonStart->SetLabelText(_("Clear")); + m_clear_state = true; + + if (m_worker.joinable()) + m_worker.join(); + + m_worker = std::thread([this]() + { + m_search_jobs.clear(); + uint32 total_size = 0; + for (const auto& itr : memory_getMMURanges()) + { + if (!m_running) + return; + + if (!itr->isMapped()) + continue; + + void* ptr = itr->getPtr(); + const uint32 size = itr->getSize(); + + total_size += (size / kGaugeStep); + m_search_jobs.emplace_back(std::async(std::launch::async, [this, ptr, size]() { return SearchValues(m_searchDataType, ptr, size); })); + } + + wxQueueEvent(this, new wxSetGaugeValue(0, total_size, m_gauge)); + + ListType_t tmp; + for (auto& it : m_search_jobs) + { + const auto result = it.get(); + tmp.insert(tmp.end(), result.cbegin(), result.cend()); + } + + std::unique_lock lock(m_mutex); + m_searchBuffer.swap(tmp); + lock.unlock(); + + wxQueueEvent(this, new wxCommandEvent(wxEVT_SEARCH_FINISHED)); + }); +} + +void MemorySearcherTool::OnFilter(wxCommandEvent& event) +{ + m_buttonStart->Disable(); + m_buttonFilter->Disable(); + + if (m_worker.joinable()) + m_worker.join(); + + m_worker = std::thread([this]() + { + const auto count = (uint32)m_searchBuffer.size(); + wxQueueEvent(this, new wxSetGaugeValue(0, count, m_gauge)); + + auto tmp = FilterValues(m_searchDataType); + + std::unique_lock lock(m_mutex); + m_searchBuffer.swap(tmp); + lock.unlock(); + + wxQueueEvent(this, new wxCommandEvent(wxEVT_SEARCH_FINISHED)); + }); + + m_gauge->SetValue(0); +} + +void MemorySearcherTool::Load() +{ + const auto memorySearcherPath = ActiveSettings::GetPath("memorySearcher/{:016x}.ini", CafeSystem::GetForegroundTitleId()); + auto memSearcherIniContents = FileStream::LoadIntoMemory(memorySearcherPath); + if (!memSearcherIniContents) + return; + + IniParser iniParser(*memSearcherIniContents, _utf8Wrapper(memorySearcherPath)); + while (iniParser.NextSection()) + { + auto option_description = iniParser.FindOption("description"); + auto option_address = iniParser.FindOption("address"); + auto option_type = iniParser.FindOption("type"); + auto option_value = iniParser.FindOption("value"); + + if (!option_description || !option_address || !option_type || !option_value) + continue; + + try + { + const auto addr = StringHelpers::ToInt64(*option_address); + if (!IsAddressValid(addr)) + continue; + } + catch (const std::invalid_argument&) + { + continue; + } + + bool found = false; + for (const auto& entry : kDataTypeNames) + { + if (boost::iequals(entry, *option_type)) + { + found = true; + break; + } + } + + if (!found && !boost::iequals(kDatatypeString, *option_type)) + continue; + + wxVector data; + data.push_back(std::string(*option_description).c_str()); + data.push_back(std::string(*option_address).c_str()); + data.push_back(std::string(*option_type).c_str()); + data.push_back(std::string(*option_value).c_str()); + data.push_back(!option_value->empty()); + m_listEntryTable->AppendItem(data); + } +} + +void MemorySearcherTool::Save() +{ + const auto memorySearcherPath = ActiveSettings::GetPath("memorySearcher/{:016x}.ini", CafeSystem::GetForegroundTitleId()); + FileStream* fs = FileStream::createFile2(memorySearcherPath); + if (fs) + { + for (int i = 0; i < m_listEntryTable->GetItemCount(); ++i) + { + fs->writeLine("[Entry]"); + + std::string tmp = "description=" + std::string(m_listEntryTable->GetTextValue(i, 0).mbc_str()); + fs->writeLine(tmp.c_str()); + + tmp = "address=" + std::string(m_listEntryTable->GetTextValue(i, 1).mbc_str()); + fs->writeLine(tmp.c_str()); + + tmp = "type=" + std::string(m_listEntryTable->GetTextValue(i, 2).mbc_str()); + fs->writeLine(tmp.c_str()); + + // only save value when FREEZE is active + if (m_listEntryTable->GetToggleValue(i, 4)) + { + tmp = "value=" + std::string(m_listEntryTable->GetTextValue(i, 3).mbc_str()); + } + else + { + tmp = "value="; + } + fs->writeLine(tmp.c_str()); + + fs->writeLine(""); + } + delete fs; + } +} + +bool MemorySearcherTool::IsAddressValid(uint32 addr) +{ + for (const auto& itr : memory_getMMURanges()) + { + if (!itr->isMapped()) + continue; + + void* ptr = itr->getPtr(); + const uint32 size = itr->getSize(); + + MEMPTR start((uint8*)ptr); + MEMPTR end((uint8*)ptr + size); + if (start.GetMPTR() <= addr && addr < end.GetMPTR()) + return true; + } + + return false; +} + +void MemorySearcherTool::OnEntryListRightClick(wxDataViewEvent& event) +{ + //void *data = reinterpret_cast(event.GetItem().GetData()); + wxMenu mnu; + //mnu.SetClientData(data); + mnu.Append(LIST_ENTRY_ADD, _("&Add new entry"))->Enable(false); + mnu.Append(LIST_ENTRY_REMOVE, _("&Remove entry")); + mnu.Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MemorySearcherTool::OnPopupClick), nullptr, this); + PopupMenu(&mnu); +} + +void MemorySearcherTool::OnResultListClick(wxMouseEvent& event) +{ + long selectedIndex = -1; + + while (true) + { + selectedIndex = m_listResults->GetNextItem(selectedIndex, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + if (selectedIndex == -1) + break; + + long address = m_listResults->GetItemData(selectedIndex); + auto currValue = m_listResults->GetItemText(selectedIndex, 1); + + char addressString[256]; + sprintf(addressString, "0x%08x", address); + + // description, address, type, value, freeze + wxVector data; + data.push_back(""); + data.push_back(addressString); + data.push_back(m_cbDataType->GetValue()); + data.push_back(currValue); + data.push_back(false); + m_listEntryTable->AppendItem(data); + } +} + +void MemorySearcherTool::Reset() +{ + m_searchBuffer.clear(); + m_buttonStart->SetLabelText(_("Search")); + m_textEntryTable->SetLabelText(_("Results")); + m_buttonFilter->Disable(); + m_cbDataType->Enable(); + m_textValue->SetValue(""); + m_listResults->DeleteAllItems(); + m_clear_state = false; +} + +bool MemorySearcherTool::VerifySearchValue() const +{ + const auto s1 = m_textValue->GetValue(); + const auto s2 = s1.c_str(); + const auto inputString = s2.AsChar(); + + switch (m_searchDataType) + { + case SearchDataType_String: + return true; + case SearchDataType_Float: + { + float value; + return ConvertStringToType(inputString, value); + } + case SearchDataType_Double: + { + double value; + return ConvertStringToType(inputString, value); + } + case SearchDataType_Int8: + { + sint8 value; + return ConvertStringToType(inputString, value); + } + case SearchDataType_Int16: + { + sint16 value; + return ConvertStringToType(inputString, value); + } + case SearchDataType_Int32: + { + sint32 value; + return ConvertStringToType(inputString, value); + } + case SearchDataType_Int64: + { + sint64 value; + return ConvertStringToType(inputString, value); + } + default: + return false; + } +} + +void MemorySearcherTool::FillResultList() +{ + //char text[128]; + //sprintf(text, "Results (%u)", (uint32)m_searchBuffer.size()); + auto text = wxStringFormat(_("Results ({0})"), L"%llu", m_searchBuffer.size()); + m_textEntryTable->SetLabelText(text); + + m_listResults->DeleteAllItems(); + + if (m_searchBuffer.empty() || m_searchBuffer.size() > kMaxResultCount) + return; + + for (const auto& address : m_searchBuffer) + { + const auto index = m_listResults->InsertItem(0, fmt::format("{:#08x}", address.GetMPTR())); + m_listResults->SetItemData(index, address.GetMPTR()); + m_listResults->SetItem(index, 1, m_textValue->GetValue()); + } +} + +void MemorySearcherTool::RefreshResultList() +{ + std::unique_lock lock(m_mutex); + if (m_searchBuffer.empty() || m_searchBuffer.size() > kMaxResultCount) + return; + + for (int i = 0; i < m_listResults->GetItemCount(); ++i) + { + const auto addr = m_listResults->GetItemData(i); + switch (m_searchDataType) + { + case SearchDataType_String: + { + // TODO Peter + break; + } + case SearchDataType_Float: + { + const auto value = memory_read(addr); + m_listResults->SetItem(i, 1, fmt::format("{}", value)); + break; + } + case SearchDataType_Double: + { + const auto value = memory_read(addr); + m_listResults->SetItem(i, 1, fmt::format("{}", value)); + break; + } + case SearchDataType_Int8: + { + const auto value = memory_read(addr); + m_listResults->SetItem(i, 1, fmt::format("{}", value)); + break; + } + case SearchDataType_Int16: + { + const auto value = memory_read(addr); + m_listResults->SetItem(i, 1, fmt::format("{}", value)); + break; + } + case SearchDataType_Int32: + { + const auto value = memory_read(addr); + m_listResults->SetItem(i, 1, fmt::format("{}", value)); + break; + } + case SearchDataType_Int64: + { + const auto value = memory_read(addr); + m_listResults->SetItem(i, 1, fmt::format("{}", value)); + break; + } + } + } +} + +void MemorySearcherTool::RefreshStashList() +{ + for (int i = 0; i < m_listEntryTable->GetItemCount(); ++i) + { + auto freeze = m_listEntryTable->GetToggleValue(i, 4); + + auto addressText = std::string(m_listEntryTable->GetTextValue(i, 1).mbc_str()); + auto type = std::string(m_listEntryTable->GetTextValue(i, 2).mbc_str()); + + auto address = stol(addressText, nullptr, 16); + + // if freeze is activated, set the value instead of refreshing it + if (freeze) + { + wxVariant var; + m_listEntryTable->GetValue(var, i, 3); + if (type == kDatatypeFloat) + { + auto value = (float)var.GetDouble(); + memory_writeFloat(address, value); + } + else if (type == kDatatypeDouble) + { + auto value = var.GetDouble(); + memory_writeDouble(address, value); + } + else if (type == kDatatypeInt8) + { + auto value = var.GetInteger(); + memory_writeU8(address, value); + } + else if (type == kDatatypeInt16) + { + auto value = var.GetInteger(); + memory_writeU16(address, value); + } + else if (type == kDatatypeInt32) + { + auto value = var.GetInteger(); + memory_writeU32(address, value); + } + else if (type == kDatatypeInt64) + { + auto valueText = std::string(var.GetString().mbc_str()); + auto value = stoull(valueText); + memory_writeU64(address, value); + } + else if (type == kDatatypeString) + { + auto valueText = std::string(var.GetString().mbc_str()); + for (int i = 0; i < valueText.size(); ++i) + { + memory_writeU8(address + i, valueText[i]); + } + memory_writeU8(address + valueText.size(), 0x00); + } + } + else + { + if (type == kDatatypeFloat) + { + auto value = memory_readFloat(address); + m_listEntryTable->SetValue(fmt::format("{}", value), i, 3); + } + else if (type == kDatatypeDouble) + { + auto value = memory_readDouble(address); + m_listEntryTable->SetValue(fmt::format("{}", value), i, 3); + } + else if (type == kDatatypeInt8) + { + auto value = (sint8)memory_readU8(address); + m_listEntryTable->SetValue(fmt::format("{}", (int)value), i, 3); + } + else if (type == kDatatypeInt16) + { + auto value = (sint16)memory_readU16(address); + m_listEntryTable->SetValue(fmt::format("{}", value), i, 3); + } + else if (type == kDatatypeInt32) + { + auto value = (sint32)memory_readU32(address); + m_listEntryTable->SetValue(fmt::format("{}", value), i, 3); + } + else if (type == kDatatypeInt64) + { + auto value = (sint64)memory_readU64(address); + m_listEntryTable->SetValue(fmt::format("{}", value), i, 3); + } + else if (type == kDatatypeString) + { + // TODO Peter + } + } + } +} + +void MemorySearcherTool::SetSearchDataType() +{ + const auto type = m_cbDataType->GetStringSelection(); + if (type == kDatatypeFloat) + m_searchDataType = SearchDataType_Float; + else if (type == kDatatypeDouble) + m_searchDataType = SearchDataType_Double; + else if (type == kDatatypeInt8) + m_searchDataType = SearchDataType_Int8; + else if (type == kDatatypeInt16) + m_searchDataType = SearchDataType_Int16; + else if (type == kDatatypeInt32) + m_searchDataType = SearchDataType_Int32; + else if (type == kDatatypeInt64) + m_searchDataType = SearchDataType_Int64; + else if (type == kDatatypeString) + m_searchDataType = SearchDataType_String; + else + m_searchDataType = SearchDataType_None; +} + +std::string MemorySearcherTool::GetSearchTypeName() const +{ + switch (m_searchDataType) + { + case SearchDataType_String: + return from_wxString(kDatatypeString); + case SearchDataType_Float: + return from_wxString(kDatatypeFloat); + case SearchDataType_Double: + return from_wxString(kDatatypeDouble); + case SearchDataType_Int8: + return from_wxString(kDatatypeInt8); + case SearchDataType_Int16: + return from_wxString(kDatatypeInt16); + case SearchDataType_Int32: + return from_wxString(kDatatypeInt32); + case SearchDataType_Int64: + return from_wxString(kDatatypeInt64); + default: + return ""; + } + +} + +template <> +bool MemorySearcherTool::ConvertStringToType(const char* inValue, sint8& outValue) const +{ + sint16 tmp; + std::istringstream iss(inValue); + iss >> std::noskipws >> tmp; + if (iss && iss.eof()) + { + if (SCHAR_MIN <= tmp && tmp <= SCHAR_MAX) + { + outValue = tmp; + return true; + } + } + return false; +} + +void MemorySearcherTool::OnPopupClick(wxCommandEvent& event) +{ + if (event.GetId() == LIST_ENTRY_REMOVE) + { + const int row = m_listEntryTable->GetSelectedRow(); + if (row == -1) + return; + + m_listEntryTable->DeleteItem(row); + } +} + +void MemorySearcherTool::OnItemEdited(wxDataViewEvent& event) +{ + auto column = event.GetColumn(); + // Edit description + if (column == 0) { } + // Edit value + else if (column == 3) + { + auto row = m_listEntryTable->GetSelectedRow(); + if (row == -1) + return; + + auto addressText = std::string(m_listEntryTable->GetTextValue(row, 1).mbc_str()); + uint32 address = stoul(addressText, nullptr, 16); + + auto type = m_listEntryTable->GetTextValue(row, 2); + if (type == kDatatypeFloat) + { + auto value = (float)event.GetValue().GetDouble(); + memory_writeFloat(address, value); + } + else if (type == kDatatypeDouble) + { + auto value = event.GetValue().GetDouble(); + memory_writeDouble(address, value); + } + else if (type == kDatatypeInt8) + { + auto value = event.GetValue().GetInteger(); + memory_writeU8(address, value); + } + else if (type == kDatatypeInt16) + { + auto value = event.GetValue().GetInteger(); + memory_writeU16(address, value); + } + else if (type == kDatatypeInt32) + { + auto value = event.GetValue().GetInteger(); + memory_writeU32(address, value); + } + else if (type == kDatatypeInt64) + { + auto valueText = std::string(event.GetValue().GetString().mbc_str()); + auto value = stoull(valueText); + memory_writeU64(address, value); + } + else if (type == kDatatypeString) + { + auto valueText = std::string(event.GetValue().GetString().mbc_str()); + for (int i = 0; i < valueText.size(); ++i) + { + memory_writeU8(address + i, valueText[i]); + } + memory_writeU8(address + valueText.size(), 0x00); + } + } +} diff --git a/src/gui/MemorySearcherTool.h b/src/gui/MemorySearcherTool.h new file mode 100644 index 00000000..2f891062 --- /dev/null +++ b/src/gui/MemorySearcherTool.h @@ -0,0 +1,262 @@ +#pragma once + +#include +#include +#include + +#include "Cafe/HW/MMU/MMU.h" +#include "util/helpers/helpers.h" +#include "gui/helpers/wxCustomEvents.h" + +enum SearchDataType +{ + SearchDataType_None, + SearchDataType_String, + SearchDataType_Float, + SearchDataType_Double, + SearchDataType_Int8, + SearchDataType_Int16, + SearchDataType_Int32, + SearchDataType_Int64, +}; + +struct TableEntry_t +{ + uint32 address; + const char description[32]; + SearchDataType type; + bool freeze; + uint16 dataLen; + uint8* data; +}; + +class MemorySearcherTool : public wxFrame +{ +public: + MemorySearcherTool(wxFrame* parent); + virtual ~MemorySearcherTool(); + + void OnTimerTick(wxTimerEvent& event); + void OnClose(wxCloseEvent&); + void OnSearch(wxCommandEvent&); + void OnSearchFinished(wxCommandEvent& event); + void OnUpdateGauge(wxSetGaugeValue& event); + void OnFilter(wxCommandEvent& event); + void OnResultListClick(wxMouseEvent& event); + void OnEntryListRightClick(wxDataViewEvent& event); + //void OnEntryListRightClick2(wxContextMenuEvent& event); + void OnPopupClick(wxCommandEvent& event); + + void OnItemEdited(wxDataViewEvent& event); + +private: + void Reset(); + bool VerifySearchValue() const; + void FillResultList(); + void RefreshResultList(); + void RefreshStashList(); + void SetSearchDataType(); + std::string GetSearchTypeName() const; + void CreateRightClickPopupMenu(); + + void Load(); + void Save(); + + static bool IsAddressValid(uint32 addr); + + template + bool ConvertStringToType(const char* inValue, T& outValue) const + { + std::istringstream iss(inValue); + iss >> std::noskipws >> outValue; + return iss && iss.eof(); + } + + template <> + bool ConvertStringToType(const char* inValue, sint8& outValue) const; + + using ListType_t = std::vector>; + ListType_t SearchValues(SearchDataType type, void* ptr, uint32 size) + { + /*if (type == SearchDataType_String) + return SearchValues((const char* )ptr, size); + else */if (type == SearchDataType_Float) + return SearchValues((float*)ptr, size); + else if (type == SearchDataType_Double) + return SearchValues((double*)ptr, size); + else if (type == SearchDataType_Int8) + return SearchValues((sint8*)ptr, size); + else if (type == SearchDataType_Int16) + return SearchValues((sint16*)ptr, size); + else if (type == SearchDataType_Int32) + return SearchValues((sint32*)ptr, size); + else if (type == SearchDataType_Int64) + return SearchValues((sint64*)ptr, size); + return {}; + } + + ListType_t FilterValues(SearchDataType type) + { + /*if (type == SearchDataType_String) + return FilterValues(); + else */if (type == SearchDataType_Float) + return FilterValues(); + else if (type == SearchDataType_Double) + return FilterValues(); + else if (type == SearchDataType_Int8) + return FilterValues(); + else if (type == SearchDataType_Int16) + return FilterValues(); + else if (type == SearchDataType_Int32) + return FilterValues(); + else if (type == SearchDataType_Int64) + return FilterValues(); + return {}; + } + + constexpr static int kGaugeStep = 0x10000; + + template + ListType_t SearchValues(T* ptr, uint32 size) + { + const auto value = m_textValue->GetValue(); + const auto* string_value = value.c_str().AsChar(); + const auto search_value = ConvertString(string_value); + + const auto* end = (T*)((uint8*)ptr + size - sizeof(T)); + + uint32 counter = 0; + std::vector> result; + for (; ptr < end; ++ptr) + { + if (!m_running) + return result; + + const auto tmp = (betype*)ptr; + if (equals(search_value, tmp->value())) + result.emplace_back(ptr); + + counter += sizeof(T); + if(counter >= kGaugeStep) + { + wxQueueEvent(this, new wxSetGaugeValue(1, m_gauge)); + counter -= kGaugeStep; + } + } + + return result; + } + + template + ListType_t FilterValues() + { + const auto value = m_textValue->GetValue(); + const auto* string_value = value.c_str().AsChar(); + const auto search_value = ConvertString(string_value); + + ListType_t newSearchBuffer; + newSearchBuffer.reserve(m_searchBuffer.size()); + + for (const auto& it : m_searchBuffer) + { + if (!m_running) + return newSearchBuffer; + + const auto tmp = (betype*)it.GetPtr(); + if (equals(search_value , tmp->value())) + newSearchBuffer.emplace_back(it); + + wxQueueEvent(this, new wxSetGaugeValue(1, m_gauge)); + } + + return newSearchBuffer; + } + +wxDECLARE_EVENT_TABLE(); + + SearchDataType m_searchDataType = SearchDataType_None; + wxComboBox* m_cbDataType; + wxTextCtrl* m_textValue; + wxButton *m_buttonStart, *m_buttonFilter; + wxListCtrl* m_listResults; + wxDataViewListCtrl* m_listEntryTable; + wxStaticText* m_textEntryTable; + wxGauge* m_gauge; + wxTimer* m_refresh_timer; + + ListType_t m_searchBuffer; + std::vector m_tableEntries; + + std::mutex m_mutex; + std::thread m_worker; + + std::atomic_bool m_running = true; + std::atomic_bool m_search_running = false; + std::vector> m_search_jobs; + + bool m_clear_state = false; +}; + + + + +// +//template +//void MemorySearcherTool::FilterValues() +//{ +// auto s1 = m_textValue->GetValue(); +// auto s2 = s1.c_str(); +// auto stringValue = s2.AsChar(); +// +// T filterValue; +// ConvertStringToType(stringValue, filterValue); +// +// std::vector newSearchBuffer; +// newSearchBuffer.reserve(m_searchBuffer.size()); +// +// uint32 gaugeValue = 0; +// uint32 count = m_searchBuffer.size(); +// uint32 counter = 0; +// +// for (auto it = m_searchBuffer.begin(); it != m_searchBuffer.end(); ++it) +// { +// if (m_running) +// return; +// +// T value = memory_read(*it); +// if constexpr (std::is_same::value) +// { +// //float value1 = filterValue * 0.95f; +// float diff = filterValue - value; +// diff = fabs(diff); +// if(diff < value*0.05f) +// { +// newSearchBuffer.push_back(*it); +// } +// +// } +// else +// { +// if (value == filterValue) +// { +// newSearchBuffer.push_back(*it); +// } +// } +// +// ++counter; +// uint32 newValue = counter * 100 / count; +// if (newValue > gaugeValue + GAUGE_STEP_SIZE) +// { +// gaugeValue = newValue; +// m_gaugeValue = newValue; +// } +// } +// +// m_mutex.lock(); +// m_searchBuffer = newSearchBuffer; +// m_gaugeValue = 100; +// m_mutex.unlock(); +// m_isSearchFinished = true; +//} +// + diff --git a/src/gui/PadViewFrame.cpp b/src/gui/PadViewFrame.cpp new file mode 100644 index 00000000..8ac016ee --- /dev/null +++ b/src/gui/PadViewFrame.cpp @@ -0,0 +1,190 @@ +#include "gui/wxgui.h" +#include "gui/guiWrapper.h" +#include "gui/PadViewFrame.h" + +#include + +#include "config/ActiveSettings.h" +#include "Cafe/OS/libs/swkbd/swkbd.h" +#include "gui/canvas/OpenGLCanvas.h" +#include "gui/canvas/VulkanCanvas.h" +#include "config/CemuConfig.h" +#include "gui/MainWindow.h" +#include "gui/helpers/wxHelpers.h" +#include "input/InputManager.h" + +#if BOOST_OS_LINUX +#include "resource/linux/resources.h" +#endif +#include "wxHelper.h" + +extern WindowInfo g_window_info; + +PadViewFrame::PadViewFrame(wxFrame* parent) + : wxFrame(nullptr, wxID_ANY, "GamePad View", wxDefaultPosition, wxSize(854, 480), wxMINIMIZE_BOX | wxMAXIMIZE_BOX | wxSYSTEM_MENU | wxCAPTION | wxCLIP_CHILDREN | wxRESIZE_BORDER | wxCLOSE_BOX | wxWANTS_CHARS) +{ + gui_initHandleContextFromWxWidgetsWindow(g_window_info.window_pad, this); + + SetIcon(wxICON(M_WND_ICON128)); + wxWindow::EnableTouchEvents(wxTOUCH_PAN_GESTURES); + + SetMinClientSize({ 320, 180 }); + + SetPosition({ g_window_info.restored_pad_x, g_window_info.restored_pad_y }); + SetSize({ g_window_info.restored_pad_width, g_window_info.restored_pad_height }); + + if (g_window_info.pad_maximized) + Maximize(); + + Bind(wxEVT_SIZE, &PadViewFrame::OnSizeEvent, this); + Bind(wxEVT_MOVE, &PadViewFrame::OnMoveEvent, this); + Bind(wxEVT_MOTION, &PadViewFrame::OnMouseMove, this); + + Bind(wxEVT_SET_WINDOW_TITLE, &PadViewFrame::OnSetWindowTitle, this); + + g_window_info.pad_open = true; +} + +PadViewFrame::~PadViewFrame() +{ + g_window_info.pad_open = false; +} + +bool PadViewFrame::Initialize() +{ + const wxSize client_size = GetClientSize(); + g_window_info.pad_width = client_size.GetWidth(); + g_window_info.pad_height = client_size.GetHeight(); + + return true; +} + +void PadViewFrame::InitializeRenderCanvas() +{ + auto sizer = new wxBoxSizer(wxVERTICAL); + { + if (ActiveSettings::GetGraphicsAPI() == kVulkan) + m_render_canvas = new VulkanCanvas(this, wxSize(854, 480), false); + else + m_render_canvas = GLCanvas_Create(this, wxSize(854, 480), false); + sizer->Add(m_render_canvas, 1, wxEXPAND, 0, nullptr); + } + SetSizer(sizer); + Layout(); + + m_render_canvas->Bind(wxEVT_KEY_UP, &PadViewFrame::OnKeyUp, this); + m_render_canvas->Bind(wxEVT_CHAR, &PadViewFrame::OnChar, this); + + m_render_canvas->Bind(wxEVT_MOTION, &PadViewFrame::OnMouseMove, this); + m_render_canvas->Bind(wxEVT_LEFT_DOWN, &PadViewFrame::OnMouseLeft, this); + m_render_canvas->Bind(wxEVT_LEFT_UP, &PadViewFrame::OnMouseLeft, this); + m_render_canvas->Bind(wxEVT_RIGHT_DOWN, &PadViewFrame::OnMouseRight, this); + m_render_canvas->Bind(wxEVT_RIGHT_UP, &PadViewFrame::OnMouseRight, this); + + m_render_canvas->Bind(wxEVT_GESTURE_PAN, &PadViewFrame::OnGesturePan, this); + + m_render_canvas->SetFocus(); +} + +void PadViewFrame::OnSizeEvent(wxSizeEvent& event) +{ + if (!IsMaximized() && !IsFullScreen()) + { + g_window_info.restored_pad_width = GetSize().x; + g_window_info.restored_pad_height = GetSize().y; + } + g_window_info.pad_maximized = IsMaximized() && !IsFullScreen(); + + const wxSize client_size = GetClientSize(); + g_window_info.pad_width = client_size.GetWidth(); + g_window_info.pad_height = client_size.GetHeight(); + + event.Skip(); +} + +void PadViewFrame::OnMoveEvent(wxMoveEvent& event) +{ + if (!IsMaximized() && !IsFullScreen()) + { + g_window_info.restored_pad_x = GetPosition().x; + g_window_info.restored_pad_y = GetPosition().y; + } +} + +void PadViewFrame::OnKeyUp(wxKeyEvent& event) +{ + event.Skip(); + + if (swkbd_hasKeyboardInputHook()) + return; + + const auto code = event.GetKeyCode(); + if (code == WXK_ESCAPE) + ShowFullScreen(false); + else if (code == WXK_RETURN && event.AltDown()) + ShowFullScreen(!IsFullScreen()); +} + +void PadViewFrame::OnGesturePan(wxPanGestureEvent& event) +{ + auto& instance = InputManager::instance(); + + std::scoped_lock lock(instance.m_pad_touch.m_mutex); + instance.m_pad_touch.position = { event.GetPosition().x, event.GetPosition().y }; + instance.m_pad_touch.left_down = event.IsGestureStart() || !event.IsGestureEnd(); + if (event.IsGestureStart() || !event.IsGestureEnd()) + instance.m_pad_touch.left_down_toggle = true; +} + +void PadViewFrame::OnChar(wxKeyEvent& event) +{ + if (swkbd_hasKeyboardInputHook()) + swkbd_keyInput(event.GetUnicodeKey()); + + event.Skip(); +} + +void PadViewFrame::OnMouseMove(wxMouseEvent& event) +{ + auto& instance = InputManager::instance(); + + std::scoped_lock lock(instance.m_pad_touch.m_mutex); + instance.m_pad_mouse.position = { event.GetPosition().x, event.GetPosition().y }; + + event.Skip(); +} + +void PadViewFrame::OnMouseLeft(wxMouseEvent& event) +{ + auto& instance = InputManager::instance(); + + std::scoped_lock lock(instance.m_pad_mouse.m_mutex); + instance.m_pad_mouse.left_down = event.ButtonDown(wxMOUSE_BTN_LEFT); + instance.m_pad_mouse.position = { event.GetPosition().x, event.GetPosition().y }; + if (event.ButtonDown(wxMOUSE_BTN_LEFT)) + instance.m_pad_mouse.left_down_toggle = true; + +} + +void PadViewFrame::OnMouseRight(wxMouseEvent& event) +{ + auto& instance = InputManager::instance(); + + std::scoped_lock lock(instance.m_pad_mouse.m_mutex); + instance.m_pad_mouse.right_down = event.ButtonDown(wxMOUSE_BTN_LEFT); + instance.m_pad_mouse.position = { event.GetPosition().x, event.GetPosition().y }; + if (event.ButtonDown(wxMOUSE_BTN_RIGHT)) + instance.m_pad_mouse.right_down_toggle = true; +} + +void PadViewFrame::OnSetWindowTitle(wxCommandEvent& event) +{ + this->SetTitle(event.GetString()); +} + +void PadViewFrame::AsyncSetTitle(std::string_view windowTitle) +{ + wxCommandEvent set_title_event(wxEVT_SET_WINDOW_TITLE); + set_title_event.SetString(wxHelper::FromUtf8(windowTitle)); + QueueEvent(set_title_event.Clone()); +} \ No newline at end of file diff --git a/src/gui/PadViewFrame.h b/src/gui/PadViewFrame.h new file mode 100644 index 00000000..e808ad1d --- /dev/null +++ b/src/gui/PadViewFrame.h @@ -0,0 +1,34 @@ +#pragma once + +#define WM_CREATE_PAD (WM_USER+1) +#define WM_DESTROY_PAD (WM_USER+2) + +wxDECLARE_EVENT(EVT_PAD_CLOSE, wxCommandEvent); +wxDECLARE_EVENT(EVT_SET_WINDOW_TITLE, wxCommandEvent); + +class PadViewFrame : public wxFrame +{ +public: + PadViewFrame(wxFrame* parent); + ~PadViewFrame(); + + bool Initialize(); + void InitializeRenderCanvas(); + + void OnKeyUp(wxKeyEvent& event); + void OnChar(wxKeyEvent& event); + + void AsyncSetTitle(std::string_view windowTitle); + +private: + + void OnMouseMove(wxMouseEvent& event); + void OnMouseLeft(wxMouseEvent& event); + void OnMouseRight(wxMouseEvent& event); + void OnSizeEvent(wxSizeEvent& event); + void OnMoveEvent(wxMoveEvent& event); + void OnGesturePan(wxPanGestureEvent& event); + void OnSetWindowTitle(wxCommandEvent& event); + + wxWindow* m_render_canvas = nullptr; +}; \ No newline at end of file diff --git a/src/gui/TitleManager.cpp b/src/gui/TitleManager.cpp new file mode 100644 index 00000000..ba3cfc19 --- /dev/null +++ b/src/gui/TitleManager.cpp @@ -0,0 +1,892 @@ +#include "gui/TitleManager.h" + +#ifdef _WIN32 +#include +#endif + +#include "gui/helpers/wxCustomEvents.h" +#include "gui/helpers/wxCustomData.h" +#include "Cafe/TitleList/GameInfo.h" +#include "util/helpers/helpers.h" +#include "gui/helpers/wxHelpers.h" +#include "gui/components/wxTitleManagerList.h" +#include "gui/components/wxDownloadManagerList.h" +#include "gui/GameUpdateWindow.h" +#include "gui/dialogs/SaveImport/SaveTransfer.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "config/ActiveSettings.h" +#include "gui/dialogs/SaveImport/SaveImportWindow.h" +#include "Cafe/Account/Account.h" +#include "Cemu/Tools/DownloadManager/DownloadManager.h" +#include "gui/CemuApp.h" + +#include "Cafe/TitleList/TitleList.h" + +#if BOOST_OS_LINUX +#include "resource/linux/resources.h" +#endif +#include "Cafe/TitleList/SaveList.h" + +wxDEFINE_EVENT(wxEVT_TITLE_FOUND, wxCommandEvent); +wxDEFINE_EVENT(wxEVT_TITLE_SEARCH_COMPLETE, wxCommandEvent); +wxDEFINE_EVENT(wxEVT_DL_TITLE_UPDATE, wxCommandEvent); +wxDEFINE_EVENT(wxEVT_DL_DISCONNECT_COMPLETE, wxCommandEvent); + +wxPanel* TitleManager::CreateTitleManagerPage() +{ + auto* panel = new wxPanel(m_notebook); + auto* sizer = new wxBoxSizer(wxVERTICAL); + { + auto* row = new wxFlexGridSizer(0, 4, 0, 0); + row->AddGrowableCol(1); + + row->Add(new wxStaticText(panel, wxID_ANY, _("Filter")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + + m_filter = new wxTextCtrl(panel, wxID_ANY); + m_filter->Bind(wxEVT_TEXT, &TitleManager::OnFilterChanged, this); + row->Add(m_filter, 1, wxALL | wxEXPAND, 5); + + const wxImage refresh = wxBITMAP_PNG(PNG_REFRESH).ConvertToImage(); + m_refresh_button = new wxBitmapButton(panel, wxID_ANY, refresh.Scale(16, 16)); + m_refresh_button->Disable(); + m_refresh_button->Bind(wxEVT_BUTTON, &TitleManager::OnRefreshButton, this); + m_refresh_button->SetToolTip(_("Refresh")); + row->Add(m_refresh_button, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + + auto* help_button = new wxStaticBitmap(panel, wxID_ANY, wxBITMAP_PNG(PNG_HELP)); + help_button->SetToolTip(wxStringFormat2(_("The following prefixes are supported:\n{0}\n{1}\n{2}\n{3}\n{4}"), + "titleid:", "name:", "type:", "version:", "region:")); + row->Add(help_button, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + + sizer->Add(row, 0, wxEXPAND, 5); + } + + sizer->Add(new wxStaticLine(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL), 0, wxEXPAND | wxALL, 5); + + m_title_list = new wxTitleManagerList(panel); + m_title_list->SetSizeHints(800, 600); + m_title_list->Bind(wxEVT_LIST_ITEM_SELECTED, &TitleManager::OnTitleSelected, this); + sizer->Add(m_title_list, 1, wxALL | wxEXPAND, 5); + + sizer->Add(new wxStaticLine(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL), 0, wxEXPAND | wxALL, 5); + + { + auto* row = new wxFlexGridSizer(0, 3, 0, 0); + row->AddGrowableCol(2); + + auto* install_button = new wxButton(panel, wxID_ANY, _("Install title")); + install_button->Bind(wxEVT_BUTTON, &TitleManager::OnInstallTitle, this); + row->Add(install_button, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + + row->Add(new wxStaticLine(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL), 0, wxEXPAND | wxALL, 5); + + { + m_save_panel = new wxPanel(panel); + auto* save_sizer = new wxFlexGridSizer(0, 7, 0, 0); + + save_sizer->Add(new wxStaticText(m_save_panel, wxID_ANY, _("Account")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + + m_save_account_list = new wxChoice(m_save_panel, wxID_ANY); + m_save_account_list->SetMinSize({ 170, -1 }); + m_save_account_list->Bind(wxEVT_CHOICE, &TitleManager::OnSaveAccountSelected, this); + save_sizer->Add(m_save_account_list, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + + auto* save_open = new wxButton(m_save_panel, wxID_ANY, _("Open directory")); + save_open->Bind(wxEVT_BUTTON, &TitleManager::OnSaveOpenDirectory, this); + save_open->SetToolTip(_("Open the directory of the save entry")); + save_open->Disable(); + save_sizer->Add(save_open, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + + auto* save_transfer = new wxButton(m_save_panel, wxID_ANY, _("Transfer")); + save_transfer->Bind(wxEVT_BUTTON, &TitleManager::OnSaveTransfer, this); + save_transfer->SetToolTip(_("Transfers the save entry to another persistent account id")); + save_transfer->Disable(); + save_sizer->Add(save_transfer, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + + auto* save_delete = new wxButton(m_save_panel, wxID_ANY, _("Delete")); + save_delete->Bind(wxEVT_BUTTON, &TitleManager::OnSaveDelete, this); + save_delete->SetToolTip(_("Irrevocable delete the save entry ")); + save_delete->Disable(); + save_sizer->Add(save_delete, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + + m_save_import = new wxButton(m_save_panel, wxID_ANY, _("Import")); + m_save_import->Bind(wxEVT_BUTTON, &TitleManager::OnSaveImport, this); + m_save_import->SetToolTip(_("Imports a zipped save entry")); + save_sizer->Add(m_save_import, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxTOP | wxBOTTOM, 5); + + auto* export_bttn = new wxButton(m_save_panel, wxID_ANY, _("Export")); + export_bttn->Bind(wxEVT_BUTTON, &TitleManager::OnSaveExport, this); + export_bttn->SetToolTip(_("Exports the selected save entry as zip file")); + export_bttn->Disable(); + save_sizer->Add(export_bttn, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT | wxTOP | wxBOTTOM, 5); + + m_save_panel->SetSizerAndFit(save_sizer); + row->Add(m_save_panel, 1, wxRESERVE_SPACE_EVEN_IF_HIDDEN | wxALIGN_CENTER_VERTICAL, 0); + m_save_panel->Hide(); // hide by default + } + + sizer->Add(row, 0, wxEXPAND, 5); + } + + panel->SetSizerAndFit(sizer); + return panel; +} + +wxPanel* TitleManager::CreateDownloadManagerPage() +{ + auto* panel = new wxPanel(m_notebook); + auto* sizer = new wxBoxSizer(wxVERTICAL); + { + auto* row = new wxBoxSizer(wxHORIZONTAL); + + m_account = new wxChoice(panel, wxID_ANY); + m_account->SetMinSize({ 250,-1 }); + auto accounts = Account::GetAccounts(); + if (!accounts.empty()) + { + const auto id = GetConfig().account.m_persistent_id.GetValue(); + for (const auto& a : accounts) + { + m_account->Append(a.ToString(), (void*)static_cast(a.GetPersistentId())); + if(a.GetPersistentId() == id) + { + m_account->SetSelection(m_account->GetCount() - 1); + } + } + } + + row->Add(m_account, 0, wxALL, 5); + + m_connect = new wxButton(panel, wxID_ANY, _("Connect")); + m_connect->Bind(wxEVT_BUTTON, &TitleManager::OnConnect, this); + row->Add(m_connect, 0, wxALL, 5); + + sizer->Add(row, 0, wxEXPAND, 5); + } + + m_status_text = new wxStaticText(panel, wxID_ANY, _("Select an account and press Connect")); + this->Bind(wxEVT_SET_TEXT, &TitleManager::OnSetStatusText, this); + sizer->Add(m_status_text, 0, wxALL, 5); + + sizer->Add(new wxStaticLine(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL), 0, wxEXPAND | wxALL, 5); + + { + auto* row = new wxFlexGridSizer(0, 3, 0, 0); + m_show_titles = new wxCheckBox(panel, wxID_ANY, _("Show available titles")); + m_show_titles->SetValue(true); + m_show_titles->Enable(false); + row->Add(m_show_titles, 0, wxALL, 5); + m_show_titles->Bind(wxEVT_CHECKBOX, &TitleManager::OnDlFilterCheckbox, this); + + m_show_updates = new wxCheckBox(panel, wxID_ANY, _("Show available updates")); + m_show_updates->SetValue(true); + m_show_updates->Enable(false); + row->Add(m_show_updates, 0, wxALL, 5); + m_show_updates->Bind(wxEVT_CHECKBOX, &TitleManager::OnDlFilterCheckbox, this); + + m_show_installed = new wxCheckBox(panel, wxID_ANY, _("Show installed")); + m_show_installed->SetValue(true); + m_show_installed->Enable(false); + row->Add(m_show_installed, 0, wxALL, 5); + m_show_installed->Bind(wxEVT_CHECKBOX, &TitleManager::OnDlFilterCheckbox, this); + + sizer->Add(row, 0, wxEXPAND, 5); + } + + m_download_list = new wxDownloadManagerList(panel); + m_download_list->SetSizeHints(800, 600); + m_download_list->Bind(wxEVT_LIST_ITEM_SELECTED, &TitleManager::OnTitleSelected, this); + sizer->Add(m_download_list, 1, wxALL | wxEXPAND, 5); + + panel->SetSizerAndFit(sizer); + return panel; +} + +TitleManager::TitleManager(wxWindow* parent, TitleManagerPage default_page) + : wxFrame(parent, wxID_ANY, _("Title manager"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE | wxTAB_TRAVERSAL) +{ + SetIcon(wxICON(X_BOX)); + + auto* sizer = new wxBoxSizer(wxVERTICAL); + m_notebook = new wxNotebook(this, wxID_ANY); + + m_notebook->AddPage(CreateTitleManagerPage(), _("Title Manager"), default_page == TitleManagerPage::TitleManager); + m_notebook->AddPage(CreateDownloadManagerPage(), _("Download Manager"), default_page == TitleManagerPage::DownloadManager); + + sizer->Add(m_notebook, 1, wxEXPAND); + + m_status_bar = CreateStatusBar(2, wxSTB_SIZEGRIP); + m_status_bar->SetStatusText(_("Searching for titles...")); + + this->SetSizerAndFit(sizer); + this->Centre(wxBOTH); + + this->Bind(wxEVT_SET_STATUS_BAR_TEXT, &TitleManager::OnSetStatusBarText, this); + this->Bind(wxEVT_TITLE_FOUND, &TitleManager::OnTitleFound, this); + this->Bind(wxEVT_TITLE_SEARCH_COMPLETE, &TitleManager::OnTitleSearchComplete, this); + this->Bind(wxEVT_DL_TITLE_UPDATE, &TitleManager::OnDownloadableTitleUpdate, this); + this->Bind(wxEVT_DL_DISCONNECT_COMPLETE, &TitleManager::OnDisconnect, this); + + // TODO typing on title list should change filter text and filter! + + // if download manager is already active then restore state + DownloadManager* dlMgr = DownloadManager::GetInstance(false); + if (dlMgr && dlMgr->IsConnected()) + { + dlMgr->setUserData(this); + dlMgr->registerCallbacks( + TitleManager::Callback_ConnectStatusUpdate, + TitleManager::Callback_AddDownloadableTitle, + TitleManager::Callback_RemoveDownloadableTitle); + SetConnected(true); + } + + m_callbackId = CafeTitleList::RegisterCallback([](CafeTitleListCallbackEvent* evt, void* ctx) { ((TitleManager*)ctx)->HandleTitleListCallback(evt); }, this); +} + +TitleManager::~TitleManager() +{ + CafeTitleList::UnregisterCallback(m_callbackId); + + // unregister callbacks for download manager + DownloadManager* dlMgr = DownloadManager::GetInstance(false); + if (dlMgr) + { + dlMgr->setUserData(nullptr); + dlMgr->registerCallbacks( + nullptr, + nullptr, + nullptr); + // if download manager is still downloading / installing then show a warning + if (dlMgr->hasActiveDownloads()) + { + static bool s_showedBGDownloadWarning = false; + if (!s_showedBGDownloadWarning) + { + wxMessageBox(_("Currently active downloads will continue in the background."), _("Information"), wxOK | wxCENTRE, this); + s_showedBGDownloadWarning = true; + } + } + } + + m_running = false; +} + +void TitleManager::SetFocusAndTab(TitleManagerPage page) +{ + m_notebook->SetSelection((int)page); + this->SetFocus(); +} + +void TitleManager::SetDownloadStatusText(const wxString& text) +{ + auto* evt = new wxCommandEvent(wxEVT_SET_TEXT); + evt->SetEventObject(m_status_text); + evt->SetString(text); + wxQueueEvent(this, evt); +} + +void TitleManager::HandleTitleListCallback(CafeTitleListCallbackEvent* evt) +{ + if (evt->eventType == CafeTitleListCallbackEvent::TYPE::SCAN_FINISHED) + { + auto* evt = new wxCommandEvent(wxEVT_TITLE_SEARCH_COMPLETE); + wxQueueEvent(this, evt); + } +} + +void TitleManager::OnTitleFound(wxCommandEvent& event) +{ + auto* obj = dynamic_cast(event.GetClientObject()); + wxASSERT(obj); + m_title_list->AddTitle(obj); +} + +void TitleManager::OnTitleSearchComplete(wxCommandEvent& event) +{ + m_isScanning = false; + if (m_connectRequested) + { + InitiateConnect(); + m_connectRequested = false; + } + // update status bar text + m_title_list->SortEntries(-1); + m_status_bar->SetStatusText(wxStringFormat2(_("Found {} titles, {} updates, {} DLCs and {} save entries"), + m_title_list->GetCountByType(wxTitleManagerList::EntryType::Base) + m_title_list->GetCountByType(wxTitleManagerList::EntryType::System), + m_title_list->GetCountByType(wxTitleManagerList::EntryType::Update), + m_title_list->GetCountByType(wxTitleManagerList::EntryType::Dlc), + m_title_list->GetCountByType(wxTitleManagerList::EntryType::Save) + )); + + // re-filter if any filter is set + const auto filter = m_filter->GetValue(); + if (!filter.IsEmpty()) + m_title_list->Filter(m_filter->GetValue()); + + m_title_list->AutosizeColumns(); + m_refresh_button->Enable(); +} + +void TitleManager::OnSetStatusBarText(wxSetStatusBarTextEvent& event) +{ + m_status_bar->SetStatusText(_(event.GetText()), event.GetNumber()); +} + +void TitleManager::OnFilterChanged(wxCommandEvent& event) +{ + event.Skip(); + m_title_list->Filter(m_filter->GetValue()); +} + +void TitleManager::OnRefreshButton(wxCommandEvent& event) +{ + m_refresh_button->Disable(); + m_isScanning = true; + // m_title_list->ClearItems(); -> Dont clear. Refresh() triggers incremental updates via notifications + m_status_bar->SetStatusText(_("Searching for titles...")); + CafeTitleList::Refresh(); +} + +void TitleManager::OnInstallTitle(wxCommandEvent& event) +{ + wxFileDialog openFileDialog(this, _("Select title to install"), "", "", "meta.xml|meta.xml", wxFD_OPEN | wxFD_FILE_MUST_EXIST); + if (openFileDialog.ShowModal() == wxID_CANCEL) + return; + + fs::path filePath(openFileDialog.GetPath().wc_str()); + try + { + filePath = filePath.parent_path(); + filePath = filePath.parent_path(); + GameUpdateWindow frame(*this, filePath); + const int updateResult = frame.ShowModal(); + + if (updateResult == wxID_OK) + { + CafeTitleList::AddTitleFromPath(frame.GetTargetPath()); + } + else + { + if (frame.GetExceptionMessage().empty()) + wxMessageBox(_("Update installation has been canceled!")); + else + { + throw std::runtime_error(frame.GetExceptionMessage()); + } + } + } + catch (const AbortException&) + { + // ignored + } + catch (const std::exception& ex) + { + wxMessageBox(ex.what(), _("Update error")); + } +} + +static void PopulateSavePersistentIds(wxTitleManagerList::TitleEntry& entry) +{ + if (!entry.persistent_ids.empty()) + return; + cemu_assert(entry.type == wxTitleManagerList::EntryType::Save); + SaveInfo saveInfo = CafeSaveList::GetSaveByTitleId(entry.title_id); + if (!saveInfo.IsValid()) + return; + fs::path savePath = saveInfo.GetPath(); + savePath /= "user"; + std::error_code ec; + for (auto it : fs::directory_iterator(savePath, ec)) + { + if(!it.is_directory(ec)) + continue; + std::string dirName = it.path().filename().string(); + uint32 persistentId = ConvertString(dirName, 16); + if (persistentId != 0) + entry.persistent_ids.emplace_back(persistentId); + } +} + +void TitleManager::OnTitleSelected(wxListEvent& event) +{ + event.Skip(); + + const auto entry = m_title_list->GetSelectedTitleEntry(); + if(entry.has_value() && entry->type == wxTitleManagerList::EntryType::Save) + { + m_save_panel->Show(); + m_save_account_list->Clear(); + + PopulateSavePersistentIds(*entry); + + // an account must be selected before any of the control buttons can be used + for(auto&& v : m_save_panel->GetChildren()) + { + if (dynamic_cast(v) && v->GetId() != m_save_import->GetId()) // import is always enabled + v->Disable(); + } + + const auto& accounts = Account::GetAccounts(); + for (const auto& id : entry->persistent_ids) + { + const auto it = std::find_if(accounts.cbegin(), accounts.cend(), [id](const auto& acc) { return acc.GetPersistentId() == id; }); + if(it != accounts.cend()) + { + m_save_account_list->Append(fmt::format("{:x} ({})", id, + boost::nowide::narrow(it->GetMiiName().data(), it->GetMiiName().size())), + (void*)(uintptr_t)id); + } + else + m_save_account_list->Append(fmt::format("{:x}", id), (void*)(uintptr_t)id); + } + } + else + { + m_save_panel->Hide(); + } +} + +void TitleManager::OnSaveOpenDirectory(wxCommandEvent& event) +{ + const auto selection= m_save_account_list->GetSelection(); + if (selection == wxNOT_FOUND) + return; + + const auto entry = m_title_list->GetSelectedTitleEntry(); + if (!entry.has_value()) + return; + + const auto persistent_id = (uint32)(uintptr_t)m_save_account_list->GetClientData(selection); + + const fs::path target = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/user/{:08x}", (uint32)(entry->title_id >> 32), (uint32)(entry->title_id & 0xFFFFFFFF), persistent_id); + if (!fs::exists(target) || !fs::is_directory(target)) + return; + +#ifdef _WIN32 + ShellExecuteW(GetHWND(), L"open", target.c_str(), nullptr, nullptr, SW_SHOWNORMAL); +#else + assert_dbg(); +#endif +} + +void TitleManager::OnSaveDelete(wxCommandEvent& event) +{ + const auto selection_index = m_save_account_list->GetSelection(); + if (selection_index == wxNOT_FOUND) + return; + + const auto selection = m_save_account_list->GetStringSelection(); + if (selection.IsEmpty()) + return; + + const auto msg = wxStringFormat2(_("Are you really sure that you want to delete the save entry for {}"), selection); + const auto result = wxMessageBox(msg, _("Warning"), wxYES_NO | wxCENTRE | wxICON_EXCLAMATION, this); + if (result == wxNO) + return; + + auto entry = m_title_list->GetSelectedTitleEntry(); + if (!entry.has_value()) + return; + + const auto persistent_id = (uint32)(uintptr_t)m_save_account_list->GetClientData(selection_index); + + const fs::path target = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/user/{:08x}", (uint32)(entry->title_id >> 32), (uint32)(entry->title_id & 0xFFFFFFFF), persistent_id); + if (!fs::exists(target) || !fs::is_directory(target)) + return; + + // edit meta saveinfo.xml + bool meta_file_edited = false; + const fs::path saveinfo = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/meta/saveinfo.xml", (uint32)(entry->title_id >> 32), (uint32)(entry->title_id & 0xFFFFFFFF)); + if (fs::exists(saveinfo) || fs::is_regular_file(saveinfo)) + { + pugi::xml_document doc; + if (doc.load_file(saveinfo.c_str())) + { + auto info_node = doc.child("info"); + if(info_node) + { + auto persistend_id_string = fmt::format(L"{:08x}", persistent_id); + const auto delete_entry = info_node.find_child([&persistend_id_string](const pugi::xml_node& node) + { + return boost::iequals(node.attribute("persistentId").as_string(), persistend_id_string); + }); + if (delete_entry) + { + info_node.remove_child(delete_entry); + meta_file_edited = doc.save_file(saveinfo.c_str()); + } + } + } + } + + if (!meta_file_edited) + forceLog_printf("TitleManager::OnSaveDelete: couldn't delete save entry in saveinfo.xml: %s", saveinfo.generic_u8string().c_str()); + + // remove from title entry + auto& persistent_ids = entry->persistent_ids; + persistent_ids.erase(std::remove(persistent_ids.begin(), persistent_ids.end(), persistent_id), persistent_ids.end()); + + std::error_code ec; + fs::remove_all(target, ec); + if (ec) + { + const auto error_msg = wxStringFormat2(_("Error when trying to delete the save directory:\n{}"), GetSystemErrorMessage(ec)); + wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE, this); + return; + } + + m_save_account_list->Delete(selection_index); +} + + +void TitleManager::OnSaveTransfer(wxCommandEvent& event) +{ + const auto selection_index = m_save_account_list->GetSelection(); + if (selection_index == wxNOT_FOUND) + return; + + const auto selection = m_save_account_list->GetStringSelection(); + if (selection.IsEmpty()) + return; + + auto entry = m_title_list->GetSelectedTitleEntry(); + if (!entry.has_value()) + return; + + const auto persistent_id = (uint32)(uintptr_t)m_save_account_list->GetClientData(selection_index); + + SaveTransfer transfer(this, entry->title_id, selection, persistent_id); + if (transfer.ShowModal() == wxCANCEL) + return; + + // remove old id entry + auto& persistent_ids = entry->persistent_ids; + persistent_ids.erase(std::remove(persistent_ids.begin(), persistent_ids.end(), persistent_id), persistent_ids.end()); + + // add new id if not added yet + const auto new_id = transfer.GetTargetPersistentId(); + if (new_id != 0 && std::find(persistent_ids.cbegin(), persistent_ids.cend(), new_id) == persistent_ids.cend()) + { + persistent_ids.emplace_back(new_id); + + const auto& account = Account::GetAccount(new_id); + if(account.GetPersistentId() == new_id) + m_save_account_list->Append(fmt::format("{:x} ({})", new_id, + boost::nowide::narrow(account.GetMiiName().data(), account.GetMiiName().size())), + (void*)(uintptr_t)new_id); + else + m_save_account_list->Append(fmt::format("{:x}", new_id), (void*)(uintptr_t)new_id); + } + + m_save_account_list->Delete(selection_index); +} + +void TitleManager::OnSaveAccountSelected(wxCommandEvent& event) +{ + event.Skip(); + for (auto&& v : m_save_panel->GetChildren()) + { + if (!v->IsEnabled()) + v->Enable(); + } +} + +void TitleManager::OnSaveExport(wxCommandEvent& event) +{ + const auto selection_index = m_save_account_list->GetSelection(); + if (selection_index == wxNOT_FOUND) + return; + + const auto selection = m_save_account_list->GetStringSelection(); + if (selection.IsEmpty()) + return; + + const auto entry = m_title_list->GetSelectedTitleEntry(); + if (!entry.has_value()) + return; + + const auto persistent_id = (uint32)(uintptr_t)m_save_account_list->GetClientData(selection_index); + + wxFileDialog path_dialog(this, _("Select a target file to export the save entry"), entry->path.string(), wxEmptyString, "Exported save entry (*.zip)|*.zip", wxFD_SAVE | wxFD_OVERWRITE_PROMPT); + if (path_dialog.ShowModal() != wxID_OK) + return; + + const auto path = path_dialog.GetPath(); + if (path.empty()) + return; + + int ze; + auto* zip = zip_open(path.ToUTF8().data(), ZIP_CREATE | ZIP_TRUNCATE, &ze); + if (!zip) + { + zip_error_t ziperror; + zip_error_init_with_code(&ziperror, ze); + const auto error_msg = wxStringFormat2(_("Error when creating the zip for the save entry:\n{}"), zip_error_strerror(&ziperror)); + wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + return; + } + + // grab all files to zip + const auto savedir = fs::path(entry->path).append(fmt::format("user/{:08x}", persistent_id)); + const auto savedir_str = savedir.generic_u8string(); + for(const auto& it : fs::recursive_directory_iterator(savedir)) + { + if (it.path() == "." || it.path() == "..") + continue; + + const auto entryname = it.path().generic_u8string(); + if(fs::is_directory(it.path())) + { + if(zip_dir_add(zip, (const char*)entryname.substr(savedir_str.size() + 1).c_str(), ZIP_FL_ENC_UTF_8) < 0 ) + { + const auto error_msg = wxStringFormat2(_("Error when trying to add a directory to the zip:\n{}"), zip_strerror(zip)); + wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + } + } + else + { + auto* source = zip_source_file(zip, (const char*)entryname.c_str(), 0, 0); + if(!source) + { + const auto error_msg = wxStringFormat2(_("Error when trying to add a file to the zip:\n{}"), zip_strerror(zip)); + wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + } + + if (zip_file_add(zip, (const char*)entryname.substr(savedir_str.size() + 1).c_str(), source, ZIP_FL_ENC_UTF_8) < 0) + { + const auto error_msg = wxStringFormat2(_("Error when trying to add a file to the zip:\n{}"), zip_strerror(zip)); + wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + + zip_source_free(source); + } + } + } + + // add own metainfo like file and store title id for verification later + std::string metacontent = fmt::format("titleId = {:#016x}", entry->title_id); + auto* metabuff = zip_source_buffer(zip, metacontent.data(), metacontent.size(), 0); + if(zip_file_add(zip, "cemu_meta", metabuff, ZIP_FL_ENC_UTF_8) < 0) + { + const auto error_msg = wxStringFormat2(_("Error when trying to add a cemu_meta to the zip:\n{}"), zip_strerror(zip)); + wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + + zip_source_free(metabuff); + } + + zip_close(zip); +} + +void TitleManager::OnSaveImport(wxCommandEvent& event) +{ + auto entry = m_title_list->GetSelectedTitleEntry(); + if (!entry.has_value()) + return; + + SaveImportWindow save_import(this, entry->title_id); + if (save_import.ShowModal() == wxCANCEL) + return; + + // add new id if not added yet + auto& persistent_ids = entry->persistent_ids; + const auto new_id = save_import.GetTargetPersistentId(); + if (new_id != 0 && std::find(persistent_ids.cbegin(), persistent_ids.cend(), new_id) == persistent_ids.cend()) + { + persistent_ids.emplace_back(new_id); + + const auto& account = Account::GetAccount(new_id); + if (account.GetPersistentId() == new_id) + m_save_account_list->Append(fmt::format("{:x} ({})", new_id, + boost::nowide::narrow(account.GetMiiName().data(), account.GetMiiName().size())), + (void*)(uintptr_t)new_id); + else + m_save_account_list->Append(fmt::format("{:x}", new_id), (void*)(uintptr_t)new_id); + } + +} + +void TitleManager::InitiateConnect() +{ + // init connection to download manager if queued + uint32 persistentId = (uint32)(uintptr_t)m_account->GetClientData(m_account->GetSelection()); + auto& account = Account::GetAccount(persistentId); + + DownloadManager* dlMgr = DownloadManager::GetInstance(); + dlMgr->reset(); + m_download_list->SetCurrentDownloadMgr(dlMgr); + + std::string deviceCertBase64 = NCrypto::CertECC::GetDeviceCertificate().encodeToBase64(); + + if (!NCrypto::SEEPROM_IsPresent()) + { + SetDownloadStatusText("Dumped online files not found"); + return; + } + + SetDownloadStatusText("Connecting..."); + // begin async connect + dlMgr->setUserData(this); + dlMgr->registerCallbacks( + TitleManager::Callback_ConnectStatusUpdate, + TitleManager::Callback_AddDownloadableTitle, + TitleManager::Callback_RemoveDownloadableTitle); + dlMgr->connect(account.GetAccountId(), account.GetAccountPasswordCache(), NCrypto::SEEPROM_GetRegion(), NCrypto::GetCountryAsString(account.GetCountry()), NCrypto::GetDeviceId(), NCrypto::GetSerial(), deviceCertBase64); +} + +void TitleManager::OnConnect(wxCommandEvent& event) +{ + if (!m_isScanning) + { + InitiateConnect(); + SetConnected(true); + } + else + { + SetDownloadStatusText(_("Getting installed title information...")); + SetConnected(true); + m_connectRequested = true; + } +} + +void TitleManager::OnDlFilterCheckbox(wxCommandEvent& event) +{ + m_download_list->Filter2(m_show_titles->GetValue(), m_show_updates->GetValue(), m_show_installed->GetValue()); + m_download_list->SortEntries(); +} + +void TitleManager::OnSetStatusText(wxCommandEvent& event) +{ + auto* text = wxDynamicCast(event.GetEventObject(), wxControl); + wxASSERT(text); + text->SetLabel(event.GetString()); +} + +void TitleManager::OnDownloadableTitleUpdate(wxCommandEvent& event) +{ + auto* obj = dynamic_cast(event.GetClientObject()); + auto entry = obj->GetData(); + m_download_list->AddOrUpdateTitle(obj); +} + +void TitleManager::OnDisconnect(wxCommandEvent& event) +{ + SetConnected(false); +} + +void TitleManager::SetConnected(bool state) +{ + m_account->Enable(!state); + m_connect->Enable(!state); + + m_show_titles->Enable(state); + m_show_updates->Enable(state); + m_show_installed->Enable(state); + m_download_list->Enable(state); +} + +void TitleManager::Callback_ConnectStatusUpdate(std::string statusText, DLMGR_STATUS_CODE statusCode) +{ + TitleManager* titleManager = static_cast(DownloadManager::GetInstance()->getUserData()); + titleManager->SetDownloadStatusText(statusText); + if (statusCode == DLMGR_STATUS_CODE::FAILED) + { + auto* evt = new wxCommandEvent(wxEVT_DL_DISCONNECT_COMPLETE); + wxQueueEvent(titleManager, evt); // this will set SetConnected() to false + return; + } +} + +void TitleManager::Callback_AddDownloadableTitle(const DlMgrTitleReport& titleInfo) +{ + TitleManager* titleManager = static_cast(DownloadManager::GetInstance()->getUserData()); + wxDownloadManagerList::EntryType type = wxDownloadManagerList::EntryType::Base; + if (((titleInfo.titleId >> 32) & 0xF) == 0xE) + type = wxDownloadManagerList::EntryType::Update; + else if (((titleInfo.titleId >> 32) & 0xF) == 0xC) + type = wxDownloadManagerList::EntryType::DLC; + if (titleInfo.status == DlMgrTitleReport::STATUS::INSTALLABLE || titleInfo.status == DlMgrTitleReport::STATUS::INSTALLABLE_UNFINISHED || titleInfo.status == DlMgrTitleReport::STATUS::INSTALLABLE_UPDATE) + { + // installable title + wxDownloadManagerList::TitleEntry titleEntry(type, false, titleInfo.titleId, titleInfo.version, titleInfo.isPaused); + titleEntry.name = wxString::FromUTF8(titleInfo.name); + titleEntry.status = wxDownloadManagerList::TitleDownloadStatus::Available; + titleEntry.progress = 0; + if (titleInfo.status == DlMgrTitleReport::STATUS::INSTALLABLE_UNFINISHED) + titleEntry.progress = 1; + if (titleInfo.status == DlMgrTitleReport::STATUS::INSTALLABLE_UPDATE) + titleEntry.progress = 2; + auto* evt = new wxCommandEvent(wxEVT_DL_TITLE_UPDATE); + evt->SetClientObject(new wxCustomData(titleEntry)); + wxQueueEvent(titleManager, evt); + } + else + { + // package + wxDownloadManagerList::TitleEntry titleEntry(type, true, titleInfo.titleId, titleInfo.version, titleInfo.isPaused); + titleEntry.name = wxString::FromUTF8(titleInfo.name); + titleEntry.progress = titleInfo.progress; + titleEntry.progressMax = titleInfo.progressMax; + switch (titleInfo.status) + { + case DlMgrTitleReport::STATUS::INITIALIZING: + titleEntry.status = wxDownloadManagerList::TitleDownloadStatus::Initializing; + break; + case DlMgrTitleReport::STATUS::QUEUED: + titleEntry.status = wxDownloadManagerList::TitleDownloadStatus::Queued; + break; + case DlMgrTitleReport::STATUS::CHECKING: + titleEntry.status = wxDownloadManagerList::TitleDownloadStatus::Checking; + break; + case DlMgrTitleReport::STATUS::DOWNLOADING: + titleEntry.status = wxDownloadManagerList::TitleDownloadStatus::Downloading; + titleEntry.progress = titleInfo.progress; + break; + case DlMgrTitleReport::STATUS::VERIFYING: + titleEntry.status = wxDownloadManagerList::TitleDownloadStatus::Verifying; + break; + case DlMgrTitleReport::STATUS::INSTALLING: + titleEntry.status = wxDownloadManagerList::TitleDownloadStatus::Installing; + break; + case DlMgrTitleReport::STATUS::INSTALLED: + titleEntry.status = wxDownloadManagerList::TitleDownloadStatus::Installed; + break; + case DlMgrTitleReport::STATUS::HAS_ERROR: + titleEntry.status = wxDownloadManagerList::TitleDownloadStatus::Error; + titleEntry.errorMsg = titleInfo.errorMsg; + break; + default: + titleEntry.status = wxDownloadManagerList::TitleDownloadStatus::None; + break; + } + + auto* evt = new wxCommandEvent(wxEVT_DL_TITLE_UPDATE); + evt->SetClientObject(new wxCustomData(titleEntry)); + wxQueueEvent(titleManager, evt); + } +} + +void TitleManager::Callback_RemoveDownloadableTitle(uint64 titleId, uint16 version) +{ + TitleManager* titleManager = static_cast(DownloadManager::GetInstance()->getUserData()); + assert_dbg(); +} diff --git a/src/gui/TitleManager.h b/src/gui/TitleManager.h new file mode 100644 index 00000000..17a48976 --- /dev/null +++ b/src/gui/TitleManager.h @@ -0,0 +1,103 @@ +#pragma once + +#include +#include + +#include +#include + +#include "Cemu/Tools/DownloadManager/DownloadManager.h" + +class wxCheckBox; +class wxStaticText; +class wxListEvent; +class wxSetStatusBarTextEvent; +class wxTitleManagerList; +class wxDownloadManagerList; +class wxTextCtrl; +class wxStatusBar; +class wxImageList; +class wxBitmapButton; +class wxPanel; +class wxChoice; +class wxNotebook; + +enum class TitleManagerPage +{ + TitleManager = 0, + DownloadManager = 1 +}; + +enum class DLMGR_STATUS_CODE; + +class TitleManager : public wxFrame +{ +public: + TitleManager(wxWindow* parent, TitleManagerPage default_page = TitleManagerPage::TitleManager); + ~TitleManager(); + + void SetFocusAndTab(TitleManagerPage page); + + void SetDownloadStatusText(const wxString& text); + +private: + wxPanel* CreateTitleManagerPage(); + wxPanel* CreateDownloadManagerPage(); + + // title manager + void OnTitleFound(wxCommandEvent& event); + void OnTitleSearchComplete(wxCommandEvent& event); + void OnSetStatusBarText(wxSetStatusBarTextEvent& event); + void OnFilterChanged(wxCommandEvent& event); + void OnRefreshButton(wxCommandEvent& event); + void OnInstallTitle(wxCommandEvent& event); + void OnTitleSelected(wxListEvent& event); + void OnSaveOpenDirectory(wxCommandEvent& event); + void OnSaveDelete(wxCommandEvent& event); + void OnSaveTransfer(wxCommandEvent& event); + void OnSaveAccountSelected(wxCommandEvent& event); + void OnSaveExport(wxCommandEvent& event); + void OnSaveImport(wxCommandEvent& event); + + void HandleTitleListCallback(struct CafeTitleListCallbackEvent* evt); + + wxNotebook* m_notebook; + + uint32 m_callbackId; + + // title manager + wxTextCtrl* m_filter; + wxTitleManagerList* m_title_list; + wxStatusBar* m_status_bar; + wxBitmapButton* m_refresh_button; + wxPanel* m_save_panel; + wxChoice* m_save_account_list; + wxButton* m_save_import; + + bool m_isScanning{ true }; // set when CafeTitleList is scanning + + std::atomic_bool m_running = true; + + // download manager + void InitiateConnect(); + void OnConnect(wxCommandEvent& event); + void OnSetStatusText(wxCommandEvent& event); + void OnDownloadableTitleUpdate(wxCommandEvent& event); + void OnDisconnect(wxCommandEvent& event); + + void OnDlFilterCheckbox(wxCommandEvent& event); + void OnDlCheckboxShowUpdates(wxCommandEvent& event); + + void SetConnected(bool state); + + static void Callback_ConnectStatusUpdate(std::string statusText, DLMGR_STATUS_CODE statusCode); + static void Callback_AddDownloadableTitle(const struct DlMgrTitleReport& titleInfo); + static void Callback_RemoveDownloadableTitle(uint64 titleId, uint16 version); + + wxChoice* m_account; + wxButton* m_connect; + wxStaticText* m_status_text; + wxCheckBox *m_show_titles, *m_show_updates, *m_show_installed; + wxDownloadManagerList* m_download_list; + bool m_connectRequested{false}; // connect was clicked before m_foundTitles was available +}; diff --git a/src/gui/canvas/IRenderCanvas.h b/src/gui/canvas/IRenderCanvas.h new file mode 100644 index 00000000..125ae954 --- /dev/null +++ b/src/gui/canvas/IRenderCanvas.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +// base class for all render interfaces +class IRenderCanvas +{ +public: + IRenderCanvas(bool is_main_window) + : m_is_main_window(is_main_window) {} + +protected: + bool m_is_main_window; +}; \ No newline at end of file diff --git a/src/gui/canvas/OpenGLCanvas.cpp b/src/gui/canvas/OpenGLCanvas.cpp new file mode 100644 index 00000000..23d575fd --- /dev/null +++ b/src/gui/canvas/OpenGLCanvas.cpp @@ -0,0 +1,96 @@ +#include "gui/canvas/OpenGLCanvas.h" +#include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h" + +#include "config/CemuConfig.h" + +#include "Common/GLInclude/GLInclude.h" +#include // this includes GL/gl.h, avoid using this in a header because it would contaminate our own OpenGL definitions (GLInclude) + +static const int g_gl_attribute_list[] = +{ + WX_GL_RGBA, + WX_GL_DOUBLEBUFFER, + WX_GL_DEPTH_SIZE, 16, + + WX_GL_MIN_RED, 8, + WX_GL_MIN_GREEN, 8, + WX_GL_MIN_BLUE, 8, + WX_GL_MIN_ALPHA, 8, + + WX_GL_STENCIL_SIZE, 8, + + //WX_GL_MAJOR_VERSION, 4, + //WX_GL_MINOR_VERSION, 1, + //wx_GL_COMPAT_PROFILE, + + 0, // end of list +}; + +wxGLContext* sGLContext = nullptr; +class OpenGLCanvas* sGLTVView = nullptr; +class OpenGLCanvas* sGLPadView = nullptr; + +class OpenGLCanvas : public IRenderCanvas, public wxGLCanvas +{ +public: + OpenGLCanvas(wxWindow* parent, const wxSize& size, bool is_main_window) + : IRenderCanvas(is_main_window), wxGLCanvas(parent, wxID_ANY, g_gl_attribute_list, wxDefaultPosition, size, wxFULL_REPAINT_ON_RESIZE | wxWANTS_CHARS) + { + cemuLog_logDebug(LogType::Force, "Creating OpenGL canvas"); + + if (m_is_main_window) + { + sGLTVView = this; + sGLContext = new wxGLContext(this); + + g_renderer = std::make_unique(); + } + else + sGLPadView = this; + + wxWindow::EnableTouchEvents(wxTOUCH_PAN_GESTURES); + } + + ~OpenGLCanvas() override + { + // todo - if this is the main window, make sure the renderer has been shut down + + if (m_is_main_window) + sGLTVView = nullptr; + else + sGLPadView = nullptr; + + if (sGLTVView == nullptr && sGLPadView == nullptr && sGLContext) + delete sGLContext; + } + +private: + //wxGLContext* m_context = nullptr; +}; + +wxWindow* GLCanvas_Create(wxWindow* parent, const wxSize& size, bool is_main_window) +{ + return new OpenGLCanvas(parent, size, is_main_window); +} + +bool GLCanvas_HasPadViewOpen() +{ + return sGLPadView != nullptr; +} + +bool GLCanvas_MakeCurrent(bool padView) +{ + OpenGLCanvas* canvas = padView ? sGLPadView : sGLTVView; + if (!canvas) + return false; + sGLContext->SetCurrent(*canvas); + return true; +} + +void GLCanvas_SwapBuffers(bool swapTV, bool swapDRC) +{ + if (swapTV && sGLTVView) + sGLTVView->SwapBuffers(); + if (swapDRC && sGLPadView) + sGLPadView->SwapBuffers(); +} \ No newline at end of file diff --git a/src/gui/canvas/OpenGLCanvas.h b/src/gui/canvas/OpenGLCanvas.h new file mode 100644 index 00000000..282e6d19 --- /dev/null +++ b/src/gui/canvas/OpenGLCanvas.h @@ -0,0 +1,8 @@ +#pragma once +#include +#include "gui/canvas/IRenderCanvas.h" + +wxWindow* GLCanvas_Create(wxWindow* parent, const wxSize& size, bool is_main_window); +bool GLCanvas_MakeCurrent(bool padView); +void GLCanvas_SwapBuffers(bool swapTV, bool swapDRC); +bool GLCanvas_HasPadViewOpen(); \ No newline at end of file diff --git a/src/gui/canvas/VulkanCanvas.cpp b/src/gui/canvas/VulkanCanvas.cpp new file mode 100644 index 00000000..dfad8c9a --- /dev/null +++ b/src/gui/canvas/VulkanCanvas.cpp @@ -0,0 +1,64 @@ +#include "gui/canvas/VulkanCanvas.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" +#include "gui/guiWrapper.h" + +#include + +VulkanCanvas::VulkanCanvas(wxWindow* parent, const wxSize& size, bool is_main_window) + : IRenderCanvas(is_main_window), wxWindow(parent, wxID_ANY, wxDefaultPosition, size, wxFULL_REPAINT_ON_RESIZE | wxWANTS_CHARS) +{ + Bind(wxEVT_PAINT, &VulkanCanvas::OnPaint, this); + Bind(wxEVT_SIZE, &VulkanCanvas::OnResize, this); + + if(is_main_window) + gui_initHandleContextFromWxWidgetsWindow(gui_getWindowInfo().canvas_main, this); + else + gui_initHandleContextFromWxWidgetsWindow(gui_getWindowInfo().canvas_pad, this); + + cemu_assert(g_vulkan_available); + + try + { + if (is_main_window) + g_renderer = std::make_unique(); + + auto vulkan_renderer = VulkanRenderer::GetInstance(); + vulkan_renderer->Initialize({size.x, size.y}, is_main_window); + } + catch(const std::exception& ex) + { + const auto msg = fmt::format(_("Error when initializing Vulkan renderer:\n{}").ToStdString(), ex.what()); + forceLog_printf(const_cast(msg.c_str())); + wxMessageDialog dialog(this, msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); + dialog.ShowModal(); + exit(0); + } + + wxWindow::EnableTouchEvents(wxTOUCH_PAN_GESTURES); +} + +VulkanCanvas::~VulkanCanvas() +{ + Unbind(wxEVT_PAINT, &VulkanCanvas::OnPaint, this); + Unbind(wxEVT_SIZE, &VulkanCanvas::OnResize, this); +} + +void VulkanCanvas::OnPaint(wxPaintEvent& event) +{ +} + +void VulkanCanvas::OnResize(wxSizeEvent& event) +{ + const wxSize size = GetSize(); + if (size.GetWidth() == 0 || size.GetHeight() == 0) + return; + + const wxRect refreshRect(size); + RefreshRect(refreshRect, false); + + if (g_renderer == nullptr) + return; + + auto vulkan_renderer = VulkanRenderer::GetInstance(); + vulkan_renderer->ResizeSwapchain({ size.x, size.y }, m_is_main_window); +} \ No newline at end of file diff --git a/src/gui/canvas/VulkanCanvas.h b/src/gui/canvas/VulkanCanvas.h new file mode 100644 index 00000000..ecc7e744 --- /dev/null +++ b/src/gui/canvas/VulkanCanvas.h @@ -0,0 +1,22 @@ +#pragma once + +#include "gui/canvas/IRenderCanvas.h" + +#include + +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" +#include + + + +class VulkanCanvas : public IRenderCanvas, public wxWindow +{ +public: + VulkanCanvas(wxWindow* parent, const wxSize& size, bool is_main_window); + ~VulkanCanvas(); + +private: + + void OnPaint(wxPaintEvent& event); + void OnResize(wxSizeEvent& event); +}; diff --git a/src/gui/components/TextList.cpp b/src/gui/components/TextList.cpp new file mode 100644 index 00000000..606c84f9 --- /dev/null +++ b/src/gui/components/TextList.cpp @@ -0,0 +1,317 @@ +#include "gui/wxgui.h" +#include "TextList.h" +#include +#include + +TextList::~TextList() +{ + m_tooltip_timer->Stop(); + + this->Unbind(wxEVT_MOTION, &TextList::OnMouseMoveEvent, this); + this->Unbind(wxEVT_KEY_DOWN, &TextList::OnKeyDownEvent, this); + this->Unbind(wxEVT_KEY_UP, &TextList::OnKeyUpEvent, this); + this->Unbind(wxEVT_PAINT, &TextList::OnPaintEvent, this); + this->Unbind(wxEVT_LEFT_DOWN, &TextList::OnMouseDownEvent, this); + this->Unbind(wxEVT_LEFT_UP, &TextList::OnMouseUpEvent, this); + this->Unbind(wxEVT_LEFT_DCLICK, &TextList::OnMouseDClickEvent, this); + this->Unbind(wxEVT_CONTEXT_MENU, &TextList::OnContextMenu, this); + this->Unbind(wxEVT_ERASE_BACKGROUND, &TextList::OnEraseBackground, this); +} + +TextList::TextList(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style) + : wxControl(parent, id, pos, size, style), wxScrollHelper(this) +{ + m_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + wxWindowBase::SetBackgroundStyle(wxBG_STYLE_PAINT); + + wxClientDC dc(this); + m_line_height = dc.GetCharHeight(); + m_char_width = dc.GetCharWidth(); + + m_yScrollPixelsPerLine = m_line_height; + + this->ShowScrollbars(wxSHOW_SB_DEFAULT, wxSHOW_SB_DEFAULT); + + m_tooltip = new wxToolTip(wxEmptyString); + + this->Bind(wxEVT_MOTION, &TextList::OnMouseMoveEvent, this); + this->Bind(wxEVT_KEY_DOWN, &TextList::OnKeyDownEvent, this); + this->Bind(wxEVT_KEY_UP, &TextList::OnKeyUpEvent, this); + this->Bind(wxEVT_PAINT, &TextList::OnPaintEvent, this); + this->Bind(wxEVT_LEFT_DOWN, &TextList::OnMouseDownEvent, this); + this->Bind(wxEVT_LEFT_UP, &TextList::OnMouseUpEvent, this); + this->Bind(wxEVT_LEFT_DCLICK, &TextList::OnMouseDClickEvent, this); + this->Bind(wxEVT_CONTEXT_MENU, &TextList::OnContextMenu, this); + this->Bind(wxEVT_ERASE_BACKGROUND, &TextList::OnEraseBackground, this); + + m_tooltip_window = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNO_BORDER); + m_tooltip_window->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_INFOBK)); + m_tooltip_window->Hide(); + + m_tooltip_timer = new wxTimer(this); + this->Bind(wxEVT_TIMER, &TextList::OnTooltipTimer, this); +} + +void TextList::RefreshControl(const wxRect* update_region) +{ + if(update_region) + Refresh(true, update_region); + else + { + wxRect region = GetClientRect(); + update_region = ®ion; + Refresh(true, update_region); + } +} + +void TextList::RefreshLine(uint32 line) +{ + wxRect update_region = GetClientRect(); + update_region.y = (sint32)line * m_line_height; + update_region.height = m_line_height; + CalcScrolledPosition(0, update_region.y, nullptr, &update_region.y); + // debug_printf("update: <%x, %x>\n", update_region.y, update_region.height); + Refresh(true, &update_region); +} + +wxSize TextList::DoGetVirtualSize() const +{ + return {wxDefaultCoord, (int)(m_element_count + 1) * m_line_height}; +} + +void TextList::DoSetSize(int x, int y, int width, int height, int sizeFlags) +{ + wxControl::DoSetSize(x, y, width, height, sizeFlags); + + m_elements_visible = (height + m_line_height - 1) / m_line_height; + Refresh(); +} + +void TextList::SetScrollPos(int orient, int pos, bool refresh) +{ + wxControl::SetScrollPos(orient, pos, refresh); +} + +void TextList::DrawLineBackground(wxDC& dc, uint32 line, const wxColour& colour, uint32 lines) const +{ + wxRect rect; + rect.x = GetPosition().x; + rect.y = line * m_line_height; + rect.width = GetSize().x; + rect.height = m_line_height * lines; + + dc.SetBrush(colour); + dc.DrawRectangle(rect); +} + +void TextList::DrawLineBackground(wxDC& dc, const wxPoint& position, const wxColour& colour, uint32 lines) const +{ + wxRect rect; + rect.x = position.x; + rect.y = position.y; + rect.width = this->GetSize().x; + rect.height = m_line_height * lines; + + dc.SetBrush(colour); + dc.DrawRectangle(rect); +} + +bool TextList::SetElementCount(size_t element_count) +{ + if (m_element_count == element_count) + return false; + + if (element_count > 0x7FFFFFFF) + element_count = 0x7FFFFFFF; + + m_element_count = element_count; + this->AdjustScrollbars(); + return true; +} + +uint32 TextList::GetElementCount() const +{ + return m_element_count; +} + +bool TextList::IsKeyDown(sint32 keycode) +{ + return m_key_states[keycode]; +} + +void TextList::WriteText(wxDC& dc, const wxString& text, wxPoint& position) const +{ + dc.ResetBoundingBox(); + dc.DrawText(text, position); + position.x += dc.MaxX() - dc.MinX(); +} + +void TextList::WriteText(wxDC& dc, const wxString& text, wxPoint& position, const wxColour& color) const +{ + dc.SetTextForeground(color); + WriteText(dc, text, position); +} + +void TextList::NextLine(wxPoint& position, const wxPoint* start_position) const +{ + position.y += m_line_height; + + if (start_position) + position.x = start_position->x; +} + +void TextList::OnMouseDown() {} +void TextList::OnMouseUp() {} + +bool TextList::OnShowTooltip(const wxPoint& position, uint32 line) +{ + return false; +} + +void TextList::OnMouseMoveEvent(wxMouseEvent& event) +{ + m_tooltip_timer->Stop(); + m_tooltip_timer->StartOnce(250); + + wxPoint position = event.GetPosition(); + CalcUnscrolledPosition(position.x, position.y, &position.x, &position.y); + + m_mouse_position = position; + + if(m_mouse_down) + m_selection.SetBottomRight(position); + + const sint32 line = position.y / m_line_height; + OnMouseMove(position, line); +} + +void TextList::OnKeyDownEvent(wxKeyEvent& event) +{ + const auto key_code = event.GetKeyCode(); + const auto it = m_key_states.find(key_code); + if(it == m_key_states.end() || !it->second) + { + m_key_states[key_code] = true; + OnKeyPressed(key_code, event.GetPosition()); + } + + event.Skip(); +} + +void TextList::OnKeyUpEvent(wxKeyEvent& event) +{ + m_key_states[event.GetKeyCode()] = false; +} + +void TextList::OnMouseDownEvent(wxMouseEvent& event) +{ + m_mouse_down = true; + + wxPoint position = event.GetPosition(); + CalcUnscrolledPosition(position.x, position.y, &position.x, &position.y); + m_selection.SetPosition(position); + m_selection.SetBottomRight(position); + + OnMouseDown(); + + event.Skip(); +} + +void TextList::OnMouseUpEvent(wxMouseEvent& event) +{ + m_mouse_down = false; + OnMouseUp(); + + event.Skip(); +} + +void TextList::OnMouseDClickEvent(wxMouseEvent& event) +{ + wxPoint position = event.GetPosition(); + CalcUnscrolledPosition(position.x, position.y, &position.x, &position.y); + m_selection.SetPosition(position); + m_selection.SetBottomRight(position); + + const uint32 line = position.y / m_line_height; + OnMouseDClick(position, line); +} + +void TextList::OnContextMenu(wxContextMenuEvent& event) +{ + wxPoint position = event.GetPosition(); + if (position == wxDefaultPosition) + return; + + wxPoint clientPosition = ScreenToClient(position); + + CalcUnscrolledPosition(clientPosition.x, clientPosition.y, &clientPosition.x, &clientPosition.y); + m_selection.SetPosition(clientPosition); + m_selection.SetBottomRight(clientPosition); + + const uint32 line = clientPosition.y / m_line_height; + OnContextMenu(clientPosition, line); +} + + +void TextList::OnTooltipTimer(wxTimerEvent& event) +{ + m_tooltip_window->Hide(); + + const auto cursor_position = wxGetMousePosition(); + + const auto position = GetScreenPosition(); + if (cursor_position.x < position.x || cursor_position.y < position.y) + return; + + const auto size = GetSize(); + if (position.x + size.x < cursor_position.x || position.y + size.y < cursor_position.y) + return; + + const sint32 line = position.y / m_line_height; + if(OnShowTooltip(m_mouse_position, line)) + { + m_tooltip_window->SetPosition(wxPoint(m_mouse_position.x + 15, m_mouse_position.y + 15)); + m_tooltip_window->SendSizeEvent(); + m_tooltip_window->Show(); + } +} + +void TextList::OnPaintEvent(wxPaintEvent& event) +{ + wxBufferedPaintDC dc(m_targetWindow, wxBUFFER_VIRTUAL_AREA); + dc.SetFont(m_font); + + // get window position + auto position = GetPosition(); + + // get current real position + wxRect rect_update = GetUpdateRegion().GetBox(); + const auto count = (uint32)std::ceil((float)rect_update.GetHeight() / m_line_height); + + position.y = (rect_update.y / m_line_height) * m_line_height; + + // paint background + const wxColour window_colour = COLOR_WHITE; + dc.SetBrush(window_colour); + dc.SetPen(window_colour); + dc.DrawRectangle(rect_update); + + //// paint selection + //if (!m_selected_text.eof()) + //{ + // dc.SetBrush(*wxBLUE_BRUSH); + // dc.SetPen(*wxBLUE_PEN); + // dc.DrawRectangle(m_selection); + //} + + sint32 start; + CalcUnscrolledPosition(rect_update.x, rect_update.y, nullptr, &start); + + start /= m_line_height; + m_scrolled_to_end = (start + count) >= m_element_count; + + OnDraw(dc, start, count, position); + + + this->Update(); +} \ No newline at end of file diff --git a/src/gui/components/TextList.h b/src/gui/components/TextList.h new file mode 100644 index 00000000..6a281533 --- /dev/null +++ b/src/gui/components/TextList.h @@ -0,0 +1,79 @@ +#pragma once + +#include + +#include +#include + +#define COLOR_BLACK 0xFF000000 +#define COLOR_GREY 0xFFA0A0A0 +#define COLOR_LIGHT_GREY 0xFFE0E0E0 +#define COLOR_WHITE 0xFFFFFFFF +#define COLOR_RED 0xFF0000FF + + +class TextList : public wxControl, public wxScrollHelper +{ +public: + virtual ~TextList(); + TextList(wxWindow* parent, wxWindowID id, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, long style = 0); + + void RefreshControl(const wxRect* update_region = nullptr); + void RefreshLine(uint32 line); + + wxSize DoGetVirtualSize() const override; + void DoSetSize(int x, int y, int width, int height, int sizeFlags) override; + void SetScrollPos(int orient, int pos, bool refresh) override; + void DrawLineBackground(wxDC& dc, uint32 line, const wxColour& colour, uint32 lines = 1) const; + void DrawLineBackground(wxDC& dc, const wxPoint& position, const wxColour& colour, uint32 lines = 1) const; + + bool SetElementCount(size_t element_count); + uint32 GetElementCount() const; + bool IsKeyDown(sint32 keycode); + +protected: + void WriteText(wxDC& dc, const wxString& text, wxPoint& position) const; + void WriteText(wxDC& dc, const wxString& text, wxPoint& position, const wxColour& color) const; + void NextLine(wxPoint& position, const wxPoint* start_position = nullptr) const; + + virtual void OnDraw(wxDC& dc, sint32 start, sint32 count, const wxPoint& start_position) {}; + virtual void OnMouseMove(const wxPoint& position, uint32 line) {} + virtual void OnKeyPressed(sint32 key_code, const wxPoint& position) {} + virtual void OnMouseDown(); + virtual void OnMouseUp(); + virtual void OnMouseDClick(const wxPoint& position, uint32 line) {} + virtual void OnContextMenu(const wxPoint& position, uint32 line) {} + virtual bool OnShowTooltip(const wxPoint& position, uint32 line); + void OnEraseBackground(wxEraseEvent&) {} + + sint32 m_line_height; + sint32 m_char_width; + size_t m_elements_visible = 0; + size_t m_element_count = 0; + bool m_scrolled_to_end = true; + + std::wstringstream m_selected_text; + bool m_mouse_down = false; + wxRect m_selection; + wxPanel* m_tooltip_window; + +private: + void OnPaintEvent(wxPaintEvent& event); + void OnMouseMoveEvent(wxMouseEvent& event); + void OnKeyDownEvent(wxKeyEvent& event); + void OnKeyUpEvent(wxKeyEvent& event); + void OnMouseDownEvent(wxMouseEvent& event); + void OnMouseUpEvent(wxMouseEvent& event); + void OnMouseDClickEvent(wxMouseEvent& event); + void OnContextMenu(wxContextMenuEvent& event); + void OnTooltipTimer(wxTimerEvent& event); + + std::unordered_map m_key_states; + + wxFont m_font; + + wxTimer* m_tooltip_timer; + wxPoint m_mouse_position; +}; diff --git a/src/gui/components/wxDownloadManagerList.cpp b/src/gui/components/wxDownloadManagerList.cpp new file mode 100644 index 00000000..dce063e9 --- /dev/null +++ b/src/gui/components/wxDownloadManagerList.cpp @@ -0,0 +1,779 @@ +#include "gui/components/wxDownloadManagerList.h" + +#include "gui/helpers/wxHelpers.h" +#include "util/helpers/SystemException.h" +#include "Cafe/TitleList/GameInfo.h" +#include "gui/components/wxGameList.h" +#include "gui/helpers/wxCustomEvents.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "config/ActiveSettings.h" +#include "gui/ChecksumTool.h" +#include "Cemu/Tools/DownloadManager/DownloadManager.h" +#include "Cafe/TitleList/TitleId.h" +#include "gui/MainWindow.h" + +wxDEFINE_EVENT(wxEVT_REMOVE_ENTRY, wxCommandEvent); + + +wxDownloadManagerList::wxDownloadManagerList(wxWindow* parent, wxWindowID id) + : wxListCtrl(parent, id, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_VIRTUAL) +{ + AddColumns(); + + // tooltip TODO: extract class mb wxPanelTooltip + m_tooltip_window = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNO_BORDER); + auto* tooltip_sizer = new wxBoxSizer(wxVERTICAL); + m_tooltip_text = new wxStaticText(m_tooltip_window, wxID_ANY, wxEmptyString); + tooltip_sizer->Add(m_tooltip_text , 0, wxALL, 5); + m_tooltip_window->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_INFOBK)); + m_tooltip_window->SetSizerAndFit(tooltip_sizer); + m_tooltip_window->Hide(); + m_tooltip_timer = new wxTimer(this); + + Bind(wxEVT_LIST_COL_CLICK, &wxDownloadManagerList::OnColumnClick, this); + Bind(wxEVT_CONTEXT_MENU, &wxDownloadManagerList::OnContextMenu, this); + Bind(wxEVT_LIST_ITEM_SELECTED, &wxDownloadManagerList::OnItemSelected, this); + Bind(wxEVT_TIMER, &wxDownloadManagerList::OnTimer, this); + Bind(wxEVT_REMOVE_ITEM, &wxDownloadManagerList::OnRemoveItem, this); + Bind(wxEVT_REMOVE_ENTRY, &wxDownloadManagerList::OnRemoveEntry, this); + Bind(wxEVT_CLOSE_WINDOW, &wxDownloadManagerList::OnClose, this); +} + +boost::optional wxDownloadManagerList::GetSelectedTitleEntry() const +{ + const auto selection = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + if (selection != wxNOT_FOUND) + { + const auto tmp = GetTitleEntry(selection); + if (tmp.has_value()) + return tmp.value(); + } + + return {}; +} + +boost::optional wxDownloadManagerList::GetSelectedTitleEntry() +{ + const auto selection = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + if (selection != wxNOT_FOUND) + { + const auto tmp = GetTitleEntry(selection); + if (tmp.has_value()) + return tmp.value(); + } + + return {}; +} + +boost::optional wxDownloadManagerList::GetTitleEntry(uint64 titleId, uint16 titleVersion) +{ + for(const auto& v : m_data) + { + if (v->entry.titleId == titleId && v->entry.version == titleVersion) + return v->entry; + } + + return {}; +} + +void wxDownloadManagerList::AddColumns() +{ + wxListItem col0; + col0.SetId(ColumnTitleId); + col0.SetText(_("Title id")); + col0.SetWidth(120); + InsertColumn(ColumnTitleId, col0); + + wxListItem col1; + col1.SetId(ColumnName); + col1.SetText(_("Name")); + col1.SetWidth(260); + InsertColumn(ColumnName, col1); + + wxListItem col2; + col2.SetId(ColumnVersion); + col2.SetText(_("Version")); + col2.SetWidth(55); + InsertColumn(ColumnVersion, col2); + + wxListItem col3; + col3.SetId(ColumnType); + col3.SetText(_("Type")); + col3.SetWidth(60); + InsertColumn(ColumnType, col3); + + wxListItem col4; + col4.SetId(ColumnProgress); + col4.SetText(_("Progress")); + col4.SetWidth(wxLIST_AUTOSIZE_USEHEADER); + InsertColumn(ColumnProgress, col4); + + wxListItem col5; + col5.SetId(ColumnStatus); + col5.SetText(_("Status")); + col5.SetWidth(240); + InsertColumn(ColumnStatus, col5); +} + +wxString wxDownloadManagerList::OnGetItemText(long item, long column) const +{ + if (item >= GetItemCount()) + return wxEmptyString; + + const auto entry = GetTitleEntry(item); + if (entry.has_value()) + return GetTitleEntryText(entry.value(), (ItemColumn)column); + + return wxEmptyString; +} + +wxItemAttr* wxDownloadManagerList::OnGetItemAttr(long item) const +{ + const auto entry = GetTitleEntry(item); + if (entry.has_value()) + { + auto& entryData = entry.value(); + if (entryData.status == TitleDownloadStatus::Downloading || + entryData.status == TitleDownloadStatus::Verifying || + entryData.status == TitleDownloadStatus::Installing) + { + const wxColour kActiveColor{ 0xFFE0E0 }; + static wxListItemAttr s_error_attr(GetTextColour(), kActiveColor, GetFont()); + return &s_error_attr; + } + else if (entryData.status == TitleDownloadStatus::Installed && entryData.isPackage) + { + const wxColour kActiveColor{ 0xE0FFE0 }; + static wxListItemAttr s_error_attr(GetTextColour(), kActiveColor, GetFont()); + return &s_error_attr; + } + else if (entryData.status == TitleDownloadStatus::Error) + { + const wxColour kActiveColor{ 0xCCCCF2 }; + static wxListItemAttr s_error_attr(GetTextColour(), kActiveColor, GetFont()); + return &s_error_attr; + } + } + + const wxColour kSecondColor{ 0xFDF9F2 }; + static wxListItemAttr s_coloured_attr(GetTextColour(), kSecondColor, GetFont()); + return item % 2 == 0 ? nullptr : &s_coloured_attr; +} + +boost::optional wxDownloadManagerList::GetTitleEntry(long item) +{ + long counter = 0; + for (const auto& data : m_sorted_data) + { + if (!data.get().visible) + continue; + + if (item != counter++) + continue; + + return data.get().entry; + } + + return {}; +} + +boost::optional wxDownloadManagerList::GetTitleEntry(long item) const +{ + long counter = 0; + for (const auto& data : m_sorted_data) + { + if (!data.get().visible) + continue; + + if (item != counter++) + continue; + + return data.get().entry; + } + + return {}; +} + +void wxDownloadManagerList::OnClose(wxCloseEvent& event) +{ + event.Skip(); + // wait until all tasks are complete + if (m_context_worker.valid()) + m_context_worker.get(); + g_mainFrame->RequestGameListRefresh(); // todo: add games instead of fully refreshing game list if a game is downloaded +} + +void wxDownloadManagerList::OnColumnClick(wxListEvent& event) +{ + const int column = event.GetColumn(); + + if (column == m_sort_by_column) + { + m_sort_less = !m_sort_less; + } + else + { + m_sort_by_column = column; + m_sort_less = true; + } + SortEntries(); + event.Skip(); +} + +void wxDownloadManagerList::RemoveItem(long item) +{ + const int item_count = GetItemCount(); + + const ItemData* ref = nullptr; + long counter = 0; + for(auto it = m_sorted_data.begin(); it != m_sorted_data.end(); ++it) + { + if (!it->get().visible) + continue; + + if (item != counter++) + continue; + + ref = &(it->get()); + m_sorted_data.erase(it); + break; + } + + // shouldn't happen + if (ref == nullptr) + return; + + for(auto it = m_data.begin(); it != m_data.end(); ++it) + { + if (ref != (*it).get()) + continue; + + m_data.erase(it); + break; + } + + SetItemCount(std::max(0, item_count - 1)); + RefreshPage(); +} + +void wxDownloadManagerList::RemoveItem(const TitleEntry& entry) +{ + const int item_count = GetItemCount(); + + const TitleEntry* ref = &entry; + for (auto it = m_sorted_data.begin(); it != m_sorted_data.end(); ++it) + { + if (ref != &it->get().entry) + continue; + + m_sorted_data.erase(it); + break; + } + + for (auto it = m_data.begin(); it != m_data.end(); ++it) + { + if (ref != &(*it).get()->entry) + continue; + + m_data.erase(it); + break; + } + + SetItemCount(std::max(0, item_count - 1)); + RefreshPage(); +} + +void wxDownloadManagerList::OnItemSelected(wxListEvent& event) +{ + event.Skip(); + m_tooltip_timer->Stop(); + const auto selection = event.GetIndex(); + + if (selection == wxNOT_FOUND) + { + m_tooltip_window->Hide(); + return; + } + +} + +enum ContextMenuEntries +{ + kContextMenuRetry, + kContextMenuDownload, + kContextMenuPause, + kContextMenuResume, +}; +void wxDownloadManagerList::OnContextMenu(wxContextMenuEvent& event) +{ + // still doing work + if (m_context_worker.valid() && !future_is_ready(m_context_worker)) + return; + + wxMenu menu; + menu.Bind(wxEVT_COMMAND_MENU_SELECTED, &wxDownloadManagerList::OnContextMenuSelected, this); + + const auto selection = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + if (selection == wxNOT_FOUND) + return; + + const auto entry = GetTitleEntry(selection); + if (!entry.has_value()) + return; + + if (entry->isPaused) + menu.Append(kContextMenuResume, _("&Resume")); + else if (entry->status == TitleDownloadStatus::Error) + menu.Append(kContextMenuRetry, _("&Retry")); + else if(entry->status == TitleDownloadStatus::Available) + menu.Append(kContextMenuDownload, _("&Download")); + //else if(entry->status == TitleDownloadStatus::Downloading || entry->status == TitleDownloadStatus::Initializing) + // menu.Append(kContextMenuPause, _("&Pause")); buggy! + + PopupMenu(&menu); + + // TODO: fix tooltip position +} + +void wxDownloadManagerList::SetCurrentDownloadMgr(DownloadManager* dlMgr) +{ + std::unique_lock _l(m_dlMgrMutex); + m_dlMgr = dlMgr; +} + +bool wxDownloadManagerList::StartDownloadEntry(const TitleEntry& entry) +{ + std::unique_lock _l(m_dlMgrMutex); + if (m_dlMgr) + m_dlMgr->initiateDownload(entry.titleId, entry.version); + return true; +} + +bool wxDownloadManagerList::RetryDownloadEntry(const TitleEntry& entry) +{ + StartDownloadEntry(entry); + return true; +} + +bool wxDownloadManagerList::PauseDownloadEntry(const TitleEntry& entry) +{ + std::unique_lock _l(m_dlMgrMutex); + if (m_dlMgr) + m_dlMgr->pauseDownload(entry.titleId, entry.version); + return true; +} + +void wxDownloadManagerList::OnContextMenuSelected(wxCommandEvent& event) +{ + // still doing work + if (m_context_worker.valid() && !future_is_ready(m_context_worker)) + return; + + const auto selection = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + if (selection == wxNOT_FOUND) + return; + + const auto entry = GetTitleEntry(selection); + if (!entry.has_value()) + return; + + switch (event.GetId()) + { + case kContextMenuDownload: + StartDownloadEntry(entry.value()); + break; + case kContextMenuRetry: + RetryDownloadEntry(entry.value()); + break; + case kContextMenuResume: + StartDownloadEntry(entry.value()); + break; + case kContextMenuPause: + PauseDownloadEntry(entry.value()); + break; + } +} + +void wxDownloadManagerList::OnTimer(wxTimerEvent& event) +{ + if(event.GetTimer().GetId() != m_tooltip_timer->GetId()) + { + event.Skip(); + return; + } + + m_tooltip_window->Show(); +} + +void wxDownloadManagerList::OnRemoveItem(wxCommandEvent& event) +{ + RemoveItem(event.GetInt()); +} + +void wxDownloadManagerList::OnRemoveEntry(wxCommandEvent& event) +{ + wxASSERT(event.GetClientData() != nullptr); + RemoveItem(*(TitleEntry*)event.GetClientData()); +} + +wxString wxDownloadManagerList::GetTitleEntryText(const TitleEntry& entry, ItemColumn column) +{ + switch (column) + { + case ColumnTitleId: + return wxStringFormat2("{:08x}-{:08x}", (uint32)(entry.titleId >> 32), (uint32)(entry.titleId & 0xFFFFFFFF)); + case ColumnName: + { + return entry.name; + } + case ColumnType: + return wxStringFormat2("{}", entry.type); + case ColumnVersion: + { + // dont show version for base game unless it is not v0 + if (entry.type == EntryType::Base && entry.version == 0) + return ""; + if (entry.type == EntryType::DLC && entry.version == 0) + return ""; + return wxStringFormat2("v{}", entry.version); + } + case ColumnProgress: + { + if (entry.status == TitleDownloadStatus::Downloading) + { + if (entry.progress >= 1000) + return "100%"; + return wxStringFormat2("{:.1f}%", (float)entry.progress / 10.0f); // one decimal + } + else if (entry.status == TitleDownloadStatus::Installing || entry.status == TitleDownloadStatus::Checking || entry.status == TitleDownloadStatus::Verifying) + { + return wxStringFormat2("{0}/{1}", entry.progress, entry.progressMax); // number of processed files/content files + } + return ""; + } + case ColumnStatus: + { + if (entry.isPaused) + return _("Paused"); + else if (entry.status == TitleDownloadStatus::Available) + { + if (entry.progress == 1) + return _("Not installed (Partially downloaded)"); + if (entry.progress == 2) + return _("Update available"); + return _("Not installed"); + } + else if (entry.status == TitleDownloadStatus::Initializing) + return _("Initializing"); + else if (entry.status == TitleDownloadStatus::Checking) + return _("Checking"); + else if (entry.status == TitleDownloadStatus::Queued) + return _("Queued"); + else if (entry.status == TitleDownloadStatus::Downloading) + return _("Downloading"); + else if (entry.status == TitleDownloadStatus::Verifying) + return _("Verifying"); + else if (entry.status == TitleDownloadStatus::Installing) + return _("Installing"); + else if (entry.status == TitleDownloadStatus::Installed) + return _("Installed"); + else if (entry.status == TitleDownloadStatus::Error) + { + wxString errorStatusMsg = _("Error:"); + errorStatusMsg.append(" "); + errorStatusMsg.append(entry.errorMsg); + return errorStatusMsg; + } + else + return "Unknown status"; + } + } + + return wxEmptyString; +} + +void wxDownloadManagerList::AddOrUpdateTitle(TitleEntryData_t* obj) +{ + const auto& data = obj->GetData(); + // if already in list only update + auto entry = GetTitleEntry(data.titleId, data.version); + if (entry.has_value()) + { + // update item + entry.value() = data; + RefreshPage(); // more efficient way to do this? + return; + } + + m_data.emplace_back(std::make_unique(true, data)); + m_sorted_data.emplace_back(*m_data[m_data.size() - 1]); + SetItemCount(m_data.size()); + + // reapply sort + Filter2(m_filterShowTitles, m_filterShowUpdates, m_filterShowInstalled); + SortEntries(); +} + +void wxDownloadManagerList::UpdateTitleStatusDepr(TitleEntryData_t* obj, const wxString& text) +{ + const auto& data = obj->GetData(); + const auto entry = GetTitleEntry(data.titleId, data.version); + // check if already added to list + if (!entry.has_value()) + return; + + // update gamelist text + for(size_t i = 0; i < m_sorted_data.size(); ++i) + { + if (m_sorted_data[i].get().entry == data) + { + SetItem(i, ColumnStatus, text); + return; + } + } + + forceLogDebug_printf("cant update title status of %llx", data.titleId); +} + +int wxDownloadManagerList::AddImage(const wxImage& image) const +{ + return -1; // m_image_list->Add(image.Scale(kListIconWidth, kListIconWidth, wxIMAGE_QUALITY_BICUBIC)); +} + +// return < +bool wxDownloadManagerList::SortFunc(std::span sortColumnOrder, const Type_t& v1, const Type_t& v2) +{ + cemu_assert_debug(sortColumnOrder.size() == 5); + + // visible have always priority + if (!v1.get().visible && v2.get().visible) + return false; + else if (v1.get().visible && !v2.get().visible) + return true; + + const auto& entry1 = v1.get().entry; + const auto& entry2 = v2.get().entry; + + for (size_t i = 0; i < sortColumnOrder.size(); i++) + { + int sortByColumn = sortColumnOrder[i]; + if (sortByColumn == ColumnTitleId) + { + // ensure strong ordering -> use type since only one entry should be now (should be changed if every save for every user is displayed spearately?) + if (entry1.titleId != entry2.titleId) + return entry1.titleId < entry2.titleId; + } + else if (sortByColumn == ColumnName) + { + const int tmp = entry1.name.CmpNoCase(entry2.name); + if (tmp != 0) + return tmp < 0; + } + else if (sortByColumn == ColumnType) + { + if (std::underlying_type_t(entry1.type) != std::underlying_type_t(entry2.type)) + return std::underlying_type_t(entry1.type) < std::underlying_type_t(entry2.type); + } + else if (sortByColumn == ColumnVersion) + { + if (entry1.version != entry2.version) + return entry1.version < entry2.version; + } + else if (sortByColumn == ColumnProgress) + { + if (entry1.progress != entry2.progress) + return entry1.progress < entry2.progress; + } + else + { + cemu_assert_debug(false); + return (uintptr_t)&entry1 < (uintptr_t)&entry2; + } + + } + + return false; +} + +#include + +void wxDownloadManagerList::SortEntries() +{ + boost::container::small_vector s_SortColumnOrder{ ColumnName, ColumnType, ColumnVersion, ColumnTitleId, ColumnProgress }; + + if (m_sort_by_column != -1) + { + // prioritize column by moving it to first position in the column sort order list + s_SortColumnOrder.erase(std::remove(s_SortColumnOrder.begin(), s_SortColumnOrder.end(), m_sort_by_column), s_SortColumnOrder.end()); + s_SortColumnOrder.insert(s_SortColumnOrder.begin(), m_sort_by_column); + } + + std::sort(m_sorted_data.begin(), m_sorted_data.end(), + [this, &s_SortColumnOrder](const Type_t& v1, const Type_t& v2) -> bool + { + const bool result = SortFunc({ s_SortColumnOrder.data(), s_SortColumnOrder.size() }, v1, v2); + return m_sort_less ? result : !result; + }); + + RefreshPage(); +} + +void wxDownloadManagerList::RefreshPage() { RefreshItems(GetTopItem(), GetTopItem() + GetCountPerPage() + 1); } + +int wxDownloadManagerList::Filter(const wxString& filter, const wxString& prefix, ItemColumn column) +{ + if (prefix.empty()) + return -1; + + if (!filter.StartsWith(prefix)) + return -1; + + int counter = 0; + const auto tmp_filter = filter.substr(prefix.size() - 1).Trim(false); + for (auto&& data : m_data) + { + if (GetTitleEntryText(data->entry, column).Upper().Contains(tmp_filter)) + { + data->visible = true; + ++counter; + } + else + data->visible = false; + } + return counter; +} + +void wxDownloadManagerList::Filter(const wxString& filter) +{ + if(filter.empty()) + { + std::for_each(m_data.begin(), m_data.end(), [](ItemDataPtr& data) { data->visible = true; }); + SetItemCount(m_data.size()); + RefreshPage(); + return; + } + + const auto filter_upper = filter.Upper().Trim(false).Trim(true); + int counter = 0; + + if (const auto result = Filter(filter_upper, "TITLEID:", ColumnTitleId) != -1) + counter = result; + else if (const auto result = Filter(filter_upper, "NAME:", ColumnName) != -1) + counter = result; + else if (const auto result = Filter(filter_upper, "TYPE:", ColumnType) != -1) + counter = result; + //else if (const auto result = Filter(filter_upper, "REGION:", ColumnRegion) != -1) + // counter = result; + else if (const auto result = Filter(filter_upper, "VERSION:", ColumnVersion) != -1) + counter = result; + else if(filter_upper == "ERROR") + { + for (auto&& data : m_data) + { + bool visible = false; + //if (data->entry.error != TitleError::None) + // visible = true; + + data->visible = visible; + if (visible) + ++counter; + } + } + else + { + for (auto&& data : m_data) + { + bool visible = false; + if (data->entry.name.Upper().Contains(filter_upper)) + visible = true; + else if (GetTitleEntryText(data->entry, ColumnTitleId).Upper().Contains(filter_upper)) + visible = true; + else if (GetTitleEntryText(data->entry, ColumnType).Upper().Contains(filter_upper)) + visible = true; + + data->visible = visible; + if (visible) + ++counter; + } + } + + SetItemCount(counter); + RefreshPage(); +} + +void wxDownloadManagerList::Filter2(bool showTitles, bool showUpdates, bool showInstalled) +{ + m_filterShowTitles = showTitles; + m_filterShowUpdates = showUpdates; + m_filterShowInstalled = showInstalled; + if (showTitles && showUpdates && showInstalled) + { + std::for_each(m_data.begin(), m_data.end(), [](ItemDataPtr& data) { data->visible = true; }); + SetItemCount(m_data.size()); + RefreshPage(); + return; + } + + size_t counter = 0; + + for (auto&& data : m_data) + { + bool visible = false; + + TitleIdParser tParser(data->entry.titleId); + bool isInstalled = data->entry.status == TitleDownloadStatus::Installed; + if (tParser.IsBaseTitleUpdate()) + { + if (showUpdates && (showInstalled || !isInstalled)) + visible = true; + } + else + { + if (showTitles && (showInstalled || !isInstalled)) + visible = true; + } + + data->visible = visible; + if (visible) + ++counter; + } + + SetItemCount(counter); + RefreshPage(); +} + +size_t wxDownloadManagerList::GetCountByType(EntryType type) const +{ + size_t result = 0; + for(const auto& data : m_data) + { + if (data->entry.type == type) + ++result; + } + return result; +} + +void wxDownloadManagerList::ClearItems() +{ + m_sorted_data.clear(); + m_data.clear(); + SetItemCount(0); + RefreshPage(); +} + +void wxDownloadManagerList::AutosizeColumns() +{ + wxAutosizeColumns(this, ColumnTitleId, ColumnMAX - 1); +} + diff --git a/src/gui/components/wxDownloadManagerList.h b/src/gui/components/wxDownloadManagerList.h new file mode 100644 index 00000000..0af5b082 --- /dev/null +++ b/src/gui/components/wxDownloadManagerList.h @@ -0,0 +1,174 @@ +#pragma once + +#include "gui/helpers/wxCustomData.h" +#include "config/CemuConfig.h" + +#include + +#include // std::optional doesn't support optional reference inner types yet +#include +#include + +class wxDownloadManagerList : public wxListCtrl +{ + friend class TitleManager; +public: + wxDownloadManagerList(wxWindow* parent, wxWindowID id = wxID_ANY); + + enum ItemColumn + { + ColumnTitleId = 0, + ColumnName, + ColumnVersion, + ColumnType, + ColumnProgress, + ColumnStatus, + + ColumnMAX, + }; + + enum class EntryType + { + Base, + Update, + DLC, + }; + + enum class TitleDownloadStatus + { + None, + Available, // available for download + Error, // error state + Queued, // queued for download + Initializing, // downloading/parsing TMD + Checking, // checking for previously downloaded files + Downloading, + Verifying, // verifying downloaded files + Installing, + Installed, + // error state? + }; + + void SortEntries(); + void RefreshPage(); + void Filter(const wxString& filter); + void Filter2(bool showTitles, bool showUpdates, bool showInstalled); + [[nodiscard]] size_t GetCountByType(EntryType type) const; + void ClearItems(); + void AutosizeColumns(); + + struct TitleEntry + { + TitleEntry(const EntryType& type, bool isPackage, uint64 titleId, uint16 version, bool isPaused) + : type(type), isPackage(isPackage), titleId(titleId), version(version), isPaused(isPaused) {} + + EntryType type; + + bool isPaused{}; + int icon = -1; + bool isPackage; + uint64 titleId; + wxString name; + uint32_t version{ 0 }; + uint32 progress; // downloading: in 1/10th of a percent, installing: number of files + uint32 progressMax{ 0 }; + CafeConsoleRegion region; + + TitleDownloadStatus status = TitleDownloadStatus::None; + std::string errorMsg; + + bool operator==(const TitleEntry& e) const + { + return type == e.type && titleId == e.titleId && version == e.version; + } + bool operator!=(const TitleEntry& e) const { return !(*this == e); } + }; + boost::optional GetSelectedTitleEntry() const; + +private: + void AddColumns(); + int Filter(const wxString& filter, const wxString& prefix, ItemColumn column); + boost::optional GetSelectedTitleEntry(); + boost::optional GetTitleEntry(uint64 titleId, uint16 titleVersion); + + class wxPanel* m_tooltip_window; + class wxStaticText* m_tooltip_text; + class wxTimer* m_tooltip_timer; + + void OnClose(wxCloseEvent& event); + void OnColumnClick(wxListEvent& event); + void OnContextMenu(wxContextMenuEvent& event); + void OnItemSelected(wxListEvent& event); + void OnContextMenuSelected(wxCommandEvent& event); + void OnTimer(class wxTimerEvent& event); + void OnRemoveItem(wxCommandEvent& event); + void OnRemoveEntry(wxCommandEvent& event); + + using TitleEntryData_t = wxCustomData; + void AddOrUpdateTitle(TitleEntryData_t* obj); + void UpdateTitleStatusDepr(TitleEntryData_t* obj, const wxString& text); + int AddImage(const wxImage& image) const; + wxString OnGetItemText(long item, long column) const override; + wxItemAttr* OnGetItemAttr(long item) const override; + [[nodiscard]] boost::optional GetTitleEntry(long item) const; + [[nodiscard]] boost::optional GetTitleEntry(long item); + //[[nodiscard]] boost::optional GetTitleEntry(const fs::path& path) const; + //[[nodiscard]] boost::optional GetTitleEntry(const fs::path& path); + + void SetCurrentDownloadMgr(class DownloadManager* dlMgr); + + //bool FixEntry(TitleEntry& entry); + //bool VerifyEntryFiles(TitleEntry& entry); + bool StartDownloadEntry(const TitleEntry& entry); + bool RetryDownloadEntry(const TitleEntry& entry); + bool PauseDownloadEntry(const TitleEntry& entry); + + void RemoveItem(long item); + void RemoveItem(const TitleEntry& entry); + + struct ItemData + { + ItemData(bool visible, const TitleEntry& entry) + : visible(visible), entry(entry) {} + + bool visible; + TitleEntry entry; + }; + using ItemDataPtr = std::unique_ptr; + std::vector m_data; + std::vector> m_sorted_data; + + int m_sort_by_column = ItemColumn::ColumnName; + bool m_sort_less = true; + + bool m_filterShowTitles = true; + bool m_filterShowUpdates = true; + bool m_filterShowInstalled = true; + DownloadManager* m_dlMgr{}; + std::mutex m_dlMgrMutex; + using Type_t = std::reference_wrapper; + bool SortFunc(std::span sortColumnOrder, const Type_t& v1, const Type_t& v2); + + static wxString GetTitleEntryText(const TitleEntry& entry, ItemColumn column); + std::future m_context_worker; +}; + +template <> +struct fmt::formatter : formatter +{ + using base = fmt::formatter; + template + auto format(const wxDownloadManagerList::EntryType& type, FormatContext& ctx) + { + switch (type) + { + case wxDownloadManagerList::EntryType::Base: + return base::format("base", ctx); + case wxDownloadManagerList::EntryType::Update: + return base::format("update", ctx); + case wxDownloadManagerList::EntryType::DLC: + return base::format("DLC", ctx); + } + return base::format(std::to_string(static_cast>(type)), ctx); + } +}; \ No newline at end of file diff --git a/src/gui/components/wxGameList.cpp b/src/gui/components/wxGameList.cpp new file mode 100644 index 00000000..4c9cdc5f --- /dev/null +++ b/src/gui/components/wxGameList.cpp @@ -0,0 +1,1098 @@ +#include "gui/components/wxGameList.h" + +#include "gui/helpers/wxCustomData.h" +#include "util/helpers/helpers.h" +#include "gui/GameProfileWindow.h" + +#ifdef _WIN32 +#include +#endif +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "config/ActiveSettings.h" +#include "config/LaunchSettings.h" +#include "Cafe/TitleList/GameInfo.h" +#include "Cafe/TitleList/TitleList.h" + +#include "gui/CemuApp.h" +#include "gui/helpers/wxHelpers.h" +#include "gui/MainWindow.h" + +#include "../wxHelper.h" + +#include "Cafe/IOSU/PDM/iosu_pdm.h" // for last played and play time + +// public events +wxDEFINE_EVENT(wxEVT_OPEN_SETTINGS, wxCommandEvent); +wxDEFINE_EVENT(wxEVT_GAMELIST_BEGIN_UPDATE, wxCommandEvent); +wxDEFINE_EVENT(wxEVT_GAMELIST_END_UPDATE, wxCommandEvent); +wxDEFINE_EVENT(wxEVT_OPEN_GRAPHIC_PACK, wxTitleIdEvent); + +// internal events +wxDEFINE_EVENT(wxEVT_GAME_ENTRY_ADDED_OR_REMOVED, wxTitleIdEvent); + +constexpr uint64 kDefaultEntryData = 0x1337; + +void _stripPathFilename(fs::path& path) +{ + if (path.has_extension()) + path = path.parent_path(); +} + +wxGameList::wxGameList(wxWindow* parent, wxWindowID id) + : wxListCtrl(parent, id, wxDefaultPosition, wxDefaultSize, GetStyleFlags(Style::kList)), m_style(Style::kList) +{ + CreateListColumns(); + + m_image_list = new wxImageList(kIconWidth, kIconWidth); + wxListCtrl::SetImageList(m_image_list, wxIMAGE_LIST_NORMAL); + + m_image_list_small = new wxImageList(kListIconWidth, kListIconWidth); + wxListCtrl::SetImageList(m_image_list_small, wxIMAGE_LIST_SMALL); + + m_tooltip_window = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNO_BORDER); + auto* tooltip_sizer = new wxBoxSizer(wxVERTICAL); + tooltip_sizer->Add(new wxStaticText(m_tooltip_window, wxID_ANY, _("This game entry seems to be either an update or the base game was merged with update data\nBroken game dumps cause various problems during emulation and may even stop working at all in future Cemu versions\nPlease make sure the base game is intact and install updates only with the File->Install Update/DLC option")), 0, wxALL, 5); + m_tooltip_window->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_INFOBK)); + m_tooltip_window->SetSizerAndFit(tooltip_sizer); + m_tooltip_window->Hide(); + + m_tooltip_timer = new wxTimer(this); + + Bind(wxEVT_CLOSE_WINDOW, &wxGameList::OnClose, this); + Bind(wxEVT_MOTION, &wxGameList::OnMouseMove, this); + Bind(wxEVT_LIST_KEY_DOWN, &wxGameList::OnKeyDown, this); + Bind(wxEVT_CONTEXT_MENU, &wxGameList::OnContextMenu, this); + Bind(wxEVT_LIST_ITEM_ACTIVATED, &wxGameList::OnItemActivated, this); + Bind(wxEVT_GAME_ENTRY_ADDED_OR_REMOVED, &wxGameList::OnGameEntryUpdatedByTitleId, this); + Bind(wxEVT_TIMER, &wxGameList::OnTimer, this); + Bind(wxEVT_LEAVE_WINDOW, &wxGameList::OnLeaveWindow, this); + + Bind(wxEVT_LIST_COL_CLICK, &wxGameList::OnColumnClick, this); + Bind(wxEVT_LIST_COL_BEGIN_DRAG, &wxGameList::OnColumnBeginResize, this); + Bind(wxEVT_LIST_COL_END_DRAG, &wxGameList::OnColumnResize, this); + Bind(wxEVT_LIST_COL_RIGHT_CLICK, &wxGameList::OnColumnRightClick, this); + + m_callbackIdTitleList = CafeTitleList::RegisterCallback([](CafeTitleListCallbackEvent* evt, void* ctx) { ((wxGameList*)ctx)->HandleTitleListCallback(evt); }, this); + + // start async worker (for icon loading) + m_async_worker_active = true; + m_async_worker_thread = std::thread(&wxGameList::AsyncWorkerThread, this); +} + +wxGameList::~wxGameList() +{ + CafeTitleList::UnregisterCallback(m_callbackIdTitleList); + + m_tooltip_timer->Stop(); + + // shut down async worker + m_async_worker_active.store(false); + m_async_task_count.increment(); + m_async_worker_thread.join(); + + // destroy image lists + delete m_image_list; + delete m_image_list_small; + + // clear image cache + m_icon_cache_mtx.lock(); + m_icon_cache.clear(); + m_icon_cache_mtx.unlock(); +} + +void wxGameList::LoadConfig() +{ + const auto& config = GetConfig(); + SetStyle((Style)config.game_list_style, false); + + if (!config.game_list_column_order.empty()) + { + wxArrayInt order; + order.reserve(ColumnFavorite); + + const auto order_string = std::string_view(config.game_list_column_order).substr(1); + + const boost::char_separator sep(","); + boost::tokenizer tokens(order_string.begin(), order_string.end(), sep); + for(const auto& token : tokens) + { + order.push_back(ConvertString(token, 10)); + } + + #ifdef wxHAS_LISTCTRL_COLUMN_ORDER + if(order.GetCount() == ColumnFavorite) + SetColumnsOrder(order); + #endif + } +} + +void wxGameList::SaveConfig(bool flush) +{ + auto& config = GetConfig(); + + config.game_list_style = (int)m_style; + #ifdef wxHAS_LISTCTRL_COLUMN_ORDER + config.game_list_column_order = fmt::format("{}", GetColumnsOrder()); + #endif + + if (flush) + g_config.Save(); +} + +bool wxGameList::IsVisible(long item) const +{ + wxRect itemRect; + GetItemRect(item, itemRect); + const wxRect clientRect = GetClientRect(); + bool visible = clientRect.Intersects(itemRect); + return visible; +} + +void wxGameList::ReloadGameEntries(bool cached) +{ + wxWindowUpdateLocker windowlock(this); + DeleteAllItems(); + // tell the game list to rescan + CafeTitleList::Refresh(); + // resend notifications for all known titles by re-registering the callback + CafeTitleList::UnregisterCallback(m_callbackIdTitleList); + m_callbackIdTitleList = CafeTitleList::RegisterCallback([](CafeTitleListCallbackEvent* evt, void* ctx) { ((wxGameList*)ctx)->HandleTitleListCallback(evt); }, this); +} + +long wxGameList::FindListItemByTitleId(uint64 title_id) const +{ + for (int i = 0; i < GetItemCount(); ++i) + { + const auto id = (uint64)GetItemData(i); + if (id == title_id) + return i; + } + + return wxNOT_FOUND; +} + +// get title name with cache +std::string wxGameList::GetNameByTitleId(uint64 titleId) +{ + auto it = m_name_cache.find(titleId); + if (it != m_name_cache.end()) + return it->second; + TitleInfo titleInfo; + if (!CafeTitleList::GetFirstByTitleId(titleId, titleInfo)) + return "Unknown title"; + std::string name; + if (!GetConfig().GetGameListCustomName(titleId, name)) + name = titleInfo.GetTitleName(); + m_name_cache.emplace(titleId, name); + return name; +} + +void wxGameList::SetStyle(Style style, bool save) +{ + if (m_style == style) + return; + + wxWindowUpdateLocker updatelock(this); + + m_style = style; + SetWindowStyleFlag(GetStyleFlags(m_style)); + + uint64 selected_title_id = 0; + auto selection = GetNextItem(wxNOT_FOUND, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + if (selection != wxNOT_FOUND) + { + selected_title_id = (uint64)GetItemData(selection); + selection = wxNOT_FOUND; + } + + switch(style) + { + case Style::kIcons: + wxListCtrl::SetImageList(m_image_list, wxIMAGE_LIST_NORMAL); + break; + case Style::kSmallIcons: + wxListCtrl::SetImageList(m_image_list_small, wxIMAGE_LIST_NORMAL); + break; + } + + ReloadGameEntries(); + SortEntries(); + UpdateItemColors(); + + if(selection != wxNOT_FOUND) + { + SetItemState(selection, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED); + EnsureVisible(selection); + } + + if(save) + { + GetConfig().game_list_style = (int)m_style; + g_config.Save(); + } + + ApplyGameListColumnWidths(); +} + +long wxGameList::GetStyleFlags(Style style) const +{ + switch (style) + { + case Style::kList: + return (wxLC_SINGLE_SEL | wxLC_REPORT); + case Style::kIcons: + return (wxLC_SINGLE_SEL | wxLC_ICON); + case Style::kSmallIcons: + return (wxLC_SINGLE_SEL | wxLC_ICON); + default: + wxASSERT(false); + return (wxLC_SINGLE_SEL | wxLC_REPORT); + } +} + +void wxGameList::UpdateItemColors(sint32 startIndex) +{ + wxWindowUpdateLocker lock(this); + for (int i = startIndex; i < GetItemCount(); ++i) + { + const auto titleId = (uint64)GetItemData(i); + if (GetConfig().IsGameListFavorite(titleId))//entry->favorite) + SetItemBackgroundColour(i, kFavoriteColor); + else if ((i&1) != 0) + SetItemBackgroundColour(i, kSecondColor); + else + SetItemBackgroundColour(i, 0xFFFFFFUL); + } +} + +int wxGameList::SortComparator(uint64 titleId1, uint64 titleId2, SortData* sortData) +{ + //if(sortData->column == ColumnGameStarted) + // return boost::ilexicographical_compare(name1, name2) ? 0 : 1; + + + bool isFavoriteA = GetConfig().IsGameListFavorite(titleId1); + bool isFavoriteB = GetConfig().IsGameListFavorite(titleId2); + if (isFavoriteA != isFavoriteB) + return isFavoriteB; + + // default to name + std::string name1 = GetNameByTitleId(titleId1); + std::string name2 = GetNameByTitleId(titleId2); + if(sortData->dir) + return boost::ilexicographical_compare(name1, name2) ? 0 : 1; + return boost::ilexicographical_compare(name1, name2) ? 1 : 0; +} + +int wxGameList::SortFunction(wxIntPtr item1, wxIntPtr item2, wxIntPtr sortData) +{ + const auto sort_data = (SortData*)sortData; + const int dir = sort_data->dir; + + return sort_data->thisptr->SortComparator((uint64)item1, (uint64)item2, sort_data); +} + +void wxGameList::SortEntries(int column) +{ + if (column == -1) + column = s_last_column; + else + { + + if (s_last_column == column) + { + s_last_column = 0; + s_direction = -1; + } + else + { + s_last_column = column; + s_direction = 1; + } + } + + switch (column) + { + case ColumnName: + case ColumnGameTime: + case ColumnGameStarted: + case ColumnRegion: + { + SortData data{ this, column, s_direction }; + SortItems(SortFunction, (wxIntPtr)&data); + break; + } + } +} + +void wxGameList::CreateListColumns() +{ + DeleteAllColumns(); + + const auto& config = GetConfig(); + wxListItem col0; + col0.SetId(ColumnHiddenName); + col0.SetWidth(0); + InsertColumn(ColumnHiddenName, col0); + + wxListItem col1; + col1.SetId(ColumnIcon); + col1.SetWidth(kListIconWidth); + InsertColumn(ColumnIcon, col1); + + wxListItem col2; + col2.SetId(ColumnName); + col2.SetText(_("Game")); + col2.SetWidth(config.column_width.name); + InsertColumn(ColumnName, col2); + + wxListItem col3; + col3.SetId(ColumnVersion); + col3.SetText(_("Version")); + col3.SetWidth(config.column_width.version); + col3.SetAlign(wxLIST_FORMAT_RIGHT); + InsertColumn(ColumnVersion, col3); + + wxListItem col4; + col4.SetId(ColumnDLC); + col4.SetText(_("DLC")); + col4.SetWidth(config.column_width.dlc); + col4.SetAlign(wxLIST_FORMAT_RIGHT); + InsertColumn(ColumnDLC, col4); + + wxListItem col5; + col5.SetId(ColumnGameTime); + col5.SetText(_("You've played")); + col5.SetWidth(config.column_width.game_time); + InsertColumn(ColumnGameTime, col5); + + wxListItem col6; + col6.SetId(ColumnGameStarted); + col6.SetText(_("Last played")); + col6.SetWidth(config.column_width.game_started); + InsertColumn(ColumnGameStarted, col6); + + wxListItem col7; + col7.SetId(ColumnRegion); + col7.SetText(_("Region")); + col7.SetWidth(config.column_width.region); + InsertColumn(ColumnRegion, col7); +} + +void wxGameList::OnKeyDown(wxListEvent& event) +{ + event.Skip(); + if (m_style != Style::kList) + return; + + const auto keycode = std::tolower(event.m_code); + if (keycode == WXK_LEFT) + { + const auto item_count = GetItemCount(); + if (item_count > 0) + { + auto selection = (int)GetNextItem(wxNOT_FOUND, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + if (selection == wxNOT_FOUND) + selection = 0; + else + selection = std::max(0, selection - GetCountPerPage()); + + SetItemState(wxNOT_FOUND, 0, wxLIST_STATE_SELECTED); + SetItemState(selection, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED); + EnsureVisible(selection); + } + } + else if (keycode == WXK_RIGHT) + { + const auto item_count = GetItemCount(); + if (item_count > 0) + { + auto selection = (int)GetNextItem(wxNOT_FOUND, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + if (selection == wxNOT_FOUND) + selection = 0; + + selection = std::min(item_count - 1, selection + GetCountPerPage()); + + SetItemState(wxNOT_FOUND, 0, wxLIST_STATE_SELECTED); + SetItemState(selection, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED); + EnsureVisible(selection); + } + } +} + + +enum ContextMenuEntries +{ + kContextMenuRefreshGames, + + kContextMenuStart, + kWikiPage, + kContextMenuFavorite, + kContextMenuEditName, + + kContextMenuGameFolder, + kContextMenuSaveFolder, + kContextMenuUpdateFolder, + kContextMenuDLCFolder, + kContextMenuEditGraphicPacks, + kContextMenuEditGameProfile, + + kContextMenuStyleList, + kContextMenuStyleIcon, + kContextMenuStyleIconSmall, +}; +void wxGameList::OnContextMenu(wxContextMenuEvent& event) +{ + auto& config = GetConfig(); + + wxMenu menu; + menu.Bind(wxEVT_COMMAND_MENU_SELECTED, &wxGameList::OnContextMenuSelected, this); + + const auto selection = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + if (selection != wxNOT_FOUND) + { + const auto title_id = (uint64)GetItemData(selection); + GameInfo2 gameInfo = CafeTitleList::GetGameInfo(title_id); + if (gameInfo.IsValid()) + { + menu.SetClientData((void*)title_id); + + menu.Append(kContextMenuStart, _("&Start")); + + bool isFavorite = false; + + menu.AppendSeparator(); + menu.AppendCheckItem(kContextMenuFavorite, _("&Favorite"))->Check(isFavorite); + menu.Append(kContextMenuEditName, _("&Edit name")); + + menu.AppendSeparator(); + menu.Append(kWikiPage, _("&Wiki page")); + menu.Append(kContextMenuGameFolder, _("&Game directory")); + menu.Append(kContextMenuSaveFolder, _("&Save directory"))->Enable(true); + menu.Append(kContextMenuUpdateFolder, _("&Update directory"))->Enable(gameInfo.HasUpdate()); + menu.Append(kContextMenuDLCFolder, _("&DLC directory"))->Enable(gameInfo.HasAOC()); + + menu.AppendSeparator(); + menu.Append(kContextMenuEditGraphicPacks, _("&Edit graphic packs")); + menu.Append(kContextMenuEditGameProfile, _("&Edit game profile")); + + menu.AppendSeparator(); + } + } + + menu.Append(kContextMenuRefreshGames, _("&Refresh game list"))->Enable(!CafeTitleList::IsScanning()); + menu.AppendSeparator(); + menu.AppendRadioItem(kContextMenuStyleList, _("Style: &List"))->Check(m_style == Style::kList); + menu.AppendRadioItem(kContextMenuStyleIcon, _("Style: &Icons"))->Check(m_style == Style::kIcons); + menu.AppendRadioItem(kContextMenuStyleIconSmall, _("Style: &Small Icons"))->Check(m_style == Style::kSmallIcons); + PopupMenu(&menu); +} + +void wxGameList::OnContextMenuSelected(wxCommandEvent& event) +{ + const auto title_id = (uint64)((wxMenu*)event.GetEventObject())->GetClientData(); + if (title_id) + { + GameInfo2 gameInfo = CafeTitleList::GetGameInfo(title_id); + if (gameInfo.IsValid()) + { + switch (event.GetId()) + { + case kContextMenuStart: + { + MainWindow::RequestLaunchGame(gameInfo.GetBase().GetPath(), wxLaunchGameEvent::INITIATED_BY::GAME_LIST); + break; + } + case kContextMenuFavorite: + GetConfig().SetGameListFavorite(title_id, !GetConfig().IsGameListFavorite(title_id)); + SortEntries(); + UpdateItemColors(); + //SaveConfig(); + break; + case kContextMenuEditName: + { + std::string customName = ""; + if (!GetConfig().GetGameListCustomName(title_id, customName)) + customName.clear(); + wxTextEntryDialog dialog(this, wxEmptyString, L"Enter a custom game title", customName); + if(dialog.ShowModal() == wxID_OK) + { + const auto custom_name = dialog.GetValue(); + GetConfig().SetGameListCustomName(title_id, wxHelper::MakeUTF8(custom_name)); + m_name_cache.clear(); + g_config.Save(); + // update list entry + for (int i = 0; i < GetItemCount(); ++i) + { + const auto id = (uint64)GetItemData(i); + if (id == title_id) + { + if (m_style == Style::kList) + SetItem(i, ColumnName, wxHelper::FromUtf8(GetNameByTitleId(title_id))); + break; + } + } + SortEntries(); + UpdateItemColors(); + } + break; + } + case kContextMenuGameFolder: + { +#ifdef _WIN32 + fs::path path(gameInfo.GetBase().GetPath()); + _stripPathFilename(path); + ShellExecuteW(GetHWND(), L"open", path.c_str(), nullptr, nullptr, SW_SHOWNORMAL); +#else + assert_dbg(); +#endif + break; + } + case kWikiPage: + { + // https://wiki.cemu.info/wiki/GameIDs + // WUP-P-ALZP + if (gameInfo.GetBase().ParseXmlInfo()) + { + std::string productCode = gameInfo.GetBase().GetMetaInfo()->GetProductCode(); + const auto tokens = TokenizeView(productCode, '-'); + wxASSERT(!tokens.empty()); + const std::string company_code = gameInfo.GetBase().GetMetaInfo()->GetCompanyCode(); + wxASSERT(company_code.size() >= 2); +#ifdef _WIN32 + ShellExecuteA(GetHWND(), "open", fmt::format("https://wiki.cemu.info/wiki/{}{}", *tokens.rbegin(), company_code.substr(company_code.size() - 2)).c_str(), nullptr, nullptr, SW_SHOWNORMAL); +#else + assert_dbg(); +#endif + } + break; + } + + case kContextMenuSaveFolder: + { +#ifdef _WIN32 + ShellExecuteW(GetHWND(), L"open", gameInfo.GetSaveFolder().c_str(), nullptr, nullptr, SW_SHOWNORMAL); +#else + assert_dbg(); +#endif + break; + } + case kContextMenuUpdateFolder: + { +#ifdef _WIN32 + fs::path path(gameInfo.GetUpdate().GetPath()); + _stripPathFilename(path); + ShellExecuteW(GetHWND(), L"open", path.c_str(), nullptr, nullptr, SW_SHOWNORMAL); +#else + assert_dbg(); +#endif + break; + } + case kContextMenuDLCFolder: + { +#ifdef _WIN32 + fs::path path(gameInfo.GetAOC().front().GetPath()); + _stripPathFilename(path); + ShellExecuteW(GetHWND(), L"open", path.c_str(), nullptr, nullptr, SW_SHOWNORMAL); +#else + assert_dbg(); +#endif + break; + } + case kContextMenuEditGraphicPacks: + { + wxTitleIdEvent open_event(wxEVT_OPEN_GRAPHIC_PACK, title_id); + ProcessWindowEvent(open_event); + break; + } + case kContextMenuEditGameProfile: + { + (new GameProfileWindow(GetParent(), title_id))->Show(); + break; + } + } + } + } + + switch (event.GetId()) + { + case kContextMenuRefreshGames: + ReloadGameEntries(false); + break; + case kContextMenuStyleList: + SetStyle(Style::kList); + break; + case kContextMenuStyleIcon: + SetStyle(Style::kIcons); + break; + case kContextMenuStyleIconSmall: + SetStyle(Style::kSmallIcons); + break; + } +} + +void wxGameList::OnColumnClick(wxListEvent& event) +{ + const int column = event.GetColumn(); + SortEntries(column); + UpdateItemColors(); + event.Skip(); +} + +void wxGameList::OnColumnRightClick(wxListEvent& event) +{ + enum ItemIds + { + ResetWidth, + ResetOrder, + + ShowName, + ShowVersion, + ShowDlc, + ShowGameTime, + ShowLastPlayed, + ShowRegion, + }; + const int column = event.GetColumn(); + wxMenu menu; + menu.SetClientObject(new wxCustomData(column)); + + menu.Append(ResetWidth, _("Reset &width")); + menu.Append(ResetOrder, _("Reset &order")) ; + + menu.AppendSeparator(); + menu.AppendCheckItem(ShowName, _("Show &name"))->Check(GetColumnWidth(ColumnName) > 0); + menu.AppendCheckItem(ShowVersion, _("Show &version"))->Check(GetColumnWidth(ColumnVersion) > 0); + menu.AppendCheckItem(ShowDlc, _("Show &dlc"))->Check(GetColumnWidth(ColumnDLC) > 0); + menu.AppendCheckItem(ShowGameTime, _("Show &game time"))->Check(GetColumnWidth(ColumnGameTime) > 0); + menu.AppendCheckItem(ShowLastPlayed, _("Show &last played"))->Check(GetColumnWidth(ColumnGameStarted) > 0); + menu.AppendCheckItem(ColumnRegion, _("Show ®ion"))->Check(GetColumnWidth(ColumnRegion) > 0); + //menu.AppendSeparator(); + //menu.Append(ResetOrder, _("&Reset order")); + + menu.Bind(wxEVT_COMMAND_MENU_SELECTED, + [this](wxCommandEvent& event) { + event.Skip(); + + const auto menu = dynamic_cast(event.GetEventObject()); + const int column = dynamic_cast*>(menu->GetClientObject())->GetData(); + auto& config = GetConfig(); + + switch (event.GetId()) + { + case ShowName: + config.column_width.name = menu->IsChecked(ShowName) ? 500 : 0; + break; + case ShowVersion: + config.column_width.version = menu->IsChecked(ShowVersion) ? 60 : 0; + break; + case ShowDlc: + config.column_width.dlc = menu->IsChecked(ShowDlc) ? 50 : 0; + break; + case ShowGameTime: + config.column_width.game_time = menu->IsChecked(ShowGameTime) ? 140 : 0; + break; + case ShowLastPlayed: + config.column_width.game_started = menu->IsChecked(ShowLastPlayed) ? 160 : 0; + break; + case ColumnRegion: + config.column_width.region = menu->IsChecked(ColumnRegion) ? 80 : 0; + break; + case ResetWidth: + { + switch (column) + { + case ColumnName: + config.column_width.name = 500; + break; + case ColumnVersion: + config.column_width.version = 60; + break; + case ColumnDLC: + config.column_width.dlc = 50; + break; + case ColumnGameTime: + config.column_width.game_time = 140; + break; + case ColumnGameStarted: + config.column_width.game_started = 160; + break; + case ColumnRegion: + config.column_width.region = 80; + break; + default: + return; + } + + break; + } + case ResetOrder: + { + config.game_list_column_order.clear(); + wxArrayInt order(ColumnFavorite); + std::iota(order.begin(), order.end(), 0); + #ifdef wxHAS_LISTCTRL_COLUMN_ORDER + SetColumnsOrder(order); + #endif + Refresh(); + return; + } + } + + g_config.Save(); + ApplyGameListColumnWidths(); + }); + + PopupMenu(&menu); + event.Skip(); +} + +void wxGameList::ApplyGameListColumnWidths() +{ + auto set_width = [this](int id, int width) + { + if (width == -3) + wxAutosizeColumn(this, id); + else + this->SetColumnWidth(id, width); + }; + + const auto& config = GetConfig(); + wxWindowUpdateLocker lock(this); + set_width(ColumnName, config.column_width.name); + set_width(ColumnVersion, config.column_width.version); + set_width(ColumnDLC, config.column_width.dlc); + set_width(ColumnGameTime, config.column_width.game_time); + set_width(ColumnGameStarted, config.column_width.game_started); + set_width(ColumnRegion, config.column_width.region); +} + +void wxGameList::OnColumnBeginResize(wxListEvent& event) +{ + const int column = event.GetColumn(); + const int width = GetColumnWidth(column); + if (width == 0) + event.Veto(); + else + event.Skip(); +} +void wxGameList::OnColumnResize(wxListEvent& event) +{ + event.Skip(); + + const int column = event.GetColumn(); + const int width = GetColumnWidth(column); + + auto& config = GetConfig(); + switch (column) + { + case ColumnName: + config.column_width.name = width; + break; + case ColumnVersion: + config.column_width.version = width; + break; + case ColumnDLC: + config.column_width.dlc = width; + break; + case ColumnGameTime: + config.column_width.game_time = width; + break; + case ColumnGameStarted: + config.column_width.game_started = width; + break; + default: + return; + } + + g_config.Save(); +} + +void wxGameList::OnColumnDrag(wxListEvent& event) +{ + const auto column = event.GetColumn(); + const auto width = GetColumnWidth(column); + if (column == ColumnHiddenName || width == 0) + event.Veto(); +} + +void wxGameList::OnClose(wxCloseEvent& event) +{ + event.Skip(); + m_exit = true; +} + +int wxGameList::FindInsertPosition(TitleId titleId) +{ + SortData data{ this, s_last_column, s_direction }; + const auto itemCount = GetItemCount(); + if (itemCount == 0) + return 0; + // todo - optimize this with binary search + int linearScanIndex = 0; + if (SortComparator(titleId, (uint64)GetItemData(0), &data)) + { + linearScanIndex = itemCount; + for (int i = 1; i < itemCount - 1; i++) + { + if (!SortComparator(titleId, (uint64)GetItemData(i), &data)) + { + linearScanIndex = i; + break; + } + } + } + return linearScanIndex; +} + +void wxGameList::OnGameEntryUpdatedByTitleId(wxTitleIdEvent& event) +{ + const auto titleId = event.GetTitleId(); + // get GameInfo from title list + GameInfo2 gameInfo = CafeTitleList::GetGameInfo(titleId); + + if (!gameInfo.IsValid()) + { + // entry no longer exists + // we dont need to do anything here because all delete operations should trigger a full list refresh + return; + } + TitleId baseTitleId = gameInfo.GetBaseTitleId(); + bool isNewEntry = false; + + auto index = FindListItemByTitleId(baseTitleId); + if(index == wxNOT_FOUND) + { + // entry doesn't exist + index = InsertItem(FindInsertPosition(baseTitleId), wxHelper::FromUtf8(gameInfo.GetTitleName())); + SetItemPtrData(index, baseTitleId); + isNewEntry = true; + } + + int icon = 0; + int icon_small = 0; + bool hasIcon = QueryIconForTitle(baseTitleId, icon, icon_small); + + if (m_style == Style::kList) + { + if(hasIcon) + SetItemColumnImage(index, ColumnIcon, icon_small); + + SetItem(index, ColumnName, wxHelper::FromUtf8(GetNameByTitleId(baseTitleId))); + + SetItem(index, ColumnVersion, fmt::format("{}", gameInfo.GetVersion())); + + if(gameInfo.HasAOC()) + SetItem(index, ColumnDLC, fmt::format("{}", gameInfo.GetAOCVersion())); + else + SetItem(index, ColumnDLC, wxString()); + + if (isNewEntry) + { + iosu::pdm::GameListStat playTimeStat; + if (iosu::pdm::GetStatForGamelist(baseTitleId, playTimeStat)) + { + // time played + uint32 timePlayed = playTimeStat.numMinutesPlayed * 60; + if (timePlayed == 0) + SetItem(index, ColumnGameTime, wxEmptyString); + else if (timePlayed < 60) + SetItem(index, ColumnGameTime, fmt::format("{} seconds", timePlayed)); + else if (timePlayed < 60 * 60) + SetItem(index, ColumnGameTime, fmt::format("{} minutes", timePlayed / 60)); + else + SetItem(index, ColumnGameTime, fmt::format("{} hours {} minutes", timePlayed / 3600, (timePlayed / 60) % 60)); + // last played + if (playTimeStat.last_played.year != 0) + { + const wxDateTime tmp((wxDateTime::wxDateTime_t)playTimeStat.last_played.day, (wxDateTime::Month)playTimeStat.last_played.month, (wxDateTime::wxDateTime_t)playTimeStat.last_played.year, 0, 0, 0, 0); + SetItem(index, ColumnGameStarted, tmp.FormatISODate()); + } + else + SetItem(index, ColumnGameStarted, _("never")); + } + else + { + SetItem(index, ColumnGameTime, wxEmptyString); + SetItem(index, ColumnGameStarted, _("never")); + } + } + + + const auto region_text = fmt::format("{}", gameInfo.GetRegion()); + SetItem(index, ColumnRegion, _(region_text)); + } + else if (m_style == Style::kIcons) + { + if(hasIcon) + SetItemImage(index, icon); + } + else if (m_style == Style::kSmallIcons) + { + if (hasIcon) + SetItemImage(index, icon_small); + } + if (isNewEntry) + UpdateItemColors(index); +} + +void wxGameList::OnItemActivated(wxListEvent& event) +{ + event.Skip(); + + const auto selection = event.GetIndex(); + if (selection == wxNOT_FOUND) + return; + + const auto item_data = (uint64)GetItemData(selection); + if(item_data == kDefaultEntryData) + { + const wxCommandEvent open_settings_event(wxEVT_OPEN_SETTINGS); + wxPostEvent(this, open_settings_event); + return; + } + + TitleInfo titleInfo; + if (!CafeTitleList::GetFirstByTitleId(item_data, titleInfo)) + return; + if (!titleInfo.IsValid()) + return; + + MainWindow::RequestLaunchGame(titleInfo.GetPath(), wxLaunchGameEvent::INITIATED_BY::GAME_LIST); +} + +void wxGameList::OnTimer(wxTimerEvent& event) +{ + const auto& obj = event.GetTimer().GetId(); + if(obj == m_tooltip_timer->GetId()) + { + m_tooltip_window->Hide(); + + auto flag = wxLIST_HITTEST_ONITEM; + const auto item = this->HitTest(m_mouse_position, flag); + if(item != wxNOT_FOUND ) + { + //const auto title_id = (uint64_t)GetItemData(item); + //auto entry = GetGameEntry(title_id); + //if (entry && entry->is_update) + //{ + // m_tooltip_window->SetPosition(wxPoint(m_mouse_position.x + 15, m_mouse_position.y + 15)); + // m_tooltip_window->SendSizeEvent(); + // m_tooltip_window->Show(); + //} + } + } + +} + +void wxGameList::OnMouseMove(wxMouseEvent& event) +{ + m_tooltip_timer->Stop(); + m_tooltip_timer->StartOnce(250); + m_mouse_position = event.GetPosition(); +} + +void wxGameList::OnLeaveWindow(wxMouseEvent& event) +{ + m_tooltip_timer->Stop(); + m_tooltip_window->Hide(); +} + +void wxGameList::HandleTitleListCallback(CafeTitleListCallbackEvent* evt) +{ + if (evt->eventType == CafeTitleListCallbackEvent::TYPE::TITLE_DISCOVERED || + evt->eventType == CafeTitleListCallbackEvent::TYPE::TITLE_REMOVED) + { + wxQueueEvent(this, new wxTitleIdEvent(wxEVT_GAME_ENTRY_ADDED_OR_REMOVED, evt->titleInfo->GetAppTitleId())); + } +} + +void wxGameList::AsyncWorkerThread() +{ + while (m_async_worker_active) + { + m_async_task_count.decrementWithWait(); + // get next titleId to load (if any) + m_async_worker_mutex.lock(); + bool hasJob = !m_icon_load_queue.empty(); + TitleId titleId = 0; + if (hasJob) + { + titleId = m_icon_load_queue.front(); + m_icon_load_queue.erase(m_icon_load_queue.begin()); + } + m_async_worker_mutex.unlock(); + if (!hasJob) + continue; + if(m_icon_loaded.find(titleId) != m_icon_loaded.end()) + continue; + m_icon_loaded.emplace(titleId); + // load and process icon + TitleInfo titleInfo; + if( !CafeTitleList::GetFirstByTitleId(titleId, titleInfo) ) + continue; + std::string tempMountPath = TitleInfo::GetUniqueTempMountingPath(); + if(!titleInfo.Mount(tempMountPath, "", FSC_PRIORITY_BASE)) + continue; + auto tgaData = fsc_extractFile((tempMountPath + "/meta/iconTex.tga").c_str()); + bool iconSuccessfullyLoaded = false; + if (tgaData && tgaData->size() > 16) + { + wxMemoryInputStream tmp_stream(tgaData->data(), tgaData->size()); + const wxImage image(tmp_stream); + // todo - is wxImageList thread safe? + int icon = m_image_list->Add(image.Scale(kIconWidth, kIconWidth, wxIMAGE_QUALITY_BICUBIC)); + int icon_small = m_image_list_small->Add(image.Scale(kListIconWidth, kListIconWidth, wxIMAGE_QUALITY_BICUBIC)); + // store in cache + m_icon_cache_mtx.lock(); + m_icon_cache.try_emplace(titleId, icon, icon_small); + m_icon_cache_mtx.unlock(); + iconSuccessfullyLoaded = true; + } + else + { + cemuLog_log(LogType::Force, "Failed to load icon for title {:016x}", titleId); + } + titleInfo.Unmount(tempMountPath); + // notify UI about loaded icon + if(iconSuccessfullyLoaded) + wxQueueEvent(this, new wxTitleIdEvent(wxEVT_GAME_ENTRY_ADDED_OR_REMOVED, titleId)); + } +} + +void wxGameList::RequestLoadIconAsync(TitleId titleId) +{ + m_async_worker_mutex.lock(); + m_icon_load_queue.push_back(titleId); + m_async_worker_mutex.unlock(); + m_async_task_count.increment(); +} + +// returns icons if cached, otherwise an async request to load them is made +bool wxGameList::QueryIconForTitle(TitleId titleId, int& icon, int& iconSmall) +{ + m_icon_cache_mtx.lock(); + auto it = m_icon_cache.find(titleId); + if (it == m_icon_cache.end()) + { + m_icon_cache_mtx.unlock(); + RequestLoadIconAsync(titleId); + return false; + } + icon = it->second.first; + iconSmall = it->second.second; + m_icon_cache_mtx.unlock(); + return true; +} diff --git a/src/gui/components/wxGameList.h b/src/gui/components/wxGameList.h new file mode 100644 index 00000000..1f422585 --- /dev/null +++ b/src/gui/components/wxGameList.h @@ -0,0 +1,148 @@ +#pragma once + +#include "config/CemuConfig.h" +#include "Cafe/TitleList/TitleId.h" + +#include +#include +#include + +#include +#include +#include +#include "util/helpers/Semaphore.h" + +class wxTitleIdEvent : public wxCommandEvent +{ +public: + wxTitleIdEvent(int type, uint64 title_id) + : wxCommandEvent(type), m_title_id(title_id) {} + + [[nodiscard]] uint64 GetTitleId() const { return m_title_id; } + +private: + uint64 m_title_id; +}; + +wxDECLARE_EVENT(wxEVT_OPEN_SETTINGS, wxCommandEvent); +wxDECLARE_EVENT(wxEVT_OPEN_GRAPHIC_PACK, wxTitleIdEvent); +wxDECLARE_EVENT(wxEVT_GAMELIST_BEGIN_UPDATE, wxCommandEvent); +wxDECLARE_EVENT(wxEVT_GAMELIST_END_UPDATE, wxCommandEvent); + +class wxGameList : public wxListCtrl +{ + friend class MainWindow; +public: + enum class Style + { + kList, + kIcons, + kSmallIcons + }; + + wxGameList(wxWindow* parent, wxWindowID id = wxID_ANY); + ~wxGameList(); + + void SetStyle(Style style, bool save = true); + + void LoadConfig(); + void SaveConfig(bool flush = false); + bool IsVisible(long item) const; // only available in wxwidgets 3.1.3 + + void ReloadGameEntries(bool cached = false); + + long FindListItemByTitleId(uint64 title_id) const; + void OnClose(wxCloseEvent& event); + +private: + std::atomic_bool m_exit = false; + Style m_style; + long GetStyleFlags(Style style) const; + + inline static const wxColour kUpdateColor{ 0x3939c3 }; + inline static const wxColour kFavoriteColor{ 0xD3F6FD }; + inline static const wxColour kSecondColor{ 0xFDF9F2 }; + void UpdateItemColors(sint32 startIndex = 0); + + enum ItemColumns + { + ColumnHiddenName = 0, + ColumnIcon, + ColumnName, + ColumnVersion, + ColumnDLC, + ColumnGameTime, + ColumnGameStarted, + ColumnRegion, + ColumnFavorite + }; + + int s_last_column = ColumnName; + int s_direction = 1; + void SortEntries(int column = -1); + struct SortData + { + wxGameList* thisptr; + int column; + int dir; + }; + + int FindInsertPosition(TitleId titleId); + int SortComparator(uint64 titleId1, uint64 titleId2, SortData* sortData); + static int SortFunction(wxIntPtr item1, wxIntPtr item2, wxIntPtr sortData); + + wxTimer* m_tooltip_timer; + wxToolTip* m_tool_tip; + wxPanel* m_tooltip_window; + wxPoint m_mouse_position; + + std::mutex m_async_worker_mutex; + std::thread m_async_worker_thread; + CounterSemaphore m_async_task_count; + std::atomic_bool m_async_worker_active{false}; + + std::vector m_icon_load_queue; + + uint64 m_callbackIdTitleList; + + std::string GetNameByTitleId(uint64 titleId); + + void HandleTitleListCallback(struct CafeTitleListCallbackEvent* evt); + + void AsyncWorkerThread(); + void RequestLoadIconAsync(TitleId titleId); + bool QueryIconForTitle(TitleId titleId, int& icon, int& iconSmall); + + inline static constexpr int kListIconWidth = 64; + inline static constexpr int kIconWidth = 128; + wxImageList* m_image_list, *m_image_list_small; + + std::mutex m_icon_cache_mtx; + std::set m_icon_loaded; + std::map> m_icon_cache; // pair contains icon and small icon + + std::map m_name_cache; + + // list mode + void CreateListColumns(); + + void OnColumnClick(wxListEvent& event); + void OnColumnRightClick(wxListEvent& event); + void ApplyGameListColumnWidths(); + void OnColumnBeginResize(wxListEvent& event); + void OnColumnResize(wxListEvent& event); + void OnColumnDrag(wxListEvent& event); + + // generic events + void OnKeyDown(wxListEvent& event); + void OnContextMenu(wxContextMenuEvent& event); + void OnContextMenuSelected(wxCommandEvent& event); + void OnGameEntryUpdatedByTitleId(wxTitleIdEvent& event); + void OnItemActivated(wxListEvent& event); + void OnTimer(wxTimerEvent& event); + void OnMouseMove(wxMouseEvent& event); + void OnLeaveWindow(wxMouseEvent& event); + + static inline std::once_flag s_launch_file_once; +}; + diff --git a/src/gui/components/wxInputDraw.cpp b/src/gui/components/wxInputDraw.cpp new file mode 100644 index 00000000..e3884b71 --- /dev/null +++ b/src/gui/components/wxInputDraw.cpp @@ -0,0 +1,118 @@ +#include "gui/components/wxInputDraw.h" + +#include + +wxInputDraw::wxInputDraw(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size) + : wxWindow(parent, id, pos, size, 0, wxPanelNameStr) +{ + SetBackgroundStyle(wxBG_STYLE_PAINT); + Bind(wxEVT_PAINT, &wxInputDraw::OnPaintEvent, this); +} + +wxInputDraw::~wxInputDraw() { Unbind(wxEVT_PAINT, &wxInputDraw::OnPaintEvent, this); } + +void wxInputDraw::OnPaintEvent(wxPaintEvent& event) +{ + wxAutoBufferedPaintDC dc(this); + OnRender(dc); +} + +void wxInputDraw::OnRender(wxDC& dc) +{ + dc.Clear(); + + glm::vec2 position; + const wxPen *black, *red, *grey; + const wxBrush *black_brush, *red_brush, *grey_brush; + if(IsEnabled()) + { + position = m_position; + + black = wxBLACK_PEN; + red = wxRED_PEN; + grey = wxGREY_PEN; + + black_brush = wxBLACK_BRUSH; + red_brush = wxRED_BRUSH; + grey_brush = wxGREY_BRUSH; + } + else + { + position = {}; + black = red = wxMEDIUM_GREY_PEN; + grey = wxLIGHT_GREY_PEN; + + black_brush = red_brush = wxMEDIUM_GREY_BRUSH; + grey_brush = wxLIGHT_GREY_BRUSH; + } + + dc.SetBackgroundMode(wxSOLID); + dc.SetBackground(*wxWHITE_BRUSH); + + const auto size = GetSize(); + const auto min_size = (float)std::min(size.GetWidth(), size.GetHeight()) - 1.0f; + const Vector2f middle{min_size / 2.0f, min_size / 2.0f}; + + // border + const wxRect border{0, 0, (int)min_size, (int)min_size}; + dc.SetPen(*black); + dc.DrawRectangle(border); + + dc.SetPen(IsEnabled() ? wxPen(wxColour(0x336600)) : *grey); // dark green + dc.DrawCircle((int)middle.x, (int)middle.y, (int)middle.x); + + if (m_deadzone > 0) + { + dc.SetPen(*grey); + dc.SetBrush(*wxLIGHT_GREY_BRUSH); + const auto deadzone_size = m_deadzone * min_size / 2.0f; + dc.DrawCircle( + static_cast(middle.x), + static_cast(middle.x), + (int)deadzone_size); + + if (length(position) >= m_deadzone) + { + dc.SetPen(*red); + dc.SetBrush(*red_brush); + + if (std::abs(1.0f - length(position)) < 0.05f) + { + dc.SetPen(wxPen(wxColour(0x336600))); + dc.SetBrush(wxColour(0x336600)); + } + } + else + { + dc.SetPen(*black); + dc.SetBrush(*black_brush); + } + } + else + { + dc.SetPen(*red); + dc.SetBrush(*red_brush); + } + + // draw axis + const wxPoint pos + { + static_cast(middle.x + position.x * middle.x), + static_cast(middle.y - position.y * middle.y) + }; + dc.DrawCircle(pos.x, pos.y, 2); + + dc.SetBrush(*wxTRANSPARENT_BRUSH); +} + +void wxInputDraw::SetAxisValue(const glm::vec2& position) +{ + m_position = position; + Refresh(); +} + +void wxInputDraw::SetDeadzone(float deadzone) +{ + m_deadzone = deadzone; + Refresh(); +} diff --git a/src/gui/components/wxInputDraw.h b/src/gui/components/wxInputDraw.h new file mode 100644 index 00000000..477dd075 --- /dev/null +++ b/src/gui/components/wxInputDraw.h @@ -0,0 +1,24 @@ +#pragma once + +#include "input/api/ControllerState.h" +#include "util/math/vector2.h" + +#include + + +class wxInputDraw : public wxWindow +{ +public: + wxInputDraw(wxWindow *parent, wxWindowID id, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize); + ~wxInputDraw(); + + virtual void OnRender(wxDC& dc); + void SetAxisValue(const glm::vec2& position); + void SetDeadzone(float deadzone); + +private: + void OnPaintEvent(wxPaintEvent& event); + + glm::vec2 m_position{}; + float m_deadzone{0}; +}; diff --git a/src/gui/components/wxLogCtrl.cpp b/src/gui/components/wxLogCtrl.cpp new file mode 100644 index 00000000..50356d29 --- /dev/null +++ b/src/gui/components/wxLogCtrl.cpp @@ -0,0 +1,160 @@ +#include "gui/components/wxLogCtrl.h" + +#include + +wxDEFINE_EVENT(EVT_ON_LIST_UPDATED, wxEvent); + +wxLogCtrl::wxLogCtrl(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style) + : TextList(parent, id, pos, size, style) +{ + m_timer = new wxTimer(this); + this->Bind(wxEVT_TIMER, &wxLogCtrl::OnTimer, this); + this->Bind(EVT_ON_LIST_UPDATED, &wxLogCtrl::OnActiveListUpdated, this); + m_timer->Start(250); +} + +wxLogCtrl::~wxLogCtrl() +{ + this->Unbind(wxEVT_TIMER, &wxLogCtrl::OnTimer, this); + this->Unbind(EVT_ON_LIST_UPDATED, &wxLogCtrl::OnActiveListUpdated, this); + + if(m_timer) + m_timer->Stop(); + + if(m_update_worker.joinable()) + m_update_worker.join(); +} + +void wxLogCtrl::SetActiveFilter(const std::string& active_filter) +{ + if(m_active_filter == active_filter) + return; + + m_active_filter = active_filter; + + if(m_update_worker.joinable()) + m_update_worker.join(); + + m_update_worker = std::thread(&wxLogCtrl::UpdateActiveEntries, this); +} + +const std::string& wxLogCtrl::GetActiveFilter() const +{ + return m_active_filter; +} + +void wxLogCtrl::SetFilterMessage(bool state) +{ + if(m_filter_messages == state) + return; + + m_filter_messages = state; + + if(m_update_worker.joinable()) + m_update_worker.join(); + + m_update_worker = std::thread(&wxLogCtrl::UpdateActiveEntries, this); +} + +bool wxLogCtrl::GetFilterMessage() const +{ + return m_filter_messages; +} + +void wxLogCtrl::PushEntry(const wxString& filter, const wxString& message) +{ + std::unique_lock lock(m_mutex); + m_log_entries.emplace_back(filter, message); + ListIt_t it = m_log_entries.back(); + lock.unlock(); + + if(m_active_filter.empty() || filter == m_active_filter || (m_filter_messages && boost::icontains(message, m_active_filter))) + { + std::unique_lock active_lock(m_active_mutex); + m_active_entries.emplace_back(std::cref(it)); + const auto entry_count = m_active_entries.size(); + active_lock.unlock(); + + if(entry_count <= m_elements_visible) + { + RefreshLine(entry_count - 1); + } + } +} + +void wxLogCtrl::OnDraw(wxDC& dc, sint32 start, sint32 count, const wxPoint& start_position) +{ + wxPoint position = start_position; + + std::scoped_lock lock(m_active_mutex); + auto it = std::next(m_active_entries.cbegin(), start); + if(it == m_active_entries.cend()) + return; + + for (sint32 i = 0; i <= count && it != m_active_entries.cend(); ++i, ++it) + { + wxColour background_colour; + if((start + i) % 2 == 0) + background_colour = COLOR_WHITE; + else + background_colour = 0xFFFDF9F2; + + DrawLineBackground(dc, position, background_colour); + + dc.SetTextForeground(COLOR_BLACK); + dc.DrawText(it->get().second, position); + + NextLine(position, &start_position); + } +} + +void wxLogCtrl::OnTimer(wxTimerEvent& event) +{ + std::unique_lock lock(m_active_mutex); + const auto count = m_active_entries.size(); + if(count == m_element_count) + return; + + lock.unlock(); + SetElementCount(count); + + if(m_scrolled_to_end) + { + Scroll(0, count); + RefreshControl(); + } +} + +void wxLogCtrl::OnActiveListUpdated(wxEvent& event) +{ + std::unique_lock lock(m_active_mutex); + const auto count = m_active_entries.size(); + lock.unlock(); + + SetElementCount(count); + RefreshControl(); +} + +void wxLogCtrl::UpdateActiveEntries() +{ + { + std::scoped_lock lock(m_mutex, m_active_mutex); + m_active_entries.clear(); + if(m_active_filter.empty()) + { + for (const auto& it : m_log_entries) + m_active_entries.emplace_back(it); + } + else + { + for (const auto& it : m_log_entries) + { + if(it.first == m_active_filter || (m_filter_messages && boost::icontains(it.second, m_active_filter)) ) + m_active_entries.emplace_back(it); + } + } + } + + wxQueueEvent(this, new wxCommandEvent(EVT_ON_LIST_UPDATED)); +} + diff --git a/src/gui/components/wxLogCtrl.h b/src/gui/components/wxLogCtrl.h new file mode 100644 index 00000000..e6932b49 --- /dev/null +++ b/src/gui/components/wxLogCtrl.h @@ -0,0 +1,36 @@ +#pragma once +#include "TextList.h" + +class wxLogCtrl : public TextList +{ +public: + wxLogCtrl(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style); + ~wxLogCtrl(); + + void SetActiveFilter(const std::string& active_filter); + [[nodiscard]] const std::string& GetActiveFilter() const; + void SetFilterMessage(bool state); + [[nodiscard]] bool GetFilterMessage() const; + + void PushEntry(const wxString& filter, const wxString& message); + +protected: + void OnDraw(wxDC& dc, sint32 start, sint32 count, const wxPoint& start_position); + +private: + void OnTimer(wxTimerEvent& event); + void OnActiveListUpdated(wxEvent& event); + + wxTimer* m_timer; + + std::string m_active_filter; + std::thread m_update_worker; + bool m_filter_messages = false; + + std::mutex m_mutex, m_active_mutex; + using ListEntry_t = std::pair; + using ListIt_t = std::list::const_reference; + std::list m_log_entries; + std::list> m_active_entries; + void UpdateActiveEntries(); +}; \ No newline at end of file diff --git a/src/gui/components/wxTitleManagerList.cpp b/src/gui/components/wxTitleManagerList.cpp new file mode 100644 index 00000000..d826c118 --- /dev/null +++ b/src/gui/components/wxTitleManagerList.cpp @@ -0,0 +1,1277 @@ +#include "gui/components/wxTitleManagerList.h" +#include "gui/helpers/wxHelpers.h" +#include "util/helpers/SystemException.h" +#include "Cafe/TitleList/GameInfo.h" +#include "Cafe/TitleList/TitleInfo.h" +#include "Cafe/TitleList/TitleList.h" +#include "gui/components/wxGameList.h" +#include "gui/helpers/wxCustomEvents.h" +#include "gui/helpers/wxHelpers.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../wxHelper.h" + +#include +#ifdef _WIN32 +#include +#endif + +#include "config/ActiveSettings.h" +#include "gui/ChecksumTool.h" +#include "gui/MainWindow.h" +#include "Cafe/TitleList/TitleId.h" +#include "Cafe/TitleList/SaveList.h" +#include "Cafe/TitleList/TitleList.h" + +#include +#include + +#include "Common/filestream.h" + +wxDEFINE_EVENT(wxEVT_TITLE_FOUND, wxCommandEvent); +wxDEFINE_EVENT(wxEVT_TITLE_REMOVED, wxCommandEvent); +wxDEFINE_EVENT(wxEVT_REMOVE_ENTRY, wxCommandEvent); + +wxTitleManagerList::wxTitleManagerList(wxWindow* parent, wxWindowID id) + : wxListCtrl(parent, id, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_VIRTUAL) +{ + AddColumns(); + + // tooltip TODO: extract class mb wxPanelTooltip + m_tooltip_window = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNO_BORDER); + auto* tooltip_sizer = new wxBoxSizer(wxVERTICAL); + m_tooltip_text = new wxStaticText(m_tooltip_window, wxID_ANY, wxEmptyString); + tooltip_sizer->Add(m_tooltip_text , 0, wxALL, 5); + m_tooltip_window->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_INFOBK)); + m_tooltip_window->SetSizerAndFit(tooltip_sizer); + m_tooltip_window->Hide(); + m_tooltip_timer = new wxTimer(this); + + Bind(wxEVT_LIST_COL_CLICK, &wxTitleManagerList::OnColumnClick, this); + Bind(wxEVT_CONTEXT_MENU, &wxTitleManagerList::OnContextMenu, this); + Bind(wxEVT_LIST_ITEM_SELECTED, &wxTitleManagerList::OnItemSelected, this); + Bind(wxEVT_TIMER, &wxTitleManagerList::OnTimer, this); + Bind(wxEVT_REMOVE_ITEM, &wxTitleManagerList::OnRemoveItem, this); + Bind(wxEVT_REMOVE_ENTRY, &wxTitleManagerList::OnRemoveEntry, this); + Bind(wxEVT_TITLE_FOUND, &wxTitleManagerList::OnTitleDiscovered, this); + Bind(wxEVT_TITLE_REMOVED, &wxTitleManagerList::OnTitleRemoved, this); + Bind(wxEVT_CLOSE_WINDOW, &wxTitleManagerList::OnClose, this); + + m_callbackIdTitleList = CafeTitleList::RegisterCallback([](CafeTitleListCallbackEvent* evt, void* ctx) { ((wxTitleManagerList*)ctx)->HandleTitleListCallback(evt); }, this); + m_callbackIdSaveList = CafeSaveList::RegisterCallback([](CafeSaveListCallbackEvent* evt, void* ctx) { ((wxTitleManagerList*)ctx)->HandleSaveListCallback(evt); }, this); +} + +wxTitleManagerList::~wxTitleManagerList() +{ + CafeSaveList::UnregisterCallback(m_callbackIdSaveList); + CafeTitleList::UnregisterCallback(m_callbackIdTitleList); +} + +boost::optional wxTitleManagerList::GetSelectedTitleEntry() const +{ + const auto selection = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + if (selection != wxNOT_FOUND) + { + const auto tmp = GetTitleEntry(selection); + if (tmp.has_value()) + return tmp.value(); + } + + return {}; +} + +boost::optional wxTitleManagerList::GetSelectedTitleEntry() +{ + const auto selection = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + if (selection != wxNOT_FOUND) + { + const auto tmp = GetTitleEntry(selection); + if (tmp.has_value()) + return tmp.value(); + } + + return {}; +} + +//boost::optional wxTitleManagerList::GetTitleEntry(EntryType type, uint64 titleid) +//{ +// for(const auto& v : m_data) +// { +// if (v->entry.title_id == titleid && v->entry.type == type) +// return v->entry; +// } +// +// return {}; +//} + +boost::optional wxTitleManagerList::GetTitleEntryByUID(uint64 uid) +{ + for (const auto& v : m_data) + { + if (v->entry.location_uid == uid) + return v->entry; + } + return {}; +} + +void wxTitleManagerList::AddColumns() +{ + wxListItem col0; + col0.SetId(ColumnTitleId); + col0.SetText(_("Title id")); + col0.SetWidth(120); + InsertColumn(ColumnTitleId, col0); + + wxListItem col1; + col1.SetId(ColumnName); + col1.SetText(_("Name")); + col1.SetWidth(435); + InsertColumn(ColumnName, col1); + + wxListItem col2; + col2.SetId(ColumnType); + col2.SetText(_("Type")); + col2.SetWidth(65); + InsertColumn(ColumnType, col2); + + wxListItem col3; + col3.SetId(ColumnVersion); + col3.SetText(_("Version")); + col3.SetWidth(40); + InsertColumn(ColumnVersion, col3); + + wxListItem col4; + col4.SetId(ColumnRegion); + col4.SetText(_("Region")); + col4.SetWidth(60); + InsertColumn(ColumnRegion, col4); + + wxListItem col5; + col5.SetId(ColumnFormat); + col5.SetText(_("Format")); + col5.SetWidth(63); + InsertColumn(ColumnFormat, col5); +} + +wxString wxTitleManagerList::OnGetItemText(long item, long column) const +{ + if (item >= GetItemCount()) + return wxEmptyString; + + const auto entry = GetTitleEntry(item); + if (entry.has_value()) + return GetTitleEntryText(entry.value(), (ItemColumn)column); + + return wxEmptyString; +} + +wxItemAttr* wxTitleManagerList::OnGetItemAttr(long item) const +{ + const auto entry = GetTitleEntry(item); + const wxColour kSecondColor{ 0xFDF9F2 }; + static wxListItemAttr s_coloured_attr(GetTextColour(), kSecondColor, GetFont()); + return item % 2 == 0 ? nullptr : &s_coloured_attr; +} + +boost::optional wxTitleManagerList::GetTitleEntry(long item) +{ + long counter = 0; + for (const auto& data : m_sorted_data) + { + if (!data.get().visible) + continue; + + if (item != counter++) + continue; + + return data.get().entry; + } + + return {}; +} + +boost::optional wxTitleManagerList::GetTitleEntry(const fs::path& path) const +{ + const auto tmp = path.generic_u8string(); + for (const auto& data : m_data) + { + if (boost::iequals(data->entry.path.generic_u8string(), tmp)) + return data->entry; + } + + return {}; +} +boost::optional wxTitleManagerList::GetTitleEntry(const fs::path& path) +{ + const auto tmp = path.generic_u8string(); + for (const auto& data : m_data) + { + if (boost::iequals(data->entry.path.generic_u8string(), tmp)) + return data->entry; + } + + return {}; +} + +boost::optional wxTitleManagerList::GetTitleEntry(long item) const +{ + long counter = 0; + for (const auto& data : m_sorted_data) + { + if (!data.get().visible) + continue; + + if (item != counter++) + continue; + + return data.get().entry; + } + + return {}; +} + +void wxTitleManagerList::OnConvertToCompressedFormat(uint64 titleId) +{ + TitleInfo titleInfo_base; + TitleInfo titleInfo_update; + TitleInfo titleInfo_aoc; + + titleId = TitleIdParser::MakeBaseTitleId(titleId); // if the titleId of a separate update is selected, this converts it back to the base titleId + TitleIdParser titleIdParser(titleId); + bool hasBaseTitleId = titleIdParser.GetType() != TitleIdParser::TITLE_TYPE::AOC; + bool hasUpdateTitleId = titleIdParser.CanHaveSeparateUpdateTitleId(); + TitleId updateTitleId = hasUpdateTitleId ? titleIdParser.GetSeparateUpdateTitleId() : 0; + + // todo - AOC titleIds might differ from the base/update game in other bits than the type. We have to use the meta data from the base/update to match aoc to the base title id + // for now we just assume they match + TitleId aocTitleId; + if (hasBaseTitleId) + aocTitleId = (titleId & (uint64)~0xFF00000000) | (uint64)0xC00000000; + else + aocTitleId = titleId; + + // find base and update + for (const auto& data : m_data) + { + if (hasBaseTitleId && data->entry.title_id == titleId) + { + if (!titleInfo_base.IsValid()) + { + titleInfo_base = TitleInfo(data->entry.path); + } + else + { + // duplicate entry + } + } + if (hasUpdateTitleId && data->entry.title_id == updateTitleId) + { + if (!titleInfo_update.IsValid()) + { + titleInfo_update = TitleInfo(data->entry.path); + } + else + { + // duplicate entry + } + } + } + // find AOC + for (const auto& data : m_data) + { + if (data->entry.title_id == aocTitleId) + { + titleInfo_aoc = TitleInfo(data->entry.path); + } + } + + std::string msg = wxHelper::MakeUTF8(_("The following content will be converted to a compressed Wii U archive file (.wua):\n \n")); + + if (titleInfo_base.IsValid()) + msg.append(fmt::format(wxHelper::MakeUTF8(_("Base game: {}")), _utf8Wrapper(titleInfo_base.GetPath()))); + else + msg.append(fmt::format(wxHelper::MakeUTF8(_("Base game: Not installed")))); + + msg.append("\n"); + + if (titleInfo_update.IsValid()) + msg.append(fmt::format(wxHelper::MakeUTF8(_("Update: {}")), _utf8Wrapper(titleInfo_update.GetPath()))); + else + msg.append(fmt::format(wxHelper::MakeUTF8(_("Update: Not installed")))); + + msg.append("\n"); + + if (titleInfo_aoc.IsValid()) + msg.append(fmt::format(wxHelper::MakeUTF8(_("DLC: {}")), _utf8Wrapper(titleInfo_aoc.GetPath()))); + else + msg.append(fmt::format(wxHelper::MakeUTF8(_("DLC: Not installed")))); + + const int answer = wxMessageBox(wxString::FromUTF8(msg), _("Confirmation"), wxOK | wxCANCEL | wxCENTRE | wxICON_QUESTION, this); + if (answer != wxOK) + return; + std::vector titlesToConvert; + if (titleInfo_base.IsValid()) + titlesToConvert.emplace_back(&titleInfo_base); + if (titleInfo_update.IsValid()) + titlesToConvert.emplace_back(&titleInfo_update); + if (titleInfo_aoc.IsValid()) + titlesToConvert.emplace_back(&titleInfo_aoc); + if (titlesToConvert.empty()) + return; + // get short name + CafeConsoleLanguage languageId = CafeConsoleLanguage::EN; // todo - use user's locale + std::string shortName; + if (titleInfo_base.IsValid()) + shortName = titleInfo_base.GetMetaInfo()->GetShortName(languageId); + else if (titleInfo_update.IsValid()) + shortName = titleInfo_update.GetMetaInfo()->GetShortName(languageId); + else if (titleInfo_aoc.IsValid()) + shortName = titleInfo_aoc.GetMetaInfo()->GetShortName(languageId); + + if (!shortName.empty()) + { + boost::replace_all(shortName, ":", ""); + } + // for the default output directory we use the first game path configured by the user + std::wstring defaultDir = L""; + if (!GetConfig().game_paths.empty()) + defaultDir = GetConfig().game_paths.front(); + // get the short name, which we will use as a suggested default file name + std::string defaultFileName = shortName; + boost::replace_all(defaultFileName, "/", ""); + boost::replace_all(defaultFileName, "\\", ""); + + CafeConsoleRegion region = CafeConsoleRegion::Auto; + if (titleInfo_base.IsValid() && titleInfo_base.HasValidXmlInfo()) + region = titleInfo_base.GetMetaInfo()->GetRegion(); + else if (titleInfo_update.IsValid() && titleInfo_update.HasValidXmlInfo()) + region = titleInfo_update.GetMetaInfo()->GetRegion(); + + if (region == CafeConsoleRegion::JPN) + defaultFileName.append(" (JP)"); + else if (region == CafeConsoleRegion::EUR) + defaultFileName.append(" (EU)"); + else if (region == CafeConsoleRegion::USA) + defaultFileName.append(" (US)"); + if (titleInfo_update.IsValid()) + { + defaultFileName.append(fmt::format(" (v{})", titleInfo_update.GetAppTitleVersion())); + } + defaultFileName.append(".wua"); + + // ask the user to provide a path for the output file + wxFileDialog saveFileDialog(this, _("Save Wii U game archive file"), defaultDir, wxHelper::FromUtf8(defaultFileName), + "WUA files (*.wua)|*.wua", wxFD_SAVE | wxFD_OVERWRITE_PROMPT); + if (saveFileDialog.ShowModal() == wxID_CANCEL) + return; + fs::path outputPath(wxHelper::MakeFSPath(saveFileDialog.GetPath())); + fs::path outputPathTmp(wxHelper::MakeFSPath(saveFileDialog.GetPath().append("__tmp"))); + struct ZArchiveWriterContext + { + static void NewOutputFile(const int32_t partIndex, void* _ctx) + { + ZArchiveWriterContext* ctx = (ZArchiveWriterContext*)_ctx; + ctx->fs = FileStream::createFile2(ctx->outputPath); + if (!ctx->fs) + ctx->isValid = false; + } + + static void WriteOutputData(const void* data, size_t length, void* _ctx) + { + ZArchiveWriterContext* ctx = (ZArchiveWriterContext*)_ctx; + if (ctx->fs) + ctx->fs->writeData(data, length); + } + + bool RecursivelyCountFiles(const std::string& fscPath) + { + sint32 fscStatus; + std::unique_ptr vfDir(fsc_openDirIterator(fscPath.c_str(), &fscStatus)); + if (!vfDir) + return false; + if (cancelled) + return false; + FSCDirEntry dirEntry; + while (fsc_nextDir(vfDir.get(), &dirEntry)) + { + if (dirEntry.isFile) + { + totalInputFileSize += (uint64)dirEntry.fileSize; + totalFileCount++; + } + else if (dirEntry.isDirectory) + { + if (!RecursivelyCountFiles(fmt::format("{}{}/", fscPath, dirEntry.path))) + { + return false; + } + } + else + cemu_assert_unimplemented(); + } + return true; + } + + bool RecursivelyAddFiles(std::string archivePath, std::string fscPath) + { + sint32 fscStatus; + std::unique_ptr vfDir(fsc_openDirIterator(fscPath.c_str(), &fscStatus)); + if (!vfDir) + return false; + if (cancelled) + return false; + zaWriter->MakeDir(archivePath.c_str(), false); + FSCDirEntry dirEntry; + while (fsc_nextDir(vfDir.get(), &dirEntry)) + { + if (dirEntry.isFile) + { + zaWriter->StartNewFile((archivePath + dirEntry.path).c_str()); + std::unique_ptr vFile(fsc_open((fscPath + dirEntry.path).c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus)); + if (!vFile) + return false; + transferBuffer.resize(32 * 1024); // 32KB + uint32 readBytes; + while (true) + { + readBytes = vFile->fscReadData(transferBuffer.data(), transferBuffer.size()); + if(readBytes == 0) + break; + zaWriter->AppendData(transferBuffer.data(), readBytes); + if (cancelled) + return false; + transferredInputBytes += readBytes; + } + currentFileIndex++; + } + else if (dirEntry.isDirectory) + { + if (!RecursivelyAddFiles(fmt::format("{}{}/", archivePath, dirEntry.path), fmt::format("{}{}/", fscPath, dirEntry.path))) + return false; + } + else + cemu_assert_unimplemented(); + } + return true; + } + + bool LoadTitleMetaAndCountFiles(TitleInfo* titleInfo) + { + std::string temporaryMountPath = TitleInfo::GetUniqueTempMountingPath(); + titleInfo->Mount(temporaryMountPath.c_str(), "", FSC_PRIORITY_BASE); + bool r = RecursivelyCountFiles(temporaryMountPath); + titleInfo->Unmount(temporaryMountPath.c_str()); + return r; + } + + bool StoreTitle(TitleInfo* titleInfo) + { + std::string temporaryMountPath = TitleInfo::GetUniqueTempMountingPath(); + titleInfo->Mount(temporaryMountPath.c_str(), "", FSC_PRIORITY_BASE); + bool r = RecursivelyAddFiles(fmt::format("{:016x}_v{}/", titleInfo->GetAppTitleId(), titleInfo->GetAppTitleVersion()), temporaryMountPath.c_str()); + titleInfo->Unmount(temporaryMountPath.c_str()); + return r; + } + + bool AddTitles(TitleInfo** titles, size_t count) + { + currentFileIndex = 0; + totalFileCount = 0; + // count files + for (size_t i = 0; i < count; i++) + { + if (!LoadTitleMetaAndCountFiles(titles[i])) + return false; + if (cancelled) + return false; + } + // store files + for (size_t i = 0; i < count; i++) + { + if (!StoreTitle(titles[i])) + return false; + } + return true; + } + + ~ZArchiveWriterContext() + { + delete fs; + delete zaWriter; + }; + + fs::path outputPath; + FileStream* fs; + ZArchiveWriter* zaWriter{}; + bool isValid{false}; + std::vector transferBuffer; + std::atomic_bool cancelled{false}; + // progress + std::atomic_uint32_t totalFileCount{}; + std::atomic_uint32_t currentFileIndex{}; + std::atomic_uint64_t totalInputFileSize{}; + std::atomic_uint64_t transferredInputBytes{}; + }writerContext; + + // mount and store + writerContext.isValid = true; + writerContext.outputPath = outputPathTmp; + writerContext.zaWriter = new ZArchiveWriter(&ZArchiveWriterContext::NewOutputFile, &ZArchiveWriterContext::WriteOutputData, &writerContext); + if (!writerContext.isValid) + { + // failed to create file + wxMessageBox(_("Unable to create file"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + return; + } + // open progress dialog + wxGenericProgressDialog progressDialog("Converting to .wua", + _("Counting files..."), + 100, // range + this, // parent + wxPD_CAN_ABORT + ); + progressDialog.Show(); + + auto asyncWorker = std::async(std::launch::async, &ZArchiveWriterContext::AddTitles, &writerContext, titlesToConvert.data(), titlesToConvert.size()); + while (!future_is_ready(asyncWorker)) + { + if (writerContext.cancelled) + { + progressDialog.Update(0, _("Stopping...")); + } + else if (writerContext.currentFileIndex != 0) + { + uint64 numSizeCompleted = writerContext.transferredInputBytes; + uint64 numSizeTotal = writerContext.totalInputFileSize; + uint32 pct = (uint32)(numSizeCompleted * (uint64)100 / numSizeTotal); + pct = std::min(pct, (uint32)100); + if (pct >= 100) + pct = 99; // never set it to 100 as progress == total will make .Update() call ShowModal() and lock up this loop + std::string textSuffix = fmt::format(" ({}MiB/{}MiB)", numSizeCompleted / 1024 / 1024, numSizeTotal / 1024 / 1024); + progressDialog.Update(pct, _("Converting files...") + textSuffix); + } + else + { + progressDialog.Update(0, _("Collecting list of files..." + fmt::format(" ({})", writerContext.totalFileCount.load()))); + } + if (progressDialog.WasCancelled()) + writerContext.cancelled.store(true); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + progressDialog.Update(100-1, _("Finalizing...")); + bool r = asyncWorker.get(); + if (!r) + { + delete writerContext.fs; + writerContext.fs = nullptr; + std::error_code ec; + fs::remove(outputPathTmp, ec); + return; + } + writerContext.zaWriter->Finalize(); + delete writerContext.fs; + writerContext.fs = nullptr; + // verify the created WUA file + ZArchiveReader* zreader = ZArchiveReader::OpenFromFile(outputPathTmp); + if (!zreader) + { + wxMessageBox(_("Conversion failed\n"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + std::error_code ec; + fs::remove(outputPathTmp, ec); + return; + } + // todo - do a quick verification here + delete zreader; + // finish + progressDialog.Hide(); + fs::rename(outputPathTmp, outputPath); + + // ask user if they want to delete the original titles + // todo + + + CafeTitleList::Refresh(); + wxMessageBox(_("Conversion finished\n"), _("Complete"), wxOK | wxCENTRE | wxICON_INFORMATION, this); +} + +void wxTitleManagerList::OnClose(wxCloseEvent& event) +{ + event.Skip(); + // wait until all tasks are complete + if (m_context_worker.valid()) + m_context_worker.get(); +} + +void wxTitleManagerList::OnColumnClick(wxListEvent& event) +{ + const int column = event.GetColumn(); + SortEntries(column); + event.Skip(); +} + +void wxTitleManagerList::RemoveItem(long item) +{ + const int item_count = GetItemCount(); + + const ItemData* ref = nullptr; + long counter = 0; + for(auto it = m_sorted_data.begin(); it != m_sorted_data.end(); ++it) + { + if (!it->get().visible) + continue; + + if (item != counter++) + continue; + + ref = &(it->get()); + m_sorted_data.erase(it); + break; + } + + // shouldn't happen + if (ref == nullptr) + return; + + for(auto it = m_data.begin(); it != m_data.end(); ++it) + { + if (ref != (*it).get()) + continue; + + m_data.erase(it); + break; + } + + SetItemCount(std::max(0, item_count - 1)); + RefreshPage(); +} + +void wxTitleManagerList::RemoveItem(const TitleEntry& entry) +{ + const int item_count = GetItemCount(); + + const TitleEntry* ref = &entry; + for (auto it = m_sorted_data.begin(); it != m_sorted_data.end(); ++it) + { + if (ref != &it->get().entry) + continue; + + m_sorted_data.erase(it); + break; + } + + for (auto it = m_data.begin(); it != m_data.end(); ++it) + { + if (ref != &(*it).get()->entry) + continue; + + m_data.erase(it); + break; + } + + SetItemCount(std::max(0, item_count - 1)); + RefreshPage(); +} + +void wxTitleManagerList::OnItemSelected(wxListEvent& event) +{ + event.Skip(); + m_tooltip_timer->Stop(); + const auto selection = event.GetIndex(); + + if (selection == wxNOT_FOUND) + { + m_tooltip_window->Hide(); + return; + } + + const auto entry = GetTitleEntry(selection); + if (!entry.has_value()) + { + m_tooltip_window->Hide(); + return; + } + + m_tooltip_window->Hide(); + return; + + //const auto mouse_position = wxGetMousePosition() - GetScreenPosition(); + //m_tooltip_window->SetPosition(wxPoint(mouse_position.x + 15, mouse_position.y + 15)); + + //wxString msg; + //switch(entry->error) + //{ + //case TitleError::WrongBaseLocation: + // msg = _("This base game is installed at the wrong location."); + // break; + //case TitleError::WrongUpdateLocation: + // msg = _("This update is installed at the wrong location."); + // break; + //case TitleError::WrongDlcLocation: + // msg = _("This DLC is installed at the wrong location."); + // break; + //default: + // return;; + //} + + //m_tooltip_text->SetLabel(wxStringFormat2("{}\n{}", msg, _("You can use the context menu to fix it."))); + //m_tooltip_window->Fit(); + //m_tooltip_timer->StartOnce(250); +} + +enum ContextMenuEntries +{ + kContextMenuOpenDirectory, + kContextMenuDelete, + kContextMenuLaunch, + kContextMenuVerifyGameFiles, + kContextMenuConvertToWUA, +}; +void wxTitleManagerList::OnContextMenu(wxContextMenuEvent& event) +{ + // still doing work + if (m_context_worker.valid() && !future_is_ready(m_context_worker)) + return; + + wxMenu menu; + menu.Bind(wxEVT_COMMAND_MENU_SELECTED, &wxTitleManagerList::OnContextMenuSelected, this); + + const auto selection = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + if (selection == wxNOT_FOUND) + return; + + const auto entry = GetTitleEntry(selection); + if (!entry.has_value()) + return; + + if(entry->type == EntryType::Base) + menu.Append(kContextMenuLaunch, _("&Launch title")); + + menu.Append(kContextMenuOpenDirectory, _("&Open directory")); + if (entry->type != EntryType::Save) + menu.Append(kContextMenuVerifyGameFiles, _("&Verify integrity of game files")); + + menu.AppendSeparator(); + + if (entry->type != EntryType::Save && entry->format != EntryFormat::WUA) + { + menu.Append(kContextMenuConvertToWUA, _("Convert to compressed Wii U archive (.wua)")); + + menu.AppendSeparator(); + } + menu.Append(kContextMenuDelete, _("&Delete")); + + PopupMenu(&menu); + + // TODO: fix tooltip position +} + +bool wxTitleManagerList::DeleteEntry(long index, const TitleEntry& entry) +{ + wxDTorFunc reset_text(wxQueueEvent, this, new wxSetStatusBarTextEvent(wxEmptyString, 1)); + wxQueueEvent(this, new wxSetStatusBarTextEvent("Deleting entry...", 1)); + + wxString msg; + const bool is_directory = fs::is_directory(entry.path); + if(is_directory) + msg = wxStringFormat2(_("Are you really sure that you want to delete the following folder:\n{}"), wxHelper::FromUtf8(_utf8Wrapper(entry.path))); + else + msg = wxStringFormat2(_("Are you really sure that you want to delete the following file:\n{}"), wxHelper::FromUtf8(_utf8Wrapper(entry.path))); + + const auto result = wxMessageBox(msg, _("Warning"), wxYES_NO | wxCENTRE | wxICON_EXCLAMATION, this); + if (result == wxNO) + return false; + + std::error_code ec; + if (is_directory) + { + if (entry.type != EntryType::Save) + { + // delete content, meta, code folders first + const auto content = entry.path / "content"; + fs::remove_all(content, ec); + + const auto meta = entry.path / "meta"; + fs::remove_all(meta, ec); + + const auto code = entry.path / "code"; + fs::remove_all(code, ec); + } + else + { + // delete meta, user folders first + const auto meta = entry.path / "meta"; + fs::remove_all(meta, ec); + + const auto user = entry.path / "user"; + fs::remove_all(user, ec); + } + + + // check if folder is empty + if(fs::is_empty(entry.path, ec)) + fs::remove_all(entry.path, ec); + } + else // simply remove file + fs::remove(entry.path, ec); + + if(ec) + { + const auto error_msg = wxStringFormat2(_("Error when trying to delete the entry:\n{}"), GetSystemErrorMessage(ec)); + wxMessageBox(error_msg, _("Error"), wxOK|wxCENTRE, this); + return false; + } + + // thread safe request to delete entry + const auto evt = new wxCommandEvent(wxEVT_REMOVE_ITEM); + evt->SetInt(index); + wxQueueEvent(this, evt); + return true; +} + +void wxTitleManagerList::OnContextMenuSelected(wxCommandEvent& event) +{ + // still doing work + if (m_context_worker.valid() && !future_is_ready(m_context_worker)) + return; + + const auto selection = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + if (selection == wxNOT_FOUND) + return; + + const auto entry = GetTitleEntry(selection); + if (!entry.has_value()) + return; + + switch (event.GetId()) + { + case kContextMenuOpenDirectory: + { + const auto path = fs::is_directory(entry->path) ? entry->path : entry->path.parent_path(); +#ifdef _WIN32 + ShellExecuteW(GetHWND(), L"open", path.generic_wstring().c_str(), nullptr, nullptr, SW_SHOWNORMAL); +#else + assert_dbg(); +#endif + } + break; + case kContextMenuDelete: + m_context_worker = std::async(std::launch::async, &wxTitleManagerList::DeleteEntry, this, selection, entry.value()); + break; + case kContextMenuLaunch: + { + try + { + MainWindow::RequestLaunchGame(entry->path, wxLaunchGameEvent::INITIATED_BY::TITLE_MANAGER); + Close(); + } + catch (const std::exception& ex) + { + forceLog_printf("wxTitleManagerList::OnContextMenuSelected: can't launch title: %s", ex.what()); + } + } + break; + case kContextMenuVerifyGameFiles: + (new ChecksumTool(this, entry.value()))->Show(); + break; + case kContextMenuConvertToWUA: + + OnConvertToCompressedFormat(entry.value().title_id); + break; + } +} + +void wxTitleManagerList::OnTimer(wxTimerEvent& event) +{ + if(event.GetTimer().GetId() != m_tooltip_timer->GetId()) + { + event.Skip(); + return; + } + + m_tooltip_window->Show(); +} + +void wxTitleManagerList::OnRemoveItem(wxCommandEvent& event) +{ + RemoveItem(event.GetInt()); +} + +void wxTitleManagerList::OnRemoveEntry(wxCommandEvent& event) +{ + wxASSERT(event.GetClientData() != nullptr); + RemoveItem(*(TitleEntry*)event.GetClientData()); +} + +wxString wxTitleManagerList::GetTitleEntryText(const TitleEntry& entry, ItemColumn column) +{ + switch (column) + { + case ColumnTitleId: + return wxStringFormat2("{:08x}-{:08x}", (uint32)(entry.title_id >> 32), (uint32)(entry.title_id & 0xFFFFFFFF)); + case ColumnName: + return entry.name; + case ColumnType: + return wxStringFormat2("{}", entry.type); + case ColumnVersion: + return wxStringFormat2("{}", entry.version); + case ColumnRegion: + return wxStringFormat2("{}", entry.region); // TODO its a flag so formatter is currently not correct + case ColumnFormat: + { + if (entry.type == EntryType::Save) + return _("Save folder"); + switch (entry.format) + { + case wxTitleManagerList::EntryFormat::Folder: + return _("Folder"); + case wxTitleManagerList::EntryFormat::WUD: + return _("WUD"); + case wxTitleManagerList::EntryFormat::WUA: + return _("WUA"); + } + return ""; + //return wxStringFormat2("{}", entry.format); + } + default: + __assume(false); + } + + return wxEmptyString; +} + +void wxTitleManagerList::HandleTitleListCallback(CafeTitleListCallbackEvent* evt) +{ + if (evt->eventType != CafeTitleListCallbackEvent::TYPE::TITLE_DISCOVERED && + evt->eventType != CafeTitleListCallbackEvent::TYPE::TITLE_REMOVED) + return; + + auto& titleInfo = *evt->titleInfo; + wxTitleManagerList::EntryType entryType; + switch (titleInfo.GetTitleType()) + { + case TitleIdParser::TITLE_TYPE::BASE_TITLE_UPDATE: + entryType = EntryType::Update; + break; + case TitleIdParser::TITLE_TYPE::AOC: + entryType = EntryType::Dlc; + break; + case TitleIdParser::TITLE_TYPE::SYSTEM_DATA: + case TitleIdParser::TITLE_TYPE::SYSTEM_OVERLAY_TITLE: + case TitleIdParser::TITLE_TYPE::SYSTEM_TITLE: + entryType = EntryType::System; + break; + default: + entryType = EntryType::Base; + } + + wxTitleManagerList::EntryFormat entryFormat; + switch (titleInfo.GetFormat()) + { + case TitleInfo::TitleDataFormat::HOST_FS: + default: + entryFormat = EntryFormat::Folder; + break; + case TitleInfo::TitleDataFormat::WUD: + entryFormat = EntryFormat::WUD; + break; + case TitleInfo::TitleDataFormat::WIIU_ARCHIVE: + entryFormat = EntryFormat::WUA; + break; + } + + if (evt->eventType == CafeTitleListCallbackEvent::TYPE::TITLE_DISCOVERED) + { + if (titleInfo.IsCached()) + return; // the title list only displays non-cached entries + wxTitleManagerList::TitleEntry entry(entryType, entryFormat, titleInfo.GetPath()); + + ParsedMetaXml* metaInfo = titleInfo.GetMetaInfo(); + + entry.location_uid = titleInfo.GetUID(); + entry.title_id = titleInfo.GetAppTitleId(); + std::string name = metaInfo->GetLongName(GetConfig().console_language.GetValue()); + const auto nl = name.find(L'\n'); + if (nl != std::string::npos) + name.replace(nl, 1, " - "); + entry.name = wxString::FromUTF8(name); + entry.version = titleInfo.GetAppTitleVersion(); + entry.region = metaInfo->GetRegion(); + + auto* cmdEvt = new wxCommandEvent(wxEVT_TITLE_FOUND); + cmdEvt->SetClientObject(new wxCustomData(entry)); + wxQueueEvent(this, cmdEvt); + } + else if (evt->eventType == CafeTitleListCallbackEvent::TYPE::TITLE_REMOVED) + { + wxTitleManagerList::TitleEntry entry(entryType, entryFormat, titleInfo.GetPath()); + entry.location_uid = titleInfo.GetUID(); + entry.title_id = titleInfo.GetAppTitleId(); + + auto* cmdEvt = new wxCommandEvent(wxEVT_TITLE_REMOVED); + cmdEvt->SetClientObject(new wxCustomData(entry)); + wxQueueEvent(this, cmdEvt); + } +} + +void wxTitleManagerList::HandleSaveListCallback(struct CafeSaveListCallbackEvent* evt) +{ + if (evt->eventType == CafeSaveListCallbackEvent::TYPE::SAVE_DISCOVERED) + { + ParsedMetaXml* metaInfo = evt->saveInfo->GetMetaInfo(); + if (!metaInfo) + return; + auto& saveInfo = *evt->saveInfo; + wxTitleManagerList::TitleEntry entry(EntryType::Save, EntryFormat::Folder, saveInfo.GetPath()); + entry.location_uid = std::hash() ( metaInfo->GetTitleId() ); + entry.title_id = metaInfo->GetTitleId(); + std::string name = metaInfo->GetLongName(GetConfig().console_language.GetValue()); + const auto nl = name.find(L'\n'); + if (nl != std::string::npos) + name.replace(nl, 1, " - "); + entry.name = wxString::FromUTF8(name); + entry.version = metaInfo->GetTitleVersion(); + entry.region = metaInfo->GetRegion(); + + auto* cmdEvt = new wxCommandEvent(wxEVT_TITLE_FOUND); + cmdEvt->SetClientObject(new wxCustomData(entry)); + wxQueueEvent(this, cmdEvt); + } +} + +void wxTitleManagerList::OnTitleDiscovered(wxCommandEvent& event) +{ + auto* obj = dynamic_cast(event.GetClientObject()); + wxASSERT(obj); + AddTitle(obj); +} + +void wxTitleManagerList::OnTitleRemoved(wxCommandEvent& event) +{ + auto* obj = dynamic_cast(event.GetClientObject()); + wxASSERT(obj); + for (auto& itr : m_data) + { + if (itr.get()->entry.location_uid == obj->get().location_uid) + { + RemoveItem(itr.get()->entry); + break; + } + } +} + +void wxTitleManagerList::AddTitle(TitleEntryData_t* obj) +{ + const auto& data = obj->GetData(); + if (GetTitleEntryByUID(data.location_uid).has_value()) + return; // already in list + m_data.emplace_back(std::make_unique(true, data)); + m_sorted_data.emplace_back(*m_data[m_data.size() - 1]); + SetItemCount(m_data.size()); +} + +int wxTitleManagerList::AddImage(const wxImage& image) const +{ + return -1; // m_image_list->Add(image.Scale(kListIconWidth, kListIconWidth, wxIMAGE_QUALITY_BICUBIC)); +} + +// return < +bool wxTitleManagerList::SortFunc(int column, const Type_t& v1, const Type_t& v2) +{ + // last sort option + if (column == -1) + return v1.get().entry.path.compare(v2.get().entry.path) < 0; + + // visible have always priority + if (!v1.get().visible && v2.get().visible) + return false; + else if (v1.get().visible && !v2.get().visible) + return true; + + const auto& entry1 = v1.get().entry; + const auto& entry2 = v2.get().entry; + + // check column: title id -> type -> path + if (column == ColumnTitleId) + { + // ensure strong ordering -> use type since only one entry should be now (should be changed if every save for every user is displayed spearately?) + if (entry1.title_id == entry2.title_id) + return SortFunc(ColumnType, v1, v2); + + return entry1.title_id < entry2.title_id; + } + else if (column == ColumnName) + { + const int tmp = entry1.name.CmpNoCase(entry2.name); + if(tmp == 0) + return SortFunc(ColumnTitleId, v1, v2); + + return tmp < 0; + } + else if (column == ColumnType) + { + if(std::underlying_type_t(entry1.type) == std::underlying_type_t(entry2.type)) + return SortFunc(-1, v1, v2); + + return std::underlying_type_t(entry1.type) < std::underlying_type_t(entry2.type); + } + else if (column == ColumnVersion) + { + if(entry1.version == entry2.version) + return SortFunc(ColumnTitleId, v1, v2); + + return std::underlying_type_t(entry1.version) < std::underlying_type_t(entry2.version); + } + else if (column == ColumnRegion) + { + if(entry1.region == entry2.region) + return SortFunc(ColumnTitleId, v1, v2); + + return std::underlying_type_t(entry1.region) < std::underlying_type_t(entry2.region); + } + + return false; +} +void wxTitleManagerList::SortEntries(int column) +{ + if(column == -1) + { + column = m_last_column_sorted; + m_last_column_sorted = -1; + if (column == -1) + column = ColumnTitleId; + } + + if (column != ColumnTitleId && column != ColumnName && column != ColumnType && column != ColumnVersion && column != ColumnRegion && column != ColumnFormat) + return; + + if (m_last_column_sorted != column) + { + m_last_column_sorted = column; + m_sort_less = true; + } + else + m_sort_less = !m_sort_less; + + std::sort(m_sorted_data.begin(), m_sorted_data.end(), + [this, column](const Type_t& v1, const Type_t& v2) -> bool + { + const bool result = SortFunc(column, v1, v2); + return m_sort_less ? result : !result; + }); + + RefreshPage(); +} + +void wxTitleManagerList::RefreshPage() { RefreshItems(GetTopItem(), GetTopItem() + GetCountPerPage() + 1); } + +int wxTitleManagerList::Filter(const wxString& filter, const wxString& prefix, ItemColumn column) +{ + if (prefix.empty()) + return -1; + + if (!filter.StartsWith(prefix)) + return -1; + + int counter = 0; + const auto tmp_filter = filter.substr(prefix.size() - 1).Trim(false); + for (auto&& data : m_data) + { + if (GetTitleEntryText(data->entry, column).Upper().Contains(tmp_filter)) + { + data->visible = true; + ++counter; + } + else + data->visible = false; + } + return counter; +} + +void wxTitleManagerList::Filter(const wxString& filter) +{ + if(filter.empty()) + { + std::for_each(m_data.begin(), m_data.end(), [](ItemDataPtr& data) { data->visible = true; }); + SetItemCount(m_data.size()); + RefreshPage(); + return; + } + + const auto filter_upper = filter.Upper().Trim(false).Trim(true); + int counter = 0; + + if (const auto result = Filter(filter_upper, "TITLEID:", ColumnTitleId) != -1) + counter = result; + else if (const auto result = Filter(filter_upper, "NAME:", ColumnName) != -1) + counter = result; + else if (const auto result = Filter(filter_upper, "TYPE:", ColumnType) != -1) + counter = result; + else if (const auto result = Filter(filter_upper, "REGION:", ColumnRegion) != -1) + counter = result; + else if (const auto result = Filter(filter_upper, "VERSION:", ColumnVersion) != -1) + counter = result; + else if (const auto result = Filter(filter_upper, "FORMAT:", ColumnFormat) != -1) + counter = result; + else if(filter_upper == "ERROR") + { + for (auto&& data : m_data) + { + bool visible = true; + data->visible = visible; + if (visible) + ++counter; + } + } + else + { + for (auto&& data : m_data) + { + bool visible = false; + if (data->entry.name.Upper().Contains(filter_upper)) + visible = true; + else if (GetTitleEntryText(data->entry, ColumnTitleId).Upper().Contains(filter_upper)) + visible = true; + else if (GetTitleEntryText(data->entry, ColumnType).Upper().Contains(filter_upper)) + visible = true; + + data->visible = visible; + if (visible) + ++counter; + } + } + + SetItemCount(counter); + RefreshPage(); +} + +size_t wxTitleManagerList::GetCountByType(EntryType type) const +{ + size_t result = 0; + for(const auto& data : m_data) + { + if (data->entry.type == type) + ++result; + } + return result; +} + +void wxTitleManagerList::ClearItems() +{ + m_sorted_data.clear(); + m_data.clear(); + SetItemCount(0); + RefreshPage(); +} + +void wxTitleManagerList::AutosizeColumns() +{ + wxAutosizeColumns(this, ColumnTitleId, ColumnMAX - 1); +} \ No newline at end of file diff --git a/src/gui/components/wxTitleManagerList.h b/src/gui/components/wxTitleManagerList.h new file mode 100644 index 00000000..2db99309 --- /dev/null +++ b/src/gui/components/wxTitleManagerList.h @@ -0,0 +1,162 @@ +#pragma once + +#include "gui/helpers/wxCustomData.h" +#include "config/CemuConfig.h" + +#include + +#include // std::optional doesn't support optional reference inner types yet +#include +#include + +class wxTitleManagerList : public wxListCtrl +{ + friend class TitleManager; +public: + wxTitleManagerList(wxWindow* parent, wxWindowID id = wxID_ANY); + ~wxTitleManagerList(); + + enum ItemColumn + { + ColumnTitleId = 0, + ColumnName, + ColumnType, + ColumnVersion, + ColumnRegion, + ColumnFormat, + + ColumnMAX, + }; + + enum class EntryType + { + Base, + Update, + Dlc, + Save, + System, + }; + + enum class EntryFormat + { + Folder, + WUD, + WUA, + }; + + // sort by column, if -1 will sort by last column or default (=titleid) + void SortEntries(int column); + void RefreshPage(); + void Filter(const wxString& filter); + [[nodiscard]] size_t GetCountByType(EntryType type) const; + void ClearItems(); + void AutosizeColumns(); + + struct TitleEntry + { + TitleEntry(const EntryType& type, const EntryFormat& format, fs::path path) + : type(type), format(format), path(std::move(path)) {} + + EntryType type; + EntryFormat format; + fs::path path; + + int icon = -1; + uint64 location_uid; + uint64 title_id; + wxString name; + uint32_t version = 0; + CafeConsoleRegion region; + + std::vector persistent_ids; // only used for save + }; + boost::optional GetSelectedTitleEntry() const; + +private: + void AddColumns(); + int Filter(const wxString& filter, const wxString& prefix, ItemColumn column); + boost::optional GetSelectedTitleEntry(); + boost::optional GetTitleEntryByUID(uint64 uid); + + class wxPanel* m_tooltip_window; + class wxStaticText* m_tooltip_text; + class wxTimer* m_tooltip_timer; + + void OnClose(wxCloseEvent& event); + void OnColumnClick(wxListEvent& event); + void OnContextMenu(wxContextMenuEvent& event); + void OnItemSelected(wxListEvent& event); + void OnContextMenuSelected(wxCommandEvent& event); + void OnTimer(class wxTimerEvent& event); + void OnRemoveItem(wxCommandEvent& event); + void OnRemoveEntry(wxCommandEvent& event); + + void OnTitleDiscovered(wxCommandEvent& event); + void OnTitleRemoved(wxCommandEvent& event); + void HandleTitleListCallback(struct CafeTitleListCallbackEvent* evt); + void HandleSaveListCallback(struct CafeSaveListCallbackEvent* evt); + + using TitleEntryData_t = wxCustomData; + void AddTitle(TitleEntryData_t* obj); + int AddImage(const wxImage& image) const; + wxString OnGetItemText(long item, long column) const override; + wxItemAttr* OnGetItemAttr(long item) const override; + [[nodiscard]] boost::optional GetTitleEntry(long item) const; + [[nodiscard]] boost::optional GetTitleEntry(long item); + [[nodiscard]] boost::optional GetTitleEntry(const fs::path& path) const; + [[nodiscard]] boost::optional GetTitleEntry(const fs::path& path); + + bool VerifyEntryFiles(TitleEntry& entry); + void OnConvertToCompressedFormat(uint64 titleId); + bool DeleteEntry(long index, const TitleEntry& entry); + + void RemoveItem(long item); + void RemoveItem(const TitleEntry& entry); + + struct ItemData + { + ItemData(bool visible, const TitleEntry& entry) + : visible(visible), entry(entry) {} + + bool visible; + TitleEntry entry; + }; + using ItemDataPtr = std::unique_ptr; + std::vector m_data; + std::vector> m_sorted_data; + + int m_last_column_sorted = -1; + bool m_sort_less = true; + using Type_t = std::reference_wrapper; + bool SortFunc(int column, const Type_t& v1, const Type_t& v2); + + static wxString GetTitleEntryText(const TitleEntry& entry, ItemColumn column); + std::future m_context_worker; + + uint64 m_callbackIdTitleList; + uint64 m_callbackIdSaveList; +}; + +template <> +struct fmt::formatter : formatter +{ + using base = fmt::formatter; + template + auto format(const wxTitleManagerList::EntryType& type, FormatContext& ctx) + { + switch (type) + { + case wxTitleManagerList::EntryType::Base: + return base::format("base", ctx); + case wxTitleManagerList::EntryType::Update: + return base::format("update", ctx); + case wxTitleManagerList::EntryType::Dlc: + return base::format("DLC", ctx); + case wxTitleManagerList::EntryType::Save: + return base::format("save", ctx); + case wxTitleManagerList::EntryType::System: + return base::format("system", ctx); + } + return base::format(std::to_string(static_cast>(type)), ctx); + } +}; \ No newline at end of file diff --git a/src/gui/debugger/BreakpointWindow.cpp b/src/gui/debugger/BreakpointWindow.cpp new file mode 100644 index 00000000..ef4eec9a --- /dev/null +++ b/src/gui/debugger/BreakpointWindow.cpp @@ -0,0 +1,264 @@ +#include "gui/wxgui.h" +#include "gui/debugger/BreakpointWindow.h" + +#include + +#include "gui/debugger/DebuggerWindow2.h" +#include "gui/guiWrapper.h" +#include "Cafe/HW/Espresso/Debugger/Debugger.h" + +#include "Cemu/ExpressionParser/ExpressionParser.h" + +enum +{ + MENU_ID_CREATE_MEM_BP_READ = 1, + MENU_ID_CREATE_MEM_BP_WRITE, + +}; + +enum ItemColumns +{ + ColumnEnabled = 0, + ColumnAddress, + ColumnType, + ColumnComment, +}; + +BreakpointWindow::BreakpointWindow(DebuggerWindow2& parent, const wxPoint& main_position, const wxSize& main_size) + : wxFrame(&parent, wxID_ANY, "Breakpoints", wxDefaultPosition, wxSize(420, 250), wxSYSTEM_MENU | wxCAPTION | wxCLIP_CHILDREN | wxRESIZE_BORDER | wxFRAME_FLOAT_ON_PARENT) +{ + this->SetSizeHints(wxDefaultSize, wxDefaultSize); + + this->wxWindowBase::SetBackgroundColour(*wxWHITE); + + wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL); + + m_breakpoints = new wxCheckedListCtrl(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT); + + + wxListItem col0; + col0.SetId(ColumnEnabled); + col0.SetText(_("On")); + col0.SetWidth(32); + m_breakpoints->InsertColumn(ColumnEnabled, col0); + + wxListItem col1; + col1.SetId(ColumnAddress); + col1.SetText(_("Address")); + col1.SetWidth(75); + m_breakpoints->InsertColumn(ColumnAddress, col1); + + wxListItem col2; + col2.SetId(ColumnType); + col2.SetText(_("Type")); + col2.SetWidth(42); + m_breakpoints->InsertColumn(ColumnType, col2); + + wxListItem col3; + col3.SetId(ColumnComment); + col3.SetText(_("Comment")); + col3.SetWidth(250); + m_breakpoints->InsertColumn(ColumnComment, col3); + + main_sizer->Add(m_breakpoints, 1, wxEXPAND); + + this->SetSizer(main_sizer); + this->wxWindowBase::Layout(); + + this->Centre(wxBOTH); + + if (parent.GetConfig().data().pin_to_main) + OnMainMove(main_position, main_size); + + m_breakpoints->Bind(wxEVT_COMMAND_LIST_ITEM_CHECKED, (wxObjectEventFunction)&BreakpointWindow::OnBreakpointToggled, this); + m_breakpoints->Bind(wxEVT_COMMAND_LIST_ITEM_UNCHECKED, (wxObjectEventFunction)&BreakpointWindow::OnBreakpointToggled, this); + m_breakpoints->Bind(wxEVT_LEFT_DCLICK, &BreakpointWindow::OnLeftDClick, this); + m_breakpoints->Bind(wxEVT_RIGHT_DOWN, &BreakpointWindow::OnRightDown, this); + + OnUpdateView(); +} + +BreakpointWindow::~BreakpointWindow() +{ + m_breakpoints->Unbind(wxEVT_COMMAND_LIST_ITEM_CHECKED, (wxObjectEventFunction)&BreakpointWindow::OnBreakpointToggled, this); + m_breakpoints->Unbind(wxEVT_COMMAND_LIST_ITEM_UNCHECKED, (wxObjectEventFunction)&BreakpointWindow::OnBreakpointToggled, this); +} + +void BreakpointWindow::OnMainMove(const wxPoint& main_position, const wxSize& main_size) +{ + wxSize size(420, 250); + this->SetSize(size); + + wxPoint position = main_position; + position.x -= 420; + position.y += main_size.y - 250; + this->SetPosition(position); +} + +void BreakpointWindow::OnUpdateView() +{ + Freeze(); + + m_breakpoints->DeleteAllItems(); + + if (!debuggerState.breakpoints.empty()) + { + uint32_t i = 0; + for (const auto bpBase : debuggerState.breakpoints) + { + + DebuggerBreakpoint* bp = bpBase; + while (bp) + { + wxListItem item; + item.SetId(i++); + + const auto index = m_breakpoints->InsertItem(item); + m_breakpoints->SetItem(index, ColumnAddress, wxString::Format("%08x", bp->address)); + const char* typeName = "UKN"; + if (bp->bpType == DEBUGGER_BP_T_NORMAL) + typeName = "X"; + else if (bp->bpType == DEBUGGER_BP_T_ONE_SHOT) + typeName = "XS"; + else if (bp->bpType == DEBUGGER_BP_T_MEMORY_READ) + typeName = "R"; + else if (bp->bpType == DEBUGGER_BP_T_MEMORY_WRITE) + typeName = "W"; + + m_breakpoints->SetItem(index, ColumnType, typeName); + m_breakpoints->SetItem(index, ColumnComment, bp->comment); + m_breakpoints->SetItemPtrData(item, (wxUIntPtr)bp); + m_breakpoints->Check(index, bp->enabled); + + bp = bp->next; + } + + } + } + + Thaw(); +} + +void BreakpointWindow::OnGameLoaded() +{ + OnUpdateView(); +} + +void BreakpointWindow::OnBreakpointToggled(wxListEvent& event) +{ + const int32_t index = event.GetIndex(); + if (0 <= index && index < m_breakpoints->GetItemCount()) + { + const bool state = m_breakpoints->IsChecked(index); + wxString line = m_breakpoints->GetItemText(index, ColumnAddress); + DebuggerBreakpoint* bp = (DebuggerBreakpoint*)m_breakpoints->GetItemData(index); + const uint32 address = std::stoul(line.c_str().AsChar(), nullptr, 16); + debugger_toggleBreakpoint(address, state, bp); + } +} + +void BreakpointWindow::OnLeftDClick(wxMouseEvent& event) +{ + const auto position = event.GetPosition(); + const sint32 index = (position.y / m_breakpoints->GetCharHeight()) - 2; + if (index < 0 || index >= m_breakpoints->GetItemCount()) + return; + + sint32 x = position.x; + const auto enabled_width = m_breakpoints->GetColumnWidth(ColumnEnabled); + if (x <= enabled_width) + return; + + x -= enabled_width; + const auto address_width = m_breakpoints->GetColumnWidth(ColumnAddress); + if(x <= address_width) + { + const auto item = m_breakpoints->GetItemText(index, ColumnAddress); + const auto address = std::stoul(item.ToStdString(), nullptr, 16); + debuggerState.debugSession.instructionPointer = address; + debuggerWindow_moveIP(); + return; + } + + x -= address_width; + const auto type_width = m_breakpoints->GetColumnWidth(ColumnType); + if (x <= type_width) + return; + + x -= type_width; + const auto comment_width = m_breakpoints->GetColumnWidth(ColumnComment); + if(x <= comment_width) + { + if (index >= debuggerState.breakpoints.size()) + return; + + const auto item = m_breakpoints->GetItemText(index, ColumnAddress); + const auto address = std::stoul(item.ToStdString(), nullptr, 16); + + auto it = debuggerState.breakpoints.begin(); + std::advance(it, index); + + wxTextEntryDialog set_value_dialog(this, _("Enter a new comment."), wxString::Format(_("Set comment for breakpoint at address %08x"), address), (*it)->comment); + if (set_value_dialog.ShowModal() == wxID_OK) + { + (*it)->comment = set_value_dialog.GetValue().ToStdWstring(); + m_breakpoints->SetItem(index, ColumnComment, set_value_dialog.GetValue()); + } + + return; + } +} + +void BreakpointWindow::OnRightDown(wxMouseEvent& event) +{ + wxMenu menu; + + menu.Append(MENU_ID_CREATE_MEM_BP_READ, _("Create memory breakpoint (read)")); + menu.Append(MENU_ID_CREATE_MEM_BP_WRITE, _("Create memory breakpoint (write)")); + + menu.Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(BreakpointWindow::OnContextMenuClick), nullptr, this); + PopupMenu(&menu); +} + +void BreakpointWindow::OnContextMenuClick(wxCommandEvent& evt) +{ + switch (evt.GetId()) + { + case MENU_ID_CREATE_MEM_BP_READ: + MemoryBreakpointDialog(false); + return; + case MENU_ID_CREATE_MEM_BP_WRITE: + MemoryBreakpointDialog(true); + return; + } +} + +void BreakpointWindow::MemoryBreakpointDialog(bool isWrite) +{ + wxTextEntryDialog goto_dialog(this, _("Enter a memory address"), _("Memory breakpoint"), wxEmptyString); + if (goto_dialog.ShowModal() == wxID_OK) + { + ExpressionParser parser; + + auto value = goto_dialog.GetValue().ToStdString(); + std::transform(value.begin(), value.end(), value.begin(), tolower); + + + try + { + debugger_addParserSymbols(parser); + const auto result = (uint32)parser.Evaluate(value); + debug_printf("goto eval result: %x\n", result); + + debugger_createMemoryBreakpoint(result, isWrite == false, isWrite == true); + this->OnUpdateView(); + } + catch (const std::exception& e) + { + //ctx.errorHandler.printError(nullptr, -1, fmt::format("Unexpected error in expression \"{}\"", expressionString)); + //return EXPRESSION_RESOLVE_RESULT::EXPRESSION_ERROR; + wxMessageBox(e.what(), "Invalid expression"); + } + + } +} \ No newline at end of file diff --git a/src/gui/debugger/BreakpointWindow.h b/src/gui/debugger/BreakpointWindow.h new file mode 100644 index 00000000..4851cb52 --- /dev/null +++ b/src/gui/debugger/BreakpointWindow.h @@ -0,0 +1,26 @@ +#pragma once +#include "gui/wxcomponents/checkedlistctrl.h" + +class DebuggerWindow2; + +class BreakpointWindow : public wxFrame +{ +public: + BreakpointWindow(DebuggerWindow2& parent, const wxPoint& main_position, const wxSize& main_size); + virtual ~BreakpointWindow(); + + void OnMainMove(const wxPoint& position, const wxSize& main_size); + void OnUpdateView(); + void OnGameLoaded(); + +private: + void OnBreakpointToggled(wxListEvent& event); + void OnLeftDClick(wxMouseEvent& event); + void OnRightDown(wxMouseEvent& event); + + void OnContextMenuClick(wxCommandEvent& evt); + + void MemoryBreakpointDialog(bool isWrite); + + wxCheckedListCtrl* m_breakpoints; +}; \ No newline at end of file diff --git a/src/gui/debugger/DebuggerWindow2.cpp b/src/gui/debugger/DebuggerWindow2.cpp new file mode 100644 index 00000000..a2dee9cf --- /dev/null +++ b/src/gui/debugger/DebuggerWindow2.cpp @@ -0,0 +1,676 @@ +#include "gui/wxgui.h" +#include "gui/debugger/DebuggerWindow2.h" + +#include + +#include "config/ActiveSettings.h" +#include "Cafe/OS/RPL/rpl_structs.h" +#include "Cafe/OS/RPL/rpl_debug_symbols.h" + +#include "gui/debugger/RegisterWindow.h" +#include "gui/debugger/DumpWindow.h" + +#include "Cafe/HW/Espresso/Debugger/Debugger.h" + +#include "Cafe/OS/RPL/rpl.h" +#include "gui/debugger/DisasmCtrl.h" +#include "gui/debugger/SymbolWindow.h" +#include "gui/debugger/BreakpointWindow.h" +#include "gui/debugger/ModuleWindow.h" +#include "util/helpers/helpers.h" + +#if BOOST_OS_LINUX +#include "resource/linux/resources.h" +#endif + +enum +{ + // file + MENU_ID_FILE_EXIT = wxID_HIGHEST + 8000, + // settings + MENU_ID_OPTIONS_PIN_TO_MAINWINDOW, + MENU_ID_OPTIONS_BREAK_ON_START, + // window + MENU_ID_WINDOW_REGISTERS, + MENU_ID_WINDOW_DUMP, + MENU_ID_WINDOW_STACK, + MENU_ID_WINDOW_BREAKPOINTS, + MENU_ID_WINDOW_MODULE, + MENU_ID_WINDOW_SYMBOL, + + // tool + TOOL_ID_GOTO, + TOOL_ID_BP, + TOOL_ID_PAUSE, + TOOL_ID_STEP_OVER, + TOOL_ID_STEP_INTO, +}; + +wxDEFINE_EVENT(wxEVT_DEBUGGER_CLOSE, wxCloseEvent); +wxDEFINE_EVENT(wxEVT_UPDATE_VIEW, wxCommandEvent); +wxDEFINE_EVENT(wxEVT_BREAKPOINT_CHANGE, wxCommandEvent); +wxDEFINE_EVENT(wxEVT_BREAKPOINT_HIT, wxCommandEvent); +wxDEFINE_EVENT(wxEVT_MOVE_IP, wxCommandEvent); +wxDEFINE_EVENT(wxEVT_RUN, wxCommandEvent); +wxDEFINE_EVENT(wxEVT_NOTIFY_MODULE_LOADED, wxCommandEvent); +wxDEFINE_EVENT(wxEVT_NOTIFY_MODULE_UNLOADED, wxCommandEvent); + +wxBEGIN_EVENT_TABLE(DebuggerWindow2, wxFrame) + EVT_SHOW(DebuggerWindow2::OnShow) + EVT_CLOSE(DebuggerWindow2::OnClose) + EVT_COMMAND(wxID_ANY, wxEVT_UPDATE_VIEW, DebuggerWindow2::OnUpdateView) + EVT_COMMAND(wxID_ANY, wxEVT_BREAKPOINT_CHANGE, DebuggerWindow2::OnBreakpointChange) + EVT_COMMAND(wxID_ANY, wxEVT_MOVE_IP, DebuggerWindow2::OnMoveIP) + EVT_COMMAND(wxID_ANY, wxEVT_COMMAND_TOOL_CLICKED, DebuggerWindow2::OnToolClicked) + EVT_COMMAND(wxID_ANY, wxEVT_BREAKPOINT_HIT, DebuggerWindow2::OnBreakpointHit) + EVT_COMMAND(wxID_ANY, wxEVT_RUN, DebuggerWindow2::OnRunProgram) + EVT_COMMAND(wxID_ANY, wxEVT_NOTIFY_MODULE_LOADED, DebuggerWindow2::OnNotifyModuleLoaded) + EVT_COMMAND(wxID_ANY, wxEVT_NOTIFY_MODULE_UNLOADED, DebuggerWindow2::OnNotifyModuleUnloaded) + // file menu + EVT_MENU(MENU_ID_FILE_EXIT, DebuggerWindow2::OnExit) + // setting + EVT_MENU(MENU_ID_OPTIONS_PIN_TO_MAINWINDOW, DebuggerWindow2::OnOptionsInput) + // window + EVT_MENU_RANGE(MENU_ID_WINDOW_REGISTERS, MENU_ID_WINDOW_MODULE, DebuggerWindow2::OnWindowMenu) +wxEND_EVENT_TABLE() + +DebuggerWindow2* g_debugger_window; + +void DebuggerConfig::Load(XMLConfigParser& parser) +{ + pin_to_main = parser.get("PinToMainWindow", true); + break_on_start = parser.get("break_on_start", true); + + auto window_parser = parser.get("Windows"); + show_register = window_parser.get("Registers", true); + show_dump = window_parser.get("MemoryDump", true); + show_stack = window_parser.get("Stack", true); + show_breakpoints = window_parser.get("Breakpoints", true); + show_modules = window_parser.get("Modules", true); + show_symbols = window_parser.get("Symbols", true); +} + +void DebuggerConfig::Save(XMLConfigParser& parser) +{ + parser.set("PinToMainWindow", pin_to_main); + parser.set("break_on_start", break_on_start); + + auto window_parser = parser.set("Windows"); + window_parser.set("Registers", show_register); + window_parser.set("MemoryDump", show_dump); + window_parser.set("Stack", show_stack); + window_parser.set("Breakpoints", show_breakpoints); + window_parser.set("Modules", show_modules); + window_parser.set("Symbols", show_symbols); +} + +void DebuggerModuleStorage::Load(XMLConfigParser& parser) +{ + auto breakpoints_parser = parser.get("Breakpoints"); + for (auto element = breakpoints_parser.get("Entry"); element.valid(); element = breakpoints_parser.get("Entry", element)) + { + const auto address_string = element.get("Address", ""); + if (*address_string == '\0') + continue; + + uint32 relative_address = std::stoul(address_string, nullptr, 16); + if (relative_address == 0) + continue; + + auto type = element.get("Type", 0); + auto enabled = element.get("Enabled", true); + const auto comment = element.get("Comment", ""); + + // calculate absolute address + uint32 module_base_address = (type == DEBUGGER_BP_T_NORMAL ? this->rpl_module->regionMappingBase_text.GetMPTR() : this->rpl_module->regionMappingBase_data); + uint32 address = module_base_address + relative_address; + + // don't change anything if there's already a breakpoint + if (debugger_getFirstBP(address) != nullptr) + continue; + + // register breakpoints in debugger + if (type == DEBUGGER_BP_T_NORMAL) + debugger_createExecuteBreakpoint(address); + else if (type == DEBUGGER_BP_T_MEMORY_READ) + debugger_createMemoryBreakpoint(address, true, false); + else if (type == DEBUGGER_BP_T_MEMORY_WRITE) + debugger_createMemoryBreakpoint(address, false, true); + else + continue; + + DebuggerBreakpoint* debugBreakpoint = debugger_getFirstBP(address); + + if (!enabled) + debugger_toggleBreakpoint(address, false, debugBreakpoint); + + debugBreakpoint->comment = boost::nowide::widen(comment); + } + + auto comments_parser = parser.get("Comments"); + for (XMLConfigParser element = comments_parser.get("Entry"); element.valid(); element = comments_parser.get("Entry", element)) + { + const auto address_string = element.get("Address", ""); + if (*address_string == '\0') + continue; + + uint32 address = std::stoul(address_string, nullptr, 16); + if (address == 0) + continue; + + const auto comment = element.get("Comment", ""); + if (*comment == '\0') + continue; + + rplDebugSymbol_createComment(address, boost::nowide::widen(comment).c_str()); + } +} + +void DebuggerModuleStorage::Save(XMLConfigParser& parser) +{ + auto breakpoints_parser = parser.set("Breakpoints"); + for (const auto& bp : debuggerState.breakpoints) + { + // check breakpoint type + if (bp->dbType != DEBUGGER_BP_T_DEBUGGER) + continue; + + // check whether the breakpoint is part of the current module being saved + RPLModule* address_module; + if (bp->bpType == DEBUGGER_BP_T_NORMAL) address_module = RPLLoader_FindModuleByCodeAddr(bp->address); + else if (bp->isMemBP()) address_module = RPLLoader_FindModuleByDataAddr(bp->address); + else continue; + + if (!address_module || !(address_module->moduleName2 == this->module_name && address_module->patchCRC == this->crc_hash)) + continue; + + uint32_t relative_address = bp->address - (bp->isMemBP() ? address_module->regionMappingBase_data : address_module->regionMappingBase_text.GetMPTR()); + auto entry = breakpoints_parser.set("Entry"); + entry.set("Address", fmt::format("{:#10x}", relative_address)); + entry.set("Comment", boost::nowide::narrow(bp->comment).c_str()); + entry.set("Type", bp->bpType); + entry.set("Enabled", bp->enabled); + + if (this->delete_breakpoints_after_saving) debugger_deleteBreakpoint(bp); + this->delete_breakpoints_after_saving = false; + } + + auto comments_parser = parser.set("Comments"); + for (const auto& comment_entry : rplDebugSymbol_getSymbols()) + { + // check comment type + const auto comment_address = comment_entry.first; + const auto comment = static_cast(comment_entry.second); + if (!comment || comment->type != RplDebugSymbolComment) + continue; + + // check whether it's part of the current module being saved + RPLModule* address_module = RPLLoader_FindModuleByCodeAddr(comment_entry.first); + if (!address_module || !(address_module->moduleName2 == module_name && address_module->patchCRC == this->crc_hash)) + continue; + + uint32_t relative_address = comment_address - address_module->regionMappingBase_text.GetMPTR(); + auto entry = comments_parser.set("Entry"); + entry.set("Address", fmt::format("{:#10x}", relative_address)); + entry.set("Comment", boost::nowide::narrow(comment->comment).c_str()); + } +} + +void DebuggerWindow2::CreateToolBar() +{ + m_toolbar = wxFrame::CreateToolBar(wxTB_HORIZONTAL, wxID_ANY); + m_toolbar->SetToolBitmapSize(wxSize(1, 1)); + + m_toolbar->AddTool(TOOL_ID_GOTO, wxEmptyString, wxBITMAP_PNG(DEBUGGER_GOTO), wxNullBitmap, wxITEM_NORMAL, _("GoTo (CTRL + G)"), "test", NULL); + m_toolbar->AddSeparator(); + + m_toolbar->AddTool(TOOL_ID_BP, wxEmptyString, wxBITMAP_PNG(DEBUGGER_BP_RED), wxNullBitmap, wxITEM_NORMAL, _("Toggle Breakpoint (F9)"), wxEmptyString, NULL); + m_toolbar->AddSeparator(); + + m_pause = wxBITMAP_PNG(DEBUGGER_PAUSE); + m_run = wxBITMAP_PNG(DEBUGGER_PLAY); + m_toolbar->AddTool(TOOL_ID_PAUSE, wxEmptyString, m_pause, wxNullBitmap, wxITEM_NORMAL, _("Break (F5)"), wxEmptyString, NULL); + + m_toolbar->AddTool(TOOL_ID_STEP_INTO, wxEmptyString, wxBITMAP_PNG(DEBUGGER_STEP_INTO), wxNullBitmap, wxITEM_NORMAL, _("Step Into (F11)"), wxEmptyString, NULL); + m_toolbar->AddTool(TOOL_ID_STEP_OVER, wxEmptyString, wxBITMAP_PNG(DEBUGGER_STEP_OVER), wxNullBitmap, wxITEM_NORMAL, _("Step Over (F10)"), wxEmptyString, NULL); + m_toolbar->AddSeparator(); + + m_toolbar->Realize(); + + m_toolbar->EnableTool(TOOL_ID_STEP_INTO, false); + m_toolbar->EnableTool(TOOL_ID_STEP_OVER, false); +} + +void DebuggerWindow2::SaveModuleStorage(const RPLModule* module, bool delete_breakpoints) +{ + auto path = GetModuleStoragePath(module->moduleName2, module->patchCRC); + for (auto& module_storage : m_modules_storage) + { + if (module_storage->data().module_name == module->moduleName2 && module_storage->data().crc_hash == module->patchCRC) + { + module_storage->data().delete_breakpoints_after_saving = delete_breakpoints; + module_storage->Save(path); + if (delete_breakpoints) m_modules_storage.erase(std::find(m_modules_storage.begin(), m_modules_storage.end(), module_storage)); + } + } + +} + +void DebuggerWindow2::LoadModuleStorage(const RPLModule* module) +{ + auto path = GetModuleStoragePath(module->moduleName2, module->patchCRC); + bool already_loaded = std::any_of(m_modules_storage.begin(), m_modules_storage.end(), [path](const std::unique_ptr& debug) { return debug->GetFilename() == path; }); + if (!path.empty() && !already_loaded) + { + m_modules_storage.emplace_back(std::move(new XMLDebuggerModuleConfig(path, { module->moduleName2, module->patchCRC, module, false }))); + } +} + +DebuggerWindow2::DebuggerWindow2(wxFrame& parent, const wxRect& display_size) + : wxFrame(&parent, wxID_ANY, wxT("PPC Debugger"), wxDefaultPosition, wxSize(1280, 300), wxMINIMIZE_BOX | wxMAXIMIZE_BOX | wxSYSTEM_MENU | wxCAPTION | wxCLOSE_BOX | wxCLIP_CHILDREN | wxRESIZE_BORDER | wxFRAME_FLOAT_ON_PARENT), + m_module_address(0) +{ + this->wxWindowBase::SetBackgroundColour(*wxWHITE); + + const auto file = ActiveSettings::GetPath("debugger/config.xml"); + m_config.SetFilename(file.generic_wstring()); + m_config.Load(); + + debuggerState.breakOnEntry = m_config.data().break_on_start; + + m_main_position = parent.GetPosition(); + m_main_size = parent.GetSize(); + + auto y = std::max(300, (display_size.GetHeight() - 500 - 300) * 0.8); + this->SetSize(1280, y); + + CreateMenuBar(); + CreateToolBar(); + + wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL); + + // load configs for already loaded modules + const auto module_count = RPLLoader_GetModuleCount(); + const auto module_list = RPLLoader_GetModuleList(); + for (sint32 i = 0; i < module_count; i++) + { + const auto module = module_list[i]; + LoadModuleStorage(module); + } + + wxString label_text = _("> no modules loaded"); + if (module_count != 0) + { + RPLModule* current_rpl_module = RPLLoader_FindModuleByCodeAddr(MEMORY_CODEAREA_ADDR); + if (current_rpl_module) + label_text = wxString::Format("> %s", current_rpl_module->moduleName2.c_str()); + else + label_text = _("> unknown module"); + } + + m_module_label = new wxStaticText(this, wxID_ANY, label_text); + m_module_label->SetBackgroundColour(*wxWHITE); + m_module_label->SetForegroundColour(wxColour(0xFFbf52fe)); + main_sizer->Add(m_module_label, 0, wxEXPAND | wxALL, 5); + + m_disasm_ctrl = new DisasmCtrl(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxScrolledWindowStyle); + main_sizer->Add(m_disasm_ctrl, 1, wxEXPAND); + + this->SetSizer(main_sizer); + this->wxWindowBase::Layout(); + + m_register_window = new RegisterWindow(*this, m_main_position, m_main_size); + m_dump_window = new DumpWindow(*this, m_main_position, m_main_size); + m_breakpoint_window = new BreakpointWindow(*this, m_main_position, m_main_size); + m_module_window = new ModuleWindow(*this, m_main_position, m_main_size); + m_symbol_window = new SymbolWindow(*this, m_main_position, m_main_size); + + const bool value = m_config.data().pin_to_main; + m_config.data().pin_to_main = true; + OnParentMove(m_main_position, m_main_size); + m_config.data().pin_to_main = value; + + g_debugger_window = this; +} + +DebuggerWindow2::~DebuggerWindow2() +{ + debuggerState.breakOnEntry = false; + g_debugger_window = nullptr; + + // save configs for all modules that are still loaded + // doesn't delete breakpoints since that should (in the future) be done by unloading the rpl modules when exiting the current game + const auto module_count = RPLLoader_GetModuleCount(); + const auto module_list = RPLLoader_GetModuleList(); + for (sint32 i = 0; i < module_count; i++) + { + const auto module = module_list[i]; + if (module) + SaveModuleStorage(module, false); + } + + if (m_register_window && m_register_window->IsShown()) + m_register_window->Close(true); + + if (m_dump_window && m_dump_window->IsShown()) + m_dump_window->Close(true); + + if (m_breakpoint_window && m_breakpoint_window->IsShown()) + m_breakpoint_window->Close(true); + + if (m_module_window && m_module_window->IsShown()) + m_module_window->Close(true); + + if (m_symbol_window && m_symbol_window->IsShown()) + m_symbol_window->Close(true); + + m_config.Save(); +} + +void DebuggerWindow2::OnClose(wxCloseEvent& event) +{ + debuggerState.breakOnEntry = false; + + const wxCloseEvent parentEvent(wxEVT_DEBUGGER_CLOSE); + wxPostEvent(m_parent, parentEvent); + + event.Skip(); +} + +void DebuggerWindow2::OnMoveIP(wxCommandEvent& event) +{ + const auto ip = debuggerState.debugSession.instructionPointer; + UpdateModuleLabel(ip); + m_disasm_ctrl->CenterOffset(ip); +} + +void DebuggerWindow2::OnParentMove(const wxPoint& main_position, const wxSize& main_size) +{ + m_main_position = main_position; + m_main_size = main_size; + + if (!m_config.data().pin_to_main) + return; + + wxSize size(m_main_size.x, GetSize().GetHeight()); + SetSize(size); + + wxPoint position = m_main_position; + position.y -= size.y; + SetPosition(position); + + m_register_window->OnMainMove(main_position, main_size); + m_dump_window->OnMainMove(main_position, main_size); + m_breakpoint_window->OnMainMove(main_position, main_size); + m_module_window->OnMainMove(main_position, main_size); + m_symbol_window->OnMainMove(main_position, main_size); +} + +void DebuggerWindow2::OnNotifyModuleLoaded(wxCommandEvent& event) +{ + RPLModule* module = (RPLModule*)event.GetClientData(); + LoadModuleStorage(module); + m_module_window->OnGameLoaded(); + m_symbol_window->OnGameLoaded(); + m_disasm_ctrl->Init(); +} + +void DebuggerWindow2::OnNotifyModuleUnloaded(wxCommandEvent& event) +{ + RPLModule* module = (RPLModule*)event.GetClientData(); + SaveModuleStorage(module, true); + m_module_window->OnGameLoaded(); + m_symbol_window->OnGameLoaded(); + m_disasm_ctrl->Init(); +} + +void DebuggerWindow2::OnGameLoaded() +{ + m_disasm_ctrl->Init(); + + m_dump_window->OnGameLoaded(); + m_module_window->OnGameLoaded(); + m_breakpoint_window->OnGameLoaded(); + m_symbol_window->OnGameLoaded(); + + RPLModule* current_rpl_module = RPLLoader_FindModuleByCodeAddr(MEMORY_CODEAREA_ADDR); + if(current_rpl_module) + m_module_label->SetLabel(wxString::Format("> %s", current_rpl_module->moduleName2.c_str())); + + this->SendSizeEvent(); +} + +XMLDebuggerConfig& DebuggerWindow2::GetConfig() +{ + return m_config; +} + +bool DebuggerWindow2::Show(bool show) +{ + const bool result = wxFrame::Show(show); + + if (show) + { + m_register_window->Show(m_config.data().show_register); + m_dump_window->Show(m_config.data().show_dump); + m_breakpoint_window->Show(m_config.data().show_breakpoints); + m_module_window->Show(m_config.data().show_modules); + m_symbol_window->Show(m_config.data().show_symbols); + } + else + { + m_register_window->Show(false); + m_dump_window->Show(false); + m_breakpoint_window->Show(false); + m_module_window->Show(false); + m_symbol_window->Show(false); + } + + return result; +} + +std::wstring DebuggerWindow2::GetModuleStoragePath(std::string module_name, uint32_t crc_hash) const +{ + if (module_name.empty() || crc_hash == 0) return std::wstring(); + return ActiveSettings::GetPath("debugger/{}_{:#10x}.xml", module_name, crc_hash).generic_wstring(); +} + +void DebuggerWindow2::OnBreakpointHit(wxCommandEvent& event) +{ + const auto ip = debuggerState.debugSession.instructionPointer; + UpdateModuleLabel(ip); + + m_toolbar->SetToolShortHelp(TOOL_ID_PAUSE, _("Run (F5)")); + m_toolbar->SetToolNormalBitmap(TOOL_ID_PAUSE, m_run); + + m_toolbar->EnableTool(TOOL_ID_STEP_INTO, true); + m_toolbar->EnableTool(TOOL_ID_STEP_OVER, true); + + m_disasm_ctrl->CenterOffset(ip); +} + +void DebuggerWindow2::OnRunProgram(wxCommandEvent& event) +{ + m_toolbar->SetToolShortHelp(TOOL_ID_PAUSE, _("Break (F5)")); + m_toolbar->SetToolNormalBitmap(TOOL_ID_PAUSE, m_pause); + + m_toolbar->EnableTool(TOOL_ID_STEP_INTO, false); + m_toolbar->EnableTool(TOOL_ID_STEP_OVER, false); +} + +void DebuggerWindow2::OnToolClicked(wxCommandEvent& event) +{ + switch(event.GetId()) + { + case TOOL_ID_GOTO: + m_disasm_ctrl->GoToAddressDialog(); + break; + case TOOL_ID_PAUSE: + if (debugger_isTrapped()) + debugger_resume(); + else + debugger_forceBreak(); + break; + case TOOL_ID_STEP_INTO: + if (debugger_isTrapped()) + debuggerState.debugSession.stepInto = true; + break; + case TOOL_ID_STEP_OVER: + if (debugger_isTrapped()) + debuggerState.debugSession.stepOver = true; + break; + } +} + +void DebuggerWindow2::OnBreakpointChange(wxCommandEvent& event) +{ + m_breakpoint_window->OnUpdateView(); + UpdateModuleLabel(); +} + +void DebuggerWindow2::OnOptionsInput(wxCommandEvent& event) +{ + switch(event.GetId()) + { + case MENU_ID_OPTIONS_PIN_TO_MAINWINDOW: + { + const bool value = !m_config.data().pin_to_main; + m_config.data().pin_to_main = value; + if(value) + OnParentMove(m_main_position, m_main_size); + + break; + } + case MENU_ID_OPTIONS_BREAK_ON_START: + { + const bool value = !m_config.data().break_on_start; + m_config.data().break_on_start = value; + debuggerState.breakOnEntry = value; + break; + } + default: + return; + } + + m_config.Save(); +} + +void DebuggerWindow2::OnWindowMenu(wxCommandEvent& event) +{ + switch (event.GetId()) + { + case MENU_ID_WINDOW_REGISTERS: + { + const bool value = !m_config.data().show_register; + m_config.data().show_register = value; + m_register_window->Show(value); + break; + } + case MENU_ID_WINDOW_DUMP: + { + const bool value = !m_config.data().show_dump; + m_config.data().show_dump = value; + m_dump_window->Show(value); + break; + } + case MENU_ID_WINDOW_BREAKPOINTS: + { + const bool value = !m_config.data().show_breakpoints; + m_config.data().show_breakpoints = value; + m_breakpoint_window->Show(value); + break; + } + case MENU_ID_WINDOW_MODULE: + { + const bool value = !m_config.data().show_modules; + m_config.data().show_modules = value; + m_module_window->Show(value); + break; + } + case MENU_ID_WINDOW_SYMBOL: + { + const bool value = !m_config.data().show_symbols; + m_config.data().show_symbols = value; + m_symbol_window->Show(value); + break; + } + default: + return; + } + + m_config.Save(); +} + +void DebuggerWindow2::OnUpdateView(wxCommandEvent& event) +{ + UpdateModuleLabel(); + m_disasm_ctrl->OnUpdateView(); + m_register_window->OnUpdateView(); + m_breakpoint_window->OnUpdateView(); +} + +void DebuggerWindow2::OnExit(wxCommandEvent& event) +{ + this->Close(); +} + +void DebuggerWindow2::OnShow(wxShowEvent& event) +{ + m_register_window->Show(); + m_dump_window->Show(); + m_breakpoint_window->Show(); + m_module_window->Show(); + m_symbol_window->Show(); + event.Skip(); +} + +void DebuggerWindow2::CreateMenuBar() +{ + auto menu_bar = new wxMenuBar; + + // file + wxMenu* file_menu = new wxMenu; + file_menu->Append(MENU_ID_FILE_EXIT, _("&Exit")); + file_menu->Bind(wxEVT_MENU, &DebuggerWindow2::OnExit, this); + + menu_bar->Append(file_menu, _("&File")); + + // options + wxMenu* options_menu = new wxMenu; + options_menu->Append(MENU_ID_OPTIONS_PIN_TO_MAINWINDOW, _("&Pin to main window"), wxEmptyString, wxITEM_CHECK)->Check(m_config.data().pin_to_main); + options_menu->Append(MENU_ID_OPTIONS_BREAK_ON_START, _("Break on &entry point"), wxEmptyString, wxITEM_CHECK)->Check(m_config.data().break_on_start); + menu_bar->Append(options_menu, _("&Options")); + + // window + wxMenu* window_menu = new wxMenu; + window_menu->Append(MENU_ID_WINDOW_REGISTERS, _("&Registers"), wxEmptyString, wxITEM_CHECK)->Check(m_config.data().show_register); + window_menu->Append(MENU_ID_WINDOW_DUMP, _("&Memory Dump"), wxEmptyString, wxITEM_CHECK)->Check(m_config.data().show_dump); + window_menu->Append(MENU_ID_WINDOW_BREAKPOINTS, _("&Breakpoints"), wxEmptyString, wxITEM_CHECK)->Check(m_config.data().show_breakpoints); + window_menu->Append(MENU_ID_WINDOW_MODULE, _("Module&list"), wxEmptyString, wxITEM_CHECK)->Check(m_config.data().show_modules); + window_menu->Append(MENU_ID_WINDOW_SYMBOL, _("&Symbols"), wxEmptyString, wxITEM_CHECK)->Check(m_config.data().show_symbols); + + menu_bar->Append(window_menu, _("&Window")); + + SetMenuBar(menu_bar); + + options_menu->Bind(wxEVT_MENU, &DebuggerWindow2::OnOptionsInput, this); + window_menu->Bind(wxEVT_MENU, &DebuggerWindow2::OnWindowMenu, this); +} + +void DebuggerWindow2::UpdateModuleLabel(uint32 address) +{ + if(address == 0) + address = m_disasm_ctrl->GetViewBaseAddress(); + + RPLModule* module = RPLLoader_FindModuleByCodeAddr(address); + if (module && m_module_address != module->regionMappingBase_text.GetMPTR()) + { + m_module_label->SetLabel(wxString::Format("> %s", module->moduleName2.c_str())); + m_module_address = module->regionMappingBase_text.GetMPTR(); + } + else if (address >= mmuRange_CODECAVE.getBase() && address < mmuRange_CODECAVE.getEnd()) + { + m_module_label->SetLabel(wxString::Format("> %s", "Cemu codecave")); + m_module_address = mmuRange_CODECAVE.getBase(); + } +} \ No newline at end of file diff --git a/src/gui/debugger/DebuggerWindow2.h b/src/gui/debugger/DebuggerWindow2.h new file mode 100644 index 00000000..bae41196 --- /dev/null +++ b/src/gui/debugger/DebuggerWindow2.h @@ -0,0 +1,116 @@ +#pragma once + +#include "gui/debugger/DisasmCtrl.h" +#include "config/XMLConfig.h" +#include "Cafe/HW/Espresso/Debugger/Debugger.h" +#include "Cafe/OS/RPL/rpl.h" + +#include +#include + +class BreakpointWindow; +class RegisterWindow; +class DumpWindow; +class ModuleWindow; +class SymbolWindow; +class wxStaticText; + +wxDECLARE_EVENT(wxEVT_UPDATE_VIEW, wxCommandEvent); +wxDECLARE_EVENT(wxEVT_BREAKPOINT_HIT, wxCommandEvent); +wxDECLARE_EVENT(wxEVT_RUN, wxCommandEvent); +wxDECLARE_EVENT(wxEVT_BREAKPOINT_CHANGE, wxCommandEvent); +wxDECLARE_EVENT(wxEVT_MOVE_IP, wxCommandEvent); +wxDECLARE_EVENT(wxEVT_NOTIFY_MODULE_LOADED, wxCommandEvent); +wxDECLARE_EVENT(wxEVT_NOTIFY_MODULE_UNLOADED, wxCommandEvent); + +struct DebuggerConfig +{ + DebuggerConfig() + : pin_to_main(true), break_on_start(true), show_register(true), show_dump(true), show_stack(true), show_breakpoints(true), show_modules(true) {} + + bool pin_to_main; + + bool break_on_start; + + bool show_register; + bool show_dump; + bool show_stack; + bool show_breakpoints; + bool show_modules; + bool show_symbols; + + void Load(XMLConfigParser& parser); + void Save(XMLConfigParser& parser); +}; +typedef XMLDataConfig XMLDebuggerConfig; + +struct DebuggerModuleStorage +{ + std::string module_name; + uint32_t crc_hash; + const RPLModule* rpl_module; + bool delete_breakpoints_after_saving; + + void Load(XMLConfigParser& parser); + void Save(XMLConfigParser& parser); +}; +typedef XMLDataConfig XMLDebuggerModuleConfig; + +class DebuggerWindow2 : public wxFrame +{ +public: + void CreateToolBar(); + void LoadModuleStorage(const RPLModule* module); + void SaveModuleStorage(const RPLModule* module, bool delete_breakpoints); + DebuggerWindow2(wxFrame& parent, const wxRect& display_size); + ~DebuggerWindow2(); + + void OnParentMove(const wxPoint& position, const wxSize& size); + void OnGameLoaded(); + + XMLDebuggerConfig& GetConfig(); + + bool Show(bool show = true) override; + std::wstring GetModuleStoragePath(std::string module_name, uint32_t crc_hash) const; +private: + void OnBreakpointHit(wxCommandEvent& event); + void OnRunProgram(wxCommandEvent& event); + void OnToolClicked(wxCommandEvent& event); + void OnBreakpointChange(wxCommandEvent& event); + void OnOptionsInput(wxCommandEvent& event); + void OnWindowMenu(wxCommandEvent& event); + void OnUpdateView(wxCommandEvent& event); + void OnExit(wxCommandEvent& event); + void OnShow(wxShowEvent& event); + void OnClose(wxCloseEvent& event); + void OnMoveIP(wxCommandEvent& event); + void OnNotifyModuleLoaded(wxCommandEvent& event); + void OnNotifyModuleUnloaded(wxCommandEvent& event); + + void CreateMenuBar(); + void UpdateModuleLabel(uint32 address = 0); + + XMLDebuggerConfig m_config; + std::vector> m_modules_storage; + + wxPoint m_main_position; + wxSize m_main_size; + + RegisterWindow* m_register_window; + DumpWindow* m_dump_window; + BreakpointWindow* m_breakpoint_window; + ModuleWindow* m_module_window; + SymbolWindow* m_symbol_window; + + DisasmCtrl* m_disasm_ctrl; + + wxToolBar* m_toolbar; + wxBitmap m_run, m_pause; + + uint32 m_module_address; + wxStaticText* m_module_label; + + +wxDECLARE_EVENT_TABLE(); +}; + diff --git a/src/gui/debugger/DisasmCtrl.cpp b/src/gui/debugger/DisasmCtrl.cpp new file mode 100644 index 00000000..dededf2c --- /dev/null +++ b/src/gui/debugger/DisasmCtrl.cpp @@ -0,0 +1,806 @@ +#include "gui/wxgui.h" +#include "gui/debugger/DisasmCtrl.h" + +#include "Cafe/OS/RPL/rpl_structs.h" +#include "Cafe/OS/RPL/rpl.h" +#include "Cafe/OS/RPL/rpl_symbol_storage.h" +#include "Cafe/OS/RPL/rpl_debug_symbols.h" +#include "Cemu/PPCAssembler/ppcAssembler.h" +#include "Cafe/HW/Espresso/Debugger/Debugger.h" +#include "gui/debugger/DebuggerWindow2.h" +#include "util/helpers/helpers.h" +#include "gui/guiWrapper.h" + +#include "Cemu/ExpressionParser/ExpressionParser.h" +#include "Cafe/HW/Espresso/Debugger/DebugSymbolStorage.h" +#include // for wxMemoryInputStream + +#define MAX_SYMBOL_LEN (120) + +#define COLOR_DEBUG_ACTIVE_BP 0xFFFFA0FF +#define COLOR_DEBUG_ACTIVE 0xFFFFA080 +#define COLOR_DEBUG_BP 0xFF8080FF + +#define SYNTAX_COLOR_GPR 0xFF000066 +#define SYNTAX_COLOR_FPR 0xFF006666 +#define SYNTAX_COLOR_SPR 0xFF666600 +#define SYNTAX_COLOR_CR 0xFF666600 +#define SYNTAX_COLOR_IMM 0xFF006600 +#define SYNTAX_COLOR_IMM_OFFSET 0xFF006600 +#define SYNTAX_COLOR_CIMM 0xFF880000 +#define SYNTAX_COLOR_PSEUDO 0xFFA0A0A0 // color for pseudo code +#define SYNTAX_COLOR_SYMBOL 0xFF0000A0 // color for function symbol + +#define OFFSET_ADDRESS (60) +#define OFFSET_ADDRESS_RELATIVE (90) +#define OFFSET_DISASSEMBLY (300) + +#define OFFSET_DISASSEMBLY_OPERAND (80) + +wxBitmap* g_ipArrowBitmap = nullptr; + +uint8 _arrowRightPNG[] = +{ + 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, + 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0B, + 0x08, 0x03, 0x00, 0x00, 0x00, 0x41, 0x3C, 0xFD, 0x0B, 0x00, 0x00, 0x00, + 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE, 0xCE, 0x1C, 0xE9, 0x00, 0x00, + 0x00, 0x04, 0x67, 0x41, 0x4D, 0x41, 0x00, 0x00, 0xB1, 0x8F, 0x0B, 0xFC, + 0x61, 0x05, 0x00, 0x00, 0x00, 0x06, 0x50, 0x4C, 0x54, 0x45, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xA5, 0x67, 0xB9, 0xCF, 0x00, 0x00, 0x00, 0x02, + 0x74, 0x52, 0x4E, 0x53, 0xFF, 0x00, 0xE5, 0xB7, 0x30, 0x4A, 0x00, 0x00, + 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0E, 0xC3, 0x00, 0x00, + 0x0E, 0xC3, 0x01, 0xC7, 0x6F, 0xA8, 0x64, 0x00, 0x00, 0x00, 0x2C, 0x49, + 0x44, 0x41, 0x54, 0x18, 0x57, 0x63, 0x60, 0x84, 0x03, 0x08, 0x13, 0x59, + 0x00, 0xCC, 0x46, 0x11, 0x00, 0x71, 0x80, 0x24, 0x32, 0xC0, 0x10, 0x60, + 0xC0, 0x10, 0xC0, 0x00, 0x58, 0xCC, 0x80, 0xD8, 0x00, 0x02, 0x60, 0x3E, + 0x7E, 0x77, 0x00, 0x31, 0x23, 0x23, 0x00, 0x21, 0x95, 0x00, 0x5B, 0x20, + 0x73, 0x8D, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, + 0x42, 0x60, 0x82 +}; + +DisasmCtrl::DisasmCtrl(wxWindow* parent, const wxWindowID& id, const wxPoint& pos, const wxSize& size, long style) + : TextList(parent, id, pos, size, style), m_mouse_line(-1), m_mouse_line_drawn(-1), m_active_line(-1) +{ + Init(); + + if (!g_ipArrowBitmap) + { + wxMemoryInputStream strm(_arrowRightPNG, sizeof(_arrowRightPNG)); + wxImage img(strm, wxBITMAP_TYPE_PNG); + g_ipArrowBitmap = new wxBitmap(img); + } + + auto tooltip_sizer = new wxBoxSizer(wxVERTICAL); + tooltip_sizer->Add(new wxStaticText(m_tooltip_window, wxID_ANY, wxEmptyString), 0, wxALL, 5); + m_tooltip_window->SetSizer(tooltip_sizer); +} + +void DisasmCtrl::Init() +{ + SelectCodeRegion(mmuRange_TEXT_AREA.getBase()); +} + +void DisasmCtrl::SelectCodeRegion(uint32 newAddress) +{ + if (newAddress >= mmuRange_TEXT_AREA.getBase() && newAddress < mmuRange_TEXT_AREA.getEnd()) + { + currentCodeRegionStart = MEMORY_CODEAREA_ADDR; + currentCodeRegionEnd = RPLLoader_GetMaxCodeOffset(); + currentCodeRegionEnd = std::max(currentCodeRegionEnd, currentCodeRegionStart + 0x1000); + } + MMURange* mmuRange = memory_getMMURangeByAddress(newAddress); + if (mmuRange) + { + currentCodeRegionStart = mmuRange->getBase(); + currentCodeRegionEnd = mmuRange->getEnd(); + } + else + { + currentCodeRegionStart = 0; + currentCodeRegionEnd = 0; + } + + // update line tracking + sint32 element_count = currentCodeRegionEnd - currentCodeRegionStart; + if (element_count <= 0x00010000) + element_count = 0x00010000; + + if (this->SetElementCount(element_count / 4)) + { + Scroll(0, 0); + RefreshControl(); + } +} + +void DisasmCtrl::DrawDisassemblyLine(wxDC& dc, const wxPoint& linePosition, MPTR virtualAddress, RPLModule* rplModule) +{ + wxPoint position = linePosition; + + bool hasPatch = debugger_hasPatch(virtualAddress); + + PPCDisassembledInstruction disasmInstr; + + const DebuggerBreakpoint* bp = debugger_getFirstBP(virtualAddress); + while (bp) + { + if (bp->isExecuteBP() && bp->enabled) + break; + bp = bp->next; + } + + uint32 opcode; + + if (bp) + opcode = bp->originalOpcodeValue; + else + opcode = memory_readU32(virtualAddress); + + ppcAssembler_disassemble(virtualAddress, opcode, &disasmInstr); + + const bool is_active_bp = debuggerState.debugSession.isTrapped && debuggerState.debugSession.instructionPointer == virtualAddress; + + // write virtual address + wxColour background_colour; + if (is_active_bp && bp != nullptr) + background_colour = wxColour(0xFFFFA0FF); + else if (is_active_bp) + background_colour = wxColour(0xFF80A0FF); + else if (bp != nullptr) + background_colour = wxColour(0xFF8080FF); + else if(virtualAddress == m_lastGotoTarget) + background_colour = wxColour(0xFFE0E0E0); + else + background_colour = wxColour(COLOR_WHITE); + + DrawLineBackground(dc, position, background_colour); + + dc.SetTextForeground(COLOR_BLACK); + dc.DrawText(wxString::Format("%08x", virtualAddress), position); + position.x += OFFSET_ADDRESS; + + dc.SetTextForeground(COLOR_GREY); + if (rplModule) + dc.DrawText(wxString::Format("+0x%-8x", virtualAddress - rplModule->regionMappingBase_text.GetMPTR()), position); + else + dc.DrawText("???", position); + + position.x += OFFSET_ADDRESS_RELATIVE; + + // draw arrow to clearly indicate instruction pointer + if(is_active_bp) + dc.DrawBitmap(*g_ipArrowBitmap, wxPoint(position.x - 24, position.y + 2), false); + + // handle data symbols + auto debugSymbolDataType = DebugSymbolStorage::GetDataType(virtualAddress); + if (debugSymbolDataType == DEBUG_SYMBOL_TYPE::FLOAT) + { + dc.SetTextForeground(hasPatch ? wxColour(0xFF2020FF) : wxColour(0xFF400000)); + dc.DrawText(fmt::format(".float"), position); + + position.x += OFFSET_DISASSEMBLY_OPERAND; + dc.SetTextForeground(hasPatch ? wxColour(0xFF2020FF) : wxColour(SYNTAX_COLOR_IMM)); + dc.DrawText(fmt::format("{}", memory_readFloat(virtualAddress)), position); + + return; + } + else if (debugSymbolDataType == DEBUG_SYMBOL_TYPE::U32) + { + dc.SetTextForeground(hasPatch ? wxColour(0xFF2020FF) : wxColour(0xFF400000)); + dc.DrawText(fmt::format(".uint"), position); + + position.x += OFFSET_DISASSEMBLY_OPERAND; + dc.SetTextForeground(hasPatch ? wxColour(0xFF2020FF) : wxColour(SYNTAX_COLOR_IMM)); + dc.DrawText(fmt::format("{}", memory_readU32(virtualAddress)), position); + + return; + } + + sint32 start_width = position.x; + dc.SetTextForeground(hasPatch ? wxColour(0xFF2020FF) : wxColour(0xFF400000)); + char opName[32]; + strcpy(opName, ppcAssembler_getInstructionName(disasmInstr.ppcAsmCode)); + std::transform(opName, opName + sizeof(opName), opName, tolower); + dc.DrawText(wxString::Format("%-12s", opName), position); + position.x += OFFSET_DISASSEMBLY_OPERAND; + + bool isRLWINM = disasmInstr.ppcAsmCode == PPCASM_OP_RLWINM || disasmInstr.ppcAsmCode == PPCASM_OP_RLWINM_ || + disasmInstr.ppcAsmCode == PPCASM_OP_CLRLWI || disasmInstr.ppcAsmCode == PPCASM_OP_CLRLWI_ || + disasmInstr.ppcAsmCode == PPCASM_OP_CLRRWI || disasmInstr.ppcAsmCode == PPCASM_OP_CLRRWI_ || + disasmInstr.ppcAsmCode == PPCASM_OP_EXTLWI || disasmInstr.ppcAsmCode == PPCASM_OP_EXTLWI_ || + disasmInstr.ppcAsmCode == PPCASM_OP_EXTRWI || disasmInstr.ppcAsmCode == PPCASM_OP_EXTRWI_ || + disasmInstr.ppcAsmCode == PPCASM_OP_SLWI || disasmInstr.ppcAsmCode == PPCASM_OP_SLWI_ || + disasmInstr.ppcAsmCode == PPCASM_OP_SRWI || disasmInstr.ppcAsmCode == PPCASM_OP_SRWI_ || + disasmInstr.ppcAsmCode == PPCASM_OP_ROTRWI || disasmInstr.ppcAsmCode == PPCASM_OP_ROTRWI_ || + disasmInstr.ppcAsmCode == PPCASM_OP_ROTLWI || disasmInstr.ppcAsmCode == PPCASM_OP_ROTLWI_; + bool forceDecDisplay = isRLWINM; + + if (disasmInstr.ppcAsmCode == PPCASM_OP_UKN) + { + // show raw bytes + WriteText(dc, wxString::Format("%02x %02x %02x %02x", (opcode >> 24) & 0xFF, (opcode >> 16) & 0xFF, (opcode >> 8) & 0xFF, (opcode >> 0) & 0xFF), position, SYNTAX_COLOR_PSEUDO); + } + + bool is_first_operand = true; + for (sint32 o = 0; o < PPCASM_OPERAND_COUNT; o++) + { + if (((disasmInstr.operandMask >> o) & 1) == 0) + continue; + + if (!is_first_operand) + WriteText(dc, ", ", position, COLOR_BLACK); + + is_first_operand = false; + switch (disasmInstr.operand[o].type) + { + case PPCASM_OPERAND_TYPE_GPR: + WriteText(dc, wxString::Format("r%d", disasmInstr.operand[o].registerIndex), position, SYNTAX_COLOR_GPR); + break; + + case PPCASM_OPERAND_TYPE_FPR: + WriteText(dc, wxString::Format("f%d", disasmInstr.operand[o].registerIndex), position, SYNTAX_COLOR_FPR); + break; + + case PPCASM_OPERAND_TYPE_SPR: + WriteText(dc, wxString::Format("spr%d", disasmInstr.operand[o].registerIndex), position, SYNTAX_COLOR_SPR); + break; + + case PPCASM_OPERAND_TYPE_CR: + WriteText(dc, wxString::Format("cr%d", disasmInstr.operand[o].registerIndex), position, SYNTAX_COLOR_CR); + break; + + case PPCASM_OPERAND_TYPE_IMM: + { + wxString string; + if (disasmInstr.operand[o].isSignedImm) + { + sint32 sImm = disasmInstr.operand[o].immS32; + if (disasmInstr.operand[o].immWidth == 16 && (sImm & 0x8000)) + sImm |= 0xFFFF0000; + + if ((sImm > -10 && sImm < 10) || forceDecDisplay) + string = wxString::Format("%d", sImm); + else + { + if (sImm < 0) + string = wxString::Format("-0x%x", -sImm); + else + string = wxString::Format("0x%x", sImm); + } + } + else + { + uint32 uImm = disasmInstr.operand[o].immS32; + if ((uImm >= 0 && uImm < 10) || forceDecDisplay) + string = wxString::Format("%u", uImm); + else + string = wxString::Format("0x%x", uImm); + } + + + WriteText(dc, string, position, SYNTAX_COLOR_IMM); + break; + } + case PPCASM_OPERAND_TYPE_PSQMODE: + { + if (disasmInstr.operand[o].immS32) + WriteText(dc, "single", position, SYNTAX_COLOR_IMM); + else + WriteText(dc, "paired", position, SYNTAX_COLOR_IMM); + break; + } + case PPCASM_OPERAND_TYPE_CIMM: + { + wxString string; + // use symbol for function calls if available + uint32 callDest = disasmInstr.operand[o].immU32; + RPLStoredSymbol* storedSymbol = nullptr; + if (disasmInstr.ppcAsmCode == PPCASM_OP_BL || disasmInstr.ppcAsmCode == PPCASM_OP_BLA) + storedSymbol = rplSymbolStorage_getByAddress(callDest); + + if (storedSymbol) + { + // if symbol is within same module then don't display libname prefix + RPLModule* rplModuleCurrent = RPLLoader_FindModuleByCodeAddr(virtualAddress); // cache this + if (rplModuleCurrent && callDest >= rplModuleCurrent->regionMappingBase_text.GetMPTR() && callDest < (rplModuleCurrent->regionMappingBase_text.GetMPTR() + rplModuleCurrent->regionSize_text)) + string = wxString((char*)storedSymbol->symbolName); + else + string = wxString::Format("%s.%s", (char*)storedSymbol->libName, (char*)storedSymbol->symbolName); + + // truncate name after 36 characters + if (string.Length() >= 36) + { + string.Truncate(34); + string.Append(".."); + } + } + else + { + string = wxString::Format("0x%08x", disasmInstr.operand[o].immU32); + } + + WriteText(dc, string, position, SYNTAX_COLOR_CIMM); + + if (disasmInstr.ppcAsmCode != PPCASM_OP_BL) + { + wxString x = wxString(" "); + if (callDest <= virtualAddress) + x.Append(wxUniChar(0x2191)); // arrow up + else + x.Append(wxUniChar(0x2193)); // arrow down + + WriteText(dc, x, position, COLOR_BLACK); + } + + break; + } + case PPCASM_OPERAND_TYPE_MEM: + { + // offset + wxString string; + if (disasmInstr.operand[o].isSignedImm && disasmInstr.operand[o].immS32 >= 0) + string = wxString::Format("+0x%x", disasmInstr.operand[o].immS32); + else + string = wxString::Format("-0x%x", -disasmInstr.operand[o].immS32); + + WriteText(dc, string, position, SYNTAX_COLOR_IMM_OFFSET); + WriteText(dc, "(", position, COLOR_BLACK); + + // register + WriteText(dc, wxString::Format("r%d", disasmInstr.operand[o].registerIndex), position, SYNTAX_COLOR_GPR); + WriteText(dc, ")", position, COLOR_BLACK); + break; + } + default: + // TODO + WriteText(dc, "", position, wxColour(0xFF444444)); + } + } + + position.x = start_width + OFFSET_DISASSEMBLY; + const auto comment = static_cast(rplDebugSymbol_getForAddress(virtualAddress)); + if (comment && comment->type == RplDebugSymbolComment) + WriteText(dc, comment->comment, position, COLOR_BLACK); + else if (isRLWINM) + { + sint32 rS, rA, SH, MB, ME; + rS = (opcode >> 21) & 0x1f; + rA = (opcode >> 16) & 0x1f; + SH = (opcode >> 11) & 0x1f; + MB = (opcode >> 6) & 0x1f; + ME = (opcode >> 1) & 0x1f; + + uint32 mask = ppcAssembler_generateMaskRLW(MB, ME); + + wxString string; + if (SH == 0) + string = wxString::Format("r%d=r%d&0x%x", rA, rS, mask); + else if ((0xFFFFFFFF << (uint32)disasmInstr.operand[2].immS32) == mask) + string = wxString::Format("r%d=r%d<<%d", rA, rS, SH); + else if ((0xFFFFFFFF >> (32 - SH) == mask)) + string = wxString::Format("r%d=r%d>>%d", rA, rS, 32 - SH); + else + string = wxString::Format("r%d=(r%d<<<%d)&0x%x", rA, rS, SH, mask); + WriteText(dc, string, position, COLOR_GREY); + } + else if (disasmInstr.ppcAsmCode == PPCASM_OP_SUBF || disasmInstr.ppcAsmCode == PPCASM_OP_SUBF_) + { + sint32 rD, rA, rB; + rD = (opcode >> 21) & 0x1f; + rA = (opcode >> 16) & 0x1f; + rB = (opcode >> 11) & 0x1f; + + wxString string; + string = wxString::Format("r%d=r%d-r%d", rD, rB, rA); + WriteText(dc, string, position, COLOR_GREY); + } +} + +void DisasmCtrl::DrawLabelName(wxDC& dc, const wxPoint& linePosition, MPTR virtualAddress, RPLStoredSymbol* storedSymbol) +{ + wxString symbol_string = wxString::Format("%s:", (char*)storedSymbol->symbolName); + if (symbol_string.Length() > MAX_SYMBOL_LEN) + { + symbol_string.Truncate(MAX_SYMBOL_LEN - 3); + symbol_string.Append("..:"); + } + wxPoint tmpPos(linePosition); + WriteText(dc, symbol_string, tmpPos, SYNTAX_COLOR_SYMBOL); +} + +void DisasmCtrl::OnDraw(wxDC& dc, sint32 start, sint32 count, const wxPoint& start_position) +{ + wxPoint position(0, 0); + + RPLModule* current_rpl_module = RPLLoader_FindModuleByCodeAddr(GetViewBaseAddress()); + + if (currentCodeRegionStart == currentCodeRegionEnd) + return; + + sint32 viewFirstLine = GetViewStart().y; + sint32 lineOffset = start - viewFirstLine; + + cemu_assert_debug(lineOffset >= 0); + + sint32 instructionIndex = 0; + sint32 numLinesToUpdate = lineOffset + count; + numLinesToUpdate = std::min(numLinesToUpdate, (sint32)m_elements_visible); + + if(m_lineToAddress.size() != m_elements_visible) + m_lineToAddress.resize(m_elements_visible); + + sint32 lineIndex = 0; + while(lineIndex < numLinesToUpdate) + { + const uint32 virtualAddress = GetViewBaseAddress() + instructionIndex * 4; + instructionIndex++; + + if (virtualAddress < currentCodeRegionStart || + virtualAddress >= currentCodeRegionEnd) + { + NextLine(position, &start_position); + m_lineToAddress[lineIndex] = std::nullopt; + lineIndex++; + continue; + } + + // check if valid memory address + if (!memory_isAddressRangeAccessible(virtualAddress, 4)) + { + NextLine(position, &start_position); + m_lineToAddress[lineIndex] = std::nullopt; + lineIndex++; + continue; + } + + // draw symbol as label + RPLStoredSymbol* storedSymbol = rplSymbolStorage_getByAddress(virtualAddress); + if (storedSymbol) + { + if(lineIndex >= lineOffset) + DrawLabelName(dc, position, virtualAddress, storedSymbol); + m_lineToAddress[lineIndex] = virtualAddress; + lineIndex++; + if (lineIndex >= numLinesToUpdate) + break; + NextLine(position, &start_position); + } + m_lineToAddress[lineIndex] = virtualAddress; + if (lineIndex >= lineOffset) + DrawDisassemblyLine(dc, position, virtualAddress, current_rpl_module); + NextLine(position, &start_position); + lineIndex++; + } + + // draw vertical separator lines: offset | disassembly | comment + dc.SetPen(*wxLIGHT_GREY_PEN); + + wxPoint line_from = start_position; + line_from.x = OFFSET_ADDRESS + OFFSET_ADDRESS_RELATIVE - 5; + wxPoint line_to = line_from; + line_to.y += (count + 1) * m_line_height; + dc.DrawLine(line_from, line_to); + + line_from.x += OFFSET_DISASSEMBLY; + line_to.x += OFFSET_DISASSEMBLY; + dc.DrawLine(line_from, line_to); +} + +void DisasmCtrl::OnMouseMove(const wxPoint& start_position, uint32 line) +{ + /*m_mouse_line = line; + if (m_mouse_line_drawn != -1) + RefreshLine(m_mouse_line_drawn); + if (m_mouse_line != -1) + RefreshLine(m_mouse_line);*/ + + wxPoint position = start_position; + + // address + if (position.x <= OFFSET_ADDRESS) + { + return; + } + + position.x -= OFFSET_ADDRESS; + + // relative offset + if (position.x <= OFFSET_ADDRESS_RELATIVE) + { + return; + } + + position.x -= OFFSET_ADDRESS_RELATIVE; + + // disassembly code + if (position.x <= OFFSET_DISASSEMBLY) + { + if(m_mouse_down) + { + + } + + if (position.x <= OFFSET_DISASSEMBLY_OPERAND) + { + return; + } + + position.x -= OFFSET_DISASSEMBLY_OPERAND; + } +} + +void DisasmCtrl::OnKeyPressed(sint32 key_code, const wxPoint& position) +{ + auto optVirtualAddress = LinePixelPosToAddress(position.y); + switch (key_code) + { + case WXK_F9: + { + if (optVirtualAddress) + { + debugger_toggleExecuteBreakpoint(*optVirtualAddress); + + RefreshControl(); + + wxCommandEvent evt(wxEVT_BREAKPOINT_CHANGE); + wxPostEvent(this->m_parent, evt); + } + return; + } + case 'G': + { + if(IsKeyDown(WXK_CONTROL)) + { + GoToAddressDialog(); + return; + } + } + } + + + // debugger currently in break state + if (debuggerState.debugSession.isTrapped) + { + switch (key_code) + { + case WXK_F5: + { + debuggerState.debugSession.run = true; + return; + } + case WXK_F10: + { + debuggerState.debugSession.stepOver = true; + return; + } + case WXK_F11: + { + debuggerState.debugSession.stepInto = true; + } + } + } + else + { + switch (key_code) + { + case WXK_F5: + { + debuggerState.debugSession.shouldBreak = true; + } + } + } +} + +void DisasmCtrl::OnMouseDClick(const wxPoint& position, uint32 line) +{ + wxPoint pos = position; + auto optVirtualAddress = LinePixelPosToAddress(position.y - GetViewStart().y * m_line_height); + if (!optVirtualAddress) + return; + MPTR virtualAddress = *optVirtualAddress; + + // address + if (pos.x <= OFFSET_ADDRESS + OFFSET_ADDRESS_RELATIVE) + { + // address + address relative + debugger_toggleExecuteBreakpoint(virtualAddress); + RefreshLine(line); + wxCommandEvent evt(wxEVT_BREAKPOINT_CHANGE); + wxPostEvent(this->m_parent, evt); + return; + } + else if (pos.x <= OFFSET_ADDRESS + OFFSET_ADDRESS_RELATIVE + OFFSET_DISASSEMBLY) + { + // double-clicked on disassembly (operation and operand data) + wxString currentInstruction = wxEmptyString; + wxTextEntryDialog set_value_dialog(this, _("Enter a new instruction."), _(wxString::Format("Overwrite instruction at address %08x", virtualAddress)), currentInstruction); + if (set_value_dialog.ShowModal() == wxID_OK) + { + PPCAssemblerInOut ctx = { 0 }; + ctx.virtualAddress = virtualAddress; + if (ppcAssembler_assembleSingleInstruction(set_value_dialog.GetValue().c_str(), &ctx)) + { + debugger_createPatch(virtualAddress, { ctx.outputData.data(), ctx.outputData.size() }); + RefreshLine(line); + } + } + return; + } + else + { + // comment + const auto comment = static_cast(rplDebugSymbol_getForAddress(virtualAddress)); + + wxString old_comment = wxEmptyString; + if (comment && comment->type == RplDebugSymbolComment) + old_comment = comment->comment; + + wxTextEntryDialog set_value_dialog(this, _("Enter a new comment."), _(wxString::Format("Create comment at address %08x", virtualAddress)), old_comment); + if (set_value_dialog.ShowModal() == wxID_OK) + { + rplDebugSymbol_createComment(virtualAddress, set_value_dialog.GetValue().wc_str()); + RefreshLine(line); + } + return; + } +} + +void DisasmCtrl::CopyToClipboard(std::string text) { +#if BOOST_OS_WINDOWS + if (OpenClipboard(nullptr)) + { + EmptyClipboard(); + const HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, text.size() + 1); + if (hGlobal) + { + memcpy(GlobalLock(hGlobal), text.c_str(), text.size() + 1); + GlobalUnlock(hGlobal); + + SetClipboardData(CF_TEXT, hGlobal); + GlobalFree(hGlobal); + } + CloseClipboard(); + } +#endif +} + +void DisasmCtrl::OnContextMenu(const wxPoint& position, uint32 line) +{ + wxPoint pos = position; + auto optVirtualAddress = LinePixelPosToAddress(position.y - GetViewStart().y * m_line_height); + if (!optVirtualAddress) + return; + MPTR virtualAddress = *optVirtualAddress; + + // address + if (pos.x <= OFFSET_ADDRESS + OFFSET_ADDRESS_RELATIVE) + { + CopyToClipboard(fmt::format("{:#10x}", virtualAddress)); + return; + } + else if (pos.x <= OFFSET_ADDRESS + OFFSET_ADDRESS_RELATIVE + OFFSET_DISASSEMBLY) + { + // double-clicked on disassembly (operation and operand data) + return; + } + else + { + // comment + return; + } +} + +bool DisasmCtrl::OnShowTooltip(const wxPoint& position, uint32 line) +{ + return false; +} + +void DisasmCtrl::ScrollWindow(int dx, int dy, const wxRect* prect) +{ + // scroll and repaint everything + RefreshControl(nullptr); + TextList::ScrollWindow(dx, dy, nullptr); +} + +wxSize DisasmCtrl::DoGetBestSize() const +{ + return TextList::DoGetBestSize(); +} + +void DisasmCtrl::OnUpdateView() +{ + RefreshControl(); +} + +uint32 DisasmCtrl::GetViewBaseAddress() const +{ + if (GetViewStart().y < 0) + return MPTR_NULL; + return currentCodeRegionStart + GetViewStart().y * 4; +} + +std::optional DisasmCtrl::LinePixelPosToAddress(sint32 posY) +{ + if (posY < 0) + return std::nullopt; + + + sint32 lineIndex = posY / m_line_height; + if (lineIndex >= m_lineToAddress.size()) + return std::nullopt; + + return m_lineToAddress[lineIndex]; +} + +uint32 DisasmCtrl::AddressToScrollPos(uint32 offset) const +{ + return (offset - currentCodeRegionStart) / 4; +} + +void DisasmCtrl::CenterOffset(uint32 offset) +{ + if (offset < currentCodeRegionStart || offset >= currentCodeRegionEnd) + SelectCodeRegion(offset); + + const sint32 line = AddressToScrollPos(offset); + if (line < 0 || line >= (sint32)m_element_count) + return; + + if (m_active_line != -1) + RefreshLine(m_active_line); + + DoScroll(0, std::max(0, line - (sint32)(m_elements_visible / 2))); + + m_active_line = line; + RefreshLine(m_active_line); + + debug_printf("scroll to %x\n", debuggerState.debugSession.instructionPointer); +} + +void DisasmCtrl::GoToAddressDialog() +{ + wxTextEntryDialog goto_dialog(this, _("Enter a target address."), _("GoTo address"), wxEmptyString); + if (goto_dialog.ShowModal() == wxID_OK) + { + ExpressionParser parser; + + auto value = goto_dialog.GetValue().ToStdString(); + std::transform(value.begin(), value.end(), value.begin(), tolower); + + const auto module_count = RPLLoader_GetModuleCount(); + const auto module_list = RPLLoader_GetModuleList(); + + std::vector module_tmp(module_count); + for (int i = 0; i < module_count; i++) + { + const auto module = module_list[i]; + if (module) + { + module_tmp[i] = (double)module->regionMappingBase_text.GetMPTR(); + parser.AddConstant(module->moduleName2, module_tmp[i]); + } + } + + double grp_tmp[32]; + PPCSnapshot& ppc_snapshot = debuggerState.debugSession.ppcSnapshot; + for (int i = 0; i < 32; i++) + { + char var_name[32]; + sprintf(var_name, "r%d", i); + grp_tmp[i] = ppc_snapshot.gpr[i]; + parser.AddConstant(var_name, grp_tmp[i]); + } + + try + { + const auto result = (uint32)parser.Evaluate(value); + debug_printf("goto eval result: %x\n", result); + m_lastGotoTarget = result; + CenterOffset(result); + debuggerWindow_updateViewThreadsafe2(); + } + catch (const std::exception& ) + { + } + } +} \ No newline at end of file diff --git a/src/gui/debugger/DisasmCtrl.h b/src/gui/debugger/DisasmCtrl.h new file mode 100644 index 00000000..993d5697 --- /dev/null +++ b/src/gui/debugger/DisasmCtrl.h @@ -0,0 +1,48 @@ +#pragma once +#include "gui/components/TextList.h" + +class DisasmCtrl : public TextList +{ +public: + DisasmCtrl(wxWindow* parent, const wxWindowID& id, const wxPoint& pos, const wxSize& size, long style); + + void Init(); + wxSize DoGetBestSize() const override; + + void OnUpdateView(); + + uint32 GetViewBaseAddress() const; + std::optional LinePixelPosToAddress(sint32 posY); + + uint32 AddressToScrollPos(uint32 offset) const; + void CenterOffset(uint32 offset); + void GoToAddressDialog(); + + + +protected: + void OnDraw(wxDC& dc, sint32 start, sint32 count, const wxPoint& start_position) override; + void OnMouseMove(const wxPoint& position, uint32 line) override; + void OnKeyPressed(sint32 key_code, const wxPoint& position) override; + void OnMouseDClick(const wxPoint& position, uint32 line) override; + void OnContextMenu(const wxPoint& position, uint32 line) override; + bool OnShowTooltip(const wxPoint& position, uint32 line) override; + void ScrollWindow(int dx, int dy, const wxRect* prect) override; + + void DrawDisassemblyLine(wxDC& dc, const wxPoint& linePosition, MPTR virtualAddress, struct RPLModule* rplModule); + void DrawLabelName(wxDC& dc, const wxPoint& linePosition, MPTR virtualAddress, struct RPLStoredSymbol* storedSymbol); + + void SelectCodeRegion(uint32 newAddress); + +private: + void CopyToClipboard(std::string text); + + sint32 m_mouse_line, m_mouse_line_drawn; + sint32 m_active_line; + uint32 m_lastGotoTarget{}; + // code region info + uint32 currentCodeRegionStart; + uint32 currentCodeRegionEnd; + // line to address mapping + std::vector> m_lineToAddress; +}; diff --git a/src/gui/debugger/DumpCtrl.cpp b/src/gui/debugger/DumpCtrl.cpp new file mode 100644 index 00000000..d8cd6448 --- /dev/null +++ b/src/gui/debugger/DumpCtrl.cpp @@ -0,0 +1,316 @@ +#include "gui/wxgui.h" +#include "gui/debugger/DumpCtrl.h" +#include "Cafe/OS/RPL/rpl.h" +#include "Cafe/OS/RPL/rpl_structs.h" +#include "Cafe/HW/Espresso/Debugger/Debugger.h" +#include "util/helpers/helpers.h" + +#include "Cemu/ExpressionParser/ExpressionParser.h" + +#define COLOR_BLACK 0xFF000000 +#define COLOR_GREY 0xFFA0A0A0 +#define COLOR_WHITE 0xFFFFFFFF + +#define COLOR_DEBUG_ACTIVE_BP 0xFFFFA0FF +#define COLOR_DEBUG_ACTIVE 0xFFFFA080 +#define COLOR_DEBUG_BP 0xFF8080FF + +#define SYNTAX_COLOR_GPR 0xFF000066 +#define SYNTAX_COLOR_FPR 0xFF006666 +#define SYNTAX_COLOR_SPR 0xFF666600 +#define SYNTAX_COLOR_CR 0xFF666600 +#define SYNTAX_COLOR_IMM 0xFF006600 +#define SYNTAX_COLOR_IMM_OFFSET 0xFF006600 +#define SYNTAX_COLOR_CIMM 0xFF880000 +#define SYNTAX_COLOR_PSEUDO 0xFFA0A0A0 // color for pseudo code +#define SYNTAX_COLOR_SYMBOL 0xFF0000A0 // color for function symbol + +#define OFFSET_ADDRESS (60) +#define OFFSET_ADDRESS_RELATIVE (90) +#define OFFSET_MEMORY (450) + +#define OFFSET_DISASSEMBLY_OPERAND (80) + + +DumpCtrl::DumpCtrl(wxWindow* parent, const wxWindowID& id, const wxPoint& pos, const wxSize& size, long style) + : TextList(parent, id, pos, size, style) +{ + MMURange* range = memory_getMMURangeByAddress(0x10000000); + if (range) + { + m_memoryRegion.baseAddress = range->getBase(); + m_memoryRegion.size = range->getSize(); + Init(); + } + else + { + m_memoryRegion.baseAddress = 0x10000000; + m_memoryRegion.size = 0x1000; + Init(); + } +} + +void DumpCtrl::Init() +{ + uint32 element_count = m_memoryRegion.size; + this->SetElementCount(element_count / 0x10); + Scroll(0, 0); + RefreshControl(); +} + +void DumpCtrl::OnDraw(wxDC& dc, sint32 start, sint32 count, const wxPoint& start_position) +{ + wxPoint position = start_position; + uint32 endAddr = m_memoryRegion.baseAddress + m_memoryRegion.size; + RPLModule* currentCodeRPL = RPLLoader_FindModuleByCodeAddr(m_memoryRegion.baseAddress + start); + RPLModule* currentDataRPL = RPLLoader_FindModuleByDataAddr(m_memoryRegion.baseAddress + start); + for (sint32 i = 0; i <= count; i++) + { + const uint32 virtual_address = m_memoryRegion.baseAddress + (start + i) * 0x10; + + dc.SetTextForeground(wxColour(COLOR_BLACK)); + dc.DrawText(wxString::Format("%08x", virtual_address), position); + position.x += OFFSET_ADDRESS; + + dc.SetTextForeground(wxColour(COLOR_GREY)); + if (currentCodeRPL) + { + dc.DrawText(wxString::Format("+0x%-8x", virtual_address - currentCodeRPL->regionMappingBase_text.GetMPTR()), position); + } + else if (currentDataRPL) + { + dc.DrawText(wxString::Format("+0x%-8x", virtual_address - currentDataRPL->regionMappingBase_data), position); + } + else + { + dc.DrawText("???", position); + } + + position.x += OFFSET_ADDRESS_RELATIVE; + + sint32 start_width = position.x; + + if (!memory_isAddressRangeAccessible(virtual_address, 0x10)) + { + for (sint32 f=0; f<0x10; f++) + { + wxPoint p(position); + WriteText(dc, wxString::Format("?? "), p); + position.x += (m_char_width * 3); + } + } + else + { + std::array data; + memory_readBytes(virtual_address, data); + for (auto b : data) + { + wxPoint p(position); + WriteText(dc, wxString::Format("%02x ", b), p); + position.x += (m_char_width * 3); + } + position.x = start_width = OFFSET_MEMORY; + dc.SetTextForeground(wxColour(COLOR_BLACK)); + for (auto b : data) + { + if (isprint(b)) + dc.DrawText(wxString::Format("%c ", b), position); + else + dc.DrawText(".", position); + + position.x += m_char_width; + } + } + + // display goto indicator + if (m_lastGotoOffset >= virtual_address && m_lastGotoOffset < (virtual_address + 16)) + { + sint32 indicatorX = start_position.x + OFFSET_ADDRESS + OFFSET_ADDRESS_RELATIVE + (m_lastGotoOffset - virtual_address) * m_char_width * 3; + wxPoint line1(start_position.x + indicatorX - 2, position.y); + wxPoint line2(line1.x, line1.y + m_line_height); + dc.SetPen(*wxRED_PEN); + dc.DrawLine(line1, line2); + dc.DrawLine(line1, wxPoint(line1.x + 3, line1.y)); + dc.DrawLine(line2, wxPoint(line2.x + 3, line2.y)); + } + + NextLine(position, &start_position); + } + + // draw vertical separator lines for 4 byte blocks + sint32 cursorOffsetHexBytes = start_position.x + OFFSET_ADDRESS + OFFSET_ADDRESS_RELATIVE; + wxPoint line_from( + cursorOffsetHexBytes + m_char_width * (3 * 4 - 1) + m_char_width / 2, + start_position.y + ); + wxPoint line_to(line_from.x, line_from.y + m_line_height * (count + 1)); + dc.SetPen(*wxLIGHT_GREY_PEN); + for (sint32 i = 0; i < 3; i++) + { + dc.DrawLine(line_from, line_to); + line_from.x += m_char_width * (3 * 4); + line_to.x += m_char_width * (3 * 4); + } +} + +void DumpCtrl::OnMouseMove(const wxPoint& start_position, uint32 line) +{ + wxPoint position = start_position; + + // address + if (position.x <= OFFSET_ADDRESS) + { + return; + } + + position.x -= OFFSET_ADDRESS; + + // relative offset + if (position.x <= OFFSET_ADDRESS_RELATIVE) + { + return; + } + + position.x -= OFFSET_ADDRESS_RELATIVE; + + // byte code + if (position.x <= OFFSET_MEMORY) + { + + } + + // string view + position.x -= OFFSET_MEMORY; +} + +void DumpCtrl::OnMouseDClick(const wxPoint& position, uint32 line) +{ + wxPoint pos = position; + if (pos.x <= OFFSET_ADDRESS + OFFSET_ADDRESS_RELATIVE) + return; + + pos.x -= OFFSET_ADDRESS + OFFSET_ADDRESS_RELATIVE; + if(pos.x <= OFFSET_MEMORY) + { + const uint32 byte_index = (pos.x / m_char_width) / 3; + const uint32 offset = LineToOffset(line) + byte_index; + const uint8 value = memory_readU8(offset); + + wxTextEntryDialog set_value_dialog(this, _("Enter a new value."), _(wxString::Format("Set byte at address %08x", offset)), wxString::Format("%02x", value)); + if (set_value_dialog.ShowModal() == wxID_OK) + { + const uint8 new_value = std::stoul(set_value_dialog.GetValue().ToStdString(), nullptr, 16); + memory_writeU8(offset, new_value); + wxRect update_rect(0, line * m_line_height, GetSize().x, m_line_height); + RefreshControl(&update_rect); + } + + return; + } +} + +void DumpCtrl::GoToAddressDialog() +{ + wxTextEntryDialog goto_dialog(this, _("Enter a target address."), _("GoTo address"), wxEmptyString); + if (goto_dialog.ShowModal() == wxID_OK) + { + try + { + ExpressionParser parser; + + auto value = goto_dialog.GetValue().ToStdString(); + std::transform(value.begin(), value.end(), value.begin(), tolower); + //parser.SetExpr(value); + + const auto module_count = RPLLoader_GetModuleCount(); + const auto module_list = RPLLoader_GetModuleList(); + + std::vector module_tmp(module_count); + for (int i = 0; i < module_count; i++) + { + const auto module = module_list[i]; + if (module) + { + module_tmp[i] = (double)module->regionMappingBase_text.GetMPTR(); + parser.AddConstant(module->moduleName2, module_tmp[i]); + } + } + + double grp_tmp[32]; + PPCSnapshot& ppc_snapshot = debuggerState.debugSession.ppcSnapshot; + for (int i = 0; i < 32; i++) + { + char var_name[32]; + sprintf(var_name, "r%d", i); + grp_tmp[i] = ppc_snapshot.gpr[i]; + parser.AddConstant(var_name, grp_tmp[i]); + } + + const auto result = (uint32)parser.Evaluate(value); + debug_printf("goto eval result: %x\n", result); + m_lastGotoOffset = result; + CenterOffset(result); + } + catch (const std::exception ex) + { + wxMessageBox(ex.what(), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + } + } +} + +void DumpCtrl::CenterOffset(uint32 offset) +{ + // check if the offset is valid + if (!memory_isAddressRangeAccessible(offset, 1)) + return; + // set region and line + MMURange* range = memory_getMMURangeByAddress(offset); + if (m_memoryRegion.baseAddress != range->getBase() || m_memoryRegion.size != range->getSize()) + { + m_memoryRegion.baseAddress = range->getBase(); + m_memoryRegion.size = range->getSize(); + Init(); + } + + const sint32 line = OffsetToLine(offset); + if (line < 0 || line >= (sint32)m_element_count) + return; + + DoScroll(0, std::max(0, line - ((sint32)m_elements_visible / 2))); + + RefreshControl(); + //RefreshLine(line); + + debug_printf("scroll to %x\n", debuggerState.debugSession.instructionPointer); +} + +uint32 DumpCtrl::LineToOffset(uint32 line) +{ + return m_memoryRegion.baseAddress + line * 0x10; +} + +uint32 DumpCtrl::OffsetToLine(uint32 offset) +{ + return (offset - m_memoryRegion.baseAddress) / 0x10; +} + + +void DumpCtrl::OnKeyPressed(sint32 key_code, const wxPoint& position) +{ + switch (key_code) + { + case 'G': + { + if (IsKeyDown(WXK_CONTROL)) + { + GoToAddressDialog(); + return; + } + } + } +} + +wxSize DumpCtrl::DoGetBestSize() const +{ + return TextList::DoGetBestSize(); +} \ No newline at end of file diff --git a/src/gui/debugger/DumpCtrl.h b/src/gui/debugger/DumpCtrl.h new file mode 100644 index 00000000..da5ad4ea --- /dev/null +++ b/src/gui/debugger/DumpCtrl.h @@ -0,0 +1,30 @@ +#pragma once +#include "gui/components/TextList.h" + + +class DumpCtrl : public TextList +{ +public: + DumpCtrl(wxWindow* parent, const wxWindowID& id, const wxPoint& pos, const wxSize& size, long style); + + void Init(); + wxSize DoGetBestSize() const override; + +protected: + void GoToAddressDialog(); + void CenterOffset(uint32 offset); + uint32 LineToOffset(uint32 line); + uint32 OffsetToLine(uint32 offset); + + void OnDraw(wxDC& dc, sint32 start, sint32 count, const wxPoint& start_position) override; + void OnMouseMove(const wxPoint& position, uint32 line) override; + void OnMouseDClick(const wxPoint& position, uint32 line) override; + void OnKeyPressed(sint32 key_code, const wxPoint& position) override; +private: + struct + { + uint32 baseAddress; + uint32 size; + }m_memoryRegion; + uint32 m_lastGotoOffset{0}; +}; diff --git a/src/gui/debugger/DumpWindow.cpp b/src/gui/debugger/DumpWindow.cpp new file mode 100644 index 00000000..06dba7df --- /dev/null +++ b/src/gui/debugger/DumpWindow.cpp @@ -0,0 +1,48 @@ +#include "gui/wxgui.h" +#include "gui/debugger/DumpWindow.h" + +#include "gui/debugger/DebuggerWindow2.h" +#include "Cafe/HW/Espresso/Debugger/Debugger.h" +#include "gui/debugger/DumpCtrl.h" + +enum +{ + // REGISTER + REGISTER_ADDRESS_R0 = wxID_HIGHEST + 8200, + REGISTER_LABEL_R0 = REGISTER_ADDRESS_R0 + 32, + REGISTER_LABEL_FPR0_0 = REGISTER_LABEL_R0 + 32, + REGISTER_LABEL_FPR1_0 = REGISTER_LABEL_R0 + 32, +}; + +DumpWindow::DumpWindow(DebuggerWindow2& parent, const wxPoint& main_position, const wxSize& main_size) + : wxFrame(&parent, wxID_ANY, wxT("Memory Dump"), wxDefaultPosition, wxSize(600, 250), wxSYSTEM_MENU | wxCAPTION | wxCLIP_CHILDREN | wxRESIZE_BORDER | wxFRAME_FLOAT_ON_PARENT) +{ + this->wxWindowBase::SetBackgroundColour(*wxWHITE); + + wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL); + m_dump_ctrl = new DumpCtrl(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxScrolledWindowStyle); + main_sizer->Add(m_dump_ctrl, 1, wxEXPAND); + + this->SetSizer(main_sizer); + this->wxWindowBase::Layout(); + + this->Centre(wxBOTH); + + if (parent.GetConfig().data().pin_to_main) + OnMainMove(main_position, main_size); +} + +void DumpWindow::OnMainMove(const wxPoint& main_position, const wxSize& main_size) +{ + wxSize size(600, 250); + this->SetSize(size); + + wxPoint position = main_position; + position.y += main_size.GetHeight(); + this->SetPosition(position); +} + +void DumpWindow::OnGameLoaded() +{ + m_dump_ctrl->Init(); +} diff --git a/src/gui/debugger/DumpWindow.h b/src/gui/debugger/DumpWindow.h new file mode 100644 index 00000000..840c5fc6 --- /dev/null +++ b/src/gui/debugger/DumpWindow.h @@ -0,0 +1,18 @@ +#pragma once + +#include "gui/debugger/DumpCtrl.h" + +class DebuggerWindow2; + +class DumpWindow : public wxFrame +{ +public: + DumpWindow(DebuggerWindow2& parent, const wxPoint& main_position, const wxSize& main_size); + + void OnMainMove(const wxPoint& position, const wxSize& main_size); + void OnGameLoaded(); + +private: + wxScrolledWindow* m_scrolled_window; + DumpCtrl* m_dump_ctrl; +}; \ No newline at end of file diff --git a/src/gui/debugger/ModuleWindow.cpp b/src/gui/debugger/ModuleWindow.cpp new file mode 100644 index 00000000..6e24c41f --- /dev/null +++ b/src/gui/debugger/ModuleWindow.cpp @@ -0,0 +1,137 @@ +#include "gui/wxgui.h" +#include "gui/guiWrapper.h" +#include "gui/debugger/ModuleWindow.h" + +#include + +#include "gui/debugger/DebuggerWindow2.h" +#include "Cafe/HW/Espresso/Debugger/Debugger.h" + +#include "Cafe/OS/RPL/rpl.h" +#include "Cafe/OS/RPL/rpl_structs.h" + +#include "Cafe/GraphicPack/GraphicPack2.h" + +enum ItemColumns +{ + ColumnName = 0, + ColumnAddress, + ColumnSize, +}; + +ModuleWindow::ModuleWindow(DebuggerWindow2& parent, const wxPoint& main_position, const wxSize& main_size) + : wxFrame(&parent, wxID_ANY, "Modules", wxDefaultPosition, wxSize(420, 250), wxSYSTEM_MENU | wxCAPTION | wxCLIP_CHILDREN | wxRESIZE_BORDER | wxFRAME_FLOAT_ON_PARENT) +{ + this->SetSizeHints(wxDefaultSize, wxDefaultSize); + + this->wxWindowBase::SetBackgroundColour(*wxWHITE); + + wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL); + + m_modules = new wxListView(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT); + + wxListItem col0; + col0.SetId(ColumnName); + col0.SetText(_("Name")); + col0.SetWidth(125); + m_modules->InsertColumn(ColumnName, col0); + + wxListItem col1; + col1.SetId(ColumnAddress); + col1.SetText(_("Address")); + col1.SetWidth(75); + col1.SetAlign(wxLIST_FORMAT_RIGHT); + m_modules->InsertColumn(ColumnAddress, col1); + + wxListItem col2; + col2.SetId(ColumnSize); + col2.SetText(_("Size")); + col2.SetWidth(75); + col2.SetAlign(wxLIST_FORMAT_RIGHT); + m_modules->InsertColumn(ColumnSize, col2); + + main_sizer->Add(m_modules, 1, wxEXPAND); + + this->SetSizer(main_sizer); + this->wxWindowBase::Layout(); + + this->Centre(wxBOTH); + + if (parent.GetConfig().data().pin_to_main) + OnMainMove(main_position, main_size); + + m_modules->Bind(wxEVT_LEFT_DCLICK, &ModuleWindow::OnLeftDClick, this); + + OnGameLoaded(); +} + +void ModuleWindow::OnMainMove(const wxPoint& main_position, const wxSize& main_size) +{ + wxSize size(420, 250); + this->SetSize(size); + + wxPoint position = main_position; + position.x -= 420; + position.y += main_size.y; + this->SetPosition(position); +} + + +void ModuleWindow::OnGameLoaded() +{ + Freeze(); + + m_modules->DeleteAllItems(); + + const auto module_count = RPLLoader_GetModuleCount(); + const auto module_list = RPLLoader_GetModuleList(); + for (int i = 0; i < module_count; i++) + { + const auto module = module_list[i]; + if (module) + { + wxListItem item; + item.SetId(i); + item.SetText(module->moduleName2.c_str()); + + const auto index = m_modules->InsertItem(item); + m_modules->SetItem(index, ColumnAddress, wxString::Format("%08x", module->regionMappingBase_text.GetMPTR())); + m_modules->SetItem(index, ColumnSize, wxString::Format("%x", module->regionSize_text)); + } + } + + sint32 patch_count = 0; + for (auto& gfx_pack : GraphicPack2::GetGraphicPacks()) + { + for (auto& patch_group : gfx_pack->GetPatchGroups()) + { + if (patch_group->isApplied()) + { + wxListItem item; + item.SetId(module_count + patch_count); + item.SetText(std::string(patch_group->getName())); + + const auto index = m_modules->InsertItem(item); + m_modules->SetItem(index, ColumnAddress, wxString::Format("%08x", patch_group->getCodeCaveBase())); + m_modules->SetItem(index, ColumnSize, wxString::Format("%x", patch_group->getCodeCaveSize())); + patch_count++; + } + } + } + + Thaw(); +} + + +void ModuleWindow::OnLeftDClick(wxMouseEvent& event) +{ + long selected = m_modules->GetFirstSelected(); + if (selected == -1) + return; + const auto text = m_modules->GetItemText(selected, ColumnAddress); + const auto address = std::stoul(text.ToStdString(), nullptr, 16); + if (address == 0) + return; + debuggerState.debugSession.instructionPointer = address; + debuggerWindow_moveIP(); +} diff --git a/src/gui/debugger/ModuleWindow.h b/src/gui/debugger/ModuleWindow.h new file mode 100644 index 00000000..6730fa78 --- /dev/null +++ b/src/gui/debugger/ModuleWindow.h @@ -0,0 +1,17 @@ +#pragma once + +class DebuggerWindow2; + +class ModuleWindow : public wxFrame +{ +public: + ModuleWindow(DebuggerWindow2& parent, const wxPoint& main_position, const wxSize& main_size); + + void OnMainMove(const wxPoint& position, const wxSize& main_size); + void OnGameLoaded(); + +private: + void OnLeftDClick(wxMouseEvent& event); + + wxListView* m_modules; +}; \ No newline at end of file diff --git a/src/gui/debugger/RegisterCtrl.cpp b/src/gui/debugger/RegisterCtrl.cpp new file mode 100644 index 00000000..bcf6fb5a --- /dev/null +++ b/src/gui/debugger/RegisterCtrl.cpp @@ -0,0 +1,255 @@ +#include "gui/wxgui.h" +#include "gui/debugger/RegisterCtrl.h" + +#include + +#include "Cafe/OS/RPL/rpl_structs.h" +#include "Cafe/OS/RPL/rpl.h" +#include "Cafe/HW/Espresso/Debugger/Debugger.h" +#include "gui/debugger/DebuggerWindow2.h" + +#define COLOR_DEBUG_ACTIVE_BP 0xFFFFA0FF +#define COLOR_DEBUG_ACTIVE 0xFFFFA080 +#define COLOR_DEBUG_BP 0xFF8080FF + +#define SYNTAX_COLOR_GPR 0xFF000066 +#define SYNTAX_COLOR_FPR 0xFF006666 +#define SYNTAX_COLOR_SPR 0xFF666600 +#define SYNTAX_COLOR_CR 0xFF666600 +#define SYNTAX_COLOR_IMM 0xFF006600 +#define SYNTAX_COLOR_IMM_OFFSET 0xFF006600 +#define SYNTAX_COLOR_CIMM 0xFF880000 +#define SYNTAX_COLOR_PSEUDO 0xFFA0A0A0 // color for pseudo code +#define SYNTAX_COLOR_SYMBOL 0xFF0000A0 // color for function symbol + +#define OFFSET_REGISTER (60) +#define OFFSET_REGISTER_LABEL (100) +#define OFFSET_FPR (120) + +RegisterCtrl::RegisterCtrl(wxWindow* parent, const wxWindowID& id, const wxPoint& pos, const wxSize& size, long style) + : TextList(parent, id, pos, size, style), m_prev_snapshot({}) +{ + this->SetElementCount(32 + 1 + 32); + RefreshControl(); +} + +void RegisterCtrl::OnDraw(wxDC& dc, sint32 start, sint32 count, const wxPoint& start_position) +{ + wxPoint position = start_position; + + //RPLModule* current_rpl_module = rpl3_getModuleByCodeAddr(MEMORY_CODEAREA_ADDR + start); + for (sint32 i = 0; i <= count; i++) + { + const uint32 line = start + i; + + if (line < 32) + { + dc.SetTextForeground(COLOR_BLACK); + dc.DrawText(wxString::Format("R%d", line), position); + position.x += OFFSET_REGISTER; + + const uint32 register_value = debuggerState.debugSession.ppcSnapshot.gpr[line]; + if(register_value != m_prev_snapshot.gpr[line]) + dc.SetTextForeground(COLOR_RED); + else + dc.SetTextForeground(COLOR_BLACK); + + dc.DrawText(wxString::Format("%08x", register_value), position); + position.x += OFFSET_REGISTER_LABEL; + + // check if address is a code offset + RPLModule* code_module = RPLLoader_FindModuleByCodeAddr(register_value); + if (code_module) + dc.DrawText(wxString::Format("<%s> + %x", code_module->moduleName2.c_str(), register_value - code_module->regionMappingBase_text.GetMPTR()), position); + + // check if address is a string + uint32 string_offset = register_value; + if (string_offset >= MEMORY_DATA_AREA_ADDR && string_offset < (MEMORY_DATA_AREA_ADDR+MEMORY_DATA_AREA_SIZE) ) + { + bool is_valid_string = true; + std::stringstream buffer; + + uint32 string_offset = register_value; + while (string_offset < (MEMORY_DATA_AREA_ADDR + MEMORY_DATA_AREA_SIZE)) + { + const uint8 c = memory_readU8(string_offset++); + if (isprint(c)) + { + buffer << c; + } + else if( c == '\0' ) + { + buffer << c; + break; + } + else + { + is_valid_string = false; + break; + } + } + + if(is_valid_string && buffer.tellp() > 1) + { + dc.DrawText(wxString::Format("s \"%s\"", buffer.str().c_str()), position); + } + else + { + // check for widestring + is_valid_string = true; + string_offset = register_value; + std::wstringstream wbuffer; + while (string_offset < (MEMORY_DATA_AREA_ADDR + MEMORY_DATA_AREA_SIZE)) + { + const uint16 c = memory_readU16(string_offset); + string_offset += 2; + + if (iswprint(c)) + { + wbuffer << c; + } + else if (c == L'\0') + { + wbuffer << c; + break; + } + else + { + is_valid_string = false; + break; + } + } + + if (is_valid_string && buffer.tellp() > 1) + { + dc.DrawText(wxString::Format(L"ws \"%s\"", wbuffer.str().c_str()), position); + } + } + } + } + else if( 32 + 1 <= line && line <= 32 + 1 + 32) + { + const uint32 register_index = line - 32 - 1; + dc.SetTextForeground(COLOR_BLACK); + dc.DrawText(wxString::Format("F0_%d", register_index), position); + position.x += OFFSET_REGISTER; + + const uint64 fpr0_value = debuggerState.debugSession.ppcSnapshot.fpr[register_index].fp0int; + if (fpr0_value != m_prev_snapshot.fpr[register_index].fp0int) + dc.SetTextForeground(COLOR_RED); + else + dc.SetTextForeground(COLOR_BLACK); + + //dc.DrawText(wxString::Format("%016llx", fpr0_value), position); + dc.DrawText(wxString::Format("%lf", debuggerState.debugSession.ppcSnapshot.fpr[register_index].fp0), position); + + position.x += OFFSET_FPR; + + dc.SetTextForeground(COLOR_BLACK); + dc.DrawText(wxString::Format("F1_%d", register_index), position); + position.x += OFFSET_REGISTER; + + const uint64 fpr1_value = debuggerState.debugSession.ppcSnapshot.fpr[register_index].fp1int; + if (fpr1_value != m_prev_snapshot.fpr[register_index].fp1int) + dc.SetTextForeground(COLOR_RED); + else + dc.SetTextForeground(COLOR_BLACK); + + //dc.DrawText(wxString::Format("%016llx", fpr1_value), position); + dc.DrawText(wxString::Format("%lf", debuggerState.debugSession.ppcSnapshot.fpr[register_index].fp1), position); + } + else + { + // empty line + } + + NextLine(position, &start_position); + } + + memcpy(&m_prev_snapshot, &debuggerState.debugSession.ppcSnapshot, sizeof(m_prev_snapshot)); +} + +void RegisterCtrl::OnMouseMove(const wxPoint& position, uint32 line) +{ + if (!m_mouse_down) + return; + + wxPoint pos = position; + if(pos.x <= OFFSET_REGISTER) + { + + } + + pos.x -= OFFSET_REGISTER; + + if (pos.x <= OFFSET_REGISTER_LABEL) + { + + } + + pos.x -= OFFSET_REGISTER_LABEL; +} + +void RegisterCtrl::OnMouseDClick(const wxPoint& position, uint32 line) +{ + if (!debuggerState.debugSession.isTrapped) + return; + + if(line < 32) + { + const uint32 register_index = line; + if (position.x <= OFFSET_REGISTER + OFFSET_REGISTER_LABEL) + { + const uint32 register_value = debuggerState.debugSession.ppcSnapshot.gpr[register_index]; + wxTextEntryDialog set_value_dialog(this, _("Enter a new value."), _(wxString::Format("Set R%d value", register_index)), wxString::Format("%08x", register_value)); + if (set_value_dialog.ShowModal() == wxID_OK) + { + const uint32 new_value = std::stoul(set_value_dialog.GetValue().ToStdString(), nullptr, 16); + debuggerState.debugSession.hCPU->gpr[register_index] = new_value; + debuggerState.debugSession.ppcSnapshot.gpr[register_index] = new_value; + + wxRect update_rect(0, line * m_line_height, GetSize().x, m_line_height); + RefreshControl(&update_rect); + } + } + } + // one line empty between = line 32 + else if (32 + 1 <= line && line <= 32 + 1 + 32) + { + const uint32 register_index = line - 32 - 1; + if (position.x <= OFFSET_REGISTER + OFFSET_FPR) + { + const double register_value = debuggerState.debugSession.ppcSnapshot.fpr[register_index].fp0; + wxTextEntryDialog set_value_dialog(this, _("Enter a new value."), _(wxString::Format("Set FP0_%d value", register_index)), wxString::Format("%lf", register_value)); + if (set_value_dialog.ShowModal() == wxID_OK) + { + const double new_value = std::stod(set_value_dialog.GetValue().ToStdString()); + debuggerState.debugSession.hCPU->fpr[register_index].fp0 = new_value; + debuggerState.debugSession.ppcSnapshot.fpr[register_index].fp0 = new_value; + + wxRect update_rect(0, line * m_line_height, GetSize().x, m_line_height); + RefreshControl(&update_rect); + } + } + else + { + const double register_value = debuggerState.debugSession.ppcSnapshot.fpr[register_index].fp1; + wxTextEntryDialog set_value_dialog(this, _("Enter a new value."), _(wxString::Format("Set FP1_%d value", register_index)), wxString::Format("%lf", register_value)); + if (set_value_dialog.ShowModal() == wxID_OK) + { + const double new_value = std::stod(set_value_dialog.GetValue().ToStdString()); + debuggerState.debugSession.hCPU->fpr[register_index].fp1 = new_value; + debuggerState.debugSession.ppcSnapshot.fpr[register_index].fp1 = new_value; + + wxRect update_rect(0, line * m_line_height, GetSize().x, m_line_height); + RefreshControl(&update_rect); + } + } + } +} + +void RegisterCtrl::OnGameLoaded() +{ + RefreshControl(); +} + diff --git a/src/gui/debugger/RegisterCtrl.h b/src/gui/debugger/RegisterCtrl.h new file mode 100644 index 00000000..378a5d5d --- /dev/null +++ b/src/gui/debugger/RegisterCtrl.h @@ -0,0 +1,18 @@ +#pragma once +#include "gui/components/TextList.h" +#include "Cafe/HW/Espresso/Debugger/Debugger.h" + +class RegisterCtrl : public TextList +{ +public: + RegisterCtrl(wxWindow* parent, const wxWindowID& id, const wxPoint& pos, const wxSize& size, long style); + void OnGameLoaded(); + +protected: + void OnDraw(wxDC& dc, sint32 start, sint32 count, const wxPoint& start_position) override; + void OnMouseMove(const wxPoint& position, uint32 line) override; + void OnMouseDClick(const wxPoint& position, uint32 line) override; + +private: + PPCSnapshot m_prev_snapshot; +}; diff --git a/src/gui/debugger/RegisterWindow.cpp b/src/gui/debugger/RegisterWindow.cpp new file mode 100644 index 00000000..b01e2040 --- /dev/null +++ b/src/gui/debugger/RegisterWindow.cpp @@ -0,0 +1,450 @@ +#include "gui/wxgui.h" +#include "gui/debugger/RegisterWindow.h" + +#include + +#include "gui/debugger/DebuggerWindow2.h" +#include "Cafe/HW/Espresso/Debugger/Debugger.h" +#include "Cafe/OS/RPL/rpl.h" +#include "Cafe/OS/RPL/rpl_structs.h" +#include "Cafe/HW/Espresso/EspressoISA.h" + +enum +{ + // REGISTER + kRegisterValueR0 = wxID_HIGHEST + 8400, + kRegisterLabelR0 = kRegisterValueR0 + 32, + kRegisterLabelLR = kRegisterLabelR0 + 32, + kRegisterValueLR = kRegisterLabelLR + 1, + kRegisterValueFPR0_0 = kRegisterValueLR + 32, + kRegisterValueFPR1_0 = kRegisterValueFPR0_0 + 32, + kRegisterValueCR0 = kRegisterValueFPR1_0 + 32, + kContextMenuZero, + kContextMenuInc, + kContextMenuDec, + kContextMenuCopy, + kContextMenuGotoDisasm, + kContextMenuGotoDump, +}; + +RegisterWindow::RegisterWindow(DebuggerWindow2& parent, const wxPoint& main_position, const wxSize& main_size) + : wxFrame(&parent, wxID_ANY, "Register View", wxDefaultPosition, wxSize(400, 975), wxSYSTEM_MENU | wxCAPTION | wxCLIP_CHILDREN | wxRESIZE_BORDER | wxFRAME_FLOAT_ON_PARENT), + m_prev_snapshot({}), m_show_double_values(true), m_context_ctrl(nullptr) +{ + SetSizeHints(wxDefaultSize, wxDefaultSize); + SetMaxSize({ 400, 975 }); + wxWindowBase::SetBackgroundColour(*wxWHITE); + + wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL); + + auto scrolled_win = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL); + wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL); + + auto gpr_sizer = new wxFlexGridSizer(0, 3, 0, 0); + + // GPRs + for(sint32 i = 0; i < 32; ++i) + { + gpr_sizer->Add(new wxStaticText(scrolled_win, wxID_ANY, wxString::Format("R%d", i)), 0, wxLEFT, 5); + + auto value = new wxTextCtrl(scrolled_win, kRegisterValueR0 + i, wxString::Format("%08x", 0), wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxNO_BORDER); + value->SetBackgroundColour(*wxWHITE); + value->Bind(wxEVT_LEFT_DCLICK, &RegisterWindow::OnMouseDClickEvent, this); + //value->Bind(wxEVT_CONTEXT_MENU, &RegisterWindow::OnValueContextMenu, this); + gpr_sizer->Add(value, 0, wxLEFT|wxRIGHT, 5); + + auto label = new wxTextCtrl(scrolled_win, kRegisterLabelR0 + i, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxNO_BORDER); + label->SetMinSize(wxSize(500, -1)); + label->SetBackgroundColour(*wxWHITE); + gpr_sizer->Add(label, 0, wxEXPAND); + } + + { + // LR + gpr_sizer->Add(new wxStaticText(scrolled_win, wxID_ANY, wxString::Format("LR")), 0, wxLEFT, 5); + auto value = new wxTextCtrl(scrolled_win, kRegisterValueLR, wxString::Format("%08x", 0), wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxNO_BORDER); + value->SetBackgroundColour(*wxWHITE); + value->Bind(wxEVT_LEFT_DCLICK, &RegisterWindow::OnMouseDClickEvent, this); + //value->Bind(wxEVT_CONTEXT_MENU, &RegisterWindow::OnValueContextMenu, this); + gpr_sizer->Add(value, 0, wxLEFT | wxRIGHT, 5); + auto label = new wxTextCtrl(scrolled_win, kRegisterLabelLR, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxNO_BORDER); + label->SetMinSize(wxSize(500, -1)); + label->SetBackgroundColour(*wxWHITE); + gpr_sizer->Add(label, 0, wxEXPAND); + } + + sizer->Add(gpr_sizer, 1, wxEXPAND); + + auto button = new wxButton(scrolled_win, wxID_ANY, "FP view mode"); + button->Bind(wxEVT_BUTTON, &RegisterWindow::OnFPViewModePress, this); + sizer->Add(button, 0, wxALL, 5); + + auto fp_sizer = new wxFlexGridSizer(0, 3, 0, 0); + for (sint32 i = 0; i < 32; ++i) + { + fp_sizer->Add(new wxStaticText(scrolled_win, wxID_ANY, wxString::Format("FP%d", i)), 0, wxLEFT, 5); + + auto value0 = new wxTextCtrl(scrolled_win, kRegisterValueFPR0_0 + i, wxString::Format("%lf", 0.0), wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxNO_BORDER); + value0->SetBackgroundColour(*wxWHITE); + value0->Bind(wxEVT_LEFT_DCLICK, &RegisterWindow::OnMouseDClickEvent, this); + fp_sizer->Add(value0, 0, wxLEFT | wxRIGHT, 5); + + auto value1 = new wxTextCtrl(scrolled_win, kRegisterValueFPR1_0 + i, wxString::Format("%lf", 0.0), wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxNO_BORDER); + value1->SetBackgroundColour(*wxWHITE); + value1->Bind(wxEVT_LEFT_DCLICK, &RegisterWindow::OnMouseDClickEvent, this); + fp_sizer->Add(value1, 0, wxLEFT | wxRIGHT, 5); + } + + sizer->Add(fp_sizer, 0, wxEXPAND); + auto cr_sizer = new wxFlexGridSizer(0, 2, 0, 0); + + // CRs + for (sint32 i = 0; i < 8; ++i) + { + cr_sizer->Add(new wxStaticText(scrolled_win, wxID_ANY, wxString::Format("CR%d", i)), 0, wxLEFT, 5); + auto value = new wxTextCtrl(scrolled_win, kRegisterValueCR0 + i, "-", wxDefaultPosition, wxDefaultSize, wxTE_READONLY | wxNO_BORDER); + value->SetBackgroundColour(*wxWHITE); + //value->Bind(wxEVT_CONTEXT_MENU, &RegisterWindow::OnValueContextMenu, this); + cr_sizer->Add(value, 0, wxRIGHT, 5); + } + + sizer->Add(cr_sizer, 0, wxEXPAND); + scrolled_win->SetSizerAndFit(sizer); + scrolled_win->SetScrollRate(0, GetCharWidth()); + + main_sizer->Add(scrolled_win, 1, wxEXPAND); + SetSizer(main_sizer); + Layout(); + + if (parent.GetConfig().data().pin_to_main) + OnMainMove(main_position, main_size); + + //Bind(wxEVT_COMMAND_MENU_SELECTED, &RegisterWindow::OnValueContextMenuSelected, this, kContextMenuZero, kContextMenuGotoDump); +} + + +void RegisterWindow::OnMainMove(const wxPoint& main_position, const wxSize& main_size) +{ + wxSize size(400, 255 + main_size.y + 250); + this->SetSize(size); + + wxPoint position = main_position; + position.x += main_size.x; + position.y -= 255; + this->SetPosition(position); +} + +void RegisterWindow::UpdateIntegerRegister(wxTextCtrl* label, wxTextCtrl* value, uint32 registerValue, bool hasChanged) +{ + //const auto value = dynamic_cast(GetWindowChild(kRegisterValueR0 + i)); + //wxASSERT(value); + + //const bool has_changed = register_value != m_prev_snapshot.gpr[i]; + if (hasChanged) + value->SetForegroundColour(COLOR_RED); + else if (value->GetForegroundColour() != COLOR_BLACK) + value->SetForegroundColour(COLOR_BLACK); + + value->SetLabelText(wxString::Format("%08x", registerValue)); + + //const auto label = dynamic_cast(GetWindowChild(kRegisterLabelR0 + i)); + //wxASSERT(label); + label->SetForegroundColour(hasChanged ? COLOR_RED : COLOR_BLACK); + + // check if address is a string + if (registerValue >= MEMORY_DATA_AREA_ADDR && registerValue < (MEMORY_DATA_AREA_ADDR + MEMORY_DATA_AREA_SIZE)) + { + bool is_valid_string = true; + std::stringstream buffer; + + uint32 string_offset = registerValue; + while (string_offset < (MEMORY_DATA_AREA_ADDR + MEMORY_DATA_AREA_SIZE)) + { + const uint8 c = memory_readU8(string_offset++); + if (isprint(c)) + buffer << c; + else if (c == '\0') + { + buffer << c; + break; + } + else + { + is_valid_string = false; + break; + } + } + + if (is_valid_string && buffer.tellp() > 1) + { + label->SetLabelText(wxString::Format("\"%s\"", buffer.str().c_str())); + return; + } + + // check for widestring + is_valid_string = true; + string_offset = registerValue; + std::wstringstream wbuffer; + while (string_offset < (MEMORY_DATA_AREA_ADDR + MEMORY_DATA_AREA_SIZE - 1)) + { + const uint16 c = memory_readU16(string_offset); + string_offset += 2; + + if (iswprint(c)) + wbuffer << c; + else if (c == L'\0') + { + wbuffer << c; + break; + } + else + { + is_valid_string = false; + break; + } + } + + if (is_valid_string && buffer.tellp() > 1) + { + label->SetLabelText(wxString::Format(L"ws\"%s\"", wbuffer.str().c_str())); + return; + } + } + + // check if address is a code offset + RPLModule* code_module = RPLLoader_FindModuleByCodeAddr(registerValue); + if (code_module) + { + label->SetLabelText(wxString::Format("<%s> + %x", code_module->moduleName2.c_str(), registerValue - code_module->regionMappingBase_text.GetMPTR())); + return; + } + + label->SetLabelText(wxEmptyString); +} + +void RegisterWindow::OnUpdateView() +{ + // m_register_ctrl->RefreshControl(); + for (int i = 0; i < 32; ++i) + { + const uint32 registerValue = debuggerState.debugSession.ppcSnapshot.gpr[i]; + const bool hasChanged = registerValue != m_prev_snapshot.gpr[i]; + const auto value = dynamic_cast(FindWindow(kRegisterValueR0 + i)); + wxASSERT(value); + const auto label = dynamic_cast(FindWindow(kRegisterLabelR0 + i)); + wxASSERT(label); + + UpdateIntegerRegister(label, value, registerValue, hasChanged); + } + + // update LR + { + const uint32 registerValue = debuggerState.debugSession.ppcSnapshot.spr_lr; + const bool hasChanged = registerValue != m_prev_snapshot.spr_lr; + const auto value = dynamic_cast(FindWindow(kRegisterValueLR)); + wxASSERT(value); + const auto label = dynamic_cast(FindWindow(kRegisterLabelLR)); + wxASSERT(label); + UpdateIntegerRegister(label, value, registerValue, hasChanged); + } + + for (int i = 0; i < 32; ++i) + { + const uint64_t register_value = debuggerState.debugSession.ppcSnapshot.fpr[i].fp0int; + + const auto value = dynamic_cast(FindWindow(kRegisterValueFPR0_0 + i)); + wxASSERT(value); + + const bool has_changed = register_value != m_prev_snapshot.fpr[i].fp0int; + if (has_changed) + value->SetForegroundColour(COLOR_RED); + else if (value->GetForegroundColour() != COLOR_BLACK) + value->SetForegroundColour(COLOR_BLACK); + else + continue; + + if(m_show_double_values) + value->SetLabelText(wxString::Format("%lf", debuggerState.debugSession.ppcSnapshot.fpr[i].fp0)); + else + value->SetLabelText(wxString::Format("%016llx", register_value)); + } + + for (int i = 0; i < 32; ++i) + { + const uint64_t register_value = debuggerState.debugSession.ppcSnapshot.fpr[i].fp1int; + + const auto value = dynamic_cast(FindWindow(kRegisterValueFPR1_0 + i)); + wxASSERT(value); + + const bool has_changed = register_value != m_prev_snapshot.fpr[i].fp1int; + if (has_changed) + value->SetForegroundColour(COLOR_RED); + else if (value->GetForegroundColour() != COLOR_BLACK) + value->SetForegroundColour(COLOR_BLACK); + else + continue; + + if (m_show_double_values) + value->SetLabelText(wxString::Format("%lf", debuggerState.debugSession.ppcSnapshot.fpr[i].fp1)); + else + value->SetLabelText(wxString::Format("%016llx", register_value)); + } + + // update CRs + for (int i = 0; i < 8; ++i) + { + const auto value = dynamic_cast(FindWindow(kRegisterValueCR0 + i)); + wxASSERT(value); + + auto cr_bits_ptr = debuggerState.debugSession.ppcSnapshot.cr + i * 4; + auto cr_bits_ptr_cmp = m_prev_snapshot.cr + i * 4; + + const bool has_changed = !std::equal(cr_bits_ptr, cr_bits_ptr + 4, cr_bits_ptr_cmp); + if (has_changed) + value->SetForegroundColour(COLOR_RED); + else if (value->GetForegroundColour() != COLOR_BLACK) + value->SetForegroundColour(COLOR_BLACK); + else + continue; + + std::vector joinArray = {}; + if (cr_bits_ptr[Espresso::CR_BIT_INDEX_LT] != 0) + joinArray.emplace_back("LT"); + if (cr_bits_ptr[Espresso::CR_BIT_INDEX_GT] != 0) + joinArray.emplace_back("GT"); + if (cr_bits_ptr[Espresso::CR_BIT_INDEX_EQ] != 0) + joinArray.emplace_back("EQ"); + if (cr_bits_ptr[Espresso::CR_BIT_INDEX_SO] != 0) + joinArray.emplace_back("SO"); + + if (joinArray.empty()) + value->SetLabelText("-"); + else + value->SetLabelText(fmt::format("{}", fmt::join(joinArray, ", "))); + } + + memcpy(&m_prev_snapshot, &debuggerState.debugSession.ppcSnapshot, sizeof(m_prev_snapshot)); +} + +void RegisterWindow::OnMouseDClickEvent(wxMouseEvent& event) +{ + if (!debuggerState.debugSession.isTrapped) + { + event.Skip(); + return; + } + + const auto id = event.GetId(); + if(kRegisterValueR0 <= id && id < kRegisterValueR0 + 32) + { + const uint32 register_index = id - kRegisterValueR0; + const uint32 register_value = debuggerState.debugSession.ppcSnapshot.gpr[register_index]; + wxTextEntryDialog set_value_dialog(this, _("Enter a new value."), _(wxString::Format("Set R%d value", register_index)), wxString::Format("%08x", register_value)); + if (set_value_dialog.ShowModal() == wxID_OK) + { + const uint32 new_value = std::stoul(set_value_dialog.GetValue().ToStdString(), nullptr, 16); + debuggerState.debugSession.hCPU->gpr[register_index] = new_value; + debuggerState.debugSession.ppcSnapshot.gpr[register_index] = new_value; + OnUpdateView(); + } + + return; + } + + if (kRegisterValueFPR0_0 <= id && id < kRegisterValueFPR0_0 + 32) + { + const uint32 register_index = id - kRegisterValueFPR0_0; + const double register_value = debuggerState.debugSession.ppcSnapshot.fpr[register_index].fp0; + wxTextEntryDialog set_value_dialog(this, _("Enter a new value."), _(wxString::Format("Set FP0_%d value", register_index)), wxString::Format("%lf", register_value)); + if (set_value_dialog.ShowModal() == wxID_OK) + { + const double new_value = std::stod(set_value_dialog.GetValue().ToStdString()); + debuggerState.debugSession.hCPU->fpr[register_index].fp0 = new_value; + debuggerState.debugSession.ppcSnapshot.fpr[register_index].fp0 = new_value; + OnUpdateView(); + } + + return; + } + + if (kRegisterValueFPR1_0 <= id && id < kRegisterValueFPR1_0 + 32) + { + const uint32 register_index = id - kRegisterValueFPR1_0; + const double register_value = debuggerState.debugSession.ppcSnapshot.fpr[register_index].fp1; + wxTextEntryDialog set_value_dialog(this, _("Enter a new value."), _(wxString::Format("Set FP1_%d value", register_index)), wxString::Format("%lf", register_value)); + if (set_value_dialog.ShowModal() == wxID_OK) + { + const double new_value = std::stod(set_value_dialog.GetValue().ToStdString()); + debuggerState.debugSession.hCPU->fpr[register_index].fp1 = new_value; + debuggerState.debugSession.ppcSnapshot.fpr[register_index].fp1 = new_value; + OnUpdateView(); + } + + return; + } + + event.Skip(); +} + +void RegisterWindow::OnFPViewModePress(wxCommandEvent& event) +{ + m_show_double_values = !m_show_double_values; + + for (int i = 0; i < 32; ++i) + { + const auto value0 = dynamic_cast(FindWindow(kRegisterValueFPR0_0 + i)); + const auto value1 = dynamic_cast(FindWindow(kRegisterValueFPR1_0 + i)); + wxASSERT(value0); + wxASSERT(value1); + + if (m_show_double_values) + { + value0->SetLabelText(wxString::Format("%lf", debuggerState.debugSession.ppcSnapshot.fpr[i].fp0)); + value1->SetLabelText(wxString::Format("%lf", debuggerState.debugSession.ppcSnapshot.fpr[i].fp1)); + } + else + { + value0->SetLabelText(wxString::Format("%016llx", debuggerState.debugSession.ppcSnapshot.fpr[i].fp0int)); + value1->SetLabelText(wxString::Format("%016llx", debuggerState.debugSession.ppcSnapshot.fpr[i].fp1int)); + } + } +} + +void RegisterWindow::OnValueContextMenu(wxContextMenuEvent& event) +{ + wxMenu menu; + + menu.Append(kContextMenuZero, _("&Zero")); + menu.Append(kContextMenuInc, _("&Increment")); + menu.Append(kContextMenuDec, _("&Decrement")); + menu.AppendSeparator(); + menu.Append(kContextMenuCopy, _("&Copy")); + menu.AppendSeparator(); + menu.Append(kContextMenuGotoDisasm, _("&Goto Disasm")); + menu.Append(kContextMenuGotoDump, _("G&oto Dump")); + + m_context_ctrl = dynamic_cast(event.GetEventObject()); + + PopupMenu(&menu); +} + +void RegisterWindow::OnValueContextMenuSelected(wxCommandEvent& event) +{ + wxASSERT(m_context_ctrl); + + switch (event.GetId()) + { + case kContextMenuZero: + break; + case kContextMenuInc: + break; + case kContextMenuDec: + break; + case kContextMenuCopy: + break; + case kContextMenuGotoDisasm: + break; + case kContextMenuGotoDump: + break; + } +} diff --git a/src/gui/debugger/RegisterWindow.h b/src/gui/debugger/RegisterWindow.h new file mode 100644 index 00000000..ad1be800 --- /dev/null +++ b/src/gui/debugger/RegisterWindow.h @@ -0,0 +1,26 @@ +#pragma once +#include "Cafe/HW/Espresso/Debugger/Debugger.h" + +class DebuggerWindow2; + +class RegisterWindow : public wxFrame +{ +public: + RegisterWindow(DebuggerWindow2& parent, const wxPoint& main_position, const wxSize& main_size); + + void OnMainMove(const wxPoint& position, const wxSize& main_size); + void OnUpdateView(); + +private: + void OnMouseDClickEvent(wxMouseEvent& event); + void OnFPViewModePress(wxCommandEvent& event); + void OnValueContextMenu(wxContextMenuEvent& event); + void OnValueContextMenuSelected(wxCommandEvent& event); + + void UpdateIntegerRegister(wxTextCtrl* label, wxTextCtrl* value, uint32 registerValue, bool hasChanged); + + PPCSnapshot m_prev_snapshot; + bool m_show_double_values; + + wxTextCtrl* m_context_ctrl; +}; \ No newline at end of file diff --git a/src/gui/debugger/SymbolCtrl.cpp b/src/gui/debugger/SymbolCtrl.cpp new file mode 100644 index 00000000..394c64f4 --- /dev/null +++ b/src/gui/debugger/SymbolCtrl.cpp @@ -0,0 +1,163 @@ +#include "gui/debugger/SymbolCtrl.h" +#include "gui/guiWrapper.h" +#include "Cafe/OS/RPL/rpl_symbol_storage.h" +#include "Cafe/HW/Espresso/Debugger/Debugger.h" + +enum ItemColumns +{ + ColumnName = 0, + ColumnAddress, + ColumnModule, +}; + +SymbolListCtrl::SymbolListCtrl(wxWindow* parent, const wxWindowID& id, const wxPoint& pos, const wxSize& size) : + wxListCtrl(parent, id, pos, size, wxLC_REPORT | wxLC_VIRTUAL) +{ + wxListItem col0; + col0.SetId(ColumnName); + col0.SetText(_("Name")); + col0.SetWidth(200); + InsertColumn(ColumnName, col0); + + wxListItem col1; + col1.SetId(ColumnAddress); + col1.SetText(_("Address")); + col1.SetWidth(75); + col1.SetAlign(wxLIST_FORMAT_RIGHT); + InsertColumn(ColumnAddress, col1); + + wxListItem col2; + col2.SetId(ColumnModule); + col2.SetText(_("Module")); + col2.SetWidth(75); + col2.SetAlign(wxLIST_FORMAT_RIGHT); + InsertColumn(ColumnModule, col2); + + Bind(wxEVT_LIST_ITEM_ACTIVATED, &SymbolListCtrl::OnLeftDClick, this); + Bind(wxEVT_LIST_ITEM_RIGHT_CLICK, &SymbolListCtrl::OnRightClick, this); + + m_list_filter = ""; + + OnGameLoaded(); + + SetItemCount(m_data.size()); +} + +void SymbolListCtrl::OnGameLoaded() +{ + m_data.clear(); + long itemId = 0; + const auto symbol_map = rplSymbolStorage_lockSymbolMap(); + for (auto const& [address, symbol_info] : symbol_map) + { + if (symbol_info == nullptr || symbol_info->symbolName == nullptr) + continue; + + auto new_entry = m_data.try_emplace( + symbol_info->address, + (char*)(symbol_info->symbolName), + (char*)(symbol_info->libName), + "", + false + ); + + new_entry.first->second.searchName += new_entry.first->second.name; + new_entry.first->second.searchName += new_entry.first->second.libName; + new_entry.first->second.searchName.MakeLower(); + + if (m_list_filter == "") + new_entry.first->second.visible = true; + else if (new_entry.first->second.searchName.Contains(m_list_filter)) + new_entry.first->second.visible = true; + } + rplSymbolStorage_unlockSymbolMap(); + + SetItemCount(m_data.size()); + RefreshItems(GetTopItem(), GetTopItem() + GetCountPerPage() + 1); +} + +wxString SymbolListCtrl::OnGetItemText(long item, long column) const +{ + if (item >= GetItemCount()) + return wxEmptyString; + + long visible_idx = 0; + for (const auto& [address, symbol] : m_data) + { + if (!symbol.visible) + continue; + + if (item == visible_idx) + { + if (column == ColumnName) + return wxString(symbol.name); + if (column == ColumnAddress) + return wxString::Format("%08x", address); + if (column == ColumnModule) + return wxString(symbol.libName); + } + visible_idx++; + } + + return wxEmptyString; +} + + +void SymbolListCtrl::OnLeftDClick(wxListEvent& event) +{ + long selected = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + if (selected == -1) + return; + const auto text = GetItemText(selected, ColumnAddress); + const auto address = std::stoul(text.ToStdString(), nullptr, 16); + if (address == 0) + return; + debuggerState.debugSession.instructionPointer = address; + debuggerWindow_moveIP(); +} + +void SymbolListCtrl::OnRightClick(wxListEvent& event) +{ + long selected = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + if (selected == -1) + return; + auto text = GetItemText(selected, ColumnAddress); + text = "0x" + text; +#if BOOST_OS_WINDOWS + if (OpenClipboard(nullptr)) + { + EmptyClipboard(); + const HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, text.size()+1); + if (hGlobal) + { + memcpy(GlobalLock(hGlobal), text.c_str(), text.size() + 1); + GlobalUnlock(hGlobal); + + SetClipboardData(CF_TEXT, hGlobal); + GlobalFree(hGlobal); + } + CloseClipboard(); + } +#endif +} + +void SymbolListCtrl::ChangeListFilter(std::string filter) +{ + m_list_filter = wxString(filter).MakeLower(); + + size_t visible_entries = m_data.size(); + for (auto& [address, symbol] : m_data) + { + if (m_list_filter == "") + symbol.visible = true; + else if (symbol.searchName.Contains(m_list_filter)) + symbol.visible = true; + else + { + visible_entries--; + symbol.visible = false; + } + } + SetItemCount(visible_entries); + RefreshItems(GetTopItem(), GetTopItem() + GetCountPerPage() + 1); +} \ No newline at end of file diff --git a/src/gui/debugger/SymbolCtrl.h b/src/gui/debugger/SymbolCtrl.h new file mode 100644 index 00000000..9aebdedc --- /dev/null +++ b/src/gui/debugger/SymbolCtrl.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +class SymbolListCtrl : public wxListCtrl +{ +public: + SymbolListCtrl(wxWindow* parent, const wxWindowID& id, const wxPoint& pos, const wxSize& size); + void OnGameLoaded(); + + void ChangeListFilter(std::string filter); +private: + struct SymbolItem { + SymbolItem() {} + SymbolItem(wxString name, wxString libName, wxString searchName, bool visible) : name(name), libName(libName), searchName(searchName), visible(visible) {} + + + wxString name; + wxString libName; + wxString searchName; + bool visible; + }; + + std::map m_data; + wxString m_list_filter; + + wxString OnGetItemText(long item, long column) const; + void OnLeftDClick(wxListEvent& event); + void OnRightClick(wxListEvent& event); +}; diff --git a/src/gui/debugger/SymbolWindow.cpp b/src/gui/debugger/SymbolWindow.cpp new file mode 100644 index 00000000..7ef0c100 --- /dev/null +++ b/src/gui/debugger/SymbolWindow.cpp @@ -0,0 +1,55 @@ +#include "gui/wxgui.h" +#include "gui/guiWrapper.h" +#include "gui/debugger/SymbolWindow.h" +#include "gui/debugger/DebuggerWindow2.h" +#include "Cafe/HW/Espresso/Debugger/Debugger.h" +#include "Cafe/OS/RPL/rpl_symbol_storage.h" + +enum ItemColumns +{ + ColumnName = 0, + ColumnAddress, + ColumnModule, +}; + +SymbolWindow::SymbolWindow(DebuggerWindow2& parent, const wxPoint& main_position, const wxSize& main_size) + : wxFrame(&parent, wxID_ANY, wxT("Symbol Window"), wxDefaultPosition, wxSize(600, 250), wxSYSTEM_MENU | wxCAPTION | wxCLIP_CHILDREN | wxRESIZE_BORDER | wxFRAME_FLOAT_ON_PARENT) +{ + this->wxWindowBase::SetBackgroundColour(*wxWHITE); + + wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL); + m_symbol_ctrl = new SymbolListCtrl(this, wxID_ANY, wxDefaultPosition, wxDefaultSize); + main_sizer->Add(m_symbol_ctrl, 1, wxEXPAND); + + m_filter = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_NO_VSCROLL); + m_filter->Bind(wxEVT_TEXT, &SymbolWindow::OnFilterChanged, this); + main_sizer->Add(m_filter, 0, wxALL | wxEXPAND, 5); + + this->SetSizer(main_sizer); + this->wxWindowBase::Layout(); + + this->Centre(wxHORIZONTAL); + + if (parent.GetConfig().data().pin_to_main) + OnMainMove(main_position, main_size); +} + +void SymbolWindow::OnMainMove(const wxPoint& main_position, const wxSize& main_size) +{ + wxSize size(420, 250); + this->SetSize(size); + + wxPoint position = main_position; + position.x -= 420; + this->SetPosition(position); +} + +void SymbolWindow::OnGameLoaded() +{ + m_symbol_ctrl->OnGameLoaded(); +} + +void SymbolWindow::OnFilterChanged(wxCommandEvent& event) +{ + m_symbol_ctrl->ChangeListFilter(m_filter->GetValue().ToStdString()); +} diff --git a/src/gui/debugger/SymbolWindow.h b/src/gui/debugger/SymbolWindow.h new file mode 100644 index 00000000..2d4cd25f --- /dev/null +++ b/src/gui/debugger/SymbolWindow.h @@ -0,0 +1,22 @@ +#pragma once + +#include "gui/debugger/SymbolCtrl.h" + +class DebuggerWindow2; + +class SymbolWindow : public wxFrame +{ +public: + SymbolWindow(DebuggerWindow2& parent, const wxPoint& main_position, const wxSize& main_size); + + void OnMainMove(const wxPoint& position, const wxSize& main_size); + void OnGameLoaded(); + + void OnLeftDClick(wxListEvent& event); + +private: + wxTextCtrl* m_filter; + SymbolListCtrl* m_symbol_ctrl; + + void OnFilterChanged(wxCommandEvent& event); +}; \ No newline at end of file diff --git a/src/gui/dialogs/CreateAccount/wxCreateAccountDialog.cpp b/src/gui/dialogs/CreateAccount/wxCreateAccountDialog.cpp new file mode 100644 index 00000000..49e7f5ec --- /dev/null +++ b/src/gui/dialogs/CreateAccount/wxCreateAccountDialog.cpp @@ -0,0 +1,100 @@ +#include "gui/dialogs/CreateAccount/wxCreateAccountDialog.h" +#include "Cafe/Account/Account.h" + +#include +#include +#include +#include +#include +#include +#include "util/helpers/helpers.h" + +wxCreateAccountDialog::wxCreateAccountDialog(wxWindow* parent) + : wxDialog(parent, wxID_ANY, _("Create new account")) +{ + auto* main_sizer = new wxFlexGridSizer(0, 2, 0, 0); + main_sizer->AddGrowableCol(1); + main_sizer->SetFlexibleDirection(wxBOTH); + main_sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); + + main_sizer->Add(new wxStaticText(this, wxID_ANY, wxT("PersistentId")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + + m_persistent_id = new wxTextCtrl(this, wxID_ANY, fmt::format("{:x}", Account::GetNextPersistentId())); + m_persistent_id->SetToolTip(_("The persistent id is the internal folder name used for your saves. Only change this if you are importing saves from a Wii U with a specific id")); + main_sizer->Add(m_persistent_id, 1, wxALL | wxEXPAND, 5); + + main_sizer->Add(new wxStaticText(this, wxID_ANY, wxT("Mii name")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + + m_mii_name = new wxTextCtrl(this, wxID_ANY); + m_mii_name->SetFocus(); + m_mii_name->SetMaxLength(10); + main_sizer->Add(m_mii_name, 1, wxALL | wxEXPAND, 5); + + main_sizer->Add(0, 0, 1, wxEXPAND, 5); + { + auto* button_sizer = new wxBoxSizer(wxHORIZONTAL); + + m_ok_button = new wxButton(this, wxID_ANY, _("OK")); + m_ok_button->Bind(wxEVT_BUTTON, &wxCreateAccountDialog::OnOK, this); + button_sizer->Add(m_ok_button, 0, wxALL, 5); + + m_cancel_buton = new wxButton(this, wxID_ANY, _("Cancel")); + m_cancel_buton->Bind(wxEVT_BUTTON, &wxCreateAccountDialog::OnCancel, this); + button_sizer->Add(m_cancel_buton, 0, wxALL, 5); + + main_sizer->Add(button_sizer, 1, wxALIGN_RIGHT, 5); + } + + this->SetSizerAndFit(main_sizer); + this->wxWindowBase::Layout(); +} + +uint32 wxCreateAccountDialog::GetPersistentId() const +{ + const std::string id_string = m_persistent_id->GetValue().c_str().AsChar(); + return ConvertString(id_string, 16); +} + +wxString wxCreateAccountDialog::GetMiiName() const +{ + return m_mii_name->GetValue(); +} + +void wxCreateAccountDialog::OnOK(wxCommandEvent& event) +{ + if(m_persistent_id->IsEmpty()) + { + wxMessageBox(_("No persistent id entered!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + return; + } + + const auto id = GetPersistentId(); + if(id < Account::kMinPersistendId) + { + wxMessageBox(fmt::format(_("The persistent id must be greater than {:x}!").ToStdString(), Account::kMinPersistendId), + _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + return; + } + + const auto& account = Account::GetAccount(id); + if(account.GetPersistentId() == id) + { + const std::wstring msg = fmt::format(_("The persistent id {:x} is already in use by account {}!").ToStdWstring(), + account.GetPersistentId(), std::wstring{ account.GetMiiName() }); + wxMessageBox(msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + return; + } + + if(m_mii_name->IsEmpty()) + { + wxMessageBox(_("Account name may not be empty!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + return; + } + + EndModal(wxID_OK); +} + +void wxCreateAccountDialog::OnCancel(wxCommandEvent& event) +{ + EndModal(wxID_CANCEL); +} \ No newline at end of file diff --git a/src/gui/dialogs/CreateAccount/wxCreateAccountDialog.h b/src/gui/dialogs/CreateAccount/wxCreateAccountDialog.h new file mode 100644 index 00000000..a61cf548 --- /dev/null +++ b/src/gui/dialogs/CreateAccount/wxCreateAccountDialog.h @@ -0,0 +1,18 @@ +#pragma once +#include + +class wxCreateAccountDialog : public wxDialog +{ +public: + wxCreateAccountDialog(wxWindow* parent); + + [[nodiscard]] uint32 GetPersistentId() const; + [[nodiscard]] wxString GetMiiName() const; +private: + class wxTextCtrl* m_persistent_id; + class wxTextCtrl* m_mii_name; + class wxButton* m_ok_button, * m_cancel_buton; + + void OnOK(wxCommandEvent& event); + void OnCancel(wxCommandEvent& event); +}; \ No newline at end of file diff --git a/src/gui/dialogs/SaveImport/SaveImportWindow.cpp b/src/gui/dialogs/SaveImport/SaveImportWindow.cpp new file mode 100644 index 00000000..188d618d --- /dev/null +++ b/src/gui/dialogs/SaveImport/SaveImportWindow.cpp @@ -0,0 +1,330 @@ +#include "SaveImportWindow.h" + +#include "pugixml.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include "zip.h" + +#include "Cafe/Account/Account.h" +#include "config/ActiveSettings.h" +#include "Cafe/OS/libs/coreinit/coreinit_Time.h" +#include "util/helpers/helpers.h" +#include "Cafe/HW/Espresso/PPCState.h" +#include "gui/helpers/wxHelpers.h" + +SaveImportWindow::SaveImportWindow(wxWindow* parent, uint64 title_id) + : wxDialog(parent, wxID_ANY, _("Import save entry"), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxFRAME_TOOL_WINDOW | wxSYSTEM_MENU | wxTAB_TRAVERSAL | wxCLOSE_BOX), + m_title_id(title_id) +{ + auto* sizer = new wxBoxSizer(wxVERTICAL); + + { + auto* row1 = new wxFlexGridSizer(0, 2, 0, 0); + row1->AddGrowableCol(1); + + row1->Add(new wxStaticText(this, wxID_ANY, _("Source")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + m_source_selection = new wxFilePickerCtrl(this, wxID_ANY, wxEmptyString, + _("Select a zipped save file"), + wxStringFormat2(_("Import save entry {}"), "(*.zip)|*.zip")); + m_source_selection->SetMinSize({ 270, -1 }); + row1->Add(m_source_selection, 1, wxALL | wxEXPAND, 5); + + row1->Add(new wxStaticText(this, wxID_ANY, _("Target")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + + const auto& accounts = Account::GetAccounts(); + m_target_selection = new wxComboBox(this, wxID_ANY); + for (const auto& account : accounts) + { + m_target_selection->Append(fmt::format("{:x} ({})", account.GetPersistentId(), + boost::nowide::narrow(account.GetMiiName().data(), account.GetMiiName().size())), + (void*)(uintptr_t)account.GetPersistentId()); + } + row1->Add(m_target_selection, 1, wxALL | wxEXPAND, 5); + + sizer->Add(row1, 0, wxEXPAND, 5); + } + + { + auto* row2 = new wxFlexGridSizer(0, 2, 0, 0); + row2->AddGrowableCol(1); + row2->SetFlexibleDirection(wxBOTH); + row2->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); + + auto* ok_button = new wxButton(this, wxID_ANY, _("OK"), wxDefaultPosition, wxDefaultSize, 0); + ok_button->Bind(wxEVT_BUTTON, &SaveImportWindow::OnImport, this); + row2->Add(ok_button, 0, wxALL, 5); + + auto* cancel_button = new wxButton(this, wxID_ANY, _("Cancel"), wxDefaultPosition, wxDefaultSize, 0); + cancel_button->Bind(wxEVT_BUTTON, [this](auto&) + { + m_return_code = wxCANCEL; + Close(); + }); + row2->Add(cancel_button, 0, wxALIGN_RIGHT | wxALL, 5); + + sizer->Add(row2, 1, wxEXPAND, 5); + } + + this->SetSizerAndFit(sizer); + this->Centre(wxBOTH); +} + +void SaveImportWindow::EndModal(int retCode) +{ + wxDialog::EndModal(retCode); + SetReturnCode(m_return_code); +} + +void SaveImportWindow::OnImport(wxCommandEvent& event) +{ + const auto source_path = m_source_selection->GetPath(); + if (source_path.empty()) + return; + + // try to find cemu_meta file to verify targetid + const std::string zipfile = source_path.ToUTF8().data(); + + int ziperr; + auto* zip = zip_open(zipfile.c_str(), ZIP_RDONLY, &ziperr); + if (zip) + { + const sint32 numEntries = zip_get_num_entries(zip, 0); + for (sint32 i = 0; i < numEntries; i++) + { + zip_stat_t sb{}; + if (zip_stat_index(zip, i, 0, &sb) != 0) + continue; + + if (!boost::equals(sb.name, "cemu_meta")) + continue; + + auto* zip_file = zip_fopen_index(zip, i, 0); + if (zip_file == nullptr) + continue; + + auto buffer = std::make_unique(sb.size); + if (zip_fread(zip_file, buffer.get(), sb.size) == sb.size) + { + std::string_view str{ buffer.get(), sb.size }; + // titleId = {:#016x} + if(boost::starts_with(str, "titleId = ")) + { + const uint64_t titleId = ConvertString(str.substr(sizeof("titleId = ") + 1), 16); + if(titleId != 0 && titleId != m_title_id) + { + const auto msg = wxStringFormat2(_("You are trying to import a savegame for a different title than your currently selected one: {:016x} vs {:016x}\nAre you sure that you want to continue?"), titleId, m_title_id); + const auto res = wxMessageBox(msg, _("Error"), wxYES_NO | wxCENTRE | wxICON_WARNING, this); + if(res == wxNO) + { + m_return_code = wxCANCEL; + Close(); + return; + } + } + } + } + + zip_fclose(zip_file); + + break; + } + + zip_close(zip); + } + + // unzip to tmp folder -> using target path directly now + std::error_code ec; + //auto tmp_source = fs::temp_directory_path(ec); + //if(ec) + //{ + // const auto error_msg = wxStringFormat2(_("Error when getting the temp directory path:\n{}"), GetSystemErrorMessage(ec)); + // wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + // return; + //} + + uint32 target_id = 0; + const auto selection = m_target_selection->GetCurrentSelection(); + if (selection != wxNOT_FOUND) + target_id = (uint32)(uintptr_t)m_target_selection->GetClientData(selection); + + if (target_id == 0) + { + target_id = ConvertString(m_target_selection->GetValue().ToStdString(), 16); + if (target_id < Account::kMinPersistendId) + { + const auto msg = wxStringFormat2(_("The given account id is not valid!\nIt must be a hex number bigger or equal than {:08x}"), Account::kMinPersistendId); + wxMessageBox(msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + return; + } + + } + + fs::path target_path = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/user/{:08x}", (uint32)(m_title_id >> 32), (uint32)(m_title_id & 0xFFFFFFFF), target_id); + if (fs::exists(target_path)) + { + if (!fs::is_directory(target_path)) + { + const auto msg = wxStringFormat2(_("There's already a file at the target directory:\n{}"), _utf8Wrapper(target_path)); + wxMessageBox(msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + m_return_code = wxCANCEL; + Close(); + return; + } + + const auto msg = _("There's already a save game available for the target account, do you want to overwrite it?\nThis will delete the existing save files for the account and replace them."); + const auto result = wxMessageBox(msg, _("Error"), wxYES_NO | wxCENTRE | wxICON_WARNING, this); + if (result == wxNO) + { + m_return_code = wxCANCEL; + Close(); + return; + } + + std::error_code ec; + while (fs::exists(target_path, ec) || ec) + { + fs::remove_all(target_path, ec); + + if (ec) + { + const auto error_msg = wxStringFormat2(_("Error when trying to delete the former save game:\n{}"), GetSystemErrorMessage(ec)); + wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE, this); + return; + } + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + } + + // extract zip file + + //tmp_source.append("Cemu").append(fmt::format("{}", rand())); + + //tmp_source = getMlcPath(L"usr/save"); + //tmp_source /= fmt::format("{:08x}/{:08x}/user/{:08x}_tmp", (uint32)(m_title_id >> 32), (uint32)(m_title_id & 0xFFFFFFFF), target_id); + auto tmp_source = target_path; + + fs::create_directories(tmp_source, ec); + if (ec) + { + const auto error_msg = wxStringFormat2(_("Error when creating the extraction path:\n{}"), GetSystemErrorMessage(ec)); + wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + return; + } + + zip = zip_open(zipfile.c_str(), ZIP_RDONLY, &ziperr); + if (!zip) + { + const auto error_msg = wxStringFormat2(_("Error when opening the import zip file:\n{}"), GetSystemErrorMessage(ec)); + wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + return; + } + + sint32 numEntries = zip_get_num_entries(zip, 0); + for (sint32 i = 0; i < numEntries; i++) + { + zip_stat_t sb{}; + if (zip_stat_index(zip, i, 0, &sb) != 0) + { + forceLog_printf("zip stat index failed on %d entry", i); + continue; + } + + if (boost::starts_with(sb.name, "../") || boost::starts_with(sb.name, "..\\")) + continue; // bad path + + if (boost::equals(sb.name, "cemu_meta")) + continue; + + size_t sb_name_len = strlen(sb.name); + if (sb_name_len == 0) + continue; + + const auto path = fs::path(tmp_source).append(sb.name); + if (sb.name[sb_name_len - 1] == '/') + { + fs::create_directories(path, ec); + if (ec) + forceLog_printf("can't create directory %s: %s", sb.name, ec.message().c_str()); + + continue; + } + + if (sb.size == 0) + continue; + + if (sb.size > (1024 * 1024 * 128)) + continue; // skip unusually huge files + + auto* zip_file = zip_fopen_index(zip, i, 0); + if (zip_file == nullptr) + continue; + + auto buffer = std::make_unique(sb.size); + if (zip_fread(zip_file, buffer.get(), sb.size) == sb.size) + { + std::ofstream file(path, std::ios::out | std::ios::binary); + if (file.is_open()) + file.write(buffer.get(), sb.size); + } + + zip_fclose(zip_file); + } + + zip_close(zip); + // extracted all files to tmp_source + + // edit meta saveinfo.xml + fs::path saveinfo = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/meta/saveinfo.xml", (uint32)(m_title_id >> 32), (uint32)(m_title_id & 0xFFFFFFFF)); + if (fs::exists(saveinfo) || fs::is_regular_file(saveinfo)) + { + pugi::xml_document doc; + if (doc.load_file(saveinfo.c_str())) + { + auto info_node = doc.child("info"); + if (info_node) + { + // delete old node if available + auto old_persistend_id_string = fmt::format("{:08x}", target_id); + const auto node_entry = info_node.find_child([&old_persistend_id_string](const pugi::xml_node& node) + { + return boost::iequals(node.attribute("persistentId").as_string(), old_persistend_id_string); + }); + // add save-entry node if not available yet + if (!node_entry) + { + // add new node + auto new_persistend_id_string = fmt::format("{:08x}", target_id); + auto new_node = info_node.append_child("account"); + new_node.append_attribute("persistentId").set_value(new_persistend_id_string.c_str()); + auto timestamp = new_node.append_child("timestamp"); + timestamp.text().set(fmt::format("{:016x}", coreinit::coreinit_getOSTime() / ESPRESSO_TIMER_CLOCK).c_str()); // TODO time not initialized yet? + + if(!doc.save_file(saveinfo.c_str())) + forceLog_printf("couldn't insert save entry in saveinfo.xml: %s", saveinfo.generic_u8string().c_str()); + } + } + } + } // todo create saveinfo if doesnt exist + + + /*wxMessageBox(fmt::format("{}\n{}", tmp_source.generic_u8string(), target_path.generic_u8string()), _("Error"), wxOK | wxCENTRE, this); + + fs::rename(tmp_source, target_path, ec); + if (ec) + { + const auto error_msg = wxStringFormat2(_("Error when trying to move the extracted save game:\n{}"), GetSystemErrorMessage(ec)); + wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE, this); + return; + }*/ + + m_target_id = target_id; + m_return_code = wxOK; + Close(); +} diff --git a/src/gui/dialogs/SaveImport/SaveImportWindow.h b/src/gui/dialogs/SaveImport/SaveImportWindow.h new file mode 100644 index 00000000..209df80a --- /dev/null +++ b/src/gui/dialogs/SaveImport/SaveImportWindow.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +class SaveImportWindow : public wxDialog +{ +public: + SaveImportWindow(wxWindow* parent, uint64 title_id); + + void EndModal(int retCode) override; + + uint32 GetTargetPersistentId() const { return m_target_id; } +private: + void OnImport(wxCommandEvent& event); + + class wxFilePickerCtrl* m_source_selection; + class wxComboBox* m_target_selection; + + uint32 m_target_id = 0; + const uint64 m_title_id; + const fs::path m_source_file; + int m_return_code = wxCANCEL; +}; + + diff --git a/src/gui/dialogs/SaveImport/SaveTransfer.cpp b/src/gui/dialogs/SaveImport/SaveTransfer.cpp new file mode 100644 index 00000000..bcccf1dd --- /dev/null +++ b/src/gui/dialogs/SaveImport/SaveTransfer.cpp @@ -0,0 +1,198 @@ +#include "SaveTransfer.h" + +#include "pugixml.hpp" +#include +#include +#include +#include +#include +#include +#include + +#include "Cafe/Account/Account.h" +#include "config/ActiveSettings.h" +#include "util/helpers/helpers.h" +#include "gui/helpers/wxHelpers.h" + +SaveTransfer::SaveTransfer(wxWindow* parent, uint64 title_id, const wxString& source_account, uint32 source_id) +: wxDialog(parent, wxID_ANY, _("Save transfer"), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxFRAME_TOOL_WINDOW | wxSYSTEM_MENU | wxTAB_TRAVERSAL | wxCLOSE_BOX), + m_title_id(title_id), m_source_id(source_id) +{ + auto* sizer = new wxBoxSizer(wxVERTICAL); + + { + auto* row1 = new wxFlexGridSizer(0, 2, 0, 0); + row1->AddGrowableCol(1); + + row1->Add(new wxStaticText(this, wxID_ANY, _("Source")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + auto* source_choice = new wxChoice(this, wxID_ANY); + source_choice->SetMinSize({170, -1}); + source_choice->Append(source_account); + source_choice->SetSelection(0); + row1->Add(source_choice, 1, wxALL | wxEXPAND, 5); + + row1->Add(new wxStaticText(this, wxID_ANY, _("Target")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + + const auto& accounts = Account::GetAccounts(); + m_target_selection = new wxComboBox(this, wxID_ANY); + for (const auto& account : accounts) + { + if (account.GetPersistentId() == m_source_id) + continue; + + m_target_selection->Append(fmt::format("{:x} ({})", account.GetPersistentId(), + boost::nowide::narrow(account.GetMiiName().data(), account.GetMiiName().size())), + (void*)(uintptr_t)account.GetPersistentId()); + } + row1->Add(m_target_selection, 1, wxALL | wxEXPAND, 5); + + sizer->Add(row1, 0, wxEXPAND, 5); + } + + { + auto* row2 = new wxFlexGridSizer(0, 2, 0, 0); + row2->AddGrowableCol(1); + row2->SetFlexibleDirection(wxBOTH); + row2->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); + + auto* ok_button = new wxButton(this, wxID_ANY, _("OK"), wxDefaultPosition, wxDefaultSize, 0); + ok_button->Bind(wxEVT_BUTTON, &SaveTransfer::OnTransfer, this); + row2->Add(ok_button, 0, wxALL, 5); + + auto* cancel_button = new wxButton(this, wxID_ANY, _("Cancel"), wxDefaultPosition, wxDefaultSize, 0); + cancel_button->Bind(wxEVT_BUTTON, [this](auto&) + { + m_return_code = wxCANCEL; + Close(); + }); + row2->Add(cancel_button, 0, wxALIGN_RIGHT | wxALL, 5); + + sizer->Add(row2, 1, wxEXPAND, 5); + } + + this->SetSizerAndFit(sizer); + this->Centre(wxBOTH); +} + +void SaveTransfer::EndModal(int retCode) +{ + wxDialog::EndModal(retCode); + SetReturnCode(m_return_code); +} + +void SaveTransfer::OnTransfer(wxCommandEvent& event) +{ + uint32 target_id = 0; + const auto selection = m_target_selection->GetCurrentSelection(); + if(selection != wxNOT_FOUND) + target_id = (uint32)(uintptr_t)m_target_selection->GetClientData(selection); + + if (target_id == 0) + { + target_id = ConvertString(m_target_selection->GetValue().ToStdString(), 16); + if(target_id < Account::kMinPersistendId) + { + const auto msg = wxStringFormat2(_("The given account id is not valid!\nIt must be a hex number bigger or equal than {:08x}"), Account::kMinPersistendId); + wxMessageBox(msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + return; + } + + } + + const fs::path source_path = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/user/{:08x}", (uint32)(m_title_id >> 32), (uint32)(m_title_id & 0xFFFFFFFF), m_source_id); + if (!fs::exists(source_path) || !fs::is_directory(source_path)) + return; + + const fs::path target_path = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/user/{:08x}", (uint32)(m_title_id >> 32), (uint32)(m_title_id & 0xFFFFFFFF), target_id); + if (fs::exists(target_path)) + { + if(!fs::is_directory(target_path)) + { + const auto msg = wxStringFormat2(_("There's already a file at the target directory:\n{}"), _utf8Wrapper(target_path)); + wxMessageBox(msg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + m_return_code = wxCANCEL; + Close(); + return; + } + + const auto msg = _("There's already a save game available for the target account, do you want to overwrite it?\nThis will delete the existing save files for the account and replace them."); + const auto result = wxMessageBox(msg, _("Error"), wxYES_NO | wxCENTRE | wxICON_WARNING, this); + if(result == wxNO) + { + m_return_code = wxCANCEL; + Close(); + return; + } + + std::error_code ec; + while (fs::exists(target_path, ec) || ec) + { + fs::remove_all(target_path, ec); + + if (ec) + { + const auto error_msg = wxStringFormat2(_("Error when trying to delete the former save game:\n{}"), GetSystemErrorMessage(ec)); + wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE, this); + return; + } + + // wait so filesystem doesnt + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + } + + // edit meta saveinfo.xml + bool meta_file_edited = false; + const fs::path saveinfo = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/meta/saveinfo.xml", (uint32)(m_title_id >> 32), (uint32)(m_title_id & 0xFFFFFFFF)); + if (fs::exists(saveinfo) || fs::is_regular_file(saveinfo)) + { + pugi::xml_document doc; + if (doc.load_file(saveinfo.c_str())) + { + auto info_node = doc.child("info"); + if (info_node) + { + // delete old node if available + auto old_persistend_id_string = fmt::format("{:08x}", target_id); + const auto delete_entry = info_node.find_child([&old_persistend_id_string](const pugi::xml_node& node) + { + return boost::iequals(node.attribute("persistentId").as_string(), old_persistend_id_string); + }); + if (delete_entry) + info_node.remove_child(delete_entry); + + + // move new node + auto new_persistend_id_string = fmt::format("{:08x}", m_source_id); + auto move_entry = info_node.find_child([&new_persistend_id_string](const pugi::xml_node& node) + { + return boost::iequals(node.attribute("persistentId").as_string(), new_persistend_id_string); + }); + if(move_entry) + move_entry.attribute("persistentId").set_value(old_persistend_id_string.c_str()); + else + { + // TODO: create if not found! (shouldnt happen though) + } + + meta_file_edited = doc.save_file(saveinfo.c_str()); + } + } + } + + if (!meta_file_edited) + forceLog_printf("SaveTransfer::OnTransfer: couldn't update save entry in saveinfo.xml: %s", saveinfo.generic_u8string().c_str()); + + std::error_code ec; + fs::rename(source_path, target_path, ec); + if (ec) + { + const auto error_msg = wxStringFormat2(_("Error when trying to move the save game:\n{}"), GetSystemErrorMessage(ec)); + wxMessageBox(error_msg, _("Error"), wxOK | wxCENTRE, this); + return; + } + + m_target_id = target_id; + m_return_code = wxOK; + Close(); +} diff --git a/src/gui/dialogs/SaveImport/SaveTransfer.h b/src/gui/dialogs/SaveImport/SaveTransfer.h new file mode 100644 index 00000000..6f208fb8 --- /dev/null +++ b/src/gui/dialogs/SaveImport/SaveTransfer.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +class wxComboBox; + +class SaveTransfer : public wxDialog +{ +public: + SaveTransfer(wxWindow* parent, uint64 title_id, const wxString& source_account, uint32 source_id); + + void EndModal(int retCode) override; + + uint32 GetTargetPersistentId() const { return m_target_id; } +private: + void OnTransfer(wxCommandEvent& event); + + wxComboBox* m_target_selection; + + uint32 m_target_id = 0; + const uint64 m_title_id; + const uint32 m_source_id; + int m_return_code = wxCANCEL; +}; diff --git a/src/gui/guiWrapper.cpp b/src/gui/guiWrapper.cpp new file mode 100644 index 00000000..eed08425 --- /dev/null +++ b/src/gui/guiWrapper.cpp @@ -0,0 +1,299 @@ +#include "gui/wxgui.h" +#include "gui/guiWrapper.h" +#include "gui/CemuApp.h" +#include "gui/MainWindow.h" +#include "gui/debugger/DebuggerWindow2.h" +#include "Cafe/HW/Latte/Core/Latte.h" +#include "config/ActiveSettings.h" +#include "config/CemuConfig.h" +#include "Cafe/HW/Latte/Renderer/Renderer.h" +#include "Cafe/CafeSystem.h" + +#include "wxHelper.h" + +WindowInfo g_window_info {}; + +std::shared_mutex g_mutex; +MainWindow* g_mainFrame = nullptr; + +#if BOOST_OS_WINDOWS +void wxMatchCemuhookEventIds(); + +void _wxLaunch() +{ + SetThreadName("MainThread_UI"); + wxEntry(); +} + +#endif + +void gui_create() +{ + SetThreadName("MainThread"); +#if BOOST_OS_WINDOWS + wxMatchCemuhookEventIds(); + // on Windows wxWidgets there is a bug where wxDirDialog->ShowModal will deadlock in Windows internals somehow + // moving the UI thread off the main thread fixes this + std::thread t = std::thread(_wxLaunch); + t.join(); +#else + int argc = 0; + char* argv[1]{}; + wxEntry(argc, argv); +#endif +} + +WindowInfo& gui_getWindowInfo() +{ + return g_window_info; +} + +void gui_updateWindowTitles(bool isIdle, bool isLoading, double fps) +{ + std::string windowText; + windowText = fmt::format("" EMULATOR_NAME " {}.{}{}", EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_SUFFIX); + + if (isIdle) + { + if (g_mainFrame) + g_mainFrame->AsyncSetTitle(windowText); + return; + } + if (isLoading) + { + windowText.append(" - loading..."); + if (g_mainFrame) + g_mainFrame->AsyncSetTitle(windowText); + return; + } + + const char* renderer = ""; + if(g_renderer) + { + switch(g_renderer->GetType()) + { + case RendererAPI::OpenGL: + renderer = "[OpenGL]"; + break; + case RendererAPI::Vulkan: + renderer = "[Vulkan]"; + break; + default: ; + } + } + + // get GPU vendor/mode + const char* graphicMode = "[Generic]"; + if (LatteGPUState.glVendor == GLVENDOR_AMD) + graphicMode = "[AMD GPU]"; + else if (LatteGPUState.glVendor == GLVENDOR_INTEL_LEGACY) + graphicMode = "[Intel GPU - Legacy]"; + else if (LatteGPUState.glVendor == GLVENDOR_INTEL_NOLEGACY) + graphicMode = "[Intel GPU]"; + else if (LatteGPUState.glVendor == GLVENDOR_INTEL) + graphicMode = "[Intel GPU]"; + else if (LatteGPUState.glVendor == GLVENDOR_NVIDIA) + graphicMode = "[NVIDIA GPU]"; + + const uint64 titleId = CafeSystem::GetForegroundTitleId(); + windowText.append(fmt::format(" - FPS: {:.2f} {} {} [TitleId: {:08x}-{:08x}]", (double)fps, renderer, graphicMode, (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF))); + + if (ActiveSettings::IsOnlineEnabled()) + windowText.append(" [Online]"); + + windowText.append(" "); + windowText.append(CafeSystem::GetForegroundTitleName()); + // append region + CafeConsoleRegion region = CafeSystem::GetForegroundTitleRegion(); + uint16 titleVersion = CafeSystem::GetForegroundTitleVersion(); + if (region == CafeConsoleRegion::JPN) + windowText.append(fmt::format(" [JP v{}]", titleVersion)); + else if (region == CafeConsoleRegion::USA) + windowText.append(fmt::format(" [US v{}]", titleVersion)); + else if (region == CafeConsoleRegion::EUR) + windowText.append(fmt::format(" [EU v{}]", titleVersion)); + else + windowText.append(fmt::format(" [v{}]", titleVersion)); + + std::shared_lock lock(g_mutex); + if (g_mainFrame) + { + g_mainFrame->AsyncSetTitle(windowText); + auto* pad = g_mainFrame->GetPadView(); + if (pad) + pad->AsyncSetTitle(fmt::format("GamePad View - FPS: {:.02f}", fps)); + } +} + +void gui_getWindowSize(int* w, int* h) +{ + *w = g_window_info.width; + *h = g_window_info.height; +} + +void gui_getPadWindowSize(int* w, int* h) +{ + if (g_window_info.pad_open) + { + *w = g_window_info.pad_width; + *h = g_window_info.pad_height; + } + else + { + *w = 0; + *h = 0; + } +} + +bool gui_isPadWindowOpen() +{ + return g_window_info.pad_open; +} + +#if BOOST_OS_LINUX +#include +#include + +typedef void GdkDisplay; +#endif + +void gui_initHandleContextFromWxWidgetsWindow(WindowHandleInfo& handleInfoOut, class wxWindow* wxw) +{ +#if BOOST_OS_WINDOWS > 0 + handleInfoOut.hwnd = wxw->GetHWND(); +#else + /* dynamically retrieve GTK imports so we dont have to include and link the whole lib */ + void (*dyn_gtk_widget_realize)(GtkWidget *widget); + dyn_gtk_widget_realize = (void(*)(GtkWidget* widget))dlsym(RTLD_NEXT, "gtk_widget_realize"); + + GdkWindow* (*dyn_gtk_widget_get_window)(GtkWidget *widget); + dyn_gtk_widget_get_window = (GdkWindow*(*)(GtkWidget* widget))dlsym(RTLD_NEXT, "gtk_widget_get_window"); + + GdkDisplay* (*dyn_gdk_window_get_display)(GdkWindow *widget); + dyn_gdk_window_get_display = (GdkDisplay*(*)(GdkWindow* window))dlsym(RTLD_NEXT, "gdk_window_get_display"); + + Display* (*dyn_gdk_x11_display_get_xdisplay)(GdkDisplay *display); + dyn_gdk_x11_display_get_xdisplay = (Display*(*)(GdkDisplay* display))dlsym(RTLD_NEXT, "gdk_x11_display_get_xdisplay"); + + Window (*dyn_gdk_x11_window_get_xid)(GdkWindow *window); + dyn_gdk_x11_window_get_xid = (Window(*)(GdkWindow *window))dlsym(RTLD_NEXT, "gdk_x11_window_get_xid"); + + if(!dyn_gtk_widget_realize || !dyn_gtk_widget_get_window || + !dyn_gdk_window_get_display || !dyn_gdk_x11_display_get_xdisplay || + !dyn_gdk_x11_window_get_xid) + { + cemuLog_log(LogType::Force, "Unable to load GDK symbols"); + return; + } + + /* end of imports */ + + // get window + GtkWidget* gtkWidget = (GtkWidget*)wxw->GetHandle(); // returns GtkWidget + dyn_gtk_widget_realize(gtkWidget); + GdkWindow* gdkWindow = dyn_gtk_widget_get_window(gtkWidget); + handleInfoOut.xlib_window = dyn_gdk_x11_window_get_xid(gdkWindow); + + // get display + GdkDisplay* gdkDisplay = dyn_gdk_window_get_display(gdkWindow); + handleInfoOut.xlib_display = dyn_gdk_x11_display_get_xdisplay(gdkDisplay); + if(!handleInfoOut.xlib_display) + { + cemuLog_log(LogType::Force, "Unable to get xlib display"); + } +#endif +} + +bool gui_isKeyDown(int key) +{ + if (key >= 256) + return false; + + return g_window_info.keydown[key]; +} + +void gui_notifyGameLoaded() +{ + std::shared_lock lock(g_mutex); + if (g_mainFrame) + { + g_mainFrame->OnGameLoaded(); + g_mainFrame->UpdateSettingsAfterGameLaunch(); + } +} + +void gui_notifyGameExited() +{ + std::shared_lock lock(g_mutex); + if(g_mainFrame) + g_mainFrame->RestoreSettingsAfterGameExited(); +} + +bool gui_isFullScreen() +{ + return g_window_info.is_fullscreen; +} + +bool gui_hasScreenshotRequest() +{ + const bool result = g_window_info.has_screenshot_request; + g_window_info.has_screenshot_request = false; + return result; +} + +extern DebuggerWindow2* g_debugger_window; +void debuggerWindow_updateViewThreadsafe2() +{ + if (g_debugger_window) + { + auto* evt = new wxCommandEvent(wxEVT_UPDATE_VIEW); + wxQueueEvent(g_debugger_window, evt); + } +} + +void debuggerWindow_moveIP() +{ + if (g_debugger_window) + { + auto* evt = new wxCommandEvent(wxEVT_MOVE_IP); + wxQueueEvent(g_debugger_window, evt); + } +} + +void debuggerWindow_notifyDebugBreakpointHit2() +{ + if (g_debugger_window) + { + auto* evt = new wxCommandEvent(wxEVT_BREAKPOINT_HIT); + wxQueueEvent(g_debugger_window, evt); + } +} + +void debuggerWindow_notifyRun() +{ + if (g_debugger_window) + { + auto* evt = new wxCommandEvent(wxEVT_RUN); + wxQueueEvent(g_debugger_window, evt); + } +} + +void debuggerWindow_notifyModuleLoaded(void* module) +{ + if (g_debugger_window) + { + auto* evt = new wxCommandEvent(wxEVT_NOTIFY_MODULE_LOADED); + evt->SetClientData(module); + wxQueueEvent(g_debugger_window, evt); + } +} + +void debuggerWindow_notifyModuleUnloaded(void* module) +{ + if (g_debugger_window) + { + auto* evt = new wxCommandEvent(wxEVT_NOTIFY_MODULE_UNLOADED); + evt->SetClientData(module); + wxQueueEvent(g_debugger_window, evt); + } +} \ No newline at end of file diff --git a/src/gui/guiWrapper.h b/src/gui/guiWrapper.h new file mode 100644 index 00000000..1dfb611f --- /dev/null +++ b/src/gui/guiWrapper.h @@ -0,0 +1,82 @@ +#pragma once + +#include + +#if BOOST_OS_LINUX > 0 +#include "xcb/xproto.h" +#endif + +struct WindowHandleInfo +{ +#if BOOST_OS_WINDOWS + std::atomic hwnd; +#elif BOOST_OS_LINUX + // XLIB + Display* xlib_display{}; + Window xlib_window{}; + + // XCB (not used by GTK so we cant retrieve these without making our own window) + //xcb_connection_t* xcb_con{}; + //xcb_window_t xcb_window{}; + // Wayland + // todo +#endif +}; + +struct WindowInfo +{ + std::atomic_bool app_active; // our app is active/has focus + + std::atomic_int32_t width, height; // client size of main window + + std::atomic_bool pad_open; // if separate pad view is open + std::atomic_int32_t pad_width, pad_height; // client size of pad window + + std::atomic_bool pad_maximized = false; + std::atomic_int32_t restored_pad_x = -1, restored_pad_y = -1; + std::atomic_int32_t restored_pad_width = -1, restored_pad_height = -1; + + std::atomic_bool has_screenshot_request; + std::atomic_bool is_fullscreen; + + std::atomic_bool keydown[256]{}; + + WindowHandleInfo window_main; + WindowHandleInfo window_pad; + + // canvas + WindowHandleInfo canvas_main; + WindowHandleInfo canvas_pad; +}; + +void gui_create(); + +WindowInfo& gui_getWindowInfo(); + +void gui_updateWindowTitles(bool isIdle, bool isLoading, double fps); +void gui_getWindowSize(int* w, int* h); +void gui_getPadWindowSize(int* w, int* h); +bool gui_isPadWindowOpen(); +bool gui_isKeyDown(int key); + +void gui_notifyGameLoaded(); +void gui_notifyGameExited(); + +bool gui_isFullScreen(); + +void gui_initHandleContextFromWxWidgetsWindow(WindowHandleInfo& handleInfoOut, class wxWindow* wxw); + +/* +* Returns true if a screenshot request is queued +* Once this function has returned true, it will reset back to +* false until the next time a screenshot is requested +*/ +bool gui_hasScreenshotRequest(); + +// debugger stuff +void debuggerWindow_updateViewThreadsafe2(); +void debuggerWindow_notifyDebugBreakpointHit2(); +void debuggerWindow_notifyRun(); +void debuggerWindow_moveIP(); +void debuggerWindow_notifyModuleLoaded(void* module); +void debuggerWindow_notifyModuleUnloaded(void* module); \ No newline at end of file diff --git a/src/gui/helpers/wxControlObject.h b/src/gui/helpers/wxControlObject.h new file mode 100644 index 00000000..45cafa44 --- /dev/null +++ b/src/gui/helpers/wxControlObject.h @@ -0,0 +1,44 @@ +#pragma once + +#include + +class wxControlObject : public wxObject +{ +public: + wxControlObject(wxControl* control) + : m_control(control) {} + + template + T* GetControl() const + { + static_assert(std::is_base_of::value, "T must inherit from wxControl"); + return dynamic_cast(m_control); + } + + wxControl* GetControl() const { return m_control; } + +private: + wxControl* m_control; +}; + +class wxControlObjects : public wxObject +{ +public: + wxControlObjects(std::vector controls) + : m_controls(std::move(controls)) {} + + template + T* GetControl(int index) const + { + static_assert(std::is_base_of::value, "T must inherit from wxControl"); + if (index < 0 || index >= m_controls.size()) + return nullptr; + + return dynamic_cast(m_controls[index]); + } + + const std::vector& GetControls() const { return m_controls; } + +private: + std::vector m_controls; +}; \ No newline at end of file diff --git a/src/gui/helpers/wxCustomData.h b/src/gui/helpers/wxCustomData.h new file mode 100644 index 00000000..bd7b0184 --- /dev/null +++ b/src/gui/helpers/wxCustomData.h @@ -0,0 +1,49 @@ +#pragma once + +#include + +template +class wxCustomData : public wxClientData +{ +public: + wxCustomData() + : m_data({}) {} + + wxCustomData(T data) + : m_data(std::move(data)) { } + + const T& GetData() const { return m_data; } + + /// + /// obtains ownership of the data + /// + /// returns the stored data + T get() { return std::move(m_data); } + + /// + /// access to the data without obtaining ownership + /// + /// returns a reference to the stored data + T& ref() { return m_data; } + + bool operator==(const T& o) const { return m_data == o.m_data; } + bool operator!=(const T& o) const { return !(*this == o); } + +private: + T m_data; +}; + +//template +//class wxCustomObject : public wxObject +//{ +//public: +// wxCustomObject(T data) +// : m_data(std::move(data)) { } +// +// const T& GetData() const { return m_data; } +// +// bool operator==(const T& o) const { return m_data == o.m_data; } +// bool operator!=(const T& o) const { return !(*this == o); } +//private: +// T m_data; +//}; diff --git a/src/gui/helpers/wxCustomEvents.cpp b/src/gui/helpers/wxCustomEvents.cpp new file mode 100644 index 00000000..081346c5 --- /dev/null +++ b/src/gui/helpers/wxCustomEvents.cpp @@ -0,0 +1,7 @@ +#include "gui/helpers/wxCustomEvents.h" + +wxDEFINE_EVENT(wxEVT_SET_STATUS_BAR_TEXT, wxSetStatusBarTextEvent); +wxDEFINE_EVENT(wxEVT_SET_GAUGE_VALUE, wxSetGaugeValue); +wxDEFINE_EVENT(wxEVT_REMOVE_ITEM, wxCommandEvent); +wxDEFINE_EVENT(wxEVT_SET_TEXT, wxCommandEvent); +wxDEFINE_EVENT(wxEVT_ENABLE, wxCommandEvent); \ No newline at end of file diff --git a/src/gui/helpers/wxCustomEvents.h b/src/gui/helpers/wxCustomEvents.h new file mode 100644 index 00000000..1eb213be --- /dev/null +++ b/src/gui/helpers/wxCustomEvents.h @@ -0,0 +1,74 @@ +#pragma once + +#include +#include + +class wxStatusBar; +class wxControl; + +wxDECLARE_EVENT(wxEVT_REMOVE_ITEM, wxCommandEvent); +wxDECLARE_EVENT(wxEVT_SET_TEXT, wxCommandEvent); +wxDECLARE_EVENT(wxEVT_ENABLE, wxCommandEvent); + +class wxSetStatusBarTextEvent; +wxDECLARE_EVENT(wxEVT_SET_STATUS_BAR_TEXT, wxSetStatusBarTextEvent); +class wxSetStatusBarTextEvent : public wxCommandEvent +{ +public: + wxSetStatusBarTextEvent(const wxString& text, int number = 0, wxEventType type = wxEVT_SET_STATUS_BAR_TEXT, int id = 0) + : wxCommandEvent(type, id), m_text(text), m_number(number) {} + + wxSetStatusBarTextEvent(const wxSetStatusBarTextEvent& event) + : wxCommandEvent(event), m_text(event.GetText()), m_number(event.GetNumber()) {} + + + [[nodiscard]] const wxString& GetText() const { return m_text; } + [[nodiscard]] int GetNumber() const { return m_number; } + + wxEvent* Clone() const override + { + return new wxSetStatusBarTextEvent(*this); + } + +private: + const wxString m_text; + const int m_number; +}; +class wxSetGaugeValue; +wxDECLARE_EVENT(wxEVT_SET_GAUGE_VALUE, wxSetGaugeValue); +class wxSetGaugeValue : public wxCommandEvent +{ +public: + wxSetGaugeValue(int value, wxGauge* gauge, wxControl* text_ctrl = nullptr, const wxString& text = wxEmptyString, wxEventType type = wxEVT_SET_GAUGE_VALUE, int id = 0) + : wxCommandEvent(type, id), m_gauge_value(value), m_gauge_range(0), m_text_ctrl(text_ctrl), m_text(text) + { + SetEventObject(gauge); + } + + wxSetGaugeValue(int value, int range, wxGauge* gauge, wxControl* text_ctrl = nullptr, const wxString& text = wxEmptyString, wxEventType type = wxEVT_SET_GAUGE_VALUE, int id = 0) + : wxCommandEvent(type, id), m_gauge_value(value), m_gauge_range(range), m_text_ctrl(text_ctrl), m_text(text) + { + SetEventObject(gauge); + } + + wxSetGaugeValue(const wxSetGaugeValue& event) + : wxCommandEvent(event), m_gauge_value(event.GetValue()), m_gauge_range(event.GetRange()), m_text_ctrl(event.GetTextCtrl()), m_text(event.GetText()) + {} + + [[nodiscard]] int GetValue() const { return m_gauge_value; } + [[nodiscard]] int GetRange() const { return m_gauge_range; } + [[nodiscard]] wxGauge* GetGauge() const { return (wxGauge*)GetEventObject(); } + [[nodiscard]] const wxString& GetText() const { return m_text; } + [[nodiscard]] wxControl* GetTextCtrl() const { return m_text_ctrl; } + + wxEvent* Clone() const override + { + return new wxSetGaugeValue(*this); + } + +private: + const int m_gauge_value, m_gauge_range; + wxControl* m_text_ctrl; + const wxString m_text; +}; + diff --git a/src/gui/helpers/wxHelpers.cpp b/src/gui/helpers/wxHelpers.cpp new file mode 100644 index 00000000..d6b273c1 --- /dev/null +++ b/src/gui/helpers/wxHelpers.cpp @@ -0,0 +1,66 @@ +#include "gui/helpers/wxHelpers.h" + +#include +#include +#include +#include + +#include "gui/helpers/wxControlObject.h" + +void wxAutosizeColumn(wxListCtrlBase* ctrl, int col) +{ + ctrl->SetColumnWidth(col, wxLIST_AUTOSIZE_USEHEADER); + int wh = ctrl->GetColumnWidth(col); + ctrl->SetColumnWidth(col, wxLIST_AUTOSIZE); + int wc = ctrl->GetColumnWidth(col); + if (wh > wc) + ctrl->SetColumnWidth(col, wxLIST_AUTOSIZE_USEHEADER); +} + +void wxAutosizeColumns(wxListCtrlBase* ctrl, int col_start, int col_end) +{ + wxWindowUpdateLocker lock(ctrl); + for (int i = col_start; i <= col_end; ++i) + wxAutosizeColumn(ctrl, i); +} + +void update_slider_text(wxCommandEvent& event, const wxFormatString& format /*= "%d%%"*/) +{ + const auto slider = dynamic_cast(event.GetEventObject()); + wxASSERT(slider); + + auto slider_text = dynamic_cast(event.GetEventUserData())->GetControl(); + wxASSERT(slider_text); + + slider_text->SetLabel(wxString::Format(format, slider->GetValue())); +} + +uint32 fix_raw_keycode(uint32 keycode, uint32 raw_flags) +{ +#if BOOST_OS_WINDOWS + const auto flags = (HIWORD(raw_flags) & 0xFFF); + if(keycode == VK_SHIFT) + { + if(flags == 0x2A) + return 160; + else if (flags == 0x36) + return 161; + } + else if (keycode == VK_CONTROL) + { + if (flags == 0x1d) + return 162; + else if (flags == 0x11d) + return 163; + } + else if (keycode == VK_MENU) + { + if ((flags & 0xFF) == 0x38) + return 164; + else if ((flags & 0xFF) == 0x38) + return 165; + } +#endif + + return keycode; +} diff --git a/src/gui/helpers/wxHelpers.h b/src/gui/helpers/wxHelpers.h new file mode 100644 index 00000000..96c8dcfa --- /dev/null +++ b/src/gui/helpers/wxHelpers.h @@ -0,0 +1,126 @@ +#pragma once + +#include +#include +#include + +template <> +struct fmt::formatter : formatter +{ + template + auto format(const wxString& str, FormatContext& ctx) + { + return formatter::format(str.c_str().AsChar(), ctx); + } +}; + +class wxTempEnable +{ +public: + wxTempEnable(wxControl* control, bool state = true) + : m_control(control), m_state(state) + { + m_control->Enable(m_state); + } + + wxTempEnable(const wxTempEnable&) = delete; + wxTempEnable(wxTempEnable&&) noexcept = default; + + ~wxTempEnable() + { + if(m_control) + m_control->Enable(!m_state); + } + +private: + wxControl* m_control; + const bool m_state; +}; + +class wxTempDisable : wxTempEnable +{ +public: + wxTempDisable(wxControl* control) + : wxTempEnable(control, false) {} +}; + +template +wxString wxStringFormat2(const wxString& format, TArgs&&...args) +{ + // ignores locale? + return fmt::format(format.ToStdString(), std::forward(args)...); +} + +template +wxString wxStringFormat2W(const wxString& format, TArgs&&...args) +{ + return fmt::format(format.ToStdWstring(), std::forward(args)...); +} + +// executes a function when destroying the obj +template +class wxDTorFunc +{ +public: + wxDTorFunc(TFunction&& func, TArgs&&... args) + { + auto bound = std::bind(std::forward(func), std::forward(args)...); + // m_function = [bound] { bound(); }; + m_function = [bound{ std::move(bound) }]{ bound(); }; + + } + ~wxDTorFunc() + { + m_function(); + } +private: + std::function m_function; +}; + +void wxAutosizeColumn(wxListCtrlBase* ctrl, int col); +void wxAutosizeColumns(wxListCtrlBase* ctrl, int col_start, int col_end); + +// creates wxString from utf8 string +inline wxString to_wxString(std::string_view str) +{ + return wxString::FromUTF8(str.data(), str.size()); +} + +// creates utf8 std::string from wxString +inline std::string from_wxString(const wxString& str) +{ + const auto tmp = str.ToUTF8(); + return std::string{ tmp.data(), tmp.length() }; +} + + +template +T get_next_sibling(const T element) +{ + static_assert(std::is_pointer_v && std::is_base_of_v>, "element must be a pointer and inherit from wxControl"); + for (auto sibling = element->GetNextSibling(); sibling; sibling = sibling->GetNextSibling()) + { + if (auto result = dynamic_cast(sibling)) + return result; + } + + return nullptr; +} + +template +T get_prev_sibling(const T element) +{ + static_assert(std::is_pointer_v && std::is_base_of_v>, "element must be a pointer and inherit from wxControl"); + for (auto sibling = element->GetPrevSibling(); sibling; sibling = sibling->GetPrevSibling()) + { + auto result = dynamic_cast(sibling); + if (result) + return result; + } + + return nullptr; +} + +void update_slider_text(wxCommandEvent& event, const wxFormatString& format = "%d%%"); + +uint32 fix_raw_keycode(uint32 keycode, uint32 raw_flags); diff --git a/src/gui/helpers/wxLogEvent.h b/src/gui/helpers/wxLogEvent.h new file mode 100644 index 00000000..8cef1d2d --- /dev/null +++ b/src/gui/helpers/wxLogEvent.h @@ -0,0 +1,32 @@ +#pragma once + +#include + +class wxLogEvent; +wxDECLARE_EVENT(EVT_LOG, wxLogEvent); + +class wxLogEvent : public wxCommandEvent +{ +public: + wxLogEvent(const wxString& filter, const wxString& message) + : wxCommandEvent(EVT_LOG), m_filter(filter), m_message(message) { } + + wxLogEvent(const wxLogEvent& event) + : wxCommandEvent(event), m_filter(event.m_filter), m_message(event.m_message) { } + + wxEvent* Clone() const { return new wxLogEvent(*this); } + + [[nodiscard]] const wxString& GetFilter() const + { + return m_filter; + } + + [[nodiscard]] const wxString& GetMessage() const + { + return m_message; + } + +private: + wxString m_filter; + wxString m_message; +}; diff --git a/src/gui/input/InputAPIAddWindow.cpp b/src/gui/input/InputAPIAddWindow.cpp new file mode 100644 index 00000000..f32a85b6 --- /dev/null +++ b/src/gui/input/InputAPIAddWindow.cpp @@ -0,0 +1,303 @@ +#include "gui/input/InputAPIAddWindow.h" + +#include "input/InputManager.h" +#include "gui/helpers/wxCustomData.h" +#include "gui/helpers/wxHelpers.h" +#include "input/api/Controller.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "input/ControllerFactory.h" + +wxDEFINE_EVENT(wxControllersRefreshed, wxCommandEvent); + +using wxTypeData = wxCustomData; +using wxControllerData = wxCustomData; + +InputAPIAddWindow::InputAPIAddWindow(wxWindow* parent, const wxPoint& position, + const std::vector& controllers) + : wxDialog(parent, wxID_ANY, _("Add input API"), position, wxDefaultSize, 0), m_controllers(controllers) +{ + this->SetSizeHints(wxDefaultSize, wxDefaultSize); + + auto* sizer = new wxBoxSizer(wxVERTICAL); + + { + auto* api_row = new wxFlexGridSizer(2); + + // API + api_row->Add(new wxStaticText(this, wxID_ANY, _("API")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + + m_input_api = new wxChoice(this, wxID_ANY); + auto& providers = InputManager::instance().get_api_providers(); + for (const auto& p : providers) + { + if (p.empty()) + continue; + + const auto provider = *p.begin(); + m_input_api->Append(to_wxString(provider->api_name()), new wxTypeData(provider->api())); + } + + m_input_api->Bind(wxEVT_CHOICE, &InputAPIAddWindow::on_api_selected, this); + api_row->Add(m_input_api, 1, wxALL | wxEXPAND, 5); + + // Controller + api_row->Add(new wxStaticText(this, wxID_ANY, _("Controller")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + + m_controller_list = new wxComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, nullptr, + wxCB_READONLY); + m_controller_list->Bind(wxEVT_COMBOBOX_DROPDOWN, &InputAPIAddWindow::on_controller_dropdown, this); + m_controller_list->Bind(wxEVT_COMBOBOX, &InputAPIAddWindow::on_controller_selected, this); + m_controller_list->SetMinSize(wxSize(240, -1)); + m_controller_list->Disable(); + api_row->Add(m_controller_list, 1, wxALL | wxEXPAND, 5); + + sizer->Add(api_row, 0, wxEXPAND, 5); + } + + sizer->Add(new wxStaticLine(this), 0, wxEXPAND); + + { + auto* end_row = new wxBoxSizer(wxHORIZONTAL); + + m_ok_button = new wxButton(this, wxID_ANY, _("Add")); + m_ok_button->Bind(wxEVT_BUTTON, &InputAPIAddWindow::on_add_button, this); + m_ok_button->Disable(); + end_row->Add(m_ok_button, 0, wxALL, 5); + + auto* cancel_button = new wxButton(this, wxID_ANY, _("Cancel")); + cancel_button->Bind(wxEVT_BUTTON, &InputAPIAddWindow::on_close_button, this); + end_row->Add(cancel_button, 0, wxALL, 5); + + sizer->Add(end_row, 0, wxEXPAND, 5); + } + + { + // optional settings + m_settings_panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + auto* panel_sizer = new wxBoxSizer(wxVERTICAL); + panel_sizer->Add(new wxStaticLine(m_settings_panel), 0, wxEXPAND, 0); + + { + auto* row = new wxBoxSizer(wxHORIZONTAL); + // we only have dsu settings atm, so add elements now + row->Add(new wxStaticText(m_settings_panel, wxID_ANY, _("IP")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + m_ip = new wxTextCtrl(m_settings_panel, wxID_ANY, wxT("127.0.0.1")); + row->Add(m_ip, 0, wxALL, 5); + + row->Add(new wxStaticText(m_settings_panel, wxID_ANY, _("Port")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + m_port = new wxTextCtrl(m_settings_panel, wxID_ANY, wxT("26760")); + row->Add(m_port, 0, wxALL, 5); + + panel_sizer->Add(row, 0, wxEXPAND); + } + + m_settings_panel->SetSizer(panel_sizer); + m_settings_panel->Layout(); + m_settings_panel->Hide(); + + sizer->Add(m_settings_panel, 1, wxEXPAND); + } + + this->SetSizer(sizer); + this->Layout(); + sizer->Fit(this); + + this->Bind(wxControllersRefreshed, &InputAPIAddWindow::on_controllers_refreshed, this); +} + +void InputAPIAddWindow::on_add_button(wxCommandEvent& event) +{ + const auto selection = m_input_api->GetSelection(); + if (selection == wxNOT_FOUND) + { + cemu_assert_debug(false); + EndModal(wxID_CANCEL); + return; + } + + for (const auto& c : m_controllers) + { + if (*c == *m_controller) + { + wxMessageBox(_("The controller is already added!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + return; + } + } + + m_type = static_cast(m_input_api->GetClientObject(selection))->get(); + EndModal(wxID_OK); +} + +void InputAPIAddWindow::on_close_button(wxCommandEvent& event) +{ + EndModal(wxID_CANCEL); +} + +bool InputAPIAddWindow::has_custom_settings() const +{ + const auto selection = m_input_api->GetStringSelection(); + return selection == to_wxString(to_string(InputAPI::DSUClient)); +} + +std::unique_ptr InputAPIAddWindow::get_settings() const +{ + if (!has_custom_settings()) + return {}; + + return std::make_unique(m_ip->GetValue().ToStdString(), + ConvertString(m_port->GetValue().ToStdString())); +} + +void InputAPIAddWindow::on_api_selected(wxCommandEvent& event) +{ + if (m_input_api->GetSelection() == wxNOT_FOUND) + return; + + m_controller_list->Enable(); + m_controller_list->SetSelection(wxNOT_FOUND); + + const auto selection = m_input_api->GetStringSelection(); + // keyboard is a special case, as theres only one device supported atm + if (selection == to_wxString(to_string(InputAPI::Keyboard))) + { + const auto controllers = InputManager::instance().get_api_provider(InputAPI::Keyboard)->get_controllers(); + if (!controllers.empty()) + { + m_controller = controllers[0]; + m_ok_button->Enable(); + + m_controller_list->Clear(); + const auto display_name = controllers[0]->display_name(); + const auto index = m_controller_list->Append(display_name, new wxCustomData(controllers[0])); + m_controller_list->SetSelection(index); + } + } + else + { +#if BOOST_OS_LINUX + // We rely on the wxEVT_COMBOBOX_DROPDOWN event to trigger filling the controller list, + // but on wxGTK the dropdown button cannot be clicked if the list is empty + // so as a quick and dirty workaround we fill the list here + wxCommandEvent tmpCmdEvt; + on_controller_dropdown(tmpCmdEvt); +#endif + } + + const auto show_settings = has_custom_settings(); + // dsu has special settings for ip/port + if (show_settings != m_settings_panel->IsShown()) + { + wxWindowUpdateLocker locker(this); + m_settings_panel->Show(show_settings); + Layout(); + Fit(); + } +} + +void InputAPIAddWindow::on_controller_dropdown(wxCommandEvent& event) +{ + if (m_search_running) + return; + + int selection = m_input_api->GetSelection(); + if (selection == wxNOT_FOUND) + return; + + const auto type = static_cast(m_input_api->GetClientObject(selection))->get(); + auto settings = get_settings(); + + ControllerProviderPtr provider; + if (settings) + provider = InputManager::instance().get_api_provider(type, *settings); + else + provider = InputManager::instance().get_api_provider(type); + + if (!provider) + return; + + std::string selected_uuid; + selection = m_controller_list->GetSelection(); + if (selection != wxNOT_FOUND) + { + // TODO selected_uuid + } + + m_search_running = true; + + wxWindowUpdateLocker lock(m_controller_list); + m_controller_list->Clear(); + + m_controller_list->Append(_("Searching for controllers..."), (wxClientData*)nullptr); + m_controller_list->SetSelection(wxNOT_FOUND); + + std::thread([this, provider, selected_uuid]() + { + auto available_controllers = provider->get_controllers(); + + wxCommandEvent event(wxControllersRefreshed); + event.SetEventObject(m_controller_list); + event.SetClientObject(new wxCustomData(std::move(available_controllers))); + event.SetInt(provider->api()); + event.SetString(selected_uuid); + wxPostEvent(this, event); + + m_search_running = false; + }).detach(); +} + +void InputAPIAddWindow::on_controller_selected(wxCommandEvent& event) +{ + if (m_search_running) + { + return; + } + + const auto selection = m_controller_list->GetSelection(); + if (selection == wxNOT_FOUND) + { + return; + } + + if (auto* controller = (wxControllerData*)m_controller_list->GetClientObject(selection)) + { + m_controller = controller->ref(); + m_ok_button->Enable(); + } +} + +void InputAPIAddWindow::on_controllers_refreshed(wxCommandEvent& event) +{ + const auto type = event.GetInt(); + wxASSERT(0 <= type && type < InputAPI::MAX); + + auto* controllers = dynamic_cast(event.GetEventObject()); + wxASSERT(controllers); + + const auto available_controllers = static_cast>>*>(event. + GetClientObject())->get(); + const auto selected_uuid = event.GetString().ToStdString(); + bool item_selected = false; + + wxWindowUpdateLocker lock(controllers); + controllers->Clear(); + for (const auto& c : available_controllers) + { + const auto display_name = c->display_name(); + const auto uuid = c->uuid(); + const auto index = controllers->Append(display_name, new wxCustomData(c)); + if (!item_selected && selected_uuid == uuid) + { + controllers->SetSelection(index); + item_selected = true; + } + } +} diff --git a/src/gui/input/InputAPIAddWindow.h b/src/gui/input/InputAPIAddWindow.h new file mode 100644 index 00000000..ebee0592 --- /dev/null +++ b/src/gui/input/InputAPIAddWindow.h @@ -0,0 +1,53 @@ +#pragma once + +#include "input/api/InputAPI.h" + +#include +#include +#include + +#include "gui/helpers/wxCustomData.h" +#include "input/api/Controller.h" + +class wxComboBox; +class wxChoice; +class wxTextCtrl; + +using wxAPIType = wxCustomData; + +class InputAPIAddWindow : public wxDialog +{ +public: + InputAPIAddWindow(wxWindow* parent, const wxPoint& position, const std::vector& controllers); + + bool is_valid() const { return m_type.has_value() && m_controller != nullptr; } + InputAPI::Type get_type() const { return m_type.value(); } + std::shared_ptr get_controller() const { return m_controller; } + + bool has_custom_settings() const; + std::unique_ptr get_settings() const; + +private: + void on_add_button(wxCommandEvent& event); + void on_close_button(wxCommandEvent& event); + + + void on_api_selected(wxCommandEvent& event); + + void on_controller_dropdown(wxCommandEvent& event); + void on_controller_selected(wxCommandEvent& event); + void on_controllers_refreshed(wxCommandEvent& event); + + wxChoice* m_input_api; + wxComboBox* m_controller_list; + wxButton* m_ok_button; + + wxPanel* m_settings_panel; + wxTextCtrl* m_ip, * m_port; + + std::optional m_type; + std::shared_ptr m_controller; + + std::vector m_controllers; + std::atomic_bool m_search_running = false; +}; diff --git a/src/gui/input/InputSettings2.cpp b/src/gui/input/InputSettings2.cpp new file mode 100644 index 00000000..556028fa --- /dev/null +++ b/src/gui/input/InputSettings2.cpp @@ -0,0 +1,979 @@ +#include "gui/input/InputSettings2.h" + +#include + +#include "input/InputManager.h" +#include "gui/helpers/wxHelpers.h" +#include "gui/helpers/wxControlObject.h" +#include "gui/helpers/wxCustomData.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config/ActiveSettings.h" +#include "gui/input/InputAPIAddWindow.h" +#include "input/ControllerFactory.h" + +#include "gui/input/panels/VPADInputPanel.h" +#include "gui/input/panels/ProControllerInputPanel.h" + +#include "gui/input/settings/DefaultControllerSettings.h" +#include "gui/input/panels/ClassicControllerInputPanel.h" +#include "gui/input/panels/WiimoteInputPanel.h" +#include "gui/input/settings/WiimoteControllerSettings.h" +#include "util/EventService.h" + +#if BOOST_OS_LINUX +#include "resource/linux/resources.h" +#endif + +bool g_inputConfigWindowHasFocus = false; + +// _("") +const wxString kDefaultProfileName = ""; + +using wxTypeData = wxCustomData; +using wxControllerData = wxCustomData; + +struct ControllerPage +{ + EmulatedControllerPtr m_controller; + + // profiles + wxComboBox* m_profiles; + wxButton* m_profile_load, * m_profile_save, * m_profile_delete; + wxStaticText* m_profile_status; + + // emulated controller + wxComboBox* m_emulated_controller; + + // controller api + wxComboBox* m_controllers; + wxButton* m_controller_api_add, *m_controller_api_remove; + + wxButton* m_controller_settings, * m_controller_calibrate, *m_controller_clear; + wxBitmapButton* m_controller_connected; + + // panel + std::array m_panels{}; +}; +using wxControllerPageData = wxCustomData; + + +InputSettings2::InputSettings2(wxWindow* parent) + : wxDialog(parent, wxID_ANY, _("Input settings")) +{ + this->SetSizeHints(wxDefaultSize, wxDefaultSize); + + g_inputConfigWindowHasFocus = true; + + m_connected = wxBITMAP_PNG(INPUT_CONNECTED); + m_disconnected = wxBITMAP_PNG(INPUT_DISCONNECTED); + m_low_battery = wxBITMAP_PNG(INPUT_LOW_BATTERY); + + auto* sizer = new wxBoxSizer(wxVERTICAL); + + m_notebook = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0); + for(size_t i = 0; i < InputManager::kMaxController; ++i) + { + auto* page = new wxPanel(m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + page->SetClientObject(nullptr); // force internal type to client object + m_notebook->AddPage(page, wxStringFormat2(_("Controller {}"), i + 1)); + } + + m_notebook->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, &InputSettings2::on_controller_page_changed, this); + sizer->Add(m_notebook, 1, wxEXPAND); + + m_notebook->SetSelection(0); + auto* first_page = initialize_page(0); + + // init first/default page for fitting size + auto* page_data = (wxControllerPageData*)first_page->GetClientObject(); + auto* panel = new VPADInputPanel(first_page); + page_data->ref().m_panels[EmulatedController::Type::VPAD] = panel; + + auto* first_page_sizer = dynamic_cast(first_page->GetSizer()); + auto* panel_sizer = first_page_sizer->FindItemAtPosition(wxGBPosition(7, 0))->GetSizer(); + panel_sizer->Add(panel, 0, wxEXPAND); + + panel->Show(); + first_page->Layout(); + + SetSizer(sizer); + Layout(); + Fit(); + + panel->Hide(); + + update_state(); + + Bind(wxEVT_TIMER, &InputSettings2::on_timer, this); + + m_timer = new wxTimer(this); + m_timer->Start(100); + + m_controller_changed = EventService::instance().connect(&InputSettings2::on_controller_changed, this); +} + +InputSettings2::~InputSettings2() +{ + m_controller_changed.disconnect(); + + g_inputConfigWindowHasFocus = false; + m_timer->Stop(); + InputManager::instance().save(); +} + + +wxWindow* InputSettings2::initialize_page(size_t index) +{ + auto* page = m_notebook->GetPage(index); + if (page->GetClientObject()) // already initialized + return page; + + page->Bind(wxEVT_LEFT_UP, &InputSettings2::on_left_click, this); + + ControllerPage page_data{}; + const auto emulated_controller = InputManager::instance().get_controller(index); + page_data.m_controller = emulated_controller; + + wxWindowUpdateLocker lock(page); + auto* sizer = new wxGridBagSizer(); + + { + // profile + sizer->Add(new wxStaticText(page, wxID_ANY, _("Profile"), wxDefaultPosition, wxDefaultSize, 0), wxGBPosition(0, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL | wxALL, 5); + auto* profiles = new wxComboBox(page, wxID_ANY, _(kDefaultProfileName)); + sizer->Add(profiles, wxGBPosition(0, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL | wxALL | wxEXPAND, 5); + + if (emulated_controller && emulated_controller->has_profile_name()) + { + profiles->SetValue(emulated_controller->get_profile_name()); + } + + auto* load_bttn = new wxButton(page, wxID_ANY, _("Load")); + load_bttn->Disable(); + sizer->Add(load_bttn, wxGBPosition(0, 2), wxDefaultSpan, wxALIGN_CENTER_VERTICAL | wxALL, 5); + + auto* save_bttn = new wxButton(page, wxID_ANY, _("Save")); + save_bttn->Disable(); + sizer->Add(save_bttn, wxGBPosition(0, 3), wxDefaultSpan, wxALIGN_CENTER_VERTICAL | wxALL, 5); + + auto* delete_bttn = new wxButton(page, wxID_ANY, _("Delete")); + delete_bttn->Disable(); + sizer->Add(delete_bttn, wxGBPosition(0, 4), wxDefaultSpan, wxALIGN_CENTER_VERTICAL | wxALL, 5); + + auto* profile_status = new wxStaticText(page, wxID_ANY, _("controller set by gameprofile. changes won't be saved permanently!"), wxDefaultPosition, wxDefaultSize, 0); + profile_status->SetMinSize(wxSize(200, -1)); + profile_status->Wrap(200); + sizer->Add(profile_status, wxGBPosition(0, 5), wxDefaultSpan, wxALIGN_CENTER_VERTICAL | wxALL | wxRESERVE_SPACE_EVEN_IF_HIDDEN, 5); + + if(InputManager::instance().is_gameprofile_set(index)) + { + profile_status->SetForegroundColour(wxTheColourDatabase->Find("ERROR")); + } + else + { + profile_status->Hide(); + } + + load_bttn->Bind(wxEVT_BUTTON, &InputSettings2::on_profile_load, this); + save_bttn->Bind(wxEVT_BUTTON, &InputSettings2::on_profile_save, this); + delete_bttn->Bind(wxEVT_BUTTON, &InputSettings2::on_profile_delete, this); + + profiles->Bind(wxEVT_COMBOBOX_DROPDOWN, &InputSettings2::on_profile_dropdown, this); + profiles->Bind(wxEVT_TEXT, &InputSettings2::on_profile_text_changed, this); + + page_data.m_profiles = profiles; + page_data.m_profile_load = load_bttn; + page_data.m_profile_save = save_bttn; + page_data.m_profile_delete = delete_bttn; + page_data.m_profile_status = profile_status; + } + + sizer->Add(new wxStaticLine(page), wxGBPosition(1, 0), wxGBSpan(1, 6), wxEXPAND); + + { + // emulated controller + sizer->Add(new wxStaticText(page, wxID_ANY, _("Emulated controller")), wxGBPosition(2, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL | wxALL, 5); + + auto* econtroller_box = new wxComboBox(page, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, nullptr, wxCB_READONLY); + econtroller_box->SetMinSize(wxSize(200, -1)); + econtroller_box->Bind(wxEVT_COMBOBOX_DROPDOWN, &InputSettings2::on_emulated_controller_dropdown, this); + econtroller_box->Bind(wxEVT_COMBOBOX, &InputSettings2::on_emulated_controller_selected, this); + + econtroller_box->AppendString(_("Disabled")); + econtroller_box->SetSelection(0); + + sizer->Add(econtroller_box, wxGBPosition(2, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL | wxALL | wxEXPAND, 5); + page_data.m_emulated_controller = econtroller_box; + } + + sizer->Add(new wxStaticLine(page), wxGBPosition(3, 0), wxGBSpan(1, 6), wxEXPAND); + + { + // controller api + sizer->Add(new wxStaticText(page, wxID_ANY, _("Controller")), wxGBPosition(4, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL | wxALL, 5); + + auto* controllers = new wxComboBox(page, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, nullptr, wxCB_READONLY); + controllers->Bind(wxEVT_COMBOBOX, &InputSettings2::on_controller_selected, this); + controllers->Bind(wxEVT_COMBOBOX_DROPDOWN, &InputSettings2::on_controller_dropdown, this); + controllers->SetMinSize(wxSize(300, -1)); + + page_data.m_controllers = controllers; + + sizer->Add(controllers, wxGBPosition(4, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL | wxALL | wxEXPAND, 5); + + { + // add/remove buttons + auto* bttn_sizer = new wxBoxSizer(wxHORIZONTAL); + + auto* add_api = new wxButton(page, wxID_ANY, wxT(" + "), wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); + add_api->Bind(wxEVT_BUTTON, &InputSettings2::on_controller_add, this); + bttn_sizer->Add(add_api, 0, wxALL, 5); + + auto* remove_api = new wxButton(page, wxID_ANY, wxT(" - "), wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); + remove_api->Bind(wxEVT_BUTTON, &InputSettings2::on_controller_remove, this); + bttn_sizer->Add(remove_api, 0, wxALL, 5); + + sizer->Add(bttn_sizer, wxGBPosition(4, 2), wxDefaultSpan, wxEXPAND, 5); + + page_data.m_controller_api_add = add_api; + page_data.m_controller_api_remove = remove_api; + } + + // controller + auto* controller_bttns = new wxBoxSizer(wxHORIZONTAL); + auto* settings = new wxButton(page, wxID_ANY, _("Settings"), wxDefaultPosition, wxDefaultSize, 0); + settings->Bind(wxEVT_BUTTON, &InputSettings2::on_controller_settings, this); + settings->Disable(); + controller_bttns->Add(settings, 0, wxALL, 5); + + auto* calibrate = new wxButton(page, wxID_ANY, _("Calibrate"), wxDefaultPosition, wxDefaultSize, 0); + calibrate->Bind(wxEVT_BUTTON, &InputSettings2::on_controller_calibrate, this); + calibrate->Disable(); + controller_bttns->Add(calibrate, 0, wxALL, 5); + + auto* clear = new wxButton(page, wxID_ANY, _("Clear"), wxDefaultPosition, wxDefaultSize, 0); + clear->Bind(wxEVT_BUTTON, &InputSettings2::on_controller_clear, this); + controller_bttns->Add(clear, 0, wxALL, 5); + + sizer->Add(controller_bttns, wxGBPosition(5, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL); + + auto* connected_button = new wxBitmapButton(page, wxID_ANY, m_disconnected); + connected_button->Bind(wxEVT_BUTTON, &InputSettings2::on_controller_connect, this); + connected_button->SetToolTip(_("Test if the controller is connected")); + sizer->Add(connected_button, wxGBPosition(5, 2), wxDefaultSpan, wxALIGN_CENTER_VERTICAL | wxALL, 5); // TODO replace with icon + + page_data.m_controller_settings = settings; + page_data.m_controller_calibrate = calibrate; + page_data.m_controller_clear = clear; + page_data.m_controller_connected = connected_button; + + } + + sizer->Add(new wxStaticLine(page), wxGBPosition(6, 0), wxGBSpan(1, 6), wxEXPAND); + + auto* panel_sizer = new wxBoxSizer(wxVERTICAL); + sizer->Add(panel_sizer, wxGBPosition(7, 0), wxGBSpan(1, 6), wxEXPAND | wxALL, 5); + + page->SetSizer(sizer); + page->Layout(); + + page->SetClientObject(new wxCustomData(page_data)); + + return page; +} + +std::pair InputSettings2::get_emulated_controller_types() const +{ + size_t vpad = 0, wpad = 0; + for(size_t i = 0; i < m_notebook->GetPageCount(); ++i) + { + auto* page = m_notebook->GetPage(i); + auto* page_data = (wxControllerPageData*)page->GetClientObject(); + if (!page_data) + continue; + + if (!page_data->ref().m_controller) // = disabled + continue; + + const auto api_type = page_data->ref().m_controller->type(); + if (api_type) + continue; + + if (api_type == EmulatedController::VPAD) + ++vpad; + else + ++wpad; + } + + return std::make_pair(vpad, wpad); +} + +std::shared_ptr InputSettings2::get_active_controller() const +{ + auto& page_data = get_current_page_data(); + + const auto selection = page_data.m_controllers->GetSelection(); + if(selection != wxNOT_FOUND) + { + if(auto* controller = (wxControllerData*)page_data.m_controllers->GetClientObject(selection)) + return controller->ref(); + } + + return {}; +} + +bool InputSettings2::has_settings(InputAPI::Type type) +{ + switch(type) + { + case InputAPI::Keyboard: + return false; + default: + return true; + } +} + +void InputSettings2::update_state() +{ + auto* page = m_notebook->GetCurrentPage(); + wxWindowUpdateLocker lock(page); + + auto* page_data_ptr = (wxControllerPageData*)page->GetClientObject(); + wxASSERT(page_data_ptr); + auto& page_data = page_data_ptr->ref(); + + page_data.m_profile_status->Hide(); + + EmulatedControllerPtr emulated_controller = page_data.m_controller; + auto has_controllers = false; + + // update emulated + if(emulated_controller) + { + has_controllers = !emulated_controller->get_controllers().empty(); + + const auto emulated_type = emulated_controller->type(); + int index = page_data.m_emulated_controller->Append(to_wxString(emulated_controller->type_to_string(emulated_type))); + page_data.m_emulated_controller->SetSelection(index); + + const auto controller_selection = page_data.m_controllers->GetStringSelection(); + page_data.m_controllers->Clear(); + if (has_controllers) + { + for (const auto& c : emulated_controller->get_controllers()) + { + page_data.m_controllers->Append(fmt::format("{} [{}]", c->display_name(), c->api_name()), new wxCustomData(c)); + } + + if (page_data.m_controllers->GetCount() > 0) + { + page_data.m_controllers->SetSelection(0); + if (!controller_selection.empty()) + page_data.m_controllers->SetStringSelection(controller_selection); + } + } + } + else + { + page_data.m_emulated_controller->SetValue(_("Disabled")); + } + + ControllerPtr controller; + if (page_data.m_controllers->GetSelection() != wxNOT_FOUND) + { + if (const auto data = (wxControllerData*)page_data.m_controllers->GetClientObject(page_data.m_controllers->GetSelection())) + controller = data->ref(); + } + + if (controller && controller->is_connected()) + page_data.m_controller_connected->SetBitmap(m_connected); + else + page_data.m_controller_connected->SetBitmap(m_disconnected); + + // update controller + page_data.m_controller_calibrate->Enable(has_controllers); + page_data.m_controller_api_remove->Enable(has_controllers); + page_data.m_controller_settings->Enable(controller && has_settings(controller->api())); + + // update settings + // update panel + // test if we need to update to correct panel + std::optional active_api{}; + for(auto i = 0; i < EmulatedController::Type::MAX; ++i) + { + if(page_data.m_panels[i] && page_data.m_panels[i]->IsShown()) + { + active_api = (EmulatedController::Type)i; + break; + } + } + + // disabled and no emulated controller + if (!active_api && !emulated_controller) + return; + + // enabled correct panel for active controller + if (active_api && emulated_controller && emulated_controller->type() == active_api.value()) + return; + + // hide all panels + for (auto* panel : page_data.m_panels) + { + if (panel) + panel->Hide(); + } + + // show required panel + if (emulated_controller) + { + auto* sizer = dynamic_cast(page->GetSizer()); + wxASSERT(sizer); + + const auto type = page_data.m_controller->type(); + InputPanel* panel = page_data.m_panels[type]; + if (!panel) + { + switch (type) + { + case EmulatedController::Type::VPAD: + panel = new VPADInputPanel(page); + break; + case EmulatedController::Pro: + panel = new ProControllerInputPanel(page); + break; + case EmulatedController::Classic: + panel = new ClassicControllerInputPanel(page); + break; + case EmulatedController::Wiimote: + panel = new WiimoteInputPanel(page); + break; + default: + cemu_assert_debug(false); + return; + } + + page_data.m_panels[type] = panel; + + auto* panel_sizer = sizer->FindItemAtPosition(wxGBPosition(7, 0))->GetSizer(); + wxASSERT(panel_sizer); + panel_sizer->Add(panel, 0, wxEXPAND); + } + + panel->load_controller(page_data.m_controller); + if (has_controllers) + panel->set_selected_controller(emulated_controller, emulated_controller->get_controllers()[0]); + + panel->Show(); + page->wxWindowBase::Layout(); + page->wxWindow::Update(); + } +} + +void InputSettings2::on_controller_changed() +{ + for(auto i = 0 ; i < m_notebook->GetPageCount(); ++i) + { + auto* page = m_notebook->GetPage(i); + if (!page) + continue; + + auto* page_data_ptr = (wxControllerPageData*)page->GetClientObject(); + if (!page_data_ptr) + continue; + + const auto& page_data = page_data_ptr->ref(); + if (page_data.m_controllers->GetSelection() != wxNOT_FOUND) + { + if (const auto data = (wxControllerData*)page_data.m_controllers->GetClientObject(page_data.m_controllers->GetSelection())) + { + if (const auto controller = data->ref()) + { + if (controller->connect()) + page_data.m_controller_connected->SetBitmap(m_connected); + else + page_data.m_controller_connected->SetBitmap(m_disconnected); + } + } + } + } +} + +void InputSettings2::on_notebook_page_changed(wxBookCtrlEvent& event) +{ + initialize_page(event.GetSelection()); + update_state(); + event.Skip(); +} + +void InputSettings2::on_timer(wxTimerEvent&) +{ + auto& page_data = get_current_page_data(); + if (!page_data.m_controller) { + return; + } + + auto* panel = page_data.m_panels[page_data.m_controller->type()]; + if (!panel) + return; + + auto controller = get_active_controller(); + if (controller) { + panel->on_timer(page_data.m_controller, controller); + } +} + +void InputSettings2::on_left_click(wxMouseEvent& event) +{ + event.Skip(); + + auto& page_data = get_current_page_data(); + if (!page_data.m_controller) { + return; + } + + auto* panel = page_data.m_panels[page_data.m_controller->type()]; + if (!panel) + return; + + panel->on_left_click(event); +} + +void InputSettings2::on_profile_dropdown(wxCommandEvent& event) +{ + auto* profile_names = dynamic_cast(event.GetEventObject()); + wxASSERT(profile_names); + wxWindowUpdateLocker lock(profile_names); + + const auto selected_value = profile_names->GetStringSelection(); + profile_names->Clear(); + + for(const auto& profile : InputManager::get_profiles()) + { + profile_names->Append(wxString::FromUTF8(profile)); + } + + profile_names->SetStringSelection(selected_value); +} + +void InputSettings2::on_profile_text_changed(wxCommandEvent& event) +{ + auto* profile_names = dynamic_cast(event.GetEventObject()); + wxASSERT(profile_names); + + auto& page_data = get_current_page_data(); + + const auto selection = page_data.m_emulated_controller->GetStringSelection(); + + // load_bttn, save_bttn, delete_bttn, profile_status + const auto text = event.GetString(); + const auto text_str = from_wxString(text); + + const bool valid_name = InputManager::is_valid_profilename(text_str); + const bool name_exists = profile_names->FindString(text) != wxNOT_FOUND; + + page_data.m_profile_load->Enable(name_exists); + page_data.m_profile_save->Enable(valid_name); + page_data.m_profile_delete->Enable(name_exists); + page_data.m_profile_status->Hide(); +} + +void InputSettings2::on_profile_load(wxCommandEvent& event) +{ + auto& page_data = get_current_page_data(); + + auto* profile_names = page_data.m_profiles; + auto* text = page_data.m_profile_status; + + const auto selection = from_wxString(profile_names->GetValue()); + text->Show(); + if (selection.empty() || !InputManager::is_valid_profilename(selection)) + { + text->SetLabelText(_("invalid profile name")); + text->SetForegroundColour(wxTheColourDatabase->Find("ERROR")); + text->Refresh(); + return; + } + + const auto page_index = m_notebook->GetSelection(); + if (InputManager::instance().load(page_index, selection)) + { + text->SetLabelText(_("profile loaded")); + text->SetForegroundColour(wxTheColourDatabase->Find("SUCCESS")); + } + else + { + text->SetLabelText(_("couldn't load profile")); + text->SetForegroundColour(wxTheColourDatabase->Find("ERROR")); + } + + text->Refresh(); + + // update controller info + page_data.m_controller = InputManager::instance().get_controller(page_index); + update_state(); +} + +void InputSettings2::on_profile_save(wxCommandEvent& event) +{ + auto& page_data = get_current_page_data(); + + auto* profile_names = page_data.m_profiles; + auto* text = page_data.m_profile_status; + + const auto selection = from_wxString(profile_names->GetValue()); + text->Show(); + if (selection.empty() || !InputManager::is_valid_profilename(selection)) + { + text->SetLabelText(_("invalid profile name")); + text->SetForegroundColour(wxTheColourDatabase->Find("ERROR")); + text->Refresh(); + return; + } + + if(InputManager::instance().save(m_notebook->GetSelection(), selection)) + { + text->SetLabelText(_("profile saved")); + text->SetForegroundColour(wxTheColourDatabase->Find("SUCCESS")); + } + else + { + text->SetLabelText(_("couldn't save profile")); + text->SetForegroundColour(wxTheColourDatabase->Find("ERROR")); + } + + text->Refresh(); +} + +void InputSettings2::on_profile_delete(wxCommandEvent& event) +{ + auto& page_data = get_current_page_data(); + + auto* profile_names = page_data.m_profiles; + auto* text = page_data.m_profile_status; + + const auto selection = from_wxString(profile_names->GetStringSelection()); + + text->Show(); + if (selection.empty() || !InputManager::is_valid_profilename(selection)) + { + text->SetLabelText(_("invalid profile name")); + text->SetForegroundColour(wxTheColourDatabase->Find("ERROR")); + text->Refresh(); + return; + } + try + { + const fs::path old_path = ActiveSettings::GetPath(fmt::format("controllerProfiles/{}.txt", selection)); + fs::remove(old_path); + + const fs::path path = ActiveSettings::GetPath(fmt::format("controllerProfiles/{}.xml", selection)); + fs::remove(path); + + profile_names->ChangeValue(_(kDefaultProfileName)); + text->SetLabelText(_("profile deleted")); + text->SetForegroundColour(wxTheColourDatabase->Find("SUCCESS")); + + page_data.m_profile_load->Disable(); + page_data.m_profile_save->Disable(); + page_data.m_profile_delete->Disable(); + } + catch (const std::exception&) + { + text->SetLabelText(_("can't delete profile")); + text->SetForegroundColour(wxTheColourDatabase->Find("ERROR")); + } + text->Refresh(); +} + + +void InputSettings2::on_controller_page_changed(wxBookCtrlEvent& event) +{ + initialize_page(event.GetSelection()); + update_state(); + event.Skip(); +} + +void InputSettings2::on_emulated_controller_selected(wxCommandEvent& event) +{ + const auto page_index = m_notebook->GetSelection(); + auto& page_data = get_current_page_data(); + + const auto selection = event.GetSelection(); + if(selection == 0) // disabled selected + { + page_data.m_controller = {}; + InputManager::instance().delete_controller(page_index, true); + } + else + { + const auto type_str = from_wxString(event.GetString()); + try + { + const auto type = EmulatedController::type_from_string(type_str); + // same has already been selected + if (page_data.m_controller && page_data.m_controller->type() == type) + return; + + // set new controller + const auto new_controller = InputManager::instance().set_controller(page_index, type); + page_data.m_controller = new_controller; + + // append controllers if some were already added before + if (new_controller->get_controllers().empty()) + { + // test if we had no emulated controller before but still assigned controllers we want to transfer now + for (uint32 i = 0; i < page_data.m_controllers->GetCount(); ++i) + { + if (auto* controller = (wxControllerData*)page_data.m_controllers->GetClientObject(i)) + { + new_controller->add_controller(controller->ref()); + } + } + } + + // set default mappings if any controllers available + for(const auto& c: new_controller->get_controllers()) + { + new_controller->set_default_mapping(c); + } + } + catch (const std::exception&) + { + cemu_assert_debug(false); + } + + } + + update_state(); +} + +void InputSettings2::on_emulated_controller_dropdown(wxCommandEvent& event) +{ + auto* emulated_controllers = dynamic_cast(event.GetEventObject()); + wxASSERT(emulated_controllers); + + wxWindowUpdateLocker lock(emulated_controllers); + + bool is_gamepad_selected = false; + const auto selected = emulated_controllers->GetSelection(); + const auto selected_value = emulated_controllers->GetStringSelection(); + if(selected != wxNOT_FOUND) + { + is_gamepad_selected = selected_value == to_wxString(EmulatedController::type_to_string(EmulatedController::Type::VPAD)); + } + + const auto [vpad_count, wpad_count] = get_emulated_controller_types(); + + emulated_controllers->Clear(); + emulated_controllers->AppendString(_("Disabled")); + + if (vpad_count < InputManager::kMaxVPADControllers || is_gamepad_selected) + emulated_controllers->Append(to_wxString(EmulatedController::type_to_string(EmulatedController::Type::VPAD))); + + if (wpad_count < InputManager::kMaxWPADControllers || !is_gamepad_selected) + { + emulated_controllers->AppendString(to_wxString(EmulatedController::type_to_string(EmulatedController::Type::Pro))); + emulated_controllers->AppendString(to_wxString(EmulatedController::type_to_string(EmulatedController::Type::Classic))); + emulated_controllers->AppendString(to_wxString(EmulatedController::type_to_string(EmulatedController::Type::Wiimote))); + } + + emulated_controllers->SetStringSelection(selected_value); +} + +void InputSettings2::on_controller_selected(wxCommandEvent& event) +{ + auto& page_data = get_current_page_data(); + + const auto enabled = event.GetSelection() != wxNOT_FOUND; + page_data.m_controller_api_remove->Enable(enabled); + // page_data->ref().m_controller_list->Clear(); + if(enabled) + { + // get selected controller if any todo + if (auto* controller = (wxControllerData*)page_data.m_controllers->GetClientObject(event.GetSelection())) + { + page_data.m_controller_settings->Enable(has_settings(controller->ref()->api())); + + if(page_data.m_controller) + { + page_data.m_panels[page_data.m_controller->type()]->set_selected_controller(page_data.m_controller, controller->ref()); + } + } + + } +} + +void InputSettings2::on_controller_dropdown(wxCommandEvent& event) +{ + if(auto* controllers = dynamic_cast(event.GetEventObject())) + { + if(controllers->GetCount()== 0) + { + on_controller_add(event); + controllers->SetSelection(0); + } + } +} + +ControllerPage& InputSettings2::get_current_page_data() const +{ + auto* page = m_notebook->GetCurrentPage(); + auto* page_data_ptr = (wxControllerPageData*)page->GetClientObject(); + wxASSERT(page_data_ptr); + return page_data_ptr->ref(); +} + +void InputSettings2::on_controller_connect(wxCommandEvent& event) +{ + auto& page_data = get_current_page_data(); + + if (page_data.m_controllers->GetSelection() != wxNOT_FOUND) + { + if (const auto data = (wxControllerData*)page_data.m_controllers->GetClientObject(page_data.m_controllers->GetSelection())) + { + if(const auto controller = data->ref()) + { + if(controller->connect()) + page_data.m_controller_connected->SetBitmap(m_connected); + else + page_data.m_controller_connected->SetBitmap(m_disconnected); + } + } + } +} + +void InputSettings2::on_controller_add(wxCommandEvent& event) +{ + auto& page_data = get_current_page_data(); + + std::vector controllers; + controllers.reserve(page_data.m_controllers->GetCount()); + for(uint32 i = 0; i < page_data.m_controllers->GetCount(); ++i) + { + if (auto* controller = (wxControllerData*)page_data.m_controllers->GetClientObject(i)) + controllers.emplace_back(controller->ref()); + } + + InputAPIAddWindow wnd(this, wxGetMousePosition() + wxSize(5, 5), controllers); + if (wnd.ShowModal() != wxID_OK) + return; + + wxASSERT(wnd.is_valid()); + + const auto controller = wnd.get_controller(); + + const auto api_type = wnd.get_type(); + controller->connect(); + const int index = page_data.m_controllers->Append(fmt::format("{} [{}]", controller->display_name(), to_string(api_type)), new wxCustomData(controller)); + + page_data.m_controllers->Select(index); + + if(page_data.m_controller) + { + page_data.m_controller->add_controller(controller); + + const auto type = page_data.m_controller->type(); + // if first controller and we got no mappings, add default mappings + if(page_data.m_controller->set_default_mapping(controller)) + page_data.m_panels[type]->load_controller(page_data.m_controller); + + page_data.m_panels[type]->set_selected_controller(page_data.m_controller, controller); + } + + update_state(); +} + +void InputSettings2::on_controller_remove(wxCommandEvent& event) +{ + auto& page_data = get_current_page_data(); + + auto* api_box = page_data.m_controllers; + int selection = api_box->GetSelection(); + if (selection == wxNOT_FOUND) + return; + + if (page_data.m_controller) { + if (auto* controller = (wxControllerData*)page_data.m_controllers->GetClientObject(selection)) + { + page_data.m_controller->remove_controller(controller->ref()); + page_data.m_panels[page_data.m_controller->type()]->load_controller(page_data.m_controller); + } + } + + page_data.m_panels[page_data.m_controller->type()]->reset_colours(); + + api_box->Delete(selection); + api_box->Refresh(); + + update_state(); + + if (api_box->GetCount() > 0) + { + selection = selection > 0 ? (selection - 1) : 0; + api_box->SetSelection(selection); + } + + update_state(); +} + +void InputSettings2::on_controller_calibrate(wxCommandEvent& event) +{ + if(const auto controller = get_active_controller()) + controller->calibrate(); +} + +void InputSettings2::on_controller_clear(wxCommandEvent& event) +{ + auto& page_data = get_current_page_data(); + if (page_data.m_controller) { + const auto type = page_data.m_controller->type(); + + page_data.m_panels[type]->reset_configuration(); + page_data.m_controller->clear_mappings(); + } +} + +void InputSettings2::on_controller_settings(wxCommandEvent& event) +{ + auto controller = get_active_controller(); + if (!controller) + return; + + switch(controller->api()) + { + + case InputAPI::DirectInput: + case InputAPI::XInput: + case InputAPI::GameCube: + case InputAPI::WGIGamepad: + case InputAPI::WGIRawController: + case InputAPI::SDLController: + case InputAPI::DSUClient: + { + DefaultControllerSettings wnd(this, wxGetMousePosition() + wxSize(5, 5), controller); + wnd.ShowModal(); + break; + } + + case InputAPI::Keyboard: break; + + #if BOOST_OS_WINDOWS + case InputAPI::Wiimote: { + const auto wiimote = std::dynamic_pointer_cast(controller); + wxASSERT(wiimote); + WiimoteControllerSettings wnd(this, wxGetMousePosition() + wxSize(5, 5), wiimote); + wnd.ShowModal(); + break; + } + #endif + } +} diff --git a/src/gui/input/InputSettings2.h b/src/gui/input/InputSettings2.h new file mode 100644 index 00000000..f6305ff4 --- /dev/null +++ b/src/gui/input/InputSettings2.h @@ -0,0 +1,74 @@ +#pragma once + +#include +#include +#include + +#include "input/api/InputAPI.h" + +#include + +struct ControllerPage; +class ControllerBase; + +class InputSettings2 : public wxDialog +{ +public: + InputSettings2(wxWindow* parent); + ~InputSettings2(); + +private: + wxNotebook* m_notebook; + wxTimer* m_timer; + + wxBitmap m_connected, m_disconnected, m_low_battery; + + wxWindow* initialize_page(size_t index); + + // count active controllers + std::pair get_emulated_controller_types() const; + + // currently selected controller from active tab + std::shared_ptr get_active_controller() const; + + bool has_settings(InputAPI::Type type); + ControllerPage& get_current_page_data() const; + void update_state(); + + boost::signals2::connection m_controller_changed; + void on_controller_changed(); + + // events + void on_notebook_page_changed(wxBookCtrlEvent& event); + void on_timer(wxTimerEvent& event); + + void on_left_click(wxMouseEvent& event); + + void on_controller_page_changed(wxBookCtrlEvent& event); + + void on_profile_dropdown(wxCommandEvent& event); + void on_profile_text_changed(wxCommandEvent& event); + + void on_profile_load(wxCommandEvent& event); + void on_profile_save(wxCommandEvent& event); + void on_profile_delete(wxCommandEvent& event); + + void on_emulated_controller_selected(wxCommandEvent& event); + void on_emulated_controller_dropdown(wxCommandEvent& event); + + void on_controller_selected(wxCommandEvent& event); + void on_controller_dropdown(wxCommandEvent& event); + void on_controller_connect(wxCommandEvent& event); + + void on_controller_add(wxCommandEvent& event); + void on_controller_remove(wxCommandEvent& event); + + void on_controller_calibrate(wxCommandEvent& event); + void on_controller_clear(wxCommandEvent& event); + void on_controller_settings(wxCommandEvent& event); + + // void on_controller_dropdown(wxCommandEvent& event); + // void on_controllers_refreshed(wxCommandEvent& event); + +}; + diff --git a/src/gui/input/panels/ClassicControllerInputPanel.cpp b/src/gui/input/panels/ClassicControllerInputPanel.cpp new file mode 100644 index 00000000..fa41bd57 --- /dev/null +++ b/src/gui/input/panels/ClassicControllerInputPanel.cpp @@ -0,0 +1,139 @@ +#include "gui/input/panels/ClassicControllerInputPanel.h" + +#include +#include +#include +#include +#include + +#include "gui/helpers/wxControlObject.h" +#include "input/emulated/ClassicController.h" +#include "gui/helpers/wxHelpers.h" +#include "gui/components/wxInputDraw.h" + +constexpr ClassicController::ButtonId g_kFirstColumnItems[] = { ClassicController::kButtonId_A, ClassicController::kButtonId_B, ClassicController::kButtonId_X, ClassicController::kButtonId_Y, ClassicController::kButtonId_L, ClassicController::kButtonId_R, ClassicController::kButtonId_ZL, ClassicController::kButtonId_ZR, ClassicController::kButtonId_Plus, ClassicController::kButtonId_Minus }; +constexpr ClassicController::ButtonId g_kSecondColumnItems[] = { ClassicController::kButtonId_StickL_Up, ClassicController::kButtonId_StickL_Down, ClassicController::kButtonId_StickL_Left, ClassicController::kButtonId_StickL_Right }; +constexpr ClassicController::ButtonId g_kThirdColumnItems[] = { ClassicController::kButtonId_StickR_Up, ClassicController::kButtonId_StickR_Down, ClassicController::kButtonId_StickR_Left, ClassicController::kButtonId_StickR_Right }; +constexpr ClassicController::ButtonId g_kFourthRowItems[] = { ClassicController::kButtonId_Up, ClassicController::kButtonId_Down, ClassicController::kButtonId_Left, ClassicController::kButtonId_Right }; + + +ClassicControllerInputPanel::ClassicControllerInputPanel(wxWindow* parent) + : InputPanel(parent) +{ + auto bold_font = GetFont(); + bold_font.MakeBold(); + + auto* main_sizer = new wxGridBagSizer(); + sint32 row = 0; + sint32 column = 0; + for (const auto& id : g_kFirstColumnItems) + { + row++; + + main_sizer->Add(new wxStaticText(this, wxID_ANY, to_wxString(ClassicController::get_button_name(id))), wxGBPosition(row, column), wxDefaultSpan, wxALL | wxALIGN_CENTER_VERTICAL, 5); + + auto* text_ctrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxTE_PROCESS_ENTER | wxTE_PROCESS_TAB); + text_ctrl->SetClientData(reinterpret_cast(id)); + text_ctrl->SetMinSize(wxSize(150, -1)); + text_ctrl->SetEditable(false); + text_ctrl->SetBackgroundColour(kKeyColourNormalMode); + bind_hotkey_events(text_ctrl); + main_sizer->Add(text_ctrl, wxGBPosition(row, column + 1), wxDefaultSpan, wxALL | wxEXPAND, 5); + } + + ////////////////////////////////////////////////////////////////// + + main_sizer->Add(new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVERTICAL), wxGBPosition(0, column + 2), wxGBSpan(11, 1), wxALL | wxEXPAND, 5); + + row = 0; + column += 3; + + auto text = new wxStaticText(this, wxID_ANY, _("Left Axis")); + text->SetFont(bold_font); + main_sizer->Add(text, wxGBPosition(row, column), wxGBSpan(1, 3), wxALL | wxEXPAND, 5); + + for (const auto& id : g_kSecondColumnItems) + { + row++; + + main_sizer->Add(new wxStaticText(this, wxID_ANY, to_wxString(ClassicController::get_button_name(id))), wxGBPosition(row, column), wxDefaultSpan, wxALL | wxALIGN_CENTER_VERTICAL, 5); + + auto* text_ctrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxTE_PROCESS_ENTER | wxTE_PROCESS_TAB); + text_ctrl->SetClientData(reinterpret_cast(id)); + text_ctrl->SetMinSize(wxSize(150, -1)); + text_ctrl->SetEditable(false); + text_ctrl->SetBackgroundColour(kKeyColourNormalMode); + bind_hotkey_events(text_ctrl); + main_sizer->Add(text_ctrl, wxGBPosition(row, column + 1), wxDefaultSpan, wxALL | wxEXPAND, 5); + } + + row++; + + // input drawer + m_left_draw = new wxInputDraw(this, wxID_ANY, wxDefaultPosition, { 60, 60 }); + main_sizer->Add(m_left_draw, wxGBPosition(row, column + 1), wxGBSpan(2, 1), wxTOP | wxBOTTOM | wxEXPAND | wxALIGN_CENTER, 5); + + main_sizer->Add(new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVERTICAL), wxGBPosition(0, column + 3), wxGBSpan(11, 1), wxALL | wxEXPAND, 5); + + ////////////////////////////////////////////////////////////////// + + row = 0; + column += 4; + + text = new wxStaticText(this, wxID_ANY, _("Right Axis")); + text->SetFont(bold_font); + main_sizer->Add(text, wxGBPosition(row, column), wxGBSpan(1, 3), wxALL | wxEXPAND, 5); + + for (const auto& id : g_kThirdColumnItems) + { + row++; + + main_sizer->Add(new wxStaticText(this, wxID_ANY, to_wxString(ClassicController::get_button_name(id))), wxGBPosition(row, column), wxDefaultSpan, wxALL | wxALIGN_CENTER_VERTICAL, 5); + + auto* text_ctrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxTE_PROCESS_ENTER | wxTE_PROCESS_TAB); + text_ctrl->SetClientData(reinterpret_cast(id)); + text_ctrl->SetMinSize(wxSize(150, -1)); + text_ctrl->SetEditable(false); + text_ctrl->SetBackgroundColour(kKeyColourNormalMode); + bind_hotkey_events(text_ctrl); + main_sizer->Add(text_ctrl, wxGBPosition(row, column + 1), wxDefaultSpan, wxALL | wxEXPAND, 5); + } + + row++; + + // input drawer + m_right_draw = new wxInputDraw(this, wxID_ANY, wxDefaultPosition, { 60, 60 }); + main_sizer->Add(m_right_draw, wxGBPosition(row, column + 1), wxGBSpan(2, 1), wxTOP | wxBOTTOM | wxEXPAND | wxALIGN_CENTER, 5); + + main_sizer->Add(new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVERTICAL), wxGBPosition(0, column + 3), wxGBSpan(11, 1), wxALL | wxEXPAND, 5); + + ////////////////////////////////////////////////////////////////// + + row = 0; + column += 4; + + text = new wxStaticText(this, wxID_ANY, _("D-pad")); + text->SetFont(bold_font); + main_sizer->Add(text, wxGBPosition(row, column), wxGBSpan(1, 3), wxALL | wxEXPAND, 5); + + for (auto id : g_kFourthRowItems) + { + row++; + + main_sizer->Add(new wxStaticText(this, wxID_ANY, to_wxString(ClassicController::get_button_name(id))), wxGBPosition(row, column), wxDefaultSpan, wxALL | wxALIGN_CENTER_VERTICAL, 5); + + auto* text_ctrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxTE_PROCESS_ENTER | wxTE_PROCESS_TAB); + text_ctrl->SetClientData(reinterpret_cast(id)); + text_ctrl->SetMinSize(wxSize(150, -1)); + text_ctrl->SetEditable(false); + text_ctrl->SetBackgroundColour(kKeyColourNormalMode); + bind_hotkey_events(text_ctrl); + main_sizer->Add(text_ctrl, wxGBPosition(row, column + 1), wxDefaultSpan, wxALL | wxEXPAND, 5); + } + + ////////////////////////////////////////////////////////////////// + + + SetSizer(main_sizer); + Layout(); +} diff --git a/src/gui/input/panels/ClassicControllerInputPanel.h b/src/gui/input/panels/ClassicControllerInputPanel.h new file mode 100644 index 00000000..73a59da1 --- /dev/null +++ b/src/gui/input/panels/ClassicControllerInputPanel.h @@ -0,0 +1,15 @@ +#pragma once + +#include "gui/input/panels/InputPanel.h" + +class wxInputDraw; + +class ClassicControllerInputPanel : public InputPanel +{ +public: + ClassicControllerInputPanel(wxWindow* parent); + +private: + wxInputDraw* m_left_draw, * m_right_draw; +}; + diff --git a/src/gui/input/panels/InputPanel.cpp b/src/gui/input/panels/InputPanel.cpp new file mode 100644 index 00000000..5d07c643 --- /dev/null +++ b/src/gui/input/panels/InputPanel.cpp @@ -0,0 +1,287 @@ +#include "gui/input/panels/InputPanel.h" + +#include +#include + +#include "gui/helpers/wxHelpers.h" + +InputPanel::InputPanel(wxWindow* parent) + : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxNO_BORDER | wxWANTS_CHARS) +{ + Bind(wxEVT_LEFT_UP, &InputPanel::on_left_click, this); +} + +void InputPanel::on_timer(const EmulatedControllerPtr& emulated_controller, const ControllerPtr& controller) +{ + const auto& state = controller->update_state(); + + if(m_focused_element == wxID_NONE) + { + return; + } + + const auto element = dynamic_cast(FindWindow(m_focused_element)); + wxASSERT(element); + + const auto mapping = reinterpret_cast(element->GetClientData()); + + // reset mapping + if(std::exchange(m_right_down, false) || wxGetKeyState(WXK_ESCAPE)) + { + element->SetBackgroundColour(kKeyColourNormalMode); + m_color_backup[element->GetId()] = kKeyColourNormalMode; + + emulated_controller->delete_mapping(mapping); + if(element->IsEmpty()) + reset_focused_element(); + else + element->SetValue(wxEmptyString); + + return; + } + + static bool s_was_idle = true; + if (state.buttons.none()) { + s_was_idle = true; + return; + } + + if (!s_was_idle) { + return; + } + + s_was_idle = false; + for (size_t i = 0; i < state.buttons.size(); ++i) + { + if (state.buttons[i]) + { + if (controller->has_axis()) { + // test if one axis direction is pressed more than the other + if ((i == kAxisXP || i == kAxisXN) && (state.buttons[kAxisYP] || state.buttons[kAxisYN])) + { + if (std::abs(state.axis.y) > std::abs(state.axis.x)) + continue; + } + else if ((i == kAxisYP || i == kAxisYN) && (state.buttons[kAxisXP] || state.buttons[kAxisXN])) + { + if (std::abs(state.axis.x) > std::abs(state.axis.y)) + continue; + } + else if ((i == kRotationXP || i == kRotationXN) && (state.buttons[kRotationYP] || state.buttons[kRotationYN])) + { + if (std::abs(state.rotation.y) > std::abs(state.rotation.x)) + continue; + } + else if ((i == kRotationYP || i == kRotationYN) && (state.buttons[kRotationXP] || state.buttons[kRotationXN])) + { + if (std::abs(state.rotation.x) > std::abs(state.rotation.y)) + continue; + } + else if ((i == kTriggerXP || i == kTriggerXN) && (state.buttons[kTriggerYP] || state.buttons[kTriggerYN])) + { + if (std::abs(state.trigger.y) > std::abs(state.trigger.x)) + continue; + } + else if ((i == kTriggerYP || i == kTriggerYN) && (state.buttons[kTriggerXP] || state.buttons[kTriggerXN])) + { + if (std::abs(state.trigger.x) > std::abs(state.trigger.y)) + continue; + } + + // ignore too low button values on configuration + if (i >= kButtonAxisStart) + { + if (controller->get_axis_value(i) < 0.33f) { + forceLogDebug_printf("skipping since value too low %f", controller->get_axis_value(i)); + s_was_idle = true; + return; + } + } + } + + emulated_controller->set_mapping(mapping, controller, i); + element->SetValue(controller->get_button_name(i)); + element->SetBackgroundColour(kKeyColourNormalMode); + m_color_backup[element->GetId()] = kKeyColourNormalMode; + break; + } + } + + if (const auto sibling = get_next_sibling(element)) + sibling->SetFocus(); + else // last element reached + { + reset_focused_element(); + this->SetFocusIgnoringChildren(); + } +} + +void InputPanel::reset_configuration() +{ + m_color_backup.clear(); + + wxWindowUpdateLocker lock(this); + for (const auto& c : GetChildren()) + { + if (auto* text = dynamic_cast(c)) + { + text->SetValue(wxEmptyString); + text->SetBackgroundColour(kKeyColourNormalMode); + text->Refresh(); + } + } +} + +void InputPanel::reset_colours() +{ + m_color_backup.clear(); + + wxWindowUpdateLocker lock(this); + for (const auto& c : GetChildren()) + { + if (auto* text = dynamic_cast(c)) + { + text->SetBackgroundColour(kKeyColourNormalMode); + text->Refresh(); + } + } +} + + +void InputPanel::load_controller(const EmulatedControllerPtr& controller) +{ + reset_configuration(); + if(!controller) + { + return; + } + + if(controller->get_controllers().empty()) + { + return; + } + + wxWindowUpdateLocker lock(this); + for (auto* child : this->GetChildren()) + { + const auto text = dynamic_cast(child); + if (text == nullptr) + continue; + + + const auto mapping = reinterpret_cast(text->GetClientData()); + if (mapping <= 0) + continue; + + auto button_name = controller->get_mapping_name(mapping); +#if BOOST_OS_WINDOWS + text->SetLabelText(button_name); +#else + // SetLabelText doesn't seem to work here for some reason on wxGTK + text->ChangeValue(button_name); +#endif + } +} + +void InputPanel::set_selected_controller(const EmulatedControllerPtr& emulated_controller, const ControllerPtr& controller) +{ + wxWindowUpdateLocker lock(this); + for (auto* child : this->GetChildren()) + { + const auto text = dynamic_cast(child); + if (text == nullptr) + continue; + + if (text->GetId() == m_focused_element) + continue; + + const auto mapping = reinterpret_cast(text->GetClientData()); + if (mapping <= 0) + continue; + + const auto mapping_controller = emulated_controller->get_mapping_controller(mapping); + if (!mapping_controller) + continue; + + text->SetBackgroundColour(*mapping_controller == *controller ? kKeyColourNormalMode : kKeyColourActiveMode); + text->Refresh(); + } +} + +void InputPanel::bind_hotkey_events(wxTextCtrl* text_ctrl) +{ + text_ctrl->Bind(wxEVT_SET_FOCUS, &InputPanel::on_edit_key_focus, this); + text_ctrl->Bind(wxEVT_KILL_FOCUS, &InputPanel::on_edit_key_kill_focus, this); + text_ctrl->Bind(wxEVT_RIGHT_DOWN, &InputPanel::on_right_click, this); +} + +void InputPanel::on_left_click(wxMouseEvent& event) +{ + if (m_focused_element == wxID_NONE) + return; + + const auto focuses_element = FindWindow(m_focused_element); + wxASSERT(focuses_element); + + wxFocusEvent focus(wxEVT_KILL_FOCUS, m_focused_element); + focus.SetWindow(focuses_element); + focuses_element->GetEventHandler()->ProcessEvent(focus); + + this->SetFocusIgnoringChildren(); +} + +void InputPanel::on_edit_key_focus(wxFocusEvent& event) +{ + auto* text = dynamic_cast(event.GetEventObject()); + wxASSERT(text); + + m_color_backup[text->GetId()] = text->GetBackgroundColour(); + + text->SetBackgroundColour(kKeyColourEditMode); + #if BOOST_OS_WINDOWS + text->HideNativeCaret(); + #endif + text->Refresh(); + + m_focused_element = text->GetId(); + event.Skip(); +} + +void InputPanel::on_edit_key_kill_focus(wxFocusEvent& event) +{ + reset_focused_element(); + event.Skip(); +} + +void InputPanel::on_right_click(wxMouseEvent& event) +{ + m_right_down = true; + if(m_focused_element == wxID_NONE) + { + auto* text = dynamic_cast(event.GetEventObject()); + wxASSERT(text); + text->SetFocus(); + } +} + +bool InputPanel::reset_focused_element() +{ + if (m_focused_element == wxID_NONE) + return false; + + auto* prev_element = dynamic_cast(FindWindow(m_focused_element)); + wxASSERT(prev_element); + + if(m_color_backup.find(prev_element->GetId()) != m_color_backup.cend()) + prev_element->SetBackgroundColour(m_color_backup[prev_element->GetId()]); + else + prev_element->SetBackgroundColour(kKeyColourNormalMode); + +#if BOOST_OS_WINDOWS + prev_element->HideNativeCaret(); +#endif + prev_element->Refresh(); + + m_focused_element = wxID_NONE; + return true; +} diff --git a/src/gui/input/panels/InputPanel.h b/src/gui/input/panels/InputPanel.h new file mode 100644 index 00000000..6ac15089 --- /dev/null +++ b/src/gui/input/panels/InputPanel.h @@ -0,0 +1,45 @@ +#pragma once + +#include + +#include "input/emulated/EmulatedController.h" +#include "input/api/Controller.h" + +class ControllerBase; +class wxTextCtrl; +class wxComboBox; + +class InputPanel : public wxPanel +{ +public: + const wxColour kKeyColourNormalMode = 0xfafafa; + const wxColour kKeyColourEditMode = 0x99ccff; + const wxColour kKeyColourActiveMode = 0xE0E0E0; + + InputPanel(wxWindow* parent); + + ControllerPtr get_active_controller() const; + + virtual void on_timer(const EmulatedControllerPtr& emulated_controller, const ControllerPtr& controller); + void on_left_click(wxMouseEvent& event); + + void reset_configuration(); + virtual void load_controller(const EmulatedControllerPtr& controller); + + void set_selected_controller(const EmulatedControllerPtr& emulated_controller, const ControllerPtr& controller_base); + void reset_colours(); + +protected: + void bind_hotkey_events(wxTextCtrl* text_ctrl); + + void on_edit_key_focus(wxFocusEvent& event); + void on_edit_key_kill_focus(wxFocusEvent& event); + void on_right_click(wxMouseEvent& event); + + bool reset_focused_element(); + + bool m_right_down = false; + int m_focused_element = wxID_NONE; + std::unordered_map m_color_backup; +}; + diff --git a/src/gui/input/panels/ProControllerInputPanel.cpp b/src/gui/input/panels/ProControllerInputPanel.cpp new file mode 100644 index 00000000..6829059c --- /dev/null +++ b/src/gui/input/panels/ProControllerInputPanel.cpp @@ -0,0 +1,156 @@ +#include "gui/input/panels/ProControllerInputPanel.h" + +#include +#include +#include +#include +#include + +#include "gui/helpers/wxControlObject.h" +#include "input/emulated/ProController.h" +#include "gui/helpers/wxHelpers.h" +#include "gui/components/wxInputDraw.h" + +const ProController::ButtonId g_kFirstColumnItems[] = { ProController::kButtonId_A, ProController::kButtonId_B, ProController::kButtonId_X, ProController::kButtonId_Y, ProController::kButtonId_L, ProController::kButtonId_R, ProController::kButtonId_ZL, ProController::kButtonId_ZR, ProController::kButtonId_Plus, ProController::kButtonId_Minus }; +const ProController::ButtonId g_kSecondColumnItems[] = { ProController::kButtonId_StickL, ProController::kButtonId_StickL_Up, ProController::kButtonId_StickL_Down, ProController::kButtonId_StickL_Left, ProController::kButtonId_StickL_Right }; +const ProController::ButtonId g_kThirdColumnItems[] = { ProController::kButtonId_StickR, ProController::kButtonId_StickR_Up, ProController::kButtonId_StickR_Down, ProController::kButtonId_StickR_Left, ProController::kButtonId_StickR_Right }; +const ProController::ButtonId g_kFourthRowItems[] = { ProController::kButtonId_Up, ProController::kButtonId_Down, ProController::kButtonId_Left, ProController::kButtonId_Right }; + + +ProControllerInputPanel::ProControllerInputPanel(wxWindow* parent) + : InputPanel(parent) +{ + auto bold_font = GetFont(); + bold_font.MakeBold(); + + auto main_sizer = new wxGridBagSizer(); + + sint32 row = 0; + sint32 column = 0; + for (auto id : g_kFirstColumnItems) + { + row++; + + main_sizer->Add(new wxStaticText(this, wxID_ANY, to_wxString(ProController::get_button_name(id))), wxGBPosition(row, column), wxDefaultSpan, wxALL | wxALIGN_CENTER_VERTICAL, 5); + + auto text_ctrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxTE_PROCESS_ENTER | wxTE_PROCESS_TAB); + text_ctrl->SetClientData((void*)id); + text_ctrl->SetMinSize(wxSize(150, -1)); + text_ctrl->SetEditable(false); + text_ctrl->SetBackgroundColour(kKeyColourNormalMode); + bind_hotkey_events(text_ctrl); + main_sizer->Add(text_ctrl, wxGBPosition(row, column + 1), wxDefaultSpan, wxALL | wxEXPAND, 5); + } + + ////////////////////////////////////////////////////////////////// + + main_sizer->Add(new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVERTICAL), wxGBPosition(0, column + 2), wxGBSpan(11, 1), wxALL | wxEXPAND, 5); + + row = 0; + column += 3; + + auto text = new wxStaticText(this, wxID_ANY, _("Left Axis")); + text->SetFont(bold_font); + main_sizer->Add(text, wxGBPosition(row, column), wxGBSpan(1, 3), wxALL | wxEXPAND, 5); + + for (auto id : g_kSecondColumnItems) + { + row++; + + main_sizer->Add(new wxStaticText(this, wxID_ANY, to_wxString(ProController::get_button_name(id))), wxGBPosition(row, column), wxDefaultSpan, wxALL | wxALIGN_CENTER_VERTICAL, 5); + + auto text_ctrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxTE_PROCESS_ENTER | wxTE_PROCESS_TAB); + text_ctrl->SetClientData((void*)id); + text_ctrl->SetMinSize(wxSize(150, -1)); + text_ctrl->SetEditable(false); + text_ctrl->SetBackgroundColour(kKeyColourNormalMode); + bind_hotkey_events(text_ctrl); + main_sizer->Add(text_ctrl, wxGBPosition(row, column + 1), wxDefaultSpan, wxALL | wxEXPAND, 5); + } + + row++; + + // input drawer + m_left_draw = new wxInputDraw(this, wxID_ANY, wxDefaultPosition, { 60, 60 }); + main_sizer->Add(m_left_draw, wxGBPosition(row, column + 1), wxGBSpan(2, 1), wxTOP | wxBOTTOM | wxEXPAND | wxALIGN_CENTER, 5); + + main_sizer->Add(new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVERTICAL), wxGBPosition(0, column + 3), wxGBSpan(11, 1), wxALL | wxEXPAND, 5); + + ////////////////////////////////////////////////////////////////// + + row = 0; + column += 4; + + text = new wxStaticText(this, wxID_ANY, _("Right Axis")); + text->SetFont(bold_font); + main_sizer->Add(text, wxGBPosition(row, column), wxGBSpan(1, 3), wxALL | wxEXPAND, 5); + + for (auto id : g_kThirdColumnItems) + { + row++; + + main_sizer->Add(new wxStaticText(this, wxID_ANY, to_wxString(ProController::get_button_name(id))), wxGBPosition(row, column), wxDefaultSpan, wxALL | wxALIGN_CENTER_VERTICAL, 5); + + auto text_ctrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxTE_PROCESS_ENTER | wxTE_PROCESS_TAB); + text_ctrl->SetClientData((void*)id); + text_ctrl->SetMinSize(wxSize(150, -1)); + text_ctrl->SetEditable(false); + text_ctrl->SetBackgroundColour(kKeyColourNormalMode); + bind_hotkey_events(text_ctrl); + main_sizer->Add(text_ctrl, wxGBPosition(row, column + 1), wxDefaultSpan, wxALL | wxEXPAND, 5); + } + + row++; + + // input drawer + m_right_draw = new wxInputDraw(this, wxID_ANY, wxDefaultPosition, { 60, 60 }); + main_sizer->Add(m_right_draw, wxGBPosition(row, column + 1), wxGBSpan(2, 1), wxTOP | wxBOTTOM | wxEXPAND | wxALIGN_CENTER, 5); + + main_sizer->Add(new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVERTICAL), wxGBPosition(0, column + 3), wxGBSpan(11, 1), wxALL | wxEXPAND, 5); + + ////////////////////////////////////////////////////////////////// + + row = 0; + column += 4; + + text = new wxStaticText(this, wxID_ANY, _("D-pad")); + text->SetFont(bold_font); + main_sizer->Add(text, wxGBPosition(row, column), wxGBSpan(1, 3), wxALL | wxEXPAND, 5); + + for (auto id : g_kFourthRowItems) + { + row++; + + main_sizer->Add(new wxStaticText(this, wxID_ANY, to_wxString(ProController::get_button_name(id))), wxGBPosition(row, column), wxDefaultSpan, wxALL | wxALIGN_CENTER_VERTICAL, 5); + + auto text_ctrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxTE_PROCESS_ENTER | wxTE_PROCESS_TAB); + text_ctrl->SetClientData((void*)id); + text_ctrl->SetMinSize(wxSize(150, -1)); + text_ctrl->SetEditable(false); + text_ctrl->SetBackgroundColour(kKeyColourNormalMode); + bind_hotkey_events(text_ctrl); + main_sizer->Add(text_ctrl, wxGBPosition(row, column + 1), wxDefaultSpan, wxALL | wxEXPAND, 5); + } + + ////////////////////////////////////////////////////////////////// + + // LoadController(controller); + + SetSizerAndFit(main_sizer); + //wxWindow::Show(show); +} + +void ProControllerInputPanel::on_timer(const EmulatedControllerPtr& emulated_controller, + const ControllerPtr& controller_base) +{ + InputPanel::on_timer(emulated_controller, controller_base); + + if (emulated_controller) + { + const auto axis = emulated_controller->get_axis(); + const auto rotation = emulated_controller->get_rotation(); + + m_left_draw->SetAxisValue(axis); + m_right_draw->SetAxisValue(rotation); + } +} diff --git a/src/gui/input/panels/ProControllerInputPanel.h b/src/gui/input/panels/ProControllerInputPanel.h new file mode 100644 index 00000000..45529f74 --- /dev/null +++ b/src/gui/input/panels/ProControllerInputPanel.h @@ -0,0 +1,15 @@ +#pragma once + +#include "gui/input/panels/InputPanel.h" +#include "gui/components/wxInputDraw.h" + +class ProControllerInputPanel : public InputPanel +{ +public: + ProControllerInputPanel(wxWindow* parent); + + void on_timer(const EmulatedControllerPtr& emulated_controller, const ControllerPtr& controller) override; + +private: + wxInputDraw* m_left_draw, * m_right_draw; +}; diff --git a/src/gui/input/panels/VPADInputPanel.cpp b/src/gui/input/panels/VPADInputPanel.cpp new file mode 100644 index 00000000..cb69b31b --- /dev/null +++ b/src/gui/input/panels/VPADInputPanel.cpp @@ -0,0 +1,220 @@ +#include "gui/input/panels/VPADInputPanel.h" + +#include +#include +#include +#include +#include + + +#include "gui/helpers/wxControlObject.h" +#include "gui/helpers/wxHelpers.h" +#include "gui/components/wxInputDraw.h" +#include "input/emulated/VPADController.h" + +constexpr VPADController::ButtonId g_kFirstColumnItems[] = +{ + VPADController::kButtonId_A, VPADController::kButtonId_B, VPADController::kButtonId_X, VPADController::kButtonId_Y, + VPADController::kButtonId_L, VPADController::kButtonId_R, VPADController::kButtonId_ZL, VPADController::kButtonId_ZR, + VPADController::kButtonId_Plus, VPADController::kButtonId_Minus +}; + +constexpr VPADController::ButtonId g_kSecondColumnItems[] = +{ + VPADController::kButtonId_StickL, VPADController::kButtonId_StickL_Up, VPADController::kButtonId_StickL_Down, VPADController::kButtonId_StickL_Left, VPADController::kButtonId_StickL_Right +}; + +constexpr VPADController::ButtonId g_kThirdColumnItems[] = +{ + VPADController::kButtonId_StickR, VPADController::kButtonId_StickR_Up, VPADController::kButtonId_StickR_Down, VPADController::kButtonId_StickR_Left, VPADController::kButtonId_StickR_Right +}; + +constexpr VPADController::ButtonId g_kFourthRowItems[] = +{ + VPADController::kButtonId_Up, VPADController::kButtonId_Down, VPADController::kButtonId_Left, VPADController::kButtonId_Right +}; + + +VPADInputPanel::VPADInputPanel(wxWindow* parent) + : InputPanel(parent) +{ + auto bold_font = GetFont(); + bold_font.MakeBold(); + + auto* main_sizer = new wxGridBagSizer(); + + sint32 row = 0; + sint32 column = 0; + for (const auto& id : g_kFirstColumnItems) + { + row++; + + main_sizer->Add(new wxStaticText(this, wxID_ANY, to_wxString(VPADController::get_button_name(id))), wxGBPosition(row, column), wxDefaultSpan, wxALL | wxALIGN_CENTER_VERTICAL, 5); + + auto* text_ctrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxTE_PROCESS_ENTER | wxTE_PROCESS_TAB); + text_ctrl->SetClientData((void*)id); + text_ctrl->SetMinSize(wxSize(150, -1)); + text_ctrl->SetEditable(false); + text_ctrl->SetBackgroundColour(kKeyColourNormalMode); + bind_hotkey_events(text_ctrl); + main_sizer->Add(text_ctrl, wxGBPosition(row, column + 1), wxDefaultSpan, wxALL | wxEXPAND, 5); + } + + ////////////////////////////////////////////////////////////////// + + main_sizer->Add(new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVERTICAL), wxGBPosition(0, column + 2), wxGBSpan(11, 1), wxALL | wxEXPAND, 5); + + row = 0; + column += 3; + + auto text = new wxStaticText(this, wxID_ANY, _("Left Axis")); + text->SetFont(bold_font); + main_sizer->Add(text, wxGBPosition(row, column), wxGBSpan(1, 3), wxALL | wxEXPAND, 5); + + for (const auto& id : g_kSecondColumnItems) + { + row++; + + main_sizer->Add(new wxStaticText(this, wxID_ANY, to_wxString(VPADController::get_button_name(id))), wxGBPosition(row, column), wxDefaultSpan, wxALL | wxALIGN_CENTER_VERTICAL, 5); + + auto* text_ctrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxTE_PROCESS_ENTER | wxTE_PROCESS_TAB); + text_ctrl->SetClientData((void*)id); + text_ctrl->SetMinSize(wxSize(150, -1)); + text_ctrl->SetEditable(false); + text_ctrl->SetBackgroundColour(kKeyColourNormalMode); + bind_hotkey_events(text_ctrl); + main_sizer->Add(text_ctrl, wxGBPosition(row, column + 1), wxDefaultSpan, wxALL | wxEXPAND, 5); + } + + row++; + + // input drawer + m_left_draw = new wxInputDraw(this, wxID_ANY, wxDefaultPosition, { 60, 60 }); + main_sizer->Add(m_left_draw, wxGBPosition(row, column + 1), wxGBSpan(2, 1), wxTOP | wxBOTTOM | wxEXPAND | wxALIGN_CENTER, 5); + + main_sizer->Add(new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVERTICAL), wxGBPosition(0, column + 3), wxGBSpan(11, 1), wxALL | wxEXPAND, 5); + + ////////////////////////////////////////////////////////////////// + + row = 0; + column += 4; + + text = new wxStaticText(this, wxID_ANY, _("Right Axis")); + text->SetFont(bold_font); + main_sizer->Add(text, wxGBPosition(row, column), wxGBSpan(1, 3), wxALL | wxEXPAND, 5); + + for (const auto& id : g_kThirdColumnItems) + { + row++; + + main_sizer->Add(new wxStaticText(this, wxID_ANY, to_wxString(VPADController::get_button_name(id))), wxGBPosition(row, column), wxDefaultSpan, wxALL | wxALIGN_CENTER_VERTICAL, 5); + + auto* text_ctrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxTE_PROCESS_ENTER | wxTE_PROCESS_TAB); + text_ctrl->SetClientData((void*)id); + text_ctrl->SetMinSize(wxSize(150, -1)); + text_ctrl->SetEditable(false); + text_ctrl->SetBackgroundColour(kKeyColourNormalMode); + bind_hotkey_events(text_ctrl); + main_sizer->Add(text_ctrl, wxGBPosition(row, column + 1), wxDefaultSpan, wxALL | wxEXPAND, 5); + } + + row++; + + // input drawer + m_right_draw = new wxInputDraw(this, wxID_ANY, wxDefaultPosition, { 60, 60 }); + main_sizer->Add(m_right_draw, wxGBPosition(row, column + 1), wxGBSpan(2, 1), wxTOP | wxBOTTOM | wxEXPAND | wxALIGN_CENTER, 5); + + // Volume + row = 10; + + text = new wxStaticText(this, wxID_ANY, _("Volume")); + text->Disable(); + main_sizer->Add(text, wxGBPosition(row, column), wxDefaultSpan, wxALL, 5); + + auto*m_volume = new wxSlider(this, wxID_ANY, 0, 0, 100); + m_volume->Disable(); + main_sizer->Add(m_volume, wxGBPosition(row, column + 1), wxDefaultSpan, wxTOP | wxBOTTOM | wxEXPAND, 5); + + const auto volume_text = new wxStaticText(this, wxID_ANY, wxString::Format("%d%%", 0)); + volume_text->Disable(); + main_sizer->Add(volume_text, wxGBPosition(row, column + 2), wxDefaultSpan, wxALL, 5); + m_volume->Bind(wxEVT_SLIDER, &VPADInputPanel::OnVolumeChange, this, wxID_ANY, wxID_ANY, new wxControlObject(volume_text)); + + main_sizer->Add(new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVERTICAL), wxGBPosition(0, column + 3), wxGBSpan(11, 1), wxALL | wxEXPAND, 5); + + ////////////////////////////////////////////////////////////////// + + row = 0; + column += 4; + + text = new wxStaticText(this, wxID_ANY, _("D-pad")); + text->SetFont(bold_font); + main_sizer->Add(text, wxGBPosition(row, column), wxGBSpan(1, 3), wxALL | wxEXPAND, 5); + + for (const auto& id : g_kFourthRowItems) + { + row++; + + main_sizer->Add(new wxStaticText(this, wxID_ANY, to_wxString(VPADController::get_button_name(id))), wxGBPosition(row, column), wxDefaultSpan, wxALL | wxALIGN_CENTER_VERTICAL, 5); + + auto text_ctrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxTE_PROCESS_ENTER | wxTE_PROCESS_TAB); + text_ctrl->SetClientData((void*)id); + text_ctrl->SetMinSize(wxSize(150, -1)); + text_ctrl->SetEditable(false); + text_ctrl->SetBackgroundColour(kKeyColourNormalMode); + bind_hotkey_events(text_ctrl); + main_sizer->Add(text_ctrl, wxGBPosition(row, column + 1), wxDefaultSpan, wxALL | wxEXPAND, 5); + } + + // Blow Mic + row = 9; + main_sizer->Add(new wxStaticText(this, wxID_ANY, _("blow mic")), wxGBPosition(row, column), wxDefaultSpan, wxALL | wxALIGN_CENTER_VERTICAL, 5); + + auto text_ctrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxTE_PROCESS_ENTER | wxTE_PROCESS_TAB); + text_ctrl->SetClientData((void*)VPADController::kButtonId_Mic); + text_ctrl->SetMinSize(wxSize(150, -1)); + text_ctrl->SetEditable(false); + text_ctrl->SetBackgroundColour(kKeyColourNormalMode); + bind_hotkey_events(text_ctrl); + main_sizer->Add(text_ctrl, wxGBPosition(row, column + 1), wxDefaultSpan, wxALL | wxEXPAND, 5); + row++; + + main_sizer->Add(new wxStaticText(this, wxID_ANY, _("show screen")), wxGBPosition(row, column), wxDefaultSpan, wxALL | wxALIGN_CENTER_VERTICAL, 5); + + text_ctrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxTE_PROCESS_ENTER | wxTE_PROCESS_TAB); + text_ctrl->SetClientData((void*)VPADController::kButtonId_Screen); + text_ctrl->SetMinSize(wxSize(150, -1)); + text_ctrl->SetEditable(false); + text_ctrl->SetBackgroundColour(kKeyColourNormalMode); + bind_hotkey_events(text_ctrl); + main_sizer->Add(text_ctrl, wxGBPosition(row, column + 1), wxDefaultSpan, wxALL | wxEXPAND, 5); + + ////////////////////////////////////////////////////////////////// + + //LoadController(controller); + + SetSizer(main_sizer); + Layout(); + + //SetSizerAndFit(main_sizer); + //wxWindow::Show(show); +} + +void VPADInputPanel::on_timer(const EmulatedControllerPtr& emulated_controller, const ControllerPtr& controller_base) +{ + InputPanel::on_timer(emulated_controller, controller_base); + + if(emulated_controller) + { + const auto axis = emulated_controller->get_axis(); + const auto rotation = emulated_controller->get_rotation(); + + m_left_draw->SetAxisValue(axis); + m_right_draw->SetAxisValue(rotation); + } +} + +void VPADInputPanel::OnVolumeChange(wxCommandEvent& event) +{ + +} diff --git a/src/gui/input/panels/VPADInputPanel.h b/src/gui/input/panels/VPADInputPanel.h new file mode 100644 index 00000000..1e46fa86 --- /dev/null +++ b/src/gui/input/panels/VPADInputPanel.h @@ -0,0 +1,18 @@ +#pragma once + +#include "gui/input/panels/InputPanel.h" + +class wxInputDraw; + +class VPADInputPanel : public InputPanel +{ +public: + VPADInputPanel(wxWindow* parent); + + void on_timer(const EmulatedControllerPtr& emulated_controller, const ControllerPtr& controller) override; + +private: + void OnVolumeChange(wxCommandEvent& event); + + wxInputDraw* m_left_draw, * m_right_draw; +}; diff --git a/src/gui/input/panels/WiimoteInputPanel.cpp b/src/gui/input/panels/WiimoteInputPanel.cpp new file mode 100644 index 00000000..a2bf583a --- /dev/null +++ b/src/gui/input/panels/WiimoteInputPanel.cpp @@ -0,0 +1,257 @@ +#include "gui/input/panels/WiimoteInputPanel.h" + +#include +#include +#include +#include +#include +#include + +#include "gui/helpers/wxControlObject.h" +#include "input/emulated/WiimoteController.h" +#include "gui/helpers/wxHelpers.h" +#include "gui/components/wxInputDraw.h" + +constexpr WiimoteController::ButtonId g_kFirstColumnItems[] = +{ + WiimoteController::kButtonId_A, WiimoteController::kButtonId_B, WiimoteController::kButtonId_1, WiimoteController::kButtonId_2, WiimoteController::kButtonId_Plus, WiimoteController::kButtonId_Minus, WiimoteController::kButtonId_Home +}; + +constexpr WiimoteController::ButtonId g_kSecondColumnItems[] = +{ + WiimoteController::kButtonId_Up, WiimoteController::kButtonId_Down, WiimoteController::kButtonId_Left, WiimoteController::kButtonId_Right +}; + +constexpr WiimoteController::ButtonId g_kThirdColumnItems[] = +{ + WiimoteController::kButtonId_Nunchuck_C, WiimoteController::kButtonId_Nunchuck_Z, + WiimoteController::kButtonId_None, + WiimoteController::kButtonId_Nunchuck_Up,WiimoteController::kButtonId_Nunchuck_Down,WiimoteController::kButtonId_Nunchuck_Left,WiimoteController::kButtonId_Nunchuck_Right +}; + +WiimoteInputPanel::WiimoteInputPanel(wxWindow* parent) + : InputPanel(parent) +{ + auto bold_font = GetFont(); + bold_font.MakeBold(); + + auto* main_sizer = new wxBoxSizer(wxVERTICAL); + + auto* extensions_sizer = new wxBoxSizer(wxHORIZONTAL); + extensions_sizer->Add(new wxStaticText(this, wxID_ANY, _("Extensions:"))); + extensions_sizer->AddSpacer(10); + + m_motion_plus = new wxCheckBox(this, wxID_ANY, _("MotionPlus")); + m_motion_plus->Bind(wxEVT_CHECKBOX, &WiimoteInputPanel::on_extension_change, this); + extensions_sizer->Add(m_motion_plus); + + m_nunchuck = new wxCheckBox(this, wxID_ANY, _("Nunchuck")); + m_nunchuck->Bind(wxEVT_CHECKBOX, &WiimoteInputPanel::on_extension_change, this); + extensions_sizer->Add(m_nunchuck); + + m_classic = new wxCheckBox(this, wxID_ANY, _("Classic")); + m_classic->Bind(wxEVT_CHECKBOX, &WiimoteInputPanel::on_extension_change, this); + m_classic->Hide(); + extensions_sizer->Add(m_classic); + + main_sizer->Add(extensions_sizer, 0, wxEXPAND | wxALL, 5); + main_sizer->Add(new wxStaticLine(this), 0, wxLEFT | wxRIGHT | wxTOP | wxEXPAND, 5); + + m_item_sizer = new wxGridBagSizer(); + + sint32 row = 0; + sint32 column = 0; + for (const auto& id : g_kFirstColumnItems) + { + row++; + + m_item_sizer->Add(new wxStaticText(this, wxID_ANY, to_wxString(WiimoteController::get_button_name(id))), wxGBPosition(row, column), wxDefaultSpan, wxALL | wxALIGN_CENTER_VERTICAL, 5); + + auto* text_ctrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxTE_PROCESS_ENTER | wxTE_PROCESS_TAB); + text_ctrl->SetClientData((void*)id); + text_ctrl->SetMinSize(wxSize(150, -1)); + text_ctrl->SetEditable(false); + text_ctrl->SetBackgroundColour(kKeyColourNormalMode); + bind_hotkey_events(text_ctrl); + m_item_sizer->Add(text_ctrl, wxGBPosition(row, column + 1), wxDefaultSpan, wxALL | wxEXPAND, 5); + } + + m_item_sizer->Add(new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVERTICAL), wxGBPosition(0, column + 2), wxGBSpan(11, 1), wxLEFT | wxRIGHT | wxBOTTOM | wxEXPAND, 5); + + ////////////////////////////////////////////////////////////////// + + row = 0; + column += 3; + + auto text = new wxStaticText(this, wxID_ANY, _("D-pad")); + text->SetFont(bold_font); + m_item_sizer->Add(text, wxGBPosition(row, column), wxGBSpan(1, 3), wxALL | wxEXPAND, 5); + + for (const auto& id : g_kSecondColumnItems) + { + row++; + + m_item_sizer->Add(new wxStaticText(this, wxID_ANY, to_wxString(WiimoteController::get_button_name(id))), wxGBPosition(row, column), wxDefaultSpan, wxALL | wxALIGN_CENTER_VERTICAL, 5); + + auto* text_ctrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxTE_PROCESS_ENTER | wxTE_PROCESS_TAB); + text_ctrl->SetClientData((void*)id); + text_ctrl->SetMinSize(wxSize(150, -1)); + text_ctrl->SetEditable(false); + text_ctrl->SetBackgroundColour(kKeyColourNormalMode); + bind_hotkey_events(text_ctrl); + m_item_sizer->Add(text_ctrl, wxGBPosition(row, column + 1), wxDefaultSpan, wxALL | wxEXPAND, 5); + } + + row = 8; + // Volume + text = new wxStaticText(this, wxID_ANY, _("Volume")); + text->Disable(); + m_item_sizer->Add(text, wxGBPosition(row, column), wxDefaultSpan, wxALL, 5); + + m_volume = new wxSlider(this, wxID_ANY, 0, 0, 100); + m_volume->Disable(); + m_item_sizer->Add(m_volume, wxGBPosition(row, column + 1), wxDefaultSpan, wxTOP | wxBOTTOM | wxEXPAND, 5); + + const auto volume_text = new wxStaticText(this, wxID_ANY, wxString::Format("%d%%", 0)); + volume_text->Disable(); + m_item_sizer->Add(volume_text, wxGBPosition(row, column + 2), wxDefaultSpan, wxALL, 5); + m_volume->Bind(wxEVT_SLIDER, &WiimoteInputPanel::on_volume_change, this, wxID_ANY, wxID_ANY, new wxControlObject(volume_text)); + row++; + + m_item_sizer->Add(new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVERTICAL), wxGBPosition(0, column + 3), wxGBSpan(11, 1), wxLEFT | wxRIGHT | wxBOTTOM | wxEXPAND, 5); + + ////////////////////////////////////////////////////////////////// + + row = 0; + column += 4; + + text = new wxStaticText(this, wxID_ANY, _("Nunchuck")); + text->SetFont(bold_font); + m_item_sizer->Add(text, wxGBPosition(row, column), wxGBSpan(1, 3), wxALL | wxEXPAND, 5); + + for (const auto& id : g_kThirdColumnItems) + { + row++; + if (id == WiimoteController::kButtonId_None) + continue; + + m_item_sizer->Add(new wxStaticText(this, wxID_ANY, to_wxString(WiimoteController::get_button_name(id))), wxGBPosition(row, column), wxDefaultSpan, wxALL | wxALIGN_CENTER_VERTICAL, 5); + + auto* text_ctrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxTE_PROCESS_ENTER | wxTE_PROCESS_TAB); + text_ctrl->SetClientData((void*)id); + text_ctrl->SetMinSize(wxSize(150, -1)); + text_ctrl->SetEditable(false); + text_ctrl->SetBackgroundColour(kKeyColourNormalMode); + bind_hotkey_events(text_ctrl); + text_ctrl->Enable(m_nunchuck->GetValue()); + m_item_sizer->Add(text_ctrl, wxGBPosition(row, column + 1), wxDefaultSpan, wxALL | wxEXPAND, 5); + + m_nunchuck_items.push_back(text_ctrl); + } + + + // input drawer + m_draw = new wxInputDraw(this, wxID_ANY, wxDefaultPosition, { 60, 60 }); + m_draw->Enable(m_nunchuck->GetValue()); + m_item_sizer->Add(5, 0, wxGBPosition(3, column + 3), wxDefaultSpan, wxTOP | wxBOTTOM | wxEXPAND | wxALIGN_CENTER, 5); + m_item_sizer->Add(m_draw, wxGBPosition(3, column + 4), wxGBSpan(4, 1), wxTOP | wxBOTTOM | wxEXPAND | wxALIGN_CENTER, 5); + + m_nunchuck_items.push_back(m_draw); + + ////////////////////////////////////////////////////////////////// + + main_sizer->Add(m_item_sizer, 1, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, 5); + + SetSizer(main_sizer); + Layout(); +} + +void WiimoteInputPanel::set_active_device_type(WPADDeviceType type) +{ + m_device_type = type; + + m_motion_plus->SetValue(type == kWAPDevMPLS || type == kWAPDevMPLSFreeStyle || type == kWAPDevMPLSClassic); + switch(type) + { + case kWAPDevFreestyle: + case kWAPDevMPLSFreeStyle: + m_nunchuck->SetValue(true); + m_classic->SetValue(false); + for (const auto& item : m_nunchuck_items) + { + item->Enable(true); + } + break; + + case kWAPDevClassic: + case kWAPDevMPLSClassic: + m_nunchuck->SetValue(false); + m_classic->SetValue(true); + for (const auto& item : m_nunchuck_items) + { + item->Enable(false); + } + break; + + default: + m_nunchuck->SetValue(false); + m_classic->SetValue(false); + for (const auto& item : m_nunchuck_items) + { + item->Enable(false); + } + } +} + +void WiimoteInputPanel::on_volume_change(wxCommandEvent& event) +{ +} + +void WiimoteInputPanel::on_extension_change(wxCommandEvent& event) +{ + const auto obj = dynamic_cast(event.GetEventObject()); + wxASSERT(obj); + + if(m_motion_plus->GetValue() && m_nunchuck->GetValue()) + set_active_device_type(kWAPDevMPLSFreeStyle); + else if(m_motion_plus->GetValue() && m_classic->GetValue()) + set_active_device_type(kWAPDevMPLSClassic); + else if (m_motion_plus->GetValue()) + set_active_device_type(kWAPDevMPLS); + else if (m_nunchuck->GetValue()) + set_active_device_type(kWAPDevFreestyle); + else if (m_classic->GetValue()) + set_active_device_type(kWAPDevClassic); + else + set_active_device_type(kWAPDevCore); +} + +void WiimoteInputPanel::on_timer(const EmulatedControllerPtr& emulated_controller, const ControllerPtr& controller) +{ + if (emulated_controller) + { + const auto wiimote = std::dynamic_pointer_cast(emulated_controller); + wxASSERT(wiimote); + + wiimote->set_device_type(m_device_type); + } + + InputPanel::on_timer(emulated_controller, controller); + + if (emulated_controller) + { + const auto axis = emulated_controller->get_axis(); + m_draw->SetAxisValue(axis); + } +} + +void WiimoteInputPanel::load_controller(const EmulatedControllerPtr& emulated_controller) +{ + InputPanel::load_controller(emulated_controller); + + if (emulated_controller) { + const auto wiimote = std::dynamic_pointer_cast(emulated_controller); + wxASSERT(wiimote); + set_active_device_type(wiimote->get_device_type()); + } +} diff --git a/src/gui/input/panels/WiimoteInputPanel.h b/src/gui/input/panels/WiimoteInputPanel.h new file mode 100644 index 00000000..68e9e5fd --- /dev/null +++ b/src/gui/input/panels/WiimoteInputPanel.h @@ -0,0 +1,41 @@ +#pragma once + +#include "gui/input/panels/InputPanel.h" +#include + +class wxCheckBox; +class wxGridBagSizer; +class wxInputDraw; + +class WiimoteInputPanel : public InputPanel +{ +public: + WiimoteInputPanel(wxWindow* parent); + + void on_timer(const EmulatedControllerPtr& emulated_controller, const ControllerPtr& controller) override; + + void load_controller(const EmulatedControllerPtr& emulated_controller) override; + +private: + wxInputDraw* m_draw; + + WPADDeviceType m_device_type = kWAPDevCore; + void set_active_device_type(WPADDeviceType type); + + void on_volume_change(wxCommandEvent& event); + void on_extension_change(wxCommandEvent& event); + + wxGridBagSizer* m_item_sizer; + + wxCheckBox* m_nunchuck, * m_classic; + wxCheckBox* m_motion_plus; + + wxSlider* m_volume; + + std::vector m_nunchuck_items; + bool m_extensions_changed; +}; + + + + diff --git a/src/gui/input/settings/DefaultControllerSettings.cpp b/src/gui/input/settings/DefaultControllerSettings.cpp new file mode 100644 index 00000000..1ad4d8f4 --- /dev/null +++ b/src/gui/input/settings/DefaultControllerSettings.cpp @@ -0,0 +1,312 @@ +#include "gui/input/settings/DefaultControllerSettings.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gui/helpers/wxControlObject.h" +#include "gui/helpers/wxHelpers.h" +#include "gui/components/wxInputDraw.h" +#include "gui/input/InputAPIAddWindow.h" + + +DefaultControllerSettings::DefaultControllerSettings(wxWindow* parent, const wxPoint& position, std::shared_ptr controller) + : wxDialog(parent, wxID_ANY, _("Controller settings"), position, wxDefaultSize, + wxDEFAULT_DIALOG_STYLE), m_controller(std::move(controller)) +{ + m_settings = m_controller->get_settings(); + m_rumble_backup = m_settings.rumble; + + this->SetSizeHints(wxDefaultSize, wxDefaultSize); + + auto* sizer = new wxBoxSizer(wxVERTICAL); + + // options + { + auto* box = new wxStaticBox(this, wxID_ANY, _("Options")); + auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); + + // Motion + m_use_motion = new wxCheckBox(box, wxID_ANY, _("Use motion")); + m_use_motion->SetValue(m_settings.motion); + m_use_motion->Enable(m_controller->has_motion()); + box_sizer->Add(m_use_motion, 0, wxEXPAND | wxALL, 5); + + // Vibration + auto* rumbleSizer = new wxBoxSizer(wxHORIZONTAL); + + const auto rumble = (int)(m_settings.rumble * 100); + rumbleSizer->Add(new wxStaticText(box, wxID_ANY, _("Rumble")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + m_rumble = new wxSlider(box, wxID_ANY, rumble, 0, 100); + rumbleSizer->Add(m_rumble, 1, wxALL | wxEXPAND, 5); + + const auto text = new wxStaticText(box, wxID_ANY, wxString::Format("%d%%", rumble)); + rumbleSizer->Add(text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + m_rumble->Bind(wxEVT_SLIDER, &DefaultControllerSettings::on_rumble_change, this, wxID_ANY, wxID_ANY, new wxControlObject(text)); + + box_sizer->Add(rumbleSizer); + + sizer->Add(box_sizer, 1, wxALL|wxEXPAND, 5); + } + + auto* row_sizer = new wxBoxSizer(wxHORIZONTAL); + // Axis + { + auto* box = new wxStaticBox(this, wxID_ANY, _("Axis")); + auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); + + { + auto* content_sizer = new wxFlexGridSizer(0, 3, 0, 0); + + // Deadzone + const auto deadzone = (int)(m_settings.axis.deadzone * 100); + content_sizer->Add(new wxStaticText(box, wxID_ANY, _("Deadzone")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + m_axis_deadzone = new wxSlider(box, wxID_ANY, deadzone, 0, 100); + content_sizer->Add(m_axis_deadzone, 1, wxALL | wxEXPAND, 5); + + const auto deadzone_text = new wxStaticText(box, wxID_ANY, wxString::Format("%d%%", deadzone)); + content_sizer->Add(deadzone_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + + m_axis_deadzone->Bind(wxEVT_SLIDER, &DefaultControllerSettings::on_deadzone_change, this, wxID_ANY, wxID_ANY, + new wxControlObject(deadzone_text)); + + + // Range + const auto range = (int)(m_settings.axis.range * 100); + content_sizer->Add(new wxStaticText(box, wxID_ANY, _("Range")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + m_axis_range = new wxSlider(box, wxID_ANY, range, 50, 200); + content_sizer->Add(m_axis_range, 1, wxALL | wxEXPAND, 5); + + const auto range_text = new wxStaticText(box, wxID_ANY, wxString::Format("%d%%", range)); + content_sizer->Add(range_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + + m_axis_range->Bind(wxEVT_SLIDER, &DefaultControllerSettings::on_range_change, this, wxID_ANY, wxID_ANY, + new wxControlObject(range_text)); + + content_sizer->AddSpacer(1); + m_axis_draw = new wxInputDraw(box, wxID_ANY, wxDefaultPosition, { 60, 60 }); + content_sizer->Add(m_axis_draw, 0, wxTOP | wxBOTTOM | wxALIGN_CENTER, 5); + + box_sizer->Add(content_sizer, 1, wxEXPAND, 0); + } + + row_sizer->Add(box_sizer, 0, wxALL | wxEXPAND, 5); + } + + // Rotation + { + auto* box = new wxStaticBox(this, wxID_ANY, _("Rotation")); + auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); + + { + auto* content_sizer = new wxFlexGridSizer(0, 3, 0, 0); + + // Deadzone + const auto deadzone = (int)(m_settings.rotation.deadzone * 100); + content_sizer->Add(new wxStaticText(box, wxID_ANY, _("Deadzone")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + m_rotation_deadzone = new wxSlider(box, wxID_ANY, deadzone, 0, 100); + content_sizer->Add(m_rotation_deadzone, 1, wxALL | wxEXPAND, 5); + + const auto deadzone_text = new wxStaticText(box, wxID_ANY, wxString::Format("%d%%", deadzone)); + content_sizer->Add(deadzone_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + + m_rotation_deadzone->Bind(wxEVT_SLIDER, &DefaultControllerSettings::on_deadzone_change, this, wxID_ANY, wxID_ANY, + new wxControlObject(deadzone_text)); + + + // Range + const auto range = (int)(m_settings.rotation.range * 100); + content_sizer->Add(new wxStaticText(box, wxID_ANY, _("Range")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + m_rotation_range = new wxSlider(box, wxID_ANY, range, 50, 200); + content_sizer->Add(m_rotation_range, 1, wxALL | wxEXPAND, 5); + + const auto range_text = new wxStaticText(box, wxID_ANY, wxString::Format("%d%%", range)); + content_sizer->Add(range_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + + m_rotation_range->Bind(wxEVT_SLIDER, &DefaultControllerSettings::on_range_change, this, wxID_ANY, wxID_ANY, + new wxControlObject(range_text)); + + content_sizer->AddSpacer(1); + m_rotation_draw = new wxInputDraw(box, wxID_ANY, wxDefaultPosition, { 60, 60 }); + content_sizer->Add(m_rotation_draw, 0, wxTOP | wxBOTTOM | wxALIGN_CENTER, 5); + + box_sizer->Add(content_sizer, 1, wxEXPAND, 0); + } + + row_sizer->Add(box_sizer, 0, wxALL | wxEXPAND, 5); + } + + // Trigger + { + auto* box = new wxStaticBox(this, wxID_ANY, _("Trigger")); + auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); + + { + auto* content_sizer = new wxFlexGridSizer(0, 3, 0, 0); + + // Deadzone + const auto deadzone = (int)(m_settings.trigger.deadzone * 100); + content_sizer->Add(new wxStaticText(box, wxID_ANY, _("Deadzone")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + m_trigger_deadzone = new wxSlider(box, wxID_ANY, deadzone, 0, 100); + content_sizer->Add(m_trigger_deadzone, 1, wxALL | wxEXPAND, 5); + + const auto deadzone_text = new wxStaticText(box, wxID_ANY, wxString::Format("%d%%", deadzone)); + content_sizer->Add(deadzone_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + + m_trigger_deadzone->Bind(wxEVT_SLIDER, &DefaultControllerSettings::on_deadzone_change, this, wxID_ANY, wxID_ANY, + new wxControlObject(deadzone_text)); + + + // Range + const auto range = (int)(m_settings.trigger.range * 100); + content_sizer->Add(new wxStaticText(box, wxID_ANY, _("Range")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + m_trigger_range = new wxSlider(box, wxID_ANY, range, 50, 200); + content_sizer->Add(m_trigger_range, 1, wxALL | wxEXPAND, 5); + + const auto range_text = new wxStaticText(box, wxID_ANY, wxString::Format("%d%%", range)); + content_sizer->Add(range_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + + m_trigger_range->Bind(wxEVT_SLIDER, &DefaultControllerSettings::on_range_change, this, wxID_ANY, wxID_ANY, + new wxControlObject(range_text)); + + content_sizer->AddSpacer(1); + m_trigger_draw = new wxInputDraw(box, wxID_ANY, wxDefaultPosition, { 60, 60 }); + content_sizer->Add(m_trigger_draw, 0, wxTOP | wxBOTTOM | wxALIGN_CENTER, 5); + + box_sizer->Add(content_sizer, 1, wxEXPAND, 0); + } + + row_sizer->Add(box_sizer, 0, wxALL | wxEXPAND, 5); + } + sizer->Add(row_sizer); + + { + auto* control_sizer = new wxFlexGridSizer(0, 4, 0, 0); + control_sizer->AddGrowableCol(3); + + auto* ok_button = new wxButton(this, wxID_ANY, _("OK")); + ok_button->Bind(wxEVT_BUTTON, [this](auto&) { update_settings(); EndModal(wxID_OK); }); + control_sizer->Add(ok_button, 0, wxALL, 5); + + control_sizer->Add(0, 0, 0, wxEXPAND, 5); + + auto* cancel_button = new wxButton(this, wxID_ANY, _("Cancel")); + cancel_button->Bind(wxEVT_BUTTON, [this](auto&) { EndModal(wxID_CANCEL); }); + control_sizer->Add(cancel_button, 0, wxALL, 5); + + auto* calibrate_button = new wxButton(this, wxID_ANY, _("Calibrate")); + calibrate_button->Bind(wxEVT_BUTTON, [this](auto&) { m_controller->calibrate(); }); + control_sizer->Add(calibrate_button, 0, wxALL | wxALIGN_RIGHT, 5); + + sizer->Add(control_sizer, 0, wxEXPAND, 5); + } + + + + this->SetSizer(sizer); + this->Layout(); + this->Fit(); + + this->Bind(wxEVT_CLOSE_WINDOW, &DefaultControllerSettings::on_close, this); + + m_timer = new wxTimer(this); + Bind(wxEVT_TIMER, &DefaultControllerSettings::on_timer, this); + m_timer->Start(); +} + +DefaultControllerSettings::~DefaultControllerSettings() +{ + m_controller->stop_rumble(); + m_timer->Stop(); +} + +void DefaultControllerSettings::update_settings() +{ + // update settings + m_controller->set_settings(m_settings); + if (m_use_motion) + m_controller->set_use_motion(m_use_motion->GetValue()); +} + +void DefaultControllerSettings::on_timer(wxTimerEvent& event) +{ + if (m_rumble_time.has_value() && std::chrono::duration_cast(std::chrono::steady_clock::now() - m_rumble_time.value()).count() > 500 ) + { + m_controller->stop_rumble(); + m_rumble_time = {}; + } + + const auto& default_state = m_controller->get_default_state(); + + auto state = m_controller->raw_state(); + m_controller->apply_axis_setting(state.axis, default_state.axis, m_settings.axis); + m_controller->apply_axis_setting(state.rotation, default_state.rotation, m_settings.rotation); + m_controller->apply_axis_setting(state.trigger, default_state.trigger, m_settings.trigger); + + m_axis_draw->SetDeadzone(m_settings.axis.deadzone); + m_axis_draw->SetAxisValue(state.axis); + + m_rotation_draw->SetDeadzone(m_settings.rotation.deadzone); + m_rotation_draw->SetAxisValue(state.rotation); + + m_trigger_draw->SetDeadzone(m_settings.trigger.deadzone); + m_trigger_draw->SetAxisValue(state.trigger); +} + +void DefaultControllerSettings::on_close(wxCloseEvent& event) +{ + if (this->GetReturnCode() == 0 || this->GetReturnCode() == wxID_OK) + update_settings(); + + event.Skip(); +} + +void DefaultControllerSettings::on_deadzone_change(wxCommandEvent& event) +{ + update_slider_text(event); + const auto new_value = (float)event.GetInt() / 100.0f; + + if (event.GetEventObject() == m_axis_deadzone) + m_settings.axis.deadzone = new_value; + else if (event.GetEventObject() == m_rotation_deadzone) + m_settings.rotation.deadzone = new_value; + else if (event.GetEventObject() == m_trigger_deadzone) + m_settings.trigger.deadzone = new_value; +} + +void DefaultControllerSettings::on_range_change(wxCommandEvent& event) +{ + update_slider_text(event); + + const auto new_value = (float)event.GetInt() / 100.0f; + + if (event.GetEventObject() == m_axis_range) + m_settings.axis.range = new_value; + else if (event.GetEventObject() == m_rotation_range) + m_settings.rotation.range = new_value; + else if (event.GetEventObject() == m_trigger_range) + m_settings.trigger.range = new_value; +} + +void DefaultControllerSettings::on_rumble_change(wxCommandEvent& event) +{ + update_slider_text(event); + + const auto rumble_value = event.GetInt(); + m_settings.rumble = (float)rumble_value / 100.0f; + + m_controller->set_rumble(m_settings.rumble); + if (rumble_value != 0) + m_controller->start_rumble(); + else + m_controller->stop_rumble(); + + m_controller->set_rumble(m_rumble_backup); + + m_rumble_time = std::chrono::steady_clock::now(); +} diff --git a/src/gui/input/settings/DefaultControllerSettings.h b/src/gui/input/settings/DefaultControllerSettings.h new file mode 100644 index 00000000..a5cb76ef --- /dev/null +++ b/src/gui/input/settings/DefaultControllerSettings.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include + +#include "input/api/Controller.h" + +class wxCheckBox; +class wxInputDraw; + +class DefaultControllerSettings : public wxDialog +{ +public: + DefaultControllerSettings(wxWindow* parent, const wxPoint& position, ControllerPtr controller); + ~DefaultControllerSettings(); + +private: + void update_settings(); + + ControllerPtr m_controller; + ControllerBase::Settings m_settings; + float m_rumble_backup; + + wxTimer* m_timer; + std::optional m_rumble_time{}; + + wxSlider* m_axis_deadzone, *m_axis_range; + wxSlider* m_rotation_deadzone, *m_rotation_range; + wxSlider* m_trigger_deadzone, *m_trigger_range; + wxSlider* m_rumble; + + wxCheckBox* m_use_motion = nullptr; + + wxInputDraw* m_axis_draw, * m_rotation_draw, *m_trigger_draw; + + void on_timer(wxTimerEvent& event); + void on_close(wxCloseEvent& event); + void on_deadzone_change(wxCommandEvent& event); + void on_range_change(wxCommandEvent& event); + void on_rumble_change(wxCommandEvent& event); +}; diff --git a/src/gui/input/settings/WiimoteControllerSettings.cpp b/src/gui/input/settings/WiimoteControllerSettings.cpp new file mode 100644 index 00000000..05e0a24c --- /dev/null +++ b/src/gui/input/settings/WiimoteControllerSettings.cpp @@ -0,0 +1,369 @@ +#include "gui/input/settings/WiimoteControllerSettings.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gui/helpers/wxControlObject.h" +#include "gui/helpers/wxHelpers.h" +#include "gui/components/wxInputDraw.h" +#include "gui/input/InputAPIAddWindow.h" + +#if BOOST_OS_WINDOWS + +WiimoteControllerSettings::WiimoteControllerSettings(wxWindow* parent, const wxPoint& position, std::shared_ptr controller) + : wxDialog(parent, wxID_ANY, _("Controller settings"), position, wxDefaultSize, + wxDEFAULT_DIALOG_STYLE), m_controller(std::move(controller)) +{ + m_settings = m_controller->get_settings(); + m_rumble_backup = m_settings.rumble; + m_packet_delay_backup = m_controller->get_packet_delay(); + + this->SetSizeHints(wxDefaultSize, wxDefaultSize); + + auto* sizer = new wxBoxSizer(wxVERTICAL); + + // extension info + { + auto* box = new wxStaticBox(this, wxID_ANY, _("Connected extension")); + auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); + + m_extension_text = new wxStaticText(box, wxID_ANY, _("None")); + box_sizer->Add(m_extension_text, 0, wxALL, 5); + + sizer->Add(box_sizer, 0, wxALL | wxEXPAND, 5); + } + + // options + { + auto* box = new wxStaticBox(this, wxID_ANY, _("Options")); + auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); + + { + auto* row_sizer = new wxBoxSizer(wxHORIZONTAL); + // Rumble + m_rumble = new wxCheckBox(box, wxID_ANY, _("Rumble")); + m_rumble->SetValue(m_settings.rumble > 0); + row_sizer->Add(m_rumble, 0, wxALL, 5); + + m_rumble->Bind(wxEVT_CHECKBOX, &WiimoteControllerSettings::on_rumble_change, this); + + // Motion + m_use_motion = new wxCheckBox(box, wxID_ANY, _("Use motion")); + m_use_motion->SetValue(m_settings.motion); + m_use_motion->Enable(m_controller->has_motion()); + row_sizer->Add(m_use_motion, 0, wxALL, 5); + + box_sizer->Add(row_sizer); + } + { + auto* row_sizer = new wxBoxSizer(wxHORIZONTAL); + + // Delay + row_sizer->Add(new wxStaticText(box, wxID_ANY, _("Packet delay")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + m_package_delay = new wxSlider(box, wxID_ANY, m_packet_delay_backup, 1, 100); + row_sizer->Add(m_package_delay, 1, wxALL | wxEXPAND, 5); + + const auto range_text = new wxStaticText(box, wxID_ANY, wxString::Format("%dms", m_packet_delay_backup)); + row_sizer->Add(range_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + + m_package_delay->Bind(wxEVT_SLIDER, &WiimoteControllerSettings::on_delay_change, this, wxID_ANY, wxID_ANY, + new wxControlObject(range_text)); + + box_sizer->Add(row_sizer); + } + + sizer->Add(box_sizer, 0, wxALL | wxEXPAND, 5); + } + + // Nunchuck + { + m_nunchuck_settings = new wxStaticBox(this, wxID_ANY, _("Nunchuck")); + auto* box_sizer = new wxStaticBoxSizer(m_nunchuck_settings, wxVERTICAL); + + { + auto* content_sizer = new wxFlexGridSizer(0, 3, 0, 0); + + // Deadzone + const auto deadzone = (int)(m_settings.axis.deadzone * 100); + content_sizer->Add(new wxStaticText(m_nunchuck_settings, wxID_ANY, _("Deadzone")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + m_nunchuck_deadzone = new wxSlider(m_nunchuck_settings, wxID_ANY, deadzone, 0, 100); + content_sizer->Add(m_nunchuck_deadzone, 1, wxALL | wxEXPAND, 5); + + const auto deadzone_text = new wxStaticText(m_nunchuck_settings, wxID_ANY, wxString::Format("%d%%", deadzone)); + content_sizer->Add(deadzone_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + + m_nunchuck_deadzone->Bind(wxEVT_SLIDER, &WiimoteControllerSettings::on_slider_change, this, wxID_ANY, wxID_ANY, + new wxControlObject(deadzone_text)); + + + // Range + const auto range = (int)(m_settings.axis.range * 100); + content_sizer->Add(new wxStaticText(m_nunchuck_settings, wxID_ANY, _("Range")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + m_nunchuck_range = new wxSlider(m_nunchuck_settings, wxID_ANY, range, 50, 200); + content_sizer->Add(m_nunchuck_range, 1, wxALL | wxEXPAND, 5); + + const auto range_text = new wxStaticText(m_nunchuck_settings, wxID_ANY, wxString::Format("%d%%", range)); + content_sizer->Add(range_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + + m_nunchuck_range->Bind(wxEVT_SLIDER, &WiimoteControllerSettings::on_slider_change, this, wxID_ANY, wxID_ANY, + new wxControlObject(range_text)); + + content_sizer->AddSpacer(1); + m_nunchuck_draw = new wxInputDraw(m_nunchuck_settings, wxID_ANY, wxDefaultPosition, { 60, 60 }); + content_sizer->Add(m_nunchuck_draw, 0, wxTOP | wxBOTTOM | wxALIGN_CENTER, 5); + + box_sizer->Add(content_sizer, 1, wxEXPAND, 0); + } + + sizer->Add(box_sizer, 0, wxALL | wxEXPAND, 5); + } + + // Classic + { + m_classic_settings = new wxStaticBox(this, wxID_ANY, _("Classic")); + auto* box_sizer = new wxStaticBoxSizer(m_classic_settings, wxVERTICAL); + + { + auto* content_sizer = new wxFlexGridSizer(0, 6, 0, 0); + + // Deadzone + { + // Axis + const auto deadzone = (int)(m_settings.axis.deadzone * 100); + content_sizer->Add(new wxStaticText(m_classic_settings, wxID_ANY, _("Deadzone")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + m_classic_axis_deadzone = new wxSlider(m_classic_settings, wxID_ANY, deadzone, 0, 100); + content_sizer->Add(m_classic_axis_deadzone, 1, wxALL | wxEXPAND, 5); + + const auto deadzone_text = new wxStaticText(m_classic_settings, wxID_ANY, wxString::Format("%d%%", deadzone)); + content_sizer->Add(deadzone_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + + m_classic_axis_deadzone->Bind(wxEVT_SLIDER, &WiimoteControllerSettings::on_slider_change, this, wxID_ANY, wxID_ANY, + new wxControlObject(deadzone_text)); + } + { + // Range + const auto deadzone = (int)(m_settings.rotation.deadzone * 100); + content_sizer->Add(new wxStaticText(m_classic_settings, wxID_ANY, _("Deadzone")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + m_classic_rotation_deadzone = new wxSlider(m_classic_settings, wxID_ANY, deadzone, 0, 100); + content_sizer->Add(m_classic_rotation_deadzone, 1, wxALL | wxEXPAND, 5); + + const auto deadzone_text = new wxStaticText(m_classic_settings, wxID_ANY, wxString::Format("%d%%", deadzone)); + content_sizer->Add(deadzone_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + + m_classic_rotation_deadzone->Bind(wxEVT_SLIDER, &WiimoteControllerSettings::on_slider_change, this, wxID_ANY, wxID_ANY, + new wxControlObject(deadzone_text)); + } + + // Range + { + // Axis + const auto range = (int)(m_settings.axis.range * 100); + content_sizer->Add(new wxStaticText(m_classic_settings, wxID_ANY, _("Range")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + m_classic_axis_range = new wxSlider(m_classic_settings, wxID_ANY, range, 50, 200); + content_sizer->Add(m_classic_axis_range, 1, wxALL | wxEXPAND, 5); + + const auto range_text = new wxStaticText(m_classic_settings, wxID_ANY, wxString::Format("%d%%", range)); + content_sizer->Add(range_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + + m_classic_axis_range->Bind(wxEVT_SLIDER, &WiimoteControllerSettings::on_slider_change, this, wxID_ANY, wxID_ANY, + new wxControlObject(range_text)); + } + { + // Rotation + const auto range = (int)(m_settings.rotation.range * 100); + content_sizer->Add(new wxStaticText(m_classic_settings, wxID_ANY, _("Range")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + m_classic_rotation_range = new wxSlider(m_classic_settings, wxID_ANY, range, 50, 200); + content_sizer->Add(m_classic_rotation_range, 1, wxALL | wxEXPAND, 5); + + const auto range_text = new wxStaticText(m_classic_settings, wxID_ANY, wxString::Format("%d%%", range)); + content_sizer->Add(range_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + + m_classic_rotation_range->Bind(wxEVT_SLIDER, &WiimoteControllerSettings::on_slider_change, this, wxID_ANY, wxID_ANY, + new wxControlObject(range_text)); + } + + content_sizer->AddSpacer(1); + m_classic_axis_draw = new wxInputDraw(m_classic_settings, wxID_ANY, wxDefaultPosition, { 60, 60 }); + content_sizer->Add(m_classic_axis_draw, 0, wxTOP | wxBOTTOM | wxALIGN_CENTER, 5); + content_sizer->AddSpacer(1); + + content_sizer->AddSpacer(1); + m_classic_rotation_draw = new wxInputDraw(m_classic_settings, wxID_ANY, wxDefaultPosition, { 60, 60 }); + content_sizer->Add(m_classic_rotation_draw, 0, wxTOP | wxBOTTOM | wxALIGN_CENTER, 5); + + box_sizer->Add(content_sizer, 1, wxEXPAND, 0); + } + + sizer->Add(box_sizer, 0, wxALL | wxEXPAND, 5); + } + + { + auto* control_sizer = new wxFlexGridSizer(0, 4, 0, 0); + control_sizer->AddGrowableCol(3); + + auto* ok_button = new wxButton(this, wxID_ANY, _("OK")); + ok_button->Bind(wxEVT_BUTTON, [this](auto&) { update_settings(); EndModal(wxID_OK); }); + control_sizer->Add(ok_button, 0, wxALL, 5); + + control_sizer->Add(0, 0, 0, wxEXPAND, 5); + + auto* cancel_button = new wxButton(this, wxID_ANY, _("Cancel")); + cancel_button->Bind(wxEVT_BUTTON, [this](auto&) { EndModal(wxID_CANCEL); }); + control_sizer->Add(cancel_button, 0, wxALL, 5); + + auto* calibrate_button = new wxButton(this, wxID_ANY, _("Calibrate")); + calibrate_button->Bind(wxEVT_BUTTON, [this](auto&) { m_controller->calibrate(); }); + control_sizer->Add(calibrate_button, 0, wxALL | wxALIGN_RIGHT, 5); + + sizer->Add(control_sizer, 0, wxEXPAND, 5); + } + + + + this->SetSizer(sizer); + this->Layout(); + this->Fit(); + + this->Bind(wxEVT_CLOSE_WINDOW, &WiimoteControllerSettings::on_close, this); + + m_timer = new wxTimer(this); + Bind(wxEVT_TIMER, &WiimoteControllerSettings::on_timer, this); + m_timer->Start(); +} + +WiimoteControllerSettings::~WiimoteControllerSettings() +{ + m_controller->stop_rumble(); + m_timer->Stop(); +} + +void WiimoteControllerSettings::update_settings() +{ + // update settings + m_controller->set_settings(m_settings); + if (m_use_motion) + m_controller->set_use_motion(m_use_motion->GetValue()); + + m_controller->set_packet_delay(m_package_delay->GetValue()); +} + +void WiimoteControllerSettings::on_timer(wxTimerEvent& event) +{ + if (m_rumble_time.has_value() && std::chrono::duration_cast(std::chrono::steady_clock::now() - m_rumble_time.value()).count() > 500) + { + m_controller->stop_rumble(); + m_rumble_time = {}; + } + + const auto& default_state = m_controller->get_default_state(); + + auto state = m_controller->raw_state(); + m_controller->apply_axis_setting(state.axis, default_state.axis, m_settings.axis); + m_controller->apply_axis_setting(state.rotation, default_state.rotation, m_settings.rotation); + m_controller->apply_axis_setting(state.trigger, default_state.trigger, m_settings.trigger); + + if (m_nunchuck_settings->IsEnabled()) + { + m_nunchuck_draw->SetDeadzone(m_settings.axis.deadzone); + m_nunchuck_draw->SetAxisValue(state.axis); + } + + if (m_classic_settings->IsEnabled()) + { + m_classic_axis_draw->SetDeadzone(m_settings.axis.deadzone); + m_classic_axis_draw->SetAxisValue(state.axis); + + m_classic_rotation_draw->SetDeadzone(m_settings.rotation.deadzone); + m_classic_rotation_draw->SetAxisValue(state.rotation); + } + + wxString label; + switch (m_controller->get_extension()) + { + case NativeWiimoteController::Nunchuck: + label = _("Nunchuck"); + m_nunchuck_settings->Enable(); + m_classic_settings->Disable(); + break; + case NativeWiimoteController::Classic: + label = _("Classic"); + m_nunchuck_settings->Disable(); + m_classic_settings->Enable(); + break; + default: + m_nunchuck_settings->Disable(); + m_classic_settings->Disable(); + } + + if(m_controller->is_mpls_attached()) + { + const bool empty = label.empty(); + if (!empty) + label.Append(" ("); + + label.Append(_("MotionPlus")); + + if (!empty) + label.Append(")"); + } + + if(label.empty()) + { + label = _("None"); + } + + m_extension_text->SetLabelText(label); +} + +void WiimoteControllerSettings::on_close(wxCloseEvent& event) +{ + if (this->GetReturnCode() == 0 || this->GetReturnCode() == wxID_OK) + update_settings(); + + event.Skip(); +} + +void WiimoteControllerSettings::on_slider_change(wxCommandEvent& event) +{ + update_slider_text(event); + const auto new_value = (float)event.GetInt() / 100.0f; + + auto* obj = event.GetEventObject(); + if (obj == m_nunchuck_deadzone || obj == m_classic_axis_deadzone) + m_settings.axis.deadzone = new_value; + else if (obj == m_nunchuck_range || obj == m_classic_axis_range) + m_settings.axis.range = new_value; + else if (obj == m_classic_rotation_deadzone) + m_settings.rotation.deadzone = new_value; + else if (obj == m_classic_rotation_range) + m_settings.rotation.range = new_value; +} + +void WiimoteControllerSettings::on_rumble_change(wxCommandEvent& event) +{ + const auto rumble_value = m_rumble->GetValue(); + m_settings.rumble = rumble_value ? 1.0f : 0.0f; + + m_controller->set_rumble(m_settings.rumble); + if (rumble_value) + m_controller->start_rumble(); + else + m_controller->stop_rumble(); + + m_controller->set_rumble(m_rumble_backup); + + m_rumble_time = std::chrono::steady_clock::now(); +} + +void WiimoteControllerSettings::on_delay_change(wxCommandEvent& event) +{ + update_slider_text(event, "%dms"); +} + +#endif \ No newline at end of file diff --git a/src/gui/input/settings/WiimoteControllerSettings.h b/src/gui/input/settings/WiimoteControllerSettings.h new file mode 100644 index 00000000..b519b9e5 --- /dev/null +++ b/src/gui/input/settings/WiimoteControllerSettings.h @@ -0,0 +1,61 @@ +#pragma once + +#if BOOST_OS_WINDOWS + +#include +#include +#include + +#include "input/api/Controller.h" +#include "input/api/Wiimote/NativeWiimoteController.h" + +class wxStaticBox; +class wxStaticText; +class wxCheckBox; +class wxInputDraw; + +class WiimoteControllerSettings : public wxDialog +{ +public: + WiimoteControllerSettings(wxWindow* parent, const wxPoint& position, std::shared_ptr controller); + ~WiimoteControllerSettings(); + +private: + void update_settings(); + + std::shared_ptr m_controller; + ControllerBase::Settings m_settings; + float m_rumble_backup; + uint32 m_packet_delay_backup; + + wxTimer* m_timer; + std::optional m_rumble_time{}; + + wxStaticText* m_extension_text; + + wxSlider* m_package_delay; + wxCheckBox* m_rumble = nullptr; + wxCheckBox* m_use_motion = nullptr; + + wxStaticBox* m_nunchuck_settings; + wxSlider* m_nunchuck_deadzone, * m_nunchuck_range; + wxInputDraw* m_nunchuck_draw; + + wxStaticBox* m_classic_settings; + wxSlider* m_classic_axis_deadzone, * m_classic_axis_range; + wxInputDraw* m_classic_axis_draw; + wxSlider* m_classic_rotation_deadzone, * m_classic_rotation_range; + wxInputDraw* m_classic_rotation_draw; + + + + + + void on_timer(wxTimerEvent& event); + void on_close(wxCloseEvent& event); + void on_slider_change(wxCommandEvent& event); + void on_rumble_change(wxCommandEvent& event); + void on_delay_change(wxCommandEvent& event); +}; + +#endif \ No newline at end of file diff --git a/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp b/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp new file mode 100644 index 00000000..cd509a52 --- /dev/null +++ b/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp @@ -0,0 +1,379 @@ +#include "gui/wxgui.h" +#include "Cafe/OS/libs/coreinit/coreinit_Thread.h" +#include "Cafe/OS/libs/coreinit/coreinit_Scheduler.h" +#include "DebugPPCThreadsWindow.h" +#include "Cafe/OS/RPL/rpl.h" +#include "Cafe/OS/RPL/rpl_symbol_storage.h" + +enum +{ + // options + REFRESH_ID, + AUTO_REFRESH_ID, + CLOSE_ID, + GPLIST_ID, + + // list context menu options + THREADLIST_MENU_BOOST_PRIO_1, + THREADLIST_MENU_BOOST_PRIO_5, + THREADLIST_MENU_DECREASE_PRIO_1, + THREADLIST_MENU_DECREASE_PRIO_5, + THREADLIST_MENU_SUSPEND, + THREADLIST_MENU_RESUME, + THREADLIST_MENU_DUMP_STACK_TRACE, +}; + +wxBEGIN_EVENT_TABLE(DebugPPCThreadsWindow, wxFrame) + EVT_BUTTON(CLOSE_ID,DebugPPCThreadsWindow::OnCloseButton) + EVT_BUTTON(REFRESH_ID,DebugPPCThreadsWindow::OnRefreshButton) + + EVT_CLOSE(DebugPPCThreadsWindow::OnClose) +wxEND_EVENT_TABLE() + +DebugPPCThreadsWindow::DebugPPCThreadsWindow(wxFrame& parent) + : wxFrame(&parent, wxID_ANY, _("PPC threads"), wxDefaultPosition, wxSize(930, 280), wxCLOSE_BOX | wxCLIP_CHILDREN | wxCAPTION | wxRESIZE_BORDER) +{ + wxFrame::SetBackgroundColour(*wxWHITE); + + auto* sizer = new wxBoxSizer(wxVERTICAL); + m_thread_list = new wxListCtrl(this, GPLIST_ID, wxPoint(0, 0), wxSize(930, 240), wxLC_REPORT); + + m_thread_list->SetFont(wxFont(8, wxFONTFAMILY_MODERN, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, "Courier New")); //wxSystemSettings::GetFont(wxSYS_OEM_FIXED_FONT)); + + // add columns + wxListItem col0; + col0.SetId(0); + col0.SetText(_("Address")); + col0.SetWidth(75); + m_thread_list->InsertColumn(0, col0); + wxListItem col1; + col1.SetId(1); + col1.SetText(_("Entry")); + col1.SetWidth(75); + m_thread_list->InsertColumn(1, col1); + wxListItem col2; + col2.SetId(2); + col2.SetText(_("Stack")); + col2.SetWidth(145); + m_thread_list->InsertColumn(2, col2); + wxListItem col3; + col3.SetId(3); + col3.SetText(_("PC")); + col3.SetWidth(120); + m_thread_list->InsertColumn(3, col3); + wxListItem colLR; + colLR.SetId(4); + colLR.SetText(_("LR")); + colLR.SetWidth(75); + m_thread_list->InsertColumn(4, colLR); + wxListItem col4; + col4.SetId(5); + col4.SetText(_("State")); + col4.SetWidth(90); + m_thread_list->InsertColumn(5, col4); + wxListItem col5; + col5.SetId(6); + col5.SetText(_("Affinity")); + col5.SetWidth(70); + m_thread_list->InsertColumn(6, col5); + wxListItem colPriority; + colPriority.SetId(7); + colPriority.SetText(_("Priority")); + colPriority.SetWidth(80); + m_thread_list->InsertColumn(7, colPriority); + wxListItem col6; + col6.SetId(8); + col6.SetText(_("SliceStart")); + col6.SetWidth(110); + m_thread_list->InsertColumn(8, col6); + wxListItem col7; + col7.SetId(9); + col7.SetText(_("SumWakeTime")); + col7.SetWidth(110); + m_thread_list->InsertColumn(9, col7); + wxListItem col8; + col8.SetId(10); + col8.SetText(_("ThreadName")); + col8.SetWidth(180); + m_thread_list->InsertColumn(10, col8); + wxListItem col9; + col9.SetId(11); + col9.SetText(_("GPR")); + col9.SetWidth(180); + m_thread_list->InsertColumn(11, col9); + wxListItem col10; + col10.SetId(12); + col10.SetText(_("Extra info")); + col10.SetWidth(180); + m_thread_list->InsertColumn(12, col10); + + sizer->Add(m_thread_list, 1, wxEXPAND | wxALL, 5); + + auto* row = new wxBoxSizer(wxHORIZONTAL); + wxButton* button = new wxButton(this, REFRESH_ID, _("Refresh"), wxPoint(0, 0), wxSize(80, 26)); + row->Add(button, 0, wxALL, 5); + + m_auto_refresh = new wxCheckBox(this, AUTO_REFRESH_ID, _("Auto refresh")); + m_auto_refresh->SetValue(true); + row->Add(m_auto_refresh, 0, wxEXPAND | wxALL, 5); + + sizer->Add(row, 0, wxEXPAND | wxALL, 5); + + m_thread_list->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(DebugPPCThreadsWindow::OnThreadListRightClick), nullptr, this); + + SetSizer(sizer); + + RefreshThreadList(); + + m_timer = new wxTimer(this); + this->Bind(wxEVT_TIMER, &DebugPPCThreadsWindow::OnTimer, this); + m_timer->Start(250); +} + +DebugPPCThreadsWindow::~DebugPPCThreadsWindow() +{ + m_timer->Stop(); +} + +void DebugPPCThreadsWindow::OnCloseButton(wxCommandEvent& event) +{ + Close(); +} + +void DebugPPCThreadsWindow::OnRefreshButton(wxCommandEvent& event) +{ + RefreshThreadList(); +} + +void DebugPPCThreadsWindow::OnClose(wxCloseEvent& event) +{ + Close(); +} + +void DebugPPCThreadsWindow::OnTimer(wxTimerEvent& event) +{ + if (m_auto_refresh->IsChecked()) + RefreshThreadList(); +} + + +#define _r(__idx) _swapEndianU32(cafeThread->context.gpr[__idx]) + +void DebugPPCThreadsWindow::RefreshThreadList() +{ + wxWindowUpdateLocker lock(m_thread_list); + + long selected_thread = 0; + const int selection = m_thread_list->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + if (selection != wxNOT_FOUND) + selected_thread = m_thread_list->GetItemData(selection); + + const int scrollPos = m_thread_list->GetScrollPos(0); + m_thread_list->DeleteAllItems(); + + __OSLockScheduler(); + srwlock_activeThreadList.LockWrite(); + for (sint32 i = 0; i < activeThreadCount; i++) + { + MPTR threadItrMPTR = activeThread[i]; + OSThread_t* cafeThread = (OSThread_t*)memory_getPointerFromVirtualOffset(threadItrMPTR); + + char tempStr[512]; + sprintf(tempStr, "%08X", threadItrMPTR); + + + wxListItem item; + item.SetId(i); + item.SetText(tempStr); + m_thread_list->InsertItem(item); + m_thread_list->SetItemData(item, (long)threadItrMPTR); + // entry point + sprintf(tempStr, "%08X", _swapEndianU32(cafeThread->entrypoint)); + m_thread_list->SetItem(i, 1, tempStr); + // stack base (low) + sprintf(tempStr, "%08X - %08X", _swapEndianU32(cafeThread->stackEnd), _swapEndianU32(cafeThread->stackBase)); + m_thread_list->SetItem(i, 2, tempStr); + // pc + RPLStoredSymbol* symbol = rplSymbolStorage_getByAddress(cafeThread->context.srr0); + if (symbol) + sprintf(tempStr, "%s (0x%08x)", (const char*)symbol->symbolName, cafeThread->context.srr0); + else + sprintf(tempStr, "%08X", cafeThread->context.srr0); + m_thread_list->SetItem(i, 3, tempStr); + // lr + sprintf(tempStr, "%08X", _swapEndianU32(cafeThread->context.lr)); + m_thread_list->SetItem(i, 4, tempStr); + // state + OSThread_t::THREAD_STATE threadState = cafeThread->state; + wxString threadStateStr = "UNDEFINED"; + if (cafeThread->suspendCounter != 0) + threadStateStr = "SUSPENDED"; + else if (threadState == OSThread_t::THREAD_STATE::STATE_NONE) + threadStateStr = "NONE"; + else if (threadState == OSThread_t::THREAD_STATE::STATE_READY) + threadStateStr = "READY"; + else if (threadState == OSThread_t::THREAD_STATE::STATE_RUNNING) + threadStateStr = "RUNNING"; + else if (threadState == OSThread_t::THREAD_STATE::STATE_WAITING) + threadStateStr = "WAITING"; + else if (threadState == OSThread_t::THREAD_STATE::STATE_MORIBUND) + threadStateStr = "MORIBUND"; + m_thread_list->SetItem(i, 5, threadStateStr); + // affinity + uint8 affinity = cafeThread->attr&7; + uint8 affinityReal = cafeThread->context.affinity; + if(affinity != affinityReal) + sprintf(tempStr, "(!) %d%d%d real: %d%d%d", (affinity >> 0) & 1, (affinity >> 1) & 1, (affinity >> 2) & 1, (affinityReal >> 0) & 1, (affinityReal >> 1) & 1, (affinityReal >> 2) & 1); + else + sprintf(tempStr, "%d%d%d", (affinity >> 0) & 1, (affinity >> 1) & 1, (affinity >> 2) & 1); + m_thread_list->SetItem(i, 6, tempStr); + // priority + sint32 effectivePriority = cafeThread->effectivePriority; + sprintf(tempStr, "%d", effectivePriority); + m_thread_list->SetItem(i, 7, tempStr); + // last awake in cycles + uint64 lastWakeUpTime = cafeThread->wakeUpTime; + sprintf(tempStr, "%I64u", lastWakeUpTime); + m_thread_list->SetItem(i, 8, tempStr); + // awake time in cycles + uint64 awakeTime = cafeThread->totalCycles; + sprintf(tempStr, "%I64u", awakeTime); + m_thread_list->SetItem(i, 9, tempStr); + // thread name + const char* threadName = "NULL"; + if (!cafeThread->threadName.IsNull()) + threadName = cafeThread->threadName.GetPtr(); + m_thread_list->SetItem(i, 10, threadName); + // GPR + sprintf(tempStr, "r3 %08x r4 %08x r5 %08x r6 %08x r7 %08x", _r(3), _r(4), _r(5), _r(6), _r(7)); + m_thread_list->SetItem(i, 11, tempStr); + // waiting condition / extra info + coreinit::OSMutex* mutex = cafeThread->waitingForMutex; + if (mutex) + sprintf(tempStr, "Mutex 0x%08x (Held by thread 0x%08X Lock-Count: %d)", memory_getVirtualOffsetFromPointer(mutex), mutex->owner.GetMPTR(), (uint32)mutex->lockCount); + else + sprintf(tempStr, ""); + + // OSSetThreadCancelState + if (cafeThread->requestFlags & OSThread_t::REQUEST_FLAG_CANCEL) + strcat(tempStr, "[Cancel requested]"); + + m_thread_list->SetItem(i, 12, tempStr); + + if(selected_thread != 0 && selected_thread == (long)threadItrMPTR) + m_thread_list->SetItemState(i, wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED, wxLIST_STATE_FOCUSED | wxLIST_STATE_SELECTED); + } + srwlock_activeThreadList.UnlockWrite(); + __OSUnlockScheduler(); + + m_thread_list->SetScrollPos(0, scrollPos, true); +} + +void DebugLogStackTrace(OSThread_t* thread, MPTR sp); + +void DebugPPCThreadsWindow::DumpStackTrace(OSThread_t* thread) +{ + cemuLog_log(LogType::Force, fmt::format("Dumping stack trace for thread {0:08x} LR: {1:08x}", memory_getVirtualOffsetFromPointer(thread), _swapEndianU32(thread->context.lr))); + DebugLogStackTrace(thread, _swapEndianU32(thread->context.gpr[1])); +} + +void DebugPPCThreadsWindow::OnThreadListPopupClick(wxCommandEvent& evt) +{ + MPTR threadMPTR = (MPTR)(size_t)static_cast(evt.GetEventObject())->GetClientData(); + // check if thread is still active + bool threadIsActive = false; + srwlock_activeThreadList.LockWrite(); + for (sint32 i = 0; i < activeThreadCount; i++) + { + MPTR threadItrMPTR = activeThread[i]; + if (threadItrMPTR == threadMPTR) + { + threadIsActive = true; + break; + } + } + srwlock_activeThreadList.UnlockWrite(); + if (threadIsActive == false) + return; + // handle command + OSThread_t* osThread = (OSThread_t*)memory_getPointerFromVirtualOffset(threadMPTR); + switch (evt.GetId()) + { + case THREADLIST_MENU_BOOST_PRIO_5: + osThread->basePriority = osThread->basePriority - 5; + break; + case THREADLIST_MENU_BOOST_PRIO_1: + osThread->basePriority = osThread->basePriority - 1; + break; + case THREADLIST_MENU_DECREASE_PRIO_5: + osThread->basePriority = osThread->basePriority + 5; + break; + case THREADLIST_MENU_DECREASE_PRIO_1: + osThread->basePriority = osThread->basePriority + 1; + break; + case THREADLIST_MENU_SUSPEND: + coreinit::OSSuspendThread(osThread); + break; + case THREADLIST_MENU_RESUME: + coreinit::OSResumeThread(osThread); + break; + case THREADLIST_MENU_DUMP_STACK_TRACE: + DumpStackTrace(osThread); + break; + } + coreinit::__OSUpdateThreadEffectivePriority(osThread); + // update thread list + RefreshThreadList(); +} + +void DebugPPCThreadsWindow::OnThreadListRightClick(wxMouseEvent& event) +{ + // Get the item index + int hitTestFlag; + int itemIndex = m_thread_list->HitTest(event.GetPosition(), hitTestFlag); + if (itemIndex == wxNOT_FOUND) + return; + // select item + m_thread_list->SetItemState(itemIndex, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED); + long sel = m_thread_list->GetNextItem(-1, wxLIST_NEXT_ALL, + wxLIST_STATE_SELECTED); + if (sel != -1) + m_thread_list->SetItemState(sel, 0, wxLIST_STATE_SELECTED); + m_thread_list->SetItemState(itemIndex, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); + // check if thread is still on the list of active threads + MPTR threadMPTR = (MPTR)m_thread_list->GetItemData(itemIndex); + bool threadIsActive = false; + srwlock_activeThreadList.LockWrite(); + for (sint32 i = 0; i < activeThreadCount; i++) + { + MPTR threadItrMPTR = activeThread[i]; + if (threadItrMPTR == threadMPTR) + { + threadIsActive = true; + break; + } + } + srwlock_activeThreadList.UnlockWrite(); + if (threadIsActive == false) + return; + // create menu entry + wxMenu menu; + menu.SetClientData((void*)(size_t)threadMPTR); + menu.Append(THREADLIST_MENU_BOOST_PRIO_5, _("Boost priority (-5)")); + menu.Append(THREADLIST_MENU_BOOST_PRIO_1, _("Boost priority (-1)")); + menu.AppendSeparator(); + menu.Append(THREADLIST_MENU_DECREASE_PRIO_5, _("Decrease priority (+5)")); + menu.Append(THREADLIST_MENU_DECREASE_PRIO_1, _("Decrease priority (+1)")); + menu.AppendSeparator(); + menu.Append(THREADLIST_MENU_RESUME, _("Resume")); + menu.Append(THREADLIST_MENU_SUSPEND, _("Suspend")); + menu.AppendSeparator(); + menu.Append(THREADLIST_MENU_DUMP_STACK_TRACE, _("Write stack trace to log")); + menu.Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(DebugPPCThreadsWindow::OnThreadListPopupClick), nullptr, this); + PopupMenu(&menu); +} + +void DebugPPCThreadsWindow::Close() +{ + this->Destroy(); +} diff --git a/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.h b/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.h new file mode 100644 index 00000000..e967ad7c --- /dev/null +++ b/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.h @@ -0,0 +1,32 @@ +#pragma once + +#include + +class DebugPPCThreadsWindow: public wxFrame +{ +public: + DebugPPCThreadsWindow(wxFrame& parent); + ~DebugPPCThreadsWindow(); + + void OnCloseButton(wxCommandEvent& event); + void OnRefreshButton(wxCommandEvent& event); + void OnClose(wxCloseEvent& event); + void RefreshThreadList(); + void OnThreadListPopupClick(wxCommandEvent &evt); + void OnThreadListRightClick(wxMouseEvent& event); + + void DumpStackTrace(struct OSThread_t* thread); + + void Close(); + +private: + wxListCtrl* m_thread_list; + wxCheckBox* m_auto_refresh; + wxTimer* m_timer; + + void OnTimer(wxTimerEvent& event); + + wxDECLARE_EVENT_TABLE(); + + +}; \ No newline at end of file diff --git a/src/gui/windows/TextureRelationViewer/TextureRelationWindow.cpp b/src/gui/windows/TextureRelationViewer/TextureRelationWindow.cpp new file mode 100644 index 00000000..8d5c02a1 --- /dev/null +++ b/src/gui/windows/TextureRelationViewer/TextureRelationWindow.cpp @@ -0,0 +1,386 @@ +#include "gui/wxgui.h" +#include "TextureRelationWindow.h" +#include "Cafe/HW/Latte/Core/LatteTexture.h" + +enum +{ + // options + REFRESH_ID, + CLOSE_ID, + TEX_LIST_A_ID, + TEX_LIST_B_ID, + CHECKBOX_SHOW_ONLY_ACTIVE, + CHECKBOX_SHOW_VIEWS, +}; + +wxBEGIN_EVENT_TABLE(TextureRelationViewerWindow, wxFrame) +EVT_BUTTON(CLOSE_ID, TextureRelationViewerWindow::OnCloseButton) +EVT_BUTTON(REFRESH_ID, TextureRelationViewerWindow::OnRefreshButton) +EVT_CHECKBOX(CHECKBOX_SHOW_ONLY_ACTIVE, TextureRelationViewerWindow::OnCheckbox) +EVT_CHECKBOX(CHECKBOX_SHOW_VIEWS, TextureRelationViewerWindow::OnCheckbox) + +EVT_CLOSE(TextureRelationViewerWindow::OnClose) +wxEND_EVENT_TABLE() + +wxListCtrl* textureRelationListA; +bool isTextureViewerOpen = false; + +void openTextureViewer(wxFrame& parentFrame) +{ + if (isTextureViewerOpen) + return; + auto frame = new TextureRelationViewerWindow(parentFrame); + frame->Show(true); +} + +TextureRelationViewerWindow::TextureRelationViewerWindow(wxFrame& parent) + : wxFrame(&parent, wxID_ANY, _("Texture cache"), wxDefaultPosition, wxSize(1000, 480), wxCLOSE_BOX | wxCLIP_CHILDREN | wxCAPTION | wxRESIZE_BORDER) +{ + isTextureViewerOpen = true; + + this->showOnlyActive = false; + this->showTextureViews = true; + + wxPanel* mainPane = new wxPanel(this); + wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL); + textureRelationListA = new wxListCtrl(mainPane, TEX_LIST_A_ID, wxPoint(0, 0), wxSize(1008, 440), wxLC_REPORT); + + textureRelationListA->SetFont(wxFont(8, wxFONTFAMILY_MODERN, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, "Courier New"));//wxSystemSettings::GetFont(wxSYS_OEM_FIXED_FONT)); + + // add columns + wxListItem col0; + sint32 columnIndex = 0; + col0.SetId(columnIndex); columnIndex++; + col0.SetText(_("Type")); + col0.SetWidth(85); + textureRelationListA->InsertColumn(columnIndex-1, col0); + wxListItem col1; + col1.SetId(columnIndex); columnIndex++; + col1.SetText(_("PhysAddr")); + col1.SetWidth(80); + textureRelationListA->InsertColumn(columnIndex-1, col1); + wxListItem col2; + col2.SetId(columnIndex); columnIndex++; + col2.SetText(_("Dim")); + col2.SetWidth(80); + textureRelationListA->InsertColumn(columnIndex-1, col2); + wxListItem col3; + col3.SetId(columnIndex); columnIndex++; + col3.SetText(_("Resolution")); + col3.SetWidth(110); + textureRelationListA->InsertColumn(columnIndex-1, col3); + wxListItem col5; + col5.SetId(columnIndex); columnIndex++; + col5.SetText(_("Format")); + col5.SetWidth(70); + textureRelationListA->InsertColumn(columnIndex-1, col5); + wxListItem colPriority; + colPriority.SetId(columnIndex); columnIndex++; + colPriority.SetText(_("Pitch")); + colPriority.SetWidth(80); + textureRelationListA->InsertColumn(columnIndex-1, colPriority); + wxListItem col6; + col6.SetId(columnIndex); columnIndex++; + col6.SetText(_("Tilemode")); + col6.SetWidth(80); + textureRelationListA->InsertColumn(columnIndex-1, col6); + wxListItem col7; + col7.SetId(columnIndex); columnIndex++; + col7.SetText(_("SliceRange")); + col7.SetWidth(90); + textureRelationListA->InsertColumn(columnIndex-1, col7); + wxListItem col8; + col8.SetId(columnIndex); columnIndex++; + col8.SetText(_("MipRange")); + col8.SetWidth(90); + textureRelationListA->InsertColumn(columnIndex-1, col8); + wxListItem colAge; + colAge.SetId(columnIndex); columnIndex++; + colAge.SetText(_("Last access")); + colAge.SetWidth(90); + textureRelationListA->InsertColumn(columnIndex - 1, colAge); + wxListItem colOverwriteRes; + colOverwriteRes.SetId(columnIndex); columnIndex++; + colOverwriteRes.SetText(_("OverwriteRes")); + colOverwriteRes.SetWidth(110); + textureRelationListA->InsertColumn(columnIndex - 1, colOverwriteRes); + + wxBoxSizer* sizerBottom = new wxBoxSizer(wxHORIZONTAL); + + sizer->Add(textureRelationListA, 1, wxEXPAND | wxBOTTOM, 0); + wxButton* button = new wxButton(mainPane, REFRESH_ID, _("Refresh"), wxPoint(0, 0), wxSize(80, 26)); + sizerBottom->Add(button, 0, wxBOTTOM | wxTOP | wxLEFT, 10); + + wxCheckBox* checkboxShowOnlyActive = new wxCheckBox(mainPane, CHECKBOX_SHOW_ONLY_ACTIVE, _("Show only active"), wxPoint(0, 0), wxSize(110, 26)); + sizerBottom->Add(checkboxShowOnlyActive, 0, wxBOTTOM | wxTOP | wxLEFT, 10); + + wxCheckBox* checkboxShowViews = new wxCheckBox(mainPane, CHECKBOX_SHOW_VIEWS, _("Show views"), wxPoint(0, 0), wxSize(90, 26)); + sizerBottom->Add(checkboxShowViews, 0, wxBOTTOM | wxTOP | wxLEFT, 10); + checkboxShowViews->SetValue(true); + + textureRelationListA->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(TextureRelationViewerWindow::OnTextureListRightClick), NULL, this); + + sizer->Add( + sizerBottom, + 0, // vertically unstretchable + wxALIGN_LEFT); + mainPane->SetSizer(sizer); + + RefreshTextureList(); + + wxFrame::SetBackgroundColour(*wxWHITE); +} + +TextureRelationViewerWindow::~TextureRelationViewerWindow() +{ + isTextureViewerOpen = false; +} + +void TextureRelationViewerWindow::OnCloseButton(wxCommandEvent& event) +{ + Close(); +} + +void TextureRelationViewerWindow::OnRefreshButton(wxCommandEvent& event) +{ + RefreshTextureList(); +} + +void TextureRelationViewerWindow::OnCheckbox(wxCommandEvent& event) +{ + if (event.GetId() == CHECKBOX_SHOW_ONLY_ACTIVE) + { + showOnlyActive = event.IsChecked(); + RefreshTextureList(); + } + else if (event.GetId() == CHECKBOX_SHOW_VIEWS) + { + showTextureViews = event.IsChecked(); + RefreshTextureList(); + } +} + +void TextureRelationViewerWindow::OnClose(wxCloseEvent& event) +{ + Close(); +} + +void TextureRelationViewerWindow::_setTextureRelationListItemTexture(wxListCtrl* uiList, sint32 rowIndex, struct LatteTextureInformation* texInfo) +{ + char tempStr[512]; + // count number of alternative views for base view + sint32 alternativeViewCount = texInfo->alternativeViewCount; + if (texInfo->isUpdatedOnGPU) + sprintf(tempStr, "TEXTURE*"); + else + sprintf(tempStr, "TEXTURE"); + if (alternativeViewCount > 0) + { + sprintf(tempStr + strlen(tempStr), "(%d)", alternativeViewCount + 1); + } + uint32 bgColor = 0xFFEEEEEE; + wxListItem item; + item.SetId(rowIndex); + item.SetText(tempStr); + item.SetBackgroundColour(wxColour(bgColor)); + uiList->InsertItem(item); + + sint32 columnIndex = 1; + // phys address + sprintf(tempStr, "%08X", texInfo->physAddress); + uiList->SetItem(rowIndex, columnIndex, tempStr); + columnIndex++; + // dim + if (texInfo->dim == Latte::E_DIM::DIM_2D) + strcpy(tempStr, "2D"); + else if (texInfo->dim == Latte::E_DIM::DIM_2D_ARRAY) + strcpy(tempStr, "2D_ARRAY"); + else if (texInfo->dim == Latte::E_DIM::DIM_3D) + strcpy(tempStr, "3D"); + else if (texInfo->dim == Latte::E_DIM::DIM_CUBEMAP) + strcpy(tempStr, "CUBEMAP"); + else if (texInfo->dim == Latte::E_DIM::DIM_1D) + strcpy(tempStr, "1D"); + else if (texInfo->dim == Latte::E_DIM::DIM_2D_MSAA) + strcpy(tempStr, "2D_MSAA"); + else if (texInfo->dim == Latte::E_DIM::DIM_2D_ARRAY_MSAA) + strcpy(tempStr, "2D_MS_ARRAY"); + else + strcpy(tempStr, "UKN"); + uiList->SetItem(rowIndex, columnIndex, tempStr); + columnIndex++; + // resolution + if (texInfo->depth == 1) + sprintf(tempStr, "%dx%d", texInfo->width, texInfo->height); + else + sprintf(tempStr, "%dx%dx%d", texInfo->width, texInfo->height, texInfo->depth); + uiList->SetItem(rowIndex, columnIndex, tempStr); + columnIndex++; + // format + if(texInfo->isDepth) + sprintf(tempStr, "%04x(d)", (uint32)texInfo->format); + else + sprintf(tempStr, "%04x", (uint32)texInfo->format); + uiList->SetItem(rowIndex, columnIndex, tempStr); + columnIndex++; + // pitch + sprintf(tempStr, "%d", texInfo->pitch); + uiList->SetItem(rowIndex, columnIndex, tempStr); + columnIndex++; + // tilemode + sprintf(tempStr, "%d", texInfo->tileMode); + uiList->SetItem(rowIndex, columnIndex, tempStr); + columnIndex++; + // sliceRange + sprintf(tempStr, ""); + uiList->SetItem(rowIndex, columnIndex, tempStr); + columnIndex++; + // mipRange + if(texInfo->mipLevels == 1) + sprintf(tempStr, "1 mip"); + else + sprintf(tempStr, "%d mips", texInfo->mipLevels); + uiList->SetItem(rowIndex, columnIndex, tempStr); + columnIndex++; + // last access + sprintf(tempStr, "%ds", (GetTickCount() - texInfo->lastAccessTick + 499) / 1000); + uiList->SetItem(rowIndex, columnIndex, tempStr); + columnIndex++; + // overwrite resolution + strcpy(tempStr, ""); + if (texInfo->overwriteInfo.hasResolutionOverwrite) + { + if(texInfo->overwriteInfo.depth != 1 || texInfo->depth != 1) + sprintf(tempStr, "%dx%dx%d", texInfo->overwriteInfo.width, texInfo->overwriteInfo.height, texInfo->overwriteInfo.depth); + else + sprintf(tempStr, "%dx%d", texInfo->overwriteInfo.width, texInfo->overwriteInfo.height); + } + uiList->SetItem(rowIndex, columnIndex, tempStr); + columnIndex++; +} + +void TextureRelationViewerWindow::_setTextureRelationListItemView(wxListCtrl* uiList, sint32 rowIndex, struct LatteTextureInformation* texInfo, struct LatteTextureViewInformation* viewInfo) +{ + char tempStr[512]; + // count number of alternative views + sint32 alternativeViewCount = 0; // todo + // set type string + if(alternativeViewCount == 0) + sprintf(tempStr, "> VIEW"); + else + sprintf(tempStr, "> VIEW(%d)", alternativeViewCount+1); + // find and handle highlight entry + uint32 bgColor = 0xFFDDDDDD; + wxListItem item; + item.SetId(rowIndex); + item.SetText(tempStr); + item.SetBackgroundColour(wxColour(bgColor)); + uiList->InsertItem(item); + //uiList->SetItemPtrData(item, (wxUIntPtr)viewInfo); + sint32 columnIndex = 1; + // phys address + sprintf(tempStr, ""); + uiList->SetItem(rowIndex, columnIndex, tempStr); + columnIndex++; + // dim + if (viewInfo->dim == Latte::E_DIM::DIM_2D) + strcpy(tempStr, "2D"); + else if (viewInfo->dim == Latte::E_DIM::DIM_2D_ARRAY) + strcpy(tempStr, "2D_ARRAY"); + else if (viewInfo->dim == Latte::E_DIM::DIM_3D) + strcpy(tempStr, "3D"); + else if (viewInfo->dim == Latte::E_DIM::DIM_CUBEMAP) + strcpy(tempStr, "CUBEMAP"); + else if (viewInfo->dim == Latte::E_DIM::DIM_1D) + strcpy(tempStr, "1D"); + else if (viewInfo->dim == Latte::E_DIM::DIM_2D_MSAA) + strcpy(tempStr, "2D_MSAA"); + else if (viewInfo->dim == Latte::E_DIM::DIM_2D_ARRAY_MSAA) + strcpy(tempStr, "2D_ARRAY_MSAA"); + else + strcpy(tempStr, "UKN"); + uiList->SetItem(rowIndex, columnIndex, tempStr); + columnIndex++; + // resolution + tempStr[0] = '\0'; + uiList->SetItem(rowIndex, columnIndex, tempStr); + columnIndex++; + // format + sprintf(tempStr, "%04x", (uint32)viewInfo->format); + uiList->SetItem(rowIndex, columnIndex, tempStr); + columnIndex++; + // pitch + tempStr[0] = '\0'; + uiList->SetItem(rowIndex, columnIndex, tempStr); + columnIndex++; + // tilemode + tempStr[0] = '\0'; + uiList->SetItem(rowIndex, columnIndex, tempStr); + columnIndex++; + // sliceRange + sprintf(tempStr, "%d-%d", viewInfo->firstSlice, viewInfo->firstSlice+ viewInfo->numSlice-1); + uiList->SetItem(rowIndex, columnIndex, tempStr); + columnIndex++; + // mipRange + sprintf(tempStr, "%d-%d", viewInfo->firstMip, viewInfo->firstMip + viewInfo->numMip - 1); + uiList->SetItem(rowIndex, columnIndex, tempStr); + columnIndex++; + // last access + tempStr[0] = '\0'; + uiList->SetItem(rowIndex, columnIndex, tempStr); + columnIndex++; +} + +void TextureRelationViewerWindow::RefreshTextureList() +{ + int scrollPos = textureRelationListA->GetScrollPos(wxVERTICAL); + textureRelationListA->DeleteAllItems(); + + std::vector texCache = LatteTexture_QueryCacheInfo(); + + // sort by physAddr in ascending order + for (sint32 i1 = 0; i1 < texCache.size(); i1++) + { + for (sint32 i2 = i1+1; i2 < texCache.size(); i2++) + { + if (texCache[i1].physAddress > texCache[i2].physAddress) + { + std::swap(texCache[i1], texCache[i2]); + } + } + } + + textureRelationListA->Freeze(); + + sint32 rowIndex = 0; + uint32 currentTick = GetTickCount(); + for (auto& tex : texCache) + { + uint32 timeSinceLastAccess = currentTick - tex.lastAccessTick; + if (showOnlyActive && timeSinceLastAccess > 3000) + continue; // hide textures which haven't been updated in more than 3 seconds + + _setTextureRelationListItemTexture(textureRelationListA, rowIndex, &tex); + rowIndex++; + if (showTextureViews) + { + for (auto& view : tex.views) + { + _setTextureRelationListItemView(textureRelationListA, rowIndex, &tex, &view); + rowIndex++; + } + } + } + textureRelationListA->Thaw(); + textureRelationListA->EnsureVisible(scrollPos + textureRelationListA->GetCountPerPage() - 1); +} + +void TextureRelationViewerWindow::OnTextureListRightClick(wxMouseEvent& event) +{ + +} + +void TextureRelationViewerWindow::Close() +{ + this->Destroy(); +} \ No newline at end of file diff --git a/src/gui/windows/TextureRelationViewer/TextureRelationWindow.h b/src/gui/windows/TextureRelationViewer/TextureRelationWindow.h new file mode 100644 index 00000000..aacab7b4 --- /dev/null +++ b/src/gui/windows/TextureRelationViewer/TextureRelationWindow.h @@ -0,0 +1,31 @@ +#pragma once + +#include + +class TextureRelationViewerWindow : public wxFrame +{ +public: + TextureRelationViewerWindow(wxFrame& parent); + ~TextureRelationViewerWindow(); + + void OnCloseButton(wxCommandEvent& event); + void OnRefreshButton(wxCommandEvent& event); + void OnCheckbox(wxCommandEvent& event); + void OnClose(wxCloseEvent& event); + void RefreshTextureList(); + void OnTextureListRightClick(wxMouseEvent& event); + + void Close(); + +private: + + wxDECLARE_EVENT_TABLE(); + + void _setTextureRelationListItemTexture(wxListCtrl* uiList, sint32 rowIndex, struct LatteTextureInformation* texInfo); + void _setTextureRelationListItemView(wxListCtrl* uiList, sint32 rowIndex, struct LatteTextureInformation* texInfo, struct LatteTextureViewInformation* viewInfo); + + bool showOnlyActive; + bool showTextureViews; +}; + +void openTextureViewer(wxFrame& parentFrame); \ No newline at end of file diff --git a/src/gui/wxHelper.h b/src/gui/wxHelper.h new file mode 100644 index 00000000..753a5caf --- /dev/null +++ b/src/gui/wxHelper.h @@ -0,0 +1,26 @@ +#pragma once +#include + +namespace wxHelper +{ + // wxString to utf8 std::string + inline std::string MakeUTF8(const wxString& str) + { + auto tmpUtf8 = str.ToUTF8(); + return std::string(tmpUtf8.data(), tmpUtf8.length()); + } + + inline fs::path MakeFSPath(const wxString& str) + { + auto tmpUtf8 = str.ToUTF8(); + auto sv = std::basic_string_view((const char8_t*)tmpUtf8.data(), tmpUtf8.length()); + return fs::path(sv); + } + + inline wxString FromUtf8(std::string_view str) + { + return wxString::FromUTF8(str.data(), str.size()); + } + + +}; diff --git a/src/gui/wxcomponents/checked.xpm b/src/gui/wxcomponents/checked.xpm new file mode 100644 index 00000000..e198a8c4 --- /dev/null +++ b/src/gui/wxcomponents/checked.xpm @@ -0,0 +1,24 @@ +/* XPM */ +static const char * checked_xpm[] = { +"16 16 5 1", +" c None", +". c #808080", +"X c Black", +"o c #c0c0c0", +"w c White", +" ", +" ", +" ............ ", +" .XXXXXXXXXXo ", +" .Xwwwwwwwwwo ", +" .XwwwwwwwXwo ", +" .XwwwwwwXXwo ", +" .XwXwwwXXXwo ", +" .XwXXwXXXwwo ", +" .XwXXXXXwwwo ", +" .XwwXXXwwwwo ", +" .XwwwXwwwwwo ", +" .Xwwwwwwwwwo ", +" .ooooooooooo ", +" ", +" "}; diff --git a/src/gui/wxcomponents/checked2.xpm b/src/gui/wxcomponents/checked2.xpm new file mode 100644 index 00000000..60fce780 --- /dev/null +++ b/src/gui/wxcomponents/checked2.xpm @@ -0,0 +1,38 @@ +/* XPM */ +static const char * checked2_xpm[] = { +"14 15 20 1", +" c None", +". c #1C5180", +"+ c #E2E2DD", +"@ c #E3E3DF", +"# c #E5E5E1", +"$ c #E7E7E3", +"% c #EAEAE6", +"& c #ECECE9", +"* c #EFEFEC", +"= c #F1F1EF", +"- c #F3F3F1", +"; c #F5F5F3", +"> c #21A121", +", c #F7F7F6", +"' c #F9F9F8", +") c #FAFAF9", +"! c #FCFCFB", +"~ c #FDFDFD", +"{ c #FEFEFE", +"] c #FFFFFF", +"............. ", +".+++@#$%&*=-. ", +".++@#$%&*=-;. ", +".+@#$%&*=>;,. ", +".@#$%&*=>>,'. ", +".#$>&*=>>>'). ", +".$%>>=>>>')!. ", +".%&>>>>>')!~. ", +".&*=>>>')!~{. ", +".*=-;>')!~{]. ", +".=-;,')!~{]]. ", +".-;,')!~{]]]. ", +"............. ", +" ", +" "}; diff --git a/src/gui/wxcomponents/checked_d.xpm b/src/gui/wxcomponents/checked_d.xpm new file mode 100644 index 00000000..c187ffaa --- /dev/null +++ b/src/gui/wxcomponents/checked_d.xpm @@ -0,0 +1,21 @@ +/* XPM */ +static const char * checked_d_xpm[] = { +"14 15 3 1", +" c None", +". c #CAC8BB", +"+ c #FFFFFF", +"............. ", +".+++++++++++. ", +".+++++++++++. ", +".++++++++.++. ", +".+++++++..++. ", +".++.+++...++. ", +".++..+...+++. ", +".++.....++++. ", +".+++...+++++. ", +".++++.++++++. ", +".+++++++++++. ", +".+++++++++++. ", +"............. ", +" ", +" "}; diff --git a/src/gui/wxcomponents/checked_dis.xpm b/src/gui/wxcomponents/checked_dis.xpm new file mode 100644 index 00000000..2d9e6e5a --- /dev/null +++ b/src/gui/wxcomponents/checked_dis.xpm @@ -0,0 +1,23 @@ +/* XPM */ +static const char * checked_dis_xpm[] = { +"16 16 4 1", +" c None", +". c #808080", +"X c Black", +"o c #c0c0c0", +" ", +" ", +" ............ ", +" .XXXXXXXXXXo ", +" .Xoooooooooo ", +" .Xooooooo.oo ", +" .Xoooooo..oo ", +" .Xo.ooo...oo ", +" .Xo..o...ooo ", +" .Xo.....oooo ", +" .Xoo...ooooo ", +" .Xooo.oooooo ", +" .Xoooooooooo ", +" .ooooooooooo ", +" ", +" "}; diff --git a/src/gui/wxcomponents/checked_ld.xpm b/src/gui/wxcomponents/checked_ld.xpm new file mode 100644 index 00000000..8c860f9a --- /dev/null +++ b/src/gui/wxcomponents/checked_ld.xpm @@ -0,0 +1,44 @@ +/* XPM */ +static const char * checked_ld_xpm[] = { +"14 15 26 1", +" c None", +". c #1C5180", +"+ c #B0B0A7", +"@ c #B3B3AA", +"# c #B7B7AD", +"$ c #BCBBB1", +"% c #C1C1B6", +"& c #C7C6BB", +"* c #CDCCC0", +"= c #D2D1C5", +"- c #D7D5C8", +"; c #DBDACC", +"> c #1C861A", +", c #DFDDCF", +"' c #1C8A1A", +") c #E3E1D3", +"! c #197A18", +"~ c #1D8C1B", +"{ c #E6E5D6", +"] c #1A7D18", +"^ c #1B8119", +"/ c #EAE8D9", +"( c #1B8419", +"_ c #EDEBDB", +": c #EFEDDD", +"< c #F1EFDF", +"............. ", +".+++@#$%&*=-. ", +".++@#$%&*=-;. ", +".+@#$%&*=>;,. ", +".@#$%&*=>',). ", +".#$!&*=>'~){. ", +".$%]^=>'~){/. ", +".%&^(>'~){/_. ", +".&*=>'~){/_:. ", +".*=-;~){/_:<. ", +".=-;,){/_:<<. ", +".-;,){/_:<<<. ", +"............. ", +" ", +" "}; diff --git a/src/gui/wxcomponents/checked_mo.xpm b/src/gui/wxcomponents/checked_mo.xpm new file mode 100644 index 00000000..31ef1330 --- /dev/null +++ b/src/gui/wxcomponents/checked_mo.xpm @@ -0,0 +1,70 @@ +/* XPM */ +static const char * checked_mo_xpm[] = { +"14 15 52 1", +" c None", +". c #1C5180", +"+ c #FFF0CF", +"@ c #FFEDC6", +"# c #FFE9BA", +"$ c #FEE4AC", +"% c #FEDF9C", +"& c #FDD98C", +"* c #FDD889", +"= c #FDDB95", +"- c #FDDC97", +"; c #FCD687", +"> c #FBCA6A", +", c #FDE3AC", +"' c #FDE6B6", +") c #FDDEA2", +"! c #FBD07A", +"~ c #FFEABD", +"{ c #FEE9BC", +"] c #F7F7F5", +"^ c #F6F6F4", +"/ c #F4F4F3", +"( c #F8F8F7", +"_ c #FAFAF9", +": c #21A121", +"< c #FDE3AE", +"[ c #FBD280", +"} c #FEECC4", +"| c #FDE1AB", +"1 c #FBD07B", +"2 c #FEE7B5", +"3 c #FEECC7", +"4 c #FDE0A8", +"5 c #FBCE77", +"6 c #FEE3A9", +"7 c #FEEBC3", +"8 c #FBD892", +"9 c #FAC867", +"0 c #FEE0A3", +"a c #FEE9BF", +"b c #F9BC46", +"c c #F9B73A", +"d c #FCD27A", +"e c #F8B636", +"f c #FBCC6B", +"g c #FCDB99", +"h c #F8B433", +"i c #FBC863", +"j c #FAC55A", +"k c #FAC55B", +"l c #FBCC70", +"m c #F8B330", +"............. ", +".+@#$%&*=-;>. ", +".@#$%&*=,')!. ", +".~{]^/^(_:<[. ", +".{}_(^(_::|1. ", +".23:_(_:::45. ", +".67::_:::_89. ", +".0a:::::_(9b. ", +".=,_:::_(^bc. ", +".d;(_:_(^/ce. ", +".f>!g489bceh. ", +".ijkl59bcehm. ", +"............. ", +" ", +" "}; diff --git a/src/gui/wxcomponents/checkedlistctrl.cpp b/src/gui/wxcomponents/checkedlistctrl.cpp new file mode 100644 index 00000000..9728f1c6 --- /dev/null +++ b/src/gui/wxcomponents/checkedlistctrl.cpp @@ -0,0 +1,402 @@ +///////////////////////////////////////////////////////////////////////////// +// Name: checkedlistctrl.cpp +// Purpose: wxCheckedListCtrl +// Author: Unknown ? (found at http://wiki.wxwidgets.org/wiki.pl?WxListCtrl) +// Modified by: Francesco Montorsi +// Created: 2005/06/29 +// RCS-ID: $Id: checkedlistctrl.cpp 1309 2010-05-01 09:49:59Z frm $ +// Copyright: (c) 2005 Francesco Montorsi +// Licence: wxWidgets licence +///////////////////////////////////////////////////////////////////////////// + + +// For compilers that support precompilation, includes "wx.h". +#include "wx/wxprec.h" + +#ifdef __BORLANDC__ +#pragma hdrstop +#endif + +// includes +#include "gui/wxcomponents/checkedlistctrl.h" +#include + +#include + +#if wxUSE_CHECKEDLISTCTRL + +// resources + +#include "gui/wxcomponents/checked.xpm" +#include "gui/wxcomponents/unchecked.xpm" +#include "gui/wxcomponents/checked_dis.xpm" +#include "gui/wxcomponents/unchecked_dis.xpm" + +IMPLEMENT_CLASS(wxCheckedListCtrl, wxListCtrl) +BEGIN_EVENT_TABLE(wxCheckedListCtrl, wxListCtrl) + EVT_LEFT_DOWN(wxCheckedListCtrl::OnMouseEvent) +END_EVENT_TABLE() + +DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_ITEM_CHECKED); +DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_ITEM_UNCHECKED); + + + +// ------------------ +// wxCHECKEDLISTCTRL +// ------------------ + +bool wxCheckedListCtrl::Create(wxWindow* parent, wxWindowID id, const wxPoint& pt, + const wxSize& sz, long style, const wxValidator& validator, const wxString& name) +{ + if (!wxListCtrl::Create(parent, id, pt, sz, style, validator, name)) + return FALSE; + + SetImageList(&m_imageList, wxIMAGE_LIST_SMALL); + + // the add order must respect the wxCLC_XXX_IMGIDX defines in the headers ! + m_imageList.Add(wxIcon(unchecked_xpm)); + m_imageList.Add(wxIcon(checked_xpm)); + m_imageList.Add(wxIcon(unchecked_dis_xpm)); + m_imageList.Add(wxIcon(checked_dis_xpm)); + + return TRUE; +} + +/* static */ +int wxCheckedListCtrl::GetItemImageFromAdditionalState(int addstate) +{ + bool checked = (addstate & wxLIST_STATE_CHECKED) != 0; + bool enabled = (addstate & wxLIST_STATE_ENABLED) != 0; + + if (checked && enabled) + return wxCLC_CHECKED_IMGIDX; + else if (checked && !enabled) + return wxCLC_DISABLED_CHECKED_IMGIDX; + else if (!checked && enabled) + return wxCLC_UNCHECKED_IMGIDX; + + wxASSERT(!checked && !enabled); // this is the last possibility + return wxCLC_DISABLED_UNCHECKED_IMGIDX; +} + +wxColour wxCheckedListCtrl::GetBgColourFromAdditionalState(int additionalstate) +{ + if ((additionalstate & wxLIST_STATE_ENABLED) && + this->IsEnabled()) + return *wxWHITE; +#ifdef __WXMSW__ + return wxColour(212, 208, 200); +#else + return wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT); +#endif +} + +/* static */ +int wxCheckedListCtrl::GetAndRemoveAdditionalState(long *state, int statemask) +{ + int additionalstate = 0; + if (!state) return -1; + + // extract the bits we are interested in + bool checked = (*state & wxLIST_STATE_CHECKED) != 0; + bool enabled = (*state & wxLIST_STATE_ENABLED) != 0; + + // and set them in a different variable if they are included in the statemask + if (checked && (statemask & wxLIST_STATE_CHECKED)) additionalstate |= wxLIST_STATE_CHECKED; + if (enabled && (statemask & wxLIST_STATE_ENABLED)) additionalstate |= wxLIST_STATE_ENABLED; + + // remove them from the original state var... + *state &= ~wxLIST_STATE_CHECKED; + *state &= ~wxLIST_STATE_ENABLED; + return additionalstate; +} + +bool wxCheckedListCtrl::GetItem(wxListItem& info) const +{ + // wx internal wxListCtrl::GetItem remove from the state mask the + // wxLIST_STATE_CHECKED & wxLIST_STATE_ENABLED bits since they + // are not part of wx standard flags... so we need to check those + // flags against the original wxListItem's statemask... + wxListItem original(info); + +#ifdef __WXDEBUG__ + // we always want to retrieve also the image state for checking purposes... + info.m_mask |= wxLIST_MASK_IMAGE; +#endif + + if (!wxListCtrl::GetItem(info)) + return FALSE; + + // these are our additional supported states: read them from m_stateList + bool checked = (m_stateList[info.m_itemId] & wxLIST_STATE_CHECKED) != 0; + bool enabled = (m_stateList[info.m_itemId] & wxLIST_STATE_ENABLED) != 0; + + // now intercept state requests about enable or check mode + if ((original.m_mask & wxLIST_MASK_STATE) && + (original.m_stateMask & wxLIST_STATE_CHECKED)) { + info.m_state |= (m_stateList[info.m_itemId] & wxLIST_STATE_CHECKED); + info.m_stateMask |= wxLIST_STATE_CHECKED; + info.m_mask |= wxLIST_MASK_STATE; // contains valid info ! + } + if ((original.m_mask & wxLIST_MASK_STATE) && + (original.m_stateMask & wxLIST_STATE_ENABLED)) { + info.m_state |= (m_stateList[info.m_itemId] & wxLIST_STATE_ENABLED); + info.m_stateMask |= wxLIST_STATE_ENABLED; + info.m_mask |= wxLIST_MASK_STATE; // contains valid info ! + } + + // check that state & image are synch +#ifdef __WXDEBUG__ + + wxASSERT_MSG((int)m_stateList.GetCount() == (int)GetItemCount(), + wxS("Something wrong ! See InsertItem()")); + + // read info by image index + bool imagecheck = (info.m_image == wxCLC_CHECKED_IMGIDX) || + (info.m_image == wxCLC_DISABLED_CHECKED_IMGIDX); + bool imageenabled = (info.m_image == wxCLC_CHECKED_IMGIDX) || + (info.m_image == wxCLC_UNCHECKED_IMGIDX); + wxASSERT_MSG((checked && imagecheck) || (!checked && !imagecheck), + wxS("This is item has checked state but it's shown as unchecked (or viceversa)")); + wxASSERT_MSG((enabled && imageenabled) || (!enabled && !imageenabled), + wxS("This is item has enabled state but it's shown as disabled (or viceversa)")); +#endif + + return TRUE; +} + +bool wxCheckedListCtrl::SetItem(wxListItem& info) +{ + // remove the checked & enabled states from the state flag: + // we'll store them in our separate array + int additionalstate = GetAndRemoveAdditionalState(&info.m_state, info.m_stateMask); + + // set image index + // we will ignore the info.m_image field since we need + // to overwrite it... + if (info.m_mask & wxLIST_MASK_STATE) { + + // if some state is not included in the state mask, then get the state info + // from our internal state array + if (!(info.m_stateMask & wxLIST_STATE_ENABLED)) + additionalstate |= (m_stateList[info.m_itemId] & wxLIST_STATE_ENABLED); + if (!(info.m_stateMask & wxLIST_STATE_CHECKED)) + additionalstate |= (m_stateList[info.m_itemId] & wxLIST_STATE_CHECKED); + + // state is valid: use it to determine the image to set... + info.m_mask |= wxLIST_MASK_IMAGE; + info.m_image = GetItemImageFromAdditionalState(additionalstate); + + // since when changing the background color, also the foreground color + // and the font of the item are changed, we try to respect the user + // choices of such attributes + info.SetTextColour(this->GetItemTextColour(info.GetId())); +#if wxCHECK_VERSION(2, 6, 2) + // before wx 2.6.2 the wxListCtrl::SetItemFont function is missing + info.SetFont(this->GetItemFont(info.GetId())); +#endif + + // change the background color to respect the enabled/disabled status... + info.SetBackgroundColour(GetBgColourFromAdditionalState(additionalstate)); + + m_stateList[info.m_itemId] = additionalstate; + + } else { + + // state is invalid; don't change image + info.m_mask &= ~wxLIST_MASK_IMAGE; + } + + // save the changes + return wxListCtrl::SetItem(info); +} + +long wxCheckedListCtrl::InsertItem(wxListItem &info) +{ + int additionalstate = GetAndRemoveAdditionalState(&info.m_state, info.m_stateMask); + if (!(info.m_mask & wxLIST_MASK_STATE) || + !(info.m_stateMask & wxLIST_STATE_ENABLED)) { + + // if not specified, the default additional state is ENABLED + additionalstate = wxLIST_STATE_ENABLED; + } + + // we always want to insert items with images... + info.m_mask |= wxLIST_MASK_IMAGE; + info.m_image = GetItemImageFromAdditionalState(additionalstate); + info.SetBackgroundColour(GetBgColourFromAdditionalState(additionalstate)); + + int itemcount = GetItemCount(); + wxASSERT_MSG(info.m_itemId <= itemcount, wxS("Invalid index !")); + wxASSERT_MSG((int)m_stateList.GetCount() == (int)GetItemCount(), + wxS("Something wrong !")); + if (info.m_itemId == itemcount) { + + // we are adding a new item at the end of the list + m_stateList.Add(additionalstate); + + } else { + + // we must shift all following items + cemu_assert_suspicious(); + //for (int i=itemcount; i > info.m_itemId; i--) + // m_stateList[i] = m_stateList[i-1]; + m_stateList[info.m_itemId] = additionalstate; + } + + return wxListCtrl::InsertItem(info); +} + +bool wxCheckedListCtrl::SetItemState(long item, long state, long stateMask) +{ + wxListItem li; + li.SetId(item); + li.SetMask(wxLIST_MASK_STATE); + li.SetState(state); + li.SetStateMask(stateMask); + + // so we are sure to use wxCheckedListCtrl::SetItem + // (and not wxListCtrl::SetItem) + return SetItem(li); +} + +int wxCheckedListCtrl::GetItemState(long item, long stateMask) const +{ + wxListItem li; + li.SetId(item); + li.SetMask(wxLIST_MASK_STATE); + li.SetStateMask(stateMask); + + // so we are sure to use wxCheckedListCtrl::GetItem + // (and not wxListCtrl::GetItem) + if (!GetItem(li)) + return -1; + return li.GetState(); +} + +long wxCheckedListCtrl::SetItem(long index, int col, const wxString& label, int WXUNUSED(imageId)) +{ + wxListItem li; + li.SetId(index); + li.SetColumn(col); + li.SetText(label); + li.SetMask(wxLIST_MASK_TEXT); + + // so we are sure to use wxCheckedListCtrl::SetItem + // (and not wxListCtrl::SetItem) + return SetItem(li); +} + +long wxCheckedListCtrl::InsertItem( long index, const wxString& label, int WXUNUSED(imageIndex) ) +{ + wxListItem info; + info.m_text = label; + info.m_mask = wxLIST_MASK_TEXT; + info.m_itemId = index; + return InsertItem(info); +} + +void wxCheckedListCtrl::Check(long item, bool checked) +{ + // NB: the "statemask" is not the "mask" of a list item; + // in the "mask" you use the wxLIST_MASK_XXXX defines; + // in the "statemask" you use the wxLIST_STATE_XXX defines + // to set a specific bit of the wxListInfo::m_state var + if (checked) + // the 2nd parameter says: activate the STATE bit relative to CHECK feature + // the 3rd parameter says: set only *that* bit + SetItemState(item, wxLIST_STATE_CHECKED, wxLIST_STATE_CHECKED); + else + SetItemState(item, 0, wxLIST_STATE_CHECKED); +} + +void wxCheckedListCtrl::Enable(long item, bool enable) +{ + if (enable) + // the 2nd parameter says: activate the STATE bit relative to ENABLE feature + // the 3rd parameter says: set only *that* bit + SetItemState(item, wxLIST_STATE_ENABLED, wxLIST_STATE_ENABLED); + else + SetItemState(item, 0, wxLIST_STATE_ENABLED); +} + +void wxCheckedListCtrl::EnableAll(bool enable) +{ + for (int i=0; i < GetItemCount(); i++) + Enable(i, enable); +} + +void wxCheckedListCtrl::CheckAll(bool check) +{ + for (int i=0; i < GetItemCount(); i++) + Check(i, check); +} + +bool wxCheckedListCtrl::DeleteItem(long item) +{ + // shift our additional state array + //for (int i=item,max=GetItemCount(); i < max-1; i++) + // m_stateList[i] = m_stateList[i+1]; + m_stateList.RemoveAt(item, 1); + + return wxListCtrl::DeleteItem(item); +} + +int wxCheckedListCtrl::GetCheckedItemCount() const +{ + int res = 0; + for (int i=0; i +#include + +#if wxUSE_CHECKEDLISTCTRL + +// image indexes (used internally by wxCheckedListCtrl) +#define wxCLC_UNCHECKED_IMGIDX 0 // unchecked & enabled +#define wxCLC_CHECKED_IMGIDX 1 // checked & enabled +#define wxCLC_DISABLED_UNCHECKED_IMGIDX 2 // unchecked & disabled +#define wxCLC_DISABLED_CHECKED_IMGIDX 3 // checked & disabled + +// additional state flags (wx's defines should end at 0x0100; see listbase.h) +#define wxLIST_STATE_CHECKED 0x010000 +#define wxLIST_STATE_ENABLED 0x100000 + +// additional wxCheckedListCtrl style flags +// (wx's defines should at 0x8000; see listbase.h) +#define wxCLC_CHECK_WHEN_SELECTING 0x10000 + + +// ------------------------- +// wxCheckedListCtrl events +// ------------------------- + +//DECLARE_EXPORTED_EVENT_TYPE(WXDLLIMPEXP_WEBUPDATE, wxEVT_COMMAND_LIST_ITEM_CHECKED, -1); +//DECLARE_EXPORTED_EVENT_TYPE(WXDLLIMPEXP_WEBUPDATE, wxEVT_COMMAND_LIST_ITEM_UNCHECKED, -1); + +DECLARE_EXPORTED_EVENT_TYPE(WXEXPORT, wxEVT_COMMAND_LIST_ITEM_CHECKED, -1); +DECLARE_EXPORTED_EVENT_TYPE(WXEXPORT, wxEVT_COMMAND_LIST_ITEM_UNCHECKED, -1); + + +//DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_ITEM_CHECKED); +//DEFINE_EVENT_TYPE(wxEVT_COMMAND_LIST_ITEM_UNCHECKED); + +#ifndef EVT_LIST_ITEM_CHECKED +#define EVT_LIST_ITEM_CHECKED(id, fn) \ + DECLARE_EVENT_TABLE_ENTRY( \ + wxEVT_COMMAND_LIST_ITEM_CHECKED, id, -1, \ + (wxObjectEventFunction)(wxEventFunction)(wxListEventFunction)&fn, \ + (wxObject *) NULL \ + ), +#endif + +#ifndef EVT_LIST_ITEM_UNCHECKED +#define EVT_LIST_ITEM_UNCHECKED(id, fn) \ + DECLARE_EVENT_TABLE_ENTRY( \ + wxEVT_COMMAND_LIST_ITEM_UNCHECKED, id, -1, \ + (wxObjectEventFunction)(wxEventFunction)(wxListEventFunction)&fn, \ + (wxObject *) NULL \ + ), +#endif + + +//! This is the class which performs all transactions with the server. +//! It uses the wxSocket facilities. +class wxCheckedListCtrl : public wxListCtrl +{ +protected: + + // we have to keep a different array to keep track of the additional + // states we support.... + wxArrayInt m_stateList; + + // our set of checkbox images... + wxImageList m_imageList; + +public: + wxCheckedListCtrl() + : wxListCtrl(), m_imageList(16, 16, TRUE) {} + + wxCheckedListCtrl(wxWindow *parent, wxWindowID id = -1, + const wxPoint& pt = wxDefaultPosition, + const wxSize& sz = wxDefaultSize, + long style = wxCLC_CHECK_WHEN_SELECTING, + const wxValidator& validator = wxDefaultValidator, + const wxString& name = wxListCtrlNameStr) + : wxListCtrl(), m_imageList(16, 16, TRUE) + { Create(parent, id, pt, sz, style, validator, name); } + + bool Create(wxWindow *parent, wxWindowID id = -1, + const wxPoint& pt = wxDefaultPosition, + const wxSize& sz = wxDefaultSize, + long style = wxCLC_CHECK_WHEN_SELECTING, + const wxValidator& validator = wxDefaultValidator, + const wxString& name = wxListCtrlNameStr); + + virtual ~wxCheckedListCtrl() {} + + +public: // utilities + + // core overloads (i.e. the most generic overloads) + bool GetItem(wxListItem& info) const; + bool SetItem(wxListItem& info); + long InsertItem(wxListItem& info); + bool DeleteItem(long item); + bool DeleteAllItems() + { m_stateList.Clear(); return wxListCtrl::DeleteAllItems(); } + + bool SortItems(wxListCtrlCompare, long) + { wxASSERT_MSG(0, wxT("Not implemented yet ! sorry... ")); return FALSE; } + + // shortcuts to the SetItemState function + void Check(long item, bool checked); + void Enable(long item, bool enable); + void CheckAll(bool checked = true); + void EnableAll(bool enable = true); + + // this needs to be redeclared otherwise it's hidden by our other Enable() function. + // However you should use #EnableAll instead of this function if you want to get + // good graphics (try to understand) + virtual bool Enable(bool enable = true) + { return wxListCtrl::Enable(enable); } + + // shortcuts to the GetItemState function + bool IsChecked(long item) const + { return GetItemState(item, wxLIST_STATE_CHECKED) != 0; } + bool IsEnabled(long item) const + { return GetItemState(item, wxLIST_STATE_ENABLED) != 0; } + + // this needs to be redeclared otherwise it's hidden by our other IsEnabled() function. + bool IsEnabled() const + { return wxWindow::IsEnabled(); } + + //! Returns the number of checked items in the control. + int GetCheckedItemCount() const; + + // we overload these so we are sure they will use our + // #GetItem and #SetItem functions... + bool SetItemState(long item, long state, long stateMask); + int GetItemState(long item, long stateMask) const; + long InsertItem( long index, const wxString& label, int imageIndex = -1); + long SetItem(long index, int col, const wxString& label, int imageId = -1); + + // the image associated with an element is already in used by wxCheckedListCtrl + // itself to show the checkbox and it cannot be handled by the user ! + bool SetItemImage(long, int) + { wxASSERT_MSG(0, wxT("This function cannot be used with wxCheckedListCtrl !")); return FALSE; } + +protected: // event handlers + + void OnMouseEvent(wxMouseEvent& event); + +protected: // internal utilities + + static int GetItemImageFromAdditionalState(int addstate); + static int GetAndRemoveAdditionalState(long *state, int statemask); + wxColour GetBgColourFromAdditionalState(int additionalstate); + +private: + DECLARE_CLASS(wxCheckedListCtrl) + DECLARE_EVENT_TABLE() +}; + + +#endif // wxUSE_CHECKEDLISTCTRL + +#endif // _WX_CHECKEDLISTCTRL_H_ + diff --git a/src/gui/wxcomponents/checktree.cpp b/src/gui/wxcomponents/checktree.cpp new file mode 100644 index 00000000..6f8ca557 --- /dev/null +++ b/src/gui/wxcomponents/checktree.cpp @@ -0,0 +1,554 @@ +#include "gui/wxcomponents/checktree.h" +#include "gui/wxcomponents/checked2.xpm" +#include "gui/wxcomponents/checked_d.xpm" +#include "gui/wxcomponents/checked_ld.xpm" +#include "gui/wxcomponents/checked_mo.xpm" +#include "gui/wxcomponents/unchecked2.xpm" +#include "gui/wxcomponents/unchecked_d.xpm" +#include "gui/wxcomponents/unchecked_ld.xpm" +#include "gui/wxcomponents/unchecked_mo.xpm" +#include +#include + +wxDEFINE_EVENT(wxEVT_CHECKTREE_FOCUS, wxTreeEvent); +wxDEFINE_EVENT(wxEVT_CHECKTREE_CHOICE, wxTreeEvent); + +//IMPLEMENT_DYNAMIC_CLASS(wxCheckTree, wxTreeCtrl) + +bool on_check_or_label(int flags) +{ + return flags & (wxTREE_HITTEST_ONITEMSTATEICON | wxTREE_HITTEST_ONITEMLABEL) ? true : false; +} + +bool on_check(int flags) +{ + return flags & (wxTREE_HITTEST_ONITEMSTATEICON) ? true : false; +} + +bool on_label(int flags) +{ + return flags & (wxTREE_HITTEST_ONITEMLABEL) ? true : false; +} + +void unhighlight(wxTreeCtrl* m_treeCtrl1, wxTreeItemId& id) +{ + if (!id.IsOk()) + return; + + const int state = m_treeCtrl1->GetItemState(id); + if (wxCheckTree::UNCHECKED <= state && state < wxCheckTree::UNCHECKED_DISABLED) + { + m_treeCtrl1->SetItemState(id, wxCheckTree::UNCHECKED); + } + else if (wxCheckTree::CHECKED <= state && state < wxCheckTree::CHECKED_DISABLED) + { + m_treeCtrl1->SetItemState(id, wxCheckTree::CHECKED); + } +} + + +void mohighlight(wxTreeCtrl* m_treeCtrl1, wxTreeItemId& id, bool toggle) +{ + if (!id.IsOk()) + return; + + const int i = m_treeCtrl1->GetItemState(id); + if (i < 0) + return; + + bool is_checked = false; + if (wxCheckTree::UNCHECKED <= i && i < wxCheckTree::UNCHECKED_DISABLED) + { + m_treeCtrl1->SetItemState(id, toggle ? wxCheckTree::CHECKED_MOUSE_OVER : wxCheckTree::UNCHECKED_MOUSE_OVER); + is_checked = true; + } + else if (wxCheckTree::CHECKED <= i && i < wxCheckTree::CHECKED_DISABLED) + { + m_treeCtrl1->SetItemState(id, toggle ? wxCheckTree::UNCHECKED_MOUSE_OVER : wxCheckTree::CHECKED_MOUSE_OVER); + is_checked = false; + } + + if (toggle) + { + wxTreeEvent event2(wxEVT_CHECKTREE_CHOICE, m_treeCtrl1, id); + event2.SetExtraLong(is_checked ? 1 : 0); + m_treeCtrl1->ProcessWindowEvent(event2); + } +} + +void ldhighlight(wxTreeCtrl* m_treeCtrl1, wxTreeItemId& id) +{ + if (!id.IsOk()) + return; + + const int i = m_treeCtrl1->GetItemState(id); + if (wxCheckTree::UNCHECKED <= i && i < wxCheckTree::UNCHECKED_DISABLED) + { + m_treeCtrl1->SetItemState(id, wxCheckTree::UNCHECKED_LEFT_DOWN); + } + else if (wxCheckTree::CHECKED <= i && i < wxCheckTree::CHECKED_DISABLED) + { + m_treeCtrl1->SetItemState(id, wxCheckTree::CHECKED_LEFT_DOWN); + } +} + +wxIMPLEMENT_CLASS(wxCheckTree, wxTreeCtrl); + +wxCheckTree::wxCheckTree() +{ + Init(); +} + +wxCheckTree::wxCheckTree(wxWindow* parent, const wxWindowID id, const wxPoint& pos, const wxSize& size, long style) + : wxTreeCtrl(parent, id, pos, size, style) +{ + Init(); +} + + +void wxCheckTree::Init() +{ + wxIcon icons[8] = + { + wxIcon(unchecked2_xpm), wxIcon(unchecked_mo_xpm), wxIcon(unchecked_ld_xpm), wxIcon(unchecked_d_xpm), wxIcon(checked2_xpm), wxIcon(checked_mo_xpm), wxIcon(checked_ld_xpm), wxIcon(checked_d_xpm) + }; + + // Make an state image list containing small icons + auto states = new wxImageList(icons[0].GetWidth(), icons[0].GetHeight(), true); + + for (const auto& icon : icons) + states->Add(icon); + + AssignStateImageList(states); + + Connect(wxEVT_TREE_SEL_CHANGING, wxTreeEventHandler( wxCheckTree::On_Tree_Sel_Changed ), nullptr, this); + + Connect(wxEVT_CHAR, wxKeyEventHandler( wxCheckTree::On_Char ), nullptr, this); + Connect(wxEVT_KEY_DOWN, wxKeyEventHandler( wxCheckTree::On_KeyDown ), nullptr, this); + Connect(wxEVT_KEY_UP, wxKeyEventHandler( wxCheckTree::On_KeyUp ), nullptr, this); + + Connect(wxEVT_ENTER_WINDOW, wxMouseEventHandler( wxCheckTree::On_Mouse_Enter_Tree ), nullptr, this); + Connect(wxEVT_LEAVE_WINDOW, wxMouseEventHandler( wxCheckTree::On_Mouse_Leave_Tree ), nullptr, this); + Connect(wxEVT_LEFT_DCLICK, wxMouseEventHandler( wxCheckTree::On_Left_DClick ), nullptr, this); + Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler( wxCheckTree::On_Left_Down ), nullptr, this); + Connect(wxEVT_LEFT_UP, wxMouseEventHandler( wxCheckTree::On_Left_Up ), nullptr, this); + Connect(wxEVT_MOTION, wxMouseEventHandler( wxCheckTree::On_Mouse_Motion ), nullptr, this); + Connect(wxEVT_MOUSEWHEEL, wxMouseEventHandler( wxCheckTree::On_Mouse_Wheel ), nullptr, this); + + Connect(wxEVT_SET_FOCUS, wxFocusEventHandler( wxCheckTree::On_Tree_Focus_Set ), nullptr, this); + Connect(wxEVT_KILL_FOCUS, wxFocusEventHandler( wxCheckTree::On_Tree_Focus_Lost ), nullptr, this); +} + +void wxCheckTree::Sort(const wxTreeItemId& node, bool recursive) +{ + if (recursive) + { + wxTreeItemIdValue cookie; + for(auto it = GetFirstChild(node, cookie); it.IsOk(); it = GetNextChild(it, cookie)) + { + Sort(it, true); + } + } + + if(GetChildrenCount(node, false) > 0) + this->SortChildren(node); +} + +int wxCheckTree::OnCompareItems(const wxTreeItemId& item1, const wxTreeItemId& item2) +{ + const bool check1 = GetChildrenCount(item1, false) == 0; + const bool check2 = GetChildrenCount(item2, false) == 0; + + if (!check1 && check2) + return -1; + + if (check1 && !check2) + return 1; + + return GetItemText(item1).Lower().compare(GetItemText(item2).Lower()); +} + + +void wxCheckTree::SetItemTextColour(const wxTreeItemId& item, const wxColour& col) +{ + const auto it = m_colors.find(item); + if (it == m_colors.end()) + m_colors.emplace(std::pair(item, col)); + else + m_colors[item] = col; + + wxTreeCtrl::SetItemTextColour(item, col); +} + +bool wxCheckTree::EnableCheckBox(const wxTreeItemId& item, bool enable) +{ + if (!item.IsOk()) + return false; + + const int state = GetItemState(item); + + if (state < 0 || state > CHECKED_DISABLED) + return false; + + if (enable) + { + if (state == UNCHECKED_DISABLED) + SetItemState(item, UNCHECKED); + else if (state == CHECKED_DISABLED) + SetItemState(item, CHECKED); + + const auto it = m_colors.find(item); + if (it != m_colors.end()) + { + SetItemTextColour(item, it->second); + m_colors.erase(it); + } + + return true; + } + if (state == UNCHECKED_DISABLED || state == CHECKED_DISABLED) + { + //don't disable a second time or we'll lose the + //text color information. + return true; + } + + if (state == UNCHECKED || state == UNCHECKED_MOUSE_OVER || state == UNCHECKED_LEFT_DOWN) + SetItemState(item, UNCHECKED_DISABLED); + else if (state == CHECKED || state == CHECKED_MOUSE_OVER || state == CHECKED_LEFT_DOWN) + SetItemState(item, CHECKED_DISABLED); + + const wxColor col = GetItemTextColour(item); + SetItemTextColour(item, wxColour(161, 161, 146)); + m_colors[item] = col; + return true; +} + +bool wxCheckTree::DisableCheckBox(const wxTreeItemId& item) +{ + return EnableCheckBox(item, false); +} + +void wxCheckTree::MakeCheckable(const wxTreeItemId& item, bool state) +{ + if (!item.IsOk()) + return; + + const int i = GetItemState(item); + if (i < 0 || i > CHECKED_DISABLED) + SetItemState(item, state ? CHECKED : UNCHECKED); +} + +bool wxCheckTree::IsCheckable(const wxTreeItemId& item) +{ + if (!item.IsOk()) + return false; + + const int i = GetItemState(item); + return i >= 0 && i <= CHECKED_DISABLED; +} + +void wxCheckTree::Check(const wxTreeItemId& item, bool state) +{ + if (!item.IsOk()) + return; + + const int old_state = GetItemState(item); + if (UNCHECKED <= old_state && old_state <= CHECKED_DISABLED) + { + const bool enable = !(old_state == UNCHECKED_DISABLED || old_state == CHECKED_DISABLED); + int check = enable ? CHECKED : CHECKED_DISABLED; + int uncheck = enable ? UNCHECKED : UNCHECKED_DISABLED; + const int new_state = state ? check : uncheck; + + if (new_state != old_state) + { + SetItemState(item, new_state); + } + } +} + +void wxCheckTree::Uncheck(const wxTreeItemId& item) +{ + Check(item, false); +} + +void wxCheckTree::On_Tree_Sel_Changed(wxTreeEvent& event) +{ + wxTreeItemId id = event.GetItem(); + + unhighlight(this, last_kf); + mohighlight(this, id, false); + last_kf = id; + + event.Skip(); +} + +void wxCheckTree::On_Char(wxKeyEvent& event) +{ + if (!GetSelection().IsOk() && GetCount() > 0) + { + //If there is no selection, any keypress should just select the first item + wxTreeItemIdValue cookie; + const auto new_item = HasFlag(wxTR_HIDE_ROOT) ? GetFirstChild(GetRootItem(), cookie) : GetRootItem(); + SelectItem(new_item); + last_kf = new_item; + return; + } + + event.Skip(); +} + +void wxCheckTree::On_KeyDown(wxKeyEvent& event) +{ + if (event.GetKeyCode() == WXK_SPACE) + { + last_kf = this->GetSelection(); + ldhighlight(this, last_kf); + } + + event.Skip(); +} + +void wxCheckTree::On_KeyUp(wxKeyEvent& event) +{ + if (event.GetKeyCode() == WXK_SPACE) + { + //last_kf = this->GetSelection(); + mohighlight(this, last_kf, true); + } + else if (event.GetKeyCode() == WXK_ESCAPE) + { + unhighlight(this, last_kf); + last_kf = {}; + Unselect(); + } + + event.Skip(); +} + + +void wxCheckTree::On_Mouse_Enter_Tree(wxMouseEvent& event) +{ + if (event.LeftIsDown()) + { + mouse_entered_tree_with_left_down = true; + } +} + +void wxCheckTree::On_Mouse_Leave_Tree(wxMouseEvent& event) +{ + unhighlight(this, last_mouse_over); + unhighlight(this, last_left_down); + last_mouse_over = {}; + last_left_down = {}; +} + +void wxCheckTree::On_Left_DClick(wxMouseEvent& event) +{ + int flags; + + HitTest(event.GetPosition(), flags); + + //double clicks on buttons can be annoying, so we'll ignore those + //but all other double clicks will just have 1 more click added to them + + //without this, the check boxes are not as responsive as they should be + if (!(flags & wxTREE_HITTEST_ONITEMBUTTON)) + { + On_Left_Down(event); + On_Left_Up(event); + } +} + +void wxCheckTree::On_Left_Down(wxMouseEvent& event) +{ + int flags; + wxTreeItemId id = HitTest(event.GetPosition(), flags); + if (!id.IsOk()) + return; + + int i = GetItemState(id); + + if (id.IsOk() && i >= 0 && on_check(flags)) + { + last_left_down = id; + ldhighlight(this, id); + } + else if (on_label(flags)) + event.Skip(); +} + +void wxCheckTree::On_Left_Up(wxMouseEvent& event) +{ + SetFocus(); + + int flags; + wxTreeItemId id = HitTest(event.GetPosition(), flags); + if (!id.IsOk()) + return; + + int i = GetItemState(id); + + if (mouse_entered_tree_with_left_down) + { + mouse_entered_tree_with_left_down = false; + + if (i >= 0 && on_check(flags)) + { + mohighlight(this, id, false); + last_mouse_over = id; + } + } + else if (id.IsOk()) + { + if (flags & wxTREE_HITTEST_ONITEMBUTTON && ItemHasChildren(id)) + { + if (IsExpanded(id)) + { + Collapse(id); + } + else + { + Expand(id); + } + } + else if (i >= 0 && on_check(flags)) + { + if (id != last_left_down) + { + unhighlight(this, last_left_down); + mohighlight(this, id, false); + } + else + { + mohighlight(this, id, true); + } + + last_left_down = wxTreeItemId(); + last_mouse_over = id; + } + else if (on_label(flags)) + { + event.Skip(); + } + else + { + unhighlight(this, last_left_down); + unhighlight(this, last_mouse_over); + last_left_down = wxTreeItemId(); + last_mouse_over = wxTreeItemId(); + } + } + else + { + //id is not ok + unhighlight(this, last_left_down); + unhighlight(this, last_mouse_over); + last_left_down = wxTreeItemId(); + last_mouse_over = wxTreeItemId(); + } +} + +void wxCheckTree::On_Mouse_Motion(wxMouseEvent& event) +{ + if (mouse_entered_tree_with_left_down) + { + //just ignore everything until the left button is released + return; + } + + int flags; + wxTreeItemId id = HitTest(event.GetPosition(), flags); + + if (!id.IsOk()) + { + unhighlight(this, last_mouse_over); + last_mouse_over = {}; + } + else if (event.LeftIsDown() && last_left_down.IsOk()) + { + //to match the behavior of ordinary check boxes, + //if we've moved to a new item while holding the mouse button down + //we want to set the item where the left down click occured to have + //mouse over highlight. And if we return to the box where the + //left down occured, we want to return it to the having the left down highlight + + //I don't understand why this is the behavior + //of ordinary check boxes, but I'm goin to match it anyway. + + if (id == last_left_down) + { + const auto state = GetItemState(last_left_down); + if (state != UNCHECKED_LEFT_DOWN && state != CHECKED_LEFT_DOWN) + { + ldhighlight(this, last_left_down); + } + } + else + mohighlight(this, last_left_down, false); + } + else + { + //4 cases 1 we're still on the same item, but we've moved off the state icon or label + // 2 we're still on the same item and on the state icon or label - do nothing + // 3 we're on a new item but not on its state icon or label (or the new item has no state) + // 4 we're on a new item, it has a state icon, and we're on the state icon or label + + const int state = GetItemState(id); + if (id == last_mouse_over) + { + if (state < 0 || !on_check(flags)) + { + unhighlight(this, last_mouse_over); + last_mouse_over = {}; + } + } + else + { + if (state < 0 || !on_check(flags)) + { + unhighlight(this, last_mouse_over); + last_mouse_over = {}; + } + else + { + unhighlight(this, last_mouse_over); + mohighlight(this, id, false); + last_mouse_over = id; + } + } + } +} + +void wxCheckTree::On_Mouse_Wheel(wxMouseEvent& event) +{ + event.Skip(); +} + +void wxCheckTree::On_Tree_Focus_Set(wxFocusEvent& event) +{ + //event.Skip(); + + //skipping this event will set the last selected item + //to be highlighted and I want the tree items to only be + //highlighted by keyboard actions. +} + +void wxCheckTree::On_Tree_Focus_Lost(wxFocusEvent& event) +{ + unhighlight(this, last_kf); + Unselect(); + event.Skip(); +} + +void wxCheckTree::SetFocusFromKbd() +{ + if (last_kf.IsOk()) + SelectItem(last_kf); + + wxTreeEvent event2(wxEVT_CHECKTREE_FOCUS, this, wxTreeItemId()); + ProcessWindowEvent(event2); + + wxWindow::SetFocusFromKbd(); +} diff --git a/src/gui/wxcomponents/checktree.h b/src/gui/wxcomponents/checktree.h new file mode 100644 index 00000000..0e7c1b12 --- /dev/null +++ b/src/gui/wxcomponents/checktree.h @@ -0,0 +1,105 @@ +#ifndef checktree_H_INCLUDED +#define checktree_H_INCLUDED + +// credits to: https://forums.wxwidgets.org/viewtopic.php?t=39582 + +#include "wx/treectrl.h" +#include + +#define WXMAKINGDLL_CHECKTREE + +// dll export macros +#ifdef WXMAKINGDLL_CHECKTREE + #define WXDLLIMPEXP_CHECKTREE WXEXPORT +#elif defined(WXUSING_CHECKTREE_SOURCE) + #define WXDLLIMPEXP_CHECKTREE +#elif defined(WXUSINGDLL) + #define WXDLLIMPEXP_CHECKTREE WXIMPORT +#else // not making nor using DLL + #define WXDLLIMPEXP_CHECKTREE +#endif + +#include +// wxDECLARE_CLASS, wxIMPLEMENT_CLASS for sorting? + +class WXDLLIMPEXP_CHECKTREE wxCheckTree : public wxTreeCtrl +{ + wxDECLARE_ABSTRACT_CLASS(wxCheckTree); + + public: + wxCheckTree(); + wxCheckTree(wxWindow *parent, const wxWindowID id, + const wxPoint& pos, const wxSize& size, + long style); + + + void Init(); + void Sort(const wxTreeItemId& node, bool recursive); + + int OnCompareItems(const wxTreeItemId& item1, const wxTreeItemId& item2) override; + + //methods overriden from base class: + void SetFocusFromKbd() override; + void SetItemTextColour(const wxTreeItemId &item, const wxColour &col) override; + + //interaction with the check boxes: + bool EnableCheckBox(const wxTreeItemId &item, bool enable = true ); + bool DisableCheckBox(const wxTreeItemId &item); + void Check(const wxTreeItemId &item,bool state=true); + void Uncheck(const wxTreeItemId &item); + void MakeCheckable(const wxTreeItemId &item, bool state = false); + bool IsCheckable(const wxTreeItemId& item); + + enum + { + UNCHECKED, + UNCHECKED_MOUSE_OVER, + UNCHECKED_LEFT_DOWN, + UNCHECKED_DISABLED, + CHECKED, + CHECKED_MOUSE_OVER, + CHECKED_LEFT_DOWN, + CHECKED_DISABLED + }; + + private: + //event handlers + void On_Tree_Sel_Changed( wxTreeEvent& event ); + + void On_Char( wxKeyEvent& event ); + void On_KeyDown( wxKeyEvent& event ); + void On_KeyUp( wxKeyEvent& event ); + + void On_Mouse_Enter_Tree( wxMouseEvent& event ); + void On_Mouse_Leave_Tree( wxMouseEvent& event ); + void On_Left_DClick( wxMouseEvent& event ); + void On_Left_Down( wxMouseEvent& event ); + void On_Left_Up( wxMouseEvent& event ); + void On_Mouse_Motion( wxMouseEvent& event ); + void On_Mouse_Wheel( wxMouseEvent& event ); + + void On_Tree_Focus_Set( wxFocusEvent& event ); + void On_Tree_Focus_Lost( wxFocusEvent& event ); + + //private data: + std::map m_colors; + + bool mouse_entered_tree_with_left_down = false; + + wxTreeItemId last_mouse_over{}; + wxTreeItemId last_left_down{}; + wxTreeItemId last_kf{}; + + + // NB: due to an ugly wxMSW hack you _must_ use DECLARE_DYNAMIC_CLASS() + // if you want your overloaded OnCompareItems() to be called. + // OTOH, if you don't want it you may omit the next line - this will + // make default (alphabetical) sorting much faster under wxMSW. + //DECLARE_DYNAMIC_CLASS(wxCheckTree) +}; + +wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_CHECKTREE, wxEVT_CHECKTREE_CHOICE, wxTreeEvent); +wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_CHECKTREE, wxEVT_CHECKTREE_FOCUS, wxTreeEvent); + + +#endif // checktree_H_INCLUDED diff --git a/src/gui/wxcomponents/unchecked.xpm b/src/gui/wxcomponents/unchecked.xpm new file mode 100644 index 00000000..eca65396 --- /dev/null +++ b/src/gui/wxcomponents/unchecked.xpm @@ -0,0 +1,24 @@ +/* XPM */ +static const char * unchecked_xpm[] = { +"16 16 5 1", +" c None", +". c #808080", +"X c Black", +"o c #c0c0c0", +"w c White", +" ", +" ", +" ............ ", +" .XXXXXXXXXXo ", +" .Xwwwwwwwwwo ", +" .Xwwwwwwwwwo ", +" .Xwwwwwwwwwo ", +" .Xwwwwwwwwwo ", +" .Xwwwwwwwwwo ", +" .Xwwwwwwwwwo ", +" .Xwwwwwwwwwo ", +" .Xwwwwwwwwwo ", +" .Xwwwwwwwwwo ", +" .ooooooooooo ", +" ", +" "}; diff --git a/src/gui/wxcomponents/unchecked2.xpm b/src/gui/wxcomponents/unchecked2.xpm new file mode 100644 index 00000000..976f7ce5 --- /dev/null +++ b/src/gui/wxcomponents/unchecked2.xpm @@ -0,0 +1,37 @@ +/* XPM */ +static const char * unchecked2_xpm[] = { +"14 15 19 1", +" c None", +". c #1C5180", +"+ c #DCDCD7", +"@ c #DEDED9", +"# c #E0E0DB", +"$ c #E2E2DE", +"% c #E5E5E2", +"& c #E8E8E5", +"* c #ECECE9", +"= c #EFEFEC", +"- c #F1F1EF", +"; c #F3F3F1", +"> c #F5F5F4", +", c #F7F7F6", +"' c #F9F9F8", +") c #FBFBFA", +"! c #FDFDFC", +"~ c #FEFEFE", +"{ c #FFFFFF", +"............. ", +".+++@#$%&*=-. ", +".++@#$%&*=-;. ", +".+@#$%&*=-;>. ", +".@#$%&*=-;>,. ", +".#$%&*=-;>,'. ", +".$%&*=-;>,'). ", +".%&*=-;>,')!. ", +".&*=-;>,')!~. ", +".*=-;>,')!~{. ", +".=-;>,')!~{{. ", +".-;>,')!~{{{. ", +"............. ", +" ", +" "}; diff --git a/src/gui/wxcomponents/unchecked_d.xpm b/src/gui/wxcomponents/unchecked_d.xpm new file mode 100644 index 00000000..69999704 --- /dev/null +++ b/src/gui/wxcomponents/unchecked_d.xpm @@ -0,0 +1,21 @@ +/* XPM */ +static const char * unchecked_d_xpm[] = { +"14 15 3 1", +" c None", +". c #CAC8BB", +"+ c #FFFFFF", +"............. ", +".+++++++++++. ", +".+++++++++++. ", +".+++++++++++. ", +".+++++++++++. ", +".+++++++++++. ", +".+++++++++++. ", +".+++++++++++. ", +".+++++++++++. ", +".+++++++++++. ", +".+++++++++++. ", +".+++++++++++. ", +"............. ", +" ", +" "}; diff --git a/src/gui/wxcomponents/unchecked_dis.xpm b/src/gui/wxcomponents/unchecked_dis.xpm new file mode 100644 index 00000000..e18e32e3 --- /dev/null +++ b/src/gui/wxcomponents/unchecked_dis.xpm @@ -0,0 +1,23 @@ +/* XPM */ +static const char * unchecked_dis_xpm[] = { +"16 16 4 1", +" c None", +". c #808080", +"X c Black", +"o c #c0c0c0", +" ", +" ", +" ............ ", +" .XXXXXXXXXXo ", +" .Xoooooooooo ", +" .Xoooooooooo ", +" .Xoooooooooo ", +" .Xoooooooooo ", +" .Xoooooooooo ", +" .Xoooooooooo ", +" .Xoooooooooo ", +" .Xoooooooooo ", +" .Xoooooooooo ", +" .ooooooooooo ", +" ", +" "}; diff --git a/src/gui/wxcomponents/unchecked_ld.xpm b/src/gui/wxcomponents/unchecked_ld.xpm new file mode 100644 index 00000000..e572efbc --- /dev/null +++ b/src/gui/wxcomponents/unchecked_ld.xpm @@ -0,0 +1,37 @@ +/* XPM */ +static const char * unchecked_ld_xpm[] = { +"14 15 19 1", +" c None", +". c #1C5180", +"+ c #B0B0A7", +"@ c #B3B3AA", +"# c #B7B7AD", +"$ c #BCBBB1", +"% c #C1C1B6", +"& c #C7C6BB", +"* c #CDCCC0", +"= c #D2D1C5", +"- c #D7D5C8", +"; c #DBDACC", +"> c #DFDDCF", +", c #E3E1D3", +"' c #E6E5D6", +") c #EAE8D9", +"! c #EDEBDB", +"~ c #EFEDDD", +"{ c #F1EFDF", +"............. ", +".+++@#$%&*=-. ", +".++@#$%&*=-;. ", +".+@#$%&*=-;>. ", +".@#$%&*=-;>,. ", +".#$%&*=-;>,'. ", +".$%&*=-;>,'). ", +".%&*=-;>,')!. ", +".&*=-;>,')!~. ", +".*=-;>,')!~{. ", +".=-;>,')!~{{. ", +".-;>,')!~{{{. ", +"............. ", +" ", +" "}; diff --git a/src/gui/wxcomponents/unchecked_mo.xpm b/src/gui/wxcomponents/unchecked_mo.xpm new file mode 100644 index 00000000..483f9778 --- /dev/null +++ b/src/gui/wxcomponents/unchecked_mo.xpm @@ -0,0 +1,42 @@ +/* XPM */ +static const char * unchecked_mo_xpm[] = { +"14 15 24 1", +" c None", +". c #1C5180", +"+ c #FFF0CF", +"@ c #FFEDC6", +"# c #FFE9BA", +"$ c #FEE4AC", +"% c #FEDF9C", +"& c #FDD98C", +"* c #FDD684", +"= c #FCD37C", +"- c #FCD074", +"; c #FBCC6B", +"> c #FBC863", +", c #FAC55A", +"' c #E7E7E3", +") c #FAC254", +"! c #FAC04E", +"~ c #F9BD48", +"{ c #F9BB43", +"] c #F9B93E", +"^ c #F9B73A", +"/ c #F8B636", +"( c #F8B433", +"_ c #F8B330", +"............. ", +".+@#$%&*=-;>. ", +".@#$%&*=-;>,. ", +".#$''''''',). ", +".$%''''''')!. ", +".%&'''''''!~. ", +".&*'''''''~{. ", +".*='''''''{]. ", +".=-''''''']^. ", +".-;'''''''^/. ", +".;>,)!~{]^/(. ", +".>,)!~{]^/(_. ", +"............. ", +" ", +" "}; diff --git a/src/gui/wxgui.h b/src/gui/wxgui.h new file mode 100644 index 00000000..f09ea420 --- /dev/null +++ b/src/gui/wxgui.h @@ -0,0 +1,105 @@ +#pragma once + +#include +#ifndef WX_PRECOMP +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern bool g_inputConfigWindowHasFocus; + +// wx helper functions +#include +struct wxStringFormatParameters +{ + sint32 parameter_index; + sint32 parameter_count; + + wchar_t* token_buffer; + wchar_t* substitude_parameter; +}; + +template +wxString wxStringFormat(std::wstring& format, wxStringFormatParameters& parameters) +{ + return format; +} + +template +wxString wxStringFormat(std::wstring& format, wxStringFormatParameters& parameters, T arg, Args... args) +{ + wchar_t tmp[64]; + swprintf(tmp, 64, LR"(\{[%d]+\})", parameters.parameter_index); + const std::wregex placeholder_regex(tmp); + + auto result = format; + while (std::regex_search(result, placeholder_regex)) + { + result = std::regex_replace(result, placeholder_regex, parameters.substitude_parameter, std::regex_constants::format_first_only); + result = wxString::Format(wxString(result), arg); + } + + parameters.parameter_index++; + if (parameters.parameter_index == parameters.parameter_count) + return result; + + parameters.substitude_parameter = std::wcstok(nullptr, LR"( )", ¶meters.token_buffer); + return wxStringFormat(result, parameters, args...); +} + +template +wxString wxStringFormat(const wxString& format, const wchar_t* parameters, T... args) +{ + const auto parameter_count = std::count(parameters, parameters + wcslen(parameters), '%'); + if (parameter_count == 0) + return format; + + const auto copy = wcsdup(parameters); + + wxStringFormatParameters para; + para.substitude_parameter = std::wcstok(copy, LR"( )", ¶.token_buffer); + para.parameter_count = parameter_count; + para.parameter_index = 0; + + auto tmp_string = format.ToStdWstring(); + auto result = wxStringFormat(tmp_string, para, args...); + + free(copy); + + return result; +} + +inline bool SendSliderEvent(wxSlider* slider, int new_value) +{ + wxCommandEvent cevent(wxEVT_SLIDER, slider->GetId()); + cevent.SetInt(new_value); + cevent.SetEventObject(slider); + return slider->HandleWindowEvent(cevent); +} diff --git a/src/imgui/CMakeLists.txt b/src/imgui/CMakeLists.txt new file mode 100644 index 00000000..49e12de9 --- /dev/null +++ b/src/imgui/CMakeLists.txt @@ -0,0 +1,21 @@ +project(imguiImpl) + +include_directories(".") + +add_library(imguiImpl) + +set_property(TARGET imguiImpl PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + +target_sources(imguiImpl PRIVATE + imgui_impl_opengl3.cpp + imgui_impl_opengl3.h + imgui_impl_vulkan.cpp + imgui_impl_vulkan.h + imgui_extension.cpp + imgui_extension.h +) + +target_precompile_headers(imguiImpl PRIVATE ../Common/precompiled.h) + +target_link_libraries(CemuCommon) +target_include_directories(imguiImpl PRIVATE ../) \ No newline at end of file diff --git a/src/imgui/imgui_extension.cpp b/src/imgui/imgui_extension.cpp new file mode 100644 index 00000000..567a1e9a --- /dev/null +++ b/src/imgui/imgui_extension.cpp @@ -0,0 +1,166 @@ +#include "imgui_extension.h" +#include "gui/guiWrapper.h" +#include "Cafe/HW/Latte/Renderer/Renderer.h" +#include "resource/IconsFontAwesome5.h" +#include "imgui_impl_opengl3.h" +#include "resource/resource.h" +#include "imgui_impl_vulkan.h" +#include "input/InputManager.h" + +// +template static T ImMin(T lhs, T rhs) { return lhs < rhs ? lhs : rhs; } +template static T ImMax(T lhs, T rhs) { return lhs >= rhs ? lhs : rhs; } +static ImVec2 ImRotate(const ImVec2& v, float cos_a, float sin_a) { return ImVec2(v.x * cos_a - v.y * sin_a, v.x * sin_a + v.y * cos_a); } + +int rotation_start_index; +void ImRotateStart() +{ + rotation_start_index = ImGui::GetWindowDrawList()->VtxBuffer.Size; +} + +ImVec2 ImRotationCenter() +{ + ImVec2 l(FLT_MAX, FLT_MAX), u(-FLT_MAX, -FLT_MAX); // bounds + + const auto& buf = ImGui::GetWindowDrawList()->VtxBuffer; + for (int i = rotation_start_index; i < buf.Size; i++) + l = ImMin(l, buf[i].pos), u = ImMax(u, buf[i].pos); + + return ImVec2((l.x + u.x) / 2, (l.y + u.y) / 2); // or use _ClipRectStack? +} + +void ImRotateEnd(float rad, ImVec2 center) +{ + const float s = sin(rad); + const float c = cos(rad); + center = ImRotate(center, s, c) - center; + + auto& buf = ImGui::GetWindowDrawList()->VtxBuffer; + for (int i = rotation_start_index; i < buf.Size; i++) + buf[i].pos = ImRotate(buf[i].pos, s, c) - center; +} + +uint8* extractCafeDefaultFont(sint32* size); +sint32 g_font_size = 0; +uint8* g_font_data = nullptr; +std::unordered_map g_imgui_fonts; +std::stack g_font_requests; + +void ImGui_PrecacheFonts() +{ + while (!g_font_requests.empty()) + { + const int size = g_font_requests.top(); + g_font_requests.pop(); + + auto& io = ImGui::GetIO(); + cemu_assert(io.Fonts->Locked == false); + + if (g_font_size == 0) + g_font_data = extractCafeDefaultFont(&g_font_size); + + ImFontConfig cfg{}; + cfg.FontDataOwnedByAtlas = false; + //cfg.FontData = g_font_data; + //cfg.FontDataSize = g_font_size; + //cfg.SizePixels = size; + ImFont* font = io.Fonts->AddFontFromMemoryTTF(g_font_data, g_font_size, (float)size, &cfg); + +#if BOOST_OS_WINDOWS + const auto hinstance = GetModuleHandle(nullptr); + const HRSRC res = FindResource(hinstance, MAKEINTRESOURCE(IDR_FONTAWESOME), RT_RCDATA); + if (res) + { + const HGLOBAL mem = ::LoadResource(hinstance, res); + if (mem) + { + void* data = LockResource(mem); + const size_t len = SizeofResource(hinstance, res); + + ImFontConfig cfgmerge{}; + cfgmerge.FontDataOwnedByAtlas = false; + cfgmerge.MergeMode = true; + cfgmerge.GlyphMinAdvanceX = 20.0f; + //cfgmerge.GlyphOffset = { 2,2 }; + + static const ImWchar icon_ranges[] = { ICON_MIN_FA, ICON_MAX_FA, 0 }; + io.Fonts->AddFontFromMemoryTTF(data, (int)len, (float)size, &cfgmerge, icon_ranges); + } + } +#endif + + g_imgui_fonts[(int)size] = font; + + // Vulkan doesn't let us destroy resources that are still being used, so we flush here + g_renderer->Flush(true); + g_renderer->DeleteFontTextures(); + } +} + +ImFont* ImGui_GetFont(float size) +{ + const auto it = g_imgui_fonts.find((int)size); + if (it != g_imgui_fonts.cend()) + return it->second; + + g_font_requests.emplace((int)size); + return nullptr; // will create the font in next precache call +} + +void ImGui_UpdateWindowInformation(bool mainWindow) +{ + extern WindowInfo g_window_info; + + ImGuiIO& io = ImGui::GetIO(); + io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; + io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; +#if BOOST_OS_WINDOWS + io.ImeWindowHandle = mainWindow ? g_window_info.window_main.hwnd : g_window_info.window_pad.hwnd; +#else + io.ImeWindowHandle = nullptr; +#endif + + io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX); + + auto& instance = InputManager::instance(); + + const auto mousePos = instance.get_mouse_position(!mainWindow); + io.MousePos = { (float)mousePos.x, (float)mousePos.y }; + + bool padDown; + const auto pos = instance.get_left_down_mouse_info(&padDown); + io.MouseDown[0] = padDown != mainWindow && pos.has_value(); + + std::fill_n(io.KeysDown, std::size(io.KeysDown), false); + std::copy(std::cbegin(g_window_info.keydown), std::cend(g_window_info.keydown), io.KeysDown); + + // printf("%f %f %d\n", io.MousePos.x, io.MousePos.y, io.MouseDown[0]); + + for (auto i = 0; i < InputManager::kMaxController; ++i) + { + const auto controller = instance.get_controller(i); + if (!controller) + continue; + + if (controller->is_start_down()) + io.NavInputs[ImGuiNavInput_Input] = 1.0f; + + if (controller->is_a_down()) + io.NavInputs[ImGuiNavInput_Activate] = 1.0f; + + if (controller->is_b_down()) + io.NavInputs[ImGuiNavInput_Cancel] = 1.0f; + + if (controller->is_left_down()) + io.NavInputs[ImGuiNavInput_DpadLeft] = 1.0f; + + if (controller->is_right_down()) + io.NavInputs[ImGuiNavInput_DpadRight] = 1.0f; + + if (controller->is_up_down()) + io.NavInputs[ImGuiNavInput_DpadUp] = 1.0f; + + if (controller->is_down_down()) + io.NavInputs[ImGuiNavInput_DpadDown] = 1.0f; + } +} diff --git a/src/imgui/imgui_extension.h b/src/imgui/imgui_extension.h new file mode 100644 index 00000000..cbc35e69 --- /dev/null +++ b/src/imgui/imgui_extension.h @@ -0,0 +1,14 @@ +#pragma once +#include "imgui.h" + +void ImRotateStart(); +ImVec2 ImRotationCenter(); +void ImRotateEnd(float rad, ImVec2 center = ImRotationCenter()); + +inline ImVec2 operator-(const ImVec2& l, const ImVec2& r) { return{ l.x - r.x, l.y - r.y }; } +inline bool operator<(const ImVec2& lhs, const ImVec2& rhs) { return lhs.x < rhs.x; } +inline bool operator>=(const ImVec2& lhs, const ImVec2& rhs) { return lhs.x >= rhs.x; } + +void ImGui_PrecacheFonts(); +ImFont* ImGui_GetFont(float size); +void ImGui_UpdateWindowInformation(bool mainWindow); \ No newline at end of file diff --git a/src/imgui/imgui_impl_opengl3.cpp b/src/imgui/imgui_impl_opengl3.cpp new file mode 100644 index 00000000..96cf6ce7 --- /dev/null +++ b/src/imgui/imgui_impl_opengl3.cpp @@ -0,0 +1,661 @@ +// dear imgui: Renderer for modern OpenGL with shaders / programmatic pipeline +// - Desktop GL: 2.x 3.x 4.x +// - Embedded GL: ES 2.0 (WebGL 1.0), ES 3.0 (WebGL 2.0) +// This needs to be used along with a Platform Binding (e.g. GLFW, SDL, Win32, custom..) + +// Implemented features: +// [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID in imgui.cpp. +// [x] Renderer: Desktop GL only: Support for large meshes (64k+ vertices) with 16-bits indices. + +// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. +// If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp. +// https://github.com/ocornut/imgui + +// CHANGELOG +// (minor and older changes stripped away, please see git history for details) +// 2019-09-22: OpenGL: Detect default GL loader using __has_include compiler facility. +// 2019-09-16: OpenGL: Tweak initialization code to allow application calling ImGui_ImplOpenGL3_CreateFontsTexture() before the first NewFrame() call. +// 2019-05-29: OpenGL: Desktop GL only: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag. +// 2019-04-30: OpenGL: Added support for special ImDrawCallback_ResetRenderState callback to reset render state. +// 2019-03-29: OpenGL: Not calling glBindBuffer more than necessary in the render loop. +// 2019-03-15: OpenGL: Added a dummy GL call + comments in ImGui_ImplOpenGL3_Init() to detect uninitialized GL function loaders early. +// 2019-03-03: OpenGL: Fix support for ES 2.0 (WebGL 1.0). +// 2019-02-20: OpenGL: Fix for OSX not supporting OpenGL 4.5, we don't try to read GL_CLIP_ORIGIN even if defined by the headers/loader. +// 2019-02-11: OpenGL: Projecting clipping rectangles correctly using draw_data->FramebufferScale to allow multi-viewports for retina display. +// 2019-02-01: OpenGL: Using GLSL 410 shaders for any version over 410 (e.g. 430, 450). +// 2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window. +// 2018-11-13: OpenGL: Support for GL 4.5's glClipControl(GL_UPPER_LEFT) / GL_CLIP_ORIGIN. +// 2018-08-29: OpenGL: Added support for more OpenGL loaders: glew and glad, with comments indicative that any loader can be used. +// 2018-08-09: OpenGL: Default to OpenGL ES 3 on iOS and Android. GLSL version default to "#version 300 ES". +// 2018-07-30: OpenGL: Support for GLSL 300 ES and 410 core. Fixes for Emscripten compilation. +// 2018-07-10: OpenGL: Support for more GLSL versions (based on the GLSL version string). Added error output when shaders fail to compile/link. +// 2018-06-08: Misc: Extracted imgui_impl_opengl3.cpp/.h away from the old combined GLFW/SDL+OpenGL3 examples. +// 2018-06-08: OpenGL: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle. +// 2018-05-25: OpenGL: Removed unnecessary backup/restore of GL_ELEMENT_ARRAY_BUFFER_BINDING since this is part of the VAO state. +// 2018-05-14: OpenGL: Making the call to glBindSampler() optional so 3.2 context won't fail if the function is a NULL pointer. +// 2018-03-06: OpenGL: Added const char* glsl_version parameter to ImGui_ImplOpenGL3_Init() so user can override the GLSL version e.g. "#version 150". +// 2018-02-23: OpenGL: Create the VAO in the render function so the setup can more easily be used with multiple shared GL context. +// 2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplSdlGL3_RenderDrawData() in the .h file so you can call it yourself. +// 2018-01-07: OpenGL: Changed GLSL shader version from 330 to 150. +// 2017-09-01: OpenGL: Save and restore current bound sampler. Save and restore current polygon mode. +// 2017-05-01: OpenGL: Fixed save and restore of current blend func state. +// 2017-05-01: OpenGL: Fixed save and restore of current GL_ACTIVE_TEXTURE. +// 2016-09-05: OpenGL: Fixed save and restore of current scissor rectangle. +// 2016-07-29: OpenGL: Explicitly setting GL_UNPACK_ROW_LENGTH to reduce issues because SDL changes it. (#752) + +//---------------------------------------- +// OpenGL GLSL GLSL +// version version string +//---------------------------------------- +// 2.0 110 "#version 110" +// 2.1 120 "#version 120" +// 3.0 130 "#version 130" +// 3.1 140 "#version 140" +// 3.2 150 "#version 150" +// 3.3 330 "#version 330 core" +// 4.0 400 "#version 400 core" +// 4.1 410 "#version 410 core" +// 4.2 420 "#version 410 core" +// 4.3 430 "#version 430 core" +// ES 2.0 100 "#version 100" = WebGL 1.0 +// ES 3.0 300 "#version 300 es" = WebGL 2.0 +//---------------------------------------- + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "imgui.h" +#include "imgui_impl_opengl3.h" +#include +#include "Common/GLInclude/GLInclude.h" +#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier +#include // intptr_t +#else +#include // intptr_t +#endif +#if defined(__APPLE__) +#include "TargetConditionals.h" +#endif + +// Auto-detect GL version +#if !defined(IMGUI_IMPL_OPENGL_ES2) && !defined(IMGUI_IMPL_OPENGL_ES3) +#if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV)) || (defined(__ANDROID__)) +#define IMGUI_IMPL_OPENGL_ES3 // iOS, Android -> GL ES 3, "#version 300 es" +#undef IMGUI_IMPL_OPENGL_LOADER_GL3W +#undef IMGUI_IMPL_OPENGL_LOADER_GLEW +#undef IMGUI_IMPL_OPENGL_LOADER_GLAD +#undef IMGUI_IMPL_OPENGL_LOADER_CUSTOM +#elif defined(__EMSCRIPTEN__) +#define IMGUI_IMPL_OPENGL_ES2 // Emscripten -> GL ES 2, "#version 100" +#undef IMGUI_IMPL_OPENGL_LOADER_GL3W +#undef IMGUI_IMPL_OPENGL_LOADER_GLEW +#undef IMGUI_IMPL_OPENGL_LOADER_GLAD +#undef IMGUI_IMPL_OPENGL_LOADER_CUSTOM +#endif +#endif + +// GL includes +#if defined(IMGUI_IMPL_OPENGL_ES2) +#include +#elif defined(IMGUI_IMPL_OPENGL_ES3) +#if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV)) +#include // Use GL ES 3 +#else +#include // Use GL ES 3 +#endif +#else +// About Desktop OpenGL function loaders: +// Modern desktop OpenGL doesn't have a standard portable header file to load OpenGL function pointers. +// Helper libraries are often used for this purpose! Here we are supporting a few common ones (gl3w, glew, glad). +// You may use another loader/header of your choice (glext, glLoadGen, etc.), or chose to manually implement your own. +#if defined(IMGUI_IMPL_OPENGL_LOADER_GL3W) +#include // Needs to be initialized with gl3wInit() in user's code +#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLEW) +#include // Needs to be initialized with glewInit() in user's code +#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLAD) +#include // Needs to be initialized with gladLoadGL() in user's code +#else +//#include IMGUI_IMPL_OPENGL_LOADER_CUSTOM"glext.h""glext.h" +#endif +#endif + +// Desktop GL has glDrawElementsBaseVertex() which GL ES and WebGL don't have. +#if defined(IMGUI_IMPL_OPENGL_ES2) || defined(IMGUI_IMPL_OPENGL_ES3) +#define IMGUI_IMPL_OPENGL_HAS_DRAW_WITH_BASE_VERTEX 0 +#else +#define IMGUI_IMPL_OPENGL_HAS_DRAW_WITH_BASE_VERTEX 1 +#endif + +// OpenGL Data +static char g_GlslVersionString[32] = ""; +static GLuint g_FontTexture = 0; +static GLuint g_ShaderHandle = 0, g_VertHandle = 0, g_FragHandle = 0; +static int g_AttribLocationTex = 0, g_AttribLocationProjMtx = 0; // Uniforms location +static int g_AttribLocationVtxPos = 0, g_AttribLocationVtxUV = 0, g_AttribLocationVtxColor = 0; // Vertex attributes location +static unsigned int g_VboHandle = 0, g_ElementsHandle = 0; + +// Functions +bool ImGui_ImplOpenGL3_Init(const char* glsl_version) +{ + // Setup back-end capabilities flags + ImGuiIO& io = ImGui::GetIO(); + io.BackendRendererName = "imgui_impl_opengl3"; +#if IMGUI_IMPL_OPENGL_HAS_DRAW_WITH_BASE_VERTEX + io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. +#endif + + // Store GLSL version string so we can refer to it later in case we recreate shaders. Note: GLSL version is NOT the same as GL version. Leave this to NULL if unsure. +#if defined(IMGUI_IMPL_OPENGL_ES2) + if (glsl_version == NULL) + glsl_version = "#version 100"; +#elif defined(IMGUI_IMPL_OPENGL_ES3) + if (glsl_version == NULL) + glsl_version = "#version 300 es"; +#else + if (glsl_version == NULL) + glsl_version = "#version 130"; +#endif + IM_ASSERT((int)strlen(glsl_version) + 2 < IM_ARRAYSIZE(g_GlslVersionString)); + strcpy(g_GlslVersionString, glsl_version); + strcat(g_GlslVersionString, "\n"); + + // Dummy construct to make it easily visible in the IDE and debugger which GL loader has been selected. + // The code actually never uses the 'gl_loader' variable! It is only here so you can read it! + // If auto-detection fails or doesn't select the same GL loader file as used by your application, + // you are likely to get a crash below. + // You can explicitly select a loader by using '#define IMGUI_IMPL_OPENGL_LOADER_XXX' in imconfig.h or compiler command-line. + const char* gl_loader = "Unknown"; + IM_UNUSED(gl_loader); +#if defined(IMGUI_IMPL_OPENGL_LOADER_GL3W) + gl_loader = "GL3W"; +#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLEW) + gl_loader = "GLEW"; +#elif defined(IMGUI_IMPL_OPENGL_LOADER_GLAD) + gl_loader = "GLAD"; +#else // IMGUI_IMPL_OPENGL_LOADER_CUSTOM + gl_loader = "Custom"; +#endif + + // Make a dummy GL call (we don't actually need the result) + // IF YOU GET A CRASH HERE: it probably means that you haven't initialized the OpenGL function loader used by this code. + // Desktop OpenGL 3/4 need a function loader. See the IMGUI_IMPL_OPENGL_LOADER_xxx explanation above. + GLint current_texture; + glGetIntegerv(GL_TEXTURE_BINDING_2D, ¤t_texture); + + return true; +} + +void ImGui_ImplOpenGL3_Shutdown() +{ + ImGui_ImplOpenGL3_DestroyDeviceObjects(); +} + +void ImGui_ImplOpenGL3_NewFrame() +{ + if (!g_ShaderHandle) + ImGui_ImplOpenGL3_CreateDeviceObjects(); + + if(g_FontTexture == 0) + ImGui_ImplOpenGL3_CreateFontsTexture(); +} + +static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_width, int fb_height, GLuint vertex_array_object) +{ + // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, polygon fill + glEnablei(GL_BLEND, 0); + glBlendEquationSeparatei(0, GL_FUNC_ADD, GL_FUNC_ADD); + glBlendFuncSeparatei(0, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + glEnable(GL_SCISSOR_TEST); +#ifdef GL_POLYGON_MODE + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); +#endif + + // Setup viewport, orthographic projection matrix + // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. + glViewport(0, 0, (GLsizei)fb_width, (GLsizei)fb_height); + float L = draw_data->DisplayPos.x; + float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x; + float T = draw_data->DisplayPos.y; + float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y; + const float ortho_projection[4][4] = + { + { 2.0f / (R - L), 0.0f, 0.0f, 0.0f }, + { 0.0f, 2.0f / (T - B), 0.0f, 0.0f }, + { 0.0f, 0.0f, -1.0f, 0.0f }, + { (R + L) / (L - R), (T + B) / (B - T), 0.0f, 1.0f }, + }; + glUseProgram(g_ShaderHandle); + glUniform1i(g_AttribLocationTex, 0); + glUniformMatrix4fv(g_AttribLocationProjMtx, 1, GL_FALSE, &ortho_projection[0][0]); +#ifdef GL_SAMPLER_BINDING + glBindSampler(0, 0); // We use combined texture/sampler state. Applications using GL 3.3 may set that otherwise. +#endif + + (void)vertex_array_object; +#ifndef IMGUI_IMPL_OPENGL_ES2 + glBindVertexArray(vertex_array_object); +#endif + + // Bind vertex/index buffers and setup attributes for ImDrawVert + glBindBuffer(GL_ARRAY_BUFFER, g_VboHandle); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, g_ElementsHandle); + glEnableVertexAttribArray(g_AttribLocationVtxPos); + glEnableVertexAttribArray(g_AttribLocationVtxUV); + glEnableVertexAttribArray(g_AttribLocationVtxColor); + glVertexAttribPointer(g_AttribLocationVtxPos, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, pos)); + glVertexAttribPointer(g_AttribLocationVtxUV, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, uv)); + glVertexAttribPointer(g_AttribLocationVtxColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, col)); +} + +// OpenGL3 Render function. +// (this used to be set in io.RenderDrawListsFn and called by ImGui::Render(), but you can now call this directly from your main loop) +// Note that this implementation is little overcomplicated because we are saving/setting up/restoring every OpenGL state explicitly, in order to be able to run within any OpenGL engine that doesn't do so. +void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) +{ + // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) + int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); + int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + if (fb_width <= 0 || fb_height <= 0) + return; + + // Backup GL state + GLenum last_active_texture; glGetIntegerv(GL_ACTIVE_TEXTURE, (GLint*)&last_active_texture); + glActiveTexture(GL_TEXTURE0); + GLint last_program; glGetIntegerv(GL_CURRENT_PROGRAM, &last_program); + GLint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); +#ifdef GL_SAMPLER_BINDING + GLint last_sampler; glGetIntegerv(GL_SAMPLER_BINDING, &last_sampler); +#endif + GLint last_array_buffer; glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_array_buffer); +#ifndef IMGUI_IMPL_OPENGL_ES2 + GLint last_vertex_array_object; glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vertex_array_object); +#endif +#ifdef GL_POLYGON_MODE + GLint last_polygon_mode[2]; glGetIntegerv(GL_POLYGON_MODE, last_polygon_mode); +#endif + GLint last_viewport[4]; glGetIntegerv(GL_VIEWPORT, last_viewport); + GLint last_scissor_box[4]; glGetIntegerv(GL_SCISSOR_BOX, last_scissor_box); + //GLenum last_blend_src_rgb; glGetIntegerv(GL_BLEND_SRC_RGB, (GLint*)&last_blend_src_rgb); + //GLenum last_blend_dst_rgb; glGetIntegerv(GL_BLEND_DST_RGB, (GLint*)&last_blend_dst_rgb); + //GLenum last_blend_src_alpha; glGetIntegerv(GL_BLEND_SRC_ALPHA, (GLint*)&last_blend_src_alpha); + //GLenum last_blend_dst_alpha; glGetIntegerv(GL_BLEND_DST_ALPHA, (GLint*)&last_blend_dst_alpha); + //GLenum last_blend_equation_rgb; glGetIntegerv(GL_BLEND_EQUATION_RGB, (GLint*)&last_blend_equation_rgb); + //GLenum last_blend_equation_alpha; glGetIntegerv(GL_BLEND_EQUATION_ALPHA, (GLint*)&last_blend_equation_alpha); + //GLboolean last_enable_blend = false; glIsEnabled(GL_BLEND); + GLboolean last_enable_cull_face = glIsEnabled(GL_CULL_FACE); + GLboolean last_enable_depth_test = glIsEnabled(GL_DEPTH_TEST); + GLboolean last_enable_scissor_test = glIsEnabled(GL_SCISSOR_TEST); + bool clip_origin_lower_left = true; +#if defined(GL_CLIP_ORIGIN) && !defined(__APPLE__) + GLenum last_clip_origin = 0; glGetIntegerv(GL_CLIP_ORIGIN, (GLint*)&last_clip_origin); // Support for GL 4.5's glClipControl(GL_UPPER_LEFT) + if (last_clip_origin == GL_UPPER_LEFT) + clip_origin_lower_left = false; +#endif + + // Setup desired GL state + // Recreate the VAO every time (this is to easily allow multiple GL contexts to be rendered to. VAO are not shared among GL contexts) + // The renderer would actually work without any VAO bound, but then our VertexAttrib calls would overwrite the default one currently bound. + GLuint vertex_array_object = 0; +#ifndef IMGUI_IMPL_OPENGL_ES2 + glGenVertexArrays(1, &vertex_array_object); +#endif + ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object); + + // Will project scissor/clipping rectangles into framebuffer space + ImVec2 clip_off = draw_data->DisplayPos; // (0,0) unless using multi-viewports + ImVec2 clip_scale = draw_data->FramebufferScale; // (1,1) unless using retina display which are often (2,2) + + // Render command lists + for (int n = 0; n < draw_data->CmdListsCount; n++) + { + const ImDrawList* cmd_list = draw_data->CmdLists[n]; + + // Upload vertex/index buffers + glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)cmd_list->VtxBuffer.Size * sizeof(ImDrawVert), (const GLvoid*)cmd_list->VtxBuffer.Data, GL_STREAM_DRAW); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, (GLsizeiptr)cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx), (const GLvoid*)cmd_list->IdxBuffer.Data, GL_STREAM_DRAW); + + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) + { + const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; + if (pcmd->UserCallback != NULL) + { + // User callback, registered via ImDrawList::AddCallback() + // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.) + if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) + ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object); + else + pcmd->UserCallback(cmd_list, pcmd); + } + else + { + // Project scissor/clipping rectangles into framebuffer space + ImVec4 clip_rect; + clip_rect.x = (pcmd->ClipRect.x - clip_off.x) * clip_scale.x; + clip_rect.y = (pcmd->ClipRect.y - clip_off.y) * clip_scale.y; + clip_rect.z = (pcmd->ClipRect.z - clip_off.x) * clip_scale.x; + clip_rect.w = (pcmd->ClipRect.w - clip_off.y) * clip_scale.y; + + if (clip_rect.x < fb_width && clip_rect.y < fb_height && clip_rect.z >= 0.0f && clip_rect.w >= 0.0f) + { + // Apply scissor/clipping rectangle + if (clip_origin_lower_left) + glScissor((int)clip_rect.x, (int)(fb_height - clip_rect.w), (int)(clip_rect.z - clip_rect.x), (int)(clip_rect.w - clip_rect.y)); + else + glScissor((int)clip_rect.x, (int)clip_rect.y, (int)clip_rect.z, (int)clip_rect.w); // Support for GL 4.5 rarely used glClipControl(GL_UPPER_LEFT) + + // Bind texture, Draw + glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->TextureId); +#if IMGUI_IMPL_OPENGL_HAS_DRAW_WITH_BASE_VERTEX + glDrawElementsBaseVertex(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx)), (GLint)pcmd->VtxOffset); +#else + glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx))); +#endif + } + } + } + } + + // Destroy the temporary VAO +#ifndef IMGUI_IMPL_OPENGL_ES2 + glDeleteVertexArrays(1, &vertex_array_object); +#endif + + // Restore modified GL state + glUseProgram(last_program); + glBindTexture(GL_TEXTURE_2D, last_texture); +#ifdef GL_SAMPLER_BINDING + glBindSampler(0, last_sampler); +#endif + glActiveTexture(last_active_texture); +#ifndef IMGUI_IMPL_OPENGL_ES2 + glBindVertexArray(last_vertex_array_object); +#endif + glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer); + //glBlendEquationSeparate(last_blend_equation_rgb, last_blend_equation_alpha); + //glBlendFuncSeparate(last_blend_src_rgb, last_blend_dst_rgb, last_blend_src_alpha, last_blend_dst_alpha); + //if (last_enable_blend) glEnable(GL_BLEND); else glDisable(GL_BLEND); + glDisablei(GL_BLEND, 0); + if (last_enable_cull_face) glEnable(GL_CULL_FACE); else glDisable(GL_CULL_FACE); + if (last_enable_depth_test) glEnable(GL_DEPTH_TEST); else glDisable(GL_DEPTH_TEST); + if (last_enable_scissor_test) glEnable(GL_SCISSOR_TEST); else glDisable(GL_SCISSOR_TEST); +#ifdef GL_POLYGON_MODE + glPolygonMode(GL_FRONT_AND_BACK, (GLenum)last_polygon_mode[0]); +#endif + glViewport(last_viewport[0], last_viewport[1], (GLsizei)last_viewport[2], (GLsizei)last_viewport[3]); + glScissor(last_scissor_box[0], last_scissor_box[1], (GLsizei)last_scissor_box[2], (GLsizei)last_scissor_box[3]); +} + +bool ImGui_ImplOpenGL3_CreateFontsTexture() +{ + if (g_FontTexture != 0) // already created + return false; + + // Build texture atlas + ImGuiIO& io = ImGui::GetIO(); + unsigned char* pixels; + int width, height; + io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bits (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory. + + // Upload texture to graphics system + GLint last_texture; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); + glGenTextures(1, &g_FontTexture); + glBindTexture(GL_TEXTURE_2D, g_FontTexture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); +#ifdef GL_UNPACK_ROW_LENGTH + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); +#endif + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + + // Store our identifier + io.Fonts->TexID = (ImTextureID)(intptr_t)g_FontTexture; + + // Restore state + glBindTexture(GL_TEXTURE_2D, last_texture); + + return true; +} + +void ImGui_ImplOpenGL3_DestroyFontsTexture() +{ + if (g_FontTexture) + { + ImGuiIO& io = ImGui::GetIO(); + glDeleteTextures(1, &g_FontTexture); + io.Fonts->TexID = 0; + g_FontTexture = 0; + } +} + +// If you get an error please report on github. You may try different GL context version or GLSL version. See GL<>GLSL version table at the top of this file. +static bool CheckShader(GLuint handle, const char* desc) +{ + GLint status = 0, log_length = 0; + glGetShaderiv(handle, GL_COMPILE_STATUS, &status); + glGetShaderiv(handle, GL_INFO_LOG_LENGTH, &log_length); + if ((GLboolean)status == GL_FALSE) + fprintf(stderr, "ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to compile %s!\n", desc); + if (log_length > 1) + { + ImVector buf; + buf.resize((int)(log_length + 1)); + glGetShaderInfoLog(handle, log_length, NULL, (GLchar*)buf.begin()); + fprintf(stderr, "%s\n", buf.begin()); + } + return (GLboolean)status == GL_TRUE; +} + +// If you get an error please report on GitHub. You may try different GL context version or GLSL version. +static bool CheckProgram(GLuint handle, const char* desc) +{ + GLint status = 0, log_length = 0; + glGetProgramiv(handle, GL_LINK_STATUS, &status); + glGetProgramiv(handle, GL_INFO_LOG_LENGTH, &log_length); + if ((GLboolean)status == GL_FALSE) + fprintf(stderr, "ERROR: ImGui_ImplOpenGL3_CreateDeviceObjects: failed to link %s! (with GLSL '%s')\n", desc, g_GlslVersionString); + if (log_length > 1) + { + ImVector buf; + buf.resize((int)(log_length + 1)); + glGetProgramInfoLog(handle, log_length, NULL, (GLchar*)buf.begin()); + fprintf(stderr, "%s\n", buf.begin()); + } + return (GLboolean)status == GL_TRUE; +} + +bool ImGui_ImplOpenGL3_CreateDeviceObjects() +{ + // Backup GL state + GLint last_texture, last_array_buffer; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); + glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_array_buffer); +#ifndef IMGUI_IMPL_OPENGL_ES2 + GLint last_vertex_array; + glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vertex_array); +#endif + + // Parse GLSL version string + int glsl_version = 130; + sscanf(g_GlslVersionString, "#version %d", &glsl_version); + + const GLchar* vertex_shader_glsl_120 = + "uniform mat4 ProjMtx;\n" + "attribute vec2 Position;\n" + "attribute vec2 UV;\n" + "attribute vec4 Color;\n" + "varying vec2 Frag_UV;\n" + "varying vec4 Frag_Color;\n" + "void main()\n" + "{\n" + " Frag_UV = UV;\n" + " Frag_Color = Color;\n" + " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" + "}\n"; + + const GLchar* vertex_shader_glsl_130 = + "uniform mat4 ProjMtx;\n" + "in vec2 Position;\n" + "in vec2 UV;\n" + "in vec4 Color;\n" + "out vec2 Frag_UV;\n" + "out vec4 Frag_Color;\n" + "void main()\n" + "{\n" + " Frag_UV = UV;\n" + " Frag_Color = Color;\n" + " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" + "}\n"; + + const GLchar* vertex_shader_glsl_300_es = + "precision mediump float;\n" + "layout (location = 0) in vec2 Position;\n" + "layout (location = 1) in vec2 UV;\n" + "layout (location = 2) in vec4 Color;\n" + "uniform mat4 ProjMtx;\n" + "out vec2 Frag_UV;\n" + "out vec4 Frag_Color;\n" + "void main()\n" + "{\n" + " Frag_UV = UV;\n" + " Frag_Color = Color;\n" + " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" + "}\n"; + + const GLchar* vertex_shader_glsl_410_core = + "layout (location = 0) in vec2 Position;\n" + "layout (location = 1) in vec2 UV;\n" + "layout (location = 2) in vec4 Color;\n" + "uniform mat4 ProjMtx;\n" + "out vec2 Frag_UV;\n" + "out vec4 Frag_Color;\n" + "void main()\n" + "{\n" + " Frag_UV = UV;\n" + " Frag_Color = Color;\n" + " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" + "}\n"; + + const GLchar* fragment_shader_glsl_120 = + "#ifdef GL_ES\n" + " precision mediump float;\n" + "#endif\n" + "uniform sampler2D Texture;\n" + "varying vec2 Frag_UV;\n" + "varying vec4 Frag_Color;\n" + "void main()\n" + "{\n" + " gl_FragColor = Frag_Color * texture2D(Texture, Frag_UV.st);\n" + "}\n"; + + const GLchar* fragment_shader_glsl_130 = + "uniform sampler2D Texture;\n" + "in vec2 Frag_UV;\n" + "in vec4 Frag_Color;\n" + "out vec4 Out_Color;\n" + "void main()\n" + "{\n" + " Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n" + "}\n"; + + const GLchar* fragment_shader_glsl_300_es = + "precision mediump float;\n" + "uniform sampler2D Texture;\n" + "in vec2 Frag_UV;\n" + "in vec4 Frag_Color;\n" + "layout (location = 0) out vec4 Out_Color;\n" + "void main()\n" + "{\n" + " Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n" + "}\n"; + + const GLchar* fragment_shader_glsl_410_core = + "in vec2 Frag_UV;\n" + "in vec4 Frag_Color;\n" + "uniform sampler2D Texture;\n" + "layout (location = 0) out vec4 Out_Color;\n" + "void main()\n" + "{\n" + " Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n" + "}\n"; + + // Select shaders matching our GLSL versions + const GLchar* vertex_shader = NULL; + const GLchar* fragment_shader = NULL; + if (glsl_version < 130) + { + vertex_shader = vertex_shader_glsl_120; + fragment_shader = fragment_shader_glsl_120; + } + else if (glsl_version >= 410) + { + vertex_shader = vertex_shader_glsl_410_core; + fragment_shader = fragment_shader_glsl_410_core; + } + else if (glsl_version == 300) + { + vertex_shader = vertex_shader_glsl_300_es; + fragment_shader = fragment_shader_glsl_300_es; + } + else + { + vertex_shader = vertex_shader_glsl_130; + fragment_shader = fragment_shader_glsl_130; + } + + // Create shaders + const GLchar* vertex_shader_with_version[2] = { g_GlslVersionString, vertex_shader }; + g_VertHandle = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(g_VertHandle, 2, vertex_shader_with_version, NULL); + glCompileShader(g_VertHandle); + CheckShader(g_VertHandle, "vertex shader"); + + const GLchar* fragment_shader_with_version[2] = { g_GlslVersionString, fragment_shader }; + g_FragHandle = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(g_FragHandle, 2, fragment_shader_with_version, NULL); + glCompileShader(g_FragHandle); + CheckShader(g_FragHandle, "fragment shader"); + + g_ShaderHandle = glCreateProgram(); + glAttachShader(g_ShaderHandle, g_VertHandle); + glAttachShader(g_ShaderHandle, g_FragHandle); + glLinkProgram(g_ShaderHandle); + CheckProgram(g_ShaderHandle, "shader program"); + + g_AttribLocationTex = glGetUniformLocation(g_ShaderHandle, "Texture"); + g_AttribLocationProjMtx = glGetUniformLocation(g_ShaderHandle, "ProjMtx"); + g_AttribLocationVtxPos = glGetAttribLocation(g_ShaderHandle, "Position"); + g_AttribLocationVtxUV = glGetAttribLocation(g_ShaderHandle, "UV"); + g_AttribLocationVtxColor = glGetAttribLocation(g_ShaderHandle, "Color"); + + // Create buffers + glGenBuffers(1, &g_VboHandle); + glGenBuffers(1, &g_ElementsHandle); + + ImGui_ImplOpenGL3_CreateFontsTexture(); + + // Restore modified GL state + glBindTexture(GL_TEXTURE_2D, last_texture); + glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer); +#ifndef IMGUI_IMPL_OPENGL_ES2 + glBindVertexArray(last_vertex_array); +#endif + + return true; +} + +void ImGui_ImplOpenGL3_DestroyDeviceObjects() +{ + if (g_VboHandle) { glDeleteBuffers(1, &g_VboHandle); g_VboHandle = 0; } + if (g_ElementsHandle) { glDeleteBuffers(1, &g_ElementsHandle); g_ElementsHandle = 0; } + if (g_ShaderHandle && g_VertHandle) { glDetachShader(g_ShaderHandle, g_VertHandle); } + if (g_ShaderHandle && g_FragHandle) { glDetachShader(g_ShaderHandle, g_FragHandle); } + if (g_VertHandle) { glDeleteShader(g_VertHandle); g_VertHandle = 0; } + if (g_FragHandle) { glDeleteShader(g_FragHandle); g_FragHandle = 0; } + if (g_ShaderHandle) { glDeleteProgram(g_ShaderHandle); g_ShaderHandle = 0; } + + ImGui_ImplOpenGL3_DestroyFontsTexture(); +} diff --git a/src/imgui/imgui_impl_opengl3.h b/src/imgui/imgui_impl_opengl3.h new file mode 100644 index 00000000..91dc0ae3 --- /dev/null +++ b/src/imgui/imgui_impl_opengl3.h @@ -0,0 +1,53 @@ +// dear imgui: Renderer for modern OpenGL with shaders / programmatic pipeline +// - Desktop GL: 3.x 4.x +// - Embedded GL: ES 2.0 (WebGL 1.0), ES 3.0 (WebGL 2.0) +// This needs to be used along with a Platform Binding (e.g. GLFW, SDL, Win32, custom..) + +// Implemented features: +// [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID in imgui.cpp. + +// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. +// If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp. +// https://github.com/ocornut/imgui + +// About Desktop OpenGL function loaders: +// Modern desktop OpenGL doesn't have a standard portable header file to load OpenGL function pointers. +// Helper libraries are often used for this purpose! Here we are supporting a few common ones (gl3w, glew, glad). +// You may use another loader/header of your choice (glext, glLoadGen, etc.), or chose to manually implement your own. + +// About GLSL version: +// The 'glsl_version' initialization parameter should be NULL (default) or a "#version XXX" string. +// On computer platform the GLSL version default to "#version 130". On OpenGL ES 3 platform it defaults to "#version 300 es" +// Only override if your GL version doesn't handle this GLSL version. See GLSL version table at the top of imgui_impl_opengl3.cpp. + +#pragma once + +// Specific OpenGL versions +//#define IMGUI_IMPL_OPENGL_ES2 // Auto-detected on Emscripten +//#define IMGUI_IMPL_OPENGL_ES3 // Auto-detected on iOS/Android + +#include "imgui.h" + +#define IMGUI_IMPL_OPENGL_LOADER_CUSTOM "glext.h" +#include "Common/GLInclude/GLInclude.h" + +// Set default OpenGL3 loader to be gl3w +#if !defined(IMGUI_IMPL_OPENGL_LOADER_GL3W) \ + && !defined(IMGUI_IMPL_OPENGL_LOADER_GLEW) \ + && !defined(IMGUI_IMPL_OPENGL_LOADER_GLAD) \ + && !defined(IMGUI_IMPL_OPENGL_LOADER_CUSTOM) +#define IMGUI_IMPL_OPENGL_LOADER_GL3W +#endif + +#undef IMGUI_IMPL_OPENGL_LOADER_GL3W + +IMGUI_IMPL_API bool ImGui_ImplOpenGL3_Init(const char* glsl_version = NULL); +IMGUI_IMPL_API void ImGui_ImplOpenGL3_Shutdown(); +IMGUI_IMPL_API void ImGui_ImplOpenGL3_NewFrame(); +IMGUI_IMPL_API void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data); + +// Called by Init/NewFrame/Shutdown +IMGUI_IMPL_API bool ImGui_ImplOpenGL3_CreateFontsTexture(); +IMGUI_IMPL_API void ImGui_ImplOpenGL3_DestroyFontsTexture(); +IMGUI_IMPL_API bool ImGui_ImplOpenGL3_CreateDeviceObjects(); +IMGUI_IMPL_API void ImGui_ImplOpenGL3_DestroyDeviceObjects(); diff --git a/src/imgui/imgui_impl_vulkan.cpp b/src/imgui/imgui_impl_vulkan.cpp new file mode 100644 index 00000000..89f94e64 --- /dev/null +++ b/src/imgui/imgui_impl_vulkan.cpp @@ -0,0 +1,1481 @@ +// dear imgui: Renderer for Vulkan +// This needs to be used along with a Platform Binding (e.g. GLFW, SDL, Win32, custom..) + +// Implemented features: +// [X] Renderer: Support for large meshes (64k+ vertices) with 16-bits indices. +// In this binding, ImTextureID is used to store a 'VkDescriptorSet' texture identifier. Read the FAQ about ImTextureID in imgui.cpp. + +// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. +// If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp. +// https://github.com/ocornut/imgui + +// The aim of imgui_impl_vulkan.h/.cpp is to be usable in your engine without any modification. +// IF YOU FEEL YOU NEED TO MAKE ANY CHANGE TO THIS CODE, please share them and your feedback at https://github.com/ocornut/imgui/ + +// Important note to the reader who wish to integrate imgui_impl_vulkan.cpp/.h in their own engine/app. +// - Common ImGui_ImplVulkan_XXX functions and structures are used to interface with imgui_impl_vulkan.cpp/.h. +// You will use those if you want to use this rendering back-end in your engine/app. +// - Helper ImGui_ImplVulkanH_XXX functions and structures are only used by this example (main.cpp) and by +// the back-end itself (imgui_impl_vulkan.cpp), but should PROBABLY NOT be used by your own engine/app code. +// Read comments in imgui_impl_vulkan.h. + +// CHANGELOG +// (minor and older changes stripped away, please see git history for details) +// 2019-05-29: Vulkan: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag. +// 2019-04-30: Vulkan: Added support for special ImDrawCallback_ResetRenderState callback to reset render state. +// 2019-04-04: *BREAKING CHANGE*: Vulkan: Added ImageCount/MinImageCount fields in ImGui_ImplVulkan_InitInfo, required for initialization (was previously a hard #define IMGUI_VK_QUEUED_FRAMES 2). Added ImGui_ImplVulkan_SetMinImageCount(). +// 2019-04-04: Vulkan: Added VkInstance argument to ImGui_ImplVulkanH_CreateWindow() optional helper. +// 2019-04-04: Vulkan: Avoid passing negative coordinates to vkCmdSetScissor, which debug validation layers do not like. +// 2019-04-01: Vulkan: Support for 32-bit index buffer (#define ImDrawIdx unsigned int). +// 2019-02-16: Vulkan: Viewport and clipping rectangles correctly using draw_data->FramebufferScale to allow retina display. +// 2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window. +// 2018-08-25: Vulkan: Fixed mishandled VkSurfaceCapabilitiesKHR::maxImageCount=0 case. +// 2018-06-22: Inverted the parameters to ImGui_ImplVulkan_RenderDrawData() to be consistent with other bindings. +// 2018-06-08: Misc: Extracted imgui_impl_vulkan.cpp/.h away from the old combined GLFW+Vulkan example. +// 2018-06-08: Vulkan: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle. +// 2018-03-03: Vulkan: Various refactor, created a couple of ImGui_ImplVulkanH_XXX helper that the example can use and that viewport support will use. +// 2018-03-01: Vulkan: Renamed ImGui_ImplVulkan_Init_Info to ImGui_ImplVulkan_InitInfo and fields to match more closely Vulkan terminology. +// 2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback, ImGui_ImplVulkan_Render() calls ImGui_ImplVulkan_RenderDrawData() itself. +// 2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves. +// 2017-05-15: Vulkan: Fix scissor offset being negative. Fix new Vulkan validation warnings. Set required depth member for buffer image copy. +// 2016-11-13: Vulkan: Fix validation layer warnings and errors and redeclare gl_PerVertex. +// 2016-10-18: Vulkan: Add location decorators & change to use structs as in/out in glsl, update embedded spv (produced with glslangValidator -x). Null the released resources. +// 2016-08-27: Vulkan: Fix Vulkan example for use when a depth buffer is active. + +#include "imgui.h" +#include "imgui_impl_vulkan.h" +#include +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" + + +// Reusable buffers used for rendering 1 current in-flight frame, for ImGui_ImplVulkan_RenderDrawData() +// [Please zero-clear before use!] +struct ImGui_ImplVulkanH_FrameRenderBuffers +{ + VkDeviceMemory VertexBufferMemory; + VkDeviceMemory IndexBufferMemory; + VkDeviceSize VertexBufferSize; + VkDeviceSize IndexBufferSize; + VkBuffer VertexBuffer; + VkBuffer IndexBuffer; +}; + +// Each viewport will hold 1 ImGui_ImplVulkanH_WindowRenderBuffers +// [Please zero-clear before use!] +struct ImGui_ImplVulkanH_WindowRenderBuffers +{ + uint32_t Index; + uint32_t Count; + ImGui_ImplVulkanH_FrameRenderBuffers* FrameRenderBuffers; +}; + +// Vulkan data +static ImGui_ImplVulkan_InitInfo g_VulkanInitInfo = {}; +static VkRenderPass g_RenderPass = VK_NULL_HANDLE; +static VkDeviceSize g_BufferMemoryAlignment = 256; +static VkPipelineCreateFlags g_PipelineCreateFlags = 0x00; +static VkDescriptorSetLayout g_DescriptorSetLayout = VK_NULL_HANDLE; +static VkPipelineLayout g_PipelineLayout = VK_NULL_HANDLE; +// static VkDescriptorSet g_DescriptorSet = VK_NULL_HANDLE; +static VkPipeline g_Pipeline = VK_NULL_HANDLE; + +// Font data +static VkSampler g_FontSampler = VK_NULL_HANDLE; +static VkDeviceMemory g_FontMemory = VK_NULL_HANDLE; +static VkImage g_FontImage = VK_NULL_HANDLE; +static VkImageView g_FontView = VK_NULL_HANDLE; +static VkDeviceMemory g_UploadBufferMemory = VK_NULL_HANDLE; +static VkBuffer g_UploadBuffer = VK_NULL_HANDLE; + +// Render buffers +static ImGui_ImplVulkanH_WindowRenderBuffers g_MainWindowRenderBuffers; + +// Forward Declarations +bool ImGui_ImplVulkan_CreateDeviceObjects(); +void ImGui_ImplVulkan_DestroyDeviceObjects(); +void ImGui_ImplVulkanH_DestroyFrame(VkDevice device, ImGui_ImplVulkanH_Frame* fd, const VkAllocationCallbacks* allocator); +void ImGui_ImplVulkanH_DestroyFrameSemaphores(VkDevice device, ImGui_ImplVulkanH_FrameSemaphores* fsd, const VkAllocationCallbacks* allocator); +void ImGui_ImplVulkanH_DestroyFrameRenderBuffers(VkDevice device, ImGui_ImplVulkanH_FrameRenderBuffers* buffers, const VkAllocationCallbacks* allocator); +void ImGui_ImplVulkanH_DestroyWindowRenderBuffers(VkDevice device, ImGui_ImplVulkanH_WindowRenderBuffers* buffers, const VkAllocationCallbacks* allocator); +void ImGui_ImplVulkanH_CreateWindowSwapChain(VkPhysicalDevice physical_device, VkDevice device, ImGui_ImplVulkanH_Window* wd, const VkAllocationCallbacks* allocator, int w, int h, uint32_t min_image_count); +void ImGui_ImplVulkanH_CreateWindowCommandBuffers(VkPhysicalDevice physical_device, VkDevice device, ImGui_ImplVulkanH_Window* wd, uint32_t queue_family, const VkAllocationCallbacks* allocator); + +struct ImGuiTexture +{ + VkImage image; + VkDeviceMemory memory; + VkImageView view; + VkSampler sampler; + VkDescriptorSet descriptor_set; + VkBuffer uploadBuffer; + VkDeviceMemory uploadBufferMemory; +}; + + +//----------------------------------------------------------------------------- +// SHADERS +//----------------------------------------------------------------------------- + +// glsl_shader.vert, compiled with: +// # glslangValidator -V -x -o glsl_shader.vert.u32 glsl_shader.vert +/* +#version 450 core +layout(location = 0) in vec2 aPos; +layout(location = 1) in vec2 aUV; +layout(location = 2) in vec4 aColor; +layout(push_constant) uniform uPushConstant { vec2 uScale; vec2 uTranslate; } pc; + +out gl_PerVertex { vec4 gl_Position; }; +layout(location = 0) out struct { vec4 Color; vec2 UV; } Out; + +void main() +{ + Out.Color = aColor; + Out.UV = aUV; + gl_Position = vec4(aPos * pc.uScale + pc.uTranslate, 0, 1); +} +*/ +static uint32_t __glsl_shader_vert_spv[] = +{ + 0x07230203,0x00010000,0x00080001,0x0000002e,0x00000000,0x00020011,0x00000001,0x0006000b, + 0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000,0x0003000e,0x00000000,0x00000001, + 0x000a000f,0x00000000,0x00000004,0x6e69616d,0x00000000,0x0000000b,0x0000000f,0x00000015, + 0x0000001b,0x0000001c,0x00030003,0x00000002,0x000001c2,0x00040005,0x00000004,0x6e69616d, + 0x00000000,0x00030005,0x00000009,0x00000000,0x00050006,0x00000009,0x00000000,0x6f6c6f43, + 0x00000072,0x00040006,0x00000009,0x00000001,0x00005655,0x00030005,0x0000000b,0x0074754f, + 0x00040005,0x0000000f,0x6c6f4361,0x0000726f,0x00030005,0x00000015,0x00565561,0x00060005, + 0x00000019,0x505f6c67,0x65567265,0x78657472,0x00000000,0x00060006,0x00000019,0x00000000, + 0x505f6c67,0x7469736f,0x006e6f69,0x00030005,0x0000001b,0x00000000,0x00040005,0x0000001c, + 0x736f5061,0x00000000,0x00060005,0x0000001e,0x73755075,0x6e6f4368,0x6e617473,0x00000074, + 0x00050006,0x0000001e,0x00000000,0x61635375,0x0000656c,0x00060006,0x0000001e,0x00000001, + 0x61725475,0x616c736e,0x00006574,0x00030005,0x00000020,0x00006370,0x00040047,0x0000000b, + 0x0000001e,0x00000000,0x00040047,0x0000000f,0x0000001e,0x00000002,0x00040047,0x00000015, + 0x0000001e,0x00000001,0x00050048,0x00000019,0x00000000,0x0000000b,0x00000000,0x00030047, + 0x00000019,0x00000002,0x00040047,0x0000001c,0x0000001e,0x00000000,0x00050048,0x0000001e, + 0x00000000,0x00000023,0x00000000,0x00050048,0x0000001e,0x00000001,0x00000023,0x00000008, + 0x00030047,0x0000001e,0x00000002,0x00020013,0x00000002,0x00030021,0x00000003,0x00000002, + 0x00030016,0x00000006,0x00000020,0x00040017,0x00000007,0x00000006,0x00000004,0x00040017, + 0x00000008,0x00000006,0x00000002,0x0004001e,0x00000009,0x00000007,0x00000008,0x00040020, + 0x0000000a,0x00000003,0x00000009,0x0004003b,0x0000000a,0x0000000b,0x00000003,0x00040015, + 0x0000000c,0x00000020,0x00000001,0x0004002b,0x0000000c,0x0000000d,0x00000000,0x00040020, + 0x0000000e,0x00000001,0x00000007,0x0004003b,0x0000000e,0x0000000f,0x00000001,0x00040020, + 0x00000011,0x00000003,0x00000007,0x0004002b,0x0000000c,0x00000013,0x00000001,0x00040020, + 0x00000014,0x00000001,0x00000008,0x0004003b,0x00000014,0x00000015,0x00000001,0x00040020, + 0x00000017,0x00000003,0x00000008,0x0003001e,0x00000019,0x00000007,0x00040020,0x0000001a, + 0x00000003,0x00000019,0x0004003b,0x0000001a,0x0000001b,0x00000003,0x0004003b,0x00000014, + 0x0000001c,0x00000001,0x0004001e,0x0000001e,0x00000008,0x00000008,0x00040020,0x0000001f, + 0x00000009,0x0000001e,0x0004003b,0x0000001f,0x00000020,0x00000009,0x00040020,0x00000021, + 0x00000009,0x00000008,0x0004002b,0x00000006,0x00000028,0x00000000,0x0004002b,0x00000006, + 0x00000029,0x3f800000,0x00050036,0x00000002,0x00000004,0x00000000,0x00000003,0x000200f8, + 0x00000005,0x0004003d,0x00000007,0x00000010,0x0000000f,0x00050041,0x00000011,0x00000012, + 0x0000000b,0x0000000d,0x0003003e,0x00000012,0x00000010,0x0004003d,0x00000008,0x00000016, + 0x00000015,0x00050041,0x00000017,0x00000018,0x0000000b,0x00000013,0x0003003e,0x00000018, + 0x00000016,0x0004003d,0x00000008,0x0000001d,0x0000001c,0x00050041,0x00000021,0x00000022, + 0x00000020,0x0000000d,0x0004003d,0x00000008,0x00000023,0x00000022,0x00050085,0x00000008, + 0x00000024,0x0000001d,0x00000023,0x00050041,0x00000021,0x00000025,0x00000020,0x00000013, + 0x0004003d,0x00000008,0x00000026,0x00000025,0x00050081,0x00000008,0x00000027,0x00000024, + 0x00000026,0x00050051,0x00000006,0x0000002a,0x00000027,0x00000000,0x00050051,0x00000006, + 0x0000002b,0x00000027,0x00000001,0x00070050,0x00000007,0x0000002c,0x0000002a,0x0000002b, + 0x00000028,0x00000029,0x00050041,0x00000011,0x0000002d,0x0000001b,0x0000000d,0x0003003e, + 0x0000002d,0x0000002c,0x000100fd,0x00010038 +}; + +// glsl_shader.frag, compiled with: +// # glslangValidator -V -x -o glsl_shader.frag.u32 glsl_shader.frag +/* +#version 450 core +layout(location = 0) out vec4 fColor; +layout(set=0, binding=0) uniform sampler2D sTexture; +layout(location = 0) in struct { vec4 Color; vec2 UV; } In; +void main() +{ + fColor = In.Color * texture(sTexture, In.UV.st); +} +*/ +static uint32_t __glsl_shader_frag_spv[] = +{ + 0x07230203,0x00010000,0x00080001,0x0000001e,0x00000000,0x00020011,0x00000001,0x0006000b, + 0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000,0x0003000e,0x00000000,0x00000001, + 0x0007000f,0x00000004,0x00000004,0x6e69616d,0x00000000,0x00000009,0x0000000d,0x00030010, + 0x00000004,0x00000007,0x00030003,0x00000002,0x000001c2,0x00040005,0x00000004,0x6e69616d, + 0x00000000,0x00040005,0x00000009,0x6c6f4366,0x0000726f,0x00030005,0x0000000b,0x00000000, + 0x00050006,0x0000000b,0x00000000,0x6f6c6f43,0x00000072,0x00040006,0x0000000b,0x00000001, + 0x00005655,0x00030005,0x0000000d,0x00006e49,0x00050005,0x00000016,0x78655473,0x65727574, + 0x00000000,0x00040047,0x00000009,0x0000001e,0x00000000,0x00040047,0x0000000d,0x0000001e, + 0x00000000,0x00040047,0x00000016,0x00000022,0x00000000,0x00040047,0x00000016,0x00000021, + 0x00000000,0x00020013,0x00000002,0x00030021,0x00000003,0x00000002,0x00030016,0x00000006, + 0x00000020,0x00040017,0x00000007,0x00000006,0x00000004,0x00040020,0x00000008,0x00000003, + 0x00000007,0x0004003b,0x00000008,0x00000009,0x00000003,0x00040017,0x0000000a,0x00000006, + 0x00000002,0x0004001e,0x0000000b,0x00000007,0x0000000a,0x00040020,0x0000000c,0x00000001, + 0x0000000b,0x0004003b,0x0000000c,0x0000000d,0x00000001,0x00040015,0x0000000e,0x00000020, + 0x00000001,0x0004002b,0x0000000e,0x0000000f,0x00000000,0x00040020,0x00000010,0x00000001, + 0x00000007,0x00090019,0x00000013,0x00000006,0x00000001,0x00000000,0x00000000,0x00000000, + 0x00000001,0x00000000,0x0003001b,0x00000014,0x00000013,0x00040020,0x00000015,0x00000000, + 0x00000014,0x0004003b,0x00000015,0x00000016,0x00000000,0x0004002b,0x0000000e,0x00000018, + 0x00000001,0x00040020,0x00000019,0x00000001,0x0000000a,0x00050036,0x00000002,0x00000004, + 0x00000000,0x00000003,0x000200f8,0x00000005,0x00050041,0x00000010,0x00000011,0x0000000d, + 0x0000000f,0x0004003d,0x00000007,0x00000012,0x00000011,0x0004003d,0x00000014,0x00000017, + 0x00000016,0x00050041,0x00000019,0x0000001a,0x0000000d,0x00000018,0x0004003d,0x0000000a, + 0x0000001b,0x0000001a,0x00050057,0x00000007,0x0000001c,0x00000017,0x0000001b,0x00050085, + 0x00000007,0x0000001d,0x00000012,0x0000001c,0x0003003e,0x00000009,0x0000001d,0x000100fd, + 0x00010038 +}; + +//----------------------------------------------------------------------------- +// FUNCTIONS +//----------------------------------------------------------------------------- + +static uint32_t ImGui_ImplVulkan_MemoryType(VkMemoryPropertyFlags properties, uint32_t type_bits) +{ + ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; + VkPhysicalDeviceMemoryProperties prop; + vkGetPhysicalDeviceMemoryProperties(v->PhysicalDevice, &prop); + for (uint32_t i = 0; i < prop.memoryTypeCount; i++) + if ((prop.memoryTypes[i].propertyFlags & properties) == properties && type_bits & (1<CheckVkResultFn) + v->CheckVkResultFn(err); +} + +static void CreateOrResizeBuffer(VkBuffer& buffer, VkDeviceMemory& buffer_memory, VkDeviceSize& p_buffer_size, size_t new_size, VkBufferUsageFlagBits usage) +{ + VulkanRenderer* vkRenderer = VulkanRenderer::GetInstance(); + + ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; + VkResult err; + if (buffer != VK_NULL_HANDLE) + { + vkRenderer->destroyBuffer(buffer); + } + if (buffer_memory != VK_NULL_HANDLE) + { + vkRenderer->destroyDeviceMemory(buffer_memory); + } + VkDeviceSize vertex_buffer_size_aligned = ((new_size - 1) / g_BufferMemoryAlignment + 1) * g_BufferMemoryAlignment; + VkBufferCreateInfo buffer_info = {}; + buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + buffer_info.size = vertex_buffer_size_aligned; + buffer_info.usage = usage; + buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + err = vkCreateBuffer(v->Device, &buffer_info, v->Allocator, &buffer); + check_vk_result(err); + + VkMemoryRequirements req; + vkGetBufferMemoryRequirements(v->Device, buffer, &req); + g_BufferMemoryAlignment = (g_BufferMemoryAlignment > req.alignment) ? g_BufferMemoryAlignment : req.alignment; + VkMemoryAllocateInfo alloc_info = {}; + alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + alloc_info.allocationSize = req.size; + alloc_info.memoryTypeIndex = ImGui_ImplVulkan_MemoryType(VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, req.memoryTypeBits); + err = vkAllocateMemory(v->Device, &alloc_info, v->Allocator, &buffer_memory); + check_vk_result(err); + + err = vkBindBufferMemory(v->Device, buffer, buffer_memory, 0); + check_vk_result(err); + p_buffer_size = new_size; +} + +static void ImGui_ImplVulkan_SetupRenderState(ImDrawData* draw_data, VkCommandBuffer command_buffer, ImGui_ImplVulkanH_FrameRenderBuffers* rb, int fb_width, int fb_height) +{ + // Bind pipeline + { + vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, g_Pipeline); + //VkDescriptorSet desc_set[1] = { g_DescriptorSet }; + //vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, g_PipelineLayout, 0, 1, desc_set, 0, NULL); + } + + // Bind Vertex And Index Buffer: + { + VkBuffer vertex_buffers[1] = { rb->VertexBuffer }; + VkDeviceSize vertex_offset[1] = { 0 }; + vkCmdBindVertexBuffers(command_buffer, 0, 1, vertex_buffers, vertex_offset); + vkCmdBindIndexBuffer(command_buffer, rb->IndexBuffer, 0, sizeof(ImDrawIdx) == 2 ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT32); + } + + // Setup viewport: + { + VkViewport viewport; + viewport.x = 0; + viewport.y = 0; + viewport.width = (float)fb_width; + viewport.height = (float)fb_height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewport(command_buffer, 0, 1, &viewport); + } + + // Setup scale and translation: + // Our visible imgui space lies from draw_data->DisplayPps (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. + { + float scale[2]; + scale[0] = 2.0f / draw_data->DisplaySize.x; + scale[1] = 2.0f / draw_data->DisplaySize.y; + float translate[2]; + translate[0] = -1.0f - draw_data->DisplayPos.x * scale[0]; + translate[1] = -1.0f - draw_data->DisplayPos.y * scale[1]; + vkCmdPushConstants(command_buffer, g_PipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, sizeof(float) * 0, sizeof(float) * 2, scale); + vkCmdPushConstants(command_buffer, g_PipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, sizeof(float) * 2, sizeof(float) * 2, translate); + } +} + +// Render function +// (this used to be set in io.RenderDrawListsFn and called by ImGui::Render(), but you can now call this directly from your main loop) +void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer command_buffer) +{ + // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) + int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); + int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + if (fb_width <= 0 || fb_height <= 0 || draw_data->TotalVtxCount == 0) + return; + + ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; + + // Allocate array to store enough vertex/index buffers + ImGui_ImplVulkanH_WindowRenderBuffers* wrb = &g_MainWindowRenderBuffers; + if (wrb->FrameRenderBuffers == NULL) + { + wrb->Index = 0; + wrb->Count = v->ImageCount; + wrb->FrameRenderBuffers = (ImGui_ImplVulkanH_FrameRenderBuffers*)IM_ALLOC(sizeof(ImGui_ImplVulkanH_FrameRenderBuffers) * wrb->Count); + memset(wrb->FrameRenderBuffers, 0, sizeof(ImGui_ImplVulkanH_FrameRenderBuffers) * wrb->Count); + } + IM_ASSERT(wrb->Count == v->ImageCount); + wrb->Index = (wrb->Index + 1) % wrb->Count; + ImGui_ImplVulkanH_FrameRenderBuffers* rb = &wrb->FrameRenderBuffers[wrb->Index]; + + VkResult err; + + // Create or resize the vertex/index buffers + size_t vertex_size = draw_data->TotalVtxCount * sizeof(ImDrawVert); + size_t index_size = draw_data->TotalIdxCount * sizeof(ImDrawIdx); + if (rb->VertexBuffer == VK_NULL_HANDLE || rb->VertexBufferSize < vertex_size) + CreateOrResizeBuffer(rb->VertexBuffer, rb->VertexBufferMemory, rb->VertexBufferSize, vertex_size, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); + if (rb->IndexBuffer == VK_NULL_HANDLE || rb->IndexBufferSize < index_size) + CreateOrResizeBuffer(rb->IndexBuffer, rb->IndexBufferMemory, rb->IndexBufferSize, index_size, VK_BUFFER_USAGE_INDEX_BUFFER_BIT); + + // Upload vertex/index data into a single contiguous GPU buffer + { + cemu_assert_debug(vertex_size != 0); + cemu_assert_debug(index_size != 0); + + + // todo - read and use actual nonCoherentAtomSize instead of assuming 0x80 + uint32 aligned_vertex_size = (vertex_size + 0x7F) & ~0x7F; + uint32 aligned_index_size = (index_size + 0x7F) & ~0x7F; + + ImDrawVert* vtx_dst = NULL; + ImDrawIdx* idx_dst = NULL; + err = vkMapMemory(v->Device, rb->VertexBufferMemory, 0, aligned_vertex_size, 0, (void**)(&vtx_dst)); + check_vk_result(err); + err = vkMapMemory(v->Device, rb->IndexBufferMemory, 0, aligned_index_size, 0, (void**)(&idx_dst)); + check_vk_result(err); + for (int n = 0; n < draw_data->CmdListsCount; n++) + { + const ImDrawList* cmd_list = draw_data->CmdLists[n]; + memcpy(vtx_dst, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert)); + memcpy(idx_dst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx)); + vtx_dst += cmd_list->VtxBuffer.Size; + idx_dst += cmd_list->IdxBuffer.Size; + } + VkMappedMemoryRange range[2] = {}; + range[0].sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; + range[0].memory = rb->VertexBufferMemory; + range[0].size = VK_WHOLE_SIZE; + range[1].sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; + range[1].memory = rb->IndexBufferMemory; + range[1].size = VK_WHOLE_SIZE; + err = vkFlushMappedMemoryRanges(v->Device, 2, range); + check_vk_result(err); + vkUnmapMemory(v->Device, rb->VertexBufferMemory); + vkUnmapMemory(v->Device, rb->IndexBufferMemory); + } + + // Setup desired Vulkan state + ImGui_ImplVulkan_SetupRenderState(draw_data, command_buffer, rb, fb_width, fb_height); + + // Will project scissor/clipping rectangles into framebuffer space + ImVec2 clip_off = draw_data->DisplayPos; // (0,0) unless using multi-viewports + ImVec2 clip_scale = draw_data->FramebufferScale; // (1,1) unless using retina display which are often (2,2) + + // Render command lists + // (Because we merged all buffers into a single one, we maintain our own offset into them) + int vtx_offset = 0; + int idx_offset = 0; + for (int n = 0; n < draw_data->CmdListsCount; n++) + { + const ImDrawList* cmd_list = draw_data->CmdLists[n]; + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) + { + const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; + if (pcmd->UserCallback != NULL) + { + // User callback, registered via ImDrawList::AddCallback() + // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.) + if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) + ImGui_ImplVulkan_SetupRenderState(draw_data, command_buffer, rb, fb_width, fb_height); + else + pcmd->UserCallback(cmd_list, pcmd); + } + else + { + // Project scissor/clipping rectangles into framebuffer space + ImVec4 clip_rect; + clip_rect.x = (pcmd->ClipRect.x - clip_off.x) * clip_scale.x; + clip_rect.y = (pcmd->ClipRect.y - clip_off.y) * clip_scale.y; + clip_rect.z = (pcmd->ClipRect.z - clip_off.x) * clip_scale.x; + clip_rect.w = (pcmd->ClipRect.w - clip_off.y) * clip_scale.y; + + if (clip_rect.x < fb_width && clip_rect.y < fb_height && clip_rect.z >= 0.0f && clip_rect.w >= 0.0f) + { + // Negative offsets are illegal for vkCmdSetScissor + if (clip_rect.x < 0.0f) + clip_rect.x = 0.0f; + if (clip_rect.y < 0.0f) + clip_rect.y = 0.0f; + + // Apply scissor/clipping rectangle + VkRect2D scissor; + scissor.offset.x = (int32_t)(clip_rect.x); + scissor.offset.y = (int32_t)(clip_rect.y); + scissor.extent.width = (uint32_t)(clip_rect.z - clip_rect.x); + scissor.extent.height = (uint32_t)(clip_rect.w - clip_rect.y); + vkCmdSetScissor(command_buffer, 0, 1, &scissor); + + VkDescriptorSet desc_set[1] = { ((ImGuiTexture*)pcmd->TextureId)->descriptor_set }; + vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, g_PipelineLayout, 0, 1, desc_set, 0, NULL); + + // Draw + //vkCmdDrawIndexed(command_buffer, pcmd->ElemCount, 1, pcmd->IdxOffset + global_idx_offset, pcmd->VtxOffset + global_vtx_offset, 0); + vkCmdDrawIndexed(command_buffer, pcmd->ElemCount, 1, idx_offset, vtx_offset, 0); + } + } + idx_offset += pcmd->ElemCount; + } + vtx_offset += cmd_list->VtxBuffer.Size; + // global_idx_offset += cmd_list->IdxBuffer.Size; + // global_vtx_offset += cmd_list->VtxBuffer.Size; + } +} + +void ImGui_ImplVulkan_DestroyFontsTexture() +{ + ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; + ImGui_ImplVulkan_DestroyFontUploadObjects(); + if (g_FontView) { vkDestroyImageView(v->Device, g_FontView, v->Allocator); g_FontView = VK_NULL_HANDLE; } + if (g_FontImage) { vkDestroyImage(v->Device, g_FontImage, v->Allocator); g_FontImage = VK_NULL_HANDLE; } + if (g_FontMemory) { vkFreeMemory(v->Device, g_FontMemory, v->Allocator); g_FontMemory = VK_NULL_HANDLE; } +} + +bool ImGui_ImplVulkan_CreateFontsTexture(VkCommandBuffer command_buffer) +{ + if(g_FontView) + return true; + + ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; + ImGuiIO& io = ImGui::GetIO(); + + unsigned char* pixels; + int width, height; + io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); + size_t upload_size = width*height*4*sizeof(char); + + VkResult err; + + // Create the Image: + { + VkImageCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + info.imageType = VK_IMAGE_TYPE_2D; + info.format = VK_FORMAT_R8G8B8A8_UNORM; + info.extent.width = width; + info.extent.height = height; + info.extent.depth = 1; + info.mipLevels = 1; + info.arrayLayers = 1; + info.samples = VK_SAMPLE_COUNT_1_BIT; + info.tiling = VK_IMAGE_TILING_OPTIMAL; + info.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + err = vkCreateImage(v->Device, &info, v->Allocator, &g_FontImage); + check_vk_result(err); + VkMemoryRequirements req; + vkGetImageMemoryRequirements(v->Device, g_FontImage, &req); + VkMemoryAllocateInfo alloc_info = {}; + alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + alloc_info.allocationSize = req.size; + alloc_info.memoryTypeIndex = ImGui_ImplVulkan_MemoryType(VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, req.memoryTypeBits); + err = vkAllocateMemory(v->Device, &alloc_info, v->Allocator, &g_FontMemory); + check_vk_result(err); + err = vkBindImageMemory(v->Device, g_FontImage, g_FontMemory, 0); + check_vk_result(err); + } + + // Create the Image View: + { + VkImageViewCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + info.image = g_FontImage; + info.viewType = VK_IMAGE_VIEW_TYPE_2D; + info.format = VK_FORMAT_R8G8B8A8_UNORM; + info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + info.subresourceRange.levelCount = 1; + info.subresourceRange.layerCount = 1; + err = vkCreateImageView(v->Device, &info, v->Allocator, &g_FontView); + check_vk_result(err); + } + + //// Update the Descriptor Set: + //{ + // VkDescriptorImageInfo desc_image[1] = {}; + // desc_image[0].sampler = g_FontSampler; + // desc_image[0].imageView = g_FontView; + // desc_image[0].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + // VkWriteDescriptorSet write_desc[1] = {}; + // write_desc[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + // write_desc[0].dstSet = g_DescriptorSet; + // write_desc[0].descriptorCount = 1; + // write_desc[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + // write_desc[0].pImageInfo = desc_image; + // vkUpdateDescriptorSets(v->Device, 1, write_desc, 0, NULL); + //} + VkDescriptorSet font_descriptor_set = (VkDescriptorSet)ImGui_ImplVulkan_AddTexture(g_FontSampler, g_FontView, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + // Create the Upload Buffer: + { + VkBufferCreateInfo buffer_info = {}; + buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + buffer_info.size = upload_size; + buffer_info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + err = vkCreateBuffer(v->Device, &buffer_info, v->Allocator, &g_UploadBuffer); + check_vk_result(err); + VkMemoryRequirements req; + vkGetBufferMemoryRequirements(v->Device, g_UploadBuffer, &req); + g_BufferMemoryAlignment = (g_BufferMemoryAlignment > req.alignment) ? g_BufferMemoryAlignment : req.alignment; + VkMemoryAllocateInfo alloc_info = {}; + alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + alloc_info.allocationSize = req.size; + alloc_info.memoryTypeIndex = ImGui_ImplVulkan_MemoryType(VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, req.memoryTypeBits); + err = vkAllocateMemory(v->Device, &alloc_info, v->Allocator, &g_UploadBufferMemory); + check_vk_result(err); + err = vkBindBufferMemory(v->Device, g_UploadBuffer, g_UploadBufferMemory, 0); + check_vk_result(err); + } + + // Upload to Buffer: + { + cemu_assert_debug(upload_size != 0); + char* map = NULL; + err = vkMapMemory(v->Device, g_UploadBufferMemory, 0, upload_size, 0, (void**)(&map)); + check_vk_result(err); + memcpy(map, pixels, upload_size); + VkMappedMemoryRange range[1] = {}; + range[0].sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; + range[0].memory = g_UploadBufferMemory; + range[0].size = upload_size; + err = vkFlushMappedMemoryRanges(v->Device, 1, range); + check_vk_result(err); + vkUnmapMemory(v->Device, g_UploadBufferMemory); + } + + // Copy to Image: + { + VkImageMemoryBarrier copy_barrier[1] = {}; + copy_barrier[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + copy_barrier[0].dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + copy_barrier[0].oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + copy_barrier[0].newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + copy_barrier[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + copy_barrier[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + copy_barrier[0].image = g_FontImage; + copy_barrier[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + copy_barrier[0].subresourceRange.levelCount = 1; + copy_barrier[0].subresourceRange.layerCount = 1; + vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 0, NULL, 1, copy_barrier); + + VkBufferImageCopy region = {}; + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.layerCount = 1; + region.imageExtent.width = width; + region.imageExtent.height = height; + region.imageExtent.depth = 1; + vkCmdCopyBufferToImage(command_buffer, g_UploadBuffer, g_FontImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + + VkImageMemoryBarrier use_barrier[1] = {}; + use_barrier[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + use_barrier[0].srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + use_barrier[0].dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + use_barrier[0].oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + use_barrier[0].newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + use_barrier[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + use_barrier[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + use_barrier[0].image = g_FontImage; + use_barrier[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + use_barrier[0].subresourceRange.levelCount = 1; + use_barrier[0].subresourceRange.layerCount = 1; + vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, NULL, 0, NULL, 1, use_barrier); + } + + // Store our identifier + //io.Fonts->TexID = (ImTextureID)(intptr_t)g_FontImage; + + auto texture = new ImGuiTexture(); + texture->descriptor_set = font_descriptor_set; + io.Fonts->TexID = (ImTextureID)texture; + + return true; +} + +bool ImGui_ImplVulkan_CreateDeviceObjects() +{ + ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; + VkResult err; + VkShaderModule vert_module; + VkShaderModule frag_module; + + // Create The Shader Modules: + { + VkShaderModuleCreateInfo vert_info = {}; + vert_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + vert_info.codeSize = sizeof(__glsl_shader_vert_spv); + vert_info.pCode = (uint32_t*)__glsl_shader_vert_spv; + err = vkCreateShaderModule(v->Device, &vert_info, v->Allocator, &vert_module); + check_vk_result(err); + VkShaderModuleCreateInfo frag_info = {}; + frag_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + frag_info.codeSize = sizeof(__glsl_shader_frag_spv); + frag_info.pCode = (uint32_t*)__glsl_shader_frag_spv; + err = vkCreateShaderModule(v->Device, &frag_info, v->Allocator, &frag_module); + check_vk_result(err); + } + + if (!g_FontSampler) + { + VkSamplerCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + info.magFilter = VK_FILTER_LINEAR; + info.minFilter = VK_FILTER_LINEAR; + info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + info.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; + info.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; + info.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + info.minLod = -1000; + info.maxLod = 1000; + info.maxAnisotropy = 1.0f; + err = vkCreateSampler(v->Device, &info, v->Allocator, &g_FontSampler); + check_vk_result(err); + } + + if (!g_DescriptorSetLayout) + { + VkSampler sampler[1] = {g_FontSampler}; + VkDescriptorSetLayoutBinding binding[1] = {}; + binding[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + binding[0].descriptorCount = 1; + binding[0].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + //binding[0].pImmutableSamplers = sampler; + VkDescriptorSetLayoutCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + info.bindingCount = 1; + info.pBindings = binding; + err = vkCreateDescriptorSetLayout(v->Device, &info, v->Allocator, &g_DescriptorSetLayout); + check_vk_result(err); + } + + //// Create Descriptor Set: + //{ + // VkDescriptorSetAllocateInfo alloc_info = {}; + // alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + // alloc_info.descriptorPool = v->DescriptorPool; + // alloc_info.descriptorSetCount = 1; + // alloc_info.pSetLayouts = &g_DescriptorSetLayout; + // err = vkAllocateDescriptorSets(v->Device, &alloc_info, &g_DescriptorSet); + // check_vk_result(err); + //} + + if (!g_PipelineLayout) + { + // Constants: we are using 'vec2 offset' and 'vec2 scale' instead of a full 3d projection matrix + VkPushConstantRange push_constants[1] = {}; + push_constants[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + push_constants[0].offset = sizeof(float) * 0; + push_constants[0].size = sizeof(float) * 4; + VkDescriptorSetLayout set_layout[1] = { g_DescriptorSetLayout }; + VkPipelineLayoutCreateInfo layout_info = {}; + layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + layout_info.setLayoutCount = 1; + layout_info.pSetLayouts = set_layout; + layout_info.pushConstantRangeCount = 1; + layout_info.pPushConstantRanges = push_constants; + err = vkCreatePipelineLayout(v->Device, &layout_info, v->Allocator, &g_PipelineLayout); + check_vk_result(err); + } + + VkPipelineShaderStageCreateInfo stage[2] = {}; + stage[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + stage[0].stage = VK_SHADER_STAGE_VERTEX_BIT; + stage[0].module = vert_module; + stage[0].pName = "main"; + stage[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + stage[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; + stage[1].module = frag_module; + stage[1].pName = "main"; + + VkVertexInputBindingDescription binding_desc[1] = {}; + binding_desc[0].stride = sizeof(ImDrawVert); + binding_desc[0].inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + + VkVertexInputAttributeDescription attribute_desc[3] = {}; + attribute_desc[0].location = 0; + attribute_desc[0].binding = binding_desc[0].binding; + attribute_desc[0].format = VK_FORMAT_R32G32_SFLOAT; + attribute_desc[0].offset = IM_OFFSETOF(ImDrawVert, pos); + attribute_desc[1].location = 1; + attribute_desc[1].binding = binding_desc[0].binding; + attribute_desc[1].format = VK_FORMAT_R32G32_SFLOAT; + attribute_desc[1].offset = IM_OFFSETOF(ImDrawVert, uv); + attribute_desc[2].location = 2; + attribute_desc[2].binding = binding_desc[0].binding; + attribute_desc[2].format = VK_FORMAT_R8G8B8A8_UNORM; + attribute_desc[2].offset = IM_OFFSETOF(ImDrawVert, col); + + VkPipelineVertexInputStateCreateInfo vertex_info = {}; + vertex_info.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertex_info.vertexBindingDescriptionCount = 1; + vertex_info.pVertexBindingDescriptions = binding_desc; + vertex_info.vertexAttributeDescriptionCount = 3; + vertex_info.pVertexAttributeDescriptions = attribute_desc; + + VkPipelineInputAssemblyStateCreateInfo ia_info = {}; + ia_info.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + ia_info.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + + VkPipelineViewportStateCreateInfo viewport_info = {}; + viewport_info.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewport_info.viewportCount = 1; + viewport_info.scissorCount = 1; + + VkPipelineRasterizationStateCreateInfo raster_info = {}; + raster_info.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + raster_info.polygonMode = VK_POLYGON_MODE_FILL; + raster_info.cullMode = VK_CULL_MODE_NONE; + raster_info.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + raster_info.lineWidth = 1.0f; + + VkPipelineMultisampleStateCreateInfo ms_info = {}; + ms_info.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + ms_info.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + VkPipelineColorBlendAttachmentState color_attachment[1] = {}; + color_attachment[0].blendEnable = VK_TRUE; + color_attachment[0].srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; + color_attachment[0].dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + color_attachment[0].colorBlendOp = VK_BLEND_OP_ADD; + color_attachment[0].srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + color_attachment[0].dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; + color_attachment[0].alphaBlendOp = VK_BLEND_OP_ADD; + color_attachment[0].colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + + VkPipelineDepthStencilStateCreateInfo depth_info = {}; + depth_info.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + + VkPipelineColorBlendStateCreateInfo blend_info = {}; + blend_info.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + blend_info.attachmentCount = 1; + blend_info.pAttachments = color_attachment; + + VkDynamicState dynamic_states[2] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; + VkPipelineDynamicStateCreateInfo dynamic_state = {}; + dynamic_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamic_state.dynamicStateCount = (uint32_t)IM_ARRAYSIZE(dynamic_states); + dynamic_state.pDynamicStates = dynamic_states; + + VkGraphicsPipelineCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + info.flags = g_PipelineCreateFlags; + info.stageCount = 2; + info.pStages = stage; + info.pVertexInputState = &vertex_info; + info.pInputAssemblyState = &ia_info; + info.pViewportState = &viewport_info; + info.pRasterizationState = &raster_info; + info.pMultisampleState = &ms_info; + info.pDepthStencilState = &depth_info; + info.pColorBlendState = &blend_info; + info.pDynamicState = &dynamic_state; + info.layout = g_PipelineLayout; + info.renderPass = g_RenderPass; + err = vkCreateGraphicsPipelines(v->Device, v->PipelineCache, 1, &info, v->Allocator, &g_Pipeline); + check_vk_result(err); + + vkDestroyShaderModule(v->Device, vert_module, v->Allocator); + vkDestroyShaderModule(v->Device, frag_module, v->Allocator); + + return true; +} + +void ImGui_ImplVulkan_DestroyFontUploadObjects() +{ + ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; + if (g_UploadBuffer) + { + vkDestroyBuffer(v->Device, g_UploadBuffer, v->Allocator); + g_UploadBuffer = VK_NULL_HANDLE; + } + if (g_UploadBufferMemory) + { + vkFreeMemory(v->Device, g_UploadBufferMemory, v->Allocator); + g_UploadBufferMemory = VK_NULL_HANDLE; + } +} + +void ImGui_ImplVulkan_DestroyDeviceObjects() +{ + ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; + ImGui_ImplVulkanH_DestroyWindowRenderBuffers(v->Device, &g_MainWindowRenderBuffers, v->Allocator); + ImGui_ImplVulkan_DestroyFontUploadObjects(); + + if (g_FontView) { vkDestroyImageView(v->Device, g_FontView, v->Allocator); g_FontView = VK_NULL_HANDLE; } + if (g_FontImage) { vkDestroyImage(v->Device, g_FontImage, v->Allocator); g_FontImage = VK_NULL_HANDLE; } + if (g_FontMemory) { vkFreeMemory(v->Device, g_FontMemory, v->Allocator); g_FontMemory = VK_NULL_HANDLE; } + if (g_FontSampler) { vkDestroySampler(v->Device, g_FontSampler, v->Allocator); g_FontSampler = VK_NULL_HANDLE; } + if (g_DescriptorSetLayout) { vkDestroyDescriptorSetLayout(v->Device, g_DescriptorSetLayout, v->Allocator); g_DescriptorSetLayout = VK_NULL_HANDLE; } + if (g_PipelineLayout) { vkDestroyPipelineLayout(v->Device, g_PipelineLayout, v->Allocator); g_PipelineLayout = VK_NULL_HANDLE; } + if (g_Pipeline) { vkDestroyPipeline(v->Device, g_Pipeline, v->Allocator); g_Pipeline = VK_NULL_HANDLE; } +} + +bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* info, VkRenderPass render_pass) +{ + // Setup back-end capabilities flags + ImGuiIO& io = ImGui::GetIO(); + io.BackendRendererName = "imgui_impl_vulkan"; + // io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. + + IM_ASSERT(info->Instance != VK_NULL_HANDLE); + IM_ASSERT(info->PhysicalDevice != VK_NULL_HANDLE); + IM_ASSERT(info->Device != VK_NULL_HANDLE); + IM_ASSERT(info->Queue != VK_NULL_HANDLE); + IM_ASSERT(info->DescriptorPool != VK_NULL_HANDLE); + IM_ASSERT(info->MinImageCount >= 2); + IM_ASSERT(info->ImageCount >= info->MinImageCount); + IM_ASSERT(render_pass != VK_NULL_HANDLE); + + g_VulkanInitInfo = *info; + g_RenderPass = render_pass; + ImGui_ImplVulkan_CreateDeviceObjects(); + + return true; +} + +ImTextureID ImGui_ImplVulkan_AddTexture(VkSampler sampler, VkImageView image_view, VkImageLayout image_layout) { + VkResult err; + + VkDescriptorSet descriptor_set; + // Create Descriptor Set: + { + VkDescriptorSetAllocateInfo alloc_info = {}; + alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + alloc_info.descriptorPool = g_VulkanInitInfo.DescriptorPool; + alloc_info.descriptorSetCount = 1; + alloc_info.pSetLayouts = &g_DescriptorSetLayout; + err = vkAllocateDescriptorSets(g_VulkanInitInfo.Device, &alloc_info, &descriptor_set); + check_vk_result(err); + } + + // Update the Descriptor Set: + { + VkDescriptorImageInfo desc_image[1] = {}; + desc_image[0].sampler = sampler; + desc_image[0].imageView = image_view; + desc_image[0].imageLayout = image_layout; + VkWriteDescriptorSet write_desc[1] = {}; + write_desc[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write_desc[0].dstSet = descriptor_set; + write_desc[0].descriptorCount = 1; + write_desc[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + write_desc[0].pImageInfo = desc_image; + vkUpdateDescriptorSets(g_VulkanInitInfo.Device, 1, write_desc, 0, NULL); + } + + return (ImTextureID)descriptor_set; +} + +void ImGui_ImplVulkan_Shutdown() +{ + ImGui_ImplVulkan_DestroyDeviceObjects(); +} + +void ImGui_ImplVulkan_NewFrame(VkCommandBuffer command_buffer, VkFramebuffer framebuffer, VkExtent2D& extend) +{ + auto& io = ImGui::GetIO(); + + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = g_RenderPass; + renderPassInfo.framebuffer = framebuffer; + renderPassInfo.renderArea.offset = {0, 0}; + renderPassInfo.renderArea.extent = extend; + renderPassInfo.clearValueCount = 0; + vkCmdBeginRenderPass(command_buffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); +} + +void ImGui_ImplVulkan_SetMinImageCount(uint32_t min_image_count) +{ + IM_ASSERT(min_image_count >= 2); + if (g_VulkanInitInfo.MinImageCount == min_image_count) + return; + + ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; + VkResult err = vkDeviceWaitIdle(v->Device); + check_vk_result(err); + ImGui_ImplVulkanH_DestroyWindowRenderBuffers(v->Device, &g_MainWindowRenderBuffers, v->Allocator); + g_VulkanInitInfo.MinImageCount = min_image_count; +} + + +//------------------------------------------------------------------------- +// Internal / Miscellaneous Vulkan Helpers +// (Used by example's main.cpp. Used by multi-viewport features. PROBABLY NOT used by your own app.) +//------------------------------------------------------------------------- +// You probably do NOT need to use or care about those functions. +// Those functions only exist because: +// 1) they facilitate the readability and maintenance of the multiple main.cpp examples files. +// 2) the upcoming multi-viewport feature will need them internally. +// Generally we avoid exposing any kind of superfluous high-level helpers in the bindings, +// but it is too much code to duplicate everywhere so we exceptionally expose them. +// +// Your engine/app will likely _already_ have code to setup all that stuff (swap chain, render pass, frame buffers, etc.). +// You may read this code to learn about Vulkan, but it is recommended you use you own custom tailored code to do equivalent work. +// (The ImGui_ImplVulkanH_XXX functions do not interact with any of the state used by the regular ImGui_ImplVulkan_XXX functions) +//------------------------------------------------------------------------- + +VkSurfaceFormatKHR ImGui_ImplVulkanH_SelectSurfaceFormat(VkPhysicalDevice physical_device, VkSurfaceKHR surface, const VkFormat* request_formats, int request_formats_count, VkColorSpaceKHR request_color_space) +{ + IM_ASSERT(request_formats != NULL); + IM_ASSERT(request_formats_count > 0); + + // Per Spec Format and View Format are expected to be the same unless VK_IMAGE_CREATE_MUTABLE_BIT was set at image creation + // Assuming that the default behavior is without setting this bit, there is no need for separate Swapchain image and image view format + // Additionally several new color spaces were introduced with Vulkan Spec v1.0.40, + // hence we must make sure that a format with the mostly available color space, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR, is found and used. + uint32_t avail_count; + vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &avail_count, NULL); + ImVector avail_format; + avail_format.resize((int)avail_count); + vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &avail_count, avail_format.Data); + + // First check if only one format, VK_FORMAT_UNDEFINED, is available, which would imply that any format is available + if (avail_count == 1) + { + if (avail_format[0].format == VK_FORMAT_UNDEFINED) + { + VkSurfaceFormatKHR ret; + ret.format = request_formats[0]; + ret.colorSpace = request_color_space; + return ret; + } + else + { + // No point in searching another format + return avail_format[0]; + } + } + else + { + // Request several formats, the first found will be used + for (int request_i = 0; request_i < request_formats_count; request_i++) + for (uint32_t avail_i = 0; avail_i < avail_count; avail_i++) + if (avail_format[avail_i].format == request_formats[request_i] && avail_format[avail_i].colorSpace == request_color_space) + return avail_format[avail_i]; + + // If none of the requested image formats could be found, use the first available + return avail_format[0]; + } +} + +VkPresentModeKHR ImGui_ImplVulkanH_SelectPresentMode(VkPhysicalDevice physical_device, VkSurfaceKHR surface, const VkPresentModeKHR* request_modes, int request_modes_count) +{ + IM_ASSERT(request_modes != NULL); + IM_ASSERT(request_modes_count > 0); + + // Request a certain mode and confirm that it is available. If not use VK_PRESENT_MODE_FIFO_KHR which is mandatory + uint32_t avail_count = 0; + vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, surface, &avail_count, NULL); + ImVector avail_modes; + avail_modes.resize((int)avail_count); + vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, surface, &avail_count, avail_modes.Data); + //for (uint32_t avail_i = 0; avail_i < avail_count; avail_i++) + // printf("[vulkan] avail_modes[%d] = %d\n", avail_i, avail_modes[avail_i]); + + for (int request_i = 0; request_i < request_modes_count; request_i++) + for (uint32_t avail_i = 0; avail_i < avail_count; avail_i++) + if (request_modes[request_i] == avail_modes[avail_i]) + return request_modes[request_i]; + + return VK_PRESENT_MODE_FIFO_KHR; // Always available +} + +void ImGui_ImplVulkanH_CreateWindowCommandBuffers(VkPhysicalDevice physical_device, VkDevice device, ImGui_ImplVulkanH_Window* wd, uint32_t queue_family, const VkAllocationCallbacks* allocator) +{ + IM_ASSERT(physical_device != VK_NULL_HANDLE && device != VK_NULL_HANDLE); + (void)physical_device; + (void)allocator; + + // Create Command Buffers + VkResult err; + for (uint32_t i = 0; i < wd->ImageCount; i++) + { + ImGui_ImplVulkanH_Frame* fd = &wd->Frames[i]; + ImGui_ImplVulkanH_FrameSemaphores* fsd = &wd->FrameSemaphores[i]; + { + VkCommandPoolCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + info.queueFamilyIndex = queue_family; + err = vkCreateCommandPool(device, &info, allocator, &fd->CommandPool); + check_vk_result(err); + } + { + VkCommandBufferAllocateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + info.commandPool = fd->CommandPool; + info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + info.commandBufferCount = 1; + err = vkAllocateCommandBuffers(device, &info, &fd->CommandBuffer); + check_vk_result(err); + } + { + VkFenceCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + info.flags = VK_FENCE_CREATE_SIGNALED_BIT; + err = vkCreateFence(device, &info, allocator, &fd->Fence); + check_vk_result(err); + } + { + VkSemaphoreCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + err = vkCreateSemaphore(device, &info, allocator, &fsd->ImageAcquiredSemaphore); + check_vk_result(err); + err = vkCreateSemaphore(device, &info, allocator, &fsd->RenderCompleteSemaphore); + check_vk_result(err); + } + } +} + +int ImGui_ImplVulkanH_GetMinImageCountFromPresentMode(VkPresentModeKHR present_mode) +{ + if (present_mode == VK_PRESENT_MODE_MAILBOX_KHR) + return 3; + if (present_mode == VK_PRESENT_MODE_FIFO_KHR || present_mode == VK_PRESENT_MODE_FIFO_RELAXED_KHR) + return 2; + if (present_mode == VK_PRESENT_MODE_IMMEDIATE_KHR) + return 1; + IM_ASSERT(0); + return 1; +} + +// Also destroy old swap chain and in-flight frames data, if any. +void ImGui_ImplVulkanH_CreateWindowSwapChain(VkPhysicalDevice physical_device, VkDevice device, ImGui_ImplVulkanH_Window* wd, const VkAllocationCallbacks* allocator, int w, int h, uint32_t min_image_count) +{ + VkResult err; + VkSwapchainKHR old_swapchain = wd->Swapchain; + err = vkDeviceWaitIdle(device); + check_vk_result(err); + + // We don't use ImGui_ImplVulkanH_DestroyWindow() because we want to preserve the old swapchain to create the new one. + // Destroy old Framebuffer + for (uint32_t i = 0; i < wd->ImageCount; i++) + { + ImGui_ImplVulkanH_DestroyFrame(device, &wd->Frames[i], allocator); + ImGui_ImplVulkanH_DestroyFrameSemaphores(device, &wd->FrameSemaphores[i], allocator); + } + IM_FREE(wd->Frames); + IM_FREE(wd->FrameSemaphores); + wd->Frames = NULL; + wd->FrameSemaphores = NULL; + wd->ImageCount = 0; + if (wd->RenderPass) + vkDestroyRenderPass(device, wd->RenderPass, allocator); + + // If min image count was not specified, request different count of images dependent on selected present mode + if (min_image_count == 0) + min_image_count = ImGui_ImplVulkanH_GetMinImageCountFromPresentMode(wd->PresentMode); + + // Create Swapchain + { + VkSwapchainCreateInfoKHR info = {}; + info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + info.surface = wd->Surface; + info.minImageCount = min_image_count; + info.imageFormat = wd->SurfaceFormat.format; + info.imageColorSpace = wd->SurfaceFormat.colorSpace; + info.imageArrayLayers = 1; + info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; // Assume that graphics family == present family + info.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; + info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + info.presentMode = wd->PresentMode; + info.clipped = VK_TRUE; + info.oldSwapchain = old_swapchain; + VkSurfaceCapabilitiesKHR cap; + err = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_device, wd->Surface, &cap); + check_vk_result(err); + if (info.minImageCount < cap.minImageCount) + info.minImageCount = cap.minImageCount; + else if (cap.maxImageCount != 0 && info.minImageCount > cap.maxImageCount) + info.minImageCount = cap.maxImageCount; + + if (cap.currentExtent.width == 0xffffffff) + { + info.imageExtent.width = wd->Width = w; + info.imageExtent.height = wd->Height = h; + } + else + { + info.imageExtent.width = wd->Width = cap.currentExtent.width; + info.imageExtent.height = wd->Height = cap.currentExtent.height; + } + err = vkCreateSwapchainKHR(device, &info, allocator, &wd->Swapchain); + check_vk_result(err); + err = vkGetSwapchainImagesKHR(device, wd->Swapchain, &wd->ImageCount, NULL); + check_vk_result(err); + VkImage backbuffers[16] = {}; + IM_ASSERT(wd->ImageCount >= min_image_count); + IM_ASSERT(wd->ImageCount < IM_ARRAYSIZE(backbuffers)); + err = vkGetSwapchainImagesKHR(device, wd->Swapchain, &wd->ImageCount, backbuffers); + check_vk_result(err); + + IM_ASSERT(wd->Frames == NULL); + wd->Frames = (ImGui_ImplVulkanH_Frame*)IM_ALLOC(sizeof(ImGui_ImplVulkanH_Frame) * wd->ImageCount); + wd->FrameSemaphores = (ImGui_ImplVulkanH_FrameSemaphores*)IM_ALLOC(sizeof(ImGui_ImplVulkanH_FrameSemaphores) * wd->ImageCount); + memset(wd->Frames, 0, sizeof(wd->Frames[0]) * wd->ImageCount); + memset(wd->FrameSemaphores, 0, sizeof(wd->FrameSemaphores[0]) * wd->ImageCount); + for (uint32_t i = 0; i < wd->ImageCount; i++) + wd->Frames[i].Backbuffer = backbuffers[i]; + } + if (old_swapchain) + vkDestroySwapchainKHR(device, old_swapchain, allocator); + + // Create the Render Pass + { + VkAttachmentDescription attachment = {}; + attachment.format = wd->SurfaceFormat.format; + attachment.samples = VK_SAMPLE_COUNT_1_BIT; + attachment.loadOp = wd->ClearEnable ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD;// VK_ATTACHMENT_LOAD_OP_DONT_CARE; + attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + attachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + VkAttachmentReference color_attachment = {}; + color_attachment.attachment = 0; + color_attachment.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + VkSubpassDescription subpass = {}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &color_attachment; + VkSubpassDependency dependency = {}; + dependency.srcSubpass = VK_SUBPASS_EXTERNAL; + dependency.dstSubpass = 0; + dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dependency.srcAccessMask = 0; + dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + VkRenderPassCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + info.attachmentCount = 1; + info.pAttachments = &attachment; + info.subpassCount = 1; + info.pSubpasses = &subpass; + info.dependencyCount = 1; + info.pDependencies = &dependency; + err = vkCreateRenderPass(device, &info, allocator, &wd->RenderPass); + check_vk_result(err); + } + + // Create The Image Views + { + VkImageViewCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + info.viewType = VK_IMAGE_VIEW_TYPE_2D; + info.format = wd->SurfaceFormat.format; + info.components.r = VK_COMPONENT_SWIZZLE_R; + info.components.g = VK_COMPONENT_SWIZZLE_G; + info.components.b = VK_COMPONENT_SWIZZLE_B; + info.components.a = VK_COMPONENT_SWIZZLE_A; + VkImageSubresourceRange image_range = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; + info.subresourceRange = image_range; + for (uint32_t i = 0; i < wd->ImageCount; i++) + { + ImGui_ImplVulkanH_Frame* fd = &wd->Frames[i]; + info.image = fd->Backbuffer; + err = vkCreateImageView(device, &info, allocator, &fd->BackbufferView); + check_vk_result(err); + } + } + + // Create Framebuffer + { + VkImageView attachment[1]; + VkFramebufferCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + info.renderPass = wd->RenderPass; + info.attachmentCount = 1; + info.pAttachments = attachment; + info.width = wd->Width; + info.height = wd->Height; + info.layers = 1; + for (uint32_t i = 0; i < wd->ImageCount; i++) + { + ImGui_ImplVulkanH_Frame* fd = &wd->Frames[i]; + attachment[0] = fd->BackbufferView; + err = vkCreateFramebuffer(device, &info, allocator, &fd->Framebuffer); + check_vk_result(err); + } + } +} + +void ImGui_ImplVulkanH_CreateWindow(VkInstance instance, VkPhysicalDevice physical_device, VkDevice device, ImGui_ImplVulkanH_Window* wd, uint32_t queue_family, const VkAllocationCallbacks* allocator, int width, int height, uint32_t min_image_count) +{ + (void)instance; + ImGui_ImplVulkanH_CreateWindowSwapChain(physical_device, device, wd, allocator, width, height, min_image_count); + ImGui_ImplVulkanH_CreateWindowCommandBuffers(physical_device, device, wd, queue_family, allocator); +} + +void ImGui_ImplVulkanH_DestroyWindow(VkInstance instance, VkDevice device, ImGui_ImplVulkanH_Window* wd, const VkAllocationCallbacks* allocator) +{ + vkDeviceWaitIdle(device); // FIXME: We could wait on the Queue if we had the queue in wd-> (otherwise VulkanH functions can't use globals) + //vkQueueWaitIdle(g_Queue); + + for (uint32_t i = 0; i < wd->ImageCount; i++) + { + ImGui_ImplVulkanH_DestroyFrame(device, &wd->Frames[i], allocator); + ImGui_ImplVulkanH_DestroyFrameSemaphores(device, &wd->FrameSemaphores[i], allocator); + } + IM_FREE(wd->Frames); + IM_FREE(wd->FrameSemaphores); + wd->Frames = NULL; + wd->FrameSemaphores = NULL; + vkDestroyRenderPass(device, wd->RenderPass, allocator); + vkDestroySwapchainKHR(device, wd->Swapchain, allocator); + vkDestroySurfaceKHR(instance, wd->Surface, allocator); + + *wd = ImGui_ImplVulkanH_Window(); +} + +void ImGui_ImplVulkanH_DestroyFrame(VkDevice device, ImGui_ImplVulkanH_Frame* fd, const VkAllocationCallbacks* allocator) +{ + vkDestroyFence(device, fd->Fence, allocator); + vkFreeCommandBuffers(device, fd->CommandPool, 1, &fd->CommandBuffer); + vkDestroyCommandPool(device, fd->CommandPool, allocator); + fd->Fence = VK_NULL_HANDLE; + fd->CommandBuffer = VK_NULL_HANDLE; + fd->CommandPool = VK_NULL_HANDLE; + + vkDestroyImageView(device, fd->BackbufferView, allocator); + vkDestroyFramebuffer(device, fd->Framebuffer, allocator); +} + +void ImGui_ImplVulkanH_DestroyFrameSemaphores(VkDevice device, ImGui_ImplVulkanH_FrameSemaphores* fsd, const VkAllocationCallbacks* allocator) +{ + vkDestroySemaphore(device, fsd->ImageAcquiredSemaphore, allocator); + vkDestroySemaphore(device, fsd->RenderCompleteSemaphore, allocator); + fsd->ImageAcquiredSemaphore = fsd->RenderCompleteSemaphore = VK_NULL_HANDLE; +} + +void ImGui_ImplVulkanH_DestroyFrameRenderBuffers(VkDevice device, ImGui_ImplVulkanH_FrameRenderBuffers* buffers, const VkAllocationCallbacks* allocator) +{ + if (buffers->VertexBuffer) { vkDestroyBuffer(device, buffers->VertexBuffer, allocator); buffers->VertexBuffer = VK_NULL_HANDLE; } + if (buffers->VertexBufferMemory) { vkFreeMemory(device, buffers->VertexBufferMemory, allocator); buffers->VertexBufferMemory = VK_NULL_HANDLE; } + if (buffers->IndexBuffer) { vkDestroyBuffer(device, buffers->IndexBuffer, allocator); buffers->IndexBuffer = VK_NULL_HANDLE; } + if (buffers->IndexBufferMemory) { vkFreeMemory(device, buffers->IndexBufferMemory, allocator); buffers->IndexBufferMemory = VK_NULL_HANDLE; } + buffers->VertexBufferSize = 0; + buffers->IndexBufferSize = 0; +} + +void ImGui_ImplVulkanH_DestroyWindowRenderBuffers(VkDevice device, ImGui_ImplVulkanH_WindowRenderBuffers* buffers, const VkAllocationCallbacks* allocator) +{ + for (uint32_t n = 0; n < buffers->Count; n++) + ImGui_ImplVulkanH_DestroyFrameRenderBuffers(device, &buffers->FrameRenderBuffers[n], allocator); + IM_FREE(buffers->FrameRenderBuffers); + buffers->FrameRenderBuffers = NULL; + buffers->Index = 0; + buffers->Count = 0; +} + +ImTextureID ImGui_ImplVulkan_GenerateTexture(VkCommandBuffer commandBuffer, const std::vector& data, const Vector2i& size) +{ + try + { + auto* texture = new ImGuiTexture(); + + VkResult err; + // Create the Image View: + { + VkImageCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + info.imageType = VK_IMAGE_TYPE_2D; + info.format = VK_FORMAT_R8G8B8A8_UNORM; + info.extent.width = size.x; + info.extent.height = size.y; + info.extent.depth = 1; + info.mipLevels = 1; + info.arrayLayers = 1; + info.samples = VK_SAMPLE_COUNT_1_BIT; + info.tiling = VK_IMAGE_TILING_OPTIMAL; + info.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + err = vkCreateImage(g_VulkanInitInfo.Device, &info, g_VulkanInitInfo.Allocator, &texture->image); + check_vk_result(err); + VkMemoryRequirements req; + vkGetImageMemoryRequirements(g_VulkanInitInfo.Device, texture->image, &req); + VkMemoryAllocateInfo alloc_info = {}; + alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + alloc_info.allocationSize = req.size; + alloc_info.memoryTypeIndex = ImGui_ImplVulkan_MemoryType(VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, req.memoryTypeBits); + err = vkAllocateMemory(g_VulkanInitInfo.Device, &alloc_info, g_VulkanInitInfo.Allocator, &texture->memory); + check_vk_result(err); + err = vkBindImageMemory(g_VulkanInitInfo.Device, texture->image, texture->memory, 0); + check_vk_result(err); + } + + // Create the Image View: + { + VkImageViewCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + info.image = texture->image; + info.viewType = VK_IMAGE_VIEW_TYPE_2D; + info.format = VK_FORMAT_R8G8B8A8_UNORM; + info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + info.subresourceRange.levelCount = 1; + info.subresourceRange.layerCount = 1; + err = vkCreateImageView(g_VulkanInitInfo.Device, &info, g_VulkanInitInfo.Allocator, &texture->view); + check_vk_result(err); + } + + // create sampler + { + VkSamplerCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + info.magFilter = VK_FILTER_LINEAR; + info.minFilter = VK_FILTER_LINEAR; + info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + info.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; + info.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; + info.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; + info.minLod = -1000; + info.maxLod = 1000; + info.maxAnisotropy = 1.0f; + err = vkCreateSampler(g_VulkanInitInfo.Device, &info, g_VulkanInitInfo.Allocator, &texture->sampler); + check_vk_result(err); + } + + texture->descriptor_set = (VkDescriptorSet)ImGui_ImplVulkan_AddTexture(texture->sampler, texture->view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + + // Create the Upload Buffer: + { + VkBufferCreateInfo buffer_info = {}; + buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + buffer_info.size = data.size(); + buffer_info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + err = vkCreateBuffer(g_VulkanInitInfo.Device, &buffer_info, g_VulkanInitInfo.Allocator, &texture->uploadBuffer); + check_vk_result(err); + VkMemoryRequirements req; + vkGetBufferMemoryRequirements(g_VulkanInitInfo.Device, texture->uploadBuffer, &req); + VkMemoryAllocateInfo alloc_info = {}; + alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + alloc_info.allocationSize = req.size; + alloc_info.memoryTypeIndex = ImGui_ImplVulkan_MemoryType(VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, req.memoryTypeBits); + err = vkAllocateMemory(g_VulkanInitInfo.Device, &alloc_info, g_VulkanInitInfo.Allocator, &texture->uploadBufferMemory); + check_vk_result(err); + err = vkBindBufferMemory(g_VulkanInitInfo.Device, texture->uploadBuffer, texture->uploadBufferMemory, 0); + check_vk_result(err); + } + + // Upload to Buffer: + { + cemu_assert_debug(data.size() != 0); + char* map = NULL; + err = vkMapMemory(g_VulkanInitInfo.Device, texture->uploadBufferMemory, 0, data.size(), 0, (void**)(&map)); + check_vk_result(err); + memcpy(map, data.data(), data.size()); + VkMappedMemoryRange range[1] = {}; + range[0].sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; + range[0].memory = texture->uploadBufferMemory; + range[0].size = data.size(); + err = vkFlushMappedMemoryRanges(g_VulkanInitInfo.Device, 1, range); + check_vk_result(err); + vkUnmapMemory(g_VulkanInitInfo.Device, texture->uploadBufferMemory); + } + + // Copy to Image: + { + VkImageMemoryBarrier copy_barrier[1] = {}; + copy_barrier[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + copy_barrier[0].dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + copy_barrier[0].oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + copy_barrier[0].newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + copy_barrier[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + copy_barrier[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + copy_barrier[0].image = texture->image; + copy_barrier[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + copy_barrier[0].subresourceRange.levelCount = 1; + copy_barrier[0].subresourceRange.layerCount = 1; + vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 0, NULL, 1, copy_barrier); + + VkBufferImageCopy region = {}; + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.imageSubresource.layerCount = 1; + region.imageExtent.width = size.x; + region.imageExtent.height = size.y; + region.imageExtent.depth = 1; + vkCmdCopyBufferToImage(commandBuffer, texture->uploadBuffer, texture->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + + VkImageMemoryBarrier use_barrier[1] = {}; + use_barrier[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + use_barrier[0].srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + use_barrier[0].dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + use_barrier[0].oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + use_barrier[0].newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + use_barrier[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + use_barrier[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + use_barrier[0].image = texture->image; + use_barrier[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + use_barrier[0].subresourceRange.levelCount = 1; + use_barrier[0].subresourceRange.layerCount = 1; + vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, NULL, 0, NULL, 1, use_barrier); + } + + return (ImTextureID)texture; + } + catch (const std::exception & ex) + { + forceLog_printf("can't generate imgui texture: %s", ex.what()); + return nullptr; + } +} + +void ImGui_ImplVulkan_DeleteTexture(ImTextureID id) +{ + auto textureInfo = (ImGuiTexture*)id; + + vkFreeDescriptorSets(g_VulkanInitInfo.Device, g_VulkanInitInfo.DescriptorPool, 1, &textureInfo->descriptor_set); + vkDestroySampler(g_VulkanInitInfo.Device, textureInfo->sampler, nullptr); + vkDestroyImageView(g_VulkanInitInfo.Device, textureInfo->view, nullptr); + vkDestroyImage(g_VulkanInitInfo.Device, textureInfo->image, nullptr); + vkDestroyBuffer(g_VulkanInitInfo.Device, textureInfo->uploadBuffer, nullptr); + vkFreeMemory(g_VulkanInitInfo.Device, textureInfo->memory, nullptr); + vkFreeMemory(g_VulkanInitInfo.Device, textureInfo->uploadBufferMemory, nullptr); + + *textureInfo = {}; +} diff --git a/src/imgui/imgui_impl_vulkan.h b/src/imgui/imgui_impl_vulkan.h new file mode 100644 index 00000000..c4af49c5 --- /dev/null +++ b/src/imgui/imgui_impl_vulkan.h @@ -0,0 +1,138 @@ +// dear imgui: Renderer for Vulkan +// This needs to be used along with a Platform Binding (e.g. GLFW, SDL, Win32, custom..) + +// Implemented features: +// [X] Renderer: Support for large meshes (64k+ vertices) with 16-bits indices. +// Missing features: +// [ ] Renderer: User texture binding. Changes of ImTextureID aren't supported by this binding! See https://github.com/ocornut/imgui/pull/914 + +// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. +// If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp. +// https://github.com/ocornut/imgui + +// The aim of imgui_impl_vulkan.h/.cpp is to be usable in your engine without any modification. +// IF YOU FEEL YOU NEED TO MAKE ANY CHANGE TO THIS CODE, please share them and your feedback at https://github.com/ocornut/imgui/ + +// Important note to the reader who wish to integrate imgui_impl_vulkan.cpp/.h in their own engine/app. +// - Common ImGui_ImplVulkan_XXX functions and structures are used to interface with imgui_impl_vulkan.cpp/.h. +// You will use those if you want to use this rendering back-end in your engine/app. +// - Helper ImGui_ImplVulkanH_XXX functions and structures are only used by this example (main.cpp) and by +// the back-end itself (imgui_impl_vulkan.cpp), but should PROBABLY NOT be used by your own engine/app code. +// Read comments in imgui_impl_vulkan.h. + +#pragma once + +#include +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" +#include "util/math/vector2.h" + +// Initialization data, for ImGui_ImplVulkan_Init() +// [Please zero-clear before use!] +struct ImGui_ImplVulkan_InitInfo +{ + VkInstance Instance; + VkPhysicalDevice PhysicalDevice; + VkDevice Device; + uint32_t QueueFamily; + VkQueue Queue; + VkPipelineCache PipelineCache; + VkDescriptorPool DescriptorPool; + uint32_t MinImageCount; // >= 2 + uint32_t ImageCount; // >= MinImageCount + const VkAllocationCallbacks* Allocator; + void (*CheckVkResultFn)(VkResult err); +}; + +// Called by user code +IMGUI_IMPL_API bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* info, VkRenderPass render_pass); +IMGUI_IMPL_API void ImGui_ImplVulkan_Shutdown(); +IMGUI_IMPL_API void ImGui_ImplVulkan_NewFrame(VkCommandBuffer command_buffer, VkFramebuffer framebuffer, VkExtent2D& extend); +IMGUI_IMPL_API void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer command_buffer); +IMGUI_IMPL_API bool ImGui_ImplVulkan_CreateFontsTexture(VkCommandBuffer command_buffer); +IMGUI_IMPL_API void ImGui_ImplVulkan_DestroyFontsTexture(); +IMGUI_IMPL_API void ImGui_ImplVulkan_DestroyFontUploadObjects(); +IMGUI_IMPL_API void ImGui_ImplVulkan_SetMinImageCount(uint32_t min_image_count); // To override MinImageCount after initialization (e.g. if swap chain is recreated) +// +//IMGUI_IMPL_API bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* info, VkRenderPass render_pass); +//IMGUI_IMPL_API void ImGui_ImplVulkan_Shutdown(); +//IMGUI_IMPL_API void ImGui_ImplVulkan_NewFrame(); +//IMGUI_IMPL_API void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer command_buffer); +//IMGUI_IMPL_API bool ImGui_ImplVulkan_CreateFontsTexture(VkCommandBuffer command_buffer); +//IMGUI_IMPL_API void ImGui_ImplVulkan_InvalidateFontUploadObjects(); +IMGUI_IMPL_API ImTextureID ImGui_ImplVulkan_AddTexture(VkSampler sampler, VkImageView image_view, VkImageLayout image_layout); +IMGUI_IMPL_API ImTextureID ImGui_ImplVulkan_GenerateTexture(VkCommandBuffer commandBuffer, const std::vector& data, const Vector2i& size); +IMGUI_IMPL_API void ImGui_ImplVulkan_DeleteTexture(ImTextureID id); + + +//------------------------------------------------------------------------- +// Internal / Miscellaneous Vulkan Helpers +// (Used by example's main.cpp. Used by multi-viewport features. PROBABLY NOT used by your own engine/app.) +//------------------------------------------------------------------------- +// You probably do NOT need to use or care about those functions. +// Those functions only exist because: +// 1) they facilitate the readability and maintenance of the multiple main.cpp examples files. +// 2) the upcoming multi-viewport feature will need them internally. +// Generally we avoid exposing any kind of superfluous high-level helpers in the bindings, +// but it is too much code to duplicate everywhere so we exceptionally expose them. +// +// Your engine/app will likely _already_ have code to setup all that stuff (swap chain, render pass, frame buffers, etc.). +// You may read this code to learn about Vulkan, but it is recommended you use you own custom tailored code to do equivalent work. +// (The ImGui_ImplVulkanH_XXX functions do not interact with any of the state used by the regular ImGui_ImplVulkan_XXX functions) +//------------------------------------------------------------------------- + +struct ImGui_ImplVulkanH_Frame; +struct ImGui_ImplVulkanH_Window; + +// Helpers +IMGUI_IMPL_API void ImGui_ImplVulkanH_CreateWindow(VkInstance instance, VkPhysicalDevice physical_device, VkDevice device, ImGui_ImplVulkanH_Window* wnd, uint32_t queue_family, const VkAllocationCallbacks* allocator, int w, int h, uint32_t min_image_count); +IMGUI_IMPL_API void ImGui_ImplVulkanH_DestroyWindow(VkInstance instance, VkDevice device, ImGui_ImplVulkanH_Window* wnd, const VkAllocationCallbacks* allocator); +IMGUI_IMPL_API VkSurfaceFormatKHR ImGui_ImplVulkanH_SelectSurfaceFormat(VkPhysicalDevice physical_device, VkSurfaceKHR surface, const VkFormat* request_formats, int request_formats_count, VkColorSpaceKHR request_color_space); +IMGUI_IMPL_API VkPresentModeKHR ImGui_ImplVulkanH_SelectPresentMode(VkPhysicalDevice physical_device, VkSurfaceKHR surface, const VkPresentModeKHR* request_modes, int request_modes_count); +IMGUI_IMPL_API int ImGui_ImplVulkanH_GetMinImageCountFromPresentMode(VkPresentModeKHR present_mode); + +// Helper structure to hold the data needed by one rendering frame +// (Used by example's main.cpp. Used by multi-viewport features. Probably NOT used by your own engine/app.) +// [Please zero-clear before use!] +struct ImGui_ImplVulkanH_Frame +{ + VkCommandPool CommandPool; + VkCommandBuffer CommandBuffer; + VkFence Fence; + VkImage Backbuffer; + VkImageView BackbufferView; + VkFramebuffer Framebuffer; +}; + +struct ImGui_ImplVulkanH_FrameSemaphores +{ + VkSemaphore ImageAcquiredSemaphore; + VkSemaphore RenderCompleteSemaphore; +}; + +// Helper structure to hold the data needed by one rendering context into one OS window +// (Used by example's main.cpp. Used by multi-viewport features. Probably NOT used by your own engine/app.) +struct ImGui_ImplVulkanH_Window +{ + int Width; + int Height; + VkSwapchainKHR Swapchain; + VkSurfaceKHR Surface; + VkSurfaceFormatKHR SurfaceFormat; + VkPresentModeKHR PresentMode; + VkRenderPass RenderPass; + bool ClearEnable; + VkClearValue ClearValue; + uint32_t FrameIndex; // Current frame being rendered to (0 <= FrameIndex < FrameInFlightCount) + uint32_t ImageCount; // Number of simultaneous in-flight frames (returned by vkGetSwapchainImagesKHR, usually derived from min_image_count) + uint32_t SemaphoreIndex; // Current set of swapchain wait semaphores we're using (needs to be distinct from per frame data) + ImGui_ImplVulkanH_Frame* Frames; + ImGui_ImplVulkanH_FrameSemaphores* FrameSemaphores; + + ImGui_ImplVulkanH_Window() + { + memset(this, 0, sizeof(*this)); + PresentMode = VK_PRESENT_MODE_MAX_ENUM_KHR; + ClearEnable = true; + } +}; + diff --git a/src/input/CMakeLists.txt b/src/input/CMakeLists.txt new file mode 100644 index 00000000..cbd86a32 --- /dev/null +++ b/src/input/CMakeLists.txt @@ -0,0 +1,98 @@ +project(CemuInput) + +include_directories(".") + +add_library(CemuInput +InputManager.cpp +InputManager.h +ControllerFactory.cpp +ControllerFactory.h +api/ControllerState.h +api/Controller.cpp +api/Controller.h +api/ControllerState.cpp +api/InputAPI.h +api/ControllerProvider.h +emulated/ProController.cpp +emulated/EmulatedController.h +emulated/EmulatedController.cpp +emulated/ProController.h +emulated/WPADController.cpp +emulated/WPADController.h +emulated/WiimoteController.h +emulated/VPADController.cpp +emulated/WiimoteController.cpp +emulated/VPADController.h +emulated/ClassicController.cpp +emulated/ClassicController.h +) + +set_property(TARGET CemuInput PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + +# SDL +target_sources(CemuInput PRIVATE +api/SDL/SDLController.cpp +api/SDL/SDLControllerProvider.cpp +api/SDL/SDLController.h +api/SDL/SDLControllerProvider.h +) + +# DSU +target_sources(CemuInput PRIVATE +api/DSU/DSUController.h +api/DSU/DSUControllerProvider.cpp +api/DSU/DSUController.cpp +api/DSU/DSUControllerProvider.h +api/DSU/DSUMessages.h +api/DSU/DSUMessages.cpp +) + +# Keyboard controller +target_sources(CemuInput PRIVATE +api/Keyboard/KeyboardControllerProvider.h +api/Keyboard/KeyboardControllerProvider.cpp +api/Keyboard/KeyboardController.cpp +api/Keyboard/KeyboardController.h +) + +# Native gamecube +target_sources(CemuInput PRIVATE +api/GameCube/GameCubeController.cpp +api/GameCube/GameCubeControllerProvider.h +api/GameCube/GameCubeControllerProvider.cpp +api/GameCube/GameCubeController.h +) + +if(WIN32) +# Native wiimote (Win32 only for now) +target_sources(CemuInput PRIVATE +api/Wiimote/WiimoteControllerProvider.h +api/Wiimote/windows/WinWiimoteDevice.cpp +api/Wiimote/windows/WinWiimoteDevice.h +api/Wiimote/WiimoteControllerProvider.cpp +api/Wiimote/WiimoteMessages.h +api/Wiimote/NativeWiimoteController.h +api/Wiimote/NativeWiimoteController.cpp +api/Wiimote/WiimoteDevice.h +) + +# XInput +target_sources(CemuInput PRIVATE +api/XInput/XInputControllerProvider.cpp +api/XInput/XInputControllerProvider.h +api/XInput/XInputController.cpp +api/XInput/XInputController.h +) + +# DirectInput +target_sources(CemuInput PRIVATE +api/DirectInput/DirectInputControllerProvider.cpp +api/DirectInput/DirectInputController.h +api/DirectInput/DirectInputControllerProvider.h +api/DirectInput/DirectInputController.cpp +) +endif() + +target_precompile_headers(CemuInput PRIVATE ../Common/precompiled.h) + +target_include_directories(CemuInput PRIVATE ../) diff --git a/src/input/ControllerFactory.cpp b/src/input/ControllerFactory.cpp new file mode 100644 index 00000000..0facdee0 --- /dev/null +++ b/src/input/ControllerFactory.cpp @@ -0,0 +1,180 @@ +#include "input/ControllerFactory.h" + +#include "input/emulated/VPADController.h" +#include "input/emulated/ProController.h" +#include "input/emulated/ClassicController.h" +#include "input/emulated/WiimoteController.h" + +#include "input/api/SDL/SDLController.h" +#include "input/api/Keyboard/KeyboardController.h" +#include "input/api/DSU/DSUController.h" +#include "input/api/GameCube/GameCubeController.h" + +#if BOOST_OS_WINDOWS +#include "input/api/XInput/XInputController.h" +#include "input/api/DirectInput/DirectInputController.h" +#endif + +#if HAS_WIIMOTE +#include "input/api/Wiimote/NativeWiimoteController.h" +#endif + +ControllerPtr ControllerFactory::CreateController(InputAPI::Type api, std::string_view uuid, + std::string_view display_name) +{ + switch (api) + { +#if HAS_KEYBOARD + case InputAPI::Keyboard: + return std::make_shared(); +#endif +#if HAS_DIRECTINPUT + case InputAPI::DirectInput: + { + GUID guid; + // Workaround for mouse2joystick users, which has 0 as it's uuid in it's profile and counts on Cemu applying it to the first directinput controller. GUIDFromString also doesn't allow for invalid uuids either. + if (uuid == "0") + { + const auto provider = InputManager::instance().get_api_provider(InputAPI::DirectInput); + const auto controllers = provider->get_controllers(); + if (controllers.empty()) + throw std::invalid_argument(fmt::format( + "can't apply non-uuid-specific directinput profile when no controllers are available")); + if (!GUIDFromString(controllers.front()->uuid().c_str(), guid)) + throw std::invalid_argument(fmt::format("invalid guid format: {}", uuid)); + } + else + { + if (!GUIDFromString(uuid.data(), guid)) + throw std::invalid_argument(fmt::format("invalid guid format: {}", uuid)); + } + + return std::make_shared(guid); + } +#endif +#if HAS_XINPUT + case InputAPI::XInput: + { + const auto index = ConvertString(uuid); + return std::make_shared(index); + } +#endif +#if HAS_SDL + case InputAPI::SDLController: + { + // diid_guid + const auto index = uuid.find_first_of('_'); + if (index == std::string_view::npos) + throw std::invalid_argument(fmt::format("invalid sdl uuid format: {}", uuid)); + + const auto guid_index = ConvertString(uuid.substr(0, index)); + const auto guid = SDL_JoystickGetGUIDFromString(std::string{uuid.substr(index + 1)}.c_str()); + + if (display_name.empty()) + return std::make_shared(guid, guid_index); + else + return std::make_shared(guid, guid_index, display_name); + } +#endif +#if HAS_DSU + case InputAPI::DSUClient: + { + const auto index = ConvertString(uuid); + return std::make_shared(index); + } +#endif +#if HAS_GAMECUBE + case InputAPI::GameCube: + { + const auto index = uuid.find_first_of('_'); + if (index == std::string_view::npos) + throw std::invalid_argument(fmt::format("invalid gamecube uuid format: {}", uuid)); + + const auto adapter = ConvertString(uuid.substr(0, index)); + const auto controller_index = ConvertString(uuid.substr(index + 1)); + return std::make_shared(adapter, controller_index); + } +#endif +#if HAS_WIIMOTE + case InputAPI::Wiimote: + { + const auto index = ConvertString(uuid); + return std::make_shared(index); + } +#endif + default: + throw std::invalid_argument(fmt::format("unhandled controller api: {}", api)); + } + /* + case InputAPI::WGIGamepad: break; + case InputAPI::WGIRawController: break; + */ +} + +EmulatedControllerPtr +ControllerFactory::CreateEmulatedController(size_t player_index, EmulatedController::Type type) +{ + switch (type) + { + case EmulatedController::Type::VPAD: + return std::make_shared(player_index); + case EmulatedController::Type::Pro: + return std::make_shared(player_index); + case EmulatedController::Type::Classic: + return std::make_shared(player_index); + case EmulatedController::Type::Wiimote: + return std::make_shared(player_index); + default: + throw std::runtime_error(fmt::format("unknown emulated controller type: {}", type)); + } +} + +ControllerProviderPtr ControllerFactory::CreateControllerProvider(InputAPI::Type api, const ControllerProviderSettings& settings) +{ + switch (api) + { +#if HAS_KEYBOARD + case InputAPI::Keyboard: + return std::make_shared(); +#endif +#if HAS_SDL + case InputAPI::SDLController: + return std::make_shared(); +#endif +#if HAS_XINPUT + case InputAPI::XInput: + return std::make_shared(); +#endif +#if HAS_DIRECTINPUT + case InputAPI::DirectInput: + return std::make_shared(); +#endif +#if HAS_DSU + case InputAPI::DSUClient: + { + try + { + const auto& dsu_settings = dynamic_cast(settings); + return std::make_shared(dsu_settings); + } + catch (const std::bad_cast&) + { + cemuLog_force("failing to cast ControllerProviderSettings class to DSUControllerProvider"); + return std::make_shared(); + } + } + +#endif +#if HAS_GAMECUBE + case InputAPI::GameCube: + return std::make_shared(); +#endif +#if HAS_WIIMOTE + case InputAPI::Wiimote: + return std::make_shared(); +#endif + default: + cemu_assert_debug(false); + return {}; + } +} diff --git a/src/input/ControllerFactory.h b/src/input/ControllerFactory.h new file mode 100644 index 00000000..98688a47 --- /dev/null +++ b/src/input/ControllerFactory.h @@ -0,0 +1,13 @@ +#pragma once + +#include "input/api/InputAPI.h" +#include "input/api/Controller.h" +#include "input/emulated/EmulatedController.h" + +class ControllerFactory +{ +public: + static ControllerPtr CreateController(InputAPI::Type api, std::string_view uuid, std::string_view display_name); + static EmulatedControllerPtr CreateEmulatedController(size_t player_index, EmulatedController::Type type); + static ControllerProviderPtr CreateControllerProvider(InputAPI::Type api, const ControllerProviderSettings& settings); +}; diff --git a/src/input/InputManager.cpp b/src/input/InputManager.cpp new file mode 100644 index 00000000..4ae43ce3 --- /dev/null +++ b/src/input/InputManager.cpp @@ -0,0 +1,954 @@ +#include "input/InputManager.h" +#include "config/ActiveSettings.h" +#include "input/ControllerFactory.h" +#include +#include +#include "Cafe/GameProfile/GameProfile.h" +#include "util/EventService.h" + +InputManager::InputManager() +{ + /* + auto create_provider = [] + template + () + { + static_assert(std::is_base_of_v); + try + { + auto controller = std::make_shared(); + m_api_available[controller->api()] = controller; + } + catch (const std::exception& ex) + { + cemuLog_force(ex.what()); + } + } + */ +#if HAS_KEYBOARD + create_provider(); +#endif +#if HAS_SDL + create_provider(); +#endif +#if HAS_XINPUT + create_provider(); +#endif +#if HAS_DIRECTINPUT + create_provider(); +#endif +#if HAS_DSU + create_provider(); +#endif +#if HAS_GAMECUBE + create_provider(); +#endif +#if HAS_WIIMOTE + create_provider(); +#endif + + m_update_thread_shutdown.store(false); + m_update_thread = std::thread(&InputManager::update_thread, this); +} + +InputManager::~InputManager() +{ + m_update_thread_shutdown.store(true); + m_update_thread.join(); +} + +void InputManager::load() noexcept +{ + for (size_t i = 0; i < kMaxController; ++i) + { + try + { + load(i); + } + catch (const std::exception& ex) + { + cemuLog_force("can't load controller profile: {}", ex.what()); + } + } +} + +bool InputManager::load(size_t player_index, std::string_view filename) +{ + fs::path file_path; + if (filename.empty()) + file_path = ActiveSettings::GetPath(fmt::format("controllerProfiles/controller{}", player_index)); + else + file_path = ActiveSettings::GetPath(fmt::format("controllerProfiles/{}", filename)); + + auto old_file = file_path; + old_file.replace_extension(".txt"); // test .txt extension + file_path.replace_extension(".xml"); // force .xml extension + + if (fs::exists(old_file) && !fs::exists(file_path)) + migrate_config(old_file); + + if (!fs::exists(file_path)) + return false; + + try + { + std::ifstream file(file_path); + if (!file.is_open()) + return false; + + pugi::xml_document doc; + if (!doc.load(file)) + return false; + + const pugi::xml_node root = doc.document_element(); + + const auto type_node = root.child("type"); + if (!type_node) + return false; + + const auto emulate = EmulatedController::type_from_string(type_node.child_value()); + auto emulated_controller = ControllerFactory::CreateEmulatedController(player_index, emulate); + + + if (const auto profile_name_node = root.child("profile")) + emulated_controller->m_profile_name = profile_name_node.child_value(); + + // custom settings + emulated_controller->load(root); + + for (const auto controller_node : root.select_nodes("controller")) + { + const auto cnode = controller_node.node(); + const auto api_node = cnode.child("api"); + if (!api_node) + continue; + + const auto uuid_node = cnode.child("uuid"); + if (!uuid_node) + continue; + + const auto* display_name = cnode.child_value("display_name"); + + try + { + const auto api = InputAPI::from_string(api_node.child_value()); + auto controller = ControllerFactory::CreateController(api, uuid_node.child_value(), display_name); + emulated_controller->add_controller(controller); + + // load optional settings + auto settings = controller->get_settings(); + if (const auto axis_node = cnode.child("axis")) + { + if (const auto value = axis_node.child("deadzone")) + settings.axis.deadzone = ConvertString(value.child_value()); + + if (const auto value = axis_node.child("range")) + settings.axis.range = ConvertString(value.child_value()); + } + if (const auto rotation_node = cnode.child("rotation")) + { + if (const auto value = rotation_node.child("deadzone")) + settings.rotation.deadzone = ConvertString(value.child_value()); + + if (const auto value = rotation_node.child("range")) + settings.rotation.range = ConvertString(value.child_value()); + } + if (const auto trigger_node = cnode.child("trigger")) + { + if (const auto value = trigger_node.child("deadzone")) + settings.trigger.deadzone = ConvertString(value.child_value()); + + if (const auto value = trigger_node.child("range")) + settings.trigger.range = ConvertString(value.child_value()); + } + + if (const auto value = cnode.child("rumble")) + settings.rumble = ConvertString(value.child_value()); + + if (const auto value = cnode.child("motion")) + settings.motion = ConvertString(value.child_value()); + + controller->set_settings(settings); + + // custom settings + controller->load(cnode); + + + // mappings + if (const auto mappings_node = cnode.child("mappings")) + { + for (const auto& entry : mappings_node.select_nodes("entry")) + { + const auto enode = entry.node(); + + const auto mapping_node = enode.child("mapping"); + if (!mapping_node) + continue; + + const auto button_node = enode.child("button"); + if (!button_node) + continue; + + const auto mapping = ConvertString(mapping_node.child_value()); + const auto button = ConvertString(button_node.child_value()); + + emulated_controller->set_mapping(mapping, controller, button); + } + } + } + catch (const std::exception& ex) + { + cemuLog_force("can't load controller: {}", ex.what()); + } + } + + set_controller(emulated_controller); + return true; + } + catch (const std::exception& ex) + { + cemuLog_force("can't load config file: {}", ex.what()); + return false; + } +} + +bool InputManager::migrate_config(const fs::path& file_path) +{ + try + { + std::ifstream file(file_path); + if (!file.is_open()) + return false; + + boost::property_tree::ptree m_data; + read_ini(file, m_data); + + const auto emulate_string = m_data.get("General.emulate"); + const auto api_string = m_data.get("General.api"); + auto uuid_opt = m_data.get_optional("General.controller"); + const auto display_name = m_data.get_optional("General.display"); + + std::string uuid; + if (api_string == to_string(InputAPI::Keyboard)) + uuid = to_string(InputAPI::Keyboard); + else + { + if (!uuid_opt) + return false; + + uuid = uuid_opt.value(); + if (api_string == to_string(InputAPI::SDLController)) + { + uuid += "_0"; + } + } + + fs::path out_file = file_path; + out_file.replace_extension(".xml"); + + pugi::xml_document doc; + auto declaration_node = doc.append_child(pugi::node_declaration); + declaration_node.append_attribute("version") = "1.0"; + declaration_node.append_attribute("encoding") = "UTF-8"; + + auto emulated_controller = doc.append_child("emulated_controller"); + emulated_controller.append_child("type").append_child(pugi::node_pcdata).set_value(emulate_string.c_str()); + + bool has_keyboard = api_string == to_string(InputAPI::Keyboard); + if (!has_keyboard) // test if only keyboard configured + { + auto controller = emulated_controller.append_child("controller"); + controller.append_child("api").append_child(pugi::node_pcdata).set_value(api_string.c_str()); + controller.append_child("uuid").append_child(pugi::node_pcdata).set_value(uuid.c_str()); + if (display_name.has_value() && !display_name->empty()) + controller.append_child("display_name").append_child(pugi::node_pcdata).set_value( + display_name.value().c_str()); + + + controller.append_child("rumble").append_child(pugi::node_pcdata).set_value( + m_data.get("Controller.rumble").c_str()); + + auto axis_node = controller.append_child("axis"); + axis_node.append_child("deadzone").append_child(pugi::node_pcdata).set_value( + m_data.get("Controller.leftDeadzone").c_str()); + axis_node.append_child("range").append_child(pugi::node_pcdata).set_value( + m_data.get("Controller.leftRange").c_str()); + + auto rotation_node = controller.append_child("rotation"); + rotation_node.append_child("deadzone").append_child(pugi::node_pcdata).set_value( + m_data.get("Controller.rightDeadzone").c_str()); + rotation_node.append_child("range").append_child(pugi::node_pcdata).set_value( + m_data.get("Controller.rightRange").c_str()); + + auto mappings_node = controller.append_child("mappings"); + for (int i = 1; i < 28; ++i) // test all possible mappings (max is 27 for vpad controller) + { + auto mapping = m_data.get_optional(fmt::format("Controller.{}", i)); + if (!mapping || mapping->empty()) + continue; + + if (!boost::starts_with(mapping.value(), "button_")) + { + if (boost::starts_with(mapping.value(), "key_")) + has_keyboard = true; + + continue; + } + + const auto button = ConvertString(mapping.value().substr(7), 16); + + uint64 flag_bit = 0; + for (auto b = 0; b < 64; ++b) + { + if (HAS_BIT(button, b)) + { + flag_bit = b; + break; + } + } + + // fix old flag layout to new one for all kind of axis stuff + if (flag_bit >= 24 && flag_bit <= 31) + flag_bit += 8; + else if (flag_bit == 32) flag_bit = kTriggerXP; + else if (flag_bit == 33) flag_bit = kRotationXP; + else if (flag_bit == 34) flag_bit = kRotationYP; + else if (flag_bit == 35) flag_bit = kTriggerYP; + else if (flag_bit == 36) flag_bit = kAxisXN; + else if (flag_bit == 37) flag_bit = kAxisYN; + else if (flag_bit == 38) flag_bit = kTriggerXN; + else if (flag_bit == 39) flag_bit = kRotationXN; + else if (flag_bit == 40) flag_bit = kRotationYN; + else if (flag_bit == 41) flag_bit = kTriggerYN; + + // fix old api mappings + if (api_string == to_string(InputAPI::XInput)) + { + const std::unordered_map xinput = + { + {kButton0, 12}, // XINPUT_GAMEPAD_A + {kButton1, 13}, // XINPUT_GAMEPAD_B + {kButton2, 14}, // XINPUT_GAMEPAD_X + {kButton3, 15}, // XINPUT_GAMEPAD_Y + + {kButton4, 8}, // XINPUT_GAMEPAD_LEFT_SHOULDER + {kButton5, 9}, // XINPUT_GAMEPAD_LEFT_SHOULDER + + {kButton6, 4}, // XINPUT_GAMEPAD_START + {kButton7, 5}, // XINPUT_GAMEPAD_BACK + + {kButton8, 6}, // XINPUT_GAMEPAD_LEFT_THUMB + {kButton9, 7}, // XINPUT_GAMEPAD_RIGHT_THUMB + + {kButton10, 0}, // XINPUT_GAMEPAD_DPAD_UP + {kButton11, 1}, // XINPUT_GAMEPAD_DPAD_DOWN + {kButton12, 2}, // XINPUT_GAMEPAD_DPAD_LEFT + {kButton13, 3}, // XINPUT_GAMEPAD_DPAD_RIGHT + }; + + const auto it = xinput.find(flag_bit); + if (it != xinput.cend()) + flag_bit = it->second; + } + else if (api_string == "DSU") + { + const std::unordered_map dsu = + { + {7, kButton0}, // ButtonSelect + {8, kButton1}, // ButtonLStick + {9, kButton2}, // ButtonRStick + {6, kButton3}, // ButtonStart + + {4, kButton10}, // ButtonL + {5, kButton11}, // ButtonR + + {0, kButton14}, // ButtonA + {1, kButton13}, // ButtonB + {2, kButton15}, // ButtonX + {3, kButton12}, // ButtonY + }; + + const auto it = dsu.find(flag_bit); + if (it != dsu.cend()) + flag_bit = it->second; + } + + + auto entry_node = mappings_node.append_child("entry"); + entry_node.append_child("mapping").append_child(pugi::node_pcdata).set_value( + fmt::format("{}", i).c_str()); + entry_node.append_child("button").append_child(pugi::node_pcdata).set_value( + fmt::format("{}", flag_bit).c_str()); + } + } + + if (has_keyboard) + { + auto controller = emulated_controller.append_child("controller"); + controller.append_child("api").append_child(pugi::node_pcdata).set_value("Keyboard"); + controller.append_child("uuid").append_child(pugi::node_pcdata).set_value("Keyboard"); + + auto mappings_node = controller.append_child("mappings"); + for (int i = 1; i < 28; ++i) // test all possible mappings (max is 27 for vpad controller) + { + auto mapping = m_data.get_optional(fmt::format("Controller.{}", i)); + if (!mapping || mapping->empty()) + continue; + + if (!boost::starts_with(mapping.value(), "key_")) + continue; + + const auto button = ConvertString(mapping.value().substr(4)); + + auto entry_node = mappings_node.append_child("entry"); + entry_node.append_child("mapping").append_child(pugi::node_pcdata).set_value( + fmt::format("{}", i).c_str()); + entry_node.append_child("button").append_child(pugi::node_pcdata).set_value( + fmt::format("{}", button).c_str()); + } + } + + std::ofstream write_file(out_file, std::ios::out | std::ios::trunc); + if (write_file.is_open()) + { + doc.save(write_file); + return true; + } + } + catch (const std::exception& ex) + { + cemuLog_force("can't migrate config file {}: {}", file_path.string(), ex.what()); + } + + return false; +} + +void InputManager::save() noexcept +{ + for (size_t i = 0; i < kMaxController; ++i) + { + try + { + save(i); + } + catch (const std::exception& ex) + { + cemuLog_force("can't save controller profile: {}", ex.what()); + } + } +} + +bool InputManager::save(size_t player_index, std::string_view filename) +{ + // dont overwrite files if set by gameprofile + if (m_is_gameprofile_set[player_index]) + return true; + + auto emulated_controller = get_controller(player_index); + if (!emulated_controller) + return false; + + fs::path file_path = ActiveSettings::GetPath("controllerProfiles"); + fs::create_directories(file_path); + + const auto is_default_file = filename.empty(); + if (is_default_file) + file_path /= fmt::format("controller{}", player_index); + else + file_path /= filename; + + file_path.replace_extension(".xml"); // force .xml extension + + pugi::xml_document doc; + auto declaration_node = doc.append_child(pugi::node_declaration); + declaration_node.append_attribute("version") = "1.0"; + declaration_node.append_attribute("encoding") = "UTF-8"; + + auto emulated_controller_node = doc.append_child("emulated_controller"); + emulated_controller_node.append_child("type").append_child(pugi::node_pcdata).set_value(std::string{ + emulated_controller->type_string() + }.c_str()); + + if (emulated_controller->has_profile_name()) + emulated_controller_node.append_child("profile").append_child(pugi::node_pcdata).set_value( + emulated_controller->get_profile_name().c_str()); + else if (!is_default_file) + { + emulated_controller->m_profile_name = std::string{filename}; + emulated_controller_node.append_child("profile").append_child(pugi::node_pcdata).set_value( + emulated_controller->get_profile_name().c_str()); + } + + // custom settings + emulated_controller->save(emulated_controller_node); + + for (const auto& controller : emulated_controller->get_controllers()) + { + auto controller_node = emulated_controller_node.append_child("controller"); + + // general + controller_node.append_child("api").append_child(pugi::node_pcdata).set_value(std::string{ + controller->api_name() + }.c_str()); + controller_node.append_child("uuid").append_child(pugi::node_pcdata).set_value(controller->uuid().c_str()); + controller_node.append_child("display_name").append_child(pugi::node_pcdata).set_value( + controller->display_name().c_str()); + + // settings + const auto& settings = controller->get_settings(); + + if (controller->has_motion()) + controller_node.append_child("motion").append_child(pugi::node_pcdata).set_value( + fmt::format("{}", settings.motion).c_str()); + + if (controller->has_rumble()) + controller_node.append_child("rumble").append_child(pugi::node_pcdata).set_value( + fmt::format("{}", settings.rumble).c_str()); + + auto axis_node = controller_node.append_child("axis"); + axis_node.append_child("deadzone").append_child(pugi::node_pcdata).set_value( + fmt::format("{}", settings.axis.deadzone).c_str()); + axis_node.append_child("range").append_child(pugi::node_pcdata).set_value( + fmt::format("{}", settings.axis.range).c_str()); + + auto rotation_node = controller_node.append_child("rotation"); + rotation_node.append_child("deadzone").append_child(pugi::node_pcdata).set_value( + fmt::format("{}", settings.rotation.deadzone).c_str()); + rotation_node.append_child("range").append_child(pugi::node_pcdata).set_value( + fmt::format("{}", settings.rotation.range).c_str()); + + auto trigger_node = controller_node.append_child("trigger"); + trigger_node.append_child("deadzone").append_child(pugi::node_pcdata).set_value( + fmt::format("{}", settings.trigger.deadzone).c_str()); + trigger_node.append_child("range").append_child(pugi::node_pcdata).set_value( + fmt::format("{}", settings.trigger.range).c_str()); + + // custom settings + controller->save(controller_node); + + // mappings for current controller + auto mappings_node = controller_node.append_child("mappings"); + for (const auto& mapping : emulated_controller->m_mappings) + { + if (!mapping.second.controller.expired() && *controller == *mapping.second.controller.lock()) + { + auto entry_node = mappings_node.append_child("entry"); + entry_node.append_child("mapping").append_child(pugi::node_pcdata).set_value( + fmt::format("{}", mapping.first).c_str()); + entry_node.append_child("button").append_child(pugi::node_pcdata).set_value( + fmt::format("{}", mapping.second.button).c_str()); + } + } + } + + + std::ofstream file(file_path, std::ios::out | std::ios::trunc); + if (file.is_open()) + { + doc.save(file); + return true; + } + return false; +} + +bool InputManager::is_gameprofile_set(size_t player_index) const +{ + return m_is_gameprofile_set[player_index]; +} + +EmulatedControllerPtr InputManager::set_controller(EmulatedControllerPtr controller) +{ + auto prev_controller = delete_controller(controller->player_index()); + + // assign controllers to new emulated controller if empty + if (prev_controller && controller->get_controllers().empty()) + { + for (const auto& c : prev_controller->get_controllers()) + { + controller->add_controller(c); + } + } + + // try to connect all controllers + /*for (auto& c : controller->get_controllers()) + { + c->connect(); + }*/ + + std::scoped_lock lock(m_mutex); + switch (controller->type()) + { + case EmulatedController::Type::VPAD: + for (auto& pad : m_vpad) + { + if (!pad) + { + pad.swap(controller); + return prev_controller; + } + } + + break; + + default: + for (auto& pad : m_wpad) + { + if (!pad) + { + pad.swap(controller); + return prev_controller; + } + } + + break; + } + + cemu_assert_debug(false); + return prev_controller; +} + +EmulatedControllerPtr InputManager::set_controller(size_t player_index, EmulatedController::Type type) +{ + try + { + auto emulated_controller = ControllerFactory::CreateEmulatedController(player_index, type); + set_controller(emulated_controller); + return emulated_controller; + } + catch (const std::exception& ex) + { + cemuLog_force("Unable to set controller type {} on player index {}: {}", type, player_index, ex.what()); + } + + return {}; +} + +EmulatedControllerPtr InputManager::set_controller(size_t player_index, EmulatedController::Type type, + const std::shared_ptr& controller) +{ + auto result = set_controller(player_index, type); + if (result) + result->add_controller(controller); + + return result; +} + +EmulatedControllerPtr InputManager::get_controller(size_t player_index) const +{ + std::shared_lock lock(m_mutex); + for (const auto& pad : m_vpad) + { + if (pad && pad->player_index() == player_index) + return pad; + } + + for (const auto& pad : m_wpad) + { + if (pad && pad->player_index() == player_index) + return pad; + } + + return {}; +} + +EmulatedControllerPtr InputManager::delete_controller(size_t player_index, bool delete_profile) +{ + std::scoped_lock lock(m_mutex); + for (auto& controller : m_vpad) + { + auto result = controller; + if (result && result->player_index() == player_index) + { + controller = {}; + + if(delete_profile) + { + std::error_code ec{}; + fs::remove(ActiveSettings::GetPath(fmt::format("controllerProfiles/controller{}.xml", player_index)), ec); + fs::remove(ActiveSettings::GetPath(fmt::format("controllerProfiles/controller{}.txt", player_index)), ec); + } + + return result; + } + } + + for (auto& controller : m_wpad) + { + auto result = controller; + if (result && result->player_index() == player_index) + { + controller = {}; + + std::error_code ec{}; + fs::remove(ActiveSettings::GetPath(fmt::format("controllerProfiles/controller{}.xml", player_index)), ec); + fs::remove(ActiveSettings::GetPath(fmt::format("controllerProfiles/controller{}.txt", player_index)), ec); + + return result; + } + } + + return {}; +} + + +std::shared_ptr InputManager::get_vpad_controller(size_t index) const +{ + if (index >= m_vpad.size()) + return {}; + + std::shared_lock lock(m_mutex); + return std::static_pointer_cast(m_vpad[index]); +} + +std::shared_ptr InputManager::get_wpad_controller(size_t index) const +{ + if (index >= m_wpad.size()) + return {}; + + std::shared_lock lock(m_mutex); + return std::static_pointer_cast(m_wpad[index]); +} + +std::pair InputManager::get_controller_count() const +{ + std::shared_lock lock(m_mutex); + const size_t vpad = std::count_if(m_vpad.cbegin(), m_vpad.cend(), [](const auto& v) { return v != nullptr; }); + const size_t wpad = std::count_if(m_wpad.cbegin(), m_wpad.cend(), [](const auto& v) { return v != nullptr; }); + return std::make_pair(vpad, wpad); +} + +void InputManager::on_device_changed() +{ + std::shared_lock lock(m_mutex); + for (auto& pad : m_vpad) + { + if (pad) + pad->connect(); + } + + for (auto& pad : m_wpad) + { + if (pad) + pad->connect(); + } + lock.unlock(); + + EventService::instance().signal(); +} + +ControllerProviderPtr InputManager::get_api_provider(InputAPI::Type api) const +{ + if(!m_api_available[api].empty()) + return *(m_api_available[api].begin()); + + cemu_assert_debug(false); + return {}; +} + +ControllerProviderPtr InputManager::get_api_provider(InputAPI::Type api, const ControllerProviderSettings& settings) +{ + for(const auto& p : m_api_available[api]) + { + if(*p == settings) + { + return p; + } + } + + const auto result = ControllerFactory::CreateControllerProvider(api, settings); + m_api_available[api].emplace_back(result); + return result; +} + +void InputManager::apply_game_profile() +{ + const auto& profiles = g_current_game_profile->GetControllerProfile(); + for (int i = 0; i < kMaxController; ++i) + { + if (profiles[i] && !profiles[i]->empty()) + { + if (load(i, profiles[i].value())) + { + m_is_gameprofile_set[i] = true; + if (const auto controller = get_controller(i)) + { + if (!controller->has_profile_name()) + controller->m_profile_name = profiles[i].value(); + } + } + } + } +} + +std::vector InputManager::get_profiles() +{ + const auto path = ActiveSettings::GetPath("controllerProfiles"); + if (!exists(path)) + return {}; + + std::set tmp; + for (const auto& entry : fs::directory_iterator(path)) + { + const auto& p = entry.path(); + if (p.has_extension() && (p.extension() == ".xml" || p.extension() == ".txt")) + { + auto stem = p.filename().stem().string(); + if (is_valid_profilename(stem)) + { + tmp.emplace(stem); + } + } + } + + std::vector result; + result.reserve(tmp.size()); + result.insert(result.end(), tmp.begin(), tmp.end()); + return result; +} + +bool InputManager::is_valid_profilename(const std::string& name) +{ + if (!boost::filesystem::windows_name(name)) + return false; + + // dont allow default profile names + for (size_t i = 0; i < kMaxController; i++) + { + if (name == fmt::format("controller{}", i)) + return false; + } + + return true; +} + +glm::ivec2 InputManager::get_mouse_position(bool pad_window) const +{ + if (pad_window) + { + std::shared_lock lock(m_pad_mouse.m_mutex); + return m_pad_mouse.position; + } + else + { + std::shared_lock lock(m_main_mouse.m_mutex); + return m_main_mouse.position; + } +} + +std::optional InputManager::get_left_down_mouse_info(bool* is_pad) +{ + if (is_pad) + *is_pad = false; + + { + std::shared_lock lock(m_main_mouse.m_mutex); + if (std::exchange(m_main_mouse.left_down_toggle, false)) + return m_main_mouse.position; + + if (m_main_mouse.left_down) + return m_main_mouse.position; + } + + { + std::shared_lock lock(m_main_touch.m_mutex); + if (std::exchange(m_main_touch.left_down_toggle, false)) + return m_main_touch.position; + + if (m_main_touch.left_down) + return m_main_touch.position; + } + + if (is_pad) + *is_pad = true; + + { + std::shared_lock lock(m_pad_mouse.m_mutex); + if (std::exchange(m_pad_mouse.left_down_toggle, false)) + return m_pad_mouse.position; + + if (m_pad_mouse.left_down) + return m_pad_mouse.position; + } + + { + std::shared_lock lock(m_pad_touch.m_mutex); + if (std::exchange(m_pad_touch.left_down_toggle, false)) + return m_pad_touch.position; + + if (m_pad_touch.left_down) + return m_pad_touch.position; + } + + return {}; +} + +std::optional InputManager::get_right_down_mouse_info(bool* is_pad) +{ + if (is_pad) + *is_pad = false; + + { + std::shared_lock lock(m_main_mouse.m_mutex); + if (std::exchange(m_main_mouse.right_down_toggle, false)) + return m_main_mouse.position; + + if (m_main_mouse.right_down) + return m_main_mouse.position; + } + + { + std::shared_lock lock(m_main_touch.m_mutex); + if (std::exchange(m_main_touch.right_down_toggle, false)) + return m_main_touch.position; + + if (m_main_touch.right_down) + return m_main_touch.position; + } + + if (is_pad) + *is_pad = true; + + { + std::shared_lock lock(m_pad_mouse.m_mutex); + if (std::exchange(m_pad_mouse.right_down_toggle, false)) + return m_pad_mouse.position; + + if (m_pad_mouse.right_down) + return m_pad_mouse.position; + } + + { + std::shared_lock lock(m_pad_touch.m_mutex); + if (std::exchange(m_pad_touch.right_down_toggle, false)) + return m_pad_touch.position; + + if (m_pad_touch.right_down) + return m_pad_touch.position; + } + + return {}; +} + +void InputManager::update_thread() +{ + SetThreadName("InputManager::update_thread"); + while (!m_update_thread_shutdown.load(std::memory_order::relaxed)) + { + std::shared_lock lock(m_mutex); + for (auto& pad : m_vpad) + { + if (pad) + pad->update(); + } + + for (auto& pad : m_wpad) + { + if (pad) + pad->update(); + } + lock.unlock(); + + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + std::this_thread::yield(); + } +} diff --git a/src/input/InputManager.h b/src/input/InputManager.h new file mode 100644 index 00000000..2af2b283 --- /dev/null +++ b/src/input/InputManager.h @@ -0,0 +1,120 @@ +#pragma once + +#if BOOST_OS_WINDOWS +#include "input/api/DirectInput/DirectInputControllerProvider.h" +#include "input/api/XInput/XInputControllerProvider.h" +#include "input/api/Wiimote/WiimoteControllerProvider.h" +#endif + +#include "util/helpers/Singleton.h" + +#include "input/api/SDL/SDLControllerProvider.h" +#include "input/api/Keyboard/KeyboardControllerProvider.h" +#include "input/api/DSU/DSUControllerProvider.h" +#include "input/api/GameCube/GameCubeControllerProvider.h" + +#include "input/emulated/VPADController.h" +#include "input/emulated/WPADController.h" + +#include +#include + +class InputManager : public Singleton +{ + friend class Singleton; + InputManager(); + ~InputManager(); + + friend class MainWindow; + friend class PadViewFrame; + +public: + constexpr static size_t kMaxController = 8; + constexpr static size_t kMaxVPADControllers = 2; + constexpr static size_t kMaxWPADControllers = 7; + + void load() noexcept; + bool load(size_t player_index, std::string_view filename = {}); + + bool migrate_config(const fs::path& file_path); + + void save() noexcept; + bool save(size_t player_index, std::string_view filename = {}); + + bool is_gameprofile_set(size_t player_index) const; + + EmulatedControllerPtr set_controller(EmulatedControllerPtr controller); + EmulatedControllerPtr set_controller(size_t player_index, EmulatedController::Type type); + EmulatedControllerPtr set_controller(size_t player_index, EmulatedController::Type type, const std::shared_ptr& controller); + + EmulatedControllerPtr delete_controller(size_t player_index, bool delete_profile = false); + + EmulatedControllerPtr get_controller(size_t player_index) const; + std::shared_ptr get_vpad_controller(size_t index) const; + std::shared_ptr get_wpad_controller(size_t index) const; + std::pair get_controller_count() const; + + bool is_api_available(InputAPI::Type api) const { return !m_api_available[api].empty(); } + + ControllerProviderPtr get_api_provider(std::string_view api_name) const; + ControllerProviderPtr get_api_provider(InputAPI::Type api) const; + // will create the provider with the given settings if it doesn't exist yet + ControllerProviderPtr get_api_provider(InputAPI::Type api, const ControllerProviderSettings& settings); + + const auto& get_api_providers() const + { + return m_api_available; + } + + void apply_game_profile(); + void on_device_changed(); + + + static std::vector get_profiles(); + static bool is_valid_profilename(const std::string& name); + + struct MouseInfo + { + mutable std::shared_mutex m_mutex; + glm::ivec2 position{}; + bool left_down = false; + bool right_down = false; + + bool left_down_toggle = false; + bool right_down_toggle = false; + } m_main_mouse{}, m_pad_mouse{}, m_main_touch{}, m_pad_touch{}; + glm::ivec2 get_mouse_position(bool pad_window) const; + std::optional get_left_down_mouse_info(bool* is_pad); + std::optional get_right_down_mouse_info(bool* is_pad); + + std::atomic m_mouse_wheel; + +private: + void update_thread(); + + std::thread m_update_thread; + std::atomic m_update_thread_shutdown{false}; + + std::array, InputAPI::MAX> m_api_available{ }; + + mutable std::shared_mutex m_mutex; + std::array m_vpad; + std::array m_wpad; + + std::array m_is_gameprofile_set{}; + + template + void create_provider() // lambda templates only work in c++20 -> define locally in ctor + { + static_assert(std::is_base_of_v); + try + { + auto controller = std::make_shared(); + m_api_available[controller->api()] = std::vector{ controller }; + } + catch (const std::exception& ex) + { + cemuLog_force(ex.what()); + } + } +}; diff --git a/src/input/api/Controller.cpp b/src/input/api/Controller.cpp new file mode 100644 index 00000000..0027392b --- /dev/null +++ b/src/input/api/Controller.cpp @@ -0,0 +1,246 @@ +#include "input/api/Controller.h" + +#include "gui/guiWrapper.h" + +ControllerBase::ControllerBase(std::string_view uuid, std::string_view display_name) + : m_uuid{uuid}, m_display_name{display_name} +{ +} + +const ControllerState& ControllerBase::update_state() +{ + if (!m_is_calibrated) + calibrate(); + + ControllerState result = raw_state(); + + // ignore default buttons + result.buttons &= ~m_default_state.buttons; + + // apply deadzone and range and ignore default axis values + apply_axis_setting(result.axis, m_default_state.axis, m_settings.axis); + apply_axis_setting(result.rotation, m_default_state.rotation, m_settings.rotation); + apply_axis_setting(result.trigger, m_default_state.trigger, m_settings.trigger); + +#define APPLY_AXIS_BUTTON(_axis_, _flag_) \ + if (result._axis_.x < -ControllerState::kAxisThreshold) \ + result.buttons.set((_flag_) + (kAxisXN - kAxisXP)); \ + else if (result._axis_.x > ControllerState::kAxisThreshold) \ + result.buttons.set((_flag_)); \ + if (result._axis_.y < -ControllerState::kAxisThreshold) \ + result.buttons.set((_flag_) + 1 + (kAxisXN - kAxisXP)); \ + else if (result._axis_.y > ControllerState::kAxisThreshold) \ + result.buttons.set((_flag_) + 1); + + if (result.axis.x < -ControllerState::kAxisThreshold) + result.buttons.set((kAxisXP) + (kAxisXN - kAxisXP)); + else if (result.axis.x > ControllerState::kAxisThreshold) + result.buttons.set((kAxisXP)); + if (result.axis.y < -ControllerState::kAxisThreshold) + result.buttons.set((kAxisXP) + 1 + (kAxisXN - kAxisXP)); + else if (result.axis.y > ControllerState::kAxisThreshold) + result.buttons.set((kAxisXP) + 1);; + APPLY_AXIS_BUTTON(rotation, kRotationXP); + APPLY_AXIS_BUTTON(trigger, kTriggerXP); + + /* +// positive values + kAxisXP, + kAxisYP, + + kRotationXP, + kRotationYP, + + kTriggerXP, + kTriggerYP, + + // negative values + kAxisXN, + kAxisYN, + + kRotationXN, + kRotationYN, + + kTriggerXN, + kTriggerYN, + */ + + +#undef APPLY_AXIS_BUTTON + + m_last_state = result; + return m_last_state; +} + +void ControllerBase::apply_axis_setting(glm::vec2& axis, const glm::vec2& default_value, + const AxisSetting& setting) const +{ + constexpr float kMaxValue = 1.0f + ControllerState::kMinAxisValue; + if (setting.deadzone < 1.0f) + { + if (axis.x < default_value.x) + axis.x = (axis.x - default_value.x) / (kMaxValue + default_value.x); + else + axis.x = (axis.x - default_value.x) / (kMaxValue - default_value.x); + + if (axis.y < default_value.y) + axis.y = (axis.y - default_value.y) / (kMaxValue + default_value.y); + else + axis.y = (axis.y - default_value.y) / (kMaxValue - default_value.y); + + auto len = length(axis); + if (len >= setting.deadzone) + { + axis *= setting.range; + len = length(axis); + + // Scaled Radial Dead Zone: stickInput = stickInput.normalized * ((stickInput.magnitude - deadzone) / (1 - deadzone)); + if (len > 0) + { + axis = normalize(axis); + axis *= ((len - setting.deadzone) / (kMaxValue - setting.deadzone)); + + if (length(axis) > 1.0f) + axis = normalize(axis); + } + + if (axis.x != 0 || axis.y != 0) + { + if (std::abs(axis.x) < ControllerState::kMinAxisValue) + axis.x = ControllerState::kMinAxisValue; + + if (std::abs(axis.y) < ControllerState::kMinAxisValue) + axis.y = ControllerState::kMinAxisValue; + } + + return; + } + } + + axis = {0, 0}; +} + +bool ControllerBase::operator==(const ControllerBase& c) const +{ + return api() == c.api() && uuid() == c.uuid(); +} + +float ControllerBase::get_axis_value(uint64 button) const +{ + if (m_last_state.buttons.test(button)) + { + if (button <= kButtonNoneAxisMAX || !has_axis()) + return 1.0f; + + switch (button) + { + case kAxisXP: + case kAxisXN: + return std::abs(m_last_state.axis.x); + case kAxisYP: + case kAxisYN: + return std::abs(m_last_state.axis.y); + + case kRotationXP: + case kRotationXN: + return std::abs(m_last_state.rotation.x); + case kRotationYP: + case kRotationYN: + return std::abs(m_last_state.rotation.y); + + case kTriggerXP: + case kTriggerXN: + return std::abs(m_last_state.trigger.x); + case kTriggerYP: + case kTriggerYN: + return std::abs(m_last_state.trigger.y); + } + } + + return 0; +} + +const ControllerState& ControllerBase::calibrate() +{ + m_default_state = raw_state(); + m_is_calibrated = is_connected(); + return m_default_state; +} + + +std::string ControllerBase::get_button_name(uint64 button) const +{ + switch (button) + { + case kButtonZL: return "ZL"; + case kButtonZR: return "ZR"; + + case kButtonUp: return "DPAD-Up"; + case kButtonDown: return "DPAD-Down"; + case kButtonLeft: return "DPAD-Left"; + case kButtonRight: return "DPAD-Right"; + + case kAxisXP: return "X-Axis+"; + case kAxisYP: return "Y-Axis+"; + + case kAxisXN: return "X-Axis-"; + case kAxisYN: return "Y-Axis-"; + + case kRotationXP: return "X-Rotation+"; + case kRotationYP: return "Y-Rotation+"; + + case kRotationXN: return "X-Rotation-"; + case kRotationYN: return "Y-Rotation-"; + + case kTriggerXP: return "X-Trigger+"; + case kTriggerYP: return "Y-Trigger+"; + + case kTriggerXN: return "X-Trigger-"; + case kTriggerYN: return "y-Trigger-"; + } + + + return fmt::format("Button {}", (uint64)button); +} + +ControllerBase::Settings ControllerBase::get_settings() const +{ + std::scoped_lock lock(m_settings_mutex); + return m_settings; +} + +void ControllerBase::set_settings(const Settings& settings) +{ + std::scoped_lock lock(m_settings_mutex); + m_settings = settings; +} + +void ControllerBase::set_axis_settings(const AxisSetting& settings) +{ + std::scoped_lock lock(m_settings_mutex); + m_settings.axis = settings; +} + +void ControllerBase::set_rotation_settings(const AxisSetting& settings) +{ + std::scoped_lock lock(m_settings_mutex); + m_settings.rotation = settings; +} + +void ControllerBase::set_trigger_settings(const AxisSetting& settings) +{ + std::scoped_lock lock(m_settings_mutex); + m_settings.trigger = settings; +} + +void ControllerBase::set_rumble(float rumble) +{ + std::scoped_lock lock(m_settings_mutex); + m_settings.rumble = rumble; +} + +void ControllerBase::set_use_motion(bool state) +{ + std::scoped_lock lock(m_settings_mutex); + m_settings.motion = state; +} diff --git a/src/input/api/Controller.h b/src/input/api/Controller.h new file mode 100644 index 00000000..29b01f28 --- /dev/null +++ b/src/input/api/Controller.h @@ -0,0 +1,201 @@ +#pragma once + +#include "input/InputManager.h" +#include "input/motion/MotionSample.h" + +namespace pugi +{ + class xml_node; +} + +enum Buttons2 : uint64 +{ + // General + kButton0, + kButton1, + kButton2, + kButton3, + kButton4, + kButton5, + kButton6, + kButton7, + kButton8, + kButton9, + kButton10, + kButton11, + kButton12, + kButton13, + kButton14, + kButton15, + kButton16, + kButton17, + kButton18, + kButton19, + kButton20, + kButton21, + kButton22, + kButton23, + kButton24, + kButton25, + kButton26, + kButton27, + kButton28, + kButton29, + kButton30, + kButton31, + + // Trigger + kButtonZL, + kButtonZR, + + // DPAD + kButtonUp, + kButtonDown, + kButtonLeft, + kButtonRight, + + // positive values + kAxisXP, + kAxisYP, + + kRotationXP, + kRotationYP, + + kTriggerXP, + kTriggerYP, + + // negative values + kAxisXN, + kAxisYN, + + kRotationXN, + kRotationYN, + + kTriggerXN, + kTriggerYN, + + kButtonMAX, + + kButtonNoneAxisMAX = kButtonRight, + kButtonAxisStart = kAxisXP, +}; + +class ControllerBase +{ +public: + ControllerBase(std::string_view uuid, std::string_view display_name); + virtual ~ControllerBase() = default; + + const std::string& uuid() const { return m_uuid; } + const std::string& display_name() const { return m_display_name; } + + virtual std::string_view api_name() const = 0; + virtual InputAPI::Type api() const = 0; + + virtual void update() {} + + virtual bool connect() { return is_connected(); } + virtual bool is_connected() = 0; + + virtual bool has_battery() { return false; } + virtual bool has_low_battery() { return false; } + + const ControllerState& calibrate(); + const ControllerState& update_state(); + const ControllerState& get_state() const { return m_last_state; } + const ControllerState& get_default_state() { return is_calibrated() ? m_default_state : calibrate(); } + virtual ControllerState raw_state() = 0; + + bool is_calibrated() const { return m_is_calibrated; } + + float get_axis_value(uint64 button) const; + virtual bool has_axis() const { return true; } + + bool use_motion() { return has_motion() && m_settings.motion; } + virtual bool has_motion() { return false; } + virtual MotionSample get_motion_sample() { return {}; } + + virtual bool has_position() { return false; } + virtual glm::vec2 get_position() { return {}; } + virtual glm::vec2 get_prev_position() { return {}; } + + virtual bool has_rumble() { return false; } + virtual void start_rumble() {} + virtual void stop_rumble() {} + + virtual std::string get_button_name(uint64 button) const; + + virtual void save(pugi::xml_node& node){} + virtual void load(const pugi::xml_node& node){} + + struct AxisSetting + { + AxisSetting(float deadzone = 0.25f) : deadzone(deadzone) {} + float deadzone; + float range = 1.0f; + }; + struct Settings + { + AxisSetting axis{}, rotation{}, trigger{}; + float rumble = 0; + bool motion = false; // only valid when has_motion is true + }; + Settings get_settings() const; + void set_settings(const Settings& settings); + void set_axis_settings(const AxisSetting& settings); + void set_rotation_settings(const AxisSetting& settings); + void set_trigger_settings(const AxisSetting& settings); + void set_rumble(float rumble); + void set_use_motion(bool state); + + void apply_axis_setting(glm::vec2& axis, const glm::vec2& default_value, const AxisSetting& setting) const; + + bool operator==(const ControllerBase& c) const; + bool operator!=(const ControllerBase& c) const { return !(*this == c); } + +protected: + std::string m_uuid; + std::string m_display_name; + + ControllerState m_last_state{}; + + bool m_is_calibrated = false; + ControllerState m_default_state{}; + + mutable std::mutex m_settings_mutex; + Settings m_settings{}; +}; + +template +class Controller : public ControllerBase +{ +public: + Controller(std::string_view uuid, std::string_view display_name) + : ControllerBase(uuid, display_name) + { + static_assert(std::is_base_of_v); + m_provider = std::dynamic_pointer_cast(InputManager::instance().get_api_provider(TProvider::kAPIType)); + cemu_assert_debug(m_provider != nullptr); + } + + Controller(std::string_view uuid, std::string_view display_name, const ControllerProviderSettings& settings) + : ControllerBase(uuid, display_name) + { + static_assert(std::is_base_of_v); + m_provider = std::dynamic_pointer_cast(InputManager::instance().get_api_provider(TProvider::kAPIType, settings)); + cemu_assert_debug(m_provider != nullptr); + } + + // update provider if settings are different from default provider + void update_provider(std::shared_ptr provider) + { + m_provider = provider; + } + +protected: + using base_type = Controller; + std::shared_ptr m_provider; +}; + +using ControllerPtr = std::shared_ptr; + diff --git a/src/input/api/ControllerProvider.h b/src/input/api/ControllerProvider.h new file mode 100644 index 00000000..7b7e9ab0 --- /dev/null +++ b/src/input/api/ControllerProvider.h @@ -0,0 +1,88 @@ +#pragma once + +#include "input/api/InputAPI.h" + +class ControllerBase; + +struct ControllerProviderSettings +{ + virtual ~ControllerProviderSettings() = default; + virtual bool operator==(const ControllerProviderSettings&) const = 0; +}; + +class ControllerProviderBase +{ +public: + ControllerProviderBase() = default; + virtual ~ControllerProviderBase() = default; + + virtual InputAPI::Type api() const = 0; + std::string_view api_name() const { return to_string(api()); } + + virtual std::vector> get_controllers() = 0; + + virtual bool has_settings() const + { + return false; + } + + virtual bool operator==(const ControllerProviderBase& p) const + { + return api() == p.api(); + } + virtual bool operator==(const ControllerProviderSettings& p) const + { + return false; + } +}; + +using ControllerProviderPtr = std::shared_ptr; + +template +class ControllerProvider : public ControllerProviderBase +{ + using base_type = ControllerProviderBase; +public: + ControllerProvider() = default; + + ControllerProvider(const TSettings& settings) + : m_settings(settings) + {} + + bool has_settings() const override + { + return true; + } + + const TSettings& get_settings() const + { + return m_settings; + } + + bool operator==(const ControllerProviderBase& p) const override + { + if (!base_type::operator==(p)) + return false; + + if (!p.has_settings()) + return false; + + auto* ptr = dynamic_cast*>(&p); + if (!ptr) + return false; + + return base_type::operator==(p) && m_settings == ptr->m_settings; + } + + bool operator==(const ControllerProviderSettings& p) const override + { + auto* ptr = dynamic_cast(&p); + if (!ptr) + return false; + + return m_settings == *ptr; + } + +protected: + TSettings m_settings{}; +}; diff --git a/src/input/api/ControllerState.cpp b/src/input/api/ControllerState.cpp new file mode 100644 index 00000000..272d4ba7 --- /dev/null +++ b/src/input/api/ControllerState.cpp @@ -0,0 +1,13 @@ +#include "input/api/ControllerState.h" + +bool ControllerState::operator==(const ControllerState& other) const +{ + return buttons == other.buttons; + /*&& (std::signbit(axis.x) == std::signbit(other.axis.x) && std::abs(axis.x - other.axis.x) <= kAxisThreshold) + && (std::signbit(axis.y) == std::signbit(other.axis.y) && std::abs(axis.y - other.axis.y) <= kAxisThreshold) + && (std::signbit(rotation.x) == std::signbit(other.rotation.x) && std::abs(rotation.x - other.rotation.x) <= kAxisThreshold) + && (std::signbit(rotation.y) == std::signbit(other.rotation.y) && std::abs(rotation.y - other.rotation.y) <= kAxisThreshold) + && (std::signbit(trigger.x) == std::signbit(other.trigger.x) && std::abs(trigger.x - other.trigger.x) <= kAxisThreshold) + && (std::signbit(trigger.y) == std::signbit(other.trigger.y) && std::abs(trigger.y - other.trigger.y) <= kAxisThreshold);*/ + +} diff --git a/src/input/api/ControllerState.h b/src/input/api/ControllerState.h new file mode 100644 index 00000000..2739b2ef --- /dev/null +++ b/src/input/api/ControllerState.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +struct ControllerState +{ + // when does a axis counts as pressed + constexpr static float kAxisThreshold = 0.1f; + + // on the real console the stick x or y values never really reach 0.0 if one of the axis is moved + // some games rely on this due to incorrectly checking if the stick is tilted via if (vstick.x != 0 && vstick.y != 0) + // here we simulate a slight bias if the axis is almost perfectly centered + constexpr static float kMinAxisValue = 0.0000001f; + + // [-1; 1] + glm::vec2 axis{ }; + glm::vec2 rotation{ }; + glm::vec2 trigger{ }; + + std::bitset<256> buttons{}; + + uint64 last_state = 0; + + bool operator==(const ControllerState& other) const; + bool operator!=(const ControllerState& other) const + { + return !(*this == other); + } +}; diff --git a/src/input/api/DSU/DSUController.cpp b/src/input/api/DSU/DSUController.cpp new file mode 100644 index 00000000..33e16dbf --- /dev/null +++ b/src/input/api/DSU/DSUController.cpp @@ -0,0 +1,168 @@ +#include "input/api/DSU/DSUController.h" + +#include + +DSUController::DSUController(uint32 index) + : base_type(fmt::format("{}", index), fmt::format("Controller {}", index + 1)), m_index(index) +{ + if (index >= DSUControllerProvider::kMaxClients) + throw std::runtime_error(fmt::format("max {} dsu controllers are supported! given index: {}", + DSUControllerProvider::kMaxClients, index)); +} + +DSUController::DSUController(uint32 index, const DSUProviderSettings& settings) + : base_type(fmt::format("{}", index), fmt::format("Controller {}", index + 1), settings), m_index(index) +{ + if (index >= DSUControllerProvider::kMaxClients) + throw std::runtime_error(fmt::format("max {} dsu controllers are supported! given index: {}", + DSUControllerProvider::kMaxClients, index)); +} + +void DSUController::save(pugi::xml_node& node) +{ + base_type::save(node); + + node.append_child("ip").append_child(pugi::node_pcdata).set_value( + fmt::format("{}", m_provider->get_settings().ip).c_str()); + node.append_child("port").append_child(pugi::node_pcdata).set_value( + fmt::format("{}", m_provider->get_settings().port).c_str()); +} + +void DSUController::load(const pugi::xml_node& node) +{ + base_type::load(node); + + DSUProviderSettings settings; + if (const auto value = node.child("ip")) + settings.ip = value.child_value(); + if (const auto value = node.child("port")) + settings.port = ConvertString(value.child_value()); + + const auto provider = InputManager::instance().get_api_provider(api(), settings); + update_provider(std::dynamic_pointer_cast(provider)); + connect(); +} + +bool DSUController::connect() +{ + if (is_connected()) + return true; + + m_provider->request_pad_data(m_index); + return is_connected(); +} + +bool DSUController::is_connected() +{ + return m_provider->is_connected(m_index); +} + +MotionSample DSUController::get_motion_sample() +{ + return m_provider->get_motion_sample(m_index); +} + +bool DSUController::has_position() +{ + const auto state = m_provider->get_state(m_index); + return state.data.tpad1.active || state.data.tpad2.active; +} + +glm::vec2 DSUController::get_position() +{ + // touchpad resolution is 1920x942 + const auto state = m_provider->get_state(m_index); + if (state.data.tpad1.active) + return glm::vec2{(float)state.data.tpad1.x / 1920.0f, (float)state.data.tpad1.y / 942.0f}; + + if (state.data.tpad2.active) + return glm::vec2{(float)state.data.tpad2.x / 1920.0f, (float)state.data.tpad2.y / 942.0f}; + + return {}; +} + +glm::vec2 DSUController::get_prev_position() +{ + const auto state = m_provider->get_prev_state(m_index); + if (state.data.tpad1.active) + return glm::vec2{(float)state.data.tpad1.x / 1920.0f, (float)state.data.tpad1.y / 942.0f}; + + if (state.data.tpad2.active) + return glm::vec2{(float)state.data.tpad2.x / 1920.0f, (float)state.data.tpad2.y / 942.0f}; + + return {}; +} + +std::string DSUController::get_button_name(uint64 button) const +{ + switch (button) + { + case kButton0: return "Share"; + case kButton1: return "Stick L"; + case kButton2: return "Stick R"; + case kButton3: return "Options"; + case kButton4: return "Up"; + case kButton5: return "Right"; + case kButton6: return "Down"; + case kButton7: return "Left"; + + case kButton8: return "ZL"; + case kButton9: return "ZR"; + case kButton10: return "L"; + case kButton11: return "R"; + case kButton12: return "Triangle"; + case kButton13: return "Circle"; + case kButton14: return "Cross"; + case kButton15: return "Square"; + + case kButton16: return "Touch"; + } + return base_type::get_button_name(button); +} + +ControllerState DSUController::raw_state() +{ + ControllerState result{}; + + if (!is_connected()) + return result; + + const auto state = m_provider->get_state(m_index); + // didn't read any data from the controller yet + if (state.info.state != DsState::Connected) + return result; + + int bitindex = 0; + for (int i = 0; i < 8; ++i, ++bitindex) + { + if (HAS_BIT(state.data.state1, i)) + { + result.buttons.set(bitindex); + } + } + + for (int i = 0; i < 8; ++i, ++bitindex) + { + if (HAS_BIT(state.data.state2, i)) + { + result.buttons.set(bitindex); + } + } + + if (state.data.touch) + result.buttons.set(kButton16); + + result.axis.x = (float)state.data.lx / std::numeric_limits::max(); + result.axis.x = (result.axis.x * 2.0f) - 1.0f; + + result.axis.y = (float)state.data.ly / std::numeric_limits::max(); + result.axis.y = (result.axis.y * 2.0f) - 1.0f; + + result.rotation.x = (float)state.data.rx / std::numeric_limits::max(); + result.rotation.x = (result.rotation.x * 2.0f) - 1.0f; + + result.rotation.y = (float)state.data.ry / std::numeric_limits::max(); + result.rotation.y = (result.rotation.y * 2.0f) - 1.0f; + + return result; +} diff --git a/src/input/api/DSU/DSUController.h b/src/input/api/DSU/DSUController.h new file mode 100644 index 00000000..801f609c --- /dev/null +++ b/src/input/api/DSU/DSUController.h @@ -0,0 +1,44 @@ +#pragma once + +#include "input/api/Controller.h" +#include "input/api/DSU/DSUControllerProvider.h" +#include "Cafe/HW/AI/AI.h" +#include "Cafe/HW/AI/AI.h" +#include "Cafe/HW/AI/AI.h" +#include "Cafe/HW/AI/AI.h" + +class DSUController : public Controller +{ +public: + DSUController(uint32 index); + DSUController(uint32 index, const DSUProviderSettings& settings); + + std::string_view api_name() const override + { + static_assert(to_string(InputAPI::DSUClient) == "DSUController"); + return to_string(InputAPI::DSUClient); + } + InputAPI::Type api() const override { return InputAPI::DSUClient; } + + void save(pugi::xml_node& node) override; + void load(const pugi::xml_node& node) override; + + bool connect() override; + bool is_connected() override; + + bool has_motion() override { return true; } + MotionSample get_motion_sample() override; + + bool has_position() override; + glm::vec2 get_position() override; + glm::vec2 get_prev_position() override; + + std::string get_button_name(uint64 button) const override; + +protected: + ControllerState raw_state() override; + +private: + uint32 m_index; +}; + diff --git a/src/input/api/DSU/DSUControllerProvider.cpp b/src/input/api/DSU/DSUControllerProvider.cpp new file mode 100644 index 00000000..4f48f9e5 --- /dev/null +++ b/src/input/api/DSU/DSUControllerProvider.cpp @@ -0,0 +1,448 @@ +#include "input/api/DSU/DSUControllerProvider.h" +#include "input/api/DSU/DSUController.h" + +DSUControllerProvider::DSUControllerProvider() + : base_type(), m_uid(rand()), m_socket(m_io_service) +{ + if (!connect()) + { + throw std::runtime_error("dsu client can't open the udp connection"); + } + + m_running = true; + m_reader_thread = std::thread(&DSUControllerProvider::reader_thread, this); + m_writer_thread = std::thread(&DSUControllerProvider::writer_thread, this); + request_version(); +} + +DSUControllerProvider::DSUControllerProvider(const DSUProviderSettings& settings) + : base_type(settings), m_uid(rand()), m_socket(m_io_service) +{ + if (!connect()) + { + throw std::runtime_error("dsu client can't open the udp connection"); + } + + m_running = true; + m_reader_thread = std::thread(&DSUControllerProvider::reader_thread, this); + m_writer_thread = std::thread(&DSUControllerProvider::writer_thread, this); + request_version(); +} + +DSUControllerProvider::~DSUControllerProvider() +{ + if (m_running) + { + m_running = false; + m_writer_thread.join(); + m_reader_thread.join(); + } +} + +std::vector> DSUControllerProvider::get_controllers() +{ + std::vector result; + + std::array indices; + for (auto i = 0; i < kMaxClients; ++i) + indices[i] = get_packet_index(i); + + request_pad_info(); + + const auto controller_result = wait_update(indices, 3000); + for (auto i = 0; i < kMaxClients; ++i) + { + if (controller_result[i] && is_connected(i)) + result.emplace_back(std::make_shared(i, m_settings)); + } + + return result; +} + +bool DSUControllerProvider::connect() +{ + // already connected? + if (m_receiver_endpoint.address().to_string() == get_settings().ip && m_receiver_endpoint.port() == get_settings().port) + return true; + + try + { + using namespace boost::asio; + + ip::udp::resolver resolver(m_io_service); + const ip::udp::resolver::query query(ip::udp::v4(), get_settings().ip, fmt::format("{}", get_settings().port)); + m_receiver_endpoint = *resolver.resolve(query); + + if (m_socket.is_open()) + m_socket.close(); + + m_socket.open(ip::udp::v4()); + // set timeout for our threads to give a chance to exit + m_socket.set_option(boost::asio::detail::socket_option::integer{200}); + + // reset data + m_state = {}; + m_prev_state = {}; + + // restart threads + return true; + } + catch (const std::exception& ex) + { + forceLog_printf("dsu client connect error: %s", ex.what()); + return false; + } +} + +bool DSUControllerProvider::is_connected(uint8_t index) const +{ + if (index >= kMaxClients) + return false; + + std::scoped_lock lock(m_mutex[index]); + return m_state[index].info.state == DsState::Connected; +} + +DSUControllerProvider::ControllerState DSUControllerProvider::get_state(uint8_t index) const +{ + if (index >= kMaxClients) + return {}; + + std::scoped_lock lock(m_mutex[index]); + return m_state[index]; +} + +DSUControllerProvider::ControllerState DSUControllerProvider::get_prev_state(uint8_t index) const +{ + if (index >= kMaxClients) + return {}; + + std::scoped_lock lock(m_mutex[index]); + return m_prev_state[index]; +} + +std::array DSUControllerProvider::wait_update( + const std::array& indices, size_t timeout) const +{ + std::array result{false, false, false, false}; + + const auto end = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeout); + do + { + for (int i = 0; i < kMaxClients; ++i) + { + if (result[i]) + continue; + + std::unique_lock lock(m_mutex[i]); + result[i] = indices[i] < m_state[i].packet_index; + } + + if (std::all_of(result.cbegin(), result.cend(), [](const bool& v) { return v == true; })) + break; + + //std::this_thread::sleep_for(std::chrono::milliseconds(1)); + std::this_thread::yield(); + } + while (std::chrono::steady_clock::now() < end); + + return result; +} + +bool DSUControllerProvider::wait_update(uint8_t index, uint32_t packet_index, size_t timeout) const +{ + if (index >= kMaxClients) + return false; + + std::unique_lock lock(m_mutex[index]); + + if (packet_index < m_state[index].packet_index) + return true; + + const auto result = m_wait_cond[index].wait_for(lock, std::chrono::milliseconds(timeout), + [this, index, packet_index]() + { + return packet_index < m_state[index].packet_index; + }); + + return result; +} + +uint32_t DSUControllerProvider::get_packet_index(uint8_t index) const +{ + std::scoped_lock lock(m_mutex[index]); + return m_state[index].packet_index; +} + +void DSUControllerProvider::request_version() +{ + auto msg = std::make_unique(m_uid); + + std::scoped_lock lock(m_writer_mutex); + m_writer_jobs.push(std::move(msg)); + m_writer_cond.notify_one(); +} + +void DSUControllerProvider::request_pad_info() +{ + auto msg = std::make_unique(m_uid, 4, std::array{0, 1, 2, 3}); + + std::scoped_lock lock(m_writer_mutex); + m_writer_jobs.push(std::move(msg)); + m_writer_cond.notify_one(); +} + +void DSUControllerProvider::request_pad_info(uint8_t index) +{ + if (index >= kMaxClients) + return; + + auto msg = std::make_unique(m_uid, 1, std::array{index}); + + std::scoped_lock lock(m_writer_mutex); + m_writer_jobs.push(std::move(msg)); + m_writer_cond.notify_one(); +} + +void DSUControllerProvider::request_pad_data() +{ + auto msg = std::make_unique(m_uid); + + std::scoped_lock lock(m_writer_mutex); + m_writer_jobs.push(std::move(msg)); + m_writer_cond.notify_one(); +} + +void DSUControllerProvider::request_pad_data(uint8_t index) +{ + if (index >= kMaxClients) + return; + + auto msg = std::make_unique(m_uid, index); + + std::scoped_lock lock(m_writer_mutex); + m_writer_jobs.push(std::move(msg)); + m_writer_cond.notify_one(); +} + +MotionSample DSUControllerProvider::get_motion_sample(uint8_t index) const +{ + if (index >= kMaxClients) + return MotionSample(); + std::scoped_lock lock(m_mutex[index]); + return m_state[index].motion_sample; +} + + +void DSUControllerProvider::reader_thread() +{ + SetThreadName("DSUControllerProvider::reader_thread"); + bool first_read = true; + while (m_running.load(std::memory_order_relaxed)) + { + ServerMessage* msg; + //try + //{ + std::array recv_buf; // NOLINT(cppcoreguidelines-pro-type-member-init, hicpp-member-init) + boost::asio::ip::udp::endpoint sender_endpoint; + boost::system::error_code ec{}; + const size_t len = m_socket.receive_from(boost::asio::buffer(recv_buf), sender_endpoint, 0, ec); + if (ec) + { +#ifdef DEBUG_DSU_CLIENT + printf(" DSUControllerProvider::ReaderThread: exception %s\n", ex.what()); +#endif + + // there's probably no server listening on the given address:port + if (first_read) // workaroud: first read always fails? + first_read = false; + else + { + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + std::this_thread::yield(); + } + continue; + } + +#ifdef DEBUG_DSU_CLIENT + printf(" DSUControllerProvider::ReaderThread: received message with len: 0x%llx\n", len); +#endif + + if (len < sizeof(ServerMessage)) // cant be a valid message + continue; + + msg = (ServerMessage*)recv_buf.data(); + // } + // catch (const std::exception&) + // { + //#ifdef DEBUG_DSU_CLIENT + // printf(" DSUControllerProvider::ReaderThread: exception %s\n", ex.what()); + //#endif + // + // // there's probably no server listening on the given address:port + // if (first_read) // workaroud: first read always fails? + // first_read = false; + // else + // { + // std::this_thread::sleep_for(std::chrono::milliseconds(250)); + // std::this_thread::yield(); + // } + // continue; + // } + + uint8_t index = 0xFF; + switch (msg->GetMessageType()) + { + case MessageType::Version: + { + const auto rsp = (VersionResponse*)msg; + if (!rsp->IsValid()) + { +#ifdef DEBUG_DSU_CLIENT + printf(" DSUControllerProvider::ReaderThread: VersionResponse is invalid!\n"); +#endif + continue; + } + +#ifdef DEBUG_DSU_CLIENT + printf(" DSUControllerProvider::ReaderThread: server version is: 0x%x\n", rsp->GetVersion()); +#endif + + m_server_version = rsp->GetVersion(); + // wdc + break; + } + case MessageType::Information: + { + const auto info = (PortInfo*)msg; + if (!info->IsValid()) + { +#ifdef DEBUG_DSU_CLIENT + printf(" DSUControllerProvider::ReaderThread: PortInfo is invalid!\n"); +#endif + continue; + } + + index = info->GetIndex(); +#ifdef DEBUG_DSU_CLIENT + printf(" DSUControllerProvider::ReaderThread: received PortInfo for index %d\n", index); +#endif + + auto& mutex = m_mutex[index]; + std::scoped_lock lock(mutex); + m_prev_state[index] = m_state[index]; + m_state[index] = *info; + m_wait_cond[index].notify_all(); + break; + } + case MessageType::Data: + { + const auto rsp = (DataResponse*)msg; + if (!rsp->IsValid()) + { +#ifdef DEBUG_DSU_CLIENT + printf(" DSUControllerProvider::ReaderThread: DataResponse is invalid!\n"); +#endif + continue; + } + + index = rsp->GetIndex(); +#ifdef DEBUG_DSU_CLIENT + printf(" DSUControllerProvider::ReaderThread: received DataResponse for index %d\n", index); +#endif + + auto& mutex = m_mutex[index]; + std::scoped_lock lock(mutex); + m_prev_state[index] = m_state[index]; + m_state[index] = *rsp; + m_wait_cond[index].notify_all(); + // update motion info immediately, guaranteeing that we dont drop packets + integrate_motion(index, *rsp); + break; + } + } + + if (index != 0xFF) + request_pad_data(index); + } +} + +void DSUControllerProvider::writer_thread() +{ + SetThreadName("DSUControllerProvider::writer_thread"); + while (m_running.load(std::memory_order_relaxed)) + { + std::unique_lock lock(m_writer_mutex); + while (m_writer_jobs.empty()) + { + if (m_writer_cond.wait_for(lock, std::chrono::milliseconds(250)) == std::cv_status::timeout) + { + if (!m_running.load(std::memory_order_relaxed)) + return; + } + } + + const auto msg = std::move(m_writer_jobs.front()); + m_writer_jobs.pop(); + lock.unlock(); + +#ifdef DEBUG_DSU_CLIENT + printf(" DSUControllerProvider::WriterThread: sending message: 0x%x (len: 0x%x)\n", (int)msg->GetMessageType(), msg->GetSize()); +#endif + try + { + m_socket.send_to(boost::asio::buffer(msg.get(), msg->GetSize()), m_receiver_endpoint); + } + catch (const std::exception&) + { +#ifdef DEBUG_DSU_CLIENT + printf(" DSUControllerProvider::WriterThread: exception %s\n", ex.what()); +#endif + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + } + } +} + +void DSUControllerProvider::integrate_motion(uint8_t index, const DataResponse& data_response) +{ + const uint64 ts = data_response.GetMotionTimestamp(); + if (ts <= m_last_motion_timestamp[index]) + { + const uint64 dif = m_last_motion_timestamp[index] - ts; + if (dif >= 10000000) // timestamp more than 10 seconds in the past, a controller reset probably happened + m_last_motion_timestamp[index] = 0; + return; + } + + const uint64 elapsedTime = ts - m_last_motion_timestamp[index]; + m_last_motion_timestamp[index] = ts; + const double elapsedTimeD = (double)elapsedTime / 1000000.0; + const auto& acc = data_response.GetAcceleration(); + const auto& gyro = data_response.GetGyro(); + + m_motion_handler[index].processMotionSample((float)elapsedTimeD, + gyro.x * 0.0174533f, + gyro.y * 0.0174533f, + gyro.z * 0.0174533f, + acc.x, + -acc.y, + -acc.z); + + m_state[index].motion_sample = m_motion_handler[index].getMotionSample(); +} + +DSUControllerProvider::ControllerState& DSUControllerProvider::ControllerState::operator=(const PortInfo& port_info) +{ + info = port_info.GetInfo(); + last_update = std::chrono::steady_clock::now(); + packet_index++; // increase packet index for every packet we assign/recv + return *this; +} + +DSUControllerProvider::ControllerState& DSUControllerProvider::ControllerState::operator=( + const DataResponse& data_response) +{ + this->operator=(static_cast(data_response)); + data = data_response.GetData(); + return *this; +} diff --git a/src/input/api/DSU/DSUControllerProvider.h b/src/input/api/DSU/DSUControllerProvider.h new file mode 100644 index 00000000..dfa4d7b8 --- /dev/null +++ b/src/input/api/DSU/DSUControllerProvider.h @@ -0,0 +1,118 @@ +#pragma once + +#include "input/motion/MotionHandler.h" +#include "input/api/DSU/DSUMessages.h" + +#include "input/api/ControllerProvider.h" + +#include + +#ifndef HAS_DSU +#define HAS_DSU 1 +#endif + +// #define DEBUG_DSU_CLIENT + +struct DSUProviderSettings : public ControllerProviderSettings +{ + std::string ip; + uint16 port; + + DSUProviderSettings() : ip("127.0.0.1"), port(26760) {} + + DSUProviderSettings(std::string ip, uint16 port) + : ip(std::move(ip)), port(port) + { + } + + bool operator==(const DSUProviderSettings& s) const + { + return port == s.port && ip == s.ip; + } + + bool operator==(const ControllerProviderSettings& s) const override + { + const auto* ptr = dynamic_cast(&s); + return ptr && *this == *ptr; + } +}; + + +class DSUControllerProvider : public ControllerProvider +{ + friend class DSUController; + using base_type = ControllerProvider; +public: + constexpr static int kMaxClients = 8; + + struct ControllerState + { + // when was info updated last time + std::chrono::steady_clock::time_point last_update{}; + uint64_t packet_index = 0; // our packet index count + + PortInfoData info{}; + DataResponseData data{}; + MotionSample motion_sample{}; + + ControllerState& operator=(const PortInfo& port_info); + ControllerState& operator=(const DataResponse& data_response); + }; + + DSUControllerProvider(); + DSUControllerProvider(const DSUProviderSettings& settings); + ~DSUControllerProvider(); + + inline static InputAPI::Type kAPIType = InputAPI::DSUClient; + InputAPI::Type api() const override { return kAPIType; } + + std::vector> get_controllers() override; + + bool connect(); + + bool is_connected(uint8_t index) const; + ControllerState get_state(uint8_t index) const; + ControllerState get_prev_state(uint8_t index) const; + MotionSample get_motion_sample(uint8_t index) const; + + std::array wait_update(const std::array& indices, size_t timeout) const; + bool wait_update(uint8_t index, uint32_t packet_index, size_t timeout) const; + + uint32_t get_packet_index(uint8_t index) const; + // refresh pad info for all pads + void request_pad_info(); + // refresh pad info for pad with given index + void request_pad_info(uint8_t index); + void request_version(); + void request_pad_data(); + void request_pad_data(uint8_t index); + + +private: + uint16 m_server_version = 0; + + std::atomic_bool m_running = false; + std::thread m_reader_thread, m_writer_thread; + + void reader_thread(); + void writer_thread(); + void integrate_motion(uint8_t index, const DataResponse& data_response); + + std::mutex m_writer_mutex; + std::condition_variable m_writer_cond; + + uint32 m_uid; + boost::asio::io_service m_io_service; + boost::asio::ip::udp::endpoint m_receiver_endpoint; + boost::asio::ip::udp::socket m_socket; + + std::array m_state{}; + std::array m_prev_state{}; + mutable std::array m_mutex; + mutable std::array m_wait_cond; + + std::queue> m_writer_jobs; + + std::array m_motion_handler; + std::array m_last_motion_timestamp{}; +}; diff --git a/src/input/api/DSU/DSUMessages.cpp b/src/input/api/DSU/DSUMessages.cpp new file mode 100644 index 00000000..d0ea61af --- /dev/null +++ b/src/input/api/DSU/DSUMessages.cpp @@ -0,0 +1,97 @@ +#include "input/api/DSU/DSUMessages.h" + +#include + +constexpr uint32_t kMagicClient = 'CUSD'; +constexpr uint32_t kMagicServer = 'SUSD'; +constexpr uint16_t kProtocolVersion = 1001; + +MessageHeader::MessageHeader(uint32_t magic, uint32_t uid) + : m_magic(magic), m_protocol_version(kProtocolVersion), m_uid(uid) { } + +void MessageHeader::Finalize(size_t size) +{ + m_packet_size = (uint16_t)(size - sizeof(MessageHeader)); + m_crc32 = CRC32(size); +} + +uint32_t MessageHeader::CRC32(size_t size) const +{ + const auto tmp = m_crc32; + m_crc32 = 0; + + boost::crc_32_type crc; + crc.process_bytes(this, size); + const auto result = crc.checksum(); + + m_crc32 = tmp; + return result; +} + +bool MessageHeader::IsClientMessage() const { return m_magic == kMagicClient; } +bool MessageHeader::IsServerMessage() const { return m_magic == kMagicServer; } + +Message::Message(uint32_t magic, uint32_t uid, MessageType type) + : MessageHeader(magic, uid), m_message_type(type) +{ + +} + +ClientMessage::ClientMessage(uint32_t uid, MessageType message_type) + : Message(kMagicClient, uid, message_type) { } + +VersionRequest::VersionRequest(uint32_t uid) + : ClientMessage(uid, MessageType::Version) +{ + Finalize(sizeof(VersionRequest)); +} + +ListPorts::ListPorts(uint32_t uid, uint32_t num_pads_requests, const std::array& request_indices) + : ClientMessage(uid, MessageType::Information), m_count(num_pads_requests), m_indices(request_indices) +{ + Finalize(sizeof(ListPorts)); +} + +DataRequest::DataRequest(uint32_t uid) + : ClientMessage(uid, MessageType::Data), m_reg_flags(RegisterFlag::AllPads), m_index(0), m_mac_address({}) +{ + Finalize(sizeof(DataRequest)); +} + +DataRequest::DataRequest(uint32_t uid, uint8_t index) + : ClientMessage(uid, MessageType::Data), m_reg_flags(RegisterFlag::Index), m_index(index), m_mac_address({}) +{ + Finalize(sizeof(DataRequest)); +} + +DataRequest::DataRequest(uint32_t uid, const MACAddress_t& mac_address) + : ClientMessage(uid, MessageType::Data), m_reg_flags(RegisterFlag::MACAddress), m_index(0), m_mac_address(mac_address) +{ + Finalize(sizeof(DataRequest)); +} + +DataRequest::DataRequest(uint32_t uid, uint8_t index, const MACAddress_t& mac_address) + : ClientMessage(uid, MessageType::Data), m_reg_flags(RegisterFlag::Index | RegisterFlag::MACAddress), m_index(index), m_mac_address(mac_address) +{ + Finalize(sizeof(DataRequest)); +} + +bool ServerMessage::ValidateCRC32(size_t size) const +{ + return GetCRC32() == CRC32(size); +} + +bool VersionResponse::IsValid() const +{ + return ValidateCRC32(sizeof(VersionResponse)); +} + +bool PortInfo::IsValid() const +{ + return ValidateCRC32(sizeof(PortInfo)); +} + +bool DataResponse::IsValid() const +{ + return ValidateCRC32(sizeof(DataResponse)); +} diff --git a/src/input/api/DSU/DSUMessages.h b/src/input/api/DSU/DSUMessages.h new file mode 100644 index 00000000..53980ebb --- /dev/null +++ b/src/input/api/DSU/DSUMessages.h @@ -0,0 +1,275 @@ +#pragma once + +// https://v1993.github.io/cemuhook-protocol/ + +#include "Common/enumFlags.h" +#include "util/math/vector3.h" + +#include +#include + +enum class DsState : uint8_t +{ + Disconnected = 0x00, + Reserved = 0x01, + Connected = 0x02 +}; + +enum class DsConnection : uint8_t +{ + None = 0x00, + Usb = 0x01, + Bluetooth = 0x02 +}; + +enum class DsModel : uint8_t +{ + None = 0, + DS3 = 1, + DS4 = 2, + Generic = 3 +}; + +enum class DsBattery : uint8_t +{ + None = 0x00, + Dying = 0x01, + Low = 0x02, + Medium = 0x03, + High = 0x04, + Full = 0x05, + Charging = 0xEE, + Charged = 0xEF +}; + +enum class RegisterFlag : uint8_t +{ + AllPads = 0x00, + Index = 0x01, + MACAddress = 0x02 +}; +ENABLE_BITMASK_OPERATORS(RegisterFlag); + +enum class MessageType : uint32_t +{ + Version = 0x100000, + Information = 0x100001, + Data = 0x100002, + Rumble = 0x100003, // TODO +}; + +using MACAddress_t = std::array; + +#pragma pack(push,1) + +class MessageHeader +{ +public: + MessageHeader(uint32_t magic, uint32_t uid); + + [[nodiscard]] uint16_t GetSize() const { return sizeof(MessageHeader) + m_packet_size; } + [[nodiscard]] bool IsClientMessage() const; + [[nodiscard]] bool IsServerMessage() const; + [[nodiscard]] uint32_t GetCRC32() const { return m_crc32; } + +protected: + void Finalize(size_t size); + + [[nodiscard]] uint32_t CRC32(size_t size) const; +private: + uint32_t m_magic; + uint16_t m_protocol_version; + uint16_t m_packet_size = 0; + mutable uint32_t m_crc32 = 0; + uint32_t m_uid; +}; +static_assert(sizeof(MessageHeader) == 0x10); + +class Message : public MessageHeader +{ +public: + Message(uint32_t magic, uint32_t uid, MessageType type); + + [[nodiscard]] MessageType GetMessageType() const { return m_message_type; } +private: + MessageType m_message_type; +}; +static_assert(sizeof(Message) == 0x14); + +// client messages +class ClientMessage : public Message +{ +public: + ClientMessage(uint32_t uid, MessageType message_type); +}; +static_assert(sizeof(ClientMessage) == sizeof(Message)); + +class VersionRequest : public ClientMessage +{ +public: + VersionRequest(uint32_t uid); +}; +static_assert(sizeof(VersionRequest) == sizeof(ClientMessage)); + +class ListPorts : public ClientMessage +{ +public: + ListPorts(uint32_t uid, uint32_t num_pads_requests, const std::array& request_indices); + +private: + uint32_t m_count; + std::array m_indices; +}; + +class DataRequest : public ClientMessage +{ +public: + DataRequest(uint32_t uid); + DataRequest(uint32_t uid, uint8_t index); + DataRequest(uint32_t uid, const MACAddress_t& mac_address); + DataRequest(uint32_t uid, uint8_t index, const MACAddress_t& mac_address); + +private: + RegisterFlag m_reg_flags; + uint8_t m_index; + MACAddress_t m_mac_address; +}; + +// server messages +class ServerMessage : public Message +{ +public: + ServerMessage() = delete; + +protected: + [[nodiscard]] bool ValidateCRC32(size_t size) const; +}; + +class VersionResponse : public ServerMessage +{ +public: + [[nodiscard]] bool IsValid() const; + [[nodiscard]] uint16_t GetVersion() const { return m_version; } + +private: + uint16_t m_version; + uint8_t padding[2]; +}; +static_assert(sizeof(VersionResponse) == 0x18); + +struct PortInfoData +{ + uint8_t index; + DsState state; + DsModel model; + DsConnection connection; + MACAddress_t mac_address; + DsBattery battery; + uint8_t is_active; +}; + +class PortInfo : public ServerMessage +{ +public: + [[nodiscard]] bool IsValid() const; + + [[nodiscard]] const PortInfoData& GetInfo() const { return m_info; } + + [[nodiscard]] uint8_t GetIndex() const { return m_info.index; } + [[nodiscard]] DsState GetState() const { return m_info.state; } + [[nodiscard]] DsModel GetModel() const { return m_info.model; } + [[nodiscard]] DsConnection GetConnection() const { return m_info.connection; } + [[nodiscard]] MACAddress_t GetMacAddress() const { return m_info.mac_address; } + [[nodiscard]] DsBattery GetBattery() const { return m_info.battery; } + [[nodiscard]] bool IsActive() const { return m_info.is_active != 0; } + +protected: + PortInfoData m_info; +}; +static_assert(sizeof(PortInfo) == 0x20); + +struct TouchPoint +{ + uint8_t active; + uint8_t index; + int16_t x, y; +}; +static_assert(sizeof(TouchPoint) == 0x6); + +struct DataResponseData +{ + uint32_t m_packet_index; + + uint8_t state1; + uint8_t state2; + uint8_t ps; + uint8_t touch; + + // y values are inverted by convention + uint8_t lx, ly; + uint8_t rx, ry; + + uint8_t dpad_left; + uint8_t dpad_down; + uint8_t dpad_right; + uint8_t dpad_up; + + uint8_t square; + uint8_t cross; + uint8_t circle; + uint8_t triangle; + + uint8_t r1; + uint8_t l1; + uint8_t r2; + uint8_t l2; + + TouchPoint tpad1, tpad2; + + uint64_t motion_timestamp; + Vector3f accel; + Vector3f gyro; +}; + +class DataResponse : public PortInfo +{ +public: + [[nodiscard]] bool IsValid() const; + + [[nodiscard]] const DataResponseData& GetData() const { return m_data; } + + uint32_t GetPacketIndex() const { return m_data.m_packet_index; } + uint8_t GetState1() const { return m_data.state1; } + uint8_t GetState2() const { return m_data.state2; } + uint8_t GetPs() const { return m_data.ps; } + uint8_t GetTouch() const { return m_data.touch; } + uint8_t GetLx() const { return m_data.lx; } + uint8_t GetLy() const { return m_data.ly; } + uint8_t GetRx() const { return m_data.rx; } + uint8_t GetRy() const { return m_data.ry; } + uint8_t GetDpadLeft() const { return m_data.dpad_left; } + uint8_t GetDpadDown() const { return m_data.dpad_down; } + uint8_t GetDpadRight() const { return m_data.dpad_right; } + uint8_t GetDpadUp() const { return m_data.dpad_up; } + uint8_t GetSquare() const { return m_data.square; } + uint8_t GetCross() const { return m_data.cross; } + uint8_t GetCircle() const { return m_data.circle; } + uint8_t GetTriangle() const { return m_data.triangle; } + uint8_t GetR1() const { return m_data.r1; } + uint8_t GetL1() const { return m_data.l1; } + uint8_t GetR2() const { return m_data.r2; } + uint8_t GetL2() const { return m_data.l2; } + const TouchPoint& GetTpad1() const { return m_data.tpad1; } + const TouchPoint& GetTpad2() const { return m_data.tpad2; } + uint64_t GetMotionTimestamp() const { return m_data.motion_timestamp; } + + const Vector3f& GetAcceleration() const { return m_data.accel; } + const Vector3f& GetGyro() const { return m_data.gyro; } + +private: + DataResponseData m_data; +}; + +//static_assert(sizeof(DataResponse) == 0x20); + +#pragma pack(pop) \ No newline at end of file diff --git a/src/input/api/DirectInput/DirectInputController.cpp b/src/input/api/DirectInput/DirectInputController.cpp new file mode 100644 index 00000000..9db551ed --- /dev/null +++ b/src/input/api/DirectInput/DirectInputController.cpp @@ -0,0 +1,337 @@ +#include "input/api/DirectInput/DirectInputController.h" +#include "gui/guiWrapper.h" + +DirectInputController::DirectInputController(const GUID& guid) + : base_type(StringFromGUID(guid), fmt::format("[{}]", StringFromGUID(guid))), + m_guid{ guid } +{ + +} + +DirectInputController::DirectInputController(const GUID& guid, std::string_view display_name) + : base_type(StringFromGUID(guid), display_name), m_guid(guid) +{ +} + +DirectInputController::~DirectInputController() +{ + if (m_effect) + m_effect->Release(); + + if (m_device) + { + m_device->Unacquire(); + + // TODO: test if really needed + // workaround for gamecube controllers crash on release? + bool should_release_device = true; + if (m_product_guid == GUID{}) { + DIDEVICEINSTANCE info{}; + info.dwSize = sizeof(DIDEVICEINSTANCE); + if (SUCCEEDED(m_device->GetDeviceInfo(&info))) + { + m_product_guid = info.guidProduct; + } + } + + // info.guidProduct = {18440079-0000-0000-0000-504944564944} + constexpr GUID kGameCubeController = { 0x18440079, 0, 0, {0,0,0x50,0x49,0x44,0x56,0x49,0x44} }; + if (kGameCubeController == m_product_guid) + should_release_device = false; + + if (should_release_device) + m_device->Release(); + } +} + +void DirectInputController::save(pugi::xml_node& node) +{ + base_type::save(node); + + node.append_child("product_guid").append_child(pugi::node_pcdata).set_value( + fmt::format("{}", StringFromGUID(m_product_guid)).c_str()); +} + +void DirectInputController::load(const pugi::xml_node& node) +{ + base_type::load(node); + + if (const auto value = node.child("product_guid")) { + if (GUIDFromString(value.child_value(), m_product_guid) && m_product_guid != GUID{} && !is_connected()) + { + // test if another controller with the same product guid is connectable and replace + for(const auto& c : m_provider->get_controllers()) + { + if(const auto ptr = std::dynamic_pointer_cast(c)) + { + if (ptr->is_connected() && ptr->get_product_guid() == m_product_guid) + { + const auto tmp_guid = m_guid; + m_guid = ptr->get_guid(); + if (connect()) + break; + + // couldn't connect + m_guid = tmp_guid; + } + } + + } + } + } +} + +bool DirectInputController::connect() +{ + if (is_connected()) + return true; + + m_effect = nullptr; + + std::scoped_lock lock(m_mutex); + HRESULT hr = m_provider->get_dinput()->CreateDevice(m_guid, &m_device, nullptr); + if (FAILED(hr) || m_device == nullptr) + return false; + + DIDEVICEINSTANCE idi{}; + idi.dwSize = sizeof(DIDEVICEINSTANCE); + if (SUCCEEDED(m_device->GetDeviceInfo(&idi))) + { + // overwrite guid name with "real" display name + m_display_name = boost::nowide::narrow(idi.tszProductName); + } + + // set data format + if (FAILED(m_device->SetDataFormat(m_provider->get_data_format()))) + { + SAFE_RELEASE(m_device); + return false; + } + + HWND hwndMainWindow = gui_getWindowInfo().window_main.hwnd; + + // set access + if (FAILED(m_device->SetCooperativeLevel(hwndMainWindow, DISCL_BACKGROUND | DISCL_EXCLUSIVE))) + { + if (FAILED(m_device->SetCooperativeLevel(hwndMainWindow, DISCL_BACKGROUND | DISCL_NONEXCLUSIVE))) + { + SAFE_RELEASE(m_device); + return false; + } + // rumble can only be used with exclusive access + } + else + { + GUID guid_effect = GUID_NULL; + // check if constant force is supported + HRESULT result = m_device->EnumEffects([](LPCDIEFFECTINFOW eff, LPVOID guid) -> BOOL + { + *(GUID*)guid = eff->guid; + return DIENUM_STOP; + }, &guid_effect, DIEFT_CONSTANTFORCE); + + if (SUCCEEDED(result) && guid_effect != GUID_NULL) + { + DWORD dwAxes[2] = { DIJOFS_X, DIJOFS_Y }; + LONG lDirection[2] = { 1, 0 }; + + DICONSTANTFORCE constant_force = { DI_FFNOMINALMAX }; // DI_FFNOMINALMAX -> should be max normally?! + + DIEFFECT effect{}; + effect.dwSize = sizeof(DIEFFECT); + effect.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS; + effect.dwDuration = INFINITE; // DI_SECONDS; + effect.dwGain = DI_FFNOMINALMAX; // No scaling + effect.dwTriggerButton = DIEB_NOTRIGGER; // Not a button response DIEB_NOTRIGGER DIJOFS_BUTTON0 + effect.cAxes = 2; + effect.rgdwAxes = dwAxes; + effect.rglDirection = lDirection; + effect.cbTypeSpecificParams = sizeof(DICONSTANTFORCE); + effect.lpvTypeSpecificParams = &constant_force; + m_device->CreateEffect(guid_effect, &effect, &m_effect, nullptr); + } + } + + DIDEVICEINSTANCE info{}; + info.dwSize = sizeof(DIDEVICEINSTANCE); + if (SUCCEEDED(m_device->GetDeviceInfo(&info))) + { + m_product_guid = info.guidProduct; + } + + std::fill(m_min_axis.begin(), m_min_axis.end(), 0); + std::fill(m_max_axis.begin(), m_max_axis.end(), std::numeric_limits::max()); + m_device->EnumObjects( + [](LPCDIDEVICEOBJECTINSTANCE lpddoi, LPVOID pvRef) -> BOOL + { + auto* thisptr = (DirectInputController*)pvRef; + + const auto instance = DIDFT_GETINSTANCE(lpddoi->dwType); + // some tools may use state.rglSlider properties, so they have 8 instead of 6 axis + if(instance >= thisptr->m_min_axis.size()) + { + return DIENUM_CONTINUE; + } + + DIPROPRANGE range{}; + range.diph.dwSize = sizeof(range); + range.diph.dwHeaderSize = sizeof(range.diph); + range.diph.dwHow = DIPH_BYID; + range.diph.dwObj = lpddoi->dwType; + if (thisptr->m_device->GetProperty(DIPROP_RANGE, &range.diph) == DI_OK) + { + thisptr->m_min_axis[instance] = range.lMin; + thisptr->m_max_axis[instance] = range.lMax; + } + + return DIENUM_CONTINUE; + }, this, DIDFT_AXIS); + + m_device->Acquire(); + return true; +} + +bool DirectInputController::is_connected() +{ + std::shared_lock lock(m_mutex); + return m_device != nullptr; +} + +bool DirectInputController::has_rumble() +{ + return m_effect != nullptr; +} + +void DirectInputController::start_rumble() +{ + if (!has_rumble()) + return; +} + +void DirectInputController::stop_rumble() +{ + if (!has_rumble()) + return; +} + +std::string DirectInputController::get_button_name(uint64 button) const +{ + switch(button) + { + case kAxisXP: return "X+"; + case kAxisYP: return "Y+"; + + case kAxisXN: return "X-"; + case kAxisYN: return "Y-"; + + case kRotationXP: return "RX+"; + case kRotationYP: return "RY+"; + + case kRotationXN: return "RX-"; + case kRotationYN: return "RY-"; + + case kTriggerXP: return "Z+"; + case kTriggerYP: return "RZ+"; + + case kTriggerXN: return "Z-"; + case kTriggerYN: return "RZ-"; + } + + return base_type::get_button_name(button); +} + +ControllerState DirectInputController::raw_state() +{ + ControllerState result{}; + if (!is_connected()) + return result; + + HRESULT hr = m_device->Poll(); + if (FAILED(hr)) + { + if (hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED) + { + result.last_state = hr; + m_device->Acquire(); + } + + return result; + } + + DIJOYSTATE state{}; + hr = m_device->GetDeviceState(sizeof(state), &state); + if (FAILED(hr)) + { + if (hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED) + { + result.last_state = hr; + m_device->Acquire(); + } + + return result; + } + + result.last_state = hr; + + // buttons + for (size_t i = 0; i < std::size(state.rgbButtons); ++i) + { + if (HAS_BIT(state.rgbButtons[i], 7)) + { + result.buttons.set(i); + } + } + + // axis + constexpr float kThreshold = 0.001f; + float v = (float(state.lX - m_min_axis[0]) / float(m_max_axis[0] - m_min_axis[0])) * 2.0f - 1.0f; + if (std::abs(v) >= kThreshold) + result.axis.x = v; + + v = (float(state.lY - m_min_axis[1]) / float(m_max_axis[1] - m_min_axis[1])) * 2.0f - 1.0f; + if (std::abs(v) >= kThreshold) + result.axis.y = -v; + + // Right Stick + v = (float(state.lRx - m_min_axis[3]) / float(m_max_axis[3] - m_min_axis[3])) * 2.0f - 1.0f; + if (std::abs(v) >= kThreshold) + result.rotation.x = v; + + v = (float(state.lRy - m_min_axis[4]) / float(m_max_axis[4] - m_min_axis[4])) * 2.0f - 1.0f; + if (std::abs(v) >= kThreshold) + result.rotation.y = -v; + + // Trigger + v = (float(state.lZ - m_min_axis[2]) / float(m_max_axis[2] - m_min_axis[2])) * 2.0f - 1.0f; + if (std::abs(v) >= kThreshold) + result.trigger.x = v; + + v = (float(state.lRz - m_min_axis[5]) / float(m_max_axis[5] - m_min_axis[5])) * 2.0f - 1.0f; + if (std::abs(v) >= kThreshold) + result.trigger.y = -v; + + // dpad + const auto pov = state.rgdwPOV[0]; + if (pov != static_cast(-1)) + { + switch (pov) + { + case 0: result.buttons.set(kButtonUp); + break; + case 4500: result.buttons.set(kButtonUp); // up + right + case 9000: result.buttons.set(kButtonRight); + break; + case 13500: result.buttons.set(kButtonRight); // right + down + case 18000: result.buttons.set(kButtonDown); + break; + case 22500: result.buttons.set(kButtonDown); // down + left + case 27000: result.buttons.set(kButtonLeft); + break; + case 31500: result.buttons.set(kButtonLeft);; // left + up + result.buttons.set(kButtonUp); // left + up + break; + } + } + + return result; +} diff --git a/src/input/api/DirectInput/DirectInputController.h b/src/input/api/DirectInput/DirectInputController.h new file mode 100644 index 00000000..2ec371a2 --- /dev/null +++ b/src/input/api/DirectInput/DirectInputController.h @@ -0,0 +1,49 @@ +#pragma once + +#include "input/api/DirectInput/DirectInputControllerProvider.h" +#include "input/api/Controller.h" + +class DirectInputController : public Controller +{ +public: + DirectInputController(const GUID& guid); + DirectInputController(const GUID& guid, std::string_view display_name); + ~DirectInputController() override; + + std::string_view api_name() const override + { + static_assert(to_string(InputAPI::DirectInput) == "DirectInput"); + return to_string(InputAPI::DirectInput); + } + InputAPI::Type api() const override { return InputAPI::DirectInput; } + + void save(pugi::xml_node& node) override; + void load(const pugi::xml_node& node) override; + + bool connect() override; + bool is_connected() override; + + bool has_rumble() override; + + void start_rumble() override; + void stop_rumble() override; + + std::string get_button_name(uint64 button) const override; + + const GUID& get_guid() const { return m_guid; } + const GUID& get_product_guid() const { return m_product_guid; } + +protected: + ControllerState raw_state() override; + +private: + GUID m_guid; + GUID m_product_guid{}; + + std::shared_mutex m_mutex; + LPDIRECTINPUTDEVICE8 m_device = nullptr; + LPDIRECTINPUTEFFECT m_effect = nullptr; + + std::array m_min_axis{}; + std::array m_max_axis{}; +}; \ No newline at end of file diff --git a/src/input/api/DirectInput/DirectInputControllerProvider.cpp b/src/input/api/DirectInput/DirectInputControllerProvider.cpp new file mode 100644 index 00000000..063cb779 --- /dev/null +++ b/src/input/api/DirectInput/DirectInputControllerProvider.cpp @@ -0,0 +1,65 @@ +#include "input/api/DirectInput/DirectInputControllerProvider.h" + +#include "input/api/DirectInput/DirectInputController.h" + +DirectInputControllerProvider::DirectInputControllerProvider() +{ + /*m_module = LoadLibraryA("dinput8.dll"); + if (!m_module) + throw std::runtime_error("can't load any xinput dll"); + + m_DirectInput8Create = (decltype(&DirectInput8Create))GetProcAddress(m_module, "DirectInput8Create"); + m_GetdfDIJoystick = (decltype(&GetdfDIJoystick))GetProcAddress(m_module, "GetdfDIJoystick"); + + if (!m_DirectInput8Create) + { + FreeLibrary(m_module); + throw std::runtime_error("can't find the DirectInput8Create export"); + }*/ + + + const auto r = DirectInput8Create(GetModuleHandle(nullptr), DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&m_dinput8, nullptr); + if (FAILED(r) || !m_dinput8) + { + const auto error = GetLastError(); + //FreeLibrary(m_module); + throw std::runtime_error(fmt::format("can't create direct input object (error: {:#x})", error)); + } +} + +DirectInputControllerProvider::~DirectInputControllerProvider() +{ + if (m_dinput8) + m_dinput8->Release(); + + /*if (m_module) + FreeLibrary(m_module); + */ +} + +std::vector> DirectInputControllerProvider::get_controllers() +{ + std::vector> result; + + m_dinput8->EnumDevices(DI8DEVCLASS_GAMECTRL, + [](LPCDIDEVICEINSTANCE lpddi, LPVOID pvRef) -> BOOL + { + auto* controllers = (decltype(&result))pvRef; + + std::string display_name = boost::nowide::narrow(lpddi->tszProductName); + controllers->emplace_back(std::make_shared(lpddi->guidInstance, display_name)); + + return DIENUM_CONTINUE; + }, &result, DIEDFL_ALLDEVICES); + + + return result; +} + +LPCDIDATAFORMAT DirectInputControllerProvider::get_data_format() const +{ + /*if (m_GetdfDIJoystick) + return m_GetdfDIJoystick();*/ + + return GetdfDIJoystick(); +} diff --git a/src/input/api/DirectInput/DirectInputControllerProvider.h b/src/input/api/DirectInput/DirectInputControllerProvider.h new file mode 100644 index 00000000..5db883c0 --- /dev/null +++ b/src/input/api/DirectInput/DirectInputControllerProvider.h @@ -0,0 +1,37 @@ +#pragma once +#if BOOST_OS_WINDOWS + +#define DIRECTINPUT_VERSION 0x0800 + +#include + +#include "input/api/ControllerProvider.h" + +#ifndef HAS_DIRECTINPUT +#define HAS_DIRECTINPUT 1 +#endif + +class DirectInputControllerProvider : public ControllerProviderBase +{ +public: + DirectInputControllerProvider(); + ~DirectInputControllerProvider() override; + + inline static InputAPI::Type kAPIType = InputAPI::DirectInput; + InputAPI::Type api() const override { return kAPIType; } + + std::vector> get_controllers() override; + + IDirectInput8* get_dinput() const { return m_dinput8; } + LPCDIDATAFORMAT get_data_format() const; + +private: + HMODULE m_module = nullptr; + + decltype(&DirectInput8Create) m_DirectInput8Create; + decltype(&GetdfDIJoystick) m_GetdfDIJoystick = nullptr; + + IDirectInput8* m_dinput8 = nullptr; +}; + +#endif \ No newline at end of file diff --git a/src/input/api/GameCube/GameCubeController.cpp b/src/input/api/GameCube/GameCubeController.cpp new file mode 100644 index 00000000..538e6241 --- /dev/null +++ b/src/input/api/GameCube/GameCubeController.cpp @@ -0,0 +1,103 @@ +#include "input/api/GameCube/GameCubeController.h" + +#ifdef HAS_GAMECUBE + +GameCubeController::GameCubeController(uint32 adapter, uint32 index) + : base_type(fmt::format("{}_{}", adapter, index), fmt::format("Controller {}", index + 1)), m_adapter(adapter), + m_index(index) +{ + // update names if multiple adapters are connected + if (adapter > 0) + m_display_name = fmt::format("Controller {} ({})", index + 1, adapter); + + m_settings.axis.range = 1.20f; + m_settings.rotation.range = 1.25f; + m_settings.trigger.range = 1.07f; +} + +bool GameCubeController::is_connected() +{ + return m_provider->is_connected(m_adapter); +} + +bool GameCubeController::has_rumble() +{ + return m_provider->has_rumble_connected(m_adapter); +} + +void GameCubeController::start_rumble() +{ + if (m_settings.rumble <= 0) + return; + + m_provider->set_rumble_state(m_adapter, m_index, true); +} + +void GameCubeController::stop_rumble() +{ + m_provider->set_rumble_state(m_adapter, m_index, false); +} + +std::string GameCubeController::get_button_name(uint64 button) const +{ + switch (button) + { + case kButton0: return "A"; + case kButton1: return "B"; + case kButton2: return "X"; + case kButton3: return "Y"; + + case kButton4: return "Left"; + case kButton5: return "Right"; + case kButton6: return "Down"; + case kButton7: return "Up"; + + case kButton8: return "Start"; + case kButton9: return "Z"; + + case kButton10: return "Trigger R"; + case kButton11: return "Trigger L"; + } + + return base_type::get_button_name(button); +} + +ControllerState GameCubeController::raw_state() +{ + ControllerState result{}; + if (!is_connected()) + return result; + + const auto state = m_provider->get_state(m_adapter, m_index); + if (state.valid) + { + for (auto i = 0; i <= kButton11; ++i) + { + if (HAS_BIT(state.button, i)) + { + result.buttons.set(i); + } + } + + // printf("(%d, %d) - (%d, %d) - (%d, %d)\n", state.lstick_x, state.lstick_y, state.rstick_x, state.rstick_y, state.lstick, state.rstick); + result.axis.x = (float)state.lstick_x / std::numeric_limits::max(); + result.axis.x = (result.axis.x * 2.0f) - 1.0f; + + result.axis.y = (float)state.lstick_y / std::numeric_limits::max(); + result.axis.y = (result.axis.y * 2.0f) - 1.0f; + + result.rotation.x = (float)state.rstick_x / std::numeric_limits::max(); + result.rotation.x = (result.rotation.x * 2.0f) - 1.0f; + + result.rotation.y = (float)state.rstick_y / std::numeric_limits::max(); + result.rotation.y = (result.rotation.y * 2.0f) - 1.0f; + + + result.trigger.x = (float)state.lstick / std::numeric_limits::max(); + result.trigger.y = (float)state.rstick / std::numeric_limits::max(); + } + + return result; +} + +#endif \ No newline at end of file diff --git a/src/input/api/GameCube/GameCubeController.h b/src/input/api/GameCube/GameCubeController.h new file mode 100644 index 00000000..fd7fe237 --- /dev/null +++ b/src/input/api/GameCube/GameCubeController.h @@ -0,0 +1,34 @@ +#pragma once + +#include "input/api/Controller.h" +#include "input/api/GameCube/GameCubeControllerProvider.h" + +#ifdef HAS_GAMECUBE + +class GameCubeController : public Controller +{ +public: + GameCubeController(uint32 adapter, uint32 index); + + std::string_view api_name() const override + { + static_assert(to_string(InputAPI::GameCube) == "GameCube"); + return to_string(InputAPI::GameCube); + } + InputAPI::Type api() const override { return InputAPI::GameCube; } + + bool is_connected() override; + bool has_rumble() override; + void start_rumble() override; + void stop_rumble() override; + + std::string get_button_name(uint64 button) const override; + +protected: + ControllerState raw_state() override; + + uint32 m_adapter; + uint32 m_index; +}; + +#endif \ No newline at end of file diff --git a/src/input/api/GameCube/GameCubeControllerProvider.cpp b/src/input/api/GameCube/GameCubeControllerProvider.cpp new file mode 100644 index 00000000..1fe339f4 --- /dev/null +++ b/src/input/api/GameCube/GameCubeControllerProvider.cpp @@ -0,0 +1,388 @@ +#include "input/api/GameCube/GameCubeControllerProvider.h" +#include "input/api/GameCube/GameCubeController.h" +#include "util/libusbWrapper/libusbWrapper.h" + +#if HAS_GAMECUBE + +constexpr uint16_t kVendorId = 0x57e, kProductId = 0x337; + +GameCubeControllerProvider::GameCubeControllerProvider() +{ + m_libusb = libusbWrapper::getInstance(); + m_libusb->init(); + if(!m_libusb->isAvailable()) + throw std::runtime_error("libusbWrapper not available"); + m_libusb->p_libusb_init(&m_context); + + for(auto i = 0; i < kMaxAdapters; ++i) + { + auto device = fetch_usb_device(i); + if(std::get<0>(device)) + { + m_adapters[i].m_device_handle = std::get<0>(device); + m_adapters[i].m_endpoint_reader = std::get<1>(device); + m_adapters[i].m_endpoint_writer = std::get<2>(device); + } + } + + if (m_libusb->p_libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) + { + m_libusb->p_libusb_hotplug_register_callback(m_context, static_cast(LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT), + LIBUSB_HOTPLUG_NO_FLAGS, kVendorId, kProductId, LIBUSB_HOTPLUG_MATCH_ANY, &GameCubeControllerProvider::hotplug_event, this, &m_callback_handle); + } + + m_running = true; + m_reader_thread = std::thread(&GameCubeControllerProvider::reader_thread, this); + m_writer_thread = std::thread(&GameCubeControllerProvider::writer_thread, this); +} + +GameCubeControllerProvider::~GameCubeControllerProvider() +{ + if (m_running) + { + m_running = false; + m_writer_thread.join(); + m_reader_thread.join(); + } + + if (m_callback_handle) + { + m_libusb->p_libusb_hotplug_deregister_callback(m_context, m_callback_handle); + m_callback_handle = 0; + } + + for (auto& a : m_adapters) + { + m_libusb->p_libusb_close(a.m_device_handle); + } + + if (m_context) + { + m_libusb->p_libusb_exit(m_context); + m_context = nullptr; + } +} + +std::vector GameCubeControllerProvider::get_controllers() +{ + std::vector result; + + const auto adapter_count = get_adapter_count(); + for (uint32 adapter_index = 0; adapter_index < adapter_count && adapter_index < kMaxAdapters; ++adapter_index) + { + // adapter doesn't tell us which one is actually connected, so we return all of them + for (int index = 0; index < 4; ++index) + result.emplace_back(std::make_shared(adapter_index, index)); + } + + return result; +} + +uint32 GameCubeControllerProvider::get_adapter_count() const +{ + uint32 adapter_count = 0; + + libusb_device** devices; + const auto count = m_libusb->p_libusb_get_device_list(nullptr, &devices); + if (count < 0) + { + forceLog_printf((char*)fmt::format("libusb error {} at libusb_get_device_list: {}", static_cast(count), m_libusb->p_libusb_error_name(static_cast(count))).c_str()); + return adapter_count; + } + + for (ssize_t i = 0; i < count; ++i) + { + if (!devices[i]) + continue; + + libusb_device_descriptor desc; + int ret = m_libusb->p_libusb_get_device_descriptor(devices[i], &desc); + if (ret != 0) + { + forceLog_printf((char*)fmt::format("libusb error {} at libusb_get_device_descriptor: {}", ret, m_libusb->p_libusb_error_name(ret)).c_str()); + continue; + } + + if (desc.idVendor != kVendorId || desc.idProduct != kProductId) + continue; + + ++adapter_count; + } + + m_libusb->p_libusb_free_device_list(devices, 1); + return adapter_count; +} + +bool GameCubeControllerProvider::has_rumble_connected(uint32 adapter_index) const +{ + if (adapter_index >= m_adapters.size()) + return false; + + std::scoped_lock lock(m_adapters[adapter_index].m_state_mutex); + return m_adapters[adapter_index].m_rumble_connected; +} + +bool GameCubeControllerProvider::is_connected(uint32 adapter_index) const +{ + if (adapter_index >= m_adapters.size()) + return false; + + return m_adapters[adapter_index].m_device_handle != nullptr; +} + +void GameCubeControllerProvider::set_rumble_state(uint32 adapter_index, uint32 index, bool state) +{ + if (adapter_index >= m_adapters.size()) + return; + + if (index >= kMaxIndex) + return; + + std::scoped_lock lock(m_writer_mutex); + m_adapters[adapter_index].rumble_states[index] = state; + m_rumble_changed = true; + m_writer_cond.notify_all(); +} + +GameCubeControllerProvider::GCState GameCubeControllerProvider::get_state(uint32 adapter_index, uint32 index) +{ + if (adapter_index >= m_adapters.size()) + return {}; + + if (index >= kMaxIndex) + return {}; + + std::scoped_lock lock(m_adapters[adapter_index].m_state_mutex); + return m_adapters[adapter_index].m_states[index]; +} + +#ifdef interface +#undef interface +#endif + +std::tuple GameCubeControllerProvider::fetch_usb_device(uint32 adapter) const +{ + std::tuple result{nullptr, 0xFF, 0xFF}; + + libusb_device** devices; + const auto count = m_libusb->p_libusb_get_device_list(nullptr, &devices); + if (count < 0) + { + forceLog_printf((char*)fmt::format("libusb error {} at libusb_get_device_list: {}", static_cast(count), m_libusb->p_libusb_error_name(static_cast(count))).c_str()); + return result; + } + + int adapter_index = 0; + for (ssize_t i = 0; i < count; ++i) + { + if (!devices[i]) + continue; + + libusb_device_descriptor desc; + int ret = m_libusb->p_libusb_get_device_descriptor(devices[i], &desc); + if (ret != 0) + { + forceLog_printf((char*)fmt::format("libusb error {} at libusb_get_device_descriptor: {}", ret, m_libusb->p_libusb_error_name(ret)).c_str()); + continue; + } + + if (desc.idVendor != kVendorId || desc.idProduct != kProductId) + continue; + + if (adapter != adapter_index++) + continue; + + libusb_device_handle* device_handle; + ret = m_libusb->p_libusb_open(devices[i], &device_handle); + if (ret != 0) + { + forceLog_printf((char*)fmt::format("libusb error {} at libusb_open: {}", ret, m_libusb->p_libusb_error_name(ret)).c_str()); + continue; + } + + if (m_libusb->p_libusb_kernel_driver_active(device_handle, 0) == 1) + { + ret = m_libusb->p_libusb_detach_kernel_driver(device_handle, 0); + if (ret != 0) + { + forceLog_printf((char*)fmt::format("libusb error {} at libusb_detach_kernel_driver: {}", ret, m_libusb->p_libusb_error_name(ret)).c_str()); + m_libusb->p_libusb_close(device_handle); + continue; + } + } + + ret = m_libusb->p_libusb_claim_interface(device_handle, 0); + if (ret != 0) + { + forceLog_printf((char*)fmt::format("libusb error {} at libusb_claim_interface: {}", ret, m_libusb->p_libusb_error_name(ret)).c_str()); + m_libusb->p_libusb_close(device_handle); + continue; + } + + libusb_config_descriptor* config = nullptr; + m_libusb->p_libusb_get_config_descriptor(devices[i], 0, &config); + for (auto ic = 0; ic < config->bNumInterfaces; ic++) + { + const auto& interface = config->interface[ic]; + for (auto j = 0; j < interface.num_altsetting; j++) + { + const auto& interface_desc = interface.altsetting[j]; + for (auto k = 0; k < interface_desc.bNumEndpoints; k++) + { + const auto& endpoint = interface_desc.endpoint[k]; + if (endpoint.bEndpointAddress & LIBUSB_ENDPOINT_IN) + std::get<1>(result) = endpoint.bEndpointAddress; + else + std::get<2>(result) = endpoint.bEndpointAddress; + } + } + } + + m_libusb->p_libusb_free_config_descriptor(config); + + // start polling + int size = 0; + uint8_t payload = 0x13; + m_libusb->p_libusb_interrupt_transfer(device_handle, std::get<2>(result), &payload, sizeof(payload), &size, 25); + + std::get<0>(result) = device_handle; + + break; + } + + m_libusb->p_libusb_free_device_list(devices, 1); + return result; +} + +void GameCubeControllerProvider::reader_thread() +{ + SetThreadName("GCControllerAdapter::reader_thread"); + while (m_running.load(std::memory_order_relaxed)) + { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + std::this_thread::yield(); + + for(auto& adapter : m_adapters) + { + if (!adapter.m_device_handle) + continue; + + std::array data{}; + int read; + const int result = m_libusb->p_libusb_interrupt_transfer(adapter.m_device_handle, adapter.m_endpoint_reader, data.data(), static_cast(data.size()), &read, 25); + if (result == 0) + { + /* + byte 1 + 0x10 NORMAL STATE + 0x20 WAVEBIRD STATE + 0x04 RUMBLE POWER + */ + for (int i = 0; i < 4; ++i) + { + GCState state; + state.valid = true; + state.button = *(uint16*)&data[1 + (i * 9) + 1]; + state.lstick_x = data[1 + (i * 9) + 3]; + state.lstick_y = data[1 + (i * 9) + 4]; + state.rstick_x = data[1 + (i * 9) + 5]; + state.rstick_y = data[1 + (i * 9) + 6]; + state.lstick = data[1 + (i * 9) + 7]; + state.rstick = data[1 + (i * 9) + 8]; + + std::scoped_lock lock(adapter.m_state_mutex); + adapter.m_rumble_connected = HAS_FLAG(data[1], 4); + adapter.m_states[i] = state; + } + } + else if (result == LIBUSB_ERROR_NO_DEVICE || result == LIBUSB_ERROR_IO) + { + forceLog_printf((char*)fmt::format("libusb error {} at libusb_interrupt_transfer: {}", result, m_libusb->p_libusb_error_name(result)).c_str()); + if (const auto handle = adapter.m_device_handle.exchange(nullptr)) + m_libusb->p_libusb_close(handle); + } + else { forceLog_printf((char*)fmt::format("libusb error {} at libusb_interrupt_transfer: {}", result, m_libusb->p_libusb_error_name(result)).c_str()); } + } + } +} + +void GameCubeControllerProvider::writer_thread() +{ + SetThreadName("GCControllerAdapter::writer_thread"); + + std::array, kMaxAdapters> rumble_states{}; + + while (m_running.load(std::memory_order_relaxed)) + { + std::unique_lock lock(m_writer_mutex); + if (!m_rumble_changed && m_writer_cond.wait_for(lock, std::chrono::milliseconds(250)) == std::cv_status::timeout) + { + if (!m_running) + return; + + continue; + } + + bool cmd_sent = false; + for (size_t i = 0; i < kMaxAdapters; ++i) + { + auto& adapter = m_adapters[i]; + if (!adapter.m_device_handle) + continue; + + if (adapter.rumble_states == rumble_states[i]) + continue; + + rumble_states[i] = adapter.rumble_states; + m_rumble_changed = false; + lock.unlock(); + + std::array rumble{ 0x11, rumble_states[i][0],rumble_states[i][1],rumble_states[i][2], rumble_states[i][3] }; + + int written; + const int result = m_libusb->p_libusb_interrupt_transfer(adapter.m_device_handle, adapter.m_endpoint_writer, rumble.data(), static_cast(rumble.size()), &written, 25); + if (result != 0) { forceLog_printf((char*)fmt::format("libusb error {} at libusb_interrupt_transfer: {}", result, m_libusb->p_libusb_error_name(result)).c_str()); } + cmd_sent = true; + + lock.lock(); + } + + if(cmd_sent) + { + lock.unlock(); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } + } +} + +int GameCubeControllerProvider::hotplug_event(libusb_context* ctx, libusb_device* dev, libusb_hotplug_event event, void* user_data) +{ + auto* thisptr = static_cast(user_data); + if (LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED == event) + { + for (auto i = 0; i < kMaxAdapters; ++i) + { + if (thisptr->m_adapters[i].m_device_handle) + continue; + + auto device = thisptr->fetch_usb_device(i); + if (std::get<0>(device)) + { + thisptr->m_adapters[i].m_endpoint_reader = std::get<1>(device); + thisptr->m_adapters[i].m_endpoint_writer = std::get<2>(device); + + thisptr->m_adapters[i].m_device_handle = std::get<0>(device); + } + } + } + /*else + { + const auto device_handle = thisptr->m_device_handle.exchange(nullptr); + if (device_handle) + thisptr->m_libusb->p_libusb_close(device_handle); + }*/ + + return 0; +} + +#endif \ No newline at end of file diff --git a/src/input/api/GameCube/GameCubeControllerProvider.h b/src/input/api/GameCube/GameCubeControllerProvider.h new file mode 100644 index 00000000..08f847a0 --- /dev/null +++ b/src/input/api/GameCube/GameCubeControllerProvider.h @@ -0,0 +1,79 @@ +#pragma once + +#include "util/libusbWrapper/libusbWrapper.h" +#include "input/api/ControllerProvider.h" + +#ifdef HAS_GAMECUBE + +class GameCubeControllerProvider : public ControllerProviderBase +{ + friend class DSUController; +public: + constexpr static size_t kMaxAdapters = 4; + constexpr static size_t kMaxIndex = 4; + + GameCubeControllerProvider(); + ~GameCubeControllerProvider(); + + inline static InputAPI::Type kAPIType = InputAPI::GameCube; + InputAPI::Type api() const override { return kAPIType; } + + std::vector> get_controllers() override; + + uint32 get_adapter_count() const; + bool has_rumble_connected(uint32 adapter_index) const; + bool is_connected(uint32 adapter_index) const; + + void set_rumble_state(uint32 adapter_index, uint32 index, bool state); + + struct GCState + { + bool valid = false; + uint16 button = 0; + + uint8 lstick_x = 0; + uint8 lstick_y = 0; + + uint8 rstick_x = 0; + uint8 rstick_y = 0; + + uint8 lstick = 0; + uint8 rstick = 0; + }; + GCState get_state(uint32 adapter_index, uint32 index); + +private: + std::shared_ptr m_libusb; + libusb_context* m_context = nullptr; + + std::atomic_bool m_running = false; + std::thread m_reader_thread, m_writer_thread; + + void reader_thread(); + void writer_thread(); + + // handle, endpoint_reader, endpoint_writer + std::tuple fetch_usb_device(uint32 adapter) const; + + std::mutex m_writer_mutex; + std::condition_variable m_writer_cond; + bool m_rumble_changed = false; + struct Adapter + { + std::atomic m_device_handle{}; + + uint8 m_endpoint_reader = 0xFF, m_endpoint_writer = 0xFF; + + mutable std::mutex m_state_mutex; + std::array m_states{}; + bool m_rumble_connected = false; + + std::array rumble_states{}; + }; + std::array m_adapters; + + libusb_hotplug_callback_handle m_callback_handle = 0; + static int hotplug_event(struct libusb_context* ctx, struct libusb_device* dev, libusb_hotplug_event event, void* user_data); +}; + +#endif \ No newline at end of file diff --git a/src/input/api/InputAPI.h b/src/input/api/InputAPI.h new file mode 100644 index 00000000..07ae16cf --- /dev/null +++ b/src/input/api/InputAPI.h @@ -0,0 +1,79 @@ +#pragma once + +#include "util/helpers/helpers.h" + +namespace InputAPI +{ + enum Type + { + Keyboard, + SDLController, + XInput, + DirectInput, + DSUClient, + GameCube, + Wiimote, + + WGIGamepad, + WGIRawController, + + MAX + }; + + constexpr std::string_view to_string(Type type) + { + switch (type) + { + case Keyboard: + return "Keyboard"; + case DirectInput: + return "DirectInput"; + case XInput: + return "XInput"; + case Wiimote: + return "Wiimote"; + case GameCube: + return "GameCube"; + case DSUClient: + return "DSUController"; + case WGIGamepad: + return "WGIGamepad"; + case WGIRawController: + return "WGIRawController"; + case SDLController: + return "SDLController"; + default: + break; + } + + throw std::runtime_error(fmt::format("unknown input api: {}", to_underlying(type))); + } + + constexpr Type from_string(std::string_view str) + { + if (str == to_string(Keyboard)) + return Keyboard; + else if (str == to_string(DirectInput)) + return DirectInput; + else if (str == to_string(XInput)) + return XInput; + else if (str == to_string(Wiimote)) + return Wiimote; + else if (str == to_string(GameCube)) + return GameCube; + else if (str == to_string(DSUClient)) + return DSUClient; + else if (str == to_string(SDLController)) + return SDLController; + else if (str == "DSU") // legacy + return DSUClient; + + //else if (str == "WGIGamepad") + // return WGIGamepad; + // + //else if (str == "WGIRawController") + // return WGIRawController; + + throw std::runtime_error(fmt::format("unknown input api: {}", str)); + } +} diff --git a/src/input/api/Keyboard/KeyboardController.cpp b/src/input/api/Keyboard/KeyboardController.cpp new file mode 100644 index 00000000..d8bc0a04 --- /dev/null +++ b/src/input/api/Keyboard/KeyboardController.cpp @@ -0,0 +1,66 @@ +#include "input/api/Keyboard/KeyboardController.h" + +#include "gui/guiWrapper.h" + +KeyboardController::KeyboardController() + : base_type("keyboard", "Keyboard") +{ + +} + +std::string KeyboardController::get_button_name(uint64 button) const +{ +#if BOOST_OS_WINDOWS + LONG scan_code = MapVirtualKeyA((UINT)button, MAPVK_VK_TO_VSC_EX); + if(HIBYTE(scan_code)) + scan_code |= 0x100; + + // because MapVirtualKey strips the extended bit for some keys + switch (button) + { + case VK_LEFT: case VK_UP: case VK_RIGHT: case VK_DOWN: // arrow keys + case VK_PRIOR: case VK_NEXT: // page up and page down + case VK_END: case VK_HOME: + case VK_INSERT: case VK_DELETE: + case VK_DIVIDE: // numpad slash + case VK_NUMLOCK: + { + scan_code |= 0x100; // set extended bit + break; + } + } + + scan_code <<= 16; + + char key_name[128]; + if (GetKeyNameTextA(scan_code, key_name, std::size(key_name)) != 0) + return key_name; +#endif + + return fmt::format("key_{}", button); +} + +extern WindowInfo g_window_info; + +ControllerState KeyboardController::raw_state() +{ + ControllerState result{}; + if (g_window_info.app_active) + { + static_assert(result.buttons.size() == std::size(g_window_info.keydown), "invalid size"); + for (uint32 i = wxKeyCode::WXK_BACK; i < result.buttons.size(); ++i) + { + if(const bool v = g_window_info.keydown[i]) + { + result.buttons.set(i, v); + } + } + // ignore generic key codes on Windows when there is also a left/right variant +#if BOOST_OS_WINDOWS + result.buttons.set(VK_SHIFT, false); + result.buttons.set(VK_CONTROL, false); + result.buttons.set(VK_MENU, false); +#endif + } + return result; +} diff --git a/src/input/api/Keyboard/KeyboardController.h b/src/input/api/Keyboard/KeyboardController.h new file mode 100644 index 00000000..281d3aff --- /dev/null +++ b/src/input/api/Keyboard/KeyboardController.h @@ -0,0 +1,27 @@ +#pragma once + +#include "input/api/Keyboard/KeyboardControllerProvider.h" +#include "input/api/Controller.h" + +class KeyboardController : public Controller +{ +public: + KeyboardController(); + + std::string_view api_name() const override // TODO: use constexpr virtual function with c++20 + { + static_assert(to_string(InputAPI::Keyboard) == "Keyboard"); + return to_string(InputAPI::Keyboard); + } + InputAPI::Type api() const override { return InputAPI::Keyboard; } + + bool is_connected() override { return true; } + + bool has_axis() const override { return false; } + + std::string get_button_name(uint64 button) const override; + +protected: + ControllerState raw_state() override; + +}; diff --git a/src/input/api/Keyboard/KeyboardControllerProvider.cpp b/src/input/api/Keyboard/KeyboardControllerProvider.cpp new file mode 100644 index 00000000..af91e949 --- /dev/null +++ b/src/input/api/Keyboard/KeyboardControllerProvider.cpp @@ -0,0 +1,10 @@ +#include "input/api/Keyboard/KeyboardControllerProvider.h" + +#include "input/api/Keyboard/KeyboardController.h" + +std::vector> KeyboardControllerProvider::get_controllers() +{ + std::vector> result; + result.emplace_back(std::make_shared()); + return result; +} diff --git a/src/input/api/Keyboard/KeyboardControllerProvider.h b/src/input/api/Keyboard/KeyboardControllerProvider.h new file mode 100644 index 00000000..9aea80ec --- /dev/null +++ b/src/input/api/Keyboard/KeyboardControllerProvider.h @@ -0,0 +1,17 @@ +#pragma once + +#include "input/api/ControllerProvider.h" + +#ifndef HAS_KEYBOARD +#define HAS_KEYBOARD 1 +#endif + +class KeyboardControllerProvider : public ControllerProviderBase +{ + friend class KeyboardController; +public: + inline static InputAPI::Type kAPIType = InputAPI::Keyboard; + InputAPI::Type api() const override { return kAPIType; } + + std::vector> get_controllers() override; +}; diff --git a/src/input/api/SDL/SDLController.cpp b/src/input/api/SDL/SDLController.cpp new file mode 100644 index 00000000..0a0e3067 --- /dev/null +++ b/src/input/api/SDL/SDLController.cpp @@ -0,0 +1,173 @@ +#include "input/api/SDL/SDLController.h" + +#include "input/api/SDL/SDLControllerProvider.h" + +SDLController::SDLController(const SDL_JoystickGUID& guid, size_t guid_index) + : base_type(fmt::format("{}_", guid_index), fmt::format("Controller {}", guid_index + 1)), m_guid_index(guid_index), + m_guid(guid) +{ + char tmp[64]; + SDL_JoystickGetGUIDString(m_guid, tmp, std::size(tmp)); + m_uuid += tmp; +} + +SDLController::SDLController(const SDL_JoystickGUID& guid, size_t guid_index, std::string_view display_name) + : base_type(fmt::format("{}_", guid_index), display_name), m_guid_index(guid_index), m_guid(guid) +{ + char tmp[64]; + SDL_JoystickGetGUIDString(m_guid, tmp, std::size(tmp)); + m_uuid += tmp; +} + +SDLController::~SDLController() +{ + if (m_controller) + SDL_GameControllerClose(m_controller); +} + +bool SDLController::is_connected() +{ + std::scoped_lock lock(m_controller_mutex); + if (!m_controller) + { + return false; + } + + if (!SDL_GameControllerGetAttached(m_controller)) + { + m_controller = nullptr; + return false; + } + + return true; +} + +bool SDLController::connect() +{ + if (is_connected()) + { + return true; + } + + m_has_rumble = false; + + const auto index = m_provider->get_index(m_guid_index, m_guid); + + std::scoped_lock lock(m_controller_mutex); + m_diid = SDL_JoystickGetDeviceInstanceID(index); + if (m_diid == -1) + return false; + + m_controller = SDL_GameControllerFromInstanceID(m_diid); + if (!m_controller) + { + m_controller = SDL_GameControllerOpen(index); + if (!m_controller) + return false; + } + + if (const char* name = SDL_GameControllerName(m_controller)) + m_display_name = name; + + for (int i = 0; i < SDL_CONTROLLER_BUTTON_MAX; ++i) + { + m_buttons[i] = SDL_GameControllerHasButton(m_controller, (SDL_GameControllerButton)i); + } + + for (int i = 0; i < SDL_CONTROLLER_AXIS_MAX; ++i) + { + m_axis[i] = SDL_GameControllerHasAxis(m_controller, (SDL_GameControllerAxis)i); + } + + if (SDL_GameControllerHasSensor(m_controller, SDL_SENSOR_ACCEL)) + { + m_has_accel = true; + SDL_GameControllerSetSensorEnabled(m_controller, SDL_SENSOR_ACCEL, SDL_TRUE); + } + + if (SDL_GameControllerHasSensor(m_controller, SDL_SENSOR_GYRO)) + { + m_has_gyro = true; + SDL_GameControllerSetSensorEnabled(m_controller, SDL_SENSOR_GYRO, SDL_TRUE); + } + + m_has_rumble = SDL_GameControllerRumble(m_controller, 0, 0, 0) == 0; + return true; +} + +void SDLController::start_rumble() +{ + std::scoped_lock lock(m_controller_mutex); + if (is_connected() && !m_has_rumble) + return; + + if (m_settings.rumble <= 0) + return; + + SDL_GameControllerRumble(m_controller, (Uint16)(m_settings.rumble * 0xFFFF), (Uint16)(m_settings.rumble * 0xFFFF), + 5 * 1000); +} + +void SDLController::stop_rumble() +{ + std::scoped_lock lock(m_controller_mutex); + if (is_connected() && !m_has_rumble) + return; + + SDL_GameControllerRumble(m_controller, 0, 0, 0); +} + +MotionSample SDLController::get_motion_sample() +{ + if (is_connected() && has_motion()) + { + return m_provider->motion_sample(m_diid); + } + + return {}; +} + +std::string SDLController::get_button_name(uint64 button) const +{ + if (const char* name = SDL_GameControllerGetStringForButton((SDL_GameControllerButton)button)) + return name; + + return base_type::get_button_name(button); +} + +ControllerState SDLController::raw_state() +{ + ControllerState result{}; + + std::scoped_lock lock(m_controller_mutex); + if (!is_connected()) + return result; + + for (int i = 0; i < SDL_CONTROLLER_BUTTON_MAX; ++i) + { + if (m_buttons[i] && SDL_GameControllerGetButton(m_controller, (SDL_GameControllerButton)i)) + { + result.buttons.set(i); + } + } + + if (m_axis[SDL_CONTROLLER_AXIS_LEFTX]) + result.axis.x = (float)SDL_GameControllerGetAxis(m_controller, SDL_CONTROLLER_AXIS_LEFTX) / 32767.0f; + + if (m_axis[SDL_CONTROLLER_AXIS_LEFTY]) + result.axis.y = (float)SDL_GameControllerGetAxis(m_controller, SDL_CONTROLLER_AXIS_LEFTY) / 32767.0f; + + if (m_axis[SDL_CONTROLLER_AXIS_RIGHTX]) + result.rotation.x = (float)SDL_GameControllerGetAxis(m_controller, SDL_CONTROLLER_AXIS_RIGHTX) / 32767.0f; + + if (m_axis[SDL_CONTROLLER_AXIS_RIGHTY]) + result.rotation.y = (float)SDL_GameControllerGetAxis(m_controller, SDL_CONTROLLER_AXIS_RIGHTY) / 32767.0f; + + if (m_axis[SDL_CONTROLLER_AXIS_TRIGGERLEFT]) + result.trigger.x = (float)SDL_GameControllerGetAxis(m_controller, SDL_CONTROLLER_AXIS_TRIGGERLEFT) / 32767.0f; + + if (m_axis[SDL_CONTROLLER_AXIS_TRIGGERRIGHT]) + result.trigger.y = (float)SDL_GameControllerGetAxis(m_controller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT) / 32767.0f; + + return result; +} diff --git a/src/input/api/SDL/SDLController.h b/src/input/api/SDL/SDLController.h new file mode 100644 index 00000000..0a0aa183 --- /dev/null +++ b/src/input/api/SDL/SDLController.h @@ -0,0 +1,60 @@ +#pragma once + +#include "input/api/Controller.h" +#include "input/api/SDL/SDLControllerProvider.h" + +#include + +class SDLController : public Controller +{ +public: + SDLController(const SDL_JoystickGUID& guid, size_t guid_index); + SDLController(const SDL_JoystickGUID& guid, size_t guid_index, std::string_view display_name); + + ~SDLController() override; + + std::string_view api_name() const override + { + static_assert(to_string(InputAPI::SDLController) == "SDLController"); + return to_string(InputAPI::SDLController); + } + InputAPI::Type api() const override { return InputAPI::SDLController; } + + bool is_connected() override; + bool connect() override; + + bool has_motion() override { return m_has_gyro && m_has_accel; } + bool has_rumble() override { return m_has_rumble; } + + void start_rumble() override; + void stop_rumble() override; + + MotionSample get_motion_sample() override; + + std::string get_button_name(uint64 button) const override; + const SDL_JoystickGUID& get_guid() const { return m_guid; } + + constexpr static SDL_JoystickGUID kLeftJoyCon{ 0x03, 0x00, 0x00, 0x00, 0x7e, 0x05, 0x00, 0x00, 0x06, 0x20, 0x00, 0x00, 0x00, 0x00,0x68 ,0x00 }; + constexpr static SDL_JoystickGUID kRightJoyCon{ 0x03, 0x00, 0x00, 0x00, 0x7e, 0x05, 0x00, 0x00, 0x07, 0x20, 0x00, 0x00, 0x00, 0x00, 0x68, 0x00 }; + constexpr static SDL_JoystickGUID kSwitchProController{ 0x03, 0x00, 0x00, 0x00, 0x7e, 0x05, 0x00, 0x00, 0x09, 0x20, 0x00, 0x00, 0x00, 0x00, 0x68, 0x00 }; + +protected: + ControllerState raw_state() override; + +private: + inline static SDL_JoystickGUID kEmptyGUID{}; + + size_t m_guid_index; + SDL_JoystickGUID m_guid; + std::recursive_mutex m_controller_mutex; + SDL_GameController* m_controller = nullptr; + SDL_JoystickID m_diid = -1; + + bool m_has_gyro = false; + bool m_has_accel = false; + bool m_has_rumble = false; + + std::array m_buttons{}; + std::array m_axis{}; +}; + diff --git a/src/input/api/SDL/SDLControllerProvider.cpp b/src/input/api/SDL/SDLControllerProvider.cpp new file mode 100644 index 00000000..c700eab7 --- /dev/null +++ b/src/input/api/SDL/SDLControllerProvider.cpp @@ -0,0 +1,244 @@ +#include "input/api/SDL/SDLControllerProvider.h" + +#include "input/api/SDL/SDLController.h" +#include "util/helpers/TempState.h" + +#include +#include + +struct SDL_JoystickGUIDHash +{ + std::size_t operator()(const SDL_JoystickGUID& guid) const + { + return boost::hash_value(guid.data); + } +}; + +SDLControllerProvider::SDLControllerProvider() +{ + SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); + + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4, "1"); + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5, "1"); + + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1"); + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); + + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE, "1"); + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, "1"); + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1"); + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_STADIA, "1"); + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_STEAM, "1"); + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_LUNA, "1"); + + if (SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC | SDL_INIT_EVENTS) < 0) + throw std::runtime_error(fmt::format("couldn't initialize SDL: %s", SDL_GetError())); + + + if (SDL_GameControllerEventState(SDL_ENABLE) < 0) { + forceLog_printf("Couldn't enable SDL gamecontroller event polling: %s", SDL_GetError()); + } + + m_running = true; + m_thread = std::thread(&SDLControllerProvider::event_thread, this); +} + +SDLControllerProvider::~SDLControllerProvider() +{ + if (m_running) + { + m_running = false; + // wake the thread with a quit event if it's currently waiting for events + SDL_Event evt; + evt.type = SDL_QUIT; + SDL_PushEvent(&evt); + // wait until thread exited + m_thread.join(); + } + + SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC | SDL_INIT_EVENTS); +} + +std::vector> SDLControllerProvider::get_controllers() +{ + std::vector> result; + + std::unordered_map guid_counter; + + TempState lock(SDL_LockJoysticks, SDL_UnlockJoysticks); + for (int i = 0; i < SDL_NumJoysticks(); ++i) + { + if (SDL_JoystickGetDeviceType(i) == SDL_JOYSTICK_TYPE_GAMECONTROLLER) + { + const auto guid = SDL_JoystickGetDeviceGUID(i); + + const auto it = guid_counter.try_emplace(guid, 0); + + if (auto* controller = SDL_GameControllerOpen(i)) + { + const char* name = SDL_GameControllerName(controller); + + result.emplace_back(std::make_shared(guid, it.first->second, name)); + SDL_GameControllerClose(controller); + } + else + result.emplace_back(std::make_shared(guid, it.first->second)); + + ++it.first->second; + } + } + + return result; +} + +int SDLControllerProvider::get_index(size_t guid_index, const SDL_JoystickGUID& guid) const +{ + size_t index = 0; + + TempState lock(SDL_LockJoysticks, SDL_UnlockJoysticks); + for (int i = 0; i < SDL_NumJoysticks(); ++i) + { + if (SDL_JoystickGetDeviceType(i) == SDL_JOYSTICK_TYPE_GAMECONTROLLER) + { + if(guid == SDL_JoystickGetDeviceGUID(i)) + { + if (index == guid_index) + { + return i; + } + + ++index; + } + + } + } + + return -1; +} + +MotionSample SDLControllerProvider::motion_sample(int diid) +{ + std::scoped_lock lock(m_motion_data_mtx[diid]); + return m_motion_data[diid]; +} + +void SDLControllerProvider::event_thread() +{ + SetThreadName("SDLControllerProvider::event_thread"); + while (m_running.load(std::memory_order_relaxed)) + { + SDL_Event event{}; + SDL_WaitEvent(&event); + + switch (event.type) + { + case SDL_QUIT: + m_running = false; + return; + + case SDL_CONTROLLERAXISMOTION: /**< Game controller axis motion */ + { + break; + } + case SDL_CONTROLLERBUTTONDOWN: /**< Game controller button pressed */ + { + break; + } + case SDL_CONTROLLERBUTTONUP: /**< Game controller button released */ + { + break; + } + case SDL_CONTROLLERDEVICEADDED: /**< A new Game controller has been inserted into the system */ + { + InputManager::instance().on_device_changed(); + break; + } + case SDL_CONTROLLERDEVICEREMOVED: /**< An opened Game controller has been removed */ + { + InputManager::instance().on_device_changed(); + break; + } + case SDL_CONTROLLERDEVICEREMAPPED: /**< The controller mapping was updated */ + { + break; + } + case SDL_CONTROLLERTOUCHPADDOWN: /**< Game controller touchpad was touched */ + { + break; + } + case SDL_CONTROLLERTOUCHPADMOTION: /**< Game controller touchpad finger was moved */ + { + break; + } + case SDL_CONTROLLERTOUCHPADUP: /**< Game controller touchpad finger was lifted */ + { + break; + } + case SDL_CONTROLLERSENSORUPDATE: /**< Game controller sensor was updated */ + { + const auto index = event.csensor.which; + const auto ts = event.csensor.timestamp; + auto& motionTracking = m_motion_tracking[index]; + + if (event.csensor.sensor == SDL_SENSOR_ACCEL) + { + const auto dif = ts - motionTracking.lastTimestampAccel; + if (dif <= 0) + break; + + if (dif >= 10000) + { + motionTracking.hasAcc = false; + motionTracking.hasGyro = false; + motionTracking.lastTimestampAccel = ts; + break; + } + + motionTracking.lastTimestampAccel = ts; + motionTracking.acc[0] = -event.csensor.data[0] / 9.81f; + motionTracking.acc[1] = -event.csensor.data[1] / 9.81f; + motionTracking.acc[2] = -event.csensor.data[2] / 9.81f; + motionTracking.hasAcc = true; + } + if (event.csensor.sensor == SDL_SENSOR_GYRO) + { + const auto dif = ts - motionTracking.lastTimestampGyro; + if (dif <= 0) + break; + + if (dif >= 10000) + { + motionTracking.hasAcc = false; + motionTracking.hasGyro = false; + motionTracking.lastTimestampGyro = ts; + break; + } + motionTracking.lastTimestampGyro = ts; + motionTracking.gyro[0] = event.csensor.data[0]; + motionTracking.gyro[1] = -event.csensor.data[1]; + motionTracking.gyro[2] = -event.csensor.data[2]; + motionTracking.hasGyro = true; + } + if (motionTracking.hasAcc && motionTracking.hasGyro) + { + auto ts = std::max(motionTracking.lastTimestampGyro, motionTracking.lastTimestampAccel); + if (ts > motionTracking.lastTimestampIntegrate) + { + const auto tsDif = ts - motionTracking.lastTimestampIntegrate; + motionTracking.lastTimestampIntegrate = ts; + float tsDifD = (float)tsDif / 1000.0f; + if (tsDifD >= 1.0f) + tsDifD = 1.0f; + m_motion_handler[index].processMotionSample(tsDifD, motionTracking.gyro.x, motionTracking.gyro.y, motionTracking.gyro.z, motionTracking.acc.x, -motionTracking.acc.y, -motionTracking.acc.z); + + std::scoped_lock lock(m_motion_data_mtx[index]); + m_motion_data[index] = m_motion_handler[index].getMotionSample(); + } + motionTracking.hasAcc = false; + motionTracking.hasGyro = false; + } + break; + } + } + } +} diff --git a/src/input/api/SDL/SDLControllerProvider.h b/src/input/api/SDL/SDLControllerProvider.h new file mode 100644 index 00000000..b736ba75 --- /dev/null +++ b/src/input/api/SDL/SDLControllerProvider.h @@ -0,0 +1,54 @@ +#pragma once +#include +#include "input/motion/MotionHandler.h" +#include "input/api/ControllerProvider.h" + +#ifndef HAS_SDL +#define HAS_SDL 1 +#endif + +static bool operator==(const SDL_JoystickGUID& g1, const SDL_JoystickGUID& g2) +{ + return memcmp(&g1, &g2, sizeof(SDL_JoystickGUID)) == 0; +} + +class SDLControllerProvider : public ControllerProviderBase +{ + friend class SDLController; +public: + SDLControllerProvider(); + ~SDLControllerProvider(); + + inline static InputAPI::Type kAPIType = InputAPI::SDLController; + InputAPI::Type api() const override { return kAPIType; } + + std::vector> get_controllers() override; + + int get_index(size_t guid_index, const SDL_JoystickGUID& guid) const; + + MotionSample motion_sample(int diid); + +private: + void event_thread(); + + std::atomic_bool m_running = false; + std::thread m_thread; + + std::array m_motion_handler{}; + std::array m_motion_data{}; + std::array m_motion_data_mtx{}; + + struct MotionInfoTracking + { + uint64 lastTimestampGyro{}; + uint64 lastTimestampAccel{}; + uint64 lastTimestampIntegrate{}; + bool hasGyro{}; + bool hasAcc{}; + glm::vec3 gyro{}; + glm::vec3 acc{}; + }; + + std::array m_motion_tracking{}; + +}; diff --git a/src/input/api/Wiimote/NativeWiimoteController.cpp b/src/input/api/Wiimote/NativeWiimoteController.cpp new file mode 100644 index 00000000..1396f1b4 --- /dev/null +++ b/src/input/api/Wiimote/NativeWiimoteController.cpp @@ -0,0 +1,233 @@ +#include "input/api/Wiimote/NativeWiimoteController.h" + +#include "input/api/Wiimote/WiimoteControllerProvider.h" + +#include + +NativeWiimoteController::NativeWiimoteController(size_t index) + : base_type(fmt::format("{}", index), fmt::format("Controller {}", index + 1)), m_index(index) +{ + m_settings.motion = true; +} + +void NativeWiimoteController::save(pugi::xml_node& node) +{ + base_type::save(node); + + node.append_child("packet_delay").append_child(pugi::node_pcdata).set_value( + fmt::format("{}", m_packet_delay).c_str()); +} + +void NativeWiimoteController::load(const pugi::xml_node& node) +{ + base_type::load(node); + + if (const auto value = node.child("packet_delay")) + m_packet_delay = ConvertString(value.child_value()); +} + +bool NativeWiimoteController::connect() +{ + if (is_connected()) + return true; + + if (!m_provider->is_registered_device(m_index)) + { + m_provider->get_controllers(); + } + + if (m_provider->is_connected(m_index)) + { + m_provider->set_packet_delay(m_index, m_packet_delay); + m_provider->set_led(m_index, m_player_index); + return true; + } + + return false; +} + +bool NativeWiimoteController::is_connected() +{ + if (m_provider->is_connected(m_index)) + { + m_provider->set_packet_delay(m_index, m_packet_delay); + return true; + } + + return false; +} + +void NativeWiimoteController::set_player_index(size_t player_index) +{ + m_player_index = player_index; + m_provider->set_led(m_index, m_player_index); +} + +NativeWiimoteController::Extension NativeWiimoteController::get_extension() const +{ + Extension result = None; + const auto ext = m_provider->get_state(m_index).m_extension; + if (std::holds_alternative(ext)) + result = Nunchuck; + else if (std::holds_alternative(ext)) + result = Classic; + + return result; +} + +bool NativeWiimoteController::is_mpls_attached() const +{ + return m_provider->get_state(m_index).m_motion_plus.has_value(); +} + +bool NativeWiimoteController::has_position() +{ + const auto state = m_provider->get_state(m_index); + return std::any_of(state.ir_camera.dots.cbegin(), state.ir_camera.dots.cend(), + [](const IRDot& v) { return v.visible; }); +} + +glm::vec2 NativeWiimoteController::get_position() +{ + const auto state = m_provider->get_state(m_index); + return state.ir_camera.position; +} + +glm::vec2 NativeWiimoteController::get_prev_position() +{ + const auto state = m_provider->get_state(m_index); + return state.ir_camera.m_prev_position; +} + +bool NativeWiimoteController::has_low_battery() +{ + const auto state = m_provider->get_state(m_index); + return HAS_FLAG(state.flags, kBatteryEmpty); +} + +void NativeWiimoteController::start_rumble() +{ + if (m_settings.rumble < 1.0f) + { + return; + } + + m_provider->set_rumble(m_index, true); +} + +void NativeWiimoteController::stop_rumble() +{ + m_provider->set_rumble(m_index, false); +} + +MotionSample NativeWiimoteController::get_motion_sample() +{ + const auto state = m_provider->get_state(m_index); + return state.motion_sample; +} + +MotionSample NativeWiimoteController::get_nunchuck_motion_sample() const +{ + const auto state = m_provider->get_state(m_index); + if (std::holds_alternative(state.m_extension)) + { + return std::get(state.m_extension).motion_sample; + } + + return {}; +} + +std::string NativeWiimoteController::get_button_name(uint64 button) const +{ + switch (button) + { + case kWiimoteButton_A: return "A"; + case kWiimoteButton_B: return "B"; + + case kWiimoteButton_One: return "1"; + case kWiimoteButton_Two: return "2"; + + case kWiimoteButton_Plus: return "+"; + case kWiimoteButton_Minus: return "-"; + + case kWiimoteButton_Home: return "HOME"; + + case kWiimoteButton_Up: return "UP"; + case kWiimoteButton_Down: return "DOWN"; + case kWiimoteButton_Left: return "LEFT"; + case kWiimoteButton_Right: return "RIGHT"; + + // nunchuck + case kWiimoteButton_C: return "C"; + case kWiimoteButton_Z: return "Z"; + + // classic + case kHighestWiimote + kClassicButton_A: return "A"; + case kHighestWiimote + kClassicButton_B: return "B"; + case kHighestWiimote + kClassicButton_Y: return "Y"; + case kHighestWiimote + kClassicButton_X: return "X"; + + case kHighestWiimote + kClassicButton_Plus: return "+"; + case kHighestWiimote + kClassicButton_Minus: return "-"; + + case kHighestWiimote + kClassicButton_Home: return "HOME"; + + case kHighestWiimote + kClassicButton_Up: return "UP"; + case kHighestWiimote + kClassicButton_Down: return "DOWN"; + case kHighestWiimote + kClassicButton_Left: return "LEFT"; + case kHighestWiimote + kClassicButton_Right: return "RIGHT"; + + case kHighestWiimote + kClassicButton_L: return "L"; + case kHighestWiimote + kClassicButton_R: return "R"; + + case kHighestWiimote + kClassicButton_ZL: return "ZL"; + case kHighestWiimote + kClassicButton_ZR: return "ZR"; + } + + return base_type::get_button_name(button); +} + +uint32 NativeWiimoteController::get_packet_delay() +{ + m_packet_delay = m_provider->get_packet_delay(m_index); + return m_packet_delay; +} + +void NativeWiimoteController::set_packet_delay(uint32 delay) +{ + m_packet_delay = delay; + m_provider->set_packet_delay(m_index, delay); +} + +ControllerState NativeWiimoteController::raw_state() +{ + ControllerState result{}; + if (!is_connected()) + return result; + + const auto state = m_provider->get_state(m_index); + result.buttons = state.buttons; + + if (std::holds_alternative(state.m_extension)) + { + const auto nunchuck = std::get(state.m_extension); + if (nunchuck.c) + result.buttons.set(kWiimoteButton_C); + + if (nunchuck.z) + result.buttons.set(kWiimoteButton_Z); + + result.axis = nunchuck.axis; + } + else if (std::holds_alternative(state.m_extension)) + { + const auto classic = std::get(state.m_extension); + result.buttons |= (uint64)classic.buttons << kHighestWiimote; + + result.axis = classic.left_axis; + result.rotation = classic.right_axis; + result.trigger = classic.trigger; + } + + return result; +} diff --git a/src/input/api/Wiimote/NativeWiimoteController.h b/src/input/api/Wiimote/NativeWiimoteController.h new file mode 100644 index 00000000..ed3caa08 --- /dev/null +++ b/src/input/api/Wiimote/NativeWiimoteController.h @@ -0,0 +1,66 @@ +#pragma once + +#include "input/api/Controller.h" +#include "input/api/Wiimote/WiimoteControllerProvider.h" + +// todo: find better name because of emulated nameclash +class NativeWiimoteController : public Controller +{ +public: + NativeWiimoteController(size_t index); + + enum Extension + { + None, + Nunchuck, + Classic, + MotionPlus, + }; + + std::string_view api_name() const override + { + static_assert(to_string(InputAPI::Wiimote) == "Wiimote"); + return to_string(InputAPI::Wiimote); + } + InputAPI::Type api() const override { return InputAPI::Wiimote; } + + void save(pugi::xml_node& node) override; + void load(const pugi::xml_node& node) override; + + bool connect() override; + bool is_connected() override; + + void set_player_index(size_t player_index); + + Extension get_extension() const; + bool is_mpls_attached() const; + + ControllerState raw_state() override; + + bool has_position() override; + glm::vec2 get_position() override; + glm::vec2 get_prev_position() override; + + bool has_motion() override { return true; } + bool has_rumble() override { return true; } + + bool has_battery() override { return true; } + bool has_low_battery() override; + + void start_rumble() override; + void stop_rumble() override; + + MotionSample get_motion_sample() override; + MotionSample get_nunchuck_motion_sample() const; + + std::string get_button_name(uint64 button) const override; + + uint32 get_packet_delay(); + void set_packet_delay(uint32 delay); + +private: + size_t m_index; + size_t m_player_index = 0; + uint32 m_packet_delay = WiimoteControllerProvider::kDefaultPacketDelay; +}; + diff --git a/src/input/api/Wiimote/WiimoteControllerProvider.cpp b/src/input/api/Wiimote/WiimoteControllerProvider.cpp new file mode 100644 index 00000000..a742a9d3 --- /dev/null +++ b/src/input/api/Wiimote/WiimoteControllerProvider.cpp @@ -0,0 +1,1056 @@ +#include "input/api/Wiimote/WiimoteControllerProvider.h" +#include "input/api/Wiimote/NativeWiimoteController.h" +#include "input/api/Wiimote/WiimoteMessages.h" + +#if BOOST_OS_WINDOWS +#include "input/api/Wiimote/windows/WinWiimoteDevice.h" +#endif + +#include + +WiimoteControllerProvider::WiimoteControllerProvider() + : m_running(true) +{ + m_reader_thread = std::thread(&WiimoteControllerProvider::reader_thread, this); + m_writer_thread = std::thread(&WiimoteControllerProvider::writer_thread, this); +} + +WiimoteControllerProvider::~WiimoteControllerProvider() +{ + if (m_running) + { + m_running = false; + m_writer_thread.join(); + m_reader_thread.join(); + } +} + +std::vector> WiimoteControllerProvider::get_controllers() +{ + std::scoped_lock lock(m_device_mutex); + for (const auto& device : WiimoteDevice_t::get_devices()) + { + // test connection of all devices as they might have been changed + const bool is_connected = device->write_data({kStatusRequest, 0x00}); + if (is_connected) + { + // only add unknown, connected devices to our list + const bool is_new_device = std::none_of(m_wiimotes.cbegin(), m_wiimotes.cend(), + [&device](const auto& it) { return *it.device == *device; }); + if (is_new_device) + { + m_wiimotes.push_back(std::make_unique(device)); + } + } + } + + std::vector> result; + for (size_t i = 0; i < m_wiimotes.size(); ++i) + { + result.emplace_back(std::make_shared(i)); + } + + return result; +} + +bool WiimoteControllerProvider::is_connected(size_t index) +{ + std::shared_lock lock(m_device_mutex); + return index < m_wiimotes.size() && m_wiimotes[index].connected; +} + +bool WiimoteControllerProvider::is_registered_device(size_t index) +{ + std::shared_lock lock(m_device_mutex); + return index < m_wiimotes.size(); +} + +void WiimoteControllerProvider::set_rumble(size_t index, bool state) +{ + std::shared_lock lock(m_device_mutex); + if (index >= m_wiimotes.size()) + return; + + m_wiimotes[index].rumble = state; + lock.unlock(); + + send_packet(index, { kStatusRequest, 0x00 }); +} + +void WiimoteControllerProvider::request_status(size_t index) +{ + send_packet(index, {kStatusRequest, 0x00}); +} + +void WiimoteControllerProvider::set_led(size_t index, size_t player_index) +{ + uint8 mask = 0; + mask |= 1 << (4 + (player_index % 4)); + if (player_index >= 4) + mask |= 1 << (4 + ((player_index - 3) % 4)); + send_packet(index, {kLED, mask}); +} + +uint32 WiimoteControllerProvider::get_packet_delay(size_t index) +{ + std::shared_lock lock(m_device_mutex); + if (index < m_wiimotes.size()) + { + return m_wiimotes[index].data_delay; + } + + return kDefaultPacketDelay; +} + +void WiimoteControllerProvider::set_packet_delay(size_t index, uint32 delay) +{ + std::shared_lock lock(m_device_mutex); + if (index < m_wiimotes.size()) + { + m_wiimotes[index].data_delay = delay; + } +} + +WiimoteControllerProvider::WiimoteState WiimoteControllerProvider::get_state(size_t index) +{ + std::shared_lock lock(m_device_mutex); + if (index < m_wiimotes.size()) + { + std::shared_lock data_lock(m_wiimotes[index].mutex); + return m_wiimotes[index].state; + } + + return {}; +} + +void WiimoteControllerProvider::reader_thread() +{ + SetThreadName("WiimoteControllerProvider::reader_thread"); + std::chrono::steady_clock::time_point lastCheck = {}; + while (m_running.load(std::memory_order_relaxed)) + { + const auto now = std::chrono::steady_clock::now(); + if (std::chrono::duration_cast(now - lastCheck) > std::chrono::seconds(2)) + { + // check for new connected wiimotes + get_controllers(); + lastCheck = std::chrono::steady_clock::now(); + } + + bool receivedAnyPacket = false; + std::shared_lock lock(m_device_mutex); + for (size_t index = 0; index < m_wiimotes.size(); ++index) + { + auto& wiimote = m_wiimotes[index]; + if (!wiimote.connected) + continue; + + const auto read_data = wiimote.device->read_data(); + if (!read_data || read_data->empty()) + continue; + receivedAnyPacket = true; + + std::shared_lock read_lock(wiimote.mutex); + WiimoteState new_state = wiimote.state; + read_lock.unlock(); + + bool update_report = false; + + const uint8* data = read_data->data(); + const auto id = (InputReportId)*data; + ++data; + switch (id) + { + case kStatus: + { +#ifdef WIIMOTE_DEBUG + printf("WiimoteControllerProvider::read_thread: kStatus\n"); +#endif + new_state.buttons = (*(uint16*)data) & (~0x60E0); + data += 2; + new_state.flags = *data; + ++data; + data += 2; // skip zeroes + new_state.battery_level = *data; + ++data; + + new_state.ir_camera.mode = set_ir_camera(index, true); + if(!new_state.m_calibrated) + calibrate(index); + + if(!new_state.m_motion_plus) + detect_motion_plus(index); + + if (HAS_FLAG(new_state.flags, kExtensionConnected)) + { +#ifdef WIIMOTE_DEBUG + printf("\tExtension flag is set\n"); +#endif + if(new_state.m_extension.index() == 0) + request_extension(index); + } + else + { + new_state.m_extension = {}; + } + + update_report = true; + } + break; + case kRead: + { +#ifdef WIIMOTE_DEBUG + printf("WiimoteControllerProvider::read_thread: kRead\n"); +#endif + new_state.buttons = (*(uint16*)data) & (~0x60E0); + data += 2; + const uint8 error_flag = *data & 0xF, size = (*data >> 4) + 1; + ++data; + + if (error_flag) + { + // 7 means that wiimote is already enabled or not available +#ifdef WIIMOTE_DEBUG + printf("Received error on data read 0x%x\n", error_flag); +#endif + continue; + } + + auto address = *(betype*)data; + data += 2; + if (address == (kRegisterCalibration & 0xFFFF)) + { +#ifdef WIIMOTE_DEBUG + printf("Calibration received\n"); +#endif + + cemu_assert(size == 8); + + new_state.m_calib_acceleration.zero.x = (uint16)*data << 2; + ++data; + new_state.m_calib_acceleration.zero.y = (uint16)*data << 2; + ++data; + new_state.m_calib_acceleration.zero.z = (uint16)*data << 2; + ++data; + // --XXYYZZ + new_state.m_calib_acceleration.zero.x |= (*data >> 4) & 0x3; // 5|4 -> 1|0 + new_state.m_calib_acceleration.zero.y |= (*data >> 2) & 0x3; // 3|4 -> 1|0 + new_state.m_calib_acceleration.zero.z |= *data & 0x3; + ++data; + + new_state.m_calib_acceleration.gravity.x = (uint16)*data << 2; + ++data; + new_state.m_calib_acceleration.gravity.y = (uint16)*data << 2; + ++data; + new_state.m_calib_acceleration.gravity.z = (uint16)*data << 2; + ++data; + new_state.m_calib_acceleration.gravity.x |= (*data >> 4) & 0x3; // 5|4 -> 1|0 + new_state.m_calib_acceleration.gravity.y |= (*data >> 2) & 0x3; // 3|4 -> 1|0 + new_state.m_calib_acceleration.gravity.z |= *data & 0x3; + ++data; + + new_state.m_calibrated = true; + } + else if (address == (kRegisterExtensionType & 0xFFFF)) + { + if (size == 0xf) + { +#ifdef WIIMOTE_DEBUG + printf("Extension type received but no extension connected\n"); +#endif + continue; + } + + +#ifdef WIIMOTE_DEBUG + printf("Extension type received\n"); +#endif + + cemu_assert(size == 6); + auto be_type = *(betype*)data; + data += 6; // 48 + be_type >>= 16; + be_type &= 0xFFFFFFFFFFFF; + switch (be_type.value()) + { + case kExtensionNunchuck: +#ifdef WIIMOTE_DEBUG + printf("\tNunchuck\n"); +#endif + new_state.m_extension = NunchuckData{}; + break; + case kExtensionClassic: +#ifdef WIIMOTE_DEBUG + printf("\tClassic\n"); +#endif + new_state.m_extension = ClassicData{}; + break; + case kExtensionClassicPro: + break; + case kExtensionGuitar: + break; + case kExtensionDrums: + break; + case kExtensionBalanceBoard: + break; + case kExtensionMotionPlus: + //m_motion_plus = true; +#ifdef WIIMOTE_DEBUG + printf("\tMotion plus detected\n"); +#endif + set_motion_plus(index, true); + new_state.m_motion_plus = MotionPlusData{}; + break; + case kExtensionPartialyInserted: +#ifdef WIIMOTE_DEBUG + printf("\tExtension only partially inserted!\n"); +#endif + new_state.m_extension = {}; + request_status(index); + break; + default: + new_state.m_extension = {}; + break; + } + + if (new_state.m_extension.index() != 0) + send_read_packet(index, kRegisterMemory, kRegisterExtensionCalibration, 0x10); + } + else if (address == (kRegisterExtensionCalibration & 0xFFFF)) + { + cemu_assert(size == 0x10); +#ifdef WIIMOTE_DEBUG + printf("Extension calibration received\n"); +#endif + std::visit( + overloaded + { + [](auto) + { + }, + [data](MotionPlusData& mp) + { + // TODO fix + }, + [data](NunchuckData& nunchuck) + { + std::array zero{}; + if (memcmp(zero.data(), data, zero.size()) == 0) + { +#ifdef WIIMOTE_DEBUG + printf("\tExtension calibration data is zero!\n"); +#endif + return; + } + + nunchuck.calibration.zero.x = (uint16)data[0] << 2; + nunchuck.calibration.zero.y = (uint16)data[1] << 2; + nunchuck.calibration.zero.z = (uint16)data[2] << 2; + // --XXYYZZ + nunchuck.calibration.zero.x |= (data[3] >> 4) & 0x3; // 5|4 -> 1|0 + nunchuck.calibration.zero.y |= (data[3] >> 2) & 0x3; // 3|4 -> 1|0 + nunchuck.calibration.zero.z |= data[3] & 0x3; + + nunchuck.calibration.gravity.x = (uint16)data[4] << 2;; + nunchuck.calibration.gravity.y = (uint16)data[5] << 2;; + nunchuck.calibration.gravity.z = (uint16)data[6] << 2;; + // --XXYYZZ + nunchuck.calibration.gravity.x |= (data[7] >> 4) & 0x3; // 5|4 -> 1|0 + nunchuck.calibration.gravity.y |= (data[7] >> 2) & 0x3; // 3|4 -> 1|0 + nunchuck.calibration.gravity.z |= data[7] & 0x3; + + nunchuck.calibration.max.x = data[8]; + nunchuck.calibration.max.y = data[11]; + + nunchuck.calibration.min.x = data[9]; + nunchuck.calibration.min.y = data[12]; + + nunchuck.calibration.center.x = data[10]; + nunchuck.calibration.center.y = data[13]; + } + }, new_state.m_extension); + } + else + { +#ifdef WIIMOTE_DEBUG + printf("Unhandled read data received\n"); +#endif + continue; + } + + update_report = true; + } + break; + case kDataCore: + { + // 30 BB BB + new_state.buttons = *(uint16*)data & (~0x60E0); + data += 2; + break; + } + case kDataCoreAcc: + { + // 31 BB BB AA AA AA + new_state.buttons = *(uint16*)data & (~0x60E0); + parse_acceleration(new_state, data); + break; + } + case kDataCoreExt8: + { + // 32 BB BB EE EE EE EE EE EE EE EE + new_state.buttons = *(uint16*)data & (~0x60E0); + data += 2; + break; + } + case kDataCoreAccIR: + { + // 33 BB BB AA AA AA II II II II II II II II II II II II + new_state.buttons = *(uint16*)data & (~0x60E0); + parse_acceleration(new_state, data); + data += parse_ir(new_state, data); + break; + } + case kDataCoreExt19: + { + // 34 BB BB EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE + new_state.buttons = *(uint16*)data & (~0x60E0); + data += 2; + break; + } + case kDataCoreAccExt: + { + // 35 BB BB AA AA AA EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE + new_state.buttons = *(uint16*)data & (~0x60E0); + parse_acceleration(new_state, data); + break; + } + case kDataCoreIRExt: + { + // 36 BB BB II II II II II II II II II II EE EE EE EE EE EE EE EE EE + new_state.buttons = *(uint16*)data & (~0x60E0); + data += 2; + break; + } + case kDataCoreAccIRExt: + { + // 37 BB BB AA AA AA II II II II II II II II II II EE EE EE EE EE EE + new_state.buttons = *(uint16*)data & (~0x60E0); + parse_acceleration(new_state, data); + data += parse_ir(new_state, data); // 10 + + std::visit( + overloaded + { + [](auto) + { + }, + [data](MotionPlusData& mp) mutable + { + glm::vec<3, uint16> raw; + raw.x = *data; + ++data; + raw.y = *data; + ++data; + raw.z = *data; + ++data; + + raw.x |= (uint16)*data << 6; // 7|2 -> 13|8 + mp.slow_yaw = *data & 2; + mp.slow_pitch = *data & 1; + ++data; + + raw.y |= (uint16)*data << 6; // 7|2 -> 13|8 + mp.slow_roll = *data & 2; + mp.extension_connected = *data & 1; + ++data; + + raw.z |= (uint16)*data << 6; // 7|2 -> 13|8 + + auto& calib = mp.calibration; + + glm::vec3 orientation = raw; + /*orientation -= calib.zero; + + Vector3 tmp = calib.gravity; + tmp -= calib.zero; + orientation /= tmp;*/ + + mp.orientation = orientation; +#ifdef WIIMOTE_DEBUG + printf("\tmp: %.2lf %.2lf %.2lf\n", mp.orientation.x, mp.orientation.y, + mp.orientation.z); +#endif + }, + [data](NunchuckData& nunchuck) mutable + { + nunchuck.raw_axis.x = *data; + ++data; + nunchuck.raw_axis.y = *data; + ++data; + + glm::vec<3, uint16> raw_acc; + raw_acc.x = (uint16)*data << 2; + ++data; + raw_acc.y = (uint16)*data << 2; + ++data; + raw_acc.z = (uint16)*data << 2; + ++data; + nunchuck.z = (*data & 1) == 0; + nunchuck.c = (*data & 2) == 0; + + raw_acc.x |= (*data >> 2) & 0x3; // 3|2 -> 1|0 + raw_acc.y |= (*data >> 4) & 0x3; // 5|4 -> 1|0 + raw_acc.z |= (*data >> 6) & 0x3; // 7|6 -> 1|0 + + auto& calib = nunchuck.calibration; + + if (nunchuck.raw_axis.x < nunchuck.calibration.center.x) // [-1, 0] + nunchuck.axis.x = ((float)nunchuck.raw_axis.x - calib.min.x) / ((float)nunchuck. + calibration.center.x - calib.min.x + 0.012f) - 1.0f; + else // [0, 1] + nunchuck.axis.x = (float)(nunchuck.raw_axis.x - nunchuck.calibration.center.x) / ( + nunchuck.calibration.max.x - nunchuck.calibration.center.x + 0.012f); + + if (nunchuck.raw_axis.y <= nunchuck.calibration.center.y) // [-1, 0] + nunchuck.axis.y = ((float)nunchuck.raw_axis.y - calib.min.y) / ((float)nunchuck. + calibration.center.y - calib.min.y + 0.012f) - 1.0f; + else // [0, 1] + nunchuck.axis.y = (float)(nunchuck.raw_axis.y - nunchuck.calibration.center.y) / ( + nunchuck.calibration.max.y - nunchuck.calibration.center.y); + + glm::vec3 acceleration = raw_acc; + nunchuck.prev_acceleration = nunchuck.acceleration; + nunchuck.acceleration = acceleration - glm::vec3(calib.zero); + + float acc[3]{ -nunchuck.acceleration.x, -nunchuck.acceleration.z, nunchuck.acceleration.y }; + const auto grav = nunchuck.calibration.gravity - nunchuck.calibration.zero; + + auto tacc = nunchuck.acceleration; + auto pacc = nunchuck.prev_acceleration; + if (grav != glm::vec<3, uint16>{}) + { + acc[0] /= (float)grav.x; + acc[1] /= (float)grav.y; + acc[2] /= (float)grav.z; + + tacc.x /= (float)grav.x; + pacc.x /= (float)grav.x; + + tacc.y /= (float)grav.y; + pacc.y /= (float)grav.y; + + tacc.z /= (float)grav.z; + pacc.z /= (float)grav.z; + } + float zero3[3]{}; + float zero4[4]{}; + + + nunchuck.motion_sample = MotionSample( + acc, + glm::length(tacc - pacc), + zero3, + zero3, + zero4 + ); +#ifdef WIIMOTE_DEBUG + printf("\tn: %d,%d | %lf - %lf | %.2lf %.2lf %.2lf\n", nunchuck.z, nunchuck.c, + nunchuck.axis.x, nunchuck.axis.y, + RadToDeg(nunchuck.acceleration.x), RadToDeg(nunchuck.acceleration.y), + RadToDeg(nunchuck.acceleration.z)); +#endif + }, + [data](ClassicData& classic) mutable + { + classic.left_raw_axis.x = *data & 0x3F; + classic.right_raw_axis.x = (*data & 0xC0) >> 3; // 7|6 -> 4|3 + ++data; + + classic.left_raw_axis.y = *data & 0x3F; + classic.right_raw_axis.x |= (*data & 0xC0) >> 5; // 7|6 -> 2|1 + ++data; + + classic.right_raw_axis.y = *data & 0x1F; + classic.raw_trigger.x = (*data & 0x60) >> 2; // 6|5 -> 4|3 + classic.right_raw_axis.x |= (*data & 0x80) >> 7; // 7 -> 0 + ++data; + + classic.raw_trigger.x |= (*data & 0xE0) >> 5; // 7|5 -> 2|0 + classic.raw_trigger.y = (*data & 0x1F); + ++data; + + classic.buttons = ~(*(uint16*)data); + data += 2; + + classic.left_axis = classic.left_raw_axis; + classic.left_axis /= 63.0f; + classic.left_axis = classic.left_axis * 2.0f - 1.0f; + + classic.right_axis = classic.right_raw_axis; + classic.right_axis /= 31.0f; + classic.right_axis = classic.right_axis * 2.0f - 1.0f; + + classic.trigger = classic.raw_trigger; + classic.trigger /= 31.0f; +#ifdef WIIMOTE_DEBUG + printf("\tc: %d | %lf - %lf | %lf - %lf | %lf - %lf\n", classic.buttons, + classic.left_axis.x, classic.left_axis.y, classic.right_axis.x, + classic.right_axis.y, classic.trigger.x, classic.trigger.y); +#endif + } + }, new_state.m_extension); + + + break; + } + case kDataExt: + { + // 3d EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE + break; + } + default: +#ifdef WIIMOTE_DEBUG + printf("unhandled input packet id %d for wiimote\n", data[0]); +#endif + } + + // update motion data + //const auto motionnow = std::chrono::high_resolution_clock::now(); + //const auto delta_time = (float)std::chrono::duration_cast(motionnow - new_state.m_last_motion_timestamp).count() / 1000.0f; + //new_state.m_last_motion_timestamp = motionnow; + + float acc[3]{-new_state.m_acceleration.x, -new_state.m_acceleration.z, new_state.m_acceleration.y}; + const auto grav = new_state.m_calib_acceleration.gravity - new_state.m_calib_acceleration.zero; + + auto tacc = new_state.m_acceleration; + auto pacc = new_state.m_prev_acceleration; + if (grav != glm::vec<3, uint16>{}) + { + acc[0] /= (float)grav.x; + acc[1] /= (float)grav.y; + acc[2] /= (float)grav.z; + + tacc.x /= (float)grav.x; + pacc.x /= (float)grav.x; + + tacc.y /= (float)grav.y; + pacc.y /= (float)grav.y; + + tacc.z /= (float)grav.z; + pacc.z /= (float)grav.z; + } + float zero3[3]{}; + float zero4[4]{}; + + + new_state.motion_sample = MotionSample( + acc, + glm::length(tacc - pacc), + zero3, + zero3, + zero4 + ); + + std::unique_lock data_lock(wiimote.mutex); + wiimote.state = new_state; + data_lock.unlock(); + + if (update_report) + update_report_type(index); + } + + lock.unlock(); + if (!receivedAnyPacket) + { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + } +} + +void WiimoteControllerProvider::parse_acceleration(WiimoteState& wiimote_state, const uint8*& data) +{ + glm::vec<3, uint16> raw_acc; + // acc encoded in BB BB + raw_acc.x = (*data >> 5) & 3; // 6|5 -> 1|0 + ++data; + raw_acc.y = (*data >> 4) & 2; // 5 -> 1 + raw_acc.z = (*data >> 5) & 2; // 6 -> 1 + ++data; + + raw_acc.x |= (uint16)*data << 2; + ++data; + raw_acc.y |= (uint16)*data << 2; + ++data; + raw_acc.z |= (uint16)*data << 2; + ++data; + + wiimote_state.m_prev_acceleration = wiimote_state.m_acceleration; + glm::vec3 acceleration = raw_acc; + + const auto& calib = wiimote_state.m_calib_acceleration; + wiimote_state.m_acceleration = acceleration - glm::vec3(calib.zero); + + glm::vec3 tmp = calib.gravity; + tmp -= calib.zero; + acceleration = (wiimote_state.m_acceleration / tmp); + + //printf("%d, %d, %d\n", (int)m_acceleration.x, (int)m_acceleration.y, (int)m_acceleration.z); + const float pi_2 = (float)std::numbers::pi / 2.0f; + wiimote_state.m_roll = std::atan2(acceleration.z, acceleration.x) - pi_2; +} + +void WiimoteControllerProvider::rotate_ir(WiimoteState& wiimote_state) +{ + const float rot = wiimote_state.m_roll; + if (rot == 0.0f) + return; + + const float sin = std::sin(rot); + const float cos = std::cos(rot); + int i = -1; + for (auto& dot : wiimote_state.ir_camera.dots) + { + i++; + if (!dot.visible) + continue; + //printf("%d:\t%.02lf | %.02lf\n", i, dot.pos.x, dot.pos.y); + // move to center, rotate and move back + dot.pos -= 0.5f; + dot.pos.x = (dot.pos.x * cos) + (dot.pos.y * (-sin)); + dot.pos.y = (dot.pos.x * sin) + (dot.pos.y * cos); + dot.pos += 0.5f; + } +} + +void WiimoteControllerProvider::calculate_ir_position(WiimoteState& wiimote_state) +{ + auto& ir = wiimote_state.ir_camera; + ir.m_prev_position = ir.position; + + std::pair indices = ir.indices; + if (ir.middle.x != 0) + { + const float last_angle = std::atan(ir.middle.y / ir.middle.x); + float best_distance = std::numeric_limits::max(); + for (size_t i = 0; i < ir.dots.size(); ++i) + { + if (!ir.dots[i].visible) + continue; + + for (size_t j = i + 1; j < ir.dots.size(); ++j) + { + if (!ir.dots[j].visible) + continue; + + const auto mid = (ir.dots[i].pos + ir.dots[j].pos) / 2.0f; + if (mid.x == 0) + continue; + + // check if angle is close enough to the last known one + float angle = std::atan(mid.y / mid.x); + if (std::abs(last_angle - angle) > DegToRad(10.0f)) + continue; + + // check if distance between points is similar to last known distance + const float distance = std::abs(ir.distance - glm::length(ir.dots[i].pos - ir.dots[j].pos)); + if (distance > 0.1f && distance > best_distance) + continue; + + // found a new pair + best_distance = distance; + indices = {(sint32)i, (sint32)j}; + } + } + } + + if (ir.dots[indices.first].visible && ir.dots[indices.second].visible) + { + ir.prev_dots[indices.first] = ir.dots[indices.first]; + ir.prev_dots[indices.second] = ir.dots[indices.second]; + ir.position = (ir.dots[indices.first].pos + ir.dots[indices.second].pos) / 2.0f; + + ir.middle = ir.position; + ir.distance = glm::length(ir.dots[indices.first].pos - ir.dots[indices.second].pos); + ir.indices = indices; + } + else if (ir.dots[indices.first].visible) + { + ir.position = ir.middle + (ir.dots[indices.first].pos - ir.prev_dots[indices.first].pos); + } + else if (ir.dots[indices.second].visible) + { + ir.position = ir.middle + (ir.dots[indices.second].pos - ir.prev_dots[indices.second].pos); + } +} + + +sint32 WiimoteControllerProvider::parse_ir(WiimoteState& wiimote_state, const uint8* data) +{ + switch (wiimote_state.ir_camera.mode) + { + case kIRDisabled: + wiimote_state.ir_camera.dots = {}; + return 0; + case kBasicIR: + { + const auto ir = (BasicIR*)data; + for (int i = 0; i < 2; ++i) + { + auto& dot1 = wiimote_state.ir_camera.dots[i * 2]; + auto& dot2 = wiimote_state.ir_camera.dots[i * 2 + 1]; + + dot1.raw.x = ir[i].x1 | (ir[i].bits.x1 << 8); // 9|8 + dot1.raw.y = ir[i].y1 | (ir[i].bits.y1 << 8); + dot1.size = 0; + + dot2.raw.x = ir[i].x2 | (ir[i].bits.x2 << 8); + dot2.raw.y = ir[i].y2 | (ir[i].bits.y2 << 8); + dot2.size = 0; + + dot1.visible = dot1.raw != glm::vec<2, uint16>(0x3ff, 0x3ff); + if (dot1.visible) + dot1.pos = glm::vec2(1.0f - dot1.raw.x / 1023.0f, (float)dot1.raw.y / 768.0f); + else + dot1.pos = {}; + + dot2.visible = dot2.raw != glm::vec<2, uint16>(0x3ff, 0x3ff); + if (dot2.visible) + dot2.pos = glm::vec2(1.0f - dot2.raw.x / 1023.0f, (float)dot2.raw.y / 768.0f); + else + dot2.pos = {}; + } + + rotate_ir(wiimote_state); + calculate_ir_position(wiimote_state); + return sizeof(BasicIR) * 2; + } + case kExtendedIR: + { + const auto ir = (ExtendedIR*)data; + for (int i = 0; i < 4; ++i) + { + auto& dot = wiimote_state.ir_camera.dots[i]; + dot.raw.x = ir[i].x; + dot.raw.y = ir[i].y; + + dot.raw.x |= (uint16)ir[i].bits.x << 8; // 9|8 + dot.raw.y |= (uint16)ir[i].bits.y << 8; // 9|8 + + dot.size = ir[i].bits.size; + + dot.visible = dot.raw != glm::vec<2, uint16>(0x3ff, 0x3ff); + if (dot.visible) + dot.pos = glm::vec2(1.0f - dot.raw.x / 1023.0f, (float)dot.raw.y / 768.0f); + else + dot.pos = {}; + } + + rotate_ir(wiimote_state); + calculate_ir_position(wiimote_state); + return sizeof(ExtendedIR) * 4; + } + default: + cemu_assert(false); + break; + } + return 0; +} + +void WiimoteControllerProvider::request_extension(size_t index) +{ + // send_write_packet(index, kRegisterMemory, kRegisterExtensionEncrypted, { 0x00 }); + send_write_packet(index, kRegisterMemory, kRegisterExtension1, {0x55}); + send_write_packet(index, kRegisterMemory, kRegisterExtension2, {0x00}); + send_read_packet(index, kRegisterMemory, kRegisterExtensionType, 6); +} + +void WiimoteControllerProvider::detect_motion_plus(size_t index) +{ + send_read_packet(index, kRegisterMemory, kRegisterMotionPlusDetect, 6); +} + +void WiimoteControllerProvider::set_motion_plus(size_t index, bool state) +{ + if (state) { + send_write_packet(index, kRegisterMemory, kRegisterMotionPlusInit, { 0x55 }); + send_write_packet(index, kRegisterMemory, kRegisterMotionPlusEnable, { 0x04 }); + } + else + { + send_write_packet(index, kRegisterMemory, kRegisterExtension1, { 0x55 }); + } +} + + +void WiimoteControllerProvider::writer_thread() +{ + SetThreadName("WiimoteControllerProvider::writer_thread"); + while (m_running.load(std::memory_order_relaxed)) + { + std::unique_lock writer_lock(m_writer_mutex); + while (m_write_queue.empty()) + { + if (m_writer_cond.wait_for(writer_lock, std::chrono::milliseconds(250)) == std::cv_status::timeout) + { + if (!m_running.load(std::memory_order_relaxed)) + return; + } + } + + auto index = (size_t)-1; + std::vector data; + std::shared_lock device_lock(m_device_mutex); + + // get first packet of device which is ready to be sent + const auto now = std::chrono::high_resolution_clock::now(); + for (auto it = m_write_queue.begin(); it != m_write_queue.end(); ++it) + { + if (it->first >= m_wiimotes.size()) + continue; + + const auto delay = m_wiimotes[it->first].data_delay.load(std::memory_order_relaxed); + if (now >= m_wiimotes[it->first].data_ts + std::chrono::milliseconds(delay)) + { + index = it->first; + data = it->second; + m_write_queue.erase(it); + break; + } + } + writer_lock.unlock(); + + if (index != (size_t)-1 && !data.empty()) + { + if (m_wiimotes[index].rumble) + data[1] |= 1; + + m_wiimotes[index].connected = m_wiimotes[index].device->write_data(data); + if (m_wiimotes[index].connected) + { + m_wiimotes[index].data_ts = std::chrono::high_resolution_clock::now(); + } + else + { + m_wiimotes[index].rumble = false; + } + } + device_lock.unlock(); + + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + std::this_thread::yield(); + } +} + +void WiimoteControllerProvider::calibrate(size_t index) +{ + send_read_packet(index, kEEPROMMemory, kRegisterCalibration, 8); +} + +void WiimoteControllerProvider::update_report_type(size_t index) +{ + std::shared_lock read_lock(m_wiimotes[index].mutex); + auto& state = m_wiimotes[index].state; + + const bool extension = state.m_extension.index() != 0; // TODO || HasMotionPlus(); + const bool ir = state.ir_camera.mode != kIRDisabled; + const bool motion = true; // UseMotion(); + + InputReportId report_type; + if (extension && ir && motion) + report_type = kDataCoreAccIRExt; + else if (extension && ir) + report_type = kDataCoreIRExt; + else if (extension && motion) + report_type = kDataCoreAccExt; + else if (ir && motion) + report_type = kDataCoreAccIR; + else if (extension) + report_type = kDataCoreExt19; + else if (ir) + report_type = kDataCoreAccIR; + else if (motion) + report_type = kDataCoreAcc; + else + report_type = kDataCore; + +#ifdef WIIMOTE_DEBUG + printf("Setting report type to %d\n", report_type); +#endif + send_packet(index, {kType, 0x04, report_type}); + + state.ir_camera.mode = set_ir_camera(index, true); +} + +IRMode WiimoteControllerProvider::set_ir_camera(size_t index, bool state) +{ + std::shared_lock read_lock(m_wiimotes[index].mutex); + auto& wiimote_state = m_wiimotes[index].state; + + IRMode mode; + if (!state) + mode = kIRDisabled; + else + { + mode = wiimote_state.m_extension.index() == 0 ? kExtendedIR : kBasicIR; + } + + if (wiimote_state.ir_camera.mode == mode) + return mode; + + wiimote_state.ir_camera.mode = mode; + + const uint8_t data = state ? 0x04 : 0x00; + send_packet(index, {kIR, data}); + send_packet(index, {kIR2, data}); + if (state) + { + send_write_packet(index, kRegisterMemory, kRegisterIR, {0x08}); + send_write_packet(index, kRegisterMemory, kRegisterIRSensitivity1, + {0x02, 0x00, 0x00, 0x71, 0x01, 0x00, 0xaa, 0x00, 0x64}); + send_write_packet(index, kRegisterMemory, kRegisterIRSensitivity2, {0x63, 0x03}); + send_write_packet(index, kRegisterMemory, kRegisterIRMode, {(uint8)mode}); + send_write_packet(index, kRegisterMemory, kRegisterIR, {0x08}); + } + + update_report_type(index); + return mode; +} + +void WiimoteControllerProvider::send_packet(size_t index, std::vector data) +{ + cemu_assert(data.size() > 1); + + std::shared_lock device_lock(m_device_mutex); + if (index >= m_wiimotes.size()) + return; + + device_lock.unlock(); + + std::unique_lock lock(m_writer_mutex); + m_write_queue.emplace_back(index, data); + m_writer_cond.notify_one(); +} + +void WiimoteControllerProvider::send_read_packet(size_t index, MemoryType type, RegisterAddress address, uint16 size) +{ + std::vector data(7); + data[0] = kReadMemory; + data[1] = type; + *(betype*)(data.data() + 2) = (address & 0xFFFFFF) << 8; // only uint24 + *(betype*)(data.data() + 2 + 3) = size; + + send_packet(index, std::move(data)); +} + +void WiimoteControllerProvider::send_write_packet(size_t index, MemoryType type, RegisterAddress address, + const std::vector& data) +{ + cemu_assert(data.size() <= 16); + std::vector packet(6 + 16); + packet[0] = kWriteMemory; + packet[1] = type; + *(betype*)(packet.data() + 2) = (address & 0xFFFFFF) << 8; // only uint24 + *(packet.data() + 2 + 3) = (uint8)data.size(); + std::copy(data.begin(), data.end(), packet.data() + 2 + 3 + 1); + send_packet(index, std::move(packet)); +} diff --git a/src/input/api/Wiimote/WiimoteControllerProvider.h b/src/input/api/Wiimote/WiimoteControllerProvider.h new file mode 100644 index 00000000..40fe878a --- /dev/null +++ b/src/input/api/Wiimote/WiimoteControllerProvider.h @@ -0,0 +1,126 @@ +#pragma once + +#include "input/motion/MotionHandler.h" +#include "input/api/Wiimote/WiimoteDevice.h" +#include "input/api/Wiimote/WiimoteMessages.h" + +#include "input/api/ControllerProvider.h" + +#include +#include +#include + +#ifndef HAS_WIIMOTE +#define HAS_WIIMOTE 1 +#endif + +#define WIIMOTE_DEBUG 1 + +class WiimoteControllerProvider : public ControllerProviderBase +{ + friend class WiimoteController; +public: + constexpr static uint32 kDefaultPacketDelay = 25; + + WiimoteControllerProvider(); + ~WiimoteControllerProvider(); + + inline static InputAPI::Type kAPIType = InputAPI::Wiimote; + InputAPI::Type api() const override { return kAPIType; } + + std::vector> get_controllers() override; + + bool is_connected(size_t index); + bool is_registered_device(size_t index); + void set_rumble(size_t index, bool state); + void request_status(size_t index); + void set_led(size_t index, size_t player_index); + + uint32 get_packet_delay(size_t index); + void set_packet_delay(size_t index, uint32 delay); + + struct WiimoteState + { + uint16 buttons = 0; + uint8 flags = 0; + uint8 battery_level = 0; + + glm::vec3 m_acceleration{}, m_prev_acceleration{}; + float m_roll = 0; + + std::chrono::high_resolution_clock::time_point m_last_motion_timestamp{}; + MotionSample motion_sample{}; + WiiUMotionHandler motion_handler{}; + + bool m_calibrated = false; + Calibration m_calib_acceleration{}; + + struct IRCamera + { + IRMode mode = kIRDisabled; + std::array dots{}, prev_dots{}; + + glm::vec2 position{}, m_prev_position{}; + glm::vec2 middle {}; + float distance = 0; + std::pair indices{ 0,1 }; + }ir_camera{}; + + std::optional m_motion_plus; + std::variant m_extension{}; + }; + WiimoteState get_state(size_t index); + + +private: + std::atomic_bool m_running = false; + std::thread m_reader_thread, m_writer_thread; + + std::shared_mutex m_device_mutex; + + struct Wiimote + { + Wiimote(WiimoteDevicePtr device) + : device(std::move(device)) {} + + WiimoteDevicePtr device; + std::atomic_bool connected = true; + std::atomic_bool rumble = false; + + std::shared_mutex mutex; + WiimoteState state{}; + + std::atomic_uint32_t data_delay = kDefaultPacketDelay; + std::chrono::high_resolution_clock::time_point data_ts{}; + }; + boost::ptr_vector m_wiimotes; + + std::list>> m_write_queue; + std::mutex m_writer_mutex; + std::condition_variable m_writer_cond; + + void reader_thread(); + void writer_thread(); + + void calibrate(size_t index); + IRMode set_ir_camera(size_t index, bool state); + + void send_packet(size_t index, std::vector data); + void send_read_packet(size_t index, MemoryType type, RegisterAddress address, uint16 size); + void send_write_packet(size_t index, MemoryType type, RegisterAddress address, const std::vector& data); + + void parse_acceleration(WiimoteState& wiimote_state, const uint8*& data); + + void rotate_ir(WiimoteState& wiimote_state); + void calculate_ir_position(WiimoteState& wiimote_state); + int parse_ir(WiimoteState& wiimote_state, const uint8* data); + + void request_extension(size_t index); + void detect_motion_plus(size_t index); + void set_motion_plus(size_t index, bool state); + + void update_report_type(size_t index); +}; + + + diff --git a/src/input/api/Wiimote/WiimoteDevice.h b/src/input/api/Wiimote/WiimoteDevice.h new file mode 100644 index 00000000..7938bbdf --- /dev/null +++ b/src/input/api/Wiimote/WiimoteDevice.h @@ -0,0 +1,16 @@ +#pragma once + +class WiimoteDevice +{ + friend class WiimoteInfo; +public: + virtual ~WiimoteDevice() = default; + + virtual bool write_data(const std::vector& data) = 0; + virtual std::optional> read_data() = 0; + + virtual bool operator==(WiimoteDevice& o) const = 0; + bool operator!=(WiimoteDevice& o) const { return *this == o; } +}; + +using WiimoteDevicePtr = std::shared_ptr; diff --git a/src/input/api/Wiimote/WiimoteMessages.h b/src/input/api/Wiimote/WiimoteMessages.h new file mode 100644 index 00000000..712c3a5c --- /dev/null +++ b/src/input/api/Wiimote/WiimoteMessages.h @@ -0,0 +1,244 @@ +#pragma once + +// https://wiibrew.org/wiki/Wiimote + +enum InputReportId : uint8 +{ + kNone = 0, + + kStatus = 0x20, + kRead = 0x21, + kWrite = 0x22, + + kDataCore = 0x30, + kDataCoreAcc = 0x31, + kDataCoreExt8 = 0x32, + kDataCoreAccIR = 0x33, + kDataCoreExt19 = 0x34, + kDataCoreAccExt = 0x35, + kDataCoreIRExt = 0x36, + kDataCoreAccIRExt = 0x37, + kDataExt = 0x3d, +}; + +enum RegisterAddress : uint32 +{ + kRegisterCalibration = 0x16, + kRegisterCalibration2 = 0x20, // backup calibration data + + kRegisterIR = 0x4b00030, + kRegisterIRSensitivity1 = 0x4b00000, + kRegisterIRSensitivity2 = 0x4b0001a, + kRegisterIRMode = 0x4b00033, + + kRegisterExtensionEncrypted = 0x4a40040, + + kRegisterExtension1 = 0x4a400f0, + kRegisterExtension2 = 0x4a400fb, + kRegisterExtensionType = 0x4a400fa, + kRegisterExtensionCalibration = 0x4a40020, + + kRegisterMotionPlusDetect = 0x4a600fa, + kRegisterMotionPlusInit = 0x4a600f0, + kRegisterMotionPlusEnable = 0x4a600fe, +}; + +enum ExtensionType : uint64 +{ + kExtensionNunchuck = 0x0000A4200000, + kExtensionClassic = 0x0000A4200101, + kExtensionClassicPro = 0x0100A4200101, + kExtensionDrawsome = 0xFF00A4200013, + kExtensionGuitar = 0x0000A4200103, + kExtensionDrums = 0x0100A4200103, + kExtensionBalanceBoard = 0x2A2C, + + kExtensionMotionPlus = 0xa6200005, + + kExtensionPartialyInserted = 0xffffffffffff, +}; + +enum MemoryType : uint8 +{ + kEEPROMMemory = 0, + kRegisterMemory = 0x4, +}; + +enum StatusBitmask : uint8 +{ + kBatteryEmpty = 0x1, + kExtensionConnected = 0x2, + kSpeakerEnabled = 0x4, + kIREnabled = 0x8, + kLed1 = 0x10, + kLed2 = 0x20, + kLed3 = 0x40, + kLed4 = 0x80 +}; + +enum OutputReportId : uint8 +{ + kLED = 0x11, + kType = 0x12, + kIR = 0x13, + kSpeakerState = 0x14, + kStatusRequest = 0x15, + kWriteMemory = 0x16, + kReadMemory = 0x17, + kSpeakerData = 0x18, + kSpeakerMute = 0x19, + kIR2 = 0x1A, +}; + +enum IRMode : uint8 +{ + kIRDisabled, + kBasicIR = 1, + kExtendedIR = 3, + kFullIR = 5, +}; + +enum WiimoteButtons +{ + kWiimoteButton_Left = 0, + kWiimoteButton_Right = 1, + kWiimoteButton_Down = 2, + kWiimoteButton_Up = 3, + kWiimoteButton_Plus = 4, + + kWiimoteButton_Two = 8, + kWiimoteButton_One = 9, + kWiimoteButton_B = 10, + kWiimoteButton_A = 11, + kWiimoteButton_Minus = 12, + + kWiimoteButton_Home = 15, + + // self defined + kWiimoteButton_C = 16, + kWiimoteButton_Z = 17, + + kHighestWiimote = 20, +}; + +enum ClassicButtons +{ + kClassicButton_R = 1, + kClassicButton_Plus = 2, + kClassicButton_Home = 3, + kClassicButton_Minus = 4, + kClassicButton_L = 5, + kClassicButton_Down = 6, + kClassicButton_Right = 7, + kClassicButton_Up = 8, + kClassicButton_Left = 9, + kClassicButton_ZR = 10, + kClassicButton_X = 11, + kClassicButton_A = 12, + kClassicButton_Y = 13, + kClassicButton_B = 14, + kClassicButton_ZL = 15, +}; + +struct Calibration +{ + glm::vec<3, uint16> zero{ 0x200, 0x200, 0x200 }; + glm::vec<3, uint16> gravity{ 0x240, 0x240, 0x240 }; +}; + +struct BasicIR +{ + uint8 x1; + uint8 y1; + + struct + { + uint8 x2 : 2; + uint8 y2 : 2; + uint8 x1 : 2; + uint8 y1 : 2; + } bits; + static_assert(sizeof(bits) == 1); + + uint8 x2; + uint8 y2; +}; +static_assert(sizeof(BasicIR) == 5); + +struct ExtendedIR +{ + uint8 x; + uint8 y; + struct + { + uint8 size : 4; + uint8 x : 2; + uint8 y : 2; + } bits; + static_assert(sizeof(bits) == 1); +}; +static_assert(sizeof(ExtendedIR) == 3); + +struct IRDot +{ + bool visible = false; + glm::vec2 pos; + glm::vec<2, uint16> raw; + uint32 size; +}; + +struct IRCamera +{ + IRMode mode; + std::array dots{}, prev_dots{}; + + glm::vec2 position, m_prev_position; + glm::vec2 middle; + float distance; + std::pair indices{ 0,1 }; +}; + +struct NunchuchCalibration : Calibration +{ + glm::vec<2, uint8> min{}; + glm::vec<2, uint8> center{ 0x7f, 0x7f }; + glm::vec<2, uint8> max{ 0xff, 0xff }; +}; + +struct MotionPlusData +{ + Calibration calibration{}; + + glm::vec3 orientation; // yaw, roll, pitch + bool slow_roll = false; + bool slow_pitch = false; + bool slow_yaw = false; + bool extension_connected = false; +}; + +struct NunchuckData +{ + glm::vec3 acceleration{}, prev_acceleration{}; + NunchuchCalibration calibration{}; + + bool c = false; + bool z = false; + + glm::vec2 axis{}; + glm::vec<2, uint8> raw_axis{}; + + MotionSample motion_sample{}; +}; + +struct ClassicData +{ + glm::vec2 left_axis{}; + glm::vec<2, uint8> left_raw_axis{}; + + glm::vec2 right_axis{}; + glm::vec<2, uint8> right_raw_axis{}; + + glm::vec2 trigger{}; + glm::vec<2, uint8> raw_trigger{}; + uint16 buttons = 0; +}; diff --git a/src/input/api/Wiimote/windows/WinWiimoteDevice.cpp b/src/input/api/Wiimote/windows/WinWiimoteDevice.cpp new file mode 100644 index 00000000..546a9615 --- /dev/null +++ b/src/input/api/Wiimote/windows/WinWiimoteDevice.cpp @@ -0,0 +1,127 @@ +#include "input/api/Wiimote/windows/WinWiimoteDevice.h" + +#include +#include + +WinWiimoteDevice::WinWiimoteDevice(HANDLE handle, std::vector identifier) + : m_handle(handle), m_identifier(std::move(identifier)) +{ + m_overlapped.hEvent = CreateEvent(nullptr, TRUE, TRUE, nullptr); +} + +WinWiimoteDevice::~WinWiimoteDevice() +{ + CancelIo(m_handle); + ResetEvent(m_overlapped.hEvent); + CloseHandle(m_handle); +} + +bool WinWiimoteDevice::write_data(const std::vector& data) +{ + return HidD_SetOutputReport(m_handle, (void*)data.data(), (ULONG)data.size()); +} + +std::optional> WinWiimoteDevice::read_data() +{ + DWORD read = 0; + std::array buffer{}; + + if (!ReadFile(m_handle, buffer.data(), (DWORD)buffer.size(), &read, &m_overlapped)) + { + const auto error = GetLastError(); + if (error == ERROR_DEVICE_NOT_CONNECTED) + return {}; + else if (error == ERROR_IO_PENDING) + { + const auto wait_result = WaitForSingleObject(m_overlapped.hEvent, 100); + if (wait_result == WAIT_TIMEOUT) + { + CancelIo(m_handle); + ResetEvent(m_overlapped.hEvent); + return {}; + } + else if (wait_result == WAIT_FAILED) + return {}; + + if (GetOverlappedResult(m_handle, &m_overlapped, &read, FALSE) == FALSE) + return {}; + } + else if (error == ERROR_INVALID_HANDLE) + { + ResetEvent(m_overlapped.hEvent); + return {}; + } + else + { + cemu_assert_debug(false); + } + } + + ResetEvent(m_overlapped.hEvent); + if (read == 0) + return {}; + + return {{buffer.cbegin(), buffer.cbegin() + read}}; +} + +std::vector WinWiimoteDevice::get_devices() +{ + std::vector result; + + GUID hid_guid; + HidD_GetHidGuid(&hid_guid); + + const auto device_info = SetupDiGetClassDevs(&hid_guid, nullptr, nullptr, (DIGCF_DEVICEINTERFACE | DIGCF_PRESENT)); + + for (DWORD index = 0; ; ++index) + { + SP_DEVICE_INTERFACE_DATA device_data{}; + device_data.cbSize = sizeof(device_data); + if (SetupDiEnumDeviceInterfaces(device_info, nullptr, &hid_guid, index, &device_data) == FALSE) + break; + + DWORD device_data_len; + if (SetupDiGetDeviceInterfaceDetail(device_info, &device_data, nullptr, 0, &device_data_len, nullptr) == FALSE + && GetLastError() != ERROR_INSUFFICIENT_BUFFER) + continue; + + std::vector detail_data_buffer; + detail_data_buffer.resize(device_data_len); + + const auto detail_data = (PSP_DEVICE_INTERFACE_DETAIL_DATA)detail_data_buffer.data(); + detail_data->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); + + if (SetupDiGetDeviceInterfaceDetail(device_info, &device_data, detail_data, device_data_len, nullptr, nullptr) + == FALSE) + continue; + + HANDLE device_handle = CreateFile(detail_data->DevicePath, (GENERIC_READ | GENERIC_WRITE), + (FILE_SHARE_READ | FILE_SHARE_WRITE), nullptr, OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, nullptr); + if (device_handle == INVALID_HANDLE_VALUE) + continue; + + HIDD_ATTRIBUTES attributes{}; + attributes.Size = sizeof(attributes); + if (HidD_GetAttributes(device_handle, &attributes) == FALSE) + { + CloseHandle(device_handle); + continue; + } + + if (attributes.VendorID != 0x057e || (attributes.ProductID != 0x0306 && attributes.ProductID != 0x0330)) + { + CloseHandle(device_handle); + continue; + } + + result.emplace_back(std::make_shared(device_handle, detail_data_buffer)); + } + + return result; +} + +bool WinWiimoteDevice::operator==(WiimoteDevice& o) const +{ + return m_identifier == static_cast(o).m_identifier; +} diff --git a/src/input/api/Wiimote/windows/WinWiimoteDevice.h b/src/input/api/Wiimote/windows/WinWiimoteDevice.h new file mode 100644 index 00000000..077882db --- /dev/null +++ b/src/input/api/Wiimote/windows/WinWiimoteDevice.h @@ -0,0 +1,24 @@ +#pragma once + +#include "input/api/Wiimote/WiimoteDevice.h" + +class WinWiimoteDevice : public WiimoteDevice +{ +public: + WinWiimoteDevice(HANDLE handle, std::vector identifier); + ~WinWiimoteDevice(); + + bool write_data(const std::vector& data) override; + std::optional> read_data() override; + + static std::vector get_devices(); + + bool operator==(WiimoteDevice& o) const override; + +private: + HANDLE m_handle; + OVERLAPPED m_overlapped{}; + std::vector m_identifier; +}; + +using WiimoteDevice_t = WinWiimoteDevice; diff --git a/src/input/api/XInput/XInputController.cpp b/src/input/api/XInput/XInputController.cpp new file mode 100644 index 00000000..08d6510d --- /dev/null +++ b/src/input/api/XInput/XInputController.cpp @@ -0,0 +1,151 @@ +#include "input/api/XInput/XInputController.h" + +XInputController::XInputController(uint32 index) + : base_type(fmt::format("{}", index), fmt::format("Controller {}", index + 1)) +{ + if (index >= XUSER_MAX_COUNT) + throw std::runtime_error(fmt::format("invalid xinput index {} (must be smaller than {})", index, + XUSER_MAX_COUNT)); + + m_index = index; + + m_settings.axis.deadzone = m_settings.rotation.deadzone = m_settings.trigger.deadzone = 0.15f; +} + +bool XInputController::connect() +{ + if (m_connected) + return true; + + m_has_battery = false; + + XINPUT_CAPABILITIES caps{}; + m_connected = m_provider->m_XInputGetCapabilities(m_index, XINPUT_FLAG_GAMEPAD, &caps) != + ERROR_DEVICE_NOT_CONNECTED; + if (!m_connected) return false; + + m_has_rumble = (caps.Vibration.wLeftMotorSpeed > 0) || (caps.Vibration.wRightMotorSpeed > 0); + + if (m_provider->m_XInputGetBatteryInformation) + { + XINPUT_BATTERY_INFORMATION battery{}; + if (m_provider->m_XInputGetBatteryInformation(m_index, BATTERY_DEVTYPE_GAMEPAD, &battery) == ERROR_SUCCESS) + { + m_has_battery = (battery.BatteryType == BATTERY_TYPE_ALKALINE || battery.BatteryType == + BATTERY_TYPE_NIMH); + } + } + + return m_connected; +} + +bool XInputController::is_connected() +{ + return m_connected; +} + + +void XInputController::start_rumble() +{ + if (!has_rumble() || m_settings.rumble <= 0) + return; + + XINPUT_VIBRATION vibration; + vibration.wLeftMotorSpeed = static_cast(m_settings.rumble * std::numeric_limits::max()); + vibration.wRightMotorSpeed = static_cast(m_settings.rumble * std::numeric_limits::max()); + m_provider->m_XInputSetState(m_index, &vibration); +} + +void XInputController::stop_rumble() +{ + if (!has_rumble()) + return; + + XINPUT_VIBRATION vibration{}; + m_provider->m_XInputSetState(m_index, &vibration); +} + +bool XInputController::has_low_battery() +{ + if (!has_battery()) + return false; + + XINPUT_BATTERY_INFORMATION battery{}; + if (m_provider->m_XInputGetBatteryInformation(m_index, BATTERY_DEVTYPE_GAMEPAD, &battery) == ERROR_SUCCESS) + { + return (battery.BatteryType == BATTERY_TYPE_ALKALINE || battery.BatteryType == BATTERY_TYPE_NIMH) && battery + .BatteryLevel <= BATTERY_LEVEL_LOW; + } + + return false; +} + +std::string XInputController::get_button_name(uint64 button) const +{ + switch (1ULL << button) + { + case XINPUT_GAMEPAD_A: return "A"; + case XINPUT_GAMEPAD_B: return "B"; + case XINPUT_GAMEPAD_X: return "X"; + case XINPUT_GAMEPAD_Y: return "Y"; + + case XINPUT_GAMEPAD_LEFT_SHOULDER: return "L"; + case XINPUT_GAMEPAD_RIGHT_SHOULDER: return "R"; + + case XINPUT_GAMEPAD_START: return "Start"; + case XINPUT_GAMEPAD_BACK: return "Select"; + + case XINPUT_GAMEPAD_LEFT_THUMB: return "L-Stick"; + case XINPUT_GAMEPAD_RIGHT_THUMB: return "R-Stick"; + case XINPUT_GAMEPAD_DPAD_UP: return "DPAD-Up"; + case XINPUT_GAMEPAD_DPAD_DOWN: return "DPAD-Down"; + case XINPUT_GAMEPAD_DPAD_LEFT: return "DPAD-Left"; + case XINPUT_GAMEPAD_DPAD_RIGHT: return "DPAD-Right"; + } + + return Controller::get_button_name(button); +} + +ControllerState XInputController::raw_state() +{ + ControllerState result{}; + if (!m_connected) + return result; + + XINPUT_STATE state; + if (m_provider->m_XInputGetState(m_index, &state) != ERROR_SUCCESS) + { + m_connected = false; + return result; + } + + // Buttons + result.buttons = state.Gamepad.wButtons; + + if (state.Gamepad.sThumbLX > 0) + result.axis.x = (float)state.Gamepad.sThumbLX / std::numeric_limits::max(); + else if (state.Gamepad.sThumbLX < 0) + result.axis.x = (float)-state.Gamepad.sThumbLX / std::numeric_limits::min(); + + if (state.Gamepad.sThumbLY > 0) + result.axis.y = (float)state.Gamepad.sThumbLY / std::numeric_limits::max(); + else if (state.Gamepad.sThumbLY < 0) + result.axis.y = (float)-state.Gamepad.sThumbLY / std::numeric_limits::min(); + + // Right Stick + if (state.Gamepad.sThumbRX > 0) + result.rotation.x = (float)state.Gamepad.sThumbRX / std::numeric_limits::max(); + else if (state.Gamepad.sThumbRX < 0) + result.rotation.x = (float)-state.Gamepad.sThumbRX / std::numeric_limits::min(); + + if (state.Gamepad.sThumbRY > 0) + result.rotation.y = (float)state.Gamepad.sThumbRY / std::numeric_limits::max(); + else if (state.Gamepad.sThumbRY < 0) + result.rotation.y = (float)-state.Gamepad.sThumbRY / std::numeric_limits::min(); + + // Trigger + result.trigger.x = (float)state.Gamepad.bLeftTrigger / std::numeric_limits::max(); + result.trigger.y = (float)state.Gamepad.bRightTrigger / std::numeric_limits::max(); + + return result; +} diff --git a/src/input/api/XInput/XInputController.h b/src/input/api/XInput/XInputController.h new file mode 100644 index 00000000..9da753f2 --- /dev/null +++ b/src/input/api/XInput/XInputController.h @@ -0,0 +1,38 @@ +#pragma once + +#include "input/api/XInput/XInputControllerProvider.h" +#include "input/api/Controller.h" + +class XInputController : public Controller +{ +public: + XInputController(uint32 index); + + std::string_view api_name() const override + { + static_assert(to_string(InputAPI::XInput) == "XInput"); + return to_string(InputAPI::XInput); + } + InputAPI::Type api() const override { return InputAPI::XInput; } + + bool connect() override; + bool is_connected() override; + + bool has_rumble() override { return m_has_rumble; } + bool has_battery() override { return m_has_battery; } + bool has_low_battery() override; + + void start_rumble() override; + void stop_rumble() override; + + std::string get_button_name(uint64 button) const override; + +protected: + ControllerState raw_state() override; + +private: + uint32 m_index; + bool m_connected = false; + bool m_has_battery = false; + bool m_has_rumble = false; +}; diff --git a/src/input/api/XInput/XInputControllerProvider.cpp b/src/input/api/XInput/XInputControllerProvider.cpp new file mode 100644 index 00000000..7b71196d --- /dev/null +++ b/src/input/api/XInput/XInputControllerProvider.cpp @@ -0,0 +1,57 @@ +#include + +#include "input/api/XInput/XInputControllerProvider.h" +#include "input/api/XInput/XInputController.h" + +XInputControllerProvider::XInputControllerProvider() +{ + // try to load newest to oldest + m_module = LoadLibraryA("XInput1_4.DLL"); + if (!m_module) + { + m_module = LoadLibraryA("XInput1_3.DLL"); + if (!m_module) + { + m_module = LoadLibraryA("XInput9_1_0.dll"); + if (!m_module) + throw std::runtime_error("can't load any xinput dll"); + } + } + +#define GET_XINPUT_PROC(__FUNC__) m_##__FUNC__ = (decltype(m_##__FUNC__))GetProcAddress(m_module, #__FUNC__) + GET_XINPUT_PROC(XInputGetCapabilities); + GET_XINPUT_PROC(XInputGetState); + GET_XINPUT_PROC(XInputSetState); + + if (!m_XInputGetCapabilities || !m_XInputGetState || !m_XInputSetState) + { + FreeLibrary(m_module); + throw std::runtime_error("can't find necessary xinput functions"); + } + + // only available in XInput1_4 and XInput1_3 + GET_XINPUT_PROC(XInputGetBatteryInformation); +#undef GET_XINPUT_PROC + +} + +XInputControllerProvider::~XInputControllerProvider() +{ + if (m_module) + FreeLibrary(m_module); +} + +std::vector> XInputControllerProvider::get_controllers() +{ + std::vector> result; + for(DWORD i = 0; i < XUSER_MAX_COUNT; ++i) + { + XINPUT_CAPABILITIES caps; + if (m_XInputGetCapabilities(i, XINPUT_FLAG_GAMEPAD, &caps) == ERROR_SUCCESS) + { + result.emplace_back(std::make_shared(i)); + } + } + + return result; +} diff --git a/src/input/api/XInput/XInputControllerProvider.h b/src/input/api/XInput/XInputControllerProvider.h new file mode 100644 index 00000000..3fe07a82 --- /dev/null +++ b/src/input/api/XInput/XInputControllerProvider.h @@ -0,0 +1,32 @@ +#pragma once +#if BOOST_OS_WINDOWS +#include "input/api/ControllerProvider.h" + +#include + +#ifndef HAS_XINPUT +#define HAS_XINPUT 1 +#endif + + +class XInputControllerProvider : public ControllerProviderBase +{ + friend class XInputController; +public: + XInputControllerProvider(); + ~XInputControllerProvider() override; + + inline static InputAPI::Type kAPIType = InputAPI::XInput; + InputAPI::Type api() const override { return kAPIType; } + + std::vector> get_controllers() override; + +private: + HMODULE m_module = nullptr; + decltype(&XInputGetBatteryInformation) m_XInputGetBatteryInformation; + decltype(&XInputGetCapabilities) m_XInputGetCapabilities; + decltype(&XInputSetState) m_XInputSetState; + decltype(&XInputGetState) m_XInputGetState; +}; + +#endif \ No newline at end of file diff --git a/src/input/emulated/ClassicController.cpp b/src/input/emulated/ClassicController.cpp new file mode 100644 index 00000000..0bb579fe --- /dev/null +++ b/src/input/emulated/ClassicController.cpp @@ -0,0 +1,258 @@ +#include "input/emulated/ClassicController.h" + +#include "input/api/Controller.h" +#include "input/api/SDL/SDLController.h" + + +ClassicController::ClassicController(size_t player_index) + : WPADController(player_index, kDataFormat_CLASSIC) +{ +} + +uint32 ClassicController::get_emulated_button_flag(uint32 id) const +{ + return s_get_emulated_button_flag(id); +} + +uint32 ClassicController::s_get_emulated_button_flag(uint32 id) +{ + switch (id) + { + case kButtonId_A: + return kCLButton_A; + case kButtonId_B: + return kCLButton_B; + case kButtonId_X: + return kCLButton_X; + case kButtonId_Y: + return kCLButton_Y; + + case kButtonId_Plus: + return kCLButton_Plus; + case kButtonId_Minus: + return kCLButton_Minus; + + case kButtonId_Up: + return kCLButton_Up; + case kButtonId_Down: + return kCLButton_Down; + case kButtonId_Left: + return kCLButton_Left; + case kButtonId_Right: + return kCLButton_Right; + + case kButtonId_L: + return kCLButton_L; + case kButtonId_ZL: + return kCLButton_ZL; + case kButtonId_R: + return kCLButton_R; + case kButtonId_ZR: + return kCLButton_ZR; + } + + return 0; +} + +std::string_view ClassicController::get_button_name(ButtonId id) +{ + switch (id) + { + case kButtonId_A: return "A"; + case kButtonId_B: return "B"; + case kButtonId_X: return "X"; + case kButtonId_Y: return "Y"; + case kButtonId_L: return "L"; + case kButtonId_R: return "R"; + case kButtonId_ZL: return "ZL"; + case kButtonId_ZR: return "ZR"; + + case kButtonId_Plus: return "+"; + case kButtonId_Minus: return "-"; + case kButtonId_Home: return "home"; + + case kButtonId_Up: return "up"; + case kButtonId_Down: return "down"; + case kButtonId_Left: return "left"; + case kButtonId_Right: return "right"; + + case kButtonId_StickL_Up: return "up"; + case kButtonId_StickL_Down: return "down"; + case kButtonId_StickL_Left: return "left"; + case kButtonId_StickL_Right: return "right"; + + case kButtonId_StickR_Up: return "up"; + case kButtonId_StickR_Down: return "down"; + case kButtonId_StickR_Left: return "left"; + case kButtonId_StickR_Right: return "right"; + + default: + return ""; + } +} + +glm::vec2 ClassicController::get_axis() const +{ + const auto left = get_axis_value(kButtonId_StickL_Left); + const auto right = get_axis_value(kButtonId_StickL_Right); + + const auto up = get_axis_value(kButtonId_StickL_Up); + const auto down = get_axis_value(kButtonId_StickL_Down); + + glm::vec2 result; + result.x = (left > right) ? -left : right; + result.y = (up > down) ? up : -down; + return length(result) > 1.0f ? normalize(result) : result; +} + +glm::vec2 ClassicController::get_rotation() const +{ + const auto left = get_axis_value(kButtonId_StickR_Left); + const auto right = get_axis_value(kButtonId_StickR_Right); + + const auto up = get_axis_value(kButtonId_StickR_Up); + const auto down = get_axis_value(kButtonId_StickR_Down); + + glm::vec2 result; + result.x = (left > right) ? -left : right; + result.y = (up > down) ? up : -down; + return length(result) > 1.0f ? normalize(result) : result; +} + +glm::vec2 ClassicController::get_trigger() const +{ + const auto left = get_axis_value(kButtonId_ZL); + const auto right = get_axis_value(kButtonId_ZR); + return { left, right }; +} + +bool ClassicController::set_default_mapping(const std::shared_ptr& controller) +{ + std::vector> mapping; + switch (controller->api()) + { + case InputAPI::SDLController: { + const auto sdl_controller = std::static_pointer_cast(controller); + if (sdl_controller->get_guid() == SDLController::kLeftJoyCon) + { + mapping = + { + {kButtonId_L, kButton9}, + {kButtonId_ZL, kTriggerXP}, + + {kButtonId_Minus, kButton4}, + + {kButtonId_Up, kButton11}, + {kButtonId_Down, kButton12}, + {kButtonId_Left, kButton13}, + {kButtonId_Right, kButton14}, + + {kButtonId_StickL_Up, kAxisYN}, + {kButtonId_StickL_Down, kAxisYP}, + {kButtonId_StickL_Left, kAxisXN}, + {kButtonId_StickL_Right, kAxisXP}, + }; + } + else if (sdl_controller->get_guid() == SDLController::kRightJoyCon) + { + mapping = + { + {kButtonId_A, kButton0}, + {kButtonId_B, kButton1}, + {kButtonId_X, kButton2}, + {kButtonId_Y, kButton3}, + + {kButtonId_R, kButton10}, + {kButtonId_ZR, kTriggerYP}, + + {kButtonId_Plus, kButton6}, + + {kButtonId_StickR_Up, kRotationYN}, + {kButtonId_StickR_Down, kRotationYP}, + {kButtonId_StickR_Left, kRotationXN}, + {kButtonId_StickR_Right, kRotationXP}, + }; + } + else + { + mapping = + { + {kButtonId_A, kButton1}, + {kButtonId_B, kButton0}, + {kButtonId_X, kButton3}, + {kButtonId_Y, kButton2}, + + {kButtonId_L, kButton9}, + {kButtonId_R, kButton10}, + {kButtonId_ZL, kTriggerXP}, + {kButtonId_ZR, kTriggerYP}, + + {kButtonId_Plus, kButton6}, + {kButtonId_Minus, kButton4}, + + {kButtonId_Up, kButton11}, + {kButtonId_Down, kButton12}, + {kButtonId_Left, kButton13}, + {kButtonId_Right, kButton14}, + + {kButtonId_StickL_Up, kAxisYN}, + {kButtonId_StickL_Down, kAxisYP}, + {kButtonId_StickL_Left, kAxisXN}, + {kButtonId_StickL_Right, kAxisXP}, + + {kButtonId_StickR_Up, kRotationYN}, + {kButtonId_StickR_Down, kRotationYP}, + {kButtonId_StickR_Left, kRotationXN}, + {kButtonId_StickR_Right, kRotationXP}, + }; + } + } + case InputAPI::XInput: + { + mapping = + { + {kButtonId_A, kButton13}, + {kButtonId_B, kButton12}, + {kButtonId_X, kButton15}, + {kButtonId_Y, kButton14}, + + {kButtonId_L, kButton8}, + {kButtonId_R, kButton9}, + {kButtonId_ZL, kTriggerXP}, + {kButtonId_ZR, kTriggerYP}, + + {kButtonId_Plus, kButton4}, + {kButtonId_Minus, kButton5}, + + {kButtonId_Up, kButton0}, + {kButtonId_Down, kButton1}, + {kButtonId_Left, kButton2}, + {kButtonId_Right, kButton3}, + + {kButtonId_StickL_Up, kAxisYP}, + {kButtonId_StickL_Down, kAxisYN}, + {kButtonId_StickL_Left, kAxisXN}, + {kButtonId_StickL_Right, kAxisXP}, + + {kButtonId_StickR_Up, kRotationYP}, + {kButtonId_StickR_Down, kRotationYN}, + {kButtonId_StickR_Left, kRotationXN}, + {kButtonId_StickR_Right, kRotationXP}, + }; + + break; + } + } + + bool mapping_updated = false; + std::for_each(mapping.cbegin(), mapping.cend(), [this, &controller, &mapping_updated](const auto& m) + { + if (m_mappings.find(m.first) == m_mappings.cend()) + { + set_mapping(m.first, controller, m.second); + mapping_updated = true; + } + }); + + return mapping_updated; +} \ No newline at end of file diff --git a/src/input/emulated/ClassicController.h b/src/input/emulated/ClassicController.h new file mode 100644 index 00000000..1ca41f1f --- /dev/null +++ b/src/input/emulated/ClassicController.h @@ -0,0 +1,72 @@ +#pragma once + +#include "input/emulated/WPADController.h" + +class ClassicController : public WPADController +{ +public: + enum ButtonId + { + kButtonId_None, + + kButtonId_A, + kButtonId_B, + kButtonId_X, + kButtonId_Y, + + kButtonId_L, + kButtonId_R, + kButtonId_ZL, + kButtonId_ZR, + + kButtonId_Plus, + kButtonId_Minus, + kButtonId_Home, + + kButtonId_Up, + kButtonId_Down, + kButtonId_Left, + kButtonId_Right, + + kButtonId_StickL_Up, + kButtonId_StickL_Down, + kButtonId_StickL_Left, + kButtonId_StickL_Right, + + kButtonId_StickR_Up, + kButtonId_StickR_Down, + kButtonId_StickR_Left, + kButtonId_StickR_Right, + + kButtonId_Max, + }; + + ClassicController(size_t player_index); + + Type type() const override { return Type::Classic; } + WPADDeviceType get_device_type() const override { return kWAPDevClassic; } + + uint32 get_emulated_button_flag(uint32 id) const override; + size_t get_highest_mapping_id() const override { return kButtonId_Max; } + bool is_axis_mapping(uint64 mapping) const override { return mapping >= kButtonId_StickL_Up && mapping <= kButtonId_StickR_Right; } + + glm::vec2 get_axis() const override; + glm::vec2 get_rotation() const override; + glm::vec2 get_trigger() const override; + + + static uint32 s_get_emulated_button_flag(uint32 id); + + static std::string_view get_button_name(ButtonId id); + + bool is_start_down() const override { return is_mapping_down(kButtonId_Plus); } + bool is_left_down() const override { return is_mapping_down(kButtonId_Left); } + bool is_right_down() const override { return is_mapping_down(kButtonId_Right); } + bool is_up_down() const override { return is_mapping_down(kButtonId_Up); } + bool is_down_down() const override { return is_mapping_down(kButtonId_Down); } + bool is_a_down() const override { return is_mapping_down(kButtonId_A); } + bool is_b_down() const override { return is_mapping_down(kButtonId_B); } + bool is_home_down() const override { return is_mapping_down(kButtonId_Home); } + + bool set_default_mapping(const std::shared_ptr& controller) override; +}; diff --git a/src/input/emulated/EmulatedController.cpp b/src/input/emulated/EmulatedController.cpp new file mode 100644 index 00000000..69c058f7 --- /dev/null +++ b/src/input/emulated/EmulatedController.cpp @@ -0,0 +1,341 @@ +#include "input/emulated/EmulatedController.h" + +#include "input/api/Controller.h" + +#if BOOST_OS_WINDOWS +#include "input/api/Wiimote/NativeWiimoteController.h" +#endif + +std::string_view EmulatedController::type_to_string(Type type) +{ + switch (type) + { + case VPAD: return "Wii U GamePad"; + case Pro: return "Wii U Pro Controller"; + case Classic: return "Wii U Classic Controller"; + case Wiimote: return "Wiimote"; + } + + throw std::runtime_error(fmt::format("unknown emulated controller: {}", to_underlying(type))); +} + +EmulatedController::Type EmulatedController::type_from_string(std::string_view str) +{ + if (str == "Wii U GamePad") + return VPAD; + else if (str == "Wii U Pro Controller") + return Pro; + else if (str == "Wii U Classic Controller") + return Classic; + else if (str == "Wiimote") + return Wiimote; + + throw std::runtime_error(fmt::format("unknown emulated controller: {}", str)); +} + +EmulatedController::EmulatedController(size_t player_index) + : m_player_index(player_index) +{ +} + +void EmulatedController::calibrate() +{ + std::shared_lock lock(m_mutex); + for (const auto& controller : m_controllers) + { + controller->calibrate(); + } +} + +void EmulatedController::connect() +{ + std::shared_lock lock(m_mutex); + for (const auto& controller : m_controllers) + { + controller->connect(); + } +} + +void EmulatedController::update() +{ + std::shared_lock lock(m_mutex); + for(const auto& controller : m_controllers) + { + controller->update(); + } +} + +void EmulatedController::controllers_update_states() +{ + std::shared_lock lock(m_mutex); + for (const auto& controller : m_controllers) + { + controller->update_state(); + } +} + +void EmulatedController::start_rumble() +{ + m_rumble = true; + std::shared_lock lock(m_mutex); + for (const auto& controller : m_controllers) + { + controller->start_rumble(); + } +} + +void EmulatedController::stop_rumble() +{ + if (!m_rumble) + return; + + m_rumble = false; + + std::shared_lock lock(m_mutex); + for (const auto& controller : m_controllers) + { + controller->stop_rumble(); + } +} + +bool EmulatedController::is_battery_low() const +{ + std::shared_lock lock(m_mutex); + return std::any_of(m_controllers.cbegin(), m_controllers.cend(), [](const auto& c) {return c->has_low_battery(); }); +} + +bool EmulatedController::has_motion() const +{ + std::shared_lock lock(m_mutex); + return std::any_of(m_controllers.cbegin(), m_controllers.cend(), [](const auto& c) {return c->use_motion(); }); +} + +MotionSample EmulatedController::get_motion_data() const +{ + std::shared_lock lock(m_mutex); + for (const auto& controller : m_controllers) + { + if (controller->use_motion()) + return controller->get_motion_sample(); + } + + return {}; +} + +bool EmulatedController::has_second_motion() const +{ + int motion = 0; + std::shared_lock lock(m_mutex); + for(const auto& controller : m_controllers) + { + if(controller->use_motion()) + { + // if wiimote has nunchuck connected, we use its acceleration + #if BOOST_OS_WINDOWS + if(controller->api() == InputAPI::Wiimote) + { + if(((NativeWiimoteController*)controller.get())->get_extension() == NativeWiimoteController::Nunchuck) + { + return true; + } + } + #endif + motion++; + } + } + + return motion >= 2; +} + +MotionSample EmulatedController::get_second_motion_data() const +{ + int motion = 0; + std::shared_lock lock(m_mutex); + for (const auto& controller : m_controllers) + { + if (controller->use_motion()) + { + // if wiimote has nunchuck connected, we use its acceleration + #if BOOST_OS_WINDOWS + if (controller->api() == InputAPI::Wiimote) + { + if (((NativeWiimoteController*)controller.get())->get_extension() == NativeWiimoteController::Nunchuck) + { + return ((NativeWiimoteController*)controller.get())->get_nunchuck_motion_sample(); + } + } + #endif + + motion++; + if(motion == 2) + { + return controller->get_motion_sample(); + } + } + } + + return {}; +} + +bool EmulatedController::has_position() const +{ + std::shared_lock lock(m_mutex); + return std::any_of(m_controllers.cbegin(), m_controllers.cend(), [](const auto& c) {return c->has_position(); }); +} + +glm::vec2 EmulatedController::get_position() const +{ + std::shared_lock lock(m_mutex); + for (const auto& controller : m_controllers) + { + if (controller->has_position()) + return controller->get_position(); + } + + return {}; +} + +glm::vec2 EmulatedController::get_prev_position() const +{ + std::shared_lock lock(m_mutex); + for (const auto& controller : m_controllers) + { + if (controller->has_position()) + return controller->get_prev_position(); + } + + return {}; +} + +std::shared_ptr EmulatedController::find_controller(std::string_view uuid, InputAPI::Type type) const +{ + std::scoped_lock lock(m_mutex); + const auto it = std::find_if(m_controllers.cbegin(), m_controllers.cend(), [uuid, type](const auto& c) { return c->api() == type && c->uuid() == uuid; }); + if (it != m_controllers.cend()) + return *it; + + return {}; +} + +void EmulatedController::add_controller(std::shared_ptr controller) +{ + controller->connect(); + + #if BOOST_OS_WINDOWS + if (const auto wiimote = std::dynamic_pointer_cast(controller)) { + wiimote->set_player_index(m_player_index); + } + #endif + + std::scoped_lock lock(m_mutex); + m_controllers.emplace_back(std::move(controller)); +} + +void EmulatedController::remove_controller(const std::shared_ptr& controller) +{ + std::scoped_lock lock(m_mutex); + const auto it = std::find(m_controllers.cbegin(), m_controllers.cend(), controller); + if (it != m_controllers.cend()) + { + m_controllers.erase(it); + + for(auto m = m_mappings.begin(); m != m_mappings.end();) + { + if(auto mc = m->second.controller.lock()) + { + if(*mc == *controller) + { + m = m_mappings.erase(m); + continue; + } + } + + ++m; + } + } +} + +void EmulatedController::clear_controllers() +{ + std::scoped_lock lock(m_mutex); + m_controllers.clear(); +} + +float EmulatedController::get_axis_value(uint64 mapping) const +{ + const auto it = m_mappings.find(mapping); + if (it != m_mappings.cend()) + { + if (const auto controller = it->second.controller.lock()) { + return controller->get_axis_value(it->second.button); + } + } + + return 0; +} + +bool EmulatedController::is_mapping_down(uint64 mapping) const +{ + const auto it = m_mappings.find(mapping); + if (it != m_mappings.cend()) + { + if (const auto controller = it->second.controller.lock()) { + return controller->get_state().buttons.test(it->second.button); + } + } + + return false; +} + + +std::string EmulatedController::get_mapping_name(uint64 mapping) const +{ + const auto it = m_mappings.find(mapping); + if (it != m_mappings.cend()) + { + if (const auto controller = it->second.controller.lock()) { + return controller->get_button_name(it->second.button); + } + } + + return {}; +} + +std::shared_ptr EmulatedController::get_mapping_controller(uint64 mapping) const +{ + const auto it = m_mappings.find(mapping); + if (it != m_mappings.cend()) + { + if (const auto controller = it->second.controller.lock()) { + return controller; + } + } + + return {}; +} + +void EmulatedController::delete_mapping(uint64 mapping) +{ + m_mappings.erase(mapping); +} + +void EmulatedController::clear_mappings() +{ + m_mappings.clear(); +} + +void EmulatedController::set_mapping(uint64 mapping, const std::shared_ptr& controller, + uint64 button) +{ + m_mappings[mapping] = { controller, button }; +} + +bool EmulatedController::operator==(const EmulatedController& o) const +{ + return type() == o.type() && m_player_index == o.m_player_index; +} + +bool EmulatedController::operator!=(const EmulatedController& o) const +{ + return !(*this == o); +} diff --git a/src/input/emulated/EmulatedController.h b/src/input/emulated/EmulatedController.h new file mode 100644 index 00000000..bcc5c165 --- /dev/null +++ b/src/input/emulated/EmulatedController.h @@ -0,0 +1,140 @@ +#pragma once +#include + +class ControllerBase; + +#include "input/motion/MotionSample.h" + +#include "input/api/ControllerState.h" +#include "input/api/InputAPI.h" + +#include "util/helpers/helpers.h" + +// mapping = wii u controller button id +// button = api button id + +class EmulatedController +{ + friend class InputManager; +public: + EmulatedController(size_t player_index); + virtual ~EmulatedController() = default; + + virtual void load(const pugi::xml_node& node){}; + virtual void save(pugi::xml_node& node){}; + + enum Type + { + VPAD, + Pro, + Classic, + Wiimote, + + MAX + }; + virtual Type type() const = 0; + std::string_view type_string() const { return type_to_string(type()); } + + static std::string_view type_to_string(Type type); + static Type type_from_string(std::string_view str); + + size_t player_index() const { return m_player_index; } + const std::string& get_profile_name() const { return m_profile_name; } + bool has_profile_name() const { return !m_profile_name.empty() && m_profile_name != "default"; } + + void calibrate(); + + void connect(); + virtual void update(); + void controllers_update_states(); + + virtual glm::vec2 get_axis() const = 0; + virtual glm::vec2 get_rotation() const = 0; + virtual glm::vec2 get_trigger() const = 0; + + void start_rumble(); + void stop_rumble(); + + bool is_battery_low() const; + + bool has_motion() const; + MotionSample get_motion_data() const; + + // some controllers (nunchuck) provide extra motion data + bool has_second_motion() const; + MotionSample get_second_motion_data() const; + + bool has_position() const; + glm::vec2 get_position() const; + glm::vec2 get_prev_position() const; + + std::shared_ptr find_controller(std::string_view uuid, InputAPI::Type type) const; + void add_controller(std::shared_ptr controller); + void remove_controller(const std::shared_ptr& controller); + void clear_controllers(); + const std::vector>& get_controllers() const { return m_controllers; } + + bool is_mapping_down(uint64 mapping) const; + std::string get_mapping_name(uint64 mapping) const; + std::shared_ptr get_mapping_controller(uint64 mapping) const; + void delete_mapping(uint64 mapping); + void clear_mappings(); + void set_mapping(uint64 mapping, const std::shared_ptr& controller_base, uint64 button); + + virtual uint32 get_emulated_button_flag(uint32 mapping) const = 0; + + bool operator==(const EmulatedController& o) const; + bool operator!=(const EmulatedController& o) const; + + virtual size_t get_highest_mapping_id() const = 0; + virtual bool is_axis_mapping(uint64 mapping) const = 0; + + virtual bool is_start_down() const = 0; + virtual bool is_left_down() const = 0; + virtual bool is_right_down() const = 0; + virtual bool is_up_down() const = 0; + virtual bool is_down_down() const = 0; + virtual bool is_a_down() const = 0; + virtual bool is_b_down() const = 0; + virtual bool is_home_down() const = 0; + + bool was_home_button_down() { return std::exchange(m_homebutton_down, false); } + + virtual bool set_default_mapping(const std::shared_ptr& controller) { return false; } + +protected: + size_t m_player_index; + std::string m_profile_name = "default"; + + mutable std::shared_mutex m_mutex; + std::vector> m_controllers; + + float get_axis_value(uint64 mapping) const; + bool m_rumble = false; + + struct Mapping + { + std::weak_ptr controller; + uint64 button; + }; + std::unordered_map m_mappings; + + bool m_homebutton_down = false; +}; + +using EmulatedControllerPtr = std::shared_ptr; + +template <> +struct fmt::formatter : formatter { + template + auto format(EmulatedController::Type v, FormatContext& ctx) { + switch (v) + { + case EmulatedController::Type::VPAD: return formatter::format("Wii U Gamepad", ctx); + case EmulatedController::Type::Pro: return formatter::format("Wii U Pro Controller", ctx); + case EmulatedController::Type::Classic: return formatter::format("Wii U Classic Controller Pro", ctx); + case EmulatedController::Type::Wiimote: return formatter::format("Wiimote", ctx); + } + throw std::invalid_argument(fmt::format("invalid emulated controller type with value {}", to_underlying(v))); + } +}; diff --git a/src/input/emulated/ProController.cpp b/src/input/emulated/ProController.cpp new file mode 100644 index 00000000..521f88b2 --- /dev/null +++ b/src/input/emulated/ProController.cpp @@ -0,0 +1,272 @@ +#include "input/emulated/ProController.h" + +#include "input/api/Controller.h" +#include "input/api/SDL/SDLController.h" + +ProController::ProController(size_t player_index) + : WPADController(player_index, kDataFormat_URCC) +{ + +} + +uint32 ProController::get_emulated_button_flag(uint32 id) const +{ + return s_get_emulated_button_flag(id); +} + +uint32 ProController::s_get_emulated_button_flag(uint32 id) +{ + switch (id) + { + case kButtonId_A: + return kProButton_A; + case kButtonId_B: + return kProButton_B; + case kButtonId_X: + return kProButton_X; + case kButtonId_Y: + return kProButton_Y; + + case kButtonId_Plus: + return kProButton_Plus; + case kButtonId_Minus: + return kProButton_Minus; + case kButtonId_Up: + return kProButton_Up; + case kButtonId_Down: + return kProButton_Down; + case kButtonId_Left: + return kProButton_Left; + case kButtonId_Right: + return kProButton_Right; + + case kButtonId_StickL: + return kProButton_StickL; + case kButtonId_StickR: + return kProButton_StickR; + + case kButtonId_L: + return kProButton_L; + case kButtonId_ZL: + return kProButton_ZL; + case kButtonId_R: + return kProButton_R; + case kButtonId_ZR: + return kProButton_ZR; + + default: + return 0; + } +} + +std::string_view ProController::get_button_name(ButtonId id) +{ + switch (id) + { + case kButtonId_A: return "A"; + case kButtonId_B: return "B"; + case kButtonId_X: return "X"; + case kButtonId_Y: return "Y"; + case kButtonId_L: return "L"; + case kButtonId_R: return "R"; + case kButtonId_ZL: return "ZL"; + case kButtonId_ZR: return "ZR"; + case kButtonId_Plus: return "+"; + case kButtonId_Minus: return "-"; + case kButtonId_Up: return "up"; + case kButtonId_Down: return "down"; + case kButtonId_Left: return "left"; + case kButtonId_Right: return "right"; + case kButtonId_StickL: return "click"; + case kButtonId_StickR: return "click"; + case kButtonId_StickL_Up: return "up"; + case kButtonId_StickL_Down: return "down"; + case kButtonId_StickL_Left: return "left"; + case kButtonId_StickL_Right: return "right"; + case kButtonId_StickR_Up: return "up"; + case kButtonId_StickR_Down: return "down"; + case kButtonId_StickR_Left: return "left"; + case kButtonId_StickR_Right: return "right"; + case kButtonId_Home: return "home"; + default: + cemu_assert_debug(false); + return ""; + } +} + + +glm::vec2 ProController::get_axis() const +{ + const auto left = get_axis_value(kButtonId_StickL_Left); + const auto right = get_axis_value(kButtonId_StickL_Right); + + const auto up = get_axis_value(kButtonId_StickL_Up); + const auto down = get_axis_value(kButtonId_StickL_Down); + + glm::vec2 result; + result.x = (left > right) ? -left : right; + result.y = (up > down) ? up : -down; + return result; +} + +glm::vec2 ProController::get_rotation() const +{ + const auto left = get_axis_value(kButtonId_StickR_Left); + const auto right = get_axis_value(kButtonId_StickR_Right); + + const auto up = get_axis_value(kButtonId_StickR_Up); + const auto down = get_axis_value(kButtonId_StickR_Down); + + glm::vec2 result; + result.x = (left > right) ? -left : right; + result.y = (up > down) ? up : -down; + return result; +} + +glm::vec2 ProController::get_trigger() const +{ + const auto left = get_axis_value(kButtonId_ZL); + const auto right = get_axis_value(kButtonId_ZR); + return { left, right }; +} + +bool ProController::set_default_mapping(const std::shared_ptr& controller) +{ + std::vector> mapping; + switch (controller->api()) + { + case InputAPI::SDLController: { + const auto sdl_controller = std::static_pointer_cast(controller); + if (sdl_controller->get_guid() == SDLController::kLeftJoyCon) + { + mapping = + { + {kButtonId_L, kButton9}, + {kButtonId_ZL, kTriggerXP}, + + {kButtonId_Minus, kButton4}, + + {kButtonId_Up, kButton11}, + {kButtonId_Down, kButton12}, + {kButtonId_Left, kButton13}, + {kButtonId_Right, kButton14}, + + {kButtonId_StickL, kButton7}, + + {kButtonId_StickL_Up, kAxisYN}, + {kButtonId_StickL_Down, kAxisYP}, + {kButtonId_StickL_Left, kAxisXN}, + {kButtonId_StickL_Right, kAxisXP}, + }; + } + else if (sdl_controller->get_guid() == SDLController::kRightJoyCon) + { + mapping = + { + {kButtonId_A, kButton0}, + {kButtonId_B, kButton1}, + {kButtonId_X, kButton2}, + {kButtonId_Y, kButton3}, + + {kButtonId_R, kButton10}, + {kButtonId_ZR, kTriggerYP}, + + {kButtonId_Plus, kButton6}, + + {kButtonId_StickR, kButton8}, + + {kButtonId_StickR_Up, kRotationYN}, + {kButtonId_StickR_Down, kRotationYP}, + {kButtonId_StickR_Left, kRotationXN}, + {kButtonId_StickR_Right, kRotationXP}, + }; + } + else + { + mapping = + { + {kButtonId_A, kButton1}, + {kButtonId_B, kButton0}, + {kButtonId_X, kButton3}, + {kButtonId_Y, kButton2}, + + {kButtonId_L, kButton9}, + {kButtonId_R, kButton10}, + {kButtonId_ZL, kTriggerXP}, + {kButtonId_ZR, kTriggerYP}, + + {kButtonId_Plus, kButton6}, + {kButtonId_Minus, kButton4}, + + {kButtonId_Up, kButton11}, + {kButtonId_Down, kButton12}, + {kButtonId_Left, kButton13}, + {kButtonId_Right, kButton14}, + + {kButtonId_StickL, kButton7}, + {kButtonId_StickR, kButton8}, + + {kButtonId_StickL_Up, kAxisYN}, + {kButtonId_StickL_Down, kAxisYP}, + {kButtonId_StickL_Left, kAxisXN}, + {kButtonId_StickL_Right, kAxisXP}, + + {kButtonId_StickR_Up, kRotationYN}, + {kButtonId_StickR_Down, kRotationYP}, + {kButtonId_StickR_Left, kRotationXN}, + {kButtonId_StickR_Right, kRotationXP}, + }; + } + break; + } + case InputAPI::XInput: + { + mapping = + { + {kButtonId_A, kButton13}, + {kButtonId_B, kButton12}, + {kButtonId_X, kButton15}, + {kButtonId_Y, kButton14}, + + {kButtonId_L, kButton8}, + {kButtonId_R, kButton9}, + {kButtonId_ZL, kTriggerXP}, + {kButtonId_ZR, kTriggerYP}, + + {kButtonId_Plus, kButton4}, + {kButtonId_Minus, kButton5}, + + {kButtonId_Up, kButton0}, + {kButtonId_Down, kButton1}, + {kButtonId_Left, kButton2}, + {kButtonId_Right, kButton3}, + + {kButtonId_StickL, kButton6}, + {kButtonId_StickR, kButton7}, + + {kButtonId_StickL_Up, kAxisYP}, + {kButtonId_StickL_Down, kAxisYN}, + {kButtonId_StickL_Left, kAxisXN}, + {kButtonId_StickL_Right, kAxisXP}, + + {kButtonId_StickR_Up, kRotationYP}, + {kButtonId_StickR_Down, kRotationYN}, + {kButtonId_StickR_Left, kRotationXN}, + {kButtonId_StickR_Right, kRotationXP}, + }; + break; + } + } + + bool mapping_updated = false; + std::for_each(mapping.cbegin(), mapping.cend(), [this, &controller, &mapping_updated](const auto& m) + { + if (m_mappings.find(m.first) == m_mappings.cend()) + { + set_mapping(m.first, controller, m.second); + mapping_updated = true; + } + }); + + return mapping_updated; +} \ No newline at end of file diff --git a/src/input/emulated/ProController.h b/src/input/emulated/ProController.h new file mode 100644 index 00000000..34e5e945 --- /dev/null +++ b/src/input/emulated/ProController.h @@ -0,0 +1,74 @@ +#pragma once + +#include "input/emulated/WPADController.h" + +class ProController : public WPADController +{ +public: + enum ButtonId + { + kButtonId_None, + + kButtonId_A, + kButtonId_B, + kButtonId_X, + kButtonId_Y, + + kButtonId_L, + kButtonId_R, + kButtonId_ZL, + kButtonId_ZR, + + kButtonId_Plus, + kButtonId_Minus, + kButtonId_Home, + + kButtonId_Up, + kButtonId_Down, + kButtonId_Left, + kButtonId_Right, + + kButtonId_StickL, + kButtonId_StickR, + + kButtonId_StickL_Up, + kButtonId_StickL_Down, + kButtonId_StickL_Left, + kButtonId_StickL_Right, + + kButtonId_StickR_Up, + kButtonId_StickR_Down, + kButtonId_StickR_Left, + kButtonId_StickR_Right, + + kButtonId_Max, + }; + + ProController(size_t player_index); + + Type type() const override { return Type::Pro; } + WPADDeviceType get_device_type() const override { return kWAPDevURCC; } + + uint32 get_emulated_button_flag(uint32 id) const override; + size_t get_highest_mapping_id() const override { return kButtonId_Max; } + bool is_axis_mapping(uint64 mapping) const override { return mapping >= kButtonId_StickL_Up && mapping <= kButtonId_StickR_Right; } + + glm::vec2 get_axis() const override; + glm::vec2 get_rotation() const override; + glm::vec2 get_trigger() const override; + + static uint32 s_get_emulated_button_flag(uint32 id); + + static std::string_view get_button_name(ButtonId id); + + bool is_start_down() const override { return is_mapping_down(kButtonId_Plus); } + bool is_left_down() const override { return is_mapping_down(kButtonId_Left); } + bool is_right_down() const override { return is_mapping_down(kButtonId_Right); } + bool is_up_down() const override { return is_mapping_down(kButtonId_Up); } + bool is_down_down() const override { return is_mapping_down(kButtonId_Down); } + bool is_a_down() const override { return is_mapping_down(kButtonId_A); } + bool is_b_down() const override { return is_mapping_down(kButtonId_B); } + bool is_home_down() const override { return is_mapping_down(kButtonId_Home); } + + bool set_default_mapping(const std::shared_ptr& controller) override; +}; diff --git a/src/input/emulated/VPADController.cpp b/src/input/emulated/VPADController.cpp new file mode 100644 index 00000000..887cfe83 --- /dev/null +++ b/src/input/emulated/VPADController.cpp @@ -0,0 +1,687 @@ +#include "input/emulated/VPADController.h" +#include "input/api/Controller.h" +#include "input/api/SDL/SDLController.h" +#include "gui/guiWrapper.h" +#include "input/InputManager.h" +#include "Cafe/HW/Latte/Core/Latte.h" +#include "Cafe/CafeSystem.h" + +enum ControllerVPADMapping2 : uint32 +{ + VPAD_A = 0x8000, + VPAD_B = 0x4000, + VPAD_X = 0x2000, + VPAD_Y = 0x1000, + + VPAD_L = 0x0020, + VPAD_R = 0x0010, + VPAD_ZL = 0x0080, + VPAD_ZR = 0x0040, + + VPAD_PLUS = 0x0008, + VPAD_MINUS = 0x0004, + VPAD_HOME = 0x0002, + + VPAD_UP = 0x0200, + VPAD_DOWN = 0x0100, + VPAD_LEFT = 0x0800, + VPAD_RIGHT = 0x0400, + + VPAD_STICK_R = 0x00020000, + VPAD_STICK_L = 0x00040000, + + VPAD_STICK_L_UP = 0x10000000, + VPAD_STICK_L_DOWN = 0x08000000, + VPAD_STICK_L_LEFT = 0x40000000, + VPAD_STICK_L_RIGHT = 0x20000000, + + VPAD_STICK_R_UP = 0x01000000, + VPAD_STICK_R_DOWN = 0x00800000, + VPAD_STICK_R_LEFT = 0x04000000, + VPAD_STICK_R_RIGHT = 0x02000000, + + // special flag + VPAD_REPEAT = 0x80000000, +}; + +void VPADController::VPADRead(VPADStatus_t& status, const BtnRepeat& repeat) +{ + controllers_update_states(); + m_mic_active = false; + m_screen_active = false; + for (uint32 i = kButtonId_A; i < kButtonId_Max; ++i) + { + // axis will be aplied later + if (is_axis_mapping(i)) + continue; + + if (is_mapping_down(i)) + { + const uint32 value = get_emulated_button_flag(i); + if (value == 0) + { + // special buttons + if (i == kButtonId_Mic) + m_mic_active = true; + else if (i == kButtonId_Screen) + m_screen_active = true; + + continue; + } + + status.hold |= value; + } + } + + m_homebutton_down |= is_home_down(); + + const auto axis = get_axis(); + status.leftStick.x = axis.x; + status.leftStick.y = axis.y; + + constexpr float kAxisThreshold = 0.5f; + constexpr float kHoldAxisThreshold = 0.1f; + const uint32 last_hold = m_last_holdvalue; + + if (axis.x <= -kAxisThreshold || (HAS_FLAG(last_hold, VPAD_STICK_L_LEFT) && axis.x <= -kHoldAxisThreshold)) + status.hold |= VPAD_STICK_L_LEFT; + else if (axis.x >= kAxisThreshold || (HAS_FLAG(last_hold, VPAD_STICK_L_RIGHT) && axis.x >= kHoldAxisThreshold)) + status.hold |= VPAD_STICK_L_RIGHT; + + if (axis.y <= -kAxisThreshold || (HAS_FLAG(last_hold, VPAD_STICK_L_DOWN) && axis.y <= -kHoldAxisThreshold)) + status.hold |= VPAD_STICK_L_DOWN; + else if (axis.y >= kAxisThreshold || (HAS_FLAG(last_hold, VPAD_STICK_L_UP) && axis.y >= kHoldAxisThreshold)) + status.hold |= VPAD_STICK_L_UP; + + const auto rotation = get_rotation(); + status.rightStick.x = rotation.x; + status.rightStick.y = rotation.y; + + if (rotation.x <= -kAxisThreshold || (HAS_FLAG(last_hold, VPAD_STICK_R_LEFT) && rotation.x <= -kHoldAxisThreshold)) + status.hold |= VPAD_STICK_R_LEFT; + else if (rotation.x >= kAxisThreshold || (HAS_FLAG(last_hold, VPAD_STICK_R_RIGHT) && rotation.x >= + kHoldAxisThreshold)) + status.hold |= VPAD_STICK_R_RIGHT; + + if (rotation.y <= -kAxisThreshold || (HAS_FLAG(last_hold, VPAD_STICK_R_DOWN) && rotation.y <= -kHoldAxisThreshold)) + status.hold |= VPAD_STICK_R_DOWN; + else if (rotation.y >= kAxisThreshold || (HAS_FLAG(last_hold, VPAD_STICK_R_UP) && rotation.y >= kHoldAxisThreshold)) + status.hold |= VPAD_STICK_R_UP; + + // button repeat + const auto now = std::chrono::high_resolution_clock::now(); + if (status.hold != m_last_holdvalue) + { + m_last_hold_change = m_last_pulse = now; + } + + if (repeat.pulse > 0) + { + if (m_last_hold_change + std::chrono::milliseconds(repeat.delay) >= now) + { + if ((m_last_pulse + std::chrono::milliseconds(repeat.pulse)) < now) + { + m_last_pulse = now; + status.hold |= VPAD_REPEAT; + } + } + } + + // general + status.release = m_last_holdvalue & ~status.hold; + status.trig = ~m_last_holdvalue & status.hold; + m_last_holdvalue = status.hold; + + // touch + update_touch(status); + + // motion + status.dir.x = {1, 0, 0}; + status.dir.y = {0, 1, 0}; + status.dir.z = {0, 0, 1}; + status.accXY = {1.0f, 0.0f}; + update_motion(status); +} + +void VPADController::update() +{ + EmulatedController::update(); + + if (!CafeSystem::IsTitleRunning()) + return; + + std::unique_lock lock(m_rumble_mutex); + if (m_rumble_queue.empty()) + { + m_parser = 0; + lock.unlock(); + + stop_rumble(); + + return; + } + + const auto tick = now_cached(); + if (std::chrono::duration_cast(tick - m_last_rumble_check).count() < 1000 / 60) + return; + + m_last_rumble_check = tick; + + const auto& it = m_rumble_queue.front(); + if (it[m_parser]) + start_rumble(); + else + stop_rumble(); + + ++m_parser; + if (m_parser >= it.size()) + { + m_rumble_queue.pop(); + m_parser = 0; + } +} + +void VPADController::update_touch(VPADStatus_t& status) +{ + status.tpData.touch = kTpTouchOff; + status.tpData.validity = kTpInvalid; + // keep x,y from previous update + // NGDK (Neko Game Development Kit 2) games (e.g. Mysterios Cities of Gold) rely on x/y remaining intact after touch is released + status.tpData.x = (uint16)m_last_touch_position.x; + status.tpData.y = (uint16)m_last_touch_position.y; + + auto& instance = InputManager::instance(); + bool pad_view; + if (has_position()) + { + const auto mouse = get_position(); + + status.tpData.touch = kTpTouchOn; + status.tpData.validity = kTpValid; + status.tpData.x = (uint16)(mouse.x * 3883.0f + 92.0f); + status.tpData.y = (uint16)(4095.0f - mouse.y * 3694.0f - 254.0f); + + m_last_touch_position = glm::ivec2{status.tpData.x, status.tpData.y}; + } + else if (const auto left_mouse = instance.get_left_down_mouse_info(&pad_view)) + { + glm::ivec2 image_pos, image_size; + LatteRenderTarget_getScreenImageArea(&image_pos.x, &image_pos.y, &image_size.x, &image_size.y, nullptr, nullptr, pad_view); + + glm::vec2 relative_mouse_pos = left_mouse.value() - image_pos; + relative_mouse_pos = { std::min(relative_mouse_pos.x, (float)image_size.x), std::min(relative_mouse_pos.y, (float)image_size.y) }; + relative_mouse_pos = { std::max(relative_mouse_pos.x, 0.0f), std::max(relative_mouse_pos.y, 0.0f) }; + relative_mouse_pos /= image_size; + + status.tpData.touch = kTpTouchOn; + status.tpData.validity = kTpValid; + status.tpData.x = (uint16)((relative_mouse_pos.x * 3883.0f) + 92.0f); + status.tpData.y = (uint16)(4095.0f - (relative_mouse_pos.y * 3694.0f) - 254.0f); + + m_last_touch_position = glm::ivec2{ status.tpData.x, status.tpData.y }; + + /*cemuLog_force("TDATA: {},{} -> {},{} -> {},{} -> {},{} -> {},{} -> {},{}", + left_mouse->x, left_mouse->y, + (left_mouse.value() - image_pos).x, (left_mouse.value() - image_pos).y, + relative_mouse_pos.x, relative_mouse_pos.y, + (uint16)(relative_mouse_pos.x * 3883.0 + 92.0), (uint16)(4095.0 - relative_mouse_pos.y * 3694.0 - 254.0), + status.tpData.x.value(), status.tpData.y.value(), status.tpData.x.bevalue(), status.tpData.y.bevalue() + );*/ + } + + status.tpProcessed1 = status.tpData; + status.tpProcessed2 = status.tpData; +} + +void VPADController::update_motion(VPADStatus_t& status) +{ + if (has_motion()) + { + auto motionSample = get_motion_data(); + + glm::vec3 acc; + motionSample.getVPADAccelerometer(&acc[0]); + //const auto& acc = motionSample.getVPADAccelerometer(); + status.acc.x = acc.x; + status.acc.y = acc.y; + status.acc.z = acc.z; + status.accMagnitude = motionSample.getVPADAccMagnitude(); + status.accAcceleration = motionSample.getVPADAccAcceleration(); + + glm::vec3 gyroChange; + motionSample.getVPADGyroChange(&gyroChange[0]); + //const auto& gyroChange = motionSample.getVPADGyroChange(); + status.gyroChange.x = gyroChange.x; + status.gyroChange.y = gyroChange.y; + status.gyroChange.z = gyroChange.z; + + //debug_printf("GyroChange %7.2lf %7.2lf %7.2lf\n", (float)status.gyroChange.x, (float)status.gyroChange.y, (float)status.gyroChange.z); + + glm::vec3 gyroOrientation; + motionSample.getVPADOrientation(&gyroOrientation[0]); + //const auto& gyroOrientation = motionSample.getVPADOrientation(); + status.gyroOrientation.x = gyroOrientation.x; + status.gyroOrientation.y = gyroOrientation.y; + status.gyroOrientation.z = gyroOrientation.z; + + float attitude[9]; + motionSample.getVPADAttitudeMatrix(attitude); + status.dir.x.x = attitude[0]; + status.dir.x.y = attitude[1]; + status.dir.x.z = attitude[2]; + status.dir.y.x = attitude[3]; + status.dir.y.y = attitude[4]; + status.dir.y.z = attitude[5]; + status.dir.z.x = attitude[6]; + status.dir.z.y = attitude[7]; + status.dir.z.z = attitude[8]; + return; + } + + bool pad_view; + auto& input_manager = InputManager::instance(); + if (const auto right_mouse = input_manager.get_right_down_mouse_info(&pad_view)) + { + const Vector2 mousePos(right_mouse->x, right_mouse->y); + + int w, h; + if (pad_view) + gui_getPadWindowSize(&w, &h); + else + gui_getWindowSize(&w, &h); + + float wx = mousePos.x / w; + float wy = mousePos.y / h; + + static glm::vec3 m_lastGyroRotation{}, m_startGyroRotation{}; + static bool m_startGyroRotationSet{}; + + float rotX = (wy * 2 - 1.0f) * 135.0f; // up/down best + float rotY = (wx * 2 - 1.0f) * -180.0f; // left/right + float rotZ = input_manager.m_mouse_wheel * 14.0f + m_lastGyroRotation.z; + input_manager.m_mouse_wheel = 0.0f; + + if (!m_startGyroRotationSet) + { + m_startGyroRotation = {rotX, rotY, rotZ}; + m_startGyroRotationSet = true; + } + + /* debug_printf("\n\ngyro:\n<%.02f, %.02f, %.02f>\n\n", + rotX, rotY, rotZ);*/ + + Quaternion q(rotX, rotY, rotZ); + auto rot = q.GetTransposedRotationMatrix(); + + /*m_forward = std::get<0>(rot); + m_right = std::get<1>(rot); + m_up = std::get<2>(rot);*/ + + status.dir.x = std::get<0>(rot); + status.dir.y = std::get<1>(rot); + status.dir.z = std::get<2>(rot); + + /*debug_printf("rot:\n<%.02f, %.02f, %.02f>\n<%.02f, %.02f, %.02f>\n<%.02f, %.02f, %.02f>\n\n", + (float)status.dir.x.x, (float)status.dir.x.y, (float)status.dir.x.z, + (float)status.dir.y.x, (float)status.dir.y.y, (float)status.dir.y.z, + (float)status.dir.z.x, (float)status.dir.z.y, (float)status.dir.z.z);*/ + + glm::vec3 rotation(rotX - m_lastGyroRotation.x, (rotY - m_lastGyroRotation.y) * 15.0f, + rotZ - m_lastGyroRotation.z); + + rotation.x = std::min(1.0f, std::max(-1.0f, rotation.x / 360.0f)); + rotation.y = std::min(1.0f, std::max(-1.0f, rotation.y / 360.0f)); + rotation.z = std::min(1.0f, std::max(-1.0f, rotation.z / 360.0f)); + + /*debug_printf("\n\ngyro:\n<%.02f, %.02f, %.02f>\n\n", + rotation.x, rotation.y, rotation.z);*/ + + constexpr float pi2 = (float)(M_PI * 2); + status.gyroChange = {rotation.x, rotation.y, rotation.z}; + status.gyroOrientation = {rotation.x, rotation.y, rotation.z}; + //status.angle = { rotation.x / pi2, rotation.y / pi2, rotation.z / pi2 }; + + status.acc = {rotation.x, rotation.y, rotation.z}; + status.accAcceleration = 1.0f; + status.accMagnitude = 1.0f; + + status.accXY = {1.0f, 0.0f}; + + m_lastGyroRotation = {rotX, rotY, rotZ}; + } +} + + +std::string_view VPADController::get_button_name(ButtonId id) +{ + switch (id) + { + case kButtonId_A: return "A"; + case kButtonId_B: return "B"; + case kButtonId_X: return "X"; + case kButtonId_Y: return "Y"; + case kButtonId_L: return "L"; + case kButtonId_R: return "R"; + case kButtonId_ZL: return "ZL"; + case kButtonId_ZR: return "ZR"; + case kButtonId_Plus: return "+"; + case kButtonId_Minus: return "-"; + case kButtonId_Up: return "up"; + case kButtonId_Down: return "down"; + case kButtonId_Left: return "left"; + case kButtonId_Right: return "right"; + case kButtonId_StickL: return "click"; + case kButtonId_StickR: return "click"; + case kButtonId_StickL_Up: return "up"; + case kButtonId_StickL_Down: return "down"; + case kButtonId_StickL_Left: return "left"; + case kButtonId_StickL_Right: return "right"; + case kButtonId_StickR_Up: return "up"; + case kButtonId_StickR_Down: return "down"; + case kButtonId_StickR_Left: return "left"; + case kButtonId_StickR_Right: return "right"; + case kButtonId_Home: return "home"; + default: + cemu_assert_debug(false); + return ""; + } +} + +void VPADController::clear_rumble() +{ + std::scoped_lock lock(m_rumble_mutex); + while (!m_rumble_queue.empty()) + m_rumble_queue.pop(); + + m_parser = 0; +} + +bool VPADController::push_rumble(uint8* pattern, uint8 length) +{ + if (pattern == nullptr || length == 0) + { + clear_rumble(); + return true; + } + + std::scoped_lock lock(m_rumble_mutex); + if (m_rumble_queue.size() >= 5) + { + forceLogDebug_printf("too many cmds"); + return false; + } + + // len = max 15 bytes of data = 120 bits = 1 seconds + // we will use 60 hz for 1 second + std::vector bitset; + int byte = 0; + int len = (int)length; + while (len > 0) + { + const uint8 p = pattern[byte]; + for (int j = 0; j < 8 && j < len; j += 2) + { + const bool set = (p & (3 << j)) != 0; + bitset.push_back(set); + } + + ++byte; + len -= 8; + } + + + m_rumble_queue.emplace(std::move(bitset)); + m_last_rumble_check = {}; + + return true; +} + +uint32 VPADController::get_emulated_button_flag(uint32 id) const +{ + switch (id) + { + case kButtonId_A: return VPAD_A; + case kButtonId_B: return VPAD_B; + case kButtonId_X: return VPAD_X; + case kButtonId_Y: return VPAD_Y; + case kButtonId_L: return VPAD_L; + case kButtonId_R: return VPAD_R; + case kButtonId_ZL: return VPAD_ZL; + case kButtonId_ZR: return VPAD_ZR; + case kButtonId_Plus: return VPAD_PLUS; + case kButtonId_Minus: return VPAD_MINUS; + case kButtonId_Up: return VPAD_UP; + case kButtonId_Down: return VPAD_DOWN; + case kButtonId_Left: return VPAD_LEFT; + case kButtonId_Right: return VPAD_RIGHT; + case kButtonId_StickL: return VPAD_STICK_L; + case kButtonId_StickR: return VPAD_STICK_R; + + case kButtonId_StickL_Up: return VPAD_STICK_L_UP; + case kButtonId_StickL_Down: return VPAD_STICK_L_DOWN; + case kButtonId_StickL_Left: return VPAD_STICK_L_LEFT; + case kButtonId_StickL_Right: return VPAD_STICK_L_RIGHT; + + case kButtonId_StickR_Up: return VPAD_STICK_R_UP; + case kButtonId_StickR_Down: return VPAD_STICK_R_DOWN; + case kButtonId_StickR_Left: return VPAD_STICK_R_LEFT; + case kButtonId_StickR_Right: return VPAD_STICK_R_RIGHT; + } + return 0; +} + +glm::vec2 VPADController::get_axis() const +{ + const auto left = get_axis_value(kButtonId_StickL_Left); + const auto right = get_axis_value(kButtonId_StickL_Right); + + const auto up = get_axis_value(kButtonId_StickL_Up); + const auto down = get_axis_value(kButtonId_StickL_Down); + + glm::vec2 result; + result.x = (left > right) ? -left : right; + result.y = (up > down) ? up : -down; + return length(result) > 1.0f ? normalize(result) : result; +} + +glm::vec2 VPADController::get_rotation() const +{ + const auto left = get_axis_value(kButtonId_StickR_Left); + const auto right = get_axis_value(kButtonId_StickR_Right); + + const auto up = get_axis_value(kButtonId_StickR_Up); + const auto down = get_axis_value(kButtonId_StickR_Down); + + glm::vec2 result; + result.x = (left > right) ? -left : right; + result.y = (up > down) ? up : -down; + return length(result) > 1.0f ? normalize(result) : result; +} + +glm::vec2 VPADController::get_trigger() const +{ + const auto left = get_axis_value(kButtonId_ZL); + const auto right = get_axis_value(kButtonId_ZR); + return {left, right}; +} + +bool VPADController::set_default_mapping(const std::shared_ptr& controller) +{ + std::vector> mapping; + switch (controller->api()) + { + case InputAPI::SDLController: { + const auto sdl_controller = std::static_pointer_cast(controller); + if (sdl_controller->get_guid() == SDLController::kLeftJoyCon) + { + mapping = + { + {kButtonId_L, kButton9}, + {kButtonId_ZL, kTriggerXP}, + + {kButtonId_Minus, kButton4}, + + {kButtonId_Up, kButton11}, + {kButtonId_Down, kButton12}, + {kButtonId_Left, kButton13}, + {kButtonId_Right, kButton14}, + + {kButtonId_StickL, kButton7}, + + {kButtonId_StickL_Up, kAxisYN}, + {kButtonId_StickL_Down, kAxisYP}, + {kButtonId_StickL_Left, kAxisXN}, + {kButtonId_StickL_Right, kAxisXP}, + + {kButtonId_Mic, kButton15}, + }; + } + else if (sdl_controller->get_guid() == SDLController::kRightJoyCon) + { + mapping = + { + {kButtonId_A, kButton0}, + {kButtonId_B, kButton1}, + {kButtonId_X, kButton2}, + {kButtonId_Y, kButton3}, + + {kButtonId_R, kButton10}, + {kButtonId_ZR, kTriggerYP}, + + {kButtonId_Plus, kButton6}, + + {kButtonId_StickR, kButton8}, + + {kButtonId_StickR_Up, kRotationYN}, + {kButtonId_StickR_Down, kRotationYP}, + {kButtonId_StickR_Left, kRotationXN}, + {kButtonId_StickR_Right, kRotationXP}, + }; + } + else if (sdl_controller->get_guid() == SDLController::kSwitchProController) + { + // Switch Pro Controller is similar to default mapping, but with a/b and x/y swapped + mapping = + { + {kButtonId_A, kButton0}, + {kButtonId_B, kButton1}, + {kButtonId_X, kButton2}, + {kButtonId_Y, kButton3}, + + {kButtonId_L, kButton9}, + {kButtonId_R, kButton10}, + {kButtonId_ZL, kTriggerXP}, + {kButtonId_ZR, kTriggerYP}, + + {kButtonId_Plus, kButton6}, + {kButtonId_Minus, kButton4}, + + {kButtonId_Up, kButton11}, + {kButtonId_Down, kButton12}, + {kButtonId_Left, kButton13}, + {kButtonId_Right, kButton14}, + + {kButtonId_StickL, kButton7}, + {kButtonId_StickR, kButton8}, + + {kButtonId_StickL_Up, kAxisYN}, + {kButtonId_StickL_Down, kAxisYP}, + {kButtonId_StickL_Left, kAxisXN}, + {kButtonId_StickL_Right, kAxisXP}, + + {kButtonId_StickR_Up, kRotationYN}, + {kButtonId_StickR_Down, kRotationYP}, + {kButtonId_StickR_Left, kRotationXN}, + {kButtonId_StickR_Right, kRotationXP}, + }; + } + else + { + mapping = + { + {kButtonId_A, kButton1}, + {kButtonId_B, kButton0}, + {kButtonId_X, kButton3}, + {kButtonId_Y, kButton2}, + + {kButtonId_L, kButton9}, + {kButtonId_R, kButton10}, + {kButtonId_ZL, kTriggerXP}, + {kButtonId_ZR, kTriggerYP}, + + {kButtonId_Plus, kButton6}, + {kButtonId_Minus, kButton4}, + + {kButtonId_Up, kButton11}, + {kButtonId_Down, kButton12}, + {kButtonId_Left, kButton13}, + {kButtonId_Right, kButton14}, + + {kButtonId_StickL, kButton7}, + {kButtonId_StickR, kButton8}, + + {kButtonId_StickL_Up, kAxisYN}, + {kButtonId_StickL_Down, kAxisYP}, + {kButtonId_StickL_Left, kAxisXN}, + {kButtonId_StickL_Right, kAxisXP}, + + {kButtonId_StickR_Up, kRotationYN}, + {kButtonId_StickR_Down, kRotationYP}, + {kButtonId_StickR_Left, kRotationXN}, + {kButtonId_StickR_Right, kRotationXP}, + }; + } + break; + } + case InputAPI::XInput: + { + mapping = + { + {kButtonId_A, kButton13}, + {kButtonId_B, kButton12}, + {kButtonId_X, kButton15}, + {kButtonId_Y, kButton14}, + + {kButtonId_L, kButton8}, + {kButtonId_R, kButton9}, + {kButtonId_ZL, kTriggerXP}, + {kButtonId_ZR, kTriggerYP}, + + {kButtonId_Plus, kButton4}, + {kButtonId_Minus, kButton5}, + + {kButtonId_Up, kButton0}, + {kButtonId_Down, kButton1}, + {kButtonId_Left, kButton2}, + {kButtonId_Right, kButton3}, + + {kButtonId_StickL, kButton6}, + {kButtonId_StickR, kButton7}, + + {kButtonId_StickL_Up, kAxisYP}, + {kButtonId_StickL_Down, kAxisYN}, + {kButtonId_StickL_Left, kAxisXN}, + {kButtonId_StickL_Right, kAxisXP}, + + {kButtonId_StickR_Up, kRotationYP}, + {kButtonId_StickR_Down, kRotationYN}, + {kButtonId_StickR_Left, kRotationXN}, + {kButtonId_StickR_Right, kRotationXP}, + }; + + break; + } + } + + bool mapping_updated = false; + std::for_each(mapping.cbegin(), mapping.cend(), [this, &controller, &mapping_updated](const auto& m) + { + if (m_mappings.find(m.first) == m_mappings.cend()) + { + set_mapping(m.first, controller, m.second); + mapping_updated = true; + } + }); + + return mapping_updated; +} diff --git a/src/input/emulated/VPADController.h b/src/input/emulated/VPADController.h new file mode 100644 index 00000000..6aef16ae --- /dev/null +++ b/src/input/emulated/VPADController.h @@ -0,0 +1,104 @@ +#pragma once + +#include "input/emulated/EmulatedController.h" +#include "Cafe/OS/libs/vpad/vpad.h" + + +class VPADController : public EmulatedController +{ +public: + enum ButtonId + { + kButtonId_None, + + kButtonId_A, + kButtonId_B, + kButtonId_X, + kButtonId_Y, + + kButtonId_L, + kButtonId_R, + kButtonId_ZL, + kButtonId_ZR, + + kButtonId_Plus, + kButtonId_Minus, + + kButtonId_Up, + kButtonId_Down, + kButtonId_Left, + kButtonId_Right, + + kButtonId_StickL, + kButtonId_StickR, + + kButtonId_StickL_Up, + kButtonId_StickL_Down, + kButtonId_StickL_Left, + kButtonId_StickL_Right, + + kButtonId_StickR_Up, + kButtonId_StickR_Down, + kButtonId_StickR_Left, + kButtonId_StickR_Right, + + kButtonId_Mic, + kButtonId_Screen, + + kButtonId_Home, + + kButtonId_Max, + }; + + using EmulatedController::EmulatedController; + + Type type() const override { return VPAD; } + + void VPADRead(VPADStatus_t& status, const BtnRepeat& repeat); + + void update() override; + + uint32 get_emulated_button_flag(uint32 id) const override; + + glm::vec2 get_axis() const override; + glm::vec2 get_rotation() const override; + glm::vec2 get_trigger() const override; + + bool is_mic_active() { return m_mic_active; } + bool is_screen_active() { return m_screen_active; } + + static std::string_view get_button_name(ButtonId id); + + void clear_rumble(); + bool push_rumble(uint8* pattern, uint8 length); + + size_t get_highest_mapping_id() const override { return kButtonId_Max; } + bool is_axis_mapping(uint64 mapping) const override { return mapping >= kButtonId_StickL_Up && mapping <= kButtonId_StickR_Right; } + + bool is_start_down() const override { return is_mapping_down(kButtonId_Plus); } + bool is_left_down() const override { return is_mapping_down(kButtonId_Left); } + bool is_right_down() const override { return is_mapping_down(kButtonId_Right); } + bool is_up_down() const override { return is_mapping_down(kButtonId_Up); } + bool is_down_down() const override { return is_mapping_down(kButtonId_Down); } + bool is_a_down() const override { return is_mapping_down(kButtonId_A); } + bool is_b_down() const override { return is_mapping_down(kButtonId_B); } + bool is_home_down() const override { return is_mapping_down(kButtonId_Home); } + + bool set_default_mapping(const std::shared_ptr& controller) override; + +private: + bool m_mic_active = false; + bool m_screen_active = false; + uint32be m_last_holdvalue = 0; + + std::chrono::high_resolution_clock::time_point m_last_hold_change{}, m_last_pulse{}; + + std::mutex m_rumble_mutex; + std::chrono::high_resolution_clock::time_point m_last_rumble_check{}; + std::queue> m_rumble_queue; + uint8 m_parser = 0; + + void update_touch(VPADStatus_t& status); + void update_motion(VPADStatus_t& status); + glm::ivec2 m_last_touch_position{}; +}; diff --git a/src/input/emulated/WPADController.cpp b/src/input/emulated/WPADController.cpp new file mode 100644 index 00000000..819596ab --- /dev/null +++ b/src/input/emulated/WPADController.cpp @@ -0,0 +1,378 @@ +#include "input/emulated/WPADController.h" + +#include "input/emulated/ClassicController.h" +#include "input/emulated/ProController.h" +#include "input/emulated/WiimoteController.h" + +WPADController::WPADController(size_t player_index, WPADDataFormat data_format) + : EmulatedController(player_index), m_data_format(data_format) +{ +} + +WPADDataFormat WPADController::get_default_data_format() const +{ + switch (get_device_type()) + { + case kWAPDevCore: + return kDataFormat_CORE_ACC_DPD; + case kWAPDevFreestyle: + return kDataFormat_FREESTYLE_ACC; + case kWAPDevClassic: + return kDataFormat_CLASSIC; + case kWAPDevMPLS: + return kDataFormat_MPLS; + case kWAPDevMPLSFreeStyle: + return kDataFormat_FREESTYLE_ACC_DPD; + case kWAPDevMPLSClassic: + return kDataFormat_CLASSIC_ACC_DPD; + case kWAPDevURCC: + return kDataFormat_URCC; + default: + return kDataFormat_CORE; + } +} + +uint32 WPADController::get_emulated_button_flag(WPADDataFormat format, uint32 id) const +{ + switch(format) + { + case kDataFormat_CORE: + case kDataFormat_CORE_ACC: + case kDataFormat_CORE_ACC_DPD: + case kDataFormat_CORE_ACC_DPD_FULL: + case kDataFormat_FREESTYLE: + case kDataFormat_FREESTYLE_ACC: + case kDataFormat_FREESTYLE_ACC_DPD: + case kDataFormat_MPLS: + return WiimoteController::s_get_emulated_button_flag(id); + case kDataFormat_CLASSIC: + case kDataFormat_CLASSIC_ACC: + case kDataFormat_CLASSIC_ACC_DPD: + return ClassicController::s_get_emulated_button_flag(id); + + case kDataFormat_TRAIN: break; + case kDataFormat_GUITAR: break; + case kDataFormat_BALANCE_CHECKER: break; + case kDataFormat_DRUM: break; + + case kDataFormat_TAIKO: break; + case kDataFormat_URCC: + return ProController::s_get_emulated_button_flag(id); + + } + + return 0; +} + +void WPADController::WPADRead(WPADStatus_t* status) +{ + controllers_update_states(); + uint32 button = 0; + for (uint32 i = 1; i < get_highest_mapping_id(); ++i) + { + if (is_mapping_down(i)) + { + const uint32 value = get_emulated_button_flag(m_data_format, i); + button |= value; + } + } + + m_homebutton_down |= is_home_down(); + + // todo fill position api from wiimote + + switch (m_data_format) + { + case kDataFormat_CORE: + case kDataFormat_CORE_ACC: + case kDataFormat_CORE_ACC_DPD: + case kDataFormat_CORE_ACC_DPD_FULL: + { + memset(status, 0x00, sizeof(*status)); + status->button = button; + break; + } + + case kDataFormat_FREESTYLE: + case kDataFormat_FREESTYLE_ACC: + case kDataFormat_FREESTYLE_ACC_DPD: + { + WPADFSStatus_t* ex_status = (WPADFSStatus_t*)status; + memset(ex_status, 0x00, sizeof(*ex_status)); + ex_status->button = button; + + auto axis = get_axis(); + axis *= 127.0f; + ex_status->fsStickX = (sint8)axis.x; + ex_status->fsStickY = (sint8)axis.y; + break; + } + + case kDataFormat_CLASSIC: + case kDataFormat_CLASSIC_ACC: + case kDataFormat_CLASSIC_ACC_DPD: + case kDataFormat_GUITAR: + case kDataFormat_DRUM: + case kDataFormat_TAIKO: + { + WPADCLStatus_t* ex_status = (WPADCLStatus_t*)status; + memset(ex_status, 0x00, sizeof(*ex_status)); + ex_status->clButton = button; + + auto axis = get_axis(); + axis *= 2048.0f; + ex_status->clLStickX = (uint16)axis.x; + ex_status->clLStickY = (uint16)axis.y; + + auto rotation = get_rotation(); + rotation *= 2048.0f; + ex_status->clRStickX = (uint16)rotation.x; + ex_status->clRStickY = (uint16)rotation.y; + break; + } + case kDataFormat_TRAIN: + { + WPADTRStatus_t* ex_status = (WPADTRStatus_t*)status; + // TODO + break; + } + case kDataFormat_BALANCE_CHECKER: + { + WPADBLStatus_t* ex_status = (WPADBLStatus_t*)status; + // TODO + break; + } + case kDataFormat_MPLS: + { + WPADMPStatus_t* ex_status = (WPADMPStatus_t*)status; + ex_status->stat = 1; // attached + // TODO + break; + } + case kDataFormat_URCC: + { + WPADUCStatus_t* ex_status = (WPADUCStatus_t*)status; + memset(ex_status, 0x00, sizeof(*ex_status)); + ex_status->ucButton = button; + + ex_status->cable = TRUE; + ex_status->charge = TRUE; + + auto axis = get_axis(); + axis *= 2048.0f; + ex_status->ucLStickX = (uint16)axis.x; + ex_status->ucLStickY = (uint16)axis.y; + + auto rotation = get_rotation(); + rotation *= 2048.0f; + ex_status->ucRStickX = (uint16)rotation.x; + ex_status->ucRStickY = (uint16)rotation.y; + + break; + } + default: + cemu_assert(false); + } + + status->dev = get_device_type(); + status->err = WPAD_ERR_NONE; +} + +void WPADController::KPADRead(KPADStatus_t& status, const BtnRepeat& repeat) +{ + uint32be* hold, *release, *trigger; + switch (type()) + { + case Pro: + hold = &status.ex_status.uc.hold; + release = &status.ex_status.uc.release; + trigger = &status.ex_status.uc.trig; + break; + case Classic: + hold = &status.ex_status.cl.hold; + release = &status.ex_status.cl.release; + trigger = &status.ex_status.cl.trig; + break; + default: + hold = &status.hold; + release = &status.release; + trigger = &status.trig; + } + + controllers_update_states(); + for (uint32 i = 1; i < get_highest_mapping_id(); ++i) + { + if (is_mapping_down(i)) + { + const uint32 value = get_emulated_button_flag(m_data_format, i); + *hold |= value; + } + } + + m_homebutton_down |= is_home_down(); + + // button repeat + const auto now = std::chrono::steady_clock::now(); + if (*hold != m_last_holdvalue) + { + m_last_hold_change = m_last_pulse = now; + } + + if (repeat.pulse > 0) + { + if (m_last_hold_change + std::chrono::milliseconds(repeat.delay) >= now) + { + if ((m_last_pulse + std::chrono::milliseconds(repeat.pulse)) < now) + { + m_last_pulse = now; + *hold |= kWPADButtonRepeat; + } + } + } + + // axis + const auto axis = get_axis(); + const auto rotation = get_rotation(); + + *release = m_last_holdvalue & ~*hold; + //status.release = m_last_holdvalue & ~*hold; + *trigger = ~m_last_holdvalue & *hold; + //status.trig = ~m_last_holdvalue & *hold; + m_last_holdvalue = *hold; + + if (is_mpls_attached()) + { + status.mpls.dir.X.x = 1; + status.mpls.dir.X.y = 0; + status.mpls.dir.X.z = 0; + + status.mpls.dir.Y.x = 0; + status.mpls.dir.Y.y = 1; + status.mpls.dir.Y.z = 0; + + status.mpls.dir.Z.x = 0; + status.mpls.dir.Z.y = 0; + status.mpls.dir.Z.z = 1; + } + + if (has_motion()) + { + auto motion_sample = get_motion_data(); + + glm::vec3 acc; + motion_sample.getAccelerometer(&acc[0]); + status.acc.x = acc.x; + status.acc.y = acc.y; + status.acc.z = acc.z; + + status.acc_value = motion_sample.getVPADAccMagnitude(); + status.acc_speed = motion_sample.getVPADAccAcceleration(); + + //glm::vec2 acc_vert; + //motion_sample.getVPADAccXY(&acc_vert[0]); + //status.acc_vertical.x = acc_vert.x; + //status.acc_vertical.y = acc_vert.y; + + status.accVertical.x = std::min(1.0f, std::abs(acc.x + acc.y)); + status.accVertical.y = std::min(std::max(-1.0f, -acc.z), 1.0f); + + if (is_mpls_attached()) + { + // todo + glm::vec3 gyroChange; + motion_sample.getVPADGyroChange(&gyroChange[0]); + //const auto& gyroChange = motionSample.getVPADGyroChange(); + status.mpls.mpls.x = gyroChange.x; + status.mpls.mpls.y = gyroChange.y; + status.mpls.mpls.z = gyroChange.z; + + //debug_printf("GyroChange %7.2lf %7.2lf %7.2lf\n", (float)status.gyroChange.x, (float)status.gyroChange.y, (float)status.gyroChange.z); + + glm::vec3 gyroOrientation; + motion_sample.getVPADOrientation(&gyroOrientation[0]); + //const auto& gyroOrientation = motionSample.getVPADOrientation(); + status.mpls.angle.x = gyroOrientation.x; + status.mpls.angle.y = gyroOrientation.y; + status.mpls.angle.z = gyroOrientation.z; + + float attitude[9]; + motion_sample.getVPADAttitudeMatrix(attitude); + status.mpls.dir.X.x = attitude[0]; + status.mpls.dir.X.y = attitude[1]; + status.mpls.dir.X.z = attitude[2]; + status.mpls.dir.Y.x = attitude[3]; + status.mpls.dir.Y.y = attitude[4]; + status.mpls.dir.Y.z = attitude[5]; + status.mpls.dir.Z.x = attitude[6]; + status.mpls.dir.Z.y = attitude[7]; + status.mpls.dir.Z.z = attitude[8]; + } + } + + if (has_position()) + { + status.dpd_valid_fg = 1; + + const auto position = get_position(); + + const auto pos = (position * 2.0f) - 1.0f; + status.pos.x = pos.x; + status.pos.y = pos.y; + + const auto delta = position - get_prev_position(); + status.vec.x = delta.x; + status.vec.y = delta.y; + status.speed = glm::length(delta); + } + + switch (type()) + { + case Wiimote: + status.ex_status.fs.stick.x = axis.x; + status.ex_status.fs.stick.y = axis.y; + + if(has_second_motion()) + { + auto motion_sample = get_second_motion_data(); + + glm::vec3 acc; + motion_sample.getAccelerometer(&acc[0]); + status.ex_status.fs.acc.x = acc.x; + status.ex_status.fs.acc.y = acc.y; + status.ex_status.fs.acc.z = acc.z; + + status.ex_status.fs.accValue = motion_sample.getVPADAccMagnitude(); + status.ex_status.fs.accSpeed = motion_sample.getVPADAccAcceleration(); + } + + break; + case Pro: + status.ex_status.uc.lstick.x = axis.x; + status.ex_status.uc.lstick.y = axis.y; + + status.ex_status.uc.rstick.x = rotation.x; + status.ex_status.uc.rstick.y = rotation.y; + + status.ex_status.uc.charge = FALSE; + status.ex_status.uc.cable = TRUE; + + break; + case Classic: + status.ex_status.cl.lstick.x = axis.x; + status.ex_status.cl.lstick.y = axis.y; + + status.ex_status.cl.rstick.x = rotation.x; + status.ex_status.cl.rstick.y = rotation.y; + + if (HAS_FLAG((uint32)status.ex_status.cl.hold, kCLButton_ZL)) + status.ex_status.cl.ltrigger = 1.0f; + + if (HAS_FLAG((uint32)status.ex_status.cl.hold, kCLButton_ZR)) + status.ex_status.cl.rtrigger = 1.0f; + break; + default: + cemu_assert(false); + } + + +} diff --git a/src/input/emulated/WPADController.h b/src/input/emulated/WPADController.h new file mode 100644 index 00000000..2636b74a --- /dev/null +++ b/src/input/emulated/WPADController.h @@ -0,0 +1,147 @@ +#pragma once + +#include + +#include "input/emulated/EmulatedController.h" +#include "Cafe/OS/libs/padscore/padscore.h" +#include "Cafe/OS/libs/vpad/vpad.h" + +constexpr uint32 kWPADButtonRepeat = 0x80000000; + +enum WPADDeviceType +{ + kWAPDevCore = 0, + kWAPDevFreestyle = 1, + kWAPDevClassic = 2, + kWAPDevMPLS = 5, + kWAPDevMPLSFreeStyle = 6, + kWAPDevMPLSClassic = 7, + kWAPDevURCC = 31, + kWAPDevNotFound = 253, + kWAPDevUnknown = 255, +}; + +// core, balanceboard +enum WPADCoreButtons +{ + kWPADButton_Left = 0x1, + kWPADButton_Right = 0x2, + kWPADButton_Down = 0x4, + kWPADButton_Up = 0x8, + kWPADButton_Plus = 0x10, + kWPADButton_2 = 0x100, + kWPADButton_1 = 0x200, + kWPADButton_B = 0x400, + kWPADButton_A = 0x800, + kWPADButton_Minus = 0x1000, + kWPADButton_Home = 0x8000, +}; + +// Nunchuck aka Freestyle +enum WPADNunchuckButtons +{ + kWPADButton_Z = 0x2000, + kWPADButton_C = 0x4000, +}; + +// Classic Controller +enum WPADClassicButtons +{ + kCLButton_Up = 0x1, + kCLButton_Left = 0x2, + kCLButton_ZR = 0x4, + kCLButton_X = 0x8, + kCLButton_A = 0x10, + kCLButton_Y = 0x20, + kCLButton_B = 0x40, + kCLButton_ZL = 0x80, + kCLButton_R = 0x200, + kCLButton_Plus = 0x400, + kCLButton_Home = 0x800, + kCLButton_Minus = 0x1000, + kCLButton_L = 0x2000, + kCLButton_Down = 0x4000, + kCLButton_Right = 0x8000 +}; + +// Pro Controller aka URCC +enum WPADProButtons +{ + kProButton_Up = 0x1, + kProButton_Left = 0x2, + kProButton_ZR = 0x4, + kProButton_X = 0x8, + kProButton_A = 0x10, + kProButton_Y = 0x20, + kProButton_B = 0x40, + kProButton_ZL = 0x80, + kProButton_R = 0x200, + kProButton_Plus = 0x400, + kProButton_Home = 0x800, + kProButton_Minus = 0x1000, + kProButton_L = 0x2000, + kProButton_Down = 0x4000, + kProButton_Right = 0x8000, + kProButton_StickR = 0x10000, + kProButton_StickL = 0x20000 +}; + +enum WPADDataFormat { + kDataFormat_CORE = 0, + kDataFormat_CORE_ACC = 1, + kDataFormat_CORE_ACC_DPD = 2, + kDataFormat_FREESTYLE = 3, + kDataFormat_FREESTYLE_ACC = 4, + kDataFormat_FREESTYLE_ACC_DPD = 5, + kDataFormat_CLASSIC = 6, + kDataFormat_CLASSIC_ACC = 7, + kDataFormat_CLASSIC_ACC_DPD = 8, + kDataFormat_CORE_ACC_DPD_FULL = 9, // buttons, motion, pointing + kDataFormat_TRAIN = 10, + kDataFormat_GUITAR = 11, + kDataFormat_BALANCE_CHECKER = 12, + kDataFormat_DRUM = 15, + kDataFormat_MPLS = 16, // buttons, motion, pointing, motion plus + kDataFormat_TAIKO = 17, + kDataFormat_URCC = 22, // buttons, URCC aka pro +}; + +class WPADController : public EmulatedController +{ + using base_type = EmulatedController; +public: + WPADController(size_t player_index, WPADDataFormat data_format); + + uint32 get_emulated_button_flag(WPADDataFormat format, uint32 id) const; + + virtual WPADDeviceType get_device_type() const = 0; + + WPADDataFormat get_data_format() const { return m_data_format; } + void set_data_format(WPADDataFormat data_format) { m_data_format = data_format; } + + void WPADRead(WPADStatus_t* status); + + void KPADRead(KPADStatus_t& status, const BtnRepeat& repeat); + virtual bool is_mpls_attached() { return false; } + + enum class ConnectCallbackStatus + { + None, // do nothing + ReportDisconnect, // call disconnect + ReportConnect, // call connect + }; + ConnectCallbackStatus m_status = ConnectCallbackStatus::ReportConnect; + ConnectCallbackStatus m_extension_status = ConnectCallbackStatus::ReportConnect; + + WPADDataFormat get_default_data_format() const; + +protected: + WPADDataFormat m_data_format; + +private: + uint32be m_last_holdvalue = 0; + + std::chrono::steady_clock::time_point m_last_hold_change{}, m_last_pulse{}; + + +}; diff --git a/src/input/emulated/WiimoteController.cpp b/src/input/emulated/WiimoteController.cpp new file mode 100644 index 00000000..f30f7c48 --- /dev/null +++ b/src/input/emulated/WiimoteController.cpp @@ -0,0 +1,184 @@ +#include "input/emulated/WiimoteController.h" + +#include "input/api/Controller.h" +#include "input/api/Wiimote/NativeWiimoteController.h" + + +WiimoteController::WiimoteController(size_t player_index) + : WPADController(player_index, kDataFormat_CORE_ACC_DPD) +{ +} + +void WiimoteController::set_device_type(WPADDeviceType device_type) +{ + m_device_type = device_type; + m_data_format = get_default_data_format(); +} + +bool WiimoteController::is_mpls_attached() +{ + return m_device_type == kWAPDevMPLS || m_device_type == kWAPDevMPLSClassic || m_device_type == kWAPDevMPLSFreeStyle; +} + +uint32 WiimoteController::get_emulated_button_flag(uint32 id) const +{ + return s_get_emulated_button_flag(id); +} + +bool WiimoteController::set_default_mapping(const std::shared_ptr& controller) +{ + std::vector> mapping; + switch (controller->api()) + { + case InputAPI::Wiimote: { + const auto sdl_controller = std::static_pointer_cast(controller); + mapping = + { + {kButtonId_A, kWiimoteButton_A}, + {kButtonId_B, kWiimoteButton_B}, + {kButtonId_1, kWiimoteButton_One}, + {kButtonId_2, kWiimoteButton_Two}, + + {kButtonId_Home, kWiimoteButton_Home}, + + {kButtonId_Plus, kWiimoteButton_Plus}, + {kButtonId_Minus, kWiimoteButton_Minus}, + + {kButtonId_Up, kWiimoteButton_Up}, + {kButtonId_Down, kWiimoteButton_Down}, + {kButtonId_Left, kWiimoteButton_Left}, + {kButtonId_Right, kWiimoteButton_Right}, + + {kButtonId_Nunchuck_Z, kWiimoteButton_Z}, + {kButtonId_Nunchuck_C, kWiimoteButton_C}, + + {kButtonId_Nunchuck_Up, kAxisYP}, + {kButtonId_Nunchuck_Down, kAxisYN}, + {kButtonId_Nunchuck_Left, kAxisXN}, + {kButtonId_Nunchuck_Right, kAxisXP}, + }; + } + } + + bool mapping_updated = false; + std::for_each(mapping.cbegin(), mapping.cend(), [this, &controller, &mapping_updated](const auto& m) + { + if (m_mappings.find(m.first) == m_mappings.cend()) + { + set_mapping(m.first, controller, m.second); + mapping_updated = true; + } + }); + + return mapping_updated; +} + +glm::vec2 WiimoteController::get_axis() const +{ + const auto left = get_axis_value(kButtonId_Nunchuck_Left); + const auto right = get_axis_value(kButtonId_Nunchuck_Right); + + const auto up = get_axis_value(kButtonId_Nunchuck_Up); + const auto down = get_axis_value(kButtonId_Nunchuck_Down); + + glm::vec2 result; + result.x = (left > right) ? -left : right; + result.y = (up > down) ? up : -down; + return result; +} + +glm::vec2 WiimoteController::get_rotation() const +{ + return {}; +} + +glm::vec2 WiimoteController::get_trigger() const +{ + return {}; +} + +void WiimoteController::load(const pugi::xml_node& node) +{ + base_type::load(node); + + if (const auto value = node.child("device_type")) + m_device_type = ConvertString(value.child_value()); +} + +void WiimoteController::save(pugi::xml_node& node) +{ + base_type::save(node); + + node.append_child("device_type").append_child(pugi::node_pcdata).set_value(fmt::format("{}", (int)m_device_type).c_str()); +} + +uint32 WiimoteController::s_get_emulated_button_flag(uint32 id) +{ + switch (id) + { + case kButtonId_A: + return kWPADButton_A; + case kButtonId_B: + return kWPADButton_B; + case kButtonId_1: + return kWPADButton_1; + case kButtonId_2: + return kWPADButton_2; + + case kButtonId_Plus: + return kWPADButton_Plus; + case kButtonId_Minus: + return kWPADButton_Minus; + case kButtonId_Home: + return kWPADButton_Home; + + case kButtonId_Up: + return kWPADButton_Up; + case kButtonId_Down: + return kWPADButton_Down; + case kButtonId_Left: + return kWPADButton_Left; + case kButtonId_Right: + return kWPADButton_Right; + + case kButtonId_Nunchuck_Z: + return kWPADButton_Z; + case kButtonId_Nunchuck_C: + return kWPADButton_C; + } + + return 0; +} + +std::string_view WiimoteController::get_button_name(ButtonId id) +{ + switch (id) + { + case kButtonId_A: return "A"; + case kButtonId_B: return "B"; + case kButtonId_1: return "1"; + case kButtonId_2: return "2"; + + case kButtonId_Home: return "home"; + case kButtonId_Plus: return "+"; + case kButtonId_Minus: return "-"; + + case kButtonId_Up: return "up"; + case kButtonId_Down: return "down"; + case kButtonId_Left: return "left"; + case kButtonId_Right: return "right"; + + case kButtonId_Nunchuck_Z: return "Z"; + case kButtonId_Nunchuck_C: return "C"; + + case kButtonId_Nunchuck_Up: return "up"; + case kButtonId_Nunchuck_Down: return "down"; + case kButtonId_Nunchuck_Left: return "left"; + case kButtonId_Nunchuck_Right: return "right"; + + default: + return ""; + } +} + + diff --git a/src/input/emulated/WiimoteController.h b/src/input/emulated/WiimoteController.h new file mode 100644 index 00000000..5cfd2838 --- /dev/null +++ b/src/input/emulated/WiimoteController.h @@ -0,0 +1,78 @@ +#pragma once + +#include "input/emulated/WPADController.h" +#include "gui/input/panels/WiimoteInputPanel.h" + +class WiimoteController : public WPADController +{ + using base_type = WPADController; +public: + enum ButtonId + { + kButtonId_None, + + kButtonId_A, + kButtonId_B, + kButtonId_1, + kButtonId_2, + + kButtonId_Nunchuck_Z, + kButtonId_Nunchuck_C, + + kButtonId_Plus, + kButtonId_Minus, + + + kButtonId_Up, + kButtonId_Down, + kButtonId_Left, + kButtonId_Right, + + + kButtonId_Nunchuck_Up, + kButtonId_Nunchuck_Down, + kButtonId_Nunchuck_Left, + kButtonId_Nunchuck_Right, + + kButtonId_Home, + + kButtonId_Max, + }; + + WiimoteController(size_t player_index); + + Type type() const override { return Type::Wiimote; } + WPADDeviceType get_device_type() const override { return m_device_type; } + void set_device_type(WPADDeviceType device_type); + + bool is_mpls_attached() override; + + uint32 get_emulated_button_flag(uint32 id) const override; + size_t get_highest_mapping_id() const override { return kButtonId_Max; } + bool is_axis_mapping(uint64 mapping) const override { return mapping >= kButtonId_Nunchuck_Up && mapping <= kButtonId_Nunchuck_Right; } + + bool set_default_mapping(const std::shared_ptr& controller) override; + + glm::vec2 get_axis() const override; + glm::vec2 get_rotation() const override; + glm::vec2 get_trigger() const override; + + void load(const pugi::xml_node& node) override; + void save(pugi::xml_node& node) override; + + static uint32 s_get_emulated_button_flag(uint32 id); + + static std::string_view get_button_name(ButtonId id); + + bool is_start_down() const override { return is_mapping_down(kButtonId_Plus); } + bool is_left_down() const override { return is_mapping_down(kButtonId_Left); } + bool is_right_down() const override { return is_mapping_down(kButtonId_Right); } + bool is_up_down() const override { return is_mapping_down(kButtonId_Up); } + bool is_down_down() const override { return is_mapping_down(kButtonId_Down); } + bool is_a_down() const override { return is_mapping_down(kButtonId_A); } + bool is_b_down() const override { return is_mapping_down(kButtonId_B); } + bool is_home_down() const override { return is_mapping_down(kButtonId_Home); } + +private: + WPADDeviceType m_device_type = kWAPDevCore; +}; diff --git a/src/input/motion/Mahony.h b/src/input/motion/Mahony.h new file mode 100644 index 00000000..b34bef9d --- /dev/null +++ b/src/input/motion/Mahony.h @@ -0,0 +1,165 @@ +#pragma once + +#include +#include +#include "util/math/quaternion.h" + +class MahonySensorFusion +{ +public: + MahonySensorFusion() + { + // assume default forward pose (holding controller in hand, tilted forward so the sticks/buttons face upward) + m_imuQ.Assign(sqrtf(0.5), sqrtf(0.5), 0.0f, 0.0f); + } + + // gx, gy, gz are in radians/sec + void updateIMU(float deltaTime, float gx, float gy, float gz, float ax, float ay, float az) + { + Vector3f av(ax, ay, az); + Vector3f gv(gx, gy, gz); + if (deltaTime > 0.2f) + deltaTime = 0.2f; // dont let stutter mess up the internal state + updateGyroBias(gx, gy, gz); + gv.x -= m_gyroBias[0]; + gv.y -= m_gyroBias[1]; + gv.z -= m_gyroBias[2]; + + // ignore small angles to avoid drift due to bias (especially on yaw) + if (fabs(gv.x) < 0.015f) + gv.x = 0.0f; + if (fabs(gv.y) < 0.015f) + gv.y = 0.0f; + if (fabs(gv.z) < 0.015f) + gv.z = 0.0f; + + // forceLogDebug_printf("[IMU Quat] time %7.4lf | %7.2lf %7.2lf %7.2lf %7.2lf | gyro( - bias) %7.4lf %7.4lf %7.4lf | acc %7.2lf %7.2lf %7.2lf | GyroBias %7.4lf %7.4lf %7.4lf", deltaTime, m_imuQ.x, m_imuQ.y, m_imuQ.z, m_imuQ.w, gv.x, gv.y, gv.z, ax, ay, az, m_gyroBias[0], m_gyroBias[1], m_gyroBias[2]); + + if (fabs(av.x) > 0.000001f || fabs(av.y) > 0.000001f || fabs(av.z) > 0.000001f) + { + av.Normalize(); + Vector3f grav = m_imuQ.GetVectorZ(); + grav.Scale(0.5f); + Vector3f errorFeedback = grav.Cross(av); + // apply scaled feedback + gv -= errorFeedback; + } + gv.Scale(0.5f * deltaTime); + m_imuQ += (m_imuQ * Quaternionf(0.0f, gv.x, gv.y, gv.z)); + m_imuQ.NormalizeXYZW(); + updateOrientationAngles(); + } + + float getRollRadians() + { + return m_roll + (float)m_rollWinding * 2.0f * 3.14159265f; + } + + float getPitchRadians() + { + return m_pitch + (float)m_pitchWinding * 2.0f * 3.14159265f; + } + + float getYawRadians() + { + return m_yaw + (float)m_yawWinding * 2.0f * 3.14159265f; + } + + void getQuaternion(float q[4]) const + { + q[0] = m_imuQ.w; + q[1] = m_imuQ.x; + q[2] = m_imuQ.y; + q[3] = m_imuQ.z; + } + + void getGyroBias(float gBias[3]) const + { + gBias[0] = m_gyroBias[0]; + gBias[1] = m_gyroBias[1]; + gBias[2] = m_gyroBias[2]; + } + +private: + + // calculate roll, yaw and pitch in radians. (-0.5 to 0.5) + void calcOrientation() + { + float sinr_cosp = 2.0f * (m_imuQ.z * m_imuQ.w + m_imuQ.x * m_imuQ.y); + float cosr_cosp = 1.0f - 2.0f * (m_imuQ.w * m_imuQ.w + m_imuQ.x * m_imuQ.x); + m_roll = std::atan2(sinr_cosp, cosr_cosp); + + // pitch (y-axis rotation) + float sinp = 2.0f * (m_imuQ.z * m_imuQ.x - m_imuQ.y * m_imuQ.w); + if (std::abs(sinp) >= 1.0) + m_pitch = std::copysign(3.14159265359f / 2.0f, sinp); + else + m_pitch = std::asin(sinp); + + // yaw (z-axis rotation) + float siny_cosp = 2.0f * (m_imuQ.z * m_imuQ.y + m_imuQ.w * m_imuQ.x); + float cosy_cosp = 1.0f - 2.0f * (m_imuQ.x * m_imuQ.x + m_imuQ.y * m_imuQ.y); + m_yaw = std::atan2(siny_cosp, cosy_cosp); + } + + void updateOrientationAngles() + { + auto calcWindingCountChange = [](float prevAngle, float newAngle) -> int + { + if (newAngle > prevAngle) + { + float angleDif = newAngle - prevAngle; + if (angleDif > 3.14159265f) + return -1; + } + else if (newAngle < prevAngle) + { + float angleDif = prevAngle - newAngle; + if (angleDif > 3.14159265f) + return 1; + } + return 0; + }; + float prevRoll = m_roll; + float prevPitch = m_pitch; + float prevYaw = m_yaw; + calcOrientation(); + // calculate roll, yaw and pitch including winding count to match what VPAD API returns + m_rollWinding += calcWindingCountChange(prevRoll, m_roll); + m_pitchWinding += calcWindingCountChange(prevPitch, m_pitch); + m_yawWinding += calcWindingCountChange(prevYaw, m_yaw); + } + + void updateGyroBias(float gx, float gy, float gz) + { + // dont let actual movement influence the bias + // but be careful about setting this too low, there are controllers out there with really bad bias (my Switch Pro had -0.0933 0.0619 0.0179 in resting state) + if (fabs(gx) >= 0.35f || fabs(gy) >= 0.35f || fabs(gz) >= 0.35f) + return; + + m_gyroTotalSum[0] += gx; + m_gyroTotalSum[1] += gy; + m_gyroTotalSum[2] += gz; + m_gyroTotalSampleCount++; + if (m_gyroTotalSampleCount >= 200) + { + m_gyroBias[0] = (float)(m_gyroTotalSum[0] / (double)m_gyroTotalSampleCount); + m_gyroBias[1] = (float)(m_gyroTotalSum[1] / (double)m_gyroTotalSampleCount); + m_gyroBias[2] = (float)(m_gyroTotalSum[2] / (double)m_gyroTotalSampleCount); + } + } + + private: + Quaternionf m_imuQ; // current orientation + // angle data + float m_roll{}; + float m_pitch{}; + float m_yaw{}; + int m_rollWinding{}; + int m_pitchWinding{}; + int m_yawWinding{}; + // gyro bias + float m_gyroBias[3]{}; + double m_gyroTotalSum[3]{}; + uint64 m_gyroTotalSampleCount{}; +}; \ No newline at end of file diff --git a/src/input/motion/MotionHandler.h b/src/input/motion/MotionHandler.h new file mode 100644 index 00000000..b7cf4eec --- /dev/null +++ b/src/input/motion/MotionHandler.h @@ -0,0 +1,60 @@ +#pragma once +#include "Mahony.h" +#include "MotionSample.h" + +// utility class to translate external motion input (DSU, SDL GamePad sensors) into the values expected by VPAD API (and maybe others in the future) +class WiiUMotionHandler +{ +public: + // gyro is in radians/sec + void processMotionSample( + float deltaTime, + float gx, float gy, float gz, + float accx, float accy, float accz) + { + m_gyro[0] = gx; + m_gyro[1] = gy; + m_gyro[2] = gz; + m_prevAcc[0] = m_acc[0]; + m_prevAcc[1] = m_acc[1]; + m_prevAcc[2] = m_acc[2]; + m_acc[0] = accx; + m_acc[1] = accy; + m_acc[2] = accz; + // integrate acc and gyro samples into IMU + m_imu.updateIMU(deltaTime, gx, gy, gz, accx, accy, accz); + + // get orientation from IMU + m_orientation[0] = _radToOrientation(-m_imu.getYawRadians()) - 0.50f; + m_orientation[1] = _radToOrientation(-m_imu.getPitchRadians()) - 0.50f; + m_orientation[2] = _radToOrientation(m_imu.getRollRadians()); + } + + MotionSample getMotionSample() + { + float q[4]; + m_imu.getQuaternion(q); + float gBias[3]; + m_imu.getGyroBias(gBias); + float gyroDebiased[3]; + gyroDebiased[0] = m_gyro[0] - gBias[0]; + gyroDebiased[1] = m_gyro[1] - gBias[1]; + gyroDebiased[2] = m_gyro[2] - gBias[2]; + return MotionSample(m_acc, MotionSample::calculateAccAcceleration(m_prevAcc, m_acc), gyroDebiased, m_orientation, q); + } +private: + + // VPAD orientation unit is 1.0 = one revolution around the axis + float _radToOrientation(float rad) + { + return rad / (2.0f * 3.14159265f); + } + + MahonySensorFusion m_imu; + // state + float m_gyro[3]{}; + float m_acc[3]{}; + float m_prevAcc[3]{}; + // calculated values + float m_orientation[3]{}; +}; \ No newline at end of file diff --git a/src/input/motion/MotionSample.h b/src/input/motion/MotionSample.h new file mode 100644 index 00000000..f4015afb --- /dev/null +++ b/src/input/motion/MotionSample.h @@ -0,0 +1,319 @@ +#pragma once +#include "util/math/vector3.h" +#include "util/math/quaternion.h" + +struct Quat +{ + float w; + float x; + float y; + float z; + + Quat() + { + w = 1.0f; + x = 0.0f; + y = 0.0f; + z = 0.0f; + } + + Quat(float inW, float inX, float inY, float inZ) + { + w = inW; + x = inX; + y = inY; + z = inZ; + } + + static Quat AngleAxis(float inAngle, float inX, float inY, float inZ) + { + Quat result = Quat(cosf(inAngle * 0.5f), inX, inY, inZ); + result.Normalize(); + return result; + } + + void Set(float inW, float inX, float inY, float inZ) + { + w = inW; + x = inX; + y = inY; + z = inZ; + } + + Quat& operator*=(const Quat& rhs) + { + Set(w * rhs.w - x * rhs.x - y * rhs.y - z * rhs.z, + w * rhs.x + x * rhs.w + y * rhs.z - z * rhs.y, + w * rhs.y - x * rhs.z + y * rhs.w + z * rhs.x, + w * rhs.z + x * rhs.y - y * rhs.x + z * rhs.w); + return *this; + } + + friend Quat operator*(Quat lhs, const Quat& rhs) + { + lhs *= rhs; + return lhs; + } + + void Normalize() + { + //printf("Normalizing: %.4f, %.4f, %.4f, %.4f\n", w, x, y, z); + const float length = sqrtf(x * x + y * y + z * z); + float targetLength = 1.0f - w * w; + if (targetLength <= 0.0f || length <= 0.0f) + { + Set(1.0f, 0.0f, 0.0f, 0.0f); + return; + } + targetLength = sqrtf(targetLength); + const float fixFactor = targetLength / length; + + x *= fixFactor; + y *= fixFactor; + z *= fixFactor; + + //printf("Normalized: %.4f, %.4f, %.4f, %.4f\n", w, x, y, z); + return; + } + + Quat Normalized() const + { + Quat result = *this; + result.Normalize(); + return result; + } + + void Conjugate() + { + x = -x; + y = -y; + z = -z; + return; + } + + Quat Conjugated() const + { + Quat result = *this; + result.Conjugate(); + return result; + } +}; + +// helper class to store unified motion data +// supports retrieving values in their API-specific (VPAD, KPAD etc.) format +class MotionSample +{ +public: + MotionSample() + { + } + + MotionSample(float acc[3], float accAcceleration, float gyro[3], float orientation[3], + float quaternion[4] + ) + { + m_acc[0] = acc[0]; + m_acc[1] = acc[1]; + m_acc[2] = acc[2]; + m_accAcceleration = accAcceleration; + m_gyro[0] = gyro[0]; + m_gyro[1] = gyro[1]; + m_gyro[2] = gyro[2]; + m_orientation[0] = orientation[0]; + m_orientation[1] = orientation[1]; + m_orientation[2] = orientation[2]; + m_q[0] = quaternion[0]; + m_q[1] = quaternion[1]; + m_q[2] = quaternion[2]; + m_q[3] = quaternion[3]; + m_accMagnitude = sqrtf(m_acc[0] * m_acc[0] + m_acc[1] * m_acc[1] + m_acc[2] * m_acc[2]); + } + + void getVPADOrientation(float orientation[3]) + { + orientation[0] = m_orientation[0]; + orientation[1] = m_orientation[1]; + orientation[2] = m_orientation[2]; + } + + void getVPADGyroChange(float gyro[3]) + { + // filter noise + if (fabs(gyro[0]) < 0.012f) + gyro[0] = 0.0f; + if (fabs(gyro[1]) < 0.012f) + gyro[1] = 0.0f; + if (fabs(gyro[2]) < 0.012f) + gyro[2] = 0.0f; + // convert + gyro[0] = _radToOrientation(-m_gyro[0]); + gyro[1] = _radToOrientation(-m_gyro[1]); + gyro[2] = _radToOrientation(m_gyro[2]); + } + + void getVPADAccelerometer(float acc[3]) + { + acc[0] = -m_acc[0]; + acc[1] = -m_acc[1]; + acc[2] = m_acc[2]; + } + + float getVPADAccMagnitude() + { + return m_accMagnitude; + } + + float getVPADAccAcceleration() // Possibly not entirely correct. Our results are smaller than VPAD API ones + { + return m_accAcceleration; + } + + void getVPADAccXY(float accXY[2]) + { + float invMag = 1.0f / m_accMagnitude; + float normAcc[3]; + normAcc[0] = m_acc[0] * invMag; + normAcc[1] = m_acc[1] * invMag; + normAcc[2] = m_acc[2] * invMag; + accXY[0] = sqrtf(normAcc[0] * normAcc[0] + normAcc[1] * normAcc[1]); + accXY[1] = -sin(getAtanPitch(-normAcc[2], normAcc[0], -normAcc[1])); + } + + void getXVector(float vOut[3], Quaternionf& q) + { + float X = q.x; + float Y = q.y; + float Z = q.z; + float W = q.w; + float xy = X * Y; + float xz = X * Z; + float yy = Y * Y; + float yw = Y * W; + float zz = Z * Z; + float zw = Z * W; + vOut[0] = 1.0f - 2.0f * (yy + zz); // x.x + vOut[2] = 2.0f * (xy + zw); // x.y + vOut[1] = 2.0f * (xz - yw); // x.z + } + + void getVPADAttitudeMatrix(float mtx[9]) + { + // VPADs attitude matrix has mixed axis handedness, the most sane way to replicate it is by generating Y and Z by rotating the X vector + Quaternionf qImu(m_q[0], m_q[1], m_q[2], m_q[3]); + Quaternionf qY = qImu * Quaternionf::FromAngleAxis(1.5708f * 1.0f, 0.0f, 0.0f, 1.0f); + Quaternionf qZ = qImu * Quaternionf::FromAngleAxis(1.5708f * 1.0f, 0.0f, 1.0f, 0.0f); + getXVector(mtx + 0, qImu); + getXVector(mtx + 3, qY); + getXVector(mtx + 6, qZ); + } + + static float calculateAccAcceleration(float prevAcc[3], float currentAcc[3]) + { + float ax = currentAcc[0] - prevAcc[0]; + float ay = currentAcc[1] - prevAcc[1]; + float az = currentAcc[2] - prevAcc[2]; + return sqrtf(ax * ax + ay * ay + az * az); + } + + void getAccelerometer(float acc[3]) + { + acc[0] = m_acc[0]; + acc[1] = m_acc[1]; + acc[2] = m_acc[2]; + } + void getGyrometer(float gyro[3]) + { + gyro[0] = m_gyro[0]; + gyro[1] = m_gyro[1]; + gyro[2] = m_gyro[2]; + } + +private: + static float _radToOrientation(float rad) + { + return rad / (2.0f * 3.14159265f); + } + + static float getAtanPitch(float X, float Y, float Z) + { + return atan2f(-X, sqrtf(Y * Y + Z * Z)); + } + + // provided values + float m_gyro[3]{}; + float m_acc[3]{}; + float m_accAcceleration{}; + float m_orientation[3]{}; + float m_q[4]{}; + // calculated values + float m_accMagnitude{}; +}; + +/* + +Captured VPAD attitude values + +Assuming GamePad is in a direct line between player (holder) and the monitor + +DRC flat on table, screen facing up. Top pointing away (away from person, pointing towards monitor): +1.00 -0.03 -0.00 +0.03 0.99 -0.13 +0.01 0.13 0.99 + +Turned 45° to the right: + 0.71 -0.03 0.71 + 0.12 0.99 -0.08 +-0.70 0.14 0.70 + +Turned 45° to the right (top of GamePad pointing right now): + 0.08 -0.03 1.00 -> Z points towards person + 0.15 0.99 0.01 +-0.99 0.15 0.09 -> DRC Z-Axis now points towards X-minus + +Turned 90° to the right (top of gamepad now pointing towards holder, away from monitor): +-1.00 -0.01 0.06 + 0.00 0.99 0.15 +-0.06 0.15 -0.99 + +Turned 90° to the right (pointing left): +-0.17 -0.01 -0.99 +-0.13 0.99 0.02 + 0.98 0.13 -0.17 + +After another 90° we end up in the initial position: + 0.99 -0.03 -0.11 + 0.01 0.99 -0.13 + 0.12 0.12 0.99 + +------ +From initial position, lean the GamePad on its left side. 45° up. So the screen is pointing to the top left + 0.66 -0.75 -0.03 + 0.74 0.66 -0.11 + 0.10 0.05 0.99 + +Further 45°, GamePad now on its left, screen pointing left: +-0.03 -1.00 -0.00 + 0.99 -0.03 -0.15 + 0.15 -0.01 0.99 + +From initial position, lean the GamePad on its right side. 45° up. So the screen is pointing to the top right + 0.75 0.65 -0.11 +-0.65 0.76 0.07 + 0.12 0.02 0.99 + +From initial position, tilt the GamePad up 90° (bottom side remains in touch with surface): + 0.99 -0.05 -0.10 +-0.10 0.01 -0.99 + 0.05 1.00 0.01 + +From initial position, stand the GamePad on its top side: + 1.00 -0.01 -0.09 + 0.09 -0.01 1.00 +-0.01 -1.00 -0.01 + +Rotate GamePad 180° around x axis, so it now lies on its screen (top of GamePad pointing to holder): + 0.99 -0.03 -0.15 +-0.04 -1.00 -0.08 +-0.15 0.09 -0.99 + +*/ \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 00000000..98f829de --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,469 @@ +#include "gui/guiWrapper.h" +#include "gui/wxgui.h" +#include "util/crypto/aes128.h" +#include "gui/MainWindow.h" +#include "Cafe/OS/RPL/rpl.h" +#include "Cafe/OS/RPL/rpl_symbol_storage.h" +#include "Cafe/OS/libs/gx2/GX2.h" +#include "Cafe/GameProfile/GameProfile.h" +#include "Cafe/GraphicPack/GraphicPack.h" +#include "config/CemuConfig.h" +#include "gui/CemuApp.h" +#include "Cafe/HW/Latte/Core/LatteOverlay.h" +#include "config/LaunchSettings.h" +#include "Cafe/OS/libs/coreinit/coreinit_Thread.h" + +#include "Cafe/CafeSystem.h" +#include "Cafe/TitleList/TitleList.h" +#include "Cafe/TitleList/SaveList.h" + +#include "Common/ExceptionHandler/ExceptionHandler.h" + +#include +#include "util/helpers/helpers.h" +#include "config/ActiveSettings.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/VsyncDriver.h" + +#include "Cafe/IOSU/legacy/iosu_crypto.h" +#include "Cafe/OS/libs/vpad/vpad.h" + +#include "audio/IAudioAPI.h" +#if BOOST_OS_WINDOWS > 0 +#pragma comment(lib,"Dbghelp.lib") +#endif + +#define SDL_MAIN_HANDLED +#include + +#if BOOST_OS_LINUX > 0 +#define _putenv(__s) putenv((char*)(__s)) +#endif + +extern "C" +{ + __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; + __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; +} + +bool _cpuExtension_SSSE3 = false; +bool _cpuExtension_SSE4_1 = false; +bool _cpuExtension_AVX2 = false; + +std::atomic_bool g_isGPUInitFinished = false; + +std::wstring executablePath; + +bool g_cemuhook_loaded = false; +bool IsCemuhookLoaded() +{ + return g_cemuhook_loaded; +} + +void checkForCemuhook() +{ + #if BOOST_OS_WINDOWS > 0 + // check if there is a dbghelp.dll in the current working directory + if (!fs::exists(ActiveSettings::GetPath("cemuhook.dll"))) + return; + // check if Cemuhook can be detected + DWORD verHandle; + DWORD verLen = GetFileVersionInfoSizeW(L"cemuhook.dll", &verHandle); + if (verLen == 0) + return; + uint8* verData = (uint8*)malloc(verLen); + GetFileVersionInfoW(L"cemuhook.dll", 0, verLen, verData); + // get version + LPVOID lpBuffer; + UINT size; + if (VerQueryValueW(verData, L"\\", (LPVOID*)&lpBuffer, (PUINT)&size)) + { + if (size) + { + VS_FIXEDFILEINFO *verInfo = (VS_FIXEDFILEINFO *)lpBuffer; + if (verInfo->dwSignature == 0xfeef04bd) + { + forceLog_printf("Cemuhook version: %d.%d.%d.%d", (verInfo->dwFileVersionMS >> 16) & 0xFFFF, (verInfo->dwFileVersionMS >> 0) & 0xFFFF, (verInfo->dwFileVersionLS >> 16) & 0xFFFF, (verInfo->dwFileVersionLS >> 0) & 0xFFFF); + g_cemuhook_loaded = true; + } + } + } + free(verData); + #endif +} + +void logCPUAndMemoryInfo() +{ + #if BOOST_OS_WINDOWS > 0 + int CPUInfo[4] = { -1 }; + unsigned nExIds, i = 0; + char CPUBrandString[0x40]; + // Get the information associated with each extended ID. + __cpuid(CPUInfo, 0x80000000); + nExIds = CPUInfo[0]; + for (i = 0x80000000; i <= nExIds; ++i) + { + __cpuid(CPUInfo, i); + // Interpret CPU brand string + if (i == 0x80000002) + memcpy(CPUBrandString, CPUInfo, sizeof(CPUInfo)); + else if (i == 0x80000003) + memcpy(CPUBrandString + 16, CPUInfo, sizeof(CPUInfo)); + else if (i == 0x80000004) + memcpy(CPUBrandString + 32, CPUInfo, sizeof(CPUInfo)); + } + forceLog_printf("CPU: %s", CPUBrandString); + + MEMORYSTATUSEX statex; + statex.dwLength = sizeof(statex); + GlobalMemoryStatusEx(&statex); + uint32 memoryInMB = (uint32)(statex.ullTotalPhys / 1024LL / 1024LL); + forceLog_printf("RAM: %uMB", memoryInMB); + #endif +} + +bool g_running_in_wine = false; +bool IsRunningInWine() +{ + return g_running_in_wine; +} + +void checkForWine() +{ + #if BOOST_OS_WINDOWS > 0 + const HMODULE hmodule = GetModuleHandleA("ntdll.dll"); + if (!hmodule) + return; + + const auto pwine_get_version = (const char*(__cdecl*)())GetProcAddress(hmodule, "wine_get_version"); + if (pwine_get_version) + { + g_running_in_wine = true; + forceLog_printf("Wine version: %s", pwine_get_version()); + } + #else + g_running_in_wine = false; + #endif +} + +void infoLog_cemuStartup() +{ + cemuLog_force("------- Init {} {}.{}{} -------", EMULATOR_NAME, EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_SUFFIX); + cemuLog_force("Init Wii U memory space (base: 0x{:016x})", (size_t)memory_base); + cemuLog_force(u8"mlc01 path: {}", ActiveSettings::GetMlcPath().generic_u8string()); + // check if Cemuhook is installed + checkForCemuhook(); + // check for wine version + checkForWine(); + // CPU and RAM info + logCPUAndMemoryInfo(); + // extensions that Cemu uses + char cpuExtensionStr[256]; + strcpy(cpuExtensionStr, ""); + if (_cpuExtension_SSSE3) + { + strcat(cpuExtensionStr, "SSSE3"); + } + if (_cpuExtension_SSE4_1) + { + if (cpuExtensionStr[0] != '\0') + strcat(cpuExtensionStr, ", "); + strcat(cpuExtensionStr, "SSE4.1"); + } + if (_cpuExtension_AVX2) + { + if (cpuExtensionStr[0] != '\0') + strcat(cpuExtensionStr, ", "); + strcat(cpuExtensionStr, "AVX2"); + } + if (AES128_useAESNI()) + { + if (cpuExtensionStr[0] != '\0') + strcat(cpuExtensionStr, ", "); + strcat(cpuExtensionStr, "AES-NI"); + } + cemuLog_force("Used CPU extensions: {}", cpuExtensionStr); +} + +// some implementations of _putenv dont copy the string and instead only store a pointer +// thus we use a helper to keep a permanent copy +std::vector sPutEnvMap; + +void _putenvSafe(const char* c) +{ + auto s = new std::string(c); + sPutEnvMap.emplace_back(s); + _putenv(s->c_str()); +} + +void reconfigureGLDrivers() +{ + // reconfigure GL drivers to store + const fs::path nvCacheDir = ActiveSettings::GetPath("shaderCache/driver/nvidia/"); + + std::error_code err; + fs::create_directories(nvCacheDir, err); + + std::string nvCacheDirEnvOption("__GL_SHADER_DISK_CACHE_PATH="); + nvCacheDirEnvOption.append(_utf8Wrapper(nvCacheDir)); + +#if BOOST_OS_WINDOWS > 0 + std::wstring tmpW = boost::nowide::widen(nvCacheDirEnvOption); + _wputenv(tmpW.c_str()); +#else + _putenvSafe(nvCacheDirEnvOption.c_str()); +#endif + _putenvSafe("__GL_SHADER_DISK_CACHE_SKIP_CLEANUP=1"); + +} + +void reconfigureVkDrivers() +{ + _putenvSafe("DISABLE_LAYER_AMD_SWITCHABLE_GRAPHICS_1=1"); + _putenvSafe("DISABLE_VK_LAYER_VALVE_steam_fossilize_1=1"); +} + +void mainEmulatorCommonInit() +{ + reconfigureGLDrivers(); + reconfigureVkDrivers(); + // crypto init + AES128_init(); + // init PPC timer (call this as early as possible because it measures frequency of RDTSC using an asynchronous thread over 3 seconds) + PPCTimer_init(); + // check available CPU extensions + int cpuInfo[4]; + __cpuid(cpuInfo, 0x1); + _cpuExtension_SSSE3 = ((cpuInfo[2] >> 9) & 1) != 0; + _cpuExtension_SSE4_1 = ((cpuInfo[2] >> 19) & 1) != 0; + + __cpuidex(cpuInfo, 0x7, 0); + _cpuExtension_AVX2 = ((cpuInfo[1] >> 5) & 1) != 0; + +#if BOOST_OS_WINDOWS > 0 + executablePath.resize(4096); + int i = GetModuleFileName(NULL, executablePath.data(), executablePath.size()); + if(i >= 0) + executablePath.resize(i); + else + executablePath.clear(); + SetCurrentDirectory(executablePath.c_str()); + + // set high priority + SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS); +#endif + ExceptionHandler_init(); + // read config + g_config.Load(); + // symbol storage + rplSymbolStorage_init(); + // static initialization + IAudioAPI::InitializeStatic(); + // load graphic packs (must happen before config is loaded) + graphicPack_loadAll(); + // initialize file system + fsc_init(); +} + +void mainEmulatorLLE(); +void ppcAsmTest(); +void gx2CopySurfaceTest(); +void ExpressionParser_test(); +void FSTVolumeTest(); + +void unitTests() +{ + ExpressionParser_test(); + gx2CopySurfaceTest(); + ppcAsmTest(); + FSTVolumeTest(); +} + +int mainEmulatorHLE() +{ + if (!TestWriteAccess(ActiveSettings::GetPath())) + wxMessageBox("Cemu doesn't have write access to it's own directory.\nPlease move it to a different location or run Cemu as administrator!", "Warning", wxOK|wxICON_ERROR); // todo - different error messages per OS + LatteOverlay_init(); + // run a couple of tests if in non-release mode +#ifndef PUBLIC_RELEASE + unitTests(); +#endif + // init common + mainEmulatorCommonInit(); + // reserve memory (no allocations yet) + memory_init(); + // init ppc core + PPCCore_init(); + // log Cemu startup info + infoLog_cemuStartup(); + // init RPL loader + RPLLoader_InitState(); + // init IOSU components + iosuCrypto_init(); + // init Cafe system (todo - the stuff above should be part of this too) + CafeSystem::Initialize(); + // init title list + CafeTitleList::Initialize(ActiveSettings::GetPath("title_list_cache.xml")); + for (auto& it : GetConfig().game_paths) + CafeTitleList::AddScanPath(it); + fs::path mlcPath = ActiveSettings::GetMlcPath(); + if (!mlcPath.empty()) + CafeTitleList::SetMLCPath(mlcPath); + CafeTitleList::Refresh(); + // init save list + CafeSaveList::Initialize(); + if (!mlcPath.empty()) + { + CafeSaveList::SetMLCPath(mlcPath); + CafeSaveList::Refresh(); + } + // Create UI + gui_create(); + return 0; +} + +bool isConsoleConnected = false; +void requireConsole() +{ + #if BOOST_OS_WINDOWS > 0 + if (isConsoleConnected) + return; + + if (AttachConsole(ATTACH_PARENT_PROCESS) != FALSE) + { + freopen("CONIN$", "r", stdin); + freopen("CONOUT$", "w", stdout); + freopen("CONOUT$", "w", stderr); + isConsoleConnected = true; + } + #endif +} + +void HandlePostUpdate() +{ + // finalize update process + // delete update cemu.exe.backup if available + const auto filename = ActiveSettings::GetFullPath().replace_extension("exe.backup"); + if (fs::exists(filename)) + { +#if BOOST_OS_WINDOWS > 0 + HANDLE lock; + do + { + lock = CreateMutex(nullptr, TRUE, L"Global\\cemu_update_lock"); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } while (lock == nullptr); + const DWORD wait_result = WaitForSingleObject(lock, 2000); + CloseHandle(lock); + + if (wait_result == WAIT_OBJECT_0) + { + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + std::error_code ec; + fs::remove(filename, ec); + } +#else + while( fs::exists(filename) ) + { + std::error_code ec; + fs::remove(filename, ec); + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + } +#endif + } +} + +void ToolShaderCacheMerger(); + +#if BOOST_OS_WINDOWS > 0 + +#ifndef PUBLIC_RELEASE +#include +int wmain(int argc, wchar_t* argv[]) +{ + SDL_SetMainReady(); + _CrtSetDbgFlag(_CRTDBG_CHECK_DEFAULT_DF); + //ToolShaderCacheMerger(); + + if (!LaunchSettings::HandleCommandline(argc, argv)) + return 0; + + ActiveSettings::LoadOnce(); + + HandlePostUpdate(); + return mainEmulatorHLE(); +} +#else +int wWinMain( _In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPTSTR lpCmdLine, _In_ int nShowCmd) +{ + SDL_SetMainReady(); + + if (!LaunchSettings::HandleCommandline(lpCmdLine)) + return 0; + + ActiveSettings::LoadOnce(); + + HandlePostUpdate(); + return mainEmulatorHLE(); +} + +#endif + +#else +int main(int argc, char *argv[]) +{ + XInitThreads(); + if (!LaunchSettings::HandleCommandline(argc, argv)) + return 0; + + ActiveSettings::LoadOnce(); + + HandlePostUpdate(); + return mainEmulatorHLE(); +} +#endif + +/* Cemuhook legacy API */ + +#pragma optimize("",off) + +__declspec(dllexport) void gameMeta_loadForCurrent() +{ + int placeholderA = 0x11223344; + int placeholderB = 0x55667788; +} + +#pragma optimize("",on) + +__declspec(dllexport) uint64 gameMeta_getTitleId() +{ + return CafeSystem::GetForegroundTitleId(); +} + +/* Cemuhook loading */ +#if BOOST_OS_WINDOWS > 0 +#pragma init_seg(".CRT$XCT") + +HANDLE dbgLib; + +int dbghelp_init(void) +{ + // load dbghelp.dll from the system folder instead of loading outdated cemuhook via dbghelp.dll + WCHAR dllPath[MAX_PATH]; + GetSystemDirectoryW(dllPath, MAX_PATH); + wcscat_s(dllPath, sizeof(dllPath) / sizeof(WCHAR), TEXT("\\dbghelp.dll")); + + dbgLib = LoadLibraryW(dllPath); + if (dbgLib == NULL) + return -1; + + return 0; +} + +HMODULE _earlyInitFunction() +{ + dbghelp_init(); + return LoadLibraryA("cemuhook2.dll"); +} + +HMODULE _cemuHookDllHandle = _earlyInitFunction(); +#endif diff --git a/src/mainLLE.cpp b/src/mainLLE.cpp new file mode 100644 index 00000000..2757c22c --- /dev/null +++ b/src/mainLLE.cpp @@ -0,0 +1,50 @@ +#include "gui/wxgui.h" +#include "util/crypto/aes128.h" +#include "gui/MainWindow.h" +#include "gui/guiWrapper.h" +#include "Common/filestream.h" + +void mainEmulatorCommonInit(); + +typedef struct +{ + /* +0x000 */ uint32be magic; +}ppcAncastHeader_t; + +void loadEncryptedPPCAncastKernel() +{ + auto kernelData = FileStream::LoadIntoMemory("kernel.img"); + if (!kernelData) + exit(-1); + // check header + ppcAncastHeader_t* ancastHeader = (ppcAncastHeader_t*)kernelData->data(); + if(ancastHeader->magic != (uint32be)0xEFA282D9) + assert_dbg(); // invalid magic + memcpy(memory_getPointerFromPhysicalOffset(0x08000000), kernelData->data(), kernelData->size()); +} + +void loadPPCBootrom() +{ + auto bootromData = FileStream::LoadIntoMemory("bootrom.bin"); + if (!bootromData) + exit(-1); + memcpy(memory_getPointerFromPhysicalOffset(0x00000000), bootromData->data(), bootromData->size()); +} + +void mainEmulatorLLE() +{ + mainEmulatorCommonInit(); + // memory init + memory_initPhysicalLayout(); + + // start GUI thread + gui_create(); + // load kernel ancast image + loadPPCBootrom(); + loadEncryptedPPCAncastKernel(); + + PPCTimer_waitForInit(); + // begin execution + PPCCoreLLE_startSingleCoreScheduler(0x00000100); +} + diff --git a/src/resource/CMakeLists.txt b/src/resource/CMakeLists.txt new file mode 100644 index 00000000..e0a6e3b2 --- /dev/null +++ b/src/resource/CMakeLists.txt @@ -0,0 +1,19 @@ +add_library(CemuResource) + +set_property(TARGET CemuResource PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + +target_precompile_headers(CemuResource PRIVATE ../Common/precompiled.h) + +# icon resources +if(UNIX) + target_sources(CemuResource PRIVATE + linux/resources.cpp + linux/resources.h + ) +endif() + +target_sources(CemuResource PRIVATE + CafeDefaultFont.cpp + ) + +target_include_directories(CemuResource PRIVATE ../) diff --git a/src/resource/CafeDefaultFont.cpp b/src/resource/CafeDefaultFont.cpp new file mode 100644 index 00000000..7fe7052a --- /dev/null +++ b/src/resource/CafeDefaultFont.cpp @@ -0,0 +1,2569 @@ + +unsigned char cafeDefaultFontZLIB[] = { + 0x78, 0xda, 0xdc, 0xbc, 0x07, 0x78, 0x54, 0xd5, 0xda, 0xf7, 0x7d, 0xef, 0x36, 0xbb, 0x0c, 0xbd, 0x43, 0x80, 0x4c, 0x08, 0x20, 0x25, 0xf4, 0x16, 0x3a, 0x24, 0xf4, 0x1a, 0x7a, 0x82, 0x20, 0x09, 0x84, 0xde, 0x7b, 0x91, 0x2e, 0x20, 0x20, 0x45, 0x04, 0xe9, 0x4d, 0x40, 0x40, 0x40, 0x98, 0x50, 0x94, 0x5e, 0x04, 0x15, 0x15, 0x05, 0x15, 0x15, 0x05, 0x01, 0x15, 0x05, 0x14, 0x14, 0xa5, 0x2a, 0x65, 0xde, 0xdf, 0x9e, 0x4c, 0x10, 0x50, 0xcf, 0x79, 0xce, 0xf3, 0x3d, 0xcf, 0x77, 0x5d, 0xef, + 0x8b, 0xe7, 0xcf, 0x6a, 0xf7, 0xba, 0xfb, 0x5e, 0x7b, 0xad, 0xd9, 0xeb, 0x20, 0x8a, 0x88, 0x64, 0xe2, 0x2f, 0x5d, 0xa4, 0x7e, 0x5c, 0xf3, 0x56, 0x67, 0xf7, 0x1e, 0xec, 0x20, 0x9e, 0x0e, 0xe3, 0x45, 0x2b, 0xff, 0xa0, 0x7e, 0xab, 0x36, 0x31, 0x7d, 0xd6, 0x9f, 0x18, 0x21, 0xf6, 0x9c, 0xf1, 0x22, + 0x05, 0xfb, 0x34, 0x6f, 0x55, 0xaa, 0x6c, 0x6f, 0xcf, 0xa5, 0x3b, 0x22, 0xca, 0x54, 0x66, 0x25, 0x76, 0xe9, 0x9b, 0x34, 0x20, 0x6e, 0xc3, 0x97, 0x7f, 0x88, 0x94, 0x29, 0x28, 0x92, 0x6d, 0x6d, 0x97, 0x61, 0x43, 0x7c, 0x55, 0xce, 0x6f, 0x6f, 0x27, 0xd2, 0xd2, 0xc7, 0xf8, 0x0f, 0xdd, 0x06, 0x74, 0xef, 0x7b, 0x21, 0xdb, 0xe9, 0x6b, 0x22, 0x75, 0x8f, 0x89, 0x78, 0x17, 0x74, 0x4f, 0x1a, 0x3c, 0xc0, 0x95, 0x06, 0xff, 0x11, 0x94, 0x56, 0xf7, 0x3e, 0x23, 0xbb, 0xbd, 0x3a, 0xae, 0x50, 0x37, 0x91, 0x89, 0x0d, 0xc4, 0x78, 0xbb, 0x40, 0x8f, 0xae, 0x49, 0xc9, 0xde, 0x97, 0x9f, 0x3b, 0x0d, 0xff, 0x74, 0x8c, 0x57, 0xec, 0x41, 0x47, 0xfa, 0xcf, 0xd3, 0xad, 0xa3, 0x5d, 0x87, 0x76, 0xc1, 0x1e, 0x7d, 0x87, 0x8c, 0x38, 0x32, 0xe3, 0x62, 0x79, 0xda, 0x97, 0x44, 0x4a, 0x55, 0xeb, 0xd3, + 0xbf, 0x4b, 0x52, 0xd1, 0xcd, 0x1b, 0x1c, 0x91, 0x56, 0x16, 0xed, 0x5a, 0x7d, 0x93, 0x46, 0x0c, 0x48, 0x3f, 0x3f, 0xfd, 0x30, 0xc6, 0x7b, 0x40, 0xef, 0xeb, 0x97, 0xd4, 0xb7, 0xeb, 0x9e, 0x37, 0x93, 0x96, 0x8a, 0xa7, 0x7e, 0x1f, 0xf4, 0xfb, 0x71, 0x40, 0xff, 0xc1, 0x43, 0x1e, 0x94, 0x13, 0xd7, 0xbe, 0x78, 0x77, 0x7c, 0xc0, 0xa0, 0xae, 0x03, 0xfa, 0x3b, 0x3d, 0xab, 0x88, 0xb4, 0x48, 0xa6, 0xbd, 0x4b, 0x5c, 0x5f, 0xa8, 0xda, 0x17, 0x49, 0x53, 0x53, 0xee, 0x76, 0xca, 0x58, 0xed, 0xa6, 0xe4, 0x36, 0x4f, 0xd0, 0x23, 0x1f, 0x5f, 0x5a, 0x3f, 0xd2, 0x2d, 0x4f, 0x26, 0xcd, 0x48, 0xbe, 0xbb, 0xfc, 0xfe, 0x79, 0x67, 0x9c, 0xe5, 0xea, 0x6f, 0x8b, 0x1a, 0x9c, 0x21, 0xee, 0xdf, 0xe6, 0x1b, 0x0f, 0xda, 0x8a, 0x38, 0xfb, 0xee, 0x2e, 0xbf, 0x7d, 0xc4, 0x19, 0x17, 0xea, 0x7f, 0xf8, + 0xc7, 0x7b, 0xdc, 0xed, 0xe1, 0xef, 0xab, 0x58, 0x3f, 0x5b, 0x3c, 0xcc, 0x8c, 0x93, 0x44, 0x19, 0xee, 0x7a, 0xc9, 0xbb, 0x40, 0x34, 0x46, 0x75, 0x3d, 0x5e, 0xb9, 0x21, 0x86, 0x88, 0x31, 0xc2, 0xc8, 0x06, 0xcb, 0x5c, 0xa9, 0xa5, 0xb6, 0x5b, 0x3a, 0xa8, 0xc5, 0x50, 0x2b, 0xa3, 0xa1, 0xaa, 0xaa, 0xae, 0xa9, 0xda, 0x37, 0xa2, 0x06, 0xe2, 0xe4, 0x4a, 0x80, 0xb9, 0x11, 0x2e, 0xef, 0x21, 0x23, 0xe3, 0x9a, 0xc8, 0x41, 0x91, 0x07, 0x81, 0x54, 0x1d, 0xac, 0x5d, 0x6a, 0x3b, 0x9f, 0x28, 0x2b, 0xdc, 0x31, 0xed, 0x8e, 0xb1, 0xd4, 0xb5, 0x54, 0xbc, 0x7a, 0x29, 0x89, 0x0a, 0xaa, 0xfa, 0x3b, 0x12, 0x5a, 0x89, 0x6a, 0xb4, 0x10, 0xc5, 0xac, 0x17, 0xb8, 0x65, 0x64, 0x95, 0x14, 0xa3, 0x82, 0xb4, 0x0f, 0xe2, 0x57, 0x49, 0xf1, 0xa8, 0xa2, 0xea, 0x13, 0x25, 0x25, 0x88, 0xde, 0x8c, 0x6d, + 0x93, 0x04, 0xe3, 0x2e, 0xfd, 0x5e, 0xc9, 0xad, 0x46, 0x4b, 0x8a, 0x5a, 0x59, 0xba, 0x68, 0x2d, 0x24, 0xa7, 0x51, 0x99, 0x71, 0x0f, 0x88, 0x94, 0x1c, 0x6e, 0x69, 0x36, 0x84, 0xf6, 0x6e, 0x2a, 0x3c, 0x75, 0x25, 0xc1, 0x85, 0x35, 0x5e, 0x3a, 0xe8, 0xbb, 0xa1, 0xd9, 0x11, 0xec, 0x6b, 0xaf, 0x5f, 0xa5, 0x5e, 0x4b, 0xaa, 0x07, 0xb1, 0x4a, 0xf2, 0xeb, 0xfb, 0x25, 0xca, 0x38, 0x2c, 0x2b, 0x82, 0x40, 0x27, 0x2b, 0x5c, 0xc2, 0x8c, 0xbc, 0x92, 0x51, 0xbf, 0x12, 0x78, 0xa0, 0xbf, 0x21, 0x65, 0xb5, 0x8b, 0x52, 0xdd, 0x85, 0xa7, 0x89, 0xd4, 0xd2, 0x93, 0x65, 0x9a, 0xb6, 0x4f, 0x9a, 0x6b, 0xfb, 0xa5, 0xb9, 0xf5, 0x94, 0x34, 0x37, 0x8f, 0x49, 0x73, 0xcf, 0x64, 0x30, 0x45, 0x9a, 0x33, 0xd6, 0x32, 0x88, 0xae, 0xd2, 0x4a, 0xef, 0x27, 0xcd, 0xd0, 0xa9, 0xb9, 0x0b, 0xf5, 0x90, 0x94, + 0x34, 0x4b, 0x48, 0x49, 0x7b, 0xbd, 0x94, 0x34, 0xaa, 0x52, 0x2f, 0x42, 0x7d, 0x95, 0x94, 0xd4, 0x9f, 0x97, 0x52, 0x41, 0x4c, 0xf9, 0x9b, 0x32, 0x54, 0x37, 0x15, 0x20, 0xcc, 0x51, 0x42, 0xf5, 0xbf, 0x41, 0x1a, 0x9d, 0xe7, 0x23, 0xf8, 0x36, 0xa2, 0xbd, 0x96, 0x76, 0x38, 0xb2, 0x92, 0x99, 0x77, 0x9e, 0x76, 0x36, 0xa9, 0x1a, 0xd4, 0xab, 0x93, 0x4c, 0xd3, 0x3b, 0x05, 0x56, 0x81, 0x5f, 0x95, 0x8b, 0xd2, 0x17, 0xd4, 0x24, 0x83, 0x5f, 0xd2, 0xf3, 0x4a, 0x27, 0xe5, 0x62, 0x60, 0xa7, 0x72, 0x29, 0xb0, 0x4b, 0x37, 0xa0, 0x01, 0xea, 0x60, 0x29, 0xa6, 0x9c, 0x95, 0x99, 0x9e, 0xa1, 0xee, 0x9c, 0x10, 0x06, 0x61, 0xcf, 0x00, 0xa9, 0xeb, 0x39, 0x29, 0x71, 0xd8, 0x38, 0x2d, 0x88, 0x2e, 0xd2, 0x52, 0xfd, 0x1a, 0x9f, 0x74, 0x95, 0x1a, 0x2e, 0x3c, 0x31, 0x20, 0x56, 0x6a, 0xe8, 0xf3, + 0x64, 0xaa, 0x5a, 0x5b, 0x4a, 0xa9, 0x27, 0xa5, 0xa8, 0xfa, 0x89, 0x3c, 0xa5, 0x9e, 0xc6, 0xc7, 0xdd, 0xa4, 0xa8, 0xfe, 0xb5, 0x14, 0xd1, 0xcf, 0x4a, 0x51, 0x23, 0x51, 0x8a, 0x9a, 0xd7, 0x41, 0x5b, 0x29, 0x62, 0xb6, 0xa3, 0xff, 0x19, 0x19, 0x1b, 0x44, 0x06, 0xc9, 0xea, 0x29, 0x2e, 0xf9, 0xb4, 0x6b, 0x92, 0x55, 0x4f, 0x2f, 0x99, 0xb5, 0x19, 0x12, 0xad, 0x9e, 0x93, 0x4e, 0x5a, 0x41, 0x49, 0xd0, 0xa2, 0xa4, 0xb7, 0x1a, 0x25, 0x45, 0xd4, 0xee, 0x12, 0xa7, 0x66, 0x96, 0xd2, 0x94, 0x65, 0xd5, 0x58, 0x29, 0xad, 0xbc, 0x28, 0x05, 0xd5, 0x5a, 0x52, 0xc8, 0xed, 0x57, 0x0a, 0x63, 0xd3, 0x53, 0x81, 0xcb, 0x6a, 0x0b, 0xea, 0xc5, 0x24, 0x4e, 0x1b, 0x0b, 0x6d, 0x37, 0xd0, 0x13, 0xfa, 0x1e, 0x94, 0xdd, 0xa5, 0x8c, 0xb2, 0x8c, 0xb1, 0xef, 0xa5, 0xa8, 0xe2, 0x97, 0x08, 0xc6, 0x1a, + 0xa9, 0x05, 0xc5, 0xd4, 0x5a, 0x93, 0x57, 0x75, 0x25, 0x87, 0x5a, 0x48, 0x0c, 0xe5, 0x96, 0x14, 0xd7, 0x7c, 0x32, 0x0c, 0x84, 0x6b, 0x3e, 0x25, 0x2f, 0x70, 0xa8, 0xb7, 0x06, 0x4d, 0x42, 0x65, 0x3d, 0x32, 0xb8, 0x08, 0x65, 0x8f, 0x50, 0x5f, 0x3c, 0x68, 0x00, 0xfa, 0x81, 0x21, 0x60, 0x38, 0xe8, 0x08, 0xde, 0x02, 0x1f, 0x85, 0xca, 0x2f, 0xc0, 0x56, 0xb0, 0x34, 0x95, 0x9f, 0x92, 0x91, 0xfa, 0x2f, 0xe0, 0x3c, 0xf5, 0x82, 0x20, 0x5a, 0x77, 0x73, 0xb7, 0xad, 0xe4, 0x76, 0x73, 0xd6, 0x88, 0x63, 0x29, 0x3c, 0x25, 0xad, 0xdc, 0x3c, 0xd6, 0x0f, 0x4b, 0x6d, 0xcf, 0x0b, 0x12, 0xeb, 0xe9, 0x21, 0x89, 0x9e, 0xd6, 0x92, 0xac, 0xbf, 0x8a, 0xff, 0x13, 0x65, 0x2a, 0xf1, 0xe8, 0xab, 0xd7, 0xc7, 0x77, 0x2d, 0xd1, 0x61, 0xa6, 0xb4, 0xd4, 0x72, 0x48, 0x53, 0xfc, 0x9e, 0x45, 0x7f, 0x5d, + 0xfa, 0x18, 0xd9, 0xf1, 0x7b, 0x1e, 0x99, 0xa0, 0x57, 0x95, 0xa7, 0xf4, 0x2d, 0xd0, 0x5a, 0x92, 0x55, 0xcb, 0x23, 0xcd, 0xb4, 0xfc, 0xd2, 0x58, 0xff, 0x42, 0x1a, 0xea, 0x3d, 0xe4, 0x59, 0x7d, 0x3b, 0x79, 0xbc, 0x86, 0xbe, 0x8d, 0x92, 0x4b, 0x6f, 0x23, 0x63, 0x8d, 0x93, 0xd2, 0xd4, 0xf8, 0x8c, 0xf6, 0xdb, 0x81, 0x07, 0x1e, 0x43, 0x46, 0x7a, 0x3c, 0xae, 0xad, 0x8a, 0x6b, 0x6f, 0x5b, 0x65, 0x8b, 0xb4, 0x74, 0x41, 0xfc, 0x52, 0xf4, 0x0a, 0xa9, 0xf0, 0x94, 0x92, 0x0c, 0x56, 0x94, 0x64, 0xe0, 0x19, 0x2d, 0x1c, 0x7c, 0xe6, 0x1a, 0x81, 0xed, 0x81, 0x3f, 0xac, 0x18, 0x30, 0x2d, 0xf5, 0xf9, 0x33, 0x8e, 0x40, 0x7b, 0x85, 0xe7, 0xfc, 0xd9, 0x40, 0xc0, 0x53, 0x44, 0x1a, 0x7a, 0xc2, 0xa1, 0xa1, 0xcf, 0x28, 0x2b, 0xf1, 0x56, 0x5f, 0x49, 0xb1, 0x8e, 0x52, 0x3f, 0x26, 0xf9, 0xcc, + 0x4c, 0xd0, 0xbd, 0x4d, 0x3d, 0x56, 0xca, 0x1b, 0x75, 0xa4, 0xbd, 0x55, 0x8e, 0x7a, 0xe5, 0xc0, 0x1f, 0xc6, 0x87, 0x3c, 0x8b, 0xa7, 0xc1, 0x16, 0x29, 0xaf, 0xbf, 0x8c, 0xbd, 0xad, 0xa4, 0xa9, 0x76, 0x81, 0x7c, 0xab, 0x91, 0x0a, 0xfd, 0x8c, 0x64, 0xf7, 0x54, 0x94, 0x9c, 0xda, 0x44, 0x29, 0xa3, 0x3f, 0x47, 0x3b, 0x17, 0x58, 0x24, 0xe2, 0x99, 0xc5, 0xfa, 0x32, 0x8d, 0x7a, 0x4f, 0x99, 0xe6, 0x49, 0xcb, 0x5d, 0xea, 0xda, 0x2d, 0xc9, 0x83, 0x4f, 0x32, 0x1b, 0xb3, 0xf1, 0xd5, 0xc1, 0xd4, 0x71, 0x3d, 0x9d, 0x34, 0xf3, 0x0c, 0x82, 0xee, 0x3d, 0xea, 0x2f, 0x4b, 0x5e, 0xe3, 0x75, 0xe8, 0x0e, 0x81, 0x77, 0xa5, 0x12, 0x68, 0xee, 0x79, 0x8a, 0xfa, 0x37, 0xac, 0x5d, 0xad, 0x03, 0x5f, 0x40, 0x9f, 0x0f, 0x7f, 0x24, 0x83, 0x8d, 0x6e, 0xa9, 0xfa, 0x64, 0xba, 0xc6, 0xf3, 0xaf, 0x9d, + 0x92, 0x78, 0xe5, 0x7d, 0xe9, 0xa6, 0x66, 0x92, 0xe7, 0x94, 0x3b, 0x32, 0x52, 0x39, 0x25, 0x3d, 0x8d, 0x8b, 0xd2, 0x13, 0x9f, 0x75, 0x54, 0x36, 0x4b, 0x67, 0xf5, 0x07, 0xe9, 0xa8, 0x7e, 0x2f, 0x9d, 0xc9, 0xb9, 0xd6, 0xea, 0x45, 0x50, 0x89, 0xbe, 0x68, 0x19, 0xa5, 0x75, 0x97, 0xce, 0x5a, 0x0f, 0xca, 0xd9, 0x52, 0x5b, 0xf9, 0x83, 0x75, 0x4d, 0x64, 0xbb, 0x5b, 0x57, 0x2f, 0xc9, 0x28, 0x7d, 0xaa, 0x6c, 0xf3, 0x54, 0x90, 0xb1, 0xda, 0x62, 0x89, 0xa5, 0x7e, 0x48, 0xcd, 0x28, 0x3d, 0x89, 0x69, 0x5b, 0x10, 0x4d, 0x2e, 0x6f, 0x05, 0x55, 0xd5, 0x8e, 0xd0, 0x76, 0x94, 0x5a, 0x5a, 0x4b, 0x19, 0x06, 0x4d, 0x5f, 0x17, 0xda, 0x77, 0xd2, 0xd9, 0x78, 0x41, 0xea, 0x52, 0x1f, 0x0d, 0x06, 0x2b, 0x4b, 0x65, 0xa4, 0xfa, 0x9b, 0x8c, 0x34, 0xe6, 0x4b, 0x1b, 0xcf, 0x4c, 0xe9, 0xea, 0x19, + 0x2d, 0x49, 0x69, 0x30, 0xce, 0x4b, 0x3b, 0xe3, 0x73, 0x79, 0x16, 0x5f, 0xb6, 0x86, 0x76, 0x1c, 0xc8, 0xa5, 0x47, 0xb3, 0xf6, 0x4d, 0x65, 0x9d, 0x9c, 0x2a, 0x53, 0x40, 0x09, 0x50, 0x24, 0x88, 0x68, 0x29, 0xe6, 0x99, 0x26, 0xd1, 0x76, 0x76, 0x79, 0x8f, 0x76, 0x29, 0xfd, 0x77, 0xe9, 0xae, 0xa5, 0x97, 0x02, 0x7a, 0x35, 0x69, 0xa7, 0x3f, 0xc5, 0x7a, 0xd7, 0x5b, 0x0a, 0x69, 0x97, 0xa4, 0xb2, 0x5e, 0x4f, 0x9e, 0xd1, 0x4e, 0x32, 0xde, 0x4c, 0x7a, 0xe0, 0xdb, 0xba, 0xcc, 0x2b, 0x0f, 0x26, 0x81, 0x56, 0x21, 0xd8, 0xa0, 0x13, 0x88, 0x0f, 0x95, 0x1d, 0x41, 0x7b, 0xa3, 0x88, 0x74, 0xc3, 0xa7, 0xf9, 0x58, 0x63, 0x9a, 0x91, 0x53, 0xcd, 0xf5, 0x4d, 0x52, 0x48, 0xaf, 0x2d, 0xf5, 0xf5, 0x22, 0x52, 0x49, 0x6f, 0x10, 0xf4, 0x41, 0x15, 0xb0, 0x34, 0xa4, 0x5b, 0x0b, 0x90, 0x15, 0xf4, + 0x02, 0x3d, 0x40, 0x17, 0x75, 0xa9, 0x54, 0x01, 0x6e, 0x59, 0x0f, 0xc4, 0x00, 0x1f, 0x68, 0x0e, 0x9a, 0x81, 0x36, 0xa0, 0x29, 0x88, 0x03, 0xed, 0x40, 0x1f, 0x17, 0x4a, 0x33, 0xa9, 0x0a, 0xca, 0x85, 0xe6, 0x26, 0x82, 0x3a, 0xa0, 0x36, 0xc8, 0x17, 0x9a, 0xeb, 0xce, 0x69, 0x19, 0x2a, 0x5b, 0x28, 0xb9, 0x1e, 0xcc, 0x35, 0x3e, 0x92, 0x67, 0x8c, 0xad, 0xbc, 0x53, 0x90, 0x09, 0xe6, 0x82, 0x31, 0xa0, 0x73, 0xa8, 0x3e, 0x0f, 0x6c, 0x00, 0x2b, 0xc0, 0xb9, 0x10, 0xcd, 0x1b, 0xe0, 0x28, 0x78, 0xd1, 0xd3, 0x5f, 0xba, 0xeb, 0x75, 0xa5, 0x86, 0x71, 0x50, 0x7a, 0xea, 0x93, 0xa5, 0x85, 0xf6, 0x2b, 0x6f, 0xdc, 0xf3, 0xac, 0x87, 0xfd, 0xa4, 0x1e, 0xcf, 0x7e, 0x43, 0x68, 0x92, 0x79, 0xdb, 0xf7, 0x04, 0x43, 0x8c, 0x33, 0xb2, 0x19, 0x6c, 0x01, 0x25, 0xc0, 0x94, 0xb4, 0xd2, 0x1d, 0x77, + 0xc7, 0xf0, 0xd5, 0x87, 0xe0, 0xe8, 0x9f, 0x6b, 0x53, 0x20, 0x7f, 0x2a, 0x1e, 0x5b, 0xab, 0xdc, 0xb2, 0x3e, 0x68, 0x9a, 0x5a, 0x06, 0x6e, 0x3d, 0xde, 0x76, 0xc7, 0x95, 0x3c, 0x9e, 0xf5, 0xbc, 0x2b, 0x57, 0xa0, 0xf3, 0x47, 0xf8, 0xfd, 0x01, 0xeb, 0xf0, 0x11, 0xd6, 0x9e, 0x92, 0x32, 0x08, 0x8c, 0x34, 0x56, 0xf1, 0x2c, 0xee, 0x02, 0xc3, 0x24, 0x51, 0x1f, 0x28, 0x1b, 0x79, 0xce, 0x36, 0x12, 0x8f, 0xce, 0xaa, 0x8e, 0x5f, 0x77, 0x4a, 0x45, 0xb5, 0x0d, 0xcf, 0x43, 0x47, 0xca, 0x76, 0x52, 0x4c, 0xbd, 0x26, 0xe9, 0xd5, 0x0b, 0xac, 0xa5, 0xf3, 0x03, 0xb7, 0xd4, 0x79, 0x52, 0x5c, 0xe4, 0x3e, 0x7a, 0x2a, 0x8d, 0x90, 0xb3, 0x0b, 0xd9, 0x9f, 0x83, 0xb5, 0x8f, 0xc8, 0x1f, 0x1e, 0x5a, 0xfb, 0x32, 0x68, 0x8e, 0x2c, 0x7c, 0x04, 0x2f, 0x3d, 0xd1, 0xee, 0x07, 0x3e, 0x48, 0x5d, 0x47, + 0xe1, 0xe5, 0xf0, 0x4e, 0xfe, 0x13, 0x2e, 0x6d, 0x45, 0xb0, 0x92, 0xb1, 0x88, 0x54, 0x48, 0x03, 0xd6, 0xc7, 0x7c, 0x7a, 0xa2, 0x32, 0x3e, 0xb8, 0xfb, 0x70, 0xe4, 0x0e, 0x7d, 0xbf, 0x81, 0xda, 0xd4, 0xcf, 0x6a, 0x0e, 0xeb, 0xaf, 0xc3, 0x7a, 0x08, 0x2f, 0xb5, 0xa6, 0xf4, 0xd2, 0x56, 0xca, 0x2a, 0xc6, 0x3e, 0xb0, 0x66, 0x49, 0xcb, 0x10, 0x7a, 0x84, 0x30, 0x30, 0xad, 0xcd, 0xb3, 0x1a, 0xaf, 0xc6, 0x48, 0x2b, 0xa5, 0x39, 0x76, 0x77, 0x93, 0x56, 0x6a, 0x27, 0xe9, 0xce, 0x3b, 0xa8, 0x95, 0xda, 0x39, 0x58, 0x1f, 0xa0, 0xb5, 0x95, 0xfe, 0xca, 0x76, 0xe9, 0xad, 0xbc, 0x21, 0xb5, 0x78, 0x27, 0x55, 0x57, 0x9b, 0x21, 0xcb, 0x27, 0x25, 0x5d, 0x88, 0x04, 0x86, 0x07, 0x6d, 0x0d, 0xca, 0xe6, 0xfd, 0x56, 0x57, 0x8a, 0x1b, 0x2d, 0xa5, 0x8c, 0x51, 0x43, 0x4a, 0x19, 0xd5, 0xa5, 0x83, + 0x19, 0x29, 0xc5, 0x59, 0x57, 0x9b, 0xe9, 0x63, 0xa4, 0x85, 0xf1, 0x1a, 0xcf, 0xc7, 0x6a, 0x49, 0xd2, 0x7b, 0x07, 0x06, 0xe2, 0xf3, 0x04, 0xa3, 0x5e, 0xe0, 0xac, 0x3a, 0x4d, 0x36, 0xf0, 0xee, 0x4c, 0xb1, 0x86, 0xca, 0x6a, 0xe3, 0x6e, 0x60, 0xa0, 0xbb, 0xae, 0x9a, 0x67, 0xa5, 0x83, 0x71, 0x95, 0x79, 0xdb, 0x58, 0x37, 0x2b, 0x49, 0x23, 0xa3, 0x96, 0x44, 0xb9, 0x7b, 0x19, 0xf7, 0xfd, 0x60, 0xf8, 0xe5, 0x55, 0x63, 0x63, 0xe0, 0xbe, 0xbe, 0x4f, 0x2a, 0xea, 0x0b, 0xa5, 0x34, 0xeb, 0x61, 0x3c, 0x31, 0x8b, 0x86, 0xbe, 0xbd, 0xd5, 0x8c, 0x38, 0xb2, 0xa7, 0x32, 0xd8, 0x0f, 0x19, 0x1a, 0xb9, 0xf9, 0xb6, 0x94, 0x35, 0x2e, 0x88, 0x87, 0xb5, 0xbe, 0xa3, 0x7e, 0x47, 0xb2, 0x18, 0x73, 0x25, 0xd2, 0xdd, 0x07, 0xb1, 0x66, 0xa7, 0x18, 0x2f, 0x07, 0xd7, 0xe4, 0xb2, 0x46, 0x6d, 0x29, + 0xab, 0xf7, 0x95, 0x18, 0x78, 0xa5, 0xe8, 0x5d, 0x03, 0x37, 0xdd, 0x3d, 0x90, 0x3e, 0x39, 0x70, 0x4e, 0x3b, 0xc2, 0x7b, 0xc8, 0xdd, 0x57, 0x64, 0x21, 0xee, 0x1b, 0x02, 0xc3, 0x3c, 0x9b, 0xe8, 0x6f, 0x9f, 0xba, 0x7f, 0xd0, 0x2b, 0x8a, 0xed, 0xa9, 0x4a, 0x2e, 0xb1, 0x47, 0xd0, 0xba, 0xf1, 0x4c, 0x5d, 0xe6, 0x7d, 0xc4, 0x5e, 0x40, 0xdf, 0x25, 0x99, 0xb4, 0x79, 0x52, 0x55, 0x1b, 0x22, 0x31, 0xda, 0x00, 0x89, 0xd4, 0x46, 0xb1, 0x5e, 0x44, 0x4a, 0x23, 0xde, 0x61, 0xa2, 0x0d, 0x0e, 0xfc, 0x68, 0x78, 0xe1, 0x57, 0x4c, 0x1a, 0x6a, 0xab, 0x58, 0xe3, 0xa2, 0xa5, 0xab, 0xfe, 0x16, 0x7b, 0x91, 0x9f, 0xd8, 0xdb, 0xdc, 0x90, 0x96, 0x2e, 0x82, 0xfb, 0x8c, 0x04, 0xa9, 0xcc, 0x9a, 0x68, 0xba, 0x7b, 0x0f, 0xf6, 0x49, 0x85, 0xd5, 0x2d, 0x81, 0x39, 0xfa, 0x24, 0x89, 0xd5, 0x46, 0xb2, + 0x46, 0x15, 0x93, 0xa7, 0xf5, 0x14, 0xe4, 0x8f, 0x42, 0x2e, 0x3c, 0xf4, 0x8c, 0xac, 0xe1, 0x55, 0xa4, 0x0c, 0x7b, 0xc5, 0x69, 0xee, 0xfb, 0xed, 0xe1, 0xde, 0xea, 0x11, 0xfc, 0xed, 0x9e, 0x8a, 0x7d, 0x8a, 0xbb, 0x37, 0x61, 0xdf, 0x37, 0x06, 0x1b, 0x5b, 0x04, 0x6d, 0x76, 0xf7, 0x7e, 0x87, 0xa5, 0x9c, 0x76, 0x2e, 0x64, 0xb7, 0x0b, 0x57, 0x77, 0xc0, 0xda, 0x5a, 0xcf, 0x7d, 0x07, 0xeb, 0x27, 0xf0, 0x77, 0xef, 0xe0, 0x5e, 0xb0, 0x15, 0x7b, 0xdd, 0xa6, 0xda, 0x99, 0xc0, 0x35, 0xdd, 0x2b, 0x1e, 0xf6, 0x4f, 0x63, 0xd3, 0xf6, 0x51, 0xac, 0x99, 0xf9, 0x94, 0x93, 0xbc, 0x93, 0x97, 0xf2, 0xce, 0x19, 0x2a, 0x63, 0xc1, 0xb8, 0xd4, 0x7d, 0x56, 0x60, 0x42, 0x90, 0xa6, 0x67, 0xea, 0xfb, 0x8a, 0x77, 0x65, 0x33, 0xe3, 0x0e, 0xeb, 0xce, 0x19, 0x9e, 0xa9, 0x34, 0xb0, 0xd7, 0x0a, 0x62, + 0x8a, 0x1c, 0x06, 0x2a, 0x31, 0x70, 0x75, 0x9a, 0xaa, 0xf5, 0x96, 0x3a, 0xca, 0xc9, 0x40, 0x7d, 0x50, 0x4f, 0x2d, 0x1b, 0x68, 0x0b, 0x6d, 0x56, 0xf5, 0x7d, 0xec, 0x78, 0x8f, 0x1c, 0xd8, 0x28, 0x6d, 0x78, 0x5f, 0x0f, 0x64, 0x2d, 0xce, 0xe2, 0xee, 0xad, 0x28, 0xd3, 0x69, 0x07, 0x88, 0xc3, 0x4c, 0x7c, 0x3f, 0x1a, 0x5f, 0xe2, 0x77, 0x6d, 0x3c, 0x6b, 0xe5, 0xb1, 0x40, 0x01, 0xf5, 0x18, 0x39, 0x7b, 0x9c, 0xfd, 0xd0, 0x4a, 0xf6, 0x10, 0x87, 0xc8, 0x9b, 0xde, 0xd8, 0x51, 0x11, 0x1d, 0xca, 0x05, 0xda, 0xea, 0xba, 0x98, 0xea, 0xcf, 0x41, 0x3f, 0xb4, 0x34, 0x23, 0x40, 0x47, 0x9e, 0x8d, 0x4c, 0xd2, 0x92, 0x75, 0xab, 0x90, 0x6e, 0x4a, 0x11, 0xe3, 0x13, 0x29, 0xe2, 0x59, 0xc2, 0xbe, 0x33, 0x9b, 0x8c, 0x37, 0xf2, 0xc8, 0x44, 0xbd, 0xa8, 0xf4, 0xc6, 0xa6, 0xe1, 0x41, 0x68, 0x92, + 0xd5, 0x88, 0x65, 0x5f, 0xd1, 0x48, 0x9a, 0x50, 0x2f, 0xaf, 0xb9, 0xf9, 0xfe, 0x34, 0xbc, 0xba, 0x4b, 0x76, 0xad, 0x1d, 0x36, 0xcc, 0x64, 0x5f, 0xf6, 0x19, 0xef, 0xd3, 0x01, 0x92, 0x41, 0xbb, 0x4e, 0x3b, 0x4a, 0x6a, 0xeb, 0x2b, 0xf0, 0xdf, 0xd7, 0x52, 0x81, 0x7d, 0x7b, 0x73, 0x72, 0xb9, 0x15, 0x7b, 0xfd, 0x96, 0x9e, 0x4f, 0xe9, 0x3b, 0xc4, 0xbb, 0x6a, 0x31, 0xb9, 0x0b, 0x98, 0xe7, 0xda, 0x1f, 0x19, 0x44, 0x63, 0xc9, 0xce, 0xfb, 0x62, 0xb4, 0x7e, 0x94, 0x77, 0x5c, 0x75, 0xe6, 0x1c, 0x92, 0x04, 0x64, 0x65, 0x31, 0x2e, 0xb3, 0x8f, 0x9b, 0x46, 0x1e, 0x0c, 0x44, 0x6e, 0x17, 0xde, 0x23, 0x4d, 0x88, 0x6b, 0x6e, 0x64, 0x2b, 0x92, 0x39, 0xe8, 0xeb, 0x9d, 0xc4, 0xb3, 0xa1, 0xe4, 0x51, 0xaf, 0x4b, 0x46, 0xed, 0x53, 0xce, 0x38, 0xef, 0xa2, 0xc7, 0x57, 0xe2, 0xd3, 0x8a, 0x52, + 0x0e, 0x0a, 0xa2, 0xba, 0xa6, 0x4b, 0x05, 0xad, 0xb2, 0x24, 0x2b, 0x1d, 0xa5, 0x0b, 0xef, 0xf6, 0xdc, 0xe4, 0x73, 0x32, 0xef, 0xe3, 0x64, 0xed, 0x2a, 0xa0, 0x5f, 0xcb, 0xc2, 0x5e, 0x33, 0x5e, 0x2a, 0xa8, 0xcb, 0xa4, 0x84, 0xba, 0x8a, 0xb1, 0xb7, 0xc1, 0x30, 0xc9, 0xa3, 0x85, 0xf1, 0xae, 0x4a, 0xe0, 0x5d, 0x12, 0xc5, 0x3e, 0x93, 0xbd, 0x27, 0xb9, 0x5b, 0x96, 0x75, 0xa4, 0x8c, 0xf2, 0x93, 0x94, 0x53, 0xae, 0x8b, 0x8f, 0x3d, 0x6a, 0xcd, 0xe0, 0x7e, 0x36, 0x0b, 0x7b, 0xd8, 0xde, 0x52, 0x46, 0xed, 0xc3, 0xf8, 0x0b, 0xac, 0x1d, 0x63, 0xc4, 0x5d, 0x9b, 0x72, 0xa9, 0xbd, 0x18, 0x8b, 0x91, 0x0c, 0x6a, 0x7b, 0xe2, 0xc2, 0x7a, 0xa1, 0xda, 0x62, 0xf3, 0xde, 0x6f, 0xe0, 0xd6, 0xd9, 0xaf, 0x8c, 0x35, 0x16, 0xe0, 0xfb, 0x11, 0x12, 0xa9, 0xe7, 0x23, 0x7e, 0x27, 0xd0, 0xe1, 0x04, + 0xfb, 0xbd, 0x63, 0x3c, 0xaf, 0xe7, 0xa5, 0xbc, 0x3a, 0x81, 0x18, 0xbf, 0xca, 0x73, 0xe4, 0xf2, 0x0f, 0x67, 0xff, 0xdb, 0x53, 0x4a, 0x21, 0xd7, 0x87, 0x0d, 0x86, 0xbb, 0xef, 0x55, 0x9e, 0x61, 0x8f, 0xdf, 0x5a, 0x9a, 0xb0, 0x67, 0x6e, 0xa1, 0xf4, 0x95, 0xca, 0xca, 0xc8, 0xc0, 0xe9, 0x60, 0x3b, 0x5e, 0x62, 0x39, 0xb7, 0x05, 0xf7, 0xce, 0xa0, 0x91, 0xda, 0x23, 0xb0, 0x3d, 0xb8, 0x77, 0xee, 0x27, 0x2d, 0xdc, 0x7d, 0xb4, 0x2b, 0x5b, 0xf9, 0x51, 0x4a, 0x28, 0xc5, 0x02, 0x3b, 0x94, 0x3d, 0x52, 0x40, 0x9d, 0xcd, 0x9e, 0x7c, 0x2a, 0xef, 0xd0, 0x91, 0xd2, 0x5e, 0x6d, 0x2e, 0xb1, 0xee, 0x7e, 0x9a, 0x33, 0x64, 0x71, 0x75, 0x84, 0x14, 0x57, 0x2e, 0x04, 0x7e, 0x57, 0x67, 0xf0, 0x3e, 0x1d, 0xcf, 0x5e, 0x68, 0x83, 0xe4, 0x51, 0xf6, 0x4b, 0x49, 0xb5, 0x1e, 0xef, 0xe8, 0x79, 0x72, + 0x58, 0x0f, 0x97, 0xc3, 0xec, 0x8f, 0x7a, 0xb2, 0x7f, 0x4f, 0x02, 0x89, 0x6a, 0x11, 0x89, 0x04, 0x55, 0x35, 0x3f, 0xfb, 0x69, 0xa0, 0xfc, 0xca, 0x1e, 0xe7, 0x33, 0xd9, 0xeb, 0x82, 0x75, 0x73, 0xd6, 0xa3, 0x60, 0x59, 0x9f, 0xa5, 0x26, 0xc2, 0xeb, 0x07, 0x79, 0xe5, 0x49, 0xb0, 0x07, 0x99, 0xe1, 0x82, 0xf3, 0x47, 0x69, 0x77, 0xdd, 0xd5, 0x6f, 0x4b, 0x4e, 0xf6, 0x14, 0x29, 0xc6, 0x58, 0x9e, 0xe3, 0xd1, 0xac, 0xbf, 0xaf, 0x51, 0x7f, 0x95, 0x5c, 0x99, 0x4d, 0x7e, 0xb3, 0x87, 0xd6, 0xd6, 0xb3, 0x7f, 0xdb, 0xc0, 0xd9, 0xee, 0x1e, 0xeb, 0xd3, 0x4a, 0xce, 0x79, 0x1f, 0x48, 0x38, 0xfb, 0xe3, 0x51, 0x7a, 0xcf, 0xc0, 0xe5, 0xe0, 0x7e, 0x78, 0x26, 0x75, 0x9e, 0x47, 0xed, 0xae, 0x64, 0x37, 0xca, 0x91, 0x2f, 0x1b, 0xc5, 0xab, 0x2d, 0x0f, 0xae, 0x6d, 0x51, 0x3a, 0xe7, 0x42, 0xf6, + 0x6d, 0xab, 0x3c, 0x55, 0xe0, 0x19, 0x25, 0x85, 0xc9, 0xd9, 0xc2, 0xfa, 0x6f, 0xac, 0xd5, 0xb3, 0x24, 0xbd, 0xb1, 0x82, 0xbe, 0xb9, 0x20, 0x45, 0x0a, 0x7b, 0xe2, 0x24, 0x9f, 0xa7, 0x2d, 0xe7, 0xd3, 0x3b, 0x8c, 0x8d, 0x60, 0x6f, 0x3c, 0x9a, 0xbc, 0xd8, 0x20, 0xf9, 0x8d, 0x0c, 0x92, 0xd1, 0x78, 0x46, 0x32, 0x98, 0x85, 0x24, 0x9f, 0x31, 0x53, 0xe2, 0xdd, 0x7d, 0x31, 0xef, 0xdf, 0x14, 0xcf, 0x06, 0xce, 0x01, 0xc0, 0x98, 0x44, 0x5d, 0x61, 0x1f, 0xec, 0x9e, 0x67, 0x1b, 0xa4, 0xee, 0x9b, 0xcd, 0xf9, 0x94, 0xee, 0x7b, 0x82, 0xb5, 0xdd, 0x7d, 0x17, 0xb0, 0x77, 0x4a, 0x41, 0x6e, 0x21, 0xa3, 0x8f, 0x64, 0xe0, 0x1d, 0x5d, 0x9d, 0x7d, 0x7d, 0x25, 0x4f, 0x49, 0x90, 0x51, 0x72, 0xf3, 0x1e, 0xcf, 0x65, 0xfa, 0x02, 0x7f, 0x98, 0xdd, 0x99, 0x73, 0x13, 0x99, 0xf7, 0x25, 0xaf, 0xfb, + 0x7e, 0xe1, 0x3d, 0xf2, 0x87, 0xfe, 0x61, 0x70, 0xbf, 0x1c, 0xa5, 0x2d, 0x90, 0x12, 0xda, 0x17, 0xac, 0x45, 0xcb, 0xf0, 0xd3, 0xd8, 0xe0, 0x33, 0x39, 0x4d, 0xef, 0x8f, 0x6d, 0x57, 0xa0, 0x65, 0x8f, 0x6c, 0x7c, 0xca, 0x58, 0x17, 0xfa, 0x4e, 0xe2, 0x83, 0x00, 0xfb, 0x67, 0x4b, 0x32, 0xe9, 0x99, 0x24, 0x1b, 0xcf, 0x51, 0x16, 0xde, 0x11, 0x79, 0x59, 0x87, 0x9b, 0xb1, 0xc6, 0x35, 0x65, 0x7d, 0x98, 0x66, 0xe4, 0xe3, 0xbd, 0x06, 0xd8, 0x13, 0x4e, 0xc3, 0x0f, 0x12, 0x7c, 0xbe, 0x58, 0xc3, 0x75, 0x87, 0xb3, 0xc5, 0x37, 0x94, 0xac, 0x95, 0xda, 0xe8, 0xc0, 0x6f, 0x41, 0x7f, 0xd6, 0x66, 0xdd, 0x5a, 0xc8, 0xfe, 0xb2, 0x31, 0x67, 0x96, 0xb2, 0x52, 0x1e, 0x7d, 0x5b, 0x18, 0x45, 0x39, 0xf3, 0x7d, 0x42, 0x8c, 0xea, 0xb0, 0x87, 0xcf, 0x0e, 0x7d, 0x3c, 0x7c, 0x17, 0xf2, 0x1c, 0xb9, + 0x6b, 0xeb, 0xf7, 0xec, 0xe9, 0xf7, 0x41, 0x93, 0x24, 0x1d, 0x8d, 0x51, 0x9c, 0x83, 0xa6, 0x4a, 0x3d, 0x50, 0x81, 0xf7, 0xcc, 0xd3, 0xee, 0x3e, 0xd5, 0x1c, 0x2f, 0x1f, 0x5a, 0xcd, 0xa5, 0xa3, 0x67, 0xad, 0x1c, 0x45, 0x56, 0x53, 0xde, 0x67, 0x4d, 0xed, 0x59, 0x9c, 0x45, 0xbf, 0x09, 0xee, 0x1b, 0xc3, 0x99, 0xdb, 0x9e, 0x78, 0x76, 0xd3, 0xf7, 0xc8, 0xd3, 0x46, 0x77, 0x69, 0xef, 0x19, 0x8c, 0x0d, 0xab, 0x64, 0x13, 0x71, 0x70, 0xf7, 0xcd, 0x73, 0xf1, 0xcf, 0x70, 0xca, 0x45, 0x5a, 0x11, 0xa5, 0x92, 0x56, 0x24, 0xf0, 0x93, 0x71, 0x26, 0xf0, 0x91, 0x71, 0x46, 0xdd, 0x9c, 0x8a, 0xb4, 0x7a, 0xe0, 0x23, 0x6b, 0x18, 0xef, 0xe1, 0x82, 0xf0, 0x24, 0x4e, 0x9c, 0x25, 0x12, 0x8d, 0xe6, 0x81, 0x0b, 0xc6, 0x52, 0xde, 0x55, 0x13, 0xe5, 0x29, 0xcf, 0x10, 0xf6, 0xd0, 0xcd, 0x59, 0x4f, + 0xe2, 0xb1, 0x63, 0x8a, 0xd4, 0xd1, 0x7b, 0x05, 0x6e, 0xe9, 0x33, 0x65, 0xa0, 0xb6, 0x57, 0xf2, 0x98, 0x5d, 0xa5, 0x85, 0xd5, 0x43, 0xba, 0x98, 0x83, 0x88, 0xc5, 0x38, 0x62, 0xf2, 0x39, 0x31, 0x7d, 0x56, 0x72, 0x58, 0x94, 0xac, 0xfb, 0xb9, 0x9d, 0xba, 0xe8, 0xb4, 0x4a, 0x12, 0xac, 0x9e, 0xd8, 0x76, 0x57, 0x9e, 0xc6, 0xfe, 0x81, 0xc6, 0xc0, 0xe0, 0x7a, 0x6b, 0x7a, 0xca, 0xe0, 0xdb, 0xcc, 0xf8, 0xe5, 0x3b, 0xd6, 0xbb, 0x41, 0x92, 0x0b, 0x1d, 0x9a, 0xeb, 0x37, 0x79, 0x47, 0xe4, 0x96, 0x36, 0xbc, 0x0b, 0x9b, 0xf3, 0x3c, 0xe4, 0x08, 0x9d, 0x43, 0xdd, 0x3d, 0x5f, 0x3c, 0x7b, 0x8e, 0xf7, 0xc1, 0x41, 0xfa, 0x97, 0xbb, 0x60, 0xaf, 0xd9, 0xdc, 0x7a, 0x4d, 0x1a, 0xfc, 0x4f, 0xc3, 0xfd, 0x8d, 0x00, 0x9f, 0x75, 0x03, 0x1d, 0x3c, 0x4d, 0xd8, 0x63, 0x2b, 0xf2, 0x6c, 0x68, 0xff, + 0x1d, 0x0b, 0x3a, 0xb3, 0xcf, 0x6d, 0xcd, 0x7e, 0xa4, 0xad, 0x71, 0x1c, 0x3d, 0x47, 0x48, 0x0b, 0xbb, 0x80, 0xb4, 0x41, 0xaf, 0x07, 0x5a, 0x32, 0xfb, 0xa7, 0xcb, 0x9c, 0x41, 0xdd, 0x33, 0x4e, 0x45, 0x19, 0xa7, 0xb6, 0x64, 0x3f, 0xde, 0x52, 0x5e, 0xe0, 0x1c, 0xd7, 0x07, 0xbc, 0xc0, 0x3a, 0xe0, 0x9e, 0x67, 0x0e, 0x85, 0x30, 0x42, 0xad, 0x22, 0x4b, 0x38, 0x67, 0xb5, 0x55, 0x4b, 0x73, 0x8e, 0x2a, 0x2d, 0xf5, 0xd5, 0xe7, 0x38, 0x0f, 0x3d, 0x27, 0x0d, 0xd4, 0x81, 0xd2, 0x10, 0x34, 0xd1, 0x8e, 0x05, 0xae, 0xba, 0x67, 0x1b, 0x9e, 0x93, 0xee, 0xe4, 0x6e, 0x7b, 0x7d, 0x81, 0x74, 0x31, 0xb2, 0x48, 0x5b, 0x9e, 0xfb, 0xe6, 0x7a, 0x75, 0xf6, 0x53, 0xe4, 0x87, 0x51, 0x9e, 0x3d, 0xcc, 0xdb, 0xc4, 0xe6, 0x7b, 0xf6, 0x48, 0x4d, 0xd9, 0x5b, 0x1d, 0xe6, 0xdc, 0x12, 0xcd, 0xb3, 0x1e, + 0x2d, 0x43, 0xc0, 0x08, 0xd0, 0x04, 0xd4, 0x04, 0x2d, 0x40, 0x25, 0xfd, 0x2b, 0xfc, 0x79, 0x0f, 0x1e, 0x19, 0x78, 0xd7, 0xf4, 0x20, 0x26, 0xe3, 0xc8, 0xa9, 0xce, 0xbc, 0x7b, 0xdc, 0xf1, 0x05, 0xd8, 0x7c, 0x1c, 0x39, 0xd1, 0x8c, 0x47, 0xcb, 0x73, 0x60, 0x61, 0xa8, 0x9c, 0x03, 0x56, 0x83, 0x37, 0xc1, 0x7b, 0xa1, 0x33, 0x8e, 0xdb, 0x3f, 0x11, 0xf4, 0x06, 0xee, 0xd9, 0xa3, 0xdc, 0x13, 0x67, 0x90, 0x78, 0xd0, 0x36, 0xd4, 0x7e, 0x3a, 0x74, 0x06, 0x71, 0xcf, 0x31, 0x35, 0x41, 0xb1, 0xd0, 0x39, 0x24, 0x26, 0xd4, 0x1f, 0x05, 0x6d, 0x34, 0xa8, 0xf0, 0x37, 0x7c, 0x12, 0x40, 0x9b, 0xd0, 0x59, 0x26, 0x22, 0x74, 0xee, 0x79, 0xf2, 0xec, 0xd3, 0x21, 0x14, 0x9b, 0x47, 0xf9, 0x37, 0x0e, 0xf1, 0xaf, 0x05, 0x8a, 0xc2, 0xa3, 0x4a, 0xe8, 0x4c, 0x54, 0x2e, 0xc4, 0xb3, 0xad, 0xba, 0x83, + 0x18, 0xa5, 0xce, 0x75, 0x75, 0x48, 0x7e, 0x64, 0x6e, 0xc3, 0xd0, 0x5c, 0xb7, 0x5d, 0x04, 0x54, 0xf8, 0x1b, 0xdb, 0x5c, 0xf9, 0x7d, 0x43, 0x7a, 0x75, 0x02, 0x0d, 0x42, 0xb2, 0xb2, 0x3f, 0x72, 0xc6, 0x7a, 0xf4, 0xac, 0x95, 0x26, 0xa7, 0x73, 0x88, 0xce, 0x95, 0xd3, 0x22, 0x54, 0x77, 0xfb, 0x8b, 0xc1, 0xb3, 0x22, 0xa8, 0xa4, 0xfe, 0x2a, 0x33, 0x25, 0x99, 0xb5, 0xa2, 0x98, 0xc4, 0x79, 0xb2, 0xb1, 0x66, 0x02, 0xf7, 0x3d, 0x2c, 0x72, 0xcf, 0xeb, 0x42, 0xed, 0xc4, 0xfb, 0xc7, 0x3d, 0x9b, 0x24, 0x07, 0x1e, 0xa8, 0xe5, 0x28, 0x0b, 0xb2, 0x17, 0x4a, 0x0e, 0xee, 0x31, 0x9b, 0x6b, 0xf3, 0x59, 0x43, 0xf7, 0xca, 0xd3, 0xc1, 0x3d, 0xd4, 0x76, 0xce, 0xee, 0xeb, 0xa4, 0x98, 0x56, 0x5f, 0x3a, 0x6a, 0xad, 0x38, 0x87, 0x26, 0x4a, 0x27, 0xf6, 0x20, 0x6d, 0xb5, 0xe6, 0xd2, 0x41, 0x2b, + 0x01, 0x06, 0x49, 0x15, 0x2d, 0x47, 0x30, 0x77, 0x2f, 0x82, 0x9a, 0xee, 0xb9, 0x82, 0x58, 0x2e, 0xe5, 0xfd, 0xd5, 0x86, 0x77, 0x4e, 0x1b, 0x75, 0x2f, 0x7b, 0x80, 0xd4, 0x33, 0x48, 0x91, 0x10, 0x9a, 0xa4, 0xfe, 0xb6, 0x13, 0xfc, 0x8d, 0xc8, 0x3d, 0xf3, 0xcc, 0x01, 0x53, 0xc0, 0xca, 0x50, 0x39, 0x0b, 0xa4, 0x0b, 0x21, 0x3e, 0x75, 0x4e, 0xf0, 0xf7, 0xa3, 0xe1, 0x9c, 0x5a, 0xba, 0x70, 0xbe, 0x13, 0xde, 0x61, 0x92, 0x56, 0xaa, 0x8b, 0x45, 0x78, 0xa7, 0x8a, 0xd2, 0x14, 0xb9, 0x53, 0x29, 0xb7, 0xa0, 0xc0, 0x1f, 0x60, 0x0c, 0xe3, 0xbe, 0xc0, 0x35, 0xe6, 0x19, 0x69, 0xb2, 0x79, 0xde, 0x4a, 0x5a, 0x6f, 0x4b, 0x47, 0x6b, 0xb4, 0xd4, 0x77, 0xcf, 0x89, 0x50, 0xdd, 0x36, 0x4a, 0xcb, 0xb1, 0x54, 0xdd, 0x1f, 0x64, 0x74, 0xf9, 0xa8, 0x19, 0x64, 0x12, 0xef, 0x5c, 0xb1, 0xd6, 0x4a, + 0xbd, 0x20, 0xfe, 0x17, 0xd6, 0x06, 0xdd, 0x8f, 0xdf, 0x4a, 0xcb, 0x24, 0x17, 0xd4, 0xfb, 0x52, 0x8e, 0x4d, 0x8d, 0x8b, 0xfb, 0xe7, 0x5e, 0xb1, 0xb4, 0x5f, 0xa5, 0x1f, 0xf9, 0x1d, 0xcd, 0xb5, 0xbf, 0x7b, 0xe8, 0x37, 0x34, 0x6f, 0xe8, 0x77, 0x96, 0xb4, 0xdf, 0xd6, 0x5a, 0xa5, 0xfd, 0xf6, 0x86, 0xbd, 0xe7, 0xf1, 0xf7, 0xc4, 0x90, 0x7f, 0xdd, 0xb1, 0x0e, 0x9a, 0xef, 0xc1, 0x0f, 0xf4, 0xbf, 0x84, 0x8d, 0xf7, 0x42, 0x7d, 0x0d, 0x42, 0x73, 0x5d, 0xfa, 0x8b, 0x21, 0x9e, 0xf0, 0x0f, 0xac, 0x0c, 0xfd, 0x36, 0x97, 0x2a, 0xf3, 0x92, 0xb8, 0xe7, 0xc9, 0x57, 0x42, 0x48, 0x3d, 0x1b, 0xba, 0xe7, 0xcc, 0xc7, 0xcf, 0x9f, 0xf3, 0xf4, 0x6f, 0xd9, 0xe7, 0xbb, 0x48, 0x94, 0x0b, 0xa1, 0xf8, 0xbd, 0x0d, 0x66, 0x30, 0x56, 0xc3, 0x68, 0x23, 0x0b, 0x5d, 0x84, 0xf4, 0x7f, 0x14, 0xc3, 0xd3, + 0x60, 0x54, 0xe1, 0xac, 0x16, 0xfa, 0x9d, 0x96, 0xf6, 0x28, 0xf7, 0x37, 0x40, 0xf7, 0x37, 0x57, 0x6d, 0x10, 0x67, 0x9e, 0x39, 0xb2, 0xd5, 0x30, 0x39, 0x5b, 0x4f, 0x96, 0x41, 0xc4, 0xd6, 0xcd, 0xb1, 0xdd, 0xb8, 0x65, 0x58, 0x2a, 0x02, 0xef, 0x07, 0x7f, 0x13, 0x5e, 0x9b, 0x0a, 0xf7, 0x37, 0x5d, 0x17, 0x5a, 0x7c, 0xe0, 0xae, 0xb1, 0x55, 0xda, 0x19, 0x8b, 0xc1, 0x46, 0xb0, 0x2e, 0x58, 0xb6, 0xe1, 0xcc, 0xd8, 0x86, 0x7d, 0x78, 0x1d, 0xde, 0x73, 0x6d, 0x8d, 0x8f, 0x41, 0x02, 0xeb, 0x72, 0x1d, 0x49, 0x50, 0x73, 0x07, 0x36, 0xe9, 0x3e, 0xf1, 0xe9, 0x85, 0x38, 0x37, 0x0d, 0x24, 0xf7, 0x53, 0xf5, 0xc3, 0x67, 0x72, 0xc4, 0xdd, 0x67, 0xb9, 0xbf, 0xb9, 0xbb, 0xcf, 0x85, 0xeb, 0x73, 0x3d, 0x39, 0x30, 0x3f, 0xf8, 0x8c, 0x94, 0xa6, 0xef, 0x19, 0x19, 0xab, 0x3d, 0xc7, 0xbe, 0xd5, + 0xfd, 0xcd, 0x7b, 0x34, 0x6d, 0x72, 0x5e, 0xe3, 0x19, 0x34, 0xd6, 0xc8, 0x61, 0xf6, 0xf9, 0xd1, 0xda, 0x26, 0x99, 0x1e, 0xfc, 0xfd, 0xaf, 0x82, 0xb4, 0x0f, 0xd2, 0xec, 0x63, 0x9d, 0xad, 0xcc, 0x7b, 0x16, 0x7b, 0xd5, 0x32, 0xf2, 0x32, 0xfc, 0x22, 0xdc, 0xbd, 0x9d, 0xbb, 0xc7, 0xd3, 0xde, 0xe3, 0xac, 0x51, 0x5f, 0x2a, 0x98, 0x9b, 0xd8, 0xaf, 0x26, 0x07, 0x16, 0x68, 0xdd, 0x02, 0xbf, 0x6b, 0xcf, 0x06, 0x3e, 0xd2, 0xfa, 0xc0, 0xff, 0xa2, 0xf4, 0x52, 0x9e, 0x92, 0x9a, 0x0a, 0xfb, 0xaa, 0xe0, 0x6f, 0xb2, 0x85, 0xc4, 0x62, 0xaf, 0x19, 0xa7, 0xd6, 0x97, 0xcc, 0x2a, 0xe7, 0x15, 0xf6, 0xb7, 0x25, 0xd3, 0xf8, 0x90, 0xc3, 0xfd, 0x5d, 0x84, 0x7e, 0xa3, 0x6d, 0x1d, 0x82, 0xeb, 0xe7, 0x1e, 0xf8, 0x6b, 0x4a, 0x5a, 0x3b, 0xf8, 0xdb, 0x83, 0xc3, 0x5e, 0x78, 0x6a, 0x30, 0xc7, 0x76, + 0x85, 0x62, 0xb9, 0xd2, 0x3d, 0xa7, 0xf3, 0x9e, 0x18, 0xcc, 0x39, 0x6a, 0xb0, 0x7b, 0xde, 0x52, 0xef, 0xb2, 0x9f, 0x3f, 0x29, 0xe1, 0xd0, 0xb7, 0x25, 0x2f, 0x03, 0x94, 0xf7, 0xc0, 0x8f, 0xa0, 0x3c, 0x88, 0x0a, 0x95, 0x85, 0x41, 0x83, 0x50, 0x3b, 0x1a, 0x14, 0x01, 0xcd, 0x40, 0x6b, 0xd0, 0x0e, 0x54, 0x07, 0xab, 0x5c, 0x99, 0x80, 0x3c, 0xbc, 0x3f, 0x3a, 0xc4, 0xe3, 0x17, 0x70, 0x1c, 0xf0, 0x2e, 0xbe, 0x5f, 0x11, 0xd4, 0x49, 0xc5, 0x03, 0xb4, 0xba, 0xff, 0x0e, 0x58, 0x43, 0xbd, 0x4f, 0x48, 0x46, 0xf9, 0x10, 0xaf, 0x06, 0xa9, 0xbc, 0x1f, 0x58, 0x21, 0x44, 0x85, 0xe6, 0xbb, 0xfd, 0x53, 0xc0, 0x40, 0xd0, 0x0d, 0x8c, 0x07, 0xfd, 0x52, 0xeb, 0xf7, 0xae, 0xa4, 0xc2, 0xd5, 0xed, 0xc1, 0x07, 0x7a, 0x5b, 0x79, 0xe5, 0x51, 0xb0, 0x46, 0xf5, 0xa1, 0xfc, 0x0b, 0xe8, 0xef, 0x40, + 0xf9, 0x17, 0xd0, 0xdf, 0x8e, 0xf2, 0x2f, 0x70, 0xd7, 0x3a, 0xca, 0xbf, 0x80, 0xfe, 0xbe, 0x94, 0x7f, 0x87, 0xff, 0x2d, 0x3d, 0xfe, 0x89, 0xfe, 0xff, 0x15, 0x3d, 0xfe, 0xc9, 0xcf, 0xff, 0x2b, 0x7a, 0x84, 0xf2, 0xf1, 0xe7, 0x50, 0x2e, 0xfe, 0xfc, 0xe4, 0xb3, 0x90, 0xfa, 0x1d, 0x24, 0xf8, 0x3d, 0x24, 0x1f, 0x34, 0x7a, 0x28, 0x0f, 0x1b, 0xa4, 0xe6, 0xa5, 0x9b, 0x9f, 0x69, 0xb9, 0x7a, 0xdf, 0x0e, 0xee, 0xed, 0x79, 0xee, 0x58, 0xbf, 0xca, 0xfe, 0x17, 0x72, 0xd5, 0x9d, 0xf7, 0x01, 0xf5, 0xaa, 0x8f, 0x3c, 0x5b, 0xaf, 0x83, 0xf5, 0x80, 0x37, 0xdc, 0xbd, 0x4f, 0xfe, 0x7c, 0x8e, 0x1e, 0xe4, 0x4a, 0xc5, 0xbd, 0xcc, 0xe0, 0x23, 0x00, 0xcf, 0xbb, 0xec, 0x6f, 0xef, 0xa1, 0xd3, 0xbd, 0xdb, 0x21, 0x1e, 0xe8, 0x74, 0x8f, 0x77, 0xdd, 0xbd, 0x32, 0x80, 0x13, 0xc3, 0xbd, 0x15, 0x80, + 0xfe, 0x7b, 0x2d, 0x42, 0xcf, 0xeb, 0xa3, 0x68, 0xf7, 0x38, 0xd2, 0xf4, 0x4a, 0x45, 0xf0, 0xfb, 0xe4, 0x7f, 0x15, 0xcd, 0x02, 0x81, 0xff, 0x88, 0xfe, 0x5f, 0xa1, 0x43, 0x5a, 0x3d, 0xd0, 0x0c, 0x54, 0x06, 0xcb, 0x43, 0xed, 0x06, 0xa0, 0xe2, 0x23, 0xed, 0x3b, 0x7f, 0x9d, 0xff, 0x2f, 0xe6, 0x60, 0x71, 0x10, 0xf4, 0xbb, 0xdf, 0x5c, 0x83, 0xa8, 0x13, 0xb8, 0x11, 0xfc, 0xf6, 0xea, 0xa2, 0xa0, 0xd8, 0x0f, 0xeb, 0x2e, 0xca, 0xb3, 0x47, 0x7e, 0x14, 0xee, 0xb7, 0xd9, 0xa6, 0xe2, 0x0b, 0xae, 0xef, 0x6e, 0x9b, 0x77, 0xc6, 0x93, 0x70, 0xf6, 0x49, 0x8a, 0x3d, 0x31, 0xf5, 0xfb, 0xed, 0x63, 0xdf, 0x70, 0xff, 0x05, 0x8c, 0x02, 0xe2, 0xf9, 0x37, 0x34, 0xf6, 0x13, 0xed, 0x17, 0x1e, 0xd6, 0xb7, 0x05, 0xde, 0xf9, 0xfb, 0x39, 0x81, 0xfc, 0xfa, 0xc4, 0x07, 0xe5, 0x00, 0x65, 0xa0, 0x10, + 0x65, 0x09, 0x10, 0xa6, 0x4f, 0x09, 0x34, 0xa3, 0x5d, 0xf9, 0xb1, 0xef, 0xca, 0xff, 0x84, 0xb4, 0xef, 0xc8, 0x12, 0xe8, 0x95, 0x56, 0xff, 0x2b, 0x02, 0xf9, 0x8d, 0xbb, 0x0f, 0xca, 0x01, 0xb7, 0x7c, 0x8e, 0x76, 0x21, 0xca, 0x12, 0x20, 0xcc, 0x6d, 0x33, 0xb7, 0x19, 0x7d, 0x65, 0xfe, 0x2b, 0xb4, 0x6a, 0x74, 0x60, 0x95, 0x1a, 0x2d, 0x33, 0x29, 0x4f, 0x87, 0xf0, 0x05, 0x6d, 0x8d, 0xf2, 0x0a, 0xe5, 0xf2, 0xe0, 0x77, 0xef, 0xe8, 0x40, 0x9b, 0x50, 0x7f, 0x4a, 0xa8, 0x4c, 0x00, 0xaf, 0x52, 0xff, 0x0c, 0x94, 0x4a, 0xa5, 0x0d, 0xfc, 0x96, 0x3a, 0x16, 0xc8, 0xa0, 0x46, 0x3f, 0x88, 0x04, 0x6e, 0x39, 0x98, 0x76, 0x76, 0xca, 0x7c, 0xc0, 0x1b, 0x6a, 0x57, 0x06, 0xe1, 0x8f, 0xc8, 0x48, 0x71, 0x61, 0xbc, 0x25, 0x29, 0xee, 0xb7, 0xf5, 0x20, 0xba, 0xa4, 0x7e, 0x63, 0xff, 0x3b, 0xe8, + 0x1e, 0xce, 0x3c, 0xee, 0xb7, 0xf7, 0x5c, 0x92, 0x35, 0x58, 0x7a, 0x24, 0x43, 0xa8, 0x4c, 0xe1, 0x2c, 0x9e, 0xe2, 0x31, 0x52, 0xbf, 0xc7, 0x3f, 0x89, 0x7f, 0xf6, 0xe5, 0xe3, 0xb0, 0x3b, 0x43, 0x7f, 0xf1, 0xcf, 0xef, 0xf8, 0xff, 0x12, 0xb1, 0x81, 0xe7, 0xff, 0x4b, 0x74, 0xff, 0x11, 0x02, 0xb5, 0x3d, 0x75, 0x1f, 0xb4, 0x05, 0x94, 0x81, 0x86, 0x94, 0x71, 0xa0, 0x06, 0xf5, 0xbe, 0xa9, 0x6d, 0x68, 0x62, 0x24, 0x21, 0x78, 0xb7, 0xe0, 0x51, 0x1c, 0x63, 0x8f, 0x95, 0x56, 0xdf, 0x01, 0xae, 0x3e, 0x86, 0x9e, 0x4f, 0xb4, 0x53, 0x1e, 0xbb, 0x8f, 0xf0, 0xef, 0xe0, 0xde, 0x57, 0x78, 0x12, 0x47, 0x02, 0x1f, 0x3c, 0xd9, 0xf7, 0xf0, 0x3e, 0xc3, 0xff, 0x34, 0x42, 0xeb, 0x8c, 0x7b, 0x47, 0xe2, 0xef, 0xe0, 0xde, 0x9b, 0x78, 0x14, 0xc1, 0x3b, 0x14, 0x4f, 0xe2, 0xa7, 0x07, 0x3f, 0xfc, + 0x6d, 0x7f, 0xea, 0xd8, 0x11, 0xfd, 0xa7, 0xfb, 0xdb, 0xc0, 0x2e, 0xea, 0x05, 0xc0, 0x09, 0x70, 0xf2, 0xe1, 0xf8, 0xc5, 0xc0, 0x3d, 0xf7, 0x4e, 0xc6, 0x3f, 0x21, 0xed, 0xae, 0xc6, 0x7f, 0x84, 0x4b, 0x81, 0xd1, 0xff, 0xf9, 0x1c, 0xf6, 0xe3, 0x25, 0xfe, 0xd2, 0x5f, 0x36, 0xed, 0x9e, 0x48, 0x1a, 0xd2, 0xbe, 0x1b, 0x04, 0xd1, 0x2d, 0x74, 0x77, 0xe4, 0x49, 0xbc, 0x15, 0xf8, 0x36, 0xed, 0x4e, 0xc9, 0xc3, 0xef, 0x0a, 0x69, 0x78, 0x3d, 0xb5, 0xb4, 0xec, 0xe0, 0xb7, 0x86, 0xe6, 0xff, 0x7d, 0x04, 0x0e, 0xfe, 0xa5, 0xaf, 0x98, 0xe4, 0xfa, 0x77, 0xf3, 0xdc, 0xbb, 0x2b, 0xea, 0x17, 0x8f, 0xdc, 0x55, 0x79, 0xfd, 0x91, 0x7b, 0x2a, 0x8f, 0xf4, 0x3d, 0xfc, 0x76, 0x52, 0x3c, 0x74, 0x17, 0xe5, 0xf6, 0xe3, 0x7d, 0xc1, 0xf3, 0x8b, 0x7b, 0x0f, 0xe5, 0x9f, 0xf0, 0xa4, 0xdd, 0x21, 0x18, + 0x0b, 0x9f, 0xe8, 0xeb, 0x14, 0x78, 0xf5, 0xe1, 0x7d, 0x94, 0x4e, 0x81, 0xd7, 0x28, 0xeb, 0x85, 0xea, 0xbf, 0x6a, 0x3f, 0x06, 0xef, 0xb4, 0x84, 0x10, 0x78, 0x4f, 0xcd, 0x21, 0x5d, 0x28, 0xcf, 0xd1, 0x9e, 0x4c, 0xb9, 0x0f, 0x7c, 0x0c, 0xbe, 0x03, 0x5f, 0x80, 0xf3, 0xca, 0xc9, 0xc0, 0x87, 0xa9, 0x6d, 0xa9, 0xe4, 0x8e, 0xab, 0x7a, 0xb0, 0xff, 0x90, 0xaa, 0x8b, 0xa3, 0x7c, 0xc6, 0xb9, 0x40, 0x97, 0x17, 0x82, 0x74, 0x17, 0x03, 0x35, 0xdd, 0xb3, 0x9a, 0x5a, 0x3a, 0xf0, 0x15, 0x73, 0x36, 0x07, 0xef, 0xca, 0xdc, 0x09, 0xbc, 0x9b, 0x7a, 0x57, 0x26, 0xb0, 0x2a, 0xed, 0xce, 0x4c, 0x10, 0xee, 0x9d, 0x18, 0x8f, 0x4c, 0x53, 0xce, 0x4a, 0x27, 0x30, 0x59, 0x8d, 0x92, 0x7c, 0x94, 0xcf, 0x2a, 0x67, 0x03, 0x9f, 0x82, 0xe3, 0x60, 0x87, 0x3a, 0x4c, 0x6a, 0x6a, 0x3f, 0xc8, 0x4c, 0xcf, + 0xf0, 0xe0, 0xf7, 0x9f, 0x3f, 0xf1, 0x36, 0x18, 0xc3, 0xbb, 0xad, 0xd3, 0x23, 0x77, 0x6e, 0xfe, 0x09, 0x5b, 0xa0, 0x6d, 0x1b, 0xba, 0xd7, 0xf0, 0x9f, 0x62, 0xa0, 0x5b, 0x72, 0xae, 0xfc, 0x6f, 0xcf, 0x0d, 0x95, 0x03, 0xff, 0x25, 0x6d, 0xdd, 0xc7, 0x72, 0x3f, 0x39, 0x70, 0xcb, 0xcd, 0x7f, 0xb7, 0x1e, 0xfc, 0x7e, 0x75, 0x46, 0x7a, 0x50, 0x4e, 0x50, 0xef, 0x49, 0xce, 0xd0, 0x37, 0xad, 0xe1, 0x41, 0x5c, 0xe0, 0xcc, 0x77, 0x46, 0xa6, 0xaa, 0x67, 0x02, 0x3f, 0xa6, 0xdd, 0x29, 0xfa, 0xb7, 0xd8, 0x26, 0x35, 0x82, 0x77, 0x8d, 0xdc, 0x7b, 0x46, 0x27, 0xa5, 0x98, 0xfa, 0x69, 0xea, 0xb7, 0xc6, 0x87, 0xf8, 0x35, 0xb0, 0xc1, 0xbd, 0x73, 0x14, 0xc4, 0x19, 0x79, 0xea, 0x61, 0xfd, 0xc9, 0xb6, 0x5b, 0xff, 0x1a, 0x50, 0x1a, 0x19, 0xa5, 0xa8, 0x8b, 0xb4, 0x31, 0xb3, 0x6d, 0x08, 0x6d, + 0xe4, 0xa9, 0x87, 0xf5, 0x27, 0xdb, 0x6e, 0x3d, 0x0d, 0x93, 0x68, 0x4f, 0xfa, 0x93, 0xee, 0xe1, 0xfd, 0xa6, 0xbf, 0x43, 0xc7, 0xe0, 0xb7, 0xc1, 0xb1, 0xff, 0x92, 0xe6, 0xef, 0xb0, 0xf2, 0x61, 0x7d, 0x98, 0x7b, 0x6f, 0x2a, 0x88, 0x1c, 0xa9, 0xdf, 0xf8, 0xfe, 0x09, 0xee, 0xbd, 0xaa, 0x34, 0xe8, 0xf5, 0x25, 0x8b, 0x7b, 0xc7, 0xea, 0xbf, 0x0b, 0xf7, 0xfb, 0xa1, 0x7b, 0x3f, 0xeb, 0x2f, 0x38, 0x2d, 0xd1, 0xc1, 0x7b, 0x5a, 0x7f, 0x73, 0x57, 0x4b, 0x8d, 0x79, 0xe4, 0xbe, 0x96, 0x8b, 0x81, 0x92, 0xa0, 0x2c, 0x91, 0x91, 0x4a, 0xcd, 0xc0, 0xdd, 0xe0, 0x9d, 0xad, 0x68, 0x69, 0x17, 0xbc, 0xb3, 0xd5, 0x33, 0xf8, 0xbd, 0x2a, 0x78, 0x67, 0x4b, 0xd9, 0x00, 0x7e, 0x96, 0x32, 0x8f, 0xdd, 0xd7, 0x62, 0x6f, 0xa2, 0xb6, 0x93, 0xf0, 0xe0, 0x9d, 0xad, 0x46, 0x92, 0xc3, 0xbd, 0xb3, 0x15, + 0xbc, 0x33, 0xe5, 0xde, 0x95, 0x72, 0xef, 0x49, 0xb5, 0x08, 0xec, 0xf5, 0x74, 0x91, 0xde, 0x9e, 0xd6, 0x81, 0x11, 0x9e, 0xd6, 0x0f, 0x66, 0x02, 0xb7, 0xfc, 0x9a, 0xf6, 0x38, 0xca, 0xe7, 0xc1, 0xd0, 0x50, 0x7b, 0xb5, 0xa7, 0x55, 0x60, 0x94, 0xfb, 0xdd, 0xfc, 0xdf, 0xd1, 0x06, 0xef, 0x5d, 0xfd, 0xff, 0x80, 0xe0, 0x5d, 0xae, 0x7f, 0x83, 0xe0, 0x3d, 0xaf, 0x0d, 0x52, 0x24, 0x58, 0xfe, 0x2f, 0xc1, 0xbd, 0x3f, 0xa6, 0xe6, 0x09, 0x6c, 0x50, 0xcb, 0x52, 0x4e, 0x91, 0xda, 0xb4, 0xc7, 0xa8, 0xb9, 0x03, 0x7f, 0x80, 0xef, 0xe9, 0x3f, 0x1d, 0x6a, 0xdf, 0x00, 0xdf, 0xa6, 0xb6, 0xa1, 0xcd, 0x1d, 0x48, 0x74, 0x7f, 0x6b, 0x4a, 0x9d, 0x2b, 0x15, 0xa8, 0xef, 0x05, 0x6f, 0x81, 0x0f, 0x8c, 0x5c, 0xe2, 0x0f, 0xde, 0x3b, 0x73, 0xd1, 0x86, 0xbc, 0xfd, 0x1f, 0x82, 0xfb, 0x5d, 0x3f, 0xed, + 0xbe, 0xda, 0xff, 0x26, 0xd2, 0xee, 0xbe, 0xb9, 0x77, 0xde, 0x1e, 0xbb, 0xf7, 0xf6, 0x28, 0x1a, 0xa5, 0xee, 0x57, 0xdd, 0x3b, 0x6f, 0x8f, 0x22, 0xf8, 0xdd, 0x2e, 0x53, 0xe8, 0x7e, 0xdb, 0xb3, 0x81, 0x40, 0xda, 0x1d, 0x36, 0xf7, 0xee, 0x5a, 0xf0, 0xfe, 0xda, 0x04, 0x29, 0xa3, 0x4d, 0xe7, 0x79, 0x4a, 0xbb, 0xc7, 0xf6, 0x24, 0x72, 0xca, 0xb4, 0xb4, 0xfb, 0x6c, 0xee, 0x7d, 0xb5, 0xe0, 0x1d, 0xb5, 0x77, 0xa5, 0x92, 0x7b, 0x97, 0x4d, 0x59, 0xc1, 0x33, 0xe1, 0xde, 0x3b, 0xdb, 0x22, 0x83, 0xf5, 0x23, 0x3c, 0x0b, 0x5b, 0xa4, 0x2b, 0xe7, 0xfe, 0xae, 0xca, 0x6b, 0xc1, 0x6f, 0xdc, 0x53, 0xdd, 0x3b, 0x64, 0xec, 0x45, 0xa6, 0x6b, 0xee, 0x3d, 0xde, 0x6c, 0xf2, 0x97, 0x3f, 0xc1, 0x7b, 0x65, 0x85, 0x65, 0x88, 0x67, 0x95, 0xd4, 0xd1, 0xa7, 0xca, 0x4b, 0xe0, 0x8a, 0x67, 0x2d, 0x7b, + 0xdb, 0x47, 0xb1, 0x2e, 0xf5, 0x7e, 0xd0, 0x93, 0x08, 0xde, 0x17, 0x7a, 0x12, 0xee, 0xfd, 0xa1, 0x27, 0x10, 0xfa, 0x5d, 0x6c, 0x73, 0xda, 0x7d, 0x20, 0x91, 0x54, 0xb8, 0xbf, 0xcb, 0x05, 0xef, 0x3c, 0x3f, 0x44, 0xe0, 0x73, 0xb0, 0x96, 0x7a, 0x7d, 0xd0, 0x34, 0xf5, 0x77, 0xbc, 0x3f, 0x11, 0xba, 0x2f, 0x54, 0x90, 0xb9, 0x0f, 0xe1, 0xde, 0x1f, 0xa2, 0x0c, 0x41, 0x16, 0xba, 0x70, 0x7f, 0xeb, 0x4b, 0xab, 0x87, 0xf0, 0xd2, 0x13, 0xed, 0x7e, 0xe0, 0x03, 0xf0, 0x56, 0xaa, 0x2e, 0x69, 0xbf, 0x57, 0x3c, 0x84, 0x4b, 0x1f, 0xbc, 0x57, 0x44, 0xf9, 0x08, 0x94, 0x08, 0x17, 0xee, 0xef, 0xcb, 0xb4, 0xd3, 0x90, 0x8f, 0xbe, 0xf1, 0xee, 0x3d, 0x23, 0xea, 0x41, 0xb8, 0xf7, 0x8d, 0x28, 0x83, 0x70, 0xef, 0x1d, 0x51, 0xa6, 0xe1, 0x2c, 0xb4, 0x0e, 0x65, 0xf0, 0xfe, 0x91, 0xb2, 0x55, 0x46, + 0xa4, 0xf9, 0x82, 0xbe, 0x5e, 0x20, 0x78, 0x17, 0x29, 0xa4, 0x9b, 0x8b, 0xe0, 0x9d, 0x24, 0xb7, 0x0c, 0xd5, 0xdd, 0x3b, 0xe5, 0x0f, 0xe1, 0xde, 0x51, 0xa2, 0x0c, 0x22, 0x44, 0x17, 0xbc, 0xab, 0x44, 0x19, 0x84, 0x7b, 0x67, 0x89, 0x32, 0x08, 0xf7, 0xee, 0x12, 0x65, 0x10, 0xee, 0x1d, 0xa6, 0xb4, 0x7e, 0xf7, 0x2e, 0x13, 0x65, 0x10, 0xee, 0x9d, 0xa6, 0x87, 0xfd, 0x9d, 0x1f, 0xa3, 0x71, 0xef, 0xcc, 0x07, 0xe1, 0xde, 0x75, 0xa2, 0x0c, 0xc2, 0xfd, 0x2d, 0x95, 0xf2, 0x21, 0xdc, 0xfb, 0x4f, 0x94, 0x41, 0xb8, 0xf7, 0xa0, 0x28, 0x83, 0x70, 0xef, 0x43, 0xa5, 0xf9, 0x20, 0xf5, 0x4e, 0x54, 0x2a, 0x9e, 0xa8, 0xa7, 0xc6, 0xd8, 0x71, 0x7d, 0xf4, 0x10, 0x59, 0x83, 0x77, 0x98, 0x5c, 0xac, 0x65, 0xed, 0x73, 0xef, 0xdf, 0xec, 0x91, 0x12, 0x6a, 0xa7, 0xc0, 0x0c, 0x2d, 0x73, 0xa0, 0xbe, + 0xb6, 0x3f, 0x70, 0x5f, 0x2b, 0x14, 0x98, 0xae, 0xe5, 0x95, 0x70, 0xf7, 0x2e, 0x88, 0x7b, 0x87, 0xc3, 0xbd, 0xbf, 0x91, 0x76, 0x77, 0xe3, 0xdf, 0xdd, 0xdb, 0xf8, 0xdb, 0xbb, 0x1a, 0xa1, 0xbb, 0x09, 0xc8, 0x4e, 0x72, 0xe1, 0xde, 0x49, 0xd0, 0x6f, 0x07, 0x24, 0x78, 0x8f, 0xe0, 0xf6, 0xfd, 0x43, 0x60, 0x87, 0x7e, 0xfb, 0x41, 0x69, 0xf0, 0x23, 0x38, 0x99, 0xda, 0xef, 0xe2, 0xc1, 0x27, 0x9c, 0x07, 0x83, 0xdf, 0xe6, 0x83, 0x6b, 0x40, 0xb1, 0xd4, 0xbb, 0x41, 0xee, 0x7e, 0x88, 0x9c, 0x0e, 0x7e, 0xa3, 0x81, 0x5f, 0x10, 0xee, 0x77, 0x1a, 0xf7, 0x0e, 0x71, 0xea, 0x3d, 0xe2, 0x3f, 0xeb, 0xee, 0xf7, 0x61, 0xf7, 0xbb, 0xaf, 0xfb, 0x3b, 0x8a, 0xbb, 0xe7, 0x77, 0x7f, 0x4b, 0x31, 0x1b, 0x89, 0xcf, 0xa8, 0x25, 0xa5, 0xdc, 0x4f, 0x25, 0x21, 0xe4, 0x4d, 0xbd, 0xd9, 0x9f, 0x65, 0x69, + 0xf0, 0xce, 0xbe, 0xdb, 0xd6, 0xb3, 0xb8, 0x77, 0xdd, 0x6e, 0xc9, 0x78, 0xf1, 0x04, 0xbf, 0x1c, 0x8c, 0x55, 0x9b, 0xab, 0x4f, 0xab, 0xbd, 0xd5, 0xe1, 0xea, 0x08, 0xf5, 0x39, 0x75, 0x92, 0xba, 0x50, 0x5d, 0xa5, 0x6e, 0xd4, 0xfa, 0x6b, 0x43, 0xb5, 0xb1, 0xda, 0x74, 0x6d, 0x86, 0xf6, 0x8a, 0xf6, 0x91, 0x3e, 0xc5, 0xc8, 0x9a, 0xa1, 0x66, 0xbe, 0x74, 0xf9, 0x32, 0xe4, 0x2b, 0x92, 0xaf, 0x68, 0xbe, 0x98, 0x7c, 0xab, 0xf2, 0xed, 0xce, 0xf7, 0x7e, 0xfe, 0x25, 0xf9, 0x97, 0xe7, 0xff, 0x3d, 0x3c, 0x7b, 0x78, 0xbe, 0xf0, 0xba, 0xe1, 0x4d, 0xc3, 0xdb, 0x85, 0x27, 0x84, 0x3f, 0x1d, 0xde, 0x31, 0x7c, 0x4c, 0xf8, 0xf6, 0xf0, 0x23, 0xe1, 0x9f, 0x86, 0x9f, 0x0e, 0xff, 0x25, 0xfc, 0x46, 0xf8, 0x03, 0x5f, 0x26, 0x5f, 0x01, 0x5f, 0x61, 0x5f, 0x69, 0x5f, 0x79, 0x5f, 0x15, 0x5f, 0x0d, + 0x5f, 0x1d, 0x5f, 0x27, 0xdf, 0x40, 0xdf, 0x48, 0xdf, 0x38, 0xdf, 0x2c, 0xdf, 0x3c, 0xdf, 0x1a, 0xdf, 0x26, 0xdf, 0x0e, 0xdf, 0x6e, 0xdf, 0xd7, 0x11, 0x46, 0x44, 0xd6, 0x88, 0x9c, 0x11, 0xbe, 0x88, 0x02, 0x11, 0x85, 0x23, 0x4a, 0x46, 0x94, 0x8d, 0x68, 0x16, 0xd1, 0x29, 0x62, 0x52, 0xc4, 0xa2, 0x88, 0xf5, 0x05, 0xd4, 0x02, 0x19, 0x0b, 0x64, 0x29, 0x90, 0xbd, 0x40, 0x9e, 0x02, 0xe1, 0x05, 0x8a, 0x16, 0x28, 0x5e, 0xa0, 0x41, 0x81, 0xa4, 0x02, 0x5d, 0x23, 0xd5, 0xc8, 0x4c, 0x91, 0x11, 0x05, 0x07, 0x17, 0xfc, 0xb1, 0xe0, 0x8d, 0x42, 0x6c, 0x0e, 0x0a, 0xa5, 0x2b, 0x94, 0xa9, 0x50, 0xb6, 0x42, 0xb9, 0x0a, 0xbd, 0x52, 0x68, 0x63, 0xa1, 0x63, 0x85, 0x8e, 0x17, 0xfa, 0xa1, 0xb0, 0x14, 0xae, 0x56, 0xb8, 0xe7, 0x53, 0xe3, 0xa2, 0xfa, 0x44, 0x0d, 0x2f, 0x99, 0x73, 0x5d, 0x9e, + 0x75, 0x11, 0x77, 0x02, 0x77, 0xf5, 0x07, 0x91, 0x0f, 0x02, 0x81, 0x40, 0x70, 0x31, 0xf3, 0xc9, 0x4a, 0x35, 0x4e, 0xed, 0xa0, 0xf6, 0xc1, 0xee, 0x91, 0xd8, 0x3d, 0x59, 0x5d, 0xa4, 0xae, 0x56, 0x37, 0x69, 0x43, 0xb4, 0x51, 0xda, 0x24, 0xec, 0x9e, 0xa5, 0xad, 0xd6, 0x4e, 0xe8, 0x53, 0xb1, 0x5b, 0xf2, 0xa5, 0xcf, 0x97, 0x11, 0xbb, 0x8b, 0xe5, 0x8b, 0xcd, 0xb7, 0x3a, 0xdf, 0x9e, 0x7c, 0x0f, 0xb0, 0x7b, 0x65, 0xb8, 0x84, 0xe7, 0x0a, 0xf7, 0x85, 0x37, 0x08, 0x8f, 0x0b, 0xd9, 0xdd, 0x29, 0x7c, 0x7c, 0xf8, 0x1b, 0xe1, 0xef, 0x84, 0x7f, 0x1e, 0xfe, 0x75, 0xf8, 0x6f, 0xe1, 0xb7, 0x7c, 0xe2, 0xcb, 0x82, 0xdd, 0xa5, 0x7c, 0x65, 0x7d, 0x95, 0x7d, 0xd5, 0xb0, 0xfb, 0x19, 0xdf, 0x00, 0xdf, 0x10, 0xdf, 0x28, 0xdf, 0x78, 0xdf, 0x6c, 0xdf, 0x4a, 0xdf, 0xab, 0xbe, 0xd7, 0x7d, 0x3b, + 0x43, 0x76, 0xe7, 0x78, 0xc4, 0xee, 0xa6, 0x11, 0xad, 0x22, 0x9e, 0x8b, 0x78, 0x31, 0x64, 0x77, 0x66, 0xec, 0xce, 0x5d, 0x20, 0x7f, 0xc8, 0xee, 0xc4, 0x02, 0xc9, 0x41, 0xbb, 0x7d, 0x05, 0x13, 0xb1, 0xfb, 0xa7, 0x82, 0x81, 0xc7, 0xec, 0xde, 0x50, 0xe8, 0xfd, 0x47, 0xec, 0x1e, 0x16, 0x95, 0x18, 0x35, 0x04, 0xbb, 0x73, 0xae, 0xf3, 0x61, 0xb7, 0x3c, 0xf0, 0x05, 0xed, 0x4e, 0xfd, 0x7f, 0x79, 0x74, 0x00, 0x6d, 0xac, 0x7c, 0xd2, 0xda, 0xce, 0x10, 0x7c, 0x66, 0xc9, 0xb9, 0xfb, 0xa3, 0x82, 0xdf, 0x29, 0xa7, 0xa7, 0xad, 0xf4, 0xb7, 0xc3, 0xbf, 0x7f, 0x20, 0xf2, 0xfd, 0xbd, 0x9f, 0xfb, 0x7d, 0x7f, 0x97, 0xf2, 0x80, 0xdb, 0x77, 0xf1, 0xa9, 0x47, 0xdf, 0x05, 0x97, 0x4b, 0xf1, 0x5f, 0x94, 0xfc, 0xcd, 0x9f, 0x6f, 0xb2, 0x07, 0xff, 0xb6, 0x41, 0x9f, 0xef, 0x75, 0x91, 0x0b, 0xdd, + 0xa8, 0xe5, 0x14, 0xf9, 0x8e, 0x35, 0xe1, 0xbb, 0xbe, 0xdf, 0x67, 0xf8, 0xae, 0xf3, 0xf9, 0xde, 0xe7, 0x59, 0xdf, 0xcf, 0xd7, 0x3d, 0x1f, 0x73, 0xe9, 0xe6, 0xf9, 0x97, 0xbe, 0xa9, 0x96, 0x36, 0xf3, 0xbb, 0xed, 0xe7, 0x0e, 0x9d, 0x7b, 0x53, 0xe4, 0xdb, 0x09, 0x41, 0x0e, 0xf7, 0xce, 0x2d, 0x3a, 0xc7, 0x7a, 0x75, 0x2e, 0xe1, 0x9b, 0x42, 0xdf, 0xe4, 0xfa, 0xc6, 0x3e, 0x2f, 0xe7, 0x86, 0x9e, 0x9f, 0x42, 0xbb, 0xd8, 0xb9, 0x88, 0x73, 0xf9, 0xcf, 0x85, 0x9d, 0xcb, 0x75, 0xee, 0xab, 0x73, 0x9f, 0xd0, 0xde, 0x02, 0x5d, 0xae, 0xb3, 0xa7, 0xcf, 0xe6, 0xfa, 0xfa, 0x57, 0x77, 0xde, 0x17, 0xe1, 0xcf, 0x2c, 0x7e, 0xe6, 0xe5, 0x74, 0x48, 0x34, 0x13, 0x3d, 0x93, 0x83, 0x36, 0xdf, 0x60, 0x47, 0x28, 0x46, 0xba, 0x3f, 0x75, 0x34, 0x7c, 0xa0, 0xb4, 0x88, 0x67, 0xbb, 0x67, 0x2f, 0x7f, + 0xbf, 0xe3, 0xb9, 0x62, 0xc6, 0x58, 0xee, 0xf7, 0x4f, 0xb1, 0x0f, 0xa6, 0x52, 0xd8, 0xf4, 0x3b, 0x23, 0x9c, 0xf9, 0x22, 0xde, 0x8f, 0xd2, 0x8d, 0x4f, 0xb7, 0x21, 0xdd, 0xf6, 0x74, 0x0f, 0xd2, 0x5b, 0xa9, 0x63, 0xe9, 0x33, 0xa5, 0x6f, 0x92, 0x7e, 0x7b, 0xfa, 0x13, 0xe9, 0x7f, 0x4c, 0x7f, 0x23, 0x43, 0xae, 0x0c, 0x79, 0x33, 0x44, 0x65, 0x68, 0x22, 0x92, 0xa1, 0x57, 0x86, 0x3e, 0xfc, 0xed, 0xbe, 0x2d, 0x82, 0x7f, 0x07, 0xc1, 0x13, 0x96, 0x61, 0xec, 0x9f, 0x72, 0x33, 0x4c, 0x7c, 0xa4, 0x3e, 0x2a, 0xc3, 0xe8, 0x0c, 0xf3, 0x32, 0xcc, 0x0f, 0xb5, 0x56, 0xb9, 0xc8, 0xb0, 0x26, 0xd4, 0x5a, 0xf9, 0xb8, 0x4f, 0x33, 0xcc, 0x7b, 0xa2, 0x3d, 0xf5, 0xb1, 0xd6, 0xf8, 0x0c, 0x0b, 0x1f, 0xd6, 0xe7, 0xa7, 0xbd, 0xac, 0x95, 0xf2, 0xca, 0x33, 0xca, 0x20, 0xa5, 0x97, 0x3e, 0x5c, 0xe9, + 0xa4, 0x74, 0x54, 0xe2, 0x94, 0x16, 0x4a, 0x5b, 0x65, 0x9b, 0x52, 0x89, 0x77, 0x49, 0x15, 0xa5, 0x9e, 0x32, 0x5b, 0x79, 0x51, 0x99, 0xa3, 0xbc, 0xa4, 0xcc, 0x55, 0xe6, 0xe9, 0x43, 0x95, 0x97, 0xf5, 0x61, 0xca, 0x7c, 0xa5, 0xb2, 0x12, 0xad, 0xec, 0x56, 0xde, 0x54, 0xf6, 0x28, 0x15, 0x94, 0x44, 0x56, 0x0c, 0x8f, 0x98, 0x62, 0x8b, 0x57, 0xd2, 0x4b, 0x06, 0xc9, 0x28, 0x99, 0x25, 0xab, 0x64, 0x93, 0xec, 0x92, 0x4b, 0x72, 0x4b, 0x98, 0xe4, 0xe7, 0x09, 0x8a, 0x90, 0x02, 0x52, 0x50, 0x0a, 0x4b, 0x11, 0x29, 0x2a, 0xc5, 0xa4, 0xb8, 0x44, 0x29, 0x2d, 0x95, 0x86, 0x4a, 0x2b, 0xe5, 0x69, 0xa5, 0xa9, 0x6c, 0x90, 0x12, 0x52, 0x5a, 0xca, 0x48, 0x25, 0xa9, 0xca, 0xca, 0xde, 0x51, 0x3a, 0xf1, 0x3e, 0xe9, 0x26, 0x3d, 0xf4, 0x21, 0xd2, 0x47, 0xfa, 0xf2, 0x4e, 0x18, 0x24, 0x43, 0x64, + 0x28, 0x2b, 0xd0, 0x68, 0x99, 0x22, 0x53, 0x65, 0x9a, 0x4c, 0x97, 0x17, 0x64, 0x86, 0xd2, 0x5a, 0xa9, 0xaf, 0xb4, 0x51, 0x3a, 0xb0, 0x17, 0x1a, 0xa1, 0xf4, 0x56, 0xfa, 0x28, 0xdd, 0x95, 0x7e, 0x4a, 0x03, 0x25, 0x41, 0x76, 0x28, 0x49, 0x32, 0x53, 0xa9, 0xa3, 0x1c, 0x40, 0xfb, 0x2e, 0xf2, 0xa6, 0xd2, 0x4d, 0xd9, 0xab, 0x8f, 0xd4, 0x47, 0xc9, 0x26, 0x67, 0x9c, 0xd2, 0x9e, 0xf7, 0xd5, 0x61, 0xfd, 0x59, 0x99, 0xa5, 0xd4, 0xd5, 0x47, 0xeb, 0x63, 0xf4, 0xb1, 0xfa, 0x38, 0xb3, 0xb8, 0x19, 0x65, 0x96, 0x30, 0x4b, 0x9a, 0xa5, 0xcc, 0xb2, 0xac, 0xa3, 0x96, 0x39, 0xda, 0x1c, 0x63, 0x8e, 0x35, 0xc7, 0x9b, 0xef, 0x9a, 0x47, 0xcd, 0xf7, 0xcc, 0x0f, 0xc4, 0xb1, 0x34, 0x2b, 0xbd, 0x95, 0xc1, 0xca, 0x68, 0x65, 0xb2, 0x32, 0x2b, 0x3b, 0x24, 0xaf, 0xd5, 0xde, 0x7a, 0xda, 0xea, 0x60, + 0x3d, 0x63, 0x8d, 0x91, 0x70, 0x19, 0x65, 0x2d, 0xb4, 0x16, 0x59, 0x8b, 0xad, 0x25, 0xd6, 0x52, 0x6b, 0x05, 0xeb, 0x69, 0x59, 0xeb, 0xb8, 0x75, 0xc2, 0xfa, 0xd8, 0xfa, 0xd4, 0xce, 0x6b, 0xe7, 0xb3, 0xf3, 0xdb, 0x3e, 0xa9, 0x62, 0x37, 0xb6, 0x5b, 0xdb, 0x6d, 0xec, 0xb6, 0x76, 0x3b, 0x3b, 0x9e, 0x77, 0xd9, 0x00, 0x7b, 0xbf, 0x7d, 0xc0, 0x3e, 0x68, 0x1f, 0xb2, 0xbf, 0x97, 0xc1, 0xf6, 0x45, 0xb3, 0xb4, 0xb5, 0xcc, 0x2c, 0x63, 0x2d, 0x17, 0x5d, 0x4a, 0x9a, 0xbd, 0xad, 0x83, 0x66, 0x1f, 0xeb, 0x90, 0xd9, 0xcf, 0x3a, 0x6c, 0xf6, 0xb5, 0xde, 0x32, 0x07, 0x4b, 0xb4, 0x39, 0x4a, 0x2a, 0x9b, 0x13, 0xac, 0x93, 0xe6, 0x44, 0xeb, 0x33, 0xf3, 0x39, 0xeb, 0x73, 0x49, 0x27, 0xd5, 0xcc, 0x71, 0xd6, 0x27, 0xe6, 0x1a, 0x2b, 0x60, 0xbe, 0x6a, 0x8b, 0xb9, 0xd6, 0x56, 0xcc, 0x75, 0xb6, + 0x6a, 0x6e, 0x94, 0x44, 0xc9, 0x24, 0x49, 0xe6, 0xfb, 0x76, 0xb8, 0x79, 0xcc, 0x8e, 0x30, 0x3f, 0xb4, 0x0b, 0x48, 0x16, 0xe9, 0x62, 0x7e, 0x24, 0xc9, 0xe6, 0x65, 0x3b, 0xa7, 0xf9, 0xa3, 0x5d, 0xc6, 0xbc, 0x6a, 0x97, 0x93, 0x9e, 0xe6, 0x75, 0xbb, 0x8a, 0x79, 0xc3, 0xae, 0x2a, 0x39, 0xa5, 0xb7, 0xf1, 0x93, 0x91, 0x4d, 0x72, 0x48, 0x2f, 0x4b, 0xb1, 0x1b, 0x5a, 0xba, 0xdd, 0xc4, 0x52, 0xed, 0x46, 0xce, 0x29, 0xc9, 0x23, 0xfd, 0xac, 0x2c, 0x76, 0x82, 0x95, 0xd5, 0x6e, 0x6f, 0x65, 0xb3, 0x9f, 0x66, 0x2f, 0x32, 0xd0, 0xaa, 0x6e, 0x4f, 0xb0, 0x6a, 0xda, 0xcf, 0x59, 0x35, 0xec, 0x89, 0x56, 0x3d, 0xfb, 0x05, 0xab, 0xbe, 0x3d, 0x43, 0x22, 0x65, 0xb8, 0xd5, 0xc0, 0x9e, 0x69, 0xb5, 0xb6, 0x17, 0x5a, 0x71, 0x32, 0x46, 0x0a, 0xc9, 0x58, 0xab, 0xa3, 0x71, 0xc5, 0xea, 0x64, 0xbf, + 0x65, 0x25, 0xda, 0x87, 0xad, 0x24, 0xfb, 0x88, 0xd5, 0xd9, 0x7e, 0x5b, 0x9e, 0x92, 0xe7, 0xad, 0x21, 0xf6, 0x69, 0x6b, 0xac, 0xfd, 0x83, 0x35, 0xce, 0x9a, 0x63, 0x5f, 0xb7, 0xe6, 0xda, 0x37, 0xad, 0x97, 0xec, 0x1b, 0xf2, 0xac, 0x3a, 0x40, 0xad, 0xed, 0x6c, 0x51, 0x07, 0xaa, 0x31, 0xea, 0x20, 0x35, 0x56, 0xad, 0xa3, 0x0e, 0x36, 0x9f, 0x55, 0xeb, 0xaa, 0xf5, 0xd4, 0x21, 0xea, 0x50, 0xb5, 0xbe, 0xda, 0x40, 0x6d, 0xa8, 0x36, 0x52, 0x87, 0xa9, 0x8d, 0xd5, 0x26, 0xbc, 0x77, 0x9a, 0x1a, 0xf9, 0xd5, 0x66, 0xea, 0x48, 0xbb, 0x9a, 0x3a, 0x8a, 0x77, 0x51, 0x9c, 0xd3, 0x4a, 0x6d, 0xa1, 0xfc, 0xa0, 0x5c, 0x52, 0x5b, 0xaa, 0xcf, 0xaa, 0xad, 0xd4, 0xd1, 0x6a, 0x6b, 0xb5, 0x8d, 0x3a, 0xc6, 0xc9, 0xa5, 0xbe, 0xac, 0x8e, 0x55, 0xdb, 0xaa, 0xe3, 0xd4, 0x76, 0xca, 0x45, 0xe5, 0xb2, + 0x1a, 0xaf, 0x26, 0xa8, 0xed, 0xd5, 0xf1, 0xbc, 0xbb, 0x26, 0xb0, 0x8a, 0x77, 0x54, 0x27, 0xf2, 0xf6, 0x9a, 0xaf, 0x3e, 0xc3, 0x4a, 0xbe, 0x40, 0x1d, 0xe1, 0x9c, 0x76, 0xce, 0xa8, 0x0b, 0x9d, 0xcf, 0xbc, 0xef, 0x7b, 0x8f, 0x79, 0x3f, 0x30, 0xff, 0x30, 0xef, 0xda, 0x31, 0x96, 0xd7, 0x4a, 0x67, 0xb7, 0x32, 0x63, 0xad, 0xcd, 0xe6, 0xe7, 0x76, 0x61, 0x2b, 0xdc, 0xee, 0x66, 0x75, 0xb5, 0xdf, 0xb3, 0xba, 0xd9, 0xef, 0x5b, 0xdd, 0xed, 0x0f, 0xac, 0x1e, 0xf6, 0x31, 0xab, 0xa7, 0xfd, 0xa1, 0x3a, 0xd9, 0xac, 0x63, 0x6d, 0x31, 0xeb, 0x5a, 0x7e, 0xb3, 0x8b, 0xb5, 0x5b, 0xed, 0xa4, 0x4e, 0x31, 0xd7, 0xdb, 0x9a, 0xf9, 0x8b, 0x5d, 0xde, 0xf8, 0xda, 0xf8, 0xc6, 0xf2, 0xd9, 0xdd, 0xbd, 0x33, 0x8d, 0x23, 0xc6, 0x61, 0x73, 0x84, 0x39, 0xd2, 0xfa, 0xc8, 0x7c, 0xcd, 0xd6, 0xd5, 0x44, + 0x35, 0xc9, 0xf2, 0xd8, 0x4d, 0xcd, 0x72, 0xd6, 0x4a, 0xb3, 0xb3, 0xb5, 0xcb, 0x2a, 0x6f, 0x8f, 0x32, 0xeb, 0x59, 0x6f, 0x9a, 0xf5, 0xad, 0x37, 0xcc, 0x39, 0xd6, 0x4f, 0xe6, 0x4b, 0xd6, 0x8f, 0xe6, 0x17, 0x76, 0x09, 0xf3, 0x94, 0x1d, 0x65, 0x45, 0xd8, 0x23, 0xad, 0x02, 0xf6, 0x08, 0xab, 0x96, 0x3d, 0xdd, 0xaa, 0x6d, 0x4f, 0xb3, 0x7a, 0xd9, 0x9f, 0x59, 0xbd, 0xed, 0x93, 0x56, 0x43, 0x7b, 0x96, 0xd5, 0xc2, 0x5e, 0xe0, 0x9d, 0xa5, 0x3e, 0x6f, 0x6e, 0xb1, 0x33, 0xa9, 0x9d, 0xd5, 0xa9, 0x6a, 0x17, 0x75, 0x9a, 0x9a, 0xac, 0x4e, 0x37, 0x1b, 0x58, 0x29, 0xe6, 0x5c, 0xeb, 0x5b, 0x2b, 0xd2, 0xee, 0x61, 0x15, 0xf4, 0xfc, 0x6e, 0x15, 0xb2, 0x7b, 0x5a, 0x85, 0xed, 0x5e, 0xd6, 0x14, 0xfb, 0xaa, 0xfa, 0x82, 0x3a, 0x43, 0x9d, 0xc9, 0x6e, 0x6c, 0x96, 0x3a, 0xdb, 0x6c, 0x68, 0x0e, + 0xb0, 0xde, 0x36, 0x6f, 0x5a, 0x6d, 0xd4, 0x17, 0xd5, 0x39, 0xde, 0x79, 0xea, 0x4b, 0x66, 0x77, 0xb5, 0xab, 0x93, 0xd3, 0x9c, 0x67, 0x7d, 0x67, 0x5e, 0xb1, 0x4b, 0xa9, 0xdd, 0xd4, 0xb9, 0x6a, 0x77, 0x7b, 0x92, 0xda, 0x43, 0x9d, 0xe7, 0xbc, 0xa3, 0xee, 0x54, 0x77, 0x19, 0x79, 0xd5, 0xdd, 0xb6, 0x57, 0xdd, 0xa3, 0xee, 0x55, 0xf7, 0xa9, 0xfb, 0xd5, 0x03, 0xea, 0x41, 0x23, 0x9f, 0xd3, 0xd7, 0x2e, 0xe6, 0x29, 0xe4, 0xbc, 0xa5, 0x5e, 0x51, 0xaf, 0x3a, 0x27, 0xd4, 0x9f, 0xd5, 0x5f, 0xd4, 0x6b, 0xea, 0xaf, 0xde, 0x97, 0xbd, 0xf3, 0x3d, 0x19, 0xd4, 0xbb, 0xea, 0x3d, 0xf5, 0xbe, 0xa3, 0x6b, 0x63, 0x1d, 0x8f, 0x93, 0x5e, 0x1b, 0xa7, 0x8d, 0xd7, 0x26, 0x38, 0x59, 0x9c, 0xec, 0xde, 0xd7, 0xbc, 0x1b, 0x9c, 0x2f, 0x9c, 0x2f, 0x79, 0x6f, 0x4e, 0xd1, 0x26, 0x6b, 0xcf, 0x6b, 0x53, + 0x9d, 0x4b, 0xce, 0x65, 0x6d, 0x1a, 0xe7, 0x94, 0xcd, 0xe2, 0xd7, 0x5e, 0x90, 0x37, 0x3d, 0x3f, 0x7b, 0xb2, 0x1a, 0xdf, 0x1b, 0xdf, 0x1a, 0xdf, 0x19, 0x17, 0x78, 0xb7, 0xce, 0x34, 0xee, 0x1b, 0x0f, 0x78, 0xc3, 0xce, 0xd6, 0x5e, 0xd4, 0xe6, 0xb0, 0x97, 0x3f, 0x24, 0x7b, 0xe4, 0x88, 0x6c, 0x93, 0x03, 0xda, 0x4b, 0x46, 0x25, 0x6d, 0xae, 0x63, 0x39, 0x19, 0x9c, 0xac, 0xde, 0xd7, 0xb5, 0x79, 0xda, 0xcb, 0xda, 0x7c, 0x6d, 0x81, 0xb6, 0x50, 0x5b, 0xa4, 0x2d, 0xf6, 0x64, 0x33, 0xd6, 0x38, 0x5f, 0x19, 0xaf, 0x1a, 0x6b, 0x8d, 0x75, 0xc6, 0x7a, 0xe3, 0x35, 0xcf, 0x2f, 0xc6, 0x3d, 0xcf, 0x35, 0xe3, 0x92, 0x71, 0xd7, 0xd8, 0x60, 0x6c, 0x34, 0x36, 0x19, 0xaf, 0x1b, 0x9b, 0x8d, 0x2d, 0x86, 0xdf, 0xf3, 0x94, 0xa7, 0xa8, 0xa7, 0xb8, 0xa7, 0x94, 0xa7, 0x9c, 0x13, 0xf0, 0x54, 0xf0, + 0x54, 0xf5, 0x94, 0x31, 0x6d, 0x4f, 0x25, 0x4f, 0x65, 0x4f, 0x09, 0xaf, 0xea, 0x35, 0xbc, 0x96, 0xd7, 0xf1, 0xa6, 0x37, 0x33, 0x7a, 0xb3, 0x78, 0x73, 0xc8, 0x56, 0x4f, 0xac, 0xa7, 0x8e, 0x37, 0x97, 0x37, 0xb7, 0x37, 0xcc, 0xe8, 0xe8, 0xdd, 0xe8, 0xcd, 0xef, 0x0d, 0xf7, 0xfa, 0xbc, 0x11, 0xde, 0x02, 0xde, 0x48, 0xd3, 0x6b, 0x5a, 0xde, 0x82, 0x9e, 0x5f, 0x3d, 0xd5, 0x3d, 0x35, 0xbc, 0x85, 0xbc, 0x85, 0xbd, 0x45, 0x3c, 0xf5, 0x3c, 0xf5, 0x3d, 0x0d, 0xbc, 0x19, 0x3d, 0x0d, 0x3d, 0x8d, 0x9c, 0x6f, 0x9d, 0xef, 0x3c, 0x77, 0x3c, 0x31, 0x9e, 0xcc, 0x9e, 0xba, 0x9e, 0x8b, 0xde, 0x4d, 0xde, 0xa2, 0xde, 0x62, 0xde, 0xe2, 0xde, 0x12, 0xde, 0xd2, 0x9e, 0x30, 0xcf, 0x8f, 0x9e, 0x2b, 0x9e, 0xe6, 0xde, 0x6c, 0x9e, 0x9a, 0x9e, 0x04, 0xef, 0x53, 0xce, 0x05, 0xaf, 0xe9, 0xcd, 0xe7, + 0x2d, 0x67, 0xe6, 0x34, 0x73, 0x79, 0x2b, 0x78, 0x2b, 0xaa, 0x79, 0xbd, 0xd1, 0xde, 0xaa, 0xde, 0x1a, 0xde, 0x52, 0xde, 0x5a, 0xde, 0xda, 0xde, 0x18, 0x6f, 0x5d, 0x33, 0xbd, 0xb7, 0xbe, 0xb7, 0x81, 0xb7, 0x91, 0xb7, 0x89, 0xb7, 0x99, 0xb7, 0x85, 0xb7, 0x95, 0xb7, 0x8d, 0x91, 0xc7, 0x1b, 0xef, 0x6d, 0xef, 0xed, 0xe8, 0x4d, 0xf4, 0x76, 0xf1, 0x76, 0xf3, 0xf6, 0xf4, 0xf6, 0xf1, 0xf6, 0xf7, 0x0e, 0xf2, 0x0e, 0xf5, 0x8e, 0xf4, 0x8e, 0xf6, 0x8e, 0x33, 0x1a, 0x7a, 0xe7, 0x18, 0xb7, 0x8d, 0x3b, 0xce, 0xe7, 0x72, 0x45, 0xae, 0x9a, 0x15, 0x9c, 0xef, 0xcd, 0xc9, 0xe6, 0xeb, 0xe6, 0x09, 0x6b, 0x82, 0x93, 0xc7, 0xe9, 0x63, 0x96, 0x37, 0xbb, 0xca, 0xbb, 0x72, 0xd4, 0x9c, 0x64, 0xcd, 0x33, 0x37, 0xc9, 0x7b, 0xe6, 0x71, 0xf3, 0x67, 0x79, 0xdf, 0xbc, 0x67, 0x19, 0xf2, 0x81, 0x95, + 0x5d, 0x8e, 0x59, 0x15, 0xe4, 0x43, 0xab, 0xa5, 0x35, 0x5e, 0x3e, 0xb2, 0x46, 0xc8, 0x71, 0x39, 0x61, 0x7e, 0x6c, 0x4d, 0x74, 0x4a, 0x39, 0x0d, 0x9d, 0x96, 0x4e, 0x6f, 0x67, 0x92, 0x7c, 0x2c, 0x9f, 0xc8, 0x49, 0xf9, 0x4c, 0x3e, 0x97, 0x2f, 0xe4, 0x94, 0x7c, 0x29, 0xa7, 0xed, 0xca, 0x72, 0x46, 0xbe, 0x66, 0x7f, 0x7d, 0xce, 0xee, 0x20, 0xdf, 0xc8, 0xb7, 0x72, 0x41, 0xbe, 0x93, 0xef, 0xe5, 0x07, 0xb9, 0x28, 0x97, 0xe5, 0x47, 0xf9, 0xc9, 0x38, 0xeb, 0x3c, 0x67, 0x77, 0x74, 0x26, 0x3a, 0x0b, 0xb4, 0x35, 0xda, 0x3a, 0x6d, 0x99, 0x77, 0xb3, 0x77, 0x8b, 0xb6, 0x49, 0x7b, 0x4d, 0xdb, 0xae, 0xad, 0xd0, 0x36, 0xb3, 0x87, 0xda, 0xaa, 0x2d, 0xd7, 0x5e, 0xd7, 0x56, 0x6a, 0x5b, 0xb4, 0x55, 0x5a, 0x8a, 0x5e, 0x46, 0x8f, 0xd6, 0xcb, 0xea, 0x95, 0xf5, 0x72, 0x7a, 0x15, 0xbd, 0xbc, + 0x5e, 0x55, 0xaf, 0xa0, 0x57, 0xd3, 0x2b, 0xea, 0xd5, 0xf5, 0x4a, 0x7a, 0x0d, 0x6d, 0xbd, 0xb6, 0xc1, 0x7a, 0xc7, 0x2e, 0xed, 0xec, 0xd2, 0x5e, 0xd5, 0xd6, 0x5a, 0xd5, 0xb4, 0x6d, 0xe6, 0x40, 0x76, 0x9f, 0x7e, 0x6d, 0xa3, 0xb1, 0xdf, 0xf8, 0xdd, 0xf8, 0xc3, 0x7c, 0xde, 0x9c, 0xaa, 0xa4, 0x73, 0xfc, 0x8a, 0x6d, 0x35, 0x32, 0x3f, 0x31, 0x3f, 0x35, 0x7f, 0x92, 0xdf, 0xe5, 0x0f, 0x25, 0xbd, 0xb3, 0xd3, 0xd9, 0xe1, 0xec, 0x56, 0x14, 0xb3, 0xa2, 0x5c, 0x33, 0xbb, 0xc9, 0xaf, 0x72, 0xdd, 0x9c, 0x22, 0x37, 0xe4, 0xa6, 0xdc, 0x72, 0xb6, 0xcb, 0x6d, 0xb9, 0x63, 0xde, 0x37, 0x37, 0x5b, 0x39, 0xe5, 0x9e, 0x55, 0xd1, 0xec, 0x6f, 0xb5, 0x92, 0xfb, 0x12, 0xb0, 0x46, 0x2a, 0xa2, 0xa8, 0x8a, 0xa6, 0xe8, 0x8a, 0xa1, 0x78, 0x14, 0x53, 0xb1, 0x14, 0x47, 0xf1, 0x5a, 0xaf, 0x28, 0x19, + 0x95, 0xcc, 0x4a, 0x16, 0x25, 0x9b, 0xf5, 0x85, 0x92, 0x5d, 0xc9, 0xa1, 0xe4, 0x74, 0xde, 0x53, 0x72, 0x29, 0xb9, 0x95, 0x30, 0x25, 0xaf, 0xfd, 0x8c, 0x12, 0x6e, 0x3f, 0x6b, 0x1d, 0x51, 0x7c, 0xf6, 0x25, 0xa5, 0x80, 0x7d, 0x5e, 0x89, 0x54, 0x0a, 0x29, 0x85, 0x95, 0xa7, 0x94, 0x22, 0x4a, 0x51, 0xa5, 0x98, 0x52, 0x5c, 0x29, 0xa1, 0x94, 0xb4, 0x4e, 0x59, 0x5f, 0x2a, 0xa5, 0x9c, 0xc3, 0x4a, 0x94, 0x3d, 0xdb, 0xce, 0x65, 0x17, 0xb4, 0x4b, 0x2a, 0x79, 0x94, 0x7c, 0x76, 0x0e, 0xe7, 0x23, 0xe7, 0x7d, 0xfb, 0xb2, 0x52, 0x50, 0xaf, 0xa5, 0x37, 0x56, 0x32, 0x28, 0xa5, 0xf5, 0xda, 0x7a, 0x13, 0x3d, 0x46, 0x6f, 0xaa, 0xc7, 0xea, 0xcd, 0xf4, 0x3a, 0x7a, 0x73, 0xbd, 0xae, 0x1e, 0xa7, 0xd7, 0xd3, 0x5b, 0x38, 0xb9, 0xd9, 0x6d, 0xdc, 0x55, 0xf2, 0xcb, 0x03, 0x25, 0xc2, 0x39, 0xe4, + 0x9c, 0xd4, 0xeb, 0xeb, 0x2d, 0xf5, 0x06, 0x7a, 0x2b, 0xbd, 0xa1, 0xde, 0xda, 0x7b, 0xd4, 0xfb, 0x9e, 0xde, 0x48, 0x6f, 0x63, 0x24, 0x78, 0xf7, 0x7b, 0x0f, 0x7a, 0x0f, 0x7b, 0xdf, 0x31, 0xda, 0x1b, 0x4f, 0x6b, 0x6f, 0x68, 0xdf, 0x69, 0x6f, 0x6a, 0x17, 0xac, 0xaa, 0xf6, 0x78, 0xf9, 0x4d, 0xc9, 0xaa, 0xed, 0xd4, 0xbe, 0xd7, 0x76, 0x69, 0x3f, 0x68, 0xbb, 0xb5, 0x8b, 0xc6, 0x4d, 0xe3, 0xaa, 0xb6, 0x47, 0xbb, 0xa4, 0xed, 0xd5, 0x2e, 0x6b, 0xfb, 0xb4, 0x1f, 0xb5, 0xfd, 0xda, 0x4f, 0xda, 0x01, 0xed, 0x8a, 0x76, 0x50, 0xbb, 0xaa, 0x1d, 0xd2, 0x7e, 0xd6, 0xde, 0xd2, 0x7e, 0x31, 0x6e, 0x19, 0x3f, 0x6b, 0x87, 0xb5, 0x6b, 0xd6, 0x8b, 0xda, 0xaf, 0xda, 0x11, 0xed, 0x37, 0xed, 0x6d, 0xed, 0xba, 0xf6, 0x8e, 0x76, 0x43, 0x7b, 0x57, 0xbb, 0xa9, 0x1d, 0xd5, 0x6e, 0x69, 0xef, 0x69, + 0xb7, 0xb5, 0xf7, 0xb5, 0x3b, 0xda, 0x07, 0xda, 0xef, 0xe6, 0x45, 0x27, 0xc5, 0x39, 0xa2, 0x1d, 0xd3, 0xfe, 0xd0, 0x3e, 0xd4, 0xee, 0x6a, 0x1f, 0x69, 0xf7, 0xb4, 0xe3, 0xda, 0x7d, 0xed, 0x84, 0xf6, 0x40, 0xfb, 0x58, 0x0b, 0x98, 0x97, 0xcc, 0x4e, 0xd6, 0x76, 0x33, 0xd1, 0xda, 0x61, 0x26, 0x5b, 0x7b, 0xcc, 0x55, 0xd6, 0x65, 0xed, 0x13, 0x5d, 0xbc, 0xbb, 0xbc, 0x7b, 0x9d, 0xad, 0xce, 0xdb, 0xce, 0x36, 0xe7, 0xa8, 0xf6, 0xa9, 0xae, 0x38, 0x6f, 0x38, 0xc7, 0x9c, 0x37, 0x9d, 0x0f, 0xad, 0x72, 0xf6, 0x70, 0xed, 0xa4, 0xae, 0x7a, 0x77, 0x7b, 0xf7, 0x39, 0x07, 0x9d, 0x4f, 0x9d, 0x3d, 0xf6, 0x35, 0x67, 0xaf, 0xfd, 0xab, 0xb3, 0xcf, 0xfe, 0xcd, 0xd9, 0xef, 0x7c, 0xac, 0x7d, 0xa6, 0x6b, 0xce, 0x01, 0xe7, 0x13, 0xed, 0x73, 0x5d, 0xd7, 0xbe, 0xd0, 0x0d, 0xed, 0x94, 0xee, 0xd1, + 0xbe, 0xb4, 0x3e, 0xd4, 0xbe, 0xd2, 0x4d, 0xed, 0xb4, 0x6e, 0x69, 0x67, 0x74, 0x5b, 0xfb, 0x5a, 0x77, 0xb4, 0xb3, 0xba, 0x57, 0x3b, 0xa7, 0xa7, 0xd3, 0xce, 0xeb, 0xe9, 0xb5, 0x6f, 0xf4, 0x0c, 0xda, 0xb7, 0x7a, 0x46, 0xe7, 0xb8, 0xf3, 0x81, 0xfd, 0x87, 0x56, 0x43, 0xab, 0xa9, 0xd5, 0xb2, 0xef, 0xd9, 0x0f, 0xec, 0x80, 0x56, 0x5b, 0x8b, 0xd1, 0x62, 0x1d, 0x55, 0xab, 0xe3, 0x98, 0x8e, 0xad, 0xd5, 0x75, 0x1c, 0xad, 0x9e, 0x56, 0x5f, 0x6b, 0xe0, 0xa4, 0x73, 0x32, 0x3a, 0x99, 0xb4, 0x86, 0x5a, 0x23, 0x27, 0xb3, 0xd6, 0x58, 0x6b, 0xa2, 0x35, 0xd5, 0x9a, 0x69, 0xcd, 0xb5, 0x38, 0x8f, 0xe9, 0x61, 0x39, 0xf7, 0x38, 0x1e, 0xaf, 0x27, 0x9d, 0xd6, 0x42, 0x6b, 0xe9, 0x49, 0xaf, 0xb5, 0x32, 0x2e, 0x1b, 0x3f, 0x7a, 0xc4, 0xa3, 0x68, 0xad, 0x3d, 0xaa, 0x47, 0xf3, 0xe8, 0x5a, 0x1b, + 0x8f, 0xe1, 0xf1, 0x68, 0x6d, 0xb5, 0x76, 0x5a, 0xbc, 0xf7, 0x43, 0x23, 0xdc, 0xe9, 0x67, 0x8f, 0xd5, 0x12, 0xb4, 0xf6, 0xd6, 0x01, 0xeb, 0x98, 0x75, 0xd5, 0x4e, 0x67, 0x47, 0x1b, 0x3e, 0xbb, 0xbe, 0xdd, 0xd2, 0x1e, 0x67, 0x4f, 0xb5, 0x5f, 0xf6, 0xce, 0xb5, 0x4f, 0xd9, 0xdf, 0xd9, 0xbf, 0x5b, 0xdb, 0xbc, 0xb3, 0x8d, 0x08, 0xa3, 0x80, 0x11, 0xe9, 0xbc, 0xeb, 0xdd, 0x63, 0x87, 0x19, 0x01, 0xed, 0x69, 0xfb, 0x53, 0xad, 0x83, 0xd6, 0xd1, 0xbe, 0xaf, 0x3d, 0xa3, 0x75, 0xd2, 0x12, 0x1d, 0x45, 0x4b, 0x72, 0x34, 0x27, 0x07, 0x67, 0x90, 0x67, 0xb5, 0xd1, 0xda, 0x18, 0xef, 0x44, 0xef, 0x64, 0xb5, 0xba, 0x77, 0xaa, 0x99, 0xdb, 0xcc, 0x63, 0x86, 0x99, 0x79, 0xcd, 0x7c, 0x66, 0x7e, 0xef, 0x70, 0xef, 0x74, 0xef, 0x0c, 0x33, 0xc9, 0xda, 0x69, 0xf6, 0xb0, 0xf6, 0x9a, 0x3d, 0xad, + 0x7d, 0x66, 0x2f, 0x6b, 0xbf, 0x39, 0xc8, 0x7a, 0xd7, 0x1c, 0x62, 0x1d, 0x35, 0x87, 0x5a, 0xef, 0x99, 0xc3, 0xac, 0xf7, 0x8d, 0x1f, 0x8c, 0x8b, 0xe6, 0x70, 0xeb, 0x03, 0xf3, 0x65, 0xeb, 0x82, 0x39, 0xdf, 0xfa, 0xde, 0x5c, 0x60, 0xfd, 0x60, 0x2e, 0xb4, 0x2e, 0x9a, 0x8b, 0xac, 0x4b, 0xe6, 0x6a, 0xeb, 0x8a, 0xb9, 0xc1, 0x36, 0x4c, 0xbf, 0x9d, 0xd9, 0x4c, 0xb1, 0xb3, 0x98, 0x5b, 0xed, 0xac, 0x9e, 0x02, 0x76, 0x36, 0x73, 0x9b, 0xa7, 0xa0, 0xf9, 0xa5, 0x9d, 0xc7, 0xfc, 0xca, 0x2e, 0x62, 0x5e, 0xb3, 0x2b, 0x98, 0xbf, 0xda, 0x15, 0xcd, 0xdf, 0xec, 0x4a, 0xe6, 0x2d, 0xbb, 0xba, 0x79, 0xdb, 0xae, 0x61, 0xde, 0xb1, 0x6b, 0x9a, 0xbf, 0xdb, 0xb5, 0xcc, 0x07, 0x76, 0x1d, 0x33, 0x60, 0xd7, 0xb5, 0xc4, 0xae, 0x67, 0x99, 0x76, 0x33, 0xcb, 0xb2, 0x9b, 0x5b, 0xb6, 0x1d, 0x67, 0x39, + 0x76, 0x0b, 0xeb, 0x29, 0xbb, 0xb7, 0x55, 0xc4, 0xee, 0x63, 0x15, 0xb5, 0xfb, 0x5a, 0xc5, 0xec, 0x7e, 0x56, 0x25, 0x7b, 0xb4, 0x15, 0x6d, 0x8f, 0xb1, 0x62, 0xec, 0xc9, 0x56, 0xac, 0x3d, 0xc5, 0xaa, 0x63, 0x3f, 0x6f, 0xd5, 0xf5, 0x44, 0x5a, 0x8d, 0xed, 0x17, 0xad, 0x26, 0xf6, 0x1c, 0xab, 0xa9, 0xfd, 0x92, 0xd5, 0xcc, 0x9e, 0x6b, 0x35, 0xb7, 0xe7, 0x59, 0x6d, 0xed, 0x45, 0x56, 0x3b, 0x7b, 0xb1, 0x15, 0x6f, 0x2f, 0xb1, 0x12, 0xec, 0xa5, 0x6a, 0x4f, 0xfb, 0x23, 0xab, 0x8f, 0x7d, 0xdc, 0xea, 0x6b, 0x9f, 0xb0, 0xfa, 0xd9, 0x1f, 0x5b, 0xfd, 0xed, 0x4f, 0xac, 0x01, 0xf6, 0xe7, 0xd6, 0x40, 0xfb, 0x0b, 0x6b, 0x90, 0xfd, 0xa5, 0x35, 0xd8, 0xfe, 0xca, 0x1a, 0x6a, 0x9f, 0xb1, 0x86, 0xd9, 0x5f, 0x5b, 0xc3, 0xed, 0xb3, 0xd6, 0x28, 0xfb, 0x1b, 0xeb, 0x59, 0xfb, 0x5b, 0xeb, 0x79, + 0xfb, 0x67, 0xeb, 0x65, 0xfb, 0x96, 0x35, 0xdf, 0xbe, 0x6d, 0x2d, 0xb0, 0xef, 0xd8, 0xd9, 0x3d, 0x85, 0xed, 0x73, 0xf6, 0x2f, 0xde, 0x3a, 0xf6, 0x7c, 0xb3, 0x92, 0xb5, 0xca, 0x8c, 0xb6, 0x56, 0x9b, 0x95, 0xad, 0x35, 0x66, 0x15, 0xeb, 0x55, 0xb3, 0xaa, 0xb5, 0xd6, 0xac, 0x66, 0xad, 0x33, 0xab, 0x5b, 0xeb, 0xcd, 0x1a, 0xd6, 0x6b, 0x66, 0x4d, 0x6b, 0x83, 0x59, 0xcb, 0xda, 0x68, 0xd6, 0xb6, 0x36, 0x71, 0x26, 0x78, 0xdd, 0x9c, 0x66, 0x7d, 0x65, 0x4e, 0xb7, 0x4e, 0x9b, 0x2f, 0x58, 0x67, 0xcc, 0x19, 0xd6, 0xd7, 0xe6, 0x4c, 0xeb, 0xac, 0x39, 0xcb, 0x3a, 0x67, 0xce, 0xb6, 0xce, 0x9b, 0x2f, 0x5a, 0xdf, 0x98, 0x27, 0xed, 0x42, 0xe6, 0x67, 0x76, 0x6e, 0x2b, 0x97, 0xdd, 0xc9, 0xca, 0x6d, 0x27, 0x5a, 0x79, 0xec, 0x24, 0x2b, 0xcc, 0xee, 0x6c, 0xe5, 0xb5, 0xbb, 0x58, 0xf9, 0xec, + 0x64, 0x2b, 0xbf, 0xdd, 0xd5, 0xf9, 0xd1, 0xb9, 0xe1, 0xfc, 0xe4, 0xdc, 0x74, 0xae, 0x38, 0xb7, 0x9c, 0xab, 0xce, 0x6d, 0xe7, 0x67, 0xe7, 0x8e, 0xd5, 0xc5, 0x7e, 0xd7, 0x4a, 0xb6, 0x8f, 0x3a, 0xbf, 0x38, 0xbf, 0x3b, 0xd7, 0x9c, 0x3f, 0x9c, 0x5f, 0x9d, 0xbb, 0xce, 0x6f, 0xce, 0x3d, 0xe7, 0xba, 0x73, 0xdf, 0x1a, 0x6d, 0x5f, 0xb0, 0x9e, 0xb3, 0x7f, 0xb4, 0x26, 0xdb, 0x57, 0xac, 0x49, 0xf6, 0x4f, 0x4e, 0x69, 0xa7, 0xbc, 0x53, 0xd6, 0xa9, 0xe8, 0x94, 0x71, 0x2a, 0x38, 0xe5, 0x9c, 0x4a, 0x66, 0x23, 0xb3, 0x99, 0xd9, 0xc4, 0x8c, 0x33, 0x1b, 0x9b, 0xcd, 0xcd, 0xa6, 0x66, 0x0b, 0xa7, 0x91, 0xd3, 0xd4, 0x69, 0xe2, 0x34, 0x77, 0x1a, 0x3b, 0xcd, 0xcc, 0xc5, 0xe6, 0x32, 0x73, 0xa9, 0xb9, 0xc2, 0x5c, 0x62, 0x2e, 0x77, 0x5a, 0x3b, 0xf1, 0x4e, 0x5b, 0xa7, 0xbd, 0xd3, 0xc6, 0x49, + 0x70, 0xda, 0x39, 0x4f, 0x9b, 0xdb, 0xcd, 0x9d, 0xe6, 0x1b, 0xe6, 0x6e, 0x73, 0x87, 0xb9, 0xcb, 0x7c, 0xd3, 0xdc, 0xe3, 0xf4, 0x77, 0x06, 0x3b, 0x03, 0x9d, 0xa1, 0xce, 0x00, 0x67, 0x88, 0x33, 0xc8, 0x19, 0x66, 0x9e, 0x36, 0xcf, 0x99, 0x5f, 0x9b, 0xdf, 0x98, 0x67, 0xcc, 0xf3, 0xe6, 0x59, 0xf3, 0x5b, 0xbb, 0xbf, 0x3d, 0xc8, 0x1e, 0x68, 0x0f, 0xb1, 0x07, 0xd8, 0x83, 0xad, 0xe2, 0x56, 0x49, 0xab, 0x84, 0x55, 0xda, 0x8a, 0xb2, 0x4a, 0x39, 0x93, 0x9d, 0x69, 0xce, 0xf3, 0xce, 0x0b, 0xce, 0x14, 0x67, 0xba, 0x33, 0xd5, 0x99, 0x61, 0xbd, 0xe0, 0x2c, 0x74, 0x96, 0x3a, 0x8b, 0x9d, 0xe5, 0xce, 0x22, 0x67, 0x99, 0xb3, 0xc4, 0x59, 0xe1, 0xe4, 0x75, 0x7c, 0x4e, 0x7e, 0xa7, 0x80, 0x93, 0xcf, 0x89, 0x70, 0xc2, 0x9d, 0x48, 0xa7, 0xb2, 0x13, 0xed, 0xb4, 0x70, 0xe2, 0x9c, 0x8e, 0x4e, + 0x07, 0xe7, 0x59, 0x67, 0x94, 0x3d, 0xcc, 0x1e, 0xea, 0xcc, 0x71, 0x5e, 0x74, 0x5e, 0x71, 0x56, 0x3a, 0x35, 0x9c, 0x18, 0xa7, 0x96, 0x53, 0xc7, 0xa9, 0xe9, 0xc4, 0x3a, 0xb5, 0x9d, 0xba, 0x66, 0x3b, 0xf3, 0x69, 0x33, 0xc1, 0xec, 0x68, 0xc6, 0x9b, 0x1d, 0xcc, 0xf6, 0xe6, 0x33, 0x4e, 0xa2, 0x93, 0xec, 0x74, 0x76, 0xba, 0x39, 0x49, 0x4e, 0x57, 0xa7, 0x8b, 0xd3, 0xdd, 0x3c, 0x60, 0x1e, 0x36, 0x0f, 0x99, 0x6f, 0x9b, 0x07, 0xcd, 0x23, 0xe6, 0x5b, 0xe6, 0x3b, 0xce, 0x1a, 0x67, 0xbd, 0xb3, 0xd6, 0xd9, 0xe0, 0xbc, 0xea, 0xbc, 0xe6, 0xac, 0x73, 0x36, 0x3a, 0x4f, 0x39, 0xc5, 0x9d, 0xa2, 0x4e, 0x09, 0xa7, 0x88, 0x13, 0xe5, 0x14, 0x73, 0x4a, 0x3a, 0xd5, 0x9c, 0xaa, 0x4e, 0x7d, 0xa7, 0xba, 0x53, 0xcf, 0xa9, 0xe2, 0x34, 0x30, 0xdb, 0x98, 0xad, 0xcd, 0x56, 0x66, 0x4b, 0xb3, 0xad, + 0x51, 0xcf, 0x68, 0x68, 0x44, 0x1b, 0xd5, 0x8c, 0xba, 0x4e, 0x4f, 0xa7, 0x93, 0xd3, 0xc3, 0x79, 0xc6, 0xe9, 0x65, 0xbe, 0x62, 0xae, 0x34, 0xf7, 0x99, 0x7b, 0xcd, 0xfd, 0x46, 0x0d, 0xa3, 0xba, 0x51, 0xd3, 0x19, 0xeb, 0x8c, 0xe1, 0x14, 0x38, 0xdc, 0x19, 0xed, 0x8c, 0x34, 0x7f, 0x30, 0xbf, 0x37, 0x2f, 0x98, 0xdf, 0x19, 0x31, 0x46, 0x2d, 0xa3, 0xb6, 0x33, 0xcf, 0x99, 0xeb, 0xcc, 0x72, 0x66, 0x3a, 0xe3, 0x9d, 0x09, 0xce, 0x4b, 0xce, 0x6c, 0x6b, 0xb6, 0x35, 0xcb, 0x9a, 0x69, 0xcd, 0xb0, 0xaa, 0x18, 0x75, 0x8c, 0x58, 0xa3, 0xaa, 0xf3, 0xba, 0xb3, 0xda, 0xd9, 0xe4, 0xac, 0x72, 0x36, 0x5b, 0x65, 0xad, 0x32, 0x4e, 0x21, 0xa7, 0xa0, 0x53, 0xd8, 0xa8, 0x62, 0x54, 0x36, 0x1a, 0x1b, 0x4d, 0x8c, 0xa6, 0x46, 0x23, 0xa3, 0x99, 0xd1, 0xdc, 0x88, 0x33, 0x5a, 0x18, 0x2d, 0x8d, 0x56, + 0x46, 0x6b, 0xa3, 0xac, 0x51, 0xda, 0x28, 0x63, 0x74, 0x32, 0x12, 0x95, 0x46, 0x46, 0x39, 0xa5, 0xb1, 0xd2, 0xc4, 0xf9, 0xda, 0xf8, 0xc5, 0xf8, 0x55, 0xa9, 0xa6, 0x54, 0x57, 0x6a, 0x19, 0xe5, 0x95, 0x1a, 0x4a, 0x4d, 0xa5, 0xb6, 0x51, 0x41, 0x69, 0xa7, 0xc4, 0x2b, 0xcd, 0x8c, 0x24, 0x7d, 0xbc, 0xbb, 0xe7, 0x32, 0x7e, 0x33, 0x6e, 0x28, 0x15, 0x8d, 0xeb, 0xfa, 0x97, 0x8e, 0xa1, 0x9f, 0xd4, 0x3f, 0xd3, 0x3f, 0xd7, 0x2f, 0xeb, 0x3f, 0xca, 0x32, 0xfd, 0x67, 0xfb, 0xae, 0x23, 0x8e, 0xd7, 0xc9, 0xa6, 0x2d, 0xb1, 0x1b, 0xe8, 0xd9, 0xf5, 0x1c, 0x7a, 0x4e, 0x3d, 0x97, 0x9e, 0x5b, 0xf9, 0x51, 0xe9, 0xa1, 0x0c, 0x54, 0x3a, 0xeb, 0x91, 0xea, 0x32, 0xe3, 0x53, 0x25, 0xc5, 0x38, 0xa9, 0x6c, 0x35, 0xbe, 0x30, 0xbe, 0x34, 0x3e, 0x33, 0xbe, 0x32, 0x3e, 0x37, 0x4e, 0x19, 0xa7, 0x8d, + 0x33, 0xc6, 0x27, 0x6a, 0x5f, 0xef, 0x8b, 0xca, 0x7b, 0xca, 0x07, 0xca, 0xfb, 0xca, 0x31, 0xe5, 0x63, 0xe5, 0x13, 0xe5, 0x23, 0xe5, 0x43, 0xe5, 0xb8, 0x72, 0x42, 0x79, 0x57, 0xd9, 0xae, 0x34, 0x57, 0xde, 0x56, 0xde, 0xd2, 0x0b, 0x2a, 0x3b, 0xf5, 0xc2, 0x46, 0x37, 0xbd, 0xb8, 0x72, 0x54, 0xf9, 0x49, 0xb9, 0x62, 0x9c, 0xb3, 0x6b, 0x1b, 0xe7, 0xad, 0xca, 0x6a, 0x2f, 0x6b, 0xab, 0xbd, 0xcc, 0xbb, 0x53, 0x5d, 0xa4, 0xf6, 0x56, 0x17, 0xab, 0x7d, 0xd4, 0x25, 0xea, 0x52, 0xb5, 0xbf, 0xba, 0x5c, 0x5d, 0x21, 0xb5, 0x24, 0x56, 0x12, 0xa4, 0x89, 0xb4, 0x17, 0x8f, 0xea, 0x84, 0xfe, 0x09, 0x0a, 0x45, 0xfe, 0xbf, 0xfd, 0xf9, 0xbf, 0xfb, 0x84, 0x2b, 0xc1, 0xb3, 0xa9, 0x65, 0x8e, 0xe1, 0x24, 0x9a, 0x99, 0x73, 0xe7, 0x22, 0xce, 0x9c, 0x8b, 0x39, 0x6f, 0x2e, 0xe1, 0xc4, 0x59, 0x96, + 0xd3, 0xe6, 0xf1, 0xe0, 0x79, 0x33, 0x1f, 0x27, 0x4e, 0xce, 0x9b, 0x9c, 0x36, 0xdb, 0x70, 0xde, 0x6c, 0x6b, 0xc7, 0x73, 0xde, 0x3c, 0xc0, 0x59, 0x93, 0x93, 0x26, 0xb9, 0xd0, 0x2d, 0x78, 0x46, 0x4e, 0x50, 0x9a, 0x29, 0xed, 0x65, 0x94, 0xd2, 0x45, 0x49, 0x52, 0x92, 0x65, 0x13, 0x67, 0xe5, 0x9d, 0x9c, 0x79, 0xf3, 0x2a, 0x6f, 0x29, 0x7b, 0x95, 0x7d, 0xca, 0x7e, 0xa5, 0x1f, 0x67, 0xe4, 0xc3, 0x44, 0xef, 0x1d, 0xf9, 0x46, 0x39, 0x22, 0x33, 0x65, 0x96, 0x13, 0xc6, 0x99, 0x76, 0x80, 0x3e, 0x8e, 0x33, 0xf6, 0x01, 0xe5, 0x6d, 0x75, 0x98, 0xb2, 0xcb, 0xfd, 0x17, 0x4b, 0x94, 0xba, 0x4a, 0x55, 0x51, 0x39, 0x39, 0x97, 0xb4, 0x32, 0xb9, 0x67, 0x43, 0x37, 0x0b, 0x83, 0x79, 0xe7, 0xe6, 0xe0, 0x1b, 0xca, 0x51, 0xfb, 0xa2, 0x35, 0x4e, 0xf1, 0x93, 0x41, 0x31, 0x4a, 0xac, 0x1b, 0x4d, + 0x32, 0xb1, 0xb9, 0x52, 0x4b, 0xa9, 0xad, 0x8f, 0xe7, 0xa4, 0x3d, 0x96, 0xf3, 0xf6, 0x78, 0x4e, 0xd9, 0xc1, 0xd3, 0xb5, 0xf9, 0xae, 0x7b, 0xb2, 0x36, 0x72, 0x70, 0xc2, 0x76, 0xcf, 0xd4, 0xed, 0x25, 0x99, 0x73, 0xc0, 0x36, 0x79, 0x33, 0xb4, 0xd3, 0x3f, 0x2c, 0x07, 0xd8, 0xed, 0xfb, 0x85, 0xff, 0x45, 0xf9, 0xfc, 0xd2, 0x26, 0xbe, 0x6e, 0x82, 0xcf, 0xd7, 0x78, 0xa7, 0x64, 0x68, 0xd1, 0xd8, 0xef, 0x69, 0xd5, 0x3e, 0xde, 0x5f, 0x3e, 0xcc, 0x5f, 0x24, 0x21, 0xb1, 0x9b, 0x6f, 0x7a, 0x9b, 0x78, 0xbf, 0x5a, 0x28, 0x69, 0x97, 0x25, 0x96, 0x74, 0xe9, 0x12, 0xd9, 0x39, 0x2c, 0x22, 0xc2, 0x2f, 0x09, 0x7e, 0x89, 0x8d, 0xac, 0xb3, 0x95, 0xec, 0x89, 0x4d, 0x8c, 0x29, 0xe1, 0x57, 0xa2, 0xfc, 0xbe, 0xc4, 0x6e, 0x25, 0xfc, 0x6a, 0x94, 0x2f, 0xd9, 0xe7, 0x3f, 0x18, 0xe7, 0xd7, 0x0b, + 0xb7, 0xdf, 0x5a, 0x44, 0x71, 0x62, 0xeb, 0x76, 0xa9, 0xdb, 0xf2, 0xe9, 0xf8, 0x88, 0xc8, 0x88, 0xb0, 0xe9, 0xf1, 0x3e, 0x7f, 0x5c, 0x5c, 0x7c, 0x84, 0xbf, 0x56, 0x42, 0x98, 0xcf, 0x1f, 0xed, 0xd6, 0xa2, 0x13, 0x12, 0x7c, 0x29, 0xa9, 0x44, 0x49, 0xc9, 0xfe, 0x22, 0x74, 0x85, 0x5a, 0x3e, 0x7f, 0x69, 0x77, 0xbc, 0xb4, 0x4b, 0x79, 0x30, 0x2e, 0xde, 0x87, 0x12, 0xd3, 0x93, 0x7c, 0x7e, 0x27, 0x2e, 0x3e, 0x91, 0x1e, 0x9f, 0x3b, 0xe6, 0xb8, 0xb5, 0x8a, 0x6e, 0xad, 0x62, 0x62, 0x58, 0x62, 0x42, 0x42, 0x42, 0x98, 0x5f, 0x29, 0x9e, 0x90, 0x10, 0xe9, 0x97, 0xb8, 0xf8, 0xae, 0x09, 0x09, 0x25, 0xfc, 0x5a, 0x94, 0x0f, 0x3e, 0x7a, 0xa1, 0x24, 0x14, 0x32, 0x62, 0xe3, 0xe2, 0xfd, 0x46, 0x64, 0x8c, 0xdf, 0x13, 0x19, 0x83, 0xfa, 0x09, 0x7e, 0x25, 0xb1, 0x84, 0x5f, 0x8f, 0x8a, 0x44, + 0x2f, 0x5f, 0x72, 0x8a, 0xd1, 0x39, 0xc6, 0xe7, 0x8e, 0xa4, 0x0a, 0x77, 0xff, 0xf6, 0x1b, 0x89, 0x75, 0xbb, 0xf8, 0xb5, 0x62, 0x11, 0xf4, 0xc7, 0xfa, 0xa6, 0xfb, 0xa6, 0xc3, 0x3b, 0xa5, 0xb4, 0x51, 0x08, 0xb3, 0x5a, 0xc4, 0x27, 0xc6, 0x85, 0x25, 0xb5, 0x4c, 0x88, 0x8f, 0x4c, 0x60, 0xb4, 0x56, 0xab, 0x78, 0x86, 0xc2, 0x5c, 0xa3, 0x42, 0x92, 0x4b, 0xf8, 0x8d, 0x28, 0xbf, 0x19, 0x5b, 0x7c, 0xab, 0xa8, 0xa9, 0xae, 0xf1, 0xd0, 0x8c, 0x8c, 0x89, 0xc4, 0xc5, 0x91, 0x31, 0x49, 0x7e, 0xb5, 0x73, 0x37, 0xbf, 0xd2, 0x05, 0xf9, 0x7e, 0xa3, 0x58, 0x09, 0xbf, 0x19, 0xe5, 0x73, 0x95, 0xf4, 0xc6, 0x76, 0xd9, 0xa9, 0x4b, 0x67, 0x9f, 0xcb, 0xc1, 0x5f, 0x2b, 0x31, 0xc1, 0x25, 0x49, 0xac, 0x13, 0x54, 0xd2, 0x8a, 0xda, 0x6a, 0x7a, 0x25, 0xb6, 0x6e, 0x4c, 0xb1, 0x88, 0x87, 0xce, 0xb6, + 0xa3, 0x1e, 0x77, 0xbe, 0x93, 0xca, 0x45, 0x29, 0x8e, 0x0a, 0xb1, 0x58, 0x9c, 0xe8, 0xab, 0x3b, 0x3d, 0x32, 0xc9, 0x0d, 0x44, 0xd0, 0x53, 0x12, 0xe6, 0x7a, 0xd3, 0xef, 0x0b, 0x43, 0xc9, 0x34, 0x2d, 0xfd, 0x5a, 0xa1, 0xc8, 0xa4, 0x3a, 0xa9, 0x22, 0xbc, 0xff, 0x30, 0xdd, 0x5f, 0x90, 0x59, 0x12, 0xf6, 0xa7, 0x69, 0x8f, 0x4e, 0x4a, 0x17, 0x15, 0x34, 0x68, 0xab, 0xd7, 0xd1, 0xea, 0xc6, 0x47, 0x84, 0x45, 0x46, 0x24, 0x14, 0x8b, 0x28, 0xe1, 0x4f, 0x1f, 0x95, 0xa2, 0xaa, 0x75, 0xfd, 0xc9, 0x49, 0x75, 0x4a, 0xf8, 0x33, 0x44, 0x41, 0xe8, 0xf3, 0xf9, 0xd3, 0xc5, 0x36, 0x72, 0xa7, 0x53, 0x89, 0x8c, 0x49, 0xf0, 0xa7, 0x77, 0x5b, 0x2d, 0x69, 0xa5, 0xa7, 0x55, 0xc2, 0x9f, 0x11, 0x36, 0x99, 0x82, 0x2e, 0xf1, 0xe1, 0x81, 0x2e, 0xc8, 0xf5, 0x67, 0x88, 0x4d, 0xf4, 0x4d, 0x4f, 0xf4, + 0xf9, 0x33, 0xe0, 0xb4, 0x12, 0xfe, 0x4c, 0x51, 0x8d, 0x5b, 0xc7, 0xa7, 0xe8, 0xc9, 0x75, 0x12, 0x0a, 0xfa, 0xd3, 0x77, 0x8d, 0x1c, 0x51, 0xc2, 0x9f, 0x39, 0xaa, 0x71, 0x8b, 0xf8, 0xc6, 0xad, 0x52, 0x3b, 0xc3, 0x22, 0xe8, 0xcf, 0x1a, 0xec, 0xcf, 0x12, 0x95, 0x22, 0x19, 0x63, 0xdb, 0xc4, 0xa7, 0x64, 0xcc, 0x18, 0xeb, 0x57, 0x92, 0x62, 0xfc, 0x19, 0x8b, 0xbb, 0x49, 0x4a, 0xea, 0xc6, 0xa4, 0xa4, 0x73, 0xff, 0x4a, 0xcf, 0x5f, 0x7e, 0x25, 0x07, 0x91, 0xd0, 0x0a, 0xc5, 0xc5, 0xa7, 0xb8, 0xce, 0xc3, 0xda, 0x98, 0xe9, 0x84, 0xd7, 0x15, 0x5b, 0x2c, 0x22, 0x92, 0x69, 0x69, 0xf5, 0xb0, 0xd4, 0x71, 0x77, 0x0a, 0xb9, 0xef, 0xf6, 0x24, 0x60, 0x49, 0x7d, 0xf4, 0xaf, 0x4f, 0xef, 0xe3, 0xa1, 0xfa, 0x87, 0x00, 0xa6, 0x88, 0x64, 0x8d, 0xc4, 0x5b, 0xb1, 0x7e, 0xa9, 0xb1, 0x55, 0x51, + 0x94, 0x60, 0xac, 0xb2, 0x46, 0x49, 0x8a, 0xa8, 0x75, 0x5b, 0xc7, 0xfb, 0x33, 0x46, 0xc6, 0xf8, 0xea, 0xfa, 0xbd, 0x24, 0xa5, 0x13, 0x49, 0xbe, 0xc5, 0xf8, 0x12, 0x11, 0xbf, 0x23, 0x53, 0x26, 0x85, 0x35, 0x30, 0x26, 0x66, 0x7a, 0x62, 0x4a, 0x16, 0x4f, 0x71, 0xff, 0xd0, 0xe2, 0x61, 0x05, 0x70, 0x53, 0x36, 0x6c, 0xcb, 0x5a, 0xbc, 0x84, 0x3f, 0x7b, 0x54, 0x8a, 0xe2, 0x96, 0x39, 0xf0, 0xb3, 0x5b, 0xe6, 0x8c, 0x4a, 0xd1, 0xdc, 0x32, 0x57, 0x54, 0x8a, 0xee, 0x96, 0xb9, 0xa3, 0x52, 0x0c, 0xb7, 0xcc, 0x13, 0x95, 0xe2, 0x71, 0xcb, 0xb0, 0xa8, 0x14, 0xd3, 0x2d, 0xf3, 0x46, 0xa5, 0x58, 0x6e, 0x99, 0x2f, 0x2a, 0xc5, 0x76, 0xcb, 0xa2, 0x51, 0xbe, 0x92, 0x7e, 0xa5, 0x63, 0x09, 0x7f, 0xb1, 0x60, 0x65, 0x60, 0x09, 0x7f, 0xf1, 0x60, 0x65, 0x50, 0x09, 0x7f, 0xfe, 0x28, 0xf1, 0xa7, + 0x2f, 0xfe, 0xdf, 0xd0, 0x31, 0x1c, 0x1d, 0xf3, 0xc3, 0xdb, 0x87, 0x8e, 0x6e, 0x19, 0x81, 0x8e, 0x6e, 0x59, 0x00, 0x1d, 0xdd, 0x32, 0x12, 0x1d, 0xdd, 0xb2, 0x20, 0x3a, 0xba, 0x65, 0x21, 0x74, 0x74, 0xcb, 0xc2, 0xe8, 0xe8, 0x96, 0x4f, 0xa1, 0xa3, 0x5b, 0x16, 0x41, 0x47, 0xb7, 0x8c, 0x8a, 0xf2, 0x55, 0x0b, 0xa6, 0x5a, 0x89, 0x28, 0xc4, 0x66, 0x4a, 0xf4, 0xc5, 0x12, 0x9f, 0xc4, 0xd8, 0x60, 0x38, 0x78, 0x7c, 0xa2, 0xdc, 0x7c, 0x2b, 0x19, 0xe5, 0x2f, 0x51, 0xdc, 0x5f, 0x82, 0x27, 0xa9, 0x14, 0x49, 0x5c, 0xdf, 0xf7, 0x0f, 0x91, 0x88, 0x4c, 0x8a, 0x8e, 0x74, 0x97, 0xb1, 0x7f, 0x49, 0x41, 0x2a, 0x95, 0xf0, 0x97, 0x7e, 0x18, 0x1e, 0x25, 0x87, 0xbf, 0x54, 0xb1, 0x14, 0x43, 0xc9, 0x5e, 0x37, 0x9e, 0x65, 0xc8, 0x35, 0xb0, 0xcc, 0xa3, 0x9e, 0xf9, 0xeb, 0x70, 0xd9, 0x28, 0x5f, + 0x85, 0xa0, 0xbe, 0xe5, 0xa0, 0x53, 0xea, 0xfe, 0x55, 0x08, 0x4f, 0xd8, 0xdf, 0x0a, 0x77, 0xfb, 0x25, 0xc7, 0xf6, 0xe0, 0xcb, 0xb4, 0x4e, 0x8d, 0xc8, 0xe8, 0x94, 0xb2, 0x4a, 0x76, 0x2c, 0x2a, 0x8f, 0xfd, 0x28, 0xfc, 0xf7, 0xfa, 0x92, 0xd8, 0x49, 0xd1, 0x25, 0xfc, 0x15, 0xa2, 0x4a, 0xe6, 0xac, 0x56, 0xc2, 0x5f, 0xf1, 0xdf, 0x91, 0x92, 0x84, 0x5d, 0x20, 0xaf, 0x44, 0x48, 0x24, 0x47, 0x21, 0x5f, 0x49, 0x5f, 0x7d, 0xf7, 0xe1, 0xc5, 0x95, 0x0d, 0xa7, 0x4f, 0xaf, 0x1f, 0x59, 0x9f, 0xa7, 0x3d, 0x9e, 0x65, 0x9d, 0x65, 0x91, 0x27, 0xba, 0xa2, 0xa2, 0x64, 0xcf, 0x86, 0xfc, 0x68, 0x56, 0x99, 0x1c, 0x3c, 0x20, 0xfc, 0x2f, 0x48, 0xe2, 0xb7, 0x63, 0x8b, 0x77, 0x9d, 0x5e, 0x32, 0xd2, 0xe7, 0xab, 0x36, 0x1d, 0x5e, 0x95, 0xff, 0x1c, 0xf6, 0x95, 0x4c, 0xe5, 0xe1, 0xd7, 0xe1, 0x09, + 0x95, 0xcf, 0x9f, 0xe8, 0x3e, 0xef, 0xb5, 0x5a, 0xc4, 0x6f, 0x53, 0x7d, 0x9a, 0x2f, 0x6c, 0x9b, 0x5a, 0x58, 0xcb, 0x93, 0x10, 0xe3, 0xae, 0x81, 0x16, 0xab, 0x69, 0x64, 0x90, 0x3a, 0xb2, 0x1e, 0x4f, 0x5f, 0xec, 0x93, 0x8f, 0x52, 0xa2, 0xbb, 0x0e, 0xa5, 0x2e, 0xf6, 0x6a, 0x6c, 0x62, 0x72, 0xa4, 0x5f, 0x8b, 0x4d, 0x4a, 0x66, 0x58, 0x8d, 0x4d, 0x0a, 0xa3, 0x9e, 0xe8, 0xae, 0x41, 0x4f, 0xce, 0x49, 0x42, 0x25, 0x16, 0xe6, 0xc8, 0x7a, 0xc4, 0x30, 0x12, 0x09, 0xf5, 0xb0, 0x8b, 0x22, 0x28, 0x05, 0x7e, 0x7f, 0x23, 0x24, 0x32, 0x75, 0xb5, 0xd3, 0x79, 0xc0, 0xf1, 0xbd, 0x41, 0x42, 0x19, 0x7f, 0xe1, 0x0a, 0x47, 0xd7, 0xa2, 0x42, 0x41, 0x25, 0xf8, 0x3b, 0x2e, 0x75, 0x95, 0xfb, 0x53, 0x16, 0x21, 0xaf, 0xe2, 0xfa, 0xc0, 0x47, 0x8f, 0x51, 0x38, 0xe4, 0x83, 0xc8, 0x6a, 0xb8, 0xa6, + 0x6a, 0xb0, 0xdb, 0x6f, 0xf1, 0xf0, 0xf8, 0x7c, 0xf5, 0x22, 0xeb, 0xbb, 0xc2, 0xdc, 0x68, 0x55, 0x0b, 0xba, 0xcc, 0x35, 0x20, 0xe4, 0x51, 0x69, 0x1d, 0x5f, 0xd2, 0x57, 0x8d, 0x77, 0xa3, 0xab, 0x71, 0xa8, 0xd3, 0xe7, 0xea, 0x92, 0xe6, 0x72, 0x4f, 0x21, 0x5a, 0x0d, 0x1f, 0x7d, 0xfb, 0xa6, 0x06, 0xea, 0xef, 0x32, 0x38, 0x14, 0x99, 0x48, 0x37, 0x8d, 0xab, 0x87, 0x34, 0x88, 0x4d, 0x0b, 0x4d, 0xa2, 0xfb, 0x7a, 0x7e, 0xd2, 0xc4, 0xb4, 0x50, 0xd6, 0x88, 0x8a, 0xf4, 0x95, 0x74, 0xbd, 0x56, 0x8f, 0x85, 0xb9, 0x5a, 0x42, 0xc9, 0x94, 0x92, 0x4a, 0x36, 0x1e, 0xc0, 0x9a, 0x0f, 0xbb, 0xe3, 0x1e, 0xed, 0xae, 0xf5, 0x38, 0xf5, 0xdf, 0xd2, 0xd4, 0x8e, 0xf2, 0x47, 0x17, 0xff, 0x5b, 0xa6, 0x31, 0x51, 0xfe, 0xca, 0xc5, 0xa7, 0x23, 0xd8, 0x4d, 0x16, 0xb4, 0xfd, 0x2b, 0x0d, 0x61, 0x29, + 0xe9, 0x2f, 0x09, 0x69, 0xec, 0xc3, 0x0c, 0x4b, 0xf3, 0xae, 0x9b, 0x5c, 0x91, 0xa4, 0x7a, 0x49, 0x1e, 0x92, 0x54, 0x76, 0x75, 0x58, 0x34, 0x58, 0xc3, 0xff, 0x1b, 0xa9, 0x58, 0xff, 0x7f, 0x2a, 0xfb, 0x5c, 0xf5, 0xdd, 0xf5, 0xa5, 0x5a, 0x24, 0x4b, 0xc8, 0x23, 0xf1, 0x8e, 0x48, 0x08, 0xe9, 0x58, 0xd7, 0x75, 0x46, 0x9a, 0xfd, 0xf5, 0x5c, 0xfb, 0x23, 0x22, 0x43, 0x0e, 0x08, 0xd9, 0xf1, 0xd0, 0xe4, 0xfa, 0x98, 0x9c, 0x3d, 0xf5, 0xe1, 0xe4, 0xed, 0xce, 0x73, 0x98, 0xb5, 0xa4, 0xbf, 0x3c, 0xcf, 0x62, 0x83, 0x7f, 0xe8, 0x6f, 0xc8, 0x9a, 0xab, 0x64, 0xcb, 0xea, 0xaf, 0x40, 0xbd, 0x51, 0x94, 0xbf, 0x12, 0x45, 0x63, 0xd7, 0x6b, 0x75, 0xf1, 0xab, 0xaf, 0x1e, 0xaf, 0xb2, 0x34, 0x3f, 0x35, 0x89, 0x72, 0xd3, 0xd1, 0xdf, 0x98, 0x6a, 0xd3, 0xa8, 0xad, 0xac, 0x33, 0x54, 0x9a, 0x51, + 0x51, 0xdc, 0x4a, 0xf3, 0xa8, 0xad, 0x4a, 0xb0, 0x27, 0x8e, 0x4a, 0xb0, 0xa7, 0x85, 0x4b, 0x53, 0x97, 0x4a, 0x4b, 0x97, 0xc6, 0xad, 0xb4, 0x72, 0x69, 0xdc, 0x4a, 0x6b, 0x97, 0xc6, 0xad, 0xb4, 0x71, 0x69, 0x6a, 0x53, 0x69, 0xeb, 0xd2, 0xb8, 0x95, 0x76, 0x2e, 0x8d, 0x5b, 0x89, 0x77, 0x69, 0xdc, 0x4a, 0x82, 0x4b, 0x13, 0x4b, 0xa5, 0xbd, 0x4b, 0xe3, 0x56, 0x9e, 0x76, 0x69, 0xdc, 0x4a, 0x07, 0x97, 0xc6, 0xad, 0x74, 0x74, 0x69, 0xea, 0x51, 0x79, 0xc6, 0xa5, 0x71, 0x2b, 0x9d, 0x5c, 0x1a, 0xb7, 0x92, 0xe8, 0xd2, 0xb8, 0x95, 0x24, 0x97, 0x26, 0x86, 0x4a, 0x67, 0x97, 0xc6, 0xad, 0x74, 0x71, 0x69, 0xdc, 0x4a, 0xb2, 0x4b, 0xe3, 0x56, 0xba, 0x46, 0xf9, 0xab, 0x3c, 0x74, 0x73, 0x37, 0xb7, 0xe1, 0xaf, 0x41, 0xad, 0x7b, 0xb0, 0x56, 0x93, 0x5a, 0x8f, 0x60, 0x3e, 0xd1, 0xa8, 0x45, + 0xa3, 0x67, 0x94, 0xbf, 0xea, 0x43, 0xea, 0x5e, 0x6e, 0x23, 0x48, 0xdd, 0x3b, 0x58, 0x73, 0xa9, 0xfb, 0x04, 0x6b, 0x2e, 0x69, 0xdf, 0x28, 0x7f, 0xb5, 0x87, 0xa4, 0xfd, 0xdc, 0x46, 0x90, 0xb4, 0x7f, 0xb0, 0xe6, 0x92, 0x0e, 0x08, 0xd6, 0x5c, 0xd2, 0x81, 0x51, 0xfe, 0xea, 0x0f, 0x49, 0x07, 0xb9, 0x8d, 0x20, 0xe9, 0xe0, 0x60, 0xcd, 0x25, 0x1d, 0x12, 0xac, 0xb9, 0xa4, 0x43, 0xa3, 0xb6, 0xd9, 0xba, 0x9a, 0xb6, 0x79, 0x8a, 0x29, 0xee, 0xb7, 0xba, 0xfa, 0xb5, 0x82, 0x71, 0x23, 0xd2, 0xde, 0x29, 0x25, 0x44, 0xd8, 0x8c, 0x1f, 0x6d, 0xc9, 0x46, 0x45, 0x99, 0xc5, 0xca, 0x9c, 0xba, 0x00, 0x0c, 0x48, 0x11, 0x4f, 0xcc, 0x1b, 0xfd, 0xda, 0xd7, 0xab, 0x58, 0xc0, 0x90, 0x62, 0x6e, 0xa3, 0x56, 0xba, 0x24, 0xab, 0xa9, 0x55, 0xdd, 0x53, 0xd4, 0xca, 0x69, 0x1a, 0x56, 0xa8, 0xab, 0xb7, + 0xa7, 0x9d, 0xa7, 0xae, 0x5e, 0xce, 0xe3, 0x33, 0x82, 0x5d, 0xde, 0x98, 0xfd, 0xb9, 0xc6, 0x67, 0x1d, 0x9f, 0x71, 0xbc, 0x33, 0xde, 0xe3, 0x5e, 0x68, 0xb0, 0xe9, 0xcb, 0x14, 0xb3, 0x9f, 0xe3, 0x60, 0xda, 0x7f, 0xc1, 0x3e, 0x2d, 0xb9, 0x4e, 0x4a, 0x41, 0x65, 0x6a, 0x0b, 0x72, 0x7b, 0x6a, 0x7c, 0xb0, 0x55, 0xd8, 0x6d, 0xed, 0xb6, 0xc6, 0x8b, 0xa2, 0xd7, 0x9a, 0xda, 0xa5, 0x75, 0xb0, 0x93, 0xad, 0x76, 0x42, 0xad, 0x74, 0x5d, 0xac, 0xe6, 0x56, 0x4d, 0x4f, 0x71, 0x2b, 0xb7, 0x69, 0xa4, 0x2b, 0xb6, 0x53, 0x09, 0x4c, 0xf6, 0xeb, 0x33, 0x79, 0xb9, 0xd4, 0x49, 0x31, 0x92, 0xeb, 0xfc, 0x67, 0xe7, 0xc7, 0xf7, 0xf9, 0x6f, 0xa1, 0x2c, 0x74, 0xff, 0xc5, 0x41, 0xed, 0x8e, 0xc8, 0x83, 0x8d, 0xee, 0xbf, 0x47, 0x68, 0x6c, 0x0a, 0xfc, 0xaa, 0x5b, 0x81, 0xdf, 0x52, 0x5b, 0x8f, 0xd2, + 0x18, 0x4b, 0xb5, 0x1b, 0x81, 0xeb, 0x0f, 0x36, 0xa5, 0x51, 0x19, 0x1b, 0xa1, 0xbb, 0x1e, 0xa2, 0x9b, 0xc5, 0x7f, 0xbd, 0xa5, 0xb7, 0x67, 0x12, 0x54, 0x73, 0xd5, 0x2b, 0x6e, 0x9f, 0x67, 0xb2, 0xb1, 0x46, 0x7b, 0x41, 0x3d, 0xfb, 0xd8, 0xf8, 0x00, 0x6d, 0xee, 0x63, 0x34, 0x23, 0xb4, 0x17, 0x1e, 0xa3, 0x7b, 0x54, 0xde, 0xfe, 0xc7, 0xb4, 0xba, 0x66, 0xec, 0x0f, 0xea, 0xf5, 0x72, 0xea, 0xbf, 0x9a, 0x98, 0xfa, 0x47, 0xd1, 0x1f, 0xc1, 0x0c, 0x8e, 0xc4, 0xae, 0x2d, 0x4d, 0x44, 0x74, 0x2b, 0xf8, 0xaf, 0xc2, 0x89, 0x87, 0x3e, 0x93, 0xba, 0xb9, 0x53, 0xc4, 0xca, 0xe5, 0xfe, 0x13, 0x90, 0xa0, 0xb5, 0x88, 0x53, 0x04, 0x8c, 0x17, 0xf1, 0x32, 0xe6, 0x2d, 0x0f, 0xce, 0x88, 0xa4, 0x1b, 0xe1, 0x7e, 0xd9, 0x06, 0x71, 0x60, 0x2f, 0x38, 0x29, 0x92, 0x61, 0x08, 0x38, 0x2f, 0x92, 0xf1, + 0xa8, 0x48, 0xa6, 0x1e, 0x22, 0x99, 0xa7, 0xb2, 0xb5, 0x8c, 0x06, 0x07, 0x45, 0xb2, 0x41, 0x97, 0x9d, 0x73, 0x6c, 0x0e, 0x64, 0xe7, 0xb8, 0x26, 0x92, 0x6b, 0x1e, 0x40, 0x66, 0x6e, 0xc2, 0x90, 0x1b, 0x79, 0x79, 0x38, 0xe1, 0x85, 0xf9, 0x00, 0x7c, 0xf2, 0x42, 0x97, 0xf7, 0x98, 0x48, 0xbe, 0xd2, 0x22, 0xf9, 0x1b, 0x88, 0x84, 0xc3, 0x3f, 0x02, 0x1e, 0x05, 0xe0, 0x1b, 0xc9, 0x06, 0xa4, 0x10, 0xba, 0x15, 0xa6, 0xaf, 0x28, 0x63, 0xc5, 0x99, 0x53, 0xe2, 0x8a, 0x48, 0x49, 0xc6, 0x4a, 0x22, 0xbb, 0x14, 0xba, 0x95, 0xc6, 0x13, 0x65, 0xe8, 0x2b, 0x07, 0xdf, 0xf2, 0xc8, 0xad, 0x78, 0x42, 0xa4, 0x0a, 0x72, 0xab, 0x4e, 0x12, 0xa9, 0x8e, 0x2d, 0x35, 0xb1, 0xa9, 0x26, 0x3a, 0xd4, 0xc2, 0xfe, 0xda, 0xd8, 0x51, 0xfb, 0x45, 0x9e, 0x5a, 0xe4, 0xc6, 0x30, 0x27, 0x16, 0xba, 0x3a, 0xd8, + 0x58, 0xe7, 0x08, 0x2b, 0x0b, 0x32, 0xea, 0x57, 0x03, 0xd0, 0xbb, 0xff, 0xc6, 0x66, 0x43, 0xec, 0x6b, 0x88, 0x8f, 0x1a, 0xae, 0x21, 0xe9, 0xe9, 0x6f, 0xc2, 0xdc, 0xa6, 0xf8, 0xaf, 0x19, 0x65, 0x1c, 0xed, 0x96, 0xd8, 0xdc, 0x2a, 0x4a, 0xa4, 0x35, 0xb6, 0xb5, 0x81, 0x57, 0xbb, 0xf5, 0x22, 0x09, 0xb5, 0x44, 0xda, 0xd3, 0x7e, 0x1a, 0xdf, 0x3d, 0x8d, 0x3e, 0x1d, 0xa0, 0xef, 0x00, 0xef, 0x8e, 0x2e, 0xd0, 0xb3, 0x13, 0x73, 0x12, 0xf1, 0x63, 0xe2, 0x68, 0x91, 0x24, 0xec, 0xe2, 0x6d, 0x2b, 0x9d, 0xa1, 0xed, 0x8c, 0x5d, 0x9d, 0x19, 0xef, 0x82, 0x6e, 0xc9, 0xf4, 0x75, 0x4d, 0x14, 0xe9, 0x06, 0x7a, 0x40, 0xd3, 0x13, 0xfa, 0x9e, 0xd8, 0xda, 0xeb, 0x9e, 0x48, 0xef, 0x0b, 0x22, 0x7d, 0xb0, 0xbd, 0x1f, 0x63, 0x03, 0xe8, 0x43, 0x45, 0x19, 0x8a, 0x2e, 0x43, 0xd1, 0x7d, 0x58, 0x32, + 0x80, 0xef, 0x70, 0x62, 0x30, 0x02, 0x3e, 0xa3, 0xb0, 0xe1, 0x59, 0xc6, 0x46, 0x83, 0x31, 0xf8, 0x64, 0x3c, 0x7c, 0x26, 0x50, 0x9f, 0x80, 0xcf, 0x26, 0xd2, 0x7e, 0x8e, 0x3a, 0x2e, 0x92, 0x49, 0xc4, 0x6d, 0x32, 0x7e, 0x98, 0x8c, 0x6e, 0x53, 0xd0, 0x7f, 0x0a, 0xfc, 0x9f, 0x27, 0x4e, 0x53, 0x91, 0x3d, 0x15, 0xbe, 0xd3, 0x06, 0x88, 0x4c, 0xcf, 0x0b, 0x90, 0xfd, 0x02, 0x34, 0x33, 0x98, 0x3f, 0x13, 0x3f, 0xcf, 0x62, 0xce, 0x2c, 0x74, 0x9e, 0x4d, 0xae, 0xcc, 0x26, 0x4e, 0x2f, 0x62, 0xeb, 0x8b, 0xc8, 0x7c, 0x11, 0x5f, 0xcf, 0x21, 0x7e, 0x2f, 0xd1, 0x37, 0x37, 0x5e, 0x64, 0x5e, 0x41, 0x80, 0x4d, 0x2f, 0xe3, 0xab, 0x97, 0xf1, 0xcd, 0x7c, 0xe4, 0xce, 0x87, 0xef, 0x02, 0xfc, 0xbe, 0x60, 0x21, 0xc0, 0x8e, 0x85, 0xc4, 0x69, 0x21, 0x72, 0x16, 0xd2, 0xbf, 0x08, 0xb9, 0x8b, 0xe0, + 0xb3, 0x08, 0x1d, 0x17, 0x93, 0x13, 0x8b, 0xe1, 0xbf, 0x04, 0x3d, 0x97, 0x30, 0x77, 0x29, 0xfa, 0x2d, 0x25, 0x76, 0xcb, 0xf0, 0xd9, 0x32, 0xf8, 0x2c, 0x23, 0x36, 0xcb, 0xa9, 0x2f, 0xc7, 0xf7, 0x2b, 0xe0, 0xbf, 0x92, 0xfa, 0x4a, 0x64, 0xbf, 0x02, 0xef, 0x57, 0xe8, 0x7b, 0x05, 0x9f, 0xad, 0x42, 0x87, 0xd5, 0xe8, 0xb0, 0x1a, 0x7e, 0x6b, 0x98, 0xb3, 0x06, 0xde, 0x6b, 0x18, 0x7b, 0x35, 0x1b, 0x20, 0xef, 0xd6, 0x22, 0x63, 0xed, 0x4a, 0x80, 0x1e, 0xeb, 0xd0, 0x67, 0x3d, 0xf9, 0xb8, 0x1e, 0xbb, 0x5e, 0x03, 0x1b, 0xd1, 0x6b, 0x13, 0xbe, 0x7e, 0x1d, 0x47, 0x6f, 0xc6, 0x7f, 0x5b, 0xe0, 0xeb, 0xa7, 0x9d, 0x42, 0xbc, 0x52, 0xb0, 0x69, 0x2b, 0xfe, 0xd8, 0x86, 0xdf, 0xb7, 0x11, 0xeb, 0x1d, 0xf4, 0xbd, 0x01, 0xef, 0x37, 0xf0, 0xd3, 0x9b, 0xf8, 0x70, 0x27, 0xba, 0xed, 0x86, 0xd7, + 0x6e, 0x74, 0xd8, 0xb3, 0x49, 0x64, 0x2f, 0xf3, 0xf6, 0x61, 0xdb, 0x3e, 0xea, 0xfb, 0xb1, 0xe9, 0x00, 0xf6, 0x1e, 0x64, 0xde, 0x21, 0x78, 0xbf, 0xc5, 0xbc, 0xb7, 0x98, 0x73, 0x98, 0x3c, 0x39, 0xcc, 0xfc, 0x23, 0xe8, 0x79, 0x84, 0xe7, 0xe5, 0x6d, 0x74, 0x7c, 0x1b, 0x1e, 0xef, 0x20, 0xfb, 0x1d, 0xf8, 0x1c, 0x45, 0xd7, 0xa3, 0xcc, 0x79, 0x0f, 0x5f, 0xbf, 0x87, 0xbe, 0xef, 0x5f, 0x12, 0xf9, 0x80, 0x39, 0xc7, 0xc8, 0xd3, 0x63, 0xd8, 0xf2, 0x21, 0x7c, 0x3f, 0x84, 0xee, 0x23, 0x68, 0x8e, 0x23, 0xeb, 0x38, 0xf9, 0x70, 0x9c, 0x78, 0x9c, 0xe0, 0xd9, 0x39, 0x41, 0xdf, 0xc7, 0xe4, 0xf6, 0xc7, 0xc8, 0xfa, 0x18, 0xdb, 0x3e, 0x81, 0xfe, 0x53, 0x62, 0xf4, 0x59, 0x3a, 0x00, 0x8f, 0xcf, 0xb1, 0xe3, 0x73, 0x64, 0x7f, 0x81, 0x0f, 0x4e, 0x61, 0xdb, 0x97, 0x37, 0x44, 0xbe, 0x22, 0x1e, + 0x5f, 0x41, 0x7b, 0x1a, 0x3b, 0xcf, 0xa0, 0xcb, 0x19, 0xea, 0x67, 0xf1, 0xc9, 0x59, 0x6c, 0x38, 0x87, 0x8e, 0xe7, 0xe0, 0x71, 0x1e, 0xbd, 0xbf, 0xc1, 0x86, 0x6f, 0xa8, 0x7f, 0x8b, 0xcd, 0xdf, 0x21, 0xf3, 0x3b, 0xe6, 0xc1, 0x4a, 0x2e, 0xd0, 0xfe, 0x01, 0x9f, 0xfd, 0x40, 0xec, 0x7e, 0x60, 0xee, 0x45, 0xf8, 0x5c, 0x24, 0x3e, 0x97, 0xb0, 0xe3, 0x12, 0x32, 0x2f, 0x23, 0xf3, 0x32, 0xcf, 0xce, 0x8f, 0xe0, 0x27, 0xf4, 0xbc, 0x4a, 0xff, 0x55, 0x6c, 0xfa, 0x19, 0x5d, 0x7f, 0x81, 0xf6, 0x17, 0x72, 0xed, 0x1a, 0xf6, 0x5f, 0xa3, 0xfc, 0x15, 0xbd, 0x7f, 0x23, 0x2f, 0xaf, 0x93, 0x03, 0xd7, 0xe1, 0x7f, 0x1d, 0xfb, 0x6f, 0x90, 0x83, 0x37, 0x88, 0xef, 0x0d, 0x9e, 0xc9, 0x1b, 0xf0, 0xbb, 0x81, 0xd0, 0x1b, 0xd8, 0x7f, 0x93, 0x5c, 0xb8, 0x49, 0xdf, 0x4d, 0x9e, 0x83, 0x9b, 0xcc, 0xbd, + 0x85, 0xce, 0xb7, 0x98, 0x77, 0x0b, 0xda, 0x5b, 0xd8, 0x77, 0x0b, 0xda, 0xdb, 0xe4, 0xca, 0x6d, 0xfc, 0x72, 0x1b, 0x7d, 0x6f, 0x63, 0xef, 0x6d, 0xe6, 0xde, 0x21, 0xcf, 0xef, 0xe0, 0xcb, 0x3b, 0xd4, 0x7f, 0x87, 0xfe, 0x77, 0xd6, 0x8c, 0x3f, 0x78, 0x9e, 0xfe, 0xc0, 0xb6, 0xbb, 0xee, 0xff, 0x87, 0xb5, 0x60, 0xe8, 0xff, 0x6b, 0x8a, 0xdf, 0x02, 0x2b, 0x45, 0x91, 0x93, 0x6c, 0x7c, 0xaa, 0x01, 0xea, 0x6a, 0x69, 0xc0, 0x4b, 0x49, 0x5d, 0x28, 0x8a, 0x76, 0x4a, 0x14, 0xa3, 0x88, 0x28, 0x9e, 0xed, 0xa2, 0x58, 0x94, 0xf6, 0x41, 0x51, 0x9c, 0x49, 0xa2, 0xa4, 0xeb, 0x01, 0xee, 0x88, 0x92, 0xfe, 0xbc, 0x28, 0x19, 0x59, 0x96, 0x33, 0xe5, 0x15, 0x25, 0xf3, 0x26, 0x51, 0xb2, 0xbc, 0x28, 0x4a, 0xd6, 0x3e, 0xec, 0x9a, 0xb2, 0x81, 0x23, 0xa2, 0x64, 0x1f, 0x20, 0x4a, 0xce, 0x64, 0x51, + 0x72, 0xcd, 0x10, 0x25, 0x37, 0xc8, 0x13, 0x0f, 0xfc, 0x00, 0x3e, 0x79, 0x4e, 0x00, 0xe6, 0xe7, 0xb9, 0x26, 0x4a, 0x18, 0x3c, 0xc2, 0x32, 0x01, 0x1f, 0x40, 0x7e, 0xd8, 0x3d, 0x51, 0xf2, 0x32, 0x8f, 0xb5, 0x54, 0xe1, 0xcc, 0xac, 0xe4, 0x87, 0x57, 0xf8, 0x54, 0x51, 0x7c, 0x71, 0x60, 0xa7, 0x28, 0x11, 0x7b, 0x45, 0x29, 0x80, 0x8e, 0x91, 0xc8, 0x8d, 0x44, 0x9f, 0xc8, 0x79, 0x60, 0x0d, 0x40, 0xcf, 0x48, 0x68, 0x23, 0xb1, 0x27, 0xf2, 0x82, 0x28, 0x05, 0xa1, 0x29, 0x78, 0x45, 0x94, 0x42, 0xc8, 0x2c, 0x8c, 0x6d, 0x4f, 0xc1, 0xef, 0xa9, 0x1b, 0xa2, 0x14, 0xe9, 0x20, 0x4a, 0xd1, 0x5c, 0x20, 0x1a, 0xa0, 0x53, 0xd1, 0x11, 0x00, 0x7b, 0x8b, 0xc2, 0xbb, 0x28, 0x36, 0x17, 0xc5, 0xb6, 0x62, 0x8c, 0x17, 0x63, 0xbc, 0x18, 0xe3, 0xc5, 0x18, 0x2f, 0xc6, 0x78, 0x31, 0xc6, 0x8b, 0x31, + 0x5e, 0x8c, 0xf1, 0xe2, 0xe8, 0x18, 0x95, 0x28, 0x4a, 0x09, 0xf4, 0x2e, 0x39, 0x04, 0xd0, 0x2e, 0x8d, 0xed, 0x65, 0x90, 0x55, 0x16, 0xbd, 0xca, 0xad, 0x17, 0x85, 0x75, 0x5c, 0x29, 0x8f, 0x4f, 0xca, 0x63, 0x6f, 0x79, 0xec, 0xac, 0xd0, 0x00, 0x8c, 0x06, 0x47, 0x45, 0xa9, 0x08, 0xcf, 0x8a, 0xe8, 0x5a, 0x89, 0x76, 0x34, 0x73, 0xa2, 0x99, 0x5f, 0xb9, 0x0e, 0x40, 0x46, 0x15, 0x6c, 0xae, 0x82, 0xbe, 0x55, 0xf1, 0x4b, 0x55, 0x6c, 0xad, 0x46, 0xbd, 0xfa, 0x31, 0x51, 0x6a, 0xa0, 0x7b, 0x4d, 0x6c, 0xad, 0x85, 0x7f, 0x6b, 0xe1, 0x9f, 0x5a, 0x67, 0xd8, 0x1c, 0xb6, 0x06, 0x94, 0x31, 0xb4, 0x63, 0xd0, 0x2d, 0x16, 0x99, 0xb1, 0xd8, 0x5f, 0xa7, 0x09, 0x60, 0x4e, 0x5d, 0xda, 0xf5, 0xa0, 0xaf, 0x07, 0xdf, 0xfa, 0xc8, 0xaf, 0x8f, 0xcf, 0x1b, 0x40, 0xdb, 0x30, 0x1d, 0x40, 0xb7, 0x46, + 0x94, 0x8d, 0x2e, 0x89, 0xd2, 0x98, 0x39, 0x4d, 0xb0, 0xb1, 0x29, 0x7d, 0xcd, 0x90, 0xdf, 0x0c, 0x1d, 0x9b, 0x43, 0xd7, 0x1c, 0xbd, 0xe2, 0xd0, 0xbd, 0x05, 0x68, 0x49, 0xcc, 0x5a, 0x31, 0xbf, 0x75, 0x79, 0x51, 0xda, 0x60, 0x63, 0x1b, 0xda, 0x6d, 0xa1, 0x6b, 0x87, 0x7d, 0xf1, 0xe8, 0x90, 0x80, 0x1f, 0x3a, 0x60, 0x57, 0x47, 0x62, 0xf2, 0x0c, 0xed, 0xc4, 0x82, 0xa2, 0x24, 0xd1, 0xd7, 0x19, 0x5d, 0xba, 0xc0, 0x3b, 0xb9, 0x96, 0x28, 0x5d, 0x91, 0xc7, 0x7b, 0x41, 0xe9, 0x86, 0x8c, 0x6e, 0xf0, 0xee, 0x4e, 0x2e, 0x75, 0x27, 0x46, 0x3d, 0xf0, 0x73, 0x0f, 0xfc, 0xd0, 0x83, 0xb8, 0xf5, 0xc4, 0xf7, 0x3d, 0x89, 0x51, 0x4f, 0xf8, 0xf7, 0xc2, 0x27, 0xbd, 0x18, 0xef, 0x8d, 0xdd, 0x3c, 0x3a, 0x4a, 0x1f, 0x72, 0xaa, 0x0f, 0x34, 0x7d, 0x99, 0xdf, 0x17, 0x7f, 0xf7, 0x25, 0xef, 0xfa, + 0x12, 0x8f, 0x7e, 0xe4, 0x6f, 0x3f, 0x72, 0xa4, 0x1f, 0x39, 0xd0, 0x1f, 0xdd, 0xfa, 0x93, 0x6f, 0x03, 0xa0, 0x19, 0x80, 0xfc, 0x01, 0xf4, 0x0d, 0x64, 0x6c, 0x20, 0x7c, 0x06, 0xa1, 0xfb, 0x20, 0x6c, 0x18, 0x0c, 0xcd, 0x60, 0x78, 0x0e, 0x41, 0x9f, 0x21, 0x8c, 0x0f, 0x85, 0x76, 0xe8, 0xd2, 0x10, 0x88, 0xcb, 0x30, 0x74, 0x1f, 0x86, 0xfc, 0x61, 0xf0, 0x1e, 0x4e, 0x5e, 0x0e, 0x47, 0xe7, 0xe1, 0xd8, 0x39, 0x02, 0x3b, 0x46, 0x92, 0x57, 0xa3, 0xd0, 0x65, 0x14, 0x76, 0x8e, 0x22, 0xef, 0x46, 0x41, 0xff, 0x2c, 0x7c, 0x9e, 0xc5, 0x87, 0xa3, 0xb1, 0x63, 0x34, 0x31, 0x1b, 0x83, 0xbc, 0xb1, 0xd0, 0x8d, 0xc5, 0x6f, 0xe3, 0x88, 0xf1, 0x78, 0xf7, 0x5a, 0x34, 0xf6, 0x4d, 0x80, 0xcf, 0x04, 0xe2, 0x34, 0x11, 0x3f, 0x4d, 0x44, 0x9f, 0xe7, 0x28, 0x9f, 0xc3, 0x57, 0x93, 0xe8, 0x9f, 0x4c, + 0x9c, 0x26, 0xe3, 0xcf, 0x29, 0xe8, 0xf5, 0x3c, 0x3e, 0x78, 0x1e, 0x7f, 0x3d, 0x0f, 0x8f, 0xa9, 0xe4, 0xd2, 0x34, 0xfc, 0x30, 0x9d, 0xf1, 0x17, 0xe8, 0x9f, 0xa1, 0x03, 0xf4, 0x9c, 0x41, 0x3e, 0xcc, 0x24, 0xc6, 0xb3, 0xe0, 0x37, 0x1b, 0x5f, 0xbc, 0x88, 0x9c, 0x39, 0xe8, 0x31, 0x87, 0xf9, 0x2f, 0x81, 0xb9, 0xb4, 0xe7, 0xe1, 0xdf, 0x97, 0xc9, 0xfb, 0xf9, 0xc4, 0x9e, 0x77, 0x95, 0xb2, 0x80, 0x58, 0x2f, 0x44, 0xdf, 0x45, 0xf0, 0x5e, 0x8c, 0x0f, 0x16, 0x13, 0x83, 0x25, 0xc8, 0x5f, 0x8a, 0x8c, 0x65, 0x8c, 0x2f, 0x87, 0xf7, 0x72, 0x74, 0x58, 0x41, 0xdf, 0x4a, 0xec, 0xe1, 0xdd, 0xa3, 0xac, 0x22, 0xff, 0x56, 0xe3, 0x1f, 0xde, 0x3b, 0xca, 0xab, 0xf0, 0x7a, 0x15, 0xbf, 0xac, 0x83, 0xff, 0x7a, 0xe2, 0xba, 0x1e, 0x1d, 0x5e, 0x43, 0xa7, 0x0d, 0x3c, 0x2b, 0x1b, 0xf0, 0xd5, 0x46, + 0xe2, 0xb3, 0x09, 0xde, 0x9b, 0xf0, 0xd3, 0x26, 0x78, 0x6c, 0x46, 0xc6, 0x16, 0xe6, 0xf9, 0x89, 0xe7, 0x56, 0xfc, 0xb8, 0x0d, 0xfe, 0xdb, 0x29, 0x77, 0xe0, 0xbb, 0x37, 0x88, 0xcd, 0x9b, 0xd4, 0x77, 0xc2, 0x73, 0x17, 0x79, 0xbc, 0x1b, 0x1d, 0xf7, 0xe0, 0xcf, 0xbd, 0xf8, 0x6e, 0x1f, 0x3a, 0xee, 0x27, 0xce, 0x07, 0xe0, 0x79, 0x10, 0x1f, 0x1c, 0x82, 0xe7, 0x5b, 0x96, 0x28, 0x87, 0x91, 0x75, 0x04, 0x1b, 0xdf, 0x41, 0x9f, 0x77, 0xf0, 0xdf, 0x51, 0xf4, 0x7e, 0x8f, 0xdc, 0x7e, 0x9f, 0x79, 0x1f, 0x60, 0xeb, 0x31, 0x7c, 0xfc, 0x21, 0x38, 0xce, 0x73, 0x79, 0x02, 0x9f, 0xf0, 0x8e, 0x50, 0x3e, 0xc1, 0x3f, 0x9f, 0x32, 0x76, 0x12, 0xfa, 0xcf, 0xd0, 0xe9, 0x0b, 0x62, 0xf1, 0x05, 0x39, 0x70, 0x8a, 0x79, 0x5f, 0xa2, 0x17, 0xef, 0x08, 0xe5, 0x2b, 0xf2, 0xe8, 0x34, 0x31, 0x3f, 0xe3, + 0x02, 0xbf, 0x9d, 0x21, 0xd6, 0x5f, 0xe3, 0xe3, 0xb3, 0xc8, 0x3e, 0x47, 0x6c, 0xce, 0x13, 0xa7, 0x6f, 0xe0, 0xfb, 0x1d, 0x7c, 0xbe, 0xe7, 0x99, 0xfa, 0x01, 0x3e, 0x97, 0xe1, 0xf3, 0x13, 0x7a, 0x5c, 0xc5, 0xe6, 0x9f, 0xe1, 0x77, 0x0d, 0x7d, 0x7f, 0xc5, 0x5f, 0xd7, 0x89, 0xd3, 0x0d, 0xd6, 0xcb, 0x9b, 0xcc, 0xbd, 0x05, 0xfd, 0x6d, 0xe4, 0xdc, 0x41, 0x8f, 0x3f, 0xe8, 0xbb, 0x0b, 0xee, 0xe1, 0x93, 0xfb, 0xf0, 0x67, 0xfd, 0x55, 0x59, 0x7b, 0x55, 0xe5, 0x84, 0xa8, 0x5a, 0xbc, 0xa8, 0x7a, 0x13, 0x51, 0x8d, 0xbc, 0xa2, 0x7a, 0x7c, 0x60, 0xa9, 0xa8, 0xe6, 0x8b, 0xa2, 0xda, 0x45, 0xc0, 0x29, 0x51, 0x1d, 0xc6, 0xd9, 0xc7, 0xaa, 0xe9, 0x2c, 0xb0, 0x57, 0xd4, 0x0c, 0xb9, 0x44, 0xcd, 0x18, 0x2d, 0x6a, 0xa6, 0x5a, 0x60, 0x93, 0xa8, 0x99, 0x27, 0x89, 0x9a, 0x85, 0xfe, 0xac, 0x2b, + 0x45, 0xcd, 0x36, 0x5e, 0xd4, 0xec, 0x89, 0xa2, 0xe6, 0xa0, 0xcc, 0xd9, 0x43, 0xd4, 0x5c, 0xf0, 0xcf, 0x9d, 0x0c, 0xae, 0x88, 0x9a, 0x07, 0x9e, 0x61, 0xf0, 0xc9, 0x0b, 0xff, 0x7c, 0xa3, 0x45, 0xcd, 0x5f, 0x47, 0xd4, 0x70, 0xfa, 0x7c, 0xc8, 0x8e, 0x80, 0x67, 0xc4, 0x35, 0x51, 0x0b, 0xdc, 0x10, 0xb5, 0x60, 0x41, 0x30, 0x55, 0xd4, 0x42, 0xc8, 0x28, 0xb4, 0x5e, 0xd4, 0xc2, 0x99, 0xc0, 0x05, 0x51, 0x9f, 0x3a, 0x26, 0x6a, 0x91, 0x19, 0xa2, 0x16, 0xa5, 0x2c, 0x86, 0x0c, 0xd6, 0x42, 0xb5, 0xb8, 0x5f, 0xd4, 0x28, 0xe4, 0x94, 0x80, 0x57, 0x49, 0xf8, 0x94, 0x82, 0x6f, 0x69, 0x68, 0xca, 0x44, 0x81, 0x3e, 0xa2, 0x96, 0x3d, 0x2a, 0x6a, 0xf9, 0x23, 0xa2, 0x56, 0xaa, 0x26, 0x6a, 0x95, 0xd2, 0xa2, 0x56, 0x45, 0x8f, 0x1a, 0xba, 0xa8, 0xb5, 0xe0, 0x1b, 0x33, 0x42, 0xd4, 0x58, 0x74, + 0x8b, 0x3d, 0x2f, 0x2a, 0x6b, 0x96, 0x5a, 0x17, 0x79, 0xf5, 0xd0, 0xa7, 0x3e, 0xba, 0xd4, 0x67, 0x5e, 0x43, 0x6c, 0x68, 0x84, 0xed, 0x8d, 0xe3, 0x00, 0x3c, 0x9a, 0x20, 0xa7, 0x09, 0xfd, 0x4d, 0x0f, 0x8a, 0xda, 0x0c, 0x3b, 0x9a, 0x5d, 0x12, 0xb5, 0xf9, 0x00, 0x70, 0x46, 0xd4, 0x38, 0xe6, 0xb5, 0x40, 0xe7, 0x96, 0xc8, 0x68, 0x89, 0x7e, 0xad, 0xa0, 0x6f, 0x83, 0x2f, 0xdb, 0x32, 0xb7, 0x2d, 0x3e, 0x6a, 0x87, 0x2f, 0xd9, 0xdf, 0xaa, 0xf1, 0xb4, 0xe3, 0xd1, 0x3b, 0x01, 0x7d, 0x12, 0x98, 0xdf, 0x1e, 0xf9, 0x4f, 0xc3, 0xb7, 0x03, 0x32, 0x3b, 0xe0, 0xc7, 0x8e, 0xe8, 0xd0, 0x11, 0x5e, 0xcf, 0x50, 0xef, 0x34, 0x44, 0xd4, 0x44, 0xec, 0x48, 0x64, 0x3c, 0x11, 0x9e, 0x49, 0xe9, 0x00, 0x3c, 0x3a, 0x33, 0xb7, 0x33, 0xed, 0x2e, 0xf0, 0x4d, 0xc6, 0x0f, 0x5d, 0x69, 0x77, 0xc5, 0x0f, + 0xdd, 0x98, 0xdb, 0x6d, 0xa7, 0xa8, 0xdd, 0xd1, 0xa9, 0x3b, 0x7e, 0xef, 0x81, 0x9d, 0x3d, 0x98, 0xdb, 0x03, 0x9f, 0xf6, 0xc4, 0x8e, 0x5e, 0x1c, 0x61, 0x7a, 0x61, 0x73, 0x2f, 0xfc, 0xdc, 0x9b, 0xb2, 0x0f, 0x32, 0xfa, 0x12, 0xd3, 0xbe, 0xf0, 0xec, 0x8b, 0x5f, 0xfa, 0x41, 0xd3, 0x9f, 0x39, 0x03, 0xe8, 0x1b, 0x70, 0x4f, 0xd4, 0x41, 0xe8, 0x37, 0x78, 0x8d, 0xa8, 0x43, 0x90, 0xc3, 0x9a, 0xa4, 0x0e, 0x5b, 0x28, 0xea, 0x70, 0xe2, 0x3c, 0x02, 0x9e, 0x23, 0xb3, 0x01, 0xe4, 0x8c, 0xc2, 0x0f, 0xa3, 0xb0, 0x9f, 0xf5, 0x47, 0x1d, 0x0d, 0xed, 0x18, 0xf4, 0x1d, 0x0b, 0xdf, 0x71, 0x0d, 0x44, 0x1d, 0xcf, 0xd8, 0x78, 0xf8, 0x4e, 0x40, 0xef, 0x89, 0xf8, 0xe6, 0x39, 0xc6, 0x27, 0xc1, 0x77, 0x0a, 0xf1, 0x99, 0x82, 0xcf, 0x9f, 0x67, 0xce, 0x54, 0x7c, 0x39, 0x0d, 0x5d, 0xa6, 0x63, 0xcf, + 0x0c, 0x62, 0x37, 0x13, 0xde, 0xb3, 0xb0, 0x7b, 0x36, 0xba, 0xcc, 0x41, 0x8f, 0x39, 0xc4, 0x60, 0x0e, 0x73, 0x58, 0x53, 0xd4, 0x97, 0xd0, 0x7b, 0x2e, 0x39, 0x3a, 0x17, 0x9a, 0x79, 0x1d, 0x00, 0x39, 0xcc, 0xfa, 0xa2, 0xbe, 0x8c, 0x1e, 0xf3, 0x89, 0xcb, 0x02, 0xfa, 0x17, 0x42, 0xbf, 0x08, 0x1d, 0x16, 0x91, 0x63, 0x8b, 0xc9, 0xcf, 0xc5, 0xc8, 0x5f, 0x82, 0xfd, 0x4b, 0xf1, 0xe9, 0x32, 0xf4, 0x5f, 0x4e, 0xdf, 0x72, 0xf8, 0xad, 0x80, 0x76, 0x25, 0x72, 0x56, 0xa2, 0xfb, 0x2b, 0xcc, 0x5f, 0xd5, 0x5a, 0xd4, 0xd5, 0xc4, 0x68, 0x35, 0xf9, 0xbe, 0x86, 0x5c, 0x7b, 0x15, 0x7d, 0xd7, 0x62, 0xf3, 0x5a, 0xf4, 0x5b, 0x87, 0x1e, 0xeb, 0xd0, 0x71, 0x1d, 0x31, 0x5d, 0xcf, 0xfc, 0xf5, 0xf8, 0xf8, 0x35, 0xf2, 0xed, 0x35, 0x68, 0x37, 0xe0, 0xfb, 0x0d, 0xf8, 0x6f, 0x03, 0x7c, 0x36, 0x32, + 0xb6, 0x11, 0xf9, 0x1b, 0x91, 0xb7, 0x09, 0xfd, 0x36, 0xe1, 0xb7, 0x4d, 0xd8, 0xf9, 0x3a, 0xba, 0x6f, 0xc6, 0x9e, 0xcd, 0xe8, 0xb4, 0x19, 0x9f, 0xb2, 0x07, 0x56, 0xb7, 0x40, 0xb7, 0x05, 0xde, 0x7e, 0x78, 0xfb, 0xc9, 0x09, 0x3f, 0x3a, 0xf8, 0xa1, 0x4f, 0xa1, 0x6f, 0x2b, 0xbe, 0xdb, 0xc6, 0x9c, 0x6d, 0xe8, 0xbe, 0x9d, 0x39, 0x3b, 0xd0, 0xfb, 0x0d, 0xf8, 0xbf, 0x41, 0x7b, 0x27, 0x39, 0xc5, 0x7a, 0xa5, 0xee, 0x22, 0x6f, 0x76, 0x13, 0xef, 0x3d, 0xc8, 0xdf, 0x4b, 0xac, 0xf7, 0xe1, 0xd3, 0x7d, 0xd0, 0xee, 0x67, 0xde, 0x01, 0xec, 0x3f, 0x80, 0x6e, 0x07, 0xe9, 0x3b, 0x84, 0x1d, 0x6f, 0x21, 0xef, 0x6d, 0xe6, 0xbd, 0x0b, 0xdd, 0x51, 0x6c, 0x7c, 0x8f, 0xf1, 0xf7, 0xe0, 0xf7, 0x3e, 0xfa, 0x7e, 0x80, 0xcf, 0x8f, 0xe1, 0x9b, 0x0f, 0xc9, 0xff, 0x0f, 0xc9, 0xcb, 0x8f, 0xd0, 0xe1, + 0x38, 0xf5, 0xe3, 0xe8, 0x71, 0x02, 0xfb, 0x3f, 0xc6, 0x47, 0x9f, 0xa2, 0xeb, 0xa7, 0xf8, 0xe4, 0x24, 0x3e, 0xf8, 0x8c, 0xf9, 0x9f, 0x21, 0xe7, 0x73, 0x78, 0x7c, 0x0e, 0xcd, 0x17, 0x94, 0xa7, 0xd0, 0xe3, 0x4b, 0xe6, 0x7c, 0x85, 0x8d, 0xa7, 0x91, 0x73, 0x06, 0x9a, 0x33, 0xe8, 0xfc, 0x35, 0xe5, 0xd7, 0xd8, 0x7f, 0x16, 0x7f, 0x9d, 0x43, 0x8f, 0xf3, 0xd8, 0x79, 0x1e, 0xfe, 0xdf, 0xd0, 0xf7, 0x1d, 0x71, 0xfb, 0x8e, 0xf8, 0x5f, 0x20, 0x7f, 0x7f, 0x40, 0xc6, 0x45, 0x72, 0xe6, 0x12, 0x3e, 0xbb, 0x4c, 0xfb, 0x47, 0x7c, 0xfb, 0x13, 0xcf, 0xfc, 0x15, 0xfa, 0xaf, 0x22, 0xfb, 0x17, 0xf2, 0xf6, 0x1a, 0x79, 0x79, 0x8d, 0xb9, 0xbf, 0x92, 0x4b, 0xbf, 0x21, 0xeb, 0x3a, 0xb2, 0xd8, 0xbb, 0xaa, 0x37, 0xc8, 0xfb, 0x9b, 0xcc, 0xbb, 0x89, 0xcf, 0x59, 0xef, 0xd4, 0x5b, 0xe4, 0xc1, 0x1d, + 0xe6, 0xfe, 0x0e, 0xff, 0xdf, 0xf1, 0xc5, 0xef, 0xc4, 0xfa, 0x0f, 0x78, 0xdf, 0xc5, 0x07, 0x77, 0x89, 0xd3, 0x3d, 0xca, 0x7b, 0xd0, 0xdc, 0x67, 0xfc, 0x01, 0xf4, 0x81, 0xf1, 0xa2, 0xc9, 0x11, 0xd1, 0x94, 0xa9, 0xa2, 0xa9, 0xf1, 0xe0, 0xa8, 0x68, 0x5a, 0x6b, 0xd1, 0xf4, 0x5c, 0xa2, 0x19, 0xe9, 0x40, 0x5e, 0x70, 0x43, 0x34, 0x4f, 0x13, 0xd1, 0x4c, 0x68, 0xad, 0x13, 0xa2, 0xd9, 0x03, 0x44, 0xf3, 0x96, 0x16, 0x8d, 0xbd, 0xa8, 0x96, 0x3e, 0x5a, 0xb4, 0x0c, 0x9b, 0x44, 0x63, 0x1f, 0xaa, 0x65, 0x5e, 0x2a, 0x5a, 0x96, 0xed, 0x00, 0x7e, 0xd9, 0xe8, 0xcf, 0x3e, 0x5a, 0xb4, 0x9c, 0x1d, 0x44, 0xcb, 0x6d, 0x01, 0xe6, 0xe6, 0x01, 0x61, 0xf3, 0x44, 0xcb, 0x0b, 0xaf, 0xfc, 0xf4, 0x85, 0x9f, 0x11, 0xcd, 0x37, 0x44, 0x34, 0xd6, 0x3e, 0x2d, 0x82, 0x79, 0x91, 0x7e, 0xd1, 0x0a, 0xc2, + 0xbf, 0xd0, 0x49, 0xd1, 0x0a, 0x5f, 0x12, 0xed, 0x29, 0xf8, 0x14, 0xb9, 0x23, 0x5a, 0x31, 0x5d, 0x34, 0xf6, 0x7c, 0x5a, 0x14, 0xfa, 0x45, 0x1d, 0x14, 0xad, 0x84, 0x0f, 0x8c, 0x00, 0xa7, 0x44, 0x2b, 0x09, 0x7f, 0xce, 0xf1, 0x5a, 0xa9, 0x6c, 0xa0, 0x01, 0xc0, 0x8e, 0x52, 0xd8, 0x50, 0x3a, 0x13, 0x80, 0xbe, 0x0c, 0xbc, 0xca, 0xc5, 0x89, 0x56, 0x1e, 0x39, 0x15, 0xa0, 0xad, 0x08, 0xaf, 0x4a, 0xc8, 0xaf, 0x74, 0x4c, 0xb4, 0xca, 0xf4, 0x57, 0xa9, 0x25, 0x1a, 0xc7, 0x10, 0xad, 0x1a, 0x3c, 0xaa, 0xa3, 0x5b, 0x0d, 0xf8, 0xd7, 0xa4, 0xaf, 0x26, 0x72, 0x6b, 0x63, 0x53, 0x0c, 0x34, 0x31, 0xe8, 0x12, 0xbb, 0x46, 0xb4, 0x3a, 0x2f, 0x8a, 0x56, 0x77, 0x86, 0x68, 0x0d, 0x56, 0x82, 0x9d, 0xa2, 0x35, 0xa4, 0x6c, 0x4c, 0xd9, 0x74, 0xbd, 0x68, 0xcd, 0x90, 0xdb, 0x02, 0xde, 0x2d, 0xf0, + 0x41, 0x4b, 0xfc, 0xd7, 0xaa, 0xbc, 0x68, 0xad, 0xa3, 0x44, 0x6b, 0x87, 0xde, 0xf1, 0xe8, 0x16, 0x0f, 0x0f, 0xd6, 0x37, 0xad, 0x23, 0x36, 0x3f, 0x83, 0xbd, 0xcf, 0x5c, 0x10, 0xad, 0x13, 0x7e, 0x4d, 0xa4, 0x9d, 0x84, 0x5f, 0x3a, 0x17, 0x04, 0xf0, 0xe9, 0xc2, 0xbc, 0x64, 0xf8, 0x26, 0x63, 0x7b, 0x32, 0x73, 0xd8, 0xb3, 0x69, 0x5d, 0xf1, 0x75, 0x57, 0x78, 0x76, 0xc5, 0xe6, 0xae, 0xf0, 0xef, 0x8a, 0x8e, 0x5d, 0x99, 0xdf, 0xbd, 0x1a, 0x20, 0x16, 0xdd, 0xd1, 0xad, 0x3b, 0xbe, 0xe8, 0x01, 0x6d, 0x0f, 0x74, 0xef, 0x81, 0xad, 0x3d, 0xe0, 0xd5, 0x03, 0xde, 0x9c, 0xfd, 0x35, 0xd6, 0x3b, 0xad, 0x37, 0xfc, 0x7b, 0xe3, 0x8f, 0x3e, 0xf8, 0xae, 0x6f, 0x11, 0xd1, 0xfa, 0x61, 0x7f, 0x7f, 0xfc, 0xd1, 0x9f, 0x18, 0xf5, 0x87, 0x67, 0xff, 0xbd, 0xe0, 0x8a, 0x68, 0x03, 0xb0, 0x79, 0x00, + 0xfe, 0x19, 0x08, 0xfd, 0xe0, 0x3a, 0x20, 0x59, 0xb4, 0x21, 0xd8, 0xcd, 0x1a, 0xa8, 0x0d, 0xc1, 0xaf, 0x43, 0x90, 0x3b, 0x14, 0x7f, 0x0d, 0x85, 0x6e, 0x28, 0xfe, 0x1e, 0x8a, 0xac, 0xa1, 0x8c, 0x0f, 0x65, 0x7c, 0x28, 0xe3, 0x43, 0x19, 0x1f, 0x46, 0x5e, 0x0c, 0x47, 0xb7, 0xe1, 0xc8, 0x1b, 0xce, 0xf8, 0x70, 0xc6, 0x87, 0x33, 0x3e, 0x9c, 0xf1, 0xe1, 0x8c, 0x0f, 0xc7, 0xb7, 0x23, 0xf0, 0xc9, 0x08, 0x7c, 0x3e, 0x02, 0xdd, 0x46, 0xc2, 0x6f, 0x14, 0xbe, 0x1b, 0x45, 0xdf, 0x28, 0xe4, 0xb2, 0xa6, 0x6a, 0xa3, 0xa0, 0x7b, 0x16, 0xfd, 0x9e, 0xed, 0x23, 0xda, 0x68, 0x64, 0x8d, 0xc1, 0xae, 0xb1, 0xf0, 0x65, 0x3f, 0xa7, 0x8d, 0xc7, 0x8f, 0x13, 0xd0, 0x71, 0x02, 0x7e, 0x98, 0x48, 0xce, 0x3c, 0x97, 0x28, 0xda, 0x24, 0xfc, 0x3f, 0x69, 0x21, 0xc0, 0xee, 0x49, 0xc4, 0x64, 0x12, 0xb2, + 0x26, 0xe1, 0x93, 0x49, 0xf8, 0x70, 0x12, 0xf2, 0x26, 0x93, 0x6f, 0x93, 0xf1, 0xf3, 0x64, 0xec, 0x9e, 0x02, 0x9e, 0x47, 0xaf, 0xe7, 0x99, 0x3b, 0x15, 0x99, 0x53, 0xf1, 0xeb, 0x54, 0xf2, 0x6f, 0x1a, 0xb2, 0xa7, 0xa1, 0xf3, 0x34, 0xe6, 0x4f, 0x47, 0x9f, 0xe9, 0xe8, 0xfc, 0x02, 0x7c, 0x66, 0x10, 0xaf, 0x19, 0xc8, 0x9b, 0x01, 0x7f, 0xf6, 0x7d, 0xda, 0x4c, 0xf4, 0x9b, 0x89, 0xcf, 0x67, 0x31, 0x6f, 0x36, 0xf1, 0x7d, 0x91, 0x1c, 0x63, 0xdf, 0xa7, 0xbd, 0x44, 0x9c, 0x5e, 0xc2, 0x97, 0x73, 0x91, 0x33, 0x0f, 0x3f, 0xb3, 0x36, 0x6b, 0x2f, 0x53, 0x7f, 0x99, 0x9c, 0x7e, 0x19, 0x3b, 0xe7, 0x13, 0x8b, 0xf9, 0xf4, 0xcd, 0x27, 0xa7, 0xe6, 0x13, 0xb7, 0xf9, 0xf8, 0x65, 0x3e, 0x3a, 0xcc, 0x67, 0x7c, 0x3e, 0xe3, 0x0b, 0x18, 0x5f, 0xc0, 0xf8, 0x02, 0xc6, 0x17, 0x30, 0xbe, 0x80, 0xf1, + 0x05, 0x8c, 0x2f, 0x60, 0x7c, 0x01, 0xe3, 0x0b, 0x19, 0x5f, 0xc8, 0xf8, 0x42, 0xc6, 0x17, 0x32, 0xbe, 0x90, 0xf1, 0x85, 0x8c, 0x2f, 0x64, 0x7c, 0x21, 0xe3, 0x8b, 0x18, 0x5f, 0xc4, 0xf8, 0x22, 0xc6, 0x17, 0x31, 0xbe, 0x88, 0xf1, 0x45, 0x8c, 0x2f, 0x62, 0x7c, 0x11, 0xe3, 0x8b, 0x19, 0x5f, 0xcc, 0xf8, 0x62, 0xc6, 0x17, 0x33, 0xbe, 0x98, 0xf1, 0xc5, 0x8c, 0x2f, 0x66, 0x7c, 0x31, 0xe3, 0x4b, 0x18, 0x5f, 0xc2, 0xf8, 0x12, 0xc6, 0x97, 0x30, 0xbe, 0x84, 0xf1, 0x25, 0x8c, 0x2f, 0x61, 0x7c, 0x09, 0xe3, 0x4b, 0x19, 0x5f, 0xca, 0xf8, 0x52, 0xc6, 0x97, 0x32, 0xce, 0x3b, 0x45, 0x5b, 0xca, 0xf8, 0x52, 0xc6, 0x97, 0x32, 0xbe, 0x8c, 0xf1, 0x65, 0x8c, 0x2f, 0x63, 0x7c, 0x19, 0xe3, 0xcb, 0x18, 0x5f, 0xc6, 0xf8, 0x32, 0xc6, 0x97, 0x31, 0xbe, 0x3c, 0x5b, 0x2a, 0x56, 0xe0, 0xbf, 0x57, + 0xa0, 0xe1, 0x7d, 0xa3, 0xad, 0xc6, 0xef, 0xab, 0xf1, 0xf1, 0x6a, 0xe2, 0xb4, 0x86, 0xf8, 0xbe, 0x8a, 0xdf, 0x5f, 0x25, 0xd7, 0x5f, 0xc5, 0xbf, 0x6b, 0x89, 0xe5, 0x7a, 0x72, 0xe0, 0x35, 0xc6, 0x36, 0x12, 0xaf, 0x4d, 0xf8, 0xfb, 0x75, 0x72, 0xe1, 0x75, 0xf2, 0x74, 0x33, 0xfc, 0xb7, 0x90, 0x1b, 0x7e, 0xe6, 0xa7, 0x20, 0x67, 0x2b, 0xf9, 0xbb, 0x8d, 0x9c, 0xd9, 0x4e, 0xcc, 0x76, 0x30, 0x67, 0x07, 0xb9, 0xff, 0x06, 0xb4, 0x6f, 0x4c, 0x0a, 0x81, 0x98, 0xbe, 0x09, 0xef, 0x37, 0xe9, 0x7b, 0x13, 0xd9, 0x6f, 0x92, 0x33, 0x6f, 0xc2, 0x77, 0x27, 0x63, 0x3b, 0x79, 0xd6, 0x76, 0x31, 0xb6, 0x8b, 0xfa, 0x6e, 0x62, 0xbc, 0x87, 0xe7, 0x6f, 0x2f, 0xd8, 0x87, 0x8c, 0xfd, 0xc4, 0xf3, 0x00, 0xfc, 0x0e, 0xb2, 0x9e, 0x1d, 0x42, 0xf7, 0xb7, 0xc8, 0xc3, 0xb7, 0xd0, 0xf7, 0x2d, 0xe2, 0x7e, + 0x98, 0x67, 0xe3, 0xf0, 0x99, 0xff, 0x0c, 0x47, 0xd0, 0xf5, 0x6d, 0xf2, 0xfd, 0x6d, 0xf2, 0xe2, 0x1d, 0x72, 0xf2, 0x1d, 0x64, 0x1c, 0x45, 0xfe, 0x87, 0xf0, 0xfc, 0x08, 0x99, 0xc7, 0x79, 0x86, 0x4f, 0x90, 0x3b, 0x27, 0xc8, 0xa7, 0x8f, 0xd3, 0xa5, 0xe2, 0x93, 0x5c, 0x21, 0x90, 0xcf, 0x9f, 0xe2, 0x9b, 0x93, 0xe4, 0xe2, 0x67, 0xe8, 0xf2, 0x39, 0x3a, 0x7d, 0xc1, 0xf3, 0x70, 0x0a, 0x3f, 0x7d, 0x09, 0xef, 0xaf, 0xd0, 0xe9, 0x34, 0xbe, 0xfe, 0x9a, 0x75, 0xe7, 0x2c, 0xcf, 0xee, 0x79, 0xe6, 0x7e, 0x43, 0x4c, 0xbe, 0xc5, 0x47, 0x17, 0xe8, 0xff, 0x81, 0xf6, 0x45, 0xf2, 0xf7, 0x32, 0x36, 0xfd, 0x48, 0xdf, 0x15, 0x62, 0xf2, 0x33, 0x72, 0xae, 0xe1, 0xdb, 0x6b, 0xf8, 0xef, 0x1a, 0xed, 0x5f, 0x79, 0x6e, 0x7f, 0xa5, 0xef, 0x37, 0x78, 0xfc, 0xc6, 0x5a, 0xf7, 0x1b, 0xbe, 0xbe, 0x8e, + 0x8e, 0xd7, 0x4f, 0xa4, 0xe2, 0x06, 0x3e, 0xba, 0x89, 0xff, 0x6e, 0xe2, 0xcf, 0x5b, 0xe8, 0x79, 0x0b, 0x39, 0xb7, 0xe0, 0x75, 0x9b, 0x38, 0xdf, 0xc1, 0x4f, 0xbf, 0xf3, 0x0c, 0xfd, 0xce, 0x73, 0xfd, 0x07, 0x71, 0xb8, 0xcb, 0xfc, 0x7b, 0xc8, 0xbf, 0x87, 0x9e, 0xf7, 0xe0, 0x7f, 0x8f, 0xfe, 0xfb, 0xf8, 0xf9, 0x3e, 0xcf, 0xd8, 0x7d, 0xfc, 0x7c, 0x9f, 0xe7, 0xe6, 0x3e, 0xcf, 0xe6, 0x03, 0x62, 0xf2, 0x00, 0xbf, 0x3e, 0xc0, 0x9f, 0x0f, 0x78, 0x8e, 0x1f, 0x30, 0xf7, 0x01, 0xb2, 0x02, 0xf0, 0x0e, 0xac, 0x11, 0x5d, 0x6a, 0x81, 0x64, 0x70, 0x0c, 0xdc, 0x10, 0x5d, 0xc9, 0x0b, 0xea, 0x80, 0x3e, 0xe0, 0x9a, 0xe8, 0x6a, 0x69, 0x30, 0x00, 0xf8, 0x01, 0x6d, 0x6d, 0x08, 0xa0, 0xd4, 0x5b, 0x83, 0x2b, 0xa2, 0x1b, 0xcc, 0x35, 0xce, 0x8b, 0xee, 0x61, 0x9e, 0x87, 0xba, 0x67, 0x13, 0xb8, + 0x24, 0xba, 0x59, 0x10, 0x24, 0x82, 0xa5, 0xe0, 0x94, 0xe8, 0x56, 0x36, 0x10, 0x07, 0xe8, 0x63, 0xef, 0xaf, 0x3b, 0xc8, 0xf6, 0xd2, 0xf6, 0xba, 0xe5, 0x5e, 0x70, 0x52, 0xf4, 0x74, 0xf0, 0x4d, 0x4f, 0x5f, 0xfa, 0x7b, 0xa2, 0x67, 0xd8, 0x2e, 0x7a, 0xc6, 0x78, 0x40, 0x5f, 0xa6, 0x85, 0xa2, 0x67, 0xae, 0x06, 0x90, 0x99, 0x19, 0xbd, 0x38, 0x13, 0xe8, 0xbc, 0x0f, 0xf5, 0xcc, 0xd0, 0x64, 0x3e, 0x23, 0x7a, 0x96, 0x74, 0x80, 0xf1, 0x2c, 0x8c, 0x65, 0xa1, 0x3f, 0x0b, 0xb6, 0x64, 0x81, 0x47, 0xd6, 0x28, 0xd0, 0x01, 0xcc, 0x00, 0xc8, 0xc8, 0x76, 0x47, 0xf4, 0xec, 0x99, 0xc0, 0x8b, 0xa2, 0xe7, 0xc0, 0x2e, 0xde, 0x9f, 0x7a, 0x4e, 0xea, 0x39, 0xd1, 0x2f, 0x17, 0xfa, 0xe7, 0x6a, 0x00, 0xe0, 0x91, 0x8b, 0xbe, 0x5c, 0xd8, 0x9b, 0x87, 0xf1, 0x30, 0x64, 0x84, 0x31, 0x1e, 0x86, 0x1e, + 0xf9, 0xa6, 0x8a, 0x9e, 0xff, 0x04, 0xc0, 0xbe, 0xfc, 0xf0, 0x0a, 0xc7, 0xc6, 0x70, 0xe4, 0x86, 0xa3, 0x73, 0x78, 0x0f, 0xd1, 0x23, 0x44, 0x74, 0xce, 0x18, 0x7a, 0x64, 0x2e, 0x40, 0x7f, 0x24, 0xb6, 0x46, 0xa2, 0x6b, 0x24, 0x3c, 0x22, 0xb1, 0x2f, 0x92, 0xb1, 0x82, 0xf8, 0xa1, 0x10, 0x7c, 0x0a, 0xa3, 0x53, 0x11, 0x7c, 0x59, 0x8c, 0x76, 0x71, 0x74, 0xe6, 0xdd, 0xab, 0x97, 0x38, 0x22, 0x7a, 0x49, 0xe4, 0x96, 0x44, 0x46, 0x49, 0xe4, 0x95, 0x42, 0xd7, 0x52, 0xe8, 0x59, 0x0a, 0xfe, 0xa5, 0x88, 0x41, 0x29, 0xf4, 0x2a, 0x8d, 0xcf, 0xcb, 0x60, 0x2f, 0x67, 0x10, 0xbd, 0x0c, 0xb1, 0x2b, 0x83, 0x4f, 0xca, 0x30, 0xbf, 0x2c, 0xb2, 0xcb, 0xa2, 0x2f, 0x67, 0x12, 0xbd, 0x1c, 0x76, 0x94, 0x83, 0x57, 0x79, 0x74, 0x28, 0x8f, 0x7f, 0xcb, 0xd3, 0x57, 0x1e, 0x9d, 0x2b, 0xc0, 0xab, 0x02, + 0xbc, 0x2a, 0xc0, 0xab, 0x02, 0xbc, 0x2a, 0x20, 0xab, 0x22, 0xba, 0x55, 0xc4, 0x5f, 0x15, 0x2f, 0x88, 0x5e, 0x09, 0x5d, 0x2b, 0xad, 0x07, 0xf8, 0xaa, 0x12, 0x3a, 0x54, 0x72, 0xfb, 0xb0, 0x33, 0x1a, 0x79, 0xd1, 0x3e, 0x50, 0x1e, 0xc0, 0x3b, 0x1a, 0x39, 0xd1, 0xf0, 0x88, 0x86, 0x3e, 0x9a, 0xd8, 0x44, 0x13, 0xf3, 0x68, 0xf4, 0x8f, 0xc6, 0xc6, 0x68, 0xe4, 0x44, 0xe3, 0xfb, 0xca, 0xe8, 0x5e, 0x19, 0xff, 0x54, 0x8e, 0x06, 0x4d, 0x00, 0xbe, 0xa8, 0x8c, 0xbd, 0x95, 0xb1, 0xbd, 0x32, 0xfa, 0x56, 0x46, 0x76, 0x65, 0x74, 0xac, 0x8c, 0x6f, 0x2b, 0x63, 0x53, 0x15, 0xf4, 0xaf, 0x82, 0x6e, 0x55, 0xc8, 0xa1, 0x2a, 0x23, 0x00, 0xfe, 0xa9, 0xb2, 0x12, 0xa0, 0x5f, 0x15, 0xf4, 0xaf, 0x42, 0x9c, 0xab, 0xe0, 0x93, 0xaa, 0x3a, 0xc0, 0xbf, 0x55, 0xb1, 0xbf, 0x2a, 0xf6, 0x57, 0xc5, 0xfe, + 0xaa, 0xf8, 0xbe, 0xea, 0x68, 0x80, 0x4d, 0x55, 0xb1, 0xb7, 0xea, 0x4e, 0x80, 0x4d, 0x55, 0xc9, 0xcb, 0x6a, 0xf8, 0xa0, 0x1a, 0xf2, 0xaa, 0xa1, 0x53, 0x75, 0xf4, 0xae, 0x8e, 0x7d, 0x35, 0xf0, 0x79, 0x0d, 0x74, 0xa9, 0x81, 0x8d, 0x35, 0xe1, 0x53, 0x13, 0x59, 0x35, 0xd1, 0xa3, 0x16, 0xb6, 0xd5, 0x42, 0x5e, 0x6d, 0xf8, 0xd7, 0x66, 0xbc, 0x36, 0x7d, 0x31, 0xd8, 0x10, 0x33, 0x4f, 0xf4, 0x58, 0xe4, 0xc6, 0x62, 0x73, 0x2c, 0x36, 0xd6, 0xc1, 0x8f, 0x75, 0x90, 0x57, 0x07, 0xff, 0xd4, 0x45, 0xe7, 0xba, 0xf8, 0xab, 0x1e, 0xfe, 0xa9, 0x47, 0x5f, 0x3d, 0x74, 0xad, 0xcf, 0x9c, 0xfa, 0xf8, 0xa6, 0x3e, 0x76, 0x35, 0x40, 0x3f, 0xf6, 0x33, 0x7a, 0x43, 0xf2, 0xab, 0x21, 0xe3, 0x0d, 0xd1, 0xa9, 0x11, 0x7a, 0x37, 0x42, 0x66, 0x23, 0x7c, 0xdb, 0x18, 0xbf, 0x34, 0xc6, 0x77, 0x4d, 0x18, + 0x6f, 0x82, 0xdd, 0x4d, 0xf0, 0x49, 0x53, 0xfc, 0xdc, 0x14, 0x3b, 0x9a, 0xe1, 0xc3, 0x66, 0xe8, 0xd1, 0x0c, 0x3d, 0x9b, 0x33, 0xde, 0x1c, 0x9e, 0xcd, 0xf1, 0x41, 0x1c, 0x76, 0xc4, 0x31, 0xa7, 0x05, 0x7d, 0x2d, 0xb0, 0xbd, 0x05, 0x32, 0x5b, 0x62, 0x47, 0x4b, 0x62, 0xd1, 0x0a, 0x3d, 0x5b, 0x91, 0xc3, 0xad, 0x90, 0xd9, 0x1a, 0x3b, 0x5b, 0x33, 0xbf, 0x35, 0xbe, 0x6b, 0x43, 0x0c, 0xda, 0xd0, 0xd7, 0x16, 0xdb, 0xda, 0x22, 0xa7, 0x2d, 0xb6, 0xb5, 0x23, 0x36, 0xed, 0xe0, 0xd9, 0x0e, 0x9e, 0xf1, 0xe8, 0x11, 0x0f, 0x9f, 0x04, 0x9e, 0xcd, 0x04, 0xf4, 0x4c, 0xa0, 0xde, 0x1e, 0x3b, 0xdb, 0xe3, 0xbb, 0xa7, 0x2d, 0x00, 0xcf, 0xa7, 0xe9, 0xeb, 0x00, 0xcf, 0x0e, 0xf0, 0xec, 0xc0, 0xfc, 0x8e, 0xac, 0x13, 0x1d, 0x89, 0x23, 0x7b, 0x2e, 0xfd, 0x19, 0xe2, 0xf6, 0x0c, 0x3a, 0x75, 0xc2, + 0xf6, 0x4e, 0xc4, 0x21, 0x91, 0x78, 0x26, 0xe2, 0xaf, 0x44, 0x7c, 0x93, 0xc4, 0x78, 0x12, 0x32, 0x93, 0x98, 0xd3, 0x19, 0xdb, 0x3b, 0x93, 0x2f, 0x5d, 0xc8, 0xa9, 0x2e, 0x8c, 0x77, 0xc1, 0xb6, 0x64, 0xe4, 0x24, 0x23, 0x33, 0x19, 0xdf, 0x74, 0x25, 0x06, 0x9c, 0x37, 0xf5, 0x6e, 0xd8, 0xde, 0x0d, 0xdb, 0xba, 0x11, 0x8f, 0xee, 0xd4, 0xbb, 0x23, 0xb3, 0x3b, 0x76, 0xf4, 0x40, 0x66, 0x0f, 0xe6, 0xf7, 0xc0, 0x77, 0x3d, 0xf1, 0x6d, 0x4f, 0x6c, 0xea, 0x85, 0xcc, 0x5e, 0xc4, 0xa8, 0x37, 0xb6, 0xf7, 0x26, 0x1e, 0xbd, 0x99, 0xd3, 0x87, 0x39, 0x7d, 0x98, 0xd3, 0x07, 0x9e, 0x7d, 0xe9, 0xeb, 0x8b, 0x1d, 0x7d, 0xc9, 0x01, 0xce, 0xa1, 0x7a, 0x3f, 0x74, 0xea, 0x8f, 0xef, 0xfa, 0x93, 0x8b, 0xfd, 0xc9, 0xd7, 0x01, 0xc8, 0x1c, 0x40, 0x5e, 0x0c, 0x64, 0xfe, 0x40, 0xe2, 0x38, 0x10, 0x5f, + 0x0c, 0x44, 0xb7, 0x81, 0xe3, 0x01, 0x7c, 0x07, 0xba, 0x63, 0xcc, 0x19, 0x88, 0xae, 0x03, 0xe1, 0x37, 0x08, 0xdb, 0x06, 0x41, 0x37, 0x88, 0x38, 0x0d, 0x62, 0xee, 0x20, 0xe2, 0x31, 0x08, 0xfa, 0x41, 0xf8, 0x72, 0x10, 0x31, 0x18, 0x84, 0x7e, 0x83, 0x19, 0x1b, 0x8c, 0xcf, 0x07, 0x33, 0x77, 0x08, 0x7c, 0x87, 0xe0, 0x83, 0x21, 0xc4, 0x68, 0x08, 0xf6, 0x0f, 0x41, 0xef, 0x21, 0xd8, 0x36, 0x04, 0x9b, 0xd9, 0xff, 0xe9, 0x43, 0x88, 0xf5, 0x10, 0xec, 0x18, 0x42, 0xce, 0x0e, 0x85, 0x8e, 0xbd, 0x9f, 0x3e, 0x94, 0xf1, 0xa1, 0x8c, 0xb3, 0xff, 0xd3, 0x87, 0x32, 0x3e, 0x0c, 0x1e, 0xc3, 0x18, 0x1b, 0x06, 0x8f, 0x61, 0xf0, 0x18, 0x86, 0x8e, 0xc3, 0x98, 0x33, 0x1c, 0x3f, 0x0e, 0xa7, 0x3e, 0x1c, 0xdf, 0x8c, 0x40, 0x8f, 0x91, 0xe4, 0xfb, 0x48, 0xec, 0x1a, 0xc9, 0xd8, 0x28, 0xc6, 0x46, + 0x61, 0xef, 0x28, 0x9e, 0xa3, 0x51, 0xe4, 0xe2, 0xb3, 0xc4, 0xe8, 0x59, 0x72, 0x76, 0x34, 0xfe, 0x1a, 0x4d, 0x9d, 0x33, 0xb5, 0x3e, 0x9a, 0xf6, 0x18, 0xda, 0x63, 0x68, 0x8f, 0xa1, 0x3d, 0x86, 0xf6, 0x58, 0x62, 0x3c, 0x16, 0x3f, 0x8f, 0xc5, 0x7f, 0x63, 0xe9, 0x1b, 0x8b, 0xed, 0x63, 0xc9, 0xfb, 0xb1, 0xf0, 0x18, 0x87, 0x1e, 0xe3, 0xf0, 0xdd, 0x38, 0xec, 0x1b, 0x87, 0x7d, 0xe3, 0x98, 0xc7, 0xbe, 0x51, 0x1f, 0x4f, 0xff, 0x78, 0x72, 0x67, 0x3c, 0xbe, 0x1f, 0x8f, 0x1d, 0xe3, 0xe1, 0x33, 0x1e, 0x7f, 0x4f, 0x80, 0xd7, 0x04, 0x74, 0x9e, 0x00, 0xbf, 0x09, 0xd0, 0x4e, 0x80, 0x76, 0x02, 0x3e, 0x9d, 0x80, 0x8f, 0x26, 0x90, 0xeb, 0x13, 0xa0, 0x9b, 0x00, 0xdd, 0x44, 0xe6, 0x4e, 0xc4, 0x2f, 0x13, 0xc9, 0xb3, 0x89, 0xf8, 0x71, 0x22, 0x7e, 0x7b, 0x0e, 0xbd, 0x9f, 0xc3, 0x8e, 0xe7, + 0xf0, 0xfb, 0x73, 0xc4, 0x69, 0x12, 0x7e, 0x9f, 0x84, 0x0f, 0x26, 0x91, 0x2f, 0x93, 0xf0, 0xd1, 0x24, 0xec, 0x9e, 0x44, 0x5e, 0x4d, 0xc2, 0x47, 0xec, 0x4d, 0xf5, 0x49, 0xe4, 0x1f, 0xfb, 0x52, 0x9d, 0x3d, 0xa9, 0x3e, 0x19, 0xba, 0xc9, 0xe4, 0xf7, 0x64, 0xec, 0x9f, 0x0c, 0xcd, 0x64, 0xe2, 0x32, 0x19, 0x5e, 0x93, 0x19, 0x9f, 0x4c, 0xbe, 0x4f, 0x21, 0xa7, 0xa7, 0x10, 0xc3, 0x29, 0xd0, 0x4c, 0x21, 0x3f, 0x9e, 0x47, 0xff, 0xe7, 0x99, 0x3f, 0x15, 0xfd, 0xd8, 0xa7, 0xea, 0xd3, 0xdc, 0x12, 0x3d, 0xa7, 0xa1, 0xe7, 0x34, 0xf4, 0x9c, 0x86, 0x7f, 0xa7, 0xb1, 0x7e, 0x4f, 0x87, 0xef, 0x74, 0x74, 0x99, 0x81, 0x9f, 0x67, 0xe2, 0xdf, 0xd9, 0xcc, 0x9b, 0x8d, 0x3f, 0x66, 0x13, 0x93, 0xd9, 0xd8, 0x37, 0x1b, 0xdf, 0xcf, 0x66, 0xce, 0x6c, 0xe6, 0xcc, 0x66, 0xce, 0x8b, 0x3c, 0xe7, 0x73, + 0xa0, 0x9d, 0x83, 0x6d, 0x73, 0xb0, 0x6d, 0x0e, 0xb6, 0xcd, 0x21, 0x17, 0xe7, 0xe0, 0xa7, 0x39, 0xf8, 0x69, 0x0e, 0xf6, 0xbf, 0x84, 0x2e, 0x2f, 0x11, 0xef, 0x97, 0xb0, 0xe3, 0x25, 0x74, 0x9b, 0x4b, 0x7b, 0x2e, 0xba, 0xcd, 0x45, 0xb7, 0xb9, 0xe8, 0x36, 0x97, 0x31, 0xf6, 0xbd, 0xfa, 0x5c, 0x6c, 0x98, 0xcb, 0xb3, 0x31, 0x97, 0xe7, 0x70, 0x2e, 0x3a, 0xcc, 0xc3, 0x1f, 0xf3, 0xd0, 0xe7, 0x65, 0xfc, 0x3b, 0x1f, 0x3f, 0xcf, 0x77, 0x4b, 0x74, 0x67, 0xff, 0xab, 0xcf, 0x47, 0xf7, 0x85, 0xf0, 0x59, 0x08, 0x9f, 0x85, 0xf0, 0x61, 0x5f, 0xab, 0x2f, 0x84, 0xcf, 0x42, 0xf8, 0x2c, 0x84, 0x0f, 0x7b, 0x5b, 0x7d, 0x21, 0x7c, 0x16, 0x92, 0xcf, 0x8b, 0xb0, 0x61, 0x11, 0x36, 0x2c, 0x22, 0xa6, 0x8b, 0x98, 0xcf, 0xfe, 0x56, 0x5f, 0x84, 0x1d, 0x8b, 0xf0, 0xff, 0x22, 0x7c, 0xc6, 0x1e, 0x57, + 0x5f, 0x4c, 0x7c, 0x16, 0x13, 0xef, 0xc5, 0xe4, 0xc3, 0x62, 0xf2, 0x61, 0x31, 0xf3, 0x16, 0xe3, 0x67, 0xf6, 0xb5, 0xfa, 0x12, 0xc6, 0x96, 0x60, 0xff, 0x12, 0xe6, 0xb2, 0xb7, 0xd5, 0x97, 0x30, 0x77, 0x09, 0x73, 0x97, 0x30, 0x97, 0xfd, 0xad, 0xbe, 0x04, 0x5d, 0x97, 0xa2, 0xeb, 0x52, 0x74, 0x65, 0x8f, 0xab, 0xb3, 0xc7, 0xd5, 0xd9, 0xe3, 0xea, 0xec, 0x71, 0x75, 0xf6, 0xb8, 0x3a, 0x7b, 0x5c, 0x9d, 0x3d, 0xae, 0xce, 0x1e, 0x57, 0x67, 0x8f, 0xab, 0x2f, 0x23, 0xb6, 0xcb, 0x88, 0xed, 0x32, 0xe2, 0xc6, 0x3e, 0x57, 0x5f, 0x46, 0x6c, 0x97, 0x11, 0x9b, 0x65, 0xc4, 0x6e, 0x19, 0xb9, 0xb2, 0x1c, 0x79, 0xcb, 0xd1, 0x65, 0x39, 0xfe, 0x5e, 0xce, 0xf8, 0x72, 0xc6, 0x97, 0x33, 0xbe, 0x9c, 0xf1, 0xe5, 0xf0, 0x59, 0x81, 0xdd, 0x2b, 0xb0, 0x7b, 0x05, 0x39, 0xb7, 0x02, 0x39, 0x2b, 0xd0, + 0x67, 0x05, 0xfa, 0xac, 0x40, 0x9f, 0x15, 0xc8, 0x5a, 0x81, 0x3e, 0x2b, 0xd1, 0x67, 0x25, 0xfa, 0xac, 0x44, 0xde, 0x4a, 0xe4, 0xad, 0x44, 0xde, 0x4a, 0xe4, 0xad, 0x84, 0xdf, 0x4a, 0xec, 0x5b, 0x09, 0xcd, 0x2b, 0xd0, 0xbc, 0x02, 0xcd, 0x2b, 0xc4, 0x6a, 0x15, 0x63, 0xab, 0x18, 0x5b, 0xcd, 0xb3, 0xb2, 0x9a, 0x78, 0xae, 0x26, 0x9e, 0xab, 0x89, 0xe7, 0x6a, 0xe2, 0xb9, 0x9a, 0x78, 0xb2, 0xd7, 0xd6, 0xd7, 0x10, 0x83, 0x35, 0xc4, 0x60, 0x0d, 0xba, 0xad, 0x41, 0xb7, 0x35, 0xe4, 0xd0, 0x1a, 0xe2, 0xb0, 0x86, 0x7c, 0x78, 0x95, 0xbe, 0x57, 0x79, 0x7e, 0xd6, 0xe2, 0xeb, 0xb5, 0xe8, 0xbf, 0x16, 0x7f, 0xad, 0x85, 0x66, 0x2d, 0xba, 0xad, 0x65, 0x7c, 0x2d, 0xf9, 0xb2, 0x0e, 0x5d, 0xd6, 0xc1, 0x77, 0x1d, 0xb2, 0xd6, 0x21, 0x6b, 0x1d, 0x76, 0xad, 0xc3, 0xae, 0x75, 0xd8, 0xb5, 0x0e, + 0xbb, 0xd6, 0x63, 0xd7, 0x7a, 0xec, 0x5a, 0x4f, 0x3c, 0xd7, 0xe3, 0xc3, 0xf5, 0xc4, 0x73, 0x3d, 0xf1, 0x5c, 0x4f, 0x3c, 0xd7, 0xe3, 0xc7, 0xd7, 0xe8, 0x7b, 0x8d, 0x67, 0x76, 0x03, 0x7a, 0x6c, 0x40, 0x8f, 0x0d, 0xd8, 0xbf, 0x81, 0x35, 0x61, 0x83, 0xdb, 0x47, 0x1e, 0x6e, 0x20, 0x76, 0x1b, 0x90, 0xb3, 0x81, 0xbc, 0xdc, 0x44, 0xfc, 0x36, 0x11, 0xbf, 0xd7, 0xf1, 0xf9, 0xeb, 0xd8, 0xf3, 0x3a, 0x7e, 0x7a, 0x1d, 0x5d, 0x5f, 0x47, 0xde, 0xeb, 0xe4, 0xe4, 0x66, 0xfa, 0x37, 0xa3, 0xe7, 0x66, 0xf4, 0xd9, 0x8c, 0x9d, 0x9b, 0xb1, 0x73, 0x33, 0x76, 0x6e, 0xc6, 0xce, 0xcd, 0xe4, 0xed, 0x66, 0x97, 0x86, 0x58, 0x6c, 0x41, 0xd6, 0x16, 0x64, 0x6d, 0x41, 0xd6, 0x16, 0xfa, 0xfd, 0xd0, 0xfb, 0xa1, 0xf7, 0xa3, 0x7f, 0x0a, 0xfa, 0xa5, 0x20, 0x77, 0x2b, 0x76, 0x6d, 0x85, 0xef, 0x56, 0xec, + 0xd8, 0x8a, 0x1d, 0xdb, 0xb0, 0x63, 0x1b, 0x76, 0x6c, 0xc3, 0x8e, 0x6d, 0xe8, 0xb7, 0x0d, 0xfd, 0xb6, 0x21, 0x7b, 0x1b, 0x7e, 0xd8, 0x86, 0x7e, 0xdb, 0xd0, 0x6f, 0x1b, 0xcf, 0xda, 0x76, 0x9e, 0xaf, 0xed, 0xe8, 0xb8, 0x83, 0xdc, 0xdc, 0x41, 0x6e, 0xee, 0xc0, 0x87, 0x3b, 0xd0, 0x73, 0x07, 0x7a, 0xec, 0x40, 0x8f, 0x1d, 0xc8, 0x7b, 0x03, 0x3d, 0xdf, 0x40, 0xe6, 0x9b, 0xc8, 0xdf, 0x89, 0x2e, 0x3b, 0xe1, 0xb9, 0x13, 0x3f, 0xec, 0x44, 0xf6, 0x4e, 0x7c, 0xb3, 0x13, 0xdf, 0xec, 0xc4, 0x37, 0x3b, 0xc9, 0xf5, 0x9d, 0xd8, 0xbc, 0x13, 0x7e, 0xbb, 0x98, 0xb3, 0x0b, 0xdb, 0x76, 0x11, 0x83, 0x5d, 0xe4, 0xec, 0x2e, 0x78, 0xee, 0x22, 0x0e, 0xbb, 0xc8, 0x91, 0x5d, 0xf0, 0xdc, 0x05, 0xcd, 0x6e, 0xe2, 0xbc, 0x1b, 0x3e, 0xbb, 0xe1, 0xb3, 0x1b, 0x3e, 0xbb, 0xf1, 0xdd, 0x6e, 0xf2, 0x62, + 0x37, 0x3c, 0x76, 0x33, 0xbe, 0x07, 0x1e, 0x7b, 0xe0, 0xb1, 0x07, 0x1e, 0x7b, 0xe0, 0xb1, 0x07, 0x1e, 0x7b, 0xe0, 0xb1, 0x07, 0x1b, 0xf6, 0x60, 0xc3, 0x5e, 0x74, 0xdd, 0xcb, 0x9a, 0xb9, 0x17, 0xda, 0x7d, 0xd0, 0xee, 0x83, 0x76, 0x1f, 0xb4, 0xfb, 0xa0, 0xe5, 0xec, 0xa3, 0xef, 0x83, 0xcf, 0x7e, 0x72, 0x61, 0x3f, 0xb6, 0xed, 0xc7, 0xb6, 0xfd, 0xd0, 0xef, 0x27, 0x1f, 0xf6, 0x93, 0x33, 0xfb, 0xd1, 0x63, 0x3f, 0xb9, 0xba, 0x9f, 0x5c, 0xdd, 0x4f, 0x1e, 0x1e, 0x20, 0x0f, 0x0f, 0x90, 0x87, 0x07, 0xf0, 0xe3, 0x01, 0x72, 0xf5, 0x00, 0xb9, 0x7a, 0x80, 0xb1, 0x03, 0x8c, 0x1d, 0x64, 0xec, 0x20, 0xba, 0x1e, 0x44, 0xf6, 0x41, 0xe6, 0x1d, 0x64, 0xde, 0x41, 0xc6, 0x0e, 0x32, 0x76, 0x88, 0xb1, 0x43, 0xcc, 0x3b, 0xc4, 0xbc, 0x43, 0xcc, 0x3b, 0xc4, 0xbc, 0x43, 0xe4, 0xd6, 0x21, 0x62, + 0x73, 0x88, 0xdc, 0x7a, 0x8b, 0x98, 0xbc, 0x45, 0x4c, 0xde, 0x22, 0x26, 0x87, 0x89, 0xc9, 0x61, 0x62, 0x72, 0x18, 0xff, 0x1d, 0xc6, 0xee, 0xc3, 0xc4, 0xf0, 0x30, 0x31, 0x3f, 0x8c, 0xaf, 0x0f, 0xe3, 0xeb, 0xc3, 0xf8, 0xe5, 0x30, 0x3c, 0x8f, 0xc0, 0xf3, 0x08, 0x3c, 0x8f, 0xe0, 0xef, 0x23, 0xc4, 0xee, 0x6d, 0xe6, 0xbf, 0x4b, 0xac, 0x8e, 0x12, 0xab, 0xa3, 0xe8, 0x71, 0x94, 0x5c, 0x3a, 0x4a, 0x3c, 0x8e, 0x32, 0xff, 0x28, 0xf3, 0x8f, 0x32, 0xff, 0x7d, 0xf4, 0x7a, 0x1f, 0xbd, 0xde, 0x47, 0xaf, 0xf7, 0xe1, 0x71, 0x8c, 0x79, 0xc7, 0x88, 0xf9, 0x31, 0x72, 0xe3, 0x18, 0x7e, 0x3d, 0x86, 0x5f, 0x3f, 0x44, 0x9f, 0x8f, 0x18, 0x3b, 0x4e, 0xfb, 0x38, 0xed, 0xe3, 0xf8, 0xee, 0x38, 0xcf, 0xcb, 0x09, 0x7c, 0x74, 0x02, 0x1f, 0x7d, 0x0c, 0xbf, 0x4f, 0x98, 0xf3, 0x09, 0x73, 0x3e, 0x81, + 0xe6, 0x13, 0x68, 0x3e, 0x81, 0xe6, 0x13, 0x68, 0x3e, 0x85, 0xe6, 0x53, 0x68, 0x3e, 0xc5, 0x8f, 0x9f, 0xe2, 0xc7, 0x93, 0xf8, 0xf9, 0x24, 0x7e, 0x3e, 0x89, 0x9f, 0x4f, 0xe2, 0x97, 0x93, 0xc8, 0x3f, 0x89, 0xfc, 0x93, 0xc8, 0x3f, 0x89, 0x8c, 0xcf, 0xb0, 0xe1, 0x33, 0x6c, 0xf8, 0x0c, 0x5b, 0x3f, 0x27, 0x3e, 0xa7, 0x88, 0xf1, 0x29, 0x62, 0x7c, 0x8a, 0x5c, 0x39, 0x45, 0xae, 0x9c, 0x82, 0xef, 0x69, 0x78, 0x9e, 0x86, 0xe7, 0x69, 0x78, 0x9e, 0x86, 0xe7, 0x69, 0x78, 0x9d, 0x21, 0xb6, 0x67, 0x88, 0xed, 0x19, 0x78, 0x7c, 0x8d, 0xbe, 0x5f, 0xe3, 0xbf, 0xaf, 0xb1, 0xff, 0x6b, 0xfc, 0x77, 0x96, 0xdc, 0x3b, 0x4b, 0x8e, 0x9d, 0x25, 0xae, 0xe7, 0x98, 0x7b, 0x1e, 0x5f, 0x9c, 0xc7, 0x17, 0xe7, 0xf1, 0xc5, 0x79, 0x74, 0x3f, 0x8f, 0xcf, 0xbf, 0xe1, 0x79, 0xf9, 0x16, 0xba, 0x6f, 0x79, + 0x5e, 0xbe, 0xc5, 0x7f, 0xdf, 0xe2, 0x87, 0x6f, 0xb1, 0xe9, 0x5b, 0x6c, 0xfa, 0x16, 0x9b, 0xbe, 0xc5, 0xa6, 0x6f, 0x91, 0xfd, 0x2d, 0x36, 0x7d, 0x07, 0x8f, 0xef, 0x90, 0x7f, 0x01, 0xb9, 0xdf, 0x23, 0xf3, 0x12, 0xcf, 0xdb, 0x25, 0xe6, 0x5f, 0x66, 0xfe, 0x65, 0xe6, 0x5f, 0x66, 0xfe, 0x65, 0xe6, 0x5f, 0x66, 0xfe, 0x65, 0xe6, 0x73, 0xe6, 0xd4, 0x7f, 0x24, 0x46, 0x3f, 0x51, 0xff, 0x09, 0x5e, 0x57, 0x98, 0x7b, 0x05, 0xdd, 0xaf, 0xe0, 0x87, 0xab, 0xd0, 0x5d, 0x85, 0xee, 0x2a, 0x63, 0x57, 0x19, 0xbb, 0x8a, 0x9c, 0xab, 0xc8, 0xb9, 0x8a, 0x9c, 0x9f, 0x91, 0xf3, 0x33, 0xb4, 0x3f, 0x43, 0xfb, 0x33, 0x76, 0xfe, 0x4c, 0x0e, 0xfe, 0x8c, 0xcf, 0x7e, 0x86, 0xdf, 0x2f, 0x8c, 0xfd, 0x82, 0x8f, 0x7e, 0x21, 0x1f, 0x7e, 0xc1, 0x47, 0xbf, 0xe0, 0xa3, 0x6b, 0xd0, 0xfc, 0x8a, 0xef, 0x7e, + 0xc3, 0xf6, 0xdf, 0xb0, 0xfd, 0x3a, 0xbc, 0xaf, 0xc3, 0xfb, 0x3a, 0xbc, 0xaf, 0xc3, 0xfb, 0x3a, 0xbc, 0xaf, 0xc3, 0xfb, 0x26, 0x3e, 0xbe, 0x49, 0xde, 0xdf, 0x24, 0xef, 0x6f, 0x12, 0x8f, 0x9b, 0xd8, 0x7f, 0x13, 0x9f, 0xdd, 0xc4, 0x47, 0x37, 0xf1, 0xd1, 0x4d, 0xfc, 0x7e, 0x0b, 0xbf, 0xdc, 0x22, 0x47, 0x6e, 0xc3, 0xe3, 0x36, 0x3c, 0x6e, 0xc3, 0xe3, 0x36, 0x3c, 0x38, 0xe7, 0xea, 0x7f, 0x90, 0x37, 0x7f, 0xa0, 0xc3, 0x1f, 0xd8, 0xf4, 0x07, 0xb6, 0xff, 0x81, 0xed, 0x77, 0xb1, 0xfd, 0x2e, 0xb6, 0xdf, 0xc5, 0xf6, 0xbb, 0xcc, 0xb9, 0xcb, 0x1c, 0xce, 0xbe, 0xfa, 0x3d, 0xe4, 0xdd, 0xc3, 0x16, 0xce, 0xbf, 0xfa, 0x7d, 0x6c, 0xb9, 0x8f, 0x2d, 0xf7, 0xd1, 0xf3, 0x3e, 0xb6, 0xdc, 0xe7, 0x79, 0xba, 0xcf, 0xda, 0xf2, 0x00, 0x9e, 0x0f, 0x88, 0xc7, 0x03, 0xe8, 0x02, 0xd0, 0x05, 0xa0, + 0x0b, 0x40, 0x17, 0x80, 0x2e, 0x00, 0x5d, 0x00, 0xba, 0x00, 0x32, 0x03, 0xdb, 0xc5, 0x90, 0x4b, 0x62, 0x28, 0x02, 0x72, 0x81, 0x3a, 0xa0, 0x03, 0x18, 0x02, 0x66, 0x80, 0x35, 0x60, 0x2f, 0x38, 0x09, 0xae, 0x88, 0xa1, 0xea, 0x20, 0x2f, 0x28, 0x0f, 0x9a, 0x80, 0x64, 0x30, 0x1a, 0xcc, 0x03, 0x9b, 0xc0, 0x11, 0x31, 0x34, 0xe8, 0x35, 0xe8, 0x35, 0xe8, 0x0d, 0x78, 0x7a, 0xce, 0x8b, 0x61, 0xde, 0x13, 0x83, 0x33, 0xb1, 0x61, 0x45, 0x01, 0x64, 0xd8, 0x89, 0x60, 0x04, 0x78, 0x11, 0xf8, 0xc5, 0xf0, 0xd2, 0xf6, 0xd2, 0xf6, 0xd2, 0xf6, 0xae, 0x07, 0x07, 0xc1, 0x29, 0x70, 0x4d, 0x8c, 0x74, 0xf0, 0x4a, 0x07, 0xaf, 0xf4, 0xc8, 0x4e, 0xcf, 0xfc, 0xf4, 0xcc, 0x4f, 0x0f, 0x7d, 0xc6, 0x49, 0x00, 0xda, 0x8c, 0xd0, 0x66, 0xb6, 0x80, 0x0f, 0x44, 0x83, 0x38, 0xd0, 0x43, 0x8c, 0x2c, 0xb5, + 0x40, 0x3c, 0x18, 0x00, 0xa6, 0x82, 0x95, 0x60, 0x27, 0x38, 0x01, 0xb0, 0x39, 0x2b, 0x36, 0x67, 0x45, 0xbf, 0xac, 0xf0, 0xcb, 0x0a, 0x7d, 0x56, 0x6c, 0xc8, 0x76, 0x43, 0x8c, 0xec, 0x20, 0x47, 0x3a, 0x50, 0x10, 0x54, 0x03, 0xad, 0x41, 0x1f, 0x80, 0xbc, 0x1c, 0x4b, 0x01, 0x3e, 0xcb, 0x71, 0x0c, 0x5c, 0x00, 0xd8, 0x95, 0xb3, 0x08, 0xc0, 0x17, 0x39, 0xd1, 0x3f, 0x37, 0xf2, 0xf3, 0xc0, 0x27, 0x0f, 0xbe, 0xc8, 0x83, 0x2f, 0xf2, 0x9c, 0x01, 0xf0, 0x0b, 0x83, 0x5f, 0x18, 0xfc, 0xc2, 0xe0, 0x17, 0x06, 0xbf, 0x30, 0xf8, 0x85, 0xc1, 0x2f, 0x0c, 0x7e, 0x9c, 0xb9, 0x8d, 0x30, 0xf8, 0x85, 0xc1, 0x2f, 0x0c, 0x7e, 0x79, 0xe1, 0x97, 0x17, 0xdd, 0xf3, 0xa2, 0x7b, 0x5e, 0x7c, 0x9b, 0x17, 0xbd, 0xf3, 0xa2, 0x77, 0x5e, 0xf4, 0xce, 0x8b, 0xde, 0xf9, 0xd1, 0x3b, 0x3f, 0x7a, 0xe7, 0x2f, + 0x0d, 0x1a, 0x00, 0x7c, 0x11, 0x0e, 0x6d, 0x38, 0x76, 0x86, 0x63, 0x67, 0x38, 0xf4, 0xe1, 0xd0, 0x87, 0x43, 0x1f, 0x7e, 0x29, 0xf5, 0x9f, 0x90, 0xf1, 0xd1, 0xef, 0xa3, 0xdf, 0x47, 0x7f, 0x04, 0x3a, 0x45, 0xa0, 0x53, 0x01, 0x74, 0x2a, 0x80, 0x4e, 0x05, 0xd0, 0xa9, 0x00, 0xfd, 0x05, 0xa0, 0x2f, 0x00, 0x7d, 0x24, 0xf4, 0x9c, 0xe9, 0x8d, 0x48, 0xf8, 0x47, 0xc2, 0x9f, 0x73, 0xbd, 0x11, 0x89, 0x6d, 0x91, 0xff, 0x87, 0xb6, 0xf7, 0x80, 0x8f, 0xeb, 0xaa, 0x12, 0xc6, 0xef, 0x7d, 0x75, 0x7a, 0xd1, 0x34, 0x4d, 0x93, 0x34, 0x9a, 0xae, 0x2e, 0x8d, 0x34, 0xea, 0xd2, 0x93, 0xac, 0x2e, 0xd9, 0xb2, 0xdc, 0x65, 0x49, 0x6e, 0x92, 0xe5, 0x6e, 0xcb, 0x25, 0x71, 0x1c, 0x3b, 0x4e, 0xec, 0x38, 0xcd, 0x49, 0x9c, 0x4a, 0x28, 0x49, 0x20, 0x90, 0x02, 0xa9, 0x98, 0x84, 0x34, 0x12, 0x20, 0xc4, + 0xde, 0xc0, 0x42, 0x60, 0xd9, 0xef, 0x4b, 0xb2, 0xbb, 0xf9, 0x01, 0x7f, 0xb2, 0xcb, 0x02, 0x4b, 0x59, 0x42, 0x58, 0x20, 0x10, 0x6b, 0xfc, 0x9d, 0x7b, 0xdf, 0x7b, 0x53, 0x64, 0xc9, 0x76, 0x58, 0xfe, 0xce, 0x4f, 0x99, 0x99, 0xf7, 0xee, 0xbd, 0xef, 0xdc, 0x73, 0xce, 0x3d, 0xed, 0x9e, 0x7b, 0x1e, 0xd0, 0x26, 0x08, 0xf8, 0x0e, 0x02, 0xbe, 0x83, 0x40, 0x9b, 0x20, 0xd0, 0x26, 0x04, 0x78, 0x0f, 0x01, 0xde, 0x43, 0x30, 0xef, 0x30, 0x8c, 0x15, 0xfe, 0x10, 0xf1, 0x51, 0xf8, 0x8c, 0xc2, 0x7c, 0x62, 0x40, 0xd7, 0x38, 0xf0, 0x52, 0x09, 0xcc, 0xb7, 0x14, 0xc6, 0x2b, 0xfd, 0x14, 0xe2, 0xcb, 0x01, 0x37, 0xe0, 0xd7, 0x93, 0x92, 0x36, 0x7c, 0x15, 0xfc, 0xae, 0x06, 0xbc, 0x57, 0xc3, 0x9c, 0x6b, 0xa0, 0x7f, 0x0d, 0x8c, 0x99, 0x80, 0xf6, 0x09, 0xc0, 0x43, 0x2d, 0xc0, 0x53, 0x07, 0xbc, + 0x95, 0x04, 0x7a, 0x27, 0xbf, 0x83, 0xf8, 0x7a, 0xc0, 0x6f, 0x3d, 0xe0, 0xa9, 0x01, 0xf0, 0xd6, 0x00, 0xbc, 0xd0, 0x08, 0x73, 0x6d, 0x84, 0x76, 0x4d, 0x00, 0x5f, 0x13, 0xe0, 0xba, 0x19, 0xda, 0xb6, 0xc0, 0x98, 0x2d, 0xf0, 0x6c, 0xf0, 0x7d, 0xf9, 0x56, 0xe0, 0xb5, 0x56, 0xe8, 0xdb, 0x0a, 0xb4, 0x05, 0x3f, 0x98, 0x6f, 0x05, 0x7e, 0x6e, 0x05, 0x7e, 0x6e, 0x05, 0x7e, 0x6e, 0x05, 0xfe, 0x6c, 0x05, 0x9e, 0x6a, 0x85, 0x71, 0xda, 0x80, 0xa7, 0xda, 0xa0, 0x4f, 0x1b, 0xb4, 0x6b, 0x83, 0x31, 0xda, 0x80, 0x26, 0x6d, 0xd0, 0xa6, 0x0d, 0x68, 0xd7, 0x06, 0xe3, 0xb7, 0xc3, 0xfd, 0x76, 0xe0, 0xf7, 0x76, 0xe0, 0xf7, 0x76, 0x80, 0xa1, 0x1d, 0xda, 0xb4, 0x03, 0x4d, 0xda, 0x61, 0x1e, 0xed, 0xf0, 0xdc, 0x76, 0x68, 0xd7, 0x0e, 0xf8, 0x6c, 0x07, 0x7c, 0x4a, 0x80, 0x4f, 0x09, 0xc6, 0x92, + 0x00, 0x9f, 0x12, 0xcc, 0x59, 0x02, 0x58, 0x25, 0x78, 0xa6, 0x04, 0xed, 0x24, 0xc0, 0x87, 0x04, 0xcf, 0xeb, 0x80, 0x36, 0x1d, 0x00, 0x57, 0x07, 0x8c, 0xd5, 0x01, 0xf7, 0x3b, 0xe0, 0x7e, 0x07, 0xdc, 0xef, 0x80, 0xfb, 0x1d, 0xf0, 0xbc, 0x0e, 0x80, 0xbd, 0x13, 0x60, 0xef, 0x84, 0x36, 0x9d, 0x00, 0x53, 0x27, 0xc0, 0xde, 0x09, 0xb0, 0x77, 0x42, 0xbb, 0x4e, 0x80, 0xab, 0x13, 0x60, 0x5f, 0x04, 0x7d, 0x17, 0x01, 0x1c, 0x8b, 0x00, 0x8e, 0x45, 0x00, 0xc7, 0x22, 0xe8, 0xbf, 0x08, 0xe0, 0x58, 0x04, 0x70, 0x2c, 0x02, 0x38, 0xba, 0xe0, 0x19, 0x5d, 0x00, 0x47, 0x17, 0xc0, 0xd1, 0x05, 0x70, 0x74, 0xc1, 0x73, 0xba, 0xa0, 0x7f, 0x17, 0xb4, 0xeb, 0x82, 0xe7, 0x74, 0x01, 0x1c, 0xdd, 0xd0, 0xa6, 0x1b, 0x9e, 0xd1, 0x0d, 0x63, 0x75, 0xc3, 0xfd, 0x6e, 0xe0, 0x8f, 0x6e, 0xa0, 0x6d, 0x37, + 0xe0, 0xba, 0x1b, 0xd6, 0x6b, 0x37, 0xd0, 0xb1, 0xe7, 0x3a, 0xc4, 0xf7, 0x02, 0x0c, 0xbd, 0x00, 0x43, 0x2f, 0xc0, 0xd0, 0x0b, 0x63, 0xf4, 0x02, 0x0c, 0xbd, 0x00, 0x43, 0x2f, 0xe0, 0x0f, 0xfc, 0x7d, 0xbe, 0x0f, 0xf0, 0xd3, 0x07, 0xf8, 0xe9, 0x03, 0xfc, 0xf4, 0xc1, 0x58, 0x7d, 0x00, 0x57, 0x1f, 0xc0, 0xd5, 0x07, 0x70, 0xf5, 0xc1, 0xf3, 0xfa, 0x00, 0xae, 0x7e, 0x98, 0x57, 0x3f, 0xcc, 0x6b, 0x00, 0xe6, 0x35, 0x00, 0xcf, 0x1c, 0x80, 0x31, 0x07, 0x60, 0xcc, 0x01, 0x18, 0x73, 0x00, 0xc6, 0x1c, 0x80, 0x31, 0x07, 0x60, 0xcc, 0x01, 0x18, 0x73, 0x00, 0xc6, 0x1c, 0x84, 0x31, 0x07, 0x61, 0xcc, 0x41, 0x18, 0x73, 0x10, 0xc6, 0x1c, 0x84, 0x31, 0x07, 0x61, 0xcc, 0x41, 0x18, 0x73, 0x10, 0xc6, 0x1c, 0x84, 0x31, 0x07, 0x61, 0xae, 0x43, 0xc0, 0x2f, 0x8b, 0xe1, 0xda, 0x62, 0xb8, 0xb6, + 0x18, 0xae, 0x2d, 0x86, 0x6b, 0x8b, 0x61, 0xfe, 0x4b, 0x60, 0x6e, 0x4b, 0x60, 0xfe, 0x4b, 0x60, 0xfe, 0x4b, 0x60, 0xfe, 0x4b, 0x60, 0x7e, 0x4b, 0x80, 0xae, 0x4b, 0x60, 0xad, 0x2d, 0x01, 0x1e, 0x5a, 0x02, 0x38, 0x58, 0x02, 0xcf, 0x19, 0x86, 0x76, 0xc3, 0x00, 0xcf, 0x30, 0x3c, 0x63, 0x18, 0xda, 0x0c, 0x03, 0x2c, 0xc3, 0x30, 0xd6, 0x30, 0xdc, 0x1f, 0x86, 0xfb, 0x4b, 0xe1, 0xfe, 0x52, 0xb8, 0xbf, 0x14, 0x70, 0xb2, 0x14, 0x70, 0xb2, 0x14, 0x70, 0x32, 0x62, 0x81, 0x3f, 0x58, 0x9f, 0x23, 0xd0, 0x67, 0x04, 0x78, 0x70, 0x04, 0x9e, 0x3f, 0x02, 0xbc, 0x3a, 0x02, 0x6b, 0x67, 0x19, 0xf0, 0xf3, 0x32, 0xe0, 0xf9, 0x65, 0xd0, 0x7e, 0x19, 0xb4, 0x5f, 0x06, 0xed, 0x97, 0x43, 0xfb, 0xe5, 0xb0, 0x8e, 0x96, 0x03, 0x1c, 0xcb, 0xe1, 0x19, 0x2b, 0x60, 0x4e, 0x2b, 0xa0, 0xef, 0x0a, 0x98, + 0xd3, 0x0a, 0x98, 0xd3, 0x4a, 0x78, 0xde, 0x6a, 0x58, 0x5b, 0xab, 0x61, 0x4d, 0xac, 0x06, 0xfc, 0xaf, 0x86, 0xb5, 0xb5, 0x06, 0xf0, 0xb3, 0x06, 0xf0, 0xb3, 0x06, 0xf0, 0xb3, 0x06, 0x60, 0x1a, 0x85, 0xb5, 0x31, 0x0a, 0xf7, 0x47, 0xe1, 0xfe, 0x28, 0xdc, 0x1f, 0x85, 0x79, 0x8f, 0xc2, 0xda, 0x5b, 0x0b, 0xf4, 0x59, 0x0b, 0xb8, 0x5b, 0x0b, 0xdf, 0xc7, 0x00, 0xb7, 0x63, 0x30, 0xf6, 0x18, 0x8c, 0x3b, 0x06, 0x6d, 0xc7, 0xa0, 0xed, 0x18, 0xe0, 0x63, 0x1c, 0xf0, 0x30, 0x0e, 0x63, 0x8d, 0x03, 0x9c, 0xe3, 0x30, 0xaf, 0x09, 0xc0, 0xed, 0x04, 0xe0, 0x65, 0x02, 0xc6, 0x9e, 0x80, 0xdf, 0xeb, 0xe0, 0xfe, 0x06, 0x58, 0x9b, 0x1b, 0xa0, 0xdf, 0x06, 0xe8, 0xb3, 0x01, 0xae, 0x6d, 0x80, 0xf1, 0x36, 0xc2, 0x78, 0x1b, 0xe1, 0xb9, 0x1b, 0x41, 0x96, 0x6e, 0x84, 0xb6, 0x1b, 0x61, 0x6e, 0x1b, + 0x81, 0x46, 0x1b, 0x61, 0x5e, 0x1b, 0x61, 0x5e, 0x9b, 0x60, 0x5e, 0x9b, 0x00, 0x0f, 0x9b, 0x40, 0x4e, 0x6d, 0x02, 0xd9, 0xb3, 0x09, 0x70, 0xb1, 0x09, 0x9e, 0xb1, 0x09, 0x70, 0xb1, 0x09, 0x9e, 0x3b, 0x09, 0x6b, 0x71, 0x12, 0xc6, 0x9d, 0x84, 0xb9, 0x4e, 0x02, 0x9c, 0x93, 0x80, 0xf7, 0x49, 0xe0, 0x81, 0x29, 0xc0, 0xc5, 0x14, 0xcc, 0x77, 0x0a, 0x68, 0xbd, 0x19, 0xee, 0x6f, 0x86, 0xb1, 0x37, 0x43, 0x9f, 0x69, 0xc0, 0xf3, 0x34, 0xc8, 0xa7, 0x2d, 0xb0, 0x9e, 0xb7, 0xc0, 0xf3, 0xb7, 0x82, 0xcc, 0xd9, 0x0a, 0xb0, 0x6c, 0x03, 0x58, 0xb7, 0x01, 0x5c, 0xdb, 0xe1, 0xfe, 0x76, 0xa0, 0xdf, 0x0e, 0xa0, 0xc9, 0x0e, 0x98, 0xdf, 0x4e, 0x18, 0x7f, 0x27, 0xd0, 0x74, 0x27, 0xc0, 0xb2, 0x0b, 0xf0, 0xb8, 0x0b, 0xe8, 0xbf, 0x1b, 0xe6, 0xb6, 0x1b, 0xe0, 0xd9, 0x0d, 0xcf, 0xdb, 0x0d, 0xb0, + 0xee, 0x86, 0xe7, 0xed, 0x81, 0xf1, 0xf6, 0x00, 0x0e, 0xf7, 0x00, 0xce, 0x66, 0xe0, 0xfe, 0x0c, 0xd0, 0x60, 0x06, 0xe0, 0xdb, 0x0b, 0x7d, 0xf6, 0x42, 0x9f, 0x7d, 0x70, 0x6d, 0x1f, 0xe0, 0x7b, 0x3f, 0xc0, 0xb2, 0x1f, 0xae, 0xed, 0x87, 0x6b, 0x07, 0x60, 0x2e, 0x07, 0x80, 0x2e, 0x07, 0x40, 0x16, 0x5e, 0x01, 0xf8, 0xb9, 0x02, 0x60, 0xb9, 0x02, 0xfa, 0x5f, 0x09, 0xb8, 0x38, 0x08, 0xcf, 0x3d, 0x08, 0xf0, 0x5c, 0x05, 0x73, 0xbe, 0x0a, 0x68, 0x7e, 0x08, 0x9e, 0x77, 0x08, 0x64, 0xd8, 0x21, 0x80, 0xe9, 0x10, 0xcc, 0xe3, 0x6a, 0xc0, 0xcb, 0xd5, 0xd0, 0xff, 0x6a, 0xe0, 0x81, 0xc3, 0x00, 0xef, 0x11, 0x78, 0xfe, 0x35, 0xc0, 0xcb, 0xd7, 0xc0, 0x33, 0xae, 0x81, 0x67, 0x5f, 0x03, 0xe3, 0x1e, 0x05, 0xbc, 0x1d, 0x05, 0xd8, 0x8f, 0xc2, 0xba, 0xb8, 0x16, 0xc6, 0xbe, 0x0e, 0xf0, 0x7c, + 0x0c, 0x64, 0xe8, 0x31, 0xb8, 0x77, 0x1c, 0xe6, 0x79, 0x1c, 0xc6, 0xb9, 0x1e, 0xe6, 0x7d, 0x3d, 0xd0, 0xed, 0x7a, 0x80, 0xe9, 0x7a, 0x18, 0xe7, 0x7a, 0x98, 0xcf, 0xf5, 0x00, 0xf7, 0xf5, 0x30, 0xa7, 0x13, 0x30, 0x16, 0xf8, 0xfc, 0xfc, 0x0d, 0xc0, 0x0b, 0x37, 0x02, 0xfe, 0x6e, 0x04, 0x9a, 0xdc, 0x04, 0xd7, 0x6e, 0x06, 0xdc, 0xdc, 0x0c, 0x70, 0xde, 0x02, 0x78, 0x3c, 0x09, 0xe3, 0xde, 0x0a, 0x30, 0xde, 0x06, 0xf3, 0xba, 0x0d, 0xe0, 0xbd, 0x1d, 0x3e, 0x6f, 0x87, 0xcf, 0x53, 0x80, 0xff, 0x53, 0x30, 0xd6, 0x1d, 0xd0, 0x1e, 0x7c, 0x76, 0xfe, 0x4e, 0x80, 0xef, 0x4e, 0x80, 0xf5, 0x2e, 0x80, 0xf9, 0x6e, 0x80, 0xe1, 0x6e, 0xf8, 0x7e, 0x0f, 0x7c, 0xbf, 0x17, 0xbe, 0xdf, 0x0b, 0xdf, 0x3f, 0x01, 0xb0, 0x7d, 0x02, 0x78, 0xfa, 0x3e, 0xf8, 0x7d, 0x1f, 0xd0, 0xe3, 0x93, 0x30, 0x87, + 0x4f, 0xc1, 0x58, 0x9f, 0x82, 0xf9, 0x82, 0x1f, 0xcd, 0x7f, 0x1a, 0xe0, 0xfb, 0x34, 0xd0, 0xeb, 0x33, 0x20, 0x3f, 0xee, 0x07, 0x9e, 0xb9, 0x1f, 0x60, 0x7d, 0x00, 0xae, 0x3f, 0x00, 0x38, 0x01, 0x3f, 0x97, 0x7f, 0x10, 0xe8, 0xff, 0x20, 0x7c, 0x82, 0xff, 0xca, 0x7f, 0x16, 0x60, 0xff, 0x1c, 0x8c, 0x05, 0xbe, 0x27, 0xff, 0x79, 0xc0, 0xd3, 0xe7, 0xe1, 0xf9, 0x5f, 0x00, 0xf8, 0xbf, 0x00, 0x73, 0x79, 0x18, 0xc6, 0x79, 0x18, 0x78, 0xf9, 0x61, 0x78, 0xc6, 0xc3, 0xb0, 0x86, 0x1f, 0x01, 0x5e, 0x06, 0xbf, 0x92, 0x7f, 0x14, 0x70, 0xf7, 0x18, 0xd0, 0xef, 0x8b, 0xd0, 0xee, 0x4b, 0x80, 0x9b, 0xc7, 0x01, 0x9e, 0x27, 0xa0, 0xed, 0x93, 0xa0, 0x77, 0x9e, 0x84, 0xfe, 0x4f, 0xc1, 0xf7, 0xa7, 0x60, 0xdc, 0xa7, 0x01, 0x9f, 0xcf, 0xc0, 0xbd, 0x67, 0x00, 0x87, 0x5f, 0x86, 0x7b, 0x5f, 0x86, + 0x7b, 0xa7, 0xa1, 0xef, 0x69, 0x98, 0xdf, 0x69, 0xb8, 0xf6, 0x15, 0x18, 0xef, 0x2b, 0x30, 0xc6, 0xb3, 0x00, 0xe3, 0xb3, 0xd0, 0xf6, 0x39, 0x80, 0xf1, 0x39, 0xc0, 0xd3, 0x73, 0x80, 0xbb, 0xaf, 0xc2, 0x73, 0xbe, 0x0a, 0x63, 0x3c, 0x0f, 0x38, 0x01, 0x3f, 0x8b, 0x7f, 0x01, 0x70, 0xf4, 0x02, 0xf4, 0x79, 0x11, 0x68, 0xfb, 0x22, 0xac, 0x8b, 0x17, 0xe1, 0x19, 0x2f, 0x02, 0x6f, 0xbf, 0x48, 0xae, 0xc1, 0xb8, 0x2f, 0x02, 0x1d, 0x5e, 0x04, 0x18, 0x5f, 0x02, 0x7c, 0xbf, 0x04, 0xfc, 0xf1, 0x12, 0xd0, 0x0a, 0x7c, 0x2e, 0x1e, 0x7c, 0x2e, 0x1e, 0x7c, 0x2e, 0xfe, 0x6b, 0xf0, 0x9c, 0xaf, 0x01, 0xcc, 0xe0, 0x43, 0xf1, 0xe0, 0x2b, 0xf1, 0xaf, 0xc0, 0xfc, 0x5f, 0x85, 0xb1, 0x5e, 0x05, 0xba, 0x82, 0x4f, 0xc4, 0x7f, 0x1d, 0xfa, 0x83, 0xef, 0xc3, 0x7f, 0x03, 0xc6, 0xf8, 0x26, 0x8c, 0xfd, + 0x1a, 0x8c, 0xf3, 0x1a, 0x3c, 0xf3, 0x35, 0x80, 0xf5, 0x35, 0xc0, 0x1b, 0xf8, 0x36, 0xfc, 0x6b, 0x40, 0xab, 0xd7, 0x61, 0xbc, 0x7f, 0x28, 0x46, 0x1c, 0x2a, 0x03, 0xe3, 0x92, 0xe5, 0x1f, 0x40, 0x61, 0xd4, 0x88, 0x5a, 0x51, 0x07, 0xea, 0x96, 0x3a, 0x11, 0xcb, 0x60, 0x86, 0xc5, 0x33, 0x08, 0x73, 0x0c, 0xe6, 0x48, 0x29, 0x37, 0x86, 0x45, 0xcc, 0x24, 0x34, 0xe6, 0x35, 0x1c, 0xac, 0x3e, 0xad, 0x56, 0x18, 0x45, 0x82, 0x30, 0x39, 0x88, 0x34, 0x1a, 0x71, 0x14, 0x89, 0xe2, 0x94, 0x38, 0xd4, 0x21, 0x59, 0xad, 0xf6, 0x58, 0x24, 0x58, 0xe2, 0x0b, 0xe9, 0x74, 0xfe, 0x52, 0x5b, 0x1b, 0x9b, 0xa8, 0x29, 0x60, 0x1c, 0x76, 0x13, 0x67, 0xc6, 0xc1, 0x68, 0xa4, 0x8d, 0xab, 0xab, 0x8d, 0x04, 0x8b, 0x4d, 0x4c, 0xb0, 0x38, 0x6a, 0xab, 0x6d, 0x63, 0x94, 0x9b, 0xf0, 0xb3, 0x82, 0xc1, 0x01, + 0x6b, 0xa0, 0x1e, 0xfe, 0xfe, 0xec, 0x69, 0xee, 0x59, 0x5a, 0x29, 0x6d, 0x1f, 0x88, 0xfb, 0xcb, 0xeb, 0xaa, 0x82, 0x5c, 0xde, 0x0d, 0x26, 0xde, 0x53, 0x96, 0xac, 0x2c, 0x5a, 0x94, 0x8c, 0xd6, 0x47, 0x7d, 0x06, 0xaf, 0x71, 0x5d, 0x5e, 0x61, 0x89, 0x3b, 0xbf, 0xa4, 0x30, 0x0f, 0x3e, 0xf3, 0xdd, 0xf0, 0x99, 0x7a, 0x98, 0xfd, 0x97, 0x73, 0xf1, 0x24, 0xbb, 0xf9, 0xdc, 0xfd, 0xdc, 0x41, 0x67, 0xc4, 0x6f, 0x0d, 0x75, 0x4c, 0x34, 0xd6, 0x0f, 0x37, 0x97, 0x46, 0x42, 0xae, 0x2d, 0x57, 0x04, 0x13, 0xa5, 0xd1, 0xf2, 0x86, 0x68, 0x65, 0xbd, 0xd5, 0x6e, 0xcd, 0x9f, 0x15, 0xf3, 0x4b, 0x8b, 0xf2, 0xf2, 0x8a, 0x4a, 0xf3, 0xdd, 0xa5, 0x64, 0x80, 0x52, 0xee, 0xd6, 0xbf, 0x4e, 0x35, 0xf1, 0x31, 0x98, 0x1c, 0xfc, 0x07, 0x42, 0x16, 0x70, 0x20, 0x22, 0x07, 0xaa, 0x94, 0xca, 0x58, 0x8c, + 0x38, 0x3c, 0x40, 0xcc, 0x56, 0x06, 0x23, 0x66, 0x13, 0xe2, 0xb8, 0xc9, 0x41, 0x16, 0x63, 0x3c, 0x85, 0x87, 0x10, 0x72, 0xd8, 0x6d, 0x56, 0x93, 0x51, 0xa7, 0x81, 0xc6, 0xa2, 0xa0, 0xb3, 0x97, 0x62, 0x36, 0x12, 0x35, 0x61, 0x91, 0xc5, 0xc1, 0xe2, 0x48, 0x94, 0x0d, 0xb0, 0x26, 0x5c, 0x8a, 0x71, 0x82, 0x7d, 0xea, 0x6d, 0x97, 0x4b, 0xef, 0xd2, 0xbf, 0xc5, 0x3e, 0xfe, 0x88, 0x2f, 0xa4, 0xb9, 0x76, 0xf6, 0xa6, 0x6b, 0xc5, 0x88, 0xf7, 0x11, 0xa6, 0xe5, 0x5f, 0x18, 0x17, 0x53, 0xde, 0xb8, 0xba, 0x74, 0xc0, 0x3f, 0xfb, 0xf6, 0xec, 0x7f, 0xf9, 0x9c, 0x78, 0x71, 0xea, 0x39, 0x5b, 0x21, 0x39, 0xe9, 0x00, 0x50, 0xe0, 0xd4, 0x13, 0xfc, 0x3d, 0x14, 0x8e, 0x0a, 0x74, 0xf0, 0x79, 0x00, 0x83, 0xc7, 0x03, 0x83, 0xa7, 0xf3, 0x97, 0xae, 0x91, 0x02, 0x5a, 0x80, 0x86, 0x65, 0x10, 0xbb, + 0x09, 0xcc, 0xde, 0xc9, 0x41, 0x81, 0x63, 0x18, 0x66, 0x6a, 0x50, 0x83, 0x45, 0x11, 0x8f, 0x21, 0x8c, 0xb7, 0xe2, 0x21, 0xaf, 0x14, 0x43, 0x1a, 0x46, 0x64, 0x34, 0xe2, 0xcc, 0xbc, 0x6d, 0x51, 0xa6, 0xe9, 0xa8, 0xe4, 0x46, 0xa8, 0xbc, 0x34, 0x16, 0x29, 0x2c, 0xf0, 0xfb, 0xbc, 0x6e, 0xa7, 0x23, 0xcf, 0x6a, 0xd0, 0x91, 0xf9, 0xe8, 0xb2, 0xe7, 0x63, 0x22, 0x33, 0xaa, 0xab, 0x4d, 0xd6, 0x67, 0xcf, 0x0b, 0x07, 0x4d, 0xac, 0xc3, 0xee, 0x4c, 0xd4, 0x10, 0x12, 0xba, 0xd8, 0x27, 0xd5, 0x59, 0xfe, 0xa6, 0x70, 0x5d, 0xed, 0xd8, 0xe6, 0xa1, 0xb6, 0x39, 0x73, 0x6d, 0x89, 0xdb, 0xe3, 0x01, 0x47, 0x65, 0xd4, 0xe9, 0xb7, 0x19, 0x78, 0x33, 0x9f, 0x35, 0xf7, 0x73, 0x6f, 0xb8, 0x7c, 0xc3, 0x9d, 0x8d, 0x9b, 0xa3, 0x19, 0x14, 0xfc, 0x75, 0x4a, 0x5f, 0x10, 0x29, 0x77, 0x07, 0x4b, 0x44, + 0x51, 0x34, 0x12, 0x7c, 0x9c, 0xff, 0x13, 0x42, 0xc2, 0xef, 0x01, 0x1f, 0x1c, 0x72, 0xa1, 0x25, 0xd2, 0x20, 0xc6, 0x3c, 0xd0, 0x85, 0x56, 0x2b, 0x10, 0xc4, 0x8d, 0x64, 0x96, 0x8c, 0x06, 0x31, 0x1b, 0x61, 0x96, 0xdc, 0x28, 0x25, 0x92, 0x0e, 0x6b, 0x34, 0x78, 0x4c, 0x21, 0x15, 0xcf, 0x03, 0x55, 0x5d, 0xbc, 0xcb, 0x09, 0x14, 0xcb, 0xb3, 0x5a, 0xcc, 0x26, 0x83, 0x56, 0x23, 0x0a, 0x30, 0x16, 0xa7, 0xa7, 0xd3, 0x14, 0x44, 0x2d, 0x16, 0x03, 0xea, 0xcc, 0x02, 0x8e, 0x80, 0x8d, 0xfc, 0xb1, 0x0f, 0x8f, 0x7b, 0x02, 0xa9, 0xb7, 0x79, 0x0b, 0x3e, 0xf7, 0xc7, 0xca, 0xd9, 0x1f, 0x3f, 0xa1, 0x8f, 0xfb, 0xbf, 0xc0, 0xfc, 0x8e, 0xd3, 0xcf, 0x06, 0x1a, 0x99, 0xe2, 0xd9, 0x3f, 0x34, 0xc1, 0x14, 0x7e, 0xb6, 0xa2, 0x6b, 0xf6, 0xab, 0xa9, 0xfb, 0x9e, 0xc6, 0xd7, 0xa6, 0x9e, 0x75, 0x7a, + 0xf9, 0x07, 0x9e, 0x4e, 0x35, 0x7e, 0x91, 0xbc, 0xd2, 0x87, 0x45, 0x5f, 0x81, 0xb5, 0xf4, 0x1c, 0xc0, 0xab, 0x43, 0x36, 0x14, 0x45, 0x5d, 0x52, 0x47, 0x01, 0xc6, 0x0c, 0x40, 0x0c, 0x94, 0x05, 0x56, 0x9a, 0x14, 0x31, 0xcb, 0x72, 0x63, 0x1a, 0x4c, 0x40, 0x15, 0x88, 0x53, 0x34, 0x06, 0xa0, 0x4f, 0xf1, 0x43, 0x0e, 0xbb, 0x5e, 0x8f, 0x51, 0x24, 0x64, 0x8f, 0x3a, 0xa2, 0x7a, 0x9b, 0x3e, 0x0f, 0x20, 0x15, 0x91, 0x0e, 0xeb, 0xb4, 0x00, 0x67, 0x18, 0x30, 0x4d, 0x96, 0x4e, 0xc0, 0x0d, 0x08, 0x4f, 0x92, 0x6f, 0x75, 0xca, 0xd2, 0x11, 0x39, 0x2b, 0x59, 0x40, 0x01, 0x2b, 0xf3, 0xec, 0xc8, 0xfe, 0xfe, 0xe2, 0x7b, 0xef, 0x4f, 0xbd, 0xf5, 0xc7, 0x07, 0x3f, 0x7d, 0xf7, 0x67, 0x6e, 0xfa, 0xd6, 0x91, 0xe6, 0xa2, 0xce, 0xa9, 0x2e, 0xdc, 0xd8, 0x75, 0xfc, 0x1b, 0x87, 0x66, 0x3f, 0x7a, + 0x3c, 0xd2, 0x39, 0x96, 0xdc, 0x71, 0x38, 0x15, 0xe0, 0x86, 0x52, 0x25, 0xd7, 0xee, 0x3c, 0x3c, 0xf3, 0xc5, 0xda, 0x4d, 0xb7, 0xae, 0xa9, 0xdd, 0xb0, 0xbc, 0xd3, 0xd9, 0x96, 0xfa, 0xf3, 0xf8, 0x27, 0x77, 0xb7, 0x00, 0xc4, 0x18, 0xad, 0x3d, 0xff, 0x3e, 0x50, 0xe8, 0x29, 0x54, 0x4e, 0x24, 0x80, 0x0d, 0x56, 0x3e, 0x8b, 0x79, 0xc4, 0x0c, 0x20, 0x16, 0x10, 0xc6, 0xa2, 0x19, 0x80, 0x97, 0x27, 0x2a, 0x10, 0xf0, 0xcc, 0x8c, 0x21, 0x86, 0xd9, 0x31, 0x08, 0xb0, 0x53, 0x86, 0xda, 0x89, 0x87, 0x30, 0x8a, 0x86, 0xfd, 0xde, 0x7c, 0xa7, 0xd5, 0x2c, 0xf2, 0xa8, 0x1c, 0x97, 0x8b, 0x04, 0xc1, 0x35, 0x85, 0x94, 0x85, 0x2a, 0x30, 0xc0, 0x9f, 0x97, 0x0c, 0x25, 0x6a, 0x9c, 0x2e, 0x91, 0x5e, 0x60, 0x88, 0x1c, 0x70, 0xd8, 0x0b, 0x18, 0xc2, 0x45, 0xf5, 0x6d, 0x98, 0x7b, 0x20, 0xcf, 0xb8, + 0x62, 0xcd, 0xec, 0xb3, 0x0f, 0xcc, 0x3e, 0xb7, 0x69, 0xcb, 0xd7, 0x30, 0xfb, 0xe0, 0xa7, 0xff, 0x7b, 0x4d, 0x8f, 0xe8, 0xb0, 0xda, 0x12, 0xcb, 0xaf, 0x1e, 0xdb, 0xf1, 0xec, 0xb1, 0xde, 0x81, 0xe3, 0xa7, 0xa7, 0xdb, 0x26, 0x97, 0x0f, 0x84, 0x34, 0x79, 0xb8, 0xd6, 0x34, 0xbe, 0x75, 0xdf, 0xce, 0xd7, 0xb1, 0xe9, 0x0b, 0x5f, 0xc0, 0xa6, 0xd7, 0x77, 0x4e, 0x2d, 0x9f, 0xd4, 0x9b, 0x22, 0x95, 0x91, 0xc1, 0xdb, 0xbf, 0x7f, 0xec, 0xfa, 0x37, 0x4f, 0x0d, 0x98, 0x0b, 0x2a, 0x03, 0xa2, 0x96, 0x54, 0xd5, 0x58, 0x9b, 0xba, 0x9f, 0xce, 0xa7, 0x0f, 0xfd, 0xa7, 0xbc, 0x84, 0xcc, 0xa5, 0x58, 0xe4, 0xfd, 0xb0, 0x18, 0x90, 0x19, 0xa8, 0xc2, 0x0e, 0x78, 0x73, 0xaf, 0xb0, 0xec, 0xc0, 0xe8, 0xe0, 0x69, 0x2d, 0x34, 0xac, 0x02, 0xee, 0xd2, 0x8a, 0x82, 0x76, 0x06, 0x01, 0xc3, 0x68, 0x35, + 0xbb, 0x61, 0xe5, 0x50, 0x4a, 0xed, 0x20, 0xf2, 0x0e, 0x8d, 0x71, 0x40, 0xb9, 0x9d, 0x20, 0x0f, 0x18, 0x86, 0x4e, 0x7f, 0x06, 0x96, 0x9e, 0xfc, 0x80, 0xcb, 0xe8, 0xb7, 0x27, 0xb7, 0x9f, 0x54, 0x3f, 0xb7, 0x0b, 0xac, 0x5a, 0x10, 0xb6, 0x33, 0xf3, 0x76, 0x45, 0x2c, 0xab, 0xf6, 0x1c, 0x1d, 0x95, 0x4c, 0xbd, 0xdd, 0xb1, 0x60, 0x38, 0xe6, 0x0c, 0xd9, 0x63, 0x79, 0x44, 0xe2, 0x86, 0x4d, 0xd8, 0x8c, 0x0b, 0xb0, 0x2c, 0x54, 0x23, 0xd1, 0xfa, 0x24, 0x11, 0xb1, 0x32, 0xcb, 0x44, 0xeb, 0x2b, 0x98, 0x4b, 0x11, 0x01, 0x56, 0x33, 0x6f, 0x1e, 0xf9, 0x8f, 0x07, 0x2c, 0x13, 0x23, 0x81, 0xe6, 0xe5, 0xd5, 0xf1, 0xfe, 0xa2, 0x3c, 0xc1, 0x26, 0x58, 0x1c, 0x05, 0xf6, 0x68, 0x7c, 0x64, 0x71, 0xfc, 0x8e, 0x5f, 0x3e, 0x32, 0x7a, 0x29, 0xda, 0xe8, 0xf2, 0x8c, 0xdf, 0x5d, 0xbf, 0x57, 0xdb, + 0x60, 0xef, 0x4d, 0x06, 0x6a, 0x43, 0x76, 0xb3, 0xa9, 0xd5, 0x06, 0x2b, 0xb9, 0x28, 0x3f, 0xe4, 0xd7, 0x1d, 0xd1, 0x1d, 0x78, 0x0d, 0xb3, 0xf7, 0x5f, 0x84, 0x64, 0x26, 0x22, 0xff, 0x60, 0xfd, 0xf0, 0xf7, 0x01, 0x37, 0x1a, 0x90, 0x1b, 0x15, 0x4a, 0x3e, 0xa4, 0xa0, 0x6a, 0x92, 0x4c, 0x1d, 0x8d, 0x81, 0x48, 0x98, 0x42, 0x43, 0x81, 0x58, 0x20, 0xc4, 0xe9, 0xf2, 0x4b, 0xb1, 0x9d, 0x11, 0x38, 0xe0, 0xfe, 0x40, 0x0d, 0xe7, 0xcc, 0x13, 0x6b, 0x2b, 0x18, 0xfa, 0xbd, 0x8d, 0xe1, 0xef, 0xdb, 0xf2, 0x6a, 0xea, 0xaf, 0x0f, 0xcd, 0xfe, 0x8c, 0xf1, 0x3d, 0x84, 0x85, 0x57, 0xb7, 0xbc, 0xdb, 0x73, 0xec, 0xc5, 0xbd, 0xa9, 0xaf, 0xe1, 0x9e, 0xbd, 0x2f, 0x1e, 0xeb, 0x61, 0x7e, 0xf0, 0xf9, 0xd4, 0x87, 0x5f, 0xdf, 0x0a, 0x16, 0xc4, 0xd7, 0x53, 0x7f, 0x7e, 0xe8, 0xba, 0xef, 0xdd, 0x3e, + 0xf0, 0xd1, 0xab, 0x03, 0xb7, 0x7f, 0x57, 0xd5, 0x01, 0x5f, 0x84, 0x67, 0x5b, 0x50, 0x00, 0xb5, 0x4b, 0x2d, 0x22, 0x66, 0x04, 0x4c, 0x68, 0xb1, 0x11, 0x00, 0xe0, 0xc6, 0xb4, 0x74, 0xd1, 0x6a, 0xb2, 0x16, 0x6d, 0x9e, 0x15, 0xa3, 0x22, 0x22, 0x40, 0x3d, 0xd6, 0x40, 0x5e, 0xc0, 0x6c, 0x32, 0x1a, 0xf4, 0x5a, 0x64, 0xc1, 0x16, 0x5d, 0x7a, 0xd1, 0xca, 0x20, 0x39, 0x02, 0x56, 0x4c, 0x40, 0x74, 0x28, 0xf0, 0x06, 0xeb, 0x12, 0x56, 0xe6, 0x27, 0x04, 0x18, 0x05, 0x30, 0x5f, 0xea, 0x67, 0xb8, 0x87, 0x82, 0xaa, 0x00, 0xfe, 0xda, 0x6b, 0x4f, 0x01, 0x4c, 0xc7, 0x08, 0x74, 0xa9, 0xe2, 0x23, 0x29, 0x27, 0xf7, 0x3e, 0x85, 0x96, 0x42, 0xce, 0xdc, 0x77, 0x84, 0x39, 0x49, 0x0b, 0xc7, 0x10, 0x59, 0x53, 0x06, 0xf0, 0xea, 0x89, 0xc6, 0x02, 0xae, 0xe1, 0x78, 0x96, 0xdb, 0x48, 0x30, 0x36, + 0x4a, 0x31, 0x26, 0x60, 0x90, 0x7f, 0xa3, 0x32, 0xca, 0xe0, 0xff, 0x7a, 0xa4, 0xb7, 0x92, 0x7f, 0xa2, 0xce, 0x5d, 0x1a, 0x16, 0x65, 0xb0, 0xc8, 0x1f, 0x57, 0x86, 0xcf, 0xdd, 0xc8, 0x5e, 0x3d, 0x3b, 0xce, 0x14, 0xcd, 0xfe, 0xf4, 0xe9, 0xa7, 0xd3, 0xc2, 0x0c, 0xc6, 0x4f, 0x3d, 0xc1, 0xb5, 0x50, 0xbb, 0xe0, 0x0a, 0x99, 0xbd, 0xe3, 0x44, 0xff, 0x0b, 0x1c, 0xbf, 0x11, 0x88, 0xc1, 0x8c, 0xc2, 0x93, 0x80, 0x2c, 0x82, 0x80, 0x47, 0x45, 0x22, 0x6e, 0x07, 0xb5, 0x98, 0x30, 0x2a, 0xad, 0xb3, 0x0d, 0x7c, 0x5d, 0x85, 0xe0, 0x37, 0xd6, 0x6a, 0x66, 0x2e, 0xda, 0x07, 0x65, 0xba, 0x00, 0x3f, 0x87, 0x8a, 0xa3, 0x14, 0x44, 0xab, 0xa3, 0x98, 0xf0, 0x73, 0x46, 0xff, 0x64, 0xa0, 0x0d, 0x88, 0x39, 0xea, 0x87, 0x6b, 0x51, 0xb4, 0xce, 0xec, 0xb8, 0x3a, 0x05, 0xe6, 0x51, 0x3c, 0x96, 0xa3, + 0x72, 0x52, 0x3f, 0x56, 0xf4, 0x8c, 0x3a, 0xb5, 0xa7, 0x73, 0x94, 0x8c, 0x8c, 0xc7, 0x52, 0x98, 0xa7, 0x16, 0xc5, 0xa5, 0x08, 0xc1, 0x2c, 0x08, 0xeb, 0x8d, 0x3c, 0x90, 0x9b, 0x1d, 0x05, 0xa0, 0x01, 0x5e, 0x50, 0x9e, 0x0c, 0x41, 0xa1, 0x16, 0x69, 0x09, 0x74, 0x02, 0xe1, 0x3d, 0x0a, 0x4d, 0x10, 0xfe, 0x8f, 0x6f, 0x62, 0xa4, 0xd9, 0xd7, 0xde, 0x61, 0xaf, 0xe6, 0x7e, 0x97, 0x2a, 0x7c, 0x7a, 0xf6, 0x73, 0xf0, 0x14, 0x18, 0x73, 0x14, 0x64, 0x69, 0x37, 0xc8, 0x9e, 0x6a, 0xd4, 0x2a, 0x35, 0xd9, 0x40, 0x05, 0x80, 0xba, 0xe1, 0x30, 0x86, 0xf1, 0x06, 0x80, 0x2e, 0xc4, 0xde, 0xe2, 0x26, 0x55, 0xe6, 0xde, 0x91, 0x66, 0xee, 0x9d, 0x94, 0x52, 0xd5, 0xa8, 0x3a, 0x1c, 0x2b, 0x8b, 0x05, 0x09, 0xa5, 0xb0, 0x95, 0x20, 0x21, 0x14, 0x51, 0x17, 0x6f, 0x01, 0x43, 0x56, 0x6f, 0xb4, 0x82, + 0xcd, 0x59, 0xbc, 0x56, 0xc0, 0x53, 0x2b, 0xe6, 0xbb, 0x77, 0xfd, 0xe9, 0xae, 0x67, 0x52, 0xbf, 0x78, 0x63, 0xc7, 0x96, 0xd7, 0xb0, 0xf5, 0x89, 0x55, 0xf7, 0x1c, 0xdd, 0x51, 0xd1, 0x6a, 0xaa, 0xf0, 0x7a, 0xda, 0x57, 0x1f, 0x18, 0xbe, 0xf2, 0x1b, 0x37, 0xf6, 0x0d, 0xdd, 0xfa, 0xed, 0xc3, 0x55, 0xcb, 0x87, 0x7a, 0x83, 0x6f, 0xd9, 0xdd, 0xcc, 0x2b, 0xb3, 0x3f, 0x5c, 0xb2, 0x98, 0x8a, 0xd1, 0x87, 0xb1, 0xf9, 0x5b, 0x3b, 0x3c, 0x55, 0x8b, 0x4a, 0x36, 0xb8, 0x0c, 0x56, 0x73, 0xac, 0x38, 0x7f, 0xf1, 0x9d, 0xdf, 0x3b, 0x7a, 0xe2, 0x9f, 0xef, 0x1a, 0xd2, 0xdb, 0x0b, 0xf2, 0x70, 0x79, 0x7e, 0xde, 0x21, 0xa4, 0xf2, 0x1b, 0xdf, 0x4f, 0xf9, 0x2d, 0x2a, 0x85, 0x80, 0xac, 0xc4, 0x46, 0xda, 0x28, 0xf0, 0x0c, 0xc1, 0x11, 0x98, 0x49, 0xf3, 0xf0, 0x19, 0x26, 0x18, 0x82, 0xbf, 0x04, + 0xfc, 0x9f, 0xef, 0x7f, 0x7b, 0xd6, 0xf6, 0xce, 0x3b, 0xcc, 0x6f, 0x89, 0x9a, 0x67, 0x6e, 0x9a, 0x3d, 0xc4, 0x3f, 0x30, 0x7b, 0x23, 0x73, 0x35, 0xac, 0x3a, 0x30, 0x45, 0x78, 0xa2, 0xe3, 0x59, 0xd0, 0xf1, 0x49, 0x29, 0xa1, 0xd7, 0x32, 0xa2, 0x00, 0x12, 0x19, 0x8b, 0x68, 0x23, 0x79, 0x06, 0x8b, 0xb9, 0x8d, 0x44, 0xad, 0x4f, 0x0e, 0xf2, 0x20, 0xa2, 0xa7, 0xd8, 0x21, 0x45, 0x9f, 0x2b, 0xff, 0x2c, 0x06, 0xc2, 0x2a, 0x40, 0x8d, 0x70, 0x50, 0x79, 0x1a, 0x59, 0x5f, 0xe4, 0x89, 0xf0, 0x7f, 0xee, 0xc8, 0xac, 0x8d, 0xf9, 0x2d, 0xbe, 0xe2, 0x21, 0xf2, 0xec, 0x87, 0x1e, 0x82, 0xa7, 0xbf, 0xfd, 0x10, 0x67, 0xfc, 0xf5, 0xaf, 0x3f, 0xfa, 0x03, 0x81, 0x80, 0x33, 0x5e, 0x8b, 0x2d, 0xa9, 0xf7, 0xc9, 0x9f, 0x32, 0x3f, 0x7c, 0x13, 0x85, 0xc3, 0x26, 0x59, 0x64, 0xa1, 0x43, 0x27, 0x65, + 0xb5, 0x30, 0x3a, 0x67, 0x69, 0x00, 0xa6, 0x81, 0x6f, 0x7a, 0xe7, 0x1d, 0x62, 0xa8, 0x61, 0x34, 0x99, 0x7a, 0x02, 0xbf, 0x05, 0x5f, 0xfd, 0x68, 0x48, 0x5e, 0x1b, 0x2e, 0xca, 0xda, 0xd4, 0xf0, 0x18, 0x24, 0xbc, 0x93, 0x59, 0x09, 0x05, 0x60, 0xc3, 0x71, 0x60, 0xb7, 0xcc, 0x64, 0xb5, 0x40, 0x99, 0x06, 0xa3, 0x92, 0x36, 0x18, 0x25, 0x2c, 0x2f, 0xe8, 0x3c, 0x59, 0x2c, 0x4f, 0x90, 0x96, 0xc3, 0xea, 0xf8, 0x2d, 0x85, 0xd5, 0x9b, 0xdf, 0x29, 0x5a, 0x88, 0xbf, 0xe7, 0xda, 0x4f, 0x18, 0xb9, 0xce, 0xbf, 0xcf, 0x3c, 0x03, 0x70, 0x3a, 0xd0, 0xb2, 0xc1, 0xd3, 0xbe, 0xa5, 0x6b, 0x9e, 0x17, 0x31, 0xcc, 0x6c, 0xc0, 0x4b, 0x3e, 0xc1, 0x2e, 0x19, 0xa5, 0x17, 0x25, 0x2b, 0x92, 0x69, 0x08, 0x0c, 0xaa, 0xb0, 0xa1, 0x57, 0xca, 0x47, 0x98, 0x05, 0x0b, 0x80, 0x99, 0x51, 0xd7, 0x80, 0x7a, + 0x6b, 0xf4, 0x05, 0x47, 0xb1, 0x5d, 0x11, 0xc0, 0xd4, 0x9a, 0xa7, 0xba, 0x83, 0x82, 0x59, 0x67, 0x4d, 0x30, 0xcf, 0xb4, 0xec, 0x7f, 0x72, 0xcf, 0xd4, 0x76, 0x03, 0xe3, 0x72, 0x59, 0x9b, 0xab, 0xa4, 0xc9, 0xae, 0xe0, 0xdb, 0xf8, 0x7f, 0xae, 0x7a, 0xed, 0x86, 0x5e, 0xdf, 0xd1, 0x3c, 0x97, 0x35, 0x32, 0x72, 0x74, 0x0d, 0xfb, 0x74, 0x86, 0x9f, 0xda, 0x00, 0xb6, 0x00, 0xe1, 0x27, 0x23, 0x88, 0x5a, 0x5a, 0xd1, 0x9f, 0x98, 0xad, 0x93, 0x3c, 0x26, 0xe8, 0xe7, 0x30, 0x5d, 0x79, 0x56, 0x6b, 0x3c, 0x1a, 0x2e, 0xa2, 0xe8, 0x01, 0x6d, 0x56, 0x8f, 0xdb, 0x70, 0x33, 0x06, 0x44, 0x89, 0x4e, 0x17, 0x55, 0x63, 0x51, 0x5c, 0xc1, 0x52, 0x4e, 0x4b, 0xe0, 0x1b, 0x7b, 0x22, 0x95, 0x36, 0xfc, 0x8f, 0xce, 0x8a, 0xe2, 0x17, 0x66, 0x7f, 0x03, 0xb6, 0x54, 0x7d, 0x9c, 0x89, 0xbf, 0x6c, 0xf7, + 0x3b, 0xac, 0x62, 0x2a, 0xaa, 0xb3, 0x7b, 0x8a, 0x5d, 0xab, 0xdf, 0x7e, 0x9b, 0xad, 0xb4, 0xb9, 0x18, 0x9b, 0xd7, 0x31, 0xfb, 0x80, 0xa7, 0x48, 0x6f, 0x6b, 0x9a, 0x5d, 0x2b, 0xe8, 0x2d, 0x7a, 0xa6, 0xca, 0x98, 0xa7, 0xe7, 0x67, 0xaf, 0xa0, 0x76, 0x10, 0xc0, 0xc5, 0xfe, 0x02, 0xbe, 0xf1, 0x54, 0x03, 0x11, 0x40, 0x88, 0xca, 0x46, 0x6b, 0xd3, 0xa2, 0x94, 0x47, 0xbc, 0xd5, 0xca, 0x01, 0x4f, 0x84, 0x1d, 0x84, 0xcb, 0xd8, 0x5f, 0x9c, 0xfb, 0xee, 0xdb, 0xcf, 0x81, 0xd5, 0xf7, 0x91, 0x8b, 0xce, 0xcb, 0x09, 0xf2, 0x84, 0x83, 0xfe, 0x56, 0x54, 0x22, 0x45, 0x2d, 0x06, 0xbd, 0x86, 0x63, 0x05, 0xc0, 0xed, 0xc0, 0x7c, 0x43, 0x59, 0x91, 0xd5, 0x95, 0x1e, 0x4a, 0xac, 0xab, 0x6d, 0xc7, 0x30, 0x60, 0xd8, 0x61, 0x17, 0x79, 0x2b, 0xc7, 0x9d, 0xfb, 0xee, 0x4b, 0x9e, 0xcf, 0xbe, 0x8d, + 0xaf, 0xf6, 0xa7, 0x1e, 0x7e, 0xee, 0x39, 0xa6, 0x78, 0xdd, 0xbd, 0x7e, 0xe3, 0x72, 0xe6, 0xdb, 0xb3, 0x9f, 0x79, 0xed, 0xce, 0x90, 0xfe, 0x85, 0x54, 0x0d, 0x3a, 0x7f, 0x5e, 0x81, 0xf5, 0x0d, 0x26, 0x82, 0x48, 0xb5, 0x71, 0x11, 0x3d, 0x4b, 0x8e, 0xda, 0xc9, 0x73, 0x10, 0x3e, 0x09, 0x30, 0x14, 0xa1, 0x72, 0xa9, 0xc4, 0x63, 0x24, 0x45, 0x3e, 0xe1, 0xf9, 0x2c, 0x02, 0x8f, 0x62, 0x86, 0xe7, 0xe6, 0x2c, 0xd9, 0x22, 0x54, 0x18, 0x70, 0x24, 0x15, 0xb9, 0x16, 0xb4, 0x02, 0xf7, 0x61, 0xa1, 0x14, 0x17, 0x47, 0xb1, 0xd3, 0x41, 0xd0, 0x0a, 0x16, 0x03, 0x98, 0x15, 0x2e, 0x8a, 0xf9, 0x84, 0xf0, 0xc9, 0xaf, 0xb1, 0x4c, 0xaa, 0xd8, 0x5d, 0x5b, 0x50, 0x9b, 0x9f, 0x72, 0x0b, 0xaf, 0x7c, 0xd6, 0x57, 0x68, 0xc2, 0x3f, 0x35, 0xe5, 0xeb, 0x1d, 0x3e, 0xfc, 0x2f, 0xc6, 0x42, 0x1f, 0x61, + 0x42, 0x36, 0xe5, 0xaa, 0xf5, 0x9e, 0xbb, 0xa7, 0xba, 0x9a, 0x9d, 0x69, 0xa8, 0x3e, 0xc7, 0xf0, 0x0f, 0xe8, 0xdd, 0xe7, 0x1e, 0xf1, 0x34, 0xf9, 0x9b, 0xeb, 0xd9, 0x31, 0xb7, 0x3e, 0x47, 0x96, 0x38, 0x90, 0x5f, 0xf2, 0x70, 0x2c, 0x43, 0x96, 0x06, 0x4e, 0x03, 0xe4, 0x40, 0x8e, 0x80, 0x2b, 0xc0, 0xeb, 0x5c, 0x04, 0x1e, 0x30, 0x65, 0x88, 0x55, 0xa3, 0x42, 0x52, 0x81, 0xeb, 0x40, 0x96, 0xec, 0x74, 0x07, 0xac, 0xe7, 0xde, 0x66, 0xd8, 0x57, 0x76, 0xfb, 0x7c, 0x56, 0xb6, 0x9c, 0x65, 0xc9, 0x73, 0xf3, 0xec, 0x9c, 0xdb, 0x5b, 0xeb, 0x3a, 0x07, 0x2b, 0xc1, 0xe8, 0xfa, 0xe8, 0xe7, 0xfe, 0xfa, 0x7c, 0xf6, 0xff, 0x50, 0x3d, 0xf6, 0x19, 0xfa, 0xac, 0x10, 0xda, 0x37, 0x78, 0xba, 0x18, 0xd8, 0x5d, 0x67, 0x07, 0x61, 0xac, 0x25, 0xb5, 0x4f, 0xc1, 0x04, 0x54, 0x7f, 0x30, 0x0c, 0x59, + 0x0d, 0xe4, 0x76, 0x3e, 0xc8, 0x34, 0xa2, 0xeb, 0x81, 0xf1, 0xa7, 0x32, 0x8b, 0x62, 0x86, 0x2c, 0x8a, 0x42, 0x75, 0x51, 0x64, 0x37, 0x51, 0xd7, 0xc6, 0x0c, 0x55, 0x62, 0xf0, 0x25, 0x84, 0x42, 0x81, 0xfc, 0x40, 0x38, 0x2c, 0x8b, 0x40, 0xd5, 0x1c, 0x53, 0x56, 0x49, 0xb2, 0x15, 0x5f, 0x38, 0x9d, 0xc8, 0xc8, 0xb5, 0xa3, 0xd5, 0x4b, 0xcb, 0x1d, 0xa2, 0xcb, 0x64, 0x8a, 0x85, 0x47, 0x56, 0x9e, 0xfb, 0xb7, 0xdc, 0xa9, 0x1d, 0x58, 0x7e, 0x72, 0x32, 0x99, 0x67, 0x5b, 0x95, 0x6f, 0x36, 0x4d, 0x6d, 0xa9, 0xe1, 0xec, 0x17, 0xcc, 0x92, 0xd8, 0x2f, 0xa3, 0xe7, 0x7f, 0xcf, 0xff, 0x1a, 0x74, 0x0e, 0xf1, 0x3c, 0xb2, 0x6c, 0xa7, 0x1c, 0xf5, 0x12, 0x8b, 0xc5, 0xb2, 0x6c, 0xa7, 0x2c, 0xc5, 0xa2, 0x9a, 0x50, 0x59, 0x6a, 0x85, 0xff, 0x35, 0x35, 0x47, 0x3e, 0x97, 0xfa, 0xcb, 0x2b, 0x5b, + 0xb7, 0xbe, 0x82, 0xc5, 0xcf, 0x65, 0xac, 0xa9, 0xbd, 0x2f, 0x1e, 0xef, 0xe9, 0x39, 0x4e, 0x3e, 0x65, 0x8b, 0xea, 0x0f, 0x67, 0x76, 0xee, 0x3c, 0x83, 0x8d, 0x9f, 0x7f, 0x08, 0x9b, 0xce, 0xec, 0xda, 0x75, 0x26, 0xf5, 0xc1, 0x43, 0xd7, 0xbd, 0x79, 0x6a, 0x68, 0xe8, 0xd4, 0x9b, 0xd7, 0x1d, 0xfb, 0xde, 0xa9, 0xc1, 0xc1, 0x53, 0xdf, 0x23, 0xbe, 0xd1, 0xe8, 0xf9, 0x7b, 0x00, 0xbe, 0x1f, 0x83, 0x86, 0x70, 0xa1, 0x66, 0xb4, 0x45, 0xb2, 0xd4, 0x82, 0x5d, 0xe5, 0xd2, 0x83, 0xa4, 0x8f, 0x07, 0x01, 0x85, 0x2c, 0x38, 0xba, 0x45, 0x80, 0xfe, 0x42, 0x10, 0x01, 0xec, 0x7e, 0x30, 0xb1, 0x36, 0x0f, 0x52, 0xc3, 0x0b, 0x16, 0xd3, 0x0e, 0x22, 0x89, 0xa9, 0xd3, 0xb1, 0x93, 0xa1, 0x54, 0xe0, 0x39, 0x7e, 0x9f, 0xdc, 0x6c, 0xbe, 0x16, 0x20, 0x52, 0xa3, 0xb1, 0x70, 0x65, 0x09, 0x15, 0xa9, + 0xe1, 0x02, 0x9c, 0x90, 0x3d, 0xa8, 0x4a, 0x40, 0xb6, 0x3c, 0x3f, 0xc0, 0xbb, 0x6a, 0x8e, 0x01, 0x65, 0x44, 0xe2, 0xfe, 0xd9, 0x22, 0x19, 0xfb, 0xb8, 0xbe, 0x8d, 0x6d, 0xc6, 0x4c, 0x20, 0x79, 0x6c, 0x80, 0xcc, 0xaf, 0xa5, 0x61, 0x63, 0xd3, 0xed, 0xcb, 0xc9, 0x8c, 0x7b, 0x3b, 0xd9, 0xd1, 0x2d, 0x33, 0x14, 0x29, 0xdb, 0x5f, 0x1e, 0x5e, 0xe7, 0xea, 0x28, 0x5c, 0xf9, 0xda, 0xcc, 0x35, 0x14, 0x35, 0xfb, 0xdf, 0x58, 0x3e, 0xa8, 0xcf, 0xb3, 0xe7, 0xe7, 0xed, 0xfe, 0x65, 0x61, 0x11, 0xcc, 0xf7, 0xd8, 0x55, 0xaf, 0xf5, 0x8c, 0x46, 0xe2, 0x04, 0x03, 0xd7, 0x7f, 0xaf, 0x97, 0x7d, 0x71, 0x32, 0xf5, 0xab, 0xc7, 0x29, 0x9a, 0xaa, 0xca, 0xaf, 0x2b, 0xf2, 0x62, 0x61, 0x12, 0x17, 0x3d, 0x4b, 0x91, 0xd5, 0x50, 0xbb, 0xc5, 0xec, 0x30, 0x08, 0x84, 0x7e, 0xe3, 0xe7, 0x3f, 0xd0, 0x94, + 0x52, 0xfa, 0x55, 0xe1, 0x82, 0xc1, 0xd3, 0x65, 0x84, 0x53, 0xa3, 0x58, 0xc4, 0x2e, 0x0c, 0x6e, 0x3d, 0xe1, 0x54, 0xf8, 0x21, 0xc8, 0x3f, 0x80, 0x53, 0x0b, 0xe0, 0x76, 0x10, 0x56, 0xbc, 0x06, 0x89, 0x9a, 0x8d, 0x02, 0x26, 0x78, 0x20, 0x7a, 0x67, 0xc7, 0xa0, 0x5e, 0xab, 0x63, 0x09, 0x2e, 0x58, 0x59, 0x7a, 0x7a, 0x65, 0xa6, 0x2e, 0xcf, 0x6d, 0x4a, 0x51, 0x46, 0xbe, 0xf2, 0xa3, 0x34, 0x44, 0x70, 0x61, 0x27, 0xf2, 0xf8, 0xc6, 0xcb, 0xea, 0x44, 0xac, 0xba, 0x29, 0xe5, 0x07, 0x3b, 0x06, 0xcc, 0xb6, 0x87, 0xa5, 0x8e, 0x8d, 0xd2, 0x37, 0x8b, 0x44, 0x82, 0x70, 0xe9, 0x9e, 0xa4, 0xb8, 0x45, 0xa1, 0xdd, 0x8e, 0x91, 0xbd, 0xca, 0x5e, 0x55, 0x59, 0x51, 0x5e, 0x56, 0x5a, 0x12, 0x8f, 0x85, 0x83, 0x24, 0x48, 0x01, 0xe6, 0xb5, 0x0d, 0xdb, 0x0c, 0xd9, 0xe6, 0xb5, 0xc2, 0xae, 0x60, + 0xd0, 0xd6, 0x65, 0x5c, 0x4d, 0xba, 0xd2, 0x5c, 0x75, 0xaa, 0xcd, 0xc8, 0xfc, 0xcf, 0xf4, 0x23, 0x07, 0xda, 0xda, 0x0e, 0x3c, 0x32, 0x3d, 0xfd, 0xd8, 0x81, 0xf6, 0xf6, 0x03, 0x8f, 0xf1, 0x0f, 0xe2, 0x73, 0x9f, 0x5b, 0xf9, 0x97, 0x07, 0xef, 0xfc, 0xcd, 0xe3, 0x13, 0x13, 0x8f, 0xff, 0xe6, 0xce, 0x07, 0xff, 0xb2, 0x92, 0xdd, 0x28, 0x5b, 0x8f, 0x8f, 0x2c, 0xbe, 0xe3, 0xfb, 0xc7, 0xae, 0xfb, 0xc1, 0x9d, 0x4b, 0x96, 0xdc, 0xf9, 0x83, 0xeb, 0x8e, 0x7d, 0xff, 0x8e, 0xc5, 0xda, 0xa7, 0x7f, 0x3a, 0x73, 0x14, 0xcc, 0x24, 0x85, 0xb7, 0xaf, 0xdd, 0xfb, 0x53, 0xc5, 0x3e, 0xa6, 0xbe, 0x0a, 0x77, 0x8c, 0xfa, 0xfa, 0x6e, 0x54, 0x23, 0x55, 0x92, 0x68, 0x11, 0xb8, 0xca, 0xec, 0x24, 0x4c, 0x98, 0x19, 0x13, 0x30, 0x35, 0x75, 0x39, 0x6e, 0x8a, 0x1b, 0x32, 0x80, 0x6f, 0x9f, 0xef, 0x72, + 0xda, 0xf5, 0x6e, 0x83, 0x5b, 0xf6, 0xeb, 0x65, 0xf7, 0x98, 0x72, 0x25, 0x48, 0x08, 0x9b, 0xe2, 0xcf, 0x53, 0xe9, 0xcb, 0x4c, 0x8d, 0x1c, 0x5e, 0x5e, 0xf2, 0xf0, 0x63, 0x3f, 0xf9, 0xc9, 0xc9, 0x37, 0x6f, 0xec, 0xe8, 0xb9, 0xf9, 0x7b, 0x27, 0x7e, 0xf2, 0x36, 0xf3, 0x72, 0xc9, 0xe2, 0x1d, 0x1d, 0xd7, 0xdf, 0x37, 0x7b, 0x96, 0xf9, 0x0f, 0xe9, 0x8a, 0xc7, 0x36, 0x6f, 0x7e, 0xfc, 0xea, 0x45, 0xb3, 0xbf, 0xa3, 0x21, 0xa3, 0x1c, 0x38, 0xbc, 0xa8, 0x5e, 0xaa, 0x15, 0xb1, 0x12, 0x6c, 0x10, 0xd2, 0x00, 0x11, 0x94, 0x67, 0x40, 0xf1, 0xb8, 0x29, 0x30, 0x5e, 0x83, 0x57, 0x06, 0x46, 0xb3, 0x30, 0x30, 0x89, 0x79, 0xc1, 0x79, 0x1b, 0xbf, 0x50, 0x32, 0xb4, 0xa3, 0xe3, 0xc4, 0x27, 0x66, 0xff, 0x01, 0xe0, 0xb9, 0x92, 0xc0, 0x73, 0xa8, 0x2b, 0xf5, 0x07, 0xfe, 0x81, 0xd4, 0x2c, 0x81, + 0x67, 0x6d, 0xea, 0x47, 0x42, 0x35, 0xe5, 0xe3, 0x4a, 0x74, 0xe0, 0x85, 0x4a, 0x37, 0x43, 0x34, 0xbb, 0xec, 0x58, 0x7b, 0xc0, 0x9d, 0x62, 0x72, 0x17, 0x2d, 0xa5, 0xfb, 0x4e, 0x96, 0xb0, 0x9c, 0x83, 0xb6, 0xb8, 0xf0, 0x26, 0x91, 0xb0, 0x5b, 0xc9, 0xc2, 0xbf, 0x48, 0xf7, 0xd1, 0xd1, 0x17, 0x63, 0xa1, 0x50, 0x2c, 0xc4, 0x13, 0x99, 0xab, 0x08, 0x30, 0x95, 0x43, 0xe4, 0xc0, 0x9c, 0x30, 0x47, 0xdc, 0xd1, 0x08, 0x0a, 0x4e, 0xa8, 0x62, 0x4c, 0x15, 0x6b, 0x5c, 0xea, 0x74, 0xb4, 0xc5, 0xf3, 0xc1, 0xee, 0x63, 0x73, 0xe4, 0x5e, 0x7c, 0x68, 0x0f, 0x88, 0x3a, 0x45, 0x9c, 0x1d, 0x53, 0xc4, 0xdb, 0x47, 0xa7, 0x40, 0xed, 0x69, 0x42, 0x73, 0x64, 0xdf, 0xd4, 0xf3, 0x0f, 0x5c, 0x57, 0x95, 0xa1, 0xcd, 0xcf, 0x29, 0x6d, 0x82, 0xc4, 0xb6, 0x0d, 0x50, 0x2b, 0x47, 0x50, 0xa2, 0x73, 0x84, + 0x53, 0xa8, 0x33, 0x99, 0x36, 0x75, 0x80, 0x4f, 0x88, 0x71, 0x6b, 0x35, 0xea, 0xe7, 0xe3, 0x13, 0x98, 0x07, 0x18, 0x3b, 0x58, 0x36, 0xab, 0xd5, 0x18, 0x90, 0xd3, 0xc5, 0xf4, 0x2d, 0xbb, 0x66, 0x45, 0xc9, 0xc3, 0x0f, 0xff, 0x23, 0xbb, 0xe1, 0x1b, 0x8d, 0x9e, 0xd4, 0x83, 0xd6, 0x58, 0xec, 0xd6, 0xb7, 0xf1, 0x67, 0x6f, 0xfd, 0xc7, 0xeb, 0xdb, 0x2b, 0x46, 0xaf, 0x1b, 0x89, 0xf9, 0x99, 0x5f, 0xc6, 0xfa, 0x37, 0xb7, 0x1e, 0x39, 0x3a, 0xfb, 0x87, 0xd9, 0xfc, 0x72, 0xfc, 0x81, 0xdb, 0x31, 0x0b, 0xc6, 0x4a, 0xe3, 0xb6, 0x7b, 0xc7, 0x07, 0x4f, 0x6c, 0x1b, 0xb2, 0xe7, 0x97, 0x02, 0xbb, 0xb4, 0x9e, 0x7f, 0x9f, 0xfd, 0x15, 0xd0, 0xac, 0x1d, 0xb5, 0x49, 0xcd, 0xed, 0x00, 0x56, 0x11, 0x06, 0xad, 0x4c, 0xde, 0xb2, 0x04, 0x16, 0x03, 0x0b, 0x36, 0x20, 0x07, 0x5a, 0x72, 0xb7, 0x0a, + 0xef, 0x8e, 0x41, 0x35, 0x08, 0x04, 0xd2, 0xb6, 0xb5, 0xb9, 0x04, 0xd6, 0xa7, 0xcf, 0x13, 0x04, 0x71, 0xeb, 0x94, 0x2d, 0xd8, 0xf4, 0xc2, 0x14, 0xd4, 0x08, 0xaf, 0xac, 0xfe, 0xd2, 0x64, 0x11, 0xea, 0x68, 0x04, 0xd8, 0xe9, 0x62, 0x3f, 0xa5, 0x73, 0x5a, 0xf3, 0x2a, 0xba, 0x37, 0x2c, 0x6a, 0x5f, 0x2f, 0x05, 0x9a, 0x37, 0x5c, 0x7d, 0xdd, 0xd5, 0x1b, 0x9a, 0x3b, 0x8e, 0x3c, 0xbf, 0xbf, 0xfb, 0xf8, 0x81, 0xcd, 0xb1, 0x56, 0xad, 0xdb, 0xe2, 0xa8, 0x1f, 0xd9, 0x3b, 0xd2, 0xb9, 0xb5, 0x37, 0xd2, 0x44, 0x6f, 0x36, 0xb5, 0xec, 0xff, 0xd2, 0xce, 0x43, 0xdf, 0x1b, 0xe4, 0x9e, 0xb5, 0x5a, 0x02, 0xf1, 0x40, 0xa0, 0x71, 0x71, 0x79, 0xd3, 0x50, 0x32, 0x56, 0xda, 0xb0, 0xf2, 0xf0, 0xe8, 0xfa, 0xc7, 0x8e, 0xf4, 0x7a, 0xab, 0xbb, 0x4b, 0xa7, 0x0d, 0x96, 0xb2, 0xba, 0xb2, 0x60, 0xc7, + 0x78, 0x43, 0xfb, 0xd2, 0x64, 0x24, 0x9e, 0x5c, 0x7d, 0xfd, 0x86, 0xc5, 0x77, 0xee, 0x59, 0xb4, 0xb8, 0x4f, 0xb6, 0x3b, 0x5b, 0x53, 0xf7, 0xd3, 0xb9, 0xae, 0x41, 0x2f, 0xca, 0xb6, 0xbb, 0x6d, 0x0d, 0xc6, 0x9a, 0x46, 0x10, 0xae, 0x75, 0x3e, 0x86, 0xe5, 0xa3, 0x98, 0x61, 0x39, 0x10, 0xb7, 0x17, 0x5c, 0xe5, 0x38, 0x22, 0x77, 0x49, 0x87, 0x4a, 0x18, 0x06, 0x88, 0x28, 0xcc, 0x20, 0x41, 0xc4, 0x82, 0x48, 0xf0, 0xa2, 0x19, 0x03, 0x5f, 0x76, 0xc7, 0xa0, 0x1a, 0xb6, 0xd9, 0x03, 0x14, 0x05, 0x87, 0x97, 0xa0, 0x68, 0x86, 0x70, 0x6e, 0x72, 0x6e, 0x0f, 0x70, 0x00, 0xc1, 0x5d, 0x9f, 0x99, 0xaf, 0xa7, 0x8a, 0xdb, 0x19, 0xa2, 0xc9, 0x6c, 0xab, 0x56, 0x2c, 0xea, 0x68, 0x6b, 0xa9, 0x4f, 0x56, 0x55, 0x94, 0xc4, 0xc3, 0xc1, 0x60, 0xb1, 0x76, 0x7e, 0x24, 0x5f, 0x24, 0xf2, 0x83, 0x3f, + 0x16, 0xf2, 0xeb, 0xa6, 0xee, 0x5d, 0x67, 0x9d, 0x37, 0x04, 0x14, 0x3b, 0xf1, 0xe3, 0x25, 0x1f, 0x93, 0x22, 0x2b, 0x3f, 0x75, 0x70, 0xa9, 0xb1, 0x6d, 0x9e, 0x70, 0xd0, 0x61, 0x7e, 0x6a, 0xd9, 0xc2, 0x64, 0xc2, 0xa8, 0x80, 0x24, 0xa7, 0xd2, 0x38, 0x38, 0x78, 0x22, 0xc4, 0x52, 0x55, 0x7d, 0x71, 0x1a, 0xd8, 0x25, 0x6e, 0x1f, 0xb5, 0x62, 0xa8, 0x0b, 0x5e, 0x17, 0xe0, 0x4e, 0xa4, 0x1a, 0xde, 0x4a, 0x25, 0xb9, 0x69, 0xee, 0x57, 0x1f, 0x39, 0xb9, 0x5f, 0x3d, 0x2b, 0xd3, 0x99, 0xec, 0x6b, 0xdc, 0x0d, 0x63, 0x98, 0x49, 0x7c, 0x84, 0x03, 0x36, 0xc6, 0xd4, 0xb3, 0xd7, 0x88, 0x0c, 0xcf, 0xab, 0xdb, 0x17, 0xaa, 0x6b, 0x6f, 0x46, 0x66, 0xc5, 0x9f, 0xd4, 0xea, 0xbc, 0x19, 0xef, 0xbe, 0x2e, 0x40, 0xfe, 0x1c, 0xcc, 0x77, 0x71, 0x67, 0xea, 0x1b, 0x6f, 0xa5, 0x5e, 0xc5, 0x5d, 0xf2, + 0x53, 0x52, 0xdb, 0x8f, 0xcc, 0xc6, 0x98, 0x7f, 0x3d, 0x82, 0xef, 0x7b, 0xf6, 0x59, 0xfa, 0xac, 0xcf, 0x9d, 0xff, 0x1d, 0x1f, 0x84, 0x67, 0xb9, 0x51, 0x48, 0x0a, 0xb0, 0x8a, 0xf7, 0x92, 0xb1, 0x48, 0x40, 0x9b, 0xd9, 0x4c, 0x06, 0x51, 0x40, 0x6e, 0xec, 0xe6, 0x55, 0xfd, 0x05, 0xd2, 0x55, 0x59, 0x1d, 0xf4, 0x6b, 0x01, 0xc3, 0xbc, 0xb7, 0xea, 0xe6, 0x75, 0x35, 0x6f, 0x2d, 0xbd, 0xef, 0x9d, 0x1b, 0x6f, 0x7c, 0xe7, 0xbe, 0xa5, 0x6f, 0x57, 0xaf, 0xbb, 0xf9, 0xc9, 0xa6, 0x6d, 0x77, 0xad, 0x21, 0x9b, 0x1b, 0x47, 0xdf, 0x79, 0x60, 0xcd, 0x9a, 0x07, 0xde, 0x39, 0x0a, 0xdf, 0x1f, 0x58, 0x73, 0xf7, 0xb6, 0x26, 0xf2, 0xcc, 0xd4, 0x63, 0xf4, 0x99, 0x35, 0x68, 0xf3, 0xf3, 0x4e, 0x32, 0x35, 0x65, 0xa7, 0xc0, 0x0f, 0xee, 0x33, 0x35, 0x4e, 0x79, 0x5e, 0x56, 0xaa, 0xd4, 0xb2, 0xa6, + 0xa0, 0x50, 0x29, 0x1a, 0x52, 0x1d, 0x81, 0x0b, 0xda, 0xa1, 0x4c, 0xb3, 0x51, 0xc9, 0x4c, 0x5f, 0xeb, 0x56, 0x13, 0x76, 0x38, 0x42, 0xc1, 0x72, 0x0d, 0xf1, 0xbe, 0x08, 0xb8, 0x62, 0x96, 0x4f, 0x5a, 0x98, 0x71, 0x57, 0x2b, 0x58, 0x65, 0x0e, 0xb2, 0x1b, 0xc8, 0x07, 0x3b, 0x8f, 0x3c, 0xb7, 0xd7, 0x16, 0xf6, 0xe7, 0xa5, 0xdd, 0xd4, 0x02, 0xea, 0xbe, 0x76, 0x26, 0xaf, 0xfd, 0xee, 0xed, 0x83, 0x64, 0x66, 0xab, 0xc8, 0x5c, 0xf9, 0x07, 0xce, 0xdd, 0xbf, 0xef, 0x8d, 0x4f, 0x4d, 0x9a, 0xf5, 0x05, 0xa1, 0x12, 0x97, 0xec, 0xb7, 0xf6, 0x13, 0x77, 0xb6, 0x76, 0x7d, 0x44, 0x18, 0xbf, 0xff, 0x87, 0x57, 0xab, 0xd3, 0x95, 0xd1, 0x20, 0xd3, 0x55, 0xdd, 0xab, 0xc9, 0x43, 0x01, 0xa9, 0x40, 0x4f, 0xfd, 0x59, 0x70, 0x20, 0x32, 0xfe, 0x3a, 0x79, 0xcf, 0x17, 0x02, 0xff, 0x9f, 0xb8, 0x55, + 0x5a, 0x1c, 0xc4, 0xaa, 0x03, 0xd3, 0x86, 0xa9, 0x07, 0xc3, 0x8f, 0xcc, 0xbe, 0xf4, 0xcd, 0xd9, 0x97, 0x9e, 0xf0, 0x05, 0xb5, 0xf8, 0x84, 0xc9, 0xab, 0xd7, 0xe5, 0x9b, 0xf0, 0xf5, 0x62, 0xd8, 0x4b, 0x3c, 0x08, 0xfe, 0x01, 0x9f, 0xf3, 0x5c, 0x55, 0x68, 0x55, 0x65, 0xe5, 0xaa, 0x10, 0xfb, 0x43, 0x7b, 0x01, 0x7d, 0x9e, 0x17, 0x21, 0x0d, 0xf1, 0xe7, 0x22, 0xc4, 0x4f, 0x2d, 0xca, 0xe3, 0x59, 0xe2, 0x39, 0xf3, 0x1c, 0x2c, 0xef, 0x1c, 0xa7, 0x25, 0x82, 0xc2, 0x2d, 0x01, 0x27, 0xf5, 0xa2, 0xc8, 0x53, 0x23, 0x51, 0x58, 0xe9, 0x73, 0x1f, 0xde, 0x8c, 0xc9, 0x4f, 0xa7, 0xe2, 0x4b, 0x69, 0xb8, 0x94, 0xf4, 0x5a, 0xea, 0x0b, 0x06, 0xad, 0x5e, 0x97, 0xfa, 0xec, 0x37, 0x53, 0xd2, 0xb3, 0xbe, 0x88, 0x80, 0xbd, 0x1a, 0xa3, 0x28, 0x9a, 0x75, 0xb8, 0x4b, 0x88, 0x7a, 0x5b, 0x7d, 0x21, + 0x0d, 0x5e, 0x94, 0x67, 0x32, 0x98, 0xb0, 0x8f, 0x8f, 0x51, 0x08, 0xb9, 0x78, 0x51, 0x79, 0x45, 0xe1, 0x47, 0xff, 0xc2, 0x3f, 0xe0, 0x75, 0x9d, 0x6b, 0xf7, 0x75, 0x86, 0x42, 0x9d, 0x3e, 0xf6, 0x5b, 0xf6, 0x02, 0x80, 0xbb, 0xbd, 0xbe, 0xa7, 0xbb, 0x81, 0x7c, 0x07, 0x98, 0x81, 0x78, 0xbc, 0x1e, 0x60, 0xf6, 0xa2, 0x22, 0xc9, 0xef, 0xb5, 0x19, 0x79, 0x8e, 0x00, 0xcd, 0xe2, 0x2c, 0x90, 0x63, 0xc5, 0xb1, 0x22, 0xb2, 0x88, 0x02, 0xd4, 0xc3, 0x02, 0x60, 0x89, 0x19, 0xac, 0x95, 0x01, 0x56, 0xbd, 0x6c, 0x5e, 0xff, 0x7f, 0x5d, 0x3e, 0x4d, 0xea, 0x76, 0x8d, 0x3e, 0xf5, 0x49, 0x9d, 0xc7, 0xfe, 0x6d, 0xfc, 0xeb, 0x54, 0xc9, 0xff, 0x75, 0x39, 0x35, 0x78, 0x87, 0xc6, 0x84, 0x57, 0x68, 0x9d, 0xd6, 0xef, 0xa5, 0xa2, 0x6e, 0x13, 0x33, 0xec, 0xce, 0x9b, 0xfd, 0x91, 0xc9, 0xcd, 0xfc, + 0x07, 0xf3, 0xb2, 0xc9, 0x34, 0xfb, 0x7d, 0x8f, 0x13, 0x9f, 0xb7, 0x58, 0x66, 0x97, 0x22, 0x7c, 0x3e, 0x05, 0x6b, 0xf0, 0xbf, 0x01, 0x0e, 0x3b, 0xf1, 0xf3, 0x0c, 0x2c, 0x79, 0xfb, 0x1a, 0x1e, 0x60, 0x70, 0x86, 0x58, 0x65, 0xc5, 0x56, 0x56, 0xf6, 0xf3, 0xac, 0x17, 0xe0, 0x8a, 0xf9, 0xe9, 0x5b, 0xb3, 0x1f, 0xbc, 0xe2, 0x2b, 0xd6, 0xe3, 0x06, 0x5b, 0xa1, 0x41, 0xef, 0xb7, 0xe1, 0x7a, 0x5d, 0xd0, 0xfb, 0x2a, 0xd3, 0x30, 0xfb, 0x1d, 0xa6, 0x81, 0x9d, 0xf2, 0x3a, 0x67, 0x7f, 0x11, 0xee, 0x2c, 0x2a, 0xea, 0x0c, 0x33, 0xee, 0xbc, 0x42, 0x99, 0x37, 0xc8, 0xc1, 0x9d, 0xab, 0xe0, 0x79, 0x26, 0x14, 0x96, 0x8a, 0x45, 0x98, 0x29, 0xd9, 0x7b, 0x61, 0x54, 0xe1, 0x01, 0x2d, 0x46, 0x15, 0x09, 0xe2, 0xb0, 0xda, 0x64, 0x3f, 0x48, 0x24, 0xb1, 0x23, 0xc2, 0xc2, 0x98, 0x08, 0x92, 0xab, + 0x9c, 0xe7, 0x7e, 0xc1, 0x06, 0x3e, 0xfa, 0x8e, 0x5e, 0xcb, 0x7a, 0xcf, 0xfd, 0x85, 0x2b, 0xe0, 0x17, 0x87, 0x02, 0x1f, 0x3d, 0xfa, 0xf4, 0x0a, 0x6b, 0x81, 0x89, 0x5b, 0xf7, 0xb4, 0xac, 0xd3, 0x89, 0xae, 0xdc, 0xce, 0xe9, 0x01, 0xc7, 0x8d, 0x68, 0xef, 0x0b, 0x05, 0x66, 0x06, 0xd3, 0xa5, 0x47, 0x0c, 0xf2, 0x08, 0x12, 0x05, 0x5e, 0x10, 0xf9, 0x19, 0xf2, 0xd2, 0x3a, 0xcc, 0xa3, 0x49, 0x90, 0x32, 0x74, 0x45, 0xed, 0x26, 0x92, 0x8b, 0x1d, 0xe3, 0xe4, 0x08, 0x96, 0x57, 0x2a, 0xb9, 0x48, 0x43, 0xea, 0x53, 0x2a, 0x36, 0xce, 0x4e, 0x0e, 0x56, 0xa2, 0x3e, 0x18, 0xcc, 0x8f, 0x86, 0xa3, 0x31, 0x8b, 0x46, 0xe7, 0x03, 0xd9, 0xcf, 0xa6, 0xe3, 0x7e, 0x58, 0x96, 0xeb, 0x0e, 0xe2, 0x24, 0x57, 0xc2, 0x0d, 0x2a, 0x4b, 0x40, 0xb8, 0xd7, 0xa7, 0xb7, 0x5a, 0x23, 0x51, 0xe6, 0xc9, 0x83, + 0x8f, 0x6d, 0xad, 0x70, 0x97, 0x36, 0x15, 0x97, 0x0f, 0x76, 0xb6, 0xf8, 0x67, 0xcf, 0xae, 0xfc, 0xf6, 0xc1, 0xd5, 0x37, 0x8e, 0x57, 0xae, 0xf6, 0x17, 0x68, 0x1d, 0xd1, 0xa1, 0xe5, 0xeb, 0x1a, 0x5b, 0xb6, 0x2d, 0x29, 0xef, 0xb8, 0xe7, 0xb7, 0x4f, 0x6f, 0x5a, 0x2f, 0xad, 0x6e, 0xaf, 0x74, 0xdb, 0xfd, 0x5a, 0x7c, 0x86, 0xf3, 0x57, 0x75, 0xc4, 0x22, 0xf5, 0x41, 0x4b, 0x5e, 0xa0, 0xaa, 0x90, 0xd9, 0x33, 0xb2, 0xb4, 0x71, 0xfa, 0xd4, 0xaa, 0xd9, 0xab, 0x6c, 0x9e, 0xd1, 0x50, 0x7d, 0xd4, 0x1e, 0xe8, 0x9a, 0xee, 0x6a, 0xd9, 0xb3, 0xa6, 0x81, 0x1b, 0xd8, 0xbf, 0xaf, 0x28, 0x5a, 0xe4, 0x34, 0xcb, 0x78, 0x49, 0x3d, 0xc1, 0xbe, 0x43, 0xf1, 0xb2, 0x14, 0x7d, 0x42, 0xb2, 0x34, 0x60, 0x81, 0xf7, 0x11, 0xe4, 0x74, 0x80, 0x05, 0x88, 0xd9, 0x81, 0xf4, 0x16, 0x09, 0x68, 0x3e, 0x81, + 0x23, 0x71, 0x61, 0x52, 0xae, 0x8d, 0x18, 0xa7, 0xe2, 0x18, 0x28, 0x3f, 0x05, 0x43, 0xd4, 0xde, 0x23, 0xa1, 0xe4, 0x8c, 0xbc, 0xaa, 0x4f, 0x77, 0xd1, 0xb2, 0x1a, 0x56, 0x0e, 0x29, 0x2f, 0xd8, 0x15, 0x65, 0x7a, 0x82, 0x33, 0x3e, 0xbc, 0xb8, 0x0c, 0x74, 0x66, 0x7e, 0x19, 0xe0, 0x4e, 0x8e, 0x28, 0x67, 0xe1, 0x2e, 0x2d, 0xbb, 0x16, 0x42, 0x5d, 0x65, 0x1a, 0xbd, 0xd9, 0x71, 0xb8, 0x39, 0xf8, 0xc4, 0xa5, 0x54, 0xae, 0x0d, 0x77, 0x78, 0xf5, 0x0b, 0x61, 0x33, 0xa8, 0x60, 0x3c, 0x27, 0x6a, 0x37, 0x07, 0xc5, 0xb3, 0xff, 0x4e, 0x83, 0x78, 0x5b, 0xa3, 0x9a, 0xe2, 0xf9, 0x31, 0xdc, 0xae, 0x92, 0x20, 0x3b, 0xc2, 0xc7, 0x22, 0xe9, 0xfc, 0xfb, 0x02, 0xe2, 0x48, 0xdd, 0xf6, 0x7c, 0x34, 0x8e, 0xc5, 0xc1, 0xd3, 0x1e, 0xc0, 0xb1, 0x61, 0x64, 0x90, 0xd1, 0x31, 0x95, 0x51, 0x06, 0xf3, + 0xc4, 0x6f, 0x4c, 0xff, 0xe4, 0x78, 0x46, 0xdd, 0xe1, 0xaa, 0x43, 0x3a, 0x30, 0xa5, 0x75, 0x2c, 0xe1, 0x3f, 0xb2, 0xc9, 0x3c, 0xa3, 0x31, 0x31, 0xac, 0xd9, 0xc8, 0x20, 0x90, 0x6e, 0x93, 0x5a, 0x10, 0x13, 0xfa, 0x31, 0x03, 0xd6, 0xeb, 0x77, 0x83, 0x16, 0xc3, 0x58, 0x18, 0x03, 0x9b, 0x45, 0xd8, 0x29, 0x10, 0x1b, 0xdd, 0xf3, 0xb7, 0x75, 0xde, 0x03, 0x9d, 0xa5, 0xf6, 0xac, 0x7e, 0x1c, 0xf8, 0xf0, 0x0b, 0xf6, 0x03, 0xc6, 0x57, 0xba, 0x11, 0xca, 0xa6, 0x47, 0x00, 0xcf, 0x30, 0xe4, 0x76, 0x9b, 0x4c, 0xee, 0x71, 0xf7, 0xd8, 0xca, 0xe5, 0xc3, 0x8b, 0xfb, 0x7a, 0xc0, 0x34, 0x6a, 0xad, 0x4b, 0x94, 0x97, 0x86, 0x8a, 0x0b, 0xfd, 0xa6, 0x7c, 0x53, 0x7e, 0x3c, 0x62, 0xb1, 0x10, 0x69, 0x92, 0x26, 0x58, 0x5d, 0x58, 0xf1, 0x01, 0xc0, 0x3e, 0x62, 0x65, 0x0a, 0x3a, 0x0b, 0x29, 0xb9, + 0x23, 0xd1, 0x8b, 0x10, 0xdd, 0xe9, 0x52, 0x48, 0x6f, 0x17, 0x82, 0xcc, 0xd3, 0x07, 0x1f, 0xdb, 0x52, 0xb1, 0x7e, 0x49, 0xfd, 0xfa, 0x9e, 0x38, 0xf3, 0x68, 0x41, 0xd3, 0xca, 0x64, 0xf3, 0x58, 0x67, 0x1c, 0x8c, 0x88, 0xf2, 0xc1, 0xe9, 0xb6, 0xee, 0xb5, 0x2d, 0x11, 0xab, 0xd9, 0x26, 0xd4, 0x06, 0x47, 0x37, 0xef, 0xa8, 0xdd, 0xff, 0x8d, 0x46, 0xdf, 0xea, 0xed, 0x07, 0x5b, 0x7a, 0xf7, 0x2f, 0x2f, 0x97, 0xe6, 0xf0, 0xc0, 0x4b, 0xfb, 0x0e, 0xde, 0x1d, 0x4c, 0x3e, 0x73, 0x70, 0xf9, 0x9d, 0xdb, 0x5b, 0xf2, 0xbc, 0xf8, 0xff, 0xe3, 0x03, 0x75, 0xdd, 0xb1, 0xe5, 0x83, 0xc5, 0xbd, 0x3b, 0xfa, 0xcf, 0xb4, 0x6e, 0x5b, 0x52, 0x16, 0x1b, 0xd8, 0xb1, 0xe8, 0xd0, 0xaa, 0xeb, 0x56, 0x94, 0xf8, 0x8a, 0x7d, 0x3a, 0xcd, 0xa2, 0x92, 0xd6, 0x48, 0xde, 0xae, 0xbd, 0x2d, 0x2b, 0x93, 0xee, + 0x50, 0xef, 0x8e, 0xde, 0x96, 0xfd, 0x13, 0xad, 0x5c, 0xc5, 0xfe, 0xab, 0x8a, 0xe3, 0xc5, 0xc0, 0x0a, 0x4b, 0x47, 0x26, 0x57, 0xaf, 0xdc, 0xd4, 0x71, 0xf8, 0xf4, 0x4c, 0x79, 0x21, 0xac, 0xbb, 0x5b, 0xce, 0x7f, 0xc0, 0xd9, 0xf8, 0xb3, 0xc8, 0x80, 0xc2, 0xe8, 0x53, 0xb2, 0xb7, 0x6f, 0x30, 0x1a, 0x88, 0xa8, 0x2d, 0x24, 0xfb, 0x57, 0x84, 0x03, 0xd4, 0x9f, 0xa0, 0x05, 0x54, 0x0e, 0xf0, 0xaa, 0x11, 0x16, 0x55, 0xec, 0x10, 0x5f, 0x8a, 0xc8, 0x27, 0xd9, 0x17, 0x2b, 0xbe, 0x58, 0x13, 0xea, 0xe0, 0x17, 0xce, 0xbd, 0x0b, 0x0e, 0x66, 0xda, 0x23, 0xa3, 0x8e, 0xbc, 0xa4, 0xb5, 0x46, 0x62, 0xe1, 0x50, 0x26, 0x0c, 0x43, 0x77, 0xaf, 0x8b, 0x05, 0xb1, 0x4e, 0xc5, 0xae, 0xe2, 0x1c, 0x88, 0x02, 0x28, 0x80, 0x04, 0x1e, 0xaa, 0x9a, 0xe9, 0xbc, 0xf9, 0x9e, 0xe3, 0x07, 0x57, 0x1c, 0xae, + 0x6b, 0x7f, 0x68, 0x7a, 0xf4, 0x8e, 0xa9, 0x64, 0xeb, 0xde, 0x2f, 0x6c, 0xae, 0x5e, 0xd6, 0xdd, 0xe4, 0xd1, 0x6a, 0x4a, 0x8e, 0x9e, 0xf9, 0x6b, 0x7b, 0xcd, 0xeb, 0x67, 0x9f, 0x7f, 0x7c, 0xa8, 0xe7, 0xae, 0x9e, 0x55, 0xdd, 0xc7, 0x5e, 0x3a, 0x70, 0xf8, 0xbb, 0x77, 0x0e, 0xdb, 0x42, 0xb5, 0x81, 0x95, 0x71, 0x98, 0x3d, 0x46, 0xc3, 0xe7, 0x3f, 0x60, 0xef, 0xe4, 0x34, 0x60, 0xf7, 0xed, 0x90, 0x74, 0x65, 0xa0, 0xbc, 0x3c, 0x60, 0xc9, 0x33, 0x8a, 0xe7, 0x19, 0xca, 0xb8, 0x32, 0x0c, 0x66, 0x99, 0xb4, 0x2b, 0xb3, 0x9b, 0xd8, 0x80, 0xaa, 0xa8, 0xf5, 0x5e, 0x46, 0xb3, 0x3d, 0x44, 0x22, 0x6b, 0x63, 0x91, 0x60, 0x2c, 0x98, 0x09, 0xd9, 0xe7, 0x06, 0xce, 0xa8, 0x65, 0x94, 0x66, 0x2e, 0x6a, 0x69, 0x8f, 0x68, 0xad, 0x16, 0x4b, 0xb0, 0x6e, 0xb0, 0xbe, 0x6f, 0x77, 0x7f, 0x24, 0x36, + 0xb0, 0xbd, 0xa3, 0x7d, 0x59, 0x32, 0x64, 0xcb, 0x37, 0x36, 0x04, 0x57, 0x6f, 0xda, 0x51, 0xbf, 0xfe, 0xd1, 0x43, 0x3d, 0xed, 0x87, 0xbf, 0x7a, 0x60, 0xd7, 0x63, 0x1d, 0x6c, 0x93, 0xde, 0xe8, 0xf6, 0xbb, 0x6b, 0x37, 0x9c, 0x5c, 0x35, 0x7a, 0xeb, 0xfa, 0xea, 0xc2, 0x50, 0x61, 0x5e, 0x5f, 0x69, 0x4b, 0x24, 0xaf, 0xe7, 0xc6, 0x33, 0x47, 0xf6, 0xbe, 0x76, 0xeb, 0x70, 0x8f, 0x44, 0xf5, 0xdc, 0x30, 0xf8, 0x30, 0x64, 0xae, 0xdd, 0xe8, 0x0b, 0xb2, 0x5c, 0xb5, 0x94, 0x62, 0x9e, 0x2d, 0xf0, 0x73, 0x2c, 0x16, 0xcc, 0xa0, 0x8f, 0xc8, 0xe6, 0x76, 0xee, 0x25, 0x86, 0x55, 0xbd, 0x97, 0x32, 0xc4, 0xf1, 0x22, 0xcf, 0x89, 0x33, 0x44, 0x07, 0x89, 0xc2, 0x6e, 0x24, 0x60, 0x5e, 0xc0, 0xbb, 0x55, 0x33, 0x70, 0xb7, 0x1c, 0xf3, 0x1c, 0x57, 0x63, 0x9e, 0x35, 0x0b, 0x34, 0xc7, 0x0c, 0x8f, + 0x99, 0xac, 0x5e, 0x24, 0x0c, 0x3a, 0xae, 0x86, 0x41, 0xf5, 0x80, 0x9e, 0x70, 0x65, 0xc8, 0x5e, 0x4a, 0x54, 0xd6, 0xc5, 0x37, 0xa6, 0x59, 0x15, 0x49, 0xe2, 0x7c, 0x98, 0x74, 0xb1, 0x77, 0xb6, 0x7c, 0x71, 0xc2, 0x3a, 0xb1, 0x34, 0xd0, 0x94, 0x76, 0x48, 0xcc, 0x4e, 0xbf, 0x3d, 0x16, 0x1b, 0x19, 0x2a, 0x59, 0x75, 0xf7, 0x8e, 0x56, 0x05, 0x6f, 0xb5, 0x0b, 0x20, 0xf8, 0xae, 0x81, 0x01, 0x4d, 0xcb, 0x3c, 0x0e, 0xc8, 0xb5, 0xda, 0x25, 0x37, 0xbc, 0x74, 0x40, 0xc6, 0x68, 0xdf, 0x05, 0x28, 0x97, 0x71, 0x7c, 0xfe, 0x03, 0xf1, 0x4b, 0x82, 0x0f, 0xad, 0x43, 0x8f, 0x49, 0x24, 0x20, 0xc5, 0x8d, 0x84, 0x88, 0xdb, 0x6e, 0xc5, 0x1a, 0xbe, 0x1d, 0x8b, 0x1a, 0x6e, 0x40, 0x5e, 0x25, 0x75, 0x88, 0x67, 0x34, 0x0c, 0x0f, 0x6a, 0x49, 0x43, 0x72, 0x6f, 0x76, 0x23, 0x3d, 0x08, 0x37, 0x3d, + 0x37, 0x89, 0x0c, 0x86, 0xcd, 0x83, 0x24, 0x0c, 0x84, 0xc6, 0x74, 0x60, 0x71, 0xed, 0xa6, 0xf9, 0x37, 0xa3, 0x5a, 0xe0, 0x2b, 0x62, 0x80, 0x78, 0xa5, 0xc6, 0xcb, 0xed, 0xa7, 0x6c, 0x19, 0x89, 0xa2, 0x76, 0x0c, 0x69, 0xb5, 0x7b, 0xb4, 0x80, 0x5e, 0xfb, 0xba, 0x89, 0xb5, 0x6b, 0x86, 0x17, 0x77, 0x2f, 0x02, 0x34, 0x97, 0x87, 0x6d, 0x41, 0x47, 0xb1, 0x91, 0xa8, 0xb7, 0xb4, 0x39, 0x60, 0x95, 0xd7, 0x19, 0x7c, 0x51, 0x77, 0x0a, 0x9d, 0x2e, 0x30, 0xac, 0xc4, 0xcb, 0xe1, 0xd5, 0x0a, 0x5c, 0xa7, 0xa4, 0x67, 0x25, 0x78, 0x9b, 0x74, 0xff, 0xe6, 0x15, 0xd7, 0xae, 0x2a, 0x3b, 0x3b, 0xb5, 0x65, 0xf0, 0xfa, 0x86, 0x33, 0xba, 0x48, 0x4d, 0x4b, 0x51, 0xf3, 0x9a, 0xe6, 0x82, 0x96, 0x64, 0x41, 0xd2, 0x5d, 0x7b, 0x59, 0x3c, 0x6d, 0x8f, 0xda, 0xbc, 0xfe, 0xc4, 0x86, 0xdb, 0xd7, 0x76, + 0x6d, 0x19, 0x6e, 0xf5, 0xb2, 0x07, 0x3b, 0x96, 0x36, 0x6c, 0x06, 0x3d, 0x76, 0x88, 0xb9, 0x69, 0xd7, 0xa1, 0xde, 0xd6, 0x59, 0x17, 0x3f, 0x69, 0x2f, 0x74, 0xe8, 0x43, 0x6d, 0xcb, 0x2a, 0xba, 0x4f, 0x34, 0x58, 0xe2, 0xf9, 0x7d, 0x17, 0xe7, 0x7f, 0x96, 0xa9, 0xda, 0x9a, 0xec, 0x39, 0x34, 0x9a, 0xf0, 0x05, 0xbc, 0x0a, 0x8d, 0xce, 0x02, 0x8d, 0xb6, 0xe0, 0xed, 0xb2, 0x05, 0x66, 0x5b, 0x8d, 0x79, 0xe3, 0x96, 0x32, 0x50, 0x7d, 0xbd, 0x58, 0xaf, 0xf1, 0x28, 0xbe, 0xbc, 0x93, 0x5c, 0x2d, 0xc3, 0x5a, 0x7e, 0x0b, 0xc6, 0x5a, 0xf5, 0x06, 0xaf, 0x8a, 0xc2, 0x56, 0xa0, 0xb6, 0x06, 0x23, 0x0d, 0x29, 0x16, 0xc3, 0xe8, 0x59, 0x06, 0x9c, 0x74, 0x64, 0xe4, 0x05, 0xe3, 0x24, 0xd2, 0x20, 0x3d, 0xab, 0xd1, 0x4f, 0x02, 0xee, 0xa9, 0xa4, 0xe3, 0xc7, 0xb4, 0x38, 0x2d, 0x0b, 0x0c, 0x3a, + 0x46, 0x16, 0x1a, 0xf2, 0x83, 0xff, 0x17, 0x83, 0xec, 0xe1, 0x54, 0x89, 0xdb, 0x71, 0x79, 0x83, 0x68, 0xb5, 0xd3, 0x83, 0x73, 0x24, 0x53, 0x7a, 0x20, 0xa9, 0xfb, 0x6f, 0x19, 0xc3, 0x80, 0x75, 0xba, 0xa9, 0x1c, 0x19, 0x07, 0xe2, 0xdb, 0x39, 0x31, 0xb6, 0x6a, 0xe5, 0x8a, 0x65, 0x8b, 0x07, 0xbb, 0x3a, 0x8b, 0x6d, 0x71, 0x59, 0xe4, 0x99, 0xd4, 0x8d, 0xf9, 0xcb, 0x67, 0x23, 0x57, 0xc2, 0x9a, 0xd9, 0xb7, 0x13, 0x44, 0xe0, 0x43, 0x1a, 0x90, 0x53, 0x23, 0x59, 0x39, 0x1c, 0x7a, 0x99, 0x82, 0x32, 0xcd, 0x54, 0x9d, 0x9b, 0x97, 0xb4, 0x7a, 0x84, 0x0a, 0x87, 0xcf, 0x8c, 0xab, 0x4c, 0xc5, 0x9e, 0x67, 0x53, 0x9f, 0xf6, 0x07, 0x6c, 0x7e, 0x13, 0x3e, 0xfa, 0xe5, 0xfc, 0xa0, 0x29, 0xf5, 0x86, 0xb9, 0xa0, 0xa0, 0x16, 0xb8, 0x36, 0xd1, 0x52, 0x98, 0xe6, 0xda, 0x4b, 0x09, 0xd8, 0x34, + 0x83, 0x79, 0x02, 0x3e, 0x7c, 0xee, 0xf3, 0x86, 0x3c, 0x3c, 0x6c, 0xb3, 0xa5, 0x0e, 0x86, 0xed, 0xc6, 0x62, 0x7b, 0xca, 0x68, 0x71, 0xe0, 0x4f, 0xb9, 0x8c, 0xa9, 0x42, 0x7e, 0x93, 0xbd, 0x28, 0x9b, 0x6b, 0x65, 0x79, 0xf1, 0xbe, 0x70, 0x00, 0x78, 0x71, 0x3b, 0x7a, 0x4d, 0x8e, 0x65, 0x9a, 0x97, 0x60, 0x9d, 0xa6, 0xd8, 0xc0, 0x60, 0x5d, 0x2b, 0x88, 0x4c, 0x9a, 0x6f, 0xa4, 0x5c, 0xe1, 0x94, 0x2b, 0xa3, 0x72, 0xc3, 0x16, 0x70, 0xc7, 0x05, 0xcc, 0x92, 0xe8, 0x10, 0x0f, 0x56, 0xf3, 0x6e, 0xa4, 0x45, 0x8c, 0x46, 0xcb, 0x4c, 0xea, 0x39, 0x46, 0x89, 0x0f, 0xed, 0x1e, 0xa4, 0xdf, 0x75, 0xa3, 0x48, 0xa7, 0x9b, 0x06, 0x6a, 0xf1, 0x3c, 0x02, 0xab, 0x48, 0xd9, 0xaa, 0x6d, 0x5c, 0xa0, 0x3b, 0x50, 0x52, 0xed, 0x8c, 0xe7, 0xef, 0x0b, 0x92, 0x64, 0x72, 0xe3, 0xba, 0x89, 0xf1, 0xb1, + 0x55, 0x2b, 0x28, 0x79, 0xcb, 0x2b, 0x6c, 0x54, 0x92, 0x84, 0x65, 0xe3, 0x58, 0xce, 0x2b, 0xa8, 0x6f, 0x63, 0x33, 0xb1, 0xf6, 0xcb, 0x95, 0x23, 0xae, 0x88, 0x4a, 0x7e, 0x3f, 0x71, 0xb0, 0xac, 0xaa, 0xfd, 0xec, 0x74, 0x09, 0x07, 0xaa, 0x6e, 0xe8, 0x3d, 0x7c, 0xc3, 0x15, 0xd6, 0xfc, 0x67, 0x34, 0x56, 0xa3, 0x26, 0x3f, 0xb9, 0xb2, 0xb5, 0x6f, 0xd7, 0x40, 0xb8, 0x36, 0x51, 0x50, 0x77, 0x99, 0x22, 0xa5, 0x24, 0x5a, 0x59, 0xbd, 0xf1, 0xae, 0x0d, 0xeb, 0x6e, 0x5a, 0x5d, 0x82, 0x8b, 0x52, 0x3f, 0x6d, 0xaa, 0xf4, 0x46, 0x7d, 0x76, 0x8d, 0xd6, 0x20, 0x0c, 0x95, 0x55, 0x9d, 0xbc, 0x8e, 0xd9, 0xe0, 0x70, 0x0c, 0xe7, 0x17, 0x74, 0x76, 0x2d, 0x2a, 0xa8, 0x18, 0x6e, 0x0e, 0x94, 0x0d, 0x6c, 0xac, 0x6d, 0xbe, 0xba, 0xde, 0x12, 0xbb, 0x94, 0x64, 0x11, 0xc6, 0x36, 0x74, 0x5d, 0xbb, + 0xb1, 0xb9, 0x61, 0xfc, 0xca, 0xb6, 0xdf, 0x9c, 0x9a, 0x7d, 0xba, 0xa5, 0xd9, 0x64, 0x35, 0x69, 0x75, 0xb2, 0xae, 0xa5, 0x74, 0x3d, 0x81, 0x77, 0xc9, 0xe4, 0xf2, 0x1f, 0xc4, 0xc8, 0x58, 0xe3, 0x03, 0xd7, 0x66, 0x31, 0x66, 0xb4, 0x3b, 0x2c, 0x60, 0x65, 0x6c, 0x1f, 0x81, 0xa5, 0x36, 0x8e, 0x75, 0x7a, 0x61, 0x40, 0xc9, 0x05, 0x53, 0x1b, 0x89, 0xf3, 0x37, 0xba, 0xd8, 0x7d, 0x83, 0x30, 0x30, 0xaa, 0x70, 0x46, 0x0f, 0x68, 0x5b, 0x2d, 0xa6, 0xe9, 0x61, 0x0c, 0xd6, 0x82, 0xba, 0x35, 0x21, 0xde, 0x68, 0xe2, 0x27, 0x45, 0xc2, 0x0d, 0xc6, 0x31, 0x64, 0x34, 0xee, 0x1e, 0xa4, 0xdf, 0x69, 0xa6, 0xcf, 0xf4, 0xa0, 0x06, 0xf4, 0x86, 0xe2, 0x09, 0x11, 0x53, 0x5a, 0x37, 0x0e, 0x34, 0x9f, 0xd1, 0xa9, 0xf9, 0x69, 0x8b, 0x16, 0x18, 0x0d, 0x24, 0x82, 0x32, 0x16, 0xb1, 0xd8, 0x2f, 0x31, + 0x94, 0x34, 0x30, 0x77, 0x14, 0x3d, 0x6b, 0x60, 0xf5, 0x86, 0x99, 0xcb, 0x1d, 0x0d, 0x14, 0x9c, 0x3a, 0x18, 0x08, 0x99, 0xfc, 0xeb, 0x8e, 0x5e, 0x79, 0x60, 0xf3, 0xe4, 0xfa, 0x75, 0x6b, 0xd7, 0x0c, 0xf6, 0x53, 0x0e, 0x2c, 0x09, 0x5a, 0x2d, 0x66, 0x25, 0xa9, 0x23, 0xc3, 0x37, 0x6a, 0xb0, 0xe9, 0xe3, 0xf2, 0xe4, 0x65, 0x98, 0x1e, 0x59, 0xb2, 0xca, 0xc1, 0xb7, 0xe4, 0xb2, 0x54, 0xd3, 0xbc, 0xcc, 0x9a, 0x48, 0x5c, 0x4c, 0xff, 0xb5, 0x7c, 0x69, 0x7c, 0xfe, 0x08, 0x6a, 0xda, 0x60, 0x29, 0x89, 0xba, 0xf2, 0x55, 0x16, 0x66, 0x7f, 0x99, 0xe1, 0xb6, 0x75, 0x0b, 0x32, 0x70, 0x72, 0x7e, 0xd5, 0xb8, 0xa0, 0x75, 0xb3, 0xf8, 0x86, 0x97, 0xa9, 0x75, 0x23, 0x94, 0xaf, 0xab, 0x52, 0xd9, 0x1a, 0x7c, 0x85, 0x65, 0x60, 0x23, 0xff, 0x08, 0xac, 0x65, 0x13, 0xf8, 0x0a, 0xd3, 0x92, 0x21, + 0x04, 0x56, 0x8d, 0xdd, 0x44, 0x82, 0x81, 0x8c, 0x62, 0xcd, 0xf8, 0x48, 0xda, 0xd8, 0x66, 0x62, 0xc7, 0x53, 0x4d, 0xb0, 0x5b, 0x36, 0xf0, 0xd5, 0x6d, 0x41, 0xa9, 0xe8, 0x82, 0xdb, 0x34, 0xb3, 0x58, 0x71, 0xbf, 0x89, 0xd5, 0x2f, 0x99, 0xcc, 0x66, 0x73, 0xd8, 0x1c, 0x8e, 0x85, 0x42, 0x91, 0x62, 0xba, 0x03, 0x4e, 0x76, 0x5d, 0x05, 0x99, 0x26, 0xc9, 0x7a, 0x1b, 0xd0, 0xaf, 0x52, 0x51, 0x20, 0xaa, 0xaa, 0xb0, 0xb2, 0xf9, 0x55, 0x7b, 0xba, 0x6f, 0xbc, 0x27, 0xd0, 0x3e, 0xde, 0xb4, 0xf2, 0x48, 0xf2, 0xec, 0x95, 0x71, 0x9d, 0xb5, 0xe3, 0xd1, 0x9d, 0xab, 0x6f, 0x9f, 0x4a, 0xb6, 0xec, 0xfb, 0xc2, 0xf4, 0xf8, 0xb1, 0x04, 0xcb, 0xb4, 0xd7, 0x7c, 0xeb, 0xe5, 0x8d, 0xb7, 0xac, 0x8e, 0x0f, 0x75, 0x73, 0x8f, 0xfc, 0xb5, 0x25, 0x7a, 0xc5, 0xe2, 0x89, 0xae, 0xeb, 0x5f, 0x3f, 0xba, + 0xf7, 0xf5, 0x53, 0x23, 0x2d, 0xf5, 0x4c, 0x19, 0x22, 0xb9, 0x10, 0x30, 0x37, 0xde, 0x4b, 0x73, 0x21, 0xea, 0xe5, 0x5c, 0x08, 0xd6, 0x4e, 0xe3, 0x12, 0xcb, 0xc1, 0x3f, 0x9a, 0xa1, 0x73, 0x2e, 0x47, 0x57, 0x49, 0x86, 0x30, 0xb8, 0xac, 0x8e, 0x9c, 0x39, 0x87, 0xb5, 0x58, 0x04, 0x83, 0x8b, 0x17, 0x99, 0x8d, 0x20, 0x2d, 0xe9, 0xec, 0xa8, 0x5a, 0x9c, 0x9e, 0x13, 0xb4, 0x89, 0x2f, 0xdc, 0x2e, 0x3b, 0x66, 0x43, 0x3d, 0x04, 0x53, 0x79, 0x99, 0xd5, 0x6a, 0x8b, 0x85, 0x02, 0xb6, 0x88, 0x9c, 0xcc, 0x96, 0x83, 0x05, 0x1c, 0x9c, 0x07, 0x0d, 0x75, 0xc1, 0xba, 0x44, 0x5d, 0xc2, 0x91, 0x60, 0xdd, 0x95, 0x33, 0x5d, 0x37, 0xde, 0xab, 0x60, 0x03, 0xaf, 0xbe, 0x87, 0xe0, 0x43, 0x7a, 0x2c, 0x1b, 0x1f, 0xbf, 0xfa, 0xd5, 0xd9, 0x7b, 0x98, 0x5f, 0xb5, 0x25, 0x5e, 0x78, 0x69, 0xe3, 0x0d, + 0xcb, 0xc3, 0x43, 0xdd, 0xec, 0x8a, 0x8f, 0x6e, 0x23, 0x38, 0x91, 0xae, 0xfd, 0xc6, 0xd1, 0xbd, 0xdf, 0xb8, 0x65, 0x71, 0x4b, 0xf2, 0x7f, 0x8e, 0xbc, 0xf5, 0x16, 0xcc, 0x7d, 0x09, 0xf5, 0x0d, 0x6f, 0x45, 0x36, 0xd4, 0x89, 0x56, 0x83, 0x47, 0xc0, 0x33, 0x24, 0x00, 0xd7, 0xd6, 0xda, 0x54, 0x5b, 0x59, 0x21, 0xb0, 0x7d, 0xb2, 0xfd, 0x63, 0x27, 0xce, 0x21, 0x20, 0x64, 0x32, 0x3b, 0x64, 0xed, 0x9d, 0xe7, 0xf2, 0x1e, 0x1a, 0x17, 0xc6, 0x28, 0x1e, 0x2d, 0xf4, 0xcb, 0x9b, 0xb1, 0x6a, 0x30, 0xbb, 0x3e, 0x93, 0x91, 0x06, 0x8e, 0xb6, 0xea, 0xd8, 0x65, 0x6c, 0x81, 0xa8, 0x18, 0x8d, 0x24, 0xdb, 0x71, 0x24, 0x5a, 0x81, 0xdb, 0x49, 0x88, 0xaf, 0xde, 0xe5, 0x14, 0x44, 0x26, 0xde, 0xb5, 0x67, 0xb8, 0x9c, 0xb5, 0x24, 0xfa, 0xd6, 0xb7, 0x90, 0x35, 0x54, 0xb6, 0x78, 0x1a, 0xfb, 0x9b, + 0x37, 0x76, 0x47, 0xa4, 0x23, 0x5f, 0x3b, 0x34, 0xf9, 0xd4, 0xd1, 0xfe, 0xf6, 0x03, 0x8f, 0x6d, 0x9d, 0x7a, 0x4a, 0x72, 0xcc, 0x4c, 0xbc, 0x12, 0x36, 0x68, 0xec, 0x1b, 0x56, 0x6c, 0xf5, 0xf8, 0x4c, 0xce, 0x8d, 0x2f, 0x8e, 0x3c, 0x1e, 0xd1, 0x6b, 0xad, 0x27, 0x13, 0xdb, 0x1e, 0xda, 0x5d, 0x32, 0xd4, 0x1c, 0xac, 0x5a, 0xbe, 0xb7, 0x63, 0xe9, 0xd5, 0x2b, 0x4a, 0xb9, 0xb6, 0xea, 0x6b, 0x3e, 0xfd, 0xd2, 0xce, 0x3b, 0xff, 0xf3, 0xf3, 0x2b, 0x49, 0xf8, 0x79, 0xe2, 0x8b, 0x47, 0x07, 0x96, 0x8d, 0x5c, 0xf7, 0xd5, 0xf6, 0x1b, 0xdb, 0xec, 0x21, 0x83, 0x14, 0x76, 0x8b, 0x15, 0xb1, 0x2e, 0x4b, 0x6b, 0xd5, 0x55, 0xed, 0xb6, 0xa0, 0x01, 0x70, 0x43, 0xfc, 0xc5, 0x77, 0x39, 0xf2, 0x56, 0x87, 0x72, 0xb0, 0xb2, 0x29, 0x26, 0x22, 0x48, 0xe0, 0x78, 0x4e, 0xe0, 0x67, 0x48, 0xe8, 0x90, + 0x13, 0x30, 0x58, 0xe3, 0x8a, 0x43, 0xbb, 0x9b, 0x84, 0xc9, 0x33, 0xb8, 0xb9, 0x9c, 0x86, 0x14, 0x5b, 0xc0, 0x82, 0x65, 0x25, 0xd1, 0x70, 0xa0, 0xd0, 0xed, 0x32, 0x1b, 0xe1, 0x51, 0x9a, 0x90, 0x46, 0xe7, 0x28, 0xc5, 0x54, 0x76, 0x91, 0x98, 0x6b, 0x96, 0xca, 0x94, 0xc3, 0x0d, 0x01, 0x35, 0x4e, 0xe1, 0x62, 0x4b, 0x02, 0xd2, 0x78, 0xcb, 0xe1, 0xdb, 0x6d, 0xcc, 0xf3, 0xa1, 0x75, 0xdb, 0xf7, 0xd6, 0x6f, 0x7b, 0xee, 0xfa, 0xfe, 0xce, 0xc3, 0xcf, 0xee, 0x99, 0x78, 0x70, 0x5f, 0x87, 0xdd, 0x3b, 0x3b, 0xcd, 0x56, 0x8c, 0xcc, 0x74, 0x77, 0xac, 0xef, 0xae, 0xcc, 0xcb, 0x37, 0x32, 0xbb, 0x9a, 0x36, 0xf5, 0xc5, 0x4e, 0x1d, 0x4d, 0x85, 0x48, 0xe0, 0xa0, 0xf7, 0x96, 0xef, 0x9d, 0xd8, 0xfe, 0xe2, 0xcd, 0x4b, 0x5a, 0x0f, 0x3c, 0xb1, 0xab, 0xbc, 0x70, 0xec, 0xa6, 0xb5, 0x65, 0x05, + 0xc1, 0x82, 0x3c, 0xb2, 0x16, 0x86, 0x53, 0x4f, 0x28, 0x73, 0xee, 0xc2, 0x86, 0xe7, 0x6b, 0x48, 0xd8, 0x57, 0x71, 0x90, 0xab, 0x91, 0xc8, 0x13, 0xff, 0x6e, 0x46, 0x07, 0x33, 0xe2, 0x45, 0x4c, 0xce, 0x58, 0xa5, 0x17, 0xb9, 0x20, 0x28, 0x3b, 0x04, 0x39, 0x91, 0xb9, 0xc1, 0xd3, 0x86, 0xcb, 0xec, 0xb8, 0x73, 0x6e, 0xc7, 0xfc, 0xbf, 0xed, 0x89, 0x52, 0x83, 0xda, 0x47, 0x8d, 0x05, 0x5e, 0xb4, 0x6f, 0x76, 0x30, 0x10, 0x54, 0x8d, 0x07, 0x21, 0xb2, 0x97, 0x56, 0x55, 0x51, 0x1a, 0x0f, 0x07, 0xfd, 0x5e, 0x5b, 0x9e, 0x51, 0x4f, 0x29, 0xa2, 0xcf, 0xa1, 0x48, 0xd6, 0x96, 0xc6, 0x7c, 0x64, 0x21, 0x2a, 0x45, 0xcc, 0x39, 0x02, 0x91, 0x26, 0x93, 0x48, 0x43, 0x81, 0x52, 0x72, 0xf3, 0x53, 0x47, 0x7a, 0x16, 0x22, 0x54, 0x43, 0xf2, 0x53, 0x2b, 0x6c, 0x91, 0x42, 0x5b, 0xe6, 0x44, 0x84, + 0x42, 0xb9, 0x73, 0x33, 0x24, 0x02, 0x98, 0x58, 0x17, 0xe1, 0x06, 0x6e, 0xf9, 0xf6, 0xb1, 0x0b, 0xa9, 0xd7, 0xd7, 0xde, 0x6f, 0xd2, 0x17, 0x84, 0x4a, 0xf3, 0x95, 0xd8, 0x1f, 0x46, 0x15, 0x40, 0xd0, 0x2f, 0xf3, 0xcf, 0xa1, 0x42, 0xd4, 0x20, 0xd5, 0x59, 0xcc, 0x64, 0x0b, 0x04, 0xf3, 0x1c, 0x33, 0x90, 0x95, 0xf8, 0xc3, 0x71, 0x68, 0x14, 0x04, 0x1d, 0xe8, 0x5f, 0x22, 0xb7, 0xa7, 0x78, 0xb2, 0x4f, 0x51, 0x88, 0x0a, 0xad, 0x8e, 0xba, 0xb0, 0x93, 0xc8, 0xe6, 0x84, 0x15, 0x16, 0x64, 0x2b, 0x4e, 0x28, 0x7a, 0x50, 0x34, 0xd1, 0x55, 0x2c, 0x5b, 0x6c, 0x6f, 0x6e, 0x77, 0xf9, 0x3f, 0x5b, 0x33, 0x71, 0xfd, 0xb2, 0xee, 0x26, 0x0e, 0x9b, 0x5c, 0x01, 0x5b, 0xf9, 0x40, 0xad, 0x0f, 0xfb, 0x53, 0xff, 0xc5, 0xae, 0x31, 0xf2, 0x2e, 0xe7, 0xc8, 0xf8, 0xea, 0x9b, 0xd7, 0x57, 0xdb, 0x37, + 0x19, 0x8d, 0x02, 0x76, 0xd6, 0xae, 0x6c, 0x5f, 0x75, 0xea, 0xdc, 0x63, 0x72, 0x7c, 0xbc, 0xe2, 0xfc, 0x07, 0xc2, 0x8f, 0xf8, 0x77, 0x51, 0x03, 0xea, 0xc6, 0xdf, 0x90, 0x23, 0x86, 0x76, 0x17, 0x26, 0xc2, 0x84, 0xe7, 0xba, 0x1b, 0x23, 0xac, 0x8e, 0xcf, 0x03, 0x6b, 0x94, 0x1b, 0x50, 0x7c, 0x1e, 0xf5, 0x9e, 0x3e, 0xe7, 0xde, 0x85, 0x97, 0x19, 0xb8, 0x3c, 0xaa, 0x78, 0x6c, 0x95, 0x40, 0x5e, 0xcd, 0x18, 0x61, 0x8d, 0xcd, 0x60, 0xdb, 0x12, 0x83, 0x16, 0x11, 0x4f, 0x99, 0x7c, 0xe5, 0x46, 0x45, 0xcc, 0x71, 0x20, 0x92, 0x75, 0x3a, 0x3c, 0x26, 0x10, 0xa4, 0xec, 0xa4, 0xf9, 0xf9, 0xae, 0x8f, 0xdb, 0x6d, 0x0f, 0x56, 0xe3, 0x9d, 0x09, 0x70, 0x96, 0x36, 0x0f, 0xd2, 0xbe, 0xe8, 0x63, 0x74, 0x2d, 0xa6, 0x3c, 0x9e, 0xdb, 0x15, 0xe9, 0xf5, 0x4a, 0x4f, 0xb4, 0x70, 0xc7, 0x10, 0x8d, + 0x2a, 0x5c, 0x46, 0x47, 0x12, 0x25, 0x98, 0x92, 0xbb, 0x23, 0xb9, 0x33, 0xc0, 0xaa, 0x01, 0xb4, 0x69, 0xd8, 0x49, 0xb9, 0xff, 0x65, 0xf5, 0x1a, 0xa5, 0x6f, 0x94, 0xc9, 0x5b, 0xd4, 0xd1, 0xda, 0xdc, 0x90, 0x0c, 0x3b, 0xad, 0x56, 0x47, 0x30, 0x12, 0x2b, 0x36, 0x64, 0x45, 0x14, 0x72, 0x02, 0x75, 0x72, 0x16, 0x95, 0xcc, 0x24, 0xc1, 0x1c, 0x06, 0xa2, 0xbe, 0x5e, 0x76, 0xa0, 0x8f, 0x73, 0xcc, 0x17, 0xc8, 0x2b, 0x59, 0x1b, 0xef, 0xdf, 0xd1, 0x5d, 0x4c, 0x78, 0xe9, 0x2c, 0x61, 0xb2, 0xba, 0x2b, 0x5e, 0xbc, 0xbe, 0x74, 0xf1, 0xf0, 0xca, 0x84, 0x33, 0x13, 0x00, 0x64, 0x77, 0x5e, 0x10, 0xe3, 0x6b, 0x35, 0x58, 0xca, 0x16, 0xef, 0x68, 0x5f, 0x02, 0x8c, 0xa6, 0xb0, 0xe0, 0x92, 0xb1, 0xcf, 0xcc, 0xb4, 0x0b, 0x1a, 0x11, 0x7f, 0x94, 0x54, 0x43, 0x83, 0x32, 0xff, 0x69, 0x57, 0x03, + 0xff, 0x8d, 0xa0, 0x75, 0x4c, 0x8d, 0x4c, 0x41, 0xb7, 0x19, 0x2c, 0x56, 0xc9, 0xc2, 0x30, 0xda, 0x38, 0xd6, 0xa3, 0x75, 0xcb, 0x30, 0x6b, 0xd6, 0x37, 0x63, 0xd1, 0xcc, 0xab, 0x4c, 0x98, 0xd3, 0xc0, 0x92, 0xd3, 0x60, 0x81, 0x7b, 0x1a, 0xb8, 0xa7, 0xb2, 0x63, 0x1b, 0x20, 0x5b, 0x4e, 0xbf, 0xda, 0x3c, 0x98, 0x39, 0xb2, 0x60, 0x31, 0x32, 0x7a, 0xbd, 0x30, 0x06, 0xe2, 0x68, 0xb7, 0xfc, 0x1d, 0x8d, 0xea, 0x58, 0x86, 0x2c, 0x46, 0x64, 0x36, 0x8b, 0x63, 0x56, 0x93, 0x41, 0xc3, 0x89, 0xe2, 0x4e, 0x51, 0xe5, 0xce, 0xff, 0xcd, 0x28, 0x7b, 0x44, 0x95, 0x59, 0x3b, 0xe5, 0xb0, 0x2c, 0x19, 0x0a, 0xfd, 0xed, 0x23, 0x11, 0x9c, 0xb4, 0x2f, 0x38, 0x12, 0xb2, 0x58, 0x94, 0x71, 0x8c, 0xf8, 0xa2, 0xc3, 0x84, 0xa8, 0xd3, 0xf1, 0x31, 0x87, 0x81, 0x11, 0x58, 0x8d, 0x66, 0x4a, 0x1e, 0x0e, + 0xc9, 0x43, 0x49, 0x1d, 0x88, 0x03, 0x77, 0x83, 0xd3, 0x4e, 0xca, 0xa3, 0xfd, 0x0d, 0x63, 0xc8, 0x5c, 0x5e, 0x84, 0xd0, 0xf8, 0xda, 0x55, 0x2b, 0x80, 0x39, 0x46, 0x96, 0x0e, 0x2f, 0x59, 0x3c, 0x34, 0x38, 0xd0, 0xdf, 0xd5, 0x19, 0x89, 0x05, 0x83, 0x61, 0x22, 0x13, 0xf3, 0x80, 0xe7, 0x17, 0x94, 0x89, 0x59, 0x3c, 0x7e, 0xe9, 0x65, 0x11, 0xb0, 0xe6, 0x44, 0xbc, 0xe7, 0x17, 0xa6, 0xaf, 0x52, 0xce, 0x5f, 0x42, 0x38, 0x7f, 0xfe, 0x75, 0x52, 0x92, 0x5e, 0x27, 0xa9, 0x01, 0x1e, 0x65, 0xad, 0x0e, 0x99, 0xff, 0x87, 0xa8, 0x08, 0x9e, 0x34, 0x28, 0x22, 0xb8, 0x27, 0xbd, 0x1e, 0x66, 0xbf, 0x74, 0xd1, 0xd5, 0xb3, 0xf6, 0xdc, 0x63, 0xe9, 0x58, 0xba, 0xbc, 0x6f, 0x0a, 0xba, 0x84, 0x2f, 0xe1, 0xbf, 0x09, 0x32, 0xfb, 0xdf, 0x65, 0x96, 0xb6, 0x84, 0xc0, 0x9a, 0x0d, 0x5b, 0xcc, 0x2c, + 0x49, 0xc5, 0xd6, 0x88, 0x2c, 0x59, 0x28, 0x36, 0x72, 0x9d, 0x5c, 0x63, 0xe8, 0x35, 0x66, 0x00, 0xda, 0xf4, 0x79, 0x2f, 0x6c, 0xaa, 0x2e, 0x8b, 0x38, 0x11, 0x3a, 0x73, 0x89, 0x0e, 0x36, 0xb4, 0x88, 0x46, 0x05, 0x99, 0x4c, 0x7a, 0x1d, 0x03, 0x54, 0xd2, 0x0c, 0x29, 0x83, 0x07, 0x78, 0x7a, 0x36, 0x8a, 0xc3, 0x0b, 0x37, 0xfc, 0x18, 0x83, 0x8e, 0x52, 0xeb, 0x0b, 0xa6, 0xd4, 0x50, 0x9f, 0xb4, 0x12, 0x95, 0x47, 0x28, 0x6c, 0xb8, 0x34, 0x85, 0x73, 0xd5, 0x20, 0x21, 0xe5, 0x02, 0xd4, 0xab, 0x99, 0x38, 0xb1, 0x6c, 0x51, 0x0b, 0x8f, 0x8d, 0xf9, 0x45, 0x76, 0x55, 0x39, 0x02, 0xa1, 0x16, 0x20, 0xce, 0xd6, 0x5c, 0x7d, 0xb9, 0x49, 0x21, 0x44, 0x1a, 0xff, 0xc2, 0x13, 0x20, 0xb3, 0x6a, 0xb1, 0x59, 0x5e, 0xc6, 0xe6, 0x28, 0x66, 0x45, 0x1b, 0xe6, 0x51, 0x08, 0x33, 0x1c, 0x4f, 0x63, + 0x47, 0xd9, 0x57, 0xb4, 0x34, 0x76, 0x44, 0xa4, 0x46, 0x29, 0x3d, 0xd6, 0x49, 0x28, 0xb2, 0x79, 0x90, 0x04, 0x16, 0x15, 0x23, 0x54, 0x4b, 0x4f, 0xff, 0x68, 0xb0, 0x8c, 0x10, 0x81, 0xa5, 0xd9, 0x7f, 0x8a, 0x88, 0xa8, 0x04, 0xc4, 0x6d, 0x4e, 0x1f, 0x07, 0xbd, 0xac, 0x6e, 0x21, 0x9a, 0x42, 0x9a, 0xdb, 0x2d, 0x2b, 0xf4, 0xaf, 0xd5, 0x5e, 0xd8, 0x49, 0x22, 0x79, 0x65, 0x22, 0x8b, 0xc5, 0x49, 0xb9, 0xdf, 0x45, 0x5b, 0x8f, 0x2a, 0xc4, 0xaa, 0x45, 0xb5, 0x89, 0x1a, 0x47, 0xd0, 0x16, 0x84, 0xbf, 0xf9, 0x88, 0x05, 0xee, 0x93, 0x75, 0xbe, 0x28, 0x37, 0x9e, 0x63, 0xb7, 0x90, 0x85, 0x55, 0x3e, 0xb2, 0x62, 0xac, 0x21, 0x37, 0x80, 0x9d, 0xa5, 0x79, 0xb2, 0x55, 0x88, 0xa8, 0x11, 0x67, 0x9f, 0xbb, 0x20, 0x30, 0x9d, 0xb5, 0x62, 0x54, 0x1a, 0x69, 0xc3, 0x40, 0xa3, 0xc5, 0x78, 0x54, + 0xb1, 0x6a, 0xcc, 0x58, 0x87, 0x5b, 0x40, 0x31, 0xe8, 0x62, 0x58, 0x83, 0x1a, 0x31, 0x43, 0x36, 0x05, 0xbc, 0xf3, 0x5c, 0x36, 0x70, 0x2a, 0xb5, 0x1a, 0x41, 0xed, 0x5e, 0x28, 0xe3, 0x4d, 0x06, 0xe0, 0x58, 0x45, 0x8c, 0xd1, 0xef, 0x68, 0x54, 0x2f, 0xf3, 0xb2, 0xc5, 0x6c, 0xd4, 0x72, 0xf4, 0x78, 0xb5, 0x42, 0xbb, 0x56, 0x59, 0xfe, 0x91, 0x51, 0xd0, 0xdf, 0x34, 0x08, 0xa1, 0x64, 0xd3, 0x82, 0x83, 0xa0, 0xcc, 0x10, 0xd8, 0x60, 0x98, 0x6f, 0x04, 0x30, 0xc1, 0x39, 0xa4, 0x63, 0x38, 0xdd, 0x05, 0x92, 0xf8, 0x52, 0x5d, 0x09, 0x8d, 0x49, 0x46, 0xcd, 0x62, 0x44, 0xc5, 0x6e, 0x5f, 0x6f, 0x4f, 0xb7, 0xd4, 0xd6, 0xdc, 0x44, 0x49, 0x4d, 0x96, 0xa6, 0xf5, 0x72, 0x84, 0xef, 0x25, 0xc9, 0x7f, 0x91, 0xf5, 0x4a, 0x99, 0x62, 0xd9, 0xf2, 0xf1, 0x0b, 0x98, 0x22, 0x5b, 0xcc, 0x5e, 0x4c, + 0xb4, 0x5e, 0x92, 0x4f, 0x94, 0x05, 0x8d, 0x51, 0x25, 0x78, 0x83, 0x7f, 0xe4, 0xcf, 0xa2, 0x22, 0xd4, 0x24, 0xd5, 0x5b, 0x2d, 0xd4, 0x36, 0x27, 0xdb, 0xef, 0x2a, 0xba, 0xe8, 0x7a, 0x50, 0x6d, 0x73, 0x9a, 0x87, 0x05, 0x8b, 0x80, 0x1e, 0xc5, 0x28, 0x02, 0x51, 0x55, 0x1b, 0x76, 0x92, 0x84, 0xab, 0x5c, 0x64, 0x28, 0x1b, 0x64, 0x74, 0x9e, 0x74, 0x96, 0xdf, 0x27, 0xb3, 0x6c, 0x3e, 0xf8, 0x95, 0x83, 0x91, 0x15, 0x7e, 0x91, 0xa9, 0x88, 0x8f, 0x5d, 0xb7, 0x2c, 0xc2, 0x6c, 0x3f, 0x9b, 0xda, 0xa0, 0x4c, 0xa1, 0x1a, 0xfc, 0x8f, 0x76, 0x9d, 0x6e, 0xda, 0xce, 0x84, 0xfa, 0x76, 0xf5, 0x95, 0x9e, 0xcb, 0xa7, 0xd0, 0x65, 0xc3, 0xd6, 0x2d, 0x75, 0x3a, 0x00, 0x2c, 0x72, 0x34, 0x84, 0x21, 0xfb, 0x9d, 0x33, 0xf4, 0x28, 0x03, 0x59, 0x9a, 0xf2, 0x7a, 0x1d, 0x25, 0xe7, 0x95, 0x89, 0xc1, + 0x48, 0xc1, 0xc3, 0xc8, 0xeb, 0x51, 0x0e, 0x52, 0x1b, 0x75, 0x5a, 0x81, 0x43, 0x45, 0xb8, 0x48, 0x4b, 0x13, 0x54, 0x9d, 0x85, 0x58, 0xce, 0x40, 0xc1, 0x2a, 0x6c, 0xd9, 0x76, 0x21, 0xf3, 0x0f, 0x2d, 0x7d, 0xd6, 0x7b, 0xf6, 0xb8, 0xfc, 0xa6, 0x6b, 0x3f, 0x7f, 0x17, 0x05, 0x90, 0x5a, 0x7d, 0x8d, 0x57, 0x7f, 0x93, 0x3f, 0x1b, 0x29, 0xfa, 0xc0, 0x68, 0x3d, 0xc9, 0xdf, 0x79, 0x9b, 0x02, 0x5e, 0x36, 0xe8, 0xc4, 0x97, 0x00, 0x58, 0xb9, 0xb7, 0xf8, 0x57, 0x90, 0x0f, 0x7c, 0xf4, 0x65, 0xd2, 0xb0, 0x09, 0x2c, 0xb1, 0x10, 0x78, 0x3a, 0x5a, 0x82, 0x4d, 0x76, 0x40, 0x83, 0x45, 0x06, 0x31, 0x22, 0x02, 0x6f, 0x50, 0x3b, 0x39, 0x38, 0x2f, 0x6e, 0x75, 0x69, 0xdc, 0x96, 0x97, 0x95, 0xc6, 0x0b, 0xfc, 0x30, 0x92, 0xcf, 0x6a, 0xb5, 0x85, 0x83, 0x4e, 0x7d, 0x0e, 0x86, 0x93, 0x04, 0xc1, + 0xc4, 0xd1, 0x2b, 0x16, 0xd4, 0x39, 0xc8, 0x73, 0x8a, 0x44, 0x05, 0x33, 0x4d, 0x8d, 0xb2, 0x25, 0x28, 0xc2, 0x3f, 0x73, 0x9b, 0x3b, 0x6a, 0x2b, 0xf0, 0x74, 0xb5, 0xcb, 0xa8, 0x5e, 0x74, 0xbd, 0xc1, 0x65, 0xbc, 0x42, 0xa3, 0xd5, 0x6a, 0x0e, 0xea, 0xdc, 0x86, 0x9b, 0xdf, 0x7b, 0x41, 0x99, 0xc1, 0xb1, 0x47, 0x9f, 0xe6, 0x85, 0x1d, 0x56, 0xdd, 0xda, 0x2d, 0xc7, 0xe4, 0x99, 0x71, 0x77, 0x69, 0x0c, 0xd7, 0x68, 0x0c, 0x06, 0xcd, 0x35, 0x60, 0x78, 0xeb, 0x49, 0xbc, 0x8a, 0xcc, 0xed, 0x9b, 0x30, 0xb7, 0x28, 0x7a, 0x0e, 0xd0, 0x0e, 0x0a, 0x00, 0xd1, 0xed, 0x73, 0x7a, 0xfd, 0x2b, 0x70, 0x9d, 0x9e, 0x20, 0x47, 0xc7, 0x25, 0x53, 0x12, 0xfc, 0xba, 0x7a, 0x2c, 0xe0, 0x38, 0xd6, 0x68, 0x49, 0x1e, 0x4d, 0x8c, 0xea, 0x56, 0x41, 0xa0, 0xc1, 0xa7, 0xcd, 0xf4, 0x20, 0x8f, 0x7c, 0xe6, + 0x7d, 0x92, 0x9a, 0x59, 0x8a, 0x99, 0x9f, 0x51, 0x99, 0x51, 0x92, 0xf9, 0x48, 0x4e, 0xeb, 0xd1, 0x34, 0x23, 0xa5, 0xd7, 0x3c, 0x2d, 0x41, 0x04, 0xc7, 0x62, 0xb1, 0xae, 0x58, 0xd7, 0xa2, 0x4e, 0x82, 0x21, 0x0f, 0x39, 0x2c, 0x42, 0xbd, 0x80, 0x9c, 0x70, 0x4e, 0x01, 0xc3, 0xcf, 0xf9, 0x8d, 0x2f, 0x86, 0x45, 0x76, 0x93, 0x3d, 0x50, 0x96, 0xef, 0x2a, 0x2d, 0xb2, 0xd9, 0x8a, 0x4a, 0x5d, 0xf9, 0x65, 0x01, 0x3b, 0x1e, 0xb6, 0xc1, 0x95, 0x7c, 0xf9, 0x4a, 0x3e, 0x5c, 0xb1, 0x9d, 0xf3, 0x5d, 0x88, 0x59, 0x5e, 0x97, 0xae, 0xd6, 0x00, 0x2d, 0xa0, 0x87, 0x7b, 0xce, 0xef, 0xbf, 0xfe, 0x78, 0x21, 0x54, 0x2b, 0xb8, 0xfd, 0x2a, 0xc5, 0xed, 0x8b, 0x19, 0xdc, 0x2a, 0xd7, 0x9f, 0xa4, 0xd7, 0x5f, 0xce, 0xc2, 0x39, 0xac, 0x89, 0xd4, 0x13, 0xdc, 0x00, 0xac, 0x89, 0x26, 0x74, 0x9b, 0x64, + 0x56, 0xd7, 0xab, 0xb6, 0x1c, 0x8b, 0xbc, 0x9a, 0xb8, 0x14, 0xcf, 0x62, 0x2f, 0xa2, 0xc0, 0x58, 0x45, 0x81, 0x69, 0x88, 0x02, 0xa3, 0xee, 0x35, 0x35, 0x34, 0xb7, 0x8a, 0xf4, 0xf0, 0xab, 0x12, 0x1d, 0xba, 0x68, 0x1f, 0x94, 0xe9, 0x22, 0x2b, 0xbf, 0x26, 0xd4, 0xd4, 0xd8, 0xe0, 0x08, 0x05, 0xa3, 0x54, 0x02, 0x5c, 0xa8, 0xfc, 0x72, 0x25, 0xc0, 0xfc, 0x89, 0x98, 0x0b, 0x0b, 0x06, 0x57, 0x4e, 0xfe, 0x51, 0x81, 0x72, 0x9a, 0xb0, 0x66, 0x61, 0x71, 0x91, 0x95, 0x69, 0xd4, 0xaf, 0x1c, 0x31, 0x9c, 0x23, 0xdf, 0xc8, 0xba, 0x04, 0xf9, 0x66, 0x36, 0x5d, 0xb6, 0x7c, 0x4b, 0xaf, 0x3e, 0xcd, 0xa5, 0x56, 0xdf, 0x7c, 0x8b, 0x6d, 0x01, 0x7a, 0xcb, 0xf0, 0x08, 0x7b, 0x80, 0x7e, 0x8d, 0x68, 0x95, 0xb4, 0x3c, 0x0c, 0xb0, 0x44, 0xcc, 0x0c, 0x8f, 0x2d, 0x24, 0x47, 0x81, 0x6e, 0xd5, 0xcf, + 0x67, 0x34, 0x92, 0x43, 0xa5, 0x69, 0xa3, 0xd1, 0xa0, 0xd7, 0xb2, 0x54, 0x4b, 0x01, 0x90, 0x8d, 0xa8, 0x11, 0x4c, 0xc6, 0x3a, 0x2a, 0x88, 0x89, 0x62, 0x32, 0x5e, 0x5a, 0x31, 0x5d, 0x20, 0x9c, 0xd3, 0x53, 0x50, 0x8d, 0xc4, 0xb4, 0x1a, 0xfa, 0x7a, 0xf3, 0x95, 0xcf, 0x1e, 0x8c, 0x2c, 0x27, 0x94, 0x89, 0x8d, 0x1d, 0x23, 0x94, 0x39, 0x93, 0x5a, 0x7f, 0x26, 0xd5, 0xbf, 0x80, 0xe2, 0x99, 0x57, 0x8a, 0xa7, 0xe9, 0x40, 0xec, 0xc6, 0xdd, 0x60, 0xb7, 0x37, 0xa3, 0x7f, 0x91, 0x4c, 0x2e, 0xac, 0xe3, 0xf3, 0xb1, 0xa0, 0xd3, 0x92, 0x83, 0xc6, 0x4a, 0x58, 0xaf, 0x06, 0x81, 0xa4, 0x16, 0xb4, 0xdc, 0x0c, 0x68, 0x7b, 0x35, 0x94, 0xa0, 0xc8, 0x00, 0xf9, 0x08, 0x20, 0x37, 0xaa, 0x17, 0x19, 0x22, 0x05, 0x8c, 0x06, 0x96, 0x95, 0x53, 0x13, 0x64, 0x63, 0xbc, 0x36, 0xab, 0x27, 0x0f, 0x4a, + 0x81, 0x1e, 0x22, 0x21, 0x5d, 0xe5, 0xaf, 0x0b, 0xf4, 0xfd, 0x5b, 0x1e, 0x48, 0x76, 0x81, 0x60, 0x36, 0xcd, 0xa8, 0xb9, 0xa9, 0xb1, 0xb6, 0xaa, 0xbc, 0x34, 0x48, 0xad, 0x01, 0xf8, 0x47, 0x36, 0x9b, 0x03, 0xd6, 0x0b, 0x35, 0xca, 0x1c, 0x02, 0x00, 0xf7, 0xb8, 0x6c, 0xb2, 0xb0, 0x2e, 0x56, 0x69, 0xc0, 0x3f, 0x01, 0x6a, 0x26, 0x35, 0x78, 0x76, 0xa7, 0xcb, 0xf7, 0xb9, 0x0b, 0x48, 0xf0, 0xca, 0xbf, 0xff, 0xee, 0xd3, 0xbe, 0x07, 0x41, 0x23, 0x99, 0x8f, 0x69, 0xac, 0x9a, 0x9e, 0x2d, 0x8b, 0x02, 0xcc, 0x76, 0x15, 0xad, 0x0b, 0x59, 0xef, 0x5f, 0x7e, 0xad, 0x22, 0xf5, 0x91, 0xd1, 0x7a, 0x3b, 0xf6, 0x34, 0xae, 0xed, 0xd8, 0x74, 0x8e, 0x00, 0x2c, 0xcb, 0x13, 0x71, 0x31, 0xff, 0x0a, 0x13, 0x41, 0x2b, 0xe8, 0x9e, 0xc3, 0x33, 0xdc, 0xb1, 0xf4, 0xf5, 0xe6, 0xac, 0xeb, 0xcf, 0x71, + 0x47, 0x55, 0xd9, 0x2e, 0x46, 0x15, 0xd9, 0x3e, 0x81, 0x35, 0xf2, 0x26, 0x9f, 0xad, 0x1d, 0x63, 0xed, 0x0a, 0x89, 0x31, 0xe2, 0x6a, 0xcc, 0x73, 0xec, 0xc0, 0x72, 0xac, 0x05, 0xb7, 0xca, 0x06, 0x1f, 0x86, 0x9c, 0x3b, 0x9c, 0xba, 0x5f, 0xdc, 0x80, 0x8c, 0x46, 0xed, 0x98, 0x01, 0x53, 0xad, 0xa7, 0xd3, 0xa9, 0x5f, 0xb3, 0x15, 0x01, 0xd1, 0xd9, 0x9a, 0x51, 0x93, 0x1e, 0x24, 0x3b, 0xb1, 0xc1, 0x44, 0x96, 0x86, 0xff, 0xbc, 0xb2, 0xf2, 0x48, 0x42, 0x7f, 0xc3, 0x18, 0x4d, 0x34, 0xa1, 0xdd, 0xd1, 0x65, 0xf6, 0x96, 0x6a, 0x65, 0x4d, 0xa2, 0x21, 0x9a, 0x64, 0x9e, 0x21, 0xe6, 0xef, 0x46, 0xcc, 0x3e, 0x50, 0x25, 0x13, 0xb1, 0x89, 0xf1, 0xb1, 0x35, 0xab, 0x46, 0x86, 0x87, 0x06, 0x7b, 0x16, 0xb5, 0xb5, 0x10, 0x02, 0x3b, 0x88, 0x86, 0xb1, 0x5e, 0x96, 0x86, 0xb9, 0x1c, 0x26, 0xc8, + 0x16, 0x21, 0xfc, 0xbf, 0x52, 0x25, 0x53, 0x42, 0x54, 0x45, 0x09, 0x55, 0x32, 0xa0, 0x76, 0x8a, 0xe0, 0x4a, 0x19, 0x51, 0x3b, 0xf2, 0x67, 0xea, 0x06, 0xba, 0xf0, 0xce, 0xce, 0xbf, 0x4c, 0x3f, 0x73, 0x5b, 0x7e, 0x2c, 0xcf, 0xef, 0xe9, 0x06, 0xd9, 0x73, 0x39, 0xba, 0xe8, 0xe2, 0xec, 0x93, 0x23, 0xb3, 0x14, 0xfe, 0x48, 0x64, 0xf1, 0xc7, 0x8b, 0x59, 0x7c, 0x53, 0x92, 0x75, 0xfd, 0x65, 0x72, 0x5d, 0xd6, 0x4f, 0xc2, 0x77, 0x40, 0xbe, 0x2d, 0x47, 0x2f, 0x49, 0xae, 0xb9, 0xf2, 0xcd, 0xd0, 0x89, 0x75, 0x1a, 0x5e, 0xd1, 0x53, 0xc9, 0xf9, 0x44, 0x9d, 0x6c, 0x91, 0x0b, 0x69, 0x8b, 0x5c, 0x4f, 0x2c, 0x72, 0x1a, 0x5a, 0xd7, 0x8d, 0x01, 0xf9, 0xb6, 0x92, 0x6d, 0xdd, 0x56, 0xa4, 0x15, 0x49, 0x09, 0x92, 0x99, 0xcb, 0x1f, 0x00, 0x65, 0xfa, 0xcb, 0xa6, 0xfd, 0x72, 0xb4, 0x7c, 0x99, + 0x12, 0x57, 0xe9, 0xe9, 0x92, 0xda, 0xa2, 0xaa, 0x04, 0xb5, 0x7e, 0x5c, 0x09, 0xba, 0xb0, 0x72, 0xfb, 0x38, 0x82, 0xd5, 0x36, 0xaf, 0xca, 0xab, 0xfb, 0x58, 0xe2, 0x76, 0x01, 0x2d, 0x98, 0xa3, 0x77, 0x5e, 0x41, 0x75, 0xe8, 0x51, 0x79, 0x69, 0x5a, 0xc0, 0xd6, 0x61, 0xb5, 0x3c, 0x11, 0xc1, 0xcc, 0x40, 0x29, 0xe6, 0xfb, 0xe4, 0x3c, 0x3c, 0x4d, 0xe6, 0x72, 0x3a, 0xeb, 0xa3, 0x0c, 0xd6, 0xcc, 0x82, 0x82, 0x55, 0x27, 0xcb, 0x48, 0x83, 0x3e, 0x2d, 0x58, 0x4b, 0xa0, 0xf9, 0x02, 0x02, 0x75, 0x6e, 0xe3, 0x51, 0xc9, 0x09, 0xc4, 0xa8, 0x43, 0x75, 0xb5, 0x89, 0xaa, 0x8a, 0xb4, 0x34, 0x35, 0x5e, 0xb6, 0x34, 0xcd, 0x59, 0x48, 0x4f, 0x5d, 0xe6, 0x22, 0xf9, 0x38, 0x2b, 0x40, 0xe1, 0xe9, 0x4f, 0x50, 0x3f, 0xa4, 0x0a, 0xf5, 0x4b, 0x3d, 0x8a, 0xcd, 0x25, 0x0a, 0xe1, 0x10, 0x8d, 0x77, + 0x64, 0x19, 0x12, 0x82, 0xa0, 0x1a, 0x12, 0xa4, 0xda, 0x15, 0xb5, 0x9b, 0x28, 0x7f, 0xce, 0x50, 0x8b, 0xa2, 0x0a, 0x55, 0xd9, 0x92, 0x61, 0x62, 0x30, 0x91, 0x93, 0x3b, 0x17, 0x31, 0x98, 0xd2, 0xe7, 0xb0, 0x99, 0x1a, 0x12, 0x97, 0x5b, 0xd0, 0x4e, 0x0a, 0xf4, 0xed, 0x1d, 0x0e, 0xd4, 0xfb, 0x44, 0x46, 0x63, 0x76, 0xdb, 0x16, 0x4b, 0x0b, 0xd9, 0x47, 0x1f, 0xfd, 0x6b, 0xfb, 0xae, 0x91, 0x0a, 0x9d, 0x76, 0xbb, 0x4e, 0xcb, 0xf5, 0x2e, 0x65, 0x3f, 0x43, 0x14, 0x32, 0x9d, 0x13, 0xb5, 0x43, 0x7a, 0xd1, 0x66, 0x69, 0xd3, 0x9c, 0x75, 0xaa, 0xd3, 0x36, 0x82, 0x2d, 0xc2, 0xcf, 0x6b, 0x8b, 0xe8, 0x31, 0xb1, 0x0f, 0x95, 0xf5, 0x65, 0x34, 0x30, 0x34, 0x1b, 0x4b, 0xa3, 0xa1, 0xf6, 0xe1, 0x0c, 0x35, 0x4a, 0x7a, 0x11, 0xf8, 0xcb, 0x60, 0x99, 0x77, 0x84, 0xc3, 0xca, 0xa2, 0x32, 0x7d, + 0xcc, 0x45, 0x95, 0x7d, 0x10, 0x9d, 0xce, 0xff, 0xe3, 0x2c, 0xa5, 0x40, 0xef, 0xde, 0xa5, 0x80, 0x15, 0x0d, 0xe7, 0x8d, 0x00, 0x4e, 0x2e, 0x7f, 0x01, 0xa5, 0xd1, 0x64, 0xe3, 0x7a, 0x46, 0x08, 0x96, 0x72, 0x63, 0x5e, 0x6f, 0xf0, 0xef, 0xa2, 0x16, 0x12, 0xf3, 0x22, 0x29, 0x74, 0xa6, 0x4a, 0xa2, 0x5a, 0x30, 0xab, 0x2b, 0xc4, 0x02, 0x4b, 0x42, 0x5e, 0x70, 0x81, 0xcb, 0xba, 0xa0, 0xc4, 0x50, 0x62, 0xa0, 0x47, 0x05, 0x9e, 0x15, 0x26, 0x11, 0xa9, 0xf5, 0x25, 0x47, 0xbe, 0x40, 0x08, 0xa1, 0x51, 0x2d, 0xa7, 0x98, 0x72, 0x19, 0x6d, 0x57, 0x40, 0xd7, 0x99, 0xda, 0x9e, 0xc6, 0xa1, 0x48, 0x27, 0x74, 0xb1, 0x3e, 0x21, 0x7a, 0x4c, 0x61, 0x81, 0x3e, 0xca, 0x7e, 0x0d, 0x74, 0xc4, 0xb9, 0xfd, 0xa4, 0x0a, 0x98, 0x92, 0x86, 0xc3, 0x9a, 0xc9, 0xdc, 0xae, 0x0b, 0x75, 0xa0, 0x09, 0x76, + 0x80, 0x87, 0x16, 0xd4, 0x02, 0x66, 0x4f, 0x43, 0xd0, 0x1a, 0x8e, 0x86, 0x03, 0xb6, 0xf9, 0x8c, 0x4d, 0x12, 0xf4, 0xb8, 0x68, 0x02, 0xdd, 0x82, 0xc1, 0xaf, 0x8b, 0xe7, 0xc5, 0x2d, 0x1c, 0x0c, 0x5b, 0x38, 0xed, 0x6d, 0x6e, 0x4c, 0x0c, 0xe4, 0x9f, 0xb6, 0x13, 0x68, 0xb8, 0x12, 0x37, 0x4b, 0x36, 0x33, 0xd6, 0xe3, 0x2e, 0x58, 0xc7, 0x7a, 0x80, 0xd2, 0x58, 0x8b, 0xc1, 0x0e, 0x1c, 0x90, 0x29, 0x56, 0x07, 0x7e, 0x96, 0x56, 0x23, 0x68, 0x27, 0x91, 0x5e, 0x7f, 0x61, 0xf4, 0xcb, 0x8c, 0x8d, 0x46, 0x34, 0x6a, 0x10, 0x59, 0x96, 0x46, 0xee, 0x2d, 0x26, 0x1d, 0xa7, 0x06, 0x85, 0x0b, 0x68, 0xc8, 0x4c, 0xed, 0x4c, 0xdd, 0x56, 0x32, 0x02, 0xba, 0xec, 0x01, 0x42, 0x34, 0x64, 0x76, 0xa9, 0x01, 0x60, 0x91, 0x4d, 0xab, 0xa3, 0x28, 0xfb, 0x07, 0x99, 0x41, 0xa4, 0x66, 0x92, 0x97, 0xcb, + 0x70, 0xfa, 0xc9, 0xdc, 0x71, 0x2e, 0xab, 0xb7, 0xb2, 0xe5, 0x80, 0x56, 0xa2, 0x95, 0x2b, 0x14, 0xd5, 0xd8, 0xd7, 0x03, 0x8b, 0xb8, 0x9d, 0x12, 0xfb, 0xb2, 0xb7, 0x1c, 0x2e, 0x9f, 0x01, 0x2e, 0xb2, 0xa6, 0xeb, 0x0e, 0xbc, 0x74, 0x9c, 0x86, 0xbf, 0x2e, 0xc5, 0x16, 0x72, 0x38, 0xac, 0x20, 0xf5, 0xcb, 0x85, 0x57, 0xf9, 0xc7, 0xe2, 0x94, 0x8c, 0xae, 0xe4, 0xdc, 0xfc, 0x4b, 0xa8, 0x10, 0x6d, 0x7e, 0x1e, 0x16, 0xb5, 0x40, 0x32, 0x0f, 0x22, 0x40, 0x9f, 0x02, 0xa0, 0x46, 0x7a, 0x1d, 0x13, 0x61, 0xcf, 0xcb, 0xc2, 0x5e, 0x64, 0x94, 0xbc, 0x9b, 0x62, 0xb9, 0xda, 0x1c, 0x69, 0x85, 0x16, 0x68, 0x34, 0x2a, 0x59, 0x94, 0xad, 0x6d, 0x5b, 0x87, 0x35, 0x30, 0xaf, 0x2e, 0x68, 0x56, 0x51, 0x45, 0xd4, 0x9a, 0x23, 0x28, 0x63, 0xa9, 0x76, 0xeb, 0x83, 0xdb, 0x57, 0xdd, 0x3f, 0xb8, 0xeb, + 0x4c, 0x72, 0x47, 0xf4, 0x9a, 0x1b, 0x31, 0x73, 0x4e, 0x99, 0x6f, 0xcd, 0x8a, 0xfb, 0xf6, 0x75, 0x1a, 0xff, 0xba, 0x92, 0x2f, 0x66, 0x84, 0x3b, 0x8e, 0xd5, 0x9c, 0xca, 0x96, 0x59, 0x77, 0xc3, 0x1c, 0x1a, 0xd0, 0x9b, 0xb2, 0x97, 0xa4, 0x57, 0x36, 0xde, 0x05, 0x2d, 0x39, 0x68, 0x41, 0x7e, 0x89, 0xca, 0xaf, 0x51, 0xb9, 0x41, 0x50, 0xa4, 0x7b, 0x1a, 0x3c, 0xc7, 0x64, 0xe4, 0xbc, 0x46, 0x95, 0xf3, 0x3a, 0x56, 0x10, 0xa6, 0x04, 0x35, 0x21, 0xa2, 0x82, 0xc6, 0xd6, 0x95, 0x96, 0xe8, 0xd2, 0xbd, 0x68, 0xec, 0x3f, 0x9b, 0x99, 0xe5, 0xb2, 0x39, 0xf3, 0xb7, 0x56, 0x9c, 0x2c, 0xba, 0x1b, 0x02, 0x76, 0x41, 0x0d, 0xc5, 0xd3, 0x65, 0xab, 0x91, 0x44, 0x36, 0xee, 0x2e, 0x1a, 0x64, 0xad, 0xdd, 0xf6, 0xe0, 0xf6, 0x25, 0x77, 0x75, 0x9f, 0x78, 0xbd, 0x6e, 0x57, 0xe4, 0x9a, 0x9b, 0x2e, + 0x1e, 0x54, 0xa5, 0x58, 0x36, 0xe0, 0x6c, 0x34, 0x2b, 0xbc, 0x52, 0x71, 0xfe, 0x7d, 0x7e, 0x39, 0xff, 0x1c, 0xf8, 0x84, 0x5f, 0x56, 0xf2, 0xba, 0x23, 0x58, 0x03, 0xce, 0x2d, 0x02, 0x57, 0x88, 0x98, 0x50, 0x51, 0xac, 0xc1, 0xfd, 0xde, 0xec, 0xab, 0xac, 0x7a, 0x75, 0x54, 0x95, 0xfa, 0x1a, 0x5e, 0xe0, 0x35, 0x24, 0x29, 0x56, 0x50, 0x0e, 0xad, 0xeb, 0x75, 0x0c, 0xc3, 0x70, 0xa3, 0x5a, 0xd9, 0x5a, 0x22, 0xf4, 0x1c, 0x27, 0xee, 0x0e, 0xc9, 0x0f, 0x00, 0x81, 0x7f, 0xa9, 0xe6, 0x34, 0xc5, 0x8b, 0xf4, 0x91, 0x2b, 0x1e, 0xca, 0x21, 0x1b, 0xea, 0xb3, 0xca, 0x16, 0x96, 0xad, 0x24, 0x48, 0x83, 0x65, 0xf3, 0xa4, 0x2c, 0x5e, 0xc4, 0xde, 0xb2, 0x91, 0x35, 0x6d, 0x0d, 0x38, 0x38, 0xef, 0xdc, 0x9c, 0xc3, 0x54, 0xe3, 0xd9, 0x79, 0x31, 0xfc, 0x56, 0x39, 0xeb, 0xca, 0x9b, 0xc2, 0x45, + 0xec, 0xda, 0xb9, 0x69, 0x83, 0x69, 0xdb, 0x6b, 0x6e, 0x6a, 0x06, 0xae, 0xb6, 0xda, 0x52, 0x4f, 0x9e, 0x22, 0x6b, 0xf0, 0xfc, 0xfb, 0xe2, 0x3f, 0x01, 0x5e, 0x57, 0xa0, 0xb7, 0x65, 0x34, 0xe5, 0x87, 0x08, 0xc7, 0x82, 0x81, 0x22, 0x12, 0x03, 0xa5, 0x17, 0xeb, 0x75, 0xdc, 0x40, 0x18, 0x8b, 0x04, 0xb9, 0xea, 0x2d, 0x5e, 0xb9, 0x65, 0x54, 0x6f, 0x29, 0x18, 0xae, 0x46, 0xc0, 0xda, 0x58, 0x64, 0x66, 0xb2, 0x77, 0xed, 0x4c, 0x06, 0x86, 0x72, 0x9f, 0x20, 0x0b, 0x63, 0xd0, 0x7c, 0xfa, 0xb5, 0xe0, 0x2f, 0xb0, 0x7a, 0xfd, 0x94, 0x9e, 0x3a, 0x19, 0x22, 0xe6, 0x31, 0x3d, 0xf7, 0xc8, 0xab, 0x45, 0x1c, 0xb2, 0x77, 0x30, 0xb2, 0xfb, 0xc2, 0x5a, 0x32, 0x1a, 0x69, 0xa2, 0x82, 0x7e, 0x1c, 0xa4, 0xf6, 0x4e, 0xbd, 0xe2, 0x64, 0xac, 0x40, 0x8a, 0x28, 0x1d, 0xec, 0x6f, 0x6f, 0x6d, 0x6a, + 0xb4, 0x06, 0x95, 0xad, 0x3d, 0xeb, 0xc7, 0xda, 0xda, 0x53, 0x11, 0x1f, 0x58, 0x80, 0x5e, 0x0b, 0x8a, 0x51, 0x42, 0x90, 0x45, 0x2d, 0x40, 0x10, 0x75, 0xd7, 0xef, 0xed, 0x32, 0x8e, 0x10, 0xa4, 0x30, 0xf5, 0x5e, 0x53, 0x05, 0x25, 0xa1, 0x2e, 0x4d, 0xc2, 0x86, 0x85, 0x25, 0xe8, 0x02, 0x34, 0x9a, 0x87, 0xa6, 0xb2, 0xc1, 0xc4, 0xa2, 0xe6, 0xd4, 0xbd, 0xec, 0xaf, 0x39, 0x2d, 0x32, 0xa3, 0x20, 0x9a, 0x40, 0x9f, 0x94, 0xcc, 0x31, 0x8c, 0xf0, 0xf2, 0x1a, 0x86, 0x43, 0x83, 0x60, 0x5f, 0x92, 0x80, 0x0f, 0xd9, 0x20, 0x2a, 0x23, 0x76, 0x06, 0xc7, 0x23, 0x0e, 0xd4, 0x14, 0x62, 0x58, 0x9e, 0x99, 0x24, 0x71, 0x2e, 0x9a, 0xca, 0xa8, 0x1e, 0x96, 0x64, 0x59, 0xba, 0x51, 0x33, 0x43, 0x44, 0x48, 0x25, 0xdc, 0x15, 0xc1, 0x2c, 0xc5, 0x33, 0x17, 0xf4, 0x9b, 0xb7, 0xc7, 0xa8, 0xe4, 0x0f, + 0x85, 0x30, 0x0a, 0x4d, 0x84, 0x26, 0xc6, 0xd7, 0x76, 0x75, 0x36, 0x35, 0x54, 0x57, 0x16, 0x15, 0x38, 0xed, 0x5a, 0x11, 0x99, 0xb1, 0x59, 0x9f, 0x29, 0x57, 0xa1, 0x24, 0x3b, 0xe2, 0xda, 0x0a, 0x2e, 0x6a, 0xc2, 0x6a, 0x46, 0xae, 0xcd, 0x61, 0xcf, 0xa4, 0xe3, 0xca, 0xd7, 0x85, 0x4c, 0xb2, 0x60, 0xb2, 0x5e, 0x4e, 0xc4, 0xad, 0x57, 0xf3, 0x71, 0x5d, 0xf8, 0xbf, 0x6b, 0x87, 0xeb, 0xbc, 0x3b, 0xb6, 0x6e, 0xd9, 0xe1, 0xad, 0x1b, 0xc6, 0xdb, 0x63, 0xdd, 0xe3, 0x9b, 0xb7, 0x57, 0xd7, 0x2e, 0x0a, 0x74, 0x6f, 0xeb, 0xef, 0xdb, 0x3d, 0x18, 0x7d, 0xa3, 0x7c, 0x97, 0xc9, 0xd2, 0x34, 0x7d, 0xfb, 0x8a, 0xee, 0x96, 0x82, 0xc2, 0xe6, 0xf5, 0x87, 0xaf, 0x3d, 0xbc, 0xbe, 0xb9, 0xe3, 0xc8, 0xb3, 0x7b, 0xf7, 0x7e, 0x71, 0x4f, 0xfd, 0xf8, 0x48, 0x59, 0xad, 0x25, 0x50, 0x5d, 0x34, 0xb8, + 0xaa, 0x69, 0xcb, 0x1d, 0x2b, 0x4f, 0xac, 0xc7, 0x1f, 0xf9, 0x12, 0x7d, 0xa5, 0x6b, 0xb6, 0x6d, 0x5b, 0x53, 0xda, 0x9b, 0xf0, 0xcf, 0xfe, 0x30, 0x54, 0xea, 0xd6, 0x0b, 0xbc, 0xe8, 0x19, 0xa8, 0x29, 0x6a, 0x88, 0xbb, 0xdc, 0xe5, 0x6d, 0x5c, 0x59, 0x7f, 0x48, 0x6b, 0x29, 0xec, 0x8e, 0x0c, 0x1e, 0x5a, 0x55, 0x61, 0xce, 0x2b, 0x75, 0x04, 0x8b, 0xcd, 0x9c, 0xd6, 0x95, 0x18, 0xed, 0xed, 0x3c, 0xb2, 0xa1, 0xa9, 0x6c, 0x60, 0x53, 0xdd, 0xc0, 0x16, 0xbf, 0xbd, 0xbb, 0xd1, 0x59, 0x59, 0x59, 0x62, 0x2d, 0xb9, 0x75, 0x62, 0xf0, 0xaa, 0x55, 0x15, 0xf5, 0x64, 0x8f, 0x62, 0x59, 0xea, 0x53, 0xec, 0x8f, 0x38, 0x1d, 0x32, 0x81, 0x2d, 0xf7, 0xa8, 0x4c, 0x04, 0x5b, 0x23, 0x46, 0x82, 0x89, 0x14, 0x0f, 0x8e, 0x02, 0xbb, 0xe7, 0xd3, 0xda, 0x11, 0x5e, 0x7a, 0x55, 0x9c, 0x73, 0x55, 0xd9, + 0x4a, 0x8c, 0xa7, 0x0b, 0x42, 0x80, 0x82, 0x19, 0x03, 0xad, 0x46, 0xf6, 0xd1, 0xd4, 0xe3, 0x19, 0x53, 0xd9, 0x25, 0x21, 0xbc, 0x52, 0xb9, 0xda, 0x96, 0xb8, 0x43, 0x54, 0x8e, 0x29, 0xfb, 0x6e, 0xf3, 0xb6, 0x07, 0xcd, 0x68, 0x36, 0x9b, 0x5b, 0xcc, 0xcd, 0xb1, 0x78, 0x30, 0x1c, 0x8b, 0x14, 0xd3, 0xfa, 0x06, 0x73, 0x52, 0x72, 0xb3, 0x3d, 0x03, 0x99, 0x50, 0xad, 0x73, 0x4e, 0x73, 0xd4, 0x27, 0x13, 0x6c, 0x7e, 0xc5, 0xde, 0x2e, 0x92, 0xa4, 0x3b, 0xd1, 0xbc, 0xec, 0x48, 0xf2, 0x6c, 0xc7, 0xe1, 0x67, 0xf7, 0x5d, 0xfb, 0x7c, 0x4b, 0xb9, 0xc6, 0x6d, 0xb3, 0x95, 0x34, 0xaf, 0x68, 0xef, 0xdb, 0xd9, 0x17, 0x0a, 0x2c, 0x1a, 0x5c, 0x99, 0x98, 0xb8, 0x77, 0x4b, 0x3d, 0x49, 0x53, 0xdd, 0x74, 0x73, 0x9d, 0x41, 0x17, 0x67, 0xb9, 0x96, 0xc4, 0xd7, 0x4e, 0x8f, 0xdf, 0xb0, 0x2a, 0x3e, + 0xd4, 0xcd, 0x7c, 0xe3, 0x5c, 0xcf, 0xf8, 0xfd, 0x33, 0xed, 0xfd, 0xc9, 0x41, 0x93, 0xd9, 0x17, 0xf0, 0xc5, 0xfb, 0x27, 0x1b, 0xb7, 0x87, 0xeb, 0x43, 0x79, 0x5d, 0xc7, 0xbf, 0x7e, 0xf5, 0xce, 0x57, 0x4f, 0x0e, 0x77, 0xb5, 0xf6, 0x45, 0x65, 0x3d, 0x7b, 0x0b, 0x30, 0xfc, 0x7b, 0xe0, 0x47, 0x39, 0x50, 0x29, 0xd8, 0xfb, 0x98, 0x9c, 0xba, 0x02, 0x21, 0x33, 0xa0, 0xaa, 0x3a, 0x25, 0xa9, 0x49, 0xa9, 0xf3, 0x6b, 0x25, 0x07, 0xa0, 0x68, 0xe1, 0x97, 0x79, 0xf6, 0x06, 0x13, 0x78, 0x28, 0x77, 0xbb, 0xef, 0xcc, 0x3c, 0x67, 0x90, 0xce, 0x92, 0x5a, 0xc3, 0x5f, 0x20, 0xcf, 0x14, 0xbd, 0x40, 0xc7, 0x10, 0xa9, 0xda, 0x28, 0x80, 0x5a, 0x31, 0x63, 0x9e, 0x2d, 0xc2, 0xb2, 0x3f, 0x8a, 0x98, 0x7d, 0xa0, 0x7a, 0x44, 0xb4, 0x4f, 0x3e, 0x3f, 0x93, 0xde, 0xce, 0x56, 0x76, 0xc7, 0x08, 0x10, + 0x41, 0x6b, 0x5c, 0x3e, 0xa3, 0x4d, 0x82, 0x91, 0x8c, 0xba, 0x6d, 0x64, 0x9b, 0x0f, 0x2a, 0xa6, 0xea, 0x94, 0x31, 0x94, 0xb7, 0xcb, 0x60, 0xd8, 0x95, 0x17, 0x32, 0xde, 0xfc, 0xbb, 0xd3, 0xfa, 0x39, 0x40, 0x0a, 0x3a, 0xad, 0xeb, 0x44, 0x5e, 0xde, 0x09, 0x97, 0x16, 0x5b, 0xcf, 0x8d, 0xcf, 0x03, 0x30, 0x3e, 0xff, 0x7b, 0x80, 0xf7, 0xa7, 0xf0, 0xcd, 0x8f, 0x1a, 0xa5, 0x64, 0x06, 0x47, 0x20, 0x60, 0x35, 0x98, 0x1c, 0xaf, 0xdd, 0x08, 0x4c, 0x91, 0xc1, 0xd6, 0x74, 0x1a, 0x5b, 0xe4, 0x1f, 0xc1, 0x98, 0x76, 0x81, 0x23, 0x63, 0x72, 0x1a, 0x32, 0x48, 0x46, 0xc0, 0xdc, 0x67, 0x14, 0xa0, 0xa6, 0x01, 0xa8, 0xb3, 0x9f, 0xfc, 0xe4, 0x59, 0x5c, 0x9c, 0xfa, 0x09, 0xbb, 0xa1, 0x73, 0x09, 0x05, 0xe7, 0x2e, 0x66, 0xcf, 0xae, 0x43, 0x3d, 0x6d, 0xb3, 0x95, 0xdc, 0x75, 0x47, 0xbe, 0xf3, + 0x9d, 0x23, 0x64, 0x2d, 0xec, 0x02, 0xe2, 0x5d, 0xcd, 0xbf, 0x4c, 0x6b, 0x39, 0x92, 0x2a, 0x91, 0x4a, 0xd2, 0xff, 0x66, 0x7a, 0x14, 0x60, 0x3a, 0x5d, 0xa0, 0xc1, 0x9e, 0xa7, 0x23, 0x69, 0x6a, 0xac, 0x95, 0x27, 0xa9, 0x97, 0xf0, 0x4c, 0xdb, 0xdc, 0x80, 0xdf, 0xca, 0x33, 0xbf, 0x74, 0x86, 0x6b, 0xbc, 0xde, 0xaa, 0xb0, 0xc3, 0x11, 0xae, 0xf2, 0x7a, 0x6b, 0xc2, 0x4e, 0xf6, 0xc3, 0x73, 0x1a, 0xf6, 0x43, 0xdc, 0xe4, 0xad, 0x26, 0xd7, 0xaa, 0xbd, 0x3e, 0xf9, 0x93, 0xe6, 0x1c, 0xb6, 0xa7, 0x9e, 0xc0, 0xa7, 0xe1, 0xb9, 0x36, 0x54, 0x8d, 0xf6, 0x7d, 0x35, 0x80, 0x59, 0xdc, 0xaf, 0x94, 0xcd, 0x16, 0x69, 0x58, 0x33, 0xcb, 0x79, 0x23, 0x7a, 0x9c, 0x6c, 0xd7, 0x64, 0x92, 0x4a, 0x63, 0x48, 0x60, 0xc1, 0x61, 0x23, 0xc9, 0xbc, 0xf3, 0xb4, 0x45, 0x99, 0xa6, 0xa3, 0x92, 0x03, 0x23, + 0x92, 0x3e, 0x4a, 0xca, 0xbe, 0xe6, 0x3b, 0xe5, 0x14, 0x68, 0x52, 0x3e, 0xc9, 0x96, 0x93, 0xc4, 0x6b, 0x62, 0x6c, 0x0b, 0xd7, 0x71, 0xfc, 0x20, 0x7b, 0x4a, 0x30, 0xc5, 0x87, 0x95, 0xa8, 0x54, 0xfd, 0x19, 0x6b, 0x4e, 0xb4, 0x8a, 0x7b, 0x2a, 0x6b, 0x8e, 0x3e, 0x32, 0xe7, 0xbf, 0x56, 0xab, 0xf1, 0x27, 0x40, 0xc4, 0x9c, 0x1a, 0xa6, 0x77, 0xc3, 0xff, 0x56, 0xb1, 0x1f, 0x02, 0x4e, 0xdd, 0x92, 0x93, 0xc9, 0xc1, 0x35, 0xc5, 0x33, 0xab, 0xe0, 0x19, 0xaf, 0x3a, 0x2b, 0xa3, 0x11, 0xae, 0xae, 0x4f, 0xdd, 0xc7, 0x7e, 0x9d, 0xe2, 0xac, 0x15, 0x8d, 0xa0, 0x3b, 0x24, 0x53, 0x2b, 0x28, 0xf3, 0x18, 0xa8, 0xfb, 0x02, 0xcc, 0x72, 0xea, 0xfe, 0x41, 0x5c, 0xab, 0x63, 0x08, 0x52, 0xf4, 0x1a, 0x86, 0xa0, 0x85, 0xe4, 0x0e, 0x4e, 0xa7, 0x6b, 0xd5, 0xec, 0x24, 0x68, 0xa2, 0x04, 0x9e, 0x91, + 0xf3, 0xe3, 0x2f, 0xd2, 0x76, 0x4f, 0x76, 0xdb, 0x51, 0xa9, 0xb0, 0xbd, 0x0d, 0xa3, 0xfe, 0xde, 0xb6, 0x91, 0xf6, 0x91, 0xc6, 0xfa, 0xda, 0x44, 0x45, 0x59, 0xb8, 0xd8, 0xe7, 0x71, 0xd8, 0x33, 0x25, 0xbe, 0xe6, 0xa2, 0x94, 0x4f, 0xcc, 0x89, 0xe0, 0xa4, 0x45, 0x14, 0x29, 0x3c, 0x42, 0xb1, 0x5b, 0x6f, 0xcb, 0x4a, 0xca, 0x22, 0x7d, 0x7f, 0xa6, 0xe2, 0xda, 0x57, 0x1d, 0x71, 0x3a, 0x23, 0xd5, 0xf8, 0x9f, 0xcf, 0x94, 0x4f, 0xdc, 0x39, 0x19, 0xe8, 0xf6, 0x99, 0x18, 0xad, 0xd5, 0xe7, 0x98, 0xd9, 0xeb, 0x49, 0xb6, 0xf5, 0x97, 0xae, 0xbc, 0x76, 0x45, 0xfc, 0xec, 0xe4, 0x74, 0xb0, 0xbe, 0x22, 0xee, 0x98, 0x54, 0x9a, 0xfa, 0xbc, 0x55, 0x21, 0x87, 0x23, 0x54, 0x75, 0x21, 0x19, 0xbe, 0x7a, 0xce, 0xba, 0xfc, 0xe6, 0x4d, 0x49, 0x9d, 0x8e, 0x44, 0x7e, 0xb6, 0x1c, 0x9c, 0xf0, 0x97, + 0xfa, 0xcd, 0xf5, 0x53, 0xa7, 0x56, 0x91, 0x8a, 0xa2, 0x3b, 0x0f, 0xe9, 0x3d, 0x25, 0x85, 0xdc, 0x37, 0xd5, 0xb6, 0x6a, 0x5f, 0x5a, 0xcb, 0xfc, 0xa5, 0xd4, 0x27, 0xe8, 0xda, 0xb0, 0xa2, 0x30, 0x4a, 0x48, 0x55, 0x5a, 0x5a, 0xb9, 0x04, 0x91, 0x82, 0xe0, 0x54, 0xac, 0xf1, 0x32, 0xba, 0x32, 0xe5, 0x06, 0x11, 0x2a, 0x2e, 0xca, 0x77, 0x92, 0x12, 0x91, 0xe1, 0x30, 0x2d, 0xad, 0x34, 0x37, 0x82, 0x73, 0xe1, 0xa2, 0xc9, 0x89, 0xd2, 0xfc, 0x52, 0x9e, 0x46, 0x06, 0x03, 0xec, 0x87, 0x1f, 0x7d, 0x27, 0x2b, 0x1a, 0xc3, 0x35, 0xcc, 0xb7, 0x96, 0xf0, 0xf9, 0x97, 0x01, 0x4e, 0xc2, 0x4b, 0x36, 0x14, 0x91, 0x82, 0x3a, 0x2d, 0x3d, 0x31, 0xc0, 0x5e, 0x08, 0x1c, 0xdc, 0xb7, 0x25, 0xc3, 0x5c, 0x2e, 0x60, 0x4a, 0x6c, 0x0d, 0xaf, 0x9a, 0x13, 0x45, 0xcb, 0x7a, 0x34, 0x8d, 0x97, 0x71, 0x0d, + 0x19, 0x39, 0x3f, 0x4b, 0x65, 0x58, 0xb5, 0x54, 0x91, 0x5b, 0x03, 0x54, 0x39, 0x69, 0x42, 0x58, 0x59, 0xa9, 0x8e, 0x05, 0x77, 0xfc, 0xc8, 0x4f, 0x03, 0x25, 0x72, 0x69, 0xc5, 0x8b, 0x39, 0xc5, 0x78, 0xe8, 0xe2, 0x6e, 0x2e, 0x7f, 0x76, 0x61, 0xdf, 0xf5, 0x6c, 0x06, 0x36, 0xc0, 0x83, 0x9f, 0xd4, 0xd0, 0x9c, 0x03, 0x1b, 0xcf, 0xfc, 0xff, 0x05, 0x17, 0xfb, 0x61, 0x6a, 0xd7, 0x02, 0x70, 0x01, 0x2c, 0x04, 0xae, 0x12, 0x60, 0xa6, 0x35, 0x00, 0xa3, 0x19, 0x15, 0x48, 0x5e, 0xb3, 0x41, 0x23, 0x72, 0x48, 0x40, 0x72, 0x7d, 0xd1, 0xcd, 0xca, 0xaa, 0x77, 0x39, 0x48, 0xc9, 0x5a, 0x4c, 0xac, 0x2d, 0x78, 0xa4, 0x5a, 0x57, 0x94, 0x59, 0xe3, 0x7b, 0xf0, 0xec, 0x57, 0x3c, 0x9f, 0x3c, 0xcb, 0xfe, 0x79, 0x6f, 0x50, 0xdf, 0x7d, 0xee, 0xb7, 0xcc, 0x9b, 0x83, 0x07, 0xfc, 0xc6, 0x45, 0xcc, + 0xa6, 0xd9, 0x9f, 0x9f, 0x3f, 0x8f, 0x6e, 0x87, 0x71, 0x3f, 0x4b, 0xce, 0xcf, 0x70, 0x07, 0xe4, 0xf3, 0x33, 0xe8, 0x93, 0x2a, 0x1e, 0xf8, 0x3f, 0x73, 0x1a, 0x78, 0xee, 0x98, 0xa4, 0xe7, 0x41, 0xb5, 0xc4, 0xf5, 0x0c, 0xcb, 0x31, 0x4a, 0xc9, 0x13, 0xa7, 0x40, 0xab, 0xdd, 0x32, 0x98, 0x91, 0xfd, 0x71, 0x9e, 0x55, 0xbc, 0x76, 0x0f, 0x92, 0xf3, 0xce, 0x55, 0xe7, 0x7e, 0x77, 0xe6, 0xa6, 0xec, 0xad, 0x97, 0xa0, 0x12, 0x50, 0x99, 0x8e, 0x48, 0x28, 0x20, 0x7b, 0xeb, 0x24, 0xd4, 0xaf, 0x14, 0x5f, 0x60, 0xb3, 0x95, 0x92, 0x29, 0x5b, 0x3d, 0xdd, 0x72, 0x45, 0x5c, 0x67, 0x6a, 0xb8, 0x65, 0x7c, 0xf3, 0x51, 0xbf, 0xb3, 0x67, 0xd5, 0xc6, 0x9a, 0x91, 0x6b, 0xd7, 0x54, 0x9c, 0x9d, 0x5c, 0x5f, 0x32, 0xd4, 0x18, 0x38, 0xbb, 0x6e, 0xac, 0x7d, 0x67, 0x39, 0xfb, 0x61, 0x74, 0xe3, 0xa2, + 0x95, 0x7b, 0xd6, 0x27, 0x97, 0xd4, 0xe4, 0x27, 0x36, 0xdd, 0x39, 0x4e, 0x94, 0xe6, 0xbe, 0x03, 0xfe, 0xa6, 0x55, 0x8d, 0xe4, 0xdb, 0x95, 0x33, 0x9d, 0x8d, 0xb3, 0xa6, 0x2c, 0x1b, 0x03, 0xe6, 0xe5, 0x46, 0x5d, 0xcf, 0xdb, 0x68, 0x99, 0x7e, 0x79, 0x46, 0x79, 0x54, 0x65, 0x72, 0x38, 0xcb, 0xc2, 0xf0, 0x4a, 0x4e, 0x59, 0x21, 0xcc, 0x35, 0x3d, 0x46, 0x5f, 0x0c, 0x80, 0x2a, 0x95, 0xab, 0xce, 0xe5, 0x42, 0x2d, 0xab, 0x79, 0x30, 0x61, 0xa5, 0x40, 0xdf, 0xc8, 0x44, 0x5d, 0x96, 0x6e, 0x87, 0x19, 0xb0, 0x3b, 0x82, 0x4d, 0x71, 0x57, 0xae, 0x4e, 0x07, 0xc0, 0x15, 0xb8, 0xc0, 0xce, 0x7f, 0x8f, 0xe2, 0xfb, 0x61, 0x39, 0x40, 0xa2, 0x0b, 0x61, 0x9e, 0xb3, 0x51, 0xe5, 0xee, 0x4d, 0xff, 0x60, 0xd3, 0x55, 0x20, 0x7d, 0xb4, 0x9a, 0xb0, 0x80, 0x95, 0x34, 0xef, 0xa9, 0xb9, 0x95, 0xe9, + 0xc9, 0x10, 0x45, 0x60, 0x0b, 0x4c, 0xa7, 0xcb, 0x0e, 0xcf, 0xd7, 0x4e, 0x8a, 0x22, 0x70, 0x22, 0x78, 0x52, 0x7a, 0x7e, 0x9e, 0xa6, 0xb9, 0x95, 0xe7, 0x75, 0x01, 0x5b, 0x38, 0x10, 0x8e, 0x87, 0x44, 0x62, 0x4b, 0xaa, 0xd3, 0x9e, 0x5b, 0xe7, 0xb4, 0x6e, 0x21, 0x34, 0xc4, 0x57, 0x1c, 0xcf, 0x29, 0x76, 0x3a, 0x1f, 0x56, 0x8e, 0x2f, 0xbf, 0x65, 0x32, 0x69, 0xb5, 0xaf, 0x74, 0x9b, 0x8c, 0x53, 0x5b, 0x98, 0x7b, 0xb3, 0x30, 0x44, 0xcf, 0xed, 0x70, 0xc5, 0x80, 0x1f, 0x1b, 0x2a, 0x44, 0xc3, 0xb2, 0x96, 0x72, 0xd1, 0x12, 0xa7, 0x32, 0xc9, 0x78, 0x75, 0x56, 0xd4, 0xaf, 0x9f, 0xf7, 0x0e, 0xc9, 0x79, 0x97, 0xc0, 0x17, 0xc1, 0xc8, 0xeb, 0xb6, 0x17, 0x3a, 0x0a, 0x65, 0xad, 0x43, 0xdf, 0xe5, 0x31, 0x47, 0x83, 0xd8, 0xd4, 0x0c, 0x5e, 0x50, 0xe0, 0x4c, 0xd3, 0xcc, 0xf3, 0xd7, 0x76, + 0x75, 0x5d, 0xfb, 0xfc, 0xcc, 0xde, 0xe7, 0xaf, 0xeb, 0xee, 0xbe, 0xee, 0xf9, 0xbd, 0x77, 0x9e, 0x3a, 0x75, 0xe7, 0xdd, 0x77, 0xdc, 0xc1, 0x69, 0xfa, 0x6f, 0xf9, 0xd6, 0x55, 0x57, 0xbd, 0x7e, 0x73, 0x7f, 0xff, 0xcd, 0xaf, 0x5f, 0x75, 0xd5, 0xb7, 0x6e, 0xe9, 0x3f, 0x77, 0xfc, 0xf5, 0xd3, 0xa7, 0xcf, 0x9c, 0x39, 0x7d, 0xfa, 0x75, 0xd0, 0xb3, 0xdd, 0xe7, 0xbf, 0xca, 0x75, 0x70, 0x4b, 0x11, 0x3c, 0x04, 0x25, 0x48, 0xdd, 0xd3, 0x0a, 0x8c, 0x39, 0x1b, 0x91, 0xab, 0xa1, 0x02, 0xa5, 0xee, 0xa9, 0x96, 0xd6, 0x3d, 0xbd, 0xa0, 0xaa, 0xe9, 0xee, 0x4b, 0xd6, 0x3d, 0xcd, 0xb4, 0xa0, 0xa7, 0x8d, 0xb4, 0xa1, 0x58, 0xa8, 0x22, 0x16, 0xa0, 0x05, 0x37, 0xe4, 0xfa, 0x27, 0xb5, 0x95, 0x58, 0x5e, 0x42, 0x76, 0x57, 0xc6, 0xf1, 0xa2, 0x15, 0x4f, 0x33, 0x5e, 0x56, 0x3d, 0x15, 0x55, 0xf7, + 0x74, 0x4d, 0xf6, 0xed, 0x19, 0x8a, 0x56, 0x0e, 0x77, 0x6d, 0xed, 0x9f, 0x59, 0x1c, 0xad, 0x65, 0x36, 0xb4, 0x77, 0x92, 0xc9, 0x7e, 0x62, 0x4f, 0xb5, 0xb3, 0xb3, 0xa8, 0xef, 0x9e, 0xce, 0x5e, 0x32, 0xe5, 0xcf, 0xec, 0x95, 0x6c, 0xe1, 0xe0, 0xc6, 0xd3, 0x65, 0x89, 0x8d, 0xb7, 0xad, 0xb9, 0x63, 0x6c, 0xa2, 0xa6, 0x16, 0x3e, 0xef, 0x9b, 0x60, 0x76, 0x77, 0x7d, 0xf3, 0x20, 0xc1, 0x80, 0xb4, 0xa8, 0xd8, 0xfd, 0xed, 0xee, 0xd7, 0x0f, 0x13, 0x2c, 0x74, 0xaf, 0x2e, 0x04, 0xf4, 0xa3, 0xa5, 0xe7, 0x3f, 0x10, 0x6e, 0x02, 0x7a, 0xe9, 0x90, 0x13, 0xf5, 0xa2, 0x5f, 0x0d, 0x9e, 0xae, 0x22, 0x71, 0xb3, 0x16, 0xd0, 0x25, 0x65, 0x21, 0x86, 0xa1, 0x25, 0x6a, 0xc8, 0x2f, 0x56, 0xf9, 0xa5, 0x24, 0x0f, 0x97, 0x21, 0x5e, 0xc7, 0xe8, 0x78, 0x66, 0x46, 0x8f, 0xb1, 0x11, 0xeb, 0x78, 0xac, + 0x9b, 0x24, 0x9b, 0x40, 0xf2, 0xf1, 0x8d, 0xdd, 0x83, 0x9c, 0x81, 0x61, 0x98, 0xec, 0xc2, 0x34, 0x55, 0x1f, 0xab, 0x0f, 0xad, 0x47, 0x93, 0xbc, 0xb0, 0x39, 0xca, 0xb4, 0x4e, 0x9f, 0x15, 0x99, 0xdb, 0x91, 0x94, 0xa1, 0x71, 0xb9, 0x0c, 0x7a, 0x84, 0x5c, 0xbd, 0xae, 0x1e, 0xa9, 0xad, 0xa9, 0xa1, 0xaa, 0xa2, 0x24, 0x56, 0x5c, 0xe4, 0xf7, 0xe6, 0x59, 0xf4, 0x4e, 0x83, 0x13, 0x26, 0xaa, 0x0b, 0x99, 0xd2, 0x67, 0x8a, 0x48, 0x95, 0xc9, 0x2c, 0x2e, 0xc2, 0x59, 0x07, 0x89, 0xe4, 0x32, 0x34, 0x39, 0x87, 0x00, 0x33, 0x75, 0x67, 0xf8, 0x7f, 0x2b, 0x68, 0x5c, 0x21, 0xd7, 0x9a, 0x49, 0xd5, 0xde, 0x78, 0xfd, 0xf5, 0x37, 0xde, 0x74, 0xe2, 0x04, 0x33, 0x42, 0xab, 0xce, 0xb4, 0xac, 0x92, 0xca, 0x6d, 0x2e, 0x63, 0x03, 0x2d, 0x3a, 0x73, 0xe0, 0x9b, 0x8d, 0xc9, 0x33, 0x37, 0x4e, 0x3d, + 0x76, 0x65, 0x67, 0xe7, 0x55, 0x5f, 0xda, 0x72, 0xfc, 0x1b, 0xc9, 0xda, 0x97, 0x4f, 0x2c, 0xbf, 0x63, 0x5b, 0x4b, 0x9e, 0x97, 0x19, 0x55, 0x0b, 0xcb, 0xa4, 0xaa, 0x55, 0x7e, 0xc4, 0xab, 0x48, 0x8d, 0x19, 0x7a, 0xa0, 0x88, 0x9c, 0x14, 0xdb, 0xb3, 0x7f, 0xd7, 0x15, 0x2a, 0xd3, 0xee, 0xdf, 0xbe, 0xf9, 0x2a, 0xa5, 0xa8, 0x0c, 0xb5, 0xa9, 0x6f, 0x49, 0x3d, 0xc9, 0xd9, 0x38, 0x3d, 0xf8, 0x43, 0x51, 0x74, 0x5c, 0xd2, 0x9b, 0xc8, 0x4b, 0x4c, 0x02, 0xb4, 0xa2, 0x8c, 0x5a, 0x41, 0x86, 0x1c, 0x0d, 0x25, 0x8b, 0x6a, 0x77, 0x76, 0xad, 0x4e, 0x8e, 0xdb, 0x9a, 0xce, 0x34, 0x9e, 0xaf, 0xc9, 0x1e, 0xb5, 0x09, 0xad, 0x20, 0x33, 0x3d, 0xb7, 0xbe, 0x4c, 0x56, 0x0b, 0x52, 0x41, 0x26, 0x60, 0x8b, 0x45, 0x23, 0x39, 0x15, 0x64, 0xe4, 0x73, 0x5a, 0xd1, 0xf4, 0xeb, 0x85, 0xa8, 0xd4, 0x51, + 0x84, 0xcc, 0x50, 0xe6, 0x1c, 0xe9, 0x35, 0x75, 0xba, 0xa2, 0xfe, 0x15, 0xeb, 0x93, 0xab, 0x6f, 0x9f, 0xaa, 0x6f, 0xdd, 0xfb, 0xf0, 0xf4, 0xd8, 0xf1, 0x04, 0x91, 0x31, 0x7f, 0x4d, 0x1f, 0x24, 0xed, 0xb9, 0x21, 0xd2, 0x5e, 0xee, 0xee, 0x3a, 0xf1, 0xfa, 0xd1, 0xbd, 0xdf, 0x3a, 0x35, 0xd2, 0x92, 0x4c, 0xf5, 0xf2, 0x8b, 0xa3, 0xe9, 0x79, 0xd3, 0xf3, 0xa2, 0x41, 0x34, 0x29, 0x19, 0xcc, 0xa6, 0x4c, 0x29, 0x9d, 0xf4, 0x4a, 0xbd, 0xb0, 0x2e, 0x4e, 0xf6, 0xe4, 0x17, 0x6c, 0x91, 0x9e, 0x99, 0xa4, 0xb5, 0xda, 0x62, 0xe1, 0xf9, 0x26, 0x96, 0x53, 0x1b, 0x47, 0x9e, 0x58, 0xee, 0xb4, 0xda, 0x3f, 0xbf, 0x55, 0x9d, 0xd3, 0xc4, 0x75, 0x89, 0x33, 0x67, 0xb2, 0x66, 0x74, 0x67, 0xef, 0xaa, 0x45, 0xc7, 0xe5, 0xf9, 0x74, 0xb5, 0xa4, 0x1a, 0x45, 0x37, 0x92, 0xe3, 0x13, 0x4f, 0xb2, 0x3f, + 0x52, 0xe8, 0xf8, 0x90, 0x52, 0x21, 0x8a, 0x1c, 0xfc, 0xcd, 0x53, 0x0e, 0xc1, 0x7a, 0xe9, 0x4f, 0x5e, 0xfd, 0xa9, 0xac, 0x3f, 0x1f, 0x3d, 0x50, 0x47, 0xeb, 0x5c, 0xe7, 0xd4, 0xd7, 0x52, 0x4f, 0xf0, 0x79, 0x16, 0x6a, 0xb3, 0x27, 0x5d, 0xae, 0x95, 0x1e, 0x16, 0x9e, 0xce, 0x3d, 0x4b, 0x9c, 0xd3, 0x64, 0x54, 0x3e, 0x2d, 0x1c, 0x35, 0x47, 0x62, 0x41, 0xfb, 0x02, 0xa7, 0x85, 0xad, 0xd9, 0xeb, 0xa3, 0x80, 0x91, 0x23, 0x11, 0xe5, 0xfb, 0xbb, 0x32, 0xc7, 0x85, 0xcf, 0xb6, 0x3f, 0xb4, 0x4d, 0x3d, 0x1b, 0x5b, 0xbd, 0xac, 0xa7, 0xd9, 0x63, 0xd4, 0xc5, 0x59, 0xa6, 0xb5, 0x56, 0x3d, 0x30, 0xcc, 0xfc, 0xeb, 0x5f, 0x9f, 0xc3, 0x3f, 0xef, 0x5b, 0xad, 0x1e, 0x18, 0xce, 0x2b, 0xae, 0x2a, 0xec, 0xcf, 0xc4, 0x19, 0x98, 0x7f, 0x02, 0xdc, 0xe4, 0xa3, 0x4f, 0xc8, 0x53, 0xd2, 0x09, 0x20, + 0x97, 0xac, 0xe0, 0x3f, 0x32, 0x03, 0x4a, 0x11, 0x64, 0x72, 0x85, 0x53, 0xae, 0x90, 0x1f, 0x8c, 0xf2, 0x63, 0x54, 0x09, 0xdd, 0xe4, 0xc9, 0x25, 0xc2, 0x65, 0x15, 0x83, 0x64, 0xe3, 0x47, 0xee, 0x29, 0xdb, 0x0c, 0xe9, 0x0a, 0xe2, 0xe9, 0xbb, 0x92, 0x3f, 0x9d, 0x6f, 0x9b, 0x6b, 0x54, 0xc8, 0xf7, 0x47, 0xc1, 0xa8, 0x70, 0x14, 0xdb, 0x33, 0x11, 0x0d, 0xa7, 0x4b, 0x14, 0x80, 0x17, 0xa8, 0x28, 0x51, 0xeb, 0x86, 0x77, 0x54, 0x9f, 0xdc, 0x18, 0x68, 0x76, 0x1a, 0x45, 0x8f, 0xa9, 0xa1, 0xa8, 0xb2, 0xaf, 0xb1, 0xcc, 0x76, 0x66, 0xa6, 0xc8, 0xc5, 0xb3, 0x75, 0x53, 0xfb, 0xcc, 0xe6, 0x7b, 0xbc, 0x7a, 0x6f, 0xdd, 0xe2, 0xea, 0xd9, 0x13, 0xec, 0x87, 0x2e, 0x3f, 0x9d, 0x67, 0xdb, 0xf9, 0xf7, 0x59, 0x2f, 0xc8, 0xe0, 0x56, 0x52, 0xdf, 0xb5, 0x15, 0x63, 0xbe, 0x00, 0xe6, 0xb1, 0x60, + 0x7d, 0xd7, 0xdd, 0xd9, 0xf5, 0x5d, 0x9b, 0x1b, 0x63, 0x91, 0x60, 0xe0, 0xa2, 0xf5, 0x5d, 0x23, 0x0b, 0x55, 0x18, 0x65, 0x7e, 0xaa, 0xc9, 0x33, 0x59, 0xa2, 0xf5, 0x03, 0x89, 0x92, 0xee, 0x2a, 0x4f, 0x79, 0xff, 0xc4, 0xc6, 0x89, 0xfe, 0xf2, 0xc4, 0xe4, 0xdd, 0xeb, 0xb6, 0x3d, 0xdb, 0x52, 0xa1, 0xc9, 0xb7, 0xdb, 0x62, 0x0d, 0x4b, 0x5b, 0x6a, 0x06, 0xaa, 0xf3, 0xcb, 0xfb, 0xd7, 0x6d, 0x5a, 0xd7, 0x5f, 0x5e, 0x3d, 0x71, 0xcb, 0x9a, 0xcd, 0x0f, 0x37, 0xb1, 0xfd, 0x46, 0xbd, 0xd3, 0xe3, 0x74, 0x84, 0x13, 0xfe, 0x48, 0x4d, 0xc8, 0x5f, 0x14, 0x6f, 0x5d, 0xd5, 0xd6, 0x7b, 0x68, 0x4d, 0x4d, 0x8f, 0x1c, 0x38, 0xf2, 0xba, 0x4b, 0x1b, 0x0a, 0x63, 0x75, 0x11, 0x5f, 0x51, 0x89, 0x34, 0xde, 0xd9, 0xbc, 0x73, 0x59, 0xb5, 0xd4, 0x40, 0xe6, 0x98, 0xba, 0x9f, 0xce, 0x71, 0x0d, 0x7a, + 0x42, 0xf6, 0xfd, 0xad, 0x6b, 0x30, 0x16, 0x1b, 0x93, 0x75, 0x3e, 0x8e, 0x15, 0x48, 0x55, 0x57, 0xb2, 0xa5, 0x3c, 0xe7, 0x1a, 0x97, 0x55, 0x15, 0x09, 0x61, 0x1e, 0x96, 0xc1, 0x0c, 0xe2, 0x49, 0x81, 0xa3, 0xdd, 0x48, 0x60, 0xb1, 0xc0, 0xee, 0xce, 0x04, 0x41, 0x69, 0x41, 0xd7, 0x71, 0x35, 0x08, 0x57, 0xb3, 0x40, 0xf3, 0x34, 0x22, 0xd5, 0xd0, 0x29, 0x20, 0x72, 0x5c, 0x8d, 0xc4, 0x59, 0xe5, 0x62, 0xae, 0x6a, 0x29, 0x57, 0xcd, 0xdf, 0xaf, 0x94, 0xeb, 0xbc, 0x88, 0x2e, 0x5d, 0x7e, 0x78, 0x78, 0x81, 0xb7, 0xf9, 0xac, 0xfd, 0x44, 0xdd, 0xc7, 0xc4, 0x7f, 0xc7, 0xcc, 0xaa, 0x06, 0x4d, 0xd3, 0x7c, 0x6f, 0xf6, 0xd1, 0x74, 0x56, 0x2d, 0x4c, 0x16, 0x99, 0xf7, 0x84, 0x6a, 0xc1, 0x87, 0x0e, 0xe3, 0x56, 0xc9, 0xdc, 0x82, 0x45, 0x7e, 0x33, 0x60, 0x67, 0x14, 0x4c, 0x1f, 0x1a, 0x64, + 0xd0, 0xd3, 0x2d, 0x5e, 0xa4, 0x61, 0x35, 0x32, 0x1f, 0x6a, 0x08, 0xfa, 0x04, 0x64, 0xe0, 0x05, 0xc3, 0x64, 0x86, 0x1f, 0xf5, 0x58, 0x79, 0xd1, 0xdc, 0xf4, 0x20, 0x79, 0x11, 0x0f, 0x33, 0xa6, 0xc3, 0xb2, 0x51, 0x34, 0x78, 0xda, 0x4a, 0x0b, 0xa8, 0xcc, 0x61, 0x64, 0x65, 0x00, 0x1a, 0x89, 0x50, 0x87, 0x20, 0xdf, 0x17, 0x18, 0x23, 0xff, 0x7f, 0x07, 0x84, 0xb4, 0x28, 0xd3, 0x57, 0xab, 0x61, 0xb5, 0x17, 0xef, 0x8b, 0xb4, 0x5a, 0xa5, 0xab, 0xbc, 0xcc, 0x94, 0x51, 0xc8, 0x56, 0xb1, 0x77, 0xef, 0x9e, 0x8d, 0x1b, 0x26, 0xc6, 0x48, 0x8e, 0x1c, 0x39, 0x97, 0x96, 0x59, 0x7a, 0xc6, 0x8f, 0xb9, 0xf4, 0x16, 0x2c, 0xbf, 0x72, 0xa9, 0x32, 0x1a, 0x7f, 0xcb, 0x8a, 0xad, 0xaf, 0x2f, 0xab, 0x58, 0x7f, 0xc7, 0xa6, 0xf5, 0xb7, 0xae, 0x2d, 0x5b, 0xb0, 0x78, 0x86, 0xc5, 0xa8, 0xf1, 0x34, + 0xae, 0x69, 0xeb, 0xdb, 0xd9, 0x17, 0xec, 0x68, 0xd3, 0x5a, 0x35, 0x1f, 0x7b, 0x89, 0xeb, 0x57, 0x6d, 0xec, 0x3f, 0x3e, 0xd9, 0xd4, 0x38, 0x71, 0x55, 0xdb, 0xfc, 0x05, 0x33, 0xfc, 0x1d, 0x9d, 0x8b, 0x0a, 0x2a, 0x46, 0x5a, 0x8a, 0xcb, 0x86, 0xa6, 0x1a, 0x7a, 0x6f, 0x6a, 0x12, 0x4d, 0x1a, 0x59, 0xc6, 0x83, 0x5c, 0xa0, 0xfc, 0x77, 0x1f, 0xfe, 0xb4, 0x4c, 0x69, 0xf7, 0x6a, 0xac, 0x15, 0xaf, 0xc3, 0x58, 0x7b, 0xa0, 0x31, 0xc9, 0x1a, 0x68, 0x7d, 0x67, 0x43, 0x94, 0x16, 0x84, 0xf2, 0x2e, 0x74, 0x8f, 0xa3, 0xc5, 0xa2, 0xf4, 0xf4, 0x38, 0x23, 0xd2, 0xf1, 0x3a, 0x79, 0xe1, 0xeb, 0xc8, 0xc2, 0xd7, 0x20, 0x93, 0xa8, 0x31, 0x65, 0xed, 0x96, 0x80, 0x29, 0xa9, 0xd3, 0x8e, 0xd2, 0x02, 0x4b, 0x7a, 0xb0, 0x18, 0x69, 0x96, 0xe4, 0xce, 0x5c, 0xf1, 0x21, 0x73, 0x6d, 0xcf, 0x5c, 0x19, + 0xa2, 0x0c, 0x65, 0xd4, 0x31, 0x99, 0xc1, 0xc8, 0xf7, 0x4b, 0x8e, 0x96, 0xff, 0xf7, 0x02, 0x4c, 0x1a, 0xc9, 0x8c, 0xa2, 0xd7, 0xf1, 0x7a, 0xe0, 0x64, 0xbd, 0x41, 0x2f, 0x18, 0x80, 0xb1, 0x0d, 0x9c, 0x81, 0xec, 0x4f, 0x5c, 0x6c, 0x54, 0xa4, 0xd7, 0x2b, 0x83, 0x66, 0x8b, 0x3d, 0x52, 0xd0, 0xff, 0xb6, 0x93, 0x47, 0x0e, 0x5f, 0x75, 0xe5, 0xce, 0xed, 0x5b, 0xa6, 0x37, 0x4f, 0xad, 0x9f, 0xc8, 0x15, 0x83, 0xe6, 0xbf, 0x97, 0x18, 0xfc, 0xfb, 0x32, 0xfd, 0xdf, 0x26, 0x3d, 0xff, 0x5e, 0x6b, 0xa1, 0x63, 0x66, 0x75, 0xfd, 0xbc, 0xe2, 0xf6, 0x1a, 0x4d, 0x47, 0xd5, 0xdf, 0x73, 0x89, 0x60, 0x74, 0xf3, 0xf9, 0xf7, 0xb9, 0x55, 0xfc, 0x73, 0x68, 0x00, 0x5d, 0x45, 0x03, 0x0a, 0xcf, 0x7b, 0x31, 0x79, 0x5d, 0x8c, 0x17, 0x3e, 0x79, 0x16, 0xab, 0x51, 0x86, 0x42, 0x04, 0xf2, 0x9a, 0x63, + 0xa8, 0xcd, 0x22, 0x47, 0x1b, 0x76, 0xc8, 0x51, 0x01, 0x5e, 0xdd, 0x42, 0x0f, 0xce, 0xd7, 0x22, 0x3b, 0x78, 0x40, 0x77, 0xcd, 0x6d, 0x18, 0x75, 0x77, 0xb5, 0xb7, 0x86, 0x83, 0x45, 0x05, 0x6e, 0x17, 0x1a, 0xc0, 0x03, 0x62, 0xc6, 0xed, 0xe6, 0x80, 0xf6, 0xdc, 0xc2, 0x72, 0x8d, 0x4b, 0x93, 0x8d, 0xc4, 0x58, 0xda, 0x18, 0xe6, 0xaa, 0xed, 0xf7, 0x4f, 0x56, 0x95, 0xb4, 0xf7, 0xb7, 0x97, 0xd4, 0x0e, 0x8f, 0x0d, 0xd7, 0x36, 0x4d, 0xdf, 0x32, 0xbc, 0xe1, 0xf3, 0x2d, 0xe5, 0xda, 0x7c, 0x9b, 0x2d, 0x5e, 0xd7, 0x9f, 0x88, 0x75, 0x56, 0xb8, 0x8b, 0xea, 0x16, 0xf5, 0x2e, 0xaa, 0x2b, 0x2a, 0xeb, 0x5c, 0xd2, 0x59, 0xe6, 0x4e, 0xae, 0x6e, 0x5d, 0xb4, 0x73, 0x28, 0x7e, 0xb6, 0xe7, 0xf8, 0x8b, 0xfc, 0x73, 0x6d, 0xeb, 0xf7, 0x26, 0x9a, 0x57, 0x76, 0xd4, 0x54, 0x54, 0x17, 0x46, 0x12, + 0xe5, 0x55, 0xbd, 0x5b, 0x17, 0x2f, 0x3d, 0xb2, 0xaa, 0x3c, 0x2d, 0x7f, 0xca, 0x5a, 0x82, 0xb1, 0xa6, 0xb2, 0xe2, 0x60, 0xac, 0xae, 0xaf, 0xa6, 0x7e, 0xb8, 0xbd, 0xae, 0xa9, 0xbb, 0xb2, 0xa8, 0xbd, 0xa6, 0xb0, 0x64, 0xd9, 0x35, 0xcb, 0xce, 0xc5, 0xd9, 0x1f, 0x6f, 0x7b, 0xfa, 0x48, 0x97, 0x9c, 0xd3, 0xc2, 0x3c, 0x0b, 0xb8, 0xf3, 0x92, 0x77, 0xfd, 0x68, 0x44, 0x06, 0x23, 0x07, 0x2d, 0x3a, 0xa9, 0x46, 0x4a, 0x29, 0x8e, 0xa6, 0x95, 0x3a, 0xe3, 0x4e, 0x47, 0x1d, 0x7d, 0xf9, 0x8a, 0x4d, 0xdd, 0x8b, 0x66, 0xd2, 0xd1, 0x2d, 0x79, 0xd7, 0xfa, 0xfb, 0x64, 0xb7, 0xb9, 0xab, 0x45, 0x24, 0x7b, 0xcb, 0x79, 0xe5, 0x7d, 0x35, 0x1e, 0x9a, 0x01, 0xc0, 0x75, 0xd1, 0xad, 0xe2, 0x69, 0xbd, 0x96, 0xc3, 0xf9, 0xc9, 0xd5, 0xed, 0x1f, 0xbd, 0xae, 0x6c, 0x29, 0xc3, 0xf3, 0xe3, 0xe7, 0xdf, + 0x67, 0x5e, 0xe7, 0x5b, 0x51, 0x31, 0x6a, 0x91, 0x1a, 0xb5, 0x40, 0xb7, 0x22, 0x90, 0x27, 0xe4, 0x18, 0x06, 0xcb, 0xec, 0x23, 0x5b, 0x2e, 0xb4, 0xf2, 0xf1, 0x34, 0x3d, 0x8c, 0x31, 0x2e, 0xc8, 0x35, 0xbf, 0xc8, 0x3b, 0x06, 0xe8, 0x29, 0x2e, 0x52, 0x14, 0xba, 0x18, 0x17, 0x13, 0xfc, 0xf3, 0x99, 0xc5, 0xb1, 0xc0, 0xbe, 0x38, 0xfe, 0xf6, 0xe1, 0x1b, 0xae, 0xb4, 0xba, 0x1e, 0x25, 0x7b, 0xdd, 0x93, 0x84, 0xb3, 0x9b, 0x95, 0xbd, 0x6e, 0x3d, 0x70, 0xf6, 0x09, 0x8b, 0xc2, 0x61, 0x2e, 0x5c, 0x79, 0xc1, 0x2e, 0x36, 0xcd, 0x6b, 0x8e, 0x82, 0xfd, 0x59, 0xcb, 0xbf, 0x81, 0x22, 0xe8, 0x48, 0x4a, 0x7e, 0x4f, 0x51, 0xb9, 0x5c, 0xa3, 0x1d, 0xe0, 0xff, 0x07, 0x80, 0xbf, 0x04, 0x49, 0x52, 0xab, 0x08, 0xf0, 0x16, 0xd3, 0x83, 0xc5, 0x14, 0x7e, 0x10, 0x8d, 0x5a, 0x2c, 0x88, 0x48, 0xd8, + 0x98, 0x3d, 0x13, 0x51, 0xd4, 0x8c, 0x23, 0x8d, 0x66, 0xa7, 0x66, 0xa8, 0x24, 0x5e, 0x12, 0x24, 0x7b, 0x59, 0x31, 0x2b, 0x2d, 0xee, 0x6b, 0x4b, 0xd4, 0xcd, 0x99, 0x81, 0x63, 0x01, 0x21, 0xd0, 0x7e, 0x6b, 0xce, 0x4c, 0x70, 0x28, 0xf5, 0xa3, 0x9c, 0xd9, 0xc0, 0x3a, 0xbd, 0x95, 0x79, 0xfe, 0xd1, 0xac, 0xf9, 0x3c, 0x7a, 0x24, 0xf5, 0x83, 0xec, 0xe5, 0x84, 0xdb, 0x65, 0xdc, 0xf3, 0x2b, 0x72, 0x72, 0x54, 0x4a, 0x31, 0x27, 0x78, 0x81, 0x00, 0x66, 0x0c, 0xc6, 0xfd, 0x40, 0x19, 0xe6, 0x68, 0x8e, 0x4a, 0xe6, 0xaa, 0x4e, 0xbd, 0x9a, 0xce, 0x51, 0xe1, 0x04, 0x51, 0xa0, 0xa5, 0x35, 0x45, 0xba, 0x87, 0xbb, 0x63, 0x50, 0x4b, 0xd2, 0x76, 0xf8, 0x51, 0x0d, 0x56, 0x5d, 0xe3, 0x71, 0x9a, 0x86, 0x42, 0xad, 0x8c, 0xaa, 0x4b, 0x34, 0xd7, 0xcb, 0x45, 0xfc, 0x48, 0x27, 0xf5, 0x45, 0x38, + 0xe9, 0x1c, 0x15, 0x47, 0x5d, 0xd8, 0x1a, 0x23, 0xa5, 0xb5, 0x0c, 0x4a, 0x16, 0xf0, 0x42, 0xc2, 0x51, 0xc1, 0xde, 0x7c, 0x47, 0xa1, 0xd9, 0x99, 0x94, 0x67, 0x2e, 0xa2, 0x32, 0x98, 0xfc, 0xf9, 0x7c, 0xc7, 0xa1, 0x2f, 0x4c, 0x52, 0xc9, 0x60, 0x75, 0xfe, 0x02, 0x22, 0x0c, 0x8a, 0xa5, 0x9e, 0xe0, 0xbe, 0x48, 0xf7, 0xa5, 0x96, 0xa1, 0x0f, 0x25, 0x73, 0x23, 0x3d, 0x88, 0xc5, 0x02, 0x57, 0x77, 0x66, 0xb6, 0xf5, 0xea, 0xc8, 0x19, 0x04, 0xac, 0x41, 0x33, 0x86, 0xcc, 0x96, 0x1d, 0x39, 0xcc, 0xa6, 0x2a, 0x22, 0x9e, 0x67, 0x81, 0xdf, 0xc9, 0xf6, 0x1d, 0x41, 0x4a, 0x56, 0xe1, 0x99, 0x02, 0x6a, 0x2d, 0x2a, 0x9d, 0x91, 0x9e, 0xd5, 0xb1, 0x7a, 0xdd, 0xa5, 0x07, 0x41, 0x59, 0x63, 0x48, 0x52, 0xa6, 0xbb, 0xa0, 0x13, 0x16, 0xee, 0xae, 0x6c, 0x1e, 0x0a, 0xe3, 0x40, 0xa9, 0x9d, 0xd9, + 0x43, 0x10, 0x85, 0x88, 0x11, 0x18, 0x79, 0x03, 0x1d, 0x52, 0x7b, 0x6b, 0x5d, 0x6d, 0x75, 0x25, 0x48, 0xc3, 0xac, 0x1d, 0x45, 0x23, 0x91, 0x88, 0x17, 0xd9, 0x51, 0xbc, 0x24, 0xf1, 0x72, 0x77, 0x1c, 0xd9, 0x5f, 0x66, 0x36, 0x81, 0xc8, 0x96, 0x63, 0x35, 0xdd, 0x72, 0x9c, 0x5e, 0xdc, 0xd6, 0x90, 0x62, 0xe6, 0xd1, 0x4f, 0x8f, 0x95, 0x91, 0xfc, 0x22, 0xe6, 0x75, 0x75, 0x3b, 0xd2, 0x67, 0xbf, 0xd4, 0x76, 0xe4, 0x45, 0x68, 0x9c, 0x93, 0x3f, 0x2f, 0xaf, 0x19, 0x6e, 0x08, 0xd6, 0x7b, 0x08, 0x6d, 0x90, 0x74, 0x3e, 0x8c, 0x05, 0x0b, 0x7d, 0xf3, 0x96, 0x1a, 0xc8, 0xe6, 0x05, 0x9e, 0x8a, 0x2d, 0xfa, 0x7a, 0x06, 0xd9, 0x33, 0x1e, 0x67, 0xe5, 0x72, 0xf5, 0x5e, 0xa9, 0xe8, 0x82, 0xdb, 0x70, 0x83, 0xb4, 0x61, 0xc7, 0x95, 0x77, 0xc0, 0x48, 0x3a, 0x99, 0xc7, 0x2d, 0x24, 0x30, 0x1d, + 0xb8, 0x0c, 0x44, 0x71, 0x43, 0x67, 0x53, 0xcc, 0x3c, 0x2c, 0xfd, 0x58, 0x29, 0x61, 0x69, 0xe6, 0xf5, 0x8b, 0x4d, 0x8d, 0xca, 0x2f, 0x22, 0x03, 0x86, 0x61, 0x3e, 0x6d, 0x68, 0x48, 0xea, 0x27, 0xf2, 0xb7, 0x25, 0xca, 0x20, 0x10, 0x60, 0x1c, 0xd1, 0x02, 0xfb, 0xb4, 0xe4, 0xbd, 0x6f, 0xec, 0x28, 0x4f, 0x4f, 0xd1, 0xea, 0x48, 0x8d, 0x82, 0x71, 0x8d, 0x9e, 0x51, 0xa4, 0x70, 0x6d, 0x4d, 0x65, 0x45, 0x69, 0xbc, 0xd0, 0x2f, 0xbf, 0x8e, 0x47, 0x91, 0xc7, 0x6d, 0xb8, 0xcd, 0x30, 0xaf, 0x3c, 0xbe, 0x78, 0xba, 0xd2, 0x45, 0x44, 0xf5, 0x0f, 0x95, 0x74, 0xb1, 0x0b, 0x6d, 0x91, 0x54, 0xfd, 0xc5, 0x64, 0xf8, 0x02, 0xf9, 0x48, 0x39, 0x92, 0x9d, 0xce, 0x5f, 0xfc, 0x29, 0xc8, 0xc0, 0xd5, 0xe8, 0x0b, 0x92, 0x7d, 0x35, 0xd6, 0x9b, 0x25, 0x10, 0x7d, 0xb5, 0x85, 0x60, 0x4b, 0xb3, 0xa0, + 0x03, 0xf5, 0x64, 0xb9, 0x12, 0xca, 0x56, 0x19, 0xe1, 0x16, 0x32, 0xeb, 0x11, 0xc9, 0x6f, 0x31, 0x8f, 0x21, 0xb3, 0x79, 0xc7, 0xa0, 0x4e, 0x24, 0xef, 0x7d, 0xc3, 0xa3, 0x26, 0x03, 0xdd, 0x4b, 0x23, 0x22, 0x8c, 0x1d, 0x07, 0x8c, 0x29, 0x31, 0x92, 0xda, 0x4b, 0x77, 0xa1, 0x2b, 0x8c, 0xf4, 0x13, 0xc6, 0xb5, 0x72, 0x1c, 0x78, 0x54, 0xf2, 0xac, 0x5a, 0x31, 0xbc, 0x64, 0xf1, 0x60, 0x5f, 0x6f, 0x4f, 0x57, 0x4b, 0x73, 0x43, 0x32, 0x68, 0xa3, 0x3a, 0xc2, 0x91, 0xb0, 0xc8, 0xe5, 0xdf, 0x73, 0x13, 0x65, 0x2f, 0x89, 0xca, 0xe0, 0x42, 0x74, 0x50, 0xc5, 0xe3, 0x8f, 0x72, 0x2a, 0x23, 0x9d, 0x4d, 0x79, 0x2e, 0x81, 0xe4, 0x6c, 0xf6, 0x52, 0x49, 0xf3, 0x73, 0x39, 0xc5, 0x8f, 0x6f, 0xcd, 0x96, 0x84, 0x17, 0x72, 0xde, 0xc2, 0x9c, 0x28, 0x93, 0x4a, 0x16, 0xa7, 0xb2, 0xfc, 0x14, 0x03, + 0x54, 0x7e, 0xee, 0xc6, 0x79, 0x92, 0x65, 0x09, 0xe6, 0x0c, 0x9d, 0xe5, 0x24, 0xa3, 0x6b, 0x60, 0x5d, 0x46, 0x80, 0x36, 0x01, 0x7a, 0x41, 0x8a, 0x01, 0x7a, 0xad, 0x19, 0xe9, 0x45, 0xcf, 0x91, 0x18, 0x46, 0xc9, 0x61, 0x9f, 0x5c, 0x82, 0x0c, 0x5a, 0xb0, 0xd9, 0x3c, 0x47, 0x8a, 0x76, 0xa4, 0x47, 0x40, 0x16, 0xd6, 0xcc, 0x5a, 0xcc, 0x97, 0x37, 0x12, 0xca, 0x1a, 0x48, 0xea, 0xce, 0x1a, 0x43, 0x30, 0x0b, 0x17, 0x19, 0x63, 0x2e, 0xad, 0xb3, 0xc7, 0x01, 0x79, 0x1a, 0xc7, 0x68, 0xd7, 0x8e, 0xad, 0xd3, 0x13, 0xe3, 0x63, 0xa3, 0xa4, 0x64, 0xed, 0x40, 0x7f, 0x4f, 0x57, 0x53, 0xc3, 0x85, 0x92, 0x35, 0xef, 0x6f, 0x90, 0xac, 0x97, 0xcb, 0x12, 0xb9, 0x42, 0x57, 0xd8, 0x38, 0x27, 0xcf, 0xe3, 0x51, 0x25, 0xcf, 0xa3, 0x31, 0xe5, 0xbc, 0x20, 0x4d, 0xb0, 0x7e, 0x41, 0x31, 0x8c, 0x7f, + 0x48, 0x93, 0x0b, 0x99, 0xa7, 0xcd, 0x97, 0x9d, 0x1c, 0xf2, 0xb1, 0x19, 0x67, 0x1e, 0x19, 0x2d, 0x10, 0x9b, 0x4c, 0x42, 0xfb, 0x25, 0x7d, 0x35, 0x78, 0x9f, 0x61, 0x0b, 0x93, 0x11, 0xd2, 0x01, 0x1d, 0xd6, 0x68, 0x35, 0xfb, 0x68, 0xd1, 0x51, 0xed, 0x28, 0xc9, 0x35, 0x9e, 0xa6, 0x1b, 0x6b, 0x60, 0x5f, 0x12, 0x5b, 0x84, 0x4a, 0xea, 0xf0, 0x3c, 0x6d, 0xa8, 0xb0, 0x26, 0x0d, 0x65, 0x9d, 0x4a, 0xc5, 0x75, 0x9e, 0xd4, 0xde, 0xd6, 0xd2, 0xd8, 0x40, 0x64, 0x36, 0x15, 0xdb, 0xd4, 0x36, 0xf9, 0xdf, 0x51, 0x41, 0xf8, 0x87, 0xb3, 0x80, 0xe0, 0x8f, 0xbd, 0x08, 0x99, 0x67, 0x3e, 0x3e, 0xde, 0x88, 0xfc, 0x3f, 0x0a, 0x76, 0xed, 0x3b, 0xec, 0x87, 0xc8, 0x8d, 0x16, 0xbf, 0x90, 0xcf, 0x93, 0xa0, 0xae, 0x82, 0x26, 0x1b, 0xcf, 0x31, 0x34, 0x65, 0x82, 0x6c, 0x3f, 0x62, 0x55, 0x85, 0xb9, + 0xd4, 0xab, 0x54, 0x73, 0x65, 0xbb, 0x45, 0xe4, 0x75, 0xad, 0x6e, 0xe4, 0x0e, 0x3a, 0x22, 0x01, 0xe5, 0x3d, 0x9c, 0xc5, 0x95, 0x73, 0x12, 0x5c, 0x92, 0xf5, 0x56, 0xf6, 0x9d, 0x2b, 0x62, 0x06, 0x53, 0xa0, 0x6f, 0xd9, 0x78, 0x7a, 0x43, 0xf9, 0x78, 0x23, 0x49, 0xe8, 0x89, 0x4e, 0xab, 0x5b, 0xa7, 0x24, 0x0d, 0x65, 0xd7, 0xa1, 0xde, 0x36, 0xe6, 0x97, 0x14, 0x3e, 0xb0, 0xa5, 0x3a, 0x01, 0xbe, 0x6a, 0xb4, 0x59, 0x32, 0x14, 0x1a, 0x00, 0xc0, 0x62, 0x2d, 0x29, 0x6c, 0xaf, 0x1c, 0x16, 0xf4, 0x71, 0x58, 0x01, 0x52, 0x86, 0x44, 0x7e, 0x69, 0xf4, 0xb8, 0xfa, 0xea, 0xda, 0x20, 0xa9, 0xf5, 0x8e, 0x05, 0x7e, 0x66, 0x6e, 0x33, 0x94, 0x69, 0x05, 0x4a, 0x37, 0x18, 0xb3, 0x05, 0x1d, 0x65, 0xc5, 0x74, 0x37, 0x78, 0x9e, 0xb7, 0x6a, 0x64, 0xc0, 0x4f, 0xe4, 0xbc, 0x49, 0x83, 0xeb, 0x94, + 0x97, 0x44, 0x7b, 0x58, 0x37, 0x77, 0x4a, 0x67, 0xe6, 0x7f, 0xc3, 0x2d, 0x1b, 0x9a, 0x67, 0x9a, 0x17, 0xe4, 0x42, 0xd9, 0x10, 0x62, 0x3f, 0x82, 0x39, 0xcf, 0x7d, 0x3f, 0xd0, 0xf4, 0x45, 0xde, 0x0f, 0x64, 0x93, 0x5f, 0x3b, 0x43, 0x52, 0xf5, 0xd8, 0x8f, 0x52, 0x53, 0xa7, 0x53, 0x53, 0x9f, 0x77, 0x15, 0xf0, 0x7f, 0x30, 0xda, 0x34, 0x1a, 0xbb, 0xe9, 0x4f, 0x42, 0x81, 0x43, 0xce, 0x9a, 0x72, 0x5a, 0x67, 0xef, 0x8c, 0x2e, 0x8e, 0x44, 0x86, 0x62, 0xcc, 0x8c, 0xd5, 0x09, 0xcf, 0xf2, 0x23, 0x24, 0x98, 0xd8, 0x8f, 0x50, 0x39, 0xc9, 0x76, 0x8b, 0xb8, 0x34, 0x59, 0xef, 0x06, 0x9a, 0xce, 0x7a, 0x37, 0x50, 0x39, 0x2a, 0x6b, 0x09, 0xc4, 0x03, 0xca, 0xbb, 0x81, 0x8a, 0xa3, 0x6c, 0x05, 0x8e, 0x0a, 0x22, 0x2b, 0x94, 0xe2, 0x39, 0x4f, 0xa7, 0x2f, 0x08, 0xca, 0x02, 0x46, 0x30, 0xa5, + 0x5e, 0x7e, 0xc0, 0xa7, 0x7b, 0x47, 0x63, 0xe0, 0xb5, 0x96, 0x7f, 0xd3, 0x05, 0xee, 0x4d, 0xbd, 0xf4, 0x19, 0x57, 0x21, 0xff, 0xa2, 0xd6, 0x24, 0x68, 0x1d, 0xfa, 0xef, 0xf1, 0x3e, 0xfb, 0x4a, 0xa7, 0x9f, 0x7f, 0x43, 0x6f, 0xd7, 0x08, 0x16, 0xdd, 0xd7, 0xf8, 0x42, 0x3b, 0x81, 0x33, 0xcc, 0xdc, 0xe7, 0xae, 0x76, 0xd7, 0x56, 0xcf, 0xee, 0x0c, 0xcb, 0x20, 0x47, 0xfa, 0x42, 0xa1, 0xbe, 0x08, 0x73, 0xc0, 0xea, 0x70, 0x58, 0x67, 0x6f, 0x8b, 0xf4, 0x87, 0x43, 0xfd, 0x51, 0x3a, 0x01, 0xa4, 0xe2, 0xeb, 0xc7, 0x80, 0x2f, 0x1f, 0x79, 0x57, 0x90, 0xcf, 0x6e, 0xca, 0xbc, 0x2b, 0x68, 0x3a, 0xf3, 0xae, 0xa0, 0x78, 0xe6, 0x5d, 0x41, 0xac, 0x09, 0x8b, 0x6c, 0xe6, 0x65, 0x41, 0x36, 0x02, 0x6a, 0xb2, 0x19, 0x0c, 0x9d, 0x1f, 0xbf, 0xea, 0xf2, 0x6a, 0xfe, 0xc8, 0xeb, 0x34, 0xef, 0xe9, + 0x3c, 0x8e, 0x67, 0xf0, 0xa6, 0xd4, 0x53, 0xaf, 0xba, 0x1c, 0x9a, 0xdf, 0xf3, 0x06, 0xdd, 0x3f, 0xe5, 0x15, 0x3c, 0x97, 0x7a, 0xca, 0x6d, 0xc2, 0xd7, 0x9b, 0x7d, 0x86, 0xd4, 0x57, 0x4d, 0x6e, 0x46, 0x8f, 0x7f, 0x6b, 0x32, 0xa5, 0x6e, 0x37, 0xfb, 0x2d, 0x38, 0x51, 0x94, 0xf2, 0x11, 0x38, 0xac, 0xa9, 0x27, 0x39, 0x0c, 0x70, 0x38, 0x51, 0xb1, 0x54, 0x68, 0x16, 0x98, 0x5c, 0xc2, 0x6d, 0xa5, 0x88, 0x74, 0x22, 0x47, 0x71, 0x50, 0x26, 0x1c, 0x4d, 0xae, 0xcc, 0x7e, 0x67, 0x10, 0xc1, 0x17, 0x87, 0x67, 0xdf, 0xd4, 0x16, 0xf8, 0xef, 0xfa, 0x5a, 0x6a, 0xfd, 0x13, 0x6e, 0x9f, 0x80, 0x19, 0x8d, 0x99, 0x17, 0x2d, 0xda, 0xbf, 0x88, 0x05, 0x36, 0xf6, 0xc3, 0xbf, 0xbe, 0xe7, 0xcc, 0xc7, 0x9f, 0x60, 0x5f, 0xca, 0x37, 0xcf, 0x7e, 0xd6, 0x55, 0xe9, 0xca, 0xaf, 0x74, 0x31, 0x1b, 0x29, + 0x12, 0x30, 0x6a, 0x00, 0x1c, 0x2c, 0x86, 0x67, 0x9b, 0xc9, 0x7b, 0x83, 0x34, 0x39, 0xef, 0x0d, 0x9a, 0x9e, 0xf3, 0xde, 0x20, 0xbb, 0xfc, 0xde, 0x20, 0x21, 0xe7, 0xc5, 0x41, 0xec, 0x62, 0xb3, 0x61, 0xf6, 0xd7, 0x4c, 0xc1, 0xb9, 0x59, 0x93, 0x91, 0xc9, 0x9f, 0xfd, 0x0d, 0xf3, 0x27, 0xf6, 0x31, 0x47, 0xcc, 0x34, 0xdb, 0xf6, 0xe9, 0x65, 0xa6, 0xa8, 0x95, 0xf9, 0xf6, 0xa7, 0x41, 0x2f, 0xaf, 0x67, 0x25, 0x66, 0x23, 0xff, 0x0c, 0xd2, 0xa3, 0x3a, 0xd4, 0x2c, 0x35, 0xe8, 0xe1, 0x29, 0x1e, 0xba, 0x93, 0x74, 0xc1, 0xbb, 0x80, 0xc0, 0x45, 0x64, 0x31, 0xbb, 0x5e, 0x7d, 0x27, 0xd0, 0x0e, 0x61, 0x88, 0xbe, 0x01, 0x28, 0x1c, 0xc9, 0xbc, 0x01, 0xc8, 0xa9, 0xe4, 0x18, 0x5d, 0xf2, 0xfd, 0x3f, 0xf8, 0x9d, 0x45, 0xe3, 0x0d, 0xf9, 0xd5, 0xd1, 0xaa, 0xae, 0x60, 0xea, 0xf1, 0xba, 0xc3, + 0x83, 0xc9, 0x55, 0x2d, 0x85, 0x9d, 0x76, 0xbb, 0xc6, 0x98, 0x5f, 0x5e, 0x5d, 0x5f, 0x1c, 0xed, 0xac, 0xf2, 0x86, 0xd7, 0xdc, 0xb1, 0xa3, 0x3e, 0x19, 0xae, 0x0e, 0xf9, 0xad, 0x66, 0xbb, 0xc8, 0x0d, 0x32, 0x16, 0x4f, 0xc0, 0x1e, 0x2d, 0x0a, 0x16, 0xe3, 0xe9, 0x9a, 0x8a, 0xe2, 0xb6, 0x95, 0x89, 0xd4, 0x71, 0x83, 0xad, 0xc2, 0x51, 0xe4, 0xd0, 0x3b, 0x62, 0xad, 0xf1, 0xe2, 0xae, 0x64, 0x90, 0x29, 0x6e, 0x6d, 0xb0, 0xda, 0xad, 0x79, 0x5a, 0xf9, 0xbd, 0xb3, 0x6c, 0x0b, 0xf3, 0x3d, 0xfe, 0x69, 0xe0, 0x23, 0x3f, 0x89, 0x82, 0xf0, 0xe4, 0x55, 0x64, 0x0c, 0x5e, 0x9f, 0x4e, 0xbc, 0xd8, 0xc1, 0x0e, 0x91, 0x54, 0x0b, 0xb7, 0xcb, 0xee, 0x77, 0xf8, 0x2f, 0x96, 0x6a, 0x51, 0x57, 0xab, 0x6c, 0x91, 0xdf, 0xd3, 0x73, 0x60, 0xa4, 0xbc, 0x7c, 0xe4, 0x40, 0x4f, 0xef, 0x81, 0x65, 0xe5, + 0xe5, 0xcb, 0x0e, 0xf4, 0x7e, 0xee, 0x73, 0xcb, 0x97, 0x2c, 0xe1, 0x9f, 0x2e, 0x1d, 0xd9, 0xdf, 0xd7, 0x7f, 0x60, 0xa4, 0xac, 0x6c, 0xe4, 0x40, 0x7f, 0xdf, 0xfe, 0x91, 0xd2, 0x14, 0x77, 0xf6, 0xec, 0xe4, 0xc6, 0x8d, 0x93, 0xf2, 0x7e, 0xf5, 0x0e, 0xb6, 0x9f, 0xf9, 0x16, 0x7d, 0x67, 0x9c, 0x83, 0xbc, 0xc9, 0x9d, 0x1e, 0x55, 0xdf, 0x4f, 0x8e, 0x09, 0x31, 0xe0, 0x63, 0x6c, 0x42, 0x1c, 0x37, 0x49, 0x0e, 0x8c, 0x92, 0xd7, 0x2b, 0x9a, 0x8c, 0x5a, 0x0d, 0x34, 0x13, 0x29, 0x18, 0x51, 0xb2, 0x16, 0xcd, 0x98, 0xa7, 0x38, 0x0c, 0xca, 0x2f, 0x83, 0x4d, 0xe0, 0x9f, 0xb6, 0xf0, 0x02, 0x2b, 0x68, 0x5b, 0xf0, 0x95, 0xbb, 0x2d, 0x76, 0x3e, 0xf4, 0x5e, 0x88, 0x75, 0x9a, 0x77, 0xff, 0xe6, 0x28, 0x57, 0xf1, 0x88, 0x3e, 0xdf, 0xe8, 0x71, 0x3d, 0xfc, 0x9f, 0x66, 0xe3, 0xe4, 0xa4, 0xde, + 0xca, 0x2c, 0x97, 0x9f, 0x1d, 0x63, 0x6f, 0xc5, 0xff, 0xc1, 0x3f, 0x06, 0xb4, 0xad, 0x21, 0x3b, 0x6c, 0x84, 0xb6, 0xee, 0xf9, 0x69, 0xab, 0xd0, 0xb4, 0x27, 0x53, 0x5b, 0xb8, 0x83, 0x05, 0xf2, 0x3a, 0xa3, 0xe1, 0x10, 0x90, 0x57, 0x93, 0x43, 0xde, 0xe4, 0x1c, 0xea, 0x46, 0x2e, 0x20, 0xee, 0x78, 0xa2, 0x2b, 0x66, 0x0d, 0xfa, 0x83, 0x09, 0x4f, 0xea, 0x67, 0x13, 0xc7, 0x2a, 0xfa, 0x13, 0xde, 0x76, 0xbb, 0x5d, 0xd4, 0xdb, 0xc3, 0xd1, 0x72, 0x5f, 0xf7, 0x0a, 0x7f, 0xef, 0xc1, 0xd1, 0x92, 0x88, 0x3f, 0xea, 0x77, 0x5b, 0x4c, 0x40, 0xd9, 0x1d, 0x8c, 0xd1, 0xe6, 0x36, 0xfb, 0xdd, 0xf9, 0x1e, 0x5c, 0xbf, 0xac, 0x20, 0x39, 0x58, 0x9e, 0x7a, 0xd9, 0x90, 0x57, 0x64, 0xf5, 0xe4, 0x69, 0xfb, 0x7a, 0x7d, 0x4d, 0x95, 0x45, 0x8c, 0xbb, 0xaa, 0xdc, 0x68, 0x31, 0x9a, 0xf5, 0xf2, 0x9c, + 0x96, 0xb2, 0xb7, 0x32, 0x49, 0xe1, 0x04, 0x32, 0x80, 0xb7, 0x96, 0x94, 0x12, 0x46, 0xc0, 0x27, 0x29, 0x9b, 0x8b, 0xfd, 0xf4, 0xac, 0x0d, 0xa8, 0x80, 0xf6, 0xf4, 0x0e, 0x77, 0x4f, 0xe6, 0xdd, 0x30, 0xbd, 0xec, 0x90, 0x35, 0x4c, 0xdf, 0xfa, 0xa2, 0xf1, 0x94, 0xda, 0x16, 0x7c, 0xeb, 0x8b, 0xf2, 0xda, 0x5d, 0x6b, 0xe2, 0x5d, 0x7b, 0x85, 0x27, 0xd1, 0x56, 0x57, 0x51, 0x54, 0x93, 0x5f, 0x30, 0x94, 0x28, 0xe9, 0x49, 0xf8, 0x8a, 0x9a, 0x97, 0xd5, 0x54, 0xb5, 0x79, 0x58, 0xd6, 0x62, 0x1a, 0x3b, 0xc9, 0x15, 0xb8, 0xac, 0x8b, 0xd7, 0xac, 0xea, 0xf1, 0xfa, 0x36, 0x16, 0x86, 0x82, 0x5d, 0x9b, 0xa4, 0xa6, 0xa9, 0xfe, 0x78, 0xb8, 0xa0, 0xc8, 0x6c, 0x60, 0x7e, 0x0f, 0xeb, 0xb6, 0x0a, 0xf0, 0xfe, 0x5b, 0xfe, 0x11, 0x14, 0x27, 0x75, 0x48, 0xe2, 0x18, 0xf3, 0x2e, 0xcc, 0x31, 0xd9, + 0xfb, 0x9a, 0x39, 0x6f, 0x71, 0xe9, 0x49, 0xbf, 0xe1, 0xa0, 0x97, 0x1b, 0x8a, 0x45, 0x83, 0xa1, 0x20, 0x85, 0x32, 0x1d, 0x7c, 0x16, 0x1c, 0xd9, 0x46, 0x46, 0x4e, 0x02, 0x3c, 0x3e, 0x2b, 0xea, 0xf5, 0x3a, 0x5b, 0x7e, 0xc0, 0xdd, 0xde, 0x22, 0x35, 0xf8, 0x83, 0xf9, 0x79, 0x46, 0x8d, 0x91, 0x8b, 0x39, 0x13, 0xc9, 0x7a, 0x6f, 0xd5, 0xaa, 0x8e, 0x70, 0x40, 0xda, 0xd0, 0xd6, 0xb2, 0x36, 0xc8, 0x47, 0x34, 0x5a, 0xbd, 0x49, 0xbf, 0x72, 0x68, 0xc9, 0x0a, 0x30, 0x1d, 0x04, 0x31, 0xe1, 0x29, 0xb6, 0x6b, 0x42, 0xdd, 0x53, 0x52, 0xe3, 0xa6, 0xbe, 0x78, 0x24, 0x4c, 0xf1, 0x5a, 0x03, 0x78, 0xcd, 0xa7, 0x78, 0x0d, 0x92, 0xa8, 0x5d, 0x31, 0x46, 0x9c, 0x81, 0xf0, 0x68, 0x1e, 0x8d, 0x1a, 0x11, 0x0d, 0xdc, 0x9e, 0xde, 0x33, 0xef, 0xc9, 0xd4, 0xdf, 0xee, 0x60, 0x86, 0x8c, 0x46, 0x63, + 0xd0, 0x18, 0x8c, 0x85, 0x42, 0xa1, 0x62, 0x51, 0x93, 0xd9, 0x2b, 0x77, 0x2e, 0x58, 0x58, 0x1b, 0xdf, 0x6e, 0x2f, 0xf7, 0x24, 0xda, 0x93, 0x35, 0x45, 0x55, 0x8e, 0x5b, 0x57, 0xfa, 0x44, 0xb1, 0x68, 0x69, 0x03, 0x45, 0x71, 0xd3, 0xb2, 0x44, 0x49, 0xb3, 0x8b, 0xb7, 0x3b, 0x65, 0xe4, 0x7a, 0x98, 0x95, 0xb3, 0x66, 0x77, 0x75, 0xa8, 0x22, 0xd4, 0x35, 0xd5, 0x4e, 0x30, 0xec, 0x71, 0x60, 0x23, 0x81, 0x15, 0xf0, 0xcb, 0x58, 0x01, 0xbf, 0x1a, 0x54, 0x8a, 0xfa, 0xa4, 0x6e, 0x3b, 0x60, 0x97, 0xe4, 0xac, 0x0a, 0x0c, 0x18, 0x7b, 0xa4, 0xf6, 0xb1, 0x06, 0x93, 0x7d, 0xcf, 0x49, 0xb5, 0x12, 0x76, 0x0e, 0x82, 0xb5, 0x20, 0x1e, 0xb4, 0xa5, 0xda, 0x92, 0x50, 0xb1, 0x27, 0x3f, 0xcf, 0xa2, 0xd4, 0xda, 0xd5, 0x6a, 0xd2, 0xb5, 0x76, 0x49, 0x80, 0x7f, 0xbe, 0x57, 0x07, 0xc8, 0xe9, 0x2f, + 0xf8, 0x3e, 0x83, 0x3b, 0xea, 0xad, 0x69, 0xd4, 0x30, 0x62, 0x77, 0xa3, 0xbf, 0xa4, 0xc8, 0x63, 0xc8, 0x13, 0x63, 0xce, 0x64, 0x63, 0x93, 0x37, 0xb9, 0xbe, 0x27, 0x16, 0xec, 0x98, 0x68, 0xae, 0x59, 0xd1, 0x5a, 0x6c, 0xb0, 0x71, 0x6f, 0x3b, 0xa2, 0xfe, 0xbc, 0x86, 0xc4, 0x8a, 0xbe, 0x0e, 0xa3, 0xd5, 0x68, 0x94, 0x11, 0xde, 0xb3, 0xad, 0xbb, 0x76, 0x5d, 0x4f, 0xbc, 0xa8, 0x75, 0x4d, 0x63, 0x91, 0x83, 0xc8, 0xf7, 0x10, 0x7b, 0x0f, 0x3e, 0x22, 0x5c, 0x03, 0x26, 0x56, 0x42, 0xaa, 0x72, 0xc3, 0xc4, 0x78, 0x3c, 0x60, 0x04, 0x7c, 0xf7, 0xab, 0x90, 0xb7, 0xd3, 0x73, 0x48, 0xa3, 0x24, 0xba, 0x20, 0x11, 0x43, 0x87, 0x2c, 0x4a, 0xa7, 0xd5, 0x6a, 0xcb, 0x30, 0x88, 0xe0, 0xc7, 0x09, 0x47, 0x30, 0xeb, 0x24, 0x17, 0x29, 0x3b, 0x82, 0x0f, 0x61, 0x97, 0x2b, 0x5a, 0x71, 0xfc, 0x9a, + 0x93, 0x71, 0xb3, 0x7d, 0x68, 0xfb, 0xba, 0x60, 0x40, 0x28, 0x37, 0xeb, 0x62, 0x49, 0xdf, 0x54, 0x6a, 0x02, 0x3f, 0x2c, 0x72, 0x16, 0x73, 0x53, 0x60, 0xed, 0x56, 0x2d, 0xcd, 0xc2, 0x0e, 0x33, 0x3f, 0x61, 0x5c, 0xfc, 0xa3, 0x20, 0x23, 0xdc, 0x68, 0x09, 0xba, 0x4f, 0x32, 0x17, 0x80, 0x9a, 0xe9, 0xc2, 0xe0, 0xf4, 0x5b, 0x19, 0xf9, 0xfc, 0x0e, 0xa9, 0xcd, 0x51, 0x91, 0x3e, 0x87, 0x43, 0xce, 0xed, 0x48, 0xe9, 0x73, 0x3b, 0x3d, 0xe9, 0x44, 0xf4, 0x8e, 0xf4, 0x79, 0x9c, 0x3e, 0x92, 0xc5, 0x34, 0xff, 0x81, 0x9f, 0x9c, 0x8e, 0x99, 0xe6, 0xa3, 0x92, 0xdf, 0xe3, 0x01, 0xbd, 0xb3, 0xc4, 0x03, 0x4e, 0x72, 0x7d, 0x5d, 0x45, 0x59, 0x38, 0x94, 0xef, 0xb4, 0x98, 0x45, 0x1e, 0xe9, 0xb1, 0x5e, 0xaf, 0x21, 0x52, 0x39, 0x93, 0xaa, 0x64, 0x03, 0x3a, 0xd1, 0x43, 0x3a, 0x84, 0xc1, 0xe8, + 0xd1, 0x1d, 0xa7, 0xba, 0x78, 0x85, 0x4c, 0xa4, 0x3e, 0x73, 0x72, 0x27, 0x99, 0x75, 0x70, 0x47, 0x57, 0x1a, 0x8f, 0x97, 0xc2, 0xdf, 0x97, 0x23, 0x4d, 0xfd, 0xc5, 0x56, 0x47, 0xcd, 0xa2, 0xf6, 0xd6, 0x5b, 0xfc, 0x25, 0x9a, 0xc2, 0x86, 0xc1, 0x32, 0x93, 0xc7, 0xe8, 0xa8, 0x1e, 0x5c, 0x35, 0x58, 0x5d, 0xdc, 0xb1, 0xbe, 0xb5, 0x65, 0x4d, 0x93, 0x2f, 0x1e, 0xb6, 0x39, 0x03, 0xc1, 0x58, 0x45, 0x51, 0xc3, 0xe2, 0xf2, 0x45, 0x35, 0xdc, 0xd3, 0xf1, 0xe2, 0x60, 0x3c, 0x1e, 0x0c, 0x94, 0xa4, 0xde, 0xb2, 0x5b, 0x05, 0xcc, 0xe9, 0x3d, 0x16, 0x4f, 0x24, 0xce, 0x44, 0xea, 0x5c, 0xa2, 0x36, 0xdf, 0x1b, 0x6d, 0x2b, 0xcb, 0x67, 0x19, 0xa3, 0xd9, 0x66, 0x60, 0xf9, 0xfc, 0xda, 0x9a, 0x60, 0x67, 0x6d, 0x91, 0x2b, 0x52, 0xed, 0x0d, 0x56, 0x99, 0xf4, 0x61, 0x9f, 0xab, 0xd6, 0xea, 0xec, + 0xad, 0x8c, 0xb6, 0x97, 0xb9, 0x3c, 0xb2, 0x6d, 0xb3, 0x94, 0xbd, 0x87, 0xf1, 0xc3, 0x5a, 0xbb, 0xf0, 0x1c, 0x49, 0x7b, 0x3a, 0xef, 0xa2, 0x87, 0x26, 0x73, 0x76, 0xa8, 0xe7, 0x48, 0x60, 0x61, 0xcd, 0x7b, 0x62, 0xe3, 0x5d, 0x7f, 0x4f, 0x55, 0x69, 0x47, 0xb9, 0xeb, 0x64, 0x2c, 0xe6, 0xaf, 0x73, 0x9d, 0x3c, 0xc9, 0x6f, 0xf3, 0x04, 0x0a, 0x6a, 0xfb, 0x4b, 0x53, 0xcf, 0xe3, 0xc1, 0x92, 0x4a, 0xb7, 0x33, 0x75, 0x2b, 0x48, 0x24, 0x7a, 0x16, 0x81, 0xbd, 0x07, 0xfd, 0x5a, 0xb8, 0x81, 0x9e, 0x81, 0xd8, 0x32, 0x78, 0xda, 0x42, 0x33, 0x41, 0x94, 0x83, 0x10, 0xed, 0x83, 0x0a, 0x87, 0x21, 0xd4, 0x81, 0x88, 0x27, 0x1d, 0xcf, 0xbe, 0xdb, 0x9d, 0x7b, 0x77, 0xee, 0x0d, 0xfa, 0x2a, 0x05, 0xb8, 0x31, 0x4a, 0x5f, 0x33, 0x9f, 0x39, 0x43, 0x01, 0x4b, 0x8a, 0x24, 0xca, 0xd4, 0xcf, 0x49, + 0x07, 0xff, 0xd7, 0x93, 0x0f, 0x9a, 0xf2, 0x0b, 0xf3, 0xac, 0x85, 0x2e, 0x93, 0xc9, 0x55, 0x68, 0xcd, 0x2b, 0xcc, 0x37, 0xf1, 0x27, 0x52, 0x66, 0xfc, 0xfb, 0x27, 0xf3, 0xe4, 0x4b, 0x79, 0x70, 0xc9, 0x68, 0xcc, 0x2f, 0x44, 0xcc, 0xf9, 0xff, 0x62, 0x7e, 0x42, 0x61, 0xb6, 0x82, 0x4c, 0xba, 0x49, 0xd2, 0xe7, 0x61, 0x96, 0xa3, 0x99, 0xdf, 0xc4, 0x51, 0xb0, 0xd0, 0x44, 0x2e, 0x25, 0x4f, 0xbd, 0x9d, 0xfa, 0x70, 0x94, 0xbb, 0xa8, 0x07, 0xd0, 0x97, 0x9e, 0x45, 0xba, 0x49, 0xf7, 0x3c, 0x4d, 0xa4, 0x02, 0xb8, 0x06, 0x36, 0x0d, 0x83, 0x67, 0x32, 0xcd, 0xb2, 0x1a, 0x8c, 0xd2, 0xf7, 0x8d, 0x66, 0xe5, 0xbb, 0x6b, 0x9c, 0xa5, 0xb6, 0xb9, 0xf9, 0xee, 0xb9, 0x13, 0x74, 0xfe, 0x5b, 0x5e, 0xbc, 0xb3, 0xca, 0x56, 0x68, 0xe5, 0x19, 0x83, 0x2d, 0xec, 0x79, 0xd0, 0xe4, 0x2a, 0x4a, 0xcf, + 0x35, 0x94, 0x80, 0x99, 0x8a, 0xc1, 0xf6, 0x4a, 0x1f, 0xcf, 0x0f, 0xea, 0x35, 0xfe, 0x18, 0xfe, 0x4b, 0xce, 0xa4, 0x6b, 0x54, 0xde, 0x68, 0x03, 0xde, 0xf0, 0x11, 0xde, 0xf0, 0x60, 0x30, 0xba, 0x80, 0x2f, 0x10, 0xa9, 0x93, 0x36, 0x49, 0x29, 0x05, 0xeb, 0x4d, 0xa2, 0x05, 0x36, 0x00, 0xe5, 0x11, 0xab, 0xd5, 0x1a, 0xb1, 0x50, 0x59, 0x40, 0xb5, 0x31, 0x49, 0xe6, 0x76, 0x90, 0xb3, 0x1d, 0x00, 0x58, 0x3b, 0x4d, 0xf5, 0x16, 0x05, 0xf2, 0x2a, 0xe6, 0xb6, 0x6b, 0x2c, 0x6e, 0xfe, 0x4a, 0xc1, 0x6a, 0xf6, 0x9f, 0x3c, 0xe9, 0x32, 0x59, 0x84, 0x03, 0x42, 0xbe, 0xf5, 0xe8, 0x7d, 0x66, 0x4f, 0xbe, 0x91, 0xbd, 0x47, 0x67, 0xfe, 0xba, 0x49, 0xf8, 0x15, 0xf3, 0xfb, 0xd4, 0x2a, 0x51, 0xff, 0x29, 0xa3, 0xfe, 0x55, 0xaf, 0xd9, 0xe0, 0x53, 0xe0, 0x40, 0xef, 0x02, 0x1c, 0xe9, 0xb3, 0x1b, + 0xed, 0x0a, 0x03, 0xa8, 0x67, 0x37, 0x14, 0xfa, 0xbe, 0x7b, 0x52, 0x38, 0x31, 0x6b, 0x26, 0x4c, 0x46, 0xfb, 0xb0, 0xb5, 0x20, 0x97, 0x83, 0x24, 0x37, 0x1d, 0xfc, 0x69, 0xe4, 0xa7, 0xdb, 0x4e, 0x24, 0xd2, 0x48, 0x5e, 0xee, 0xc2, 0x10, 0xae, 0x16, 0x88, 0xaf, 0x09, 0x5a, 0x23, 0x60, 0x0b, 0xda, 0x8a, 0x6d, 0xa1, 0x62, 0x62, 0x5c, 0xd8, 0x32, 0x6f, 0xb7, 0x73, 0x52, 0xf6, 0x26, 0x2b, 0x7d, 0x4e, 0xc6, 0xf2, 0x3f, 0xfa, 0x17, 0x55, 0x55, 0xb5, 0x5b, 0x3c, 0x63, 0x35, 0x6b, 0x26, 0x4e, 0x0e, 0x07, 0xab, 0x4e, 0x86, 0x63, 0xb0, 0x24, 0x4e, 0xae, 0xf4, 0x08, 0xfc, 0x58, 0x51, 0x3c, 0x51, 0x52, 0x53, 0xba, 0x65, 0x3d, 0x61, 0xfa, 0xd1, 0xf2, 0x32, 0xf2, 0x51, 0x5a, 0xe1, 0xb4, 0xa7, 0x6e, 0x64, 0xb4, 0x1e, 0xb2, 0xcf, 0x43, 0x70, 0xea, 0xe3, 0x1f, 0xc1, 0x22, 0xf7, 0xed, + 0xf3, 0x3f, 0x83, 0x79, 0x69, 0xbf, 0x82, 0xf0, 0x4b, 0xec, 0x3d, 0xa7, 0x13, 0xa5, 0xb2, 0x4d, 0x41, 0xf4, 0x49, 0x23, 0xc0, 0x6d, 0x43, 0x5e, 0x02, 0x79, 0xda, 0x46, 0xec, 0xc9, 0x24, 0xe7, 0xf6, 0x62, 0xd5, 0x5c, 0xf4, 0x3a, 0xbc, 0x8a, 0xb9, 0xa8, 0x59, 0xc8, 0x5c, 0x84, 0x96, 0x75, 0x4d, 0x1b, 0xba, 0xc3, 0xe1, 0xee, 0x0d, 0x4d, 0xcd, 0x1b, 0xba, 0x22, 0x91, 0xae, 0x0d, 0xcd, 0x57, 0x5e, 0x79, 0x90, 0x7f, 0x24, 0xdc, 0x33, 0xd9, 0xd6, 0x36, 0xd9, 0x13, 0x89, 0xc8, 0x9f, 0xe1, 0xd4, 0xf2, 0xcf, 0xc3, 0x3f, 0xd9, 0xae, 0x61, 0x7e, 0xc3, 0x24, 0xc1, 0x56, 0x23, 0xef, 0xc0, 0x58, 0xa7, 0xe4, 0x35, 0xaa, 0xe9, 0x7d, 0x55, 0x39, 0x49, 0x8b, 0x3d, 0xe9, 0xc4, 0xbd, 0x5e, 0xb2, 0xa3, 0xdd, 0xa9, 0xa6, 0xf6, 0x49, 0x73, 0x0d, 0x9f, 0xac, 0x16, 0x72, 0xce, 0x22, 0x49, + 0xed, 0xcb, 0xb2, 0x7f, 0xd4, 0x9c, 0xc5, 0x1c, 0x03, 0xc8, 0x41, 0xb1, 0xfd, 0xae, 0xbd, 0x12, 0xec, 0x1f, 0x93, 0x3b, 0xe8, 0x8c, 0x54, 0xbb, 0x0c, 0x45, 0x23, 0x8d, 0x25, 0xdd, 0xb2, 0x09, 0x54, 0xda, 0x94, 0x7f, 0x72, 0xa5, 0x5f, 0x90, 0x0d, 0xa0, 0xd2, 0x96, 0xb0, 0xc5, 0xef, 0x5d, 0x11, 0x06, 0x05, 0x3d, 0x49, 0x4d, 0x20, 0xb7, 0xf3, 0x07, 0xcc, 0xe9, 0x7c, 0x3a, 0x9f, 0x6a, 0x98, 0x8f, 0x4b, 0x99, 0xcf, 0x31, 0x79, 0x0a, 0x66, 0x62, 0x54, 0x98, 0x64, 0xa3, 0x82, 0xa5, 0x2f, 0x83, 0x23, 0x57, 0xf8, 0xac, 0x2b, 0xa3, 0x72, 0xc3, 0xac, 0x0c, 0xbe, 0x9e, 0x74, 0x7a, 0x5e, 0x2f, 0x11, 0x8d, 0x9d, 0xe9, 0x0c, 0x3e, 0x29, 0xd7, 0x1a, 0xc9, 0x69, 0xa2, 0xbe, 0xee, 0x23, 0x24, 0x27, 0xf0, 0x69, 0xe6, 0x49, 0xe0, 0xab, 0xcb, 0xb5, 0x49, 0xea, 0x93, 0x09, 0xb0, 0x4a, + 0x2a, 0x3c, 0xc9, 0x16, 0xb3, 0x27, 0xec, 0x0c, 0xd4, 0x80, 0x24, 0x2d, 0x1c, 0x54, 0xed, 0xbe, 0x44, 0x95, 0xe4, 0xe1, 0x45, 0x2f, 0x6f, 0x73, 0x59, 0x97, 0xae, 0x94, 0xe7, 0x8c, 0xb7, 0xcf, 0x2e, 0xfd, 0xcf, 0xa2, 0x88, 0x6a, 0x98, 0x04, 0xbc, 0x9e, 0x7c, 0x99, 0xff, 0xf1, 0x9d, 0x30, 0x67, 0x07, 0x1a, 0x97, 0x27, 0xa2, 0xe3, 0x41, 0x98, 0x98, 0xd4, 0x2c, 0x3d, 0xf8, 0xc1, 0xc9, 0x3f, 0x94, 0x79, 0x66, 0x25, 0xe9, 0xf5, 0x28, 0xea, 0x5d, 0xce, 0xe9, 0x97, 0x32, 0xf9, 0x79, 0xea, 0x0d, 0x92, 0xd3, 0x0f, 0x8e, 0x6e, 0x5a, 0x11, 0xa8, 0xd9, 0x77, 0x82, 0x9a, 0x7c, 0xf7, 0x03, 0x4f, 0x67, 0x79, 0x20, 0x5f, 0xcb, 0xe5, 0x69, 0x0a, 0x4c, 0xd1, 0x32, 0xcb, 0xc9, 0x65, 0x0e, 0x13, 0xcf, 0x77, 0x54, 0x26, 0x2d, 0xdb, 0x6d, 0x7c, 0x49, 0x38, 0x75, 0x96, 0xd1, 0x9a, 0xed, + 0x24, 0x8e, 0x06, 0xb6, 0xe9, 0x57, 0x81, 0xd7, 0x6b, 0x89, 0x6d, 0x5a, 0x0b, 0x26, 0xa8, 0xfb, 0x62, 0x39, 0x77, 0x3d, 0xe9, 0x9c, 0xbb, 0x5e, 0x66, 0xa8, 0x3c, 0x18, 0x2e, 0x07, 0x53, 0x82, 0x88, 0x1b, 0x21, 0x6d, 0x9a, 0xce, 0xb3, 0x35, 0x4e, 0x58, 0x8b, 0x4b, 0x9b, 0xa8, 0x77, 0xe9, 0xac, 0x3a, 0xa7, 0xa7, 0xd8, 0x59, 0x1c, 0x09, 0xd7, 0x4b, 0xf5, 0x61, 0x6f, 0x72, 0xb8, 0x36, 0x39, 0x59, 0x50, 0x24, 0x58, 0x8c, 0x46, 0x87, 0x27, 0xe4, 0x2d, 0x29, 0x0e, 0x37, 0x48, 0x0d, 0x61, 0x77, 0x62, 0xa0, 0xa6, 0x7e, 0xcc, 0xcf, 0x57, 0x5a, 0x74, 0x7a, 0x9d, 0xc7, 0xe5, 0x70, 0xd9, 0xec, 0x81, 0xda, 0x92, 0x48, 0x47, 0xa5, 0x27, 0xe4, 0x4b, 0x6a, 0xf5, 0x46, 0xb3, 0x21, 0x60, 0x77, 0xe6, 0xdb, 0x6c, 0xc1, 0xa6, 0xca, 0x40, 0x53, 0xb9, 0x27, 0xec, 0x27, 0xf2, 0x26, 0x00, + 0x73, 0x39, 0x29, 0xd4, 0xa3, 0x42, 0xb2, 0x6a, 0x0d, 0x34, 0x3f, 0x20, 0x77, 0x8f, 0x59, 0x4a, 0xc7, 0x84, 0x7a, 0xd1, 0x50, 0xd0, 0x6a, 0x8d, 0x66, 0x59, 0x4e, 0x6a, 0xa8, 0xae, 0x1d, 0x27, 0x9b, 0xb1, 0x83, 0x18, 0x51, 0xd4, 0xa2, 0xa0, 0x30, 0x9f, 0xf4, 0xf5, 0x47, 0x7b, 0x06, 0x6b, 0xf4, 0xe6, 0x15, 0x5e, 0xde, 0xd3, 0x7b, 0xd3, 0x4d, 0x4e, 0x1b, 0xe8, 0x35, 0x70, 0x98, 0xd8, 0x97, 0x5d, 0xae, 0xa5, 0xdd, 0xd8, 0x69, 0x36, 0x35, 0x58, 0x6e, 0x70, 0x3d, 0x38, 0x95, 0xfa, 0x1f, 0x87, 0x1b, 0xec, 0x69, 0x02, 0xcb, 0x00, 0xc8, 0x10, 0x27, 0xf8, 0x79, 0x2e, 0x02, 0x8b, 0x13, 0x74, 0x0a, 0xf8, 0x78, 0x44, 0xf6, 0x11, 0x71, 0x27, 0x65, 0x02, 0x6e, 0xb2, 0x00, 0x75, 0x21, 0x57, 0xd0, 0x11, 0x02, 0x54, 0xa6, 0x63, 0x6a, 0x78, 0x4e, 0x50, 0x8a, 0x71, 0xae, 0x74, 0x6b, + 0xc5, 0xa2, 0x81, 0xba, 0x92, 0xce, 0x32, 0xd7, 0x2d, 0xf1, 0x98, 0xbf, 0xd6, 0xc5, 0x3f, 0x30, 0xfb, 0x67, 0x77, 0xa4, 0x28, 0x4a, 0xd4, 0x3b, 0x1e, 0x4c, 0x3d, 0x5f, 0x52, 0xe9, 0x71, 0xe2, 0x03, 0x88, 0xd6, 0x33, 0x06, 0x7b, 0x62, 0x9e, 0x77, 0x4f, 0x4b, 0x19, 0x91, 0x9d, 0x87, 0x40, 0x57, 0x70, 0xa0, 0xbf, 0x30, 0x1b, 0x64, 0xa9, 0x27, 0x41, 0xc3, 0x13, 0x44, 0x4b, 0x24, 0x18, 0xff, 0xf7, 0xaf, 0x79, 0x73, 0x9f, 0xd9, 0xc9, 0x4c, 0x08, 0x7a, 0x5e, 0x30, 0x88, 0xeb, 0x58, 0xbb, 0x89, 0x3c, 0x8c, 0xd1, 0x9a, 0x0c, 0xb3, 0xfe, 0x5a, 0x8f, 0xa7, 0xce, 0x7f, 0xce, 0x60, 0x82, 0xe7, 0xb8, 0x41, 0xbe, 0xb7, 0xf2, 0x0f, 0x82, 0x5f, 0x03, 0xcf, 0x09, 0x3a, 0x34, 0x2c, 0x39, 0x7d, 0x44, 0xcf, 0xd8, 0x48, 0x83, 0x8a, 0xcf, 0x18, 0x0f, 0xc4, 0x5b, 0x2c, 0x94, 0x49, 0xb3, + 0x1f, 0x02, 0x18, 0xae, 0xa1, 0x3f, 0x64, 0xad, 0xc4, 0xe6, 0x04, 0x9b, 0xdc, 0x7b, 0xcd, 0x2e, 0xa6, 0x8b, 0xd3, 0x70, 0xbc, 0x41, 0x5c, 0xa2, 0xf5, 0xd7, 0x17, 0x69, 0x16, 0x0b, 0x3a, 0x2e, 0xaf, 0x97, 0x75, 0x98, 0x76, 0x7e, 0x71, 0xa7, 0x4d, 0x5c, 0x0d, 0x3e, 0x2f, 0xa7, 0x1b, 0x15, 0x5c, 0xdb, 0xf9, 0x07, 0xcc, 0xd6, 0xff, 0xf1, 0x54, 0xb8, 0x5c, 0x15, 0x9e, 0x3f, 0x17, 0x14, 0xfe, 0xc9, 0x5d, 0xee, 0x1a, 0xff, 0x93, 0xc5, 0x0c, 0x58, 0xc1, 0x0e, 0x4b, 0x81, 0xa5, 0xa0, 0x38, 0xf5, 0x5b, 0x37, 0xb5, 0xaf, 0x9c, 0x80, 0x8f, 0x8a, 0xf4, 0x7b, 0xa6, 0xb3, 0x63, 0x47, 0xd2, 0xa0, 0x42, 0x84, 0x58, 0x5e, 0x89, 0x85, 0x23, 0xe8, 0x57, 0x4a, 0xc4, 0x2a, 0xc7, 0x62, 0xa3, 0x14, 0xea, 0x24, 0x85, 0x58, 0x64, 0x2a, 0x6e, 0x30, 0xd9, 0xf8, 0x2d, 0xac, 0xc0, 0xaf, 0xe1, + 0xf3, 0x4c, 0x87, 0x4f, 0x3f, 0x7e, 0x83, 0xd9, 0xc4, 0x6f, 0xe5, 0x75, 0x4b, 0x74, 0xce, 0xa3, 0x8f, 0xb3, 0xf7, 0x98, 0xf5, 0xcf, 0xe8, 0xf2, 0x34, 0x27, 0x75, 0x16, 0x6c, 0x3c, 0xa7, 0xd1, 0x7e, 0x29, 0xcf, 0x79, 0x8d, 0xfd, 0x8f, 0xb2, 0x7d, 0xc7, 0x83, 0xec, 0x2b, 0x82, 0xe7, 0x5f, 0x10, 0x33, 0xa2, 0x04, 0xe9, 0x54, 0x63, 0x46, 0x79, 0x41, 0x42, 0x10, 0x39, 0x66, 0x54, 0x7f, 0x01, 0x51, 0x8a, 0x52, 0xbf, 0xd6, 0xda, 0xed, 0x5b, 0x26, 0xff, 0xf9, 0x10, 0x10, 0x6e, 0x8a, 0xd7, 0x72, 0xbc, 0x8e, 0xdf, 0xc4, 0xd9, 0x8d, 0x40, 0x99, 0x2b, 0x1c, 0x96, 0xff, 0x83, 0x7f, 0x62, 0xd6, 0xff, 0xc1, 0x5c, 0x68, 0xb6, 0x16, 0x5a, 0xde, 0xd7, 0x9b, 0xe5, 0xe7, 0x96, 0x82, 0xfc, 0xf9, 0x36, 0x7d, 0xb7, 0x7c, 0x5c, 0x8a, 0x90, 0x6c, 0x5e, 0x0d, 0x2d, 0xf8, 0xa5, 0xc6, 0x8c, + 0xa4, 0x74, 0xcc, 0xa8, 0x43, 0x89, 0x19, 0x69, 0xe4, 0x98, 0x11, 0x9b, 0x90, 0x63, 0x46, 0xb6, 0x60, 0x5d, 0x00, 0x7f, 0x5b, 0xa7, 0xff, 0xc5, 0x9f, 0x52, 0xdb, 0x75, 0x86, 0x5f, 0xbe, 0x8f, 0x4f, 0xf1, 0x9b, 0xcc, 0x05, 0x86, 0xd4, 0x3b, 0x3b, 0x9b, 0xb5, 0x7e, 0x33, 0xae, 0xd8, 0x01, 0xcf, 0xb8, 0x92, 0xbb, 0x8d, 0x71, 0xf0, 0x4f, 0x21, 0x2d, 0xf2, 0x48, 0x2e, 0xf8, 0x8d, 0xf7, 0x13, 0x63, 0x64, 0x07, 0xd9, 0x3b, 0x14, 0x38, 0xa4, 0xc5, 0x5a, 0x36, 0xab, 0xf8, 0x6e, 0x94, 0xc5, 0x95, 0xc1, 0x62, 0x13, 0x29, 0xb3, 0x6b, 0xfe, 0x2d, 0xff, 0x94, 0xcb, 0xf5, 0x81, 0xd1, 0x4a, 0xd8, 0x05, 0xfa, 0x15, 0x0a, 0x3a, 0x26, 0x26, 0xb6, 0xc1, 0x38, 0xf9, 0x92, 0x83, 0xbc, 0xac, 0x1a, 0xed, 0x03, 0xc0, 0xb6, 0x65, 0x8f, 0x62, 0x23, 0x71, 0x3c, 0x1a, 0x50, 0xf8, 0x5d, 0x71, + 0x28, 0xef, 0xbd, 0xdb, 0x5c, 0x5e, 0x6b, 0xea, 0x0d, 0xb1, 0x4d, 0xef, 0x4a, 0xfd, 0x37, 0xb8, 0x69, 0xf2, 0xbb, 0xb5, 0xb1, 0x0f, 0xe0, 0xf9, 0x16, 0x85, 0xc7, 0x4d, 0x2a, 0x17, 0x61, 0xb4, 0x9f, 0x88, 0xce, 0x1d, 0x98, 0xe0, 0x58, 0x8b, 0x34, 0x41, 0x7a, 0xc6, 0x94, 0x06, 0x06, 0x89, 0x7c, 0x62, 0xbe, 0xf5, 0x5b, 0xb3, 0xcf, 0x75, 0xe5, 0xbd, 0xa6, 0x40, 0x90, 0x7f, 0x2a, 0x95, 0xb2, 0x1a, 0x3f, 0x70, 0xb9, 0xe4, 0x71, 0x74, 0x82, 0x8e, 0x0d, 0x00, 0x3c, 0x7a, 0xe4, 0x92, 0xec, 0xe4, 0xd5, 0x65, 0x78, 0x1f, 0x5c, 0xdf, 0x06, 0x7f, 0x7a, 0xa4, 0xa3, 0xa3, 0x28, 0xc1, 0xbd, 0x36, 0x36, 0xc1, 0x06, 0x52, 0x5f, 0xb3, 0xfa, 0x5c, 0xb7, 0xff, 0xbb, 0xd6, 0x6a, 0xf7, 0x58, 0xc5, 0xb6, 0xd4, 0xbf, 0x9b, 0x34, 0x38, 0xcf, 0x60, 0x14, 0x19, 0x99, 0x0e, 0x2b, 0xb8, 0xbb, + 0x98, 0x37, 0x81, 0x0e, 0x16, 0xe4, 0x95, 0xf2, 0x45, 0x4c, 0x98, 0x8f, 0x10, 0x02, 0xed, 0x07, 0x4a, 0x4c, 0x32, 0x43, 0xd6, 0x52, 0x0b, 0x7d, 0xbb, 0xf8, 0xdc, 0x4a, 0xbe, 0xcc, 0x9b, 0x73, 0xcb, 0xf6, 0xce, 0x29, 0xcf, 0x4b, 0xc6, 0x1e, 0x02, 0xbc, 0xbd, 0x4b, 0xcf, 0x83, 0xbb, 0xc9, 0x29, 0x37, 0x32, 0xb6, 0x7a, 0x0a, 0x1c, 0x6d, 0xb3, 0xc6, 0x33, 0x23, 0xa7, 0x4f, 0x7a, 0x33, 0xef, 0xe6, 0x1e, 0xea, 0xce, 0x3e, 0xc4, 0x3d, 0x17, 0x5e, 0x58, 0xd7, 0x3a, 0xf9, 0x04, 0x28, 0xc1, 0xa4, 0xfc, 0x02, 0x7b, 0x8a, 0x4a, 0x0b, 0x32, 0x5b, 0x69, 0x6c, 0x3a, 0x2d, 0x3f, 0x9c, 0xae, 0xa4, 0xcc, 0xa8, 0x6f, 0xbe, 0xf7, 0xc2, 0x7b, 0x37, 0x1b, 0x5c, 0xe2, 0xd5, 0x5a, 0xf8, 0x77, 0x58, 0x74, 0x19, 0xf8, 0x07, 0x52, 0x7f, 0xc4, 0x7a, 0xad, 0x70, 0x5c, 0x6b, 0x30, 0x68, 0x8f, + 0xf3, 0x3a, 0xfa, 0x8c, 0x1e, 0x05, 0xee, 0x3c, 0x02, 0x37, 0x19, 0x38, 0x83, 0x60, 0x59, 0x36, 0xe9, 0xe6, 0x97, 0x4d, 0xef, 0xfe, 0xee, 0x85, 0xdf, 0x9d, 0x32, 0x06, 0x4d, 0xfb, 0x58, 0x01, 0x1c, 0x46, 0x76, 0x9f, 0x29, 0x68, 0x14, 0xbd, 0xa9, 0xdf, 0x61, 0xab, 0x60, 0x3b, 0x05, 0x8b, 0x82, 0xd3, 0xf2, 0xb7, 0xdb, 0xc9, 0x41, 0xc3, 0x18, 0x77, 0x35, 0x23, 0xf2, 0x6f, 0x20, 0xa3, 0xcc, 0x97, 0x98, 0x1c, 0xfe, 0x43, 0x68, 0x33, 0x06, 0xbe, 0x34, 0xe8, 0x90, 0x11, 0x1b, 0x15, 0xbe, 0x24, 0xda, 0x4a, 0x0e, 0xa7, 0xe1, 0x9f, 0xb9, 0x0c, 0xa2, 0x2d, 0xe0, 0xd1, 0x17, 0x79, 0xbb, 0x9a, 0xf8, 0x37, 0x9c, 0x06, 0x6d, 0xa0, 0x79, 0xac, 0xcb, 0x65, 0x26, 0xf3, 0xc5, 0x68, 0x2b, 0xf7, 0x24, 0xf3, 0x75, 0xfe, 0x0e, 0x90, 0x21, 0x60, 0x87, 0x0b, 0xc4, 0xfe, 0x06, 0x2c, 0xaf, + 0x4f, 0xe7, 0x96, 0x4d, 0xb2, 0x34, 0x65, 0x29, 0xcf, 0x6a, 0x36, 0xea, 0x34, 0x1c, 0x8b, 0xbc, 0xd8, 0x2b, 0xa7, 0x8c, 0x25, 0xeb, 0x13, 0x8a, 0x3d, 0x48, 0x74, 0x73, 0x50, 0x3d, 0x04, 0x9b, 0x2a, 0x0e, 0xe3, 0x83, 0xde, 0xaa, 0xae, 0x78, 0xb0, 0x35, 0x59, 0xed, 0x0c, 0x84, 0xf1, 0x21, 0x6f, 0xcd, 0xa2, 0x78, 0xa8, 0x35, 0x59, 0xc5, 0x27, 0x63, 0x65, 0x35, 0xcb, 0x9a, 0x8b, 0x7c, 0xc5, 0xbe, 0x92, 0xd2, 0xea, 0xe5, 0x4d, 0x01, 0x5f, 0xc0, 0x47, 0xe9, 0x2c, 0x32, 0xef, 0x09, 0xbf, 0x07, 0xdd, 0x96, 0x94, 0x12, 0xf0, 0x7c, 0xd6, 0x49, 0x7d, 0x44, 0x8e, 0x50, 0x1c, 0xad, 0x46, 0x24, 0xa4, 0xcf, 0x32, 0xf4, 0x25, 0x58, 0xc4, 0x3e, 0x45, 0x78, 0x18, 0x23, 0x9f, 0xc7, 0x6a, 0x86, 0xb9, 0x16, 0xe2, 0x42, 0xfe, 0xff, 0x11, 0xf7, 0x1e, 0xe0, 0x51, 0x5c, 0x67, 0xa3, 0xf0, + 0x9c, 0x29, 0xdb, 0x7b, 0xd5, 0xf6, 0xde, 0x57, 0x75, 0xb5, 0xbb, 0xea, 0xbb, 0xea, 0x42, 0x15, 0x15, 0x10, 0x42, 0x20, 0x10, 0x42, 0xa2, 0x09, 0x90, 0x05, 0xc6, 0x14, 0xd3, 0x3b, 0xb6, 0x01, 0x57, 0x8a, 0x71, 0xdc, 0x8d, 0x0b, 0x8e, 0x71, 0x83, 0xb8, 0x24, 0xb1, 0x0d, 0x8e, 0xed, 0x24, 0x4e, 0xc7, 0xce, 0xcd, 0xe7, 0xe4, 0x4b, 0xfb, 0x92, 0x38, 0x71, 0x12, 0xc7, 0x8e, 0x73, 0x1d, 0x83, 0x46, 0xf7, 0x9c, 0x33, 0xbb, 0xab, 0x82, 0xb0, 0xc9, 0xfd, 0xff, 0xe7, 0xb9, 0x8f, 0x8d, 0x76, 0x67, 0xe7, 0xcc, 0x99, 0x53, 0xde, 0xf3, 0xf6, 0x92, 0x4e, 0x79, 0x31, 0x31, 0x16, 0x19, 0xa9, 0x4d, 0x0f, 0x86, 0x94, 0xe9, 0x9d, 0x7a, 0xc9, 0x4d, 0x86, 0xbc, 0xba, 0x9c, 0x9c, 0x86, 0xf2, 0x98, 0x49, 0xef, 0xd4, 0x49, 0x36, 0x19, 0xf2, 0xeb, 0xb2, 0x73, 0x1b, 0xca, 0xa3, 0xbc, 0x83, + 0x4a, 0xb3, 0x4b, 0x95, 0xdd, 0x91, 0x80, 0x94, 0xc6, 0xa1, 0xb2, 0xb8, 0x95, 0xd9, 0x9d, 0x49, 0xaf, 0xc3, 0x67, 0x47, 0x3c, 0xdd, 0x3c, 0xfa, 0x6e, 0xf2, 0x6d, 0x66, 0x37, 0xce, 0x13, 0x5a, 0xc8, 0x29, 0x06, 0xa4, 0x93, 0x12, 0x74, 0x22, 0x89, 0x4a, 0x4d, 0x21, 0x40, 0x04, 0xdd, 0x14, 0x48, 0x6d, 0x01, 0x20, 0xda, 0x7a, 0xce, 0xa1, 0x8c, 0x9a, 0x9c, 0x82, 0xfa, 0x2b, 0x32, 0x6a, 0xd6, 0x69, 0x70, 0x7e, 0x4c, 0x94, 0x0d, 0x13, 0xe7, 0xc7, 0xd4, 0x80, 0x5d, 0xd3, 0xf3, 0x63, 0x7e, 0x65, 0xda, 0x4b, 0x8e, 0xa7, 0x6f, 0xe0, 0x29, 0xc8, 0x9f, 0xf3, 0x3e, 0xc7, 0x63, 0xb5, 0x24, 0x8d, 0x33, 0x0c, 0xeb, 0xba, 0x07, 0x95, 0x87, 0x32, 0x42, 0xeb, 0x03, 0x36, 0xb5, 0xda, 0x16, 0xd0, 0xa3, 0x0c, 0xd1, 0xe0, 0x71, 0x38, 0x3c, 0x3d, 0x1c, 0x26, 0x7c, 0x6d, 0x30, 0x4b, 0x0f, + 0x87, 0xcb, 0xdb, 0x95, 0x15, 0x40, 0x42, 0x5c, 0x60, 0x22, 0x83, 0xf4, 0xd4, 0x6b, 0x7c, 0x06, 0xd6, 0xd0, 0xdf, 0x23, 0x9f, 0x63, 0x1a, 0xe1, 0xe6, 0x41, 0x9c, 0x87, 0x90, 0x0e, 0x31, 0x27, 0x85, 0x91, 0x09, 0xd0, 0xa2, 0x54, 0xa0, 0xe8, 0x58, 0xb5, 0x43, 0xeb, 0x58, 0x43, 0xd6, 0x8c, 0xbd, 0xca, 0x34, 0x6e, 0xc7, 0xcf, 0xac, 0xe7, 0xe5, 0x91, 0x67, 0x78, 0x0f, 0x7f, 0xd5, 0x33, 0xeb, 0xc9, 0xa2, 0xb1, 0xb7, 0x79, 0x0f, 0xaf, 0xc3, 0xcf, 0xdc, 0x04, 0x71, 0xe2, 0x13, 0xf0, 0x3c, 0x6b, 0xd1, 0x79, 0x26, 0x48, 0x04, 0x33, 0xc4, 0x22, 0xbc, 0x45, 0xe8, 0x38, 0x20, 0x8b, 0x26, 0x8f, 0x21, 0xb4, 0x40, 0xcb, 0xa4, 0x0d, 0xcf, 0x91, 0x54, 0x26, 0x14, 0x0c, 0x2f, 0xe0, 0x68, 0xdb, 0xaa, 0x2a, 0xcb, 0x21, 0xa9, 0x23, 0x1e, 0x0c, 0xc6, 0x1d, 0xd2, 0x43, 0x96, 0xaa, 0x55, + 0xf4, 0xad, 0xa1, 0xa6, 0x25, 0x45, 0xf6, 0x98, 0x4f, 0xab, 0xf5, 0xc5, 0xec, 0x45, 0x4b, 0x9a, 0xb8, 0xf9, 0x2c, 0xe4, 0x09, 0xc9, 0x37, 0xf8, 0x6a, 0x28, 0x1c, 0xa7, 0x35, 0xdb, 0xa3, 0xf0, 0x4d, 0x04, 0xd9, 0x9d, 0x8e, 0x55, 0x25, 0x09, 0x08, 0x96, 0x72, 0x19, 0x7e, 0x9f, 0x12, 0x28, 0xa7, 0xbd, 0x2f, 0x06, 0x3f, 0x79, 0xe0, 0x68, 0xc3, 0x68, 0x5b, 0xf0, 0x1e, 0x85, 0xa7, 0x2c, 0xdc, 0x5d, 0x7b, 0xcf, 0x43, 0x3c, 0xa1, 0xbd, 0x6a, 0x69, 0x8d, 0x2b, 0xe6, 0xd1, 0xb4, 0xd4, 0x6e, 0xbf, 0x05, 0x76, 0xf0, 0x06, 0xbd, 0x82, 0x5c, 0xc4, 0xfc, 0x01, 0xc7, 0x55, 0x56, 0x70, 0xf6, 0x41, 0x15, 0x31, 0x49, 0x97, 0x0f, 0x07, 0x32, 0x88, 0xd8, 0xe6, 0xac, 0x74, 0x14, 0x34, 0x3c, 0x18, 0x00, 0x8f, 0x00, 0x9d, 0x0b, 0x12, 0x40, 0xe8, 0x73, 0x7b, 0xfd, 0x93, 0x3c, 0x1c, 0x27, + 0x04, 0xb6, 0x38, 0xe6, 0xb5, 0x38, 0xc5, 0xd2, 0x1b, 0x8e, 0xa2, 0xa6, 0x40, 0xb0, 0xa9, 0xd8, 0xe9, 0x2c, 0x6e, 0x0a, 0x06, 0x9a, 0x8a, 0x1c, 0xab, 0x0b, 0x73, 0xc2, 0xd1, 0x68, 0x38, 0xa7, 0x90, 0xf1, 0x86, 0xa0, 0x18, 0x60, 0x8e, 0xd4, 0x85, 0x82, 0x75, 0xf9, 0x66, 0x73, 0x7e, 0x5d, 0x30, 0x14, 0x8b, 0x85, 0xc2, 0xb1, 0x18, 0x07, 0x67, 0x3f, 0x60, 0x3e, 0x21, 0x3b, 0xf8, 0x3d, 0x78, 0x7c, 0x90, 0xda, 0x5f, 0x6b, 0x14, 0xd7, 0x33, 0x86, 0x1f, 0xd8, 0xa2, 0x75, 0x5e, 0x7f, 0x7d, 0xcc, 0x66, 0x8b, 0xd5, 0xfb, 0xbd, 0x75, 0x51, 0xdb, 0xe2, 0xc2, 0x5c, 0xf4, 0xae, 0xdc, 0x42, 0xde, 0xc3, 0xfe, 0xea, 0x3c, 0x93, 0x29, 0xaf, 0xda, 0xef, 0xab, 0xca, 0x31, 0x1a, 0x73, 0xaa, 0x7c, 0xc1, 0x78, 0x3c, 0x98, 0xcd, 0x8d, 0x81, 0x82, 0x6b, 0x34, 0x48, 0x7e, 0xce, 0xaf, 0xc2, + 0x63, 0x08, 0x20, 0xd9, 0xc3, 0x8c, 0x4a, 0x7d, 0x42, 0x29, 0x09, 0x05, 0x66, 0xd2, 0x04, 0x3d, 0x3a, 0xd3, 0x82, 0xb9, 0x26, 0xdf, 0xbf, 0xd6, 0xda, 0x25, 0x65, 0x76, 0xbb, 0x3d, 0x60, 0xf7, 0x7b, 0xd0, 0xf8, 0x51, 0x24, 0xd4, 0x97, 0x8d, 0x7f, 0x82, 0xcc, 0x5e, 0x7b, 0x35, 0xc1, 0x3b, 0x7f, 0x95, 0x5b, 0xf4, 0x37, 0xde, 0x29, 0x73, 0xba, 0x18, 0x67, 0xa8, 0xbe, 0xc0, 0x6c, 0x2e, 0xa8, 0xcf, 0x2c, 0x2c, 0x9e, 0x54, 0x34, 0x0a, 0x3e, 0x65, 0xc7, 0x14, 0x1c, 0x65, 0xa6, 0x88, 0xf7, 0x99, 0x9f, 0x52, 0x41, 0x41, 0x12, 0xcf, 0x2d, 0x44, 0x14, 0x26, 0xf3, 0xaf, 0x63, 0xd8, 0x70, 0xcc, 0x21, 0x7b, 0xf0, 0xfa, 0xc6, 0xcc, 0x70, 0x63, 0xa6, 0x22, 0xef, 0x5b, 0x0b, 0xaa, 0x3c, 0x9e, 0x9a, 0x02, 0xab, 0xb5, 0xa0, 0xc6, 0xe3, 0xa9, 0x2a, 0xb0, 0xce, 0x2f, 0x08, 0x07, 0xa3, + 0xd1, 0x60, 0xb8, 0x00, 0xfc, 0x9b, 0xfd, 0x85, 0xd2, 0xa2, 0xbf, 0xf5, 0x82, 0x50, 0x01, 0xc9, 0x3d, 0xef, 0x66, 0x6f, 0x32, 0xdb, 0x60, 0xc8, 0x4e, 0x7a, 0xbd, 0x15, 0x61, 0x83, 0x21, 0x5c, 0xe1, 0x0d, 0x44, 0xa3, 0x81, 0x50, 0x24, 0x02, 0x4e, 0xfc, 0x54, 0x26, 0x78, 0x8b, 0xe3, 0x02, 0x48, 0xe2, 0x39, 0xfa, 0x56, 0xaa, 0x0d, 0xf3, 0x25, 0x5a, 0xc2, 0x9d, 0x74, 0x70, 0x68, 0x92, 0xa1, 0x68, 0x78, 0x4c, 0x20, 0xb3, 0x24, 0x82, 0x44, 0x51, 0xa4, 0x15, 0x69, 0xe4, 0x52, 0xcc, 0xa4, 0xf0, 0xa6, 0x30, 0x29, 0x0e, 0x2a, 0x3d, 0x26, 0x52, 0xf2, 0x96, 0xd4, 0xaa, 0x6f, 0xdf, 0x2f, 0x75, 0xb9, 0xc0, 0x85, 0xcf, 0x20, 0xe7, 0xd2, 0xf7, 0x82, 0x40, 0xae, 0xca, 0x52, 0x66, 0x78, 0x17, 0xfc, 0x21, 0x56, 0x4a, 0x78, 0x18, 0x16, 0x4f, 0x41, 0x1e, 0x66, 0x37, 0xa4, 0xb1, 0x28, + 0xa6, 0x37, 0x9c, 0x0c, 0xe0, 0xb7, 0x21, 0xac, 0xc7, 0xbd, 0x36, 0x8d, 0xf7, 0xf0, 0xbb, 0xd5, 0x22, 0xd5, 0xe4, 0x77, 0xf3, 0x33, 0xef, 0xce, 0x7c, 0x23, 0x85, 0x3f, 0x50, 0x98, 0xf4, 0x0b, 0x6f, 0x97, 0x85, 0x5c, 0xe0, 0x8b, 0xcb, 0x4a, 0xbb, 0x7e, 0xe5, 0x4b, 0xaa, 0xb0, 0x8b, 0x6f, 0xfa, 0x54, 0xa6, 0xf8, 0x9d, 0x5e, 0xff, 0xa9, 0x5c, 0xfe, 0x3b, 0x5d, 0x16, 0xe2, 0x9b, 0xe8, 0xe7, 0xa0, 0x9c, 0xff, 0x1a, 0xca, 0xbb, 0x80, 0xe3, 0xe7, 0x39, 0x37, 0x19, 0xc8, 0x60, 0xbb, 0x71, 0xb0, 0xff, 0x34, 0x5c, 0x4a, 0xea, 0xa7, 0xe7, 0x4f, 0x61, 0x8a, 0x32, 0xd9, 0x1e, 0x0a, 0xd0, 0x67, 0x01, 0x7c, 0x5c, 0xce, 0x33, 0x92, 0x4a, 0xbe, 0x05, 0xf6, 0x89, 0x70, 0x1e, 0x9e, 0x42, 0xca, 0x75, 0x19, 0xa1, 0xed, 0x99, 0x3b, 0x56, 0x6a, 0xdd, 0xb9, 0x26, 0x63, 0x8e, 0x53, 0xab, + 0x75, 0xe6, 0x18, 0x4d, 0xb9, 0x6e, 0x2d, 0xef, 0xa4, 0x31, 0xc7, 0xa5, 0xd5, 0xba, 0xb8, 0x2b, 0x78, 0x97, 0xe3, 0x75, 0xfe, 0xce, 0xde, 0x4b, 0xf6, 0x13, 0x66, 0x48, 0xd7, 0xd7, 0x73, 0xd8, 0x44, 0x64, 0x80, 0xc2, 0x22, 0x12, 0x9a, 0xb0, 0x8c, 0x9e, 0xba, 0x20, 0x33, 0x11, 0x87, 0x2a, 0x02, 0x2e, 0x1d, 0x35, 0x34, 0xa5, 0xfa, 0x7c, 0x2a, 0x90, 0x0b, 0xde, 0xa1, 0x46, 0xa7, 0x96, 0xa5, 0xd7, 0xe2, 0xdf, 0x08, 0x0e, 0x13, 0x4e, 0x54, 0x9e, 0xef, 0x39, 0xa7, 0x73, 0x6b, 0x7c, 0xe8, 0xfc, 0x33, 0xd7, 0x0a, 0x1f, 0x88, 0x80, 0xe7, 0x67, 0x74, 0xeb, 0x8f, 0xaf, 0xad, 0x9f, 0xc1, 0xdf, 0x9e, 0xcb, 0x73, 0xf9, 0x6b, 0xf6, 0x49, 0xb2, 0x1d, 0x72, 0x3c, 0x06, 0x62, 0x3e, 0x37, 0x24, 0x31, 0x85, 0x8d, 0xc4, 0x52, 0x09, 0xa4, 0x7f, 0x26, 0x7c, 0x45, 0xa6, 0xae, 0x52, 0xf1, + 0x64, 0x52, 0x92, 0x22, 0x53, 0x7b, 0xb4, 0x1c, 0x79, 0xc7, 0x2b, 0x51, 0x70, 0x25, 0x31, 0x9a, 0xce, 0x76, 0xb6, 0x1c, 0x3b, 0x7d, 0x40, 0x01, 0x4a, 0xc3, 0x67, 0x08, 0x03, 0x30, 0xd0, 0xc8, 0xeb, 0x6f, 0x72, 0xae, 0x63, 0x7d, 0xc6, 0x55, 0x82, 0xb4, 0xa5, 0x73, 0x17, 0xf7, 0xd4, 0x0f, 0x4e, 0x38, 0x46, 0xbc, 0xcb, 0xe5, 0x23, 0x2e, 0x5f, 0xed, 0x97, 0x4e, 0x38, 0x40, 0x20, 0xb2, 0xe5, 0x66, 0xb7, 0x80, 0xbf, 0x8f, 0x3f, 0x04, 0x47, 0x1b, 0xe7, 0x82, 0xff, 0x25, 0x64, 0xda, 0xa9, 0x0a, 0x07, 0xfa, 0xab, 0x10, 0x70, 0x42, 0xce, 0x86, 0x9c, 0xe0, 0x68, 0x7a, 0x92, 0xe2, 0xc9, 0xcc, 0x1b, 0x33, 0x85, 0x79, 0xfb, 0x3e, 0xe4, 0x33, 0x65, 0x46, 0x8d, 0xd8, 0x6e, 0xac, 0x2d, 0xe8, 0xd3, 0x49, 0x44, 0x8e, 0x68, 0x53, 0x21, 0xe4, 0xdd, 0x7c, 0xe8, 0x5d, 0x45, 0xf4, 0x01, + 0x28, 0xa3, 0x20, 0x9a, 0x99, 0x82, 0x49, 0x24, 0xf3, 0x40, 0xf6, 0x84, 0x23, 0x97, 0xc0, 0xa5, 0x8e, 0x80, 0xdf, 0x9f, 0x59, 0xb2, 0x85, 0x3e, 0x00, 0x4c, 0x78, 0x1d, 0x9f, 0x25, 0x08, 0x3a, 0x04, 0xe9, 0x25, 0xc3, 0xc9, 0x10, 0x04, 0xd9, 0x03, 0x17, 0x24, 0x84, 0xd4, 0x4a, 0x61, 0x94, 0x99, 0x48, 0x41, 0xf1, 0x21, 0x4f, 0x8d, 0x9d, 0xf3, 0xe8, 0xd0, 0xd8, 0x82, 0xf7, 0xa8, 0xcd, 0xf4, 0x67, 0x97, 0xc5, 0xb0, 0x3d, 0x3a, 0x73, 0x50, 0xe2, 0x64, 0xca, 0xe1, 0x77, 0x1c, 0xf9, 0x9f, 0x34, 0x33, 0x29, 0x8e, 0x1c, 0x3d, 0x0e, 0xbb, 0x9e, 0xcf, 0xf5, 0xa1, 0x35, 0xe1, 0x3e, 0xe2, 0x0e, 0x00, 0xa5, 0x5e, 0x19, 0xe0, 0x03, 0x1d, 0x97, 0xd4, 0x38, 0x0e, 0x22, 0x9f, 0x51, 0xdd, 0xec, 0x41, 0xb1, 0x51, 0x2c, 0x36, 0x4a, 0xc9, 0xb3, 0xc6, 0x2f, 0x3e, 0x90, 0x48, 0x49, 0xf9, + 0xcf, 0x9f, 0xa2, 0x4e, 0x58, 0x67, 0xe7, 0xe6, 0xb5, 0xd9, 0xaf, 0xdc, 0x9c, 0x4c, 0x24, 0xca, 0x2d, 0x49, 0x07, 0xfd, 0x1b, 0x38, 0x9b, 0x9e, 0xf1, 0x7f, 0x30, 0x7f, 0x81, 0x38, 0x05, 0xe9, 0xf0, 0x43, 0x58, 0xc6, 0xc3, 0xe6, 0x5b, 0xd0, 0x9f, 0x4e, 0x62, 0x13, 0xc9, 0x98, 0x63, 0x0a, 0xc9, 0x66, 0xc8, 0xdf, 0x78, 0x94, 0x0a, 0x1e, 0xdf, 0x88, 0x6b, 0x16, 0x7b, 0x34, 0x24, 0x8f, 0x76, 0x39, 0xdd, 0x5e, 0x78, 0x64, 0x54, 0x31, 0x77, 0xa4, 0x80, 0xd6, 0xa9, 0xf8, 0x85, 0x53, 0x4b, 0x13, 0x93, 0x12, 0x32, 0x34, 0x76, 0x89, 0xfa, 0xef, 0x65, 0xaf, 0xb2, 0x5f, 0x3c, 0x70, 0x3f, 0xfb, 0xef, 0x57, 0x96, 0x2f, 0x7f, 0x05, 0xf0, 0xef, 0x7f, 0x00, 0xf0, 0x5e, 0x5d, 0xf6, 0x8b, 0xba, 0x9d, 0xe7, 0x6f, 0xb8, 0xe1, 0xfc, 0xae, 0xba, 0xba, 0x5d, 0xe8, 0x73, 0x67, 0x1d, 0x15, + 0x7a, 0x7a, 0xf6, 0x83, 0xec, 0xa7, 0x17, 0x86, 0x87, 0x2f, 0x00, 0xe9, 0x83, 0x0f, 0x00, 0xd9, 0x85, 0xd5, 0xab, 0x2f, 0xb0, 0x9f, 0x3c, 0xb0, 0xe3, 0xfb, 0x87, 0x9b, 0x9b, 0x0f, 0x7f, 0x7f, 0xc7, 0xce, 0xef, 0x1d, 0x6e, 0x6a, 0x3a, 0xfc, 0x3d, 0xb4, 0xbe, 0xc8, 0x9c, 0xd5, 0x86, 0xf5, 0x13, 0x90, 0x4e, 0x0b, 0x26, 0x56, 0x08, 0xf1, 0x64, 0x61, 0xb8, 0x2d, 0x61, 0x3b, 0x5a, 0x1d, 0x47, 0x2a, 0x21, 0x09, 0x5e, 0x20, 0xb4, 0xc9, 0x20, 0xc2, 0xb4, 0x9d, 0x33, 0xbb, 0x84, 0xec, 0xcd, 0x32, 0x93, 0x58, 0x94, 0x25, 0x63, 0xb7, 0xf2, 0x3d, 0xa6, 0xfb, 0xc9, 0x86, 0x6f, 0x9b, 0x75, 0xd4, 0x8f, 0xdc, 0x73, 0x73, 0x73, 0xe7, 0xba, 0xaf, 0xe4, 0xa9, 0x6d, 0x68, 0x23, 0x28, 0xa2, 0x13, 0xee, 0xe1, 0x21, 0xf8, 0x95, 0x22, 0xf8, 0x50, 0x62, 0x2b, 0x4a, 0x46, 0x21, 0x85, 0x80, + 0x78, 0x8f, 0xe9, 0xc7, 0xfb, 0xcf, 0x83, 0xa7, 0x34, 0xd4, 0x84, 0x0a, 0xc7, 0xe0, 0xbd, 0x0d, 0x93, 0xcd, 0x34, 0x0d, 0x25, 0x3b, 0x91, 0x50, 0x40, 0xf3, 0x69, 0x3e, 0xe4, 0x4e, 0x90, 0x4a, 0x5b, 0xc0, 0x87, 0x98, 0x10, 0x4a, 0xc2, 0xe8, 0x1f, 0xe2, 0xa9, 0xe8, 0x43, 0x97, 0xdf, 0xa1, 0xe3, 0x97, 0xdf, 0xf9, 0x1c, 0xf3, 0x63, 0xa7, 0x9e, 0x7f, 0xfe, 0xb2, 0xfe, 0xf9, 0xe7, 0xc9, 0x6d, 0x2f, 0xa5, 0x61, 0x86, 0x99, 0x85, 0xed, 0xfa, 0x88, 0xf7, 0x48, 0xe9, 0x9b, 0x42, 0x58, 0x33, 0x8f, 0xa6, 0x04, 0xa1, 0x86, 0xe6, 0x67, 0x71, 0x7e, 0x63, 0xf0, 0x1f, 0x33, 0xeb, 0xd2, 0x98, 0xfa, 0x3d, 0xfa, 0x78, 0x1a, 0x70, 0x00, 0x51, 0x09, 0xc7, 0xbb, 0x11, 0x7e, 0xf5, 0x20, 0xfe, 0x1e, 0xa9, 0xde, 0xac, 0x70, 0x35, 0xd4, 0xd8, 0xd8, 0x8a, 0x98, 0xfa, 0x54, 0x77, 0x48, 0xa3, + 0x9b, 0x1a, 0x30, 0x20, 0xdc, 0x2e, 0x43, 0x96, 0x5e, 0x4b, 0x78, 0x80, 0x87, 0xe1, 0x23, 0x46, 0xca, 0xeb, 0x43, 0x69, 0x5c, 0xac, 0x94, 0x56, 0x87, 0x2b, 0x84, 0xf2, 0x11, 0xfa, 0xe6, 0x04, 0x79, 0x21, 0x40, 0xd6, 0x40, 0x87, 0x16, 0xbc, 0x9d, 0xdf, 0xea, 0x0b, 0xd5, 0x58, 0x80, 0x5c, 0xa9, 0x95, 0xd0, 0x40, 0xeb, 0x62, 0xbf, 0x6b, 0x0d, 0x6a, 0xbd, 0xd5, 0x16, 0xf2, 0x9d, 0xcb, 0xef, 0xe8, 0x94, 0xe0, 0x47, 0xac, 0x53, 0x2c, 0xa3, 0xe3, 0xf4, 0x67, 0x7c, 0x81, 0xbe, 0xd0, 0xc9, 0x3e, 0xa3, 0xb1, 0x64, 0x69, 0x84, 0xfa, 0x02, 0x3f, 0xbb, 0xcf, 0xe5, 0x97, 0x32, 0xcc, 0xe9, 0xd9, 0x59, 0xb9, 0x2a, 0xd2, 0x45, 0xc6, 0x25, 0x76, 0xe5, 0x82, 0xd3, 0x1c, 0xaf, 0x51, 0x3d, 0x7e, 0x98, 0xd7, 0xc0, 0x93, 0x11, 0x22, 0x48, 0xd7, 0x72, 0xf1, 0x3a, 0x0b, 0x69, 0xe1, 0x28, + 0xc1, 0x63, 0x78, 0xeb, 0x04, 0x00, 0xd7, 0xfc, 0xa6, 0x01, 0xca, 0x6f, 0xc0, 0x30, 0x44, 0x37, 0xa2, 0x3e, 0x4c, 0x2f, 0x1f, 0x82, 0x28, 0xd3, 0x96, 0x9b, 0x93, 0xa5, 0x54, 0x66, 0xa1, 0xff, 0x14, 0x22, 0xbe, 0x25, 0xe4, 0xe1, 0xc2, 0x9d, 0x7c, 0xd8, 0x3d, 0xd1, 0x4a, 0xea, 0xd5, 0x5c, 0x7d, 0x5e, 0xbe, 0xd6, 0x15, 0xe5, 0x4a, 0x9c, 0xc6, 0xa3, 0x11, 0xea, 0xc9, 0x95, 0x67, 0x36, 0x55, 0x56, 0x6e, 0x3a, 0xb3, 0xf2, 0x9d, 0xcc, 0x97, 0x7b, 0x3f, 0xff, 0xfa, 0xc2, 0x85, 0x5f, 0xff, 0xfc, 0x5e, 0xfc, 0xa5, 0xef, 0xeb, 0x9f, 0x9f, 0x7c, 0xe7, 0x33, 0xd0, 0xbc, 0xf3, 0xcc, 0x92, 0xfe, 0xa7, 0x77, 0x36, 0x93, 0x64, 0xf3, 0xce, 0xa7, 0xfb, 0x97, 0x9c, 0xd9, 0xd9, 0x0c, 0xe8, 0x45, 0x60, 0xd9, 0xd7, 0x3f, 0xdc, 0xb3, 0xe7, 0x4f, 0xcf, 0x0c, 0x81, 0xd7, 0x5f, 0x07, 0x43, + 0xcf, 0xfc, 0x69, 0xcf, 0x9e, 0x0f, 0xbf, 0xbe, 0x0c, 0xbc, 0x8c, 0xf7, 0x6e, 0x31, 0x41, 0xf0, 0xf8, 0x78, 0xed, 0xb1, 0x6c, 0x47, 0x23, 0xd4, 0x43, 0x13, 0x03, 0x5c, 0x26, 0x98, 0xf4, 0xb9, 0x87, 0x2d, 0x3d, 0x84, 0x5b, 0xa9, 0xd4, 0x7b, 0x94, 0x4a, 0xad, 0x90, 0x6f, 0xe2, 0x12, 0x0a, 0xe1, 0xc2, 0x4e, 0x54, 0x54, 0x89, 0x2d, 0x15, 0x68, 0xe0, 0xca, 0x88, 0x12, 0x4e, 0x45, 0x59, 0x18, 0x8b, 0xf0, 0xf8, 0x6d, 0xa7, 0xfe, 0x78, 0xfc, 0xd2, 0xf1, 0x3f, 0xdc, 0xdb, 0x76, 0xd8, 0xe9, 0xc9, 0xee, 0x3f, 0x31, 0x7c, 0x69, 0xf8, 0xe4, 0x92, 0x6c, 0x8f, 0x93, 0x39, 0x35, 0x36, 0xbe, 0xe3, 0x07, 0x77, 0xcf, 0xe1, 0xb1, 0x0f, 0x83, 0x85, 0xbc, 0xb9, 0x77, 0xfd, 0x70, 0x3b, 0x09, 0x82, 0x36, 0xb6, 0x61, 0xe1, 0xed, 0x43, 0xc5, 0x0c, 0x75, 0xe7, 0x95, 0xd5, 0x4c, 0xf1, 0xd0, + 0xed, 0x0b, 0xc1, 0x37, 0x6c, 0x41, 0x38, 0xb6, 0xa5, 0x70, 0xc9, 0x3e, 0x66, 0x9e, 0x22, 0x4a, 0x89, 0xf9, 0x49, 0x89, 0x91, 0x47, 0x52, 0xa0, 0xc8, 0x4d, 0x12, 0xd8, 0x37, 0x0e, 0x45, 0xe7, 0x65, 0xa5, 0x73, 0x6c, 0x44, 0x9a, 0x78, 0x0c, 0x89, 0xf8, 0xb9, 0x14, 0xc4, 0x99, 0x26, 0x6e, 0x45, 0xa7, 0xdd, 0xc2, 0x86, 0x39, 0xd8, 0x61, 0x49, 0xc8, 0x15, 0x0c, 0xba, 0xf8, 0x7c, 0x43, 0xc8, 0x83, 0x8b, 0xc7, 0x46, 0x27, 0x87, 0x9c, 0xc1, 0xa9, 0x68, 0x1d, 0x48, 0x1f, 0xea, 0x40, 0xa5, 0x91, 0xa9, 0xc9, 0xbe, 0xa8, 0x08, 0xb0, 0x98, 0x8f, 0x7d, 0x8e, 0xb1, 0xcf, 0x5a, 0xf7, 0x0f, 0x14, 0xb7, 0xee, 0x3d, 0xdb, 0xdf, 0x7f, 0x76, 0x6f, 0x5b, 0xf1, 0xd2, 0xfd, 0xad, 0x63, 0x9f, 0x39, 0x7c, 0x40, 0xe1, 0x6b, 0xf7, 0xf7, 0x9e, 0x58, 0x9b, 0x58, 0xfd, 0xcd, 0xb1, 0xdb, 0x6f, + 0x1f, 0xfb, 0xe6, 0xea, 0xc4, 0xda, 0x13, 0xbd, 0xfe, 0x76, 0xdf, 0xf3, 0x6b, 0xac, 0xbe, 0xcf, 0x64, 0x7d, 0x47, 0xce, 0x2f, 0xdb, 0x74, 0x6e, 0x73, 0x59, 0xd9, 0xe6, 0x73, 0x9b, 0x96, 0x9d, 0x3f, 0xd2, 0x27, 0xfb, 0xcc, 0x67, 0x5d, 0x23, 0xe0, 0xd9, 0x36, 0x3c, 0xf0, 0x9d, 0x91, 0xbb, 0xfe, 0x85, 0x7a, 0xfa, 0xd7, 0x5d, 0x23, 0xdf, 0x79, 0x60, 0x83, 0x8d, 0x27, 0x48, 0xf9, 0x77, 0x8c, 0x7f, 0x42, 0x9f, 0xa4, 0xc5, 0x84, 0x86, 0x28, 0x27, 0xb6, 0x9d, 0x8f, 0xe6, 0x59, 0x29, 0x44, 0xd1, 0x38, 0x5a, 0x6c, 0xc0, 0x2a, 0x7f, 0x94, 0xa9, 0x39, 0x86, 0xca, 0xb7, 0xa0, 0x90, 0x12, 0xa4, 0xd4, 0x2b, 0xcc, 0x84, 0xfe, 0x5f, 0xa3, 0x41, 0x2e, 0x89, 0xc3, 0xdb, 0x19, 0x26, 0x27, 0x63, 0x34, 0x98, 0x7e, 0x1f, 0x99, 0xfa, 0xb4, 0x5a, 0x6d, 0xb9, 0xb6, 0xcc, 0xed, 0x47, 0x82, + 0xa1, 0x00, 0xa1, 0xcd, 0x89, 0xf2, 0xd1, 0x38, 0x91, 0x17, 0xc0, 0xf9, 0xe7, 0x91, 0xf5, 0x79, 0x5a, 0x16, 0xc2, 0x52, 0xc4, 0xbb, 0xe1, 0x66, 0x54, 0x8e, 0x3c, 0x27, 0xd1, 0x9a, 0x5b, 0xbc, 0xac, 0x35, 0xc7, 0x5f, 0x33, 0xbf, 0x20, 0xbf, 0xbb, 0x36, 0x47, 0x00, 0xf6, 0x55, 0x57, 0xf8, 0x1a, 0xca, 0x73, 0x65, 0xf6, 0xb6, 0xb9, 0xbd, 0xd9, 0x73, 0xee, 0x5c, 0x5d, 0x51, 0xbb, 0xf9, 0x91, 0xc5, 0x2b, 0xef, 0xcb, 0x57, 0x49, 0x7c, 0x1b, 0x62, 0xa4, 0x30, 0xcb, 0x1f, 0xb7, 0x93, 0xdf, 0x75, 0x94, 0xe7, 0x9a, 0x73, 0x16, 0xdd, 0x31, 0xb8, 0x70, 0xdf, 0x1c, 0xbf, 0xa7, 0x65, 0x63, 0x27, 0xfb, 0xad, 0x36, 0x63, 0x50, 0x1b, 0xae, 0xce, 0xcd, 0xaf, 0x0b, 0x6b, 0x6b, 0x77, 0xbd, 0xb2, 0xe1, 0xe6, 0xef, 0x1e, 0x6e, 0xe9, 0xae, 0x5a, 0xe4, 0x1b, 0xfb, 0x43, 0x7e, 0x5f, 0x20, + 0x58, 0xea, 0x51, 0x71, 0xeb, 0x75, 0x80, 0x3d, 0x46, 0xeb, 0x20, 0xbc, 0x58, 0x88, 0x62, 0xe2, 0x44, 0x52, 0x06, 0xb1, 0x26, 0x89, 0x88, 0x67, 0x1c, 0x1e, 0x3a, 0x2a, 0x05, 0x33, 0x4e, 0xc4, 0xfe, 0x43, 0xa4, 0x3b, 0x90, 0x36, 0x72, 0x46, 0x32, 0x45, 0xc2, 0x0b, 0x51, 0xda, 0xe2, 0xbc, 0x4c, 0xaa, 0x92, 0xab, 0x5a, 0x46, 0xaf, 0x6e, 0x09, 0x1b, 0xa1, 0xc3, 0xfe, 0x25, 0x2d, 0x71, 0x71, 0x0d, 0x49, 0x71, 0x51, 0xac, 0x30, 0xe0, 0x75, 0xf8, 0x9d, 0x08, 0xe0, 0x52, 0x4e, 0x15, 0x08, 0xdc, 0x42, 0x20, 0x1a, 0x49, 0x67, 0xe3, 0x84, 0x8b, 0xeb, 0x9b, 0xf0, 0xae, 0xe0, 0xa7, 0x6b, 0x74, 0xfb, 0x94, 0x2e, 0xd0, 0x14, 0x5c, 0xd6, 0x3a, 0x6b, 0x4d, 0xa3, 0x27, 0xbf, 0x73, 0x6d, 0x45, 0x85, 0xaf, 0x68, 0x41, 0x95, 0x7b, 0x64, 0xa0, 0x74, 0x6e, 0x91, 0xe9, 0x42, 0xe1, 0x8a, + 0x07, 0x87, 0x87, 0xee, 0x5a, 0x9c, 0x3b, 0x3a, 0x34, 0x6b, 0xb4, 0x2d, 0x90, 0x58, 0xff, 0xc8, 0xe0, 0x53, 0x4b, 0x2f, 0x00, 0xa2, 0xac, 0x24, 0xb7, 0x63, 0x75, 0xa2, 0x79, 0x79, 0xc2, 0x7c, 0xaf, 0xbe, 0xb0, 0xa3, 0x6c, 0xe4, 0x86, 0xd0, 0xe2, 0x63, 0xc3, 0xbc, 0x1d, 0x6f, 0x1d, 0x6c, 0x28, 0x5d, 0xb2, 0xbd, 0x66, 0xe4, 0x7e, 0x97, 0xaa, 0xa6, 0x7f, 0x53, 0x5d, 0xf7, 0xd7, 0x36, 0xd4, 0xad, 0x61, 0xe3, 0x10, 0x8f, 0xad, 0x86, 0x70, 0xe6, 0x61, 0x9e, 0x26, 0xe4, 0x78, 0xe5, 0x56, 0x25, 0x45, 0x16, 0x80, 0x92, 0x69, 0x71, 0xc6, 0x65, 0x04, 0x6c, 0x6e, 0xe4, 0xa6, 0x03, 0x8f, 0xdf, 0x08, 0x9e, 0x27, 0xb1, 0x06, 0x62, 0xe1, 0x54, 0x0a, 0xde, 0x34, 0x0c, 0x0d, 0x23, 0xcb, 0xdb, 0x57, 0x37, 0xc3, 0x95, 0xd7, 0x85, 0x7e, 0xbf, 0x36, 0xa0, 0x77, 0xa3, 0xcc, 0x1a, 0xa9, + 0x5c, 0x2b, 0x70, 0x2d, 0x80, 0x93, 0x8f, 0xc3, 0x3e, 0xb9, 0x24, 0x0a, 0x9e, 0xc9, 0x25, 0xca, 0x55, 0x99, 0x94, 0x2b, 0x20, 0x44, 0xf9, 0xea, 0x06, 0x2b, 0x93, 0xc3, 0xad, 0xe1, 0xdc, 0x9e, 0x1d, 0xb3, 0x79, 0xcd, 0x43, 0xe5, 0xc6, 0xd6, 0xd6, 0xca, 0x75, 0xbd, 0x65, 0x42, 0xf0, 0xc3, 0xba, 0x35, 0xcd, 0xfe, 0x96, 0xbd, 0xcf, 0x0f, 0x6d, 0xf8, 0xfa, 0x68, 0xbc, 0xf3, 0xae, 0xef, 0x6d, 0x9c, 0xb7, 0xa5, 0xc5, 0xb5, 0x9c, 0x1c, 0xdc, 0xfa, 0xfc, 0xba, 0x58, 0x64, 0xc1, 0xce, 0xd6, 0xd6, 0x91, 0x12, 0xe7, 0x09, 0x5b, 0x7d, 0x73, 0x9b, 0xaf, 0xb6, 0xb5, 0x0c, 0x12, 0xe4, 0x01, 0xaa, 0x64, 0xe9, 0x81, 0xd9, 0x37, 0xbe, 0xbc, 0xab, 0x76, 0xc1, 0x53, 0xff, 0x3c, 0xfe, 0x1d, 0x90, 0xf3, 0xfd, 0xe5, 0x91, 0x8e, 0x65, 0x85, 0xf3, 0xee, 0xe0, 0x78, 0xe3, 0x00, 0x7b, 0x86, + 0xce, 0x86, 0x74, 0x11, 0xdb, 0x6f, 0x91, 0x4f, 0x82, 0x49, 0xc0, 0x50, 0x68, 0x45, 0xb0, 0x61, 0x11, 0x45, 0x39, 0x90, 0xb1, 0x26, 0x5c, 0xdd, 0x00, 0x61, 0x47, 0x17, 0xe1, 0xf4, 0xf8, 0x74, 0x0c, 0xe2, 0x8d, 0x64, 0x14, 0xc6, 0x8b, 0x4c, 0x28, 0xe5, 0x69, 0x61, 0x05, 0x2a, 0x7d, 0x05, 0x85, 0xa4, 0x08, 0x3a, 0xdb, 0x3c, 0x77, 0x78, 0x7b, 0xcd, 0x05, 0x5d, 0x5e, 0xbc, 0xbc, 0xb6, 0x2d, 0xec, 0x15, 0xe8, 0x7c, 0xb6, 0xbe, 0x23, 0xc3, 0x2d, 0xa6, 0x9c, 0xd6, 0x44, 0xbe, 0x8a, 0xe7, 0xd4, 0x50, 0x9f, 0x6f, 0xf9, 0xd7, 0x47, 0xbf, 0x1e, 0x64, 0xe7, 0x82, 0xaf, 0xaf, 0x7c, 0xfb, 0x99, 0xdb, 0x97, 0x17, 0xcf, 0x92, 0x9b, 0x34, 0x92, 0xe5, 0x6f, 0xb2, 0xbf, 0x3d, 0xdf, 0xfc, 0xe4, 0xf3, 0xe7, 0x7b, 0xbc, 0x26, 0x0e, 0xce, 0x9b, 0xc7, 0x3f, 0xa5, 0x79, 0xcc, 0x63, 0x90, + 0xcb, 0xaa, 0x21, 0x96, 0x27, 0xc5, 0x35, 0x80, 0xa4, 0xa3, 0x22, 0x72, 0x62, 0xbb, 0xac, 0xc8, 0x8f, 0x02, 0x6e, 0xc0, 0x48, 0xda, 0xd7, 0x27, 0xd4, 0x84, 0xbc, 0xec, 0x30, 0x5b, 0x54, 0x88, 0xe4, 0xdd, 0x2f, 0x6b, 0x90, 0x8b, 0xfc, 0xbe, 0x95, 0x90, 0x28, 0x57, 0xe4, 0x84, 0x2d, 0x26, 0xbd, 0x0e, 0x9b, 0x77, 0xf9, 0x5c, 0x11, 0x79, 0x4c, 0xa6, 0x38, 0x2a, 0x35, 0xd9, 0x25, 0x6b, 0x5a, 0x8a, 0xd8, 0x0c, 0xc2, 0xd4, 0x93, 0xa1, 0x8a, 0x65, 0x8d, 0x81, 0x9c, 0xe6, 0x81, 0x58, 0xd1, 0x50, 0x6b, 0xae, 0xb3, 0x76, 0x39, 0xb8, 0x8f, 0x32, 0x18, 0x64, 0xc9, 0xce, 0x05, 0x81, 0xae, 0xba, 0xd8, 0xfc, 0xd1, 0x0d, 0xa3, 0xf3, 0x63, 0x55, 0x37, 0x9f, 0x5d, 0x3d, 0xfa, 0xdc, 0x96, 0xe4, 0x73, 0xcf, 0x65, 0xcf, 0x2e, 0x75, 0xf9, 0xe7, 0xee, 0x5e, 0xe0, 0xea, 0xe9, 0x5b, 0xe8, + 0x3b, 0x18, 0x6a, 0x5f, 0x3f, 0xab, 0x6d, 0xd3, 0xa2, 0x26, 0xbb, 0x36, 0xb1, 0x70, 0x5b, 0x47, 0xf5, 0xc6, 0xf9, 0x51, 0x7a, 0xbb, 0x51, 0x29, 0x57, 0xca, 0x23, 0x6e, 0x67, 0x69, 0x49, 0xa2, 0x69, 0xe1, 0xc6, 0x63, 0x8b, 0x97, 0x9f, 0xb9, 0xb9, 0xb6, 0x61, 0xc7, 0xd3, 0x83, 0xa7, 0xff, 0x92, 0x30, 0x15, 0x55, 0xb5, 0x15, 0x14, 0xce, 0xab, 0xf4, 0x30, 0x22, 0x99, 0x10, 0xee, 0x5f, 0xc7, 0xf8, 0xa7, 0xd4, 0x02, 0x9c, 0x4f, 0x63, 0x5f, 0x52, 0x84, 0xbc, 0x38, 0xa5, 0xb8, 0xf4, 0x36, 0xb7, 0x40, 0xd9, 0x04, 0x09, 0xc5, 0x40, 0x92, 0x1e, 0xc9, 0x14, 0x2b, 0x87, 0x0c, 0x12, 0x9f, 0x66, 0x06, 0xd2, 0x0e, 0xd4, 0x31, 0x9c, 0xc2, 0xbc, 0x17, 0xd7, 0xbb, 0xc4, 0x09, 0x5d, 0xaf, 0xbb, 0x7d, 0x2e, 0xa2, 0x31, 0xaa, 0xb2, 0x92, 0x78, 0x34, 0x37, 0x3b, 0x1c, 0x74, 0x3b, 0x6d, + 0x96, 0x2c, 0x9d, 0x1b, 0x71, 0x53, 0x9e, 0x34, 0xa6, 0x9c, 0x58, 0xa1, 0x54, 0xf1, 0x87, 0x4c, 0xa4, 0x73, 0x29, 0xe4, 0x4a, 0xd2, 0x96, 0x59, 0x2e, 0x30, 0x6c, 0x81, 0x6b, 0x5e, 0xff, 0x60, 0x7e, 0xe7, 0x5d, 0x23, 0x95, 0x43, 0x73, 0xb2, 0x2b, 0xfc, 0xaa, 0x82, 0x81, 0xbb, 0x07, 0x46, 0x1f, 0x29, 0x0e, 0x8b, 0xd5, 0x32, 0x99, 0x27, 0xde, 0x5e, 0x1c, 0x6d, 0x8b, 0x1a, 0x37, 0xae, 0x7e, 0xe4, 0x91, 0x2d, 0x37, 0x59, 0x8b, 0x3b, 0x0a, 0x93, 0x73, 0x4b, 0x7c, 0xea, 0x2c, 0xe9, 0xed, 0xa1, 0x32, 0xaf, 0x32, 0xd8, 0x76, 0x43, 0x7d, 0xf7, 0x66, 0xbb, 0x2a, 0x52, 0x56, 0x69, 0x8b, 0x0d, 0x34, 0x67, 0xd7, 0xc4, 0x9a, 0x94, 0x62, 0xbd, 0x59, 0xaf, 0xf3, 0x46, 0x2c, 0xc9, 0xf6, 0x5d, 0x73, 0xeb, 0x3d, 0x89, 0x1c, 0xa3, 0xcd, 0x6d, 0x53, 0xc1, 0xb5, 0x6a, 0x61, 0xcf, 0x53, + 0xc5, 0x90, 0x07, 0x88, 0x23, 0x1b, 0x51, 0x18, 0xeb, 0xbc, 0x8d, 0x06, 0x92, 0xa8, 0x4f, 0x8b, 0x0b, 0x21, 0xa2, 0x39, 0x1e, 0xcb, 0xcf, 0x26, 0x11, 0xa7, 0xa5, 0x43, 0x52, 0x07, 0x83, 0x69, 0x24, 0x3a, 0xa3, 0xd8, 0x66, 0x8e, 0xc1, 0x22, 0xc5, 0xaa, 0x20, 0x66, 0x15, 0xce, 0x08, 0xd2, 0x4e, 0x2a, 0x44, 0x32, 0x59, 0x61, 0x4f, 0x78, 0x69, 0x7f, 0x4f, 0xb5, 0xdf, 0x59, 0xb3, 0xa4, 0x72, 0xe1, 0xba, 0xa4, 0x2e, 0xdb, 0x7f, 0x5c, 0xed, 0x96, 0x85, 0x3b, 0x6a, 0xe6, 0xee, 0xeb, 0xcd, 0x6d, 0xbd, 0xe5, 0x5b, 0x23, 0x89, 0x83, 0x0d, 0x37, 0xe8, 0xac, 0x6a, 0x21, 0x79, 0x0f, 0xd3, 0x28, 0xce, 0x72, 0x16, 0xb8, 0x83, 0xfd, 0xab, 0x37, 0xee, 0x3c, 0x34, 0xab, 0x72, 0x61, 0x55, 0xae, 0xc6, 0x5c, 0x51, 0xdd, 0xe8, 0xaf, 0xde, 0x9e, 0x2c, 0xb5, 0x77, 0x05, 0xd5, 0x5e, 0xaf, + 0x57, 0x63, 0xac, 0xe9, 0xdf, 0x35, 0x6f, 0xdd, 0xa5, 0x77, 0xbf, 0xbf, 0x52, 0x20, 0x94, 0xa8, 0xf4, 0xe2, 0xc5, 0xf8, 0xac, 0xaa, 0xd8, 0xa7, 0x68, 0x1d, 0xdc, 0x6b, 0x3b, 0x91, 0x83, 0x6a, 0x9a, 0x52, 0x84, 0x41, 0x40, 0xd2, 0x19, 0x67, 0x0b, 0xc4, 0x59, 0xc7, 0x90, 0xed, 0x36, 0x8c, 0x4c, 0xe2, 0x79, 0x64, 0x33, 0x2a, 0x5e, 0xea, 0x76, 0x23, 0x61, 0x40, 0x3d, 0x35, 0xa7, 0xd3, 0x64, 0x37, 0x8b, 0x42, 0x6f, 0x08, 0xa8, 0xc4, 0x06, 0x9f, 0xb9, 0x63, 0xb3, 0xb1, 0xf6, 0xec, 0xea, 0x49, 0x9e, 0xef, 0xc9, 0x64, 0x98, 0xfa, 0xb6, 0x34, 0x4b, 0x2d, 0x6e, 0x6e, 0x9f, 0x35, 0x1f, 0x79, 0xba, 0x5f, 0x71, 0x53, 0x1f, 0x70, 0xf9, 0xcf, 0xc8, 0x5f, 0x35, 0xd5, 0x43, 0x7c, 0x3a, 0x0c, 0xf1, 0x69, 0x25, 0xc4, 0xa7, 0x3c, 0x28, 0xe1, 0x39, 0x50, 0xa6, 0x5a, 0x3e, 0x4a, 0xa6, + 0x0f, 0xf9, 0x5a, 0x28, 0x9d, 0xa0, 0x03, 0x07, 0x99, 0xc3, 0xc9, 0x74, 0x87, 0x69, 0x96, 0xcb, 0x90, 0x60, 0x68, 0xd0, 0xcb, 0x1c, 0x72, 0x07, 0x5f, 0xca, 0x97, 0x8a, 0x85, 0xf0, 0x59, 0x9e, 0x53, 0x88, 0x98, 0x70, 0x15, 0xf6, 0xed, 0xf2, 0xa1, 0x48, 0x64, 0x1e, 0x05, 0x10, 0x3a, 0xd1, 0x6a, 0x48, 0x0c, 0x36, 0x80, 0xc3, 0x8d, 0x20, 0xcc, 0x3f, 0x71, 0xeb, 0xa1, 0x13, 0x7c, 0x20, 0x3a, 0xbe, 0xff, 0xc0, 0x09, 0x11, 0x08, 0x8d, 0xbc, 0xb8, 0xbd, 0xb6, 0x16, 0x25, 0x41, 0x3b, 0x87, 0x92, 0xa0, 0x9d, 0x23, 0x8f, 0xb1, 0xff, 0xb8, 0x72, 0x05, 0xc8, 0x0f, 0x00, 0xf2, 0xd3, 0x4f, 0x59, 0x96, 0x3c, 0xbc, 0xe8, 0x25, 0xf6, 0x6f, 0xcf, 0xbf, 0xc0, 0xfe, 0xf5, 0xa5, 0xbe, 0xbe, 0x97, 0x80, 0xfa, 0x85, 0xe7, 0x81, 0xea, 0xa5, 0x45, 0x18, 0xaf, 0xa0, 0xd8, 0xbf, 0x4f, 0x98, + 0x27, 0xa1, 0xfc, 0x56, 0x43, 0x24, 0x92, 0x65, 0x50, 0x06, 0x84, 0x68, 0x86, 0x46, 0x99, 0x04, 0x70, 0x59, 0x9b, 0x55, 0x4d, 0x28, 0x53, 0x3e, 0xd1, 0x8b, 0xfc, 0x8e, 0x50, 0xe6, 0x70, 0x1e, 0x8f, 0x4b, 0x39, 0x44, 0x35, 0x0b, 0x85, 0xc2, 0x1a, 0x61, 0x8d, 0x5f, 0x17, 0x70, 0xf9, 0xbc, 0x1e, 0xa7, 0x4a, 0x8c, 0x0b, 0x46, 0x41, 0xd6, 0x09, 0x39, 0x1a, 0x40, 0x58, 0xe1, 0x85, 0x90, 0x5f, 0x1a, 0x37, 0x68, 0x08, 0x19, 0x68, 0x69, 0xb1, 0x73, 0x1e, 0x4a, 0x03, 0x90, 0xd6, 0xb6, 0x16, 0xfa, 0x26, 0xe1, 0x0d, 0xf0, 0xe7, 0x1f, 0xbd, 0x6d, 0xdd, 0xb1, 0xac, 0xa1, 0x89, 0x12, 0xd8, 0x1c, 0x03, 0xbc, 0xf2, 0xad, 0xaf, 0x6e, 0x1b, 0x3a, 0x3e, 0x98, 0x1f, 0x2a, 0xad, 0xa8, 0x49, 0xe6, 0x77, 0x96, 0xbb, 0x4c, 0xf1, 0xae, 0xe2, 0xea, 0xb5, 0x6d, 0x61, 0xde, 0xa1, 0xdf, 0x7c, + 0x6d, 0x6e, 0x70, 0xf6, 0xba, 0xc6, 0xee, 0x3b, 0x6f, 0x9c, 0xa3, 0xa7, 0xef, 0x9f, 0xd7, 0x44, 0x1f, 0x38, 0x59, 0xf3, 0xab, 0xb6, 0x2c, 0x7f, 0x6d, 0xdb, 0x53, 0xec, 0x5f, 0x5f, 0x5b, 0x96, 0x58, 0xff, 0xd0, 0x92, 0x97, 0x12, 0x25, 0x6d, 0xc6, 0x80, 0xa9, 0x62, 0xa8, 0xf9, 0xa5, 0xc4, 0x8a, 0xa6, 0x40, 0xe2, 0x96, 0xff, 0x7e, 0xb0, 0xad, 0x16, 0xf8, 0x5a, 0x37, 0xcc, 0x8e, 0x2f, 0xae, 0xf3, 0x47, 0x57, 0x3d, 0xbc, 0x16, 0xc1, 0xd0, 0xce, 0xf1, 0x7f, 0x92, 0x28, 0x7f, 0xa3, 0x32, 0x25, 0xf7, 0xe6, 0x64, 0x3c, 0x48, 0x20, 0x47, 0xe4, 0x71, 0x61, 0x99, 0x55, 0x8d, 0x4c, 0xd2, 0xa5, 0x20, 0x83, 0xec, 0x76, 0x5e, 0x4c, 0xd4, 0xcd, 0x31, 0x86, 0x4b, 0xed, 0x15, 0x8b, 0x92, 0x76, 0xea, 0xf3, 0x31, 0xf7, 0xac, 0x96, 0x32, 0xad, 0x5d, 0x27, 0x0e, 0x36, 0xad, 0x48, 0xa0, + 0x3e, 0x7d, 0xe3, 0x9f, 0xd1, 0x15, 0x70, 0x3d, 0xc3, 0xc4, 0x2f, 0x92, 0x62, 0x2b, 0xa4, 0x8c, 0x3a, 0x29, 0x49, 0x93, 0x69, 0x24, 0xa4, 0x4f, 0xfb, 0x8e, 0x45, 0x9a, 0x18, 0x9c, 0xa1, 0x11, 0x01, 0x6a, 0x2e, 0x56, 0xa9, 0x48, 0x26, 0xdf, 0x8e, 0x4e, 0xba, 0x5d, 0x48, 0xa4, 0x53, 0x53, 0xcc, 0x78, 0x3b, 0x37, 0xa3, 0x90, 0xf9, 0x92, 0xce, 0xc3, 0x5f, 0xf6, 0x74, 0xd2, 0x34, 0x71, 0x07, 0xf1, 0xfe, 0xa9, 0xdb, 0x29, 0xc4, 0x86, 0x0b, 0x74, 0xa3, 0x40, 0x98, 0x30, 0x11, 0xf2, 0x78, 0x73, 0x5c, 0x3c, 0x28, 0xd6, 0x79, 0xd2, 0x9c, 0x20, 0xc5, 0x89, 0xab, 0x58, 0x61, 0x07, 0xa8, 0x1c, 0x2a, 0x43, 0x00, 0x49, 0xe4, 0xff, 0x1f, 0xa1, 0x2b, 0x0a, 0x72, 0x03, 0x6d, 0xf5, 0x65, 0xda, 0x27, 0x05, 0x22, 0x92, 0x91, 0x0a, 0x3e, 0xe2, 0x9b, 0x0d, 0x8f, 0x81, 0x43, 0x4b, 0xc4, + 0x06, 0x77, 0xbe, 0x6b, 0x96, 0xd8, 0x94, 0xe3, 0x0e, 0x75, 0xd4, 0x97, 0xa8, 0xc1, 0x2e, 0x4d, 0xa2, 0xf8, 0x40, 0x5b, 0xc0, 0xa4, 0x08, 0x37, 0x97, 0x81, 0xf3, 0x7a, 0x9f, 0x5a, 0xed, 0xd3, 0x8c, 0xbd, 0x20, 0xcf, 0xa2, 0xbe, 0x06, 0x14, 0xd6, 0x40, 0x96, 0x08, 0xd3, 0x44, 0x45, 0xa0, 0xa6, 0xf0, 0xf2, 0x17, 0xc5, 0x05, 0x1c, 0xad, 0x3e, 0x00, 0xcf, 0xff, 0x93, 0x70, 0xef, 0x3c, 0xc4, 0x2f, 0x38, 0xde, 0x57, 0x6c, 0x13, 0x42, 0x04, 0x20, 0x03, 0x04, 0x0f, 0xe7, 0xae, 0x4b, 0x5d, 0x01, 0x5e, 0x46, 0x93, 0xe5, 0x42, 0xa8, 0x21, 0x87, 0xcb, 0xa1, 0x88, 0xd7, 0xb5, 0x09, 0xa5, 0x8e, 0x4b, 0x7f, 0xe7, 0xd0, 0x44, 0x6a, 0xa1, 0xa7, 0x37, 0xcd, 0x9d, 0xd4, 0x34, 0x77, 0xa2, 0xa9, 0xf1, 0x7a, 0x9a, 0x16, 0x21, 0xe6, 0xdb, 0x91, 0x69, 0x85, 0x49, 0x47, 0x38, 0xd5, 0x88, + 0x98, 0x68, 0xd3, 0xc3, 0x39, 0xdb, 0x21, 0x99, 0x4b, 0xed, 0x52, 0x7b, 0xbc, 0x2e, 0xc4, 0x81, 0x67, 0xd6, 0x19, 0x1d, 0x23, 0x28, 0xed, 0x62, 0xaf, 0xf3, 0x29, 0xa1, 0x3b, 0xf0, 0x0b, 0xfd, 0x64, 0x75, 0x45, 0xb0, 0x25, 0x99, 0x23, 0x2e, 0xbd, 0xbb, 0xef, 0xd6, 0x26, 0x3e, 0xd3, 0x1f, 0xcc, 0xbe, 0x38, 0x38, 0x34, 0x6f, 0x77, 0xe8, 0x62, 0x73, 0xe2, 0x40, 0xab, 0x31, 0x98, 0x15, 0x69, 0x89, 0xf6, 0x2c, 0xe9, 0x0a, 0xce, 0x75, 0xb3, 0x9f, 0x86, 0x02, 0xf4, 0xbf, 0xc7, 0xee, 0x1b, 0xde, 0xd1, 0x3f, 0x97, 0x3c, 0x3e, 0xe6, 0x6c, 0xaa, 0x9f, 0x88, 0x35, 0xf9, 0x08, 0xae, 0xa5, 0x1e, 0xd9, 0x4b, 0xe4, 0x13, 0xfe, 0x13, 0x58, 0x5c, 0x08, 0xa7, 0x3c, 0x36, 0x74, 0x0e, 0x2d, 0xcd, 0xd7, 0x85, 0x00, 0x4f, 0x04, 0x78, 0xfc, 0x49, 0xf1, 0x39, 0x98, 0xcd, 0xa1, 0x3e, 0xb2, + 0xf5, 0xae, 0x3b, 0xd4, 0xba, 0x89, 0x1d, 0x7c, 0x40, 0x6f, 0x63, 0x3e, 0x91, 0xaa, 0x04, 0x91, 0xd9, 0x89, 0x3c, 0x0d, 0x83, 0xf8, 0x9b, 0x43, 0xec, 0xc7, 0x2c, 0xfb, 0x8f, 0x55, 0x99, 0xa0, 0x97, 0xd6, 0x73, 0x6f, 0xbc, 0xbd, 0xd8, 0x61, 0x84, 0xef, 0x6d, 0x85, 0x34, 0x08, 0xa9, 0x2f, 0xaa, 0x90, 0x1c, 0x5a, 0x08, 0x65, 0x7f, 0xd0, 0xe8, 0x74, 0x90, 0x64, 0x3d, 0x66, 0x25, 0xc9, 0x45, 0x14, 0x2e, 0xa3, 0x85, 0x38, 0x15, 0xa2, 0xb9, 0xaa, 0xb2, 0xa2, 0xcc, 0xe1, 0x2d, 0xc0, 0xdc, 0x16, 0xc2, 0x2d, 0x93, 0x88, 0x67, 0x4a, 0x7a, 0x9b, 0x91, 0x32, 0xa5, 0x52, 0xd7, 0x63, 0x47, 0x17, 0x07, 0x25, 0xc8, 0xb3, 0xf4, 0x54, 0x0e, 0x37, 0x07, 0x8f, 0xec, 0xef, 0x3d, 0xd8, 0xb0, 0xa2, 0xd6, 0xe1, 0x6d, 0x59, 0xdf, 0x9a, 0xec, 0x2a, 0xd0, 0x20, 0x02, 0xe5, 0x91, 0xd8, 0x8a, + 0xb2, 0x5b, 0x8f, 0xac, 0xae, 0xca, 0x8a, 0x75, 0x27, 0x3b, 0xe7, 0xda, 0xe2, 0x8d, 0xa1, 0x33, 0x61, 0x37, 0xf9, 0x0c, 0xd3, 0xe8, 0x0a, 0xf8, 0x6a, 0x16, 0xc4, 0x7a, 0x97, 0xdc, 0xeb, 0xac, 0x98, 0x5b, 0x18, 0x6b, 0x2d, 0x2f, 0x34, 0xeb, 0x0b, 0x8a, 0xab, 0x7c, 0xb3, 0x76, 0x27, 0xcb, 0xec, 0x9d, 0x01, 0xad, 0xc7, 0x61, 0x91, 0xea, 0x12, 0x8b, 0xf7, 0x2d, 0xcc, 0x5d, 0xb2, 0xa0, 0xc5, 0xe8, 0xde, 0x3c, 0x27, 0xb7, 0xa3, 0xb6, 0x58, 0x1b, 0x70, 0xcd, 0xe2, 0xd6, 0xb6, 0x09, 0xce, 0xd1, 0x0c, 0xe7, 0x58, 0x4e, 0xe4, 0x25, 0xb3, 0x3d, 0x88, 0x1e, 0x34, 0xaa, 0x55, 0x88, 0xce, 0x52, 0x34, 0x40, 0xe9, 0x9e, 0x33, 0x85, 0x16, 0x90, 0x86, 0xa3, 0xac, 0xa4, 0x30, 0x3f, 0x27, 0x4c, 0x94, 0x83, 0x72, 0xce, 0x54, 0xf4, 0xd5, 0x53, 0x43, 0x98, 0x69, 0xf2, 0x72, 0x90, 0xee, + 0xe9, 0x13, 0x73, 0x8b, 0xed, 0x71, 0x3c, 0x31, 0x43, 0x49, 0x5f, 0x6d, 0xef, 0xa2, 0xc4, 0x8d, 0x8f, 0xaf, 0x78, 0x29, 0xcf, 0xb2, 0xac, 0x76, 0x79, 0x9d, 0xeb, 0xe8, 0xbe, 0x05, 0xe4, 0x13, 0x5f, 0x31, 0xab, 0xbc, 0xa1, 0xbe, 0x56, 0x83, 0xe7, 0xf0, 0xca, 0x9e, 0x5b, 0x17, 0x47, 0xda, 0x5d, 0x01, 0x47, 0x49, 0x7b, 0xfe, 0xaa, 0x75, 0xf7, 0x72, 0x73, 0x9b, 0x35, 0xfe, 0x39, 0x7d, 0x82, 0xfa, 0x98, 0x88, 0x03, 0xcb, 0x8b, 0xf9, 0x80, 0xe0, 0xa7, 0xe5, 0x54, 0x33, 0x83, 0xcd, 0xfd, 0x80, 0x1c, 0xc1, 0xd1, 0x87, 0xd8, 0xe1, 0x39, 0x07, 0xe7, 0xe5, 0xcd, 0xc5, 0xe7, 0x46, 0xfc, 0xa5, 0x6d, 0xc2, 0x64, 0x1a, 0x1d, 0x7e, 0x55, 0x3f, 0xca, 0xeb, 0xe8, 0x47, 0x7d, 0x1d, 0xfd, 0x68, 0xb1, 0xe8, 0xc7, 0x20, 0xcf, 0x15, 0x24, 0xc7, 0xa0, 0x96, 0x6b, 0x66, 0xea, 0xed, 0x2b, + 0x1b, 0x61, 0x41, 0x1a, 0x23, 0x4c, 0xb1, 0x47, 0xe3, 0xd5, 0xe9, 0x5d, 0x01, 0xa7, 0x80, 0x6f, 0xe6, 0x20, 0x16, 0x4b, 0xc8, 0x74, 0x29, 0x27, 0x44, 0x73, 0x31, 0x23, 0x0e, 0x1e, 0xc9, 0x31, 0x88, 0xe5, 0x48, 0x3a, 0x04, 0xaa, 0xb8, 0x0b, 0x63, 0x4f, 0x07, 0x7d, 0xc2, 0x6e, 0x3b, 0x0c, 0x00, 0x5f, 0xa1, 0xb3, 0xfb, 0xb2, 0xfc, 0x0d, 0xb1, 0x2d, 0x1b, 0x04, 0x22, 0x31, 0xc5, 0x9e, 0xa2, 0xf5, 0xde, 0x48, 0x62, 0x56, 0x40, 0x6a, 0xc9, 0x92, 0x6b, 0xac, 0xe5, 0x11, 0x37, 0xef, 0xe7, 0x22, 0xb5, 0x59, 0x45, 0x33, 0xd4, 0x9d, 0x46, 0x77, 0x76, 0xbb, 0xff, 0xe6, 0x67, 0x37, 0x37, 0xfa, 0xa4, 0xad, 0x0e, 0xe7, 0xe3, 0xe7, 0xb3, 0x97, 0x6d, 0xb9, 0x6b, 0xfe, 0x19, 0xf6, 0x8b, 0x6f, 0xef, 0x68, 0xb6, 0xd3, 0x42, 0xb9, 0x68, 0x5f, 0xe3, 0xc3, 0x80, 0xff, 0xcd, 0x4e, 0x89, + 0x41, 0x8b, 0x1c, 0x2b, 0xb7, 0x40, 0xdc, 0x69, 0xa0, 0x85, 0x84, 0x02, 0x62, 0x9b, 0xdc, 0x64, 0x18, 0x49, 0x7d, 0x2e, 0xec, 0x6a, 0x99, 0x26, 0x7f, 0xb1, 0x8c, 0x03, 0x65, 0x21, 0xc2, 0x59, 0x79, 0x74, 0xb3, 0xda, 0x1f, 0xf0, 0x3a, 0x99, 0xb4, 0x24, 0x1b, 0xcb, 0xe8, 0x4d, 0x5c, 0x53, 0x6c, 0x4b, 0x28, 0x33, 0x22, 0x48, 0x14, 0xec, 0xac, 0xd9, 0x77, 0xa7, 0xaf, 0x7e, 0x49, 0x69, 0x6c, 0x71, 0x5d, 0xe0, 0x62, 0xc5, 0x86, 0x27, 0x57, 0xf5, 0x3f, 0xb9, 0xa3, 0x11, 0x25, 0x02, 0x9c, 0x77, 0x67, 0xf1, 0x45, 0x20, 0xa9, 0xad, 0xe0, 0x92, 0x00, 0xfa, 0x9a, 0xd7, 0xd6, 0x2f, 0x7b, 0x7c, 0x43, 0xb2, 0x76, 0xcf, 0xeb, 0x37, 0xa3, 0xec, 0x88, 0x15, 0xc5, 0x6c, 0x15, 0x91, 0xce, 0x25, 0x7b, 0x96, 0xfa, 0x27, 0x1c, 0x9f, 0x1f, 0x69, 0x07, 0x55, 0x48, 0xa7, 0xd6, 0x48, 0x32, + 0x24, 0xf6, 0x8a, 0x84, 0xb4, 0x29, 0x27, 0xe3, 0xf1, 0x08, 0x25, 0x1b, 0x25, 0x94, 0x5b, 0x94, 0x7e, 0xa5, 0xcf, 0x66, 0xc1, 0x05, 0x2f, 0x14, 0x40, 0xc1, 0xe7, 0xa7, 0xcf, 0x8e, 0x2e, 0x35, 0x54, 0x9f, 0x07, 0x9f, 0x12, 0x2b, 0x98, 0xec, 0xab, 0x1a, 0x21, 0x77, 0x2e, 0x3e, 0xd8, 0xed, 0xbf, 0xf3, 0xf0, 0xed, 0xb7, 0x69, 0xfc, 0x89, 0x20, 0x18, 0x73, 0x5a, 0xff, 0x3c, 0xab, 0xbd, 0x7a, 0xdb, 0x0b, 0x23, 0x6b, 0xce, 0xed, 0xac, 0x6f, 0x3a, 0xf2, 0x93, 0xfd, 0xd4, 0xc2, 0xbc, 0x85, 0x07, 0xe7, 0x3f, 0xf3, 0xc2, 0x2b, 0x2f, 0x24, 0xd6, 0x2f, 0xaa, 0x96, 0x1f, 0x5b, 0xa8, 0xf2, 0x17, 0x9d, 0xdb, 0x36, 0xf2, 0xea, 0xfe, 0xe6, 0xba, 0xdd, 0xdf, 0xda, 0xb8, 0xf1, 0xdb, 0x7b, 0x53, 0xf8, 0x13, 0xe1, 0xb1, 0xdd, 0x90, 0x17, 0xcd, 0x43, 0x71, 0x6a, 0x79, 0xd8, 0xe8, 0x60, + 0x32, 0x62, 0x5e, 0x1a, 0x10, 0xeb, 0x32, 0x6b, 0x0a, 0x9a, 0x73, 0xc2, 0x2e, 0x27, 0xc5, 0x9f, 0x9e, 0x57, 0x69, 0xe6, 0xf3, 0x8d, 0xf0, 0x2b, 0x35, 0x5b, 0xa8, 0x54, 0x28, 0xdc, 0x85, 0x4d, 0xf1, 0x86, 0x35, 0x8d, 0xde, 0xfc, 0xbe, 0x83, 0x3d, 0xc9, 0xce, 0x49, 0xd8, 0x6a, 0xd1, 0xe3, 0x5b, 0x67, 0x25, 0xb6, 0x3c, 0x7f, 0xe3, 0xea, 0xc7, 0x92, 0x54, 0x89, 0x58, 0x6a, 0xb0, 0x18, 0x0a, 0xe6, 0x6d, 0xa8, 0x59, 0xb4, 0xb9, 0xbd, 0xc8, 0x72, 0xd5, 0x59, 0x36, 0xd7, 0xdf, 0x78, 0x76, 0xef, 0xd2, 0x67, 0xf7, 0xb6, 0xd4, 0x25, 0xb9, 0x31, 0x67, 0x8d, 0x7f, 0x4e, 0xdd, 0x0a, 0xcf, 0xae, 0x89, 0x58, 0xcc, 0x1d, 0x5a, 0x1d, 0x46, 0xb7, 0x48, 0x30, 0x9c, 0xc2, 0x06, 0xa5, 0x8e, 0xeb, 0x4c, 0x77, 0xf1, 0x91, 0xb8, 0xc6, 0x63, 0x3d, 0x3d, 0xe7, 0x5d, 0x5a, 0x8f, 0x8b, 0x03, + 0x19, 0xa4, 0x6c, 0x9b, 0xca, 0x45, 0x29, 0x71, 0x49, 0x43, 0x07, 0x75, 0xab, 0xdb, 0xc1, 0x7e, 0x77, 0x12, 0x43, 0xc5, 0x7e, 0xcf, 0xe1, 0xa0, 0x0a, 0xa8, 0x87, 0x9c, 0x9e, 0xb1, 0x37, 0x1b, 0x9b, 0x33, 0x8c, 0x15, 0x59, 0x37, 0xd7, 0xe0, 0xe5, 0xc6, 0x8d, 0x62, 0x7b, 0xff, 0x0c, 0x69, 0x95, 0x17, 0xd1, 0x2a, 0xc8, 0xf0, 0x63, 0x2a, 0x95, 0x8a, 0x19, 0x28, 0xc4, 0xa4, 0xca, 0x4b, 0x78, 0x3c, 0xda, 0x00, 0xa6, 0x14, 0x93, 0x34, 0x0a, 0x88, 0x56, 0x66, 0xf2, 0x16, 0x91, 0x3e, 0x2f, 0x5c, 0x61, 0xea, 0x05, 0x91, 0x46, 0x2e, 0x6c, 0xdd, 0xf9, 0x78, 0x6f, 0xff, 0x89, 0x15, 0xf1, 0x0b, 0x8e, 0xb2, 0xce, 0x48, 0x5e, 0x5f, 0x53, 0xae, 0x58, 0xa1, 0x12, 0x90, 0x22, 0x85, 0x56, 0x44, 0x7d, 0x9e, 0x58, 0x3f, 0xba, 0x3e, 0x79, 0xc7, 0xcf, 0x0e, 0x54, 0x46, 0x97, 0x1e, 0xe9, + 0x41, 0xc1, 0xad, 0xb1, 0x39, 0x65, 0x36, 0xff, 0xc2, 0x7b, 0xd7, 0x47, 0x86, 0x86, 0x06, 0x23, 0x2a, 0xa3, 0x9c, 0x8f, 0xe1, 0xb5, 0x85, 0x3d, 0xc3, 0xec, 0x83, 0xfb, 0x2f, 0x25, 0x8a, 0x10, 0x96, 0x0f, 0x62, 0x08, 0x98, 0x38, 0x4b, 0x3c, 0x2c, 0x66, 0x23, 0x0e, 0x20, 0x7d, 0x9a, 0x8a, 0xe2, 0x4a, 0x65, 0x96, 0xcf, 0x0f, 0x21, 0xd5, 0x98, 0xd6, 0x04, 0x23, 0x21, 0x70, 0xf2, 0x61, 0x4a, 0x29, 0x5a, 0x11, 0x9a, 0xd0, 0x5b, 0x91, 0x76, 0x9b, 0xd3, 0xb0, 0xe9, 0xa9, 0xca, 0xbe, 0xa3, 0x4b, 0x0a, 0x42, 0x75, 0xf3, 0xf3, 0x12, 0x0d, 0xe7, 0x43, 0x1d, 0x1b, 0x9a, 0x06, 0x1e, 0xbd, 0xb1, 0xb2, 0xf3, 0xd8, 0xfb, 0x7b, 0xce, 0xef, 0xfe, 0xd9, 0xdd, 0xed, 0x91, 0x45, 0x7b, 0xdb, 0x0b, 0xf8, 0x50, 0x5a, 0x13, 0xf4, 0x2d, 0xcd, 0x5f, 0x74, 0xdb, 0x82, 0x3d, 0xa0, 0x64, 0xc5, + 0xdd, 0x0b, 0x5b, 0xb7, 0x2f, 0x28, 0x98, 0xdd, 0xd7, 0xb5, 0xa9, 0xcd, 0xd3, 0xbc, 0xf7, 0xc5, 0xe5, 0x5b, 0xbf, 0x7b, 0x74, 0x36, 0x60, 0x5f, 0x06, 0x75, 0xa0, 0xed, 0x96, 0x6f, 0xdf, 0xd0, 0x7b, 0xff, 0x9e, 0x21, 0x47, 0x9b, 0xc4, 0xa8, 0x57, 0x51, 0x05, 0x2f, 0x6f, 0x6a, 0x3f, 0x38, 0x50, 0x02, 0xb0, 0x9e, 0xbb, 0x15, 0xce, 0x69, 0x3f, 0xa4, 0x5b, 0x9c, 0x9e, 0x7b, 0x49, 0x8a, 0x85, 0x4a, 0xd5, 0xa4, 0x45, 0x8a, 0x56, 0x24, 0xd0, 0xa0, 0x8c, 0xdb, 0x29, 0x15, 0x37, 0x2a, 0x3b, 0xc0, 0xe3, 0x2d, 0xe7, 0xe1, 0x00, 0xe5, 0x6b, 0xb7, 0x5a, 0x9b, 0x6a, 0xd5, 0x93, 0x94, 0x4d, 0xd6, 0x88, 0x8b, 0xae, 0xad, 0x11, 0x9f, 0x58, 0x07, 0x65, 0x84, 0xaa, 0x5b, 0x70, 0xb8, 0xbf, 0xa0, 0xa0, 0xff, 0xf0, 0x82, 0xf3, 0x0b, 0xb9, 0x2f, 0x0b, 0xcf, 0xef, 0xb9, 0x74, 0xac, 0xbd, + 0xfd, 0xd8, 0xa5, 0x3d, 0x93, 0xbe, 0x80, 0xc2, 0xfe, 0x5b, 0xba, 0xbb, 0x6f, 0xe9, 0x8f, 0x02, 0x10, 0xe5, 0xbe, 0x15, 0x02, 0x6a, 0x0f, 0x68, 0x3c, 0xf0, 0xfa, 0x4d, 0x1b, 0x5f, 0x3b, 0xd0, 0xc8, 0x4d, 0xbe, 0xf1, 0xc0, 0x6b, 0x1b, 0x6f, 0x7a, 0x1d, 0x5e, 0x81, 0xbb, 0x50, 0x7e, 0x7d, 0x96, 0x3d, 0x45, 0xbd, 0x0f, 0x99, 0xb8, 0x08, 0xf1, 0x68, 0x52, 0x98, 0xed, 0x56, 0x8b, 0x68, 0x06, 0x87, 0x8d, 0xcb, 0xb0, 0xd2, 0x19, 0xf1, 0xc7, 0xc8, 0xc3, 0x33, 0x96, 0x8a, 0xc5, 0x46, 0xdc, 0x5d, 0x1e, 0x91, 0xe6, 0x01, 0x67, 0xbe, 0x5f, 0x94, 0xe1, 0xbc, 0xcd, 0x93, 0xee, 0x43, 0xb0, 0xc8, 0x9b, 0x08, 0xe8, 0x2e, 0xe2, 0x52, 0xa8, 0xe0, 0xc3, 0x93, 0xe2, 0xc1, 0x67, 0x68, 0xd2, 0xc3, 0xc5, 0x96, 0xc0, 0xb1, 0x15, 0x04, 0x3c, 0xde, 0x10, 0x56, 0x61, 0x33, 0x19, 0x16, 0x7c, + 0x26, 0xfe, 0x3b, 0x15, 0x00, 0x8c, 0x53, 0x48, 0xc3, 0x85, 0x8c, 0x50, 0xef, 0xdb, 0x73, 0x83, 0x2d, 0xb5, 0x65, 0xc6, 0x27, 0x18, 0xb9, 0xe0, 0x7b, 0x8c, 0xd9, 0x70, 0x0b, 0x98, 0xf3, 0x9c, 0xcc, 0xec, 0xc9, 0x77, 0xba, 0x49, 0x99, 0xc3, 0xbe, 0xfc, 0x40, 0xf0, 0x11, 0x5a, 0x26, 0x7c, 0x99, 0x67, 0x32, 0xdc, 0xc2, 0xbe, 0xf2, 0xba, 0xc4, 0xe4, 0x08, 0x59, 0x7e, 0xd2, 0x16, 0x30, 0xca, 0x43, 0x2d, 0xe5, 0x60, 0x87, 0xad, 0x44, 0xcf, 0x0e, 0x2a, 0xf4, 0xe4, 0x3b, 0xe0, 0x76, 0xab, 0xdf, 0x20, 0x6e, 0x40, 0xcc, 0xf8, 0xec, 0x7e, 0xf6, 0x36, 0x6b, 0xb1, 0x0e, 0xec, 0x51, 0x64, 0x8d, 0x75, 0xb2, 0x2d, 0xa6, 0x80, 0x51, 0xca, 0x9d, 0xcf, 0x4d, 0x10, 0x6e, 0x4e, 0x42, 0xb8, 0xc9, 0x46, 0x31, 0x5d, 0x41, 0xbb, 0x85, 0x02, 0xa4, 0x08, 0x6b, 0x15, 0xb1, 0x3d, 0x07, 0xbb, + 0x03, 0xe7, 0x70, 0x52, 0x46, 0x46, 0x91, 0x96, 0x4d, 0x84, 0xcd, 0x6a, 0xa5, 0x09, 0x09, 0x16, 0x60, 0x62, 0xd3, 0xdd, 0x04, 0xb2, 0x32, 0xe0, 0x1c, 0xe6, 0x19, 0x33, 0x03, 0x82, 0x0f, 0x88, 0x37, 0x98, 0x01, 0x89, 0x56, 0x26, 0xe8, 0xbd, 0xf7, 0xdd, 0xd1, 0x0b, 0xec, 0x5f, 0xd8, 0x8f, 0xf6, 0x4b, 0xf4, 0x0a, 0x51, 0xfe, 0x82, 0x3d, 0x5d, 0x17, 0xda, 0xf6, 0x0f, 0x14, 0x49, 0x55, 0x2a, 0xc6, 0xa5, 0x49, 0x9d, 0xdd, 0xbb, 0xbf, 0xbf, 0x33, 0x29, 0x64, 0xbf, 0x01, 0xea, 0x55, 0x80, 0xfc, 0x2f, 0x52, 0x27, 0xd7, 0x99, 0x24, 0x6c, 0xac, 0xe9, 0xc6, 0xae, 0x42, 0x11, 0x23, 0xf9, 0xe2, 0x53, 0x7e, 0xde, 0xc2, 0x3b, 0x56, 0xce, 0x5a, 0xbb, 0xb0, 0x2b, 0x87, 0xf3, 0xa7, 0xee, 0x1a, 0xff, 0x98, 0x79, 0x0e, 0xe2, 0x96, 0x3a, 0xe4, 0x67, 0x21, 0x05, 0x14, 0x8e, 0x3d, 0x21, + 0x29, 0x28, 0x48, 0x8c, 0xf0, 0x79, 0x78, 0xe0, 0x13, 0xda, 0x05, 0x0e, 0xd7, 0xd4, 0x11, 0xb5, 0x1e, 0x6d, 0x8e, 0xde, 0xef, 0xc3, 0xfa, 0x72, 0x9d, 0x2a, 0x8d, 0x6e, 0x32, 0x2a, 0x72, 0x55, 0x1c, 0xcd, 0x81, 0xcf, 0xc1, 0x34, 0xc6, 0x3e, 0x33, 0x20, 0x22, 0x26, 0x58, 0xd6, 0x5c, 0xb2, 0xfc, 0xe8, 0xdc, 0xf8, 0xfc, 0xc6, 0x22, 0xad, 0xb6, 0x78, 0xd6, 0xfc, 0xe8, 0xdc, 0xa3, 0xcb, 0x4b, 0x9a, 0xcb, 0x6e, 0x11, 0x19, 0x94, 0xb4, 0x48, 0x11, 0xf2, 0x99, 0x43, 0x95, 0xad, 0xae, 0x70, 0x65, 0x48, 0x7b, 0x51, 0x1b, 0xaa, 0xcc, 0x76, 0xb5, 0x54, 0x86, 0xcc, 0xbe, 0x90, 0x42, 0xc4, 0x28, 0xb3, 0x20, 0x92, 0x5a, 0xcf, 0x8e, 0xef, 0xbf, 0xe9, 0x8d, 0x83, 0x8d, 0xc6, 0xdc, 0x4a, 0x9f, 0xaf, 0x32, 0xd7, 0xd8, 0x78, 0xf0, 0x8d, 0x9b, 0xf6, 0x03, 0xb0, 0x5e, 0xa8, 0x75, 0x64, + 0x99, 0xfa, 0xd6, 0x6d, 0x2b, 0x5d, 0x7b, 0xa4, 0xd3, 0x1e, 0x68, 0x5c, 0x56, 0x01, 0x78, 0xec, 0x17, 0x48, 0x37, 0x67, 0xef, 0x3c, 0xb2, 0xb6, 0x74, 0xdb, 0xba, 0x3e, 0x53, 0x96, 0x43, 0x2b, 0x44, 0xf6, 0xe2, 0x2e, 0x7a, 0x0b, 0x59, 0xc9, 0xfc, 0x82, 0xe0, 0x11, 0x51, 0xae, 0x34, 0xa4, 0x24, 0xe3, 0x7e, 0x1f, 0xc1, 0x56, 0x72, 0xec, 0x77, 0xd5, 0x8d, 0x55, 0x43, 0xd8, 0x35, 0xb4, 0xa5, 0x27, 0x89, 0xbc, 0x1a, 0x79, 0x04, 0xcf, 0x41, 0x21, 0x7d, 0x05, 0x72, 0x76, 0xe6, 0x53, 0xa0, 0xab, 0x6a, 0x93, 0x45, 0x2b, 0x5e, 0x43, 0x6f, 0x01, 0x8b, 0x3c, 0x36, 0xf6, 0x7f, 0x30, 0x1e, 0x99, 0x4b, 0xef, 0xa4, 0xac, 0xcc, 0xaf, 0x61, 0xdb, 0x2c, 0x28, 0x4a, 0xac, 0x4f, 0x27, 0xa9, 0x46, 0x59, 0x8d, 0xbb, 0x19, 0xb4, 0xc4, 0xd8, 0x8f, 0x2f, 0x6d, 0xa4, 0x84, 0x5d, 0xa7, 0xf8, + 0x31, 0x2d, 0xec, 0x1e, 0x5d, 0x2f, 0x44, 0x7c, 0x15, 0x87, 0x3d, 0x43, 0x24, 0x96, 0x55, 0xaf, 0xfd, 0x28, 0x3c, 0x26, 0x2a, 0x82, 0x08, 0x40, 0x56, 0x40, 0xab, 0x96, 0x8a, 0xf1, 0xf0, 0xf8, 0x13, 0xc3, 0xf3, 0x70, 0x2c, 0xca, 0x35, 0xdd, 0xdc, 0x3a, 0x2a, 0xe1, 0xe8, 0x45, 0x6b, 0x41, 0x8f, 0xc2, 0xe4, 0xd1, 0x04, 0x8b, 0xe5, 0x26, 0xb7, 0x56, 0xe3, 0x31, 0x29, 0xc6, 0xc6, 0x15, 0x66, 0x8f, 0x46, 0xe3, 0x32, 0x29, 0x14, 0x26, 0x97, 0x46, 0xe3, 0x31, 0x2b, 0xe8, 0x9d, 0xdc, 0xfc, 0xf6, 0x6a, 0xdc, 0x26, 0x79, 0x71, 0x40, 0xeb, 0x36, 0x2b, 0x14, 0x66, 0xd8, 0x18, 0x5e, 0xc1, 0x87, 0x34, 0xa9, 0xeb, 0xf1, 0x71, 0xa2, 0x83, 0xfa, 0x16, 0x58, 0xc8, 0xfc, 0x99, 0xe2, 0x13, 0x7f, 0x87, 0x34, 0xf2, 0x6e, 0x2e, 0xee, 0x86, 0xbe, 0x3b, 0x15, 0x77, 0x83, 0xf4, 0x95, 0x01, + 0xb0, 0x10, 0xcc, 0x81, 0x7c, 0x8e, 0x2e, 0xa9, 0x46, 0x55, 0x2a, 0xeb, 0xb1, 0x17, 0x2b, 0xe7, 0x6e, 0xca, 0x69, 0xde, 0x48, 0xbe, 0x2f, 0x07, 0xc4, 0x2b, 0x48, 0xaf, 0x0f, 0x11, 0x4c, 0x90, 0xb0, 0x36, 0x8e, 0x94, 0x2b, 0x29, 0x81, 0x29, 0xa0, 0x16, 0x66, 0x19, 0xcc, 0x60, 0xce, 0x31, 0xf6, 0xf7, 0x9b, 0x7c, 0x62, 0xbb, 0xc4, 0x53, 0x3f, 0xab, 0x25, 0x34, 0xac, 0xb7, 0x62, 0x7a, 0x84, 0xec, 0xf9, 0x47, 0xe0, 0x19, 0x94, 0x70, 0x55, 0x88, 0xf8, 0x20, 0x6d, 0xab, 0x60, 0x18, 0x2c, 0x4f, 0x19, 0x52, 0x24, 0x09, 0x1e, 0x49, 0x23, 0xd5, 0x2c, 0x93, 0x02, 0xc2, 0x6c, 0x32, 0x1a, 0xb2, 0x74, 0x52, 0x8b, 0x0c, 0x85, 0x82, 0x4b, 0x80, 0x44, 0xc0, 0x9b, 0x50, 0xfe, 0x42, 0xda, 0x9d, 0x09, 0xf3, 0x71, 0xe0, 0x64, 0x47, 0xe4, 0xed, 0xed, 0xa3, 0x0d, 0x0e, 0x47, 0xfd, 0x68, + 0x3b, 0xfb, 0x4f, 0x20, 0xd9, 0xfb, 0xda, 0xd6, 0xd2, 0x9a, 0xdd, 0xdf, 0xda, 0xc4, 0xda, 0xa9, 0x33, 0x63, 0x81, 0x27, 0xdc, 0x95, 0xf3, 0x63, 0xc5, 0xf3, 0x2b, 0x1c, 0xac, 0x81, 0x3c, 0x5a, 0xba, 0xfc, 0x48, 0xd7, 0xc0, 0x83, 0xa3, 0xe5, 0xcc, 0xa9, 0xd3, 0x6c, 0x0d, 0x91, 0xf1, 0x35, 0xd0, 0x4f, 0xf7, 0x35, 0x30, 0x20, 0x8c, 0x60, 0xe4, 0x7c, 0x0d, 0x78, 0x19, 0x5f, 0x03, 0xfd, 0xd8, 0xc8, 0x7b, 0xd4, 0x50, 0xc6, 0xd7, 0x20, 0xf5, 0x2c, 0xef, 0xbf, 0x09, 0x3e, 0xf2, 0x34, 0x40, 0x19, 0xf7, 0x47, 0xaf, 0xee, 0x40, 0xa9, 0xa0, 0x79, 0x59, 0xa9, 0x1e, 0x94, 0x11, 0xdc, 0x07, 0xf9, 0xc6, 0x23, 0x5c, 0x27, 0xa0, 0x9a, 0xd3, 0xa5, 0xcb, 0xd8, 0x47, 0x79, 0x04, 0xb6, 0x5d, 0xdb, 0x88, 0xda, 0x24, 0xe2, 0x39, 0x71, 0xee, 0x2a, 0x43, 0x93, 0x90, 0xcf, 0x08, 0xe8, 0x94, + 0x2d, 0xdb, 0x98, 0xbe, 0x22, 0x7b, 0x11, 0x08, 0x5a, 0xc8, 0x66, 0x81, 0x80, 0x20, 0x04, 0x36, 0x81, 0xcd, 0x6a, 0xd1, 0x69, 0x35, 0x2a, 0xb9, 0x0c, 0x82, 0x18, 0x9f, 0xe0, 0x2b, 0x45, 0x3c, 0x6d, 0xc8, 0xa3, 0x74, 0x60, 0xb9, 0x18, 0xb2, 0x39, 0x90, 0xcf, 0x54, 0xa6, 0x3e, 0x22, 0x15, 0x94, 0x11, 0xc4, 0x1d, 0x4a, 0xba, 0x8a, 0x2d, 0x91, 0x84, 0x4a, 0x1b, 0x02, 0xf4, 0x32, 0xaf, 0x75, 0xfb, 0xe5, 0xbb, 0xe0, 0x9f, 0x7b, 0xac, 0x65, 0x45, 0xf9, 0x7e, 0x8b, 0x80, 0x5a, 0x7a, 0x9a, 0x6e, 0xfd, 0x88, 0x3d, 0xc1, 0xfe, 0xeb, 0x5c, 0x29, 0x5b, 0x66, 0x76, 0x82, 0x5e, 0xf6, 0x77, 0x66, 0x27, 0x49, 0xca, 0xab, 0xb7, 0xbc, 0x0a, 0x94, 0x60, 0xf5, 0xdf, 0x2e, 0x8b, 0xb9, 0x75, 0x83, 0xf8, 0x5f, 0x60, 0x85, 0x63, 0x2e, 0x46, 0x35, 0x59, 0x62, 0x06, 0x12, 0xf0, 0x40, 0x23, + 0x8e, 0xf3, 0x05, 0xf0, 0x9c, 0x0c, 0x08, 0xf9, 0x24, 0xc3, 0x18, 0x9a, 0x20, 0x76, 0x25, 0x08, 0x23, 0xd1, 0x5c, 0x90, 0xe7, 0x40, 0xe1, 0xde, 0xf0, 0x8f, 0x5d, 0xc4, 0xb3, 0x64, 0x3c, 0x0b, 0x70, 0xa9, 0x13, 0xa4, 0xeb, 0xe4, 0xdc, 0x0b, 0x2a, 0x50, 0xb5, 0x01, 0x5f, 0x3a, 0x59, 0x43, 0x29, 0x50, 0x4e, 0x2e, 0x68, 0x0a, 0xa9, 0x0a, 0x94, 0xee, 0x05, 0xd6, 0xdb, 0x72, 0xab, 0x35, 0xec, 0x63, 0xe6, 0x9a, 0xc2, 0x87, 0xdf, 0x79, 0xa8, 0xb0, 0xd6, 0xc4, 0x3e, 0xa6, 0xa9, 0xce, 0x39, 0x0c, 0xee, 0xb3, 0xb7, 0x44, 0x42, 0x65, 0x16, 0x76, 0xe5, 0x63, 0x8e, 0x62, 0x25, 0xe8, 0xb6, 0x55, 0x87, 0x0f, 0xbd, 0x73, 0x4b, 0xb8, 0xda, 0x06, 0xba, 0x95, 0xc5, 0xf6, 0xd3, 0xec, 0x4a, 0x4b, 0x59, 0xa8, 0xa0, 0xd5, 0x1e, 0x70, 0x81, 0x8f, 0x83, 0x9e, 0xb1, 0x1b, 0xc9, 0x5b, 0x3c, + 0x41, 0x56, 0xe1, 0x0a, 0x90, 0x2b, 0xe3, 0x95, 0x0a, 0x79, 0x51, 0x90, 0x2c, 0x76, 0x58, 0x58, 0x43, 0xd8, 0x4c, 0xae, 0x1e, 0xbb, 0xd3, 0x1c, 0x06, 0x7f, 0xb4, 0x38, 0xc6, 0xde, 0x0a, 0x16, 0xc9, 0x95, 0xc9, 0x38, 0x37, 0x57, 0x2f, 0xe4, 0xff, 0x5d, 0xcc, 0x53, 0x44, 0x05, 0xd2, 0xe3, 0x8b, 0x01, 0xcd, 0x64, 0xc1, 0x83, 0x9e, 0xb6, 0x00, 0x67, 0x67, 0x82, 0xf7, 0xd3, 0x89, 0x2d, 0x19, 0x88, 0xa4, 0x91, 0xca, 0x32, 0xa5, 0x51, 0xb1, 0x67, 0x52, 0xa2, 0x38, 0x00, 0xd6, 0xe3, 0x5f, 0x77, 0x7b, 0x17, 0x48, 0xeb, 0xf1, 0xb3, 0x43, 0x5c, 0xed, 0x00, 0x9d, 0xc6, 0x29, 0x80, 0xdb, 0xab, 0xce, 0x58, 0xa3, 0x32, 0x36, 0xba, 0x69, 0xf1, 0x3e, 0x24, 0xdf, 0x15, 0x4d, 0xe3, 0xf4, 0x74, 0x1e, 0x80, 0xbe, 0x8e, 0xff, 0x3a, 0x38, 0xf2, 0xc2, 0xb6, 0x9a, 0x65, 0x8b, 0x63, 0xf3, + 0x12, 0xae, 0xba, 0xfd, 0x17, 0xb7, 0x9d, 0xbc, 0x32, 0xaf, 0x5c, 0x60, 0x94, 0xeb, 0xca, 0xe6, 0x6f, 0xef, 0x3d, 0xf1, 0x78, 0x68, 0xf6, 0x68, 0xc3, 0x77, 0x5e, 0x41, 0x18, 0xd9, 0x37, 0x6b, 0x55, 0x4d, 0xdb, 0xda, 0xd6, 0x88, 0x4a, 0xa9, 0x13, 0x31, 0xba, 0x86, 0x96, 0xf8, 0x92, 0x03, 0xed, 0x2b, 0x1f, 0xce, 0xcb, 0xaa, 0xea, 0xec, 0x8f, 0x2d, 0x7a, 0x74, 0x4b, 0xfd, 0xd0, 0xdc, 0x21, 0x89, 0x22, 0x1c, 0x0d, 0xdf, 0xb2, 0x23, 0x36, 0x3f, 0xe9, 0x02, 0xcf, 0x58, 0xe2, 0xed, 0x85, 0xb9, 0xad, 0x71, 0x9b, 0x23, 0xe0, 0x50, 0x28, 0x89, 0x29, 0xfe, 0x18, 0x26, 0xe4, 0x6d, 0xa7, 0xe1, 0x21, 0x53, 0x4f, 0x23, 0xb6, 0x58, 0x19, 0xb0, 0x9b, 0x89, 0x11, 0x93, 0x2b, 0x13, 0x61, 0x54, 0x06, 0x95, 0x0c, 0x3a, 0x62, 0x48, 0xdd, 0x1d, 0x8b, 0xa7, 0x5d, 0x4d, 0x10, 0x51, 0xe2, + 0xf1, 0x21, 0x40, 0x20, 0x9f, 0x73, 0x66, 0xd6, 0x25, 0x5a, 0xa0, 0xf6, 0x6b, 0xc6, 0xb6, 0xc9, 0xf3, 0x6d, 0xf7, 0xbf, 0xc7, 0xf0, 0x55, 0x7e, 0x0d, 0xb9, 0x5b, 0x9e, 0x67, 0x63, 0x4e, 0x7d, 0xb1, 0x94, 0xfa, 0x20, 0x7b, 0x20, 0x96, 0xd7, 0x61, 0xbf, 0x52, 0x6d, 0x08, 0x32, 0xa7, 0xae, 0xb8, 0xb3, 0x07, 0xe2, 0x79, 0x9d, 0x76, 0xea, 0x9b, 0x86, 0xe0, 0xc4, 0x38, 0x2a, 0xe0, 0x38, 0xec, 0x28, 0xf7, 0x8a, 0x65, 0x4a, 0xfc, 0x26, 0xc2, 0xe1, 0x06, 0xec, 0x21, 0x02, 0x47, 0x03, 0x81, 0x53, 0xe9, 0xb3, 0xf3, 0x78, 0xc6, 0x34, 0x74, 0x52, 0x78, 0x4c, 0x57, 0x83, 0x20, 0x53, 0xf1, 0x6d, 0x7b, 0x48, 0xcd, 0x1e, 0x93, 0x9a, 0xec, 0x3e, 0xe3, 0x13, 0x97, 0x2e, 0xdd, 0x1e, 0x4e, 0x58, 0xc0, 0x0a, 0x45, 0xb6, 0xfd, 0x05, 0xb6, 0xc7, 0x90, 0xf4, 0xc6, 0x9a, 0x5c, 0x4e, 0x3b, + 0x49, 0x6a, 0x4c, 0x4a, 0xfe, 0xd8, 0x8d, 0xcc, 0xa9, 0xb1, 0x3b, 0x4d, 0x01, 0xf0, 0x37, 0xa3, 0x63, 0xec, 0x9c, 0x3b, 0x57, 0x26, 0xab, 0x2c, 0xc6, 0xb5, 0x93, 0xbe, 0x18, 0xff, 0x84, 0xa1, 0x70, 0x3c, 0x4a, 0x6d, 0xd3, 0x59, 0x27, 0x8e, 0xed, 0x85, 0x03, 0xe3, 0x30, 0x21, 0x91, 0x76, 0x14, 0x30, 0x12, 0xb8, 0x3c, 0xce, 0xc4, 0x0d, 0xf8, 0x13, 0xba, 0x8b, 0x99, 0x78, 0x17, 0x94, 0x68, 0xce, 0xeb, 0x9c, 0x5e, 0xa5, 0x82, 0xe1, 0x19, 0xb0, 0x57, 0x8b, 0x88, 0x44, 0x9b, 0x8c, 0x05, 0x77, 0xd2, 0x08, 0xd4, 0x0e, 0x86, 0x7a, 0x8f, 0x2d, 0x95, 0xe5, 0xd7, 0xcc, 0x5d, 0xb6, 0xa1, 0x6a, 0x43, 0xbd, 0xb7, 0xa5, 0xbe, 0xb2, 0xd0, 0x23, 0xa2, 0x96, 0xd2, 0x9f, 0xb1, 0x7f, 0x65, 0x87, 0xd9, 0xdf, 0xbd, 0x30, 0xe4, 0x09, 0xee, 0xb7, 0xf4, 0x3e, 0x05, 0x4c, 0xe0, 0x14, 0x40, + 0xb1, 0xbd, 0x70, 0x4c, 0x02, 0x09, 0xc6, 0xd1, 0x39, 0xc4, 0x06, 0x6e, 0x54, 0x0e, 0x61, 0x06, 0x51, 0xd3, 0x34, 0x4e, 0x4b, 0x0d, 0x11, 0xb5, 0x80, 0x4c, 0x0b, 0x0f, 0x46, 0x8a, 0x8b, 0x2b, 0xbf, 0x56, 0x1b, 0x8c, 0xb3, 0xf0, 0x77, 0x6c, 0x62, 0x70, 0x31, 0x10, 0x98, 0x4d, 0x08, 0xb9, 0x67, 0x87, 0xbd, 0x6e, 0xa7, 0x1d, 0x23, 0xf8, 0x1c, 0x59, 0x0e, 0x87, 0xe0, 0x45, 0xd7, 0x46, 0xf0, 0xd3, 0x67, 0xa6, 0xe4, 0xc9, 0x66, 0x44, 0xf7, 0xb6, 0x99, 0xe6, 0x3b, 0x23, 0xfa, 0x9f, 0x79, 0x11, 0xc6, 0xee, 0x4b, 0xd1, 0x2a, 0x41, 0x27, 0x5e, 0x07, 0x17, 0x31, 0xcc, 0x91, 0x7e, 0x2b, 0x5f, 0x44, 0x92, 0x34, 0xc0, 0xb3, 0x84, 0x28, 0x0e, 0xe1, 0x65, 0x44, 0xae, 0x16, 0x64, 0x16, 0xc1, 0x25, 0x42, 0x95, 0x51, 0xc9, 0x6e, 0x82, 0x8f, 0x5a, 0xf1, 0x67, 0x6c, 0xd5, 0x93, 0xb4, + 0xa0, 0xb9, 0x3b, 0x1d, 0x28, 0xd3, 0x58, 0x8a, 0xbc, 0xb9, 0x64, 0x2e, 0x6e, 0xf6, 0xe2, 0x6b, 0xcf, 0x1e, 0xc3, 0x20, 0xa4, 0x1c, 0x4a, 0xde, 0x1c, 0x3c, 0xeb, 0x86, 0xd4, 0xac, 0x5f, 0x4f, 0xcf, 0x7a, 0x4c, 0xfd, 0xde, 0x7b, 0xe4, 0x5f, 0x2f, 0xcd, 0x38, 0x51, 0x24, 0x22, 0x42, 0x18, 0xdc, 0x47, 0x6e, 0x86, 0x93, 0x9b, 0xf0, 0x57, 0x43, 0x32, 0x54, 0xe0, 0x1a, 0x36, 0x21, 0x7b, 0xc6, 0x26, 0xe4, 0x60, 0x9a, 0xf5, 0x3a, 0x31, 0x44, 0xf7, 0x0e, 0x9b, 0x2e, 0xa0, 0x0f, 0x88, 0xb5, 0x62, 0xad, 0x52, 0x8e, 0xcb, 0x96, 0x08, 0x21, 0x0a, 0x4a, 0x59, 0xcd, 0xe3, 0x3e, 0xce, 0x29, 0x83, 0x0f, 0x90, 0x03, 0x9b, 0x76, 0xaa, 0x53, 0x1b, 0x88, 0x0a, 0x5b, 0x76, 0x7c, 0x7d, 0xc5, 0x8a, 0xaf, 0x6f, 0x6f, 0x11, 0x92, 0x92, 0xb6, 0x6d, 0x4f, 0x2e, 0x1b, 0x7a, 0x72, 0xfb, 0x6c, + 0x09, 0xf8, 0x0b, 0xf6, 0x60, 0x9b, 0xea, 0xd5, 0x46, 0x1e, 0xba, 0xe1, 0xf9, 0x9b, 0x2b, 0x2b, 0x6f, 0x7e, 0xfe, 0x86, 0x07, 0x57, 0x3d, 0xb9, 0xa1, 0x02, 0x69, 0x69, 0xc8, 0x9d, 0xd8, 0x81, 0x6d, 0x9a, 0x53, 0x5b, 0xea, 0x4c, 0xab, 0xa6, 0xfb, 0x7a, 0x65, 0x4e, 0x72, 0x9a, 0xe6, 0x66, 0x7c, 0xbd, 0x54, 0x97, 0xc6, 0x1a, 0xdf, 0xa3, 0xfb, 0x27, 0x7c, 0xbd, 0x48, 0x88, 0xcf, 0x3f, 0x81, 0x5f, 0x6d, 0x44, 0xdd, 0xb9, 0x2c, 0x05, 0xc9, 0x15, 0x75, 0x42, 0xb8, 0x5c, 0x95, 0xc2, 0x50, 0xe9, 0xc4, 0xbc, 0x0e, 0x74, 0x06, 0xa7, 0xff, 0xe8, 0x42, 0xd6, 0x55, 0x09, 0x2e, 0x24, 0x6d, 0xf5, 0x3a, 0x3d, 0x08, 0x7b, 0x09, 0x71, 0x6a, 0x0a, 0x0e, 0x44, 0x13, 0x5c, 0xb6, 0x26, 0x80, 0x2c, 0x7a, 0x18, 0x7d, 0xd1, 0x9f, 0x8c, 0xbd, 0x64, 0xae, 0x6c, 0x9a, 0x9b, 0xbf, 0xa1, 0x21, + 0x2b, 0x3f, 0x27, 0xa0, 0xaa, 0x1e, 0xfb, 0xf6, 0x5b, 0x66, 0x87, 0x04, 0xb4, 0xf9, 0xf5, 0x62, 0xab, 0x0a, 0xd8, 0x04, 0x1e, 0x13, 0x73, 0xea, 0xb2, 0xb3, 0xac, 0x2d, 0x47, 0x1d, 0xdc, 0x2f, 0x32, 0xe5, 0xb8, 0x0f, 0x51, 0x9f, 0x9b, 0x74, 0x63, 0xf7, 0x34, 0xb5, 0xfa, 0x5a, 0xbc, 0xe4, 0x0a, 0x95, 0x0d, 0xe3, 0x8c, 0x71, 0x38, 0xe7, 0x65, 0xcc, 0xc3, 0x84, 0x01, 0xe9, 0x19, 0x74, 0x58, 0x37, 0x8d, 0x07, 0x84, 0x44, 0x59, 0x03, 0x12, 0xd6, 0x8c, 0x38, 0x86, 0xdb, 0x89, 0xb9, 0x0d, 0x1c, 0xb9, 0x6d, 0x01, 0x98, 0xb4, 0xba, 0x26, 0x67, 0x3f, 0xa2, 0xfe, 0x69, 0x3f, 0xb0, 0xac, 0x38, 0x14, 0xcb, 0xae, 0x52, 0xb3, 0xdd, 0xdf, 0x1e, 0xfb, 0xc6, 0x93, 0x66, 0x97, 0x10, 0xec, 0xe1, 0xdc, 0xf9, 0xe8, 0x0f, 0x86, 0xfa, 0xd5, 0xba, 0xbd, 0x85, 0xb1, 0xcb, 0x10, 0x6f, 0x99, + 0x75, 0x57, 0xf2, 0x38, 0x97, 0x3e, 0x08, 0x37, 0xb3, 0xe0, 0xbb, 0xff, 0x9b, 0xf9, 0x20, 0x25, 0x7b, 0xe7, 0x25, 0xb3, 0xa7, 0x78, 0x96, 0x41, 0x31, 0x1a, 0x72, 0x2f, 0x03, 0x50, 0xc8, 0xc6, 0x5c, 0x8b, 0x91, 0xd7, 0x3c, 0x59, 0x92, 0xe6, 0xa1, 0xf2, 0x91, 0xd7, 0xe1, 0x5b, 0xb6, 0xa9, 0xf7, 0xae, 0x15, 0x25, 0x25, 0x2b, 0xee, 0xea, 0xfd, 0x2e, 0xfc, 0x52, 0x5c, 0x8c, 0xbe, 0x1c, 0xfc, 0xed, 0x43, 0x3d, 0x3d, 0x0f, 0xfd, 0xf6, 0x20, 0xfe, 0x32, 0xff, 0xe1, 0xdf, 0xee, 0xff, 0x2e, 0xe8, 0x02, 0x55, 0x23, 0x77, 0xcf, 0xe9, 0xba, 0x7b, 0xa4, 0x8a, 0x24, 0xe1, 0xb7, 0xae, 0x39, 0xf0, 0x1b, 0xa0, 0x7e, 0x4d, 0xf5, 0x9d, 0xfa, 0xd1, 0x86, 0x0d, 0x3f, 0xba, 0xaf, 0x8f, 0x7a, 0xfa, 0x69, 0xaa, 0xef, 0xbe, 0x1f, 0x6d, 0xb8, 0x11, 0x7d, 0x7f, 0x1c, 0xe3, 0x7f, 0xf6, 0x51, + 0xe6, 0xcf, 0x70, 0xaf, 0x65, 0xc8, 0x3f, 0x13, 0x31, 0x4e, 0x06, 0xce, 0x1b, 0x0b, 0xbb, 0x82, 0x1a, 0x9b, 0x70, 0x55, 0x36, 0x44, 0x8a, 0x64, 0x84, 0x0c, 0xa5, 0x09, 0x76, 0xf1, 0x21, 0x3e, 0xf5, 0xa4, 0xb9, 0x26, 0xee, 0xe4, 0x31, 0x7f, 0xf6, 0x5a, 0xb7, 0x5d, 0x3e, 0xf8, 0x1e, 0x79, 0xee, 0xd2, 0x69, 0x8e, 0x27, 0x82, 0xfb, 0x25, 0x46, 0xe0, 0x94, 0xea, 0x5f, 0x95, 0xee, 0x1f, 0xf7, 0x6c, 0xc0, 0xc6, 0x97, 0xf9, 0x18, 0x1a, 0x11, 0xd0, 0x58, 0xc0, 0x44, 0xff, 0x2e, 0x25, 0xea, 0x3f, 0xc3, 0x90, 0x71, 0x6f, 0x60, 0x54, 0x6c, 0x3e, 0x64, 0xc1, 0xd8, 0x7c, 0xf8, 0x06, 0x44, 0xd2, 0x10, 0xc7, 0x95, 0x7e, 0x07, 0xa6, 0x61, 0xf3, 0x20, 0x9f, 0xb9, 0x03, 0xbe, 0x43, 0x8d, 0xec, 0x12, 0x2a, 0x8a, 0xe4, 0xd4, 0x39, 0x9c, 0xf7, 0x44, 0x1a, 0x38, 0xd3, 0x24, 0x55, 0x4d, + 0xa8, 0xb5, 0xea, 0x0c, 0x49, 0x95, 0x01, 0xec, 0x1c, 0x07, 0x25, 0x3c, 0x10, 0x8b, 0x2b, 0xe9, 0x1d, 0x97, 0xd6, 0xfd, 0xfc, 0x9b, 0xf7, 0x8d, 0x54, 0xbc, 0xd7, 0x78, 0xea, 0x1f, 0x8f, 0xa1, 0x57, 0x91, 0x4b, 0xac, 0x4e, 0x3a, 0x77, 0xe0, 0xbe, 0x1b, 0xc0, 0x7b, 0x6c, 0x60, 0xd1, 0x86, 0x7c, 0x75, 0x90, 0x7c, 0x20, 0x43, 0x37, 0xf9, 0xaf, 0xc2, 0x77, 0x8a, 0x91, 0x0e, 0x91, 0xc7, 0xc0, 0x05, 0x03, 0x06, 0xac, 0x53, 0x26, 0xe6, 0x67, 0xde, 0x25, 0x26, 0xc4, 0xb8, 0x4e, 0x68, 0x7a, 0x4e, 0x91, 0xd4, 0x3f, 0xfe, 0xab, 0x9f, 0x7f, 0xf6, 0x1e, 0x59, 0x7d, 0x89, 0xac, 0x47, 0xef, 0x48, 0x2f, 0x16, 0x9a, 0x0c, 0x5e, 0x2f, 0xc1, 0x5d, 0xd8, 0xf7, 0x14, 0xe5, 0x24, 0xa0, 0xf1, 0xe0, 0x39, 0x47, 0xbf, 0xe9, 0x3b, 0xa2, 0x22, 0x54, 0xb8, 0x77, 0xa5, 0x4b, 0xc8, 0x33, 0x4d, + 0xdf, 0x13, 0xf4, 0x4f, 0x80, 0x18, 0xd7, 0xcf, 0x7f, 0xcd, 0xbd, 0xe9, 0xbd, 0x69, 0x5b, 0x83, 0xb7, 0x07, 0xe2, 0x73, 0x28, 0x48, 0x33, 0xf7, 0xcc, 0x24, 0x7b, 0xd0, 0x34, 0xd3, 0x43, 0x20, 0x26, 0xf5, 0xff, 0x83, 0xec, 0x11, 0x75, 0x28, 0xa9, 0xe3, 0x33, 0xcb, 0x1e, 0xec, 0x20, 0x39, 0x32, 0x33, 0xf1, 0xf9, 0xfb, 0xd3, 0x88, 0xd2, 0xa0, 0x3c, 0x08, 0x70, 0x8d, 0x79, 0x8b, 0xe0, 0xd8, 0xbc, 0x80, 0x8f, 0x6b, 0xe9, 0x29, 0xe9, 0x9f, 0x4f, 0x93, 0x97, 0x4c, 0x44, 0x41, 0x32, 0x97, 0x37, 0x69, 0xcc, 0x06, 0xce, 0x49, 0x66, 0xc1, 0xc4, 0x70, 0xf1, 0x60, 0x4d, 0x32, 0x13, 0x37, 0x58, 0xfe, 0x35, 0x07, 0x1b, 0xb9, 0xa6, 0x98, 0x74, 0xe9, 0x1a, 0x32, 0x12, 0x1c, 0x26, 0x20, 0x0a, 0xc7, 0x3f, 0xa6, 0x3f, 0x84, 0x34, 0x23, 0x8f, 0xa8, 0x4c, 0x56, 0x28, 0x50, 0x6e, 0x8e, + 0xc6, 0x6b, 0x30, 0xb0, 0xfd, 0x33, 0x32, 0xbc, 0x6a, 0xb7, 0x52, 0xe3, 0x75, 0xfb, 0x21, 0xdb, 0x9a, 0xca, 0x2b, 0xc1, 0x91, 0x09, 0xec, 0xc1, 0x8e, 0x5d, 0xee, 0x75, 0xfa, 0x14, 0x25, 0x81, 0xab, 0xc9, 0xd1, 0x12, 0x74, 0xa7, 0xb1, 0xfb, 0x6f, 0x27, 0x4f, 0x01, 0xea, 0xe5, 0x65, 0xfd, 0x2f, 0x8c, 0x9d, 0x7a, 0x6e, 0x6c, 0x5e, 0x97, 0x54, 0x25, 0x52, 0x09, 0xdc, 0x8d, 0x5d, 0x03, 0x15, 0x7d, 0x8f, 0xed, 0x68, 0x17, 0x8d, 0x95, 0x90, 0x17, 0x25, 0xed, 0xdb, 0x1e, 0xed, 0xef, 0xdd, 0xdc, 0x19, 0x51, 0x2b, 0x35, 0x7c, 0xfa, 0xb3, 0xce, 0xa5, 0xc3, 0x6f, 0xb0, 0x9f, 0x3c, 0xf4, 0x10, 0xfb, 0xc9, 0x1b, 0xc3, 0xa3, 0xcb, 0x17, 0xc8, 0x84, 0x7c, 0x47, 0xae, 0x55, 0x0e, 0x29, 0xca, 0xba, 0x07, 0x87, 0x9f, 0xbc, 0xb1, 0xc2, 0x9b, 0xeb, 0x95, 0xa5, 0x64, 0x97, 0xf9, 0xe3, + 0x1f, 0x33, 0x72, 0x3c, 0xa7, 0x8e, 0x64, 0x9b, 0x1a, 0xe2, 0x32, 0x0a, 0x6e, 0x00, 0xd2, 0x08, 0xd1, 0x0c, 0x4d, 0x31, 0x23, 0x02, 0x28, 0xc2, 0xf0, 0x79, 0x04, 0x1f, 0x4d, 0x0c, 0x62, 0x36, 0x5e, 0x7f, 0x3a, 0xe6, 0xde, 0x8e, 0x12, 0x8b, 0xa7, 0x27, 0x06, 0x88, 0xa0, 0xdf, 0xed, 0x72, 0x3a, 0x2c, 0xa6, 0x2c, 0x9d, 0x12, 0xe5, 0xc6, 0xc9, 0x03, 0x79, 0x42, 0x1e, 0x8a, 0xf3, 0xe3, 0xfc, 0xf3, 0x39, 0xd7, 0x08, 0xe4, 0xd9, 0x3d, 0xc5, 0xc5, 0x86, 0x0f, 0x05, 0xd6, 0xd4, 0x84, 0x2b, 0x00, 0x7d, 0x4a, 0x25, 0xed, 0x9a, 0x37, 0xf6, 0xdc, 0xa9, 0xb1, 0xe7, 0x97, 0x2c, 0x7b, 0x19, 0x50, 0xf7, 0x9d, 0xf8, 0xdb, 0xbc, 0x3a, 0xbe, 0x56, 0xa9, 0x8e, 0x74, 0x6e, 0xee, 0xed, 0x7f, 0x74, 0x6b, 0xbb, 0x84, 0x7c, 0x63, 0xac, 0x4c, 0xd4, 0xbe, 0xfd, 0xb1, 0x45, 0x15, 0x03, 0x9d, + 0x8d, 0x6e, 0x81, 0x0a, 0x14, 0xca, 0x16, 0x2c, 0x1f, 0x1d, 0x7e, 0x03, 0xc8, 0x1e, 0x7a, 0x08, 0xc8, 0xde, 0x18, 0x5e, 0xda, 0x39, 0x20, 0x96, 0xc1, 0xa9, 0x55, 0xdc, 0xf8, 0xe4, 0xf0, 0x83, 0xeb, 0x5e, 0xd8, 0x52, 0x29, 0xb7, 0xe6, 0x3a, 0xf8, 0x42, 0x4e, 0xae, 0x7c, 0x76, 0xfc, 0x13, 0xfe, 0xfb, 0x70, 0x9e, 0x6a, 0xc8, 0xd3, 0x7d, 0x8f, 0xa3, 0x70, 0x90, 0xa7, 0xe3, 0x43, 0xfe, 0x97, 0x0f, 0x77, 0x0b, 0x49, 0x92, 0xa8, 0xfa, 0xaa, 0xbd, 0x49, 0x90, 0x51, 0x08, 0x3b, 0xa8, 0x74, 0xe2, 0xd6, 0xaf, 0x68, 0xe8, 0xc2, 0x0d, 0x11, 0x97, 0xe8, 0x99, 0x68, 0x88, 0x98, 0x9e, 0xb4, 0x9a, 0x79, 0x7a, 0xe3, 0x64, 0xe0, 0x5a, 0xed, 0xe0, 0x21, 0x34, 0xa6, 0x1a, 0x13, 0xb8, 0x2d, 0xae, 0x71, 0xa2, 0xd1, 0x00, 0x42, 0x93, 0xa3, 0xc9, 0xc9, 0x0e, 0x87, 0x82, 0x01, 0x3f, 0xca, + 0x0e, 0xc5, 0x65, 0xf9, 0x98, 0xc2, 0x28, 0xa6, 0x0c, 0x16, 0x5c, 0x75, 0xc1, 0x09, 0xa7, 0x14, 0xd2, 0xc7, 0xb1, 0x4d, 0x4c, 0xd5, 0xd0, 0xa3, 0xeb, 0x2a, 0x2a, 0xd6, 0x3d, 0x3a, 0x94, 0xfa, 0x1c, 0xfb, 0x99, 0x64, 0xd9, 0x53, 0x7f, 0xbc, 0xed, 0x9e, 0xcf, 0xce, 0xf4, 0xf5, 0x9d, 0xf9, 0xec, 0x9e, 0x5b, 0xff, 0xf4, 0xd4, 0x32, 0x09, 0xfb, 0xfe, 0x7b, 0xef, 0x3d, 0xd2, 0x72, 0xe4, 0xdd, 0x9d, 0x3b, 0x7e, 0x70, 0xb4, 0xb5, 0xf5, 0xe8, 0x0f, 0x76, 0xec, 0x7c, 0xf7, 0x48, 0x0b, 0xb9, 0xf5, 0xe8, 0xdf, 0x9f, 0x5e, 0x94, 0x66, 0x3f, 0xde, 0x18, 0xee, 0x7b, 0xea, 0xef, 0x47, 0xc7, 0xf6, 0xc1, 0x23, 0xb1, 0x17, 0xd5, 0x68, 0xfd, 0x02, 0x9e, 0xcf, 0x03, 0xf8, 0x7c, 0xfa, 0x89, 0x68, 0xb2, 0xc0, 0x02, 0x20, 0x72, 0x6c, 0xc4, 0x8c, 0x14, 0x09, 0xa8, 0x4c, 0x9a, 0x2b, 0x0e, + 0x5d, 0x1a, 0x51, 0xe5, 0x4f, 0xbf, 0xd7, 0x98, 0xa5, 0x84, 0x02, 0x3a, 0x7c, 0x42, 0xec, 0xe0, 0x23, 0xee, 0x49, 0xe9, 0x48, 0x7b, 0xb2, 0x02, 0x24, 0x02, 0x23, 0x06, 0x0a, 0xcb, 0xc0, 0x19, 0xc7, 0x2c, 0x07, 0xf5, 0x3c, 0xfb, 0xf7, 0xf4, 0x79, 0x05, 0x7f, 0xbc, 0x74, 0xde, 0x98, 0x13, 0x0e, 0xea, 0xd8, 0xf7, 0xa1, 0xb8, 0xfb, 0x00, 0x58, 0x51, 0xda, 0xd5, 0xb5, 0xad, 0x33, 0x50, 0xb3, 0xfb, 0x9b, 0x9b, 0xc1, 0x1f, 0x28, 0x09, 0xf8, 0x93, 0x2b, 0xd9, 0x13, 0x2b, 0x5e, 0x90, 0x70, 0x5e, 0xf9, 0x27, 0xb9, 0x54, 0xa8, 0x0f, 0xd8, 0xd9, 0x76, 0x28, 0xdd, 0xf2, 0xe7, 0x7b, 0x55, 0xb5, 0xfd, 0x9b, 0xeb, 0x17, 0x1c, 0x1f, 0x2e, 0xc3, 0x70, 0x6f, 0x82, 0x23, 0x7b, 0x04, 0x8e, 0xdb, 0x81, 0x72, 0x4d, 0xe0, 0x15, 0x47, 0xd1, 0x5a, 0x48, 0x0d, 0x9e, 0xd2, 0x37, 0x60, 0xe3, + 0x2f, 0xca, 0xd6, 0x60, 0x44, 0xf0, 0x3d, 0xc1, 0xa9, 0xca, 0x24, 0x70, 0xf1, 0x1d, 0xc0, 0xc1, 0xc1, 0x37, 0x87, 0x52, 0x32, 0x7e, 0xa3, 0x18, 0xd1, 0xb8, 0x10, 0x32, 0x44, 0xd9, 0xdf, 0x4f, 0xa7, 0xf1, 0x4a, 0x5f, 0x5f, 0x06, 0xdb, 0xfc, 0xec, 0x12, 0x7b, 0x23, 0x6d, 0x61, 0xab, 0xa9, 0xe2, 0x34, 0x72, 0xc9, 0x60, 0x9c, 0xb1, 0x11, 0xfa, 0xe3, 0x33, 0x67, 0xd8, 0x59, 0xdc, 0xf8, 0xc6, 0xc7, 0x99, 0x83, 0xe9, 0x9c, 0x0d, 0x72, 0x44, 0xde, 0x50, 0xe0, 0x33, 0x85, 0xfc, 0x40, 0x47, 0x10, 0x1c, 0xa5, 0x24, 0x21, 0x12, 0xa4, 0x75, 0x2b, 0x7a, 0xad, 0x0a, 0x49, 0x6c, 0x90, 0xdf, 0x81, 0x2c, 0x05, 0x92, 0x20, 0x73, 0x00, 0x76, 0x9c, 0xc2, 0x63, 0x21, 0xb7, 0xdc, 0xf0, 0xee, 0xd3, 0xb7, 0x2e, 0x89, 0x5e, 0x2a, 0xd9, 0xf7, 0x8b, 0xfb, 0xd3, 0x23, 0x28, 0xb0, 0x38, 0x98, + 0xdc, 0xa5, 0xf7, 0x8d, 0xb0, 0x85, 0xe0, 0xdd, 0xbe, 0x9b, 0xf2, 0x95, 0xc1, 0xb1, 0x15, 0x68, 0x00, 0xdc, 0x79, 0x29, 0x84, 0xeb, 0xb3, 0x31, 0xc5, 0xfb, 0xd7, 0x24, 0x2b, 0xb9, 0x1a, 0xd9, 0x08, 0xb3, 0x89, 0x32, 0x18, 0x78, 0x82, 0xaf, 0x9f, 0xc0, 0xc0, 0xff, 0x31, 0x4f, 0x1f, 0x75, 0x70, 0xa5, 0xb3, 0xb5, 0xd4, 0xe6, 0x6b, 0x10, 0x8f, 0x93, 0x60, 0xe8, 0x12, 0x38, 0xcf, 0xce, 0x9a, 0x99, 0x82, 0x78, 0x1e, 0x78, 0xff, 0xfd, 0x07, 0x3e, 0xe2, 0xc6, 0x8c, 0xfc, 0x66, 0x6b, 0x98, 0xd3, 0x84, 0x94, 0xc8, 0x27, 0x76, 0x27, 0x45, 0x6a, 0xec, 0x2d, 0x91, 0x0d, 0xe8, 0x06, 0xee, 0xbc, 0x9b, 0x20, 0x6a, 0xa6, 0x47, 0xd3, 0x87, 0xcd, 0x34, 0xe1, 0x62, 0xc9, 0x9d, 0x74, 0xf3, 0x97, 0x35, 0xc1, 0xc7, 0xd6, 0xc0, 0xdd, 0x4d, 0xcd, 0x9f, 0x99, 0x72, 0x4c, 0x91, 0x8b, 0x66, + 0x7e, 0x9e, 0xcb, 0x61, 0xd0, 0x43, 0xf4, 0x27, 0x05, 0x52, 0x1e, 0x9e, 0x72, 0x2c, 0xed, 0x4a, 0xac, 0xd1, 0xa9, 0x65, 0x14, 0x7f, 0x72, 0x09, 0xbe, 0xb4, 0xbb, 0x58, 0xdc, 0xa3, 0xd7, 0xf1, 0xf8, 0xe4, 0xbc, 0xdb, 0x6f, 0xbf, 0xed, 0xb6, 0xda, 0xd5, 0x2d, 0xfe, 0xa3, 0x3f, 0x1b, 0x38, 0xb2, 0x30, 0x0f, 0x6e, 0xed, 0xe2, 0xd1, 0xc4, 0xd0, 0x43, 0x37, 0x94, 0xd5, 0xed, 0x3c, 0xb7, 0x76, 0xc5, 0xf3, 0x7b, 0x9a, 0x4a, 0x76, 0xfe, 0xe8, 0x6e, 0xb0, 0x52, 0x4c, 0x9b, 0x1c, 0xbb, 0xcf, 0x3f, 0x7e, 0xfa, 0xa5, 0x50, 0xd7, 0x96, 0xd9, 0xcf, 0x5e, 0xa4, 0xe7, 0x08, 0x6a, 0x37, 0x3c, 0x3c, 0x94, 0xdd, 0x18, 0x35, 0x37, 0xec, 0x3a, 0x3f, 0x32, 0x7a, 0x7e, 0x57, 0x5d, 0xed, 0xd7, 0x00, 0xf1, 0xca, 0xfd, 0x40, 0xf2, 0xf6, 0x66, 0x89, 0x75, 0xc8, 0x91, 0x43, 0x22, 0x1b, 0xd8, + 0xf8, 0xa7, 0xb4, 0x00, 0xf3, 0x4c, 0x15, 0xc4, 0xb1, 0x73, 0xb9, 0x22, 0x9c, 0xf4, 0x21, 0xe5, 0xf4, 0x84, 0x02, 0xc6, 0xf1, 0x2c, 0xd0, 0x11, 0x4d, 0x55, 0xc6, 0x74, 0x64, 0x2c, 0xf8, 0x33, 0xde, 0x76, 0x65, 0x2a, 0xe8, 0x5c, 0xf3, 0xe9, 0x6b, 0x3e, 0x88, 0xf0, 0x19, 0xae, 0x23, 0x5a, 0x52, 0xe4, 0xf7, 0x6a, 0x2a, 0xb4, 0xe5, 0xd8, 0x9d, 0x95, 0x77, 0x95, 0x3b, 0x2b, 0x35, 0xa5, 0x66, 0xd3, 0x14, 0x37, 0x4d, 0x07, 0xb2, 0xae, 0x90, 0x79, 0x65, 0x43, 0xb3, 0x82, 0x05, 0x5d, 0xc3, 0x25, 0x85, 0x7d, 0x75, 0x41, 0x47, 0xcd, 0x4a, 0xca, 0x17, 0xb5, 0xc9, 0x4a, 0x17, 0xdf, 0xbc, 0xe3, 0xe6, 0xc5, 0xa5, 0xf5, 0x3b, 0x9f, 0x1b, 0x1e, 0x7d, 0xe6, 0xa6, 0xf2, 0x27, 0x4f, 0xe6, 0x76, 0x25, 0xdc, 0x81, 0xae, 0xed, 0x73, 0xc1, 0xab, 0x0e, 0xcb, 0xc1, 0x60, 0xe7, 0xcd, 0x1d, + 0xcd, 0xa3, 0xdd, 0x95, 0x16, 0x6d, 0x62, 0xde, 0x86, 0xe6, 0x9a, 0xcd, 0x0b, 0x62, 0x74, 0x0f, 0xca, 0x6f, 0xe5, 0x2a, 0x8a, 0x15, 0x55, 0x77, 0xad, 0x3d, 0xdc, 0x7b, 0xc3, 0x73, 0x37, 0x57, 0x35, 0x6c, 0x7f, 0x66, 0xd9, 0xc3, 0x3f, 0xaf, 0xc8, 0x8a, 0x56, 0xb5, 0x47, 0xf3, 0x17, 0xd4, 0x85, 0xb6, 0x5b, 0x2d, 0x58, 0x57, 0x8e, 0x6a, 0xdf, 0xde, 0x4d, 0x7d, 0x0e, 0xf9, 0x7e, 0x3d, 0x11, 0x46, 0x27, 0xc2, 0xc9, 0x59, 0x12, 0x53, 0xc0, 0x80, 0xc2, 0x77, 0x38, 0xa1, 0xd1, 0x84, 0x99, 0x28, 0x8c, 0xf5, 0x8c, 0x4c, 0xb3, 0x21, 0x4b, 0x2c, 0x06, 0x44, 0x28, 0x90, 0x15, 0x36, 0x84, 0xc5, 0x7a, 0x31, 0xc4, 0x1e, 0x42, 0x3e, 0x21, 0x02, 0x22, 0x61, 0x1a, 0x3c, 0x90, 0xd3, 0xb9, 0x21, 0x63, 0x72, 0x70, 0xc5, 0xd3, 0x26, 0xc6, 0x94, 0x3d, 0xdc, 0xa1, 0x24, 0xb5, 0x7d, 0x6b, + 0x34, 0x79, 0xad, 0xc5, 0x3f, 0x7d, 0x25, 0x58, 0x1f, 0xb1, 0xa8, 0xb3, 0x1b, 0xe3, 0xbc, 0xbe, 0x03, 0xf3, 0x02, 0xaa, 0x70, 0x53, 0xc9, 0xc8, 0xd6, 0xf2, 0xe5, 0xb7, 0xb6, 0xb2, 0xeb, 0x0e, 0xf6, 0x34, 0x3b, 0x8a, 0x83, 0x7a, 0xf6, 0x35, 0xf2, 0xaf, 0xec, 0x0f, 0xb3, 0x42, 0xe5, 0x6e, 0x53, 0x81, 0x3f, 0x6b, 0x97, 0xa7, 0x6a, 0x5e, 0xc4, 0xd1, 0x50, 0x19, 0x93, 0x6b, 0x36, 0x0e, 0x34, 0xad, 0x6d, 0x74, 0xa3, 0x62, 0xc8, 0x5c, 0x0d, 0x5f, 0x17, 0xfc, 0x3a, 0x45, 0xc7, 0x6b, 0x9a, 0x49, 0xc7, 0x4b, 0xb9, 0xd8, 0xc0, 0x05, 0xf2, 0x24, 0xb5, 0xf4, 0xca, 0xa9, 0x54, 0x1d, 0x65, 0xfc, 0x2c, 0xfd, 0x09, 0xe4, 0xce, 0xea, 0x39, 0xcb, 0xb3, 0x9e, 0x46, 0xca, 0x3f, 0x6a, 0x68, 0x7a, 0x3f, 0xa6, 0xa4, 0x0e, 0xdd, 0x99, 0xd0, 0x01, 0xa7, 0x6f, 0x70, 0xc2, 0x22, 0xd2, 0xcc, + 0x2a, 0x53, 0x7c, 0x39, 0x87, 0x2e, 0xf1, 0xbb, 0xc0, 0x87, 0xf4, 0x27, 0xec, 0x28, 0x7a, 0xdb, 0x17, 0xdc, 0x79, 0xd6, 0xb2, 0x8f, 0xd2, 0xe7, 0xe0, 0xbb, 0xa7, 0xe9, 0x82, 0x4d, 0xd7, 0xad, 0x0b, 0xd6, 0xcf, 0xa0, 0x0b, 0x86, 0xc7, 0xd0, 0x33, 0x55, 0x13, 0x5c, 0x50, 0x41, 0xa9, 0xe2, 0x10, 0x5d, 0x27, 0xd8, 0x1f, 0x89, 0x9c, 0xd9, 0x31, 0x2b, 0xd5, 0x65, 0x33, 0x6c, 0xba, 0xf2, 0x86, 0xd1, 0x7c, 0xb3, 0xc3, 0x55, 0x93, 0x28, 0xca, 0x71, 0x8a, 0xc8, 0xc3, 0x87, 0xc9, 0x1f, 0xdf, 0xf5, 0xa7, 0x67, 0xb6, 0x06, 0x78, 0x6c, 0x8b, 0xd9, 0x0e, 0x7a, 0xd9, 0x5f, 0xea, 0x9c, 0xe0, 0x43, 0x5d, 0xd5, 0xc6, 0x37, 0x80, 0xfe, 0xf5, 0x2b, 0xa7, 0xe0, 0xda, 0xe8, 0xc7, 0xc7, 0x79, 0x76, 0xea, 0xdf, 0x44, 0x11, 0xd2, 0x01, 0x47, 0x67, 0xd4, 0x01, 0x9b, 0x32, 0x3a, 0xe0, 0xfc, + 0xdc, 0xab, 0x75, 0xc0, 0xd4, 0x24, 0x1d, 0x30, 0x35, 0x5d, 0x07, 0xac, 0xce, 0xe8, 0x80, 0x53, 0x86, 0x43, 0x4e, 0xff, 0xc6, 0xb3, 0xef, 0x8f, 0xc6, 0x15, 0x2f, 0xeb, 0x6b, 0xb3, 0x17, 0x7f, 0x6b, 0x71, 0x76, 0xad, 0xfe, 0x25, 0x65, 0xbc, 0x70, 0x3f, 0xa0, 0x8c, 0x95, 0x79, 0x9e, 0xa8, 0x9e, 0x65, 0x4f, 0x9a, 0x73, 0xa4, 0xef, 0x6a, 0xca, 0x82, 0x1d, 0xdf, 0x9a, 0xdd, 0x19, 0x78, 0x57, 0x96, 0x63, 0x3e, 0xc1, 0xb2, 0x59, 0x85, 0x9e, 0xbc, 0x4a, 0xa3, 0x03, 0x4a, 0xc2, 0x5e, 0x2f, 0x5b, 0x06, 0x2e, 0xc2, 0xbf, 0xef, 0xba, 0x1c, 0xe0, 0x91, 0x70, 0x91, 0x4c, 0x9a, 0xeb, 0x05, 0x3b, 0x2c, 0x59, 0xec, 0x77, 0x1c, 0x4e, 0xf0, 0x3c, 0xdb, 0x52, 0x0d, 0x4a, 0xb2, 0x2c, 0xec, 0x0e, 0x6f, 0x9e, 0x54, 0x16, 0x0f, 0xe3, 0xfd, 0xcf, 0x1f, 0xff, 0x84, 0xca, 0x4b, 0xfb, 0x70, + 0x4b, 0x01, 0xcd, 0x18, 0xff, 0x13, 0xdd, 0xaf, 0xf3, 0x3f, 0xd4, 0xfd, 0x3a, 0xa7, 0xe9, 0x7e, 0x4b, 0x8b, 0x63, 0x85, 0x39, 0xe1, 0x50, 0xc0, 0xe5, 0xb0, 0x9a, 0xa7, 0xeb, 0x7e, 0x27, 0x0e, 0x0c, 0xe2, 0x78, 0xd2, 0xd1, 0xab, 0x38, 0x92, 0x03, 0xd3, 0xe6, 0xd8, 0x64, 0xdd, 0xef, 0xac, 0xe2, 0xc7, 0xd6, 0x2f, 0x3c, 0xdc, 0x0f, 0x05, 0xbc, 0xda, 0x48, 0xdf, 0x9a, 0xca, 0x35, 0x47, 0x5b, 0xf3, 0x06, 0x97, 0xcc, 0x73, 0x15, 0x49, 0xb3, 0xd4, 0x81, 0xd2, 0xce, 0x8a, 0x68, 0x67, 0xb1, 0x75, 0xc3, 0x96, 0x75, 0xeb, 0x86, 0x37, 0x1a, 0xa3, 0x6d, 0x85, 0x25, 0xb3, 0x8b, 0x3c, 0x52, 0x99, 0x5a, 0x4c, 0x1d, 0x8e, 0xd5, 0x64, 0x37, 0x2d, 0x2e, 0xb4, 0x56, 0x96, 0x45, 0x94, 0x96, 0x6d, 0x7d, 0xf5, 0xa3, 0x6d, 0x41, 0x95, 0xb7, 0x2c, 0xd4, 0xa0, 0xb2, 0xb9, 0x6d, 0xc6, 0x9c, + 0x0a, 0x4f, 0xc3, 0xdc, 0x5d, 0xed, 0x49, 0x73, 0xc4, 0xa7, 0xd3, 0x9b, 0xf5, 0x62, 0x25, 0x77, 0x5e, 0xc6, 0xc7, 0x71, 0x5e, 0x6d, 0xe3, 0x74, 0xbd, 0xaf, 0x69, 0x92, 0xde, 0xd7, 0x48, 0x18, 0xbe, 0x4c, 0xef, 0xcb, 0xe5, 0x2c, 0xa6, 0x2e, 0x5f, 0xa4, 0x69, 0x91, 0x5a, 0xc4, 0x66, 0x8b, 0xdd, 0xfa, 0x0d, 0x17, 0x68, 0x5a, 0xa3, 0x06, 0x3f, 0x15, 0x7b, 0xf4, 0x28, 0x79, 0x35, 0x79, 0x5c, 0x93, 0x6f, 0xd0, 0x7a, 0x64, 0x63, 0xbb, 0x55, 0x10, 0x00, 0xc7, 0x96, 0x69, 0xf2, 0xb3, 0xc2, 0x26, 0x72, 0x9b, 0xd2, 0x9e, 0x1e, 0x03, 0xce, 0x11, 0x6d, 0x45, 0x3a, 0x5f, 0x01, 0x20, 0x19, 0x88, 0xb3, 0x18, 0x38, 0x10, 0x86, 0x4b, 0xd0, 0x61, 0xc2, 0xa9, 0x1c, 0xe1, 0xc9, 0x74, 0x28, 0x21, 0x44, 0x3a, 0x53, 0x1c, 0x44, 0x06, 0xc0, 0xc0, 0x64, 0xc8, 0xe4, 0xf4, 0xbf, 0xe4, 0xdf, + 0xe5, 0x05, 0x10, 0x90, 0xc6, 0xb3, 0x0a, 0xbd, 0x79, 0x55, 0x10, 0x00, 0xf6, 0x47, 0x8b, 0x35, 0x08, 0x02, 0x57, 0x5c, 0xb8, 0xb0, 0x20, 0x58, 0xaa, 0xa5, 0x2e, 0xe8, 0xad, 0x69, 0xa0, 0x61, 0x17, 0x40, 0xc8, 0xc2, 0x70, 0x46, 0xfd, 0x9b, 0x6d, 0x75, 0x3a, 0x70, 0xbc, 0x25, 0xa4, 0xd1, 0x0a, 0x9c, 0xb3, 0x7a, 0xba, 0xbe, 0xd7, 0x74, 0x2d, 0x7d, 0xaf, 0xe9, 0xab, 0xf4, 0xbd, 0x9c, 0xab, 0x16, 0x56, 0x39, 0xa1, 0x93, 0x4c, 0x2b, 0x2e, 0xb0, 0x3f, 0x96, 0xe4, 0xd5, 0x74, 0x2e, 0x5c, 0x92, 0xdf, 0x1b, 0x73, 0xd6, 0x56, 0x97, 0x15, 0xb8, 0x45, 0xe4, 0x5d, 0xd4, 0xa2, 0xd7, 0xd8, 0x0f, 0x2f, 0x6c, 0xaa, 0xd6, 0x5b, 0xb7, 0xe8, 0xab, 0x37, 0x5e, 0x00, 0x59, 0xaf, 0x21, 0x1c, 0x03, 0xc7, 0xc3, 0x9b, 0x87, 0xf1, 0x7a, 0xf8, 0xda, 0xba, 0x5e, 0xd3, 0x75, 0xe8, 0x7a, 0x4d, + 0x5f, 0xaa, 0xeb, 0x95, 0x20, 0x2a, 0x10, 0x74, 0x3b, 0xed, 0x56, 0x54, 0xbb, 0x44, 0x1c, 0x96, 0x84, 0x39, 0x3a, 0x20, 0x9a, 0x4c, 0x07, 0x94, 0xea, 0x49, 0x8c, 0xd1, 0xb4, 0x49, 0x29, 0xe9, 0xdf, 0x0e, 0x0e, 0xe8, 0xf2, 0x5a, 0x8a, 0x7e, 0xf8, 0xc3, 0x15, 0xb7, 0xcf, 0xf3, 0xe5, 0x2f, 0xba, 0x75, 0x3e, 0xbb, 0x6a, 0xa6, 0x69, 0x1e, 0xe9, 0x6f, 0x72, 0x96, 0x67, 0x9b, 0xd8, 0xa7, 0xc1, 0x6f, 0x22, 0x73, 0xd6, 0x96, 0xd5, 0xde, 0xd8, 0x95, 0x7b, 0xf5, 0xcc, 0xd9, 0x8d, 0xe9, 0x3a, 0xf5, 0x04, 0x75, 0x99, 0x70, 0x23, 0x3f, 0x7d, 0x8b, 0x1c, 0x22, 0x2b, 0x94, 0xa5, 0x0c, 0x25, 0x2b, 0x06, 0xc4, 0x08, 0x03, 0xcf, 0x24, 0x65, 0x4a, 0x2b, 0x12, 0xe1, 0x0e, 0xb8, 0x09, 0x97, 0x23, 0xcb, 0xa7, 0xe4, 0xf1, 0xb2, 0xd2, 0x90, 0x8a, 0xc1, 0x14, 0x19, 0x08, 0xb0, 0xf7, 0xab, + 0x72, 0x92, 0x6e, 0x0d, 0x9b, 0x2a, 0x88, 0xb3, 0x7c, 0x3e, 0x7b, 0x46, 0x68, 0x56, 0xeb, 0x54, 0x16, 0x21, 0x7b, 0x9a, 0x27, 0x38, 0xfb, 0x35, 0x8d, 0x5f, 0x06, 0x02, 0x52, 0x9d, 0x48, 0xa4, 0x95, 0x02, 0x8f, 0xcc, 0xaf, 0xa1, 0x2e, 0x5f, 0x61, 0xc8, 0x7d, 0xd6, 0x66, 0xfb, 0xd8, 0x63, 0x4a, 0xb5, 0x5a, 0x49, 0x2e, 0xb0, 0xb7, 0xd8, 0xc6, 0x36, 0x53, 0x97, 0x55, 0x46, 0x56, 0x94, 0x15, 0x33, 0x99, 0x62, 0x59, 0xe0, 0x5f, 0x26, 0x65, 0xa6, 0xf6, 0x3c, 0xdc, 0x27, 0x31, 0x82, 0xe3, 0x94, 0xd5, 0xa2, 0x1f, 0xeb, 0x98, 0x26, 0x8f, 0x71, 0xaa, 0x16, 0xc6, 0x95, 0x51, 0x19, 0x53, 0xbf, 0xb9, 0xc8, 0xe6, 0x5e, 0xb8, 0x00, 0x7e, 0x8c, 0x0e, 0x0c, 0xf8, 0x19, 0x1b, 0xa6, 0x3e, 0x67, 0x47, 0xc0, 0x51, 0x54, 0xbb, 0x18, 0x12, 0x95, 0x00, 0xde, 0x7f, 0x07, 0xd2, 0x71, 0x3b, + 0x67, 0xd0, 0x71, 0x9b, 0xae, 0xd2, 0x71, 0x13, 0x48, 0xe7, 0xd7, 0x8d, 0x9a, 0xc1, 0x91, 0x90, 0x74, 0xff, 0x0c, 0xad, 0x7a, 0x92, 0x16, 0xb4, 0xe7, 0x19, 0x6e, 0x18, 0xed, 0xba, 0x43, 0xe2, 0xe0, 0x76, 0x5d, 0x7c, 0xcd, 0x5d, 0xcf, 0xa8, 0xb8, 0xe9, 0xe7, 0x06, 0x97, 0xea, 0xf2, 0x5a, 0xe3, 0x3f, 0xfc, 0xc1, 0xca, 0xa3, 0x3d, 0xbe, 0xfc, 0xc5, 0xb7, 0xcc, 0x67, 0x57, 0x72, 0x53, 0xb8, 0x38, 0xc3, 0x0e, 0x4f, 0x4c, 0x09, 0xed, 0x2c, 0xe2, 0x57, 0xd2, 0x35, 0xdf, 0xb9, 0x98, 0x87, 0xe5, 0x1c, 0x8e, 0xb6, 0xcf, 0xa0, 0xe4, 0x76, 0x4e, 0x56, 0x72, 0x9b, 0xbe, 0xa2, 0x09, 0x06, 0x66, 0xe3, 0xb5, 0xc2, 0x23, 0xb0, 0x2a, 0x1c, 0xb9, 0x20, 0xc6, 0x7d, 0x39, 0x9c, 0xc2, 0x75, 0x5a, 0x0a, 0x4a, 0x20, 0x97, 0x1e, 0xdf, 0xb7, 0xf7, 0x1e, 0x29, 0x90, 0xdf, 0xb3, 0x6b, 0xcf, + 0x31, 0x39, 0x08, 0x4e, 0xab, 0x0f, 0x0f, 0x5e, 0x3f, 0xf9, 0xf0, 0xc3, 0x27, 0x77, 0xdd, 0x7d, 0xdf, 0x7d, 0x77, 0x83, 0x37, 0xa7, 0x97, 0x88, 0xcf, 0xd4, 0x1f, 0xc6, 0x70, 0x30, 0x45, 0xdf, 0x6d, 0xba, 0x96, 0xbe, 0x9b, 0xdb, 0x77, 0x6a, 0x5e, 0x9a, 0x89, 0x01, 0xb0, 0x11, 0x41, 0x7d, 0x90, 0x7e, 0x9e, 0x4c, 0x47, 0x33, 0x9b, 0x66, 0xb4, 0x51, 0x47, 0x1d, 0xd4, 0x07, 0xec, 0xce, 0x8b, 0xec, 0x4e, 0xea, 0x0d, 0xdc, 0xc1, 0xd2, 0xbb, 0xb8, 0x31, 0x40, 0x80, 0xa4, 0x0d, 0x34, 0x4d, 0x58, 0x88, 0xca, 0x17, 0x8d, 0x58, 0xfd, 0xcc, 0x25, 0x1c, 0x52, 0x66, 0xb4, 0xd0, 0xce, 0x94, 0x16, 0x1a, 0x67, 0x5d, 0xe0, 0x7a, 0x4f, 0xd3, 0x34, 0xfc, 0x7b, 0xcf, 0x39, 0x87, 0xda, 0xe3, 0xe6, 0xf4, 0xd3, 0x14, 0x17, 0x78, 0x31, 0x49, 0x41, 0x9d, 0x2a, 0x67, 0x40, 0xfe, 0x52, 0x95, + 0x5b, 0x54, 0xe5, 0x35, 0xb9, 0x35, 0x02, 0x4b, 0xa8, 0x44, 0xc1, 0xfe, 0xf4, 0x2c, 0xbb, 0x0c, 0xb9, 0x70, 0x7f, 0x2c, 0x53, 0x0b, 0x04, 0x5a, 0x09, 0xd5, 0x5c, 0xdc, 0x10, 0x90, 0xf1, 0x84, 0x62, 0x7a, 0xb5, 0x27, 0x78, 0xe5, 0x3d, 0xce, 0x87, 0x3b, 0xd0, 0xe6, 0xf3, 0xb5, 0x06, 0xe1, 0xfe, 0xb7, 0xb0, 0x8f, 0x32, 0x55, 0x90, 0xe7, 0x47, 0xfb, 0xef, 0x03, 0xaa, 0x09, 0x1f, 0x31, 0xc3, 0x24, 0xef, 0x2f, 0xe7, 0x24, 0xef, 0x2f, 0x07, 0xf2, 0xfe, 0xb2, 0xf0, 0xd2, 0xfe, 0xbd, 0x5f, 0xd6, 0xd4, 0x35, 0xd1, 0x54, 0x79, 0xfd, 0xbd, 0x6a, 0xaf, 0xbf, 0x57, 0xfd, 0xf5, 0xf6, 0x7a, 0x5d, 0x1d, 0x72, 0x5e, 0xbf, 0x32, 0x9f, 0x17, 0x6b, 0xe2, 0x35, 0x4a, 0x0d, 0xd6, 0xc4, 0x7b, 0x90, 0x4b, 0xa7, 0xd7, 0xe7, 0xc2, 0xe9, 0x9a, 0x67, 0xf6, 0x68, 0xb3, 0x9c, 0x79, 0xf1, 0xc5, + 0x33, 0x4f, 0x3d, 0xfd, 0xe2, 0x8b, 0x4f, 0x3f, 0x7b, 0xf3, 0x3b, 0x28, 0x73, 0xc0, 0x3b, 0x37, 0x9f, 0xdf, 0xfa, 0x36, 0xfa, 0xf2, 0xf6, 0xd6, 0xf3, 0x7b, 0x45, 0x17, 0x9f, 0x78, 0xe2, 0x4d, 0x91, 0xe8, 0xcd, 0x27, 0x9e, 0xb8, 0x28, 0xa2, 0x76, 0xf1, 0x5a, 0xf7, 0xbf, 0xba, 0x6e, 0xfd, 0xab, 0xfb, 0x5b, 0x78, 0xec, 0x73, 0xa0, 0x95, 0xd7, 0xb2, 0xff, 0xd5, 0xf5, 0xeb, 0x5e, 0xdd, 0xdf, 0xca, 0x03, 0x77, 0xa7, 0xe0, 0x16, 0xf2, 0xb2, 0xf7, 0x42, 0xb8, 0xcb, 0xe8, 0xde, 0x4d, 0xff, 0xb1, 0xee, 0x9d, 0xbe, 0xd7, 0x6e, 0xd8, 0x78, 0xe5, 0x7e, 0x84, 0x02, 0xee, 0xe2, 0xd8, 0x50, 0x88, 0xcd, 0x4e, 0x21, 0xc8, 0xe4, 0xfa, 0xc7, 0xe7, 0x62, 0x42, 0xf7, 0x6e, 0xfa, 0x4f, 0x75, 0xef, 0xd4, 0x6f, 0xd8, 0x33, 0x90, 0xf3, 0x65, 0x9f, 0x4e, 0xe1, 0x49, 0xf6, 0x37, 0x13, 0xef, + 0xe0, 0x7c, 0x69, 0xe1, 0x69, 0xd9, 0x8d, 0x63, 0x72, 0x42, 0x49, 0xbf, 0x62, 0x06, 0xdd, 0xbb, 0x69, 0x42, 0xf7, 0xae, 0x24, 0x94, 0x1a, 0xd5, 0x64, 0xdd, 0x3b, 0x98, 0xd0, 0xbd, 0x53, 0xbb, 0x2f, 0xec, 0xfe, 0xfd, 0x93, 0xfd, 0x17, 0x63, 0xc3, 0x0f, 0xae, 0xc2, 0x08, 0xf9, 0xb4, 0x53, 0x1e, 0xdf, 0xbf, 0x1e, 0xcc, 0x66, 0x7f, 0x50, 0xd7, 0xe9, 0x90, 0x66, 0x81, 0xb7, 0x32, 0x67, 0x9d, 0xf9, 0x10, 0xe3, 0xfc, 0x8c, 0xde, 0xdd, 0x74, 0xbd, 0x7a, 0x77, 0xe6, 0xc3, 0x2f, 0x1e, 0xb8, 0x00, 0x6e, 0xb9, 0x08, 0x0e, 0x71, 0x85, 0x48, 0xb8, 0x85, 0x42, 0x13, 0xc1, 0x6b, 0xc5, 0x7b, 0x04, 0xd7, 0x3b, 0x49, 0xeb, 0xdd, 0x4d, 0xff, 0xb7, 0x7a, 0x77, 0xde, 0x23, 0x76, 0xc3, 0xa6, 0x2f, 0x8e, 0x71, 0x6f, 0x9a, 0xbe, 0x2d, 0x78, 0x6b, 0x20, 0x8d, 0x31, 0x43, 0x7c, 0xb1, 0x08, + 0xd3, 0x18, 0xd3, 0x8c, 0x7a, 0x77, 0xd3, 0x14, 0xbd, 0x3b, 0x22, 0x1b, 0x29, 0x82, 0x61, 0x92, 0x98, 0x38, 0x82, 0x21, 0xb8, 0x26, 0xc1, 0x88, 0x42, 0x09, 0xf1, 0xc3, 0xab, 0x38, 0x83, 0xf5, 0xec, 0xb7, 0xc1, 0xe5, 0x99, 0x38, 0x81, 0x25, 0xf7, 0x60, 0x02, 0x81, 0xe5, 0x59, 0xc6, 0x8d, 0xc7, 0x64, 0x80, 0x73, 0x2d, 0x4f, 0x96, 0x08, 0xd2, 0xa3, 0x12, 0xf2, 0x48, 0xc4, 0x73, 0xf1, 0x27, 0x53, 0x33, 0xa3, 0x11, 0x8f, 0xca, 0x62, 0x84, 0xc4, 0x0c, 0x8f, 0xcc, 0x20, 0x31, 0x7c, 0x05, 0x03, 0x13, 0x51, 0xa2, 0x8d, 0x27, 0xb5, 0x4b, 0x97, 0x4e, 0x1a, 0x1b, 0x22, 0x63, 0xeb, 0x5f, 0xa6, 0x7e, 0x73, 0x61, 0xa6, 0xb1, 0x7d, 0xce, 0x6e, 0x04, 0x07, 0xd0, 0x5e, 0x11, 0x1c, 0x5d, 0xa6, 0x6e, 0x49, 0x8d, 0x6f, 0x9a, 0xde, 0xdf, 0x34, 0x45, 0xef, 0x8f, 0x06, 0x36, 0x75, 0x48, + 0xfc, 0x6b, 0x0f, 0x69, 0xc6, 0xe1, 0x5c, 0x63, 0x2c, 0x1c, 0x0c, 0xc6, 0xa1, 0xcc, 0xb3, 0x11, 0xd2, 0xd1, 0x6c, 0x62, 0xf3, 0x8b, 0x2a, 0x40, 0x65, 0xcc, 0xa3, 0xd9, 0x5f, 0xa5, 0xf9, 0xbf, 0x4e, 0x71, 0xa7, 0xff, 0x1a, 0xe2, 0x8e, 0xd8, 0xe5, 0x56, 0xaa, 0x5c, 0xde, 0x49, 0xc6, 0x82, 0x09, 0x52, 0x3a, 0x91, 0x9e, 0x67, 0xaa, 0xa1, 0xa0, 0xaa, 0xf2, 0xb1, 0xd5, 0xeb, 0x5f, 0xd8, 0x92, 0xa8, 0xdb, 0xf4, 0xe8, 0xa2, 0xf8, 0xaa, 0x25, 0xdd, 0xae, 0x28, 0x14, 0x69, 0x3c, 0x45, 0x1d, 0x89, 0x8d, 0xf7, 0xca, 0xd8, 0x10, 0xf8, 0x89, 0x20, 0xb7, 0x71, 0x28, 0x11, 0x6f, 0x8e, 0x7a, 0x14, 0x0a, 0xa5, 0x80, 0xda, 0x95, 0xac, 0x6b, 0xbb, 0xe5, 0xb5, 0x1b, 0x6e, 0xbe, 0xb0, 0xaf, 0x0e, 0x09, 0x32, 0x55, 0x2a, 0x8b, 0xd3, 0xf2, 0xd0, 0xf1, 0x03, 0xb5, 0xab, 0x9b, 0xfd, 0x7a, + 0x93, 0x5e, 0x9a, 0xb2, 0x0f, 0x40, 0x3e, 0x82, 0x3a, 0x8a, 0xe7, 0xbf, 0x3d, 0x29, 0xca, 0x06, 0x80, 0x6f, 0x82, 0x72, 0xdf, 0x84, 0xcc, 0x87, 0xd2, 0x09, 0x11, 0xe4, 0x08, 0x4a, 0x67, 0x42, 0x31, 0x34, 0x9c, 0x1c, 0x41, 0x41, 0xdc, 0xd3, 0x9f, 0x4e, 0xa5, 0xef, 0x44, 0x38, 0x08, 0xa7, 0xd2, 0x77, 0xf0, 0xf0, 0x22, 0x5c, 0x77, 0x7b, 0x17, 0xf2, 0x43, 0x16, 0xfb, 0xbd, 0x2e, 0xa7, 0x52, 0xe3, 0xc2, 0x8b, 0x00, 0xa6, 0x19, 0x0e, 0x66, 0x2c, 0x85, 0xa0, 0xd3, 0x53, 0x9d, 0x02, 0xa5, 0x42, 0xe1, 0x89, 0x36, 0xc7, 0x13, 0x43, 0x4d, 0xb9, 0x02, 0xf0, 0x53, 0x36, 0x24, 0x3b, 0xb9, 0x29, 0xd1, 0x51, 0xe4, 0x51, 0x67, 0x49, 0xa3, 0xae, 0xee, 0x25, 0xab, 0xe2, 0x8b, 0x1e, 0xdd, 0x54, 0x97, 0xd8, 0xf2, 0xc2, 0xfa, 0xd5, 0x8f, 0x55, 0x52, 0x49, 0xb1, 0x14, 0xce, 0xd6, 0xdf, + 0xbc, 0xba, 0xf6, 0xc0, 0xf1, 0x87, 0xe0, 0x0a, 0xa8, 0xaa, 0x42, 0x65, 0x5e, 0x55, 0xdd, 0xbe, 0x0b, 0x37, 0xdf, 0xf0, 0xda, 0x2d, 0x6d, 0xc8, 0x23, 0x1f, 0xc2, 0xe2, 0xf8, 0x27, 0xcc, 0xfb, 0x70, 0x0d, 0xc4, 0x84, 0xf7, 0x4b, 0x6c, 0x07, 0xce, 0xeb, 0xb5, 0x1d, 0x38, 0xbf, 0xda, 0x76, 0x60, 0xca, 0xd8, 0x04, 0x9c, 0x5f, 0x6a, 0x3b, 0x98, 0xdc, 0x6e, 0x66, 0xdb, 0x81, 0x44, 0x02, 0x08, 0x89, 0x57, 0xe2, 0xf5, 0x20, 0xfb, 0x8c, 0xdd, 0x6c, 0x54, 0x29, 0xb8, 0xd2, 0x05, 0xa2, 0x49, 0xfa, 0x49, 0x5c, 0x42, 0x66, 0x8a, 0x6e, 0x92, 0xf4, 0xb9, 0x30, 0x27, 0x4a, 0x93, 0x07, 0x76, 0xef, 0x3e, 0xb0, 0x7f, 0xf7, 0x6e, 0xf6, 0x55, 0x51, 0xc3, 0x86, 0x07, 0x06, 0x86, 0x9f, 0xde, 0x5c, 0x55, 0xb3, 0xe5, 0xe9, 0x15, 0x43, 0x0f, 0xdd, 0x58, 0x27, 0xf8, 0xcb, 0x85, 0x0b, 0x87, + 0xde, 0x38, 0x7b, 0xf6, 0xc2, 0x85, 0xb3, 0x67, 0xdf, 0x00, 0xdf, 0x5e, 0xf6, 0xd8, 0xfa, 0x8a, 0x34, 0xcb, 0x96, 0xb8, 0xe9, 0x89, 0x95, 0x6c, 0x2d, 0x3c, 0x44, 0xed, 0x58, 0x8f, 0x03, 0x41, 0x89, 0xe2, 0x64, 0x2c, 0x3f, 0xd2, 0xb6, 0x5b, 0x67, 0xb0, 0x11, 0x20, 0x7a, 0x6d, 0x4a, 0x1b, 0x09, 0x90, 0xa3, 0x85, 0xd8, 0x2f, 0xf6, 0x99, 0x0c, 0x0a, 0x99, 0x54, 0x02, 0x1f, 0x13, 0x3a, 0x04, 0xd8, 0x50, 0x90, 0x52, 0xa5, 0x46, 0x38, 0x3d, 0x09, 0x84, 0x03, 0x4a, 0x46, 0x4d, 0x36, 0x14, 0x90, 0x5f, 0x7b, 0x30, 0xde, 0x98, 0xad, 0x1e, 0x5d, 0x02, 0x1a, 0x5e, 0xae, 0xb5, 0x78, 0x4d, 0x1a, 0xd1, 0x0f, 0x45, 0x3a, 0xb3, 0xd7, 0xb8, 0x1f, 0x58, 0x3c, 0xc9, 0xe6, 0x15, 0x55, 0xd6, 0xe0, 0x82, 0x3b, 0x96, 0x91, 0x2e, 0xd0, 0x69, 0x2b, 0x28, 0xb3, 0xb6, 0xf5, 0x80, 0x4b, 0x88, + 0xf6, 0x08, 0x55, 0x46, 0x05, 0xfb, 0x5b, 0xa5, 0x41, 0x29, 0x04, 0x9b, 0x12, 0x3a, 0x51, 0xb4, 0x7e, 0x4e, 0x38, 0xb6, 0xbc, 0x23, 0x42, 0x80, 0xf1, 0xf7, 0xd9, 0x73, 0xd4, 0x27, 0xcc, 0x45, 0xc2, 0x45, 0x2c, 0x4d, 0xca, 0x11, 0x0b, 0xa7, 0x87, 0xd0, 0x4b, 0x36, 0x8a, 0x84, 0x24, 0x59, 0xcf, 0x29, 0x40, 0x2d, 0x3c, 0x40, 0x43, 0x86, 0x99, 0xa0, 0x29, 0x9c, 0xcb, 0x07, 0xe2, 0xa8, 0x01, 0x6c, 0xed, 0x48, 0x73, 0x7f, 0x33, 0xdf, 0xc7, 0x2a, 0x31, 0xc4, 0x05, 0x26, 0x39, 0xda, 0xa5, 0xce, 0xe5, 0x00, 0x9c, 0x4b, 0x32, 0xaf, 0x21, 0xf9, 0xbe, 0x4c, 0xdc, 0x01, 0x62, 0x48, 0x5c, 0xd1, 0x08, 0xa7, 0x23, 0x07, 0xcd, 0xc9, 0x93, 0x83, 0x2f, 0x3e, 0xb7, 0x6f, 0xe3, 0x7d, 0x91, 0xf6, 0xb8, 0xc5, 0x56, 0xd2, 0x1e, 0x69, 0xda, 0x5d, 0x74, 0xf1, 0xd9, 0x67, 0x2f, 0x82, 0x6e, + 0xf6, 0x09, 0xca, 0x51, 0xd5, 0xfa, 0xe1, 0xcf, 0x7f, 0xc5, 0xf6, 0x7f, 0xb3, 0xa2, 0xe7, 0xb9, 0x57, 0xde, 0x5a, 0xdb, 0xbc, 0xb6, 0xc1, 0x55, 0x57, 0x31, 0xb6, 0x86, 0xae, 0x3b, 0xf6, 0xc7, 0x3f, 0x1e, 0x23, 0x52, 0xf6, 0x5f, 0xba, 0x94, 0x91, 0x63, 0x59, 0xa7, 0x2e, 0x59, 0x8d, 0x72, 0x65, 0x8c, 0x66, 0xd4, 0xf9, 0x88, 0x7b, 0x62, 0x7a, 0xb8, 0x7d, 0x10, 0x4e, 0xc3, 0xaf, 0xff, 0xa9, 0x14, 0x13, 0xc5, 0xe4, 0x11, 0x0e, 0x99, 0xbc, 0xc8, 0x49, 0x31, 0xbf, 0xff, 0x5d, 0x46, 0x8a, 0x59, 0xc4, 0xfe, 0x1b, 0xf0, 0x5f, 0x06, 0x55, 0xec, 0xb7, 0x66, 0xc2, 0xbb, 0x8b, 0x8f, 0x81, 0x2c, 0xf6, 0x4f, 0xc7, 0x3e, 0xc6, 0xb8, 0x67, 0x29, 0x7d, 0x80, 0x7c, 0x15, 0xfb, 0xcd, 0xa1, 0x78, 0x12, 0xa4, 0x45, 0x26, 0x48, 0x9c, 0x2b, 0xd2, 0x80, 0x4c, 0x32, 0x7a, 0xad, 0x5c, 0x0a, + 0x61, 0xd9, 0x04, 0x4c, 0x0c, 0x2f, 0x9d, 0x52, 0xb4, 0x40, 0x37, 0x49, 0x61, 0x5c, 0x8a, 0x82, 0x99, 0xc1, 0x51, 0x7f, 0xdc, 0x21, 0x5f, 0xa7, 0xd1, 0x05, 0x5a, 0xd7, 0xd6, 0xd5, 0xad, 0x6d, 0x0d, 0xe8, 0x34, 0xeb, 0xe4, 0x8e, 0x38, 0xfd, 0x89, 0xca, 0x5f, 0x99, 0xeb, 0xd6, 0x15, 0xcc, 0x2e, 0xb6, 0xd9, 0x8a, 0x67, 0x17, 0xe8, 0xdc, 0xb9, 0x95, 0x7e, 0x14, 0x7b, 0xff, 0x14, 0xbd, 0x96, 0x5c, 0xcf, 0x94, 0x73, 0xf5, 0xa9, 0x71, 0xca, 0xb2, 0x51, 0x94, 0x2c, 0x92, 0x24, 0xba, 0xd3, 0x6a, 0x6c, 0x82, 0x6c, 0x9b, 0xf2, 0x72, 0xd1, 0x97, 0xbc, 0xfc, 0x88, 0xc9, 0xa5, 0x15, 0x0e, 0xaa, 0x95, 0xf6, 0xb2, 0x39, 0x85, 0x91, 0xae, 0x32, 0x87, 0x4a, 0x35, 0x24, 0xd0, 0xba, 0xe8, 0x77, 0xc5, 0xc6, 0x90, 0xdd, 0xae, 0x76, 0x17, 0xfb, 0x75, 0x3a, 0x7f, 0xb1, 0x5b, 0x6d, 0xb7, + 0x87, 0x8c, 0x29, 0x7c, 0xbb, 0x94, 0xf9, 0x37, 0xf9, 0x2a, 0x9f, 0xfa, 0xff, 0xe9, 0xfd, 0x33, 0x4f, 0x9e, 0xb7, 0x50, 0xe9, 0x4b, 0xa0, 0xc9, 0xb7, 0x44, 0xcd, 0xe6, 0x68, 0x0b, 0x9a, 0x7c, 0xc2, 0xa7, 0xe4, 0x74, 0xae, 0x87, 0xc6, 0xff, 0x01, 0xee, 0x80, 0x6b, 0x8e, 0xa4, 0x46, 0x24, 0x69, 0x23, 0x0f, 0x6e, 0x64, 0x05, 0x1b, 0xc8, 0x04, 0x41, 0x0c, 0x53, 0x90, 0x33, 0xb2, 0x9a, 0x35, 0x2a, 0xd8, 0x46, 0xaa, 0x63, 0x50, 0x8a, 0x3e, 0x9c, 0x7d, 0xc8, 0x85, 0xdc, 0x2b, 0xa8, 0x69, 0xf9, 0x0c, 0xc1, 0x1a, 0x9a, 0x2f, 0xe2, 0xed, 0xe1, 0x89, 0xf8, 0x74, 0x50, 0xe3, 0xce, 0x33, 0x9b, 0xf2, 0x50, 0xbe, 0xbb, 0x3c, 0x93, 0x39, 0xcf, 0xad, 0x61, 0x4e, 0x8d, 0x5d, 0xcc, 0x9e, 0xdb, 0xbd, 0x20, 0x12, 0x59, 0xd0, 0x3d, 0x37, 0x9b, 0x2c, 0xfb, 0xe2, 0x7f, 0x4d, 0xbe, 0x0b, + 0x5b, 0x73, 0xf1, 0x66, 0xff, 0xa0, 0xf2, 0x98, 0x33, 0x84, 0x8f, 0x28, 0x45, 0xde, 0x36, 0x13, 0x19, 0x4d, 0xa0, 0x04, 0x4e, 0xaf, 0x49, 0x47, 0x17, 0xad, 0x42, 0x74, 0x03, 0x4b, 0xa8, 0xc3, 0x4c, 0x33, 0x24, 0x1a, 0x61, 0x33, 0x4e, 0x67, 0xa2, 0xce, 0x20, 0x35, 0x5a, 0x0e, 0x5c, 0x3e, 0xef, 0x44, 0x41, 0x74, 0x97, 0xd3, 0x37, 0x3d, 0xf5, 0x62, 0x9b, 0xbd, 0x79, 0xfe, 0x8a, 0xf2, 0xf6, 0x83, 0x03, 0x45, 0xd9, 0x35, 0xed, 0xf5, 0x11, 0x46, 0xbd, 0x57, 0xc6, 0x04, 0xab, 0x3b, 0xea, 0xf2, 0xec, 0x45, 0xcd, 0xa1, 0xb2, 0xae, 0x52, 0xaf, 0xd4, 0x22, 0x3d, 0xa2, 0x71, 0x4d, 0x9e, 0x83, 0x4b, 0xc3, 0x98, 0x2d, 0x85, 0x7e, 0x7d, 0xce, 0xdc, 0x4d, 0x4d, 0xcd, 0x6b, 0x3b, 0xca, 0x63, 0x51, 0xc7, 0xb1, 0x47, 0x73, 0x6a, 0x4a, 0x8b, 0x6a, 0xbb, 0xcb, 0x02, 0x55, 0xb9, 0x06, + 0x48, 0x4f, 0x0c, 0x97, 0xcf, 0x5c, 0x35, 0x2b, 0x9c, 0xaf, 0x16, 0xce, 0x6b, 0x3f, 0xf3, 0x1c, 0xa1, 0x26, 0x92, 0xc8, 0x9a, 0x10, 0x80, 0xf3, 0xc9, 0x71, 0x91, 0x90, 0x33, 0x68, 0x0c, 0x02, 0xb2, 0x81, 0xa0, 0x68, 0x88, 0x33, 0x88, 0x11, 0x14, 0xed, 0x8d, 0xd1, 0xfb, 0x00, 0x66, 0x11, 0xd3, 0xd5, 0x0f, 0x01, 0x51, 0x51, 0x56, 0x5a, 0x8c, 0x2a, 0xd1, 0x70, 0x86, 0x60, 0x94, 0xde, 0xd7, 0x33, 0x4d, 0xd6, 0xa6, 0x32, 0x3c, 0x03, 0x9a, 0x7b, 0x0e, 0xf0, 0x51, 0x71, 0x2c, 0x57, 0x32, 0x29, 0xcc, 0x82, 0x3d, 0x3b, 0xcf, 0x4c, 0x9e, 0x0b, 0x9c, 0x9b, 0xd8, 0x3e, 0x37, 0xbb, 0x65, 0xe7, 0xc2, 0xc2, 0xe2, 0xb9, 0x43, 0xf3, 0x2b, 0x79, 0x60, 0x93, 0x39, 0xaa, 0x3f, 0xff, 0xe2, 0x01, 0x46, 0x29, 0xc8, 0x2a, 0x4f, 0x56, 0xe4, 0x59, 0x0d, 0x79, 0x35, 0x41, 0x4b, 0x48, 0xa0, + 0xd3, 0x52, 0xff, 0x9e, 0x34, 0x21, 0xbc, 0x20, 0xf4, 0x41, 0x89, 0xd4, 0xdf, 0x72, 0x43, 0xe3, 0xec, 0x35, 0x2d, 0x45, 0x85, 0x65, 0xa1, 0xd3, 0xb3, 0x1e, 0x68, 0x25, 0x97, 0xe5, 0x8c, 0xfd, 0x53, 0x5d, 0x17, 0xb4, 0xe5, 0x85, 0x83, 0x79, 0x25, 0x5e, 0x67, 0x49, 0xd8, 0x20, 0x50, 0x40, 0x19, 0x99, 0x18, 0x62, 0xff, 0x06, 0x06, 0x89, 0x7f, 0x23, 0xa7, 0xb1, 0xa4, 0x49, 0x03, 0x88, 0xfa, 0x4c, 0x4e, 0xe4, 0xa5, 0x28, 0x57, 0x37, 0x9c, 0x93, 0x0d, 0xd8, 0x50, 0x5a, 0xbd, 0xc8, 0xb4, 0xca, 0xa3, 0x7c, 0x1f, 0xce, 0xf4, 0x57, 0x41, 0xc5, 0x7f, 0x63, 0xca, 0x86, 0x87, 0xc9, 0x91, 0x63, 0xb6, 0xe7, 0x3b, 0x14, 0xc6, 0x82, 0xc6, 0x5c, 0x87, 0x5c, 0xc8, 0x17, 0xd9, 0x6d, 0x26, 0x1e, 0xda, 0x21, 0x94, 0xd8, 0x56, 0x1f, 0x88, 0xd9, 0xb2, 0x97, 0x2e, 0x59, 0xe0, 0xb3, 0xc9, + 0x94, 0x7c, 0x39, 0x5f, 0xec, 0xce, 0x2d, 0xb6, 0x8f, 0x8f, 0x13, 0xbb, 0xe1, 0xbb, 0x0f, 0x51, 0xbf, 0x24, 0xbc, 0x20, 0xee, 0x22, 0x00, 0x1f, 0x94, 0x10, 0x46, 0xf2, 0xf7, 0x84, 0xf0, 0x59, 0x00, 0xbe, 0x41, 0xfe, 0x1e, 0x79, 0xff, 0xc3, 0x36, 0x9b, 0xe0, 0x19, 0xd8, 0x81, 0xdb, 0x94, 0x28, 0xb8, 0x36, 0x8a, 0xa9, 0x6d, 0xe0, 0x1c, 0x56, 0xc0, 0x36, 0x23, 0x70, 0x0e, 0x53, 0x72, 0x4b, 0xc2, 0xad, 0xe1, 0x52, 0x40, 0xc6, 0xa7, 0xc1, 0xd6, 0x8a, 0xe9, 0x70, 0x73, 0xe3, 0x55, 0x40, 0x01, 0xdf, 0x0b, 0xfb, 0x64, 0xb6, 0xc1, 0x3e, 0xe1, 0x7b, 0x61, 0x8f, 0x3e, 0x50, 0x42, 0xa3, 0xc8, 0x22, 0xf8, 0x7e, 0x92, 0xcf, 0xe5, 0xb4, 0xa5, 0xce, 0x82, 0x4e, 0x1e, 0xd2, 0x3f, 0xf0, 0x9e, 0x67, 0x08, 0x90, 0x17, 0x52, 0xe7, 0x20, 0x23, 0x01, 0xb2, 0x1f, 0xca, 0x48, 0x1d, 0xfa, + 0x38, 0x1d, 0xb6, 0x66, 0xb5, 0x55, 0x95, 0x2b, 0x68, 0x61, 0x96, 0x47, 0xa2, 0xb1, 0xcb, 0x24, 0x1a, 0xea, 0xec, 0xac, 0x07, 0xbf, 0x76, 0xb4, 0xd8, 0x23, 0xb6, 0x89, 0xac, 0x3d, 0xcb, 0x47, 0x62, 0x7a, 0xb7, 0x00, 0xbf, 0x6b, 0x09, 0xf5, 0x14, 0xe8, 0x61, 0xae, 0xc0, 0xd9, 0x25, 0x09, 0x19, 0xfd, 0x51, 0x2a, 0xfe, 0xe1, 0x23, 0x2e, 0xfe, 0x01, 0xde, 0x87, 0xef, 0x22, 0x0f, 0xc1, 0x77, 0x79, 0x41, 0x19, 0x1e, 0x43, 0x19, 0x88, 0xa7, 0x9f, 0x23, 0x8f, 0x32, 0x57, 0xe0, 0xe8, 0xf0, 0x73, 0xe8, 0x79, 0x50, 0x0b, 0x9f, 0x57, 0x65, 0x9e, 0x47, 0xcb, 0x94, 0xee, 0x08, 0x10, 0x5d, 0x50, 0x12, 0x69, 0x02, 0x8a, 0x89, 0x31, 0x03, 0x1c, 0x31, 0xe7, 0xf3, 0xc6, 0x70, 0xe4, 0x04, 0xfc, 0x00, 0x44, 0xd8, 0x96, 0x35, 0xbb, 0xaa, 0x4c, 0x49, 0x0b, 0x0d, 0xa9, 0x41, 0x03, 0xc5, + 0xac, 0x07, 0xef, 0xbb, 0xbd, 0xd8, 0x2b, 0xb6, 0x09, 0xad, 0x3d, 0x2b, 0xb8, 0x41, 0xc3, 0x77, 0xc3, 0xbe, 0xc8, 0x5d, 0xb0, 0x2f, 0x2f, 0x48, 0xe2, 0x31, 0x25, 0xd1, 0x98, 0xe0, 0x3b, 0x96, 0x10, 0x3b, 0xc0, 0xb7, 0xa8, 0xd3, 0xf8, 0x1d, 0x7c, 0x1a, 0xbe, 0xc3, 0xa3, 0xe3, 0xc9, 0x01, 0x15, 0x55, 0xeb, 0xad, 0x40, 0x0e, 0x78, 0x58, 0x91, 0x7d, 0x56, 0x26, 0x30, 0x26, 0x2e, 0x5f, 0x4e, 0x18, 0x05, 0xb2, 0x13, 0x0a, 0x29, 0x5f, 0x26, 0xf8, 0xd8, 0x18, 0x92, 0xd8, 0xc0, 0x7e, 0x23, 0xd8, 0x67, 0x95, 0x06, 0x8d, 0xe3, 0x06, 0x09, 0x5f, 0x85, 0x6a, 0xb9, 0x6e, 0x86, 0x7d, 0xbd, 0x97, 0xea, 0x4b, 0x49, 0xa2, 0xf1, 0x6a, 0x29, 0x5f, 0x0e, 0xaa, 0x13, 0x10, 0xcf, 0x01, 0xb9, 0xc0, 0x1b, 0x4b, 0x00, 0xf0, 0x1e, 0xd7, 0x8d, 0x5c, 0x26, 0x90, 0x0a, 0x4e, 0xa0, 0x7e, 0x49, + 0x87, 0x91, 0xdd, 0x64, 0x93, 0x84, 0x8c, 0xac, 0x41, 0xca, 0x53, 0x4b, 0xc6, 0x8d, 0x41, 0xa9, 0x15, 0xad, 0x15, 0xb1, 0x83, 0xfc, 0x1b, 0xec, 0xcb, 0x0b, 0xaa, 0xf0, 0x78, 0xab, 0x00, 0xb7, 0xcf, 0xf0, 0x1d, 0x14, 0x0f, 0xff, 0x5e, 0x8d, 0x7f, 0xaf, 0x46, 0xbf, 0xa3, 0xbc, 0x7b, 0xe3, 0x8f, 0x52, 0xad, 0x90, 0xdf, 0x10, 0x72, 0x30, 0xc5, 0x05, 0xa8, 0x0e, 0x82, 0x66, 0x97, 0x13, 0xc1, 0x94, 0x3e, 0x65, 0x14, 0x2a, 0x05, 0x63, 0xb2, 0x32, 0x7b, 0x07, 0xd9, 0x25, 0x8b, 0xba, 0x3a, 0x15, 0x5e, 0x0f, 0xf3, 0x73, 0xa7, 0x27, 0xe5, 0xa7, 0xc6, 0x3e, 0x02, 0x66, 0xa1, 0x7c, 0xdc, 0xe9, 0xe7, 0x47, 0xb9, 0xbc, 0x99, 0xa9, 0x54, 0xcc, 0x11, 0xa5, 0xeb, 0xd9, 0xe3, 0xc7, 0x79, 0xe0, 0xf3, 0x87, 0x21, 0xfe, 0x79, 0x81, 0x7d, 0x0a, 0x34, 0xe3, 0xb6, 0x7c, 0x94, 0x83, 0x3a, + 0xc5, 0x9d, 0xf4, 0xa7, 0xf3, 0xf1, 0x2f, 0xa7, 0x38, 0x5d, 0x18, 0x4a, 0x0b, 0x1c, 0xc1, 0xac, 0xa0, 0xeb, 0x85, 0xe3, 0xc7, 0xd1, 0xd3, 0x57, 0x7e, 0xcb, 0xfe, 0xf1, 0xca, 0x6f, 0x66, 0x18, 0x2f, 0xca, 0xb7, 0x8f, 0x12, 0xf8, 0x12, 0xcd, 0x2e, 0x3b, 0xf7, 0x3e, 0xa4, 0x0f, 0x46, 0x68, 0xa6, 0xb2, 0x13, 0x4a, 0x22, 0xe4, 0x9c, 0x0e, 0x7b, 0x99, 0x8c, 0xb9, 0xe8, 0x71, 0x7e, 0xe1, 0xf7, 0xe0, 0xec, 0x90, 0xc4, 0x66, 0x92, 0x47, 0xae, 0x21, 0xcf, 0x7c, 0x55, 0xfe, 0xe8, 0xcd, 0xe0, 0x9f, 0xac, 0x84, 0x3c, 0xc3, 0xe5, 0x15, 0x7d, 0x9e, 0x14, 0x51, 0x97, 0xc8, 0xe3, 0x5f, 0xf5, 0xcc, 0xf3, 0x94, 0xf3, 0xca, 0xaf, 0xc8, 0xe3, 0x87, 0xb9, 0xb8, 0x54, 0x52, 0xc4, 0x9c, 0xff, 0xea, 0x67, 0xb6, 0x31, 0xc5, 0x5f, 0xbc, 0x95, 0x7e, 0xa6, 0x9a, 0xfd, 0x0d, 0xd5, 0x3d, 0x3e, + 0x00, 0x9f, 0xb1, 0x25, 0xcd, 0x24, 0xce, 0xd0, 0x3d, 0x87, 0x4c, 0x95, 0x94, 0x02, 0x44, 0x4b, 0xba, 0xd2, 0x10, 0xa4, 0x7e, 0x8c, 0xd6, 0x11, 0xa5, 0xba, 0xaf, 0x7c, 0x74, 0xdf, 0xae, 0x5d, 0xf0, 0xce, 0xeb, 0xc4, 0x0f, 0xa9, 0xb7, 0xa8, 0x9f, 0x43, 0xbc, 0x90, 0xcd, 0x09, 0x00, 0xe2, 0xb4, 0xee, 0x6f, 0x08, 0x89, 0x81, 0x8a, 0x89, 0x70, 0xad, 0x54, 0x22, 0xd1, 0x17, 0x38, 0x8c, 0x31, 0x9d, 0x1a, 0xbd, 0x5e, 0xbe, 0xe2, 0xd6, 0xd9, 0xb3, 0x0f, 0xaf, 0x28, 0x2f, 0x5f, 0x71, 0x78, 0xf6, 0xec, 0x5b, 0x57, 0x94, 0x93, 0x1d, 0xed, 0x87, 0x57, 0x96, 0x97, 0xaf, 0x3c, 0xdc, 0x3e, 0xfb, 0x56, 0xf4, 0x79, 0x2b, 0xb7, 0xe7, 0x2b, 0xc0, 0xf3, 0xe0, 0x34, 0xf9, 0x69, 0x2a, 0x1f, 0xed, 0xb4, 0xce, 0x67, 0xee, 0x7a, 0x85, 0x21, 0x54, 0x6c, 0x77, 0x94, 0xa2, 0x04, 0xc1, 0xa5, + 0x0e, 0x7b, 0x71, 0xc8, 0x40, 0x76, 0xa4, 0xaf, 0x1c, 0xc5, 0xe8, 0xb3, 0x18, 0x3e, 0x3c, 0x97, 0xbd, 0x04, 0x7e, 0xc3, 0xd3, 0x10, 0x7a, 0x74, 0x0e, 0xf8, 0xe8, 0x1c, 0x68, 0x54, 0x7a, 0x74, 0xac, 0x26, 0x02, 0x5f, 0x51, 0x96, 0x3a, 0x62, 0xe5, 0x90, 0x52, 0xa1, 0xec, 0x6e, 0x5a, 0x51, 0x69, 0xb5, 0x56, 0xae, 0x68, 0xea, 0x86, 0x17, 0x43, 0x2b, 0xc9, 0xf5, 0x3f, 0x63, 0xd7, 0xbc, 0xe0, 0x70, 0x8b, 0xa3, 0xeb, 0x7e, 0xf7, 0x3f, 0x7f, 0xde, 0xbc, 0xe5, 0xcf, 0xff, 0xf3, 0xbb, 0x75, 0x51, 0xb1, 0xdb, 0xf1, 0x02, 0xbb, 0x9a, 0xab, 0x1d, 0x0a, 0xfb, 0xbe, 0x38, 0xa5, 0xef, 0x42, 0xd2, 0x87, 0x4e, 0x56, 0x4a, 0x09, 0x87, 0xc2, 0xaa, 0x62, 0x2a, 0x60, 0x5c, 0x35, 0x04, 0x61, 0xb0, 0xbb, 0x69, 0x79, 0x95, 0xd5, 0x5a, 0xb5, 0x1c, 0xf6, 0xad, 0x54, 0x0e, 0xad, 0x82, 0x7d, + 0x83, 0x3b, 0x33, 0x7d, 0x6f, 0xd9, 0x9c, 0xe9, 0x1b, 0xdc, 0x81, 0xfb, 0x7e, 0x8e, 0xfd, 0x05, 0x19, 0x86, 0x70, 0x2d, 0x25, 0x56, 0xa4, 0x13, 0xf3, 0x91, 0x34, 0x0a, 0xa0, 0x47, 0x82, 0x46, 0x26, 0x52, 0x8e, 0x98, 0x14, 0xcd, 0xa6, 0x99, 0xd4, 0x80, 0xcb, 0xaf, 0x84, 0xd3, 0xcf, 0x5e, 0xfb, 0xb9, 0x1e, 0xce, 0x8a, 0x2d, 0x25, 0x24, 0x4a, 0xb5, 0x83, 0x41, 0x35, 0x0d, 0x94, 0xd8, 0xc5, 0x18, 0x05, 0x60, 0xa1, 0x3a, 0x7c, 0x73, 0x1f, 0xd4, 0x5b, 0xd9, 0x7d, 0x60, 0xb3, 0x55, 0xcf, 0xec, 0xf8, 0xf7, 0x7f, 0x59, 0x75, 0x2d, 0x7c, 0x5f, 0x8b, 0xce, 0x0a, 0x1f, 0x2d, 0x65, 0x7f, 0x01, 0xce, 0xe1, 0xb1, 0xa5, 0xe2, 0xba, 0xb3, 0xd2, 0xb9, 0xf2, 0x27, 0x12, 0x09, 0x73, 0xe0, 0x95, 0x1a, 0x9a, 0x7a, 0xe2, 0x3e, 0x57, 0xcb, 0x6c, 0x39, 0x97, 0x4c, 0xf1, 0x1a, 0x4f, 0xf5, + 0xf4, 0x9c, 0x53, 0x3b, 0x5c, 0xf8, 0xd0, 0xe2, 0x14, 0xae, 0xa5, 0x80, 0x2b, 0x23, 0xe3, 0x00, 0xe7, 0xd8, 0xbd, 0xd6, 0xac, 0x07, 0x1f, 0xcc, 0xb2, 0x82, 0x2d, 0xec, 0x2f, 0xe0, 0x60, 0x78, 0x1e, 0x38, 0x2c, 0xee, 0x0c, 0xc3, 0xf5, 0x82, 0x52, 0x0a, 0x51, 0x46, 0x1c, 0x79, 0x51, 0x8c, 0xc3, 0x2a, 0xb9, 0xa1, 0x39, 0x08, 0x06, 0x55, 0x16, 0x40, 0x05, 0x38, 0x69, 0x82, 0x02, 0x34, 0x5c, 0x06, 0x54, 0x6f, 0xaa, 0x3b, 0x25, 0x6f, 0x02, 0xb2, 0x2d, 0x35, 0x46, 0xf3, 0x0c, 0x0d, 0xc1, 0x44, 0xc1, 0xf2, 0xaf, 0xee, 0x07, 0x79, 0x84, 0x40, 0x98, 0x88, 0x21, 0xdf, 0x36, 0x8b, 0x29, 0x4b, 0x4b, 0x94, 0x81, 0x52, 0x5c, 0xaf, 0x80, 0x4b, 0xbd, 0x85, 0x03, 0x7b, 0xb0, 0x6d, 0x17, 0x67, 0xd1, 0xe0, 0x7c, 0xb9, 0x39, 0xb6, 0x00, 0x09, 0xaf, 0x51, 0x4e, 0x00, 0xc9, 0xe4, 0xdb, + 0x70, 0x81, 0x5f, 0x37, 0xcd, 0x91, 0x29, 0x64, 0x73, 0x6a, 0x6a, 0xf0, 0x47, 0x93, 0x4a, 0x62, 0x8b, 0xcd, 0xd9, 0xde, 0x15, 0x94, 0xab, 0xe5, 0xd5, 0x55, 0x55, 0xd5, 0xf0, 0x23, 0xd8, 0xb5, 0x7d, 0x4e, 0xcc, 0x26, 0x61, 0x76, 0xf4, 0x77, 0x15, 0x0d, 0x0d, 0x2c, 0xce, 0x6f, 0x5a, 0xad, 0x56, 0xaf, 0x6e, 0xca, 0xef, 0x1f, 0x58, 0x16, 0xef, 0xea, 0x57, 0xeb, 0xe6, 0xe5, 0xce, 0xb9, 0xa9, 0x3e, 0xb2, 0x64, 0xc9, 0xe2, 0x82, 0x44, 0xdb, 0x8d, 0x6d, 0x89, 0xfc, 0xfe, 0x25, 0x4b, 0x22, 0xf5, 0x37, 0xcd, 0xc9, 0x9d, 0xa7, 0x53, 0xc3, 0xf5, 0x4a, 0xc2, 0xf5, 0x32, 0xe1, 0xf5, 0xda, 0xfb, 0xa2, 0x15, 0xe5, 0x9b, 0x48, 0xad, 0x97, 0x1d, 0x4d, 0x8f, 0x41, 0xd3, 0x63, 0x20, 0x7b, 0x87, 0xa6, 0x3b, 0x2d, 0xf1, 0x77, 0x6a, 0xb9, 0x4c, 0x57, 0xb7, 0x23, 0x27, 0x72, 0x1e, 0x7f, + 0x65, 0x2f, 0x08, 0xf6, 0xca, 0x4a, 0xf3, 0x72, 0x42, 0xfe, 0x48, 0x48, 0xc1, 0x43, 0xb0, 0x97, 0x5e, 0x25, 0xec, 0x85, 0x86, 0xf6, 0x3c, 0xbd, 0x20, 0xe9, 0xe2, 0x82, 0xd8, 0x06, 0x8e, 0xc0, 0x20, 0x8d, 0x03, 0x90, 0x04, 0x4d, 0x9a, 0xaa, 0xaa, 0xd0, 0x52, 0xcc, 0xd9, 0x3e, 0x27, 0x6a, 0x93, 0xa8, 0xf0, 0x92, 0x75, 0xd5, 0xd4, 0x74, 0xa5, 0x97, 0x2c, 0x0a, 0x6f, 0xa0, 0x25, 0xab, 0xaa, 0x22, 0x23, 0x6d, 0x89, 0x82, 0xc5, 0x13, 0x6b, 0xd0, 0xdf, 0x15, 0x5f, 0x36, 0xd0, 0x9f, 0x5e, 0xb4, 0xc5, 0x03, 0x43, 0x45, 0x93, 0x17, 0xad, 0x3f, 0x3f, 0xd1, 0x86, 0xe1, 0x6a, 0x03, 0x35, 0x8f, 0xfc, 0x80, 0xf9, 0x1f, 0xa2, 0x80, 0xa8, 0x4a, 0xaa, 0xf2, 0xc2, 0x21, 0x9f, 0xd5, 0x68, 0xd0, 0xa9, 0x94, 0x52, 0x06, 0x48, 0x39, 0xef, 0x23, 0x9c, 0x17, 0x28, 0x7d, 0xe2, 0x06, 0x09, + 0x8c, 0x1b, 0x31, 0x96, 0x9d, 0x04, 0xcf, 0x2f, 0xba, 0x9c, 0x59, 0x24, 0xce, 0x01, 0x8e, 0x6a, 0xce, 0xa2, 0x9d, 0x8f, 0x62, 0x03, 0xb9, 0x1c, 0xa0, 0x08, 0x2f, 0x54, 0x31, 0x1b, 0xe1, 0x21, 0x3d, 0x2e, 0x2f, 0xa9, 0xc5, 0x41, 0x52, 0x09, 0x80, 0x1c, 0x4c, 0x51, 0x61, 0xb9, 0x0f, 0x72, 0x9f, 0x50, 0x59, 0xc5, 0xf2, 0x45, 0x8c, 0x40, 0x6c, 0x56, 0x3e, 0x9e, 0xfb, 0xb8, 0xca, 0xa9, 0xf6, 0x3a, 0x9e, 0x80, 0x3f, 0x5a, 0xc4, 0x02, 0xfe, 0x22, 0x86, 0xcf, 0xfd, 0xaa, 0x74, 0xa8, 0x9c, 0x4e, 0xda, 0xd9, 0x31, 0x20, 0xd6, 0x2b, 0x5c, 0xfe, 0x37, 0x5e, 0xd7, 0xb8, 0x94, 0x32, 0x9d, 0x68, 0x49, 0xc7, 0x12, 0x91, 0x94, 0x64, 0xb4, 0x03, 0xf0, 0xf7, 0x2c, 0x85, 0xd2, 0xab, 0xb9, 0xf0, 0xba, 0xc6, 0xa3, 0x94, 0xeb, 0xc4, 0xfd, 0x1d, 0xfd, 0x22, 0x19, 0x49, 0x6b, 0x39, 0xdc, + 0xbb, 0x9a, 0x3d, 0x49, 0x9b, 0x98, 0xb7, 0x09, 0x17, 0xd2, 0x6a, 0x18, 0xb4, 0x90, 0x74, 0x1a, 0x35, 0x10, 0xa2, 0x05, 0x50, 0x28, 0x01, 0x14, 0x0a, 0xaa, 0x1e, 0x44, 0x3a, 0xfe, 0xb4, 0x67, 0x0a, 0x2e, 0x8b, 0x3c, 0x92, 0x4a, 0x4f, 0xe8, 0xf2, 0x7a, 0x5d, 0x1a, 0x9c, 0x01, 0x1e, 0x4a, 0xce, 0x9c, 0x03, 0xb0, 0x9a, 0x4b, 0xbc, 0x80, 0xf2, 0x33, 0x51, 0xa8, 0x52, 0x26, 0x85, 0x84, 0x17, 0x38, 0x67, 0xf2, 0xa8, 0x51, 0x5b, 0x68, 0xb1, 0x14, 0x6a, 0x8d, 0xec, 0x8d, 0xd9, 0xb9, 0x8e, 0xc6, 0xd6, 0x36, 0x9f, 0x31, 0x5c, 0x52, 0x12, 0x36, 0xfa, 0xda, 0x5a, 0x1b, 0x1d, 0xb9, 0xd9, 0x54, 0x27, 0x3b, 0x72, 0xe5, 0x1f, 0x12, 0x99, 0x4c, 0x42, 0xc9, 0xc1, 0xd1, 0x2a, 0x47, 0x54, 0x28, 0x91, 0xd0, 0xe0, 0x43, 0x83, 0x81, 0xd5, 0xd3, 0x12, 0x89, 0x30, 0xea, 0xa8, 0x9a, 0x3c, + 0xd6, 0x38, 0xaa, 0x4a, 0x1a, 0xf6, 0x93, 0x3c, 0x7e, 0xb6, 0x8f, 0x64, 0x78, 0x2a, 0x11, 0xc4, 0xe1, 0x4a, 0x31, 0x84, 0x2f, 0x1a, 0xe5, 0xf6, 0xa5, 0x00, 0xa7, 0xa3, 0xe0, 0xf3, 0x07, 0x9b, 0x84, 0xc8, 0x17, 0xb2, 0x17, 0x99, 0x36, 0x87, 0x90, 0xec, 0x37, 0x42, 0x36, 0xc7, 0x63, 0x70, 0xd4, 0x4a, 0xb5, 0xcf, 0xe7, 0xc2, 0x59, 0x01, 0x80, 0x23, 0x1d, 0xe7, 0x9e, 0x9e, 0x01, 0x2e, 0xb9, 0x5b, 0x8e, 0x10, 0xe1, 0x8c, 0x33, 0x71, 0x90, 0x47, 0xc1, 0x2d, 0xe9, 0x29, 0xa4, 0xe6, 0x94, 0x9a, 0x04, 0xb8, 0x65, 0xe6, 0xc9, 0xb1, 0x37, 0x02, 0x45, 0x95, 0xb3, 0x50, 0x20, 0x91, 0xd2, 0xac, 0x1e, 0xcd, 0x11, 0x7c, 0x48, 0x4b, 0x25, 0x82, 0x42, 0x67, 0x15, 0x59, 0x3d, 0xf3, 0x54, 0x21, 0x6f, 0xb3, 0x64, 0x7c, 0x0f, 0xb5, 0x8b, 0x79, 0x8a, 0x90, 0x43, 0xac, 0x0d, 0x65, 0xab, + 0x6e, 0x00, 0x98, 0xe6, 0x80, 0x14, 0x1e, 0x2b, 0xa2, 0x10, 0x90, 0x34, 0x75, 0x55, 0x41, 0x31, 0x64, 0xc3, 0x25, 0x17, 0xa5, 0xeb, 0x8a, 0xad, 0x02, 0xcd, 0x73, 0x3a, 0x2b, 0xca, 0x8a, 0xe3, 0x05, 0x79, 0x06, 0xbd, 0x46, 0x85, 0x4b, 0x1e, 0xa3, 0x50, 0x8d, 0x54, 0x0a, 0x10, 0x19, 0x98, 0x9a, 0x22, 0x84, 0x37, 0x81, 0xa0, 0xae, 0x8a, 0x3c, 0xe4, 0x4d, 0x2e, 0xf1, 0x09, 0x99, 0x21, 0x79, 0xf5, 0x8a, 0x1d, 0xe5, 0x91, 0x70, 0x62, 0xe9, 0xe6, 0xb2, 0xb2, 0x08, 0x69, 0x14, 0xa8, 0x64, 0x0a, 0x5f, 0xbc, 0x31, 0x92, 0x3b, 0xab, 0xc0, 0x94, 0xd7, 0x36, 0xb8, 0x62, 0xb0, 0x2d, 0xaf, 0xa5, 0x33, 0xaf, 0x2c, 0x32, 0x70, 0x7b, 0xdf, 0x8a, 0xe7, 0xca, 0x72, 0x04, 0x59, 0x6a, 0x75, 0xa0, 0xb8, 0xb3, 0xa2, 0x7f, 0x45, 0x41, 0xfb, 0xb2, 0xd5, 0xcb, 0xda, 0x0b, 0xe6, 0xf4, 0xe6, + 0x57, 0xe4, 0x2f, 0x3c, 0x38, 0x6f, 0xf0, 0xe1, 0x12, 0xf2, 0xbd, 0xbc, 0xda, 0x78, 0xc4, 0x6a, 0xa8, 0x0f, 0x47, 0x1a, 0xe2, 0xf9, 0x16, 0x4b, 0x2d, 0xf8, 0x50, 0x26, 0xd2, 0x19, 0x74, 0x7a, 0x5f, 0xa1, 0xd5, 0x1d, 0xcf, 0x0d, 0xe4, 0xc4, 0xea, 0x17, 0x94, 0xb7, 0xef, 0xf4, 0x7a, 0x96, 0xd4, 0xd6, 0x6f, 0x9a, 0x57, 0x50, 0x17, 0x6b, 0x92, 0xc9, 0xcd, 0x0e, 0x53, 0x77, 0x7d, 0x38, 0x51, 0x10, 0x08, 0x45, 0xea, 0xfa, 0x92, 0x6d, 0x5b, 0xdd, 0xde, 0x15, 0x4d, 0x65, 0xc3, 0x1d, 0xf9, 0xc9, 0x22, 0x08, 0x1f, 0xb5, 0xe3, 0x51, 0xa6, 0x06, 0xfb, 0x08, 0x7a, 0x92, 0xce, 0x94, 0x7d, 0x7d, 0x04, 0xe9, 0x5a, 0x6e, 0x20, 0xd3, 0xee, 0x6c, 0x03, 0x44, 0xb3, 0x1f, 0xa9, 0xab, 0x14, 0x38, 0x53, 0xa8, 0x2b, 0x65, 0x5e, 0x4f, 0x8b, 0x68, 0x0e, 0xa6, 0xe6, 0x17, 0x67, 0xd8, 0x7f, + 0x9d, 0x59, 0xfe, 0xd8, 0x86, 0x44, 0x62, 0xc3, 0x63, 0xcb, 0xa9, 0x32, 0xfa, 0xb3, 0x2f, 0x92, 0xcc, 0x6b, 0x5f, 0x24, 0xa9, 0xee, 0x8a, 0x55, 0x47, 0x3a, 0xba, 0x8f, 0x0e, 0x21, 0xbe, 0xe2, 0x66, 0xf2, 0x05, 0x5a, 0x32, 0x35, 0xdf, 0xf6, 0x94, 0xfa, 0x43, 0x61, 0x7b, 0xba, 0x4a, 0xd0, 0xd4, 0x04, 0x66, 0x11, 0x5a, 0x72, 0x58, 0x6d, 0x14, 0x7c, 0x20, 0xd3, 0x08, 0x04, 0x6a, 0xc9, 0xcf, 0x78, 0x26, 0xed, 0x3d, 0x60, 0xdf, 0x36, 0xf2, 0x05, 0x8d, 0x02, 0x9c, 0x31, 0x17, 0x18, 0x0c, 0x05, 0x66, 0xb6, 0x4b, 0xae, 0x25, 0xff, 0x84, 0xd9, 0x66, 0x00, 0x61, 0x9d, 0x62, 0x08, 0x13, 0x3a, 0x97, 0x04, 0x60, 0xc0, 0x28, 0x72, 0xdc, 0x5c, 0x87, 0xb8, 0x5d, 0x12, 0x82, 0x04, 0x03, 0x3f, 0x18, 0x6a, 0x80, 0x37, 0x59, 0x9f, 0x72, 0xfd, 0xe5, 0x72, 0xc8, 0xff, 0x9d, 0xa8, 0x03, + 0x5f, 0xf3, 0x36, 0x2c, 0xaf, 0x2a, 0x5c, 0xba, 0xb0, 0xcb, 0x35, 0xf9, 0x82, 0x5c, 0xd3, 0x51, 0x5b, 0xb7, 0xae, 0x33, 0xdb, 0xe6, 0xb5, 0xb5, 0xc3, 0x2f, 0x1d, 0x39, 0x76, 0x8f, 0x1d, 0xe7, 0x5d, 0xa0, 0xee, 0x01, 0x2e, 0x9c, 0xaf, 0xc3, 0x9e, 0xb4, 0x60, 0xc7, 0x11, 0xe4, 0xcb, 0xcb, 0x05, 0xec, 0xa3, 0xdc, 0xef, 0xf0, 0x3b, 0x0a, 0x25, 0xc3, 0xe5, 0xa0, 0x94, 0xd8, 0x3f, 0x1e, 0x62, 0x36, 0x25, 0x70, 0xa9, 0x18, 0x8b, 0xd3, 0x62, 0xe3, 0xa9, 0x98, 0x53, 0xec, 0x7f, 0x9f, 0x42, 0x18, 0xe3, 0x14, 0x70, 0x60, 0xf9, 0x94, 0xba, 0x87, 0xdc, 0x84, 0x63, 0x25, 0x16, 0x62, 0xf9, 0x61, 0x21, 0x98, 0x8b, 0xe1, 0x7e, 0xce, 0xf8, 0xc7, 0xcc, 0x7e, 0xe6, 0x0c, 0x21, 0x26, 0x1a, 0x88, 0x95, 0x49, 0x59, 0xc8, 0x4a, 0x32, 0x74, 0x43, 0x85, 0x52, 0x4c, 0x13, 0x58, 0x63, 0x8f, + 0xd5, 0xd1, 0xe9, 0x74, 0xec, 0xab, 0x90, 0xbe, 0x18, 0xf4, 0xa2, 0x11, 0x2c, 0xc5, 0xe9, 0xe0, 0xb9, 0xaf, 0x00, 0x17, 0x6c, 0x9c, 0xda, 0x86, 0x48, 0x35, 0xc1, 0x44, 0x7d, 0x18, 0xa9, 0x2d, 0x25, 0xb5, 0xd5, 0x85, 0x05, 0x3e, 0x8f, 0xc7, 0xe5, 0xc3, 0x24, 0x28, 0x95, 0x60, 0x18, 0x05, 0x87, 0xa6, 0x4a, 0x96, 0x65, 0xea, 0xaa, 0xe7, 0x4c, 0x24, 0x9d, 0xe3, 0xf3, 0x7c, 0x13, 0x49, 0x8b, 0xf5, 0x48, 0x70, 0x45, 0xb2, 0x0c, 0xf2, 0x76, 0xfa, 0xf5, 0x40, 0x7f, 0x76, 0x53, 0x7f, 0x64, 0xe5, 0x93, 0x55, 0xa4, 0xf0, 0x5b, 0x76, 0x8f, 0x7e, 0x6f, 0xdf, 0xa7, 0xb7, 0xb7, 0xdf, 0xbb, 0xb9, 0x29, 0xa7, 0xe3, 0x86, 0xaa, 0xd2, 0x78, 0x6c, 0xe9, 0xed, 0xbd, 0xed, 0x7b, 0x17, 0xc7, 0xe9, 0xcd, 0x1e, 0xa5, 0xc0, 0x92, 0x5b, 0x15, 0xca, 0x6b, 0x89, 0x59, 0x14, 0x8e, 0x42, 0x37, + 0xb8, 0xdb, 0x13, 0xe6, 0xc3, 0xde, 0x36, 0x81, 0x86, 0x2a, 0xf2, 0xe5, 0xf8, 0x89, 0x79, 0x15, 0xfd, 0x55, 0xce, 0xa6, 0x04, 0xfb, 0x47, 0x79, 0xd6, 0xbe, 0x79, 0x83, 0xf1, 0xe5, 0x77, 0xf7, 0xd5, 0x6e, 0x5d, 0xd1, 0x65, 0x6d, 0xb9, 0xbd, 0x79, 0xd6, 0xae, 0xc5, 0x45, 0x91, 0xb9, 0x23, 0xe5, 0x06, 0x41, 0xc0, 0x18, 0x0b, 0x99, 0xed, 0xf1, 0xc6, 0xa0, 0x23, 0x91, 0xa8, 0xf4, 0xb0, 0xfd, 0x55, 0x77, 0xcc, 0xd2, 0x64, 0x6d, 0xfc, 0x5d, 0x3f, 0x5e, 0xbf, 0xc1, 0x71, 0x05, 0x2f, 0xca, 0xcc, 0x81, 0x78, 0xa3, 0x8f, 0x98, 0x95, 0xac, 0xeb, 0x03, 0x84, 0xb0, 0x15, 0x02, 0x67, 0x1e, 0xa0, 0xf9, 0x10, 0x69, 0xf0, 0x08, 0x4a, 0xc8, 0x83, 0x94, 0x19, 0xa5, 0x65, 0x10, 0x12, 0x03, 0x24, 0xaa, 0xe5, 0x0c, 0xa5, 0x4d, 0x3e, 0x64, 0x6c, 0x18, 0x86, 0xee, 0x4e, 0x29, 0xa0, 0x68, + 0xa6, 0x6d, 0xc1, 0x7c, 0x77, 0x30, 0xe8, 0xf7, 0xfb, 0x03, 0x5e, 0x07, 0x46, 0x93, 0x69, 0x0f, 0x60, 0x2e, 0x22, 0x8e, 0x37, 0x91, 0x60, 0x82, 0xfb, 0x5d, 0xc5, 0xb9, 0x4c, 0xc4, 0x74, 0x36, 0x90, 0x09, 0x18, 0xac, 0xa0, 0xe1, 0x92, 0x65, 0xaa, 0x17, 0x4e, 0xad, 0x07, 0x49, 0x67, 0x79, 0x83, 0x0d, 0x9b, 0xe6, 0xe5, 0x27, 0x2a, 0xac, 0xe5, 0xf1, 0x3c, 0xc5, 0x71, 0x35, 0x30, 0x17, 0x58, 0x73, 0xe7, 0x24, 0xbd, 0xdd, 0x27, 0x7e, 0xb4, 0x79, 0xef, 0x9f, 0x9e, 0x1a, 0x58, 0x79, 0xfe, 0xf3, 0xc3, 0xc7, 0xff, 0xd4, 0xae, 0xb3, 0x0b, 0x4c, 0x2b, 0xd9, 0x8f, 0xce, 0x9c, 0x65, 0x7f, 0xf6, 0xcb, 0xad, 0xf1, 0xa5, 0x87, 0xcf, 0xbc, 0xbd, 0x7a, 0xe1, 0x73, 0xf7, 0xed, 0x5d, 0x14, 0xab, 0x18, 0xbe, 0x73, 0x4e, 0xd3, 0xa0, 0xbc, 0x64, 0x7f, 0x6b, 0xd9, 0xfc, 0x32, 0x4b, 0xe5, + 0xce, 0xd7, 0xb6, 0xd7, 0x2e, 0xcf, 0xa5, 0xf2, 0x05, 0xe5, 0x03, 0x3b, 0xab, 0xeb, 0x3a, 0xd5, 0x79, 0xb3, 0x2b, 0xd4, 0xb3, 0x7a, 0x0b, 0xd5, 0xf1, 0xc1, 0x5b, 0xbb, 0x0e, 0xfc, 0xe0, 0x40, 0xed, 0xe0, 0x73, 0x9f, 0x1e, 0x7e, 0x7c, 0x9c, 0x78, 0xaa, 0x3b, 0x99, 0x2f, 0x15, 0x69, 0x1a, 0xea, 0xba, 0xd6, 0xfe, 0x18, 0x64, 0x7f, 0x7b, 0xf8, 0xbb, 0x67, 0xef, 0x58, 0x5e, 0x1c, 0x68, 0x5b, 0x77, 0xec, 0xfc, 0xaa, 0xe1, 0x6f, 0xec, 0x6f, 0x69, 0x68, 0x6e, 0xad, 0x0e, 0x36, 0x0e, 0x96, 0xce, 0x79, 0xfa, 0xd6, 0x79, 0x3a, 0x0d, 0xe7, 0x83, 0xd2, 0x3f, 0xfe, 0x09, 0xcf, 0x08, 0xf1, 0x70, 0x88, 0xa8, 0x26, 0x16, 0x11, 0x7b, 0x92, 0x32, 0x0d, 0xc2, 0xbb, 0x80, 0x46, 0x55, 0x7f, 0x68, 0xea, 0xaa, 0xdc, 0xaf, 0x69, 0x7b, 0x00, 0x0d, 0x61, 0x9a, 0x86, 0xac, 0x8f, 0x40, 0x90, + 0xce, 0x56, 0xcc, 0xe7, 0x63, 0x9b, 0xd0, 0x30, 0x6f, 0x4a, 0xee, 0xd7, 0xaf, 0x6c, 0xbf, 0x16, 0xd9, 0x90, 0x24, 0xe1, 0xb0, 0x3f, 0xe8, 0x77, 0x79, 0xfc, 0x5e, 0x21, 0xda, 0x0c, 0xb4, 0xcc, 0x53, 0xd7, 0x93, 0x3f, 0x35, 0xe3, 0x13, 0xda, 0x1c, 0x0f, 0xde, 0x80, 0x89, 0x20, 0xce, 0x1c, 0x3a, 0xae, 0xb1, 0xc2, 0x5f, 0xb0, 0xfe, 0x10, 0x5e, 0xcb, 0x68, 0x3a, 0x26, 0x90, 0x33, 0x55, 0xc5, 0x8f, 0xad, 0x1f, 0x78, 0x70, 0x34, 0x51, 0xbb, 0xf9, 0xf4, 0x92, 0xd5, 0x27, 0x8b, 0x22, 0x8c, 0x52, 0x2a, 0xb7, 0xe5, 0x35, 0x14, 0xd7, 0x0c, 0x37, 0xfb, 0x03, 0xb3, 0x96, 0x96, 0x46, 0x6a, 0x22, 0x81, 0x2b, 0xd5, 0x85, 0x8b, 0xf7, 0x3d, 0xf4, 0xca, 0xe0, 0x71, 0x20, 0x7e, 0x6b, 0xdd, 0xba, 0xb7, 0xd8, 0xcf, 0x8e, 0x0f, 0xbe, 0xf2, 0xd0, 0xbe, 0xc5, 0x85, 0xab, 0xc3, 0xb3, 0x47, + 0x0f, 0x3d, 0xd0, 0xdd, 0xfd, 0xe0, 0xad, 0xa3, 0xed, 0xe1, 0x70, 0xfb, 0xe8, 0xad, 0x0f, 0x76, 0x77, 0x3f, 0x70, 0x68, 0x74, 0x76, 0x18, 0xfc, 0x1e, 0x08, 0x98, 0xfa, 0xea, 0x59, 0xb5, 0x5b, 0x1e, 0x5f, 0x3a, 0xf8, 0xc4, 0xa6, 0x9a, 0xba, 0xa2, 0x06, 0x9e, 0x54, 0x67, 0xd4, 0x85, 0xdb, 0xd7, 0x35, 0x34, 0xad, 0x9f, 0x1d, 0x50, 0xe8, 0x8d, 0xff, 0x1c, 0x7c, 0x15, 0x75, 0x81, 0x7b, 0x3b, 0xce, 0xfe, 0xeb, 0xad, 0x75, 0x85, 0x8b, 0xf7, 0x3e, 0xfc, 0xca, 0xe0, 0xbc, 0x87, 0x6f, 0x5b, 0xd7, 0x91, 0x9d, 0xdd, 0xb1, 0xee, 0xb6, 0x87, 0x7a, 0x7a, 0x1e, 0x39, 0xbc, 0xbe, 0x33, 0x3b, 0xbb, 0x73, 0xfd, 0xe1, 0x47, 0xd0, 0x5e, 0xd0, 0x99, 0xbd, 0x40, 0x71, 0x54, 0x71, 0xa2, 0x8d, 0xb8, 0x9b, 0x5b, 0xfd, 0x7c, 0xb8, 0x11, 0xa8, 0x14, 0xc1, 0x48, 0xc6, 0x0e, 0x80, 0x75, 0xff, + 0x34, 0xaa, 0x5c, 0xc3, 0xe3, 0xf1, 0x7b, 0x25, 0x80, 0xcf, 0x5f, 0xd5, 0x24, 0x86, 0xac, 0x00, 0x4e, 0x39, 0x3a, 0x2c, 0x80, 0x3b, 0xf0, 0x1f, 0x3e, 0xb3, 0x56, 0x00, 0x77, 0xc1, 0x53, 0x5c, 0x64, 0x31, 0xa3, 0x00, 0x81, 0xea, 0xca, 0xa2, 0xb6, 0xe2, 0xb6, 0x50, 0xc0, 0x1c, 0xb7, 0xc4, 0xa7, 0x86, 0x6b, 0x49, 0xd3, 0x8a, 0xec, 0xa9, 0xf6, 0x04, 0xec, 0x3f, 0x00, 0x26, 0x2d, 0x3d, 0x4a, 0xd5, 0xa6, 0x9e, 0xb2, 0x39, 0xe8, 0xbc, 0x50, 0xc9, 0xb2, 0xd6, 0x6c, 0x95, 0x32, 0x58, 0x1f, 0xdb, 0xba, 0xb5, 0x77, 0x4f, 0x77, 0x30, 0x38, 0x6f, 0xdf, 0xc2, 0x8d, 0x4f, 0x01, 0xcd, 0xd5, 0x0b, 0xcc, 0x2d, 0xfc, 0xf4, 0xfd, 0xc0, 0xbb, 0x44, 0x7e, 0xc7, 0x90, 0x5d, 0xe1, 0x76, 0x97, 0x04, 0x74, 0xec, 0x2b, 0xe0, 0x9d, 0x70, 0xeb, 0xca, 0xf2, 0xd2, 0x15, 0xed, 0xb9, 0xec, 0x5b, + 0xd4, 0xb6, 0xcb, 0x6f, 0x5e, 0xb5, 0xae, 0x3d, 0xdc, 0x7a, 0x33, 0xdd, 0xd3, 0xf7, 0x81, 0xdb, 0x9d, 0x29, 0x6b, 0xee, 0x24, 0x2a, 0x88, 0xb9, 0xc4, 0x03, 0x2f, 0xaa, 0x01, 0x9d, 0xc9, 0x18, 0x5f, 0x80, 0xf2, 0xa4, 0xf0, 0x68, 0x8a, 0x37, 0x22, 0x4e, 0xaf, 0xa3, 0x08, 0xe5, 0xeb, 0xa2, 0x50, 0x61, 0x72, 0x3e, 0x5f, 0xd0, 0x2b, 0x05, 0x28, 0xc7, 0xab, 0x04, 0x32, 0x60, 0xc2, 0x5e, 0x42, 0x28, 0x1c, 0x16, 0xc2, 0xb5, 0xff, 0x4f, 0x1f, 0x5a, 0x2b, 0x84, 0x8b, 0xef, 0x4b, 0x26, 0xdc, 0x2e, 0xb4, 0xf8, 0x8d, 0x0d, 0x89, 0xb9, 0xc9, 0xb9, 0x05, 0x79, 0xae, 0x0a, 0x77, 0x85, 0xc3, 0x6e, 0xb3, 0x18, 0xf4, 0x52, 0xa7, 0xcc, 0xc9, 0x2d, 0xbf, 0x6c, 0xfa, 0xf2, 0xeb, 0x38, 0xc6, 0x06, 0x95, 0x39, 0xcc, 0xb8, 0x80, 0x5e, 0xc7, 0x3e, 0x38, 0x91, 0x05, 0x4e, 0x19, 0xa8, 0x8e, + 0x6c, 0xdd, 0xfa, 0xcc, 0x13, 0xcb, 0x96, 0xab, 0x4c, 0x92, 0x9f, 0x3f, 0xec, 0x56, 0xbc, 0x22, 0x34, 0x18, 0x67, 0x3d, 0x05, 0x0a, 0xfe, 0xb3, 0xed, 0xf8, 0x93, 0xc6, 0x5b, 0xec, 0x76, 0x17, 0xba, 0x94, 0xec, 0x2f, 0xc1, 0xe9, 0x1d, 0x5b, 0x7a, 0xf7, 0x19, 0xc5, 0x76, 0x1d, 0xfb, 0x92, 0x19, 0xb8, 0x64, 0x32, 0xf6, 0xd1, 0xff, 0xab, 0x5d, 0x41, 0x78, 0xbe, 0x9b, 0x6a, 0x4c, 0xd5, 0xc7, 0xf0, 0x22, 0xfe, 0xd0, 0xa6, 0xd5, 0x48, 0xc4, 0x90, 0xd0, 0x21, 0xcf, 0x58, 0x80, 0x3c, 0x63, 0x31, 0xdf, 0x43, 0xf4, 0x08, 0x05, 0x7c, 0x8a, 0x20, 0x06, 0xb8, 0xb2, 0xd0, 0x99, 0x4b, 0xe4, 0x5f, 0x2e, 0xf0, 0x0a, 0x3c, 0x4a, 0x4d, 0x14, 0xf9, 0x86, 0x40, 0x4e, 0xd8, 0x8c, 0xb4, 0x47, 0x88, 0x1d, 0x72, 0x79, 0x50, 0x51, 0x71, 0xcc, 0xaf, 0xc0, 0x25, 0xd3, 0xa7, 0x5c, 0x22, 0xd5, + 0x38, 0xfa, 0x19, 0x99, 0xf0, 0xbb, 0xc1, 0x87, 0x67, 0x0f, 0x9e, 0xa5, 0x3f, 0xd9, 0x28, 0x3a, 0xcb, 0x37, 0xc8, 0x35, 0x4e, 0xe9, 0x59, 0xe1, 0xc6, 0xf5, 0x2a, 0xa5, 0xf8, 0x7e, 0xa9, 0x90, 0x11, 0xf3, 0x1f, 0x10, 0x2b, 0x95, 0xcc, 0xa9, 0x51, 0xd6, 0x0e, 0x7e, 0x3d, 0x3a, 0xf6, 0x75, 0xd0, 0xdb, 0xc1, 0xbe, 0x24, 0x97, 0x5b, 0x41, 0x5d, 0x3b, 0xfb, 0x28, 0xd9, 0xc1, 0x97, 0xb1, 0xbf, 0x34, 0xda, 0xe5, 0x16, 0x88, 0xab, 0x64, 0x7c, 0x34, 0x87, 0x2d, 0x54, 0x0d, 0x93, 0x60, 0x9e, 0x24, 0xa2, 0xc4, 0x1c, 0xe2, 0x63, 0xce, 0x82, 0xac, 0x8a, 0x42, 0x0e, 0xb6, 0xb5, 0xba, 0xaa, 0xa4, 0x98, 0x41, 0x5c, 0x0c, 0x9f, 0x46, 0x65, 0x8c, 0xd1, 0x8f, 0xc2, 0xa9, 0x3f, 0xf6, 0x70, 0xcd, 0xc3, 0x19, 0x36, 0x98, 0x0f, 0x31, 0x32, 0x9f, 0x1c, 0x21, 0x68, 0x01, 0x8f, 0x84, 0x9c, + 0xf0, 0x42, 0xcc, 0x09, 0x8b, 0x45, 0x42, 0x0a, 0x23, 0x53, 0x8a, 0x1a, 0xc2, 0x46, 0xea, 0x1c, 0xf8, 0x4c, 0xe1, 0xd5, 0xcf, 0xf0, 0x49, 0xba, 0x1b, 0x3d, 0x47, 0x0a, 0xe1, 0x23, 0x24, 0xca, 0x2e, 0x96, 0x61, 0xa4, 0xd3, 0xcf, 0x26, 0x63, 0xd3, 0x39, 0xee, 0xeb, 0x79, 0x1a, 0xa0, 0xa8, 0x1a, 0x4d, 0x2c, 0x16, 0x9b, 0x13, 0xeb, 0xaa, 0xaf, 0xd3, 0xb9, 0x35, 0xd9, 0x2e, 0x6f, 0xb6, 0x5b, 0x29, 0x41, 0x58, 0xfc, 0xea, 0x1a, 0xbf, 0x93, 0xbd, 0x22, 0x26, 0xd7, 0xf8, 0xe5, 0xb6, 0x84, 0x87, 0xfd, 0x54, 0x27, 0xb6, 0x84, 0x73, 0xb0, 0x43, 0x7b, 0x02, 0xfe, 0x25, 0x52, 0x0b, 0xb5, 0x16, 0x9f, 0xc9, 0x93, 0xe3, 0x2f, 0xab, 0x2f, 0xf3, 0x9b, 0x63, 0x6d, 0x05, 0x96, 0xe2, 0xc2, 0x1c, 0x95, 0x5d, 0xa2, 0x94, 0x64, 0x39, 0xc2, 0x36, 0x28, 0x6a, 0xa3, 0xdf, 0x4d, 0x91, 0xe6, + 0xbc, 0xd8, 0x42, 0x3b, 0x95, 0x77, 0x93, 0xf0, 0xac, 0xcc, 0xa1, 0x91, 0x1b, 0xf8, 0xcf, 0x08, 0x37, 0xa1, 0xcd, 0x7b, 0x80, 0x27, 0x66, 0xe8, 0xd4, 0xee, 0x65, 0xab, 0x25, 0x72, 0xa9, 0xdb, 0x92, 0x65, 0xd4, 0xea, 0x7d, 0x89, 0x48, 0xb0, 0x21, 0x6a, 0x13, 0x6b, 0x2d, 0xaa, 0x0a, 0xa5, 0x42, 0xa3, 0x08, 0xdb, 0xcd, 0x16, 0x8d, 0xd6, 0x57, 0x19, 0x77, 0x26, 0xf2, 0x21, 0x57, 0x97, 0x8f, 0x76, 0xb8, 0x93, 0xfd, 0x86, 0x55, 0x2e, 0x07, 0xb5, 0xb3, 0xd3, 0x3b, 0x2c, 0xb3, 0x40, 0x2e, 0x3d, 0xb5, 0xc5, 0x70, 0x8f, 0xbb, 0x28, 0x3b, 0xf9, 0x67, 0xe6, 0x69, 0x5c, 0x27, 0xd0, 0x8c, 0x14, 0x52, 0x53, 0x2a, 0xa5, 0xad, 0xa2, 0x9a, 0xd3, 0x65, 0x02, 0xaf, 0xae, 0x22, 0x36, 0x95, 0xc9, 0xe8, 0x8a, 0xf4, 0x6c, 0xac, 0xab, 0xdb, 0x34, 0x3f, 0x12, 0x99, 0xbf, 0xa9, 0xae, 0x6e, + 0x63, 0x4f, 0xe4, 0x90, 0xa5, 0xb0, 0x2e, 0x10, 0xa8, 0x47, 0x22, 0x58, 0x7d, 0x20, 0x50, 0x57, 0x68, 0xa1, 0x97, 0xd5, 0xdc, 0x34, 0x2f, 0x12, 0x99, 0x77, 0x53, 0x4d, 0xf5, 0x4d, 0x3d, 0x85, 0x85, 0x3d, 0x37, 0x55, 0xc1, 0xbb, 0x56, 0x2b, 0xba, 0x5b, 0x1f, 0xb5, 0x5a, 0xa3, 0xf5, 0x68, 0x3c, 0xdb, 0x89, 0xff, 0xa2, 0x85, 0xf4, 0x10, 0x1c, 0x4f, 0x1c, 0x79, 0x0c, 0xe5, 0x66, 0xfb, 0x3c, 0x70, 0x3b, 0x63, 0x79, 0x6e, 0x2b, 0xc4, 0x6a, 0x71, 0x8b, 0x49, 0x4b, 0x93, 0x04, 0xd5, 0x98, 0xe3, 0x25, 0xa9, 0x06, 0x33, 0x20, 0xeb, 0x09, 0xce, 0x60, 0x9b, 0xce, 0x07, 0xb9, 0x06, 0x34, 0x87, 0x02, 0x3e, 0x34, 0x60, 0x7d, 0x08, 0x4c, 0x1b, 0x20, 0xf0, 0x71, 0x12, 0x24, 0xdf, 0x87, 0x39, 0xc6, 0x58, 0xdc, 0x17, 0xe7, 0x4a, 0xae, 0xeb, 0xe3, 0x7a, 0x2c, 0x5c, 0xf1, 0xf8, 0x7a, + 0xb0, 0xda, 0x59, 0xd2, 0x16, 0xce, 0x6e, 0x2b, 0x75, 0x3a, 0x4b, 0xdb, 0xb2, 0xc3, 0x6d, 0x25, 0x4e, 0xb2, 0xf8, 0x58, 0x78, 0xa8, 0xb8, 0x68, 0x69, 0xe8, 0xc4, 0x92, 0x63, 0x16, 0xbb, 0xcd, 0x7c, 0x7c, 0x60, 0xd2, 0xb5, 0xc3, 0x66, 0x3e, 0x46, 0x3e, 0x10, 0x6e, 0x41, 0xcd, 0x5b, 0xd2, 0x8f, 0xb1, 0xca, 0xe3, 0xe8, 0xc6, 0xf1, 0xa5, 0xc7, 0x42, 0x93, 0x1b, 0x1e, 0x5f, 0x7a, 0x4f, 0x78, 0x59, 0x71, 0x7c, 0x69, 0xe8, 0x78, 0x2a, 0xff, 0xe2, 0x3f, 0xe8, 0xe5, 0xf0, 0x6c, 0xd5, 0x13, 0x0b, 0x92, 0x3d, 0x90, 0x5f, 0xa7, 0xa2, 0x40, 0x20, 0x44, 0xb5, 0x6a, 0x29, 0x24, 0x40, 0x8c, 0x10, 0x42, 0xbe, 0x80, 0x2f, 0x14, 0x8c, 0x40, 0x2e, 0x46, 0xca, 0xc5, 0xfb, 0x43, 0x74, 0x2b, 0x06, 0x7c, 0x21, 0x8f, 0x9f, 0x49, 0x40, 0x0f, 0x61, 0x5f, 0x20, 0x10, 0xf5, 0x12, 0x22, 0xd1, + 0xb0, 0xa8, 0xb9, 0xbe, 0x2e, 0x99, 0xa8, 0x28, 0x47, 0x02, 0xa5, 0xd3, 0xe5, 0x71, 0xa9, 0x5c, 0x1e, 0xb7, 0x42, 0x06, 0xa1, 0x58, 0x1d, 0xc1, 0x72, 0xf1, 0x04, 0x03, 0xc2, 0xe3, 0x63, 0x89, 0xda, 0x01, 0xf9, 0x69, 0x33, 0xd0, 0x73, 0xdf, 0xb1, 0x9b, 0x0b, 0x8d, 0x2b, 0x8f, 0xa6, 0xe2, 0x39, 0x5d, 0xd1, 0x88, 0xb7, 0x10, 0xad, 0x91, 0x2b, 0xeb, 0x88, 0x76, 0xf1, 0xe1, 0xf3, 0xc3, 0xfb, 0x5f, 0xab, 0xad, 0x16, 0xa8, 0x14, 0x4a, 0x4f, 0xd9, 0x82, 0x59, 0xb7, 0xbc, 0x64, 0x27, 0xb3, 0xac, 0x46, 0x56, 0x0d, 0x31, 0x18, 0x78, 0x1b, 0x7e, 0xd9, 0x6c, 0xfe, 0xc6, 0xa1, 0x68, 0x6b, 0x49, 0xc0, 0x66, 0x90, 0x8a, 0xa4, 0x82, 0xce, 0xfa, 0x1f, 0x1f, 0x59, 0xff, 0xf2, 0xc1, 0x6e, 0xe5, 0xa1, 0x9d, 0xf0, 0xfe, 0x2e, 0xea, 0x96, 0xad, 0xef, 0xdc, 0xd6, 0x3c, 0x34, 0xaf, 0x49, + 0x24, 0xf3, 0x84, 0x3d, 0xdf, 0x3c, 0x5f, 0xae, 0xb6, 0xab, 0xbd, 0x1a, 0x08, 0x82, 0xf0, 0xcb, 0xc5, 0x37, 0x35, 0x36, 0xb7, 0x4d, 0xc3, 0x13, 0xb4, 0x2e, 0x1b, 0x6d, 0xbb, 0xf3, 0xc7, 0x3b, 0x47, 0x64, 0x66, 0xb9, 0xc6, 0xab, 0x46, 0xeb, 0x23, 0x87, 0xeb, 0x63, 0xa4, 0x87, 0x89, 0x5a, 0x6e, 0x7d, 0x28, 0x78, 0x8a, 0xd0, 0xfa, 0xd4, 0x42, 0x49, 0x6b, 0xd6, 0xf4, 0x45, 0x4a, 0x73, 0x69, 0xc8, 0x7a, 0x3d, 0x75, 0xad, 0x96, 0x4e, 0x5a, 0xa1, 0x8a, 0xf2, 0xb2, 0xd2, 0x78, 0x34, 0x2f, 0x27, 0xb3, 0x3e, 0xd2, 0x2f, 0x5b, 0x1f, 0x9e, 0x66, 0xda, 0xfa, 0x90, 0x33, 0xaf, 0x8f, 0xfc, 0x80, 0xba, 0x67, 0xef, 0xb3, 0xab, 0xb6, 0x7c, 0xbd, 0x26, 0x5f, 0x62, 0x53, 0x2a, 0x03, 0xa5, 0xed, 0x45, 0x7b, 0x4f, 0xdb, 0xc0, 0x6f, 0xed, 0x59, 0x6c, 0x3d, 0x64, 0xdf, 0x4f, 0xc2, 0xcf, + 0x27, 0x8d, 0x0f, 0xec, 0xac, 0xe9, 0xab, 0x0a, 0xa9, 0x14, 0x7a, 0x59, 0x41, 0xd5, 0x9b, 0xfb, 0x56, 0x3d, 0xbd, 0xbd, 0x43, 0xb9, 0x67, 0x33, 0x5c, 0x9c, 0x2d, 0xe4, 0x77, 0x17, 0xdf, 0xbb, 0xba, 0xa4, 0xa5, 0xa6, 0x44, 0xaa, 0xce, 0xb2, 0x64, 0xed, 0xdb, 0xe6, 0xd1, 0x38, 0x14, 0x76, 0xa5, 0xcc, 0x28, 0x85, 0x5f, 0xf6, 0x1f, 0xb4, 0xfb, 0xec, 0x0a, 0x79, 0x69, 0xc7, 0xfc, 0xf2, 0x91, 0xfb, 0x07, 0xe6, 0x4b, 0x8d, 0x32, 0xa5, 0x5d, 0x81, 0x79, 0xde, 0xf9, 0xe3, 0x3a, 0xda, 0xc5, 0x5b, 0x81, 0x6b, 0x6e, 0x35, 0x13, 0xcf, 0x24, 0x4d, 0x65, 0x10, 0xe1, 0x35, 0xd7, 0x54, 0x42, 0x8e, 0x29, 0x5f, 0x2d, 0x80, 0x82, 0x67, 0x9e, 0x0f, 0x1e, 0x0a, 0x1b, 0xa4, 0x2f, 0x4c, 0xa6, 0x98, 0x07, 0x4d, 0xd1, 0xa3, 0x04, 0x43, 0x91, 0x14, 0x24, 0x3d, 0xc8, 0x23, 0x8a, 0x24, 0xd6, + 0x70, 0xd6, 0x14, 0x2c, 0x82, 0x11, 0xc4, 0xda, 0x4c, 0x12, 0xf2, 0xaf, 0x6e, 0x3b, 0x4c, 0xe0, 0xc2, 0x1f, 0xd7, 0xd1, 0x25, 0x4a, 0x6a, 0x0d, 0x21, 0xb3, 0xa2, 0xb4, 0x24, 0x1c, 0x84, 0xeb, 0x68, 0x41, 0x8a, 0x0e, 0x4f, 0x4a, 0xc9, 0x41, 0x71, 0x51, 0x78, 0xb6, 0x94, 0x46, 0x9e, 0x2b, 0xd7, 0x8b, 0xa2, 0x91, 0x52, 0xea, 0x0d, 0xbd, 0x9a, 0x33, 0x03, 0x70, 0x35, 0xb3, 0x11, 0x97, 0xc0, 0xe7, 0x34, 0x41, 0xe4, 0x8a, 0xad, 0x7b, 0x5c, 0x65, 0xed, 0x39, 0x4d, 0x89, 0xde, 0x52, 0xb3, 0xbd, 0xb8, 0x25, 0x7c, 0x53, 0xc3, 0x6d, 0x23, 0xb5, 0xe5, 0xeb, 0x9f, 0x18, 0x96, 0x00, 0xab, 0xbe, 0x41, 0x71, 0xd3, 0x77, 0x1b, 0x4a, 0x85, 0x46, 0x85, 0x26, 0xbf, 0x66, 0x61, 0xa5, 0xa7, 0xee, 0xc0, 0x70, 0x55, 0xc9, 0xda, 0x87, 0x57, 0x88, 0xd0, 0x2d, 0xd9, 0x8a, 0x13, 0x25, 0xb9, + 0x5a, 0xb3, 0x2c, 0x50, 0xdc, 0x98, 0x73, 0x5c, 0x7a, 0x70, 0x55, 0x6e, 0x4d, 0xc4, 0x2d, 0x27, 0x8f, 0xf0, 0xbc, 0x45, 0x0d, 0xbe, 0xec, 0x9a, 0x88, 0x57, 0xe1, 0xd3, 0x45, 0x3b, 0xb6, 0x2c, 0x9c, 0xff, 0xc0, 0xe6, 0x66, 0xfe, 0x69, 0x8d, 0xfd, 0x57, 0xd2, 0xfe, 0xc6, 0xf9, 0x12, 0x85, 0x3b, 0x37, 0x5b, 0x08, 0xc6, 0xb4, 0xd1, 0xee, 0x5d, 0x4b, 0x1a, 0x6f, 0x5d, 0x53, 0x47, 0xdf, 0xaa, 0xb1, 0xbf, 0x29, 0xeb, 0x2c, 0x69, 0x70, 0x64, 0xd9, 0xec, 0x02, 0x84, 0x7f, 0xf6, 0x8d, 0x67, 0x41, 0x9c, 0xf5, 0x47, 0xb8, 0x1f, 0x18, 0x67, 0xe5, 0x78, 0x48, 0x0a, 0xca, 0x6d, 0xc8, 0x85, 0x0d, 0x29, 0x9f, 0x47, 0x09, 0x0a, 0x02, 0x2d, 0xc5, 0x8c, 0x40, 0xc1, 0x0d, 0x30, 0x58, 0x15, 0x04, 0x39, 0xb4, 0x35, 0x69, 0x7b, 0xee, 0x1a, 0xa2, 0xd9, 0xa5, 0xf5, 0xb9, 0x8c, 0xb9, 0x58, + 0xe3, 0x81, 0xeb, 0xcb, 0xe9, 0x1d, 0xe9, 0x82, 0xdc, 0xf1, 0xab, 0x96, 0x20, 0x05, 0x80, 0x9c, 0xba, 0x4b, 0x4f, 0xae, 0x46, 0x35, 0x62, 0xb6, 0xdf, 0x00, 0x7e, 0x92, 0x78, 0x79, 0xa5, 0x84, 0xb4, 0xe8, 0x67, 0x29, 0x7a, 0x4f, 0xde, 0x90, 0xa8, 0xda, 0xf6, 0xf2, 0x46, 0x29, 0xbc, 0x6a, 0x50, 0xb6, 0xef, 0x8e, 0x45, 0x04, 0x0a, 0x85, 0xc4, 0x1e, 0x2a, 0xf5, 0xc7, 0xca, 0xdb, 0x8b, 0xbc, 0x2a, 0x89, 0x9a, 0x47, 0x8d, 0xf0, 0x8a, 0x16, 0xef, 0xe9, 0x38, 0xf9, 0x92, 0xa1, 0xb1, 0x51, 0xf0, 0x38, 0x9c, 0xa7, 0xb8, 0x6d, 0xcf, 0xb9, 0xd1, 0xb5, 0xaf, 0x1e, 0xea, 0xa0, 0xcf, 0x68, 0xec, 0x7f, 0x94, 0x26, 0xf3, 0x6b, 0xc5, 0x52, 0xa5, 0x2e, 0x8b, 0x3f, 0x66, 0xa1, 0x6d, 0x0e, 0xa3, 0x50, 0x00, 0xb7, 0x35, 0x1b, 0x32, 0x80, 0xf3, 0x21, 0x9f, 0x59, 0x82, 0xbc, 0x4b, 0xe5, + 0x90, 0x29, 0x8c, 0x84, 0xfd, 0x14, 0x0f, 0x4e, 0x11, 0x67, 0x24, 0xa1, 0x19, 0xc4, 0x1b, 0xa6, 0xb4, 0xcc, 0x58, 0xa4, 0xc7, 0xc2, 0xd1, 0x52, 0x5e, 0xb3, 0x1e, 0x4a, 0xf0, 0xda, 0xb0, 0xc7, 0x29, 0x40, 0x2c, 0x0c, 0x52, 0x1d, 0x4f, 0x92, 0xd5, 0xbd, 0x29, 0x61, 0xdd, 0xc2, 0x69, 0xf9, 0xd0, 0x1f, 0x7e, 0x0c, 0xc5, 0x5e, 0x22, 0x0f, 0x1f, 0x54, 0x9d, 0x49, 0xe9, 0xca, 0x76, 0xd9, 0xb7, 0xc7, 0xd7, 0x9c, 0x5e, 0xdb, 0xba, 0x65, 0x51, 0x9d, 0xa1, 0xcd, 0x5c, 0xa0, 0x55, 0xfb, 0xf3, 0x13, 0xc1, 0xb2, 0xde, 0x72, 0x1b, 0x78, 0x16, 0x1e, 0xad, 0xfd, 0xb1, 0xbc, 0x8a, 0x72, 0xf2, 0xb0, 0x44, 0x6f, 0x53, 0x5f, 0x29, 0x77, 0x26, 0xf2, 0xcc, 0x6f, 0x90, 0x0f, 0x99, 0xfd, 0x6c, 0xdf, 0x63, 0x5b, 0x1a, 0x7c, 0x35, 0x0b, 0xa2, 0x05, 0x32, 0xda, 0x60, 0x08, 0x3b, 0x54, 0xfe, + 0xa6, 0x95, 0x55, 0xe3, 0x6d, 0x1a, 0xfb, 0x1f, 0x1a, 0x56, 0x67, 0xcb, 0x3a, 0xe5, 0xb6, 0x2c, 0xc5, 0x36, 0x91, 0x3d, 0xbf, 0x3a, 0x1b, 0x60, 0x9b, 0x24, 0xe1, 0x67, 0x1f, 0xa2, 0xce, 0x31, 0xcf, 0x13, 0x6e, 0x22, 0x9e, 0x2c, 0x94, 0x03, 0x14, 0x56, 0xc7, 0xa0, 0x0c, 0x43, 0x3c, 0x40, 0xf2, 0xfa, 0x27, 0x29, 0x2a, 0x52, 0x46, 0xb6, 0xe5, 0xa9, 0x50, 0x15, 0xb7, 0xd6, 0xa5, 0x36, 0x38, 0xb5, 0x02, 0xb4, 0x6f, 0x10, 0x98, 0x5d, 0xd1, 0x42, 0x88, 0x4c, 0xe2, 0x6a, 0x1f, 0x52, 0x83, 0x23, 0x18, 0xc6, 0x9a, 0x4b, 0x04, 0xd8, 0x0e, 0x2d, 0xb9, 0xa2, 0x5b, 0x3a, 0x6b, 0xf3, 0x53, 0x6b, 0x93, 0xe1, 0xd6, 0x72, 0xbf, 0x60, 0xde, 0x9d, 0xd9, 0x3f, 0xd1, 0x64, 0xfd, 0xe3, 0xd7, 0x36, 0x47, 0x76, 0x5b, 0x79, 0x50, 0xa8, 0x05, 0x65, 0xe4, 0x17, 0x63, 0xf9, 0xcb, 0xef, 0x5a, + 0x9c, 0xbd, 0xac, 0x02, 0x18, 0xe2, 0xf3, 0x2a, 0xc9, 0x37, 0xe5, 0xc2, 0x40, 0xfb, 0x43, 0x6f, 0x5e, 0x1c, 0xb6, 0x1b, 0x0c, 0xf1, 0xf9, 0xc9, 0x53, 0x9c, 0xbd, 0xd0, 0x0f, 0xf7, 0x60, 0x31, 0xe4, 0x29, 0x9d, 0x68, 0x0f, 0x8c, 0x80, 0x10, 0xa2, 0xf0, 0x3f, 0xb8, 0xfa, 0x88, 0x4b, 0x5f, 0xc2, 0x43, 0x71, 0x57, 0xc8, 0xeb, 0xb3, 0x5f, 0x04, 0x30, 0x8f, 0x43, 0xd3, 0x4b, 0xe9, 0x66, 0xa7, 0x5d, 0x93, 0xf2, 0x2e, 0x86, 0x6c, 0xa4, 0x98, 0x53, 0xa8, 0xe2, 0xff, 0xb8, 0xd8, 0x03, 0xf4, 0x1f, 0x98, 0x1a, 0x53, 0x53, 0x0a, 0xc8, 0xef, 0x82, 0x10, 0xfb, 0x2c, 0x68, 0x63, 0x9f, 0xbd, 0xc8, 0x7d, 0xc0, 0xcb, 0x95, 0x0f, 0x9b, 0x5d, 0x22, 0xc0, 0x93, 0x6b, 0x05, 0x7c, 0x8d, 0x0c, 0x30, 0x02, 0x8f, 0xe9, 0x11, 0x72, 0xdd, 0xda, 0x65, 0x23, 0xec, 0x4f, 0x41, 0xf6, 0xc8, 0xb2, + 0xb5, 0x54, 0xc0, 0xa8, 0x1f, 0xfb, 0xb5, 0x27, 0x61, 0xb3, 0x25, 0x3c, 0xa4, 0x5d, 0x6d, 0xe5, 0x74, 0xd7, 0x41, 0x38, 0x5e, 0x29, 0xdd, 0x37, 0x79, 0xbc, 0x70, 0x6c, 0x84, 0x70, 0x74, 0xa6, 0x51, 0x7f, 0xc5, 0x78, 0xb5, 0xae, 0xe8, 0xe4, 0xf1, 0x4e, 0x70, 0x57, 0x70, 0xbc, 0x0f, 0x03, 0x86, 0x7d, 0x17, 0x44, 0xd8, 0x77, 0x9f, 0x65, 0xbf, 0x0f, 0x0a, 0xe1, 0x3f, 0x1e, 0x7b, 0xd7, 0x3d, 0xf6, 0xb0, 0xe8, 0xa7, 0x1a, 0x83, 0x40, 0xa0, 0x57, 0xff, 0x44, 0x1c, 0x73, 0xed, 0x27, 0xd5, 0xab, 0xfb, 0x86, 0x3f, 0xf8, 0x60, 0xb8, 0x6f, 0x35, 0xd9, 0xe4, 0xd5, 0xb1, 0x27, 0x83, 0x65, 0x16, 0x6b, 0xb9, 0x1f, 0x0c, 0xea, 0xbd, 0x9c, 0xec, 0x5a, 0x34, 0xfe, 0x31, 0xaf, 0x87, 0x79, 0x96, 0x10, 0xc2, 0x33, 0xdc, 0x04, 0xb7, 0xf9, 0x1f, 0xa8, 0x22, 0x12, 0x0f, 0x12, 0x5f, 0x21, + 0x1f, 0xe9, 0xb5, 0x90, 0x23, 0x5e, 0x1c, 0x12, 0x17, 0x12, 0x82, 0xfc, 0x88, 0x04, 0x02, 0x07, 0x8f, 0xe4, 0x43, 0x16, 0x93, 0xa0, 0x44, 0x7c, 0xac, 0xae, 0x11, 0x89, 0x85, 0xa2, 0xa5, 0xd8, 0xcd, 0x16, 0x11, 0x21, 0xb8, 0xfa, 0x62, 0x31, 0xe8, 0x15, 0xa4, 0xb4, 0x5d, 0x9c, 0x5a, 0xac, 0xf4, 0x2b, 0x1f, 0xc7, 0x0f, 0xf2, 0x78, 0x38, 0x9b, 0xf2, 0xaa, 0xa9, 0x5d, 0x24, 0x13, 0xff, 0xe9, 0xd3, 0x58, 0xa1, 0x26, 0x16, 0xa7, 0x45, 0x6a, 0x88, 0x89, 0x2d, 0x62, 0xd1, 0xfc, 0x79, 0x1d, 0xb3, 0x1b, 0xea, 0x2a, 0xca, 0xf2, 0x73, 0x7d, 0x1e, 0xa7, 0xdd, 0x64, 0x50, 0xca, 0x45, 0x71, 0x71, 0xdc, 0xe5, 0x94, 0xa2, 0xba, 0x90, 0xa9, 0x98, 0xbc, 0x10, 0xc8, 0xa4, 0xd4, 0xf8, 0x32, 0xfd, 0x05, 0x84, 0xf0, 0xe9, 0x45, 0x48, 0x27, 0xea, 0x29, 0xa2, 0xc4, 0xdb, 0xf4, 0x0f, 0xc5, + 0x66, 0xe3, 0x6d, 0x97, 0x7f, 0x2b, 0xb2, 0x69, 0x6e, 0x07, 0x8f, 0x88, 0x85, 0x65, 0xd9, 0xbb, 0x7b, 0x3b, 0x76, 0x2f, 0xc8, 0x8f, 0x2e, 0xde, 0xdb, 0xde, 0xbe, 0x35, 0x58, 0xc0, 0x48, 0x25, 0x12, 0x83, 0x3b, 0x16, 0xcc, 0x69, 0x89, 0xdb, 0x1c, 0x10, 0x8b, 0x1b, 0x83, 0x6e, 0x9b, 0x82, 0x27, 0xa1, 0x25, 0xb1, 0x81, 0x43, 0x5d, 0x5d, 0x87, 0x06, 0x62, 0xe9, 0xcf, 0x93, 0xb6, 0xe2, 0x8e, 0x48, 0xa4, 0xa3, 0xd8, 0xb6, 0x61, 0xd9, 0xb2, 0x0d, 0xcc, 0xcd, 0x52, 0xf5, 0x17, 0xbb, 0x65, 0x4a, 0x6a, 0xb7, 0xb0, 0xaa, 0xb8, 0xb4, 0x68, 0xe9, 0xc1, 0xf6, 0x8e, 0x43, 0x4b, 0xe3, 0xb1, 0x50, 0x3d, 0x5f, 0xa4, 0xd4, 0x2a, 0x1d, 0x15, 0x0b, 0x4a, 0x4a, 0x7a, 0x2b, 0x1c, 0x8c, 0x44, 0x2d, 0x63, 0x78, 0x63, 0xbf, 0x9b, 0x7d, 0x60, 0xb0, 0xb8, 0x78, 0xf0, 0xc0, 0xec, 0xd9, 0x07, + 0xd1, 0xe7, 0xc1, 0xd9, 0xc5, 0xbd, 0x09, 0xa7, 0x33, 0xd1, 0x5b, 0x3c, 0xba, 0x73, 0x27, 0xdc, 0xf3, 0x77, 0x08, 0x42, 0x70, 0x16, 0xf2, 0xbe, 0x5a, 0xc2, 0x01, 0x65, 0x9c, 0x62, 0xe2, 0x26, 0x2e, 0x40, 0xdf, 0x43, 0x30, 0x62, 0xc0, 0x13, 0x30, 0xbc, 0x01, 0x88, 0xd1, 0x85, 0x90, 0xed, 0x80, 0x27, 0x0a, 0x95, 0x77, 0xe0, 0xa3, 0x6c, 0xef, 0x5c, 0x62, 0x63, 0xbc, 0x25, 0x81, 0xab, 0xda, 0x51, 0x78, 0xef, 0x51, 0x6b, 0xec, 0x69, 0xbc, 0x2a, 0xdd, 0xb6, 0x27, 0x69, 0x72, 0x39, 0xf5, 0xba, 0xe2, 0xa2, 0x78, 0x2c, 0x3b, 0xe4, 0x8c, 0xba, 0xa2, 0x16, 0x93, 0xce, 0xa1, 0x77, 0xc0, 0xff, 0x15, 0x12, 0x64, 0x48, 0x8d, 0x28, 0x5d, 0xd3, 0x42, 0xdc, 0x20, 0x87, 0x8a, 0xad, 0x6e, 0xa9, 0x4a, 0xbd, 0xd4, 0x34, 0x27, 0x25, 0x06, 0x95, 0x67, 0x3c, 0xfd, 0xf2, 0xb0, 0xc1, 0xa1, + 0xbc, 0x72, 0x89, 0xa4, 0x5e, 0x59, 0x63, 0x36, 0x2b, 0xa9, 0x6c, 0x8a, 0x22, 0xbf, 0xd9, 0xb9, 0x78, 0x71, 0x67, 0xd7, 0xa2, 0x45, 0x9d, 0x8d, 0x9b, 0xbb, 0x73, 0x73, 0xbb, 0x37, 0x37, 0xc2, 0xcf, 0xbc, 0xbc, 0xee, 0xcd, 0xec, 0x1a, 0xf2, 0xb6, 0xb1, 0x75, 0x28, 0x3d, 0x90, 0x4a, 0x43, 0x1b, 0x4c, 0x85, 0xfa, 0x2b, 0x3e, 0xe6, 0x94, 0x54, 0x7f, 0xf9, 0x0f, 0x96, 0x78, 0x16, 0xb5, 0x78, 0x79, 0xdf, 0xc2, 0xe5, 0xcb, 0x17, 0xf6, 0x2d, 0x1f, 0xfb, 0x7b, 0x64, 0x1e, 0x64, 0xde, 0x37, 0xf5, 0x14, 0x70, 0x9f, 0xf3, 0x22, 0x1b, 0x6f, 0x83, 0xf4, 0x2d, 0x17, 0xe3, 0x7e, 0x54, 0x5b, 0xb2, 0x18, 0xe9, 0x2c, 0x85, 0x90, 0xd9, 0x82, 0xf2, 0x14, 0xcd, 0x00, 0xba, 0x5f, 0x06, 0x24, 0x12, 0x38, 0x63, 0x14, 0x73, 0x2c, 0xee, 0x51, 0xc8, 0xa5, 0x94, 0x58, 0x3c, 0xd4, 0x84, 0x1c, + 0x3c, 0x97, 0x32, 0xcd, 0x34, 0x4d, 0x17, 0xd3, 0xc5, 0xb1, 0xc2, 0x48, 0x41, 0x7e, 0x6e, 0x38, 0xe8, 0xf3, 0x7a, 0xdc, 0x0e, 0x97, 0x4e, 0xa9, 0x76, 0xb8, 0x95, 0x0a, 0x25, 0x3a, 0xd9, 0x2e, 0x2a, 0xc2, 0x15, 0xde, 0x45, 0xa4, 0x4c, 0x0e, 0x2f, 0xb1, 0x58, 0xa1, 0x76, 0x61, 0xe1, 0x56, 0x0d, 0x57, 0x24, 0x01, 0x22, 0x6a, 0x17, 0xd6, 0xe0, 0xe2, 0x73, 0x4e, 0x45, 0xb8, 0x44, 0xae, 0xe4, 0x4f, 0xff, 0x5a, 0xfe, 0x11, 0x78, 0x53, 0xce, 0xf8, 0x83, 0x3b, 0x2a, 0x9e, 0xb4, 0x99, 0xf8, 0xc2, 0x0f, 0x3b, 0xe6, 0xc9, 0x0c, 0x0a, 0x3e, 0x5d, 0xfd, 0x51, 0xb5, 0xa0, 0xc0, 0x3e, 0xb7, 0xe3, 0x90, 0xd5, 0xcc, 0x17, 0xbe, 0x53, 0xfe, 0x3b, 0x19, 0x6c, 0x72, 0x67, 0x2d, 0x2f, 0xcf, 0x3e, 0xaf, 0xe3, 0xaf, 0xed, 0xdd, 0x76, 0x37, 0xaf, 0x96, 0x5a, 0xc5, 0xbe, 0x0d, 0xde, 0xee, + 0xb4, 0x59, 0xd9, 0xb7, 0x3d, 0xe1, 0x04, 0xbb, 0x50, 0xaa, 0xb7, 0xaa, 0x20, 0x8a, 0x3b, 0xed, 0x77, 0x82, 0x87, 0xdd, 0xd9, 0x09, 0x50, 0xd4, 0x61, 0xb7, 0x82, 0x05, 0x76, 0x0f, 0xbb, 0x08, 0x3c, 0xe8, 0xb7, 0xb3, 0xa7, 0xb1, 0x7c, 0xb1, 0x6c, 0xfc, 0x9f, 0xf4, 0x1d, 0x90, 0x70, 0x23, 0x19, 0x69, 0x2e, 0xc7, 0x50, 0x21, 0x33, 0x2d, 0xa4, 0xf2, 0xe4, 0x40, 0x5a, 0x52, 0x9a, 0xe0, 0x9f, 0xae, 0xba, 0x85, 0xd9, 0xa5, 0x99, 0x1e, 0xe8, 0xb9, 0x76, 0x65, 0xf8, 0xe9, 0xa5, 0x49, 0xe9, 0x3b, 0xe6, 0xdc, 0x7e, 0x61, 0xcd, 0x9a, 0x37, 0x8e, 0x76, 0x75, 0x1d, 0x7d, 0x63, 0xcd, 0x9a, 0x0b, 0xb7, 0xcf, 0xb9, 0x58, 0xd2, 0xb7, 0x25, 0x59, 0xb9, 0xa9, 0xaf, 0xb8, 0xb8, 0x6f, 0x53, 0x65, 0x72, 0x4b, 0x5f, 0x09, 0x99, 0xb7, 0xff, 0x67, 0x77, 0xb6, 0xb6, 0xde, 0xf9, 0xb3, 0xfd, + 0xfb, 0xdf, 0x43, 0x9f, 0xef, 0xed, 0x5f, 0x73, 0xdf, 0x60, 0x5e, 0xde, 0xe0, 0x7d, 0x6b, 0x56, 0x9f, 0x1a, 0xcc, 0xcd, 0x1d, 0xe4, 0x72, 0xa8, 0x3a, 0x51, 0x7d, 0x72, 0x7a, 0x09, 0x21, 0x41, 0x76, 0x02, 0x31, 0x0f, 0xdb, 0x09, 0xd2, 0x3e, 0x27, 0xc8, 0x35, 0x0e, 0xfe, 0x95, 0x10, 0x12, 0x25, 0x67, 0x27, 0xc0, 0x11, 0x98, 0x90, 0x4e, 0xe7, 0x22, 0x03, 0xf1, 0xb7, 0x48, 0xf2, 0x57, 0xc6, 0x3c, 0x65, 0x15, 0x38, 0x49, 0x2f, 0xb9, 0xfc, 0x35, 0xca, 0xa2, 0x2f, 0x30, 0xdc, 0xaf, 0x61, 0xf4, 0xdd, 0xc0, 0x85, 0xec, 0x05, 0xdd, 0x28, 0xe6, 0x0d, 0xf2, 0xeb, 0x3c, 0xb0, 0x9d, 0x46, 0xd7, 0xad, 0xe3, 0x3e, 0xea, 0x02, 0xbe, 0xde, 0x71, 0x19, 0x5d, 0x7b, 0xc6, 0x63, 0x74, 0x36, 0xf2, 0x27, 0x07, 0x3b, 0xcd, 0xe8, 0xba, 0x78, 0xdc, 0x4b, 0xdd, 0x0e, 0x69, 0x03, 0x0f, 0xec, + 0xfa, 0x05, 0xba, 0xee, 0x1b, 0xff, 0x07, 0x75, 0x99, 0x99, 0x05, 0xaf, 0x77, 0xf3, 0xd0, 0x75, 0xee, 0x78, 0x8c, 0xfa, 0x26, 0xbe, 0xbf, 0xe7, 0x47, 0xe8, 0x7a, 0x39, 0xbc, 0xff, 0x07, 0xa6, 0x03, 0x5e, 0xef, 0x15, 0xa3, 0xeb, 0xda, 0xf1, 0x28, 0x75, 0x0e, 0xee, 0x09, 0x0f, 0xec, 0xfb, 0x0b, 0xce, 0x25, 0x02, 0xf7, 0x88, 0xfe, 0x7f, 0xbc, 0x47, 0x74, 0xcb, 0xde, 0xe7, 0x06, 0x07, 0x9f, 0xdd, 0xd3, 0xd2, 0xb2, 0xe7, 0xd9, 0xc1, 0xc1, 0xe7, 0xf6, 0xb6, 0xbc, 0x11, 0x6e, 0x1b, 0x4e, 0x26, 0x87, 0x5b, 0x43, 0xa1, 0x56, 0xf4, 0xd9, 0x16, 0x26, 0xf3, 0x8e, 0xbc, 0x7f, 0xa4, 0xbe, 0x1e, 0xfe, 0x39, 0xfa, 0xfe, 0xd1, 0xfa, 0x7a, 0xf8, 0x67, 0xa6, 0x3d, 0xda, 0x03, 0xd7, 0xf2, 0x7b, 0x70, 0x8f, 0x14, 0x84, 0x3f, 0xe9, 0x91, 0x0a, 0xb8, 0x18, 0x28, 0xc4, 0x49, 0xe2, 0x7c, + 0xf7, 0x38, 0x30, 0x89, 0xdb, 0x29, 0x05, 0xa1, 0x30, 0x2a, 0x95, 0xc8, 0x3d, 0xc4, 0xa3, 0x45, 0x0e, 0xd1, 0x99, 0xdd, 0x8a, 0x28, 0xa9, 0xef, 0x5d, 0xb9, 0x02, 0xf2, 0x29, 0xf2, 0x77, 0x86, 0x42, 0x59, 0x29, 0x38, 0x76, 0xe2, 0xe8, 0xd1, 0xa3, 0xe4, 0x56, 0x73, 0x89, 0xed, 0x6b, 0x2a, 0x46, 0x3b, 0x07, 0x38, 0xae, 0xe4, 0xc0, 0xf7, 0x74, 0xc2, 0xf7, 0xfc, 0x1c, 0xee, 0x51, 0x1e, 0x51, 0x9a, 0x2c, 0xb2, 0x00, 0x8a, 0xcc, 0x46, 0xde, 0x36, 0x28, 0x31, 0x13, 0x89, 0x44, 0x2c, 0x54, 0xf1, 0x0c, 0x79, 0x8b, 0xe2, 0x2c, 0x24, 0xd8, 0x84, 0xbe, 0x14, 0xb9, 0x85, 0xba, 0x1c, 0xa6, 0x2c, 0x95, 0x82, 0xa1, 0x89, 0x3c, 0x90, 0x8b, 0xf9, 0x79, 0x6c, 0x76, 0x8d, 0x82, 0x89, 0x6a, 0x9a, 0x69, 0xf3, 0xd2, 0x84, 0xbb, 0x3f, 0x9f, 0x41, 0x01, 0xe6, 0x77, 0x38, 0x3c, 0x57, 0xb6, + 0x80, 0x97, 0x51, 0xf0, 0xbb, 0x25, 0xda, 0x9c, 0x53, 0xd4, 0x51, 0xe2, 0x11, 0x0b, 0x34, 0x3a, 0x46, 0xce, 0x5b, 0x23, 0xad, 0x59, 0x73, 0x7c, 0xf1, 0x9c, 0x43, 0x4b, 0x62, 0xde, 0xea, 0xbe, 0x62, 0xf6, 0x07, 0xb9, 0xbd, 0xee, 0x67, 0xfc, 0xe6, 0x55, 0x6b, 0x40, 0x6f, 0xb0, 0xb5, 0xb5, 0x23, 0x3b, 0x50, 0x1b, 0xb1, 0x38, 0x62, 0x75, 0x5e, 0xad, 0x1a, 0x90, 0xea, 0xd6, 0x03, 0x83, 0x25, 0x91, 0x05, 0xdb, 0x9b, 0xcb, 0x57, 0x2d, 0x99, 0x1f, 0xfa, 0xb3, 0x38, 0xe5, 0xd3, 0xdc, 0x09, 0xe1, 0xed, 0x6d, 0x38, 0x97, 0x6a, 0x54, 0xc5, 0x0a, 0xe5, 0xee, 0x36, 0x42, 0x99, 0x1a, 0x4e, 0x85, 0xc7, 0xd0, 0x0c, 0x8f, 0x1e, 0x21, 0x52, 0x92, 0x21, 0xf2, 0x7d, 0x20, 0xe9, 0xab, 0x4b, 0xa6, 0xc7, 0xfc, 0xca, 0xac, 0x70, 0x8e, 0x9b, 0x2f, 0x32, 0x4d, 0x38, 0xf4, 0xf2, 0xa6, 0xc6, + 0xfb, 0xa7, 0x13, 0x67, 0xe1, 0x59, 0x46, 0xa7, 0xcf, 0xd6, 0x77, 0x93, 0xb4, 0x6a, 0xf8, 0x9e, 0xc5, 0x73, 0x8f, 0x0c, 0x15, 0xaf, 0x1b, 0xde, 0xb1, 0xa5, 0xfc, 0x86, 0x87, 0x06, 0x97, 0xde, 0xb9, 0xb2, 0x3e, 0x6b, 0xbe, 0x27, 0x47, 0x2c, 0x0b, 0x27, 0xe6, 0x16, 0x95, 0x2c, 0xae, 0xf6, 0x38, 0x5b, 0x6e, 0xee, 0x4d, 0xae, 0x6d, 0xcf, 0x71, 0x96, 0xb4, 0xe5, 0xc4, 0xdb, 0x53, 0xb3, 0x57, 0x30, 0xe4, 0xff, 0x46, 0x73, 0x2a, 0xe8, 0xd9, 0xda, 0xb4, 0xfa, 0x98, 0xd7, 0xf9, 0xe8, 0xce, 0xa6, 0x43, 0xab, 0x92, 0xb9, 0xdd, 0x5b, 0x9a, 0xfd, 0x0a, 0xa5, 0xc1, 0x57, 0x17, 0xb3, 0xbb, 0x12, 0x73, 0x0b, 0x8a, 0xe7, 0x95, 0xd9, 0xf6, 0x93, 0x96, 0xe2, 0x39, 0xc5, 0xf9, 0xcd, 0x11, 0xa3, 0x3d, 0x56, 0xe7, 0x43, 0x8b, 0xc1, 0xf9, 0xf9, 0x0a, 0xe1, 0xd9, 0x52, 0xc2, 0xb3, 0x45, + 0x43, 0x4a, 0x56, 0x9b, 0xac, 0x82, 0x54, 0x88, 0x56, 0x01, 0xbc, 0x91, 0xb8, 0x4a, 0x3c, 0x84, 0x9c, 0x75, 0x7c, 0x86, 0x47, 0x61, 0xa7, 0x34, 0xfc, 0x85, 0x00, 0x0b, 0xb0, 0xdb, 0xf7, 0x6c, 0x86, 0x81, 0xeb, 0xa2, 0x65, 0xb4, 0x1a, 0xb5, 0x5c, 0x2a, 0x11, 0x8b, 0x04, 0xe8, 0x01, 0x01, 0x52, 0xd2, 0xfe, 0x1f, 0xd6, 0xde, 0x03, 0x3e, 0xae, 0xe2, 0xda, 0x1f, 0xbf, 0x73, 0xdb, 0x56, 0xed, 0xea, 0x6e, 0xef, 0xbd, 0x6a, 0xb5, 0xea, 0xbb, 0xea, 0xd2, 0xaa, 0x5a, 0x56, 0xb5, 0x5c, 0x54, 0x2c, 0xd9, 0x96, 0x2d, 0xc9, 0xb2, 0x6c, 0xe3, 0xde, 0x1b, 0x2e, 0xd8, 0xd8, 0x60, 0x4c, 0xb1, 0x4d, 0x28, 0x26, 0x06, 0x4c, 0xc7, 0x26, 0x10, 0xc0, 0x24, 0x04, 0x02, 0x31, 0xbc, 0x17, 0x42, 0x09, 0x79, 0x09, 0x10, 0xe0, 0x05, 0xd2, 0xe0, 0x25, 0x79, 0x09, 0x09, 0x25, 0xa6, 0xd9, 0xba, + 0xfa, 0xcf, 0xcc, 0xbd, 0xbb, 0x5a, 0xc9, 0x32, 0xf0, 0x3e, 0xff, 0x9f, 0xc1, 0xde, 0xdd, 0x5b, 0x67, 0xce, 0xcc, 0x9c, 0x73, 0xe6, 0x94, 0xef, 0xe1, 0x8a, 0x8a, 0xb1, 0x52, 0x2e, 0x64, 0x38, 0x7b, 0x84, 0x39, 0x45, 0xde, 0xa9, 0xe4, 0xc3, 0x54, 0x85, 0xc5, 0x70, 0xe2, 0xde, 0xb1, 0x27, 0xb4, 0x7e, 0x2a, 0x93, 0x3c, 0xfc, 0x28, 0xb0, 0x82, 0x8f, 0xcb, 0x1b, 0xc6, 0x6e, 0x18, 0x36, 0x98, 0xf9, 0x1f, 0x80, 0x4e, 0xbb, 0xa1, 0x9b, 0x9a, 0x75, 0xf1, 0x69, 0x61, 0x4c, 0x16, 0xc1, 0x35, 0xfe, 0x14, 0x5c, 0xc3, 0x51, 0x34, 0xbf, 0xfc, 0x70, 0x3c, 0x50, 0x4d, 0x45, 0xb2, 0x39, 0x00, 0x77, 0xf3, 0x33, 0x51, 0x35, 0x3d, 0x02, 0x55, 0xd3, 0xbb, 0x4c, 0x0d, 0x7b, 0xaf, 0x2e, 0x94, 0x15, 0x72, 0xe3, 0x3d, 0x51, 0x7c, 0x72, 0x5e, 0x52, 0x15, 0x30, 0x4e, 0x54, 0xd3, 0x44, 0x1b, + 0xf6, 0xa0, 0x56, 0xd8, 0x3e, 0x80, 0x05, 0x8b, 0x7b, 0x96, 0x9e, 0x18, 0x8d, 0xcf, 0xdc, 0xf3, 0xe8, 0xd2, 0x8a, 0x95, 0x03, 0x9d, 0xce, 0x62, 0x55, 0x81, 0x29, 0x7f, 0xeb, 0xcc, 0xba, 0x15, 0x2d, 0x21, 0xdf, 0x8c, 0xd1, 0xfa, 0xbc, 0xd1, 0xf8, 0x86, 0x15, 0xe4, 0x21, 0x7f, 0x88, 0x6f, 0x22, 0x57, 0xea, 0x2b, 0x46, 0x6f, 0x9a, 0x37, 0xfa, 0xd0, 0xb6, 0x5a, 0x53, 0xa8, 0xc8, 0xde, 0xae, 0x53, 0x71, 0x9e, 0x82, 0x48, 0xfb, 0xaa, 0xba, 0xb2, 0x85, 0xb5, 0x3e, 0x4d, 0xa6, 0x87, 0xdc, 0xb0, 0xc4, 0x15, 0x49, 0xc5, 0x50, 0x9f, 0x87, 0x7c, 0x49, 0x4b, 0xe4, 0x23, 0x44, 0x7b, 0xa3, 0x80, 0xc8, 0x32, 0x2d, 0x46, 0xcf, 0x4a, 0x0a, 0xae, 0x8e, 0xfc, 0x5c, 0xbf, 0xd7, 0x6a, 0x16, 0x42, 0xa6, 0x85, 0xd5, 0x31, 0x19, 0x3b, 0x8b, 0x12, 0x0a, 0x15, 0x4f, 0x0a, 0xa4, 0x16, 0x01, + 0x6f, 0x49, 0x4f, 0xdd, 0x68, 0x73, 0x30, 0xd4, 0x34, 0x5c, 0x59, 0xbf, 0xbc, 0x25, 0x18, 0x98, 0x31, 0x62, 0xc9, 0xb4, 0xab, 0xa2, 0x2b, 0xaa, 0x67, 0xdd, 0xb4, 0xba, 0xa6, 0x7e, 0xc7, 0x99, 0xd1, 0x65, 0x0f, 0x6e, 0x4a, 0xb4, 0xcd, 0x03, 0x4d, 0xba, 0xe2, 0xc0, 0x7d, 0x87, 0xa2, 0x6d, 0x23, 0xe5, 0x89, 0xe1, 0x19, 0xfe, 0x9c, 0xd6, 0x81, 0xc2, 0xaa, 0xd1, 0xb6, 0x6c, 0xf2, 0x65, 0x9d, 0x5f, 0x6b, 0x73, 0xc6, 0x96, 0x7e, 0x6f, 0xc9, 0x82, 0xbb, 0xb7, 0x36, 0xd4, 0x6d, 0x3b, 0x3d, 0xda, 0xf7, 0x8b, 0x35, 0xe0, 0x49, 0xbb, 0x2b, 0x39, 0x16, 0x71, 0xea, 0x23, 0x38, 0x16, 0x1c, 0xf6, 0xbd, 0x23, 0x07, 0x36, 0x85, 0xb2, 0x8a, 0xc9, 0x79, 0xa8, 0x00, 0xac, 0x48, 0x73, 0x21, 0xa5, 0xd2, 0x1b, 0xa4, 0x91, 0x36, 0x88, 0xe6, 0x7f, 0x12, 0x23, 0x0b, 0x17, 0xc6, 0xa5, 0x3e, + 0x92, 0xca, 0xc7, 0xfe, 0x24, 0x2f, 0xb2, 0x5d, 0x4f, 0x3a, 0x1d, 0xde, 0xb1, 0xa5, 0x66, 0x0f, 0xbd, 0x70, 0xa1, 0x3e, 0xa8, 0xbb, 0xf0, 0x33, 0xa3, 0x8f, 0x36, 0x97, 0x86, 0x6d, 0xb6, 0x8d, 0x50, 0x43, 0x58, 0x00, 0xe9, 0xf5, 0xdf, 0x90, 0x4f, 0x23, 0x3e, 0x3c, 0x03, 0xed, 0x86, 0x4b, 0x00, 0xce, 0xf8, 0x67, 0x24, 0x00, 0x19, 0x0a, 0x50, 0xb0, 0x23, 0x0a, 0x01, 0xc1, 0x9e, 0xf1, 0x64, 0x15, 0x51, 0x84, 0xd5, 0x53, 0x53, 0x9d, 0x9f, 0xeb, 0xb4, 0xeb, 0x82, 0xfa, 0xa0, 0x40, 0x3b, 0xe9, 0x34, 0xb4, 0xd3, 0xa6, 0x31, 0x5b, 0x74, 0x5c, 0x9b, 0x5e, 0xd0, 0x25, 0x9e, 0xe6, 0x07, 0x86, 0x6d, 0x27, 0xad, 0x15, 0x8b, 0xeb, 0x7c, 0xe1, 0x19, 0x8b, 0x62, 0xb1, 0x05, 0x33, 0xc2, 0xbe, 0xda, 0x81, 0x8a, 0xe2, 0x39, 0xa5, 0x76, 0x73, 0x7c, 0x5e, 0x45, 0x45, 0x57, 0xdc, 0x02, + 0xd7, 0xd1, 0xd1, 0xad, 0x3b, 0x6b, 0x36, 0xdd, 0x3b, 0x34, 0x78, 0xcf, 0xa6, 0x9a, 0x1d, 0xdb, 0x86, 0x86, 0xcb, 0x96, 0x1f, 0x9d, 0x3f, 0xff, 0xd8, 0x8a, 0xb2, 0xe1, 0x03, 0xbe, 0x44, 0x6f, 0x2c, 0xb1, 0xb0, 0xc2, 0x6e, 0xaf, 0x58, 0x98, 0x88, 0xf5, 0x26, 0x7c, 0x74, 0x99, 0xa7, 0xac, 0x3d, 0x92, 0x3d, 0x27, 0x11, 0x0c, 0x26, 0xe6, 0x64, 0x47, 0xda, 0xcb, 0x3c, 0x63, 0xe3, 0xfe, 0xfb, 0x77, 0xf5, 0xde, 0x30, 0x1c, 0x8f, 0x0f, 0xdf, 0xd0, 0xbb, 0xeb, 0x7e, 0x7f, 0xe8, 0xc4, 0xca, 0x96, 0xbd, 0x8b, 0x8a, 0x8b, 0x17, 0xed, 0x6d, 0x59, 0x79, 0x02, 0x4e, 0x8b, 0xf9, 0x50, 0x36, 0xbd, 0x02, 0x65, 0x11, 0x07, 0xe7, 0x4c, 0x4e, 0x22, 0xa2, 0x47, 0x3b, 0x1d, 0x3c, 0x67, 0xd6, 0x27, 0x7b, 0x4f, 0xe0, 0x6c, 0x0b, 0x22, 0x15, 0x1c, 0x90, 0x1d, 0x0a, 0x87, 0x3c, 0x0c, 0x8a, + 0xd4, 0x11, 0x5c, 0xb8, 0x29, 0x9b, 0x25, 0xdc, 0xe3, 0xa4, 0x6b, 0xe2, 0x22, 0x37, 0x15, 0xc6, 0xc5, 0xb0, 0x7f, 0x53, 0xf5, 0x92, 0x3a, 0x6f, 0xa0, 0x7e, 0x61, 0x71, 0xf9, 0x40, 0xbd, 0x0f, 0xac, 0x30, 0x06, 0xb8, 0xfc, 0x3d, 0x6d, 0x33, 0xaf, 0x5e, 0x56, 0x55, 0xbb, 0xf9, 0xde, 0xc1, 0x25, 0x77, 0xae, 0xad, 0x34, 0x46, 0x12, 0x61, 0xfe, 0xb7, 0x5c, 0xbe, 0xe7, 0x7e, 0xf2, 0xb7, 0x5b, 0xf7, 0x84, 0x1b, 0xe7, 0x17, 0x94, 0x2f, 0x48, 0xb8, 0x03, 0x75, 0xfd, 0x7c, 0x9e, 0x3d, 0x6e, 0x8d, 0x16, 0x14, 0x0c, 0x1c, 0x59, 0xd0, 0x73, 0xdb, 0xba, 0xda, 0xca, 0x35, 0x27, 0x16, 0x95, 0xaf, 0x5e, 0x79, 0x45, 0x29, 0xbf, 0xc8, 0x68, 0xc7, 0xf3, 0x7e, 0xe9, 0xf8, 0x27, 0xf4, 0x39, 0x8c, 0x65, 0x17, 0xc4, 0x68, 0xeb, 0x13, 0x7b, 0xfb, 0x09, 0x89, 0x39, 0x21, 0x18, 0x27, 0x15, + 0x27, 0xc4, 0xd8, 0x70, 0x97, 0x08, 0xc7, 0x73, 0xfd, 0xb7, 0xbf, 0xbc, 0x6a, 0xd5, 0xcb, 0xb7, 0xf5, 0xf5, 0xdd, 0x86, 0x3e, 0x6f, 0xef, 0x7f, 0xb9, 0x6c, 0xf1, 0xde, 0xa6, 0xa6, 0xbd, 0x8b, 0x4b, 0x4b, 0x85, 0xcf, 0x32, 0xf2, 0x97, 0xcf, 0xf0, 0xff, 0x78, 0xb2, 0xa7, 0xe7, 0x49, 0xa0, 0x7b, 0xe6, 0x19, 0xa0, 0x47, 0xdf, 0xf8, 0xbf, 0x3f, 0x73, 0xf7, 0xbb, 0xfb, 0x4b, 0x4a, 0xf6, 0xbf, 0x7b, 0xf7, 0xdd, 0xef, 0x5e, 0x55, 0x52, 0x72, 0xd5, 0xbb, 0x68, 0xea, 0xc2, 0x16, 0x92, 0x3d, 0xcc, 0x49, 0x21, 0x0f, 0x39, 0x83, 0x9d, 0x94, 0x23, 0x8c, 0xa6, 0xf3, 0x60, 0x4b, 0x4a, 0x42, 0xaa, 0x08, 0x95, 0x91, 0xc3, 0xf3, 0x59, 0x54, 0x66, 0x50, 0x96, 0x30, 0x52, 0x67, 0x7a, 0x5e, 0x24, 0xc9, 0x37, 0x94, 0x0e, 0x87, 0x4d, 0x56, 0x06, 0x4e, 0x30, 0x27, 0xbf, 0x5e, 0x4c, 0xd7, + 0x9a, 0x0a, 0x2c, 0x4f, 0xc9, 0xe5, 0x40, 0xa1, 0x9c, 0x01, 0x86, 0x71, 0x0c, 0x75, 0x3b, 0x9c, 0xa2, 0x1a, 0x48, 0x03, 0x16, 0xdc, 0xf8, 0x6f, 0xf4, 0xbb, 0x63, 0xfc, 0x63, 0xda, 0x8a, 0x7f, 0xdf, 0xc4, 0x63, 0x9d, 0x06, 0x9e, 0x1f, 0x64, 0xee, 0x80, 0xbf, 0x8f, 0x6a, 0xd0, 0xef, 0x19, 0xe3, 0x1f, 0x53, 0x1f, 0xa2, 0x7c, 0x78, 0x70, 0xec, 0x3d, 0xf4, 0xbb, 0x18, 0x9e, 0x57, 0xe0, 0xdf, 0xc7, 0x5f, 0x47, 0xbf, 0xeb, 0x20, 0xa3, 0xf8, 0x04, 0xdf, 0x7f, 0xf3, 0x1f, 0x10, 0x8d, 0xcb, 0x20, 0x8d, 0xdb, 0xff, 0x5f, 0xd2, 0xb8, 0xbd, 0xf3, 0xba, 0xe7, 0x56, 0xad, 0x7a, 0xee, 0xf0, 0xac, 0x59, 0x87, 0xd1, 0xe7, 0x75, 0x9d, 0x2f, 0xc7, 0x7a, 0x37, 0x54, 0x57, 0x6f, 0x40, 0xd6, 0x76, 0xfc, 0x19, 0xfb, 0xce, 0x34, 0x46, 0x45, 0xe5, 0x79, 0x48, 0x63, 0x0e, 0xe9, 0x20, 0x2a, + 0x89, 0xa8, 0x83, 0x40, 0xd2, 0x4e, 0xd5, 0x41, 0x20, 0xc7, 0xb0, 0x4e, 0xd2, 0x41, 0x52, 0x34, 0x86, 0x4a, 0x08, 0x7f, 0xf1, 0x4b, 0x90, 0x47, 0xfd, 0x5b, 0x65, 0x33, 0x6b, 0xd9, 0x4a, 0xf0, 0xc8, 0xc9, 0xe3, 0xc7, 0x8f, 0x53, 0x67, 0xa2, 0x79, 0xaf, 0x2b, 0x58, 0x3a, 0x43, 0x32, 0x0f, 0xac, 0xff, 0xfa, 0x73, 0x8c, 0x0f, 0x09, 0xe9, 0xe4, 0xc0, 0xf8, 0x90, 0x05, 0x89, 0x5c, 0x3b, 0x64, 0xaa, 0x51, 0x41, 0x7a, 0x89, 0xc9, 0x40, 0x2b, 0x30, 0xba, 0x57, 0xba, 0x02, 0xa2, 0xd7, 0x0a, 0xea, 0x07, 0x33, 0xa1, 0x7e, 0x60, 0x26, 0x9a, 0x2e, 0x93, 0x27, 0xa9, 0x1f, 0x18, 0xdf, 0xe6, 0x5d, 0x97, 0xef, 0x62, 0x45, 0xa6, 0x16, 0xfc, 0x67, 0x69, 0x7f, 0xc2, 0x1b, 0xa8, 0x9d, 0x5f, 0x54, 0xd4, 0x5d, 0x9f, 0x23, 0x57, 0x45, 0xc2, 0x4b, 0x55, 0xb3, 0x77, 0xdc, 0xb5, 0x60, 0xe1, + 0xc9, 0x0d, 0xb5, 0xb9, 0x9d, 0x2b, 0x2a, 0xf8, 0x63, 0x59, 0xf3, 0x02, 0x3f, 0xf6, 0x3b, 0x36, 0xd5, 0xe9, 0xa2, 0x1a, 0xf0, 0xf3, 0xd2, 0xe5, 0xa3, 0xab, 0x2a, 0x2b, 0xfa, 0x2a, 0x1c, 0x8e, 0x78, 0x73, 0x24, 0x60, 0x91, 0x68, 0x86, 0x6e, 0x19, 0x29, 0x42, 0x81, 0x1f, 0xb3, 0x0e, 0xed, 0xd8, 0x58, 0xc1, 0x1f, 0x56, 0xa8, 0x30, 0x7f, 0xed, 0x84, 0x73, 0xc3, 0x0e, 0xfb, 0x50, 0x8b, 0x6a, 0xc2, 0xab, 0xa1, 0xfe, 0x61, 0x13, 0xf4, 0x8f, 0xa4, 0xbe, 0x01, 0x25, 0xad, 0x98, 0x44, 0x24, 0x8e, 0xf2, 0x4a, 0xd0, 0x5a, 0x5e, 0x9a, 0x9f, 0x97, 0x9b, 0x13, 0xf0, 0x99, 0x0c, 0x3e, 0x56, 0xae, 0x4f, 0xcb, 0x24, 0x22, 0xbf, 0xb3, 0xe6, 0x11, 0xdc, 0xa1, 0x46, 0x6d, 0x5f, 0x70, 0x62, 0x75, 0x95, 0xa7, 0x7a, 0x7e, 0xc9, 0xa1, 0x6b, 0x5a, 0xaf, 0xfa, 0xc1, 0xe0, 0xda, 0xbb, 0xd7, + 0xb5, 0x58, 0xe6, 0xb9, 0xab, 0x54, 0xea, 0x60, 0xed, 0x60, 0x4b, 0xd3, 0xca, 0x26, 0x9f, 0xad, 0xe3, 0xf0, 0x9a, 0xfe, 0x2b, 0xdb, 0xbd, 0xbe, 0x1a, 0x38, 0x15, 0x7a, 0x70, 0xaf, 0x43, 0xb4, 0x1e, 0xf5, 0x24, 0xb6, 0xf8, 0xe0, 0xec, 0x86, 0xe5, 0xb3, 0x8a, 0x35, 0x81, 0x87, 0xf7, 0x0d, 0xdf, 0xb5, 0xba, 0xbc, 0x78, 0xf0, 0xda, 0x39, 0x3e, 0x95, 0xd2, 0xee, 0x68, 0xa9, 0xcb, 0xc9, 0x6d, 0xe9, 0x8f, 0x96, 0x2c, 0xac, 0x0b, 0x1c, 0x01, 0xbe, 0x44, 0x4f, 0xbc, 0xbc, 0xa7, 0xdc, 0x2e, 0xd2, 0x00, 0xce, 0x5d, 0x2d, 0x1c, 0xb3, 0x4e, 0x38, 0xf7, 0x19, 0xc2, 0x40, 0x54, 0x24, 0x4a, 0x51, 0xb4, 0x85, 0x56, 0x82, 0xb5, 0x47, 0xac, 0x61, 0x08, 0x41, 0x7e, 0x43, 0x90, 0xbf, 0xe3, 0xa4, 0xae, 0x21, 0xb2, 0x95, 0x65, 0x09, 0x82, 0x35, 0xb0, 0x06, 0xbd, 0x0e, 0x25, 0x5c, 0x42, + 0x26, 0xcf, 0x10, 0x8c, 0xa8, 0x6a, 0xc4, 0xa7, 0xd3, 0x35, 0xde, 0xa2, 0x18, 0x5e, 0x46, 0x2d, 0x37, 0xe8, 0xee, 0x7b, 0x78, 0xec, 0x1a, 0x83, 0x83, 0xd1, 0x91, 0x67, 0x7f, 0x44, 0x2a, 0xc9, 0x3c, 0x7b, 0x8d, 0x77, 0xac, 0x63, 0x2d, 0x67, 0xe2, 0x8f, 0x80, 0x75, 0x66, 0xed, 0x10, 0x75, 0xfb, 0x45, 0x5c, 0xcb, 0x73, 0x39, 0xa4, 0xbf, 0x14, 0xd7, 0x9d, 0x81, 0x32, 0x27, 0xe0, 0x27, 0x49, 0x1a, 0x2a, 0x1b, 0x68, 0x00, 0x92, 0xa9, 0xd0, 0xb0, 0xbd, 0x24, 0x81, 0x91, 0x9f, 0x70, 0x3e, 0xfc, 0x60, 0x2a, 0x15, 0x1a, 0x0e, 0x84, 0x57, 0x1b, 0xf2, 0x06, 0x42, 0x6e, 0xa4, 0xfd, 0x4d, 0xd5, 0x36, 0xd2, 0x60, 0x9e, 0x10, 0xd1, 0x27, 0x94, 0x8d, 0x8d, 0xab, 0x7a, 0xd7, 0x9c, 0xde, 0x58, 0x8e, 0x56, 0x5a, 0xcd, 0xc6, 0xa5, 0xdd, 0x9e, 0x0a, 0xad, 0x4b, 0x5f, 0xd8, 0x38, 0x50, + 0x3b, 0x73, 0x63, 0x67, 0x24, 0xd0, 0xba, 0xae, 0x75, 0xf9, 0x96, 0xed, 0x1b, 0xc9, 0x47, 0x63, 0x55, 0x7c, 0x0b, 0x75, 0x95, 0xb1, 0x6e, 0xe3, 0xc9, 0x05, 0xeb, 0x9e, 0xbe, 0x6a, 0x86, 0x25, 0x52, 0xe2, 0x9c, 0x6b, 0x73, 0x06, 0x9c, 0xb9, 0x5d, 0x5b, 0x66, 0xd6, 0x2c, 0x6b, 0x0e, 0x06, 0x7c, 0xe4, 0x7d, 0xf3, 0xb2, 0x62, 0x82, 0x8c, 0x5e, 0x06, 0xdb, 0x58, 0x81, 0xe3, 0xd6, 0x10, 0x37, 0x80, 0x14, 0xec, 0x9d, 0xc2, 0xcd, 0x34, 0x84, 0xc6, 0x37, 0x55, 0x3a, 0x43, 0x09, 0x81, 0xc5, 0x33, 0x5d, 0xc1, 0x49, 0xc7, 0x9e, 0x56, 0x15, 0x64, 0x1d, 0x26, 0x3b, 0xd4, 0x0e, 0xcd, 0x98, 0x47, 0x6b, 0x63, 0x4e, 0x74, 0x07, 0x72, 0xb9, 0x0b, 0x4b, 0xed, 0x21, 0xfa, 0x90, 0x2d, 0x57, 0x67, 0xd3, 0xee, 0x17, 0xf4, 0x45, 0x28, 0x7e, 0x68, 0x0e, 0xce, 0x55, 0x0e, 0xae, 0x38, 0xc8, + 0x41, 0x75, 0x82, 0x7c, 0x82, 0x67, 0xa0, 0xbe, 0x3f, 0x98, 0x74, 0xa5, 0xad, 0x10, 0x2d, 0x88, 0xd9, 0x90, 0x03, 0x4d, 0x2b, 0x9a, 0xe2, 0x97, 0x97, 0x4c, 0xc1, 0x1b, 0x76, 0x37, 0x2c, 0x9f, 0x19, 0x08, 0x37, 0x0f, 0x57, 0x56, 0x2f, 0x6b, 0x0e, 0x81, 0x6b, 0x82, 0x9e, 0xf8, 0xfe, 0x96, 0x8e, 0x1b, 0x57, 0xd7, 0x36, 0x5e, 0xf9, 0xd8, 0xf2, 0xd1, 0x87, 0xb7, 0xd4, 0x1a, 0x23, 0x95, 0x41, 0xfe, 0x7e, 0x55, 0xcc, 0xf3, 0x38, 0xf5, 0xef, 0xfd, 0x37, 0x44, 0x5b, 0x06, 0xe3, 0x35, 0xa3, 0x33, 0x83, 0xe1, 0xe6, 0xa5, 0xbc, 0xa4, 0x30, 0x3f, 0x9c, 0x15, 0x1f, 0xbd, 0x6d, 0x68, 0xe1, 0x7d, 0xdb, 0x67, 0xd4, 0x6f, 0x3f, 0x3d, 0x52, 0xbd, 0x6e, 0xe5, 0x68, 0x31, 0xcf, 0xe9, 0x2c, 0x98, 0x27, 0x97, 0x8d, 0x1b, 0xc8, 0x25, 0x90, 0x31, 0x48, 0xc0, 0x7d, 0xf0, 0xb7, 0x03, 0xe5, + 0x14, 0x91, 0x3f, 0x1a, 0x77, 0xa4, 0x72, 0x8a, 0x06, 0xc7, 0x1d, 0x64, 0x09, 0xb8, 0x08, 0xcf, 0xdf, 0x4f, 0x50, 0xc2, 0x79, 0x90, 0x7e, 0xbe, 0x11, 0x9e, 0x6f, 0xc3, 0xf7, 0x3f, 0x40, 0x90, 0xd3, 0x9c, 0xaf, 0x85, 0xcf, 0x9f, 0x81, 0xcf, 0x3f, 0x48, 0x80, 0x69, 0xce, 0xbb, 0xe0, 0xfd, 0xc3, 0xe0, 0x6b, 0x78, 0xfe, 0xa1, 0xe4, 0xf3, 0x27, 0xbd, 0xbf, 0x03, 0xde, 0x5f, 0x89, 0xcf, 0x3f, 0x9c, 0x6c, 0xdf, 0xa4, 0xfb, 0xdb, 0xe1, 0xf9, 0x79, 0xf8, 0xfc, 0xe9, 0xe4, 0xf3, 0x27, 0xdd, 0x3f, 0x0f, 0x3e, 0x7f, 0x0e, 0x3e, 0x7f, 0x66, 0xda, 0xf6, 0xb7, 0xc1, 0xfb, 0x3b, 0x70, 0xfb, 0x1e, 0x49, 0x3e, 0x9f, 0x4a, 0x3f, 0x3f, 0x0b, 0xde, 0x5f, 0x87, 0xcf, 0xff, 0x60, 0x7c, 0x7c, 0x9a, 0xe7, 0x77, 0x8f, 0xef, 0x20, 0x1b, 0xc0, 0x76, 0x4a, 0x42, 0x9f, 0x9b, 0xb6, 0x7d, 0x2b, + 0x89, 0x26, 0x32, 0x8f, 0x78, 0x0c, 0x9e, 0x7f, 0x61, 0xfa, 0xf3, 0xe3, 0xff, 0x24, 0xf3, 0x80, 0x02, 0x9e, 0x7f, 0x71, 0x5a, 0xfa, 0x97, 0xf3, 0x77, 0x01, 0x2b, 0xb9, 0x04, 0x9e, 0xff, 0xaf, 0x69, 0xdb, 0x5f, 0x08, 0xcf, 0xeb, 0xf0, 0xf9, 0x5f, 0x8f, 0xf3, 0x53, 0xcf, 0x63, 0x99, 0xf8, 0x25, 0x1c, 0xdf, 0x0f, 0xb0, 0x4c, 0x3c, 0xf2, 0x8d, 0xfb, 0x78, 0xc5, 0x74, 0xa7, 0x86, 0x2e, 0xbf, 0xc5, 0x17, 0xee, 0x52, 0x5d, 0xee, 0xae, 0xcb, 0xec, 0xfe, 0xff, 0x0f, 0xfb, 0x7f, 0x72, 0x49, 0x76, 0xcb, 0x70, 0x59, 0xe9, 0x50, 0x73, 0x24, 0xd2, 0x3c, 0x54, 0x5a, 0x36, 0xdc, 0x92, 0x7d, 0xbd, 0xce, 0x57, 0x60, 0xb7, 0xe7, 0x79, 0x75, 0x28, 0x0b, 0xcf, 0x9a, 0xef, 0x37, 0x80, 0x55, 0x03, 0xd7, 0xf5, 0x47, 0xa3, 0xfd, 0xd7, 0x0d, 0x24, 0x3f, 0xdb, 0x87, 0xca, 0x0d, 0x86, 0xf2, + 0xa1, 0xf6, 0xb6, 0xe1, 0x32, 0xa3, 0xb1, 0x6c, 0x18, 0xf3, 0x81, 0x01, 0x48, 0x88, 0x18, 0xf9, 0xbe, 0xa0, 0xdf, 0x28, 0x58, 0x86, 0xba, 0xac, 0x01, 0x00, 0xaa, 0x37, 0xa6, 0x09, 0xe1, 0x5b, 0xc8, 0x09, 0xdb, 0xff, 0x62, 0xc8, 0x3e, 0x63, 0xfc, 0xe0, 0x4d, 0xcc, 0xb0, 0xd6, 0x27, 0x8d, 0xfc, 0x71, 0xd3, 0x82, 0x05, 0x0b, 0xc0, 0xdd, 0x39, 0x9d, 0x6a, 0x85, 0xbc, 0xe2, 0xf9, 0xb1, 0x2b, 0x05, 0x5e, 0x53, 0x0f, 0xdf, 0xd1, 0x02, 0xe9, 0x9c, 0x85, 0xb0, 0x04, 0x8d, 0xb0, 0xe7, 0xc1, 0xe4, 0xde, 0x1f, 0xbb, 0xf2, 0x06, 0x27, 0x8b, 0x5c, 0xbb, 0x95, 0x53, 0x43, 0x91, 0x9b, 0x05, 0xc2, 0x93, 0x44, 0x2e, 0xf2, 0x4b, 0x09, 0xd9, 0xfd, 0x53, 0x04, 0xae, 0x1a, 0x09, 0x5c, 0xd2, 0x66, 0x34, 0x8d, 0x7d, 0xa5, 0xc8, 0x78, 0xb7, 0xa0, 0xae, 0x38, 0x2b, 0x12, 0xcb, 0x50, 0x58, + 0xed, 0xed, 0x92, 0x68, 0x7d, 0x6f, 0x51, 0xbc, 0xab, 0xc2, 0x6d, 0xce, 0x8a, 0xdb, 0x6f, 0x31, 0xe5, 0xe9, 0xf7, 0x9a, 0xf5, 0x73, 0x73, 0x55, 0x0e, 0xd5, 0x6f, 0xf3, 0x96, 0x17, 0xe6, 0x15, 0x45, 0x83, 0x5a, 0x35, 0x93, 0x11, 0xef, 0x8c, 0x59, 0x9d, 0xc5, 0xed, 0x39, 0xa1, 0x86, 0xaa, 0x72, 0xc7, 0xed, 0x12, 0xa9, 0x90, 0x07, 0xf6, 0x25, 0xd9, 0x08, 0xdb, 0x5b, 0x42, 0xfc, 0x47, 0x42, 0xae, 0x84, 0xf2, 0xd5, 0x84, 0xe5, 0x6b, 0xaa, 0x9e, 0x14, 0x01, 0x9b, 0x97, 0xbe, 0xbd, 0x17, 0x84, 0xec, 0x6a, 0x90, 0x9c, 0x29, 0x97, 0xbd, 0x44, 0x30, 0xd0, 0x2b, 0xbf, 0xfd, 0x29, 0xaa, 0x6f, 0x7b, 0xca, 0x37, 0x3e, 0x00, 0xe3, 0x03, 0xc9, 0x7c, 0x9c, 0x29, 0x3b, 0x7b, 0x72, 0xda, 0xb0, 0xc4, 0x70, 0x79, 0x49, 0x3f, 0x41, 0xdc, 0x60, 0x97, 0x24, 0x5a, 0xdb, 0x95, 0x5f, 0x34, + 0xb7, 0xc2, 0xb5, 0x7e, 0x6f, 0xa4, 0xb1, 0x3f, 0xaf, 0x64, 0x6e, 0x75, 0x34, 0xb3, 0xd2, 0x94, 0x25, 0x65, 0x32, 0x6d, 0x59, 0x4e, 0x5b, 0xb6, 0x43, 0x9d, 0x19, 0xa8, 0xca, 0xe9, 0xaa, 0x8f, 0x47, 0xa3, 0x31, 0xa5, 0xc2, 0x64, 0x23, 0x31, 0x15, 0x1d, 0x85, 0x0d, 0xc1, 0xdd, 0xd1, 0xc0, 0xe6, 0xd2, 0xae, 0x52, 0xbb, 0x29, 0x6f, 0x66, 0x81, 0x45, 0x2e, 0xd7, 0xc8, 0xed, 0x56, 0xad, 0xc6, 0x1e, 0xd0, 0xd9, 0xc3, 0x56, 0xe5, 0x22, 0x50, 0x52, 0x90, 0x9b, 0x0f, 0x27, 0x8f, 0x8a, 0x11, 0x72, 0xf7, 0xe1, 0xda, 0xfb, 0x1d, 0xb6, 0x23, 0x40, 0x99, 0x8e, 0xed, 0x08, 0xe2, 0xac, 0x40, 0x86, 0x84, 0x49, 0x82, 0x1d, 0xcb, 0xf4, 0xcb, 0x9a, 0x0f, 0x62, 0x45, 0xc5, 0x94, 0x20, 0xd2, 0x91, 0xdf, 0x46, 0x14, 0xe9, 0x60, 0x1f, 0xfb, 0x57, 0xf0, 0x33, 0x95, 0xaa, 0x6d, 0x13, 0xff, + 0x6b, 0x9d, 0x81, 0x52, 0x80, 0x79, 0x57, 0x03, 0xf5, 0xfb, 0x91, 0x42, 0xfe, 0xc5, 0x26, 0x45, 0xe6, 0xe9, 0xd3, 0x5c, 0x46, 0x15, 0x78, 0x83, 0x2f, 0xc4, 0x78, 0x20, 0x5f, 0x92, 0x95, 0xb0, 0x1d, 0x59, 0xc4, 0xa7, 0x09, 0x95, 0x17, 0xd0, 0x84, 0x07, 0x90, 0xb4, 0x1a, 0xca, 0xa5, 0x64, 0x3c, 0xa7, 0x37, 0x0d, 0xe0, 0x04, 0x21, 0x86, 0x0f, 0xa6, 0xa4, 0xbb, 0x28, 0xd3, 0x27, 0xc6, 0xfd, 0x5b, 0x2f, 0x9d, 0x18, 0xff, 0xef, 0xf8, 0x54, 0xd5, 0x77, 0x7d, 0xea, 0x77, 0x7a, 0x20, 0x9e, 0x17, 0x72, 0xaf, 0x2e, 0xe0, 0xd3, 0x09, 0x0a, 0x88, 0x36, 0x9e, 0x5e, 0x4f, 0x75, 0x12, 0xcc, 0xe4, 0x84, 0xb1, 0x23, 0x02, 0xc6, 0x22, 0x59, 0x65, 0x3d, 0xe5, 0x8e, 0x70, 0xf3, 0x50, 0x45, 0xc3, 0x9a, 0x40, 0x96, 0x5a, 0x9f, 0x61, 0x75, 0x67, 0xbb, 0xcc, 0x51, 0xaf, 0xbe, 0xba, 0xc2, + 0x12, 0xb3, 0x74, 0x55, 0x83, 0xd5, 0xfe, 0xec, 0x9f, 0x82, 0xd7, 0x65, 0xae, 0xb2, 0x8e, 0xbc, 0xf2, 0x81, 0xc6, 0x40, 0xdc, 0x57, 0xa1, 0xcd, 0xd4, 0x64, 0xaa, 0x5d, 0x79, 0xee, 0xd2, 0x2a, 0xa5, 0xd4, 0x04, 0xd6, 0x95, 0xdb, 0x83, 0x02, 0xcf, 0x6d, 0x83, 0xf4, 0x9e, 0x0b, 0xe9, 0xad, 0x26, 0xb2, 0x89, 0xe7, 0x9e, 0xd4, 0x02, 0xa1, 0x40, 0x07, 0x22, 0xb4, 0x05, 0xb6, 0x97, 0xa4, 0x97, 0x8a, 0xa6, 0x8e, 0xa1, 0x94, 0xa9, 0x63, 0x35, 0x95, 0xa4, 0xef, 0x24, 0xbc, 0xe2, 0x89, 0x0b, 0x86, 0xa8, 0x24, 0x55, 0x2f, 0x73, 0x81, 0xf0, 0x04, 0xd5, 0x37, 0x3f, 0xe1, 0x1b, 0x6e, 0xc6, 0x74, 0xe3, 0x00, 0x91, 0x9d, 0xe5, 0x76, 0x1a, 0xf5, 0x32, 0x09, 0xa1, 0x06, 0xea, 0x09, 0x7b, 0xcb, 0x04, 0x20, 0xf2, 0xa5, 0xd6, 0x16, 0xc1, 0x99, 0x0e, 0x66, 0xdb, 0x72, 0x3c, 0xda, 0x44, + 0xa9, 0x23, 0xcf, 0xcd, 0xd5, 0x94, 0xe9, 0x72, 0x0d, 0xb6, 0x86, 0x50, 0xa0, 0xa5, 0xdc, 0x1f, 0x68, 0x18, 0x28, 0xaf, 0x58, 0x54, 0xe7, 0xed, 0x7a, 0x48, 0x16, 0x34, 0x6f, 0x1f, 0xd6, 0xba, 0xa3, 0xd6, 0x8a, 0x5a, 0xad, 0x27, 0xc7, 0x52, 0xdc, 0x08, 0x6e, 0x34, 0xe7, 0x98, 0x0d, 0x56, 0x73, 0x51, 0x67, 0x49, 0x61, 0x5f, 0x5d, 0x28, 0xd0, 0xb0, 0xa8, 0x6c, 0xc5, 0xda, 0x0f, 0x54, 0x1a, 0xa1, 0x96, 0x1b, 0x24, 0xe4, 0x2c, 0x48, 0x43, 0x2d, 0xe1, 0x4d, 0xb8, 0x00, 0x26, 0x21, 0x25, 0x46, 0x4f, 0x2e, 0x4e, 0xb1, 0x6b, 0x2d, 0xa1, 0xd5, 0x86, 0xd3, 0xf4, 0x37, 0x6a, 0xc2, 0xba, 0x42, 0xce, 0x92, 0x30, 0xfc, 0xbf, 0x68, 0x99, 0xda, 0xc0, 0xad, 0x04, 0x5a, 0x9d, 0x9e, 0xff, 0xa1, 0x52, 0x47, 0xfe, 0xae, 0x45, 0x65, 0x52, 0x8c, 0xbd, 0x2e, 0xe5, 0x54, 0x52, 0xd2, 0x14, + 0xb0, 0xea, 0x95, 0x4b, 0x70, 0xfc, 0x07, 0x1a, 0xaf, 0x0e, 0xc8, 0x0b, 0x33, 0x10, 0xaa, 0x12, 0xf1, 0x5e, 0x42, 0x6e, 0x86, 0x9a, 0x6e, 0x0e, 0x56, 0x77, 0x85, 0x61, 0xb3, 0x5f, 0xde, 0xdc, 0xb2, 0x9a, 0x4e, 0x8e, 0xdc, 0x37, 0x5c, 0x34, 0x44, 0x27, 0x47, 0xef, 0x5b, 0x9f, 0xa4, 0xfa, 0xf6, 0x27, 0x7d, 0xcb, 0x43, 0xf0, 0x48, 0x1a, 0xd5, 0x2a, 0x40, 0x14, 0xc7, 0x82, 0x7e, 0xa3, 0x5e, 0x65, 0x57, 0xdb, 0xe1, 0x78, 0x66, 0x80, 0x0c, 0xc1, 0x06, 0x14, 0x4f, 0x96, 0x47, 0x85, 0xc3, 0x39, 0xa9, 0xaa, 0xaf, 0xf1, 0x1b, 0xcc, 0x3f, 0xa0, 0xbd, 0xbc, 0x84, 0x73, 0x66, 0x99, 0xf3, 0x6b, 0x4a, 0xca, 0x2c, 0x01, 0xb3, 0x32, 0x2f, 0x16, 0x2b, 0xcc, 0xb0, 0x04, 0x47, 0x6b, 0x2b, 0xfc, 0xf5, 0x8b, 0x4a, 0x4b, 0x17, 0xd5, 0x05, 0x2a, 0x6a, 0x0b, 0x0b, 0x3c, 0x55, 0xf3, 0x0a, + 0x0b, 0xbb, 0x2a, 0x3d, 0x05, 0xc3, 0x55, 0x79, 0xce, 0x2c, 0xab, 0xb2, 0xb0, 0x32, 0xaf, 0x8a, 0x34, 0x64, 0x5a, 0xbc, 0x9a, 0x82, 0xd2, 0xd2, 0x02, 0x8d, 0xd7, 0x92, 0x39, 0x66, 0xec, 0xad, 0xca, 0x9f, 0x55, 0xe2, 0x74, 0x96, 0xcc, 0xca, 0xaf, 0xea, 0x35, 0x1a, 0x3b, 0x4b, 0xb2, 0x66, 0xa2, 0x58, 0xb5, 0x99, 0x59, 0x25, 0x02, 0xee, 0x0c, 0xfc, 0x97, 0xac, 0x87, 0x63, 0xa1, 0x80, 0xdc, 0x2a, 0x98, 0xf0, 0xa9, 0x05, 0x5d, 0x3a, 0x65, 0xe6, 0x11, 0x75, 0x6a, 0x51, 0x93, 0x0e, 0xf8, 0xb0, 0x26, 0xad, 0x4d, 0xba, 0xf5, 0x60, 0xaf, 0x50, 0x9d, 0x4e, 0x76, 0xaa, 0x1a, 0x2d, 0x04, 0x7f, 0xbc, 0x5d, 0x51, 0x52, 0x92, 0xa8, 0x2d, 0x28, 0x49, 0x6c, 0x51, 0x3b, 0x55, 0xb9, 0xe5, 0xc1, 0x96, 0x72, 0x9f, 0xaf, 0x6e, 0x41, 0x69, 0x69, 0x5f, 0xc2, 0x93, 0x61, 0xf6, 0x19, 0x8f, + 0xca, 0x03, 0xa6, 0x5d, 0x24, 0x53, 0x56, 0x96, 0x28, 0x8c, 0x57, 0x55, 0xf1, 0x2f, 0xe8, 0x3c, 0x9c, 0xc9, 0x16, 0x6f, 0x2b, 0xc8, 0x9e, 0x53, 0x13, 0x74, 0x57, 0x75, 0xc5, 0x03, 0x75, 0xd5, 0x95, 0xee, 0xdf, 0xe0, 0xe9, 0x49, 0x00, 0xfe, 0x28, 0x6c, 0xca, 0x7e, 0xb8, 0xb7, 0x10, 0xf3, 0xa8, 0x41, 0x72, 0x57, 0x21, 0xe4, 0x7d, 0x07, 0x53, 0x79, 0xdf, 0x67, 0x75, 0x79, 0xae, 0x39, 0x54, 0x5c, 0x97, 0xef, 0x9d, 0x5b, 0xe6, 0x31, 0x31, 0xf3, 0x5d, 0x1e, 0x41, 0x6f, 0x5b, 0x38, 0xfe, 0x25, 0xb3, 0x91, 0xb9, 0x87, 0x08, 0x82, 0x47, 0xc9, 0x7d, 0x18, 0x39, 0xe0, 0x01, 0xc2, 0x42, 0x3e, 0x0f, 0xa0, 0x9e, 0x4c, 0xe5, 0xc2, 0xdf, 0x02, 0x72, 0xc1, 0xf3, 0x29, 0x3d, 0xaf, 0x1f, 0x5e, 0xdf, 0x29, 0x5e, 0xbf, 0x47, 0xbc, 0xde, 0x84, 0xaf, 0x7f, 0x98, 0xd2, 0x4d, 0xbd, 0x1e, + 0x52, 0x6b, 0x90, 0x38, 0x4d, 0x07, 0xe8, 0x1b, 0x10, 0xfe, 0x11, 0xe4, 0x9b, 0x34, 0x05, 0xe8, 0x79, 0x0c, 0x82, 0xc2, 0x42, 0x50, 0xa1, 0x28, 0x36, 0x00, 0xe9, 0x22, 0x34, 0x68, 0xe3, 0x26, 0x92, 0x83, 0xc4, 0x10, 0x06, 0x84, 0x02, 0x43, 0x07, 0xf8, 0x2b, 0x4e, 0xf2, 0xab, 0xc1, 0x8d, 0x27, 0xc1, 0x51, 0x72, 0x1e, 0xdf, 0x0d, 0x1e, 0x3c, 0x06, 0xee, 0xe7, 0x71, 0x82, 0x1f, 0xfc, 0x4d, 0x9d, 0x27, 0xff, 0xf6, 0x6d, 0x79, 0xd9, 0x47, 0xa9, 0x25, 0x17, 0xbf, 0x4f, 0xfe, 0xed, 0x18, 0x6e, 0xcb, 0x4e, 0xe2, 0x2d, 0x5a, 0x49, 0xf7, 0xc3, 0xb6, 0xb0, 0x8f, 0xb3, 0x28, 0x3f, 0xdf, 0x2f, 0x03, 0x41, 0x19, 0x28, 0x96, 0x01, 0xa3, 0x0c, 0x50, 0xf7, 0xf0, 0x77, 0xf3, 0x27, 0x97, 0x81, 0x01, 0xfe, 0xf4, 0x08, 0x98, 0x07, 0xe6, 0x2c, 0xe3, 0x1f, 0x01, 0x0b, 0xdf, 0x06, 0x8b, + 0xf8, 0x93, 0xa3, 0x60, 0x00, 0xcc, 0x1d, 0xe5, 0x1f, 0x06, 0x73, 0x46, 0xf9, 0x47, 0xf8, 0xbb, 0x85, 0xfc, 0x84, 0x25, 0xc4, 0x53, 0xb4, 0x9f, 0xbe, 0x1a, 0x7e, 0x33, 0x12, 0x11, 0x22, 0x3b, 0x11, 0x46, 0xea, 0x55, 0x32, 0x11, 0x98, 0x46, 0xd9, 0xf0, 0x53, 0x32, 0x3b, 0xa0, 0xb6, 0xe9, 0x17, 0x7a, 0x88, 0xda, 0x05, 0xa6, 0x44, 0x5d, 0x72, 0x53, 0x7e, 0x2f, 0xa1, 0x5e, 0xb8, 0x58, 0x09, 0x16, 0x4c, 0x85, 0x3d, 0x99, 0xfa, 0x1b, 0xf6, 0x0c, 0x9c, 0xb1, 0x09, 0x07, 0x6c, 0x22, 0x0e, 0xc7, 0xd8, 0xdc, 0x29, 0x07, 0xf0, 0x7c, 0x3e, 0x0a, 0xce, 0x50, 0xe7, 0xa9, 0xe3, 0x38, 0x67, 0xdf, 0x93, 0x70, 0x8a, 0xad, 0x5d, 0x7c, 0x09, 0xe5, 0x92, 0x89, 0xfb, 0x28, 0x94, 0x24, 0x49, 0x3f, 0x4c, 0xc3, 0x9a, 0x63, 0xe0, 0xaf, 0x22, 0x86, 0xe5, 0x31, 0xe2, 0x2c, 0xf5, 0x39, 0xbd, + 0x07, 0xb2, 0xbd, 0x75, 0x02, 0xcb, 0xc9, 0x22, 0x24, 0x2c, 0x2b, 0x59, 0x8a, 0x6d, 0xfd, 0x23, 0x52, 0xc0, 0xca, 0x65, 0x24, 0x94, 0x8c, 0xcc, 0x12, 0x1a, 0xe0, 0xc7, 0x0b, 0x3a, 0x27, 0x25, 0x94, 0x01, 0x4d, 0x84, 0xe0, 0xc5, 0x12, 0xc1, 0x7b, 0xf1, 0xcd, 0x97, 0xe2, 0xaa, 0x9f, 0x88, 0xb7, 0x26, 0x03, 0x47, 0x38, 0x1c, 0x38, 0x82, 0x00, 0xbc, 0x24, 0x5e, 0x84, 0x5e, 0x86, 0xff, 0x16, 0x4a, 0x0a, 0xf5, 0xe4, 0xcf, 0xfb, 0xc1, 0xb5, 0x7c, 0xce, 0xa6, 0xeb, 0x36, 0xf1, 0x63, 0xa0, 0xb9, 0x9f, 0x9f, 0x0f, 0xfe, 0xb1, 0xf6, 0xba, 0xb5, 0x7f, 0xa3, 0xc0, 0xf3, 0xc7, 0xbe, 0xf8, 0xe2, 0xd8, 0xf3, 0xc7, 0xfe, 0xf1, 0x8f, 0x63, 0x88, 0x06, 0x0f, 0x81, 0xcd, 0xd4, 0xef, 0xa8, 0xd7, 0x70, 0x16, 0xc3, 0x91, 0x04, 0xaa, 0x8c, 0x41, 0x69, 0x91, 0x95, 0x11, 0x25, 0xe9, 0xe4, + 0x7a, 0x48, 0x86, 0xa5, 0x9b, 0x05, 0xfc, 0x48, 0x1f, 0x82, 0xc2, 0x02, 0x12, 0x9c, 0xcd, 0x08, 0x1b, 0x27, 0x61, 0x07, 0x65, 0x70, 0x4c, 0xe1, 0x28, 0x2e, 0x4a, 0xda, 0xae, 0x96, 0xa2, 0xfc, 0xe4, 0x48, 0x32, 0x99, 0x3a, 0x75, 0x39, 0xbc, 0x8c, 0x45, 0xa3, 0x3e, 0x71, 0x57, 0x72, 0xf8, 0x7b, 0x13, 0x56, 0xbb, 0x0d, 0x10, 0x45, 0x05, 0x91, 0x70, 0xc0, 0x87, 0x12, 0x18, 0x4c, 0x06, 0x94, 0x33, 0x86, 0x32, 0xc6, 0xe4, 0x62, 0xf4, 0x7c, 0xb1, 0x31, 0x0d, 0xa5, 0x2c, 0x28, 0x88, 0x37, 0xed, 0x65, 0x8e, 0x53, 0x5a, 0x47, 0x49, 0x8e, 0x57, 0x6a, 0xf1, 0x6e, 0xa8, 0x2a, 0x5a, 0x36, 0x34, 0x10, 0x75, 0x94, 0xe6, 0x7a, 0x64, 0x4e, 0xdf, 0xa6, 0xda, 0xd8, 0xe8, 0xd0, 0xa2, 0xec, 0x6f, 0x38, 0x47, 0x0d, 0xa9, 0xf4, 0x26, 0xf9, 0xce, 0xbc, 0x1c, 0x57, 0xd0, 0xad, 0xd2, 0x9b, + 0x15, 0x5b, 0x8a, 0x0a, 0xdc, 0x41, 0x37, 0xff, 0xe0, 0x74, 0x47, 0xd1, 0x58, 0x9f, 0x24, 0x3e, 0xa0, 0x1e, 0xa4, 0x17, 0x12, 0x99, 0x70, 0xcd, 0x28, 0x65, 0x28, 0xdf, 0x9e, 0x95, 0x18, 0x8c, 0x8c, 0x03, 0xd8, 0x01, 0x88, 0x09, 0xe2, 0xee, 0xed, 0x70, 0x51, 0x51, 0x18, 0x9c, 0xd6, 0x71, 0x17, 0x3f, 0xa7, 0xe4, 0x9c, 0x8e, 0x3c, 0xa7, 0x57, 0x2a, 0xf5, 0x6f, 0x2b, 0x6c, 0xdc, 0x61, 0x70, 0x7c, 0x16, 0x38, 0x7e, 0x98, 0xb3, 0x89, 0xbe, 0xbc, 0xdf, 0xc3, 0x67, 0xfd, 0x4d, 0x7c, 0x56, 0xa6, 0x80, 0x8f, 0x81, 0x03, 0x76, 0xfd, 0xf1, 0xe2, 0x40, 0xd0, 0x8f, 0x32, 0x71, 0xa9, 0xbf, 0x5d, 0xfc, 0x9c, 0xd3, 0x81, 0xd3, 0xa1, 0x58, 0x2c, 0xc4, 0xcf, 0xd5, 0x71, 0xe4, 0x2f, 0x66, 0xf1, 0x2b, 0xd0, 0x03, 0xde, 0x4e, 0x3d, 0x13, 0xdb, 0x39, 0x66, 0xc1, 0xb5, 0xf7, 0x2c, 0xa4, + 0xbe, 0x1a, 0xd5, 0x91, 0x97, 0x4a, 0x48, 0x86, 0xc6, 0x72, 0x68, 0x31, 0x4a, 0xb4, 0x22, 0xd8, 0x4b, 0xa7, 0x35, 0x4d, 0xd3, 0x6a, 0x5a, 0x8d, 0xa6, 0x90, 0x86, 0xcb, 0x94, 0x41, 0x6d, 0xac, 0x18, 0xad, 0x41, 0xfd, 0x04, 0xab, 0x19, 0xa4, 0xce, 0x5d, 0xac, 0xa2, 0xce, 0xf1, 0xab, 0xee, 0x44, 0x40, 0xb8, 0x77, 0x7e, 0x70, 0x8c, 0xfc, 0xfa, 0x18, 0xff, 0x3d, 0x30, 0x7a, 0x0c, 0xac, 0xe7, 0xaf, 0x43, 0x73, 0xe7, 0x2c, 0x31, 0x8b, 0x7a, 0x9e, 0x46, 0xd5, 0xdf, 0xf4, 0x08, 0x51, 0x0a, 0x3e, 0x46, 0xad, 0x62, 0x59, 0xa2, 0x69, 0x7a, 0xd6, 0x83, 0x1f, 0x8f, 0x33, 0x20, 0xd2, 0x29, 0x75, 0x96, 0x92, 0x5f, 0xfc, 0x1c, 0x94, 0xe6, 0xc5, 0x6c, 0x05, 0x46, 0x70, 0xca, 0xa6, 0x41, 0xf4, 0xd2, 0xd8, 0xe0, 0xbb, 0x5e, 0xd6, 0x28, 0x59, 0xb5, 0xfc, 0x45, 0xa5, 0x83, 0xbb, 0x1a, + 0xec, 0xe8, 0x04, 0x5b, 0x0f, 0x70, 0x4e, 0x05, 0x7c, 0xe7, 0x4b, 0xf0, 0x9d, 0xbf, 0x9e, 0x78, 0xa7, 0x5e, 0x25, 0x81, 0x93, 0xeb, 0x9b, 0xdf, 0x29, 0x12, 0x14, 0x25, 0x38, 0x0b, 0x14, 0x7d, 0x09, 0xbd, 0x13, 0xfd, 0xd5, 0xd8, 0xc0, 0x29, 0x63, 0x81, 0x2d, 0x96, 0xc7, 0x2f, 0xb0, 0x69, 0xe0, 0x3b, 0xdf, 0xe8, 0xe4, 0xaf, 0xba, 0x9a, 0x73, 0x28, 0x5f, 0x94, 0xab, 0x59, 0xa5, 0xe6, 0x9c, 0xc2, 0xc9, 0x1d, 0x40, 0x63, 0x74, 0x03, 0x88, 0x53, 0x9f, 0x41, 0xbe, 0xca, 0x10, 0x55, 0x62, 0xfd, 0x78, 0xfc, 0xb6, 0x11, 0xbc, 0xb6, 0xe7, 0xa1, 0x8d, 0x4f, 0x2f, 0xf2, 0x29, 0xb5, 0x59, 0x31, 0xd7, 0x45, 0xa9, 0x97, 0x93, 0x8f, 0xf7, 0x3e, 0x89, 0x0a, 0x01, 0xe0, 0x34, 0x4c, 0x0c, 0x82, 0xfb, 0xd9, 0x7d, 0x63, 0x75, 0xd4, 0x20, 0x88, 0x03, 0xe7, 0x31, 0x31, 0xe6, 0xe2, 0x9f, + 0xac, 0x99, 0x79, 0x88, 0x50, 0x42, 0xae, 0x99, 0x97, 0x88, 0x1a, 0xf4, 0x24, 0x86, 0x15, 0x07, 0x04, 0x43, 0x02, 0x06, 0x69, 0x54, 0xc9, 0x38, 0xa7, 0x09, 0xc4, 0x28, 0x5d, 0x28, 0xe8, 0xf7, 0xe1, 0xcd, 0x15, 0x10, 0x77, 0xe6, 0x70, 0x97, 0x0a, 0xc4, 0xac, 0x41, 0x23, 0xca, 0x1b, 0x4e, 0x16, 0xca, 0xc3, 0x79, 0x57, 0x46, 0x37, 0xa3, 0xaf, 0xde, 0xf4, 0xc0, 0xb2, 0xa5, 0x0f, 0x6d, 0xaf, 0xa7, 0x8e, 0x5d, 0x5c, 0x39, 0x63, 0xff, 0x73, 0x5b, 0x6e, 0xf9, 0x68, 0xfe, 0xf2, 0xbc, 0xed, 0xb7, 0x3d, 0xbb, 0xee, 0x0c, 0xff, 0xb7, 0x17, 0x97, 0x5f, 0xf1, 0x02, 0x50, 0xdd, 0xfd, 0x08, 0x90, 0x9f, 0x1d, 0xa0, 0x2f, 0x2c, 0xa3, 0xd6, 0x0d, 0x9c, 0xda, 0x54, 0xd7, 0xb0, 0xfd, 0x81, 0x41, 0xcd, 0x38, 0xb1, 0xe0, 0xcc, 0xfe, 0x59, 0x1b, 0xb7, 0xb7, 0x35, 0xac, 0x6a, 0x0d, 0xad, + 0x79, 0x11, 0x28, 0x6e, 0x3f, 0x01, 0x32, 0x7e, 0xbe, 0x6e, 0xe3, 0xcf, 0xf9, 0xaf, 0x6e, 0x44, 0x6d, 0xaf, 0x1a, 0xff, 0x84, 0x76, 0x31, 0x67, 0x60, 0xdb, 0x2b, 0x91, 0xcf, 0xb0, 0xb2, 0x2c, 0x37, 0xc7, 0xcd, 0xa0, 0x50, 0x8b, 0x66, 0x3a, 0x09, 0xb0, 0x34, 0xa9, 0xe1, 0x80, 0x88, 0x15, 0x66, 0x85, 0x4c, 0x06, 0x94, 0x22, 0x83, 0xa1, 0xb6, 0xd0, 0x02, 0x46, 0x66, 0xde, 0x00, 0xce, 0x60, 0x64, 0x52, 0xd6, 0x35, 0x56, 0x97, 0x8a, 0xbe, 0x32, 0x0a, 0x88, 0x84, 0x95, 0x28, 0xb9, 0x91, 0xd2, 0x94, 0x6d, 0x18, 0x6e, 0xd3, 0x8e, 0xfe, 0x60, 0x57, 0x43, 0xc9, 0xc5, 0x8b, 0x59, 0x9d, 0x41, 0x65, 0xf1, 0xac, 0x65, 0xdb, 0x0f, 0xb7, 0x56, 0xf5, 0x55, 0xd8, 0x17, 0x3e, 0xfc, 0xc9, 0x31, 0xa0, 0xf5, 0xc5, 0x3c, 0xbe, 0x86, 0xea, 0x62, 0xc3, 0x5d, 0xe6, 0xee, 0x0d, 0x87, 0xdb, + 0x6b, 0x57, 0xb4, 0x84, 0x5c, 0xd5, 0x6e, 0xc6, 0x38, 0xff, 0xfb, 0x6f, 0x6c, 0x0d, 0x0f, 0xaf, 0xdf, 0x55, 0x57, 0x5d, 0x3f, 0xd6, 0x25, 0xd5, 0x2a, 0x46, 0x7f, 0xfe, 0xe8, 0xb1, 0x15, 0xe5, 0x91, 0x99, 0x83, 0x25, 0x83, 0x8f, 0xdd, 0xb2, 0xbb, 0x40, 0x61, 0x54, 0xac, 0x3e, 0xda, 0xe3, 0xf3, 0x54, 0x75, 0x17, 0x85, 0x5a, 0xf6, 0x2e, 0x29, 0x89, 0x6f, 0xfd, 0x8f, 0xeb, 0x75, 0x52, 0xb5, 0x14, 0xc9, 0xb5, 0x51, 0xe2, 0x4b, 0xa6, 0x8b, 0xba, 0x0f, 0xdb, 0x69, 0x3a, 0x88, 0xb6, 0x44, 0x73, 0x4b, 0xb1, 0x59, 0x4d, 0xa3, 0x68, 0x12, 0x89, 0x1c, 0xee, 0x7a, 0x84, 0x6a, 0x75, 0x32, 0x52, 0x4a, 0x21, 0xcb, 0x77, 0x37, 0xfe, 0x42, 0x30, 0x7d, 0x2c, 0x60, 0x08, 0xa6, 0x23, 0x1c, 0x42, 0x1e, 0xbf, 0x19, 0x0d, 0x95, 0xe5, 0x85, 0xf9, 0xa1, 0x8e, 0x70, 0x47, 0xba, 0xdf, 0x4f, + 0x91, 0x96, 0x22, 0x84, 0x19, 0x99, 0x83, 0x72, 0xa3, 0xfe, 0x4e, 0xa4, 0x75, 0xa6, 0x03, 0x3e, 0x07, 0xd3, 0x71, 0x19, 0x51, 0xfe, 0x34, 0x45, 0xeb, 0x10, 0xde, 0x9b, 0x35, 0xa7, 0xc2, 0xe3, 0x2d, 0x8f, 0xe5, 0x99, 0x4c, 0x79, 0xb1, 0xf2, 0xb1, 0x9f, 0xa4, 0xbe, 0x7a, 0x3d, 0x95, 0x39, 0xf0, 0x64, 0x25, 0x79, 0x63, 0xf5, 0xfa, 0x2e, 0xa8, 0xfa, 0xad, 0xaf, 0x2e, 0xee, 0x6b, 0x4b, 0xd8, 0xec, 0x89, 0xd6, 0xfe, 0xe2, 0xea, 0x75, 0xdd, 0x85, 0x85, 0xdd, 0xeb, 0xaa, 0x8b, 0xfb, 0x5b, 0x13, 0x76, 0x5b, 0xa2, 0xad, 0x0f, 0xdc, 0x62, 0xc8, 0xa9, 0xcf, 0xcd, 0x69, 0xc8, 0x31, 0xd8, 0x0b, 0xea, 0xfc, 0x81, 0xda, 0x02, 0xbb, 0xbd, 0xa0, 0x36, 0xe0, 0xaf, 0x2b, 0xb0, 0x1b, 0xe0, 0xc1, 0xdc, 0xfa, 0x1c, 0x03, 0xa9, 0x2a, 0x98, 0xbf, 0xab, 0xad, 0x6d, 0xd7, 0xfc, 0x02, 0x77, + 0x49, 0x4b, 0x38, 0xdc, 0x52, 0xe2, 0x9e, 0xfa, 0x5b, 0xd0, 0x11, 0xc7, 0x3f, 0xa5, 0x0b, 0x99, 0x87, 0x09, 0x8e, 0xa8, 0x22, 0x36, 0x9f, 0xcd, 0x61, 0xd2, 0x6a, 0xa3, 0x04, 0x52, 0x01, 0x0a, 0x62, 0x91, 0x54, 0x04, 0xdd, 0x87, 0x72, 0x60, 0x57, 0x08, 0xe0, 0xa8, 0xe2, 0x0c, 0xb1, 0x7e, 0xa7, 0x0b, 0xd1, 0xb6, 0x28, 0xa1, 0x07, 0x04, 0xaa, 0x60, 0x9b, 0x9d, 0xe5, 0x71, 0x61, 0xe8, 0x2e, 0x0e, 0x70, 0x49, 0x5f, 0x6a, 0x8a, 0x88, 0x06, 0x2a, 0x69, 0x7d, 0x20, 0x27, 0xc7, 0xf9, 0xc5, 0x8b, 0xd2, 0x8b, 0x18, 0x80, 0x2f, 0xab, 0xd7, 0x2e, 0x6c, 0xd0, 0xc9, 0xfd, 0x15, 0x73, 0x4a, 0x3b, 0x77, 0xce, 0x8d, 0x6c, 0x6e, 0x08, 0xb6, 0x74, 0xf6, 0xc4, 0xfa, 0x6e, 0x5e, 0x51, 0xd6, 0x7c, 0xec, 0xf7, 0xc7, 0x7b, 0x8e, 0x2e, 0x2f, 0x6f, 0x3b, 0xf4, 0xec, 0xea, 0x91, 0xef, 0xe5, + 0x91, 0xbe, 0xda, 0x45, 0xe5, 0xe1, 0xc6, 0xd2, 0x2c, 0x95, 0xc2, 0xa0, 0x38, 0x5c, 0xb4, 0xf2, 0xbe, 0xb5, 0x59, 0x1d, 0x95, 0xfe, 0xf2, 0xd1, 0x1b, 0xe6, 0x5d, 0x79, 0x0d, 0x7d, 0xc6, 0x96, 0x65, 0x57, 0x37, 0x5f, 0xf7, 0xca, 0xee, 0x57, 0xf9, 0x47, 0x3e, 0x3b, 0x50, 0xb6, 0xf2, 0xd6, 0x81, 0x0d, 0xbf, 0x38, 0x3e, 0x6f, 0xce, 0x2c, 0x57, 0x85, 0x71, 0xe0, 0xa6, 0x25, 0x05, 0x3a, 0xb3, 0x2e, 0x43, 0x87, 0xf9, 0x3e, 0xcd, 0xdf, 0x4b, 0x7d, 0xc4, 0x2e, 0x82, 0x33, 0x0a, 0xd7, 0x11, 0x00, 0x84, 0xe8, 0x43, 0x41, 0x99, 0x3d, 0x48, 0xc6, 0x8a, 0xe0, 0xfc, 0xcb, 0xc8, 0xd6, 0x60, 0x38, 0x28, 0x98, 0xe4, 0x92, 0xd1, 0xfb, 0x54, 0x12, 0xaa, 0x51, 0xc8, 0x8f, 0x17, 0x0a, 0x7e, 0x81, 0xdf, 0x65, 0xd4, 0x0e, 0xed, 0x6d, 0xf3, 0xb6, 0xba, 0x14, 0xa4, 0x4c, 0xeb, 0x32, 0x97, + 0xcf, 0x2b, 0xf3, 0x4a, 0xf6, 0x2a, 0x6b, 0x87, 0x85, 0x63, 0x14, 0x3a, 0x56, 0x36, 0xaf, 0xdc, 0x23, 0xa5, 0xdf, 0x59, 0x7a, 0x74, 0x20, 0x9a, 0xc1, 0xcd, 0x51, 0xaa, 0x25, 0x94, 0xa5, 0xa4, 0xb7, 0xfa, 0x42, 0xeb, 0x94, 0x03, 0xb8, 0x7d, 0x61, 0xc8, 0x75, 0xde, 0x62, 0x1f, 0x81, 0x9a, 0x5c, 0x38, 0x11, 0xc8, 0xc4, 0x95, 0x7f, 0xc4, 0x12, 0x24, 0x49, 0xe0, 0xd8, 0xe4, 0x46, 0xcf, 0x48, 0x18, 0xbc, 0x6e, 0x6c, 0x97, 0x93, 0xe1, 0xb8, 0x36, 0x04, 0x58, 0x30, 0x11, 0xb5, 0x8a, 0xab, 0x54, 0xbe, 0x35, 0xf6, 0xf6, 0xbd, 0xe7, 0xef, 0x74, 0x44, 0xc1, 0x1c, 0x93, 0x93, 0x39, 0xa4, 0x50, 0x33, 0x4c, 0x86, 0x14, 0xdc, 0xcb, 0x38, 0x75, 0xec, 0x23, 0x5f, 0xcd, 0x26, 0x6f, 0xf4, 0x45, 0xab, 0x8d, 0x1c, 0xbf, 0xdb, 0xdb, 0xe4, 0xb3, 0x97, 0xb9, 0x98, 0x00, 0x67, 0x80, 0x6f, + 0x68, 0xe4, 0x1f, 0x66, 0x42, 0xb8, 0x46, 0xa0, 0x33, 0x61, 0xc3, 0x75, 0x3e, 0x44, 0x57, 0x06, 0xca, 0x25, 0x5b, 0x86, 0x91, 0xc5, 0xa7, 0x66, 0xce, 0x7b, 0x63, 0x6e, 0x26, 0x74, 0xf6, 0x65, 0xde, 0xf1, 0xf2, 0x59, 0xfa, 0x4f, 0xf4, 0xf9, 0xaf, 0xce, 0xb2, 0xcd, 0xe8, 0xef, 0xfd, 0xb8, 0x3f, 0x33, 0xf9, 0x87, 0xe9, 0x0b, 0xd8, 0xdf, 0x10, 0x49, 0x84, 0x94, 0xb0, 0x23, 0x80, 0x90, 0x62, 0xf4, 0x49, 0x42, 0x84, 0x2c, 0xc7, 0x99, 0x52, 0x18, 0x49, 0x12, 0xd2, 0xdc, 0xc2, 0x69, 0x45, 0xd8, 0x17, 0x28, 0x3d, 0xa0, 0x0c, 0x41, 0x02, 0x44, 0x06, 0x90, 0xec, 0x70, 0xd3, 0x17, 0x2e, 0xee, 0x22, 0xa3, 0x63, 0xbf, 0xa1, 0xf6, 0x5e, 0x68, 0x97, 0x2b, 0x50, 0x59, 0x67, 0x85, 0x9c, 0xfe, 0x21, 0x7d, 0x7e, 0xac, 0xa5, 0x74, 0xac, 0xe5, 0xfe, 0x46, 0xb5, 0x4b, 0x45, 0x7e, 0x49, + 0x7e, 0xa6, 0x72, 0xab, 0x1a, 0xf1, 0x7a, 0x18, 0x1a, 0xbf, 0x81, 0xf6, 0x31, 0xff, 0x26, 0x54, 0x84, 0x0e, 0xe5, 0x1c, 0x69, 0x14, 0x12, 0x1a, 0x73, 0x0e, 0x24, 0xa4, 0x50, 0x39, 0x97, 0x51, 0x9d, 0x16, 0x89, 0x2a, 0xc8, 0x08, 0x50, 0x7a, 0x3e, 0x2b, 0xa1, 0x44, 0xba, 0x41, 0x5a, 0xca, 0x40, 0x21, 0xb5, 0xec, 0x2b, 0xb5, 0x4e, 0x9a, 0x61, 0xff, 0xfc, 0x53, 0x4e, 0x2f, 0x95, 0x1a, 0x39, 0xf0, 0x28, 0x3f, 0x74, 0x90, 0x1f, 0x02, 0x27, 0x0e, 0x92, 0xe7, 0x00, 0xef, 0x4a, 0x78, 0x6a, 0xab, 0x78, 0x82, 0x57, 0xfa, 0x1a, 0xbd, 0xde, 0x46, 0x2f, 0x38, 0x7f, 0xf1, 0x97, 0x54, 0x01, 0x55, 0x20, 0xe0, 0x18, 0x11, 0xb5, 0x92, 0x7e, 0xfa, 0x1c, 0x61, 0x46, 0xf3, 0x8b, 0x93, 0xe2, 0x7a, 0x57, 0x70, 0x96, 0x35, 0x99, 0x01, 0x98, 0x31, 0x15, 0xd6, 0x28, 0xbf, 0x14, 0x6f, 0x0d, + 0x00, 0x42, 0x3c, 0x03, 0x08, 0xf5, 0xa3, 0x10, 0x76, 0x19, 0xce, 0x2d, 0x84, 0xf3, 0x85, 0x10, 0xbf, 0x46, 0xc9, 0xaa, 0x3a, 0xbd, 0x54, 0xc7, 0x7f, 0x5e, 0x18, 0x8a, 0x6f, 0xae, 0xa6, 0x3f, 0xbd, 0xa0, 0xaa, 0xde, 0x1c, 0xd7, 0x45, 0x2d, 0x40, 0xa9, 0x93, 0xea, 0xeb, 0xc8, 0xab, 0xc8, 0xaa, 0x46, 0x43, 0x44, 0xc7, 0x7f, 0x51, 0xe8, 0x90, 0x48, 0x7f, 0x28, 0x95, 0x70, 0x41, 0x33, 0x7f, 0x5e, 0x17, 0x31, 0x34, 0xa2, 0x76, 0x6c, 0x20, 0xea, 0x25, 0x23, 0xf4, 0x8b, 0x84, 0x49, 0x6c, 0x07, 0xea, 0xbb, 0x09, 0x80, 0x26, 0x80, 0x61, 0xea, 0x92, 0x78, 0x3b, 0x7d, 0x18, 0x1a, 0x57, 0x68, 0x07, 0xdc, 0x7f, 0x20, 0x14, 0x33, 0xec, 0x72, 0x81, 0xd2, 0x12, 0x4e, 0x25, 0x5c, 0xd0, 0xbb, 0x1a, 0x48, 0x46, 0xc6, 0xce, 0xa1, 0x76, 0x00, 0x79, 0x7e, 0x38, 0xbe, 0xa9, 0xfa, 0x82, + 0x8a, 0xfe, 0x14, 0xb6, 0x23, 0x5c, 0xc0, 0xff, 0x1b, 0x37, 0x63, 0xef, 0xd8, 0x39, 0xd4, 0x0c, 0x20, 0xcb, 0x77, 0xa1, 0x66, 0x48, 0xdd, 0xf9, 0x40, 0x21, 0xb4, 0x02, 0x3d, 0xfc, 0x91, 0x71, 0x07, 0x13, 0x65, 0x35, 0x90, 0x1e, 0x9a, 0x84, 0xda, 0x6c, 0xca, 0x94, 0xd0, 0x48, 0x67, 0x20, 0x46, 0xad, 0x00, 0xe3, 0xa1, 0xb0, 0xf0, 0x1d, 0xa8, 0xc3, 0xfa, 0xf4, 0x9a, 0x21, 0xb9, 0x00, 0x30, 0xd1, 0x06, 0xd4, 0xb5, 0xcf, 0x51, 0xd7, 0x1e, 0x93, 0x4a, 0x32, 0x43, 0x66, 0xf8, 0xb6, 0x88, 0xbe, 0x91, 0xac, 0xa1, 0x7e, 0x53, 0x8f, 0xdb, 0x02, 0x69, 0xb2, 0xa5, 0xfa, 0x42, 0x26, 0xfd, 0x71, 0xf5, 0xd6, 0xb8, 0x3e, 0x6a, 0x81, 0x3d, 0x97, 0xea, 0xeb, 0xc9, 0x04, 0x7a, 0xe7, 0x0f, 0xf8, 0x07, 0x98, 0x1c, 0x38, 0xe7, 0xcc, 0x84, 0x2e, 0xc1, 0x69, 0x64, 0x24, 0xd4, 0x97, 0x51, + 0x12, 0xd7, 0x20, 0xd1, 0x6a, 0x47, 0x6f, 0x85, 0x3b, 0x2d, 0x64, 0x05, 0x05, 0x06, 0xa3, 0x08, 0xe6, 0x9d, 0xac, 0x4a, 0x6d, 0x64, 0x72, 0xc6, 0x9e, 0x1b, 0x3b, 0x87, 0xde, 0x0c, 0xfb, 0xea, 0x4a, 0xbe, 0x19, 0x92, 0x3b, 0x62, 0xf8, 0xdb, 0xd8, 0x73, 0x64, 0x75, 0x3d, 0x1e, 0x8d, 0x02, 0xf4, 0x66, 0xfa, 0x5f, 0x17, 0xb8, 0xea, 0xad, 0xc5, 0x7a, 0x71, 0x34, 0x70, 0x5f, 0xa3, 0xe3, 0xff, 0x66, 0xba, 0x18, 0x3b, 0xa4, 0x79, 0x56, 0x22, 0xa8, 0x54, 0x90, 0xe8, 0xad, 0x97, 0x0b, 0x82, 0x31, 0x11, 0xa6, 0x76, 0xb7, 0x10, 0x34, 0x10, 0xf4, 0x04, 0x62, 0x5c, 0x51, 0x31, 0x9c, 0x03, 0x41, 0x31, 0x16, 0x03, 0x4e, 0x82, 0x58, 0x90, 0xe9, 0x5a, 0xec, 0x30, 0xb3, 0xb5, 0xbd, 0x17, 0xf7, 0xec, 0xa4, 0xae, 0x8c, 0xf6, 0x45, 0x2c, 0x95, 0x3e, 0xfe, 0x2a, 0x9b, 0x8f, 0x64, 0xec, + 0x17, 0x7f, 0x43, 0xfa, 0xad, 0x60, 0x4b, 0x7d, 0xac, 0xb7, 0xf7, 0xe2, 0xbe, 0x9d, 0xd4, 0xae, 0x68, 0x87, 0x87, 0xa6, 0x4d, 0x4e, 0xa4, 0x69, 0x12, 0x07, 0x60, 0x1b, 0x7e, 0x80, 0xdb, 0x00, 0x77, 0x56, 0x78, 0xb0, 0x31, 0x28, 0x05, 0x7a, 0xf7, 0xbc, 0x09, 0xac, 0xa2, 0x3a, 0x2e, 0x8c, 0xf4, 0x22, 0xbf, 0x04, 0x12, 0xdf, 0xa0, 0x87, 0x14, 0xf0, 0x4a, 0x38, 0xb7, 0x04, 0xf9, 0x1c, 0x85, 0xe9, 0xc7, 0xfc, 0x60, 0xe7, 0xc5, 0x3d, 0xbd, 0xb5, 0xac, 0xd9, 0xb1, 0x98, 0x82, 0x8c, 0xd7, 0xc6, 0x5f, 0xe5, 0xab, 0xb4, 0x44, 0xfa, 0xa2, 0x6b, 0x77, 0x52, 0x3b, 0x7b, 0x7b, 0x63, 0xf5, 0xfc, 0x41, 0xab, 0x9f, 0xa4, 0xa2, 0x8b, 0x9d, 0x26, 0x9a, 0xf6, 0x74, 0x44, 0x85, 0xdc, 0x0d, 0xd8, 0xf7, 0x2c, 0xf8, 0x5e, 0x5f, 0xc2, 0xad, 0x56, 0x91, 0x64, 0x13, 0xea, 0x36, 0x89, 0x90, + 0xdb, 0xd2, 0x7c, 0x09, 0x26, 0xc2, 0x58, 0x27, 0x86, 0x4a, 0x30, 0xc5, 0x45, 0x71, 0x23, 0x9a, 0x65, 0xa8, 0x36, 0x50, 0x31, 0x1c, 0x0c, 0x44, 0x84, 0x78, 0x31, 0xd3, 0x75, 0xf1, 0x57, 0xf0, 0x85, 0x60, 0x87, 0xf0, 0x42, 0xdc, 0x79, 0xdc, 0x10, 0x2a, 0x7a, 0xf1, 0x0d, 0xb0, 0xc4, 0x61, 0x06, 0x34, 0xe5, 0xee, 0x88, 0x52, 0xbb, 0x76, 0x5d, 0xdc, 0xdb, 0xdb, 0x5b, 0xd4, 0x08, 0x36, 0xdb, 0x7c, 0x00, 0xf7, 0x7b, 0x1c, 0xf6, 0x3b, 0x4c, 0x58, 0x50, 0xec, 0x91, 0x2a, 0x83, 0xc4, 0x9a, 0x29, 0x06, 0x8e, 0x23, 0xe7, 0x23, 0xca, 0x0f, 0x91, 0x42, 0x91, 0x1a, 0x8b, 0x51, 0x0b, 0xc9, 0xae, 0x8f, 0x14, 0x72, 0xc6, 0x02, 0xdc, 0x73, 0x3c, 0xf6, 0x46, 0x80, 0x8d, 0x78, 0xc5, 0xb0, 0x3d, 0x7a, 0xe3, 0x01, 0x48, 0x77, 0xf8, 0x92, 0xf6, 0x28, 0x75, 0x25, 0xa4, 0x42, 0x74, 0x7e, + 0x36, 0xa4, 0x3c, 0xd8, 0x01, 0x29, 0x4f, 0x45, 0x49, 0xbf, 0x8d, 0x3f, 0xe4, 0xad, 0xb0, 0x44, 0xe6, 0x47, 0xa9, 0x9d, 0xbb, 0x2e, 0xee, 0x43, 0x94, 0xa7, 0x80, 0xd9, 0xb1, 0x24, 0xb5, 0xf6, 0xd1, 0x9a, 0x0b, 0x21, 0x5e, 0xe7, 0xd6, 0x49, 0x51, 0x34, 0x5c, 0x73, 0x48, 0x4f, 0x82, 0x26, 0x14, 0x46, 0x77, 0xc9, 0xb2, 0x73, 0xd9, 0x4b, 0x11, 0x28, 0xd0, 0xa4, 0x75, 0x67, 0x10, 0x56, 0xbe, 0x4c, 0xe0, 0x09, 0xb0, 0x51, 0x97, 0x5f, 0x87, 0x17, 0x23, 0xee, 0xa0, 0xc0, 0x09, 0xc6, 0x9e, 0x4f, 0x32, 0x0a, 0x9d, 0x97, 0xa3, 0xde, 0xbc, 0xec, 0xe2, 0xb4, 0x06, 0x05, 0xfe, 0x40, 0xd6, 0x26, 0x39, 0x87, 0xce, 0xa5, 0x9a, 0xb4, 0x60, 0xe1, 0x7a, 0x85, 0x6b, 0x27, 0x17, 0xae, 0xd7, 0x2c, 0xb8, 0x2f, 0x32, 0x20, 0x9c, 0x43, 0x71, 0xb1, 0xc0, 0x49, 0x91, 0xbe, 0x40, 0x65, 0xe9, + 0x4b, 0x57, 0x58, 0x3d, 0x4e, 0x40, 0xfe, 0x33, 0xb9, 0x78, 0xd0, 0x73, 0x31, 0x3f, 0xfa, 0x4c, 0x1b, 0x31, 0x34, 0x90, 0x35, 0x64, 0x95, 0xb0, 0x9c, 0x0b, 0x9c, 0x12, 0xa9, 0xca, 0xa5, 0xc3, 0x2b, 0xaa, 0x81, 0x7f, 0x20, 0xb9, 0xa2, 0x74, 0x1e, 0x8e, 0x2e, 0xaf, 0xde, 0x02, 0xd7, 0xb1, 0x19, 0xb7, 0xbb, 0x9e, 0xac, 0x19, 0xfb, 0x59, 0x7d, 0xb2, 0xab, 0x9b, 0xab, 0x2f, 0xbc, 0xc8, 0x79, 0x75, 0xb8, 0xaf, 0xf5, 0xa8, 0x8d, 0xaf, 0xd1, 0x03, 0x64, 0x35, 0xf3, 0x22, 0x1c, 0xcd, 0xf2, 0x27, 0x95, 0x69, 0x96, 0x64, 0x0e, 0x4f, 0xf3, 0x91, 0x24, 0x98, 0xea, 0x30, 0x29, 0x80, 0x31, 0x91, 0x60, 0x7d, 0xda, 0xa1, 0xde, 0x27, 0x03, 0x06, 0x0f, 0x9a, 0xfc, 0x5a, 0x11, 0xa6, 0x98, 0x55, 0x83, 0x94, 0x1b, 0x44, 0xd0, 0x49, 0x5e, 0x73, 0x0e, 0x96, 0x2e, 0xec, 0xad, 0x2b, 0xd4, + 0x2c, 0x8c, 0x05, 0x5d, 0x01, 0x9d, 0xc3, 0xa4, 0x91, 0xca, 0x14, 0x0c, 0x73, 0xa3, 0xd1, 0xd6, 0x5c, 0x5c, 0xd0, 0xac, 0x9b, 0x73, 0x48, 0x12, 0x34, 0x1a, 0xdd, 0x32, 0xb9, 0x0c, 0xee, 0x99, 0x51, 0x7b, 0x5e, 0x62, 0x3e, 0x21, 0x9b, 0x24, 0x3e, 0xd8, 0x9e, 0x86, 0x54, 0x7b, 0x94, 0x42, 0x49, 0x20, 0xdc, 0x9e, 0x29, 0x81, 0x58, 0x68, 0xbb, 0x82, 0x1b, 0x35, 0xe5, 0xf8, 0x77, 0x68, 0xd9, 0x4b, 0x8e, 0x65, 0xa5, 0x0b, 0x07, 0x67, 0xc6, 0xd5, 0x8b, 0xe2, 0xd1, 0x70, 0x40, 0xe7, 0xb2, 0xe8, 0x24, 0x32, 0x05, 0xcb, 0x7e, 0x6c, 0xb2, 0x35, 0x16, 0x17, 0x94, 0xab, 0xca, 0x57, 0x48, 0xec, 0x3a, 0x83, 0x55, 0xaa, 0x90, 0xc2, 0x96, 0x91, 0xc4, 0x5c, 0xfa, 0x46, 0x9a, 0x64, 0x4b, 0xe1, 0xce, 0xd4, 0x8c, 0x22, 0x6c, 0xf4, 0x62, 0xe5, 0x26, 0xac, 0x4e, 0xa4, 0x83, 0xd5, 0x64, + 0x66, 0x66, 0x9a, 0x33, 0x4d, 0x5c, 0xd8, 0xc5, 0x08, 0x90, 0x35, 0xb9, 0x70, 0x5c, 0x59, 0xc1, 0x3e, 0xa8, 0x2d, 0xf4, 0x0b, 0xee, 0x3e, 0xe4, 0x6b, 0x7b, 0x75, 0x9f, 0x49, 0xb3, 0x5d, 0x2a, 0x93, 0x49, 0x77, 0x48, 0x8c, 0xca, 0x83, 0x7f, 0x3c, 0x4b, 0x1a, 0x3f, 0x71, 0x55, 0x9a, 0x2c, 0xab, 0x32, 0x5d, 0x5e, 0xfa, 0x46, 0xf5, 0x5e, 0xa9, 0x52, 0x29, 0xdd, 0xcb, 0xca, 0x80, 0xe2, 0x8e, 0x77, 0xdc, 0x8c, 0xdd, 0x7d, 0xc8, 0x60, 0x82, 0xaf, 0x68, 0x65, 0xe5, 0x34, 0x29, 0xb9, 0x1b, 0xea, 0x13, 0x16, 0x62, 0xe6, 0x59, 0x0d, 0xdc, 0x59, 0x51, 0x88, 0x3c, 0x05, 0x18, 0xb0, 0x4e, 0x30, 0x61, 0xae, 0x41, 0xb1, 0xe1, 0x6b, 0xd1, 0xb6, 0x65, 0x14, 0xc3, 0x67, 0x21, 0xfc, 0x26, 0x02, 0x65, 0xb4, 0x51, 0xeb, 0xd0, 0x99, 0xd1, 0xde, 0x84, 0x9a, 0x10, 0x4a, 0xe2, 0x2a, 0xe1, + 0x63, 0xe4, 0x78, 0xf7, 0x22, 0x36, 0x8a, 0x2a, 0x44, 0x8a, 0x4f, 0x2e, 0x99, 0x6c, 0x2c, 0x4d, 0x0a, 0x2d, 0x92, 0xaa, 0x35, 0x66, 0x6e, 0xf4, 0x48, 0x86, 0x4f, 0x73, 0x85, 0x52, 0x79, 0x85, 0xc6, 0x97, 0x71, 0xf0, 0x5f, 0x8f, 0x4a, 0xee, 0x7e, 0xc7, 0xc3, 0xc2, 0x66, 0x29, 0x38, 0x25, 0xcb, 0x2f, 0x93, 0x19, 0xaf, 0xd2, 0x68, 0xae, 0x32, 0xca, 0x80, 0x80, 0x17, 0x3c, 0xee, 0xa0, 0x6f, 0x24, 0x5f, 0x85, 0xb4, 0x52, 0x10, 0x56, 0xc4, 0x39, 0x35, 0x0a, 0xdc, 0x50, 0xb4, 0x0b, 0x41, 0x8d, 0xa0, 0x70, 0xc6, 0xe1, 0x20, 0xd5, 0xca, 0x85, 0x83, 0x3a, 0x6c, 0x93, 0x42, 0x8b, 0x00, 0xa9, 0xb7, 0x4e, 0xc0, 0x4c, 0x21, 0xd8, 0x3e, 0x63, 0xa5, 0xeb, 0x93, 0x9f, 0x72, 0x66, 0x8d, 0x5a, 0xba, 0x9a, 0xcc, 0x99, 0x42, 0x32, 0x66, 0xb1, 0x9d, 0x71, 0xbf, 0xc3, 0x2a, 0x39, 0xc5, + 0xa1, 0x4f, 0xd2, 0x28, 0x26, 0xb6, 0x81, 0x95, 0x93, 0xef, 0x4a, 0x4e, 0x41, 0x9d, 0x05, 0x73, 0x51, 0x93, 0x30, 0x5e, 0xc9, 0x4c, 0x54, 0x84, 0xdd, 0xb8, 0x16, 0xb6, 0x62, 0x94, 0x6a, 0xf5, 0x73, 0x61, 0x41, 0x4d, 0x9a, 0xd4, 0x79, 0x46, 0x68, 0x93, 0x01, 0x2e, 0xc4, 0x77, 0x27, 0x77, 0x9d, 0xbf, 0x05, 0xb7, 0xc9, 0xe3, 0xce, 0x5c, 0xcd, 0xca, 0x27, 0x7a, 0x3e, 0xcb, 0xce, 0x7a, 0xde, 0x31, 0x19, 0x0e, 0x11, 0xc2, 0x5c, 0xa1, 0x36, 0xb0, 0x6b, 0xb0, 0x7f, 0x66, 0xee, 0x59, 0x07, 0x4b, 0xd2, 0x78, 0x1a, 0x2b, 0x44, 0x07, 0x0d, 0xc1, 0xd0, 0xcb, 0xc4, 0x06, 0xc0, 0xdd, 0x31, 0xc5, 0x0c, 0x0a, 0x33, 0xc7, 0x8a, 0x2b, 0x4d, 0x22, 0x03, 0xf4, 0x9a, 0x69, 0xce, 0xf6, 0x26, 0x64, 0x06, 0x93, 0x17, 0xce, 0x2b, 0x41, 0x5d, 0x9c, 0x4c, 0xa6, 0xe2, 0xa4, 0xcf, 0xe4, 0xd2, + 0x59, 0x7e, 0xc9, 0x44, 0xab, 0x75, 0xb6, 0xe5, 0xf9, 0xab, 0xa3, 0x96, 0xfa, 0x62, 0x55, 0x5b, 0xac, 0xc8, 0xeb, 0xce, 0xb4, 0xe8, 0x39, 0x89, 0x5c, 0xc1, 0x4c, 0x9a, 0x75, 0x77, 0x1a, 0xed, 0x1a, 0xb8, 0x07, 0xcd, 0x9f, 0xa9, 0x89, 0x2c, 0x93, 0x04, 0xcd, 0x7a, 0xbc, 0x40, 0x65, 0x84, 0x30, 0x07, 0xa9, 0x0d, 0x92, 0x8f, 0x09, 0x1b, 0xdc, 0xe7, 0x2f, 0x3d, 0xeb, 0x55, 0xc1, 0xdd, 0x7d, 0xb2, 0x6f, 0x5e, 0xb4, 0xd7, 0x87, 0xbb, 0xfe, 0x35, 0x04, 0x32, 0x04, 0x2f, 0x85, 0x6b, 0x83, 0x5a, 0x7b, 0xe9, 0x7a, 0xf5, 0x4d, 0xba, 0x8a, 0x5a, 0x35, 0xed, 0x65, 0xb0, 0xb7, 0x5c, 0xd8, 0x6c, 0x30, 0x79, 0x70, 0x6f, 0x2f, 0xdf, 0x3b, 0xed, 0xa4, 0x41, 0x23, 0xa3, 0x97, 0xe9, 0xdb, 0xc1, 0x29, 0xb3, 0xf7, 0x19, 0x93, 0x6d, 0x72, 0xf7, 0xf0, 0x2a, 0xe7, 0xf7, 0x4e, 0x9a, 0xc8, + 0x78, 0x1c, 0xc9, 0x77, 0xd8, 0x97, 0x08, 0x27, 0x91, 0x8b, 0x32, 0x0d, 0x51, 0x83, 0xd1, 0x2e, 0x22, 0x00, 0x68, 0x96, 0x6a, 0x66, 0xe0, 0xbf, 0xf4, 0xfa, 0xd4, 0x4e, 0x02, 0x6e, 0x97, 0x59, 0x8a, 0x40, 0xe1, 0x4a, 0x83, 0x2c, 0x9c, 0xd9, 0x7e, 0x6f, 0xd8, 0x8b, 0xa3, 0xd3, 0x41, 0x0a, 0x2e, 0x0a, 0x6e, 0xe4, 0x92, 0x78, 0x51, 0xd8, 0x3f, 0xa8, 0x9d, 0x32, 0x88, 0xe4, 0x3b, 0x26, 0xb8, 0x4d, 0xcd, 0x69, 0x2c, 0x2b, 0xb4, 0x1a, 0xdd, 0x7a, 0xf9, 0x56, 0x53, 0x6e, 0x43, 0x34, 0xb7, 0xb1, 0xb4, 0xd0, 0x06, 0x7f, 0x29, 0x96, 0x4f, 0x19, 0x45, 0xf6, 0x85, 0xe8, 0xec, 0x44, 0xc0, 0x15, 0x74, 0x6b, 0xec, 0x5e, 0x0e, 0x7d, 0x75, 0x07, 0x5d, 0x1a, 0x9b, 0x57, 0x33, 0xf6, 0xcb, 0x49, 0x2b, 0x01, 0x8f, 0x17, 0xf9, 0x47, 0x69, 0x23, 0x5c, 0x07, 0x39, 0xc4, 0x15, 0x09, 0x95, + 0x1a, 0xb0, 0x8c, 0x13, 0xc1, 0x85, 0xa0, 0xd8, 0x09, 0x31, 0x4f, 0xce, 0x35, 0x79, 0x55, 0x30, 0x08, 0xca, 0x05, 0xfb, 0x3c, 0x71, 0x4e, 0xe8, 0x28, 0xda, 0xcd, 0xfa, 0x27, 0x5f, 0x82, 0xc0, 0x46, 0xba, 0x53, 0x17, 0xb2, 0x2c, 0xda, 0xcd, 0xb2, 0x54, 0x07, 0x72, 0x8c, 0x86, 0xbd, 0x68, 0x41, 0xe1, 0x9e, 0x4f, 0x1a, 0x1b, 0xed, 0x65, 0xe9, 0x30, 0x75, 0x81, 0x7d, 0x6e, 0xce, 0x6b, 0xcc, 0xc9, 0x69, 0xaa, 0x8c, 0x5b, 0x8d, 0x1e, 0x83, 0x72, 0xab, 0x39, 0xbf, 0x31, 0x9a, 0xdb, 0x54, 0x19, 0xb3, 0x19, 0x3d, 0x46, 0x65, 0xfa, 0x8a, 0x03, 0x81, 0xe9, 0x49, 0x20, 0xf8, 0x48, 0x67, 0xd0, 0x87, 0xc9, 0x3f, 0xb2, 0xa7, 0x08, 0x3d, 0x1c, 0x3d, 0x38, 0x76, 0x12, 0x38, 0x4d, 0x99, 0x75, 0x70, 0x1f, 0x0e, 0x15, 0x58, 0x94, 0xfd, 0x8c, 0xfa, 0x4f, 0x60, 0xdc, 0xf8, 0x41, 0xd0, + 0x6a, 0x30, 0x00, 0xc2, 0xe0, 0x34, 0x38, 0xac, 0x66, 0x2e, 0x53, 0x29, 0x67, 0x19, 0x42, 0x0f, 0xf4, 0xd8, 0x2d, 0x8e, 0x76, 0xda, 0x85, 0xc9, 0x00, 0x66, 0x04, 0xfc, 0xa5, 0xc5, 0xb9, 0x4e, 0x68, 0xb9, 0x81, 0x1b, 0xfa, 0x76, 0xb6, 0x7b, 0xaf, 0xd1, 0xe7, 0xcc, 0x2c, 0x2c, 0x9c, 0x99, 0xa3, 0xbf, 0xc6, 0xdb, 0xbe, 0xf3, 0xf3, 0x8f, 0xd4, 0x36, 0xe3, 0xa6, 0x63, 0x2a, 0xb7, 0x97, 0x3e, 0x1c, 0x69, 0x59, 0x52, 0xe2, 0x8a, 0x07, 0xf5, 0xfa, 0x60, 0xdc, 0x55, 0xb2, 0xa4, 0x25, 0x42, 0x1a, 0x79, 0x9e, 0xcb, 0xf8, 0xd4, 0x64, 0xc0, 0xfe, 0x5b, 0x96, 0xa1, 0xca, 0xa5, 0x01, 0xc8, 0x1f, 0xed, 0x28, 0xd2, 0x9e, 0x45, 0x3c, 0x71, 0x1d, 0x83, 0x6a, 0xac, 0xad, 0x43, 0xa9, 0x8d, 0xeb, 0xb0, 0x13, 0xb7, 0x1b, 0x57, 0xd5, 0x96, 0x00, 0xc8, 0x3c, 0x3a, 0x20, 0xb3, 0x26, 0x20, + 0xcf, 0xd6, 0x69, 0x38, 0xa5, 0x3d, 0xc3, 0x8e, 0x18, 0xb7, 0x57, 0x8a, 0xb5, 0x1d, 0x4c, 0x5b, 0xc4, 0xb9, 0x85, 0x96, 0x62, 0x37, 0x15, 0x6a, 0x27, 0x55, 0xce, 0x3f, 0xc2, 0xd9, 0x8c, 0xab, 0x7f, 0x25, 0xe3, 0x74, 0x16, 0xee, 0xf5, 0xbe, 0xed, 0xad, 0xee, 0x5d, 0x0b, 0x47, 0x46, 0x16, 0xee, 0x72, 0xb7, 0x6e, 0x97, 0x06, 0xf8, 0x3f, 0xaa, 0x24, 0x80, 0x53, 0xaa, 0x24, 0xe4, 0xd8, 0xef, 0xc3, 0x4d, 0x03, 0x25, 0x35, 0xad, 0xad, 0x35, 0x25, 0x03, 0x4d, 0xe1, 0x24, 0xdd, 0xde, 0x49, 0xa7, 0x1b, 0x0d, 0x50, 0xd6, 0xb8, 0x80, 0x81, 0x32, 0x85, 0x6e, 0x46, 0x48, 0x37, 0x9b, 0xc5, 0xe0, 0x34, 0x3a, 0xbf, 0x8d, 0x6e, 0x14, 0xe2, 0xa4, 0x18, 0xbf, 0x6d, 0x1a, 0xba, 0xfd, 0x09, 0xce, 0x89, 0xa3, 0xab, 0x8d, 0x76, 0xf5, 0x47, 0xd3, 0xd1, 0xcd, 0x68, 0xfc, 0x34, 0x23, 0x13, + 0xd2, 0x06, 0xf2, 0xf4, 0xcf, 0x59, 0x86, 0xfc, 0x8d, 0xd4, 0x4f, 0xc8, 0xa0, 0x5c, 0xa9, 0x48, 0x94, 0x62, 0xa4, 0x62, 0x44, 0x37, 0x28, 0xdc, 0x90, 0xb3, 0xe9, 0x52, 0xba, 0x29, 0xe4, 0x80, 0x30, 0x42, 0xb2, 0x65, 0xaa, 0xe5, 0x56, 0x85, 0x95, 0xa5, 0x09, 0x19, 0x90, 0xa1, 0x06, 0x6a, 0xd1, 0xde, 0x1e, 0x35, 0x47, 0x06, 0xa6, 0x10, 0xee, 0x3f, 0x3c, 0x7e, 0xcd, 0xeb, 0xcb, 0x8c, 0x0e, 0x15, 0xff, 0x04, 0xb8, 0x6d, 0x32, 0xd9, 0xfc, 0x0a, 0x23, 0xff, 0x4f, 0xb9, 0x02, 0x38, 0xc7, 0xfe, 0x30, 0x89, 0x68, 0x49, 0x9a, 0xfd, 0x19, 0x4a, 0xe5, 0x3c, 0x62, 0x69, 0x42, 0xae, 0x46, 0x3a, 0x34, 0xdc, 0x27, 0x25, 0xbd, 0xbb, 0x0e, 0xc8, 0xd3, 0x19, 0x82, 0x5c, 0x86, 0x74, 0x56, 0x1a, 0x79, 0xce, 0x18, 0x82, 0x62, 0xa8, 0x45, 0x12, 0x54, 0x53, 0x67, 0x90, 0xc6, 0xe0, 0x6a, + 0xf0, 0x02, 0x9a, 0x64, 0xd6, 0x5c, 0xe6, 0x0a, 0x28, 0xb4, 0xad, 0x56, 0x6b, 0x9e, 0x35, 0xcf, 0xa7, 0xf3, 0x1b, 0xb2, 0xfc, 0x52, 0x81, 0x27, 0x4e, 0xb0, 0xc1, 0x5c, 0x90, 0xa6, 0xe4, 0xf8, 0x93, 0x2e, 0xd6, 0xd4, 0x00, 0x80, 0xfe, 0x58, 0x08, 0xab, 0x5e, 0x5a, 0x89, 0x5c, 0xce, 0xfa, 0x1c, 0x50, 0x21, 0x9b, 0x0f, 0x15, 0xb2, 0x45, 0x60, 0x27, 0x24, 0x7e, 0xdf, 0xa4, 0xe1, 0x60, 0x25, 0x58, 0x1d, 0xc3, 0xec, 0xb0, 0xc8, 0x64, 0x6b, 0x2e, 0x41, 0x2a, 0x9a, 0x03, 0x0d, 0x44, 0xfa, 0xc0, 0x88, 0x73, 0x18, 0x8e, 0x05, 0xd2, 0xd3, 0xb2, 0x89, 0x6d, 0x93, 0xfb, 0x8c, 0xd4, 0xb5, 0x10, 0x6a, 0x3b, 0xbc, 0x0c, 0x2f, 0x36, 0xd8, 0xf9, 0xa5, 0x13, 0x5d, 0xa3, 0x28, 0x06, 0xf2, 0x0e, 0x06, 0xf1, 0x0c, 0x86, 0x82, 0x02, 0x21, 0x3c, 0xe5, 0x52, 0x66, 0xd5, 0xe5, 0xae, 0x15, + 0xe8, 0x90, 0x6d, 0xcd, 0xf6, 0xfc, 0xdf, 0xe8, 0x80, 0x47, 0x17, 0xf4, 0xa5, 0xab, 0x79, 0xbe, 0x94, 0xf2, 0x07, 0xb6, 0xc0, 0xc1, 0xed, 0x4b, 0x0d, 0xb5, 0x64, 0x01, 0x9b, 0x52, 0xfb, 0x62, 0x46, 0x41, 0x15, 0x2c, 0xb3, 0xa1, 0x81, 0x4e, 0x0e, 0xfa, 0x04, 0x7f, 0x41, 0xb2, 0x41, 0x4f, 0xc4, 0x10, 0x7a, 0xa6, 0x0d, 0xd0, 0x92, 0x6c, 0xc0, 0xb0, 0x64, 0xf3, 0x24, 0xcf, 0x12, 0x40, 0x19, 0x33, 0xa4, 0x74, 0xea, 0xa2, 0x29, 0xc8, 0x43, 0xe9, 0xbc, 0x86, 0x98, 0x31, 0x36, 0xb1, 0x70, 0xe4, 0xd3, 0x33, 0x9c, 0xe4, 0x97, 0x4b, 0x78, 0xe7, 0x34, 0x0b, 0xc9, 0x89, 0x24, 0xc5, 0xe6, 0xe9, 0xa5, 0xc8, 0xa5, 0x4b, 0x0b, 0xbc, 0x81, 0xb8, 0xe5, 0xa5, 0x3c, 0x54, 0xe8, 0x5f, 0x2b, 0x1c, 0x5f, 0x24, 0x37, 0x50, 0x4e, 0xc4, 0xfe, 0x84, 0xdc, 0x04, 0xfb, 0x17, 0xc2, 0xfd, 0x13, + 0x64, 0x86, 0x1f, 0xf6, 0x86, 0x81, 0x52, 0x1c, 0x8e, 0x99, 0xd8, 0xdf, 0x64, 0x37, 0xe1, 0x70, 0xd1, 0x7d, 0xc8, 0x41, 0x36, 0x8a, 0x4a, 0x07, 0x46, 0x2e, 0xb9, 0x2e, 0x8d, 0x2e, 0x28, 0xb5, 0x11, 0xef, 0x26, 0xf0, 0x9d, 0xc8, 0x95, 0xa6, 0xd5, 0x00, 0x22, 0x27, 0x1b, 0x55, 0x73, 0xd5, 0xe4, 0x6b, 0xf3, 0x33, 0x94, 0x72, 0x29, 0x24, 0x0e, 0x07, 0xb8, 0x34, 0xe2, 0xa4, 0x96, 0xea, 0x37, 0x92, 0x26, 0x7d, 0xe5, 0x7a, 0x91, 0xe4, 0xd8, 0x3c, 0xbd, 0x54, 0x61, 0x99, 0xf4, 0xb5, 0x0c, 0xde, 0xb9, 0x0c, 0x49, 0x00, 0xb0, 0xf0, 0x47, 0x49, 0xd9, 0xf8, 0x3e, 0x11, 0xdb, 0x5f, 0x44, 0x16, 0x58, 0x43, 0x88, 0xd8, 0xfe, 0xcc, 0x94, 0x50, 0x43, 0x52, 0xa6, 0x73, 0x67, 0x9b, 0x50, 0xc9, 0x01, 0xad, 0x2b, 0x62, 0x32, 0x65, 0xbb, 0x75, 0x9f, 0x9b, 0xb2, 0xd1, 0x8f, 0x6c, 0x93, + 0x39, 0xdb, 0xad, 0xd5, 0xba, 0xb3, 0x11, 0x8d, 0x7b, 0xc7, 0x3f, 0x61, 0xaf, 0x64, 0xb3, 0xe0, 0x33, 0x4b, 0x91, 0x1c, 0xc8, 0xb5, 0x60, 0xcb, 0x12, 0x0b, 0x30, 0x50, 0x47, 0x2a, 0xac, 0x97, 0x01, 0x13, 0x30, 0x8b, 0xc8, 0xb7, 0xa9, 0x2b, 0xd5, 0x95, 0x44, 0x23, 0x5e, 0x77, 0x7a, 0x99, 0x87, 0xc9, 0x79, 0x37, 0x40, 0x40, 0xc7, 0x41, 0xff, 0x92, 0x29, 0xf8, 0x40, 0xc1, 0x8d, 0x22, 0x22, 0x96, 0xc6, 0x8a, 0xa0, 0xc8, 0x58, 0xfb, 0xd4, 0x9e, 0xc6, 0xc6, 0x3d, 0x4f, 0xad, 0x5d, 0xfb, 0xd4, 0xde, 0xc6, 0xc6, 0xbd, 0x4f, 0x51, 0xcf, 0x49, 0xa4, 0x07, 0xf7, 0x94, 0x56, 0x8c, 0x3c, 0xc3, 0x7f, 0x7d, 0xe7, 0x49, 0xfe, 0xab, 0x9f, 0x2c, 0x5b, 0xf6, 0x13, 0x20, 0x39, 0x79, 0x2f, 0x90, 0x2f, 0x6c, 0x68, 0x51, 0x71, 0xaa, 0x75, 0x0f, 0xb6, 0x1c, 0x79, 0x65, 0xcf, 0xee, 0x57, + 0x51, 0xad, 0xbb, 0x57, 0x77, 0xef, 0x79, 0xe5, 0x48, 0x0b, 0xb3, 0xc7, 0x50, 0xe6, 0xd8, 0xfe, 0x90, 0xa3, 0xf7, 0xb7, 0xdb, 0xef, 0xe2, 0x3f, 0x7b, 0x61, 0xe5, 0xca, 0x17, 0x40, 0xc6, 0x5d, 0x77, 0x02, 0xd5, 0x0b, 0x57, 0xac, 0x1d, 0x51, 0xd4, 0x44, 0x75, 0x36, 0xab, 0xcb, 0x88, 0xac, 0x73, 0x27, 0xc7, 0xff, 0xc5, 0x9e, 0x86, 0xfd, 0x8c, 0xa2, 0xd8, 0x45, 0x09, 0x60, 0x49, 0x76, 0x1d, 0xc2, 0x32, 0xa6, 0x18, 0x62, 0x90, 0x16, 0x02, 0xa6, 0x27, 0x42, 0xbb, 0x21, 0x55, 0xa3, 0x44, 0x36, 0xa7, 0xf7, 0xeb, 0xfd, 0x46, 0xbc, 0xc0, 0x85, 0xa0, 0x11, 0x09, 0x97, 0xa4, 0x30, 0xae, 0x18, 0x27, 0x2c, 0x10, 0xae, 0x50, 0x9f, 0xea, 0x0e, 0x7b, 0x87, 0x44, 0x1a, 0x1b, 0xba, 0x69, 0xe1, 0xac, 0x9b, 0xdf, 0x3a, 0x70, 0xe0, 0xad, 0x9b, 0x67, 0xbd, 0x99, 0xbf, 0xf0, 0x60, 0x57, + 0xd7, 0xc1, 0x85, 0x05, 0x6f, 0x2c, 0x5c, 0x81, 0x1a, 0xcf, 0x66, 0xc1, 0xb6, 0xb6, 0x6c, 0xef, 0x2d, 0x00, 0x63, 0xaf, 0xed, 0x7a, 0xeb, 0x44, 0x4f, 0xcf, 0x89, 0xb7, 0x76, 0x51, 0xc3, 0x17, 0x4f, 0xf4, 0xdc, 0x34, 0x5a, 0x56, 0x36, 0x7a, 0x63, 0x0f, 0x35, 0xfc, 0x1a, 0xd9, 0x18, 0x17, 0x9b, 0x8c, 0x6b, 0x8c, 0x7c, 0x4a, 0xff, 0x85, 0x3e, 0x0a, 0x65, 0x6d, 0x3e, 0xb1, 0x2c, 0xa1, 0x08, 0xc3, 0xe1, 0x08, 0x68, 0x10, 0xd8, 0x92, 0xc8, 0xd4, 0x6d, 0x93, 0x87, 0x69, 0xd5, 0xa4, 0x61, 0xb2, 0x7e, 0xc3, 0xe9, 0xd5, 0x48, 0x97, 0xd7, 0xe3, 0x12, 0x4c, 0xf9, 0xca, 0x3c, 0x9f, 0xc7, 0x62, 0x12, 0x8a, 0x2f, 0x49, 0xe4, 0x53, 0x8a, 0x2f, 0x09, 0x83, 0x68, 0xd0, 0x4f, 0x9a, 0x5a, 0x86, 0xd4, 0x00, 0xc6, 0x0b, 0xc9, 0xb2, 0x1b, 0x8e, 0x1c, 0xb9, 0xe1, 0xa6, 0xeb, 0xaf, 0xa7, + 0xf2, 0x59, 0xe9, 0xae, 0xed, 0x45, 0x69, 0x45, 0x33, 0xd7, 0xee, 0x7e, 0xb1, 0xba, 0xa2, 0x56, 0xa5, 0x51, 0x15, 0xc6, 0x53, 0xe5, 0x98, 0x68, 0x99, 0x2e, 0x6e, 0x5b, 0xff, 0x7d, 0xcb, 0xda, 0x63, 0xe9, 0x15, 0x34, 0x17, 0x74, 0x29, 0x6b, 0x22, 0x99, 0x26, 0x93, 0xc3, 0x10, 0xc4, 0x35, 0x2c, 0x76, 0x8d, 0x7f, 0xcc, 0xd8, 0x61, 0xbf, 0x73, 0x88, 0xd5, 0x09, 0x65, 0xc0, 0x4b, 0x12, 0x94, 0x2b, 0x83, 0x14, 0xf8, 0xba, 0x43, 0xa8, 0x16, 0xca, 0x52, 0x70, 0xf8, 0x08, 0x0a, 0xc1, 0xcb, 0xad, 0x41, 0x15, 0xa8, 0x96, 0xa2, 0xc0, 0xf3, 0x34, 0xa8, 0x50, 0xdf, 0xb4, 0x97, 0x60, 0x78, 0x03, 0xb1, 0x70, 0x09, 0xc6, 0x0b, 0x85, 0xfb, 0x50, 0xf8, 0x92, 0xa8, 0xd6, 0xab, 0x0f, 0xb8, 0x27, 0x8f, 0x34, 0xd2, 0x8b, 0x45, 0x3f, 0x11, 0x1a, 0x6c, 0xb4, 0xe0, 0x8b, 0xd3, 0x86, 0x3a, + 0x5e, 0xc8, 0x64, 0xb2, 0x92, 0xac, 0x39, 0xdb, 0x66, 0x6d, 0x0c, 0x29, 0x55, 0xee, 0xa6, 0xd9, 0xfd, 0xb1, 0xb9, 0x57, 0x76, 0x65, 0xbf, 0x38, 0x34, 0xd2, 0xb2, 0xb7, 0xf4, 0x85, 0xe6, 0x4e, 0xdc, 0x6b, 0xfa, 0x28, 0xec, 0x6c, 0xcd, 0xaa, 0xce, 0x7c, 0x7a, 0x4c, 0x1a, 0x5c, 0xea, 0x2d, 0x0b, 0x1b, 0x4b, 0x86, 0x8f, 0x74, 0x91, 0x57, 0x8f, 0x6d, 0xbd, 0x62, 0xeb, 0x8c, 0x2a, 0xf2, 0xaf, 0xf7, 0x4b, 0x1a, 0xf2, 0x52, 0xbd, 0x46, 0x3b, 0xd3, 0x39, 0xe3, 0x32, 0x7a, 0x39, 0xf3, 0x22, 0xfc, 0x96, 0x09, 0xb5, 0xfd, 0x1b, 0x12, 0xca, 0x10, 0x20, 0x25, 0xa6, 0x4c, 0x9a, 0x12, 0xaa, 0x59, 0x23, 0x70, 0xb4, 0xa8, 0x52, 0x41, 0xca, 0xa4, 0x90, 0x11, 0xca, 0x68, 0x5c, 0xb9, 0x89, 0x00, 0x0b, 0x08, 0xb9, 0x7c, 0x18, 0x79, 0x06, 0x31, 0x7c, 0xc0, 0x52, 0x34, 0x8f, 0xb1, 0xcf, + 0x0c, 0x65, 0xd3, 0x5b, 0x13, 0xf9, 0xdf, 0x7e, 0x3d, 0xbc, 0xb2, 0x25, 0xa9, 0x68, 0x63, 0x64, 0x4a, 0x03, 0xc7, 0x71, 0xb9, 0x5c, 0x6e, 0x4e, 0x34, 0x3b, 0xa2, 0x0d, 0xf9, 0xdc, 0x3a, 0x9f, 0x97, 0xc3, 0xb8, 0x47, 0x46, 0xe4, 0x04, 0xc6, 0x05, 0x54, 0x85, 0x78, 0xaa, 0x62, 0x80, 0xd0, 0xe5, 0x3c, 0xb9, 0x53, 0x70, 0x1f, 0x62, 0x42, 0x5d, 0xa8, 0x01, 0xea, 0xc3, 0x8b, 0x56, 0xf2, 0xe9, 0xdc, 0x35, 0xb5, 0x87, 0x8e, 0x5e, 0xb5, 0x69, 0xee, 0xb6, 0x18, 0x98, 0x75, 0xdd, 0xc6, 0xb0, 0x5c, 0x53, 0x77, 0x72, 0xb4, 0xf7, 0x86, 0xe1, 0xe2, 0xca, 0xf5, 0xa7, 0x96, 0xf6, 0x5f, 0x59, 0xf8, 0xc9, 0x27, 0x2f, 0x6c, 0xdf, 0x41, 0xbd, 0x5f, 0x55, 0xf8, 0x93, 0x9f, 0x9c, 0xb9, 0xaf, 0xb5, 0x91, 0x7a, 0x7d, 0xc7, 0x85, 0xc3, 0xc1, 0x4d, 0x1d, 0xfd, 0x68, 0xfe, 0x6c, 0x7a, 0xe1, + 0x70, 0x5b, 0x65, 0xfc, 0xcb, 0x1d, 0x6f, 0xbc, 0x01, 0xd7, 0xc2, 0x3a, 0x62, 0x29, 0xfd, 0x34, 0xfd, 0x08, 0xf6, 0x1d, 0xa2, 0x18, 0x93, 0xcb, 0xa4, 0x80, 0x7e, 0x57, 0xe4, 0xb1, 0x75, 0x5d, 0xc7, 0x5f, 0x5a, 0xbb, 0xf6, 0xa5, 0xe3, 0x5d, 0xc9, 0xcf, 0x47, 0xca, 0x87, 0x0f, 0xb6, 0xb7, 0x1f, 0x5a, 0x5a, 0x5e, 0xbe, 0xf4, 0x50, 0x7b, 0xfb, 0xc1, 0xe1, 0x72, 0xf2, 0x9a, 0xf4, 0xd3, 0xe8, 0x13, 0x1d, 0x15, 0xae, 0x42, 0x9f, 0x87, 0x08, 0x9c, 0x4c, 0xbd, 0x95, 0x58, 0x4e, 0x3f, 0x45, 0x3f, 0x00, 0xd7, 0xa8, 0x91, 0xf0, 0xc2, 0x51, 0x2b, 0x27, 0x66, 0x10, 0x73, 0xa0, 0x04, 0x5a, 0x41, 0x6c, 0x26, 0xf6, 0x11, 0xd7, 0x13, 0xb7, 0x13, 0xf7, 0x13, 0x8f, 0x13, 0xcf, 0x11, 0x7c, 0xcb, 0xa3, 0x79, 0x70, 0xfc, 0x7a, 0xc4, 0x10, 0x9e, 0x41, 0x39, 0x20, 0x15, 0x00, 0xd5, 0x98, + 0x1b, 0x54, 0x01, 0xa8, 0x2a, 0x48, 0xa4, 0xac, 0x64, 0x30, 0x13, 0x48, 0x39, 0x20, 0xd3, 0x48, 0x65, 0x83, 0x3a, 0xa0, 0xd1, 0x03, 0xad, 0x41, 0xa3, 0x1d, 0x34, 0x01, 0x83, 0x19, 0x18, 0x1d, 0x06, 0xe3, 0xa0, 0x0d, 0x58, 0xec, 0xc0, 0xea, 0xb4, 0x58, 0x07, 0x09, 0x07, 0xe1, 0x74, 0x38, 0x17, 0x65, 0x00, 0x24, 0xe6, 0x94, 0x50, 0xcc, 0xad, 0x42, 0x43, 0xbd, 0xf0, 0xd2, 0x47, 0xc3, 0x73, 0x08, 0x09, 0x0c, 0x0a, 0xba, 0xef, 0xfa, 0x12, 0xc2, 0x61, 0x75, 0x3a, 0xba, 0xd3, 0x5f, 0xe5, 0x70, 0xf6, 0xa1, 0xb7, 0x41, 0xc9, 0x58, 0xf6, 0xd3, 0x67, 0x7e, 0x74, 0xf6, 0x87, 0x8f, 0x9e, 0x7e, 0xe8, 0xbe, 0x7b, 0xee, 0xfc, 0xfe, 0x6d, 0xb7, 0x1c, 0xbb, 0xe9, 0xc8, 0xe1, 0x83, 0x07, 0xf6, 0xee, 0xde, 0xb1, 0x6d, 0xd3, 0x86, 0x35, 0xab, 0x96, 0x2f, 0x1b, 0x5a, 0xb2, 0xb0, 0xbf, + 0xa7, 0x6b, 0xf6, 0xac, 0xd6, 0xe6, 0xc6, 0xfa, 0x44, 0x55, 0x59, 0x89, 0x4f, 0xfc, 0xe3, 0x71, 0xa1, 0xc8, 0x1a, 0x84, 0xbb, 0x6d, 0xc0, 0xe8, 0x36, 0x01, 0xe3, 0xff, 0xf1, 0x7b, 0xfa, 0xbd, 0x92, 0xef, 0xf0, 0x9d, 0x49, 0xfb, 0xee, 0xff, 0xff, 0x71, 0x3c, 0xbd, 0x0d, 0x64, 0x9f, 0xd7, 0x6c, 0xf1, 0xf9, 0x2c, 0x66, 0xef, 0x59, 0x9f, 0xd9, 0xe2, 0xf5, 0x5a, 0xcc, 0xbe, 0x3b, 0xd0, 0x3f, 0xe8, 0xc7, 0xb0, 0xcf, 0x8c, 0xbe, 0x98, 0x7d, 0x59, 0xc9, 0x2f, 0x63, 0x83, 0xc9, 0x6f, 0xb7, 0x27, 0x6f, 0x1b, 0x49, 0x5e, 0xed, 0x4f, 0xde, 0x4f, 0xdd, 0x96, 0xbc, 0xe8, 0xe2, 0xb5, 0xc9, 0x93, 0x54, 0x66, 0xf2, 0xdb, 0x58, 0x77, 0xf2, 0x4e, 0x52, 0x99, 0xbc, 0x83, 0xbf, 0x3e, 0x79, 0xc7, 0x4f, 0x92, 0x27, 0xe9, 0x15, 0xc2, 0x29, 0x9f, 0x42, 0xb8, 0xcd, 0xdb, 0x26, 0xfe, 0xde, + 0x8b, 0x7f, 0xfb, 0x7c, 0x77, 0x89, 0xbf, 0xc1, 0x5b, 0xdf, 0x72, 0xc1, 0xf7, 0xc5, 0x4f, 0xf1, 0x5d, 0xbe, 0x7d, 0x69, 0xdf, 0xd1, 0x67, 0x8b, 0x78, 0xbd, 0x45, 0xfc, 0xcc, 0x14, 0x3f, 0x51, 0x7d, 0x97, 0xf1, 0x4f, 0x18, 0x03, 0xf3, 0x30, 0x9c, 0xfd, 0xbb, 0x5b, 0x1e, 0x75, 0xc1, 0x59, 0xae, 0xce, 0x85, 0x13, 0x31, 0x84, 0xea, 0x34, 0xa3, 0xc0, 0x60, 0x04, 0xf7, 0x88, 0x8e, 0xb0, 0xe2, 0x11, 0xa8, 0x51, 0x23, 0xac, 0x47, 0x17, 0x2e, 0x34, 0x81, 0x40, 0x52, 0x50, 0x64, 0xe7, 0x8a, 0x16, 0xc1, 0x9f, 0xc2, 0x00, 0x2c, 0x82, 0xad, 0x89, 0x60, 0x2a, 0x10, 0x9a, 0x65, 0x31, 0x32, 0x51, 0x32, 0x17, 0x4e, 0xe4, 0x57, 0x49, 0x61, 0x0d, 0x37, 0xfc, 0x9c, 0x3e, 0xe8, 0xf6, 0x87, 0x05, 0x53, 0x87, 0xe8, 0xa4, 0xe6, 0xd2, 0x61, 0xcf, 0xb1, 0xba, 0xc6, 0x25, 0x83, 0x03, 0x92, + 0x70, 0x29, 0xfb, 0x2a, 0x56, 0xad, 0xda, 0x50, 0x3b, 0x74, 0xff, 0x96, 0xda, 0xf2, 0x0d, 0x0f, 0xaf, 0xae, 0xe8, 0xc9, 0xb5, 0xb2, 0x66, 0x55, 0x46, 0x3c, 0xb6, 0x73, 0xab, 0x7f, 0xe6, 0xaa, 0xa6, 0x9a, 0xd5, 0x4b, 0x7a, 0xb3, 0xde, 0xba, 0x4d, 0xe3, 0x22, 0xe9, 0x57, 0x6b, 0xbb, 0x0a, 0x75, 0x33, 0x77, 0x3f, 0x32, 0x3c, 0xf6, 0xde, 0xf2, 0x7b, 0xd7, 0x96, 0x6b, 0x74, 0x83, 0x26, 0x95, 0x7a, 0xc7, 0x41, 0xb2, 0xa0, 0x71, 0x4d, 0x7b, 0x96, 0xbb, 0xa2, 0x3b, 0x7e, 0xe1, 0x3c, 0x73, 0xc2, 0x63, 0x49, 0xd6, 0xba, 0x39, 0x8e, 0x69, 0x91, 0x4f, 0xbc, 0x24, 0x48, 0x2b, 0x75, 0x3e, 0xa4, 0x45, 0x16, 0xdc, 0x52, 0x24, 0x69, 0x21, 0x92, 0x28, 0xfd, 0x30, 0x85, 0x49, 0x04, 0x8f, 0xb0, 0x69, 0x47, 0x7a, 0x7b, 0x85, 0x07, 0xd8, 0xd2, 0x68, 0x84, 0x3b, 0x9f, 0x74, 0x3c, 0x81, + 0x35, 0x38, 0xb6, 0xdc, 0x25, 0xc4, 0x96, 0x33, 0x68, 0xc5, 0xaf, 0x99, 0x7a, 0x6d, 0x92, 0x50, 0x6b, 0x44, 0x92, 0x8a, 0x57, 0x7d, 0x03, 0x49, 0xd7, 0x60, 0x7c, 0x4b, 0x44, 0x53, 0xef, 0x37, 0xd1, 0x54, 0x00, 0xb4, 0xfc, 0x6e, 0x44, 0x65, 0xb5, 0xf6, 0xa0, 0xf5, 0xf2, 0x64, 0xbd, 0xb8, 0x7b, 0xf9, 0x3d, 0x88, 0xac, 0x4b, 0xcc, 0x2a, 0x86, 0x65, 0x76, 0x1c, 0xa4, 0xae, 0xbf, 0x84, 0xb0, 0x80, 0x18, 0x80, 0xba, 0xd0, 0x17, 0x90, 0xae, 0x25, 0xc4, 0xfd, 0x09, 0x45, 0xd4, 0x0f, 0xe7, 0x86, 0x1c, 0xbb, 0x9d, 0x05, 0x4d, 0x28, 0x2c, 0xa0, 0x37, 0x43, 0x0d, 0x3f, 0x29, 0xfd, 0x06, 0x85, 0xca, 0x6b, 0xe2, 0x57, 0xd1, 0x5d, 0xb0, 0x92, 0x48, 0x12, 0xec, 0x3b, 0xde, 0x80, 0xf2, 0x7f, 0x20, 0xd9, 0x26, 0xae, 0xc5, 0x52, 0x52, 0xa8, 0xea, 0x26, 0x66, 0x04, 0x4e, 0x5c, 0x89, + 0xc8, 0x16, 0xe2, 0xf4, 0x61, 0x7f, 0x70, 0x92, 0xd5, 0x4d, 0xa4, 0x17, 0x2d, 0xa8, 0x8b, 0x93, 0x09, 0xe6, 0x4c, 0xd1, 0x96, 0xfe, 0xa2, 0xfb, 0xc4, 0xbb, 0xfb, 0x16, 0x9f, 0x3e, 0xb6, 0x35, 0xda, 0xa0, 0xca, 0x94, 0x67, 0x66, 0x95, 0x36, 0x2f, 0xda, 0xd2, 0xd2, 0x7d, 0xed, 0x40, 0x91, 0x40, 0xb6, 0xc1, 0xde, 0xac, 0x37, 0x6f, 0xd5, 0xb8, 0x01, 0x55, 0x89, 0x08, 0x3c, 0x7c, 0xff, 0xe6, 0x5a, 0x92, 0xdd, 0xf7, 0xf3, 0x03, 0xf5, 0xbe, 0xca, 0x39, 0x79, 0x03, 0x3a, 0xa5, 0x2d, 0x9c, 0x17, 0xb6, 0x65, 0x77, 0xef, 0x9a, 0x03, 0x7e, 0x9b, 0x24, 0x1e, 0xaf, 0xa1, 0xae, 0xf4, 0x58, 0x8e, 0x43, 0x2a, 0xeb, 0x11, 0x95, 0x79, 0xa1, 0x26, 0xd5, 0xc3, 0xe3, 0xe7, 0xa9, 0x3f, 0x41, 0xa5, 0x28, 0x4c, 0xbc, 0xd1, 0xf2, 0x68, 0x36, 0xa4, 0x84, 0x8a, 0x10, 0x93, 0x25, 0xb8, 0xe4, + 0xf4, 0xcc, 0x9b, 0x7c, 0x94, 0xc1, 0x0b, 0x18, 0x1d, 0x60, 0x27, 0x0e, 0xa0, 0xc9, 0x29, 0x9b, 0xb4, 0x80, 0x57, 0x4d, 0x59, 0xc0, 0xc2, 0xd3, 0xa7, 0x3f, 0x2f, 0x24, 0x45, 0xe4, 0x4d, 0x9a, 0xdc, 0xab, 0xa6, 0x4c, 0xee, 0xd5, 0x42, 0xdd, 0x11, 0x5c, 0xe5, 0x41, 0x9c, 0xa9, 0xd3, 0x5c, 0x82, 0x33, 0x21, 0x38, 0xad, 0xdf, 0x9d, 0x24, 0x7a, 0x6a, 0xae, 0xb2, 0x53, 0x97, 0x7f, 0x40, 0x2c, 0x5b, 0x89, 0xe7, 0xe9, 0x50, 0xa4, 0xad, 0xa5, 0x35, 0x6b, 0xc6, 0x96, 0xae, 0xbc, 0xc7, 0xee, 0x8b, 0x26, 0xbc, 0x26, 0x46, 0xaf, 0x54, 0x78, 0xdd, 0xed, 0x1d, 0xf3, 0x17, 0xcc, 0xdd, 0x5e, 0xfc, 0xec, 0xc1, 0x4c, 0x3b, 0xa0, 0xd6, 0x47, 0x4b, 0x5c, 0x8a, 0x78, 0xff, 0xce, 0x46, 0xfe, 0xfa, 0x93, 0x0f, 0xaa, 0xd4, 0x3d, 0xba, 0x0c, 0x59, 0x6b, 0x3f, 0xd8, 0xd1, 0x3b, 0xd4, 0xd9, + 0x38, 0x56, 0x44, 0x7d, 0x65, 0x37, 0x88, 0xf4, 0xa4, 0x45, 0x7a, 0x2e, 0x4c, 0xa8, 0x9c, 0x50, 0xec, 0x1a, 0xa1, 0xd2, 0xcd, 0xa2, 0x10, 0xe4, 0x66, 0xa1, 0x87, 0x66, 0xa1, 0x48, 0x13, 0xb1, 0x28, 0x35, 0xd3, 0x96, 0xb6, 0xa4, 0x82, 0xdc, 0x53, 0xe7, 0xf0, 0x51, 0x71, 0x7a, 0xad, 0x12, 0xcf, 0xe3, 0xe9, 0x84, 0xba, 0xe6, 0x41, 0x3d, 0xf3, 0x7f, 0x63, 0x8f, 0x52, 0x13, 0x89, 0xfa, 0xd3, 0xe5, 0x3a, 0x44, 0xa2, 0x2e, 0x47, 0x50, 0x97, 0x5b, 0x4e, 0x3e, 0x90, 0xa1, 0xee, 0x36, 0x28, 0x65, 0xad, 0x7d, 0xe4, 0xea, 0x89, 0xfe, 0xcc, 0x49, 0x76, 0x18, 0xd7, 0x2e, 0x1b, 0xff, 0x9c, 0x3a, 0x07, 0xfb, 0x95, 0x4b, 0xfc, 0x2c, 0x6d, 0x46, 0xc0, 0xed, 0x37, 0xad, 0x47, 0xfe, 0xa6, 0xe4, 0x8c, 0x98, 0x38, 0xd0, 0x9b, 0x5c, 0x89, 0x53, 0x39, 0x11, 0x1e, 0xb5, 0xd4, 0xd7, 0x09, + 0xc6, 0x2d, 0xce, 0x80, 0xef, 0x78, 0xc3, 0xea, 0xc9, 0x0c, 0x2c, 0x7d, 0x46, 0xb0, 0xbd, 0xc2, 0x2c, 0x49, 0x4f, 0x90, 0xc1, 0x93, 0xc2, 0x1b, 0x9c, 0x8e, 0x81, 0xa5, 0x56, 0x24, 0x39, 0xcd, 0xbc, 0xa8, 0x17, 0x88, 0xb4, 0xb9, 0x2b, 0xaf, 0x6a, 0xed, 0x9d, 0x83, 0x4d, 0xbb, 0x57, 0xf6, 0xba, 0xab, 0x32, 0x54, 0x52, 0x5d, 0x61, 0xdb, 0xaa, 0x59, 0x9b, 0xb7, 0xce, 0x5f, 0x30, 0x67, 0x47, 0xf1, 0xb3, 0x87, 0x32, 0x6d, 0xe9, 0xb3, 0xe3, 0x97, 0x03, 0x37, 0x0e, 0x16, 0x58, 0xa2, 0x95, 0xde, 0x2e, 0x4e, 0x1e, 0xca, 0x0f, 0xad, 0x5d, 0x0e, 0xaa, 0x10, 0x4d, 0xf9, 0x33, 0xe4, 0x6e, 0x71, 0x8e, 0xb4, 0xf3, 0x0f, 0x81, 0xe7, 0xa8, 0x2f, 0x09, 0x3b, 0xd1, 0xfe, 0x44, 0x06, 0x9c, 0xfe, 0x33, 0x05, 0xfb, 0x85, 0x81, 0xc0, 0xc5, 0x57, 0xd0, 0xae, 0x04, 0x39, 0x20, 0x92, 0x38, + 0x81, 0x28, 0xf7, 0x01, 0xfe, 0x86, 0xd3, 0x68, 0xcd, 0xc4, 0x05, 0xc4, 0xc4, 0xf9, 0xde, 0xa7, 0xbc, 0x61, 0x4e, 0x2f, 0xa4, 0xd7, 0x62, 0x18, 0x19, 0x8c, 0x29, 0x8a, 0x2a, 0xa1, 0x61, 0x20, 0x48, 0x01, 0x76, 0xd4, 0x08, 0x9e, 0x73, 0x2e, 0x2c, 0xea, 0x1b, 0x6e, 0xad, 0x2a, 0x79, 0x91, 0xd3, 0x85, 0xdd, 0xfa, 0xdc, 0xa0, 0xc1, 0xae, 0x55, 0x32, 0x6a, 0x86, 0x7f, 0xcf, 0x68, 0xeb, 0xa8, 0x2d, 0x1d, 0x0e, 0xa2, 0xfa, 0xe3, 0x0a, 0x47, 0x20, 0x6a, 0xf6, 0x66, 0x49, 0x24, 0x12, 0x54, 0x63, 0x2e, 0x4e, 0x5d, 0x4b, 0x9e, 0x62, 0xb7, 0xc0, 0xcd, 0x6f, 0x43, 0xa2, 0x56, 0x0b, 0x99, 0x1e, 0x36, 0x9c, 0x34, 0x27, 0x8b, 0x3e, 0xc2, 0x01, 0x41, 0x99, 0x6a, 0xab, 0x92, 0x5b, 0xa8, 0xee, 0x54, 0x19, 0x86, 0x1e, 0x94, 0xef, 0x17, 0xf4, 0xdb, 0xad, 0x26, 0x03, 0xa7, 0x86, 0x5b, + 0xc9, 0x28, 0x88, 0x4a, 0x64, 0x28, 0x26, 0xef, 0xbb, 0x61, 0xc2, 0x91, 0x73, 0xbe, 0x3b, 0xd2, 0x5b, 0xf3, 0xb7, 0x43, 0xb8, 0x09, 0xf5, 0x4a, 0xa9, 0xa3, 0x64, 0x8c, 0x5d, 0x43, 0xc8, 0x50, 0xdc, 0x55, 0xb2, 0x8a, 0x1c, 0x2a, 0x5b, 0x89, 0x83, 0x94, 0x3a, 0x10, 0xdf, 0x9e, 0x85, 0x43, 0x08, 0x64, 0x84, 0x0c, 0x85, 0x40, 0xb1, 0x32, 0xb8, 0x69, 0x48, 0x82, 0x1c, 0xea, 0x2f, 0x00, 0x29, 0xff, 0xe5, 0xf7, 0xc0, 0x7f, 0x31, 0x5f, 0xfc, 0x7c, 0x1b, 0xff, 0x1e, 0xf9, 0xde, 0x56, 0x6c, 0x8f, 0x8a, 0x53, 0x12, 0xaa, 0x05, 0xd2, 0x47, 0x0d, 0x57, 0x7b, 0xe7, 0xd9, 0xb0, 0x9e, 0xa4, 0x30, 0x68, 0xba, 0x1f, 0xfb, 0xd4, 0x00, 0xc0, 0xd1, 0xa5, 0xdd, 0x28, 0x47, 0x02, 0xef, 0xb7, 0x7b, 0xd0, 0x3b, 0xe6, 0xa2, 0x8d, 0xb8, 0x85, 0x20, 0x29, 0x72, 0xfd, 0x34, 0x57, 0x50, 0x70, + 0x30, 0x43, 0x3e, 0x5f, 0xc0, 0xc3, 0xc8, 0x26, 0x32, 0x3c, 0xc4, 0x54, 0x69, 0x6c, 0x4b, 0x9c, 0x92, 0xe5, 0xc1, 0x4a, 0x5e, 0xdf, 0xb4, 0x6c, 0x64, 0xa3, 0xa3, 0x74, 0x76, 0x61, 0xd1, 0xec, 0x52, 0x27, 0xb9, 0x63, 0x87, 0x35, 0xa2, 0x19, 0xcc, 0x2b, 0x9e, 0x7b, 0xed, 0x92, 0x78, 0x7c, 0xc9, 0xb5, 0x73, 0xe7, 0x5e, 0xb3, 0x24, 0xde, 0xda, 0xc0, 0x80, 0xf5, 0xbb, 0x77, 0xaf, 0x17, 0x61, 0xeb, 0xc6, 0x5e, 0x53, 0xeb, 0xae, 0x50, 0xa6, 0xe3, 0xd9, 0x0d, 0x9c, 0x40, 0xf9, 0x72, 0x59, 0x70, 0xac, 0x5d, 0xb0, 0x2f, 0x71, 0x54, 0x8b, 0x24, 0x0e, 0xc7, 0xd9, 0x8a, 0xac, 0xd5, 0x97, 0x94, 0xa0, 0x11, 0x41, 0xaf, 0xbb, 0x53, 0xfb, 0xe6, 0x1e, 0xb2, 0x35, 0xea, 0x0d, 0x46, 0x7d, 0x90, 0x60, 0x96, 0xcb, 0xe1, 0x5d, 0xa7, 0x97, 0x9a, 0x49, 0x01, 0x5e, 0x83, 0x8f, 0x33, 0xf4, + 0x0a, 0xb3, 0x27, 0xea, 0x88, 0x15, 0xe5, 0x37, 0x77, 0x35, 0xe7, 0x7b, 0x6a, 0x07, 0x2a, 0xbd, 0x0d, 0x89, 0x12, 0x7d, 0x36, 0xa3, 0x51, 0xa8, 0x1c, 0xc1, 0x22, 0x7f, 0xac, 0x0c, 0x9f, 0x70, 0x55, 0xcf, 0x2f, 0xaf, 0x5d, 0x1b, 0x64, 0xfe, 0x68, 0x50, 0x6b, 0xd5, 0xd1, 0x80, 0xcb, 0x6b, 0xb1, 0xe6, 0x34, 0x57, 0x16, 0xf4, 0xd4, 0x04, 0x54, 0x66, 0x9f, 0xbe, 0x59, 0xaa, 0xd0, 0x99, 0x74, 0x39, 0x79, 0x3e, 0xbf, 0xd9, 0x94, 0xd3, 0x51, 0x1f, 0x6e, 0xaf, 0xf0, 0x85, 0x91, 0x0e, 0x9a, 0x01, 0xc7, 0xfb, 0xcf, 0x70, 0xbc, 0x35, 0x28, 0x9a, 0x59, 0x81, 0xe3, 0xec, 0xe0, 0xae, 0x19, 0x80, 0x0e, 0x44, 0xed, 0x59, 0x62, 0x26, 0x3c, 0xe7, 0xe6, 0x68, 0x19, 0x8a, 0x8c, 0xf2, 0x4e, 0x01, 0x84, 0x2c, 0x24, 0xff, 0xcc, 0xbf, 0x73, 0x90, 0x7f, 0x67, 0xab, 0xce, 0xcc, 0xdc, 0x29, + 0xd5, 0x48, 0x25, 0x99, 0xd2, 0x93, 0x8c, 0x59, 0xcb, 0xae, 0x19, 0xf3, 0x92, 0xef, 0x69, 0xd5, 0xfc, 0x62, 0x4b, 0x85, 0xdb, 0x5d, 0x61, 0x01, 0x27, 0x33, 0xf4, 0xf0, 0x5d, 0x66, 0xf8, 0xae, 0x5f, 0xc0, 0x77, 0x59, 0x11, 0x1e, 0x9a, 0x55, 0xa7, 0x82, 0xab, 0x42, 0x48, 0xdd, 0x22, 0x3b, 0x5a, 0x10, 0x43, 0x87, 0xaf, 0x0b, 0x69, 0xb2, 0x50, 0xb1, 0x4b, 0xb1, 0x60, 0x4c, 0x12, 0x9a, 0x55, 0x8b, 0x23, 0x0f, 0xb5, 0xf1, 0x6a, 0x10, 0x2f, 0x87, 0x4c, 0xf3, 0x17, 0x37, 0xab, 0xb5, 0xcc, 0x49, 0xc8, 0xd2, 0x6e, 0x61, 0x34, 0xaa, 0xeb, 0x3e, 0xfe, 0x9f, 0x9b, 0xd5, 0x2a, 0xe6, 0x24, 0x2b, 0xbb, 0x56, 0xae, 0xbf, 0xe1, 0xaf, 0xd4, 0x51, 0xb5, 0x02, 0xa8, 0xd8, 0x0c, 0xe6, 0x73, 0x79, 0x26, 0xd8, 0x02, 0x86, 0xa5, 0x72, 0xfe, 0x82, 0x9e, 0x7b, 0x4f, 0xc7, 0x9f, 0xc2, 0x7a, + 0x26, 0xe4, 0xce, 0x47, 0xc9, 0xbb, 0x60, 0x1b, 0x74, 0xa8, 0x4a, 0xa2, 0x92, 0x22, 0xa8, 0x24, 0x28, 0x5b, 0xb2, 0xc3, 0xd9, 0x1e, 0x8e, 0x92, 0x19, 0x05, 0x74, 0x7c, 0x2c, 0x20, 0x70, 0x67, 0xc5, 0x44, 0xb6, 0x9b, 0x6f, 0xe6, 0x3f, 0xdf, 0xaf, 0x33, 0x4a, 0x76, 0xca, 0x39, 0x09, 0x9b, 0x29, 0xdf, 0x05, 0xfb, 0x7a, 0x80, 0x7e, 0x90, 0xff, 0x5f, 0x60, 0x02, 0xff, 0xa1, 0xc9, 0xfc, 0x93, 0x31, 0xa4, 0xd3, 0x85, 0x8c, 0x7f, 0xca, 0x10, 0xeb, 0x49, 0x45, 0xe0, 0xbb, 0x86, 0xe1, 0xbb, 0x32, 0x44, 0xc4, 0x91, 0xe4, 0x12, 0x12, 0xb3, 0x7d, 0x67, 0x81, 0x56, 0x4e, 0xc7, 0x69, 0x71, 0x77, 0x75, 0x12, 0x21, 0xcc, 0xaf, 0x18, 0xb8, 0x63, 0x6e, 0x72, 0x58, 0xcd, 0xdf, 0x0a, 0x56, 0x8e, 0xfd, 0x2a, 0x03, 0x2c, 0xe3, 0xef, 0x21, 0xdd, 0x6c, 0xb1, 0xd1, 0xc8, 0xd3, 0xdb, 0x2a, + 0x0c, 0x3a, 0xc0, 0x6f, 0x25, 0xc0, 0xd8, 0x23, 0x2c, 0x4b, 0xce, 0x66, 0x6f, 0xfb, 0xa6, 0x3c, 0x1b, 0xf4, 0xb4, 0xb1, 0x47, 0xe8, 0x7b, 0x2e, 0xf4, 0xb3, 0xb7, 0x1d, 0x23, 0x70, 0xf4, 0xe4, 0x2d, 0xe0, 0x2d, 0xb6, 0xfd, 0x1b, 0xef, 0x81, 0xac, 0x10, 0x34, 0x1f, 0x3e, 0xcc, 0xb6, 0xf3, 0x18, 0xa1, 0x80, 0xf8, 0x31, 0x7d, 0x0b, 0x39, 0x82, 0xef, 0xc1, 0x35, 0x25, 0x49, 0x21, 0x1d, 0x84, 0x4c, 0x4b, 0x07, 0x49, 0xaf, 0x29, 0x89, 0x6e, 0xff, 0xf1, 0x75, 0xd7, 0x01, 0xf7, 0x75, 0xd7, 0xa1, 0x27, 0x80, 0xb3, 0xf0, 0x29, 0xe4, 0xf8, 0x5b, 0xf4, 0x61, 0x72, 0x3d, 0x73, 0x1a, 0xb2, 0x0b, 0x3d, 0x8a, 0x48, 0xc2, 0xf0, 0x77, 0x7d, 0xa8, 0x5a, 0x3c, 0x54, 0xa0, 0x41, 0xab, 0x5c, 0x0e, 0x08, 0xb9, 0x5e, 0xae, 0xd7, 0x64, 0x0a, 0x0e, 0x1e, 0x5c, 0x9c, 0x29, 0xe5, 0x6f, 0xf2, + 0xe2, 0xb8, 0x02, 0xc1, 0xf5, 0x74, 0xab, 0xd7, 0x9b, 0x71, 0x75, 0xa7, 0xd1, 0x91, 0xf1, 0xd2, 0x13, 0x38, 0xa4, 0xe0, 0xf1, 0x05, 0x46, 0xbb, 0xea, 0x3c, 0x73, 0x1a, 0xb9, 0x98, 0x38, 0x40, 0xa2, 0x48, 0x02, 0xfc, 0x05, 0xf9, 0x9a, 0xee, 0x63, 0xe5, 0xe4, 0x11, 0x89, 0x15, 0xe5, 0x5f, 0x11, 0xd1, 0x44, 0x16, 0x03, 0x95, 0x5e, 0x1c, 0x71, 0x88, 0xbf, 0x10, 0xa0, 0x0f, 0xb7, 0x1d, 0x3b, 0x98, 0x32, 0x55, 0x72, 0xad, 0x42, 0x2b, 0xbe, 0x9b, 0x9d, 0x78, 0x77, 0x2e, 0xf0, 0xa6, 0xbe, 0x81, 0x3b, 0xbd, 0x11, 0xd5, 0x4d, 0x0b, 0x8d, 0xd6, 0xcc, 0x5f, 0xfe, 0xc5, 0x1b, 0xe5, 0x7e, 0x3c, 0x6a, 0x74, 0x73, 0x17, 0x24, 0x56, 0xa3, 0xf1, 0xcf, 0x99, 0xaa, 0xcf, 0x4c, 0x86, 0x3f, 0xab, 0xd5, 0x9f, 0xa5, 0x7c, 0x0a, 0x37, 0xc2, 0x5e, 0x86, 0xd1, 0x3b, 0x91, 0x83, 0x95, 0x44, + 0x0e, 0xd6, 0xc9, 0x5e, 0x37, 0x40, 0x04, 0x7c, 0x36, 0xcb, 0x84, 0xe3, 0x40, 0x32, 0xbd, 0xe3, 0x60, 0x8a, 0x45, 0x7a, 0x1a, 0x77, 0x41, 0x50, 0xe7, 0x8d, 0x22, 0xb8, 0x5d, 0x9d, 0xce, 0x9d, 0x6d, 0x36, 0x47, 0xbd, 0xba, 0x69, 0x7c, 0x04, 0x07, 0xcd, 0xd9, 0x1e, 0x9d, 0xce, 0x03, 0xcf, 0xe7, 0xa0, 0xcf, 0x1c, 0x62, 0xc2, 0xf7, 0x83, 0x30, 0x45, 0xfc, 0x28, 0x4f, 0x83, 0xc1, 0x15, 0x5c, 0x10, 0xfe, 0x5c, 0xd2, 0x6f, 0x33, 0xe1, 0x87, 0x63, 0xb1, 0x1f, 0x4e, 0xaf, 0x73, 0xbb, 0x1c, 0x36, 0x8b, 0x59, 0xe7, 0xd7, 0xfb, 0x43, 0x50, 0x2d, 0x30, 0x5c, 0x92, 0x9b, 0x3f, 0xd5, 0x81, 0x49, 0x6a, 0xa6, 0x36, 0x6e, 0xef, 0x24, 0x9b, 0xbe, 0xe4, 0x80, 0x39, 0x8a, 0xce, 0xc1, 0x6b, 0x72, 0xd0, 0x67, 0x0e, 0xbf, 0x74, 0xb2, 0x27, 0x13, 0xad, 0x9d, 0xcd, 0xf4, 0xed, 0xe4, 0x83, + 0x70, 0xdf, 0xa2, 0x47, 0x11, 0x20, 0x28, 0x11, 0x5a, 0x0c, 0x5e, 0x42, 0xe1, 0x31, 0x2b, 0x48, 0x48, 0xc8, 0x09, 0x22, 0x0a, 0x15, 0xb3, 0x1d, 0xc8, 0x87, 0x90, 0xe6, 0x4c, 0x00, 0x37, 0x74, 0xac, 0xa8, 0xb5, 0x5f, 0x93, 0xe1, 0x2e, 0xce, 0xca, 0x2a, 0x76, 0x67, 0x5c, 0x63, 0xaf, 0x5d, 0xc1, 0x3c, 0x1c, 0x69, 0x19, 0x2c, 0x4e, 0xd2, 0xa8, 0x78, 0xb0, 0x45, 0xc8, 0x87, 0x03, 0x3e, 0xba, 0x93, 0xd4, 0xb2, 0x99, 0x84, 0x19, 0x45, 0xc8, 0x9a, 0xa7, 0x09, 0x0c, 0xf5, 0x35, 0xe3, 0xf5, 0x91, 0x2c, 0x56, 0xcc, 0x7a, 0x04, 0xed, 0x41, 0xb0, 0x72, 0x48, 0x40, 0xa7, 0x52, 0xca, 0x48, 0x4d, 0x66, 0x23, 0xfc, 0x97, 0x29, 0x4a, 0x24, 0xe2, 0x15, 0x95, 0xdd, 0xb3, 0xe8, 0x15, 0xb2, 0x4c, 0x26, 0x83, 0x95, 0x59, 0x7d, 0x11, 0x33, 0xa8, 0x8f, 0xe5, 0xd6, 0xcc, 0xad, 0xed, 0xf8, + 0x5e, 0x9d, 0xf0, 0x3e, 0x15, 0xdd, 0x09, 0xc6, 0xe0, 0xfb, 0xac, 0x68, 0x55, 0x69, 0xa7, 0x79, 0x9f, 0xc5, 0x54, 0x8f, 0xcb, 0xe4, 0x21, 0x42, 0x67, 0x81, 0xa0, 0x83, 0xc2, 0xe9, 0x97, 0x6a, 0x80, 0xb1, 0xf4, 0xe1, 0x5b, 0xc1, 0x18, 0xe7, 0x8c, 0x98, 0x19, 0x19, 0x23, 0x31, 0x99, 0xcd, 0x52, 0x54, 0xfb, 0x6e, 0x56, 0x57, 0x65, 0x45, 0xac, 0x86, 0xb9, 0xd3, 0x12, 0x75, 0x69, 0x80, 0x39, 0xe2, 0xb3, 0xca, 0x20, 0x0b, 0x54, 0xcb, 0x2d, 0x75, 0xdf, 0xeb, 0xa8, 0x9d, 0x5b, 0x03, 0x9f, 0x79, 0x0b, 0x2f, 0x25, 0xd7, 0x8d, 0xbf, 0x2a, 0x64, 0x9a, 0xa1, 0x42, 0x98, 0x08, 0x3f, 0x4c, 0x4c, 0x11, 0x41, 0xf1, 0xcc, 0xa8, 0xf0, 0x14, 0x21, 0xe1, 0x04, 0x80, 0x03, 0x86, 0x13, 0x10, 0x80, 0xc9, 0x75, 0xd7, 0x22, 0x54, 0xe2, 0x22, 0xbe, 0xe2, 0xd4, 0x2e, 0x81, 0x7f, 0xa6, 0x3d, + 0x27, 0x55, 0xc1, 0x60, 0x01, 0x52, 0xb2, 0xc4, 0x6a, 0x96, 0x49, 0x86, 0x00, 0x50, 0x05, 0x8d, 0x42, 0xbd, 0x9b, 0x5c, 0xc7, 0x3f, 0x76, 0xed, 0xb5, 0xa0, 0x83, 0x7f, 0xf8, 0x14, 0x78, 0xf1, 0x14, 0xba, 0x9f, 0x3e, 0x40, 0xae, 0x63, 0x9b, 0xe1, 0xfa, 0x70, 0xa0, 0x1c, 0xfe, 0x69, 0xb2, 0x55, 0x26, 0xe5, 0xaa, 0x08, 0x2d, 0x78, 0x97, 0x3e, 0x00, 0x1a, 0xae, 0xc4, 0xef, 0xbf, 0x09, 0xbe, 0x7f, 0x0b, 0x7e, 0x7f, 0x08, 0x45, 0x36, 0x88, 0x38, 0x0d, 0xa8, 0xce, 0x60, 0x0a, 0xaf, 0x01, 0x20, 0x30, 0xa9, 0xb4, 0xde, 0x00, 0x0c, 0xd7, 0x50, 0xc8, 0x91, 0x5b, 0x78, 0xcb, 0x99, 0xc3, 0xfc, 0x81, 0x5d, 0xbb, 0x40, 0x23, 0xff, 0xf4, 0xa4, 0xbe, 0x40, 0x79, 0x24, 0xc2, 0xa9, 0x43, 0xba, 0xcc, 0xc3, 0xfd, 0x21, 0x91, 0x87, 0x3a, 0xc5, 0xe0, 0x18, 0xaf, 0x10, 0xe0, 0x4d, 0xae, + 0xbb, 0xff, 0xf0, 0xfd, 0xe0, 0xdd, 0xc7, 0xf9, 0x9f, 0xe0, 0x06, 0x4d, 0x3c, 0x43, 0x81, 0x9f, 0x81, 0xe0, 0xc6, 0xc1, 0x02, 0x64, 0x36, 0xc7, 0x0f, 0x10, 0x29, 0x92, 0x0a, 0x14, 0xd7, 0x7b, 0x45, 0x23, 0x6f, 0x21, 0x7e, 0x0e, 0xfc, 0x1f, 0xd2, 0x05, 0x92, 0x36, 0x45, 0xdb, 0x87, 0xe1, 0xb3, 0xc2, 0xc9, 0x7a, 0xbf, 0xa9, 0xe2, 0xa5, 0x02, 0x7b, 0x46, 0x66, 0xe4, 0x5b, 0xc0, 0xbb, 0x7c, 0xe0, 0xb7, 0xbb, 0xe0, 0xb5, 0xab, 0xf8, 0x83, 0x64, 0xcd, 0xf8, 0x3f, 0x08, 0x03, 0x92, 0x2d, 0x72, 0x11, 0x55, 0x06, 0x99, 0x7b, 0x27, 0x6c, 0x1c, 0x59, 0x7e, 0x4c, 0x48, 0xa6, 0x70, 0x9a, 0x5a, 0x76, 0xe0, 0x91, 0xa3, 0x8e, 0x8a, 0xfe, 0xca, 0x96, 0x0d, 0xee, 0x7e, 0xb9, 0x8a, 0x96, 0xab, 0xf5, 0x2e, 0x6b, 0x76, 0xa1, 0x6e, 0x34, 0xbf, 0xaf, 0x21, 0xab, 0x75, 0x9e, 0x8f, 0x06, + 0x7a, 0x8b, 0xbe, 0xaa, 0x18, 0xb5, 0xe9, 0x15, 0xf8, 0x9e, 0xef, 0xe3, 0xf7, 0x40, 0xee, 0xad, 0xc4, 0xfa, 0x01, 0x21, 0x38, 0xed, 0x85, 0xf8, 0x71, 0xac, 0xdc, 0xac, 0x21, 0x5b, 0xf5, 0xbe, 0x42, 0x0f, 0x26, 0x54, 0x52, 0x93, 0x4e, 0x01, 0xf8, 0x57, 0x82, 0x42, 0xb0, 0xa0, 0x28, 0x62, 0x75, 0xe9, 0x33, 0xe5, 0x74, 0x86, 0x7c, 0x81, 0x7b, 0x63, 0x0b, 0x4a, 0x96, 0xb9, 0x69, 0x67, 0x71, 0x15, 0x7c, 0x0b, 0xa0, 0x7d, 0xf3, 0x5a, 0xb3, 0x1a, 0xfa, 0xf2, 0x47, 0x05, 0xdf, 0xe5, 0x39, 0x3e, 0x9f, 0x5c, 0x38, 0x8e, 0xec, 0xd5, 0x4e, 0xa2, 0x5a, 0xa8, 0x2a, 0xa9, 0x9d, 0x30, 0x5a, 0x0b, 0x7d, 0x5b, 0x0e, 0x70, 0xd5, 0xd4, 0xcb, 0x98, 0xb2, 0x7b, 0xcf, 0xfa, 0x02, 0x97, 0x33, 0x66, 0x4f, 0xe4, 0x02, 0x9f, 0x73, 0x97, 0x34, 0x87, 0xb3, 0x5a, 0x4a, 0x3d, 0x9e, 0xd2, 0x96, + 0xac, 0x70, 0x73, 0x89, 0x7b, 0x65, 0x51, 0x6e, 0x24, 0x1e, 0x8f, 0xe4, 0x16, 0xf1, 0xff, 0x8c, 0xcc, 0x28, 0xb0, 0xd9, 0x0a, 0x66, 0x44, 0xb2, 0x1a, 0xf3, 0x6d, 0xb6, 0xfc, 0x46, 0xc8, 0x3f, 0x8a, 0xb3, 0xa2, 0xb1, 0x18, 0x5e, 0xbb, 0xcd, 0x7c, 0x0c, 0xbc, 0x35, 0xfe, 0x77, 0x38, 0x46, 0xa9, 0x7a, 0xc0, 0xe2, 0x40, 0x2d, 0x9f, 0xa8, 0x07, 0x9c, 0x26, 0x5a, 0x7b, 0x9f, 0x10, 0x46, 0x8f, 0x11, 0x85, 0xab, 0x1e, 0xcb, 0x56, 0x40, 0x14, 0xf0, 0xa7, 0xc9, 0xb7, 0xc7, 0x7f, 0x04, 0xe7, 0x9e, 0x19, 0x6e, 0x68, 0x84, 0x00, 0xc0, 0xc5, 0x13, 0x53, 0x46, 0x0c, 0x6b, 0x8b, 0xa1, 0x65, 0xe4, 0x26, 0xdf, 0xde, 0xcf, 0x0f, 0xed, 0x27, 0xef, 0xe3, 0x4f, 0xdf, 0x7a, 0x2b, 0x50, 0x12, 0xe2, 0xfd, 0xaf, 0xe3, 0xfb, 0x5d, 0xc4, 0xfa, 0x27, 0x25, 0xe8, 0x66, 0xd1, 0xb9, 0x6b, 0x90, + 0xb0, 0x70, 0x21, 0x50, 0x14, 0xb9, 0x14, 0xcf, 0xc2, 0x7e, 0x20, 0x3c, 0xd2, 0x2a, 0xc0, 0x2d, 0xeb, 0x85, 0xb3, 0xe4, 0xba, 0xc9, 0x27, 0x13, 0xd6, 0xb4, 0xe3, 0x90, 0xac, 0xdd, 0xa9, 0xd3, 0x14, 0x31, 0xab, 0x57, 0xf0, 0xf7, 0xb8, 0x08, 0x97, 0x57, 0xeb, 0xd5, 0xfa, 0xbc, 0xc8, 0xdf, 0xc3, 0x08, 0xd6, 0x5d, 0xa1, 0xca, 0x07, 0x2e, 0xd6, 0x69, 0x80, 0x93, 0x4b, 0xf8, 0x62, 0x34, 0xc4, 0x2b, 0x01, 0xf9, 0xf6, 0xe8, 0xdc, 0xfc, 0x56, 0xb3, 0xb9, 0x35, 0x7f, 0xee, 0xe8, 0xb5, 0x6e, 0x8f, 0xd3, 0x75, 0xd8, 0xe5, 0xf4, 0xb8, 0x9b, 0x0c, 0x2b, 0x47, 0x7c, 0x6e, 0xb7, 0x6f, 0x64, 0xa5, 0xc1, 0x10, 0x8e, 0x02, 0x10, 0x0d, 0x1a, 0x0c, 0x41, 0xf4, 0x19, 0x36, 0x08, 0x7d, 0xba, 0x53, 0xa4, 0x49, 0xb5, 0x08, 0x1e, 0x83, 0xd6, 0x34, 0x1c, 0xd8, 0xa5, 0x88, 0xf5, 0xcf, 0x4f, + 0x6e, 0xf3, 0x34, 0xf8, 0x28, 0x58, 0x97, 0x76, 0x50, 0xa8, 0xe0, 0x9b, 0xce, 0xc0, 0xdc, 0x88, 0xfb, 0xc4, 0xc8, 0xb7, 0xc7, 0xe6, 0xef, 0x07, 0x27, 0x9a, 0xf8, 0xbf, 0x02, 0xd3, 0x7d, 0xf7, 0xa1, 0xf9, 0x95, 0xe4, 0x1f, 0xc9, 0x9c, 0x5b, 0x8a, 0x10, 0xb2, 0x9e, 0x53, 0x81, 0xe1, 0xbd, 0x88, 0xa9, 0xa4, 0xad, 0x7b, 0x7d, 0x21, 0x94, 0xf7, 0x6e, 0xf0, 0xd5, 0x5f, 0xff, 0x7a, 0x25, 0x6f, 0x01, 0xff, 0x73, 0xe6, 0x37, 0x5b, 0xc0, 0x8b, 0x68, 0xcf, 0xc7, 0xbf, 0x46, 0xbe, 0x31, 0x7e, 0x96, 0x30, 0x11, 0xbd, 0x89, 0x0c, 0x23, 0xa4, 0x94, 0x86, 0x43, 0x66, 0x57, 0x6c, 0x59, 0x43, 0x94, 0xe6, 0x68, 0x86, 0x4c, 0x95, 0xdf, 0x14, 0x6b, 0xe7, 0x5a, 0x70, 0x82, 0x2d, 0x8d, 0x73, 0x92, 0x88, 0x6e, 0xf4, 0x99, 0x52, 0x46, 0x30, 0xbe, 0x82, 0x41, 0x97, 0xa9, 0x52, 0xc8, 0xe1, + 0x3e, 0xd0, 0x04, 0x4c, 0x48, 0x13, 0x62, 0xd0, 0xce, 0x2f, 0xe0, 0x55, 0x01, 0x5c, 0x28, 0x35, 0x10, 0xf4, 0x08, 0x99, 0xa6, 0xc6, 0x38, 0x69, 0x6d, 0x1a, 0x54, 0x1d, 0x64, 0x2b, 0x30, 0x59, 0x2b, 0xd8, 0x83, 0xaa, 0xc1, 0xa6, 0xc1, 0xc2, 0xc2, 0xe6, 0xd6, 0x4e, 0x5f, 0x3d, 0xc8, 0x41, 0x34, 0xcd, 0x01, 0xf5, 0xbe, 0xce, 0xd6, 0x86, 0x06, 0x61, 0xae, 0xdc, 0x49, 0x6f, 0x26, 0x97, 0xb3, 0x35, 0x38, 0xc7, 0x5a, 0x8a, 0x73, 0x3c, 0x8d, 0x12, 0x54, 0x9a, 0x25, 0x58, 0x6c, 0x2c, 0x36, 0x82, 0x37, 0x8f, 0xcf, 0x3a, 0x9e, 0xfc, 0x9f, 0xe9, 0x17, 0xbe, 0x1d, 0xeb, 0x3c, 0x7e, 0xbc, 0x13, 0xcf, 0x77, 0x37, 0xbd, 0x9f, 0xa4, 0xd9, 0xff, 0x16, 0xa2, 0x41, 0x27, 0xb2, 0x83, 0xa0, 0x32, 0xdb, 0x2d, 0xe6, 0x08, 0x51, 0x64, 0x07, 0x67, 0xe5, 0xcc, 0x78, 0xc2, 0x8a, 0x95, 0x7b, + 0x0b, 0xf5, 0x42, 0x09, 0x41, 0xbc, 0xf0, 0x62, 0x55, 0x00, 0x6c, 0xb7, 0x79, 0x6c, 0xee, 0xb2, 0xd9, 0x05, 0x91, 0x2c, 0x9b, 0xdb, 0xe6, 0x2a, 0x9b, 0x9d, 0x9f, 0x1d, 0x62, 0x1a, 0x0c, 0xb9, 0xf1, 0x4a, 0x5f, 0xa8, 0x21, 0xdf, 0xb6, 0x0d, 0xf8, 0xdd, 0x86, 0xbc, 0x78, 0x95, 0x37, 0x5c, 0x9f, 0x67, 0xdd, 0x0c, 0xfc, 0xf8, 0xbd, 0xcc, 0x3f, 0x49, 0x5a, 0x9a, 0xf9, 0xff, 0xfe, 0xbd, 0xec, 0x3d, 0x06, 0x7f, 0x56, 0xa1, 0x2f, 0x54, 0x1b, 0x35, 0x6f, 0x03, 0x56, 0xb7, 0x21, 0x90, 0x55, 0xe4, 0x0d, 0xd7, 0x44, 0x8d, 0x9b, 0x01, 0x16, 0x47, 0x4d, 0x74, 0x21, 0xf9, 0x1e, 0xd4, 0x2b, 0x3c, 0x28, 0x4a, 0xc9, 0x6e, 0x81, 0x1a, 0xa3, 0x42, 0x8a, 0x5d, 0xc7, 0x36, 0x2b, 0x49, 0x35, 0xc9, 0x65, 0x28, 0x7e, 0x1d, 0x0e, 0x33, 0xe4, 0xf4, 0x42, 0x21, 0x1c, 0xa8, 0x4e, 0x51, 0xd4, + 0x20, 0x96, 0x9f, 0x1e, 0xc2, 0x13, 0xf0, 0x69, 0x82, 0x42, 0x15, 0x58, 0x91, 0xe5, 0xe2, 0x02, 0x38, 0x13, 0xe5, 0x26, 0x71, 0xfd, 0x1b, 0xec, 0x03, 0x24, 0xdf, 0xb3, 0xe6, 0xd5, 0x87, 0x5d, 0xb5, 0xae, 0xc1, 0xfe, 0x45, 0x1a, 0xb7, 0xca, 0xed, 0x07, 0xeb, 0xad, 0x05, 0x75, 0x61, 0x6f, 0x83, 0x7b, 0xa8, 0x7f, 0x91, 0xdb, 0xe4, 0xf1, 0x03, 0xe6, 0x9a, 0x82, 0xd9, 0xe5, 0x2e, 0xbd, 0xea, 0xf6, 0xc2, 0x63, 0x52, 0x59, 0x5e, 0x24, 0x7f, 0x4e, 0x99, 0xdb, 0x88, 0x7e, 0xa8, 0x72, 0xb2, 0x11, 0x2e, 0x23, 0xaf, 0xa7, 0x6b, 0xc6, 0x37, 0xc0, 0x3d, 0x75, 0x69, 0x42, 0xae, 0x56, 0x92, 0xa0, 0x49, 0x42, 0xc3, 0xb6, 0x09, 0x93, 0x4f, 0x91, 0xac, 0x2f, 0xbd, 0x7c, 0xfa, 0x3a, 0xbc, 0x4f, 0x18, 0xf5, 0x58, 0xa1, 0x31, 0xe8, 0x24, 0x0c, 0x64, 0x31, 0xfe, 0x40, 0xbc, 0x18, 0x61, + 0x1a, 0xd1, 0x5e, 0x87, 0x83, 0x3f, 0xcf, 0xe7, 0xf0, 0x9f, 0xa3, 0x5d, 0x8f, 0x1c, 0xfc, 0x9a, 0xff, 0x47, 0x66, 0xa1, 0x7a, 0xff, 0xf0, 0xf0, 0x21, 0xf8, 0x71, 0xd5, 0xc8, 0x08, 0x1a, 0x97, 0xdd, 0x63, 0x1f, 0x92, 0xef, 0x8f, 0xff, 0x10, 0xea, 0xd6, 0x75, 0xc2, 0xbb, 0xd4, 0x38, 0x16, 0x84, 0x58, 0x80, 0xc5, 0x2f, 0x2e, 0x7a, 0x6c, 0xc1, 0xeb, 0x13, 0x23, 0x44, 0xa2, 0x7f, 0x84, 0x38, 0x91, 0x5e, 0xac, 0x7c, 0x0a, 0xeb, 0x54, 0x46, 0xc8, 0x34, 0x49, 0xd1, 0x0c, 0x25, 0xa2, 0x36, 0x0e, 0xf7, 0x76, 0xe4, 0xe6, 0x9b, 0x86, 0x0f, 0xa2, 0xd7, 0xf0, 0x07, 0x4f, 0x9d, 0x02, 0x0a, 0xd8, 0x00, 0xfe, 0x3c, 0x6a, 0xec, 0x97, 0xf4, 0x36, 0x32, 0x9b, 0xad, 0x43, 0xa8, 0x2e, 0x8f, 0x33, 0x00, 0xcd, 0x5d, 0x3c, 0x71, 0xc9, 0xec, 0x0f, 0x66, 0xdd, 0x72, 0xcb, 0x2c, 0x66, 0xe1, + 0x07, 0xb3, 0x8e, 0x1d, 0xeb, 0x84, 0xd7, 0x7d, 0x4a, 0xdf, 0x04, 0xf7, 0x37, 0xe5, 0x04, 0x87, 0xe7, 0x4b, 0x0a, 0x5d, 0x0a, 0x31, 0x33, 0xd1, 0x06, 0x44, 0x11, 0x1d, 0x3a, 0xce, 0x24, 0x30, 0xd6, 0xa4, 0xf6, 0xaa, 0x17, 0x7c, 0xb2, 0xe0, 0xad, 0xc2, 0xb9, 0x68, 0xa3, 0x3a, 0xb7, 0x30, 0x52, 0x52, 0x12, 0xa1, 0x6f, 0x72, 0x94, 0x74, 0xe4, 0xe5, 0xb5, 0x97, 0x38, 0x46, 0x4a, 0xe2, 0xf1, 0x62, 0x41, 0x2e, 0xd5, 0xd0, 0xc3, 0xe4, 0xfb, 0xac, 0x1d, 0xfb, 0x51, 0xd3, 0xf5, 0x66, 0xa8, 0x24, 0xa3, 0xc8, 0x44, 0xa8, 0xe9, 0xa3, 0x19, 0x9a, 0xa6, 0xc3, 0xe9, 0x75, 0x5e, 0x0f, 0xd6, 0x9b, 0x83, 0xfa, 0xe0, 0x65, 0xf4, 0x66, 0x41, 0x67, 0x0d, 0xa4, 0x54, 0x56, 0x52, 0xaa, 0x85, 0x0a, 0xb3, 0x29, 0x82, 0x82, 0x4c, 0x22, 0x38, 0xd8, 0x64, 0xcb, 0xfc, 0x5d, 0x9d, 0xfe, 0x2b, + 0x87, 0x57, 0xc4, 0x3a, 0x0a, 0xcd, 0xbb, 0xfc, 0x9d, 0x57, 0xd2, 0xef, 0x99, 0x84, 0x10, 0x14, 0x93, 0x59, 0xb8, 0x0a, 0x04, 0xa3, 0x9d, 0xab, 0x6b, 0xfb, 0x56, 0xd9, 0x8b, 0x5a, 0xa2, 0xb5, 0xab, 0x3b, 0xa3, 0x78, 0xbd, 0xbf, 0x3f, 0xf6, 0x05, 0xa9, 0x19, 0x7f, 0x1e, 0xd3, 0x8c, 0x4e, 0xae, 0x77, 0xb8, 0xd2, 0xb7, 0xdd, 0x32, 0xeb, 0x83, 0x0f, 0x66, 0xf1, 0xff, 0x7d, 0x6c, 0xd6, 0x07, 0x1f, 0xce, 0x82, 0x2d, 0x95, 0x8f, 0x7d, 0x4e, 0x56, 0x8d, 0x3f, 0x77, 0x09, 0x6d, 0xab, 0x30, 0x6d, 0xf9, 0xff, 0xfe, 0xb0, 0xf3, 0xd8, 0xb1, 0x59, 0x08, 0x7f, 0x70, 0xec, 0x73, 0xca, 0x85, 0xaf, 0x53, 0x10, 0xd2, 0x27, 0xe5, 0x28, 0x1c, 0x3b, 0x2f, 0xe2, 0xc4, 0x17, 0x03, 0x34, 0xc3, 0x8d, 0x83, 0x1f, 0xa0, 0x3b, 0xc8, 0x67, 0xe1, 0xe5, 0x1f, 0x7c, 0x50, 0xf3, 0x21, 0x1a, 0x13, + 0xfe, 0x57, 0xb7, 0xde, 0xda, 0xf9, 0xc1, 0x07, 0x82, 0xcc, 0xa4, 0xef, 0x23, 0x73, 0xd8, 0x0e, 0x71, 0x6c, 0x52, 0xf9, 0x7b, 0x70, 0x58, 0xba, 0x45, 0x93, 0x0e, 0x41, 0x75, 0x98, 0x38, 0x9d, 0x30, 0x36, 0x82, 0x84, 0x8e, 0x25, 0xe5, 0x36, 0x68, 0x46, 0x63, 0x92, 0x1c, 0x1f, 0x26, 0xa3, 0x38, 0x1e, 0x2f, 0x19, 0x71, 0x94, 0xb4, 0xe7, 0xe5, 0x75, 0x94, 0x88, 0xb5, 0x2f, 0x96, 0xf3, 0x6b, 0xc1, 0x9b, 0xe3, 0x1f, 0x11, 0xb9, 0x44, 0x5d, 0x42, 0x9b, 0x9b, 0x13, 0x09, 0x79, 0x5d, 0x4e, 0x93, 0x51, 0x9b, 0xa9, 0x90, 0xb0, 0x2a, 0x21, 0xfd, 0xd3, 0xf7, 0x1d, 0x16, 0xc5, 0x93, 0xc1, 0x80, 0x03, 0x69, 0xdd, 0x0c, 0xaa, 0x37, 0xed, 0xfc, 0xa6, 0x12, 0xd5, 0x06, 0x63, 0x40, 0x2c, 0x50, 0x1d, 0xaf, 0x06, 0x46, 0x70, 0x30, 0xc3, 0xa2, 0xb2, 0x9b, 0xda, 0xbc, 0x6d, 0x99, 0x46, + 0x99, 0xaa, 0x89, 0x91, 0xc8, 0x0c, 0x2a, 0xf8, 0x43, 0x6d, 0x55, 0x3b, 0xac, 0x33, 0xbd, 0x33, 0xed, 0x1a, 0x74, 0x4c, 0x63, 0x6b, 0xf3, 0xfe, 0x54, 0x2a, 0x27, 0x29, 0x2e, 0xbb, 0x32, 0x5b, 0xa6, 0x53, 0xd9, 0x3c, 0xbd, 0x3d, 0x9c, 0x2d, 0x43, 0xa9, 0x91, 0x46, 0xab, 0xb2, 0xf1, 0xf1, 0x48, 0x65, 0x56, 0xa6, 0xd5, 0xe6, 0x99, 0xdf, 0xcb, 0xd9, 0x55, 0xa6, 0xcc, 0xec, 0x0a, 0x48, 0x94, 0xbb, 0xf9, 0x23, 0x24, 0x3f, 0xfe, 0x1b, 0xec, 0x23, 0x2f, 0x26, 0x1a, 0x13, 0x75, 0x31, 0x00, 0x68, 0xc8, 0x18, 0x10, 0xf0, 0xe3, 0x94, 0xc4, 0x5f, 0x21, 0xdf, 0x97, 0x59, 0xc3, 0xb4, 0x9a, 0x4d, 0x19, 0x50, 0x4d, 0x2b, 0xcc, 0x0f, 0x07, 0xbd, 0x6e, 0x53, 0xb1, 0xb9, 0x58, 0xcb, 0x29, 0x8d, 0x19, 0x46, 0x21, 0x4e, 0x45, 0x48, 0xf8, 0x9d, 0x88, 0x53, 0xf1, 0xa6, 0x7d, 0xf7, 0xa7, + 0x21, 0x21, 0x4d, 0x36, 0x3e, 0x56, 0x01, 0xb2, 0xdd, 0xe9, 0xf3, 0x39, 0x5d, 0x3e, 0xdf, 0xef, 0x5c, 0x5e, 0xaf, 0x0b, 0x7e, 0x82, 0x40, 0xa0, 0x2a, 0x62, 0x34, 0x46, 0xaa, 0x02, 0x79, 0xf5, 0x5a, 0x5d, 0x5d, 0x5e, 0xa0, 0x2a, 0xdb, 0x64, 0xca, 0x86, 0xbf, 0xea, 0x74, 0xda, 0x7a, 0xfe, 0x9f, 0x3e, 0xbb, 0xdd, 0x97, 0xfe, 0xf7, 0x43, 0x73, 0x76, 0xb9, 0xc7, 0x53, 0x91, 0x6d, 0x0e, 0xba, 0x5c, 0x41, 0x73, 0x76, 0x85, 0xc7, 0x53, 0x9e, 0x6d, 0x0e, 0xb8, 0x5c, 0x01, 0x61, 0xfc, 0x5e, 0xa1, 0xdb, 0xc9, 0xcd, 0xec, 0x95, 0x44, 0x06, 0xd1, 0x24, 0x44, 0xa3, 0x9b, 0x08, 0xac, 0xc1, 0x88, 0x59, 0xee, 0x90, 0xcd, 0xf6, 0x22, 0x43, 0xef, 0x20, 0x62, 0x2e, 0x06, 0xc4, 0xf8, 0xd7, 0x09, 0x1b, 0x87, 0xb4, 0x13, 0x02, 0x63, 0xc9, 0x20, 0x32, 0xdc, 0x5a, 0x37, 0x66, 0x2c, 0x14, + 0xae, 0x69, 0xe0, 0xd5, 0xe2, 0x72, 0x07, 0xe4, 0xe6, 0x2d, 0x4b, 0xf4, 0x2a, 0x69, 0xdd, 0xd7, 0x9b, 0x97, 0x18, 0x32, 0x24, 0x75, 0xcc, 0x29, 0xfe, 0x61, 0x93, 0xf6, 0x31, 0x30, 0xcf, 0xac, 0x79, 0x0c, 0xe3, 0x09, 0xf1, 0x05, 0xd2, 0x8a, 0x71, 0x84, 0x21, 0x5c, 0x7d, 0x36, 0x43, 0xc9, 0x32, 0x34, 0x6a, 0x07, 0xe2, 0x6d, 0x4a, 0x32, 0x99, 0x43, 0x8e, 0x95, 0x3b, 0x8d, 0x60, 0x01, 0xe9, 0x4e, 0x1e, 0xc5, 0x72, 0x5b, 0x01, 0x08, 0xb9, 0x14, 0xc5, 0xb9, 0x51, 0x29, 0xc8, 0x08, 0x96, 0x76, 0xbb, 0x7c, 0xc1, 0x62, 0x07, 0x45, 0xcf, 0x38, 0x3a, 0xf6, 0x97, 0x77, 0xd7, 0x14, 0xbd, 0xce, 0x2b, 0xf8, 0xd7, 0xe1, 0x7f, 0x8a, 0xd7, 0x0b, 0xd7, 0xbc, 0xfb, 0x97, 0x31, 0xfe, 0x45, 0x43, 0x24, 0x11, 0xde, 0xd7, 0x35, 0x38, 0xd8, 0xb5, 0x2f, 0x9c, 0x88, 0x18, 0x10, 0x86, 0xcb, + 0x6c, 0xfa, 0x06, 0x69, 0x05, 0xf3, 0x2a, 0x25, 0x21, 0x83, 0x04, 0xc1, 0x2e, 0x14, 0xb0, 0xfa, 0xd8, 0x85, 0x29, 0x8c, 0x97, 0x65, 0xf4, 0x6f, 0xa5, 0x01, 0x66, 0x39, 0x3c, 0x9f, 0x75, 0xe9, 0x79, 0x84, 0x67, 0xc9, 0x6f, 0x95, 0x06, 0xc6, 0x95, 0x82, 0x5e, 0x6a, 0xfb, 0xee, 0x7a, 0x29, 0xda, 0x55, 0x2c, 0x93, 0xfc, 0xf4, 0xcb, 0xda, 0x7f, 0x1f, 0xc5, 0xcf, 0x59, 0x4b, 0x1f, 0x92, 0xda, 0x99, 0xff, 0x21, 0x1c, 0xc4, 0x9a, 0x96, 0x47, 0xb5, 0x38, 0x3b, 0x25, 0x5d, 0xdc, 0x21, 0xc5, 0x70, 0x18, 0xfb, 0x20, 0x90, 0xb2, 0x89, 0x82, 0x5f, 0x51, 0xde, 0x03, 0x4a, 0x94, 0xc6, 0x97, 0x30, 0xcc, 0x70, 0x4b, 0x32, 0x6e, 0x0a, 0x5d, 0x06, 0xb7, 0x44, 0xc9, 0x2b, 0x20, 0x77, 0xa4, 0x11, 0x4c, 0xcf, 0xc4, 0x95, 0x0c, 0xf2, 0x37, 0xc8, 0x34, 0x7e, 0x9f, 0xc6, 0x2f, 0xe4, 0x0a, + 0xe0, 0xea, 0xe3, 0x34, 0x9a, 0x8e, 0x62, 0x34, 0x20, 0x8d, 0x4d, 0x0a, 0x52, 0xfb, 0x9f, 0xfe, 0xbe, 0xec, 0xec, 0x83, 0x77, 0xde, 0x73, 0x66, 0xf1, 0xad, 0x47, 0x4e, 0x96, 0xac, 0x3b, 0xb3, 0x09, 0xff, 0x3a, 0xbd, 0xf8, 0xd6, 0xeb, 0x4f, 0x32, 0xaf, 0xdc, 0xf6, 0x80, 0x23, 0x5a, 0x14, 0x75, 0xcc, 0xeb, 0x6b, 0xdb, 0x32, 0x27, 0xe2, 0x88, 0xc6, 0xd0, 0x57, 0x34, 0xa6, 0xf4, 0x83, 0x90, 0x9e, 0x1f, 0xa2, 0x3c, 0x70, 0x71, 0x4c, 0x67, 0x08, 0xbb, 0x89, 0x89, 0x31, 0xc5, 0x45, 0xea, 0xb9, 0xb4, 0xbd, 0x37, 0x1a, 0x5e, 0xe2, 0x92, 0x21, 0xc5, 0x08, 0x1f, 0x71, 0x9f, 0xdb, 0x45, 0x1b, 0x25, 0x39, 0x70, 0x48, 0x8f, 0xa1, 0x21, 0x2d, 0x7c, 0x1d, 0xfc, 0x1b, 0x14, 0x80, 0x7c, 0x70, 0xfe, 0xf5, 0x22, 0x34, 0xa4, 0xcc, 0x6c, 0x43, 0xa4, 0x3a, 0xbc, 0xb7, 0x6b, 0x68, 0xa8, + 0x6b, 0x6f, 0xb8, 0x3a, 0x62, 0x10, 0xc7, 0x44, 0x2f, 0xd5, 0x41, 0x19, 0x2d, 0x23, 0xe2, 0x4f, 0xc9, 0x00, 0x68, 0xa2, 0xc1, 0x77, 0x16, 0xd0, 0x9c, 0x28, 0xa0, 0x59, 0x24, 0xa0, 0xe1, 0x7f, 0x92, 0xf3, 0x48, 0x22, 0x7e, 0xfe, 0xd5, 0x4e, 0x76, 0x9f, 0x28, 0x94, 0x8f, 0x8e, 0x08, 0x98, 0xe9, 0x74, 0x2f, 0xf8, 0x03, 0x1b, 0x83, 0x3c, 0x22, 0x97, 0xa8, 0x49, 0x54, 0x29, 0xe0, 0x33, 0xcc, 0x50, 0x36, 0xa1, 0x58, 0x56, 0x96, 0x81, 0x77, 0xa3, 0x6a, 0x61, 0xac, 0x84, 0x64, 0x51, 0x5c, 0x32, 0x01, 0x59, 0x07, 0x92, 0x53, 0x14, 0xee, 0x2f, 0xd5, 0x87, 0xbc, 0x25, 0x54, 0x87, 0xd7, 0x63, 0x08, 0xe8, 0x7d, 0x81, 0x4c, 0x54, 0x35, 0x0a, 0x88, 0xae, 0x90, 0x78, 0x31, 0x25, 0x40, 0xa3, 0x71, 0xa9, 0x02, 0xd0, 0x01, 0x5c, 0xff, 0x39, 0x56, 0x94, 0x4a, 0x58, 0x1f, 0xc9, 0x2e, + 0xf7, 0xaa, 0xec, 0x56, 0x7b, 0xb6, 0xf1, 0xab, 0x59, 0xeb, 0xa1, 0x06, 0x65, 0x2a, 0xe1, 0xb4, 0xac, 0xdc, 0x93, 0xc8, 0xa9, 0x69, 0xb1, 0x54, 0x8d, 0xb6, 0x37, 0x58, 0xdc, 0x16, 0x83, 0x3a, 0x43, 0xc3, 0x30, 0x0e, 0x52, 0xc1, 0x19, 0x94, 0x16, 0xbd, 0xde, 0x00, 0x34, 0x33, 0xad, 0x79, 0xb5, 0x41, 0xfe, 0xf7, 0x72, 0xce, 0x6a, 0x77, 0x55, 0x57, 0x9b, 0x0b, 0xb3, 0x6c, 0xa4, 0x61, 0x96, 0x42, 0xa5, 0xc8, 0x90, 0xc1, 0x76, 0xce, 0x81, 0x7d, 0xe1, 0xd9, 0x42, 0x82, 0x25, 0xc2, 0xc4, 0xf6, 0x27, 0x35, 0x40, 0x48, 0xbd, 0x71, 0xe0, 0x3a, 0x69, 0x14, 0x4b, 0xb2, 0x14, 0xb9, 0x26, 0xd5, 0x0b, 0x14, 0x73, 0x4c, 0x2f, 0x42, 0xd5, 0xec, 0x41, 0xaf, 0x04, 0x19, 0xd7, 0x50, 0x2d, 0xe1, 0xac, 0xa9, 0x97, 0x49, 0x50, 0x51, 0x67, 0x96, 0x02, 0x28, 0xc4, 0x95, 0xa6, 0xb1, 0x26, + 0x82, 0x7c, 0x09, 0x24, 0x0d, 0x47, 0xd9, 0x24, 0x91, 0xc0, 0x2d, 0x43, 0x58, 0x12, 0x42, 0xc9, 0xfb, 0x50, 0xf1, 0x96, 0xc1, 0xf7, 0xb2, 0x1e, 0x19, 0xb2, 0xbb, 0x60, 0x9e, 0xa8, 0x06, 0xc9, 0x5a, 0x74, 0x4e, 0x30, 0x19, 0xfa, 0x00, 0x6d, 0x7e, 0xc0, 0xf5, 0x91, 0x9c, 0xec, 0x7c, 0x29, 0x29, 0xa9, 0xcc, 0x37, 0xfb, 0x6d, 0x46, 0x85, 0x4a, 0xea, 0xd3, 0xe5, 0x16, 0x14, 0x99, 0xf2, 0xe6, 0x56, 0xf9, 0x9c, 0x65, 0x73, 0x8a, 0xa2, 0xcd, 0x71, 0x87, 0x82, 0x63, 0x76, 0xf8, 0x4a, 0xf3, 0xa2, 0x2d, 0x55, 0xa5, 0x4a, 0xb5, 0x52, 0x99, 0x63, 0x72, 0x70, 0x12, 0x67, 0xd5, 0x82, 0xca, 0x9c, 0x39, 0x55, 0x3e, 0x5b, 0xbc, 0xbd, 0xc0, 0xae, 0x15, 0x74, 0x8b, 0x25, 0xf4, 0x00, 0x71, 0x91, 0xfd, 0x1a, 0x2e, 0x11, 0x0e, 0x45, 0x59, 0x22, 0xdc, 0x14, 0x8c, 0x6b, 0x85, 0x71, 0x6c, + 0x68, 0x20, 0x58, 0x37, 0x40, 0x6a, 0x76, 0x10, 0xc8, 0x6c, 0x29, 0x45, 0x42, 0x13, 0xea, 0x50, 0xfa, 0x88, 0x16, 0x55, 0xc4, 0x14, 0x18, 0xb7, 0x60, 0x1d, 0xfa, 0x68, 0xc7, 0xb5, 0x4a, 0xbd, 0x3d, 0xd3, 0x19, 0x8d, 0x3a, 0x5d, 0x39, 0xac, 0x8e, 0x6f, 0x05, 0x4f, 0xdc, 0x9a, 0x69, 0xd3, 0x2b, 0xa3, 0x2e, 0x57, 0x34, 0x4a, 0x08, 0x74, 0x26, 0xdd, 0x90, 0xce, 0x5a, 0xc2, 0x2e, 0xbc, 0x4f, 0xdc, 0x49, 0x33, 0x38, 0x41, 0x17, 0x6e, 0xfe, 0x52, 0x7b, 0x69, 0x84, 0x1d, 0x61, 0x35, 0xeb, 0xec, 0x7a, 0xfb, 0x04, 0xca, 0x3e, 0x98, 0x84, 0xf0, 0x56, 0x45, 0x6a, 0x45, 0x21, 0x02, 0xaf, 0x2d, 0x2f, 0x9a, 0x87, 0xc4, 0xf4, 0xbc, 0xa2, 0xa2, 0x79, 0x95, 0x1e, 0x4f, 0xe5, 0xbc, 0xa2, 0x58, 0x7e, 0x7e, 0x6c, 0x19, 0x5b, 0xe8, 0xae, 0xec, 0x89, 0x17, 0xf7, 0x54, 0xb9, 0xdd, 0x55, + 0x3d, 0xc5, 0xf1, 0x9e, 0x4a, 0x37, 0x7f, 0x75, 0x23, 0xfc, 0x73, 0xf8, 0x30, 0xec, 0xcd, 0x08, 0x1c, 0xf3, 0x71, 0x36, 0x1b, 0xee, 0xa3, 0x30, 0xe2, 0x3f, 0x45, 0xe2, 0xaa, 0xee, 0xcd, 0x0c, 0x0d, 0x35, 0x7d, 0x82, 0x9c, 0x37, 0xa1, 0xc1, 0x89, 0xbb, 0x51, 0x21, 0x65, 0xd8, 0xab, 0x0f, 0x70, 0x2c, 0x52, 0x16, 0xb8, 0xf4, 0x02, 0xe5, 0x13, 0x11, 0x87, 0x60, 0xbc, 0x4d, 0xa1, 0x62, 0x25, 0xd6, 0x9a, 0x9c, 0x40, 0x69, 0x48, 0xb7, 0xc3, 0xe3, 0xb5, 0x44, 0x75, 0x6c, 0x36, 0x5f, 0xa4, 0x94, 0x3b, 0xad, 0x6e, 0x4b, 0x4e, 0x4d, 0x00, 0xf8, 0xf8, 0xdf, 0xf9, 0x43, 0x06, 0x1d, 0x40, 0xb6, 0x37, 0x34, 0xef, 0xde, 0x87, 0xf4, 0x08, 0x60, 0x3c, 0x63, 0x00, 0x18, 0x03, 0x9c, 0x7a, 0xe9, 0x3e, 0x1d, 0x34, 0x10, 0xab, 0xe0, 0x9c, 0x02, 0x04, 0x03, 0x2e, 0x99, 0x49, 0x21, 0x9f, + 0xd7, 0xe7, 0x15, 0x18, 0x58, 0xaa, 0xb0, 0x61, 0xca, 0x9f, 0x38, 0xd5, 0x91, 0x07, 0x7e, 0xcc, 0x28, 0xe4, 0x32, 0x4e, 0x6f, 0x33, 0x14, 0x17, 0x95, 0xe4, 0x99, 0x1d, 0x7a, 0x4e, 0xa1, 0x96, 0xfa, 0x42, 0x4b, 0xf2, 0xb2, 0x5a, 0x4b, 0x3d, 0xf6, 0xb2, 0xb9, 0x25, 0xb1, 0x0e, 0x17, 0x73, 0x4e, 0x22, 0x95, 0x67, 0xc8, 0x5b, 0x6a, 0x1b, 0x66, 0x2a, 0xd5, 0x19, 0xca, 0x9c, 0x5c, 0xa7, 0xab, 0xb2, 0xa7, 0xb4, 0xa0, 0x2b, 0xe1, 0xf3, 0x08, 0x18, 0x17, 0x68, 0xfc, 0x48, 0xf6, 0x2b, 0xb8, 0xe6, 0xbd, 0x44, 0x22, 0x51, 0xe9, 0x81, 0xec, 0x59, 0x81, 0x34, 0x6d, 0x0d, 0x1c, 0x32, 0xaa, 0x19, 0xea, 0x01, 0x34, 0xc9, 0xcc, 0x43, 0x8c, 0x16, 0xa0, 0x32, 0xda, 0x68, 0x34, 0xbb, 0x71, 0xca, 0x33, 0xce, 0x44, 0xee, 0x50, 0x2a, 0x95, 0x5e, 0xa5, 0x37, 0x14, 0xd0, 0xf9, 0xdc, 0x78, + 0xa7, 0x12, 0x98, 0x08, 0x4c, 0xd4, 0x72, 0x53, 0xea, 0xbd, 0xe3, 0xb8, 0x44, 0x70, 0xc0, 0x9d, 0x9b, 0x5d, 0x1c, 0x0d, 0xb8, 0xb2, 0x75, 0x3b, 0x5a, 0x95, 0x2a, 0x56, 0x66, 0x6b, 0xcc, 0xf3, 0x55, 0x45, 0xcd, 0xb6, 0xa2, 0x99, 0xd9, 0x81, 0x42, 0x3d, 0xf3, 0xb8, 0xa7, 0xbe, 0xad, 0xa5, 0xc2, 0x6c, 0x24, 0x95, 0x63, 0x23, 0x4a, 0x79, 0xd0, 0x11, 0x70, 0x55, 0x74, 0x97, 0x14, 0xf5, 0x24, 0xfc, 0x06, 0xed, 0x47, 0x98, 0x0f, 0xae, 0xa1, 0x07, 0x48, 0x29, 0x6c, 0xaf, 0x1e, 0x21, 0xf4, 0x23, 0x8f, 0xa2, 0x50, 0x9b, 0x02, 0x5b, 0xcb, 0x90, 0x48, 0x42, 0x9b, 0x84, 0x41, 0x8c, 0x3b, 0x06, 0x55, 0x1a, 0x04, 0x3c, 0xa6, 0xf5, 0x0a, 0x7e, 0x5a, 0xad, 0x98, 0x3d, 0x86, 0x92, 0xe2, 0x53, 0xee, 0xe6, 0x7f, 0x58, 0x2a, 0xb2, 0x02, 0x65, 0x70, 0x54, 0xbd, 0x5e, 0x0b, 0x6c, 0xd1, + 0x0e, 0x96, 0x30, 0xda, 0x2c, 0xb9, 0x89, 0x00, 0xff, 0x3b, 0xe0, 0xf3, 0x87, 0xf4, 0x5a, 0xfe, 0x29, 0xf2, 0x56, 0x41, 0x26, 0xae, 0x86, 0xef, 0x45, 0xfc, 0xc4, 0x87, 0xb0, 0xce, 0x59, 0x94, 0x06, 0x65, 0xc7, 0x20, 0x17, 0x12, 0xd8, 0x04, 0x04, 0x78, 0x06, 0xa7, 0xbc, 0x04, 0x7b, 0x0c, 0x7a, 0x51, 0x49, 0x60, 0x82, 0x44, 0x2f, 0xd6, 0x7a, 0xb4, 0x3e, 0xa1, 0x80, 0x9e, 0x50, 0xaf, 0x54, 0xc8, 0x4a, 0x40, 0x0d, 0x40, 0x64, 0x9a, 0xd4, 0x10, 0x78, 0xf6, 0xcf, 0xc6, 0xe2, 0x40, 0xb4, 0x44, 0x65, 0x9c, 0x13, 0x6d, 0x99, 0xbd, 0xb3, 0xde, 0x91, 0xb5, 0xc3, 0xed, 0x30, 0x06, 0xb9, 0x1d, 0x6d, 0x0a, 0x35, 0xcb, 0x7c, 0x6d, 0x76, 0x45, 0xfd, 0xd9, 0x81, 0xbe, 0xb9, 0xa8, 0x61, 0x6d, 0x41, 0xdc, 0xbe, 0x90, 0x5f, 0xcb, 0xf1, 0x8f, 0x83, 0x57, 0x95, 0x72, 0xd4, 0xbe, 0x15, + 0xf4, 0x00, 0xb8, 0x1a, 0xf2, 0x6e, 0x3d, 0xca, 0x93, 0x87, 0x0a, 0x38, 0xa1, 0xc2, 0x9c, 0x9b, 0x46, 0xfa, 0x31, 0xde, 0xef, 0xe2, 0x95, 0xd9, 0x8b, 0x56, 0x7a, 0x1b, 0xd4, 0x6b, 0xbc, 0x13, 0x34, 0xc1, 0xe5, 0x19, 0xd3, 0x1b, 0xf2, 0x07, 0x43, 0x49, 0xc0, 0xa9, 0x93, 0xd2, 0x99, 0x12, 0xab, 0xc2, 0x1d, 0x54, 0xed, 0x9c, 0xa9, 0x53, 0x51, 0xcc, 0x1f, 0x83, 0x51, 0xf5, 0xa2, 0x4c, 0xda, 0xeb, 0xe4, 0xff, 0x02, 0x5e, 0x55, 0x71, 0x98, 0x26, 0x09, 0x38, 0xd7, 0x77, 0xb1, 0x4f, 0x10, 0x4e, 0x84, 0xf4, 0xa3, 0x10, 0xea, 0x45, 0x60, 0x8d, 0x8a, 0x82, 0x1c, 0x86, 0xa1, 0xc0, 0x62, 0xc4, 0x3a, 0x71, 0xe2, 0x06, 0xd1, 0x0f, 0xbf, 0x10, 0xb3, 0xe0, 0x4e, 0x2d, 0xa8, 0x15, 0x8a, 0x83, 0xb0, 0xd8, 0xd8, 0x83, 0xff, 0xc1, 0x9e, 0x38, 0x7d, 0xa1, 0xde, 0xab, 0xd7, 0x89, 0xe6, + 0x3e, 0x23, 0xd8, 0x15, 0x6b, 0xa8, 0xae, 0x8d, 0xc8, 0x55, 0xcd, 0x26, 0xd6, 0x90, 0xd8, 0xb4, 0xc9, 0x2f, 0xcb, 0x90, 0xcb, 0x58, 0x56, 0x49, 0xd1, 0xfb, 0xb2, 0x66, 0x54, 0x7e, 0xa6, 0x52, 0x16, 0xaa, 0x36, 0xe8, 0xaf, 0x99, 0xff, 0x97, 0x20, 0xc3, 0x30, 0x2c, 0xc6, 0x99, 0xad, 0x84, 0xe3, 0xa3, 0x87, 0x6b, 0x9f, 0x43, 0x36, 0xcc, 0x09, 0xdf, 0xa3, 0x08, 0x4c, 0x81, 0xf1, 0xee, 0x33, 0x39, 0x21, 0x4d, 0x9e, 0xf2, 0x52, 0x49, 0x67, 0x1c, 0x46, 0xc2, 0x2d, 0x24, 0xf5, 0x4f, 0xac, 0x79, 0x62, 0x44, 0xa5, 0x25, 0x5b, 0xa5, 0x0a, 0x46, 0x96, 0xd9, 0x4e, 0x6a, 0x95, 0x68, 0x81, 0x43, 0xd2, 0x2a, 0xde, 0x75, 0x57, 0x39, 0xcb, 0xeb, 0xde, 0x95, 0x67, 0x60, 0x2c, 0x5b, 0xf8, 0x0e, 0x1b, 0x7c, 0x87, 0x15, 0x61, 0xdd, 0x59, 0xb5, 0x19, 0x29, 0x9f, 0x23, 0x4a, 0x60, 0x40, + 0xae, 0x47, 0xa2, 0x0f, 0xa7, 0x00, 0x77, 0x84, 0x35, 0x69, 0xb9, 0xa4, 0x40, 0xcc, 0xec, 0x2b, 0x0e, 0x0a, 0x31, 0x22, 0x42, 0x7f, 0x25, 0xa4, 0x6d, 0xb3, 0x2a, 0x93, 0xe9, 0xa5, 0xa5, 0xad, 0x4c, 0xa6, 0x72, 0xf5, 0xed, 0xc7, 0xb7, 0xa8, 0x94, 0x4c, 0x2f, 0xc5, 0x32, 0xf5, 0x32, 0xed, 0xda, 0x63, 0xf4, 0x80, 0x4a, 0xf6, 0x3d, 0x4e, 0xb5, 0x45, 0xa6, 0xfa, 0xe7, 0xfb, 0x12, 0xd9, 0x4d, 0x32, 0x8d, 0x6c, 0xa5, 0xe6, 0x1d, 0x1c, 0x9f, 0x9c, 0x03, 0x37, 0xc4, 0x9f, 0xb2, 0xf9, 0x22, 0x2e, 0x5d, 0x34, 0x91, 0x85, 0x22, 0x86, 0x60, 0x4f, 0x91, 0x59, 0x21, 0xc9, 0xe6, 0xa7, 0xba, 0x00, 0x42, 0xf0, 0x0f, 0xb6, 0x61, 0x16, 0x03, 0x89, 0x7f, 0xaa, 0x3f, 0x68, 0xca, 0xef, 0x53, 0x83, 0x60, 0xdf, 0x10, 0xff, 0x05, 0x4e, 0x5b, 0xc8, 0x42, 0x99, 0x0a, 0x59, 0x38, 0x6d, 0x01, + 0xbc, 0x85, 0x72, 0x16, 0x92, 0xb9, 0x0b, 0xe8, 0x93, 0xb9, 0x77, 0x88, 0xdf, 0x39, 0xd4, 0x65, 0x8a, 0xb8, 0x34, 0x1a, 0x57, 0x24, 0x99, 0xce, 0x00, 0x82, 0xa6, 0x88, 0x53, 0xab, 0x75, 0x46, 0x26, 0xf2, 0x1b, 0xa6, 0x69, 0x33, 0x32, 0xbd, 0x60, 0xef, 0x1d, 0xc2, 0x79, 0x4f, 0xc2, 0xea, 0x4d, 0xc6, 0xf1, 0x48, 0xb5, 0x39, 0x08, 0x8c, 0xd4, 0x14, 0xdb, 0xeb, 0x54, 0x70, 0xbd, 0x0b, 0x43, 0x60, 0xdf, 0xe0, 0xd6, 0xa9, 0x4d, 0xe6, 0xb3, 0xa6, 0x36, 0x99, 0xf6, 0x0c, 0xa1, 0xce, 0xfd, 0x46, 0x6c, 0x9a, 0x59, 0x6c, 0x3b, 0xe8, 0x99, 0x38, 0x80, 0xdb, 0x8e, 0x7c, 0xbb, 0x9b, 0x79, 0x25, 0x71, 0xf3, 0xf8, 0x9b, 0x44, 0x2d, 0x61, 0x4c, 0xe8, 0xaa, 0x9c, 0x24, 0x46, 0x1a, 0xc1, 0xd8, 0x07, 0xa3, 0x65, 0x25, 0x3e, 0xb4, 0xb7, 0x93, 0x24, 0xc1, 0x8b, 0x84, 0x78, 0xf5, 0xa4, + 0xad, 0x08, 0x5b, 0x2d, 0x91, 0x54, 0x17, 0x70, 0x2d, 0x63, 0xa2, 0x39, 0x49, 0xcc, 0x4e, 0x0a, 0xe6, 0x50, 0x87, 0x2c, 0x70, 0x53, 0xa4, 0xd4, 0xe8, 0xcc, 0x5a, 0x4f, 0x56, 0x56, 0x41, 0x96, 0x2b, 0xe8, 0x76, 0xf9, 0x5d, 0x3a, 0x0e, 0x52, 0x24, 0x50, 0x60, 0xf7, 0xd8, 0x43, 0xb9, 0x21, 0xf8, 0x3f, 0xd4, 0x21, 0x94, 0x32, 0x85, 0x22, 0x33, 0xe0, 0x36, 0x76, 0xb3, 0xea, 0x0c, 0xb9, 0x44, 0xa9, 0x92, 0x28, 0x7c, 0x36, 0x9b, 0x07, 0xee, 0x39, 0x58, 0x39, 0xe7, 0xb0, 0x5a, 0x6d, 0x9a, 0xcc, 0x0c, 0x45, 0x26, 0x83, 0x22, 0x9d, 0x58, 0xaf, 0x55, 0x6f, 0xc9, 0x94, 0xb1, 0x12, 0x85, 0xc6, 0x69, 0xb5, 0xba, 0x0d, 0x6a, 0xa9, 0x44, 0xc6, 0x30, 0x70, 0x0d, 0x66, 0x68, 0x0d, 0x82, 0xff, 0x6b, 0x3b, 0xf9, 0x20, 0xf3, 0x14, 0x61, 0x20, 0xca, 0x05, 0x5d, 0x5b, 0x33, 0x51, 0x7e, + 0x79, 0xb8, 0x45, 0x48, 0xc2, 0x46, 0x09, 0xf4, 0x49, 0xf7, 0xdd, 0xc4, 0x49, 0xb8, 0x19, 0x3a, 0xab, 0xf3, 0xe9, 0x44, 0x18, 0xed, 0x34, 0xb7, 0xa2, 0x90, 0x51, 0x05, 0x80, 0xd6, 0x19, 0xd0, 0xfa, 0x6b, 0xf2, 0x6d, 0xd7, 0xe4, 0x76, 0x6f, 0x6f, 0x8b, 0xcd, 0x2d, 0x77, 0x5d, 0xc3, 0xac, 0xf7, 0x45, 0x4d, 0x32, 0x53, 0x6e, 0x5d, 0x76, 0xcd, 0xfa, 0xae, 0x02, 0x6b, 0xf9, 0xc2, 0x7a, 0xc1, 0x8e, 0x40, 0x6f, 0x23, 0x35, 0xa2, 0xed, 0xe5, 0x52, 0x3b, 0x02, 0xb3, 0x10, 0xd9, 0x11, 0x3a, 0x2f, 0xb1, 0x0f, 0xc8, 0x24, 0x69, 0xf6, 0x01, 0x06, 0xdd, 0x50, 0x2c, 0xd8, 0x07, 0xa8, 0x8c, 0x0f, 0x90, 0x65, 0x40, 0xb0, 0x0f, 0xd4, 0x7c, 0xf8, 0x21, 0x3c, 0x24, 0xc4, 0xef, 0xc3, 0xc1, 0x63, 0x50, 0x2d, 0x0f, 0x39, 0xd4, 0x48, 0xca, 0x88, 0x2b, 0x13, 0xf2, 0x02, 0xb8, 0x3e, 0x1c, + 0x58, 0x46, 0x08, 0xb1, 0x4e, 0xee, 0xa4, 0xd1, 0x80, 0x95, 0xc2, 0x85, 0x23, 0xa4, 0x28, 0x0d, 0x62, 0x2c, 0x26, 0x1c, 0x65, 0x81, 0x23, 0xf6, 0x83, 0x28, 0xb3, 0x95, 0x62, 0x97, 0x25, 0x2f, 0x95, 0x42, 0x3d, 0x97, 0xee, 0xbf, 0xf4, 0xca, 0xde, 0x84, 0x5d, 0xaf, 0x53, 0x40, 0x6e, 0x13, 0x0e, 0x06, 0x7c, 0xba, 0x32, 0x7d, 0xa9, 0x42, 0xab, 0xd0, 0xa8, 0x94, 0x32, 0x09, 0x21, 0x07, 0x72, 0x79, 0x1a, 0xaa, 0xad, 0xdb, 0x4c, 0x16, 0x4e, 0xb6, 0x45, 0x48, 0x68, 0x1c, 0x09, 0xe5, 0xe6, 0x04, 0x89, 0x69, 0x34, 0xb0, 0x82, 0x4b, 0x0e, 0xce, 0x29, 0xea, 0x83, 0xce, 0xf5, 0x33, 0x3d, 0xc7, 0x6e, 0xe7, 0xdf, 0xf8, 0xfc, 0x8e, 0x5b, 0x6e, 0xba, 0xed, 0xea, 0x9f, 0xed, 0x28, 0x77, 0xd5, 0x0e, 0xd5, 0x83, 0xd2, 0xfa, 0xbd, 0x3f, 0xdd, 0x3a, 0x76, 0xd1, 0xbc, 0x73, 0x35, 0x1c, + 0x5d, 0xb9, 0x73, 0x1b, 0xa7, 0x2d, 0x1e, 0xbd, 0x7d, 0xf8, 0xc1, 0x40, 0x6d, 0x5f, 0x7c, 0xc5, 0x76, 0xde, 0x4d, 0xb7, 0xf2, 0x59, 0x57, 0xae, 0xdc, 0xb6, 0xf6, 0xfe, 0xa2, 0x25, 0xd7, 0xf6, 0x14, 0x0d, 0xcc, 0xa9, 0x35, 0x54, 0xf1, 0x5f, 0xf4, 0x7f, 0x6f, 0x55, 0x05, 0x3d, 0xbe, 0xb8, 0xc7, 0x92, 0xaf, 0xd2, 0x07, 0xa4, 0xb6, 0xbc, 0xae, 0xa2, 0xf6, 0x9d, 0xbd, 0x79, 0xb0, 0x5f, 0xf9, 0xb0, 0x17, 0x2e, 0x48, 0xa7, 0x4c, 0xa8, 0xb7, 0x95, 0x26, 0xe2, 0x19, 0x4a, 0x9c, 0x17, 0x8f, 0xb2, 0x53, 0x50, 0xb6, 0x06, 0x4e, 0x36, 0x6f, 0x61, 0x91, 0x7a, 0xdf, 0x2f, 0xa0, 0x22, 0x6b, 0x38, 0x94, 0xe3, 0xc9, 0xd9, 0x35, 0x76, 0x35, 0x52, 0x6f, 0x33, 0x41, 0xa6, 0x24, 0x5d, 0x7f, 0x73, 0x73, 0x41, 0x84, 0x14, 0x9d, 0x8c, 0xee, 0x77, 0x73, 0x94, 0xec, 0xea, 0x97, 0x0e, 0xd4, + 0xd6, 0x5d, 0xf5, 0xc2, 0x95, 0x63, 0x5f, 0xbc, 0x07, 0x0c, 0xb7, 0x83, 0xfc, 0xee, 0x2d, 0x2d, 0x6e, 0x5f, 0xdb, 0xe6, 0x39, 0xfc, 0xaf, 0xa8, 0xe3, 0x33, 0x77, 0xde, 0xdb, 0xb7, 0xe4, 0xa1, 0x1d, 0x33, 0xa8, 0x3b, 0x6f, 0x04, 0x2b, 0xc6, 0xbe, 0x1a, 0xd3, 0xc5, 0xba, 0x96, 0xc7, 0x63, 0xc3, 0xed, 0x79, 0x63, 0xcf, 0x42, 0x8a, 0xe7, 0x8e, 0x7f, 0x4c, 0xff, 0x99, 0x39, 0x2d, 0xc4, 0x40, 0xa1, 0x98, 0x98, 0x08, 0x48, 0x2f, 0x6f, 0x86, 0x22, 0xfb, 0xe0, 0xc0, 0xac, 0x12, 0xd5, 0x26, 0xa1, 0x68, 0x26, 0x1e, 0x92, 0x95, 0x4c, 0x2b, 0x1a, 0x0b, 0xa7, 0xdd, 0x68, 0xd0, 0x72, 0x70, 0x14, 0x50, 0x0c, 0x94, 0x98, 0xe6, 0x99, 0x86, 0xcf, 0x9d, 0x9e, 0x24, 0x48, 0x1b, 0x34, 0x22, 0x98, 0x16, 0xc2, 0x6c, 0xbe, 0x5b, 0xae, 0x91, 0xfa, 0x9a, 0xe7, 0x0c, 0x56, 0x2d, 0x7d, 0x74, + 0x6f, 0xf3, 0x8c, 0x3d, 0x8f, 0xaf, 0x98, 0xbf, 0x7d, 0x76, 0xa1, 0x96, 0xd3, 0x4b, 0x1a, 0x7b, 0x3e, 0xba, 0xed, 0x04, 0xa0, 0x9e, 0x1e, 0x59, 0xfc, 0xc4, 0xd8, 0x89, 0x1f, 0x5e, 0xec, 0x99, 0x9b, 0x01, 0x8a, 0x64, 0x12, 0x77, 0xae, 0x43, 0xdd, 0x7c, 0xe4, 0xd5, 0x7d, 0x7b, 0x5e, 0xbb, 0xae, 0x25, 0x90, 0x1b, 0x50, 0x29, 0x06, 0xe7, 0x0c, 0xad, 0x3c, 0xc7, 0x7f, 0x7a, 0xf7, 0xdd, 0xfc, 0xa7, 0xe7, 0x56, 0xae, 0x5b, 0xd6, 0xaf, 0x12, 0x74, 0x87, 0xfe, 0xf1, 0x8f, 0xd9, 0x72, 0xf6, 0x1d, 0xc8, 0x4b, 0x70, 0x7f, 0x28, 0xd2, 0x9f, 0x89, 0x52, 0xe1, 0xa0, 0xd2, 0xc2, 0x48, 0x18, 0x5a, 0xb2, 0x06, 0x6d, 0xaf, 0x24, 0xec, 0x2a, 0xc4, 0x0a, 0x01, 0x89, 0x6b, 0x8e, 0x24, 0xb1, 0xe6, 0x50, 0x61, 0x52, 0x21, 0x66, 0x36, 0x10, 0xf2, 0x06, 0x82, 0xe1, 0x10, 0x56, 0x27, 0xfc, + 0x13, 0x15, 0x65, 0x50, 0x32, 0x97, 0xc1, 0x88, 0x6a, 0x5a, 0xa7, 0x82, 0xbb, 0x82, 0x48, 0x96, 0xdb, 0xc1, 0xf4, 0x61, 0x5e, 0x50, 0xdc, 0x33, 0x5c, 0xcf, 0xd8, 0x0f, 0x4f, 0x8c, 0x3d, 0xb1, 0x78, 0xe4, 0x69, 0x40, 0x9d, 0xe8, 0xb9, 0xa5, 0x16, 0x38, 0x1b, 0xd7, 0xcd, 0xa9, 0xe8, 0xcd, 0xb5, 0x4a, 0x03, 0xba, 0x60, 0x76, 0x4b, 0x43, 0xa3, 0x44, 0xc7, 0x69, 0x0b, 0xe7, 0x6c, 0xeb, 0x5b, 0xf1, 0xf8, 0x9e, 0x19, 0x2d, 0x7b, 0x1f, 0x1d, 0xae, 0x1a, 0x9c, 0xdb, 0xec, 0x93, 0x6a, 0xe4, 0x9a, 0x8c, 0x97, 0x97, 0xad, 0x5b, 0x79, 0x0e, 0xa8, 0xee, 0xbe, 0x1b, 0xa8, 0xce, 0xad, 0x74, 0x3b, 0xa5, 0x1a, 0x59, 0xcd, 0x96, 0xbe, 0xb8, 0x8e, 0x5b, 0xe5, 0x94, 0x33, 0x8a, 0xe5, 0x43, 0xcb, 0x07, 0x15, 0x2a, 0x48, 0x88, 0x96, 0xeb, 0x5e, 0xdb, 0xb3, 0xef, 0xd5, 0x23, 0xcd, 0x6a, + 0x47, 0xae, 0x5b, 0x22, 0x53, 0x61, 0x7e, 0x02, 0xd7, 0x25, 0xfb, 0x12, 0x9c, 0x6f, 0x4a, 0x22, 0x9b, 0x18, 0x78, 0xd2, 0x07, 0x04, 0x5b, 0x1a, 0x8e, 0x3f, 0x47, 0xdb, 0x11, 0x1c, 0x21, 0x00, 0xa7, 0xdc, 0x64, 0x18, 0x69, 0x07, 0xb2, 0x17, 0x00, 0x7a, 0x19, 0xd2, 0x38, 0xfb, 0x2f, 0xbd, 0xa0, 0x37, 0xa1, 0x57, 0x65, 0xa0, 0x4d, 0x85, 0xc9, 0x90, 0x91, 0xad, 0x8a, 0xc0, 0x69, 0xa9, 0x04, 0xca, 0x49, 0x89, 0x70, 0x6e, 0x38, 0x25, 0x85, 0x21, 0x66, 0xe9, 0xe9, 0xd7, 0xda, 0x78, 0x32, 0xfb, 0x8d, 0x7f, 0x1a, 0x34, 0xde, 0x09, 0xd8, 0x67, 0x46, 0x70, 0xde, 0xdb, 0xd8, 0x87, 0x53, 0x96, 0xd9, 0xe9, 0xe6, 0xeb, 0x5e, 0xde, 0xb3, 0xfb, 0x95, 0xeb, 0x9a, 0x2f, 0x3c, 0x43, 0x7f, 0xbc, 0xec, 0x59, 0xfe, 0x8b, 0x3b, 0xef, 0xe2, 0xbf, 0x7c, 0x76, 0xd9, 0xb4, 0x6b, 0x0b, 0xd5, + 0x1f, 0xca, 0xc6, 0x7d, 0xb5, 0xa3, 0xaa, 0x32, 0xa2, 0x5f, 0x68, 0x10, 0x07, 0x2c, 0x4b, 0x70, 0x6c, 0x33, 0x0b, 0x04, 0xd8, 0x20, 0x04, 0xa1, 0x93, 0x01, 0x75, 0x8b, 0x0c, 0x7b, 0x86, 0xdd, 0x66, 0xb5, 0x98, 0x32, 0x55, 0xf0, 0x26, 0x85, 0x1b, 0x25, 0x75, 0xfb, 0x39, 0x77, 0x72, 0xe8, 0x00, 0xfc, 0x2a, 0x4a, 0x68, 0x37, 0xe7, 0x8e, 0x51, 0xdd, 0xfc, 0xbf, 0x3b, 0xd7, 0xcd, 0x70, 0xbb, 0x9b, 0xd6, 0x75, 0x82, 0x3f, 0xf0, 0xae, 0x6d, 0xcf, 0xee, 0xab, 0x2f, 0xdf, 0xf9, 0xfc, 0x7e, 0xa0, 0x1c, 0x0b, 0x3f, 0x08, 0xfe, 0xe2, 0xae, 0x9a, 0x5f, 0x1a, 0x9f, 0x5f, 0xe3, 0xa3, 0x3f, 0xfb, 0x7a, 0xa8, 0x72, 0xdd, 0x5d, 0x83, 0x73, 0xaf, 0x5f, 0x56, 0x0e, 0x9e, 0xb9, 0x1f, 0xd5, 0xba, 0x41, 0xf5, 0x09, 0x31, 0x0e, 0x19, 0x8a, 0xc7, 0x13, 0x94, 0x85, 0xc5, 0x69, 0x61, 0xd4, + 0x13, 0xd5, 0x24, 0x27, 0x1c, 0xcd, 0x61, 0x10, 0x73, 0x63, 0x0c, 0x4d, 0x04, 0xf6, 0xf8, 0xca, 0xc5, 0x03, 0xe4, 0xbd, 0x63, 0xbf, 0x27, 0xdd, 0x63, 0x7d, 0xd4, 0xb6, 0x33, 0xe0, 0x8d, 0xfb, 0xc1, 0xcf, 0xcf, 0xa0, 0x3d, 0xc4, 0xf8, 0x27, 0x4c, 0x1b, 0xf3, 0x18, 0xe4, 0xb9, 0x45, 0x68, 0xd7, 0x33, 0x81, 0x78, 0x27, 0xc1, 0x56, 0xc5, 0xc5, 0x13, 0x90, 0x77, 0xc4, 0x04, 0x34, 0x1e, 0x41, 0x14, 0xe4, 0x45, 0xc2, 0xa8, 0xe8, 0x1a, 0xa7, 0x86, 0x77, 0xca, 0x7d, 0x52, 0x1c, 0xa5, 0x20, 0xce, 0x54, 0x21, 0x5c, 0x14, 0x68, 0x26, 0x23, 0xdd, 0xb9, 0x31, 0x10, 0x1e, 0x8d, 0x4d, 0x06, 0xee, 0x92, 0x65, 0x47, 0xfb, 0x16, 0xdf, 0xb6, 0xa6, 0x51, 0x79, 0x61, 0xac, 0x78, 0xdf, 0x8d, 0x27, 0x3a, 0x8e, 0x5d, 0x7c, 0x6a, 0xa4, 0xff, 0x91, 0xaf, 0xee, 0xd8, 0xfb, 0xbb, 0xbb, 0xfa, + 0x58, 0xb5, 0x59, 0x4b, 0xbd, 0xcf, 0xcc, 0xd8, 0xf5, 0xd8, 0xea, 0xc4, 0xd2, 0x39, 0xb5, 0x45, 0x11, 0x0d, 0x67, 0x91, 0x92, 0x4d, 0x0b, 0x6f, 0x5f, 0x55, 0x59, 0x71, 0xc5, 0xed, 0x03, 0xe4, 0xa1, 0xca, 0xae, 0x98, 0x69, 0xe8, 0x1c, 0xff, 0xa7, 0x1f, 0x1f, 0xe7, 0xf9, 0x9f, 0xae, 0xea, 0x3a, 0xf5, 0xaf, 0x5b, 0x9d, 0x61, 0x93, 0x7c, 0xff, 0x2f, 0x0e, 0xd4, 0xeb, 0x9d, 0x3e, 0xa7, 0x5e, 0xa5, 0x40, 0xeb, 0x76, 0xf1, 0xf8, 0x27, 0xf4, 0x3a, 0xe6, 0x0c, 0xd1, 0x48, 0xb4, 0x26, 0x66, 0xda, 0xa0, 0x7a, 0xab, 0x46, 0xdb, 0x21, 0xa4, 0xe3, 0x42, 0x85, 0x06, 0xf6, 0x4f, 0xc2, 0xa0, 0x75, 0x8b, 0x0c, 0x56, 0x8c, 0x04, 0x25, 0xb2, 0x8a, 0xc1, 0xaf, 0x93, 0xd6, 0x6d, 0x43, 0x1d, 0x42, 0x4b, 0xcc, 0xcb, 0x09, 0xf8, 0xdc, 0x4e, 0x8b, 0x09, 0x77, 0xd2, 0x3f, 0x61, 0x01, 0x10, + 0xeb, 0x84, 0x4f, 0x13, 0x96, 0x09, 0x35, 0xe2, 0x4b, 0x97, 0x2e, 0xbd, 0x2e, 0x77, 0xe3, 0x81, 0x9b, 0x3b, 0x06, 0x9f, 0xbe, 0xbe, 0x2b, 0xd8, 0x34, 0x92, 0xa8, 0x5f, 0x54, 0x66, 0x99, 0x71, 0xf5, 0xb9, 0x1d, 0xb7, 0xbd, 0xdf, 0x91, 0xa7, 0xb2, 0x29, 0x34, 0x05, 0xb3, 0xb7, 0xf6, 0xb6, 0xae, 0x6e, 0xf2, 0x46, 0x67, 0x6f, 0x68, 0xfa, 0xfb, 0x58, 0xd7, 0xae, 0xd9, 0xe1, 0xdc, 0xf9, 0xfb, 0xe6, 0xb6, 0xae, 0x6a, 0xcb, 0x57, 0xe3, 0xd5, 0xfb, 0x7a, 0xc5, 0xdc, 0x98, 0xb9, 0x74, 0xd5, 0xdd, 0xa3, 0x95, 0xab, 0x17, 0xcc, 0x34, 0xe9, 0xaa, 0x5a, 0xbb, 0xa3, 0xad, 0xd7, 0x8e, 0x56, 0x75, 0xcf, 0x9c, 0x63, 0x52, 0xba, 0x02, 0x2e, 0x6f, 0x79, 0x47, 0xa4, 0xb0, 0x23, 0x6e, 0xbd, 0x2d, 0xda, 0xd4, 0x9f, 0x57, 0x36, 0x38, 0x33, 0xcb, 0x19, 0xc9, 0x56, 0x4a, 0xf1, 0xfa, 0x05, + 0xe3, 0x6b, 0xf9, 0xe3, 0x74, 0x04, 0xce, 0x1d, 0x1b, 0x11, 0x4b, 0x14, 0xa8, 0x05, 0xab, 0x82, 0xe0, 0x49, 0x5b, 0x8c, 0x8c, 0xc5, 0x74, 0xaf, 0x50, 0x32, 0x48, 0x84, 0xfa, 0x5b, 0x83, 0xc3, 0x3a, 0x6d, 0x84, 0x4d, 0x9b, 0xe5, 0xe7, 0x38, 0xbc, 0x35, 0x44, 0x68, 0xc3, 0xdc, 0xd4, 0x62, 0x80, 0xf1, 0x18, 0x3c, 0x0c, 0xae, 0x26, 0x13, 0x63, 0xcf, 0x07, 0x3b, 0x77, 0xcc, 0x75, 0x17, 0xe7, 0x47, 0x4d, 0x36, 0xc8, 0x86, 0x34, 0xd9, 0xfa, 0x96, 0x7a, 0x6a, 0x1b, 0xfd, 0x2f, 0xde, 0x79, 0x66, 0xec, 0xcc, 0x9c, 0x6b, 0x87, 0x8a, 0x19, 0x99, 0x4a, 0xb6, 0xca, 0x21, 0x87, 0xea, 0xfe, 0xf2, 0x21, 0xe6, 0xc8, 0x19, 0xcc, 0x57, 0x7b, 0x21, 0x5f, 0xdd, 0x0e, 0xf9, 0x6a, 0x03, 0xb2, 0x67, 0x43, 0x95, 0x9a, 0x2d, 0x80, 0x93, 0x91, 0x31, 0xc1, 0xa5, 0xe5, 0x42, 0x70, 0x05, 0xcd, + 0x04, 0x8b, 0x86, 0x48, 0x32, 0x98, 0xd2, 0x8b, 0x45, 0xc9, 0x8d, 0x05, 0x46, 0xb2, 0x88, 0x5d, 0x28, 0x08, 0x37, 0x52, 0xfe, 0x50, 0x08, 0x9b, 0xac, 0x10, 0xfb, 0x9c, 0x40, 0xac, 0xc4, 0x84, 0xe7, 0x04, 0x14, 0x40, 0x6e, 0x32, 0x0e, 0x29, 0xdc, 0xca, 0xa6, 0x71, 0x5c, 0x21, 0x21, 0x8f, 0x29, 0xaf, 0x54, 0xe5, 0x58, 0x2d, 0xd5, 0xdd, 0x1b, 0x3a, 0x36, 0xfd, 0xf4, 0x40, 0x53, 0xeb, 0xd5, 0xcf, 0xac, 0x2d, 0x5e, 0xd0, 0x39, 0xd3, 0xf7, 0x86, 0xce, 0x0c, 0xce, 0xc5, 0x37, 0xee, 0xbf, 0xb5, 0xf7, 0x04, 0xff, 0xd5, 0xb3, 0xcb, 0x47, 0x9e, 0x07, 0xdc, 0x43, 0xc3, 0xf7, 0x35, 0x62, 0xae, 0x3b, 0x3f, 0x37, 0xd5, 0x5d, 0xfa, 0xfc, 0x80, 0x51, 0xc9, 0xa9, 0x43, 0x1e, 0x53, 0xdb, 0x0d, 0xaf, 0xec, 0xda, 0xff, 0x5f, 0x37, 0xb4, 0x2a, 0x74, 0x0e, 0x0d, 0x88, 0x9a, 0x34, 0x5b, + 0xc7, 0x7e, 0x15, 0x2c, 0x0b, 0x6a, 0x31, 0xe7, 0x3d, 0x05, 0xd4, 0x3f, 0x5b, 0xe1, 0x75, 0x05, 0x26, 0xf8, 0x2e, 0xa2, 0x88, 0x80, 0x75, 0xf3, 0x3e, 0xec, 0xdf, 0x28, 0x73, 0x07, 0x21, 0x83, 0x7a, 0x76, 0x76, 0x22, 0xac, 0xcd, 0xa0, 0xa8, 0x64, 0x50, 0xe4, 0xa0, 0x50, 0x8b, 0x6b, 0xbe, 0x20, 0xcc, 0x09, 0xc2, 0xa8, 0xcf, 0x54, 0x29, 0x15, 0xc8, 0x7b, 0x86, 0x0b, 0x83, 0x88, 0xf1, 0x91, 0xac, 0x44, 0x06, 0xdc, 0x32, 0xbc, 0x57, 0x95, 0xe1, 0xf0, 0xc8, 0x23, 0x1a, 0xd5, 0x2f, 0xa5, 0x7a, 0xf5, 0xcd, 0x32, 0x0d, 0x58, 0x0c, 0x1e, 0xbc, 0x70, 0x06, 0xfc, 0x60, 0xec, 0xc9, 0xd7, 0x8c, 0x46, 0x05, 0x78, 0x05, 0x3c, 0xa7, 0xb5, 0xbf, 0x72, 0x4a, 0xa9, 0x05, 0x2e, 0x63, 0x96, 0xee, 0x37, 0x4a, 0x05, 0x38, 0x33, 0xf6, 0x28, 0xd9, 0x4f, 0xd5, 0x64, 0x72, 0x63, 0x8b, 0xc9, + 0x93, 0x2e, 0x3c, 0x3e, 0x0f, 0x8d, 0xff, 0x9b, 0x5c, 0x0e, 0xdb, 0xc3, 0x11, 0xad, 0x42, 0x60, 0x7a, 0xa6, 0x50, 0xec, 0x5a, 0xe4, 0x7d, 0xab, 0xc9, 0x24, 0x5a, 0xfd, 0xe4, 0xe3, 0x2b, 0x05, 0x8c, 0xa9, 0x29, 0x97, 0xf6, 0xf6, 0x3e, 0xe9, 0xf7, 0xe2, 0x20, 0x14, 0x2d, 0x32, 0xcd, 0x94, 0x4f, 0x60, 0xc8, 0x3f, 0xf4, 0x56, 0x7d, 0x5b, 0xaf, 0x35, 0xa7, 0xca, 0x57, 0x3f, 0xd2, 0xe8, 0x63, 0xee, 0xb8, 0x70, 0xdb, 0xec, 0xf6, 0x19, 0x16, 0xaf, 0x41, 0x96, 0x33, 0x67, 0x9d, 0xa0, 0x7b, 0x3e, 0x86, 0x71, 0xd4, 0x1e, 0x82, 0x92, 0x67, 0x75, 0xcb, 0xa3, 0x01, 0xf8, 0x3e, 0xb9, 0x02, 0x90, 0xc0, 0x8c, 0x61, 0x0a, 0xac, 0xf8, 0x07, 0x2b, 0xfc, 0xe8, 0x15, 0x4e, 0xa3, 0x6c, 0x97, 0xa4, 0x16, 0x04, 0xfa, 0x10, 0xaf, 0x46, 0xd9, 0xd7, 0x29, 0x99, 0x94, 0x3c, 0x89, 0x13, 0x78, + 0x52, 0xb5, 0xdf, 0xc4, 0x0b, 0x50, 0xd4, 0x3b, 0x17, 0xcc, 0x0a, 0xf9, 0x5d, 0x38, 0xea, 0x1d, 0xa7, 0x68, 0x0a, 0xbb, 0xff, 0xd4, 0x44, 0x91, 0xa0, 0x5a, 0x92, 0x08, 0x8f, 0x10, 0x2f, 0x05, 0x28, 0x89, 0x70, 0x80, 0x68, 0x21, 0x38, 0xd0, 0x18, 0x28, 0x34, 0x02, 0x79, 0xf5, 0x99, 0x4d, 0x55, 0x9d, 0x4e, 0xad, 0xd4, 0xaf, 0x53, 0x67, 0x19, 0x4a, 0x7a, 0xec, 0x9f, 0x69, 0x73, 0xad, 0xae, 0x42, 0x23, 0xe9, 0xf8, 0xb1, 0xce, 0xae, 0xe7, 0x24, 0x7c, 0x81, 0xbe, 0x20, 0x77, 0xc1, 0x9b, 0x6f, 0x52, 0xb9, 0x1a, 0x17, 0x38, 0x3c, 0xd2, 0xa5, 0xc9, 0x58, 0xe9, 0x94, 0x51, 0x34, 0x9b, 0x17, 0xe1, 0xef, 0x74, 0x85, 0x94, 0x8a, 0xa0, 0x6b, 0x6c, 0x01, 0xab, 0xc8, 0x54, 0x90, 0xd9, 0x16, 0xf3, 0xd8, 0x46, 0x48, 0x7d, 0x40, 0x9c, 0x1a, 0xff, 0x44, 0xf2, 0x22, 0x5c, 0xbb, 0x05, + 0x44, 0xf7, 0x53, 0xf9, 0x6a, 0x14, 0x30, 0x89, 0x64, 0x2f, 0x32, 0x60, 0x1b, 0xa4, 0x12, 0x86, 0x42, 0x78, 0x91, 0x2c, 0xae, 0x94, 0x8d, 0x5c, 0xad, 0x42, 0x51, 0x1d, 0x4b, 0xf2, 0x04, 0x92, 0x07, 0xf8, 0xac, 0x90, 0xd9, 0x8b, 0x93, 0xbd, 0xd0, 0xd8, 0x14, 0x10, 0x05, 0x7e, 0xbd, 0x5f, 0x1f, 0x0a, 0xb8, 0x11, 0xdc, 0x32, 0x48, 0x4b, 0xd6, 0x0d, 0xa6, 0xe5, 0xec, 0xa6, 0x12, 0xb4, 0x39, 0x11, 0xce, 0x97, 0x93, 0xbc, 0xb8, 0xaa, 0x44, 0x69, 0x74, 0xb5, 0x2f, 0x5c, 0x9d, 0x68, 0x58, 0xdf, 0x57, 0x67, 0x70, 0x2d, 0x5c, 0xb3, 0xa3, 0x72, 0xdd, 0xe9, 0xf5, 0xa5, 0x6f, 0x86, 0x5b, 0x97, 0xd7, 0xd4, 0xac, 0x9e, 0x9d, 0xf3, 0x96, 0xbb, 0x76, 0x69, 0xc3, 0xc8, 0x03, 0x85, 0xcc, 0x89, 0xaf, 0x47, 0xe2, 0x8f, 0x20, 0xd4, 0x7c, 0x7f, 0xfd, 0xe2, 0xca, 0x86, 0xa1, 0x84, 0x63, + 0xc6, 0x35, 0xbf, 0xd8, 0x43, 0xdd, 0x70, 0x71, 0x4d, 0xff, 0xe1, 0x85, 0x79, 0xb9, 0x0b, 0x0f, 0x2f, 0x40, 0xdf, 0xbb, 0xae, 0x5d, 0x5c, 0x74, 0xec, 0x26, 0xea, 0x0f, 0x02, 0x8f, 0xda, 0xc8, 0xcc, 0x84, 0xfd, 0x0c, 0x10, 0x57, 0x3c, 0xa9, 0x49, 0xc3, 0x0c, 0x33, 0x42, 0xae, 0x9d, 0xea, 0x0b, 0x71, 0x49, 0xe2, 0xd6, 0xb4, 0xa7, 0xb1, 0xa7, 0x43, 0x27, 0x04, 0xa5, 0x2d, 0x4a, 0xbb, 0x00, 0xa7, 0x18, 0x41, 0x89, 0x0d, 0x5f, 0x12, 0xf0, 0x86, 0x22, 0xee, 0x94, 0xe5, 0x50, 0x28, 0x03, 0x3e, 0x11, 0x1d, 0x95, 0xe4, 0x6f, 0xc9, 0xb2, 0xe0, 0x1c, 0x33, 0x33, 0x59, 0xf6, 0xdb, 0xd3, 0xb2, 0x69, 0xd2, 0xca, 0x4f, 0x56, 0x01, 0x4f, 0x2f, 0xf3, 0xad, 0x47, 0x4c, 0x4f, 0xcb, 0xad, 0x4e, 0xb2, 0xbc, 0x47, 0x92, 0x45, 0xbf, 0x7f, 0x2d, 0xec, 0x73, 0x7a, 0xa1, 0xcc, 0xfd, 0x3b, + 0xd4, 0x91, 0xe5, 0x38, 0x4a, 0x14, 0x4a, 0x5d, 0x09, 0xa0, 0x58, 0x80, 0x23, 0x7f, 0xb0, 0x5e, 0x2c, 0x05, 0x58, 0x31, 0x16, 0x39, 0x1e, 0x54, 0x8c, 0x8d, 0x06, 0x54, 0x72, 0xd7, 0xed, 0x34, 0x84, 0x8d, 0x61, 0x85, 0x5e, 0xa1, 0x17, 0xa5, 0x2e, 0xb6, 0x51, 0x0b, 0xca, 0x71, 0xb1, 0xc8, 0xfa, 0x24, 0x69, 0x3a, 0x53, 0x8a, 0xe5, 0x81, 0x98, 0xac, 0x6d, 0xf7, 0x23, 0xa3, 0xa3, 0x8f, 0x5c, 0xd9, 0x26, 0x23, 0x95, 0x1d, 0xbb, 0x1e, 0x1a, 0x59, 0xfa, 0xd0, 0x95, 0xb3, 0x94, 0xe0, 0xef, 0x13, 0x3a, 0x53, 0x0a, 0x2b, 0x80, 0xbc, 0x66, 0xed, 0xe3, 0x3b, 0x6a, 0x6a, 0x76, 0x3c, 0xbe, 0xf6, 0xae, 0x15, 0x0f, 0x6d, 0xaa, 0xaa, 0xda, 0xf4, 0xd0, 0x0a, 0x72, 0xcf, 0x15, 0x2f, 0xf0, 0x9f, 0xde, 0x39, 0x05, 0x1e, 0x00, 0x92, 0xb7, 0x9f, 0xbf, 0x47, 0x12, 0xc3, 0xb5, 0x14, 0xcb, + 0x89, 0xd1, 0x84, 0x3c, 0x1f, 0x32, 0x66, 0x1b, 0x98, 0x48, 0xaf, 0x47, 0xd9, 0x39, 0x90, 0x55, 0x33, 0xb8, 0x37, 0x13, 0xc8, 0xba, 0x48, 0xc2, 0x2e, 0x63, 0xc5, 0xec, 0x9d, 0x4b, 0xcf, 0xaf, 0x16, 0xcf, 0x63, 0x54, 0xdd, 0xf2, 0xb2, 0x58, 0x61, 0x6e, 0xd4, 0xef, 0xb5, 0x98, 0x2e, 0x5f, 0xa1, 0x14, 0x88, 0x25, 0x69, 0x31, 0x54, 0xc2, 0xe4, 0x7a, 0x8d, 0x82, 0xd9, 0x0e, 0x1b, 0x0e, 0xc9, 0x4f, 0x87, 0xee, 0xd9, 0x58, 0x5d, 0xbd, 0xf1, 0x9e, 0xa1, 0x61, 0xe1, 0x93, 0xfa, 0xb5, 0x36, 0x58, 0x16, 0x8a, 0x2d, 0x8e, 0xd4, 0x34, 0x2c, 0x38, 0x7d, 0xfe, 0x96, 0x5b, 0xcf, 0x9f, 0xee, 0xef, 0x3f, 0x7d, 0xfe, 0xd6, 0xab, 0xde, 0x99, 0x5b, 0x7f, 0xf7, 0xd2, 0xce, 0x6d, 0xb3, 0xc3, 0x6f, 0xdd, 0xd3, 0x76, 0xfd, 0x6b, 0x7b, 0x76, 0xff, 0xf2, 0x86, 0xf6, 0xf6, 0x1b, 0x7e, 0xb9, + 0x7b, 0xcf, 0x6b, 0xd7, 0xb7, 0x51, 0xb7, 0x87, 0x1a, 0x0a, 0xed, 0x79, 0xc1, 0x85, 0x1f, 0x1d, 0x86, 0xc4, 0x38, 0xb7, 0x12, 0x32, 0x75, 0x91, 0x18, 0x9d, 0x2d, 0x0d, 0xf3, 0x0a, 0x17, 0x1d, 0x98, 0xf3, 0xf5, 0xb0, 0xa8, 0x2f, 0x33, 0x4f, 0xe0, 0x7d, 0x6c, 0x36, 0xb1, 0x2c, 0xa5, 0x2f, 0xa3, 0x0d, 0xac, 0x15, 0x25, 0xe5, 0x50, 0x34, 0xb6, 0xb1, 0x23, 0xf6, 0x28, 0x11, 0xa0, 0x13, 0xc4, 0x74, 0x26, 0x37, 0x82, 0xd5, 0x02, 0xcc, 0xb2, 0xd4, 0x45, 0x58, 0x77, 0x4e, 0xbf, 0xa6, 0x37, 0x61, 0x84, 0xec, 0x1a, 0x69, 0xcd, 0x46, 0x83, 0x4e, 0x91, 0xad, 0x8c, 0x08, 0xdb, 0x55, 0x69, 0x0a, 0xc0, 0x39, 0x00, 0x27, 0xb4, 0x56, 0xdc, 0xd8, 0x61, 0xd6, 0x34, 0x8d, 0xda, 0x7c, 0x73, 0xe7, 0xf6, 0x39, 0x59, 0xa7, 0xee, 0x7b, 0xff, 0xfd, 0x6b, 0x5e, 0x3d, 0x50, 0xdb, 0x78, 0xe8, + 0xe5, 0xfd, 0xef, 0xbf, 0x31, 0x45, 0x61, 0x26, 0x7f, 0x9c, 0xd5, 0xb6, 0xa2, 0x66, 0xdf, 0xcd, 0x63, 0x2f, 0x92, 0x7f, 0x4e, 0x6c, 0xbc, 0x6f, 0x78, 0xf8, 0xc1, 0x6d, 0x75, 0x63, 0xff, 0x9a, 0x56, 0x5f, 0x7e, 0x6c, 0x3c, 0x8b, 0xfe, 0x1f, 0xe6, 0x61, 0xd8, 0x57, 0x3f, 0xaa, 0x02, 0xed, 0x45, 0x5a, 0x05, 0x2a, 0x00, 0x48, 0xae, 0x87, 0x12, 0x1c, 0x10, 0x2c, 0x18, 0x94, 0xa4, 0x7a, 0x23, 0x26, 0x09, 0xc2, 0x7e, 0x00, 0xc2, 0x62, 0x42, 0x96, 0x16, 0x2d, 0x97, 0xa1, 0x98, 0xae, 0x0b, 0x70, 0x14, 0x27, 0x98, 0xab, 0x3e, 0x85, 0xc5, 0x6d, 0x30, 0x92, 0x4d, 0xb3, 0x77, 0xce, 0xcd, 0x3a, 0x75, 0xea, 0x17, 0xd4, 0xc0, 0x4f, 0x4b, 0x2d, 0xfc, 0x1d, 0x5c, 0x28, 0x74, 0xed, 0x9b, 0x6f, 0xfe, 0xe2, 0xda, 0x5f, 0xec, 0xab, 0xce, 0xe9, 0xdd, 0xdd, 0x19, 0xb2, 0x93, 0x1a, 0x54, + 0xcc, 0x77, 0xc7, 0xae, 0xb1, 0x7f, 0x8f, 0x99, 0xb2, 0xc1, 0x67, 0x66, 0x3d, 0x7f, 0x88, 0x79, 0xf8, 0xc3, 0xd2, 0xd1, 0x63, 0xfd, 0x2d, 0x57, 0x8d, 0xb6, 0xea, 0x4c, 0x11, 0xd8, 0xa8, 0xe6, 0xf1, 0x8f, 0xa9, 0x2f, 0xe0, 0xbc, 0xad, 0x44, 0x1a, 0xbe, 0x5d, 0xa8, 0x2e, 0x0c, 0xd9, 0x04, 0x8d, 0xd1, 0x91, 0x90, 0xb7, 0x69, 0x55, 0x5a, 0x6e, 0xa7, 0xe8, 0xc5, 0x84, 0x5a, 0x6f, 0x79, 0x69, 0xbc, 0x28, 0xa4, 0xcd, 0xf5, 0x20, 0xb6, 0xa1, 0x4d, 0x55, 0xb8, 0x99, 0xb0, 0xf1, 0x1b, 0xd2, 0xfc, 0x43, 0x6c, 0x3a, 0xa6, 0xf3, 0x96, 0x96, 0x57, 0xb6, 0xae, 0x7c, 0x60, 0x7d, 0x45, 0xf9, 0xc0, 0xf6, 0xdd, 0xdb, 0x07, 0xca, 0xaf, 0xdf, 0xd1, 0xb9, 0xb6, 0xb3, 0x58, 0x9f, 0x69, 0x96, 0x55, 0x86, 0x87, 0x36, 0xec, 0x6d, 0xd8, 0xf0, 0xc4, 0x8e, 0x9a, 0xf2, 0x81, 0x1d, 0xbb, 0x77, + 0x0c, 0x94, 0x3b, 0x2b, 0xfb, 0x2a, 0xea, 0x06, 0x1a, 0x72, 0x34, 0x9c, 0x41, 0xce, 0x18, 0x9b, 0xda, 0x6a, 0xae, 0x38, 0xd2, 0xb6, 0x68, 0xef, 0xfc, 0xf2, 0xdc, 0xec, 0x8a, 0x79, 0x89, 0x95, 0x1b, 0xb2, 0x63, 0xd9, 0x99, 0xca, 0xa5, 0x91, 0x86, 0x7c, 0x6b, 0xd5, 0x9a, 0x13, 0x0b, 0x7b, 0x76, 0xf4, 0x54, 0xe6, 0xe7, 0x54, 0x74, 0x96, 0x67, 0xb7, 0x97, 0xba, 0xdd, 0x61, 0x77, 0x26, 0xf2, 0x61, 0x66, 0xc3, 0xb9, 0xe7, 0x81, 0x73, 0xcf, 0x82, 0x62, 0x2c, 0x38, 0x14, 0x53, 0xd6, 0x4c, 0x26, 0x97, 0x16, 0x96, 0xc9, 0x50, 0x85, 0xb0, 0x98, 0xdc, 0x28, 0xaa, 0xcd, 0x28, 0xd4, 0xf3, 0x10, 0xe1, 0xa9, 0xd3, 0xa7, 0x88, 0x9b, 0xf1, 0xf0, 0x25, 0x6f, 0xfe, 0xa8, 0xb6, 0xaf, 0xd8, 0x2c, 0x17, 0xa6, 0x85, 0xae, 0x78, 0xc5, 0xed, 0x43, 0xd4, 0x3c, 0xfa, 0x7f, 0x2f, 0x18, 0xe8, + 0xff, 0xd5, 0x87, 0xab, 0xb3, 0x4c, 0x79, 0x93, 0x26, 0x02, 0x96, 0xd5, 0x0e, 0x7e, 0x23, 0x7d, 0x15, 0x7c, 0xb7, 0x89, 0xb8, 0xee, 0x49, 0x21, 0x14, 0x35, 0xc9, 0xc3, 0x91, 0x0d, 0x46, 0xd0, 0x32, 0x01, 0x98, 0x88, 0x15, 0x4d, 0xf1, 0xf0, 0x69, 0x4e, 0x2f, 0xc7, 0xa7, 0x11, 0x64, 0x83, 0x0e, 0x1f, 0x23, 0xc8, 0x45, 0x13, 0x97, 0x51, 0x18, 0x68, 0x0f, 0x81, 0x6d, 0x81, 0x94, 0xc9, 0x2b, 0xfd, 0x2c, 0xae, 0xfb, 0xc5, 0x71, 0x7a, 0xbf, 0x1b, 0xfb, 0x63, 0x70, 0x2c, 0x6a, 0x3c, 0x2d, 0x14, 0x35, 0x86, 0x76, 0x44, 0x7a, 0x37, 0xf9, 0x4c, 0xf3, 0x6c, 0x4f, 0x54, 0xa5, 0xb0, 0x48, 0xf4, 0xd1, 0x79, 0x85, 0x0b, 0xf6, 0xcd, 0x09, 0xf0, 0x71, 0x7a, 0x29, 0x5f, 0x92, 0xbd, 0xe8, 0x0a, 0x95, 0xda, 0xd4, 0xa5, 0xe5, 0xe2, 0x4b, 0xae, 0x99, 0xc3, 0x10, 0x3f, 0xfc, 0x21, 0xca, + 0x87, 0x1a, 0xff, 0x98, 0xf9, 0x18, 0xf6, 0xad, 0x1c, 0xe5, 0xfb, 0x14, 0xc8, 0xa1, 0xb4, 0x0d, 0xe9, 0x50, 0x6c, 0x51, 0xb3, 0xb0, 0x90, 0x53, 0x18, 0x28, 0x42, 0xde, 0x1d, 0xdc, 0x3c, 0x00, 0x22, 0x2f, 0x27, 0x12, 0x76, 0xda, 0xb5, 0x9c, 0x4a, 0x49, 0x94, 0x83, 0x72, 0xb4, 0xb7, 0x65, 0x92, 0x5b, 0x07, 0x0c, 0x29, 0x5c, 0x14, 0x2f, 0x74, 0xeb, 0x91, 0xa3, 0x75, 0x02, 0x5a, 0x21, 0x88, 0x70, 0x86, 0xed, 0x00, 0xa3, 0xce, 0x52, 0x68, 0xca, 0x53, 0x59, 0x37, 0x8e, 0x3d, 0x7b, 0x45, 0xd5, 0xea, 0xdb, 0xfa, 0x42, 0xb3, 0x03, 0xfc, 0xc7, 0x41, 0x37, 0xa9, 0x68, 0xbf, 0x7a, 0xa8, 0xb4, 0x63, 0xff, 0xa3, 0x8b, 0x17, 0x3f, 0xba, 0xbf, 0xbd, 0x74, 0xf0, 0xea, 0x76, 0x52, 0xe1, 0x0e, 0xc2, 0x33, 0x9d, 0xa1, 0xbe, 0x5b, 0x57, 0x57, 0x5f, 0xf1, 0xec, 0x58, 0xe6, 0xe2, 0xc7, + 0xce, 0x1f, 0x5f, 0xfd, 0x9f, 0x77, 0x6d, 0x74, 0xb1, 0x92, 0x2b, 0x9c, 0x81, 0xf3, 0xaa, 0x45, 0x47, 0x9e, 0x1a, 0xd9, 0x76, 0x76, 0x5b, 0x45, 0x05, 0xfc, 0x67, 0xe4, 0xa9, 0x23, 0x8b, 0x54, 0xe7, 0x03, 0xce, 0x2b, 0x24, 0xac, 0x6b, 0xe3, 0x5d, 0xff, 0xb9, 0xfa, 0xf8, 0xf9, 0xc7, 0x04, 0x5c, 0x8b, 0x7b, 0xc7, 0xff, 0xc5, 0xcc, 0xc3, 0x76, 0x9b, 0xf9, 0xd8, 0xd5, 0xfa, 0x64, 0x14, 0x61, 0x21, 0x34, 0x5b, 0xd1, 0x27, 0x5c, 0x1f, 0x62, 0x2e, 0xb5, 0x81, 0x10, 0x54, 0x0d, 0xbc, 0x32, 0xd2, 0x52, 0x11, 0x51, 0x48, 0x23, 0x39, 0x29, 0x25, 0x3a, 0x2d, 0xc1, 0xfc, 0xa9, 0x2c, 0xa8, 0x6b, 0x08, 0xf9, 0x76, 0x22, 0xb6, 0x13, 0x39, 0x55, 0xcd, 0xa0, 0x93, 0x75, 0xc5, 0x11, 0x8c, 0x25, 0x55, 0x93, 0x37, 0x2f, 0xf6, 0xf8, 0xfd, 0x89, 0x35, 0xb7, 0x3d, 0xf9, 0x8b, 0xa5, 0xdd, + 0xd7, 0x0f, 0x17, 0xbf, 0x61, 0x2b, 0x6a, 0xcb, 0xab, 0x39, 0x78, 0xe5, 0x9a, 0x39, 0x79, 0x0a, 0x57, 0x59, 0x8e, 0x36, 0x4b, 0xab, 0x0b, 0x48, 0xad, 0xcc, 0x41, 0x4e, 0xfb, 0xc7, 0x5f, 0xdf, 0xc8, 0x7f, 0xfe, 0xe1, 0x33, 0xfb, 0x67, 0x96, 0x6c, 0x7c, 0x7c, 0x07, 0xf5, 0x5f, 0x17, 0x73, 0x3b, 0x56, 0xcf, 0x70, 0x97, 0x2c, 0xbf, 0xe5, 0xf9, 0x0f, 0xf7, 0x14, 0xf5, 0xd7, 0x87, 0x58, 0x38, 0x79, 0x1d, 0xab, 0xf1, 0x1e, 0x68, 0x0c, 0xae, 0x8b, 0xef, 0x41, 0x3e, 0xe5, 0x24, 0x16, 0x08, 0xb3, 0x4e, 0x89, 0x24, 0xae, 0x5d, 0xc7, 0xa1, 0x18, 0x35, 0xa8, 0x49, 0xa2, 0x9f, 0x54, 0xf2, 0xa7, 0x98, 0x5d, 0xcf, 0x61, 0x33, 0x3c, 0x05, 0xc4, 0x12, 0xb2, 0x43, 0xa4, 0x60, 0x69, 0x15, 0xd6, 0xd1, 0x44, 0x69, 0x59, 0x78, 0xbc, 0xf7, 0xac, 0x47, 0x17, 0xf2, 0x08, 0xe8, 0xe8, 0x53, + 0x14, 0xc6, 0x64, 0x02, 0x11, 0x86, 0x8c, 0x8d, 0x17, 0x53, 0x77, 0x24, 0x6e, 0x1f, 0x8d, 0x77, 0xe6, 0x5a, 0xa5, 0x41, 0x9d, 0x3a, 0x62, 0xcc, 0x6f, 0x72, 0xf0, 0x47, 0xde, 0x18, 0xfb, 0xf4, 0x27, 0x36, 0x8f, 0x02, 0x94, 0x68, 0x9d, 0x0a, 0x43, 0x88, 0x7e, 0x73, 0xfd, 0xa0, 0x96, 0x5b, 0x86, 0x76, 0x07, 0xb2, 0x58, 0xc1, 0xd8, 0xcd, 0x63, 0x2f, 0x91, 0x25, 0xd4, 0x90, 0xd5, 0x30, 0xf6, 0x17, 0x7f, 0xc2, 0x95, 0x68, 0x40, 0x31, 0xa7, 0x50, 0x8b, 0xdd, 0x01, 0xe7, 0xa2, 0x01, 0xf9, 0x4a, 0x15, 0x08, 0x82, 0x11, 0x19, 0xff, 0x68, 0x1c, 0x64, 0xba, 0x18, 0x4a, 0x4f, 0x49, 0x2f, 0x21, 0x91, 0x60, 0xfd, 0x16, 0x67, 0x1a, 0x41, 0xcd, 0x96, 0x83, 0x8b, 0x01, 0xfe, 0xd5, 0xe2, 0x8d, 0x92, 0x90, 0x63, 0x94, 0x84, 0x13, 0x47, 0x89, 0x46, 0xe8, 0x2f, 0xbd, 0xc3, 0xc8, 0xff, + 0x10, 0x54, 0xf3, 0x1d, 0xfc, 0x03, 0x94, 0xfb, 0xc2, 0x4b, 0x0a, 0x19, 0x58, 0xc9, 0xbf, 0x08, 0x7e, 0x00, 0x2a, 0x2e, 0x7e, 0x45, 0x3b, 0x98, 0x36, 0xbf, 0x8b, 0xcf, 0xba, 0x8b, 0xb7, 0x9d, 0x99, 0xcb, 0x39, 0x54, 0x24, 0x71, 0x17, 0x78, 0xf6, 0x8c, 0xb0, 0xe6, 0xf3, 0xe0, 0x3e, 0xbb, 0x06, 0xeb, 0xa7, 0x55, 0x89, 0xf2, 0x5c, 0x40, 0x33, 0x41, 0xb8, 0x2c, 0xd1, 0x1e, 0x93, 0x6a, 0x46, 0xba, 0x36, 0x2d, 0xf8, 0x4d, 0x69, 0xe4, 0x37, 0x4d, 0xcf, 0xf9, 0xc6, 0xa6, 0x49, 0x38, 0x39, 0x38, 0x3d, 0x32, 0x98, 0x63, 0xaf, 0x92, 0x30, 0xe1, 0x27, 0x32, 0xe0, 0xd2, 0xed, 0x7e, 0x11, 0x90, 0x6c, 0x2a, 0x8a, 0xb6, 0x22, 0x87, 0xaf, 0x7c, 0x72, 0x7d, 0xac, 0xef, 0xb6, 0x57, 0xd6, 0xce, 0xbc, 0x66, 0xfb, 0x68, 0x78, 0xb6, 0x42, 0xcb, 0x2a, 0x75, 0xda, 0xcc, 0x58, 0xdb, 0x50, + 0xf5, 0xdc, 0xdd, 0x3d, 0xd1, 0xc0, 0xdc, 0x43, 0x83, 0x0f, 0xab, 0x38, 0xb0, 0x6d, 0x6c, 0x25, 0xf5, 0xac, 0xc2, 0x40, 0x6d, 0x61, 0x6a, 0x56, 0x1e, 0xe9, 0xdc, 0xf0, 0xf4, 0xde, 0x46, 0x47, 0xe9, 0xec, 0xd8, 0x42, 0x25, 0x2d, 0xcd, 0xd4, 0x64, 0x07, 0x2d, 0x05, 0x5d, 0x6b, 0xaa, 0x8b, 0x17, 0xd4, 0x07, 0xfb, 0x39, 0x1f, 0x07, 0xd6, 0x9e, 0xe9, 0xd0, 0x84, 0x75, 0xb0, 0x3f, 0x3d, 0xb0, 0x3f, 0xa3, 0xb8, 0x3f, 0x79, 0x89, 0x28, 0x64, 0x31, 0xa8, 0x2a, 0xd2, 0x1a, 0x82, 0x86, 0x8a, 0x37, 0xb3, 0x0a, 0xed, 0x90, 0x7b, 0x85, 0x89, 0x2e, 0xb8, 0xc5, 0x70, 0xb1, 0x68, 0x3f, 0xa7, 0x37, 0xe1, 0x0a, 0x18, 0xe9, 0xb6, 0x01, 0x01, 0xa3, 0x1d, 0x52, 0x19, 0x95, 0x18, 0xb9, 0xc4, 0x18, 0x60, 0x30, 0xd2, 0xa3, 0xe1, 0xd1, 0xed, 0xd7, 0xcc, 0x5c, 0xfb, 0xca, 0x6d, 0x7d, 0xb1, + 0xf5, 0x4f, 0xec, 0xe6, 0x57, 0x19, 0x14, 0xd4, 0xb3, 0x63, 0x2b, 0xc1, 0x36, 0x2e, 0xe3, 0xa1, 0xc1, 0x43, 0xf3, 0x02, 0xd1, 0x9e, 0x3d, 0x73, 0xab, 0x87, 0xda, 0x62, 0x99, 0x5a, 0x9d, 0x42, 0xa2, 0x51, 0xbc, 0x1e, 0x9b, 0x5d, 0xea, 0x68, 0xdc, 0xfb, 0xf4, 0x86, 0xce, 0xeb, 0x57, 0xd4, 0x30, 0xe0, 0x80, 0x2e, 0xac, 0xe9, 0x38, 0xc3, 0x5f, 0x0f, 0x5b, 0xde, 0x1f, 0xac, 0x5f, 0x50, 0x5c, 0xbd, 0xa6, 0xab, 0xc0, 0x12, 0xcc, 0xd6, 0x64, 0x4a, 0x69, 0x25, 0xec, 0x43, 0xc9, 0xf8, 0xc7, 0x74, 0x0e, 0xe6, 0x55, 0xcb, 0x12, 0x4a, 0xa8, 0x5e, 0x81, 0xdc, 0x1c, 0x3d, 0x64, 0x8e, 0x48, 0x2f, 0x0b, 0x60, 0x2c, 0x3b, 0x80, 0x8b, 0x3d, 0x0d, 0x22, 0x7b, 0x1c, 0x4e, 0xfe, 0x9d, 0xd8, 0xfc, 0xe3, 0xfd, 0x9b, 0x8f, 0x40, 0x49, 0xa0, 0xcc, 0xb2, 0xe4, 0x75, 0xb0, 0xa3, 0xec, 0xfc, + 0xa9, 0x97, 0xc1, 0xcd, 0x92, 0xbe, 0x21, 0x14, 0x0d, 0x79, 0x3d, 0x42, 0xb2, 0x3e, 0xeb, 0x75, 0x53, 0xe9, 0x65, 0xdf, 0x02, 0x97, 0x8e, 0xa2, 0x50, 0x4b, 0x8f, 0xf5, 0xe0, 0xb8, 0x4b, 0x04, 0x61, 0xcf, 0xc6, 0xaa, 0xf8, 0xa1, 0xd1, 0x70, 0xab, 0x6f, 0xef, 0x0b, 0x7b, 0x13, 0xdd, 0x37, 0x3e, 0x33, 0xfc, 0xd0, 0x27, 0xb3, 0x67, 0x2a, 0x38, 0xa9, 0x5c, 0x23, 0xd5, 0x16, 0x77, 0x8c, 0x36, 0xf4, 0xec, 0xea, 0x0c, 0xe6, 0x74, 0xed, 0x68, 0xab, 0xdc, 0x5d, 0xb1, 0xed, 0x24, 0xe7, 0xd7, 0x54, 0x1a, 0x1c, 0x5a, 0x19, 0xf5, 0x7d, 0x66, 0x46, 0x56, 0x8c, 0xbf, 0x57, 0x22, 0xa9, 0xdb, 0x7c, 0x6a, 0x60, 0xed, 0x53, 0x7b, 0x1a, 0x36, 0x8e, 0xce, 0xcb, 0x90, 0xca, 0xa4, 0x99, 0xd9, 0x61, 0x7b, 0x51, 0xf7, 0xda, 0xca, 0x9a, 0xa1, 0x7a, 0xaf, 0x46, 0xe7, 0x23, 0xd5, 0xa4, 0x52, + 0x63, 0x54, 0x8c, 0x08, 0xf3, 0x74, 0x3e, 0xe4, 0xdf, 0x7f, 0x81, 0xfc, 0xad, 0x0a, 0xd9, 0x1b, 0x0c, 0xb8, 0x76, 0x1b, 0xf2, 0x00, 0x02, 0x1a, 0xce, 0x53, 0x29, 0x21, 0x61, 0xa5, 0x92, 0x41, 0x19, 0x60, 0xd1, 0x32, 0x62, 0x89, 0x54, 0xd5, 0x83, 0x15, 0x02, 0x2c, 0x92, 0x30, 0x55, 0xab, 0x2a, 0xb5, 0x6e, 0xad, 0x3f, 0x94, 0x1d, 0xf2, 0x67, 0xca, 0x11, 0x22, 0x8b, 0x57, 0x3f, 0xd9, 0xac, 0xe0, 0x20, 0x8d, 0xd3, 0x98, 0x20, 0x62, 0x82, 0x09, 0x42, 0xfc, 0xe0, 0x0a, 0x99, 0xbf, 0xdc, 0x2d, 0x9a, 0x13, 0xbe, 0x16, 0xcd, 0x09, 0x5d, 0x47, 0x77, 0xad, 0x88, 0x56, 0xa8, 0x72, 0xac, 0xd6, 0xea, 0xae, 0x8d, 0x1d, 0x1b, 0xd3, 0xad, 0x0f, 0x6f, 0xeb, 0xcd, 0x80, 0x7d, 0xdb, 0x60, 0x02, 0xff, 0x79, 0x37, 0xe8, 0x78, 0x71, 0xb2, 0x1d, 0xc1, 0x92, 0x57, 0x97, 0x35, 0x9d, 0xd1, + 0x61, 0xc8, 0xa8, 0x5d, 0xbe, 0xd9, 0xa8, 0xbd, 0x82, 0xff, 0x54, 0xd8, 0x3f, 0x8f, 0x7f, 0x02, 0xb7, 0x83, 0x27, 0xa0, 0x5e, 0x70, 0x4a, 0x60, 0x6a, 0x7e, 0x82, 0x21, 0x48, 0x09, 0x43, 0x2e, 0x66, 0xb1, 0x4b, 0x44, 0x06, 0xa4, 0xd2, 0x64, 0x2a, 0xb8, 0x44, 0x82, 0xc3, 0xd0, 0x27, 0x25, 0x97, 0x5f, 0xf6, 0x62, 0xc8, 0x57, 0x84, 0x3b, 0x92, 0x22, 0x20, 0x8f, 0x90, 0x49, 0x19, 0xa9, 0x8c, 0x59, 0x33, 0xf9, 0x1e, 0xe2, 0xb2, 0xb7, 0x40, 0x21, 0xad, 0x8a, 0x84, 0x03, 0x5e, 0x64, 0xaf, 0xd4, 0x87, 0xdc, 0x98, 0xa8, 0xf1, 0x49, 0x25, 0x32, 0x2a, 0xb1, 0x82, 0x52, 0x88, 0x82, 0xf8, 0xa9, 0x64, 0x25, 0x64, 0x64, 0x21, 0x87, 0xda, 0x21, 0x5b, 0xd1, 0x39, 0x30, 0xe7, 0x8a, 0x84, 0xa5, 0xf7, 0xc6, 0x67, 0x06, 0x3b, 0xaf, 0x5f, 0x59, 0x35, 0xa6, 0x7d, 0xeb, 0x2d, 0xf2, 0xa3, + 0x37, 0xc1, 0xe2, 0xde, 0x9a, 0xf5, 0xdd, 0x05, 0x15, 0xcd, 0x46, 0xaf, 0xc2, 0xcd, 0x52, 0xff, 0x32, 0x97, 0x0e, 0xec, 0x6a, 0xf8, 0x01, 0xff, 0xf5, 0x0f, 0xba, 0x73, 0x96, 0xde, 0xb3, 0xe9, 0xaf, 0x63, 0x5b, 0x99, 0x13, 0x63, 0x07, 0xc8, 0x6d, 0x17, 0xa9, 0x6b, 0xf7, 0x55, 0xef, 0xfb, 0xf9, 0xc1, 0x81, 0x41, 0x79, 0x40, 0x88, 0xbd, 0x79, 0x8c, 0x7f, 0x88, 0xc9, 0x83, 0xf2, 0x41, 0x0d, 0x37, 0x87, 0xa1, 0x84, 0x3f, 0x00, 0x15, 0x27, 0x67, 0xa6, 0x1a, 0x83, 0xf9, 0x62, 0x63, 0x19, 0x26, 0x0c, 0x66, 0xf7, 0xcb, 0xc8, 0x56, 0xce, 0x12, 0x0e, 0x23, 0x66, 0xef, 0x8f, 0xfb, 0x04, 0x44, 0x2b, 0xb1, 0xfc, 0x53, 0xb2, 0xc0, 0x07, 0x4b, 0xa7, 0x12, 0xe2, 0x0f, 0x3c, 0x02, 0x54, 0x3f, 0x5a, 0x94, 0xdd, 0xb1, 0xa6, 0xa1, 0x6a, 0xc3, 0xe8, 0xc2, 0x2c, 0xaa, 0x6c, 0xf5, 0xba, + 0x4d, 0x35, 0x6b, 0x1f, 0xdb, 0x9a, 0x38, 0x70, 0x9e, 0xbf, 0xef, 0xcd, 0x37, 0x11, 0x92, 0xc7, 0x3d, 0xcd, 0x0f, 0x9c, 0xfb, 0xc3, 0xbe, 0xa6, 0x0d, 0xb3, 0xb3, 0xdd, 0x55, 0x5d, 0xb1, 0xd7, 0xca, 0x3a, 0x0b, 0x0c, 0xb3, 0xaf, 0x7e, 0x64, 0xc1, 0x7d, 0xfc, 0xef, 0xbf, 0xfa, 0x4d, 0x37, 0xff, 0x1a, 0x7b, 0x71, 0x02, 0x1b, 0xe5, 0x21, 0x8c, 0x8d, 0xe2, 0x40, 0x16, 0x22, 0x07, 0xe4, 0xf6, 0x46, 0xe4, 0xb2, 0xc7, 0x45, 0xdb, 0xc5, 0x84, 0x38, 0x91, 0xc6, 0x42, 0xb6, 0x10, 0xdc, 0xe2, 0xfb, 0x04, 0xa9, 0x4b, 0xa5, 0xa2, 0x27, 0xa6, 0xc2, 0x8d, 0x54, 0xae, 0x5a, 0xb5, 0x1e, 0xc3, 0x8d, 0xbc, 0x35, 0x05, 0x5d, 0xe4, 0xb5, 0x1a, 0x11, 0x5d, 0xe4, 0xeb, 0xff, 0x66, 0xec, 0x69, 0x78, 0x22, 0xf7, 0xe1, 0xc6, 0x90, 0xc4, 0x20, 0xe4, 0x31, 0x77, 0xc2, 0x79, 0xa5, 0x25, 0x3a, + 0x50, 0x54, 0x45, 0x7d, 0x9e, 0x4d, 0x8e, 0x3d, 0xea, 0x0c, 0x6c, 0x50, 0xd2, 0x62, 0x25, 0x8e, 0x33, 0x06, 0xbc, 0xeb, 0xd0, 0xb5, 0x97, 0x95, 0x04, 0xfd, 0xc9, 0xe0, 0xa1, 0xe4, 0x26, 0x2e, 0x28, 0xe8, 0x3f, 0x49, 0xdc, 0xb0, 0x09, 0x19, 0x90, 0xb2, 0xb0, 0xc6, 0x8b, 0x61, 0x4b, 0x25, 0x42, 0x3c, 0xbf, 0x11, 0x17, 0xe8, 0x09, 0x06, 0xe2, 0x85, 0xe4, 0x9e, 0x99, 0xdb, 0x7b, 0xf3, 0xd1, 0xe0, 0xd6, 0x6c, 0xe8, 0x2e, 0x2a, 0xe8, 0xd9, 0x4e, 0x02, 0xaf, 0xab, 0xa0, 0x6b, 0x5d, 0x75, 0xc3, 0xae, 0xc5, 0xa5, 0x33, 0xaf, 0x7d, 0x79, 0xcf, 0x9e, 0x97, 0xaf, 0x99, 0x59, 0xbe, 0x64, 0x4f, 0x23, 0x3a, 0xed, 0x0c, 0xdd, 0x62, 0xd6, 0x2a, 0xb5, 0xa6, 0xc2, 0xee, 0x0d, 0x89, 0xb2, 0x91, 0xd6, 0xa8, 0x51, 0xa7, 0xd4, 0xf9, 0x6f, 0x73, 0xd5, 0x8d, 0x34, 0x25, 0x16, 0xb7, 0x56, + 0xd8, 0x6d, 0x95, 0xad, 0x8b, 0x13, 0x4d, 0x23, 0x75, 0x2e, 0x86, 0xcc, 0x5b, 0x5d, 0xd4, 0xb2, 0x61, 0x7e, 0xa3, 0xc3, 0xd5, 0x36, 0x72, 0xf5, 0xfc, 0x81, 0x3b, 0x56, 0x55, 0x54, 0xac, 0xba, 0x63, 0xa0, 0x7b, 0xdf, 0x60, 0xb3, 0xd3, 0xd1, 0xb4, 0x70, 0x6b, 0x47, 0xc9, 0x50, 0xb1, 0xcf, 0x62, 0x6f, 0xce, 0xa9, 0xe8, 0x6b, 0x2a, 0xb3, 0xfe, 0x7f, 0xe4, 0xbd, 0x07, 0x74, 0x1c, 0xc7, 0x95, 0x2e, 0xdc, 0xd5, 0x61, 0x72, 0xce, 0x98, 0x3c, 0x98, 0x8c, 0x30, 0x18, 0x60, 0x30, 0x83, 0x0c, 0x0c, 0x40, 0xe4, 0x41, 0x20, 0x40, 0x02, 0x04, 0x88, 0xc0, 0x80, 0xc0, 0x4c, 0x82, 0x14, 0x83, 0x28, 0x06, 0x49, 0x4c, 0x22, 0x29, 0x89, 0x0a, 0xa4, 0x6c, 0x2b, 0x38, 0x48, 0xb6, 0x68, 0x99, 0x92, 0x6c, 0x4b, 0x32, 0x25, 0xdb, 0x92, 0x65, 0xcb, 0xa4, 0xed, 0xf5, 0xea, 0x39, 0xae, 0x1c, + 0x76, 0xed, 0xf5, 0xda, 0xbb, 0x7e, 0x6b, 0xaf, 0xd3, 0x93, 0xe4, 0x67, 0x3f, 0x05, 0x62, 0xf0, 0xaa, 0xaa, 0xbb, 0x67, 0x7a, 0x06, 0x41, 0xb4, 0xdf, 0xff, 0x9f, 0xf7, 0x9f, 0xf3, 0xeb, 0x1c, 0x0a, 0xe8, 0x9e, 0xaa, 0x41, 0xf7, 0xad, 0xaa, 0x5b, 0xf7, 0xde, 0xfa, 0xee, 0x77, 0xad, 0xb5, 0xdd, 0x13, 0xf5, 0x45, 0x5d, 0x0e, 0x6b, 0x80, 0xcd, 0x59, 0x79, 0x88, 0x3e, 0x0b, 0xe5, 0xe1, 0x25, 0x4e, 0xb3, 0x66, 0xab, 0xcc, 0x02, 0x9d, 0x7a, 0x29, 0x76, 0x87, 0x6d, 0x99, 0x0b, 0x12, 0xdb, 0x16, 0x85, 0xf8, 0x28, 0x33, 0xb3, 0x1d, 0x8a, 0x81, 0x48, 0xc4, 0x6e, 0xd4, 0x29, 0x36, 0x2d, 0x96, 0x65, 0x25, 0x44, 0xd5, 0xd8, 0x10, 0x33, 0x04, 0x22, 0x4e, 0x59, 0xaa, 0x2d, 0xcf, 0x9c, 0x32, 0x47, 0x70, 0x4c, 0x6c, 0x5e, 0xc2, 0xab, 0x35, 0x1a, 0x3d, 0x7e, 0x4c, 0xaa, 0xe9, 0x37, + 0xe6, 0x31, 0xf8, 0x34, 0xa2, 0xd5, 0x94, 0xdd, 0x45, 0xc5, 0x80, 0x3e, 0xeb, 0x5f, 0x7d, 0x64, 0x38, 0xba, 0xa6, 0xd4, 0x28, 0xb2, 0x2a, 0x94, 0x91, 0xc2, 0x50, 0x5d, 0x50, 0x77, 0x63, 0x56, 0x2e, 0xa5, 0x6c, 0x68, 0xc7, 0x37, 0xdd, 0xf8, 0xcd, 0xb3, 0xcf, 0xf5, 0x9d, 0xda, 0x90, 0xd0, 0x1a, 0x86, 0x2c, 0x6a, 0x95, 0x3d, 0xba, 0x2a, 0x58, 0x88, 0xf6, 0x7e, 0x7a, 0xf2, 0xd9, 0x7e, 0x9f, 0xe7, 0xfd, 0x27, 0xd1, 0x1c, 0x10, 0x41, 0x9b, 0xf1, 0x17, 0xf8, 0xac, 0xc4, 0x8f, 0x10, 0x3d, 0x52, 0x54, 0xeb, 0x0b, 0xa1, 0x19, 0x18, 0x62, 0x13, 0x02, 0xe4, 0x4d, 0x09, 0x9d, 0x27, 0xbf, 0x0f, 0x2f, 0x73, 0xbf, 0x91, 0xd3, 0x9e, 0x9e, 0x8c, 0xd9, 0xeb, 0xf7, 0xf2, 0x63, 0xde, 0x00, 0xbc, 0x38, 0x41, 0x0f, 0x15, 0xfd, 0x8a, 0xd1, 0x1b, 0xe6, 0xfd, 0x15, 0x2c, 0xff, 0x5f, 0x39, + 0xb8, 0xfd, 0x0a, 0xcf, 0x09, 0x78, 0xe5, 0xca, 0x1b, 0xe4, 0xcf, 0x7e, 0x74, 0x85, 0x7c, 0x60, 0x94, 0x67, 0xfd, 0x5b, 0xbf, 0x9e, 0xe7, 0x02, 0x5c, 0xff, 0x38, 0xb9, 0x7d, 0xfe, 0x43, 0xe8, 0x1f, 0xbb, 0xa6, 0x27, 0xd2, 0x97, 0x44, 0xf7, 0xc1, 0xf5, 0xa2, 0x23, 0x1a, 0x80, 0x92, 0xd5, 0x7e, 0xca, 0xb2, 0x08, 0x49, 0xd0, 0x76, 0x1d, 0xf2, 0x1c, 0xd0, 0xc0, 0xe0, 0x6b, 0x86, 0xbf, 0xe6, 0x38, 0x38, 0x3c, 0xb8, 0xd4, 0x15, 0x1b, 0x97, 0xe0, 0x5f, 0x61, 0x0f, 0x16, 0xbd, 0xc0, 0x73, 0x51, 0x2c, 0xd3, 0x70, 0x67, 0x7e, 0x43, 0xf7, 0xcd, 0x7e, 0x63, 0x80, 0xd5, 0xce, 0x0c, 0x0e, 0x56, 0x65, 0x4f, 0x47, 0x97, 0x6a, 0x9c, 0x2c, 0x21, 0xc4, 0x94, 0x88, 0x42, 0x07, 0x18, 0xcb, 0xb5, 0x27, 0xb2, 0xcd, 0x71, 0xb5, 0x4f, 0xb9, 0xc7, 0x1b, 0x0c, 0x05, 0x83, 0xec, 0x29, 0x24, + 0xa6, 0xa1, 0x13, 0xec, 0x52, 0x99, 0x14, 0xbd, 0xdc, 0xf8, 0x09, 0x5c, 0x71, 0x55, 0x28, 0xdf, 0x5d, 0xcb, 0x3b, 0x50, 0x66, 0xfa, 0x8e, 0xee, 0x4f, 0xef, 0xdd, 0xf2, 0xfc, 0xc9, 0x54, 0xcb, 0x2d, 0x1f, 0x1d, 0xab, 0x99, 0xdb, 0xb1, 0x31, 0x44, 0xaa, 0x91, 0xef, 0x74, 0x6a, 0x4d, 0x60, 0xfc, 0xad, 0xb3, 0xc7, 0x7e, 0xfa, 0xf1, 0xf5, 0xdb, 0x5e, 0xfc, 0xcb, 0x3d, 0x35, 0x5b, 0x36, 0xad, 0x2f, 0xa1, 0x48, 0xbb, 0xe9, 0x91, 0xee, 0x01, 0x6f, 0xa9, 0x52, 0x61, 0x15, 0xd1, 0xdd, 0x43, 0xeb, 0xba, 0xef, 0x7b, 0xe3, 0xfc, 0x89, 0x6f, 0x9e, 0x69, 0xb5, 0xd5, 0x8d, 0x25, 0xe7, 0x6d, 0x06, 0x6d, 0x62, 0xea, 0xdc, 0x9a, 0x6f, 0xed, 0xd8, 0xbb, 0xe9, 0x25, 0x20, 0xbe, 0xfc, 0x05, 0x60, 0x79, 0x6d, 0x46, 0x17, 0x6e, 0x2d, 0xdf, 0x6a, 0x73, 0xbe, 0xb7, 0x6e, 0xc3, 0x4e, 0xa5, + 0xaa, 0x80, 0x1d, 0x43, 0x1d, 0x7c, 0xb5, 0xb7, 0x70, 0x2c, 0x25, 0x88, 0x32, 0x71, 0xfc, 0x38, 0xbe, 0x20, 0x92, 0x64, 0x80, 0x5b, 0xf0, 0x85, 0x31, 0x11, 0x1f, 0x52, 0x39, 0xd8, 0x14, 0x47, 0x47, 0xdd, 0x05, 0x79, 0xc1, 0x05, 0xe9, 0xb2, 0xc1, 0x05, 0x34, 0xe1, 0x3c, 0x82, 0xf0, 0xc2, 0x9a, 0x25, 0xc2, 0x0b, 0x57, 0xae, 0x80, 0x8f, 0x09, 0x02, 0x0c, 0xbf, 0x0d, 0x75, 0xe1, 0x00, 0xc3, 0x9f, 0xe7, 0x2d, 0xa5, 0xe0, 0xed, 0x02, 0xe3, 0xfc, 0x0b, 0x64, 0xef, 0xa3, 0xe4, 0x17, 0x84, 0x21, 0x06, 0xf8, 0xdc, 0x5e, 0x68, 0x40, 0xed, 0x81, 0xcf, 0x2d, 0x21, 0xdc, 0xe8, 0xd4, 0x45, 0x29, 0x63, 0x10, 0x42, 0xa3, 0x1b, 0xee, 0x8b, 0x94, 0x54, 0x82, 0x88, 0x2f, 0x28, 0x62, 0x33, 0xb7, 0x41, 0x62, 0x9f, 0x5c, 0x2a, 0x95, 0xba, 0xa5, 0x6e, 0xad, 0x57, 0xef, 0xd1, 0xea, 0xac, + 0x70, 0x89, 0xc0, 0x31, 0xf2, 0x98, 0x59, 0x7e, 0x05, 0x3f, 0x76, 0x21, 0x3c, 0xf1, 0x18, 0x0b, 0x7d, 0xf2, 0xe0, 0x21, 0xa1, 0x62, 0x46, 0x6a, 0x2a, 0xfd, 0x9d, 0xcd, 0x5a, 0x87, 0x5c, 0x6e, 0xd3, 0x82, 0xfa, 0xf4, 0x5f, 0xde, 0x48, 0xff, 0x25, 0xfd, 0xbd, 0x7f, 0x7e, 0x1a, 0x79, 0x12, 0x77, 0x81, 0x9f, 0x9d, 0x91, 0x79, 0x6d, 0xaf, 0x3c, 0xfd, 0xcf, 0xd4, 0x3f, 0x7f, 0x21, 0xd0, 0xe2, 0xf6, 0xb4, 0xf8, 0xbf, 0x90, 0x2e, 0x9b, 0x7f, 0x87, 0x94, 0x80, 0x1f, 0x3c, 0x02, 0xc2, 0x36, 0xd3, 0x3b, 0xef, 0xe8, 0x5c, 0xe9, 0x9f, 0x3c, 0xb2, 0xb0, 0x40, 0x7c, 0x3c, 0xfd, 0x21, 0xc6, 0xcb, 0x3c, 0x46, 0x06, 0x88, 0x00, 0xae, 0x93, 0x78, 0x15, 0x0c, 0xbd, 0x07, 0x50, 0xa5, 0x50, 0xf2, 0x8b, 0xef, 0x01, 0x8c, 0x2a, 0x47, 0x7b, 0xe3, 0x63, 0xf4, 0x7f, 0xe2, 0x31, 0xa8, 0x24, + 0xee, 0x40, 0xa8, 0x0c, 0x8a, 0x70, 0x02, 0x11, 0xcd, 0x73, 0x2a, 0xe2, 0x8a, 0x8c, 0x79, 0x83, 0x81, 0xdc, 0xa2, 0x14, 0x9a, 0xef, 0x98, 0x91, 0x67, 0x4e, 0x84, 0x2b, 0x32, 0x32, 0x24, 0xca, 0x85, 0x9f, 0x5b, 0xae, 0x39, 0x91, 0x6d, 0x3d, 0x9a, 0x34, 0x23, 0x30, 0x40, 0xa1, 0x1b, 0x9d, 0x0f, 0x7f, 0xf0, 0x38, 0x2e, 0xe1, 0xdf, 0xaf, 0x1c, 0x32, 0x42, 0x7e, 0x3f, 0x9c, 0x96, 0xac, 0xdf, 0x0f, 0xe7, 0xee, 0xca, 0x03, 0xfc, 0xd9, 0x9c, 0x78, 0xc0, 0xdd, 0x39, 0x23, 0xcd, 0xee, 0xcb, 0x4f, 0x33, 0x0f, 0x30, 0x1f, 0x25, 0xc2, 0x08, 0x8d, 0xe1, 0x07, 0xb8, 0x68, 0x1e, 0x6b, 0x03, 0x4d, 0xb1, 0x43, 0x2c, 0x01, 0xd0, 0xea, 0x19, 0x47, 0x16, 0xc4, 0x74, 0x8a, 0x4d, 0x54, 0x84, 0xff, 0x0f, 0x13, 0x21, 0xbf, 0x47, 0xab, 0x0d, 0x7b, 0xbc, 0x28, 0x0a, 0xef, 0xd7, 0xa2, 0x0a, + 0xd3, 0x5a, 0xee, 0x58, 0x81, 0x62, 0x23, 0x5f, 0x5a, 0xee, 0x08, 0xa2, 0x0e, 0xa0, 0xd2, 0x27, 0x70, 0xbf, 0x8e, 0x33, 0x0f, 0x04, 0x9c, 0xb7, 0x77, 0xb1, 0x87, 0x09, 0x41, 0x99, 0xc1, 0x5a, 0x68, 0x5e, 0xf7, 0xa3, 0x1f, 0xb5, 0x07, 0xca, 0xf4, 0xe0, 0xdb, 0xa6, 0x48, 0xe1, 0x8b, 0xf3, 0x7f, 0x30, 0x04, 0x4c, 0x55, 0x61, 0xf0, 0xa9, 0x4f, 0xa7, 0x57, 0x39, 0x0a, 0xc1, 0x10, 0x3e, 0x4c, 0x88, 0x2a, 0x75, 0x72, 0x06, 0x1d, 0x27, 0xcc, 0x3f, 0xa8, 0x37, 0x93, 0x7a, 0x9b, 0x71, 0xfe, 0xb1, 0x02, 0x8f, 0x5c, 0x5f, 0x3b, 0x3f, 0x4f, 0xf3, 0x39, 0xa4, 0x4f, 0xe3, 0xbd, 0x4b, 0x8f, 0x50, 0xc0, 0x12, 0x04, 0xbd, 0xec, 0xe6, 0x7d, 0xc7, 0x29, 0x3c, 0x88, 0x6c, 0x51, 0xfa, 0xec, 0xa3, 0xa3, 0xba, 0x9f, 0xd0, 0x9c, 0x40, 0x81, 0x30, 0xee, 0xb9, 0xf3, 0x76, 0x16, 0xf8, 0x90, + 0x37, 0x76, 0x09, 0xb7, 0x92, 0x74, 0x03, 0x7a, 0x9c, 0x9c, 0x0d, 0x04, 0xfe, 0x5d, 0x68, 0x43, 0xfc, 0xe6, 0xff, 0x06, 0x56, 0xe4, 0x49, 0x8c, 0x15, 0x59, 0x3b, 0xd5, 0x38, 0xf3, 0xf9, 0x13, 0x29, 0x84, 0x15, 0x19, 0xbb, 0x6d, 0x4d, 0x4c, 0xaf, 0x35, 0x88, 0xdb, 0xd7, 0xfd, 0x49, 0x80, 0x15, 0x99, 0xff, 0x1b, 0xb1, 0x22, 0x2d, 0x70, 0x5e, 0xbf, 0x82, 0xeb, 0xfd, 0x0a, 0xce, 0xe8, 0x19, 0x7e, 0x91, 0x0b, 0xca, 0xfe, 0xf0, 0x9c, 0x39, 0xf8, 0xfc, 0x81, 0x3b, 0xa0, 0xd7, 0x52, 0xaf, 0xdc, 0x38, 0x43, 0x7e, 0x6a, 0xfe, 0x6b, 0x64, 0x12, 0x9d, 0x25, 0x3c, 0x0b, 0xfe, 0xfd, 0x59, 0x72, 0x23, 0xc1, 0x9f, 0x81, 0xd1, 0xfb, 0xe1, 0xf7, 0x62, 0xf6, 0x8d, 0x4c, 0xa6, 0x2a, 0xf6, 0x43, 0xb1, 0xee, 0xc8, 0x26, 0xdb, 0xb3, 0xd6, 0x32, 0xbd, 0x9f, 0xb5, 0x86, 0x11, 0x55, 0x2a, + 0xb4, 0x77, 0x1f, 0x44, 0xe7, 0x8b, 0x7f, 0x59, 0x78, 0x9b, 0xd6, 0x31, 0xd7, 0x09, 0x15, 0x51, 0x46, 0xbc, 0xcc, 0xae, 0x5b, 0x85, 0x5a, 0x85, 0xb2, 0x31, 0x3c, 0xf0, 0xdb, 0x70, 0x04, 0x84, 0xbf, 0xa4, 0xb3, 0x3b, 0xa1, 0x1f, 0xee, 0xe3, 0x52, 0xc0, 0x90, 0x22, 0x66, 0x13, 0x5c, 0xa7, 0x33, 0x99, 0x33, 0xbd, 0x59, 0x16, 0x0d, 0x80, 0x92, 0x6e, 0x76, 0x52, 0x7c, 0xa0, 0xee, 0x26, 0x1a, 0xa3, 0x0a, 0xe4, 0xc9, 0xf0, 0xf2, 0xed, 0x68, 0x7a, 0x3a, 0x83, 0x33, 0xc0, 0x05, 0xc7, 0x93, 0xaa, 0xb2, 0x08, 0x12, 0x55, 0x20, 0xe4, 0x0f, 0x14, 0x22, 0xc3, 0xc1, 0xcf, 0x1e, 0xcd, 0x70, 0xde, 0x64, 0x3c, 0xb7, 0xd0, 0x78, 0x50, 0x8c, 0xd7, 0x10, 0x9b, 0x5a, 0x6f, 0xf4, 0x82, 0x9e, 0xe8, 0x9e, 0xd6, 0xd3, 0x97, 0x3c, 0x4d, 0x63, 0xb5, 0x43, 0x47, 0x13, 0x4d, 0x9f, 0xd8, 0xb6, + 0xee, 0xde, 0xe9, 0x44, 0xfd, 0xbe, 0x27, 0x66, 0xcb, 0x07, 0xda, 0x6a, 0x0b, 0x64, 0x92, 0xf0, 0xed, 0x17, 0x2f, 0x5e, 0xff, 0xdd, 0xef, 0xde, 0x6d, 0xac, 0x78, 0xf1, 0x8b, 0x9b, 0x4e, 0xaf, 0xf1, 0xf7, 0xb4, 0xdd, 0xd7, 0x39, 0xd4, 0x74, 0xfc, 0xd5, 0xe3, 0x7b, 0x5f, 0x3d, 0xd7, 0xab, 0xf7, 0x55, 0x7a, 0x86, 0xc2, 0xf4, 0xfe, 0xa3, 0x6f, 0xbc, 0x71, 0x14, 0xea, 0xc0, 0x73, 0x50, 0x7e, 0x7a, 0x2c, 0xbf, 0x20, 0xf1, 0x54, 0x8e, 0xfc, 0x5c, 0xb9, 0xf2, 0x73, 0x09, 0xe5, 0x87, 0xf8, 0xd7, 0xb0, 0xe5, 0x96, 0x7d, 0xcb, 0xdd, 0x4b, 0x89, 0x6e, 0xe5, 0x76, 0x58, 0x6a, 0xbe, 0x25, 0x9b, 0x2c, 0x16, 0x98, 0x4c, 0xab, 0xf5, 0x21, 0x61, 0x61, 0xce, 0xb3, 0xe5, 0x85, 0x15, 0x60, 0x65, 0x85, 0x42, 0x14, 0x50, 0x4a, 0x6d, 0x67, 0x2e, 0x7a, 0x9a, 0xc6, 0x6b, 0x87, 0x8e, 0xc5, + 0x9b, 0x1e, 0xdf, 0x0a, 0xa5, 0x54, 0xd5, 0xb0, 0xf7, 0x93, 0xb3, 0x9b, 0x4e, 0x57, 0x4a, 0x25, 0x45, 0xc7, 0xa9, 0xf8, 0xfc, 0x85, 0xf7, 0x9a, 0x2a, 0x5e, 0xfb, 0xd2, 0xa6, 0x73, 0xeb, 0xc2, 0x3d, 0xed, 0xf7, 0x77, 0x0c, 0xaf, 0x3a, 0xf1, 0xf5, 0xe3, 0x7b, 0x5f, 0xbb, 0x30, 0xd0, 0xdd, 0x32, 0x14, 0x66, 0xae, 0x5f, 0xc0, 0xe7, 0xd7, 0xff, 0x06, 0xe5, 0x53, 0x99, 0x2b, 0x1f, 0x03, 0x52, 0x85, 0x3e, 0xf8, 0xac, 0x59, 0x39, 0xd1, 0x50, 0x4e, 0x8b, 0x6e, 0xd3, 0xf0, 0x36, 0x27, 0x2f, 0xdb, 0x07, 0x8b, 0xca, 0xb6, 0xa2, 0x94, 0x5c, 0x1f, 0x2c, 0x20, 0xa9, 0x1e, 0xcb, 0x07, 0x47, 0x6c, 0x3e, 0x50, 0x3e, 0x41, 0x7d, 0x0c, 0xec, 0x5a, 0x59, 0x3e, 0xaf, 0xdf, 0x78, 0x70, 0x79, 0xf1, 0x50, 0x7f, 0x39, 0x89, 0x8e, 0xec, 0x01, 0xb1, 0x1a, 0xca, 0xe7, 0x22, 0x73, 0x84, 0x68, + 0x02, 0x06, 0xf6, 0x3d, 0xd4, 0x71, 0x20, 0x16, 0x95, 0x40, 0x1d, 0x81, 0x98, 0xa3, 0x58, 0x02, 0xcb, 0x38, 0x90, 0x08, 0xef, 0x70, 0x32, 0x29, 0xcb, 0xcb, 0x3f, 0x40, 0x9b, 0x09, 0xde, 0x27, 0xa7, 0x32, 0xfa, 0x70, 0x77, 0x8a, 0xd7, 0x91, 0x3b, 0x71, 0x81, 0x78, 0xe7, 0xdf, 0xda, 0x8d, 0xad, 0x2b, 0x8f, 0x09, 0x92, 0xf3, 0xb3, 0x1d, 0x44, 0xb0, 0x23, 0x06, 0x62, 0x2c, 0xd9, 0x29, 0x59, 0x49, 0x88, 0xa1, 0xd7, 0x8f, 0xf2, 0x8b, 0xf2, 0xfb, 0x11, 0x12, 0xb1, 0x48, 0xb2, 0x74, 0x37, 0x64, 0x8f, 0x2a, 0x3c, 0xfe, 0x40, 0x28, 0xe0, 0x0d, 0x79, 0xe1, 0xf6, 0xe7, 0x28, 0x5e, 0x0a, 0xf2, 0x96, 0x9f, 0x70, 0x63, 0xce, 0x81, 0xdb, 0xe0, 0x74, 0x05, 0xea, 0x4c, 0xa5, 0x54, 0xab, 0xd1, 0x78, 0xe3, 0xa9, 0xaa, 0xce, 0xdd, 0x5d, 0x81, 0x50, 0xf7, 0xf6, 0xe6, 0xa6, 0xc1, 0x84, + 0x4f, 0x6f, 0x51, 0x56, 0x7b, 0xd7, 0x6d, 0xde, 0x51, 0xb5, 0xe1, 0xc9, 0xc3, 0xed, 0x4d, 0x47, 0xbe, 0xb0, 0x7f, 0xa2, 0xd7, 0x91, 0xdc, 0x92, 0x0a, 0x0f, 0x96, 0x1a, 0xc5, 0x56, 0xb9, 0x2a, 0xe2, 0x4e, 0x46, 0xa9, 0x13, 0x9d, 0x72, 0x65, 0x81, 0xa3, 0xa0, 0x72, 0xe3, 0xf9, 0xe1, 0xd1, 0xbb, 0x37, 0x94, 0xbb, 0x7c, 0x2e, 0x5d, 0x67, 0x71, 0x7d, 0x40, 0xd7, 0x7e, 0xe6, 0xda, 0xd1, 0xbd, 0x5f, 0xbb, 0xbb, 0xdf, 0xb6, 0xba, 0xf1, 0x96, 0x91, 0x38, 0xeb, 0xfd, 0x0c, 0xf5, 0x62, 0x5b, 0x74, 0x70, 0xe1, 0x6d, 0xea, 0xe7, 0xdc, 0x1c, 0xdf, 0x9e, 0x54, 0x78, 0x01, 0x41, 0xeb, 0x55, 0xac, 0xe7, 0xc0, 0x2f, 0x63, 0x8e, 0x4d, 0x6f, 0x26, 0x63, 0x92, 0xef, 0x4e, 0xf1, 0x29, 0x17, 0x6c, 0xfc, 0xd8, 0xb7, 0x64, 0x13, 0x6c, 0xfe, 0xf0, 0xa6, 0x3b, 0x0b, 0x44, 0xd0, 0x87, 0x7c, + 0x5e, 0x76, 0x15, 0xf3, 0x9c, 0xd1, 0xec, 0x29, 0xbc, 0x7e, 0x31, 0x63, 0x34, 0xc6, 0x77, 0x59, 0xb2, 0xb3, 0xf4, 0x68, 0xe2, 0xfa, 0xc1, 0xb0, 0x4c, 0xdb, 0xfc, 0xe4, 0x4e, 0x5e, 0xe1, 0x8d, 0xdf, 0x19, 0x9b, 0xbf, 0x8f, 0x8a, 0x53, 0x64, 0x66, 0xa2, 0xb6, 0xa5, 0x7f, 0x19, 0x3c, 0xd0, 0x3b, 0xd1, 0x7a, 0x12, 0xce, 0xd5, 0xaf, 0x5f, 0x18, 0xa8, 0xaf, 0x02, 0x1f, 0xbb, 0x80, 0xde, 0xb1, 0x68, 0xe1, 0xcf, 0xd4, 0xaf, 0x19, 0x54, 0x63, 0xb6, 0x95, 0x98, 0x48, 0xca, 0xe2, 0x70, 0xdf, 0x6a, 0xc5, 0x59, 0x30, 0xdc, 0x99, 0x2e, 0x02, 0xd5, 0x12, 0xc8, 0x25, 0xe5, 0xd6, 0xd4, 0x8e, 0x9c, 0xa9, 0xb7, 0xd2, 0xe7, 0x68, 0xdc, 0x93, 0xd2, 0xe6, 0xc6, 0x32, 0x9f, 0x2d, 0xcc, 0xb0, 0x75, 0x44, 0x73, 0x8e, 0x71, 0x85, 0xac, 0x67, 0x22, 0x21, 0x67, 0x9b, 0x20, 0x30, 0x0c, 0x16, + 0xaa, 0x67, 0xfb, 0xca, 0x0a, 0xdb, 0xb6, 0xb6, 0x37, 0x6e, 0xe9, 0x0e, 0x47, 0x7a, 0xa6, 0xd2, 0xbf, 0xa6, 0x2d, 0x05, 0xaa, 0xe6, 0x35, 0xe3, 0xe1, 0xb5, 0x6d, 0x89, 0xd1, 0x7d, 0x87, 0xf6, 0x8d, 0x26, 0x5a, 0x8e, 0x7e, 0x6e, 0xf7, 0xbe, 0xe7, 0x8f, 0x24, 0x9f, 0x7f, 0xa1, 0x74, 0x75, 0xbd, 0x37, 0x34, 0x7c, 0x6a, 0xdc, 0x3b, 0xb2, 0x61, 0x22, 0x48, 0xb6, 0x1b, 0x92, 0x13, 0xc7, 0xd6, 0xac, 0x3a, 0xb4, 0x3e, 0x5e, 0xbc, 0x7a, 0x7f, 0x57, 0xff, 0xe1, 0x0d, 0xa9, 0x74, 0xb1, 0x55, 0xa7, 0xd6, 0xaa, 0x63, 0xbe, 0xc2, 0xda, 0xba, 0xc6, 0x9e, 0x89, 0x5b, 0x3f, 0xbc, 0x71, 0xcb, 0xb3, 0x47, 0xda, 0x3a, 0x6f, 0x7f, 0x76, 0xe6, 0xa9, 0xdf, 0x37, 0x59, 0xab, 0x5b, 0xfa, 0x2b, 0x2a, 0x47, 0x9b, 0xfd, 0x22, 0x99, 0x4a, 0xca, 0x72, 0x86, 0x1d, 0x4c, 0x1f, 0x20, 0x1f, 0x65, + 0x5e, 0x20, 0xa2, 0xc4, 0x3f, 0x73, 0x6b, 0x18, 0x9d, 0x80, 0x8a, 0x0b, 0xa1, 0x7c, 0xcc, 0x28, 0x52, 0x8c, 0xd6, 0xb0, 0xf0, 0x0e, 0x83, 0xd7, 0x30, 0xef, 0x28, 0x62, 0x53, 0x17, 0x8b, 0x03, 0x8c, 0x22, 0x5c, 0xca, 0x2c, 0x36, 0xad, 0xa8, 0x71, 0x28, 0xa4, 0x39, 0x4a, 0xe8, 0x28, 0xae, 0xd0, 0x70, 0x7b, 0x46, 0x0d, 0x3a, 0xe1, 0x3d, 0xc4, 0xce, 0xbe, 0x71, 0xa9, 0x0e, 0x24, 0x2e, 0xf7, 0x03, 0x8d, 0x6d, 0x92, 0xc6, 0xe1, 0x38, 0xf8, 0x24, 0xcb, 0xb4, 0xc3, 0x9e, 0xa1, 0xdf, 0xe2, 0xf1, 0xfb, 0xb5, 0xc6, 0x18, 0x97, 0xf9, 0xc7, 0x55, 0x71, 0xe6, 0x21, 0x5f, 0x39, 0x9c, 0x99, 0x41, 0x14, 0xd7, 0x8e, 0x71, 0xeb, 0x91, 0xfc, 0x30, 0x50, 0x99, 0x3d, 0xfa, 0xd2, 0xee, 0x4a, 0x3b, 0x70, 0xa4, 0xff, 0x2b, 0xbc, 0xf6, 0xc4, 0x68, 0xf9, 0x6a, 0xb8, 0xc4, 0xcc, 0x2a, 0x55, + 0xc8, 0x3f, 0x30, 0xb4, 0xdd, 0xec, 0xf8, 0x58, 0xc5, 0xc4, 0xc9, 0xc1, 0xb6, 0x5a, 0xa6, 0x41, 0xa9, 0x14, 0x01, 0x53, 0xe5, 0x50, 0xd3, 0xf0, 0x85, 0x1b, 0xf7, 0xaf, 0x39, 0xbf, 0x39, 0xa1, 0xd5, 0x0f, 0xc3, 0x75, 0x35, 0xbd, 0x95, 0x3a, 0xa5, 0x64, 0xcc, 0xa6, 0x81, 0xf1, 0x75, 0x67, 0x37, 0x94, 0x1b, 0x90, 0x9e, 0x3c, 0xb7, 0xf0, 0xa6, 0x28, 0x02, 0xd7, 0x58, 0x80, 0x38, 0xce, 0xea, 0x31, 0x99, 0x19, 0x1a, 0xa4, 0x3a, 0xc0, 0xb0, 0x71, 0x14, 0x78, 0x21, 0x62, 0x2f, 0xb8, 0x13, 0x29, 0x07, 0xca, 0x30, 0x65, 0x31, 0x61, 0xb8, 0xd4, 0x0a, 0x47, 0x7e, 0x49, 0xb2, 0x87, 0x70, 0x48, 0x0c, 0xe8, 0x73, 0xe4, 0x3c, 0xcf, 0x66, 0x90, 0x63, 0xfc, 0x82, 0x43, 0x0d, 0xd9, 0x20, 0x3e, 0x1b, 0x40, 0x81, 0x3e, 0x50, 0xc0, 0xab, 0xd5, 0x07, 0xd9, 0xaa, 0x24, 0xd9, 0x6a, 0x05, + 0xd9, 0x54, 0x1f, 0x2d, 0x8f, 0x27, 0x30, 0x60, 0x69, 0x88, 0x22, 0x65, 0x1b, 0x2f, 0xcd, 0xcc, 0x5e, 0xda, 0x58, 0x36, 0xbd, 0x25, 0x75, 0xb2, 0xfa, 0xda, 0xb5, 0xe4, 0xa3, 0x33, 0x88, 0xc7, 0xfe, 0x1b, 0x9b, 0xa6, 0xa9, 0x77, 0xe6, 0x0f, 0x0f, 0x5f, 0x98, 0xa9, 0x46, 0x94, 0xf5, 0xc0, 0xb4, 0xeb, 0x70, 0x47, 0xc3, 0xbc, 0x99, 0xb9, 0x3e, 0xff, 0x42, 0xf3, 0x6a, 0x74, 0x27, 0xfd, 0xbb, 0x3f, 0x1c, 0x3c, 0x48, 0xde, 0x85, 0xec, 0x0a, 0x68, 0x7b, 0xeb, 0x69, 0x39, 0x21, 0x27, 0x3c, 0xc4, 0xaa, 0x97, 0x6c, 0x72, 0x64, 0xfc, 0xf2, 0x08, 0x1f, 0x6d, 0x26, 0xf0, 0xb8, 0x3b, 0xc5, 0x73, 0x87, 0xa0, 0xf3, 0x27, 0x6c, 0x50, 0xe1, 0xe8, 0x1a, 0x7f, 0x7f, 0xf4, 0x45, 0x8f, 0xdd, 0x1f, 0xc4, 0x21, 0x49, 0x1c, 0x22, 0xc3, 0x91, 0xf6, 0xcc, 0x76, 0xc6, 0x45, 0x23, 0xcd, 0xa0, + 0x67, 0xdb, 0x97, 0xcf, 0xaf, 0x3e, 0xb2, 0x65, 0xe4, 0x9e, 0x2a, 0x59, 0xcb, 0xf3, 0xb7, 0xf4, 0x9e, 0xde, 0x54, 0xd5, 0xb0, 0xf5, 0x9e, 0x8f, 0x3d, 0xb5, 0xfa, 0xda, 0x81, 0xb0, 0x6c, 0x6b, 0xd1, 0xae, 0xd3, 0x9f, 0x98, 0x3c, 0x70, 0xb0, 0xa7, 0xfd, 0x74, 0xef, 0x64, 0x74, 0xfc, 0xdc, 0xc8, 0xc4, 0x93, 0x17, 0x0e, 0x4f, 0xf7, 0x3b, 0xd2, 0xff, 0xc9, 0xf4, 0x06, 0x59, 0x3b, 0xf4, 0x1c, 0x5c, 0xd3, 0xf3, 0xcc, 0xd3, 0x44, 0x84, 0x38, 0xc6, 0xc5, 0xb8, 0x7c, 0x40, 0xc4, 0x28, 0x38, 0xfb, 0x07, 0x5e, 0x88, 0xb9, 0x0b, 0x2e, 0xc6, 0x95, 0x61, 0x99, 0x47, 0xe2, 0xcf, 0xa2, 0xf7, 0x66, 0x79, 0x57, 0x1d, 0x57, 0x58, 0x16, 0xc3, 0x6d, 0x10, 0xcc, 0x65, 0x48, 0x7c, 0xc4, 0xe2, 0x25, 0x1a, 0xa2, 0xe8, 0x05, 0x9c, 0xa1, 0xfe, 0xa0, 0xdf, 0x8d, 0xe7, 0x68, 0xd6, 0x2d, 0x12, + 0x71, 0x60, 0xac, 0x40, 0xc6, 0xa3, 0x5f, 0xb4, 0x47, 0x80, 0x9e, 0x88, 0xd1, 0xae, 0x06, 0x51, 0x55, 0xa1, 0xf5, 0xf9, 0xf4, 0xc3, 0x0e, 0x8f, 0xde, 0xa1, 0x02, 0xc7, 0x3f, 0x67, 0xf1, 0xaa, 0xd2, 0xdf, 0x54, 0x3b, 0x9d, 0x95, 0xd7, 0xc2, 0x6b, 0xef, 0x1c, 0xad, 0xe8, 0xcf, 0x4e, 0x5b, 0x72, 0x46, 0xa1, 0x03, 0xfd, 0x7a, 0x7d, 0xfa, 0x90, 0xdf, 0xa0, 0x2c, 0x34, 0xa4, 0x95, 0x1a, 0x23, 0xf8, 0x88, 0x59, 0x99, 0x76, 0xd1, 0x09, 0xe1, 0xcc, 0x85, 0xf2, 0x90, 0x2e, 0xfc, 0x85, 0xd6, 0x42, 0x79, 0x54, 0x12, 0x77, 0x26, 0x75, 0x85, 0x70, 0xcf, 0x8e, 0x96, 0x45, 0x4a, 0x51, 0xf0, 0x41, 0x29, 0xcd, 0x6a, 0x4c, 0x33, 0x5f, 0x74, 0x7c, 0x07, 0x5e, 0xc6, 0x38, 0xcd, 0x88, 0x23, 0x52, 0x2e, 0x59, 0xee, 0x63, 0x4c, 0x9b, 0x6c, 0xcb, 0x7e, 0x82, 0xbd, 0x2f, 0xf6, 0xe3, 0x0c, + 0x55, 0x32, 0xc2, 0x31, 0x55, 0x12, 0x31, 0x7f, 0x51, 0x84, 0xf5, 0xc3, 0x30, 0x13, 0x0f, 0x54, 0x91, 0xa8, 0x7c, 0x2f, 0xaa, 0x4f, 0x8e, 0xd2, 0x96, 0x30, 0x2b, 0x43, 0x10, 0x79, 0x93, 0xbc, 0x3e, 0x75, 0x81, 0x2a, 0xb3, 0x18, 0x25, 0xb4, 0xd2, 0xda, 0x78, 0x95, 0xaf, 0x6b, 0x55, 0xad, 0xf1, 0x29, 0x89, 0x9c, 0x66, 0x94, 0x92, 0x7f, 0x13, 0xd9, 0x0b, 0xae, 0x80, 0x73, 0x85, 0x2f, 0x87, 0xbf, 0x6c, 0x91, 0x5b, 0x7d, 0xe5, 0x85, 0x5d, 0x72, 0x7b, 0x99, 0x3f, 0xdc, 0xdf, 0x56, 0xad, 0x77, 0x7d, 0x35, 0xfc, 0x15, 0x50, 0x61, 0x6c, 0xac, 0x3d, 0xdb, 0x1f, 0xb6, 0x69, 0x4a, 0x7a, 0xea, 0xc1, 0x37, 0xcc, 0x45, 0x06, 0x43, 0x91, 0x71, 0xfe, 0xd3, 0x6a, 0x0b, 0xf5, 0xc8, 0x44, 0xeb, 0x81, 0xd6, 0x56, 0x67, 0xd8, 0x22, 0xeb, 0x52, 0xdb, 0x0c, 0x72, 0x4d, 0xa8, 0xb5, 0x72, + 0x6d, 0xfb, 0x81, 0xb6, 0x1b, 0x5f, 0xab, 0xa9, 0xc0, 0x35, 0x38, 0xa0, 0x6f, 0xfc, 0x0a, 0x2d, 0x81, 0x7b, 0x66, 0x25, 0xdc, 0x4f, 0xe4, 0x1e, 0x68, 0x22, 0x54, 0xe2, 0x44, 0xbf, 0x6c, 0x0d, 0x0e, 0xde, 0x80, 0xdb, 0x9d, 0x45, 0x11, 0xa2, 0xb5, 0xb9, 0x95, 0x61, 0x6b, 0x70, 0x2c, 0xfe, 0x78, 0x0f, 0xf7, 0x31, 0xdc, 0x26, 0xfd, 0x46, 0x5f, 0x28, 0xec, 0xcb, 0xd9, 0x26, 0xd9, 0x73, 0x82, 0xaa, 0x45, 0x25, 0x37, 0x72, 0x62, 0xe0, 0x22, 0x31, 0xa5, 0xb3, 0x6f, 0x5d, 0xfb, 0xe0, 0x7d, 0xf7, 0x3d, 0xd8, 0x39, 0x54, 0xaa, 0xba, 0x5e, 0xba, 0xf9, 0xa3, 0x99, 0xba, 0x1b, 0x33, 0x4f, 0x27, 0xab, 0x4f, 0xac, 0x49, 0xed, 0xee, 0xf2, 0x5e, 0x57, 0xba, 0x13, 0xe1, 0xa2, 0xee, 0x42, 0x4a, 0x56, 0x5c, 0x8b, 0xca, 0x6f, 0x94, 0x34, 0x77, 0xd8, 0x40, 0xfd, 0xed, 0xdb, 0x5a, 0xf9, + 0xca, 0x1b, 0x8d, 0xf5, 0x75, 0x4d, 0x08, 0xf7, 0x73, 0x43, 0x45, 0xbd, 0x8d, 0x4a, 0x8f, 0xfb, 0x1d, 0x04, 0xbf, 0xa6, 0xe1, 0xbc, 0x40, 0xef, 0x7c, 0x8d, 0x5d, 0x0a, 0x9a, 0x22, 0xb8, 0x52, 0xb2, 0x76, 0x30, 0xda, 0x2a, 0xe0, 0x2d, 0x71, 0xce, 0x2d, 0xde, 0xe7, 0xca, 0x5d, 0x2d, 0x42, 0x33, 0x97, 0x85, 0x4f, 0xd1, 0xf4, 0xd6, 0x8c, 0xb9, 0xb6, 0x72, 0xe3, 0x3d, 0x7c, 0xe3, 0x64, 0x09, 0x4a, 0xfe, 0x86, 0x0a, 0x72, 0x2e, 0x67, 0x81, 0x2d, 0xd3, 0x7c, 0x94, 0x5d, 0x6f, 0x7a, 0x64, 0x26, 0xe3, 0x9c, 0x85, 0x95, 0xcc, 0xe4, 0xa5, 0x97, 0xdc, 0x32, 0x26, 0xf3, 0xc4, 0x1d, 0xb1, 0x45, 0xeb, 0x6d, 0x69, 0xe3, 0xb9, 0xb5, 0x3e, 0x5d, 0xc3, 0xdc, 0x9d, 0xb3, 0xe0, 0xb0, 0x0e, 0x6a, 0x5d, 0x78, 0x93, 0x72, 0xc3, 0xf9, 0xd4, 0x80, 0xd0, 0x46, 0x4e, 0x68, 0x32, 0x35, 0x40, + 0x2b, 0x0c, 0x1d, 0x81, 0xa0, 0x4d, 0x01, 0x6d, 0x76, 0x34, 0x60, 0x30, 0xef, 0x2a, 0x57, 0xb3, 0x85, 0x87, 0x24, 0xc0, 0x15, 0x57, 0x53, 0x1d, 0x8f, 0x15, 0xba, 0xed, 0x56, 0xb3, 0x11, 0x1a, 0xff, 0x46, 0x2e, 0x13, 0x23, 0x0f, 0xac, 0x53, 0x06, 0x84, 0xc0, 0x1e, 0xde, 0x0e, 0x35, 0xc1, 0x01, 0x72, 0xd7, 0x7f, 0xf1, 0xc0, 0xe8, 0xf9, 0xc9, 0x8a, 0x48, 0x6a, 0xc3, 0xf4, 0x86, 0x54, 0xc4, 0x5a, 0xde, 0x51, 0x52, 0xd1, 0x99, 0x08, 0x6a, 0x2d, 0xaa, 0x92, 0xda, 0xcb, 0x3b, 0x87, 0x4e, 0x8f, 0x45, 0x23, 0xa9, 0x8d, 0x33, 0x1b, 0x53, 0x11, 0x4b, 0x45, 0x4f, 0xbc, 0xb6, 0xbf, 0x2a, 0x6c, 0xd0, 0x9a, 0x25, 0x91, 0x9d, 0x89, 0xf6, 0xe2, 0xbe, 0x1d, 0xcd, 0xc9, 0xf1, 0xd6, 0x68, 0xc0, 0x5b, 0x54, 0x19, 0xb0, 0x97, 0xfb, 0x0c, 0x26, 0xab, 0xc9, 0xd2, 0x51, 0x9d, 0x2c, 0x4a, + 0x6d, 0x6d, 0x6c, 0x1a, 0x6d, 0x8e, 0xf8, 0xbd, 0x25, 0x35, 0x61, 0x47, 0x3c, 0x5c, 0x60, 0xf3, 0xd8, 0xd5, 0xaa, 0x14, 0xaa, 0x79, 0x9e, 0xbe, 0x44, 0x5e, 0x67, 0x1a, 0xe0, 0xec, 0xb9, 0xcc, 0x8e, 0xb2, 0x4a, 0x89, 0xf3, 0x40, 0x09, 0x06, 0x4e, 0x19, 0x09, 0xe6, 0x8e, 0x16, 0xdc, 0x90, 0x52, 0xbc, 0xbe, 0x8d, 0xb0, 0x19, 0x9c, 0x22, 0x28, 0x0a, 0x11, 0x33, 0x87, 0x52, 0x33, 0x70, 0x0c, 0x67, 0x36, 0x7b, 0x5c, 0x8d, 0x4f, 0xf0, 0xb0, 0x19, 0x30, 0x27, 0x86, 0xb3, 0x22, 0x9e, 0xd3, 0x81, 0x90, 0x10, 0x52, 0x42, 0x22, 0x5d, 0xba, 0x23, 0x21, 0x95, 0xf2, 0xfd, 0xa0, 0x53, 0x5e, 0x11, 0xf5, 0xfb, 0xbd, 0x5e, 0xad, 0x36, 0x64, 0xc4, 0xc7, 0x76, 0x7e, 0x96, 0x67, 0x97, 0x85, 0x5b, 0x23, 0x3e, 0xd8, 0x3a, 0x0e, 0x32, 0x8e, 0xd7, 0x22, 0x0f, 0xf7, 0x15, 0x1a, 0x12, 0xa4, + 0xb5, 0xac, 0x3a, 0xb1, 0xae, 0xa1, 0xf0, 0x80, 0xd6, 0x72, 0xb9, 0x84, 0x32, 0xeb, 0xa6, 0x81, 0x3b, 0xfd, 0xcb, 0xda, 0x32, 0x5b, 0xd0, 0x66, 0x90, 0x0e, 0x3b, 0x9a, 0xa1, 0xad, 0xde, 0x53, 0xaa, 0x66, 0x6d, 0xf5, 0xfa, 0xb8, 0xc8, 0xe4, 0x6b, 0x9d, 0x6a, 0x22, 0x37, 0x1a, 0x8d, 0xfd, 0x66, 0x50, 0xa6, 0xd5, 0xa7, 0x9f, 0xbe, 0x30, 0xff, 0x6c, 0x7d, 0x9d, 0x4a, 0xab, 0xfa, 0x7d, 0xd3, 0x2d, 0x23, 0x95, 0x5a, 0x43, 0x7f, 0x81, 0x4a, 0x35, 0x30, 0xc8, 0xd6, 0x8b, 0x5f, 0x78, 0x93, 0xfc, 0x3a, 0xf3, 0x0c, 0xdc, 0xa3, 0x3e, 0xcc, 0xc9, 0x4e, 0x0b, 0xdd, 0x11, 0x29, 0xf4, 0x42, 0x4a, 0x00, 0x21, 0xc6, 0xb2, 0xd3, 0x42, 0xeb, 0x2c, 0x7b, 0x83, 0x93, 0x5d, 0x21, 0x52, 0x31, 0xdc, 0xa1, 0x2f, 0xb2, 0x8d, 0xb0, 0xab, 0x3d, 0x8b, 0x8e, 0x2b, 0x71, 0x69, 0x2b, 0xd6, 0x98, + 0x08, 0x43, 0x8b, 0x8b, 0xa1, 0x29, 0x66, 0x8e, 0x67, 0xf3, 0x5d, 0xb6, 0x2d, 0x0e, 0x60, 0xfa, 0x0a, 0x5d, 0x4e, 0x87, 0xbd, 0xc0, 0x6c, 0x32, 0xa8, 0x51, 0xbe, 0x69, 0x04, 0x44, 0xa4, 0x18, 0x04, 0x14, 0xe0, 0x24, 0xd5, 0x94, 0x05, 0x8b, 0xb0, 0x0b, 0x87, 0xb3, 0xba, 0x84, 0xcc, 0x79, 0xe0, 0x5b, 0x47, 0x4e, 0x1f, 0xd4, 0x9a, 0x2f, 0x7b, 0x75, 0x27, 0x76, 0x96, 0x75, 0x06, 0xd5, 0xe8, 0xac, 0xc6, 0xef, 0x6d, 0xac, 0x46, 0xf2, 0xaa, 0x8b, 0xd8, 0x82, 0x76, 0x83, 0x44, 0x26, 0x17, 0xd5, 0x46, 0x4f, 0x69, 0xce, 0xdf, 0xc1, 0x4a, 0xe8, 0xe7, 0x07, 0xe7, 0x34, 0x9a, 0x7e, 0xb8, 0x64, 0xd6, 0x0c, 0x5f, 0xe4, 0xe5, 0x24, 0x95, 0x4d, 0x96, 0x44, 0xb1, 0x7c, 0x34, 0x50, 0x37, 0x5d, 0x86, 0xeb, 0xc7, 0x47, 0x7c, 0x84, 0xe3, 0xad, 0x93, 0xc0, 0x1d, 0xc7, 0x63, 0xc1, 0x58, + 0x26, 0x4e, 0xa9, 0xa0, 0x5b, 0x34, 0x7f, 0x0b, 0x5d, 0x91, 0xfc, 0x15, 0x5f, 0x53, 0x41, 0xc7, 0x1e, 0x54, 0xd1, 0x3c, 0xc5, 0xf8, 0xd6, 0x8c, 0x5d, 0x8a, 0x10, 0x42, 0xb3, 0xd9, 0x73, 0xac, 0xcc, 0xa7, 0xc8, 0x33, 0x40, 0x89, 0xa3, 0x98, 0x7b, 0x7c, 0x36, 0x73, 0x78, 0xc5, 0x7f, 0x3e, 0x3a, 0xfa, 0x52, 0xa1, 0xdf, 0xe3, 0xcf, 0x9e, 0x57, 0x0a, 0xa4, 0x22, 0x66, 0xb9, 0x7f, 0x33, 0x20, 0x9a, 0x46, 0x50, 0x45, 0xbe, 0xa7, 0x2a, 0x6d, 0x19, 0xa9, 0xc9, 0x0a, 0xc4, 0x1d, 0xd7, 0xa7, 0x9d, 0x52, 0xa7, 0xe3, 0x81, 0x2f, 0xa7, 0x37, 0x5c, 0x29, 0xb0, 0x89, 0x00, 0x29, 0x55, 0x89, 0x24, 0x1a, 0x09, 0xb5, 0x3a, 0xb0, 0xaa, 0xc2, 0xa1, 0xd6, 0x22, 0x79, 0xd8, 0x4b, 0xde, 0x7f, 0xdc, 0x64, 0x01, 0x0f, 0x51, 0x5f, 0xb4, 0xa8, 0xe7, 0x3f, 0x66, 0x8e, 0x5a, 0x2c, 0x51, 0x0b, + 0x96, 0x49, 0x1d, 0xd4, 0x7f, 0xab, 0xa9, 0x77, 0x08, 0x33, 0xc2, 0xd3, 0x28, 0x96, 0xc1, 0xd3, 0xcc, 0x2e, 0x81, 0xa7, 0x31, 0xb0, 0x78, 0x1a, 0xb8, 0x99, 0xc4, 0x8c, 0x1e, 0x8a, 0x03, 0xd4, 0xe8, 0xbd, 0x71, 0x8f, 0x1e, 0x79, 0x64, 0xab, 0xd5, 0x8a, 0x6f, 0x7c, 0x2b, 0xfd, 0xc3, 0xff, 0x22, 0x9d, 0x37, 0xe6, 0x55, 0xca, 0xdf, 0xfd, 0x1a, 0x74, 0xff, 0xc3, 0xfc, 0x1f, 0xc8, 0xbf, 0x52, 0x97, 0x8d, 0x21, 0x55, 0xfa, 0xfd, 0xb3, 0xe9, 0xd7, 0x1f, 0x1e, 0x54, 0x05, 0xb5, 0x20, 0x71, 0x16, 0x48, 0x1f, 0x66, 0xe3, 0x87, 0xe9, 0x0f, 0x53, 0xe7, 0xe0, 0x73, 0x94, 0x22, 0x5c, 0xa2, 0x15, 0x65, 0x61, 0x77, 0x43, 0xdd, 0x86, 0xd0, 0x33, 0x73, 0xd0, 0x9d, 0x26, 0x69, 0x11, 0x4a, 0x0f, 0x66, 0xf3, 0x7c, 0xb8, 0x3a, 0x45, 0x04, 0xa6, 0xfa, 0xb1, 0x40, 0x37, 0xda, 0xa7, 0xc5, + 0x05, 0x3b, 0xf5, 0x28, 0xa0, 0x63, 0xca, 0x85, 0x9c, 0xe4, 0xfa, 0xd1, 0x08, 0x9a, 0x12, 0x3f, 0x4a, 0xfe, 0x34, 0xad, 0x03, 0x3d, 0xd6, 0x17, 0xba, 0xb6, 0xb7, 0xba, 0xc3, 0xbd, 0xbb, 0x5b, 0xeb, 0xfa, 0x6b, 0xc2, 0x06, 0x8f, 0xb6, 0xd4, 0xd9, 0x31, 0x34, 0xd3, 0x70, 0xe8, 0x8b, 0x47, 0x93, 0xe5, 0xb3, 0x8f, 0x6e, 0x4f, 0x5f, 0xd7, 0x98, 0xa8, 0x77, 0x1e, 0x4e, 0x6f, 0x35, 0x7a, 0x9a, 0x02, 0x6d, 0x9b, 0x6a, 0x5b, 0x77, 0xa6, 0x82, 0x05, 0xae, 0x02, 0x47, 0xca, 0x16, 0xf1, 0xe8, 0xda, 0x6f, 0xfb, 0xd4, 0xe4, 0xea, 0xbb, 0xa6, 0x1a, 0x44, 0xe0, 0x36, 0x6d, 0x31, 0x5b, 0x63, 0xc2, 0x9b, 0x7e, 0x88, 0xf2, 0xc2, 0xe7, 0xaf, 0x24, 0xca, 0x92, 0x25, 0x0e, 0x44, 0x22, 0x81, 0x8e, 0xce, 0x51, 0x54, 0x60, 0x8a, 0x3f, 0xc1, 0x9c, 0xcd, 0x6c, 0x3b, 0x73, 0x64, 0x8f, 0xd6, + 0x50, 0x54, 0x14, 0x31, 0x70, 0x94, 0x2f, 0x62, 0x06, 0xf9, 0x79, 0x22, 0xb1, 0x80, 0x84, 0x9d, 0x8b, 0xe7, 0xb3, 0x74, 0xab, 0x00, 0x07, 0xfd, 0x39, 0x66, 0x64, 0x0a, 0x98, 0xb4, 0xe9, 0xf7, 0x46, 0x6f, 0x1f, 0x08, 0x7c, 0xeb, 0xdb, 0xb5, 0xdd, 0xc5, 0x9a, 0xee, 0x4e, 0xb3, 0xc7, 0xa2, 0x15, 0x29, 0xac, 0x62, 0x73, 0xe9, 0x40, 0xd5, 0x43, 0x9f, 0x7a, 0xec, 0x89, 0xb3, 0xb7, 0xed, 0xd8, 0xf3, 0x29, 0x2b, 0xf0, 0xa5, 0x45, 0xe4, 0x4f, 0xa9, 0xcb, 0xd6, 0x88, 0xea, 0x4f, 0xa2, 0x48, 0xf7, 0x74, 0xc3, 0x03, 0x9f, 0x33, 0x8b, 0x0c, 0x36, 0x97, 0x2e, 0x5c, 0x41, 0x8b, 0x68, 0x95, 0x65, 0xd4, 0xa0, 0x3d, 0x7a, 0xcb, 0xae, 0xbb, 0xb4, 0xca, 0x8d, 0xbd, 0x5d, 0x6b, 0x1b, 0x0b, 0x0d, 0x40, 0xfe, 0x30, 0x7c, 0x8f, 0x9a, 0x85, 0xbf, 0x50, 0x65, 0xf0, 0x3d, 0x12, 0xc4, 0x81, + 0xa4, 0x2a, 0x08, 0xfd, 0xac, 0x00, 0x5c, 0x01, 0x1a, 0xcc, 0xd2, 0xcf, 0x6e, 0xd1, 0x81, 0x4c, 0x6d, 0x15, 0x02, 0x21, 0x04, 0xe8, 0x29, 0x94, 0x19, 0x96, 0x47, 0xa3, 0x8f, 0x31, 0x11, 0x37, 0xd3, 0x10, 0xf1, 0xed, 0x27, 0xe5, 0x5e, 0xfd, 0xda, 0x80, 0xdf, 0xcf, 0x9d, 0xce, 0x2d, 0x81, 0xa3, 0xc9, 0x04, 0xb6, 0x03, 0x02, 0x14, 0x0d, 0x82, 0x94, 0x20, 0xbb, 0xcf, 0x03, 0xfa, 0xec, 0x55, 0xf6, 0x89, 0xf3, 0x13, 0x91, 0xe4, 0xce, 0xfb, 0xfa, 0xb7, 0x7d, 0xa5, 0xbd, 0x46, 0xa6, 0x91, 0x68, 0xd5, 0x8a, 0x92, 0x86, 0xde, 0x48, 0xd5, 0x40, 0xdc, 0x7a, 0x68, 0x57, 0x68, 0x24, 0x70, 0xcb, 0x01, 0x99, 0x5e, 0xee, 0x0d, 0x7b, 0xc9, 0x17, 0x2b, 0x6a, 0xd3, 0x6f, 0x91, 0x07, 0x69, 0xba, 0xb4, 0x7f, 0x4f, 0x0b, 0xdc, 0x8c, 0x23, 0x03, 0xc3, 0x8d, 0x0a, 0xb1, 0x5a, 0xee, 0x76, + 0x1a, 0x5d, 0x95, 0x6d, 0x81, 0xa1, 0x49, 0xa5, 0xd2, 0x0d, 0x1f, 0x3f, 0x64, 0x1c, 0x6d, 0x0d, 0x60, 0x1b, 0xae, 0x7b, 0xe1, 0x6d, 0xea, 0x0d, 0xa8, 0x33, 0x24, 0x70, 0x66, 0xee, 0xe6, 0x25, 0xb0, 0x5c, 0xa2, 0xd8, 0x6e, 0x61, 0xa2, 0x98, 0xed, 0xa6, 0x1a, 0xe2, 0x88, 0x07, 0xf4, 0xfe, 0x4a, 0x8a, 0x82, 0x7e, 0x8f, 0xab, 0xc0, 0xac, 0x56, 0xc2, 0x3f, 0x25, 0xc1, 0xf9, 0x56, 0x7a, 0x2e, 0xea, 0xc1, 0x63, 0x18, 0x32, 0x56, 0x5d, 0x82, 0x4b, 0x28, 0x83, 0x97, 0xc1, 0x77, 0x3d, 0xc9, 0x89, 0xba, 0x23, 0x17, 0x74, 0xf3, 0x29, 0xdf, 0xe4, 0xf6, 0xbd, 0x55, 0xdb, 0x5e, 0x38, 0xd9, 0xd5, 0x72, 0xe4, 0xb9, 0xb9, 0x89, 0x8f, 0xee, 0x4b, 0xea, 0x6d, 0xe4, 0x23, 0x54, 0x64, 0x60, 0xae, 0xad, 0x79, 0xb2, 0x2d, 0xaa, 0xb3, 0x28, 0xc1, 0xbd, 0xb5, 0x9b, 0x3b, 0x43, 0x17, 0x8e, + 0x83, 0x9f, 0x17, 0x35, 0x04, 0x74, 0x1d, 0xe7, 0x5f, 0x3f, 0xb5, 0xed, 0x8b, 0x77, 0xf5, 0x35, 0xec, 0xff, 0xcc, 0xae, 0x88, 0x6b, 0xec, 0xec, 0x68, 0x89, 0xd3, 0xeb, 0x44, 0x09, 0xd6, 0x34, 0x41, 0xa4, 0x2f, 0x41, 0x53, 0x08, 0xee, 0x87, 0x84, 0x9e, 0xf0, 0x12, 0x9b, 0x81, 0x3c, 0xa9, 0xee, 0x86, 0x96, 0xc5, 0x50, 0x1d, 0xa9, 0x60, 0xe2, 0x80, 0x54, 0x52, 0x9c, 0x8f, 0x56, 0x03, 0xdd, 0x6b, 0x85, 0x92, 0x51, 0x4c, 0x11, 0x4a, 0xb8, 0x99, 0x2a, 0xc9, 0x29, 0x95, 0x54, 0x43, 0x51, 0x12, 0x91, 0x1a, 0x4d, 0x7b, 0xb0, 0x99, 0x90, 0xcb, 0x67, 0x53, 0x70, 0xc3, 0xa1, 0x65, 0x63, 0x84, 0x4c, 0x86, 0x5f, 0x39, 0xb3, 0xfd, 0xa6, 0x3e, 0x1f, 0x82, 0x5f, 0x50, 0x4f, 0xc8, 0x65, 0xb4, 0x4c, 0x4e, 0xcf, 0xad, 0xfc, 0x45, 0xcb, 0x7c, 0x47, 0xb2, 0x65, 0xa9, 0xee, 0x52, 0xd8, + 0x1d, 0xce, 0x57, 0x35, 0x40, 0x9c, 0x37, 0x9b, 0x55, 0x40, 0x2a, 0x01, 0x22, 0x42, 0x2a, 0xda, 0xb4, 0xf4, 0x97, 0x40, 0x73, 0xaf, 0xc4, 0x68, 0x90, 0x4a, 0x09, 0x62, 0xf3, 0xa6, 0xf1, 0xb1, 0x35, 0x03, 0xed, 0x6d, 0xad, 0x2d, 0x15, 0xe5, 0xd1, 0xb2, 0xe2, 0xb0, 0xdf, 0xe7, 0xb0, 0x19, 0xbc, 0x46, 0xaf, 0x54, 0x2f, 0xd5, 0x73, 0x83, 0xa2, 0x43, 0xcc, 0x68, 0x68, 0x2e, 0x26, 0x9a, 0x40, 0x84, 0x41, 0x9e, 0x06, 0x28, 0x14, 0x65, 0x21, 0xac, 0x01, 0xe0, 0xe5, 0x32, 0x49, 0xbc, 0xf1, 0x18, 0x54, 0xd4, 0x98, 0xa9, 0xb8, 0x8a, 0x83, 0x15, 0x57, 0x98, 0x3d, 0x88, 0xbd, 0xd8, 0x09, 0xb2, 0xfc, 0x62, 0xb0, 0xa7, 0xc8, 0x1c, 0x03, 0x06, 0x71, 0xa8, 0x75, 0x53, 0xf2, 0xfc, 0x03, 0x0a, 0x92, 0x0a, 0xb5, 0x0c, 0x6f, 0x9c, 0x29, 0xab, 0xe8, 0x92, 0x03, 0x66, 0xcb, 0x0e, 0x5b, + 0xbc, 0x3f, 0x56, 0xd9, 0x1f, 0xb7, 0xed, 0x00, 0xbf, 0xb9, 0xd5, 0x38, 0x78, 0xe0, 0x43, 0xeb, 0xb7, 0x7e, 0x6c, 0x77, 0x93, 0x64, 0xdb, 0x71, 0xf3, 0x9a, 0xda, 0x8a, 0x86, 0xe6, 0xe6, 0xd4, 0x70, 0xed, 0x96, 0xfb, 0x86, 0x4e, 0x6d, 0x00, 0xd6, 0xd2, 0x5d, 0x2a, 0x4d, 0xed, 0xec, 0xbd, 0x6b, 0xbd, 0xcd, 0x4e, 0xa7, 0xb3, 0x7a, 0xfc, 0xe0, 0xb1, 0xc3, 0x53, 0xcd, 0xa2, 0xa9, 0x03, 0x88, 0x60, 0xba, 0xb9, 0x76, 0xbb, 0xcd, 0x6b, 0x90, 0x8b, 0xc4, 0x22, 0x43, 0x8d, 0x97, 0x3a, 0xb7, 0x6d, 0xa4, 0xb8, 0x23, 0xe6, 0xb0, 0xc7, 0x3a, 0x8b, 0x47, 0xb6, 0xdd, 0x00, 0xd1, 0xa1, 0x66, 0xbf, 0xbf, 0x79, 0x28, 0x7a, 0xa2, 0xa8, 0xd9, 0xa0, 0x6f, 0xa9, 0x2a, 0x5d, 0xe7, 0x2e, 0xba, 0x7b, 0x22, 0x75, 0xeb, 0x70, 0xa4, 0xaa, 0xcb, 0x27, 0xd5, 0xb8, 0xda, 0x02, 0xa9, 0xc3, 0xc3, + 0x11, 0x89, 0x44, 0x53, 0x68, 0x74, 0x38, 0x54, 0x22, 0x65, 0x41, 0x7c, 0x98, 0xe5, 0x6e, 0x5e, 0xf8, 0x5d, 0xfa, 0x12, 0x55, 0x01, 0x75, 0x44, 0x04, 0xe9, 0x6a, 0x35, 0xc7, 0x9f, 0x91, 0xbb, 0xd6, 0x17, 0x97, 0xd6, 0x98, 0x43, 0x4c, 0xef, 0x21, 0x6f, 0x30, 0xc4, 0x16, 0x57, 0xce, 0x47, 0x3c, 0x66, 0x71, 0xc0, 0x19, 0x45, 0x87, 0x79, 0xa5, 0x3f, 0xb4, 0xfd, 0xd1, 0xd9, 0xf2, 0xe4, 0xb1, 0x97, 0x0e, 0x35, 0xcc, 0x0e, 0x75, 0x38, 0x23, 0x12, 0x8b, 0xce, 0x10, 0xae, 0x5e, 0x5d, 0xd7, 0xba, 0xbb, 0x37, 0xec, 0x6e, 0xdb, 0xde, 0xf5, 0xbc, 0x15, 0xa4, 0xd2, 0x7a, 0xf2, 0xa7, 0x26, 0x0d, 0xf8, 0xb1, 0xa8, 0x61, 0xea, 0xae, 0xd5, 0x93, 0x4f, 0x1e, 0xe9, 0xd0, 0x79, 0x22, 0xb6, 0x94, 0x4a, 0x0d, 0xd5, 0x75, 0xa8, 0x67, 0x57, 0x6b, 0xed, 0xa6, 0xb6, 0x40, 0x93, 0x07, 0xfa, + 0xb1, 0x0f, 0x0f, 0x5a, 0x8b, 0xb5, 0x28, 0x07, 0x65, 0x30, 0xfd, 0x61, 0x66, 0x17, 0x73, 0x1d, 0xd7, 0xa1, 0xef, 0x25, 0x7e, 0x92, 0xb4, 0xa6, 0x00, 0x29, 0xae, 0xb3, 0x2a, 0xa0, 0x11, 0xe4, 0x00, 0x22, 0xba, 0x4a, 0x0a, 0xf7, 0xbe, 0xca, 0x52, 0x92, 0x21, 0x98, 0x6e, 0x3e, 0x0c, 0x2d, 0x91, 0x64, 0xeb, 0xde, 0xed, 0xce, 0x54, 0x5b, 0xd9, 0x99, 0xc5, 0xc8, 0x41, 0xeb, 0x92, 0xcb, 0x59, 0x81, 0x9b, 0x12, 0x9c, 0xde, 0xe6, 0x15, 0xba, 0xed, 0x59, 0xae, 0x5b, 0xb2, 0x14, 0xf1, 0xc1, 0x20, 0x42, 0xbb, 0x65, 0x7b, 0x0a, 0xdb, 0xc3, 0xc9, 0x5b, 0x00, 0x88, 0xde, 0x9e, 0xae, 0x8e, 0xa6, 0x86, 0xe2, 0xb0, 0xc7, 0x85, 0x28, 0x9c, 0x58, 0x92, 0x3b, 0x19, 0x66, 0x4e, 0xe5, 0x88, 0xed, 0xc4, 0xd0, 0x55, 0xc8, 0x09, 0xa3, 0x9a, 0x0d, 0xbc, 0x4b, 0x68, 0xe2, 0xe7, 0xa2, 0x18, + 0xd5, 0x8b, 0xc7, 0x73, 0x76, 0x51, 0x80, 0x35, 0x46, 0x9f, 0x8c, 0x55, 0x45, 0xe3, 0xe1, 0x76, 0xb7, 0xa9, 0x2d, 0x7d, 0x3d, 0x27, 0xd2, 0x4a, 0x85, 0x47, 0x62, 0x89, 0x81, 0x84, 0x1d, 0x58, 0x6b, 0xc7, 0x9b, 0x7b, 0x77, 0x04, 0xe4, 0x7a, 0x99, 0xda, 0x63, 0xad, 0xb7, 0x26, 0x1c, 0x6a, 0xb3, 0x5c, 0xde, 0xfc, 0x29, 0x61, 0x0c, 0xf6, 0xfa, 0x7f, 0x45, 0x03, 0x9e, 0xb8, 0xcf, 0x55, 0x55, 0x41, 0xfd, 0x3e, 0x1b, 0x89, 0xfd, 0x41, 0xc8, 0x63, 0x77, 0xf9, 0x6a, 0xbb, 0x83, 0xc1, 0xde, 0x86, 0x40, 0xa2, 0x42, 0x17, 0x30, 0x98, 0xa5, 0xba, 0x70, 0xf9, 0x68, 0x30, 0x3c, 0x11, 0xdb, 0xd0, 0x3b, 0x9e, 0x0d, 0xd1, 0x92, 0x25, 0x2c, 0x26, 0xa2, 0x0b, 0xfa, 0x40, 0xff, 0xfe, 0x7f, 0x11, 0x7b, 0x65, 0x8c, 0x6f, 0xe8, 0x28, 0x8a, 0xf4, 0x6d, 0xa9, 0x2e, 0x5f, 0xd7, 0x1c, 0x28, + 0xea, 0xd8, 0x00, 0xbe, 0x60, 0xb5, 0x44, 0xfb, 0xb7, 0xd5, 0x34, 0xec, 0x1b, 0x8d, 0x27, 0x6f, 0x7d, 0x7a, 0xfb, 0x8e, 0x2b, 0x87, 0x92, 0xf1, 0x75, 0x7b, 0x1b, 0x6a, 0xb6, 0xf4, 0x97, 0x59, 0x1c, 0xf7, 0x9a, 0x75, 0x12, 0xa5, 0x26, 0xdc, 0x3e, 0x51, 0x19, 0x19, 0x6c, 0xf0, 0xaa, 0x55, 0x12, 0x9d, 0xef, 0xac, 0x2b, 0xb9, 0xa9, 0xb9, 0x73, 0xd7, 0x50, 0xab, 0xcb, 0xd1, 0xba, 0x76, 0x47, 0x6b, 0xdb, 0x74, 0xb3, 0x9b, 0x31, 0x44, 0xf6, 0x56, 0xa6, 0xf6, 0x8d, 0xb4, 0xb9, 0xdc, 0x3d, 0xb3, 0x67, 0xc7, 0x37, 0x7f, 0x74, 0x4f, 0x7d, 0xfd, 0xce, 0x8f, 0x4c, 0xac, 0x3f, 0x35, 0xdd, 0xed, 0x76, 0x77, 0x4d, 0xde, 0x36, 0x90, 0x98, 0x49, 0xf8, 0xac, 0x8e, 0xce, 0x40, 0xd3, 0xc6, 0x9e, 0x7a, 0xbb, 0xad, 0xbe, 0x67, 0x63, 0x63, 0xa0, 0xcb, 0x6e, 0x43, 0x1c, 0xb1, 0x44, 0x75, + 0xfa, 0x22, 0x35, 0x03, 0xd7, 0x62, 0x21, 0x8a, 0x19, 0xa2, 0x28, 0xaa, 0xcc, 0x08, 0x28, 0x91, 0x84, 0xc7, 0x5e, 0x71, 0x17, 0x64, 0x06, 0xd7, 0xed, 0x16, 0x58, 0x23, 0xcb, 0x40, 0xaf, 0x82, 0xf9, 0xd0, 0x2b, 0x61, 0xd3, 0x1c, 0xe4, 0x95, 0xcc, 0xa8, 0x35, 0xea, 0xa1, 0xe5, 0x8a, 0x56, 0x34, 0x93, 0xef, 0xe7, 0xe4, 0x40, 0xae, 0xe0, 0x4a, 0xf6, 0x50, 0x33, 0xd0, 0xb9, 0xe9, 0x2e, 0xc2, 0xce, 0x8d, 0x42, 0x89, 0x9c, 0x9b, 0xf9, 0x63, 0x2a, 0x25, 0x69, 0x41, 0x86, 0xa1, 0x5a, 0x31, 0xff, 0x7b, 0xb2, 0xb3, 0x0c, 0x1d, 0x3b, 0xe8, 0xf4, 0xab, 0x2d, 0x6a, 0xf5, 0xc0, 0x60, 0x03, 0xb2, 0x10, 0xc9, 0x6f, 0x3d, 0x3c, 0x08, 0x4d, 0xc6, 0xf9, 0x46, 0x34, 0xf6, 0x33, 0xe9, 0xfb, 0xc9, 0x5f, 0xe1, 0x75, 0x5b, 0x8c, 0x32, 0xc7, 0xa0, 0x83, 0x40, 0x06, 0xd1, 0x5a, 0xf5, 0x39, + 0x50, 0x75, 0xf7, 0xee, 0x0c, 0xab, 0x26, 0x4d, 0xcf, 0x64, 0xf3, 0xed, 0xe7, 0xa8, 0x1e, 0x40, 0x14, 0x17, 0xd9, 0xad, 0x3a, 0x0d, 0xbb, 0x36, 0x44, 0x79, 0x6b, 0x83, 0xe2, 0x57, 0x83, 0x19, 0xbf, 0x81, 0x60, 0x31, 0x40, 0x5f, 0x25, 0x06, 0xfe, 0x17, 0x3b, 0xf9, 0x5d, 0xc6, 0xf6, 0xc3, 0x78, 0xba, 0x0f, 0x26, 0x6c, 0x78, 0xba, 0x9f, 0x1f, 0x87, 0x26, 0xb6, 0xda, 0x6d, 0xab, 0x33, 0x96, 0x98, 0xcb, 0xaf, 0xbf, 0x59, 0xee, 0xf7, 0xc4, 0xbd, 0xee, 0xaa, 0x0a, 0x50, 0x2d, 0x9c, 0xd1, 0xeb, 0xd5, 0x2e, 0x0d, 0x9a, 0xd0, 0xe1, 0xee, 0xc2, 0xb9, 0x27, 0x98, 0x41, 0x1c, 0x1f, 0xb9, 0x8f, 0xd9, 0x07, 0xed, 0x09, 0x39, 0xd1, 0x4c, 0x9c, 0x4d, 0x1a, 0xab, 0xa2, 0x24, 0x49, 0xc3, 0x17, 0x61, 0xb2, 0x2f, 0x42, 0x67, 0xf8, 0xde, 0xb2, 0xef, 0x23, 0x1a, 0xe3, 0xa2, 0xb8, 0xa8, + 0x86, 0x95, 0x44, 0xf0, 0x6a, 0x08, 0x28, 0xc7, 0x37, 0xe3, 0xca, 0x5d, 0xf1, 0x2a, 0x23, 0xbf, 0x29, 0x76, 0xcb, 0x1a, 0xea, 0xe2, 0xb1, 0x8a, 0xf2, 0x48, 0x49, 0x56, 0x1c, 0xd2, 0xe5, 0xc4, 0xc1, 0x2a, 0x87, 0x5c, 0x71, 0x08, 0x88, 0x9f, 0x32, 0x55, 0xa3, 0xe8, 0xb3, 0x9c, 0x7a, 0x30, 0xb6, 0xdf, 0x4a, 0x85, 0x47, 0x91, 0x42, 0xc0, 0x12, 0x6a, 0xb9, 0x7b, 0x3d, 0x92, 0x10, 0xd4, 0x07, 0xc6, 0x52, 0x73, 0x39, 0x1b, 0x24, 0x3e, 0x10, 0x86, 0x0a, 0xa2, 0x73, 0x60, 0x02, 0x57, 0x3c, 0x7d, 0xab, 0x3c, 0xe0, 0xa9, 0xcc, 0xca, 0xcc, 0x8b, 0x65, 0xd6, 0x28, 0x90, 0x99, 0x77, 0xee, 0x71, 0x92, 0x0b, 0x1f, 0x53, 0xef, 0x64, 0xab, 0xa0, 0x62, 0x3d, 0x50, 0x96, 0xbe, 0x8f, 0x2a, 0x64, 0x1a, 0xa0, 0x1c, 0x6b, 0x88, 0xc1, 0x64, 0x3f, 0x2a, 0x11, 0x98, 0x3b, 0x1f, 0xe8, 0x6e, + 0x82, 0x11, 0x31, 0xfb, 0x78, 0xe9, 0x88, 0x11, 0x1e, 0x87, 0xdd, 0x91, 0x24, 0x39, 0x53, 0xa3, 0xa6, 0xba, 0x2a, 0x11, 0x8f, 0x09, 0x27, 0xc8, 0xb2, 0x12, 0x31, 0x2f, 0x21, 0x91, 0x3c, 0xb7, 0x9f, 0xac, 0x86, 0xc2, 0x48, 0x84, 0xdb, 0xdc, 0x78, 0xba, 0x70, 0xc2, 0xc0, 0xd2, 0xc0, 0xf3, 0x45, 0xc5, 0x49, 0x03, 0x79, 0xb5, 0xc5, 0xb4, 0x59, 0x37, 0x85, 0x5c, 0x59, 0xa1, 0x1c, 0x0a, 0x1d, 0x2e, 0x6f, 0x5d, 0xfe, 0xdc, 0x41, 0x72, 0xf8, 0x2f, 0x41, 0x00, 0x00, 0xef, 0x61, 0x0b, 0x6f, 0x8b, 0x9e, 0xc0, 0xe7, 0x71, 0x0e, 0xa2, 0x8d, 0xf8, 0x48, 0x52, 0xb3, 0x0a, 0xfa, 0xad, 0xb1, 0x88, 0xd3, 0xa1, 0x56, 0x11, 0xe2, 0x6c, 0x1e, 0xa2, 0x8b, 0x90, 0x4a, 0x67, 0x52, 0x6c, 0xb2, 0x21, 0x4a, 0xad, 0x44, 0x95, 0xd4, 0xd0, 0x06, 0x22, 0x16, 0x91, 0x18, 0x4a, 0x62, 0xe3, 0xa3, + 0xd5, 0x2b, 0x34, 0xdb, 0x83, 0x76, 0x26, 0xef, 0xa2, 0x16, 0x50, 0x96, 0xd3, 0x7c, 0x33, 0x80, 0x5b, 0x21, 0xc8, 0x42, 0x5b, 0x6b, 0xc8, 0xe7, 0x0d, 0xf9, 0x03, 0x4b, 0x42, 0x16, 0x82, 0xb9, 0xa7, 0x79, 0x79, 0x67, 0xce, 0x4b, 0x1c, 0xee, 0xc5, 0xa8, 0x9f, 0x47, 0xf7, 0xb4, 0xa2, 0x0d, 0x07, 0xc3, 0x18, 0x72, 0x77, 0x1f, 0x36, 0xb6, 0x86, 0xf7, 0x97, 0xad, 0xf7, 0xc4, 0xeb, 0x02, 0xfe, 0xfc, 0x63, 0xbf, 0xeb, 0xef, 0x36, 0x72, 0x1b, 0x4d, 0xaa, 0x3d, 0x90, 0xdd, 0x73, 0xc8, 0x9e, 0x8e, 0x21, 0x3e, 0xd4, 0x36, 0xdc, 0xfd, 0x60, 0xee, 0x51, 0x20, 0xbb, 0xcf, 0x20, 0x1b, 0xe1, 0x19, 0xd1, 0x13, 0xb4, 0x18, 0xcb, 0xb7, 0x93, 0xb8, 0x97, 0x55, 0x9e, 0xba, 0x66, 0xf8, 0xfa, 0xe5, 0x48, 0xc4, 0x0c, 0x01, 0xb7, 0x54, 0x06, 0xc7, 0x32, 0xd1, 0x4d, 0x69, 0xee, 0x4d, 0x4e, + 0xd7, 0xba, 0x58, 0x71, 0x49, 0x25, 0xa4, 0x40, 0xa2, 0x50, 0x54, 0xd9, 0x34, 0x50, 0x24, 0xd5, 0xd9, 0xa5, 0xe4, 0x2e, 0x68, 0x05, 0x85, 0xda, 0xd1, 0x1a, 0xf2, 0xea, 0xff, 0x2e, 0xa1, 0xc2, 0xa5, 0x9a, 0x2b, 0x52, 0x0c, 0xa0, 0xab, 0x30, 0xaf, 0x20, 0xd7, 0x2f, 0x27, 0x2f, 0xe7, 0x08, 0x51, 0x20, 0xe6, 0x4d, 0xa7, 0x2b, 0x65, 0x8c, 0xd1, 0x50, 0xed, 0x97, 0xfd, 0x65, 0x69, 0xc9, 0x36, 0xf7, 0x4c, 0xb6, 0x9e, 0x62, 0x25, 0x5b, 0x9f, 0x48, 0xf7, 0x80, 0x5f, 0x75, 0x0c, 0xaf, 0x3a, 0xf9, 0x1a, 0x16, 0x6d, 0x77, 0xcb, 0x90, 0x59, 0x1b, 0xc4, 0xbe, 0x72, 0x63, 0xfa, 0x19, 0xea, 0x28, 0xd4, 0x81, 0x23, 0xc4, 0x47, 0xb9, 0xa0, 0x0b, 0x14, 0x1b, 0xd5, 0xad, 0x26, 0x45, 0x24, 0x0e, 0xba, 0x70, 0x57, 0xe2, 0x2c, 0x62, 0xb8, 0x84, 0xa0, 0x19, 0xe8, 0x81, 0x80, 0x39, 0x02, + 0x21, 0xcc, 0xc8, 0xdd, 0x04, 0x29, 0x62, 0x48, 0xd1, 0x6e, 0xa8, 0xe3, 0xb8, 0x50, 0x27, 0x52, 0x8f, 0xd9, 0x22, 0x68, 0x15, 0x8b, 0x9a, 0x8b, 0x19, 0x52, 0xbc, 0x9b, 0xe7, 0xab, 0xc9, 0xf4, 0x22, 0xb2, 0x9d, 0x46, 0x93, 0x9a, 0xe1, 0xb5, 0x6b, 0x06, 0xaa, 0x13, 0xb1, 0xf2, 0x92, 0x22, 0x6f, 0x04, 0xfa, 0xa2, 0x66, 0x9e, 0xcb, 0x30, 0x08, 0x6d, 0xfd, 0x5c, 0x1b, 0x55, 0xb4, 0x34, 0xf9, 0x85, 0x88, 0xb5, 0x25, 0x00, 0xf6, 0x57, 0x31, 0x64, 0x94, 0x3a, 0x5a, 0xda, 0x1b, 0xa9, 0x1b, 0x6a, 0x6b, 0x0a, 0x7a, 0x1b, 0xab, 0x13, 0x89, 0x6a, 0x64, 0xc0, 0x1a, 0xf4, 0xa1, 0xea, 0xd5, 0xf5, 0x15, 0xdd, 0xe5, 0x96, 0xd2, 0xae, 0xc9, 0xcd, 0x93, 0x5d, 0xa5, 0xe5, 0x13, 0xe7, 0x46, 0x66, 0x3e, 0x59, 0x5b, 0x2c, 0xd1, 0xa9, 0x34, 0xc1, 0xaa, 0xee, 0x58, 0x51, 0x5b, 0xd4, 0x5a, + 0xda, 0x35, 0xb1, 0x69, 0xa2, 0xab, 0xb4, 0x68, 0xe0, 0x50, 0x9f, 0xda, 0xa9, 0xb1, 0x94, 0x94, 0xc5, 0x3d, 0xee, 0x12, 0x94, 0xad, 0x97, 0xfe, 0x81, 0x41, 0x6b, 0x2c, 0xed, 0xac, 0x88, 0xd4, 0x06, 0xad, 0x36, 0xaf, 0x03, 0x9a, 0xb9, 0x76, 0x8f, 0xad, 0xa0, 0xb8, 0xda, 0x15, 0x8a, 0x07, 0xec, 0xee, 0xa2, 0xe4, 0x78, 0x4b, 0xdd, 0xce, 0xc1, 0xf2, 0x64, 0x75, 0x87, 0x52, 0x6e, 0xb2, 0x9a, 0x8c, 0xfe, 0x98, 0x23, 0x50, 0xe1, 0x73, 0xb8, 0xc3, 0x0d, 0xc3, 0x8d, 0xc9, 0x3d, 0xc3, 0xd5, 0x52, 0x85, 0x53, 0x6b, 0x2b, 0xf1, 0xe8, 0x54, 0x6a, 0x0b, 0x1b, 0x53, 0xae, 0x81, 0x63, 0xf1, 0x16, 0xb4, 0x1f, 0xfc, 0xc4, 0xce, 0xab, 0x0a, 0x0c, 0x7f, 0x63, 0x25, 0xee, 0x45, 0x14, 0x36, 0x24, 0x12, 0x21, 0x17, 0xf9, 0xe1, 0x8a, 0xc9, 0x21, 0xd2, 0x8c, 0xac, 0xa0, 0x8b, 0x32, 0xad, + 0xf8, 0x38, 0x6a, 0x6e, 0x6b, 0x22, 0xdb, 0x18, 0x5a, 0x0b, 0x1e, 0x3f, 0xb4, 0x17, 0xd8, 0x8a, 0x84, 0xac, 0x68, 0xe9, 0xdc, 0xdc, 0x26, 0xe0, 0x64, 0x78, 0xb9, 0xbd, 0x05, 0xe5, 0x36, 0x30, 0x37, 0xdc, 0xb5, 0x6e, 0xdf, 0xe0, 0x43, 0x4a, 0x55, 0xd6, 0x46, 0x18, 0xbc, 0x75, 0xac, 0xab, 0xa3, 0xbf, 0x32, 0x2b, 0x8b, 0x8a, 0x9e, 0x91, 0x9e, 0x8a, 0x1c, 0x4b, 0x81, 0x4a, 0xf4, 0x8e, 0xa4, 0xca, 0xd9, 0x17, 0x04, 0x50, 0x17, 0xa2, 0x23, 0x14, 0x0a, 0xfa, 0xb1, 0xe1, 0x64, 0x40, 0xa7, 0x26, 0xb1, 0xb3, 0x82, 0x12, 0xdd, 0x28, 0xfc, 0x6a, 0x7c, 0x20, 0x0e, 0x63, 0xc9, 0xbc, 0x45, 0x56, 0xb6, 0x66, 0x46, 0xd6, 0xe1, 0x16, 0x1b, 0xbd, 0xda, 0xec, 0x59, 0x3d, 0x54, 0xfe, 0x62, 0xc7, 0xc8, 0xae, 0xa3, 0xcd, 0x9b, 0x1f, 0xbf, 0xa5, 0x29, 0x36, 0x71, 0x47, 0xcf, 0xb5, 0xbe, + 0x3b, 0xc7, 0x63, 0xde, 0x55, 0xd3, 0xcd, 0x2d, 0x1b, 0xdb, 0xcb, 0xa1, 0x8b, 0x4d, 0x4d, 0x15, 0xd6, 0x15, 0x5b, 0xaa, 0xa6, 0xee, 0x5e, 0xd3, 0x73, 0x7a, 0xe7, 0x6a, 0xd3, 0x6f, 0x41, 0xbf, 0xac, 0xbc, 0x67, 0x36, 0x59, 0xb9, 0xae, 0xc9, 0xeb, 0xf4, 0x39, 0xf5, 0x08, 0x47, 0x91, 0x7e, 0x84, 0xfe, 0x0f, 0x5a, 0x0e, 0xf5, 0x4a, 0x35, 0xf1, 0x2b, 0x8e, 0x8d, 0xb7, 0x08, 0x9a, 0xfc, 0x16, 0x15, 0x0f, 0xcb, 0x46, 0x97, 0x0c, 0x7f, 0xb9, 0x08, 0x95, 0xbd, 0x7b, 0x79, 0xb0, 0xb5, 0x75, 0x99, 0x86, 0x8b, 0xc0, 0xd6, 0x85, 0x1c, 0xd8, 0x7a, 0x36, 0x17, 0xa9, 0xf1, 0x81, 0x60, 0xeb, 0xa5, 0xdb, 0xe7, 0x82, 0xad, 0x11, 0xd6, 0x3a, 0x10, 0x0a, 0x72, 0xa7, 0x27, 0xb9, 0x7e, 0x47, 0x2e, 0xd2, 0x3a, 0x43, 0xdd, 0x67, 0xae, 0x4a, 0x08, 0x61, 0xd6, 0x94, 0xa5, 0xf4, 0x96, 0xd6, + 0x8c, 0x36, 0x02, 0xaf, 0x97, 0xae, 0xae, 0xec, 0xde, 0xde, 0xe6, 0x11, 0x42, 0xd8, 0x06, 0xdb, 0xeb, 0xac, 0x4a, 0x59, 0xf8, 0x60, 0x65, 0xbd, 0x3b, 0x82, 0x26, 0x02, 0x45, 0x36, 0x54, 0xf2, 0x3a, 0x68, 0xfe, 0xe3, 0x7a, 0x5d, 0x7c, 0xe4, 0x96, 0xa6, 0x2f, 0x76, 0xae, 0xe3, 0xb5, 0xba, 0xae, 0x30, 0xea, 0xea, 0x0a, 0xbe, 0xff, 0xc3, 0xc1, 0x01, 0x95, 0x0a, 0xd5, 0xeb, 0x09, 0xa4, 0x9f, 0xa4, 0x67, 0xe1, 0x9c, 0x97, 0x40, 0xab, 0xf9, 0x00, 0x8e, 0x44, 0xbc, 0xa8, 0xc1, 0xe0, 0xfd, 0x6e, 0x1b, 0xfe, 0x05, 0xe0, 0x9c, 0x4f, 0x14, 0xa0, 0x28, 0x90, 0x88, 0x10, 0xc7, 0xb8, 0x94, 0x42, 0x14, 0xce, 0x9b, 0xc5, 0x6c, 0x01, 0x4e, 0xee, 0x34, 0xda, 0x89, 0x12, 0x1c, 0x68, 0x62, 0x98, 0x6d, 0x82, 0x56, 0x41, 0x4e, 0x03, 0xe8, 0x9b, 0x11, 0x44, 0xa1, 0xc7, 0xed, 0xb4, 0x59, + 0xcd, 0x46, 0x83, 0x5e, 0xa7, 0x55, 0x2b, 0x65, 0x28, 0xa4, 0x22, 0x41, 0xbe, 0x19, 0xe0, 0xcb, 0xc7, 0x31, 0x5e, 0xae, 0xb8, 0x9b, 0x37, 0x1f, 0xe2, 0x0c, 0x5e, 0xdf, 0x2d, 0x51, 0x33, 0x12, 0x8d, 0x74, 0x3b, 0x38, 0xf3, 0xfd, 0xf4, 0x41, 0x14, 0xdf, 0xfd, 0xca, 0x9f, 0x7f, 0xf6, 0xc4, 0xc1, 0x2b, 0x05, 0x76, 0xd1, 0x66, 0x70, 0x68, 0x52, 0xec, 0xd2, 0x3d, 0xb6, 0xff, 0x23, 0xe4, 0xfe, 0xf4, 0xfb, 0xe6, 0xa8, 0xd9, 0x12, 0x35, 0x03, 0x5a, 0x71, 0x63, 0x87, 0xc9, 0x02, 0x1e, 0x21, 0xc3, 0x27, 0x80, 0xca, 0xa2, 0xfe, 0xcd, 0x6f, 0xb4, 0x26, 0x9c, 0x07, 0x45, 0x2e, 0x3c, 0x00, 0x7d, 0x84, 0xad, 0xcc, 0x3c, 0xb4, 0x95, 0xca, 0x89, 0x8b, 0x57, 0x8d, 0x80, 0xca, 0x9c, 0xb2, 0xbb, 0x32, 0xfe, 0x26, 0xca, 0xde, 0x15, 0xe3, 0x90, 0x36, 0x5a, 0xe1, 0x79, 0x93, 0xc5, 0xc1, + 0x37, 0xc3, 0xf9, 0xec, 0x70, 0x87, 0x9b, 0xca, 0x69, 0x84, 0xf0, 0x79, 0x14, 0x43, 0xa1, 0xe5, 0x2f, 0x6c, 0x27, 0x62, 0xf3, 0xde, 0xb3, 0x93, 0x03, 0xb1, 0x1a, 0x94, 0x95, 0x42, 0xf5, 0xea, 0x29, 0x30, 0xf3, 0x9e, 0xaa, 0x04, 0x63, 0x57, 0xb3, 0x14, 0xec, 0x19, 0x14, 0x73, 0xee, 0x1c, 0x31, 0x66, 0xe7, 0xc7, 0x8f, 0x5b, 0x7a, 0xba, 0x5b, 0x5a, 0x7a, 0x53, 0xe4, 0x1b, 0xa5, 0xab, 0xcb, 0x47, 0x4f, 0xac, 0x0d, 0x17, 0xae, 0x8d, 0xb6, 0xec, 0xee, 0x2f, 0x89, 0x8e, 0x1c, 0xee, 0xee, 0x3e, 0xbc, 0x2e, 0x3a, 0x34, 0xe0, 0x0f, 0xa9, 0x54, 0x66, 0x31, 0x5d, 0x36, 0xd0, 0xdf, 0x37, 0x80, 0xfe, 0xbd, 0x77, 0x4e, 0xaf, 0x4b, 0x4c, 0x9d, 0x5f, 0x43, 0xfd, 0x83, 0xd9, 0x5c, 0xdc, 0xbb, 0xb3, 0xa5, 0x6d, 0xef, 0x40, 0x69, 0x64, 0xdd, 0xf1, 0x81, 0xf7, 0xbb, 0xb6, 0x4c, 0xf3, + 0x7a, 0x62, 0x15, 0x9c, 0xd9, 0x8d, 0xcc, 0x33, 0x44, 0x9c, 0xe5, 0xbf, 0xa5, 0x68, 0x1a, 0xe3, 0x71, 0x45, 0x80, 0x92, 0xc0, 0x7d, 0x88, 0xe0, 0x08, 0xa7, 0xb8, 0xd3, 0x43, 0x8e, 0x55, 0x1c, 0x33, 0xd2, 0xc0, 0x1e, 0x71, 0x73, 0x49, 0x10, 0x3a, 0x42, 0x09, 0x29, 0xab, 0xd8, 0xc4, 0x28, 0x86, 0xc9, 0xe6, 0x32, 0xf3, 0xda, 0x4d, 0x90, 0x29, 0x85, 0x4d, 0x47, 0x8a, 0xc7, 0xf7, 0xc4, 0x8c, 0x94, 0x52, 0xeb, 0xd3, 0xa5, 0x2f, 0x16, 0x0d, 0x05, 0xc0, 0x37, 0xdc, 0xfe, 0x1b, 0x75, 0x5a, 0x3d, 0xf8, 0x46, 0xba, 0x9a, 0x1c, 0x8c, 0xd7, 0xf8, 0x5b, 0xd6, 0x57, 0x56, 0x8e, 0xb4, 0x46, 0x64, 0xca, 0x92, 0xd0, 0x16, 0xf5, 0xe0, 0xd1, 0xc7, 0x27, 0x26, 0x3e, 0xb1, 0xbf, 0xa5, 0xb2, 0xe4, 0x39, 0x72, 0x42, 0xe7, 0xd3, 0xa7, 0xef, 0x91, 0xab, 0xfc, 0xce, 0x83, 0xab, 0x0c, 0xa5, + 0x3a, 0xf0, 0xad, 0x47, 0xda, 0xce, 0xf5, 0xd7, 0x8f, 0xd5, 0x3b, 0x9d, 0x89, 0xee, 0xe2, 0x80, 0x55, 0xac, 0x9b, 0xfe, 0xc8, 0x96, 0xca, 0xaa, 0xad, 0x0f, 0x4d, 0x0e, 0xdd, 0xdb, 0xfc, 0x08, 0xab, 0xe7, 0x43, 0x0b, 0x7f, 0xa2, 0xa2, 0xd0, 0x5e, 0xae, 0x41, 0x39, 0xdc, 0x65, 0x80, 0x62, 0xf4, 0xb8, 0x04, 0x32, 0xdc, 0x28, 0xe9, 0x7d, 0xbc, 0xbf, 0x87, 0x78, 0xf3, 0x78, 0x87, 0x0f, 0xbe, 0x33, 0x97, 0xe4, 0xa9, 0xd5, 0x86, 0x9c, 0x3e, 0x53, 0x29, 0x9b, 0xe9, 0xc8, 0xee, 0x71, 0x6c, 0xd8, 0x26, 0x21, 0xdc, 0x11, 0x39, 0x4e, 0xd4, 0xac, 0x25, 0x0c, 0xde, 0x70, 0x96, 0x37, 0xb6, 0x34, 0x96, 0x3b, 0x6b, 0xb6, 0xdc, 0xbf, 0xae, 0x7a, 0x6e, 0x76, 0xd8, 0x51, 0xad, 0xb0, 0xe8, 0xca, 0x5b, 0x86, 0xab, 0x63, 0xab, 0xab, 0x9c, 0xce, 0x8a, 0xa6, 0x96, 0xa6, 0x72, 0xe7, 0x01, + 0xa1, 0x1d, 0x4c, 0x45, 0x8b, 0xea, 0x4a, 0xfd, 0x81, 0x48, 0xe3, 0x50, 0x6d, 0xff, 0xe9, 0x8d, 0x09, 0x53, 0xb8, 0xba, 0xb0, 0x53, 0xe7, 0xf4, 0x3a, 0xed, 0xb1, 0x8e, 0xe2, 0xd2, 0x64, 0xd4, 0x1f, 0x2c, 0x6d, 0x1a, 0xae, 0x8f, 0xe6, 0xd8, 0xc1, 0xd0, 0x6b, 0x4d, 0xdf, 0x46, 0x7d, 0x0f, 0xda, 0xc1, 0x7a, 0x62, 0x23, 0x4b, 0x2b, 0xee, 0x96, 0x00, 0x91, 0x4c, 0x4a, 0x32, 0x84, 0x88, 0xd9, 0x44, 0x03, 0xae, 0xfc, 0x0c, 0x07, 0xb6, 0x65, 0x53, 0xeb, 0xa1, 0x0f, 0x8c, 0x6a, 0xd0, 0x2c, 0xdb, 0x0e, 0x9d, 0xf2, 0xe9, 0x38, 0xa4, 0x37, 0xff, 0x9f, 0x1c, 0xe1, 0xf4, 0xf8, 0x12, 0x9f, 0x98, 0x8d, 0x1c, 0x25, 0xea, 0x78, 0x8c, 0xe4, 0x21, 0x30, 0x92, 0x7e, 0xea, 0x7a, 0xfa, 0x29, 0xf4, 0x03, 0x8c, 0x5c, 0x07, 0x23, 0xe4, 0x53, 0x7f, 0x78, 0x64, 0x1e, 0xfe, 0xff, 0x91, 0x3f, + 0x3c, 0x42, 0x9e, 0x98, 0x3f, 0xfe, 0x08, 0xc2, 0x8e, 0x40, 0x7d, 0xf3, 0x28, 0x7c, 0x46, 0x3b, 0x9a, 0x5b, 0x50, 0xee, 0x8c, 0x0a, 0x88, 0x30, 0xf7, 0x2f, 0x8d, 0x9f, 0x89, 0xc3, 0x08, 0x8a, 0x31, 0x8f, 0x77, 0x1e, 0x50, 0xde, 0x4e, 0xd8, 0xbd, 0x5a, 0xbd, 0x57, 0x8b, 0x2b, 0x0e, 0xf1, 0x38, 0xf9, 0xa5, 0x40, 0x39, 0xf4, 0xa3, 0xee, 0x82, 0x5b, 0xbf, 0x93, 0x0b, 0xc5, 0xb9, 0x94, 0xee, 0xb5, 0xbb, 0xc1, 0x18, 0x79, 0xd7, 0x22, 0x08, 0x8e, 0x92, 0xc5, 0xb4, 0xc0, 0xe7, 0xda, 0x03, 0x9f, 0xcb, 0x87, 0x9e, 0x4b, 0x09, 0x2d, 0x57, 0x7c, 0x66, 0xc1, 0x90, 0x04, 0x33, 0x85, 0x10, 0x93, 0x33, 0x2c, 0x0e, 0x05, 0x61, 0xd8, 0xc6, 0x11, 0x96, 0x7f, 0x9a, 0x3b, 0xaf, 0x82, 0x6d, 0x7c, 0x84, 0x4f, 0xeb, 0xf7, 0x78, 0xcd, 0x7e, 0x8e, 0x45, 0x27, 0x1f, 0x94, 0x12, 0xe3, 0x11, + 0xfd, 0x59, 0x6c, 0xca, 0x52, 0x50, 0x14, 0xc5, 0xaf, 0x5d, 0xd6, 0x5b, 0xe7, 0xb2, 0x70, 0x14, 0xe6, 0xfa, 0x8d, 0xc7, 0x73, 0x01, 0x28, 0x8f, 0xb3, 0xaf, 0xc0, 0xc3, 0x50, 0xe0, 0xc3, 0xe2, 0x98, 0xc7, 0x93, 0x38, 0xe6, 0x61, 0x20, 0x8a, 0x93, 0x21, 0x71, 0x0e, 0x66, 0x1f, 0x7b, 0xc0, 0x39, 0x90, 0x7d, 0x68, 0x72, 0x68, 0xd9, 0x04, 0xfc, 0x7c, 0xb0, 0x3e, 0x1b, 0x93, 0x80, 0x62, 0x9b, 0x3f, 0x95, 0x1b, 0x86, 0x48, 0xff, 0x0a, 0xfd, 0xcd, 0xbc, 0xf0, 0x03, 0xdc, 0x37, 0xaa, 0xa0, 0x52, 0xbb, 0x41, 0x4b, 0x09, 0x05, 0xdc, 0xbb, 0xfb, 0x92, 0xd2, 0xf2, 0x52, 0xbd, 0x86, 0x06, 0x58, 0x97, 0xda, 0x79, 0x72, 0x52, 0x68, 0x6a, 0x90, 0x38, 0xbc, 0x8f, 0xb5, 0x83, 0x2d, 0x69, 0x42, 0x47, 0xab, 0x78, 0xdf, 0xe4, 0xe3, 0xfe, 0xf8, 0x83, 0xd1, 0xa4, 0x12, 0xfe, 0x57, 0xad, + 0xac, 0xf2, 0x15, 0x9a, 0xfd, 0x0c, 0x22, 0xf7, 0xc5, 0x67, 0x3c, 0x1c, 0x61, 0x89, 0x14, 0x78, 0x11, 0xd5, 0x52, 0x30, 0x80, 0x8e, 0x7e, 0xaa, 0x82, 0x11, 0x10, 0x17, 0x10, 0xc7, 0x8a, 0xcd, 0xd9, 0xca, 0xa3, 0xa0, 0x26, 0x99, 0x8a, 0x96, 0x46, 0x2b, 0x35, 0x72, 0xf2, 0x17, 0xe9, 0x99, 0xe7, 0xfe, 0xdd, 0xd1, 0xe0, 0xb4, 0x88, 0x65, 0x1e, 0xa5, 0x45, 0xa1, 0xd3, 0xe8, 0x3d, 0xc9, 0x0d, 0x4d, 0x8d, 0x63, 0x75, 0x0e, 0x77, 0xdd, 0xea, 0xe8, 0x66, 0x75, 0x81, 0x5c, 0x62, 0x52, 0xff, 0x95, 0x71, 0x19, 0xc8, 0x63, 0xee, 0xb5, 0x89, 0xa2, 0x58, 0x6d, 0xd4, 0x1a, 0x37, 0x83, 0xd6, 0x1b, 0x12, 0xf0, 0x1d, 0x89, 0x4a, 0xd4, 0x62, 0x2a, 0x12, 0x43, 0xeb, 0x3b, 0xe0, 0x1e, 0x2a, 0xaa, 0x99, 0x5c, 0x15, 0xf0, 0x35, 0x0e, 0x44, 0x2a, 0x86, 0x3a, 0x6a, 0xcd, 0xdf, 0x74, 0x76, + 0x84, 0x8a, 0xd6, 0x96, 0x92, 0x73, 0x5a, 0x13, 0x94, 0x7b, 0x39, 0x7c, 0x8b, 0xe7, 0xa0, 0xdc, 0x17, 0x61, 0xfc, 0x67, 0x6f, 0x06, 0xe3, 0x4f, 0x3e, 0x37, 0x7f, 0x07, 0xb8, 0x96, 0x3e, 0x09, 0x8e, 0xa1, 0xea, 0xc1, 0x0f, 0x83, 0xa2, 0x87, 0xc1, 0x63, 0x59, 0x6c, 0x95, 0x0f, 0x7e, 0x6f, 0x3e, 0xc6, 0x7f, 0x76, 0x19, 0x8c, 0x3f, 0xe5, 0x4b, 0x17, 0x7e, 0xe5, 0x2b, 0xe0, 0x17, 0xe0, 0x9b, 0xe9, 0x5a, 0xea, 0x9d, 0xf4, 0x26, 0x5c, 0x56, 0x67, 0xe1, 0x6d, 0xe6, 0x0c, 0xf4, 0x29, 0x94, 0x50, 0x1f, 0x27, 0x93, 0xf2, 0x20, 0x1c, 0x97, 0x38, 0x52, 0x62, 0xbc, 0x23, 0xac, 0xc9, 0x81, 0x8d, 0x65, 0xa8, 0xba, 0xb2, 0xb7, 0x10, 0x55, 0xd7, 0xd5, 0x50, 0x30, 0x8c, 0x98, 0xba, 0xfc, 0xbc, 0xc9, 0x97, 0xa9, 0xc2, 0x90, 0xc3, 0xf6, 0x5e, 0x05, 0x87, 0x00, 0x07, 0x01, 0x70, 0x6c, + 0x90, 0x6a, 0xde, 0xf6, 0xe0, 0x44, 0x51, 0x71, 0xfb, 0xfa, 0x68, 0x53, 0xe7, 0x4b, 0xc5, 0x83, 0x07, 0x53, 0x53, 0x4f, 0x1e, 0x68, 0xee, 0x7f, 0x72, 0xfe, 0xb3, 0x57, 0xde, 0x7b, 0x72, 0x20, 0xb6, 0xe1, 0xf4, 0x40, 0x85, 0x18, 0x6a, 0x7d, 0xc9, 0xe4, 0x74, 0x78, 0xec, 0xbe, 0xd9, 0x0b, 0xd2, 0xa6, 0x99, 0xf3, 0x43, 0x7d, 0xb7, 0x8f, 0x57, 0xac, 0x9e, 0x5c, 0x7b, 0xb8, 0xdf, 0xdf, 0x73, 0xfa, 0xea, 0xd6, 0x63, 0xff, 0xed, 0xfe, 0xfe, 0xbe, 0xbb, 0xbf, 0xb6, 0x6f, 0xec, 0xe3, 0xa7, 0x66, 0x3d, 0xfd, 0x0a, 0xab, 0x59, 0x47, 0x55, 0x7c, 0xf9, 0x70, 0xf7, 0xf1, 0x0d, 0x0d, 0x32, 0x56, 0x3e, 0x65, 0xe9, 0x2b, 0x74, 0x1f, 0xf3, 0x32, 0x91, 0x40, 0x7c, 0x7e, 0x6a, 0x15, 0x89, 0xfd, 0x45, 0x49, 0x18, 0xda, 0xdb, 0x28, 0xe8, 0xc5, 0xf2, 0x3e, 0xcd, 0xe0, 0x64, 0xe3, 0x51, + 0xe4, 0x3e, 0xce, 0xa6, 0x64, 0x52, 0x31, 0x85, 0xd7, 0x2b, 0xc3, 0xe0, 0xb0, 0x31, 0xf4, 0x22, 0xe1, 0x17, 0x25, 0x88, 0x84, 0xd6, 0x18, 0x0e, 0xa2, 0xe2, 0xd6, 0x26, 0xa4, 0xcf, 0x62, 0x19, 0x44, 0x23, 0xde, 0x57, 0x31, 0x88, 0x0a, 0x1d, 0xc3, 0xe7, 0x14, 0x83, 0x76, 0x09, 0xeb, 0x44, 0x7b, 0xb4, 0xdf, 0x41, 0xc0, 0xc6, 0x47, 0xee, 0x29, 0x08, 0xea, 0x9d, 0xd6, 0xd6, 0x26, 0x72, 0xbb, 0x9e, 0xab, 0x0f, 0x6d, 0x37, 0x28, 0x18, 0xa5, 0xd8, 0xe9, 0x9e, 0x88, 0x8f, 0xcd, 0xf4, 0x34, 0x25, 0xd2, 0x1b, 0xa9, 0x11, 0x0c, 0x6e, 0xbc, 0xf3, 0xc9, 0x67, 0x19, 0xd1, 0x0e, 0xad, 0x6c, 0xfd, 0x96, 0x3b, 0x6f, 0x58, 0x04, 0x15, 0xa3, 0xbb, 0xf8, 0x42, 0xd2, 0xeb, 0x6f, 0x5c, 0x66, 0xe3, 0x39, 0x70, 0xff, 0xfd, 0x3a, 0x7c, 0x47, 0x3b, 0x11, 0x21, 0x06, 0x92, 0x7d, 0x2a, 0x20, + 0x01, 0x6a, 0x40, 0xa2, 0xc3, 0x78, 0xf4, 0x92, 0x88, 0x4e, 0x57, 0x4c, 0xcc, 0x21, 0x04, 0x47, 0x96, 0xbb, 0x6c, 0x06, 0xbb, 0x17, 0xfc, 0x2b, 0x23, 0x00, 0xe4, 0x34, 0x0d, 0xdf, 0x33, 0x52, 0xe2, 0x76, 0x39, 0x1d, 0x48, 0x73, 0x6a, 0xb9, 0x57, 0xb5, 0xae, 0xf0, 0xaa, 0xf0, 0x9d, 0x40, 0x1e, 0xaf, 0x76, 0x1d, 0xc8, 0x7f, 0xcb, 0xeb, 0xe9, 0x8d, 0xe4, 0xa6, 0xab, 0xdf, 0xbb, 0x4b, 0x59, 0xc0, 0x6c, 0x65, 0xa4, 0xb4, 0x58, 0xbe, 0x55, 0x54, 0xa0, 0x38, 0xb9, 0xd4, 0x2b, 0xa2, 0xf7, 0xa1, 0x1f, 0x80, 0x66, 0x8d, 0xf8, 0x04, 0x34, 0xdd, 0x74, 0xea, 0x13, 0x12, 0x31, 0x91, 0x79, 0xbf, 0x3d, 0xf0, 0xfd, 0x7c, 0xf0, 0xfd, 0xce, 0x5f, 0x2d, 0x80, 0xaf, 0xc5, 0xf3, 0x88, 0x96, 0x62, 0x37, 0x04, 0x1a, 0x42, 0x70, 0xe2, 0xcf, 0xa4, 0xa4, 0x38, 0x14, 0xcb, 0x5f, 0x21, 0x57, + 0x75, 0x54, 0x8c, 0x55, 0x08, 0xd6, 0xc0, 0x34, 0x06, 0x62, 0xa0, 0xf6, 0x32, 0xc0, 0xb7, 0x96, 0x8c, 0xc9, 0xa1, 0x58, 0x76, 0x2c, 0xd1, 0x76, 0x34, 0xe9, 0x0a, 0xf8, 0x01, 0x51, 0x1c, 0xf6, 0x47, 0x02, 0x11, 0x97, 0x93, 0x4d, 0x0c, 0xd4, 0x69, 0x15, 0x72, 0x89, 0x88, 0xf0, 0x01, 0x9f, 0x82, 0x23, 0xc2, 0xe5, 0x6a, 0xbc, 0xaa, 0x28, 0x5e, 0x20, 0x5e, 0x01, 0xd4, 0x95, 0xc9, 0x54, 0xa2, 0x25, 0x0d, 0x85, 0xe5, 0x3e, 0xbb, 0x02, 0x17, 0x9d, 0xbd, 0xb5, 0x2a, 0x15, 0x31, 0x60, 0xb9, 0x5c, 0x47, 0xa2, 0x8a, 0x0e, 0x1f, 0x06, 0xdf, 0xce, 0x14, 0xa7, 0x65, 0x5e, 0x16, 0x29, 0x2d, 0xba, 0xb7, 0x95, 0xda, 0xdb, 0x49, 0xe8, 0xa9, 0x56, 0x72, 0x52, 0xe1, 0xa5, 0xb5, 0xe6, 0xf8, 0x70, 0xb1, 0x9a, 0xab, 0x58, 0xcb, 0xc9, 0xe5, 0x39, 0x3c, 0xee, 0x15, 0xc4, 0x67, 0x58, 0x96, + 0x67, 0x39, 0x3f, 0xc5, 0x19, 0xe4, 0x0e, 0xa9, 0x90, 0x41, 0x05, 0xa7, 0x82, 0x98, 0x9d, 0x0a, 0xa3, 0x6c, 0x9b, 0xb0, 0x0c, 0x48, 0x60, 0x77, 0x09, 0x9a, 0x13, 0x68, 0x3a, 0x28, 0xc4, 0x64, 0xde, 0x84, 0x90, 0x73, 0x13, 0xc2, 0x96, 0x8c, 0xe5, 0xb5, 0xc5, 0x53, 0x07, 0xfd, 0x2a, 0x1e, 0x53, 0x00, 0x0c, 0x0a, 0x5e, 0xdc, 0x6b, 0x34, 0x69, 0x83, 0x73, 0x88, 0x88, 0x94, 0x86, 0x83, 0xde, 0x42, 0x47, 0x85, 0xb3, 0x22, 0x3b, 0x9f, 0x94, 0x1f, 0x38, 0x9f, 0x16, 0xd5, 0x61, 0x5d, 0x6a, 0x3e, 0x89, 0x51, 0x69, 0xd6, 0x73, 0x2a, 0xae, 0x34, 0xeb, 0x39, 0x7b, 0xcb, 0x8e, 0xe5, 0xe6, 0x13, 0x73, 0x79, 0x51, 0xc9, 0x56, 0x1a, 0xcb, 0xed, 0x5f, 0x44, 0xb5, 0xb8, 0x16, 0x57, 0x39, 0xd1, 0x47, 0xfc, 0x38, 0x29, 0x6f, 0x4e, 0x92, 0xb4, 0x38, 0x06, 0x64, 0x72, 0x5e, 0xe7, 0x95, + 0xc1, 0x3d, 0x1d, 0xba, 0x24, 0xb3, 0xb8, 0x74, 0x2c, 0xf4, 0xd0, 0x89, 0x69, 0x14, 0x63, 0xc2, 0xf1, 0x61, 0x28, 0x2a, 0xb9, 0x5c, 0x34, 0x2a, 0xc5, 0x51, 0x65, 0x95, 0x82, 0x94, 0xc9, 0xa6, 0x65, 0xc8, 0xda, 0x37, 0x22, 0x28, 0x15, 0xea, 0xc6, 0xec, 0xbb, 0xe9, 0x5e, 0xc9, 0x38, 0xdb, 0x81, 0xa0, 0xc5, 0x12, 0x31, 0x2d, 0x99, 0xcb, 0x76, 0x24, 0x38, 0xc0, 0xd5, 0x92, 0xfd, 0xa0, 0x3f, 0xe0, 0xae, 0xa8, 0x08, 0x85, 0x2a, 0xfa, 0x2a, 0xfa, 0x7a, 0x7b, 0x52, 0xdd, 0x5d, 0x9d, 0x6d, 0xab, 0x1a, 0xeb, 0x6b, 0xaa, 0x43, 0xe5, 0xa1, 0x68, 0x30, 0x14, 0x0a, 0xf9, 0xd4, 0x99, 0x1a, 0x72, 0x59, 0x3f, 0x80, 0xc9, 0xbb, 0xd6, 0x8b, 0x31, 0x45, 0x3d, 0xda, 0x0b, 0x57, 0x1a, 0x12, 0x6a, 0x52, 0xe7, 0x0c, 0x99, 0x8c, 0x41, 0x87, 0x56, 0xeb, 0x08, 0x1a, 0x4d, 0x21, 0xa7, 0x0e, + 0xf4, 0x6a, 0xe1, 0x1d, 0x13, 0x7b, 0xc7, 0x04, 0xef, 0x68, 0xcb, 0x7e, 0xa6, 0xf5, 0x15, 0x6c, 0x3f, 0xa1, 0xf2, 0x78, 0x6f, 0x3c, 0xbf, 0x78, 0xbc, 0xe8, 0x2f, 0x9a, 0x02, 0xa8, 0x6d, 0x00, 0xb5, 0xd5, 0xa1, 0x6f, 0xcb, 0xbb, 0x06, 0x3b, 0x7f, 0xae, 0x56, 0xfd, 0xa3, 0xd9, 0xf2, 0xee, 0x37, 0x97, 0x1b, 0x47, 0x5e, 0x97, 0x8b, 0xbe, 0x01, 0xe7, 0x7b, 0x3f, 0xd0, 0xb3, 0xd2, 0x36, 0xb4, 0x57, 0x94, 0x53, 0x4a, 0xa9, 0x17, 0xba, 0x1c, 0x1a, 0xb8, 0x22, 0xa8, 0xee, 0x36, 0x20, 0xed, 0xb4, 0x25, 0x0d, 0xf0, 0x87, 0x22, 0xf7, 0x23, 0x9c, 0x29, 0x84, 0xba, 0x54, 0x13, 0x4a, 0xa5, 0x14, 0xce, 0x5d, 0xa9, 0x14, 0x2a, 0x43, 0x99, 0x8c, 0xff, 0x15, 0xad, 0x7a, 0xc9, 0xa8, 0x4a, 0x4e, 0x4a, 0x24, 0xb3, 0x29, 0x8d, 0x5a, 0xcc, 0xd0, 0xe8, 0xc0, 0x26, 0xc3, 0x2a, 0xb5, 0x95, + 0xe4, 0x4b, 0x16, 0x25, 0x60, 0x7f, 0xc5, 0x18, 0xa1, 0x50, 0xcc, 0xb0, 0xdd, 0x89, 0x9b, 0xec, 0x9d, 0x6c, 0xc8, 0xf0, 0x4e, 0xdd, 0xf4, 0x17, 0x10, 0xd9, 0xfe, 0x70, 0xc0, 0x11, 0x09, 0x65, 0x3f, 0xd1, 0xdf, 0xd7, 0xdb, 0xdd, 0xb9, 0xaa, 0xb9, 0xb1, 0xa1, 0x3a, 0x1e, 0x8d, 0x98, 0x70, 0x6e, 0xbc, 0xcf, 0x1b, 0xd4, 0xc2, 0x2d, 0xc9, 0xb3, 0xfc, 0xce, 0x93, 0xaf, 0x98, 0x50, 0x01, 0x88, 0x0c, 0x5e, 0x7f, 0xd1, 0xa0, 0x8b, 0x46, 0xb9, 0x8d, 0xc9, 0xa1, 0x57, 0x30, 0x6a, 0xc6, 0xe9, 0x9a, 0xac, 0x84, 0x1b, 0x53, 0x63, 0x45, 0x7a, 0xc3, 0xb5, 0x74, 0xd7, 0x75, 0x16, 0x9d, 0x7f, 0x6a, 0x70, 0x55, 0x3d, 0x93, 0x45, 0xf1, 0x7f, 0xe5, 0x91, 0x7b, 0x2c, 0x21, 0x9d, 0xc3, 0xda, 0x06, 0x07, 0x7c, 0x99, 0x9d, 0x4a, 0xa0, 0xc7, 0x7a, 0x30, 0x6a, 0x7f, 0x4a, 0xc1, 0x61, 0xfb, + 0xdb, 0x73, 0x46, 0x9b, 0xd5, 0x6b, 0xe2, 0x2a, 0x38, 0xce, 0x71, 0xa2, 0x83, 0x78, 0x8d, 0x1d, 0x36, 0x4d, 0x89, 0xc5, 0x4c, 0x49, 0x19, 0x29, 0x76, 0x2c, 0x8b, 0x01, 0x03, 0x47, 0x59, 0x03, 0x7f, 0x48, 0xb2, 0xb7, 0xa9, 0xec, 0x08, 0x2b, 0x44, 0xb4, 0x48, 0x41, 0xcf, 0x41, 0xc9, 0x32, 0x63, 0x2a, 0xa5, 0x84, 0xc2, 0xa9, 0xee, 0x08, 0xee, 0xca, 0x5f, 0x20, 0x94, 0x3f, 0x3d, 0x2a, 0x13, 0x93, 0xc8, 0xfc, 0x52, 0xcb, 0x29, 0x8a, 0xb3, 0x38, 0x2b, 0x05, 0x5d, 0x25, 0x63, 0xec, 0x39, 0x32, 0xdb, 0x8f, 0x5c, 0xbe, 0xdb, 0x68, 0xd2, 0x4e, 0x10, 0x1d, 0x6d, 0xe8, 0x2c, 0x04, 0xf9, 0xb3, 0x95, 0xb1, 0x68, 0xc4, 0x6b, 0x8c, 0xfb, 0xf1, 0xd8, 0x68, 0xd0, 0xb8, 0xfc, 0xcd, 0x03, 0x40, 0x2d, 0xda, 0x54, 0x99, 0x67, 0x6e, 0x52, 0xf8, 0xe2, 0x17, 0x05, 0x3b, 0xed, 0x36, 0x06, + 0xed, 0xb4, 0x7f, 0x83, 0xec, 0x17, 0xed, 0xbe, 0x78, 0x2c, 0x44, 0x7f, 0x86, 0x63, 0x51, 0x47, 0xb4, 0x81, 0x71, 0x56, 0x35, 0xca, 0x2d, 0x66, 0x52, 0xc6, 0x0d, 0x85, 0x8d, 0x2d, 0xf2, 0x85, 0x6e, 0x89, 0xf8, 0x5b, 0x39, 0x0d, 0x46, 0xb9, 0x28, 0x5c, 0x23, 0x9c, 0xec, 0xcc, 0x98, 0x46, 0x38, 0x1a, 0xfc, 0x85, 0x0a, 0xa0, 0x81, 0xd2, 0xe4, 0x8c, 0x8d, 0x9c, 0x13, 0xb2, 0x82, 0x1f, 0x1b, 0xf6, 0xef, 0x54, 0xc8, 0xc0, 0xdf, 0xd1, 0xd3, 0xf8, 0xff, 0xc0, 0xdf, 0x4f, 0xd6, 0xc0, 0x2f, 0x10, 0xce, 0x09, 0x0d, 0x3b, 0x27, 0xf8, 0xce, 0xe4, 0xf2, 0x7d, 0x51, 0x64, 0xcf, 0xdd, 0x50, 0x4f, 0x10, 0x2d, 0xc9, 0xfa, 0xb6, 0x86, 0x36, 0x28, 0xca, 0xba, 0xda, 0x9a, 0x9c, 0x69, 0xa2, 0xbd, 0xe9, 0x69, 0x92, 0x67, 0x71, 0x50, 0x19, 0x0b, 0x63, 0xe5, 0x19, 0x72, 0xe5, 0x49, 0xde, + 0xfe, 0xb0, 0xab, 0x6f, 0xad, 0xee, 0xad, 0x30, 0x91, 0xdb, 0x87, 0x33, 0x36, 0xc7, 0x07, 0xcf, 0x10, 0x6c, 0x93, 0xa8, 0x34, 0x19, 0x9b, 0x84, 0x79, 0x86, 0x37, 0x42, 0x78, 0xfb, 0x4c, 0xec, 0xc1, 0xeb, 0x35, 0x05, 0x26, 0xb9, 0xf5, 0xea, 0xd3, 0xa8, 0x29, 0x44, 0x71, 0x27, 0x96, 0x90, 0xdd, 0x5e, 0x00, 0xd0, 0x7a, 0x85, 0x3f, 0xe8, 0xec, 0xed, 0x0c, 0x6e, 0xbd, 0x56, 0x0d, 0x94, 0xd0, 0xc4, 0x50, 0x12, 0x73, 0x2a, 0x80, 0x58, 0xf0, 0xb5, 0x0a, 0x14, 0x12, 0xe4, 0x4c, 0x55, 0xfe, 0x42, 0x86, 0x18, 0x84, 0x46, 0x45, 0x14, 0x89, 0x0c, 0x0d, 0x8d, 0x5c, 0x4a, 0x89, 0xc5, 0xd3, 0x62, 0x7e, 0x56, 0x64, 0xbf, 0x81, 0x10, 0x7e, 0x81, 0x0a, 0xdc, 0xd4, 0x37, 0x18, 0xff, 0x0f, 0x9f, 0x21, 0xb9, 0x2a, 0xa7, 0x33, 0x8e, 0xf1, 0xce, 0xa4, 0xf8, 0xef, 0x21, 0x05, 0x5f, 0x43, + 0x2e, 0xff, 0x2d, 0x68, 0x8e, 0x14, 0x42, 0x05, 0x42, 0x74, 0xb4, 0xc3, 0x59, 0x52, 0x97, 0x48, 0x55, 0xa5, 0x58, 0x65, 0x52, 0x51, 0x8e, 0x2d, 0x27, 0x34, 0x53, 0x74, 0x39, 0x7e, 0xc7, 0x07, 0x29, 0x11, 0x5e, 0xef, 0x2f, 0x63, 0x51, 0xad, 0xa8, 0x45, 0xae, 0xe1, 0xb9, 0x44, 0x1f, 0x42, 0x66, 0xd6, 0x79, 0x25, 0x67, 0x66, 0x9d, 0x77, 0x64, 0xcc, 0xac, 0x15, 0x95, 0x08, 0x3f, 0x9d, 0x96, 0x30, 0xbd, 0x58, 0xdb, 0x4b, 0xbc, 0x16, 0xda, 0x5e, 0x71, 0xa2, 0x89, 0x58, 0x43, 0x6c, 0x01, 0x21, 0x6e, 0xbe, 0xc0, 0xc9, 0xa1, 0x14, 0x4e, 0x0e, 0x5b, 0x52, 0x8f, 0xe7, 0x8b, 0x9a, 0x54, 0x00, 0x0d, 0x60, 0x14, 0xe8, 0x6e, 0x26, 0xdd, 0xb7, 0x51, 0x0f, 0x94, 0x40, 0xad, 0x9c, 0xd5, 0x02, 0xb5, 0x06, 0xa8, 0x08, 0xb5, 0x6a, 0x5a, 0x81, 0xc5, 0x8d, 0x10, 0xe8, 0x9c, 0xb4, 0xd9, + 0x5f, 0x73, 0x84, 0xad, 0xcb, 0x1b, 0xf4, 0x06, 0xf4, 0x2d, 0xca, 0x7d, 0x7f, 0xef, 0x97, 0x24, 0x5b, 0x60, 0x7f, 0x5a, 0xb9, 0x8f, 0x80, 0xdd, 0x80, 0x82, 0x99, 0xcb, 0x7e, 0x0f, 0x91, 0x99, 0x05, 0xbc, 0xbb, 0xb5, 0xdc, 0x97, 0xc0, 0x51, 0x0f, 0x24, 0xe0, 0xa8, 0x4f, 0x6f, 0x5e, 0x3f, 0xb2, 0xba, 0xaf, 0xbd, 0x35, 0xd1, 0x94, 0x68, 0xac, 0xab, 0x59, 0x34, 0xf2, 0x86, 0xbf, 0x6f, 0xe4, 0x33, 0xf6, 0x9d, 0xfe, 0x03, 0x2c, 0xc1, 0x9b, 0x9d, 0x11, 0xcc, 0xb5, 0x8c, 0x9d, 0x77, 0x7f, 0xbe, 0x09, 0x98, 0x7e, 0x21, 0xdf, 0x4c, 0xfc, 0x5b, 0x26, 0x8b, 0xa8, 0x96, 0xb5, 0x00, 0xd3, 0xc7, 0x3f, 0xc0, 0x54, 0xc4, 0xf1, 0x58, 0xe8, 0xd3, 0x3f, 0xc5, 0x34, 0x10, 0x8d, 0xc4, 0x57, 0x92, 0x6a, 0x3f, 0x74, 0x6f, 0x0a, 0x00, 0xa0, 0xc8, 0xee, 0x08, 0x1c, 0xad, 0x2e, 0xd6, 0x4a, + 0x0b, 0x12, 0x22, 0xb1, 0x68, 0x1f, 0x2a, 0x44, 0xc7, 0x63, 0xb0, 0x51, 0x55, 0x13, 0x1a, 0xc7, 0x07, 0x33, 0x64, 0x64, 0x6c, 0x59, 0x73, 0x27, 0x4e, 0x80, 0x40, 0xcd, 0x09, 0xf8, 0x09, 0x90, 0x4a, 0xe6, 0x96, 0xee, 0x46, 0x08, 0x7a, 0x25, 0x2b, 0xb8, 0x0e, 0xb4, 0x84, 0xce, 0xeb, 0x80, 0x9b, 0xa2, 0xca, 0x31, 0x28, 0xb3, 0x55, 0xd8, 0x09, 0x1d, 0xf0, 0x37, 0xd4, 0x85, 0xb5, 0x21, 0x7d, 0x91, 0x57, 0x6b, 0xc4, 0x67, 0xd1, 0xb9, 0x46, 0xda, 0x22, 0xf4, 0x72, 0x2e, 0x3e, 0x22, 0x27, 0xb2, 0x60, 0xa6, 0x9f, 0xe2, 0xac, 0xb2, 0x9a, 0x34, 0x89, 0xd2, 0x1f, 0xec, 0x06, 0x89, 0x54, 0x21, 0xaa, 0x8d, 0x9e, 0xee, 0x38, 0x72, 0x3a, 0x93, 0x1b, 0x41, 0x7e, 0x5d, 0x93, 0x63, 0xc9, 0xa5, 0xff, 0x35, 0x63, 0x8f, 0x09, 0xf1, 0xfd, 0x7c, 0x02, 0x00, 0x1b, 0x19, 0xce, 0x09, 0x35, + 0x70, 0xf1, 0xef, 0xf4, 0x15, 0x31, 0xc2, 0x8b, 0xac, 0x07, 0x12, 0x56, 0xbc, 0x86, 0x06, 0x40, 0xc9, 0xcb, 0xed, 0x24, 0x5b, 0x43, 0x96, 0xea, 0xee, 0x85, 0x4e, 0x4b, 0x97, 0x2d, 0xf7, 0xb6, 0x8a, 0xbb, 0xcd, 0x39, 0x9d, 0xa5, 0x0a, 0xe8, 0x5c, 0xc9, 0xf6, 0x49, 0x45, 0x24, 0x45, 0xc9, 0x47, 0x95, 0x00, 0xe1, 0x5b, 0x11, 0xae, 0x84, 0x1c, 0x97, 0x30, 0x24, 0xca, 0xaa, 0x54, 0xa9, 0x49, 0x2e, 0xa4, 0xb4, 0x15, 0xf0, 0xe3, 0x52, 0xc9, 0x76, 0x52, 0x43, 0x43, 0x42, 0x45, 0x12, 0xaa, 0xb9, 0xe5, 0x7b, 0x13, 0xd0, 0xd5, 0xe5, 0xfb, 0x26, 0xab, 0x33, 0xdd, 0x18, 0x15, 0x93, 0xdf, 0x0d, 0xa7, 0x70, 0xa2, 0xbe, 0xcc, 0xb8, 0x84, 0x45, 0x0a, 0x64, 0xbb, 0xc2, 0x31, 0x82, 0x3e, 0xff, 0xba, 0x21, 0xe8, 0x51, 0x75, 0xb6, 0xb5, 0xae, 0x6a, 0x46, 0x99, 0x3f, 0xe1, 0xa0, 0xcf, + 0xeb, 0x76, 0x1a, 0x74, 0x6a, 0x95, 0x52, 0x21, 0x66, 0xa0, 0x0c, 0xd6, 0x6b, 0x30, 0xc6, 0xc5, 0xf5, 0x41, 0xa3, 0xf7, 0x81, 0x23, 0xba, 0x78, 0x60, 0x45, 0x6f, 0x66, 0xcd, 0xed, 0xda, 0xb4, 0xa9, 0x96, 0xcb, 0xd3, 0xe0, 0x07, 0x36, 0x5d, 0xb5, 0xec, 0x50, 0x83, 0xef, 0x97, 0xd0, 0x68, 0xc4, 0x9f, 0x55, 0x65, 0x47, 0xfc, 0x3b, 0x02, 0xfb, 0x3b, 0x7f, 0xbc, 0x97, 0x1f, 0x7f, 0x50, 0x9e, 0x3f, 0x0b, 0xf0, 0x1e, 0x7f, 0x6c, 0xe1, 0x4f, 0xd4, 0x15, 0x1c, 0x23, 0xac, 0x22, 0x0e, 0x26, 0xe5, 0x21, 0xb8, 0xe3, 0x01, 0x2b, 0xae, 0x86, 0xc7, 0x0e, 0x55, 0x80, 0xa0, 0xc4, 0xa4, 0x98, 0xc2, 0x09, 0xdc, 0x62, 0x8a, 0xe0, 0xf2, 0xfe, 0x45, 0xfc, 0xa1, 0x5e, 0x36, 0xd3, 0xbb, 0x68, 0x51, 0x43, 0x0e, 0xdd, 0x8f, 0xa3, 0x6d, 0x39, 0x8c, 0x04, 0xa3, 0xd0, 0x92, 0x0c, 0xfa, 0xfd, + 0x5e, 0x7f, 0x88, 0x2d, 0x2e, 0x9a, 0x89, 0x2c, 0x8a, 0xf3, 0x45, 0x8b, 0x39, 0x61, 0x71, 0xa5, 0xb5, 0xec, 0xe9, 0x33, 0xa8, 0x3f, 0x74, 0x79, 0x4b, 0x59, 0x41, 0x71, 0x6d, 0xe1, 0xe8, 0x1d, 0xb5, 0x9b, 0x37, 0x24, 0x87, 0x9b, 0xa2, 0x05, 0x06, 0x87, 0xd4, 0x3b, 0xf4, 0xad, 0x43, 0xeb, 0xce, 0x8c, 0x97, 0xad, 0x73, 0x38, 0xa5, 0xc6, 0x60, 0xcf, 0x9a, 0xc9, 0x9a, 0xfa, 0x6d, 0x7d, 0xa5, 0xcd, 0x17, 0xff, 0xf8, 0x2c, 0x19, 0xa7, 0x1d, 0xd1, 0xe6, 0x50, 0xa0, 0xca, 0xab, 0x69, 0x6b, 0x48, 0xdf, 0xb2, 0xef, 0x16, 0x77, 0xd0, 0x6d, 0x52, 0x25, 0x57, 0xaf, 0xae, 0x9e, 0xbd, 0x30, 0x4c, 0x9e, 0xd5, 0xd9, 0x46, 0x7d, 0xd5, 0x01, 0x83, 0xa7, 0x75, 0xa6, 0xad, 0x7e, 0xcf, 0x48, 0x35, 0xcd, 0xda, 0x3e, 0x03, 0x0b, 0x6f, 0x53, 0x3f, 0xc3, 0x67, 0xd2, 0x41, 0xe2, 0x31, 0xee, + 0x4c, 0xda, 0x07, 0x08, 0xda, 0x90, 0x3d, 0x93, 0x86, 0x97, 0x8c, 0x21, 0x7b, 0x26, 0x6d, 0xc5, 0x05, 0x8a, 0xd1, 0x61, 0xb0, 0x88, 0x21, 0x17, 0x25, 0xfa, 0xb3, 0x87, 0x86, 0x76, 0x4e, 0x70, 0xcb, 0x90, 0x01, 0xb8, 0x17, 0x7d, 0x9c, 0x4f, 0x04, 0x00, 0x95, 0x8d, 0x5a, 0xad, 0x0e, 0xaa, 0x03, 0x21, 0x9f, 0x0f, 0xb1, 0x01, 0x14, 0xe4, 0xb3, 0x01, 0x68, 0x17, 0x23, 0x86, 0xaa, 0x12, 0x31, 0xca, 0x5c, 0xb6, 0x77, 0x55, 0x16, 0xda, 0x82, 0xd8, 0x00, 0x04, 0x88, 0x96, 0xcd, 0x67, 0xe3, 0x0a, 0x59, 0x98, 0xa2, 0x1a, 0xb3, 0xa0, 0x95, 0x9f, 0xde, 0x90, 0x08, 0x31, 0x40, 0x6d, 0x0d, 0x9d, 0x41, 0x56, 0x2e, 0x98, 0xf7, 0x84, 0x93, 0xcb, 0x23, 0x2f, 0xb9, 0x70, 0xba, 0x22, 0xc8, 0xa4, 0xb6, 0xa2, 0xb7, 0x41, 0x5a, 0x73, 0x11, 0x53, 0x87, 0x75, 0xb9, 0x8f, 0xf7, 0xe4, 0x10, + 0x79, 0xcc, 0xae, 0x48, 0xe4, 0x31, 0xbb, 0x22, 0x91, 0xc7, 0x68, 0x52, 0xea, 0xc9, 0x30, 0x79, 0x70, 0x35, 0x5a, 0x17, 0xa5, 0x3e, 0x67, 0x99, 0x3c, 0x70, 0xfe, 0x73, 0xfc, 0xd8, 0x50, 0xed, 0x78, 0x93, 0xe7, 0xe2, 0x99, 0xb6, 0x3d, 0x51, 0x55, 0xfc, 0xae, 0xcd, 0xb3, 0x9f, 0xdc, 0xdb, 0x50, 0x35, 0x7d, 0xef, 0xba, 0x9d, 0x97, 0x93, 0x5a, 0x59, 0xf8, 0xc0, 0x81, 0xb0, 0x0c, 0x78, 0xda, 0x7a, 0xc3, 0xeb, 0xce, 0x6d, 0xfa, 0xd2, 0x6b, 0x15, 0x4d, 0x67, 0x1a, 0xdb, 0x06, 0x2e, 0x7c, 0x7d, 0xef, 0xf1, 0xaf, 0x9f, 0x6c, 0x9d, 0xe8, 0x3d, 0x10, 0xa4, 0xde, 0x11, 0xca, 0x04, 0xe7, 0x77, 0x26, 0x78, 0x2e, 0x1d, 0x4d, 0xc9, 0xe2, 0xfc, 0xce, 0x12, 0x41, 0x7e, 0x27, 0x9d, 0xcd, 0xef, 0xf4, 0x2c, 0x9f, 0xb2, 0x99, 0xcb, 0x74, 0xf2, 0x01, 0x0d, 0xb1, 0x98, 0x56, 0xce, 0xeb, + 0x5c, 0x4c, 0x7e, 0x82, 0xf2, 0x3a, 0x7d, 0x37, 0x95, 0xd7, 0x89, 0x85, 0x76, 0xf3, 0x99, 0x9d, 0x1c, 0x19, 0xca, 0xcd, 0x25, 0x77, 0x22, 0x66, 0x94, 0xbc, 0x6c, 0x6a, 0xb4, 0x3f, 0xc5, 0xe0, 0x1a, 0xdc, 0x0f, 0x75, 0x53, 0x09, 0xb1, 0x23, 0x29, 0xb3, 0x42, 0x53, 0xa0, 0x04, 0x67, 0xfa, 0xb3, 0x62, 0xf3, 0xf1, 0xb4, 0x50, 0x44, 0x2e, 0x2b, 0xd4, 0x6e, 0x21, 0x2b, 0x94, 0xed, 0x26, 0x9a, 0x21, 0x90, 0x5e, 0x52, 0x8a, 0xa9, 0x47, 0xf0, 0xd4, 0xc9, 0x83, 0xde, 0x67, 0xf2, 0x40, 0x29, 0xa1, 0x34, 0xf6, 0xdd, 0x14, 0xcd, 0xc8, 0xae, 0xcb, 0xcd, 0x5f, 0x55, 0xb0, 0xac, 0x22, 0xe7, 0x10, 0xab, 0x48, 0x05, 0x66, 0x15, 0x41, 0x99, 0x26, 0xed, 0x77, 0x5d, 0x3b, 0xba, 0xf7, 0xab, 0xf7, 0xf4, 0xb5, 0x27, 0xe1, 0xfc, 0x59, 0xbd, 0x10, 0xa3, 0x3e, 0x8a, 0x75, 0x70, 0x07, 0x71, + 0x32, 0xa9, 0xeb, 0x00, 0x94, 0xc8, 0x09, 0x08, 0x50, 0x8b, 0x71, 0x73, 0x55, 0x80, 0xe9, 0xc8, 0x64, 0x19, 0x71, 0xc5, 0x8a, 0xb2, 0x00, 0x05, 0x4e, 0xb9, 0x66, 0x0a, 0xd9, 0xe1, 0x77, 0xbe, 0x99, 0x86, 0xf8, 0xad, 0x8d, 0x80, 0x68, 0x5b, 0x15, 0x29, 0xf1, 0x15, 0x9a, 0x8d, 0x18, 0xe8, 0xa9, 0x64, 0xb1, 0x07, 0x7e, 0x0e, 0x7b, 0x80, 0x81, 0x5b, 0x40, 0x08, 0xe7, 0x32, 0x99, 0x73, 0xc5, 0x20, 0x00, 0xc9, 0x67, 0x8a, 0x1f, 0x91, 0xed, 0x33, 0x1b, 0x23, 0x45, 0xe6, 0x90, 0xbf, 0x10, 0x6e, 0xa8, 0xc0, 0x24, 0x81, 0x82, 0xf2, 0xc5, 0xbb, 0xa1, 0xa0, 0xba, 0x03, 0xcd, 0xf5, 0x96, 0xda, 0x8e, 0x35, 0xe5, 0x0d, 0x23, 0xb5, 0xf6, 0x92, 0xa1, 0xe3, 0x83, 0x86, 0xa0, 0x5e, 0x0c, 0xf7, 0x5e, 0xb7, 0xbb, 0x47, 0xa2, 0x96, 0xae, 0xda, 0xd1, 0x13, 0x6e, 0x3a, 0xf2, 0xc2, 0x81, + 0x5d, 0x97, 0x93, 0x27, 0xdb, 0x13, 0x81, 0x98, 0xa1, 0x7c, 0xa0, 0x01, 0xce, 0xdc, 0x97, 0x58, 0x56, 0x96, 0xa9, 0xfb, 0xd6, 0x1f, 0x7b, 0x34, 0x34, 0xb2, 0xbf, 0xcd, 0xee, 0x6b, 0x58, 0x53, 0x56, 0xb3, 0xa9, 0x23, 0x44, 0x91, 0xde, 0x9e, 0x62, 0xab, 0x86, 0x56, 0xd4, 0xef, 0x88, 0xda, 0x46, 0x6f, 0x7b, 0x64, 0x0c, 0x91, 0xb4, 0x40, 0x71, 0x62, 0x3c, 0xd1, 0x45, 0xfa, 0x3f, 0x30, 0x0e, 0xb4, 0x92, 0xb8, 0x9c, 0x54, 0x84, 0x04, 0x50, 0x22, 0x2e, 0xa5, 0x9a, 0x65, 0x05, 0x5a, 0x04, 0xdc, 0x59, 0x92, 0xab, 0x71, 0xb9, 0xc6, 0x2b, 0xa1, 0x82, 0x66, 0x3e, 0x18, 0x15, 0x84, 0x40, 0x41, 0xfa, 0xbf, 0x05, 0x14, 0x84, 0xa0, 0x9f, 0x42, 0x44, 0x90, 0x10, 0x9f, 0xb8, 0x14, 0x22, 0x08, 0xe1, 0x14, 0xb3, 0x60, 0xa0, 0x2c, 0x20, 0x71, 0xfe, 0x23, 0x2c, 0x18, 0xe8, 0xab, 0x59, + 0x30, 0x10, 0x82, 0x78, 0xbe, 0xbb, 0x30, 0x38, 0xa0, 0x46, 0xc0, 0x0f, 0xcc, 0x6b, 0xc3, 0x38, 0x39, 0x7d, 0xf6, 0x00, 0xa7, 0xcf, 0x50, 0x09, 0xe6, 0x04, 0x20, 0xc4, 0x01, 0x2c, 0x48, 0xac, 0xcf, 0xcc, 0x80, 0xce, 0xb9, 0xc5, 0x33, 0x99, 0x0a, 0x32, 0x67, 0xf9, 0xac, 0x22, 0x6e, 0xb5, 0xa1, 0xed, 0x0d, 0x17, 0x65, 0x2c, 0x21, 0x28, 0x5a, 0x4c, 0x53, 0x62, 0x41, 0xe2, 0xac, 0xa0, 0x29, 0x42, 0x69, 0xf3, 0x86, 0xc3, 0x1e, 0x04, 0xf6, 0x94, 0x87, 0x7c, 0x5e, 0x8f, 0x3f, 0xb8, 0x94, 0xa8, 0x16, 0xab, 0xa4, 0x25, 0x76, 0xc0, 0x5c, 0x61, 0x71, 0xbc, 0x24, 0x26, 0x91, 0x45, 0xa5, 0x0c, 0xf9, 0x07, 0xd7, 0x2e, 0xe6, 0xc6, 0x11, 0x8a, 0x8b, 0x32, 0x09, 0xf5, 0xd2, 0xfb, 0x5f, 0xc8, 0x85, 0xc6, 0x12, 0xf9, 0xf9, 0x70, 0x47, 0xae, 0xba, 0x11, 0x6d, 0x34, 0x37, 0xd1, 0x82, + 0x04, 0x43, 0x8a, 0x48, 0x46, 0x34, 0x87, 0x0b, 0x8e, 0x23, 0xdc, 0xe5, 0x14, 0xaf, 0x7f, 0x31, 0xd2, 0x32, 0x6b, 0x3e, 0xdd, 0x54, 0x4b, 0x4c, 0x99, 0x63, 0x91, 0x49, 0x09, 0x02, 0x1d, 0xc1, 0x20, 0x74, 0xb6, 0xb4, 0x54, 0x56, 0x8a, 0x00, 0x54, 0x6c, 0x7d, 0x72, 0x8e, 0xc5, 0x53, 0xcc, 0x16, 0x65, 0xcf, 0x61, 0x42, 0xcb, 0x22, 0x53, 0xaa, 0x28, 0xa5, 0xfe, 0xde, 0x23, 0x75, 0x13, 0x49, 0xcf, 0x9d, 0x36, 0x7d, 0x72, 0xdf, 0x47, 0x27, 0xe6, 0x9e, 0x3b, 0xd2, 0xd2, 0x75, 0xf2, 0x85, 0x6d, 0x55, 0x7b, 0xb7, 0x4f, 0xfa, 0xaa, 0x95, 0x16, 0x5d, 0xb4, 0x6d, 0xb2, 0xb9, 0x6d, 0x6e, 0x20, 0x42, 0x91, 0xbb, 0x8e, 0x5f, 0x08, 0x75, 0x6e, 0xae, 0x3d, 0xec, 0x2a, 0xdd, 0x75, 0x65, 0x7f, 0x43, 0xdf, 0xd9, 0x97, 0xb6, 0x9f, 0x7a, 0xfd, 0x5c, 0x87, 0x2e, 0xd0, 0x50, 0x84, 0x51, + 0x28, 0x25, 0xeb, 0xef, 0x1a, 0xfb, 0xff, 0x65, 0x3e, 0x20, 0x49, 0xb4, 0xc3, 0x35, 0x72, 0x1a, 0xeb, 0xec, 0x3e, 0xe2, 0xdb, 0xec, 0xc4, 0xd7, 0x35, 0x27, 0xa0, 0x87, 0x94, 0x2c, 0xb6, 0x98, 0x68, 0x11, 0x52, 0xbc, 0x80, 0x45, 0x42, 0x67, 0x6e, 0x8a, 0xb9, 0x9b, 0xa3, 0xbc, 0x88, 0x68, 0xa8, 0x2f, 0x68, 0x34, 0xe2, 0x5c, 0x65, 0x14, 0x3e, 0x85, 0x77, 0x77, 0xe6, 0x84, 0x60, 0x67, 0xc6, 0x72, 0xbc, 0x99, 0xd6, 0x7b, 0x58, 0x35, 0x04, 0xd7, 0x15, 0xc5, 0x56, 0xa2, 0xa4, 0x10, 0xb2, 0x77, 0xa5, 0xe6, 0x58, 0x0d, 0x15, 0x07, 0xbc, 0x25, 0xa5, 0x45, 0x3e, 0x5c, 0xdc, 0x3a, 0x93, 0x55, 0x1a, 0x64, 0x78, 0x1d, 0x24, 0xe6, 0xf3, 0xf3, 0x22, 0x34, 0x72, 0x8a, 0xfc, 0xec, 0x24, 0xcb, 0x6e, 0x89, 0xe6, 0x2a, 0xb3, 0xc9, 0x01, 0x32, 0x8a, 0xe9, 0x5d, 0xe0, 0xac, 0x1f, 0x6f, + 0xea, 0xdc, 0xd9, 0xe9, 0x87, 0xda, 0xfb, 0x90, 0xa3, 0x2f, 0x56, 0x3f, 0x10, 0x35, 0xf4, 0xd2, 0x74, 0xcb, 0xd1, 0xe7, 0xe6, 0xba, 0x0f, 0x6f, 0x68, 0x8f, 0x98, 0xf4, 0x76, 0xf2, 0xa4, 0xe7, 0xa1, 0xed, 0xcd, 0x1b, 0xb0, 0xc4, 0xab, 0xbc, 0x68, 0x4c, 0xfa, 0xee, 0xd9, 0x3f, 0x6a, 0xfb, 0x81, 0x56, 0x56, 0x52, 0xe5, 0x2e, 0x94, 0x43, 0x85, 0x05, 0x26, 0x7c, 0xbd, 0xcd, 0x45, 0x15, 0x13, 0xa7, 0xd7, 0x28, 0x0b, 0x14, 0x43, 0x2a, 0x73, 0xb0, 0xae, 0xdd, 0xad, 0xb1, 0xd9, 0x6b, 0x5d, 0x68, 0x38, 0xbc, 0xf5, 0xfd, 0xa3, 0x63, 0xc5, 0x76, 0xa3, 0xec, 0x57, 0x3b, 0xb6, 0xa2, 0x01, 0xc1, 0x3b, 0x6a, 0xb0, 0x77, 0x6f, 0x77, 0x97, 0xdf, 0x68, 0xaf, 0x8f, 0xb5, 0xa9, 0x30, 0x59, 0x2c, 0x80, 0x7a, 0xec, 0xcf, 0xd4, 0x38, 0xe6, 0x86, 0xe8, 0x4e, 0x76, 0xc0, 0xcd, 0x44, 0x1c, + 0x85, 0x4a, 0xcc, 0xcb, 0x9e, 0x9e, 0xa3, 0x5a, 0x50, 0x04, 0x34, 0x93, 0x50, 0x05, 0x2d, 0x06, 0xce, 0x3e, 0x92, 0xa0, 0x18, 0x32, 0x4b, 0x41, 0xba, 0x3b, 0xc3, 0x3e, 0x0a, 0x15, 0x50, 0x5d, 0x4d, 0xc0, 0x1b, 0xf0, 0x40, 0xe5, 0x22, 0xc9, 0xa0, 0x74, 0xf9, 0xfd, 0x10, 0x97, 0xf6, 0x13, 0x2d, 0x95, 0x59, 0x9d, 0x10, 0x6c, 0x89, 0x94, 0x45, 0xae, 0x57, 0xa9, 0xfc, 0x55, 0x03, 0x35, 0xf1, 0xfe, 0xb8, 0xf5, 0xd6, 0x5d, 0xfb, 0xf6, 0x1d, 0x39, 0xe4, 0xaa, 0x59, 0x13, 0x6b, 0x5a, 0x5b, 0x17, 0xc2, 0x46, 0xc3, 0xc8, 0xe6, 0xd9, 0xf2, 0xc1, 0x4b, 0x7b, 0x9a, 0x67, 0x87, 0x4a, 0x9b, 0x82, 0xba, 0x8a, 0xa9, 0x87, 0xa6, 0xf6, 0x7d, 0xaa, 0x86, 0x5a, 0xa5, 0x95, 0x9b, 0xed, 0x66, 0x53, 0x20, 0xe6, 0x48, 0x0e, 0x9c, 0x18, 0xee, 0xf0, 0x37, 0x45, 0xac, 0x3c, 0x2d, 0x99, 0xb6, + 0xa8, 0x7f, 0x6f, 0xc7, 0xba, 0xc3, 0x6e, 0x5d, 0x65, 0x7d, 0xb3, 0x2b, 0x31, 0xd5, 0x53, 0xda, 0x9a, 0xe0, 0xeb, 0x2f, 0xfc, 0x4f, 0xfa, 0xab, 0xb4, 0x94, 0x18, 0x03, 0x45, 0x49, 0x7d, 0x3f, 0x90, 0xc8, 0xc6, 0xcc, 0xa4, 0x58, 0xaa, 0x06, 0x24, 0x34, 0xdf, 0x18, 0x9a, 0xe6, 0xf4, 0x52, 0x82, 0x90, 0x4a, 0xc4, 0x12, 0x29, 0xd2, 0xbf, 0x1c, 0x29, 0x94, 0x98, 0x10, 0x41, 0xbb, 0x71, 0x0a, 0x9f, 0x6d, 0xd1, 0xa8, 0xec, 0xba, 0x44, 0xc2, 0x67, 0x86, 0x72, 0xd0, 0x70, 0xd6, 0x52, 0x57, 0xfc, 0x9d, 0xbd, 0xb3, 0x66, 0xec, 0xdf, 0xfb, 0xb7, 0x93, 0x0d, 0x99, 0x8e, 0x40, 0x4c, 0x88, 0xd1, 0x89, 0x5b, 0xde, 0x17, 0x10, 0x2b, 0xf5, 0x47, 0x0e, 0x82, 0x69, 0xcd, 0x40, 0x4f, 0x77, 0x47, 0x5b, 0x53, 0x43, 0x7d, 0x6d, 0x65, 0x45, 0x59, 0x29, 0xf4, 0x41, 0x3d, 0x41, 0x39, 0x02, + 0xb5, 0xa3, 0x09, 0x8c, 0x15, 0x89, 0xc0, 0xf3, 0x6c, 0x40, 0xb6, 0x8d, 0x90, 0x6c, 0x4c, 0x08, 0xee, 0x43, 0xa9, 0xe7, 0x19, 0x5b, 0x59, 0xc0, 0x2b, 0xca, 0x22, 0x18, 0xab, 0xa8, 0x6f, 0xca, 0x65, 0x96, 0x80, 0x0d, 0xcf, 0x65, 0x91, 0xde, 0xd1, 0x1b, 0x6b, 0x80, 0x8b, 0xe0, 0x5f, 0x18, 0x39, 0x53, 0xbe, 0xba, 0xc6, 0xd5, 0xb6, 0xef, 0x52, 0x7f, 0xd9, 0xcc, 0xa6, 0x11, 0x2f, 0xd4, 0xb5, 0xfa, 0x50, 0xed, 0x60, 0x63, 0x7c, 0x6d, 0xad, 0xeb, 0xe0, 0xd1, 0x7d, 0xfb, 0x76, 0xdd, 0x6a, 0x8d, 0xf7, 0x57, 0xd6, 0xae, 0xae, 0xf6, 0xab, 0x54, 0x7a, 0x79, 0x49, 0xcd, 0xe5, 0x43, 0x1b, 0x1f, 0x98, 0xaa, 0x00, 0xe0, 0xc3, 0xe4, 0xbc, 0xa5, 0xa0, 0xce, 0x5a, 0xe2, 0x86, 0x93, 0xa8, 0xa0, 0x16, 0x2e, 0x86, 0xfa, 0x76, 0xb7, 0x6a, 0x35, 0x49, 0x93, 0xea, 0xb2, 0xa6, 0xfe, 0xb2, + 0x8e, 0xbd, 0x7d, 0x45, 0xda, 0x40, 0x7d, 0x71, 0x87, 0x1e, 0x4e, 0x0e, 0x6b, 0xa4, 0xd1, 0xdf, 0x39, 0x74, 0x62, 0xa0, 0xd9, 0x1e, 0x0b, 0x98, 0xe0, 0xe4, 0x91, 0x6b, 0x7b, 0x12, 0xad, 0xa5, 0xa9, 0xa9, 0xb8, 0x44, 0x25, 0xa9, 0xc7, 0xfc, 0x3b, 0xd0, 0x57, 0xb1, 0xc0, 0x35, 0xe1, 0x24, 0xaa, 0x51, 0xe5, 0x13, 0x34, 0x19, 0x9c, 0x22, 0x01, 0x01, 0xef, 0x12, 0xfc, 0x81, 0x2b, 0x36, 0xc0, 0x5b, 0x92, 0x1e, 0x15, 0xb7, 0x09, 0xf8, 0x1c, 0x36, 0xcc, 0xbe, 0xea, 0x04, 0x4e, 0xb6, 0x9e, 0x70, 0x42, 0xc8, 0xe7, 0x26, 0x48, 0x65, 0x65, 0x90, 0x46, 0x11, 0xe5, 0x40, 0x59, 0xa1, 0x8c, 0xc9, 0x07, 0xc6, 0x67, 0x2d, 0x55, 0x23, 0x4d, 0x13, 0x77, 0x0e, 0xfa, 0xfd, 0x03, 0x27, 0xc6, 0x93, 0xe3, 0x75, 0x36, 0x73, 0x45, 0x5f, 0xe2, 0x51, 0xb0, 0x6d, 0x68, 0xfa, 0x9f, 0xfe, 0xe1, + 0xfc, 0x0f, 0xee, 0xe9, 0x68, 0x87, 0xfa, 0x78, 0xd7, 0xa3, 0x9b, 0x4b, 0xb5, 0xc1, 0x24, 0x39, 0xdc, 0xd3, 0x64, 0x8f, 0x05, 0x2d, 0x15, 0x1b, 0xce, 0x8f, 0x8c, 0xdc, 0xb3, 0xa9, 0xb2, 0xa0, 0xb4, 0x31, 0xe0, 0xad, 0x2f, 0xb5, 0x9e, 0xac, 0x71, 0x9f, 0x9e, 0xb8, 0xff, 0x61, 0x9e, 0x90, 0xa7, 0xac, 0x6f, 0xaa, 0xd2, 0xd9, 0x54, 0x1b, 0xc5, 0x3a, 0x7b, 0x4d, 0xfa, 0x21, 0xfa, 0xd7, 0xcc, 0x33, 0xd0, 0xae, 0xe9, 0x22, 0xbe, 0xcc, 0x71, 0x52, 0x22, 0xbb, 0xa6, 0x0b, 0x10, 0xd2, 0x1a, 0x6c, 0xc4, 0x14, 0x01, 0x46, 0x8c, 0x39, 0x29, 0x91, 0x6d, 0x93, 0x7f, 0x9b, 0xb3, 0x6f, 0x2a, 0x08, 0x11, 0x9c, 0x86, 0x22, 0xf1, 0x5c, 0xae, 0x9d, 0x83, 0x4f, 0x7c, 0xb1, 0x9d, 0x83, 0x4f, 0x93, 0x70, 0x65, 0x83, 0x6c, 0x12, 0x6b, 0x0d, 0x34, 0x78, 0xa4, 0x34, 0x25, 0x9d, 0xe3, 0x3b, + 0xe7, 0x18, 0x3e, 0x2b, 0xf5, 0x45, 0xa9, 0x2e, 0x6d, 0x41, 0xaf, 0x3f, 0x88, 0x0d, 0x20, 0x1c, 0x5e, 0xfc, 0x20, 0x03, 0x48, 0x98, 0x75, 0xc8, 0x0a, 0x3e, 0xdf, 0x92, 0xa4, 0x0a, 0x58, 0x3c, 0x39, 0xdc, 0x17, 0x07, 0x8f, 0x26, 0x3c, 0x6d, 0xdb, 0xbb, 0x2b, 0x57, 0x67, 0xf8, 0x55, 0x2a, 0x9b, 0x8f, 0x3c, 0xbf, 0xef, 0x8e, 0xab, 0xf5, 0x11, 0x89, 0x45, 0xaf, 0x2f, 0xaa, 0x5b, 0x0b, 0x95, 0x79, 0x97, 0xaf, 0xe9, 0xe2, 0xec, 0xc4, 0xc5, 0x2d, 0x55, 0x19, 0x03, 0xa9, 0xa1, 0xf2, 0xcb, 0x9f, 0x1f, 0x3f, 0x3d, 0x8c, 0x0c, 0xa4, 0x58, 0x13, 0x26, 0x46, 0x44, 0x94, 0x1a, 0x03, 0x03, 0xef, 0xbf, 0x36, 0xfe, 0xd8, 0xde, 0x64, 0x57, 0x02, 0x27, 0x61, 0xd8, 0x8b, 0xba, 0xa7, 0x6b, 0x0e, 0xf6, 0x0c, 0xb6, 0x9e, 0xf8, 0xca, 0x6d, 0x3b, 0x5f, 0x39, 0xdf, 0x5f, 0x9f, 0xc0, 0x3a, + 0xaa, 0x77, 0xe1, 0x2f, 0xd4, 0xf7, 0x68, 0x8a, 0x28, 0x23, 0x76, 0x25, 0x55, 0x0e, 0xa8, 0x96, 0x10, 0x15, 0xb6, 0x1c, 0xbb, 0xc9, 0xdc, 0x8c, 0xe4, 0x32, 0xd4, 0x36, 0xf1, 0x11, 0x77, 0x84, 0xc0, 0x16, 0x5a, 0x0d, 0x2b, 0x35, 0xc0, 0xd6, 0x02, 0x9c, 0x91, 0x41, 0x3f, 0x2a, 0x47, 0x2b, 0x93, 0x32, 0x34, 0x51, 0x06, 0xca, 0xd8, 0x52, 0xc2, 0x09, 0xbe, 0xfe, 0x86, 0x56, 0x48, 0x24, 0x69, 0xca, 0x2b, 0xd4, 0x41, 0x8e, 0xb6, 0x4c, 0x16, 0xdf, 0xa1, 0x35, 0x82, 0x81, 0xc0, 0xda, 0xc9, 0x9d, 0x4d, 0x7b, 0x5e, 0x3e, 0xd7, 0xd3, 0x7d, 0xfe, 0xda, 0xe1, 0x63, 0x57, 0x9b, 0x12, 0xca, 0x90, 0xc5, 0x1c, 0xa9, 0xe9, 0x28, 0xeb, 0xb9, 0x6d, 0x38, 0x52, 0x3e, 0x7a, 0xdb, 0xbd, 0x66, 0xc3, 0x4b, 0x06, 0xcd, 0xce, 0xf4, 0x94, 0x3d, 0x6c, 0x53, 0xf6, 0x9e, 0xff, 0xea, 0xbe, 0x7d, + 0x5f, 0x3d, 0xd7, 0xd3, 0x55, 0x3f, 0x6c, 0x90, 0xa9, 0x55, 0x56, 0xbd, 0xa2, 0x62, 0xe2, 0xe4, 0xc0, 0xba, 0x73, 0x1b, 0xca, 0x09, 0x72, 0xe1, 0xcd, 0xf4, 0x25, 0xea, 0xf7, 0xd4, 0x3b, 0x84, 0x1a, 0xda, 0xd4, 0x81, 0xa4, 0x37, 0xe1, 0x57, 0x67, 0xea, 0x79, 0x66, 0xe9, 0xdb, 0xe6, 0x40, 0x4f, 0x3c, 0x16, 0x0a, 0xd8, 0x0a, 0x82, 0x34, 0x4b, 0x48, 0xc4, 0x0d, 0x32, 0xd4, 0x37, 0x65, 0xa0, 0x2a, 0x37, 0x21, 0xb7, 0x09, 0x08, 0xaa, 0x08, 0xb1, 0x38, 0x53, 0xf0, 0xba, 0xd1, 0x22, 0x33, 0xba, 0x8c, 0xde, 0xb0, 0xd5, 0xe0, 0xb9, 0x6c, 0x74, 0xea, 0x24, 0xee, 0x86, 0xb1, 0xba, 0xc6, 0xb1, 0x7a, 0xa7, 0x44, 0xe7, 0x34, 0x35, 0xa6, 0xb7, 0x5d, 0x36, 0x86, 0x10, 0x7b, 0xa7, 0x94, 0x12, 0x2b, 0x80, 0x58, 0x62, 0x37, 0x5c, 0x4e, 0xef, 0x28, 0x2a, 0x19, 0x74, 0x19, 0x83, 0x4e, + 0x5d, 0x55, 0x2c, 0xb8, 0xba, 0x68, 0x95, 0xb9, 0x68, 0x55, 0x75, 0xa5, 0x2d, 0x31, 0xd1, 0x1a, 0x0c, 0xb7, 0x4d, 0xc4, 0x6c, 0xb1, 0x9a, 0x96, 0xa2, 0xdb, 0xc8, 0x77, 0x4d, 0xda, 0xf9, 0xbf, 0xca, 0x4d, 0x72, 0xb3, 0x89, 0x94, 0x6a, 0x4d, 0xf3, 0x52, 0x82, 0xe5, 0xfa, 0xff, 0x33, 0x4d, 0xd1, 0x34, 0xb4, 0x7f, 0x52, 0xa8, 0xfa, 0x44, 0x67, 0x85, 0x5b, 0x89, 0x73, 0x8e, 0x69, 0x7c, 0x38, 0xc7, 0xb0, 0x1c, 0x75, 0xd9, 0x11, 0xf1, 0x97, 0x96, 0xfa, 0x8b, 0xfd, 0x38, 0x32, 0x83, 0xde, 0xca, 0xc4, 0xbf, 0x14, 0x97, 0x56, 0xc2, 0x12, 0x94, 0x88, 0x29, 0xb3, 0x13, 0x08, 0x4c, 0xb8, 0x26, 0x40, 0xe5, 0x72, 0x95, 0xb1, 0x28, 0x4f, 0xf0, 0xaf, 0x46, 0x63, 0xb9, 0xd7, 0x59, 0x6e, 0x36, 0x04, 0xaf, 0x98, 0xca, 0x6a, 0x3b, 0x23, 0x81, 0x90, 0x16, 0x78, 0xbd, 0xb1, 0xb0, 0x5b, + 0xf3, 0x72, 0x8b, 0xd7, 0xee, 0x69, 0x5a, 0x5f, 0x53, 0xbb, 0xbe, 0xc1, 0xed, 0x2e, 0x6c, 0x7a, 0x45, 0xe3, 0x09, 0x55, 0x16, 0x7a, 0x49, 0x6d, 0x30, 0x10, 0xe9, 0xac, 0x2d, 0x33, 0x9d, 0x95, 0x6b, 0x24, 0x22, 0x8d, 0x1c, 0xb4, 0x7b, 0x62, 0x96, 0x60, 0xac, 0xa8, 0xd8, 0x9e, 0x70, 0xd5, 0x93, 0x94, 0xab, 0xcc, 0xad, 0x55, 0x18, 0x6c, 0xea, 0x4e, 0xb9, 0xc9, 0x67, 0x49, 0xff, 0x71, 0x63, 0x6c, 0x63, 0x51, 0x6c, 0xb4, 0xd9, 0x1f, 0x48, 0x0e, 0x95, 0x95, 0x4d, 0x95, 0x6f, 0x00, 0x26, 0x8b, 0xd7, 0x2c, 0xef, 0x54, 0xdb, 0x0c, 0x0a, 0xad, 0xbb, 0xcc, 0xf5, 0x1d, 0x95, 0x53, 0xaf, 0x77, 0xaa, 0xd0, 0x3c, 0x3e, 0x9e, 0x7e, 0x9a, 0xfa, 0x31, 0x1c, 0x53, 0x23, 0xe2, 0xc9, 0x36, 0x60, 0xc0, 0x38, 0x3a, 0xaf, 0x40, 0x45, 0x0f, 0x48, 0x01, 0xf1, 0x47, 0x16, 0xb9, 0x6c, 0x24, + 0x8c, 0x5e, 0xa3, 0x97, 0xc3, 0x80, 0x66, 0xb2, 0xd0, 0x78, 0xb7, 0xae, 0x4a, 0x4b, 0xfd, 0x98, 0x87, 0x29, 0x5f, 0x9f, 0xde, 0x92, 0x3a, 0x51, 0x43, 0xbd, 0xf3, 0xde, 0x0b, 0xe0, 0xc7, 0x2d, 0xfd, 0x08, 0x9f, 0x8c, 0xc8, 0x9b, 0x77, 0x1d, 0xee, 0x68, 0x24, 0x7f, 0x9b, 0xc1, 0x87, 0xfe, 0x0a, 0xfa, 0x68, 0x98, 0x17, 0x11, 0xf3, 0x1a, 0xd9, 0xe1, 0x42, 0x42, 0x31, 0x0e, 0x2d, 0xb4, 0xbb, 0x30, 0xaf, 0x91, 0x1d, 0x88, 0x04, 0x37, 0x46, 0x05, 0x11, 0x48, 0x9e, 0x3e, 0x3b, 0x93, 0xeb, 0x83, 0x11, 0xd6, 0x00, 0x27, 0xdf, 0x31, 0x22, 0x68, 0x91, 0xc3, 0xbd, 0x95, 0x73, 0x39, 0x96, 0x68, 0x85, 0xb3, 0x96, 0xfc, 0x7a, 0xaf, 0x91, 0xcd, 0x5a, 0x12, 0x30, 0xa9, 0xf1, 0x10, 0xec, 0x25, 0xc2, 0x44, 0x82, 0xf7, 0x3a, 0x59, 0xbd, 0x88, 0xf3, 0x8b, 0x3a, 0xc4, 0x82, 0xb0, 0xe7, + 0x0f, 0xf3, 0xc0, 0xec, 0x45, 0xe1, 0x20, 0x8c, 0xcd, 0xbe, 0x88, 0xdf, 0xb9, 0x8a, 0xb8, 0xc1, 0xe5, 0x1e, 0x22, 0x2c, 0x82, 0x01, 0x88, 0xa9, 0x04, 0x90, 0x88, 0xfd, 0xd0, 0xb0, 0xa3, 0x79, 0xca, 0xa2, 0xfc, 0x4f, 0x44, 0x48, 0xab, 0xc3, 0x9b, 0x64, 0xde, 0xcd, 0x51, 0x2e, 0x2d, 0x31, 0x98, 0x49, 0xf7, 0xde, 0x81, 0x94, 0x2f, 0xe7, 0x77, 0xa1, 0x23, 0x25, 0x36, 0xbd, 0x0b, 0xcb, 0x62, 0x2e, 0x43, 0xb3, 0x16, 0x21, 0x18, 0x89, 0x48, 0x02, 0xbd, 0xb5, 0x15, 0xba, 0xf1, 0x12, 0x44, 0xbd, 0x92, 0xf1, 0x0c, 0xdb, 0x11, 0xd7, 0x91, 0x97, 0xea, 0x8a, 0xfd, 0xa0, 0xc9, 0x82, 0x38, 0x1f, 0xab, 0x88, 0x2a, 0xbf, 0xc7, 0x1f, 0x86, 0xff, 0xfc, 0xa8, 0x10, 0x40, 0x6c, 0x91, 0x7c, 0x1d, 0x99, 0x41, 0x58, 0xcc, 0xd0, 0xc9, 0x0d, 0xca, 0xb9, 0x7c, 0x91, 0x73, 0x03, 0xe2, 0x69, + 0xdb, 0xd1, 0x1d, 0x1b, 0x28, 0xd5, 0xe0, 0x9a, 0x1f, 0xee, 0x86, 0x4a, 0x3c, 0x3e, 0xb9, 0xc2, 0xff, 0x11, 0x37, 0x36, 0xa9, 0x26, 0x41, 0x26, 0x3a, 0x79, 0x9d, 0x1d, 0x27, 0xa4, 0x0b, 0x10, 0xc9, 0xdd, 0x8f, 0x99, 0xeb, 0x84, 0x82, 0xf0, 0x12, 0xab, 0x92, 0x49, 0x17, 0x20, 0xd0, 0xfa, 0x27, 0xc8, 0x6e, 0xb1, 0x84, 0x04, 0x34, 0x4a, 0x2b, 0x23, 0xa7, 0x78, 0x86, 0x5b, 0xde, 0x7c, 0xc0, 0xb4, 0x49, 0x04, 0xe1, 0x2d, 0xf4, 0xb8, 0x91, 0xd9, 0xa0, 0x51, 0xab, 0x94, 0xa8, 0x5c, 0xba, 0x1f, 0xfb, 0xb1, 0x3c, 0xd0, 0x18, 0x2e, 0x0b, 0xc6, 0x8b, 0x4b, 0x12, 0xe6, 0x04, 0x41, 0x62, 0x60, 0x87, 0xc4, 0x50, 0x68, 0x2d, 0x8b, 0xb4, 0x54, 0x83, 0xcb, 0x9f, 0xbe, 0xde, 0x5c, 0x3f, 0xd5, 0x11, 0x74, 0x55, 0x75, 0x97, 0x14, 0x25, 0x2d, 0xd7, 0x3f, 0x4d, 0x11, 0x1d, 0x6a, 0x57, + 0x81, 0x26, 0xe2, 0x0e, 0x14, 0xdd, 0x3b, 0x7f, 0x2f, 0x79, 0x8b, 0xa3, 0x66, 0x20, 0x5e, 0x96, 0x8a, 0x3b, 0x0a, 0x8c, 0xe0, 0x27, 0xf3, 0xf5, 0x78, 0x1e, 0x9d, 0x4d, 0x5f, 0x24, 0x2f, 0xe0, 0xfc, 0x88, 0x06, 0x76, 0x38, 0xb5, 0x18, 0xfe, 0x88, 0x74, 0x71, 0x36, 0xe3, 0xdf, 0x02, 0x37, 0x7e, 0x04, 0xe6, 0x9e, 0x63, 0x51, 0xb5, 0x82, 0x14, 0xff, 0x17, 0x3d, 0x85, 0x46, 0xb6, 0xb6, 0x9b, 0x60, 0x01, 0x73, 0xde, 0x0f, 0x79, 0x81, 0x4b, 0xc0, 0xb9, 0x7e, 0xd9, 0x1f, 0x52, 0xaa, 0x2c, 0xa2, 0xf4, 0x17, 0xd9, 0x3c, 0x1b, 0xe6, 0xe5, 0xf7, 0x22, 0xaf, 0xe3, 0xc4, 0x1a, 0x34, 0x8f, 0x2f, 0xd1, 0x93, 0xf0, 0xef, 0xc7, 0x88, 0x8e, 0x64, 0xab, 0x0f, 0x50, 0xb4, 0x1a, 0xb3, 0x11, 0x64, 0xc8, 0xc4, 0x00, 0x83, 0xfc, 0xff, 0x39, 0x56, 0x64, 0x34, 0xcd, 0x1f, 0x41, 0xe2, 0xb0, + 0x48, 0xf6, 0x39, 0x70, 0x95, 0x24, 0x6f, 0x30, 0x8c, 0x63, 0x22, 0x2b, 0x53, 0x87, 0x20, 0x74, 0x3f, 0x0b, 0x13, 0xc7, 0xe5, 0xd2, 0x7e, 0xb5, 0xf9, 0xbe, 0xc9, 0xc8, 0xf2, 0xfc, 0x21, 0x67, 0xb4, 0x52, 0xe0, 0x49, 0x6b, 0xaf, 0x5d, 0xc3, 0x14, 0x22, 0x6f, 0x48, 0x1b, 0x37, 0xdd, 0xd9, 0x3b, 0x79, 0xf9, 0x58, 0x57, 0x96, 0x42, 0xa4, 0xa8, 0x6f, 0x77, 0x6b, 0xed, 0x86, 0x56, 0x5f, 0x93, 0xd6, 0xad, 0x04, 0xf7, 0xdf, 0x78, 0x9c, 0xb9, 0x9e, 0xae, 0xc4, 0x44, 0x22, 0x50, 0x27, 0x2e, 0xbc, 0xc9, 0xfc, 0x01, 0xea, 0xc4, 0x10, 0x31, 0x96, 0x94, 0x07, 0xe5, 0xd0, 0xf7, 0x65, 0x30, 0x21, 0x1f, 0x57, 0x2f, 0x90, 0x2d, 0x45, 0xcc, 0xa2, 0x71, 0x89, 0xf1, 0x45, 0x35, 0x8a, 0x67, 0x05, 0x35, 0x8a, 0xc7, 0x73, 0x6a, 0x14, 0x87, 0x88, 0x10, 0x54, 0x9d, 0xfa, 0x80, 0x4f, 0x58, + 0xa3, 0x18, 0x43, 0x64, 0xd0, 0x4e, 0x1e, 0xe0, 0x95, 0x68, 0xa3, 0x50, 0x9d, 0x32, 0x7f, 0x38, 0x10, 0x52, 0x28, 0xab, 0xcf, 0x8d, 0xcf, 0x1c, 0x77, 0x98, 0xdb, 0x87, 0x36, 0x97, 0x7f, 0xea, 0xf9, 0xeb, 0x53, 0x1b, 0x8a, 0x7a, 0x6a, 0x3c, 0xd7, 0x27, 0xc7, 0x9a, 0x76, 0x96, 0x22, 0xcc, 0x7d, 0x70, 0x63, 0xcb, 0xd0, 0x9e, 0xc9, 0x44, 0x5f, 0xb9, 0xe5, 0x1f, 0xbe, 0x84, 0x54, 0xec, 0xde, 0x03, 0x8e, 0xda, 0xe1, 0x1a, 0xf4, 0xdb, 0x81, 0xbd, 0x2d, 0x35, 0xe4, 0xdb, 0x68, 0xbc, 0x6e, 0x4f, 0x3f, 0xc9, 0xfc, 0x11, 0xbe, 0x53, 0x11, 0xe2, 0x76, 0x09, 0xa3, 0x77, 0x82, 0x9a, 0x10, 0xbe, 0x93, 0xf0, 0x65, 0xb8, 0x92, 0xca, 0x59, 0x5d, 0x5f, 0x44, 0x14, 0xc1, 0x07, 0x36, 0x06, 0xfc, 0x5a, 0xee, 0x81, 0xb1, 0xc9, 0x11, 0x10, 0x56, 0x54, 0x5e, 0xf4, 0xbc, 0x7f, 0xbc, 0x6e, + 0xab, 0x5b, 0x95, 0x2a, 0x9e, 0xb9, 0x9d, 0x7d, 0xdc, 0x81, 0xdb, 0xd7, 0x45, 0xae, 0x4d, 0x4f, 0xc2, 0x07, 0x2e, 0xbc, 0xb6, 0x61, 0xac, 0x11, 0x3d, 0xf0, 0x7b, 0xcf, 0x82, 0x5f, 0xb8, 0xe3, 0x21, 0xd3, 0xee, 0xc9, 0x44, 0x6f, 0xb9, 0x25, 0x36, 0x75, 0xdf, 0xf8, 0x92, 0x0f, 0x8d, 0x75, 0x25, 0xf3, 0xbf, 0xa0, 0x9d, 0x5f, 0x47, 0x3c, 0xcf, 0x6a, 0x38, 0x79, 0x5c, 0x44, 0x8a, 0x98, 0x02, 0x7c, 0xb4, 0x67, 0xcb, 0x5e, 0xd1, 0x19, 0xe6, 0x5c, 0x97, 0x18, 0xa7, 0xb1, 0x22, 0x1d, 0x09, 0x75, 0x3d, 0x2a, 0xd8, 0x33, 0x9d, 0xca, 0x98, 0xb8, 0xd9, 0x70, 0x28, 0x9b, 0x9e, 0xcd, 0xb6, 0x05, 0x60, 0x99, 0xa6, 0xe8, 0x34, 0x50, 0x44, 0x8b, 0x28, 0x8c, 0xb8, 0x5b, 0xaa, 0x35, 0x6f, 0xfe, 0xb2, 0xa1, 0x50, 0x55, 0x6d, 0x35, 0xdc, 0x4e, 0x90, 0x8e, 0xe3, 0x2c, 0x5c, 0xae, 0x58, + 0x2f, 0xb5, 0x92, 0x52, 0x53, 0xe5, 0xee, 0x39, 0xe8, 0x3c, 0xa7, 0x89, 0x1d, 0x6b, 0x53, 0xc7, 0xd0, 0xa6, 0x8a, 0x81, 0xdb, 0x47, 0x22, 0xf9, 0xca, 0x4d, 0x38, 0xf8, 0x88, 0x77, 0x81, 0x9a, 0x5b, 0x85, 0x07, 0xbf, 0xc2, 0x1c, 0xdb, 0x7c, 0xff, 0x78, 0xbe, 0x82, 0xdb, 0xb7, 0x1f, 0x49, 0x15, 0x6d, 0x49, 0x07, 0xe7, 0x5a, 0x6a, 0xe6, 0x55, 0xdc, 0x79, 0x0f, 0x58, 0xb8, 0x13, 0xed, 0x43, 0x50, 0xb6, 0x45, 0x7c, 0x0d, 0x35, 0x99, 0x1e, 0x6e, 0xb1, 0x76, 0x80, 0x4a, 0xea, 0xd9, 0x32, 0x17, 0x4c, 0x46, 0xb2, 0x76, 0xbc, 0x6a, 0x45, 0x80, 0xcf, 0xaa, 0x65, 0xf9, 0x9b, 0xc7, 0x72, 0x88, 0x9e, 0xdd, 0x2c, 0x55, 0x45, 0x4e, 0xf6, 0x6d, 0x6e, 0xbb, 0x64, 0x90, 0xa0, 0x01, 0xbc, 0xc3, 0xcc, 0x2d, 0xd5, 0x94, 0xf7, 0x74, 0xe7, 0x58, 0xd2, 0x7f, 0x14, 0x2e, 0xf5, 0x1a, 0x7d, + 0xec, 0x06, 0x4d, 0xe5, 0x6d, 0xd0, 0x8b, 0xe5, 0x89, 0x84, 0x97, 0xcc, 0x90, 0x50, 0xb0, 0x9b, 0x74, 0x9e, 0x57, 0x10, 0x47, 0xf2, 0xda, 0xc1, 0xf3, 0x4d, 0xf0, 0x1b, 0xf5, 0x8d, 0xff, 0x12, 0xca, 0x8c, 0x8e, 0x07, 0xb9, 0x7d, 0x9a, 0xfe, 0x2d, 0x96, 0xcf, 0x3d, 0x2f, 0x9a, 0xd4, 0x38, 0x77, 0x94, 0x17, 0x04, 0x8a, 0x85, 0x73, 0xe9, 0x3f, 0xe8, 0x68, 0x33, 0x93, 0x39, 0x3d, 0x47, 0x64, 0x05, 0xc1, 0x65, 0x4a, 0x0b, 0x8f, 0x40, 0x73, 0xda, 0x21, 0x14, 0x08, 0x89, 0xca, 0x45, 0xcc, 0x2d, 0xd5, 0x94, 0xc8, 0xb6, 0x44, 0x82, 0xf0, 0x04, 0x03, 0x1e, 0x7d, 0x50, 0x90, 0x5f, 0x4d, 0x2e, 0xcd, 0xd8, 0x21, 0x28, 0x69, 0xf4, 0x5b, 0x2e, 0x96, 0xbe, 0x04, 0x3d, 0x07, 0x1f, 0x4f, 0x47, 0x5a, 0x1d, 0xc5, 0xd0, 0xc9, 0xab, 0x8b, 0xa9, 0x38, 0xe6, 0xeb, 0xd9, 0x40, 0x3a, 0x6b, + 0xa3, 0xfd, 0x12, 0xdb, 0x87, 0x8e, 0xa4, 0x15, 0x27, 0x41, 0xcd, 0x62, 0xf0, 0xf0, 0x34, 0x6f, 0x0c, 0x7a, 0xcc, 0x1e, 0x06, 0x85, 0x1f, 0xb2, 0x35, 0xc7, 0xbd, 0xd9, 0x1a, 0xe3, 0xd4, 0x2f, 0x37, 0x9a, 0x9d, 0xaa, 0xf9, 0x3f, 0x92, 0xe4, 0x93, 0x9b, 0x2d, 0x16, 0x35, 0x69, 0x22, 0x29, 0xa4, 0xb0, 0xd4, 0x3a, 0xf2, 0x09, 0x73, 0xa9, 0x61, 0xfe, 0x21, 0xea, 0x1d, 0xb9, 0x61, 0x7e, 0xbc, 0x20, 0x6a, 0x24, 0x67, 0x08, 0x0a, 0xfb, 0xf5, 0x85, 0x50, 0xe6, 0x22, 0x68, 0x91, 0x7b, 0x88, 0xad, 0x5c, 0xb9, 0x89, 0x25, 0x0a, 0x89, 0xef, 0x16, 0x16, 0x12, 0xb7, 0x7d, 0x40, 0x13, 0x7c, 0x4a, 0x64, 0x55, 0xab, 0x10, 0x58, 0xa0, 0xc0, 0xac, 0xf2, 0xa8, 0x3d, 0x62, 0xa5, 0x58, 0x29, 0x97, 0xc2, 0x3f, 0x23, 0x62, 0x43, 0xce, 0xf8, 0xc8, 0xb0, 0x8a, 0xaf, 0x6d, 0x9a, 0x97, 0x14, + 0x04, 0xac, 0xd2, 0x0b, 0xe7, 0xee, 0xba, 0x57, 0x0a, 0xe4, 0xf7, 0x9e, 0x3a, 0x73, 0x41, 0x0e, 0x5c, 0x73, 0x57, 0x6f, 0x6f, 0x6d, 0xbd, 0xfd, 0x2a, 0x4f, 0xad, 0x0b, 0xbe, 0xfb, 0xd8, 0x53, 0x4f, 0x3d, 0xb6, 0xfd, 0xa1, 0x27, 0x9e, 0x78, 0x08, 0x7c, 0xbf, 0xeb, 0xdc, 0x6b, 0xb7, 0xde, 0xfa, 0xf5, 0xb3, 0x5d, 0xbc, 0xeb, 0xce, 0x9e, 0xad, 0xf6, 0x2d, 0xfc, 0x95, 0xf9, 0x01, 0xf4, 0x15, 0xf5, 0x44, 0x14, 0xd5, 0x91, 0x40, 0x45, 0x08, 0x65, 0x41, 0x38, 0xe2, 0x66, 0x94, 0x29, 0x81, 0x56, 0x1b, 0xbc, 0x60, 0xd8, 0x8b, 0x51, 0xf6, 0x63, 0x2f, 0x21, 0x46, 0x15, 0x1c, 0x25, 0x9b, 0x44, 0x99, 0x72, 0xe2, 0xbb, 0x53, 0x72, 0x29, 0x4a, 0x1e, 0x11, 0x1c, 0xb5, 0xd7, 0xe4, 0xb6, 0xe2, 0x5c, 0xc8, 0x0c, 0x77, 0x27, 0xd7, 0x7e, 0x14, 0x4f, 0x29, 0xf6, 0x22, 0xeb, 0x5c, 0xba, + 0x30, 0x8b, 0x52, 0xd4, 0x80, 0xe8, 0x9d, 0x4b, 0x8a, 0x8b, 0xc2, 0x21, 0xbf, 0xd7, 0x56, 0x60, 0x32, 0xb2, 0x25, 0xc9, 0x15, 0x4b, 0x94, 0x24, 0xf7, 0x8b, 0x3d, 0xf1, 0x5c, 0x07, 0x1c, 0x95, 0x1b, 0xc6, 0x94, 0xa8, 0x64, 0x74, 0xd5, 0x8e, 0xee, 0x50, 0xa8, 0x7b, 0xc7, 0xaa, 0x56, 0xf6, 0x27, 0xf5, 0xdf, 0xc1, 0xfc, 0x87, 0xea, 0x3f, 0x3d, 0xbb, 0xf5, 0xe9, 0xc3, 0xab, 0x56, 0x1d, 0x7e, 0x7a, 0xeb, 0xec, 0xa7, 0xeb, 0xc9, 0xed, 0xe9, 0x46, 0x30, 0x95, 0xfe, 0xe8, 0xc5, 0x8a, 0xc9, 0x33, 0x6b, 0x07, 0xcf, 0x4c, 0xc6, 0x62, 0x93, 0x67, 0x06, 0xd7, 0x9e, 0x99, 0xac, 0x10, 0x3d, 0x7c, 0xb2, 0x25, 0xd5, 0x77, 0xf7, 0xab, 0x73, 0x7b, 0x5e, 0xbd, 0xbb, 0x2f, 0xb5, 0xea, 0xd4, 0x23, 0xe9, 0xdf, 0x5d, 0x4c, 0xff, 0x07, 0xc1, 0xe6, 0x7e, 0xbd, 0xc5, 0x3c, 0x0d, 0xe7, 0x83, + 0x83, 0xad, 0x50, 0x5c, 0x8a, 0x46, 0xb8, 0x1b, 0xc5, 0x32, 0x00, 0x3a, 0x0b, 0xc5, 0xf4, 0xe8, 0x58, 0x30, 0x24, 0x3e, 0x10, 0x44, 0xeb, 0x0b, 0x4e, 0x07, 0x97, 0x33, 0x56, 0x1e, 0x0e, 0x3a, 0xeb, 0x5c, 0x75, 0xc6, 0x40, 0xc0, 0x27, 0x41, 0x26, 0x4e, 0x96, 0xc9, 0x4d, 0x10, 0xe7, 0x6c, 0x00, 0x31, 0x23, 0x55, 0x41, 0x67, 0x9d, 0xb5, 0x60, 0xc6, 0x9d, 0x06, 0x3e, 0x6a, 0xd2, 0x58, 0xdc, 0x52, 0xea, 0xeb, 0x4d, 0x16, 0xfd, 0xcf, 0xbf, 0xfe, 0xf5, 0x9d, 0xe2, 0xe6, 0x3e, 0x6f, 0x49, 0x73, 0xb1, 0xf1, 0xda, 0xc6, 0x97, 0x00, 0xf3, 0x64, 0x6f, 0xed, 0xb6, 0x07, 0xd6, 0x25, 0xd6, 0x77, 0x55, 0x1b, 0x8c, 0x35, 0x5d, 0xa3, 0xf1, 0xe1, 0x07, 0xb6, 0xd6, 0xf6, 0x7d, 0x1a, 0x2a, 0xb5, 0x4f, 0x36, 0x6e, 0xe9, 0x0a, 0x87, 0xd6, 0x1c, 0x5f, 0xf7, 0xcd, 0xaf, 0xbf, 0x76, 0x7d, + 0xf8, 0xf6, 0xb5, 0x45, 0xe1, 0xae, 0x2d, 0x8d, 0xdf, 0xff, 0x3e, 0x39, 0x33, 0xf1, 0xd8, 0x77, 0xe6, 0x66, 0x3e, 0x77, 0x22, 0x65, 0x2d, 0x6b, 0x0e, 0x06, 0x9b, 0xcb, 0xac, 0x3d, 0xa7, 0x3e, 0x3f, 0xb5, 0xe7, 0xbf, 0x3d, 0x36, 0x81, 0xed, 0x38, 0xe5, 0xc2, 0x9f, 0xc8, 0xb5, 0x70, 0x7d, 0x59, 0x10, 0x17, 0x09, 0x22, 0xcf, 0x96, 0x9b, 0x45, 0x70, 0x0e, 0x2b, 0xb0, 0x35, 0x65, 0xcb, 0x5e, 0x51, 0x19, 0x7c, 0x86, 0x9e, 0xcd, 0xcc, 0x63, 0x2d, 0x3d, 0x6e, 0x29, 0xf2, 0xc4, 0xdb, 0x5c, 0xd2, 0x1e, 0xb2, 0x3f, 0x28, 0x3e, 0x27, 0x06, 0x93, 0x36, 0x3a, 0x79, 0x22, 0x37, 0x41, 0x0b, 0x61, 0x3d, 0x63, 0x96, 0x7b, 0xdb, 0x42, 0x58, 0x0a, 0x0d, 0x7e, 0x0f, 0xe7, 0xdb, 0x61, 0x28, 0x2f, 0x57, 0x5d, 0x88, 0x2b, 0xb7, 0x57, 0xa5, 0x25, 0xd7, 0xee, 0x75, 0x59, 0x18, 0x45, 0xf4, + 0xee, 0x4d, 0x9e, 0x3a, 0x93, 0x52, 0x6c, 0x55, 0x55, 0xbb, 0xcb, 0x3a, 0x6b, 0x4a, 0xf4, 0x68, 0x59, 0x9b, 0x1d, 0xf7, 0x4d, 0xed, 0x53, 0xa9, 0x2f, 0xda, 0xe4, 0xb6, 0x78, 0x6f, 0x05, 0x79, 0x24, 0xf3, 0x7e, 0xd0, 0x4e, 0xfc, 0x1b, 0xdf, 0x6f, 0x66, 0xf9, 0xf7, 0x9b, 0xf9, 0xc0, 0xf7, 0x9b, 0xf9, 0x7f, 0xe1, 0xfd, 0x98, 0xeb, 0xef, 0xd5, 0xe7, 0xbe, 0x1f, 0xdd, 0x81, 0xdf, 0x0f, 0xee, 0xa3, 0x63, 0x70, 0xfc, 0x02, 0xc4, 0xd3, 0xdc, 0xfb, 0x59, 0xa5, 0xf0, 0x8d, 0xd4, 0x70, 0xff, 0xc4, 0xef, 0xc7, 0x5f, 0x51, 0x19, 0xa6, 0x2e, 0x3b, 0xc1, 0x0f, 0x1f, 0xe7, 0x5a, 0xe7, 0x6d, 0x20, 0x25, 0x78, 0xfd, 0xf3, 0xbc, 0x1b, 0x79, 0x6d, 0x09, 0xe1, 0x1e, 0x52, 0x94, 0x61, 0xe7, 0x13, 0xb6, 0x16, 0xf8, 0xec, 0x79, 0xdb, 0x08, 0xf4, 0x76, 0x0d, 0x39, 0xdb, 0x48, 0xc3, 0xe2, + 0xf7, 0x17, 0x1e, 0xcf, 0x8e, 0x71, 0x9b, 0x48, 0xe4, 0x44, 0x9e, 0x30, 0xae, 0xe7, 0xef, 0x21, 0x6f, 0x4e, 0xed, 0x15, 0x8c, 0xfb, 0xfb, 0xaf, 0x73, 0xc7, 0xb0, 0x78, 0x1f, 0x7d, 0x9a, 0xfc, 0x1e, 0x2d, 0x87, 0xa2, 0x7f, 0x88, 0x1d, 0x5e, 0x19, 0xd2, 0xd4, 0x5a, 0xc0, 0x51, 0xf3, 0x96, 0x70, 0x77, 0x68, 0xee, 0x0e, 0xba, 0x20, 0xb9, 0x8b, 0x51, 0x6e, 0x42, 0x2c, 0x4d, 0xcc, 0x5b, 0xf2, 0x7f, 0x44, 0xcc, 0xeb, 0x81, 0xa2, 0x10, 0x94, 0xef, 0x36, 0x8b, 0x45, 0xd8, 0x0b, 0x15, 0x94, 0x11, 0x6d, 0x2e, 0x3f, 0x9f, 0xf7, 0xd6, 0xd7, 0xe6, 0xdc, 0x66, 0x86, 0x8a, 0x4f, 0xef, 0x53, 0x73, 0xaf, 0x5a, 0x7e, 0xa3, 0x8c, 0xe9, 0x35, 0x3b, 0x78, 0xbf, 0x1e, 0xbf, 0x67, 0x98, 0xf8, 0x3d, 0x3b, 0xcc, 0x6a, 0x39, 0x54, 0xe8, 0x26, 0x54, 0x1e, 0xce, 0x03, 0x44, 0xa8, 0x8e, 0x06, + 0xfb, 0xc8, 0xfc, 0x6d, 0x86, 0xbf, 0x8d, 0xee, 0x50, 0xc2, 0x3b, 0xfc, 0x6b, 0x5b, 0xb2, 0x2b, 0x60, 0x36, 0xb7, 0xec, 0x1c, 0x3f, 0x8f, 0xd0, 0x07, 0x99, 0xb9, 0xb1, 0x3b, 0xbf, 0x0d, 0x3b, 0x8f, 0xb8, 0xfa, 0x74, 0xf9, 0x6d, 0x09, 0x61, 0x15, 0xbb, 0x30, 0x41, 0x52, 0x0c, 0xc5, 0x32, 0x8d, 0x53, 0x88, 0x69, 0x9c, 0xe3, 0xe2, 0x5d, 0xdc, 0x16, 0x1d, 0x40, 0xc8, 0xfc, 0x9e, 0x20, 0x5c, 0x45, 0x6e, 0x4c, 0xcd, 0xcb, 0x02, 0xdb, 0xf2, 0x45, 0xb8, 0xa8, 0x76, 0xdd, 0x39, 0x24, 0x3a, 0x45, 0xf4, 0xfc, 0xe6, 0x5c, 0x89, 0xc6, 0xe1, 0x34, 0x42, 0x25, 0xeb, 0xd4, 0xdc, 0x44, 0xa3, 0xde, 0x31, 0x3b, 0xee, 0x9f, 0x61, 0xe5, 0x6b, 0xaf, 0xec, 0x2d, 0x9f, 0xef, 0xc0, 0x13, 0x69, 0x35, 0x3b, 0xc9, 0xb0, 0x9c, 0xaf, 0xc1, 0x8d, 0xe1, 0xdb, 0x34, 0xdc, 0xa5, 0x88, 0x41, 0x5c, + 0x5d, 0xf2, 0xaa, 0x0c, 0x73, 0xf2, 0xda, 0xe0, 0x4f, 0x8a, 0xe5, 0xf7, 0x40, 0x25, 0x27, 0xb5, 0xc2, 0x57, 0xe5, 0xdc, 0x33, 0x0b, 0x82, 0xd0, 0x50, 0x24, 0x31, 0x27, 0x78, 0x33, 0xfc, 0x11, 0x74, 0x86, 0xfd, 0x7e, 0x9c, 0x88, 0xec, 0x59, 0x14, 0xf6, 0x31, 0x82, 0xbb, 0xae, 0xe7, 0x9e, 0xb5, 0x5f, 0x26, 0x2f, 0xae, 0x39, 0x3f, 0x95, 0xd0, 0xe9, 0x87, 0xe1, 0xe5, 0xeb, 0xf8, 0x99, 0xa0, 0xb3, 0x41, 0x4a, 0xf0, 0x33, 0xb5, 0x5e, 0x45, 0x05, 0x51, 0x01, 0x57, 0x94, 0x5a, 0x9b, 0xc9, 0xb2, 0xe2, 0x83, 0x05, 0xe8, 0x29, 0x00, 0x42, 0x4f, 0x61, 0x46, 0xcd, 0x9c, 0x8f, 0x46, 0x5f, 0x84, 0xce, 0x83, 0x07, 0xbb, 0xe4, 0x95, 0xb8, 0x1c, 0x70, 0xf6, 0x34, 0x46, 0xeb, 0x05, 0xad, 0x9c, 0x2b, 0x6e, 0xe2, 0x5d, 0x73, 0xf2, 0xca, 0xeb, 0xf0, 0x7a, 0x98, 0x75, 0xcc, 0xe7, 0x77, + 0xb1, 0x18, 0x2e, 0xb8, 0xc1, 0x3f, 0x0e, 0x75, 0x91, 0x0c, 0x5a, 0x4f, 0x89, 0x64, 0xcc, 0xc5, 0xd6, 0x95, 0xcc, 0x3f, 0xf7, 0x98, 0xcd, 0x56, 0x95, 0xb4, 0xe4, 0x54, 0x94, 0x14, 0x2f, 0xaa, 0x44, 0x48, 0x71, 0x39, 0xfe, 0x1e, 0xee, 0x4c, 0x08, 0x2e, 0x8b, 0x97, 0x13, 0xfd, 0xf1, 0x82, 0x2d, 0xdb, 0x0e, 0x93, 0xe3, 0x9f, 0x89, 0x9a, 0x7f, 0x22, 0x77, 0xd8, 0x07, 0x5e, 0x01, 0xf1, 0x2d, 0x0f, 0x4d, 0x96, 0xee, 0xdf, 0xe9, 0x2b, 0x20, 0x8d, 0x70, 0x8f, 0x0b, 0xf4, 0x0e, 0xa6, 0x9f, 0x9b, 0xff, 0x7d, 0x00, 0xb4, 0xea, 0x34, 0xe9, 0xfd, 0xd4, 0x3b, 0xc5, 0xbd, 0x3b, 0x9b, 0xb7, 0x3c, 0xe2, 0x31, 0x06, 0xf0, 0x33, 0x9e, 0x85, 0x4f, 0xf3, 0x89, 0xec, 0x33, 0x2a, 0xd9, 0x2c, 0x7f, 0x0e, 0xe3, 0xc3, 0x70, 0x59, 0xae, 0xb0, 0xe1, 0x38, 0xc1, 0x3d, 0xa3, 0xc7, 0x65, 0x43, + 0xa5, 0x2f, 0x0d, 0xd9, 0x67, 0xf4, 0xb3, 0x87, 0x32, 0xe8, 0x19, 0x45, 0xfc, 0xc1, 0xae, 0x87, 0x2b, 0x2c, 0xa8, 0x4f, 0xd4, 0x01, 0xf0, 0xea, 0xd6, 0x2d, 0x05, 0x95, 0xfd, 0x89, 0x43, 0x60, 0xa2, 0xc0, 0xb7, 0x73, 0x7f, 0xc9, 0x86, 0x4b, 0x5b, 0xd2, 0xff, 0xf8, 0xca, 0x80, 0xc3, 0x21, 0xff, 0x89, 0xb1, 0xfc, 0x33, 0x97, 0x06, 0x7b, 0x03, 0x70, 0x13, 0x4e, 0x3f, 0x07, 0xfe, 0x14, 0x30, 0x7a, 0x1e, 0xde, 0xda, 0xbc, 0xb3, 0xb7, 0x98, 0x7a, 0x27, 0x7d, 0x40, 0xad, 0x03, 0xad, 0x01, 0x8e, 0x8f, 0x1f, 0xea, 0x74, 0x0f, 0xb4, 0x3b, 0xfa, 0x89, 0x0f, 0x27, 0xd5, 0xfd, 0x00, 0x48, 0xa2, 0x76, 0x92, 0x14, 0x07, 0x01, 0xf4, 0x94, 0xb9, 0xdc, 0x5e, 0x54, 0x88, 0x0a, 0x3e, 0xa6, 0x68, 0x0e, 0xd5, 0xfa, 0x10, 0x89, 0x77, 0xf3, 0xa7, 0x1e, 0xb8, 0xe0, 0x34, 0x57, 0xee, 0x01, + 0xb9, 0x08, 0xd9, 0xc3, 0x93, 0x44, 0x7e, 0x0f, 0x9e, 0x04, 0x7b, 0x89, 0x9e, 0x02, 0x1a, 0xec, 0xd1, 0xa4, 0xbe, 0x37, 0x55, 0x5b, 0x1d, 0x8f, 0x15, 0x87, 0x7d, 0x85, 0x48, 0x14, 0x3e, 0xe8, 0x8a, 0x9b, 0x8a, 0x81, 0x28, 0x9f, 0x9e, 0x0a, 0x8e, 0x96, 0x31, 0x7f, 0xd9, 0x35, 0x80, 0x65, 0xe8, 0xad, 0xc8, 0xff, 0xae, 0xb2, 0x6a, 0x82, 0x89, 0xee, 0x58, 0x31, 0x4b, 0x49, 0xb5, 0x79, 0xa2, 0xab, 0x34, 0x36, 0xf5, 0xe0, 0xe4, 0xd8, 0x60, 0x79, 0x92, 0x5f, 0x91, 0x3d, 0x45, 0xdd, 0x5b, 0x93, 0x0e, 0x7c, 0x7e, 0x12, 0xce, 0xe5, 0xb5, 0x8a, 0x4e, 0x9e, 0x1d, 0x9d, 0xf9, 0x64, 0x2d, 0xd5, 0x65, 0xc9, 0x67, 0xa2, 0xea, 0x38, 0x3c, 0x52, 0x61, 0xef, 0xea, 0x1d, 0xce, 0xae, 0xd9, 0x47, 0x96, 0x25, 0xb2, 0x82, 0x3e, 0x68, 0x47, 0xfa, 0xc3, 0xe4, 0x05, 0xe6, 0x0a, 0xe1, 0x27, + 0xf6, 0x72, 0xb5, 0x57, 0x10, 0x6f, 0xbb, 0x8d, 0xf7, 0x41, 0x25, 0x50, 0x53, 0xda, 0x78, 0x1f, 0x14, 0x7d, 0x6c, 0x16, 0x44, 0x2a, 0x73, 0xdd, 0x4a, 0x1f, 0x81, 0xa2, 0x00, 0x6c, 0x55, 0x7b, 0x12, 0x55, 0xb5, 0xcf, 0x46, 0x26, 0x05, 0x3e, 0x65, 0x52, 0xea, 0x41, 0x47, 0x50, 0xb8, 0xb0, 0xbd, 0x7e, 0xe5, 0x23, 0x27, 0xec, 0x52, 0x2e, 0x8a, 0x37, 0x62, 0x4d, 0x00, 0xaf, 0x91, 0x26, 0x08, 0xc3, 0x6b, 0xba, 0x92, 0x57, 0x05, 0x6a, 0xd5, 0xf4, 0x96, 0xf7, 0xbf, 0xbd, 0xe6, 0x9e, 0x99, 0x1a, 0xfe, 0x0a, 0xbe, 0x5f, 0x3b, 0x7e, 0xbf, 0xa7, 0x09, 0x1f, 0x31, 0x7a, 0x55, 0x8f, 0xe9, 0x74, 0x05, 0xef, 0x91, 0x53, 0x20, 0x85, 0xdf, 0xd1, 0x7d, 0xcb, 0xd4, 0x7c, 0xd8, 0x91, 0xa9, 0xf9, 0x80, 0xf9, 0x39, 0xa5, 0x9e, 0x00, 0x52, 0xc2, 0xdc, 0x51, 0x84, 0xc0, 0x21, 0xcc, 0xd1, + 0x1a, 0x8b, 0xe2, 0x79, 0x3c, 0x83, 0x8e, 0x91, 0xbb, 0xe6, 0xc3, 0x7b, 0xaf, 0xb0, 0x5a, 0x84, 0x6e, 0xda, 0x3a, 0x9d, 0xad, 0x09, 0xfa, 0xfe, 0xd7, 0xd8, 0x50, 0x1f, 0x41, 0x2e, 0x0c, 0xa7, 0xef, 0x23, 0xff, 0x08, 0xdf, 0x43, 0x4e, 0xd4, 0x13, 0x8f, 0x72, 0x67, 0x8e, 0xe8, 0xa8, 0xa0, 0x16, 0x00, 0x91, 0x07, 0xd5, 0xf6, 0x29, 0xf6, 0x91, 0x0c, 0x5b, 0x07, 0xcf, 0x00, 0xe8, 0x45, 0xb7, 0x33, 0x95, 0x73, 0x38, 0x14, 0x87, 0xe0, 0xcc, 0x51, 0x9c, 0xc3, 0x92, 0x1b, 0x20, 0x28, 0x1a, 0xee, 0x80, 0x68, 0x8d, 0x70, 0x78, 0x8f, 0xcc, 0x01, 0xa3, 0xb0, 0x21, 0xc6, 0xfc, 0x55, 0x27, 0x10, 0xd9, 0x65, 0x81, 0x99, 0x27, 0x77, 0x44, 0x98, 0x3f, 0x73, 0x05, 0xa2, 0x65, 0x66, 0x83, 0xae, 0x4b, 0x8d, 0x2f, 0xa2, 0x30, 0x11, 0x8b, 0x02, 0xd0, 0xd1, 0x0b, 0xe2, 0xf5, 0x53, 0xc5, + 0xb5, 0x49, 0xc4, 0x93, 0x65, 0xed, 0x46, 0x77, 0x7b, 0x38, 0x1e, 0xad, 0xfa, 0x97, 0xfc, 0x8a, 0x4f, 0xe5, 0xe6, 0x52, 0x63, 0xbd, 0xd5, 0xa3, 0x96, 0x68, 0x24, 0xe3, 0xe7, 0x9b, 0xc7, 0x6b, 0xad, 0xc0, 0x96, 0x18, 0x4c, 0xc4, 0x46, 0xc2, 0xd4, 0x5b, 0x15, 0x55, 0x6e, 0x6f, 0xa5, 0x27, 0x50, 0xce, 0x04, 0x85, 0xa1, 0xe6, 0x1b, 0xfe, 0xc7, 0xe7, 0x0a, 0x53, 0xe1, 0xb0, 0x4e, 0x6a, 0xd6, 0xb8, 0xd4, 0xeb, 0x03, 0x0d, 0xbd, 0xc1, 0x60, 0x77, 0x9d, 0xd7, 0x65, 0xf7, 0x84, 0xe0, 0x5b, 0xe9, 0x17, 0xde, 0x26, 0x9f, 0x61, 0x9a, 0x88, 0x42, 0x62, 0x00, 0x55, 0x7e, 0xa2, 0x68, 0x02, 0x5b, 0x8a, 0x3c, 0xf8, 0x98, 0x26, 0x90, 0x56, 0x80, 0x8a, 0x90, 0x3f, 0x1b, 0xe4, 0xaa, 0x34, 0x2c, 0xf9, 0xc9, 0x1e, 0x3c, 0x17, 0x50, 0xce, 0x86, 0x16, 0xcf, 0x69, 0xae, 0x42, 0x0e, 0x66, + 0xe5, 0x60, 0xa3, 0x4b, 0x99, 0xf9, 0x60, 0x32, 0x22, 0xab, 0xc6, 0x48, 0x3e, 0xc3, 0xd2, 0xf2, 0xa4, 0x7f, 0x09, 0xdc, 0x42, 0xbc, 0xfe, 0xa9, 0x8e, 0xa3, 0xa7, 0x0e, 0x6a, 0xcd, 0x60, 0xdc, 0x94, 0xfe, 0xbe, 0x56, 0x0f, 0x86, 0x2f, 0x90, 0x83, 0x1c, 0x06, 0x7f, 0x43, 0x71, 0xf4, 0xfc, 0x1d, 0xf3, 0x8f, 0x9b, 0x8c, 0x38, 0xc7, 0xf9, 0x22, 0xf9, 0x1c, 0xd3, 0x00, 0x9f, 0x7d, 0x0a, 0x57, 0xff, 0x66, 0x64, 0x80, 0x25, 0xe2, 0xe0, 0xcc, 0x5a, 0x86, 0xde, 0x27, 0xa0, 0x1a, 0xca, 0x6c, 0x7e, 0x38, 0x98, 0xe6, 0xe5, 0x19, 0x89, 0x70, 0x9e, 0xb1, 0x90, 0x91, 0x28, 0xdb, 0x0a, 0x17, 0x7a, 0x0c, 0xc1, 0x6d, 0xb0, 0x90, 0x25, 0x9e, 0x5d, 0x54, 0x01, 0x37, 0x9f, 0x8a, 0xe8, 0x81, 0xca, 0x06, 0xac, 0xae, 0x44, 0x9a, 0xd2, 0x81, 0x58, 0xf7, 0x8e, 0x36, 0x4f, 0x4e, 0xe9, 0x8d, + 0x06, 0xa1, 0xf1, 0x40, 0x9d, 0x15, 0x12, 0x0c, 0x91, 0x44, 0x62, 0xe1, 0x4d, 0xfa, 0x7e, 0xcc, 0x15, 0xe6, 0x27, 0x66, 0x5f, 0x54, 0x4b, 0x48, 0x96, 0xf0, 0x05, 0x45, 0x78, 0x9c, 0x32, 0x09, 0xa2, 0x21, 0x84, 0x72, 0x16, 0x11, 0x9b, 0xa4, 0x80, 0x4b, 0x9d, 0x61, 0x63, 0x3e, 0x78, 0x0f, 0x2f, 0x5c, 0xa2, 0x01, 0x57, 0xa3, 0x3e, 0x9b, 0x1e, 0xa0, 0xf1, 0xfb, 0xbc, 0x85, 0x28, 0xbe, 0x1d, 0xf0, 0xe8, 0xbd, 0x1a, 0x39, 0x57, 0x1f, 0x1c, 0x07, 0x49, 0xfd, 0x5e, 0x36, 0xf4, 0x4b, 0x2d, 0x2a, 0x10, 0x4e, 0xb5, 0xa6, 0xcb, 0xf0, 0xa9, 0x19, 0xe8, 0x39, 0x77, 0x20, 0xa4, 0x50, 0x79, 0x3a, 0x07, 0xc7, 0x71, 0xd0, 0xe6, 0xf0, 0xe1, 0xeb, 0xe0, 0x07, 0xd7, 0xce, 0x81, 0xef, 0x4e, 0xa0, 0xf3, 0xb3, 0x57, 0xd3, 0x15, 0xd9, 0x58, 0xcd, 0xc4, 0x59, 0xf0, 0x44, 0x7a, 0x12, 0xfd, + 0x43, 0x7b, 0xd7, 0xd0, 0xc2, 0x5f, 0xe8, 0x75, 0xf0, 0xbd, 0xea, 0x88, 0x0d, 0x49, 0x85, 0x19, 0x6d, 0xfc, 0x71, 0x0f, 0x02, 0x94, 0x70, 0x33, 0xcc, 0x42, 0x8a, 0x48, 0x14, 0x06, 0x60, 0x77, 0xda, 0x0c, 0x5d, 0x35, 0x72, 0xa1, 0x96, 0xfe, 0x08, 0x33, 0xf9, 0xeb, 0xd1, 0x7a, 0x2b, 0x29, 0x72, 0x3b, 0x6d, 0x05, 0x2a, 0x05, 0x51, 0x07, 0xea, 0xb0, 0xa9, 0x00, 0x75, 0x65, 0x1e, 0xbf, 0x32, 0x9a, 0x7d, 0x18, 0xb6, 0x66, 0x64, 0x69, 0x98, 0xf9, 0xf8, 0x7d, 0x30, 0x8e, 0x69, 0xb3, 0xa8, 0x2f, 0xb8, 0x57, 0x79, 0x50, 0x91, 0xbb, 0xde, 0x33, 0x2f, 0x6d, 0xdb, 0xf6, 0xe2, 0x99, 0xde, 0x70, 0xf7, 0x96, 0x46, 0xcf, 0x2a, 0xf7, 0x13, 0x85, 0x76, 0x70, 0x20, 0x3a, 0xdc, 0xe4, 0xaf, 0x18, 0xda, 0x9b, 0x6c, 0xda, 0x3b, 0x54, 0xe1, 0x4f, 0x0e, 0x95, 0x83, 0x83, 0x8e, 0x42, 0x6a, + 0x4c, 0xc4, 0x98, 0x57, 0xef, 0x3c, 0xd9, 0x35, 0x73, 0xe5, 0x58, 0x7b, 0xfb, 0xb1, 0x2b, 0x33, 0x5d, 0x27, 0x77, 0xae, 0x36, 0x33, 0xa2, 0xcd, 0x56, 0xcf, 0x4f, 0x25, 0x75, 0xe3, 0x07, 0x5b, 0x7a, 0xf7, 0xf5, 0x04, 0x02, 0x3d, 0xfb, 0x7a, 0x5b, 0x0e, 0x8e, 0xd7, 0x49, 0x7e, 0xea, 0xb1, 0x6e, 0xc6, 0xfb, 0xf7, 0xde, 0x85, 0xb7, 0xa9, 0x5f, 0xd3, 0x62, 0xa2, 0x8a, 0xb8, 0xc4, 0xad, 0x2b, 0x56, 0xcb, 0x92, 0xbc, 0x07, 0x91, 0x79, 0x69, 0x76, 0xea, 0x2e, 0xf9, 0xf1, 0x1e, 0xfc, 0x71, 0x14, 0x83, 0xfa, 0x51, 0xd0, 0x2b, 0xa3, 0xa8, 0x73, 0x9b, 0x24, 0x0b, 0x91, 0xd1, 0x4c, 0x33, 0xd4, 0x1c, 0x5f, 0xed, 0x75, 0x71, 0x23, 0x16, 0xd7, 0x1f, 0x2a, 0x29, 0x0a, 0xb1, 0x2a, 0x3b, 0xb7, 0xc0, 0x2b, 0x14, 0x18, 0xd2, 0x48, 0x48, 0x11, 0x61, 0x4c, 0x89, 0x50, 0x70, 0xac, 0x4a, + 0x0f, 0x82, 0x85, 0x92, 0x54, 0xf9, 0xc6, 0xa3, 0xed, 0xb6, 0x35, 0xf7, 0xbe, 0xbc, 0x75, 0xf0, 0xf4, 0x86, 0x18, 0x74, 0x28, 0xa5, 0xf2, 0x8b, 0x76, 0x2b, 0x2d, 0x12, 0x6b, 0x9c, 0xf1, 0xce, 0xa2, 0x9a, 0x1d, 0x03, 0xe5, 0xfa, 0x40, 0xb5, 0xdf, 0x53, 0x2c, 0x55, 0x59, 0x69, 0xea, 0xf7, 0x6a, 0x65, 0xf3, 0xce, 0x0b, 0xbd, 0x0f, 0xbf, 0xfd, 0x99, 0xd1, 0xea, 0x3d, 0x4f, 0xed, 0xe9, 0xf9, 0xc2, 0xc6, 0xc4, 0xfe, 0x46, 0xb7, 0xb9, 0x7c, 0xba, 0x6c, 0xe2, 0xa9, 0x96, 0xbe, 0xbd, 0x9d, 0x85, 0x0d, 0x47, 0x5e, 0x3a, 0x92, 0xda, 0x54, 0x6d, 0x94, 0x29, 0xad, 0x50, 0x5e, 0x3a, 0x38, 0xec, 0xef, 0xc1, 0x39, 0xa3, 0x23, 0xe0, 0x2c, 0x90, 0x60, 0x03, 0x96, 0x10, 0xc4, 0xf9, 0xb4, 0x25, 0x6e, 0x44, 0x9e, 0xe3, 0xc1, 0xb9, 0xbc, 0xc2, 0x4a, 0x5c, 0x31, 0xea, 0xbd, 0x4f, 0x98, + 0x5d, 0xcc, 0x9f, 0x55, 0x3a, 0x89, 0xc4, 0xa0, 0xfa, 0xab, 0xc8, 0x69, 0x78, 0x04, 0x3c, 0xf6, 0x79, 0xb3, 0x86, 0x9c, 0x0b, 0xa1, 0x61, 0x09, 0xcd, 0xdf, 0xaf, 0x35, 0xc1, 0xaf, 0xc5, 0x63, 0xe2, 0x58, 0x48, 0x8b, 0x54, 0xf0, 0xa2, 0x98, 0xf0, 0x24, 0x9d, 0x3e, 0x93, 0x04, 0xe9, 0x81, 0x6e, 0x36, 0x8a, 0xca, 0x9f, 0xa0, 0x84, 0x3d, 0x45, 0xf5, 0x6e, 0xe4, 0x93, 0x2d, 0xfa, 0x4b, 0xe8, 0x32, 0x10, 0x14, 0xf1, 0x7f, 0xb6, 0xc2, 0xac, 0x37, 0x61, 0xaa, 0x24, 0x68, 0xec, 0x89, 0x54, 0x8f, 0xc0, 0x47, 0x78, 0x51, 0xaa, 0x16, 0x49, 0x8d, 0xf2, 0xd7, 0x19, 0xbb, 0x61, 0xc8, 0xe4, 0x60, 0xbe, 0x69, 0xd4, 0x29, 0xf4, 0x5f, 0x16, 0xb9, 0x0c, 0x17, 0x41, 0xe7, 0xa3, 0x76, 0xd9, 0x4f, 0xd4, 0x52, 0x46, 0x21, 0xf9, 0x67, 0x59, 0xe1, 0x25, 0xf4, 0x6c, 0x81, 0x2e, 0x9f, 0xaf, + 0x2b, 0x30, 0x7f, 0x8f, 0xc6, 0x68, 0xd4, 0x90, 0xfb, 0x53, 0x9d, 0x5d, 0xdd, 0xec, 0x73, 0xfa, 0xe6, 0xb7, 0x57, 0x46, 0xad, 0xe5, 0x56, 0xf2, 0x21, 0x68, 0x4d, 0x10, 0x0a, 0xc4, 0x09, 0xc5, 0xf4, 0x12, 0x26, 0xc2, 0x9e, 0x2c, 0x30, 0x21, 0x9e, 0x11, 0x24, 0x14, 0x82, 0xdc, 0xc7, 0x09, 0xc5, 0x2b, 0x10, 0x0a, 0x10, 0x0a, 0x05, 0xb3, 0xfc, 0x51, 0x37, 0x50, 0xc1, 0x96, 0x74, 0x5a, 0xaa, 0x82, 0x03, 0x24, 0x7d, 0x57, 0xe4, 0xd2, 0x3f, 0x46, 0x56, 0xe2, 0x52, 0x2e, 0x16, 0x35, 0xb9, 0xd9, 0x5c, 0x66, 0xb1, 0x44, 0xcd, 0xf3, 0x1f, 0xd7, 0x9a, 0x18, 0x97, 0xc9, 0x92, 0xde, 0x81, 0x6b, 0xd8, 0xc0, 0xbf, 0xf7, 0x39, 0xea, 0x5d, 0xe8, 0x43, 0x38, 0x92, 0x56, 0x39, 0xc5, 0xa6, 0xa4, 0x90, 0xb8, 0x52, 0x22, 0xeb, 0xa5, 0x14, 0x17, 0x6a, 0x29, 0x36, 0xce, 0xaa, 0xc5, 0xee, + 0x16, 0x27, 0x02, 0xf4, 0xe7, 0xc8, 0xc6, 0xaf, 0xa4, 0xef, 0x7b, 0xb2, 0xc0, 0x21, 0xfb, 0xb2, 0x4b, 0x2f, 0xb5, 0x68, 0xbe, 0x24, 0x71, 0x5a, 0x2e, 0x83, 0x7b, 0xd2, 0xfb, 0xc1, 0x79, 0x72, 0x4f, 0x81, 0x3e, 0xfd, 0xb9, 0xfa, 0x0a, 0x6b, 0xc2, 0x09, 0x06, 0x34, 0x76, 0xcc, 0x0b, 0x7a, 0x91, 0x6e, 0xc6, 0xbc, 0xe2, 0xb3, 0x57, 0xe5, 0x1c, 0x2f, 0x28, 0x1b, 0xb2, 0x5e, 0x82, 0x69, 0x6b, 0x51, 0xc8, 0x7a, 0x11, 0x19, 0x68, 0x96, 0x94, 0x4b, 0x10, 0x6b, 0xc0, 0xa1, 0x06, 0xad, 0xd1, 0x98, 0x1b, 0x6a, 0xc8, 0x63, 0xe8, 0x32, 0x66, 0x83, 0xd5, 0xcd, 0xa5, 0x3d, 0xe1, 0xd4, 0x96, 0xa4, 0x63, 0xfe, 0x33, 0x42, 0x4a, 0x50, 0xd2, 0x1e, 0x4d, 0xe6, 0x46, 0x19, 0x22, 0x39, 0xd4, 0x5d, 0x3f, 0xc6, 0x76, 0x26, 0xf2, 0x29, 0x6a, 0x16, 0x9a, 0xe9, 0x21, 0xf8, 0x4e, 0x72, 0xa2, + 0x92, 0xe8, 0x4a, 0xb6, 0x87, 0xb0, 0xdf, 0xe3, 0x05, 0x54, 0x47, 0x86, 0x42, 0x3b, 0x53, 0xb5, 0x40, 0x2a, 0x96, 0x50, 0x7c, 0xb2, 0x09, 0xd4, 0xe5, 0x0a, 0x05, 0x1c, 0xe9, 0x4a, 0x45, 0x25, 0x0a, 0x82, 0x06, 0x03, 0x59, 0xaa, 0x68, 0x59, 0x6e, 0x06, 0x81, 0x37, 0x8b, 0xcc, 0x2d, 0x06, 0xd9, 0x7c, 0x81, 0xdc, 0x37, 0x22, 0xd3, 0xd3, 0x38, 0x67, 0x20, 0xe0, 0xd5, 0x5c, 0x2a, 0x68, 0xe8, 0x1e, 0xa9, 0xc4, 0x29, 0x02, 0xc3, 0xc7, 0x06, 0x87, 0x44, 0x4a, 0x11, 0xe3, 0x71, 0xf5, 0x88, 0x14, 0xe2, 0xb4, 0x4d, 0xf8, 0x86, 0x0f, 0x37, 0x24, 0x82, 0x95, 0x86, 0x68, 0x7f, 0xdd, 0xd4, 0xc9, 0x3e, 0x97, 0xb7, 0x61, 0x20, 0x52, 0x3b, 0xd5, 0x19, 0xf2, 0x74, 0x17, 0x59, 0x35, 0x8c, 0x3c, 0xba, 0x2e, 0x94, 0x47, 0x54, 0x86, 0xea, 0x33, 0x3c, 0xfa, 0xff, 0xdd, 0xfa, 0x0c, 0x9f, + 0xbc, 0xb5, 0x2d, 0x7b, 0xb8, 0x1a, 0xe8, 0xda, 0xb1, 0xa8, 0x3e, 0x03, 0x49, 0xb8, 0xd3, 0xf7, 0x53, 0x8d, 0xf0, 0xf9, 0x2b, 0x88, 0x24, 0x71, 0x67, 0x52, 0x99, 0x70, 0x49, 0x28, 0x91, 0x58, 0x2d, 0x23, 0x81, 0x88, 0xe4, 0xb8, 0xd6, 0x42, 0x99, 0x9a, 0x89, 0x59, 0x8a, 0x7a, 0xae, 0x58, 0xc2, 0x4e, 0x7c, 0xea, 0xc6, 0xbf, 0x12, 0x32, 0x16, 0xf9, 0xa6, 0x24, 0x21, 0x16, 0x91, 0x62, 0x41, 0x17, 0x61, 0xc3, 0xd1, 0xa4, 0xa9, 0x32, 0x56, 0x5b, 0x1d, 0x4b, 0x56, 0x26, 0x51, 0xa0, 0x3b, 0xe8, 0xc7, 0x27, 0xf5, 0x6c, 0xd8, 0x9f, 0x62, 0x7d, 0x5a, 0x68, 0x12, 0xf2, 0x60, 0xfb, 0x2a, 0xb6, 0x4c, 0x53, 0x62, 0xb1, 0x3c, 0xb2, 0xa6, 0xa5, 0x93, 0x04, 0x4f, 0x6c, 0x7b, 0x74, 0xb6, 0x22, 0x1e, 0x73, 0xd9, 0xeb, 0x6c, 0x1e, 0x95, 0xa5, 0xea, 0xec, 0xc0, 0xe8, 0xd1, 0xde, 0x42, + 0x00, 0x5c, 0x4d, 0x13, 0x4d, 0x53, 0x4f, 0xac, 0xb2, 0x16, 0xb6, 0x6d, 0xeb, 0x78, 0xc1, 0x06, 0xba, 0xd3, 0x06, 0x28, 0x20, 0x6d, 0xfa, 0x78, 0xcd, 0x84, 0x37, 0x76, 0x5b, 0x5b, 0x4d, 0xb9, 0x2e, 0xd4, 0x80, 0xa4, 0x35, 0x7d, 0xba, 0x7f, 0xe8, 0x23, 0xf5, 0xe5, 0x09, 0x64, 0x21, 0x96, 0x7a, 0x03, 0xc5, 0x6d, 0xc3, 0x25, 0xe1, 0x9e, 0x5a, 0xef, 0x9a, 0xd6, 0x86, 0xde, 0x3a, 0x24, 0x33, 0xb7, 0x89, 0x93, 0xd9, 0xfb, 0x3f, 0x74, 0xd8, 0x2a, 0x93, 0x45, 0x6e, 0x83, 0xd7, 0xa6, 0x41, 0xe3, 0x5f, 0x08, 0x85, 0xf8, 0x31, 0xe6, 0xf2, 0x8a, 0x9c, 0xb7, 0x3b, 0xf2, 0x38, 0x6f, 0x29, 0x41, 0xd6, 0xa1, 0x36, 0x8f, 0xf3, 0xf6, 0x17, 0xde, 0x89, 0xad, 0x7b, 0x12, 0xdb, 0xaf, 0x9e, 0x4e, 0x55, 0x4e, 0xdd, 0x3f, 0x71, 0x7d, 0xe6, 0x22, 0x9c, 0xbf, 0xfd, 0x7b, 0x3b, 0x5b, 0x36, + 0x76, 0x20, 0xce, 0x5b, 0x46, 0x8c, 0x20, 0xc9, 0x8d, 0xb7, 0x3e, 0xb7, 0x7f, 0xe4, 0xa3, 0x77, 0x6c, 0xb4, 0xa6, 0x7d, 0x64, 0xab, 0xb2, 0x75, 0xfa, 0xce, 0xd4, 0xc0, 0x6d, 0x83, 0x45, 0x1c, 0x84, 0x1c, 0xae, 0xae, 0x08, 0x1c, 0x85, 0x2b, 0xf8, 0x99, 0x4a, 0x93, 0x45, 0x28, 0xea, 0xe9, 0x21, 0x70, 0x55, 0xad, 0x4c, 0x00, 0x2e, 0xfb, 0x5c, 0x78, 0xc5, 0x59, 0x7d, 0xa6, 0x30, 0x0e, 0x72, 0xf0, 0xc8, 0x62, 0x27, 0x89, 0x23, 0x47, 0xd9, 0x83, 0x06, 0xb3, 0xd6, 0x0b, 0x9e, 0x9f, 0xb8, 0x7f, 0xaa, 0x32, 0x75, 0xfa, 0xea, 0xf6, 0xc4, 0x9e, 0xad, 0x13, 0xde, 0x2a, 0xa5, 0x45, 0x57, 0xde, 0xb1, 0xb1, 0xa5, 0x73, 0x6f, 0x7f, 0x51, 0x64, 0xe3, 0xc5, 0x99, 0xeb, 0xe0, 0xe7, 0xd6, 0x8d, 0x77, 0x7c, 0x74, 0x64, 0xff, 0x73, 0xb7, 0x36, 0x66, 0x70, 0xfc, 0x45, 0x83, 0xb7, 0x0d, + 0xa4, 0xee, 0x9c, 0x6e, 0x55, 0xce, 0xbf, 0x82, 0xf9, 0x81, 0x17, 0xfe, 0x4a, 0x9d, 0x64, 0x1e, 0x23, 0x3c, 0xd0, 0xce, 0x96, 0xbb, 0x10, 0xa5, 0x9d, 0x1a, 0x13, 0x63, 0x72, 0x66, 0x50, 0xa6, 0x14, 0x02, 0x45, 0x4d, 0x09, 0x49, 0x31, 0x6d, 0xcb, 0x7c, 0x84, 0x4c, 0xed, 0x17, 0x2d, 0xfc, 0xb3, 0x2f, 0x01, 0xa8, 0xc5, 0x53, 0x26, 0xa8, 0x8d, 0x81, 0x05, 0xfc, 0xe8, 0xa7, 0xd8, 0x47, 0xc7, 0xa9, 0x08, 0xe5, 0xed, 0x1b, 0x5b, 0xee, 0xff, 0x24, 0x7a, 0xf0, 0x6b, 0xd4, 0xd7, 0xac, 0x1b, 0xee, 0x7c, 0x6c, 0x64, 0xff, 0xf3, 0xf0, 0xc1, 0x83, 0xf5, 0x45, 0x9d, 0x7a, 0xa7, 0xcf, 0x79, 0xf9, 0x09, 0xf8, 0xd8, 0x2d, 0x2a, 0xb2, 0x15, 0xcb, 0x73, 0x67, 0xfa, 0x13, 0xd4, 0x33, 0xf8, 0xdc, 0xb1, 0x36, 0x59, 0x55, 0x04, 0x00, 0xc3, 0x26, 0xb2, 0xe6, 0x57, 0xa2, 0x5e, 0x84, 0x61, + 0xdd, 0x4a, 0xc3, 0x2d, 0x97, 0x4f, 0xff, 0x02, 0x2b, 0x14, 0x9e, 0x06, 0x64, 0x5c, 0xa3, 0xc3, 0xe1, 0xdd, 0xdd, 0x38, 0xff, 0x2b, 0xd6, 0x1e, 0x43, 0x56, 0x54, 0xb8, 0x6f, 0x5f, 0x57, 0x75, 0x57, 0x05, 0xca, 0xff, 0xaa, 0xf2, 0x0e, 0x4f, 0x6e, 0x8e, 0x6c, 0x7d, 0xf6, 0x78, 0xfb, 0xbb, 0x6f, 0x6e, 0x79, 0x32, 0x49, 0xd5, 0xe2, 0xf4, 0xa5, 0xa6, 0x93, 0xaf, 0xdf, 0xfb, 0xd0, 0xf7, 0xef, 0xac, 0xcd, 0xa4, 0x7f, 0xa5, 0x3e, 0x05, 0xe8, 0x2f, 0x82, 0x31, 0xb0, 0xb9, 0x3d, 0x09, 0x77, 0xab, 0x91, 0x85, 0xb7, 0x98, 0x1d, 0xd0, 0x47, 0x44, 0xbc, 0x55, 0x75, 0x98, 0x47, 0x10, 0xba, 0x7e, 0x14, 0x03, 0x35, 0x12, 0x17, 0x88, 0xc3, 0x13, 0x80, 0x2f, 0x5b, 0x18, 0x42, 0xff, 0xb1, 0x3e, 0x8b, 0x81, 0xc4, 0x0f, 0x0b, 0x1f, 0xab, 0x52, 0x87, 0x15, 0x12, 0xd0, 0x89, 0xf3, 0x20, + 0xb7, 0xfe, 0x7c, 0x8a, 0x80, 0x1d, 0x93, 0x4f, 0xff, 0x8f, 0x07, 0x1f, 0xf8, 0x1f, 0x4f, 0x4f, 0xc2, 0x9f, 0x0f, 0x3c, 0x08, 0x7f, 0x5e, 0x4b, 0x1e, 0x7a, 0x6a, 0xeb, 0x96, 0xa7, 0x0e, 0x35, 0x37, 0x1f, 0x7a, 0x6a, 0xcb, 0xd6, 0xa7, 0x0e, 0x25, 0xd3, 0xaf, 0x18, 0x7d, 0x65, 0x36, 0x6b, 0xa4, 0x10, 0xee, 0x42, 0x11, 0xab, 0xad, 0xcc, 0x67, 0x24, 0xbf, 0xfb, 0x78, 0xfa, 0xcf, 0xd7, 0x76, 0xee, 0xbc, 0x06, 0x94, 0x8f, 0x7f, 0x02, 0xa8, 0xbe, 0xbe, 0x6b, 0xd7, 0xb5, 0xf4, 0x9f, 0x3f, 0x71, 0xe2, 0x87, 0x97, 0xfa, 0xfb, 0x2f, 0xfd, 0xf0, 0xc4, 0xc9, 0x1f, 0x5e, 0xec, 0xef, 0xbf, 0xf8, 0xc3, 0x93, 0xd6, 0x88, 0xd7, 0x68, 0xf4, 0xb2, 0x3d, 0xe0, 0x37, 0xb0, 0xe7, 0x77, 0x6b, 0x16, 0xde, 0xa6, 0xed, 0x1c, 0xde, 0x78, 0xfb, 0xd5, 0x32, 0x7c, 0x0c, 0xc5, 0xd3, 0xf0, 0x53, + 0x22, 0x7c, 0x1c, 0x35, 0x85, 0xc7, 0x41, 0x0c, 0x16, 0xe5, 0xee, 0xad, 0xd8, 0x02, 0x1f, 0x4d, 0x9a, 0xdd, 0x2e, 0x40, 0x14, 0x85, 0x5c, 0xd5, 0xee, 0x6a, 0x87, 0xcd, 0xa8, 0x97, 0x4b, 0x11, 0xf8, 0x58, 0x82, 0x0d, 0xec, 0x5c, 0xd4, 0x71, 0x16, 0xae, 0x4f, 0x65, 0xd3, 0xd7, 0xb2, 0x20, 0x7d, 0xf0, 0x2f, 0x08, 0x04, 0x60, 0xab, 0x1b, 0x6b, 0x1e, 0x3f, 0x31, 0xe0, 0xf7, 0x0f, 0xde, 0x39, 0xd1, 0x34, 0x52, 0x65, 0x99, 0x1d, 0x7f, 0xf4, 0xd6, 0x53, 0xaf, 0x9f, 0x6f, 0xef, 0xb8, 0xe7, 0x07, 0xe7, 0xff, 0xe1, 0x9f, 0xa6, 0x87, 0x22, 0xc9, 0xa0, 0xb6, 0x74, 0xf3, 0xa3, 0xe0, 0x9f, 0xac, 0xa5, 0xf5, 0xde, 0x40, 0x63, 0x69, 0x41, 0xe5, 0xa6, 0x7b, 0x46, 0x46, 0xce, 0x6f, 0xa8, 0xb0, 0x04, 0x63, 0xf6, 0xa6, 0x9e, 0x93, 0x64, 0x9c, 0x3f, 0xc3, 0x7c, 0xf8, 0xfe, 0x89, 0xd3, + 0x6e, 0x4d, 0xb4, 0xb6, 0xc9, 0x59, 0x39, 0xd5, 0x57, 0xc6, 0x61, 0x5e, 0xe9, 0x67, 0xe9, 0x7f, 0x23, 0x9a, 0x89, 0x5b, 0x93, 0xfa, 0x2a, 0x20, 0x16, 0x45, 0xa0, 0x70, 0x18, 0x13, 0x5c, 0xf4, 0x4e, 0x68, 0x48, 0xd0, 0x99, 0xc4, 0x34, 0x11, 0xc1, 0x56, 0x57, 0xe7, 0x98, 0x24, 0x05, 0x88, 0xa9, 0xac, 0x83, 0x64, 0xbb, 0x99, 0x76, 0x18, 0x98, 0x2d, 0x0f, 0x05, 0x43, 0x5e, 0x83, 0x3e, 0x14, 0xc2, 0x20, 0xa1, 0x5c, 0x94, 0x6b, 0x4c, 0x88, 0x86, 0xcd, 0x3d, 0x9c, 0x14, 0xa2, 0x62, 0xb1, 0xe2, 0xa6, 0xbe, 0x9b, 0x50, 0x84, 0xcd, 0xe6, 0xb2, 0x9a, 0x8e, 0xb2, 0x14, 0x8b, 0x7c, 0xed, 0x44, 0x10, 0x59, 0x9d, 0x11, 0x0c, 0xf6, 0x7e, 0xf9, 0xd8, 0x9e, 0x97, 0xcf, 0xf6, 0x76, 0x9f, 0xbf, 0x7e, 0xb8, 0x71, 0x7b, 0xc4, 0x14, 0x5f, 0xd7, 0x1c, 0x5d, 0xe5, 0xb5, 0x88, 0xdd, 0x5a, + 0xb9, 0x4d, 0x15, 0x2d, 0xa6, 0x8e, 0xe7, 0x63, 0x61, 0x79, 0xc4, 0x6c, 0x75, 0x9c, 0xc7, 0xcb, 0x1a, 0xb4, 0x86, 0xd2, 0xa1, 0x96, 0xb0, 0x4a, 0xb5, 0xd9, 0x2a, 0xa3, 0x29, 0xaa, 0xa5, 0x09, 0xaa, 0xc5, 0x85, 0xe1, 0xf4, 0xfd, 0xe0, 0x9f, 0x98, 0x2f, 0xc1, 0x35, 0xe1, 0x22, 0x9a, 0x88, 0xd5, 0xc9, 0x5e, 0x68, 0x19, 0x32, 0x85, 0x66, 0x68, 0x3d, 0x95, 0xf8, 0x49, 0xb8, 0x6b, 0x77, 0xa3, 0x32, 0xd6, 0x24, 0x43, 0x61, 0x5a, 0x65, 0x9e, 0xfa, 0x90, 0xad, 0x64, 0xc1, 0xad, 0xea, 0x39, 0xba, 0xc7, 0x68, 0x00, 0x44, 0x53, 0x63, 0x4d, 0x55, 0x59, 0xa9, 0xad, 0xc0, 0xe0, 0x32, 0xba, 0xd8, 0x13, 0x5a, 0x29, 0x9e, 0x1f, 0xb9, 0xe0, 0x73, 0xc1, 0x1e, 0x65, 0x02, 0x8b, 0x83, 0x1d, 0xfc, 0xd6, 0x06, 0x75, 0x54, 0xa7, 0x2d, 0xea, 0x37, 0x1a, 0xfd, 0x51, 0x9b, 0xad, 0xc2, 0x6f, + 0x32, 0xf9, 0x2b, 0xd2, 0xbf, 0x88, 0xb4, 0x1b, 0x5d, 0x38, 0xfe, 0x01, 0x3e, 0x56, 0x6e, 0x2e, 0x31, 0xa2, 0x7d, 0x4d, 0xa2, 0x91, 0x8c, 0xdd, 0xdd, 0x82, 0x02, 0x1e, 0xc0, 0x96, 0x18, 0xa8, 0x42, 0x11, 0x8f, 0xeb, 0xf4, 0x33, 0x46, 0x7f, 0xb9, 0xcd, 0x5e, 0x8e, 0x7a, 0x97, 0xdb, 0x6d, 0xf0, 0xe7, 0x7b, 0xd3, 0x15, 0x55, 0x2e, 0x5f, 0xdc, 0xe3, 0x2f, 0x07, 0x9b, 0x9f, 0x98, 0x2b, 0x4c, 0x85, 0xd8, 0xb8, 0x87, 0x6a, 0x94, 0x8d, 0x7b, 0xd4, 0xfa, 0x50, 0xdc, 0x83, 0xfa, 0x3c, 0xcb, 0xd9, 0x8e, 0xea, 0x03, 0xc0, 0xfd, 0xdf, 0x85, 0xb0, 0xa6, 0xea, 0x4c, 0xe4, 0x97, 0xa4, 0xc1, 0x14, 0x74, 0xa7, 0x33, 0x80, 0x8a, 0x0c, 0xfc, 0xc8, 0x45, 0xb8, 0x82, 0x41, 0xbf, 0x47, 0x8b, 0x53, 0xc4, 0x31, 0x84, 0x8e, 0xab, 0x9b, 0x08, 0xd0, 0x8b, 0x06, 0x39, 0xec, 0x30, 0x55, 0x07, + 0xb4, 0xd4, 0xd1, 0xeb, 0x88, 0x85, 0x36, 0xfd, 0x7d, 0xc4, 0x42, 0x0b, 0xb6, 0x5a, 0x7d, 0x06, 0xbb, 0x2a, 0x7d, 0xe7, 0xe7, 0x2c, 0x3e, 0x25, 0xa8, 0x95, 0xa8, 0xb4, 0x46, 0x75, 0x25, 0xf5, 0xce, 0x7b, 0x5f, 0x20, 0x7f, 0xa3, 0xd0, 0xa5, 0x9f, 0xd3, 0xeb, 0xc1, 0xb9, 0x42, 0xb3, 0xc2, 0x6b, 0x00, 0x7f, 0xd6, 0x18, 0xd3, 0x5b, 0xe5, 0x2a, 0x31, 0x05, 0x7e, 0xc9, 0xe3, 0x51, 0xc9, 0x9f, 0xc1, 0xe7, 0x63, 0x08, 0x17, 0x86, 0x79, 0x70, 0x87, 0xac, 0x59, 0xfe, 0x53, 0x86, 0x60, 0xb4, 0x5a, 0x5a, 0x66, 0x2a, 0xf6, 0x1b, 0x91, 0xbb, 0x4e, 0xfe, 0x6c, 0xfe, 0xb3, 0xaf, 0x7c, 0xec, 0x63, 0xd4, 0x3b, 0x1c, 0xd7, 0xe5, 0x60, 0xfa, 0x69, 0x2e, 0xe7, 0x30, 0x5b, 0x23, 0x17, 0x61, 0xf3, 0x2b, 0xe1, 0xab, 0xfb, 0x72, 0x73, 0x0e, 0x85, 0xb7, 0xf8, 0x1a, 0xb9, 0xcb, 0xe5, 0x1c, + 0xf2, 0x25, 0xa9, 0xd8, 0xf2, 0xc1, 0x1c, 0xcd, 0xfb, 0x8a, 0x8d, 0xf7, 0x64, 0x6a, 0x0d, 0xaf, 0x9c, 0xa4, 0x98, 0xd7, 0x1c, 0x65, 0x52, 0x85, 0xbc, 0xfa, 0xbf, 0x21, 0x4b, 0x51, 0x9b, 0x0f, 0xc9, 0x8f, 0xec, 0xe3, 0xaa, 0x4f, 0x0c, 0xa3, 0x1c, 0xc5, 0xdc, 0xe8, 0xe9, 0x35, 0x9c, 0xd8, 0x39, 0x93, 0x68, 0xd8, 0xfb, 0xc4, 0xcc, 0xc6, 0xd3, 0xa5, 0x59, 0x72, 0xf7, 0xde, 0x36, 0xca, 0x28, 0x8c, 0xa0, 0xbd, 0xb7, 0x01, 0xfc, 0xa6, 0x8b, 0x4d, 0xec, 0x7c, 0xed, 0xc2, 0xea, 0xd6, 0x5a, 0xbe, 0x2e, 0xca, 0xbf, 0x33, 0xd7, 0xe1, 0x7c, 0xd1, 0x13, 0x15, 0x88, 0xed, 0x0b, 0x11, 0x77, 0x68, 0x22, 0x80, 0x10, 0x39, 0xf5, 0x0c, 0x62, 0xae, 0x01, 0x68, 0x41, 0x71, 0x59, 0xfa, 0xf0, 0x3e, 0xc8, 0xbf, 0x1f, 0xe5, 0xee, 0x4b, 0x72, 0xee, 0x67, 0x6f, 0x51, 0xdc, 0xad, 0xd1, 0x51, + 0x8e, 0x15, 0xc4, 0x89, 0x09, 0x0e, 0x91, 0xbb, 0x27, 0x1a, 0x43, 0x16, 0xe5, 0x6e, 0x3c, 0x1b, 0xe8, 0x71, 0x86, 0x23, 0xec, 0x64, 0xff, 0x54, 0x88, 0xe0, 0x08, 0x95, 0xf9, 0x46, 0xc4, 0xf2, 0xed, 0xd9, 0x1c, 0xb8, 0x4c, 0x7b, 0x82, 0x6b, 0x2e, 0x19, 0x25, 0x10, 0xb5, 0x5e, 0x7e, 0x6b, 0xf4, 0xc0, 0xd1, 0x0f, 0x68, 0x8d, 0xb3, 0xe1, 0xd1, 0xaf, 0xcc, 0x38, 0xc1, 0x15, 0x74, 0x89, 0x2c, 0xdb, 0x85, 0xa7, 0x7e, 0x99, 0xce, 0x90, 0x53, 0xe3, 0xda, 0x2e, 0x70, 0xd4, 0x15, 0x5a, 0x7d, 0xc8, 0xe7, 0x31, 0x07, 0xbc, 0x50, 0xaf, 0x38, 0x8a, 0xfd, 0xc8, 0xa9, 0x11, 0xe7, 0x26, 0xf3, 0xb2, 0x2e, 0x9b, 0x67, 0x89, 0x4a, 0x2e, 0x5a, 0x0f, 0xf5, 0x73, 0x5a, 0x02, 0x9d, 0x9a, 0x9f, 0xe7, 0x54, 0x1b, 0xa1, 0xfe, 0x11, 0xfb, 0x70, 0xce, 0x1b, 0x1f, 0x5a, 0x94, 0x99, 0x7a, 0x9d, + 0x12, 0x9d, 0x91, 0x4a, 0xc9, 0x6f, 0x4d, 0x08, 0x0a, 0x8c, 0x9c, 0xc1, 0x4e, 0xce, 0xc3, 0xf9, 0x35, 0x5b, 0xd2, 0x95, 0xec, 0xba, 0xba, 0x24, 0xb6, 0xe1, 0x5c, 0xe8, 0x21, 0x70, 0x17, 0x3b, 0x38, 0xe6, 0x5e, 0x40, 0xc8, 0x10, 0x30, 0x3c, 0x06, 0x9d, 0x87, 0x06, 0xa8, 0xfb, 0x44, 0x4e, 0xa8, 0x5b, 0x19, 0x7e, 0x02, 0xf0, 0x1f, 0x8b, 0xf3, 0x3f, 0xc6, 0x9f, 0x28, 0x96, 0xf8, 0x84, 0x1f, 0xf5, 0x2a, 0x82, 0x96, 0x30, 0x12, 0x04, 0x50, 0x23, 0xa1, 0xbb, 0x4b, 0x8a, 0xa6, 0x08, 0xb9, 0x7c, 0x06, 0x87, 0x9f, 0x65, 0x63, 0x0a, 0x80, 0xf3, 0xb1, 0xd0, 0x89, 0x0d, 0x07, 0x73, 0xce, 0x1e, 0x2e, 0x70, 0x7f, 0xb8, 0x79, 0xc9, 0xee, 0x84, 0xa0, 0xb7, 0x58, 0xfc, 0x81, 0x5f, 0x84, 0xe6, 0x49, 0xd3, 0xf2, 0x5f, 0x44, 0x70, 0xdf, 0xa3, 0x18, 0x25, 0x14, 0x8a, 0xd9, 0xe5, 0xbe, + 0x26, 0xb9, 0x8a, 0xa0, 0xa5, 0x8c, 0xf4, 0xa6, 0xbe, 0x01, 0xf7, 0x05, 0x00, 0xa7, 0xf0, 0xec, 0xc9, 0x85, 0xe1, 0x8d, 0x26, 0x0d, 0x03, 0xab, 0xfb, 0xfb, 0x52, 0x5d, 0xcd, 0xd0, 0x97, 0x43, 0x6e, 0xa4, 0x3e, 0x50, 0xa8, 0x5c, 0x94, 0xc0, 0xe3, 0xbf, 0x19, 0xec, 0xee, 0x92, 0x93, 0x07, 0x39, 0x57, 0xb9, 0xa9, 0xcd, 0xe4, 0x16, 0xec, 0x7a, 0x1e, 0xe5, 0x5c, 0xcf, 0x52, 0xa9, 0x45, 0x6b, 0x08, 0xd7, 0x40, 0xd7, 0x73, 0x17, 0x74, 0x3d, 0x5b, 0xb7, 0x77, 0xbd, 0xa0, 0x52, 0x80, 0xd4, 0xfc, 0x7f, 0x1e, 0x5c, 0x6a, 0x5e, 0x99, 0x35, 0xc2, 0xb4, 0xe7, 0x17, 0x57, 0xaa, 0x1c, 0xa8, 0xb5, 0xca, 0xc1, 0x47, 0x6e, 0x3c, 0xbe, 0x78, 0xc6, 0xb1, 0xde, 0x2a, 0xe2, 0x03, 0x1b, 0x5c, 0x68, 0x16, 0xff, 0x16, 0xd7, 0x25, 0xd3, 0x12, 0x6e, 0x62, 0x04, 0xbc, 0xc9, 0xcd, 0xbd, 0x7e, + 0x40, 0x4a, 0x75, 0x80, 0x21, 0x9b, 0xdc, 0x24, 0x01, 0x14, 0x40, 0x44, 0xd0, 0xdd, 0xd5, 0x40, 0xd4, 0xc1, 0xcf, 0xbd, 0x7e, 0x40, 0x2d, 0xf3, 0x71, 0x21, 0xf7, 0xb1, 0x7c, 0xe9, 0x8f, 0xa3, 0xcb, 0x7e, 0x2c, 0xe9, 0xb0, 0x2d, 0xfe, 0x84, 0x66, 0x3f, 0x19, 0xcd, 0x68, 0xac, 0x22, 0x54, 0xa9, 0x88, 0x60, 0xc0, 0x14, 0x1c, 0x5f, 0x16, 0x40, 0x23, 0x1d, 0x93, 0x03, 0x94, 0x95, 0xa5, 0x52, 0xd0, 0x4a, 0x1a, 0x2a, 0xa6, 0x71, 0x89, 0x98, 0x14, 0x89, 0xa6, 0x45, 0xfc, 0x74, 0xad, 0xcc, 0xe9, 0x01, 0xc7, 0x9c, 0xef, 0x40, 0xac, 0xdc, 0x97, 0xcd, 0x21, 0x5b, 0xd4, 0x97, 0xe0, 0xba, 0xca, 0x47, 0x71, 0x8d, 0xd0, 0xa5, 0x7a, 0x46, 0xf1, 0xdc, 0xbe, 0xe9, 0x9e, 0x62, 0x8c, 0xb4, 0xe5, 0x2e, 0x25, 0xe3, 0x70, 0xa6, 0xee, 0x91, 0xc0, 0xb9, 0x5d, 0x7b, 0x13, 0xdf, 0xa0, 0xa4, + 0xb2, 0x5f, 0x00, 0xb5, 0x1e, 0x3e, 0x65, 0x85, 0x9d, 0xb1, 0xc2, 0xf3, 0x7b, 0x3c, 0x80, 0xf0, 0x8c, 0x78, 0x46, 0xd6, 0x0c, 0x0e, 0xac, 0xee, 0x4d, 0xad, 0x6a, 0x6e, 0xa8, 0x2b, 0x2f, 0x0b, 0xfa, 0x9d, 0x76, 0x93, 0x41, 0xcb, 0x85, 0x80, 0xd4, 0xb9, 0x21, 0x20, 0x06, 0x25, 0xed, 0xe4, 0x69, 0x45, 0x66, 0x99, 0xb0, 0xd0, 0xb2, 0x13, 0x1d, 0x88, 0x1e, 0xc0, 0xbc, 0x12, 0xe1, 0x80, 0x57, 0x33, 0x7f, 0x88, 0x14, 0x51, 0x50, 0x6d, 0xfe, 0x6b, 0x8e, 0xda, 0x24, 0xbf, 0x94, 0x0d, 0x1d, 0x0d, 0x1d, 0x67, 0x43, 0x47, 0x6e, 0x37, 0x0a, 0x1d, 0xcd, 0x5f, 0x5d, 0x62, 0xc2, 0x33, 0x2a, 0xc5, 0xfc, 0xef, 0x32, 0x61, 0x24, 0x3d, 0x23, 0x61, 0x72, 0xd4, 0xea, 0x5d, 0x4b, 0x07, 0x96, 0x96, 0x98, 0xf3, 0x99, 0x5a, 0x7c, 0x7e, 0x68, 0xe5, 0x28, 0x98, 0x06, 0xc2, 0x4c, 0xf4, 0x13, + 0xff, 0x81, 0x0d, 0x93, 0x17, 0xfb, 0x9b, 0x49, 0x4a, 0x0c, 0x38, 0x3a, 0x4d, 0x74, 0x05, 0xd0, 0x15, 0x7f, 0x9b, 0x27, 0xd1, 0x8c, 0x10, 0x22, 0x89, 0x68, 0x1f, 0x81, 0xce, 0x51, 0x58, 0x8c, 0x05, 0x77, 0xac, 0x8d, 0x49, 0xf3, 0x58, 0xf4, 0x3f, 0xaa, 0x71, 0x89, 0xcd, 0x5a, 0x76, 0x83, 0x43, 0xf4, 0x88, 0xe5, 0x39, 0xbd, 0xe0, 0xc6, 0x9c, 0x39, 0x0e, 0xc7, 0xbf, 0x2f, 0xd5, 0xf1, 0x6f, 0xfd, 0x4b, 0x5c, 0x35, 0xcd, 0x54, 0x57, 0x67, 0x47, 0x5b, 0x43, 0x7d, 0x55, 0xa5, 0xc7, 0x65, 0xb7, 0x6a, 0x35, 0x84, 0x19, 0x98, 0x64, 0x59, 0x98, 0xa3, 0x28, 0x5e, 0x59, 0xe5, 0xd5, 0xa2, 0x71, 0x05, 0x8b, 0x4a, 0x3f, 0xd1, 0xac, 0xa5, 0x9d, 0x7b, 0xfa, 0xc4, 0x62, 0x5e, 0xa8, 0xd1, 0x81, 0xa9, 0x84, 0xae, 0xb4, 0x6b, 0x72, 0xd3, 0x64, 0x57, 0xa9, 0xe3, 0x3f, 0xdc, 0xc9, 0x0d, + 0x4d, 0x64, 0xef, 0x12, 0xf5, 0x9e, 0x94, 0xbe, 0xc6, 0x8d, 0xe7, 0xc6, 0xdf, 0x38, 0xf7, 0xb9, 0x69, 0x7f, 0xb6, 0x44, 0xdc, 0xc9, 0xc0, 0x48, 0x6c, 0xe6, 0x53, 0x35, 0x17, 0xcd, 0xc1, 0x0a, 0xab, 0xbf, 0xdc, 0x6b, 0xf7, 0x14, 0x35, 0x4f, 0xb4, 0x34, 0xd7, 0xcf, 0xff, 0x31, 0xd0, 0x1c, 0x75, 0x90, 0x57, 0xb9, 0x42, 0x4f, 0x15, 0x8e, 0x40, 0x85, 0xdf, 0xe1, 0x0e, 0x37, 0x0e, 0x37, 0x06, 0x7b, 0xdb, 0xea, 0x8a, 0x0b, 0x2a, 0x46, 0x0e, 0xb5, 0x93, 0xfa, 0xec, 0x69, 0x96, 0x5a, 0x9b, 0xac, 0x61, 0xfd, 0xd4, 0x50, 0xfa, 0x12, 0xfd, 0x1a, 0x73, 0x05, 0xee, 0x7c, 0xed, 0xa0, 0x8f, 0x85, 0xa0, 0x98, 0xf4, 0x40, 0x46, 0x36, 0x02, 0x20, 0x47, 0x95, 0x23, 0xa3, 0x40, 0x42, 0x04, 0x00, 0x23, 0x42, 0xfb, 0xe0, 0x52, 0x1f, 0x88, 0x19, 0xde, 0x16, 0xad, 0x24, 0x64, 0x24, 0x45, + 0xca, 0x28, 0x61, 0x7e, 0x28, 0xe2, 0x24, 0xe7, 0x16, 0x17, 0xb2, 0x29, 0x90, 0xcc, 0x59, 0xae, 0x11, 0x41, 0x75, 0xd4, 0xc2, 0x9b, 0xef, 0xbb, 0x27, 0xbf, 0x2f, 0x7a, 0xdc, 0x3a, 0xd8, 0x97, 0x26, 0x65, 0x34, 0x1c, 0x55, 0x5a, 0x4e, 0x53, 0x72, 0x81, 0xd9, 0xfa, 0xc1, 0x5f, 0x91, 0x6c, 0x59, 0xd4, 0x7b, 0x51, 0xa2, 0xea, 0x92, 0xdf, 0x22, 0xcc, 0x54, 0x85, 0xb3, 0x45, 0x87, 0x8a, 0x05, 0xd7, 0xd6, 0xf8, 0x30, 0x58, 0xc0, 0x6f, 0x2c, 0x54, 0xa0, 0xed, 0xce, 0xc0, 0xe1, 0xe3, 0x3c, 0xfe, 0x0f, 0x04, 0x0e, 0x34, 0x2c, 0x7d, 0x5e, 0x09, 0xce, 0xb5, 0x56, 0xda, 0xe3, 0xd1, 0xb0, 0x26, 0xfd, 0x5d, 0x50, 0x91, 0x7f, 0xca, 0x9c, 0x87, 0x31, 0x8a, 0x1e, 0xe9, 0x6a, 0xd8, 0xd8, 0x52, 0x98, 0x9d, 0x2b, 0x15, 0xc0, 0xde, 0x9e, 0xd4, 0x58, 0x0b, 0xb5, 0xe4, 0x96, 0x7f, 0xc9, + 0xc1, 0x1c, 0xbc, 0xba, 0xe6, 0xdc, 0x54, 0x42, 0x6b, 0x18, 0x2a, 0x50, 0x29, 0x5f, 0x9f, 0x2d, 0x4d, 0x04, 0x3b, 0xa7, 0x1b, 0xc8, 0xb5, 0xd9, 0xc9, 0x81, 0x6b, 0xde, 0x85, 0x16, 0x62, 0xcc, 0x05, 0xb8, 0xa6, 0x95, 0x84, 0x8d, 0x98, 0x06, 0xa7, 0x58, 0x86, 0x49, 0xe3, 0xf4, 0x3a, 0x92, 0x92, 0x97, 0x18, 0xe0, 0x5e, 0x92, 0xea, 0x24, 0xc5, 0x98, 0xa0, 0x5a, 0xdc, 0xc1, 0x15, 0x70, 0x46, 0x1f, 0x32, 0x8b, 0x3f, 0xcc, 0xde, 0x27, 0x85, 0xf7, 0x79, 0x15, 0x00, 0x47, 0x4f, 0x21, 0xdb, 0xc7, 0x93, 0x3c, 0xf0, 0x87, 0xf8, 0x52, 0xe4, 0xda, 0x61, 0x56, 0xe9, 0xdd, 0x29, 0xfc, 0x3b, 0x27, 0x7f, 0x52, 0x09, 0xad, 0x70, 0xf1, 0x38, 0xaa, 0x48, 0xb2, 0x33, 0xc3, 0x7b, 0xf9, 0x77, 0x7f, 0xc5, 0x9e, 0x4c, 0xf1, 0xe9, 0xea, 0x25, 0xbf, 0x02, 0x0e, 0x2f, 0xff, 0x05, 0x60, 0xb9, + 0xfe, 0x68, 0x02, 0xe5, 0x75, 0x25, 0x97, 0xeb, 0x8a, 0xc2, 0xd2, 0x6c, 0xc7, 0x94, 0x12, 0x20, 0x8f, 0x8a, 0xfb, 0x12, 0x34, 0x81, 0xe0, 0xde, 0xb2, 0x79, 0xe3, 0x86, 0xc9, 0x89, 0xb1, 0x35, 0x83, 0xfd, 0xbd, 0xf5, 0xb5, 0x15, 0x51, 0xbf, 0x17, 0x95, 0x8b, 0xe2, 0xe9, 0x89, 0x54, 0xf9, 0xf4, 0x44, 0x7e, 0xf6, 0x58, 0x98, 0xad, 0x2b, 0xed, 0x61, 0x6e, 0x92, 0xac, 0x08, 0x9d, 0x39, 0x2d, 0x26, 0xee, 0x63, 0x4f, 0x54, 0xaa, 0xd9, 0xa3, 0x08, 0x1f, 0xa2, 0x2f, 0x9a, 0x9f, 0xa8, 0xae, 0x29, 0xea, 0x70, 0x7a, 0x03, 0x95, 0xb1, 0xf4, 0xbf, 0x53, 0x5f, 0xca, 0x92, 0x3e, 0x75, 0xfb, 0x9b, 0xeb, 0x58, 0x2e, 0xa3, 0xd1, 0x9a, 0x5c, 0x2e, 0xa3, 0x94, 0x44, 0x2d, 0x0d, 0xb4, 0x39, 0xea, 0xee, 0x4f, 0x1d, 0x3b, 0x95, 0x3d, 0x52, 0xff, 0xd7, 0xb5, 0x5b, 0x73, 0x89, 0x8d, 0x2e, + 0x34, 0xb4, 0xfa, 0xdd, 0xf5, 0x97, 0x46, 0xb6, 0x7e, 0xbe, 0xcd, 0x2d, 0xa0, 0x38, 0x0a, 0xae, 0x5b, 0x92, 0xe2, 0x68, 0x7b, 0x54, 0x53, 0x64, 0x6b, 0xa9, 0xbf, 0x70, 0x27, 0x39, 0x96, 0x9d, 0x9f, 0x6e, 0xc4, 0x76, 0x84, 0xe3, 0xc8, 0xe9, 0x8b, 0x22, 0x07, 0xf3, 0x02, 0xd1, 0x09, 0xa4, 0x9c, 0x0f, 0x57, 0x5b, 0x43, 0xca, 0xa4, 0x08, 0x42, 0x42, 0xeb, 0xd9, 0x6c, 0x50, 0xce, 0xa3, 0xce, 0xdc, 0x17, 0x71, 0xf7, 0xf3, 0x6e, 0x51, 0x59, 0x08, 0x65, 0x8c, 0x67, 0x31, 0xdf, 0xc1, 0x26, 0x6b, 0xb1, 0x27, 0xb3, 0x98, 0xd0, 0x1c, 0x17, 0x57, 0x60, 0xb3, 0x7a, 0x04, 0x58, 0xe2, 0x39, 0xc0, 0x6b, 0xb1, 0xf2, 0x6c, 0x57, 0xc1, 0x99, 0xae, 0x48, 0xb4, 0x42, 0x47, 0x38, 0xf3, 0x48, 0x06, 0x31, 0x70, 0xcc, 0xdd, 0x54, 0x5f, 0xc1, 0x01, 0x0d, 0x52, 0x3a, 0x1d, 0x6d, 0xc9, 0x86, + 0xea, 0x84, 0x09, 0xf1, 0xaa, 0x7a, 0xfc, 0x61, 0x1f, 0x56, 0x3a, 0x2b, 0x27, 0x0f, 0xe1, 0xf4, 0x8f, 0x15, 0x48, 0x99, 0x2b, 0xcc, 0xd4, 0x11, 0x4f, 0xe7, 0x20, 0x9b, 0xf5, 0x91, 0xaf, 0x70, 0xd8, 0xc4, 0x90, 0xf4, 0xeb, 0x1c, 0x29, 0xf3, 0xc9, 0xc1, 0xb6, 0x5a, 0x3a, 0x4b, 0xaf, 0xfa, 0xf9, 0x90, 0x9c, 0xda, 0xee, 0xe3, 0x93, 0x41, 0x4e, 0x08, 0x35, 0x0e, 0xf9, 0x10, 0x97, 0x2e, 0x92, 0x65, 0x65, 0x1e, 0xc7, 0x2c, 0xaa, 0x9b, 0x95, 0x1c, 0x8b, 0xea, 0x70, 0x10, 0x8e, 0xe7, 0x1d, 0x70, 0x03, 0xfe, 0x36, 0xb4, 0x9d, 0x9b, 0x89, 0xc6, 0x64, 0x9d, 0x03, 0x63, 0x33, 0x79, 0xfc, 0x25, 0x3a, 0x55, 0x9c, 0x49, 0xe5, 0xa0, 0x30, 0xf9, 0xbd, 0x1a, 0xfe, 0xda, 0x4c, 0x34, 0xd7, 0xd7, 0x04, 0x7c, 0x6e, 0x97, 0xad, 0x20, 0x2e, 0x46, 0xc7, 0x34, 0x68, 0x57, 0xae, 0x03, 0xf9, + 0x05, 0x1a, 0x45, 0xcb, 0x6c, 0xd2, 0xa0, 0x15, 0x6d, 0xc2, 0x6f, 0x0c, 0x4c, 0x55, 0xe9, 0x72, 0xe1, 0x6a, 0x25, 0x18, 0xf2, 0x96, 0xca, 0xdd, 0x95, 0x1b, 0x36, 0x9d, 0x1d, 0xff, 0xd1, 0xb9, 0xcf, 0x4e, 0x07, 0x98, 0xeb, 0xef, 0x9f, 0x44, 0xdb, 0x6e, 0xce, 0x5e, 0x5c, 0x8f, 0x80, 0x6a, 0x35, 0x1d, 0x96, 0xe5, 0xf7, 0x61, 0xfa, 0x18, 0x9a, 0xbb, 0x27, 0xe0, 0x0b, 0xfc, 0x0c, 0xbe, 0xab, 0x99, 0x08, 0x22, 0x04, 0x17, 0x89, 0x33, 0x1e, 0xb9, 0xb3, 0x28, 0x8a, 0x0f, 0xed, 0xe1, 0x7a, 0x37, 0x5e, 0xa3, 0xc7, 0xc0, 0x46, 0xbd, 0x39, 0x7f, 0x9a, 0xcf, 0xa2, 0xab, 0x03, 0xe8, 0xa4, 0x89, 0xfe, 0x19, 0xb2, 0xe7, 0xa0, 0xd3, 0xfc, 0x7e, 0xdf, 0xad, 0x83, 0x45, 0xd7, 0x3a, 0x92, 0x26, 0x68, 0x20, 0xfe, 0x98, 0x92, 0x53, 0x97, 0x59, 0xf7, 0xd8, 0x9e, 0xdc, 0xd1, 0x4f, 0x7f, + 0xf6, 0xfd, 0x35, 0xab, 0xea, 0xc8, 0x6f, 0x3e, 0xcc, 0xee, 0xf9, 0x3b, 0xa0, 0x82, 0x7f, 0x95, 0x79, 0x8c, 0x10, 0x13, 0x6a, 0x22, 0x9a, 0x2c, 0x15, 0x21, 0x66, 0x18, 0x06, 0xe1, 0x0a, 0x29, 0x82, 0x4d, 0x88, 0x61, 0x18, 0x6c, 0x13, 0xb1, 0x04, 0xe0, 0xd3, 0x38, 0xe5, 0x12, 0xb3, 0xbf, 0xa3, 0x00, 0x8f, 0x27, 0x4b, 0xc0, 0x8f, 0xff, 0x51, 0xaf, 0x9e, 0x99, 0xff, 0xd2, 0x19, 0x6a, 0x0d, 0xfb, 0x7f, 0x70, 0x28, 0x7d, 0x8e, 0x0c, 0x83, 0x6e, 0xf6, 0x27, 0x01, 0x16, 0x0e, 0xa1, 0x5c, 0x71, 0x9c, 0xaf, 0x7d, 0x30, 0xa9, 0x32, 0x01, 0x92, 0xf6, 0x43, 0x63, 0x02, 0x8e, 0x2c, 0x43, 0x71, 0xc0, 0x36, 0x0f, 0xc5, 0x07, 0x2e, 0x77, 0xa4, 0x32, 0x85, 0x8d, 0x73, 0x12, 0xc8, 0x51, 0x5d, 0x63, 0x8a, 0x14, 0x91, 0x14, 0xce, 0x1e, 0x5e, 0xb1, 0xe9, 0x68, 0x52, 0x0d, 0x7f, 0x09, + 0x10, 0x81, 0xa0, 0xdf, 0x13, 0xf4, 0x6a, 0x51, 0xa1, 0x2c, 0x61, 0xc2, 0xf9, 0x12, 0x78, 0xb7, 0xfc, 0x04, 0xf4, 0xca, 0x7a, 0x77, 0x19, 0x42, 0x07, 0xa9, 0x4b, 0x58, 0x74, 0x50, 0x36, 0x1f, 0xfd, 0x0d, 0x36, 0x1f, 0x9d, 0x52, 0xe0, 0xd2, 0x81, 0xfd, 0xec, 0xf1, 0xf2, 0x3c, 0x85, 0x73, 0xd3, 0x7f, 0x87, 0xf3, 0xe3, 0x0e, 0xa5, 0x2f, 0xd2, 0xbf, 0xc0, 0x79, 0xda, 0x5f, 0x46, 0x59, 0xe8, 0x24, 0x9c, 0x5f, 0x8c, 0x48, 0xca, 0xbd, 0x2b, 0x3a, 0x30, 0x0f, 0x08, 0xde, 0x95, 0xc8, 0x7d, 0x7e, 0xb4, 0x15, 0x08, 0x00, 0xfd, 0x85, 0xb8, 0x18, 0x0c, 0xbc, 0x09, 0x24, 0xe2, 0xb9, 0xe5, 0x7b, 0x11, 0x82, 0x4e, 0xd0, 0x88, 0xe2, 0xa5, 0xc4, 0xf5, 0x23, 0x6e, 0xa2, 0x1b, 0x8a, 0xe4, 0x78, 0xbc, 0x7a, 0x24, 0xaf, 0x60, 0xa1, 0x94, 0x27, 0xf1, 0x15, 0x70, 0xb0, 0x2d, 0x2f, 0x34, + 0x01, 0xec, 0x9f, 0xfe, 0x05, 0x87, 0xc6, 0x5e, 0x49, 0x8a, 0xd7, 0x78, 0xe8, 0x36, 0x7f, 0x36, 0xff, 0xec, 0x4a, 0x12, 0xcd, 0x24, 0x04, 0x90, 0x84, 0x1e, 0xce, 0xd7, 0xb7, 0x30, 0xee, 0xca, 0x44, 0x94, 0x25, 0x4b, 0x68, 0x84, 0xfb, 0xed, 0x46, 0x45, 0xbd, 0x11, 0x95, 0xd1, 0x66, 0x2e, 0xdd, 0x81, 0x2b, 0xc1, 0x67, 0x32, 0x22, 0x6a, 0x5b, 0x39, 0xae, 0xae, 0x88, 0xaa, 0x7b, 0x03, 0x1e, 0x13, 0xc2, 0xd2, 0xcc, 0x7b, 0xc4, 0x2c, 0x1c, 0x84, 0xfc, 0xe4, 0x11, 0xa9, 0x5e, 0x26, 0x35, 0x48, 0x8f, 0x90, 0xf7, 0x3f, 0x50, 0xe0, 0x94, 0xf4, 0xa4, 0xc7, 0x7a, 0x44, 0x1e, 0xcb, 0x83, 0xe0, 0xbe, 0xaf, 0x81, 0x3b, 0x40, 0x9f, 0xb9, 0xc9, 0xe7, 0x6f, 0x32, 0xa7, 0x5f, 0x48, 0xdf, 0x61, 0xd1, 0x7f, 0xf7, 0xbb, 0x5a, 0x2b, 0x42, 0xa4, 0x90, 0x0b, 0x69, 0xb8, 0x66, 0xff, 0x27, + 0x8e, 0xf5, 0x5a, 0x88, 0xbe, 0x64, 0x0a, 0xba, 0x1d, 0xd0, 0x97, 0xc1, 0x58, 0x6e, 0x40, 0x6e, 0x42, 0xee, 0x03, 0x43, 0x11, 0xcc, 0x26, 0xc4, 0x8c, 0x9d, 0xa5, 0x5b, 0xa6, 0xc7, 0xb8, 0x02, 0x3c, 0x22, 0x11, 0x74, 0x2d, 0x2d, 0x22, 0x54, 0x78, 0x47, 0x8f, 0xb2, 0xbe, 0x15, 0x72, 0x99, 0x54, 0x22, 0x46, 0xe1, 0x61, 0x39, 0x7e, 0x4c, 0xfc, 0x8c, 0x7a, 0xb8, 0xbe, 0xd1, 0xbf, 0x20, 0xf7, 0x9c, 0x1e, 0x23, 0xf9, 0xa1, 0x06, 0xb9, 0x45, 0xf9, 0x79, 0x70, 0xa9, 0x10, 0xcc, 0xa6, 0x9f, 0x29, 0x04, 0x0f, 0xcc, 0xff, 0xc0, 0x95, 0x7e, 0x60, 0x5a, 0xe2, 0xb3, 0xde, 0x47, 0x32, 0xe4, 0x5b, 0xf0, 0x61, 0xff, 0x10, 0x68, 0xb2, 0xa7, 0x1f, 0x02, 0xff, 0x86, 0x73, 0xa0, 0x1e, 0x7e, 0xf7, 0xbb, 0x06, 0x33, 0xf5, 0xce, 0xc3, 0xd8, 0x86, 0xeb, 0x59, 0x78, 0x53, 0xa4, 0xa0, 0xe5, + 0x58, 0x6e, 0xa3, 0xc4, 0x37, 0x38, 0x36, 0xd7, 0xee, 0x16, 0x68, 0x68, 0x87, 0xbd, 0x24, 0x81, 0x4b, 0xea, 0xe0, 0x4b, 0x29, 0x77, 0x39, 0xca, 0x7b, 0xf8, 0x32, 0x8a, 0xa4, 0x64, 0x88, 0x25, 0x0a, 0x1a, 0xfa, 0x0c, 0x31, 0x27, 0x01, 0xa4, 0x52, 0x45, 0x02, 0x86, 0x84, 0x66, 0x15, 0x32, 0x67, 0xc6, 0x14, 0x40, 0x2e, 0xdf, 0x9d, 0xa2, 0x71, 0x9c, 0x13, 0xad, 0xc4, 0x9d, 0x28, 0x25, 0xa0, 0x91, 0x90, 0x49, 0x49, 0xe9, 0xd2, 0xdd, 0x88, 0x6c, 0x2f, 0xb8, 0xc7, 0x2d, 0xf9, 0x05, 0xa3, 0xc9, 0x42, 0x82, 0x18, 0x1d, 0x59, 0xdd, 0xd7, 0xd9, 0x9e, 0x6c, 0xac, 0xab, 0x89, 0xc7, 0x4a, 0x8b, 0x83, 0x7e, 0x04, 0x1f, 0xd3, 0x69, 0xd8, 0x62, 0x99, 0x3e, 0x75, 0x96, 0xf8, 0xac, 0x09, 0x93, 0xe7, 0x06, 0xd8, 0x5d, 0x2b, 0x9f, 0x97, 0x38, 0xc8, 0x7b, 0xd2, 0x02, 0x32, 0x34, 0x96, + 0xb5, 0x4b, 0x68, 0x1a, 0x89, 0xdf, 0x75, 0xd6, 0x0e, 0x25, 0x0e, 0x9e, 0x56, 0x7d, 0xe9, 0xd6, 0x27, 0xb7, 0x44, 0x26, 0xfb, 0xaa, 0x36, 0x74, 0x84, 0xa7, 0x26, 0x93, 0xeb, 0x10, 0xad, 0xae, 0x5d, 0xe6, 0x1d, 0xfa, 0xe2, 0xbe, 0x43, 0x17, 0xbd, 0x89, 0xcf, 0x1e, 0x5c, 0x73, 0xff, 0xf6, 0x7a, 0xad, 0x8d, 0xdc, 0xc3, 0x44, 0x52, 0xb3, 0x0d, 0x6d, 0xeb, 0xeb, 0x03, 0x5a, 0xbb, 0xb2, 0xd2, 0xbb, 0x7e, 0x66, 0x47, 0xe5, 0xbe, 0xaf, 0xd6, 0xd8, 0xd7, 0x6d, 0x3f, 0x54, 0xdf, 0xbe, 0x7f, 0x4d, 0x69, 0xf2, 0xe2, 0x9f, 0x9e, 0x05, 0x4f, 0x34, 0x6c, 0xeb, 0x2b, 0x79, 0xf8, 0xae, 0xc3, 0x4c, 0x61, 0xbc, 0x2d, 0x34, 0x98, 0xf2, 0xb6, 0xef, 0xe8, 0x7a, 0xeb, 0x96, 0x5b, 0x0b, 0x43, 0x85, 0x46, 0x75, 0xd3, 0xc0, 0xea, 0xcd, 0xc3, 0x43, 0x9b, 0x9a, 0x8f, 0x7c, 0x7e, 0x2e, 0xe2, + 0x1a, 0xbe, 0x63, 0x6d, 0x91, 0xdd, 0x6b, 0xd7, 0xaf, 0x2a, 0xaa, 0x0f, 0xea, 0x76, 0xcd, 0xd5, 0xaf, 0x4d, 0x14, 0xf8, 0x3a, 0x76, 0x74, 0xd6, 0xef, 0x1f, 0xaf, 0xa7, 0xe1, 0xb8, 0x55, 0xc2, 0xc1, 0x43, 0xb5, 0xd5, 0x54, 0x70, 0x77, 0x28, 0x25, 0x86, 0x93, 0x6b, 0xdc, 0x39, 0xe7, 0x1e, 0x52, 0x54, 0x0b, 0x8b, 0x82, 0x3b, 0xe3, 0x26, 0x39, 0x40, 0x45, 0xbd, 0x14, 0x58, 0x15, 0xca, 0x00, 0x17, 0xa3, 0x9f, 0x16, 0xf7, 0x14, 0x58, 0xd4, 0x6a, 0x40, 0xe0, 0x14, 0xbd, 0x80, 0xa5, 0xb4, 0xa0, 0x54, 0x6d, 0x56, 0x9b, 0x74, 0x1a, 0x3c, 0xe5, 0xc4, 0x84, 0x0a, 0xa8, 0x94, 0x19, 0x27, 0x36, 0x50, 0x0c, 0x8c, 0x31, 0xa3, 0xd7, 0x58, 0xc0, 0x32, 0xa0, 0x06, 0xbc, 0x71, 0x1e, 0xb6, 0xcf, 0x42, 0xe4, 0xe3, 0x08, 0x68, 0x48, 0xd6, 0xc7, 0x3b, 0x8b, 0xf5, 0xeb, 0xa6, 0xbf, 0x7a, + 0xf5, 0xea, 0x87, 0x36, 0x4d, 0x8f, 0x6e, 0x9a, 0xba, 0x30, 0x56, 0xb4, 0x76, 0xf2, 0x7b, 0xe5, 0x93, 0xe7, 0x47, 0xd3, 0x77, 0xde, 0x73, 0xcf, 0x7d, 0xb6, 0xe2, 0x6a, 0x47, 0xfb, 0x9a, 0xed, 0xeb, 0xe7, 0xc8, 0x9f, 0xa5, 0x7f, 0xd0, 0x3b, 0xd8, 0xdd, 0x7e, 0xc1, 0xdf, 0x3a, 0x51, 0xdd, 0x7f, 0xb4, 0x30, 0x7a, 0x35, 0xb9, 0xb3, 0xbf, 0x74, 0x61, 0x3d, 0xf9, 0x71, 0x6c, 0xaf, 0xf5, 0x2d, 0xfc, 0x85, 0xfa, 0x0c, 0x4d, 0x11, 0x25, 0xc4, 0x21, 0x94, 0x2d, 0x4c, 0x23, 0xde, 0x10, 0x26, 0xcb, 0x4d, 0x49, 0xd1, 0xd0, 0x8a, 0x46, 0x5b, 0x3d, 0xc1, 0xd0, 0x04, 0xb3, 0x9b, 0x77, 0xc0, 0x76, 0x67, 0x15, 0x17, 0xc0, 0x47, 0x80, 0x1f, 0xd8, 0x8e, 0xc7, 0x1f, 0x22, 0xe0, 0x86, 0xc5, 0xa4, 0x55, 0x43, 0x13, 0xba, 0x04, 0x94, 0xb0, 0x69, 0x00, 0xae, 0xbc, 0x30, 0x8b, 0x78, 0xf1, + 0xb1, 0x77, 0x55, 0x82, 0x5a, 0xa5, 0x91, 0xa7, 0x1a, 0x5f, 0x3c, 0xb6, 0xf7, 0xea, 0x1d, 0xad, 0x5d, 0xa7, 0xbf, 0xb4, 0x67, 0xee, 0x4a, 0x43, 0x93, 0x08, 0xda, 0xc0, 0x85, 0xf1, 0xfe, 0xfa, 0xd6, 0x7d, 0x03, 0xa5, 0xd1, 0x35, 0x73, 0xcd, 0xc1, 0xc6, 0x78, 0x99, 0x45, 0xfd, 0xa6, 0x7c, 0xb0, 0x77, 0x00, 0x1f, 0xe8, 0xbd, 0x7a, 0xbe, 0xa7, 0xb3, 0x7e, 0x54, 0x82, 0x4c, 0xda, 0xe8, 0xd8, 0xa9, 0xb5, 0xc3, 0xa7, 0xc7, 0xa2, 0x12, 0x9d, 0xc3, 0xa8, 0x64, 0x73, 0x2c, 0x68, 0x84, 0x63, 0x53, 0x10, 0x05, 0xf8, 0x94, 0x88, 0xb3, 0xff, 0x66, 0x33, 0xa7, 0xdb, 0x70, 0x8b, 0xf5, 0x84, 0x3c, 0x18, 0x7b, 0x01, 0xd8, 0xcc, 0x04, 0x94, 0x3d, 0x21, 0x66, 0x13, 0x16, 0xd0, 0x23, 0xd1, 0xaa, 0xee, 0xbb, 0x5e, 0xdd, 0x9f, 0x3e, 0x0e, 0x4e, 0xec, 0x7f, 0xf5, 0xae, 0xee, 0xd7, 0x4a, + 0xd6, 0x1e, 0x4a, 0x7d, 0xe3, 0x1b, 0xa9, 0x43, 0x6b, 0x4b, 0xc0, 0xfc, 0xdc, 0xcb, 0x77, 0xa5, 0xa8, 0x77, 0x52, 0x67, 0x5e, 0xde, 0x33, 0x78, 0x6a, 0xbc, 0x7c, 0xbe, 0xba, 0x7c, 0xec, 0x34, 0xd2, 0x99, 0x46, 0xf8, 0x37, 0x0b, 0xe0, 0xdf, 0xd4, 0x10, 0x1e, 0xa2, 0x29, 0x59, 0x2f, 0x46, 0x5e, 0x09, 0x9e, 0x33, 0xf8, 0x9c, 0x54, 0x8a, 0x33, 0xfa, 0x24, 0x3c, 0x40, 0x7a, 0x9a, 0xe9, 0xd1, 0x69, 0x01, 0x81, 0x6a, 0xa5, 0xd9, 0xad, 0x5a, 0x8f, 0xce, 0x83, 0xf5, 0x93, 0x94, 0xd0, 0x00, 0x8d, 0x20, 0xde, 0x81, 0x1f, 0xc6, 0xe8, 0xd5, 0xea, 0x79, 0xdf, 0x02, 0xee, 0xff, 0x68, 0x76, 0x98, 0xbb, 0x0f, 0xad, 0x2d, 0x2d, 0x5d, 0x7b, 0xa8, 0xfb, 0x9b, 0xe9, 0xf4, 0x37, 0x6f, 0x81, 0xcf, 0x07, 0x9f, 0xf5, 0x96, 0xf4, 0xb1, 0xdb, 0x6f, 0xff, 0x08, 0x7c, 0x96, 0xb5, 0xe8, 0xa9, + 0xd2, 0x3f, 0xda, 0x99, 0xfe, 0x1e, 0xb5, 0x11, 0x3d, 0x24, 0x7a, 0x5c, 0xf0, 0xed, 0x9d, 0xe0, 0x1a, 0x97, 0x27, 0x2c, 0xc5, 0x18, 0x2c, 0xa8, 0xd5, 0x09, 0x96, 0xa8, 0x76, 0x13, 0x66, 0x1d, 0x19, 0xe5, 0x73, 0xb0, 0x30, 0x66, 0x0c, 0x33, 0x66, 0xc0, 0x56, 0x72, 0x6c, 0x85, 0xb0, 0x27, 0x7b, 0x7c, 0x1e, 0xa9, 0xc7, 0x08, 0xda, 0x51, 0x8e, 0x28, 0xb8, 0x36, 0x7f, 0x07, 0x79, 0x07, 0xb5, 0x81, 0xd5, 0x82, 0x48, 0x03, 0x02, 0xa2, 0x7c, 0xe1, 0xcf, 0x54, 0x3d, 0x2d, 0x21, 0x9a, 0x50, 0xad, 0x3c, 0x25, 0xb4, 0x72, 0xac, 0x38, 0xa7, 0x0e, 0x95, 0xfe, 0x16, 0x89, 0x51, 0x8c, 0x9b, 0xc3, 0x41, 0x31, 0x88, 0x50, 0x8d, 0x9e, 0xe2, 0x2b, 0x80, 0xe7, 0xe0, 0x30, 0x1b, 0xeb, 0x6b, 0xaa, 0xca, 0xcb, 0x10, 0x57, 0xac, 0xcb, 0x61, 0x31, 0xb1, 0xdc, 0x8b, 0xd9, 0x92, 0xad, 0x7c, + 0x08, 0x3b, 0xb0, 0x44, 0x04, 0x3b, 0x87, 0x26, 0x2d, 0xf8, 0xbf, 0x99, 0x7b, 0xef, 0xf8, 0xb8, 0xaa, 0x33, 0x7f, 0xf8, 0x9e, 0x5b, 0xa6, 0xb7, 0x3b, 0x77, 0x7a, 0xbf, 0xd3, 0x35, 0x45, 0x33, 0xd2, 0x34, 0x75, 0x8d, 0x7a, 0xb5, 0xe5, 0x26, 0xd9, 0xb2, 0xdc, 0x25, 0xcb, 0x0d, 0xe3, 0x46, 0x31, 0x60, 0x63, 0x8a, 0x29, 0xb6, 0xc1, 0x06, 0x4c, 0x30, 0xb0, 0x26, 0x01, 0x42, 0x09, 0x25, 0x04, 0x08, 0x90, 0x90, 0xd0, 0x4b, 0x76, 0x93, 0x25, 0x6d, 0x13, 0x9c, 0xdf, 0xb2, 0x3f, 0x02, 0xd9, 0x6c, 0x92, 0x25, 0x09, 0x09, 0x90, 0x90, 0x90, 0x80, 0xae, 0xdf, 0x73, 0xce, 0xbd, 0x53, 0x24, 0xcb, 0x40, 0xf2, 0xbe, 0x7f, 0xbc, 0xf9, 0x10, 0x8f, 0x66, 0x6e, 0x39, 0xfd, 0xe9, 0xcf, 0xf7, 0xa9, 0x2b, 0x61, 0xdd, 0x75, 0x4e, 0x8f, 0x26, 0xdb, 0x10, 0xd6, 0xdd, 0xcd, 0x93, 0xbb, 0xef, + 0x6d, 0x4c, 0xaa, 0x39, 0x9d, 0x36, 0xd4, 0xb0, 0xa8, 0x29, 0x3b, 0x92, 0x73, 0x78, 0x9b, 0x97, 0xe5, 0xf7, 0xec, 0xb9, 0xe4, 0x02, 0x6f, 0xe3, 0x92, 0xac, 0x04, 0x91, 0x47, 0x6d, 0x8b, 0x21, 0xd0, 0xbb, 0x85, 0xbb, 0xfa, 0xc7, 0x2e, 0x46, 0xa0, 0x77, 0x45, 0x6f, 0x7e, 0x72, 0x41, 0xb2, 0x3b, 0x3f, 0x5c, 0x86, 0xc8, 0xf3, 0xe6, 0xc3, 0x96, 0x2b, 0x46, 0xfb, 0x43, 0xed, 0x49, 0x04, 0x92, 0xc7, 0xa1, 0xb5, 0x9f, 0x12, 0xc6, 0xc1, 0x2e, 0x38, 0xaf, 0x94, 0x24, 0x5d, 0xa2, 0xd4, 0x2c, 0x09, 0xf4, 0x1f, 0xce, 0x28, 0x32, 0xd3, 0x6f, 0x45, 0x33, 0x6a, 0x32, 0xaa, 0x14, 0xf0, 0x1e, 0x8a, 0x65, 0xe0, 0xa0, 0x50, 0x38, 0x31, 0x35, 0x07, 0x3c, 0xf3, 0xd0, 0xcb, 0xff, 0x6b, 0x09, 0xd5, 0x3b, 0xab, 0x1d, 0xbf, 0x28, 0x65, 0x53, 0xf8, 0x85, 0xb3, 0x2e, 0x68, 0xb1, 0x04, 0xeb, + 0x9c, 0xae, 0x7a, 0x74, 0xa5, 0x1e, 0x9d, 0x6d, 0xdd, 0xe9, 0x0f, 0xc1, 0x07, 0x38, 0xe7, 0xfb, 0x1c, 0x6c, 0xb0, 0x7d, 0x52, 0x0e, 0x08, 0x9c, 0x63, 0x25, 0xc7, 0xb1, 0xa1, 0x92, 0xb6, 0xcf, 0x12, 0x62, 0xf0, 0xa6, 0x94, 0x92, 0x71, 0x0e, 0x51, 0x32, 0xef, 0xce, 0xb9, 0xb0, 0x63, 0x6e, 0x54, 0x4d, 0x25, 0x21, 0x0f, 0x45, 0xd5, 0x8c, 0x3f, 0xc5, 0xf9, 0x2d, 0x95, 0xe3, 0xe2, 0x8f, 0xb4, 0x81, 0xea, 0xa4, 0x7e, 0xf0, 0x41, 0x6c, 0xd9, 0xfe, 0x25, 0x43, 0xc3, 0x50, 0xb3, 0x22, 0x39, 0x93, 0x3a, 0xe2, 0x59, 0xbc, 0xf4, 0x59, 0xb0, 0x65, 0xf4, 0xf0, 0xfa, 0x2c, 0xe7, 0x6a, 0x74, 0x19, 0x38, 0xd5, 0xfa, 0xdd, 0xe4, 0x71, 0x69, 0xff, 0xfd, 0x1a, 0xf6, 0xd9, 0x85, 0x66, 0xc9, 0x31, 0x4f, 0x56, 0x91, 0x24, 0xfe, 0x06, 0x79, 0x96, 0x0d, 0xf9, 0x90, 0x0c, 0xce, 0x57, 0x57, + 0x31, 0x45, 0xd9, 0x43, 0x12, 0x02, 0x15, 0x64, 0xde, 0x16, 0x2b, 0xf5, 0xeb, 0xfb, 0x79, 0x9f, 0x46, 0x78, 0x5b, 0x17, 0xe0, 0x9b, 0x9f, 0x79, 0x26, 0x91, 0xb1, 0x02, 0x3b, 0x1b, 0x75, 0xdf, 0x2d, 0x3c, 0xa0, 0x77, 0xb3, 0x71, 0x3f, 0xc7, 0x81, 0x0b, 0x38, 0xbd, 0xd0, 0x49, 0x7d, 0x24, 0x2c, 0xf7, 0x82, 0xf5, 0x56, 0xa3, 0xb0, 0xd2, 0xe4, 0x54, 0x18, 0x52, 0xb0, 0xbd, 0x0e, 0xd8, 0x0f, 0x05, 0xec, 0x07, 0x4b, 0xc4, 0x8a, 0x11, 0x83, 0x46, 0xad, 0xa0, 0x29, 0x19, 0x4a, 0x50, 0x9b, 0xcf, 0x9d, 0xcc, 0x12, 0xac, 0xb5, 0xec, 0x4e, 0x96, 0xe7, 0xb2, 0xed, 0xf0, 0x4c, 0xb2, 0x21, 0xb3, 0x49, 0x0f, 0x58, 0x4a, 0x31, 0xf3, 0xc8, 0xf9, 0x96, 0xb5, 0xcf, 0x00, 0x99, 0xed, 0x0f, 0x5f, 0xfc, 0x22, 0x38, 0xb7, 0xf9, 0xa0, 0x4d, 0x59, 0x0f, 0xfe, 0x20, 0x6c, 0xbe, 0xf2, 0x32, + 0xb7, 0x72, 0x9f, 0xf0, 0xa6, 0x94, 0x1f, 0xfd, 0x21, 0xbd, 0x18, 0xe7, 0x95, 0x47, 0x88, 0x36, 0x71, 0x51, 0xcc, 0x44, 0x25, 0xef, 0xad, 0x12, 0x6e, 0xe3, 0x9c, 0xef, 0x77, 0x1c, 0xd2, 0x84, 0xc2, 0x70, 0x2a, 0x73, 0x5f, 0x89, 0x27, 0x98, 0x1b, 0x82, 0x43, 0x2f, 0x46, 0x84, 0xa0, 0x44, 0x10, 0xd0, 0xe7, 0x0b, 0xc9, 0xa5, 0x17, 0x0e, 0x95, 0xa8, 0xc5, 0xd0, 0x85, 0x4b, 0x93, 0x90, 0x7c, 0x3d, 0x7f, 0x64, 0xe1, 0xc2, 0x23, 0xcf, 0xe3, 0x4c, 0xee, 0x85, 0x47, 0x9e, 0xc3, 0x44, 0xac, 0x6e, 0xd5, 0xc1, 0x25, 0xcb, 0x0e, 0xae, 0x4a, 0xa7, 0x57, 0x1d, 0xc4, 0x7c, 0x23, 0x07, 0xf9, 0xc6, 0xb1, 0x12, 0xdf, 0x70, 0x40, 0x1e, 0x18, 0xab, 0xc2, 0xc3, 0x0e, 0x95, 0x80, 0xae, 0x65, 0xe0, 0x53, 0x01, 0xb1, 0x3f, 0xc7, 0x7d, 0x38, 0xb4, 0x06, 0xf2, 0x8d, 0x68, 0xd8, 0xe3, 0x42, + 0xc8, 0xd0, 0x0a, 0x59, 0x89, 0x6f, 0x14, 0xea, 0xad, 0x25, 0x84, 0xd0, 0x79, 0x80, 0x0f, 0x25, 0x0a, 0x90, 0xbf, 0xdd, 0xa0, 0xb7, 0xa5, 0x72, 0x6d, 0x91, 0xe2, 0xae, 0x65, 0xe9, 0xe4, 0x92, 0x5d, 0xdd, 0x2d, 0x8b, 0x72, 0x7e, 0x83, 0xde, 0x28, 0x6b, 0x6f, 0x79, 0x68, 0xe7, 0x8e, 0xa7, 0xaf, 0x1a, 0xe8, 0xbe, 0xec, 0xc9, 0x9d, 0x97, 0x3e, 0xd9, 0x36, 0xa8, 0x79, 0x4f, 0x6b, 0x76, 0x1b, 0x15, 0xe9, 0x89, 0xab, 0xc6, 0x96, 0x1d, 0x9c, 0x48, 0x43, 0x8e, 0xa1, 0x55, 0x8c, 0xb7, 0xf4, 0x0f, 0x1f, 0x7e, 0x6e, 0xf7, 0xee, 0xe7, 0x0f, 0x43, 0x7e, 0xb2, 0x44, 0x8d, 0xd7, 0x6a, 0x84, 0x30, 0xd1, 0xd3, 0xd4, 0x6f, 0xe0, 0x5a, 0x05, 0x88, 0xe6, 0xa1, 0x47, 0xeb, 0xa5, 0xb5, 0xc2, 0x2b, 0x51, 0x09, 0xd6, 0xc4, 0xf8, 0x55, 0x46, 0x9c, 0x20, 0x08, 0xd6, 0x96, 0x2f, 0x93, 0x70, + 0xa1, 0xc2, 0xd2, 0x42, 0x71, 0x73, 0xce, 0x75, 0xa8, 0x04, 0x33, 0x84, 0x45, 0xa9, 0x11, 0x31, 0x78, 0xe7, 0xd0, 0xc0, 0xc0, 0x21, 0x11, 0x80, 0x80, 0xba, 0x0a, 0xc5, 0xf7, 0x7c, 0xeb, 0x85, 0xec, 0x06, 0xf8, 0xef, 0xf3, 0xe0, 0xf4, 0xce, 0x27, 0x0e, 0xf4, 0xf4, 0x1c, 0x78, 0x62, 0xe7, 0xae, 0xa7, 0xd0, 0xe7, 0x53, 0xbb, 0xfa, 0xcf, 0x1d, 0x8e, 0xdc, 0x76, 0xc3, 0xc0, 0xce, 0x05, 0x11, 0xa9, 0x9e, 0xf4, 0x52, 0xe2, 0x16, 0x7a, 0x2b, 0xf5, 0x6b, 0x22, 0x4e, 0xd8, 0x8b, 0x96, 0xa8, 0x8d, 0x24, 0xfa, 0x4a, 0xc0, 0x6e, 0xd3, 0x90, 0xb1, 0x79, 0x6b, 0x48, 0xa4, 0x92, 0x67, 0x0b, 0x95, 0xc2, 0xd7, 0xc8, 0x46, 0x05, 0x05, 0x0c, 0x99, 0x1e, 0x54, 0xd7, 0xb3, 0x95, 0xc9, 0x23, 0xb5, 0x80, 0xba, 0x40, 0x9b, 0x88, 0xd6, 0xae, 0xb9, 0x7e, 0xdd, 0xda, 0xa3, 0x6b, 0x6a, 0x6b, + 0x12, 0x5a, 0x46, 0xbe, 0xb2, 0x67, 0xa0, 0xdc, 0xc3, 0x9e, 0x95, 0x0a, 0x1a, 0x50, 0x06, 0x43, 0x62, 0x63, 0xb1, 0x7f, 0xf7, 0x48, 0x3c, 0x3e, 0xb2, 0xbb, 0xbf, 0xb8, 0x31, 0x61, 0x30, 0xc8, 0x75, 0xcc, 0xd2, 0xee, 0x47, 0xf7, 0x9c, 0xf7, 0xd4, 0xfe, 0xce, 0xce, 0xfd, 0x4f, 0x9d, 0xb7, 0xe7, 0xd1, 0xee, 0xa5, 0x8c, 0x0e, 0xcb, 0xc0, 0x4b, 0x08, 0x25, 0xfd, 0x07, 0x5a, 0x4e, 0xa0, 0x4c, 0xcb, 0x1c, 0x94, 0xa5, 0xec, 0x61, 0xc8, 0x03, 0xea, 0x7c, 0x9c, 0x12, 0x1e, 0x74, 0x33, 0xae, 0xb1, 0x17, 0x02, 0x54, 0xbf, 0x09, 0x90, 0x7d, 0xa5, 0x09, 0x96, 0xd2, 0xa7, 0x2a, 0x59, 0x26, 0x18, 0x37, 0x41, 0x44, 0xbf, 0x25, 0xd7, 0x96, 0x2f, 0x43, 0x19, 0xe3, 0xa9, 0x54, 0x28, 0x1a, 0xc6, 0x27, 0x41, 0x9a, 0xcb, 0x76, 0x20, 0x91, 0x7c, 0x39, 0xa8, 0xc8, 0xa0, 0x05, 0x6c, 0x6a, + 0xb1, 0x56, 0x50, 0x32, 0x51, 0xc8, 0x3e, 0x18, 0xcb, 0xa3, 0xe9, 0x3d, 0xb9, 0x7a, 0x79, 0x34, 0x03, 0x27, 0xfb, 0xf0, 0x72, 0x2a, 0xf7, 0x83, 0x9e, 0x57, 0xf7, 0xa3, 0xe9, 0x0f, 0x05, 0x16, 0x7a, 0xad, 0x3f, 0xe9, 0xfe, 0xf6, 0xf9, 0xe8, 0x8b, 0xd7, 0x3d, 0xc4, 0x3b, 0xc8, 0xc7, 0xfa, 0xb6, 0xa1, 0xe9, 0x2e, 0x8c, 0x15, 0xd7, 0xa1, 0xc9, 0x8f, 0xcd, 0xa8, 0x6f, 0xed, 0x1e, 0x40, 0x4b, 0x31, 0x72, 0x6d, 0x63, 0x93, 0x29, 0x18, 0xdc, 0x70, 0xb2, 0xa5, 0x88, 0x56, 0x68, 0xe0, 0xd2, 0x7c, 0x83, 0xa5, 0xd3, 0x87, 0xc6, 0xbd, 0xe0, 0xf4, 0x07, 0xb2, 0x83, 0x18, 0xa7, 0xd7, 0x02, 0x97, 0xe3, 0xc6, 0xa2, 0xca, 0x0b, 0x54, 0x20, 0x85, 0x73, 0xe6, 0xc5, 0x73, 0x13, 0x27, 0xa0, 0xa2, 0x02, 0x54, 0x0a, 0xa8, 0x2d, 0x6a, 0xa0, 0x64, 0xa0, 0xa0, 0xd6, 0x2b, 0xab, 0x70, 0x23, + 0x48, 0x35, 0x39, 0x4b, 0xb6, 0xff, 0xdc, 0x77, 0x63, 0xdc, 0x6a, 0x5e, 0xa7, 0x03, 0x44, 0x6f, 0x77, 0x47, 0x7b, 0x73, 0x63, 0xa6, 0x2e, 0x19, 0x0f, 0xf0, 0x6e, 0xa7, 0xce, 0xa2, 0xb3, 0x98, 0x8c, 0x50, 0xfc, 0xd4, 0x02, 0x6d, 0x95, 0xf8, 0x59, 0x8a, 0x3c, 0xaa, 0x42, 0x33, 0xb6, 0x58, 0x67, 0x9f, 0xab, 0x79, 0x84, 0xf9, 0x08, 0xfd, 0xb5, 0xee, 0xa9, 0x4e, 0xfe, 0xca, 0x6b, 0xaf, 0xbe, 0xf2, 0xca, 0x4f, 0x7e, 0xec, 0x69, 0x5c, 0x96, 0xbf, 0xe0, 0xa0, 0x6e, 0x66, 0x0f, 0x16, 0xca, 0xcf, 0x7b, 0xae, 0xb1, 0xf0, 0xd2, 0xd5, 0x53, 0xf7, 0x5f, 0xd0, 0xd9, 0xb9, 0xf7, 0x81, 0x4d, 0x57, 0x3c, 0x97, 0xcf, 0x3e, 0x7d, 0xe5, 0xd2, 0x63, 0x5b, 0x5a, 0x8c, 0x4e, 0x72, 0x1b, 0x93, 0x84, 0x22, 0x7c, 0xcb, 0x58, 0xb1, 0x96, 0xb3, 0x68, 0x0f, 0xd7, 0xaf, 0x3b, 0xbc, 0xe2, 0xd1, + 0x17, 0x5f, 0x78, 0xec, 0xd1, 0x57, 0x80, 0x55, 0x94, 0xdb, 0xc1, 0xbf, 0xa1, 0x18, 0xc5, 0x1d, 0x7b, 0xce, 0xb9, 0x40, 0x3c, 0x0a, 0xd7, 0x0e, 0xec, 0xd9, 0xba, 0x71, 0x6f, 0xc7, 0xc5, 0x25, 0x91, 0x5d, 0x8c, 0x60, 0x25, 0x89, 0x65, 0x90, 0x1e, 0x9d, 0x03, 0xe9, 0x35, 0x07, 0x4f, 0xe8, 0x92, 0xa2, 0xb2, 0x3b, 0xed, 0x52, 0xd1, 0x62, 0x06, 0xaf, 0x12, 0x63, 0x11, 0x88, 0x88, 0x27, 0x74, 0xc9, 0x8a, 0x83, 0xc5, 0xd6, 0x33, 0x7e, 0x15, 0x85, 0x54, 0x8c, 0xa8, 0x31, 0x62, 0x5a, 0xd8, 0xd4, 0x10, 0x09, 0xa1, 0x18, 0x2d, 0x59, 0x95, 0xb8, 0x15, 0x11, 0x13, 0x62, 0x80, 0x98, 0xae, 0x2a, 0x9f, 0x93, 0x37, 0x83, 0x02, 0xf8, 0xf2, 0x05, 0x38, 0x5d, 0xa5, 0xda, 0xc3, 0x58, 0x21, 0x8a, 0x84, 0xf3, 0x19, 0xf0, 0x76, 0x72, 0xa4, 0xc5, 0x1f, 0x6a, 0x5f, 0x56, 0x1b, 0x1b, 0x2a, + 0xf0, 0xfe, 0xe6, 0x11, 0x70, 0xbe, 0x8d, 0xe3, 0x0b, 0x43, 0xb1, 0xda, 0x65, 0xed, 0xa1, 0xdc, 0xc6, 0x9b, 0xd7, 0xac, 0x39, 0x3e, 0x9d, 0x8b, 0x74, 0x8c, 0xa6, 0xe2, 0x0b, 0x1a, 0xfd, 0x26, 0xe7, 0xb5, 0x66, 0x56, 0xa5, 0xd2, 0xfa, 0x1a, 0x06, 0x62, 0xa1, 0xf6, 0x94, 0x53, 0xab, 0x54, 0x19, 0x7d, 0x87, 0x4d, 0xb1, 0xae, 0x54, 0x4d, 0x7b, 0x26, 0x66, 0xe2, 0x62, 0x99, 0x62, 0x4d, 0xaa, 0x2b, 0x66, 0xa2, 0xce, 0xe5, 0x87, 0xfd, 0xf5, 0x23, 0xed, 0xf5, 0x66, 0x6b, 0xc3, 0xd0, 0xfa, 0x62, 0xcf, 0xae, 0x45, 0x89, 0xc4, 0xa2, 0x5d, 0x3d, 0x6d, 0xab, 0xfb, 0xf2, 0x16, 0x73, 0xb6, 0x73, 0x59, 0x2e, 0xd4, 0x19, 0xf5, 0x99, 0x8d, 0x09, 0x57, 0xb8, 0x31, 0x15, 0x36, 0xb2, 0x91, 0x74, 0x73, 0xd8, 0x9a, 0x64, 0xad, 0x3e, 0x44, 0x1f, 0x26, 0xc1, 0x27, 0x74, 0x0e, 0xee, 0x45, + 0x16, 0xe5, 0x71, 0xd0, 0x14, 0x72, 0xc2, 0xed, 0x29, 0xa7, 0x32, 0x97, 0xb8, 0x5a, 0xd0, 0xe8, 0xc7, 0x98, 0x30, 0xe2, 0x9e, 0x28, 0x97, 0x5a, 0xa6, 0xce, 0x39, 0x7a, 0xe3, 0x4d, 0x47, 0xbf, 0xd7, 0x73, 0xe0, 0xc9, 0x5d, 0x08, 0x44, 0x05, 0x7c, 0xf2, 0xe8, 0xcb, 0x2f, 0x3f, 0xba, 0xf7, 0xa5, 0x6b, 0x07, 0x07, 0xaf, 0x7d, 0x69, 0x2f, 0x7a, 0xf7, 0xe9, 0x0f, 0xe8, 0x3c, 0xf8, 0x04, 0xbe, 0x41, 0xe2, 0x66, 0x46, 0xd4, 0x00, 0x09, 0x76, 0x97, 0x28, 0x21, 0x66, 0x65, 0x73, 0x7f, 0xc4, 0x59, 0x70, 0x1a, 0xb1, 0x61, 0xbf, 0x29, 0x88, 0x1b, 0x2e, 0x4d, 0x6e, 0x46, 0x44, 0x69, 0xa4, 0xf3, 0x25, 0xc0, 0x96, 0xef, 0x1d, 0xbb, 0xe9, 0x86, 0xa3, 0xe0, 0x93, 0xbd, 0x2f, 0x5d, 0x33, 0x30, 0x70, 0xcd, 0x4b, 0x7b, 0x1f, 0x7d, 0xe9, 0xa5, 0x47, 0x89, 0x72, 0xee, 0xf4, 0x6a, 0x6c, + 0x97, 0xb0, 0x12, 0xf5, 0xc5, 0x14, 0xb2, 0x48, 0x40, 0xdd, 0x04, 0x67, 0x4d, 0x93, 0x50, 0xed, 0x97, 0x72, 0x7b, 0xa0, 0xee, 0xaf, 0x52, 0x02, 0x6c, 0x98, 0x50, 0x5a, 0x55, 0x56, 0xa8, 0x8c, 0x28, 0x80, 0xa2, 0x0c, 0x14, 0x8b, 0xd2, 0xa6, 0xb9, 0xb2, 0x43, 0x18, 0x25, 0x6a, 0xfc, 0x64, 0xcb, 0xc6, 0x8d, 0x5b, 0x8e, 0x1e, 0x9d, 0xbe, 0x79, 0x7d, 0xaa, 0x6e, 0xf2, 0xf8, 0xe4, 0xd1, 0x67, 0xc0, 0xdf, 0xa7, 0x46, 0x17, 0xaf, 0x13, 0x6e, 0x01, 0x42, 0x7a, 0x6c, 0x6f, 0xdf, 0xe0, 0x25, 0x2b, 0xea, 0x84, 0x7f, 0x81, 0xad, 0x92, 0x90, 0x2b, 0x10, 0x54, 0xb3, 0x94, 0x17, 0xdd, 0x54, 0x2c, 0xd8, 0xb1, 0xc5, 0x14, 0x83, 0xda, 0x90, 0x80, 0x9a, 0xc4, 0xa1, 0x91, 0x72, 0x50, 0x95, 0xbc, 0xad, 0x86, 0x6c, 0x45, 0xcd, 0xab, 0x7d, 0x16, 0x93, 0x01, 0xca, 0xf7, 0xf0, 0x31, 0x25, + 0xaf, 0xc0, 0xd6, 0xd2, 0x12, 0x33, 0x28, 0x05, 0xcc, 0x51, 0xb2, 0x38, 0xe0, 0x44, 0xe7, 0x02, 0x5c, 0x06, 0xf2, 0xc0, 0xc5, 0x62, 0x72, 0x34, 0xc8, 0x94, 0x33, 0xa2, 0x4f, 0x8b, 0x59, 0xd2, 0xc7, 0x37, 0x93, 0x66, 0x30, 0x22, 0x66, 0x47, 0x83, 0x5f, 0x7e, 0xa2, 0x00, 0xd7, 0x19, 0x8c, 0xc2, 0x33, 0x61, 0x70, 0x41, 0xd8, 0xcc, 0xdf, 0xbe, 0x09, 0xa5, 0x49, 0x13, 0x67, 0xf6, 0xd3, 0x8c, 0xed, 0x37, 0x50, 0xd2, 0x22, 0x00, 0x4d, 0x4c, 0xca, 0xc5, 0x28, 0x3e, 0x84, 0x41, 0x53, 0x4a, 0xe0, 0x96, 0xfa, 0xc9, 0xfb, 0x3c, 0x0e, 0x9b, 0xae, 0xd2, 0xcf, 0x10, 0x2b, 0xf1, 0x0e, 0xd8, 0x4f, 0x5e, 0xa2, 0xad, 0xe1, 0x08, 0x85, 0x52, 0x7b, 0x3c, 0xc8, 0x8f, 0x41, 0x1e, 0xb8, 0x30, 0x3f, 0x92, 0xb5, 0x6f, 0xda, 0x0c, 0xf2, 0xc2, 0xf7, 0x36, 0x1f, 0x5f, 0x9b, 0x40, 0xe9, 0xe5, + 0xa7, 0x1f, 0xa8, 0x33, 0xff, 0x4c, 0xed, 0x76, 0x2f, 0xbe, 0x19, 0x8c, 0x88, 0x89, 0xe6, 0xd4, 0xd4, 0x27, 0x0a, 0x94, 0x5f, 0xbe, 0xf9, 0x36, 0xde, 0x8c, 0x7a, 0x2a, 0x3c, 0x63, 0xd4, 0x83, 0x23, 0x68, 0x9f, 0x42, 0x9d, 0x89, 0xca, 0xe3, 0xda, 0xe3, 0x28, 0xb3, 0x0d, 0xad, 0xe6, 0x78, 0x09, 0x69, 0x69, 0x6e, 0xe5, 0x71, 0x3e, 0xc7, 0x53, 0x79, 0xe1, 0x07, 0xcf, 0x08, 0xdf, 0xa7, 0x78, 0x6a, 0xe2, 0x93, 0x7b, 0xa9, 0x89, 0x3b, 0x44, 0x5e, 0xb8, 0xef, 0xf4, 0x9f, 0xa9, 0x57, 0xe1, 0x3b, 0xec, 0x84, 0x54, 0x5d, 0xd2, 0x48, 0xe1, 0x84, 0x1c, 0x1a, 0x54, 0x81, 0x95, 0x8b, 0x70, 0xd2, 0x73, 0xaf, 0xe0, 0x42, 0xe4, 0xf3, 0xdc, 0x3e, 0x3e, 0x5e, 0x84, 0xda, 0xbc, 0x89, 0xd3, 0x69, 0xe4, 0x32, 0xc2, 0x0e, 0xec, 0x4c, 0x89, 0x46, 0x20, 0x0b, 0x9c, 0xb4, 0x69, 0xd1, 0x9f, + 0x1e, 0x92, 0xf4, 0x15, 0x96, 0xb7, 0x78, 0x9f, 0x69, 0xda, 0x7e, 0x72, 0xc3, 0x86, 0x93, 0xe7, 0x34, 0x7d, 0xdb, 0xd7, 0x32, 0x76, 0x7b, 0xb0, 0x73, 0x4d, 0x13, 0xb9, 0x6e, 0xe6, 0xce, 0x89, 0xdb, 0x77, 0xb6, 0xb7, 0xef, 0xbc, 0x7d, 0x02, 0xfd, 0xdd, 0xb8, 0xa6, 0x0b, 0xe5, 0x2b, 0x6d, 0x24, 0xfa, 0xe9, 0xed, 0xd4, 0x5d, 0xb0, 0xaf, 0xd1, 0x62, 0x88, 0xc1, 0x19, 0x30, 0x52, 0x82, 0xc4, 0x7a, 0x48, 0xce, 0x00, 0xb9, 0x5c, 0x12, 0x00, 0x49, 0x30, 0x62, 0xe6, 0x93, 0x78, 0xf0, 0x1c, 0x8f, 0x44, 0x4d, 0x89, 0x14, 0xc1, 0x49, 0xc0, 0x5b, 0x38, 0xc2, 0x6f, 0xa4, 0x3e, 0x8a, 0x6c, 0x0a, 0x34, 0xc5, 0xac, 0xb3, 0xc1, 0x2c, 0xa9, 0xbb, 0x2e, 0x14, 0x51, 0xa4, 0x56, 0x95, 0xf1, 0xb5, 0xae, 0x68, 0x20, 0x30, 0x5f, 0x1e, 0x21, 0xfa, 0x99, 0x5f, 0xe0, 0xb6, 0x93, 0x50, 0xfe, + 0x1d, 0x2c, 0xf6, 0xc1, 0xf6, 0x69, 0xb1, 0x7d, 0x06, 0xb6, 0xaf, 0x80, 0x5b, 0x83, 0x91, 0xd1, 0xcc, 0x24, 0x41, 0x42, 0xd5, 0x75, 0xb9, 0x12, 0xc8, 0x08, 0x94, 0x02, 0x3f, 0x59, 0xe9, 0x51, 0xaa, 0xd6, 0xe9, 0x68, 0x2c, 0xd4, 0x76, 0xa4, 0x3a, 0x42, 0x01, 0x47, 0xd2, 0x99, 0x44, 0x3d, 0x54, 0xe1, 0xe5, 0x99, 0xb7, 0x87, 0xa1, 0x39, 0x10, 0x4d, 0xdc, 0x5c, 0xc8, 0xa6, 0xff, 0x3c, 0x63, 0x08, 0x17, 0xf7, 0xc3, 0x21, 0x08, 0x7f, 0xb4, 0xc5, 0x7c, 0x46, 0xa3, 0x2f, 0x66, 0xb3, 0x27, 0x78, 0x8e, 0xe3, 0x13, 0xf6, 0x39, 0xdf, 0xe7, 0x1d, 0xe3, 0x72, 0x78, 0xc1, 0x66, 0x4b, 0xf8, 0x38, 0xce, 0x87, 0x3e, 0x79, 0x4e, 0xd8, 0x6f, 0x42, 0xbf, 0xc4, 0xd0, 0x23, 0x31, 0xf4, 0x8b, 0x09, 0xce, 0xff, 0xf8, 0xe9, 0x05, 0xf4, 0x7a, 0xfa, 0x19, 0x22, 0x86, 0xa8, 0x87, 0x0c, 0xa5, + 0x64, 0x0c, 0x4a, 0x11, 0xf0, 0xeb, 0xa5, 0xf8, 0x66, 0x48, 0x4a, 0xe0, 0xa6, 0x5a, 0x4e, 0x88, 0x6a, 0x11, 0x20, 0x46, 0x58, 0x33, 0x1f, 0xe6, 0x53, 0x52, 0xa5, 0x9f, 0x2a, 0x0d, 0x54, 0x1a, 0x68, 0x6d, 0xf5, 0x90, 0x51, 0x16, 0x8a, 0x9c, 0xfa, 0x5e, 0xe7, 0x68, 0x05, 0x34, 0x0f, 0x8d, 0x6b, 0xf7, 0xf9, 0xee, 0x66, 0x09, 0xde, 0x6d, 0x57, 0x67, 0xe3, 0x8c, 0x16, 0x8e, 0x9b, 0xa6, 0x1b, 0x0e, 0x4d, 0x6c, 0x3c, 0xe0, 0xb6, 0xf4, 0x8e, 0x21, 0x00, 0xb9, 0xe5, 0xb5, 0x08, 0x2e, 0x70, 0xa8, 0xd1, 0xff, 0xca, 0x1a, 0x84, 0xbe, 0xf7, 0x2a, 0x1c, 0x20, 0xde, 0xdb, 0x46, 0xc9, 0x0e, 0x6b, 0x44, 0x74, 0x5c, 0x8d, 0xf5, 0x3b, 0x0a, 0xa1, 0x9c, 0x95, 0xd2, 0xcd, 0xe0, 0xbf, 0x46, 0x82, 0xe5, 0xb1, 0x7a, 0xa2, 0x04, 0x01, 0x31, 0xe5, 0x4c, 0xc4, 0xae, 0xe5, 0x20, 0x4f, 0xa7, + 0xde, 0x17, 0x36, 0x3e, 0x28, 0x4c, 0x9f, 0xb0, 0x79, 0x15, 0xef, 0x69, 0x2c, 0x2a, 0x85, 0x49, 0xf3, 0x07, 0xb9, 0xcf, 0x8a, 0x34, 0x4a, 0xea, 0x23, 0x1b, 0x37, 0x73, 0xab, 0x67, 0x30, 0x12, 0x19, 0xf4, 0x90, 0x9b, 0x59, 0x94, 0x5f, 0xe8, 0x85, 0xd4, 0xeb, 0xdf, 0x31, 0x9e, 0x22, 0xd4, 0xcd, 0xfc, 0x26, 0x19, 0x46, 0x0d, 0x46, 0x65, 0x84, 0x67, 0x83, 0x89, 0xc1, 0xcb, 0xcd, 0xbc, 0x0d, 0x23, 0xcb, 0xc2, 0x16, 0x4b, 0xb6, 0xde, 0x4a, 0xd3, 0x79, 0xd1, 0xc7, 0x59, 0xfd, 0x2d, 0xc3, 0xfc, 0xbb, 0xf0, 0xea, 0x83, 0x6f, 0x31, 0x0a, 0x9a, 0x56, 0x30, 0xbf, 0x78, 0x50, 0x78, 0xf5, 0x0e, 0x3b, 0xcf, 0x3c, 0xc2, 0x68, 0x18, 0x95, 0xe1, 0xdf, 0x18, 0xbf, 0xad, 0x00, 0xbb, 0xf7, 0xaf, 0x0a, 0xad, 0x4c, 0xa9, 0xfe, 0x2a, 0xfc, 0x86, 0x7a, 0x47, 0x3e, 0xae, 0xf3, 0x1a, 0x8d, + 0x5e, 0xdd, 0xcc, 0x42, 0xea, 0x23, 0xab, 0x69, 0xe6, 0x1e, 0x6b, 0xce, 0xdd, 0x94, 0x22, 0x57, 0xb1, 0x0e, 0xd8, 0xe9, 0x7b, 0x2c, 0x19, 0x57, 0xa1, 0x16, 0x7d, 0x81, 0x7d, 0x2e, 0xc0, 0xf9, 0xe9, 0x87, 0x7d, 0xd6, 0x4a, 0x56, 0x9e, 0x12, 0xf9, 0x80, 0x73, 0x37, 0x2e, 0xd1, 0x10, 0x13, 0xcb, 0x89, 0xaa, 0x13, 0x76, 0xe5, 0x48, 0xc9, 0x42, 0x54, 0x3f, 0x37, 0xf3, 0x11, 0xa9, 0xff, 0xe4, 0x63, 0x23, 0xa9, 0x10, 0xe4, 0xe4, 0x7b, 0xd4, 0x63, 0x5e, 0xd7, 0x4c, 0xe0, 0xb6, 0x21, 0xa7, 0x83, 0x7c, 0x0b, 0xd9, 0x31, 0xb2, 0xa7, 0xff, 0x42, 0x05, 0xe1, 0x7b, 0xd3, 0xc4, 0xe6, 0x22, 0x4a, 0xe1, 0xa2, 0x63, 0x0c, 0x7c, 0xb9, 0x11, 0x7b, 0x32, 0xcb, 0xc0, 0xd1, 0x14, 0xdc, 0x2e, 0x00, 0xa9, 0xca, 0xf4, 0xb8, 0x84, 0xd7, 0x54, 0xa5, 0x64, 0x7f, 0xea, 0x0d, 0x98, 0x19, 0x22, + 0xa8, 0xa3, 0x34, 0x91, 0x36, 0x47, 0x93, 0xb5, 0x18, 0xea, 0x08, 0x4b, 0xe7, 0x73, 0xcc, 0x1b, 0x96, 0x2a, 0x25, 0x08, 0x87, 0xe8, 0xb5, 0xc3, 0xde, 0x53, 0x72, 0x95, 0x51, 0x58, 0x7a, 0xc1, 0x50, 0xa0, 0x7b, 0xe7, 0xf1, 0x85, 0x1b, 0x9f, 0xe8, 0xec, 0x57, 0xb1, 0x8c, 0x8a, 0x65, 0xd5, 0xfe, 0x74, 0x7b, 0x4d, 0x7e, 0x51, 0xde, 0x69, 0xcb, 0x8e, 0xb6, 0x1e, 0xd2, 0xe8, 0x7f, 0x23, 0xac, 0xa0, 0x3e, 0xea, 0xd3, 0x07, 0x8c, 0x7f, 0x92, 0xa5, 0x87, 0x36, 0x34, 0x8e, 0x5d, 0xb5, 0x3a, 0xbd, 0x78, 0xe9, 0x32, 0x35, 0x25, 0xd7, 0xa9, 0xdd, 0x76, 0xd6, 0x97, 0xeb, 0x0d, 0xfb, 0x1b, 0x63, 0xd6, 0x25, 0x3a, 0xaf, 0xee, 0xed, 0xdb, 0xd0, 0x5e, 0xeb, 0x3a, 0xfd, 0x1e, 0xe5, 0x85, 0x32, 0x43, 0x1b, 0xd1, 0x59, 0x6c, 0xf7, 0xc0, 0x41, 0x27, 0xe2, 0x3a, 0x2d, 0xcd, 0x20, 0xb8, + 0xd0, 0xf9, 0xe1, 0x0e, 0xce, 0x2d, 0xfb, 0x4d, 0xe1, 0x80, 0x9b, 0x9b, 0x0a, 0x39, 0xbf, 0xcf, 0xe5, 0xb0, 0x9a, 0xfd, 0x32, 0x11, 0x51, 0xba, 0x2c, 0x37, 0xe5, 0xca, 0x86, 0x63, 0x6c, 0x14, 0x2e, 0x8b, 0x9a, 0xf2, 0xaa, 0xfc, 0x63, 0x1c, 0xa6, 0xe3, 0x6d, 0xf9, 0xc6, 0xf9, 0xe3, 0x87, 0xd7, 0xd6, 0xe7, 0x47, 0xb7, 0x64, 0x53, 0x63, 0x1d, 0xe1, 0x8d, 0xe3, 0xf5, 0xfd, 0xf9, 0x08, 0x6b, 0xd3, 0x25, 0x9a, 0xee, 0xdf, 0x36, 0x7a, 0xd5, 0x44, 0xba, 0x6e, 0xe1, 0xfa, 0x74, 0x66, 0xcd, 0x40, 0xc2, 0x56, 0x37, 0x9c, 0x6b, 0x1a, 0x29, 0xd4, 0x98, 0x58, 0xab, 0xf2, 0x9c, 0x7c, 0x6f, 0x6c, 0x68, 0x53, 0x5b, 0xd7, 0x58, 0x83, 0x5f, 0xbf, 0x59, 0x69, 0x74, 0xc6, 0xfd, 0x8d, 0x7d, 0x16, 0x87, 0xc5, 0xd6, 0xd7, 0x50, 0x8c, 0xf6, 0x4f, 0x35, 0xb7, 0x8d, 0xd4, 0x7b, 0xb5, 0x9b, + 0x55, 0x66, 0x3e, 0x13, 0x75, 0x65, 0x23, 0x36, 0x27, 0xef, 0xd2, 0xeb, 0xa4, 0x9a, 0x84, 0xa7, 0xff, 0x44, 0x07, 0xe1, 0x00, 0xea, 0x88, 0xda, 0x62, 0x3c, 0x61, 0x83, 0xeb, 0xab, 0x94, 0xc0, 0x51, 0xe1, 0xc8, 0x30, 0xb2, 0xb2, 0x08, 0xeb, 0xb3, 0x03, 0x6f, 0xfb, 0x3a, 0x22, 0x9d, 0x0c, 0x05, 0x43, 0x68, 0xa5, 0x42, 0xb3, 0xe5, 0xe4, 0x56, 0x5c, 0xff, 0xab, 0x50, 0xc9, 0x94, 0x92, 0x64, 0xc7, 0x0c, 0x1d, 0x44, 0x70, 0xd0, 0xeb, 0x7e, 0xb6, 0x69, 0xfa, 0x67, 0xeb, 0xc2, 0x51, 0x96, 0x0c, 0xfa, 0x17, 0x8d, 0xae, 0x88, 0x67, 0x57, 0xb4, 0x07, 0xea, 0x37, 0x1c, 0x5d, 0xb5, 0xea, 0xd8, 0xfa, 0xfa, 0x40, 0xfb, 0x8a, 0x6c, 0x7c, 0xc5, 0xe8, 0xa2, 0xc0, 0x55, 0x03, 0x7a, 0x27, 0xa7, 0x19, 0xee, 0xec, 0x1c, 0xd6, 0x70, 0x4e, 0xfd, 0x80, 0xc2, 0x16, 0xf7, 0xd7, 0x9d, 0xbb, + 0xf7, 0xd2, 0x8e, 0xae, 0xf3, 0x46, 0xd3, 0xe9, 0xd1, 0xf3, 0xba, 0x3a, 0x0e, 0x5c, 0x78, 0x6e, 0x5d, 0x20, 0x66, 0x53, 0x54, 0xd9, 0xf0, 0x18, 0xa4, 0xfb, 0x11, 0x12, 0x74, 0x5c, 0x85, 0x65, 0x1a, 0x70, 0xfe, 0xa9, 0xe8, 0x28, 0x54, 0x0a, 0xad, 0xcf, 0x92, 0x97, 0x51, 0x1b, 0x3f, 0xf9, 0x17, 0x9c, 0xe0, 0x0b, 0xa0, 0xf0, 0x45, 0x50, 0xef, 0xfd, 0x53, 0x39, 0xc4, 0xef, 0xdd, 0x6d, 0xf7, 0x48, 0x44, 0x44, 0xfd, 0x47, 0x19, 0x6f, 0xbb, 0x1e, 0xdc, 0xfe, 0xa0, 0x8d, 0x23, 0x37, 0x7b, 0x06, 0xa2, 0xd1, 0x01, 0xcf, 0xcc, 0xad, 0xa2, 0xc7, 0x86, 0xa8, 0xc2, 0x22, 0xc4, 0x3c, 0x1d, 0xed, 0x95, 0x71, 0xa2, 0xca, 0xb2, 0x53, 0xe2, 0xe9, 0x25, 0xa7, 0x26, 0xf5, 0xf6, 0xb3, 0x42, 0xe0, 0x59, 0x6a, 0x79, 0xa9, 0x93, 0x80, 0x58, 0x44, 0x10, 0xf4, 0x66, 0xf8, 0x67, 0x88, 0x88, + 0x17, 0xa3, 0x32, 0x40, 0x23, 0xe1, 0x15, 0x4a, 0x2d, 0x65, 0xd8, 0x56, 0xc9, 0xed, 0x04, 0xaf, 0x07, 0x59, 0xd6, 0x1a, 0x62, 0x59, 0xb3, 0x08, 0x7b, 0x8a, 0x77, 0x54, 0x20, 0x07, 0xd7, 0x01, 0x83, 0x28, 0xb0, 0x38, 0x49, 0x2d, 0x83, 0x72, 0xe9, 0x58, 0xa8, 0xc9, 0xd0, 0x9b, 0x0b, 0xe7, 0xdc, 0xb5, 0xf5, 0xd9, 0xad, 0x77, 0x9e, 0x53, 0x38, 0xe0, 0xf6, 0xdb, 0x1b, 0x26, 0xba, 0x9e, 0xed, 0x5a, 0xdd, 0x68, 0xe7, 0x3d, 0xd4, 0x47, 0xc2, 0xf1, 0x95, 0x37, 0x6d, 0x69, 0x63, 0xfe, 0xf4, 0x27, 0x59, 0xdb, 0x96, 0x9b, 0xc6, 0xc1, 0xf6, 0x90, 0x4b, 0xf8, 0x69, 0xf3, 0x44, 0x31, 0x44, 0x93, 0xe7, 0xcc, 0xdc, 0x4c, 0x87, 0x8a, 0x13, 0xcd, 0x20, 0xe1, 0x0a, 0xe1, 0xb1, 0x39, 0x20, 0x5d, 0x78, 0x11, 0xf6, 0xcd, 0x4a, 0xf4, 0x88, 0x56, 0x34, 0x0b, 0x32, 0xee, 0x8e, 0x97, 0x10, + 0xbd, 0xc4, 0xec, 0x74, 0x11, 0x66, 0xb6, 0xea, 0x82, 0x04, 0x73, 0x56, 0x96, 0x2e, 0xbe, 0x61, 0x09, 0x84, 0x59, 0x83, 0x98, 0x43, 0xcd, 0xf2, 0x32, 0x12, 0xc3, 0xb5, 0xe2, 0x6d, 0x44, 0x19, 0x0b, 0x3c, 0xf5, 0xe2, 0x73, 0xc2, 0xef, 0xd4, 0xc1, 0xfa, 0xe2, 0xf0, 0x58, 0x3a, 0x53, 0x9b, 0xb7, 0x65, 0xd2, 0xc9, 0xa0, 0x43, 0x46, 0x5e, 0x45, 0x6d, 0x7c, 0x5e, 0x78, 0xe7, 0xe5, 0x8b, 0xbb, 0x6d, 0x46, 0x76, 0xaf, 0xa1, 0x69, 0xd7, 0x4b, 0xc0, 0xfb, 0xef, 0x58, 0x26, 0xd6, 0x53, 0xc7, 0xc9, 0xff, 0x41, 0x2e, 0x48, 0xa8, 0x77, 0x16, 0x8a, 0x59, 0xba, 0x24, 0xeb, 0xd1, 0xbb, 0xa1, 0x60, 0x43, 0x82, 0x3d, 0x0c, 0xda, 0xd5, 0xc4, 0x28, 0xf6, 0xde, 0xad, 0x44, 0x1a, 0xf5, 0xc2, 0x79, 0x7c, 0x76, 0x11, 0xbc, 0xda, 0x90, 0xd4, 0xcd, 0xf2, 0xd9, 0x81, 0x5f, 0x8f, 0xc8, 0xf5, + 0x0a, 0xb9, 0x5e, 0x3e, 0x02, 0x7e, 0x76, 0x9e, 0xc9, 0x26, 0xab, 0x13, 0xfe, 0xb3, 0x8e, 0x76, 0x18, 0x2f, 0x00, 0x9e, 0xdb, 0xe9, 0x07, 0xdf, 0x30, 0xa6, 0xdd, 0xee, 0xb4, 0xf1, 0x0d, 0xe1, 0x1d, 0x93, 0xee, 0xd0, 0x21, 0xad, 0x99, 0x7c, 0x53, 0x94, 0xd1, 0x09, 0xea, 0x38, 0x75, 0x0b, 0xaa, 0x4d, 0x45, 0xd8, 0x88, 0x2b, 0x9f, 0x14, 0x5d, 0x76, 0xe2, 0x64, 0x85, 0x09, 0x39, 0xd2, 0x69, 0xe5, 0x8a, 0xf5, 0x67, 0xb8, 0xf0, 0x90, 0xcb, 0x4e, 0xdc, 0x28, 0x34, 0xd6, 0x7d, 0xcf, 0x7a, 0x23, 0x94, 0xf7, 0x69, 0x6a, 0xb4, 0xe4, 0xe6, 0xa3, 0xe8, 0x91, 0xf1, 0xa2, 0xfb, 0xb3, 0x1d, 0x7d, 0x70, 0x60, 0xd6, 0x8c, 0x39, 0x60, 0x9d, 0xe5, 0xe8, 0x03, 0x6f, 0xd6, 0x28, 0x39, 0xd5, 0x2e, 0x10, 0xb6, 0xfc, 0xf5, 0x1d, 0x2b, 0xb0, 0x09, 0x5b, 0xcc, 0xa7, 0x89, 0xa2, 0xc2, 0x65, + 0xde, 0x05, 0x76, 0x90, 0x11, 0xfa, 0x41, 0x30, 0xe6, 0xae, 0xb7, 0xbc, 0x0f, 0x16, 0x3c, 0x7d, 0xfe, 0x33, 0x17, 0x3f, 0x74, 0x48, 0xcf, 0x91, 0x6f, 0x5e, 0x84, 0xe5, 0xa8, 0x29, 0x38, 0xdf, 0xd7, 0xc1, 0xf1, 0xa9, 0xa0, 0x36, 0x1a, 0x42, 0xb4, 0xd2, 0x85, 0xa6, 0x79, 0x10, 0xca, 0x4f, 0x72, 0x80, 0xc1, 0x3f, 0xaa, 0xf2, 0x99, 0x25, 0x2c, 0x56, 0x66, 0xd8, 0x6c, 0x52, 0x43, 0xf6, 0x0d, 0xd9, 0x6a, 0xc8, 0x1c, 0x52, 0x73, 0x6a, 0xa3, 0x4e, 0x23, 0x22, 0x25, 0x29, 0xab, 0xd1, 0x9c, 0xcc, 0xac, 0x79, 0x7e, 0x9f, 0x10, 0x0b, 0xf6, 0x05, 0xd3, 0x6e, 0x75, 0x43, 0xcf, 0xe5, 0x53, 0xc5, 0x9e, 0x96, 0xe2, 0xc0, 0xe6, 0x4e, 0x6f, 0x73, 0xd7, 0x8d, 0xc1, 0x9e, 0xa9, 0x0e, 0xe1, 0xd7, 0xb4, 0xca, 0xe8, 0x0a, 0x99, 0x12, 0x75, 0xaf, 0x80, 0xf7, 0x5e, 0x8a, 0xe5, 0x0b, 0xf1, + 0x0b, 0x5c, 0x99, 0xfe, 0x64, 0x66, 0xa9, 0xcd, 0x7f, 0x69, 0x6a, 0x51, 0x33, 0x8f, 0x17, 0x85, 0x22, 0x7c, 0xd4, 0xcd, 0xe4, 0x03, 0xb2, 0x5d, 0x84, 0x01, 0x4a, 0x7f, 0x29, 0x62, 0x59, 0x71, 0xb1, 0x1f, 0xdb, 0x5e, 0x35, 0x40, 0xa6, 0x86, 0x14, 0x5d, 0x06, 0x85, 0xbe, 0x33, 0x3c, 0x5d, 0xd8, 0x63, 0x8d, 0x5c, 0x5c, 0xd3, 0xf2, 0x61, 0xa7, 0x83, 0x65, 0x01, 0x81, 0x21, 0x28, 0xa3, 0x8e, 0x94, 0x33, 0xc5, 0xda, 0x59, 0x9b, 0x58, 0xcf, 0x54, 0x74, 0x59, 0x68, 0xcb, 0xa3, 0x40, 0x6c, 0x0a, 0xbb, 0xb7, 0xce, 0x36, 0x14, 0xec, 0xde, 0x02, 0xb7, 0x07, 0x53, 0x6e, 0xb5, 0xd6, 0x93, 0xf2, 0x5f, 0xb1, 0x73, 0xe7, 0x54, 0x47, 0x77, 0x73, 0xc7, 0xc0, 0x96, 0x4e, 0x6f, 0x4b, 0xe7, 0x0d, 0xc1, 0xee, 0x8d, 0x45, 0xe1, 0x57, 0xeb, 0xd7, 0x53, 0xa7, 0xcd, 0xfe, 0xa8, 0x09, 0x15, + 0xee, 0xed, 0x5d, 0x30, 0x48, 0x2a, 0xde, 0x28, 0xe4, 0xf3, 0x85, 0xad, 0x78, 0x60, 0xcb, 0xac, 0xe2, 0xc0, 0x9e, 0x5c, 0x00, 0xfe, 0x4b, 0xdc, 0x6f, 0x68, 0x3d, 0xfe, 0x0c, 0xd7, 0x03, 0xf9, 0x7a, 0xdc, 0xe8, 0xdc, 0x11, 0x22, 0x0c, 0x78, 0xa9, 0xa6, 0xd5, 0x67, 0x79, 0x7a, 0xc8, 0x3f, 0xe7, 0x27, 0x0f, 0x2f, 0x13, 0x7e, 0x03, 0xec, 0xcb, 0x0e, 0x4f, 0xe6, 0x6f, 0xf7, 0x36, 0x2e, 0xc9, 0x5c, 0x71, 0x45, 0x66, 0x49, 0xa3, 0x97, 0x01, 0x8b, 0xae, 0x5e, 0x5f, 0x20, 0xdf, 0x2c, 0xac, 0xbf, 0x66, 0x51, 0xe3, 0x44, 0xbb, 0x5f, 0xb0, 0xf2, 0xed, 0x13, 0x22, 0x8d, 0x43, 0x6d, 0xe6, 0x60, 0x9b, 0x6a, 0x42, 0x82, 0x02, 0x75, 0x11, 0xb4, 0x0c, 0x40, 0x49, 0x92, 0x59, 0x5f, 0xf2, 0xaa, 0xa0, 0x03, 0x8f, 0x29, 0x33, 0x8a, 0xf2, 0xe7, 0xcf, 0xbc, 0x0c, 0x75, 0x30, 0x72, 0x54, + 0x12, 0x59, 0x48, 0x6a, 0xc1, 0x78, 0x51, 0x37, 0xc7, 0xe3, 0xc2, 0xe1, 0x79, 0x14, 0x3d, 0x2e, 0x1f, 0x7f, 0xf8, 0x21, 0x68, 0x12, 0x52, 0xe0, 0xc7, 0xcc, 0x5f, 0xd1, 0x76, 0x14, 0xb7, 0x22, 0xec, 0x47, 0x2b, 0xec, 0x87, 0x1e, 0xf7, 0x63, 0x49, 0x09, 0x05, 0x63, 0xb6, 0x38, 0xbb, 0xbe, 0x24, 0x0a, 0x4d, 0x23, 0xa2, 0xe4, 0x3b, 0xe3, 0x2a, 0x12, 0x76, 0x47, 0x91, 0x58, 0x32, 0x8e, 0x53, 0x07, 0xc7, 0x8b, 0x4a, 0x31, 0xee, 0x04, 0xcb, 0xbb, 0x50, 0x50, 0x42, 0xcb, 0x05, 0xff, 0xe5, 0x49, 0xbd, 0x50, 0x0b, 0x9a, 0x3e, 0xfc, 0x50, 0xf8, 0x0e, 0xf8, 0x0f, 0xea, 0xf8, 0xc5, 0xcf, 0x9c, 0xff, 0xf4, 0x45, 0xd8, 0x4e, 0x4c, 0x1d, 0x21, 0x5f, 0x94, 0xed, 0x85, 0x1a, 0x45, 0xae, 0x58, 0x6f, 0x84, 0x6f, 0x8c, 0x02, 0x28, 0x31, 0xc0, 0xbd, 0x45, 0x21, 0x18, 0x06, 0xb4, 0x8f, + 0xb0, 0x52, 0x4c, 0xa3, 0x83, 0x5b, 0x02, 0x3f, 0x05, 0xc3, 0x5c, 0x34, 0x16, 0x0d, 0x88, 0x4d, 0x64, 0xd8, 0x33, 0x9c, 0x83, 0xf3, 0x24, 0x0e, 0xe6, 0xc0, 0xab, 0x7f, 0x6f, 0xb8, 0x6a, 0x62, 0xd1, 0x35, 0x93, 0x0d, 0xf9, 0xa9, 0xa3, 0x2b, 0x26, 0x0e, 0x26, 0xe3, 0x6a, 0x1f, 0x67, 0xf4, 0x47, 0xd2, 0x9e, 0xcc, 0xd2, 0x66, 0xaf, 0xbf, 0x75, 0x69, 0x7d, 0xac, 0xcd, 0xb5, 0x4c, 0x07, 0x69, 0xaa, 0xf0, 0xc3, 0x9a, 0x50, 0xc3, 0xd4, 0xa1, 0xc5, 0x4b, 0x0e, 0x4f, 0x15, 0x0a, 0xb1, 0x5e, 0x83, 0x42, 0xa3, 0x36, 0x6a, 0xe5, 0x81, 0xe2, 0x44, 0xa1, 0x65, 0x4d, 0xd1, 0xaf, 0xd3, 0x5e, 0x68, 0xd0, 0x48, 0xeb, 0x77, 0x0a, 0xcf, 0x1b, 0xe4, 0xfd, 0x28, 0xab, 0x8d, 0x26, 0x20, 0x51, 0x11, 0x3d, 0x11, 0xeb, 0x71, 0x96, 0x1b, 0x31, 0x4e, 0x22, 0x08, 0xb8, 0x05, 0x67, 0xb8, 0xc1, + 0x44, 0xbe, 0x85, 0x90, 0x60, 0xc8, 0x53, 0x27, 0x84, 0x67, 0x4e, 0x9c, 0x00, 0xdd, 0xb2, 0x9d, 0x33, 0x01, 0xb8, 0x3c, 0xdf, 0x21, 0xdf, 0x14, 0xbe, 0x07, 0xf2, 0xd8, 0xfe, 0x42, 0x1d, 0x27, 0xfe, 0x2e, 0x43, 0xe1, 0x3d, 0x36, 0x64, 0xdf, 0x9c, 0x3d, 0xc9, 0xac, 0x81, 0x84, 0xe2, 0x3a, 0x07, 0x5f, 0xf2, 0xf7, 0x13, 0x27, 0x60, 0x47, 0xde, 0x14, 0xd7, 0xd2, 0x4e, 0x1d, 0x01, 0xfb, 0xe1, 0x33, 0x1c, 0x92, 0xf5, 0x45, 0x5f, 0x0e, 0x01, 0x25, 0xc8, 0xdd, 0xd8, 0xb2, 0x8b, 0xfc, 0x34, 0x70, 0x27, 0x73, 0x7c, 0xb0, 0xda, 0x09, 0x33, 0xcb, 0x05, 0xb3, 0xdf, 0xd3, 0xb6, 0xaa, 0xb5, 0xd0, 0xa0, 0x04, 0x06, 0xbd, 0xc2, 0x69, 0x2a, 0xe4, 0x4f, 0xd0, 0xff, 0xd2, 0x36, 0xd5, 0x17, 0xd1, 0xae, 0xd2, 0x18, 0x64, 0xed, 0x0b, 0xc1, 0x7f, 0x96, 0xf6, 0xed, 0x5b, 0xb0, 0x0d, 0x17, + 0xca, 0x03, 0x57, 0x63, 0xfd, 0x93, 0x41, 0xf8, 0xf9, 0x50, 0xe1, 0x91, 0xf2, 0xf4, 0x51, 0x78, 0x36, 0x39, 0x41, 0xa3, 0x2c, 0xec, 0x11, 0x96, 0x0d, 0x45, 0x82, 0xd8, 0x0b, 0xc3, 0x21, 0x4b, 0x2b, 0xc6, 0xcc, 0x40, 0x58, 0x6e, 0x56, 0x4e, 0x12, 0x10, 0x44, 0xa7, 0xcc, 0xdf, 0x3d, 0x21, 0xf6, 0x5b, 0x3a, 0xb7, 0xf5, 0x8a, 0x77, 0xcc, 0x0e, 0xd6, 0xad, 0x7d, 0xef, 0x2a, 0x87, 0x43, 0xf9, 0xac, 0xca, 0x69, 0x8d, 0xdd, 0x72, 0x0b, 0xd3, 0x65, 0xfd, 0x8b, 0x51, 0x27, 0xfc, 0xd4, 0xa5, 0x93, 0x5b, 0x39, 0xe1, 0x41, 0xbd, 0x0e, 0xc8, 0x74, 0x6a, 0xe1, 0x15, 0x38, 0x64, 0xb1, 0x2f, 0x66, 0xcc, 0x27, 0x3a, 0x24, 0x20, 0x3e, 0x02, 0x1b, 0x8c, 0x90, 0x44, 0x83, 0x8b, 0x95, 0x88, 0x88, 0x4a, 0xe8, 0x37, 0x0a, 0x61, 0x8f, 0x90, 0xa3, 0x25, 0xc7, 0x0c, 0x49, 0x2c, 0x1c, 0x2f, + 0xaa, 0xab, 0x33, 0xfd, 0x80, 0x98, 0xe9, 0x67, 0x16, 0x46, 0x6e, 0xa1, 0x8d, 0x07, 0xc8, 0x37, 0x67, 0xee, 0x2e, 0x9f, 0x53, 0xea, 0x30, 0x6c, 0x83, 0x47, 0xfb, 0xd3, 0xa1, 0x61, 0x28, 0x9c, 0x8c, 0x2f, 0xc1, 0x1b, 0x31, 0xc8, 0x1d, 0x4e, 0x50, 0xcb, 0xd1, 0x67, 0xf5, 0x7a, 0xf3, 0x84, 0x8f, 0xb7, 0xe4, 0xca, 0xb5, 0x33, 0xb2, 0x05, 0x64, 0x9c, 0xf1, 0x47, 0xa8, 0x0a, 0x6a, 0x72, 0xb5, 0x8a, 0x75, 0xf8, 0x2a, 0xea, 0x3f, 0x0c, 0x21, 0x53, 0x90, 0xfd, 0x29, 0x49, 0x1e, 0xdc, 0x6d, 0x32, 0xa9, 0x7e, 0xa0, 0x34, 0x28, 0x14, 0xac, 0xf2, 0x35, 0x25, 0xa4, 0x7a, 0x68, 0x63, 0x7c, 0xdb, 0x1b, 0x16, 0x76, 0xf3, 0x3c, 0x38, 0xc6, 0x05, 0xf5, 0x42, 0x0f, 0xf9, 0xa6, 0x9c, 0x15, 0xf6, 0xb3, 0x51, 0x93, 0x39, 0x6a, 0x04, 0x97, 0xb3, 0xf2, 0x0a, 0x3d, 0x41, 0xfb, 0xd1, 0x84, + 0xfc, 0x51, 0x32, 0x2c, 0x97, 0x21, 0x14, 0x67, 0x38, 0xd4, 0xe5, 0x18, 0xcd, 0x99, 0x18, 0x47, 0xab, 0x84, 0x7b, 0x67, 0x22, 0x4c, 0xbc, 0x95, 0xad, 0x02, 0x73, 0xb6, 0xcc, 0xc2, 0x72, 0x26, 0x4f, 0x2d, 0x64, 0xed, 0x6a, 0xe1, 0x26, 0xea, 0xe0, 0x42, 0x93, 0x51, 0x0b, 0x76, 0x92, 0x24, 0xea, 0x84, 0x46, 0x07, 0x5e, 0x0b, 0x7b, 0x85, 0xb5, 0xe4, 0x9b, 0x0a, 0x83, 0x50, 0x6f, 0x0a, 0xb1, 0xe0, 0x24, 0x6e, 0x77, 0x11, 0x6c, 0xf7, 0xa5, 0x52, 0xbb, 0xcc, 0x3c, 0xed, 0x4e, 0xe0, 0x76, 0x47, 0x70, 0xbb, 0x1c, 0x1b, 0x2e, 0xb5, 0x0b, 0x27, 0x05, 0xa7, 0x78, 0x22, 0x23, 0x88, 0x4c, 0x8e, 0x64, 0x7a, 0xf2, 0xa5, 0xab, 0x28, 0xe1, 0x26, 0xb5, 0x8d, 0x5d, 0x78, 0x10, 0x2a, 0xa8, 0xbb, 0x34, 0x46, 0x71, 0xf0, 0x77, 0xf9, 0x82, 0x42, 0x4e, 0xa7, 0x81, 0x27, 0x63, 0x8a, 0x0d, + 0x99, 0xc0, 0x0f, 0x0c, 0x0a, 0x4c, 0xb3, 0x21, 0xdd, 0xa0, 0x02, 0x90, 0x6e, 0x20, 0x3f, 0xcb, 0xfc, 0x34, 0x3b, 0xfc, 0x99, 0x2e, 0x2f, 0xb4, 0xef, 0xa9, 0x00, 0xa2, 0xdd, 0x88, 0x6e, 0x97, 0x3e, 0x31, 0xfd, 0x46, 0xd4, 0xfb, 0x82, 0x4d, 0x9b, 0x2e, 0x80, 0x14, 0xfc, 0xda, 0x8d, 0x8d, 0x8d, 0x1b, 0xaf, 0x5d, 0xb4, 0xe8, 0x10, 0xfa, 0x3c, 0x84, 0xe9, 0xb8, 0xbf, 0x7d, 0xa2, 0x71, 0xf7, 0xe5, 0x97, 0xa3, 0x7e, 0x24, 0x21, 0xfd, 0xda, 0x89, 0xcf, 0xdc, 0x30, 0x91, 0x29, 0xa6, 0x7b, 0xbb, 0xd2, 0x2e, 0x15, 0x83, 0xf5, 0x5e, 0x34, 0x0b, 0xe4, 0x6e, 0xa2, 0x0c, 0x69, 0x0a, 0x86, 0xb1, 0xe1, 0x78, 0xd8, 0x34, 0xd4, 0x58, 0x65, 0x38, 0x06, 0xff, 0xa0, 0xe1, 0x58, 0x26, 0xda, 0x8d, 0x41, 0xc9, 0x6e, 0x3c, 0xe2, 0xc9, 0x46, 0x2c, 0xb6, 0x58, 0xc1, 0xeb, 0xaa, 0x0b, 0x9a, + 0x2d, 0x91, 0x2c, 0x60, 0x59, 0x8d, 0x25, 0x9c, 0x75, 0xfb, 0x1a, 0x13, 0xf6, 0xc8, 0xc0, 0x39, 0x3d, 0x3d, 0xe7, 0x0c, 0x46, 0x1c, 0xc9, 0x66, 0xde, 0x93, 0x8b, 0x5a, 0xb5, 0xdc, 0x76, 0x56, 0x2b, 0xd7, 0x5a, 0x42, 0xf5, 0xee, 0xba, 0x4e, 0x05, 0xa3, 0x40, 0x10, 0x42, 0x1a, 0x77, 0x92, 0x77, 0xc5, 0x03, 0x6e, 0x9d, 0xd6, 0x1d, 0x4c, 0x38, 0xf9, 0xa4, 0x5b, 0x43, 0x6e, 0xb7, 0x16, 0xac, 0x81, 0x86, 0x64, 0x50, 0xcf, 0xd6, 0x14, 0x7a, 0x6b, 0xeb, 0x97, 0x36, 0xfb, 0x7c, 0xcd, 0x4b, 0xeb, 0x13, 0xdd, 0x99, 0x88, 0xc1, 0x10, 0x4a, 0x37, 0x87, 0x1d, 0xb5, 0x50, 0xfb, 0xb3, 0x05, 0xec, 0x11, 0x9f, 0x43, 0x63, 0x6d, 0xcf, 0xe8, 0xdd, 0x6a, 0xa8, 0x49, 0x97, 0xf8, 0xe8, 0x25, 0x58, 0x8e, 0xb4, 0x22, 0xe4, 0x12, 0xc8, 0x28, 0xe8, 0x3d, 0x32, 0x09, 0xc7, 0x0c, 0x92, 0x05, + 0x24, 0xde, 0x2e, 0x67, 0xa4, 0xd2, 0x47, 0x04, 0x35, 0xf2, 0xe9, 0x16, 0x56, 0x73, 0xc5, 0xc2, 0x6a, 0x0e, 0xb0, 0x60, 0x65, 0x7f, 0x67, 0x57, 0xdf, 0xba, 0x75, 0x43, 0xdb, 0xfb, 0xfc, 0x81, 0xfe, 0xed, 0x03, 0xeb, 0x6e, 0x61, 0x98, 0xce, 0x96, 0x7c, 0xc7, 0x87, 0x60, 0x5b, 0xb0, 0x7d, 0x79, 0x26, 0x3f, 0x51, 0x0c, 0x7e, 0x0c, 0x69, 0x82, 0xd8, 0x87, 0x07, 0x71, 0x1f, 0xbc, 0x44, 0x4b, 0xb1, 0xd1, 0x8d, 0x29, 0x14, 0xaa, 0xeb, 0x41, 0xee, 0x29, 0xe5, 0x12, 0xc9, 0xc4, 0x9e, 0xc8, 0x2a, 0x3d, 0x81, 0xfd, 0x30, 0x71, 0x46, 0x56, 0xaf, 0x56, 0xce, 0xd3, 0x0f, 0x80, 0x74, 0xce, 0x12, 0xa5, 0xaa, 0x00, 0x64, 0xf6, 0x0f, 0x76, 0xf5, 0xf4, 0x8d, 0x80, 0x47, 0xaf, 0x09, 0x71, 0xd7, 0x29, 0x2c, 0xa6, 0xcc, 0x2d, 0xbf, 0x1a, 0xde, 0xde, 0xeb, 0x1b, 0x19, 0x72, 0x9b, 0x18, + 0x4b, 0x4f, 0x26, 0xd3, 0xf8, 0x3f, 0xc2, 0x7e, 0xcf, 0xcf, 0x21, 0xa9, 0xfa, 0x1d, 0xf9, 0xa6, 0xaf, 0x71, 0x24, 0x35, 0x70, 0xae, 0x83, 0xf5, 0x20, 0xf9, 0x1f, 0xf6, 0x6f, 0xb5, 0x0c, 0x95, 0x81, 0xf3, 0x21, 0xf4, 0x5a, 0x64, 0x55, 0x47, 0x40, 0x35, 0x48, 0x1e, 0x2d, 0x11, 0x90, 0xb2, 0xd1, 0x12, 0x97, 0x13, 0x81, 0x9c, 0x73, 0xf5, 0xa9, 0x13, 0xa7, 0xc8, 0x2e, 0xe6, 0x4f, 0x33, 0x77, 0x92, 0xeb, 0xf6, 0xc3, 0x67, 0x56, 0x62, 0x9e, 0xb9, 0x93, 0x30, 0x13, 0x41, 0x14, 0x8a, 0x09, 0x5f, 0x22, 0x1a, 0xc9, 0xf1, 0x36, 0x43, 0x58, 0x9a, 0x46, 0x56, 0xa3, 0x82, 0x2c, 0xd3, 0x0c, 0xcc, 0x8c, 0x34, 0x92, 0x39, 0x66, 0x47, 0x0b, 0x38, 0x3a, 0xb4, 0xe4, 0x96, 0xf8, 0xc8, 0xee, 0x3e, 0xe4, 0x92, 0xbb, 0x65, 0x29, 0x6d, 0x58, 0xb5, 0x08, 0x3c, 0x21, 0x0c, 0x77, 0xed, 0x5c, + 0x9c, 0x4c, 0x2e, 0xde, 0xd9, 0x85, 0xfe, 0x5e, 0xb4, 0x0a, 0xe3, 0x39, 0x51, 0xc7, 0xe9, 0x16, 0xd8, 0x56, 0x04, 0xb5, 0xe5, 0xe3, 0xc8, 0xb2, 0x49, 0x47, 0x82, 0x48, 0xda, 0x02, 0x57, 0x3e, 0x42, 0x84, 0x9b, 0x24, 0x83, 0x0e, 0xa0, 0x2a, 0x06, 0x1d, 0xc9, 0xca, 0x5b, 0x6d, 0xcf, 0x29, 0x61, 0x19, 0x65, 0xe8, 0x96, 0xff, 0x3e, 0x74, 0x0b, 0x2a, 0xba, 0x20, 0xa3, 0x6f, 0xbd, 0xf6, 0x97, 0x97, 0xd6, 0xa8, 0x76, 0x33, 0x72, 0x46, 0xa1, 0xb9, 0x8a, 0xb1, 0x9b, 0x12, 0x26, 0xab, 0xfc, 0xa0, 0x46, 0x41, 0x2b, 0xe8, 0x3d, 0x94, 0x93, 0xc3, 0x34, 0xe0, 0x0d, 0xb5, 0x4d, 0xa7, 0xb3, 0xa9, 0x85, 0x30, 0xf9, 0xa6, 0x5f, 0xd8, 0x6a, 0x8c, 0x5a, 0x93, 0x41, 0x70, 0x8b, 0xd6, 0xcc, 0xe9, 0x85, 0xad, 0xfe, 0xb8, 0x39, 0xc2, 0x82, 0x5b, 0x74, 0x26, 0x54, 0xdb, 0x93, 0x3a, 0x44, + 0xd2, 0xcc, 0x3d, 0x90, 0xdd, 0xd6, 0x11, 0xbb, 0x8b, 0xba, 0x30, 0x00, 0x0c, 0xb2, 0x78, 0xd9, 0x70, 0xbe, 0xaf, 0xc8, 0x21, 0x02, 0x67, 0x94, 0x86, 0x9f, 0x1c, 0x2a, 0x19, 0x22, 0xb0, 0x76, 0x85, 0xb5, 0x87, 0xb3, 0x17, 0x92, 0x9f, 0xac, 0xbe, 0x11, 0xb2, 0x10, 0x4b, 0x08, 0xd5, 0x86, 0x0f, 0xe3, 0x28, 0x5b, 0x4e, 0xb2, 0x52, 0xc8, 0xe4, 0xe6, 0xb3, 0x96, 0x86, 0xc7, 0x10, 0x7d, 0xe8, 0x80, 0xff, 0x28, 0xd3, 0x5d, 0x63, 0xf0, 0x7b, 0x02, 0x19, 0x47, 0x2c, 0xec, 0x8e, 0xb8, 0xed, 0x06, 0x9d, 0x59, 0x66, 0xaf, 0xdd, 0x51, 0x4c, 0x0d, 0xd4, 0x3b, 0xdb, 0x4c, 0x26, 0xb9, 0x3a, 0x3a, 0x58, 0xe8, 0x59, 0xe6, 0xe9, 0xbb, 0x60, 0x9c, 0x7e, 0x85, 0xd4, 0x72, 0x76, 0xbd, 0xdb, 0x6e, 0x73, 0x3c, 0x9b, 0x4e, 0x6a, 0x0d, 0x5a, 0x9d, 0x3a, 0x19, 0xab, 0xf1, 0xe6, 0x87, 0x12, + 0xa0, 0x4f, 0xc3, 0xf2, 0x81, 0x40, 0x7f, 0xaf, 0xbb, 0x29, 0xe5, 0x25, 0x45, 0x59, 0xb6, 0x0e, 0xee, 0x0d, 0x2b, 0x73, 0x1f, 0x94, 0x65, 0x43, 0xc4, 0x01, 0x51, 0xa2, 0xd3, 0xfb, 0xa1, 0x50, 0xa2, 0x41, 0x81, 0x97, 0x46, 0x54, 0x25, 0x06, 0x41, 0x11, 0xc3, 0x5f, 0x98, 0xaa, 0x5f, 0x24, 0x11, 0xb4, 0xaa, 0x22, 0xfc, 0xb6, 0x4a, 0xc8, 0xfb, 0x34, 0x32, 0x71, 0xfb, 0x71, 0xf2, 0x71, 0x69, 0x0a, 0xa4, 0x40, 0xee, 0xd9, 0x37, 0x41, 0xf9, 0x53, 0xab, 0xd5, 0x86, 0xb4, 0xc1, 0x68, 0x30, 0x18, 0x9c, 0x55, 0xee, 0xdd, 0xf2, 0x29, 0xd5, 0xde, 0xc1, 0xf5, 0xa6, 0x5a, 0x47, 0xa6, 0x3d, 0x97, 0x08, 0xd6, 0x5b, 0x0f, 0x8f, 0x3a, 0xe5, 0x1a, 0xdf, 0xc2, 0x7c, 0xac, 0x37, 0xe3, 0xf2, 0x35, 0x2f, 0xc9, 0xa4, 0xdb, 0x1c, 0x72, 0xb9, 0x93, 0xe1, 0xac, 0xec, 0x82, 0x15, 0x63, 0xbd, + 0x6e, 0x27, 0xd8, 0x3a, 0xf3, 0x57, 0x7b, 0xc2, 0x1f, 0x0f, 0x76, 0x4f, 0xb5, 0x37, 0x4d, 0x0d, 0xd4, 0xf0, 0x0e, 0xbb, 0x0d, 0xe9, 0x26, 0xf5, 0xd4, 0x75, 0x54, 0x9a, 0xb9, 0x1f, 0x9e, 0x79, 0x13, 0xb1, 0x04, 0x28, 0xc4, 0x95, 0xe6, 0xda, 0x01, 0xa9, 0xea, 0x03, 0x4a, 0xd2, 0x04, 0xe4, 0xca, 0x08, 0x2f, 0x01, 0x57, 0x4a, 0xbf, 0x2a, 0xaa, 0x7f, 0x95, 0x70, 0x2b, 0xd3, 0x84, 0x92, 0x94, 0x93, 0xa8, 0x64, 0xa3, 0x5c, 0xa3, 0x25, 0x91, 0x2e, 0x36, 0x89, 0xf0, 0x19, 0x54, 0x13, 0x6a, 0xa0, 0x52, 0xe1, 0x08, 0xff, 0x73, 0x25, 0xa0, 0x2c, 0x14, 0xeb, 0x3d, 0x5d, 0xce, 0x65, 0xca, 0x94, 0x9f, 0xa3, 0xb1, 0x9d, 0xe1, 0x2c, 0xcf, 0xcf, 0x7e, 0xb4, 0xaa, 0x40, 0xe4, 0x19, 0x4f, 0x11, 0x55, 0x8d, 0xa2, 0xea, 0x90, 0xf3, 0x3c, 0x3f, 0x3e, 0x0e, 0x25, 0x71, 0x62, 0xc9, 0xe2, + 0xa1, 0x81, 0x9e, 0xae, 0x96, 0xa6, 0x42, 0x2e, 0x53, 0x17, 0xaf, 0x09, 0x05, 0x3c, 0x2e, 0x84, 0x93, 0x89, 0xc3, 0x2e, 0x75, 0xf3, 0x84, 0x5d, 0xca, 0x3e, 0x23, 0xea, 0x12, 0x54, 0x82, 0x24, 0xca, 0x2e, 0x5d, 0x71, 0xb3, 0x7e, 0x55, 0x6d, 0x0d, 0xd8, 0xeb, 0x73, 0xf2, 0x2b, 0xf1, 0xa6, 0xf5, 0xa5, 0xda, 0x63, 0x11, 0x77, 0x18, 0x32, 0x01, 0x9d, 0x09, 0xee, 0xd9, 0xa9, 0x96, 0xa6, 0x21, 0x93, 0x7b, 0xac, 0x31, 0xb9, 0xa0, 0xd1, 0xa7, 0xe1, 0x40, 0x44, 0xd1, 0x91, 0xf1, 0xc7, 0x9c, 0x9c, 0x46, 0xad, 0x61, 0x42, 0x96, 0x6c, 0x43, 0x83, 0x23, 0xbf, 0xc6, 0xe7, 0xdb, 0xd1, 0xbe, 0x70, 0x39, 0xde, 0xcc, 0x9b, 0xcc, 0x31, 0xde, 0xdc, 0xda, 0xb0, 0x8c, 0xd4, 0x9b, 0x9d, 0x7a, 0xaf, 0xbb, 0xa9, 0x6b, 0x77, 0xba, 0x4e, 0x67, 0xd4, 0xe9, 0xe1, 0x9e, 0x8e, 0xc6, 0xc2, 0xc1, + 0x18, 0x94, 0xb3, 0x1b, 0x7d, 0x96, 0xce, 0x56, 0xad, 0x5e, 0x23, 0x97, 0xa5, 0x1d, 0x7e, 0x4e, 0x51, 0x9b, 0xc9, 0xa4, 0x16, 0xf4, 0xbb, 0x9b, 0xeb, 0x78, 0x52, 0xda, 0xdf, 0x1c, 0x3c, 0xe3, 0x0a, 0x22, 0x4e, 0xb4, 0x16, 0x9b, 0xce, 0x5a, 0x91, 0x77, 0xb2, 0x1c, 0xff, 0x32, 0x8d, 0xe2, 0xa6, 0x62, 0xd1, 0x50, 0xe0, 0x2c, 0xe5, 0x78, 0x43, 0xf3, 0x95, 0xe3, 0x95, 0xe6, 0x22, 0xf2, 0xb0, 0xc6, 0x11, 0x76, 0xd6, 0x37, 0x2a, 0x84, 0xaf, 0x59, 0xf2, 0x8d, 0x4d, 0xce, 0xfc, 0xda, 0xbe, 0x68, 0xa0, 0x73, 0x4d, 0x53, 0xfd, 0xb2, 0x56, 0x3f, 0x1c, 0x6b, 0x83, 0xbc, 0xa7, 0xd1, 0x1d, 0x83, 0x6c, 0xd0, 0xa0, 0xa4, 0x27, 0xcc, 0x11, 0xb7, 0xb1, 0x90, 0x7d, 0xdf, 0x09, 0xbb, 0x1c, 0xec, 0xdd, 0xdc, 0x93, 0x5b, 0xd3, 0x5b, 0xe3, 0x6b, 0x1d, 0x6f, 0xf0, 0x59, 0xfa, 0x3b, 0xb4, + 0xac, 0x16, 0xe9, 0x0c, 0x19, 0x28, 0x9f, 0x7f, 0x0f, 0xf6, 0xbd, 0x40, 0xec, 0x28, 0xea, 0x0a, 0x80, 0x90, 0x25, 0xe0, 0xe9, 0xf3, 0x4a, 0x70, 0xc3, 0x41, 0xb8, 0x95, 0xfc, 0x88, 0x55, 0xd2, 0xa2, 0x18, 0x83, 0xd3, 0xfa, 0x27, 0xc5, 0x6d, 0x57, 0xca, 0xd4, 0x9e, 0x16, 0x83, 0x62, 0xca, 0x90, 0xc2, 0x67, 0xdc, 0x5c, 0xb9, 0x6f, 0xbc, 0xa8, 0xaa, 0x0d, 0x07, 0x82, 0x7c, 0x30, 0x22, 0xaf, 0x2e, 0x04, 0x2b, 0xab, 0xaa, 0x03, 0x6b, 0x99, 0xa7, 0x0c, 0x2c, 0x28, 0x1d, 0x4d, 0x70, 0x87, 0x42, 0xab, 0x54, 0x72, 0xf6, 0xa0, 0x23, 0x1a, 0xaf, 0x4b, 0x36, 0x37, 0x67, 0x93, 0xf1, 0x98, 0x2b, 0xe4, 0xb4, 0x68, 0x8c, 0xf2, 0xa8, 0x25, 0x9b, 0xcb, 0x58, 0x10, 0xd6, 0x69, 0x5d, 0x34, 0x19, 0x40, 0xf8, 0xdd, 0xad, 0xcb, 0xbd, 0x4c, 0x42, 0x2b, 0x53, 0x69, 0x54, 0x2e, 0x9b, 0xdd, + 0x33, 0xe1, 0x75, 0xb9, 0x43, 0x3a, 0x56, 0xa7, 0xcd, 0x38, 0xfc, 0x26, 0x85, 0xb9, 0xa6, 0x25, 0x52, 0xdb, 0x66, 0xe0, 0xda, 0xdd, 0xae, 0x4c, 0xc4, 0x1a, 0x42, 0x3c, 0xcf, 0x47, 0x1d, 0x05, 0x2f, 0x32, 0xf7, 0xc2, 0x79, 0x78, 0xab, 0xa8, 0x42, 0x31, 0x7e, 0x36, 0x1c, 0xe3, 0x27, 0x9a, 0xc5, 0x63, 0xe5, 0x50, 0xbf, 0x39, 0x21, 0x7e, 0x18, 0xa4, 0xa9, 0x52, 0xa2, 0x7c, 0x1a, 0xa7, 0xf0, 0xa8, 0x3f, 0xed, 0x89, 0x6d, 0xf3, 0x3d, 0xa1, 0xf9, 0x07, 0xdb, 0xf8, 0x47, 0x5e, 0x8f, 0x92, 0xc2, 0xd4, 0x68, 0xd2, 0x03, 0xa1, 0xda, 0xa0, 0xc4, 0x12, 0xca, 0x61, 0x87, 0xe0, 0x8c, 0xb0, 0xc3, 0x7c, 0x55, 0x6d, 0x56, 0xc9, 0xba, 0x1d, 0xf1, 0xc1, 0xc9, 0xcd, 0x5a, 0x62, 0x0b, 0x5b, 0x82, 0xb5, 0xc1, 0x08, 0xef, 0xcc, 0x2f, 0xce, 0xb5, 0x8e, 0x79, 0xbd, 0x0a, 0xad, 0x4a, 0x69, + 0x84, 0x6b, 0x51, 0x13, 0x4b, 0xd7, 0xc2, 0xb5, 0xa8, 0x8d, 0xc7, 0xdc, 0x21, 0xa7, 0x59, 0x63, 0x50, 0x31, 0x23, 0xf0, 0x88, 0xc8, 0xcd, 0xd1, 0xe6, 0x48, 0xac, 0x99, 0x35, 0xb5, 0x7a, 0x91, 0x98, 0x17, 0x72, 0xe7, 0xb5, 0x72, 0x95, 0x56, 0xe5, 0xb2, 0x3a, 0x2a, 0x6b, 0x81, 0x78, 0x43, 0x3b, 0x79, 0x17, 0xf1, 0x3b, 0xe6, 0x0e, 0x1c, 0x63, 0x08, 0xb9, 0x39, 0xd2, 0xaf, 0x49, 0x6a, 0x2d, 0x8a, 0x32, 0x44, 0x7a, 0x25, 0x31, 0x09, 0xe6, 0x06, 0x18, 0x72, 0x50, 0x56, 0x90, 0xcf, 0x09, 0x44, 0x5a, 0x74, 0xf8, 0x5f, 0x74, 0x36, 0x9f, 0x91, 0xf5, 0x58, 0x75, 0x3a, 0xab, 0x87, 0x35, 0xfa, 0x6c, 0x3a, 0xea, 0x66, 0x52, 0x39, 0xf3, 0xd7, 0x93, 0x46, 0x2f, 0xfa, 0x09, 0xb9, 0x5d, 0x10, 0xb7, 0xf6, 0x8a, 0x7a, 0xc9, 0x22, 0xf2, 0x2d, 0xd2, 0x0d, 0xd7, 0x3a, 0x42, 0x7c, 0xb5, + 0xa8, 0xe2, 0xa1, 0x5a, 0xcf, 0x56, 0xe1, 0xe0, 0xe2, 0xd2, 0x98, 0x24, 0x58, 0x8b, 0x8f, 0x2a, 0x92, 0x0c, 0x27, 0x45, 0x87, 0x00, 0xfe, 0x93, 0x2e, 0xad, 0xef, 0x19, 0x77, 0x6d, 0xab, 0xdc, 0xb5, 0x8d, 0x2e, 0xad, 0xe9, 0x67, 0xbd, 0xeb, 0xb3, 0x5e, 0x23, 0x82, 0x77, 0x72, 0xc1, 0x68, 0x0d, 0x86, 0xd6, 0xe2, 0xce, 0x52, 0xa5, 0x6e, 0x56, 0x45, 0x1e, 0xeb, 0x8f, 0x7d, 0x03, 0x99, 0x78, 0x47, 0xd2, 0x6a, 0x49, 0x0f, 0x66, 0xdd, 0x71, 0x87, 0x9e, 0xd1, 0xab, 0x14, 0x06, 0x7d, 0x7f, 0x34, 0xea, 0xce, 0x41, 0x6e, 0xe6, 0x50, 0x32, 0xeb, 0xbd, 0x21, 0x77, 0x6e, 0x20, 0x2e, 0xfc, 0x28, 0x39, 0xdc, 0xe0, 0x55, 0xab, 0x5b, 0x59, 0x25, 0xb3, 0x02, 0x14, 0x63, 0x29, 0xbb, 0x45, 0x38, 0x42, 0x2a, 0x6d, 0xe2, 0x1c, 0x05, 0x21, 0x5d, 0xf8, 0x6f, 0x48, 0x17, 0xa2, 0x48, 0x6a, + 0x46, 0xf9, 0x36, 0x51, 0x1c, 0x2b, 0x57, 0x0a, 0x7e, 0x2b, 0x05, 0xbd, 0x61, 0xc6, 0x5b, 0x89, 0x98, 0x86, 0x87, 0x3c, 0x0a, 0x4f, 0x78, 0xa0, 0xba, 0xbb, 0xd5, 0x5b, 0x29, 0x05, 0x66, 0xa7, 0x14, 0x2e, 0x90, 0xab, 0xd5, 0x2a, 0xce, 0xe6, 0xb7, 0xb7, 0xb7, 0x74, 0x34, 0xb8, 0x03, 0x56, 0x0e, 0x1e, 0xe5, 0x1a, 0x4b, 0x26, 0xdf, 0xe0, 0x4c, 0x8f, 0x15, 0xc3, 0x7c, 0xc7, 0xda, 0xf6, 0x96, 0x71, 0xa8, 0x30, 0x29, 0x94, 0x1a, 0xad, 0x66, 0x6c, 0x68, 0xe1, 0x32, 0x9d, 0x41, 0xa7, 0xc9, 0x60, 0x6a, 0xd6, 0x33, 0x55, 0x6c, 0xdc, 0xd0, 0x5f, 0x03, 0x95, 0x16, 0x40, 0xd4, 0xd1, 0xb7, 0x91, 0x4d, 0x70, 0x3d, 0xf5, 0xc4, 0x79, 0x55, 0xb1, 0x1b, 0x55, 0xc1, 0x21, 0x93, 0x44, 0x69, 0xdd, 0xe6, 0x5e, 0xd9, 0x46, 0x94, 0xbd, 0xe8, 0xf3, 0x3c, 0x33, 0xdf, 0xed, 0x18, 0xb3, 0x04, + 0x7e, 0xd7, 0x13, 0xfa, 0x20, 0x2b, 0x96, 0x1f, 0xca, 0x06, 0xfc, 0x95, 0x40, 0x13, 0xf0, 0xf0, 0x05, 0x17, 0x1e, 0x0d, 0x77, 0xaf, 0x6b, 0x6e, 0x5a, 0xd7, 0x13, 0xa2, 0x6f, 0xbb, 0xeb, 0xae, 0xb6, 0xc9, 0xde, 0x50, 0xa8, 0x77, 0xb2, 0x4d, 0x9c, 0xd7, 0x7a, 0xea, 0x7a, 0xb2, 0x99, 0xbe, 0x0d, 0xc5, 0x53, 0x8b, 0x31, 0x2c, 0x92, 0x71, 0x00, 0x8b, 0x6f, 0xd8, 0x27, 0x63, 0x20, 0x0c, 0xfc, 0x19, 0xa1, 0x24, 0x68, 0x12, 0xc9, 0xe6, 0x50, 0xcf, 0xba, 0xa6, 0x66, 0xf8, 0xda, 0xeb, 0xf7, 0xb6, 0x34, 0xd2, 0xb7, 0xb5, 0x4d, 0xf5, 0x84, 0x42, 0x3d, 0x53, 0x6d, 0x77, 0x2d, 0x5a, 0x2c, 0xbe, 0xbb, 0x83, 0x7a, 0x84, 0x5c, 0xca, 0xec, 0x83, 0x27, 0xe9, 0x42, 0x69, 0x1e, 0xb0, 0x91, 0x86, 0x58, 0x5b, 0xf2, 0x21, 0x4e, 0x92, 0xa5, 0x5c, 0x57, 0x73, 0xf9, 0xa7, 0x72, 0xf1, 0xd4, + 0x73, 0xf1, 0x54, 0x78, 0xce, 0xf2, 0x18, 0xaa, 0xe7, 0x57, 0x65, 0x5d, 0xab, 0x72, 0x25, 0xc3, 0x09, 0x79, 0x8a, 0x35, 0xf3, 0x11, 0x11, 0x9d, 0x0e, 0xcc, 0xf1, 0x97, 0x63, 0xe7, 0x31, 0xa9, 0xf3, 0x44, 0x3c, 0x68, 0xcb, 0x3d, 0x09, 0x86, 0x62, 0x69, 0x87, 0xb8, 0xcf, 0xe8, 0x8f, 0x7d, 0x03, 0x59, 0xb4, 0x51, 0x8f, 0xc0, 0xad, 0x99, 0xb5, 0x1e, 0x19, 0x73, 0xca, 0xe1, 0xeb, 0x06, 0xa1, 0x0e, 0x5d, 0xcb, 0x9c, 0x44, 0x91, 0x8a, 0xc5, 0x06, 0x3f, 0x3c, 0x9b, 0x6a, 0x86, 0x44, 0xf5, 0xef, 0x30, 0x50, 0x2b, 0x35, 0x2e, 0xfa, 0x4b, 0x26, 0x45, 0xdc, 0x56, 0x31, 0x27, 0x46, 0x9c, 0xb5, 0x00, 0x11, 0x08, 0x70, 0x7e, 0x2e, 0x38, 0x4f, 0x7d, 0x3b, 0x50, 0xaa, 0x6f, 0x97, 0x91, 0x94, 0x0e, 0x29, 0x57, 0xab, 0x16, 0x8a, 0x76, 0x4a, 0x67, 0xb1, 0xb6, 0xae, 0xcd, 0x60, 0x5f, + 0x55, 0xb7, 0x62, 0xcd, 0xe1, 0x91, 0x40, 0xfa, 0x70, 0x24, 0x6c, 0x8f, 0x73, 0xcc, 0x49, 0x28, 0xd4, 0xf1, 0xee, 0x50, 0x26, 0x5e, 0x9f, 0xd8, 0xb4, 0x16, 0x0c, 0x09, 0x4f, 0x8e, 0xd7, 0xc6, 0xd1, 0x47, 0xba, 0xc6, 0x6a, 0x02, 0xd8, 0x46, 0x68, 0x83, 0xeb, 0x18, 0x86, 0x7b, 0xae, 0x86, 0xd8, 0x57, 0x54, 0x87, 0xcc, 0x70, 0xa6, 0x14, 0xd8, 0xcf, 0x56, 0x9a, 0x78, 0xd4, 0x49, 0x11, 0x85, 0x9a, 0x96, 0xac, 0x3e, 0xd2, 0x06, 0xc4, 0x88, 0xea, 0x73, 0xaf, 0x68, 0xce, 0xf2, 0xcc, 0x7c, 0xb7, 0xc3, 0xf9, 0xfe, 0x46, 0x4d, 0x4d, 0x28, 0x18, 0xc4, 0x25, 0x44, 0xce, 0x70, 0xda, 0x55, 0x18, 0x66, 0x89, 0xc6, 0x37, 0x03, 0x32, 0x0c, 0xd4, 0x0e, 0x7b, 0x66, 0x53, 0x6d, 0x6a, 0x53, 0xc6, 0x6e, 0x57, 0x03, 0xce, 0x37, 0x12, 0xa9, 0x49, 0x3b, 0xd2, 0x5d, 0xb1, 0x58, 0x17, 0xfc, + 0xa8, 0x09, 0x8f, 0xf8, 0x4c, 0xd4, 0x8b, 0x6a, 0xa3, 0x5e, 0x11, 0xf4, 0xf9, 0x82, 0x0a, 0xbd, 0x51, 0x9d, 0x31, 0xbb, 0xd3, 0xdb, 0x1b, 0x82, 0xed, 0x49, 0x87, 0x23, 0xd9, 0x1e, 0x6c, 0xd8, 0x9e, 0x76, 0x9b, 0x45, 0x59, 0x7e, 0x11, 0xf9, 0x0e, 0xd9, 0x22, 0xdb, 0x4b, 0xd8, 0x89, 0x2c, 0x5a, 0xa1, 0x08, 0xc0, 0xba, 0x34, 0xa8, 0x07, 0x0c, 0x41, 0x0d, 0x42, 0xc9, 0x47, 0xb6, 0xa7, 0x84, 0x16, 0x3e, 0xcb, 0x61, 0x40, 0x60, 0x11, 0x91, 0xab, 0x8d, 0x04, 0xf9, 0x28, 0x86, 0x66, 0xe5, 0x3c, 0x60, 0x6e, 0x8c, 0x3b, 0x0e, 0xd8, 0x8a, 0xcc, 0xea, 0x7a, 0x89, 0x4e, 0x98, 0x03, 0x6f, 0xb0, 0x71, 0x37, 0x9f, 0xf2, 0xea, 0xcc, 0x81, 0xb8, 0xd9, 0xa7, 0xcf, 0xd6, 0xa7, 0xc2, 0x99, 0xf8, 0x61, 0x57, 0xd3, 0x8a, 0x96, 0xc6, 0xc5, 0x19, 0x5b, 0x3a, 0xdd, 0x3d, 0xc0, 0xb7, 0x8c, + 0x66, 0xc3, 0x6d, 0xb6, 0xc3, 0x74, 0xc0, 0x86, 0x00, 0xc2, 0x9c, 0xc1, 0x84, 0x4d, 0x7e, 0x6e, 0xb8, 0x3e, 0x9b, 0x1c, 0x59, 0xc7, 0x20, 0x8b, 0x21, 0x9f, 0xed, 0xf6, 0x67, 0x16, 0x99, 0x4c, 0x93, 0x5d, 0x89, 0x25, 0x6d, 0x21, 0x8b, 0xf5, 0x87, 0xe2, 0x99, 0xd1, 0x91, 0xbf, 0x27, 0x73, 0xcc, 0x5d, 0x50, 0x33, 0xaf, 0x29, 0x86, 0xad, 0x0a, 0x06, 0x63, 0xdf, 0xa2, 0x0b, 0x7b, 0x70, 0xf4, 0x15, 0xb6, 0x17, 0x61, 0x26, 0x04, 0xef, 0xf0, 0x04, 0x42, 0x16, 0x7c, 0x2e, 0x75, 0x94, 0xe8, 0x93, 0x13, 0x11, 0xa7, 0xeb, 0x71, 0xd0, 0x1c, 0x22, 0xc6, 0x64, 0x4e, 0x93, 0x68, 0xec, 0xf2, 0x1f, 0x56, 0x79, 0x13, 0x4d, 0x21, 0xce, 0x51, 0x93, 0x59, 0x58, 0x08, 0xaa, 0x8c, 0x1e, 0xa8, 0x86, 0x32, 0x16, 0x1d, 0x73, 0x32, 0xbf, 0xf7, 0xa2, 0x0b, 0xb2, 0x6f, 0xbd, 0xd9, 0x7c, 0xc5, + 0xf6, 0xbe, 0xac, 0xd9, 0x53, 0x3f, 0x71, 0xe9, 0x50, 0x60, 0x78, 0xc1, 0x20, 0x6f, 0x33, 0xc2, 0xa9, 0xaa, 0x85, 0xfb, 0x29, 0x21, 0x3b, 0x1f, 0xc5, 0x9f, 0x11, 0x0f, 0x8a, 0x9b, 0x48, 0xdd, 0x06, 0xc9, 0x6e, 0x9d, 0x02, 0x71, 0x08, 0x09, 0x00, 0x03, 0xfd, 0x04, 0xca, 0x3f, 0xcd, 0xba, 0xa1, 0x94, 0x07, 0xef, 0x42, 0x86, 0x78, 0x12, 0x17, 0x77, 0x87, 0x1f, 0x34, 0x39, 0x59, 0x66, 0x2d, 0xd3, 0x54, 0x09, 0xf8, 0xc2, 0x0c, 0x0f, 0x14, 0x96, 0x4c, 0x67, 0x5f, 0xfc, 0xf4, 0x67, 0x51, 0x2d, 0x55, 0x40, 0x34, 0x37, 0xc4, 0xa2, 0x4e, 0xbb, 0xc5, 0x44, 0xb0, 0x80, 0x95, 0x55, 0x8a, 0x94, 0x4b, 0x96, 0x22, 0xae, 0x4a, 0xa4, 0xf3, 0xd0, 0x55, 0xd2, 0xab, 0x68, 0x2c, 0xc2, 0x33, 0x05, 0x6a, 0xb3, 0x45, 0x14, 0xbb, 0x62, 0x89, 0x7a, 0x39, 0x9d, 0x2b, 0xfe, 0x33, 0xd2, 0xc0, + 0x2a, 0x43, 0xc9, 0xb4, 0xc9, 0x65, 0x4f, 0xf5, 0x2e, 0xee, 0x4d, 0xf9, 0x3b, 0xd6, 0xb6, 0xb4, 0xac, 0xed, 0x0c, 0x2c, 0x5a, 0x62, 0x0a, 0x38, 0xf4, 0x6b, 0xd7, 0x9b, 0xea, 0x73, 0x59, 0x33, 0xf5, 0x97, 0xfe, 0x76, 0x7f, 0x3e, 0xe6, 0xd7, 0x29, 0xdd, 0x35, 0x85, 0x20, 0xb2, 0x2e, 0x91, 0x1c, 0xab, 0x51, 0xaa, 0x94, 0x06, 0x8d, 0xd1, 0x17, 0xae, 0xeb, 0x5e, 0x53, 0xcc, 0x8d, 0x77, 0x84, 0x82, 0xc5, 0x95, 0xb9, 0x91, 0x3d, 0x7e, 0x8d, 0x93, 0x8f, 0xd8, 0x5b, 0x87, 0x29, 0x99, 0x82, 0x81, 0xf3, 0x9a, 0x22, 0x7f, 0x4f, 0xe9, 0xb1, 0xfe, 0x5d, 0x8f, 0x72, 0x54, 0xc3, 0xd8, 0xab, 0x85, 0xad, 0x40, 0x78, 0x7d, 0xb1, 0x54, 0x8e, 0x82, 0xf4, 0x45, 0x9f, 0x16, 0x1c, 0x67, 0x7d, 0x1d, 0xcb, 0xda, 0xa0, 0xf6, 0x2c, 0x17, 0xd1, 0xb2, 0x4b, 0xa5, 0x54, 0x03, 0x62, 0xf7, 0xcd, + 0x15, 0x7f, 0x2c, 0xb2, 0x81, 0x59, 0x3d, 0x22, 0xf6, 0x0a, 0x0a, 0x44, 0x06, 0x6f, 0x2e, 0x5a, 0x61, 0xf6, 0x45, 0x38, 0xbb, 0xef, 0xf2, 0x8d, 0xa3, 0x0d, 0xab, 0x3a, 0x82, 0xb5, 0x8b, 0x77, 0x75, 0x5e, 0xde, 0xb9, 0x63, 0x24, 0xe9, 0xca, 0xf4, 0xc5, 0x83, 0x0c, 0xc9, 0x28, 0x65, 0x81, 0xf8, 0xf2, 0x61, 0xea, 0x03, 0x72, 0xe5, 0xf2, 0x60, 0x63, 0xc4, 0xe4, 0x4b, 0xac, 0x5b, 0x1a, 0xe9, 0x5e, 0x93, 0xef, 0xd8, 0x3c, 0x10, 0x01, 0x5f, 0xfa, 0x12, 0x88, 0xf4, 0x4e, 0xb6, 0xd4, 0x2e, 0xe9, 0xca, 0xb1, 0x0d, 0x0a, 0xa3, 0x41, 0x43, 0x59, 0x86, 0xf3, 0x0b, 0x96, 0x23, 0xb0, 0x30, 0xa8, 0xcb, 0xfd, 0x82, 0x54, 0x30, 0x5f, 0x26, 0x52, 0xc4, 0x8a, 0xa2, 0x32, 0xee, 0xe7, 0x54, 0xc8, 0x1b, 0x31, 0x28, 0xa1, 0x25, 0x20, 0x5c, 0x64, 0xa8, 0x39, 0x13, 0xeb, 0x70, 0x4e, 0x06, + 0xc2, 0xbd, 0xc0, 0xe4, 0xda, 0x85, 0x32, 0xe7, 0x18, 0x0c, 0x77, 0xcf, 0x10, 0x73, 0x2f, 0x8b, 0x6e, 0x99, 0x14, 0x51, 0x0b, 0xc9, 0x48, 0x3c, 0x80, 0xd5, 0x62, 0xa9, 0xa0, 0x3c, 0x22, 0x9a, 0x7a, 0x6c, 0x04, 0xe5, 0x22, 0xc8, 0xbe, 0x94, 0x6b, 0x11, 0xed, 0x03, 0xc8, 0x4c, 0x20, 0xd9, 0xca, 0xe1, 0x98, 0x33, 0xa4, 0xc2, 0xe2, 0xb1, 0x27, 0xa3, 0x7e, 0x5d, 0x27, 0x29, 0xa7, 0x17, 0xd2, 0x2c, 0x3b, 0x79, 0xdf, 0x02, 0x9d, 0xd7, 0xc7, 0x29, 0x2d, 0xe6, 0xa6, 0x85, 0xb6, 0x6e, 0x52, 0xc6, 0x0c, 0xd2, 0x46, 0x76, 0xf2, 0xde, 0xa5, 0x2a, 0xb7, 0x9b, 0xea, 0x2f, 0x78, 0x8c, 0x4a, 0x5b, 0x3c, 0x70, 0x3b, 0xcb, 0x6b, 0xee, 0x55, 0xeb, 0xc1, 0xb6, 0xc7, 0x3d, 0xe6, 0x8c, 0x9a, 0xd3, 0x2a, 0x12, 0x8d, 0x27, 0x59, 0x8f, 0xe6, 0x3e, 0xb5, 0x5e, 0xb8, 0xed, 0x79, 0xab, 0x4d, + 0xa4, 0x2f, 0xfd, 0xc2, 0x5e, 0xb2, 0x01, 0xd4, 0x11, 0x1a, 0x82, 0x27, 0x96, 0x15, 0x55, 0x88, 0xb6, 0xa0, 0x5a, 0x32, 0x25, 0x90, 0x5e, 0x4b, 0x99, 0xaa, 0x94, 0x62, 0x24, 0x69, 0x7a, 0x2b, 0x8d, 0xab, 0x2e, 0xe1, 0x35, 0x2d, 0x5f, 0x45, 0xd6, 0x34, 0xa2, 0x6c, 0x4c, 0x1b, 0xff, 0x06, 0x17, 0x8d, 0x04, 0x71, 0xd1, 0x25, 0xab, 0x07, 0x54, 0xa8, 0x4d, 0x60, 0x96, 0x20, 0x89, 0xe8, 0xcc, 0x7f, 0x58, 0xf2, 0x8e, 0x4c, 0x5b, 0x73, 0x3a, 0xd9, 0x7e, 0xd8, 0xd7, 0xb6, 0xaa, 0x25, 0xb3, 0xb2, 0x27, 0x0a, 0xd5, 0xfe, 0xfa, 0x68, 0xb7, 0xf3, 0xf0, 0xb0, 0xd3, 0x82, 0x74, 0xfe, 0x62, 0x7b, 0x7e, 0x79, 0xab, 0x3f, 0xd4, 0x3d, 0xd9, 0xda, 0x3c, 0x39, 0x50, 0x63, 0xb7, 0xfe, 0x88, 0x20, 0x4e, 0x9f, 0x26, 0x52, 0xc2, 0x5e, 0x4a, 0x0f, 0xd2, 0x94, 0x9c, 0x1a, 0x21, 0x88, 0x8f, + 0xaf, 0x23, 0x94, 0x8f, 0x11, 0xe4, 0x37, 0x3f, 0xbe, 0xee, 0xd1, 0x4c, 0x5c, 0x1c, 0xd7, 0xa5, 0xa7, 0xdf, 0x93, 0x5d, 0x46, 0x43, 0x3d, 0x8f, 0x68, 0x27, 0xbe, 0x53, 0xd4, 0xa3, 0xf2, 0xca, 0xcd, 0x50, 0xcb, 0x8a, 0xf9, 0x49, 0x9a, 0xa1, 0xca, 0x6a, 0x06, 0x43, 0x21, 0x47, 0xd6, 0x4e, 0x35, 0x8a, 0xbd, 0x64, 0xc4, 0xd8, 0x4b, 0x72, 0x5c, 0xa5, 0xc0, 0x11, 0xbf, 0x4a, 0x5c, 0x62, 0xa9, 0x14, 0x49, 0x2d, 0x72, 0xea, 0xcf, 0xfb, 0xc4, 0x0e, 0x19, 0xc6, 0xae, 0x99, 0x7b, 0xb3, 0x0a, 0x43, 0x02, 0x54, 0xaa, 0x01, 0xcd, 0x7a, 0x00, 0x1e, 0x78, 0x17, 0x81, 0xe1, 0x1f, 0xb3, 0x99, 0xda, 0x44, 0x34, 0x8c, 0x52, 0x28, 0xcd, 0x9c, 0x56, 0x8d, 0x95, 0x55, 0x0d, 0x0e, 0xc5, 0xc7, 0x47, 0x43, 0x4e, 0x95, 0x43, 0xd2, 0x03, 0xbc, 0x18, 0xd8, 0x53, 0x52, 0xe0, 0xca, 0xba, 0x7c, + 0x99, 0xeb, 0x22, 0x46, 0xcc, 0xfc, 0x88, 0x2f, 0xae, 0x6a, 0xb9, 0xe4, 0x7a, 0x8e, 0x4d, 0xdf, 0x71, 0xe1, 0x92, 0x9b, 0xb6, 0x17, 0x4d, 0xce, 0x99, 0x69, 0xaa, 0x76, 0xf1, 0xce, 0x9e, 0x8e, 0xb5, 0x3d, 0x29, 0xa3, 0x4d, 0xdb, 0x10, 0x5c, 0xbd, 0x69, 0x4b, 0xea, 0xe0, 0x2b, 0xad, 0x0b, 0x5e, 0xbf, 0x68, 0xe9, 0x81, 0xd1, 0xc4, 0x2b, 0x93, 0x93, 0xe3, 0xb7, 0x37, 0xf2, 0xb7, 0x92, 0xdb, 0x9b, 0x36, 0xf4, 0x47, 0x8f, 0x5e, 0x0a, 0xe4, 0x5d, 0x8b, 0x5b, 0xcf, 0x7b, 0x70, 0x7b, 0xd2, 0x3b, 0x71, 0xcd, 0xca, 0x04, 0x8a, 0xa6, 0xc6, 0xa0, 0xb0, 0xe7, 0xae, 0xdf, 0xb0, 0xa3, 0x52, 0xe9, 0x7b, 0xed, 0xa2, 0xa9, 0x8b, 0xc0, 0x2f, 0x88, 0x12, 0xae, 0xd0, 0x79, 0x70, 0xee, 0x35, 0x44, 0x0e, 0x72, 0x6b, 0x73, 0xc4, 0xaf, 0x21, 0x68, 0x19, 0x51, 0xef, 0xb4, 0x31, 0x34, 0x25, + 0xe3, 0x0c, 0x24, 0xc0, 0xca, 0x2e, 0x9a, 0x4f, 0x3f, 0x1c, 0x3b, 0xdc, 0x30, 0x32, 0x94, 0xc3, 0x8e, 0x52, 0xd8, 0xcf, 0xc5, 0x66, 0x25, 0x31, 0xcc, 0x89, 0x2c, 0xd5, 0xf8, 0x0e, 0x9f, 0x71, 0x93, 0x14, 0x0f, 0x21, 0x21, 0x88, 0x92, 0xa5, 0x32, 0xdf, 0x7a, 0xad, 0x56, 0x9b, 0xd3, 0xe6, 0xc2, 0x41, 0x93, 0xd5, 0x1a, 0xc5, 0x39, 0xe4, 0x1e, 0xca, 0x2a, 0x99, 0xe3, 0x18, 0x13, 0x96, 0xd7, 0x6b, 0xa9, 0x08, 0xce, 0x2b, 0x47, 0xf4, 0xa5, 0x50, 0x55, 0x4f, 0x13, 0x55, 0xa4, 0xce, 0x81, 0x5f, 0xb5, 0x9f, 0xbf, 0x6b, 0x67, 0x8b, 0x98, 0x3a, 0x7e, 0x6c, 0xff, 0xfe, 0x57, 0x8a, 0xe7, 0xc1, 0xaf, 0x2f, 0xb7, 0xa5, 0x8f, 0x5d, 0xbe, 0x6f, 0x56, 0x85, 0x86, 0xb6, 0x24, 0x79, 0x58, 0x97, 0xac, 0xaf, 0xd5, 0xdd, 0x87, 0xe2, 0x25, 0xb7, 0x1d, 0xba, 0x97, 0x15, 0x5e, 0x04, 0x03, + 0x5a, 0xf8, 0x8b, 0x5e, 0xd0, 0x01, 0x41, 0xb3, 0x74, 0xe0, 0xf0, 0x3d, 0x2c, 0x98, 0xac, 0xd4, 0x6d, 0x78, 0x4c, 0xb3, 0x64, 0x50, 0xe4, 0x7f, 0x81, 0xd3, 0xef, 0xd1, 0x63, 0x70, 0x6e, 0x26, 0x88, 0xc9, 0xe2, 0xba, 0x09, 0x00, 0x94, 0x08, 0xf3, 0xb7, 0x1f, 0x28, 0xe4, 0x49, 0x5c, 0xdf, 0x7b, 0x2e, 0xf6, 0x2f, 0x8a, 0xa2, 0x94, 0x9f, 0x4b, 0xc8, 0x51, 0x18, 0xc3, 0xb9, 0x84, 0x82, 0x01, 0x0a, 0xe6, 0x5c, 0x82, 0x81, 0x34, 0x46, 0x76, 0x6e, 0x09, 0xc4, 0xed, 0xdc, 0x72, 0x05, 0x6e, 0x54, 0x8b, 0x62, 0x79, 0x30, 0x19, 0x08, 0xc5, 0xe1, 0x3f, 0x62, 0x61, 0xda, 0x59, 0x59, 0x92, 0x74, 0xe9, 0x2c, 0xe6, 0x9b, 0x11, 0x4b, 0xb5, 0xcc, 0x9f, 0x03, 0x47, 0x4b, 0x18, 0xaa, 0x26, 0x19, 0xdc, 0x71, 0x90, 0xe6, 0x86, 0xcb, 0x88, 0x8f, 0x1b, 0xab, 0xd1, 0x8e, 0xfc, 0x8d, 0x43, + 0x4b, 0x47, 0x23, 0xac, 0x97, 0x6d, 0x69, 0xd0, 0x5b, 0xce, 0x05, 0xc6, 0x78, 0x7f, 0x83, 0x02, 0xd5, 0x15, 0xda, 0xf2, 0x78, 0x4b, 0xad, 0xc2, 0x66, 0xe2, 0xa2, 0x52, 0x21, 0x21, 0x7f, 0xf3, 0xf0, 0xb2, 0x31, 0x34, 0xe9, 0xf0, 0x3e, 0xeb, 0x76, 0x63, 0x72, 0xb8, 0x49, 0x59, 0xb7, 0xfa, 0xd0, 0x0a, 0x5c, 0x54, 0x48, 0x82, 0x3d, 0xca, 0xb8, 0xf9, 0x5a, 0xbf, 0xd3, 0xe1, 0x36, 0x31, 0x4c, 0x2e, 0xa6, 0xb3, 0xa6, 0xc6, 0x07, 0xf3, 0x1a, 0x9f, 0x1f, 0x15, 0x18, 0xea, 0xcd, 0x57, 0x6a, 0x0a, 0x05, 0xea, 0x02, 0xf0, 0x1e, 0x33, 0x23, 0xcf, 0xc5, 0xb4, 0xd6, 0xf4, 0xca, 0xa1, 0xbc, 0x9a, 0xaf, 0x91, 0xca, 0x0b, 0xa1, 0xd8, 0x04, 0xe1, 0x66, 0xea, 0x47, 0xb4, 0x12, 0xaa, 0x09, 0x01, 0x62, 0x0d, 0x71, 0x7d, 0x51, 0xdd, 0x07, 0x18, 0x72, 0xa4, 0x16, 0xb1, 0x55, 0x69, 0xaf, + 0xc5, 0x11, 0x80, 0x2d, 0x54, 0xde, 0x27, 0x4b, 0xb9, 0x62, 0x90, 0x66, 0x95, 0x70, 0x13, 0x15, 0x8a, 0xcd, 0x18, 0x47, 0x0a, 0x27, 0xe2, 0xee, 0x94, 0xe3, 0xc3, 0xab, 0x40, 0x15, 0x37, 0x51, 0x32, 0xfe, 0xd9, 0x9e, 0xaa, 0x7e, 0x00, 0xe3, 0x4e, 0x4d, 0xac, 0x5c, 0x31, 0x56, 0x6c, 0xcb, 0x67, 0x93, 0x71, 0x9f, 0x07, 0x17, 0xb1, 0xd2, 0x03, 0x3d, 0xca, 0xc3, 0x64, 0x2a, 0x19, 0x85, 0x90, 0x71, 0x51, 0x68, 0x21, 0x2a, 0x39, 0x35, 0x54, 0x49, 0x7c, 0x14, 0x7f, 0x93, 0xe5, 0xce, 0x10, 0xc8, 0xaa, 0xe4, 0x32, 0x14, 0x79, 0x48, 0xba, 0x37, 0x9d, 0xe3, 0xcc, 0x8f, 0x64, 0xb2, 0x23, 0x39, 0xe7, 0x39, 0x3f, 0xaa, 0xe9, 0x99, 0x98, 0xde, 0x5a, 0x97, 0xed, 0xe4, 0x7b, 0xb6, 0x0c, 0xf4, 0xed, 0x18, 0x8a, 0x24, 0x74, 0x86, 0xa6, 0xe9, 0xeb, 0x97, 0xf5, 0x34, 0x7b, 0xbd, 0xcd, + 0x6b, 0x2f, 0x39, 0x70, 0xc9, 0xda, 0xe6, 0x8e, 0x7d, 0x8f, 0xef, 0xda, 0xf9, 0x95, 0x1d, 0x85, 0x89, 0xc5, 0x89, 0x8c, 0x81, 0x4f, 0xf3, 0x43, 0x63, 0x4d, 0x9b, 0x8e, 0x8d, 0x76, 0xee, 0x49, 0x0b, 0xff, 0x9b, 0x7c, 0x7b, 0xf3, 0x78, 0xac, 0x3f, 0xe3, 0x76, 0x43, 0x86, 0x39, 0xbe, 0x99, 0x7a, 0x3c, 0x18, 0xb7, 0xab, 0x19, 0x99, 0xdc, 0x31, 0x58, 0xcf, 0x17, 0x6a, 0xac, 0xf6, 0x64, 0xdb, 0x27, 0x13, 0x9e, 0xde, 0xf0, 0xd0, 0xde, 0xb1, 0x5a, 0xbd, 0x31, 0x6e, 0x0e, 0xf8, 0x0d, 0xb4, 0xd2, 0x92, 0x1d, 0xef, 0xeb, 0xbc, 0x64, 0x5d, 0x53, 0x62, 0x70, 0x7d, 0x6e, 0x70, 0x93, 0xc7, 0xd4, 0xdd, 0x68, 0xa9, 0xab, 0x8d, 0x1b, 0x62, 0x47, 0x56, 0x0d, 0x5d, 0x34, 0x56, 0xeb, 0x76, 0xf6, 0x87, 0x94, 0x65, 0x9f, 0xe0, 0x65, 0x50, 0xaf, 0x50, 0xe3, 0x4c, 0x46, 0xc9, 0x27, 0x2e, + 0xc6, 0x66, 0x63, 0xcf, 0xf8, 0x24, 0x71, 0x66, 0x6e, 0x68, 0x00, 0x2a, 0x2f, 0x50, 0xa5, 0x36, 0xf3, 0x39, 0xf2, 0xb2, 0x13, 0x82, 0x70, 0xe2, 0x04, 0x20, 0x91, 0x66, 0x70, 0xea, 0x14, 0xa9, 0x7c, 0xee, 0x39, 0xb8, 0x0e, 0x11, 0xb8, 0xc6, 0xf7, 0xe3, 0xfc, 0x8d, 0x1e, 0xe4, 0x67, 0x6b, 0x81, 0x27, 0xa7, 0x5e, 0x45, 0xe2, 0x68, 0xbb, 0x72, 0xd0, 0x66, 0x39, 0x4f, 0x63, 0x27, 0x18, 0x06, 0x44, 0x4f, 0x77, 0x47, 0x6b, 0x4d, 0x04, 0xf9, 0xd9, 0x18, 0xbc, 0x0e, 0xb3, 0xfd, 0x6c, 0x70, 0x86, 0xf1, 0x5f, 0xf4, 0x19, 0x7e, 0x36, 0x5c, 0xdf, 0x24, 0xc0, 0x63, 0xb0, 0x61, 0xf0, 0xa3, 0xce, 0x2d, 0x83, 0x51, 0x67, 0xaa, 0x2d, 0x30, 0x70, 0xf5, 0x74, 0x8b, 0xbf, 0xb8, 0x16, 0xfc, 0xd4, 0x69, 0x18, 0xdc, 0xb7, 0xb2, 0xbe, 0x73, 0xf2, 0xbc, 0xc9, 0xce, 0xb6, 0x8b, 0x1e, 0xdb, + 0xb3, 0xe5, 0x8e, 0xad, 0xb9, 0xa6, 0x73, 0x6e, 0x5d, 0x13, 0x68, 0x4b, 0xb9, 0xd2, 0x2b, 0xf6, 0x0d, 0xe5, 0x36, 0xc7, 0x85, 0x8f, 0x12, 0x41, 0xca, 0xf7, 0x73, 0x7f, 0xeb, 0xb2, 0xfa, 0xd4, 0x70, 0x7b, 0xc6, 0x62, 0x69, 0x1e, 0x1c, 0xcf, 0x45, 0x7a, 0xb3, 0x1e, 0xfa, 0xa0, 0xc7, 0xa1, 0x08, 0x8e, 0x2d, 0x1b, 0xb2, 0xb9, 0x72, 0xad, 0xc3, 0x53, 0x07, 0x97, 0x2f, 0x3c, 0x7a, 0x4e, 0x31, 0xbb, 0x7c, 0x77, 0x7b, 0xc7, 0x79, 0x93, 0x0b, 0x9d, 0xc6, 0x50, 0x4d, 0xca, 0x1d, 0x59, 0xb9, 0x6e, 0x43, 0x4a, 0x26, 0xab, 0x09, 0x0c, 0xc0, 0x31, 0xd7, 0x9f, 0xfe, 0x33, 0x7d, 0x3b, 0xf5, 0x37, 0x6c, 0xcb, 0xdf, 0x27, 0xb2, 0x2d, 0xb7, 0x52, 0x4e, 0xa2, 0xc8, 0x42, 0x24, 0x71, 0xac, 0x57, 0x88, 0x78, 0x1a, 0x92, 0xfd, 0x69, 0x07, 0x55, 0xd2, 0x6e, 0x3e, 0xe5, 0xa6, 0x73, 0x70, + 0xd5, 0xc4, 0x4f, 0x7d, 0x09, 0xaa, 0x71, 0x1d, 0x0a, 0xa2, 0x95, 0x31, 0x87, 0xcc, 0x21, 0x83, 0x4a, 0xaa, 0x68, 0x23, 0xd1, 0x88, 0x50, 0xa0, 0x34, 0x61, 0xb3, 0x0b, 0xda, 0x2c, 0x14, 0x52, 0xde, 0x96, 0xe5, 0x85, 0xc2, 0x58, 0x8b, 0x17, 0x0c, 0x1f, 0x13, 0xa3, 0xd9, 0xb7, 0x37, 0x1d, 0x3b, 0xf6, 0x0c, 0xf8, 0xf1, 0xb3, 0xc7, 0xc0, 0x4f, 0x5b, 0x50, 0xf8, 0x3a, 0x0a, 0x6b, 0x6f, 0x69, 0x29, 0x05, 0xb5, 0xb7, 0x1c, 0x05, 0x27, 0x84, 0x2d, 0xe8, 0xff, 0x88, 0x6f, 0x64, 0x21, 0x6d, 0xfc, 0x26, 0xce, 0x2b, 0xae, 0x45, 0x11, 0x15, 0x52, 0x0f, 0x91, 0xbf, 0xae, 0xd4, 0x43, 0xc9, 0xb3, 0x0b, 0x47, 0x50, 0x9b, 0x8c, 0xa0, 0xde, 0x05, 0x59, 0x53, 0x10, 0xf7, 0xae, 0x94, 0x03, 0x5e, 0x03, 0x02, 0x73, 0x6a, 0xed, 0xd4, 0x82, 0x48, 0xb8, 0x8d, 0x6c, 0x46, 0xbc, 0x20, 0x03, + 0x2e, 0xe7, 0x9b, 0x97, 0x65, 0xe2, 0xcb, 0xfb, 0x33, 0x0a, 0xc1, 0x46, 0x15, 0x6e, 0x52, 0x2d, 0xbb, 0xf4, 0x8b, 0xa3, 0xeb, 0x6f, 0xdb, 0x52, 0x38, 0x74, 0xe8, 0x15, 0xf0, 0x8e, 0xcc, 0xab, 0x23, 0x95, 0x06, 0xb3, 0xea, 0x6a, 0xa5, 0x51, 0xa7, 0x22, 0x6f, 0x00, 0xbb, 0xf2, 0xa3, 0x2d, 0x5e, 0x7e, 0xf8, 0xc0, 0xea, 0x4b, 0xbf, 0x71, 0x61, 0x43, 0x6e, 0xea, 0xd8, 0xf8, 0xe4, 0x51, 0x70, 0x8b, 0xb0, 0x75, 0xec, 0x2b, 0x63, 0x46, 0x87, 0x5e, 0x9e, 0xdf, 0x38, 0x39, 0x95, 0xc5, 0x7b, 0xdd, 0x20, 0xec, 0xa1, 0x7a, 0x98, 0x07, 0x21, 0xb7, 0x7b, 0x41, 0xaa, 0x6d, 0x96, 0x00, 0x72, 0x59, 0xb9, 0xca, 0x65, 0x02, 0x28, 0xa4, 0x2f, 0x52, 0x55, 0x50, 0x3f, 0x02, 0x0d, 0xc1, 0x44, 0xa4, 0xaa, 0x26, 0xd8, 0xd4, 0x50, 0xb9, 0x52, 0xd8, 0xd6, 0x32, 0x32, 0x68, 0x8d, 0x24, 0x60, + 0xed, 0x9c, 0xff, 0x09, 0xa2, 0xea, 0x81, 0x62, 0x06, 0x72, 0x09, 0x5a, 0x21, 0xc7, 0x75, 0xd1, 0x14, 0xb8, 0x2e, 0x9a, 0xf8, 0x28, 0xc6, 0x86, 0xa1, 0xd6, 0x42, 0x1a, 0x27, 0xbd, 0xa0, 0xa4, 0x20, 0x4e, 0x49, 0x28, 0x22, 0xa1, 0x08, 0x1f, 0x09, 0xf1, 0x41, 0x13, 0x46, 0x11, 0x11, 0x9d, 0xad, 0x67, 0x56, 0x97, 0xcb, 0xe6, 0xcb, 0x70, 0x44, 0x73, 0xaa, 0x26, 0x92, 0x54, 0x60, 0xe1, 0xbe, 0x71, 0xaa, 0x4e, 0x2a, 0xe0, 0xa1, 0xc7, 0x05, 0x3e, 0x3a, 0xdc, 0x6e, 0xbd, 0x2e, 0x1d, 0x5a, 0xb2, 0xb0, 0x8c, 0x45, 0x24, 0x82, 0x9d, 0x81, 0x9d, 0x08, 0xfc, 0x77, 0x45, 0x75, 0xd9, 0xb8, 0x97, 0xed, 0x3a, 0xdd, 0xd4, 0x66, 0xb2, 0x0a, 0xea, 0xec, 0xdf, 0x61, 0x97, 0x1b, 0xe0, 0x9c, 0xae, 0x84, 0x7b, 0xa1, 0x0d, 0x10, 0x45, 0x35, 0xaf, 0x85, 0x44, 0xd8, 0x86, 0xa7, 0x50, 0x3c, + 0x02, 0x29, 0x42, 0x26, 0xa7, 0xe5, 0xd8, 0xe6, 0xcf, 0xc8, 0xd1, 0x58, 0xe5, 0xf2, 0x12, 0xf0, 0xad, 0x34, 0xb8, 0x73, 0xe6, 0x4e, 0xa7, 0xef, 0x73, 0x3d, 0xb6, 0x63, 0xbe, 0x55, 0xc8, 0xcf, 0x7d, 0xac, 0x34, 0xb3, 0xf3, 0x3c, 0x3e, 0x67, 0x49, 0xce, 0xf2, 0xa0, 0xb4, 0x24, 0x67, 0x3e, 0x2f, 0xd9, 0xbb, 0x58, 0x53, 0x38, 0x10, 0x45, 0xd8, 0x50, 0x78, 0x4d, 0x2a, 0xe1, 0xdc, 0x9f, 0x55, 0xf6, 0x2f, 0x3c, 0xab, 0x90, 0x05, 0xa9, 0x59, 0x71, 0x60, 0x71, 0xb8, 0x69, 0xc7, 0xdd, 0x9b, 0x5a, 0xd6, 0xd7, 0xa4, 0x8b, 0x12, 0xb4, 0x8b, 0x54, 0x80, 0x25, 0xa9, 0xb4, 0x71, 0x5c, 0xbc, 0x6d, 0x79, 0x47, 0xeb, 0x78, 0x93, 0x7b, 0xdb, 0x8e, 0x7b, 0x1c, 0x20, 0x28, 0xc8, 0x50, 0x0d, 0x0b, 0x72, 0x52, 0x5e, 0x3b, 0xb8, 0xb1, 0xb5, 0xef, 0xa2, 0xf1, 0x0c, 0xab, 0xdf, 0x53, 0xbd, + 0x52, 0xaf, 0x61, 0x66, 0xec, 0x72, 0xd5, 0x75, 0x45, 0xfa, 0x97, 0xb5, 0xf9, 0x4d, 0x40, 0x7d, 0xdb, 0x12, 0x47, 0xad, 0x4e, 0xb4, 0x53, 0x50, 0x47, 0xc8, 0xbc, 0xe4, 0x73, 0xdc, 0x2b, 0xf9, 0x1c, 0xb5, 0x50, 0xe3, 0xd3, 0xa0, 0x31, 0x23, 0x0f, 0x3c, 0xf6, 0x39, 0xc2, 0x5f, 0x98, 0xaa, 0x5f, 0x24, 0x9f, 0xa3, 0x15, 0xab, 0x86, 0x0c, 0x40, 0xa0, 0x7d, 0x34, 0xa8, 0xb8, 0x6c, 0x9c, 0x45, 0xbe, 0x5c, 0x58, 0x71, 0x72, 0xa8, 0xa4, 0x3f, 0xce, 0xbe, 0x07, 0x19, 0x6c, 0x83, 0xd1, 0x50, 0x50, 0xb4, 0x80, 0x56, 0xe9, 0x1a, 0xf2, 0xc8, 0xec, 0x74, 0x44, 0xb9, 0x0c, 0x47, 0xea, 0x59, 0xdf, 0xb0, 0xd6, 0x85, 0x12, 0xd9, 0xb6, 0x8c, 0xa3, 0xd6, 0xa4, 0x75, 0x14, 0xd3, 0xf5, 0x4b, 0x9a, 0x7d, 0xae, 0x4c, 0x6f, 0x2c, 0x3f, 0xe2, 0x53, 0x2b, 0xed, 0xa3, 0xa3, 0x76, 0x25, 0x5d, + 0xef, 0x74, 0xf7, 0x8e, 0xad, 0x58, 0xc0, 0x5a, 0x57, 0x38, 0xf8, 0x9a, 0x81, 0xa9, 0xa6, 0xf6, 0xa9, 0x6e, 0xa8, 0xb5, 0x25, 0xed, 0x92, 0xad, 0x96, 0x24, 0xd2, 0x54, 0x02, 0xfc, 0x09, 0xeb, 0xb8, 0x45, 0x62, 0x71, 0x71, 0x61, 0x11, 0x50, 0x32, 0x07, 0xaa, 0x81, 0x97, 0xc3, 0x46, 0x99, 0x0c, 0x60, 0xfa, 0x08, 0x12, 0x4a, 0xfe, 0x24, 0x5a, 0xf7, 0xd9, 0x42, 0xc3, 0xa4, 0xb8, 0xd2, 0x72, 0xd1, 0x83, 0x07, 0x88, 0xb6, 0x96, 0x78, 0xd4, 0xe7, 0x31, 0x73, 0x06, 0x5d, 0xa5, 0xb2, 0x1d, 0xa8, 0xc0, 0xc5, 0x55, 0xeb, 0xf7, 0xb3, 0xd0, 0xe1, 0x62, 0xa0, 0x84, 0x0e, 0x37, 0xcb, 0x5f, 0xd3, 0x12, 0x8b, 0x1b, 0x8d, 0x0a, 0x9d, 0x56, 0x2b, 0xbb, 0x5e, 0xa6, 0x41, 0x86, 0x5e, 0xde, 0xde, 0xde, 0x02, 0x75, 0x5b, 0x4f, 0x30, 0x6e, 0xf3, 0xd7, 0xfb, 0xf4, 0x96, 0x64, 0x57, 0x52, + 0xae, 0x93, 0xa3, 0xfc, 0x7a, 0x8b, 0x35, 0x27, 0x53, 0x76, 0x2f, 0xe6, 0x8b, 0xeb, 0xda, 0x5a, 0x56, 0x06, 0xa9, 0x0f, 0x5c, 0x56, 0x1d, 0x27, 0x37, 0xf2, 0x0e, 0x90, 0x55, 0x28, 0xd5, 0x3a, 0xf5, 0xf2, 0xc5, 0xb5, 0x2d, 0x5c, 0x34, 0xeb, 0xd5, 0x70, 0xfe, 0x5a, 0xa7, 0x27, 0x1f, 0xb1, 0x02, 0x83, 0x9f, 0x63, 0x55, 0x94, 0xa2, 0x25, 0xed, 0xde, 0x3f, 0x81, 0x2c, 0xc1, 0xe1, 0x10, 0x9e, 0x8b, 0x14, 0x5c, 0xf7, 0x3a, 0xd9, 0x56, 0x42, 0x4b, 0x14, 0x88, 0xc6, 0x62, 0xde, 0x07, 0xb7, 0x3f, 0x42, 0x91, 0x21, 0x07, 0x33, 0xf5, 0xe9, 0x9a, 0x60, 0x40, 0x46, 0xf5, 0x4b, 0xe8, 0x54, 0x93, 0xe5, 0xf0, 0x84, 0x69, 0x14, 0x9e, 0xe0, 0x75, 0x9b, 0x39, 0x31, 0xab, 0x94, 0x91, 0x22, 0x60, 0x2a, 0xe5, 0xfc, 0x3c, 0x25, 0xed, 0x58, 0x12, 0x8c, 0x20, 0x05, 0x97, 0x47, 0xc2, 0xf9, + 0x76, 0x54, 0x7b, 0xa9, 0x16, 0xaa, 0xc5, 0x16, 0x6b, 0xc1, 0x6a, 0x91, 0xc9, 0x41, 0x12, 0x55, 0x70, 0x20, 0x0d, 0xcd, 0x75, 0x2d, 0xed, 0x5d, 0xcf, 0x36, 0x74, 0x5c, 0x7b, 0x59, 0x6e, 0x55, 0x57, 0x98, 0x6f, 0x1d, 0xcd, 0xad, 0x6e, 0x56, 0xd7, 0xc6, 0xa6, 0xed, 0x4a, 0xb9, 0xce, 0xa5, 0x75, 0xe9, 0x8d, 0x9c, 0x5a, 0xbf, 0x65, 0x6c, 0xa5, 0x4b, 0x2e, 0xd7, 0x50, 0x7f, 0x31, 0xc6, 0xfb, 0xb2, 0xfe, 0xf4, 0x40, 0xa6, 0xb7, 0x1f, 0x9c, 0xce, 0xde, 0x3c, 0x7d, 0xe2, 0x5e, 0x7f, 0x71, 0x55, 0x73, 0xed, 0xd2, 0xf6, 0x70, 0x6b, 0xaa, 0xd7, 0xd1, 0x9e, 0xd4, 0x59, 0x15, 0x7a, 0x46, 0xc1, 0xc8, 0x7c, 0xae, 0x34, 0x9b, 0x2e, 0x24, 0xb5, 0x56, 0x39, 0xf6, 0x55, 0x5d, 0x0f, 0xbe, 0x89, 0x7c, 0x55, 0xc0, 0x5c, 0x54, 0xa9, 0xff, 0x7f, 0xee, 0xab, 0x12, 0xbd, 0x88, 0xff, 0x48, + 0x1b, 0xc5, 0xe4, 0x67, 0x21, 0x6b, 0x6c, 0x9b, 0xe3, 0xe0, 0x2a, 0x79, 0xb8, 0xc2, 0xb5, 0x7e, 0xd1, 0xc3, 0x05, 0xe6, 0x9a, 0x10, 0x67, 0x57, 0x4b, 0x99, 0xeb, 0xe1, 0x0a, 0x47, 0x6a, 0xbd, 0xcb, 0xdb, 0xea, 0x86, 0xb3, 0xae, 0x40, 0x6d, 0xb4, 0x3e, 0xd8, 0x34, 0x14, 0x35, 0x67, 0x72, 0x59, 0x4b, 0x54, 0xc9, 0x6a, 0x2c, 0x8e, 0x90, 0x33, 0x11, 0xaf, 0xcd, 0x36, 0x37, 0xd7, 0xa6, 0x63, 0x35, 0x8e, 0xa0, 0xdd, 0xa8, 0x54, 0x69, 0x15, 0xcc, 0x94, 0x3b, 0x68, 0x8d, 0x64, 0x5c, 0xee, 0x76, 0xce, 0xd0, 0x9a, 0x8a, 0x34, 0xd7, 0x98, 0xe5, 0x26, 0xbf, 0x23, 0xab, 0xd1, 0xb1, 0xba, 0x90, 0xdb, 0xe5, 0x9d, 0xf0, 0x38, 0xac, 0x6e, 0x95, 0x56, 0x25, 0xd7, 0x62, 0x7e, 0xcc, 0x90, 0x6f, 0x81, 0x2c, 0x73, 0x07, 0x61, 0x47, 0xb1, 0xff, 0x6a, 0x15, 0xb2, 0xf9, 0x0f, 0xa2, 0x30, + 0x6f, 0xe4, 0x2b, 0x25, 0xd6, 0x8b, 0x2e, 0xa0, 0x75, 0x88, 0xa6, 0x30, 0x28, 0x91, 0x80, 0x0b, 0x9b, 0xc5, 0x72, 0x49, 0x81, 0x59, 0xfa, 0x50, 0x1e, 0x0b, 0x10, 0xb9, 0x8c, 0x39, 0x03, 0xb2, 0x1d, 0xc6, 0x68, 0x57, 0xda, 0xe8, 0x35, 0xd0, 0x40, 0x6b, 0x0c, 0x3b, 0x8a, 0xc5, 0xc3, 0x1d, 0xf4, 0x25, 0x7f, 0x0c, 0xb4, 0xa7, 0x5c, 0x0c, 0x33, 0xac, 0x52, 0xb8, 0xa3, 0x7f, 0x9c, 0x7c, 0xfe, 0x79, 0x64, 0x4b, 0x27, 0xdf, 0x25, 0x2d, 0x50, 0xe6, 0x35, 0xa3, 0x9a, 0x07, 0x26, 0x0c, 0x82, 0x81, 0x71, 0x4b, 0x66, 0x65, 0xa6, 0x8f, 0xe3, 0xa4, 0x10, 0x51, 0xfc, 0x35, 0x13, 0xe6, 0x00, 0x94, 0x78, 0xa5, 0x18, 0xc1, 0xdc, 0x5c, 0xe5, 0x9d, 0x25, 0x2d, 0x87, 0xdd, 0xbd, 0xe9, 0x58, 0x67, 0xc2, 0x7a, 0xa8, 0x06, 0xd9, 0xec, 0xa1, 0x34, 0xbc, 0xf8, 0x3f, 0x9d, 0x3e, 0x4f, 0x76, + 0x00, 0x5b, 0xc7, 0x63, 0x29, 0x87, 0x19, 0x9c, 0x2f, 0xd2, 0xa4, 0x18, 0x75, 0x33, 0xc8, 0xca, 0xae, 0x21, 0x58, 0xc2, 0x8d, 0x50, 0xf8, 0xb4, 0x62, 0xe4, 0x27, 0x50, 0x20, 0x40, 0x4c, 0x62, 0x52, 0x0e, 0x18, 0x14, 0xff, 0xc4, 0x90, 0x28, 0xa0, 0x18, 0x17, 0x8d, 0x9a, 0xa4, 0xe0, 0x21, 0x74, 0xbb, 0x9c, 0x0e, 0x54, 0x70, 0xd7, 0x6c, 0xe2, 0x8c, 0x6a, 0x25, 0xb2, 0x31, 0xa2, 0x48, 0x72, 0xae, 0x12, 0x9d, 0x81, 0x66, 0x84, 0x83, 0x5d, 0xcc, 0x49, 0x53, 0xf1, 0xd5, 0x92, 0xcb, 0x2f, 0x98, 0xd5, 0xd9, 0x7c, 0xe7, 0x17, 0x0f, 0xc3, 0xb9, 0x28, 0x32, 0x6f, 0x60, 0x8f, 0x9f, 0xcf, 0xaa, 0xcb, 0x04, 0x91, 0xff, 0x4f, 0xb8, 0xeb, 0x37, 0xbf, 0x99, 0xfc, 0xfa, 0xd7, 0x45, 0x7d, 0x77, 0x98, 0x3a, 0x06, 0x5a, 0xe1, 0x5a, 0xb0, 0x28, 0xb7, 0x4a, 0x0e, 0xca, 0xf0, 0x18, 0x88, + 0x1a, 0x60, 0x87, 0x23, 0x39, 0x1c, 0x0a, 0xe0, 0x6c, 0x13, 0x4e, 0xc4, 0xc6, 0x2b, 0xcb, 0xe2, 0xc3, 0x87, 0x1c, 0xee, 0x3a, 0xa3, 0x27, 0xcc, 0xf1, 0x99, 0x00, 0xcb, 0xdc, 0x21, 0x9c, 0xf0, 0xfa, 0x62, 0x7a, 0x8b, 0x5e, 0x61, 0x8b, 0x35, 0xf1, 0xe2, 0xbb, 0x17, 0x53, 0x37, 0x13, 0xaf, 0x63, 0x5f, 0x26, 0x57, 0x34, 0x94, 0x42, 0x94, 0xe1, 0xcc, 0x56, 0x42, 0x63, 0x5f, 0x3f, 0x78, 0x10, 0x39, 0x26, 0x51, 0x2d, 0x34, 0x38, 0x3f, 0x39, 0x78, 0xaf, 0x1a, 0xf9, 0x81, 0x18, 0x80, 0xdd, 0x4c, 0x14, 0xb1, 0xbe, 0x94, 0x5f, 0x31, 0x09, 0x86, 0xab, 0x42, 0x96, 0xab, 0x46, 0x0c, 0x72, 0xcd, 0x57, 0x37, 0xc3, 0xff, 0xe8, 0xdd, 0x3f, 0xff, 0xf9, 0xc2, 0x37, 0x71, 0x1c, 0xfc, 0xe9, 0xff, 0x22, 0x7f, 0x0e, 0x5a, 0xe1, 0x5c, 0x3b, 0x88, 0x30, 0xd1, 0x80, 0x30, 0x6a, 0xb2, 0x50, + 0x1f, 0xb7, 0x01, 0x1a, 0x78, 0xa0, 0x1a, 0x42, 0xab, 0x18, 0x92, 0xa0, 0xa0, 0x26, 0x2f, 0x53, 0x40, 0x0d, 0x14, 0x05, 0xc5, 0x43, 0x0e, 0x40, 0x01, 0x1a, 0xae, 0x01, 0x0a, 0xf2, 0xc2, 0x86, 0x41, 0x38, 0xe8, 0x68, 0xc4, 0xe5, 0xac, 0x4f, 0x47, 0x1a, 0xa2, 0x0d, 0x7e, 0x9f, 0x33, 0xec, 0x0a, 0x1b, 0xa3, 0x71, 0x25, 0x76, 0x41, 0x49, 0xb5, 0x3c, 0x52, 0x62, 0xd9, 0xdf, 0x1c, 0x22, 0xfa, 0x56, 0x28, 0x5b, 0x57, 0x97, 0x3b, 0x9d, 0xbd, 0x3e, 0x20, 0xea, 0xd2, 0xda, 0xd5, 0x71, 0xa3, 0x45, 0xa5, 0xae, 0xed, 0x08, 0xe6, 0xfc, 0x06, 0x60, 0xf4, 0x25, 0xdd, 0x81, 0xc0, 0xe1, 0xd7, 0x4c, 0x2e, 0x85, 0xd6, 0x65, 0xb0, 0xda, 0xec, 0x77, 0xb0, 0xa2, 0x5b, 0x96, 0x0d, 0x66, 0x74, 0x56, 0x1f, 0xf5, 0xbf, 0x2b, 0xdb, 0xd8, 0x00, 0xe7, 0xd1, 0xca, 0x75, 0xc6, 0xb0, 0xd9, 0x5f, + 0x63, 0x32, 0x45, 0xbd, 0x26, 0x03, 0x78, 0x6b, 0xe6, 0x76, 0xb3, 0x57, 0xa3, 0xb7, 0xe9, 0x59, 0x2b, 0xf9, 0xb5, 0xd9, 0x2b, 0x5a, 0x5a, 0x47, 0xd9, 0x20, 0x5c, 0x47, 0x57, 0xd1, 0x2e, 0xad, 0x63, 0x19, 0x59, 0x01, 0x52, 0x85, 0xcf, 0xb3, 0x88, 0xb2, 0xc1, 0x99, 0xdf, 0xce, 0x5e, 0x44, 0x40, 0x74, 0x91, 0xdf, 0x07, 0x8d, 0xb2, 0xab, 0x08, 0x1b, 0x91, 0x28, 0xd6, 0x28, 0xa5, 0x78, 0xe5, 0x2a, 0xc7, 0x17, 0xb1, 0x16, 0x5b, 0x83, 0x30, 0x5c, 0xa4, 0x8d, 0xb0, 0xb1, 0x5c, 0x34, 0x24, 0xe5, 0xb9, 0xcd, 0xc1, 0xc9, 0x84, 0xc7, 0x15, 0x1e, 0x1c, 0xd0, 0xc8, 0x86, 0x9a, 0xe3, 0xd6, 0x1a, 0x87, 0x8a, 0x31, 0x2a, 0x14, 0x9c, 0xce, 0xce, 0xed, 0x3a, 0x44, 0x7d, 0x72, 0x39, 0xdf, 0x56, 0xe7, 0x51, 0xa9, 0x1b, 0x59, 0x25, 0xe3, 0xf3, 0x05, 0xc9, 0xf7, 0x67, 0x2e, 0x2c, 0xe9, + 0xa8, 0xe0, 0x7a, 0x78, 0x5e, 0x4b, 0x35, 0x40, 0x26, 0x87, 0xaa, 0xc3, 0x86, 0xcf, 0x8c, 0x0c, 0x06, 0xd7, 0x0b, 0x2f, 0x1f, 0xa4, 0xc9, 0x1d, 0xa4, 0x52, 0x98, 0x16, 0xfd, 0xda, 0x6f, 0x53, 0x59, 0xc8, 0x17, 0x32, 0xc4, 0x2b, 0x45, 0x1d, 0x24, 0x30, 0x44, 0x12, 0x1e, 0x33, 0x33, 0x3c, 0x67, 0x25, 0x03, 0xa3, 0x1f, 0x07, 0xaf, 0x93, 0x6b, 0x45, 0x40, 0x52, 0x54, 0xe3, 0x18, 0x52, 0x6c, 0x39, 0x0e, 0xe2, 0x11, 0xbf, 0x30, 0x25, 0x9e, 0x30, 0xcf, 0x9d, 0xdb, 0xaa, 0xef, 0xdc, 0xc6, 0x94, 0x78, 0xc1, 0xe7, 0x79, 0xe7, 0xe7, 0x79, 0x1d, 0x96, 0x33, 0x79, 0x2e, 0xc0, 0x05, 0x90, 0xa5, 0x16, 0xc9, 0x99, 0x9c, 0x98, 0x2b, 0x28, 0x95, 0x90, 0x3d, 0xd3, 0xe7, 0x2d, 0x95, 0x01, 0xa9, 0xf6, 0x7d, 0xff, 0xc8, 0xd9, 0x91, 0x4c, 0xb7, 0x1b, 0x1c, 0x13, 0xf5, 0x2b, 0x56, 0x5b, + 0xd2, 0x43, 0x59, 0x57, 0xc2, 0x61, 0xc0, 0xfe, 0x6f, 0x43, 0x5f, 0xd9, 0xb9, 0x87, 0x7d, 0xe0, 0x53, 0xae, 0x50, 0x26, 0x56, 0x1f, 0xdf, 0xb4, 0x16, 0x79, 0xc1, 0x0b, 0x5e, 0x95, 0xba, 0x95, 0x55, 0xc9, 0x56, 0x80, 0xe2, 0x78, 0x32, 0x81, 0x9c, 0x94, 0xe9, 0x1a, 0x8b, 0x49, 0xb8, 0x5a, 0x14, 0xb0, 0x10, 0x1d, 0xfd, 0x3d, 0xf6, 0x49, 0xf2, 0xc8, 0x17, 0xee, 0x83, 0x84, 0x5b, 0x49, 0x61, 0x3b, 0x0e, 0x89, 0xa1, 0x2b, 0x64, 0x40, 0x32, 0xff, 0x95, 0xbd, 0x93, 0x78, 0xad, 0x78, 0x82, 0xc7, 0x0e, 0xc9, 0x00, 0x2b, 0x39, 0x24, 0x73, 0x9f, 0xe5, 0x8e, 0x3c, 0xec, 0x68, 0x8e, 0xcd, 0xeb, 0x8b, 0x5c, 0xf4, 0xa6, 0x23, 0x78, 0x56, 0x57, 0x24, 0x01, 0x4e, 0x3f, 0x41, 0xbe, 0x2d, 0xc5, 0x33, 0xdc, 0x55, 0x54, 0xa1, 0x58, 0x06, 0x07, 0x2e, 0x63, 0x2d, 0xae, 0xb9, 0xb3, 0x52, + 0x9e, 0x1a, 0x92, 0x01, 0x4a, 0x44, 0x76, 0xc1, 0x01, 0x08, 0x64, 0x69, 0xb1, 0xcf, 0x7a, 0xcb, 0xb6, 0x72, 0x8e, 0xf9, 0xa7, 0xbe, 0xe5, 0x53, 0x5f, 0x80, 0x63, 0x18, 0xfc, 0xd1, 0x48, 0xc0, 0x3c, 0x3b, 0x86, 0xa1, 0xbc, 0x70, 0x67, 0x2c, 0xac, 0x18, 0xc3, 0x90, 0x15, 0xd9, 0x4c, 0xc4, 0x9d, 0xb3, 0x96, 0x56, 0x92, 0x36, 0xe0, 0x95, 0x1c, 0x75, 0x28, 0xca, 0x31, 0x0c, 0xc8, 0xa1, 0x8c, 0x02, 0x17, 0xee, 0xab, 0x5e, 0x45, 0xc1, 0x56, 0x8a, 0xf3, 0x78, 0x9b, 0x7c, 0x1a, 0xcf, 0x4b, 0xb6, 0x58, 0x67, 0x01, 0xa5, 0x32, 0x6c, 0x04, 0x76, 0x88, 0xac, 0x2d, 0xd7, 0xd5, 0x9d, 0x1c, 0xaa, 0xf6, 0x8d, 0x58, 0x82, 0x3c, 0x37, 0x5f, 0x79, 0x6b, 0x71, 0x87, 0x79, 0xcf, 0xac, 0x70, 0xfd, 0xb4, 0x3d, 0xe1, 0xca, 0x0e, 0xa5, 0x2d, 0xe5, 0x00, 0x0b, 0xa5, 0x14, 0x80, 0xd1, 0x67, + 0x30, 0x28, 0x54, 0x06, 0x9a, 0xfc, 0x50, 0xad, 0xf2, 0x16, 0x86, 0x93, 0x20, 0x5d, 0x0e, 0xb1, 0x08, 0x4a, 0xbd, 0x7f, 0x61, 0x85, 0x4c, 0xc5, 0x96, 0x79, 0x07, 0xe9, 0x81, 0xfc, 0x00, 0xc7, 0xcf, 0xb2, 0x32, 0x1c, 0xd3, 0x8a, 0xdd, 0xbe, 0xe3, 0xa2, 0x0b, 0xb2, 0xc4, 0x9f, 0x59, 0x5b, 0x55, 0x94, 0x3c, 0x65, 0x31, 0xb9, 0x81, 0x14, 0xbc, 0x8f, 0xc0, 0x61, 0x48, 0x4f, 0x8f, 0xc1, 0xa4, 0x7a, 0x87, 0xdc, 0xd3, 0xc3, 0xea, 0xd5, 0xbf, 0x27, 0x49, 0xe6, 0x8e, 0x99, 0xbf, 0xaa, 0x34, 0xc0, 0xc9, 0x7a, 0xb4, 0x1f, 0x92, 0x4a, 0x85, 0x56, 0xf8, 0x1f, 0x96, 0xd7, 0xbd, 0x8f, 0x73, 0xa7, 0xd2, 0x50, 0x4e, 0x6e, 0x84, 0x3a, 0x03, 0x0d, 0x39, 0x90, 0x17, 0x45, 0xad, 0x41, 0x76, 0x80, 0x21, 0x1f, 0x50, 0x12, 0x0f, 0x49, 0xc3, 0x63, 0x89, 0x83, 0x3b, 0x2a, 0xbe, 0x5b, 0xad, + 0x06, 0xfe, 0x4d, 0x58, 0xcd, 0x1a, 0xaf, 0xd6, 0xcb, 0xa8, 0x19, 0x35, 0x14, 0x96, 0x69, 0x82, 0xe6, 0x51, 0x35, 0x43, 0x0e, 0x2e, 0x5b, 0x41, 0xb4, 0xd2, 0xca, 0x0b, 0x73, 0xf2, 0xe1, 0x9f, 0xd4, 0x9c, 0xb7, 0x5b, 0x03, 0x14, 0xad, 0x0d, 0x8d, 0x6d, 0x8a, 0x03, 0x28, 0x24, 0x42, 0x8c, 0x61, 0xe8, 0x0e, 0x87, 0xbb, 0xd7, 0xd1, 0x17, 0x5e, 0x77, 0xdd, 0x60, 0x47, 0x57, 0x57, 0xc7, 0x2f, 0x51, 0x7c, 0x44, 0xdb, 0x64, 0x6f, 0x38, 0x2c, 0x7e, 0x86, 0xc4, 0xdc, 0xfe, 0x14, 0xf9, 0x07, 0x4a, 0x2f, 0xdb, 0x45, 0xc8, 0xa0, 0x34, 0x1f, 0x41, 0x32, 0x0c, 0xa1, 0x24, 0x64, 0x4a, 0xd9, 0x6a, 0xf1, 0xdc, 0x89, 0x3d, 0x94, 0x57, 0x87, 0x20, 0x46, 0xc2, 0x36, 0x16, 0x4e, 0x90, 0x89, 0x35, 0x89, 0x26, 0x22, 0x31, 0x24, 0x3d, 0x20, 0xfa, 0xe7, 0x38, 0x51, 0x6d, 0xad, 0xf2, 0xde, + 0xe5, 0x32, 0xe0, 0xad, 0x05, 0x2b, 0x56, 0x2c, 0xb8, 0x1c, 0xff, 0xd3, 0x29, 0xc6, 0x19, 0x77, 0xc2, 0x3f, 0x16, 0x25, 0x93, 0x8b, 0xe0, 0x1f, 0xd4, 0x9f, 0xc8, 0xd1, 0x91, 0x91, 0x65, 0x24, 0xb9, 0x6c, 0x64, 0x64, 0x94, 0x04, 0x8f, 0x83, 0x70, 0xcf, 0x86, 0xd6, 0xb6, 0x0d, 0xbd, 0x61, 0xe4, 0xad, 0x0b, 0xf7, 0x6e, 0x68, 0x6b, 0xdd, 0xd0, 0x13, 0x06, 0xaf, 0xe1, 0xf5, 0x4b, 0x42, 0x1e, 0xfc, 0x34, 0x9c, 0xcf, 0x3e, 0xa2, 0xbf, 0xd8, 0xd3, 0x07, 0x80, 0x02, 0xe5, 0xde, 0xca, 0x03, 0x38, 0x90, 0x0e, 0x5e, 0x87, 0x42, 0x8f, 0x0c, 0x81, 0xd2, 0x13, 0x0c, 0xc5, 0xac, 0x25, 0xa4, 0x42, 0x03, 0x93, 0x43, 0xa2, 0xb5, 0x57, 0xaa, 0x32, 0x09, 0xf5, 0x90, 0x24, 0xd4, 0xb2, 0xfd, 0xa6, 0x64, 0x80, 0xc7, 0x7a, 0xb6, 0xec, 0x0c, 0x67, 0xea, 0xa7, 0x2a, 0xdc, 0xc8, 0xa8, 0x4b, + 0x97, 0x75, 0xaf, 0x2f, 0xa8, 0x58, 0x95, 0xd9, 0xe9, 0xb7, 0xf8, 0x43, 0xa1, 0x42, 0xb1, 0x10, 0x72, 0xe6, 0x17, 0x65, 0xc3, 0x41, 0x93, 0x43, 0xef, 0x54, 0x29, 0xcd, 0x8c, 0xca, 0x91, 0xb2, 0xc6, 0x5a, 0x42, 0x2c, 0xcf, 0x18, 0x74, 0x1a, 0xb3, 0x23, 0xe8, 0x8c, 0x05, 0x42, 0x0d, 0xc5, 0x86, 0x90, 0x3d, 0x33, 0x58, 0x9f, 0x9f, 0x70, 0x33, 0x29, 0x83, 0x4a, 0xad, 0x72, 0x58, 0xcd, 0x56, 0xce, 0xc4, 0x67, 0x63, 0x08, 0x95, 0x43, 0x69, 0xe1, 0x03, 0x72, 0x8d, 0xa9, 0x56, 0xad, 0xf2, 0xd4, 0xb5, 0xfb, 0xda, 0xf3, 0x4a, 0xb5, 0x56, 0xaf, 0xe1, 0x4d, 0x16, 0xa8, 0xb4, 0x07, 0x9a, 0x52, 0x7c, 0x53, 0xd2, 0x11, 0x72, 0x23, 0x5a, 0xf4, 0x24, 0x94, 0x75, 0xcf, 0x97, 0xed, 0x85, 0x5a, 0x37, 0xe4, 0x9f, 0x4e, 0xc8, 0x36, 0x15, 0x58, 0xfb, 0x42, 0x71, 0x5d, 0x00, 0xe5, 0x4d, + 0xcd, 0x09, 0x1e, 0x89, 0x46, 0x24, 0x68, 0xa9, 0x79, 0x60, 0xa6, 0xe7, 0x62, 0xdd, 0xbf, 0x61, 0x4e, 0x0e, 0x64, 0x5d, 0x69, 0x87, 0x5e, 0x64, 0xa5, 0x01, 0xde, 0x5c, 0xdb, 0x2f, 0x7e, 0x67, 0x51, 0x0d, 0xe0, 0x00, 0x4f, 0x3d, 0x52, 0xa2, 0x02, 0x4a, 0x26, 0x1d, 0x25, 0x55, 0xc9, 0x05, 0x12, 0x4d, 0x60, 0x52, 0x51, 0xd4, 0x9a, 0x1f, 0xf2, 0xf6, 0xeb, 0x64, 0x0d, 0x44, 0x8a, 0xb8, 0xbc, 0xa8, 0xf3, 0x42, 0xc5, 0xd0, 0x04, 0x48, 0x39, 0xe2, 0xf1, 0x25, 0x0c, 0xe1, 0x88, 0x58, 0x28, 0x9c, 0x94, 0x03, 0xb9, 0x68, 0x15, 0x81, 0xff, 0xad, 0x2b, 0xd7, 0x0b, 0x9f, 0x1c, 0x52, 0x2a, 0x48, 0xb8, 0x4c, 0x72, 0x9c, 0xcb, 0xf9, 0x19, 0x77, 0x02, 0x54, 0x15, 0x82, 0xc0, 0x77, 0x8f, 0x17, 0x59, 0xd1, 0xdb, 0x1a, 0x60, 0xd9, 0x08, 0x17, 0x8c, 0x86, 0x54, 0xa8, 0xbe, 0xd5, 0x3c, + 0x02, 0x83, 0xac, 0x02, 0xa9, 0xde, 0x0e, 0xf2, 0xcd, 0x38, 0xe5, 0x50, 0xc2, 0xc6, 0x05, 0xd7, 0xb1, 0xa1, 0x96, 0x98, 0x35, 0xe6, 0x50, 0xd3, 0xa8, 0xe0, 0xb1, 0xd6, 0x6a, 0xd6, 0x9b, 0x5a, 0xfa, 0xea, 0xd4, 0xfa, 0xa5, 0x2e, 0xc6, 0xde, 0x7f, 0xcd, 0x35, 0x66, 0xce, 0xcc, 0xaa, 0x68, 0xf5, 0x8d, 0x3e, 0x28, 0x58, 0x28, 0xa1, 0x60, 0xa1, 0x92, 0xb9, 0x03, 0x3c, 0xbd, 0xa8, 0x07, 0x58, 0xf4, 0xba, 0x06, 0xc3, 0x55, 0xd6, 0x3b, 0xa6, 0x84, 0x3f, 0x9b, 0xed, 0x3a, 0xa9, 0x56, 0x03, 0x3c, 0xfb, 0x9b, 0x20, 0x3f, 0x53, 0x10, 0x01, 0xa2, 0xa3, 0xd8, 0xa6, 0x80, 0xab, 0xa1, 0xc7, 0xc6, 0xac, 0xea, 0xda, 0xe2, 0x88, 0x1c, 0xca, 0xc7, 0x51, 0x85, 0xf1, 0x49, 0x6c, 0xe0, 0x10, 0xe3, 0xbb, 0xa8, 0xe1, 0x80, 0x9f, 0xf7, 0x61, 0x4b, 0x67, 0xb9, 0xb4, 0x78, 0x40, 0xdc, 0x94, + 0xed, 0x48, 0x4c, 0x2d, 0x87, 0x53, 0x97, 0xb5, 0x15, 0x2c, 0xb4, 0x7e, 0xe9, 0x69, 0xac, 0x2f, 0xfc, 0x5b, 0x1b, 0x0a, 0xb9, 0x91, 0x68, 0x7b, 0x2e, 0x77, 0xe8, 0xe9, 0xc3, 0x6d, 0xf4, 0x1e, 0x3f, 0x54, 0x1b, 0x2c, 0x43, 0x6f, 0xdb, 0x83, 0xde, 0x30, 0x52, 0x24, 0xfc, 0x6b, 0xbf, 0x07, 0xff, 0x87, 0xcf, 0x54, 0x86, 0x3a, 0x4a, 0x8e, 0x42, 0x9a, 0x98, 0x44, 0x91, 0x26, 0x61, 0x8d, 0x02, 0x76, 0xd1, 0xcd, 0xe2, 0x0a, 0x9f, 0xe5, 0x7c, 0x2e, 0x72, 0x72, 0xa8, 0x94, 0x33, 0x41, 0x41, 0x19, 0x09, 0x10, 0x08, 0xee, 0xde, 0x66, 0x31, 0xa0, 0x2c, 0xe0, 0x24, 0x48, 0xca, 0xcb, 0xa5, 0xea, 0xf2, 0x05, 0x19, 0x8e, 0xd6, 0x80, 0xb3, 0xaa, 0x93, 0xec, 0x89, 0x88, 0x36, 0x40, 0x35, 0xd0, 0x02, 0x49, 0xa8, 0x3f, 0x22, 0x2a, 0x8a, 0xa0, 0xbb, 0x61, 0x5d, 0x6f, 0xa4, 0xbb, 0x29, + 0xc2, 0x77, 0xda, 0x4c, 0x2f, 0x86, 0x1a, 0x06, 0x0b, 0x85, 0xa1, 0xc6, 0xd0, 0x8b, 0x26, 0x5b, 0x67, 0x20, 0xd4, 0xd8, 0x1b, 0xe9, 0x5d, 0x47, 0x1d, 0x45, 0x69, 0x79, 0x5d, 0x9b, 0x6d, 0xb2, 0x1e, 0xa3, 0xf5, 0x56, 0x59, 0x5f, 0x43, 0x7b, 0x4f, 0x4f, 0x7b, 0x43, 0x9f, 0xec, 0x56, 0xab, 0xb1, 0x47, 0x66, 0xdb, 0xdc, 0x55, 0xbf, 0xb4, 0xd5, 0x8f, 0xfb, 0xbe, 0x92, 0xba, 0x9e, 0xf4, 0x62, 0xdd, 0x4b, 0xcc, 0x87, 0x00, 0x62, 0x3d, 0xcf, 0x7f, 0x28, 0x1f, 0xa2, 0xa5, 0xb6, 0xfe, 0x4a, 0x6f, 0xf3, 0xb2, 0x5c, 0x6e, 0x69, 0xb3, 0xf7, 0xca, 0x7a, 0x9a, 0x84, 0xf4, 0xaa, 0x5e, 0xf8, 0x41, 0x7a, 0x49, 0x13, 0xcf, 0x37, 0x2d, 0x49, 0xc3, 0xbf, 0x7f, 0x98, 0xc1, 0x79, 0x79, 0x5d, 0xb0, 0x2d, 0x0f, 0xe4, 0x73, 0x75, 0x28, 0xba, 0x5e, 0x89, 0xbd, 0xe4, 0xa5, 0x9a, 0xe2, 0x93, + 0xf3, 0xe8, 0xfb, 0x95, 0xab, 0xdb, 0xe6, 0xd1, 0xed, 0xcf, 0xf6, 0xec, 0xd9, 0x1e, 0x43, 0xe1, 0x48, 0xd1, 0x9a, 0x48, 0x14, 0x3b, 0xd7, 0x67, 0x43, 0xd9, 0xa1, 0xea, 0xe2, 0x61, 0x5c, 0x5c, 0xdc, 0x82, 0x6b, 0x8b, 0x5b, 0xe6, 0x96, 0x16, 0x37, 0xdb, 0xa3, 0xae, 0x5c, 0x57, 0x58, 0x97, 0x18, 0x9a, 0x6a, 0x5c, 0xb9, 0x45, 0x09, 0x80, 0x7a, 0x0b, 0x67, 0x24, 0x65, 0x8a, 0x5a, 0xde, 0x9a, 0xe0, 0x4d, 0x4a, 0xbd, 0x49, 0xad, 0x85, 0xdc, 0xce, 0x48, 0x32, 0xb7, 0x28, 0xe4, 0xc1, 0xe6, 0xc1, 0xd0, 0x82, 0x4b, 0x46, 0x93, 0xbb, 0x76, 0x84, 0x27, 0xf2, 0x85, 0x4e, 0x8b, 0x3e, 0x93, 0xaa, 0x5f, 0xe6, 0xed, 0xc8, 0xd9, 0xb3, 0x8b, 0x1b, 0x42, 0x71, 0x33, 0x43, 0x21, 0x4e, 0x0a, 0x08, 0x39, 0xe4, 0xa3, 0x6e, 0xac, 0xdf, 0x95, 0xb1, 0x45, 0x70, 0x34, 0x0f, 0x06, 0x18, 0x11, + 0x03, 0x79, 0x58, 0xc2, 0x20, 0x49, 0xd1, 0x38, 0x1f, 0x04, 0x92, 0x52, 0x94, 0x10, 0xd2, 0x8e, 0xf3, 0xbd, 0xdc, 0xdf, 0xdf, 0xff, 0xda, 0x6e, 0xbd, 0x19, 0x8a, 0x72, 0x5a, 0x46, 0xcd, 0x4e, 0xd0, 0x9c, 0x0e, 0xf1, 0x50, 0x52, 0xa9, 0x53, 0x7f, 0xe2, 0xc8, 0x3a, 0xb2, 0xd9, 0x4f, 0xd4, 0x12, 0xce, 0x42, 0x9c, 0x7c, 0x8d, 0x6c, 0x86, 0xed, 0xf0, 0xc8, 0x33, 0x20, 0x16, 0xb3, 0x2e, 0xc9, 0x15, 0x95, 0xc8, 0xba, 0x52, 0xb2, 0x3c, 0x18, 0xe6, 0x4c, 0xa8, 0x26, 0x75, 0x95, 0x54, 0x21, 0xd5, 0xa4, 0xe6, 0x02, 0x22, 0xba, 0x4a, 0xa6, 0x52, 0x92, 0x9a, 0x6c, 0x76, 0xc0, 0x83, 0xde, 0x1c, 0x62, 0x85, 0x5f, 0x2b, 0x95, 0xbf, 0xff, 0x23, 0xb8, 0xde, 0xf8, 0xce, 0xfb, 0x46, 0x87, 0xce, 0xa4, 0x50, 0xb0, 0x34, 0xf9, 0x0b, 0x91, 0xfe, 0x3a, 0x9a, 0x95, 0x2e, 0x35, 0xc8, 0x6c, + 0x6b, 0xf2, 0x99, 0x84, 0x1f, 0xdf, 0xe8, 0xf3, 0x31, 0x4a, 0x16, 0x9e, 0xeb, 0x38, 0x95, 0x25, 0x57, 0xc3, 0x3d, 0xa7, 0x24, 0xd2, 0xc8, 0x0e, 0x98, 0xc0, 0x08, 0xe9, 0x04, 0x08, 0x62, 0xdb, 0xa6, 0x77, 0x9e, 0x9a, 0xd4, 0x93, 0x92, 0xe9, 0x52, 0xac, 0x4c, 0x0d, 0x0f, 0xb7, 0x4a, 0x05, 0x08, 0x55, 0x5a, 0x95, 0x46, 0x88, 0xbf, 0x01, 0x3f, 0x3a, 0x47, 0x72, 0x86, 0x50, 0x02, 0xa5, 0xaa, 0xca, 0x0e, 0x28, 0x13, 0x4b, 0x52, 0x5b, 0xe6, 0x96, 0x1e, 0x0a, 0x54, 0xc6, 0x23, 0x93, 0x53, 0xe0, 0xbe, 0x58, 0x1d, 0xcb, 0x71, 0x2e, 0xcd, 0x46, 0xad, 0x2f, 0x52, 0xeb, 0xec, 0x6e, 0x47, 0xb6, 0x3e, 0x2f, 0x05, 0xd9, 0xa1, 0xc5, 0x92, 0x27, 0x65, 0xd4, 0xc7, 0x70, 0x6c, 0xef, 0x81, 0xa3, 0x2a, 0xc5, 0x3b, 0xd4, 0x69, 0xa7, 0x4d, 0xc7, 0x45, 0x92, 0xe9, 0x62, 0x48, 0xdf, 0x9f, + 0xf5, 0xe6, 0xa3, 0x56, 0x03, 0x8f, 0x4d, 0x7b, 0xd6, 0x1a, 0x53, 0x69, 0x98, 0x7a, 0xb7, 0x42, 0xf8, 0x31, 0x96, 0x75, 0xff, 0x46, 0xbe, 0x0d, 0xbe, 0x05, 0xc7, 0x18, 0x22, 0xa6, 0x8a, 0x2a, 0x3f, 0xa0, 0x51, 0x36, 0x0d, 0x0e, 0xbb, 0x53, 0xe3, 0x42, 0xe2, 0x14, 0x0d, 0xc5, 0xf3, 0xcd, 0x38, 0xeb, 0x1b, 0x79, 0xab, 0x11, 0xb5, 0x5b, 0x5b, 0x02, 0xc4, 0xc7, 0x11, 0xb7, 0xbc, 0x78, 0x0b, 0xa2, 0xd9, 0x67, 0xb9, 0x07, 0x65, 0x0b, 0x9b, 0x82, 0x41, 0x53, 0xa4, 0x8c, 0x8e, 0x53, 0xc2, 0x65, 0x9c, 0x65, 0x3f, 0xca, 0x42, 0x7a, 0x57, 0xaa, 0xbe, 0xbd, 0x66, 0xa5, 0xbf, 0x73, 0xb2, 0xa3, 0x73, 0xdc, 0xe3, 0x53, 0x5b, 0xb5, 0x66, 0x47, 0xc0, 0xd1, 0xd2, 0x59, 0xe8, 0x58, 0xcb, 0x5d, 0xff, 0x36, 0xb8, 0xda, 0xa0, 0xa6, 0x8f, 0xe8, 0x36, 0x0e, 0xa7, 0x47, 0x3b, 0xc2, 0x90, + 0x07, 0x73, 0x6a, 0x8d, 0xba, 0xd8, 0x9c, 0x69, 0x4b, 0x98, 0x0d, 0x5f, 0xdd, 0xd6, 0x64, 0xf4, 0xaa, 0xb1, 0x9c, 0xd3, 0x01, 0xe9, 0xf1, 0x90, 0xec, 0x22, 0x42, 0x05, 0xb7, 0x66, 0x08, 0xd1, 0x3a, 0x84, 0x5a, 0x04, 0x35, 0x77, 0x72, 0xb2, 0x4a, 0x28, 0xab, 0x8a, 0xa2, 0x33, 0x22, 0x60, 0x2b, 0xb7, 0xd3, 0x18, 0xe2, 0x42, 0x6a, 0x56, 0xcd, 0xea, 0xb5, 0xf0, 0x49, 0x55, 0x10, 0x4b, 0x62, 0x62, 0xfc, 0x45, 0x21, 0x27, 0x0a, 0x63, 0x73, 0xc1, 0x89, 0xfe, 0x8d, 0xe4, 0xa0, 0x2a, 0xee, 0x4a, 0xf0, 0x1c, 0x29, 0xef, 0xc8, 0x37, 0x14, 0xe5, 0x55, 0x12, 0x59, 0x4f, 0x38, 0xdc, 0xb3, 0x8e, 0x7e, 0xa3, 0x75, 0x2c, 0x6f, 0xb7, 0xe7, 0xc7, 0x5a, 0xd7, 0xed, 0xda, 0xb0, 0x61, 0x17, 0x68, 0x4f, 0xaf, 0xb9, 0x6e, 0xd5, 0xea, 0xeb, 0xd6, 0xa6, 0xd3, 0x6b, 0xaf, 0x5b, 0xbd, 0xea, + 0xba, 0x35, 0x69, 0xbc, 0xf7, 0xb7, 0xc0, 0x33, 0x96, 0x87, 0xfd, 0x0d, 0x11, 0x77, 0x20, 0x7c, 0x7a, 0x06, 0x65, 0xd9, 0x13, 0x6c, 0x15, 0x8c, 0x8c, 0x5d, 0xc4, 0x68, 0x80, 0x02, 0x22, 0xca, 0x0e, 0x9f, 0x2c, 0xa1, 0xa5, 0x55, 0x34, 0x8e, 0xb3, 0xdc, 0x50, 0xd1, 0x37, 0x3e, 0xe5, 0x0d, 0x9f, 0xf2, 0x30, 0xd6, 0x35, 0xa2, 0x21, 0xee, 0xac, 0xba, 0x46, 0xe0, 0x0c, 0x6f, 0xd1, 0x9f, 0xb0, 0x41, 0x2b, 0x69, 0x3d, 0x12, 0xc1, 0xe2, 0xbb, 0x35, 0x3d, 0x98, 0x29, 0x07, 0x4d, 0x1b, 0xfa, 0x98, 0xad, 0x4e, 0xdf, 0x2c, 0x3d, 0xe3, 0x08, 0x69, 0x4d, 0x0e, 0xe7, 0x25, 0x19, 0x63, 0x05, 0xce, 0x9d, 0x3b, 0x04, 0x56, 0x62, 0x3a, 0xbf, 0xa2, 0xa8, 0x86, 0xf2, 0x04, 0xad, 0x96, 0x91, 0xe2, 0x86, 0x44, 0x9e, 0x04, 0x3b, 0x12, 0xe1, 0x45, 0x9c, 0x9c, 0x52, 0x68, 0x27, 0xd2, 0xe6, + 0xcf, 0x15, 0x93, 0x80, 0x2b, 0x97, 0xb0, 0x2f, 0x01, 0x5f, 0x01, 0x12, 0x86, 0x0e, 0x14, 0xf5, 0xf9, 0x40, 0x88, 0x97, 0x4c, 0x71, 0x7e, 0xa9, 0x4a, 0x38, 0xde, 0x84, 0xa2, 0xbe, 0x08, 0x56, 0x2e, 0x36, 0x1b, 0x68, 0xb9, 0xbd, 0x98, 0x08, 0x5a, 0x94, 0x50, 0x28, 0xf0, 0x6a, 0xc3, 0x49, 0x03, 0x72, 0x4f, 0xeb, 0x4d, 0x99, 0x64, 0x86, 0xdd, 0x6a, 0x64, 0x62, 0x21, 0xd0, 0x22, 0xd6, 0x37, 0x81, 0x72, 0xcf, 0xbb, 0x90, 0x56, 0x05, 0x10, 0x3e, 0xb2, 0x15, 0xf6, 0x51, 0xa7, 0x24, 0x51, 0xc0, 0x3b, 0x21, 0x45, 0x86, 0xcd, 0xed, 0x01, 0xc7, 0xf1, 0x81, 0xc8, 0x6c, 0x62, 0x35, 0xa7, 0xf1, 0x2a, 0xc8, 0x7e, 0xf0, 0xae, 0x23, 0xe9, 0x8d, 0xb5, 0xd6, 0x70, 0xd6, 0xe6, 0x58, 0xd8, 0x54, 0xee, 0xc9, 0x61, 0xb3, 0x15, 0x93, 0x2c, 0x06, 0x92, 0x2c, 0xa5, 0xa7, 0xae, 0x8d, 0x3f, + 0x27, 0x5e, 0x67, 0x90, 0x3a, 0x35, 0xf3, 0x1f, 0x01, 0xb7, 0xa8, 0xf8, 0x90, 0x28, 0xa7, 0x9c, 0x34, 0x63, 0x59, 0xc4, 0x47, 0xe4, 0x8b, 0x19, 0xcd, 0x19, 0xe8, 0xb9, 0x12, 0xdf, 0x94, 0x95, 0x0c, 0xf5, 0x3e, 0x0f, 0xb6, 0x10, 0x72, 0xf3, 0x64, 0x45, 0x16, 0x20, 0x31, 0x12, 0xcf, 0x2b, 0x46, 0x98, 0xc6, 0x32, 0x09, 0x64, 0xf8, 0x80, 0x4d, 0x44, 0x43, 0xb5, 0xf1, 0x57, 0xe5, 0x66, 0x6d, 0x3a, 0x63, 0xcd, 0x2c, 0x6e, 0x7a, 0xe4, 0xa0, 0x43, 0xa7, 0x65, 0x96, 0x2a, 0xf9, 0x0b, 0xa9, 0x4f, 0x02, 0x6e, 0x4f, 0xfc, 0xd0, 0x5f, 0x0d, 0x66, 0xb9, 0x79, 0x61, 0x3d, 0xdf, 0x14, 0xb7, 0x93, 0xca, 0xa7, 0xe4, 0xea, 0x23, 0x76, 0x3c, 0x6f, 0x2f, 0x53, 0xdf, 0x27, 0x8d, 0xb2, 0x41, 0x48, 0x4f, 0x6d, 0x45, 0x33, 0x32, 0xd0, 0x11, 0x28, 0x75, 0x76, 0x0b, 0x20, 0x14, 0x32, 0x44, + 0x14, 0x29, 0xdc, 0xb6, 0x45, 0x04, 0x93, 0xe1, 0xc0, 0xbf, 0x46, 0x82, 0x86, 0xa7, 0x65, 0x36, 0x6e, 0xf4, 0x0a, 0xd9, 0x60, 0x3e, 0x2c, 0xd0, 0x5a, 0x1b, 0xb9, 0x5f, 0x94, 0xb7, 0xe0, 0x7b, 0xa8, 0x3b, 0xf0, 0x7b, 0x38, 0x22, 0x50, 0xf4, 0x31, 0x24, 0x8d, 0x5c, 0x10, 0x08, 0xc8, 0x04, 0x6c, 0x51, 0x43, 0x8a, 0x6b, 0x34, 0xa8, 0x38, 0x35, 0x27, 0xbe, 0x54, 0x36, 0xeb, 0xa5, 0xfc, 0x7c, 0xaf, 0x27, 0xb5, 0xf3, 0xb4, 0x54, 0x69, 0x11, 0x9d, 0xcf, 0xa7, 0xc1, 0x3a, 0x19, 0xa2, 0x26, 0xb2, 0xaf, 0xb3, 0x1a, 0x90, 0x46, 0x54, 0x0c, 0x6e, 0x78, 0xc8, 0xe5, 0x3c, 0x40, 0x0f, 0x6a, 0xc9, 0x7c, 0x01, 0xc4, 0x4d, 0x1a, 0x9d, 0xcf, 0xa4, 0x09, 0xd9, 0x95, 0x34, 0xdb, 0xd2, 0xb9, 0xc8, 0xe6, 0x49, 0x30, 0xff, 0xa5, 0x08, 0x5a, 0xf3, 0xbb, 0x36, 0x8f, 0x7b, 0x54, 0x1e, 0x75, + 0xb8, 0xf1, 0x86, 0x2f, 0xde, 0x89, 0x19, 0xe7, 0x7a, 0x7a, 0x0b, 0x48, 0x33, 0xbf, 0x16, 0x71, 0x11, 0x6d, 0x52, 0x70, 0x38, 0xdc, 0x3d, 0x6b, 0x4b, 0xbe, 0xef, 0x8d, 0x84, 0x14, 0x1c, 0x2e, 0x16, 0x9a, 0x46, 0xc9, 0x77, 0xcb, 0x25, 0xd7, 0x18, 0x20, 0x47, 0xc6, 0x9f, 0x82, 0xea, 0x98, 0x98, 0x39, 0x2a, 0xa6, 0xf7, 0x98, 0x4b, 0xc6, 0x42, 0x90, 0xae, 0xcd, 0x66, 0x6b, 0x63, 0x43, 0x8d, 0x3c, 0xdf, 0x38, 0x14, 0x63, 0xae, 0xcd, 0x27, 0xe2, 0xf9, 0x55, 0xae, 0xba, 0xde, 0x58, 0xbc, 0x37, 0xe3, 0x12, 0x79, 0xec, 0x3a, 0xd8, 0x76, 0x6a, 0x76, 0xdb, 0x88, 0x5c, 0x52, 0x6b, 0x4b, 0xae, 0xb7, 0x8d, 0x00, 0xb7, 0x7d, 0xb6, 0xa0, 0xbf, 0x72, 0xdb, 0x5c, 0x35, 0x68, 0x07, 0x24, 0x87, 0xeb, 0xaa, 0x9b, 0xa6, 0x5f, 0xcd, 0xc7, 0x13, 0xf9, 0x35, 0xae, 0x4c, 0x6f, 0x3c, 0xd6, + 0x5b, 0xe7, 0x42, 0x31, 0x51, 0x90, 0xbe, 0x11, 0xb2, 0x0b, 0xa1, 0x5e, 0x9c, 0x2c, 0xc6, 0x0c, 0xda, 0xd9, 0xb5, 0xee, 0x01, 0xd8, 0x45, 0xa0, 0x1d, 0xb9, 0x5c, 0x0a, 0x9b, 0x23, 0xc8, 0x91, 0x40, 0xd4, 0x26, 0x2a, 0x31, 0x95, 0xcc, 0x1a, 0xb3, 0x58, 0xee, 0xbe, 0x94, 0x41, 0x13, 0xb0, 0xe4, 0x1a, 0x1a, 0xec, 0xa8, 0x26, 0xd1, 0x9a, 0x55, 0x87, 0xb3, 0xfd, 0x09, 0x53, 0x77, 0xd1, 0x13, 0xe7, 0x1d, 0x1a, 0x56, 0x21, 0xf3, 0xa2, 0x84, 0x24, 0x14, 0x6a, 0xb8, 0xf4, 0x5a, 0xdf, 0x07, 0xa0, 0x20, 0xf7, 0xa7, 0x9a, 0x7c, 0xfd, 0xbd, 0x3a, 0x83, 0x4e, 0xf4, 0x1d, 0x34, 0xc3, 0xbe, 0xd8, 0x71, 0x5f, 0xa0, 0xfe, 0xab, 0x85, 0x74, 0xd6, 0x3d, 0xa7, 0xc6, 0x3d, 0x45, 0x9d, 0xd1, 0x1f, 0x5b, 0x20, 0x8c, 0xa3, 0xf1, 0xb9, 0x4a, 0x24, 0x78, 0xb9, 0x68, 0x8d, 0x4c, 0x2a, 0x73, + 0xff, 0x97, 0x89, 0x75, 0x35, 0x7d, 0xeb, 0x61, 0x9f, 0x1a, 0x72, 0x96, 0xa8, 0x82, 0xd5, 0x38, 0xf8, 0xb8, 0xa7, 0xd8, 0x6d, 0x4a, 0xf6, 0x65, 0x0f, 0xd3, 0x57, 0xfb, 0xae, 0x5d, 0xda, 0xb2, 0xaa, 0xcd, 0xa7, 0x30, 0xf9, 0x1d, 0x19, 0x2d, 0xec, 0x4a, 0x6f, 0xbf, 0xaf, 0x29, 0xe5, 0x97, 0x0b, 0xdf, 0x15, 0xd7, 0x65, 0x05, 0x95, 0xa0, 0x56, 0xc0, 0x33, 0x5b, 0x85, 0xb7, 0x84, 0x13, 0xea, 0xf7, 0x88, 0xa6, 0x64, 0x36, 0x29, 0xa2, 0x35, 0xcd, 0x83, 0xb7, 0xb4, 0xe2, 0x0a, 0xa3, 0x43, 0x71, 0xa7, 0xce, 0x0c, 0xf5, 0x37, 0xcd, 0xad, 0x72, 0xa7, 0xf9, 0x10, 0x18, 0x9c, 0xa6, 0x12, 0x26, 0x03, 0xc8, 0xba, 0xeb, 0x1d, 0x8e, 0x7a, 0xb7, 0xf0, 0x9a, 0xde, 0x8c, 0x32, 0x5c, 0x2b, 0x6d, 0x60, 0x39, 0x4e, 0xf5, 0xe9, 0x72, 0x5c, 0x19, 0x23, 0xae, 0x9c, 0xca, 0x4b, 0xad, 0x10, + 0x9e, 0x9a, 0x16, 0x1e, 0xc5, 0xad, 0x39, 0x0d, 0x52, 0x63, 0x90, 0x38, 0xde, 0x40, 0xee, 0x34, 0x19, 0x84, 0xef, 0x37, 0xa6, 0xed, 0x19, 0x37, 0xc8, 0xe8, 0xcd, 0xe2, 0x78, 0x1e, 0xa7, 0x6f, 0x05, 0xed, 0xb2, 0x85, 0x12, 0xae, 0x05, 0x52, 0x4e, 0x47, 0x25, 0xeb, 0x3d, 0x01, 0x4a, 0xb8, 0x16, 0x19, 0x36, 0xf0, 0xf8, 0xf5, 0xd7, 0xcb, 0x16, 0x0a, 0x83, 0xe2, 0x99, 0x7e, 0x06, 0xfe, 0x63, 0xc7, 0x78, 0x50, 0xfa, 0xb3, 0xa2, 0xb4, 0x71, 0x1c, 0x67, 0xc4, 0x5b, 0xbf, 0x0d, 0x27, 0x1a, 0xf3, 0x9c, 0x0e, 0xa0, 0x29, 0xe1, 0x41, 0x97, 0x02, 0x8a, 0xc4, 0xf2, 0x83, 0xc2, 0x97, 0x4f, 0x89, 0x7f, 0x80, 0xd5, 0x40, 0xad, 0x87, 0xff, 0x13, 0x3e, 0x24, 0xdf, 0x45, 0x9f, 0x40, 0x8d, 0xfb, 0xf5, 0x0c, 0xb8, 0x8c, 0xb4, 0x93, 0x0f, 0xc0, 0x36, 0x66, 0xe1, 0x6d, 0x88, 0x9a, 0x33, + 0x14, 0x94, 0x91, 0x08, 0x5b, 0x7a, 0xe5, 0x4d, 0xe5, 0x37, 0x7d, 0x51, 0x7a, 0x03, 0x20, 0x8e, 0x82, 0x16, 0x72, 0x1f, 0xf9, 0x07, 0xf8, 0xfc, 0x02, 0x91, 0x2f, 0x3b, 0x30, 0x1c, 0xe3, 0x34, 0x4e, 0x6a, 0x81, 0x2f, 0x43, 0x9f, 0x00, 0x41, 0x4f, 0x00, 0x62, 0x21, 0x64, 0xab, 0xe8, 0xc0, 0xee, 0x9e, 0xef, 0x9a, 0x98, 0xf0, 0x22, 0x27, 0xe4, 0x50, 0xef, 0xc3, 0xc6, 0x27, 0x33, 0x8f, 0x43, 0x59, 0xc8, 0x7d, 0x82, 0xf1, 0x91, 0x9b, 0xc0, 0xb7, 0x0f, 0x1c, 0x00, 0x19, 0xe1, 0xfb, 0xe2, 0x5c, 0x56, 0xda, 0xec, 0x10, 0xdb, 0xb4, 0xe2, 0x8e, 0x6f, 0x3a, 0x23, 0xa3, 0x1c, 0x29, 0x14, 0xf3, 0xa6, 0x9a, 0x8f, 0x3f, 0x75, 0x46, 0xb2, 0xf9, 0xbe, 0x47, 0x6e, 0x7a, 0x04, 0xfc, 0x81, 0xdc, 0x29, 0xbc, 0x06, 0xb2, 0x97, 0xce, 0x6e, 0x47, 0x4d, 0x84, 0x8b, 0x01, 0xec, 0xd0, 0x18, + 0xad, 0xf2, 0xa2, 0xd0, 0x34, 0x5a, 0x03, 0x1a, 0x2c, 0x98, 0xdf, 0x89, 0x82, 0x5f, 0x88, 0xde, 0x79, 0xea, 0xf6, 0xdb, 0x0f, 0xdc, 0x76, 0x9b, 0xf8, 0xbe, 0x53, 0xf0, 0x7d, 0xa3, 0x9f, 0xba, 0x07, 0x78, 0x33, 0x7f, 0x14, 0xfc, 0x41, 0x30, 0x92, 0xa3, 0x07, 0xa4, 0x3e, 0xdc, 0x49, 0x9e, 0xa4, 0xfc, 0x84, 0x9d, 0x58, 0x2b, 0xda, 0x0f, 0xb4, 0x7a, 0x94, 0x0d, 0x31, 0x68, 0xb7, 0x42, 0xee, 0x45, 0xf4, 0x3b, 0x67, 0x7d, 0x07, 0xfd, 0x52, 0x4e, 0xaf, 0x11, 0x60, 0xe8, 0xaa, 0xe5, 0x00, 0x94, 0x0f, 0xac, 0x13, 0x2f, 0x2f, 0x4a, 0x04, 0x9a, 0x7d, 0x90, 0xc7, 0x9f, 0x0c, 0xf1, 0x11, 0xec, 0xc6, 0x08, 0x59, 0x2d, 0x62, 0xa6, 0xec, 0x6c, 0xdb, 0x8f, 0xfc, 0x28, 0xa0, 0x59, 0x75, 0xa2, 0xe0, 0xf3, 0xab, 0xd5, 0x0e, 0x19, 0xe7, 0x5e, 0x90, 0x69, 0x5d, 0x9c, 0x36, 0x7d, 0x89, + 0x7c, 0x7e, 0x32, 0x64, 0xb3, 0xb7, 0x64, 0x7a, 0x74, 0x3a, 0x7b, 0x93, 0xce, 0x1a, 0x69, 0xee, 0xf5, 0xe9, 0x5b, 0xc5, 0x7d, 0x5b, 0x4b, 0xfe, 0x82, 0xec, 0x81, 0xb2, 0x80, 0x96, 0xc8, 0x12, 0xa1, 0xa2, 0x3f, 0x1b, 0x0f, 0xb8, 0xb5, 0x18, 0x1b, 0x02, 0x8f, 0x08, 0xac, 0x45, 0x22, 0x00, 0x3c, 0xc3, 0x99, 0xba, 0x50, 0xd0, 0x6e, 0x0d, 0xd1, 0x28, 0xcc, 0xb7, 0x4a, 0x5f, 0x4e, 0x81, 0xc2, 0x6c, 0x14, 0x88, 0x76, 0x20, 0x6a, 0x50, 0xe8, 0xe4, 0x49, 0xf1, 0xe1, 0x20, 0xa1, 0x52, 0x9b, 0x0c, 0x5a, 0x93, 0x46, 0xa9, 0x5d, 0xa7, 0x65, 0x55, 0x0c, 0xeb, 0xcf, 0x07, 0x43, 0xb9, 0x80, 0x81, 0x56, 0xb3, 0x5a, 0xd7, 0xbf, 0xed, 0xd6, 0x59, 0x99, 0x8d, 0x2a, 0x86, 0x52, 0x30, 0x1b, 0x19, 0x9d, 0x6e, 0xd7, 0x77, 0xa8, 0xa7, 0xcc, 0x61, 0x9d, 0xc9, 0x65, 0x71, 0x70, 0x41, 0x93, + 0x33, 0xe8, 0x08, 0xba, 0x1d, 0x7a, 0xe4, 0xde, 0xb7, 0x46, 0x73, 0x1e, 0xbd, 0xdd, 0x13, 0xb4, 0xe7, 0xc0, 0x6e, 0x9d, 0xfa, 0x87, 0x1c, 0xa7, 0x36, 0x2a, 0x7f, 0xa8, 0xd6, 0x09, 0xc7, 0x50, 0x2f, 0xef, 0x82, 0x03, 0x59, 0x87, 0xeb, 0x4e, 0x89, 0x7e, 0x12, 0xd1, 0x45, 0x32, 0x59, 0x41, 0x8f, 0x94, 0x97, 0xfd, 0x24, 0x22, 0xc4, 0x6a, 0x8e, 0x5c, 0xf7, 0x45, 0xe1, 0x2d, 0xc0, 0x33, 0x27, 0xff, 0x3e, 0xc5, 0x28, 0x8e, 0x83, 0x59, 0xef, 0x80, 0x1c, 0x59, 0xe2, 0x20, 0xeb, 0x25, 0x6b, 0x70, 0xe5, 0x35, 0xe5, 0x1d, 0x2f, 0xbe, 0x87, 0xad, 0x7a, 0x0f, 0xf5, 0x9d, 0xe3, 0xa0, 0x4b, 0xdc, 0x8b, 0xff, 0xaf, 0xdf, 0x45, 0x1e, 0x3c, 0x4e, 0x5e, 0xf5, 0xff, 0xd1, 0xbb, 0x40, 0xe7, 0x71, 0xea, 0x5f, 0x67, 0xbf, 0x0b, 0xfb, 0x93, 0xa4, 0x29, 0x12, 0x0f, 0xf5, 0xdc, 0x9a, 0xf2, + 0x40, 0x7a, 0x8b, 0xd0, 0x21, 0xbe, 0xe4, 0x38, 0x83, 0x7c, 0x94, 0xd7, 0x11, 0x57, 0x90, 0x0f, 0x53, 0xe7, 0xc3, 0xdb, 0x42, 0x28, 0x0d, 0x78, 0x5e, 0xc8, 0x9a, 0x79, 0xf0, 0x6a, 0x1e, 0x9e, 0x79, 0xfb, 0x26, 0x40, 0x5e, 0x46, 0xfe, 0x69, 0xe6, 0x7a, 0xb1, 0x1f, 0xa5, 0xf7, 0xc8, 0x09, 0x7f, 0xd1, 0x8b, 0xd9, 0xd7, 0xa8, 0x24, 0x2f, 0x20, 0x5b, 0x33, 0x3a, 0x63, 0x24, 0x58, 0x30, 0x0b, 0x16, 0x37, 0xc3, 0xf2, 0xe4, 0xc3, 0xc2, 0xee, 0x9b, 0x6e, 0x02, 0x47, 0xc1, 0x33, 0xc2, 0x77, 0xc8, 0x3f, 0x09, 0xdf, 0x39, 0x7d, 0x9a, 0x48, 0x9f, 0xe6, 0x48, 0x23, 0x48, 0x40, 0x76, 0xd2, 0x71, 0xfa, 0xf4, 0xc7, 0xd7, 0x4a, 0x31, 0xf6, 0xd7, 0x8a, 0x31, 0xf6, 0xf0, 0xfa, 0x85, 0x74, 0x9a, 0x7c, 0x40, 0xf6, 0x2d, 0x32, 0x4c, 0x3c, 0x4d, 0xdc, 0x43, 0x44, 0x98, 0xc6, 0xd3, 0x13, + 0xc4, 0x47, 0x40, 0xce, 0x34, 0x13, 0x2f, 0x12, 0x1f, 0x11, 0xf6, 0xc7, 0x08, 0xf0, 0xcd, 0xd3, 0xf7, 0xc0, 0xbb, 0x1f, 0x03, 0xe0, 0xd1, 0x8f, 0xd0, 0x27, 0x29, 0x7e, 0xa2, 0x3e, 0x42, 0x69, 0x9c, 0xfe, 0x05, 0x73, 0x3f, 0xe1, 0x27, 0x1e, 0x7e, 0x12, 0x21, 0x9e, 0x95, 0x50, 0xb8, 0x8d, 0xc8, 0xc8, 0x24, 0x42, 0x26, 0x66, 0x50, 0xfa, 0x76, 0x82, 0xaa, 0x24, 0x0c, 0x96, 0xaf, 0xe4, 0x2a, 0x57, 0xd8, 0xb3, 0x3e, 0x63, 0x3e, 0xeb, 0x33, 0xd6, 0xb3, 0x3c, 0x33, 0xdf, 0xed, 0x28, 0xda, 0x61, 0xfc, 0x1b, 0x11, 0xab, 0x3f, 0xca, 0x33, 0x72, 0x7b, 0x9c, 0xcb, 0x4a, 0xe9, 0x47, 0x99, 0x7a, 0x64, 0xc6, 0xd2, 0x51, 0xa2, 0xb6, 0x43, 0xa2, 0xdc, 0x24, 0xeb, 0x05, 0x23, 0x27, 0xae, 0xd8, 0xec, 0xf5, 0x6e, 0xbe, 0xe2, 0xc4, 0xc8, 0x05, 0xfd, 0xd9, 0xad, 0xeb, 0x96, 0xb5, 0x05, + 0x7e, 0x1a, 0x68, 0x5b, 0xb6, 0x6e, 0x6b, 0xb6, 0x9f, 0xd9, 0x1f, 0x6b, 0x39, 0xfa, 0xee, 0x63, 0x8f, 0xbd, 0x7b, 0xb4, 0x25, 0x76, 0x4d, 0x78, 0xf4, 0xda, 0x97, 0x04, 0xe1, 0x1e, 0xe1, 0x7c, 0x70, 0xe4, 0x1e, 0x40, 0xbe, 0x74, 0xed, 0x68, 0x58, 0x5c, 0xb7, 0xc7, 0xe0, 0x9c, 0xc4, 0xe1, 0xfe, 0xb1, 0x21, 0xbf, 0x97, 0x5c, 0xcc, 0x9c, 0x29, 0x89, 0x1d, 0x62, 0xd1, 0xac, 0xc9, 0x72, 0x95, 0x95, 0xb8, 0xd4, 0x3f, 0xc9, 0x2d, 0xca, 0x06, 0xa3, 0xbc, 0x5c, 0x8e, 0x0b, 0xac, 0x94, 0x21, 0x53, 0x6a, 0xa9, 0x00, 0x06, 0x47, 0x34, 0x83, 0x6b, 0xc0, 0xf8, 0xc0, 0xfa, 0x26, 0x2b, 0x14, 0xa0, 0xd9, 0x56, 0xa0, 0xf7, 0x37, 0xc4, 0x84, 0xaf, 0x9d, 0xa2, 0x2e, 0xa6, 0xff, 0x28, 0x78, 0xec, 0xf5, 0x0b, 0x32, 0x1a, 0xab, 0x49, 0xe3, 0x0a, 0x5b, 0x95, 0x33, 0x5f, 0x62, 0x4e, 0x7e, + 0x15, 0x6d, 0xb2, 0xc5, 0x70, 0x8f, 0x25, 0x99, 0xaf, 0x40, 0xe9, 0x39, 0x46, 0x7c, 0xff, 0xa9, 0xa8, 0x89, 0x44, 0xf8, 0x29, 0x25, 0x6b, 0x95, 0x24, 0x0a, 0x66, 0x70, 0xa4, 0x2d, 0xe6, 0xc1, 0x95, 0x85, 0x28, 0x5f, 0xcd, 0xcd, 0xbd, 0x6a, 0xfe, 0xd4, 0x67, 0x1d, 0x9f, 0xfa, 0xac, 0xeb, 0x53, 0x9e, 0x3d, 0xdb, 0x63, 0x78, 0xd9, 0x10, 0xfc, 0x72, 0xac, 0xc6, 0xeb, 0x56, 0x2b, 0x71, 0x04, 0xb0, 0xbc, 0x82, 0xb4, 0x53, 0x82, 0x06, 0x93, 0xcf, 0xc1, 0xdb, 0x64, 0x03, 0xe4, 0xf3, 0xab, 0xbf, 0xb0, 0xa5, 0xb1, 0x71, 0xcb, 0x17, 0x56, 0x4b, 0x9f, 0xba, 0xf3, 0x9f, 0xbb, 0x66, 0x60, 0xe5, 0x3d, 0xbf, 0x3a, 0x72, 0xe4, 0xd7, 0x5f, 0x5e, 0x39, 0x70, 0xcd, 0xf3, 0xe7, 0xbf, 0x0e, 0xde, 0xce, 0x6f, 0xbc, 0x71, 0xd5, 0xc4, 0x8d, 0x53, 0xf9, 0xfc, 0xd4, 0x8d, 0x13, 0xab, 0x6e, + 0xdc, 0x98, 0x7f, 0xcc, 0xb0, 0xe1, 0x96, 0x97, 0x76, 0xec, 0xfb, 0xc9, 0xed, 0xcb, 0x97, 0xdf, 0xfe, 0x93, 0x7d, 0x3b, 0x5f, 0xfa, 0xc2, 0x06, 0x03, 0x36, 0x9f, 0xc0, 0x35, 0xe5, 0x10, 0x0e, 0x25, 0x5c, 0x53, 0x2d, 0x46, 0x28, 0x12, 0x43, 0xa1, 0x36, 0xc0, 0xee, 0xc6, 0x11, 0x79, 0x49, 0x20, 0x2c, 0x2e, 0xce, 0x47, 0xcb, 0x6d, 0x18, 0x2d, 0xb3, 0xc0, 0xf1, 0x48, 0xae, 0x92, 0x53, 0x3c, 0x45, 0x1e, 0xf6, 0xb8, 0x75, 0x1f, 0x7f, 0xf2, 0x7d, 0x30, 0x7d, 0xc8, 0x0b, 0xff, 0xa0, 0x32, 0x51, 0x3f, 0x99, 0xa2, 0x02, 0x51, 0xff, 0xcc, 0x8f, 0xcb, 0x7b, 0x85, 0xf9, 0x2b, 0x7c, 0x6f, 0x18, 0xd9, 0xc3, 0xdc, 0x7a, 0xd9, 0x19, 0x98, 0x54, 0x54, 0x1c, 0x83, 0x65, 0x26, 0x30, 0xe1, 0x09, 0x13, 0x21, 0xde, 0x5a, 0x60, 0x65, 0x72, 0x09, 0x8c, 0x2a, 0x2f, 0x02, 0x2f, 0x21, 0x9b, + 0x60, 0x05, 0x8f, 0x0a, 0x88, 0x78, 0x54, 0x28, 0x23, 0x82, 0xf9, 0xeb, 0xf7, 0x29, 0x5a, 0xd8, 0xc3, 0xfa, 0x1d, 0x32, 0x7b, 0xc0, 0x20, 0xec, 0xa6, 0xe9, 0x1f, 0x3c, 0xe1, 0x72, 0x1a, 0xc1, 0x15, 0x6a, 0x16, 0x8a, 0x92, 0x6a, 0x70, 0xb9, 0x52, 0x67, 0xe0, 0x74, 0x88, 0x72, 0xd1, 0x4e, 0x57, 0x27, 0x2f, 0x98, 0x2c, 0x4e, 0xa7, 0x05, 0xbc, 0xeb, 0xef, 0x74, 0x7d, 0xfc, 0x2b, 0xe6, 0xa4, 0xce, 0x22, 0xe4, 0x74, 0x36, 0x8d, 0xd6, 0xae, 0x03, 0xdf, 0x55, 0xeb, 0x90, 0xa9, 0x0b, 0x63, 0x74, 0x33, 0xbd, 0xcc, 0x43, 0x44, 0x3d, 0x31, 0x5a, 0xd4, 0xeb, 0x10, 0x1c, 0x67, 0x7d, 0x5d, 0xaa, 0x36, 0xc9, 0x88, 0x31, 0x9d, 0x2c, 0x46, 0x1e, 0x01, 0xb8, 0x18, 0xc6, 0xce, 0x52, 0x7e, 0x4c, 0xe5, 0x5c, 0x9e, 0x71, 0xa9, 0x7c, 0x3a, 0x9f, 0x8a, 0x84, 0xb9, 0x30, 0x9a, 0x3f, 0x10, + 0xc2, 0x75, 0x52, 0x91, 0xd5, 0x41, 0x26, 0xa1, 0x57, 0xd0, 0x16, 0xa3, 0xd9, 0x44, 0xca, 0x21, 0xeb, 0x04, 0xf9, 0x82, 0x88, 0xb2, 0x0f, 0x45, 0x63, 0x26, 0x02, 0xe0, 0xa8, 0x21, 0x55, 0xbb, 0xb3, 0x79, 0x4b, 0xa6, 0xe5, 0xa6, 0x15, 0x57, 0xbc, 0x35, 0xcd, 0xc7, 0x8c, 0xdd, 0xfd, 0x47, 0x6f, 0xb9, 0x7b, 0xd5, 0x43, 0x40, 0xf9, 0xe4, 0x9a, 0xab, 0xae, 0xbb, 0xc2, 0xe4, 0x50, 0x2b, 0x74, 0xe1, 0x65, 0x7d, 0x29, 0x00, 0x22, 0xab, 0x9a, 0x85, 0x2d, 0x2b, 0xc8, 0x63, 0xce, 0xd4, 0xcc, 0xb9, 0xd4, 0xf0, 0x47, 0xfe, 0x3a, 0x53, 0x38, 0xda, 0xdc, 0xc8, 0x02, 0xfb, 0xa6, 0x64, 0x4f, 0xd2, 0x3a, 0xf9, 0xb4, 0xf0, 0x97, 0xfb, 0xbe, 0x29, 0xdc, 0xf0, 0x0d, 0xce, 0x1d, 0x64, 0x13, 0x57, 0x7c, 0xf9, 0xb9, 0x2d, 0x3a, 0x97, 0x2e, 0xd0, 0x1d, 0x78, 0xef, 0x46, 0x70, 0x70, 0x49, + 0xbb, 0x70, 0xb1, 0x24, 0xbf, 0x0b, 0x5f, 0xa2, 0x0f, 0x31, 0x7f, 0x83, 0x12, 0xc0, 0x43, 0x4f, 0x40, 0x31, 0x92, 0xe8, 0x13, 0xcf, 0x12, 0x8b, 0x05, 0xc0, 0x72, 0xd1, 0xc8, 0x0c, 0x51, 0x3a, 0x46, 0x73, 0x2e, 0xe4, 0x88, 0xd2, 0x09, 0x9a, 0xf7, 0x09, 0xc7, 0xd9, 0x9e, 0x70, 0xcd, 0xff, 0xc4, 0x3c, 0x37, 0x8b, 0xa7, 0x45, 0x0b, 0x88, 0x78, 0x2c, 0x1c, 0x24, 0xb2, 0x20, 0x4b, 0xa3, 0xb3, 0x22, 0x9d, 0x11, 0x2b, 0x92, 0x40, 0x70, 0x38, 0x25, 0x8e, 0x9d, 0x47, 0x27, 0xc6, 0x98, 0x0f, 0x62, 0xb3, 0x57, 0xe9, 0xdc, 0x50, 0xd6, 0x6d, 0x5f, 0xbf, 0xbc, 0x6f, 0xf1, 0x91, 0xa7, 0x37, 0xad, 0xdd, 0xd5, 0x62, 0x4c, 0x46, 0x6f, 0xe5, 0x42, 0xca, 0xd0, 0x60, 0xc7, 0x15, 0xbf, 0x7d, 0x6c, 0xf3, 0xa6, 0x6f, 0x01, 0xea, 0x64, 0xef, 0x8a, 0x8c, 0x95, 0x5e, 0xc6, 0x9a, 0x54, 0x64, + 0x78, 0xf9, 0xf5, 0xf4, 0x7b, 0x3d, 0x07, 0x5f, 0xdc, 0x77, 0xe0, 0x5b, 0x07, 0xc7, 0x9c, 0xb6, 0xb6, 0xbe, 0x05, 0x91, 0x81, 0x2b, 0x8b, 0x2d, 0xbe, 0xa5, 0x35, 0xe6, 0x70, 0x90, 0x67, 0x7d, 0x7b, 0x9f, 0xfa, 0xf0, 0x8b, 0x5f, 0x04, 0xd4, 0xd3, 0x1b, 0x95, 0x9c, 0xd7, 0xa2, 0x31, 0x9a, 0x14, 0xf5, 0x0b, 0xb3, 0x4e, 0xc4, 0x6b, 0xb6, 0x9f, 0xfe, 0x80, 0x0e, 0x31, 0x5f, 0x25, 0x65, 0xc4, 0x4f, 0xf0, 0x9c, 0x8e, 0x9c, 0xfe, 0x80, 0xda, 0x42, 0x2b, 0x88, 0x34, 0xf1, 0x00, 0x4e, 0xef, 0x78, 0xd2, 0x84, 0x63, 0x52, 0x9c, 0xf0, 0x13, 0xa1, 0xe9, 0x8f, 0x8b, 0x0a, 0xac, 0x97, 0x10, 0xe1, 0xc8, 0xd7, 0x97, 0x41, 0x6a, 0xf2, 0xb8, 0x02, 0x25, 0x36, 0x60, 0xa5, 0xca, 0x09, 0xd8, 0x61, 0x42, 0xce, 0xc8, 0x18, 0xb9, 0x0c, 0xf9, 0xd3, 0x51, 0xa0, 0x6e, 0xf5, 0xed, 0x44, 0xd5, + 0xdd, 0xc5, 0x64, 0x39, 0xb0, 0x73, 0xce, 0x03, 0xa5, 0xa2, 0x2e, 0xb3, 0xee, 0xc7, 0x71, 0x72, 0x7e, 0xd6, 0x14, 0xb4, 0x46, 0x33, 0x0a, 0xb9, 0x2b, 0x1e, 0x2a, 0x81, 0x6c, 0x94, 0x74, 0xc4, 0x4a, 0x76, 0x8e, 0x1c, 0xca, 0xba, 0x62, 0x86, 0x2e, 0xce, 0xb4, 0xd9, 0xe2, 0x1b, 0xe8, 0xef, 0x71, 0x5f, 0xf4, 0xc2, 0xd5, 0xfd, 0x1d, 0x57, 0xff, 0xe0, 0x3a, 0x4f, 0x21, 0xdf, 0xe0, 0x53, 0x33, 0x01, 0xbb, 0xce, 0x5b, 0x93, 0x0f, 0xf6, 0xef, 0x5a, 0x94, 0x56, 0x90, 0x9a, 0x99, 0xf7, 0x75, 0xf7, 0xdf, 0x94, 0x6a, 0x8f, 0xbb, 0xb4, 0x3e, 0x0f, 0xe3, 0xd6, 0x18, 0xd4, 0x74, 0xef, 0xd5, 0x2f, 0xef, 0xdb, 0xf5, 0xc2, 0x91, 0x11, 0x5a, 0xa1, 0x55, 0x2e, 0xe6, 0x39, 0xd8, 0xc1, 0xe8, 0xf0, 0xf6, 0x9e, 0x6b, 0x6f, 0xbd, 0x5b, 0xa6, 0x90, 0xd9, 0x7d, 0x70, 0xce, 0x8a, 0x70, 0xce, + 0x26, 0x4a, 0x73, 0x66, 0xc3, 0x73, 0x86, 0x78, 0x0c, 0x9a, 0x33, 0x44, 0x3d, 0xca, 0x73, 0x26, 0x46, 0xff, 0xad, 0x2f, 0x4f, 0x54, 0xbe, 0x82, 0xf1, 0x93, 0x02, 0x95, 0x39, 0x9b, 0x1d, 0x2c, 0x58, 0x7d, 0x7b, 0x69, 0x36, 0x52, 0x9f, 0x16, 0x5d, 0xb8, 0x9e, 0x98, 0xef, 0x7e, 0x31, 0xb6, 0x90, 0x35, 0x06, 0x2d, 0x0d, 0x41, 0x34, 0x67, 0xdc, 0x6c, 0x64, 0x96, 0x2a, 0xe0, 0x8c, 0x02, 0x14, 0xa1, 0x4a, 0x85, 0x45, 0xc2, 0x91, 0x31, 0xbe, 0x21, 0x9f, 0xf7, 0x5e, 0xf7, 0x83, 0xab, 0x3b, 0xfa, 0xaf, 0x7e, 0xe1, 0x22, 0x77, 0xcf, 0x40, 0x3f, 0xaf, 0x66, 0xdc, 0x3e, 0xad, 0x3b, 0xde, 0x9e, 0xba, 0xe9, 0x3e, 0xdd, 0xcc, 0xfb, 0xa4, 0x46, 0x91, 0x5e, 0xb4, 0x73, 0x20, 0x98, 0xab, 0xf1, 0xea, 0xed, 0x01, 0x9a, 0xfa, 0xb5, 0x52, 0xab, 0xa0, 0x47, 0x8e, 0xbc, 0xb0, 0x6b, 0xdf, + 0xcb, 0x57, 0xf7, 0xd2, 0x6a, 0x83, 0xa6, 0xcf, 0x67, 0x87, 0x13, 0x75, 0xf7, 0xad, 0xd7, 0xf6, 0x6c, 0x1f, 0x8e, 0xc2, 0xc9, 0x33, 0xf9, 0xd0, 0xbe, 0xab, 0x39, 0xfd, 0x1e, 0xfd, 0x01, 0xf3, 0x20, 0x21, 0x23, 0xde, 0x10, 0xb1, 0x32, 0x4f, 0xbf, 0x4f, 0x4f, 0xd2, 0x72, 0xb8, 0xbc, 0xc5, 0x62, 0x6b, 0xd4, 0x0e, 0x4f, 0x52, 0x40, 0x0d, 0x25, 0x2c, 0x03, 0x2a, 0x49, 0x43, 0x0d, 0x46, 0x00, 0xd9, 0x0f, 0x99, 0x4e, 0x5f, 0x89, 0x29, 0xe5, 0x2b, 0xb1, 0xca, 0x59, 0x4c, 0x88, 0x53, 0x44, 0x2d, 0xaa, 0x6d, 0x17, 0x60, 0xe4, 0x66, 0x84, 0x00, 0x8d, 0x8d, 0x04, 0x90, 0x70, 0x41, 0xba, 0x04, 0x22, 0x6d, 0xa4, 0x04, 0xcf, 0x8d, 0x06, 0x08, 0x7f, 0x41, 0xb4, 0xd8, 0xaa, 0x23, 0xe1, 0x60, 0xe9, 0xc9, 0xba, 0x86, 0xc6, 0xc9, 0xfe, 0xb0, 0xaa, 0xbd, 0xc3, 0xdd, 0x64, 0x9b, 0x79, + 0x6d, 0x7f, 0x7b, 0x9f, 0x5b, 0x15, 0x6a, 0xf4, 0xe7, 0xc5, 0x9f, 0x95, 0xf8, 0x67, 0x32, 0xbb, 0x4f, 0xfa, 0xf9, 0xc8, 0x25, 0x3a, 0x83, 0xa5, 0x65, 0xcb, 0x03, 0x7f, 0xfc, 0x72, 0x72, 0x81, 0x63, 0xe6, 0x8e, 0x4d, 0x0f, 0xff, 0xfd, 0xd1, 0xed, 0xad, 0x66, 0xf4, 0x63, 0xf3, 0xd6, 0xaf, 0xfc, 0xf1, 0xee, 0xc4, 0xb0, 0x83, 0x9c, 0x94, 0x7e, 0xc4, 0x3a, 0x8a, 0xf7, 0xf4, 0x7b, 0xb2, 0xc7, 0xa9, 0xf7, 0x08, 0x9e, 0xe8, 0x45, 0xa3, 0xea, 0x00, 0x40, 0x91, 0xc5, 0x15, 0x67, 0x10, 0x33, 0x81, 0x2c, 0x65, 0xa7, 0x1c, 0xe5, 0x2f, 0x2b, 0x26, 0x90, 0x93, 0xb5, 0x16, 0x73, 0x15, 0x11, 0x05, 0x2a, 0xcb, 0x0c, 0xfb, 0xfd, 0xfe, 0x5e, 0x7f, 0x8f, 0x35, 0x1c, 0x09, 0x9a, 0x6b, 0xec, 0x41, 0x15, 0x5c, 0xb0, 0x52, 0x26, 0x06, 0x26, 0x16, 0x7c, 0x05, 0x9b, 0xbb, 0x15, 0x64, 0x00, + 0xce, 0x29, 0xab, 0x0a, 0x11, 0xad, 0xd4, 0xda, 0x09, 0xe0, 0x43, 0x41, 0xbd, 0x6a, 0x8e, 0x77, 0x24, 0x03, 0x0b, 0x3a, 0xe2, 0x5a, 0x8b, 0x4b, 0xff, 0xc9, 0xcb, 0x3a, 0x97, 0x45, 0x1b, 0xef, 0x58, 0x18, 0x48, 0x74, 0xc4, 0xcd, 0xaf, 0x90, 0xef, 0x7a, 0xbd, 0x0f, 0xf3, 0xb1, 0xa6, 0xcd, 0x37, 0x8e, 0x15, 0x56, 0x0e, 0x36, 0x98, 0x6b, 0x8e, 0x6f, 0x1d, 0xbb, 0x61, 0x73, 0x53, 0x8c, 0x7f, 0x5b, 0xc5, 0xb9, 0x8c, 0xe0, 0xcb, 0x6d, 0x9b, 0x06, 0x6b, 0x7c, 0x4b, 0x8f, 0xed, 0x48, 0xac, 0x59, 0xb9, 0xd4, 0x1f, 0x58, 0xba, 0x72, 0x75, 0x62, 0xc7, 0xb1, 0xa5, 0xbe, 0x9a, 0xc1, 0x4d, 0x6d, 0xbf, 0x01, 0xd3, 0xab, 0x1d, 0xc1, 0xa5, 0x0f, 0xaf, 0xbc, 0xf0, 0xa5, 0x43, 0x83, 0x8e, 0x74, 0x47, 0x64, 0xe1, 0xd4, 0xe0, 0xa1, 0x97, 0x2e, 0x5c, 0xf9, 0xf0, 0xd2, 0xa5, 0x1a, 0xbb, + 0x59, 0x8c, 0x5f, 0xbf, 0x58, 0xd8, 0x4a, 0xdb, 0x68, 0x25, 0x61, 0x20, 0x3a, 0x89, 0x37, 0xc4, 0x3c, 0xd8, 0x4e, 0xa0, 0xc0, 0x79, 0xb0, 0x41, 0xbc, 0xdb, 0x01, 0xc0, 0x41, 0x03, 0xf9, 0x72, 0x2d, 0xf9, 0xc4, 0x50, 0x05, 0x0f, 0x2b, 0x8d, 0x60, 0x44, 0xea, 0xe9, 0x52, 0x55, 0xb4, 0x20, 0x85, 0xca, 0xf6, 0x89, 0x69, 0x63, 0xf3, 0x3c, 0x57, 0xbe, 0x17, 0x15, 0x2e, 0x4a, 0x40, 0xce, 0xc6, 0xc8, 0xa8, 0xb3, 0xdc, 0x49, 0x92, 0x38, 0x6e, 0xbc, 0xfc, 0x7e, 0x78, 0x3b, 0x2d, 0xa3, 0xf7, 0x7c, 0xae, 0x67, 0x64, 0x62, 0x8c, 0x57, 0xb4, 0x86, 0x87, 0xc2, 0x6b, 0x4d, 0xd8, 0xaf, 0x94, 0xa3, 0x5c, 0x02, 0x50, 0x55, 0xdf, 0xce, 0x4c, 0x89, 0xf8, 0x47, 0x99, 0x8a, 0xcc, 0x08, 0xb0, 0xaf, 0x10, 0x47, 0xe1, 0xcc, 0x4e, 0xd5, 0x05, 0x6d, 0xf5, 0x97, 0xf7, 0x5c, 0x7d, 0x3c, 0xd2, + 0x37, 0xd9, 0x9c, 0x5f, 0xd7, 0x5b, 0xa3, 0x71, 0xb4, 0x4c, 0x0e, 0x24, 0xa7, 0xb3, 0xc5, 0xb1, 0x8c, 0x99, 0x54, 0x72, 0x86, 0x02, 0xd9, 0xdc, 0x57, 0xb7, 0x22, 0x37, 0xfd, 0xe5, 0xdd, 0xad, 0x6d, 0x17, 0x3c, 0xb8, 0x6d, 0xfd, 0x43, 0x07, 0x06, 0x5b, 0x76, 0xdf, 0x3d, 0xbd, 0xe6, 0xb6, 0x46, 0xe0, 0xed, 0x69, 0xff, 0xd6, 0x13, 0x6b, 0xae, 0x59, 0x1e, 0x8d, 0x0c, 0xef, 0xe8, 0x13, 0x0a, 0x8b, 0xb6, 0x75, 0x38, 0x55, 0xf6, 0xfa, 0xfe, 0xb4, 0xc6, 0x6a, 0xd4, 0xe4, 0x23, 0xaa, 0xe4, 0xe6, 0x87, 0x2e, 0x05, 0xdf, 0xdd, 0xf4, 0x95, 0x0b, 0x8a, 0x3d, 0x57, 0x3e, 0x7f, 0xd1, 0x8e, 0x67, 0x0f, 0x8f, 0xf4, 0x62, 0xec, 0x0e, 0xf2, 0xf4, 0x3b, 0xc2, 0x43, 0xb4, 0x1d, 0xaf, 0x49, 0x12, 0xe3, 0x3c, 0xc1, 0x35, 0x09, 0xe0, 0xdc, 0x64, 0x8c, 0x92, 0x8c, 0xe9, 0x7a, 0xe5, 0xa0, + 0x95, 0xec, 0xa3, 0x89, 0x21, 0x71, 0xec, 0xc8, 0x10, 0xc1, 0x45, 0xe7, 0x1f, 0xf6, 0x19, 0x69, 0xc8, 0x08, 0x23, 0x17, 0x5b, 0x2b, 0x40, 0x7b, 0xfd, 0xe5, 0xdd, 0x68, 0x94, 0x1b, 0xf0, 0x28, 0x5f, 0xc1, 0x63, 0x79, 0xf0, 0x32, 0x3c, 0x96, 0x15, 0xc7, 0x1b, 0x6f, 0xbb, 0xed, 0x15, 0xa8, 0x23, 0x79, 0x7a, 0xda, 0x2a, 0x03, 0xaa, 0xee, 0x78, 0x5b, 0xe3, 0x37, 0x8f, 0x1e, 0x3e, 0x7c, 0x14, 0xd1, 0x8d, 0x85, 0xc2, 0xc3, 0xcc, 0x35, 0xcc, 0x49, 0xc8, 0xaf, 0xfe, 0x17, 0xd3, 0x8d, 0x8b, 0x85, 0x87, 0x30, 0xed, 0xad, 0x21, 0x2e, 0x13, 0xe9, 0xa7, 0x0a, 0x85, 0x01, 0x38, 0x11, 0xd0, 0x2d, 0xca, 0x11, 0x42, 0x51, 0x79, 0xe2, 0x97, 0x71, 0xf1, 0x32, 0x4f, 0xd0, 0x90, 0x06, 0xd1, 0x18, 0xfd, 0x95, 0xa1, 0xe1, 0x52, 0x97, 0x6b, 0xec, 0xe5, 0xa5, 0x01, 0xa2, 0xf2, 0xca, 0x25, + 0xed, 0x60, 0xce, 0xbd, 0x25, 0x17, 0x4d, 0xe9, 0xd6, 0xf1, 0xa2, 0x0a, 0x2a, 0x08, 0x21, 0x3e, 0x14, 0x94, 0xcb, 0x9d, 0x52, 0x59, 0xb1, 0x8a, 0x4f, 0x06, 0x95, 0x62, 0xad, 0xda, 0x01, 0x14, 0x12, 0x11, 0x2f, 0xce, 0xee, 0x7e, 0xe2, 0xb2, 0xce, 0x83, 0x1d, 0x1a, 0x3a, 0x60, 0x77, 0x6f, 0x28, 0x8e, 0x6d, 0x6b, 0x31, 0xcf, 0xf4, 0xac, 0xca, 0x5b, 0x28, 0xa5, 0xd1, 0x50, 0x00, 0x06, 0x3e, 0x1f, 0xf9, 0xcb, 0x2b, 0xe0, 0xa3, 0xcb, 0x5f, 0xbd, 0xaa, 0xc7, 0x60, 0x84, 0xbc, 0xc6, 0x90, 0xdd, 0x70, 0xdd, 0xf2, 0xbc, 0x39, 0xd1, 0x93, 0xd2, 0xd8, 0x8c, 0x1a, 0x67, 0xd0, 0xac, 0x9c, 0xb9, 0x52, 0xb4, 0x7f, 0x8c, 0xc0, 0x35, 0xf4, 0xc3, 0x71, 0xab, 0xa1, 0xa6, 0x97, 0x2a, 0x26, 0x7c, 0x1a, 0xac, 0x4c, 0x54, 0xd3, 0x47, 0x49, 0x68, 0x4f, 0xa3, 0x80, 0x64, 0x3f, 0x6f, 0x35, + 0x8b, 0x99, 0x10, 0x48, 0x5a, 0x0f, 0x95, 0xf3, 0xc6, 0x4d, 0x16, 0xeb, 0x99, 0x92, 0x7a, 0xd3, 0x0d, 0x47, 0x8f, 0xde, 0x70, 0xd3, 0xb1, 0x63, 0xa9, 0x89, 0x1b, 0xa7, 0xf3, 0x3d, 0x97, 0x89, 0x85, 0x86, 0xf2, 0xd3, 0x37, 0x4e, 0xbc, 0x7c, 0xf8, 0xa5, 0x47, 0x51, 0xb9, 0xa1, 0x47, 0x5f, 0xba, 0x41, 0x3f, 0x7a, 0xf0, 0xd1, 0x6d, 0xa8, 0x20, 0x17, 0x2a, 0x45, 0xb6, 0xed, 0xb1, 0x83, 0xa3, 0x7a, 0xe1, 0x71, 0xbc, 0x1e, 0x66, 0xe1, 0x41, 0xea, 0xb7, 0xb8, 0xe6, 0xc3, 0x6e, 0xa9, 0xfc, 0x53, 0x95, 0x70, 0x5e, 0x3b, 0x84, 0xab, 0x2a, 0x94, 0xe4, 0xb1, 0x33, 0x2e, 0x35, 0xe0, 0x4b, 0xb6, 0xb3, 0x3d, 0x35, 0xef, 0x03, 0x08, 0xb6, 0xa5, 0x24, 0xec, 0x83, 0x59, 0xc2, 0x3e, 0x79, 0xf5, 0x95, 0x50, 0xc6, 0xff, 0x68, 0xa6, 0x1f, 0x8c, 0x1f, 0x84, 0x52, 0xff, 0xbb, 0xe4, 0x37, + 0x85, 0x07, 0xa1, 0xb8, 0x6f, 0x25, 0xdf, 0x8d, 0xfa, 0x05, 0x64, 0x91, 0x38, 0x04, 0xe7, 0xf0, 0x21, 0xd8, 0xd7, 0x08, 0xaa, 0x37, 0xe2, 0xd1, 0xcd, 0x23, 0xeb, 0xe3, 0x46, 0xf0, 0xd1, 0x4f, 0x93, 0xc3, 0x22, 0x1a, 0x23, 0x6b, 0x8d, 0xcc, 0x91, 0xf7, 0x03, 0xd5, 0xe2, 0x3e, 0x14, 0x3f, 0xca, 0xf8, 0xb3, 0x54, 0x86, 0x7e, 0xe8, 0x51, 0x85, 0x42, 0xf8, 0xb9, 0x21, 0xe8, 0x74, 0x05, 0x0d, 0xc2, 0xcf, 0x15, 0x8a, 0xaf, 0xbd, 0xa0, 0x33, 0x19, 0x74, 0x8a, 0xff, 0x51, 0x1b, 0x14, 0x0a, 0x93, 0xfa, 0x97, 0x0a, 0x1d, 0x6b, 0xd2, 0xe1, 0x9a, 0x1a, 0x5f, 0x88, 0x4d, 0x27, 0x85, 0x43, 0x16, 0x87, 0xc3, 0x02, 0x2e, 0x4c, 0x4e, 0xc7, 0x3e, 0xb1, 0x30, 0x0b, 0x28, 0xb9, 0x4e, 0x23, 0xfc, 0xaf, 0xce, 0xa1, 0xd1, 0x38, 0x0c, 0xc0, 0xa2, 0xd1, 0xc9, 0xd1, 0xe0, 0xd5, 0xb0, 0xcf, + 0xc3, 0x34, 0xaa, 0x89, 0x5b, 0x92, 0xf7, 0xb3, 0x99, 0xba, 0x74, 0xea, 0xd3, 0xe4, 0x7d, 0x2c, 0x07, 0xa4, 0xe7, 0x95, 0xf7, 0xf1, 0xa5, 0x06, 0x24, 0xef, 0xd7, 0x48, 0xf2, 0x3e, 0x77, 0xa6, 0xbc, 0x2f, 0xed, 0x8c, 0x79, 0xe5, 0xfd, 0xdf, 0x81, 0xae, 0xec, 0x74, 0x32, 0x7d, 0x7e, 0xcf, 0xba, 0x6f, 0x2d, 0xf6, 0xc5, 0x8c, 0x8d, 0x2d, 0x7b, 0xf7, 0x5f, 0x3d, 0x74, 0xe4, 0x67, 0x37, 0x0d, 0xf9, 0x5a, 0xc7, 0x9b, 0x76, 0x43, 0x79, 0x5f, 0xae, 0x73, 0x0c, 0x76, 0xa7, 0x01, 0xf0, 0x8f, 0xe5, 0x85, 0x5f, 0x2d, 0x25, 0x69, 0x6b, 0xcd, 0xcc, 0x87, 0xe0, 0xa1, 0xdf, 0x07, 0x9a, 0x2c, 0xd1, 0x44, 0xa1, 0x00, 0xe5, 0xfd, 0x95, 0x89, 0xce, 0x84, 0x65, 0xcd, 0x23, 0x1f, 0x9e, 0x58, 0xf7, 0xf8, 0xc3, 0x0f, 0xae, 0xe0, 0xdc, 0x01, 0xb6, 0xe6, 0xbc, 0x13, 0x4f, 0x9f, 0xab, 0x73, + 0xe8, 0x22, 0x8b, 0x83, 0x3f, 0xbc, 0x08, 0x5c, 0x30, 0xd2, 0x26, 0x5c, 0x21, 0xca, 0xfb, 0x23, 0xc2, 0x37, 0xa8, 0xfd, 0x74, 0x13, 0xdc, 0x0b, 0x7c, 0xd1, 0xa3, 0xd3, 0x92, 0x64, 0x1f, 0x51, 0xc6, 0xe7, 0x84, 0x37, 0xd4, 0x92, 0xc3, 0xc9, 0x78, 0x30, 0xc0, 0x7b, 0x29, 0x24, 0x65, 0xcf, 0x2f, 0x61, 0xb7, 0x61, 0xc7, 0x77, 0x29, 0xcd, 0x1f, 0x1e, 0x51, 0x60, 0xad, 0x5b, 0x73, 0x68, 0xbc, 0xb8, 0xb4, 0xde, 0x84, 0xc5, 0x6b, 0x8d, 0xb7, 0x21, 0xb9, 0xf6, 0x2b, 0xfb, 0x07, 0x3a, 0x2e, 0x7f, 0xee, 0xd2, 0xd1, 0x31, 0x2c, 0x5a, 0x5f, 0x7b, 0xd9, 0xea, 0x83, 0xa3, 0x51, 0x32, 0xb9, 0xf6, 0xe2, 0xc5, 0x0d, 0x6e, 0x6b, 0x7d, 0x63, 0x67, 0x59, 0xb2, 0x0e, 0xf1, 0x6e, 0xad, 0xab, 0xef, 0xfc, 0x47, 0xaf, 0x9a, 0xba, 0xff, 0xa2, 0xee, 0x40, 0xad, 0xc6, 0x68, 0x56, 0x4c, 0x2d, + 0x09, 0xf7, 0x6f, 0x45, 0xe7, 0x33, 0x21, 0xdc, 0x44, 0x73, 0xcc, 0xf7, 0x08, 0x0d, 0xd1, 0x4e, 0xb6, 0x15, 0x35, 0x5a, 0x0d, 0x9a, 0x78, 0xb7, 0x84, 0x00, 0xa1, 0xc6, 0x39, 0x73, 0x72, 0x19, 0x29, 0x93, 0xa3, 0xea, 0xd0, 0x22, 0x9a, 0x01, 0x24, 0x29, 0x99, 0xa1, 0x33, 0xe9, 0x6e, 0x16, 0xb1, 0xa8, 0x74, 0x19, 0x8a, 0xeb, 0x1f, 0x7a, 0xac, 0x01, 0x3f, 0xa6, 0xfb, 0xe7, 0x5a, 0xe3, 0xfe, 0xb9, 0xd6, 0xcc, 0xff, 0x5c, 0x6b, 0xd6, 0x7f, 0xae, 0x35, 0xdb, 0x3f, 0xd7, 0x9a, 0xef, 0x9f, 0x6b, 0x2d, 0x8a, 0xe5, 0x0a, 0x78, 0x32, 0x76, 0x13, 0x72, 0x52, 0xbe, 0xbb, 0xfc, 0xe0, 0xd9, 0x9f, 0x29, 0xc6, 0xe7, 0xb9, 0x9d, 0xa6, 0x77, 0xcd, 0xf3, 0x88, 0x64, 0x5c, 0x11, 0xd3, 0x80, 0x2c, 0x76, 0x8b, 0x15, 0x65, 0xdf, 0x61, 0xf5, 0xa6, 0x3a, 0xfb, 0x2e, 0x37, 0x3b, 0xfb, 0xae, + 0x92, 0x30, 0x8c, 0x2b, 0x69, 0x88, 0x84, 0xdc, 0x0d, 0xa0, 0x78, 0x08, 0x86, 0xd3, 0x3b, 0x3b, 0xaf, 0x3d, 0x7e, 0xc5, 0x85, 0xcb, 0x2e, 0xc9, 0xb5, 0xdf, 0x39, 0x3d, 0x7e, 0x6c, 0x2a, 0xdf, 0xba, 0xeb, 0xee, 0x8d, 0xeb, 0xae, 0xca, 0x78, 0x0b, 0x0b, 0x6b, 0x91, 0x7c, 0x86, 0xd8, 0x09, 0xdf, 0x38, 0x52, 0xab, 0x71, 0x88, 0x7f, 0xff, 0xbd, 0xbd, 0xfe, 0xa5, 0x57, 0x9e, 0x7c, 0x60, 0xb8, 0xf7, 0xc6, 0xde, 0xb1, 0x9e, 0xcb, 0xbf, 0x79, 0xde, 0x25, 0xdf, 0xbb, 0x61, 0xa4, 0xbf, 0xfd, 0xef, 0xbd, 0xab, 0xf2, 0x66, 0xc4, 0x9e, 0x1a, 0x80, 0x1e, 0xb2, 0x27, 0x66, 0xf9, 0xec, 0xef, 0x58, 0x76, 0x17, 0x6e, 0xa6, 0xbf, 0x0b, 0x65, 0xf7, 0x22, 0x78, 0x5f, 0x74, 0x0d, 0xb8, 0x1a, 0x6a, 0x48, 0x9a, 0xc9, 0x38, 0x49, 0x19, 0xed, 0x03, 0x94, 0xcc, 0x0b, 0xd5, 0x20, 0x14, 0x3e, + 0xc9, 0x0c, 0x16, 0x00, 0xd3, 0xef, 0x3c, 0xcb, 0x65, 0x52, 0xbc, 0x3c, 0x2e, 0x6e, 0x58, 0x1f, 0x8a, 0x64, 0x41, 0x09, 0x8a, 0x98, 0xd7, 0x4a, 0x12, 0x48, 0x99, 0x57, 0x57, 0x78, 0xc8, 0xa7, 0xdf, 0x27, 0x32, 0x14, 0xee, 0x73, 0xbe, 0xcf, 0xfc, 0x39, 0xdf, 0x67, 0xfd, 0x9c, 0xef, 0xb3, 0x7d, 0xce, 0xf7, 0x39, 0x3e, 0xe7, 0xfb, 0xfc, 0x9f, 0xf3, 0x7d, 0x09, 0x1c, 0x02, 0x5a, 0x2a, 0x16, 0x3c, 0xcf, 0xfd, 0x25, 0x8a, 0x89, 0x6e, 0x2f, 0x06, 0xcb, 0x77, 0x56, 0x42, 0x88, 0xe6, 0x3c, 0x50, 0xbd, 0x35, 0xb5, 0xc5, 0xb6, 0x64, 0x3c, 0x1a, 0x0e, 0x60, 0xa1, 0xc7, 0x16, 0x67, 0xe6, 0xc9, 0x17, 0xf9, 0x3c, 0xba, 0x57, 0x33, 0xa0, 0xbf, 0xeb, 0x2e, 0x6e, 0x1a, 0xaa, 0x19, 0x4e, 0xea, 0x65, 0x0e, 0x8d, 0x36, 0xe5, 0x2b, 0xa6, 0xeb, 0x72, 0x6b, 0x7a, 0x22, 0x74, 0xae, 0xd1, + 0x9a, 0xe0, 0x66, 0xe9, 0x62, 0x0d, 0xeb, 0xfb, 0xa2, 0x74, 0xae, 0xc1, 0x96, 0x34, 0x55, 0x74, 0xb1, 0x93, 0xc9, 0xf6, 0x3d, 0x2b, 0x72, 0xac, 0x69, 0xc4, 0xae, 0xd3, 0x8d, 0x2d, 0x68, 0xe1, 0xf2, 0xeb, 0x4f, 0xfe, 0xfc, 0x36, 0x5f, 0xce, 0x54, 0xad, 0x96, 0x99, 0x1b, 0x36, 0xde, 0xf9, 0xcb, 0xdb, 0x7d, 0x8d, 0x26, 0xac, 0x96, 0x3d, 0xb2, 0xbd, 0x45, 0xf2, 0x93, 0xde, 0x7d, 0xfa, 0x6f, 0xe0, 0x35, 0x72, 0x87, 0x98, 0x73, 0x23, 0xd5, 0x30, 0xd9, 0x59, 0x55, 0xdd, 0xf4, 0x2c, 0x39, 0x37, 0x77, 0x3f, 0xee, 0xf2, 0xb5, 0xd8, 0xc2, 0x29, 0x6b, 0x4d, 0x5b, 0xcc, 0x4c, 0xee, 0x10, 0x46, 0x03, 0xc1, 0x3c, 0xe7, 0x36, 0xa9, 0x3c, 0x99, 0xde, 0x1a, 0x2c, 0x93, 0x3d, 0x26, 0xdc, 0x2b, 0xd3, 0xcb, 0xa1, 0xc0, 0x49, 0x34, 0x13, 0x8b, 0x8a, 0x0b, 0xea, 0x83, 0xa4, 0x52, 0x0e, + 0x06, 0x49, 0x15, 0xaa, 0xb5, 0xb0, 0x1b, 0xd1, 0x7f, 0x39, 0x85, 0x70, 0x6b, 0xe5, 0x72, 0x3b, 0x46, 0x9c, 0x75, 0x0c, 0xa9, 0x81, 0x52, 0x89, 0xad, 0xce, 0x6e, 0x7a, 0xd8, 0xe5, 0x02, 0x84, 0xab, 0xd9, 0xd5, 0xdc, 0xd4, 0x50, 0x9b, 0x4c, 0xc4, 0x50, 0xe9, 0x71, 0xab, 0x99, 0xd5, 0x2b, 0xe5, 0x50, 0x7c, 0x75, 0x6a, 0x64, 0x38, 0x98, 0xa2, 0x0a, 0x88, 0xaa, 0xb9, 0xac, 0x0e, 0x02, 0x2a, 0xc0, 0x61, 0x49, 0x04, 0xcd, 0x77, 0x05, 0xf5, 0x1d, 0x1b, 0x1c, 0x51, 0x15, 0x40, 0x5a, 0x66, 0xb2, 0xd4, 0x2c, 0xdc, 0xd1, 0xdb, 0xbb, 0x63, 0x61, 0x8d, 0xc5, 0xb4, 0x47, 0xcf, 0x17, 0xa2, 0xd1, 0x02, 0xaf, 0x27, 0xf7, 0xdd, 0xf6, 0xc3, 0xdd, 0xf7, 0xd2, 0x0a, 0x2e, 0x6a, 0x9a, 0xb9, 0x54, 0x5f, 0xe7, 0xfd, 0xd2, 0x29, 0x46, 0x6e, 0x8c, 0x9a, 0xc8, 0x2b, 0xf5, 0x69, 0xef, 0x97, + 0xe4, 0x64, 0xd0, 0x52, 0xbf, 0x20, 0xe7, 0x72, 0xe5, 0x16, 0xd4, 0x5b, 0x82, 0xa9, 0xf6, 0x08, 0xcb, 0x46, 0xda, 0x53, 0x7f, 0xbb, 0x77, 0x46, 0x00, 0x13, 0xd4, 0xff, 0x4d, 0x4e, 0xe6, 0xd3, 0x4b, 0x7c, 0x9f, 0x74, 0xd9, 0x63, 0xcc, 0xc9, 0x4f, 0x82, 0xc9, 0xc9, 0x42, 0x7a, 0xa9, 0x8f, 0x7a, 0xd6, 0x1e, 0xfb, 0x58, 0x0d, 0xe7, 0x20, 0x4c, 0x10, 0xf4, 0x5f, 0x98, 0x93, 0x90, 0xef, 0x05, 0x88, 0xee, 0x62, 0x07, 0x54, 0x27, 0xa0, 0x52, 0x81, 0xac, 0x12, 0xaa, 0x12, 0x58, 0x39, 0xdc, 0x55, 0x76, 0x8c, 0xf1, 0x41, 0xad, 0x82, 0x7b, 0xcb, 0x41, 0x0d, 0xeb, 0xb4, 0x48, 0x40, 0xc5, 0xc5, 0xd6, 0x51, 0xda, 0x9c, 0x45, 0x1b, 0xd0, 0x05, 0xd4, 0x4a, 0x42, 0x03, 0x34, 0x6a, 0x59, 0xa5, 0xfa, 0x27, 0xaa, 0xb3, 0x32, 0xab, 0xd0, 0x7a, 0x2e, 0x83, 0xca, 0x6f, 0x98, 0xc9, 0x8f, + 0x16, 0xef, 0xee, 0xe7, 0xf9, 0xbe, 0xdd, 0x8b, 0x85, 0x3f, 0x03, 0xcd, 0x55, 0x2f, 0xec, 0x6f, 0xee, 0xbe, 0xf2, 0xb9, 0x8b, 0x04, 0xdf, 0x07, 0x1f, 0xbc, 0x0e, 0x56, 0x08, 0x5f, 0x79, 0x20, 0xd8, 0xb1, 0x32, 0xdf, 0xb8, 0xb2, 0x8d, 0x17, 0xec, 0xe4, 0x0d, 0xcd, 0x9b, 0x8f, 0x2d, 0x9b, 0xbc, 0x6b, 0x77, 0x2b, 0x1d, 0xbd, 0xeb, 0x27, 0x3f, 0xb9, 0xeb, 0x8f, 0x68, 0x2f, 0xa0, 0xfe, 0xee, 0xc7, 0x76, 0xe4, 0x64, 0x31, 0x46, 0x23, 0xe1, 0x12, 0x17, 0x00, 0x41, 0x46, 0x17, 0x1c, 0x48, 0x67, 0x47, 0x87, 0xc5, 0x81, 0x85, 0x3f, 0x2d, 0xa1, 0xc5, 0x6e, 0x58, 0x56, 0x21, 0x73, 0x48, 0x15, 0xd7, 0x03, 0xb8, 0xe2, 0x3b, 0x6f, 0x06, 0xef, 0x82, 0xe5, 0xc2, 0x03, 0xa7, 0xde, 0x7f, 0x9f, 0x9a, 0xa6, 0x3f, 0x14, 0xba, 0xee, 0x9a, 0xd9, 0x42, 0x9e, 0xb8, 0x8b, 0x9c, 0xba, 0x5f, + 0xb2, 0x29, 0x0b, 0x5f, 0xa0, 0x1f, 0x87, 0x6d, 0xc4, 0x89, 0x67, 0x45, 0xeb, 0xa1, 0x0e, 0xa1, 0x12, 0x87, 0xa0, 0x22, 0xab, 0xc7, 0xc1, 0x66, 0xce, 0x59, 0x3f, 0x50, 0x38, 0x7f, 0xdb, 0x25, 0x01, 0x6a, 0xd3, 0x0c, 0x41, 0x4f, 0x42, 0x4d, 0x14, 0xc7, 0x39, 0xda, 0x51, 0x22, 0x83, 0xa3, 0x62, 0x35, 0x72, 0x63, 0xab, 0x51, 0x2d, 0xce, 0x4b, 0x25, 0x19, 0x8a, 0x21, 0xb1, 0x64, 0x37, 0xdf, 0x23, 0x25, 0x4b, 0x10, 0x7a, 0x02, 0xd2, 0x86, 0xb9, 0x37, 0x4b, 0xc5, 0x92, 0x67, 0x3d, 0x83, 0x91, 0x90, 0xd5, 0x2c, 0x6b, 0x8e, 0xfa, 0x4d, 0x51, 0xbf, 0x42, 0xe6, 0x9a, 0x1d, 0x15, 0x5b, 0x3e, 0xf1, 0x15, 0xcb, 0xbf, 0x1c, 0x7b, 0x45, 0x78, 0x70, 0x4d, 0xfb, 0x91, 0x25, 0xdb, 0x5e, 0xb8, 0x61, 0xd9, 0xc2, 0xa3, 0xaf, 0x5e, 0x30, 0xb5, 0x55, 0x43, 0x5a, 0xad, 0x6c, 0x73, 0x7a, + 0xe4, 0xe0, 0xea, 0x6c, 0x7e, 0xfd, 0x35, 0x8b, 0x13, 0xfd, 0x4d, 0x69, 0xf3, 0x29, 0x6a, 0x7a, 0x66, 0x27, 0xa5, 0xf7, 0x05, 0x5b, 0xae, 0x7d, 0xf3, 0xae, 0x5b, 0x7e, 0xff, 0xe5, 0xe5, 0xae, 0x4b, 0x8d, 0x56, 0x36, 0xb7, 0xe3, 0x81, 0xf3, 0x2f, 0xb9, 0x77, 0x2a, 0xae, 0x31, 0x39, 0xb4, 0x33, 0xc7, 0x98, 0x93, 0xf7, 0xc3, 0xb9, 0xd3, 0x0a, 0xf7, 0x2a, 0x0e, 0xc3, 0xb9, 0x2b, 0xa2, 0xdd, 0xd4, 0xe2, 0x25, 0x49, 0x78, 0xa2, 0x94, 0x40, 0x4e, 0x03, 0x52, 0x2d, 0x27, 0x27, 0x51, 0xa0, 0x37, 0xec, 0xae, 0x06, 0xa8, 0xd5, 0x60, 0x15, 0x83, 0x5c, 0xfd, 0x0e, 0xc4, 0x50, 0xdd, 0xd8, 0x26, 0x54, 0x24, 0x8a, 0x0d, 0xf9, 0xba, 0x14, 0x1b, 0x82, 0x1a, 0x1a, 0x1b, 0xe2, 0x03, 0x5a, 0x99, 0x3b, 0x1e, 0x62, 0x91, 0xa9, 0x84, 0xad, 0x2e, 0x5b, 0x8e, 0xb8, 0x2a, 0x3e, 0x38, 0x00, + 0x4b, 0xbe, 0x11, 0x20, 0xd9, 0xec, 0x9b, 0x01, 0xae, 0x68, 0x9e, 0xaf, 0x94, 0x34, 0x87, 0x72, 0x3d, 0x50, 0x1c, 0x0e, 0x7b, 0x0e, 0x14, 0x52, 0x5d, 0x26, 0xe1, 0x3e, 0x57, 0x77, 0xf6, 0xcb, 0xdf, 0xbd, 0x3b, 0xdb, 0xe3, 0x14, 0xee, 0x33, 0x75, 0xd5, 0x1e, 0x05, 0x77, 0xf8, 0x16, 0x64, 0xe2, 0x2d, 0x6e, 0x61, 0xeb, 0x7d, 0x7c, 0x23, 0x0b, 0x96, 0x7b, 0xbb, 0x12, 0x87, 0xbf, 0x7b, 0x24, 0xd1, 0xe5, 0x05, 0xcb, 0xd9, 0x46, 0xdf, 0xfd, 0xc2, 0x56, 0x77, 0x4b, 0xbc, 0x7e, 0xa1, 0x0f, 0x24, 0xef, 0x17, 0x5a, 0x5c, 0x7e, 0x30, 0x51, 0x13, 0x00, 0xef, 0xc5, 0x42, 0x33, 0xe7, 0x93, 0x47, 0x42, 0x31, 0xc1, 0x10, 0xa8, 0x21, 0xb7, 0x16, 0x3a, 0x0c, 0xfa, 0x86, 0x18, 0xd9, 0xc8, 0xbb, 0x05, 0x7b, 0xc2, 0x45, 0x6e, 0x9f, 0x39, 0xee, 0x4a, 0x80, 0xdf, 0xb8, 0xf9, 0x99, 0x7f, + 0x8d, 0x35, 0xe8, 0xd9, 0x62, 0x41, 0x68, 0x2d, 0xed, 0xa7, 0x7b, 0x99, 0x5b, 0xe1, 0x9c, 0x44, 0x50, 0x66, 0x78, 0x00, 0x90, 0x0c, 0x8e, 0xd9, 0x25, 0xe5, 0x0c, 0xf2, 0x63, 0xd1, 0xb4, 0x7d, 0x08, 0xd1, 0x18, 0xb0, 0x0a, 0xc5, 0x65, 0x55, 0x4d, 0x47, 0x84, 0x88, 0xc0, 0x79, 0x60, 0x23, 0x7c, 0x40, 0x29, 0x73, 0xce, 0x9e, 0x08, 0xaa, 0x5c, 0xbf, 0x7d, 0xce, 0x68, 0x99, 0x5b, 0xe1, 0x68, 0xc7, 0x7c, 0x71, 0x4e, 0x38, 0xa1, 0x75, 0xfa, 0x22, 0x8e, 0x07, 0x5e, 0x7f, 0xfd, 0xc6, 0x44, 0xbb, 0x1b, 0x6c, 0x31, 0x24, 0x7d, 0x4f, 0x08, 0xe3, 0xf6, 0x62, 0x38, 0x3f, 0x14, 0x00, 0x39, 0x69, 0x44, 0x7e, 0x1f, 0x49, 0x9a, 0x9c, 0xac, 0x7c, 0xe6, 0x7c, 0xe6, 0xe4, 0xcc, 0x71, 0x67, 0x0d, 0xf8, 0x83, 0x83, 0x9f, 0x79, 0x2a, 0x98, 0xd2, 0xe9, 0x3a, 0x1a, 0x85, 0xb6, 0x8a, 0x7f, + 0x65, 0x2f, 0xec, 0xbb, 0x1f, 0x55, 0xc3, 0x32, 0x01, 0x99, 0x1c, 0x72, 0x7f, 0x19, 0x39, 0x48, 0xc8, 0xd0, 0xc1, 0x93, 0x41, 0xea, 0x20, 0x87, 0x44, 0x44, 0x4e, 0xae, 0x57, 0x00, 0x74, 0xf6, 0x90, 0x16, 0xeb, 0x20, 0x86, 0x23, 0xe2, 0xb9, 0x33, 0xf9, 0x94, 0x70, 0x09, 0x79, 0xb8, 0xf7, 0x50, 0xc6, 0x3a, 0x8b, 0xa3, 0x2a, 0x50, 0xaf, 0x51, 0x1d, 0x31, 0x89, 0xdc, 0x01, 0x5c, 0x01, 0x85, 0xd9, 0xfb, 0x7c, 0x9b, 0x4f, 0x78, 0xde, 0x92, 0x4f, 0x9e, 0x38, 0xf8, 0xfa, 0xeb, 0x07, 0x4f, 0xf8, 0xd2, 0x16, 0xd0, 0xa1, 0x4b, 0xf9, 0x1e, 0x11, 0x2e, 0x49, 0x0e, 0x35, 0x0c, 0xf9, 0x3a, 0x49, 0x32, 0xec, 0x25, 0x85, 0xbf, 0x02, 0xa5, 0xd8, 0x55, 0x90, 0x16, 0xfe, 0x83, 0x86, 0x23, 0xf8, 0x83, 0xd3, 0x37, 0xf3, 0x54, 0xaf, 0x43, 0x57, 0x6c, 0x26, 0x24, 0xda, 0xc0, 0xdc, 0x0b, + 0xfb, 0x1a, 0x45, 0x11, 0x7d, 0x21, 0x00, 0x14, 0x38, 0xdd, 0x59, 0x0e, 0xa5, 0x15, 0x9a, 0x5a, 0x4f, 0xa0, 0x6a, 0x6f, 0x0a, 0x54, 0x93, 0x09, 0x75, 0x94, 0x14, 0x3b, 0xca, 0x7b, 0x79, 0xb1, 0xab, 0x11, 0x9f, 0x4a, 0xec, 0x6a, 0x65, 0x7a, 0x2b, 0x24, 0x6b, 0xee, 0x34, 0x33, 0xf7, 0x3e, 0x5f, 0x3d, 0xc5, 0x88, 0x72, 0x59, 0x84, 0xdf, 0x9e, 0x39, 0xd1, 0x95, 0xf9, 0x15, 0x09, 0xd8, 0xdf, 0x66, 0x4f, 0x31, 0xee, 0xb3, 0x9b, 0x20, 0x64, 0xdd, 0x18, 0x77, 0x14, 0xd2, 0x33, 0x1f, 0x00, 0xe2, 0xde, 0xc0, 0xb5, 0x19, 0x4a, 0x65, 0xb9, 0xe8, 0x72, 0x77, 0x23, 0x3c, 0xee, 0xaa, 0x1c, 0xee, 0x87, 0x59, 0x5d, 0x45, 0x4e, 0x74, 0xec, 0x88, 0xa8, 0x9a, 0x53, 0x59, 0xb7, 0xd4, 0x49, 0x87, 0x2f, 0xea, 0x78, 0xe0, 0xa7, 0xc2, 0x46, 0x72, 0xc7, 0x8d, 0x96, 0x10, 0xef, 0xd1, 0x83, + 0xcd, 0x86, 0x5a, 0x2f, 0xec, 0x63, 0x7a, 0x24, 0x3f, 0xec, 0xaf, 0xea, 0xe2, 0x1f, 0xbf, 0x3a, 0x73, 0x5c, 0xa6, 0x77, 0x9a, 0xc4, 0x2e, 0xf6, 0x3a, 0xf4, 0xb0, 0x87, 0x78, 0xef, 0xca, 0x78, 0x1c, 0x73, 0x95, 0x29, 0xa6, 0xa5, 0x5d, 0xbb, 0xbe, 0x6a, 0xd7, 0xae, 0x9c, 0xb3, 0x6b, 0x8d, 0x84, 0x51, 0x9c, 0xd1, 0x59, 0xbb, 0xb6, 0x5c, 0xe5, 0x27, 0xc3, 0xca, 0xf8, 0xb0, 0xe7, 0xd2, 0xdf, 0xcd, 0x70, 0xa7, 0x4e, 0x91, 0xef, 0xbe, 0x2e, 0x6d, 0x44, 0x84, 0x9c, 0x06, 0xd7, 0xf5, 0x6a, 0xf2, 0xe2, 0x8f, 0xd5, 0xe2, 0x9e, 0x93, 0xbf, 0x51, 0xa2, 0xf1, 0xa8, 0x4d, 0xc0, 0xe0, 0xd2, 0x4b, 0xf4, 0x38, 0xd4, 0x00, 0x50, 0xcb, 0xb8, 0x4d, 0x70, 0x26, 0x8d, 0xc7, 0x14, 0xbe, 0xd4, 0x94, 0xfc, 0x8d, 0x99, 0x1d, 0xaf, 0x8b, 0xed, 0x40, 0xd2, 0xf4, 0xb1, 0xba, 0xd2, 0x88, 0x78, + 0x26, 0x6f, 0x56, 0xb4, 0xc2, 0x36, 0x52, 0xc4, 0xcb, 0xa2, 0xa1, 0x49, 0x8f, 0x48, 0x7a, 0x04, 0x92, 0x74, 0xa3, 0x9e, 0x14, 0x89, 0xfc, 0xac, 0x5f, 0x44, 0x2a, 0x8f, 0x6e, 0x0c, 0x96, 0xa9, 0xb0, 0x4c, 0x86, 0x61, 0x4b, 0xec, 0x38, 0x19, 0xec, 0x0c, 0x32, 0x9f, 0x10, 0xb1, 0xb6, 0xe6, 0x50, 0xee, 0x39, 0xcf, 0xcc, 0xa2, 0xf3, 0xd1, 0xb3, 0xd1, 0xf9, 0xd9, 0x0f, 0x61, 0x42, 0xaf, 0x61, 0x31, 0xa5, 0xb7, 0x44, 0xfd, 0xe8, 0x8c, 0xcd, 0xa6, 0xf4, 0x6d, 0xe0, 0x2c, 0xb4, 0x1e, 0xfd, 0x9f, 0x31, 0x42, 0x6a, 0x7f, 0xce, 0xf3, 0x22, 0xb5, 0x9f, 0xdc, 0x4a, 0xca, 0x49, 0x48, 0xef, 0x8d, 0x4d, 0x75, 0x23, 0x57, 0xae, 0x91, 0xe8, 0x7d, 0x73, 0xda, 0xfc, 0xfa, 0xcc, 0xe0, 0x29, 0x7a, 0x3d, 0xa5, 0xe3, 0x25, 0x82, 0x7f, 0xcf, 0x72, 0x97, 0xaf, 0xdd, 0xc7, 0x5a, 0x0d, 0xb3, + 0x49, 0x3e, 0xfd, 0xe1, 0xc7, 0x6a, 0xe6, 0x24, 0x96, 0xa5, 0x56, 0x9e, 0x7e, 0x4f, 0x36, 0xc9, 0x3c, 0x04, 0x65, 0xb4, 0x71, 0xa0, 0x13, 0x27, 0xca, 0x10, 0x03, 0x34, 0xb5, 0xa8, 0xc8, 0x50, 0x32, 0xc2, 0x06, 0x37, 0x25, 0x35, 0x28, 0x89, 0xc4, 0xf0, 0x77, 0x66, 0xf6, 0xef, 0x73, 0x7e, 0x52, 0x50, 0x18, 0x56, 0x94, 0xc5, 0xe6, 0x3f, 0x39, 0x3c, 0x20, 0x72, 0x62, 0x12, 0xc5, 0x2b, 0x63, 0xe1, 0xd6, 0x87, 0xb2, 0xa1, 0x64, 0x32, 0xb1, 0x88, 0x13, 0x5f, 0x76, 0xc3, 0x7c, 0xc6, 0x8d, 0x01, 0x50, 0x12, 0xc8, 0xa3, 0x08, 0x83, 0x86, 0xa1, 0x51, 0x69, 0x03, 0xf1, 0x81, 0x12, 0x50, 0xfc, 0x19, 0xf7, 0x17, 0xeb, 0x3f, 0xe5, 0x56, 0x50, 0xbe, 0x13, 0xa1, 0xa6, 0x95, 0x1f, 0x42, 0x12, 0xb7, 0x6e, 0xf9, 0x68, 0x4d, 0x4d, 0x34, 0x68, 0x8d, 0x06, 0xc3, 0x09, 0x44, 0x55, 0x40, + 0xa9, 0x4a, 0x8d, 0xb8, 0x12, 0xd5, 0xd5, 0x06, 0x00, 0x29, 0x39, 0x11, 0x67, 0x7b, 0xc1, 0x4a, 0xd5, 0xba, 0xca, 0xe8, 0xa5, 0xc8, 0x7f, 0x8b, 0xb5, 0x4a, 0xec, 0x15, 0xfb, 0xa6, 0x25, 0x92, 0xf3, 0x8e, 0xad, 0x60, 0x23, 0xad, 0x09, 0x7f, 0x53, 0xcc, 0x46, 0x3e, 0xd4, 0x78, 0x59, 0x47, 0xd7, 0x89, 0xc1, 0x81, 0x7b, 0xc6, 0x6f, 0xf9, 0xdb, 0xd7, 0x27, 0x27, 0xbf, 0x0d, 0xf4, 0x0f, 0x55, 0x5c, 0x65, 0xbe, 0x68, 0x3e, 0xb8, 0xed, 0xf1, 0xcb, 0xfb, 0xfa, 0x0e, 0x3c, 0xbe, 0x2d, 0xdc, 0xe7, 0x0b, 0xb5, 0x25, 0xec, 0x4f, 0xdc, 0xbb, 0xf2, 0xe0, 0x58, 0x6c, 0xfb, 0xda, 0x48, 0xbf, 0xab, 0x65, 0xd8, 0x61, 0x0f, 0xd3, 0xe4, 0x7d, 0x23, 0x97, 0x4d, 0x2f, 0x74, 0x77, 0xfd, 0xe4, 0xaa, 0xc6, 0x73, 0x47, 0x73, 0x81, 0xc1, 0x5d, 0x33, 0x5e, 0xd6, 0xe0, 0x8f, 0x84, 0xc2, 0xe7, + 0xbc, 0x04, 0x74, 0x77, 0xdf, 0x0d, 0x74, 0x2f, 0x6d, 0xa3, 0x95, 0x1a, 0xe5, 0x62, 0x3e, 0xc3, 0xc8, 0x98, 0xa1, 0xeb, 0xbf, 0x7f, 0xf9, 0x95, 0xaf, 0x1d, 0x1d, 0x54, 0x28, 0x6a, 0xa6, 0xf7, 0x1d, 0x1f, 0x7b, 0xeb, 0x9d, 0xbe, 0xfd, 0x5f, 0x59, 0xf7, 0xa5, 0xd3, 0xc4, 0x84, 0x5a, 0xa9, 0x0f, 0xf0, 0xe8, 0x6c, 0x79, 0x84, 0x7b, 0xe9, 0x83, 0x70, 0x5f, 0xe8, 0xd0, 0xf9, 0xc5, 0x28, 0x67, 0x58, 0x68, 0xb6, 0x0f, 0x95, 0xea, 0x6b, 0x3a, 0x86, 0xc4, 0x2d, 0x0f, 0xbf, 0xeb, 0x08, 0x1d, 0x22, 0x16, 0xac, 0x5c, 0x66, 0x97, 0x44, 0xe1, 0x12, 0x45, 0x33, 0x93, 0xcf, 0xfc, 0x1f, 0xc8, 0xcd, 0xde, 0x11, 0xf2, 0xf4, 0x34, 0xfd, 0xdb, 0x8f, 0x9f, 0x15, 0x49, 0x05, 0xfd, 0xdb, 0xc7, 0x1f, 0x47, 0x6d, 0xe8, 0xe1, 0x11, 0x39, 0x0d, 0xdb, 0x70, 0x21, 0x6c, 0x0b, 0x17, 0xa7, 0xa2, + 0x20, 0xc5, 0x1c, 0xac, 0xc8, 0x83, 0x32, 0x50, 0x11, 0x05, 0x13, 0x7e, 0x1c, 0x8c, 0x87, 0xa8, 0x26, 0x10, 0x85, 0x40, 0x14, 0x9a, 0x05, 0x66, 0xb9, 0xfa, 0x11, 0xd9, 0x24, 0xdf, 0x47, 0x85, 0xb7, 0x7f, 0x8a, 0xaa, 0x8e, 0xcd, 0x7c, 0xf0, 0x6d, 0x97, 0x4f, 0x0b, 0x1a, 0x4c, 0x5e, 0xb5, 0xda, 0x65, 0x02, 0x05, 0xb5, 0xdf, 0xf9, 0xcc, 0xcc, 0x07, 0xa4, 0xff, 0x2e, 0xe1, 0x00, 0xb8, 0xfc, 0xae, 0x45, 0x54, 0xcc, 0x65, 0x16, 0x34, 0xfe, 0x06, 0xa7, 0xb3, 0xc1, 0x0f, 0xfe, 0x6c, 0xf4, 0x7e, 0x72, 0x4a, 0xc4, 0xf4, 0x85, 0x7c, 0x7e, 0x08, 0xf3, 0xca, 0xfa, 0x62, 0x8a, 0xb7, 0x73, 0x6a, 0x9a, 0xa1, 0x50, 0xa7, 0x28, 0xca, 0x8e, 0x55, 0x3e, 0x87, 0x54, 0xb8, 0xa2, 0x4c, 0x2c, 0xfd, 0x84, 0x3f, 0xea, 0x8f, 0xf2, 0x01, 0x34, 0xf8, 0x6a, 0x06, 0x2f, 0x09, 0x38, 0x4a, 0xb1, + 0x8f, 0x12, 0xf3, 0x81, 0x8c, 0x6b, 0x08, 0x92, 0xce, 0x25, 0x56, 0x97, 0x42, 0xb8, 0x5e, 0xa1, 0x16, 0x4e, 0xa8, 0x1c, 0xa6, 0x7f, 0x05, 0xbf, 0x13, 0x62, 0x3f, 0xb1, 0x5a, 0x14, 0x60, 0x9b, 0x42, 0x07, 0x96, 0x29, 0x2d, 0xec, 0xbf, 0x0b, 0x11, 0x70, 0x89, 0x44, 0x56, 0xed, 0x3a, 0x72, 0xc4, 0x6e, 0x9c, 0xf9, 0xbf, 0x3a, 0x3b, 0xf9, 0x4b, 0xf2, 0x69, 0x9d, 0x6e, 0xe6, 0xfb, 0x0e, 0x0b, 0x38, 0x6d, 0x30, 0xcc, 0x2c, 0x9a, 0xf9, 0x36, 0xe6, 0x3d, 0x90, 0xb6, 0xbf, 0x8f, 0x69, 0x7b, 0x5d, 0xb1, 0x16, 0x85, 0xfc, 0x50, 0xe3, 0x32, 0xdc, 0x59, 0x85, 0x9c, 0x64, 0x18, 0xb0, 0x92, 0xf8, 0x6c, 0xca, 0xce, 0xe3, 0x95, 0xe2, 0x45, 0xca, 0xfe, 0x3e, 0x5c, 0xae, 0x8f, 0x0f, 0x0b, 0x1b, 0x69, 0x35, 0xe4, 0x36, 0x4f, 0x9d, 0x92, 0x7a, 0x01, 0x19, 0xcb, 0x57, 0x3f, 0xfe, 0x26, + 0x22, 0x15, 0x62, 0x0c, 0xc0, 0xbd, 0x0c, 0x8a, 0xaf, 0x75, 0x20, 0x79, 0xc2, 0xaa, 0x20, 0x71, 0xd1, 0x68, 0x92, 0x82, 0xb2, 0x20, 0xb3, 0x53, 0x9c, 0x29, 0x34, 0x47, 0x73, 0xdb, 0x76, 0x10, 0x0e, 0xab, 0x99, 0x63, 0x67, 0x4d, 0x14, 0xab, 0x03, 0xb8, 0x3a, 0x29, 0x6b, 0xf2, 0x00, 0x9c, 0x1a, 0xc0, 0xac, 0x40, 0x9c, 0x65, 0xcf, 0xff, 0x79, 0xf6, 0x8e, 0x9d, 0x6d, 0xa7, 0x06, 0x4f, 0xbe, 0x7f, 0x5f, 0x99, 0xbd, 0x6c, 0xf0, 0xf8, 0xe9, 0xd4, 0xe4, 0x1d, 0xbb, 0xc0, 0x29, 0xa1, 0x66, 0xed, 0x05, 0x75, 0x5c, 0x8c, 0xbc, 0x13, 0xf5, 0x06, 0xf7, 0x07, 0xee, 0x23, 0x84, 0xcf, 0x67, 0x47, 0x9e, 0x15, 0x9b, 0x85, 0xa4, 0x19, 0x54, 0xac, 0x8a, 0x26, 0x07, 0x4b, 0x58, 0xc4, 0x90, 0xe5, 0xa0, 0x5e, 0xa1, 0xaa, 0x37, 0x48, 0xbf, 0x70, 0x0e, 0xe1, 0xee, 0xa0, 0x4e, 0xd9, 0x09, + 0x3b, 0xcb, 0x99, 0x59, 0x93, 0xa4, 0x5f, 0x04, 0x58, 0x94, 0xe8, 0x91, 0x93, 0x95, 0xd2, 0xe8, 0x50, 0xb9, 0x54, 0xd8, 0x2f, 0xfa, 0xb2, 0xd7, 0xcf, 0xdb, 0x71, 0x62, 0xf3, 0x23, 0xfb, 0x7b, 0x4f, 0xed, 0xbf, 0xfa, 0xc4, 0xf4, 0x7e, 0x1c, 0x77, 0xb7, 0xc1, 0xcb, 0x0a, 0x1f, 0x0a, 0x54, 0x64, 0xf2, 0xee, 0xbd, 0xa8, 0x4f, 0xfb, 0xf6, 0xcb, 0x40, 0x9b, 0xf0, 0x3d, 0x9d, 0x8f, 0xbc, 0xb3, 0x14, 0xff, 0xf4, 0x30, 0xec, 0x13, 0x87, 0xe2, 0xdf, 0xf4, 0x28, 0xe0, 0x09, 0x85, 0x49, 0x52, 0xa8, 0x7c, 0x13, 0x8a, 0xbb, 0x96, 0x36, 0x13, 0x18, 0x66, 0xad, 0x66, 0x23, 0x2d, 0xc3, 0x71, 0xbe, 0x1e, 0x0a, 0xc7, 0x5f, 0xd5, 0x02, 0x31, 0x5f, 0x1c, 0x5c, 0xb3, 0xfb, 0xc7, 0x5f, 0xbf, 0x71, 0xba, 0xe1, 0xf5, 0xb6, 0x23, 0xff, 0x7d, 0xef, 0xa9, 0x53, 0x54, 0xb3, 0x87, 0xc7, 0x13, + 0x20, 0xc4, 0xc0, 0xcf, 0xf0, 0x04, 0xcc, 0xac, 0x93, 0xe8, 0xb6, 0xfd, 0xf4, 0xfb, 0xb2, 0xe5, 0xcc, 0x63, 0x84, 0x8a, 0xe8, 0x26, 0x16, 0x14, 0x07, 0x9b, 0x00, 0x80, 0x2c, 0x0f, 0xf9, 0x5f, 0x4a, 0xd6, 0x2f, 0x85, 0x92, 0x84, 0x8a, 0x6a, 0x19, 0xe3, 0xf5, 0xff, 0xe1, 0xed, 0x3d, 0xe0, 0xdb, 0xaa, 0xce, 0xfe, 0xf1, 0x7b, 0xee, 0xd0, 0xde, 0xdb, 0x92, 0x65, 0x6b, 0x4b, 0x96, 0x65, 0x5b, 0xb6, 0x6c, 0xc9, 0xdb, 0xb2, 0xe3, 0x3d, 0x13, 0x6f, 0xc7, 0x76, 0x96, 0x13, 0x67, 0x39, 0x89, 0xe3, 0x0c, 0xb2, 0xc8, 0x26, 0x10, 0x32, 0x58, 0x01, 0xc2, 0x4a, 0x09, 0x23, 0x40, 0xc2, 0x08, 0x90, 0xb0, 0xc2, 0x48, 0x49, 0x4a, 0x0b, 0x94, 0xf6, 0x2d, 0x2d, 0xb4, 0x6f, 0x79, 0xcb, 0x5b, 0x0a, 0x1d, 0x6f, 0x29, 0x2d, 0xd0, 0xd2, 0x02, 0x89, 0x6f, 0x7e, 0xe7, 0x9c, 0x7b, 0x25, 0xcb, + 0x23, 0x8b, 0xf6, 0xff, 0xe7, 0x43, 0x12, 0xe9, 0xea, 0x9c, 0x73, 0xcf, 0x7c, 0xd6, 0xf9, 0x3e, 0xcf, 0x83, 0x54, 0x6e, 0x1b, 0xb2, 0x96, 0xe1, 0xbb, 0x35, 0xbb, 0x00, 0x4e, 0x44, 0xe5, 0xb4, 0x50, 0x76, 0x46, 0xba, 0xd3, 0x91, 0x6a, 0x4d, 0x32, 0xaa, 0x95, 0xd8, 0x27, 0x48, 0x22, 0x88, 0x85, 0x69, 0x45, 0x86, 0xe8, 0x38, 0x3c, 0x7d, 0x2c, 0x4e, 0xab, 0x81, 0xcf, 0xb4, 0x01, 0x48, 0x6f, 0xec, 0xea, 0xd3, 0x88, 0xc3, 0x1d, 0x21, 0xc8, 0x27, 0x73, 0x63, 0xfe, 0xa2, 0xdb, 0x7a, 0xe7, 0xde, 0x3d, 0x5c, 0x2d, 0xfb, 0x40, 0xb5, 0xf8, 0xe8, 0x9f, 0xf6, 0x6f, 0xff, 0xcd, 0xfd, 0xbd, 0xc9, 0xee, 0xf3, 0x4e, 0xa6, 0x66, 0xf3, 0xd3, 0x2b, 0x66, 0xef, 0x1e, 0xa8, 0x4a, 0x56, 0x9b, 0x45, 0xd1, 0xc8, 0x8e, 0x5b, 0xef, 0x69, 0xb9, 0xf9, 0x9f, 0xcf, 0x2f, 0x15, 0x88, 0x4a, 0x57, + 0x75, 0xe7, 0xaa, 0x75, 0x2b, 0x92, 0xb4, 0xca, 0xe6, 0x0a, 0x6a, 0xe6, 0xac, 0x7b, 0x96, 0x97, 0x14, 0x2f, 0xbb, 0x67, 0xce, 0xb6, 0x0f, 0xee, 0xef, 0xed, 0x7c, 0xf0, 0x6f, 0x77, 0x95, 0x05, 0xae, 0x7b, 0x73, 0xd7, 0xb4, 0x40, 0x6e, 0x40, 0x21, 0x9d, 0x5e, 0xda, 0x91, 0x67, 0x9a, 0xf5, 0x02, 0x50, 0x1c, 0x37, 0x14, 0xcc, 0x41, 0xc6, 0x0c, 0x3d, 0x93, 0x22, 0x55, 0x64, 0xa6, 0x16, 0x65, 0xa2, 0xb9, 0x60, 0x8f, 0xf1, 0x73, 0x51, 0x45, 0xb4, 0x46, 0x5b, 0x8a, 0xf8, 0xb9, 0xc8, 0xf0, 0x93, 0x02, 0x94, 0x82, 0xf1, 0x72, 0x13, 0x82, 0x54, 0x31, 0x2b, 0x9a, 0x90, 0xaa, 0xca, 0xdc, 0x9c, 0xc0, 0x77, 0x98, 0x10, 0xc0, 0x47, 0x66, 0xfd, 0xce, 0x33, 0xd2, 0xf7, 0xd7, 0x1d, 0x67, 0xb7, 0xbc, 0x77, 0xa8, 0x8f, 0xb9, 0x8a, 0x09, 0x59, 0x38, 0xa8, 0x40, 0x5e, 0x46, 0x8a, 0xa5, + 0xcf, 0xb1, 0x0f, 0x4c, 0x9a, 0x13, 0x24, 0x27, 0xdd, 0xcd, 0xa4, 0xc1, 0xbd, 0x12, 0x24, 0xae, 0xe5, 0xef, 0xeb, 0xa0, 0x2c, 0x24, 0x50, 0x01, 0x2e, 0x12, 0x78, 0xec, 0x0b, 0x19, 0xbf, 0xaf, 0x4b, 0x25, 0x04, 0x04, 0x45, 0x0b, 0x28, 0x14, 0x6a, 0x07, 0x51, 0x60, 0x9a, 0x36, 0x8f, 0x99, 0xbb, 0xac, 0x04, 0x4e, 0x28, 0x05, 0x8f, 0x15, 0x8d, 0x2c, 0x38, 0x93, 0x4b, 0xc6, 0x0c, 0x5d, 0x56, 0x94, 0xd2, 0x5b, 0x8a, 0xd4, 0x1f, 0xa8, 0xcc, 0x66, 0x60, 0x5d, 0x16, 0x0a, 0xb5, 0xcc, 0xd4, 0x9a, 0xac, 0x7d, 0xbc, 0x2e, 0x44, 0x8d, 0x58, 0x1c, 0x65, 0x9b, 0x56, 0xcc, 0xaa, 0xf2, 0x36, 0xee, 0x79, 0x63, 0xc3, 0xbc, 0x21, 0x19, 0x96, 0x6f, 0xa0, 0x3e, 0x3b, 0x2b, 0xb7, 0xe9, 0xda, 0x43, 0x33, 0xd8, 0x0f, 0x13, 0x75, 0x22, 0xaa, 0xd5, 0x67, 0x14, 0x24, 0xe7, 0xb5, 0xae, 0xba, + 0x67, 0x60, 0xff, 0x47, 0xf7, 0xb6, 0x5b, 0xb6, 0x68, 0x0c, 0xaa, 0x9c, 0xc1, 0x43, 0x43, 0x3b, 0x1e, 0xe8, 0x49, 0x4d, 0x54, 0x8b, 0xb0, 0x0f, 0xe0, 0xb7, 0x90, 0x76, 0x7d, 0x82, 0x69, 0xd7, 0x62, 0x4e, 0x96, 0x70, 0xc1, 0xfe, 0xf2, 0x56, 0x07, 0x2c, 0x99, 0x82, 0x5e, 0x4e, 0x1a, 0xc6, 0x5f, 0x70, 0x58, 0x1f, 0x3c, 0x62, 0xdf, 0x58, 0x31, 0x22, 0xfe, 0x33, 0x16, 0x24, 0x9c, 0xe3, 0x8b, 0xf6, 0x40, 0x99, 0x13, 0x13, 0x3b, 0x83, 0xc3, 0xc3, 0xcb, 0xb4, 0x6e, 0x6c, 0x2e, 0xb2, 0x4b, 0x48, 0x34, 0x72, 0x2e, 0x39, 0xb6, 0x19, 0x68, 0xed, 0x6a, 0xe6, 0x93, 0xbb, 0x7e, 0x3a, 0xf2, 0x7d, 0xb6, 0x48, 0x91, 0x5d, 0xd9, 0xb9, 0x70, 0x6d, 0xc5, 0xda, 0x1a, 0x4f, 0x53, 0x4d, 0x79, 0xae, 0x5b, 0x42, 0xcd, 0x7f, 0x04, 0x59, 0x7d, 0xe8, 0xaf, 0xd8, 0xcf, 0xd8, 0x21, 0xf6, 0xe3, + 0x13, 0x83, 0x6e, 0xff, 0xf5, 0xd6, 0xde, 0x63, 0xc0, 0x02, 0xee, 0x05, 0xa9, 0xe7, 0xa4, 0xbc, 0x3e, 0x7a, 0x80, 0xa9, 0x83, 0xe3, 0xb0, 0x12, 0x8f, 0x70, 0x98, 0x21, 0x11, 0xf6, 0x73, 0xb0, 0x9c, 0xe4, 0x7c, 0x2a, 0xf8, 0x95, 0xb4, 0xf1, 0xeb, 0x33, 0x17, 0x73, 0x83, 0xa9, 0x96, 0x32, 0x6e, 0xb9, 0x1c, 0xb7, 0x9e, 0xe3, 0xca, 0x27, 0x2c, 0x28, 0xb6, 0x5c, 0x8e, 0x2f, 0xc9, 0x5b, 0x30, 0x13, 0x2a, 0xa0, 0xdc, 0x23, 0x38, 0x86, 0x91, 0x95, 0xb0, 0x22, 0x09, 0xd7, 0xa1, 0x73, 0x23, 0x6e, 0x03, 0xd4, 0x93, 0x56, 0x3e, 0x2e, 0xe7, 0x33, 0x75, 0x45, 0xab, 0x8f, 0xae, 0x88, 0xdb, 0x2d, 0xa2, 0xf3, 0x2a, 0x9d, 0x31, 0xa9, 0xff, 0xdb, 0x5f, 0x8e, 0x3c, 0xb3, 0x31, 0x9a, 0xbc, 0x05, 0x0a, 0xae, 0xa9, 0xd5, 0x2b, 0x67, 0x90, 0x49, 0x63, 0x1a, 0x00, 0xaf, 0x97, 0x0b, 0x10, + 0xce, 0x54, 0x39, 0x59, 0xb7, 0xe1, 0xd6, 0x11, 0x69, 0x38, 0xb1, 0xb5, 0xe1, 0x52, 0x6f, 0x70, 0x1c, 0x50, 0x8d, 0x39, 0x20, 0xb7, 0x32, 0x63, 0x8a, 0x4d, 0x3a, 0x5a, 0x91, 0x98, 0x62, 0x83, 0x16, 0x61, 0x9c, 0x5a, 0x83, 0x79, 0xdf, 0x31, 0xcc, 0x6b, 0x30, 0xef, 0x4b, 0x92, 0x90, 0x14, 0x72, 0x39, 0xa3, 0xe0, 0x94, 0x50, 0xe4, 0x30, 0xce, 0x34, 0x86, 0x25, 0x16, 0x8c, 0x93, 0x4e, 0x90, 0x90, 0xe0, 0x76, 0x40, 0xac, 0x8f, 0x97, 0x90, 0x9c, 0x1c, 0xf7, 0x0b, 0x4d, 0xe0, 0x7e, 0xf4, 0xd6, 0x3f, 0x43, 0xe6, 0xfb, 0xab, 0x04, 0xe6, 0x87, 0xf8, 0x0c, 0xfb, 0x67, 0xab, 0x03, 0x9c, 0x07, 0xa7, 0x26, 0xb3, 0xbf, 0xd8, 0xf8, 0x85, 0xb0, 0x83, 0x84, 0x0b, 0xd9, 0x25, 0x92, 0x95, 0x90, 0xed, 0x21, 0x5e, 0x4c, 0x0b, 0x68, 0x52, 0x30, 0xcc, 0x24, 0x4c, 0x03, 0x46, 0x34, 0x8d, + 0x4d, 0x83, 0x8b, 0x70, 0xa5, 0xdb, 0xf5, 0xe1, 0xc4, 0xfd, 0x89, 0x2e, 0x56, 0x71, 0x5a, 0xcf, 0x84, 0xfc, 0x99, 0x38, 0xdb, 0x1d, 0x67, 0xd3, 0x14, 0xae, 0x87, 0x53, 0x73, 0x84, 0x22, 0x59, 0x47, 0x52, 0x6e, 0x4a, 0xae, 0x89, 0x4d, 0x12, 0x9c, 0x3a, 0x94, 0x9c, 0xaa, 0x00, 0xbf, 0x55, 0x98, 0xa4, 0xfa, 0x64, 0xf0, 0x4b, 0x79, 0x6a, 0xf2, 0x21, 0x3c, 0x63, 0x14, 0x6b, 0xcc, 0xb5, 0x9c, 0xbf, 0x2d, 0x3b, 0x9b, 0x1a, 0xce, 0xcf, 0x3e, 0x4f, 0x32, 0xf7, 0x4a, 0x93, 0xce, 0x3f, 0x64, 0x2e, 0xb4, 0x16, 0x45, 0xa8, 0xde, 0x24, 0x29, 0xc7, 0xb3, 0x49, 0xa2, 0xed, 0xc2, 0x17, 0x4c, 0x13, 0xa6, 0xd3, 0xb9, 0x97, 0xcc, 0x7b, 0x65, 0x8b, 0x5f, 0xd6, 0xd8, 0x11, 0x40, 0x38, 0x27, 0x88, 0x12, 0x83, 0x59, 0x92, 0x78, 0x92, 0x2c, 0x42, 0x24, 0x39, 0x16, 0x2e, 0x95, 0xbb, 0x38, + 0x05, 0x08, 0x28, 0x99, 0x90, 0x96, 0x8d, 0xc3, 0x55, 0xd1, 0x58, 0x26, 0xb3, 0xc7, 0x29, 0xf0, 0xb9, 0xd1, 0xc8, 0x8e, 0x5b, 0xee, 0x6d, 0x39, 0x70, 0xfe, 0xf9, 0x85, 0x7d, 0x4f, 0x7e, 0x73, 0xdf, 0xf6, 0xff, 0x39, 0xdc, 0x2b, 0x50, 0x26, 0x69, 0xa9, 0x0f, 0x31, 0x31, 0x8e, 0x0e, 0xb6, 0x55, 0xe4, 0xa6, 0x6b, 0x20, 0x39, 0x26, 0x6b, 0x63, 0x74, 0x97, 0xdc, 0x5d, 0xd2, 0x99, 0x67, 0x9a, 0xff, 0x3a, 0xfb, 0xbb, 0x17, 0x6f, 0x67, 0xd9, 0x57, 0x97, 0x23, 0x1a, 0x9c, 0x9a, 0x66, 0x92, 0x40, 0x2a, 0x5c, 0xa9, 0x4f, 0x75, 0xa5, 0xea, 0x15, 0x78, 0x7f, 0x04, 0x2f, 0x7c, 0x41, 0x97, 0xc3, 0xf5, 0xc8, 0x41, 0x91, 0xcb, 0xb2, 0x20, 0xe7, 0xf1, 0x42, 0x96, 0xaf, 0xc4, 0x71, 0x94, 0x08, 0x14, 0x66, 0x9e, 0x8b, 0x87, 0x4d, 0xa3, 0x78, 0xd8, 0xbc, 0xdb, 0x30, 0xde, 0x30, 0xa0, + 0x8f, 0xc0, 0x9a, 0x10, 0xd4, 0x07, 0xb3, 0x7c, 0x2a, 0x81, 0xc0, 0x3c, 0x16, 0x29, 0x33, 0x06, 0x14, 0x33, 0x18, 0x39, 0x3e, 0xc3, 0xa7, 0xbd, 0x40, 0xcb, 0x15, 0x8b, 0x8d, 0xb9, 0x60, 0xcb, 0xc9, 0x55, 0x79, 0xbd, 0x77, 0xbf, 0xbd, 0xb2, 0xee, 0xc6, 0x8d, 0x8b, 0xd3, 0x5a, 0xa5, 0x5a, 0x81, 0x4c, 0xa7, 0x55, 0xe5, 0x35, 0xcd, 0x2f, 0x6b, 0xdf, 0xda, 0x9d, 0xe1, 0x69, 0xdf, 0x3d, 0x70, 0x4c, 0xa1, 0x06, 0x1b, 0x46, 0x87, 0xa8, 0x57, 0xa4, 0x06, 0x6a, 0x1d, 0x53, 0x3e, 0xb4, 0x7f, 0xc6, 0xea, 0x97, 0xb6, 0x57, 0xa7, 0x14, 0xb4, 0xe6, 0xcd, 0x92, 0xd1, 0x22, 0x95, 0x26, 0xe0, 0x35, 0xe7, 0x74, 0x0e, 0x97, 0x45, 0xfa, 0x2b, 0xbd, 0x7d, 0x6a, 0x97, 0x1a, 0xac, 0x7c, 0xa2, 0x45, 0x93, 0xa6, 0x83, 0xba, 0x6b, 0x0f, 0x5c, 0xa7, 0x4f, 0x99, 0xc7, 0xe1, 0x6c, 0xeb, 0x89, + 0xb4, 0x8b, 0xc4, 0x7a, 0xb1, 0xc5, 0x03, 0x98, 0xdb, 0x99, 0x46, 0xa3, 0x01, 0x79, 0x18, 0xdb, 0x53, 0x0d, 0x69, 0xc6, 0x34, 0xa9, 0x5e, 0xaa, 0xe7, 0x57, 0x4a, 0x8c, 0x56, 0x8a, 0xf7, 0x30, 0xe6, 0xf3, 0x19, 0x0b, 0x01, 0x0f, 0x6b, 0x15, 0xc0, 0xd5, 0x71, 0x79, 0x38, 0x35, 0x0a, 0xe4, 0x89, 0x9b, 0xb6, 0x3e, 0xb9, 0x78, 0xf1, 0x93, 0x5b, 0x9a, 0xc4, 0xa4, 0xac, 0x65, 0xf3, 0xd1, 0x85, 0x83, 0x47, 0xb7, 0x4c, 0x97, 0x81, 0x4f, 0xef, 0x07, 0x82, 0x97, 0x17, 0x2e, 0x7c, 0x99, 0xfd, 0xf6, 0xfe, 0xef, 0xb1, 0xdf, 0x9c, 0x5a, 0xb4, 0xe8, 0x14, 0x10, 0x92, 0x37, 0xae, 0x7c, 0x76, 0x53, 0x79, 0xf9, 0xa6, 0x67, 0x57, 0x1e, 0x5e, 0x7a, 0x74, 0x6d, 0x29, 0xc2, 0xb7, 0x90, 0xdb, 0x96, 0x9d, 0x61, 0xbf, 0xbc, 0xff, 0x30, 0xfb, 0xf7, 0x33, 0x43, 0x43, 0x67, 0x80, 0xfc, 0xf0, + 0xfd, 0x40, 0x71, 0x66, 0x19, 0x77, 0x4e, 0x68, 0x23, 0xb6, 0x91, 0x43, 0x5d, 0x83, 0x89, 0xe5, 0x56, 0x9f, 0x52, 0x9d, 0x91, 0x11, 0x32, 0xa4, 0xcd, 0x08, 0x90, 0x54, 0x96, 0xa0, 0xcd, 0xe8, 0xc1, 0xf5, 0x48, 0x97, 0xf9, 0x14, 0x99, 0x9b, 0xcf, 0x3d, 0xc3, 0x49, 0xa5, 0x9c, 0xbd, 0xd4, 0xcd, 0x1e, 0xc0, 0xf6, 0x6c, 0x07, 0xf1, 0x63, 0x7c, 0xcd, 0x79, 0x52, 0x09, 0x10, 0x59, 0xb0, 0xa0, 0x7f, 0x19, 0x10, 0x8b, 0x94, 0x9b, 0x0e, 0x29, 0xb3, 0x90, 0xd3, 0x6c, 0x50, 0x06, 0x3c, 0x71, 0x0f, 0x21, 0x16, 0xe3, 0xb5, 0xc7, 0x42, 0x87, 0xb9, 0x21, 0xae, 0xf4, 0x5a, 0xa9, 0x18, 0x59, 0x86, 0xca, 0xab, 0x00, 0x41, 0x83, 0x86, 0x2f, 0x53, 0x35, 0xa6, 0xd3, 0xa2, 0x9a, 0xd1, 0xac, 0xc9, 0x95, 0x20, 0x5d, 0x84, 0xd2, 0xf9, 0xec, 0xa9, 0xea, 0xe2, 0x58, 0xfd, 0x3a, 0x4e, 0x8f, + 0x41, 0xc1, 0x39, 0xb4, 0x76, 0xb7, 0x1b, 0x12, 0x47, 0x19, 0xd2, 0x79, 0xb1, 0xb9, 0x3d, 0x34, 0x45, 0xb0, 0x11, 0xfb, 0x98, 0x09, 0xbe, 0x93, 0x3d, 0xfa, 0x2b, 0x6b, 0xf9, 0xc2, 0x7a, 0x7f, 0x63, 0x86, 0x52, 0x68, 0x96, 0xc9, 0x33, 0x6d, 0xd1, 0x20, 0x3b, 0xfa, 0xe5, 0x97, 0xbc, 0x51, 0x9e, 0x55, 0x7d, 0x5b, 0xb6, 0xb0, 0x3e, 0x4d, 0xa3, 0x9d, 0x6e, 0x52, 0x2a, 0x3b, 0x9b, 0x8a, 0x79, 0x13, 0x3d, 0xd2, 0xb3, 0x0e, 0x30, 0xfd, 0x58, 0x26, 0x79, 0x9a, 0x9b, 0x1e, 0x79, 0x96, 0xd7, 0x9e, 0x44, 0x0b, 0x19, 0x31, 0x2f, 0x96, 0x24, 0x7c, 0x27, 0xe3, 0xd1, 0x86, 0x53, 0x38, 0x9d, 0x10, 0xd1, 0x74, 0xaa, 0x0f, 0xa9, 0x1a, 0x53, 0x72, 0x33, 0x77, 0xec, 0x76, 0x6d, 0x72, 0xe9, 0x71, 0xbc, 0xcc, 0x31, 0xe1, 0x16, 0x6e, 0x52, 0x71, 0x2a, 0xc6, 0xc9, 0x82, 0x44, 0x10, 0x2a, + 0x79, 0x4e, 0x38, 0x37, 0x98, 0x6b, 0x4c, 0x15, 0xb0, 0xed, 0x52, 0x3a, 0x5f, 0x7f, 0xec, 0xd6, 0x4d, 0x68, 0x86, 0x42, 0x19, 0x9c, 0xa1, 0x4d, 0x97, 0x57, 0x00, 0x37, 0x3d, 0xfc, 0x2e, 0xba, 0x6d, 0x8b, 0xcf, 0xdc, 0x25, 0x14, 0xc1, 0x17, 0xd1, 0x7d, 0x30, 0x3c, 0x81, 0x05, 0x70, 0x3e, 0x6d, 0x08, 0x99, 0x64, 0x80, 0xfb, 0x8f, 0xc1, 0xf3, 0x06, 0x75, 0x41, 0x21, 0x82, 0x73, 0x92, 0xf3, 0x38, 0x66, 0x00, 0x38, 0x23, 0x24, 0x32, 0x49, 0x43, 0xb9, 0xcc, 0x86, 0xa4, 0x32, 0x7b, 0x62, 0xd7, 0x11, 0x49, 0x89, 0xf7, 0x1e, 0xf9, 0x3f, 0xc3, 0x2d, 0xc0, 0x14, 0xfc, 0x22, 0xb1, 0xbb, 0x6f, 0x80, 0x67, 0xd8, 0xfb, 0xc1, 0x7c, 0x76, 0xd6, 0x2f, 0x8c, 0x7a, 0xbe, 0xcb, 0x9a, 0x94, 0xb7, 0xd9, 0x4e, 0xb0, 0x88, 0x3d, 0x92, 0xd0, 0xc9, 0xfd, 0x87, 0xc9, 0x65, 0xb1, 0x5e, 0xda, + 0x46, 0x6f, 0x3b, 0x0c, 0x27, 0x38, 0x82, 0x28, 0x20, 0x3e, 0x83, 0x16, 0x1c, 0x91, 0x89, 0xf7, 0xfb, 0xe5, 0xb8, 0x35, 0x0e, 0xcb, 0xd2, 0xc7, 0x69, 0xfb, 0x72, 0x39, 0x41, 0xc8, 0x2d, 0x72, 0x8b, 0xd9, 0xa4, 0x52, 0xc0, 0xd2, 0x52, 0xbb, 0x10, 0x52, 0x0f, 0xb7, 0x3a, 0x6e, 0x56, 0x81, 0x3b, 0x93, 0x27, 0xef, 0x90, 0x31, 0x51, 0x99, 0xbf, 0x9e, 0xb1, 0xb1, 0xcd, 0x9f, 0xd6, 0xba, 0xb1, 0x0d, 0x3c, 0xc5, 0xce, 0xb8, 0xf1, 0xc7, 0xbb, 0xca, 0xab, 0x6f, 0x78, 0x7b, 0xe7, 0xaf, 0x0f, 0x93, 0xf5, 0x81, 0x96, 0xe5, 0xe5, 0x15, 0x4b, 0x1a, 0xd2, 0xe8, 0x73, 0xdf, 0xce, 0x8f, 0xae, 0x3d, 0xb2, 0x60, 0xc1, 0x63, 0x1b, 0x2a, 0x49, 0x25, 0xe6, 0x3f, 0x91, 0x0b, 0x5f, 0x31, 0x5f, 0xc1, 0xbe, 0xa8, 0x88, 0x32, 0x30, 0x70, 0x32, 0x05, 0x40, 0x75, 0x80, 0x77, 0x0b, 0x71, 0x89, + 0x01, 0x0a, 0x94, 0x2a, 0x84, 0x7a, 0x1a, 0x8d, 0x8e, 0x10, 0x8d, 0x6d, 0x46, 0x48, 0xa7, 0x8d, 0xb9, 0xb9, 0xdb, 0xc9, 0xd8, 0x4d, 0xf8, 0xe5, 0xcb, 0x3a, 0xe3, 0x69, 0xb3, 0xae, 0xb4, 0x5d, 0xf3, 0x55, 0xb4, 0x9b, 0x7c, 0x15, 0xed, 0x7a, 0xae, 0xb8, 0xdd, 0x68, 0xe6, 0x45, 0x8a, 0xc5, 0x1d, 0xfd, 0x9d, 0xe8, 0x9e, 0x3a, 0x5e, 0x81, 0xbf, 0xa5, 0xb6, 0xa8, 0xd5, 0x80, 0x50, 0x97, 0xa9, 0x4b, 0x43, 0xd9, 0xe8, 0x3a, 0xd5, 0x61, 0x4b, 0x36, 0x4b, 0xc5, 0x84, 0x0a, 0xa8, 0x24, 0x82, 0x31, 0x04, 0x3e, 0x27, 0xf7, 0x31, 0x63, 0x61, 0xa0, 0xc7, 0x58, 0x35, 0xbe, 0xfc, 0x19, 0x0b, 0xf4, 0x82, 0xcc, 0x66, 0x64, 0x3b, 0x5a, 0x5a, 0x6f, 0xfd, 0xa2, 0x8a, 0xd2, 0x79, 0x95, 0x2e, 0xf2, 0x71, 0x46, 0x22, 0x64, 0xb2, 0xba, 0xb7, 0xb7, 0x0e, 0xdd, 0xec, 0x28, 0x7c, 0x7b, 0xe7, + 0xe0, 0x43, 0xab, 0x4b, 0xf1, 0x52, 0xbf, 0xd7, 0x50, 0x1f, 0x28, 0x71, 0xab, 0x19, 0x21, 0xe3, 0xb1, 0x93, 0x9f, 0x06, 0xa6, 0x2f, 0xaf, 0x28, 0x5f, 0xda, 0xe8, 0x77, 0x96, 0x76, 0x85, 0xc0, 0xc0, 0x66, 0x67, 0x4d, 0x45, 0x91, 0x61, 0xe6, 0xbe, 0xb9, 0xa1, 0x59, 0xbd, 0xd3, 0xe7, 0x95, 0x5f, 0xf3, 0xf0, 0xc0, 0xc2, 0x63, 0x1b, 0xe1, 0x2e, 0x38, 0xf7, 0xda, 0xd0, 0x6c, 0x6b, 0x64, 0x7a, 0x28, 0xb5, 0x28, 0xe4, 0x93, 0xa5, 0x7a, 0x70, 0xbe, 0x79, 0xc8, 0xeb, 0xde, 0x67, 0x8e, 0x11, 0xb5, 0x44, 0x43, 0xb4, 0xd6, 0x0f, 0x69, 0x77, 0x1e, 0xce, 0xed, 0x07, 0x37, 0x2d, 0x0a, 0xa8, 0x81, 0xe2, 0x96, 0x03, 0x01, 0x40, 0x40, 0x79, 0x48, 0x53, 0x85, 0x24, 0x36, 0xee, 0x63, 0xae, 0x67, 0x8b, 0x03, 0xe5, 0xed, 0x58, 0xb8, 0xaa, 0x25, 0x6a, 0x52, 0xdd, 0x4e, 0x9f, 0xdd, 0x97, + 0x86, 0xe5, 0xde, 0x44, 0xaf, 0x47, 0x3e, 0x63, 0xd5, 0x98, 0x4d, 0x97, 0xcb, 0x4f, 0x3a, 0x2e, 0xe6, 0x6c, 0x0a, 0x69, 0xe4, 0xf0, 0x9d, 0x68, 0x06, 0x98, 0x53, 0x78, 0xcc, 0x3d, 0xdb, 0x5a, 0x5b, 0x6f, 0x98, 0x1b, 0x76, 0xd5, 0x0f, 0x37, 0xbc, 0x71, 0x0a, 0xa1, 0x83, 0xbd, 0x75, 0x4b, 0x2b, 0x5b, 0x56, 0x34, 0x87, 0x34, 0x6a, 0x83, 0x24, 0xb7, 0xf5, 0x83, 0xdd, 0xc3, 0x27, 0x36, 0x57, 0x2e, 0x9c, 0x7d, 0xed, 0x2a, 0x9d, 0xb7, 0xc0, 0xdd, 0x53, 0x23, 0x10, 0x08, 0x3c, 0x0e, 0x72, 0x71, 0x7c, 0xe8, 0x99, 0xb3, 0x6e, 0x9d, 0xdf, 0x3a, 0x52, 0x6b, 0x7f, 0xca, 0x1a, 0x99, 0x91, 0x9b, 0xd5, 0x1c, 0x49, 0xb5, 0xa7, 0xd9, 0x55, 0xea, 0x05, 0xb5, 0x4d, 0x91, 0x79, 0x37, 0xcc, 0x58, 0xf2, 0x60, 0x30, 0xf3, 0xad, 0x43, 0x45, 0x2d, 0x39, 0x66, 0x7a, 0xe5, 0x60, 0x7c, 0x3a, + 0x50, 0x9e, 0x32, 0xc8, 0x33, 0x4f, 0x60, 0x3f, 0x97, 0xda, 0x68, 0x95, 0x13, 0xd0, 0x82, 0x4c, 0x84, 0xd1, 0xac, 0x47, 0xb8, 0x4c, 0x9a, 0x41, 0x72, 0x0c, 0x81, 0x92, 0x0a, 0x0f, 0xc4, 0x0c, 0x09, 0x36, 0xee, 0x36, 0xa1, 0x67, 0xbc, 0xd9, 0x27, 0x87, 0xc8, 0xf1, 0x68, 0x7d, 0x76, 0xf7, 0x84, 0xcb, 0x04, 0x3c, 0x13, 0xde, 0x4b, 0xce, 0x84, 0x15, 0xd0, 0x27, 0x10, 0xc3, 0x75, 0xd6, 0xad, 0x68, 0x78, 0x03, 0x24, 0x8c, 0xba, 0x29, 0xa4, 0x55, 0x1b, 0xc4, 0xb9, 0x33, 0xd0, 0xa8, 0xb7, 0x54, 0x2e, 0x99, 0x3b, 0xbc, 0x83, 0x37, 0x12, 0x81, 0x6b, 0xa6, 0xaf, 0xac, 0x99, 0x7a, 0x90, 0x4b, 0x1f, 0xce, 0x4a, 0x7f, 0xfa, 0xc6, 0xcf, 0x38, 0x1d, 0xf0, 0xc2, 0xdf, 0x04, 0xff, 0x07, 0xcf, 0x7e, 0x1e, 0xb1, 0x90, 0x07, 0xa0, 0xa0, 0x85, 0xa5, 0x00, 0xca, 0xca, 0xc2, 0x87, 0xdd, + 0x49, 0xe2, 0x13, 0x3b, 0xa1, 0x4b, 0x1b, 0x3b, 0xe2, 0x15, 0xfe, 0x29, 0x8a, 0x10, 0xf1, 0x12, 0x5c, 0xb0, 0x3b, 0xbe, 0x30, 0xa7, 0x01, 0xe6, 0x11, 0xb9, 0x6e, 0xa8, 0x02, 0xea, 0xd3, 0x44, 0xe3, 0x85, 0xb9, 0xbc, 0x29, 0x14, 0x41, 0x5d, 0x2c, 0x04, 0x1b, 0x5e, 0x72, 0xc1, 0x4f, 0xf0, 0x92, 0xcf, 0xdc, 0xd1, 0xd1, 0xb4, 0xb5, 0x2f, 0x67, 0x2a, 0xf5, 0x50, 0xe3, 0xca, 0x75, 0x74, 0x56, 0x73, 0xdb, 0x7d, 0x21, 0x5c, 0xe4, 0x42, 0xc3, 0xcc, 0xfd, 0x73, 0x43, 0x19, 0xdd, 0xdb, 0x3b, 0xa9, 0xc7, 0xa6, 0xd4, 0x19, 0x8f, 0x87, 0xeb, 0x33, 0x74, 0xc3, 0x83, 0xa9, 0xc5, 0x68, 0x71, 0xdd, 0x58, 0x6f, 0xb8, 0xf0, 0xb9, 0x30, 0x08, 0xe7, 0xc0, 0x4f, 0x2c, 0xe2, 0x54, 0x45, 0xe7, 0xd8, 0xf6, 0xa6, 0x09, 0x74, 0xd9, 0x3b, 0x97, 0xc1, 0xa6, 0x3c, 0xbc, 0xb5, 0xe3, 0xd3, 0xe0, + 0xbd, 0x58, 0x29, 0x2c, 0x41, 0x8d, 0x9d, 0x02, 0x8e, 0x6d, 0xfa, 0x89, 0x34, 0x37, 0x4e, 0xbc, 0x30, 0xe9, 0x20, 0x24, 0xe8, 0x5c, 0xe3, 0x07, 0x2f, 0xd4, 0x73, 0x83, 0xdf, 0xd9, 0xde, 0xbc, 0xa5, 0x2f, 0x87, 0xd7, 0xc4, 0x34, 0xae, 0x3c, 0x38, 0x5e, 0x6e, 0x53, 0x2f, 0x8a, 0x6f, 0xea, 0x40, 0xf7, 0x8e, 0x4e, 0x50, 0x18, 0x57, 0xcf, 0x9e, 0x0e, 0x37, 0x64, 0xe8, 0x12, 0x36, 0x30, 0x40, 0xf7, 0x19, 0xcc, 0x49, 0x28, 0xbb, 0xe6, 0x10, 0x79, 0xd1, 0x1c, 0x3d, 0xf6, 0xa9, 0x41, 0x19, 0xad, 0x90, 0x05, 0x08, 0xc1, 0x8a, 0xf1, 0xa5, 0x8d, 0x2d, 0x9e, 0x57, 0xd7, 0x1e, 0xdb, 0xae, 0x2e, 0x5f, 0xc0, 0xe7, 0xc6, 0x9a, 0x1a, 0xd7, 0xdd, 0x98, 0x68, 0x1a, 0xb3, 0xf0, 0xf3, 0xe2, 0x6b, 0x9c, 0x9a, 0xe1, 0xd4, 0x1a, 0xcc, 0xc9, 0xee, 0x07, 0xff, 0x78, 0xf3, 0xa3, 0xec, 0x67, + 0x67, 0x96, 0x2e, 0x3c, 0x0d, 0xd4, 0x47, 0x3b, 0x6f, 0xdb, 0xbc, 0x34, 0xa3, 0x58, 0x91, 0x69, 0xb1, 0x94, 0x75, 0xae, 0x69, 0x59, 0xf3, 0xea, 0xae, 0xda, 0xa6, 0xfd, 0x6f, 0x5d, 0x3b, 0xff, 0xbe, 0xa1, 0x02, 0xf6, 0x39, 0x7d, 0x12, 0x79, 0xea, 0xfa, 0x1b, 0x7e, 0x79, 0x47, 0x0b, 0x36, 0xcd, 0x3f, 0x08, 0x94, 0xdf, 0x5f, 0x8a, 0xf2, 0xe9, 0xcd, 0x31, 0xca, 0xd4, 0x4a, 0x9f, 0xc3, 0xd4, 0x74, 0xf3, 0xdb, 0x9b, 0xaf, 0xfb, 0xd9, 0xcd, 0x8d, 0xd9, 0x73, 0xf6, 0xf7, 0x9b, 0x34, 0xeb, 0x39, 0x3d, 0x37, 0xf9, 0xc2, 0x57, 0xf4, 0x03, 0x58, 0xcf, 0xfb, 0x96, 0x63, 0x53, 0xa9, 0xb1, 0x00, 0xfc, 0xe8, 0xbe, 0x96, 0xe9, 0xe1, 0x8d, 0xe5, 0x7c, 0x6c, 0x2d, 0x7b, 0xdc, 0x8d, 0xf1, 0x92, 0xc5, 0x9c, 0x71, 0x7f, 0xc6, 0x2b, 0x68, 0xcd, 0x7c, 0x65, 0xad, 0x25, 0x5f, 0x49, 0x6b, + 0x97, 0x6d, 0x88, 0xe3, 0x28, 0x0a, 0x4e, 0x53, 0xd5, 0x7b, 0xe1, 0x36, 0xe2, 0x16, 0x64, 0xc2, 0x59, 0x29, 0xc2, 0x7b, 0x8a, 0x07, 0xf9, 0xdb, 0xf3, 0xe8, 0x1d, 0x6c, 0x7e, 0xc2, 0x5e, 0x59, 0x86, 0xb6, 0xd2, 0xf3, 0x47, 0xd1, 0x46, 0x62, 0xc3, 0xcc, 0xbd, 0xcf, 0x9c, 0x5f, 0x34, 0x6e, 0x8b, 0xe0, 0x8d, 0xf4, 0xdc, 0x0f, 0xd0, 0x36, 0xa2, 0x0e, 0x3e, 0x83, 0xe2, 0x95, 0x40, 0xdd, 0x6d, 0x84, 0x79, 0x82, 0xa8, 0x46, 0x71, 0x90, 0x93, 0xe1, 0x69, 0x57, 0x22, 0xbf, 0x87, 0xfa, 0x8b, 0xf8, 0x49, 0x0d, 0xc4, 0xee, 0xfd, 0xc6, 0x91, 0xff, 0xaa, 0x69, 0x25, 0x45, 0xa1, 0xec, 0x60, 0xa6, 0xc7, 0x65, 0x4f, 0x35, 0x9b, 0xb0, 0x82, 0x9a, 0xe0, 0x2a, 0x15, 0x89, 0xe1, 0x61, 0x0d, 0x13, 0xaf, 0x8a, 0x70, 0x12, 0xe3, 0xb1, 0x4d, 0x85, 0x25, 0x4b, 0x23, 0x3d, 0x92, 0xb5, 0x66, + 0xd7, 0x1d, 0x2d, 0x03, 0x2f, 0xdd, 0xd4, 0xe9, 0xad, 0x5d, 0x18, 0xad, 0x9c, 0x5d, 0x68, 0xae, 0xb9, 0xfe, 0xf5, 0x4d, 0x77, 0x7f, 0xd8, 0x12, 0x54, 0x24, 0x4b, 0x35, 0x39, 0xad, 0xeb, 0x7b, 0x1a, 0x57, 0xd4, 0x3a, 0x33, 0x5a, 0x57, 0xd7, 0x7e, 0x3a, 0xda, 0xb9, 0xb9, 0x35, 0x0d, 0x12, 0x8d, 0xf6, 0xc6, 0xe5, 0x4d, 0xd9, 0x4a, 0x91, 0x46, 0xa2, 0x91, 0xff, 0xb4, 0xb8, 0x3d, 0x2f, 0xa9, 0x60, 0xf9, 0x03, 0x8b, 0x4b, 0x56, 0xf4, 0xd7, 0x99, 0x74, 0xa5, 0x8d, 0x5d, 0x19, 0x8d, 0x7b, 0x16, 0x97, 0x76, 0xd5, 0xb5, 0x99, 0x64, 0x36, 0x8f, 0xcd, 0x59, 0xd4, 0x92, 0x1e, 0x6a, 0x09, 0x5b, 0xee, 0xce, 0xa8, 0xed, 0x0b, 0x16, 0x0e, 0xd4, 0xf9, 0x53, 0xd3, 0x03, 0x32, 0x91, 0x58, 0xc1, 0xdb, 0xc9, 0x0e, 0x30, 0x1f, 0x60, 0xfc, 0xcc, 0xb7, 0x9c, 0x4e, 0x23, 0xe6, 0xed, 0x4b, + 0xe2, 0x98, 0x7d, 0xc9, 0x86, 0xd1, 0x32, 0x34, 0x2d, 0xec, 0x21, 0x10, 0xba, 0x0a, 0x5f, 0xbc, 0x51, 0xbd, 0xcc, 0x54, 0x42, 0xb9, 0x03, 0xa3, 0x65, 0xc6, 0xc0, 0x71, 0x53, 0x55, 0x49, 0x94, 0xcc, 0x39, 0x31, 0x3e, 0x8f, 0x00, 0x0c, 0xc9, 0x4c, 0x51, 0x03, 0x69, 0x6e, 0x66, 0xee, 0x33, 0xe6, 0xbf, 0xce, 0xf1, 0x62, 0x7d, 0x4e, 0xbc, 0x5e, 0x1c, 0x5c, 0x77, 0xe9, 0xea, 0x5c, 0x00, 0x61, 0x14, 0x53, 0x28, 0x9d, 0x48, 0x47, 0x66, 0x3b, 0xa4, 0x04, 0x49, 0xa0, 0x68, 0xec, 0x9e, 0x52, 0xf7, 0x99, 0xc2, 0x90, 0xf7, 0xc1, 0x64, 0x15, 0x68, 0x4a, 0xb3, 0xde, 0x2f, 0xc6, 0x89, 0xf4, 0x17, 0x37, 0xf0, 0x91, 0xc4, 0x6e, 0xc8, 0x7b, 0x4f, 0xc1, 0xf9, 0xb7, 0x10, 0x85, 0x09, 0xb8, 0xb6, 0x18, 0xa0, 0x0d, 0xe1, 0xdb, 0x08, 0x4e, 0x55, 0xb1, 0x5c, 0x0c, 0xd7, 0x56, 0x98, 0x5c, + 0x58, 0x10, 0xf9, 0x8f, 0xe0, 0xda, 0x62, 0x26, 0x20, 0x6a, 0xfa, 0x94, 0xb0, 0x36, 0xf0, 0xe1, 0x81, 0x17, 0x56, 0x1d, 0xa3, 0x69, 0x89, 0x56, 0xc2, 0x66, 0x48, 0x5d, 0xc6, 0xb5, 0x67, 0x68, 0x5a, 0xa7, 0x05, 0x3f, 0x97, 0xba, 0x8d, 0x6b, 0x99, 0x7b, 0x5d, 0x86, 0x9c, 0xe9, 0x05, 0xa9, 0xa9, 0x05, 0xd3, 0x11, 0xaa, 0xad, 0xdc, 0xa7, 0xd1, 0xf8, 0xca, 0xb3, 0xbe, 0xfd, 0x9a, 0x55, 0x83, 0x5e, 0xf2, 0xa0, 0x2e, 0x3b, 0x49, 0xef, 0x56, 0x8c, 0xee, 0xd0, 0xd8, 0xa8, 0x6f, 0x46, 0x17, 0xea, 0xb2, 0x4d, 0x01, 0x0b, 0xb9, 0x59, 0x6d, 0x3b, 0x7f, 0x2f, 0x67, 0x23, 0x0a, 0x10, 0x04, 0xf5, 0x3b, 0x5a, 0x42, 0x48, 0xe0, 0xc1, 0xba, 0x28, 0xae, 0xcd, 0x32, 0x0e, 0xd7, 0x26, 0x93, 0x02, 0x22, 0x8e, 0x6a, 0x33, 0x1a, 0x74, 0x52, 0xbb, 0xcc, 0x0e, 0xc7, 0x2d, 0x01, 0x12, 0x0e, + 0xd7, 0x16, 0xe6, 0x44, 0x30, 0xb5, 0x76, 0x22, 0xac, 0x0d, 0x79, 0xcd, 0x90, 0x43, 0x0b, 0x06, 0x0c, 0xc1, 0xa6, 0xfc, 0x9f, 0xfe, 0x74, 0xf1, 0x2d, 0xdd, 0xde, 0xec, 0xd9, 0x7b, 0x67, 0xb2, 0xab, 0x4f, 0x9c, 0x78, 0xe9, 0x83, 0x0f, 0x6e, 0x9a, 0xdb, 0xe0, 0x28, 0xc9, 0xb0, 0xb0, 0x4f, 0x80, 0x8f, 0x42, 0x1d, 0x2b, 0x8a, 0xab, 0xd6, 0xb4, 0x67, 0x51, 0x60, 0xdf, 0xa6, 0x4d, 0xfb, 0x6e, 0xe1, 0xe2, 0xb7, 0xc2, 0xf7, 0x6f, 0xc4, 0xfe, 0x17, 0x53, 0xe3, 0xd9, 0x2c, 0x17, 0xc5, 0xb3, 0xa1, 0x20, 0x9d, 0x71, 0x3c, 0xdb, 0x43, 0x3f, 0xff, 0xf9, 0x99, 0x13, 0x27, 0xc8, 0xbb, 0xa9, 0xf9, 0xec, 0xa7, 0xfb, 0xd8, 0x52, 0xf0, 0xfa, 0x3e, 0xf0, 0xd8, 0x01, 0x02, 0xfb, 0x4c, 0x1c, 0xa0, 0xee, 0x81, 0xed, 0x07, 0x10, 0x96, 0x0d, 0x67, 0x23, 0xa1, 0x00, 0x03, 0xd2, 0x80, 0x80, + 0x31, 0x68, 0x49, 0x64, 0x59, 0xb2, 0x8c, 0x7f, 0x42, 0x63, 0x9c, 0x43, 0x00, 0x1b, 0xb7, 0x01, 0xce, 0xb0, 0x86, 0x6e, 0x39, 0x84, 0x3d, 0xdc, 0x76, 0xc1, 0x16, 0x03, 0xa4, 0xde, 0x62, 0xfb, 0x9a, 0x95, 0x8c, 0x39, 0x7a, 0xd9, 0x63, 0x65, 0x51, 0xa8, 0x59, 0x14, 0x70, 0x7b, 0x42, 0x1d, 0xce, 0x4b, 0x87, 0x12, 0xd0, 0x02, 0x0a, 0xc1, 0xaf, 0xf9, 0x76, 0x69, 0x82, 0xa4, 0xa7, 0x28, 0xca, 0xeb, 0xcd, 0x01, 0x22, 0xa0, 0xf5, 0x39, 0x8c, 0x3e, 0x27, 0xb6, 0xb6, 0x22, 0xf2, 0x3d, 0x1e, 0x4a, 0x3d, 0x19, 0xe5, 0x80, 0xef, 0xbe, 0x28, 0x27, 0x9b, 0x96, 0xb5, 0xbc, 0xaa, 0xeb, 0x86, 0xfe, 0xec, 0x92, 0xa5, 0xb7, 0x76, 0x78, 0xea, 0xd3, 0x28, 0x01, 0x09, 0x2b, 0xc9, 0x3c, 0xd6, 0x60, 0x6b, 0xb1, 0xdd, 0x5e, 0xd2, 0x91, 0x5b, 0xd4, 0x68, 0x3d, 0x43, 0x7d, 0x7d, 0x80, 0x3d, + 0xec, 0xf2, 0x96, 0xae, 0x7e, 0x64, 0xf1, 0x9a, 0x53, 0x3b, 0x6a, 0xc4, 0xd2, 0x14, 0x4f, 0x8a, 0x5a, 0x27, 0xf1, 0xb4, 0x5f, 0x3f, 0xa7, 0xe7, 0x9a, 0xda, 0x14, 0x87, 0x9e, 0x45, 0xab, 0x02, 0x08, 0x23, 0xfb, 0xb0, 0x60, 0x0f, 0xf5, 0x0d, 0x51, 0x86, 0x76, 0x51, 0xd1, 0x94, 0x78, 0x36, 0xcb, 0x45, 0xf0, 0x6c, 0x65, 0x44, 0x59, 0x24, 0xef, 0xe2, 0x78, 0x36, 0x2a, 0x01, 0xcf, 0x46, 0x4d, 0xc4, 0xb3, 0x69, 0xe3, 0x78, 0x36, 0x14, 0x57, 0x28, 0x8e, 0x3c, 0xd2, 0x0a, 0xf6, 0xa4, 0x9a, 0xd7, 0x95, 0xe7, 0x45, 0x54, 0x2f, 0x19, 0xab, 0x32, 0xe6, 0xbc, 0x3a, 0x27, 0xa3, 0xca, 0xf8, 0xa2, 0x3a, 0x92, 0x7b, 0x3d, 0xa0, 0xcc, 0xe5, 0x41, 0x77, 0x9e, 0x91, 0x65, 0xef, 0x4e, 0xce, 0x94, 0xbf, 0xa3, 0x2b, 0xf6, 0xb7, 0xbe, 0x3a, 0xbd, 0x2d, 0xed, 0x1d, 0x45, 0x66, 0xf2, 0x5d, + 0x2c, 0x6b, 0xca, 0x75, 0x07, 0xcb, 0xcd, 0x8f, 0x1e, 0x60, 0x9b, 0x92, 0x6d, 0xa0, 0xd7, 0xee, 0x04, 0x21, 0x8f, 0x87, 0x2d, 0x06, 0x67, 0xe1, 0xdf, 0xef, 0x38, 0xed, 0xe0, 0xa1, 0x40, 0xbe, 0x42, 0x9e, 0xe5, 0x01, 0x5b, 0xad, 0x26, 0xf6, 0x0d, 0xbb, 0x03, 0x3c, 0xcb, 0x36, 0x4d, 0x03, 0x85, 0x26, 0x2b, 0xbb, 0xd5, 0x13, 0x94, 0x2b, 0x22, 0x01, 0xf6, 0x73, 0x4e, 0x8e, 0x40, 0x74, 0x65, 0x08, 0xce, 0x8a, 0x1b, 0xd9, 0xcb, 0xb1, 0xef, 0x59, 0x3d, 0x21, 0x24, 0x68, 0x38, 0x1b, 0x03, 0x28, 0x4c, 0x9a, 0x05, 0x33, 0xe1, 0xbe, 0x38, 0x49, 0x86, 0xdb, 0xc3, 0xae, 0x46, 0x03, 0x37, 0xc5, 0xd0, 0x88, 0x63, 0xe3, 0x08, 0x4d, 0x9e, 0x07, 0xee, 0x0a, 0x87, 0xfc, 0x9b, 0x32, 0x07, 0x76, 0xf9, 0x82, 0x29, 0xd7, 0x13, 0xac, 0x30, 0x3f, 0xf2, 0x43, 0x3c, 0xdc, 0x02, 0x1d, 0x1a, + 0xee, 0xe2, 0x33, 0x67, 0xfa, 0xfc, 0x45, 0x7a, 0xea, 0x8c, 0x31, 0x65, 0xac, 0x67, 0xdc, 0x90, 0xe0, 0x20, 0xf0, 0x90, 0xa8, 0x6f, 0xd8, 0x66, 0x07, 0xc6, 0x20, 0xec, 0xbe, 0x70, 0x81, 0x2e, 0x87, 0x7d, 0x75, 0x22, 0xdb, 0xb6, 0x1e, 0x30, 0x02, 0x1b, 0x20, 0x21, 0x09, 0x40, 0xc8, 0x9e, 0x38, 0xa2, 0x48, 0x40, 0xa0, 0xac, 0x10, 0xf0, 0xac, 0xd1, 0xb4, 0xa5, 0x81, 0xe2, 0xac, 0x1b, 0x1e, 0x7c, 0xc2, 0x8c, 0x63, 0xd6, 0x0d, 0x6a, 0x1c, 0x74, 0x0d, 0x9e, 0xf6, 0x30, 0xb7, 0x22, 0x14, 0x37, 0x12, 0xba, 0x7c, 0x57, 0xee, 0x34, 0xe9, 0x9d, 0xca, 0x42, 0xe7, 0x75, 0x0b, 0xce, 0x9c, 0x59, 0x70, 0x5d, 0x24, 0xe9, 0x31, 0x49, 0x7e, 0xf2, 0xc1, 0xdf, 0xe8, 0xe0, 0x94, 0x47, 0x8d, 0x29, 0x1e, 0x10, 0xb2, 0xbb, 0x18, 0x76, 0x14, 0x30, 0x5c, 0xe7, 0x3e, 0xfd, 0x83, 0x3c, 0x1b, + 0x14, 0x9a, 0x2d, 0xec, 0x56, 0x6f, 0x96, 0x5c, 0x11, 0xce, 0xc0, 0xf3, 0x1a, 0x80, 0x7d, 0xed, 0x86, 0x7d, 0xf5, 0x20, 0xdc, 0x9a, 0xf3, 0x12, 0xb8, 0x35, 0x4b, 0x0c, 0x08, 0x96, 0x6a, 0xe5, 0x71, 0x6b, 0xee, 0x31, 0xdc, 0x5a, 0x7c, 0x3f, 0xc5, 0x69, 0x12, 0x9a, 0xcf, 0xc4, 0xdd, 0x43, 0x77, 0x5f, 0x9f, 0x5b, 0xa8, 0x7b, 0x11, 0x4e, 0xe5, 0x12, 0x48, 0x2c, 0xce, 0x9c, 0x3a, 0x85, 0xa6, 0xf3, 0x1d, 0x6e, 0xbe, 0x8d, 0x79, 0x68, 0x8b, 0xc4, 0xe7, 0x91, 0xdc, 0xb7, 0x64, 0xc9, 0xbe, 0x53, 0x0e, 0x3b, 0x28, 0xc4, 0x93, 0x0d, 0xfb, 0x9a, 0x1f, 0xe0, 0x65, 0xc9, 0x0b, 0xf4, 0x7b, 0xb0, 0xaf, 0x36, 0x44, 0xaf, 0xac, 0x53, 0xe3, 0xd5, 0xe2, 0xdd, 0x44, 0x17, 0x78, 0xb0, 0x8b, 0x71, 0xbc, 0x5a, 0xac, 0x8b, 0x1c, 0x5e, 0x6d, 0x42, 0xe7, 0xde, 0x1b, 0xeb, 0x1c, 0xfb, 0x1a, + 0x49, 0x5f, 0xbc, 0x6f, 0x03, 0x77, 0xa0, 0x45, 0x1e, 0xd7, 0x33, 0x6e, 0x6f, 0x22, 0x3a, 0x37, 0x09, 0xa7, 0x66, 0xf9, 0x8e, 0x38, 0x35, 0xfa, 0x1e, 0x5b, 0xd2, 0xba, 0x77, 0xd8, 0xac, 0x33, 0x67, 0xc0, 0xcf, 0xce, 0xf2, 0x27, 0x07, 0xfc, 0x82, 0x0d, 0x50, 0x5f, 0xb3, 0xc3, 0xe0, 0x66, 0xc8, 0x66, 0xe0, 0x3b, 0xa1, 0xf8, 0xf9, 0x3c, 0xa6, 0xdd, 0x28, 0xb7, 0x5f, 0x22, 0x4e, 0x4d, 0xc0, 0x6f, 0x29, 0xee, 0x9a, 0x5e, 0x9d, 0x80, 0x3f, 0x49, 0x78, 0x87, 0x9d, 0x79, 0x9e, 0xf5, 0x9f, 0xe5, 0xde, 0x80, 0x28, 0xf7, 0xf9, 0x7b, 0xc7, 0xda, 0xe7, 0xcf, 0xdb, 0x01, 0x41, 0x16, 0x6c, 0x3f, 0x48, 0x3c, 0xcf, 0x91, 0x59, 0x0c, 0x3b, 0xf6, 0x41, 0xc1, 0x52, 0x95, 0x88, 0x43, 0xe6, 0x1f, 0x50, 0x09, 0x94, 0x7b, 0x02, 0xda, 0xcc, 0x32, 0x35, 0x42, 0x8d, 0xa3, 0xdc, 0x7c, 0x59, + 0x14, 0x62, 0x07, 0xaa, 0x39, 0xb3, 0x27, 0xd4, 0x61, 0xae, 0x00, 0x97, 0x66, 0x99, 0x8c, 0x4b, 0x33, 0x4e, 0x81, 0x4b, 0xf3, 0x5c, 0x1a, 0x98, 0x46, 0x7d, 0x94, 0xb5, 0xbc, 0x92, 0x27, 0xdb, 0xed, 0x9e, 0x06, 0x1f, 0x29, 0xa0, 0x10, 0xd9, 0x76, 0x73, 0x64, 0xbb, 0xb8, 0x33, 0xd7, 0x1a, 0xf4, 0xda, 0x94, 0x68, 0xbe, 0xa8, 0x6e, 0x52, 0xe0, 0xbc, 0x28, 0xe9, 0x16, 0xab, 0x74, 0x12, 0x48, 0xbc, 0xe1, 0x74, 0xc2, 0xa9, 0xe3, 0x7c, 0x2f, 0xa1, 0x6c, 0x7e, 0x0c, 0xfb, 0x5e, 0x36, 0xa3, 0xb8, 0xd3, 0x28, 0xbb, 0x7b, 0x35, 0x45, 0x0a, 0x08, 0x0d, 0x46, 0x9f, 0x4d, 0x02, 0x8e, 0x39, 0x26, 0x20, 0xcc, 0x1a, 0xeb, 0x7d, 0xbe, 0x7c, 0x87, 0xde, 0xe7, 0x72, 0xf9, 0x38, 0x38, 0x97, 0x01, 0xa3, 0xb9, 0x90, 0x7b, 0x4f, 0x22, 0x96, 0x8b, 0x4c, 0x94, 0xce, 0x91, 0x47, 0x5e, 0x02, + 0x8e, 0x6b, 0x2c, 0x33, 0x3a, 0xdc, 0xec, 0x38, 0x94, 0x25, 0x0a, 0xb8, 0xfb, 0x5a, 0x79, 0x61, 0x41, 0x51, 0x59, 0x51, 0x41, 0x31, 0xf8, 0x8b, 0xb5, 0x3e, 0xad, 0x6a, 0x73, 0x41, 0xc1, 0xc6, 0x8a, 0x81, 0xc7, 0xb7, 0xd5, 0x97, 0x6d, 0x7c, 0x76, 0x8d, 0xb3, 0xca, 0x29, 0x65, 0x52, 0x92, 0x64, 0x29, 0x8a, 0xda, 0xe5, 0xf5, 0x9e, 0x8c, 0xb6, 0x75, 0x0d, 0x6a, 0x9b, 0xaa, 0xba, 0x39, 0xbd, 0x79, 0x79, 0x65, 0xe5, 0xca, 0x19, 0x19, 0xa0, 0x1f, 0xca, 0xdf, 0x49, 0x36, 0x95, 0xcd, 0xca, 0x80, 0x7f, 0xf6, 0xdd, 0x11, 0x0a, 0xdd, 0xda, 0xdd, 0x31, 0xb7, 0x7f, 0xf4, 0x15, 0xb1, 0xc4, 0x6a, 0x31, 0x5b, 0x63, 0x11, 0x10, 0x24, 0xd2, 0x56, 0x9b, 0x0e, 0x50, 0xb9, 0x73, 0x6e, 0xec, 0xec, 0xd9, 0x37, 0x2f, 0x97, 0x22, 0xab, 0x4e, 0xad, 0x6a, 0xde, 0xd0, 0x9e, 0x1e, 0xec, 0xba, + 0xa6, 0x66, 0xf1, 0x2b, 0xb5, 0x32, 0x01, 0x89, 0xe3, 0x21, 0x98, 0xd9, 0x87, 0xa9, 0xff, 0x81, 0x93, 0x35, 0x01, 0xaf, 0x65, 0xb9, 0x5a, 0xbc, 0x56, 0xff, 0x4b, 0xa9, 0x49, 0xeb, 0xdf, 0x61, 0xb7, 0x51, 0xaf, 0x53, 0xf3, 0x47, 0x83, 0xdc, 0x91, 0xa1, 0xe6, 0x1f, 0x38, 0xc0, 0xe5, 0x06, 0x3c, 0x46, 0x7d, 0x8d, 0x73, 0x56, 0x5a, 0xa3, 0x66, 0x29, 0x45, 0xe0, 0x7c, 0xe7, 0x68, 0x82, 0x2d, 0xfc, 0x99, 0x4c, 0x77, 0xa8, 0x29, 0x81, 0x11, 0x59, 0x6a, 0xd5, 0xe3, 0x83, 0xea, 0x15, 0x01, 0x72, 0xf0, 0x2c, 0xbb, 0xfc, 0xb0, 0xd1, 0x2a, 0xfc, 0xd2, 0xac, 0x10, 0xe9, 0x14, 0xff, 0x14, 0xa6, 0xe8, 0xee, 0xaa, 0x40, 0xe9, 0x12, 0xa9, 0xe7, 0x8c, 0xaa, 0xd1, 0x97, 0xaa, 0xeb, 0x9c, 0xb5, 0x2e, 0xb2, 0x5a, 0x6d, 0x40, 0xef, 0xd1, 0xc1, 0xf7, 0x9c, 0x87, 0xef, 0xb1, 0xa0, 0xbb, + 0x1a, 0x8b, 0x66, 0x2a, 0x5c, 0x98, 0x85, 0x67, 0x4e, 0xe9, 0x63, 0xb8, 0x30, 0x26, 0xee, 0xf8, 0x9c, 0x37, 0xf1, 0xe5, 0x80, 0x5c, 0xf2, 0xe1, 0x87, 0x67, 0x3f, 0xfc, 0x70, 0x52, 0x0f, 0xd8, 0xe5, 0xb2, 0x7d, 0x2f, 0xbf, 0xbc, 0xaf, 0x79, 0x42, 0x27, 0xce, 0x73, 0x11, 0xcd, 0x34, 0x90, 0x1e, 0x85, 0x61, 0x3f, 0x5c, 0x08, 0x0b, 0xe6, 0x30, 0xeb, 0xc6, 0xb0, 0x60, 0x9c, 0xd0, 0x0d, 0xfa, 0xc8, 0xf1, 0x04, 0xc9, 0x45, 0x38, 0x7d, 0x8e, 0xb4, 0x49, 0x58, 0x30, 0x2a, 0x16, 0xc5, 0x90, 0xbb, 0x18, 0xd0, 0xa2, 0x6e, 0x85, 0xd1, 0xc5, 0x40, 0x1e, 0x1d, 0x86, 0xbc, 0x71, 0x81, 0xd1, 0x22, 0xfa, 0x8a, 0x91, 0x88, 0x3e, 0x92, 0x98, 0xf5, 0x4f, 0x82, 0x79, 0xec, 0xe3, 0x2f, 0x1b, 0xf5, 0xa2, 0x2f, 0x18, 0x99, 0xe4, 0xa7, 0x9a, 0x94, 0x67, 0xd9, 0xc7, 0x01, 0xc9, 0x53, 0xae, + 0x24, 0x05, 0xd8, 0xa1, 0x4c, 0x96, 0xb1, 0x27, 0x14, 0x49, 0xa4, 0x14, 0x7c, 0xa6, 0x50, 0xb0, 0xfb, 0x94, 0x56, 0x15, 0x08, 0xd9, 0xd8, 0x64, 0x76, 0x13, 0x8a, 0x7a, 0x9a, 0xcc, 0x3e, 0xcc, 0xac, 0xc0, 0xf4, 0x73, 0x1c, 0x16, 0xcc, 0xf2, 0x1d, 0xb1, 0x60, 0xcc, 0x0a, 0xd8, 0xb9, 0xf3, 0xdf, 0x63, 0x5f, 0xa3, 0x94, 0xec, 0x57, 0x63, 0x04, 0x94, 0x9a, 0x77, 0xc7, 0x1d, 0xa3, 0x39, 0xe8, 0x6c, 0xe2, 0x00, 0x7f, 0x70, 0x8e, 0x4a, 0xe0, 0x3b, 0x4d, 0xe8, 0x3e, 0x5c, 0x3f, 0x19, 0x0b, 0x66, 0x99, 0x12, 0x0b, 0x66, 0x22, 0x4c, 0x46, 0x9d, 0x66, 0x32, 0x16, 0x0c, 0x24, 0x62, 0xc1, 0xe8, 0x12, 0xb8, 0x15, 0x7f, 0xbc, 0xe3, 0x93, 0xa3, 0x73, 0xcf, 0x86, 0x87, 0x0e, 0x2f, 0x3d, 0x13, 0x23, 0xe0, 0x8f, 0x38, 0x94, 0x91, 0xeb, 0x57, 0x83, 0xe9, 0xec, 0x4f, 0xaa, 0xdb, 0xec, + 0x72, 0x13, 0xf8, 0x21, 0xa7, 0x2f, 0x00, 0xa2, 0x09, 0xca, 0xe1, 0xdb, 0xf1, 0xbe, 0xc9, 0x8f, 0xe6, 0x99, 0x8d, 0x3a, 0x01, 0x24, 0x78, 0xdc, 0x75, 0x38, 0x54, 0x1b, 0x40, 0x2c, 0x81, 0xdd, 0x5c, 0x7c, 0x3a, 0x12, 0x30, 0x60, 0x16, 0xc2, 0xa2, 0xd7, 0xab, 0xf5, 0xf1, 0xcb, 0x79, 0xb8, 0x5a, 0xfa, 0x09, 0x18, 0x30, 0xb8, 0x62, 0x6a, 0x6a, 0xfb, 0x59, 0x77, 0x8d, 0x73, 0x67, 0xf3, 0xb6, 0xbe, 0xd0, 0xd9, 0xa6, 0xa6, 0x9d, 0xb6, 0x5a, 0x2f, 0x72, 0x22, 0x06, 0x47, 0xc4, 0x4a, 0xe6, 0xed, 0x57, 0xc9, 0xd4, 0xda, 0xd5, 0x6d, 0xa8, 0x4b, 0x1d, 0xed, 0x62, 0x60, 0x65, 0xff, 0x40, 0xcb, 0xc5, 0xe0, 0x87, 0x04, 0xc7, 0x5f, 0xa8, 0x57, 0x61, 0x9f, 0xd4, 0x08, 0x07, 0x26, 0x9f, 0x84, 0x03, 0xb3, 0x8c, 0xe1, 0xc0, 0x74, 0xea, 0x18, 0x0e, 0x0c, 0x8c, 0xc7, 0x81, 0x35, 0x6e, + 0xff, 0xfd, 0x63, 0xf3, 0xce, 0x46, 0x86, 0x0e, 0x2f, 0x39, 0x73, 0x86, 0x9c, 0x81, 0x07, 0xcf, 0x1e, 0x07, 0x39, 0xd5, 0xad, 0x76, 0x99, 0x89, 0x2d, 0xc0, 0x04, 0x92, 0x24, 0xb2, 0x2f, 0x7c, 0x49, 0xff, 0x82, 0xcf, 0x11, 0xbe, 0x3d, 0x2a, 0x29, 0x83, 0xfa, 0x77, 0x06, 0x94, 0x98, 0x62, 0x0e, 0xca, 0x3e, 0x42, 0x40, 0x33, 0xb4, 0x80, 0x19, 0x26, 0xc5, 0x24, 0x94, 0x4d, 0x68, 0xc8, 0xdc, 0x06, 0x62, 0x17, 0xeb, 0x0e, 0x64, 0xbd, 0xc4, 0xa1, 0x17, 0xec, 0x42, 0xcc, 0x29, 0xae, 0xa4, 0xa8, 0x13, 0xe5, 0x26, 0x30, 0x11, 0x44, 0x7e, 0x24, 0x94, 0x9d, 0x9e, 0xe6, 0x71, 0xa1, 0xc8, 0xf9, 0x1a, 0x35, 0xce, 0xef, 0x3d, 0x86, 0x93, 0x12, 0x08, 0xdd, 0x31, 0xf8, 0x93, 0x71, 0x0a, 0xbc, 0x14, 0x8f, 0x1f, 0xe3, 0xbc, 0x84, 0xc3, 0x11, 0xda, 0xc5, 0xe7, 0xf6, 0x66, 0xf7, 0x9b, + 0x54, 0xca, 0xc6, 0x26, 0x79, 0xe3, 0xfa, 0x07, 0x06, 0xfa, 0xef, 0x1b, 0x29, 0x4f, 0xcc, 0xf1, 0x1d, 0xd4, 0x18, 0xe5, 0xf9, 0xce, 0x59, 0x4b, 0x57, 0x46, 0x06, 0x9f, 0xda, 0xde, 0x44, 0x17, 0x0c, 0x36, 0x05, 0xd4, 0xda, 0x58, 0x72, 0xef, 0xaf, 0x39, 0xf5, 0xbb, 0x30, 0x97, 0xec, 0xb9, 0x73, 0x59, 0xe9, 0x14, 0x69, 0xbe, 0xa7, 0x6d, 0x3f, 0x7d, 0x2d, 0x50, 0x67, 0x75, 0x56, 0x43, 0x5d, 0x9d, 0xe3, 0x2b, 0xd9, 0x70, 0xff, 0x72, 0xf3, 0x86, 0xf1, 0x62, 0x55, 0xfc, 0xbc, 0xe5, 0xe7, 0x40, 0xe1, 0x03, 0xf2, 0x95, 0x2b, 0x99, 0x0a, 0xab, 0x10, 0xee, 0xa2, 0xd2, 0x92, 0x48, 0xde, 0xbf, 0x33, 0x0f, 0x42, 0xee, 0x14, 0x64, 0x52, 0x57, 0x3d, 0x19, 0xd6, 0xda, 0xd6, 0x99, 0x19, 0xb6, 0xa4, 0xf5, 0x1d, 0xfb, 0x17, 0x95, 0x5c, 0xcd, 0x7c, 0x38, 0xf2, 0xdc, 0x26, 0x01, 0xfb, + 0x67, 0x78, 0xa4, 0xda, 0x15, 0x2d, 0x5b, 0x9f, 0x5a, 0x91, 0x38, 0x2f, 0x9c, 0xce, 0xf9, 0x29, 0x96, 0x5b, 0x9e, 0x7e, 0x2e, 0xdd, 0x47, 0x0a, 0x71, 0xd4, 0xb4, 0x00, 0xbf, 0x93, 0xd0, 0x4c, 0xe0, 0xe3, 0x44, 0xf2, 0x81, 0x96, 0x39, 0x0d, 0xb7, 0x41, 0x24, 0x26, 0xa1, 0x22, 0xd8, 0xc7, 0xcd, 0x09, 0x2f, 0x9c, 0xb8, 0x27, 0x96, 0xe7, 0x0a, 0x11, 0xc2, 0x39, 0x63, 0x15, 0x49, 0x1c, 0x0c, 0x24, 0x56, 0x4e, 0x44, 0x12, 0xa4, 0x88, 0x88, 0x97, 0xc7, 0xa9, 0xa9, 0x27, 0x14, 0xef, 0xc1, 0x39, 0xaa, 0x33, 0x33, 0x3c, 0xae, 0x14, 0x6b, 0x92, 0x51, 0xa3, 0x52, 0x2a, 0xe4, 0x32, 0xa9, 0x98, 0x08, 0x82, 0x20, 0xba, 0x6b, 0x62, 0xe2, 0x28, 0x89, 0xb8, 0x4a, 0x92, 0x20, 0xa5, 0x4e, 0x14, 0x64, 0xc8, 0xda, 0x98, 0x4e, 0xf9, 0x0f, 0x4e, 0x39, 0xe1, 0x25, 0x56, 0xb5, 0xcb, 0xf8, + 0xc0, 0xd3, 0x9c, 0x1c, 0xc3, 0xab, 0x9f, 0x72, 0xf7, 0x07, 0xfe, 0x9e, 0x1b, 0xfa, 0x91, 0x80, 0x12, 0x53, 0x53, 0x38, 0x09, 0xd6, 0xe2, 0x53, 0x88, 0x7e, 0xfa, 0xe6, 0xca, 0x17, 0xb6, 0xd7, 0xc6, 0x04, 0x19, 0x3c, 0x87, 0x04, 0xdc, 0x5b, 0x1f, 0xc0, 0x39, 0x9c, 0x8c, 0x35, 0xb3, 0x5c, 0x19, 0xd6, 0xcc, 0xf2, 0x9d, 0xb1, 0x66, 0x02, 0x12, 0x1d, 0x2d, 0x6c, 0xa2, 0xa2, 0x34, 0x11, 0xbb, 0x9a, 0xfe, 0xe0, 0xf6, 0xe7, 0x47, 0x4e, 0xb0, 0x3f, 0x93, 0x05, 0x2b, 0xdb, 0xfa, 0xe7, 0x65, 0xf7, 0x86, 0x1d, 0x55, 0xd3, 0x8a, 0x73, 0x5c, 0x12, 0xf2, 0xc0, 0x01, 0x64, 0x8a, 0xa1, 0x66, 0x9f, 0x66, 0xff, 0xef, 0xcc, 0xfa, 0x69, 0xc6, 0x94, 0x8d, 0xc6, 0x69, 0xeb, 0xce, 0x00, 0xd3, 0xe9, 0x18, 0x4d, 0xdd, 0xcd, 0xde, 0x46, 0x7d, 0x04, 0xc7, 0xe0, 0x20, 0xe6, 0xf1, 0x26, 0xc0, + 0x86, 0xe3, 0xfe, 0x49, 0xc8, 0x32, 0xcb, 0x54, 0x20, 0x41, 0x6b, 0xac, 0x08, 0x8a, 0x06, 0x8b, 0xb2, 0x9f, 0x25, 0x14, 0xa5, 0xf9, 0x1b, 0x02, 0x07, 0xe1, 0xc0, 0x10, 0xb1, 0xa4, 0x44, 0x88, 0x18, 0x5c, 0x1b, 0x6a, 0x2a, 0x94, 0x18, 0xf5, 0x51, 0xce, 0xbc, 0xfd, 0x7d, 0xfe, 0xaa, 0x34, 0xb8, 0x13, 0xa0, 0xa8, 0x0b, 0x17, 0xc4, 0xe9, 0x4d, 0xaf, 0xcd, 0xb3, 0x72, 0xe2, 0x37, 0xa4, 0xc6, 0xe9, 0x43, 0x07, 0xe7, 0x04, 0x24, 0x52, 0xa5, 0x51, 0x6f, 0x54, 0xaa, 0x75, 0x62, 0x7d, 0xc1, 0xfc, 0x66, 0xf0, 0x54, 0x82, 0x38, 0xce, 0xeb, 0x18, 0x28, 0xfe, 0x84, 0x72, 0xb2, 0x8e, 0xf1, 0x1d, 0xf0, 0x62, 0xf4, 0xb1, 0x03, 0x2f, 0xac, 0x3a, 0x19, 0x57, 0x30, 0xd4, 0x13, 0xd4, 0x0b, 0xc4, 0x93, 0xa0, 0x5c, 0xb6, 0x3d, 0xc6, 0x1f, 0x8d, 0x53, 0xe0, 0xc5, 0x2c, 0x53, 0xe1, 0xc5, + 0x20, 0x7f, 0x44, 0xec, 0x71, 0x0a, 0xbc, 0x58, 0x02, 0x87, 0xa4, 0xb6, 0xbf, 0x03, 0x19, 0xe4, 0x4b, 0x3b, 0x3e, 0x79, 0x0c, 0x31, 0xc8, 0xfb, 0x97, 0x22, 0x76, 0xc4, 0x7e, 0x04, 0xcf, 0xf3, 0xdf, 0x80, 0xc6, 0x3e, 0x81, 0x45, 0xc6, 0xc6, 0xce, 0xbc, 0x45, 0x9d, 0x23, 0x7c, 0x44, 0x61, 0x34, 0x92, 0xaa, 0x16, 0x51, 0x02, 0xcc, 0x1d, 0x05, 0x40, 0x00, 0xb9, 0x23, 0x93, 0x30, 0x07, 0x13, 0xc0, 0x62, 0x3e, 0xc2, 0x17, 0xb0, 0x9b, 0xbc, 0x13, 0xc1, 0x62, 0x97, 0x88, 0xba, 0x87, 0x0c, 0x1f, 0xcc, 0x5b, 0xb7, 0x3f, 0xbf, 0xea, 0xb0, 0x50, 0xc8, 0x3e, 0x2e, 0x4e, 0xd6, 0x1a, 0x34, 0x56, 0x31, 0xfb, 0x88, 0x40, 0x74, 0xfc, 0x90, 0xce, 0xa7, 0x00, 0x69, 0x72, 0x83, 0x44, 0xa2, 0x97, 0x03, 0xb7, 0xc2, 0xa7, 0x3b, 0x84, 0xa7, 0x8d, 0xdc, 0x95, 0xd2, 0x68, 0x1b, 0x3d, 0xa2, + 0xd6, 0x6a, 0xd5, 0x64, 0x9f, 0xad, 0x29, 0x75, 0x74, 0x03, 0x75, 0x4e, 0x63, 0x66, 0x25, 0xa6, 0xb0, 0xc5, 0x12, 0x36, 0x81, 0x7f, 0x5a, 0xd4, 0xe7, 0xef, 0xc2, 0xf4, 0xba, 0xfe, 0xc2, 0x97, 0x14, 0x47, 0xaf, 0x33, 0x88, 0xe5, 0x1c, 0x6f, 0xf3, 0x5c, 0x14, 0x3c, 0xe6, 0x48, 0x04, 0x8f, 0x59, 0xae, 0xa8, 0x20, 0xba, 0x7b, 0x40, 0x78, 0x98, 0x80, 0xdf, 0xeb, 0xb6, 0xa7, 0x26, 0x19, 0x95, 0x72, 0x4c, 0xcb, 0x91, 0x1d, 0x5f, 0xcb, 0x6b, 0x02, 0x60, 0x7c, 0x18, 0x2f, 0x74, 0x6b, 0x15, 0x0f, 0xde, 0xe5, 0xfd, 0xc6, 0x1e, 0xed, 0x2f, 0xda, 0xb8, 0x5f, 0x33, 0xda, 0xe0, 0x9a, 0xb5, 0x64, 0x65, 0x64, 0xf1, 0xb3, 0x3b, 0xea, 0x2a, 0x36, 0x3e, 0x3d, 0x0c, 0x29, 0x77, 0x54, 0x6b, 0x81, 0x1a, 0x22, 0xa6, 0xdc, 0xb3, 0x20, 0xe5, 0x36, 0xc9, 0xc1, 0x3e, 0x8e, 0x4a, 0x83, 0xff, + 0x41, 0xac, 0xa9, 0xe6, 0xc6, 0xb7, 0x77, 0x2e, 0x7e, 0xe1, 0xfa, 0xe6, 0x92, 0xd5, 0x8f, 0x2d, 0xcb, 0x4c, 0xed, 0xbd, 0xa1, 0x07, 0x53, 0x69, 0x14, 0x9b, 0xf4, 0xc2, 0x97, 0xec, 0x3d, 0x54, 0x21, 0xdc, 0x43, 0x02, 0xf2, 0xf7, 0x22, 0x14, 0xc4, 0xbc, 0x05, 0xf2, 0x7a, 0x14, 0x87, 0x06, 0xe5, 0xb6, 0xb2, 0xa3, 0xfb, 0x3b, 0x35, 0x77, 0x87, 0x39, 0x09, 0x98, 0xe5, 0x48, 0x04, 0x66, 0x59, 0x2e, 0x53, 0x04, 0x99, 0xc1, 0xa3, 0x66, 0xa5, 0x02, 0x6e, 0x7f, 0x22, 0xc9, 0xa8, 0xb0, 0x2b, 0xed, 0x42, 0xb9, 0x50, 0x0e, 0xa9, 0xaa, 0x80, 0x10, 0x38, 0x38, 0xf8, 0x16, 0xf2, 0x5e, 0x89, 0x25, 0xeb, 0x02, 0x13, 0xf2, 0x43, 0x00, 0xb3, 0x78, 0xff, 0xee, 0xeb, 0xf7, 0x89, 0x81, 0x74, 0xdf, 0xce, 0x5d, 0xfb, 0xa5, 0x20, 0x15, 0x45, 0xb0, 0xa9, 0xdc, 0x72, 0x72, 0x78, 0xe5, 0xc9, + 0xad, 0x55, 0x55, 0x5b, 0x4f, 0x82, 0x9f, 0xdc, 0xfb, 0xe8, 0xa3, 0xf7, 0x2e, 0xb9, 0xfd, 0x81, 0x07, 0x6e, 0x07, 0xff, 0x85, 0x22, 0xd8, 0xac, 0x7b, 0xfd, 0x86, 0xba, 0xba, 0x1b, 0x5e, 0x5f, 0x87, 0x22, 0xda, 0x70, 0x67, 0xf3, 0x06, 0xf6, 0x21, 0xca, 0x01, 0xc7, 0x29, 0xc3, 0x31, 0x63, 0x91, 0x0e, 0xce, 0x69, 0x30, 0x50, 0x2f, 0xe6, 0x6c, 0xf6, 0x9c, 0xcc, 0x8f, 0x54, 0x17, 0x15, 0x83, 0x4e, 0x86, 0x3d, 0x41, 0x79, 0xa1, 0x1c, 0x6c, 0xda, 0x29, 0xa4, 0xee, 0x23, 0x35, 0x1c, 0x6a, 0x2e, 0x8d, 0xf0, 0x20, 0xf4, 0x21, 0xe1, 0x08, 0x32, 0x74, 0xc8, 0xcb, 0x36, 0x62, 0x1a, 0x76, 0xb5, 0xf8, 0x2c, 0xcb, 0x77, 0xc7, 0x67, 0x59, 0xfe, 0x0d, 0x7c, 0x96, 0xe5, 0x0a, 0xf1, 0x59, 0xc8, 0x7c, 0x1c, 0x9a, 0x32, 0x7b, 0xe4, 0x98, 0x49, 0xf9, 0xa5, 0x89, 0xd8, 0xa3, 0x9f, 0x8f, + 0xd9, 0x98, 0xdf, 0xfd, 0xfe, 0xb8, 0xfb, 0x88, 0x98, 0xc5, 0x99, 0xd3, 0x7d, 0x0e, 0xd0, 0xb9, 0x70, 0xce, 0xb2, 0xaf, 0x1e, 0x9f, 0x65, 0xb9, 0x2a, 0x7c, 0x96, 0xe5, 0xea, 0xf0, 0x59, 0x96, 0x29, 0xf1, 0x59, 0xd9, 0x57, 0x84, 0xcf, 0xa2, 0x04, 0xe3, 0xd4, 0x30, 0x5e, 0x2a, 0x40, 0x6a, 0x58, 0xee, 0xc4, 0xb0, 0x08, 0x4b, 0x8c, 0x16, 0xf1, 0x57, 0x22, 0xe5, 0x6f, 0xa5, 0x66, 0x1d, 0x56, 0xc9, 0x4e, 0x19, 0x0d, 0xa2, 0x2f, 0x84, 0xda, 0xff, 0x12, 0x1b, 0xd4, 0x48, 0x25, 0x23, 0x0e, 0x7c, 0x7f, 0x02, 0x38, 0x0b, 0xec, 0x48, 0x49, 0x4a, 0xd4, 0xcc, 0xac, 0xa9, 0x20, 0xa4, 0x52, 0x41, 0xcd, 0x6c, 0x0b, 0xc0, 0xf3, 0x99, 0x0b, 0x0f, 0x2e, 0x0b, 0xe7, 0xd3, 0x7e, 0x29, 0x7c, 0x96, 0x65, 0x0c, 0x9f, 0xe5, 0x1b, 0x8f, 0xcf, 0x8a, 0x75, 0x1d, 0xd8, 0xf3, 0x42, 0xe3, 0x7a, + 0x0f, 0xe0, 0x1e, 0xa0, 0xd8, 0x97, 0x13, 0xbb, 0x1b, 0x66, 0xd9, 0x6f, 0xd8, 0x8f, 0xc6, 0xf5, 0xf8, 0x83, 0x0b, 0xe7, 0xc7, 0xba, 0x08, 0xde, 0xdc, 0x0f, 0x0e, 0x27, 0xf4, 0x71, 0xf6, 0x7e, 0x14, 0x27, 0xf7, 0xc2, 0x97, 0xcc, 0x3b, 0xcc, 0x59, 0x42, 0x45, 0x4c, 0x03, 0x64, 0x54, 0x91, 0x09, 0xf0, 0xfd, 0x3e, 0x81, 0x80, 0x51, 0xb1, 0xcc, 0x2c, 0x6e, 0x51, 0x1c, 0x15, 0x84, 0x6f, 0x6b, 0xe1, 0x01, 0xed, 0x25, 0x10, 0x05, 0x99, 0x88, 0x4a, 0x52, 0x5f, 0x59, 0x61, 0x0e, 0x96, 0x64, 0xba, 0x9a, 0x96, 0xcd, 0x78, 0xf7, 0x40, 0x22, 0x3e, 0x32, 0x56, 0xe3, 0x22, 0x2d, 0x5f, 0x49, 0x39, 0x1e, 0xbb, 0x14, 0x55, 0xc1, 0x13, 0x35, 0x4d, 0x5d, 0xe1, 0xd6, 0xfa, 0x5c, 0x99, 0x1e, 0x07, 0x96, 0x44, 0xf8, 0x04, 0xb5, 0xfc, 0xc5, 0x98, 0x7b, 0x2a, 0xbc, 0x12, 0x5a, 0x86, 0x18, + 0x7e, 0x61, 0x0c, 0xb2, 0x44, 0x19, 0x83, 0x2b, 0xaa, 0x76, 0xdd, 0xe6, 0xab, 0xea, 0xcf, 0xcb, 0xeb, 0xab, 0xf2, 0x91, 0xf7, 0xc4, 0x30, 0x4b, 0x0b, 0x6f, 0x72, 0xe6, 0x3e, 0xbb, 0x72, 0xf0, 0xf0, 0x8a, 0x42, 0x14, 0x80, 0xad, 0x6f, 0x5b, 0xe8, 0xac, 0xd6, 0x9d, 0xef, 0x8e, 0xe3, 0x96, 0x28, 0xb2, 0x2c, 0xe7, 0xfb, 0x2f, 0xce, 0xdd, 0xdd, 0x95, 0xe6, 0xae, 0xe8, 0x8b, 0xfc, 0x8e, 0x43, 0x75, 0xec, 0x9b, 0x1b, 0xea, 0xe8, 0xe8, 0x6e, 0xaf, 0xdc, 0xf1, 0xfa, 0xe6, 0x95, 0xaf, 0xef, 0x9f, 0x51, 0x1c, 0x21, 0x03, 0xe7, 0xf6, 0x22, 0x2c, 0x87, 0x35, 0xd2, 0x12, 0x8a, 0xa3, 0x39, 0xd0, 0xfe, 0x52, 0x5f, 0xf8, 0x1b, 0xfd, 0x47, 0x1a, 0x69, 0x31, 0x9b, 0xa3, 0x12, 0x5f, 0x1c, 0xbb, 0xc4, 0x4d, 0x99, 0x37, 0x0e, 0x61, 0x12, 0xe2, 0x0c, 0x6d, 0x09, 0xd8, 0x25, 0x47, 0xe2, + 0xe5, 0x35, 0x0a, 0x70, 0x49, 0x53, 0x42, 0x8a, 0x16, 0x22, 0xb0, 0x13, 0x49, 0x09, 0x71, 0x8e, 0xa3, 0x8b, 0x97, 0xe7, 0x0e, 0x5f, 0x15, 0x51, 0x99, 0xe1, 0x76, 0x7a, 0x2e, 0x01, 0x77, 0x0a, 0x4f, 0x89, 0xf1, 0x51, 0x90, 0x42, 0x43, 0x2c, 0x3a, 0x3c, 0x37, 0x77, 0xf4, 0x8f, 0x63, 0x93, 0xd5, 0xb0, 0xad, 0x3f, 0x0f, 0xa5, 0x12, 0x5a, 0xb5, 0x6a, 0x68, 0x9d, 0x39, 0xaf, 0x25, 0xb7, 0x70, 0x7a, 0xbe, 0x5b, 0xae, 0xd0, 0x4a, 0x33, 0x0a, 0x8e, 0xac, 0xee, 0xdf, 0x3f, 0x37, 0x5b, 0xe3, 0xab, 0x08, 0xbe, 0xd2, 0xd4, 0xd0, 0x5d, 0x33, 0x11, 0x04, 0x13, 0x98, 0xb1, 0xa6, 0x3e, 0x18, 0xf5, 0xaa, 0xb6, 0xcf, 0x88, 0x26, 0x87, 0xbc, 0x06, 0x63, 0xb2, 0x51, 0xaa, 0x6e, 0x08, 0x57, 0x66, 0x34, 0xcc, 0xc9, 0xb5, 0x96, 0x15, 0x87, 0xd4, 0x79, 0xaf, 0x77, 0xd4, 0x8d, 0x01, 0x61, + 0x00, 0xa1, 0x85, 0xf2, 0xd9, 0xb3, 0x38, 0x8e, 0x68, 0x6d, 0xb4, 0x2a, 0x15, 0x2a, 0x20, 0x81, 0x4b, 0x62, 0x9c, 0x1c, 0x53, 0x62, 0x9c, 0x82, 0x44, 0x10, 0x4d, 0x80, 0x73, 0x22, 0xc6, 0xc9, 0x73, 0xe9, 0xd1, 0x23, 0x88, 0x13, 0xf5, 0x2c, 0x94, 0xe1, 0xde, 0x5c, 0xb9, 0x71, 0x64, 0x04, 0x0e, 0x34, 0x97, 0x1b, 0xa8, 0x02, 0x0e, 0x34, 0x50, 0x70, 0x64, 0x55, 0xff, 0x4d, 0x73, 0x72, 0xb4, 0xfe, 0xaa, 0x50, 0xff, 0xf2, 0x98, 0xe9, 0x23, 0xb7, 0xb6, 0x73, 0x8a, 0x81, 0xa5, 0x94, 0xc3, 0x81, 0x59, 0xb7, 0xf4, 0xdf, 0x8c, 0x74, 0x8e, 0x0b, 0x5f, 0x0a, 0x48, 0x48, 0x67, 0x22, 0xc4, 0xaf, 0xb8, 0x53, 0x3b, 0x19, 0xdb, 0x64, 0x49, 0xc0, 0x36, 0x39, 0xc7, 0x82, 0xfa, 0x5c, 0xb2, 0x9c, 0x7d, 0x2c, 0xb8, 0xce, 0x15, 0xb4, 0x67, 0xbe, 0xa2, 0xf6, 0x2e, 0xdf, 0x14, 0x17, 0xf8, + 0x1a, 0x4e, 0x70, 0x84, 0x08, 0x5f, 0x0c, 0x4c, 0x35, 0x51, 0xd3, 0x99, 0x70, 0x1c, 0x99, 0x3f, 0x72, 0x5b, 0x6a, 0x5b, 0x6b, 0xc3, 0xd6, 0xbe, 0xdc, 0xa9, 0x14, 0x20, 0xee, 0x14, 0x7a, 0xd4, 0x08, 0x32, 0x62, 0x27, 0x97, 0xc6, 0x0f, 0x5e, 0xa0, 0x7b, 0x7b, 0x27, 0xa9, 0x9c, 0xa8, 0x17, 0x8d, 0x76, 0x4f, 0x3c, 0x7e, 0xe8, 0x9e, 0xea, 0x1f, 0xcc, 0xcf, 0xf1, 0xfd, 0xec, 0x5f, 0xb9, 0x39, 0x4f, 0x86, 0xe7, 0x0c, 0x92, 0x4f, 0x01, 0x0f, 0x8f, 0xb2, 0x8c, 0x81, 0xa8, 0xc6, 0xe6, 0xfb, 0xe2, 0x65, 0xc6, 0xe6, 0xfa, 0x72, 0xed, 0x98, 0xaf, 0xa0, 0x1d, 0xdf, 0x65, 0xdb, 0x89, 0xda, 0x26, 0xfd, 0x9c, 0x88, 0xe6, 0x72, 0xc6, 0x62, 0x2a, 0x73, 0x17, 0xba, 0xe9, 0x53, 0x23, 0xba, 0x4a, 0xc0, 0x04, 0x44, 0x57, 0xc2, 0x12, 0xbc, 0x10, 0x3f, 0xd5, 0x68, 0x09, 0x38, 0x65, 0x69, + 0x1c, 0xed, 0x9b, 0x30, 0xeb, 0x3b, 0x63, 0x2a, 0xd4, 0xe4, 0xc9, 0xe6, 0xec, 0x7c, 0x17, 0xbe, 0xa2, 0x4d, 0xb4, 0x80, 0xc8, 0x24, 0xce, 0x9d, 0xe4, 0x60, 0x5d, 0x1c, 0x24, 0x2a, 0x25, 0x86, 0xee, 0x9a, 0x1b, 0x43, 0x77, 0x39, 0x12, 0xd1, 0x5d, 0xfc, 0xc4, 0x5f, 0xaa, 0x94, 0x33, 0x1e, 0x93, 0xea, 0xf2, 0x6d, 0x99, 0xaf, 0xa8, 0xad, 0xe4, 0x2b, 0x68, 0xeb, 0x72, 0xcd, 0x8c, 0x81, 0xa1, 0x32, 0x89, 0x4c, 0x8d, 0x67, 0x3c, 0x3a, 0x6d, 0x7c, 0x0c, 0xe6, 0x38, 0x36, 0x0d, 0x93, 0x55, 0x9c, 0x0e, 0x94, 0x36, 0x95, 0x8c, 0x3c, 0x38, 0xb8, 0xee, 0xec, 0x9e, 0xc6, 0xfa, 0x1b, 0xcf, 0x6c, 0x28, 0x5d, 0xd6, 0xdf, 0x60, 0x0d, 0xca, 0xd2, 0x4c, 0x8a, 0xb4, 0xe2, 0x19, 0x91, 0x86, 0xb5, 0x33, 0xfc, 0xc7, 0x8f, 0x6c, 0xdd, 0xf1, 0x4b, 0xb5, 0x1e, 0x5c, 0x58, 0xdb, 0x7f, 0xc7, + 0xe2, 0xfc, 0xa6, 0x1b, 0x5f, 0x1b, 0x19, 0x39, 0x7d, 0x63, 0x93, 0xde, 0x1d, 0xb4, 0xd4, 0xeb, 0xa4, 0x4a, 0x91, 0xc9, 0xa8, 0x0a, 0xcd, 0xd9, 0xdd, 0xf1, 0xca, 0x8b, 0x1b, 0x97, 0xe9, 0x54, 0x43, 0x78, 0xfe, 0xcd, 0x17, 0xfe, 0x4e, 0xb7, 0x60, 0xbb, 0xf8, 0x51, 0x6e, 0xbf, 0xa7, 0xc2, 0xfe, 0x03, 0x1a, 0x30, 0xf4, 0x30, 0xd2, 0x1f, 0xf1, 0x75, 0x03, 0x56, 0xf3, 0x27, 0xcc, 0xfc, 0x25, 0x8b, 0x39, 0xe3, 0xbb, 0xfe, 0xb2, 0xad, 0x5d, 0xb6, 0xa1, 0x31, 0xec, 0x98, 0xd3, 0x89, 0xf6, 0x2a, 0x9e, 0xae, 0x71, 0x54, 0x03, 0x1b, 0xb9, 0xc7, 0xf3, 0x9e, 0xdc, 0x31, 0x42, 0xd1, 0x9f, 0x8b, 0x2e, 0x40, 0xd8, 0x6d, 0x0d, 0xf5, 0xdd, 0xb5, 0xe3, 0xc8, 0xc2, 0x7e, 0x0e, 0x75, 0x48, 0xea, 0x0e, 0x1c, 0x18, 0xfd, 0xcb, 0x50, 0xe7, 0xca, 0x31, 0x44, 0x25, 0x8a, 0x93, 0xda, 0x7a, + 0xe1, 0xef, 0x50, 0x30, 0x15, 0x91, 0x02, 0xe2, 0x7d, 0xac, 0xab, 0x12, 0x50, 0x86, 0x7e, 0x1b, 0xce, 0x93, 0x01, 0xea, 0xaa, 0x7f, 0xe3, 0x04, 0x64, 0xa9, 0x13, 0x21, 0xed, 0xeb, 0x8d, 0x50, 0x88, 0xae, 0xb3, 0xe0, 0x6f, 0x22, 0xfe, 0x1b, 0x2f, 0x41, 0x3b, 0xa6, 0x22, 0x85, 0x9c, 0x55, 0x02, 0x33, 0x20, 0xab, 0x20, 0x06, 0xa8, 0x4a, 0x23, 0x84, 0x50, 0x35, 0x15, 0x8a, 0x86, 0xa7, 0xae, 0x41, 0x88, 0x44, 0x09, 0x15, 0x90, 0xd8, 0x9d, 0x41, 0x08, 0x49, 0x11, 0x39, 0xae, 0x02, 0xb6, 0x3a, 0x30, 0x31, 0x2c, 0x88, 0x73, 0x5c, 0xa5, 0xa8, 0x3f, 0x5e, 0x1e, 0xaa, 0x9a, 0x22, 0xc1, 0xec, 0x8b, 0x56, 0x23, 0xf9, 0x04, 0xec, 0x6e, 0xb7, 0x16, 0x19, 0xa2, 0x54, 0x5c, 0xc4, 0x98, 0x49, 0xa4, 0x59, 0x0b, 0x95, 0x92, 0x49, 0x92, 0x79, 0x1e, 0xad, 0x3a, 0x33, 0x15, 0x55, 0x56, + 0xab, 0x5e, 0x9a, 0x80, 0xa9, 0xca, 0x9e, 0xc2, 0x48, 0x25, 0x1a, 0x2f, 0x87, 0xa3, 0x5c, 0xd8, 0x50, 0x9b, 0x9d, 0xc6, 0x3c, 0x02, 0x35, 0x82, 0x67, 0xb8, 0xdd, 0xa4, 0xb0, 0x00, 0x91, 0x20, 0x08, 0xe5, 0x6a, 0x29, 0x52, 0x26, 0xd0, 0x7d, 0xeb, 0xd8, 0x03, 0x86, 0xa4, 0x62, 0x91, 0xc2, 0x73, 0xe0, 0x4c, 0x4a, 0x44, 0x42, 0xc9, 0xb0, 0x18, 0xdd, 0x87, 0x23, 0xad, 0x63, 0x20, 0x36, 0x19, 0x4b, 0xd1, 0x78, 0x25, 0x3d, 0x48, 0x3b, 0x1b, 0x44, 0xc7, 0x12, 0x4f, 0xc2, 0x7c, 0x14, 0x68, 0x30, 0x12, 0xab, 0x44, 0x20, 0xbc, 0x59, 0x07, 0xac, 0x4a, 0x30, 0x14, 0x42, 0xc5, 0x5c, 0xa6, 0x26, 0xa7, 0xd1, 0x65, 0x13, 0xd9, 0x59, 0x19, 0x4e, 0xb7, 0xcf, 0x89, 0xcd, 0x4b, 0x32, 0x94, 0x0e, 0x17, 0xdd, 0xbc, 0xf0, 0x7f, 0x26, 0xdf, 0x47, 0x8e, 0x0b, 0x2c, 0x11, 0x51, 0x33, 0xd3, + 0xd8, 0x62, 0x30, 0x9f, 0xbd, 0x17, 0x9c, 0x1d, 0xdd, 0x62, 0x9b, 0xe6, 0x82, 0x6a, 0x76, 0x65, 0xdd, 0x75, 0x2f, 0xae, 0x18, 0x3e, 0x5a, 0x52, 0x26, 0x50, 0xab, 0x54, 0x8e, 0xbc, 0x96, 0xe2, 0xca, 0x91, 0x19, 0x19, 0xc1, 0xb6, 0xe1, 0xf2, 0xba, 0x10, 0xf5, 0xc5, 0x5d, 0xec, 0x67, 0xb7, 0xb1, 0x7f, 0x38, 0x08, 0x8e, 0x8b, 0x24, 0x8d, 0xbb, 0x5f, 0x5e, 0x31, 0xfc, 0xda, 0x9e, 0x96, 0xda, 0xa2, 0x1e, 0x91, 0xdc, 0x94, 0x62, 0xca, 0xe9, 0xbf, 0xae, 0xb5, 0x7d, 0x7b, 0x4f, 0x96, 0x12, 0x3c, 0x80, 0x93, 0xa4, 0xf5, 0x5f, 0x30, 0x30, 0x1a, 0xc1, 0x62, 0x42, 0x42, 0x68, 0x89, 0x4e, 0xe2, 0xc3, 0xa8, 0xb5, 0x38, 0x48, 0x0a, 0x98, 0xca, 0x72, 0x12, 0x08, 0x9a, 0x80, 0x08, 0x34, 0x6b, 0x49, 0x42, 0x9c, 0x0a, 0x28, 0xc2, 0x06, 0xe7, 0x49, 0xc0, 0x87, 0xd6, 0xcc, 0x41, 0x41, + 0x8c, 0x05, 0xcc, 0x20, 0x21, 0x82, 0x0a, 0x95, 0x48, 0x3c, 0x4c, 0x88, 0x09, 0x20, 0x26, 0x96, 0xd3, 0xc8, 0xf3, 0x8d, 0x5c, 0x08, 0xb7, 0xce, 0xd8, 0x84, 0x60, 0x55, 0x78, 0x28, 0x9e, 0x0b, 0x25, 0xfb, 0xe2, 0x35, 0xc9, 0x55, 0x53, 0x57, 0x8c, 0x06, 0x51, 0x1d, 0x66, 0xe4, 0xca, 0xab, 0xc0, 0xfd, 0xa9, 0xef, 0x68, 0xab, 0xab, 0x89, 0x96, 0x16, 0xe6, 0xe5, 0xe6, 0xb8, 0x1c, 0x29, 0xd6, 0x64, 0x73, 0x85, 0x46, 0x22, 0x31, 0xa0, 0x48, 0x8f, 0x28, 0x01, 0x3a, 0x94, 0xf6, 0x95, 0x28, 0x84, 0xa3, 0xce, 0x60, 0x64, 0x14, 0x40, 0x89, 0x8d, 0x5b, 0x11, 0x8f, 0x57, 0x88, 0x13, 0xe8, 0x71, 0xa8, 0x4d, 0x2e, 0x47, 0x91, 0x30, 0x9c, 0x83, 0x16, 0x24, 0x82, 0x3f, 0xc3, 0xba, 0xfc, 0xca, 0x78, 0x29, 0xde, 0x9e, 0x6c, 0x24, 0xb7, 0xd9, 0x6b, 0x3c, 0x8b, 0x75, 0x99, 0x66, 0x79, + 0x92, 0x7c, 0x53, 0xed, 0xda, 0xd6, 0x8c, 0x75, 0x0b, 0x28, 0xa6, 0xfe, 0x47, 0x37, 0xa8, 0x49, 0xab, 0xb1, 0x4e, 0x5f, 0x5f, 0xa9, 0x01, 0x29, 0xc6, 0x5a, 0xf3, 0x9c, 0xbb, 0x96, 0x14, 0x34, 0xdf, 0xf4, 0xd6, 0x26, 0x25, 0xfa, 0xa6, 0x11, 0xa9, 0xc5, 0xd6, 0xb0, 0x59, 0x01, 0x3f, 0xd7, 0x19, 0x1a, 0xae, 0xcd, 0xad, 0x16, 0xe9, 0xd4, 0xb2, 0x64, 0x77, 0x56, 0xea, 0x60, 0xc3, 0xca, 0x30, 0x86, 0x72, 0xbe, 0x22, 0x95, 0x00, 0xba, 0x9a, 0x86, 0xe4, 0xce, 0x9c, 0xd4, 0x7f, 0xdd, 0x63, 0x83, 0x8f, 0x7c, 0x51, 0xe1, 0xef, 0x1e, 0x52, 0x3d, 0xae, 0xb3, 0xfd, 0x1a, 0x28, 0x4e, 0xea, 0x6c, 0x40, 0xee, 0xdd, 0x7e, 0xfc, 0x97, 0x1b, 0xf7, 0x7d, 0x71, 0xea, 0xda, 0xa4, 0x83, 0x3a, 0xdb, 0xab, 0x50, 0xc1, 0xbc, 0x59, 0x67, 0xfb, 0x5f, 0x63, 0x63, 0xce, 0x80, 0x54, 0xa1, 0x32, + 0x59, 0x15, 0xe7, 0x3e, 0x96, 0x16, 0x64, 0x0b, 0x11, 0xb8, 0x13, 0x10, 0xf3, 0x2f, 0x7c, 0xce, 0x24, 0x31, 0x8f, 0x13, 0xd3, 0x88, 0x3f, 0x72, 0x94, 0x41, 0xef, 0x82, 0xcc, 0x7f, 0x1a, 0x54, 0x93, 0x4a, 0x44, 0x50, 0x3f, 0x88, 0xe8, 0xc4, 0x70, 0xcd, 0x68, 0x78, 0x54, 0xf8, 0xe7, 0x60, 0xfc, 0xf3, 0x1e, 0x8e, 0xf7, 0x65, 0x10, 0x8c, 0x40, 0x28, 0x60, 0x84, 0x48, 0x3b, 0x47, 0x80, 0xb1, 0x61, 0x94, 0x9b, 0x4a, 0x48, 0x74, 0xc1, 0xa9, 0xc7, 0x8a, 0xd6, 0x52, 0x4e, 0x53, 0x1f, 0x5b, 0x6c, 0xf4, 0xa6, 0x4c, 0x58, 0x49, 0x24, 0x60, 0x44, 0xf1, 0x4a, 0x04, 0x0a, 0x1d, 0x4d, 0x8a, 0x88, 0x81, 0xb1, 0x6a, 0x44, 0x42, 0xad, 0x68, 0x6e, 0xbc, 0x02, 0x42, 0x36, 0x10, 0xc8, 0x16, 0x80, 0x92, 0x81, 0x2f, 0x8f, 0x41, 0x6a, 0xa6, 0xac, 0x86, 0xa8, 0x91, 0xdb, 0x0f, 0x8f, 0x96, + 0xcb, 0xef, 0x50, 0xc7, 0xd2, 0xa1, 0x73, 0x59, 0x3c, 0x79, 0xcb, 0x32, 0x9f, 0x06, 0x2b, 0x1e, 0xbb, 0x65, 0xd2, 0x09, 0x33, 0xaa, 0xb1, 0x3f, 0x3f, 0xe7, 0x78, 0x49, 0x9f, 0x11, 0x9a, 0x15, 0xf9, 0xb6, 0xaa, 0x4e, 0x87, 0xaf, 0xba, 0xb4, 0xc0, 0x2a, 0xd2, 0x48, 0xd5, 0x8a, 0xf6, 0xee, 0xd1, 0x67, 0xee, 0x1d, 0x3d, 0x31, 0x17, 0x27, 0x85, 0xb8, 0xfb, 0xb3, 0xee, 0x6a, 0xa1, 0x4e, 0xa3, 0x0d, 0xb5, 0x6e, 0x9c, 0x89, 0xd2, 0x47, 0x14, 0x2d, 0xbe, 0xa5, 0x63, 0x41, 0xaa, 0x89, 0x91, 0x66, 0x8c, 0x34, 0xda, 0x8b, 0x0c, 0xe4, 0x47, 0x16, 0xe9, 0xd2, 0xb9, 0xec, 0x23, 0x8c, 0x36, 0xc5, 0x67, 0x86, 0x8b, 0xd0, 0xb7, 0x68, 0x24, 0x1e, 0x95, 0x65, 0x68, 0x7e, 0x1b, 0x5c, 0x22, 0x4f, 0x96, 0x07, 0xc5, 0x64, 0x59, 0xfd, 0xf2, 0x8d, 0x5d, 0x4a, 0xf2, 0x45, 0xa3, 0x75, 0xef, + 0xbc, 0x61, 0x85, 0x12, 0xad, 0x55, 0x35, 0x54, 0x5b, 0x2b, 0x70, 0x2e, 0xbb, 0x08, 0x9c, 0x0b, 0x34, 0x09, 0xf8, 0x06, 0x1c, 0xd2, 0x1b, 0x94, 0x7f, 0x60, 0x2e, 0x0a, 0x63, 0x8f, 0xc1, 0xad, 0x03, 0x0d, 0x1c, 0x69, 0xc1, 0xb9, 0xed, 0xd4, 0xb1, 0xff, 0x70, 0xb6, 0xf0, 0x38, 0x49, 0x89, 0x9b, 0x85, 0x3e, 0x04, 0xff, 0x60, 0x65, 0x20, 0x9b, 0xfd, 0xe9, 0xfb, 0xaf, 0xbf, 0x4e, 0x15, 0xd2, 0x7f, 0x63, 0xdb, 0x9e, 0x60, 0xcf, 0xad, 0x40, 0x59, 0xa0, 0x56, 0xd0, 0xb2, 0x27, 0x30, 0x8f, 0xcf, 0x83, 0xef, 0x9d, 0xc3, 0x1c, 0x23, 0x2a, 0x88, 0xfa, 0x68, 0x4d, 0x0e, 0x10, 0x49, 0x8c, 0x2a, 0x19, 0x82, 0x41, 0xd7, 0x4b, 0x81, 0x48, 0x08, 0xf9, 0x95, 0x88, 0x80, 0xbd, 0x88, 0x53, 0x4a, 0x89, 0x44, 0xdc, 0x4b, 0x88, 0xc5, 0x4b, 0x31, 0x91, 0xeb, 0xe5, 0x7a, 0x52, 0x51, 0x5e, + 0x5c, 0x98, 0x19, 0x80, 0xc4, 0xcd, 0xe8, 0x76, 0x3b, 0xd5, 0x2a, 0x4c, 0xdf, 0x50, 0x24, 0x2e, 0x74, 0xf3, 0x85, 0x1c, 0x3c, 0xc2, 0x21, 0x3b, 0x87, 0xc0, 0xcc, 0x2b, 0xa5, 0xb8, 0x8c, 0xdf, 0x4e, 0x0c, 0x46, 0x4d, 0x48, 0x3b, 0xe8, 0xe1, 0xc5, 0x19, 0x2b, 0xb0, 0x53, 0xcb, 0xd8, 0x19, 0xe0, 0x29, 0x9b, 0x89, 0xdd, 0x1d, 0x09, 0x96, 0x96, 0x90, 0xfb, 0x65, 0x06, 0x9b, 0xf6, 0x7c, 0xa9, 0xa3, 0x34, 0x98, 0x7c, 0xfa, 0xf4, 0x69, 0x9b, 0xe9, 0x86, 0xc8, 0xf2, 0x47, 0x56, 0x34, 0x6f, 0x9a, 0x5d, 0x95, 0xd4, 0x9c, 0x9c, 0x63, 0xd0, 0xfa, 0xb2, 0xcb, 0xfc, 0xc5, 0xbd, 0x25, 0x29, 0xe0, 0x29, 0xf2, 0xe3, 0xed, 0x45, 0x3a, 0xfb, 0xea, 0xda, 0x65, 0x19, 0x8a, 0x36, 0x65, 0xaa, 0x49, 0xb5, 0x59, 0x62, 0xcb, 0x9e, 0x96, 0xf1, 0xe8, 0xf0, 0xf6, 0x62, 0x9d, 0xed, 0x96, 0x59, + 0x47, 0x36, 0xd6, 0x7a, 0x2b, 0xfb, 0xf2, 0x72, 0x14, 0x74, 0x52, 0x52, 0xc0, 0xae, 0xf1, 0x35, 0x2c, 0xa9, 0xb8, 0x95, 0xc0, 0x69, 0xa9, 0xa0, 0x24, 0xc1, 0xfc, 0x2f, 0x9c, 0x77, 0x0a, 0xd2, 0x16, 0x39, 0xa4, 0x83, 0x45, 0x44, 0x7f, 0x74, 0xa6, 0x46, 0xa5, 0xa0, 0x18, 0xbd, 0x56, 0x4e, 0x41, 0x69, 0x99, 0x21, 0xe6, 0xc9, 0x04, 0x3a, 0x9a, 0x06, 0x52, 0xb1, 0x10, 0x65, 0xaa, 0x27, 0xe7, 0xaa, 0x81, 0x52, 0x39, 0xd0, 0x20, 0x81, 0x2b, 0x32, 0x5f, 0xd4, 0xa8, 0xd3, 0x49, 0x24, 0xba, 0x22, 0x5d, 0x51, 0x61, 0x41, 0x7e, 0x24, 0x9c, 0x97, 0x13, 0xcc, 0xca, 0xf0, 0xa7, 0xf9, 0xbc, 0x1e, 0xb7, 0xcb, 0xe9, 0xb0, 0xa7, 0x24, 0x5b, 0x92, 0x0c, 0x7a, 0xad, 0x46, 0x22, 0x97, 0xc8, 0x35, 0x7a, 0xb5, 0xca, 0x80, 0x53, 0x57, 0x19, 0x43, 0x90, 0x0c, 0xc1, 0x89, 0x70, 0x7b, 0x9d, + 0x7a, 0x0a, 0xa9, 0x86, 0x21, 0x46, 0x1f, 0xd2, 0x8f, 0x05, 0x94, 0xe1, 0xd7, 0x8f, 0x9f, 0x25, 0xce, 0xde, 0x8e, 0xa4, 0xed, 0x90, 0x9e, 0xde, 0xc1, 0x7e, 0x58, 0xf5, 0x1b, 0x21, 0x4d, 0x1d, 0x62, 0x47, 0xab, 0x7e, 0x2f, 0xa0, 0xa8, 0x87, 0xc9, 0x5f, 0x3c, 0x69, 0x33, 0xad, 0x7d, 0x34, 0xc9, 0xae, 0x66, 0x3f, 0x66, 0xef, 0x7c, 0xea, 0xc9, 0x27, 0x9f, 0xb4, 0x25, 0xad, 0x39, 0x9a, 0x9c, 0xac, 0x06, 0x6e, 0x30, 0xef, 0xc9, 0x27, 0x29, 0x66, 0x03, 0xbb, 0x32, 0xa5, 0xc8, 0xfc, 0xc4, 0x8a, 0x0d, 0x1b, 0x48, 0x93, 0xb5, 0xd0, 0xf4, 0xda, 0xf0, 0x86, 0x22, 0x9d, 0x6d, 0xf4, 0x13, 0x8d, 0x0e, 0xfc, 0x73, 0xf4, 0x13, 0x32, 0x79, 0xc5, 0x06, 0x38, 0x3d, 0xa4, 0x57, 0x6e, 0x1c, 0x1d, 0x25, 0xbd, 0xa3, 0xff, 0x8d, 0x8c, 0x27, 0x70, 0x26, 0x32, 0xe0, 0x9e, 0x58, 0x85, 0x73, + 0x82, 0xc9, 0xa1, 0x94, 0x55, 0x13, 0xad, 0x94, 0x22, 0xf0, 0xbe, 0x80, 0xc0, 0x50, 0x78, 0xbc, 0x01, 0x06, 0x1a, 0x70, 0x7a, 0x05, 0xb2, 0x47, 0x02, 0x09, 0x06, 0x39, 0x88, 0x74, 0x88, 0xf9, 0x82, 0x46, 0x40, 0x38, 0x1d, 0x29, 0x3c, 0x22, 0x57, 0xaf, 0x55, 0x2a, 0xe4, 0x52, 0xa1, 0x80, 0x46, 0xa0, 0x0e, 0x46, 0xc6, 0xa5, 0x60, 0x8d, 0xd8, 0x93, 0x80, 0x1d, 0xc5, 0x41, 0x03, 0x4e, 0x45, 0x3c, 0x70, 0x1b, 0xce, 0xfe, 0x64, 0x0c, 0x81, 0x7f, 0xdc, 0x79, 0x50, 0x01, 0xd7, 0xfe, 0x69, 0x26, 0xad, 0x69, 0xf5, 0xf4, 0x11, 0xf2, 0x8b, 0xeb, 0xc4, 0xb5, 0x9b, 0x4f, 0xae, 0x1d, 0x79, 0x73, 0xef, 0x5e, 0x50, 0xb4, 0xf9, 0xa5, 0x6b, 0xcb, 0x05, 0x3b, 0xc8, 0xdb, 0xaf, 0xdb, 0x78, 0x81, 0x00, 0xc3, 0xd1, 0xd9, 0x65, 0x29, 0xec, 0x01, 0x44, 0x59, 0x59, 0x21, 0x75, 0xfb, 0x30, + 0xb8, 0x33, 0x6f, 0xe1, 0x41, 0x84, 0x73, 0x7a, 0xe7, 0xc2, 0xdf, 0x85, 0x5e, 0xd8, 0x6f, 0x09, 0xec, 0x75, 0x1f, 0xf1, 0x7c, 0x54, 0xd1, 0x07, 0x84, 0x84, 0x1e, 0xea, 0xfa, 0x65, 0x80, 0x92, 0x20, 0xeb, 0x16, 0x72, 0x8d, 0x2b, 0x20, 0x04, 0x42, 0x5a, 0x88, 0x2e, 0x07, 0xc4, 0xb4, 0x84, 0x16, 0x43, 0x9e, 0x2e, 0x25, 0x84, 0x02, 0xa9, 0x70, 0x40, 0x86, 0xd2, 0xf0, 0x8a, 0x11, 0x62, 0x0a, 0x99, 0x8d, 0xe4, 0x5c, 0x2a, 0x34, 0x89, 0x84, 0xea, 0x15, 0x21, 0x1c, 0xc1, 0x7c, 0x44, 0xa4, 0x4a, 0xae, 0xa6, 0x2e, 0x92, 0x9b, 0xe6, 0xa3, 0x16, 0x70, 0x8e, 0x83, 0x15, 0x22, 0x28, 0x0d, 0x38, 0x11, 0x6c, 0x79, 0x66, 0x77, 0x5d, 0x4d, 0x65, 0x45, 0x51, 0x41, 0xc0, 0xef, 0x71, 0x3b, 0xec, 0xb6, 0x14, 0xb3, 0x49, 0xea, 0x94, 0x39, 0x39, 0xf0, 0xb2, 0x42, 0x12, 0xcb, 0xf7, 0xe4, + 0x49, 0x00, 0x2f, 0x0b, 0xe1, 0x0e, 0xe0, 0x02, 0xf3, 0xe1, 0x39, 0x03, 0x09, 0x69, 0x3a, 0xe9, 0xc9, 0x49, 0x46, 0xb0, 0x42, 0x46, 0xc7, 0x04, 0x0a, 0xb2, 0xba, 0xf5, 0xda, 0x76, 0xff, 0x83, 0x0f, 0x3e, 0xfe, 0xf8, 0x9e, 0x37, 0xb7, 0x97, 0x65, 0xf6, 0x6c, 0x9d, 0x91, 0x96, 0x0c, 0x7a, 0x5f, 0xcd, 0x4f, 0x66, 0x7f, 0xa9, 0xf6, 0xa6, 0x2d, 0xfb, 0x85, 0xc0, 0x23, 0x37, 0x29, 0xec, 0xe9, 0x05, 0x9e, 0xe2, 0xea, 0xe2, 0x99, 0x8b, 0x66, 0x16, 0xfb, 0x5b, 0xd7, 0xb7, 0xf8, 0x67, 0xd4, 0x96, 0x99, 0xd2, 0x45, 0x46, 0x8d, 0xc6, 0x16, 0x28, 0xce, 0x70, 0xe7, 0x39, 0x54, 0xc5, 0xbd, 0x8b, 0x7b, 0x8b, 0xbd, 0x4d, 0x23, 0x0d, 0xed, 0xbb, 0x33, 0xc9, 0x3f, 0xf9, 0xea, 0x16, 0x94, 0x6c, 0xda, 0x3c, 0xfa, 0x77, 0xf2, 0x44, 0xc1, 0xe2, 0x03, 0x7d, 0x0d, 0x3b, 0x17, 0x37, 0xea, + 0x4c, 0xe9, 0xa3, 0x3f, 0xcb, 0x00, 0x5f, 0x26, 0xe9, 0x47, 0x9f, 0x65, 0xee, 0x3d, 0x7f, 0xce, 0xa8, 0xd6, 0xaa, 0x73, 0xfd, 0x2e, 0x7f, 0xaa, 0x2d, 0xb7, 0xb5, 0xa2, 0x68, 0xa0, 0x36, 0x4d, 0x69, 0x71, 0xeb, 0xeb, 0xe4, 0x0a, 0xbd, 0x49, 0xaf, 0xb6, 0xba, 0xf5, 0xde, 0xf4, 0xd4, 0xd4, 0x70, 0x77, 0x5d, 0xce, 0xcc, 0x4a, 0x5f, 0x24, 0x93, 0xc0, 0xd9, 0xea, 0xfa, 0xe1, 0x5f, 0x5f, 0xe2, 0x73, 0x28, 0x23, 0x54, 0x44, 0x12, 0x61, 0x25, 0x3c, 0x44, 0x37, 0xf1, 0x1c, 0x12, 0x9d, 0x45, 0x1a, 0x83, 0x98, 0x94, 0xc6, 0x8d, 0x5c, 0x39, 0x4a, 0xb1, 0x20, 0x89, 0x66, 0x80, 0x42, 0x26, 0x61, 0x68, 0x52, 0x0a, 0x0f, 0xa4, 0x41, 0x47, 0x6a, 0x34, 0x03, 0x0d, 0x66, 0x93, 0x4a, 0x48, 0x31, 0x04, 0x21, 0xea, 0x41, 0xf1, 0x87, 0x68, 0x91, 0x68, 0xb0, 0x41, 0x0e, 0xa4, 0xd2, 0xf9, + 0x52, 0x24, 0x48, 0x70, 0x8f, 0xf8, 0x22, 0x90, 0xaa, 0xcd, 0xbb, 0x58, 0x2b, 0x7c, 0x95, 0x9e, 0x68, 0x81, 0xd7, 0x2b, 0x97, 0x7b, 0xbb, 0xbd, 0xdd, 0x5d, 0x9d, 0x1d, 0x6d, 0xd3, 0x5b, 0x1a, 0xeb, 0xaa, 0xab, 0x2a, 0xa2, 0x65, 0x25, 0xdc, 0x41, 0xcf, 0x0d, 0xe5, 0x64, 0x07, 0xb3, 0x32, 0x03, 0x69, 0x3e, 0x0e, 0x84, 0xae, 0x51, 0xcb, 0x55, 0x72, 0x55, 0x92, 0x5a, 0xad, 0xb2, 0xf0, 0x87, 0x5c, 0xe8, 0x34, 0xf2, 0xb7, 0x64, 0x5e, 0xa7, 0x12, 0xc4, 0xbe, 0x50, 0xa1, 0x88, 0xdd, 0x1d, 0xf2, 0x72, 0x19, 0x4f, 0x19, 0xa1, 0x1d, 0x9f, 0x7b, 0xca, 0x49, 0xd9, 0xd1, 0x9f, 0x3c, 0xbb, 0x97, 0x3b, 0xf4, 0x54, 0x88, 0xcb, 0x05, 0x8a, 0x64, 0x8f, 0xb1, 0x4f, 0x54, 0x48, 0x2f, 0xa8, 0x63, 0x3f, 0xf1, 0xbe, 0x9b, 0x74, 0x9f, 0x5f, 0x94, 0x24, 0x15, 0xaa, 0xc5, 0xec, 0x69, 0xcf, + 0x71, 0xef, 0x2b, 0x87, 0x3c, 0x22, 0x8d, 0x48, 0x9a, 0x24, 0xda, 0xf7, 0x73, 0x2f, 0xfb, 0x3b, 0xf0, 0xeb, 0xf5, 0x7a, 0x91, 0x41, 0x2c, 0x36, 0x88, 0x28, 0x85, 0x0f, 0x48, 0x6c, 0x49, 0x9f, 0xdd, 0xf1, 0xf1, 0x2d, 0xec, 0x3b, 0xb7, 0x7e, 0x72, 0x07, 0xfb, 0x6b, 0x20, 0xf1, 0xfd, 0xce, 0x96, 0xb4, 0x6f, 0xf5, 0x91, 0x64, 0x2f, 0xd3, 0x07, 0x5a, 0x17, 0xd0, 0x69, 0x96, 0x65, 0x56, 0x2f, 0xbd, 0x00, 0x4c, 0xef, 0x83, 0x1f, 0xef, 0x5e, 0xfd, 0x06, 0x35, 0xf7, 0xd0, 0xf0, 0xd1, 0x9c, 0x0d, 0xd1, 0xf2, 0x0d, 0xd9, 0xc0, 0x7c, 0x08, 0x7d, 0x46, 0x6e, 0xf7, 0x39, 0xf0, 0xf3, 0x70, 0xb7, 0x77, 0x61, 0x28, 0xb4, 0xd0, 0xfb, 0xa3, 0x43, 0x90, 0xca, 0x8e, 0x3e, 0x41, 0xb6, 0xa1, 0x3f, 0xc3, 0xe8, 0x0b, 0x78, 0xdb, 0x62, 0x64, 0x17, 0x82, 0xbb, 0x74, 0x29, 0xdc, 0xbf, 0x7a, + 0x2b, 0x9b, 0x37, 0xcc, 0x61, 0x2e, 0x7e, 0x04, 0xa9, 0xc6, 0x87, 0xf4, 0x4e, 0xb8, 0xa2, 0x49, 0x08, 0xcb, 0x47, 0x30, 0x24, 0xe2, 0x67, 0xc3, 0xd8, 0xd0, 0x28, 0x58, 0x2e, 0x16, 0x22, 0x6b, 0x3f, 0x43, 0x90, 0x88, 0xb7, 0x63, 0x7d, 0x0a, 0x9f, 0x32, 0xec, 0x13, 0x9c, 0x24, 0x4f, 0x32, 0x19, 0x74, 0x5a, 0xec, 0x15, 0x2c, 0x73, 0x68, 0xed, 0x12, 0x89, 0x31, 0x1d, 0x11, 0x0a, 0x1c, 0xb1, 0x22, 0x17, 0x85, 0xab, 0x55, 0xdb, 0x1d, 0x1e, 0x7c, 0x45, 0x1a, 0xb2, 0xab, 0x7f, 0x44, 0x96, 0xbc, 0xf0, 0xc4, 0x33, 0xcf, 0xb2, 0xab, 0xe9, 0x05, 0xa3, 0x67, 0x5e, 0x7c, 0xfc, 0x99, 0x67, 0xc1, 0x1e, 0x7a, 0xe7, 0x91, 0x23, 0xac, 0x08, 0x7c, 0x7d, 0xe2, 0xbc, 0x8e, 0xde, 0x79, 0x6e, 0xd3, 0xc3, 0x8f, 0x80, 0xaf, 0x59, 0xd1, 0x09, 0xea, 0x2f, 0x31, 0xfc, 0x10, 0x73, 0x2b, 0xce, + 0x0f, 0xef, 0x8f, 0x7a, 0x45, 0x02, 0x92, 0x16, 0xa3, 0xa4, 0xa0, 0x34, 0x98, 0x87, 0xa0, 0x91, 0x03, 0x18, 0x10, 0x3b, 0x9f, 0x68, 0x74, 0x3a, 0xb4, 0x18, 0x13, 0xab, 0x36, 0xd9, 0x24, 0x12, 0x8c, 0xdb, 0x85, 0xa7, 0x0c, 0x05, 0xc7, 0x8c, 0x41, 0xf9, 0x13, 0x63, 0x7a, 0x0a, 0x50, 0x32, 0x24, 0x28, 0x52, 0xde, 0xfa, 0xa2, 0xce, 0xaa, 0x57, 0x0b, 0xd9, 0x72, 0xb5, 0xc7, 0xbe, 0xf2, 0x17, 0xef, 0xbe, 0xfb, 0x8b, 0x6a, 0x4f, 0x96, 0x16, 0xbc, 0xa5, 0xcf, 0x72, 0x9c, 0x64, 0x95, 0xa9, 0xc9, 0xe0, 0x29, 0x76, 0x49, 0xb6, 0x4b, 0x20, 0x55, 0x49, 0xc9, 0x6c, 0x83, 0x81, 0x1a, 0x5d, 0x4d, 0xee, 0x3d, 0x4c, 0xae, 0x18, 0xbd, 0x45, 0x6b, 0x24, 0xf5, 0x16, 0xfd, 0xe8, 0x5b, 0x5e, 0xcd, 0x61, 0x7d, 0x6e, 0xac, 0x8f, 0xf4, 0x09, 0x1c, 0xf7, 0x25, 0x2f, 0x9a, 0x63, 0xd2, 0x6a, + 0xd4, 0x2a, 0xa5, 0x02, 0x92, 0x53, 0x06, 0xe5, 0x5c, 0x55, 0xa2, 0xeb, 0x79, 0x9c, 0x7c, 0x15, 0xab, 0xc4, 0x09, 0x59, 0x1f, 0x51, 0x24, 0x96, 0xe4, 0x64, 0x2e, 0xeb, 0xa3, 0x5b, 0xcf, 0xe8, 0x51, 0xba, 0x1e, 0x25, 0xa2, 0x19, 0x4c, 0x9e, 0x3b, 0x8f, 0x81, 0x7b, 0xab, 0x0c, 0x70, 0xf0, 0xbf, 0xbf, 0x00, 0x2b, 0xfb, 0x31, 0xb0, 0x9a, 0xb5, 0x9f, 0xbf, 0xc7, 0xfe, 0x05, 0xe8, 0xd8, 0xbf, 0x98, 0x75, 0x7f, 0x60, 0xc3, 0xf4, 0x42, 0xfa, 0xcf, 0xec, 0xbb, 0xb7, 0x5f, 0x7f, 0xe0, 0xd6, 0xdb, 0xbb, 0x34, 0xe9, 0xe2, 0xcd, 0xa3, 0xbf, 0x03, 0x2f, 0xdf, 0xbc, 0xeb, 0xe6, 0x5b, 0x6f, 0xee, 0xd4, 0xfa, 0x25, 0xeb, 0xc0, 0x2d, 0x28, 0x9c, 0x1b, 0x3c, 0xa1, 0xa5, 0xec, 0x31, 0x71, 0x31, 0xec, 0x5b, 0x80, 0x28, 0x23, 0xda, 0x89, 0x51, 0x2e, 0x2e, 0x4d, 0x2b, 0xce, 0xc7, 0x64, + 0x41, 0xff, 0x0a, 0xe2, 0x71, 0x69, 0xf2, 0x08, 0x78, 0xb8, 0x68, 0x66, 0x11, 0x94, 0xfc, 0x68, 0x52, 0xb4, 0x1c, 0xab, 0xee, 0x88, 0x99, 0x0c, 0x20, 0x29, 0x0e, 0xd3, 0xc5, 0xe5, 0x0d, 0x08, 0xba, 0x06, 0xfa, 0xa0, 0x4e, 0x05, 0x30, 0xb5, 0x5c, 0x24, 0x89, 0x05, 0x91, 0x2f, 0xbb, 0x82, 0xca, 0x44, 0x42, 0x5d, 0x04, 0x03, 0x1d, 0xd7, 0x4c, 0x34, 0xca, 0xb5, 0x80, 0x84, 0xc9, 0xef, 0xd6, 0x04, 0x14, 0x30, 0xad, 0x80, 0x68, 0x6f, 0x9b, 0xde, 0x5c, 0x5d, 0x59, 0x54, 0x90, 0x9d, 0x95, 0xe6, 0x75, 0x39, 0xcc, 0x26, 0x9d, 0x46, 0x2a, 0x26, 0x02, 0x20, 0xc0, 0x71, 0xb5, 0x14, 0xa0, 0x41, 0xc1, 0xee, 0x69, 0xa7, 0x3d, 0x12, 0xc3, 0x21, 0x22, 0x20, 0x20, 0xf6, 0xee, 0x0a, 0xc7, 0x0d, 0xc2, 0x25, 0x00, 0x70, 0xb9, 0xb2, 0x60, 0x21, 0x0e, 0x70, 0xaa, 0xe4, 0xef, 0x97, 0x85, + 0x48, 0x79, 0xf6, 0x78, 0x21, 0x01, 0x08, 0x3c, 0x79, 0xfe, 0x56, 0x8f, 0x2f, 0xed, 0x91, 0x8f, 0xd8, 0x7f, 0xec, 0x63, 0x1d, 0x96, 0x39, 0xdb, 0x32, 0xf7, 0xc8, 0x0b, 0xf2, 0x57, 0x3e, 0x9c, 0xdc, 0x5e, 0xf9, 0xd6, 0xec, 0xa7, 0xef, 0x18, 0xb1, 0xdb, 0xb4, 0xab, 0x3f, 0x78, 0xac, 0xbc, 0x7f, 0x7a, 0x73, 0xb6, 0x33, 0x8b, 0x59, 0x61, 0xec, 0xcb, 0xcf, 0x1b, 0xec, 0xa9, 0x31, 0x9a, 0x80, 0xa7, 0x36, 0x18, 0xe8, 0x6e, 0x8e, 0xea, 0xde, 0x34, 0xcf, 0xdd, 0xf5, 0xc8, 0xdc, 0xce, 0x9b, 0x66, 0x9a, 0x53, 0x93, 0x87, 0x6e, 0x7e, 0xac, 0xbb, 0x7e, 0x99, 0x73, 0xf5, 0xeb, 0xcc, 0xbd, 0x8b, 0x5e, 0x61, 0xff, 0x75, 0xff, 0x61, 0xf6, 0xeb, 0x57, 0x16, 0xcd, 0x15, 0x3e, 0xff, 0x02, 0x99, 0x1e, 0x0e, 0x8d, 0x0e, 0x0d, 0x3f, 0x98, 0xd3, 0x7a, 0xc7, 0xcf, 0xb7, 0x6d, 0xfd, 0xe0, + 0xfe, 0x99, 0xa6, 0x64, 0x53, 0xd0, 0xb1, 0xea, 0xdc, 0x70, 0xcd, 0xb4, 0xf0, 0xc2, 0x3b, 0xe6, 0x2c, 0xba, 0xa5, 0x37, 0x2d, 0x6d, 0xc6, 0xfa, 0x16, 0x59, 0x62, 0x92, 0x98, 0xc2, 0x3c, 0x36, 0x9f, 0x3b, 0xd3, 0x85, 0xec, 0x76, 0x7a, 0x21, 0xf3, 0x29, 0x94, 0x94, 0x1a, 0x88, 0xea, 0xa8, 0xba, 0xa1, 0x3a, 0x9a, 0x6e, 0x64, 0x04, 0x28, 0x83, 0x6d, 0x55, 0x38, 0x8f, 0x22, 0x6b, 0xb8, 0x08, 0xee, 0xc8, 0x84, 0x88, 0xed, 0x5b, 0x0b, 0x1a, 0xb8, 0xb0, 0xdf, 0x18, 0xfe, 0x8c, 0xf3, 0x41, 0x0e, 0x70, 0x0f, 0x7a, 0x9e, 0xaf, 0xaf, 0x75, 0xd8, 0x32, 0x29, 0x89, 0x9e, 0x8f, 0xb9, 0x48, 0xc5, 0xac, 0xe7, 0x48, 0xc1, 0x9a, 0x90, 0x38, 0x85, 0x33, 0xe9, 0x08, 0xbd, 0x5c, 0x21, 0xaf, 0x82, 0x12, 0x4a, 0x51, 0x08, 0x1b, 0x40, 0xee, 0x32, 0x07, 0x8a, 0xec, 0x16, 0xa7, 0x56, 0x64, + 0x70, 0x66, 0x9a, 0x92, 0x32, 0x1c, 0xba, 0xf6, 0xac, 0x46, 0x5b, 0xb0, 0xbf, 0x2e, 0x23, 0xd4, 0x3e, 0x54, 0x58, 0xbc, 0xb8, 0x25, 0xd3, 0xe0, 0xcd, 0x4d, 0x4d, 0x9f, 0x5e, 0xea, 0xf6, 0x4c, 0xeb, 0x0f, 0x37, 0xda, 0x0b, 0xfd, 0x26, 0xa3, 0x2f, 0x9c, 0xea, 0x6e, 0x98, 0x56, 0xa8, 0xfb, 0xf1, 0x36, 0x55, 0xda, 0x42, 0x90, 0x44, 0x2f, 0x2f, 0x5f, 0x39, 0xd8, 0x9f, 0x11, 0x5d, 0x3e, 0x6f, 0x66, 0x46, 0x59, 0x7f, 0xb1, 0xd5, 0x5a, 0xd8, 0x7d, 0xbe, 0xad, 0xf9, 0xfa, 0xe2, 0x69, 0x7b, 0x0e, 0xdc, 0xdf, 0xdd, 0x7f, 0xcb, 0x40, 0x28, 0x38, 0xe7, 0xe6, 0x39, 0xa5, 0xd7, 0xed, 0xde, 0x53, 0x19, 0x1e, 0xd9, 0x72, 0x6b, 0x67, 0xd3, 0xb6, 0x65, 0x7d, 0xbe, 0x1a, 0x7b, 0x4b, 0xe7, 0xac, 0x60, 0xb0, 0xaf, 0x6f, 0x66, 0x66, 0xd5, 0x96, 0xa1, 0xee, 0x54, 0xb0, 0x97, 0x7d, 0xc1, + 0x4f, 0x1e, 0x46, 0x37, 0x4f, 0x44, 0x2a, 0xfc, 0xeb, 0x1a, 0x9c, 0xab, 0x5e, 0x41, 0xe8, 0xa0, 0x74, 0x3d, 0x10, 0x9d, 0x63, 0x85, 0x1b, 0xaa, 0x4e, 0x29, 0x27, 0x69, 0x19, 0x9a, 0x27, 0x9a, 0x98, 0x2b, 0x55, 0x91, 0x40, 0x22, 0xc4, 0x69, 0x70, 0xa0, 0x30, 0x49, 0x12, 0x22, 0x31, 0x29, 0x42, 0x8e, 0xf7, 0x8a, 0x5e, 0x42, 0xa1, 0x80, 0x5b, 0x53, 0x2c, 0x9e, 0x2f, 0x6e, 0x94, 0x88, 0x09, 0x22, 0x2f, 0x37, 0x98, 0x99, 0x11, 0x48, 0x8f, 0x49, 0x94, 0xf6, 0xd4, 0x14, 0x83, 0x5e, 0xa7, 0x51, 0x29, 0xe5, 0x32, 0xb1, 0x42, 0xa2, 0x40, 0x80, 0x05, 0xb7, 0x06, 0x4d, 0x60, 0x29, 0xc8, 0x2b, 0x06, 0x5e, 0xbb, 0x5e, 0x8b, 0x44, 0x2c, 0xbd, 0xda, 0x8e, 0x04, 0x05, 0xe0, 0x34, 0xe8, 0x90, 0xf8, 0x1d, 0xd2, 0x3b, 0x15, 0x63, 0xce, 0x06, 0x63, 0xf2, 0x16, 0x15, 0x81, 0xdd, 0x62, + 0xcb, 0xb7, 0x6c, 0xbe, 0xd5, 0xca, 0xb6, 0x83, 0xa7, 0xcc, 0xb7, 0xee, 0xdc, 0x42, 0xb2, 0x3b, 0xe0, 0xb3, 0x9d, 0xb7, 0x7b, 0xbf, 0xfa, 0xc9, 0x96, 0x1f, 0xdd, 0x0a, 0xff, 0x03, 0x35, 0xcb, 0x1f, 0x5b, 0xdf, 0xa4, 0xbe, 0x85, 0x1a, 0x56, 0xda, 0xd4, 0xf2, 0x24, 0xd9, 0x08, 0xfb, 0xdf, 0x33, 0xe7, 0xbf, 0x4a, 0x52, 0x03, 0xad, 0xec, 0x47, 0xb2, 0x24, 0xb9, 0x3a, 0x55, 0xb5, 0xf8, 0xc1, 0xe7, 0xd9, 0x36, 0x4a, 0xb8, 0x78, 0x64, 0x09, 0x98, 0x1e, 0xe8, 0xb8, 0xb6, 0x95, 0x8f, 0x2f, 0x6a, 0x60, 0xaa, 0xb0, 0x5d, 0xa1, 0x82, 0xa8, 0x8b, 0x56, 0x87, 0xfc, 0xa4, 0x80, 0x2c, 0x96, 0xc0, 0x83, 0x08, 0xc5, 0x24, 0x1a, 0xa5, 0x36, 0xa6, 0x51, 0xaa, 0x74, 0x6a, 0x04, 0xe7, 0x76, 0x16, 0x02, 0x28, 0x58, 0xd2, 0x02, 0x9c, 0x00, 0x68, 0x69, 0xdc, 0xb8, 0x37, 0x44, 0x34, 0x4a, + 0xa5, 0xd2, 0x0a, 0x69, 0x85, 0xbb, 0xd9, 0x69, 0x76, 0x65, 0x8a, 0x50, 0x72, 0x66, 0x2e, 0x2f, 0x97, 0x11, 0x60, 0x93, 0x09, 0xa7, 0xb1, 0x47, 0xf8, 0xcc, 0x7e, 0xbc, 0xca, 0xce, 0xa9, 0xe9, 0x50, 0x99, 0xf0, 0x52, 0xfc, 0x61, 0x53, 0xe3, 0xa3, 0x47, 0x81, 0xa5, 0xc7, 0xd6, 0x45, 0xb3, 0xfb, 0x76, 0xb6, 0x93, 0x4f, 0x2c, 0xfb, 0xe7, 0xcd, 0xe9, 0x3e, 0x15, 0xd2, 0xc0, 0x8d, 0xdb, 0x7e, 0x7a, 0x73, 0xd3, 0xdc, 0xe7, 0x00, 0x38, 0x2c, 0x47, 0x2a, 0xbb, 0x76, 0xdf, 0xbb, 0xd3, 0x91, 0x23, 0xaf, 0x29, 0x5c, 0xd3, 0x5b, 0xb2, 0x54, 0x91, 0xa2, 0x0a, 0xb6, 0x35, 0xd6, 0x38, 0x1f, 0xd2, 0x27, 0xd1, 0x5f, 0x8a, 0xda, 0xf7, 0x9f, 0xb9, 0x66, 0xc9, 0xd3, 0x37, 0xcc, 0x36, 0x91, 0xc2, 0xd1, 0xff, 0x6a, 0x6e, 0xa2, 0x1e, 0xd3, 0xd9, 0xfe, 0x96, 0xbc, 0xf5, 0x85, 0xbf, 0xec, + 0xfe, 0x1e, 0x10, 0x9d, 0x1e, 0x16, 0xdc, 0xa8, 0xb3, 0x9d, 0x96, 0xce, 0xa9, 0x43, 0x8e, 0xbd, 0x72, 0x87, 0xcd, 0xcc, 0x9c, 0x7b, 0x02, 0x00, 0xa9, 0x2e, 0x45, 0x03, 0x32, 0x78, 0xbf, 0x5e, 0x8a, 0x18, 0x84, 0x3c, 0x67, 0x98, 0x97, 0x6f, 0xfc, 0x48, 0xd7, 0x12, 0x03, 0x52, 0x06, 0xea, 0xe5, 0x50, 0xae, 0x51, 0x2a, 0x24, 0x14, 0xd2, 0xf7, 0xe6, 0x21, 0x36, 0x84, 0xd8, 0x11, 0x03, 0xe8, 0xb9, 0x84, 0x4c, 0x36, 0x80, 0xa1, 0xfa, 0xf3, 0x19, 0xc8, 0x12, 0xe5, 0x7e, 0xf9, 0x98, 0x5a, 0x61, 0x53, 0x3b, 0xb5, 0x76, 0x35, 0x92, 0x35, 0x54, 0x58, 0xd7, 0x12, 0xda, 0x41, 0x4c, 0xa2, 0x00, 0x46, 0xb8, 0xdc, 0x5a, 0x4c, 0x4e, 0xec, 0xf8, 0x1e, 0x19, 0xd9, 0xe2, 0x50, 0xd2, 0x39, 0x6d, 0x48, 0x6b, 0xd7, 0x53, 0xdb, 0xd8, 0x77, 0x0a, 0xc1, 0x11, 0xf6, 0xcc, 0x2f, 0xa6, 0x89, + 0xf4, 0x12, 0xa9, 0x4e, 0x04, 0x6e, 0x2c, 0xfd, 0xd3, 0x73, 0xf7, 0x3d, 0x9c, 0xec, 0x16, 0x6e, 0x1b, 0x3d, 0xbf, 0x4d, 0xe0, 0xb5, 0x3c, 0x7c, 0xdf, 0x93, 0xbf, 0x2d, 0x85, 0x42, 0xc4, 0x8c, 0xb3, 0xbf, 0x3a, 0x0b, 0x7a, 0xc8, 0xcf, 0x1e, 0x07, 0x05, 0x6f, 0x25, 0xd7, 0xf8, 0xfd, 0xb5, 0x56, 0xf6, 0xd8, 0xe3, 0x7b, 0xd9, 0x06, 0x8b, 0x11, 0xec, 0x60, 0xaf, 0xd5, 0xa5, 0x80, 0x93, 0x7b, 0x1f, 0xdf, 0x47, 0x3a, 0x46, 0x3f, 0xdc, 0x87, 0x78, 0x55, 0xef, 0x85, 0xcf, 0xe9, 0xb9, 0xcc, 0xe3, 0x44, 0x0b, 0xd1, 0x12, 0x6d, 0xd4, 0x40, 0xe5, 0xbd, 0xa5, 0xb9, 0xa1, 0xbe, 0xae, 0x16, 0xf5, 0x16, 0xf9, 0xe8, 0x01, 0x25, 0x0d, 0x0a, 0xa0, 0x1a, 0x4b, 0xd5, 0x13, 0xf8, 0xb6, 0x19, 0xe1, 0x72, 0x28, 0xf8, 0xff, 0xf2, 0x58, 0x48, 0x3e, 0xac, 0x4e, 0xe2, 0x5b, 0xc7, 0x21, 0xa6, + 0xb1, 0xac, 0x24, 0x2f, 0x37, 0x3b, 0xcb, 0xed, 0xf4, 0x09, 0x50, 0x9a, 0x00, 0x66, 0x2c, 0x16, 0xeb, 0x84, 0xb8, 0x01, 0x46, 0x21, 0x27, 0x42, 0xb9, 0xc7, 0xd2, 0xb5, 0x8d, 0x85, 0x2a, 0xe4, 0x9c, 0x70, 0xbd, 0x11, 0x58, 0x3d, 0xe2, 0x7e, 0x8c, 0xf4, 0x65, 0x44, 0x52, 0x4b, 0x7a, 0x8b, 0xa7, 0xcd, 0xae, 0xca, 0xd2, 0xa0, 0x88, 0x02, 0x0d, 0x6f, 0xad, 0x1f, 0x7a, 0x6c, 0x55, 0x71, 0xc0, 0x7d, 0xd4, 0xfc, 0xa5, 0xb3, 0xd9, 0x07, 0x46, 0xcc, 0xac, 0x24, 0xad, 0x36, 0x37, 0xc5, 0x5d, 0x35, 0x50, 0x3a, 0x63, 0xe5, 0x8c, 0x88, 0x41, 0x69, 0x16, 0x95, 0xa4, 0xcd, 0x5f, 0xbd, 0xbd, 0x6a, 0xf5, 0x89, 0x4d, 0xe5, 0x46, 0x7f, 0xa1, 0xe3, 0x4d, 0x0b, 0x08, 0xf9, 0xdb, 0x7d, 0x6c, 0x94, 0x16, 0xec, 0x72, 0xf6, 0x94, 0xfa, 0xaa, 0xf3, 0xe2, 0x51, 0x07, 0x4a, 0x16, 0x5d, 0xdf, + 0xd0, 0x3c, 0x12, 0x48, 0x1b, 0x2c, 0x34, 0x7a, 0x35, 0x3d, 0x83, 0xeb, 0xf4, 0x59, 0xc5, 0x8d, 0xc1, 0x9c, 0xda, 0x2c, 0x63, 0x20, 0x2f, 0xa0, 0x92, 0x0d, 0xa6, 0x57, 0x65, 0x5b, 0x50, 0x82, 0xa7, 0x50, 0x47, 0x4d, 0x61, 0x52, 0x70, 0x61, 0xbd, 0xce, 0xad, 0x5d, 0xc6, 0x9f, 0x11, 0x25, 0x3e, 0x23, 0x25, 0x08, 0x8f, 0x6a, 0x83, 0xf3, 0xe3, 0x03, 0x04, 0x95, 0x0d, 0xf5, 0x4d, 0x09, 0x94, 0x26, 0x54, 0x80, 0x04, 0x0c, 0x8a, 0x48, 0x41, 0x8f, 0x40, 0x35, 0x4c, 0x48, 0x20, 0xf3, 0x8b, 0x50, 0x00, 0xff, 0x5f, 0x8e, 0x12, 0x61, 0xae, 0x8a, 0x59, 0xbc, 0x96, 0x72, 0x2c, 0x0b, 0x12, 0xda, 0x21, 0xd0, 0xe8, 0x81, 0x1a, 0xa5, 0xd9, 0x95, 0xac, 0x12, 0x21, 0x63, 0x00, 0x7f, 0x58, 0xdc, 0x68, 0x13, 0x5c, 0xfc, 0xa8, 0x70, 0x16, 0xad, 0x31, 0x47, 0x64, 0x8a, 0x5c, 0xfc, 0xf8, + 0xc6, 0xca, 0xf0, 0x9c, 0xeb, 0xa6, 0x93, 0x6b, 0xbb, 0x47, 0x9f, 0x11, 0xa3, 0x73, 0x52, 0x6b, 0xd8, 0xfa, 0x93, 0x5b, 0x9a, 0x07, 0x5f, 0x00, 0xe0, 0x1e, 0x39, 0xfa, 0xaa, 0x5d, 0xf9, 0x5c, 0x4d, 0xb5, 0x10, 0xaa, 0xb3, 0xfe, 0x92, 0xee, 0xd2, 0xc5, 0x4c, 0xe9, 0x40, 0x5b, 0xbd, 0x0b, 0xdb, 0xae, 0xe8, 0x7f, 0xd0, 0x2d, 0xfb, 0x7e, 0xb4, 0x79, 0xf9, 0x89, 0xdd, 0x33, 0xb5, 0xba, 0x45, 0x23, 0x47, 0x74, 0xb6, 0x4f, 0xf4, 0x9b, 0x5e, 0xfe, 0xd7, 0xcd, 0x87, 0x81, 0xfc, 0xf5, 0x65, 0x60, 0xb7, 0xce, 0xf6, 0x03, 0x55, 0x6f, 0xe5, 0x80, 0x54, 0x61, 0xf5, 0x78, 0x25, 0xe7, 0x5e, 0x52, 0xa6, 0x64, 0xd9, 0xb1, 0x9d, 0x8a, 0x26, 0x74, 0x17, 0xbe, 0x14, 0x64, 0xc3, 0xb3, 0x21, 0x86, 0xf4, 0xa2, 0x81, 0xe8, 0x21, 0xbe, 0x88, 0x2a, 0x6a, 0xa0, 0x88, 0x5e, 0x01, 0x28, 0xb1, + 0x1b, 0xbb, 0x7d, 0x70, 0xd7, 0x2b, 0x45, 0x52, 0x00, 0x55, 0x70, 0xa1, 0x18, 0x65, 0x63, 0x84, 0x14, 0x85, 0x11, 0xa0, 0x58, 0xbf, 0x14, 0x29, 0xa4, 0x90, 0x5d, 0x42, 0xd4, 0x23, 0x07, 0x48, 0xe2, 0xc7, 0xc0, 0x12, 0x19, 0xc7, 0xe2, 0xd1, 0xd4, 0xd0, 0x88, 0x9b, 0xc7, 0x03, 0x0a, 0x17, 0x4c, 0x6a, 0x02, 0xd6, 0x22, 0x24, 0x42, 0x91, 0x64, 0x5e, 0xbc, 0xad, 0x8b, 0x34, 0x10, 0x2d, 0xb9, 0xba, 0xba, 0xbc, 0x3c, 0x81, 0xad, 0x24, 0x2b, 0x70, 0xa8, 0xbc, 0xa4, 0x69, 0xd3, 0x7a, 0xba, 0xbb, 0x3a, 0x5a, 0x67, 0x34, 0x37, 0x4e, 0x6b, 0x98, 0xd6, 0x90, 0xe5, 0xf4, 0x66, 0xb8, 0x34, 0x70, 0x67, 0x4b, 0xc6, 0xfc, 0xed, 0x20, 0xd9, 0xbe, 0xb4, 0x72, 0x87, 0xae, 0x31, 0x04, 0x31, 0xdd, 0x0e, 0x70, 0xa7, 0x5e, 0x09, 0x00, 0x3e, 0x1b, 0x76, 0xbc, 0xce, 0x20, 0xc4, 0x34, 0x49, + 0xac, 0xe6, 0x7d, 0xe7, 0x7e, 0x27, 0x4d, 0xd1, 0xdf, 0x4c, 0xff, 0x58, 0x69, 0x90, 0xa5, 0xa6, 0xe5, 0x39, 0x4a, 0xca, 0x0a, 0x3a, 0xe7, 0x77, 0x16, 0x78, 0xeb, 0x87, 0xaa, 0xbc, 0xcd, 0x50, 0xbd, 0x0b, 0x0a, 0x74, 0x32, 0xa5, 0x33, 0xab, 0xd4, 0x3f, 0xad, 0xcc, 0x1e, 0xae, 0xaa, 0xad, 0x0a, 0xdb, 0x5d, 0x35, 0x83, 0x15, 0xf5, 0x9b, 0x03, 0xe7, 0x7e, 0xd4, 0x22, 0x54, 0x8a, 0x84, 0x4a, 0x61, 0x0b, 0xf8, 0xe5, 0x6a, 0x9d, 0x49, 0x90, 0xcd, 0xfe, 0x77, 0x36, 0x6d, 0xd6, 0xac, 0x05, 0x29, 0x77, 0x33, 0x9b, 0xe4, 0xda, 0x6f, 0x77, 0x28, 0xd4, 0x64, 0x9d, 0x41, 0xa9, 0x51, 0x66, 0xb8, 0x6d, 0x2e, 0xb3, 0x39, 0xb3, 0xa1, 0x24, 0xa7, 0xbb, 0xdc, 0xa3, 0x48, 0x72, 0xe9, 0xeb, 0x45, 0x32, 0x5d, 0x92, 0x2e, 0x94, 0x6e, 0x71, 0x18, 0x55, 0x3a, 0x6b, 0x56, 0x55, 0x28, 0xad, + 0xa9, 0xd8, 0xed, 0x77, 0x81, 0xca, 0x5f, 0x6b, 0x82, 0x56, 0x6b, 0x50, 0xf3, 0x6b, 0xf6, 0xff, 0x74, 0x8a, 0xdd, 0xbb, 0xe5, 0x7a, 0xf2, 0x37, 0xdc, 0xfd, 0xde, 0xe3, 0x70, 0xf1, 0x37, 0x51, 0xff, 0x05, 0x37, 0x78, 0x11, 0x67, 0x9c, 0x57, 0xc3, 0x59, 0x1b, 0x6c, 0x88, 0x65, 0x0e, 0x47, 0x32, 0x2d, 0xca, 0xfc, 0xca, 0x27, 0x15, 0x8f, 0x3f, 0xeb, 0x89, 0x4a, 0x13, 0x33, 0x8a, 0xbb, 0x71, 0x46, 0x71, 0x7a, 0xd3, 0xf9, 0xe0, 0xd6, 0x6d, 0xdb, 0xa8, 0xff, 0x3a, 0xff, 0x34, 0x6e, 0x7b, 0x19, 0x6c, 0xbb, 0x84, 0x6e, 0x47, 0x81, 0x5f, 0xa3, 0x86, 0x58, 0x26, 0x6a, 0x8a, 0x5b, 0x4b, 0x28, 0x16, 0xbb, 0x50, 0x1a, 0x71, 0x6d, 0x4c, 0xc6, 0xe0, 0xe2, 0x94, 0x60, 0x93, 0xa0, 0xda, 0xb9, 0x0c, 0x81, 0xd9, 0x10, 0xa8, 0x6d, 0x6b, 0x4e, 0xff, 0xae, 0xb6, 0xb6, 0x5d, 0xfd, 0x39, + 0x5b, 0xc9, 0xcd, 0xb3, 0x8f, 0x6e, 0x6b, 0x6c, 0xdc, 0x76, 0x74, 0xf6, 0xe8, 0x76, 0x72, 0x73, 0xd3, 0xf6, 0x59, 0x79, 0x79, 0xb3, 0xb6, 0x37, 0x8d, 0x6e, 0x87, 0xd4, 0xfc, 0x36, 0xb8, 0x3f, 0xbf, 0xa2, 0x2b, 0xe0, 0x27, 0x21, 0x21, 0x25, 0x02, 0xd1, 0x34, 0xde, 0xe0, 0x89, 0x7c, 0xcb, 0x20, 0xaf, 0x07, 0x73, 0x09, 0x9a, 0x61, 0xe8, 0x0e, 0x3e, 0x08, 0x1c, 0xcd, 0x34, 0x71, 0x0e, 0x66, 0x02, 0xc8, 0xd0, 0xb4, 0xf6, 0xb1, 0x6b, 0x80, 0xdb, 0xa8, 0x79, 0xe7, 0x0f, 0xc5, 0xfe, 0x90, 0x7f, 0x3d, 0x00, 0xfe, 0x74, 0x60, 0xf4, 0x2f, 0x07, 0x60, 0x5b, 0xd7, 0xc0, 0x91, 0x6e, 0x61, 0x0e, 0xc1, 0x53, 0x22, 0x41, 0x19, 0x55, 0xc4, 0x22, 0xa8, 0x44, 0x11, 0x02, 0xa2, 0x16, 0x35, 0x8f, 0xd0, 0x09, 0x70, 0x38, 0x1a, 0x2d, 0x09, 0xa7, 0x41, 0x4c, 0xaa, 0xed, 0x5a, 0x35, 0xd4, + 0x8c, 0x22, 0xf4, 0x67, 0xac, 0x82, 0x55, 0x50, 0x9f, 0xdc, 0xfa, 0xed, 0xd7, 0xe4, 0x0e, 0x40, 0xad, 0x07, 0x4b, 0xd9, 0x3b, 0x46, 0x93, 0xa8, 0xbc, 0xf3, 0xf9, 0xd4, 0x8f, 0xc0, 0x7b, 0x80, 0x9b, 0xfb, 0xbb, 0x40, 0x1f, 0x75, 0x8e, 0xfc, 0x3f, 0x38, 0x8d, 0xa5, 0x9c, 0x61, 0xdf, 0x80, 0xe4, 0x33, 0xb0, 0x10, 0x43, 0xb0, 0x3a, 0xd0, 0x46, 0xee, 0x41, 0xac, 0xbc, 0xc9, 0x12, 0xd5, 0x63, 0x36, 0x3d, 0xf1, 0x79, 0xcf, 0x49, 0x38, 0x0a, 0x94, 0xc9, 0x05, 0xd8, 0xf5, 0x08, 0x95, 0x77, 0x6e, 0x34, 0x65, 0x2b, 0x35, 0x8f, 0xec, 0xf8, 0x06, 0xa0, 0x84, 0xee, 0x00, 0x44, 0xd8, 0x28, 0x55, 0x2a, 0xe8, 0x24, 0xf4, 0x44, 0xc9, 0x49, 0x64, 0xfa, 0x45, 0x77, 0xb7, 0x99, 0xd8, 0x2d, 0x8e, 0xb3, 0x6d, 0xa2, 0xb3, 0xb2, 0x04, 0xc9, 0x85, 0x7a, 0x9e, 0xa0, 0xc1, 0x32, 0x5d, 0xbc, + 0xd4, 0x48, 0x80, 0x96, 0x9e, 0x93, 0x7a, 0xaf, 0x0b, 0x37, 0x1f, 0xbf, 0x18, 0xa1, 0xf8, 0xf5, 0x01, 0x91, 0xe2, 0xc1, 0x1b, 0x9a, 0x9c, 0x4d, 0x36, 0x19, 0x25, 0xd6, 0xda, 0x92, 0x0a, 0xbb, 0x8a, 0xac, 0x2f, 0x51, 0x7f, 0x3d, 0xf5, 0xf9, 0xee, 0x5c, 0xb9, 0xba, 0x4d, 0xa6, 0x14, 0x52, 0xbe, 0x85, 0xcf, 0xde, 0xf8, 0x6d, 0x23, 0x01, 0x2e, 0x7c, 0xca, 0x2e, 0x06, 0x5f, 0x30, 0xef, 0x11, 0x46, 0xa2, 0xf0, 0xa4, 0x84, 0xc7, 0x34, 0x07, 0xb0, 0x74, 0x4a, 0x92, 0x0b, 0x70, 0x9a, 0x7b, 0x24, 0x6a, 0x2c, 0x41, 0xbb, 0x4c, 0x85, 0xf3, 0x0b, 0x27, 0x3c, 0x42, 0x1d, 0xf0, 0xa0, 0x0e, 0xb8, 0x75, 0x5c, 0x50, 0xc1, 0x48, 0x29, 0x17, 0x95, 0x4f, 0x1d, 0x02, 0x5f, 0x58, 0x72, 0x2a, 0xdb, 0x67, 0x87, 0x9c, 0x8d, 0x36, 0x29, 0xdf, 0x85, 0xc2, 0x94, 0x97, 0xde, 0xbd, 0xf6, 0x95, + 0xdd, 0x33, 0xc3, 0x86, 0x58, 0x1f, 0x16, 0x3d, 0x7b, 0x23, 0x23, 0x86, 0x7d, 0xf8, 0x09, 0x69, 0x12, 0x28, 0xc8, 0x83, 0x70, 0x4f, 0x98, 0xf0, 0x4c, 0x12, 0xa0, 0x03, 0xbd, 0xa5, 0x07, 0xeb, 0x78, 0x6a, 0x15, 0x5a, 0x39, 0x14, 0x8b, 0x4b, 0xa0, 0xf8, 0xe6, 0xb4, 0x20, 0x4a, 0x9a, 0xb6, 0xa2, 0xd9, 0x79, 0x92, 0x95, 0x51, 0x99, 0x82, 0x87, 0x60, 0x1d, 0x63, 0x54, 0x87, 0x40, 0xd5, 0x60, 0x04, 0x76, 0x6a, 0x31, 0x96, 0x23, 0x28, 0x35, 0x16, 0x99, 0x91, 0xf3, 0x74, 0xe6, 0x56, 0xc1, 0x43, 0xff, 0x5a, 0x24, 0x3a, 0x48, 0xf0, 0x75, 0xf0, 0x7b, 0x18, 0x22, 0x25, 0x6a, 0x99, 0x72, 0xc5, 0xe2, 0xeb, 0x85, 0x50, 0x94, 0x02, 0xc5, 0xa8, 0x6d, 0x2b, 0x35, 0x87, 0x34, 0x9d, 0xfb, 0x07, 0xbd, 0x17, 0xc5, 0x81, 0xf8, 0x09, 0x7a, 0x67, 0xbc, 0x3e, 0x6c, 0x00, 0xd6, 0x87, 0x02, + 0x36, 0xaa, 0x8f, 0xf6, 0x19, 0x09, 0xe2, 0xf5, 0xb1, 0x03, 0x19, 0x7c, 0xf9, 0xa8, 0x8d, 0x9a, 0xc3, 0xca, 0x68, 0xd9, 0x56, 0xee, 0xfd, 0x68, 0x9c, 0xb0, 0xcf, 0x0c, 0xc6, 0x74, 0xf2, 0x47, 0x16, 0x75, 0x20, 0x76, 0x72, 0x49, 0xa2, 0x39, 0xf1, 0xd4, 0x02, 0x7c, 0x6a, 0x05, 0x8a, 0xf3, 0xf7, 0x6f, 0x25, 0x0f, 0x6e, 0xa5, 0xf7, 0x9e, 0xfb, 0x07, 0xde, 0x97, 0x68, 0xbe, 0xf0, 0xd8, 0x19, 0xc2, 0x15, 0xb5, 0xc3, 0xba, 0x78, 0x55, 0x62, 0xb3, 0x36, 0x13, 0x7b, 0x22, 0x8d, 0x6b, 0x06, 0xfb, 0xa7, 0x52, 0x99, 0xe7, 0xef, 0x27, 0x7f, 0x2b, 0x78, 0xe8, 0xdc, 0xea, 0xad, 0xb4, 0x8c, 0xdb, 0xdf, 0x0f, 0xb2, 0xcb, 0x84, 0x67, 0x21, 0x4f, 0x69, 0x23, 0xee, 0x7e, 0xbe, 0x35, 0xe8, 0xa1, 0x04, 0x4c, 0x0c, 0xd1, 0xee, 0x92, 0x40, 0x32, 0xce, 0x30, 0xe9, 0x0d, 0x22, 0x80, 0xa2, + 0x52, 0x20, 0x80, 0x61, 0x6e, 0x1c, 0x8f, 0x18, 0x44, 0xdb, 0x22, 0x27, 0x9e, 0x13, 0xc0, 0x81, 0x26, 0x11, 0x8e, 0x63, 0x60, 0xca, 0x3a, 0x5c, 0x49, 0x34, 0x5c, 0x7e, 0xba, 0x2f, 0x52, 0x12, 0x5f, 0x54, 0x68, 0x60, 0xeb, 0x6d, 0x44, 0x9b, 0x1b, 0x12, 0x1f, 0x5f, 0xda, 0x7a, 0xdd, 0x1a, 0x29, 0x4a, 0xf1, 0xa9, 0xe6, 0x09, 0x3c, 0x17, 0x6a, 0x38, 0xe1, 0x8e, 0x9f, 0xcb, 0x3e, 0xcb, 0xbb, 0xcd, 0xf0, 0x22, 0x4d, 0x02, 0xe2, 0x22, 0x1e, 0x4e, 0x27, 0xa2, 0x16, 0x9e, 0xcd, 0xdf, 0xf7, 0xf9, 0xf3, 0xbe, 0x6b, 0xf7, 0xee, 0x09, 0x37, 0xcf, 0xce, 0xd5, 0x80, 0x7c, 0x95, 0x46, 0x4c, 0x99, 0x9b, 0x6f, 0x5a, 0x93, 0xb9, 0xfd, 0xfa, 0x0d, 0xde, 0xf5, 0x47, 0x06, 0x33, 0x6c, 0x2d, 0xb3, 0x57, 0x56, 0x54, 0xaf, 0xea, 0x9d, 0x66, 0xb0, 0xcd, 0x5a, 0xb1, 0xa9, 0x64, 0xd5, 0xe3, + 0xab, 0x0a, 0xde, 0x4f, 0x6b, 0x5c, 0x5c, 0x5e, 0x3e, 0xdc, 0x9a, 0xf9, 0xbe, 0xbd, 0x62, 0x41, 0xd5, 0xa2, 0x47, 0x43, 0xcc, 0xbd, 0xdf, 0x9e, 0x59, 0x70, 0xcf, 0xe2, 0x08, 0x94, 0xd2, 0xc4, 0x7a, 0xa7, 0x45, 0xaa, 0x31, 0xca, 0xb2, 0x6a, 0xb3, 0x93, 0xe0, 0x57, 0x7d, 0xa8, 0xa3, 0xec, 0x99, 0xa2, 0xd9, 0xd3, 0x5c, 0xee, 0xca, 0xb9, 0x25, 0x55, 0xf3, 0xa3, 0x29, 0x35, 0x37, 0xbe, 0xb9, 0x8d, 0xba, 0xf9, 0xfc, 0x70, 0xdf, 0xde, 0x59, 0xc1, 0xac, 0x59, 0x7b, 0xfb, 0xd1, 0xe7, 0xce, 0x3d, 0x73, 0x73, 0x0f, 0xdc, 0x4a, 0xfd, 0x96, 0x9b, 0xfb, 0x42, 0x48, 0x7b, 0x47, 0x98, 0x87, 0x09, 0x27, 0x50, 0x45, 0x95, 0x5a, 0x0d, 0x54, 0x74, 0x90, 0x1b, 0x27, 0x24, 0xbe, 0x74, 0x0c, 0x62, 0x68, 0xe6, 0x7d, 0xb4, 0x90, 0x41, 0x99, 0xec, 0x15, 0x30, 0x24, 0x4e, 0x8f, 0x8e, 0x33, + 0xbf, 0x5b, 0x38, 0x72, 0x34, 0x45, 0x89, 0xd0, 0x58, 0x09, 0xd9, 0x65, 0xdb, 0x50, 0x5f, 0xb6, 0x0d, 0xed, 0x65, 0xdb, 0xd0, 0x5f, 0xa6, 0x8d, 0x4b, 0x55, 0xef, 0x19, 0x03, 0x73, 0x38, 0x09, 0xa7, 0xd1, 0xeb, 0xd3, 0xaa, 0x85, 0xc2, 0xa4, 0xb1, 0x58, 0xe8, 0x3c, 0x59, 0xe3, 0x38, 0x3c, 0xbe, 0x8a, 0xc2, 0xa1, 0x61, 0x47, 0xde, 0xdb, 0xf0, 0xdb, 0xd3, 0xf7, 0xad, 0x28, 0x49, 0x9b, 0xb9, 0x6f, 0xc0, 0x92, 0xae, 0x45, 0xeb, 0x10, 0xae, 0xf1, 0xa9, 0x1a, 0xbe, 0xf7, 0xaf, 0x63, 0x5c, 0x30, 0x72, 0x9b, 0x5d, 0x14, 0x5e, 0x7e, 0x74, 0xfd, 0xdc, 0xc3, 0xab, 0xa2, 0x34, 0x3d, 0x4b, 0xad, 0x97, 0xd2, 0x59, 0xcd, 0x83, 0xe1, 0xe5, 0x37, 0x16, 0xab, 0x33, 0xa0, 0x06, 0x8a, 0xe2, 0x5f, 0x2e, 0xc3, 0xf1, 0xc8, 0x83, 0xc4, 0xc1, 0xe7, 0x02, 0x04, 0x29, 0x88, 0xfb, 0xf3, 0xb8, + 0x08, 0x28, 0x3d, 0x0a, 0x09, 0x24, 0x34, 0x09, 0xd3, 0x91, 0x64, 0x12, 0xc0, 0x48, 0x67, 0xce, 0xb7, 0x31, 0x88, 0x24, 0xf0, 0x1c, 0x2a, 0xe6, 0xcb, 0xe3, 0x88, 0x97, 0xa5, 0x00, 0xb5, 0x8a, 0x41, 0x5e, 0x75, 0x08, 0x49, 0x3d, 0x3f, 0x5e, 0x95, 0x8b, 0x11, 0x91, 0x58, 0x28, 0x16, 0x82, 0x1d, 0x39, 0x98, 0x53, 0xcc, 0xb8, 0xa2, 0x38, 0xd5, 0x91, 0xc5, 0x9e, 0xa6, 0x81, 0xff, 0xa3, 0x9c, 0x7b, 0x7c, 0x08, 0x74, 0x0e, 0x21, 0x80, 0x2e, 0xeb, 0x12, 0x2e, 0xc0, 0xb9, 0xdd, 0x3d, 0x29, 0x36, 0x7a, 0xde, 0xca, 0xa7, 0x37, 0x46, 0x07, 0x73, 0x1b, 0x4a, 0xc8, 0x08, 0xda, 0xde, 0x86, 0xbc, 0x9e, 0xca, 0xbc, 0xc1, 0xe2, 0xc6, 0xc1, 0xe2, 0xa4, 0xc8, 0xf5, 0x9f, 0x3c, 0x99, 0x18, 0x34, 0x7d, 0x55, 0xdf, 0x2d, 0xf3, 0xf3, 0x24, 0xfe, 0xb0, 0x54, 0x6d, 0x90, 0xa5, 0xd7, 0x86, + 0xac, 0x12, 0x63, 0xb8, 0x3b, 0x0a, 0xf6, 0xf7, 0x5e, 0x93, 0xa7, 0xce, 0xe0, 0x02, 0xa9, 0x03, 0x62, 0x0e, 0xd4, 0x57, 0xe4, 0x50, 0x5f, 0xa9, 0x22, 0x32, 0xa3, 0xe9, 0x0e, 0x9c, 0x4f, 0x7c, 0xcc, 0x77, 0x90, 0xd3, 0x3f, 0x43, 0xf1, 0x20, 0x45, 0xf0, 0xf4, 0x66, 0xf9, 0xb4, 0x59, 0x5e, 0x06, 0xae, 0x9e, 0x36, 0xc1, 0x39, 0x58, 0xa7, 0xc0, 0xd1, 0xbe, 0x27, 0xa9, 0x1d, 0x02, 0xec, 0x6d, 0xcc, 0x5d, 0x2e, 0x22, 0x3d, 0xe5, 0xe3, 0x8c, 0x60, 0x70, 0xfe, 0xdd, 0x4b, 0x07, 0xee, 0x5a, 0x94, 0x17, 0xe9, 0x5d, 0xbd, 0x6e, 0x75, 0x6f, 0x64, 0xff, 0xb5, 0x50, 0xc7, 0x08, 0x23, 0x1d, 0xa3, 0x34, 0x6d, 0xfe, 0x9a, 0x6d, 0x58, 0xc7, 0xc8, 0xef, 0x5d, 0xb3, 0x71, 0x4d, 0x6f, 0xfe, 0xf2, 0x05, 0xd9, 0x8d, 0x21, 0x8b, 0x42, 0xa5, 0x50, 0xaa, 0xa9, 0xe5, 0xd9, 0xc3, 0xb5, 0xd1, + 0x0d, 0x73, 0x8a, 0x72, 0xfb, 0xb7, 0x35, 0xf6, 0xef, 0x9c, 0x5b, 0x59, 0x10, 0xae, 0xe8, 0x2e, 0x5d, 0xb6, 0x6a, 0x4c, 0xab, 0x28, 0x1d, 0xbe, 0x77, 0x56, 0xfb, 0xfa, 0xee, 0x68, 0x38, 0x5c, 0xd5, 0x5b, 0xde, 0x3a, 0x27, 0x39, 0xb3, 0xc8, 0x66, 0xf6, 0x5a, 0xf5, 0x62, 0xb5, 0x41, 0xc2, 0xe5, 0x22, 0xfe, 0x04, 0x12, 0xca, 0x25, 0x70, 0x9c, 0x24, 0x61, 0x47, 0xb7, 0x8a, 0x76, 0x2d, 0x5c, 0x49, 0x14, 0xd4, 0x87, 0x41, 0x2e, 0xa3, 0x98, 0x34, 0x85, 0x1a, 0x50, 0x30, 0x08, 0x2c, 0x48, 0x04, 0x90, 0x43, 0x4a, 0x4a, 0xb2, 0xd9, 0xa4, 0x90, 0x49, 0x44, 0x50, 0x3f, 0x23, 0x09, 0x52, 0x88, 0xf2, 0xd9, 0x8a, 0x01, 0x63, 0x47, 0x82, 0x26, 0x0a, 0x45, 0x8b, 0x06, 0x9e, 0x0a, 0x30, 0x27, 0xb4, 0x72, 0x37, 0xe9, 0x74, 0x2e, 0xbb, 0x9a, 0x5d, 0x45, 0xb7, 0x7c, 0xbb, 0x81, 0xac, + 0x35, 0x94, 0xdc, 0x39, 0xbd, 0x6a, 0x75, 0x68, 0xc1, 0xec, 0x2e, 0x97, 0x4e, 0xe7, 0xea, 0x9a, 0xbd, 0x20, 0xb4, 0xba, 0x6a, 0xfa, 0x1d, 0x25, 0x86, 0x63, 0xe4, 0x4f, 0x46, 0x73, 0x8e, 0xd1, 0x4a, 0xef, 0xd2, 0xde, 0xeb, 0xd3, 0x4c, 0xe1, 0xee, 0x32, 0x97, 0xab, 0xac, 0x3b, 0x6c, 0x4a, 0xbb, 0xbe, 0x77, 0xa9, 0x0f, 0xdb, 0x0b, 0x16, 0x5c, 0xf8, 0x9c, 0x69, 0x64, 0xbe, 0xc2, 0x36, 0xa5, 0x75, 0x27, 0x7d, 0x3c, 0xa7, 0x46, 0x02, 0xa1, 0x07, 0x52, 0x51, 0x46, 0x80, 0xb2, 0x90, 0x62, 0xf5, 0x30, 0x3d, 0xbe, 0x2a, 0x61, 0xa8, 0x1b, 0xf3, 0x40, 0xdf, 0x5c, 0x84, 0xd0, 0x70, 0xf1, 0x05, 0xb9, 0x1c, 0xb6, 0x03, 0x63, 0x15, 0x12, 0x8b, 0x41, 0x11, 0x5c, 0xa7, 0x03, 0x84, 0xae, 0x41, 0xd7, 0x50, 0x13, 0x0d, 0xe7, 0x06, 0xfc, 0x2e, 0xab, 0x54, 0x4c, 0x68, 0x81, 0x56, 0x2c, + 0x4c, 0xc8, 0x85, 0xc6, 0xaf, 0x5d, 0xa2, 0xdf, 0x03, 0x26, 0xcf, 0x89, 0xee, 0xf1, 0x78, 0xa5, 0xe9, 0x18, 0xa1, 0x8e, 0x90, 0x1f, 0xf6, 0xdf, 0xbe, 0xb8, 0xa0, 0x60, 0xf1, 0xed, 0xfd, 0x08, 0x5a, 0x05, 0xff, 0x25, 0x07, 0xd0, 0x36, 0x35, 0x45, 0x57, 0xf6, 0x38, 0x3b, 0x3b, 0xa7, 0x27, 0xcf, 0xda, 0x3b, 0xb7, 0x40, 0x56, 0xf8, 0xeb, 0x63, 0x7b, 0x3e, 0x39, 0xdc, 0x3d, 0xf3, 0xc1, 0x3f, 0xec, 0xb9, 0xfd, 0xd3, 0x07, 0x3b, 0x35, 0x59, 0x6d, 0xdb, 0x4e, 0x6e, 0x48, 0x6d, 0xeb, 0xe9, 0xf1, 0x44, 0x6a, 0xd3, 0x54, 0xe0, 0xb1, 0xec, 0xd9, 0xbb, 0xbb, 0xba, 0x6e, 0x9c, 0x9b, 0x9b, 0xd1, 0xb1, 0x79, 0xc6, 0x9c, 0x3b, 0x17, 0x85, 0x99, 0x46, 0x99, 0xc6, 0x24, 0xcb, 0x69, 0xca, 0xb3, 0x40, 0x2a, 0xec, 0x9f, 0x75, 0x70, 0xf9, 0xfc, 0xa1, 0x96, 0x9b, 0xce, 0xae, 0xb9, 0xe6, + 0xed, 0x03, 0x6d, 0xb3, 0x9e, 0xb9, 0xf0, 0xc0, 0xd2, 0x1f, 0x1c, 0xdb, 0x33, 0x3f, 0x8c, 0xe9, 0xb5, 0xdb, 0xc2, 0xd1, 0x5d, 0x2f, 0x1c, 0x6a, 0x2d, 0x8e, 0x59, 0xed, 0x8f, 0x7a, 0xf9, 0xa9, 0x18, 0x16, 0xa0, 0x7d, 0x4d, 0x50, 0x5c, 0x68, 0x47, 0x3c, 0x81, 0x01, 0x14, 0xe6, 0x20, 0x4d, 0x67, 0x57, 0xab, 0x50, 0x66, 0x65, 0xee, 0xbe, 0x31, 0x91, 0xff, 0xe0, 0x60, 0x26, 0x76, 0xba, 0x96, 0xed, 0x7c, 0xef, 0x7b, 0xfe, 0x7c, 0x87, 0x12, 0x94, 0xa8, 0x75, 0x12, 0xca, 0x58, 0x38, 0xa7, 0xfe, 0x91, 0xf7, 0xc0, 0x51, 0xea, 0xaf, 0xe7, 0x35, 0xd4, 0x5f, 0xc5, 0x46, 0x4f, 0xb2, 0x4c, 0x67, 0x94, 0x85, 0x9a, 0x72, 0x92, 0x00, 0xc9, 0xb2, 0xf8, 0xfd, 0x5b, 0xd8, 0xe5, 0xcc, 0x67, 0xd4, 0xd7, 0x44, 0x13, 0xf1, 0xe4, 0xf3, 0x8d, 0x99, 0x31, 0x9e, 0xeb, 0x1b, 0xe3, 0xb9, 0x99, + 0x09, 0x5c, 0x91, 0xcb, 0x94, 0xd8, 0x37, 0x9e, 0xe7, 0xfa, 0x71, 0x12, 0x50, 0x8e, 0x93, 0xce, 0x1f, 0x5f, 0x07, 0x27, 0x57, 0x44, 0x1f, 0xb9, 0xd5, 0x8c, 0x73, 0xdf, 0x40, 0x22, 0xf7, 0xbd, 0x74, 0x1d, 0x3a, 0xc6, 0x87, 0x9b, 0x88, 0x26, 0xa7, 0xde, 0xa9, 0xf7, 0x4c, 0xe6, 0xc3, 0x9e, 0x84, 0x79, 0x30, 0xa2, 0xe5, 0x35, 0xc4, 0xf8, 0xb0, 0x27, 0x91, 0x0d, 0x1b, 0xe2, 0x5c, 0x18, 0x07, 0x08, 0x50, 0x33, 0x9f, 0x05, 0x46, 0x7e, 0x78, 0x4f, 0x4a, 0x65, 0x55, 0xa5, 0xb5, 0xb1, 0x3f, 0xa4, 0x05, 0x98, 0x4a, 0x59, 0x5b, 0xf6, 0xac, 0xb0, 0x94, 0x44, 0xcb, 0x2c, 0xfd, 0x5b, 0x5a, 0x1c, 0xc9, 0x25, 0xd5, 0x2d, 0x19, 0x0b, 0x36, 0x5b, 0x8d, 0xd5, 0x9d, 0xf3, 0xb2, 0x67, 0x6c, 0xe9, 0xca, 0x3c, 0x33, 0x7f, 0x96, 0xbf, 0xb1, 0xc0, 0x71, 0x66, 0x76, 0x6f, 0xe9, 0x50, 0x06, + 0xf5, 0xf5, 0xf9, 0xc1, 0xa1, 0x7b, 0x16, 0x04, 0x21, 0x03, 0x01, 0x22, 0xc4, 0x7f, 0x21, 0x61, 0x4f, 0xaf, 0x0b, 0x25, 0x03, 0xf4, 0x40, 0x95, 0xd5, 0x59, 0xdd, 0x6a, 0x8f, 0xf8, 0x0c, 0x83, 0xbd, 0xe1, 0xe6, 0x6c, 0x53, 0x68, 0xe0, 0xa6, 0x3e, 0x14, 0xad, 0x7c, 0xe5, 0x1a, 0x6b, 0x61, 0x67, 0x01, 0xfa, 0xb4, 0x66, 0x65, 0x45, 0x01, 0xf9, 0x25, 0xb7, 0x07, 0xa6, 0xb3, 0x0f, 0x53, 0x6f, 0xd0, 0x02, 0xc2, 0x8d, 0x72, 0x76, 0x23, 0x95, 0x57, 0xa9, 0x51, 0x43, 0x06, 0xec, 0x8a, 0x33, 0x60, 0x4b, 0x54, 0xa3, 0x06, 0x0c, 0xa9, 0x81, 0x54, 0x3a, 0xf6, 0x10, 0x43, 0x3d, 0x1c, 0x53, 0x33, 0xbb, 0x70, 0x3c, 0x41, 0x74, 0x4a, 0xec, 0x47, 0xe4, 0x9e, 0x91, 0xd9, 0x40, 0xf0, 0xa8, 0xb0, 0x58, 0x01, 0x8e, 0xd7, 0xb9, 0x09, 0xb7, 0xcf, 0xeb, 0x71, 0x26, 0xf0, 0xba, 0x98, 0xeb, + 0x14, 0x22, 0x92, 0x63, 0xcc, 0x8e, 0x93, 0x5d, 0xa8, 0x37, 0xce, 0xf6, 0xdd, 0x13, 0xed, 0x84, 0xfb, 0xf8, 0x7b, 0x47, 0x2d, 0x7e, 0x2d, 0x90, 0x6a, 0x8d, 0x32, 0x7f, 0xa9, 0x4f, 0xeb, 0x8c, 0x76, 0xe7, 0x85, 0xb7, 0x36, 0x53, 0x5f, 0x7f, 0xfb, 0x04, 0xe9, 0x37, 0xea, 0xb2, 0x96, 0x1c, 0xdd, 0xf4, 0xce, 0xfb, 0x0c, 0xd5, 0x84, 0xd8, 0x5d, 0xa0, 0x61, 0x41, 0x41, 0xdb, 0xaa, 0x5a, 0xbb, 0x4a, 0x4d, 0x7e, 0x0f, 0xae, 0xba, 0xfa, 0xc2, 0x17, 0xd4, 0x3f, 0x69, 0x09, 0xa1, 0x84, 0x8b, 0xfa, 0x07, 0xee, 0x82, 0x50, 0x59, 0x05, 0x80, 0x48, 0x4b, 0x41, 0x25, 0x23, 0x0f, 0x40, 0xc5, 0x1c, 0x45, 0x4d, 0x82, 0x4f, 0xc4, 0x09, 0x4f, 0x78, 0x64, 0x4b, 0x9a, 0x04, 0xb9, 0x9c, 0x32, 0x84, 0x10, 0x71, 0x2d, 0x8a, 0x64, 0x28, 0x74, 0x7d, 0x2b, 0xea, 0x95, 0x8a, 0x49, 0x91, 0x28, + 0xcc, 0x45, 0x3e, 0x45, 0xde, 0xf5, 0xb9, 0x64, 0xec, 0x46, 0x22, 0x63, 0x52, 0x05, 0x14, 0x8d, 0x2b, 0xb3, 0x01, 0x57, 0x23, 0x26, 0x55, 0x8a, 0x66, 0x8f, 0x2b, 0x4f, 0x4f, 0x55, 0x9e, 0xa6, 0x03, 0x5c, 0x25, 0x02, 0xd7, 0x81, 0x1b, 0x34, 0x55, 0xa5, 0x02, 0x84, 0xaa, 0x49, 0xd5, 0x38, 0xad, 0xbc, 0x20, 0x92, 0x95, 0xe1, 0xf7, 0x79, 0xdc, 0x4e, 0x7b, 0xb2, 0x19, 0x36, 0xa3, 0x04, 0x4a, 0x99, 0x90, 0xbb, 0x48, 0x1f, 0x8b, 0x59, 0x91, 0xe8, 0xec, 0x9a, 0x49, 0x63, 0x76, 0x9a, 0x78, 0x13, 0x3c, 0xc1, 0x8f, 0xa1, 0x14, 0x53, 0x6f, 0xf0, 0xcf, 0xe6, 0xa5, 0x7e, 0xff, 0xaa, 0x86, 0x74, 0x38, 0xd7, 0xda, 0xb4, 0x28, 0x19, 0x50, 0x07, 0x6d, 0x59, 0xa1, 0xac, 0xd9, 0x37, 0xcd, 0xc9, 0x69, 0x2c, 0xcc, 0x4c, 0xf7, 0x9b, 0xfd, 0x95, 0xd2, 0xbb, 0x75, 0xbb, 0xd6, 0xe6, 0xcf, + 0xa8, 0x6b, 0x09, 0x87, 0xea, 0x33, 0x0d, 0xd6, 0x92, 0x43, 0x0b, 0x67, 0x6c, 0x68, 0x4d, 0x4b, 0x6b, 0xbb, 0xb6, 0x7d, 0xf6, 0xc3, 0xd3, 0x18, 0x21, 0x23, 0x10, 0x2b, 0x4c, 0x7a, 0xf2, 0x28, 0x5c, 0xd7, 0xb0, 0x36, 0xc5, 0xa3, 0xd1, 0xb8, 0x53, 0xb4, 0xa0, 0x39, 0x34, 0x94, 0x57, 0x7b, 0x7c, 0xe1, 0xbc, 0xa3, 0x5b, 0xea, 0x74, 0x56, 0xa7, 0x55, 0x17, 0x29, 0xdb, 0x71, 0x9b, 0x3f, 0xc7, 0x9f, 0xd9, 0xbe, 0xba, 0x66, 0x79, 0x4f, 0x5e, 0x79, 0x5a, 0x65, 0x4f, 0x56, 0x56, 0x4f, 0x95, 0xbf, 0x20, 0x2c, 0xd6, 0x4a, 0x14, 0x7a, 0xb9, 0x10, 0xed, 0xd7, 0xca, 0x0b, 0x9f, 0x53, 0x73, 0x68, 0x11, 0x51, 0x81, 0x78, 0xb1, 0x1d, 0xc0, 0xed, 0x38, 0x89, 0x17, 0x87, 0xc7, 0xf1, 0x62, 0x8f, 0xf3, 0xd2, 0xbc, 0x78, 0x4a, 0x56, 0x6c, 0x40, 0x9c, 0xf8, 0x25, 0xa3, 0x35, 0x6d, 0xfa, + 0xba, 0xe9, 0xf5, 0x6b, 0x5b, 0xd3, 0x33, 0xea, 0xfa, 0xe7, 0xf5, 0xd7, 0x65, 0xac, 0x1a, 0xac, 0x9c, 0x5d, 0x15, 0xd4, 0xa7, 0x6a, 0x32, 0x52, 0xda, 0x16, 0xac, 0x2a, 0x9d, 0x05, 0x89, 0x77, 0x66, 0xfd, 0xec, 0xf9, 0xb3, 0xeb, 0x33, 0xdb, 0x2b, 0x9c, 0x11, 0xaf, 0x5e, 0xad, 0x56, 0x90, 0x3b, 0x53, 0x6b, 0xfd, 0xbe, 0xae, 0xea, 0x4c, 0x7f, 0xdd, 0x40, 0x41, 0x69, 0x4f, 0x45, 0xd0, 0xe7, 0x0a, 0x44, 0x3c, 0x15, 0x33, 0x2c, 0xf6, 0x64, 0x6b, 0x97, 0x25, 0xd3, 0xae, 0x09, 0xcc, 0x18, 0xa9, 0x29, 0xea, 0x2c, 0xcb, 0xf4, 0xba, 0x33, 0x8b, 0xd3, 0x0b, 0x2b, 0x34, 0xa9, 0x3e, 0x83, 0x29, 0x55, 0xac, 0x52, 0x0b, 0x38, 0x1e, 0xfc, 0x4f, 0x48, 0x93, 0x57, 0xc3, 0x81, 0x38, 0xb9, 0x88, 0x41, 0x12, 0x28, 0x6d, 0xb8, 0x1d, 0x5a, 0x9a, 0x86, 0x3c, 0xb8, 0x9e, 0x8e, 0x3b, 0xa8, + 0x84, 0xb9, 0x6c, 0x43, 0x3d, 0x9c, 0xec, 0xe8, 0x72, 0xb9, 0x3c, 0x2e, 0x8f, 0xd3, 0xef, 0xf4, 0xa8, 0xd0, 0x39, 0x4a, 0x03, 0x91, 0x50, 0x8c, 0x03, 0x87, 0x13, 0x58, 0xb0, 0x81, 0xe7, 0xc1, 0xc0, 0x29, 0x06, 0xf4, 0xea, 0x73, 0xdb, 0xe7, 0x01, 0xb0, 0x4c, 0x9c, 0xb1, 0x38, 0x1c, 0xce, 0xcc, 0x71, 0xd7, 0x57, 0x97, 0x5b, 0xc4, 0x12, 0x4b, 0x79, 0x75, 0xbd, 0x2b, 0x94, 0x91, 0x17, 0x5e, 0x92, 0x21, 0x02, 0x43, 0xdf, 0x67, 0x7f, 0xcf, 0xfe, 0xfe, 0x3e, 0x32, 0xaa, 0xa8, 0xaa, 0x1a, 0x52, 0x6b, 0x94, 0xae, 0xa2, 0x74, 0xa5, 0x32, 0xbd, 0xc8, 0x05, 0x09, 0xc4, 0x50, 0x55, 0x95, 0x7c, 0xf4, 0x35, 0xf0, 0x1a, 0x5b, 0x8e, 0xf9, 0xf1, 0xca, 0x0b, 0x5f, 0xd2, 0x65, 0xcc, 0xbb, 0x84, 0x9a, 0x98, 0x46, 0x2c, 0x3f, 0xe9, 0xe2, 0xf9, 0x31, 0x32, 0xc1, 0x39, 0x50, 0x67, 0x05, + 0xbd, 0xc8, 0x6f, 0x14, 0xb3, 0x12, 0x4c, 0x57, 0x11, 0xb5, 0xe5, 0x7d, 0x0b, 0x73, 0xb1, 0xfb, 0x61, 0x8c, 0x50, 0x24, 0x16, 0x4e, 0x2c, 0xd2, 0x13, 0x35, 0x02, 0xa2, 0xac, 0xa4, 0xb8, 0x30, 0x27, 0xe8, 0xf3, 0xd8, 0x52, 0xac, 0x16, 0xc8, 0x85, 0xd5, 0x40, 0x3d, 0x99, 0x0b, 0x1b, 0xc6, 0x98, 0xb0, 0x67, 0x4a, 0x1e, 0xac, 0x89, 0x7b, 0x0f, 0x47, 0xc8, 0xae, 0xaa, 0xa5, 0xb5, 0x1e, 0x4f, 0xed, 0xd2, 0xaa, 0xba, 0xe5, 0x75, 0xee, 0xdb, 0xc0, 0x3f, 0x15, 0x2a, 0x21, 0xa9, 0x09, 0xb6, 0x55, 0x64, 0xcf, 0xf2, 0xd6, 0x0c, 0x4f, 0xcf, 0x16, 0xe6, 0x9e, 0xd8, 0xd5, 0xf7, 0xc0, 0xba, 0xaa, 0x9a, 0xad, 0x27, 0x96, 0xed, 0x7d, 0x69, 0x65, 0x56, 0xe6, 0xb2, 0xe7, 0x6e, 0xc8, 0x98, 0x13, 0x76, 0x67, 0x25, 0x4b, 0xc1, 0x8d, 0x79, 0xb3, 0xb6, 0x35, 0x36, 0x6e, 0xea, 0x0a, 0x66, + 0xb5, 0x2e, 0x2b, 0x3d, 0x7d, 0x86, 0xa9, 0x95, 0x28, 0x35, 0x62, 0x5f, 0xa9, 0xdf, 0x20, 0x8c, 0xac, 0x3b, 0x7d, 0x43, 0xff, 0xb2, 0xba, 0xed, 0xcf, 0x2c, 0x59, 0xf2, 0xc2, 0xee, 0xe6, 0x45, 0xcf, 0x7c, 0xba, 0xe3, 0x29, 0xe0, 0x7d, 0x6f, 0x9d, 0x50, 0xa0, 0x30, 0xaa, 0x39, 0x8c, 0x34, 0x7b, 0x8c, 0x3a, 0x82, 0x79, 0x2e, 0xb2, 0xfd, 0xf0, 0x3c, 0x17, 0x73, 0x5a, 0x12, 0x4b, 0x1e, 0xc8, 0xe5, 0x18, 0x1f, 0xf8, 0xe0, 0x15, 0x71, 0x5d, 0xea, 0x08, 0x7b, 0xe4, 0xec, 0xd2, 0xf1, 0x5c, 0x77, 0xd5, 0x59, 0x30, 0x93, 0xea, 0x38, 0xd7, 0x49, 0x1f, 0x8d, 0x73, 0xdd, 0x2a, 0xbf, 0x1a, 0x45, 0xfe, 0xc2, 0xfe, 0x5c, 0xb9, 0x42, 0x1d, 0x83, 0x62, 0x61, 0x44, 0x08, 0x47, 0x34, 0x15, 0xd9, 0x08, 0x48, 0x02, 0x59, 0x22, 0xe1, 0xd6, 0xea, 0xe2, 0x51, 0xe6, 0x24, 0x68, 0x09, 0x04, + 0x02, 0x1e, 0xde, 0xe4, 0x85, 0xc4, 0x16, 0x64, 0x59, 0xa4, 0x11, 0xbd, 0x88, 0xe8, 0x52, 0xe0, 0xf7, 0x52, 0x1a, 0x41, 0x46, 0x50, 0xfa, 0x84, 0x19, 0xa5, 0x2b, 0x0f, 0x9d, 0xfe, 0xcd, 0xba, 0x6b, 0x3e, 0x3c, 0x7d, 0x68, 0xb8, 0xb4, 0x74, 0xf8, 0xd0, 0xe9, 0x0f, 0xaf, 0x41, 0x9f, 0x57, 0x96, 0x36, 0xe5, 0x2f, 0xbc, 0xf9, 0xa9, 0xb7, 0x96, 0x2d, 0x7b, 0xeb, 0xa9, 0x9b, 0x17, 0xe6, 0x27, 0x7e, 0x26, 0x6f, 0x5f, 0xf7, 0x9b, 0xef, 0xc3, 0x02, 0xb0, 0xe2, 0xf7, 0x13, 0x2b, 0xfe, 0x66, 0x3d, 0xfc, 0xfd, 0x16, 0x54, 0xf6, 0x16, 0x54, 0xf6, 0xcd, 0xe3, 0x5c, 0xbd, 0xe3, 0x6f, 0x72, 0x7c, 0x6a, 0xe0, 0xc2, 0xe7, 0x22, 0x26, 0x16, 0x83, 0xd9, 0x01, 0x18, 0x22, 0x92, 0x4b, 0x0a, 0x84, 0x5c, 0x94, 0x1f, 0x09, 0x88, 0xe7, 0x72, 0x43, 0x81, 0x38, 0x97, 0x8b, 0x71, 0x26, 0x2e, + 0x14, 0xf4, 0xcb, 0x86, 0xb9, 0x79, 0x3c, 0xda, 0x4f, 0x75, 0x65, 0x79, 0x99, 0x53, 0xeb, 0x42, 0x00, 0x40, 0x9f, 0x4b, 0x8a, 0x63, 0x18, 0x8d, 0x4b, 0x0d, 0x91, 0x98, 0xb9, 0x8d, 0x43, 0xf8, 0xc5, 0xd1, 0x7f, 0xa4, 0x5a, 0xa5, 0xc1, 0x3c, 0x28, 0xa4, 0x46, 0x57, 0x45, 0x1a, 0xb5, 0x2a, 0x76, 0xc9, 0xe0, 0xa5, 0x7f, 0x90, 0x5c, 0xd1, 0xdc, 0x97, 0xbf, 0xfb, 0xf5, 0x4d, 0x05, 0x0d, 0xfb, 0x7f, 0xba, 0x73, 0xdb, 0xcf, 0xda, 0xda, 0x7e, 0xb6, 0x6d, 0xe7, 0x4f, 0xf7, 0x37, 0x14, 0x6c, 0x3c, 0xb3, 0x7b, 0xf9, 0x5d, 0xf9, 0x01, 0xa9, 0x56, 0xa1, 0xb0, 0x65, 0x4d, 0xcb, 0xbe, 0xef, 0xc8, 0xa9, 0xa7, 0x7b, 0x6f, 0x2b, 0x78, 0xbf, 0xe0, 0xd6, 0xde, 0x67, 0x5e, 0x3a, 0x72, 0x5f, 0x70, 0x5a, 0x96, 0x1d, 0xf9, 0x2e, 0x31, 0x59, 0x96, 0x0c, 0xbb, 0x76, 0xe5, 0x19, 0xf6, 0xdb, + 0x3b, 0x8e, 0xb3, 0x7f, 0x3e, 0xbd, 0xa0, 0xaf, 0xa3, 0xa3, 0x6f, 0xc1, 0x69, 0x60, 0x38, 0x7e, 0x07, 0x10, 0x9c, 0x59, 0x59, 0x1d, 0x5e, 0xa2, 0x96, 0x1a, 0x93, 0x8d, 0xec, 0xa7, 0xec, 0xdf, 0xd9, 0xbf, 0xb2, 0x7f, 0xaa, 0x29, 0x03, 0x7f, 0x60, 0xcd, 0x65, 0x35, 0xc0, 0x04, 0x34, 0x40, 0x0e, 0xf4, 0xd8, 0xe1, 0x87, 0xcf, 0x79, 0x72, 0xe1, 0x73, 0x61, 0x29, 0x94, 0xdf, 0x23, 0xc4, 0x2d, 0x27, 0x8d, 0x28, 0x1e, 0x64, 0x3d, 0x77, 0xab, 0x9e, 0x1d, 0x87, 0x21, 0x49, 0x81, 0x04, 0x48, 0x25, 0xc3, 0x22, 0x40, 0x8b, 0x01, 0x90, 0x22, 0x47, 0x67, 0x04, 0x8e, 0xe4, 0x43, 0xf5, 0x4a, 0x24, 0x09, 0x3e, 0x38, 0x05, 0x97, 0xa8, 0x83, 0x4b, 0x0b, 0x85, 0xb1, 0x18, 0xd7, 0xd8, 0x29, 0x04, 0x55, 0x8e, 0x39, 0x7f, 0x69, 0xc2, 0xb9, 0x99, 0x01, 0x9f, 0x57, 0xe7, 0x74, 0xbb, 0x50, + 0xa6, 0x50, 0x07, 0x72, 0x4a, 0x75, 0xf3, 0x97, 0x07, 0xbc, 0x37, 0x08, 0xa6, 0x47, 0x89, 0xd8, 0x4a, 0x84, 0x87, 0x48, 0x49, 0xb8, 0x59, 0x10, 0x96, 0x76, 0xfc, 0xfd, 0xd8, 0x4d, 0xff, 0x77, 0x6c, 0x81, 0x84, 0xfd, 0xc9, 0xfb, 0xef, 0x83, 0x5c, 0xc5, 0xb2, 0xe3, 0x7f, 0xde, 0x7b, 0xfb, 0xc7, 0x1d, 0x08, 0x4e, 0xa9, 0xc9, 0x6e, 0x5e, 0xdd, 0xd9, 0x77, 0xef, 0x35, 0x0d, 0x32, 0xf2, 0xba, 0xd1, 0xf5, 0xe2, 0xa6, 0xf5, 0xf7, 0xf5, 0x14, 0xce, 0x6a, 0xae, 0x72, 0x62, 0xfc, 0xe5, 0x5b, 0x8b, 0x46, 0xe6, 0x1e, 0xff, 0xea, 0xc0, 0xe8, 0x5a, 0xe6, 0xde, 0xd1, 0x9b, 0x6e, 0xfa, 0xcb, 0x63, 0xfd, 0x03, 0xed, 0x1c, 0x8e, 0xb2, 0x74, 0xcd, 0xd1, 0xa1, 0xc3, 0xab, 0x9e, 0xdd, 0x54, 0x1e, 0xbf, 0x49, 0x40, 0xf4, 0xf6, 0x5b, 0x48, 0x66, 0xae, 0xc3, 0xf7, 0xaf, 0x66, 0x84, 0x9c, + 0xa0, 0x51, 0xd4, 0xb8, 0x7a, 0x21, 0x22, 0x47, 0xc8, 0x02, 0x3c, 0x17, 0x67, 0xe0, 0xe5, 0x82, 0xff, 0x21, 0x7d, 0x07, 0xa5, 0x44, 0xe6, 0xb1, 0x15, 0x62, 0x74, 0x9d, 0x2a, 0x42, 0x41, 0x33, 0x29, 0xee, 0x2e, 0x8d, 0xe2, 0x7c, 0x49, 0x31, 0xda, 0x83, 0xf3, 0xc9, 0x0c, 0x51, 0xd7, 0x5c, 0x2f, 0x35, 0xcb, 0xa4, 0x49, 0xb2, 0x5d, 0xd4, 0xa6, 0xa3, 0xc9, 0x2e, 0xd1, 0xff, 0xde, 0xfa, 0xe6, 0xfe, 0xff, 0x15, 0xb9, 0x2c, 0xc7, 0xc8, 0x39, 0x6f, 0x93, 0x5f, 0x81, 0x16, 0xeb, 0xf4, 0xcc, 0xac, 0xe9, 0x29, 0xec, 0xb3, 0xa3, 0xd2, 0x64, 0x03, 0xe9, 0x1f, 0x7d, 0x88, 0xec, 0x1b, 0x7d, 0x5f, 0x9b, 0x0a, 0x7b, 0x43, 0xe2, 0x3c, 0x99, 0xff, 0x83, 0xfb, 0xe5, 0x40, 0x58, 0x18, 0x1a, 0x81, 0x5a, 0xeb, 0xc5, 0x52, 0x92, 0x10, 0xd0, 0x24, 0xee, 0x19, 0xc2, 0x13, 0x26, 0x35, 0x08, + 0x19, 0x24, 0x91, 0xe1, 0xbe, 0x25, 0xa6, 0x6d, 0x4e, 0xec, 0xa3, 0xec, 0x92, 0x7d, 0xe4, 0xd7, 0x03, 0x84, 0x98, 0x9f, 0x5c, 0xa4, 0xaf, 0x80, 0x60, 0xc3, 0xef, 0xbf, 0x4f, 0x4a, 0x41, 0xea, 0x25, 0xfa, 0x0c, 0xff, 0x7e, 0x08, 0x4e, 0x78, 0x0f, 0xf9, 0x08, 0xec, 0xbb, 0x81, 0x20, 0x04, 0x83, 0x38, 0x9e, 0x7f, 0x3e, 0x8a, 0xb8, 0xc3, 0xa0, 0x64, 0xb2, 0xf5, 0xf0, 0x20, 0xc2, 0x9f, 0x04, 0xe4, 0x30, 0x37, 0xb3, 0x60, 0x39, 0x8e, 0x0d, 0x89, 0xc3, 0xaf, 0xc6, 0xe6, 0x57, 0xad, 0xd6, 0x7b, 0xcb, 0xb5, 0x5e, 0xbb, 0x05, 0x87, 0x17, 0x4b, 0xa1, 0x8c, 0xa5, 0x54, 0x04, 0x1e, 0x5b, 0x2e, 0xba, 0x1c, 0x95, 0x0e, 0x84, 0xb8, 0xdf, 0x5e, 0xbc, 0x5d, 0xb8, 0xdb, 0x4a, 0xae, 0x00, 0x2c, 0x49, 0x7e, 0xdf, 0xe4, 0x4d, 0x35, 0x88, 0x84, 0xc6, 0x54, 0xaf, 0x09, 0x28, 0xcf, 0xff, + 0x99, 0x3e, 0x71, 0xd4, 0xe2, 0x16, 0xdd, 0xa6, 0xf5, 0xe5, 0xb7, 0x14, 0x96, 0x91, 0xf4, 0x5b, 0x80, 0x2c, 0x2e, 0x6c, 0xc9, 0xf7, 0x69, 0x6f, 0x13, 0xb9, 0x2d, 0x47, 0x5f, 0x4e, 0xae, 0xec, 0xdb, 0x30, 0x83, 0x05, 0xcc, 0x1b, 0x2c, 0xd1, 0xba, 0xb1, 0xbf, 0x32, 0x99, 0x7a, 0x21, 0x25, 0x27, 0x33, 0xc3, 0x6c, 0xce, 0xc8, 0x84, 0xc4, 0xa0, 0xe1, 0xdb, 0xd1, 0x54, 0x2d, 0x78, 0x28, 0x5c, 0x9d, 0xa6, 0x26, 0x47, 0xd7, 0x92, 0x37, 0x92, 0xea, 0xb4, 0xea, 0x30, 0xdb, 0xa7, 0x4d, 0x05, 0x7f, 0xae, 0x1d, 0x6e, 0xc9, 0x64, 0xc8, 0x75, 0xa3, 0x37, 0x30, 0x19, 0xd3, 0x87, 0x6b, 0xb9, 0x3c, 0xe3, 0x04, 0x21, 0x7a, 0x1d, 0x8f, 0xb5, 0x04, 0x45, 0x4f, 0x80, 0x63, 0x15, 0x83, 0x7a, 0x28, 0xb5, 0xe1, 0x65, 0x12, 0x93, 0x28, 0x2c, 0x8c, 0xa8, 0x67, 0xc2, 0x5a, 0x71, 0x88, 0x30, + 0x78, 0x32, 0xb8, 0xd1, 0xca, 0xae, 0x78, 0xb4, 0x20, 0x12, 0x5b, 0x2e, 0x3c, 0x68, 0x26, 0xd9, 0xe4, 0xb5, 0xc5, 0x06, 0xad, 0x80, 0x83, 0x3e, 0x79, 0xcc, 0xe2, 0x16, 0x1e, 0xc0, 0x83, 0x2e, 0x25, 0x99, 0x37, 0x01, 0x59, 0x93, 0x59, 0x5b, 0x94, 0xa1, 0xbd, 0x03, 0x2d, 0xe3, 0x29, 0x7d, 0x47, 0xfe, 0xe8, 0xa9, 0xf7, 0xdf, 0xa7, 0x32, 0xe0, 0xc0, 0x7f, 0x78, 0x45, 0x03, 0x57, 0xf9, 0xf9, 0x81, 0xff, 0x31, 0xbf, 0xd5, 0x32, 0xba, 0x1a, 0xae, 0xef, 0x6e, 0xf2, 0x9a, 0xf8, 0xe0, 0x01, 0x91, 0xc4, 0x2e, 0xa3, 0x46, 0x21, 0x4d, 0x1e, 0x20, 0x55, 0x51, 0x49, 0x08, 0x08, 0x44, 0xa5, 0x38, 0xbb, 0x1c, 0x27, 0x2c, 0xe7, 0x23, 0x62, 0x2c, 0x10, 0x32, 0x90, 0xb9, 0x11, 0x28, 0x38, 0x2b, 0x0e, 0x58, 0x87, 0x84, 0x58, 0x1b, 0xbe, 0xb0, 0x11, 0xf7, 0x4a, 0xa0, 0x68, 0x6b, 0xc6, + 0xe1, 0x35, 0x89, 0x5e, 0xe4, 0xde, 0x6c, 0x6d, 0x20, 0xe4, 0xf2, 0x54, 0x79, 0xcc, 0x39, 0xaa, 0xe2, 0xe2, 0xf5, 0x89, 0xb1, 0xea, 0x18, 0x60, 0x7a, 0x89, 0x96, 0x10, 0xf9, 0x8b, 0x4e, 0x6c, 0x09, 0x96, 0x84, 0xc5, 0xe4, 0x52, 0xe2, 0xb2, 0x6d, 0xc6, 0xdb, 0x41, 0x16, 0xb1, 0xd2, 0x2b, 0x6c, 0x47, 0xd2, 0x0b, 0xa9, 0x21, 0xdf, 0x0c, 0x31, 0xae, 0x95, 0xcc, 0x71, 0xad, 0xc8, 0x09, 0xf9, 0xaa, 0xab, 0x6c, 0x4a, 0x1c, 0xb3, 0xb8, 0x15, 0x4f, 0xd9, 0x0a, 0xdc, 0x8f, 0x12, 0x31, 0x29, 0x99, 0xba, 0xb5, 0x84, 0x36, 0xa2, 0xe5, 0x97, 0xac, 0x4e, 0xc8, 0xa4, 0x84, 0xac, 0x8b, 0xb8, 0x64, 0x1b, 0xbc, 0xbd, 0xd2, 0x30, 0x30, 0xb7, 0xa7, 0xab, 0xb3, 0xbd, 0xb9, 0xb1, 0xae, 0x06, 0x45, 0xf3, 0xf7, 0x39, 0x5d, 0x39, 0x0e, 0x05, 0xda, 0xcf, 0x9c, 0xc2, 0x01, 0x37, 0x33, 0x2f, + 0x83, 0xc6, 0x1c, 0xff, 0x84, 0xce, 0x4b, 0x06, 0xf7, 0xa7, 0xc6, 0x4c, 0x28, 0xbc, 0x97, 0x75, 0x38, 0x26, 0xc1, 0x41, 0x22, 0x96, 0x4e, 0x96, 0x34, 0x25, 0xf9, 0x32, 0xf2, 0xbd, 0x7f, 0x7c, 0xf7, 0x9e, 0xfd, 0xa9, 0x85, 0xe1, 0x3c, 0x4b, 0xc5, 0xc2, 0x1a, 0x57, 0x7a, 0xcb, 0x48, 0xed, 0x1b, 0xe0, 0x65, 0x9c, 0x07, 0xa0, 0x3e, 0x96, 0x07, 0x40, 0x2f, 0x89, 0xe5, 0x01, 0x58, 0xd0, 0x9f, 0xdb, 0x55, 0xe2, 0x28, 0xd9, 0x74, 0x6a, 0xb3, 0x23, 0x9c, 0xe1, 0xd3, 0x46, 0x72, 0x75, 0xd9, 0xad, 0x25, 0x55, 0xeb, 0x43, 0x55, 0x33, 0xc3, 0x06, 0x52, 0xac, 0x55, 0x45, 0xd8, 0x65, 0x61, 0x3f, 0x45, 0x53, 0x3b, 0x76, 0x6d, 0xd8, 0x21, 0x10, 0x08, 0xdc, 0x15, 0x33, 0xf3, 0x0a, 0xfb, 0x4a, 0x6d, 0x60, 0xca, 0x74, 0x01, 0x8b, 0x1f, 0x08, 0x1a, 0xca, 0x3b, 0x16, 0x95, 0xb6, 0x1d, + 0x5c, 0x53, 0xcd, 0x08, 0x18, 0xbf, 0xd7, 0xe4, 0x32, 0xc9, 0x25, 0xd6, 0x50, 0x6d, 0xba, 0xcc, 0xa0, 0x96, 0x5e, 0xb8, 0x40, 0xcc, 0x64, 0x8f, 0x49, 0x4a, 0x20, 0x0f, 0xf6, 0x10, 0x16, 0x1c, 0x9f, 0x68, 0x8f, 0xa0, 0x18, 0xd3, 0x89, 0x9e, 0x0b, 0x0f, 0x0a, 0xea, 0x98, 0x6f, 0x89, 0x54, 0xa2, 0x0c, 0x45, 0xe2, 0x53, 0x49, 0xa0, 0xd4, 0x65, 0x46, 0xd7, 0x15, 0xf5, 0xb1, 0x74, 0x27, 0x04, 0x0a, 0xe0, 0xd8, 0x85, 0x01, 0xc1, 0xe8, 0x72, 0xa9, 0x25, 0x33, 0x2d, 0x33, 0x80, 0x22, 0x7f, 0xb9, 0x63, 0x28, 0x26, 0x4e, 0x40, 0xe1, 0xee, 0xe9, 0xb1, 0x74, 0x82, 0xf4, 0x63, 0x21, 0xf7, 0x98, 0xb3, 0x10, 0xc7, 0xf3, 0x41, 0x51, 0x1f, 0xf4, 0xdf, 0xc1, 0x19, 0x98, 0x8c, 0x35, 0xe9, 0xe9, 0xd5, 0xc6, 0x59, 0x77, 0x2e, 0x2d, 0x28, 0x58, 0x7a, 0xe7, 0x6c, 0x63, 0xb5, 0x3f, 0xbd, + 0x9a, 0xbc, 0xae, 0xeb, 0xfe, 0x8f, 0x6e, 0x34, 0x34, 0x07, 0x83, 0x8d, 0x86, 0x3d, 0x1f, 0x3f, 0xd0, 0xdd, 0xfd, 0xc0, 0x27, 0x7b, 0x0c, 0x0d, 0xc1, 0x60, 0x93, 0xe1, 0xc6, 0x8f, 0x0e, 0x77, 0x1d, 0x57, 0x74, 0xed, 0x3d, 0xb5, 0x6a, 0xd5, 0xcb, 0x37, 0x76, 0x29, 0x6c, 0x2e, 0xb7, 0x4d, 0xd1, 0xb5, 0xe7, 0x95, 0x55, 0xab, 0x5f, 0xde, 0xd3, 0x25, 0xb7, 0xb9, 0xdd, 0x24, 0x71, 0xdb, 0xb9, 0xd7, 0x36, 0xa8, 0x3d, 0x99, 0x19, 0x1e, 0xd5, 0xba, 0xd7, 0xce, 0xdf, 0x7e, 0x60, 0xf4, 0xb5, 0xf5, 0x2a, 0x4f, 0x46, 0xa6, 0x47, 0xbd, 0xee, 0xb5, 0x73, 0x1c, 0x6e, 0x65, 0x3e, 0x92, 0xd3, 0x84, 0x4b, 0x08, 0x2d, 0xe1, 0x27, 0x56, 0x11, 0x3f, 0x8b, 0x2a, 0x92, 0xa0, 0xac, 0xd6, 0x02, 0xe4, 0xc2, 0x12, 0x20, 0x56, 0x20, 0x73, 0x3e, 0x32, 0xa3, 0xd7, 0x11, 0x42, 0x91, 0x9c, 0x11, + 0x2e, 0xd2, 0x00, 0x99, 0x5c, 0x29, 0x97, 0x29, 0x87, 0x09, 0xa5, 0x42, 0xae, 0x54, 0x2c, 0x47, 0xf2, 0x30, 0x8d, 0xcc, 0x71, 0xc8, 0x04, 0xc5, 0x20, 0x13, 0x94, 0x80, 0x60, 0x44, 0x02, 0x66, 0x40, 0x0d, 0xe4, 0x72, 0xa8, 0x68, 0x03, 0xb4, 0x09, 0x55, 0x40, 0xa1, 0xe0, 0xce, 0xa7, 0x1d, 0x6d, 0xe5, 0x7a, 0xd4, 0x96, 0x48, 0x28, 0x1f, 0xfe, 0xf7, 0x1b, 0xeb, 0x89, 0xda, 0x56, 0xae, 0x18, 0x5a, 0x32, 0x38, 0x7f, 0xde, 0xdc, 0xd9, 0xfd, 0x3d, 0x5d, 0xed, 0xad, 0x0d, 0x75, 0xe5, 0x65, 0x45, 0x05, 0x6e, 0x83, 0xcb, 0xa7, 0x77, 0x68, 0x9c, 0x2a, 0x2d, 0xda, 0xd8, 0xfc, 0xde, 0x74, 0xe2, 0x6d, 0x4d, 0x87, 0x40, 0x2e, 0x42, 0xf1, 0xe1, 0x45, 0xe0, 0xac, 0x16, 0x78, 0x03, 0x1b, 0x2f, 0x2d, 0x57, 0x26, 0x66, 0x04, 0x8e, 0x09, 0x96, 0xa1, 0x89, 0x62, 0x25, 0xf3, 0xe5, 0xec, + 0x03, 0xeb, 0x17, 0xe7, 0x96, 0xd5, 0xad, 0xbf, 0x6e, 0x4d, 0xb0, 0xbd, 0xa5, 0xa5, 0x73, 0x76, 0x01, 0x2b, 0x73, 0xd8, 0x1c, 0x24, 0x23, 0x17, 0xe6, 0xa4, 0x67, 0xa7, 0x87, 0x22, 0x86, 0x8c, 0xca, 0xac, 0x1f, 0x21, 0xd9, 0x33, 0xb2, 0xfb, 0xcc, 0x46, 0x2c, 0x7b, 0x6e, 0xfd, 0x59, 0x3b, 0x92, 0x3d, 0x7f, 0xc2, 0xc9, 0x9e, 0x91, 0xbe, 0xe6, 0x72, 0x6b, 0x06, 0x2f, 0x7d, 0x06, 0xef, 0x3b, 0xf2, 0xd2, 0x33, 0xab, 0xee, 0x0b, 0x3e, 0x10, 0x3c, 0xb4, 0xea, 0xe9, 0x53, 0x48, 0xfa, 0x0c, 0xda, 0x90, 0x43, 0xb9, 0x60, 0x4d, 0x7a, 0x6e, 0x7a, 0xcd, 0xb4, 0x35, 0x5b, 0xf5, 0xa9, 0xee, 0x54, 0x3d, 0xfb, 0xa0, 0x56, 0x28, 0x72, 0xb8, 0x48, 0x32, 0xd3, 0xe6, 0xc9, 0xae, 0x2c, 0x70, 0x45, 0xb3, 0x2c, 0x97, 0x94, 0x4e, 0xb5, 0xf6, 0x0c, 0xcb, 0x78, 0xf9, 0x74, 0xe5, 0x9c, 0xb9, + 0xc3, 0x53, 0xc8, 0xa6, 0xbd, 0xec, 0xc3, 0xf4, 0xb7, 0xf0, 0x5c, 0x64, 0x82, 0x45, 0x51, 0x89, 0x0a, 0xea, 0xed, 0x48, 0xe5, 0x23, 0xf9, 0xeb, 0x1d, 0x6f, 0x2c, 0x32, 0x60, 0x42, 0xe8, 0xfc, 0x98, 0x5f, 0x27, 0x0a, 0x92, 0x92, 0xe0, 0xb0, 0x28, 0xc3, 0x1e, 0x8e, 0xb1, 0xc8, 0xfb, 0xf1, 0xe2, 0xe8, 0x6a, 0x82, 0xf3, 0xf2, 0xc4, 0x1f, 0x13, 0x2a, 0xa8, 0x2f, 0x53, 0xc1, 0x3e, 0xb1, 0x82, 0xf6, 0x6a, 0xdf, 0xa0, 0xbf, 0xf4, 0x08, 0x9c, 0x13, 0x46, 0x60, 0xbc, 0x9a, 0x01, 0x5f, 0x71, 0xc3, 0x3c, 0x3d, 0x96, 0x38, 0x9d, 0x3e, 0xa8, 0xde, 0x64, 0xe3, 0xc0, 0x97, 0x97, 0x71, 0x65, 0x42, 0x74, 0x18, 0x6a, 0x91, 0x18, 0x51, 0x3e, 0x64, 0x8e, 0x68, 0xc6, 0xbc, 0x96, 0xfe, 0xda, 0x05, 0xc5, 0x6c, 0xb5, 0x36, 0xd4, 0xb6, 0xa1, 0x17, 0x79, 0x2d, 0xd5, 0x6d, 0x3f, 0xbe, 0x28, + 0x54, 0x9a, 0x96, 0xa2, 0x29, 0x4e, 0x4d, 0x5a, 0x2f, 0x24, 0xc9, 0x25, 0xdf, 0x07, 0xb2, 0xc9, 0x3e, 0x4a, 0x28, 0x6f, 0xb4, 0x58, 0x22, 0x66, 0x53, 0x92, 0x51, 0xdc, 0xd1, 0x95, 0x50, 0xae, 0x3e, 0x4e, 0x7d, 0x4d, 0x94, 0xa3, 0xf8, 0xb9, 0xb9, 0x2e, 0x25, 0x85, 0x2f, 0x12, 0x68, 0x8a, 0x42, 0x2e, 0xb9, 0x7c, 0x34, 0x13, 0x38, 0x86, 0x72, 0x22, 0x1a, 0xf0, 0xa4, 0x9b, 0x70, 0x4e, 0x35, 0xce, 0x2a, 0x25, 0xc4, 0xe4, 0x0c, 0x4b, 0xab, 0x48, 0xe8, 0xc1, 0x89, 0x10, 0x11, 0x8e, 0x1b, 0x87, 0xc8, 0x8b, 0x7d, 0x8e, 0x03, 0x40, 0xb1, 0x1f, 0xf9, 0x75, 0x3a, 0x63, 0x5a, 0xd3, 0x8a, 0xea, 0x55, 0x6a, 0xad, 0xbc, 0x6c, 0xc4, 0xe3, 0x1e, 0x89, 0xca, 0x14, 0xea, 0x75, 0x65, 0x8b, 0x9b, 0xfc, 0x3b, 0x52, 0x2d, 0x0b, 0x7a, 0x7d, 0xd5, 0x21, 0xeb, 0xce, 0x14, 0x8b, 0x25, 0xbb, + 0x26, 0xe0, 0xaf, 0x8f, 0xd8, 0xe4, 0x2a, 0xb9, 0x41, 0x47, 0x7d, 0xdd, 0xf6, 0xbd, 0xd6, 0xe1, 0xd7, 0x8f, 0xdd, 0x5e, 0x21, 0x57, 0x94, 0x9d, 0x5c, 0xb8, 0xf0, 0x64, 0x54, 0x21, 0x2b, 0xda, 0x7b, 0xf7, 0xa3, 0x5d, 0xe0, 0xa0, 0xcb, 0xc8, 0xbe, 0xb8, 0xff, 0xc3, 0x0e, 0xdf, 0xe0, 0x9a, 0x2d, 0x65, 0x60, 0x3b, 0xfc, 0x76, 0xba, 0xec, 0xda, 0xb5, 0x4b, 0x02, 0x69, 0x83, 0xd7, 0xde, 0x31, 0xb3, 0xac, 0xaf, 0x22, 0x43, 0xa9, 0xb6, 0x72, 0x31, 0xa8, 0xbf, 0x62, 0x8a, 0x68, 0x8a, 0x08, 0x83, 0xff, 0xe5, 0xa4, 0x03, 0x89, 0x19, 0x88, 0x90, 0x1f, 0x9e, 0x14, 0xe7, 0x4e, 0xe5, 0xbe, 0x50, 0x52, 0x1c, 0x01, 0x47, 0xc6, 0xfb, 0x91, 0x32, 0x22, 0xc8, 0x71, 0x65, 0x50, 0xf8, 0x85, 0x62, 0xbc, 0x74, 0x18, 0x6a, 0xa9, 0x28, 0x5e, 0x3d, 0x0a, 0x2b, 0x2c, 0x12, 0xc5, 0x82, 0x2b, + 0x13, 0x52, 0x29, 0x17, 0xfc, 0x8d, 0xcb, 0x7e, 0xae, 0xfe, 0x0e, 0x35, 0xb9, 0x74, 0xe8, 0xfa, 0xef, 0xfc, 0x4e, 0xf3, 0x77, 0x7e, 0x67, 0xf2, 0x77, 0x7e, 0x67, 0xe0, 0x3b, 0xbe, 0x33, 0x5a, 0x78, 0xa9, 0x4a, 0xb8, 0x38, 0xef, 0x72, 0x8b, 0x1d, 0xe7, 0xcd, 0x5c, 0x6d, 0x22, 0x96, 0xce, 0x1d, 0x9d, 0x98, 0x54, 0x40, 0x64, 0x67, 0x21, 0xc8, 0xac, 0xcb, 0x91, 0x6a, 0x45, 0x31, 0x1a, 0xb9, 0xa0, 0x81, 0x42, 0x06, 0x2e, 0x6d, 0x58, 0x8e, 0x93, 0x03, 0xf0, 0x4e, 0x81, 0xa4, 0x97, 0x0f, 0x14, 0x7e, 0x29, 0xe5, 0x35, 0x4c, 0xbf, 0xaf, 0x94, 0x36, 0x14, 0x3f, 0xb9, 0x7e, 0xc1, 0xc3, 0xeb, 0xea, 0xc4, 0xaf, 0xbe, 0xf2, 0xca, 0x6b, 0xb2, 0x96, 0x6b, 0x1f, 0x9e, 0xb7, 0xf4, 0xe1, 0xc2, 0x32, 0x46, 0xa3, 0x54, 0xd9, 0x73, 0x1a, 0x0a, 0xf2, 0xe7, 0xd4, 0x67, 0x8b, 0xc1, 0x51, + 0xb6, 0x43, 0x14, 0x6a, 0x9a, 0x1b, 0x76, 0x15, 0xe6, 0x04, 0x8c, 0xca, 0xcf, 0xa5, 0xad, 0x4d, 0x33, 0xa6, 0xad, 0x7b, 0x64, 0x01, 0x5b, 0x49, 0x7d, 0xcd, 0xb6, 0xce, 0x7b, 0x70, 0x75, 0xb4, 0xb6, 0xb8, 0x47, 0x24, 0x4f, 0xb2, 0x26, 0xb9, 0x6b, 0x17, 0x56, 0xec, 0xaf, 0x59, 0x56, 0xef, 0x15, 0x69, 0xac, 0x7a, 0x39, 0x92, 0x25, 0x84, 0x04, 0x41, 0xf7, 0x53, 0xdf, 0x5c, 0x42, 0x67, 0xb5, 0x5c, 0x4e, 0x67, 0x4d, 0x44, 0x82, 0x8d, 0xd7, 0x59, 0xc9, 0x67, 0xbb, 0x25, 0x7a, 0xa9, 0x58, 0x2f, 0xe9, 0x26, 0x1f, 0xbf, 0x33, 0x29, 0x45, 0x74, 0x70, 0xf0, 0x99, 0x79, 0x07, 0x05, 0x36, 0xd3, 0x41, 0xf0, 0xb3, 0x13, 0xa4, 0xf8, 0x8d, 0xa4, 0x2a, 0x5f, 0x5a, 0xa5, 0xf9, 0x07, 0xa3, 0xff, 0x32, 0x69, 0x41, 0x17, 0xbb, 0x19, 0x6c, 0x67, 0x1f, 0x53, 0x9b, 0x61, 0x5f, 0x70, 0x6e, + 0x0a, 0xa8, 0xf7, 0xf9, 0x71, 0xbf, 0x2e, 0xa1, 0xb3, 0x5a, 0xae, 0x58, 0x67, 0xbd, 0x58, 0x1f, 0xb5, 0xbc, 0x12, 0xa4, 0x0d, 0xd1, 0xdb, 0x50, 0x5f, 0x25, 0x7a, 0x49, 0x17, 0x79, 0xec, 0x4e, 0x53, 0xaa, 0xe8, 0xe0, 0xfc, 0x67, 0x06, 0xee, 0x14, 0xda, 0x4d, 0x07, 0x8f, 0xb3, 0xaf, 0xbf, 0xf2, 0x0a, 0xd8, 0xf4, 0xf1, 0x73, 0x17, 0xeb, 0x31, 0xfc, 0x7b, 0x33, 0xf5, 0x0d, 0xab, 0x02, 0x9f, 0xc3, 0x7e, 0x6b, 0x51, 0xbe, 0x79, 0x1c, 0x5f, 0x2c, 0x9f, 0x08, 0x46, 0x33, 0x42, 0x41, 0x06, 0x99, 0xfe, 0xeb, 0x69, 0x3e, 0x9b, 0x04, 0xf2, 0x4e, 0xc1, 0x41, 0xb7, 0xc6, 0x66, 0xd5, 0xc8, 0x69, 0x6e, 0x42, 0x2e, 0xd6, 0x05, 0xa4, 0x45, 0x65, 0x57, 0xaa, 0xa7, 0x26, 0xeb, 0x1d, 0x16, 0x9d, 0x40, 0xa0, 0xb3, 0x38, 0xf4, 0x87, 0x47, 0xff, 0x40, 0xed, 0x3c, 0x68, 0xb4, 0x89, 0x06, + 0x95, 0xf6, 0x40, 0x81, 0x2f, 0x15, 0x08, 0x9e, 0xa1, 0x40, 0xb2, 0xaf, 0x20, 0xdd, 0xae, 0x5c, 0x28, 0xb4, 0x1b, 0x0f, 0xee, 0x35, 0xe6, 0x4d, 0x6b, 0x8f, 0xb0, 0xbb, 0xe9, 0xbd, 0xec, 0x0d, 0xf9, 0x9d, 0xd3, 0xf2, 0x8c, 0xe4, 0xeb, 0x66, 0xbf, 0xd7, 0x67, 0x30, 0xf8, 0xbc, 0x7e, 0xf3, 0x53, 0xe7, 0x6f, 0x32, 0xab, 0x81, 0xca, 0x97, 0x9b, 0x2c, 0x05, 0xec, 0x34, 0xf0, 0x0a, 0x90, 0x26, 0xe7, 0xfa, 0xd8, 0xcf, 0xe1, 0xc8, 0x1a, 0xc2, 0xed, 0xa5, 0x1e, 0x11, 0x78, 0x9e, 0xad, 0x13, 0x7a, 0x4a, 0x3b, 0xc2, 0xfc, 0xba, 0xbc, 0x0b, 0xd7, 0x05, 0xe9, 0xa8, 0x50, 0x1f, 0xcf, 0x67, 0x48, 0xa4, 0xa4, 0x12, 0x93, 0x75, 0xd4, 0x84, 0xb5, 0x19, 0xa7, 0xa1, 0x4a, 0xe3, 0x1a, 0x6a, 0xd9, 0xd5, 0x69, 0xa8, 0xd4, 0xdf, 0xc6, 0x86, 0xfb, 0xbd, 0xd1, 0x3f, 0xa1, 0xe1, 0xda, 0x85, + 0x0b, 0x95, 0xf6, 0xf4, 0x42, 0xaf, 0x0d, 0x30, 0x70, 0xb8, 0x2e, 0x7b, 0x38, 0xdd, 0xa5, 0x5c, 0x2c, 0xb4, 0xc1, 0xe1, 0x2a, 0x8b, 0x3d, 0xec, 0x82, 0x57, 0x5e, 0x21, 0xa7, 0xb1, 0x7b, 0xe8, 0xfd, 0xec, 0xf5, 0x91, 0xce, 0xca, 0x5c, 0x34, 0xe4, 0x74, 0xaf, 0xcf, 0x68, 0xf4, 0x79, 0xd3, 0x2f, 0x31, 0xe4, 0x46, 0x4f, 0xbe, 0x8e, 0xad, 0x80, 0xeb, 0xd9, 0x08, 0x9e, 0x8d, 0x0d, 0x1b, 0xe7, 0x29, 0x1b, 0xa0, 0x52, 0x19, 0x86, 0x58, 0x4f, 0xde, 0xcc, 0x69, 0x4d, 0xda, 0xd5, 0x40, 0x21, 0x9b, 0x3b, 0x8b, 0x94, 0x28, 0xe0, 0x68, 0xc5, 0x5e, 0x40, 0xd1, 0xc8, 0x9b, 0x79, 0xd2, 0x53, 0x06, 0x5f, 0x70, 0x21, 0xc1, 0xa4, 0x84, 0x50, 0xc8, 0x24, 0x32, 0x85, 0x64, 0x98, 0x90, 0x48, 0x65, 0x12, 0xe9, 0x72, 0x39, 0x90, 0x11, 0x52, 0xb1, 0x4c, 0x3a, 0x40, 0xc0, 0x6d, 0x2a, 0x10, + 0x8b, 0x06, 0x70, 0x44, 0x44, 0xac, 0x74, 0xda, 0x1b, 0x70, 0xec, 0x3c, 0x24, 0x1b, 0x58, 0x11, 0xad, 0x49, 0x8d, 0x3b, 0xb2, 0x5f, 0x45, 0x23, 0xce, 0x29, 0x1b, 0x51, 0xff, 0x27, 0x7a, 0xa2, 0xff, 0x4f, 0xf4, 0xc4, 0xf8, 0x9f, 0xe8, 0x89, 0xff, 0x3f, 0xd1, 0x13, 0xb4, 0x9c, 0x45, 0x97, 0x6b, 0x04, 0x85, 0x5f, 0x40, 0x62, 0xdc, 0xf8, 0xd6, 0xb8, 0x26, 0xa2, 0x33, 0x08, 0xb9, 0x4c, 0x3e, 0x32, 0xa1, 0x09, 0xa4, 0x08, 0x33, 0xf4, 0x22, 0x62, 0x8a, 0xde, 0x60, 0xbe, 0xc1, 0xc7, 0xc0, 0xe4, 0xc5, 0x2f, 0x1e, 0x35, 0x10, 0xeb, 0x56, 0x0f, 0xcf, 0x52, 0xec, 0xd7, 0xac, 0x19, 0x19, 0x9e, 0x3f, 0xaf, 0xb7, 0x07, 0x29, 0xc6, 0x0d, 0x75, 0xd3, 0x20, 0x2d, 0x0c, 0xe7, 0x86, 0xb2, 0x33, 0xd2, 0x9d, 0xc9, 0x26, 0xe4, 0xe8, 0x8a, 0x88, 0x1b, 0xef, 0xa2, 0x12, 0x8f, 0x1f, 0x86, + 0xf5, 0x5d, 0xfe, 0x4a, 0x2a, 0xa6, 0xef, 0x22, 0x27, 0x67, 0xc1, 0x44, 0xb0, 0xcb, 0x45, 0x82, 0x47, 0x45, 0xbc, 0xf8, 0xc6, 0x14, 0x7b, 0x0d, 0x96, 0xf1, 0xee, 0xe9, 0x7a, 0x74, 0xbb, 0x47, 0x2a, 0x4a, 0x66, 0x45, 0x6d, 0x28, 0x5c, 0x54, 0xa8, 0x31, 0x94, 0x94, 0x3b, 0x6f, 0x5f, 0x4f, 0x46, 0x4b, 0x72, 0x5a, 0x46, 0x4b, 0x95, 0x21, 0x52, 0x54, 0x94, 0x54, 0xd5, 0x1b, 0x36, 0x50, 0x62, 0x8d, 0x2a, 0x1f, 0x28, 0xed, 0xb9, 0xde, 0x9c, 0x05, 0x59, 0x95, 0x6b, 0x3b, 0xb3, 0x53, 0x8a, 0xbb, 0x8b, 0x6c, 0x75, 0xce, 0x50, 0x4b, 0x38, 0x79, 0xed, 0xa6, 0x49, 0x01, 0xa8, 0x72, 0xf6, 0xce, 0xfa, 0xe9, 0x4a, 0x83, 0x55, 0xb6, 0x40, 0x20, 0x13, 0x30, 0x32, 0x61, 0x8b, 0xa1, 0xc4, 0xa9, 0x77, 0xa8, 0x75, 0x66, 0x97, 0x82, 0xfa, 0x56, 0x91, 0x3b, 0xad, 0xc5, 0x8f, 0xc3, 0x4e, + 0xa9, 0x42, 0x65, 0x75, 0xde, 0x8a, 0x91, 0xd6, 0x2c, 0x89, 0xd3, 0x95, 0x9d, 0x09, 0x85, 0x69, 0x6b, 0xa8, 0x26, 0x5d, 0x66, 0x54, 0x4b, 0x2d, 0x6e, 0x83, 0x44, 0x62, 0x8f, 0xce, 0x2e, 0xcd, 0x9a, 0x5e, 0x68, 0x17, 0x9b, 0x33, 0x8a, 0x1d, 0xcd, 0x5d, 0xe3, 0x62, 0x57, 0x05, 0xa3, 0xa2, 0xaf, 0xa4, 0xaa, 0xb5, 0x22, 0xad, 0x44, 0xa2, 0x15, 0x2d, 0xb4, 0x24, 0x89, 0x98, 0x3e, 0xb3, 0x0a, 0xc7, 0x51, 0x69, 0x61, 0x8f, 0x89, 0x96, 0xd0, 0x22, 0xa8, 0x5b, 0xaf, 0xc0, 0x3e, 0xa0, 0x7b, 0xe8, 0xc5, 0x7c, 0x0e, 0x80, 0x07, 0xe9, 0x23, 0xf4, 0x2c, 0xc2, 0x48, 0x44, 0x90, 0x6e, 0x8d, 0xdc, 0x66, 0x28, 0xcd, 0x65, 0x74, 0x6b, 0xbf, 0x8f, 0xd3, 0xad, 0x51, 0xb0, 0xff, 0x98, 0x6a, 0x0d, 0x70, 0x1c, 0xe6, 0xcb, 0x68, 0xd6, 0x9a, 0x85, 0x03, 0x0b, 0x16, 0x19, 0x5a, 0xb2, 0x73, + 0x9a, 0x75, 0x0b, 0x17, 0x0c, 0x0c, 0xea, 0xa6, 0x87, 0x42, 0xad, 0xe0, 0xd1, 0x82, 0x25, 0x07, 0x67, 0xeb, 0xa1, 0x46, 0xdd, 0xa4, 0x9f, 0x7d, 0xe7, 0xe2, 0x82, 0xfc, 0xc5, 0x77, 0xcc, 0xd2, 0x35, 0x87, 0x72, 0xa6, 0xeb, 0x67, 0xdd, 0xb9, 0x38, 0xff, 0x80, 0xe5, 0x85, 0xdb, 0xef, 0x78, 0xd1, 0xec, 0x0b, 0x06, 0x7d, 0xe6, 0x97, 0x6e, 0xbf, 0xfd, 0x79, 0x8b, 0x3f, 0x2f, 0x0f, 0x94, 0x2e, 0x3b, 0xb1, 0xab, 0x4d, 0xe6, 0xc9, 0xcc, 0xf4, 0xc8, 0xdb, 0xae, 0x3b, 0xb1, 0x6c, 0xe8, 0xd9, 0xeb, 0xda, 0xe5, 0xfe, 0x9c, 0x1c, 0xbf, 0xbc, 0x6d, 0xd7, 0xb3, 0x48, 0x8f, 0xee, 0x80, 0xf2, 0xe5, 0x0f, 0x98, 0x2f, 0x21, 0x6f, 0x4a, 0x23, 0x96, 0x93, 0x23, 0x51, 0x0d, 0xd2, 0xa3, 0x75, 0x40, 0xc4, 0xd4, 0x43, 0x5d, 0xba, 0x10, 0xea, 0xd2, 0x34, 0x1f, 0xdc, 0xe8, 0xdf, 0xd0, 0xa5, + 0x1d, 0x13, 0x74, 0x69, 0x8e, 0xd2, 0xfc, 0x87, 0x1a, 0x74, 0x8a, 0x63, 0xca, 0xd4, 0x7f, 0xb4, 0x87, 0x88, 0x8c, 0xfd, 0x3b, 0x2a, 0xff, 0xe4, 0x16, 0xcd, 0xff, 0xc1, 0x16, 0xb9, 0x41, 0x27, 0xff, 0x87, 0xfb, 0xf8, 0x9f, 0xec, 0x1e, 0x4f, 0xa2, 0x6c, 0x43, 0x4b, 0x16, 0x2e, 0x98, 0x3b, 0xbb, 0xbf, 0x6f, 0x66, 0x77, 0x7b, 0x6b, 0x4b, 0x53, 0x6d, 0x75, 0x69, 0x71, 0x7e, 0xd8, 0x69, 0x70, 0x99, 0x2e, 0x63, 0xe8, 0x10, 0x4c, 0xb4, 0x73, 0x8c, 0xcb, 0x01, 0x72, 0x91, 0xd8, 0x34, 0x1c, 0x65, 0xe3, 0x4d, 0x1c, 0x61, 0xfe, 0x92, 0x1d, 0xdf, 0x9c, 0xbd, 0xc9, 0x99, 0x38, 0x6a, 0x37, 0xec, 0x5a, 0x1d, 0x6c, 0x9f, 0xde, 0xd2, 0x31, 0x27, 0x9f, 0x95, 0x3b, 0x6c, 0x76, 0x52, 0xac, 0x0c, 0xf9, 0x83, 0x01, 0xce, 0xc2, 0x71, 0x8b, 0x7f, 0x65, 0xdb, 0xc0, 0xde, 0x1e, 0x5f, 0xee, + 0xfc, 0x3b, 0xe6, 0xce, 0xbc, 0xad, 0xb8, 0xf8, 0xd6, 0xde, 0x79, 0xb7, 0xcf, 0xcf, 0xf5, 0xf5, 0xec, 0x9d, 0x3f, 0x63, 0xc4, 0xef, 0x15, 0x6b, 0xa4, 0x52, 0x93, 0x2d, 0x3d, 0x75, 0xce, 0xe2, 0x15, 0x6b, 0x1b, 0x17, 0xd8, 0xf6, 0xdb, 0x16, 0x34, 0xae, 0x5d, 0xb1, 0x78, 0x4e, 0x4a, 0xc0, 0x66, 0x92, 0xca, 0x54, 0x12, 0x7a, 0x34, 0x3d, 0x2f, 0xbd, 0xb6, 0x62, 0xcd, 0x16, 0xce, 0xba, 0xf1, 0x90, 0x46, 0x28, 0x72, 0xba, 0xe8, 0xcc, 0x54, 0x6c, 0xdc, 0x28, 0xcb, 0xb2, 0x44, 0x7d, 0xb9, 0xd3, 0xf7, 0xbc, 0xb4, 0x64, 0xc3, 0xd9, 0x1b, 0xea, 0x4a, 0x0b, 0x0b, 0x4b, 0xeb, 0x6e, 0x38, 0xbb, 0x61, 0xc9, 0x4b, 0x7b, 0xa6, 0xe7, 0xfa, 0x66, 0x2b, 0xc5, 0x4a, 0x8d, 0xf2, 0xf8, 0xc9, 0x27, 0x9f, 0x6b, 0xa8, 0xad, 0x6d, 0x78, 0xee, 0xc9, 0x93, 0xc7, 0xe1, 0x57, 0xb1, 0x12, 0xdb, + 0x32, 0x5a, 0xd8, 0x87, 0xa8, 0xeb, 0x68, 0x14, 0x69, 0xf8, 0xfd, 0xa8, 0x44, 0x79, 0x39, 0x5b, 0x86, 0xe3, 0xa2, 0xb6, 0x8c, 0x29, 0x4d, 0x13, 0x8e, 0xab, 0x35, 0x4d, 0x38, 0x2e, 0x6a, 0x9a, 0xb8, 0x54, 0x5f, 0x26, 0x1a, 0x0f, 0x62, 0x96, 0x03, 0x3b, 0xb6, 0x1c, 0x08, 0x2e, 0x19, 0x63, 0x28, 0x6e, 0x36, 0x20, 0x67, 0xd8, 0x52, 0x67, 0x3f, 0xbc, 0xbe, 0xba, 0x6c, 0xe3, 0x89, 0xd5, 0xcb, 0x8e, 0x94, 0xe7, 0x8a, 0xd5, 0x2a, 0x95, 0x33, 0xaf, 0x21, 0x52, 0xbb, 0xbc, 0xce, 0xe3, 0xab, 0x5f, 0x52, 0x6e, 0x0f, 0xa4, 0x68, 0x04, 0xf5, 0xa9, 0xe6, 0x75, 0x52, 0x2a, 0x96, 0xb6, 0xa4, 0x3a, 0x5a, 0x2b, 0x45, 0x5a, 0x0d, 0xce, 0x5b, 0xb2, 0x67, 0x76, 0x36, 0xb2, 0x18, 0x14, 0x25, 0xdb, 0xf0, 0xbc, 0x1a, 0xc0, 0x63, 0x24, 0x43, 0x46, 0xe0, 0xa6, 0x4e, 0x8d, 0x26, 0x73, 0xc1, + 0xc1, 0x3b, 0x70, 0x98, 0xd8, 0x1e, 0xde, 0xc3, 0x79, 0x0c, 0xcf, 0xae, 0xb7, 0xe7, 0x91, 0x0c, 0xab, 0x26, 0x23, 0xfb, 0xf7, 0x73, 0x76, 0xc7, 0x6e, 0xfa, 0x76, 0xf2, 0x47, 0x82, 0x5d, 0x90, 0x5e, 0x7a, 0x89, 0x74, 0xa2, 0x86, 0x83, 0x91, 0xe9, 0x51, 0x4a, 0x46, 0x40, 0x33, 0x73, 0xd1, 0xb5, 0x26, 0xa7, 0x35, 0x0e, 0xa0, 0xcb, 0x4d, 0x4b, 0xec, 0x39, 0x85, 0x5f, 0xd2, 0x45, 0xe1, 0x58, 0xb4, 0x28, 0x07, 0x03, 0xd1, 0xd2, 0x13, 0x15, 0xab, 0x75, 0x3e, 0x9f, 0xcf, 0x85, 0x5c, 0x2e, 0xc0, 0x84, 0x50, 0x6a, 0xcc, 0x84, 0xef, 0x28, 0x70, 0x09, 0xa8, 0xd6, 0xd9, 0x03, 0x26, 0x93, 0xdf, 0xa6, 0xd5, 0xda, 0xfc, 0x26, 0x53, 0xc0, 0xae, 0x03, 0xdb, 0xb5, 0x36, 0xf8, 0x24, 0x80, 0x9e, 0x70, 0xff, 0x8e, 0x1e, 0x21, 0x2b, 0x47, 0x5f, 0x66, 0x24, 0xa6, 0x74, 0x9b, 0x46, 0x63, + 0x4b, 0x37, 0x25, 0x05, 0xec, 0x5a, 0xad, 0x3d, 0x90, 0x34, 0xe1, 0x3b, 0x18, 0xd8, 0x82, 0xc6, 0xd2, 0x0e, 0xc7, 0xf2, 0x63, 0xc1, 0xaf, 0xe0, 0x58, 0x5c, 0x50, 0x23, 0x59, 0x72, 0xd2, 0x8a, 0xa2, 0xa9, 0xf0, 0xc8, 0x38, 0x33, 0x0d, 0xb0, 0x2b, 0x24, 0x49, 0x90, 0x23, 0x02, 0xdc, 0x6d, 0x11, 0x23, 0x84, 0x5c, 0x6e, 0x00, 0x23, 0xcb, 0x13, 0x7e, 0xc4, 0x8f, 0x91, 0x93, 0x04, 0xf7, 0x3b, 0xe8, 0x43, 0x11, 0x9e, 0xc1, 0xf4, 0x9e, 0x68, 0x92, 0xc7, 0x0d, 0x88, 0x60, 0xa6, 0x3b, 0xdf, 0x93, 0xef, 0x72, 0x18, 0xe1, 0x39, 0xe6, 0x31, 0x8d, 0x12, 0x5d, 0xba, 0x76, 0x42, 0xec, 0x66, 0xb7, 0x93, 0xc2, 0x52, 0x85, 0xc1, 0x18, 0xe6, 0xdc, 0xc0, 0xa8, 0x09, 0x05, 0xde, 0x41, 0xc3, 0x4d, 0x18, 0x3e, 0x08, 0x3f, 0xf7, 0xd1, 0x0d, 0x32, 0xa3, 0x70, 0x83, 0x18, 0xfe, 0xb7, 0x51, + 0x68, 0x94, 0xed, 0xd8, 0x98, 0x38, 0x0d, 0xf0, 0x5f, 0x66, 0xc7, 0xc4, 0x71, 0xd7, 0x03, 0xa9, 0x58, 0xb0, 0x4d, 0x2c, 0x93, 0x89, 0xb7, 0x31, 0x12, 0xf6, 0xd8, 0xc4, 0x9f, 0xe3, 0x6b, 0xfb, 0x27, 0xbc, 0xb6, 0xd9, 0x44, 0x1b, 0x37, 0x11, 0x26, 0x01, 0x5c, 0x40, 0xe4, 0xed, 0x06, 0x98, 0x91, 0x09, 0xcb, 0x9b, 0x92, 0xf0, 0x53, 0xe2, 0x0a, 0xf7, 0xe1, 0x15, 0x86, 0x53, 0xa0, 0x4c, 0xf3, 0x65, 0x06, 0x7c, 0xd9, 0x69, 0xd9, 0x68, 0x9d, 0x85, 0x28, 0x00, 0xc3, 0xe5, 0xd6, 0x19, 0xcd, 0x02, 0xd6, 0x7b, 0xae, 0x64, 0xb5, 0xd9, 0x3a, 0x78, 0x56, 0x6e, 0x5b, 0x61, 0xb4, 0x2a, 0x3f, 0xbb, 0xfc, 0x92, 0xff, 0xc0, 0x60, 0xfa, 0x52, 0x8e, 0xbc, 0x7a, 0xc1, 0x85, 0xb7, 0xd8, 0x7b, 0xc0, 0x46, 0xe2, 0x00, 0x61, 0x20, 0xfa, 0x31, 0xbe, 0xee, 0xa4, 0x0c, 0x2f, 0xbc, 0x05, 0xfd, + 0x4b, 0x83, 0x18, 0xe8, 0xce, 0xc0, 0xfb, 0x83, 0x10, 0xf3, 0xc7, 0xb2, 0xf5, 0x0d, 0x93, 0x38, 0xae, 0x3e, 0x05, 0xf7, 0x35, 0x4e, 0xc2, 0x17, 0x2b, 0x00, 0x4f, 0x7b, 0xec, 0xf7, 0x9e, 0xe7, 0x51, 0x84, 0x68, 0x15, 0x23, 0x49, 0x4a, 0x0f, 0x39, 0x23, 0x53, 0x04, 0x5c, 0xfb, 0xd9, 0xbf, 0xfe, 0x72, 0x72, 0x62, 0xb8, 0xe3, 0x03, 0x2f, 0x71, 0x61, 0xd4, 0x5a, 0x92, 0x14, 0x8a, 0xce, 0x26, 0xe4, 0x6f, 0x71, 0x1a, 0xf6, 0x71, 0x25, 0x71, 0xd7, 0xff, 0x0f, 0x7d, 0xcc, 0xb3, 0x4f, 0xee, 0xe3, 0x0f, 0xaf, 0x03, 0x59, 0x13, 0xe3, 0x56, 0xdf, 0xf5, 0xda, 0x84, 0x3e, 0x12, 0x87, 0xd8, 0x7b, 0xc8, 0x16, 0xe2, 0x16, 0x42, 0x47, 0x94, 0xc3, 0x6e, 0x51, 0x18, 0x8d, 0xe4, 0xc0, 0xee, 0x62, 0xf0, 0xc7, 0x11, 0x44, 0x51, 0x30, 0x3a, 0x6c, 0x98, 0xc2, 0xee, 0x44, 0xf0, 0xb4, 0x8c, + 0xc4, 0xbc, 0x79, 0x62, 0xcf, 0x7b, 0x9e, 0x73, 0xdb, 0x9d, 0x2a, 0x04, 0x80, 0x41, 0x33, 0x65, 0xc0, 0xb7, 0x50, 0x86, 0x98, 0x19, 0x28, 0xef, 0xd0, 0xd1, 0x60, 0xd4, 0x96, 0x25, 0x97, 0x99, 0x05, 0xca, 0x8c, 0xc6, 0xb4, 0x86, 0x85, 0x51, 0xeb, 0x2d, 0x6f, 0x34, 0x75, 0x2a, 0x14, 0x49, 0x2d, 0x5a, 0x4d, 0x5e, 0xf7, 0xaa, 0xb2, 0xb1, 0x3e, 0x44, 0x60, 0x1f, 0xea, 0x4e, 0x4a, 0xd1, 0xea, 0xf2, 0x7d, 0xd0, 0xa0, 0x2c, 0x8a, 0xd4, 0x08, 0xda, 0x95, 0x63, 0xb3, 0x62, 0x80, 0x0f, 0x01, 0xe7, 0xdf, 0x3c, 0x42, 0x8c, 0xfd, 0xd0, 0x13, 0x45, 0xce, 0x29, 0x3a, 0x42, 0x07, 0x3b, 0x83, 0x62, 0x34, 0xb8, 0x27, 0x75, 0xa5, 0x04, 0x80, 0xda, 0x89, 0x7d, 0x89, 0x0c, 0x25, 0xf6, 0xa5, 0x1f, 0xc9, 0xc7, 0xec, 0x41, 0xba, 0x8f, 0x96, 0xc0, 0xb3, 0x13, 0x25, 0x4e, 0x72, 0x02, 0x8f, + 0x36, 0x1d, 0x48, 0x84, 0xd1, 0x1c, 0x52, 0x2a, 0x49, 0x02, 0x02, 0xa9, 0x18, 0xc1, 0xf5, 0x91, 0x26, 0x0c, 0x9f, 0x8a, 0x26, 0x3c, 0xe5, 0xb1, 0x8f, 0xc1, 0x78, 0x60, 0x2e, 0x29, 0x21, 0x60, 0xa4, 0x02, 0x94, 0xb3, 0x8e, 0x06, 0x28, 0x1e, 0xc7, 0x3c, 0x42, 0x22, 0x11, 0xf6, 0xa2, 0x98, 0x5e, 0x5c, 0x6c, 0x04, 0xc4, 0x67, 0x86, 0x01, 0x0e, 0xd1, 0x75, 0xf1, 0x2a, 0xd8, 0xf5, 0x12, 0xd5, 0x23, 0x26, 0x54, 0xeb, 0x89, 0x6a, 0x4a, 0x8b, 0xc3, 0xb9, 0xd9, 0x41, 0x97, 0xcf, 0xad, 0xd6, 0xb8, 0xf4, 0x2e, 0x1c, 0x20, 0x8a, 0x71, 0x72, 0x90, 0xda, 0x31, 0xbf, 0x46, 0xe4, 0xa4, 0x9c, 0x37, 0xe6, 0xbb, 0x8b, 0x43, 0xaf, 0xe1, 0xf0, 0x47, 0x89, 0x11, 0x9a, 0xc3, 0x11, 0xba, 0xef, 0x76, 0x4d, 0xcd, 0xf0, 0x3d, 0x0b, 0xae, 0x39, 0x51, 0xeb, 0x13, 0x25, 0x19, 0x34, 0x81, 0x8a, + 0x39, 0x55, 0x8b, 0x47, 0x8c, 0xec, 0x03, 0x60, 0x95, 0x6d, 0x5a, 0x43, 0x67, 0x6e, 0xff, 0x81, 0x85, 0x91, 0x92, 0x95, 0x0f, 0x0e, 0x0e, 0x5c, 0x1f, 0x96, 0xc8, 0x7c, 0x6b, 0x76, 0xb0, 0x2f, 0x65, 0xae, 0xac, 0xdc, 0x75, 0x00, 0x05, 0xeb, 0x6f, 0xdb, 0x18, 0x6e, 0x2b, 0x1b, 0xac, 0xf5, 0x95, 0x86, 0x9b, 0x15, 0xc6, 0x64, 0x7b, 0x72, 0x5d, 0xe9, 0xa6, 0xd3, 0x29, 0x81, 0x14, 0x45, 0xe1, 0xc8, 0x63, 0x2b, 0x06, 0x8e, 0x6e, 0xae, 0x2d, 0x2b, 0x28, 0xf3, 0x9e, 0xcf, 0x26, 0x3f, 0x09, 0x67, 0x3e, 0x7a, 0x4f, 0xeb, 0xaa, 0x5a, 0x7b, 0x45, 0x29, 0xd2, 0x51, 0x6a, 0xd9, 0xdd, 0xa2, 0x79, 0x82, 0x87, 0x08, 0x2f, 0xe3, 0x21, 0x37, 0x13, 0x67, 0x81, 0x80, 0x99, 0x4f, 0x12, 0x84, 0xe8, 0x69, 0x82, 0x3c, 0x7e, 0xf6, 0x78, 0x28, 0x9d, 0x18, 0x5f, 0xc6, 0x4b, 0x7e, 0x9d, 0x58, + 0x06, 0x4c, 0x55, 0xc6, 0x4f, 0x3e, 0x71, 0xd9, 0x32, 0x01, 0xf2, 0x85, 0xcb, 0x96, 0xc9, 0x24, 0x6f, 0xbe, 0x6c, 0x7f, 0xd2, 0xc9, 0xb7, 0x2e, 0xdb, 0x8e, 0x8f, 0x3c, 0x72, 0xd9, 0x76, 0xb2, 0xc8, 0x8f, 0x2e, 0xdb, 0x4e, 0x1a, 0xf9, 0x78, 0x62, 0x19, 0x8a, 0x2f, 0x03, 0xc9, 0x43, 0xac, 0x0c, 0xc2, 0xa8, 0xb5, 0xc3, 0xcd, 0x12, 0x8d, 0x96, 0x14, 0x70, 0x19, 0x29, 0x05, 0x34, 0x49, 0x0b, 0x48, 0x64, 0x90, 0xa3, 0x18, 0x84, 0x5a, 0x63, 0x08, 0x91, 0x90, 0x81, 0x7a, 0xbe, 0x90, 0xa0, 0x05, 0xc2, 0xb1, 0xe4, 0xdd, 0x70, 0x1f, 0x79, 0x9c, 0x01, 0x9f, 0x0f, 0xa1, 0xd8, 0x50, 0x98, 0x37, 0x6d, 0xae, 0xa6, 0x94, 0x74, 0x85, 0x72, 0xe8, 0x14, 0x12, 0x5f, 0xc9, 0x91, 0xb4, 0xd3, 0xe1, 0xca, 0x24, 0xc9, 0x89, 0x68, 0x36, 0x26, 0x22, 0x48, 0xa4, 0xfa, 0x58, 0x43, 0xe7, 0x0c, + 0x5b, 0xba, 0x98, 0xf2, 0x0d, 0xf7, 0x56, 0x6d, 0xfd, 0xf5, 0xaf, 0xfc, 0x11, 0x88, 0x6f, 0xba, 0x09, 0x88, 0xff, 0xf8, 0xca, 0xf5, 0xf5, 0xe8, 0x1b, 0xfb, 0xaf, 0x9b, 0x6e, 0x62, 0xff, 0x85, 0xbe, 0xcd, 0xac, 0xdc, 0xf8, 0xc4, 0xbb, 0x5f, 0xee, 0xd8, 0xf1, 0xe5, 0xbb, 0x4f, 0x6c, 0xac, 0x4c, 0xfc, 0x4c, 0xfd, 0x2a, 0x25, 0x2f, 0x1a, 0xec, 0xab, 0xcf, 0xc8, 0x68, 0xbf, 0xa6, 0xbe, 0x7a, 0xa4, 0x25, 0x5d, 0x65, 0xf5, 0x9b, 0xef, 0x55, 0x17, 0x38, 0x1e, 0x9c, 0x57, 0x5b, 0x90, 0x1a, 0x72, 0x6a, 0xeb, 0x8b, 0x0a, 0x1a, 0xc8, 0x3b, 0x50, 0xa3, 0xaf, 0xa2, 0x46, 0x5f, 0x4d, 0x68, 0x94, 0x7f, 0xe1, 0xf8, 0x46, 0x7f, 0xce, 0x7d, 0xfe, 0xf9, 0x97, 0xbf, 0xf5, 0x59, 0x1d, 0x35, 0x4b, 0x6b, 0x2b, 0xae, 0xe9, 0xce, 0xcd, 0x6a, 0x5f, 0x5b, 0x13, 0xec, 0xee, 0xe8, 0xc8, 0x60, + 0xbf, 0x49, 0x4d, 0x26, 0x1f, 0xad, 0x6d, 0x30, 0xa6, 0x45, 0xec, 0x55, 0x4d, 0x2d, 0xc4, 0x84, 0xb9, 0xf7, 0x50, 0x0f, 0x10, 0x67, 0xd1, 0xfe, 0x03, 0xd7, 0xc4, 0xd7, 0x40, 0x15, 0x5b, 0xcb, 0xa7, 0xc9, 0x29, 0xd7, 0xcb, 0x4b, 0xbb, 0xf8, 0x3a, 0x6f, 0x27, 0xd6, 0xe1, 0xca, 0x3e, 0x0d, 0xa6, 0xde, 0xbb, 0xd4, 0xe3, 0x53, 0xbd, 0xe7, 0x92, 0x75, 0x02, 0xd4, 0x89, 0xab, 0xae, 0x93, 0x39, 0xf5, 0x7b, 0x2e, 0x39, 0x9e, 0x74, 0xea, 0xd1, 0xab, 0x7e, 0x8f, 0x8f, 0x7a, 0xf2, 0xaa, 0xdf, 0x93, 0x45, 0xbd, 0x76, 0xd5, 0xef, 0x49, 0xa3, 0x0e, 0x4f, 0x55, 0x87, 0x3b, 0x23, 0x4f, 0x53, 0xb1, 0x3a, 0x70, 0xff, 0x4f, 0x3c, 0x2b, 0xc3, 0xc4, 0xdd, 0x51, 0xe9, 0xdc, 0x99, 0xa4, 0x48, 0x0a, 0x8f, 0x8c, 0x04, 0x29, 0x25, 0xc8, 0x52, 0x96, 0x4b, 0x88, 0xa4, 0x12, 0xa9, 0x48, 0x32, + 0x76, 0x72, 0xa4, 0x22, 0x46, 0xda, 0x45, 0x48, 0xe0, 0xb9, 0x91, 0xd0, 0x03, 0x32, 0x31, 0xc9, 0x08, 0x01, 0xf2, 0x51, 0x4f, 0x3c, 0x42, 0x96, 0x68, 0xde, 0xa4, 0x6a, 0x0c, 0xae, 0x38, 0x10, 0xaf, 0x08, 0xc4, 0xa8, 0x9e, 0x78, 0x5c, 0xbd, 0x9e, 0xa8, 0xa1, 0xa3, 0x63, 0xc5, 0xb2, 0x85, 0x0b, 0x3a, 0xe6, 0x75, 0xcc, 0x83, 0x24, 0x97, 0x3b, 0x87, 0xf2, 0xff, 0xc4, 0x39, 0xc4, 0xd1, 0xd3, 0x23, 0x42, 0x94, 0xef, 0x26, 0x0b, 0x88, 0xc1, 0xbf, 0x7f, 0x2a, 0xe9, 0xdf, 0x5e, 0xec, 0x54, 0xb2, 0x1f, 0x9f, 0xa6, 0x16, 0x49, 0x53, 0xac, 0x16, 0x51, 0x2e, 0x70, 0x92, 0x29, 0xff, 0x1f, 0x9d, 0x51, 0xea, 0x97, 0xe7, 0xd3, 0xc8, 0x3d, 0x99, 0x39, 0xf3, 0x24, 0x62, 0x20, 0x95, 0x95, 0x03, 0x11, 0xfb, 0xb3, 0x84, 0x43, 0x3b, 0xe1, 0xcc, 0x7e, 0x8b, 0xf7, 0x84, 0x1f, 0x04, 0x2e, + 0xb5, 0xf7, 0xa0, 0x10, 0x81, 0x82, 0x6c, 0xcc, 0x60, 0xce, 0x42, 0xe6, 0x9b, 0x14, 0x35, 0x20, 0x71, 0x67, 0xc1, 0x58, 0xc4, 0xa7, 0x44, 0x8f, 0x60, 0x30, 0xe3, 0x2c, 0x73, 0xf6, 0xdb, 0x62, 0x58, 0x92, 0x24, 0x06, 0x2f, 0x7c, 0x4e, 0x87, 0x99, 0x7b, 0xa1, 0x8c, 0x9d, 0x8d, 0x32, 0x20, 0x19, 0x79, 0x2f, 0x37, 0x3e, 0x9d, 0x36, 0x4d, 0xa3, 0xf8, 0x74, 0x63, 0x61, 0x53, 0xd1, 0x9d, 0x99, 0xdb, 0x69, 0x49, 0xe2, 0x54, 0x07, 0x81, 0x64, 0xb2, 0x3b, 0x14, 0x0a, 0x69, 0x34, 0x11, 0x7d, 0xcd, 0x07, 0xbf, 0x22, 0x2b, 0xaa, 0x86, 0xa7, 0x07, 0xd2, 0x9b, 0x86, 0x2a, 0xaa, 0x96, 0x37, 0xfb, 0x3d, 0xf5, 0x4b, 0x8d, 0x6a, 0xbb, 0x32, 0xb8, 0xa6, 0xb2, 0xe3, 0xee, 0x35, 0x95, 0xb5, 0x3b, 0x9e, 0x5b, 0xb1, 0xfc, 0xe9, 0x6b, 0x2b, 0xdb, 0x66, 0x82, 0x85, 0xca, 0xb0, 0xf3, 0xc9, + 0xdb, 0x32, 0x9a, 0x87, 0xca, 0xaa, 0x96, 0x35, 0xfa, 0xb2, 0x5a, 0x96, 0x14, 0x56, 0x2c, 0x6f, 0x0e, 0x90, 0x7f, 0x37, 0xa4, 0xe9, 0x52, 0x1d, 0xf9, 0x43, 0xf7, 0x2f, 0x1a, 0x78, 0x62, 0x4b, 0x5d, 0xf5, 0xb6, 0xe7, 0x87, 0xe7, 0xbc, 0xb7, 0x1e, 0x7c, 0xa4, 0xb3, 0x70, 0x7a, 0x60, 0x1f, 0x1c, 0x47, 0x80, 0x79, 0x1c, 0xeb, 0x0a, 0x35, 0x44, 0x59, 0xb4, 0x38, 0x1f, 0xc7, 0x99, 0x8a, 0x39, 0x7a, 0x09, 0x01, 0x6f, 0xb3, 0x5d, 0x8a, 0x3d, 0xd9, 0x30, 0x1e, 0x7d, 0x88, 0x6e, 0xd4, 0xeb, 0x00, 0x51, 0x5e, 0x96, 0x9d, 0x95, 0x6a, 0xd5, 0x79, 0xf5, 0x5e, 0x6e, 0x54, 0xa2, 0x29, 0x46, 0xa5, 0x4d, 0xd0, 0x81, 0xd1, 0x73, 0x6d, 0x0a, 0x48, 0x1c, 0x5e, 0xa2, 0x8b, 0x81, 0x80, 0xac, 0xac, 0x5c, 0x52, 0xef, 0xc9, 0x6c, 0x5e, 0x54, 0x58, 0xb4, 0xa8, 0x39, 0xc3, 0x53, 0xbf, 0xa4, + 0xb2, 0xb4, 0xbf, 0x34, 0xd5, 0x52, 0x34, 0xab, 0x72, 0xda, 0xec, 0x22, 0x4b, 0x6a, 0x69, 0xff, 0x5d, 0xbb, 0xf6, 0xd4, 0x6c, 0x3b, 0x31, 0x34, 0x74, 0x62, 0x5b, 0xcd, 0x9e, 0x5d, 0x2b, 0x86, 0xcb, 0x56, 0x3d, 0x30, 0x6f, 0xde, 0x03, 0xab, 0xca, 0x86, 0xf7, 0x78, 0xaa, 0xe6, 0x14, 0x54, 0x2f, 0x28, 0x4f, 0x4d, 0x2d, 0x5f, 0x50, 0x5d, 0x30, 0xa7, 0xca, 0x43, 0xef, 0x71, 0x47, 0xdb, 0x83, 0xc8, 0x15, 0xc2, 0x5f, 0xd5, 0x93, 0x15, 0x6c, 0x8f, 0xba, 0x47, 0x7f, 0xe9, 0x7b, 0xe6, 0x86, 0x59, 0x07, 0x97, 0x16, 0x16, 0x2e, 0x3d, 0x38, 0xeb, 0x86, 0x67, 0x7c, 0xfe, 0x47, 0x56, 0x4d, 0xdf, 0x33, 0x58, 0x58, 0x38, 0xb8, 0x67, 0xfa, 0xaa, 0x47, 0x30, 0x5d, 0xe8, 0xb8, 0xf0, 0x37, 0xe6, 0x5f, 0xcc, 0x31, 0x5e, 0x67, 0xaa, 0x82, 0xb2, 0xf6, 0x11, 0xce, 0x30, 0x85, 0x1c, 0xbd, + 0x08, 0x21, 0xb2, 0x89, 0x0a, 0x09, 0x91, 0x58, 0x28, 0xe2, 0x4e, 0x2e, 0x72, 0x2d, 0x58, 0x8a, 0x22, 0x09, 0x31, 0xbd, 0x02, 0x2e, 0x88, 0x0c, 0xef, 0xc2, 0x92, 0x7e, 0xa9, 0xe2, 0xb0, 0x20, 0xaa, 0x83, 0xcd, 0x08, 0x43, 0x02, 0x9c, 0xc3, 0x66, 0x62, 0x69, 0xb4, 0x73, 0x62, 0xb4, 0x61, 0x72, 0x0d, 0xe4, 0xf1, 0xda, 0xd9, 0xee, 0xf3, 0xb9, 0x9d, 0x1e, 0xa4, 0x8a, 0xa1, 0xb8, 0x69, 0x13, 0x55, 0x31, 0x61, 0xee, 0x78, 0xd3, 0x43, 0x42, 0xe8, 0x8b, 0xcb, 0x94, 0x24, 0x7f, 0x95, 0xd7, 0xbd, 0x26, 0x5a, 0xb5, 0xa6, 0x33, 0x3b, 0xbb, 0x73, 0x4d, 0x55, 0xd5, 0x9a, 0xae, 0xec, 0x83, 0x96, 0xcc, 0x52, 0xa7, 0xb3, 0x14, 0x01, 0x1e, 0xd1, 0xbf, 0x99, 0x16, 0x72, 0xaf, 0x34, 0x16, 0xfa, 0xe2, 0x16, 0xa6, 0x6e, 0xca, 0xe2, 0xae, 0x12, 0x54, 0xbc, 0xc4, 0x85, 0x8a, 0xd3, 0xe5, + 0xed, 0x3b, 0x7b, 0xb3, 0xb3, 0x7b, 0x77, 0xb6, 0x77, 0xee, 0xea, 0xcf, 0xce, 0xee, 0xdf, 0xd5, 0x19, 0xed, 0x89, 0x24, 0x25, 0x45, 0x7a, 0xa2, 0xa5, 0x5d, 0x61, 0x93, 0x29, 0xdc, 0x05, 0xd6, 0x73, 0x11, 0x2f, 0x40, 0x5b, 0x42, 0xc1, 0x60, 0x10, 0x15, 0xec, 0x46, 0x05, 0xbb, 0xa3, 0x65, 0x9d, 0xa8, 0x60, 0x27, 0xb6, 0x5b, 0x6f, 0x64, 0x8f, 0x81, 0xed, 0xb4, 0x08, 0x67, 0x6b, 0x42, 0x59, 0x86, 0x79, 0x45, 0x69, 0x79, 0x03, 0x0e, 0xd6, 0x80, 0xe6, 0x78, 0x11, 0xe7, 0x64, 0xa9, 0xd3, 0x20, 0x9c, 0x9e, 0x81, 0x81, 0xe7, 0x56, 0xab, 0x46, 0xd6, 0xe8, 0x10, 0x72, 0x88, 0xa5, 0x12, 0xc8, 0x23, 0x9a, 0x87, 0x87, 0x21, 0x49, 0x16, 0x5c, 0x07, 0xff, 0xd0, 0x1f, 0xeb, 0x9c, 0xc1, 0x64, 0x4b, 0xd0, 0xa5, 0xd7, 0xbb, 0x82, 0x96, 0xe4, 0xa0, 0x53, 0xc7, 0x1e, 0x23, 0x2d, 0x19, + 0x9d, 0x5d, 0xfd, 0xa1, 0x50, 0x7f, 0x57, 0x67, 0xc6, 0xe8, 0xef, 0xe9, 0x5f, 0xa3, 0xa7, 0xa8, 0x54, 0x32, 0x57, 0x0a, 0xd3, 0x9a, 0x6a, 0x6a, 0x27, 0xd9, 0x24, 0x58, 0x44, 0x09, 0xc1, 0xa3, 0x50, 0x6c, 0xdf, 0x49, 0x88, 0x21, 0x97, 0x7a, 0x81, 0xda, 0xc9, 0x91, 0x15, 0xf8, 0xfb, 0x34, 0x6a, 0x13, 0x59, 0x8b, 0x7f, 0x7f, 0x6c, 0xca, 0xdf, 0xe7, 0xc1, 0xfa, 0x11, 0xc1, 0x3c, 0xf8, 0xfb, 0x23, 0x53, 0xfe, 0xde, 0x0e, 0x37, 0xe6, 0x2a, 0xe6, 0x10, 0xe1, 0x05, 0xc7, 0xc9, 0x39, 0xf0, 0x91, 0x17, 0x3c, 0x72, 0xe1, 0x1f, 0xe4, 0x69, 0x20, 0x04, 0x47, 0x29, 0x04, 0x13, 0x12, 0x43, 0x6e, 0xf8, 0x02, 0x79, 0x3a, 0x5e, 0x1e, 0xce, 0x12, 0xd3, 0xc5, 0x97, 0xcf, 0xe7, 0xcb, 0xff, 0x13, 0x97, 0x7f, 0x94, 0x92, 0x4d, 0x51, 0xbe, 0x03, 0x96, 0xdf, 0xc1, 0x3c, 0x84, 0xcb, 0xff, + 0x3f, 0xea, 0xde, 0x03, 0x3e, 0xae, 0xe2, 0xea, 0x1b, 0xbe, 0x33, 0xb7, 0x6d, 0x97, 0xb6, 0xef, 0x6a, 0xb5, 0xab, 0xed, 0xbb, 0xea, 0x65, 0xb5, 0x5a, 0x75, 0xad, 0x7a, 0xb7, 0x2d, 0xb9, 0xca, 0x96, 0x71, 0x91, 0x2c, 0x37, 0xd9, 0x96, 0x85, 0x71, 0xa1, 0xd8, 0xd8, 0x18, 0x07, 0x30, 0xc5, 0x04, 0x43, 0x4c, 0x09, 0x21, 0x81, 0x90, 0xd0, 0x42, 0xc7, 0xa1, 0x85, 0x84, 0x38, 0x84, 0x04, 0x42, 0x48, 0x9e, 0x40, 0x08, 0x49, 0x48, 0x20, 0x10, 0x9e, 0x02, 0x21, 0x05, 0x02, 0x09, 0xd6, 0xd5, 0x77, 0x66, 0xee, 0xee, 0xaa, 0x78, 0x6d, 0xcb, 0x79, 0xf2, 0x7e, 0xbf, 0xf7, 0xb5, 0x2d, 0xaf, 0xf6, 0x4e, 0xb9, 0x33, 0x67, 0x66, 0xce, 0x9c, 0x33, 0x73, 0xce, 0xff, 0xec, 0xa4, 0xf9, 0xef, 0x61, 0x4c, 0x89, 0xfa, 0x3d, 0xb3, 0xf3, 0x13, 0x5f, 0x08, 0xe9, 0x76, 0xb6, 0x0c, 0xe8, 0x1f, + 0x62, 0x6a, 0xc8, 0xbd, 0x2f, 0x83, 0x78, 0x96, 0x47, 0x24, 0xb6, 0x07, 0xe6, 0x59, 0xbc, 0x15, 0xe6, 0x2a, 0x65, 0x0f, 0x5b, 0x53, 0x8e, 0x87, 0x63, 0x4c, 0x4f, 0x18, 0xe4, 0xca, 0x6c, 0x7a, 0x34, 0xc4, 0xa7, 0x94, 0x0f, 0xae, 0x01, 0x45, 0xac, 0x16, 0x1d, 0x37, 0x05, 0x2b, 0x64, 0x45, 0xb3, 0x86, 0x86, 0x2d, 0x73, 0xf7, 0x2c, 0xdf, 0x58, 0xd7, 0x77, 0xe5, 0x50, 0x65, 0x61, 0xcb, 0x82, 0x8e, 0x32, 0xde, 0x74, 0xb9, 0x96, 0xcb, 0x6d, 0xe9, 0x6b, 0x2b, 0x71, 0x57, 0x75, 0x17, 0xd4, 0x2e, 0xaa, 0x09, 0x6a, 0x1d, 0x19, 0xd2, 0xc6, 0xd9, 0xc3, 0xf6, 0x73, 0x67, 0x34, 0x64, 0x2d, 0x58, 0xb8, 0xbb, 0xbb, 0x67, 0x53, 0x4f, 0x45, 0x71, 0x89, 0xe7, 0xd8, 0xdd, 0x25, 0x4d, 0xe5, 0x45, 0x35, 0x0b, 0xaa, 0x73, 0x9b, 0x8b, 0xec, 0x4e, 0xaf, 0xd3, 0xce, 0xb1, 0x53, 0xd9, 0x89, + 0x13, 0x6d, 0x09, 0xbd, 0x17, 0x81, 0xb5, 0xaf, 0x78, 0x7d, 0xc6, 0xda, 0xdf, 0xc2, 0xec, 0x67, 0x9e, 0x92, 0xd7, 0x7f, 0x6a, 0x41, 0xab, 0x91, 0xa8, 0x41, 0xb3, 0x97, 0xb4, 0x16, 0x11, 0x16, 0xa0, 0x02, 0x51, 0x60, 0x1a, 0x0f, 0x28, 0x3e, 0x63, 0x11, 0xba, 0xa6, 0x49, 0x39, 0x61, 0x85, 0x0a, 0x25, 0xf8, 0x40, 0x24, 0x4d, 0x89, 0x34, 0x9c, 0x60, 0x46, 0x29, 0x62, 0x4d, 0xba, 0xf7, 0xe2, 0x1d, 0xe3, 0x9b, 0x36, 0xac, 0x3e, 0x6f, 0x1a, 0x4f, 0xd0, 0xfd, 0x5b, 0x79, 0x42, 0xe0, 0xff, 0x77, 0x9e, 0xc1, 0xee, 0x3a, 0x6b, 0x8d, 0xff, 0x3e, 0xb6, 0xd2, 0x70, 0x76, 0xb6, 0xc3, 0xa0, 0x49, 0x49, 0xba, 0x0f, 0xb4, 0x70, 0x22, 0x1f, 0x08, 0x8f, 0xc2, 0x0e, 0x5f, 0x92, 0xcf, 0x46, 0xf4, 0x3e, 0xcc, 0x71, 0xfc, 0x89, 0xcf, 0xde, 0x27, 0xfa, 0xd7, 0x7a, 0xb6, 0x08, 0x4f, 0xf0, + 0xcf, 0x30, 0x0a, 0x26, 0x87, 0x89, 0xc0, 0x2e, 0xba, 0x2a, 0x3e, 0xa8, 0x27, 0xd8, 0x34, 0x5d, 0x04, 0x5c, 0x5a, 0xe0, 0x58, 0x61, 0x4c, 0x9d, 0x30, 0x8d, 0x18, 0x22, 0x0e, 0x8b, 0x04, 0x9c, 0x0b, 0x24, 0x41, 0x81, 0x51, 0x0a, 0xca, 0x55, 0x14, 0x27, 0x91, 0x40, 0x2b, 0xad, 0x13, 0x7b, 0xa2, 0xe5, 0x1e, 0x37, 0xc8, 0x35, 0x4c, 0x43, 0x5d, 0x79, 0x7b, 0xb4, 0x3d, 0x3f, 0xd7, 0x1d, 0xf1, 0x44, 0x08, 0xc0, 0xba, 0xc5, 0xa4, 0xcc, 0x51, 0xe5, 0x88, 0xa0, 0xbc, 0x21, 0x85, 0x8c, 0x60, 0x5b, 0x41, 0xaf, 0x35, 0xcd, 0xb1, 0x84, 0x19, 0x30, 0x3d, 0x6c, 0x2b, 0xa6, 0xf8, 0xb5, 0x66, 0x9f, 0xde, 0x38, 0x7b, 0x13, 0x9e, 0x1d, 0x61, 0xee, 0xfe, 0xfc, 0x40, 0x28, 0xb7, 0xb0, 0x30, 0xbf, 0x36, 0xa0, 0xcf, 0x0b, 0x19, 0xc4, 0x05, 0xbd, 0x46, 0x55, 0x3d, 0xaf, 0x55, 0x3b, 0xfb, + 0x2e, 0xaf, 0x5a, 0xd3, 0x11, 0x0e, 0x77, 0xac, 0xa9, 0xaa, 0x5c, 0xd3, 0x9e, 0x9b, 0xdb, 0xbe, 0x26, 0xd6, 0xb6, 0x67, 0x20, 0x12, 0x19, 0xd8, 0xd3, 0xd6, 0xba, 0x67, 0x79, 0x24, 0xb2, 0x7c, 0x0f, 0x6c, 0xac, 0x59, 0x59, 0xee, 0x3d, 0x4f, 0xe8, 0x5d, 0xb9, 0xf6, 0x50, 0x8d, 0x4e, 0xa5, 0xbf, 0xc2, 0xb0, 0x4d, 0xc5, 0x1f, 0x41, 0x07, 0xa5, 0x50, 0x5e, 0xe7, 0xda, 0xca, 0xaa, 0xb5, 0x5d, 0x79, 0x79, 0x5d, 0x6b, 0xab, 0x2a, 0xd7, 0x76, 0xe6, 0xe1, 0x45, 0x91, 0x81, 0x5d, 0x2d, 0x2d, 0x20, 0xbf, 0x95, 0x2f, 0x23, 0x9f, 0x03, 0x11, 0x99, 0xe7, 0x5c, 0x84, 0xd7, 0xe2, 0x6d, 0xac, 0x82, 0x60, 0x58, 0xa2, 0x41, 0x59, 0xd7, 0x1d, 0x4c, 0xca, 0xf3, 0x8f, 0xe2, 0x11, 0xf6, 0x35, 0xfc, 0x31, 0x2b, 0xa0, 0x6e, 0x66, 0x76, 0xda, 0x3e, 0x3c, 0xc2, 0x7f, 0x1b, 0x7f, 0x0c, 0xe5, + 0x7a, 0x4e, 0x29, 0x37, 0x8a, 0xca, 0xd1, 0x8b, 0xdc, 0x0a, 0xe0, 0x5b, 0x4d, 0x20, 0x68, 0x3f, 0x26, 0xa7, 0x3e, 0x06, 0xa9, 0x90, 0x76, 0x18, 0xd2, 0xde, 0xa7, 0x69, 0xcd, 0x8c, 0x7e, 0x56, 0x1a, 0x94, 0xc3, 0x9f, 0x01, 0xcf, 0x0a, 0xc9, 0xe5, 0x48, 0x79, 0x54, 0x0d, 0x79, 0x32, 0x93, 0x79, 0x88, 0xb6, 0x31, 0x55, 0x0f, 0x6b, 0xa5, 0x79, 0x69, 0x3d, 0xa4, 0x3e, 0xd4, 0x78, 0x9a, 0xbc, 0x3b, 0x71, 0x06, 0x7a, 0x8c, 0x9d, 0x0f, 0x6d, 0x9d, 0xaf, 0x9d, 0xdd, 0xd6, 0xef, 0xa1, 0x7c, 0xf6, 0x87, 0x5c, 0x39, 0xf4, 0x71, 0x1e, 0x73, 0x2e, 0x69, 0xbb, 0xd1, 0xdf, 0x39, 0x37, 0xbb, 0x06, 0xd2, 0x06, 0xf9, 0x19, 0x69, 0xc0, 0x97, 0x6f, 0x85, 0x49, 0xfa, 0x35, 0xfe, 0x5e, 0x90, 0x62, 0xec, 0xc4, 0xcb, 0x93, 0x9a, 0x35, 0x11, 0x11, 0x8e, 0x4d, 0xa2, 0xbf, 0x25, 0xb7, 0xc7, + 0x61, 0xb2, 0x3d, 0x5a, 0x4c, 0x1a, 0x15, 0x01, 0x78, 0x31, 0x90, 0xed, 0x31, 0xa2, 0x37, 0x46, 0x8c, 0xa7, 0xec, 0x8c, 0xdf, 0xcb, 0x3a, 0xe8, 0xf8, 0xdd, 0x6c, 0xe6, 0x8a, 0x6b, 0xd1, 0x26, 0xe9, 0xa6, 0x89, 0x13, 0x7c, 0xf0, 0x54, 0x1e, 0x8a, 0x99, 0x25, 0x93, 0x7f, 0x66, 0x63, 0xd0, 0x06, 0x23, 0x13, 0x25, 0x36, 0x33, 0x51, 0x72, 0xcd, 0xd6, 0x95, 0x3c, 0xbc, 0xe4, 0x51, 0x02, 0xae, 0x92, 0x02, 0x10, 0xe2, 0x41, 0x68, 0xd5, 0x28, 0x96, 0x85, 0x62, 0x7f, 0x96, 0x6d, 0x4a, 0x28, 0x46, 0xb3, 0x94, 0x98, 0x69, 0x60, 0x73, 0x64, 0xcf, 0x98, 0x42, 0xa2, 0x8b, 0xa1, 0x8f, 0x92, 0x1b, 0x72, 0xa2, 0x31, 0xa8, 0xd0, 0xdd, 0xbb, 0x7c, 0x03, 0xdd, 0x31, 0x72, 0x3a, 0x76, 0x0d, 0x64, 0x1f, 0x32, 0x35, 0x6d, 0x5f, 0x54, 0xe2, 0xae, 0xec, 0x2e, 0x88, 0xf4, 0xc7, 0x4b, 0x0d, + 0xdc, 0xd7, 0xa0, 0xa1, 0xd9, 0xd3, 0x1b, 0xfe, 0xf9, 0x5b, 0xce, 0xf2, 0xb0, 0xb5, 0xa8, 0x7f, 0x47, 0x6b, 0xd3, 0xfa, 0x35, 0x9b, 0x5a, 0x4f, 0x7c, 0x5a, 0xb6, 0x78, 0xe1, 0xf2, 0x58, 0x5e, 0x53, 0xb1, 0xdd, 0xe2, 0xf6, 0x69, 0x29, 0xcd, 0x57, 0x4e, 0x3a, 0xd1, 0x7f, 0x0a, 0x83, 0x30, 0x8e, 0x0b, 0xc4, 0x0e, 0x99, 0xe6, 0x1d, 0xc9, 0xf1, 0x68, 0x87, 0xb4, 0xff, 0xa0, 0x69, 0x7d, 0x19, 0xb3, 0xd3, 0xbe, 0x37, 0xe9, 0xc5, 0x35, 0x42, 0x27, 0xa4, 0xf5, 0x9f, 0x92, 0xd6, 0x31, 0xe9, 0x45, 0xdf, 0xa7, 0x69, 0x0b, 0xb5, 0xb3, 0xd3, 0x3a, 0xa1, 0x5c, 0x80, 0xa6, 0x2d, 0x52, 0xcc, 0x4e, 0xeb, 0x81, 0xb4, 0x10, 0x4d, 0x5b, 0xac, 0x99, 0x95, 0x36, 0xf9, 0x21, 0x08, 0xf2, 0x04, 0x63, 0x5d, 0x44, 0x0f, 0x21, 0x82, 0xe1, 0x45, 0xf2, 0xaf, 0x9b, 0x3c, 0x22, 0x44, 0xf9, 0xd7, + 0x40, 0xb6, 0x20, 0x92, 0xc3, 0xa5, 0xf2, 0xc9, 0xd3, 0xa5, 0xa4, 0x04, 0x62, 0xd6, 0xc3, 0xbe, 0x7f, 0x94, 0xfa, 0xbd, 0x95, 0xc5, 0x8b, 0x2b, 0xbc, 0x59, 0x02, 0x47, 0xdd, 0x82, 0x13, 0xee, 0xa6, 0x20, 0xe9, 0x13, 0x40, 0x6e, 0xd9, 0xb7, 0x4d, 0x56, 0x82, 0x62, 0x4c, 0x2c, 0xcf, 0x98, 0x97, 0xe7, 0x13, 0x55, 0x76, 0x72, 0xa8, 0x2a, 0xf8, 0x3c, 0xfa, 0x29, 0x3c, 0x51, 0x19, 0xe5, 0xdf, 0x43, 0x70, 0x20, 0x3d, 0xd1, 0x59, 0xc0, 0xa2, 0x66, 0xfe, 0x68, 0xd8, 0x2d, 0x89, 0xf3, 0x2e, 0x5b, 0x1d, 0x6d, 0xba, 0xe0, 0xae, 0x35, 0x6b, 0xee, 0xdc, 0xd9, 0x54, 0xb1, 0xea, 0xe0, 0x3c, 0x49, 0xf4, 0x84, 0xd0, 0x6d, 0xab, 0x8f, 0x6d, 0xaa, 0x5a, 0xfb, 0xd0, 0xdf, 0x8e, 0x5c, 0xff, 0xb7, 0x87, 0xd6, 0x56, 0x6d, 0x3e, 0xb6, 0xfa, 0xd1, 0xad, 0xae, 0x10, 0x7a, 0x41, 0x57, 0x3f, + 0x74, 0xd5, 0xe0, 0xfa, 0xdb, 0x37, 0x55, 0x54, 0x6c, 0xba, 0x7d, 0xfd, 0xe0, 0x55, 0x43, 0xf5, 0x3a, 0xa9, 0x3a, 0xe4, 0xda, 0x7a, 0x97, 0x75, 0xf5, 0x95, 0xf7, 0x0f, 0xed, 0x7d, 0xfd, 0xd6, 0xa5, 0x4b, 0x6f, 0x7d, 0x7d, 0xef, 0xd0, 0xfd, 0x57, 0xae, 0xb6, 0xde, 0x45, 0xfa, 0xb9, 0x1c, 0xf4, 0x97, 0x3c, 0xe8, 0xbb, 0xc0, 0x5d, 0xf0, 0x31, 0xf9, 0x3e, 0x04, 0xdf, 0x23, 0xd0, 0x37, 0x81, 0xdb, 0x29, 0xd2, 0x79, 0x09, 0xfc, 0xf6, 0x59, 0x8a, 0x0b, 0x59, 0x45, 0xb0, 0x32, 0xa7, 0x43, 0x40, 0xea, 0x10, 0xc1, 0x80, 0x54, 0x81, 0xb0, 0xc2, 0xa8, 0x07, 0x32, 0x33, 0xb4, 0xac, 0x5a, 0x3d, 0x92, 0x84, 0x84, 0xe4, 0x38, 0xae, 0x8a, 0xab, 0xaa, 0x20, 0x30, 0xd4, 0xc5, 0x05, 0x79, 0x04, 0x18, 0xd2, 0xe3, 0xb3, 0xe8, 0x8d, 0x1e, 0xbf, 0x3e, 0x53, 0x4f, 0x41, 0x21, 0x59, 0x8f, + 0x6c, 0x6f, 0x4b, 0x2e, 0x7c, 0x32, 0x90, 0x8f, 0xce, 0xd3, 0x98, 0x31, 0x09, 0x0a, 0xe9, 0x0d, 0xc2, 0x7c, 0x35, 0xfa, 0x68, 0x70, 0x0b, 0x19, 0xb1, 0xd6, 0x63, 0x84, 0x8c, 0x22, 0xcb, 0x46, 0xa5, 0x37, 0xea, 0x50, 0x18, 0xfd, 0x20, 0x83, 0x0f, 0xe7, 0x5d, 0x5a, 0x7f, 0xaf, 0x2b, 0x5b, 0x54, 0xfe, 0x57, 0xff, 0x80, 0xce, 0x96, 0x29, 0x72, 0xcd, 0xd2, 0x1b, 0x2d, 0x62, 0x99, 0x7b, 0x69, 0xff, 0x95, 0x39, 0x0e, 0x51, 0xf9, 0x62, 0xfd, 0x1f, 0x74, 0x90, 0xe7, 0x86, 0x56, 0xa1, 0xd8, 0x3d, 0xd0, 0x8f, 0xc2, 0xfd, 0x4b, 0xdd, 0x7e, 0xa1, 0x95, 0xdd, 0x2c, 0xbd, 0x88, 0x5e, 0x5c, 0x98, 0xe3, 0x92, 0x5e, 0x0c, 0x14, 0x34, 0x48, 0x2b, 0xb5, 0x56, 0x97, 0x01, 0xcd, 0x97, 0xbe, 0x11, 0xf6, 0xa2, 0x3b, 0xfd, 0x85, 0x0d, 0xa8, 0xb2, 0xdf, 0xed, 0x42, 0x83, 0xee, 0x80, 0xb4, + 0x0a, 0x7d, 0x15, 0x28, 0xff, 0x0d, 0xd9, 0x0e, 0x64, 0xd2, 0xc2, 0xd9, 0x84, 0x8d, 0x8c, 0x80, 0x36, 0x7d, 0x42, 0xbe, 0xaf, 0x99, 0xb4, 0xb1, 0xef, 0x72, 0xef, 0xc3, 0xf7, 0xcd, 0xbf, 0x26, 0xdf, 0x09, 0x1e, 0xfd, 0x72, 0xfe, 0x3e, 0x2c, 0x20, 0x39, 0x2e, 0x51, 0x13, 0x7c, 0x27, 0x58, 0xe1, 0x5e, 0xa6, 0x2a, 0x5e, 0x91, 0x85, 0x18, 0x25, 0xf1, 0x46, 0x4b, 0xc4, 0xd8, 0x5c, 0x3b, 0x15, 0x33, 0x81, 0xdc, 0x90, 0x27, 0xc3, 0x25, 0x78, 0xdd, 0x26, 0x43, 0x32, 0x58, 0x42, 0xa6, 0x3a, 0x19, 0xa2, 0x40, 0x0e, 0x96, 0x40, 0x4e, 0x9e, 0xe1, 0x2f, 0x9a, 0x1d, 0xd9, 0x96, 0x0d, 0xa0, 0xa0, 0xf4, 0x30, 0xb4, 0xfe, 0xe1, 0x13, 0xf2, 0x07, 0x7c, 0x8d, 0x3c, 0x98, 0xed, 0x53, 0xa1, 0x5c, 0x93, 0x43, 0xa5, 0xc8, 0x32, 0xa2, 0x5c, 0x45, 0xc0, 0xf1, 0x10, 0x3e, 0x7f, 0xdb, 0xd6, + 0x31, 0xe9, 0x4d, 0x14, 0x1c, 0xdb, 0xba, 0x8d, 0xcd, 0xcd, 0xb2, 0x4e, 0xfc, 0x3e, 0xaf, 0xdd, 0xe7, 0x6f, 0xcb, 0xc3, 0x6e, 0xa3, 0x2b, 0x81, 0xc9, 0x00, 0xed, 0xbd, 0xe0, 0xdf, 0x1d, 0xdb, 0xe1, 0x3f, 0xb1, 0x73, 0xe2, 0x0f, 0xa8, 0x4b, 0x7a, 0xe2, 0xb5, 0xef, 0x7f, 0x9f, 0x5d, 0x7b, 0x6a, 0x6c, 0x07, 0xa0, 0xdb, 0x30, 0xbc, 0xf7, 0x3a, 0x90, 0x1b, 0x05, 0xce, 0x54, 0x4c, 0x74, 0xe9, 0x01, 0x58, 0x57, 0x6a, 0x8a, 0xeb, 0xaf, 0x06, 0x49, 0xe0, 0x5c, 0x70, 0xfd, 0x73, 0x5c, 0xd9, 0x32, 0xaa, 0xbf, 0xc9, 0xa8, 0x27, 0x11, 0xb8, 0x4f, 0xc1, 0xf5, 0x4f, 0x81, 0xce, 0xce, 0xc0, 0xf5, 0xe7, 0xac, 0x11, 0xbc, 0xe3, 0xd8, 0x7d, 0xfa, 0x89, 0xbf, 0x60, 0x83, 0xe6, 0x9e, 0xaf, 0x3d, 0xcd, 0xf6, 0x5f, 0xae, 0x5a, 0x70, 0xe8, 0x99, 0x9d, 0x4f, 0xbf, 0x78, 0xec, 0x18, 0xba, + 0x4c, 0x42, 0x0a, 0xed, 0x41, 0x7c, 0xe3, 0xc1, 0x8b, 0x26, 0xd1, 0xb6, 0x03, 0x17, 0x49, 0x37, 0x90, 0xf5, 0x26, 0x89, 0xec, 0xd1, 0x31, 0x74, 0xec, 0xd5, 0x17, 0x09, 0xdd, 0x96, 0x4e, 0xfe, 0x85, 0x3f, 0x8f, 0xfa, 0x08, 0x0f, 0xc6, 0x07, 0x40, 0x44, 0x61, 0xcb, 0x41, 0xf4, 0xc4, 0x5d, 0xa0, 0x8b, 0x10, 0x27, 0xe7, 0x31, 0x06, 0xc4, 0x50, 0x51, 0xa9, 0x18, 0xe3, 0x10, 0xa3, 0x45, 0xb4, 0x1b, 0x20, 0xb4, 0x80, 0x80, 0xaa, 0x14, 0xc4, 0xa1, 0xe4, 0x0d, 0xc4, 0x66, 0x62, 0x30, 0x4c, 0xbd, 0xae, 0x46, 0x55, 0x3d, 0x1d, 0xed, 0x8d, 0xf1, 0x86, 0xfa, 0xea, 0xca, 0x92, 0x62, 0xaf, 0xcf, 0xef, 0x33, 0xf8, 0xad, 0xfe, 0x4c, 0x22, 0x8d, 0x1a, 0x23, 0x32, 0x10, 0xf3, 0x34, 0x03, 0x07, 0x51, 0x0e, 0x5f, 0x21, 0x98, 0xb3, 0x91, 0x55, 0xfe, 0x55, 0xc6, 0x93, 0x9d, 0x06, 0xf5, + 0x13, 0x82, 0x2e, 0x06, 0xcb, 0xc9, 0x29, 0x81, 0x6f, 0xe9, 0xcb, 0xd6, 0xcd, 0x5f, 0xff, 0xf5, 0xa1, 0xe6, 0x8b, 0xb7, 0x9d, 0xe7, 0x2f, 0x33, 0xd8, 0x8c, 0x99, 0x25, 0xdd, 0x9b, 0xba, 0x1e, 0xfe, 0x63, 0x18, 0x5f, 0xe4, 0xca, 0x9a, 0xf8, 0x16, 0x8b, 0x70, 0x91, 0x2b, 0x4b, 0xb2, 0x78, 0x7f, 0x77, 0xdf, 0xd2, 0xed, 0x5d, 0x25, 0x16, 0x8d, 0x45, 0xdb, 0xe4, 0x5b, 0xbb, 0xfb, 0x70, 0xf7, 0xe5, 0xaf, 0xdd, 0x36, 0x64, 0x7c, 0xf1, 0x59, 0xd8, 0xf7, 0xbe, 0xc3, 0x1e, 0xbe, 0xe4, 0xc7, 0xd7, 0xf4, 0xd8, 0x8b, 0x5b, 0x0b, 0x62, 0x26, 0xa3, 0x37, 0xec, 0xfd, 0xce, 0xf1, 0x3a, 0xa3, 0x3b, 0x18, 0xca, 0xc8, 0xd6, 0xc1, 0xe7, 0x89, 0x1f, 0x84, 0x8a, 0x43, 0x4a, 0x75, 0x4d, 0xa4, 0xaf, 0xd2, 0x39, 0xff, 0x86, 0x9f, 0xed, 0x1f, 0xd3, 0x65, 0x67, 0x98, 0x82, 0x46, 0x59, 0xe7, + 0x21, 0x78, 0xc3, 0x56, 0xee, 0x3c, 0xca, 0x3f, 0xf6, 0x24, 0x2c, 0x16, 0x80, 0x71, 0xa8, 0xd5, 0x9a, 0x11, 0xca, 0x32, 0x08, 0xfb, 0x40, 0xea, 0x99, 0xd8, 0xb2, 0x09, 0x1e, 0xe2, 0x88, 0x07, 0x49, 0x4e, 0xcd, 0xf8, 0xd9, 0x32, 0x0e, 0xc4, 0x5d, 0x67, 0xe3, 0x37, 0x3e, 0x31, 0x22, 0xf3, 0x1b, 0x71, 0x1a, 0xbf, 0x69, 0x80, 0xc7, 0xd3, 0xf8, 0x4d, 0x4c, 0xe6, 0x37, 0x31, 0x19, 0xcb, 0xde, 0x48, 0x33, 0xe2, 0x5b, 0xff, 0xa3, 0xf2, 0x17, 0xe8, 0x51, 0x0d, 0x70, 0x92, 0xdd, 0xb1, 0x5b, 0x5d, 0x0e, 0x41, 0xf5, 0x56, 0xcb, 0x32, 0xca, 0x6d, 0xa2, 0xbf, 0x8c, 0x8a, 0x91, 0x9c, 0xe5, 0x9d, 0x97, 0xc0, 0x43, 0xcd, 0x77, 0x2a, 0xff, 0x83, 0x64, 0xb9, 0x2a, 0x46, 0x98, 0x4d, 0xfb, 0x9b, 0x8d, 0x4b, 0x72, 0xfc, 0x7c, 0x14, 0xdf, 0xfd, 0x11, 0xda, 0x07, 0xfc, 0xe4, 0x23, 0x7f, + 0x61, 0xbd, 0xf4, 0x06, 0xe1, 0x34, 0xbf, 0x78, 0x0b, 0xf8, 0x4c, 0xd8, 0x5f, 0xd0, 0xf0, 0x11, 0x3c, 0x7e, 0xc7, 0x13, 0x90, 0xde, 0x40, 0xe1, 0xdc, 0x9c, 0xdf, 0x93, 0xb9, 0xbf, 0x63, 0xd2, 0xc2, 0x3e, 0xc5, 0x37, 0x82, 0x0c, 0x6c, 0x64, 0xda, 0x98, 0x96, 0x78, 0x63, 0x5b, 0x1d, 0x68, 0x85, 0x46, 0x98, 0xe6, 0x7c, 0x81, 0x1f, 0xc3, 0x94, 0xea, 0x62, 0x40, 0x49, 0x64, 0x29, 0x56, 0x38, 0x99, 0x5d, 0xd4, 0x14, 0x87, 0x45, 0x2b, 0xa7, 0x01, 0x12, 0x37, 0xc5, 0x89, 0xa3, 0x67, 0x28, 0x48, 0x4c, 0x9c, 0xe9, 0x11, 0x99, 0x8c, 0x4d, 0x1b, 0x62, 0xe5, 0x7d, 0x3f, 0x27, 0x61, 0x03, 0x08, 0xbd, 0x8f, 0x15, 0xa1, 0x50, 0x8c, 0x3a, 0xba, 0x59, 0x53, 0x31, 0x85, 0x22, 0xd3, 0x21, 0x57, 0x71, 0xf3, 0xa6, 0xa1, 0xec, 0x92, 0xa6, 0x60, 0x67, 0xa4, 0xbd, 0xd0, 0x64, 0x2f, 0xa8, + 0xf5, 0x6d, 0xad, 0xdb, 0xbd, 0xba, 0x26, 0xb6, 0xee, 0xba, 0xa5, 0x99, 0xdb, 0x33, 0x37, 0x3c, 0x58, 0x9b, 0x67, 0x0e, 0xea, 0x42, 0x35, 0x7d, 0x55, 0x8e, 0xba, 0x9d, 0xe7, 0x55, 0x17, 0x9f, 0x77, 0xdd, 0xaa, 0xcc, 0xb1, 0xcc, 0xa5, 0x47, 0x4a, 0x0b, 0x15, 0xa6, 0x0c, 0xad, 0xb7, 0xb8, 0xb1, 0xe0, 0x72, 0xd5, 0xea, 0x16, 0x4f, 0xa9, 0x3f, 0x4b, 0x8d, 0x35, 0xbc, 0x2b, 0xbf, 0xc2, 0xe5, 0x2a, 0xf2, 0x39, 0xd4, 0x6e, 0x7d, 0x5e, 0xeb, 0x48, 0x6f, 0xfb, 0xc5, 0xab, 0x6a, 0x85, 0xd7, 0xdf, 0x50, 0x2f, 0x6a, 0x68, 0xf2, 0x65, 0xfb, 0x7d, 0x22, 0x1a, 0xce, 0xcc, 0x6f, 0xdf, 0xbc, 0xa0, 0x7a, 0xfb, 0xf2, 0x5a, 0xfe, 0x3f, 0x7e, 0xa9, 0x6e, 0x2e, 0xed, 0xd2, 0x66, 0x9a, 0xb3, 0xb3, 0x45, 0xe8, 0xe6, 0xee, 0xc9, 0x10, 0xfb, 0x22, 0xf7, 0x65, 0x46, 0x00, 0xad, 0x72, 0x6b, + 0xdc, 0x18, 0xce, 0x21, 0x01, 0xcb, 0x73, 0x33, 0x04, 0x58, 0xda, 0xb8, 0x2b, 0x1b, 0xa4, 0xb4, 0x4e, 0x79, 0x42, 0x79, 0x88, 0x15, 0x25, 0xcc, 0x08, 0xe2, 0x28, 0x89, 0x79, 0x86, 0xc6, 0x0a, 0xc5, 0x40, 0x9a, 0x44, 0xe4, 0xba, 0x51, 0x72, 0x47, 0x7f, 0x96, 0x3c, 0xdb, 0x88, 0x1f, 0xb9, 0x32, 0x37, 0xa4, 0xcf, 0xd2, 0x3b, 0xa8, 0x21, 0x86, 0x6c, 0x9c, 0x07, 0x4a, 0x37, 0x25, 0x57, 0x3a, 0x1a, 0xb1, 0xb2, 0x59, 0x3e, 0xae, 0x3d, 0x74, 0xf0, 0xfc, 0x0d, 0xe8, 0x69, 0xf7, 0xfc, 0xa5, 0x2b, 0x8b, 0x74, 0x17, 0xe8, 0x96, 0x1c, 0xd9, 0x58, 0x53, 0x77, 0xc1, 0x03, 0xdb, 0x32, 0x2e, 0xc8, 0xe8, 0xbb, 0xb8, 0xac, 0x48, 0x6d, 0xd1, 0xeb, 0x9c, 0x81, 0xf2, 0x40, 0x49, 0x65, 0x77, 0xb9, 0x3f, 0x33, 0xd3, 0xac, 0x62, 0x2b, 0x32, 0x1f, 0xbb, 0xf1, 0xda, 0xfb, 0xec, 0x38, 0x58, + 0xe1, 0x37, 0xb0, 0x3f, 0xf9, 0xa9, 0xba, 0xfb, 0x92, 0x7b, 0x37, 0xad, 0xfb, 0xd6, 0x65, 0xf3, 0xc5, 0x5f, 0xbc, 0xa1, 0x6e, 0x2c, 0x6c, 0xd2, 0x69, 0xf5, 0x56, 0xab, 0x30, 0x31, 0x9f, 0x73, 0xb8, 0xac, 0x6a, 0x35, 0xe5, 0xcf, 0xc0, 0x70, 0xd9, 0xbf, 0x70, 0xdb, 0x99, 0x12, 0x72, 0xbe, 0xa8, 0x05, 0x3e, 0x93, 0x1f, 0xc0, 0xa2, 0x30, 0x8d, 0xd1, 0x20, 0x86, 0x60, 0x75, 0x0f, 0x51, 0x03, 0x78, 0x99, 0x5f, 0x0a, 0x02, 0xe5, 0xd8, 0xc3, 0x62, 0x8f, 0x59, 0xef, 0xf7, 0x19, 0x03, 0x81, 0x80, 0x57, 0xbe, 0x98, 0x4a, 0x05, 0xb6, 0x49, 0xda, 0xd9, 0x10, 0x48, 0x16, 0x27, 0x4a, 0x86, 0xc3, 0xa1, 0xd1, 0x70, 0x3c, 0xd1, 0x7a, 0x12, 0x00, 0x06, 0x36, 0xa5, 0x45, 0x05, 0xab, 0x6e, 0xda, 0x7c, 0xe0, 0x81, 0xc2, 0x4a, 0xb5, 0x55, 0x97, 0x61, 0xb5, 0x39, 0x73, 0xab, 0xc3, 0xe3, + 0x5b, 0xd0, 0x8d, 0x6e, 0xbb, 0x74, 0xd7, 0xf2, 0x96, 0x16, 0x1c, 0x3c, 0x39, 0x54, 0xbf, 0xe0, 0x26, 0x6c, 0xce, 0x0e, 0x6f, 0x5f, 0x74, 0xcb, 0x8e, 0xd6, 0x4b, 0xb7, 0x15, 0x2a, 0x34, 0x66, 0xb7, 0xdf, 0x6d, 0xbe, 0xe2, 0xe2, 0xf1, 0x2e, 0x93, 0x7b, 0xfc, 0x8a, 0xc5, 0xfa, 0x2f, 0x5f, 0xa8, 0x5b, 0xb1, 0xf0, 0xeb, 0xf2, 0x3e, 0xd3, 0x40, 0x94, 0x3d, 0xe0, 0x05, 0x7a, 0xe2, 0x45, 0xc0, 0x88, 0xb0, 0xaf, 0x30, 0xa2, 0x62, 0xcd, 0xa9, 0x1b, 0xce, 0x19, 0x77, 0x19, 0xfa, 0xd7, 0x17, 0x4d, 0xec, 0x32, 0xcf, 0xa2, 0x67, 0xa5, 0x66, 0xe4, 0x93, 0x7e, 0x7b, 0xe2, 0xa9, 0xa7, 0x58, 0x91, 0x7d, 0x46, 0xfa, 0xf1, 0xd1, 0x6f, 0x8f, 0xbe, 0xf2, 0xca, 0x28, 0xeb, 0xff, 0x9a, 0xfc, 0xce, 0x3e, 0xa0, 0xdd, 0xa7, 0x40, 0xbb, 0x2a, 0x66, 0x49, 0x7c, 0x61, 0x16, 0x12, 0xc4, 0x0a, + 0xa0, 0x98, 0x06, 0x68, 0x97, 0x84, 0x1b, 0xe0, 0x29, 0x88, 0xc3, 0x10, 0x85, 0x1b, 0x60, 0x31, 0x47, 0xb6, 0x65, 0xac, 0x46, 0x48, 0x03, 0xca, 0xa6, 0x16, 0x04, 0x1c, 0xd9, 0x68, 0x80, 0x4a, 0x71, 0x55, 0x4c, 0x65, 0xa4, 0x94, 0xe0, 0x9d, 0x13, 0x72, 0x92, 0x78, 0x7e, 0x7a, 0xc2, 0x9d, 0x03, 0x49, 0x72, 0xcd, 0x0a, 0x1c, 0x34, 0x9b, 0xbe, 0x53, 0x21, 0x87, 0xc8, 0x39, 0x35, 0xfb, 0x29, 0x90, 0x6e, 0xfe, 0x4d, 0x37, 0xdd, 0xe4, 0xb6, 0xaf, 0x2c, 0x58, 0x7d, 0xd3, 0xa6, 0x03, 0x0f, 0x14, 0x55, 0xa9, 0x52, 0x34, 0x3e, 0x7f, 0x14, 0x5d, 0x2c, 0x1d, 0x44, 0x17, 0xbb, 0x6d, 0xd2, 0x33, 0xcb, 0xbd, 0xcd, 0xbe, 0x2f, 0x53, 0x2a, 0x36, 0x2d, 0xdf, 0x19, 0x34, 0xb9, 0xc3, 0x33, 0x29, 0xfd, 0x85, 0x4b, 0x72, 0x97, 0xef, 0x0c, 0x98, 0x3c, 0x19, 0x57, 0x2c, 0x52, 0x28, 0x80, + 0x27, 0xc4, 0x80, 0xc6, 0x39, 0x40, 0x63, 0x9e, 0x9e, 0xf2, 0x35, 0xc7, 0xe3, 0x8c, 0x12, 0x26, 0x87, 0x92, 0x1d, 0x22, 0x3b, 0x26, 0xd9, 0x3c, 0x41, 0x8b, 0x5e, 0x33, 0xb5, 0xaf, 0x26, 0xf6, 0x4f, 0x8f, 0x7b, 0x6a, 0xff, 0x4c, 0x1b, 0x17, 0x87, 0xec, 0x9f, 0x72, 0x5c, 0x1c, 0x34, 0x3b, 0x2e, 0xce, 0x9f, 0xf6, 0xec, 0xd2, 0x4a, 0x97, 0xa0, 0xcb, 0x79, 0x77, 0xdd, 0xca, 0xf8, 0x21, 0x7c, 0xfc, 0x32, 0xfd, 0x2f, 0x7e, 0x74, 0xe8, 0xe9, 0x9d, 0x3b, 0x51, 0xe3, 0xe8, 0xed, 0x1b, 0x63, 0xc2, 0x7e, 0xf4, 0xd9, 0x92, 0xd5, 0x4f, 0x20, 0x6f, 0xa4, 0x25, 0xcf, 0x20, 0xbd, 0x7d, 0xf3, 0x37, 0xa5, 0x1b, 0xf0, 0xb7, 0x46, 0x51, 0x4b, 0x6e, 0xdf, 0x85, 0x0b, 0xc8, 0xfe, 0xb0, 0x0c, 0xda, 0x7b, 0xe0, 0xff, 0xe1, 0xfd, 0xe1, 0xb3, 0x0f, 0x63, 0x1f, 0xa0, 0xe7, 0x75, 0x42, 0x38, + 0xef, 0xb2, 0xd8, 0xdd, 0x39, 0xc0, 0x3b, 0xfe, 0x1b, 0xf6, 0x07, 0x3b, 0xec, 0x0f, 0xe5, 0x1f, 0x54, 0x82, 0x30, 0x3a, 0xd0, 0x79, 0x05, 0xd9, 0x34, 0x5e, 0x8a, 0xfd, 0x9e, 0x64, 0x39, 0x5a, 0x09, 0xfb, 0xc3, 0xb2, 0xd6, 0x3f, 0xb5, 0x2e, 0x06, 0x59, 0x34, 0x36, 0xf7, 0xfd, 0x81, 0x9c, 0x4d, 0xc2, 0xfe, 0x70, 0x1b, 0xec, 0x0f, 0x02, 0xf7, 0xf5, 0x77, 0x64, 0x39, 0x3d, 0xc4, 0x3e, 0x4a, 0x78, 0x23, 0x77, 0xf7, 0xff, 0x24, 0x65, 0x29, 0x25, 0xcc, 0x73, 0x81, 0xfb, 0x86, 0x8d, 0x7c, 0x27, 0xf1, 0x39, 0x4a, 0x80, 0xae, 0x02, 0xda, 0xe6, 0x4b, 0x9c, 0x85, 0x72, 0x66, 0xf2, 0x9d, 0xfb, 0xa6, 0x99, 0x7c, 0x4f, 0xac, 0x0b, 0x2c, 0x70, 0xf7, 0x50, 0x59, 0x6c, 0x04, 0xd2, 0xe7, 0xd1, 0xf4, 0x7b, 0x8b, 0x65, 0xbd, 0xe0, 0x2f, 0xdc, 0x3a, 0x6e, 0x14, 0xca, 0x6f, 0xec, 0x26, + 0xeb, 0x68, 0x09, 0xb3, 0x96, 0xfd, 0x3d, 0xa7, 0x01, 0xc9, 0xac, 0xad, 0xfb, 0xa1, 0x32, 0x62, 0xeb, 0x06, 0x8b, 0x95, 0xe8, 0xc9, 0x2c, 0xb3, 0x26, 0x19, 0x90, 0x71, 0x04, 0xc9, 0x98, 0x93, 0x2c, 0xe2, 0x16, 0x4f, 0x4b, 0xe5, 0x38, 0x02, 0xe0, 0xc7, 0xa1, 0x5e, 0x62, 0xea, 0x96, 0x42, 0x17, 0x46, 0x09, 0xd1, 0x96, 0xa2, 0xca, 0xfc, 0x5e, 0xba, 0xea, 0x0e, 0xe9, 0x6a, 0xb4, 0xe3, 0x0e, 0xb4, 0x13, 0x7d, 0x28, 0x5d, 0x80, 0x0e, 0x1f, 0x45, 0x87, 0xa5, 0x0b, 0xc8, 0xfc, 0xb8, 0x0c, 0xf5, 0xb2, 0x8f, 0xb3, 0x01, 0x8a, 0x5f, 0x4c, 0xf0, 0x63, 0x52, 0xe8, 0xc5, 0x08, 0x91, 0xf3, 0x57, 0x8a, 0x0d, 0xc8, 0x10, 0xf0, 0x57, 0x3d, 0x35, 0x9f, 0x49, 0x62, 0x16, 0x5f, 0xc6, 0x96, 0x9d, 0x7c, 0x85, 0xfc, 0xa0, 0x77, 0x6e, 0x44, 0x6f, 0x1c, 0x95, 0x65, 0x91, 0x63, 0xc8, 0xc9, + 0x7e, 0x85, 0x1d, 0xa0, 0x77, 0x97, 0x2b, 0xe2, 0xcb, 0x4c, 0xc0, 0x76, 0x04, 0x10, 0x26, 0x4b, 0x10, 0x27, 0x7a, 0x81, 0x6f, 0x12, 0xd4, 0x7f, 0x58, 0x1d, 0x2c, 0x1e, 0x22, 0x60, 0x40, 0x24, 0x5e, 0xf7, 0x90, 0x12, 0x71, 0x02, 0xcf, 0x2d, 0x85, 0x77, 0x13, 0xa0, 0x17, 0xf2, 0x95, 0x23, 0xb7, 0x39, 0x1c, 0x3f, 0xdf, 0x99, 0x8d, 0x98, 0xf2, 0xb2, 0xfc, 0xdc, 0xa0, 0x3f, 0x3b, 0xe6, 0x8c, 0xd9, 0x2c, 0x19, 0x5a, 0x95, 0x82, 0x71, 0x20, 0x87, 0x2a, 0x11, 0x21, 0x29, 0x66, 0x9d, 0xe6, 0xae, 0x15, 0x92, 0x6f, 0xa6, 0x8c, 0xa7, 0x79, 0x8e, 0xdf, 0x72, 0x56, 0x14, 0xb8, 0x15, 0x59, 0xde, 0x6d, 0xb5, 0x25, 0x6b, 0xcf, 0x5b, 0x91, 0xeb, 0xac, 0x28, 0x74, 0x2b, 0x5c, 0xde, 0xb1, 0x78, 0xd9, 0xda, 0xf3, 0x06, 0x72, 0xcf, 0x90, 0x86, 0x3f, 0xd3, 0x9a, 0xcc, 0xca, 0x4b, 0x0a, + 0xf3, 0x5d, 0x01, 0x97, 0xd6, 0x6c, 0x53, 0xed, 0x8e, 0x14, 0xc3, 0x6f, 0xd2, 0x93, 0x3a, 0x93, 0x85, 0x3e, 0xf5, 0xbb, 0xb4, 0x26, 0xfa, 0x34, 0x27, 0x90, 0x03, 0x64, 0xba, 0x8c, 0xd9, 0x08, 0xb4, 0x9c, 0x04, 0x09, 0x63, 0x8d, 0x0c, 0x62, 0xea, 0xa1, 0x30, 0x3c, 0xeb, 0x15, 0x48, 0x50, 0x29, 0x61, 0x53, 0x14, 0xf8, 0xb5, 0x1c, 0xb9, 0xb5, 0xa2, 0xec, 0x18, 0x76, 0x8f, 0x11, 0x91, 0xa2, 0x25, 0x11, 0xfc, 0xe3, 0x33, 0x64, 0x91, 0x21, 0xeb, 0x8c, 0x8c, 0x31, 0xc5, 0xab, 0xa9, 0xfe, 0x42, 0x7c, 0xd4, 0x44, 0x5f, 0xcc, 0x17, 0xf5, 0x10, 0xd3, 0x99, 0x58, 0x44, 0x8c, 0x98, 0xf1, 0x97, 0x17, 0xa1, 0x51, 0xa9, 0x67, 0xeb, 0x35, 0x5b, 0xff, 0x82, 0xaa, 0x16, 0x4b, 0xe3, 0xe8, 0xf5, 0x8d, 0x87, 0x37, 0xfe, 0x0c, 0x5f, 0xff, 0xe0, 0x8d, 0xef, 0xbe, 0x7b, 0xe3, 0x83, 0x47, + 0xdf, 0x7c, 0xf3, 0xa8, 0xcc, 0xb3, 0xaf, 0x67, 0xbe, 0xc8, 0x6e, 0x63, 0x7f, 0xcf, 0xa8, 0x18, 0xe1, 0x51, 0xa5, 0x80, 0x4a, 0xf2, 0x81, 0x6d, 0xc3, 0x5e, 0x6c, 0x46, 0x51, 0x64, 0x46, 0x2f, 0x06, 0x23, 0x91, 0x20, 0xfa, 0xc6, 0x84, 0x17, 0xbf, 0x85, 0x97, 0x9b, 0x34, 0x1a, 0xd3, 0x2f, 0x1f, 0x46, 0xfb, 0xfa, 0xd0, 0xde, 0x87, 0xe4, 0x31, 0xbe, 0x13, 0xe6, 0xf5, 0x7e, 0xce, 0x0a, 0x23, 0x99, 0xc9, 0x38, 0xe3, 0x59, 0x09, 0xe7, 0x3a, 0x71, 0x06, 0x20, 0xb5, 0x0c, 0x6b, 0x1c, 0x23, 0xb6, 0x94, 0x14, 0x9c, 0x2c, 0x51, 0xf5, 0x9d, 0xf8, 0xad, 0x09, 0x2f, 0xaa, 0x30, 0x12, 0x8f, 0x27, 0x23, 0xfa, 0x26, 0x79, 0xc3, 0xd1, 0xa3, 0xf8, 0x6b, 0x0a, 0x0d, 0xcf, 0x6b, 0x15, 0x27, 0x1e, 0x42, 0x23, 0x7d, 0x68, 0xdd, 0x83, 0xe4, 0x1d, 0xc7, 0xe1, 0x1d, 0x37, 0x4e, 0x7f, 0x07, + 0x0d, 0x7c, 0x73, 0xda, 0x77, 0x40, 0xe5, 0x01, 0x12, 0x33, 0x04, 0x85, 0x02, 0xd1, 0xe3, 0xe4, 0x1d, 0xf4, 0x3d, 0xdf, 0x30, 0x12, 0x37, 0x23, 0xa3, 0x34, 0x00, 0xef, 0x78, 0xa4, 0x4f, 0xba, 0xe5, 0xa1, 0x13, 0x0a, 0x2d, 0xcf, 0x6b, 0x14, 0x2f, 0x3c, 0x48, 0x69, 0xf0, 0x23, 0xa0, 0xc1, 0x37, 0x80, 0x06, 0x6a, 0xa0, 0x81, 0x1a, 0x13, 0x1a, 0x24, 0xaa, 0x09, 0x42, 0x2d, 0xec, 0x37, 0x48, 0x05, 0xa6, 0xc2, 0xac, 0xd2, 0x3c, 0x69, 0x00, 0xaf, 0xed, 0x93, 0xf6, 0x3f, 0xf4, 0x86, 0x3a, 0x43, 0x50, 0x59, 0x5e, 0x7f, 0x28, 0xc1, 0x53, 0xd9, 0x77, 0x38, 0x72, 0x8a, 0x9e, 0xc1, 0x94, 0xc6, 0x8b, 0x14, 0x22, 0xe6, 0x49, 0x48, 0x4a, 0xe2, 0xa2, 0x28, 0x90, 0x43, 0x58, 0x19, 0xfc, 0x3b, 0xd1, 0x50, 0xe0, 0x8c, 0x19, 0x5c, 0x06, 0x19, 0x36, 0x83, 0x3e, 0x53, 0xa9, 0x72, 0x24, + 0xda, 0x3c, 0xb5, 0x3a, 0x97, 0xb1, 0x57, 0x9d, 0xdc, 0xc9, 0x5e, 0x95, 0x5c, 0xa1, 0xd0, 0xdc, 0x37, 0x8e, 0x12, 0x7d, 0xf3, 0x28, 0x5a, 0x2a, 0xdd, 0x43, 0xdb, 0x7a, 0x19, 0xfa, 0x90, 0x7d, 0x1c, 0xef, 0x4f, 0x60, 0x48, 0xcf, 0x5e, 0x99, 0x94, 0x10, 0xc6, 0xe4, 0x9a, 0xc4, 0xfb, 0x8f, 0xd2, 0x32, 0x07, 0x98, 0xb7, 0x69, 0x19, 0x8a, 0xef, 0xad, 0xa4, 0x26, 0x7c, 0xa4, 0xe0, 0xfa, 0xd9, 0xa8, 0xce, 0xd4, 0x62, 0x2e, 0x0d, 0xda, 0x73, 0x0a, 0xdf, 0x3b, 0x20, 0xe3, 0x3d, 0x3f, 0x7e, 0xf7, 0xc4, 0xf9, 0x6c, 0xe4, 0x6d, 0xa4, 0x49, 0xcc, 0xa1, 0xcd, 0xcc, 0x21, 0xf6, 0x25, 0xf6, 0x37, 0x94, 0x7e, 0x02, 0xa1, 0x5f, 0x40, 0x89, 0x42, 0x4a, 0x14, 0x53, 0x22, 0x2b, 0xec, 0xf3, 0x43, 0xd2, 0x93, 0xd2, 0xf1, 0x0d, 0xa8, 0x43, 0x3a, 0xb1, 0x01, 0xb6, 0xee, 0xea, 0x8d, 0xd2, + 0x0f, 0x50, 0xfb, 0x15, 0xa8, 0x5d, 0x3a, 0xbe, 0x11, 0x75, 0xa0, 0xda, 0x8d, 0xd2, 0x09, 0x54, 0xbd, 0x5e, 0xfa, 0x81, 0xf4, 0xa4, 0x7c, 0xc7, 0xbb, 0x14, 0xd6, 0xcd, 0x3b, 0x30, 0xbe, 0x2c, 0x63, 0x65, 0xf2, 0x99, 0x60, 0xdc, 0x37, 0x85, 0x9d, 0x4e, 0x78, 0xc1, 0xaa, 0xd4, 0x45, 0x1a, 0xdb, 0x13, 0x0e, 0x87, 0x03, 0x53, 0xe8, 0xe9, 0xb3, 0xaf, 0x65, 0xf4, 0xb3, 0xbe, 0x2f, 0x65, 0x0f, 0x9f, 0xbc, 0x00, 0xb5, 0x1a, 0x7d, 0xc5, 0x0e, 0x47, 0xb1, 0xcf, 0x6c, 0x96, 0x3f, 0x8d, 0xb3, 0xbf, 0x03, 0xcd, 0xd0, 0xcd, 0xf0, 0x8b, 0xc9, 0x24, 0x3f, 0x20, 0x9f, 0x13, 0x97, 0xa4, 0x1e, 0x14, 0x91, 0x12, 0x45, 0x30, 0xee, 0x11, 0x18, 0xfc, 0x0d, 0xf8, 0x37, 0x54, 0x0a, 0xd8, 0x23, 0x7b, 0x6b, 0xfc, 0x1f, 0xda, 0x4b, 0x63, 0x5c, 0xac, 0x9c, 0x04, 0x18, 0x2b, 0xcc, 0x0b, 0xd3, + 0x90, 0x1f, 0x3e, 0xa3, 0x67, 0xc6, 0x5e, 0x1a, 0x9b, 0xbe, 0x97, 0x8a, 0x74, 0xfb, 0x94, 0xb7, 0xd2, 0xd8, 0xa9, 0x5b, 0x29, 0xe4, 0xa6, 0xf9, 0xd0, 0xc1, 0x55, 0xd6, 0x55, 0x9f, 0xab, 0xb9, 0x6c, 0x67, 0xbe, 0xb5, 0xd1, 0x6c, 0xe0, 0xc5, 0x4d, 0x81, 0xb8, 0xd1, 0xce, 0x67, 0x9d, 0xe7, 0xe0, 0x7d, 0x96, 0x3a, 0x7f, 0xc4, 0x02, 0x8f, 0xce, 0xb3, 0x6e, 0x24, 0x19, 0x8a, 0x9d, 0x9c, 0xdb, 0xd2, 0x10, 0x58, 0xed, 0xaf, 0xb7, 0xd8, 0x39, 0x27, 0x7a, 0x60, 0xef, 0x7b, 0xb5, 0x66, 0xcb, 0xde, 0xac, 0x9c, 0x82, 0x1f, 0x7b, 0x6d, 0x47, 0xbe, 0x94, 0x6d, 0xfd, 0xb1, 0xdd, 0x5d, 0xb0, 0xb7, 0xd6, 0x62, 0xbe, 0xd9, 0x6a, 0x7b, 0xf1, 0x45, 0x87, 0xf9, 0x18, 0xd1, 0xd5, 0x99, 0xf3, 0x70, 0x2b, 0x5e, 0x09, 0xf3, 0x81, 0x8c, 0xdf, 0x69, 0xb7, 0xa8, 0x69, 0xfb, 0x93, 0x4f, 0x96, + 0x32, 0xa3, 0x11, 0x73, 0x04, 0xb7, 0x7e, 0x77, 0xdb, 0x73, 0xcf, 0x6d, 0xfb, 0x2e, 0xca, 0x7b, 0xf9, 0xe5, 0x8d, 0x3f, 0xfe, 0x31, 0x99, 0x5f, 0xa3, 0x28, 0x0f, 0x36, 0x8e, 0x1b, 0xce, 0x36, 0xe7, 0x47, 0xd1, 0x09, 0xa9, 0x16, 0xdd, 0x20, 0xfb, 0x0d, 0x8d, 0x32, 0xaf, 0x42, 0x99, 0x0f, 0xcf, 0x75, 0x2f, 0x93, 0xeb, 0x20, 0x3f, 0x95, 0x1b, 0x90, 0x73, 0xbd, 0x3c, 0xbf, 0x97, 0x4d, 0x6e, 0x65, 0x3e, 0x65, 0x88, 0xfd, 0x04, 0xbc, 0xdf, 0x08, 0xe3, 0xd5, 0x9e, 0x04, 0x87, 0x07, 0x61, 0xd5, 0x6f, 0x25, 0xef, 0xb7, 0x06, 0xa9, 0x27, 0x51, 0x11, 0x4a, 0x3a, 0x50, 0x08, 0xe2, 0x06, 0xa5, 0xde, 0x6c, 0xe6, 0xdd, 0xa1, 0x3c, 0x5f, 0x5e, 0xb8, 0xae, 0xea, 0x62, 0x85, 0x5a, 0x61, 0xca, 0xf2, 0x98, 0x70, 0xb1, 0x3b, 0x27, 0xaf, 0x22, 0x50, 0x76, 0x41, 0x81, 0x5c, 0xf7, 0x92, + 0xc9, 0x7f, 0x30, 0xff, 0x80, 0xba, 0xd5, 0x8c, 0x71, 0x2a, 0x52, 0x12, 0xb0, 0x0a, 0xbf, 0x97, 0xf2, 0xb4, 0x64, 0x6d, 0x30, 0x94, 0x4b, 0x8a, 0x7d, 0xde, 0xe2, 0x62, 0xaf, 0xaf, 0xb8, 0xcb, 0x57, 0x4c, 0x7f, 0xa5, 0xe5, 0x47, 0xa1, 0xb3, 0x95, 0x58, 0x0f, 0xab, 0xc5, 0x1f, 0xf7, 0xe8, 0x13, 0x96, 0x1d, 0x20, 0x78, 0x2e, 0xe5, 0x28, 0x98, 0x6f, 0x22, 0xca, 0x81, 0xc7, 0xe8, 0x33, 0x7b, 0x89, 0xe9, 0xad, 0x31, 0x61, 0xb7, 0x41, 0x6c, 0x6d, 0xe4, 0xe0, 0xa8, 0x3e, 0x7d, 0xa4, 0xcc, 0xfa, 0x8e, 0xa7, 0xb7, 0x2a, 0xbf, 0xb1, 0xc0, 0x7a, 0x65, 0x38, 0xd7, 0x59, 0x6e, 0xbd, 0xaa, 0x33, 0x4b, 0x8d, 0x9e, 0xf7, 0x15, 0xb8, 0xca, 0x3b, 0xf3, 0xa5, 0xc7, 0x51, 0x77, 0x5e, 0x91, 0xdd, 0x22, 0x5d, 0x8d, 0x95, 0x4e, 0x86, 0x00, 0xa3, 0xaf, 0xc5, 0xf5, 0xb8, 0x15, 0xd4, 0x05, + 0xaa, 0x21, 0xe5, 0xe5, 0x86, 0x7c, 0x1e, 0xb7, 0x80, 0xd8, 0x92, 0x62, 0x47, 0x96, 0xcd, 0x28, 0x80, 0xf6, 0xdc, 0x15, 0xf6, 0x62, 0xb6, 0xc3, 0x8e, 0x70, 0x7b, 0x0a, 0x81, 0x97, 0x5c, 0xb2, 0x2e, 0x4d, 0x18, 0xbf, 0x32, 0x78, 0x7e, 0x51, 0xc8, 0x22, 0xc7, 0x5c, 0x48, 0x59, 0x5b, 0x50, 0xaf, 0x11, 0x14, 0x22, 0xdd, 0x0c, 0x89, 0xa1, 0x18, 0x71, 0x43, 0x89, 0x85, 0x62, 0x56, 0xe8, 0xbc, 0x35, 0x66, 0x15, 0x29, 0x39, 0xad, 0x7f, 0xd7, 0xd9, 0x72, 0x0c, 0x06, 0xb7, 0x55, 0x17, 0xf1, 0x1b, 0x72, 0x6c, 0x3a, 0xc4, 0xf6, 0xe6, 0x97, 0x67, 0x15, 0x99, 0xe6, 0xb5, 0x74, 0x67, 0x69, 0xd5, 0xbd, 0x6d, 0x3d, 0xf9, 0xd1, 0x48, 0x5e, 0x4f, 0x5b, 0x8f, 0x1d, 0xbe, 0xa0, 0x45, 0x86, 0x1c, 0xab, 0x4e, 0x67, 0xcd, 0x31, 0xf8, 0x23, 0x3a, 0xab, 0x5b, 0x9a, 0xec, 0x49, 0x66, 0xc9, + 0x2a, 0x36, 0xf5, 0x42, 0x01, 0x3b, 0x7c, 0x29, 0x88, 0x40, 0xe9, 0x5e, 0xc2, 0x73, 0x5a, 0x26, 0x6f, 0xc0, 0xf3, 0x58, 0x72, 0x1e, 0xa0, 0x67, 0xba, 0x99, 0x7b, 0xe2, 0x86, 0xee, 0x26, 0xcc, 0xb3, 0xc5, 0x7a, 0x05, 0x8b, 0xf9, 0xa2, 0x00, 0x3d, 0x10, 0x48, 0x40, 0x10, 0xc3, 0x64, 0x66, 0xb9, 0x91, 0xd9, 0xa7, 0x03, 0x72, 0xb0, 0x04, 0xd0, 0x6e, 0x07, 0x13, 0x81, 0x2a, 0x64, 0xb3, 0xda, 0xc0, 0xe9, 0x72, 0xa3, 0xf3, 0xa7, 0x67, 0x8e, 0xfb, 0x13, 0xa1, 0x5d, 0xce, 0x98, 0x6d, 0x60, 0x20, 0x9e, 0xd1, 0xde, 0x5a, 0x5f, 0x5b, 0x55, 0x99, 0x17, 0xf6, 0xe4, 0x64, 0x3b, 0x4e, 0x39, 0x74, 0xb0, 0x4c, 0x9d, 0x39, 0x10, 0xf7, 0x0d, 0x7a, 0xec, 0x90, 0xc0, 0x23, 0x49, 0xe9, 0xd4, 0x44, 0x63, 0x9a, 0xae, 0x56, 0xa3, 0xc1, 0x8a, 0xea, 0x0c, 0xbb, 0xcf, 0x94, 0xed, 0xce, 0xb3, + 0xab, 0xca, 0xc3, 0x7d, 0xbe, 0x96, 0xa8, 0xc7, 0x13, 0x3f, 0xaf, 0x56, 0x30, 0x67, 0x46, 0x54, 0xb1, 0xb5, 0xde, 0x3c, 0x41, 0xa7, 0x56, 0x59, 0x9c, 0x81, 0x2c, 0xa3, 0xaf, 0xb9, 0x1c, 0x52, 0x56, 0xd6, 0xf2, 0xa6, 0xcc, 0x88, 0xb2, 0xac, 0x23, 0xcb, 0xab, 0xd0, 0xaa, 0x15, 0x99, 0x96, 0x6c, 0xe3, 0x26, 0xa1, 0xa2, 0xc0, 0xe8, 0x30, 0x67, 0x0a, 0xa8, 0x83, 0xcd, 0xb4, 0x39, 0x33, 0x03, 0xb9, 0x3a, 0xbb, 0xda, 0x91, 0xd7, 0x50, 0x54, 0xba, 0x24, 0x1e, 0x62, 0x9b, 0xb4, 0x96, 0xcd, 0xaa, 0x62, 0x4f, 0xa3, 0x42, 0xa1, 0x33, 0x9a, 0x84, 0x5f, 0xa8, 0xb2, 0x8b, 0x5a, 0x22, 0xe1, 0xde, 0xda, 0x00, 0xaa, 0xd1, 0x9a, 0xd7, 0xa9, 0xf3, 0xb2, 0x22, 0x6a, 0xa5, 0x52, 0xa3, 0xe5, 0xa1, 0xcf, 0x43, 0x30, 0x06, 0x71, 0x7c, 0x31, 0x6c, 0x9a, 0x65, 0x4c, 0x6d, 0xbc, 0x2a, 0xcf, + 0xe7, 0x66, 0x59, 0x94, 0x0f, 0xf3, 0x89, 0x23, 0xba, 0x37, 0xb1, 0xe0, 0x06, 0x36, 0x82, 0x88, 0x71, 0x39, 0x89, 0x1a, 0x44, 0xe8, 0x03, 0x93, 0x6b, 0x69, 0x62, 0x1d, 0x22, 0x66, 0x7e, 0x28, 0xe4, 0xcb, 0x2a, 0x24, 0x33, 0x3c, 0x75, 0x9a, 0x90, 0x0c, 0xe8, 0x3c, 0x23, 0x90, 0x73, 0xb2, 0xf3, 0x41, 0xf9, 0x48, 0xc1, 0x8a, 0x96, 0xd6, 0xb4, 0x54, 0x14, 0x22, 0x3e, 0xb8, 0xa6, 0x5c, 0xc0, 0x46, 0x7d, 0x99, 0xaa, 0xa0, 0xbb, 0x32, 0x87, 0x78, 0x30, 0x8a, 0xd8, 0x08, 0x34, 0x08, 0x37, 0x66, 0xfb, 0x78, 0x8d, 0x3a, 0xc3, 0xe7, 0x72, 0xda, 0xbd, 0x76, 0x93, 0x3a, 0x53, 0x89, 0xbe, 0xae, 0x9c, 0xdf, 0xd9, 0xdc, 0xa5, 0xcb, 0x2a, 0x0c, 0x70, 0xcd, 0x3a, 0xf3, 0x46, 0x45, 0x5e, 0xf3, 0x8a, 0xca, 0xd8, 0xaa, 0xf6, 0x7c, 0xb6, 0x5b, 0x6b, 0x1e, 0x57, 0xf8, 0xb2, 0x4a, 0x94, + 0x0a, 0xb3, 0x42, 0xfa, 0x1e, 0xab, 0xcd, 0x50, 0xab, 0x65, 0x3d, 0x1c, 0x0f, 0xe3, 0x77, 0x99, 0x42, 0xb2, 0x5a, 0x88, 0x3d, 0x7d, 0x38, 0xe7, 0xcc, 0xe7, 0x09, 0xc4, 0xc5, 0x9f, 0x9a, 0x4e, 0x93, 0xf3, 0x04, 0x9f, 0x5f, 0x6f, 0xf4, 0x85, 0x02, 0x99, 0xf4, 0x3c, 0x21, 0x32, 0x15, 0x25, 0x37, 0x75, 0x38, 0xe9, 0x44, 0x11, 0xb3, 0x2f, 0x71, 0x94, 0x40, 0x34, 0x5e, 0x59, 0x3d, 0x6e, 0xe8, 0x76, 0xd7, 0xad, 0xa8, 0x2d, 0x68, 0x89, 0x86, 0x74, 0x51, 0x55, 0x06, 0x68, 0xaf, 0x8e, 0xa0, 0xab, 0x3c, 0xf6, 0xeb, 0x5f, 0xe7, 0x14, 0x80, 0x68, 0xe3, 0x76, 0x48, 0x2e, 0xaf, 0xa7, 0x1b, 0x1d, 0x1a, 0x2f, 0x59, 0x12, 0x0f, 0x9a, 0x7c, 0xa5, 0x4e, 0x3f, 0x2f, 0x18, 0x6d, 0xc6, 0x78, 0xc5, 0xf8, 0x8a, 0x65, 0xf5, 0xb9, 0xea, 0x1a, 0x73, 0x68, 0xa9, 0x18, 0x0e, 0xed, 0x95, 0xf9, + 0x53, 0x1e, 0xb4, 0x7f, 0x1c, 0xf6, 0x39, 0x0f, 0x39, 0x5f, 0xb7, 0xd3, 0xf3, 0x75, 0x15, 0x52, 0x32, 0xca, 0xf1, 0x74, 0xa7, 0xec, 0xf2, 0x61, 0x82, 0x27, 0x67, 0xf6, 0xf9, 0x3a, 0x69, 0x23, 0xf9, 0x91, 0x99, 0x3c, 0xfc, 0xb0, 0x04, 0x9b, 0x35, 0xe5, 0xcf, 0x5a, 0x83, 0xd0, 0x1d, 0x5f, 0xb8, 0xf1, 0xc6, 0x1b, 0xaf, 0xbc, 0xe1, 0x86, 0x1b, 0x0e, 0xfd, 0x7c, 0x3c, 0xa0, 0xdd, 0xc2, 0xab, 0x78, 0x4e, 0xc9, 0x8f, 0xf2, 0x76, 0xc3, 0x05, 0x28, 0xde, 0x54, 0xdf, 0xb8, 0x6f, 0x5f, 0x63, 0x7d, 0x13, 0xba, 0xcd, 0x7b, 0x32, 0xd3, 0x6f, 0xb6, 0xf8, 0x33, 0x4e, 0xea, 0x4c, 0x74, 0xd0, 0xa1, 0x6d, 0x2b, 0xa1, 0x6d, 0xff, 0xf2, 0x19, 0x87, 0x79, 0x46, 0xc3, 0xc8, 0x19, 0x47, 0xcd, 0xfb, 0xef, 0x1f, 0x39, 0xf2, 0xa5, 0xc5, 0x8b, 0xd1, 0x93, 0x78, 0xe8, 0xb1, 0xad, 0x3b, 0xe3, + 0x87, 0x0f, 0xc7, 0xb1, 0x7e, 0x8f, 0x7c, 0xcf, 0x80, 0xd7, 0xc3, 0x38, 0x56, 0x30, 0x7d, 0xf1, 0x79, 0x64, 0x1c, 0x0b, 0xfd, 0x58, 0xa9, 0x48, 0x3b, 0x8e, 0x1a, 0x79, 0x1c, 0xd5, 0x20, 0xc6, 0x23, 0x51, 0x21, 0x88, 0x04, 0xe1, 0x82, 0x9e, 0xa8, 0x0f, 0x2b, 0x7b, 0xe4, 0x68, 0xa1, 0x05, 0x79, 0x01, 0x68, 0x02, 0x1d, 0x59, 0xed, 0x99, 0x47, 0x96, 0x8e, 0xae, 0x30, 0x6b, 0x78, 0x09, 0x0d, 0x9b, 0x4e, 0x19, 0xe2, 0x90, 0x2b, 0x12, 0x7b, 0x13, 0xfe, 0x64, 0x64, 0x26, 0x47, 0xd9, 0xe3, 0xed, 0xee, 0xee, 0x46, 0xc7, 0x1b, 0x67, 0x8f, 0x73, 0x23, 0x10, 0xd3, 0xe7, 0x2e, 0xb5, 0x24, 0x87, 0xba, 0xb3, 0xa9, 0x9e, 0xca, 0x5e, 0x2d, 0xd0, 0xc7, 0xfd, 0x40, 0x4f, 0x72, 0x27, 0xe0, 0x9c, 0xf3, 0x79, 0x86, 0x33, 0x3b, 0x4b, 0x3e, 0xcd, 0x30, 0xe8, 0x33, 0xd2, 0xdd, 0x07, 0xf8, + 0xcc, 0xd1, 0x08, 0xb9, 0x0f, 0xe0, 0xe9, 0x79, 0x86, 0x59, 0x8e, 0x3d, 0x47, 0xce, 0x6c, 0x22, 0xa8, 0xa6, 0xa6, 0x55, 0xf1, 0xec, 0x77, 0xc4, 0xa6, 0x86, 0x0a, 0x74, 0x51, 0xa3, 0xea, 0xc0, 0x9e, 0xe8, 0x17, 0x6a, 0x6a, 0x9e, 0xbd, 0xf4, 0x12, 0x45, 0x1c, 0x2d, 0x2d, 0xaf, 0xd9, 0xf6, 0x50, 0x55, 0xc9, 0xbd, 0xc3, 0x5b, 0x3f, 0x46, 0x2f, 0x34, 0xbe, 0xb6, 0x75, 0x3d, 0x19, 0xef, 0x82, 0xc9, 0x7f, 0xc0, 0x5c, 0x7c, 0x17, 0xd6, 0xd4, 0x70, 0x7c, 0x0d, 0x48, 0x96, 0xc8, 0x8a, 0x78, 0xc6, 0x83, 0x44, 0xa1, 0x18, 0xa9, 0x14, 0x5c, 0x57, 0x03, 0xe2, 0x51, 0xe7, 0xec, 0x0b, 0x81, 0x69, 0xe3, 0xc2, 0xcc, 0xbc, 0x17, 0x18, 0x4e, 0xde, 0x06, 0x0c, 0xab, 0x7a, 0x48, 0x20, 0x57, 0xe2, 0xc4, 0xed, 0xf1, 0x99, 0x7c, 0x06, 0x5f, 0xc0, 0x9f, 0x18, 0x96, 0x59, 0x60, 0x20, 0x42, + 0x22, 0x46, 0xab, 0x60, 0x22, 0x83, 0x43, 0x7e, 0x9b, 0xc2, 0x19, 0x46, 0xc9, 0x7b, 0x80, 0x68, 0x2d, 0x08, 0x5e, 0x05, 0xdd, 0xea, 0xb2, 0xee, 0x35, 0xd5, 0xed, 0xa3, 0xee, 0x52, 0x51, 0xad, 0x56, 0x1b, 0x1c, 0xe1, 0x9c, 0x86, 0xde, 0x0c, 0xc4, 0x9a, 0x0c, 0xef, 0xa2, 0xdf, 0x58, 0xf4, 0xdf, 0xb2, 0x5f, 0xec, 0xcb, 0xcf, 0x36, 0xa9, 0x35, 0xca, 0x1a, 0xdf, 0x8e, 0x8e, 0xf8, 0xda, 0xf6, 0x02, 0x65, 0x67, 0x33, 0x6a, 0x41, 0x3f, 0xaf, 0x5f, 0xd7, 0x11, 0x8a, 0x15, 0xc7, 0x14, 0x62, 0xa6, 0x31, 0x73, 0x5e, 0x77, 0x8e, 0xc6, 0xaa, 0x30, 0x28, 0x9c, 0x39, 0x6a, 0xcb, 0x1d, 0x06, 0xab, 0x41, 0x51, 0x55, 0x59, 0x1d, 0xee, 0x5a, 0xdf, 0x10, 0x77, 0xc2, 0x43, 0xba, 0x36, 0x2b, 0x27, 0x2f, 0x43, 0x36, 0xb6, 0x05, 0xe4, 0x12, 0xe1, 0x51, 0xad, 0x08, 0x72, 0xb7, 0x11, 0x18, + 0x9f, 0x45, 0xc8, 0x40, 0x54, 0x0c, 0x89, 0x59, 0x5d, 0xe4, 0x98, 0xe5, 0xa1, 0x92, 0x02, 0x8d, 0x56, 0x5d, 0xd7, 0x1d, 0x6f, 0xec, 0xaa, 0x53, 0x6b, 0x35, 0x05, 0x25, 0x68, 0x78, 0xe4, 0xc5, 0x25, 0x56, 0x9b, 0x18, 0x3a, 0xff, 0xc3, 0xfd, 0x97, 0xfe, 0x69, 0x3c, 0xa0, 0xb0, 0x5a, 0x97, 0xfd, 0x50, 0x8e, 0xff, 0x0d, 0xf5, 0x59, 0x66, 0xd4, 0x57, 0x1e, 0x0c, 0x05, 0x2b, 0x1a, 0x28, 0x7a, 0x8f, 0x20, 0x86, 0x8a, 0x88, 0x10, 0x79, 0x7b, 0x49, 0xa1, 0x46, 0xab, 0xa9, 0xef, 0x8a, 0xc7, 0xbb, 0xea, 0xe1, 0x97, 0x42, 0x52, 0xdf, 0x0f, 0x97, 0x59, 0xad, 0x8a, 0xc0, 0xf9, 0x1f, 0xec, 0xdf, 0xff, 0xe1, 0xf9, 0x21, 0xd1, 0x66, 0x5d, 0xf2, 0x22, 0x3d, 0xe7, 0x88, 0xb0, 0x07, 0xf1, 0x06, 0x61, 0x35, 0x2b, 0x72, 0xdf, 0x83, 0xe9, 0x45, 0x6d, 0xc6, 0xf0, 0x74, 0x9b, 0xb1, 0xa5, + 0xec, 0x3b, 0xb8, 0x95, 0x7f, 0x1b, 0xd2, 0x9f, 0x4f, 0xa6, 0xcf, 0xb0, 0x29, 0x1b, 0xe5, 0xbe, 0x85, 0x4b, 0x78, 0x2b, 0xa4, 0x7f, 0xff, 0x34, 0xe9, 0x8b, 0x20, 0x7d, 0x3b, 0xa4, 0x9f, 0x48, 0x5b, 0xff, 0x00, 0xfe, 0x2b, 0xf3, 0x19, 0xe7, 0x86, 0xf4, 0x1f, 0xc0, 0x76, 0x71, 0x6a, 0xf9, 0xc5, 0xec, 0x25, 0x89, 0xf4, 0x17, 0x26, 0x27, 0xd3, 0xa4, 0x57, 0xb3, 0x17, 0xe3, 0xb5, 0xd4, 0x26, 0xee, 0xee, 0xb4, 0xf5, 0x7b, 0xa0, 0x7f, 0x23, 0xa4, 0x7f, 0xe8, 0x5e, 0xd8, 0xc2, 0x4f, 0x4d, 0x9f, 0x07, 0xe5, 0x6b, 0x69, 0xfa, 0x7d, 0x69, 0xeb, 0x27, 0xe9, 0x8b, 0x69, 0xfa, 0xfd, 0xc9, 0xf6, 0xe1, 0x99, 0xed, 0x3b, 0x88, 0x17, 0xd2, 0xf4, 0x07, 0x40, 0x12, 0x38, 0xb5, 0x7c, 0x2f, 0x94, 0x9f, 0x4f, 0xdb, 0xf7, 0xad, 0x64, 0xfb, 0xd8, 0xe9, 0xe9, 0x0b, 0xa0, 0x7c, 0x33, 0x4d, + 0x7f, 0x30, 0xf9, 0xfe, 0x19, 0xf5, 0x0f, 0xb2, 0x7f, 0xc4, 0x0d, 0xfc, 0x8f, 0xa0, 0xff, 0x2f, 0xa6, 0x7d, 0x7f, 0x33, 0x6b, 0xc5, 0xbd, 0xc2, 0x0f, 0x20, 0xfd, 0x47, 0xc9, 0xf2, 0x33, 0xea, 0x1f, 0x82, 0xf4, 0x38, 0x68, 0xb9, 0x22, 0xf7, 0xe3, 0xb4, 0xf4, 0x69, 0x80, 0xf7, 0x0f, 0x93, 0xf7, 0x73, 0x2f, 0xa5, 0x1d, 0xbf, 0x3c, 0x48, 0x1f, 0xa7, 0xf3, 0xe3, 0xe5, 0xb4, 0xe9, 0xdd, 0x90, 0x3e, 0x40, 0xd3, 0x7f, 0x32, 0x79, 0x32, 0x4d, 0x7a, 0x13, 0xa4, 0xaf, 0xa7, 0xf5, 0xbf, 0x92, 0xb6, 0x7c, 0x1c, 0xd2, 0x2f, 0xa1, 0xe5, 0x7f, 0x9a, 0x2c, 0x3f, 0xa3, 0xfd, 0xb9, 0xec, 0x25, 0x78, 0x1b, 0x2d, 0xff, 0xea, 0xe4, 0x44, 0xda, 0xf1, 0x17, 0x51, 0x96, 0xf0, 0x22, 0xa4, 0xff, 0x2c, 0xad, 0xcd, 0x63, 0x04, 0xd2, 0x4d, 0x34, 0xfd, 0xe7, 0x93, 0x52, 0xba, 0xf4, 0x49, 0x17, + 0xde, 0x80, 0xfe, 0x29, 0xcf, 0xff, 0x49, 0x97, 0x4c, 0x9f, 0x49, 0xd7, 0xd4, 0xfc, 0x94, 0xae, 0x66, 0x3e, 0x9b, 0x7c, 0x4a, 0x9e, 0x9f, 0x72, 0x3a, 0x9a, 0x9e, 0xbe, 0x64, 0xd2, 0xca, 0xfc, 0x83, 0xa6, 0xbf, 0x90, 0x2c, 0x3f, 0x23, 0x7d, 0x1f, 0xd4, 0xdf, 0x8d, 0x8a, 0x91, 0xc8, 0xfd, 0x90, 0x31, 0xa6, 0x49, 0x1f, 0x9c, 0xbc, 0x14, 0xe4, 0x9e, 0x21, 0x79, 0x7c, 0xd3, 0xbc, 0xbf, 0x45, 0x7a, 0x18, 0xcf, 0xc3, 0xab, 0xc8, 0xf8, 0x26, 0xeb, 0x67, 0xa7, 0xa7, 0x0f, 0x43, 0x7a, 0x23, 0xba, 0x85, 0x8e, 0x6f, 0xba, 0xf2, 0x0d, 0xf0, 0xfe, 0x61, 0x8c, 0xe5, 0xf1, 0x4d, 0xf3, 0xfe, 0x3c, 0x48, 0x1f, 0xa7, 0xfd, 0x7f, 0x39, 0x6d, 0x7a, 0x27, 0xa4, 0x2f, 0xa5, 0xe9, 0x3f, 0x99, 0xfc, 0x67, 0x9a, 0xf4, 0x26, 0x48, 0x5f, 0x4f, 0xeb, 0x7f, 0x25, 0x6d, 0x79, 0xf2, 0xfe, 0x8b, + 0x69, 0xf9, 0x9f, 0x4e, 0x7e, 0x9e, 0xa6, 0xfd, 0xe1, 0x49, 0x2b, 0xde, 0x4a, 0xcb, 0xbf, 0x3a, 0x79, 0x32, 0x4d, 0xf9, 0x28, 0xfe, 0x2e, 0xde, 0xc4, 0xdf, 0x46, 0xc7, 0x07, 0xe3, 0xef, 0xca, 0xfd, 0x9b, 0x66, 0x73, 0xba, 0x94, 0x75, 0x01, 0x7f, 0x72, 0xca, 0xfc, 0x49, 0x4e, 0x9f, 0x61, 0x93, 0x3a, 0xca, 0xfe, 0x37, 0x2e, 0xe1, 0x16, 0xca, 0xfc, 0x29, 0x6d, 0xfa, 0x11, 0x48, 0x7f, 0x40, 0xe6, 0x4f, 0x69, 0xea, 0x5f, 0x86, 0x8b, 0x98, 0x4f, 0xd9, 0x41, 0x3a, 0xfe, 0xe9, 0xca, 0x2f, 0xc6, 0x4f, 0x32, 0x9f, 0xd1, 0x74, 0xe0, 0x4f, 0x69, 0xd2, 0xb7, 0x40, 0xfb, 0xab, 0xb8, 0x9f, 0xb1, 0x64, 0xfc, 0x51, 0x9a, 0xf4, 0x6a, 0x78, 0xdb, 0x5a, 0xfe, 0x2e, 0x99, 0x7f, 0xa5, 0x79, 0xff, 0x2a, 0x28, 0x5f, 0xce, 0xdf, 0x4e, 0x6c, 0x7a, 0xd3, 0xd6, 0xdf, 0x01, 0xe9, 0xf3, 0x69, + 0xf9, 0x6f, 0x82, 0x18, 0x7e, 0x6a, 0x7a, 0x1c, 0x6a, 0x6b, 0xa5, 0xe9, 0xf7, 0xa4, 0x2d, 0x6f, 0x86, 0xf2, 0xcb, 0x09, 0x7d, 0xd1, 0xbd, 0x93, 0x9f, 0xa5, 0x79, 0xff, 0x7c, 0xf8, 0x56, 0x47, 0xd3, 0xef, 0x4b, 0xdb, 0xff, 0x5e, 0x48, 0x5f, 0x44, 0xd3, 0xef, 0x4f, 0xdb, 0xfe, 0xf9, 0x50, 0x7f, 0x2f, 0x4d, 0x7f, 0x60, 0x72, 0xe2, 0x34, 0xe5, 0xe5, 0xf6, 0x7f, 0x2b, 0x59, 0x9e, 0x9d, 0x9e, 0xde, 0x0f, 0xe5, 0xe5, 0xf6, 0x3f, 0x98, 0xa4, 0xdf, 0x8c, 0xfa, 0x07, 0x59, 0x2f, 0xf0, 0x47, 0x5e, 0x5e, 0x3f, 0x69, 0xd2, 0x5b, 0xf0, 0x42, 0x3c, 0x0f, 0x74, 0x5f, 0xba, 0x7e, 0xd2, 0xd4, 0xbf, 0x16, 0xd2, 0x1b, 0xf8, 0x5a, 0xb2, 0x7e, 0x92, 0xf4, 0xc1, 0x33, 0xe9, 0xf7, 0x5d, 0xbc, 0x8e, 0xbc, 0x1f, 0xd6, 0x4f, 0xba, 0xf1, 0xcb, 0x83, 0xf4, 0x71, 0x3a, 0x3f, 0x5f, 0x4e, 0x4b, + 0x9f, 0x3e, 0x48, 0x5f, 0x45, 0xd3, 0x7f, 0x92, 0x9c, 0xbf, 0x28, 0x7d, 0xfd, 0xaf, 0x4c, 0x4a, 0x69, 0xd2, 0x5b, 0x20, 0x7d, 0x3f, 0x2d, 0xff, 0xd3, 0xb4, 0xed, 0xcf, 0xc3, 0x4f, 0xe2, 0x31, 0x5a, 0xfe, 0xd5, 0xb4, 0xe5, 0x2b, 0x70, 0x33, 0xb2, 0x80, 0xb4, 0x07, 0xfc, 0x31, 0x6d, 0x7a, 0x0c, 0xd2, 0x6d, 0x34, 0xfd, 0xe7, 0x69, 0xdb, 0x77, 0x0d, 0xbc, 0xf5, 0xaf, 0xfc, 0x09, 0x26, 0xc8, 0xed, 0x20, 0x77, 0x00, 0xa8, 0x1a, 0x1d, 0xc0, 0xfb, 0x12, 0x96, 0xdc, 0xfb, 0x68, 0x2e, 0x96, 0x59, 0x30, 0xd9, 0xc5, 0xbf, 0xc9, 0xbf, 0xc2, 0x58, 0x99, 0x52, 0xa6, 0x93, 0x69, 0x8d, 0x37, 0x15, 0xe4, 0x83, 0x4e, 0x98, 0x07, 0x7a, 0x9f, 0x07, 0x81, 0x5c, 0xd7, 0xd5, 0x60, 0xc5, 0x6c, 0x47, 0xa7, 0x16, 0x93, 0xd3, 0x06, 0x8e, 0xba, 0xd9, 0x92, 0xcb, 0x66, 0xd0, 0xa2, 0xf1, 0x76, + 0xe2, 0x6f, 0x48, 0xaf, 0x4a, 0x98, 0x9e, 0xd2, 0x92, 0xe2, 0xa2, 0x80, 0x2f, 0x27, 0xdb, 0x6e, 0xe3, 0x89, 0x54, 0xaa, 0xe3, 0xa9, 0x21, 0x98, 0x48, 0x54, 0xc3, 0x2c, 0x54, 0x11, 0x43, 0xbe, 0x44, 0xf0, 0x96, 0xc4, 0x85, 0x41, 0x54, 0x1f, 0x8a, 0x36, 0x20, 0x8f, 0x39, 0xc2, 0xd6, 0xf3, 0x54, 0xc3, 0x8e, 0x11, 0xd5, 0x9a, 0xc4, 0x62, 0xc5, 0x17, 0x2f, 0xbe, 0x6c, 0x75, 0x63, 0x50, 0x4f, 0x62, 0x49, 0x6b, 0x6a, 0xce, 0x6b, 0xf6, 0xb7, 0x5d, 0xf5, 0xd3, 0x2b, 0xd1, 0x77, 0xbf, 0x15, 0x89, 0x2a, 0xdd, 0x7a, 0x53, 0xd9, 0x78, 0xfd, 0xea, 0x4b, 0xe7, 0xb9, 0x87, 0x77, 0x0f, 0x23, 0xfc, 0xad, 0xab, 0x20, 0x67, 0x3c, 0x9c, 0x29, 0xe7, 0x5c, 0xd5, 0x44, 0x73, 0xf2, 0xcf, 0x57, 0x1d, 0xff, 0xcd, 0xc7, 0x1f, 0xfd, 0x66, 0xf1, 0xe2, 0x5d, 0xcb, 0x5b, 0xbc, 0x9a, 0xd8, 0x8e, + 0xc3, 0xf7, 0x8f, 0xdd, 0x82, 0xa2, 0xc8, 0xf1, 0xd7, 0x25, 0x13, 0xe5, 0xd2, 0xbd, 0xc1, 0x02, 0xc4, 0xbe, 0x6e, 0xb2, 0x14, 0x2d, 0x3f, 0xb8, 0x04, 0x15, 0x14, 0xbd, 0xa9, 0xf9, 0xf8, 0xe4, 0xe7, 0xe7, 0x55, 0x3d, 0xf1, 0xeb, 0x8f, 0xff, 0xf4, 0xe6, 0x92, 0x25, 0x7b, 0x96, 0xb7, 0x7a, 0x35, 0x95, 0x3b, 0xae, 0xbe, 0x7f, 0xeb, 0x2d, 0xd2, 0x4b, 0xd2, 0x7b, 0x7f, 0xa5, 0xf6, 0xbe, 0x3a, 0xc6, 0x3e, 0xf9, 0x53, 0xfe, 0x73, 0xfe, 0x61, 0x90, 0xd9, 0xac, 0x8c, 0x83, 0xc9, 0x61, 0x7c, 0x14, 0x23, 0xa1, 0x98, 0x89, 0x30, 0x31, 0xa6, 0x06, 0x24, 0xe5, 0x66, 0x66, 0x29, 0xf3, 0xf7, 0x78, 0x59, 0xa3, 0x1d, 0xb3, 0xb8, 0xc9, 0x81, 0x19, 0xb6, 0xb9, 0x2e, 0x8b, 0x55, 0x33, 0x0d, 0xf5, 0x39, 0x2e, 0x27, 0xcf, 0xab, 0x63, 0x15, 0xd1, 0x50, 0x90, 0x57, 0x71, 0x65, 0xf9, 0x01, + 0x16, 0xa9, 0x22, 0x79, 0x58, 0x44, 0xa5, 0xb9, 0x58, 0x10, 0x15, 0x09, 0xc4, 0x8b, 0x22, 0x78, 0x89, 0x1a, 0xf4, 0x81, 0x21, 0x85, 0x06, 0xf3, 0x5a, 0x25, 0xe6, 0x40, 0x31, 0x5b, 0xc3, 0xa8, 0x60, 0xb4, 0x54, 0x88, 0x38, 0x82, 0x08, 0xa2, 0x40, 0xee, 0xf5, 0x69, 0xec, 0xf9, 0x75, 0x29, 0x2c, 0xaa, 0xd2, 0x33, 0x96, 0x4a, 0x66, 0x27, 0x38, 0xf3, 0x14, 0x7b, 0x6d, 0x98, 0x98, 0x95, 0x9f, 0xe3, 0x9b, 0x06, 0x06, 0xe2, 0xae, 0xca, 0x4a, 0xb7, 0x7b, 0x51, 0x7f, 0x57, 0x47, 0x65, 0x4d, 0x65, 0x4d, 0x75, 0x55, 0x71, 0x51, 0x61, 0x81, 0xdb, 0xe7, 0xf6, 0x65, 0xe9, 0x4d, 0xc4, 0xb2, 0xdc, 0x9e, 0x3f, 0xfb, 0x44, 0x18, 0x45, 0xac, 0xbe, 0x80, 0x55, 0x0c, 0xf1, 0xf0, 0x83, 0xcc, 0x62, 0xd4, 0x28, 0x86, 0x62, 0x2c, 0xfc, 0x20, 0x5f, 0x28, 0xc2, 0x87, 0x62, 0xd6, 0x00, 0xfc, + 0xa0, 0x68, 0xcc, 0xcc, 0xc6, 0xac, 0xa2, 0x11, 0x7e, 0xd8, 0x69, 0xce, 0x40, 0xf4, 0x7c, 0x4a, 0xd9, 0x77, 0xf8, 0xe9, 0xcd, 0x1b, 0x9f, 0x3a, 0xdc, 0xd7, 0x77, 0xf8, 0xa9, 0x8d, 0x9b, 0x9f, 0x3e, 0xdc, 0x87, 0x7e, 0x34, 0xa6, 0xbc, 0x14, 0x7d, 0x61, 0xf5, 0xbc, 0x31, 0xf4, 0x85, 0xd8, 0x91, 0x46, 0x94, 0x75, 0xff, 0x50, 0xe5, 0xcd, 0xf1, 0xad, 0x6b, 0x56, 0x6e, 0x95, 0xde, 0x1a, 0x53, 0x5c, 0x2a, 0xed, 0x59, 0x33, 0x7f, 0x4c, 0xda, 0x13, 0x3b, 0x12, 0x97, 0xde, 0x97, 0x53, 0xd6, 0x0e, 0x6e, 0x8d, 0xd4, 0x8f, 0x1e, 0xe9, 0xef, 0xbf, 0x6e, 0x73, 0x7d, 0xfd, 0xe6, 0xeb, 0xfa, 0xfb, 0x8f, 0x8c, 0xd6, 0xe3, 0xef, 0x6f, 0x7a, 0x9a, 0x54, 0xf9, 0xf4, 0xa6, 0x44, 0xd5, 0xec, 0x52, 0xc8, 0x7c, 0x4b, 0x23, 0xc9, 0x2c, 0xfd, 0x6e, 0xbb, 0x62, 0xbf, 0xb4, 0x9b, 0x56, 0x53, + 0x79, 0x7d, 0xa3, 0xf4, 0xc7, 0x54, 0x35, 0xc8, 0x03, 0x29, 0xe8, 0x0a, 0x48, 0x41, 0x87, 0xaa, 0x20, 0xa5, 0xb8, 0xff, 0x08, 0xa9, 0x32, 0x55, 0x35, 0xc3, 0x68, 0xe7, 0x30, 0x37, 0x7e, 0x11, 0x2f, 0x68, 0xac, 0x23, 0xae, 0x06, 0xb8, 0xb9, 0xc9, 0x91, 0xc5, 0x31, 0x2c, 0x99, 0x17, 0x1c, 0xcf, 0x54, 0x84, 0x30, 0xe2, 0x62, 0x91, 0xb2, 0xfc, 0xbc, 0xa0, 0x20, 0xa2, 0x68, 0x69, 0x6e, 0x80, 0x03, 0xfa, 0x27, 0xe6, 0x44, 0x50, 0xa1, 0xc2, 0xbc, 0x1a, 0x46, 0x08, 0x91, 0x11, 0x4a, 0xdc, 0xde, 0x9d, 0x76, 0x2e, 0xe4, 0xa6, 0xcd, 0x9d, 0x7e, 0x0e, 0xcc, 0xb1, 0x66, 0x18, 0xfb, 0x2c, 0x32, 0xf6, 0x69, 0xc6, 0x5d, 0x93, 0x66, 0xdc, 0x8d, 0x11, 0xd6, 0x07, 0xaa, 0x62, 0x28, 0x60, 0xe5, 0x43, 0xbc, 0x39, 0x10, 0x05, 0x4e, 0x13, 0x33, 0x8a, 0x6c, 0x8c, 0xf5, 0x19, 0x23, 0x28, + 0x84, 0xe0, 0x69, 0xc0, 0x1a, 0x88, 0xf2, 0x66, 0x14, 0x43, 0xf0, 0xd4, 0x28, 0xe2, 0x4d, 0xf6, 0x82, 0x6a, 0x8f, 0xa7, 0xba, 0xc0, 0x9e, 0xfc, 0xdc, 0x30, 0x66, 0x6e, 0x43, 0x9b, 0x57, 0x4b, 0x9f, 0xd6, 0xe1, 0x8c, 0x98, 0xd4, 0xe9, 0xc7, 0x7e, 0x29, 0x13, 0x7d, 0xa3, 0x52, 0x5a, 0xe9, 0x7b, 0x72, 0xcd, 0x1f, 0x6b, 0xd7, 0x8f, 0x59, 0xda, 0xa4, 0x1b, 0xd7, 0x20, 0x55, 0xdd, 0xc4, 0x5f, 0x62, 0xe8, 0xb8, 0x7f, 0xe2, 0x37, 0xe8, 0xcf, 0xd2, 0x40, 0x25, 0xba, 0xd3, 0xf7, 0xe4, 0xda, 0xf7, 0x6a, 0xf1, 0x09, 0x4f, 0x4d, 0x7e, 0x56, 0x56, 0x7e, 0x8d, 0xc7, 0x53, 0x43, 0x6a, 0xac, 0x61, 0x75, 0x89, 0xc2, 0xfe, 0x6f, 0xaf, 0x7d, 0xaf, 0x6e, 0xfd, 0xf6, 0xa9, 0xc2, 0x95, 0xe8, 0xdb, 0xbe, 0x89, 0xdf, 0x26, 0x0a, 0xa7, 0x52, 0xd1, 0xe6, 0x35, 0xd2, 0xdf, 0xeb, 0xb0, 0xae, + 0x4a, 0xea, 0xf0, 0xc9, 0xf7, 0x6e, 0x51, 0xf6, 0x30, 0xeb, 0x13, 0x76, 0xc3, 0xb8, 0xfa, 0x08, 0x9a, 0x4a, 0xd2, 0xf1, 0x62, 0x69, 0x77, 0xd2, 0xc2, 0x66, 0x19, 0xd3, 0x13, 0x0c, 0x87, 0xfd, 0x9c, 0xd2, 0x76, 0x06, 0xf7, 0x0a, 0xc2, 0xf0, 0x58, 0x5f, 0xc5, 0xd0, 0x55, 0x8b, 0x16, 0x5d, 0x35, 0x54, 0x91, 0xfc, 0xbc, 0x25, 0xa7, 0xaa, 0x3f, 0x12, 0xe9, 0xaf, 0xca, 0xd9, 0xb9, 0x7e, 0xfd, 0x4e, 0x1e, 0x2d, 0xb8, 0x62, 0x5d, 0x55, 0xd5, 0xba, 0x2b, 0x16, 0x2c, 0xb8, 0x92, 0x7c, 0x5e, 0xb9, 0xa0, 0x6a, 0x45, 0x83, 0xd7, 0xdb, 0xb0, 0xa2, 0x6a, 0x7c, 0xff, 0x7e, 0xca, 0xc3, 0x4f, 0xaa, 0xb9, 0x7b, 0xd8, 0x4f, 0xf8, 0xf7, 0x18, 0x11, 0x17, 0x7c, 0xfe, 0x24, 0x93, 0x78, 0x06, 0x3a, 0xc5, 0x27, 0x22, 0x8f, 0xe8, 0x33, 0xe4, 0x96, 0xf9, 0x3e, 0x72, 0x27, 0x7c, 0x73, 0xd0, + 0xe4, 0x3f, 0xa4, 0x1b, 0x71, 0x88, 0xfd, 0x8c, 0xb1, 0x13, 0x44, 0x2f, 0x15, 0x3d, 0xb3, 0x16, 0x88, 0x0d, 0x36, 0x83, 0x39, 0x66, 0x0d, 0xc3, 0xf3, 0x23, 0xdd, 0xd3, 0x3c, 0x4e, 0xc9, 0x41, 0x49, 0x20, 0x90, 0x29, 0xaa, 0x1c, 0x24, 0xdc, 0xe8, 0x74, 0xd4, 0x88, 0x8a, 0x28, 0x35, 0x8d, 0x80, 0xbf, 0x38, 0x74, 0xbd, 0xa7, 0x7d, 0xfb, 0x02, 0x4f, 0x2c, 0x5b, 0xc1, 0x39, 0x82, 0xbd, 0xf1, 0x5b, 0x6f, 0xfd, 0xfe, 0xf5, 0xe8, 0x1e, 0xc9, 0xda, 0xb0, 0xa5, 0xaf, 0x48, 0xa5, 0xdc, 0x64, 0xe4, 0xda, 0xfa, 0xd0, 0x7f, 0x5d, 0x8b, 0xbe, 0x27, 0x35, 0x50, 0xfa, 0x55, 0xc0, 0x7f, 0x01, 0xfe, 0x49, 0xa0, 0x5f, 0x0e, 0xc1, 0xf5, 0x17, 0x51, 0xf2, 0xca, 0x4e, 0x91, 0xb4, 0xfd, 0x5e, 0x47, 0xac, 0x1d, 0x47, 0x08, 0x3d, 0x87, 0x59, 0x6a, 0xf2, 0x28, 0xe3, 0x58, 0xce, 0x44, 0x54, + 0x99, 0x6d, 0x01, 0x6e, 0xf4, 0x25, 0x8c, 0x35, 0xe0, 0x2f, 0x2a, 0x4e, 0xd9, 0x6f, 0x97, 0x06, 0x2d, 0x96, 0x60, 0xe9, 0x9b, 0x5f, 0x3c, 0x71, 0xf3, 0xcd, 0x27, 0xbe, 0xc8, 0xdd, 0x6f, 0x0e, 0x94, 0xc2, 0xb3, 0x80, 0x19, 0x3e, 0xb3, 0x1d, 0xf0, 0x79, 0x72, 0x42, 0x5a, 0x8a, 0xee, 0x49, 0x34, 0x0e, 0x4d, 0x4a, 0xd0, 0x36, 0x12, 0x5f, 0x41, 0xcd, 0x78, 0xe2, 0xae, 0x19, 0xb7, 0x26, 0x72, 0x1c, 0x69, 0x4a, 0x90, 0xd4, 0xb5, 0xc9, 0xd4, 0x0b, 0xf1, 0xc8, 0x8f, 0x5e, 0xff, 0x11, 0xfc, 0xc3, 0x5f, 0x99, 0x58, 0x83, 0xbf, 0xb2, 0x17, 0xef, 0x9b, 0xd8, 0x4f, 0x75, 0x7a, 0x02, 0x96, 0x28, 0xb1, 0xff, 0x48, 0x53, 0xdf, 0xc8, 0x19, 0xeb, 0x43, 0xd2, 0xe5, 0xdf, 0xb9, 0x1c, 0xfe, 0xa1, 0x97, 0xa5, 0x72, 0xf4, 0xf2, 0x18, 0x31, 0xfd, 0xa1, 0xf5, 0x2d, 0x96, 0x2e, 0xc0, 0x02, + 0xec, 0xd9, 0x36, 0xe6, 0x31, 0x19, 0x23, 0x44, 0x49, 0x43, 0x02, 0x39, 0xc8, 0x27, 0x46, 0x49, 0x44, 0xdc, 0x6c, 0xa8, 0x7f, 0x1d, 0x8d, 0x63, 0xc4, 0x2e, 0x27, 0x44, 0xec, 0xa6, 0x50, 0x3f, 0x2b, 0x64, 0x3f, 0xab, 0x84, 0xe7, 0xd1, 0xe9, 0xf3, 0x6c, 0x62, 0x92, 0x31, 0xba, 0xed, 0x32, 0x52, 0x10, 0xb3, 0x6a, 0x56, 0x5e, 0x96, 0x5e, 0x96, 0x13, 0x40, 0x1c, 0xba, 0xd7, 0x93, 0x41, 0x3d, 0x35, 0x4b, 0x32, 0xe6, 0xa6, 0x8d, 0xb1, 0xe9, 0x8d, 0x9e, 0x40, 0x20, 0x61, 0x11, 0x3e, 0x0b, 0x77, 0xa4, 0x0e, 0x11, 0x4c, 0x4b, 0x2c, 0xcc, 0x86, 0x47, 0xf9, 0xc5, 0x89, 0xa3, 0xbf, 0x27, 0xe0, 0x23, 0x7a, 0x13, 0x05, 0x1f, 0xe9, 0x19, 0xe4, 0x4f, 0xfc, 0xf3, 0x55, 0x79, 0xfd, 0xf5, 0x4b, 0xb7, 0xf2, 0x2e, 0xfe, 0x3e, 0xd8, 0x7d, 0xdb, 0x98, 0x8f, 0xe4, 0x86, 0xda, 0xac, 0xd0, + 0x98, 0x36, 0xc4, 0x28, 0x2b, 0x74, 0x0c, 0x4c, 0xe4, 0x28, 0x52, 0xa0, 0x02, 0x24, 0xf2, 0x7c, 0x97, 0x83, 0x24, 0x71, 0x69, 0x93, 0x12, 0x38, 0x94, 0xf9, 0x29, 0x14, 0x15, 0xea, 0xfd, 0x40, 0x4f, 0x0a, 0xb7, 0xa6, 0xc2, 0x82, 0x8d, 0xd2, 0x2e, 0xd1, 0x03, 0xe1, 0x31, 0x31, 0x49, 0x96, 0x33, 0x17, 0xd9, 0x36, 0xb3, 0x48, 0xbc, 0x0a, 0x24, 0x23, 0x25, 0xc7, 0x2a, 0xc7, 0x18, 0x41, 0x41, 0x00, 0xc4, 0xc6, 0x92, 0xa8, 0x2c, 0xe9, 0x0b, 0x33, 0x53, 0x65, 0x09, 0x01, 0x5b, 0x9a, 0xc2, 0x01, 0x0f, 0xfc, 0x0d, 0x05, 0xbd, 0xd4, 0x85, 0x73, 0x06, 0x04, 0x87, 0x7e, 0xba, 0xf9, 0x15, 0xc5, 0x39, 0xd4, 0x9f, 0x0a, 0x63, 0x32, 0x0b, 0x3a, 0x8c, 0xb5, 0x95, 0x6c, 0x6b, 0x3d, 0x74, 0x83, 0xa7, 0x61, 0xb0, 0x7a, 0xf1, 0xc5, 0x15, 0xb9, 0x8b, 0x0e, 0x0c, 0x94, 0x2e, 0x28, 0xb4, + 0x08, 0x36, 0x9d, 0x36, 0x1c, 0xe8, 0x5f, 0x34, 0x1b, 0xf1, 0x24, 0xb3, 0xf1, 0xeb, 0xa3, 0x4b, 0xaf, 0x19, 0xae, 0xa8, 0x1d, 0xff, 0xda, 0xc8, 0xe0, 0xfe, 0x08, 0x8b, 0x1b, 0xca, 0xbe, 0xf7, 0xe4, 0x9a, 0x2b, 0x97, 0xe6, 0xf6, 0xb4, 0xb2, 0x96, 0x85, 0x57, 0xad, 0xad, 0xd0, 0x1b, 0x97, 0xd8, 0x32, 0x74, 0xc3, 0x1b, 0x3e, 0xdf, 0x37, 0x1d, 0x1a, 0x65, 0xd1, 0xd6, 0xde, 0x95, 0x2d, 0x97, 0x3d, 0xbf, 0x77, 0xfb, 0xf3, 0xd7, 0xf6, 0xd5, 0xc6, 0x28, 0x4e, 0xca, 0xad, 0xdc, 0xcf, 0x39, 0x05, 0xa3, 0x80, 0x7d, 0xef, 0x97, 0x71, 0x55, 0x14, 0x91, 0x00, 0x43, 0xac, 0x90, 0x44, 0xf6, 0x2a, 0x65, 0x44, 0x9e, 0x98, 0x9d, 0x8c, 0x01, 0x3b, 0x62, 0x78, 0xe0, 0x08, 0x04, 0x38, 0x35, 0xe9, 0x22, 0x98, 0x08, 0x1f, 0x38, 0xda, 0x4d, 0xac, 0xb3, 0x53, 0xc8, 0x2e, 0xf2, 0x50, 0xcc, + 0xa5, 0xe0, 0xb6, 0x99, 0x05, 0x49, 0xf8, 0x0c, 0xb9, 0x0c, 0xa3, 0x64, 0x15, 0xac, 0x52, 0x71, 0xe6, 0xb2, 0xcc, 0x54, 0x51, 0xb2, 0xf1, 0x31, 0x4c, 0x5d, 0x4d, 0x35, 0x31, 0x8e, 0x2b, 0x20, 0xf1, 0xb2, 0x2c, 0x26, 0xad, 0x9a, 0x80, 0xea, 0xfa, 0xd5, 0xc4, 0xef, 0xbb, 0x5c, 0x3e, 0xa5, 0x4c, 0x51, 0x7e, 0x3a, 0xe1, 0xa9, 0xf7, 0x95, 0x2f, 0x01, 0x65, 0x4c, 0x8e, 0x2c, 0x53, 0x80, 0x33, 0x56, 0x36, 0xcf, 0x13, 0x1f, 0xac, 0xbd, 0xe8, 0x1a, 0x23, 0xfb, 0x4c, 0x82, 0xea, 0x95, 0xbb, 0xab, 0x37, 0x3e, 0x7a, 0x59, 0x67, 0xd3, 0x45, 0x8f, 0x6c, 0x5b, 0xf9, 0xe5, 0xf1, 0x46, 0x93, 0x63, 0x62, 0x84, 0x2d, 0xea, 0x1b, 0x6b, 0x6d, 0x5c, 0xd5, 0x5a, 0x6c, 0xb0, 0x69, 0x2b, 0x93, 0xa3, 0x84, 0xb7, 0x54, 0xaf, 0xed, 0x08, 0x5f, 0xbb, 0xf7, 0xe4, 0x42, 0x99, 0xec, 0x4d, 0x16, + 0x53, 0xfb, 0x95, 0x2f, 0x1d, 0xdc, 0x74, 0xfc, 0x8a, 0x79, 0x75, 0x3b, 0xee, 0xdd, 0x52, 0x98, 0xb3, 0xe2, 0x0b, 0xcb, 0x0b, 0x5c, 0x3e, 0x97, 0xa1, 0x43, 0x06, 0xd1, 0x91, 0xef, 0x14, 0x16, 0xc2, 0x58, 0x0c, 0xc3, 0x58, 0xcc, 0x67, 0x1e, 0x8f, 0x5b, 0x6c, 0x88, 0x45, 0x6a, 0xe0, 0xc0, 0xb5, 0x30, 0x22, 0x90, 0x26, 0xb4, 0x23, 0x95, 0x82, 0x4f, 0x00, 0xea, 0x54, 0xa6, 0xf0, 0xd0, 0x92, 0x04, 0xe3, 0x80, 0x4e, 0x1c, 0x9f, 0x32, 0xe9, 0xa6, 0x74, 0x92, 0x8d, 0x4a, 0x29, 0x8d, 0xe9, 0x79, 0xee, 0x98, 0x0a, 0x68, 0xdc, 0x70, 0x4a, 0x51, 0x02, 0x3e, 0x4c, 0x0e, 0x84, 0xcf, 0x50, 0x05, 0x33, 0x55, 0xc3, 0x40, 0xdc, 0xd6, 0xdd, 0xd9, 0xd2, 0xd4, 0xd8, 0x50, 0x5d, 0x19, 0x8d, 0x14, 0xe6, 0xe7, 0x85, 0xbd, 0x6e, 0xa7, 0xc3, 0x62, 0xf2, 0x12, 0x3a, 0x07, 0x52, 0x91, 0x0c, + 0x63, 0x53, 0xd1, 0x7b, 0x67, 0x20, 0xa9, 0x59, 0x6a, 0x90, 0x39, 0xb5, 0x4c, 0x66, 0x91, 0x3b, 0x05, 0x35, 0x84, 0x1f, 0x2d, 0x5b, 0x5f, 0xda, 0x7f, 0x74, 0x5b, 0xe3, 0xc8, 0xe2, 0xc2, 0x86, 0x90, 0xa1, 0x6c, 0xe8, 0xc6, 0xa1, 0xf1, 0xbb, 0xaa, 0x68, 0x44, 0xa9, 0x40, 0xac, 0xaf, 0x2a, 0x3a, 0x3f, 0x9a, 0xb5, 0x7b, 0xcb, 0xf8, 0xf8, 0x45, 0xbb, 0x72, 0xaa, 0x16, 0x46, 0x1a, 0x16, 0xd5, 0x84, 0x8d, 0xd3, 0x28, 0x9f, 0x5c, 0x1f, 0x36, 0x93, 0x3e, 0x6f, 0xfe, 0xf6, 0xf6, 0xa5, 0x7b, 0xdc, 0x86, 0xf2, 0xda, 0xc6, 0x9c, 0x8a, 0xa1, 0x9e, 0xc2, 0x96, 0x8a, 0x6e, 0x8a, 0xda, 0x6f, 0x09, 0x46, 0x9c, 0xf1, 0xbe, 0x03, 0x4b, 0xda, 0x03, 0x0d, 0x45, 0x59, 0x39, 0xfe, 0x9c, 0xe4, 0x20, 0xa4, 0x90, 0x8c, 0x28, 0x76, 0xff, 0xe4, 0x27, 0x14, 0x6f, 0xba, 0x86, 0x59, 0x15, 0xd7, + 0x94, 0xa9, 0x30, 0xc2, 0x61, 0x13, 0xe1, 0xaa, 0x89, 0x75, 0x61, 0xe3, 0x69, 0x5c, 0x3a, 0x12, 0xc5, 0x70, 0x44, 0x16, 0x2f, 0x88, 0x6b, 0x3d, 0xb5, 0xf2, 0x4d, 0x9f, 0x44, 0x8d, 0x7b, 0x8d, 0x04, 0x84, 0x2c, 0x3f, 0x37, 0xc7, 0x69, 0xd4, 0xeb, 0x34, 0x4c, 0x0d, 0xaa, 0x11, 0x61, 0xbb, 0xe4, 0x13, 0x53, 0x31, 0xa6, 0x4b, 0x44, 0xaf, 0xf3, 0x98, 0xa7, 0x9c, 0xa4, 0xa8, 0x96, 0x15, 0xf5, 0xc8, 0x71, 0xec, 0x82, 0x21, 0xf9, 0x26, 0x13, 0xaf, 0xdc, 0x78, 0xfc, 0x50, 0x6f, 0x6e, 0xe7, 0x48, 0xbd, 0xbb, 0xc9, 0x7d, 0xa7, 0xd7, 0x89, 0x2e, 0x28, 0x59, 0xd2, 0x10, 0x28, 0x5b, 0xbc, 0xbd, 0xa1, 0x61, 0x7c, 0x71, 0x59, 0xa0, 0x61, 0x49, 0x09, 0xda, 0xe1, 0xf4, 0x7e, 0xcd, 0xdd, 0xec, 0x21, 0xf1, 0x41, 0x7a, 0x0f, 0x3d, 0xa1, 0x6e, 0xbb, 0xe4, 0xde, 0x75, 0x9d, 0x07, 0x46, + 0x17, 0x58, 0x79, 0x7e, 0x6d, 0x96, 0xf7, 0x57, 0x8a, 0x9a, 0xc1, 0x9d, 0x4d, 0xbd, 0xe3, 0x3d, 0xc1, 0x60, 0xcf, 0x78, 0x6f, 0xd3, 0xce, 0xc1, 0x1a, 0xc5, 0xaf, 0xbc, 0x59, 0x6b, 0x05, 0xde, 0xda, 0x37, 0x7a, 0xa0, 0x73, 0xdd, 0xbd, 0x97, 0xb4, 0x31, 0x49, 0x6c, 0x6b, 0xf6, 0xab, 0x40, 0x03, 0x15, 0xe3, 0x21, 0x71, 0xf9, 0x72, 0xc8, 0xd5, 0x62, 0x17, 0xe9, 0x8c, 0x1c, 0x3e, 0x0d, 0x21, 0x8a, 0x2a, 0x30, 0xd2, 0x4d, 0xba, 0x3a, 0x4c, 0x7c, 0xc1, 0x6c, 0xb2, 0xf3, 0x83, 0x5a, 0x29, 0x32, 0x2a, 0xa4, 0x12, 0x13, 0x66, 0x68, 0xd4, 0xcf, 0x51, 0x0f, 0xed, 0x0f, 0xb1, 0x45, 0xb2, 0xe5, 0xa6, 0x27, 0xe9, 0xf3, 0x68, 0x45, 0x4f, 0x57, 0xcc, 0x8f, 0xda, 0xd7, 0x6f, 0xdc, 0x83, 0x07, 0xef, 0x29, 0xb1, 0xfe, 0x52, 0xed, 0xcc, 0xee, 0x7b, 0x06, 0x45, 0xd7, 0xdf, 0x78, 0x5e, + 0xe1, 0x8e, 0x51, 0xbf, 0x1d, 0x9b, 0xb3, 0x8a, 0x1b, 0x83, 0xbd, 0xfd, 0xd2, 0xc3, 0x13, 0xff, 0x13, 0x44, 0x2d, 0x86, 0x4c, 0x69, 0x07, 0xfb, 0x8f, 0xfc, 0x79, 0xa3, 0x8d, 0xeb, 0x6f, 0xf1, 0x98, 0x83, 0xb4, 0x8d, 0x23, 0x93, 0x9f, 0xb0, 0xf3, 0xd9, 0xb7, 0xa9, 0xdf, 0xef, 0x72, 0x79, 0x6c, 0xcc, 0x49, 0x29, 0x69, 0xa4, 0x7b, 0xba, 0xf9, 0xb5, 0x8c, 0xb8, 0x9e, 0x26, 0x71, 0x1b, 0x19, 0x35, 0x23, 0xd1, 0x90, 0x61, 0x7c, 0x87, 0xa6, 0x5b, 0x63, 0x0f, 0x3c, 0x41, 0xbc, 0x71, 0x39, 0x95, 0xed, 0xac, 0xde, 0xb8, 0xec, 0xfc, 0x86, 0x0d, 0x5f, 0xe8, 0xe8, 0x38, 0xb4, 0xbe, 0xbe, 0x7e, 0xfd, 0xa1, 0x8e, 0x8e, 0x2f, 0x6c, 0x68, 0xf8, 0xa1, 0xb3, 0xa4, 0xc1, 0xe7, 0xab, 0x27, 0xde, 0xe6, 0xf5, 0x3e, 0x5f, 0x43, 0x89, 0x13, 0x7d, 0xb0, 0xff, 0xd9, 0x4b, 0x6a, 0x6b, 0x2f, + 0x79, 0x76, 0xff, 0xfe, 0x67, 0x2e, 0xae, 0xad, 0xbd, 0xf8, 0x99, 0xfd, 0x23, 0x07, 0xba, 0x9d, 0xce, 0xee, 0x03, 0x23, 0xeb, 0x2f, 0xeb, 0xce, 0xce, 0xee, 0xbe, 0x2c, 0x79, 0xaf, 0x88, 0x3e, 0x60, 0x7f, 0x09, 0x5a, 0x4a, 0xe7, 0x13, 0x1a, 0x1e, 0x13, 0x19, 0x50, 0x8e, 0xe8, 0x63, 0x22, 0xb7, 0x77, 0x20, 0xa5, 0x8c, 0xb1, 0xf2, 0xc4, 0x4a, 0x5c, 0x92, 0xcf, 0x78, 0x9c, 0x34, 0x14, 0x1e, 0x88, 0x13, 0xa3, 0x6d, 0xe2, 0x3e, 0x9f, 0x08, 0x00, 0x3f, 0x0d, 0x41, 0x04, 0xa1, 0x0f, 0xa6, 0x81, 0x7c, 0x9c, 0x82, 0xc5, 0x41, 0x68, 0xba, 0x72, 0xf2, 0x13, 0xfc, 0xdf, 0xb4, 0x0d, 0xf9, 0xcc, 0x85, 0x8f, 0xcb, 0xc6, 0x13, 0x32, 0x69, 0xed, 0x09, 0x74, 0x8c, 0x61, 0x2a, 0x27, 0x4d, 0x47, 0xc7, 0x48, 0xc4, 0x1d, 0x3a, 0x4d, 0x86, 0x6d, 0x44, 0x30, 0xb1, 0x33, 0x1c, 0xe6, 0xc6, + 0x93, 0x19, 0xa6, 0xa7, 0x01, 0xff, 0xd6, 0x23, 0x86, 0x2c, 0x0c, 0xb3, 0x51, 0xe4, 0x19, 0x2d, 0xd2, 0xca, 0xf0, 0x1a, 0x15, 0x53, 0xc1, 0x29, 0x8d, 0x69, 0xb1, 0x35, 0xa8, 0x5f, 0x16, 0xba, 0xbb, 0x69, 0x41, 0x5f, 0xbd, 0x37, 0x16, 0x34, 0xf5, 0x44, 0x55, 0x06, 0xa5, 0xa3, 0xd6, 0x53, 0xb8, 0xa2, 0xb3, 0xb8, 0x78, 0xc9, 0x85, 0x5d, 0x3d, 0xbb, 0x17, 0x15, 0x54, 0x94, 0x1c, 0xd7, 0x57, 0x7b, 0x1f, 0x38, 0xb6, 0xa8, 0x7b, 0xde, 0x42, 0x7b, 0x61, 0xad, 0xaf, 0x75, 0x09, 0x7a, 0x47, 0x63, 0xd3, 0xe8, 0x8d, 0xfe, 0xce, 0x6d, 0xdd, 0xad, 0x17, 0xae, 0xac, 0x88, 0xac, 0xdc, 0x3f, 0xaf, 0xf3, 0xfa, 0x5e, 0x94, 0x93, 0xe3, 0x62, 0x58, 0x32, 0x9f, 0xf0, 0x07, 0x30, 0x9f, 0xf4, 0x30, 0xe7, 0xe3, 0xcc, 0x91, 0xc7, 0xcb, 0x28, 0x94, 0x86, 0xdc, 0xfb, 0x9c, 0x53, 0x10, 0x35, + 0x46, 0x66, 0x20, 0x6a, 0x24, 0xa6, 0xd8, 0x19, 0xb3, 0x6d, 0xe3, 0x28, 0x22, 0x1c, 0xec, 0x6a, 0x44, 0xb2, 0x1e, 0x4a, 0x66, 0x9d, 0x9e, 0x0e, 0xd4, 0xb0, 0x1a, 0x0d, 0x88, 0xa9, 0xa9, 0xca, 0xcf, 0x75, 0xd8, 0x0d, 0x1e, 0xa3, 0x47, 0x8e, 0xfd, 0x78, 0x2a, 0x38, 0x87, 0xc5, 0x38, 0xc5, 0x4b, 0x81, 0x54, 0x67, 0x40, 0xe6, 0x40, 0xf7, 0x06, 0x62, 0x7e, 0xbd, 0x2d, 0xb7, 0x3c, 0xdb, 0x59, 0x1e, 0xb6, 0x75, 0xb5, 0xd7, 0x35, 0x65, 0xb8, 0x4b, 0xdc, 0xee, 0x62, 0x77, 0x46, 0xd3, 0xd6, 0xbe, 0xf6, 0xe2, 0x45, 0xbb, 0x3a, 0x3a, 0x77, 0x2d, 0x2e, 0x6e, 0xef, 0xaf, 0xaf, 0x2b, 0x98, 0x37, 0x1a, 0x8f, 0x6f, 0x9e, 0x97, 0x7f, 0xbd, 0x25, 0x18, 0xcd, 0xf1, 0x47, 0x7d, 0x04, 0xdd, 0xd2, 0x5f, 0xdf, 0x8f, 0x9f, 0x6a, 0xab, 0x4c, 0x8a, 0xee, 0x95, 0x6d, 0xd2, 0xe3, 0x39, 0xbb, + 0x16, 0x34, 0x8f, 0xf6, 0xe6, 0xe6, 0xf6, 0x8e, 0x36, 0x2f, 0xd8, 0x95, 0x93, 0x33, 0xda, 0x5d, 0xb5, 0x96, 0xb8, 0x1b, 0xaf, 0xad, 0x3a, 0x26, 0xf3, 0xcf, 0x55, 0x30, 0x91, 0x3e, 0x05, 0x3a, 0x16, 0x31, 0x0b, 0xe3, 0x2a, 0x07, 0x8c, 0x78, 0x3e, 0xec, 0xe5, 0x38, 0x11, 0x67, 0xdd, 0xc2, 0x90, 0xe0, 0xda, 0x30, 0xf6, 0x84, 0x77, 0x4c, 0x99, 0x01, 0x92, 0xd9, 0xcc, 0x52, 0x3b, 0x8f, 0xa1, 0xe9, 0x8f, 0x07, 0xe2, 0x19, 0x88, 0xf1, 0xe4, 0x18, 0xf5, 0x3c, 0xc7, 0x14, 0xa1, 0x42, 0x9e, 0x92, 0x01, 0x18, 0xa2, 0x47, 0x46, 0x57, 0x61, 0x13, 0x93, 0x04, 0xe6, 0x77, 0xb1, 0x1c, 0xdb, 0x10, 0xcb, 0xe6, 0x1e, 0x30, 0x5d, 0xf0, 0x63, 0x39, 0xfe, 0x89, 0x0d, 0x19, 0x26, 0xc4, 0x81, 0xb2, 0x69, 0x6f, 0xa9, 0xb2, 0xe4, 0x7a, 0x6d, 0x82, 0x22, 0x3f, 0x38, 0xa2, 0xaa, 0x5b, 0xbe, + 0xbd, 0xbe, 0x79, 0x5b, 0x5f, 0x61, 0xa0, 0xa6, 0xc3, 0xff, 0x98, 0xaf, 0xd5, 0xf9, 0x54, 0xd0, 0xb9, 0xab, 0xc9, 0x54, 0x60, 0x40, 0xae, 0x40, 0x53, 0xbc, 0xd1, 0x5f, 0xdb, 0xa8, 0x73, 0xe4, 0x3a, 0xb2, 0xcc, 0xa2, 0xbd, 0x73, 0x4b, 0x87, 0x3f, 0xaf, 0x67, 0x43, 0x43, 0x74, 0x70, 0xe1, 0xbc, 0xf0, 0x8b, 0x6a, 0x2d, 0xb5, 0x4d, 0xfa, 0x04, 0xbf, 0x00, 0xeb, 0xa3, 0x84, 0xf9, 0x4a, 0x3c, 0xa3, 0x88, 0xc7, 0x3c, 0x2a, 0x44, 0x2c, 0x6f, 0x45, 0x98, 0x63, 0x13, 0x33, 0x25, 0xc0, 0xb0, 0x3c, 0xc7, 0x53, 0xc8, 0x3f, 0x86, 0x67, 0x65, 0x01, 0x86, 0xaa, 0xf3, 0x23, 0x29, 0xe8, 0xbf, 0x51, 0x9c, 0x5c, 0x33, 0x73, 0xc8, 0xbc, 0x8d, 0x88, 0x47, 0xbe, 0x54, 0x3e, 0x01, 0x88, 0x23, 0x10, 0x84, 0xe7, 0x44, 0xfe, 0xa9, 0x5c, 0x04, 0xd4, 0xd4, 0x14, 0xf6, 0x85, 0xc2, 0x3e, 0x2f, + 0xd5, 0x0c, 0xc9, 0x65, 0xec, 0xac, 0x75, 0x33, 0x05, 0x28, 0x97, 0x9c, 0x4a, 0x82, 0x18, 0x32, 0x7a, 0xf0, 0xd7, 0xcb, 0xeb, 0x5e, 0x73, 0x66, 0x95, 0x58, 0xdb, 0xb7, 0x74, 0x06, 0x23, 0xcb, 0x2f, 0xe9, 0x2a, 0xee, 0x6f, 0xaf, 0xb5, 0xd5, 0xa8, 0xec, 0x3a, 0xa3, 0x27, 0x14, 0x09, 0xba, 0x63, 0xb9, 0x56, 0xbd, 0xbf, 0x3a, 0xd7, 0x59, 0xe3, 0xdc, 0x3d, 0x0f, 0xfd, 0x94, 0xdd, 0x9f, 0x57, 0xf8, 0x0c, 0xcf, 0x13, 0x5e, 0xdc, 0x7b, 0xe9, 0x60, 0xb9, 0xc9, 0x57, 0x68, 0x5f, 0xac, 0xd3, 0x58, 0xec, 0x16, 0x4b, 0x6e, 0x4d, 0xd0, 0x53, 0x99, 0x6b, 0xd1, 0x68, 0x7d, 0xe8, 0x1f, 0xd0, 0xc0, 0x41, 0xa0, 0xd5, 0x5b, 0x30, 0x0f, 0xaa, 0x99, 0xfd, 0x71, 0x95, 0x16, 0xb1, 0x5c, 0x16, 0x4c, 0xfc, 0xe4, 0x2e, 0xea, 0x24, 0xae, 0xc5, 0x40, 0x35, 0x12, 0x76, 0x9b, 0x32, 0xdd, 0x29, + 0x0d, 0x77, 0x34, 0x15, 0x23, 0xe3, 0x0c, 0x99, 0xb6, 0x21, 0x0a, 0x6c, 0xca, 0x10, 0x18, 0x60, 0x42, 0x86, 0x44, 0xbe, 0xa9, 0x54, 0x20, 0x87, 0xd2, 0xaf, 0xb7, 0x14, 0x14, 0x51, 0xf8, 0x0a, 0x63, 0x72, 0x76, 0x08, 0x53, 0xd1, 0x80, 0x81, 0x1c, 0x15, 0x04, 0xe3, 0x3a, 0x69, 0x6f, 0x15, 0xa5, 0xe8, 0xd8, 0x84, 0x26, 0x3a, 0x36, 0x74, 0x81, 0xaa, 0x6a, 0xd1, 0x68, 0x7d, 0xf3, 0x86, 0x8e, 0x60, 0x5f, 0xdb, 0xa2, 0xfe, 0xfc, 0xbe, 0x9d, 0x3d, 0x9d, 0x1b, 0x7b, 0x22, 0xc6, 0x25, 0xde, 0x32, 0xb5, 0x60, 0xf6, 0x46, 0x83, 0xde, 0x88, 0xcf, 0xb0, 0x66, 0x6c, 0x68, 0xb0, 0xb1, 0xaa, 0xa8, 0x2b, 0x43, 0x50, 0x68, 0xad, 0x46, 0xfc, 0x1c, 0x99, 0x2f, 0xb9, 0x6d, 0x2b, 0x23, 0xab, 0xb7, 0xda, 0xed, 0x1b, 0x96, 0x35, 0x6c, 0xe9, 0x2f, 0x0e, 0xb4, 0xae, 0xa9, 0x0d, 0xea, 0x74, + 0x36, 0x5d, 0x28, 0x98, 0x65, 0x0b, 0x95, 0x3b, 0xda, 0xe2, 0xd7, 0xa2, 0xf6, 0x96, 0xea, 0xba, 0x8a, 0x88, 0x5a, 0xaf, 0xc6, 0xac, 0xbc, 0xd7, 0x0e, 0xc2, 0x7f, 0xaf, 0x02, 0x9d, 0xb4, 0x4c, 0x01, 0xd3, 0x2c, 0x77, 0xdc, 0x46, 0x12, 0x38, 0x06, 0x0f, 0x25, 0x41, 0x1a, 0x47, 0x12, 0xbe, 0x8f, 0x0e, 0x58, 0x40, 0x29, 0x13, 0xb2, 0x44, 0x16, 0x9a, 0x30, 0x70, 0x1c, 0x84, 0xdf, 0x20, 0x35, 0x68, 0x9b, 0x81, 0x13, 0x65, 0x94, 0x51, 0x8c, 0xd3, 0xa2, 0x4b, 0x59, 0xa6, 0x50, 0x9e, 0xee, 0x50, 0xe8, 0x14, 0xa7, 0x41, 0x86, 0x9a, 0x86, 0xab, 0x26, 0x5d, 0xae, 0x36, 0xaa, 0xd2, 0x23, 0x3b, 0x41, 0x3f, 0xaa, 0x81, 0xab, 0x3b, 0x61, 0x6d, 0xf0, 0x8c, 0x85, 0x59, 0x1d, 0x57, 0xc3, 0x3a, 0xe6, 0x8c, 0x22, 0x96, 0x97, 0x3e, 0x99, 0xec, 0x56, 0x10, 0x3b, 0xd9, 0x84, 0xf1, 0xb6, + 0xbc, 0x49, 0x30, 0x54, 0x78, 0x70, 0xc4, 0xad, 0xc4, 0x31, 0x67, 0x7c, 0x46, 0x32, 0x4d, 0x01, 0x6e, 0x08, 0x82, 0x28, 0x23, 0x58, 0x04, 0x90, 0x2e, 0xa8, 0x29, 0xb5, 0x92, 0xb8, 0xbe, 0x13, 0x6e, 0x88, 0xf4, 0xc0, 0x01, 0x58, 0xd9, 0xf8, 0xc0, 0xec, 0x8b, 0x7a, 0x08, 0x2c, 0x3c, 0x8a, 0xe8, 0xd1, 0xc7, 0x2c, 0x37, 0x81, 0x73, 0x4c, 0x86, 0xa1, 0x3b, 0xa4, 0xab, 0xf5, 0x2e, 0xce, 0x88, 0xee, 0x3c, 0x8e, 0x76, 0x22, 0x6d, 0x56, 0x8d, 0x5b, 0xda, 0x3b, 0x9a, 0x69, 0xfd, 0xc3, 0x1f, 0x6c, 0x86, 0x11, 0x3c, 0x3c, 0xf1, 0x15, 0x86, 0xda, 0x3c, 0xe2, 0x9f, 0x41, 0x7b, 0x0d, 0x4c, 0x95, 0xdc, 0x40, 0x3d, 0x41, 0x4c, 0x1f, 0x98, 0xb9, 0xc9, 0x66, 0xb0, 0x09, 0xec, 0x87, 0x35, 0xd3, 0xf7, 0x57, 0x03, 0x63, 0x30, 0x85, 0xe8, 0xfe, 0x4a, 0x66, 0x94, 0x0c, 0xc5, 0x41, 0x82, + 0x41, 0x10, 0x2f, 0x6a, 0xfc, 0xb3, 0x0c, 0x85, 0xf4, 0x82, 0xb6, 0x24, 0x7c, 0x35, 0x6a, 0xd6, 0xb9, 0xf4, 0xd2, 0xf9, 0x06, 0x07, 0xfb, 0xcb, 0x25, 0xa1, 0x22, 0xfd, 0x44, 0xc8, 0x11, 0xc2, 0x27, 0xb2, 0x8b, 0x4c, 0xd9, 0xfa, 0x2b, 0xc8, 0xb9, 0xc6, 0xfb, 0xd2, 0x85, 0x8c, 0x5d, 0x20, 0x56, 0xff, 0x41, 0x19, 0xab, 0x44, 0x4d, 0x01, 0x3b, 0xa1, 0x11, 0x9b, 0xc8, 0xe4, 0x56, 0x26, 0xbf, 0x6d, 0x1c, 0x78, 0x4c, 0xb6, 0xe8, 0x24, 0xb8, 0x15, 0xf6, 0xb2, 0x32, 0xa1, 0xeb, 0xb3, 0x12, 0x52, 0xbe, 0x01, 0xca, 0xbf, 0x20, 0xae, 0x05, 0x9d, 0x3b, 0x2f, 0x4e, 0x89, 0xa3, 0x52, 0x2a, 0x44, 0x81, 0xc5, 0x48, 0xc3, 0x74, 0xd0, 0x0a, 0x1f, 0x4f, 0x1c, 0x1c, 0x3c, 0x0a, 0x9f, 0x1b, 0x07, 0x1e, 0x35, 0x21, 0x02, 0xa8, 0x63, 0x15, 0x43, 0x20, 0x13, 0x88, 0x21, 0x62, 0x4c, 0x18, + 0xb3, 0xb6, 0xef, 0x6b, 0xdd, 0x50, 0x36, 0xdc, 0xb2, 0x6f, 0x1f, 0xfc, 0x6b, 0x15, 0x6e, 0xdf, 0xd7, 0xb2, 0xe1, 0xb3, 0x98, 0xf8, 0x03, 0xf2, 0xa0, 0x15, 0xfe, 0xc9, 0xfc, 0xfc, 0x36, 0xac, 0x62, 0x6f, 0xc3, 0xc7, 0xce, 0x66, 0x6b, 0x7a, 0x1b, 0xcb, 0x4c, 0x30, 0xf8, 0xd8, 0xb5, 0xf2, 0x99, 0x08, 0x7b, 0x1f, 0xea, 0xe1, 0x4f, 0xc2, 0x5e, 0x2a, 0x3c, 0xaa, 0xd7, 0x10, 0xfb, 0x71, 0x6a, 0x79, 0xe1, 0x42, 0x56, 0x17, 0xca, 0x40, 0x45, 0x18, 0xb8, 0xb7, 0xd5, 0xac, 0xd1, 0x79, 0x4c, 0x9a, 0x80, 0x5d, 0xc9, 0xe9, 0x6b, 0x9b, 0x16, 0xd8, 0x72, 0x0a, 0xf8, 0x9f, 0x28, 0xfc, 0xd6, 0x8a, 0xb1, 0x8d, 0x03, 0x2e, 0x65, 0x8e, 0x3a, 0x58, 0x75, 0xfd, 0x97, 0xbf, 0xda, 0x29, 0xdf, 0xf9, 0xdf, 0x87, 0x2f, 0x83, 0xba, 0x82, 0x3c, 0xf1, 0x0e, 0x16, 0xf9, 0x72, 0x14, 0xa3, 0xeb, + 0x66, 0x18, 0x6d, 0xc2, 0xcb, 0xd8, 0x6e, 0x6a, 0xcf, 0xaa, 0x78, 0x5c, 0x04, 0x7e, 0x88, 0x4b, 0xf2, 0x8d, 0x01, 0x33, 0xaf, 0x87, 0x9f, 0x61, 0xf4, 0xb1, 0xa4, 0xa1, 0x3f, 0x7f, 0x1c, 0xbf, 0x78, 0x1c, 0x6d, 0x83, 0xff, 0x48, 0x5d, 0x77, 0x70, 0xbb, 0xf0, 0x26, 0xa1, 0x11, 0x0b, 0xd8, 0x42, 0xda, 0x89, 0xc2, 0xdc, 0x1a, 0x6c, 0xe4, 0xbf, 0x03, 0x33, 0xb6, 0xe6, 0x71, 0x3e, 0x11, 0xc9, 0x59, 0x4f, 0xa7, 0x02, 0x39, 0x41, 0x59, 0x9f, 0xb4, 0x3e, 0x8d, 0x90, 0xa9, 0x90, 0x49, 0x2c, 0x35, 0xcf, 0x9f, 0xf6, 0x68, 0xe0, 0x71, 0x8b, 0x3f, 0xc4, 0x8a, 0xd6, 0xfc, 0xf4, 0x51, 0xd1, 0x50, 0x4d, 0x6e, 0x30, 0x80, 0x05, 0xad, 0xa2, 0xb6, 0xac, 0xaa, 0xbc, 0xb6, 0x29, 0x2b, 0xda, 0x1f, 0xe3, 0xae, 0xb2, 0x29, 0x14, 0x81, 0x30, 0xc6, 0xb1, 0x40, 0x61, 0x75, 0x5f, 0x73, 0x5e, + 0x57, 0x45, 0x0e, 0x93, 0x68, 0xc7, 0x3a, 0x68, 0xc7, 0xf3, 0xd0, 0x8e, 0xfe, 0x64, 0x03, 0x88, 0x85, 0xe8, 0xfa, 0x24, 0x43, 0x8b, 0xe0, 0x64, 0x40, 0xc5, 0x4c, 0xd2, 0xf5, 0xf3, 0xa7, 0x3f, 0x97, 0x41, 0x5d, 0xcf, 0x9f, 0x0d, 0xfa, 0x3f, 0x30, 0xf0, 0x78, 0x79, 0x30, 0x93, 0x36, 0x2f, 0x15, 0x63, 0x5e, 0x36, 0x9b, 0x0d, 0xc1, 0x30, 0x58, 0xcc, 0x98, 0xa9, 0xec, 0x8b, 0x66, 0x35, 0xd5, 0x96, 0x57, 0x95, 0xd5, 0x2a, 0xb4, 0x02, 0x17, 0xf4, 0xe5, 0x72, 0xeb, 0x7c, 0x35, 0xbd, 0x79, 0xcd, 0xad, 0xd5, 0x85, 0x81, 0x18, 0x42, 0x81, 0xa0, 0x42, 0x61, 0x83, 0xfa, 0x7a, 0xb8, 0xcd, 0xb8, 0x92, 0x7f, 0x14, 0xb6, 0x9e, 0x04, 0xbe, 0x1a, 0x65, 0xc5, 0x11, 0x02, 0x46, 0x01, 0xcf, 0x04, 0x0f, 0x2b, 0x82, 0x9e, 0x2d, 0xfb, 0xc7, 0xa3, 0x9e, 0x86, 0x8b, 0x9c, 0x66, 0xd5, 0x76, + 0x6e, 0x33, 0xea, 0x08, 0xe4, 0x48, 0x27, 0xe9, 0x5c, 0xf8, 0x3d, 0x7c, 0x7b, 0xf9, 0xf4, 0xe5, 0x7d, 0xb4, 0x3c, 0x1b, 0xac, 0xa8, 0x41, 0x46, 0xb4, 0x62, 0x4c, 0x6d, 0x76, 0x5e, 0xd4, 0x00, 0x25, 0xb8, 0x9c, 0x80, 0xf4, 0x6d, 0x5a, 0x7e, 0x03, 0x77, 0x1f, 0x7e, 0x96, 0xbf, 0x8e, 0x71, 0x30, 0xf9, 0xf1, 0xb0, 0x40, 0xe3, 0xfe, 0x30, 0x78, 0x15, 0x55, 0x56, 0x89, 0xbe, 0x95, 0xcf, 0x52, 0xf5, 0x83, 0x98, 0x5a, 0xa9, 0x14, 0x1c, 0x4b, 0xbc, 0x60, 0xc4, 0x44, 0x18, 0xf8, 0xc8, 0x54, 0x74, 0x45, 0xd1, 0x97, 0xc4, 0x36, 0x96, 0xbc, 0x01, 0xb4, 0xcb, 0x51, 0xd2, 0x92, 0xeb, 0xab, 0xab, 0x28, 0xb5, 0x78, 0x02, 0x68, 0x8f, 0xa3, 0xac, 0x39, 0xd7, 0x5f, 0x57, 0x51, 0xc2, 0x57, 0x84, 0x0b, 0xca, 0xfa, 0x6b, 0xdc, 0xd9, 0xde, 0xec, 0xbc, 0xfc, 0xd2, 0x85, 0xd5, 0x9e, 0x6c, + 0x0f, 0xe1, 0x6b, 0x77, 0xc3, 0x1c, 0x79, 0x1c, 0xe6, 0x88, 0x8d, 0xc9, 0x61, 0xee, 0x95, 0xd1, 0x98, 0x55, 0x02, 0x81, 0x56, 0x26, 0xed, 0xe8, 0x4a, 0x6c, 0x55, 0x89, 0x27, 0x1c, 0x43, 0xe3, 0x61, 0xc1, 0x17, 0x56, 0xfe, 0x32, 0x30, 0x90, 0x00, 0xb2, 0xa6, 0xf3, 0x86, 0x06, 0xd4, 0x89, 0x50, 0xad, 0x29, 0xf9, 0x05, 0x27, 0x37, 0x3b, 0x17, 0xb0, 0xf3, 0xc4, 0xf4, 0x4a, 0x64, 0x23, 0x76, 0xe1, 0x89, 0xdf, 0x39, 0xba, 0xdb, 0xa5, 0x32, 0x30, 0x89, 0x64, 0x39, 0x98, 0x71, 0x84, 0x0a, 0x8e, 0xba, 0x2c, 0xbb, 0xcb, 0x69, 0xcf, 0xc9, 0xca, 0xb1, 0x04, 0x6d, 0x02, 0x8c, 0xba, 0x11, 0x18, 0xe4, 0x14, 0xf3, 0x9f, 0x9a, 0x95, 0x64, 0x9c, 0x7c, 0xc6, 0x27, 0x8d, 0xd6, 0x40, 0x80, 0x55, 0x67, 0xba, 0xf2, 0xb3, 0x34, 0xd5, 0x91, 0xba, 0x46, 0x32, 0x3b, 0xd1, 0x8f, 0x9d, 0x66, + 0xf5, 0xd6, 0xed, 0x0d, 0xdc, 0x55, 0x5a, 0x1d, 0x0f, 0xd3, 0x94, 0xcb, 0xb0, 0x7b, 0x0d, 0xc9, 0x79, 0x8a, 0xfa, 0x61, 0x40, 0x25, 0x94, 0xd0, 0x0f, 0xaf, 0x07, 0x9a, 0x7c, 0x15, 0x68, 0x62, 0x67, 0xdc, 0xcc, 0xed, 0x67, 0xa1, 0x09, 0x9b, 0xa2, 0x09, 0x27, 0x7f, 0x49, 0xd2, 0xc4, 0x46, 0xfb, 0x42, 0x90, 0x4d, 0x22, 0x34, 0x48, 0x6a, 0xe2, 0xf7, 0x14, 0x45, 0x80, 0xf8, 0x6c, 0xa2, 0xc3, 0x72, 0x26, 0x3a, 0xe6, 0xf4, 0x57, 0x96, 0xba, 0x7a, 0xa5, 0x92, 0x99, 0x44, 0x2a, 0x95, 0xa3, 0x23, 0x44, 0xab, 0x38, 0xee, 0xd3, 0x5b, 0x82, 0x61, 0x5e, 0xb4, 0x27, 0xe9, 0x90, 0x3a, 0xa0, 0x48, 0x12, 0x22, 0xe0, 0xa3, 0x73, 0xee, 0x2e, 0x4a, 0x08, 0x58, 0xa5, 0x32, 0x25, 0xe4, 0x75, 0x8a, 0x26, 0xb6, 0x6f, 0x85, 0x89, 0xb8, 0x27, 0x49, 0x09, 0x8c, 0xa7, 0x93, 0x42, 0x83, 0x70, + 0x4e, 0x40, 0xa6, 0xc3, 0x21, 0xa0, 0xc3, 0xaf, 0x85, 0x97, 0x60, 0xdd, 0x96, 0x31, 0x5b, 0x66, 0x70, 0x10, 0xbf, 0xcc, 0x41, 0x94, 0x0a, 0x2c, 0x30, 0x1c, 0x2b, 0x70, 0x43, 0xc0, 0xb9, 0x40, 0x9f, 0x10, 0xd9, 0xa1, 0x19, 0x4c, 0xc5, 0x47, 0x99, 0xca, 0x99, 0x73, 0x81, 0x8a, 0x64, 0xb5, 0x5a, 0xcb, 0xac, 0x65, 0x3e, 0xbf, 0xc1, 0x17, 0x00, 0x9e, 0xa3, 0x12, 0xb3, 0x4f, 0xc7, 0x73, 0xd2, 0xcc, 0x78, 0x72, 0x2a, 0x99, 0x86, 0x17, 0xa1, 0x3b, 0xd2, 0x2e, 0x02, 0x0b, 0x2c, 0x8f, 0x34, 0x6c, 0x0a, 0x0f, 0x97, 0xf5, 0xc1, 0xca, 0xf0, 0xc0, 0xca, 0x80, 0x25, 0xe2, 0x81, 0x25, 0x12, 0x2e, 0x80, 0xa6, 0xdf, 0x01, 0x7c, 0xeb, 0x5e, 0xca, 0xb7, 0x9c, 0xcc, 0x66, 0xf9, 0x10, 0x3b, 0x8b, 0x99, 0x9a, 0xdc, 0xb2, 0x8c, 0x9f, 0xf8, 0xc2, 0x24, 0x59, 0x98, 0x35, 0xc5, 0xbd, 0x68, + 0x7a, 0x62, 0x12, 0x33, 0x94, 0x93, 0xc9, 0x97, 0xd5, 0x43, 0x33, 0x9e, 0x93, 0xc9, 0x6d, 0xb3, 0x66, 0x3b, 0xac, 0x4e, 0x9b, 0x13, 0x58, 0x9a, 0x70, 0x5a, 0x96, 0x96, 0x98, 0xdb, 0x68, 0xf7, 0x29, 0x9c, 0x0d, 0x31, 0x84, 0x2d, 0xc1, 0xd4, 0x3e, 0x95, 0xc3, 0xa1, 0x00, 0xe1, 0x54, 0xa8, 0x23, 0x31, 0x9e, 0xeb, 0x52, 0xe3, 0xf9, 0xa5, 0xe4, 0x40, 0x52, 0x4e, 0x9c, 0x6e, 0x88, 0x4e, 0x61, 0xce, 0x3e, 0xca, 0x89, 0xcf, 0x92, 0x35, 0x1e, 0x3e, 0x6d, 0xae, 0x53, 0x58, 0xf8, 0xf4, 0x91, 0xcf, 0x87, 0xbe, 0xd3, 0x91, 0x4f, 0xdb, 0x77, 0xe3, 0x69, 0x46, 0x3e, 0x0d, 0x9b, 0xff, 0xf5, 0xe9, 0x07, 0xfe, 0x54, 0xf2, 0xe0, 0x58, 0x9a, 0x81, 0xa7, 0xb4, 0xba, 0x16, 0x68, 0x75, 0x5b, 0x62, 0xec, 0x57, 0xcb, 0x63, 0x6f, 0x4b, 0x01, 0xa5, 0x91, 0x15, 0x9d, 0x5a, 0xb8, 0xa9, 0x91, + 0xb7, 0x4c, 0x8d, 0x3c, 0x9b, 0x54, 0x7b, 0x23, 0x89, 0xb3, 0x0c, 0x79, 0xe0, 0xa7, 0x3f, 0x26, 0xab, 0xd8, 0x0a, 0x9d, 0x26, 0xab, 0x38, 0x7d, 0xa7, 0x79, 0x79, 0x0d, 0xa3, 0x91, 0x53, 0x07, 0xbc, 0x62, 0x3b, 0xdd, 0x4a, 0xd2, 0xf4, 0xc8, 0x47, 0xf6, 0x16, 0xe0, 0xcf, 0x1a, 0x6e, 0x0b, 0xab, 0xa3, 0xfb, 0x52, 0x16, 0xe8, 0xaa, 0xf3, 0x12, 0x3c, 0x89, 0xf0, 0x66, 0x79, 0xe6, 0xe5, 0x77, 0x0b, 0xd3, 0x36, 0x2b, 0x90, 0xbc, 0x89, 0xed, 0x34, 0xa2, 0x1e, 0x9a, 0xd3, 0x13, 0xa8, 0x03, 0x62, 0x7e, 0xae, 0x27, 0xc7, 0x6a, 0xd6, 0xaa, 0xe9, 0x86, 0x28, 0x4e, 0x6d, 0x88, 0x81, 0xb3, 0x21, 0xed, 0x7b, 0x1b, 0xf6, 0x10, 0xa6, 0x8b, 0x2e, 0x3a, 0x25, 0x86, 0xc2, 0x07, 0xb3, 0x91, 0xf7, 0xb9, 0x2d, 0xa8, 0x8d, 0xf0, 0xe0, 0x2f, 0x9f, 0x05, 0x63, 0x9f, 0xf9, 0x3f, 0xd8, 0x37, + 0x9f, 0x38, 0xb5, 0x59, 0x9f, 0xb5, 0x6f, 0x57, 0x50, 0x1e, 0xda, 0x80, 0xcc, 0x73, 0xea, 0x1b, 0xf0, 0x54, 0xe9, 0xa9, 0xb9, 0xf4, 0x4d, 0x8e, 0x8f, 0xf0, 0x12, 0xc8, 0x03, 0xc5, 0x4c, 0x33, 0xb3, 0x4d, 0xee, 0x9b, 0x1b, 0x44, 0x01, 0xd9, 0xac, 0x59, 0x45, 0x25, 0x04, 0x91, 0x21, 0x7e, 0xb4, 0xc4, 0x6f, 0x40, 0x89, 0x44, 0x31, 0x9f, 0x5e, 0xa5, 0xa7, 0xc9, 0x02, 0x89, 0x90, 0x67, 0x29, 0xc9, 0x23, 0x0c, 0x42, 0x7e, 0x51, 0x58, 0x30, 0x10, 0x77, 0x20, 0x86, 0x84, 0xb0, 0x2a, 0xc8, 0xf3, 0xba, 0x67, 0xca, 0x15, 0xea, 0x33, 0xc8, 0x15, 0xec, 0x59, 0xa8, 0x81, 0xb9, 0xd3, 0xca, 0x1d, 0xdf, 0x3c, 0x7b, 0x8c, 0x05, 0x61, 0x3b, 0x88, 0x26, 0xb3, 0xd6, 0xa1, 0x74, 0xf3, 0xd9, 0x88, 0xf5, 0x7f, 0x8d, 0x7c, 0xcb, 0x6c, 0xe5, 0x5e, 0xc2, 0x8f, 0xf0, 0x5d, 0x67, 0xd3, 0x21, + 0xb6, 0xd2, 0xb8, 0x21, 0x5d, 0xfb, 0x68, 0x99, 0x5d, 0xdc, 0xd5, 0xf8, 0x1e, 0xfe, 0x36, 0xc6, 0x4c, 0xee, 0x69, 0x41, 0xf6, 0x21, 0x1c, 0x62, 0x15, 0x8d, 0x7a, 0x81, 0x40, 0xe2, 0xd3, 0x67, 0x6a, 0x54, 0x02, 0xcf, 0x98, 0x91, 0x99, 0x4f, 0x5e, 0x3c, 0x47, 0x92, 0xe7, 0x61, 0x64, 0x7c, 0xd0, 0x91, 0xf9, 0x9b, 0x9b, 0x9c, 0x57, 0x69, 0x3d, 0xb1, 0xbc, 0xbc, 0x98, 0x47, 0x7b, 0x95, 0xb3, 0x69, 0x33, 0x77, 0x75, 0x7e, 0xf7, 0xda, 0x4a, 0x77, 0x45, 0xc8, 0x6c, 0x0e, 0x55, 0xb8, 0x2b, 0xd7, 0x76, 0xcb, 0xf8, 0xc1, 0x43, 0xd2, 0x4e, 0x74, 0xde, 0xe4, 0x93, 0x4c, 0x06, 0xd3, 0xfa, 0xb8, 0x08, 0x6b, 0x20, 0x79, 0xec, 0x6a, 0x4c, 0x59, 0x4e, 0x25, 0x62, 0x14, 0x94, 0x11, 0xe6, 0x6d, 0x9b, 0x7a, 0x0a, 0xb2, 0xce, 0xe2, 0x29, 0x17, 0xae, 0x81, 0xc7, 0x03, 0x3e, 0x2a, 0x6e, + 0x5b, 0x23, 0xf2, 0x6d, 0x87, 0x7c, 0xad, 0x1a, 0x1d, 0xfa, 0xa2, 0xa7, 0xd0, 0x91, 0x99, 0x1d, 0x30, 0xf5, 0x76, 0xb6, 0x5f, 0x5d, 0x12, 0x72, 0xe8, 0xb3, 0xf4, 0xca, 0xee, 0x3e, 0xd0, 0xdf, 0xfe, 0xcc, 0x35, 0xb3, 0x25, 0xfc, 0xe5, 0x20, 0x43, 0xad, 0x97, 0xbd, 0xa3, 0x80, 0x30, 0x2c, 0x0b, 0x32, 0x3f, 0xf1, 0xea, 0x01, 0x49, 0x89, 0x28, 0xc1, 0x43, 0x29, 0x61, 0xc8, 0x44, 0xa6, 0xfe, 0xf9, 0xb3, 0xd3, 0x88, 0x50, 0x48, 0x3c, 0x81, 0xe4, 0x34, 0x10, 0x8f, 0xc8, 0xee, 0xc1, 0x0e, 0xd2, 0x00, 0x0b, 0x0b, 0x06, 0xe4, 0x88, 0x0a, 0x76, 0xc6, 0xe6, 0x09, 0x7b, 0x48, 0x44, 0x05, 0xa3, 0x39, 0x35, 0x23, 0x3d, 0xd1, 0xd4, 0xc1, 0xd0, 0x9d, 0x55, 0xb6, 0x02, 0xaf, 0xc9, 0xe8, 0x2d, 0xc8, 0xc2, 0x86, 0xc4, 0xaf, 0x85, 0x76, 0xee, 0x2b, 0x83, 0x3a, 0x67, 0x9e, 0x33, 0xa7, + 0x20, 0x5b, 0x0b, 0xbf, 0xe4, 0x67, 0x93, 0x5f, 0xc8, 0xa1, 0x13, 0x77, 0x00, 0xda, 0xfc, 0x45, 0xc6, 0xca, 0x84, 0xe3, 0x01, 0x32, 0x26, 0x40, 0xaf, 0x55, 0xa9, 0xf3, 0xdc, 0x21, 0x06, 0x06, 0xc6, 0x68, 0xc8, 0xd4, 0x11, 0x45, 0x94, 0xb1, 0x22, 0xab, 0x8c, 0x0a, 0xa7, 0xe3, 0x7c, 0xd1, 0xb2, 0x7a, 0x8e, 0xcc, 0x1b, 0x5f, 0x79, 0x11, 0xc6, 0x9e, 0xbc, 0xae, 0xae, 0xde, 0xc1, 0x6d, 0x6d, 0xb5, 0xc3, 0x4b, 0xfb, 0x16, 0x2e, 0xaf, 0x38, 0x74, 0xe1, 0xd5, 0x16, 0x7f, 0x31, 0x6f, 0xb3, 0x78, 0x83, 0x5e, 0xcb, 0x6e, 0x2b, 0xb4, 0xd5, 0x7a, 0xcd, 0x65, 0x79, 0x55, 0xbe, 0xcc, 0x19, 0x7a, 0x51, 0x79, 0x5a, 0xbd, 0x68, 0x1d, 0xa6, 0x13, 0x77, 0xba, 0x4a, 0x04, 0x8f, 0x64, 0xcd, 0x47, 0xf5, 0xbf, 0xd4, 0x7c, 0xce, 0x41, 0x3f, 0x5c, 0x77, 0xea, 0xfa, 0x59, 0x97, 0x5c, 0x3f, + 0xaa, 0x7f, 0xc7, 0xfa, 0x29, 0x9a, 0xfc, 0x33, 0x7f, 0x80, 0x62, 0x9c, 0x0a, 0x8f, 0x66, 0xf0, 0xa0, 0x4f, 0x2b, 0x11, 0x2f, 0x22, 0x36, 0x64, 0x44, 0x31, 0x25, 0x8e, 0x21, 0xa3, 0x95, 0xe5, 0x45, 0xf6, 0x41, 0xe9, 0x73, 0x34, 0xb6, 0x5a, 0xba, 0xe6, 0x4f, 0x77, 0xfe, 0x59, 0xba, 0x62, 0x35, 0xba, 0x10, 0xf1, 0xd2, 0x95, 0xf0, 0xf1, 0xa7, 0xbb, 0xfe, 0x0c, 0x8f, 0xd9, 0x85, 0xd2, 0xe5, 0x13, 0xd7, 0x23, 0x46, 0xba, 0x7b, 0x0b, 0x1a, 0x44, 0xec, 0x11, 0x10, 0x79, 0x2e, 0x42, 0xf8, 0x7a, 0x28, 0xb2, 0x7c, 0x8b, 0x74, 0x97, 0x74, 0x84, 0xbc, 0x47, 0xc1, 0xac, 0x9c, 0xcc, 0x53, 0xac, 0x17, 0x4a, 0x41, 0x6f, 0xf7, 0x31, 0x51, 0xa6, 0x9d, 0x19, 0x60, 0x36, 0x33, 0x97, 0x30, 0xd7, 0x31, 0xaf, 0xc5, 0x7f, 0x2e, 0x22, 0x5e, 0xb0, 0x09, 0xbc, 0x6d, 0x2c, 0xc7, 0xe9, 0x60, + 0xed, 0x59, 0x8a, 0x2c, 0xbb, 0x62, 0x2c, 0x43, 0xab, 0x66, 0x95, 0xaa, 0x4c, 0x95, 0x32, 0x73, 0xcc, 0x6a, 0x36, 0xb2, 0x7a, 0x03, 0x63, 0xd0, 0x93, 0xfb, 0x08, 0x98, 0x15, 0xe7, 0x7b, 0x40, 0xff, 0x11, 0x78, 0x4e, 0x18, 0x0a, 0x65, 0x07, 0x59, 0x9b, 0x0b, 0x65, 0xd9, 0x6d, 0x59, 0x43, 0x3e, 0x8d, 0x97, 0x55, 0xe8, 0x90, 0x4a, 0xa9, 0x50, 0x0d, 0x05, 0x4c, 0x7e, 0x36, 0xd3, 0x82, 0x0c, 0xfa, 0x4c, 0xc3, 0x90, 0x1b, 0x51, 0x87, 0x48, 0x37, 0x4a, 0x2d, 0xa7, 0x7d, 0x7b, 0xb7, 0x8c, 0xae, 0x58, 0xde, 0xd9, 0x11, 0xab, 0x08, 0xf8, 0xc9, 0x91, 0xf8, 0xe1, 0xab, 0xae, 0x38, 0x74, 0xf0, 0xb2, 0xbd, 0xd7, 0xed, 0xbb, 0x6e, 0xcf, 0xee, 0x9d, 0x3b, 0xc6, 0xb7, 0x8f, 0x5e, 0xb2, 0xe5, 0x92, 0xf5, 0x23, 0xc3, 0x6b, 0x57, 0xaf, 0x5a, 0xbe, 0x79, 0xc5, 0xe6, 0xc5, 0x8b, 0xfa, + 0x17, 0xcc, 0xeb, 0xed, 0x18, 0xe8, 0x1c, 0x68, 0x6e, 0x8a, 0xd7, 0xd7, 0xd6, 0x54, 0xb4, 0xc7, 0xda, 0x4b, 0x4b, 0x8a, 0x0a, 0xf2, 0x72, 0xfd, 0xd1, 0x40, 0x94, 0xe0, 0x73, 0xd9, 0xac, 0x06, 0x9f, 0xd1, 0xa7, 0xd5, 0xa8, 0x14, 0xc0, 0x35, 0xf4, 0x48, 0x1f, 0xa6, 0xb1, 0x6e, 0x61, 0x09, 0xf8, 0xe8, 0x39, 0x99, 0x99, 0xf0, 0x71, 0xe3, 0xac, 0xef, 0x68, 0xd6, 0x77, 0xcf, 0xbf, 0x39, 0x9d, 0x5b, 0x5b, 0x37, 0x58, 0x9f, 0x73, 0x55, 0x71, 0x5d, 0x5d, 0xd1, 0x55, 0x39, 0x75, 0x83, 0x75, 0x33, 0xbe, 0x4d, 0x14, 0xce, 0xf8, 0x2a, 0xac, 0x84, 0x0c, 0x39, 0x57, 0xc2, 0xd7, 0xe2, 0x2b, 0xe1, 0xeb, 0x3f, 0x7f, 0x3b, 0x23, 0x95, 0xfb, 0xed, 0x8c, 0xd4, 0xcf, 0xbf, 0x7c, 0xa6, 0xd4, 0x2c, 0x5f, 0xc3, 0xb2, 0x8a, 0xea, 0x9a, 0x9a, 0xea, 0x8a, 0x65, 0x0d, 0x3e, 0xc1, 0x3a, 0xfd, + 0xdb, 0xc9, 0x4f, 0x7c, 0x0d, 0x03, 0x15, 0x35, 0xf0, 0xa7, 0x62, 0xa0, 0xc1, 0x37, 0xfd, 0x77, 0xa9, 0x8e, 0x7c, 0xa3, 0xf9, 0x12, 0x29, 0xc9, 0xdf, 0xd9, 0x43, 0xa7, 0x4b, 0x91, 0xe7, 0x54, 0xbb, 0x74, 0x50, 0xd1, 0x24, 0x5c, 0xca, 0xb8, 0x98, 0x6a, 0x98, 0x4f, 0xbb, 0x98, 0x9b, 0x98, 0xc7, 0x98, 0x57, 0x99, 0x0f, 0xf0, 0x6a, 0x39, 0xc0, 0x66, 0xa9, 0xd1, 0x80, 0x45, 0xc5, 0x2e, 0xa4, 0x41, 0x2e, 0xc4, 0x68, 0x3e, 0xa8, 0xc6, 0x6c, 0xe6, 0x00, 0x52, 0xf1, 0xaf, 0x22, 0x41, 0x35, 0xaf, 0x17, 0x2b, 0xb5, 0x62, 0x57, 0x31, 0xca, 0x7c, 0x1b, 0x24, 0x93, 0xce, 0x7b, 0x90, 0xa2, 0xe3, 0x2a, 0xa4, 0x6c, 0x77, 0xc8, 0xae, 0xa7, 0xe7, 0x5a, 0x50, 0xd7, 0xee, 0x90, 0xcb, 0xa8, 0xcf, 0xa5, 0x0c, 0x51, 0x56, 0x49, 0x33, 0x17, 0x31, 0xa2, 0x02, 0x29, 0x44, 0x34, 0xc6, 0x20, + 0x8d, 0x02, 0x69, 0xb6, 0x32, 0x8c, 0x06, 0x6b, 0x18, 0xd8, 0x06, 0xf8, 0x0c, 0x55, 0x06, 0xaf, 0x1a, 0x63, 0x04, 0x95, 0x56, 0x25, 0x68, 0xc7, 0xc8, 0xe9, 0x04, 0x9b, 0x81, 0x81, 0x43, 0x67, 0xea, 0x51, 0x06, 0x9f, 0x99, 0x31, 0xc4, 0xa8, 0x74, 0x06, 0xac, 0x54, 0x29, 0x57, 0x31, 0x1a, 0x8d, 0x62, 0x85, 0x51, 0x8d, 0x15, 0x0a, 0x7b, 0x37, 0xa3, 0xd5, 0x66, 0x69, 0x7b, 0x12, 0x3d, 0x59, 0x31, 0xc7, 0xba, 0x19, 0xad, 0x52, 0xa5, 0x55, 0x6e, 0x65, 0x74, 0x4a, 0xdd, 0xf9, 0xe9, 0xde, 0x63, 0x40, 0x4a, 0x9d, 0x4a, 0x39, 0x34, 0xed, 0x3d, 0x1a, 0xf2, 0x0a, 0x3f, 0xbc, 0x62, 0x15, 0x23, 0xaa, 0x91, 0x5a, 0x7e, 0x85, 0xfa, 0xdf, 0xf0, 0x0a, 0xda, 0x07, 0x8d, 0x46, 0xbd, 0xc2, 0x88, 0xd4, 0x6a, 0xf2, 0x9a, 0xf8, 0xca, 0x7f, 0xe1, 0x0d, 0x50, 0x98, 0xc4, 0x1e, 0xd5, 0x0c, + 0x9d, 0xe5, 0x55, 0x0a, 0x6a, 0xb9, 0x54, 0x74, 0xec, 0x4b, 0x7b, 0x76, 0xbf, 0xf7, 0x87, 0xdf, 0xfe, 0xfa, 0xb5, 0xff, 0x78, 0xe1, 0x07, 0xdf, 0x3e, 0x7e, 0xc7, 0xed, 0x5f, 0x7a, 0xec, 0xd8, 0x63, 0xfb, 0xf7, 0xed, 0xbe, 0x69, 0xcf, 0x4d, 0x5b, 0x46, 0x57, 0x0e, 0x2e, 0x59, 0xd8, 0xdc, 0x14, 0x29, 0xcd, 0xcf, 0x0d, 0xf8, 0x1c, 0x36, 0xab, 0x49, 0x30, 0x93, 0x33, 0x47, 0x1a, 0x56, 0xdd, 0x4b, 0x5c, 0xb6, 0x62, 0xf5, 0x6c, 0x0e, 0xb2, 0x08, 0xc4, 0x26, 0x41, 0xc7, 0x8a, 0xb2, 0x41, 0xa4, 0xbc, 0x2b, 0xd0, 0x8b, 0x00, 0x39, 0xdc, 0xa5, 0xd5, 0x58, 0x44, 0xce, 0x71, 0xeb, 0x41, 0x91, 0xb4, 0x92, 0xbc, 0x6c, 0x31, 0x22, 0x25, 0x51, 0xa4, 0x9e, 0x25, 0x17, 0x8e, 0x64, 0xf7, 0x30, 0xd1, 0xcb, 0xd4, 0x50, 0x11, 0xdb, 0x80, 0x2a, 0xac, 0x2e, 0x64, 0x86, 0x42, 0xa9, 0x5d, + 0x26, 0x71, 0xba, 0x4e, 0xbc, 0xc2, 0xc8, 0xa1, 0x03, 0x94, 0x81, 0x7a, 0xa6, 0xc9, 0x81, 0xd4, 0xff, 0x0c, 0x2a, 0x60, 0xc9, 0x91, 0x31, 0x54, 0x12, 0x2c, 0x97, 0x6f, 0x20, 0x32, 0x90, 0x00, 0x65, 0xa2, 0x45, 0xc0, 0xcf, 0x4d, 0x72, 0xc8, 0xc1, 0xa4, 0xef, 0xa3, 0x20, 0xc7, 0xe4, 0x0c, 0x09, 0x7b, 0x0c, 0x99, 0x08, 0x76, 0xd6, 0x70, 0x5e, 0xae, 0x21, 0x4f, 0x1b, 0x76, 0xf5, 0xaf, 0x19, 0xad, 0x44, 0x98, 0x17, 0xb9, 0x4c, 0x85, 0xae, 0x30, 0x5a, 0x69, 0xff, 0xe7, 0x2f, 0x40, 0xee, 0xcb, 0x2a, 0x6c, 0x98, 0x5f, 0x95, 0x37, 0xb8, 0x62, 0xb1, 0x3f, 0x5b, 0x93, 0x29, 0xe8, 0x04, 0x95, 0xb7, 0xb0, 0xc2, 0xf5, 0xe3, 0x64, 0xc1, 0x3c, 0x28, 0x98, 0x2b, 0x17, 0x44, 0x1c, 0x14, 0x54, 0xea, 0x0a, 0xa2, 0xb1, 0xac, 0x80, 0x21, 0xb7, 0x20, 0xdf, 0xe0, 0xf6, 0x9b, 0x8b, 0x8b, + 0x8b, 0x4c, 0xae, 0x92, 0x1c, 0xad, 0x90, 0xab, 0x57, 0xf3, 0x20, 0x3f, 0xa8, 0x4c, 0x06, 0xf4, 0x67, 0x7b, 0xae, 0x2b, 0xb3, 0x21, 0xe2, 0x2a, 0x74, 0x65, 0xd8, 0x8b, 0x5a, 0xf2, 0x5d, 0x0a, 0x01, 0x29, 0x04, 0xa5, 0xcb, 0x65, 0xe7, 0x61, 0x3f, 0x47, 0x7a, 0x57, 0xbe, 0xdd, 0x55, 0xe0, 0xca, 0xb0, 0x15, 0x37, 0xd3, 0x24, 0x9d, 0xd2, 0xe9, 0xb2, 0x0b, 0xe8, 0x6f, 0x50, 0x63, 0x9e, 0xc1, 0xed, 0xcb, 0x5f, 0x9b, 0xeb, 0x2a, 0x76, 0x6b, 0xf8, 0xbc, 0x54, 0x7d, 0x1f, 0x35, 0x47, 0x8a, 0x1a, 0xcc, 0x81, 0xb2, 0xec, 0xd9, 0x6d, 0xe4, 0x0e, 0x1b, 0xf2, 0xf2, 0xf3, 0x0d, 0x99, 0x99, 0x06, 0x43, 0xaa, 0x94, 0x4a, 0x23, 0x72, 0x50, 0x4c, 0x6d, 0x32, 0x48, 0x99, 0xd0, 0x0c, 0x7d, 0x3c, 0x52, 0x53, 0x47, 0x1a, 0x91, 0xa3, 0x55, 0x0a, 0xf4, 0x45, 0x13, 0xef, 0x9c, 0xb6, 0x90, + 0xca, 0x6c, 0xe0, 0x5a, 0xf4, 0xa4, 0xdf, 0xee, 0x81, 0x80, 0x59, 0xcf, 0xdb, 0x4d, 0xae, 0xfe, 0xb5, 0xa4, 0xdf, 0x6a, 0x4e, 0x0f, 0xf4, 0x2a, 0xaf, 0x4a, 0xd2, 0x2b, 0x9e, 0x68, 0xcd, 0x12, 0x7f, 0xb6, 0xa8, 0x51, 0x26, 0x9b, 0xc3, 0x4b, 0x2d, 0x11, 0x6b, 0xc8, 0xa5, 0x9f, 0x91, 0xa8, 0xd1, 0xa9, 0x3d, 0x90, 0xf6, 0xcf, 0x97, 0xf5, 0x7a, 0x4c, 0xeb, 0xa5, 0xd4, 0x9c, 0xaa, 0xb5, 0x00, 0x46, 0x01, 0x67, 0x94, 0x35, 0xc4, 0x53, 0xe4, 0x4a, 0xb5, 0x14, 0xf6, 0xb3, 0x6f, 0x4d, 0xaa, 0xf8, 0x62, 0xc1, 0xc0, 0xe4, 0x81, 0x36, 0x16, 0x8a, 0xfb, 0xf3, 0xbc, 0x1e, 0xb7, 0xcd, 0xa4, 0xd5, 0xa8, 0x59, 0x15, 0x81, 0x6d, 0x99, 0x29, 0x0e, 0x13, 0x8c, 0x2f, 0x4c, 0x04, 0x27, 0x98, 0x43, 0x30, 0x7f, 0x91, 0x8e, 0x06, 0xff, 0x10, 0x85, 0x60, 0x08, 0x11, 0x01, 0xc6, 0xac, 0x97, + 0x11, 0x38, 0x73, 0xe0, 0x21, 0x99, 0x3e, 0x64, 0x56, 0x12, 0x80, 0x2f, 0x7c, 0x93, 0x42, 0xcc, 0x0c, 0xdb, 0xa5, 0xbf, 0x19, 0xf3, 0x2d, 0xad, 0xb8, 0x11, 0xd7, 0xb7, 0x5a, 0xf2, 0x4d, 0xd2, 0xdf, 0x23, 0x2e, 0x51, 0xa1, 0x73, 0x9b, 0x90, 0xc6, 0x04, 0x8f, 0x27, 0x9e, 0x9b, 0x78, 0x9e, 0x3c, 0x46, 0x2a, 0x93, 0x5b, 0x27, 0x55, 0x70, 0x2d, 0x9f, 0x3f, 0xc3, 0xe9, 0x1a, 0xf6, 0x54, 0x98, 0x0b, 0xb3, 0xa4, 0x8f, 0x4d, 0x0a, 0x73, 0x0b, 0x6e, 0x9c, 0xf8, 0x5e, 0x8b, 0x59, 0x01, 0x19, 0x22, 0xe1, 0x8a, 0xdd, 0x0d, 0x13, 0x21, 0xbd, 0xdf, 0x84, 0xd4, 0x90, 0xd2, 0x3c, 0xf1, 0x1c, 0x6e, 0x68, 0xb6, 0x28, 0x4d, 0xd2, 0xa7, 0x26, 0x9f, 0x5e, 0x5a, 0x7b, 0xad, 0x2c, 0x97, 0x7c, 0x8a, 0x3f, 0xc5, 0xab, 0xb8, 0x1b, 0x40, 0x3e, 0xf2, 0xc7, 0x3d, 0xb2, 0x80, 0x94, 0x08, 0xeb, + 0x35, 0x13, 0xdf, 0xc0, 0x9a, 0x4f, 0xe5, 0xb1, 0x24, 0xb6, 0x35, 0x05, 0x2a, 0x4b, 0x5c, 0xc4, 0x7d, 0x5a, 0xd0, 0x56, 0x92, 0xa5, 0x33, 0xe8, 0x22, 0x15, 0xc3, 0x82, 0x58, 0xbc, 0xe2, 0xd0, 0x32, 0x76, 0xbe, 0xd1, 0x5b, 0xe4, 0xc8, 0xb4, 0xd9, 0x5c, 0x96, 0x50, 0xbe, 0xa9, 0x22, 0xbb, 0x75, 0xbc, 0xbf, 0x90, 0xc4, 0x78, 0x78, 0x79, 0xd2, 0xc2, 0xbd, 0x22, 0x6c, 0x64, 0x54, 0x8c, 0x8e, 0x31, 0x80, 0x54, 0x5b, 0x4d, 0x7c, 0x5e, 0xfd, 0x30, 0x99, 0xe1, 0x9d, 0x5a, 0x85, 0x76, 0x9c, 0x51, 0x29, 0x55, 0xe7, 0x0b, 0x88, 0x20, 0x74, 0x82, 0x7a, 0x37, 0x24, 0x66, 0x62, 0x9e, 0x57, 0xac, 0xd0, 0x20, 0x85, 0x62, 0xa8, 0x3b, 0x83, 0xd5, 0xb1, 0xc4, 0xb0, 0x4f, 0x8d, 0xa8, 0xcf, 0x71, 0x46, 0x86, 0x5a, 0x8d, 0x98, 0xea, 0x4a, 0xea, 0x7a, 0x5c, 0x9c, 0x9f, 0x47, 0x2c, 0x59, + 0x4d, 0xc6, 0x0c, 0x43, 0x06, 0x08, 0x23, 0x6a, 0x9d, 0x5a, 0xa7, 0xd5, 0x28, 0xa8, 0x1d, 0x8b, 0x3e, 0x75, 0xef, 0x1e, 0x94, 0x71, 0xc7, 0xec, 0x38, 0xa2, 0x37, 0xca, 0x51, 0x74, 0xa3, 0x89, 0x98, 0xc6, 0x9c, 0x9e, 0x46, 0x2f, 0x36, 0x53, 0x1b, 0x1e, 0x8f, 0x9e, 0x38, 0x24, 0xd7, 0x20, 0xfc, 0xab, 0xbe, 0xf3, 0x3b, 0xbd, 0x47, 0x6f, 0x55, 0xee, 0x7d, 0xf6, 0xd9, 0xbd, 0x5f, 0xd8, 0xbf, 0xff, 0x0b, 0x17, 0x3e, 0xb8, 0x3d, 0xea, 0x6e, 0x1a, 0x6e, 0x41, 0x55, 0x2d, 0x07, 0xbe, 0xb3, 0x07, 0x21, 0x97, 0xb5, 0x43, 0xba, 0x10, 0x1d, 0x22, 0x9f, 0xf7, 0x04, 0x9b, 0x56, 0x54, 0x6c, 0xbe, 0x48, 0xf2, 0xa0, 0xdf, 0x49, 0x1e, 0xae, 0x47, 0xca, 0x43, 0xaf, 0x4b, 0x05, 0xca, 0x8b, 0x87, 0xcf, 0xdf, 0x2e, 0x7c, 0x83, 0x8f, 0xad, 0xf9, 0xc2, 0xa2, 0xf2, 0xd5, 0x0b, 0x9b, 0x2c, + 0xf5, 0xd2, 0xa7, 0x83, 0x5f, 0xda, 0x5a, 0xfb, 0x84, 0xc9, 0xfd, 0xdf, 0xfc, 0x6d, 0x37, 0x9b, 0xdc, 0xb2, 0xdd, 0xf0, 0xfb, 0x50, 0xc9, 0x5f, 0xc4, 0xe5, 0x40, 0x93, 0x96, 0xb8, 0x06, 0xcb, 0xc8, 0xd1, 0x2a, 0x25, 0xc6, 0xf2, 0x05, 0x50, 0x5c, 0x47, 0xc4, 0x54, 0x60, 0xad, 0x6b, 0x92, 0x97, 0x4a, 0x66, 0x19, 0x44, 0x81, 0x08, 0xaf, 0x04, 0xcd, 0x86, 0x58, 0x8d, 0x60, 0x86, 0xa2, 0xd9, 0x18, 0xa9, 0xa0, 0x2a, 0x86, 0x68, 0x44, 0x73, 0x4f, 0x28, 0x86, 0xfe, 0xf2, 0x5c, 0xfd, 0x46, 0xe9, 0x99, 0x32, 0x74, 0xc1, 0x86, 0x7a, 0xe1, 0xd6, 0xe7, 0x1a, 0x36, 0x7c, 0xfa, 0xa9, 0xa2, 0x66, 0x7d, 0x3d, 0x83, 0xa4, 0x0c, 0x72, 0x6f, 0x45, 0xdf, 0xd9, 0x11, 0xcf, 0x24, 0x08, 0x56, 0x3c, 0x47, 0x14, 0xa2, 0x2e, 0x85, 0x38, 0xe3, 0xbd, 0xc9, 0x4b, 0xb3, 0x4d, 0xf2, 0xc1, 0x5e, + 0x7a, 0x14, 0x1d, 0x8b, 0x8c, 0xa2, 0x63, 0x84, 0x57, 0x5a, 0xe1, 0xdd, 0x56, 0xd1, 0x63, 0x2f, 0x93, 0x9e, 0xd9, 0x58, 0xff, 0xdc, 0x73, 0xf5, 0x1b, 0xd0, 0x05, 0xd2, 0x85, 0x0a, 0xe5, 0x86, 0x06, 0xf8, 0x7d, 0x3d, 0x81, 0xee, 0x3b, 0x28, 0xdd, 0x88, 0x0e, 0x0b, 0x4b, 0xa8, 0xbd, 0x4f, 0x0d, 0x33, 0x9f, 0xc4, 0x6d, 0x61, 0x04, 0x46, 0xe4, 0x08, 0x9e, 0xa9, 0x8c, 0x65, 0x32, 0x34, 0x2d, 0x88, 0x41, 0x42, 0x0c, 0x1f, 0x43, 0x04, 0x14, 0x87, 0xfc, 0xf1, 0x2b, 0x54, 0xd9, 0xf9, 0xd6, 0xb3, 0x40, 0xe2, 0x9c, 0xed, 0xfb, 0xc1, 0xd9, 0xb0, 0xda, 0xe7, 0xfa, 0xfd, 0x3f, 0x13, 0x11, 0x28, 0xb2, 0x13, 0x89, 0x78, 0xf9, 0x2c, 0x38, 0xee, 0x53, 0x1f, 0x0c, 0xcc, 0x2a, 0x42, 0x86, 0xac, 0x4d, 0xba, 0x52, 0xb1, 0x3a, 0x11, 0x07, 0xc9, 0x49, 0x2f, 0x17, 0x64, 0xc3, 0xac, 0x29, + 0x23, 0x71, 0x12, 0x84, 0x88, 0x22, 0x8a, 0xcc, 0x31, 0x08, 0x51, 0xdb, 0xbf, 0x14, 0x50, 0xe8, 0x5f, 0x8a, 0x0e, 0x04, 0x53, 0x61, 0xaa, 0xfd, 0x59, 0x71, 0x2b, 0x8d, 0x53, 0x39, 0x2e, 0x37, 0x1b, 0x31, 0x19, 0x5a, 0x72, 0xd4, 0xc2, 0x52, 0x0b, 0xb2, 0xd9, 0x8d, 0x8e, 0xd2, 0xce, 0x70, 0x0b, 0xd3, 0x36, 0x52, 0x7e, 0xa9, 0x70, 0x57, 0xda, 0xa6, 0x24, 0x9a, 0x49, 0x14, 0xe5, 0x64, 0x8c, 0x21, 0x32, 0x8f, 0x9a, 0x49, 0x24, 0x0c, 0xe0, 0x19, 0xf0, 0x9c, 0x84, 0x13, 0x4e, 0xd9, 0xa6, 0x24, 0x2d, 0xdc, 0xc6, 0xf8, 0x9e, 0xdc, 0x70, 0x2c, 0x1a, 0x6e, 0xce, 0x6d, 0x4e, 0x05, 0xcf, 0x9c, 0x65, 0xff, 0x15, 0x9a, 0x75, 0xd0, 0xc3, 0x9e, 0xa6, 0xd5, 0xfc, 0x4f, 0xce, 0x6a, 0x27, 0xf6, 0xf0, 0x19, 0xfa, 0x85, 0x1f, 0x4a, 0xda, 0x8e, 0x25, 0x6d, 0xc9, 0x12, 0xb6, 0x63, 0xeb, + 0x13, 0xb6, 0x64, 0xec, 0xb5, 0x67, 0xec, 0x38, 0x4e, 0xf5, 0x5b, 0xcb, 0xe4, 0x12, 0x1b, 0x7f, 0x05, 0xb5, 0x1e, 0x03, 0x8a, 0x60, 0x72, 0xc3, 0x3d, 0x26, 0x20, 0x96, 0x27, 0x42, 0x18, 0x5e, 0x93, 0x44, 0x12, 0x1f, 0xe3, 0x7a, 0x32, 0x74, 0x0c, 0xe3, 0x74, 0xe8, 0x72, 0x33, 0x72, 0xa9, 0x49, 0x99, 0x49, 0x54, 0x59, 0x40, 0xf3, 0xb4, 0xba, 0x70, 0x45, 0x03, 0x12, 0xcc, 0xfa, 0x88, 0xfe, 0x74, 0x9d, 0xe5, 0x5a, 0xa5, 0xdf, 0x96, 0x2b, 0x1c, 0x4e, 0x97, 0x7a, 0x03, 0xfb, 0xdd, 0xeb, 0xce, 0xd0, 0x2d, 0xee, 0x56, 0xe9, 0xb3, 0x46, 0x90, 0x17, 0x95, 0xaa, 0xb5, 0x65, 0x45, 0x13, 0x17, 0xb0, 0xbf, 0xc4, 0xca, 0x33, 0x8f, 0xdf, 0x54, 0x3f, 0x8a, 0x98, 0x2e, 0x82, 0xc8, 0x60, 0xa4, 0xd6, 0x7b, 0xe4, 0x5e, 0x49, 0x24, 0xa8, 0x58, 0x98, 0x93, 0x4d, 0xaa, 0xe8, 0x89, 0x0d, + 0xb5, 0xf4, 0x4b, 0x0e, 0x64, 0x49, 0x31, 0x62, 0xea, 0x6b, 0x8b, 0xbb, 0x4a, 0xba, 0xf2, 0x72, 0x9d, 0x0e, 0x98, 0x66, 0x45, 0xa8, 0x90, 0x9a, 0x12, 0x78, 0x05, 0x31, 0x36, 0x05, 0x88, 0x2e, 0x5a, 0x2d, 0x44, 0x76, 0x4b, 0x68, 0xe9, 0x64, 0xfb, 0xa5, 0xf6, 0x46, 0x41, 0xdf, 0x69, 0xa7, 0xe3, 0x97, 0x9d, 0xad, 0xbe, 0xc7, 0xfc, 0x1d, 0x35, 0x81, 0xc2, 0xbe, 0x6d, 0xcd, 0xf5, 0xdb, 0x97, 0xd7, 0xa9, 0x46, 0x82, 0xf9, 0x0a, 0xc1, 0xe6, 0xcd, 0xb5, 0x54, 0xb5, 0xd8, 0xf3, 0x6b, 0x3c, 0xd2, 0x49, 0x53, 0x06, 0x3e, 0xe6, 0xcf, 0x91, 0x3e, 0x3e, 0x03, 0x25, 0xd0, 0x5e, 0xad, 0xfa, 0xc5, 0xf0, 0xbc, 0x85, 0x83, 0xd1, 0x86, 0x0d, 0x3d, 0x79, 0xfe, 0x8e, 0x2d, 0x9d, 0x76, 0xd1, 0x9c, 0xe5, 0xc8, 0x75, 0xe8, 0x1a, 0x6b, 0xfd, 0x8d, 0xf1, 0xa6, 0x80, 0xf4, 0x8e, 0xa1, 0xc0, + 0xd4, 0xb4, 0xcb, 0x19, 0xe4, 0x83, 0x67, 0x26, 0xd1, 0x34, 0x1a, 0x55, 0x33, 0x4b, 0x80, 0x4b, 0xb7, 0x56, 0xc3, 0x96, 0x58, 0x08, 0x1c, 0xd7, 0x4d, 0xfc, 0x03, 0xba, 0x14, 0x48, 0x60, 0x08, 0x96, 0x1e, 0x65, 0x9b, 0x1c, 0xa6, 0xf6, 0x9c, 0x49, 0xf3, 0x1a, 0x8e, 0x4b, 0x5a, 0xba, 0xd7, 0xd6, 0x74, 0x75, 0xd4, 0x2c, 0xa9, 0x5d, 0x52, 0x54, 0x10, 0xd4, 0x9b, 0x42, 0x4a, 0xe2, 0x0c, 0x61, 0x49, 0x46, 0x86, 0x27, 0x56, 0x0f, 0xe6, 0xc4, 0x31, 0x06, 0x91, 0x9f, 0x8b, 0x50, 0x06, 0x4a, 0x19, 0xd2, 0x84, 0x64, 0x0c, 0x1e, 0x4a, 0xcb, 0xd3, 0xd1, 0x8c, 0xbd, 0xc1, 0x68, 0xd5, 0x2a, 0x84, 0x8c, 0xae, 0xa2, 0xaa, 0xc6, 0xc1, 0xa1, 0xb1, 0x35, 0x06, 0x5f, 0x04, 0xc4, 0x5e, 0xaf, 0x59, 0x50, 0x97, 0x79, 0x97, 0x18, 0x23, 0x3d, 0x1b, 0x3b, 0x7b, 0x76, 0xf6, 0xe5, 0xf7, 0x2f, + 0x6a, 0xeb, 0x0b, 0x76, 0x6c, 0x68, 0xae, 0x1f, 0x5d, 0x54, 0xa5, 0x42, 0xe1, 0x33, 0x10, 0x8f, 0xdd, 0xc6, 0x62, 0xb5, 0x5e, 0x1d, 0xa9, 0xa8, 0xab, 0x6e, 0x69, 0x47, 0xd7, 0xc6, 0xdb, 0x1c, 0xe5, 0x21, 0x5b, 0x56, 0x30, 0xa4, 0xb3, 0xe9, 0x74, 0xc1, 0xda, 0x35, 0xad, 0x81, 0xe2, 0xfe, 0x2d, 0x0d, 0xcb, 0x36, 0xd8, 0xed, 0x5b, 0x57, 0x47, 0x56, 0xb6, 0xe5, 0x12, 0xea, 0xb2, 0x9e, 0x33, 0x52, 0x71, 0x8a, 0x4f, 0xf0, 0xf4, 0x36, 0xe8, 0x50, 0x9c, 0x98, 0xa4, 0xc3, 0x30, 0x61, 0x6a, 0xd0, 0x42, 0xf0, 0x0b, 0x83, 0x4a, 0x5e, 0xc1, 0x92, 0x8d, 0x0c, 0xb4, 0x1c, 0xe2, 0x2e, 0xc3, 0xe2, 0x11, 0x15, 0x90, 0x16, 0x31, 0x02, 0x6c, 0x7c, 0x2c, 0x2b, 0x0e, 0x26, 0xdd, 0x0d, 0x42, 0xb3, 0x32, 0xc2, 0xbf, 0xad, 0xe9, 0x72, 0x0e, 0xc4, 0x73, 0x6c, 0xd4, 0xe2, 0x25, 0xe8, 0xb7, + 0x96, 0xd9, 0xca, 0x88, 0xdd, 0x0b, 0x08, 0x24, 0x19, 0x3a, 0x22, 0x8d, 0xf0, 0x0c, 0xaf, 0x26, 0x93, 0xd5, 0xa8, 0x43, 0x64, 0xe1, 0xf9, 0x88, 0x25, 0x45, 0xc4, 0x63, 0x26, 0xb8, 0x21, 0x15, 0x75, 0xe8, 0xb4, 0x74, 0xbe, 0x76, 0x82, 0x63, 0xef, 0x38, 0x2e, 0xad, 0x34, 0x72, 0x2e, 0x3d, 0xda, 0x71, 0xc7, 0x90, 0xc1, 0x24, 0x7d, 0xf3, 0x4c, 0x5c, 0xa6, 0x1a, 0x1d, 0x70, 0xd7, 0x64, 0x49, 0x7f, 0xc3, 0x6b, 0x26, 0x6e, 0x1b, 0x31, 0xd8, 0xfe, 0xf0, 0x07, 0x6b, 0xe6, 0x28, 0xb7, 0x69, 0xce, 0xf3, 0xad, 0x84, 0xe9, 0x61, 0xfa, 0xe3, 0xf3, 0xf3, 0x61, 0x1f, 0xe2, 0x61, 0x51, 0x22, 0x3b, 0x88, 0x63, 0x2c, 0x10, 0x93, 0x5e, 0x2d, 0x8c, 0x81, 0x14, 0xcb, 0x30, 0x82, 0x48, 0xc4, 0x11, 0x72, 0x76, 0x88, 0x87, 0x92, 0x41, 0xb9, 0xe9, 0xc4, 0xa3, 0xce, 0x16, 0x63, 0x8a, 0x9e, + 0xb2, 0xd2, 0x78, 0x7d, 0x69, 0x4f, 0x59, 0x8f, 0x2f, 0x1c, 0x36, 0x86, 0xfd, 0x99, 0x2a, 0x62, 0xcc, 0xe6, 0x61, 0xad, 0x33, 0x4d, 0xd9, 0x7c, 0x5e, 0x61, 0x6a, 0xca, 0xc9, 0x27, 0xf1, 0x31, 0xe2, 0xff, 0x74, 0x3a, 0x42, 0xf0, 0xdb, 0xa4, 0xd2, 0x79, 0xbb, 0x9d, 0x35, 0xce, 0xdc, 0x6a, 0xbf, 0xde, 0x9a, 0x1b, 0x73, 0x07, 0x23, 0x21, 0x8f, 0x51, 0x67, 0x57, 0xd5, 0xd8, 0x6a, 0xdb, 0xfb, 0x8b, 0xbb, 0x2e, 0x59, 0x1e, 0x09, 0x76, 0x6e, 0x69, 0xb7, 0x96, 0x64, 0x39, 0x5f, 0xab, 0x2b, 0x97, 0xfe, 0x78, 0x66, 0x96, 0x25, 0xfa, 0xb4, 0x1a, 0x4b, 0x6e, 0xa5, 0x27, 0x58, 0x93, 0x6b, 0xb1, 0xd8, 0x2d, 0x1a, 0xdd, 0x62, 0x7b, 0xa1, 0xcf, 0x54, 0x3e, 0x78, 0x69, 0x6f, 0xe3, 0xe8, 0xbc, 0x7c, 0x9e, 0x7f, 0xa6, 0x30, 0x0f, 0xaf, 0x9a, 0xeb, 0x3e, 0x44, 0x6c, 0x6d, 0xdb, 0x09, + 0xde, 0x7c, 0x58, 0x96, 0x65, 0x41, 0x80, 0x05, 0xe2, 0x8c, 0x29, 0x10, 0x0f, 0x44, 0x22, 0x86, 0xa2, 0xa0, 0x03, 0x10, 0x37, 0x19, 0x62, 0x9f, 0x4c, 0xa9, 0x35, 0x26, 0xf4, 0x14, 0x16, 0x10, 0x23, 0xd1, 0x82, 0xf6, 0xc2, 0xf6, 0xa0, 0x3f, 0x3b, 0x2b, 0x53, 0xa7, 0x14, 0x89, 0xe9, 0xac, 0xec, 0x82, 0x95, 0x50, 0x1f, 0xc9, 0x22, 0x35, 0x12, 0xba, 0xb0, 0x04, 0xfe, 0x69, 0xa6, 0xd1, 0x1f, 0x68, 0x10, 0xc6, 0xd3, 0x71, 0xb3, 0x9e, 0x96, 0x1e, 0x53, 0x30, 0xe6, 0xad, 0xef, 0x5b, 0x70, 0xff, 0x03, 0xde, 0x6a, 0xfd, 0xf1, 0x92, 0x8a, 0x82, 0x45, 0xbb, 0x7b, 0xba, 0x2e, 0x5c, 0x52, 0x5c, 0xdc, 0xb9, 0xa2, 0xd0, 0x53, 0xeb, 0x50, 0x1a, 0x54, 0xb6, 0x33, 0xf1, 0xb1, 0xc1, 0x25, 0xad, 0xbe, 0xda, 0x42, 0xfb, 0xc2, 0x79, 0xdd, 0x8b, 0xd8, 0x06, 0x57, 0x8e, 0xf4, 0x76, 0xef, + 0xf5, 0x9d, 0xf3, 0xf6, 0xaf, 0x8c, 0x54, 0xac, 0xbc, 0xb0, 0xb5, 0x7b, 0x5b, 0xa7, 0xdf, 0xa8, 0xd7, 0xd8, 0x34, 0xec, 0xc8, 0x9c, 0xe7, 0x94, 0x01, 0x38, 0x7d, 0x45, 0x3c, 0x82, 0xa8, 0x37, 0xd5, 0xb4, 0x6d, 0x8a, 0xf2, 0x2b, 0x20, 0xd9, 0xa0, 0xbc, 0x57, 0x99, 0x8c, 0x0c, 0xe3, 0x75, 0x1b, 0x8b, 0x4c, 0x45, 0xc4, 0x3c, 0xcb, 0xef, 0x13, 0x65, 0xf3, 0x2c, 0x4b, 0xc4, 0x43, 0x94, 0x28, 0x7a, 0x09, 0x4e, 0xa0, 0x82, 0x4e, 0xbb, 0x52, 0x2a, 0x1c, 0x06, 0x74, 0x8d, 0xde, 0xa5, 0x93, 0x9e, 0xbd, 0x3a, 0x5c, 0xa2, 0x45, 0xd5, 0x8a, 0x0c, 0xe9, 0xce, 0x33, 0xce, 0x82, 0x2b, 0xf4, 0xd9, 0xa6, 0xa2, 0xec, 0x89, 0xda, 0x90, 0x03, 0xff, 0x4a, 0x5f, 0x14, 0x5a, 0x72, 0xb6, 0x9d, 0x6b, 0x7a, 0xac, 0x57, 0xd9, 0xc6, 0x78, 0x90, 0x59, 0x10, 0xef, 0x2d, 0xa5, 0x66, 0xc6, 0x84, + 0x1d, 0x70, 0x98, 0x5c, 0x06, 0x23, 0x05, 0x62, 0x78, 0x79, 0xb4, 0xf1, 0x0a, 0x60, 0x37, 0x24, 0x40, 0xda, 0x94, 0xff, 0x51, 0x53, 0x23, 0x39, 0x0c, 0x5d, 0x30, 0xaf, 0x71, 0xb0, 0x69, 0xb0, 0xba, 0x32, 0x2f, 0x2c, 0x5b, 0x09, 0xc3, 0xf0, 0xeb, 0x91, 0x5e, 0x3d, 0x7d, 0xf8, 0xe5, 0xa5, 0xc2, 0x4e, 0x31, 0x69, 0xd0, 0xb9, 0x8c, 0x15, 0xd3, 0x0c, 0x88, 0xad, 0xd3, 0xa7, 0x45, 0xf0, 0xb4, 0x1b, 0xf9, 0x92, 0xf6, 0x2e, 0x5b, 0xb8, 0xdc, 0x99, 0x5d, 0x9e, 0x6b, 0xd3, 0xfb, 0x63, 0x81, 0xda, 0xa6, 0x0c, 0x77, 0xb1, 0xdb, 0x5d, 0xe2, 0xce, 0x68, 0x7a, 0xff, 0xc2, 0xfc, 0x79, 0x9b, 0xe3, 0xf1, 0xd1, 0x79, 0x05, 0x75, 0xf5, 0xfd, 0xed, 0xc5, 0x8b, 0x77, 0x75, 0x76, 0xec, 0x5a, 0x54, 0xdc, 0x7e, 0xf2, 0x4c, 0x73, 0x63, 0x71, 0x7f, 0x7d, 0xd2, 0xb0, 0x38, 0x27, 0x1a, 0xb4, + 0xcc, 0x36, 0x2d, 0xae, 0x3f, 0x96, 0xb4, 0x25, 0xee, 0x1e, 0xcd, 0x99, 0x61, 0x67, 0xcc, 0x4e, 0x9e, 0x85, 0xb8, 0xd3, 0xd7, 0x53, 0x01, 0x28, 0xd5, 0x0d, 0xf1, 0xda, 0x5c, 0x58, 0x4e, 0x64, 0x3d, 0x01, 0xf3, 0xe1, 0xf0, 0x98, 0x08, 0xeb, 0x89, 0x65, 0xf8, 0xa9, 0xdd, 0x8e, 0x46, 0x3a, 0x13, 0x06, 0xe5, 0x05, 0x55, 0x54, 0x58, 0x5b, 0x5d, 0xd8, 0x51, 0xd4, 0x11, 0x2e, 0xf0, 0x06, 0xbd, 0x0a, 0xea, 0x32, 0x9b, 0x3a, 0xa8, 0xf7, 0x06, 0x59, 0x32, 0x7d, 0x8c, 0x33, 0x83, 0xa6, 0x44, 0xca, 0x88, 0x76, 0x7e, 0x3a, 0xba, 0xb1, 0x6f, 0x76, 0x57, 0xd5, 0x74, 0x19, 0x7d, 0x91, 0x9c, 0xaa, 0x8e, 0xb5, 0x77, 0x7a, 0xab, 0xf4, 0xb7, 0x65, 0xe5, 0x39, 0x33, 0xf3, 0xe7, 0x8f, 0xb7, 0x75, 0xed, 0x5a, 0x54, 0x58, 0xd8, 0x35, 0x58, 0x12, 0x8f, 0x2a, 0x74, 0x8a, 0xc0, 0x99, 0xf6, + 0xb4, 0xd1, 0x8e, 0xf9, 0xbd, 0xad, 0x9e, 0x58, 0xae, 0xb5, 0x7b, 0xe2, 0x8e, 0xec, 0x1c, 0x24, 0x16, 0x2e, 0x5e, 0xbc, 0xac, 0xa4, 0x7d, 0xe7, 0xa2, 0xe2, 0xf2, 0x65, 0xbb, 0x9a, 0x3a, 0x36, 0xb7, 0x7b, 0x9d, 0x2a, 0xa3, 0x9a, 0xdb, 0x7c, 0x66, 0xc2, 0x70, 0x33, 0xe4, 0xdd, 0x28, 0xd3, 0xc7, 0xbc, 0xfa, 0x44, 0x49, 0x2e, 0xe6, 0xa8, 0x69, 0x3b, 0xb9, 0x58, 0x2a, 0x52, 0x01, 0x57, 0x16, 0xc7, 0x95, 0x02, 0xd1, 0xa1, 0xc6, 0x19, 0x0e, 0x24, 0x04, 0xa0, 0x16, 0x0f, 0x9a, 0x9a, 0x02, 0x23, 0xc5, 0xd8, 0x34, 0x99, 0x98, 0x72, 0x67, 0x47, 0x22, 0xc4, 0x6a, 0xda, 0x52, 0x0c, 0x71, 0x5b, 0xe0, 0xc9, 0x11, 0x20, 0x29, 0xbb, 0xf5, 0x94, 0xb2, 0xf1, 0x4a, 0xb9, 0x18, 0x31, 0xfc, 0x1a, 0x57, 0x22, 0x81, 0x15, 0xe6, 0x5a, 0x94, 0x58, 0xc6, 0xc7, 0x2a, 0xc2, 0xe1, 0xd6, 0xe6, + 0x8a, 0xbe, 0x58, 0x5f, 0x38, 0x1a, 0x8e, 0x5a, 0x6c, 0x44, 0x22, 0x57, 0x13, 0xb9, 0xe4, 0x2c, 0x12, 0xf9, 0x0c, 0x89, 0xf5, 0x74, 0x7c, 0x4f, 0xf8, 0xcb, 0x59, 0xc5, 0xf3, 0x93, 0xb7, 0x4c, 0x13, 0x6a, 0xff, 0xfa, 0xbf, 0x91, 0xd5, 0xd1, 0xe5, 0xe7, 0x28, 0xf5, 0xce, 0x9c, 0xdf, 0x44, 0xeb, 0xeb, 0x89, 0x77, 0x5a, 0x32, 0x14, 0xc4, 0xc6, 0x8c, 0x32, 0x0f, 0x2c, 0x8c, 0x13, 0x21, 0x03, 0x84, 0x93, 0x71, 0x98, 0xf4, 0x8c, 0x08, 0x9b, 0xc6, 0x18, 0xb1, 0xa5, 0x48, 0xf2, 0x0d, 0x67, 0xb6, 0x4e, 0x47, 0xae, 0xf2, 0x09, 0x5c, 0xb4, 0xce, 0xa1, 0x73, 0x64, 0xd9, 0x8d, 0x72, 0x94, 0x45, 0x93, 0x7a, 0xb6, 0x4c, 0xef, 0x99, 0x0b, 0xb9, 0xf8, 0xed, 0xd3, 0x68, 0x31, 0xa1, 0x98, 0xf6, 0xe5, 0xf6, 0x73, 0x91, 0xf6, 0xcf, 0x8d, 0x0c, 0xec, 0x34, 0xd9, 0xbf, 0x96, 0x59, 0xc6, + 0xdc, 0x1c, 0xd7, 0xc4, 0x4a, 0x8c, 0x44, 0x84, 0xa5, 0xf2, 0xbf, 0x3c, 0x33, 0x0b, 0xd4, 0xc0, 0xee, 0xc5, 0x71, 0x95, 0x40, 0x8e, 0x43, 0x60, 0x7a, 0x61, 0x62, 0x26, 0x0d, 0xa2, 0x47, 0x4a, 0x25, 0x20, 0xf6, 0x0f, 0x49, 0x59, 0xc3, 0x11, 0x2f, 0x91, 0xb3, 0x33, 0x48, 0x40, 0xe3, 0x32, 0x1d, 0xcf, 0x52, 0x64, 0x20, 0xee, 0xa9, 0xaf, 0x2b, 0x06, 0x5d, 0xa2, 0xa7, 0xab, 0x6e, 0x59, 0xfd, 0xb2, 0xe2, 0xda, 0xe2, 0xda, 0x9a, 0xea, 0xf2, 0xc8, 0x94, 0x4e, 0xa1, 0x39, 0x07, 0x9d, 0x62, 0x4e, 0x53, 0x93, 0xbf, 0x6d, 0xae, 0x0a, 0xc6, 0xc9, 0xe1, 0x39, 0xce, 0xd0, 0xb9, 0x2b, 0x1b, 0xac, 0xf3, 0x5f, 0x1d, 0xa3, 0x6a, 0xe0, 0xc2, 0xc3, 0xcc, 0xc7, 0xf1, 0xcc, 0xe6, 0xda, 0x6a, 0x56, 0x98, 0xa6, 0x7f, 0xc8, 0x7c, 0xa7, 0x42, 0x83, 0x94, 0xbc, 0x72, 0x5c, 0xad, 0xc0, 0x20, + 0xda, 0x8e, 0x33, 0xb2, 0x34, 0x0c, 0x5c, 0x9a, 0x63, 0x55, 0x2c, 0xa7, 0x1a, 0x4b, 0xa3, 0x9b, 0xc8, 0x7e, 0x8c, 0xf2, 0x28, 0xd7, 0xa6, 0x2d, 0xcd, 0x88, 0x2c, 0xc1, 0x56, 0x19, 0x63, 0x38, 0x52, 0x07, 0x89, 0xb2, 0xc1, 0x8a, 0xe9, 0xea, 0x88, 0xc7, 0xe5, 0xe2, 0x0c, 0xaf, 0xe0, 0xc7, 0xd5, 0x48, 0xc1, 0x28, 0xce, 0xb5, 0x0a, 0xe0, 0x47, 0xf6, 0xae, 0xce, 0x9a, 0x9a, 0x65, 0x4b, 0x3a, 0x87, 0xbb, 0x86, 0x6b, 0x3a, 0x6a, 0x3a, 0x2c, 0x76, 0x59, 0x53, 0xd2, 0x12, 0x18, 0xc5, 0x73, 0xd7, 0x94, 0xe6, 0x34, 0x13, 0xb8, 0xeb, 0xce, 0x59, 0x6d, 0x92, 0x6e, 0x9d, 0xe3, 0x94, 0xf8, 0x17, 0x54, 0x28, 0x64, 0x3b, 0xb7, 0xb9, 0xc1, 0xcd, 0xd0, 0xa9, 0xfc, 0x20, 0x01, 0x6d, 0x8a, 0xaf, 0xcf, 0xb1, 0x21, 0x56, 0x48, 0xe8, 0x55, 0x5a, 0xe0, 0xfd, 0x4a, 0x01, 0x36, 0x06, 0xe2, + 0xe8, 0xa2, 0x14, 0x98, 0xad, 0x22, 0x4c, 0x18, 0x15, 0x66, 0x55, 0x63, 0x3a, 0xa4, 0xd4, 0xf0, 0x6a, 0x58, 0xe5, 0x4a, 0x66, 0x0d, 0x31, 0x63, 0x4a, 0x3a, 0xa5, 0x06, 0x03, 0x56, 0xaa, 0x31, 0x95, 0x97, 0x05, 0xe2, 0xc1, 0xb8, 0xd5, 0x6f, 0xf5, 0xfb, 0xbc, 0xd9, 0x8e, 0xd9, 0x9a, 0x53, 0xc6, 0xe9, 0x35, 0xa7, 0xb9, 0xd1, 0xfd, 0xde, 0x53, 0xd4, 0xa8, 0xcf, 0x3f, 0x9f, 0xeb, 0x66, 0x90, 0x4e, 0xa5, 0xc2, 0x6b, 0xce, 0x75, 0x03, 0x60, 0xa7, 0xe9, 0x58, 0xf5, 0xcc, 0x72, 0xe6, 0x57, 0x09, 0xb3, 0x90, 0xaa, 0x32, 0xcc, 0x0b, 0x44, 0xdf, 0x12, 0x93, 0xfa, 0x16, 0xd7, 0xe5, 0x38, 0xf5, 0xb1, 0x02, 0x1e, 0x0f, 0xc8, 0x45, 0x6a, 0x89, 0xbd, 0x21, 0x4f, 0x2e, 0xea, 0x12, 0x6a, 0x19, 0xec, 0x22, 0x58, 0x8d, 0x39, 0xf5, 0x98, 0x16, 0xa9, 0x34, 0x4a, 0xcc, 0x08, 0xaa, 0xb4, + 0x1a, 0x9a, 0x5a, 0x3d, 0xa6, 0x86, 0xa5, 0xd3, 0x34, 0xbb, 0x38, 0x03, 0x75, 0x63, 0x05, 0x59, 0x31, 0xa4, 0x92, 0xad, 0xb3, 0x2a, 0x51, 0xa4, 0xa9, 0x64, 0x20, 0x9e, 0x15, 0x6f, 0x28, 0x2d, 0x9d, 0xd7, 0xd3, 0xb0, 0x3c, 0xbe, 0xbc, 0xb4, 0xbe, 0xb4, 0xde, 0x62, 0x4d, 0x28, 0x7b, 0x3a, 0x39, 0x3a, 0xdb, 0x1c, 0x95, 0xbd, 0xc0, 0x9c, 0x36, 0xf6, 0x79, 0xe7, 0xa2, 0xf9, 0x9d, 0xdc, 0x39, 0xc7, 0x61, 0x9d, 0x9b, 0x16, 0xb8, 0xe4, 0x9c, 0xf7, 0x79, 0x6e, 0x86, 0x5e, 0x58, 0xc5, 0x2c, 0x66, 0xbe, 0x16, 0x57, 0x97, 0x17, 0x62, 0x1e, 0x13, 0xf5, 0x30, 0xe9, 0x46, 0x57, 0xac, 0x81, 0x11, 0x55, 0x8c, 0xab, 0x45, 0x0c, 0x09, 0xe3, 0x04, 0x06, 0x1e, 0xb6, 0x2d, 0xc4, 0x8d, 0x83, 0x7a, 0x44, 0x10, 0x4a, 0x95, 0x20, 0x46, 0xa9, 0x12, 0x4a, 0xa3, 0x52, 0x39, 0xa6, 0x24, 0xc1, + 0xed, 0x4f, 0x53, 0x22, 0x81, 0xc9, 0x0f, 0xa3, 0x49, 0xca, 0x6d, 0xa5, 0xe5, 0x84, 0x54, 0xb9, 0x81, 0xb8, 0xaf, 0xa6, 0xba, 0x00, 0x94, 0xcd, 0xce, 0xf6, 0xea, 0xc5, 0x35, 0x8b, 0x0b, 0xaa, 0x0a, 0xaa, 0x2a, 0x63, 0xa5, 0x25, 0xd3, 0x95, 0x4e, 0xed, 0x39, 0x28, 0x9d, 0x73, 0xdb, 0xee, 0x16, 0xcf, 0x41, 0x03, 0x9d, 0xd8, 0x3b, 0xd7, 0x9d, 0x6e, 0x4e, 0xea, 0x28, 0x5a, 0xf9, 0xaf, 0xaf, 0x47, 0x03, 0xe3, 0x06, 0x69, 0x04, 0x34, 0x39, 0xa2, 0xa1, 0x3a, 0x4c, 0x98, 0x88, 0x22, 0x09, 0x27, 0xad, 0x31, 0x90, 0x28, 0x64, 0xb1, 0x4c, 0x48, 0x28, 0xac, 0xc4, 0x1e, 0x36, 0x79, 0xdc, 0xe3, 0xf5, 0x18, 0x41, 0x67, 0x2d, 0x29, 0xf2, 0xd4, 0x7a, 0x6b, 0x8d, 0x6e, 0xa3, 0x3b, 0xc7, 0x65, 0xb3, 0xca, 0xba, 0xab, 0x3a, 0xbd, 0xee, 0x3a, 0x27, 0xc9, 0x8c, 0x33, 0x9f, 0xa2, 0xc8, + 0x9e, 0x8c, 0xcc, 0x79, 0x52, 0xcf, 0x56, 0x6a, 0xcf, 0x79, 0xfe, 0xf2, 0xb3, 0x74, 0xdc, 0x79, 0xcc, 0x26, 0xc4, 0xc7, 0xd5, 0x9d, 0x4d, 0x58, 0xc0, 0x44, 0xd5, 0x4d, 0xce, 0xe0, 0x4a, 0xe0, 0xe8, 0x9c, 0x72, 0x5c, 0x4b, 0x98, 0x07, 0x99, 0x85, 0x98, 0xc5, 0x30, 0x0b, 0x89, 0x92, 0xa6, 0x62, 0x88, 0xb5, 0x00, 0x52, 0x4f, 0x53, 0x83, 0x35, 0xb2, 0x1a, 0x3c, 0x5d, 0x00, 0xa8, 0x4f, 0x5b, 0x9e, 0x21, 0xec, 0x5e, 0x24, 0x61, 0xb6, 0x48, 0x2d, 0x5b, 0x69, 0x2d, 0x62, 0xda, 0x5a, 0xe2, 0x4d, 0x72, 0x05, 0x0c, 0xa7, 0xe0, 0xc6, 0xb5, 0x74, 0x6d, 0x9c, 0x73, 0x25, 0x20, 0x04, 0xe4, 0x2e, 0x98, 0xdf, 0x48, 0x55, 0xf2, 0xf3, 0x06, 0xe7, 0x6f, 0x5a, 0xb0, 0xa9, 0x71, 0x5e, 0xe3, 0xbc, 0xde, 0x9e, 0xb6, 0xd6, 0xd9, 0xaa, 0x79, 0xc6, 0xff, 0x42, 0x35, 0x9f, 0xdb, 0xa2, 0xe9, + 0x3e, 0x67, 0x3d, 0x7d, 0xce, 0x2b, 0xe8, 0x5f, 0x56, 0xda, 0xd1, 0xdf, 0xff, 0x55, 0xf9, 0x80, 0xe8, 0xf0, 0xe4, 0xec, 0xfa, 0xcb, 0x71, 0x75, 0xb4, 0x08, 0x44, 0x3c, 0xa2, 0xca, 0x27, 0x05, 0xfc, 0x08, 0x08, 0x6c, 0xac, 0x62, 0x5c, 0x25, 0x12, 0x5c, 0x63, 0xe0, 0x64, 0xc4, 0x2c, 0x11, 0x76, 0xa3, 0x84, 0x9a, 0x3f, 0xa5, 0xdd, 0x0b, 0x30, 0x52, 0x14, 0xca, 0x85, 0xf2, 0xbf, 0x98, 0x5c, 0x0a, 0xa4, 0x39, 0x76, 0x3c, 0xa1, 0x84, 0xce, 0xa9, 0xe4, 0x40, 0xdc, 0x56, 0x5b, 0x03, 0x0a, 0x3b, 0x3d, 0x0b, 0x2f, 0xac, 0x2e, 0xac, 0xb6, 0xd8, 0xe9, 0x19, 0x81, 0x86, 0x1c, 0x4b, 0xce, 0xed, 0x8c, 0x60, 0x6e, 0x22, 0xc6, 0x3b, 0x73, 0x3a, 0x30, 0x98, 0x68, 0x9a, 0xab, 0x38, 0x37, 0xa7, 0xd3, 0x03, 0xbc, 0xf6, 0x5c, 0x17, 0xf8, 0xcc, 0xf3, 0x84, 0x25, 0xa0, 0xfb, 0xdf, 0x12, + 0x57, 0xd5, 0x80, 0xd4, 0x9e, 0x8f, 0xf0, 0xd4, 0xf6, 0x24, 0xcb, 0xd4, 0x3c, 0x71, 0x83, 0x02, 0x6a, 0x13, 0x47, 0x25, 0xd8, 0x99, 0x58, 0xea, 0xf1, 0x99, 0xfc, 0x55, 0x04, 0x4e, 0x38, 0x35, 0x3c, 0x15, 0xd3, 0x4b, 0x80, 0xf2, 0xaa, 0x14, 0x91, 0x72, 0xec, 0x34, 0x25, 0x81, 0x7b, 0x4e, 0x8d, 0x8e, 0x63, 0xd9, 0xd2, 0xdc, 0xf0, 0xc8, 0xf0, 0xd2, 0xf1, 0x65, 0xe3, 0xfd, 0x7d, 0xad, 0xcd, 0xb1, 0x68, 0x78, 0x49, 0xee, 0x62, 0x72, 0x32, 0xa0, 0x99, 0xc3, 0x5d, 0xdd, 0x5c, 0xd5, 0xb4, 0xd3, 0x0d, 0x9a, 0xd8, 0x75, 0xf6, 0x43, 0x83, 0x9b, 0xe7, 0xaa, 0xbc, 0x5d, 0xf0, 0xbf, 0x39, 0x51, 0x90, 0x36, 0xfc, 0x9b, 0x6e, 0x8f, 0xa8, 0x3f, 0x32, 0xb7, 0x88, 0xfd, 0x8c, 0xfa, 0x55, 0x12, 0x2c, 0x35, 0x96, 0x86, 0x46, 0x4d, 0xf8, 0x5e, 0x0f, 0xa4, 0x10, 0xb2, 0x12, 0x41, 0x42, + 0xf4, 0x9e, 0x18, 0xfc, 0x0c, 0xb2, 0x9f, 0x9d, 0x54, 0xec, 0xc3, 0xff, 0x9c, 0x10, 0xc8, 0x2f, 0x97, 0xe2, 0xcf, 0x60, 0x69, 0x1d, 0x44, 0x87, 0xf1, 0xfd, 0xec, 0x63, 0x67, 0xb6, 0xf9, 0xd6, 0x7b, 0x0e, 0xe2, 0xf0, 0xc4, 0x1b, 0xec, 0x63, 0x13, 0xbf, 0x24, 0x67, 0xbe, 0x53, 0x65, 0xe4, 0x18, 0x25, 0xf2, 0xbd, 0xf6, 0x9a, 0xd9, 0x86, 0x03, 0xb3, 0x5f, 0x4f, 0xeb, 0xb8, 0x14, 0x85, 0x25, 0x5a, 0xd1, 0x7e, 0x14, 0x22, 0xe6, 0x06, 0x5b, 0x98, 0x9d, 0x5c, 0x33, 0xd7, 0x23, 0xc7, 0x28, 0xa1, 0xef, 0x5f, 0x9a, 0xb0, 0x0a, 0x60, 0xd0, 0xfc, 0xb0, 0x9f, 0xbe, 0x7f, 0xd6, 0xf5, 0xfe, 0x96, 0xde, 0xcb, 0x1f, 0x19, 0x19, 0x79, 0xf4, 0x50, 0x6f, 0xef, 0xa1, 0x47, 0x47, 0x46, 0x1e, 0xb9, 0xbc, 0x17, 0xaf, 0x4e, 0x7d, 0x7b, 0xf4, 0x60, 0x6f, 0xef, 0xc1, 0x47, 0xa1, 0x8d, 0x7b, + 0xd1, 0x41, 0xfc, 0x08, 0xfb, 0x14, 0x9d, 0xff, 0x53, 0x6d, 0x1c, 0xa2, 0x6d, 0x5c, 0x9a, 0xb8, 0x81, 0xc7, 0xf0, 0x86, 0x04, 0x7e, 0xc8, 0xec, 0x77, 0xc4, 0x66, 0x61, 0x28, 0xee, 0x2d, 0xe8, 0x19, 0xa9, 0xa9, 0xde, 0xd0, 0x53, 0x58, 0xd8, 0xb3, 0xa1, 0xba, 0x66, 0xa4, 0xa7, 0xe0, 0x62, 0x5b, 0x5e, 0xa5, 0xdb, 0x5d, 0x95, 0x67, 0xb7, 0xe7, 0x55, 0xb9, 0xdd, 0x95, 0x79, 0x36, 0xfc, 0x95, 0x54, 0xf2, 0xfa, 0xde, 0x82, 0x82, 0xde, 0xf5, 0x35, 0xe4, 0xe9, 0xb4, 0x5c, 0xd4, 0x16, 0x84, 0xe0, 0xce, 0xc1, 0xf6, 0xfb, 0x1e, 0xc8, 0x7f, 0x91, 0xc7, 0x04, 0x1e, 0x33, 0xed, 0xb2, 0x55, 0xb9, 0x7a, 0x96, 0x91, 0x32, 0x71, 0xcb, 0x99, 0x16, 0x25, 0x62, 0xe0, 0x31, 0x2b, 0x1d, 0x0a, 0x3d, 0x8d, 0x08, 0x56, 0xe1, 0xf7, 0xdc, 0x30, 0xf1, 0xfe, 0x9b, 0x63, 0xe5, 0x3f, 0x45, 0x9f, + 0xa0, 0x52, 0xbe, 0xdf, 0x92, 0xdf, 0x90, 0x7b, 0x60, 0xc9, 0x30, 0xb5, 0xf5, 0x61, 0x12, 0xb8, 0x76, 0xa4, 0x7e, 0x8e, 0xc5, 0x28, 0x55, 0x7f, 0x82, 0x6f, 0xae, 0x43, 0xb2, 0x2d, 0xf6, 0x4c, 0x42, 0xa7, 0xea, 0x77, 0x73, 0x04, 0x5d, 0xd4, 0x87, 0x4a, 0xd1, 0x27, 0x3f, 0x8d, 0x6c, 0xff, 0xd5, 0xfb, 0x13, 0x47, 0xf9, 0xf7, 0x86, 0x97, 0x1c, 0xc8, 0x6d, 0xc8, 0xb7, 0xc8, 0xed, 0xcf, 0xe3, 0xee, 0x84, 0xf6, 0xff, 0x11, 0xe8, 0xda, 0x12, 0x57, 0x19, 0xa9, 0x95, 0xbd, 0xec, 0x3c, 0x6e, 0xa3, 0x6e, 0xaf, 0xd4, 0x70, 0x25, 0x71, 0x22, 0xb3, 0x8e, 0x99, 0x69, 0xcb, 0x42, 0xc8, 0x4e, 0xda, 0x80, 0xa1, 0x3f, 0x8f, 0xfb, 0x0d, 0x7e, 0x62, 0x53, 0xa2, 0x87, 0x97, 0x25, 0xec, 0x6c, 0xa9, 0x2d, 0x5d, 0xed, 0x45, 0x07, 0x0e, 0xf7, 0xdc, 0x7c, 0xed, 0x57, 0x2a, 0xc7, 0x1f, 0xd8, + 0xd9, 0x75, 0xc5, 0xbe, 0x8b, 0xea, 0xb8, 0xff, 0x32, 0x06, 0x5d, 0xc6, 0xc5, 0x2b, 0x7a, 0x77, 0x2f, 0xcc, 0xcf, 0x74, 0xf8, 0x8c, 0x72, 0x1f, 0xaf, 0x64, 0x5f, 0xe7, 0x5f, 0x22, 0x32, 0x5d, 0xdc, 0xa9, 0x24, 0x67, 0xa0, 0xc4, 0xa5, 0xb1, 0x8b, 0x49, 0x40, 0xae, 0xc1, 0x9b, 0x71, 0x8f, 0x21, 0xd7, 0x4b, 0x6d, 0x65, 0x12, 0xb6, 0x83, 0x51, 0xa2, 0x98, 0x54, 0x44, 0x23, 0xec, 0xeb, 0xef, 0xfc, 0x0f, 0xad, 0xb7, 0xee, 0xa2, 0xfd, 0x57, 0xf5, 0xdc, 0x7c, 0xdd, 0x57, 0xf8, 0x97, 0x6e, 0xf9, 0x66, 0x66, 0x96, 0xcf, 0x78, 0x9b, 0xfc, 0x1a, 0x7a, 0xa7, 0x41, 0x7c, 0x59, 0x3f, 0x14, 0xde, 0x66, 0x1c, 0x4c, 0x80, 0xa9, 0x8f, 0xd7, 0x10, 0xb3, 0x2e, 0xd8, 0x88, 0x04, 0x24, 0x90, 0xe0, 0x11, 0xd4, 0xb1, 0x75, 0x75, 0xca, 0xb1, 0x75, 0x88, 0xed, 0xc9, 0xce, 0x46, 0x4c, 0x76, + 0x20, 0xdb, 0xef, 0xc9, 0x99, 0xe9, 0x88, 0xa2, 0x52, 0x9d, 0xc1, 0xc1, 0x95, 0x3a, 0xf7, 0x93, 0xd3, 0x87, 0x33, 0xb8, 0xba, 0x22, 0xfe, 0x43, 0x9d, 0xd3, 0x7a, 0xc1, 0x0d, 0x3a, 0xaf, 0x2f, 0x9d, 0xd7, 0x2b, 0xba, 0x4f, 0x9a, 0xd0, 0x6b, 0xff, 0x0a, 0xe2, 0x28, 0xb4, 0xb9, 0x07, 0xa4, 0xf9, 0x95, 0x8a, 0xa5, 0x8c, 0x1a, 0xf4, 0x91, 0x8e, 0x78, 0xab, 0x01, 0x61, 0xde, 0x4b, 0xa2, 0x48, 0x74, 0x09, 0x48, 0x36, 0xf5, 0xe2, 0x99, 0x71, 0x91, 0x20, 0x60, 0xc0, 0x30, 0xf0, 0x0c, 0xc7, 0xf2, 0x1c, 0x85, 0x3f, 0x21, 0xc7, 0xb7, 0x98, 0x9d, 0xaf, 0xd5, 0x30, 0x4c, 0x38, 0xe8, 0x74, 0xd8, 0x2c, 0x9a, 0x7c, 0x6d, 0x3e, 0x54, 0xa2, 0xf2, 0x29, 0x54, 0xe6, 0x7c, 0x25, 0x75, 0x9a, 0x27, 0xb0, 0x0c, 0x6c, 0xd2, 0x63, 0x23, 0x85, 0x15, 0x95, 0x72, 0xa9, 0x59, 0x29, 0x3d, 0xad, + 0x77, 0x5a, 0xaf, 0x7e, 0x47, 0x99, 0x69, 0xca, 0xd2, 0x9f, 0x6f, 0xf5, 0x5a, 0x35, 0xbb, 0xec, 0x25, 0x6d, 0x45, 0x45, 0x1d, 0x75, 0x15, 0x0e, 0xab, 0xd7, 0xa2, 0xd9, 0x63, 0x2f, 0x6d, 0x2b, 0x2c, 0xee, 0xa8, 0x8b, 0x2a, 0x96, 0x4a, 0xef, 0x64, 0x88, 0x48, 0xaf, 0xd1, 0x89, 0x58, 0x2a, 0xd3, 0x67, 0xfb, 0x0c, 0x85, 0xfd, 0x0d, 0x41, 0x77, 0xc8, 0x63, 0x70, 0xfa, 0xf5, 0x85, 0x0b, 0xe3, 0x41, 0x4f, 0xc8, 0x4d, 0xe9, 0x4f, 0xfc, 0x50, 0x3e, 0x14, 0x9e, 0x87, 0x39, 0x0c, 0x12, 0x46, 0x3c, 0x97, 0x4b, 0xf4, 0x00, 0xc3, 0x9e, 0x43, 0xee, 0x6f, 0x09, 0x8b, 0x21, 0x58, 0x79, 0x98, 0x9d, 0xc7, 0x30, 0xc4, 0xa3, 0x9e, 0x40, 0x64, 0xf9, 0x78, 0x02, 0x24, 0x36, 0x45, 0x55, 0x62, 0x6b, 0xf7, 0x61, 0x8a, 0x7c, 0x52, 0x03, 0xf1, 0x59, 0x11, 0x9e, 0x97, 0x24, 0x20, 0x98, 0xd5, + 0x2a, 0x5d, 0xb1, 0x8f, 0x54, 0xb7, 0x9e, 0xf8, 0xbb, 0x24, 0xde, 0x53, 0x18, 0xcf, 0xe3, 0x12, 0x16, 0x2e, 0xe4, 0x40, 0x72, 0xd6, 0x7b, 0x10, 0x7d, 0x8f, 0xc0, 0x31, 0x4a, 0xa4, 0xa4, 0x9e, 0x2c, 0x04, 0x0e, 0x8b, 0x46, 0x63, 0xa1, 0x21, 0xbc, 0x72, 0x80, 0x22, 0x37, 0x6c, 0xb3, 0x3a, 0x75, 0x1f, 0x8a, 0xf2, 0x7b, 0xac, 0xd6, 0xbf, 0x6a, 0xf5, 0x08, 0x93, 0xf7, 0x4c, 0x4e, 0x32, 0x3b, 0x84, 0x12, 0x76, 0x40, 0x91, 0x8d, 0x43, 0xcc, 0x03, 0x4c, 0x25, 0xf3, 0x16, 0x12, 0x18, 0xe2, 0x33, 0x4d, 0xe3, 0xcb, 0xbf, 0x25, 0x83, 0x4b, 0x62, 0x46, 0x2d, 0x94, 0xe0, 0xfb, 0x15, 0x4e, 0x20, 0x7d, 0xc6, 0x59, 0xda, 0x82, 0x19, 0x82, 0x23, 0xa0, 0x10, 0x18, 0x35, 0x56, 0x93, 0xb6, 0x18, 0x69, 0x88, 0x5e, 0xd2, 0x18, 0x44, 0x1a, 0xf3, 0x6b, 0xbd, 0xdd, 0x64, 0x50, 0xbe, 0x7d, + 0x8d, 0xd5, 0xa1, 0x97, 0x5e, 0xd8, 0x88, 0x2b, 0x27, 0x5e, 0x54, 0x38, 0xb1, 0x42, 0xab, 0x91, 0x3e, 0x12, 0x74, 0xc8, 0x2d, 0x1d, 0x3a, 0x5f, 0x3e, 0xa7, 0x1e, 0x86, 0x39, 0xfe, 0x0b, 0xe1, 0x78, 0xc2, 0x3f, 0xab, 0x2e, 0x5e, 0x0d, 0xb3, 0x5c, 0x81, 0x04, 0x98, 0xea, 0x09, 0xc7, 0xab, 0xe9, 0xde, 0xdb, 0x43, 0xc4, 0x7b, 0xfb, 0x34, 0xde, 0x56, 0xaa, 0x7f, 0xdd, 0xdb, 0xea, 0x0c, 0x53, 0xff, 0xa6, 0xb3, 0x7b, 0x5b, 0xa5, 0x5d, 0x12, 0x0a, 0x5b, 0x7e, 0x8e, 0xd1, 0x98, 0x33, 0xdd, 0xdb, 0x6a, 0xc6, 0x77, 0x46, 0xf6, 0x4d, 0x7b, 0x09, 0xbf, 0x08, 0x7d, 0x37, 0x52, 0x5c, 0xd9, 0x60, 0xdc, 0x47, 0x7d, 0x21, 0xa0, 0xd3, 0xa0, 0x3d, 0x70, 0x3c, 0xcf, 0x2d, 0x66, 0x38, 0x8e, 0x1f, 0x20, 0xda, 0x77, 0xaf, 0xde, 0x44, 0x76, 0x08, 0x0a, 0x32, 0x79, 0xb6, 0xce, 0x90, 0x89, + 0xd0, 0x76, 0xf6, 0x66, 0x4f, 0xdc, 0x4d, 0xa7, 0x48, 0xfd, 0x59, 0x1a, 0x2a, 0xfd, 0x6d, 0x9f, 0xac, 0xbf, 0xc2, 0xbc, 0x78, 0x43, 0x7c, 0x3d, 0xd1, 0xd6, 0xd6, 0x24, 0xd6, 0x14, 0x0d, 0x2a, 0xb7, 0x12, 0x53, 0xef, 0x4d, 0xe2, 0x6d, 0xb5, 0x91, 0xf0, 0xda, 0xac, 0xd3, 0xf7, 0x83, 0x84, 0xd5, 0x9c, 0x73, 0x57, 0x78, 0xd2, 0x95, 0x12, 0xa3, 0x3b, 0xdf, 0x66, 0xcd, 0x25, 0x8d, 0xca, 0xb5, 0xda, 0xf2, 0xdd, 0x46, 0xf4, 0x4d, 0xe8, 0x94, 0x15, 0x3a, 0x67, 0x30, 0x40, 0xe7, 0xac, 0xd0, 0xc9, 0x89, 0xdb, 0xc8, 0xf4, 0x12, 0xd7, 0xd8, 0x72, 0xc9, 0xb3, 0xdc, 0xa9, 0x8e, 0xcf, 0xfc, 0xfe, 0x3a, 0x61, 0xff, 0x0b, 0x79, 0x89, 0x6d, 0x16, 0xaf, 0xa1, 0x51, 0xdb, 0x17, 0xca, 0xbb, 0x85, 0x9d, 0x43, 0x49, 0x24, 0xcc, 0x84, 0x6b, 0x10, 0xbd, 0x4c, 0xda, 0x48, 0xbc, 0x85, 0x5c, + 0xb3, 0xd2, 0x88, 0x35, 0xc6, 0xd2, 0x44, 0x0c, 0x3d, 0x16, 0xcf, 0xa7, 0x00, 0x99, 0xd9, 0x59, 0x86, 0x4c, 0xca, 0xaa, 0xf8, 0x19, 0xac, 0x6a, 0x76, 0x6f, 0xd8, 0xe6, 0xe9, 0x0c, 0x4a, 0xfa, 0x93, 0xd9, 0x5f, 0xec, 0xb0, 0x17, 0x91, 0xc8, 0x79, 0x45, 0x76, 0x47, 0xb1, 0xdf, 0x2c, 0x96, 0x49, 0xef, 0xe8, 0x14, 0xc8, 0xa0, 0xd1, 0x8a, 0x38, 0x9c, 0x55, 0xe4, 0x35, 0x9b, 0xbd, 0x45, 0x59, 0x24, 0x01, 0x32, 0xca, 0x3e, 0xb4, 0x8b, 0xb8, 0xeb, 0xf1, 0xcb, 0xc2, 0x09, 0x18, 0x03, 0x3f, 0xb3, 0xf4, 0x71, 0x17, 0xb1, 0x0d, 0x49, 0x00, 0xe4, 0x58, 0x49, 0xd8, 0x16, 0xbc, 0x9e, 0x41, 0xbc, 0x7c, 0x8e, 0x23, 0x70, 0xb0, 0x58, 0x87, 0x58, 0x8a, 0x00, 0x43, 0xfd, 0xde, 0x67, 0x27, 0x0c, 0xc4, 0xcd, 0x26, 0x13, 0x62, 0x4c, 0x7e, 0x93, 0x8f, 0xa0, 0xb2, 0xca, 0xa0, 0xac, 0x62, + 0x1a, 0x50, 0x56, 0x9e, 0xf5, 0xb1, 0x14, 0x74, 0xcf, 0x62, 0xad, 0x20, 0xb1, 0x86, 0x22, 0xe8, 0x3a, 0x47, 0x09, 0x41, 0x5f, 0x2d, 0x71, 0x38, 0xca, 0x02, 0x16, 0x4b, 0xa0, 0x0c, 0x79, 0xdf, 0x7e, 0xe2, 0xed, 0x2b, 0x34, 0x56, 0xf1, 0x42, 0x25, 0xfc, 0xb9, 0x48, 0xb4, 0x6a, 0xf8, 0xe3, 0x24, 0x39, 0xbb, 0x8c, 0x64, 0x2b, 0xcb, 0x26, 0xd9, 0x1d, 0xd2, 0x27, 0x48, 0xad, 0x14, 0x0e, 0x28, 0x35, 0x1a, 0xe5, 0x01, 0x50, 0x94, 0x31, 0xd3, 0x26, 0xa8, 0xf0, 0x9b, 0x0a, 0x17, 0xf4, 0x25, 0xc8, 0xac, 0x95, 0x3b, 0xe1, 0x26, 0x8d, 0xa3, 0xb0, 0x4e, 0xe4, 0x3c, 0x1f, 0xb6, 0xed, 0x29, 0xe1, 0x83, 0x18, 0x0b, 0x33, 0xf3, 0x1d, 0xf1, 0x9c, 0xe9, 0x59, 0xf0, 0xf6, 0x53, 0x72, 0x24, 0xbb, 0x15, 0x34, 0x05, 0xe6, 0xd8, 0xad, 0x54, 0x10, 0xa5, 0x08, 0xba, 0x76, 0x16, 0xd1, 0x51, + 0xe9, 0x47, 0x4f, 0x7c, 0x74, 0xad, 0xd6, 0xa7, 0x1b, 0x67, 0x05, 0x16, 0xfe, 0x8d, 0xeb, 0x7c, 0x5a, 0x31, 0x7b, 0x5a, 0x06, 0x07, 0x29, 0x80, 0xa4, 0x8f, 0x90, 0x5e, 0x30, 0x5e, 0xcb, 0x2b, 0x39, 0x4e, 0xc9, 0x5f, 0x63, 0x12, 0x64, 0x5b, 0xdb, 0xdf, 0x73, 0x03, 0x6c, 0x9c, 0xef, 0x62, 0x9c, 0xc4, 0x1f, 0x91, 0xc0, 0xc2, 0x80, 0xdc, 0x21, 0x43, 0xaf, 0x11, 0x39, 0x85, 0x84, 0xbc, 0x85, 0xa4, 0x6c, 0x6b, 0x16, 0x27, 0x58, 0x88, 0x70, 0x40, 0x62, 0x6d, 0x11, 0x54, 0x1a, 0x9f, 0x38, 0xf3, 0xb3, 0x08, 0x45, 0xd9, 0x38, 0x8b, 0x4b, 0x59, 0xbd, 0x41, 0xcf, 0xba, 0xde, 0xf3, 0xc0, 0x67, 0x26, 0x9b, 0xf3, 0xbe, 0x9b, 0xcb, 0x84, 0xef, 0x45, 0x98, 0xe5, 0xbb, 0x34, 0x99, 0xea, 0x47, 0x34, 0xf0, 0x67, 0xe7, 0xce, 0xa9, 0xff, 0x1f, 0x51, 0x1a, 0x34, 0x24, 0x6e, 0x25, 0xca, + 0xc7, 0xd5, 0xec, 0x2a, 0x72, 0x64, 0xf7, 0x28, 0xc8, 0x47, 0xc4, 0x0f, 0x8c, 0x84, 0x9c, 0x42, 0x95, 0xfb, 0xa4, 0x1f, 0xb2, 0xab, 0x24, 0x57, 0x3f, 0x7a, 0x47, 0xc6, 0xdc, 0x90, 0xee, 0xc5, 0xcb, 0x99, 0x6c, 0x68, 0x50, 0x6e, 0x3c, 0x48, 0xe4, 0x24, 0x07, 0xbd, 0x21, 0x4b, 0xda, 0x87, 0x0e, 0x13, 0xfb, 0x58, 0xba, 0x24, 0x36, 0xb0, 0x3d, 0x61, 0xbd, 0x1c, 0x7b, 0x8e, 0x97, 0x3d, 0xc6, 0xa9, 0x5c, 0x1a, 0x4b, 0x09, 0xac, 0xc1, 0x50, 0xac, 0x02, 0x19, 0x78, 0x8d, 0x31, 0xdb, 0x52, 0x5a, 0xa1, 0x75, 0x95, 0x05, 0x33, 0xaa, 0xcf, 0x6b, 0xf6, 0x3b, 0xcb, 0xbb, 0x8a, 0xca, 0xcf, 0xcb, 0x71, 0xf1, 0x3f, 0x11, 0x45, 0xb1, 0xbe, 0xc8, 0x9a, 0xe7, 0x31, 0x65, 0x3b, 0x23, 0xcd, 0xc1, 0x70, 0x4b, 0x69, 0x76, 0xb6, 0xb5, 0x53, 0x4b, 0x63, 0x25, 0x08, 0x22, 0x7e, 0x5b, 0x61, + 0x84, 0xbd, 0xe9, 0x09, 0x86, 0x34, 0x40, 0x60, 0x1e, 0x23, 0xe0, 0xaa, 0x04, 0xf1, 0x1f, 0x39, 0xc8, 0xe6, 0x44, 0xe2, 0x09, 0x70, 0x57, 0xb3, 0xbc, 0xe0, 0xc4, 0x41, 0xe6, 0xfe, 0x16, 0x82, 0x6d, 0xfd, 0xbd, 0xc9, 0x7e, 0x66, 0x25, 0xec, 0x5f, 0x08, 0x3d, 0xb4, 0x52, 0xde, 0xbf, 0x90, 0xf4, 0xc7, 0xc9, 0x1b, 0x38, 0x2f, 0xff, 0x5b, 0x12, 0x3b, 0x51, 0xf6, 0x32, 0x57, 0xcb, 0xc6, 0xc4, 0x20, 0xd1, 0xca, 0x38, 0x39, 0xf2, 0x1e, 0xce, 0x6c, 0x1c, 0x78, 0x2c, 0x40, 0x43, 0x04, 0x8a, 0x32, 0x1e, 0x0f, 0x5b, 0x83, 0xe6, 0x59, 0xe3, 0xee, 0xc5, 0xdc, 0x0e, 0xb5, 0xc1, 0x64, 0x33, 0x6c, 0x29, 0x73, 0x3b, 0x04, 0x53, 0x86, 0x59, 0x23, 0xc0, 0x58, 0x7e, 0x71, 0x32, 0xca, 0x5e, 0xc3, 0x9d, 0x80, 0xfd, 0x59, 0x4f, 0xd6, 0x7b, 0x02, 0x68, 0xc7, 0x47, 0x8b, 0xcb, 0xe1, 0xb7, + 0x48, 0x1d, 0xf8, 0x1f, 0x19, 0x41, 0x47, 0xcb, 0xc9, 0x63, 0x19, 0xb9, 0xd6, 0x16, 0xee, 0x8b, 0x56, 0xc7, 0x3f, 0xef, 0xb4, 0x3a, 0xe4, 0xb9, 0x70, 0x12, 0x5f, 0x81, 0xbb, 0x59, 0x2f, 0x8c, 0x03, 0x68, 0x48, 0x32, 0x22, 0xef, 0x62, 0x0a, 0x6d, 0x32, 0x40, 0x2a, 0xeb, 0xa5, 0xfc, 0x95, 0xd5, 0xb3, 0x44, 0x70, 0x30, 0x7b, 0xa2, 0xb8, 0x7b, 0xe2, 0x61, 0xd6, 0x7b, 0xcb, 0x2d, 0x89, 0x18, 0x12, 0x42, 0x26, 0xf0, 0xdd, 0xe3, 0x40, 0x17, 0xd8, 0xab, 0x91, 0x09, 0xe8, 0xf2, 0x84, 0x4c, 0x17, 0xf4, 0x6d, 0x64, 0x4a, 0xd0, 0x05, 0xf2, 0xb0, 0x9d, 0x8a, 0x7e, 0xba, 0xaf, 0x87, 0x4f, 0x93, 0x87, 0xf8, 0x41, 0x7f, 0x20, 0xfc, 0x27, 0xe4, 0xb9, 0x9f, 0xc9, 0x47, 0xb5, 0x90, 0xe7, 0xf1, 0x54, 0x9e, 0xda, 0x44, 0x9e, 0x36, 0x78, 0xd7, 0x9b, 0x8a, 0x62, 0xc8, 0xf3, 0xf0, 0xe9, + 0xde, 0x35, 0xf9, 0x3e, 0x69, 0x0f, 0x7d, 0xd7, 0x7d, 0x93, 0x2f, 0x9e, 0xe6, 0x5d, 0x3b, 0x84, 0x2c, 0x7c, 0xbf, 0xf8, 0x9b, 0x44, 0x9b, 0xab, 0x20, 0xcf, 0xf3, 0xa9, 0x3c, 0x55, 0x89, 0x3c, 0x5b, 0xb9, 0x47, 0x40, 0xe6, 0xb9, 0x1c, 0xf2, 0x1c, 0x87, 0x3c, 0x35, 0x90, 0xe7, 0x7b, 0xa9, 0x3c, 0x35, 0x74, 0x30, 0xf1, 0xe4, 0x1d, 0xc0, 0x23, 0xae, 0x10, 0x1d, 0x40, 0x77, 0x23, 0x91, 0x45, 0x78, 0x90, 0x1a, 0xa9, 0xbe, 0x40, 0x7f, 0x91, 0x37, 0x1c, 0xd0, 0x19, 0xd4, 0x20, 0x27, 0x64, 0xea, 0x54, 0x46, 0xb5, 0x51, 0x96, 0x8b, 0x84, 0x69, 0x72, 0x11, 0x2c, 0xa4, 0xd4, 0x6f, 0xe8, 0x16, 0x5f, 0xbe, 0xee, 0x8b, 0x2b, 0xad, 0xd9, 0x99, 0xaf, 0xfc, 0xd1, 0x57, 0xa0, 0x7f, 0x72, 0xa3, 0xd5, 0xa3, 0xff, 0x5c, 0x74, 0x58, 0xad, 0x7f, 0xc8, 0xd4, 0xfd, 0xcd, 0x66, 0xf9, 0x43, + 0x46, 0xc6, 0xdf, 0x60, 0x34, 0x56, 0x0a, 0x4a, 0xfc, 0xbc, 0x68, 0x04, 0xe9, 0x27, 0x2f, 0x1e, 0x92, 0x7d, 0x13, 0x39, 0xd9, 0xdd, 0x90, 0x9b, 0x72, 0x37, 0x24, 0x62, 0x98, 0x82, 0x84, 0xe9, 0xca, 0x40, 0x19, 0x09, 0x31, 0x4c, 0xf6, 0xd4, 0xa3, 0xf2, 0x68, 0x05, 0x3a, 0x72, 0xe4, 0x6b, 0x37, 0xb5, 0x2e, 0x2d, 0xa8, 0x0d, 0x64, 0xde, 0x74, 0xa7, 0x68, 0x3c, 0xbc, 0xaf, 0xb5, 0xd7, 0x14, 0x88, 0x79, 0xaf, 0x38, 0x90, 0x90, 0xe9, 0x9f, 0x15, 0x2e, 0xa7, 0x78, 0xf2, 0xb1, 0x78, 0x39, 0xb1, 0x49, 0x47, 0x82, 0xb8, 0x46, 0x91, 0x5e, 0xd6, 0x21, 0xc0, 0xd9, 0x33, 0xe5, 0x1c, 0xe5, 0x99, 0x84, 0x79, 0xb2, 0x5f, 0x9e, 0x5e, 0x96, 0x91, 0x0e, 0x51, 0x87, 0xe9, 0x74, 0xe2, 0xca, 0x7c, 0x2a, 0x1f, 0xf6, 0x90, 0x35, 0x28, 0x3e, 0x9d, 0x18, 0x37, 0xdb, 0xd4, 0x1a, 0x84, 0x31, + 0xb1, 0xc9, 0x63, 0xc2, 0x74, 0x71, 0x1f, 0xb3, 0xb7, 0x0b, 0xc7, 0xa8, 0xce, 0xdd, 0x20, 0xef, 0x9f, 0xc6, 0x64, 0xc4, 0xb8, 0x24, 0xc4, 0x3c, 0xdd, 0x78, 0x6c, 0xa9, 0xa7, 0x29, 0xb9, 0x71, 0x80, 0xc8, 0x8d, 0xbd, 0x03, 0x4f, 0x4c, 0x0f, 0x1a, 0x4a, 0xfe, 0x76, 0xb1, 0x8b, 0x4e, 0xde, 0x4f, 0x7e, 0x84, 0x63, 0xfb, 0xee, 0xde, 0x27, 0xfb, 0x8a, 0x7c, 0x55, 0x1a, 0xc5, 0x1b, 0x27, 0xbf, 0x4a, 0x4c, 0x4f, 0x1f, 0x85, 0x61, 0x28, 0xc9, 0xe7, 0x89, 0xc5, 0x7f, 0x0c, 0x6f, 0x7c, 0xf7, 0xdd, 0x05, 0x47, 0x8f, 0x3e, 0xf4, 0xee, 0xbb, 0x7d, 0x37, 0xdf, 0x3c, 0x3b, 0x1f, 0x47, 0x78, 0x1c, 0x22, 0x01, 0x30, 0xad, 0xd8, 0x7e, 0xf4, 0xe8, 0x82, 0x77, 0xdf, 0x95, 0x46, 0x6f, 0xbe, 0xb9, 0xef, 0xdd, 0x77, 0x21, 0xdf, 0x5b, 0x13, 0x9f, 0x62, 0xc3, 0xe4, 0x77, 0xe5, 0x7c, 0x34, + 0x46, 0x31, 0x75, 0x21, 0x40, 0x17, 0x1e, 0x83, 0x6c, 0x0b, 0xa4, 0x5f, 0x43, 0xee, 0xf7, 0x16, 0xc0, 0xec, 0x52, 0x4d, 0xfc, 0x1d, 0xd7, 0x4f, 0x3e, 0x47, 0xf3, 0xf1, 0xe4, 0xbd, 0x88, 0xa2, 0x5f, 0xe1, 0xfa, 0x77, 0x17, 0x1c, 0x3b, 0x06, 0xf9, 0xde, 0xeb, 0x83, 0x8a, 0x81, 0x0e, 0xcf, 0x4b, 0x1e, 0xbc, 0x6a, 0xf2, 0x76, 0x8a, 0x81, 0x7e, 0x06, 0xbd, 0xde, 0x1f, 0x3c, 0x9d, 0x5e, 0x6f, 0x89, 0x24, 0x60, 0x2b, 0x85, 0xe7, 0x3d, 0x95, 0xdd, 0xb9, 0x79, 0xdd, 0x55, 0x5e, 0x6f, 0x55, 0x77, 0x5e, 0x6e, 0x77, 0xa5, 0x67, 0x4b, 0x79, 0x51, 0x41, 0x34, 0x5a, 0x50, 0x54, 0x2e, 0xbd, 0x95, 0xdf, 0x16, 0xc9, 0xce, 0x8e, 0xb4, 0xe5, 0xe7, 0xb5, 0x95, 0xc1, 0x7e, 0xdb, 0x96, 0x97, 0x5f, 0x51, 0x91, 0x5f, 0x50, 0x51, 0x21, 0xd3, 0xe8, 0x18, 0x77, 0x08, 0x8f, 0x0b, 0x5d, 0xd0, + 0x56, 0x57, 0xdc, 0x91, 0xd6, 0x57, 0x23, 0x11, 0x77, 0x58, 0x8e, 0x5f, 0x8e, 0xc7, 0xa5, 0x87, 0x0f, 0xa3, 0x37, 0xf9, 0x97, 0xa4, 0xa7, 0xd1, 0x09, 0xb9, 0xfc, 0xe5, 0xd3, 0xca, 0xa7, 0xf3, 0xb9, 0x48, 0x96, 0xa7, 0x71, 0x8b, 0xf1, 0xf8, 0x61, 0xe9, 0x61, 0xf4, 0x26, 0x77, 0x39, 0x6a, 0x93, 0x7d, 0xee, 0x8f, 0x91, 0xd8, 0x86, 0x50, 0x15, 0x4f, 0x4f, 0x7e, 0x88, 0x8e, 0x40, 0x77, 0xb8, 0xe5, 0x32, 0xf8, 0x19, 0x3d, 0xde, 0xe7, 0xf5, 0x14, 0x59, 0x34, 0x60, 0x26, 0xd1, 0x99, 0xa1, 0x01, 0xc1, 0xc3, 0xfb, 0xf6, 0xa1, 0x13, 0xd2, 0xd3, 0xb4, 0xfc, 0x51, 0x28, 0xbf, 0x33, 0x59, 0x9e, 0xdc, 0x0f, 0x62, 0xc2, 0x58, 0xd3, 0x95, 0x47, 0x34, 0xbe, 0x33, 0xde, 0x29, 0xb9, 0xd0, 0x52, 0x12, 0x5d, 0x76, 0x1f, 0x6a, 0x85, 0xf2, 0x5d, 0xd2, 0x51, 0xee, 0xd8, 0xe4, 0x53, 0x30, + 0x13, 0x57, 0x75, 0x3f, 0xa4, 0x23, 0x33, 0x91, 0x9c, 0x34, 0x40, 0xe3, 0x47, 0x98, 0x44, 0x33, 0x36, 0xa4, 0xf0, 0x5c, 0x0d, 0x34, 0x89, 0x68, 0x31, 0x53, 0x29, 0x71, 0x6b, 0xea, 0x61, 0x92, 0x6c, 0xcb, 0x09, 0xd9, 0xe6, 0x25, 0x7c, 0xe1, 0x45, 0x46, 0x84, 0xa9, 0x4a, 0x7c, 0xe1, 0x79, 0x3d, 0xb4, 0x1f, 0x78, 0x33, 0x77, 0xec, 0xf3, 0x47, 0xd6, 0xb0, 0x57, 0xb7, 0x49, 0x6f, 0xa2, 0xe0, 0xdd, 0x77, 0x33, 0xd3, 0xda, 0xc0, 0x33, 0x83, 0x72, 0x1b, 0x0c, 0x14, 0x97, 0x65, 0x24, 0x45, 0x89, 0xa9, 0x26, 0xe8, 0x67, 0x92, 0x48, 0x6e, 0x41, 0xf2, 0x19, 0x69, 0x40, 0x32, 0x49, 0x6e, 0x81, 0x7a, 0x3a, 0x01, 0x78, 0x42, 0x40, 0x33, 0x7d, 0xfd, 0x07, 0x97, 0xc2, 0xbb, 0x29, 0xfd, 0xde, 0x95, 0xcc, 0xac, 0x6b, 0x72, 0x07, 0xf0, 0xc7, 0x8a, 0xe3, 0xb0, 0xb1, 0x77, 0x80, 0xcc, + 0xdd, 0x9e, 0x04, 0x87, 0x93, 0x77, 0x17, 0xd9, 0x9f, 0x46, 0x3e, 0xc6, 0x59, 0xcc, 0xa4, 0x76, 0x9c, 0xe4, 0x31, 0x0b, 0x30, 0x8c, 0x40, 0xb0, 0x3c, 0x06, 0xa3, 0xc3, 0xba, 0xa4, 0x8f, 0xa4, 0x4f, 0x5c, 0x2e, 0xa4, 0x46, 0x7a, 0xe9, 0xc2, 0x75, 0x57, 0x66, 0x46, 0x32, 0x0e, 0xae, 0x97, 0x31, 0x6e, 0x16, 0xe3, 0xdd, 0xc2, 0x4a, 0x1a, 0x03, 0xa3, 0x3d, 0x01, 0x75, 0x40, 0x50, 0x05, 0x40, 0xaa, 0x5c, 0x39, 0xe3, 0xac, 0x28, 0x8b, 0xc0, 0x08, 0x60, 0x76, 0x31, 0x79, 0x17, 0x34, 0x64, 0x88, 0x42, 0xac, 0x0f, 0x90, 0x7c, 0x20, 0xf4, 0x6b, 0x4c, 0x26, 0x93, 0xd5, 0x64, 0x35, 0x87, 0xfd, 0x3c, 0x3d, 0x0c, 0x99, 0x29, 0x19, 0xc7, 0x60, 0x76, 0xa2, 0xa3, 0x96, 0x60, 0x69, 0x76, 0x52, 0xb4, 0x24, 0xa0, 0xff, 0xef, 0xee, 0xe3, 0x97, 0x38, 0x64, 0xa0, 0xff, 0xa4, 0x2c, 0xf9, + 0x3f, 0x13, 0x9f, 0x61, 0x85, 0x2c, 0x0b, 0xff, 0x80, 0x0b, 0xe3, 0x7f, 0x08, 0x7d, 0xb4, 0x5d, 0xbe, 0xb8, 0xdb, 0x4a, 0xbd, 0xc9, 0x88, 0xb7, 0x7e, 0xfb, 0xec, 0x33, 0x2b, 0xf9, 0xf0, 0xee, 0x14, 0x5d, 0x03, 0x85, 0x10, 0xfa, 0x39, 0x88, 0xaf, 0x8e, 0xe9, 0xe2, 0x2c, 0x0a, 0x49, 0x47, 0x06, 0xd0, 0x18, 0xff, 0xa3, 0x54, 0x43, 0xe4, 0x17, 0xf7, 0x4a, 0x47, 0x96, 0x23, 0xaa, 0x02, 0x5c, 0xcd, 0x5d, 0x82, 0x8f, 0x0a, 0x63, 0xf4, 0xbd, 0xf9, 0xf1, 0x30, 0x81, 0x58, 0x48, 0x98, 0xdb, 0x62, 0xd2, 0xf9, 0x54, 0x9f, 0x11, 0x33, 0x15, 0xec, 0x80, 0x4f, 0x23, 0x80, 0x5a, 0x61, 0x42, 0x4f, 0x89, 0xd2, 0x89, 0x80, 0x06, 0xff, 0x45, 0xfa, 0xc7, 0xaf, 0xb5, 0x24, 0xa4, 0xe6, 0x44, 0x03, 0x2c, 0xef, 0x5d, 0x2a, 0xaf, 0xf7, 0x7a, 0x58, 0xaf, 0xaf, 0xf2, 0x0f, 0xc2, 0xac, 0xc8, + 0x8e, 0xdb, 0xe5, 0x53, 0xa6, 0xf3, 0xc8, 0x9c, 0x59, 0x87, 0x4f, 0x5d, 0x2c, 0x51, 0xfc, 0xea, 0xc4, 0x56, 0x2c, 0xf2, 0x0f, 0x4a, 0xaf, 0xef, 0xbd, 0x9f, 0xae, 0x75, 0x2f, 0xf7, 0x1b, 0x61, 0x2d, 0xcc, 0x95, 0xac, 0xb8, 0x55, 0xa9, 0xe0, 0xa1, 0xd9, 0x02, 0xf5, 0x26, 0x94, 0x85, 0x19, 0xa3, 0x81, 0x12, 0x29, 0x60, 0xf6, 0x45, 0x03, 0x30, 0x1a, 0x3c, 0xf7, 0x1b, 0xa9, 0xf0, 0x2e, 0xe9, 0x97, 0x77, 0x71, 0x2d, 0xdc, 0xcb, 0x5b, 0x9e, 0x79, 0xa7, 0x55, 0xda, 0x8b, 0xe3, 0x12, 0x5d, 0xf3, 0x93, 0x5b, 0xb9, 0xdf, 0xe0, 0x1d, 0x89, 0x7a, 0x04, 0x1e, 0x93, 0x0b, 0xe0, 0xf6, 0xa9, 0x7a, 0x8c, 0x98, 0x2e, 0xf7, 0x00, 0x2c, 0x77, 0x3e, 0x1a, 0xd1, 0x73, 0xbf, 0xf9, 0xfc, 0x99, 0xbb, 0x50, 0xee, 0xd7, 0xc5, 0x9b, 0x25, 0xac, 0x90, 0x36, 0xb7, 0xfe, 0xb7, 0xf4, 0x91, 0x2c, + 0xd7, 0x6c, 0xe5, 0x76, 0xe2, 0xef, 0x0b, 0x57, 0xe3, 0x10, 0x5f, 0x39, 0x79, 0x01, 0xf3, 0x0a, 0x13, 0xe2, 0xab, 0x99, 0xc3, 0xcc, 0x2b, 0xb0, 0xe7, 0x1c, 0x67, 0xee, 0x63, 0xec, 0xe4, 0x4c, 0xe2, 0x15, 0xd8, 0x72, 0x88, 0x6c, 0x47, 0x3f, 0x31, 0xfa, 0xf6, 0xe4, 0x7d, 0x29, 0xd9, 0x61, 0x27, 0xc8, 0x0e, 0x72, 0xd9, 0x21, 0x5a, 0xb6, 0x86, 0x79, 0xee, 0x1c, 0xca, 0x7e, 0x5f, 0x38, 0x02, 0x65, 0xab, 0xfe, 0xc5, 0xf7, 0xca, 0x65, 0x57, 0x9e, 0xe3, 0x7b, 0x77, 0x71, 0x25, 0xf0, 0xde, 0x27, 0x40, 0x76, 0x7d, 0x92, 0xb9, 0x8b, 0x91, 0x7b, 0xfd, 0x19, 0x12, 0xe9, 0xdb, 0x3f, 0xa3, 0x65, 0xbf, 0x3d, 0x79, 0x57, 0xa2, 0xf0, 0x67, 0x72, 0x61, 0xfa, 0x99, 0x2c, 0x7b, 0xcf, 0x8c, 0xb2, 0xab, 0x68, 0xd9, 0x1a, 0x90, 0x99, 0xe6, 0x52, 0xf6, 0x41, 0xe1, 0xa9, 0x54, 0xd9, 0xaa, + 0xc9, 0x35, 0x89, 0xf7, 0xee, 0x3a, 0x6b, 0xd9, 0xfc, 0xc9, 0x3f, 0x73, 0xbf, 0xe3, 0xef, 0x67, 0x04, 0xdc, 0x7c, 0x92, 0x8c, 0x70, 0xb1, 0x74, 0x23, 0xfb, 0x09, 0xff, 0x34, 0x53, 0xc0, 0x74, 0xc6, 0xdb, 0x32, 0x74, 0x98, 0xc8, 0xb0, 0xa2, 0xe0, 0x71, 0x63, 0x8e, 0x67, 0x53, 0xda, 0x02, 0x0d, 0xfd, 0xc1, 0x0c, 0x10, 0x73, 0xc2, 0x91, 0x6e, 0x05, 0x12, 0xc5, 0xe1, 0xee, 0xa4, 0xa3, 0xdd, 0x18, 0x0d, 0x22, 0x5b, 0xc0, 0x14, 0x18, 0x2b, 0x02, 0xc6, 0x80, 0xcf, 0xa2, 0x54, 0x39, 0xf2, 0x23, 0xfa, 0x50, 0x11, 0xaa, 0x43, 0x11, 0x19, 0xea, 0xc2, 0x4a, 0x7d, 0xac, 0x05, 0x27, 0x6c, 0x58, 0xa9, 0x38, 0x13, 0xb8, 0x8c, 0x86, 0x2a, 0xf1, 0xe8, 0x7f, 0xb2, 0xc9, 0xea, 0xbc, 0xfd, 0x96, 0xab, 0xed, 0x21, 0xa3, 0x2b, 0xab, 0xa5, 0x01, 0x6f, 0xf2, 0x74, 0x6c, 0x9f, 0xef, 0x89, + 0x65, 0x8b, 0x58, 0x91, 0x61, 0x37, 0xf6, 0xc6, 0xa5, 0xd5, 0xec, 0x32, 0x2d, 0x6f, 0xb5, 0xf4, 0xed, 0xff, 0xfa, 0x03, 0xbc, 0xb0, 0x59, 0xaf, 0x5a, 0xbe, 0x7e, 0xff, 0xe7, 0x6f, 0x24, 0x42, 0x98, 0xa8, 0x94, 0x5c, 0xfb, 0x02, 0xf6, 0x96, 0x93, 0x77, 0x27, 0xfa, 0x21, 0x6c, 0x83, 0x7e, 0x34, 0x33, 0xdf, 0x93, 0x63, 0xec, 0x98, 0xfc, 0x99, 0x19, 0x2c, 0x11, 0xba, 0x54, 0xca, 0x28, 0x12, 0x15, 0x6c, 0x97, 0x0f, 0xb8, 0xa9, 0x23, 0x6e, 0x82, 0x0f, 0x6e, 0x66, 0x12, 0xb5, 0x8b, 0x21, 0x45, 0xa2, 0xd0, 0x29, 0x19, 0xb0, 0x6a, 0xa8, 0x3b, 0x85, 0x63, 0x35, 0xd4, 0x4d, 0x5c, 0x46, 0x99, 0x01, 0x01, 0x64, 0x01, 0xe8, 0xbd, 0x56, 0x83, 0x55, 0xaa, 0x61, 0x62, 0x93, 0x3a, 0x15, 0x4c, 0x22, 0x92, 0x24, 0xc6, 0xba, 0x14, 0x3a, 0xed, 0x59, 0x4b, 0x0d, 0x50, 0x61, 0xaa, 0x99, + 0x69, 0x6e, 0x6a, 0x8c, 0x37, 0xd4, 0xd7, 0x05, 0x08, 0xf1, 0xcc, 0xd1, 0x80, 0x85, 0xd8, 0xbf, 0x4c, 0xd1, 0x8f, 0xf0, 0x16, 0xab, 0xa8, 0x63, 0x89, 0x14, 0x2a, 0x83, 0x1e, 0x9f, 0x86, 0xa0, 0x28, 0x41, 0x4f, 0x5f, 0x92, 0xa6, 0x65, 0x2b, 0x0f, 0xf6, 0x37, 0xd7, 0xf2, 0x48, 0x67, 0xf5, 0x18, 0x0b, 0xbb, 0xca, 0xb3, 0xd1, 0xb3, 0xb7, 0x5c, 0x6d, 0x0b, 0x1b, 0x9c, 0x59, 0xad, 0x84, 0xca, 0xd3, 0x03, 0xc3, 0x48, 0xab, 0xbe, 0x2f, 0x75, 0x26, 0xc8, 0xdc, 0xb3, 0xf4, 0x8a, 0x55, 0xa5, 0xa6, 0x21, 0x8d, 0x56, 0x40, 0x96, 0xf2, 0xc5, 0x0d, 0x6d, 0xe9, 0xc8, 0x4e, 0x22, 0xc7, 0x10, 0xaa, 0xb3, 0xcb, 0x09, 0xe5, 0x09, 0xff, 0xbc, 0x10, 0xf4, 0x90, 0xbb, 0xf9, 0xcb, 0xe8, 0x39, 0x52, 0xb9, 0x8c, 0xc6, 0xa4, 0x9d, 0x3a, 0x3e, 0x1a, 0xa2, 0x10, 0xeb, 0x2c, 0x55, 0x91, 0x96, + 0xb2, 0x14, 0x5b, 0x0a, 0xcb, 0x8a, 0xfd, 0x69, 0x11, 0xd6, 0x4f, 0x39, 0xff, 0xda, 0x70, 0xca, 0xd9, 0x57, 0xfd, 0x29, 0x47, 0x76, 0xaa, 0xb3, 0x22, 0x47, 0x91, 0x9b, 0x09, 0x15, 0xe8, 0x28, 0x56, 0xfe, 0xbb, 0xd0, 0xd6, 0x69, 0xb1, 0xe5, 0x61, 0xb3, 0x4b, 0xbf, 0xa3, 0x60, 0xeb, 0xec, 0xdd, 0x84, 0xaf, 0x9c, 0xb5, 0x7f, 0x81, 0xfe, 0xf4, 0x6d, 0xe9, 0x46, 0x5c, 0xc2, 0xdf, 0xc6, 0x04, 0x99, 0xf5, 0x93, 0x44, 0xc2, 0x78, 0x68, 0x72, 0x98, 0xf0, 0xc3, 0xc9, 0xff, 0x96, 0x6e, 0x65, 0xcb, 0xc8, 0x73, 0xfc, 0x1e, 0x45, 0xba, 0x7c, 0xc8, 0x20, 0xf3, 0xfc, 0x47, 0x26, 0xff, 0xc2, 0xc7, 0xe1, 0x79, 0x11, 0x73, 0x54, 0xd6, 0x67, 0x55, 0xf9, 0x88, 0x17, 0xb5, 0xd4, 0x36, 0xc2, 0x91, 0xfc, 0x02, 0x33, 0x87, 0xcc, 0x4b, 0x92, 0x5c, 0x40, 0x40, 0x8e, 0x79, 0x86, 0x18, 0x58, + 0x11, 0x57, 0xdb, 0xad, 0x0c, 0x81, 0x0f, 0x67, 0xb7, 0x12, 0xa7, 0x80, 0x01, 0x98, 0x4d, 0x43, 0x14, 0x16, 0x71, 0x90, 0xc0, 0x76, 0x11, 0xe4, 0xe4, 0x78, 0xc9, 0x59, 0xb3, 0x0b, 0xc2, 0xb0, 0x5c, 0x46, 0x06, 0x5b, 0x06, 0xb9, 0x44, 0x4f, 0x02, 0x45, 0x99, 0xc3, 0x99, 0xc4, 0x45, 0x36, 0x11, 0x33, 0x3d, 0x35, 0xb1, 0xa6, 0x6e, 0xab, 0x48, 0x8c, 0x82, 0x98, 0x2c, 0x78, 0x12, 0xb3, 0x46, 0x76, 0xe3, 0xde, 0x47, 0xb7, 0x47, 0x97, 0xdf, 0xf2, 0xd2, 0xf6, 0x8e, 0x03, 0xf5, 0x3e, 0xde, 0x6e, 0xd4, 0xe4, 0x14, 0xc4, 0xfc, 0x8b, 0x2e, 0x5d, 0x56, 0x18, 0x5c, 0x7c, 0xe5, 0xd0, 0x17, 0x73, 0xd0, 0xe0, 0xc4, 0xed, 0xaf, 0x71, 0x6d, 0x5a, 0x13, 0xbb, 0x9c, 0x6f, 0x58, 0x7f, 0xb8, 0x6f, 0xc7, 0xa3, 0x17, 0xc5, 0xb3, 0xb3, 0xf7, 0x98, 0x74, 0x4a, 0x8d, 0xb2, 0x60, 0xde, 0xa6, + 0x86, 0xd8, 0xf2, 0xb8, 0x7f, 0x10, 0xb4, 0x99, 0xeb, 0x3e, 0xd7, 0xf3, 0xb7, 0xcd, 0x37, 0x84, 0x2c, 0x30, 0x97, 0x56, 0x33, 0x0c, 0xf7, 0x0d, 0xa0, 0x8d, 0x48, 0x6e, 0x1b, 0x08, 0x50, 0x9e, 0x8c, 0x07, 0xbc, 0x86, 0x1e, 0x37, 0x92, 0x18, 0x2e, 0x43, 0x09, 0xd0, 0x60, 0x85, 0x42, 0xa1, 0x56, 0xa8, 0x49, 0x50, 0xed, 0x4c, 0x12, 0xc4, 0x86, 0xe8, 0x1d, 0xa4, 0x5d, 0x44, 0xdb, 0x5e, 0xcd, 0xb5, 0x4b, 0x1d, 0xaf, 0x49, 0x6d, 0x5c, 0xfb, 0xe7, 0x4f, 0x72, 0xf3, 0x1e, 0x39, 0x39, 0xcc, 0xde, 0x86, 0x99, 0x47, 0x1e, 0x61, 0xe4, 0x1d, 0x8d, 0xe1, 0xdc, 0x50, 0xbf, 0x8f, 0x78, 0x40, 0xaa, 0x10, 0xc7, 0xa3, 0x2e, 0x10, 0xb3, 0x68, 0x5c, 0x6c, 0xbc, 0x26, 0x79, 0x7b, 0x41, 0x62, 0x1e, 0x29, 0x07, 0x18, 0xa5, 0x72, 0x28, 0x11, 0xee, 0xc8, 0xe7, 0xf5, 0xea, 0x0d, 0x1e, 0x7f, + 0x50, 0x6f, 0xcc, 0xa4, 0x01, 0x5f, 0x3c, 0x2e, 0x64, 0x8d, 0x98, 0x7d, 0x3a, 0x44, 0xc2, 0x18, 0xf8, 0xbc, 0x21, 0x44, 0x82, 0x1b, 0x10, 0x73, 0xc2, 0x18, 0x6c, 0xd3, 0xf4, 0x77, 0x0f, 0xe7, 0x96, 0x6e, 0x6c, 0x68, 0x51, 0xff, 0xf6, 0x77, 0xe6, 0x5b, 0xf7, 0x95, 0x78, 0xd0, 0xd1, 0xef, 0x54, 0x66, 0x4b, 0xb7, 0xeb, 0xc3, 0xb9, 0x0f, 0x3c, 0x77, 0xe8, 0xab, 0x59, 0x13, 0x12, 0x16, 0xcd, 0x5f, 0x39, 0xfc, 0x02, 0x7b, 0x3f, 0xcf, 0xfa, 0xd7, 0x35, 0xdc, 0xbc, 0xe1, 0xb6, 0x80, 0xbd, 0x74, 0xe2, 0x67, 0x85, 0xe8, 0xaf, 0x76, 0xf3, 0x0d, 0x9b, 0x97, 0xde, 0xbc, 0x72, 0x5d, 0x02, 0x9f, 0x98, 0x5b, 0x08, 0x6d, 0xcd, 0x27, 0xde, 0x2f, 0xb9, 0x01, 0xbf, 0x28, 0xf0, 0x24, 0x22, 0x3a, 0xeb, 0xcb, 0x71, 0xa9, 0x39, 0xf9, 0x56, 0x8a, 0xe0, 0x25, 0x33, 0x63, 0x04, 0x79, 0x89, + 0x1c, 0xe8, 0xe0, 0x41, 0x99, 0x36, 0xce, 0x40, 0xb0, 0x80, 0xae, 0xab, 0x80, 0xd9, 0x24, 0xf2, 0x66, 0xfa, 0xbf, 0x5e, 0x1e, 0xc4, 0x8a, 0x04, 0x2e, 0x95, 0x47, 0x2f, 0x46, 0xcb, 0x1b, 0x50, 0x94, 0xfe, 0xaf, 0x8f, 0xa0, 0x3f, 0xa0, 0xfa, 0x6c, 0xe9, 0x2d, 0xfa, 0xdf, 0xba, 0xde, 0xbd, 0x03, 0xa5, 0x6e, 0xd3, 0x81, 0xd6, 0xcb, 0xbf, 0xbf, 0x57, 0x7a, 0xe1, 0x49, 0xc7, 0xed, 0xf0, 0xef, 0x35, 0xf6, 0xbe, 0xbb, 0x8e, 0x78, 0xb5, 0x37, 0x5f, 0x7e, 0xd7, 0xf5, 0x3e, 0xf5, 0x2d, 0xd2, 0xdf, 0xf3, 0x16, 0xec, 0xe8, 0x34, 0x65, 0x95, 0x5f, 0xf0, 0xd8, 0x45, 0x71, 0x74, 0xde, 0x79, 0x37, 0x38, 0xb5, 0x0b, 0xaf, 0x38, 0xef, 0x06, 0x97, 0x7a, 0x21, 0x5e, 0xc8, 0xa0, 0xc9, 0xa7, 0xa4, 0x5b, 0xd1, 0x66, 0xd0, 0x04, 0x2c, 0xcc, 0x4a, 0x39, 0x1e, 0x92, 0x86, 0x1e, 0x6b, 0x3a, + 0xc8, 0x27, 0x47, 0xe3, 0x21, 0x79, 0x29, 0x14, 0x60, 0xe2, 0xd8, 0x66, 0xb8, 0x3b, 0x15, 0xba, 0x87, 0x86, 0x82, 0x71, 0xca, 0x09, 0x2c, 0xc1, 0xf4, 0xe6, 0xc6, 0x68, 0x86, 0x24, 0xca, 0x39, 0x89, 0xf7, 0x72, 0xdc, 0x13, 0x08, 0xe8, 0x33, 0x09, 0xce, 0xf5, 0xff, 0xc7, 0xde, 0x9b, 0xc0, 0x47, 0x55, 0x9d, 0xfd, 0xe3, 0xe7, 0xdc, 0x75, 0x96, 0x64, 0x26, 0x93, 0x49, 0x32, 0x49, 0x26, 0xdb, 0x64, 0x92, 0xc9, 0x9e, 0x90, 0x3d, 0x21, 0x09, 0x19, 0x02, 0x04, 0x48, 0xd8, 0x11, 0x30, 0x82, 0x22, 0xab, 0x68, 0x58, 0x02, 0x58, 0x15, 0xf1, 0x15, 0x11, 0x54, 0x40, 0xc4, 0x05, 0xb5, 0x0a, 0xa8, 0xb8, 0xe0, 0x06, 0x28, 0x88, 0xa8, 0x88, 0x5a, 0xb6, 0x57, 0x5b, 0xb1, 0xb5, 0xb6, 0x62, 0xad, 0xb5, 0xad, 0x5a, 0x6b, 0xdf, 0xd6, 0xda, 0xaa, 0xd5, 0xba, 0x90, 0xb9, 0xf3, 0x3f, 0xcf, + 0x39, 0xf7, 0xde, 0xb9, 0x77, 0x32, 0x09, 0x8b, 0xf4, 0x7d, 0x3f, 0xbf, 0xcf, 0xe7, 0x6f, 0x3f, 0x55, 0xf8, 0xce, 0xd9, 0xee, 0x39, 0xcf, 0x79, 0xce, 0xf7, 0x39, 0xcb, 0xf3, 0x54, 0xfb, 0xeb, 0xab, 0xfb, 0xc4, 0x2e, 0xaa, 0x3d, 0xdc, 0x39, 0x61, 0x47, 0x74, 0xe0, 0xa2, 0x3b, 0x5f, 0x84, 0xb0, 0x45, 0x89, 0x6e, 0x1a, 0xb6, 0x68, 0x2c, 0xc8, 0xc1, 0x15, 0xca, 0x46, 0x6e, 0x3e, 0x6d, 0xe3, 0xf8, 0xfd, 0x0e, 0x70, 0x19, 0xd0, 0xd1, 0xa7, 0x55, 0x40, 0xc3, 0xb4, 0x35, 0xce, 0xd0, 0x2a, 0xb6, 0x53, 0x49, 0x5b, 0x15, 0xf9, 0xbd, 0xeb, 0xf9, 0x7c, 0x9f, 0xdf, 0xd8, 0xaa, 0xe8, 0xc0, 0x26, 0xfb, 0x5b, 0x27, 0xdc, 0x3b, 0x28, 0x98, 0x53, 0xee, 0xb0, 0xa7, 0xcb, 0x5a, 0x9c, 0x92, 0x3b, 0xdf, 0x65, 0xb1, 0x47, 0x92, 0x5c, 0x91, 0xd8, 0x23, 0x57, 0x28, 0x5b, 0xb9, 0x95, 0xa7, + 0xd3, 0x77, 0xcc, 0x16, 0xe8, 0xdb, 0x77, 0x5a, 0xb7, 0xe9, 0x09, 0x62, 0xf7, 0x5d, 0x11, 0xae, 0xf7, 0xf5, 0xed, 0x3b, 0xfc, 0x96, 0xb2, 0x63, 0x02, 0xde, 0x7f, 0xaa, 0xee, 0x23, 0x63, 0xfc, 0x3a, 0x19, 0xe3, 0xff, 0x8d, 0x76, 0x56, 0xfb, 0x6b, 0x63, 0xb4, 0xf3, 0xad, 0x79, 0xd8, 0x79, 0x8a, 0x46, 0x12, 0xae, 0x43, 0x38, 0x81, 0xf0, 0x96, 0x78, 0x90, 0x0b, 0x88, 0x3f, 0xa1, 0x96, 0xdf, 0x53, 0x9c, 0x48, 0x79, 0x6a, 0x05, 0x51, 0x90, 0xdd, 0xe2, 0xab, 0x84, 0x3f, 0x75, 0x51, 0xfc, 0x19, 0x7e, 0x2e, 0xc3, 0x95, 0x95, 0x42, 0x9a, 0xf8, 0x82, 0x8e, 0xff, 0x37, 0xe7, 0xa3, 0x78, 0xb9, 0xb2, 0x52, 0x9c, 0x2c, 0xee, 0x23, 0xf8, 0x74, 0x15, 0xdf, 0xc5, 0xf0, 0xf0, 0xe7, 0xe2, 0xab, 0xb4, 0x9c, 0xe9, 0x6a, 0x39, 0x3f, 0xa1, 0x7a, 0xff, 0x24, 0x99, 0xb7, 0xb3, 0xc5, 0xfb, + 0x51, 0x01, 0xde, 0x83, 0x7f, 0x4f, 0xa3, 0x78, 0x3e, 0x16, 0xde, 0x4a, 0x63, 0x7d, 0x62, 0x53, 0xac, 0xcf, 0x69, 0xe1, 0x6f, 0xc5, 0x0b, 0x58, 0x3a, 0xae, 0x81, 0x40, 0x05, 0x24, 0xdd, 0x37, 0xdc, 0x21, 0x92, 0xfe, 0x09, 0x3e, 0x89, 0xee, 0xd2, 0xf4, 0x4d, 0xdf, 0x10, 0x33, 0xfd, 0x4e, 0xee, 0x64, 0x3f, 0xe9, 0xbb, 0x62, 0xa6, 0xdf, 0xc5, 0x8b, 0xfd, 0xa4, 0x9f, 0x10, 0x33, 0xfd, 0x53, 0x40, 0xfd, 0xfa, 0xa4, 0x9f, 0x42, 0xd2, 0xb7, 0x8b, 0x8f, 0xd0, 0xf4, 0x57, 0xd0, 0xf4, 0x4f, 0xa0, 0x24, 0xd6, 0x1e, 0xde, 0xda, 0x4f, 0xfa, 0x0b, 0x63, 0xa6, 0x7f, 0x8a, 0x77, 0xc4, 0x48, 0x3f, 0x9c, 0xa4, 0x5f, 0x4a, 0xf4, 0x1f, 0xa4, 0x5f, 0x4f, 0xd3, 0x3f, 0x89, 0x0a, 0xd4, 0xf4, 0xb9, 0x2c, 0xbd, 0x29, 0x76, 0xec, 0x54, 0x92, 0xfe, 0x7a, 0x35, 0xfd, 0x2c, 0x9a, 0x7e, 0x27, + 0x92, 0xd5, 0xef, 0x2d, 0x8f, 0x51, 0x3e, 0xa4, 0x5f, 0x11, 0x33, 0xfd, 0x53, 0x7c, 0x71, 0x8c, 0xf4, 0x33, 0x48, 0x7a, 0x81, 0xa5, 0xc7, 0x9f, 0xd1, 0xf4, 0xbb, 0x91, 0x8d, 0xa5, 0xe7, 0xf6, 0x47, 0xa7, 0x27, 0x7a, 0xa2, 0x4b, 0x79, 0x4c, 0xfc, 0x94, 0x70, 0x6d, 0x37, 0x0a, 0xa2, 0x6b, 0xb5, 0x30, 0x22, 0xea, 0xdb, 0xfa, 0x6e, 0x7d, 0x03, 0x9e, 0x46, 0x17, 0xa3, 0x92, 0xbf, 0x80, 0xfa, 0xfa, 0x8b, 0x8b, 0x9d, 0x6c, 0xa1, 0x29, 0xd9, 0x29, 0x0b, 0x82, 0x60, 0x10, 0x83, 0x1b, 0x0a, 0x93, 0x03, 0x70, 0x2c, 0x07, 0x21, 0x01, 0xa2, 0xe2, 0x76, 0x6a, 0xbe, 0xe8, 0xea, 0xd9, 0x55, 0xa6, 0x3a, 0x70, 0x10, 0x91, 0x92, 0x48, 0x6f, 0x3d, 0x41, 0x04, 0xa2, 0x14, 0x16, 0xa2, 0xc9, 0x83, 0xab, 0xda, 0xaf, 0x7f, 0x7e, 0xe9, 0xd2, 0xe7, 0x57, 0xb7, 0xb7, 0xaf, 0x86, 0xff, 0x5e, + 0xdf, 0xce, 0x1d, 0xcf, 0xb9, 0xb0, 0x76, 0xfa, 0xbc, 0x61, 0x75, 0xf7, 0x9e, 0x7c, 0x76, 0xce, 0x82, 0x83, 0x58, 0x7e, 0x60, 0x3b, 0x96, 0x5e, 0xba, 0xe4, 0xfc, 0xed, 0x1f, 0xdd, 0xec, 0xce, 0xcf, 0x4a, 0xac, 0x28, 0x48, 0xc9, 0x48, 0x8a, 0x13, 0xe3, 0x65, 0xee, 0x17, 0xab, 0x8f, 0x6f, 0xea, 0xec, 0xdc, 0x74, 0x7c, 0xf5, 0x75, 0x6f, 0x6c, 0x1a, 0x33, 0x66, 0xd3, 0x1b, 0x27, 0x37, 0x7a, 0x32, 0xc6, 0xb7, 0xd5, 0xce, 0x0c, 0x88, 0x3d, 0x87, 0xb1, 0xf5, 0xbe, 0xed, 0xd8, 0x71, 0x74, 0xd1, 0xa2, 0xa3, 0xca, 0x97, 0xdb, 0xef, 0xec, 0x7d, 0x65, 0x45, 0x82, 0x3d, 0x2b, 0xbf, 0xd8, 0xe3, 0x2f, 0x96, 0x65, 0x39, 0x9e, 0xce, 0xa1, 0x5d, 0xe1, 0xaf, 0xb8, 0x6c, 0xc2, 0x29, 0x03, 0xe8, 0x77, 0x74, 0x6e, 0xed, 0x0f, 0x9f, 0x47, 0xf5, 0x63, 0x0e, 0x99, 0x5b, 0x98, 0xfa, 0x84, + 0x6c, 0x0e, 0x36, 0x4a, 0x58, 0x80, 0xf7, 0x01, 0x02, 0x3f, 0xcb, 0x82, 0x39, 0x2b, 0x21, 0x77, 0xe0, 0x70, 0x04, 0x28, 0x82, 0x1d, 0xdb, 0x6c, 0x86, 0x3d, 0xa7, 0x64, 0x04, 0xb1, 0x89, 0xb4, 0x7f, 0xe2, 0xc8, 0x0a, 0x0e, 0x9b, 0x57, 0xfe, 0xda, 0xea, 0x5a, 0x35, 0xb2, 0x5f, 0xb2, 0x3f, 0x99, 0xfc, 0xdf, 0x25, 0xe0, 0xde, 0xd7, 0x77, 0x92, 0x7f, 0x4e, 0xbc, 0x49, 0xfe, 0xd9, 0xb7, 0x0f, 0x1f, 0xdb, 0xb2, 0x7c, 0x0b, 0x57, 0x19, 0x7a, 0x93, 0xfc, 0x87, 0xda, 0xb8, 0x08, 0xde, 0x55, 0x21, 0x41, 0x22, 0xf5, 0x67, 0xa2, 0xa1, 0xc1, 0x21, 0x4e, 0xb2, 0x70, 0x64, 0xf0, 0x70, 0x77, 0xb0, 0x83, 0xd0, 0x07, 0x35, 0xb6, 0x96, 0x40, 0x88, 0x98, 0x2c, 0xd0, 0x78, 0x9e, 0x73, 0xa8, 0xb1, 0x64, 0x68, 0x48, 0x26, 0xca, 0xcc, 0x4b, 0xcc, 0x75, 0x25, 0x25, 0xb9, 0xc0, 0x4e, 0x82, + 0x46, 0x10, 0x0a, 0x01, 0x5b, 0xbd, 0xf5, 0xa4, 0x15, 0x34, 0xda, 0x2e, 0xdb, 0x03, 0x2e, 0x70, 0x09, 0x52, 0xef, 0xeb, 0x99, 0xd9, 0x78, 0x63, 0x56, 0x6d, 0x47, 0xe9, 0x89, 0x9c, 0xb4, 0x82, 0x12, 0xbc, 0x21, 0xbb, 0x61, 0xdc, 0xa0, 0x1c, 0xdf, 0xbe, 0x7d, 0xdc, 0x3b, 0x62, 0x71, 0xa0, 0x72, 0x62, 0xb3, 0x9f, 0xe7, 0xea, 0x43, 0xd7, 0x26, 0x14, 0x95, 0x55, 0x9f, 0xd7, 0xec, 0xb3, 0x2b, 0xa5, 0x54, 0xe6, 0xc6, 0x2b, 0x8f, 0x09, 0x3e, 0xc1, 0x42, 0xd8, 0x53, 0x03, 0xda, 0xa0, 0x9e, 0xc1, 0x69, 0xa2, 0xb2, 0xd8, 0x20, 0x4c, 0x40, 0xa5, 0x0c, 0x42, 0x97, 0x1a, 0x3b, 0xdd, 0x12, 0x73, 0xba, 0x60, 0x01, 0x92, 0x78, 0x91, 0x87, 0x70, 0x79, 0x31, 0x92, 0x9a, 0xa5, 0x2f, 0xae, 0xbe, 0xb6, 0x6a, 0x50, 0x61, 0x72, 0x5e, 0x5e, 0xae, 0xac, 0x85, 0xa4, 0x50, 0xf7, 0x46, 0x0d, + 0xb2, 0xc7, 0x9b, 0x82, 0x52, 0x98, 0x04, 0x2f, 0xf5, 0xd6, 0x3b, 0x6e, 0xdf, 0xb4, 0xe9, 0xf6, 0x3b, 0x6e, 0xc5, 0x5f, 0x65, 0x5f, 0x54, 0x43, 0x04, 0xae, 0xe1, 0xd2, 0xc7, 0xaf, 0x6c, 0x1d, 0xb1, 0x6a, 0xff, 0xd2, 0x9e, 0xfd, 0xab, 0x86, 0xd7, 0xcd, 0xbb, 0x7d, 0x7a, 0x52, 0x20, 0xc7, 0x4d, 0xc4, 0x2d, 0xd3, 0x1d, 0x27, 0x3a, 0x45, 0xdc, 0xbb, 0xe7, 0xc8, 0x91, 0x3d, 0x7b, 0x8e, 0x1e, 0xed, 0x2d, 0x07, 0x31, 0xab, 0x9b, 0x19, 0x90, 0xc6, 0xdd, 0x74, 0xf0, 0xf2, 0xab, 0x0e, 0xaf, 0x1f, 0x3d, 0x7a, 0xfd, 0xe1, 0xab, 0xba, 0xf7, 0xae, 0x3d, 0xcf, 0x69, 0xcf, 0xce, 0x2f, 0x49, 0xd5, 0x64, 0x0c, 0x7c, 0x92, 0x2a, 0x5f, 0x73, 0xcf, 0x84, 0x2f, 0x1f, 0xe8, 0x7e, 0x9a, 0x47, 0xf3, 0x49, 0xba, 0x51, 0xdd, 0xc3, 0xfe, 0x44, 0x79, 0x90, 0xbb, 0x21, 0x7c, 0x98, 0xed, 0x95, + 0xe8, 0x7e, 0x4b, 0x16, 0x80, 0xdf, 0x12, 0x76, 0x54, 0x01, 0x7e, 0x4b, 0x44, 0xfd, 0x0a, 0x07, 0x7e, 0x40, 0xbd, 0xbe, 0xe1, 0xfc, 0xac, 0x55, 0xbd, 0xb8, 0xc1, 0xca, 0x39, 0xa6, 0x3c, 0xc4, 0x2d, 0x08, 0xc3, 0xfd, 0x10, 0xe6, 0x13, 0x9f, 0x91, 0x8e, 0x05, 0x54, 0x4a, 0xe0, 0xca, 0x09, 0x9c, 0x1c, 0x89, 0xfa, 0x95, 0x13, 0x6e, 0x81, 0x7e, 0xdd, 0x64, 0x88, 0x7a, 0xcf, 0x04, 0xec, 0x08, 0xa5, 0x03, 0xb7, 0xd2, 0x93, 0x26, 0xd5, 0x96, 0xe9, 0x62, 0x5e, 0x7e, 0x58, 0xd3, 0x9b, 0xb0, 0xcb, 0xff, 0xcc, 0x2d, 0xb7, 0x28, 0x1d, 0x54, 0x77, 0xed, 0x55, 0xe2, 0xc4, 0xbb, 0xc5, 0x6d, 0x64, 0xad, 0x82, 0xb0, 0x12, 0x32, 0x27, 0xa0, 0xdf, 0x7c, 0x77, 0x90, 0x68, 0x2c, 0x0e, 0xbf, 0xf0, 0xdd, 0x41, 0x75, 0x6f, 0x60, 0x92, 0x12, 0xc7, 0xbf, 0x2f, 0x1e, 0x23, 0x69, 0xea, 0xd5, + 0x34, 0x37, 0xf5, 0x49, 0x33, 0x57, 0x79, 0x9d, 0x3b, 0x10, 0x7e, 0x8a, 0x97, 0xd1, 0xbe, 0x70, 0xef, 0xf7, 0xd9, 0xec, 0x24, 0xe1, 0xfb, 0x6c, 0x4d, 0xe7, 0x85, 0xef, 0x0e, 0x7f, 0x29, 0x24, 0x8a, 0xc7, 0x50, 0x1c, 0xb1, 0x6d, 0xbf, 0x50, 0x03, 0x56, 0xd6, 0x61, 0xab, 0x9c, 0x87, 0x25, 0x31, 0x3e, 0x0e, 0x6e, 0xc5, 0x43, 0x28, 0x31, 0xa1, 0x63, 0x18, 0xe1, 0xdc, 0xa3, 0xbd, 0x7d, 0x7f, 0x13, 0xb4, 0xdf, 0xd4, 0x48, 0x90, 0x65, 0x70, 0x55, 0x57, 0x94, 0x64, 0x71, 0x0e, 0xb2, 0xda, 0xb1, 0xc4, 0x59, 0x25, 0xf0, 0x07, 0x37, 0xaf, 0x53, 0xf5, 0x91, 0xcd, 0xa9, 0x02, 0x08, 0xec, 0x82, 0x45, 0xa2, 0xca, 0x3d, 0xb3, 0x4c, 0x34, 0x3a, 0x55, 0xe5, 0xa9, 0xd3, 0x0b, 0x42, 0xe4, 0xc8, 0x94, 0x45, 0xad, 0x4a, 0x6c, 0x0b, 0x36, 0x37, 0x35, 0xd6, 0x27, 0xd6, 0x24, 0x26, 0x05, + 0x0a, 0xf3, 0xf3, 0x72, 0xa9, 0x5a, 0xc9, 0xc2, 0xba, 0x98, 0xcb, 0xb5, 0x51, 0xb1, 0x77, 0x64, 0xa9, 0x04, 0xbb, 0x68, 0xa8, 0x48, 0xf5, 0x74, 0x27, 0xc5, 0x53, 0x5b, 0x9d, 0x6c, 0x9c, 0xf5, 0xb8, 0x73, 0x50, 0x4f, 0xdb, 0xba, 0xcd, 0xd7, 0x5f, 0x79, 0xde, 0xca, 0xda, 0xd6, 0xed, 0xf3, 0xbb, 0x6e, 0x9d, 0x5b, 0xd7, 0xb2, 0xf4, 0xa1, 0x79, 0x95, 0x93, 0x46, 0x0c, 0x4e, 0xb7, 0x5a, 0x8a, 0xaf, 0x15, 0x6d, 0x62, 0xa0, 0x04, 0x6f, 0xcc, 0x69, 0x1c, 0x57, 0x61, 0x89, 0x97, 0x8f, 0x26, 0x26, 0x68, 0x6a, 0xc1, 0xed, 0xf9, 0xbe, 0xb5, 0xea, 0xc8, 0xb1, 0xfd, 0x4f, 0x8c, 0x69, 0xbf, 0xbd, 0x7d, 0xea, 0x88, 0xd5, 0x2f, 0x5c, 0xbe, 0xf2, 0xf5, 0xdb, 0xc6, 0xbb, 0xf3, 0x6a, 0x7c, 0x53, 0x8a, 0x84, 0xa7, 0x30, 0x2e, 0x29, 0x05, 0x1d, 0x81, 0xf1, 0xfa, 0xf7, 0x25, 0xa6, 0x2e, + 0x44, 0x1a, 0xb3, 0x93, 0x8c, 0xd5, 0x9b, 0x64, 0xac, 0x1c, 0xa8, 0x0d, 0x6d, 0x0d, 0x26, 0xb7, 0x0c, 0xe6, 0x6c, 0x96, 0x32, 0xcc, 0x49, 0xa5, 0x18, 0x89, 0x19, 0x34, 0x30, 0xa7, 0xa0, 0xb2, 0xe2, 0xf2, 0x38, 0x3b, 0x67, 0x41, 0x12, 0x67, 0x21, 0x1d, 0x63, 0x93, 0x89, 0xb1, 0x64, 0xe3, 0xe0, 0x7d, 0xc8, 0x3c, 0xe8, 0x12, 0xea, 0x5d, 0x85, 0x06, 0xcb, 0x64, 0x3d, 0x4a, 0x03, 0x91, 0x56, 0x9d, 0x46, 0x06, 0x88, 0x6f, 0xaa, 0xe9, 0x11, 0x08, 0x7d, 0x15, 0x4c, 0x71, 0x3a, 0x9d, 0x6d, 0xce, 0xb6, 0x60, 0x6b, 0x73, 0x53, 0x5e, 0x62, 0x5e, 0x61, 0x5e, 0x6e, 0x7d, 0x20, 0x37, 0xbe, 0x4f, 0x7c, 0x4d, 0xac, 0xb2, 0x41, 0x97, 0x3f, 0xb7, 0x02, 0x9b, 0xc3, 0x68, 0xba, 0x02, 0x91, 0xce, 0xd5, 0xfb, 0xd6, 0x1c, 0x5b, 0x13, 0x3f, 0x0f, 0xfd, 0x65, 0x71, 0x58, 0xae, 0x28, 0xb2, + 0xb9, 0xcc, 0x71, 0x34, 0x13, 0xe3, 0xb5, 0xce, 0x4d, 0x71, 0x1d, 0xb3, 0xc5, 0x91, 0xbe, 0x35, 0x46, 0xd6, 0x14, 0x52, 0x59, 0x17, 0x9e, 0x7c, 0xb2, 0xe0, 0x47, 0xc6, 0x38, 0x9a, 0xd8, 0x2d, 0xb2, 0xde, 0x15, 0x6e, 0xfe, 0x23, 0x5f, 0x54, 0x46, 0xe7, 0x1a, 0x91, 0x93, 0xbf, 0xd0, 0xfd, 0x80, 0x64, 0x1a, 0xaf, 0xf9, 0x30, 0xbe, 0x33, 0xf4, 0x1b, 0xc6, 0x0e, 0x42, 0xbf, 0x51, 0xe7, 0xd1, 0xb5, 0xe1, 0xcf, 0xf9, 0x77, 0xc4, 0x5b, 0xc9, 0x5c, 0xbb, 0x91, 0xae, 0x6f, 0xfb, 0xd0, 0x76, 0xaa, 0x17, 0x2a, 0x95, 0x47, 0xf9, 0x41, 0x44, 0x7f, 0x77, 0xa1, 0x47, 0x83, 0xe9, 0xf5, 0x58, 0xb2, 0x0c, 0x27, 0x36, 0xaa, 0x9b, 0xb4, 0x64, 0x0c, 0x06, 0xbf, 0xbf, 0x08, 0x4f, 0xc3, 0x56, 0x24, 0xa9, 0x4e, 0x68, 0xeb, 0x11, 0x31, 0x09, 0x25, 0x78, 0xa8, 0x40, 0xd6, 0x40, 0x81, 0xe3, + 0x7b, 0xc0, 0x80, 0x25, 0x36, 0xff, 0x1c, 0x62, 0xe1, 0xd3, 0xd7, 0xaa, 0xb9, 0x9d, 0x5a, 0x48, 0x47, 0x9f, 0x1e, 0xd7, 0x34, 0x93, 0x5e, 0x86, 0x3f, 0xfd, 0x9c, 0x7e, 0x63, 0xce, 0xae, 0xa0, 0x63, 0xca, 0xe4, 0x7c, 0x7f, 0xc0, 0x17, 0xf0, 0x07, 0x46, 0xe4, 0xd9, 0xa4, 0xcc, 0xc8, 0xa5, 0x81, 0x40, 0x81, 0x94, 0xab, 0xfb, 0x52, 0xd4, 0xdf, 0x2e, 0x30, 0xb3, 0x58, 0xa3, 0x1d, 0xf4, 0xd0, 0x36, 0x32, 0x2d, 0xd4, 0x50, 0x4c, 0x7a, 0x44, 0x26, 0x7a, 0xd5, 0x60, 0xa8, 0x7a, 0xd5, 0x20, 0xab, 0x40, 0xb2, 0x8b, 0x83, 0xe6, 0xcd, 0x3e, 0xdf, 0xdf, 0x10, 0x9f, 0xea, 0x2e, 0x6a, 0x9a, 0x3c, 0xa4, 0x76, 0x72, 0x63, 0xd6, 0x15, 0x2b, 0x97, 0x2f, 0x5f, 0x78, 0x55, 0x7a, 0xed, 0xf8, 0x9a, 0xc1, 0x13, 0x1a, 0xf2, 0xe3, 0x1d, 0x6e, 0x7b, 0x59, 0xe3, 0xa3, 0x97, 0x5f, 0xb8, 0x69, + 0x56, 0xa5, 0xbb, 0x78, 0x44, 0xf5, 0x45, 0x8b, 0x4b, 0xc7, 0x2e, 0x68, 0x6e, 0x18, 0xa2, 0xdf, 0x4e, 0x78, 0x8b, 0xde, 0x4e, 0xf0, 0x37, 0x3b, 0x70, 0x62, 0xa0, 0xb9, 0x64, 0x54, 0x62, 0x76, 0x5e, 0x76, 0x7a, 0xf9, 0x90, 0xfc, 0x51, 0x53, 0xaf, 0x9f, 0x18, 0xcc, 0xa8, 0x2e, 0x48, 0xf1, 0x64, 0x78, 0xec, 0xae, 0xce, 0xba, 0xe1, 0x65, 0x9d, 0x17, 0xd7, 0x64, 0x0d, 0x6d, 0xae, 0x76, 0x65, 0x5e, 0x7b, 0xd1, 0xe0, 0x59, 0x9d, 0x35, 0x71, 0x69, 0x93, 0xab, 0xf5, 0xab, 0x0c, 0xf4, 0xc8, 0x81, 0x8c, 0xcb, 0x6d, 0x64, 0x5c, 0x86, 0xa3, 0xf5, 0xc1, 0xc4, 0x62, 0x2c, 0x92, 0xce, 0xe1, 0x24, 0x08, 0x8f, 0xe7, 0x80, 0xa0, 0x1f, 0xea, 0x78, 0x94, 0x23, 0x41, 0x94, 0x45, 0x41, 0xee, 0x81, 0xde, 0x95, 0x25, 0xd8, 0x72, 0xa1, 0x0a, 0x23, 0x17, 0xb6, 0x5c, 0xa8, 0x26, 0xf1, 0x75, + 0x6a, 0x97, 0x65, 0x33, 0x11, 0x0d, 0xf0, 0x7e, 0xaa, 0x0c, 0x7e, 0x63, 0x86, 0xae, 0xa0, 0xdd, 0x5f, 0xe8, 0xcf, 0x2f, 0xcc, 0x2f, 0x2a, 0xb6, 0x48, 0x19, 0x10, 0x73, 0xd3, 0xd4, 0x75, 0xd5, 0x91, 0xa1, 0x30, 0xce, 0x0a, 0x53, 0x14, 0x4e, 0x2d, 0xf8, 0xa6, 0x87, 0xbf, 0xad, 0xf6, 0x96, 0x09, 0x75, 0x83, 0xf5, 0x9e, 0x72, 0x6a, 0xdd, 0x1e, 0x88, 0x9f, 0xb9, 0x63, 0x45, 0x7b, 0xeb, 0xca, 0x67, 0x2f, 0x5f, 0xf4, 0xe8, 0xd0, 0x1a, 0xab, 0x0b, 0x9e, 0x7d, 0x74, 0xd6, 0x8f, 0x5a, 0x3c, 0x3a, 0x50, 0xd8, 0x71, 0xd9, 0xd0, 0xd6, 0x49, 0x75, 0x79, 0xee, 0xd4, 0xf8, 0xdb, 0x87, 0x8e, 0x74, 0xa4, 0x4d, 0xaa, 0xd4, 0xba, 0x88, 0xf5, 0x72, 0x93, 0xa3, 0xfd, 0xc6, 0xa3, 0xd7, 0x2c, 0x3d, 0x74, 0xf3, 0xf8, 0xf6, 0xe0, 0x28, 0x7b, 0x7c, 0x5a, 0x66, 0x5a, 0xcd, 0xc5, 0x1b, 0xa6, + 0x76, 0xdd, 0x3c, 0xb3, 0x12, 0x22, 0x6d, 0x22, 0x75, 0x1d, 0x7a, 0x84, 0x7b, 0x5d, 0xc2, 0x84, 0x3b, 0x8f, 0xc4, 0x10, 0x8d, 0x99, 0x1e, 0x34, 0x10, 0x5c, 0x8d, 0x31, 0xc4, 0x49, 0x62, 0x35, 0xe3, 0xfe, 0xca, 0x30, 0xfe, 0xfc, 0xf0, 0x76, 0x14, 0xc0, 0x63, 0x71, 0x0b, 0x61, 0xd8, 0x63, 0x89, 0xcd, 0x39, 0x1b, 0xb9, 0x60, 0x3f, 0xbe, 0x85, 0x6d, 0x6d, 0xbf, 0x10, 0x9e, 0xad, 0xce, 0xa5, 0xa3, 0xfc, 0xcf, 0xc5, 0x5c, 0xa9, 0x03, 0x05, 0xf8, 0xeb, 0x61, 0x2e, 0xf1, 0x6b, 0xf0, 0x0d, 0xb4, 0x8c, 0x06, 0x49, 0x12, 0xde, 0x97, 0xb6, 0x20, 0x19, 0xf7, 0x72, 0x1b, 0x58, 0x1c, 0x6e, 0xb4, 0x21, 0xfc, 0x05, 0x9f, 0x05, 0x73, 0x13, 0xd3, 0x58, 0x42, 0xb8, 0x06, 0x6f, 0xa4, 0xe3, 0x1b, 0x50, 0x1e, 0x15, 0xfc, 0xe2, 0x4e, 0x74, 0x3e, 0x7a, 0x32, 0x98, 0x42, 0xd6, 0x00, 0xb9, + 0x0a, 0x8b, 0x42, 0x32, 0xe6, 0x45, 0x3f, 0x21, 0x76, 0x39, 0xe0, 0xe3, 0x4e, 0x9f, 0x73, 0x56, 0x8b, 0x6c, 0xb1, 0xca, 0x40, 0xf4, 0x44, 0x49, 0x10, 0xe1, 0xb1, 0x90, 0x24, 0xc8, 0x74, 0xc5, 0xa0, 0xe3, 0x94, 0xd3, 0xa9, 0x8d, 0xa2, 0x4f, 0xdf, 0x0e, 0xce, 0xc4, 0x74, 0xce, 0x9d, 0x76, 0x4e, 0xbf, 0x31, 0x27, 0x51, 0x8c, 0x53, 0xcf, 0x9b, 0x38, 0x7e, 0xe4, 0x88, 0xe1, 0x6d, 0xcd, 0x83, 0xeb, 0x6b, 0x07, 0x95, 0x17, 0x15, 0x64, 0x65, 0xe4, 0xda, 0xa4, 0xe4, 0x12, 0xb7, 0x36, 0x95, 0x24, 0x59, 0xf5, 0x45, 0xd8, 0xaf, 0x38, 0x18, 0xf6, 0xf2, 0xe8, 0x6c, 0x94, 0x09, 0xf3, 0x35, 0xd8, 0x03, 0x90, 0xe0, 0xa2, 0x49, 0xbf, 0x5b, 0xdf, 0xf3, 0xec, 0xb5, 0xc3, 0x2f, 0xb9, 0x18, 0x1c, 0x01, 0x37, 0x74, 0xdf, 0x37, 0xaf, 0xa6, 0x31, 0x86, 0x78, 0x58, 0xb6, 0xf4, 0x9e, 0xdf, + 0x62, 0x49, 0x77, 0xa6, 0x34, 0x5f, 0xb0, 0x6a, 0xfa, 0xbd, 0x8f, 0x97, 0x4c, 0x58, 0x36, 0xea, 0xd5, 0x83, 0x10, 0x6c, 0xb4, 0x60, 0x74, 0xf7, 0xf0, 0xf1, 0x4b, 0xc6, 0x55, 0x27, 0xba, 0x52, 0x6c, 0x62, 0xca, 0xa8, 0xb1, 0xf5, 0xb3, 0xd7, 0x4d, 0xbc, 0xec, 0xe1, 0x41, 0xa9, 0x6d, 0x93, 0x67, 0xd5, 0x9d, 0x77, 0xf7, 0x95, 0x13, 0x1c, 0x69, 0x13, 0x07, 0x45, 0x0b, 0xcc, 0xfc, 0xa9, 0xf3, 0xe3, 0x12, 0x4a, 0x6b, 0x4b, 0x6f, 0xbe, 0xae, 0xee, 0x82, 0xa0, 0x1f, 0x3f, 0x9d, 0x59, 0x3f, 0xb1, 0xa6, 0x62, 0x5c, 0x7d, 0xb6, 0xaf, 0xc8, 0x97, 0xe0, 0x82, 0x71, 0xb9, 0x40, 0x79, 0x94, 0xd4, 0xbd, 0x0b, 0xb5, 0xe3, 0x92, 0xa0, 0xb3, 0x88, 0xcc, 0x3b, 0x17, 0x1c, 0xef, 0xc2, 0xa4, 0xd3, 0xe2, 0xed, 0xf5, 0x37, 0x87, 0x72, 0x62, 0xcf, 0x21, 0xef, 0xc0, 0x53, 0x35, 0xa7, 0x9f, + 0xa9, 0xca, 0x28, 0xf2, 0x99, 0xd7, 0x95, 0x7e, 0x16, 0x75, 0x9d, 0x69, 0x35, 0x5d, 0xe0, 0x1b, 0xd7, 0x1e, 0x00, 0xcd, 0x90, 0x7c, 0x4a, 0xcd, 0x10, 0xb1, 0xf7, 0x62, 0x6a, 0x06, 0x2a, 0x25, 0x1e, 0xd1, 0x39, 0xee, 0xbd, 0xbb, 0xca, 0xab, 0xfa, 0x8c, 0x7e, 0x46, 0xde, 0x5d, 0x5f, 0x3c, 0x79, 0xe1, 0x25, 0x2f, 0x62, 0xfe, 0xbe, 0x7b, 0xff, 0x71, 0x7e, 0xbb, 0x9c, 0xec, 0x72, 0x57, 0x4f, 0xbe, 0x7a, 0x7a, 0xf7, 0x33, 0xab, 0x47, 0x76, 0x5c, 0xbf, 0x67, 0xfe, 0x90, 0x39, 0x93, 0x3b, 0xf2, 0x2c, 0x89, 0xb6, 0xc4, 0xf8, 0xd7, 0x2f, 0x5c, 0x62, 0x4f, 0x1f, 0x5b, 0x66, 0x1a, 0xee, 0xdc, 0xc1, 0x4e, 0x89, 0x98, 0x7e, 0x96, 0xfb, 0x1f, 0xc2, 0x8e, 0x23, 0x0b, 0xe7, 0x4e, 0x9e, 0x63, 0x77, 0x04, 0x2a, 0x02, 0x9d, 0xb7, 0xfc, 0x7c, 0xf5, 0x9a, 0x37, 0x36, 0x75, 0x38, 0xb3, + 0x2a, 0x7c, 0xb2, 0xd5, 0x01, 0x73, 0x74, 0x7c, 0xf8, 0x4b, 0xd0, 0xb5, 0x64, 0x6d, 0xac, 0x64, 0xeb, 0x27, 0xda, 0xdb, 0xfb, 0x2b, 0xb6, 0x7e, 0xf6, 0xfe, 0x4a, 0x9d, 0xf3, 0x65, 0x24, 0xcd, 0x62, 0xa2, 0x8f, 0x03, 0xdc, 0x9e, 0xef, 0xd9, 0xbd, 0xb1, 0x6f, 0xfa, 0xa4, 0xb9, 0x20, 0xfc, 0x39, 0x95, 0x9d, 0x00, 0x58, 0x6d, 0x74, 0x1d, 0xbe, 0x25, 0xb4, 0x5f, 0x5d, 0x87, 0xf7, 0x47, 0x9d, 0x87, 0x05, 0xe0, 0x3c, 0x8c, 0xd6, 0xb5, 0xab, 0x4f, 0x9a, 0xbb, 0xf0, 0x4f, 0xf0, 0x36, 0xa2, 0x1b, 0x0a, 0xf0, 0x60, 0x54, 0x8a, 0x7f, 0x41, 0xb4, 0x11, 0xf9, 0xaf, 0xf0, 0x0c, 0x4a, 0xa4, 0xb7, 0x68, 0xd4, 0xe3, 0xc1, 0x17, 0x84, 0x67, 0x28, 0x09, 0x26, 0xe9, 0x7d, 0xdc, 0x36, 0xde, 0xca, 0xef, 0xe6, 0x65, 0x71, 0x3b, 0x5a, 0xdd, 0x7b, 0x07, 0xbb, 0xf1, 0xd6, 0x7b, 0x87, 0xbe, + 0x8f, 0x00, 0x9e, 0xd9, 0x2b, 0xb8, 0x6f, 0x78, 0x99, 0x27, 0x6b, 0x7f, 0xc8, 0xc5, 0x38, 0x74, 0xc8, 0xa5, 0xff, 0x3e, 0x0b, 0xaf, 0xc0, 0x83, 0xb8, 0x7f, 0x93, 0xdf, 0x89, 0x1e, 0x0b, 0x25, 0xa8, 0xbf, 0x27, 0xa8, 0x1c, 0x1b, 0xa3, 0x46, 0xe5, 0x7a, 0xfe, 0x6a, 0xf2, 0xed, 0x41, 0x74, 0x2c, 0x68, 0xcf, 0x77, 0x71, 0x58, 0xca, 0x20, 0x26, 0xa8, 0x16, 0x58, 0xb1, 0x02, 0x59, 0x64, 0x51, 0xb6, 0xc0, 0xeb, 0x45, 0xb2, 0xc0, 0x13, 0xe1, 0x21, 0x16, 0xa8, 0xea, 0xed, 0x46, 0x92, 0x58, 0x7c, 0x45, 0xf3, 0xd6, 0x26, 0xa3, 0x78, 0xa7, 0xce, 0xb6, 0x24, 0x6a, 0x47, 0xb4, 0x2e, 0x3a, 0x87, 0xf6, 0x1c, 0x3c, 0x46, 0x4e, 0xe3, 0x56, 0x29, 0xb1, 0x0d, 0xc9, 0x2a, 0x56, 0x94, 0xef, 0x23, 0x14, 0xcf, 0x4a, 0xe8, 0x9d, 0x3b, 0x12, 0xb8, 0x93, 0x70, 0xe0, 0xe4, 0x18, 0xc1, 0xd2, + 0xfb, 0x44, 0xad, 0xa4, 0x29, 0x1b, 0xfd, 0xd3, 0x66, 0x77, 0xd7, 0x6b, 0x8b, 0xd5, 0x85, 0x63, 0xa2, 0xc3, 0x40, 0xe3, 0xfe, 0x16, 0x2f, 0xfe, 0xd2, 0xe2, 0x96, 0x40, 0x62, 0xfb, 0x4d, 0x64, 0xad, 0xfa, 0xc9, 0xc6, 0x71, 0xe9, 0x17, 0x18, 0x23, 0x40, 0x3f, 0x87, 0x47, 0xc6, 0xb1, 0x95, 0x6b, 0x3d, 0xac, 0x5c, 0x55, 0x6c, 0xe5, 0xc2, 0xa8, 0x96, 0x70, 0x3a, 0x51, 0xdc, 0x87, 0x9a, 0xd1, 0x8c, 0x60, 0x57, 0x76, 0x16, 0xa1, 0x4a, 0x09, 0x98, 0x93, 0xcb, 0x9d, 0x9c, 0x85, 0xe3, 0x3b, 0x9a, 0x30, 0xc6, 0xa3, 0xc1, 0x81, 0x01, 0xdc, 0x8e, 0x9a, 0x8b, 0x2c, 0x36, 0x8c, 0xed, 0x16, 0xac, 0x3b, 0x23, 0xed, 0x86, 0x07, 0x6b, 0x7c, 0x17, 0x7b, 0xbb, 0x16, 0x87, 0xed, 0xf6, 0xb9, 0x76, 0x30, 0xf3, 0x9a, 0x51, 0xf3, 0xe0, 0x86, 0x9a, 0xea, 0x5c, 0x57, 0x72, 0x6d, 0x7e, 0x5a, + 0x52, 0x62, 0x25, 0x50, 0x5d, 0x9f, 0x91, 0xc2, 0x12, 0xfb, 0xa0, 0x9f, 0x83, 0xc0, 0x64, 0x9f, 0x79, 0xb7, 0x00, 0xe7, 0x46, 0x2c, 0x03, 0xc9, 0x2e, 0xb1, 0x03, 0xc0, 0x35, 0xc4, 0x6c, 0x10, 0x22, 0x07, 0x80, 0x99, 0xca, 0x5f, 0x93, 0x5d, 0x9a, 0xc1, 0xe0, 0xf1, 0xe0, 0x2f, 0x38, 0xcd, 0x2a, 0x78, 0x8f, 0x9e, 0xfd, 0xcd, 0xa0, 0x67, 0x7f, 0xb3, 0xe3, 0xd5, 0xb3, 0xbf, 0xa9, 0x9b, 0x94, 0x5f, 0x5b, 0x54, 0x63, 0x41, 0x99, 0x4b, 0xad, 0x4c, 0x3f, 0x59, 0x30, 0x6f, 0x24, 0x72, 0x17, 0x87, 0x3c, 0x68, 0x34, 0x4e, 0xec, 0xdc, 0xe3, 0x86, 0x18, 0x5d, 0x75, 0x18, 0xdb, 0x9a, 0x3d, 0x64, 0xb9, 0xaa, 0x4c, 0x00, 0x4f, 0x19, 0x23, 0x4b, 0x38, 0x24, 0xe7, 0xe7, 0x7a, 0x79, 0x11, 0x89, 0x1d, 0xde, 0x60, 0x6e, 0xe4, 0x67, 0x1c, 0xcf, 0x25, 0x20, 0x4e, 0x8e, 0x57, 0x53, 0x70, + 0x56, 0xe4, 0xc5, 0xa2, 0x55, 0x86, 0xbd, 0x62, 0x28, 0xa8, 0xd0, 0x11, 0xcf, 0x91, 0x1f, 0x48, 0x8a, 0x39, 0x84, 0x6f, 0x62, 0x1a, 0xd6, 0x13, 0x8e, 0x0b, 0x6d, 0xd3, 0x89, 0x2d, 0x61, 0xb3, 0xcd, 0xef, 0xb4, 0x48, 0x1c, 0x0b, 0x81, 0xc9, 0xae, 0xac, 0xd4, 0x39, 0x48, 0x81, 0x32, 0x17, 0x2f, 0x43, 0x18, 0x73, 0x11, 0x59, 0x81, 0xcb, 0x6a, 0xd9, 0xd1, 0x00, 0xb9, 0x83, 0x83, 0x4f, 0x99, 0x11, 0x41, 0x06, 0xc8, 0x6d, 0x9f, 0x0e, 0xc3, 0xb5, 0x58, 0xcf, 0x0c, 0xc1, 0xe6, 0x53, 0x53, 0x53, 0x47, 0xa7, 0x8e, 0x6e, 0x0b, 0x92, 0xb1, 0xab, 0x2a, 0x2f, 0x73, 0xb9, 0xdd, 0x3e, 0x4f, 0x7e, 0xb2, 0x3f, 0xd1, 0x09, 0x86, 0x4a, 0x16, 0x8c, 0x15, 0xbb, 0x33, 0x48, 0x4c, 0x41, 0x8f, 0x6a, 0x08, 0x8a, 0x64, 0x7d, 0x06, 0xdf, 0xc1, 0xd4, 0x68, 0x21, 0x16, 0x20, 0xfc, 0x1b, 0xde, + 0x25, 0xd5, 0xd5, 0xbb, 0x20, 0x0a, 0x3a, 0xdd, 0xd5, 0xf6, 0xb0, 0x49, 0x90, 0x89, 0xeb, 0x6a, 0x85, 0xe6, 0x8b, 0x8e, 0xf5, 0xcc, 0x99, 0x59, 0x3c, 0xa6, 0xd1, 0x17, 0xda, 0xb9, 0xec, 0xd8, 0xf4, 0x8b, 0xa6, 0xb7, 0x2e, 0x2c, 0x13, 0x1f, 0xb8, 0xf2, 0xbf, 0x8e, 0x09, 0x4b, 0x8f, 0x4c, 0x3f, 0x76, 0xd1, 0xd1, 0x9e, 0xa3, 0x0b, 0xee, 0xbd, 0x76, 0xcd, 0x8f, 0x8a, 0x6c, 0x8e, 0x86, 0xf5, 0x33, 0xe6, 0x5d, 0x9b, 0x99, 0xd2, 0x3e, 0x75, 0x56, 0xd5, 0xc4, 0x55, 0xe7, 0x97, 0x4f, 0xe7, 0xf2, 0xb2, 0x4b, 0x9f, 0x5a, 0x76, 0x79, 0xe6, 0xe0, 0xa9, 0x8d, 0x7e, 0x5c, 0x98, 0xe9, 0xbf, 0xa2, 0xa7, 0xad, 0x71, 0xee, 0x15, 0xf7, 0xe5, 0x2a, 0x8f, 0xe1, 0xb6, 0x40, 0x86, 0x72, 0x0d, 0x3e, 0x96, 0x5d, 0xc6, 0x85, 0xfe, 0x81, 0xbf, 0x2d, 0x5a, 0xb6, 0x62, 0x7b, 0x0e, 0x9e, 0x5d, + 0x30, 0x6b, 0xd8, 0x94, 0x25, 0x33, 0xeb, 0xc6, 0x55, 0xa5, 0x56, 0xcf, 0xbe, 0x6d, 0xc6, 0xa6, 0x92, 0xcb, 0x60, 0x9c, 0xc3, 0x3f, 0x56, 0x76, 0x0a, 0x6e, 0xc1, 0x4e, 0xec, 0xc2, 0x54, 0xd4, 0x8e, 0x7e, 0x13, 0x4c, 0xa2, 0xde, 0x7c, 0x13, 0x89, 0xfd, 0xe1, 0x26, 0x3c, 0xa8, 0x7a, 0x10, 0x27, 0x59, 0x04, 0x6d, 0xf1, 0xb5, 0xc5, 0x71, 0x3c, 0xb2, 0x48, 0xbc, 0x65, 0x0e, 0xe2, 0xac, 0x84, 0x36, 0x71, 0xd2, 0x1c, 0x6a, 0xd3, 0xd9, 0xb1, 0xba, 0x39, 0x44, 0xad, 0xbd, 0x85, 0x30, 0x03, 0x16, 0xc8, 0xfa, 0x82, 0x78, 0x7a, 0xb9, 0x96, 0x68, 0xb9, 0x82, 0x55, 0xb1, 0x32, 0xd8, 0xed, 0xf3, 0x3b, 0x4d, 0xfb, 0x50, 0xa6, 0x4c, 0x64, 0xa8, 0x32, 0x9c, 0xce, 0x11, 0xc3, 0x86, 0x0e, 0x19, 0xdc, 0x58, 0x5f, 0x57, 0x55, 0x59, 0x51, 0x56, 0x58, 0x90, 0x9d, 0xe9, 0x4c, 0x75, 0xa6, + 0x16, 0x82, 0x55, 0xe9, 0xd1, 0x6d, 0x75, 0x76, 0xdf, 0xd2, 0x4d, 0x86, 0x45, 0xb5, 0x17, 0x23, 0xea, 0xa8, 0x04, 0xc3, 0x8c, 0x2b, 0x48, 0xf6, 0xd7, 0x9a, 0xcd, 0x76, 0x17, 0x6c, 0x50, 0x8d, 0x89, 0x98, 0x95, 0xff, 0x55, 0xfb, 0x5c, 0xe3, 0xb4, 0x00, 0x35, 0xc7, 0x57, 0xe5, 0x8c, 0x3e, 0x6f, 0x66, 0x1d, 0xb1, 0x2a, 0xeb, 0x5b, 0x96, 0x3e, 0x3c, 0xdf, 0xc5, 0xe5, 0xd4, 0x8f, 0x2b, 0x77, 0x7b, 0x8e, 0x9a, 0x2d, 0x77, 0x32, 0x74, 0xdf, 0xeb, 0x96, 0x65, 0x7b, 0x28, 0x27, 0xae, 0xb6, 0xf0, 0x00, 0x31, 0xcc, 0xf9, 0x79, 0x81, 0xd6, 0xb2, 0xb4, 0xe1, 0x6b, 0x89, 0x69, 0x79, 0x78, 0xd3, 0xc4, 0xca, 0x89, 0x8d, 0x39, 0xe2, 0xd2, 0x57, 0x23, 0xc6, 0x3b, 0xff, 0x7d, 0x01, 0xbb, 0x7b, 0xfc, 0x1d, 0x19, 0xa4, 0x3a, 0x32, 0x3e, 0x35, 0xe8, 0x4b, 0xd6, 0x9f, 0x49, 0xf0, 0x10, + 0xab, 0xc2, 0xc5, 0x09, 0x30, 0x48, 0x42, 0x6e, 0x36, 0xf8, 0x85, 0xd7, 0x62, 0xf0, 0xc1, 0x6f, 0xb6, 0x3e, 0xbf, 0x01, 0xcc, 0xf5, 0x81, 0x21, 0xd4, 0x01, 0x94, 0x97, 0x67, 0xc5, 0x02, 0x02, 0x02, 0x32, 0x07, 0xf1, 0x16, 0x2c, 0x4a, 0xbc, 0x38, 0x87, 0x85, 0x6d, 0xb4, 0x61, 0x55, 0xad, 0xcf, 0x95, 0xb4, 0x1b, 0x70, 0x85, 0x7d, 0xd3, 0xd2, 0xe9, 0xa3, 0xc7, 0x79, 0xd4, 0xd3, 0x93, 0x25, 0x06, 0x5e, 0x08, 0x63, 0xa1, 0xa7, 0x9f, 0x2c, 0xea, 0x1b, 0x3e, 0x3d, 0x07, 0x19, 0xc2, 0x84, 0xaa, 0x41, 0x65, 0xa5, 0x89, 0xee, 0xc4, 0xbc, 0xc4, 0x40, 0x6e, 0x52, 0xae, 0x1d, 0xe6, 0x18, 0x9b, 0x2e, 0xd4, 0x31, 0x3e, 0x7d, 0xb2, 0x6b, 0xda, 0x41, 0x21, 0x33, 0xcb, 0xb8, 0x07, 0xe0, 0x82, 0xa4, 0xf8, 0xf1, 0xca, 0x0d, 0xb3, 0x7c, 0x4d, 0x29, 0xf1, 0x72, 0xba, 0xa3, 0x21, 0xa7, + 0x62, 0x54, 0x63, 0xa9, 0xdb, 0x15, 0xa7, 0x29, 0xc2, 0xe4, 0x84, 0xa3, 0x71, 0xba, 0xdd, 0x9f, 0xe0, 0xea, 0xc9, 0xf1, 0x88, 0x7c, 0xed, 0xdc, 0x65, 0x4e, 0xe7, 0x66, 0xaf, 0xdd, 0x5b, 0x3b, 0xb6, 0x72, 0xbb, 0xc8, 0x74, 0xa0, 0xa0, 0xec, 0xc3, 0x0f, 0x0a, 0x6c, 0x28, 0x78, 0xfc, 0x33, 0x4f, 0x26, 0x19, 0x87, 0x5f, 0x20, 0xc4, 0xfd, 0x4c, 0xb0, 0xa2, 0x41, 0x68, 0x4b, 0xe7, 0x9e, 0x00, 0xdc, 0xa2, 0xb3, 0xd0, 0x5b, 0x0e, 0x51, 0x5d, 0xed, 0xb6, 0xd0, 0x95, 0xc1, 0xdc, 0xd3, 0x2c, 0x83, 0xbf, 0xbf, 0x8e, 0x36, 0xf5, 0x5b, 0xf1, 0x00, 0xfd, 0x16, 0xdd, 0x65, 0x41, 0x47, 0x05, 0xeb, 0x2f, 0xbf, 0x2f, 0x3f, 0x0f, 0xce, 0xaa, 0xdd, 0x46, 0x2b, 0x90, 0xf4, 0x4d, 0x26, 0xee, 0xbf, 0xbf, 0x52, 0x3c, 0x47, 0x8b, 0xce, 0xbb, 0xbe, 0xab, 0x72, 0x42, 0x59, 0x8a, 0x94, 0xea, + 0x88, 0x2f, 0xcc, 0x7f, 0xd4, 0x15, 0xaf, 0x75, 0x54, 0x4a, 0xc2, 0xb1, 0x38, 0xbb, 0x2e, 0xc3, 0x4e, 0x0b, 0xb7, 0x79, 0xf2, 0x86, 0x39, 0x75, 0x89, 0xee, 0xa9, 0x24, 0xe1, 0xf1, 0xe7, 0xf4, 0x5e, 0xda, 0x8a, 0x5f, 0xe4, 0x35, 0x81, 0xa5, 0xfb, 0x19, 0xe1, 0xcf, 0xf9, 0x5c, 0xb1, 0x05, 0x0d, 0x41, 0x53, 0x82, 0x93, 0xd2, 0xb1, 0x20, 0xe5, 0x27, 0x71, 0x16, 0x1e, 0xa2, 0x2c, 0x55, 0x61, 0x3b, 0x27, 0x74, 0x10, 0xee, 0x20, 0x2e, 0xb3, 0xc2, 0xd6, 0x06, 0x07, 0x21, 0x8d, 0xc9, 0xd2, 0xc9, 0xd9, 0x2d, 0xdc, 0x1c, 0x99, 0x28, 0x02, 0x2d, 0x0e, 0xb6, 0xdd, 0x0e, 0x2f, 0x92, 0xd1, 0x42, 0x34, 0xa6, 0xbe, 0x16, 0x7c, 0xb5, 0xe7, 0xba, 0x5c, 0x85, 0xee, 0xa4, 0xc4, 0x64, 0xb6, 0xe3, 0x46, 0x3f, 0xab, 0x05, 0x9b, 0x76, 0x7e, 0x28, 0xc2, 0x02, 0xc4, 0xbb, 0xa2, 0xd7, 0x4a, + 0x7d, 0x8b, 0x39, 0xc5, 0xc3, 0xe7, 0x0e, 0x5a, 0x3b, 0x72, 0xe5, 0x0d, 0x71, 0x71, 0xda, 0x67, 0xb9, 0x9c, 0x57, 0xb8, 0x3c, 0x3b, 0x4a, 0x05, 0x4f, 0xe2, 0x1c, 0x9c, 0xa3, 0x7c, 0x60, 0xfc, 0xf8, 0xa6, 0x72, 0x6f, 0x41, 0x46, 0x92, 0xc5, 0x66, 0x97, 0xc6, 0x94, 0x0e, 0xda, 0x70, 0xdd, 0xbf, 0xd5, 0x8f, 0xe4, 0x7f, 0x9f, 0x9c, 0x3c, 0xde, 0x83, 0x2b, 0x5c, 0x6e, 0x65, 0xe7, 0x26, 0xe5, 0x97, 0x5a, 0x37, 0xdc, 0xd9, 0xdc, 0xe4, 0x70, 0x39, 0xac, 0x36, 0xc6, 0xd7, 0x88, 0x56, 0xe5, 0xd7, 0xf1, 0xdf, 0xa2, 0x6a, 0x34, 0x35, 0x38, 0x39, 0x37, 0x8d, 0x93, 0x2d, 0xd5, 0x84, 0x35, 0x40, 0xec, 0x24, 0xc4, 0x77, 0x20, 0x8b, 0x20, 0x0b, 0x16, 0xb2, 0x16, 0xf1, 0x9c, 0x95, 0x03, 0xbf, 0x2d, 0x02, 0x02, 0x77, 0x78, 0x73, 0xc8, 0x8a, 0x64, 0x45, 0xb2, 0x95, 0xba, 0xce, 0x51, + 0x3b, 0x82, 0x94, 0x75, 0x01, 0x7b, 0x7d, 0x5d, 0x59, 0x91, 0x54, 0xe0, 0x72, 0xe7, 0x91, 0x7f, 0xd1, 0xfb, 0x08, 0xf9, 0xe4, 0x23, 0x69, 0x40, 0x74, 0x50, 0x53, 0xfa, 0xfb, 0xc9, 0x14, 0x0f, 0xdc, 0x46, 0xa0, 0x17, 0x35, 0x40, 0xd5, 0xe9, 0x8f, 0x2c, 0x03, 0x05, 0x78, 0x3c, 0x97, 0xd5, 0x1b, 0x72, 0xc4, 0x63, 0x6b, 0x82, 0x37, 0xce, 0x91, 0x95, 0xea, 0xba, 0x27, 0x67, 0xf0, 0xc4, 0xaa, 0xe2, 0x09, 0xf9, 0xcf, 0x87, 0xfe, 0xce, 0xfd, 0xdb, 0x19, 0xf7, 0x57, 0x87, 0x37, 0x2e, 0x3e, 0xc3, 0xe3, 0x5c, 0x9f, 0x56, 0xd5, 0x59, 0x55, 0x3c, 0x3e, 0xff, 0xde, 0x7b, 0x27, 0x39, 0x08, 0xc9, 0x98, 0x29, 0xc9, 0xf6, 0x94, 0x9c, 0x94, 0xea, 0x29, 0x2d, 0xbe, 0x14, 0xd7, 0xbf, 0xee, 0x9d, 0x94, 0x5c, 0xe8, 0x50, 0x8e, 0x8b, 0xb2, 0x2d, 0x19, 0xb0, 0x66, 0x5f, 0x4a, 0x02, 0xe1, + 0xae, 0xad, 0xe1, 0x6c, 0xf4, 0x29, 0xe7, 0x22, 0x4c, 0xfa, 0x1e, 0x84, 0x4e, 0x6e, 0x64, 0xdc, 0xf7, 0xe4, 0x46, 0x9d, 0xdb, 0x8e, 0x0f, 0x67, 0xe3, 0x5b, 0xc9, 0xd8, 0xcb, 0xe8, 0xc1, 0x70, 0x98, 0xfd, 0x8e, 0x8d, 0xbf, 0x77, 0x84, 0x3d, 0x5c, 0x0a, 0x2e, 0xe4, 0x65, 0xf4, 0x88, 0x96, 0xdf, 0xf4, 0xbb, 0x25, 0x9c, 0xcd, 0x65, 0xe1, 0x42, 0x92, 0x7f, 0x07, 0xc2, 0x31, 0x7e, 0x9f, 0xa0, 0xfc, 0x88, 0x6b, 0x26, 0x5d, 0x2b, 0xf3, 0x63, 0x62, 0xd6, 0xef, 0x52, 0xae, 0xe2, 0x1a, 0x70, 0x19, 0xf9, 0x7d, 0x2c, 0xe2, 0x63, 0xe4, 0x8f, 0x53, 0x16, 0x71, 0x76, 0x5c, 0x41, 0x7e, 0x9f, 0xd0, 0xf7, 0x77, 0xb2, 0x0c, 0x96, 0x93, 0xb1, 0xfc, 0x5c, 0xbc, 0x1f, 0x95, 0xa0, 0xa9, 0x70, 0x63, 0x2e, 0xe0, 0xb1, 0xf2, 0x3c, 0x9a, 0xdc, 0x5e, 0xcf, 0x4b, 0x22, 0xd7, 0x61, 0x83, 0x97, + 0x4c, 0x22, 0x21, 0x8a, 0x3d, 0x56, 0x0b, 0x27, 0x4a, 0x44, 0xb6, 0x17, 0x83, 0x6f, 0x6d, 0x34, 0xa7, 0x53, 0xc6, 0x74, 0x7a, 0x22, 0x34, 0xae, 0xb3, 0x75, 0x48, 0x4b, 0x53, 0x55, 0xe5, 0xa0, 0x72, 0x52, 0x44, 0xb1, 0xab, 0xa4, 0xc5, 0x65, 0x87, 0xab, 0xe4, 0xf0, 0x70, 0xc9, 0xe3, 0xa6, 0x0f, 0x97, 0x98, 0xdc, 0x56, 0x99, 0xfe, 0x06, 0xab, 0x10, 0x1f, 0x28, 0x20, 0x23, 0x29, 0xf9, 0xc5, 0x28, 0xa0, 0x04, 0xeb, 0x99, 0xeb, 0xa2, 0xf2, 0x7e, 0x77, 0x7c, 0xa9, 0xcb, 0xc3, 0xcf, 0x17, 0xad, 0xa2, 0x18, 0x67, 0xb9, 0x5c, 0x48, 0x72, 0xd4, 0xe7, 0x5a, 0x96, 0xca, 0x36, 0x91, 0x00, 0xdd, 0x42, 0x8a, 0x63, 0xe1, 0x2f, 0x16, 0x25, 0x5a, 0xae, 0xb1, 0x4a, 0x92, 0xf5, 0x5a, 0xab, 0x07, 0x7f, 0x17, 0x8d, 0x5c, 0xa6, 0xe7, 0xb6, 0x39, 0xcd, 0x99, 0x79, 0x9e, 0xbb, 0xd7, 0x95, + 0xa0, 0xac, 0xf5, 0x56, 0xa5, 0xa5, 0x55, 0x79, 0xf1, 0x75, 0x0e, 0x57, 0x8e, 0xb2, 0x3a, 0xbd, 0x32, 0xd5, 0x53, 0x99, 0x8e, 0xaf, 0x75, 0xba, 0x42, 0x97, 0xa4, 0xe3, 0xfb, 0xf3, 0xb2, 0xfd, 0xb9, 0xca, 0xdc, 0xf4, 0x9b, 0x13, 0x4c, 0x7f, 0x25, 0xf9, 0x5c, 0x90, 0x2f, 0xb5, 0xa2, 0x0e, 0x5f, 0xe7, 0x4c, 0x80, 0x6c, 0x83, 0x20, 0x1b, 0xb3, 0x8b, 0x94, 0x11, 0xb8, 0x22, 0x7c, 0x84, 0xd9, 0x45, 0xdf, 0xbd, 0xcf, 0xfa, 0xfe, 0xbb, 0xf7, 0x23, 0x76, 0x11, 0xf9, 0x7d, 0x10, 0xfd, 0xfd, 0x86, 0x58, 0xbf, 0x2b, 0x4b, 0x38, 0x1b, 0xbe, 0x9d, 0xbb, 0x07, 0xc9, 0xb8, 0x33, 0xf4, 0x12, 0xea, 0x07, 0x83, 0x3b, 0x16, 0xc2, 0x46, 0x2e, 0x99, 0xd8, 0x7c, 0xf4, 0x0c, 0x87, 0xde, 0xb1, 0x87, 0xdb, 0x6a, 0xdd, 0xc8, 0x74, 0x86, 0x13, 0x79, 0x86, 0x8b, 0x2b, 0xf4, 0x33, 0x1c, 0x71, + 0x57, 0xe4, 0x10, 0x07, 0xb3, 0xb7, 0x7b, 0xe2, 0x36, 0x94, 0x00, 0xef, 0xc1, 0x6c, 0x2c, 0xf6, 0x05, 0xdc, 0xb8, 0xe7, 0xe1, 0x16, 0xe5, 0x1c, 0x1a, 0xde, 0x38, 0x01, 0x39, 0xd5, 0x0b, 0xbc, 0xd1, 0x6f, 0xee, 0xb8, 0x37, 0xfa, 0x3c, 0xb1, 0xdb, 0x16, 0xf5, 0xa2, 0x8e, 0xd6, 0xa1, 0xdc, 0xc3, 0xbd, 0x11, 0x7e, 0x98, 0x14, 0xe4, 0x0d, 0xa6, 0xca, 0xd4, 0x03, 0x2e, 0x3c, 0x57, 0x59, 0x06, 0xda, 0xb5, 0x07, 0x8d, 0x71, 0x95, 0xa8, 0x97, 0xf9, 0x61, 0x63, 0x95, 0xc8, 0x83, 0x93, 0x3d, 0x55, 0x24, 0xa5, 0xaf, 0x89, 0xf3, 0xc4, 0xff, 0xc8, 0x62, 0xb5, 0x5a, 0xae, 0xb4, 0xa5, 0xc5, 0xad, 0xfb, 0xf0, 0x39, 0xe5, 0x1e, 0x4b, 0xfc, 0x35, 0x96, 0xb8, 0x38, 0xcb, 0x35, 0x76, 0x1b, 0xb6, 0xb3, 0xf8, 0x7a, 0xca, 0x36, 0xee, 0x89, 0xf0, 0x2e, 0x94, 0x0c, 0xb1, 0x3b, 0xe8, 0xbb, + 0x18, 0x7a, 0x95, 0x7e, 0xba, 0xc0, 0xca, 0x3e, 0xab, 0x10, 0x7b, 0xca, 0xb6, 0x18, 0x21, 0xf6, 0x20, 0x86, 0xb3, 0xf2, 0x32, 0xf7, 0x72, 0xf8, 0x41, 0xe4, 0x85, 0x57, 0xd7, 0x7a, 0x1c, 0x46, 0xe6, 0xa7, 0x1d, 0xdc, 0xb9, 0x2d, 0xe0, 0xfa, 0x06, 0x71, 0xa6, 0x27, 0x69, 0x67, 0x1c, 0xc4, 0xf9, 0x85, 0x18, 0x31, 0x9c, 0x71, 0xf8, 0x46, 0x7c, 0x23, 0x57, 0xc5, 0x2d, 0x45, 0x19, 0xf0, 0x26, 0x17, 0x9e, 0xb1, 0x0a, 0xd3, 0x44, 0xcc, 0x7c, 0x34, 0xcd, 0x21, 0xfc, 0x91, 0xde, 0x49, 0x14, 0xd0, 0xf8, 0x44, 0x88, 0xb3, 0x6a, 0x7a, 0xc8, 0x1a, 0x15, 0x4e, 0xb5, 0xb6, 0x9a, 0xab, 0xca, 0xac, 0xe9, 0x2c, 0x2f, 0xed, 0x1c, 0xd6, 0x92, 0x95, 0x13, 0xc0, 0x37, 0x66, 0xd6, 0x75, 0x56, 0x94, 0x74, 0xb6, 0xb5, 0x64, 0x67, 0x17, 0xae, 0xe5, 0xba, 0x41, 0x0f, 0x66, 0xf8, 0x33, 0x8a, + 0xcb, 0x88, 0x92, 0xcc, 0xcd, 0xcc, 0xcd, 0x2c, 0x2a, 0xa3, 0x63, 0x78, 0x27, 0x19, 0xc3, 0x07, 0xc8, 0x18, 0x36, 0xec, 0xb7, 0xa9, 0x31, 0xe4, 0x80, 0x3a, 0xc5, 0x83, 0xa0, 0xe8, 0x07, 0x7f, 0xde, 0xa0, 0x3d, 0xf2, 0xf7, 0x4b, 0xd9, 0x5b, 0x01, 0x5d, 0x7c, 0xc4, 0x53, 0x8b, 0xcf, 0xac, 0x28, 0xe9, 0x81, 0xf1, 0x7d, 0x82, 0x8c, 0xef, 0x8b, 0x64, 0x7c, 0x73, 0x83, 0xd9, 0x02, 0xbb, 0xc3, 0xcf, 0x51, 0xab, 0x68, 0x41, 0xdf, 0x00, 0x8a, 0x22, 0x0b, 0x4d, 0x66, 0x78, 0x81, 0x1f, 0x63, 0x74, 0xdb, 0x4b, 0x3b, 0xe6, 0xd6, 0x6b, 0x83, 0x5b, 0x3f, 0xb7, 0xa3, 0x14, 0xe6, 0x58, 0xe8, 0x29, 0x49, 0xe2, 0x26, 0xc9, 0x2b, 0xb8, 0x00, 0xee, 0xa5, 0xb7, 0x5d, 0x20, 0xea, 0xf7, 0x1f, 0xd9, 0x1b, 0xbe, 0x3f, 0x6a, 0x7b, 0x22, 0x55, 0xc2, 0x34, 0xee, 0x5d, 0x62, 0x92, 0xc9, 0x9c, + 0x8b, 0x10, 0xf5, 0x9d, 0x6c, 0xee, 0x8a, 0x3b, 0x75, 0xbd, 0x8a, 0x42, 0x1f, 0x08, 0xeb, 0x88, 0x9c, 0x6b, 0x7e, 0x3f, 0x58, 0x3c, 0x8f, 0x59, 0x7d, 0x4e, 0x65, 0xd5, 0x67, 0x46, 0x74, 0x59, 0x27, 0xff, 0x17, 0xd6, 0x9d, 0xbc, 0x0a, 0xfe, 0xaf, 0x5c, 0x7e, 0x27, 0x3e, 0x7a, 0x27, 0xa9, 0xe7, 0x80, 0xc2, 0x71, 0x97, 0x84, 0x5f, 0x20, 0x0c, 0x43, 0x21, 0x3a, 0xe2, 0x32, 0xa6, 0xff, 0xbf, 0xbb, 0x4c, 0xd7, 0x21, 0xf7, 0x84, 0x3e, 0xe3, 0x96, 0x85, 0xf7, 0x92, 0x76, 0x58, 0xc8, 0xef, 0x97, 0xa8, 0x3a, 0xe4, 0x12, 0xfd, 0xf7, 0xed, 0xa1, 0x6f, 0xb9, 0xcb, 0x40, 0xc7, 0x40, 0x7c, 0xf8, 0xef, 0x56, 0xab, 0xbf, 0xaf, 0xd6, 0x7f, 0xef, 0x0e, 0x67, 0xe1, 0x9b, 0xd0, 0xbf, 0xc8, 0xef, 0xf5, 0x08, 0x7d, 0xff, 0x8a, 0x7a, 0xfe, 0xf9, 0x8a, 0xbe, 0x37, 0xb3, 0x5c, 0xd9, 0x2a, + 0x8e, 0x86, 0x3b, 0x22, 0x68, 0x23, 0xdb, 0x32, 0xb4, 0xe7, 0xc0, 0x41, 0x6a, 0x22, 0xf5, 0x97, 0xe0, 0x8d, 0xfc, 0x4d, 0xa4, 0xb7, 0x3b, 0x21, 0x41, 0x2e, 0xb2, 0x20, 0xb8, 0xd6, 0x37, 0xcb, 0x66, 0xe5, 0xe0, 0x76, 0xa6, 0x44, 0xa6, 0x20, 0x9a, 0xdb, 0x29, 0x52, 0xd7, 0x37, 0x88, 0xe7, 0x7b, 0xe0, 0x3c, 0xad, 0x08, 0x5e, 0x65, 0x23, 0xd8, 0x33, 0x8e, 0x99, 0x58, 0xdb, 0x20, 0xec, 0xe1, 0x59, 0xc4, 0x56, 0x52, 0x7d, 0x81, 0xcb, 0x95, 0x92, 0x97, 0x54, 0x48, 0x3a, 0x8c, 0xb2, 0x66, 0x7a, 0x33, 0xd8, 0xdf, 0xdf, 0xa6, 0x60, 0xbd, 0x1f, 0xdc, 0xbe, 0xba, 0xc4, 0xd1, 0x27, 0x42, 0xee, 0xe6, 0xa4, 0x0b, 0x27, 0xfa, 0x9a, 0x26, 0x57, 0x16, 0x8d, 0xce, 0x49, 0x94, 0xdc, 0x52, 0x42, 0x72, 0x56, 0x52, 0x41, 0xd1, 0xc4, 0xb1, 0xe5, 0xc5, 0xef, 0x70, 0x9f, 0x89, 0xdb, 0xbe, + 0x9f, 0xcb, 0xdd, 0x14, 0x5a, 0x31, 0x32, 0x69, 0x64, 0x9d, 0xaf, 0x26, 0x2f, 0xc9, 0xe9, 0x68, 0x71, 0xcb, 0xb2, 0x9c, 0x93, 0x9a, 0x97, 0x69, 0xdb, 0x20, 0x6e, 0x0b, 0xdd, 0xc8, 0x5d, 0x4d, 0xfa, 0x68, 0xb9, 0xf2, 0x35, 0xf7, 0xa6, 0x60, 0xe7, 0x02, 0xe8, 0x47, 0xb0, 0xc3, 0x2f, 0x7e, 0x24, 0xb2, 0xbe, 0x5b, 0xaf, 0x6c, 0xe3, 0x3f, 0xa4, 0xe7, 0xcb, 0x33, 0x61, 0xef, 0x5d, 0x4a, 0x41, 0x0f, 0xd0, 0x3e, 0x4b, 0x23, 0x02, 0xf0, 0x7b, 0xf1, 0x18, 0xca, 0x81, 0x3b, 0x99, 0x19, 0x18, 0xcb, 0xd9, 0x64, 0xa8, 0x1d, 0x76, 0x8e, 0x13, 0x78, 0xea, 0x62, 0x02, 0xb8, 0x2f, 0xb1, 0xcd, 0xe1, 0x8a, 0x05, 0x02, 0x62, 0x24, 0xcf, 0x53, 0xaf, 0x65, 0x12, 0x16, 0xe8, 0x27, 0xff, 0x67, 0xb7, 0x56, 0x4d, 0xa4, 0xd6, 0x68, 0xaf, 0x79, 0xe0, 0x6e, 0x05, 0xfe, 0x37, 0x21, 0x79, 0x77, + 0xf9, 0x06, 0x4f, 0xac, 0x4c, 0x4c, 0x38, 0x26, 0xc7, 0x4b, 0x81, 0x42, 0x7c, 0x8f, 0xaf, 0x79, 0xe2, 0x20, 0x57, 0xe2, 0xb1, 0x38, 0x7e, 0x78, 0x21, 0xa5, 0x6d, 0x7c, 0x68, 0x14, 0x1f, 0xc7, 0xc1, 0xb6, 0x08, 0x61, 0x38, 0x3c, 0x57, 0x18, 0x5a, 0xc3, 0xd3, 0x76, 0x67, 0x87, 0x3f, 0xe7, 0x8e, 0x48, 0xcd, 0xa4, 0xdd, 0xd7, 0xd0, 0x7d, 0xc4, 0xfd, 0xe1, 0xd7, 0xd0, 0x01, 0x90, 0x6f, 0x6e, 0xcf, 0x01, 0x55, 0xbe, 0x43, 0x53, 0x85, 0x8d, 0xca, 0xd7, 0x64, 0x4d, 0x91, 0xd1, 0x93, 0xbd, 0x1f, 0xb0, 0xb5, 0xa7, 0x37, 0x2c, 0xd9, 0xc2, 0x16, 0x79, 0x08, 0xc1, 0x76, 0x6a, 0x58, 0xa8, 0x57, 0xd8, 0x18, 0x7e, 0x90, 0xa6, 0xdb, 0xa5, 0x63, 0x7f, 0x93, 0xc8, 0x6c, 0xa5, 0xe9, 0x76, 0xeb, 0x58, 0x8d, 0x70, 0x7b, 0x78, 0x17, 0xbd, 0x07, 0xfb, 0x94, 0x8e, 0x95, 0x92, 0xf2, 0x5e, + 0x94, 0xbd, 0x04, 0x7b, 0x3a, 0x46, 0xba, 0x3d, 0x3a, 0x16, 0xd0, 0xd3, 0xed, 0xd5, 0xb1, 0x51, 0xc2, 0xce, 0xf0, 0x7d, 0xe2, 0xad, 0x70, 0xd2, 0x68, 0x28, 0x4f, 0x0e, 0x1f, 0x96, 0xbe, 0x20, 0xd8, 0xb3, 0x3a, 0xd6, 0x20, 0xdc, 0x15, 0x7e, 0x5c, 0x5c, 0x03, 0x37, 0x6e, 0x74, 0xac, 0x50, 0x4a, 0x08, 0xbf, 0x20, 0x7d, 0x4b, 0xb0, 0xe7, 0x74, 0x6c, 0x9c, 0x70, 0x3c, 0x7c, 0x87, 0xd8, 0x41, 0xb0, 0xe7, 0x75, 0xec, 0x3c, 0x69, 0x50, 0x78, 0xa3, 0xf4, 0x30, 0xc1, 0x5e, 0xd0, 0xb1, 0x69, 0xe4, 0x7b, 0xd7, 0xd3, 0xf6, 0x1d, 0xd0, 0xb1, 0x16, 0xc9, 0x1a, 0x7e, 0x48, 0x76, 0x13, 0xec, 0x45, 0x1d, 0x7b, 0x48, 0xb8, 0x34, 0xdc, 0x2a, 0x7e, 0x42, 0xb0, 0x83, 0x3a, 0xf6, 0x94, 0xf8, 0x65, 0xb8, 0x0a, 0xee, 0x2c, 0xa3, 0x97, 0x74, 0xec, 0x0e, 0x61, 0x23, 0xaa, 0xa0, 0xfd, 0xf7, + 0x13, 0x1d, 0xbb, 0x86, 0xf4, 0xdf, 0x1c, 0xfa, 0xbd, 0x87, 0x22, 0x7d, 0x2a, 0x3c, 0xa3, 0x7c, 0x23, 0x1e, 0x22, 0xd8, 0x61, 0x1d, 0xfb, 0xa7, 0x94, 0xae, 0x7c, 0x21, 0x67, 0x12, 0xec, 0x88, 0x8e, 0xbd, 0xa9, 0x6c, 0x0d, 0x0f, 0x25, 0xab, 0x82, 0x8c, 0x8e, 0xea, 0xd8, 0x21, 0xe5, 0xc9, 0x70, 0x25, 0x8a, 0x27, 0xd8, 0x31, 0x1d, 0xab, 0x13, 0x56, 0x87, 0x15, 0xf1, 0x03, 0x82, 0x7d, 0xa6, 0x63, 0xc3, 0x89, 0xc6, 0xda, 0x06, 0xdf, 0x86, 0x2b, 0x23, 0x98, 0xf8, 0x5d, 0x78, 0x9b, 0x4c, 0xb4, 0x18, 0xae, 0xd6, 0x30, 0x65, 0x9d, 0xf2, 0x48, 0x38, 0x47, 0x02, 0x17, 0x72, 0x23, 0xc1, 0xc9, 0x11, 0x4d, 0xf7, 0xaa, 0xf2, 0x60, 0x78, 0x5e, 0xf8, 0x30, 0x31, 0xe7, 0xfe, 0xa4, 0xe7, 0x7d, 0x44, 0x79, 0x28, 0x3c, 0x2a, 0x7c, 0x84, 0x60, 0x1f, 0x33, 0x0c, 0x61, 0x65, 0x61, 0x78, + 0x07, 0xbe, 0x53, 0x28, 0x23, 0x5c, 0x25, 0xa0, 0x7b, 0xf3, 0x61, 0x6f, 0x9f, 0x17, 0x47, 0xbf, 0x7d, 0xf6, 0x1b, 0xdf, 0x3e, 0x13, 0x1a, 0xf8, 0x2b, 0x77, 0x7d, 0xce, 0xf9, 0xdc, 0xa5, 0xae, 0x4a, 0xff, 0x05, 0x09, 0xfe, 0x42, 0x21, 0x98, 0x53, 0x08, 0xf2, 0xa2, 0xdc, 0x19, 0xde, 0x45, 0xd6, 0x22, 0x59, 0x4a, 0x8b, 0xc8, 0x90, 0x72, 0x0f, 0xc1, 0xc8, 0xb8, 0x49, 0xc9, 0x91, 0x71, 0x53, 0xb6, 0x85, 0xd7, 0x13, 0x6e, 0x40, 0x66, 0x69, 0x44, 0x86, 0x94, 0x97, 0xc3, 0xf7, 0x91, 0x35, 0x5c, 0x96, 0x3c, 0x11, 0x39, 0x50, 0xbe, 0x0e, 0xdf, 0x11, 0xbe, 0x9c, 0xb4, 0xf9, 0x23, 0xfd, 0x7b, 0x2d, 0xc2, 0x2c, 0xe5, 0x5f, 0xe2, 0x2b, 0x04, 0x6b, 0x30, 0x60, 0xf3, 0x08, 0x06, 0xdf, 0xd6, 0xa8, 0x63, 0xa5, 0x42, 0x77, 0x38, 0x55, 0xdc, 0x47, 0xb0, 0xc1, 0x91, 0xbe, 0x17, 0xba, + 0x95, 0x5d, 0x14, 0x6b, 0xea, 0x2b, 0xbb, 0x62, 0xb3, 0x8e, 0xdd, 0x28, 0xcc, 0x0a, 0xdf, 0x45, 0xeb, 0x68, 0xd1, 0xb1, 0xa5, 0x04, 0x5b, 0x4d, 0xb1, 0x21, 0x3a, 0x36, 0x9f, 0x60, 0x07, 0xa5, 0xe3, 0x04, 0x6b, 0xd5, 0xb1, 0xeb, 0x84, 0x79, 0xe1, 0x0d, 0xb4, 0x2d, 0x41, 0x1d, 0x5b, 0x4c, 0xb0, 0x6b, 0x28, 0xd6, 0x66, 0xc8, 0x3b, 0x4f, 0xcd, 0x3b, 0x54, 0x9f, 0xd3, 0x7f, 0x17, 0x16, 0x85, 0xff, 0x41, 0xdb, 0x37, 0x2c, 0x06, 0x36, 0xdc, 0x3c, 0x8f, 0x68, 0xde, 0x11, 0xfa, 0xf7, 0x1e, 0x57, 0xae, 0x08, 0xbf, 0x1a, 0x3e, 0x40, 0xb0, 0xd1, 0xca, 0x02, 0x95, 0x8b, 0x3e, 0x48, 0xec, 0x89, 0x5f, 0x12, 0x19, 0xb2, 0x43, 0x3c, 0x5d, 0x1b, 0xcf, 0xb1, 0x00, 0x6a, 0x98, 0x90, 0x22, 0x42, 0x06, 0x38, 0x7a, 0xab, 0x9b, 0x5d, 0x1d, 0xb3, 0x23, 0xbb, 0x2f, 0x91, 0x3e, 0x5c, 0x04, + 0x5d, 0x8e, 0x89, 0x65, 0x57, 0x95, 0x8d, 0x6b, 0xf9, 0x5f, 0xde, 0xaf, 0xfc, 0x44, 0xd9, 0x83, 0x7b, 0xf2, 0x53, 0x3f, 0x06, 0xe5, 0xcc, 0xff, 0x13, 0xa7, 0x6d, 0x4e, 0x7f, 0xed, 0x23, 0x53, 0xd9, 0x71, 0x50, 0xb6, 0x7d, 0x80, 0xb2, 0xe3, 0x50, 0x5c, 0x74, 0xd9, 0xc4, 0xa0, 0x70, 0x41, 0xe1, 0x4f, 0x2b, 0x87, 0x70, 0x4f, 0x5a, 0x91, 0xf5, 0x1d, 0xaa, 0xfa, 0x0f, 0x72, 0x63, 0x37, 0xa7, 0x27, 0x2b, 0xeb, 0xf1, 0x63, 0xe7, 0xaa, 0xfc, 0xc7, 0x94, 0x9f, 0xe2, 0x9e, 0xac, 0x32, 0xe1, 0x57, 0x50, 0x3e, 0xbe, 0x96, 0xdf, 0xb0, 0x39, 0x2f, 0x23, 0xf4, 0x1e, 0x04, 0xd6, 0x3d, 0xdb, 0xf2, 0x6b, 0x4d, 0xe5, 0x3f, 0xa4, 0xfc, 0x9c, 0x94, 0x5f, 0x2e, 0xbc, 0x05, 0xe5, 0x0f, 0x15, 0x5e, 0xdb, 0x9c, 0x9f, 0x19, 0xf2, 0xf3, 0x39, 0xb4, 0x7f, 0x16, 0x92, 0x75, 0xe7, 0x62, 0xaa, + 0x9b, 0x08, 0xe3, 0x90, 0x44, 0x81, 0x17, 0xa0, 0x06, 0x8e, 0xbd, 0xff, 0xd1, 0xca, 0x86, 0x07, 0xa3, 0xf4, 0x19, 0x3f, 0xb4, 0xbc, 0xde, 0x53, 0xcb, 0x5d, 0x7c, 0xbf, 0xf2, 0xe4, 0xa5, 0x9f, 0xa9, 0x9d, 0x5d, 0xb2, 0xe4, 0xcd, 0x8f, 0xb4, 0xb2, 0x68, 0x5b, 0x9d, 0xc0, 0x6f, 0x1d, 0x10, 0xd8, 0x8f, 0x2c, 0x57, 0xd0, 0x5c, 0x88, 0xb1, 0x04, 0x11, 0x71, 0x4d, 0x0d, 0x76, 0x22, 0x67, 0xbe, 0xfa, 0xae, 0xcf, 0xef, 0x72, 0x72, 0x12, 0xdc, 0xfd, 0xa9, 0xc7, 0xf9, 0x30, 0x9c, 0x7f, 0xe5, 0x38, 0x4f, 0x66, 0x96, 0x47, 0xb9, 0xed, 0x52, 0xdc, 0x8c, 0x7d, 0x50, 0x8d, 0xf0, 0xd8, 0x1b, 0x98, 0x4b, 0x4c, 0xc4, 0x6d, 0x4b, 0x94, 0xbf, 0x87, 0x8a, 0x0d, 0x75, 0xd9, 0xe1, 0x3d, 0x3b, 0x04, 0x5d, 0xe3, 0xa9, 0x51, 0x00, 0xc6, 0x01, 0x7d, 0xa2, 0x69, 0x96, 0x9a, 0x24, 0x9f, 0xde, + 0xf3, 0x3e, 0x5a, 0x8d, 0x8f, 0x76, 0xcc, 0x1f, 0x59, 0x1d, 0xd3, 0x31, 0x47, 0x3f, 0xe5, 0x55, 0x5a, 0xfe, 0x2f, 0xf1, 0x30, 0x53, 0xf9, 0xa4, 0x5f, 0x6c, 0xfd, 0xf4, 0x0b, 0x29, 0xd9, 0xd0, 0x2f, 0x38, 0x0b, 0x67, 0x63, 0x28, 0x37, 0x34, 0xf8, 0x52, 0xbc, 0x48, 0x10, 0x79, 0x2a, 0x8d, 0xdc, 0x6e, 0x6e, 0xd1, 0x12, 0xe5, 0x10, 0x27, 0xf2, 0xaf, 0x71, 0x79, 0xc8, 0xd4, 0x4f, 0xac, 0x6c, 0x49, 0x3c, 0x65, 0xd9, 0x56, 0x52, 0xb8, 0x36, 0x98, 0xdb, 0x95, 0x2d, 0xa4, 0xf4, 0x44, 0x0b, 0x15, 0x46, 0xbc, 0x82, 0x5b, 0x80, 0x1b, 0x49, 0xf1, 0x71, 0x6e, 0x65, 0x3d, 0xf7, 0x57, 0x43, 0xd9, 0x71, 0x50, 0xb6, 0xbd, 0x9f, 0xb2, 0x89, 0xac, 0x68, 0x65, 0xd7, 0x5a, 0x69, 0xc3, 0xd5, 0xc2, 0xb7, 0x2a, 0x0f, 0x90, 0xc2, 0xe3, 0x12, 0x38, 0x2a, 0x8a, 0xcd, 0xfc, 0x13, 0xb8, 0x96, + 0x14, 0x6e, 0x49, 0x73, 0x86, 0xde, 0xe3, 0x9f, 0xa0, 0x6d, 0x9f, 0x71, 0xe6, 0xf2, 0xe2, 0x76, 0x81, 0xbc, 0x6c, 0xd9, 0xf0, 0xbe, 0x3a, 0x7d, 0x96, 0x8d, 0x56, 0x56, 0x91, 0xc9, 0x43, 0xcb, 0x3a, 0x33, 0x79, 0xc9, 0xd3, 0xe5, 0x85, 0xa7, 0x5b, 0x5e, 0x05, 0xe0, 0x4c, 0x17, 0x04, 0xe6, 0x37, 0xd6, 0xc2, 0xb4, 0xf4, 0x42, 0xab, 0x72, 0xf7, 0x06, 0xa2, 0xf8, 0x99, 0xc0, 0x14, 0x2b, 0xeb, 0x92, 0xd2, 0xd3, 0x93, 0xb9, 0xab, 0x47, 0x87, 0xbe, 0xc6, 0xdf, 0xd7, 0x23, 0x43, 0x7d, 0x0e, 0xa8, 0x0f, 0x1c, 0x4e, 0x0d, 0x58, 0x9f, 0x03, 0x39, 0xfc, 0x7a, 0x7d, 0x2c, 0x06, 0x29, 0xc8, 0x27, 0xf4, 0xd3, 0x9f, 0x3d, 0xf9, 0x6a, 0x6d, 0x41, 0x1c, 0x47, 0x45, 0x67, 0xde, 0x4f, 0xd3, 0x58, 0x5d, 0x6f, 0x7e, 0x8a, 0x17, 0x1a, 0xeb, 0x8a, 0x03, 0xf9, 0xb4, 0x5a, 0x06, 0x92, 0x4f, + 0x32, 0x1a, 0xc9, 0x46, 0xf9, 0xa4, 0xf5, 0x54, 0x33, 0x01, 0x55, 0xab, 0x99, 0xfe, 0x05, 0xed, 0xbb, 0xb5, 0xac, 0x8e, 0x07, 0xb9, 0x1b, 0x90, 0xb1, 0x0e, 0x26, 0x47, 0x16, 0xf9, 0xd4, 0x32, 0x4a, 0x8b, 0xf6, 0xd0, 0xa2, 0x9f, 0x74, 0xca, 0x50, 0x72, 0xf7, 0x87, 0x54, 0x8e, 0xba, 0x71, 0x49, 0x7c, 0x02, 0x94, 0xbd, 0xf6, 0x18, 0x3f, 0xdf, 0x58, 0x76, 0x3c, 0x94, 0x1d, 0xd7, 0x4f, 0xd9, 0xf1, 0x28, 0x5e, 0x97, 0x23, 0x56, 0xb6, 0x26, 0xa5, 0xf7, 0x89, 0x02, 0x94, 0xbe, 0x48, 0xe2, 0xa9, 0x9c, 0xd6, 0x73, 0x8b, 0x24, 0x2b, 0x14, 0xbf, 0xda, 0x22, 0x2b, 0xeb, 0x05, 0xd5, 0x4e, 0x98, 0x76, 0xb6, 0xb2, 0x74, 0xd7, 0xe6, 0x77, 0x55, 0x55, 0x79, 0xa0, 0x36, 0xf4, 0x33, 0xf0, 0x0f, 0x04, 0x65, 0x9d, 0x23, 0x59, 0xfa, 0x95, 0x50, 0x96, 0x95, 0x59, 0x2a, 0x91, 0x4a, 0x70, + 0x01, 0x93, 0x25, 0xfe, 0xf1, 0xd0, 0x7b, 0x19, 0x79, 0xbe, 0x74, 0xfe, 0xc5, 0xda, 0xde, 0x93, 0xdc, 0xb7, 0x2d, 0xe8, 0x9c, 0xd5, 0x07, 0x7d, 0xf5, 0x8e, 0xb5, 0x28, 0x4d, 0xad, 0xaf, 0x16, 0xbb, 0xe9, 0x38, 0xff, 0x8f, 0xb2, 0x3e, 0x39, 0x9d, 0xd5, 0x77, 0x02, 0x7f, 0x84, 0x57, 0x18, 0xea, 0xfb, 0xc1, 0xb2, 0xfb, 0x71, 0x6a, 0xbe, 0x5a, 0xdb, 0x28, 0x8c, 0x68, 0x6d, 0xfe, 0xd7, 0xd4, 0xba, 0x1e, 0xfa, 0x80, 0x7b, 0xca, 0xf8, 0x6d, 0x3f, 0x50, 0x76, 0xd5, 0x6a, 0x2e, 0xfa, 0x1b, 0x1d, 0xab, 0x36, 0x56, 0xc7, 0xa5, 0xfc, 0x6b, 0xc8, 0x58, 0x87, 0x9d, 0xee, 0x1b, 0xc9, 0x3c, 0x3f, 0xb0, 0xe8, 0xd6, 0x9a, 0x44, 0x37, 0x89, 0x16, 0x7c, 0xd9, 0x1f, 0xa0, 0xe0, 0x22, 0x5c, 0xe2, 0xa4, 0x25, 0x77, 0x1c, 0x11, 0x1e, 0x67, 0x72, 0x35, 0xe5, 0x0c, 0xe5, 0xaa, 0x56, 0x93, + 0xab, 0xcd, 0xf7, 0x9c, 0x60, 0x4b, 0x64, 0xa8, 0x30, 0x94, 0x02, 0x0b, 0x24, 0x2d, 0x4b, 0x1d, 0xe7, 0xfc, 0x60, 0xae, 0x23, 0x1e, 0xfa, 0x1d, 0xf6, 0x9f, 0x58, 0x7f, 0xc3, 0x06, 0xd4, 0xa9, 0x05, 0xea, 0x2d, 0xbe, 0x3c, 0x3b, 0xa7, 0x9c, 0x53, 0xee, 0xbc, 0x07, 0xe7, 0xa9, 0x02, 0x95, 0x13, 0xf2, 0x67, 0x06, 0x02, 0x99, 0xc2, 0xc9, 0xc2, 0x5e, 0x8e, 0xff, 0x2a, 0xf8, 0xc3, 0xeb, 0x71, 0xa9, 0x82, 0xab, 0xd6, 0x53, 0x81, 0x53, 0xe8, 0xd0, 0xce, 0x07, 0xc1, 0xa5, 0xf5, 0x9c, 0x7c, 0x9f, 0xfb, 0x1d, 0x5e, 0xa5, 0xf7, 0xcf, 0x0f, 0xad, 0x0b, 0x84, 0x56, 0xad, 0xab, 0x19, 0x5b, 0xe9, 0x00, 0x3f, 0x0a, 0x42, 0xcb, 0xea, 0xda, 0x85, 0xdf, 0xe4, 0x0e, 0x9a, 0xeb, 0x72, 0x40, 0x5d, 0xf1, 0x71, 0x36, 0x6b, 0xec, 0xba, 0x8c, 0x02, 0x5b, 0xab, 0x0b, 0x6c, 0xbd, 0x2a, 0xb0, + 0x6a, 0x4d, 0xe3, 0xbe, 0x81, 0x8a, 0x3e, 0x7c, 0x4d, 0xad, 0x65, 0xf1, 0xbb, 0xfc, 0xe7, 0xa6, 0x3a, 0x6c, 0xe0, 0xe7, 0x89, 0xc6, 0x62, 0xa5, 0xfe, 0x2a, 0xd5, 0x39, 0x81, 0xa9, 0x32, 0x53, 0xeb, 0xb1, 0x21, 0x5b, 0xb2, 0x4f, 0x75, 0x26, 0x10, 0x11, 0x56, 0x65, 0xa8, 0x5a, 0xc3, 0x2c, 0xba, 0x64, 0xb3, 0xe2, 0xcb, 0x44, 0x2b, 0xf5, 0x9f, 0x7a, 0x86, 0x3a, 0x4a, 0x2e, 0xc0, 0xc0, 0x8f, 0x3e, 0x5b, 0x80, 0xa7, 0x30, 0xe2, 0xf2, 0xe6, 0x12, 0x5c, 0x52, 0x8c, 0x98, 0x2f, 0x56, 0x4d, 0xe6, 0x03, 0x41, 0x3f, 0x38, 0x27, 0xd5, 0xb8, 0x5c, 0xd4, 0xec, 0xb5, 0x23, 0x8b, 0x4b, 0xef, 0x78, 0x1f, 0xd1, 0x42, 0x75, 0xd5, 0x3e, 0xca, 0x72, 0x15, 0x65, 0xc7, 0x02, 0xbc, 0xc4, 0x93, 0xc5, 0xa4, 0x48, 0xb4, 0x28, 0xbf, 0x5c, 0x82, 0xdb, 0x12, 0xb1, 0xb1, 0xec, 0x78, 0xe8, 0xe7, + 0x38, 0x68, 0xa8, 0x08, 0xc5, 0xb3, 0x6d, 0x58, 0x93, 0xc6, 0x0e, 0xe8, 0x45, 0xe7, 0xd3, 0xa2, 0x35, 0x0a, 0xfd, 0x47, 0xe5, 0x18, 0x2d, 0x5c, 0x67, 0xd1, 0x21, 0xe5, 0xef, 0x50, 0x3c, 0x30, 0xe9, 0x48, 0xf9, 0x6c, 0x1c, 0xfb, 0x2d, 0x9f, 0x8c, 0x63, 0xa4, 0x7c, 0x2b, 0x2b, 0x5f, 0x5b, 0x13, 0x5e, 0x57, 0x7e, 0x4b, 0x2b, 0x30, 0x30, 0xe9, 0x6c, 0x45, 0xa1, 0x55, 0xa8, 0x74, 0xfa, 0x2c, 0xeb, 0xc1, 0xa2, 0xa9, 0x9e, 0x97, 0x95, 0x4f, 0x68, 0x3d, 0x06, 0x46, 0x3d, 0xf1, 0x1b, 0xa8, 0x46, 0x63, 0xd5, 0x67, 0x59, 0x4f, 0x6d, 0x54, 0x3d, 0xfb, 0x95, 0x7f, 0xb0, 0x7a, 0x74, 0x66, 0x7d, 0xd3, 0xe7, 0x50, 0x8d, 0xca, 0xae, 0x23, 0xdc, 0x3d, 0x1e, 0xf4, 0x68, 0x1c, 0x3f, 0x90, 0x1e, 0x85, 0x95, 0x34, 0x5f, 0xd7, 0xa3, 0xc0, 0x86, 0xc1, 0x8f, 0x86, 0xce, 0x85, 0x95, 0xdb, + 0xf0, 0x44, 0x1c, 0x8c, 0x10, 0xe1, 0xcd, 0x38, 0xad, 0x38, 0xda, 0x76, 0x22, 0x75, 0x68, 0xb6, 0x93, 0x1a, 0x77, 0xb8, 0x2f, 0x0f, 0xee, 0x6b, 0x3d, 0xb9, 0xa2, 0xad, 0x27, 0x6e, 0x37, 0xb5, 0x9e, 0x34, 0xae, 0xfa, 0xa0, 0x71, 0x2d, 0xb0, 0x0f, 0x58, 0xfe, 0xe9, 0x5a, 0x50, 0x84, 0xb4, 0x32, 0x0b, 0x8a, 0x10, 0xd6, 0x1f, 0x54, 0x47, 0x6d, 0xff, 0x56, 0x54, 0xb3, 0x6a, 0x44, 0x11, 0xd6, 0x7a, 0x76, 0x36, 0x8e, 0xcb, 0x60, 0xe3, 0x90, 0x2e, 0x21, 0x36, 0x0e, 0xf4, 0x48, 0x84, 0x5f, 0x9f, 0xf6, 0x3a, 0x1c, 0xa5, 0x42, 0x4d, 0x94, 0x95, 0x99, 0x1f, 0x44, 0xa8, 0x22, 0x8c, 0x95, 0x9a, 0x20, 0x0a, 0x97, 0x53, 0x6f, 0xa8, 0x8b, 0x7a, 0x69, 0x25, 0x55, 0x59, 0x07, 0xac, 0x8b, 0x88, 0x90, 0x79, 0xcd, 0x37, 0xd2, 0x55, 0x93, 0x31, 0x05, 0x6c, 0x55, 0x35, 0xa5, 0x08, 0x59, + 0xfd, 0x8f, 0xd8, 0x52, 0xdc, 0x5a, 0x66, 0x4b, 0x11, 0xa6, 0x7a, 0x0e, 0x6d, 0x29, 0xdc, 0xad, 0xdb, 0x52, 0xc0, 0x52, 0xcf, 0xc6, 0x96, 0xaa, 0xed, 0xd7, 0x96, 0xaa, 0x37, 0x98, 0x52, 0xc0, 0x50, 0x7f, 0xb8, 0xad, 0x43, 0x04, 0x1d, 0x6c, 0x1d, 0x6a, 0x97, 0xcd, 0x38, 0xa7, 0xfc, 0x34, 0xbd, 0xc8, 0x02, 0xf4, 0x3a, 0x03, 0x0f, 0x8a, 0xf0, 0x53, 0x6f, 0x32, 0xb7, 0x62, 0xb4, 0xe2, 0xe0, 0x33, 0x5b, 0xd0, 0x39, 0xab, 0x4f, 0x5b, 0xea, 0xd5, 0xfa, 0x54, 0xdb, 0x8a, 0xf1, 0x53, 0x5a, 0x1f, 0xd8, 0x56, 0x94, 0x9f, 0xce, 0x38, 0x87, 0xfc, 0x54, 0xad, 0x4d, 0xb5, 0xad, 0x80, 0x9f, 0xb2, 0xba, 0xde, 0xfc, 0x94, 0xf1, 0xd3, 0x73, 0x65, 0x5b, 0xb1, 0x6a, 0x98, 0x6d, 0x85, 0xdb, 0x58, 0x1d, 0x0f, 0xaa, 0xfc, 0xf4, 0x4c, 0x6d, 0x2b, 0x33, 0x41, 0x4d, 0x90, 0x22, 0xb6, 0x15, + 0x21, 0xa8, 0xf1, 0x2e, 0x28, 0x7a, 0xed, 0x31, 0x4a, 0x50, 0xcf, 0xdc, 0xee, 0xa9, 0x8d, 0xb2, 0x7b, 0x9a, 0xa9, 0xd9, 0x03, 0x06, 0xb9, 0xd1, 0x0e, 0xa1, 0x5c, 0x0e, 0xc6, 0xf9, 0x6c, 0xf8, 0xa9, 0xca, 0xd5, 0x33, 0x70, 0x69, 0x84, 0x9f, 0x52, 0xc2, 0x1e, 0xf2, 0x08, 0xde, 0xe0, 0x0f, 0xaf, 0xc7, 0x15, 0xd3, 0xb0, 0x62, 0xfc, 0x54, 0x37, 0xac, 0x34, 0x7e, 0x7a, 0x0e, 0xea, 0xea, 0x6b, 0x54, 0x31, 0x7e, 0xaa, 0x1b, 0x55, 0x1a, 0x3f, 0x9d, 0x66, 0xe6, 0xa7, 0xfd, 0x71, 0xe1, 0xd8, 0xfc, 0x34, 0xb6, 0x41, 0xf5, 0x61, 0xc4, 0x9e, 0x02, 0x82, 0x1a, 0xa9, 0xc3, 0x06, 0xbc, 0x4f, 0xa6, 0x21, 0xf9, 0xce, 0x88, 0x9c, 0x9a, 0x0c, 0x29, 0xd5, 0x8c, 0x12, 0xad, 0xe8, 0xec, 0xf6, 0x63, 0x78, 0xc6, 0x4f, 0xdf, 0xdf, 0x80, 0xe7, 0xab, 0xab, 0xce, 0xaa, 0xd1, 0xdc, 0xf2, 0x7a, + 0xf3, 0xbc, 0x1a, 0x98, 0x9f, 0xc6, 0x21, 0x6b, 0x84, 0x9f, 0xfa, 0xc9, 0x60, 0x0e, 0xc1, 0x8c, 0xa0, 0x7e, 0xa1, 0xec, 0xd8, 0x80, 0x2f, 0xb3, 0x14, 0xa5, 0x6b, 0x04, 0x35, 0xf4, 0xe0, 0x68, 0x6e, 0x45, 0xb2, 0x17, 0x23, 0x64, 0xde, 0x7f, 0x39, 0x15, 0xe7, 0x2a, 0x30, 0x71, 0x54, 0xb8, 0xed, 0xa6, 0x92, 0xd4, 0xaf, 0x94, 0x43, 0xac, 0x06, 0x9d, 0xa5, 0xfe, 0x2d, 0xf4, 0x4b, 0x5a, 0x07, 0xdd, 0xf0, 0x35, 0xea, 0x3d, 0x90, 0x9d, 0x7e, 0xeb, 0x20, 0xb2, 0x53, 0x60, 0xe2, 0xa9, 0xec, 0x46, 0x1d, 0x5b, 0x16, 0xfe, 0xa8, 0x9c, 0x60, 0x95, 0x44, 0x98, 0x2a, 0xfe, 0x2e, 0xf4, 0x35, 0xab, 0x46, 0xa3, 0xaa, 0xe7, 0xa8, 0xae, 0x5f, 0x2a, 0x1f, 0xb3, 0xba, 0x0c, 0x6c, 0x35, 0x53, 0x71, 0xd0, 0xba, 0x8c, 0x7c, 0xf5, 0x2c, 0xea, 0xaa, 0x8d, 0xae, 0xeb, 0x35, 0xe5, 0x33, 0xb5, + 0x2e, 0x9d, 0xb1, 0x8e, 0x56, 0xb2, 0x69, 0x55, 0x3a, 0x67, 0x3d, 0x1b, 0x7b, 0xc7, 0x65, 0xb0, 0x77, 0xf8, 0x79, 0x60, 0xef, 0xb0, 0x3d, 0xb0, 0x45, 0xa6, 0xbd, 0xc8, 0x7e, 0x74, 0xa8, 0x61, 0x2f, 0x92, 0xf0, 0x5e, 0x0c, 0x52, 0xaf, 0xd2, 0x5e, 0xe5, 0x36, 0x42, 0xaf, 0x45, 0x3c, 0x51, 0xa7, 0xbd, 0xca, 0x2b, 0x84, 0x5e, 0x0b, 0xb8, 0xa0, 0xd8, 0x64, 0x4b, 0x91, 0x35, 0x40, 0x93, 0xd5, 0x7e, 0x79, 0x6f, 0x9e, 0x2b, 0xb2, 0x06, 0x30, 0x7b, 0xca, 0xd5, 0xd7, 0x9e, 0xe2, 0x5f, 0x55, 0xed, 0xa9, 0x61, 0x26, 0x7b, 0x0a, 0xb8, 0x97, 0xde, 0xdf, 0xfd, 0x73, 0xaf, 0x18, 0x66, 0x95, 0x2b, 0x86, 0x59, 0xc5, 0xed, 0xd6, 0xcd, 0x2a, 0x42, 0x28, 0x8d, 0x76, 0x08, 0xac, 0x9b, 0xa7, 0xaa, 0xe7, 0x4c, 0xcc, 0x2b, 0xc2, 0x3e, 0x0c, 0xe6, 0x95, 0xc6, 0xb5, 0xcf, 0xbe, 0xbe, 0xda, + 0x53, 0x98, 0x59, 0xcd, 0x06, 0x2b, 0x8b, 0xad, 0x4d, 0xd1, 0xbc, 0x7e, 0xe0, 0x75, 0xda, 0x95, 0xa7, 0x8f, 0x11, 0x28, 0x73, 0xcd, 0xfe, 0x01, 0x9e, 0x4c, 0xcc, 0x9f, 0xa0, 0x2a, 0x07, 0x94, 0x24, 0x6f, 0xe6, 0xc6, 0xd5, 0xc7, 0xb0, 0x7d, 0x06, 0xe6, 0xad, 0xe0, 0xfe, 0x4f, 0x2d, 0x5f, 0x66, 0xe6, 0x95, 0xca, 0x8d, 0x23, 0xc6, 0x15, 0x10, 0x63, 0x62, 0x5a, 0x81, 0xf4, 0xfe, 0x47, 0x6c, 0x2b, 0xdc, 0x4d, 0x6d, 0x2b, 0x8d, 0xbb, 0x9e, 0x1b, 0xbb, 0xc7, 0x64, 0x5b, 0xd5, 0xab, 0xa6, 0x15, 0x21, 0xb0, 0x3f, 0xdc, 0xee, 0x81, 0x5d, 0xe1, 0x25, 0x6f, 0xb2, 0xfd, 0xe0, 0x73, 0x60, 0xf7, 0x88, 0x3a, 0x7d, 0x65, 0xa6, 0x42, 0x3a, 0x1e, 0x16, 0x61, 0xaf, 0x60, 0x2e, 0x7c, 0xc3, 0x4f, 0x6c, 0x39, 0x67, 0x36, 0x96, 0xc6, 0x02, 0x4c, 0x36, 0x16, 0x63, 0xae, 0x9a, 0x8d, 0x45, + 0x88, 0xab, 0xc9, 0x86, 0xf8, 0xa1, 0x76, 0x16, 0x61, 0x02, 0x26, 0x3b, 0x0b, 0x98, 0xab, 0x6a, 0x67, 0x11, 0xe2, 0xfa, 0x1f, 0xb1, 0xb3, 0x40, 0x5d, 0x81, 0x9d, 0x45, 0x58, 0x6b, 0xa4, 0x7c, 0x2b, 0x8c, 0xb7, 0xa5, 0x9f, 0xf1, 0xb6, 0x92, 0xc5, 0x3b, 0xc2, 0x2b, 0x0d, 0x56, 0xd6, 0x32, 0xee, 0xaf, 0x94, 0xae, 0x82, 0x89, 0xf5, 0x12, 0x7e, 0x43, 0xe5, 0xaa, 0x33, 0xce, 0x96, 0xab, 0xaa, 0x36, 0x50, 0x3d, 0x35, 0x81, 0x74, 0x7b, 0xea, 0x5c, 0x70, 0x55, 0x95, 0xb7, 0x27, 0xe2, 0xc1, 0x11, 0xae, 0xca, 0x8c, 0x9f, 0x1c, 0xa1, 0x23, 0xf8, 0xc3, 0xeb, 0x71, 0xc5, 0x34, 0xb2, 0x18, 0x57, 0xd5, 0x8d, 0x2c, 0x8d, 0xab, 0xce, 0x38, 0x37, 0x5c, 0xd5, 0x6c, 0x60, 0x31, 0xae, 0xaa, 0x1b, 0x58, 0x1a, 0x57, 0x9d, 0x71, 0x2e, 0xb8, 0xaa, 0xd9, 0xb8, 0xfa, 0x30, 0x62, 0x5b, 0x31, + 0xae, 0x3a, 0xe3, 0x07, 0x72, 0x55, 0x93, 0x51, 0xa5, 0x9a, 0x54, 0x1a, 0x57, 0x9d, 0x76, 0x96, 0x5c, 0xf5, 0xdd, 0xcd, 0xb8, 0x5b, 0xd5, 0x14, 0x3f, 0xab, 0xe5, 0x0f, 0xb4, 0x98, 0xcf, 0x28, 0xce, 0x92, 0xab, 0xfe, 0x4d, 0x79, 0x88, 0x14, 0x2b, 0x95, 0x66, 0x6a, 0x5c, 0xb5, 0xf7, 0xd2, 0x5a, 0xfe, 0xc5, 0x74, 0x1f, 0x8e, 0x65, 0x17, 0x9c, 0x0d, 0x57, 0x0d, 0x23, 0xe5, 0x05, 0x56, 0x83, 0xce, 0x55, 0x3f, 0xe8, 0x7d, 0x88, 0xd6, 0xa1, 0x72, 0xd5, 0x69, 0xe7, 0x80, 0x3f, 0x7e, 0xa6, 0x1c, 0x67, 0x95, 0x18, 0xb8, 0xea, 0x87, 0xbd, 0x27, 0x58, 0x35, 0x06, 0xae, 0x3a, 0xed, 0x9c, 0xf0, 0xe2, 0xdf, 0xb2, 0xba, 0x22, 0x5c, 0x95, 0xfb, 0xb6, 0xf7, 0x24, 0xad, 0xcb, 0xc8, 0x55, 0xa7, 0x9d, 0x03, 0xae, 0xfa, 0xb6, 0xf2, 0xb1, 0x5a, 0x97, 0xce, 0x55, 0xd3, 0x43, 0x29, 0xb4, + 0x2a, 0x9d, 0xab, 0x9e, 0x8d, 0xed, 0xe3, 0x32, 0xd8, 0x3e, 0x64, 0x35, 0x00, 0xdb, 0xc7, 0xb4, 0x7f, 0x71, 0xda, 0x67, 0xf2, 0x32, 0xac, 0x29, 0x8c, 0xab, 0x7e, 0xa8, 0xdc, 0x45, 0x78, 0xb5, 0x9c, 0xa4, 0x51, 0xd5, 0x63, 0xa1, 0xb5, 0x84, 0x54, 0xbb, 0x9d, 0xc0, 0x54, 0xfb, 0xec, 0x57, 0x0c, 0xcc, 0x55, 0xc9, 0x3a, 0x9f, 0x1f, 0xe1, 0xaa, 0x9a, 0xbc, 0xba, 0xfa, 0xda, 0x56, 0xfc, 0xab, 0x9a, 0x6d, 0x35, 0x0c, 0xf5, 0x3d, 0xdb, 0x3e, 0x0d, 0x5e, 0x17, 0x4b, 0x6c, 0x5d, 0xb1, 0x4c, 0x2c, 0x6e, 0x77, 0xc4, 0xc4, 0x62, 0x7b, 0xa0, 0xe6, 0xbd, 0xa5, 0x53, 0xd5, 0x75, 0x66, 0xa6, 0x16, 0x5e, 0x61, 0x32, 0xb5, 0x62, 0xec, 0x9d, 0x9d, 0x49, 0x7d, 0xb5, 0xa7, 0x34, 0xb7, 0x9a, 0x4d, 0xd6, 0x16, 0xe3, 0xad, 0x3f, 0xd4, 0x06, 0xe2, 0xfc, 0x60, 0x03, 0xb1, 0xbd, 0xaa, 0x45, + 0x67, 0x7a, 0x46, 0xcf, 0x4b, 0x44, 0xb2, 0xd4, 0x2d, 0x62, 0xd9, 0xa5, 0xdc, 0xb1, 0x00, 0xcf, 0xc3, 0x17, 0x6b, 0xd4, 0x37, 0xc1, 0xa1, 0x1c, 0x5a, 0x82, 0x1b, 0xb9, 0x4b, 0xeb, 0xcf, 0xca, 0xbe, 0x92, 0xa1, 0x6c, 0xa2, 0xaa, 0x29, 0xf1, 0x95, 0x04, 0x28, 0x5c, 0x33, 0xb0, 0x08, 0xf5, 0x95, 0x78, 0x28, 0x1c, 0x0c, 0x2c, 0x4a, 0x80, 0xff, 0x23, 0x36, 0x16, 0xb7, 0x96, 0xd9, 0x58, 0x74, 0x5f, 0xf8, 0x3f, 0x65, 0x63, 0xe1, 0x6e, 0xdd, 0xc6, 0x22, 0xe4, 0xf5, 0x07, 0xd9, 0x3c, 0xa7, 0xb0, 0xb1, 0xea, 0x8d, 0x26, 0x16, 0xe5, 0xdc, 0xe7, 0xc4, 0xe6, 0x01, 0xf6, 0x41, 0xac, 0x92, 0x06, 0xdc, 0x15, 0x21, 0xc8, 0x9b, 0xf9, 0x0d, 0x2d, 0xe7, 0xa2, 0x7c, 0x8d, 0x71, 0x44, 0x6c, 0x2a, 0x46, 0x8a, 0x89, 0x4d, 0xa5, 0xf1, 0xe1, 0x73, 0x61, 0x57, 0x11, 0xa6, 0x11, 0xb1, 0xab, + 0x80, 0x08, 0x13, 0xbb, 0x0a, 0x66, 0x04, 0x8a, 0x71, 0x2e, 0x36, 0x80, 0x5c, 0x91, 0x41, 0x8f, 0xb6, 0x7b, 0xb2, 0xf9, 0x88, 0x5d, 0x95, 0xe9, 0xe1, 0x54, 0xd6, 0x4a, 0x0c, 0xab, 0x44, 0x8e, 0xb2, 0xd6, 0x33, 0xb7, 0x7b, 0x6a, 0xa3, 0xec, 0x9e, 0x22, 0x30, 0x7b, 0x54, 0xfe, 0xbb, 0xf0, 0xb4, 0x39, 0x57, 0x7f, 0x06, 0x0f, 0x50, 0x56, 0xc6, 0xd9, 0x13, 0x71, 0x67, 0x84, 0xb1, 0x02, 0x6f, 0xff, 0xa7, 0xb0, 0x2e, 0xf8, 0x83, 0xeb, 0x70, 0xc5, 0x32, 0xaa, 0x18, 0x5b, 0x55, 0x8d, 0x2a, 0x8d, 0xab, 0xfe, 0x90, 0x7a, 0xfa, 0x31, 0xa8, 0x18, 0x53, 0xd5, 0x0c, 0x2a, 0xee, 0x60, 0x94, 0x3d, 0x05, 0x67, 0xd1, 0xf6, 0xfe, 0xce, 0xfc, 0x8d, 0x86, 0x54, 0xed, 0x80, 0x86, 0xd4, 0x87, 0xba, 0x1d, 0x45, 0x48, 0xaa, 0xd1, 0xce, 0x61, 0xdc, 0xaf, 0x3f, 0x8e, 0x4a, 0x4c, 0x9d, 0x24, + 0x9d, 0xa3, 0x6a, 0x16, 0x94, 0x32, 0xd4, 0x64, 0x40, 0x31, 0xf3, 0x09, 0x18, 0xea, 0x99, 0xdf, 0x1b, 0xd1, 0xf8, 0xe9, 0x89, 0x7b, 0xf0, 0x22, 0x75, 0x60, 0x53, 0x0a, 0x05, 0x25, 0x68, 0xbc, 0x93, 0x10, 0x07, 0x7d, 0x20, 0xe1, 0x33, 0xa6, 0xa7, 0x1f, 0x2b, 0x0f, 0xdc, 0x83, 0x17, 0x12, 0xd1, 0xd1, 0xe8, 0xe9, 0xc9, 0xf2, 0x42, 0xe1, 0x64, 0x66, 0x3e, 0x36, 0xdd, 0xa9, 0xc8, 0x0b, 0xfa, 0xe2, 0x25, 0xb5, 0x9d, 0x03, 0x52, 0xd3, 0x02, 0x13, 0x35, 0xfd, 0x46, 0x79, 0x86, 0x15, 0xae, 0x33, 0xd3, 0x77, 0x4f, 0x2e, 0xa1, 0xc5, 0xb3, 0x6b, 0xb3, 0xa6, 0x3b, 0x22, 0xa4, 0x0e, 0x47, 0x7f, 0x75, 0x0c, 0xb8, 0xb6, 0x7f, 0xa3, 0x1c, 0x63, 0xb5, 0x18, 0xa8, 0xe9, 0x9b, 0x27, 0x77, 0xb1, 0x7a, 0x18, 0x35, 0x3d, 0x27, 0xf5, 0xfc, 0x4d, 0x79, 0x8b, 0xd5, 0x63, 0xa0, 0xa5, 0xbf, + 0x3b, 0xf9, 0x7b, 0x5a, 0x8f, 0x4a, 0x4b, 0xcf, 0xa6, 0x9e, 0xda, 0xbe, 0x5c, 0xe5, 0x7d, 0xb5, 0x1e, 0x8d, 0x92, 0xf2, 0xff, 0xea, 0xe5, 0x68, 0x35, 0xda, 0x85, 0xda, 0xb3, 0xb3, 0x71, 0x5c, 0x06, 0x1b, 0x87, 0x4c, 0x5c, 0xb0, 0x71, 0xc8, 0x94, 0x35, 0xda, 0x38, 0xbe, 0x60, 0x96, 0x5d, 0x8a, 0x79, 0x47, 0xca, 0xb4, 0xbc, 0xd3, 0x06, 0x33, 0x4e, 0xfa, 0x07, 0xe5, 0x6e, 0x52, 0x22, 0xef, 0xb2, 0x6b, 0xa4, 0xf4, 0x48, 0x6f, 0x07, 0x61, 0xcf, 0x0e, 0x8f, 0x85, 0xed, 0x9f, 0x4e, 0x3b, 0x17, 0x9c, 0x34, 0xca, 0x86, 0xe2, 0x5f, 0xd5, 0x6c, 0xa8, 0x61, 0x51, 0x36, 0xd4, 0x0f, 0xe4, 0xa4, 0x7d, 0x4c, 0x29, 0x6e, 0x77, 0xc4, 0x94, 0x62, 0x9c, 0x74, 0xda, 0xb9, 0xe3, 0xa4, 0x7d, 0x4c, 0x2a, 0xc2, 0x49, 0x8d, 0x26, 0x15, 0xe5, 0xa4, 0xd3, 0xce, 0x1d, 0x27, 0xed, 0x63, 0x56, + 0x35, 0x9b, 0xac, 0x2a, 0xc6, 0x49, 0x7f, 0xa8, 0xad, 0x43, 0x14, 0x35, 0xd8, 0x3a, 0x44, 0x43, 0x9f, 0xf9, 0xdd, 0x4e, 0xc6, 0x49, 0x35, 0x6b, 0xe7, 0x37, 0x3c, 0x21, 0x8e, 0x84, 0x44, 0xf3, 0x32, 0x9e, 0xad, 0xf1, 0x52, 0x8b, 0x18, 0xba, 0x8e, 0x90, 0x68, 0x8b, 0x95, 0x5b, 0x54, 0x8f, 0x62, 0x9c, 0x9f, 0x9e, 0xbe, 0x3d, 0xe5, 0xea, 0x63, 0x4f, 0xf1, 0xf3, 0x22, 0xf6, 0x54, 0x8c, 0xbb, 0xb5, 0xe7, 0xc8, 0x9e, 0xe2, 0xd6, 0xaa, 0xf6, 0x54, 0xd4, 0xdd, 0xda, 0x73, 0x6f, 0x4f, 0xe1, 0xee, 0x88, 0x3d, 0x65, 0xbe, 0x6b, 0x7b, 0x4e, 0x64, 0x29, 0xca, 0x9e, 0xaa, 0x37, 0x99, 0x53, 0x6c, 0x1f, 0xee, 0x4c, 0xed, 0x9b, 0xda, 0x28, 0xfb, 0xe6, 0x43, 0x30, 0x6f, 0xd4, 0xbb, 0x77, 0x66, 0x1b, 0xc4, 0x6a, 0x39, 0xa5, 0x0d, 0x42, 0x08, 0x85, 0x6e, 0xe0, 0xfc, 0x8a, 0x4b, 0x88, + 0x53, 0x36, 0x2f, 0xc0, 0x17, 0xe3, 0x79, 0x1a, 0xd1, 0x75, 0xa6, 0x59, 0xc0, 0x0a, 0xa9, 0xe5, 0x9f, 0x68, 0x41, 0x3f, 0xc4, 0x7e, 0xa2, 0x6c, 0xc5, 0x92, 0x08, 0xa5, 0xab, 0xf6, 0x13, 0xd0, 0x5c, 0x77, 0x9c, 0x6a, 0x3f, 0x51, 0x9b, 0x7f, 0x91, 0x69, 0x1e, 0xf4, 0xd3, 0x76, 0xe3, 0x3c, 0x90, 0x69, 0xdb, 0x55, 0x03, 0xea, 0x63, 0x5e, 0x14, 0xa0, 0xf8, 0x25, 0x3c, 0x9e, 0xac, 0x92, 0x5c, 0x5e, 0xe4, 0xa8, 0x09, 0xc5, 0xe1, 0x22, 0x8d, 0xeb, 0xfe, 0x07, 0x6c, 0x28, 0xdc, 0xc6, 0x6c, 0x28, 0xba, 0xe7, 0xbb, 0xe8, 0x4c, 0x65, 0x27, 0x62, 0xdb, 0x68, 0x36, 0x14, 0x6f, 0xb2, 0xa1, 0x22, 0x94, 0x5a, 0x35, 0xa2, 0x54, 0x5a, 0x1d, 0xe3, 0x2d, 0x0a, 0xd8, 0x05, 0xea, 0x37, 0xc0, 0x93, 0xe9, 0x08, 0xb3, 0xeb, 0xdf, 0xb2, 0x01, 0x32, 0x4c, 0xec, 0x82, 0x6a, 0x7c, 0x51, 0x84, + 0x09, 0x6f, 0x16, 0x7e, 0x16, 0x44, 0xe7, 0xa0, 0x7c, 0x57, 0x1f, 0xcb, 0x89, 0xb1, 0x60, 0x62, 0x39, 0x01, 0x03, 0x3e, 0x27, 0xe5, 0x9b, 0x2d, 0x27, 0xc6, 0x7e, 0x89, 0xe5, 0x04, 0xc4, 0xb7, 0xef, 0x3b, 0xa6, 0x7e, 0xcb, 0x37, 0x59, 0x4d, 0xb5, 0xfd, 0x59, 0x4d, 0x1f, 0x52, 0xa3, 0x09, 0xe6, 0x19, 0xbc, 0x9f, 0x0b, 0xa3, 0x70, 0x8e, 0x74, 0x21, 0x66, 0xef, 0xe7, 0x66, 0xb0, 0xe8, 0xd6, 0x33, 0xe0, 0xe5, 0x25, 0x0e, 0xfd, 0x23, 0xbc, 0x83, 0x03, 0x7f, 0xc3, 0x56, 0xe6, 0x03, 0x59, 0x0f, 0x0b, 0xca, 0x1e, 0xc3, 0x89, 0xfa, 0x63, 0x38, 0xe5, 0xcd, 0xd4, 0xd6, 0xfc, 0x79, 0xfc, 0xb8, 0xf8, 0x9a, 0xbc, 0xa5, 0x09, 0x25, 0x39, 0xe2, 0x9f, 0x53, 0xf2, 0x68, 0xd9, 0xea, 0xfb, 0x3a, 0x4e, 0x92, 0x46, 0xd0, 0x77, 0x5b, 0x6a, 0x79, 0xe4, 0xef, 0x7f, 0xf9, 0x4f, 0xbc, 0xeb, + 0xc4, 0x19, 0xc2, 0x46, 0xee, 0xb0, 0xb8, 0x8b, 0x93, 0x10, 0x8b, 0x21, 0x90, 0xcd, 0x6d, 0xe3, 0x0a, 0xf9, 0x5f, 0xf2, 0xa4, 0x2c, 0x84, 0x98, 0xaf, 0x38, 0x6c, 0xf0, 0x15, 0x87, 0x33, 0xb9, 0x6d, 0x82, 0x00, 0xbe, 0xe4, 0xd0, 0xa1, 0x58, 0xbe, 0xe4, 0x14, 0x41, 0x59, 0xa9, 0xbc, 0x19, 0x7e, 0x08, 0xa2, 0x12, 0x0c, 0xf8, 0x36, 0x2e, 0xd6, 0x1b, 0xba, 0x18, 0x6f, 0xed, 0x62, 0xbd, 0xc9, 0x8b, 0xf9, 0x76, 0xaf, 0xcf, 0x1b, 0x3f, 0x32, 0x16, 0x56, 0x6e, 0x7f, 0xf8, 0xe7, 0xfc, 0x03, 0xc8, 0x8b, 0xaa, 0x82, 0x15, 0x12, 0x3c, 0xf1, 0x47, 0xd3, 0xfa, 0x3e, 0xf5, 0xa7, 0xb1, 0x36, 0xfb, 0xbe, 0xf4, 0xef, 0x2f, 0x00, 0xa6, 0xf2, 0x89, 0xf6, 0x70, 0xb7, 0x68, 0x54, 0x6b, 0x7d, 0x9a, 0xf6, 0x6e, 0xb7, 0x78, 0x64, 0x6b, 0x9d, 0xf6, 0x6e, 0x37, 0xc3, 0x9f, 0xc1, 0xde, 0xec, + 0x92, 0x3f, 0xa8, 0xfe, 0x03, 0x85, 0x5c, 0xe9, 0x06, 0x2e, 0x80, 0x96, 0x80, 0x32, 0x17, 0x1f, 0x42, 0xf7, 0xd2, 0x39, 0x56, 0x85, 0x8a, 0xb9, 0x77, 0x71, 0x1d, 0xe9, 0xab, 0x46, 0xf6, 0xf4, 0xda, 0x89, 0x98, 0x07, 0x80, 0x59, 0x1c, 0x13, 0x4f, 0x2f, 0xb8, 0xe2, 0xa6, 0x32, 0xc4, 0x63, 0x4c, 0x23, 0xbf, 0xc1, 0xb3, 0x73, 0x1e, 0x1b, 0xe2, 0x5b, 0xe6, 0x43, 0x3c, 0x0a, 0x08, 0xbc, 0xf8, 0xee, 0x0d, 0xca, 0xdc, 0x1b, 0xb8, 0x47, 0x8b, 0xb7, 0x6c, 0xc1, 0x71, 0xff, 0xc9, 0xb2, 0xff, 0x1f, 0x78, 0xd7, 0x18, 0x91, 0x85, 0xf6, 0xfe, 0xdf, 0x3a, 0x92, 0xbc, 0x4d, 0xe2, 0xd6, 0xf0, 0x7e, 0xf9, 0x65, 0x74, 0x09, 0x7a, 0xb6, 0xf7, 0x5f, 0xf8, 0xbc, 0x96, 0xe9, 0xb5, 0x5e, 0xf2, 0xe7, 0xc3, 0xa1, 0x0b, 0xb8, 0x41, 0xe5, 0x2f, 0x94, 0x6f, 0xc5, 0x23, 0xc9, 0x9f, 0x2f, 0xe4, + 0x37, 0xc1, 0x9f, 0x91, 0x97, 0xfa, 0x56, 0x3c, 0x4f, 0xf5, 0xad, 0xc8, 0x0d, 0x52, 0x63, 0xaf, 0xf1, 0x9b, 0x98, 0x4f, 0xc6, 0x50, 0xa9, 0xf8, 0x52, 0xf8, 0xb0, 0xfc, 0x10, 0x2a, 0x80, 0xb7, 0xcb, 0xe1, 0xfd, 0x98, 0xbd, 0x61, 0xde, 0x4b, 0x3d, 0xc4, 0xbe, 0x10, 0xde, 0xaf, 0x46, 0x3f, 0xdb, 0xab, 0xcd, 0x93, 0x50, 0x83, 0xf2, 0x8b, 0xf0, 0xe3, 0xe1, 0x97, 0x30, 0x7d, 0xd7, 0xfc, 0x7d, 0x23, 0x9b, 0x47, 0xdf, 0x37, 0xea, 0xbf, 0xef, 0x16, 0x2e, 0x0e, 0x67, 0x13, 0x3d, 0x22, 0xe3, 0x5f, 0xeb, 0xdf, 0xfa, 0xb8, 0xf8, 0x45, 0xb8, 0x50, 0xce, 0x23, 0xd8, 0xdb, 0x3a, 0xf6, 0xbd, 0xb2, 0x59, 0xf9, 0x6b, 0x78, 0x0d, 0xc1, 0xfe, 0xac, 0xc9, 0x7d, 0xef, 0xaf, 0xc4, 0x2f, 0xd0, 0x56, 0x39, 0x9f, 0xd9, 0x57, 0x5a, 0xcc, 0x4b, 0x2d, 0x3a, 0x23, 0x0b, 0x58, 0x48, 0xd7, 0x1f, + 0x87, 0x6b, 0x18, 0xac, 0x3f, 0x6e, 0xea, 0xa3, 0x03, 0x7c, 0xf3, 0xd4, 0x0f, 0xe1, 0xab, 0xb7, 0x2a, 0x2f, 0xba, 0x32, 0x3c, 0xb7, 0x60, 0x65, 0x2c, 0x97, 0x78, 0xbd, 0xd5, 0x95, 0x94, 0xee, 0x92, 0xf3, 0x69, 0xd0, 0xf0, 0xe5, 0xf7, 0x43, 0xd4, 0xf0, 0x33, 0x78, 0xcb, 0xac, 0xa4, 0x0a, 0xeb, 0x88, 0xae, 0x82, 0xf7, 0xd7, 0xaf, 0x46, 0x30, 0xf1, 0x03, 0xe5, 0x6b, 0xe9, 0x24, 0xa6, 0x18, 0x3e, 0x5f, 0x8d, 0xfc, 0x7a, 0xbe, 0xe6, 0xaf, 0xa0, 0xd7, 0x45, 0xbe, 0xf1, 0x00, 0x69, 0x7b, 0x3c, 0x8b, 0x37, 0x6a, 0x0a, 0x9c, 0x39, 0x9d, 0x06, 0x14, 0x70, 0xfb, 0x83, 0xe0, 0x62, 0x84, 0xf7, 0x79, 0xe8, 0x69, 0x1a, 0xf5, 0x42, 0xe1, 0x1b, 0xa9, 0x5c, 0x8c, 0x37, 0x7a, 0xbc, 0x2e, 0xe5, 0xd5, 0x5f, 0xe6, 0xe6, 0x25, 0x5e, 0x8f, 0x15, 0xf1, 0x4b, 0x2c, 0x39, 0x71, 0xb6, 0xdd, + 0x73, 0x3f, 0x2b, 0xf7, 0x17, 0xa4, 0x4f, 0x6a, 0xf5, 0x72, 0x63, 0xc6, 0x21, 0x75, 0xe7, 0x40, 0xb9, 0x6e, 0xea, 0xba, 0x04, 0x36, 0xb7, 0xb2, 0x38, 0x4f, 0x2d, 0x94, 0xf9, 0x92, 0x92, 0xc7, 0x35, 0x38, 0x93, 0x13, 0x13, 0x2c, 0xe2, 0x17, 0x92, 0x73, 0xd7, 0x72, 0x5e, 0x8e, 0xa7, 0xfe, 0x4a, 0x7a, 0xaf, 0x23, 0x6d, 0xfd, 0x22, 0xd2, 0x56, 0x2d, 0x36, 0xea, 0xb4, 0x48, 0x6c, 0xd4, 0x7c, 0x7f, 0x02, 0x6b, 0xab, 0xac, 0x45, 0x6c, 0xf7, 0x39, 0x15, 0xff, 0x51, 0xe8, 0xe0, 0x8f, 0xac, 0x2e, 0x77, 0x9a, 0x8b, 0x6b, 0x90, 0x9e, 0xd9, 0xa9, 0x45, 0x64, 0x27, 0x65, 0x9e, 0xbc, 0x9f, 0xe8, 0xe8, 0x35, 0x44, 0x47, 0x67, 0x42, 0xfc, 0x45, 0xb7, 0xc8, 0xa9, 0x9b, 0x35, 0xaa, 0x87, 0x95, 0x4b, 0x1b, 0xfc, 0x75, 0x39, 0x34, 0xd6, 0x2b, 0x6d, 0x25, 0x2d, 0x55, 0xef, 0x84, 0x7a, + 0x08, 0x30, 0x4d, 0xd8, 0xe7, 0x1a, 0x68, 0xf4, 0xbb, 0x7f, 0x24, 0x95, 0xdc, 0xaf, 0xf7, 0xc8, 0x23, 0x8f, 0xd0, 0xa1, 0xfc, 0x13, 0xf9, 0xa3, 0x64, 0x93, 0x9c, 0xff, 0x7d, 0xcc, 0x61, 0x61, 0xdd, 0xf3, 0xda, 0x6b, 0x50, 0xb3, 0xdd, 0x03, 0xdf, 0x93, 0x49, 0xea, 0x0e, 0x91, 0xba, 0xb3, 0xa1, 0xee, 0x54, 0x4b, 0x74, 0xdd, 0x85, 0xb5, 0x81, 0x2a, 0xa8, 0x9b, 0x67, 0xb5, 0xaa, 0xd5, 0xbb, 0x59, 0xad, 0x6a, 0xf5, 0xa3, 0x5d, 0x19, 0x29, 0x9b, 0x3c, 0x69, 0xac, 0x7a, 0xf8, 0x46, 0xb5, 0xd2, 0x2d, 0x5b, 0xe9, 0x1f, 0xdf, 0x97, 0x72, 0xa0, 0x62, 0xa8, 0x9e, 0x7d, 0xb2, 0xdd, 0xf3, 0xd2, 0x4b, 0xec, 0xd3, 0x49, 0xfd, 0xdf, 0xf0, 0x8d, 0xe1, 0xbf, 0x08, 0x7e, 0xa2, 0xaf, 0x6f, 0x66, 0xde, 0xfc, 0x92, 0xc1, 0xd5, 0x02, 0x12, 0xd0, 0x4c, 0xea, 0xcc, 0x9d, 0x30, 0x36, 0x9e, + 0x87, 0x57, 0xe6, 0x2c, 0x3a, 0x5e, 0xaa, 0x01, 0xec, 0x04, 0xa5, 0x46, 0x1d, 0xef, 0xcd, 0xa7, 0x5e, 0x6e, 0xf3, 0xfa, 0xcf, 0x4c, 0x46, 0x8a, 0x7a, 0x5b, 0x01, 0xa0, 0x8f, 0xbb, 0x15, 0x70, 0x5c, 0x0b, 0x91, 0x5f, 0xbc, 0xc8, 0x4b, 0x1d, 0xaf, 0x40, 0xa8, 0x08, 0x3e, 0xb6, 0xe3, 0x95, 0xfa, 0x34, 0xba, 0x06, 0x4c, 0x99, 0x30, 0x22, 0xb2, 0x06, 0x4c, 0x19, 0x3f, 0x3c, 0x8d, 0xac, 0x0e, 0x42, 0x66, 0xf4, 0x2a, 0x50, 0x58, 0x46, 0xf5, 0x8d, 0xd0, 0x18, 0xde, 0x27, 0xed, 0x67, 0xfa, 0x26, 0xbc, 0xa3, 0xa5, 0xb0, 0x71, 0x2d, 0xd5, 0x37, 0x0d, 0xe8, 0xaa, 0xea, 0x11, 0xd5, 0x15, 0x54, 0xdf, 0x34, 0x70, 0x19, 0xf0, 0x67, 0x94, 0x4e, 0xf5, 0xc6, 0x0e, 0x55, 0x6f, 0x5c, 0xa5, 0xaa, 0x1b, 0x2e, 0x43, 0xf3, 0xef, 0x30, 0x4a, 0x68, 0x0a, 0xdf, 0x27, 0x55, 0x10, 0x7d, 0xb3, + 0x8f, 0xe8, 0x9b, 0xdb, 0x30, 0xf3, 0xa3, 0xb0, 0x5d, 0xd5, 0x37, 0xb7, 0xa9, 0xf9, 0xb6, 0xeb, 0xf3, 0xea, 0x0d, 0x61, 0x23, 0x1a, 0x4e, 0xf8, 0x40, 0x7c, 0x24, 0xae, 0xe7, 0x4c, 0x20, 0xa4, 0xdd, 0x1c, 0x23, 0xd6, 0x71, 0xaa, 0x2e, 0xe0, 0x23, 0xba, 0x00, 0x57, 0x0f, 0xff, 0xcc, 0x99, 0xe1, 0xb9, 0x02, 0x2b, 0xa5, 0x9c, 0xaf, 0xdd, 0xe1, 0xf3, 0x8b, 0xbb, 0x20, 0xd6, 0x03, 0x5e, 0xb5, 0xcc, 0xe3, 0xa1, 0x65, 0x56, 0x12, 0x8e, 0x71, 0x3f, 0x2d, 0x33, 0x25, 0xe8, 0xa6, 0xa2, 0x4f, 0x3b, 0xbb, 0x1b, 0x8f, 0x31, 0xcf, 0x51, 0xe6, 0x29, 0xc6, 0x37, 0x5b, 0xa9, 0xc4, 0xd4, 0x6b, 0xd1, 0x01, 0x7f, 0xae, 0x63, 0x04, 0x97, 0x21, 0xdc, 0x82, 0xc1, 0x75, 0x91, 0xc7, 0x43, 0xb4, 0x53, 0xef, 0x9f, 0x49, 0xfb, 0x5c, 0xa4, 0x2c, 0x3b, 0xc4, 0x9f, 0xd0, 0x78, 0x13, 0x75, 0x2d, + 0x4c, 0x8a, 0xf3, 0x45, 0x4f, 0xcd, 0x14, 0x8f, 0x0b, 0x4a, 0xba, 0x2a, 0xa4, 0x70, 0xed, 0xa4, 0x30, 0x61, 0x63, 0xbc, 0x6b, 0xcd, 0x2a, 0x88, 0x41, 0xd1, 0xfb, 0x00, 0x69, 0xd3, 0x71, 0xbd, 0x1c, 0x3a, 0x27, 0x59, 0x48, 0x6d, 0xae, 0x1b, 0x8d, 0xf1, 0x9b, 0xa6, 0x63, 0x3d, 0xdd, 0xdf, 0x53, 0x84, 0xab, 0xe0, 0x23, 0xef, 0x24, 0xdf, 0xc7, 0xb5, 0x8b, 0x65, 0x6b, 0x58, 0x30, 0x0b, 0xfa, 0x7d, 0x4d, 0xa4, 0x4d, 0xf1, 0xa4, 0x2c, 0x2f, 0xcc, 0x6f, 0x75, 0x2e, 0x92, 0x99, 0xc0, 0xa1, 0xe5, 0x6a, 0xcf, 0xd5, 0xc5, 0x9c, 0x8d, 0xbc, 0x3a, 0x1b, 0x31, 0x9d, 0x8d, 0xf1, 0xd0, 0xd0, 0xcd, 0x77, 0x90, 0x3a, 0xd6, 0x6b, 0xdf, 0x3e, 0x6f, 0x3e, 0xa9, 0xec, 0xa8, 0xda, 0xee, 0x07, 0x1e, 0x70, 0xc5, 0xb3, 0x6e, 0x78, 0xec, 0x31, 0x8f, 0x87, 0x7e, 0xc3, 0x44, 0x52, 0x6f, 0x09, + 0xad, 0x97, 0xe8, 0x6f, 0x8f, 0xd5, 0x50, 0x2f, 0x7d, 0x68, 0xce, 0x86, 0xcc, 0x8b, 0xd2, 0xab, 0x03, 0xd5, 0xc0, 0x5b, 0xdd, 0xe6, 0x09, 0xa9, 0x56, 0x4c, 0x5b, 0x50, 0x5d, 0x02, 0x23, 0x78, 0xb3, 0xd6, 0x02, 0xfa, 0x95, 0x50, 0xf1, 0xfc, 0x4b, 0xf4, 0xe1, 0x64, 0x2d, 0xf8, 0x12, 0xaa, 0x7e, 0xfc, 0x71, 0x52, 0x3d, 0xf5, 0xa5, 0x71, 0x45, 0xf8, 0x61, 0x69, 0x23, 0x44, 0x01, 0xed, 0x9d, 0xc2, 0xe2, 0x97, 0x86, 0xba, 0xd1, 0x2f, 0x30, 0xf5, 0xad, 0x11, 0x1e, 0x30, 0x0a, 0x29, 0xd2, 0xf2, 0xdf, 0xa1, 0xe6, 0x1f, 0xc6, 0x62, 0x98, 0x86, 0x1e, 0x38, 0xc3, 0xfc, 0x0f, 0x4b, 0xb7, 0x41, 0x24, 0xd1, 0x1f, 0x50, 0x3f, 0xcb, 0xdf, 0x72, 0x16, 0xf5, 0x4f, 0x13, 0x06, 0x91, 0xfa, 0x9f, 0x23, 0x73, 0xeb, 0x00, 0x49, 0xff, 0x88, 0xda, 0x0f, 0x34, 0xa2, 0x29, 0x69, 0xc7, 0x80, + 0x11, 0x4d, 0xf5, 0xfc, 0xeb, 0xa3, 0xf2, 0xb7, 0xb2, 0x68, 0xaa, 0xa1, 0x07, 0x4f, 0x33, 0xff, 0xad, 0xd2, 0x8b, 0x86, 0xfc, 0x8d, 0xbd, 0x43, 0xd5, 0xfa, 0xa7, 0x9d, 0x76, 0xfd, 0xe6, 0xfc, 0x4d, 0xa7, 0x5f, 0x3f, 0xe1, 0x59, 0xb3, 0xc2, 0xff, 0x90, 0xae, 0x24, 0xf9, 0x08, 0xf7, 0x0a, 0x7f, 0xc5, 0xfa, 0x0d, 0x97, 0x81, 0x7f, 0x69, 0xfe, 0x85, 0xf0, 0x57, 0x90, 0x9e, 0x27, 0x8b, 0x76, 0x99, 0xaa, 0x8b, 0xd4, 0xf4, 0x27, 0x0c, 0xe9, 0x49, 0xbd, 0xb8, 0x31, 0x3a, 0x7d, 0xa3, 0x39, 0x3d, 0x94, 0x3f, 0xfc, 0xcc, 0xca, 0x67, 0xe5, 0x62, 0x99, 0xe5, 0x53, 0xfd, 0x5d, 0x37, 0xd2, 0xef, 0x50, 0x33, 0x52, 0x1b, 0x86, 0x68, 0x22, 0x71, 0xa2, 0x3c, 0x84, 0xf0, 0x6d, 0x9e, 0x6e, 0x9e, 0xec, 0xc4, 0xf1, 0x31, 0xf1, 0xdd, 0x26, 0xdc, 0xab, 0xe3, 0x4f, 0xe3, 0xb4, 0x08, 0x2e, + 0x7d, 0xa1, 0xe3, 0xcf, 0x9a, 0xf0, 0x6f, 0x75, 0xfc, 0x39, 0x13, 0xfe, 0xb0, 0x8e, 0xbf, 0x60, 0xc4, 0x65, 0xb7, 0x8e, 0xbf, 0x68, 0xc2, 0xbb, 0x74, 0xfc, 0x25, 0x9c, 0x1e, 0xc1, 0x2d, 0x41, 0x1d, 0x7f, 0x05, 0x7b, 0xa9, 0x4f, 0x2a, 0x8a, 0xd3, 0xbd, 0xa2, 0x64, 0x54, 0x11, 0x2c, 0x85, 0x78, 0x28, 0xb8, 0x43, 0x54, 0xfd, 0x68, 0xcd, 0x26, 0xcb, 0x58, 0x09, 0x75, 0x12, 0x5c, 0x0a, 0x9e, 0xde, 0x20, 0xc0, 0x94, 0x83, 0x18, 0x34, 0xb0, 0x77, 0x24, 0xc9, 0xc4, 0x9c, 0xe1, 0x03, 0x05, 0x0e, 0x2c, 0xf3, 0xcc, 0x85, 0x9c, 0x8f, 0xaa, 0x0c, 0x5c, 0xcd, 0xef, 0x3a, 0xe1, 0xf1, 0xd8, 0x3d, 0xf6, 0xb7, 0xf9, 0x27, 0x1e, 0xc9, 0xc8, 0xb3, 0xac, 0x0a, 0xdd, 0xb4, 0x4a, 0x0e, 0x78, 0x1f, 0xe1, 0x9a, 0x7f, 0xc3, 0x79, 0xb8, 0xb2, 0xc6, 0x69, 0x25, 0x1d, 0x99, 0xa1, 0x13, 0xa1, + 0xbf, 0x66, 0xa4, 0xe0, 0xb1, 0xca, 0x3e, 0x77, 0x36, 0xa9, 0x5b, 0x6f, 0x1f, 0x8d, 0x3f, 0xc7, 0x53, 0xff, 0x43, 0x7f, 0x0f, 0x97, 0x86, 0x7f, 0xcb, 0xe2, 0xf2, 0x84, 0x7f, 0x4b, 0x07, 0x81, 0x23, 0xa9, 0x90, 0x38, 0x9e, 0xa4, 0xb7, 0xa0, 0x14, 0x68, 0xab, 0xd0, 0xa7, 0xad, 0x69, 0xb4, 0xad, 0xe9, 0xd0, 0xd6, 0x94, 0xe4, 0xa4, 0x44, 0xa7, 0xc3, 0x6e, 0x25, 0x89, 0x2d, 0x92, 0x04, 0x6d, 0x2d, 0xc7, 0xb1, 0x1a, 0xfb, 0xc4, 0x09, 0xd2, 0x54, 0xd6, 0xda, 0x1d, 0x19, 0x7e, 0xeb, 0xaa, 0xd0, 0xba, 0x55, 0x96, 0x7c, 0xef, 0x0e, 0xae, 0xf9, 0x5d, 0x68, 0x6d, 0xc6, 0xe8, 0xe2, 0x92, 0xd1, 0x7d, 0x9a, 0x4b, 0xdb, 0xab, 0x6c, 0x86, 0x7e, 0xd3, 0xfa, 0x13, 0xff, 0x19, 0xa7, 0x18, 0xfa, 0x3f, 0x4f, 0xc7, 0xdf, 0x36, 0xf6, 0xbf, 0xfc, 0x90, 0x8e, 0xbf, 0x63, 0x1a, 0x2f, 0x7d, + 0x7c, 0xf1, 0xbb, 0x26, 0xfc, 0x73, 0x1d, 0x7f, 0xcf, 0x88, 0x5b, 0xda, 0x75, 0xfc, 0x7d, 0x0d, 0x87, 0xf6, 0xe8, 0xf2, 0x56, 0xc0, 0xe4, 0x2d, 0xba, 0x5d, 0x96, 0x80, 0x9e, 0xef, 0x0f, 0xa6, 0xf2, 0xf2, 0x75, 0xfc, 0x03, 0x13, 0x1e, 0xf9, 0x8e, 0x8f, 0xfa, 0xa9, 0xff, 0x63, 0x53, 0xfd, 0x6e, 0xbd, 0xfe, 0x17, 0x63, 0xd5, 0x6f, 0x98, 0x0f, 0x7b, 0x4d, 0xdf, 0xf9, 0xbc, 0x9e, 0x8f, 0xc8, 0x37, 0x4e, 0xc2, 0xaa, 0xfc, 0xb3, 0xf1, 0xc7, 0x49, 0xaa, 0x8f, 0x7a, 0x96, 0xf6, 0x7d, 0x53, 0x5a, 0x32, 0x73, 0xd1, 0x11, 0x43, 0xda, 0x46, 0x53, 0x5a, 0xbd, 0x3e, 0x71, 0x7b, 0xec, 0x79, 0x23, 0x3e, 0x68, 0xc2, 0x33, 0x74, 0xf9, 0x3b, 0x82, 0x53, 0x09, 0xca, 0xe4, 0x2f, 0x4c, 0xe5, 0x8f, 0x27, 0x12, 0x08, 0x63, 0xfe, 0x7b, 0x92, 0xd6, 0x8e, 0xf2, 0xd0, 0xd2, 0xa0, 0x2d, 0x2f, + 0xc3, 0x22, 0x62, 0x11, 0x49, 0xe0, 0xec, 0x2e, 0x87, 0x46, 0x17, 0x13, 0x38, 0x61, 0x39, 0x26, 0x2c, 0x17, 0xf6, 0x05, 0xf8, 0xd9, 0xcc, 0x8d, 0x97, 0x24, 0xcd, 0xe9, 0x14, 0x31, 0x0d, 0x5a, 0xea, 0xed, 0x9b, 0x04, 0x7e, 0x25, 0xe9, 0xb8, 0xe9, 0x6a, 0x92, 0xae, 0x20, 0x61, 0x0a, 0x7e, 0x9f, 0x37, 0x3d, 0x2d, 0xd5, 0x93, 0x9c, 0xe8, 0x72, 0xc4, 0x09, 0x10, 0x8e, 0x93, 0xb3, 0xc2, 0xf6, 0x81, 0x1d, 0xfb, 0xab, 0x71, 0x01, 0x9d, 0x74, 0xaa, 0x18, 0x33, 0x33, 0xcb, 0x8f, 0xab, 0xbd, 0x18, 0xa8, 0x32, 0xe6, 0x77, 0xcd, 0x3f, 0xf6, 0xde, 0x60, 0x5c, 0xd6, 0x40, 0xa7, 0x20, 0x77, 0x82, 0xce, 0x40, 0xa5, 0x4c, 0x39, 0xea, 0x09, 0xe6, 0x4c, 0x99, 0x70, 0x3e, 0x99, 0x84, 0xd7, 0x7c, 0x66, 0x4f, 0x4c, 0x4a, 0x4d, 0x5c, 0xa4, 0xac, 0xe5, 0x3c, 0xd8, 0xae, 0x7c, 0x8d, 0xb7, + 0xde, 0x41, 0xe7, 0xe4, 0xc9, 0x57, 0xa8, 0x88, 0x8f, 0xcf, 0xf1, 0x2e, 0x12, 0xb7, 0x29, 0x3f, 0xc5, 0xc3, 0x9c, 0xc9, 0x71, 0x52, 0xe8, 0xbb, 0xc8, 0xdc, 0x7c, 0x45, 0xef, 0xb3, 0x86, 0xb0, 0x8b, 0xc6, 0xaf, 0x1b, 0x17, 0x85, 0xb7, 0x28, 0x8f, 0x53, 0xbc, 0x31, 0x0a, 0x1f, 0xa2, 0xe2, 0xf7, 0x53, 0x5d, 0x76, 0x5c, 0xc7, 0x5b, 0xc3, 0xa9, 0x86, 0xb9, 0x7f, 0x44, 0xc7, 0x1b, 0x19, 0x1e, 0x1e, 0x15, 0x85, 0x07, 0xd5, 0x72, 0xea, 0xa2, 0xf0, 0xb6, 0x7e, 0xca, 0x1f, 0x6a, 0x2a, 0x7f, 0x9f, 0x8e, 0x0f, 0x56, 0xcb, 0xff, 0x36, 0x0a, 0x6f, 0x32, 0xa6, 0x3f, 0x13, 0x9d, 0xab, 0x5c, 0x61, 0xd4, 0x05, 0xe2, 0x68, 0xf0, 0x0e, 0xa8, 0xe3, 0x5a, 0x3f, 0x14, 0xb0, 0x7e, 0x8b, 0xfc, 0x1e, 0x1e, 0x17, 0xf5, 0x3b, 0xed, 0x3f, 0xc3, 0xef, 0x8d, 0x51, 0xbf, 0x0f, 0x89, 0xfa, 0xfd, + 0x7e, 0xf8, 0x5d, 0xff, 0xde, 0x02, 0xd6, 0x9f, 0x7d, 0xea, 0x3f, 0xa2, 0xff, 0xde, 0x68, 0xfe, 0x3d, 0x3c, 0x2a, 0xea, 0xf7, 0x60, 0x54, 0xf9, 0x75, 0x51, 0xbf, 0xb7, 0x9d, 0xa2, 0xfe, 0xa1, 0x51, 0xf5, 0x83, 0xbb, 0xf2, 0xf1, 0x32, 0x8f, 0xc8, 0xef, 0x0a, 0xe8, 0x82, 0x6a, 0x55, 0x17, 0x50, 0x5c, 0xfa, 0x56, 0xc3, 0xc9, 0x5c, 0x57, 0x75, 0x84, 0x52, 0x69, 0xec, 0x47, 0xce, 0x89, 0xd9, 0x38, 0xfd, 0x1b, 0x21, 0xe9, 0x0b, 0xba, 0xc6, 0x8a, 0x6c, 0x8d, 0xe5, 0x87, 0x47, 0x70, 0x3a, 0x4e, 0x0c, 0x7f, 0x81, 0x9f, 0x40, 0xe3, 0xb3, 0x7d, 0x45, 0xf0, 0x7f, 0x10, 0x45, 0x2d, 0xa2, 0x54, 0x34, 0x2e, 0xd8, 0x89, 0xb1, 0x48, 0xd6, 0x09, 0x24, 0x41, 0x28, 0x1a, 0x79, 0x96, 0x15, 0xd6, 0x0b, 0x0b, 0x82, 0x40, 0xc8, 0xa2, 0xd0, 0x45, 0x17, 0x0d, 0x1b, 0xb6, 0x58, 0x58, 0x1c, + 0x6f, 0xb2, 0x74, 0x48, 0x12, 0xc9, 0x9d, 0x2a, 0xa5, 0x7a, 0xc8, 0x0a, 0xe2, 0x4e, 0x74, 0x25, 0x38, 0xe3, 0x6d, 0x56, 0x8b, 0x4c, 0xca, 0x12, 0xed, 0x74, 0x19, 0x21, 0x73, 0xcf, 0x8a, 0x65, 0x9f, 0xb6, 0x82, 0xf8, 0x92, 0x7d, 0x10, 0x96, 0xd9, 0xcd, 0x3f, 0x38, 0xc3, 0x91, 0xe5, 0x54, 0xde, 0x16, 0x13, 0x70, 0xef, 0xbf, 0x2b, 0x42, 0xef, 0x3f, 0x69, 0x2f, 0xca, 0x7c, 0x90, 0xfb, 0xa7, 0x60, 0x0b, 0xe5, 0x36, 0x70, 0xb9, 0xa1, 0x7f, 0x0d, 0x26, 0xcb, 0xc9, 0x27, 0xa5, 0x63, 0x72, 0x43, 0xcf, 0x2a, 0x77, 0xef, 0xc6, 0xab, 0x94, 0x67, 0x52, 0xbc, 0xe2, 0xb6, 0xdd, 0x4a, 0xe3, 0x63, 0x34, 0xa6, 0x21, 0x0f, 0xf1, 0xb4, 0x84, 0x7d, 0xf4, 0x5e, 0x2e, 0x44, 0x18, 0x1f, 0x1e, 0x1c, 0x9a, 0x85, 0x31, 0x17, 0xb9, 0x96, 0x23, 0x63, 0x9e, 0x17, 0xa6, 0x5b, 0x30, 0xac, 0xc7, + 0xb0, 0xcb, 0x48, 0xdd, 0xc0, 0x97, 0x8a, 0x63, 0x92, 0x93, 0xec, 0x76, 0x8c, 0x02, 0x79, 0x49, 0x05, 0xc9, 0x05, 0x76, 0xb7, 0x3d, 0xd1, 0x11, 0x67, 0x95, 0x91, 0x0d, 0xdb, 0xac, 0x64, 0x79, 0xce, 0x67, 0xd1, 0xa9, 0xfc, 0xbe, 0x34, 0xd5, 0x05, 0xa2, 0xbf, 0x56, 0x8b, 0x49, 0x25, 0xb8, 0x58, 0x7c, 0x64, 0xee, 0x99, 0x89, 0xcb, 0x47, 0xe7, 0xde, 0xb9, 0x55, 0x79, 0xfb, 0xeb, 0xfb, 0xee, 0xbd, 0x63, 0xcb, 0x4d, 0x87, 0xaf, 0x69, 0xca, 0x69, 0x9b, 0x3b, 0x1c, 0x37, 0x0e, 0xbf, 0xfe, 0x95, 0x15, 0xa1, 0x93, 0x4f, 0x04, 0xda, 0xa6, 0xd7, 0x75, 0xaf, 0x54, 0x7c, 0xc2, 0x18, 0xa5, 0x78, 0xd5, 0xc2, 0x95, 0x3d, 0x8f, 0xd5, 0xcc, 0xbe, 0xf9, 0xfc, 0x9a, 0x8b, 0x27, 0xb7, 0xa5, 0x0c, 0x51, 0xbe, 0x99, 0xf1, 0xe3, 0xc5, 0xcd, 0xa4, 0xc5, 0x67, 0xd0, 0xf6, 0xb4, 0x48, 0xdb, + 0xd3, 0x07, 0x68, 0xbb, 0xf4, 0xbf, 0xd8, 0x76, 0x2b, 0x69, 0xfb, 0x31, 0x7a, 0x0e, 0xe8, 0x41, 0x15, 0x68, 0x62, 0x70, 0x5c, 0x2e, 0xc6, 0x20, 0x2d, 0x10, 0x7b, 0x5a, 0xc0, 0x73, 0xac, 0x98, 0xb3, 0x60, 0xe6, 0xa5, 0x53, 0x14, 0xa5, 0xe9, 0x76, 0x0c, 0xea, 0x1a, 0x3c, 0xc4, 0xd3, 0x18, 0xf7, 0x73, 0xe5, 0x31, 0x69, 0xa9, 0x18, 0x95, 0x97, 0x95, 0x96, 0x14, 0x17, 0xa6, 0x56, 0xa4, 0x55, 0x78, 0x92, 0x5d, 0x4e, 0xb8, 0x78, 0x43, 0x3e, 0xc4, 0x81, 0x1d, 0x71, 0x36, 0xc3, 0x87, 0x40, 0xf0, 0xcd, 0x64, 0x17, 0xfb, 0x18, 0xa2, 0xb2, 0xa3, 0xbf, 0x06, 0x82, 0x74, 0x72, 0x47, 0xb5, 0x2f, 0x3a, 0x7c, 0x98, 0x7e, 0x53, 0x38, 0x1c, 0xfd, 0x55, 0x07, 0x0e, 0xa8, 0xdf, 0x75, 0xdf, 0x96, 0xc7, 0xb9, 0x2b, 0x62, 0x7e, 0x16, 0x9e, 0xb8, 0x85, 0xdf, 0x8b, 0xd4, 0x18, 0x6d, 0xc2, + 0x3e, 0xd9, 0x4f, 0xe6, 0x87, 0xa4, 0xc6, 0x98, 0x39, 0x8a, 0xe7, 0x90, 0x35, 0x0d, 0x88, 0xf0, 0x1c, 0x75, 0x9d, 0xdc, 0xab, 0x6c, 0x86, 0xb1, 0x63, 0x69, 0x60, 0xbd, 0x46, 0x5f, 0xa9, 0x31, 0xac, 0xbe, 0x36, 0xe2, 0xe2, 0x47, 0x2a, 0xde, 0x15, 0xfe, 0x5c, 0x74, 0xc8, 0x43, 0x20, 0xde, 0x4c, 0x98, 0xf1, 0xdd, 0x5b, 0x0d, 0xb8, 0x57, 0xc7, 0x9f, 0xc6, 0x5b, 0x63, 0xe2, 0x7b, 0x4d, 0x78, 0x06, 0xc3, 0xe9, 0x7a, 0xbb, 0x85, 0xae, 0xb7, 0x58, 0x5f, 0x6f, 0xb1, 0x1e, 0xdb, 0xa6, 0x0c, 0x8d, 0x08, 0xb6, 0xb9, 0xb1, 0xc0, 0xf1, 0x64, 0xb1, 0xe5, 0x3a, 0x10, 0x2f, 0x20, 0xb2, 0x28, 0xf6, 0x10, 0x59, 0x82, 0x48, 0x16, 0x8b, 0xb5, 0xa0, 0xca, 0x34, 0xb4, 0x14, 0x0b, 0x04, 0x07, 0xae, 0x5f, 0x0b, 0xf2, 0x33, 0xbd, 0xa9, 0x29, 0x2e, 0xa7, 0x2c, 0xa2, 0x32, 0x5c, 0x26, 0xc3, + 0xe4, 0xad, 0x52, 0xe3, 0xad, 0xe0, 0x53, 0x85, 0x07, 0x12, 0xb6, 0x25, 0xc6, 0x9f, 0x77, 0x7e, 0xe8, 0x99, 0x6d, 0xa1, 0x7d, 0xb3, 0x4f, 0x15, 0x09, 0x08, 0xd7, 0x38, 0x66, 0x2c, 0x58, 0xb6, 0xf0, 0x08, 0x76, 0x3c, 0x34, 0x40, 0xd0, 0x1f, 0xe0, 0xda, 0x39, 0x84, 0x3f, 0xcc, 0x20, 0xfc, 0xc1, 0x8e, 0xda, 0xd0, 0xc6, 0x60, 0x72, 0x73, 0x63, 0x25, 0x4f, 0xb4, 0x0a, 0xe6, 0xe4, 0x40, 0x3a, 0x87, 0xb9, 0x1c, 0xcc, 0xd3, 0x20, 0x6b, 0x39, 0x34, 0x9a, 0x92, 0x0c, 0xf1, 0x3a, 0xb8, 0x1e, 0x4a, 0x6d, 0x31, 0xf9, 0x44, 0x5e, 0x58, 0x4e, 0xa8, 0x42, 0x24, 0xb6, 0x60, 0x37, 0x0b, 0x46, 0x88, 0x58, 0x48, 0x48, 0x92, 0x41, 0x12, 0xa5, 0x65, 0x31, 0x73, 0xc5, 0xca, 0xd0, 0x15, 0xb4, 0x17, 0x14, 0x14, 0xe6, 0xe7, 0x05, 0x8a, 0x73, 0xa9, 0x5f, 0x51, 0xd5, 0xa7, 0x7a, 0x12, 0xb1, + 0xf0, 0x99, 0x1b, 0xf5, 0x7a, 0x76, 0xc0, 0xcd, 0x1e, 0x32, 0xb0, 0x3d, 0xb9, 0x40, 0xa4, 0xd3, 0xe8, 0xb6, 0x9c, 0x9c, 0x85, 0x59, 0xef, 0x15, 0xf0, 0x5f, 0x16, 0x74, 0x8f, 0x82, 0x5e, 0x29, 0x2d, 0x9e, 0xd2, 0x7a, 0xdf, 0xf4, 0x48, 0x84, 0x24, 0xe8, 0xc2, 0x6b, 0xde, 0x9a, 0xb2, 0x02, 0xc8, 0xc7, 0x27, 0xe7, 0x75, 0xd1, 0x9e, 0x9c, 0xf9, 0xf0, 0x88, 0x6a, 0xc6, 0x40, 0x36, 0xd9, 0x93, 0xed, 0xd0, 0xa9, 0x8e, 0x14, 0xe1, 0x67, 0x89, 0x69, 0xd0, 0x5d, 0xf3, 0x1e, 0x6f, 0x9b, 0xdf, 0x30, 0x58, 0x8d, 0x92, 0x44, 0x3a, 0x75, 0x78, 0xf0, 0xd1, 0x1c, 0x2f, 0x1e, 0x3c, 0x8b, 0x8c, 0x07, 0xed, 0xda, 0x42, 0x7f, 0x3b, 0xb0, 0x92, 0x23, 0x56, 0xb7, 0x8d, 0xf4, 0xb1, 0xe3, 0x2c, 0x64, 0xa4, 0x5a, 0x97, 0x91, 0x9a, 0x18, 0x32, 0x22, 0xff, 0xdf, 0xcb, 0x48, 0x97, 0xb2, 0x95, + 0xce, 0x2f, 0x3b, 0x3a, 0x0f, 0x0b, 0xea, 0x29, 0x4b, 0x3d, 0xb6, 0xca, 0x45, 0x09, 0x9c, 0xc0, 0x79, 0x31, 0x2f, 0xf0, 0x1d, 0x5e, 0x33, 0x22, 0xf2, 0x1d, 0x6a, 0x1c, 0xd1, 0x46, 0xea, 0x99, 0x18, 0xc9, 0x3d, 0xc8, 0x6e, 0xb1, 0x59, 0xec, 0xb6, 0x1e, 0x70, 0xa3, 0x6b, 0xc7, 0x16, 0x88, 0x4a, 0x49, 0xf5, 0x15, 0x91, 0x01, 0x9b, 0x8d, 0x9b, 0x2e, 0x11, 0xa6, 0xb9, 0xd0, 0xec, 0x7c, 0x97, 0xd5, 0x74, 0x26, 0x05, 0x2c, 0x89, 0xf2, 0xde, 0x1b, 0xec, 0x93, 0x57, 0xe0, 0x44, 0x0e, 0xbc, 0xf9, 0x0e, 0x54, 0x86, 0xd1, 0xa9, 0x6f, 0x57, 0x30, 0x71, 0xf2, 0xc4, 0xce, 0xd1, 0x43, 0x5b, 0xf3, 0x0b, 0xc1, 0xab, 0xaf, 0x3b, 0x9f, 0x06, 0x3f, 0xc0, 0xfa, 0x86, 0xb0, 0xdb, 0x81, 0x9d, 0xb8, 0x3f, 0xe7, 0xbe, 0xa7, 0x15, 0xf2, 0x8b, 0xbf, 0x30, 0xb2, 0x83, 0xfc, 0x7e, 0xf7, 0xc4, + 0x3f, 0x6d, 0x4b, 0x88, 0xe9, 0x06, 0xb8, 0xe8, 0xd6, 0xff, 0x79, 0xa4, 0xeb, 0x34, 0x02, 0x80, 0xc1, 0xc6, 0x33, 0xdb, 0x72, 0xfe, 0x6e, 0xe3, 0xcc, 0xa5, 0xd6, 0x86, 0x18, 0x0e, 0x83, 0xaf, 0xb1, 0x5d, 0x7e, 0x08, 0xf3, 0x5b, 0x07, 0x8c, 0x06, 0xa6, 0xe9, 0x6a, 0xf1, 0x6e, 0x6a, 0xaf, 0x58, 0x55, 0xfb, 0xc8, 0x6b, 0xc0, 0x33, 0xb4, 0x98, 0xb6, 0x44, 0x4f, 0x7a, 0xcd, 0x76, 0x09, 0xd5, 0xd5, 0xd7, 0x1a, 0xe3, 0xde, 0x12, 0x9b, 0x2b, 0x3b, 0xfc, 0xa9, 0x9a, 0xe6, 0x53, 0x3d, 0xcd, 0x07, 0xc6, 0x34, 0xe2, 0x47, 0x31, 0xd3, 0xac, 0x31, 0xa6, 0x91, 0x92, 0x63, 0xa5, 0x41, 0xc8, 0xb6, 0x8a, 0xc6, 0xfd, 0x64, 0xed, 0x2c, 0x95, 0x64, 0xad, 0x9d, 0xd6, 0x61, 0x06, 0x7c, 0xa3, 0xf8, 0x6f, 0x7d, 0x7f, 0xe1, 0x31, 0xe6, 0x3f, 0x1e, 0xb5, 0x06, 0x9b, 0x65, 0xcc, 0x49, 0xea, + 0x7a, 0x0a, 0x51, 0x6d, 0xac, 0x44, 0x84, 0x20, 0x80, 0xb4, 0xaa, 0xa3, 0xe6, 0x8a, 0x63, 0x12, 0x5d, 0x18, 0xe5, 0x64, 0x67, 0x65, 0x66, 0xa4, 0xbb, 0x7c, 0x89, 0x3e, 0x27, 0xbc, 0xb8, 0xb2, 0xa2, 0x04, 0x9c, 0x60, 0xd3, 0xd7, 0xd2, 0x72, 0x58, 0x2e, 0xc1, 0x35, 0x36, 0x66, 0xd1, 0xbc, 0x39, 0x49, 0xd0, 0xd6, 0xcf, 0x3f, 0x68, 0x41, 0xbc, 0x95, 0x17, 0x71, 0x86, 0xf2, 0x31, 0x6e, 0xa7, 0x61, 0xbb, 0x2f, 0x79, 0x49, 0xf9, 0x7e, 0x7b, 0xe8, 0xe3, 0x43, 0x87, 0x76, 0x75, 0xdc, 0xf2, 0xfa, 0xea, 0xeb, 0x8e, 0xdf, 0xd2, 0xa1, 0xe4, 0x5e, 0xa3, 0xa4, 0x08, 0x9f, 0x2f, 0x78, 0x59, 0xf9, 0x66, 0xfb, 0x83, 0xca, 0xb7, 0x2f, 0x2f, 0xe0, 0xee, 0xbe, 0x86, 0x46, 0x4f, 0xfc, 0x7f, 0xac, 0xbd, 0xea, 0x1a, 0x5f, 0x4a, 0xb9, 0xb1, 0x9d, 0xed, 0x4b, 0xa1, 0x3f, 0xc6, 0xc4, 0x77, + 0x9b, 0x70, 0xaf, 0x8e, 0x3f, 0x8d, 0x3e, 0x8d, 0x89, 0xef, 0x35, 0xe2, 0x74, 0x5f, 0x8a, 0xe1, 0xcf, 0x99, 0xf0, 0x87, 0x75, 0xfc, 0x05, 0x53, 0x39, 0x6e, 0x1d, 0x7f, 0xd1, 0x84, 0x67, 0x30, 0x1c, 0xe4, 0x18, 0x7d, 0xda, 0x67, 0xbd, 0xa7, 0x69, 0xe8, 0xfd, 0x8a, 0x8a, 0x60, 0x29, 0xe1, 0x66, 0x82, 0xc8, 0x0b, 0xb3, 0xd4, 0xc3, 0x34, 0x5c, 0x42, 0xc3, 0x74, 0xc3, 0x19, 0x18, 0x2a, 0xd5, 0x2f, 0x59, 0xc0, 0x3f, 0xb2, 0x9c, 0x56, 0x92, 0x2f, 0xfb, 0x34, 0x6f, 0xe9, 0x2e, 0xa1, 0x14, 0xf7, 0xde, 0xc8, 0x5f, 0x1d, 0x9a, 0xc1, 0xe5, 0x84, 0x3e, 0xd8, 0xbd, 0x5b, 0x63, 0xd9, 0xa4, 0x36, 0xc2, 0x64, 0x84, 0x9f, 0x8b, 0xfb, 0x08, 0x47, 0xb5, 0xc3, 0x5d, 0x0e, 0xd8, 0x65, 0x52, 0x9e, 0xa1, 0xbb, 0x4c, 0x58, 0xdf, 0x65, 0x3a, 0x65, 0x3b, 0xd2, 0x22, 0xed, 0x48, 0x37, 0xb7, + 0x43, 0x3a, 0xdd, 0x76, 0x0c, 0x38, 0x66, 0xb1, 0xfa, 0x9a, 0x70, 0xb4, 0x52, 0x3a, 0x2f, 0xed, 0xea, 0x9e, 0x0a, 0x32, 0xf4, 0x69, 0x9e, 0x8e, 0xbf, 0x8d, 0x3e, 0x33, 0x94, 0xf3, 0x85, 0x5e, 0xce, 0xb3, 0x86, 0x31, 0xf8, 0x8c, 0xee, 0x41, 0xb1, 0xf4, 0xef, 0xa8, 0xb8, 0x95, 0x96, 0xa3, 0x8f, 0x25, 0x7e, 0xd7, 0x90, 0x7e, 0x26, 0xdd, 0x83, 0x62, 0xf8, 0x7b, 0xc6, 0xb1, 0xa4, 0x7b, 0x40, 0x0c, 0x7f, 0xdf, 0xd8, 0x4e, 0x5d, 0x86, 0x0a, 0x98, 0x6c, 0x19, 0xda, 0x7b, 0xb3, 0x59, 0xc6, 0xc4, 0xed, 0xb1, 0x65, 0x46, 0x7c, 0x90, 0xe6, 0x23, 0xe3, 0xa0, 0x6c, 0xa5, 0xe3, 0x50, 0x80, 0xb6, 0xa9, 0x7e, 0xe6, 0xd3, 0x21, 0xfa, 0x5e, 0x1c, 0xf5, 0xfe, 0xe6, 0x8d, 0xfc, 0x8d, 0xd3, 0xfd, 0xcc, 0x17, 0x83, 0x95, 0x67, 0x21, 0x56, 0x9e, 0x66, 0xdc, 0xcd, 0xe9, 0x44, 0x16, 0x0b, + 0xdf, 0x65, 0xb3, 0x72, 0x10, 0x89, 0x59, 0x8f, 0x3b, 0xd4, 0x03, 0x77, 0x15, 0x2a, 0x11, 0xe6, 0x09, 0x5d, 0x20, 0x74, 0x69, 0xc0, 0x4c, 0x5a, 0xf8, 0xa1, 0x1e, 0x64, 0xf4, 0x3b, 0xef, 0x62, 0xae, 0xe7, 0xc1, 0xef, 0xbc, 0xaf, 0x5f, 0x8f, 0xf3, 0x11, 0x61, 0xf0, 0xc9, 0xdc, 0x6b, 0xfd, 0xf8, 0x9c, 0x57, 0x2e, 0xd0, 0x84, 0x84, 0xdb, 0x81, 0xfb, 0x71, 0x3a, 0xcf, 0x44, 0x67, 0x37, 0xe2, 0xc3, 0xaf, 0x12, 0x0e, 0xc9, 0xd1, 0x3d, 0x28, 0x09, 0x65, 0xa3, 0x2b, 0x9f, 0xcf, 0x76, 0x21, 0xb8, 0x37, 0xac, 0xf2, 0xc6, 0x22, 0xc2, 0x76, 0xc4, 0xe5, 0x16, 0x8c, 0xad, 0x70, 0x0a, 0x89, 0x66, 0xb1, 0x0d, 0x28, 0x8e, 0xa3, 0xd1, 0x75, 0xe6, 0x80, 0x47, 0x79, 0xfa, 0x81, 0x34, 0x44, 0x5c, 0x11, 0x6c, 0xe2, 0x2e, 0x8b, 0xce, 0x10, 0x2b, 0x6d, 0x57, 0xd0, 0x91, 0x95, 0xe9, 0xca, + 0x4f, 0xcc, 0xa3, 0x9f, 0x0d, 0xc1, 0x78, 0x70, 0x3d, 0xd1, 0x5f, 0x18, 0x3e, 0xcb, 0x57, 0xcb, 0xd8, 0xa1, 0xcb, 0x47, 0x49, 0x21, 0xae, 0x4e, 0xf6, 0xf1, 0xf8, 0x8e, 0xae, 0xae, 0xb7, 0xf1, 0xd8, 0xd0, 0x1f, 0xc9, 0x07, 0xf5, 0xde, 0x58, 0x0c, 0xec, 0xef, 0xdf, 0xfc, 0x15, 0x16, 0x75, 0xdf, 0x49, 0xd8, 0xa5, 0xbc, 0xf7, 0x3d, 0x27, 0x5f, 0xfd, 0x18, 0x7e, 0xed, 0xe4, 0x2b, 0xbb, 0xe5, 0x46, 0xc2, 0xf0, 0x26, 0x08, 0x07, 0xec, 0x74, 0xbb, 0xa9, 0xf3, 0x31, 0x65, 0xa7, 0x41, 0xb6, 0x2d, 0x99, 0xba, 0x4c, 0x6c, 0x30, 0xc8, 0xca, 0x08, 0x4b, 0x86, 0x2e, 0x63, 0xbb, 0x71, 0x1e, 0xfa, 0x03, 0x56, 0xf5, 0x0f, 0x8b, 0xc7, 0xf0, 0x07, 0x6d, 0xcf, 0x7e, 0xaf, 0x72, 0x8f, 0x71, 0xde, 0x90, 0xf5, 0x4c, 0x8b, 0xcf, 0xfb, 0x7a, 0x04, 0x87, 0x53, 0x4e, 0xc4, 0xa2, 0xbc, 0x63, + 0x3d, 0xca, 0x3b, 0xc7, 0x64, 0x4f, 0x76, 0x23, 0x17, 0x6a, 0x44, 0x6f, 0xa8, 0xd2, 0x57, 0xea, 0xe6, 0x44, 0x32, 0xd6, 0x82, 0x48, 0xa5, 0x4f, 0xfb, 0x9b, 0xa4, 0x4b, 0x5f, 0xa3, 0x03, 0xe2, 0xb6, 0xb2, 0xdb, 0x05, 0x16, 0x0e, 0xcd, 0x21, 0x06, 0xb1, 0x3d, 0xce, 0x66, 0x9f, 0x45, 0x98, 0x8f, 0xa5, 0x8b, 0x88, 0x14, 0xe9, 0xd4, 0xb8, 0x38, 0xb9, 0xcb, 0x19, 0xcf, 0x11, 0xd3, 0x91, 0xaa, 0x91, 0x48, 0xa0, 0xc9, 0xa0, 0x96, 0x57, 0x94, 0x21, 0xd0, 0x64, 0xcf, 0xe9, 0x95, 0x41, 0x46, 0x4a, 0x0f, 0x39, 0x19, 0xcc, 0x4b, 0x24, 0xd2, 0x99, 0xd8, 0x98, 0xd8, 0xd8, 0x50, 0x4f, 0xc4, 0xaf, 0xba, 0xaa, 0x72, 0x50, 0x45, 0x79, 0x59, 0x41, 0xbe, 0x2f, 0x27, 0x23, 0x9d, 0x7c, 0x86, 0x2b, 0x2f, 0x29, 0x37, 0x01, 0x6e, 0xa4, 0x25, 0x49, 0xfe, 0x48, 0x90, 0x93, 0xba, 0x5a, 0xfe, + 0xb4, 0xa4, 0x97, 0xdf, 0xfd, 0xd0, 0x6d, 0xa3, 0x96, 0x8d, 0x2f, 0xbe, 0x3b, 0x21, 0xbf, 0xb9, 0x74, 0xda, 0x88, 0x09, 0xa7, 0x23, 0xcb, 0xb2, 0x7b, 0xd5, 0xcd, 0x60, 0xbb, 0xfa, 0xeb, 0xf2, 0x93, 0xc6, 0x8e, 0xf8, 0xf6, 0xb3, 0x81, 0x65, 0x3b, 0x1c, 0x0e, 0x67, 0x51, 0xbd, 0xfb, 0x8a, 0x3e, 0xe6, 0xed, 0xa1, 0x7f, 0xd3, 0xfb, 0x1d, 0xd5, 0x51, 0x78, 0x4b, 0xe8, 0x16, 0x8a, 0x47, 0xa7, 0x1f, 0xc2, 0xf0, 0x70, 0x3e, 0xc5, 0x8f, 0xe8, 0x78, 0x63, 0x28, 0x44, 0xd3, 0x97, 0x45, 0xe1, 0x41, 0xb5, 0x1c, 0x6f, 0x14, 0xde, 0xa6, 0x96, 0x33, 0x8e, 0xe0, 0x95, 0x64, 0xbd, 0x20, 0xb8, 0x48, 0xf7, 0x0d, 0x15, 0x4e, 0xdb, 0xdf, 0x24, 0xe9, 0xf7, 0xe9, 0xe9, 0x9b, 0x94, 0x2e, 0x83, 0x1e, 0xe3, 0x75, 0xbd, 0x58, 0x6d, 0x90, 0xd9, 0x12, 0x6a, 0x87, 0xc7, 0xab, 0x76, 0xf8, 0x27, + 0xd4, 0x0e, 0xc7, 0x06, 0x3b, 0x1c, 0xec, 0xe1, 0x11, 0x54, 0x37, 0x3a, 0xd4, 0xf3, 0xa2, 0x5d, 0x28, 0x82, 0xbb, 0x75, 0xfc, 0x45, 0x13, 0x9e, 0xc1, 0x70, 0xca, 0x0b, 0x77, 0x9a, 0xd7, 0x53, 0x48, 0xa3, 0xac, 0x14, 0x47, 0x88, 0xbb, 0xf4, 0xbc, 0xff, 0x8d, 0xa7, 0xa2, 0x58, 0x75, 0xed, 0x35, 0x95, 0x39, 0x44, 0xc7, 0x77, 0xe3, 0xed, 0x11, 0x9c, 0xae, 0xf5, 0x0e, 0x75, 0x3f, 0x74, 0x97, 0xce, 0x4d, 0x47, 0xd3, 0x72, 0x9c, 0x6a, 0x9b, 0x97, 0x6a, 0xeb, 0x27, 0x8d, 0x0f, 0x62, 0x47, 0x05, 0xc1, 0x3c, 0xd8, 0x49, 0x41, 0x02, 0x9e, 0x45, 0x2f, 0x4b, 0x94, 0xd0, 0x4b, 0x15, 0x7d, 0xd7, 0x6f, 0x35, 0x74, 0x47, 0x24, 0x40, 0xc7, 0x3b, 0x86, 0x40, 0x1c, 0x5a, 0xcc, 0x8d, 0xf0, 0x65, 0xa4, 0xdc, 0x71, 0x74, 0x3c, 0x9c, 0x22, 0x5b, 0xbd, 0x53, 0xa2, 0x56, 0xef, 0x81, 0xea, + 0x4e, 0xa3, 0x75, 0xf7, 0x5d, 0xb3, 0x4f, 0xab, 0xee, 0x3e, 0xdf, 0xba, 0x17, 0xbe, 0x55, 0xc7, 0x33, 0x18, 0x4e, 0xc7, 0x61, 0x69, 0x9f, 0x71, 0x20, 0xeb, 0x22, 0xb4, 0x49, 0xcb, 0x4b, 0xd6, 0xc3, 0xe5, 0x91, 0xbc, 0x74, 0xbd, 0x77, 0xaa, 0x67, 0x7e, 0x6a, 0x99, 0xca, 0x36, 0x63, 0x7a, 0x29, 0x05, 0xae, 0xc5, 0xb3, 0xf9, 0x41, 0xf0, 0x57, 0x34, 0xdc, 0x38, 0x3f, 0x4c, 0xb8, 0x61, 0x7e, 0x98, 0x70, 0x75, 0x7e, 0x28, 0x6b, 0x68, 0xbd, 0xc7, 0x75, 0xbc, 0x95, 0xcd, 0x0f, 0x98, 0x37, 0x24, 0xfd, 0x11, 0x1d, 0x37, 0xcc, 0x1b, 0x13, 0x6e, 0x98, 0x37, 0x26, 0xbc, 0xad, 0x9f, 0xf2, 0x87, 0xaa, 0xe5, 0x8f, 0x33, 0x8e, 0x9f, 0x61, 0x3e, 0x55, 0xd2, 0x72, 0xf6, 0xe9, 0xe9, 0x9b, 0x94, 0x89, 0x6a, 0x3f, 0x5c, 0x61, 0xec, 0x07, 0x71, 0x34, 0x77, 0x3e, 0xeb, 0x07, 0x8a, 0x6b, + 0xdf, 0x55, 0xc0, 0xfa, 0x21, 0xf2, 0xbb, 0x52, 0x1d, 0xf5, 0x3b, 0xed, 0x0f, 0xc3, 0xef, 0xd1, 0xf9, 0x87, 0x44, 0xfd, 0xbe, 0x06, 0x7e, 0xd7, 0xdb, 0x5f, 0xc0, 0xfa, 0xc7, 0x50, 0x7f, 0x3e, 0xcd, 0x7f, 0x44, 0xff, 0xbd, 0xd1, 0xfc, 0xbb, 0x52, 0x16, 0xf5, 0x7b, 0x30, 0xaa, 0x7c, 0x6f, 0xd4, 0xef, 0x6d, 0xa7, 0xa8, 0x7f, 0x68, 0x54, 0xfd, 0x0f, 0x11, 0x49, 0xdb, 0x49, 0xe7, 0x29, 0x9c, 0x27, 0x48, 0x68, 0xe7, 0x1a, 0xb6, 0xae, 0x91, 0x52, 0xb8, 0x47, 0x0c, 0xf8, 0x6e, 0x86, 0x87, 0x7f, 0x47, 0xf0, 0x4b, 0xa9, 0xec, 0x32, 0xfc, 0xe9, 0x5b, 0x23, 0x78, 0x0f, 0xe5, 0x89, 0x0c, 0x7f, 0x56, 0xc5, 0x7f, 0x43, 0xf0, 0x4b, 0xa8, 0x5c, 0x32, 0xfc, 0x39, 0x86, 0xc3, 0xae, 0x3c, 0xd7, 0x44, 0xf5, 0x00, 0xc3, 0x5f, 0x50, 0xd3, 0xff, 0x9d, 0xe0, 0xec, 0x5c, 0x84, 0xe1, 0x2f, + 0xaa, 0xe9, 0xef, 0x27, 0xed, 0xdc, 0x4e, 0xe7, 0x06, 0x84, 0x39, 0x97, 0xd0, 0x91, 0x5b, 0xcd, 0x8c, 0x5f, 0x9d, 0xaf, 0xf8, 0x26, 0xba, 0x13, 0x0b, 0x77, 0x9b, 0x81, 0xe6, 0x33, 0x82, 0xef, 0x4a, 0xe0, 0xe4, 0x94, 0x12, 0x1f, 0x99, 0x96, 0xf8, 0xa6, 0x77, 0xde, 0x21, 0x09, 0x48, 0x3d, 0x53, 0x68, 0xda, 0x7d, 0x6a, 0x79, 0x30, 0xff, 0xb1, 0x79, 0xfe, 0xf7, 0xd7, 0xf6, 0x18, 0xf5, 0xa4, 0x75, 0x32, 0x02, 0x4f, 0xea, 0x91, 0xa2, 0xeb, 0xe9, 0xef, 0xfb, 0x89, 0x96, 0xc7, 0xbf, 0xa5, 0x7c, 0x9b, 0xe2, 0xf8, 0xed, 0xdb, 0x19, 0xbe, 0x43, 0xd9, 0x8c, 0xef, 0xa4, 0x72, 0xca, 0xf0, 0x3f, 0xdf, 0xcc, 0xfa, 0xe5, 0x44, 0x54, 0xbf, 0xef, 0x55, 0xfb, 0xab, 0x82, 0xe0, 0x9e, 0x08, 0x2e, 0x6e, 0x8f, 0xdd, 0x8f, 0xe2, 0x83, 0x2a, 0xfe, 0xa5, 0xf2, 0x3a, 0x37, 0x52, 0x2b, 0x1f, + 0xf8, 0x4a, 0x78, 0x83, 0x99, 0xaf, 0xa8, 0x6d, 0x7e, 0xd7, 0x32, 0x49, 0xcd, 0x4b, 0x78, 0x51, 0xf8, 0x79, 0x9c, 0x84, 0x59, 0xdb, 0xd9, 0x4a, 0x93, 0xa4, 0xdd, 0xc5, 0x08, 0x3b, 0x68, 0x5f, 0xbc, 0xa2, 0xa6, 0x05, 0x3d, 0xf2, 0x09, 0x95, 0x3b, 0x7f, 0x14, 0xde, 0x12, 0xfa, 0x2f, 0x8a, 0x47, 0xa7, 0x1f, 0xa2, 0xe2, 0xcb, 0x01, 0xa7, 0x72, 0xea, 0x52, 0xf5, 0x08, 0xb5, 0x3f, 0xc2, 0xc9, 0x34, 0xfd, 0x11, 0x1d, 0x6f, 0x64, 0xb8, 0x92, 0x19, 0x85, 0x07, 0xd5, 0x72, 0xec, 0x51, 0x78, 0x5b, 0x3f, 0xe5, 0x0f, 0x55, 0xcb, 0x6f, 0x34, 0xca, 0x01, 0xe8, 0x11, 0x15, 0xcf, 0x8e, 0xc2, 0x9b, 0x94, 0xd6, 0x33, 0x97, 0xdb, 0x33, 0x91, 0x95, 0x33, 0x93, 0x2b, 0xb4, 0x97, 0xac, 0xa3, 0xd9, 0x74, 0x8f, 0x84, 0xb6, 0x0f, 0xb9, 0x39, 0xa6, 0xef, 0x3d, 0xe1, 0xcf, 0xf9, 0x5b, 0xa8, + 0x3c, 0xb8, 0x55, 0x9b, 0x7c, 0x1f, 0x6d, 0x07, 0xc1, 0xb9, 0xa7, 0x48, 0xfa, 0x64, 0x34, 0xa9, 0x73, 0x4f, 0xc6, 0x84, 0xf3, 0xf7, 0xcb, 0xf4, 0x9d, 0xa9, 0x77, 0x3f, 0x8b, 0x91, 0xd7, 0x45, 0xc1, 0x20, 0x18, 0x00, 0x69, 0x11, 0xd3, 0xc6, 0x07, 0xa6, 0x4d, 0xaa, 0x6e, 0xda, 0xc0, 0x6f, 0x9a, 0x05, 0x43, 0x7e, 0xea, 0x7a, 0x2e, 0x39, 0x37, 0x29, 0x4f, 0x90, 0x52, 0xd5, 0xd8, 0x6f, 0x2c, 0xec, 0x38, 0x8d, 0x16, 0x5a, 0xeb, 0xaa, 0xe6, 0x9e, 0x6a, 0x5e, 0xbe, 0x73, 0xc9, 0xdc, 0xcb, 0xe2, 0x38, 0x8f, 0xc7, 0xd5, 0x34, 0x28, 0x38, 0x67, 0xb8, 0xff, 0x04, 0xfe, 0xea, 0xaa, 0x43, 0x37, 0x8c, 0xcc, 0xb8, 0x36, 0xd1, 0xe3, 0x0a, 0x4c, 0xbc, 0xf6, 0x7c, 0x7e, 0x37, 0xd2, 0xda, 0xc6, 0xcf, 0x20, 0x6d, 0xcb, 0x44, 0xdd, 0xac, 0x6d, 0x56, 0x7a, 0xe6, 0xe3, 0x85, 0xff, 0xf2, + 0x7a, 0xdb, 0xbc, 0x32, 0x9c, 0xbd, 0x21, 0x4e, 0x24, 0x96, 0x05, 0x18, 0x15, 0x70, 0xc7, 0x8d, 0x85, 0x67, 0x87, 0x5b, 0x93, 0xb9, 0x6a, 0x04, 0xb4, 0x9e, 0xa8, 0x54, 0x70, 0x6f, 0x5c, 0x4d, 0xd4, 0x15, 0x84, 0x85, 0x9a, 0xb4, 0x39, 0x9f, 0x05, 0xc0, 0xf2, 0x27, 0x47, 0x37, 0xbb, 0x05, 0xc3, 0x56, 0x0a, 0xf9, 0x1f, 0x3f, 0xa3, 0x6f, 0xeb, 0xdf, 0x78, 0xe3, 0xc4, 0x53, 0xdc, 0xba, 0x3b, 0x4d, 0x5f, 0x70, 0xef, 0x16, 0xee, 0xea, 0xd0, 0x8d, 0x6c, 0x7d, 0x59, 0x29, 0x0e, 0xa1, 0xf3, 0x2a, 0x49, 0xe5, 0x47, 0x92, 0xce, 0x5f, 0x08, 0x4e, 0xfa, 0x8b, 0x70, 0x88, 0x6c, 0xfd, 0x3c, 0x88, 0x30, 0x89, 0x39, 0x22, 0x36, 0x90, 0x98, 0x7c, 0x9f, 0xcb, 0x55, 0x94, 0x23, 0xc9, 0xe9, 0x64, 0x9c, 0x09, 0xa5, 0x2e, 0xc0, 0xe5, 0x3c, 0x65, 0x13, 0xd5, 0x70, 0xcc, 0x53, 0x4f, 0x9f, + 0x64, 0x60, 0x07, 0x96, 0x53, 0x3c, 0xe2, 0x90, 0x03, 0x49, 0x99, 0xc9, 0x2e, 0x59, 0x29, 0xb0, 0x25, 0xa5, 0xe7, 0x7a, 0xa6, 0x9d, 0x38, 0xd1, 0x1e, 0xa8, 0x70, 0xe3, 0x9f, 0xa5, 0x94, 0xe7, 0x3e, 0x17, 0xfa, 0x7b, 0x52, 0x20, 0xa5, 0xbe, 0x48, 0xb2, 0x27, 0xd8, 0xb9, 0x41, 0xf1, 0x89, 0x76, 0x31, 0xf4, 0x23, 0x42, 0x3a, 0x36, 0xbb, 0x3d, 0x9c, 0xdb, 0x9b, 0x1c, 0xda, 0x96, 0xe6, 0xb3, 0xbb, 0x07, 0xab, 0x5c, 0x61, 0x08, 0x95, 0x91, 0x24, 0x95, 0x7f, 0x0c, 0xd2, 0x39, 0xc4, 0x10, 0xaa, 0xf3, 0x93, 0x54, 0xce, 0x96, 0xa7, 0xd9, 0xe2, 0xc6, 0x6f, 0x23, 0x9c, 0x43, 0xd2, 0xce, 0x75, 0x8c, 0xb8, 0xf8, 0x91, 0x8a, 0xdf, 0x83, 0x10, 0xff, 0x17, 0x5a, 0x4e, 0x32, 0x5b, 0x3b, 0x36, 0x69, 0xb6, 0xd2, 0x4a, 0xfe, 0x2f, 0x34, 0x7d, 0x32, 0xeb, 0x23, 0xb4, 0x83, 0xf6, 0x11, + 0x91, 0x54, 0xfe, 0x3b, 0xfa, 0xb6, 0xaa, 0x2c, 0x58, 0x2c, 0x80, 0x4b, 0x76, 0x2c, 0x71, 0x78, 0x16, 0x82, 0xd7, 0x30, 0x10, 0x08, 0x14, 0x5d, 0x60, 0x7a, 0x61, 0x45, 0xff, 0xb1, 0xd8, 0xd2, 0x4b, 0xf2, 0x93, 0xd9, 0xde, 0x17, 0xf9, 0x5f, 0xb2, 0xdf, 0xc5, 0x7f, 0xd7, 0xfb, 0xfa, 0x9e, 0x3d, 0x27, 0x5e, 0x7f, 0x7d, 0xdf, 0x3e, 0x6e, 0x1d, 0x1d, 0x97, 0x2d, 0x8a, 0x66, 0xbb, 0x6d, 0x36, 0xd6, 0x8b, 0xff, 0x0c, 0xf5, 0x12, 0xdc, 0x01, 0x38, 0x9d, 0xcb, 0x80, 0x17, 0x30, 0x1c, 0xe6, 0xf4, 0x16, 0xad, 0xbd, 0x5f, 0x1b, 0xf3, 0x89, 0x1f, 0xa9, 0xf9, 0x88, 0x2d, 0x68, 0xc4, 0xa5, 0x64, 0x0d, 0x0f, 0x7f, 0x2e, 0xfd, 0x9e, 0xce, 0x49, 0xf6, 0x7d, 0x6e, 0xa1, 0x42, 0x4d, 0x7f, 0x97, 0x78, 0x8f, 0x78, 0x40, 0xc7, 0xe7, 0x0b, 0x59, 0x9a, 0x6c, 0x48, 0x3f, 0x26, 0xe9, 0x73, + 0xe0, 0xbb, 0xd3, 0xe3, 0x39, 0x10, 0x76, 0x32, 0xd9, 0xc0, 0x78, 0xee, 0x11, 0x85, 0x28, 0x8a, 0x9b, 0x83, 0xb2, 0x7d, 0xc9, 0x75, 0x2e, 0x49, 0x4e, 0xa5, 0x72, 0x01, 0xd1, 0x6b, 0xa5, 0x12, 0x9c, 0x5b, 0x80, 0x53, 0x92, 0x41, 0x4c, 0x20, 0x6e, 0x33, 0x8d, 0x68, 0x0a, 0x4f, 0xfc, 0xa4, 0x1f, 0xbf, 0xc8, 0x73, 0x4a, 0x6e, 0x5a, 0x4d, 0x56, 0x4d, 0xaa, 0x92, 0x26, 0x1d, 0xbc, 0x3f, 0x23, 0xdb, 0x81, 0x3f, 0x70, 0xa4, 0xda, 0x93, 0x33, 0xf0, 0x6f, 0xe2, 0xb3, 0x33, 0xe8, 0x7b, 0x2a, 0xc5, 0x53, 0xe3, 0xed, 0xdd, 0x5c, 0x59, 0xc9, 0xf7, 0x34, 0x54, 0xf6, 0x72, 0xe2, 0x36, 0x7b, 0x5a, 0xef, 0x23, 0xe9, 0x83, 0x33, 0x9b, 0xea, 0xf9, 0xe9, 0x69, 0xf6, 0xd3, 0x6b, 0x9f, 0x91, 0x06, 0x6b, 0xed, 0x93, 0xfe, 0x97, 0xda, 0xc7, 0xe4, 0x55, 0xfa, 0x31, 0x95, 0xb3, 0x54, 0x26, + 0xaf, 0x9c, 0x11, 0xcf, 0x60, 0x38, 0xf0, 0x68, 0xce, 0x13, 0x8b, 0x47, 0xc3, 0xb7, 0x69, 0x79, 0xf1, 0x9f, 0x39, 0xb7, 0x81, 0x83, 0xb7, 0x10, 0x3c, 0x8d, 0xe6, 0xdd, 0x8d, 0x17, 0xf7, 0xcd, 0x4b, 0xd3, 0xa4, 0xeb, 0x69, 0xf6, 0xe2, 0xab, 0x63, 0xa7, 0x91, 0x3e, 0xd7, 0xd3, 0x3c, 0x1b, 0x2b, 0x0d, 0xd1, 0x19, 0x8c, 0x93, 0xa6, 0xa9, 0x3a, 0xe3, 0x72, 0x93, 0xcd, 0x93, 0x8c, 0x32, 0x83, 0xe9, 0xf4, 0x4a, 0x6d, 0x09, 0x75, 0x85, 0xc0, 0x04, 0x21, 0x19, 0x25, 0xfb, 0x3c, 0x3e, 0x51, 0xa6, 0x0f, 0xc2, 0xe0, 0x72, 0x3d, 0xb1, 0xb9, 0xb5, 0x1e, 0x2e, 0xc7, 0xb5, 0xc4, 0xee, 0x58, 0x98, 0xe6, 0x73, 0xf5, 0x9e, 0xe0, 0xf8, 0x83, 0x8b, 0x33, 0x32, 0x5c, 0x7c, 0x19, 0xcf, 0x53, 0xe7, 0x05, 0x49, 0x42, 0x9a, 0xb7, 0xc6, 0xd3, 0x5b, 0x20, 0x6e, 0x8b, 0xf7, 0x9c, 0xfc, 0x24, + 0xb3, 0x3e, 0x95, 0xff, 0x95, 0xfe, 0x2d, 0x13, 0xf4, 0x76, 0x3e, 0x49, 0x56, 0xf7, 0x47, 0xd4, 0x55, 0xfe, 0x11, 0xd3, 0xf7, 0x7a, 0xf5, 0x34, 0x47, 0xf0, 0x8a, 0xb0, 0xa2, 0x7e, 0x8b, 0xd2, 0xc7, 0x2e, 0xd1, 0xee, 0x6a, 0x2d, 0xd4, 0x75, 0x84, 0x01, 0x27, 0x3a, 0x62, 0x91, 0x36, 0x87, 0x8c, 0xb8, 0x94, 0xac, 0xe1, 0xe1, 0xcf, 0x2d, 0x3f, 0xa1, 0x73, 0x88, 0xf5, 0x89, 0x5b, 0x72, 0x69, 0x73, 0x48, 0xbe, 0x9a, 0xce, 0x21, 0x86, 0xcf, 0x97, 0x54, 0x1e, 0x15, 0xfe, 0x42, 0xfc, 0x94, 0xca, 0x80, 0x57, 0xbd, 0x4b, 0xb9, 0x2e, 0x26, 0xbe, 0xdb, 0x84, 0x7b, 0x75, 0xfc, 0x69, 0x7c, 0x67, 0x04, 0xa7, 0xfc, 0xd5, 0xab, 0xde, 0xa5, 0x34, 0xe2, 0xdf, 0xea, 0xf8, 0x73, 0x26, 0xfc, 0x61, 0x1d, 0x7f, 0xc1, 0x88, 0x53, 0x1e, 0xe0, 0x55, 0x6d, 0xec, 0x3b, 0x63, 0xd6, 0x7b, 0x08, + 0x70, 0x38, 0xa3, 0x03, 0x5c, 0xdc, 0x45, 0x6f, 0x3b, 0x64, 0x07, 0x33, 0x90, 0xba, 0xa6, 0x55, 0xc3, 0xc2, 0x45, 0x17, 0xda, 0x1a, 0x34, 0xa6, 0xb0, 0xb0, 0x30, 0x4f, 0x80, 0x79, 0x4f, 0xf7, 0xfb, 0x73, 0xf3, 0xf4, 0xd3, 0x53, 0x21, 0x25, 0x51, 0xae, 0x31, 0x1f, 0x4e, 0x89, 0x9f, 0xd2, 0x2d, 0xff, 0x07, 0x94, 0xef, 0x0e, 0x2e, 0x58, 0x70, 0x10, 0xcb, 0x0f, 0xd0, 0x63, 0x80, 0xf7, 0xe0, 0x60, 0x60, 0xe9, 0xf3, 0xd7, 0xb7, 0xb7, 0x5f, 0x0f, 0xff, 0x5d, 0xdd, 0xce, 0xfd, 0xe2, 0x41, 0xe5, 0x5f, 0x47, 0x17, 0x2e, 0x3c, 0x8a, 0xe3, 0x1f, 0xdc, 0x8e, 0x1d, 0x47, 0x17, 0x2d, 0x3a, 0xaa, 0x7c, 0xb9, 0xfd, 0xba, 0x37, 0x36, 0x8d, 0x19, 0xb3, 0xe9, 0x8d, 0xeb, 0x56, 0x1f, 0xdf, 0xd4, 0xd9, 0xb9, 0xe9, 0x38, 0xf0, 0x9a, 0x9b, 0x48, 0xfb, 0xfe, 0x46, 0xed, 0x2c, 0x6f, 0x98, + 0xf1, 0xe4, 0xea, 0xe8, 0xbb, 0x94, 0xfd, 0x7d, 0x43, 0x8e, 0xfe, 0x0d, 0x3e, 0xf5, 0x1b, 0xa4, 0xff, 0x93, 0x6f, 0x40, 0x5d, 0x44, 0x36, 0x3f, 0xa5, 0x7b, 0x17, 0x5e, 0x55, 0x36, 0x37, 0x1b, 0xc6, 0x24, 0x4f, 0xc7, 0xdf, 0xc6, 0x77, 0x19, 0xf0, 0x87, 0x74, 0xfc, 0x1d, 0xd3, 0x18, 0xea, 0x63, 0x8e, 0xdf, 0x35, 0xe1, 0x9f, 0xeb, 0xf8, 0x7b, 0x46, 0x9c, 0xee, 0x6d, 0x7b, 0xd5, 0xfb, 0x95, 0x2a, 0x0e, 0xed, 0xd1, 0x65, 0xa1, 0x80, 0xc9, 0x60, 0x9f, 0x76, 0x45, 0x64, 0x65, 0x2f, 0xfd, 0x9d, 0x27, 0xf9, 0x9e, 0x04, 0x99, 0x23, 0x7f, 0xf2, 0xa0, 0xe1, 0xb8, 0x9e, 0x1d, 0xd4, 0x16, 0xc2, 0x63, 0x09, 0x09, 0xb6, 0x5b, 0x79, 0x5e, 0x9a, 0x4e, 0x38, 0x4f, 0x37, 0x70, 0x34, 0x7a, 0xa4, 0xb4, 0xa4, 0xd3, 0x82, 0x65, 0x99, 0x6e, 0xea, 0x2d, 0x10, 0xe0, 0x5c, 0xd6, 0x3e, 0x70, + 0xfa, 0xb9, 0xd1, 0xe9, 0xe3, 0xce, 0xb0, 0x7c, 0xc7, 0x19, 0x96, 0x9f, 0x7a, 0x46, 0xe5, 0x07, 0x2b, 0x90, 0x45, 0x94, 0x45, 0x88, 0xfd, 0x3e, 0x40, 0x16, 0x14, 0xc9, 0xd1, 0xd5, 0x45, 0xdf, 0xcb, 0x0c, 0x1b, 0x3a, 0xa4, 0xb9, 0x30, 0x39, 0x50, 0x98, 0xef, 0x4a, 0xb0, 0xda, 0xbc, 0x25, 0xf0, 0x40, 0x03, 0x6b, 0x51, 0x99, 0x55, 0xc9, 0x63, 0x97, 0x13, 0x20, 0x24, 0x7c, 0xe4, 0xec, 0x97, 0x1d, 0xb0, 0x11, 0xce, 0xe5, 0xe0, 0x93, 0x93, 0x28, 0x3d, 0xac, 0xae, 0xf2, 0x08, 0xc1, 0xd0, 0x4f, 0xb9, 0x86, 0xde, 0x2f, 0x35, 0x19, 0xd4, 0x64, 0x92, 0x7b, 0x26, 0xfb, 0xa2, 0x9a, 0xe9, 0xf3, 0x86, 0xd6, 0x6d, 0xfe, 0xd7, 0xae, 0x99, 0x11, 0x99, 0xbd, 0xf0, 0xc9, 0x7f, 0xde, 0x91, 0xe8, 0xf3, 0x26, 0x54, 0x14, 0xa4, 0x64, 0xba, 0xe3, 0x44, 0xa7, 0x28, 0x0d, 0x5a, 0xde, 0x5b, + 0xa2, 0x8a, 0xe6, 0x6a, 0x55, 0x54, 0x4f, 0xde, 0xea, 0xc9, 0x18, 0xdf, 0x56, 0x3d, 0x33, 0x60, 0x5b, 0x7e, 0x18, 0xf3, 0x5b, 0x22, 0xa2, 0x7c, 0xaf, 0xa2, 0x1c, 0xfa, 0x91, 0xdd, 0x9e, 0xe9, 0x2f, 0x48, 0xf1, 0x17, 0xcb, 0xb2, 0x1c, 0xdf, 0x57, 0x36, 0xc4, 0xed, 0xb1, 0xf5, 0x8e, 0xf8, 0xa0, 0x09, 0x7f, 0x5e, 0x97, 0x35, 0xa2, 0xa7, 0xd4, 0x3b, 0xb1, 0x77, 0xf6, 0xb9, 0x13, 0xcb, 0xd2, 0x1e, 0x34, 0xa5, 0x4d, 0xa5, 0x6b, 0x17, 0x44, 0x0a, 0x4e, 0xa4, 0x69, 0x53, 0xd5, 0xdb, 0xef, 0x74, 0x11, 0xd3, 0xf3, 0x64, 0xb0, 0x3c, 0x74, 0x6d, 0xd8, 0xdc, 0xe7, 0x4c, 0x99, 0xa5, 0x79, 0xdf, 0x54, 0x2e, 0xbb, 0x6b, 0x7b, 0x67, 0x9f, 0xbb, 0xb6, 0x6c, 0xce, 0x4c, 0xd5, 0xca, 0x13, 0x6f, 0xa4, 0x75, 0x93, 0x34, 0x5c, 0x54, 0x79, 0x16, 0xb7, 0x5e, 0x1e, 0xf9, 0x16, 0xec, 0xc5, + 0xaa, 0xce, 0x66, 0x69, 0xb1, 0xd7, 0x94, 0x36, 0x53, 0xef, 0x97, 0x0d, 0xa6, 0xb9, 0x99, 0xa1, 0x97, 0xb1, 0x1b, 0xff, 0x9c, 0xed, 0xfd, 0xd3, 0x32, 0x2c, 0xf0, 0xe0, 0x40, 0xdb, 0xfb, 0x0f, 0x0f, 0xa5, 0xfa, 0xed, 0x15, 0xbd, 0x8c, 0x76, 0x85, 0xde, 0xd5, 0x54, 0xa6, 0x46, 0xe1, 0x2d, 0xa1, 0x3d, 0x14, 0x8f, 0x4e, 0x3f, 0x84, 0xe1, 0xe1, 0xd1, 0x14, 0x3f, 0xa2, 0xe3, 0x8d, 0x8a, 0x8f, 0xa6, 0x9f, 0x10, 0x85, 0x07, 0xd5, 0x72, 0x86, 0x44, 0xe1, 0x6d, 0x6a, 0x39, 0x8b, 0x28, 0xbe, 0x4f, 0xc7, 0x07, 0xb3, 0x72, 0xd4, 0xf2, 0x23, 0x78, 0x93, 0x72, 0xe9, 0x29, 0xd6, 0x30, 0xd0, 0x43, 0x70, 0xff, 0x2a, 0x03, 0xe9, 0xf7, 0xaf, 0xe0, 0xbc, 0x02, 0x21, 0x61, 0x35, 0xbd, 0x83, 0x97, 0x06, 0x2f, 0x93, 0xe1, 0x56, 0x3c, 0x2f, 0x20, 0x7e, 0x0e, 0x7d, 0xf1, 0x04, 0x77, 0x2d, + 0x4a, 0x3a, 0xc9, 0x04, 0x2b, 0x15, 0xc6, 0xc4, 0xd9, 0x31, 0x4a, 0xf5, 0xa4, 0x24, 0xd9, 0xd3, 0xe2, 0xd2, 0xd8, 0x7d, 0x3b, 0x76, 0xed, 0x85, 0x5e, 0x53, 0x23, 0xf4, 0xc4, 0xad, 0xde, 0x4c, 0xa3, 0x8c, 0x90, 0x9b, 0x3b, 0x71, 0xe5, 0xe4, 0xe2, 0x87, 0x1f, 0xfd, 0xc3, 0x1f, 0x36, 0xbc, 0x71, 0xe3, 0xd0, 0xf6, 0x75, 0xc7, 0xd7, 0xfe, 0xe1, 0x04, 0x77, 0xa0, 0x78, 0x6c, 0xf7, 0xd0, 0x35, 0x77, 0x87, 0x8e, 0x71, 0x7f, 0x0a, 0xfe, 0xe8, 0xd1, 0x79, 0xf3, 0x9e, 0xb8, 0x7a, 0x58, 0xe8, 0x9f, 0xf4, 0x5e, 0xfc, 0xe9, 0xb4, 0x23, 0x0d, 0xda, 0x91, 0x1e, 0xb3, 0x1d, 0xd2, 0xb9, 0x6a, 0x87, 0xba, 0xe7, 0xbe, 0x9a, 0x72, 0x89, 0x2c, 0xf5, 0xfc, 0xfb, 0x39, 0x03, 0x9e, 0xc1, 0x70, 0x7a, 0x0e, 0x7d, 0x38, 0xea, 0x9e, 0x37, 0x47, 0x52, 0x23, 0x61, 0x33, 0xbd, 0x13, 0xe8, 0x43, + 0x3f, 0xea, 0xdc, 0xe3, 0x21, 0x1a, 0x2f, 0x00, 0xd7, 0x46, 0x45, 0x09, 0x89, 0x73, 0xe8, 0x3d, 0x00, 0x1b, 0xbd, 0x07, 0x00, 0x17, 0x1b, 0xb9, 0x2e, 0x0b, 0xf9, 0xa8, 0xf9, 0x70, 0x12, 0x33, 0x57, 0x22, 0xfa, 0x2e, 0x8f, 0xfc, 0x55, 0x66, 0x37, 0x06, 0x62, 0x65, 0x61, 0xc9, 0xba, 0x82, 0x99, 0x09, 0x4e, 0xf5, 0xde, 0x80, 0x37, 0x3d, 0xd5, 0xe9, 0x4b, 0xf0, 0x45, 0x6e, 0x0d, 0xda, 0x6d, 0x86, 0x6e, 0xa0, 0xb7, 0x06, 0x4d, 0x5d, 0x41, 0x2f, 0x0e, 0x5c, 0xa1, 0x76, 0xc7, 0x2b, 0xaf, 0x18, 0x3a, 0xe4, 0xa5, 0x97, 0xb4, 0x2e, 0x79, 0x6b, 0xcb, 0x5b, 0xc6, 0x3e, 0xe1, 0x37, 0x6c, 0xc1, 0x57, 0xe9, 0xdf, 0x3e, 0x5f, 0xfb, 0x76, 0xb1, 0x11, 0xbd, 0x41, 0x79, 0x23, 0xa7, 0xf1, 0x46, 0xc3, 0xf8, 0xc5, 0xa3, 0x6c, 0xd4, 0x12, 0x1c, 0x0c, 0x0e, 0x6f, 0x31, 0x4a, 0x4b, 0xe1, + 0x78, 0x9c, 0xea, 0xe1, 0x04, 0x9e, 0xef, 0x80, 0xf7, 0x8d, 0x02, 0x26, 0x03, 0x0a, 0x4f, 0xe0, 0x44, 0xc3, 0x80, 0x3a, 0x1d, 0x70, 0x0b, 0xc2, 0x91, 0xed, 0xcc, 0xf6, 0xe5, 0x4b, 0x52, 0x4a, 0x09, 0x31, 0x1a, 0xd8, 0x48, 0x56, 0x57, 0xd5, 0x17, 0xd4, 0xf3, 0xf4, 0x1b, 0x24, 0xd9, 0x23, 0x17, 0x48, 0xcc, 0xeb, 0x42, 0x75, 0xf3, 0x10, 0x18, 0xd0, 0x6c, 0xef, 0xdc, 0xeb, 0xa6, 0xc2, 0x47, 0x5c, 0xb1, 0xfc, 0x82, 0x55, 0x4b, 0x3a, 0xa6, 0x92, 0x81, 0xfd, 0x2c, 0xb0, 0x72, 0x1c, 0x8c, 0x2b, 0xbf, 0x65, 0x12, 0xfe, 0x0a, 0x3e, 0xe3, 0x86, 0xe3, 0x4d, 0x37, 0x4f, 0xda, 0xe5, 0x64, 0xa3, 0x6b, 0x94, 0x31, 0x2f, 0xaa, 0x0f, 0xd6, 0xc8, 0x58, 0xbd, 0x68, 0x2a, 0xe9, 0xc2, 0x26, 0x8a, 0x46, 0x71, 0x87, 0xab, 0xf4, 0x44, 0xd0, 0xbc, 0x71, 0x5e, 0x26, 0x68, 0x96, 0xfe, 0x05, + 0xbe, 0x3a, 0xa6, 0xa8, 0x9d, 0xc0, 0xcf, 0x15, 0x8f, 0xe9, 0x1e, 0xba, 0xf6, 0xae, 0xd0, 0x7f, 0x93, 0x7e, 0xbd, 0x02, 0xfa, 0x75, 0xc5, 0x70, 0xe5, 0x5f, 0xe2, 0x36, 0x25, 0xa4, 0xc9, 0xd4, 0x27, 0x54, 0xd6, 0x7c, 0xaa, 0xac, 0xbd, 0x8c, 0x22, 0xb8, 0x57, 0xc7, 0xf7, 0xa2, 0x9f, 0x69, 0x36, 0xb5, 0xf0, 0x09, 0xe5, 0xd1, 0x3e, 0xd5, 0xa6, 0x0e, 0x53, 0xfc, 0x52, 0x73, 0x7a, 0x71, 0xbb, 0x96, 0x9e, 0xe2, 0x6e, 0x1d, 0x7f, 0xd0, 0x84, 0x67, 0x30, 0x9c, 0xca, 0xf2, 0x4f, 0x63, 0xdc, 0x0d, 0xda, 0x6c, 0xac, 0x8b, 0xd8, 0xcb, 0xdf, 0xe8, 0x67, 0xf5, 0x9f, 0x50, 0x2e, 0xec, 0x53, 0xed, 0xe8, 0x6f, 0xd4, 0x7b, 0x1c, 0x3f, 0xd3, 0xef, 0x7f, 0x1a, 0xf2, 0x11, 0x3b, 0xfa, 0x5b, 0x8a, 0xb7, 0x84, 0x3f, 0xe7, 0xff, 0x46, 0xbf, 0x35, 0x57, 0xfd, 0xd6, 0x67, 0x0c, 0xb8, 0x57, + 0xc7, 0x9f, 0x46, 0xaf, 0xc4, 0xc4, 0xf7, 0x6a, 0xb8, 0xb2, 0x92, 0xff, 0x1b, 0xe5, 0x77, 0xb9, 0xd4, 0x7e, 0xff, 0xef, 0x2b, 0xd9, 0xbe, 0x12, 0x4d, 0x4f, 0x78, 0x69, 0x2b, 0x1a, 0x12, 0x6c, 0x6a, 0xc5, 0x58, 0xcc, 0xc1, 0xc4, 0x9a, 0xea, 0x40, 0xe0, 0x46, 0x00, 0xf1, 0x3d, 0x54, 0xf6, 0x84, 0xc5, 0x64, 0x46, 0x51, 0xea, 0x90, 0xd3, 0xa9, 0x5d, 0xec, 0xf3, 0x71, 0x63, 0x5a, 0x9a, 0x8a, 0x0b, 0xf3, 0xfd, 0x19, 0xe9, 0xfe, 0x5c, 0x2a, 0x7e, 0xa6, 0x4b, 0x60, 0x52, 0xb2, 0xfa, 0xe2, 0x96, 0xed, 0x1c, 0xe9, 0x4c, 0x42, 0xaa, 0xa5, 0xaf, 0x6f, 0x53, 0x3c, 0xfc, 0x3d, 0xb6, 0x14, 0x57, 0x62, 0xf9, 0x88, 0x8b, 0x87, 0xb5, 0xce, 0x0c, 0xfa, 0x9a, 0x2e, 0xbe, 0xfa, 0xba, 0xab, 0x2f, 0x6e, 0x1a, 0x7a, 0xcd, 0xfe, 0xe5, 0x23, 0xae, 0xbf, 0x7c, 0x5e, 0x61, 0x8b, 0x35, 0x2d, + 0x21, 0xb9, 0x7e, 0xe2, 0xd2, 0x89, 0x6d, 0x0b, 0x46, 0x06, 0x06, 0xd3, 0x1f, 0x07, 0x37, 0x2f, 0x7f, 0x7c, 0xe1, 0x8a, 0xe3, 0x9d, 0xc2, 0x33, 0xae, 0x04, 0x5f, 0x91, 0xcf, 0xd7, 0x38, 0xb6, 0x6c, 0xf0, 0x98, 0xba, 0xc2, 0x92, 0x86, 0x29, 0x2b, 0xbb, 0x66, 0x3e, 0x7a, 0xcd, 0x48, 0x6f, 0xe5, 0x88, 0x92, 0xf9, 0x71, 0x09, 0xa5, 0xb5, 0xa5, 0xfe, 0xa1, 0x33, 0x1a, 0x5a, 0x27, 0xd4, 0x05, 0x8a, 0xea, 0xa6, 0xad, 0xb9, 0x78, 0xec, 0x6d, 0x4b, 0x86, 0x8d, 0x1d, 0x85, 0x0c, 0x7d, 0x93, 0xc1, 0xfa, 0x80, 0x8e, 0xdf, 0xcb, 0x7d, 0xec, 0xcd, 0x16, 0x65, 0xb3, 0xb1, 0x9f, 0xf0, 0x9f, 0xaf, 0xd4, 0xf3, 0x0a, 0x58, 0xbe, 0x45, 0xeb, 0x57, 0x71, 0x93, 0xb1, 0xbf, 0xe9, 0xda, 0x49, 0xcb, 0x14, 0x6f, 0x43, 0xaf, 0xb0, 0x32, 0xb9, 0xa8, 0x32, 0xb5, 0x7a, 0x51, 0x00, 0xca, 0x34, + 0xd4, 0x1e, 0xd1, 0x84, 0x90, 0x16, 0xce, 0x58, 0xd7, 0xd2, 0xf1, 0xcb, 0x53, 0xc7, 0xef, 0x18, 0xc3, 0x89, 0x0c, 0xaf, 0xa5, 0xf2, 0x91, 0xa7, 0xca, 0xf0, 0x31, 0x3a, 0x7e, 0x34, 0x3d, 0x7d, 0x4f, 0xe6, 0x0d, 0xa6, 0xc2, 0x4e, 0x03, 0xdf, 0x85, 0x60, 0x53, 0x04, 0xd1, 0x37, 0x64, 0xd4, 0x0b, 0x04, 0x58, 0x45, 0x70, 0xe0, 0x46, 0x38, 0x9d, 0xb0, 0x56, 0x69, 0x78, 0x5b, 0xa9, 0x13, 0xe6, 0x0b, 0x7f, 0x3b, 0x99, 0x22, 0xfc, 0xed, 0x19, 0x34, 0x40, 0x19, 0x69, 0x50, 0x46, 0xba, 0x5a, 0x86, 0x74, 0xca, 0x32, 0x94, 0xad, 0xb4, 0x0c, 0x3f, 0xba, 0xfe, 0x34, 0xef, 0x8e, 0x64, 0x49, 0x44, 0x43, 0x8b, 0x5d, 0x44, 0xaa, 0xe6, 0x74, 0x5a, 0xe4, 0x18, 0xf7, 0x45, 0xf2, 0xb5, 0x4d, 0xd5, 0xbe, 0x09, 0x4d, 0x77, 0x44, 0x12, 0xc8, 0x1f, 0xfc, 0xc8, 0xaf, 0xde, 0x10, 0x01, 0xfa, + 0xda, 0xff, 0x0d, 0x11, 0xfa, 0x05, 0xc9, 0x3e, 0x17, 0x77, 0x73, 0x3f, 0x27, 0xea, 0xd5, 0xf0, 0x65, 0x4a, 0x43, 0xcc, 0xb3, 0x73, 0xf2, 0xb1, 0xcf, 0x9c, 0x4c, 0xa1, 0xeb, 0x6e, 0xf8, 0x38, 0xe1, 0x05, 0x32, 0xbd, 0x17, 0x92, 0x8e, 0x96, 0x04, 0xe3, 0xd3, 0x30, 0x12, 0xe2, 0x65, 0x81, 0x83, 0x6d, 0x55, 0xf5, 0x6e, 0x48, 0x3e, 0x51, 0x91, 0xdc, 0x72, 0x19, 0xdc, 0xcc, 0xb1, 0xdb, 0x6b, 0xf3, 0x3a, 0xd9, 0x5f, 0xb4, 0x3b, 0x2f, 0x18, 0xcf, 0x85, 0x5d, 0xd9, 0x2c, 0x9a, 0x8e, 0x5e, 0x9b, 0x9d, 0x16, 0x23, 0x41, 0x57, 0xd0, 0xc9, 0xf3, 0x7c, 0x3a, 0x9f, 0x9e, 0xef, 0x4a, 0xcc, 0x4b, 0x4a, 0x80, 0x3d, 0x3d, 0x0c, 0x3e, 0xd6, 0x20, 0xbc, 0x00, 0x7d, 0x11, 0xe1, 0x62, 0x4e, 0x1f, 0x9a, 0xb0, 0x9c, 0xcc, 0xef, 0xff, 0x07, 0xde, 0xa4, 0x2c, 0x7b, 0x5b, 0x79, 0x04, 0xee, + 0x80, 0x70, 0x85, 0xe4, 0x5b, 0x1c, 0xea, 0x35, 0x90, 0xab, 0x84, 0xbf, 0x29, 0x2f, 0xe1, 0xe1, 0xa1, 0x0f, 0x43, 0xb5, 0xf8, 0x09, 0x25, 0x3f, 0xc7, 0xcb, 0x7d, 0xc1, 0xcd, 0x7f, 0x86, 0xde, 0x02, 0x79, 0x64, 0xbc, 0x2e, 0x77, 0x19, 0x4c, 0xbe, 0xa8, 0x74, 0x1e, 0xed, 0x33, 0x37, 0xb2, 0x88, 0x0e, 0x33, 0xc8, 0x20, 0xd1, 0x61, 0x9a, 0x6c, 0x7e, 0x6d, 0xc4, 0x89, 0xee, 0xd2, 0xf0, 0x7b, 0x8c, 0xb8, 0x94, 0xac, 0xe2, 0x0f, 0x84, 0xff, 0x29, 0xfa, 0xa9, 0x4e, 0x0b, 0xa8, 0xfb, 0x11, 0x23, 0x62, 0xe2, 0xbb, 0x4d, 0xb8, 0x57, 0xc7, 0x9f, 0xc6, 0x13, 0x23, 0x38, 0xdd, 0x8f, 0x08, 0xa8, 0xfb, 0x11, 0x46, 0xfc, 0x5b, 0x1d, 0x7f, 0xce, 0x84, 0x3f, 0xac, 0xe3, 0x2f, 0x18, 0x71, 0xaa, 0xef, 0x03, 0xea, 0x7e, 0x84, 0x11, 0xef, 0xd2, 0xf1, 0x97, 0xf0, 0xe4, 0x98, 0xed, 0x39, + 0xa4, 0xa5, 0x27, 0x36, 0xab, 0x9f, 0x7e, 0x6f, 0x40, 0xb5, 0x55, 0x27, 0x18, 0xd2, 0xe7, 0xe9, 0xf8, 0xdb, 0xfd, 0x94, 0xb3, 0xd7, 0x54, 0xef, 0xf3, 0x2a, 0x0e, 0xb6, 0xc0, 0x44, 0xd5, 0x1e, 0x99, 0x88, 0x4c, 0xa7, 0x4b, 0x5a, 0x5a, 0x7a, 0x0e, 0x15, 0x50, 0x39, 0xfa, 0x8a, 0x53, 0xa4, 0xad, 0xd0, 0xd3, 0xee, 0x3d, 0x83, 0x72, 0x77, 0x12, 0x55, 0x30, 0x60, 0xda, 0xc8, 0x77, 0x10, 0x7b, 0x2b, 0x66, 0xbf, 0x12, 0x7b, 0x4b, 0xeb, 0xa7, 0xd7, 0x23, 0xfd, 0x04, 0xe7, 0x6b, 0x78, 0x7c, 0xf4, 0xf9, 0x1a, 0x49, 0x73, 0x8f, 0xb1, 0x2f, 0xa5, 0x64, 0x63, 0x99, 0xd4, 0xf6, 0x09, 0xa8, 0xb6, 0xcf, 0xc4, 0x3e, 0xfa, 0x96, 0xd5, 0xfb, 0x8e, 0x5e, 0xef, 0x2d, 0x6a, 0xde, 0xc8, 0x5b, 0xe0, 0x42, 0x55, 0x5e, 0x32, 0xa2, 0xde, 0x96, 0x16, 0xaa, 0xe3, 0xc6, 0xde, 0xa5, 0x11, 0x4b, + 0xc0, 0x22, 0x50, 0x79, 0x2c, 0x62, 0x72, 0xca, 0x2d, 0x8b, 0x89, 0xef, 0x36, 0xe1, 0x5e, 0x1d, 0x7f, 0x9a, 0xbb, 0x36, 0x82, 0x53, 0x79, 0x64, 0xf8, 0x73, 0x46, 0x9c, 0xce, 0xb9, 0x22, 0x75, 0x0f, 0xf6, 0xda, 0x3e, 0x73, 0xce, 0xab, 0x6c, 0xb6, 0x08, 0xb4, 0x6d, 0x45, 0xea, 0x1e, 0xec, 0xb5, 0x54, 0xdf, 0x3a, 0xc9, 0xb7, 0xd8, 0x89, 0xbe, 0xf5, 0xa2, 0x9c, 0x60, 0xa6, 0xd7, 0x1d, 0x4f, 0xd4, 0x87, 0xea, 0xb5, 0x55, 0xdf, 0x07, 0x2d, 0xcc, 0x2d, 0xcc, 0x01, 0xe5, 0xef, 0xa3, 0x3b, 0xa0, 0x92, 0x4c, 0xd5, 0x85, 0x95, 0x3a, 0xb7, 0xc3, 0xda, 0x71, 0x89, 0x68, 0xff, 0xb5, 0x27, 0xc3, 0xa2, 0xdc, 0x62, 0xb1, 0x2b, 0x3f, 0xb6, 0xa5, 0x27, 0xbd, 0x86, 0x3f, 0x55, 0x8a, 0x7f, 0xed, 0x49, 0xb1, 0xe0, 0x6e, 0x8b, 0x03, 0x9f, 0x67, 0x4d, 0x71, 0x1d, 0x57, 0x0a, 0xd2, 0x1c, + 0xdc, 0xf8, 0xb4, 0xc4, 0xd0, 0xfb, 0x8e, 0x34, 0xee, 0x4f, 0xdc, 0x01, 0x87, 0x23, 0xf4, 0xf3, 0xf4, 0x14, 0x1c, 0x4e, 0x48, 0x08, 0x4d, 0x18, 0xb0, 0x1d, 0x69, 0xb4, 0x1d, 0xe9, 0x6a, 0x3b, 0xa4, 0xff, 0x64, 0x3b, 0x48, 0x3f, 0xd1, 0x76, 0xd0, 0xbe, 0x2c, 0x56, 0xf5, 0x57, 0xa8, 0x4f, 0x5f, 0xd2, 0x34, 0x74, 0x1c, 0x8a, 0xd5, 0xfb, 0x9e, 0x0a, 0xb5, 0x09, 0xc9, 0xbf, 0x85, 0x7f, 0xd0, 0xf1, 0x2c, 0x51, 0xef, 0x87, 0xbe, 0x1f, 0x13, 0xdf, 0x6d, 0xc2, 0xbd, 0x3a, 0xfe, 0x34, 0xfa, 0x9f, 0x08, 0x4e, 0xcb, 0x2f, 0x51, 0xcb, 0xff, 0x1f, 0x18, 0x2b, 0x86, 0x93, 0x3e, 0x4a, 0x82, 0xbd, 0xea, 0x38, 0x1e, 0xe9, 0x7e, 0x47, 0xb5, 0xc3, 0xf7, 0xd2, 0x5c, 0x17, 0x2f, 0xab, 0xa1, 0x6e, 0xb4, 0x53, 0x00, 0x70, 0x3c, 0x0a, 0x5d, 0xc3, 0x7d, 0xf0, 0x76, 0xe8, 0xcb, 0x83, 0x19, + 0xb9, 0x76, 0xdc, 0xe0, 0xce, 0x8e, 0xb3, 0x67, 0xba, 0x71, 0xbd, 0xcd, 0xef, 0x7d, 0x89, 0x6b, 0x80, 0x4d, 0x16, 0x7e, 0xae, 0x37, 0x25, 0xf4, 0x97, 0xfc, 0xb6, 0x9c, 0x9c, 0xb6, 0x7c, 0x2e, 0x2d, 0x31, 0x9b, 0xf9, 0xe7, 0xf9, 0x84, 0xd6, 0x07, 0xfb, 0x91, 0x25, 0xec, 0xdc, 0x3e, 0x74, 0x47, 0x9f, 0x73, 0xfb, 0x98, 0x6d, 0x05, 0x9c, 0xe8, 0xf8, 0x7f, 0x50, 0x79, 0x2b, 0x51, 0x75, 0xfc, 0xc7, 0x86, 0x6f, 0xfb, 0x42, 0x4f, 0xff, 0xac, 0xf1, 0x9b, 0xa9, 0x6e, 0x2b, 0x51, 0xef, 0xc4, 0xfe, 0xd5, 0x90, 0xfe, 0x61, 0x3d, 0xfd, 0x0b, 0xa6, 0xf4, 0x19, 0x9c, 0xd6, 0xb6, 0x23, 0xe8, 0x2f, 0xd1, 0xe3, 0xa4, 0x1c, 0xa3, 0xed, 0x3f, 0x82, 0xd4, 0xbc, 0x62, 0x63, 0x88, 0xbe, 0x59, 0x0f, 0xed, 0x8b, 0xc2, 0x83, 0x21, 0x7a, 0x1e, 0x11, 0x3a, 0x10, 0x85, 0xb7, 0x85, 0xe8, 0x9e, + 0x84, 0x92, 0x4b, 0xdb, 0x70, 0x5c, 0xc7, 0x87, 0xb2, 0x72, 0x94, 0x5f, 0x1a, 0xfb, 0x87, 0x9e, 0x67, 0xaf, 0xa5, 0xf8, 0xdb, 0x51, 0x78, 0x53, 0xe8, 0xab, 0xd3, 0xf8, 0x16, 0xb7, 0x8e, 0xbf, 0xa8, 0x8e, 0x77, 0x2f, 0xc1, 0x3f, 0x8d, 0x35, 0xde, 0xda, 0x61, 0x35, 0x8c, 0xb7, 0xd4, 0xdf, 0x78, 0xff, 0xe1, 0x44, 0xe8, 0x5f, 0x07, 0x33, 0x7c, 0x64, 0xbc, 0x93, 0xd4, 0xf1, 0xb6, 0xc2, 0x78, 0x37, 0x86, 0x5e, 0xe3, 0x1a, 0xf9, 0x39, 0x31, 0xc6, 0x1b, 0x55, 0x91, 0xfa, 0xae, 0xa2, 0x72, 0x5a, 0xaa, 0xca, 0xe9, 0x5b, 0x06, 0xdc, 0xab, 0xe3, 0x7b, 0xd1, 0xef, 0x0d, 0x78, 0x06, 0xc3, 0xe9, 0x18, 0xbc, 0xdf, 0xe7, 0x6e, 0x38, 0x4d, 0xa3, 0xf9, 0xb6, 0x96, 0xc9, 0x4c, 0x86, 0xf7, 0x85, 0x9c, 0x46, 0x2e, 0x49, 0x8a, 0x2e, 0x95, 0x61, 0x26, 0xbb, 0xdc, 0x6c, 0xdf, 0x5d, 0x86, + 0xb7, 0x99, 0xb0, 0x3d, 0x08, 0x9b, 0x87, 0xc2, 0x55, 0x29, 0xbd, 0x7f, 0xe1, 0x7d, 0x27, 0x7f, 0x6a, 0xb7, 0xf2, 0xde, 0xde, 0xef, 0x84, 0x2c, 0x71, 0x6c, 0x9e, 0xef, 0xe4, 0x8e, 0xdd, 0xe7, 0xb9, 0xb2, 0x1c, 0xc2, 0x45, 0xbb, 0x51, 0xcc, 0xf6, 0x3d, 0xad, 0xb5, 0x8f, 0xc8, 0xe0, 0x55, 0x54, 0x06, 0x4b, 0x55, 0x19, 0xfc, 0x48, 0xc5, 0xbf, 0x36, 0xe2, 0x84, 0x67, 0x7c, 0xa4, 0x73, 0xf0, 0xcb, 0x28, 0x67, 0x2f, 0xa3, 0xb6, 0xcd, 0x93, 0x33, 0x50, 0x4c, 0x7c, 0x97, 0x09, 0xdf, 0xa6, 0xe3, 0x4f, 0x99, 0xf0, 0x5b, 0x75, 0x7c, 0x9f, 0x09, 0x5f, 0xa3, 0xe3, 0xfb, 0x4d, 0x78, 0x87, 0x8e, 0x3f, 0xdf, 0x4f, 0xf9, 0x07, 0x4c, 0xf8, 0x27, 0x3a, 0x7e, 0xf0, 0x42, 0x03, 0x2e, 0xb7, 0xe9, 0xf8, 0xcb, 0x17, 0xb2, 0xbd, 0x1d, 0x8a, 0x0b, 0x76, 0xa2, 0xb7, 0x1a, 0xd1, 0xd2, 0xe7, + 0xb2, 0x9c, 0xe0, 0xeb, 0x8d, 0x90, 0xcb, 0x2c, 0xd8, 0x1f, 0x21, 0x4b, 0xa2, 0x28, 0xc9, 0x62, 0x0f, 0x3c, 0xc5, 0xc5, 0x22, 0x9a, 0x83, 0x24, 0x89, 0xda, 0x65, 0xb9, 0xec, 0xd9, 0x19, 0xb8, 0x49, 0x4c, 0x87, 0x67, 0x42, 0xc5, 0x03, 0x24, 0x24, 0x49, 0x3a, 0xe9, 0x5e, 0x09, 0xa1, 0x9b, 0x3e, 0xb8, 0xd1, 0x6a, 0xf7, 0xfb, 0x53, 0x0b, 0xf2, 0x0b, 0x0a, 0x13, 0x2c, 0x52, 0x06, 0x31, 0xe8, 0x78, 0xfd, 0x35, 0x0f, 0x66, 0x46, 0x5c, 0xb2, 0x8b, 0x3a, 0xc9, 0x71, 0xf0, 0xf4, 0x46, 0x2b, 0xb1, 0xe4, 0xea, 0xe1, 0xa9, 0x65, 0x2e, 0x23, 0xda, 0xdc, 0xce, 0x2b, 0x1f, 0x5d, 0x50, 0x9e, 0x56, 0x32, 0x38, 0xb7, 0xac, 0xb3, 0xad, 0x39, 0x33, 0x74, 0x6c, 0xca, 0x6b, 0x57, 0x4e, 0xbb, 0x71, 0x46, 0xc5, 0xb4, 0xcc, 0x2c, 0x6b, 0x72, 0xc1, 0x98, 0xc9, 0x17, 0x35, 0x36, 0x5f, 0x3a, + 0xae, 0x6c, 0xe8, 0xe6, 0xcf, 0x76, 0xcf, 0x9e, 0x19, 0x9c, 0xd6, 0x5a, 0x91, 0x96, 0x94, 0x69, 0xc5, 0x47, 0x85, 0xcc, 0x41, 0x43, 0x0b, 0x03, 0xf5, 0xfe, 0x84, 0x44, 0xdf, 0xa0, 0x6c, 0x6e, 0xc9, 0xc4, 0x09, 0x8d, 0xf3, 0x37, 0x4d, 0x0d, 0x5d, 0xe5, 0x4e, 0xef, 0xca, 0xab, 0x2f, 0x48, 0xf2, 0x0d, 0x9f, 0x3f, 0xbc, 0x79, 0xc9, 0xf9, 0x0d, 0x42, 0xc7, 0xf2, 0x65, 0x39, 0x05, 0x39, 0x29, 0x4e, 0xa4, 0xd9, 0x53, 0xa4, 0x5f, 0xb4, 0xfe, 0xc2, 0x7f, 0x9e, 0x66, 0xec, 0xdf, 0x63, 0x3a, 0xfe, 0xeb, 0x48, 0xff, 0x0a, 0x33, 0xa5, 0x46, 0x1d, 0x3f, 0xc1, 0xf0, 0xf0, 0x35, 0x24, 0x7d, 0xb7, 0xd4, 0x48, 0xe6, 0x7b, 0x19, 0xdd, 0x2c, 0xff, 0xcd, 0x45, 0x91, 0x72, 0xde, 0x92, 0x7a, 0xf4, 0xf4, 0xbf, 0x35, 0x8e, 0x93, 0xf4, 0x53, 0x1d, 0xff, 0x9d, 0x86, 0x43, 0x7b, 0xf4, 0xf1, + 0x0e, 0xd0, 0xf6, 0x50, 0xa9, 0xd2, 0xf3, 0x3c, 0xac, 0xe7, 0xf9, 0xbd, 0x51, 0x16, 0x0c, 0xf8, 0x1f, 0x4d, 0xf8, 0x9f, 0x74, 0xfc, 0xc3, 0x19, 0xb1, 0xeb, 0xfe, 0xd3, 0x8c, 0x81, 0xea, 0x3e, 0x30, 0x23, 0x96, 0x1c, 0xee, 0x31, 0x95, 0x75, 0xa3, 0x86, 0x0b, 0xd3, 0x4c, 0xf8, 0x0d, 0x2a, 0x5e, 0x80, 0x9e, 0x47, 0x33, 0x88, 0x42, 0x92, 0xd0, 0xe1, 0x19, 0xda, 0x0e, 0x74, 0x93, 0x6e, 0xa7, 0xb2, 0xb2, 0x0f, 0xe9, 0x65, 0x43, 0x1a, 0x32, 0x9d, 0x51, 0x45, 0xb8, 0x56, 0x28, 0x16, 0x8e, 0x11, 0xe9, 0x75, 0x10, 0xfd, 0xd7, 0x81, 0xb6, 0x06, 0xd3, 0x46, 0x60, 0xc9, 0xd2, 0x8a, 0x65, 0xa9, 0x06, 0xb6, 0xfa, 0x32, 0x93, 0xe0, 0xe2, 0x75, 0x09, 0xb1, 0xec, 0x44, 0xd5, 0x4e, 0x1a, 0x84, 0xac, 0x16, 0xeb, 0x32, 0x22, 0xa6, 0x48, 0x92, 0x89, 0x41, 0x27, 0xf2, 0x22, 0xbc, 0xa1, + 0x84, 0x3d, 0x26, 0x90, 0x55, 0xcb, 0x74, 0x64, 0xb1, 0x2c, 0x06, 0x83, 0x8f, 0x09, 0xf5, 0x5c, 0x10, 0xea, 0x9a, 0xa8, 0x2c, 0x42, 0xcc, 0x2c, 0xaa, 0xa1, 0x48, 0xc5, 0x7b, 0x21, 0xbd, 0x55, 0xdf, 0x31, 0x2a, 0xdf, 0x5f, 0x92, 0x57, 0x5f, 0x9e, 0xa7, 0xde, 0xaa, 0x97, 0xeb, 0x93, 0xc1, 0x45, 0x09, 0x3d, 0xb8, 0xa8, 0x2f, 0x70, 0x10, 0x9b, 0xd1, 0x43, 0x8f, 0xe2, 0x35, 0x21, 0x57, 0x9f, 0x62, 0xd6, 0x97, 0x63, 0x10, 0xf6, 0x56, 0xac, 0xc9, 0x7a, 0x85, 0xea, 0x23, 0x98, 0xba, 0x69, 0xe2, 0x76, 0x65, 0x66, 0x66, 0xd5, 0xa7, 0x1f, 0x4f, 0x4c, 0xa6, 0x72, 0xff, 0xcf, 0x4b, 0xa7, 0xfb, 0xb8, 0x40, 0x49, 0x44, 0xea, 0x5d, 0xbe, 0x84, 0x0b, 0x9c, 0x45, 0x9e, 0xe1, 0x9b, 0x06, 0x8f, 0xa8, 0xea, 0x79, 0xf6, 0xfa, 0xeb, 0x8b, 0xd7, 0x68, 0xd2, 0xef, 0x27, 0x33, 0x64, 0xd2, + 0xea, 0xb2, 0xcd, 0xce, 0x80, 0x77, 0x38, 0xd7, 0x5d, 0x8e, 0x57, 0x62, 0x81, 0x53, 0x0e, 0xf1, 0x30, 0x13, 0x9e, 0xf2, 0x4c, 0xaa, 0x1f, 0x92, 0xc7, 0xed, 0xee, 0x5a, 0xae, 0xcf, 0x05, 0x9e, 0xdb, 0xe4, 0xf1, 0x7e, 0x90, 0x73, 0xf9, 0xd4, 0x9a, 0xb9, 0x93, 0x5b, 0x1c, 0x3f, 0xef, 0xa6, 0xf3, 0xa1, 0x75, 0xe2, 0x84, 0xaa, 0xf2, 0xbf, 0x78, 0x98, 0x6f, 0x99, 0x16, 0x65, 0xa3, 0x68, 0xa3, 0xfa, 0x62, 0x1c, 0x6e, 0xeb, 0xdc, 0xe3, 0x06, 0x2b, 0x7c, 0x30, 0xe9, 0x78, 0xaa, 0x39, 0xc0, 0x0a, 0x77, 0xc0, 0xdf, 0x9c, 0xd8, 0x8a, 0xb2, 0x30, 0xb6, 0xd2, 0x67, 0x8c, 0x90, 0xa6, 0x2a, 0x86, 0x8a, 0x50, 0xfb, 0xd0, 0x8a, 0x35, 0x65, 0x32, 0xb7, 0xd3, 0x8e, 0xe1, 0x19, 0x21, 0xe9, 0xd0, 0x1e, 0x0e, 0xce, 0xa5, 0xb2, 0xe8, 0xfb, 0x45, 0x2d, 0xa7, 0x95, 0x2c, 0x19, 0x56, 0x4c, + 0x4b, 0xb0, 0x9a, 0x4a, 0x40, 0xfd, 0x16, 0x90, 0x4b, 0x0a, 0x68, 0x39, 0x8d, 0x02, 0xe8, 0x30, 0xaa, 0x5a, 0x6a, 0xa1, 0xb9, 0x90, 0xe0, 0xf0, 0x3e, 0xf9, 0xed, 0x02, 0xe9, 0x01, 0xdb, 0xa9, 0xcb, 0x41, 0x91, 0x62, 0xe0, 0x00, 0x2c, 0x71, 0x4c, 0xc7, 0xe8, 0x91, 0xc3, 0x87, 0xe5, 0x6b, 0x0a, 0x90, 0xbd, 0x84, 0x34, 0x28, 0x40, 0xb6, 0xbb, 0xd5, 0x82, 0xfb, 0x57, 0x80, 0x15, 0xba, 0x92, 0xac, 0xa6, 0x67, 0x61, 0x34, 0xcc, 0x49, 0x94, 0x52, 0xe4, 0x66, 0x96, 0x8d, 0x29, 0xea, 0xbc, 0x24, 0x98, 0xd9, 0x3e, 0x80, 0x52, 0xf4, 0xab, 0x8a, 0xf3, 0xde, 0x41, 0xc1, 0x9c, 0x72, 0x87, 0x3d, 0x5d, 0x8e, 0xd2, 0x92, 0xa1, 0xe7, 0x92, 0x5c, 0xb5, 0xe7, 0x2f, 0x6f, 0xad, 0xed, 0x4f, 0x49, 0xb6, 0x32, 0x2d, 0xaa, 0xc4, 0xbd, 0x3b, 0x76, 0xaa, 0xc3, 0x91, 0x16, 0x6b, 0x4d, 0xc2, + 0x95, 0xa7, 0xb5, 0xb6, 0xed, 0xd4, 0x70, 0x8e, 0x57, 0xf1, 0x2a, 0xf3, 0x9a, 0x8a, 0xc3, 0xba, 0x1e, 0xaa, 0x34, 0xea, 0x64, 0xce, 0xd9, 0xc5, 0xf0, 0x60, 0xf8, 0x73, 0x09, 0xd1, 0xf4, 0x15, 0x74, 0x8d, 0xdf, 0x85, 0x6f, 0x31, 0xe0, 0x1d, 0x3a, 0xfe, 0x3c, 0xe0, 0x84, 0x29, 0x51, 0x9c, 0xc8, 0x70, 0x3c, 0x4a, 0x25, 0xda, 0x47, 0xee, 0xdc, 0x93, 0x4e, 0xc4, 0x24, 0x6e, 0x62, 0x27, 0x67, 0xe3, 0x2a, 0x0a, 0x38, 0x4c, 0x9f, 0x82, 0xe8, 0x7f, 0x85, 0x97, 0x21, 0x44, 0x88, 0x5d, 0x24, 0x49, 0x2d, 0xb2, 0x71, 0x3c, 0x67, 0xe3, 0x61, 0xe4, 0x31, 0x12, 0x71, 0x8f, 0xc5, 0xc1, 0xf1, 0xce, 0x78, 0x8e, 0xe8, 0x13, 0x04, 0x5e, 0x05, 0x38, 0xfb, 0xf4, 0x38, 0x6c, 0xb7, 0xe7, 0x76, 0x0a, 0x44, 0xa4, 0xa5, 0xe9, 0x70, 0x65, 0xca, 0x27, 0x81, 0x18, 0xa6, 0x9f, 0x5d, 0x66, 0x3f, + 0x1c, 0x3e, 0xb4, 0x1a, 0xf2, 0x09, 0x44, 0x0d, 0xf5, 0x9b, 0x8f, 0x88, 0x9c, 0x9a, 0x0d, 0x44, 0x50, 0x2f, 0x81, 0x08, 0x5e, 0x5e, 0x5a, 0x1a, 0x19, 0xa2, 0x19, 0x69, 0xd3, 0xa7, 0x4c, 0x1e, 0x3f, 0x76, 0x54, 0xfb, 0xb0, 0xa1, 0x43, 0x5a, 0x6a, 0xab, 0xcb, 0x4a, 0xf2, 0x72, 0xb3, 0x33, 0x1d, 0xa9, 0x8e, 0xd4, 0xa2, 0x40, 0x42, 0x02, 0x30, 0x45, 0xfd, 0x7c, 0xb5, 0x36, 0x5f, 0x3d, 0x8e, 0x75, 0xe2, 0x2c, 0x9e, 0x1d, 0xb8, 0xa6, 0x64, 0xab, 0xca, 0x6a, 0x00, 0xb1, 0x4c, 0xf1, 0xa8, 0xc2, 0x99, 0x24, 0xf9, 0xb9, 0xdd, 0x57, 0x3e, 0x7a, 0x49, 0xf9, 0xcc, 0x71, 0xf5, 0x33, 0xdb, 0x8b, 0xb8, 0x1d, 0x59, 0x83, 0xa7, 0xd4, 0x35, 0x4d, 0x6f, 0x2b, 0x92, 0x39, 0xb1, 0xac, 0x73, 0xfe, 0x90, 0x11, 0x17, 0x34, 0x07, 0x5c, 0x4e, 0xb7, 0x54, 0xe3, 0xef, 0x9a, 0xd7, 0x5d, 0xb3, + 0xfc, 0x95, 0xc6, 0x8c, 0x69, 0x97, 0x5d, 0xd9, 0x3c, 0x72, 0xf9, 0xe4, 0xb2, 0x60, 0x94, 0x90, 0xbe, 0xb0, 0xec, 0xca, 0x3b, 0xfc, 0x75, 0x4f, 0x5d, 0x39, 0xf9, 0xb6, 0xcb, 0x9a, 0x13, 0xbd, 0xf8, 0x8f, 0xa2, 0xaf, 0x76, 0x44, 0xe1, 0xe4, 0xce, 0xdc, 0x91, 0xdd, 0xa3, 0x8f, 0xb6, 0x5c, 0x3a, 0xae, 0xb4, 0xb0, 0xa3, 0x7b, 0xd8, 0x8a, 0xa9, 0xd7, 0x9d, 0x57, 0x9c, 0x91, 0x9b, 0x61, 0xb3, 0x0c, 0x2b, 0x6e, 0x09, 0x24, 0x2e, 0x5a, 0xda, 0x3c, 0xa5, 0x2e, 0x2d, 0x6f, 0x64, 0xf7, 0xc8, 0xe6, 0xe5, 0x17, 0xb6, 0x08, 0xe5, 0xcb, 0xaf, 0xca, 0x2d, 0xca, 0x25, 0xf2, 0x3a, 0x61, 0xe2, 0x9c, 0x69, 0x53, 0x66, 0x0f, 0x5d, 0xb9, 0xa7, 0xa7, 0x2c, 0x9b, 0xc8, 0xc6, 0xfa, 0xf0, 0x97, 0x82, 0x9b, 0xae, 0xe3, 0x83, 0xa8, 0x6c, 0x1c, 0x56, 0xdf, 0xd9, 0xad, 0x27, 0xbc, 0xd0, 0x80, + 0x13, 0x5e, 0xf8, 0x98, 0x8a, 0x7f, 0x6d, 0xc4, 0x09, 0x2f, 0x7c, 0x8c, 0xea, 0xc3, 0xf5, 0xca, 0x56, 0xc0, 0x51, 0x1c, 0xe1, 0x4f, 0xbf, 0x62, 0x0a, 0x27, 0x31, 0x3e, 0x0e, 0xe8, 0x78, 0x75, 0x69, 0x2a, 0x2f, 0x21, 0x9f, 0xfa, 0xbc, 0x3b, 0x59, 0x05, 0x53, 0x39, 0x19, 0x95, 0x12, 0x03, 0x95, 0xe0, 0xa2, 0xa0, 0x3d, 0xf2, 0x0e, 0x10, 0xed, 0x31, 0xaf, 0x53, 0x73, 0x64, 0xb8, 0x18, 0xb8, 0x2f, 0x1b, 0xbf, 0x3e, 0x2f, 0xba, 0x73, 0x07, 0x4a, 0x1d, 0xfd, 0x7c, 0xbb, 0x12, 0x09, 0x44, 0x73, 0xc1, 0x73, 0xed, 0xa8, 0x0c, 0xf4, 0x99, 0x11, 0xe4, 0x42, 0x90, 0xc9, 0xfc, 0x5e, 0x3b, 0xce, 0x15, 0xc8, 0xf3, 0xe5, 0x97, 0xe6, 0xe7, 0xe5, 0x5a, 0x89, 0x7e, 0xca, 0x07, 0xd7, 0x01, 0xd4, 0x57, 0x47, 0xae, 0x24, 0xd7, 0xea, 0xe3, 0x2b, 0xeb, 0x3b, 0x9f, 0xea, 0x9d, 0xcd, 0xba, + 0x16, 0x4c, 0x9f, 0x20, 0x15, 0xc8, 0x70, 0x12, 0x54, 0x8d, 0xc7, 0x0c, 0xea, 0x69, 0x5b, 0xb7, 0xf9, 0xfa, 0x2b, 0xcf, 0x5b, 0x59, 0xdb, 0xba, 0x7d, 0x7e, 0xd7, 0xad, 0x73, 0xeb, 0xe6, 0x4c, 0xcf, 0x0c, 0x5e, 0xd2, 0x59, 0x34, 0xa6, 0xcc, 0x29, 0xa5, 0xc7, 0xc5, 0x57, 0xe4, 0x04, 0x07, 0x05, 0x0a, 0x2a, 0x27, 0x8d, 0x18, 0x9c, 0x6e, 0xb5, 0x14, 0x5f, 0x7b, 0xf4, 0xfb, 0xd6, 0xaa, 0x23, 0xc7, 0xf6, 0x3f, 0x31, 0xa6, 0xfd, 0xf6, 0xf6, 0xa9, 0x23, 0x56, 0xbf, 0x70, 0xf9, 0x03, 0x5f, 0x8c, 0xb8, 0xa4, 0x75, 0xf9, 0xf9, 0xb5, 0x89, 0xee, 0xf1, 0x44, 0x76, 0xa7, 0x8e, 0x2d, 0x93, 0xdc, 0x79, 0x35, 0xbe, 0x29, 0x45, 0xe2, 0x31, 0xe6, 0x27, 0x99, 0xbf, 0x8d, 0xce, 0xf7, 0x4a, 0x36, 0xdf, 0xd1, 0x7d, 0x08, 0xe9, 0xf8, 0x36, 0x1d, 0x7f, 0xaa, 0x1f, 0x7c, 0x8f, 0x09, 0x3f, + 0xa4, 0xe3, 0x87, 0x01, 0x07, 0xaf, 0xb7, 0x80, 0x0b, 0x16, 0x62, 0x01, 0x74, 0x07, 0x6d, 0xa5, 0x18, 0x8b, 0xe9, 0x98, 0x4c, 0xc1, 0x0e, 0xa6, 0x04, 0xf2, 0x22, 0xc7, 0x16, 0x1c, 0x61, 0x18, 0xfa, 0xb1, 0x45, 0xae, 0xce, 0x03, 0x7c, 0x70, 0x47, 0xe2, 0xd4, 0xc9, 0xfc, 0x40, 0x17, 0xac, 0x85, 0x01, 0x7f, 0x21, 0x1c, 0x6e, 0xa4, 0x47, 0x1d, 0x6e, 0x70, 0x6c, 0xde, 0x65, 0x1b, 0xd6, 0x01, 0x7a, 0xa4, 0x31, 0xd1, 0xea, 0x4a, 0x48, 0xf0, 0xd7, 0x76, 0xd6, 0x8f, 0x5a, 0x3c, 0x3a, 0x50, 0xd8, 0x71, 0xd9, 0xd0, 0xd6, 0x49, 0x75, 0x79, 0xee, 0xd4, 0xf8, 0x06, 0xff, 0xb4, 0xd9, 0xdd, 0xf5, 0x33, 0x77, 0xac, 0x68, 0x6f, 0x5d, 0xf9, 0xec, 0xe5, 0x8b, 0x1e, 0x1d, 0xca, 0x0f, 0xb6, 0xc7, 0xa7, 0x65, 0xa6, 0xd5, 0x5c, 0xbc, 0x61, 0x6a, 0xd7, 0xcd, 0x33, 0x2b, 0xb3, 0xf3, 0xb2, + 0x13, 0x47, 0x95, 0x34, 0x07, 0x12, 0xdb, 0x6f, 0x3c, 0x7a, 0xcd, 0xd2, 0x43, 0x37, 0x8f, 0x6f, 0x0f, 0xd2, 0x3d, 0xe6, 0x8f, 0xc2, 0xb5, 0xfc, 0xef, 0x05, 0xf0, 0x43, 0xde, 0x84, 0x6e, 0x0b, 0xa6, 0xd5, 0x54, 0x95, 0xf2, 0x48, 0x6c, 0x22, 0x06, 0x1b, 0x51, 0x2d, 0x72, 0x6e, 0x12, 0x87, 0xb9, 0x0c, 0xf2, 0x1d, 0xc0, 0xa3, 0xac, 0xd4, 0x87, 0xc5, 0xa9, 0x5c, 0x52, 0x2c, 0x3e, 0x53, 0x1f, 0x16, 0x91, 0x0c, 0x4b, 0xa8, 0x0f, 0x8b, 0x40, 0x5e, 0x61, 0x3e, 0xe9, 0x13, 0x1f, 0xbd, 0x1a, 0x1c, 0x60, 0xbd, 0x91, 0x8d, 0x53, 0x8c, 0xdd, 0xa1, 0x79, 0xaf, 0x60, 0x94, 0xa9, 0xba, 0x8a, 0x71, 0x24, 0x5e, 0x75, 0x12, 0x40, 0xac, 0x84, 0x8d, 0xbe, 0x72, 0xe8, 0x1e, 0x6f, 0xed, 0x98, 0xd5, 0xa6, 0xee, 0x19, 0xb3, 0xae, 0xe1, 0x76, 0xa0, 0x49, 0xbf, 0xaf, 0x6d, 0x82, 0x5e, 0x9a, + 0x39, 0xfe, 0x32, 0xa0, 0x45, 0x3b, 0x8a, 0xfd, 0x35, 0xa4, 0x57, 0x1d, 0x59, 0x49, 0xfc, 0x02, 0x2b, 0xf4, 0xd7, 0xa5, 0x63, 0x66, 0x5f, 0xa8, 0xf7, 0x57, 0x59, 0xe9, 0xc7, 0x1e, 0x2f, 0x9e, 0x31, 0xec, 0xb9, 0xe5, 0xd0, 0x6b, 0x19, 0x8f, 0x7a, 0xbc, 0x4a, 0xaf, 0xd7, 0x3f, 0xca, 0x1e, 0xef, 0x8e, 0x33, 0xcb, 0xca, 0xe0, 0x60, 0x7d, 0x44, 0x56, 0xfa, 0x19, 0xfd, 0x3a, 0x7d, 0xf4, 0x6b, 0x84, 0x31, 0xea, 0xd0, 0xcb, 0xff, 0x37, 0x43, 0x0f, 0xf2, 0xaf, 0x6c, 0x55, 0xe7, 0x51, 0x95, 0x69, 0x1e, 0x4d, 0x22, 0xdf, 0xf4, 0x3e, 0xd5, 0x75, 0xf5, 0x8c, 0x77, 0x5f, 0xc2, 0xd2, 0x4f, 0x22, 0x76, 0x40, 0x04, 0x07, 0xdd, 0xf8, 0x84, 0x8a, 0x7f, 0x6d, 0xc4, 0x89, 0x6e, 0xdc, 0xa9, 0xe2, 0xf7, 0x18, 0x71, 0x29, 0x19, 0x70, 0x22, 0x09, 0x93, 0x88, 0xce, 0x5c, 0x4d, 0x74, 0xa6, + 0x03, 0x0d, 0x46, 0x8f, 0x32, 0x9d, 0x96, 0x50, 0x8f, 0x91, 0x5c, 0xe0, 0x80, 0x2b, 0xad, 0xa9, 0xa4, 0x03, 0x41, 0x63, 0x9a, 0x21, 0x81, 0x52, 0xc9, 0x5c, 0x7a, 0x51, 0x89, 0x6a, 0x33, 0xd5, 0x5d, 0xcf, 0xe2, 0x4e, 0x9b, 0x95, 0x83, 0xde, 0x85, 0xa7, 0x97, 0x73, 0x3b, 0x81, 0x05, 0x46, 0xa8, 0x5b, 0x35, 0xe2, 0x25, 0x41, 0xe2, 0x85, 0x9e, 0xa8, 0x2c, 0xf4, 0x0e, 0x14, 0xcd, 0xa7, 0xa9, 0x40, 0xf5, 0xc4, 0x11, 0x98, 0x5a, 0x30, 0xd1, 0xe9, 0x74, 0x0e, 0x76, 0x0e, 0x76, 0x17, 0xe6, 0xf9, 0x7d, 0xf9, 0x85, 0x81, 0x5c, 0x78, 0x0f, 0x4c, 0xbd, 0xa9, 0x48, 0x6c, 0x70, 0xea, 0xea, 0xc5, 0x18, 0xea, 0xcf, 0x9f, 0x5b, 0xa1, 0x0e, 0xa3, 0x36, 0x60, 0xae, 0x6a, 0x17, 0x9f, 0x3a, 0x68, 0xc9, 0x88, 0x1b, 0x37, 0xfb, 0x5a, 0x67, 0x0c, 0x9e, 0x72, 0x4d, 0x1d, 0xbe, 0x34, 0x5a, + 0x05, 0x2e, 0x2d, 0xb2, 0xb9, 0x86, 0xee, 0x58, 0x38, 0xed, 0x96, 0xb9, 0x75, 0xcd, 0xcb, 0x1e, 0x9a, 0x3f, 0x63, 0x75, 0xf5, 0x31, 0x9e, 0x6b, 0xad, 0x3a, 0x7c, 0x60, 0xd6, 0xfa, 0x69, 0x45, 0x63, 0x46, 0x6c, 0x7c, 0xd1, 0xa8, 0x03, 0x6b, 0x0b, 0x7e, 0x34, 0xf6, 0xc2, 0xe1, 0x6b, 0x8e, 0x5c, 0xbb, 0xf4, 0xc8, 0xa6, 0x89, 0xcd, 0xf5, 0x5c, 0xe9, 0xf7, 0xbf, 0x64, 0x36, 0xbc, 0x3a, 0x5e, 0xb0, 0x8f, 0x82, 0x6e, 0x0f, 0xc6, 0xe5, 0x61, 0x24, 0x24, 0xd1, 0x7e, 0x03, 0x9d, 0x05, 0xc7, 0x60, 0x19, 0xd0, 0x39, 0x74, 0x63, 0x88, 0xca, 0x5e, 0x6e, 0xa7, 0x1e, 0xd6, 0x2a, 0x5d, 0xa7, 0xc9, 0xb9, 0x90, 0x46, 0x05, 0xfb, 0x4b, 0x19, 0xcc, 0x89, 0xfc, 0xc0, 0x92, 0x23, 0x9a, 0x5a, 0x9d, 0xc0, 0x7e, 0xba, 0x80, 0x38, 0x48, 0xf7, 0xe5, 0x3b, 0xf3, 0x0b, 0xf3, 0xf2, 0x02, 0xb9, + 0xf4, 0x2d, 0x9e, 0xa9, 0xeb, 0xdc, 0xae, 0x18, 0x1d, 0x65, 0xee, 0xa6, 0x63, 0x57, 0xf4, 0xe9, 0x15, 0x63, 0xa7, 0x08, 0x8f, 0x7c, 0xdf, 0x1c, 0xdd, 0x13, 0x48, 0x93, 0x5b, 0xf9, 0x75, 0xd2, 0x0f, 0xaa, 0xbc, 0xa1, 0x8d, 0xc2, 0x7f, 0xe9, 0x7a, 0xfe, 0x3d, 0x2a, 0xe7, 0x4d, 0x14, 0x7f, 0x12, 0x3c, 0x05, 0xc4, 0xc0, 0x77, 0x99, 0xf0, 0x6d, 0x3a, 0xfe, 0x54, 0x3f, 0xf8, 0x1e, 0x13, 0xbe, 0x46, 0xc7, 0xf7, 0x9b, 0xf0, 0x0e, 0x1d, 0x7f, 0xbe, 0x9f, 0x72, 0x0e, 0x98, 0xf0, 0x43, 0x3a, 0x7e, 0x18, 0x70, 0x70, 0x65, 0x0e, 0x38, 0xd1, 0x31, 0x16, 0xc2, 0x7b, 0x17, 0xb3, 0x55, 0x28, 0x40, 0x4c, 0x6a, 0x91, 0xac, 0xee, 0x3d, 0xb0, 0x6d, 0x26, 0x48, 0x58, 0x80, 0xbb, 0x1b, 0x74, 0x10, 0x72, 0x41, 0xba, 0xb5, 0xe3, 0x73, 0xef, 0x69, 0x25, 0xf4, 0x83, 0xd4, 0x27, 0x21, 0x54, + 0x5a, 0x5c, 0x90, 0xef, 0xcb, 0x4e, 0xf3, 0x38, 0xe3, 0xc1, 0xc3, 0x66, 0x9e, 0x45, 0x4a, 0x2e, 0xc1, 0x35, 0xcc, 0xa1, 0xb5, 0x49, 0x17, 0x31, 0x8a, 0xe7, 0xd3, 0xb8, 0xa1, 0x87, 0x2f, 0xf6, 0x05, 0x67, 0x34, 0xaf, 0xbc, 0xc5, 0xcd, 0xed, 0xcf, 0xbb, 0xe8, 0xb2, 0xa5, 0xf5, 0x97, 0xee, 0x5b, 0x33, 0xba, 0x6d, 0xe5, 0x33, 0x4b, 0x2e, 0xbc, 0x6f, 0xd9, 0xd0, 0x24, 0x6f, 0x68, 0x3e, 0x5f, 0x3e, 0xb1, 0x67, 0xc4, 0xd0, 0x99, 0x23, 0x2a, 0x12, 0x53, 0xe3, 0xb9, 0x45, 0x83, 0x67, 0x8f, 0x2a, 0xdc, 0x74, 0xad, 0x92, 0x07, 0x64, 0x6d, 0xe4, 0xfa, 0xe3, 0x6b, 0x2f, 0x7b, 0x7e, 0xdd, 0xb8, 0x96, 0xcb, 0x9f, 0x5c, 0x54, 0x96, 0x3d, 0xfd, 0xa6, 0x0b, 0x4a, 0xb3, 0xfc, 0x59, 0x89, 0xa7, 0x1e, 0xb3, 0x18, 0x7d, 0x4d, 0x74, 0x13, 0xe9, 0x23, 0x0d, 0x27, 0xba, 0xe9, 0x51, 0x43, + 0xfa, 0x63, 0x3a, 0xfe, 0x6b, 0xf4, 0x90, 0x01, 0xbf, 0x55, 0x2f, 0x67, 0x5f, 0xa4, 0x7c, 0xe1, 0x11, 0xba, 0x8f, 0xc3, 0xd2, 0x9f, 0x60, 0xe9, 0xc3, 0x87, 0x20, 0xbd, 0x01, 0xff, 0x4d, 0xa4, 0x1c, 0x21, 0x85, 0xee, 0xe3, 0x30, 0xfc, 0xb7, 0xc6, 0xf2, 0xe9, 0x5e, 0x0a, 0xc3, 0x7f, 0xa7, 0xe1, 0xd0, 0x4e, 0x7d, 0xec, 0x0b, 0x98, 0x6c, 0x69, 0xed, 0x85, 0xb1, 0x26, 0x3a, 0x99, 0x8d, 0xf5, 0x28, 0xf4, 0x95, 0xea, 0x2b, 0xa8, 0x08, 0x0b, 0xe2, 0xa8, 0x0a, 0xce, 0x22, 0x11, 0xd3, 0xda, 0x42, 0x7d, 0x05, 0x19, 0x11, 0xab, 0xee, 0x2b, 0xa8, 0x92, 0xd8, 0x04, 0x02, 0xa1, 0x95, 0x3d, 0x36, 0x08, 0x5e, 0x21, 0x6a, 0x17, 0x7a, 0x18, 0xe7, 0x03, 0xeb, 0x00, 0x01, 0xa3, 0xb4, 0x62, 0x8b, 0x85, 0xea, 0xc1, 0x1e, 0x59, 0xbb, 0xfb, 0x79, 0x3a, 0x19, 0x97, 0x98, 0x33, 0x06, 0x1b, + 0xb4, 0x3c, 0xc8, 0x22, 0x59, 0x25, 0x8b, 0x75, 0xe0, 0xbc, 0x9a, 0xa3, 0x20, 0x92, 0x95, 0xa8, 0x88, 0x74, 0x84, 0x86, 0x0d, 0x6d, 0x69, 0x6e, 0xa8, 0x83, 0xb7, 0xec, 0xb9, 0x39, 0x19, 0xe9, 0x49, 0x89, 0x54, 0xe2, 0xec, 0xb6, 0x88, 0xc4, 0xb9, 0xcb, 0xf9, 0x98, 0xd2, 0x06, 0xcb, 0x62, 0xbf, 0x2e, 0x83, 0x74, 0x49, 0xfc, 0xd3, 0x85, 0xf7, 0x2d, 0x6d, 0xed, 0x4f, 0x06, 0x1b, 0x72, 0x26, 0xce, 0x98, 0x53, 0xe9, 0x8a, 0x79, 0x38, 0xaf, 0xc9, 0x67, 0x68, 0x9d, 0x6d, 0xc2, 0xba, 0x43, 0x57, 0xf7, 0x15, 0xce, 0x51, 0x81, 0xfa, 0x82, 0x14, 0xbe, 0x29, 0xc6, 0xd9, 0x3d, 0xdd, 0x9b, 0x4a, 0x0d, 0xd7, 0x0a, 0x6e, 0xc2, 0xa7, 0x78, 0xf2, 0x41, 0x09, 0x28, 0x88, 0x9e, 0x08, 0xa6, 0x36, 0x61, 0xce, 0x0a, 0xc4, 0xc7, 0x82, 0x25, 0xec, 0x4f, 0xe3, 0x44, 0x69, 0x50, 0x31, 0x27, + 0x8b, 0x1a, 0xa5, 0xaa, 0x42, 0x36, 0xab, 0x6d, 0x19, 0xb1, 0x72, 0x45, 0x2c, 0x01, 0x53, 0x97, 0x2d, 0xcb, 0x79, 0x3b, 0x87, 0x88, 0x15, 0x28, 0x21, 0x3c, 0x97, 0x74, 0xa3, 0x75, 0x3a, 0xe9, 0x3c, 0xba, 0x66, 0xd1, 0x1e, 0x5c, 0x08, 0x9d, 0x7f, 0xa6, 0x99, 0x96, 0xc8, 0x70, 0x3f, 0x0d, 0xa1, 0xe0, 0x90, 0xc6, 0xfa, 0x8a, 0xb2, 0xa2, 0x02, 0x5f, 0x76, 0x46, 0x7a, 0x6a, 0x4a, 0x82, 0x33, 0xde, 0x2e, 0x0a, 0xa4, 0xa9, 0x3c, 0x78, 0xb5, 0xc3, 0x35, 0x05, 0x4e, 0xec, 0xcf, 0xc2, 0x1e, 0xa6, 0xb0, 0x89, 0x85, 0x02, 0x9d, 0x4c, 0x7a, 0x9e, 0x37, 0x91, 0xaf, 0x28, 0xee, 0xa5, 0x6e, 0x50, 0xf1, 0xa5, 0xa5, 0x97, 0x1e, 0x3e, 0xca, 0x65, 0xa4, 0xe2, 0x25, 0x19, 0x6d, 0x05, 0x64, 0x00, 0xb0, 0x1d, 0xd7, 0xb4, 0xb6, 0x27, 0x79, 0x95, 0xfb, 0x7e, 0x32, 0x66, 0xb9, 0xd6, 0xef, + 0x54, 0x3b, 0xdc, 0xb3, 0xf4, 0x06, 0xe0, 0x60, 0xc7, 0x47, 0x8c, 0x85, 0xf1, 0x69, 0x5e, 0x54, 0x71, 0x15, 0xdd, 0x9b, 0x5a, 0x34, 0x7b, 0xd8, 0x8e, 0x15, 0x23, 0x96, 0x14, 0x73, 0x6d, 0x09, 0xae, 0x4d, 0xd7, 0xe2, 0x8a, 0xa6, 0xa7, 0x96, 0x97, 0x65, 0x2b, 0x6f, 0x37, 0xd3, 0x3e, 0xa7, 0xba, 0xe2, 0x57, 0x84, 0x87, 0x0d, 0x1d, 0xf1, 0xf3, 0xf5, 0x30, 0x26, 0x69, 0xe9, 0x2f, 0x7b, 0xbc, 0x91, 0x79, 0x76, 0x44, 0x9b, 0x67, 0xe2, 0x3a, 0xa3, 0x9e, 0x30, 0xe0, 0x37, 0xe9, 0x7a, 0xe2, 0x1e, 0xa3, 0x9e, 0x20, 0x9c, 0x44, 0xd5, 0x13, 0xca, 0xeb, 0x11, 0x9c, 0xfa, 0x8f, 0x78, 0x88, 0x9e, 0x17, 0x73, 0xba, 0xff, 0x08, 0x9e, 0xcd, 0x4d, 0xfa, 0x1e, 0x24, 0x0b, 0x5d, 0x84, 0x4b, 0xd4, 0xd9, 0xd9, 0x8a, 0x2d, 0xd6, 0x8b, 0x46, 0x70, 0x71, 0xb6, 0x09, 0xd8, 0x1e, 0x47, 0x67, + 0xa7, 0x11, 0x89, 0xd7, 0x67, 0x67, 0x10, 0x59, 0x65, 0x8b, 0x6c, 0xb5, 0x50, 0x37, 0x10, 0x12, 0x87, 0xe6, 0x38, 0xb1, 0x88, 0x64, 0xab, 0x28, 0xcf, 0x85, 0xdd, 0x4a, 0x08, 0x5f, 0xa5, 0xce, 0x96, 0xc5, 0xe0, 0xaa, 0xc4, 0x36, 0x1d, 0xd9, 0x6c, 0x0b, 0x3b, 0xe3, 0x71, 0x5c, 0x9c, 0x7d, 0x3a, 0xb2, 0xdb, 0x7b, 0xec, 0xda, 0x64, 0x3d, 0x8b, 0x72, 0x96, 0x98, 0xcb, 0x09, 0x8e, 0xd4, 0x8b, 0x88, 0xb3, 0xc5, 0xdb, 0xe2, 0xe2, 0xcf, 0xa8, 0x28, 0x14, 0x1f, 0xaf, 0x95, 0x04, 0x7b, 0x0b, 0x29, 0x29, 0x08, 0x75, 0x4d, 0x9b, 0x3c, 0x69, 0xdc, 0x98, 0xe1, 0xc3, 0x5a, 0x9a, 0x1a, 0xea, 0xaa, 0x06, 0x15, 0x17, 0xa6, 0x64, 0xa5, 0x64, 0x65, 0xa4, 0xc3, 0x0b, 0x99, 0xbc, 0xa4, 0x3c, 0xe6, 0xa0, 0x82, 0xdd, 0xd4, 0x52, 0x3d, 0x54, 0x0c, 0xe1, 0x6a, 0xdd, 0x3f, 0x68, 0x9a, 0x77, + 0x64, 0xb6, 0x75, 0x8f, 0x1f, 0xdf, 0xdd, 0x96, 0xb9, 0x21, 0xde, 0x57, 0x5f, 0x5c, 0x5c, 0xef, 0x8b, 0xbf, 0xec, 0x87, 0x4e, 0x7c, 0x71, 0x5b, 0xc3, 0xec, 0xce, 0x92, 0x92, 0xce, 0xd9, 0x0d, 0x39, 0x75, 0x05, 0xc9, 0xc9, 0x05, 0x75, 0x39, 0xbd, 0x6f, 0xfd, 0x10, 0x55, 0xd0, 0x77, 0x9d, 0xc7, 0x95, 0xa6, 0x35, 0x6c, 0xa7, 0x86, 0x73, 0x7c, 0x6c, 0x3e, 0x82, 0xc3, 0x2a, 0x5e, 0x4e, 0x14, 0xea, 0xd3, 0x72, 0x2b, 0xc1, 0x5b, 0x54, 0x5f, 0x13, 0xdd, 0x78, 0x95, 0x7a, 0x3f, 0x62, 0x95, 0x7a, 0x96, 0x56, 0xae, 0x6c, 0xe5, 0x9e, 0x16, 0xf7, 0xa1, 0x1a, 0xf4, 0x38, 0xe3, 0x73, 0x4e, 0xd8, 0x0a, 0xb7, 0x06, 0x88, 0xf5, 0x91, 0x46, 0x16, 0x0b, 0x2a, 0x97, 0x46, 0x44, 0xd2, 0xf9, 0x74, 0x39, 0xb1, 0x51, 0x48, 0x1b, 0xe4, 0x1e, 0xb8, 0x73, 0x01, 0x6a, 0x83, 0xde, 0xe2, 0xc7, + 0x5d, 0x16, 0x8c, 0xf1, 0x7c, 0xb6, 0x57, 0x60, 0x76, 0x6b, 0x52, 0xab, 0x67, 0xd0, 0xfc, 0x9a, 0xf4, 0x9b, 0xd1, 0xe4, 0xcb, 0xc4, 0x51, 0x5d, 0x99, 0xe2, 0xf6, 0xe5, 0xe7, 0xbb, 0x5c, 0xc9, 0xd5, 0x36, 0xb6, 0xf7, 0x09, 0x16, 0x4f, 0x26, 0x9c, 0xe9, 0xc1, 0x7e, 0x66, 0x5f, 0x4a, 0xed, 0x2a, 0x28, 0xc7, 0x2d, 0xb8, 0x5a, 0xb5, 0xf2, 0xb8, 0x9d, 0xd8, 0xe1, 0xf1, 0xb9, 0xcb, 0x3a, 0x6a, 0x32, 0x70, 0xa6, 0xf2, 0xd7, 0x1d, 0xd1, 0x64, 0xfa, 0x27, 0x97, 0x79, 0x32, 0xef, 0xaf, 0xba, 0x70, 0xcd, 0xa4, 0x11, 0x83, 0xc5, 0x96, 0xf8, 0x78, 0x09, 0xa7, 0xd4, 0x4c, 0x69, 0x9d, 0xba, 0x29, 0x34, 0xd8, 0xcc, 0xa2, 0xf9, 0xf3, 0xe3, 0x45, 0x4f, 0xca, 0xc4, 0x19, 0xd3, 0xd6, 0xcd, 0xac, 0x4c, 0xa2, 0xfd, 0x5b, 0xa1, 0x6c, 0x16, 0xec, 0x94, 0x23, 0x0c, 0x53, 0xef, 0x3f, 0x70, + 0x2a, 0xfe, 0xba, 0x70, 0x50, 0xc3, 0xe9, 0x1d, 0x12, 0x25, 0xca, 0xa7, 0x0c, 0x86, 0x9d, 0x45, 0xe1, 0x66, 0xc2, 0x39, 0x0b, 0xd1, 0xe8, 0x60, 0x7b, 0x72, 0x12, 0xb1, 0x53, 0x88, 0x4a, 0x26, 0xc4, 0xdb, 0x6e, 0xe3, 0x24, 0x2b, 0x96, 0x31, 0xb8, 0x20, 0x52, 0xf7, 0x64, 0xa8, 0x05, 0xc2, 0x75, 0x89, 0xf4, 0x6e, 0x31, 0x74, 0xd2, 0x5c, 0x70, 0x6a, 0x4e, 0x72, 0x16, 0x16, 0x04, 0xe0, 0x41, 0x6c, 0x4d, 0x7e, 0x8a, 0xcb, 0x45, 0x37, 0x85, 0xa9, 0xef, 0x0a, 0xe3, 0xb7, 0xab, 0xa6, 0x2d, 0xed, 0x2c, 0xfa, 0xc8, 0x12, 0xee, 0x0c, 0xf3, 0x95, 0xca, 0xc5, 0xc7, 0xe0, 0x9b, 0x9b, 0xae, 0xdc, 0x7b, 0x65, 0xe0, 0xbc, 0x4c, 0x99, 0x2b, 0x2f, 0x9a, 0x7e, 0xdd, 0xa4, 0x00, 0x77, 0xd9, 0xdd, 0x77, 0x1f, 0xbb, 0x8b, 0xab, 0xc7, 0xfe, 0xde, 0x47, 0xd5, 0x8f, 0xad, 0x24, 0xb3, 0xa1, + 0xd5, 0x66, 0x9b, 0x9f, 0xc4, 0xe5, 0x8d, 0x5a, 0x34, 0xaa, 0x44, 0x19, 0xbf, 0x49, 0x99, 0x86, 0x9f, 0xd8, 0x04, 0xdf, 0x48, 0xda, 0x7f, 0x54, 0x3c, 0x48, 0x38, 0x73, 0x27, 0xe3, 0xc0, 0xda, 0xb7, 0x13, 0xbc, 0x8d, 0x7e, 0x7b, 0x27, 0xe3, 0x4d, 0x44, 0x4a, 0x23, 0x7d, 0x75, 0x50, 0xc7, 0xcd, 0x7d, 0xa5, 0xe2, 0x03, 0xf6, 0xd5, 0x41, 0x94, 0x07, 0x7d, 0xe5, 0x4e, 0x3c, 0x8b, 0xbe, 0xca, 0x43, 0x79, 0xfe, 0x5c, 0xd2, 0x57, 0xee, 0x7c, 0x7f, 0xcc, 0xbe, 0xa2, 0xfb, 0xe1, 0xa0, 0x47, 0x72, 0xa5, 0x98, 0x3d, 0xb5, 0x65, 0x63, 0x5a, 0x81, 0x3b, 0x2b, 0x7d, 0x78, 0xab, 0xda, 0x47, 0x6d, 0x38, 0x55, 0xef, 0xa3, 0xd5, 0x3b, 0x76, 0x8b, 0x52, 0xb7, 0xcb, 0x76, 0xc1, 0x25, 0xab, 0x95, 0x0b, 0x36, 0x29, 0x63, 0xf0, 0xb3, 0x9b, 0xd8, 0xf7, 0x4a, 0xeb, 0x69, 0x3f, 0x8c, 0x61, + 0xdf, 0x2b, 0x5c, 0xa7, 0x7d, 0xaf, 0xdc, 0xa8, 0xe1, 0xf0, 0xbd, 0xc2, 0x35, 0xb1, 0xbe, 0x57, 0x96, 0x89, 0x6c, 0x0c, 0x45, 0x0b, 0x82, 0xf3, 0xca, 0xb0, 0x68, 0x29, 0xf7, 0x70, 0x56, 0x31, 0x15, 0x4b, 0x56, 0x2b, 0xb6, 0x23, 0xa1, 0x23, 0xc1, 0xc9, 0xd9, 0x1d, 0x38, 0x8e, 0x2c, 0x1b, 0xd4, 0x15, 0xd0, 0x74, 0x66, 0xa5, 0x4a, 0x12, 0xdd, 0x2a, 0x98, 0xc3, 0x62, 0xd5, 0x74, 0xd9, 0x64, 0x4e, 0x10, 0xe6, 0x77, 0xc6, 0x83, 0x42, 0x66, 0x9d, 0x30, 0x14, 0x0d, 0x0d, 0xb6, 0x0e, 0x69, 0x69, 0x6e, 0x1a, 0xdc, 0xd8, 0x50, 0x5f, 0x55, 0x91, 0x9f, 0x5c, 0x0b, 0x82, 0x03, 0x5e, 0x9c, 0xb4, 0xee, 0x88, 0x16, 0x1f, 0x22, 0x3d, 0xda, 0x54, 0x1b, 0x40, 0x9e, 0xc4, 0x5d, 0xca, 0xcc, 0xa3, 0xca, 0xe8, 0x63, 0x6c, 0x1a, 0xad, 0x9d, 0x34, 0xac, 0x59, 0x8c, 0x4c, 0xb8, 0x97, 0x9b, + 0xae, 0x78, 0xe6, 0xca, 0xc0, 0x64, 0x10, 0xb2, 0xc2, 0xe9, 0xab, 0x41, 0xc8, 0xee, 0xb9, 0xe7, 0xe8, 0x9d, 0xaa, 0x90, 0x5d, 0xa0, 0x77, 0xe2, 0x18, 0x3a, 0xab, 0xe6, 0xc4, 0xa9, 0xb3, 0xaf, 0xbd, 0x1f, 0xc1, 0x43, 0x9a, 0x8c, 0xc9, 0x43, 0xa9, 0xcc, 0x9c, 0xc7, 0x64, 0x4f, 0xeb, 0x5b, 0xb2, 0x18, 0x1d, 0xa2, 0x76, 0xdc, 0x79, 0x4c, 0xf6, 0x84, 0xd5, 0x9a, 0xec, 0x49, 0xeb, 0x22, 0xe9, 0xc9, 0x58, 0xac, 0xd2, 0xc7, 0xa2, 0x41, 0xc3, 0xe9, 0x58, 0xac, 0xec, 0x67, 0x2c, 0x0e, 0x12, 0x0d, 0x7a, 0x98, 0x85, 0x05, 0x4a, 0x28, 0x4d, 0xf5, 0xf0, 0x56, 0x11, 0x46, 0x82, 0xeb, 0x28, 0xc1, 0xe2, 0x28, 0x6f, 0x30, 0x81, 0xfc, 0xc7, 0x12, 0x81, 0xa9, 0x86, 0x84, 0xa4, 0x75, 0xe6, 0x81, 0x12, 0xa7, 0x5b, 0x30, 0x8c, 0x0f, 0x1d, 0x29, 0xf6, 0xc7, 0x98, 0x43, 0xe5, 0x0d, 0xd6, + 0x9c, 0xcd, 0x08, 0x77, 0x05, 0xe1, 0x75, 0x43, 0x0b, 0x52, 0x07, 0x18, 0xdc, 0x3e, 0x0d, 0x2a, 0xf7, 0x9f, 0xe1, 0x20, 0xf7, 0x33, 0x11, 0x06, 0x1e, 0xe2, 0x2d, 0x1b, 0x53, 0x0b, 0x13, 0x33, 0xd3, 0x47, 0xb4, 0x9e, 0xe6, 0xe0, 0x1a, 0x67, 0x8c, 0x3e, 0xb0, 0x6c, 0xfc, 0xc4, 0x56, 0x7a, 0x57, 0xa4, 0x4b, 0xbd, 0xbb, 0x96, 0x45, 0xf1, 0x26, 0xe5, 0x4e, 0xfe, 0x53, 0xba, 0x1e, 0x5e, 0xc8, 0xee, 0x44, 0x5c, 0x81, 0x62, 0xe2, 0x07, 0x4c, 0xf8, 0x21, 0x1d, 0x3f, 0x6c, 0xc2, 0x1f, 0xd6, 0xf1, 0x57, 0xfb, 0x29, 0x67, 0x8f, 0x09, 0xdf, 0xa5, 0xe3, 0xbb, 0x4c, 0x78, 0x87, 0x8e, 0x3f, 0x7f, 0x05, 0x32, 0xec, 0x5b, 0x41, 0xf9, 0x17, 0xd1, 0xf6, 0xbf, 0xaa, 0xbe, 0x2b, 0x67, 0xf8, 0x36, 0x1d, 0x7f, 0xca, 0x84, 0x1f, 0xd2, 0xf1, 0xc3, 0xfd, 0xa4, 0x3f, 0x40, 0xdf, 0xb5, 0x73, + 0x0c, 0x17, 0x6c, 0xc8, 0x81, 0x9a, 0xd1, 0x0e, 0x76, 0x3a, 0xe4, 0x6e, 0xc4, 0x48, 0x72, 0xc0, 0xfe, 0x68, 0x01, 0xe1, 0xf4, 0xb0, 0xdf, 0x25, 0x90, 0x15, 0x1b, 0x50, 0x39, 0x0a, 0xed, 0x62, 0x19, 0x8a, 0x88, 0xad, 0x05, 0x3e, 0x0c, 0x7b, 0xe8, 0x3e, 0xbe, 0x45, 0xe6, 0x28, 0x49, 0x53, 0xb7, 0x19, 0xe7, 0x1a, 0x37, 0xb4, 0xbc, 0xc1, 0x32, 0x2d, 0x2d, 0x3c, 0xe3, 0x9b, 0xaf, 0xef, 0xfc, 0xf7, 0x93, 0xbe, 0x2b, 0x98, 0xe0, 0x74, 0x3a, 0x9b, 0x9d, 0x4d, 0x85, 0x45, 0x7e, 0xd8, 0xfe, 0xa2, 0x6f, 0xf5, 0xa2, 0xf6, 0x70, 0x8c, 0x6b, 0x35, 0x23, 0x74, 0x2d, 0x51, 0xbb, 0x95, 0x84, 0xaf, 0xf1, 0xa9, 0xe5, 0x4b, 0x87, 0xc3, 0xae, 0xce, 0x85, 0x4d, 0x93, 0xae, 0xa9, 0x3b, 0x36, 0x74, 0xe5, 0x33, 0xcb, 0x56, 0xed, 0x6f, 0x2e, 0xb3, 0xa4, 0xb9, 0xdd, 0xc5, 0x4d, 0xe7, 0xb5, + 0x8e, 0x5a, 0x38, 0x2a, 0xcf, 0x37, 0xac, 0x73, 0x4a, 0xf5, 0x85, 0x77, 0x5e, 0x52, 0x0f, 0xfb, 0x3c, 0xb3, 0xd7, 0xd5, 0xc6, 0xd9, 0x8a, 0x78, 0xa1, 0xb9, 0xfa, 0xc5, 0x3d, 0x33, 0x6e, 0x98, 0x5a, 0x34, 0x66, 0x04, 0xf7, 0x4a, 0x6f, 0xfb, 0x8c, 0xad, 0x3d, 0xad, 0xa3, 0xeb, 0x3a, 0x1d, 0xce, 0x0c, 0x5f, 0x46, 0xd1, 0xe8, 0x39, 0x8d, 0x97, 0xe5, 0xd7, 0xe7, 0x25, 0x0e, 0xbf, 0xfe, 0xe5, 0xab, 0x17, 0xbe, 0xb4, 0x61, 0xfc, 0xf0, 0x96, 0x51, 0x05, 0xe8, 0xff, 0xef, 0xd3, 0xff, 0x5c, 0x9f, 0x8a, 0x37, 0xd1, 0x3e, 0x9d, 0xc4, 0x0d, 0x61, 0xc6, 0x47, 0xd2, 0x58, 0x8c, 0x6c, 0xb4, 0xf7, 0x9a, 0x48, 0xef, 0x95, 0xa6, 0x72, 0x1c, 0x4f, 0x3a, 0x95, 0x75, 0x9f, 0xf1, 0xb7, 0x38, 0xc3, 0x6f, 0x59, 0xea, 0x6f, 0xf6, 0x18, 0xbf, 0xe5, 0xc6, 0xfc, 0xcd, 0x02, 0x03, 0xd5, 0x17, + 0x86, 0x91, 0x22, 0xff, 0xb0, 0x96, 0x94, 0x13, 0xa3, 0x8e, 0x17, 0x65, 0x1e, 0x06, 0xcb, 0x36, 0x1d, 0x8e, 0xec, 0x17, 0x77, 0xc6, 0xc7, 0xd1, 0xad, 0x60, 0x2b, 0xdd, 0x0a, 0xb6, 0x80, 0x67, 0x04, 0xf6, 0x3e, 0x8d, 0x5d, 0x06, 0x80, 0x36, 0x36, 0xc4, 0xc8, 0x85, 0x22, 0x79, 0xc0, 0x4c, 0xea, 0x37, 0x3f, 0x7c, 0x47, 0xb3, 0x9e, 0xdf, 0x6e, 0xa7, 0xa3, 0x4e, 0x8d, 0xa2, 0xd3, 0x2f, 0x03, 0xbe, 0xb7, 0xfd, 0xcc, 0xca, 0x60, 0x5b, 0xda, 0xb4, 0x08, 0xb0, 0xc2, 0x8c, 0xa5, 0x15, 0x92, 0xd2, 0xda, 0xf4, 0xd2, 0x78, 0x90, 0xc7, 0xc5, 0xa7, 0xd9, 0x30, 0x0c, 0xc2, 0xa9, 0x95, 0x44, 0x6c, 0xca, 0x53, 0x15, 0x42, 0x09, 0xbe, 0x5a, 0x46, 0x17, 0xea, 0x53, 0x04, 0x7b, 0xe2, 0x9b, 0x4a, 0x84, 0x7c, 0x92, 0x73, 0xd2, 0xc4, 0xf1, 0xe0, 0xf1, 0xd9, 0x9f, 0x9f, 0xe7, 0xa6, 0xfb, + 0xe7, 0x8e, 0xd3, 0xda, 0x3f, 0xf7, 0xb1, 0x93, 0x9d, 0x01, 0xc5, 0xbf, 0x09, 0xbb, 0xcc, 0x13, 0x80, 0x5b, 0x12, 0x6d, 0x04, 0x28, 0x27, 0xed, 0xa3, 0x57, 0x3c, 0xb6, 0xe0, 0x34, 0x26, 0xc5, 0x15, 0xc6, 0x59, 0xf1, 0xa8, 0xd9, 0x46, 0x98, 0xb0, 0x76, 0x66, 0xed, 0x80, 0x13, 0xa4, 0x77, 0x5b, 0x0c, 0xfd, 0xbe, 0xc7, 0xa4, 0xf7, 0x77, 0xe9, 0xf8, 0x2e, 0x13, 0xde, 0xa1, 0xe3, 0xcf, 0x33, 0x3c, 0x4c, 0xfe, 0xcd, 0x7f, 0x48, 0xef, 0x28, 0xce, 0x64, 0x77, 0x28, 0xc3, 0x33, 0xd9, 0x99, 0x33, 0xe0, 0x94, 0x63, 0xce, 0x54, 0xd7, 0x95, 0x7f, 0x68, 0x67, 0xd4, 0x46, 0x1c, 0xff, 0x19, 0xe2, 0xc4, 0x92, 0x72, 0x9e, 0x80, 0xf4, 0xf4, 0xbe, 0x2d, 0x4b, 0xff, 0x5c, 0x78, 0x16, 0x3d, 0xcf, 0x6c, 0x57, 0xb6, 0xf2, 0x1f, 0x10, 0x3e, 0x55, 0x89, 0xee, 0x66, 0x73, 0xc7, 0x0d, 0xf7, + 0x98, 0x5c, 0x18, 0x73, 0x65, 0x49, 0x9c, 0x88, 0xb3, 0x89, 0x7d, 0x48, 0x35, 0x63, 0x34, 0x2a, 0x09, 0xda, 0x3b, 0x9a, 0x5c, 0x46, 0x5c, 0xd4, 0x17, 0x31, 0x8b, 0x3b, 0x65, 0xca, 0x55, 0xa2, 0xec, 0xc2, 0x22, 0x44, 0xe8, 0x3c, 0x06, 0x7b, 0x30, 0x66, 0x62, 0x93, 0x2d, 0x18, 0x47, 0xdf, 0xd1, 0xb8, 0xfd, 0xc9, 0xf4, 0x94, 0x39, 0x72, 0xf5, 0xcf, 0xc5, 0x4e, 0x9a, 0x5d, 0xfe, 0x01, 0x5e, 0xd7, 0x54, 0xe3, 0xce, 0xe0, 0xd6, 0x79, 0xe7, 0xad, 0x9a, 0x5a, 0x7a, 0x6c, 0xee, 0x25, 0x9d, 0x6b, 0x1a, 0xca, 0xfa, 0x79, 0x66, 0x13, 0x38, 0xca, 0x5f, 0x39, 0x74, 0x42, 0xc3, 0xbc, 0x4d, 0x53, 0x43, 0x2b, 0xb8, 0x9b, 0x16, 0xad, 0x18, 0xd9, 0x12, 0xf2, 0xc4, 0xf6, 0x57, 0x79, 0x0c, 0x69, 0x67, 0xfc, 0xc6, 0x7e, 0x15, 0x3f, 0x42, 0x5b, 0xe9, 0x5d, 0xdb, 0x2f, 0x48, 0xbf, 0x42, + 0xff, 0x65, 0xa2, 0xf9, 0x41, 0x9b, 0xd6, 0x49, 0xda, 0xd9, 0x4a, 0x8e, 0x4c, 0x48, 0x23, 0x86, 0x9b, 0x19, 0xe0, 0xe8, 0x28, 0xe2, 0x98, 0xc9, 0xab, 0xc5, 0xad, 0xf2, 0x9a, 0x92, 0xe8, 0xbf, 0x92, 0xc4, 0xb8, 0x4b, 0x4d, 0x02, 0xfd, 0x01, 0xff, 0xd0, 0xfe, 0x90, 0x62, 0xf6, 0x07, 0xbc, 0x11, 0xad, 0xad, 0x4e, 0x26, 0xa6, 0x32, 0x1e, 0x13, 0xdc, 0xa2, 0x7e, 0xfd, 0x7c, 0xf2, 0xf5, 0xc7, 0x7e, 0xfc, 0xe3, 0x63, 0x38, 0x57, 0xf9, 0x03, 0x7f, 0x71, 0xdb, 0x38, 0xfa, 0xad, 0xb7, 0x73, 0x4b, 0x16, 0xad, 0x68, 0x1f, 0x12, 0xaa, 0x10, 0xae, 0xbb, 0xe6, 0xa7, 0x3f, 0xbd, 0x86, 0x7c, 0xd7, 0x22, 0xe5, 0x2e, 0xfe, 0x5a, 0xea, 0xb3, 0x63, 0x36, 0xf3, 0xd9, 0xc1, 0x09, 0x54, 0xbf, 0x2f, 0x22, 0x1f, 0x07, 0xbe, 0x3c, 0xc0, 0x8f, 0x01, 0x78, 0x56, 0x84, 0xfb, 0x6f, 0xc4, 0xf6, + 0xca, 0xa1, 0x57, 0x50, 0xbc, 0x9d, 0x9a, 0x37, 0xe4, 0xa4, 0x44, 0x9b, 0x05, 0x36, 0x09, 0x5d, 0xa2, 0xc4, 0x42, 0xaa, 0xbb, 0xb5, 0xdd, 0x1c, 0x75, 0xe3, 0x06, 0x4f, 0x39, 0xfa, 0x3f, 0x29, 0xf9, 0x55, 0x5e, 0xef, 0xa0, 0xfc, 0xe4, 0xe4, 0xfc, 0x41, 0x5e, 0x6f, 0x55, 0x7e, 0x0a, 0xff, 0x6d, 0xaf, 0x85, 0xff, 0x16, 0x0f, 0xf6, 0x56, 0xfe, 0x7f, 0xec, 0xbd, 0x09, 0x80, 0x94, 0xc5, 0x95, 0x38, 0x5e, 0xf5, 0x9d, 0x7d, 0xf7, 0xf4, 0x7d, 0x4d, 0xcf, 0x74, 0x4f, 0x5f, 0x33, 0xdd, 0xd3, 0x73, 0xf5, 0xdc, 0x67, 0x0f, 0x37, 0x33, 0x03, 0x33, 0x20, 0xd7, 0xc8, 0xcd, 0x0c, 0x97, 0x22, 0x0c, 0xa8, 0x88, 0x17, 0xa8, 0x88, 0x0a, 0x22, 0xa8, 0x40, 0x44, 0xc5, 0x8d, 0x89, 0x46, 0x57, 0x21, 0x04, 0x45, 0x34, 0x26, 0xc6, 0x28, 0xb0, 0xbb, 0xd9, 0x98, 0xc4, 0xe4, 0x67, 0xd6, 0xb8, + 0x26, 0xba, 0x9a, 0x78, 0x65, 0xa3, 0x59, 0xe3, 0x15, 0xaf, 0xf9, 0xe6, 0x5f, 0xc7, 0xf7, 0x7d, 0x7d, 0xcc, 0x0c, 0xe8, 0x26, 0xd9, 0x24, 0xbb, 0x7f, 0x50, 0xba, 0xfb, 0x7d, 0xef, 0xab, 0x7a, 0xf5, 0xaa, 0x5e, 0xd5, 0xab, 0xaa, 0x77, 0x60, 0x58, 0xb5, 0xaf, 0x90, 0x7e, 0x12, 0x3e, 0x5f, 0x20, 0xed, 0xc3, 0xf5, 0x32, 0x32, 0x3d, 0xf0, 0xf5, 0x91, 0x15, 0x64, 0xfc, 0xfe, 0xab, 0xf4, 0x43, 0xa6, 0x48, 0x81, 0x93, 0x98, 0x68, 0xcb, 0xf3, 0xce, 0xe0, 0x18, 0x30, 0x20, 0xed, 0x66, 0xde, 0x46, 0x34, 0xdb, 0x40, 0x12, 0x6c, 0xa5, 0xf3, 0x29, 0x0e, 0x90, 0x8b, 0xa7, 0xad, 0x21, 0x1c, 0xc3, 0x9b, 0x44, 0xbc, 0x58, 0x89, 0xd7, 0xde, 0x55, 0xd4, 0x1c, 0x0b, 0x1b, 0x46, 0x64, 0x96, 0xed, 0x2a, 0x05, 0x17, 0x88, 0xac, 0xc0, 0x8a, 0xc2, 0xd8, 0xef, 0x80, 0xcc, 0x2b, 0xfd, 0x69, + 0x17, 0x04, 0x89, 0xb2, 0xd2, 0x68, 0x28, 0x88, 0x5d, 0x7b, 0xdd, 0x4e, 0xbd, 0x16, 0xd8, 0xa0, 0x0d, 0xa7, 0x6e, 0xb2, 0xe5, 0x5c, 0x88, 0xc8, 0xc1, 0x0b, 0x88, 0xfd, 0x96, 0x25, 0x65, 0x69, 0xc8, 0x32, 0xd0, 0x7a, 0x2b, 0x9b, 0x31, 0x88, 0x51, 0xf0, 0xb7, 0xb2, 0x85, 0xd6, 0xf1, 0x53, 0x50, 0x35, 0xc0, 0xe2, 0x8e, 0x64, 0xb1, 0xa9, 0x10, 0xb3, 0xed, 0x53, 0x5e, 0xb6, 0xc1, 0x42, 0xbc, 0xbc, 0x83, 0xc9, 0xd8, 0x59, 0x8d, 0xfc, 0x07, 0x22, 0x79, 0x2b, 0x7f, 0x04, 0xf5, 0xe7, 0x20, 0xc9, 0xbf, 0xf1, 0xe0, 0x08, 0xf5, 0x2f, 0xdf, 0x81, 0x18, 0x14, 0xa7, 0x70, 0x3a, 0xef, 0x50, 0xf8, 0xc8, 0xf7, 0x10, 0xbc, 0x8e, 0xc4, 0x72, 0xa1, 0xf8, 0x47, 0x65, 0xf8, 0xcf, 0x10, 0x3c, 0xca, 0xef, 0x55, 0xe1, 0xc7, 0x65, 0xf8, 0x6f, 0x10, 0xdc, 0xcc, 0x6f, 0x57, 0xe0, 0xfc, 0xd3, + 0x32, 0x1c, 0xc7, 0xa3, 0xf3, 0xf1, 0x5d, 0x2a, 0xfe, 0x63, 0x32, 0xfc, 0x15, 0x04, 0xb7, 0x66, 0x95, 0xff, 0xb8, 0x0c, 0xff, 0x31, 0x82, 0x5b, 0x28, 0xfe, 0x47, 0x14, 0x7f, 0xe9, 0x99, 0xca, 0xc7, 0xb7, 0x76, 0xf0, 0x01, 0xb2, 0xef, 0x23, 0x70, 0xf8, 0x1c, 0x8e, 0x0e, 0x3a, 0x06, 0xfd, 0xc7, 0x14, 0x7a, 0xd0, 0x78, 0x29, 0x67, 0x71, 0xce, 0xa1, 0x41, 0x13, 0x1d, 0x2f, 0x1b, 0x46, 0xc5, 0xd0, 0x7b, 0x1a, 0x27, 0x05, 0x16, 0xde, 0x92, 0xdf, 0x8d, 0x21, 0x9e, 0xec, 0x87, 0xad, 0x38, 0x9b, 0x32, 0x2e, 0x43, 0x3e, 0x43, 0x6b, 0x95, 0xf7, 0x88, 0x3a, 0xf4, 0xcf, 0x27, 0xa8, 0x3c, 0x3d, 0x08, 0xa6, 0x8b, 0x78, 0xc8, 0x62, 0x5b, 0x68, 0x16, 0x2c, 0xa7, 0xe3, 0x82, 0xec, 0xcd, 0x88, 0x94, 0x16, 0x08, 0xd4, 0x3f, 0x2e, 0xa4, 0x04, 0xbd, 0x82, 0x9f, 0x1c, 0x38, 0x8d, 0x8f, + 0x13, 0xe0, 0x03, 0x64, 0x0f, 0x04, 0x9f, 0xc6, 0x31, 0xea, 0x50, 0x79, 0x68, 0xfe, 0x86, 0x3d, 0xa8, 0x3c, 0x16, 0xe7, 0x18, 0x65, 0xe8, 0xa9, 0x98, 0x12, 0x40, 0x0b, 0xcb, 0x13, 0xab, 0xa3, 0xf2, 0x04, 0x7b, 0x4e, 0x51, 0x71, 0xc1, 0xf1, 0xb4, 0xb1, 0x6c, 0x28, 0x6d, 0x22, 0xb2, 0xb1, 0x31, 0x8b, 0xc7, 0x87, 0x15, 0x1e, 0x30, 0xac, 0xcc, 0x03, 0x54, 0x13, 0xfc, 0x55, 0x66, 0x2c, 0xc0, 0x11, 0xd2, 0x2e, 0x66, 0xe4, 0xdb, 0xd2, 0x01, 0x22, 0xdb, 0x16, 0x10, 0x01, 0xf3, 0x4f, 0x68, 0x49, 0x90, 0xb9, 0xee, 0x63, 0x15, 0x38, 0x7e, 0x1b, 0x8e, 0x02, 0x8d, 0x27, 0xe2, 0x00, 0xc9, 0x60, 0xe6, 0xeb, 0x96, 0xb3, 0x3d, 0xa3, 0xe9, 0x8d, 0x46, 0x99, 0x23, 0x39, 0x5f, 0xf3, 0x90, 0xb0, 0x20, 0x98, 0x01, 0x28, 0x09, 0xb8, 0x9d, 0x38, 0xee, 0x70, 0x84, 0x7a, 0x83, 0xe7, 0xe8, + 0xac, 0xf5, 0x75, 0x63, 0x4c, 0x0c, 0xc1, 0xa9, 0x1b, 0xfb, 0x82, 0x0d, 0x85, 0x1a, 0xce, 0x17, 0x9d, 0x91, 0x7e, 0xcb, 0x19, 0x45, 0x23, 0x5b, 0x96, 0x85, 0xc2, 0xea, 0x28, 0x9a, 0x24, 0x3e, 0xfb, 0x41, 0xc7, 0x05, 0xb3, 0x2a, 0x74, 0xda, 0xf3, 0x6c, 0xdc, 0x94, 0x59, 0x5c, 0xe3, 0x18, 0xf3, 0x05, 0x6a, 0xcb, 0x8b, 0xd2, 0x01, 0x34, 0x5e, 0xa8, 0xcc, 0x4f, 0x4d, 0x4f, 0x2a, 0x82, 0x2c, 0x07, 0xbb, 0x34, 0x50, 0x40, 0x94, 0x71, 0x02, 0x43, 0x7c, 0xf2, 0x15, 0x11, 0x26, 0xba, 0x78, 0xc6, 0x06, 0x0a, 0x02, 0x9c, 0xc6, 0xa7, 0xac, 0x34, 0x86, 0xc4, 0xb7, 0xd0, 0xeb, 0xb0, 0x53, 0xe1, 0xc5, 0x71, 0xfc, 0x61, 0x9e, 0xf0, 0xda, 0x42, 0xf9, 0x8d, 0x51, 0x3a, 0xd7, 0x96, 0x4d, 0x31, 0x6a, 0xc1, 0x2b, 0x07, 0x73, 0x1a, 0xb5, 0x7f, 0xff, 0xa9, 0x83, 0xa3, 0x65, 0xf7, 0xf3, + 0x61, 0xc9, 0x95, 0xd5, 0x32, 0xf8, 0x5b, 0x3a, 0x2a, 0x98, 0x91, 0xa3, 0xa8, 0x5f, 0x86, 0x48, 0x5b, 0x62, 0xb0, 0x52, 0xed, 0x17, 0x6d, 0x76, 0xbf, 0xa4, 0x08, 0xcb, 0x2b, 0xd4, 0x7e, 0x69, 0x04, 0x4a, 0xcc, 0x98, 0x71, 0x51, 0xaa, 0x80, 0x12, 0x26, 0xe6, 0x2c, 0xa5, 0x58, 0xce, 0x5e, 0x8a, 0xed, 0xec, 0xa5, 0x38, 0xce, 0x5e, 0x4a, 0xc5, 0xd9, 0x4a, 0x19, 0x35, 0xd2, 0x64, 0x24, 0x59, 0x97, 0xec, 0x27, 0xe3, 0x2d, 0x5c, 0xe2, 0x75, 0x23, 0x56, 0xd9, 0xea, 0x23, 0x82, 0x98, 0x33, 0xde, 0x98, 0x9a, 0xb1, 0x07, 0x5c, 0x4f, 0x70, 0xda, 0xc6, 0xde, 0x92, 0x7a, 0xbf, 0x86, 0xd1, 0x98, 0x3d, 0x36, 0x3c, 0xe4, 0x46, 0xad, 0x4b, 0xea, 0x90, 0xd3, 0x69, 0xb9, 0xa9, 0x7d, 0x63, 0x8e, 0x39, 0x24, 0x57, 0x48, 0x7e, 0x98, 0x2a, 0x32, 0xe7, 0xac, 0x96, 0xe7, 0xcc, 0x41, 0xa2, + 0x0b, 0xfc, 0x0b, 0xea, 0xbf, 0xcf, 0x90, 0x7c, 0xfa, 0x40, 0x45, 0x3a, 0xa1, 0xd7, 0xe1, 0xf8, 0xde, 0x5d, 0x02, 0xe4, 0xb0, 0xb1, 0x05, 0x07, 0x70, 0xb4, 0xf9, 0x55, 0xdd, 0x4a, 0xdc, 0xa5, 0x21, 0x32, 0x5f, 0x20, 0xda, 0x0b, 0x44, 0xbc, 0xdd, 0x0b, 0x8d, 0xa2, 0x5e, 0x19, 0x61, 0x9f, 0xdd, 0x86, 0xa9, 0x46, 0x23, 0x4a, 0x94, 0xa9, 0xde, 0xbf, 0xff, 0xf4, 0x6d, 0x68, 0x32, 0x71, 0x65, 0x13, 0xaa, 0x0c, 0x21, 0xa2, 0xa7, 0x5c, 0xce, 0x0e, 0x13, 0x3d, 0x65, 0x0d, 0xf5, 0xb3, 0xdf, 0x0a, 0x94, 0x18, 0xb2, 0xec, 0x30, 0xd1, 0x23, 0xd7, 0xc8, 0x7a, 0xe4, 0x22, 0x55, 0x8f, 0x1c, 0x26, 0xe7, 0x31, 0x14, 0xff, 0x9b, 0xd7, 0x00, 0x55, 0x8f, 0xcc, 0x2a, 0x07, 0xbe, 0xbe, 0x39, 0xa3, 0x07, 0x65, 0xc1, 0xf9, 0x5f, 0x6f, 0xa1, 0xfe, 0xd7, 0xd8, 0xd6, 0x71, 0x06, 0xd1, 0x23, + 0xf7, 0xd3, 0x95, 0x57, 0xe7, 0x81, 0x90, 0x8b, 0x41, 0x51, 0x83, 0x2d, 0x66, 0x95, 0x1f, 0x5a, 0x46, 0xb9, 0x59, 0x28, 0xc3, 0x11, 0x16, 0x05, 0x0e, 0x0e, 0xd0, 0x03, 0x5d, 0xc2, 0x1b, 0x1c, 0x45, 0x10, 0x1f, 0x7b, 0x0d, 0xe6, 0x5d, 0x27, 0x57, 0x01, 0x0d, 0xd0, 0x02, 0x8d, 0x76, 0xe8, 0x8c, 0xef, 0x64, 0x5f, 0x23, 0xa7, 0x4d, 0x95, 0xc9, 0x48, 0xc4, 0x85, 0x03, 0x36, 0x46, 0x0a, 0xf0, 0x7d, 0x42, 0x30, 0x14, 0x8d, 0xa1, 0x7d, 0xc6, 0xa8, 0x70, 0x8d, 0x02, 0xce, 0x6a, 0x04, 0x47, 0x5f, 0x30, 0xd4, 0xb1, 0xf7, 0xac, 0x0c, 0x3b, 0xa4, 0x7f, 0x36, 0x17, 0x15, 0xd5, 0x9e, 0x3a, 0x55, 0xe1, 0x28, 0x34, 0xc3, 0x2a, 0x53, 0x89, 0xf7, 0x61, 0xe9, 0x76, 0x7f, 0xd0, 0xe6, 0x37, 0x41, 0xc3, 0xc9, 0xfc, 0xcd, 0x05, 0x17, 0x84, 0x07, 0x5d, 0x46, 0xa9, 0x98, 0x3f, 0xfd, 0xf9, + 0xd7, 0x0c, 0x56, 0xd8, 0x6b, 0xb3, 0x49, 0x97, 0x44, 0xec, 0xc6, 0x12, 0xbb, 0xf4, 0xb5, 0x9c, 0xfd, 0x83, 0xc2, 0x73, 0x34, 0x4e, 0x04, 0x40, 0xd7, 0x36, 0xb4, 0xf2, 0x31, 0x9b, 0x71, 0x1f, 0x70, 0x17, 0xd1, 0x33, 0x31, 0x1c, 0xf9, 0x16, 0xaf, 0xd9, 0xd2, 0xe5, 0x70, 0x16, 0xe6, 0xb5, 0x0c, 0xff, 0x27, 0x59, 0x67, 0xf7, 0x23, 0x7c, 0x03, 0xe2, 0xf5, 0xe8, 0xb5, 0x68, 0xe5, 0x19, 0xd7, 0x22, 0xc6, 0x70, 0xd7, 0xe9, 0x3d, 0x7b, 0x4e, 0xdf, 0xc5, 0xdc, 0x31, 0xbc, 0x8a, 0xb9, 0x63, 0x0f, 0x73, 0x68, 0x78, 0x90, 0xd4, 0x73, 0x39, 0x5a, 0x57, 0xce, 0xcb, 0xd4, 0x83, 0xd6, 0x95, 0xe5, 0x64, 0xcc, 0x3c, 0x27, 0xed, 0x63, 0xf4, 0xd8, 0x47, 0x84, 0xc0, 0x63, 0x14, 0x8e, 0xfd, 0xcf, 0xe4, 0xb5, 0xfa, 0x59, 0xe9, 0x43, 0xc6, 0x96, 0x79, 0x8f, 0xff, 0xb5, 0xfc, 0xde, 0x09, + 0xe9, 0x20, 0x93, 0xcc, 0xc0, 0x05, 0x87, 0x4c, 0x37, 0x5a, 0x8f, 0x98, 0x0a, 0x44, 0xb7, 0x0d, 0xd4, 0xa5, 0x6b, 0xd0, 0x1c, 0xad, 0xd3, 0x32, 0x3c, 0x1a, 0x42, 0xcb, 0x39, 0x9c, 0x8b, 0x00, 0x7b, 0x4f, 0xa8, 0xf9, 0x0b, 0x06, 0x49, 0x7a, 0x6a, 0x24, 0xdc, 0x16, 0xe5, 0x0f, 0x31, 0xb4, 0x72, 0x28, 0x67, 0x95, 0xa9, 0x3a, 0x25, 0xa6, 0x24, 0xbc, 0xe5, 0x2e, 0xdc, 0x28, 0xdc, 0x2c, 0xb6, 0xe0, 0xe6, 0x3d, 0x92, 0x09, 0xbe, 0xb7, 0xe7, 0xe6, 0x3d, 0x4c, 0x74, 0xf8, 0x45, 0x7a, 0xde, 0x78, 0x13, 0xd2, 0x6f, 0x2f, 0xc5, 0x7a, 0x83, 0xcc, 0xc3, 0x55, 0x70, 0x44, 0x59, 0x7f, 0xf9, 0x3f, 0x72, 0x1a, 0x10, 0x07, 0x0b, 0xd3, 0x7a, 0x1e, 0x29, 0xe0, 0x65, 0x7a, 0x86, 0xe5, 0x18, 0xd9, 0xbd, 0xc9, 0x89, 0x5d, 0xfe, 0x71, 0x42, 0x77, 0x06, 0x80, 0x55, 0xdd, 0x1a, 0xb4, 0x01, + 0x96, 0x3d, 0x3f, 0xbc, 0x00, 0x03, 0xf0, 0x53, 0xb8, 0x10, 0xaf, 0xda, 0xeb, 0x33, 0x0f, 0x69, 0x20, 0x80, 0x38, 0x88, 0x87, 0x1c, 0x21, 0x47, 0x34, 0x1c, 0xc4, 0xe7, 0x38, 0x78, 0xa8, 0x15, 0x2b, 0x06, 0xd0, 0x6c, 0xb6, 0xde, 0x6e, 0xca, 0xd6, 0xe0, 0x77, 0x5e, 0x5c, 0xa6, 0x33, 0x35, 0xee, 0x5c, 0xb4, 0x72, 0xab, 0xdf, 0x39, 0x65, 0xde, 0xf2, 0x9a, 0x59, 0xdb, 0x16, 0x54, 0x9c, 0x1e, 0x58, 0x1a, 0xef, 0x69, 0x0a, 0x9e, 0x5e, 0xb2, 0xb0, 0x63, 0x5d, 0x92, 0xfd, 0x38, 0xb6, 0x7c, 0xe2, 0xdc, 0x0d, 0x4b, 0xeb, 0x67, 0xd6, 0xb8, 0x53, 0x2b, 0x6e, 0x5e, 0x84, 0x37, 0x2d, 0x9b, 0x2e, 0xf2, 0x37, 0xcf, 0x6b, 0xc2, 0xdf, 0x36, 0x0f, 0x4d, 0x68, 0x1a, 0x96, 0xf3, 0x0a, 0x91, 0x76, 0x91, 0xfd, 0xe5, 0x3a, 0xd9, 0xde, 0xfd, 0x9f, 0xb2, 0xe0, 0x4f, 0xa9, 0xf0, 0xa7, 0x15, + 0xb8, 0xb4, 0x0f, 0xf3, 0x41, 0x81, 0xc3, 0xd7, 0xe1, 0xc3, 0x8a, 0x3c, 0x0b, 0x47, 0x10, 0x7f, 0x3a, 0xc0, 0x4f, 0xe5, 0x3b, 0x43, 0x2f, 0x0e, 0xce, 0x61, 0x63, 0x34, 0xb0, 0x0e, 0x6a, 0x75, 0xe4, 0xce, 0x30, 0x1b, 0xa2, 0x27, 0x27, 0xe2, 0x98, 0x77, 0x11, 0xbc, 0x71, 0xe1, 0x17, 0x8a, 0x02, 0x43, 0x04, 0x54, 0xa3, 0x01, 0xfd, 0x1c, 0xbe, 0x08, 0xa2, 0x0e, 0x18, 0xe4, 0xac, 0x60, 0x48, 0xab, 0x9c, 0x77, 0x94, 0x11, 0x29, 0x66, 0x55, 0xb3, 0xf6, 0xf5, 0xe3, 0xbf, 0x80, 0xa6, 0x01, 0x1d, 0x1a, 0xf2, 0x3a, 0xfd, 0xd0, 0x19, 0xdf, 0x01, 0x7a, 0xbd, 0xf2, 0x0a, 0xf6, 0xab, 0x68, 0x6b, 0x69, 0x6e, 0x4c, 0x55, 0xe3, 0xfe, 0x40, 0x1b, 0xa8, 0x10, 0xf1, 0xab, 0x08, 0x8e, 0xd1, 0x03, 0xa1, 0x31, 0x3a, 0x6a, 0x8c, 0x59, 0x41, 0x60, 0x9e, 0xcd, 0xee, 0x96, 0x53, 0x63, 0xf4, + 0xdb, 0x89, 0xfc, 0x89, 0x61, 0x74, 0x4f, 0xe5, 0xf7, 0xa6, 0x64, 0xcc, 0x99, 0x21, 0xc8, 0xfe, 0xeb, 0xc6, 0x91, 0x3a, 0xfe, 0x13, 0x0e, 0xcf, 0xa7, 0xf5, 0xe0, 0x59, 0xca, 0x58, 0x47, 0x31, 0x64, 0x78, 0x13, 0x5a, 0x4c, 0xea, 0x53, 0xe5, 0x65, 0xa5, 0x76, 0x11, 0xa0, 0xfd, 0x09, 0x31, 0x1f, 0x47, 0x70, 0x76, 0x14, 0x5c, 0xe9, 0x0d, 0xf4, 0x4b, 0x73, 0x21, 0x4e, 0x82, 0xd6, 0x8f, 0x7a, 0x84, 0xec, 0xb5, 0xb1, 0x09, 0x0e, 0x89, 0x58, 0xb9, 0xaa, 0x9b, 0x82, 0x20, 0xee, 0x0d, 0x9a, 0x4e, 0x24, 0x1f, 0x99, 0xa3, 0xc1, 0x8e, 0x64, 0xb3, 0xb0, 0xf5, 0x99, 0x17, 0xc6, 0xc0, 0x05, 0x2a, 0x2a, 0x3e, 0x96, 0xca, 0xe0, 0x62, 0xc3, 0xf1, 0xb0, 0xd3, 0x11, 0x72, 0xe2, 0x3e, 0x20, 0x5b, 0xfa, 0xb1, 0xb9, 0xde, 0x41, 0xa6, 0x64, 0x91, 0x86, 0xca, 0x44, 0xdb, 0x79, 0x88, 0x6f, + 0xe7, 0x48, 0x88, 0x89, 0x06, 0x13, 0x74, 0x30, 0xff, 0x91, 0xcf, 0x72, 0xc7, 0xa4, 0x73, 0x96, 0x56, 0x2f, 0xc0, 0x06, 0x22, 0xdb, 0x2e, 0xda, 0x7a, 0x7a, 0xda, 0x64, 0xe9, 0xb3, 0xd3, 0x4f, 0x63, 0xeb, 0x11, 0xc6, 0xbd, 0xb7, 0x7d, 0x16, 0x9c, 0x01, 0x9b, 0x73, 0xb9, 0x5d, 0x37, 0xa3, 0xc6, 0x03, 0x77, 0xb9, 0x7c, 0x3f, 0xf0, 0x7f, 0xef, 0x4e, 0xdc, 0x17, 0xcb, 0x87, 0xcc, 0xd2, 0xa0, 0x54, 0x77, 0x9b, 0xf4, 0x53, 0x97, 0x8f, 0x9d, 0xf1, 0xaa, 0x6d, 0xfd, 0xc2, 0xcc, 0xb9, 0x0a, 0x96, 0x9f, 0x0b, 0xe4, 0xf3, 0x99, 0xfb, 0xb3, 0xce, 0x5b, 0x0e, 0xa9, 0xf0, 0x63, 0x39, 0xf0, 0xbd, 0x2a, 0xfc, 0xb8, 0xea, 0x13, 0x70, 0x39, 0xfb, 0x2a, 0x91, 0xab, 0x0b, 0xe4, 0x78, 0x29, 0xf7, 0x80, 0xb1, 0xca, 0x7f, 0x30, 0xa7, 0x9c, 0xa7, 0x54, 0xf8, 0xd3, 0x6a, 0x39, 0xfb, 0xb2, + 0xcb, 0x81, 0xaf, 0x83, 0x3b, 0x33, 0xe7, 0x11, 0x19, 0x38, 0xff, 0x6b, 0xd9, 0x3e, 0x77, 0xa7, 0x74, 0x30, 0x1b, 0x2e, 0x38, 0x64, 0xbb, 0x75, 0xbc, 0x3e, 0x7f, 0x05, 0xc9, 0x73, 0x05, 0xbe, 0xe3, 0xc2, 0x92, 0xa7, 0x0f, 0x15, 0x32, 0x1a, 0x4e, 0xab, 0xc4, 0x47, 0x51, 0x7e, 0xd1, 0xf8, 0x28, 0x78, 0xe4, 0x84, 0x54, 0xa7, 0x91, 0x55, 0x64, 0x3d, 0x66, 0xfb, 0x79, 0x38, 0x2a, 0x48, 0x0a, 0x2d, 0x2b, 0x06, 0x30, 0x12, 0xc2, 0x97, 0x6d, 0x68, 0xc7, 0x45, 0x4f, 0x57, 0x64, 0x72, 0xf0, 0x8c, 0xfb, 0x46, 0x76, 0x70, 0x95, 0xfe, 0x34, 0x0e, 0x9d, 0x5d, 0x01, 0x2a, 0xa2, 0x68, 0xec, 0x60, 0x03, 0x01, 0x9d, 0xae, 0x30, 0x11, 0x19, 0xd3, 0x1e, 0x20, 0x77, 0x4c, 0xc9, 0x32, 0xcc, 0x7d, 0x25, 0x5f, 0x1c, 0x9f, 0x25, 0xe7, 0x42, 0x78, 0x18, 0x99, 0x83, 0xd3, 0x66, 0x2d, 0xae, + 0xc3, 0xa7, 0x25, 0xfb, 0x73, 0x4f, 0xf8, 0x94, 0x03, 0x21, 0x34, 0x72, 0x56, 0x85, 0x9a, 0xcb, 0x5c, 0xf8, 0xe8, 0x44, 0x32, 0x2a, 0xfa, 0xcf, 0x01, 0xfe, 0xb7, 0xe4, 0xbc, 0xe4, 0x02, 0x39, 0x4e, 0xf0, 0x06, 0xd5, 0xd6, 0xaf, 0x84, 0xf4, 0xeb, 0x06, 0xb9, 0x5f, 0x0f, 0x8f, 0x09, 0x3f, 0x92, 0x03, 0x3f, 0xa4, 0xc2, 0x8f, 0xe6, 0xc0, 0xf7, 0xaa, 0xf0, 0xe3, 0x39, 0xf0, 0xed, 0x2a, 0xfc, 0x44, 0x0e, 0xbc, 0x4b, 0x85, 0x3f, 0x36, 0x4e, 0xf9, 0x8f, 0x8f, 0x43, 0xcf, 0xf7, 0x65, 0xbb, 0x6b, 0x02, 0x47, 0xe3, 0xc3, 0x06, 0x8a, 0x71, 0x24, 0x61, 0x12, 0x15, 0x14, 0xad, 0xcb, 0xa0, 0x9e, 0x68, 0xde, 0x4a, 0x06, 0x45, 0x87, 0x1d, 0x02, 0x9f, 0xc7, 0x5e, 0xec, 0x28, 0xa6, 0xbb, 0x25, 0x92, 0x15, 0x5e, 0xde, 0x25, 0x29, 0xba, 0xb6, 0x4d, 0xf1, 0x09, 0x71, 0xd8, 0x9d, 0x4c, + 0xf3, 0xd0, 0x89, 0x6d, 0x93, 0x26, 0x6d, 0x3b, 0x31, 0xb4, 0xf1, 0xc4, 0x55, 0x93, 0x27, 0x5f, 0x75, 0x62, 0xe3, 0xcd, 0x7b, 0xf6, 0xdc, 0x7c, 0xeb, 0xde, 0xbd, 0x9c, 0x66, 0xfa, 0xce, 0xa7, 0xb7, 0x6c, 0x39, 0x79, 0xc3, 0xf4, 0xe9, 0x37, 0x9c, 0xdc, 0xb2, 0xe5, 0xe9, 0x9d, 0xd3, 0x3f, 0xbf, 0xe6, 0xe4, 0xb1, 0x63, 0xa7, 0x4e, 0x1d, 0x3b, 0x76, 0x32, 0x8b, 0xce, 0x17, 0x55, 0x3a, 0xdf, 0x1e, 0x83, 0xce, 0x5e, 0xba, 0x67, 0x71, 0xa9, 0xc4, 0x96, 0x64, 0x88, 0x0d, 0xe2, 0x39, 0x6a, 0xcc, 0x27, 0x21, 0x7c, 0xa6, 0x66, 0x1b, 0xd5, 0x12, 0xe1, 0x2f, 0xd0, 0x12, 0x6a, 0xd3, 0x89, 0xe9, 0x55, 0xda, 0x81, 0xe4, 0x37, 0xbb, 0x1f, 0x4e, 0xab, 0xf0, 0xe7, 0xc0, 0x11, 0x15, 0x7e, 0x94, 0xf8, 0x04, 0x6f, 0x90, 0x6d, 0x4c, 0x09, 0x7c, 0xe4, 0x87, 0x18, 0x9f, 0xd8, 0x98, 0x6e, + 0x90, 0x6d, 0x4c, 0x33, 0xf8, 0x25, 0xc4, 0xc6, 0x74, 0x83, 0x6c, 0x63, 0x9a, 0x0d, 0xff, 0x81, 0x0a, 0xff, 0xa5, 0x02, 0xc7, 0xf4, 0xa8, 0xe3, 0x22, 0x46, 0xe9, 0x19, 0x35, 0xfe, 0x32, 0xe3, 0xe6, 0x18, 0x79, 0x8e, 0xed, 0xdb, 0x1e, 0xc4, 0xe3, 0x0c, 0x7d, 0x33, 0x83, 0x56, 0x39, 0xc6, 0xd2, 0x09, 0x0f, 0x8e, 0x01, 0xde, 0xe5, 0x43, 0x9f, 0x22, 0x8d, 0x4f, 0xef, 0x26, 0xe6, 0xc7, 0x0c, 0x2b, 0xb0, 0x8c, 0x40, 0x22, 0x80, 0xf2, 0x34, 0x02, 0xa8, 0xec, 0xcd, 0x81, 0x27, 0x13, 0x6e, 0x91, 0x12, 0x2a, 0xb4, 0x1c, 0x21, 0x8a, 0x2c, 0x23, 0x0e, 0xa1, 0x69, 0x8a, 0x15, 0x85, 0xf5, 0xa3, 0xf1, 0x41, 0x06, 0xbd, 0x3f, 0x6d, 0x6c, 0x69, 0xaa, 0xaf, 0xad, 0x08, 0x87, 0x43, 0x16, 0x1a, 0x84, 0x88, 0x84, 0x08, 0x75, 0xaa, 0x1e, 0x48, 0x59, 0xe1, 0x41, 0xd9, 0xec, 0xcd, 0x7b, + 0x4e, 0x68, 0x50, 0x76, 0xf1, 0xf0, 0x13, 0xcc, 0xa4, 0xe1, 0x27, 0xf6, 0xde, 0x4a, 0x7b, 0x0f, 0xfe, 0x86, 0x84, 0x04, 0x9d, 0xd0, 0xb0, 0xf2, 0xeb, 0x1b, 0xdb, 0x70, 0xa7, 0xe2, 0x4e, 0x6e, 0xbe, 0xe0, 0xce, 0xe5, 0xd6, 0xb0, 0xcf, 0xa2, 0x06, 0x04, 0xe5, 0x9e, 0xd9, 0xf6, 0xf9, 0xc3, 0xc7, 0x4e, 0x92, 0x5e, 0xfd, 0xbc, 0x1a, 0x07, 0x02, 0xad, 0x5d, 0x1a, 0xd5, 0xf6, 0x5d, 0xff, 0xed, 0x8d, 0xb8, 0xbb, 0x71, 0xf7, 0x6f, 0x78, 0x74, 0xc7, 0x6c, 0xbd, 0xc1, 0x1f, 0x2a, 0x93, 0x83, 0x80, 0x82, 0xac, 0x3e, 0xb8, 0x4e, 0xe1, 0x25, 0x37, 0x3f, 0x87, 0xc7, 0x4f, 0xa9, 0x3c, 0x7e, 0x3a, 0x1b, 0x4e, 0xfc, 0x9f, 0x37, 0xc8, 0xfe, 0xcf, 0x87, 0xf1, 0x82, 0x48, 0x9f, 0x67, 0x7b, 0x40, 0xab, 0xb8, 0xaf, 0x2a, 0x65, 0xf0, 0x3b, 0x72, 0xca, 0x78, 0x4c, 0x85, 0xdf, 0x98, 0x03, + 0x3f, 0xa9, 0xc2, 0x6f, 0x18, 0x07, 0x7e, 0x7d, 0x0e, 0x8d, 0x4f, 0xaa, 0xf0, 0x29, 0xb9, 0x70, 0x65, 0x7c, 0xf2, 0x6d, 0xe3, 0xe0, 0xb7, 0xe7, 0xc0, 0x4f, 0xaa, 0xf8, 0x4d, 0xe3, 0xc0, 0xd3, 0xb9, 0x70, 0xb5, 0x9c, 0x09, 0x39, 0xf0, 0xbc, 0xf9, 0x40, 0x85, 0x1f, 0x57, 0xf1, 0x5b, 0xbe, 0xd0, 0x7c, 0x79, 0x58, 0x81, 0x33, 0xec, 0xd8, 0xf3, 0x22, 0xda, 0x25, 0x50, 0xf8, 0xe4, 0x91, 0x47, 0xb8, 0x4e, 0x72, 0x86, 0x36, 0x24, 0xcf, 0xdf, 0x0f, 0xc8, 0xbe, 0x7d, 0x87, 0x39, 0x1b, 0xa7, 0x07, 0x26, 0xd4, 0x4f, 0xd7, 0xa4, 0xf1, 0x5d, 0x24, 0x40, 0x73, 0x0e, 0x0b, 0x14, 0x57, 0x30, 0x1f, 0x71, 0x8e, 0xc0, 0x93, 0x4e, 0x89, 0xaa, 0x3e, 0x05, 0xf1, 0x82, 0xe7, 0xe7, 0x94, 0xfb, 0xc6, 0xb1, 0x50, 0x42, 0x0a, 0x4a, 0xba, 0x18, 0x49, 0x80, 0x4f, 0x75, 0xcc, 0x1b, 0x8d, 0x81, + 0x96, 0x46, 0x6d, 0xd0, 0x56, 0x1a, 0x8b, 0x12, 0x07, 0x31, 0xc5, 0x17, 0x8f, 0x1a, 0x4e, 0xc6, 0xe4, 0xe5, 0x4f, 0x5e, 0x26, 0xc9, 0x9a, 0xe8, 0x82, 0x3d, 0x19, 0x47, 0x8a, 0x2b, 0xeb, 0x74, 0x81, 0xe9, 0x73, 0x96, 0xd6, 0xcf, 0xbf, 0x69, 0xb0, 0xa1, 0x6d, 0xe3, 0x3d, 0xab, 0x16, 0x5e, 0x93, 0xc2, 0x2b, 0xe2, 0xa7, 0xaa, 0x27, 0xc5, 0x94, 0x1d, 0xd1, 0x8e, 0xa4, 0x67, 0xd2, 0xb5, 0x27, 0xb7, 0x6e, 0x7c, 0x7a, 0xcf, 0xac, 0xd6, 0x7a, 0x69, 0x2a, 0x3f, 0x23, 0x46, 0xd7, 0x3f, 0xd4, 0x6e, 0xc2, 0x8f, 0x4d, 0xd4, 0x5e, 0xa2, 0x4d, 0x59, 0x17, 0x31, 0xfc, 0x29, 0x19, 0x8e, 0xc7, 0xec, 0x9d, 0xe4, 0x7c, 0xee, 0x23, 0x95, 0x4f, 0x95, 0xe0, 0x1b, 0x79, 0x7c, 0xc2, 0x27, 0x5b, 0x11, 0x0d, 0xe4, 0x44, 0x88, 0x3d, 0x11, 0x97, 0x93, 0xb6, 0x62, 0xaf, 0xd5, 0xf5, 0x6a, 0x63, + 0xd7, 0xe1, 0x29, 0x60, 0x8d, 0xea, 0x27, 0x7b, 0x66, 0xe4, 0x0d, 0x0a, 0x72, 0xba, 0x2c, 0x0b, 0x4f, 0xab, 0x5d, 0x95, 0xeb, 0x0f, 0x99, 0x83, 0x8b, 0x7d, 0x52, 0x2a, 0xca, 0xc9, 0xfd, 0x0a, 0x66, 0xa5, 0x2e, 0xcb, 0xad, 0x71, 0x4c, 0x56, 0x92, 0xf8, 0x9c, 0x21, 0xba, 0xfb, 0x3c, 0x2b, 0x4b, 0x6f, 0xbf, 0xfd, 0xd4, 0xbe, 0x7d, 0x67, 0x64, 0xeb, 0x91, 0x3d, 0xdb, 0xb6, 0xed, 0xe1, 0xd2, 0xf2, 0x9d, 0x3a, 0xf1, 0x17, 0x25, 0xbc, 0x6a, 0x85, 0x7e, 0xaa, 0x84, 0x15, 0x14, 0x9a, 0xb0, 0x6b, 0x68, 0x3d, 0x14, 0x40, 0x84, 0x7a, 0x8b, 0xca, 0x3e, 0x51, 0xd9, 0x70, 0x0d, 0xf1, 0x22, 0xcd, 0x05, 0xe1, 0x2c, 0xc1, 0xb2, 0xe9, 0x42, 0x28, 0x97, 0x57, 0x54, 0xa7, 0x1b, 0x94, 0xfd, 0x41, 0x35, 0x50, 0x71, 0x1f, 0x2d, 0x22, 0x8a, 0xdc, 0x28, 0x76, 0x8d, 0x87, 0x8e, 0xa9, 0x48, + 0x8c, 0x8f, 0x8e, 0xb6, 0x64, 0x6b, 0xf2, 0x5c, 0x4e, 0xeb, 0x01, 0x27, 0xf2, 0x22, 0x76, 0x39, 0xcd, 0x7b, 0x8b, 0xe0, 0xcb, 0x0e, 0x58, 0x1b, 0xe8, 0x9b, 0x39, 0x7e, 0xa7, 0xa8, 0x8f, 0x5a, 0x9a, 0x6c, 0x41, 0xa4, 0xfe, 0x95, 0x9f, 0xb9, 0x8f, 0xc6, 0xf4, 0x3d, 0x0d, 0x09, 0x67, 0x97, 0x81, 0xc6, 0xfa, 0x7c, 0x35, 0xb1, 0xb2, 0xbe, 0xf9, 0x0c, 0x42, 0x71, 0xc1, 0x13, 0x53, 0x1f, 0xcf, 0x51, 0x19, 0x4d, 0x8a, 0x88, 0x64, 0xcf, 0x0d, 0x36, 0x34, 0xe6, 0x6f, 0x4a, 0x3b, 0x6d, 0x1a, 0x51, 0xe0, 0x91, 0xa8, 0xc0, 0xb2, 0x70, 0x10, 0xe9, 0x23, 0xa1, 0x12, 0x12, 0x57, 0xf5, 0x7f, 0x74, 0x9a, 0x48, 0xc4, 0x12, 0x99, 0x69, 0xa2, 0x21, 0xd6, 0xe0, 0xc2, 0xb1, 0x5a, 0x47, 0x31, 0x0f, 0xc7, 0x6a, 0xcd, 0xe3, 0x55, 0x91, 0xeb, 0xbc, 0xab, 0x36, 0x26, 0xab, 0x46, 0x31, 0x6c, + 0xf5, 0x92, 0x95, 0xdb, 0x36, 0xd6, 0xd4, 0x65, 0x58, 0xc4, 0xdd, 0x3b, 0xfb, 0x5b, 0x55, 0xb7, 0xad, 0xc9, 0xe3, 0xd3, 0x81, 0xb7, 0x27, 0x1f, 0x9a, 0xfd, 0x2d, 0x43, 0xf6, 0xfc, 0x81, 0x18, 0xf4, 0x3c, 0x99, 0x67, 0x2f, 0xa6, 0xf3, 0x87, 0x83, 0xce, 0x1f, 0x6b, 0x10, 0xfc, 0x15, 0x72, 0xa6, 0x7a, 0xf1, 0x08, 0xb1, 0xcf, 0xb2, 0x53, 0xf8, 0xd5, 0xd2, 0xe5, 0xcc, 0xb3, 0xc4, 0xaf, 0xff, 0x62, 0xf9, 0x0c, 0x6c, 0x31, 0xcd, 0x47, 0x83, 0xf0, 0x7f, 0x8f, 0x78, 0x1c, 0x00, 0xdf, 0xa6, 0x6c, 0xd2, 0x09, 0x68, 0x9f, 0x61, 0x81, 0x2c, 0xde, 0xce, 0x74, 0x1f, 0x2b, 0x97, 0x21, 0x1a, 0x19, 0x82, 0x7f, 0x30, 0xf2, 0x0f, 0x45, 0x2e, 0x0a, 0x45, 0xc8, 0x0a, 0x90, 0x24, 0x7b, 0xa5, 0x9b, 0x14, 0x7c, 0xdf, 0x47, 0x1d, 0x00, 0xf9, 0x1e, 0xb9, 0x8c, 0x40, 0x06, 0x07, 0x8d, 0xcb, + 0x55, 0x99, 0xdd, 0x8c, 0x8a, 0x87, 0x64, 0x06, 0x7b, 0x75, 0x42, 0x9c, 0xd5, 0x26, 0x07, 0x35, 0x73, 0xc9, 0x4d, 0x31, 0xf1, 0x36, 0x38, 0x68, 0xa1, 0xd9, 0x6d, 0x32, 0x37, 0xdb, 0x4e, 0x97, 0x88, 0x79, 0x8e, 0x8f, 0x23, 0xc4, 0xcc, 0xf1, 0x16, 0xcd, 0x14, 0xb1, 0xac, 0x7a, 0xd7, 0xf2, 0x60, 0x8b, 0xd3, 0x28, 0x7a, 0x4d, 0x8d, 0x81, 0xca, 0x69, 0x4d, 0xe5, 0xb6, 0x3b, 0xef, 0x3c, 0x75, 0xcb, 0x2d, 0x43, 0x01, 0x17, 0xcf, 0xd6, 0x0d, 0x6e, 0x32, 0x9b, 0xf7, 0xf9, 0xf4, 0xbe, 0xba, 0x19, 0xd5, 0x93, 0xf7, 0xc8, 0x77, 0x4b, 0x2e, 0xbf, 0xb2, 0x97, 0x64, 0x9e, 0x25, 0xfa, 0x05, 0xe5, 0xf1, 0xd3, 0x32, 0x8f, 0x2f, 0x91, 0xf6, 0x65, 0xf3, 0x12, 0xbe, 0x8e, 0x79, 0xa9, 0xc0, 0xc9, 0x1e, 0x02, 0xc3, 0xa3, 0xe0, 0x31, 0x87, 0xfa, 0x94, 0xf0, 0xba, 0x4f, 0xba, 0x93, 0xf9, + 0x09, 0xe2, 0x75, 0x12, 0xc6, 0x29, 0xe7, 0x4c, 0x0a, 0xaf, 0x0b, 0xa9, 0xf9, 0x38, 0x9d, 0x0d, 0x4c, 0x0a, 0xbf, 0x55, 0x68, 0x79, 0x1e, 0x54, 0xc0, 0xf3, 0x94, 0x49, 0xe9, 0x09, 0x0a, 0xe8, 0x57, 0xba, 0xa3, 0x24, 0xa7, 0x13, 0x58, 0x56, 0x3c, 0x77, 0xb4, 0x7d, 0x79, 0xc6, 0xc9, 0x3d, 0xb7, 0x2b, 0xc6, 0xc3, 0xc6, 0x04, 0x24, 0x55, 0x53, 0xf4, 0x71, 0xdf, 0xca, 0x32, 0x3f, 0xc0, 0xc1, 0x5d, 0xe4, 0xfe, 0xcc, 0x79, 0x2f, 0xab, 0x37, 0xc7, 0x7a, 0xad, 0x9f, 0xf4, 0x2e, 0xde, 0xa0, 0x8e, 0xdb, 0xbb, 0xe3, 0x98, 0xb1, 0x93, 0x9e, 0x4e, 0x8f, 0xea, 0xe9, 0x47, 0x47, 0xed, 0x5b, 0x47, 0x75, 0xbb, 0xe4, 0xce, 0x33, 0x5e, 0xff, 0x58, 0xee, 0xff, 0x1b, 0x50, 0xff, 0xff, 0x84, 0xdc, 0x07, 0x12, 0x59, 0x62, 0x58, 0x2a, 0x4b, 0x23, 0xff, 0x49, 0xe0, 0x47, 0x14, 0x38, 0x1c, + 0x91, 0x65, 0xac, 0x7d, 0xe4, 0x5d, 0xd6, 0x47, 0x64, 0x72, 0x33, 0x95, 0xc9, 0x9a, 0x6c, 0xf8, 0x21, 0x15, 0x7e, 0x74, 0x1c, 0xf8, 0x31, 0x05, 0x2e, 0x5d, 0xce, 0xfa, 0xc8, 0x5e, 0x87, 0xc2, 0xff, 0xc9, 0x49, 0xc7, 0x0f, 0xc1, 0x47, 0x7b, 0xb6, 0x36, 0x1c, 0xe3, 0xb7, 0x0d, 0x42, 0xbe, 0x08, 0x32, 0xdc, 0xb8, 0x31, 0x7e, 0x4b, 0xb2, 0x63, 0xfc, 0xb6, 0x34, 0x95, 0x92, 0xeb, 0xb9, 0x33, 0xc4, 0xf8, 0x8d, 0x8e, 0x17, 0xe2, 0x97, 0x79, 0x45, 0x63, 0x35, 0x15, 0xc4, 0x1a, 0xba, 0x52, 0xf1, 0xc9, 0x55, 0xde, 0xe4, 0xf4, 0xc5, 0xcb, 0x17, 0x4f, 0x4f, 0xa6, 0x06, 0x6e, 0x5d, 0xb2, 0xf6, 0xe1, 0xd6, 0x0a, 0x8d, 0xdb, 0x6e, 0x2b, 0x6d, 0xec, 0x6b, 0xad, 0xe9, 0xaa, 0x76, 0x27, 0xa7, 0x2f, 0x59, 0xb1, 0x64, 0x7a, 0xb2, 0x7a, 0xf1, 0xce, 0x05, 0x2b, 0xef, 0x69, 0x66, 0xa7, + 0x1b, 0xf5, 0x4e, 0xaf, 0xd3, 0x11, 0x49, 0xf9, 0xa3, 0x35, 0x61, 0x7f, 0xa0, 0xac, 0x6d, 0x5e, 0xfb, 0xd4, 0x4b, 0x17, 0xd4, 0x4c, 0xa1, 0x36, 0x3f, 0x3e, 0x4f, 0xa2, 0xb1, 0xb8, 0xb4, 0x2e, 0x5a, 0x18, 0x88, 0xa7, 0x17, 0x4d, 0x68, 0x59, 0x37, 0xbb, 0x3a, 0xdd, 0xa8, 0xf2, 0xe4, 0x29, 0xb5, 0xed, 0x4f, 0xab, 0x3c, 0xd9, 0x97, 0xcd, 0x13, 0xf8, 0xba, 0x33, 0xc3, 0xc3, 0x06, 0xc1, 0x2f, 0xc3, 0xa3, 0xd8, 0xee, 0x14, 0x47, 0x3a, 0xba, 0x76, 0x31, 0x8d, 0x6b, 0xbf, 0x58, 0xc9, 0x69, 0x4b, 0xca, 0x15, 0x4e, 0x2b, 0xef, 0xf3, 0x7b, 0xb3, 0xcb, 0x55, 0xeb, 0x8b, 0x92, 0x72, 0x69, 0xad, 0x88, 0xdf, 0xd2, 0x9d, 0xec, 0x76, 0xc4, 0xef, 0x73, 0xe0, 0x14, 0x59, 0x32, 0x9b, 0xa1, 0x56, 0xf4, 0xa1, 0xe9, 0x09, 0x1f, 0xf7, 0xb0, 0x34, 0x10, 0x8f, 0x0a, 0x60, 0x54, 0x5b, 0xe6, + 0x1a, 0xa0, 0xd1, 0xf2, 0x5a, 0x0d, 0x0e, 0xc4, 0x23, 0x68, 0x79, 0xb4, 0x21, 0x93, 0x6f, 0x54, 0xd6, 0x77, 0x63, 0x2b, 0x7c, 0x41, 0x89, 0xa3, 0x93, 0x77, 0x00, 0x54, 0x44, 0x4c, 0xe7, 0xd4, 0x37, 0x75, 0x5a, 0x5e, 0xb7, 0x5e, 0x0f, 0x75, 0x82, 0x56, 0x97, 0x5d, 0x02, 0x18, 0xf7, 0xfd, 0x12, 0x62, 0x3a, 0x77, 0xd6, 0xf7, 0x65, 0xdb, 0x0b, 0x39, 0x7c, 0x4e, 0xee, 0xa9, 0xd2, 0xc4, 0xf1, 0x5e, 0x87, 0x38, 0x22, 0x2e, 0x33, 0x34, 0x6e, 0x31, 0x39, 0x47, 0x4d, 0xfd, 0x69, 0xdc, 0x37, 0xe7, 0x80, 0x73, 0x26, 0x4d, 0xe8, 0x68, 0x6b, 0xac, 0x0f, 0x45, 0x4a, 0xb0, 0x3c, 0x1b, 0x74, 0xbe, 0xb1, 0x0f, 0x9c, 0x68, 0x3c, 0x81, 0x71, 0xc6, 0x9e, 0x98, 0x3f, 0x52, 0x45, 0x76, 0x7b, 0xbe, 0x44, 0x3f, 0x51, 0x3c, 0x31, 0x7a, 0xe6, 0xa1, 0x98, 0x18, 0x63, 0x00, 0x27, 0xf2, 0x0e, + 0xab, 0x34, 0x9a, 0x33, 0x0c, 0xcc, 0xa9, 0xa3, 0x87, 0xf2, 0xa2, 0x2e, 0xc5, 0x0e, 0x9b, 0x79, 0x98, 0xf8, 0x0d, 0x5d, 0x2e, 0xfb, 0x0d, 0x5d, 0x92, 0xeb, 0x37, 0x84, 0x70, 0x62, 0x68, 0xed, 0x7d, 0x0a, 0xed, 0xf0, 0xa2, 0xe0, 0x0a, 0x89, 0xc8, 0x73, 0x15, 0x95, 0xe7, 0x38, 0x5a, 0x0f, 0x4e, 0x21, 0x78, 0x3b, 0xbe, 0xeb, 0xc3, 0x5b, 0x7e, 0x4b, 0x0c, 0xb2, 0x7c, 0x7b, 0x91, 0x9f, 0xe3, 0xa0, 0x68, 0x96, 0x87, 0x58, 0x1e, 0x8c, 0x8e, 0x32, 0x37, 0x09, 0x92, 0xc1, 0xf1, 0xdc, 0x26, 0x20, 0xb2, 0x90, 0x15, 0xe1, 0x90, 0x80, 0x86, 0x04, 0x31, 0x1a, 0x94, 0xa7, 0xe6, 0x45, 0x4a, 0x9f, 0xc6, 0x73, 0xd0, 0xf0, 0x9c, 0x0c, 0x99, 0xf5, 0x59, 0xd8, 0xb8, 0xeb, 0x16, 0x29, 0x21, 0x98, 0xf5, 0x21, 0x8b, 0xa5, 0xd4, 0x1b, 0xb6, 0x27, 0x70, 0x64, 0x88, 0x88, 0x09, 0x8e, 0xeb, + 0x1d, 0x86, 0x43, 0x69, 0xe1, 0xf8, 0x5a, 0x6d, 0xb0, 0xc1, 0x86, 0x4f, 0x9b, 0x49, 0xba, 0x63, 0xe5, 0x78, 0xc0, 0x59, 0x0c, 0x99, 0x53, 0x65, 0xe7, 0xb5, 0x14, 0x8c, 0x69, 0x33, 0x16, 0x5f, 0xb8, 0x62, 0xb3, 0xc5, 0xf5, 0x8d, 0x72, 0xce, 0x65, 0x1d, 0x80, 0x01, 0xe9, 0x95, 0x96, 0x0a, 0x5f, 0xac, 0xd0, 0xae, 0xd1, 0xe9, 0x85, 0xe6, 0x9e, 0x48, 0x52, 0x6c, 0x1e, 0xc3, 0x7a, 0xec, 0x2a, 0xcb, 0x75, 0x17, 0x30, 0xcb, 0x1c, 0x8e, 0x5e, 0x17, 0xac, 0xb4, 0xd8, 0xa4, 0xc3, 0x7b, 0x86, 0xbf, 0xd9, 0xda, 0x62, 0xb2, 0x98, 0xb4, 0xba, 0x25, 0x98, 0xbf, 0x65, 0x23, 0xef, 0x32, 0xff, 0x49, 0xf6, 0x4c, 0x57, 0xc8, 0x7b, 0xa6, 0xe7, 0x48, 0xdf, 0x94, 0xa1, 0xf5, 0xf8, 0x24, 0x8f, 0xf3, 0x5b, 0x5d, 0x41, 0xe7, 0x0c, 0x59, 0xe6, 0xcb, 0xa4, 0x0f, 0x99, 0xd7, 0x32, 0x70, 0xfe, + 0xd7, 0x2a, 0xfc, 0x20, 0x23, 0x65, 0xe0, 0x82, 0xa3, 0x86, 0xc6, 0x88, 0x7e, 0x70, 0xa4, 0x8e, 0xfd, 0x06, 0xea, 0xa7, 0x72, 0xd4, 0xdb, 0x93, 0xd2, 0x9d, 0x02, 0xea, 0x85, 0xa4, 0x1d, 0x47, 0xcc, 0x8f, 0x05, 0x19, 0x9e, 0xc3, 0xa1, 0xfd, 0x59, 0xb8, 0x09, 0xb0, 0x0c, 0xbb, 0x09, 0x47, 0xe2, 0xb8, 0x50, 0x84, 0xd8, 0xad, 0x86, 0x9e, 0xe2, 0x73, 0x1c, 0xbf, 0x08, 0xb1, 0x79, 0x1d, 0xdf, 0x63, 0x8d, 0xc4, 0xc3, 0x21, 0x4b, 0xa9, 0x05, 0x73, 0xd6, 0x96, 0xcb, 0x3a, 0xe2, 0xdd, 0x09, 0x1d, 0x59, 0xec, 0xa3, 0x32, 0x41, 0x0f, 0xed, 0x6d, 0xa1, 0x17, 0x33, 0xfc, 0xd2, 0xac, 0xc3, 0xe7, 0xf4, 0xc3, 0x1f, 0x67, 0x31, 0xad, 0xea, 0xda, 0xa9, 0xfd, 0x97, 0xf8, 0x1e, 0xc6, 0x27, 0xf6, 0xf0, 0xfd, 0xa3, 0x47, 0xa1, 0x9d, 0x99, 0x92, 0xe1, 0xd4, 0x83, 0x2e, 0xdf, 0xe7, 0x3f, + 0xde, 0xa5, 0x30, 0xab, 0xbc, 0xaa, 0x6b, 0xb2, 0xf4, 0x96, 0xcb, 0xc7, 0x5a, 0xa1, 0x8d, 0xf2, 0x8d, 0xdb, 0x42, 0xe6, 0xbe, 0xab, 0xc9, 0x9e, 0xff, 0x24, 0xb3, 0x1c, 0xc8, 0xfc, 0xe4, 0x36, 0x93, 0x75, 0xec, 0x6a, 0x7a, 0x06, 0x2b, 0xc3, 0x63, 0x23, 0xef, 0xf2, 0x01, 0xb2, 0xee, 0x5d, 0x2d, 0x51, 0x3f, 0xa6, 0xa5, 0x0a, 0x3e, 0x9f, 0x24, 0xeb, 0x18, 0xc5, 0x3f, 0x9a, 0x29, 0x87, 0x17, 0xc9, 0xd9, 0x01, 0x2d, 0xff, 0xe9, 0x2c, 0xb8, 0x8d, 0xe8, 0x49, 0x14, 0xff, 0xb1, 0xac, 0xf2, 0x0b, 0x89, 0xad, 0x14, 0x2d, 0xff, 0x78, 0x56, 0xf9, 0xba, 0xac, 0xf2, 0x1f, 0xcf, 0x2a, 0x27, 0xbb, 0xde, 0x63, 0x4a, 0x39, 0xd2, 0x0f, 0x79, 0x17, 0x19, 0x0f, 0xb8, 0x9c, 0x18, 0x6a, 0xd7, 0x52, 0x6a, 0xeb, 0xc6, 0x9c, 0x9b, 0x6b, 0xeb, 0xa6, 0xe2, 0xb6, 0x51, 0xdc, 0x2c, 0x9c, 0xec, + 0x18, 0xc6, 0x98, 0xae, 0x6e, 0x62, 0xdf, 0x44, 0xca, 0x43, 0xed, 0xde, 0x45, 0xed, 0x9b, 0x70, 0xb9, 0x39, 0xf6, 0x4d, 0x0c, 0xe1, 0xdd, 0x5b, 0xa8, 0x6e, 0x1b, 0x48, 0x83, 0xfb, 0xd3, 0x3a, 0xb4, 0x8b, 0xd4, 0xc7, 0x91, 0xe4, 0x29, 0x17, 0xa2, 0xb5, 0x40, 0x87, 0xe4, 0x5a, 0x07, 0x86, 0x0c, 0x38, 0x75, 0x23, 0x23, 0xf2, 0xcc, 0x72, 0x63, 0xc6, 0xb4, 0x46, 0x8b, 0xb6, 0x7c, 0xfa, 0x7e, 0x6a, 0x18, 0x2c, 0x8a, 0xec, 0x22, 0x8d, 0x12, 0x28, 0xaf, 0xf1, 0x0b, 0xbf, 0x46, 0xe6, 0x65, 0x51, 0xd4, 0x2c, 0x42, 0xda, 0xd6, 0x3a, 0x0d, 0x12, 0xe9, 0x20, 0x04, 0xc4, 0x93, 0xa9, 0xb9, 0xa6, 0xba, 0xa2, 0xbc, 0x24, 0x80, 0x13, 0x67, 0xe0, 0xb4, 0x0e, 0x8a, 0x9d, 0x8e, 0x69, 0x6c, 0x3b, 0x1d, 0xaa, 0x3d, 0xe7, 0x88, 0x34, 0x3d, 0x17, 0xcc, 0x1d, 0xb9, 0x96, 0x14, 0xfb, 0x6e, + 0x9e, 0xe1, 0xdd, 0x4b, 0x87, 0x4e, 0xef, 0xdd, 0x2b, 0x31, 0xb9, 0x03, 0xf4, 0xf2, 0x1d, 0x68, 0x08, 0xdf, 0x97, 0xc0, 0x43, 0x98, 0x39, 0x79, 0x88, 0x7b, 0x98, 0x58, 0x82, 0xd4, 0xe0, 0xf7, 0x6a, 0xa8, 0x29, 0xcf, 0x1f, 0xa9, 0xe6, 0x1d, 0xca, 0x08, 0x76, 0x79, 0xd5, 0xae, 0xab, 0xb2, 0x65, 0x5e, 0xea, 0x25, 0x73, 0x26, 0xe5, 0x2f, 0x96, 0xc5, 0xcb, 0xd2, 0xba, 0x12, 0xc8, 0x69, 0x9d, 0x10, 0x07, 0x13, 0x53, 0xa2, 0x69, 0x6a, 0xb4, 0x9a, 0x4d, 0x3a, 0x72, 0x93, 0xcf, 0xb3, 0x60, 0xb9, 0x88, 0xf6, 0xcd, 0xda, 0x7e, 0xba, 0x4d, 0xe6, 0x79, 0xb8, 0x48, 0x80, 0xf2, 0xa5, 0x5b, 0xfc, 0x0c, 0x88, 0xe4, 0x0a, 0x8e, 0xe7, 0x85, 0x45, 0xd8, 0x93, 0x1f, 0xe7, 0x1c, 0x29, 0x20, 0xe6, 0x4d, 0xb6, 0x78, 0x88, 0x18, 0x03, 0x90, 0x0b, 0xf4, 0x2f, 0xc1, 0x20, 0xee, 0xad, 0xb3, + 0x33, 0x44, 0x31, 0x6a, 0x3b, 0x5b, 0xf3, 0x55, 0x59, 0x38, 0xac, 0x8c, 0x79, 0xa4, 0x61, 0x67, 0x64, 0xf6, 0xe7, 0x19, 0x99, 0x85, 0x23, 0x19, 0xb8, 0xf0, 0x0e, 0x91, 0x85, 0x1d, 0x54, 0xc6, 0xb9, 0xe7, 0x55, 0xf8, 0xef, 0x88, 0x2c, 0xef, 0xa0, 0x32, 0x2e, 0xc3, 0xd1, 0x58, 0x17, 0x0f, 0x50, 0x38, 0x95, 0x71, 0xee, 0xe7, 0x0a, 0xbe, 0xf8, 0x75, 0xb2, 0xaf, 0xa4, 0xf8, 0x47, 0x33, 0xe5, 0x88, 0x57, 0x10, 0x3b, 0x43, 0x5a, 0xfe, 0xd3, 0x59, 0xf0, 0x1b, 0x88, 0x5d, 0x22, 0xc5, 0x7f, 0x2c, 0xab, 0xfc, 0x5b, 0x88, 0x8c, 0xd3, 0xf2, 0x8f, 0x67, 0x95, 0xbf, 0x2d, 0xab, 0xfc, 0xc7, 0xb3, 0xca, 0xc9, 0xae, 0xf7, 0x98, 0x52, 0x8e, 0xf4, 0x43, 0x71, 0x0f, 0x6d, 0x17, 0x91, 0xc9, 0xe3, 0xdc, 0xcf, 0x3e, 0x2d, 0x86, 0xb8, 0x7d, 0x3f, 0x57, 0x64, 0x32, 0x5b, 0xc6, 0x11, 0x6e, + 0x1b, 0xc5, 0x25, 0xbe, 0x6b, 0x3f, 0x1b, 0x4b, 0xc6, 0xc5, 0xa7, 0x89, 0x8c, 0xd3, 0xf2, 0x8e, 0xf0, 0x80, 0xca, 0x78, 0xa6, 0xbc, 0x6c, 0x19, 0x17, 0x97, 0x12, 0x19, 0x5f, 0x04, 0x9e, 0x48, 0xeb, 0xbb, 0xa1, 0x60, 0xea, 0x88, 0x33, 0x19, 0x21, 0x6f, 0x32, 0x43, 0x24, 0x9d, 0xd0, 0x08, 0x86, 0x2c, 0xaa, 0xb8, 0x5a, 0x33, 0xe2, 0x6a, 0xd0, 0x22, 0x25, 0xd0, 0xd4, 0x5f, 0x00, 0x4d, 0xa6, 0x55, 0xdd, 0x3a, 0x88, 0x05, 0x5d, 0xaf, 0x61, 0x64, 0x49, 0x6f, 0xfb, 0x12, 0xef, 0x12, 0x61, 0xc7, 0x05, 0x68, 0x16, 0xe9, 0xa1, 0x2c, 0xee, 0x09, 0xb4, 0xf4, 0x2f, 0x3c, 0xb7, 0x7f, 0xc1, 0xbc, 0x73, 0x66, 0xcf, 0xea, 0x9d, 0x3a, 0x65, 0x62, 0x67, 0x43, 0xdd, 0xd8, 0x82, 0x6f, 0xfb, 0xb2, 0x82, 0x7f, 0xd6, 0xb1, 0xae, 0x0e, 0x79, 0x61, 0x65, 0xde, 0x9c, 0xf0, 0x1f, 0x77, 0x9c, + 0xbe, 0xe5, 0x16, 0xc9, 0xd9, 0x5c, 0x49, 0x44, 0x40, 0x6b, 0x40, 0x22, 0xb0, 0x03, 0x89, 0x80, 0xd4, 0x30, 0xae, 0x50, 0xc0, 0x9f, 0x26, 0x59, 0x97, 0x75, 0x90, 0x39, 0x7a, 0xc7, 0x97, 0x98, 0x2c, 0xc6, 0x97, 0x1e, 0x58, 0xad, 0xca, 0x10, 0x9d, 0x43, 0x50, 0xff, 0xb5, 0x81, 0x69, 0x60, 0x6f, 0x5a, 0xdf, 0x00, 0x39, 0x43, 0xc2, 0xc9, 0x64, 0x26, 0x91, 0x84, 0x11, 0xea, 0x0d, 0xfa, 0x4d, 0x66, 0x75, 0x72, 0x20, 0xbe, 0x83, 0x86, 0x7e, 0x13, 0x34, 0x18, 0xb0, 0x9f, 0x2c, 0x9e, 0x48, 0xb4, 0xea, 0xf5, 0x7d, 0xe5, 0x19, 0xb1, 0xc9, 0x64, 0x82, 0x5f, 0x11, 0x16, 0xe1, 0x49, 0x9a, 0x4c, 0x27, 0xde, 0x69, 0x53, 0xa7, 0x4c, 0x46, 0xaa, 0x71, 0x7b, 0x5b, 0x4b, 0xaa, 0xa6, 0x32, 0x19, 0x52, 0x26, 0x96, 0x82, 0xb3, 0x4d, 0x2c, 0x5f, 0xbc, 0x03, 0xc4, 0xa5, 0x7f, 0x02, 0xc3, + 0xc7, 0x9d, 0x8c, 0xce, 0xce, 0x5e, 0x55, 0x8e, 0x0f, 0x2b, 0xf2, 0xca, 0xb0, 0x59, 0x72, 0x3c, 0x3d, 0x33, 0xdf, 0xc0, 0x11, 0x19, 0xbe, 0x15, 0xed, 0xcb, 0xe8, 0x39, 0xd6, 0x75, 0xf2, 0x7d, 0xef, 0xdd, 0x63, 0xc2, 0x8f, 0xe4, 0xc0, 0x0f, 0xa9, 0xf0, 0xa3, 0x39, 0xf0, 0xed, 0x2a, 0xfc, 0x44, 0x0e, 0xbc, 0x4b, 0x85, 0x3f, 0x36, 0x4e, 0x39, 0x8f, 0xe7, 0xc0, 0xdf, 0x50, 0xe1, 0xdf, 0x95, 0xe3, 0x03, 0x60, 0xf8, 0x7f, 0x11, 0xfa, 0xaf, 0x93, 0xef, 0x7b, 0x73, 0xca, 0x07, 0xa3, 0xca, 0x47, 0xfb, 0xca, 0xe7, 0xd9, 0x8f, 0x95, 0x72, 0xe0, 0xeb, 0x39, 0xf8, 0xa7, 0x55, 0xf8, 0x73, 0x59, 0xe5, 0x67, 0xd3, 0x73, 0x2c, 0x1b, 0x9f, 0xdc, 0x7d, 0x5d, 0x27, 0xdf, 0x7d, 0x65, 0xc3, 0xdf, 0x92, 0xe1, 0x68, 0xbe, 0x02, 0xcf, 0xd0, 0xf9, 0x0a, 0x3f, 0xcf, 0x9e, 0xaf, 0x54, 0xdc, + 0x17, 0xd4, 0x32, 0x16, 0x8c, 0x59, 0x86, 0xc8, 0xf5, 0x83, 0x7b, 0x65, 0xda, 0xbf, 0x92, 0xa1, 0x9d, 0x94, 0xf9, 0xe0, 0xa7, 0x90, 0xe6, 0x66, 0xf8, 0x14, 0x2a, 0x65, 0x4a, 0x3f, 0xcc, 0xc1, 0x39, 0x0e, 0xee, 0x1e, 0x35, 0xa7, 0x6e, 0x95, 0x0e, 0x66, 0xf3, 0x40, 0x70, 0xc8, 0xf1, 0x79, 0x68, 0xbd, 0xaf, 0x2a, 0x70, 0x7e, 0x47, 0x0e, 0x3d, 0x8f, 0xa9, 0xf0, 0xdd, 0xa4, 0x2d, 0x10, 0x95, 0xb3, 0x9b, 0xdf, 0xcc, 0x7e, 0x0c, 0x2a, 0xc0, 0xfe, 0x47, 0x03, 0x46, 0x86, 0x63, 0x95, 0xe8, 0xd9, 0xd8, 0x68, 0x8f, 0xcd, 0x36, 0x23, 0xa3, 0x59, 0x17, 0xf3, 0x36, 0xc5, 0x25, 0x14, 0x0b, 0x50, 0x6f, 0x1a, 0xf9, 0x4c, 0x64, 0x5d, 0x2e, 0x26, 0x36, 0xc3, 0x61, 0x44, 0x46, 0x23, 0x0e, 0x8d, 0x8d, 0x0c, 0x32, 0xb8, 0xe4, 0x84, 0x2a, 0x84, 0xfd, 0x48, 0xa2, 0x11, 0x7a, 0x42, 0x95, + 0x89, 0x29, 0xab, 0x86, 0x94, 0xb5, 0xa4, 0x88, 0xb0, 0xd6, 0x37, 0x10, 0xe3, 0x3a, 0xd5, 0x4b, 0x81, 0xdf, 0x2c, 0x7b, 0x25, 0x0c, 0x96, 0x1a, 0x4c, 0xc1, 0x69, 0xb3, 0x17, 0xd5, 0x29, 0x2e, 0x36, 0xd7, 0x34, 0x9d, 0x52, 0xdd, 0x14, 0xa4, 0x87, 0x64, 0xa7, 0x84, 0x8c, 0xd9, 0x04, 0x73, 0xfd, 0xf0, 0xa5, 0x17, 0x5c, 0x3a, 0xb5, 0x9d, 0x79, 0xeb, 0xf3, 0x43, 0xd4, 0x4b, 0x41, 0x1d, 0x33, 0x87, 0x15, 0x7e, 0x31, 0xac, 0xcc, 0xc7, 0xcd, 0xb9, 0xb2, 0x03, 0x47, 0x64, 0x38, 0xce, 0x3d, 0xfd, 0x19, 0xb1, 0x89, 0xb8, 0x81, 0xc6, 0x45, 0xbf, 0x88, 0xea, 0x14, 0x36, 0x34, 0x56, 0x3f, 0x23, 0xfd, 0x74, 0x03, 0xdd, 0x27, 0x5d, 0x4c, 0xe7, 0x49, 0xab, 0x74, 0x27, 0xfb, 0x29, 0xe2, 0x7b, 0x0c, 0x5c, 0x21, 0xdb, 0xa2, 0x16, 0xa3, 0x39, 0x4e, 0x47, 0x1c, 0x73, 0x7c, 0xea, 0x0f, + 0x56, 0xb5, 0x45, 0x2d, 0x06, 0x38, 0x84, 0x33, 0xe0, 0x86, 0xf0, 0xe9, 0x21, 0xd9, 0xfc, 0x0c, 0x66, 0x32, 0x97, 0x0f, 0x41, 0x12, 0x29, 0x4b, 0xc6, 0x00, 0xa8, 0x17, 0x39, 0x86, 0xcd, 0xc6, 0x54, 0xb2, 0x97, 0x0f, 0x61, 0xbb, 0x02, 0x5d, 0x39, 0x39, 0x04, 0x24, 0x56, 0xbd, 0x91, 0x31, 0x0f, 0xfc, 0xc8, 0xa9, 0xa0, 0x8d, 0x06, 0xdc, 0x27, 0x13, 0x1f, 0x73, 0xc7, 0xb1, 0xfc, 0x93, 0x81, 0xf7, 0xa4, 0xc1, 0xaf, 0xb9, 0x8a, 0xf8, 0xf7, 0x8d, 0x36, 0x8d, 0xc6, 0x6e, 0xfa, 0x48, 0x28, 0x72, 0xdc, 0xbe, 0x3f, 0xff, 0xac, 0xcf, 0x69, 0x19, 0xbe, 0x39, 0x36, 0x23, 0x1a, 0xed, 0x29, 0x65, 0x86, 0x2c, 0x4e, 0xc2, 0x0f, 0x3f, 0x62, 0x82, 0x89, 0xf0, 0x6f, 0x27, 0x9d, 0x93, 0xe4, 0x7c, 0xba, 0xf9, 0xf0, 0x23, 0x39, 0xf0, 0x43, 0x2a, 0xfc, 0x68, 0x0e, 0x7c, 0xbb, 0x0a, 0x3f, + 0x91, 0x03, 0x7f, 0x4a, 0x85, 0x3f, 0xad, 0xc0, 0xa5, 0x7d, 0x82, 0x89, 0xfd, 0x4c, 0x81, 0xc3, 0xd7, 0xe1, 0xb5, 0x59, 0xf8, 0x6f, 0xa8, 0xf8, 0xdf, 0x85, 0x3b, 0xd5, 0x5c, 0xe2, 0x2f, 0xa1, 0xfe, 0x29, 0xc4, 0x39, 0x3b, 0x0a, 0xed, 0xa6, 0x4c, 0xce, 0x0e, 0x5f, 0x26, 0x67, 0x47, 0x59, 0x26, 0x67, 0x07, 0x8b, 0x83, 0x64, 0x67, 0x92, 0x76, 0xd8, 0x30, 0xe7, 0xea, 0xd1, 0xce, 0x93, 0x7d, 0xe9, 0x09, 0x97, 0x4f, 0xf3, 0x21, 0xaf, 0xd3, 0xbc, 0xaa, 0xf3, 0x3a, 0x8e, 0xc2, 0x15, 0xd2, 0x91, 0x27, 0x5c, 0x0e, 0xcd, 0x1f, 0x78, 0x83, 0xee, 0x59, 0x6b, 0xd1, 0x71, 0xe9, 0x88, 0xc7, 0x04, 0xb7, 0x9b, 0x0b, 0x0d, 0xd2, 0x23, 0x26, 0x0f, 0xa3, 0x87, 0xef, 0x98, 0x4c, 0xd2, 0x4d, 0x66, 0x7f, 0x01, 0x4c, 0x05, 0xa4, 0x42, 0x65, 0x5c, 0xbd, 0x44, 0xda, 0xb3, 0x2b, 0x27, 0x96, + 0x21, 0x85, 0x6f, 0x57, 0xe1, 0x27, 0xd6, 0xa8, 0xe3, 0x8a, 0xab, 0x42, 0x74, 0x57, 0x83, 0xeb, 0xe8, 0xc0, 0x31, 0x56, 0x95, 0x96, 0x78, 0x38, 0x91, 0x57, 0xac, 0xa8, 0xb2, 0x7e, 0x33, 0xea, 0xe8, 0x2a, 0xa2, 0x63, 0x45, 0x43, 0x14, 0xa6, 0x31, 0x4e, 0xc0, 0xb2, 0xf3, 0x4c, 0xe5, 0x23, 0x8e, 0xca, 0x33, 0x55, 0x0d, 0xaa, 0x11, 0x6b, 0x42, 0x68, 0x88, 0x69, 0xc7, 0x3b, 0x97, 0x1a, 0x9f, 0x61, 0x5a, 0xc8, 0x55, 0xe5, 0x0f, 0xb5, 0x55, 0x67, 0xe6, 0x20, 0x04, 0x79, 0x03, 0xef, 0x0c, 0x2c, 0x95, 0xae, 0x42, 0xbc, 0xb3, 0x48, 0x87, 0x39, 0x48, 0xc6, 0xda, 0x8d, 0xf2, 0xba, 0x78, 0x0b, 0xe1, 0x69, 0x3e, 0xfc, 0x48, 0x0e, 0xfc, 0x90, 0x0a, 0x3f, 0x9a, 0x03, 0xdf, 0xae, 0xc2, 0x4f, 0x60, 0x38, 0xea, 0x03, 0x02, 0x47, 0x7d, 0xe0, 0x04, 0x25, 0xe9, 0x62, 0xb3, 0xc0, 0x60, + 0x1f, 0x09, 0x16, 0x89, 0x2a, 0xf5, 0x49, 0xf3, 0x13, 0xff, 0x19, 0x27, 0x70, 0x94, 0x84, 0x38, 0xc1, 0x99, 0xd0, 0x92, 0x54, 0x2f, 0xb6, 0xec, 0x1c, 0x17, 0x88, 0x17, 0x29, 0x0e, 0x0e, 0xff, 0x48, 0x5b, 0xe4, 0xbf, 0xe5, 0x3b, 0xd2, 0xd2, 0x07, 0x3d, 0x85, 0xf8, 0x3a, 0xc2, 0xcc, 0x8b, 0x05, 0xda, 0x4f, 0xc4, 0x22, 0x1b, 0xfb, 0xf1, 0xa7, 0xaf, 0x3a, 0xdd, 0xf0, 0x00, 0xfb, 0x6d, 0xb7, 0x79, 0xf8, 0x1f, 0x5c, 0x95, 0x2e, 0x77, 0xa5, 0x8b, 0x59, 0x2e, 0xcb, 0x57, 0x1e, 0xad, 0xb0, 0x5a, 0xa5, 0x75, 0x1f, 0xa6, 0x49, 0x85, 0xbf, 0x0e, 0x1f, 0xc8, 0xc2, 0xdf, 0xab, 0xb6, 0xe1, 0x78, 0x4e, 0xdb, 0x4e, 0xab, 0xf8, 0xcf, 0x81, 0x5b, 0xb3, 0xe0, 0x5d, 0x2a, 0xfe, 0x63, 0x39, 0xf8, 0x4f, 0xa9, 0xf0, 0xa7, 0x73, 0xe0, 0x6f, 0xa8, 0xf0, 0xef, 0x7e, 0xa1, 0x72, 0xf2, 0x78, + 0x3a, 0x46, 0xdf, 0x7c, 0x5f, 0x86, 0x37, 0x21, 0x39, 0x98, 0x49, 0x74, 0x87, 0xdd, 0x90, 0xdc, 0x0b, 0xcc, 0xa0, 0xf2, 0xd1, 0x88, 0xe0, 0x33, 0x08, 0x1f, 0x76, 0xd3, 0xf3, 0xff, 0x9e, 0x6c, 0xf8, 0x53, 0x2a, 0xfc, 0xe9, 0x9e, 0xb1, 0xf1, 0x8f, 0x2a, 0x70, 0x34, 0x7f, 0xcf, 0x20, 0x7c, 0xdb, 0x4d, 0xe7, 0xef, 0x7a, 0x05, 0xfe, 0x61, 0x36, 0x9c, 0xff, 0x75, 0x3d, 0x95, 0xbf, 0x46, 0x34, 0xaf, 0x3f, 0x84, 0xfa, 0xbe, 0x44, 0xf1, 0xee, 0xd3, 0x39, 0x20, 0x2b, 0x68, 0x64, 0xe1, 0x53, 0x7e, 0x64, 0x24, 0x2f, 0x00, 0xe4, 0xbc, 0x6d, 0xc4, 0x4f, 0x80, 0xe4, 0x0e, 0xc9, 0x97, 0xbd, 0x98, 0x6a, 0x8f, 0x38, 0x06, 0x6a, 0x8e, 0xf4, 0xe9, 0x1c, 0x16, 0x07, 0x9a, 0xda, 0xc3, 0x78, 0x66, 0xe7, 0xc7, 0x70, 0x51, 0x56, 0x32, 0x8f, 0xe0, 0x8b, 0x54, 0x18, 0x64, 0x1f, 0x52, 0x04, + 0x4d, 0xf4, 0xea, 0x4d, 0x15, 0x48, 0xd0, 0x86, 0xab, 0x4d, 0x46, 0xc6, 0x3d, 0xfc, 0x36, 0xf3, 0x91, 0xd9, 0x30, 0xfc, 0x3b, 0x66, 0x7f, 0x34, 0x47, 0xac, 0x66, 0x9b, 0x62, 0x16, 0xe6, 0x5f, 0x6e, 0x9f, 0xed, 0x28, 0x35, 0x0d, 0xb7, 0xe3, 0xbd, 0x5a, 0x29, 0x7b, 0x23, 0xfc, 0x0d, 0x7f, 0x1f, 0xd0, 0x83, 0x1a, 0x7c, 0x6b, 0xa2, 0x47, 0x44, 0x7b, 0xc8, 0xed, 0xe5, 0x78, 0xf9, 0x34, 0xce, 0x27, 0xce, 0xe0, 0x64, 0x25, 0x5a, 0xc5, 0xf6, 0x84, 0x42, 0xce, 0x58, 0x24, 0x1c, 0x2d, 0x20, 0x91, 0x72, 0x65, 0x0b, 0x28, 0x44, 0x5f, 0x7d, 0x5e, 0x16, 0x8d, 0x68, 0x7e, 0x12, 0x0d, 0xb8, 0x28, 0x35, 0xa9, 0xd4, 0x12, 0xf2, 0x87, 0x52, 0x5e, 0xe9, 0xb5, 0xc5, 0x57, 0x57, 0x4c, 0x4f, 0xf9, 0x3a, 0xec, 0x76, 0x51, 0x6f, 0x8f, 0xc4, 0x92, 0x85, 0x93, 0xe7, 0xf8, 0xa7, 0x5e, 0xd2, + 0x1f, 0x8f, 0xfa, 0x63, 0x7e, 0x4f, 0x81, 0xc9, 0x2e, 0x72, 0xe7, 0x33, 0x46, 0x9b, 0xc7, 0xec, 0xf7, 0xb8, 0xbd, 0xb0, 0x61, 0x76, 0x51, 0x7d, 0x77, 0x52, 0x7a, 0xdc, 0x60, 0x0d, 0x58, 0xbc, 0x56, 0xed, 0xb4, 0xa9, 0x85, 0xcd, 0x95, 0x01, 0xc6, 0x53, 0x95, 0x34, 0x16, 0x18, 0xcd, 0x7a, 0x6a, 0xdf, 0x50, 0x3a, 0xe2, 0x86, 0xbf, 0x81, 0x29, 0xd2, 0xa6, 0xdb, 0xd2, 0xba, 0x4c, 0x9b, 0xa8, 0x29, 0x48, 0x48, 0x6d, 0x1a, 0x03, 0x04, 0x91, 0x11, 0x06, 0x00, 0x44, 0x4d, 0x82, 0xec, 0x52, 0xd4, 0x2d, 0xa4, 0x1f, 0x36, 0xa8, 0xce, 0x4e, 0x67, 0x45, 0x5d, 0x87, 0x3b, 0xf7, 0x8b, 0x14, 0xd8, 0x4f, 0xb2, 0x8e, 0x64, 0x98, 0xc5, 0xff, 0xa5, 0x98, 0xa5, 0x19, 0xcd, 0x2b, 0xbd, 0x35, 0xa8, 0xf2, 0xaa, 0x98, 0xf1, 0x12, 0x5e, 0x99, 0x64, 0x5e, 0xf5, 0xb1, 0x37, 0x32, 0xf5, 0xc2, + 0xb5, 0xc0, 0x00, 0xc2, 0x38, 0xe7, 0xa9, 0x11, 0x29, 0x2f, 0x06, 0x6c, 0x3b, 0xe3, 0x87, 0x2c, 0x60, 0xbb, 0x00, 0xcf, 0xb0, 0xfc, 0x5c, 0xe5, 0xd6, 0x7f, 0x80, 0x74, 0x3d, 0x69, 0x11, 0xea, 0x7a, 0x4b, 0xa4, 0x34, 0x12, 0x2e, 0xc1, 0xbe, 0x1f, 0xb6, 0x31, 0x03, 0x74, 0x53, 0xda, 0x49, 0xa4, 0x3e, 0x4b, 0xea, 0x45, 0x7b, 0x85, 0x37, 0xd5, 0x5e, 0x57, 0x11, 0xa8, 0x71, 0x17, 0xf5, 0xa4, 0xe2, 0x53, 0x52, 0x85, 0x81, 0x96, 0xd9, 0x35, 0x55, 0xed, 0x5e, 0x96, 0x2d, 0x30, 0x2d, 0xdc, 0xc5, 0x15, 0xb9, 0x2c, 0x33, 0x16, 0xcc, 0x9b, 0xe2, 0x2b, 0x5c, 0x5e, 0x1c, 0x0e, 0x4d, 0x5a, 0x91, 0x6e, 0x1e, 0x9c, 0x5e, 0x16, 0x29, 0x0a, 0x98, 0x0d, 0xcc, 0x1f, 0xa8, 0x4c, 0x56, 0xa1, 0x71, 0xfa, 0x0e, 0x7f, 0x2f, 0x28, 0x03, 0x6b, 0xd3, 0xba, 0x32, 0x08, 0x79, 0x17, 0xe4, 0x88, + 0xff, 0x73, 0x98, 0xb8, 0x86, 0x2b, 0x57, 0x7c, 0xf2, 0xdd, 0xde, 0x40, 0x37, 0xb1, 0x22, 0x57, 0x2e, 0xf8, 0x56, 0x9d, 0x29, 0x1a, 0xf6, 0x40, 0x36, 0x1a, 0x8e, 0x86, 0x1d, 0x0b, 0x85, 0x43, 0xa4, 0x5d, 0xea, 0x35, 0xa0, 0x90, 0x73, 0xf6, 0x9c, 0x13, 0x10, 0x00, 0x9e, 0x16, 0xf5, 0x7a, 0x9d, 0xcd, 0x1d, 0xf4, 0x74, 0xb4, 0xa6, 0x1b, 0xfd, 0x21, 0xb7, 0xd5, 0xa8, 0x31, 0x72, 0xa5, 0xce, 0x54, 0x7d, 0x83, 0xaf, 0x6a, 0x5e, 0x67, 0x24, 0x98, 0x5e, 0xd6, 0xde, 0x7a, 0x6e, 0x88, 0x8f, 0x6a, 0xb4, 0x7a, 0x93, 0x7e, 0x6e, 0xcf, 0xcc, 0x39, 0x68, 0x8b, 0x28, 0x88, 0x29, 0x6f, 0x89, 0x5d, 0x13, 0x9e, 0x3c, 0x98, 0x6e, 0x5a, 0x31, 0xad, 0x2c, 0x1a, 0xa1, 0x7d, 0x51, 0x83, 0xfa, 0xc2, 0x4d, 0xfa, 0x22, 0x84, 0x73, 0xe4, 0x96, 0x40, 0xc0, 0x19, 0x70, 0xec, 0x09, 0x2b, 0xce, + 0x29, 0xd9, 0x85, 0x56, 0x6d, 0x8e, 0x41, 0x9d, 0x21, 0x47, 0xbf, 0x3d, 0x3f, 0xa3, 0x3a, 0xae, 0x62, 0x7a, 0x8c, 0x46, 0x63, 0xc8, 0x18, 0x2a, 0x0d, 0x87, 0xc3, 0x25, 0xa2, 0x2e, 0x13, 0xef, 0xd6, 0x39, 0x6e, 0xb8, 0x5b, 0x78, 0x93, 0x3d, 0xe9, 0x4d, 0x75, 0xd4, 0xd7, 0x04, 0xaa, 0x1c, 0x37, 0xce, 0x2d, 0x14, 0xc5, 0x40, 0x5f, 0x23, 0xe9, 0x96, 0xe6, 0xd9, 0xa9, 0x78, 0x8b, 0x8b, 0xb7, 0x3b, 0x69, 0x87, 0x78, 0x99, 0xb9, 0xc3, 0x66, 0x4f, 0x75, 0xb8, 0x22, 0x3c, 0x69, 0xb0, 0x03, 0xf7, 0x8a, 0xd7, 0x01, 0x8d, 0x88, 0x56, 0xd4, 0x1f, 0x8c, 0x05, 0xf5, 0x87, 0x06, 0x24, 0xc0, 0x96, 0x13, 0x76, 0xd4, 0x19, 0x50, 0xee, 0x8c, 0xb0, 0x08, 0xa1, 0x06, 0x0a, 0x2c, 0x14, 0x06, 0x94, 0xf8, 0xda, 0x63, 0x75, 0x47, 0x0c, 0xb0, 0x02, 0x23, 0xb0, 0xcc, 0xd0, 0x98, 0xe8, 0xd9, + 0x3d, 0xe2, 0xd6, 0x6a, 0xd1, 0x8e, 0x2d, 0xa1, 0x8d, 0x63, 0x4f, 0x3c, 0x6b, 0x81, 0x51, 0x4f, 0xe2, 0x74, 0x6a, 0x33, 0x71, 0x3a, 0xf1, 0x2d, 0xcc, 0x58, 0xf1, 0xaa, 0x69, 0x38, 0x3f, 0xf8, 0x15, 0x83, 0x27, 0xe6, 0xab, 0x69, 0x42, 0xfb, 0x97, 0xc9, 0x4d, 0xfe, 0x78, 0xc0, 0x6b, 0xb0, 0x8a, 0xa5, 0xce, 0xfa, 0xa6, 0x66, 0x5f, 0xfd, 0xd2, 0x29, 0xa5, 0xa1, 0xce, 0xc5, 0x2d, 0x35, 0x73, 0xda, 0x4a, 0x0c, 0x36, 0xee, 0xdf, 0x1c, 0x31, 0xbf, 0xb5, 0x31, 0x35, 0x67, 0x5a, 0xa7, 0xd1, 0x62, 0x34, 0xd2, 0x1e, 0x9a, 0xb2, 0x76, 0x72, 0xed, 0x92, 0x29, 0x65, 0x81, 0xb6, 0x05, 0x4d, 0x01, 0x07, 0xee, 0xa3, 0xaa, 0x11, 0x0b, 0x63, 0x81, 0x71, 0xd2, 0xee, 0xc9, 0xe9, 0x09, 0xb4, 0xe1, 0x4a, 0x53, 0xd4, 0xb9, 0x92, 0x01, 0x1c, 0xc3, 0x2d, 0x15, 0x89, 0xeb, 0x88, 0x06, 0x92, + 0xdc, 0xcc, 0xe3, 0x36, 0x22, 0xf2, 0x25, 0x1a, 0xd1, 0xa8, 0x55, 0x1b, 0x61, 0xd1, 0x8c, 0xd5, 0x88, 0x85, 0x8e, 0xa8, 0xdf, 0xda, 0x90, 0x9a, 0x4b, 0xda, 0x60, 0x48, 0xf9, 0x4a, 0x6c, 0xa8, 0x0d, 0x6b, 0x26, 0xd7, 0x29, 0x6d, 0x70, 0x62, 0x59, 0x0a, 0xb3, 0xfb, 0xe0, 0x15, 0xc2, 0x95, 0xc0, 0x03, 0xaa, 0xd3, 0x15, 0x1e, 0x44, 0x2c, 0x0f, 0xbb, 0x8c, 0x68, 0x8c, 0x4d, 0xc7, 0x77, 0x34, 0x17, 0x66, 0x1a, 0xc1, 0xc0, 0x7e, 0xec, 0x85, 0x3f, 0xc0, 0xf4, 0xe0, 0x48, 0x67, 0xb6, 0x8c, 0x4c, 0x08, 0x7e, 0x48, 0x4d, 0x48, 0xb2, 0x62, 0x9e, 0xb9, 0xe0, 0xa5, 0xd0, 0xe5, 0x8a, 0x55, 0x5c, 0x73, 0xe5, 0xae, 0x32, 0xb3, 0xbd, 0xe7, 0xbc, 0x25, 0xa1, 0xa0, 0x90, 0x34, 0xeb, 0x4a, 0xeb, 0x0b, 0x07, 0xa5, 0xc5, 0xf0, 0x1e, 0x91, 0x2b, 0x30, 0x37, 0x07, 0xcf, 0x5d, 0xa3, 0x05, + 0x2c, 0x88, 0x30, 0x2f, 0x33, 0x2e, 0xfe, 0x1b, 0x68, 0x7e, 0xf6, 0x80, 0x99, 0xb0, 0x28, 0x6d, 0x2e, 0x42, 0x4b, 0xeb, 0x24, 0xc8, 0x81, 0x76, 0x0b, 0xc3, 0xf0, 0xac, 0x3c, 0x4d, 0xc7, 0xb0, 0x4f, 0x2d, 0xde, 0x0c, 0x0d, 0xa0, 0x57, 0x04, 0x56, 0x58, 0x8a, 0x46, 0xba, 0xb8, 0x50, 0x87, 0xba, 0x51, 0x1c, 0xc0, 0x4b, 0x26, 0xb5, 0xdd, 0x65, 0x94, 0xa9, 0xfa, 0x0c, 0xe8, 0xe7, 0xe7, 0xa1, 0x1b, 0xbe, 0x5c, 0xe9, 0xb6, 0x2f, 0x53, 0x7a, 0x3a, 0x91, 0x8f, 0xa9, 0x83, 0x1a, 0x0d, 0x49, 0x72, 0x4a, 0xae, 0x86, 0x73, 0xb0, 0x89, 0x8b, 0xa9, 0xdf, 0xeb, 0x85, 0xc0, 0x3b, 0xd3, 0x3b, 0x73, 0x46, 0x77, 0x43, 0x5d, 0x45, 0x79, 0x24, 0x8c, 0x83, 0xa2, 0x8a, 0x3c, 0xd0, 0x43, 0x3d, 0x4d, 0xd9, 0x9d, 0x31, 0x0e, 0xc7, 0x01, 0x2b, 0x49, 0xfa, 0x1e, 0x2c, 0xd7, 0x36, 0x87, 0xdd, + 0x0c, 0x9d, 0xca, 0x3c, 0x8b, 0xe3, 0x9a, 0x70, 0x19, 0xa3, 0xf2, 0x7a, 0x92, 0xcb, 0xa7, 0xbe, 0x41, 0x0d, 0x4f, 0x0f, 0x75, 0x89, 0xb2, 0xb2, 0x04, 0xfa, 0xff, 0x5b, 0xd1, 0xe6, 0xe9, 0x25, 0x16, 0x47, 0xcd, 0xc4, 0x8e, 0xb6, 0x9d, 0xfe, 0xb8, 0xa6, 0xb8, 0xb1, 0xbb, 0xdc, 0xe4, 0x35, 0x3a, 0xaa, 0xbb, 0xe7, 0x75, 0x57, 0x97, 0x74, 0x2e, 0x6d, 0x6b, 0x5d, 0xd0, 0x5c, 0x58, 0x16, 0xb1, 0x39, 0x83, 0xa1, 0xd2, 0x8a, 0x40, 0xe3, 0x8c, 0xe4, 0xc4, 0x1a, 0xee, 0x9b, 0x65, 0x25, 0xa1, 0x32, 0xb4, 0x19, 0x88, 0x4b, 0x3f, 0xb7, 0x5b, 0xd0, 0xa6, 0x5f, 0xef, 0x2d, 0xf0, 0x46, 0xcb, 0x98, 0x68, 0x9d, 0x4b, 0xd4, 0xba, 0x7d, 0xb1, 0xf6, 0x72, 0x37, 0xcb, 0x18, 0xcd, 0x36, 0x03, 0xcb, 0xbb, 0x6b, 0x6b, 0x42, 0x13, 0x6a, 0x03, 0xae, 0x68, 0xb5, 0x2f, 0x54, 0x65, 0xd2, 0x47, + 0x0a, 0x5d, 0xb5, 0x16, 0xe7, 0xd4, 0xca, 0x58, 0x47, 0xb9, 0x0b, 0x7b, 0xb4, 0xfd, 0xff, 0x7d, 0xff, 0x7f, 0xb8, 0xef, 0x21, 0xe8, 0x67, 0xf7, 0x31, 0x51, 0xb4, 0xbe, 0x39, 0x40, 0x29, 0xde, 0x8c, 0x2a, 0x31, 0x4c, 0x00, 0xc7, 0x42, 0xa4, 0x30, 0x2d, 0x56, 0xd4, 0x60, 0x34, 0xdf, 0x90, 0x28, 0x24, 0x3c, 0x5a, 0xc8, 0x6c, 0x63, 0x44, 0x21, 0x49, 0xfd, 0xc6, 0x3f, 0xa5, 0x2a, 0xd1, 0x99, 0x74, 0xed, 0x2a, 0x2d, 0xf5, 0xd7, 0xb9, 0x76, 0xed, 0xe2, 0xd7, 0x7a, 0x83, 0x45, 0xb5, 0xd3, 0x13, 0xd2, 0x09, 0xd8, 0x1d, 0xaf, 0xf4, 0x38, 0xa5, 0x1b, 0xb1, 0xd6, 0xc0, 0x80, 0x4b, 0xd1, 0x3c, 0x17, 0x11, 0x76, 0xa0, 0xbe, 0xb0, 0x81, 0x59, 0x27, 0x38, 0x7c, 0xb5, 0x8f, 0xc6, 0x57, 0x1c, 0x87, 0x7b, 0x42, 0x30, 0xac, 0x04, 0x0c, 0x90, 0x0c, 0x7f, 0x64, 0x92, 0x03, 0xc4, 0xeb, + 0x52, 0x06, 0x73, 0xf8, 0x78, 0x00, 0xce, 0xc5, 0x0f, 0x70, 0xfc, 0x38, 0x08, 0x66, 0xf4, 0xa7, 0x4d, 0x00, 0x58, 0x0b, 0x94, 0x28, 0x24, 0x72, 0xd4, 0x84, 0x86, 0x0c, 0xd3, 0xd1, 0xaa, 0x0a, 0xad, 0xbb, 0xee, 0x32, 0xb9, 0x03, 0x56, 0x4b, 0xb1, 0xcb, 0x58, 0x1d, 0x0a, 0xd7, 0xf2, 0xd7, 0x4a, 0x66, 0xf8, 0x87, 0xc3, 0xd6, 0x62, 0x97, 0xc9, 0xe4, 0x2a, 0xb6, 0x86, 0x6b, 0xe4, 0xfb, 0xf2, 0xcf, 0x98, 0x97, 0xc1, 0x30, 0xa2, 0x0b, 0xc7, 0x45, 0x58, 0xf4, 0xa8, 0x4e, 0xcb, 0x50, 0x07, 0x7c, 0x4c, 0x98, 0x13, 0x70, 0x68, 0x12, 0xe6, 0xd8, 0x01, 0xea, 0x98, 0xbe, 0x0c, 0x9b, 0x63, 0x0e, 0xe0, 0xa1, 0x15, 0xe4, 0x31, 0xf9, 0x48, 0x73, 0x19, 0xca, 0xc1, 0x98, 0x0f, 0xa8, 0x2d, 0x03, 0x04, 0x7d, 0x39, 0xc1, 0x11, 0xa2, 0x0e, 0x41, 0xe7, 0x4c, 0xd8, 0xf2, 0xe3, 0x09, 0x34, + 0xe4, 0xf9, 0xaa, 0xff, 0xa7, 0xb5, 0x74, 0x62, 0x95, 0xb5, 0xd8, 0xc2, 0x33, 0x06, 0x5b, 0xc4, 0x8b, 0x88, 0x2f, 0xc6, 0xc4, 0x63, 0x6a, 0x2d, 0xd6, 0x62, 0xb7, 0x09, 0x35, 0x40, 0x0c, 0x75, 0x54, 0x16, 0xf2, 0x7c, 0xb7, 0x5e, 0xe3, 0x2f, 0x85, 0x9f, 0xa8, 0x6d, 0x41, 0x4f, 0x8d, 0x46, 0x77, 0xb1, 0x6c, 0xb7, 0x87, 0xfa, 0xb5, 0x1d, 0xf5, 0xab, 0x1f, 0xd4, 0xa4, 0x2b, 0x4d, 0x48, 0x71, 0xc4, 0xee, 0xe8, 0x88, 0x5e, 0xa2, 0xac, 0xb0, 0x0c, 0x60, 0x07, 0x38, 0x45, 0x55, 0x19, 0x20, 0x0e, 0xb7, 0x7e, 0xe0, 0xb7, 0x44, 0x5d, 0x51, 0x0b, 0x56, 0x55, 0x6c, 0xd8, 0x4b, 0x5a, 0x76, 0xc9, 0x33, 0x41, 0x11, 0x29, 0x58, 0x44, 0x11, 0xc6, 0x3e, 0xd4, 0x68, 0x89, 0x79, 0xd1, 0x65, 0x2a, 0x10, 0x2e, 0x12, 0xdc, 0x96, 0xad, 0x5f, 0xd1, 0x38, 0x8c, 0x2e, 0xf3, 0xd7, 0xaf, 0x2c, + 0xf0, 0xf0, 0x9b, 0x05, 0x8b, 0xd9, 0xbf, 0x4b, 0xb8, 0x56, 0x9a, 0x27, 0xea, 0x0f, 0x1a, 0xf5, 0x4f, 0x98, 0xec, 0x1a, 0xbd, 0xff, 0x33, 0x9d, 0xf9, 0x7b, 0x26, 0xe1, 0x3f, 0x15, 0x7d, 0x71, 0x3e, 0xbb, 0x0f, 0xbc, 0x8a, 0x68, 0x62, 0x41, 0x71, 0xba, 0x90, 0xa1, 0xdd, 0xc9, 0x64, 0xba, 0x33, 0x2b, 0xfa, 0x05, 0x52, 0x8f, 0x2c, 0xaf, 0xa2, 0xd2, 0x86, 0xcd, 0xca, 0xbb, 0xa8, 0x3d, 0x6c, 0x2d, 0xd2, 0x6d, 0x42, 0xa0, 0x36, 0x5d, 0x2d, 0xa0, 0xf6, 0xf8, 0x49, 0x3e, 0x0d, 0x9e, 0x28, 0xbf, 0x22, 0xf6, 0xd5, 0x1d, 0xe8, 0x16, 0x90, 0xf6, 0xc9, 0x28, 0xbf, 0x98, 0x1e, 0xec, 0x35, 0x56, 0x62, 0x0b, 0x97, 0x10, 0xeb, 0x85, 0x4c, 0x22, 0x22, 0x27, 0x19, 0xb8, 0x58, 0x80, 0x73, 0x5d, 0x40, 0x5d, 0xff, 0xea, 0x9f, 0x58, 0x55, 0xd5, 0x51, 0xe0, 0x5d, 0x58, 0xb3, 0x60, 0xf1, + 0xae, 0xde, 0x50, 0xd5, 0xae, 0x48, 0x29, 0x62, 0xc5, 0xae, 0xb9, 0x5e, 0x81, 0x5f, 0x18, 0x28, 0x4b, 0xc5, 0x6b, 0x12, 0xab, 0x97, 0xe2, 0xe1, 0xdc, 0x9f, 0x2c, 0xc7, 0x1f, 0x89, 0x0a, 0xa7, 0x5d, 0xba, 0x8e, 0xd1, 0x7a, 0x89, 0x9e, 0x88, 0x75, 0xaf, 0x26, 0x44, 0x9f, 0x0d, 0xf8, 0x72, 0x7c, 0xa8, 0x06, 0xba, 0x55, 0x13, 0xde, 0x55, 0xd4, 0x87, 0xca, 0xe3, 0xb2, 0xfb, 0x1c, 0x3e, 0xd9, 0xf3, 0x48, 0x37, 0x96, 0xe7, 0x51, 0x1d, 0xa2, 0x0e, 0x61, 0xd6, 0x35, 0x2f, 0x9b, 0x1c, 0x89, 0x4c, 0x5e, 0xd6, 0xdc, 0xb2, 0x6c, 0x52, 0x34, 0x3a, 0x69, 0x59, 0xcb, 0xe6, 0xcd, 0x97, 0xf0, 0xf7, 0x46, 0xa6, 0x0c, 0xb4, 0xb7, 0x0f, 0x4c, 0x89, 0x46, 0xe9, 0x67, 0x44, 0x3a, 0xe7, 0x6b, 0xe8, 0x0f, 0x50, 0xf4, 0xa0, 0x26, 0xa4, 0x07, 0x61, 0x1a, 0x90, 0x16, 0x81, 0xab, 0x27, 0x3d, + 0x8d, 0x2a, 0x47, 0x34, 0xcc, 0x57, 0xbc, 0xa0, 0x18, 0xd8, 0xfb, 0x27, 0xd3, 0x01, 0xe3, 0x67, 0xa0, 0xa3, 0x8f, 0x79, 0x9b, 0xa9, 0x47, 0xfb, 0x57, 0x9c, 0x77, 0x62, 0x50, 0xb6, 0xfb, 0x2f, 0x96, 0xed, 0xfe, 0xab, 0x90, 0x40, 0x05, 0x55, 0x96, 0x9c, 0xdf, 0x0d, 0x94, 0xcd, 0x0b, 0xd5, 0x2f, 0x09, 0x90, 0xda, 0x36, 0xcb, 0x1b, 0x9c, 0x3c, 0x14, 0xaa, 0x8c, 0x92, 0x4d, 0x41, 0xd0, 0x56, 0x1a, 0x89, 0xe6, 0x6c, 0x76, 0x14, 0xab, 0xe6, 0x9c, 0xdd, 0x8e, 0x83, 0x74, 0xee, 0x8b, 0xf6, 0x4a, 0xb4, 0xd9, 0x31, 0x79, 0x42, 0xce, 0x68, 0xb5, 0xcb, 0x10, 0x98, 0xd5, 0x14, 0x9f, 0x4c, 0xf7, 0x3b, 0x89, 0x66, 0xf7, 0xae, 0xb9, 0x7e, 0x81, 0xee, 0x76, 0x12, 0xad, 0x91, 0x02, 0xbf, 0x6f, 0x4e, 0x04, 0x69, 0xd6, 0x03, 0x64, 0xbf, 0xe3, 0x71, 0xfe, 0x84, 0x39, 0xe6, 0xa6, 0xe3, + 0x0f, 0xde, 0x8c, 0xda, 0xe4, 0x00, 0x2b, 0x68, 0x23, 0x74, 0x3c, 0x1a, 0xb8, 0x26, 0xc5, 0xc2, 0x18, 0xfd, 0xe0, 0xe8, 0x8f, 0x7e, 0xfa, 0xd8, 0x4d, 0xcf, 0x1a, 0x70, 0x1f, 0xc8, 0x2d, 0x20, 0x5f, 0xf1, 0x75, 0xa9, 0x53, 0x3d, 0x86, 0x20, 0x4f, 0xc8, 0xce, 0x61, 0x80, 0xed, 0xe9, 0x7f, 0x2c, 0x68, 0x0b, 0x86, 0x32, 0x33, 0xac, 0x62, 0x3e, 0x2a, 0x0f, 0x4f, 0xa7, 0xeb, 0x27, 0x9e, 0x09, 0xc9, 0x12, 0xb7, 0x96, 0xb3, 0x6a, 0x8a, 0x4c, 0xd1, 0x64, 0xc1, 0xae, 0x59, 0x0e, 0x33, 0xcf, 0x77, 0x56, 0xd6, 0x17, 0x9c, 0x67, 0xe3, 0xcb, 0x22, 0xd2, 0x29, 0x46, 0x6b, 0xb6, 0x13, 0x59, 0xa9, 0x46, 0xfb, 0xb2, 0xef, 0xa1, 0xb1, 0x98, 0x02, 0x8d, 0xe9, 0xba, 0x14, 0xda, 0x52, 0x79, 0x72, 0x6d, 0x2e, 0xf3, 0x36, 0x64, 0x72, 0xe6, 0x19, 0xb4, 0x63, 0x49, 0x86, 0xed, 0x49, 0xa4, + 0x22, 0x62, 0x95, 0x52, 0x50, 0x77, 0x59, 0xca, 0x32, 0x96, 0x63, 0x4d, 0x55, 0xc1, 0xa9, 0x9b, 0xad, 0xdb, 0x75, 0x16, 0x9d, 0xc3, 0x57, 0xe2, 0x0c, 0x46, 0x23, 0x0d, 0xe9, 0x86, 0x88, 0xaf, 0xbe, 0xb7, 0xb6, 0x7e, 0xa0, 0x38, 0x68, 0x70, 0x19, 0x1c, 0xbe, 0xb0, 0xaf, 0x2c, 0x14, 0x69, 0x4c, 0x37, 0x46, 0x3c, 0xa9, 0xae, 0x9a, 0x86, 0x85, 0x7e, 0xbe, 0xb2, 0x40, 0xa7, 0xd7, 0x79, 0x5d, 0x0e, 0x97, 0xcd, 0x1e, 0xac, 0x8d, 0x47, 0x3b, 0x2b, 0xbd, 0xe1, 0xc2, 0x7a, 0xbb, 0xd1, 0x6c, 0x08, 0xda, 0x9d, 0x6e, 0x9b, 0x2d, 0xd4, 0x5c, 0x19, 0x6c, 0x4e, 0x7a, 0x23, 0x7e, 0xd2, 0x8e, 0x20, 0x6a, 0xc7, 0x2e, 0xa1, 0x01, 0x14, 0x83, 0xd6, 0x74, 0x93, 0x16, 0x42, 0xd6, 0x0f, 0xd1, 0xe6, 0xb7, 0x0b, 0x20, 0xc1, 0xdf, 0x84, 0x97, 0x01, 0x72, 0x54, 0x83, 0xc4, 0x1e, 0xca, 0x7b, + 0x94, 0x55, 0xa0, 0x07, 0x02, 0x97, 0xc3, 0x6a, 0x29, 0x30, 0x9b, 0x0c, 0x3c, 0x07, 0x8a, 0x61, 0xb1, 0x48, 0xe2, 0x31, 0x29, 0x97, 0x8e, 0x1d, 0xb0, 0xbe, 0x85, 0x06, 0x12, 0x50, 0x6e, 0x37, 0xf0, 0xa5, 0xa4, 0x34, 0xa5, 0xbb, 0x46, 0x6f, 0x3e, 0xa7, 0x90, 0xf7, 0x4e, 0xbd, 0xfe, 0x7a, 0xa7, 0xcd, 0x6e, 0xd5, 0x09, 0x22, 0x5f, 0x56, 0x38, 0x9d, 0xbd, 0xb1, 0x6f, 0x32, 0x74, 0x9a, 0x4d, 0x8d, 0x05, 0x3b, 0x5c, 0x77, 0x0d, 0x4a, 0x1f, 0x38, 0x3c, 0x68, 0x93, 0x38, 0xc9, 0xe5, 0x22, 0xb4, 0x75, 0x21, 0x79, 0x77, 0xf2, 0x87, 0x80, 0x0b, 0xa4, 0xd2, 0x55, 0x4e, 0x9c, 0x52, 0x12, 0xb1, 0x96, 0xe9, 0x82, 0xc4, 0x8e, 0x8a, 0xc7, 0x13, 0xd1, 0x00, 0x39, 0xcf, 0x26, 0x06, 0xf9, 0xab, 0xc8, 0xd1, 0xa4, 0x0b, 0xb8, 0x42, 0x8e, 0x30, 0x62, 0xaf, 0x1b, 0xaf, 0x53, 0x99, 0xed, + 0x5f, 0xe6, 0xde, 0x85, 0x71, 0xce, 0xf5, 0x68, 0xc5, 0x40, 0x57, 0x5d, 0x7c, 0x42, 0xb9, 0x6b, 0x67, 0x59, 0xa9, 0xbf, 0xd6, 0xc5, 0x1f, 0x1a, 0xfe, 0xa3, 0x27, 0x1a, 0x88, 0xe1, 0xb5, 0x14, 0x76, 0x4b, 0x27, 0xe2, 0x95, 0x5e, 0x27, 0xbc, 0x88, 0xce, 0x89, 0x22, 0x9a, 0xe3, 0xfd, 0x88, 0x06, 0x2b, 0x8e, 0x05, 0xa1, 0x27, 0x21, 0x63, 0x30, 0xf8, 0x42, 0x72, 0x26, 0x3a, 0x40, 0x62, 0x02, 0x5b, 0x81, 0xc5, 0x62, 0xe1, 0xd0, 0xb2, 0x03, 0xd9, 0x10, 0x4b, 0xda, 0x4b, 0x8e, 0x42, 0xf1, 0xdc, 0x9e, 0x62, 0xfc, 0x3f, 0xbe, 0xf2, 0x47, 0x9b, 0xcc, 0x4e, 0x66, 0xb1, 0xa0, 0xe7, 0x05, 0x83, 0xb8, 0x84, 0xb5, 0x9b, 0x70, 0x75, 0x8c, 0xd6, 0x64, 0x18, 0xf6, 0xd7, 0x7a, 0xbd, 0x75, 0xfe, 0xcf, 0x0d, 0x26, 0x6c, 0x6f, 0x8f, 0xe6, 0xde, 0x16, 0xfe, 0x2e, 0x50, 0x8e, 0xa3, 0x81, + 0x45, 0xdc, 0x1a, 0xbc, 0x20, 0xe3, 0x96, 0x32, 0xe0, 0x42, 0x9e, 0x63, 0x59, 0xb2, 0x1a, 0x92, 0x64, 0xd2, 0x89, 0x56, 0x4b, 0x9c, 0xb6, 0x0f, 0x27, 0x04, 0x60, 0x2b, 0x60, 0x0c, 0x1f, 0x46, 0x0b, 0xe8, 0x67, 0x76, 0xd5, 0xa8, 0x0f, 0x6a, 0xb2, 0xe9, 0x60, 0x5b, 0xee, 0x5f, 0x67, 0x13, 0xe7, 0xf3, 0x68, 0xb7, 0x26, 0xf2, 0xfd, 0x82, 0xeb, 0xbc, 0x7b, 0x37, 0x9a, 0x5d, 0xcc, 0x24, 0x4e, 0xc3, 0xf3, 0x7a, 0x71, 0xa6, 0xd6, 0xdf, 0x10, 0xd0, 0xcc, 0x10, 0x74, 0x1c, 0xaf, 0xe1, 0xa7, 0xb2, 0x0e, 0x42, 0xa1, 0x07, 0x3a, 0x0a, 0x8a, 0x0b, 0x2c, 0xc5, 0x16, 0xe9, 0x1d, 0x0f, 0x1a, 0xf5, 0x96, 0x0f, 0xbc, 0x15, 0x2e, 0x57, 0x85, 0xf7, 0x8f, 0x45, 0xc5, 0x1f, 0x79, 0x92, 0x2e, 0x67, 0xd2, 0xf3, 0x51, 0x81, 0x19, 0xd1, 0x5d, 0x8c, 0xf8, 0xd3, 0xa8, 0xe6, 0x07, 0x57, 0xee, + 0x1a, 0x48, 0x17, 0x91, 0xd4, 0x35, 0x48, 0xbf, 0x28, 0xb5, 0xc6, 0x0b, 0x38, 0x4c, 0x30, 0x3d, 0xe9, 0x81, 0x66, 0x7a, 0x6a, 0x1c, 0x23, 0xf4, 0xd6, 0x13, 0x5a, 0x45, 0xa6, 0x71, 0x87, 0xd9, 0xc6, 0xaf, 0x66, 0x05, 0x6e, 0x01, 0x6f, 0x33, 0x5d, 0x7e, 0xec, 0x81, 0x1d, 0x66, 0x93, 0xb0, 0x9a, 0xd7, 0xf5, 0x6a, 0x9d, 0xdb, 0x1e, 0x60, 0xf7, 0x99, 0xf5, 0x47, 0x75, 0x56, 0xcd, 0x2e, 0x5d, 0x01, 0x34, 0x7d, 0xa6, 0xd1, 0xfe, 0xa3, 0xd5, 0x79, 0xa5, 0xfd, 0x43, 0xda, 0x3f, 0xce, 0x91, 0x00, 0x53, 0x01, 0xcb, 0x50, 0xfd, 0xd1, 0x74, 0xc8, 0x97, 0x75, 0xd7, 0x81, 0x08, 0x98, 0x8f, 0x09, 0x00, 0x0b, 0x09, 0x17, 0x7b, 0x15, 0x2a, 0xf8, 0x33, 0x51, 0x51, 0xb1, 0xc3, 0x44, 0xa8, 0xe0, 0x17, 0xf0, 0x56, 0x99, 0x0a, 0x7e, 0x0d, 0xaf, 0x9b, 0xa9, 0x73, 0x6e, 0x7d, 0xe0, 0x15, + 0x44, 0x84, 0xde, 0xa2, 0xd9, 0xa5, 0x37, 0x43, 0xe3, 0xe7, 0x1a, 0xdd, 0xfd, 0x0a, 0x11, 0x88, 0x06, 0x34, 0x17, 0x57, 0x93, 0xdc, 0x04, 0x41, 0x7c, 0x1b, 0x41, 0xc7, 0x08, 0x80, 0x0b, 0xe9, 0x18, 0x21, 0xe3, 0xd2, 0x01, 0xec, 0xd6, 0x10, 0x1e, 0x23, 0xf4, 0xc8, 0xbc, 0x41, 0xed, 0xac, 0x7a, 0x3a, 0x4c, 0xaa, 0xa5, 0xdf, 0x69, 0x6d, 0xf6, 0x35, 0x03, 0x3f, 0xbb, 0xac, 0xc0, 0xca, 0x0d, 0xf2, 0x5a, 0x56, 0xab, 0x5f, 0xc1, 0x39, 0x8c, 0xa8, 0x23, 0x2e, 0x76, 0x14, 0xfc, 0x3f, 0xf8, 0xb2, 0x59, 0xff, 0xbe, 0xb9, 0xc8, 0x1c, 0x28, 0x7e, 0x57, 0x6f, 0xa6, 0xed, 0x4e, 0xa0, 0xb9, 0xf2, 0x5f, 0x50, 0x9d, 0x66, 0x50, 0x96, 0x8e, 0x62, 0xeb, 0x4d, 0x0d, 0x6e, 0xa6, 0x9a, 0xc3, 0x99, 0x2c, 0x89, 0x24, 0x04, 0xf0, 0x2a, 0x92, 0xc3, 0xd9, 0x4e, 0x3a, 0xc0, 0x2e, 0x88, 0x6c, + 0x8a, 0x26, 0x71, 0xb6, 0x85, 0xea, 0x82, 0xf0, 0x5f, 0x74, 0xfa, 0x37, 0x3f, 0x92, 0xce, 0xd3, 0x19, 0xde, 0x7a, 0x17, 0xee, 0xe1, 0x57, 0x98, 0x8b, 0x0c, 0xd2, 0xf3, 0xeb, 0x5a, 0xb4, 0x7e, 0x33, 0xac, 0x38, 0x1f, 0xab, 0x59, 0x48, 0xe6, 0x7b, 0xc9, 0xd8, 0xf7, 0xa5, 0xdd, 0xf8, 0xe4, 0x97, 0xe4, 0x88, 0xa6, 0x32, 0x37, 0x08, 0x7a, 0x2c, 0xe5, 0x01, 0x56, 0xe7, 0x92, 0xd3, 0xbe, 0xd3, 0x71, 0x48, 0x1a, 0x07, 0x53, 0x7c, 0xef, 0xa3, 0x85, 0x21, 0xad, 0x74, 0x85, 0xc9, 0xa7, 0xd7, 0xb9, 0x4d, 0xd2, 0x95, 0x62, 0xc4, 0xf7, 0x55, 0x66, 0xda, 0xf7, 0x0b, 0x9d, 0xec, 0x4f, 0xc3, 0xf3, 0x2a, 0x2b, 0xe7, 0x85, 0x3f, 0xaf, 0xb2, 0x15, 0xa3, 0x82, 0x51, 0x1d, 0x9d, 0x72, 0x1e, 0xea, 0x08, 0xa8, 0x4f, 0xa7, 0xf0, 0xd4, 0x59, 0x84, 0x4a, 0xb7, 0x91, 0x83, 0x1f, 0xb2, 0xb6, + 0xaa, 0x8d, 0x21, 0x96, 0x93, 0x83, 0x0c, 0x9a, 0x7d, 0xc2, 0x21, 0x8f, 0xdb, 0xe5, 0x00, 0x11, 0x18, 0xe1, 0x89, 0xad, 0x4a, 0x34, 0x86, 0x63, 0xcc, 0x14, 0xb1, 0x0e, 0x27, 0xc9, 0x3d, 0x2f, 0x22, 0x7e, 0xca, 0x99, 0xaa, 0xb5, 0x10, 0xef, 0xd3, 0x83, 0x0e, 0xf8, 0x83, 0xea, 0x99, 0xb1, 0xc4, 0x24, 0x3f, 0x34, 0x5b, 0x1c, 0x06, 0x0e, 0xe9, 0x57, 0xd2, 0x0f, 0x8b, 0xe2, 0x8e, 0xe8, 0x44, 0x3f, 0xf3, 0xaf, 0x9f, 0xfd, 0xab, 0xd3, 0x02, 0x7f, 0x2a, 0x95, 0xe8, 0x4d, 0x5c, 0x03, 0xf7, 0xa1, 0xa8, 0x71, 0xd5, 0x96, 0x48, 0xdf, 0xb2, 0xfb, 0xdd, 0x76, 0xad, 0xab, 0xa6, 0x54, 0xba, 0x2e, 0x54, 0x6a, 0xe4, 0xf9, 0xfb, 0xfb, 0xdc, 0x95, 0x56, 0x26, 0xc4, 0x34, 0x18, 0x02, 0x96, 0x45, 0xf7, 0x53, 0xfe, 0x2f, 0x03, 0x00, 0xcd, 0x60, 0x98, 0xee, 0x44, 0xba, 0x54, 0xc0, 0xa7, + 0x21, 0x00, 0x6d, 0xe6, 0x06, 0x68, 0xb4, 0x12, 0x0f, 0x26, 0xd8, 0x4b, 0x26, 0x87, 0x08, 0x08, 0x5b, 0x2c, 0x2e, 0x1c, 0x77, 0x5f, 0x2b, 0xf8, 0x68, 0x14, 0x25, 0xbc, 0xd6, 0x54, 0xb0, 0x75, 0x16, 0xa2, 0x26, 0xd9, 0x8b, 0x18, 0x9c, 0xac, 0xaa, 0x1d, 0xfd, 0x46, 0xba, 0xb4, 0x20, 0xf6, 0x1e, 0x7a, 0xf3, 0xe0, 0xbf, 0x1d, 0x7c, 0xe3, 0xce, 0xde, 0x3d, 0x25, 0x91, 0xe4, 0xf2, 0xdb, 0xd7, 0xfd, 0xdb, 0xba, 0x3b, 0x56, 0x24, 0x23, 0x25, 0x68, 0x20, 0x8c, 0x5c, 0xf5, 0x93, 0x03, 0x73, 0x05, 0xe9, 0x1e, 0xb8, 0x58, 0x98, 0xb7, 0xff, 0xd9, 0x6d, 0x0c, 0x8c, 0x17, 0x4b, 0xd3, 0x16, 0xdf, 0xb2, 0xaa, 0x89, 0x67, 0xf7, 0x7d, 0x7e, 0x01, 0xdf, 0xb4, 0xea, 0x96, 0xc5, 0xf0, 0xdb, 0xc5, 0xf1, 0x91, 0x91, 0x91, 0x27, 0x50, 0xbf, 0xbd, 0x43, 0xf2, 0x9c, 0xff, 0x74, 0x98, 0xe4, + 0x87, 0x97, 0xd6, 0xe4, 0xe6, 0x87, 0x47, 0xf4, 0x9f, 0x8f, 0x70, 0x3e, 0xe6, 0x0f, 0x83, 0x16, 0x1c, 0x2b, 0xc6, 0x2b, 0x20, 0x1d, 0xbb, 0x31, 0xcc, 0xe0, 0xd9, 0x55, 0x49, 0xa1, 0x7a, 0x7e, 0xb7, 0xc0, 0x33, 0x78, 0x4d, 0x65, 0xa0, 0x12, 0x37, 0xad, 0x05, 0x34, 0x27, 0x42, 0xf1, 0x78, 0x08, 0xab, 0xaf, 0x11, 0x92, 0x31, 0xa2, 0x0e, 0xd1, 0x5d, 0x9b, 0x49, 0x9e, 0xe6, 0x72, 0x04, 0xf1, 0x12, 0x10, 0x44, 0x4b, 0x56, 0x9e, 0x93, 0x30, 0xea, 0x13, 0xfe, 0xe3, 0xd2, 0xc0, 0xf0, 0x07, 0x33, 0x6f, 0x18, 0x68, 0x9a, 0x79, 0xdd, 0xb1, 0xe5, 0xcb, 0x8f, 0xed, 0x98, 0xd9, 0x34, 0x70, 0xc3, 0xcc, 0xe1, 0x0f, 0x02, 0xa5, 0xb0, 0x20, 0x76, 0x4e, 0xe9, 0xc2, 0x3b, 0x36, 0x74, 0x5c, 0xf0, 0xbd, 0xe1, 0x5b, 0x6e, 0x96, 0x9e, 0xb8, 0xa0, 0x63, 0xc3, 0x1d, 0x0b, 0x4b, 0xcf, 0x89, + 0x1d, 0x5f, 0x5f, 0x14, 0xfb, 0xd0, 0xb4, 0x64, 0xef, 0x63, 0xab, 0x2f, 0x7d, 0xf4, 0xb2, 0xd6, 0xd6, 0xcb, 0x1e, 0xbd, 0x74, 0xf5, 0x63, 0x7b, 0x97, 0x98, 0x3e, 0x8c, 0x15, 0xad, 0xd7, 0x08, 0xc5, 0x9b, 0xef, 0xfe, 0xe7, 0xa1, 0xfd, 0x1f, 0xe1, 0x92, 0x3e, 0xda, 0x3f, 0xf4, 0xcf, 0x77, 0x6f, 0x2e, 0x16, 0x34, 0xa8, 0xed, 0x5b, 0x50, 0xbb, 0xde, 0x25, 0x7e, 0xb2, 0x3f, 0xa5, 0x7e, 0xb5, 0x52, 0x1f, 0xc9, 0xfd, 0x7e, 0x53, 0x1e, 0xbc, 0x6d, 0xf8, 0x4d, 0x02, 0xcf, 0xc7, 0x6f, 0x97, 0xe1, 0x2f, 0x61, 0xb8, 0xf0, 0x8c, 0x0a, 0xef, 0x90, 0xe6, 0x13, 0x1f, 0x8f, 0x6d, 0x04, 0xff, 0xa4, 0x0a, 0x6f, 0xa2, 0x70, 0xe9, 0xfa, 0x3c, 0x78, 0x5a, 0x2e, 0xe7, 0xe2, 0x3c, 0xf8, 0x84, 0x71, 0xca, 0xef, 0x94, 0xcb, 0xbf, 0x87, 0xe0, 0x1f, 0x57, 0xe1, 0xcd, 0x32, 0xfc, 0x32, 0x04, + 0xff, 0x2f, 0xda, 0xa7, 0x24, 0x6e, 0x5f, 0x8b, 0x74, 0x07, 0xb9, 0xcb, 0x19, 0x94, 0x36, 0x23, 0xfc, 0xc3, 0x2a, 0xfe, 0x74, 0x66, 0x3b, 0xc1, 0xdf, 0x42, 0xe0, 0x4a, 0xbb, 0x62, 0x94, 0x0f, 0x99, 0xe7, 0xd2, 0x4d, 0x79, 0xcf, 0x09, 0x3f, 0xb2, 0x9e, 0xe7, 0xbf, 0xdf, 0x9e, 0xf7, 0xfc, 0x25, 0xfc, 0x5c, 0xa5, 0x3f, 0x46, 0xf9, 0x93, 0x55, 0xff, 0x36, 0xf2, 0xfe, 0x49, 0xf5, 0x79, 0x53, 0xee, 0x73, 0xe9, 0xfa, 0xbc, 0xe7, 0xe9, 0xbc, 0xf2, 0x2f, 0xce, 0x7b, 0x3e, 0xe1, 0x2c, 0xf5, 0x77, 0xe6, 0x96, 0x0f, 0xfa, 0x46, 0xde, 0xe3, 0xee, 0x20, 0xfe, 0xcc, 0x3f, 0x93, 0xfd, 0x99, 0xb3, 0xe1, 0x4f, 0x2a, 0x70, 0xbe, 0x71, 0x1c, 0x78, 0xdb, 0x38, 0xf0, 0xf6, 0x6c, 0x38, 0xa9, 0x9f, 0xc2, 0x3b, 0x72, 0xf0, 0x4f, 0xaa, 0xf0, 0xa6, 0x71, 0xe0, 0xe9, 0x71, 0xe0, 0x13, 0xc6, + 0x29, 0xbf, 0x13, 0x7c, 0x91, 0x76, 0x1d, 0x57, 0xf1, 0x5b, 0x72, 0xe0, 0x7b, 0x55, 0x78, 0x6b, 0x0e, 0xbc, 0x4b, 0x2d, 0xe7, 0xb1, 0x1c, 0xf8, 0x21, 0x15, 0xfe, 0xb8, 0x02, 0x97, 0x36, 0x73, 0x77, 0x10, 0xdf, 0x36, 0x5a, 0xce, 0x74, 0xb8, 0x39, 0x03, 0x57, 0xf9, 0x13, 0xa3, 0xf0, 0x6c, 0xbe, 0xe6, 0x3d, 0x27, 0x7c, 0x3d, 0xc3, 0xfb, 0xed, 0x63, 0x3d, 0x57, 0xf9, 0x10, 0xa3, 0x7c, 0x1e, 0xf5, 0xfe, 0x49, 0xf5, 0x79, 0xd3, 0x59, 0x9e, 0xa7, 0xcf, 0xf2, 0x7c, 0xc2, 0x59, 0xea, 0xef, 0x1c, 0xf3, 0xfd, 0xe3, 0xea, 0xf3, 0xe6, 0xb3, 0x3c, 0x6f, 0x19, 0xf3, 0xf9, 0xde, 0x51, 0xfc, 0x53, 0xfa, 0x69, 0xf6, 0xc8, 0xfb, 0xec, 0x22, 0xd2, 0xdf, 0xcf, 0x93, 0xbb, 0xdd, 0xb7, 0x17, 0x83, 0x2c, 0xf8, 0x93, 0x0a, 0x9c, 0x9f, 0x32, 0x0e, 0xbc, 0x6d, 0x1c, 0x78, 0x7b, 0x0e, 0xfc, + 0xa4, 0x0a, 0x6f, 0x1a, 0x07, 0x9e, 0x1e, 0x07, 0x3e, 0x21, 0x07, 0x3e, 0x1e, 0x9d, 0xc7, 0x55, 0xfc, 0x16, 0x19, 0x6e, 0x95, 0x0e, 0x73, 0x4e, 0x82, 0xff, 0x02, 0xc5, 0xdf, 0x48, 0xd7, 0xd5, 0x52, 0x04, 0x2f, 0xe7, 0x34, 0x20, 0x80, 0x63, 0xfc, 0x15, 0xa0, 0xb5, 0xc8, 0xad, 0x41, 0x5a, 0x14, 0xd3, 0x85, 0xc3, 0x51, 0x71, 0x0b, 0xb1, 0xc3, 0x33, 0xd9, 0x4d, 0x11, 0x87, 0xe6, 0x35, 0xa0, 0x27, 0x1c, 0x09, 0xd9, 0x42, 0x4a, 0x20, 0x36, 0xf5, 0xa0, 0xa1, 0x36, 0x9a, 0x50, 0x53, 0x64, 0xc9, 0x1b, 0x55, 0x0e, 0x87, 0xec, 0xed, 0xb9, 0xa6, 0xf1, 0x74, 0xba, 0x23, 0xa9, 0xf7, 0xc4, 0x0a, 0x67, 0x5f, 0xee, 0x99, 0xf2, 0xad, 0x0b, 0xb0, 0x95, 0x95, 0x74, 0x98, 0xfd, 0x15, 0x8d, 0x47, 0xc3, 0xbc, 0xdc, 0x3d, 0xb5, 0xd7, 0xe8, 0xb6, 0xe9, 0x7b, 0x66, 0x4d, 0x3f, 0x17, 0x5b, + 0x57, 0xa9, 0x74, 0x3e, 0x29, 0xd3, 0x89, 0xe3, 0x34, 0x7c, 0x35, 0x8b, 0xfe, 0x0c, 0xbc, 0x0d, 0xec, 0x19, 0x13, 0xde, 0x2e, 0xc7, 0x25, 0x22, 0x70, 0x32, 0x8e, 0x5e, 0x90, 0xe7, 0x8b, 0xdd, 0x59, 0xf8, 0x27, 0x55, 0x78, 0x53, 0x4e, 0xf9, 0x19, 0x78, 0x1a, 0x7c, 0x65, 0x4c, 0xf8, 0x04, 0xf0, 0x8f, 0x63, 0x96, 0xdf, 0x99, 0x53, 0x7e, 0x1e, 0x9f, 0x55, 0xf8, 0x71, 0x15, 0xbf, 0x05, 0xbc, 0x95, 0x05, 0xdf, 0xab, 0xc2, 0x5b, 0xc1, 0x83, 0x32, 0x7c, 0x33, 0xe7, 0x24, 0x7e, 0x76, 0xa4, 0x1c, 0x7e, 0xfa, 0x66, 0x90, 0x81, 0xab, 0xed, 0x8d, 0x51, 0xfe, 0x9c, 0xe1, 0x39, 0xe1, 0xd3, 0x19, 0x9e, 0x13, 0x7e, 0xe5, 0x3f, 0x57, 0xdb, 0x15, 0xa3, 0x7c, 0x1b, 0xf5, 0xfe, 0x49, 0xf5, 0x79, 0xd3, 0x98, 0xf5, 0x67, 0x9e, 0x13, 0x3e, 0x9e, 0xe1, 0x39, 0xe1, 0xe7, 0x19, 0xea, 0xef, + 0x1c, 0xb3, 0x7e, 0x85, 0x8f, 0x51, 0xbe, 0xf9, 0x52, 0xf9, 0xe9, 0xa8, 0x67, 0x31, 0xca, 0xe3, 0x51, 0xef, 0xee, 0x55, 0x9f, 0x13, 0x5e, 0x67, 0x3d, 0xbf, 0x7a, 0xe4, 0x03, 0xa6, 0x98, 0xf4, 0xdd, 0x2f, 0xe9, 0x5c, 0x4f, 0xe3, 0x16, 0x8e, 0x3c, 0x88, 0xe0, 0x9b, 0xf8, 0x57, 0x54, 0xf8, 0x3b, 0x23, 0x73, 0x88, 0xec, 0x10, 0x7c, 0xf6, 0x63, 0x60, 0x01, 0x3d, 0xf4, 0x3a, 0xa1, 0x80, 0x9e, 0xe8, 0xc8, 0xa7, 0x3c, 0x1b, 0xd4, 0x8b, 0x80, 0x5c, 0xf8, 0x3a, 0x7c, 0xce, 0x3a, 0x0a, 0xb5, 0xbf, 0xff, 0x44, 0x24, 0x54, 0x80, 0x15, 0x7c, 0x1b, 0xde, 0x3b, 0xb7, 0x40, 0xf5, 0xdc, 0xea, 0xea, 0xd3, 0x1d, 0x53, 0xe6, 0x7a, 0xcb, 0x5b, 0x02, 0xed, 0x4b, 0xd3, 0x01, 0xf6, 0xe3, 0xe1, 0xf0, 0xf4, 0x19, 0xad, 0x8e, 0x80, 0x53, 0x1f, 0xef, 0x5e, 0x4b, 0x63, 0xef, 0x76, 0x22, 0x3a, + 0x26, 0x20, 0x3a, 0x3c, 0xe0, 0x12, 0x39, 0xbc, 0x2a, 0x07, 0xd1, 0xde, 0x1d, 0x07, 0xb3, 0xc1, 0xc1, 0x67, 0x56, 0x91, 0x38, 0x35, 0x59, 0x16, 0x07, 0x86, 0x33, 0xa1, 0x10, 0x4b, 0x83, 0x33, 0x15, 0x40, 0xa3, 0x95, 0xa2, 0xca, 0xdc, 0xc4, 0xf9, 0x3b, 0x82, 0x83, 0xd7, 0x44, 0x14, 0x62, 0xdb, 0xa0, 0x12, 0xc8, 0x30, 0x84, 0x6f, 0x10, 0x98, 0x09, 0x0a, 0xdd, 0x03, 0x03, 0xa7, 0x7f, 0xf0, 0x83, 0x8e, 0x29, 0xd7, 0xa9, 0x94, 0x1f, 0x24, 0x21, 0x3e, 0xf7, 0xdc, 0x34, 0x7d, 0x06, 0xe1, 0xfd, 0x95, 0x84, 0xf7, 0x4f, 0x2a, 0x3c, 0xe6, 0xa7, 0xc8, 0xbc, 0xff, 0x94, 0xc2, 0x81, 0x02, 0x6f, 0xc3, 0xbc, 0x47, 0xf0, 0xd7, 0xf3, 0xe0, 0xed, 0x32, 0xfc, 0x97, 0x08, 0x5e, 0x45, 0xc6, 0x0f, 0x85, 0x77, 0xc8, 0xb1, 0x27, 0x69, 0xf9, 0x27, 0x55, 0x78, 0x93, 0x5c, 0xfe, 0x07, 0x14, + 0xae, 0x96, 0x93, 0x96, 0xcb, 0xf9, 0x75, 0x1e, 0x7c, 0xc2, 0x38, 0xe5, 0x77, 0x9e, 0x69, 0x8c, 0x20, 0xf8, 0x32, 0x04, 0x5f, 0x41, 0xf4, 0x48, 0x8a, 0x3f, 0x49, 0x86, 0x1f, 0xc1, 0xe5, 0x0b, 0xcf, 0xa8, 0xf0, 0xc9, 0x14, 0x2e, 0x8f, 0xc1, 0xe3, 0x6a, 0xf9, 0xcd, 0x32, 0xfd, 0x75, 0x79, 0xf0, 0x16, 0xb9, 0xde, 0x27, 0x08, 0x7c, 0xaf, 0x5a, 0x4e, 0xab, 0x5c, 0xfe, 0x49, 0x02, 0xef, 0x52, 0xe0, 0xe0, 0x31, 0x19, 0xfe, 0x0c, 0x81, 0x1f, 0x52, 0xe1, 0x8f, 0xcb, 0x63, 0xf9, 0x18, 0x9a, 0x87, 0x9e, 0x23, 0xb1, 0x98, 0x8f, 0x52, 0xf7, 0x75, 0x7d, 0x40, 0xcb, 0x70, 0xac, 0x19, 0x02, 0x81, 0xc4, 0x53, 0x93, 0x7f, 0xa1, 0x21, 0xa1, 0xe4, 0xf8, 0x0b, 0xc9, 0x16, 0xba, 0x38, 0xb0, 0x24, 0xde, 0xa1, 0xae, 0x93, 0x47, 0x09, 0xfd, 0x8e, 0x06, 0xf5, 0x1a, 0x35, 0x13, 0x45, 0x3e, + 0xea, 0x86, 0x2c, 0xd4, 0x0d, 0x0a, 0x2a, 0xbe, 0x7a, 0x90, 0xb1, 0xc8, 0x11, 0xfe, 0x60, 0xd6, 0xa8, 0x93, 0x71, 0xe4, 0xa1, 0x17, 0x05, 0x11, 0x5b, 0xc8, 0x16, 0x8d, 0x86, 0xe4, 0xa1, 0x47, 0xb3, 0xb1, 0x53, 0x5f, 0x3d, 0x12, 0x6d, 0x0f, 0x1f, 0x8e, 0x65, 0x19, 0xfe, 0xa2, 0x2f, 0xdc, 0x73, 0x93, 0xda, 0xe3, 0x33, 0xd2, 0x15, 0xfa, 0xe6, 0xaf, 0x2c, 0x99, 0x76, 0x5e, 0x44, 0xe4, 0x97, 0xc7, 0x93, 0xa7, 0x57, 0xae, 0xea, 0xbf, 0x26, 0x71, 0xba, 0xa7, 0xe3, 0x86, 0x99, 0xde, 0xb8, 0x3b, 0xd5, 0x53, 0xb7, 0x60, 0x45, 0xa2, 0x2c, 0x3e, 0x37, 0x22, 0xbd, 0x9f, 0x28, 0xe3, 0x3e, 0x19, 0xbe, 0x6b, 0xdd, 0x55, 0xcb, 0xe7, 0x31, 0x07, 0x87, 0x4b, 0xba, 0xa7, 0x92, 0x98, 0xa2, 0x87, 0x39, 0x0f, 0x19, 0x9f, 0xbf, 0x96, 0xf5, 0xad, 0x5b, 0xe5, 0x58, 0xa3, 0x18, 0x7e, 0x52, + 0x85, 0x37, 0xc9, 0x70, 0x6c, 0x13, 0xfc, 0x9f, 0x64, 0x2e, 0x79, 0x43, 0xd6, 0x1b, 0x6f, 0xca, 0x82, 0x6f, 0x57, 0xe1, 0x27, 0x72, 0xe0, 0xaf, 0xa8, 0xf0, 0x77, 0x72, 0xe0, 0x4f, 0x2a, 0x70, 0x34, 0xdf, 0x8f, 0x0d, 0x6f, 0x1b, 0x07, 0xde, 0x9e, 0x0d, 0x27, 0xe3, 0xf6, 0x0d, 0x79, 0x3d, 0xcc, 0xc6, 0x3f, 0xa9, 0xc2, 0x9b, 0xc6, 0x81, 0xa7, 0xc7, 0x81, 0x4f, 0x18, 0xa7, 0xfc, 0xce, 0x1c, 0xfc, 0xe3, 0x2a, 0x7c, 0xe2, 0x38, 0xf0, 0x49, 0xe3, 0x94, 0x33, 0x79, 0x1c, 0xfc, 0xe6, 0x71, 0xe0, 0x2d, 0x39, 0xf0, 0xbd, 0x2a, 0xbc, 0x35, 0x07, 0xde, 0xa5, 0xf2, 0xf9, 0xb1, 0x1c, 0xf8, 0x21, 0x15, 0xfe, 0x38, 0x86, 0x03, 0x06, 0xcc, 0x90, 0x8e, 0xf0, 0xd7, 0x21, 0xfd, 0xc8, 0x08, 0x1a, 0xc1, 0x82, 0x13, 0x71, 0x08, 0x19, 0x25, 0x86, 0x75, 0x61, 0x26, 0xe2, 0x8b, 0x40, 0x0e, + 0x45, 0xf1, 0xf0, 0x5d, 0xd7, 0xad, 0x44, 0x03, 0x1b, 0xf3, 0xf1, 0x86, 0x6e, 0x25, 0xfa, 0x97, 0xbe, 0xb1, 0xc1, 0x62, 0x71, 0xc7, 0x4a, 0x0b, 0x44, 0x3c, 0x86, 0x95, 0x6d, 0xbd, 0xe0, 0x08, 0x65, 0xef, 0xe2, 0xe9, 0xc1, 0x05, 0xb6, 0xd2, 0x72, 0x15, 0xe1, 0x93, 0x16, 0x7c, 0x2d, 0x8b, 0x76, 0xfd, 0x6c, 0xe7, 0x92, 0x9b, 0x57, 0xd4, 0x24, 0xa6, 0x9c, 0x5b, 0xd5, 0x31, 0xed, 0xb1, 0xc4, 0xec, 0xcd, 0xdd, 0x03, 0xdf, 0xb8, 0xb8, 0xf3, 0x9c, 0xdb, 0x7e, 0x71, 0xed, 0x63, 0xdb, 0x7f, 0x7e, 0x60, 0x56, 0x6a, 0xe9, 0x8e, 0x59, 0x35, 0x22, 0x23, 0x9a, 0x34, 0x4b, 0x06, 0xab, 0x97, 0xde, 0xb4, 0xe8, 0x5a, 0xd8, 0xbc, 0xf6, 0xc0, 0xe2, 0x99, 0xdb, 0x16, 0xd5, 0xf4, 0x2d, 0x99, 0x73, 0x69, 0x6f, 0xa4, 0x67, 0xc7, 0x89, 0x35, 0x57, 0xfe, 0xf0, 0xe6, 0x3e, 0x28, 0x7d, 0x07, + 0x4e, 0x81, 0xbd, 0x37, 0x7e, 0x7f, 0xe3, 0xc2, 0xaf, 0x5e, 0xbb, 0x2a, 0xd8, 0x6b, 0xf0, 0xba, 0xac, 0x6c, 0xcd, 0x77, 0x2e, 0x9d, 0xb5, 0x73, 0xa0, 0x19, 0x92, 0x39, 0xe1, 0x52, 0xd4, 0x76, 0xb4, 0x37, 0x01, 0x49, 0x7c, 0x1e, 0x1c, 0x0f, 0xf8, 0x59, 0xc8, 0xe8, 0x88, 0x71, 0x22, 0xc0, 0x47, 0x2e, 0xe4, 0x40, 0x98, 0x84, 0x6a, 0xf1, 0x66, 0x4c, 0x55, 0x93, 0xa0, 0xbc, 0xd0, 0x66, 0xf1, 0x09, 0x82, 0x3b, 0x81, 0x4f, 0x88, 0xe4, 0x46, 0x84, 0x01, 0x3e, 0x7d, 0xc1, 0xd7, 0xab, 0x99, 0xe3, 0x17, 0x7c, 0xcd, 0x5c, 0x5b, 0x9f, 0xe2, 0x07, 0x0c, 0x0e, 0x93, 0x66, 0xe1, 0x9d, 0x3f, 0xde, 0x74, 0x4a, 0xfa, 0x9d, 0xf4, 0xf6, 0xf5, 0x06, 0x57, 0x81, 0xae, 0x7a, 0xd1, 0xb5, 0x73, 0x4e, 0xf5, 0x5e, 0x3f, 0xd0, 0x68, 0xb4, 0x5a, 0xf9, 0x90, 0x9d, 0xfd, 0xb8, 0xe3, 0xa2, 0x4d, + 0x17, 0xa5, 0x0f, 0xfc, 0xe8, 0xea, 0xb4, 0x56, 0xfa, 0x36, 0x9c, 0x6a, 0x85, 0xcc, 0x2f, 0x19, 0xa7, 0xd9, 0xe9, 0x33, 0x48, 0xf5, 0xdd, 0x17, 0xcf, 0xa9, 0xd5, 0xf1, 0x86, 0x4f, 0xdf, 0x17, 0xab, 0x16, 0xdf, 0x7a, 0xde, 0xf4, 0x0d, 0x8b, 0xe7, 0x54, 0x44, 0xfc, 0xb8, 0x3f, 0xe7, 0x8c, 0xbc, 0xcb, 0x3f, 0x4c, 0xe4, 0xef, 0x77, 0x54, 0xfe, 0xe0, 0x26, 0x90, 0x81, 0x3f, 0xa9, 0xc0, 0xf9, 0x29, 0xe3, 0xc0, 0xdb, 0xc6, 0x81, 0xb7, 0x67, 0xc3, 0xc9, 0x38, 0xa5, 0xf0, 0x8e, 0x1c, 0xfc, 0x93, 0x2a, 0xbc, 0x69, 0x1c, 0x78, 0x7a, 0x1c, 0xf8, 0x84, 0x71, 0xca, 0xef, 0xcc, 0xc1, 0x3f, 0xae, 0xc2, 0x9b, 0xc7, 0x81, 0xb7, 0xc0, 0x0b, 0xb3, 0xe0, 0x7b, 0x55, 0x78, 0xab, 0x82, 0x8f, 0xf6, 0xf3, 0x0f, 0x13, 0x9b, 0xd4, 0xdf, 0xc9, 0xfb, 0xf8, 0xf9, 0x19, 0xb8, 0xda, 0xde, 0x18, + 0xe5, 0xcf, 0x19, 0x9e, 0xb7, 0x9d, 0xe5, 0x79, 0xfb, 0x58, 0xcf, 0xd5, 0x76, 0xc5, 0x28, 0xdf, 0x46, 0xbd, 0x7f, 0x52, 0x7d, 0xde, 0x74, 0x96, 0xe7, 0xe9, 0xb3, 0x3c, 0x9f, 0x70, 0x96, 0xfa, 0x3b, 0xc7, 0x7c, 0xff, 0xb8, 0xfa, 0xbc, 0xf9, 0x2c, 0xcf, 0x5b, 0xc6, 0x7c, 0xbe, 0x57, 0x7d, 0xde, 0x9a, 0xfd, 0x1c, 0xcd, 0x27, 0x0f, 0x01, 0xc0, 0xed, 0x45, 0x32, 0x65, 0x40, 0x02, 0xd3, 0x94, 0xae, 0xc7, 0xf7, 0xba, 0xf8, 0xa0, 0x77, 0x00, 0xf0, 0x3c, 0xd7, 0x8f, 0x2f, 0xcd, 0xe4, 0xd9, 0x02, 0x10, 0x3f, 0x44, 0x93, 0x11, 0x02, 0xe2, 0x2f, 0xe8, 0x34, 0xfa, 0x4d, 0x7e, 0xbd, 0x16, 0x18, 0xa0, 0x41, 0xa3, 0xb3, 0x27, 0x22, 0x34, 0x3c, 0x03, 0x13, 0x0a, 0x5a, 0xd4, 0x2b, 0xce, 0x20, 0xf1, 0xfd, 0x67, 0x6e, 0x99, 0xb5, 0x69, 0x5a, 0x30, 0x38, 0x75, 0xd3, 0x2c, 0xe9, 0x03, + 0x68, 0xd8, 0xf1, 0xd4, 0x95, 0x2d, 0x93, 0xb6, 0x3f, 0x79, 0xa9, 0x14, 0x60, 0x8f, 0x0c, 0x97, 0x3d, 0x10, 0xee, 0x3c, 0xb7, 0xbe, 0xe9, 0xdc, 0xf6, 0xa0, 0xe4, 0x61, 0x6e, 0x6e, 0x59, 0xb3, 0x77, 0xce, 0xc0, 0xd7, 0x36, 0xb5, 0xf1, 0x87, 0xee, 0x97, 0x26, 0x51, 0xfd, 0x17, 0xd3, 0xe6, 0x22, 0xb1, 0xd3, 0xff, 0x40, 0xe3, 0x76, 0x83, 0x67, 0x08, 0xdc, 0x04, 0x80, 0xa6, 0x48, 0x64, 0x11, 0xfc, 0x03, 0x62, 0xdf, 0x9d, 0x62, 0xae, 0xcf, 0xc0, 0x49, 0x0e, 0x9e, 0x0f, 0x68, 0x0e, 0x1e, 0x19, 0x1e, 0x1d, 0x79, 0x8f, 0x0b, 0x11, 0xf8, 0x87, 0x14, 0x0e, 0x0e, 0x2a, 0xe5, 0xf3, 0xd3, 0x45, 0x06, 0xc1, 0x3f, 0xc2, 0xbe, 0x4a, 0x30, 0x05, 0x2f, 0x42, 0x50, 0x7a, 0x4e, 0x3a, 0x22, 0xfb, 0x2a, 0x51, 0x9c, 0x36, 0x05, 0x07, 0x1c, 0x86, 0x6b, 0xc6, 0xc6, 0x11, 0xbe, 0xae, 0xe2, + 0x7c, 0x7b, 0xbc, 0x72, 0x84, 0x3f, 0xaa, 0x38, 0x8f, 0x2a, 0x38, 0x4c, 0x2e, 0x4e, 0x3b, 0x69, 0xef, 0x1f, 0x69, 0x7b, 0x61, 0x92, 0xdc, 0xd7, 0xf7, 0x8f, 0xfc, 0x81, 0xc7, 0x3e, 0xb9, 0x3a, 0xe0, 0x00, 0x65, 0xd8, 0x42, 0x92, 0x44, 0x42, 0xc2, 0x41, 0xbd, 0x97, 0x93, 0x8b, 0x45, 0x9c, 0x0d, 0x2a, 0xd5, 0xad, 0x58, 0x11, 0xd6, 0xf2, 0x3d, 0x2e, 0xa7, 0x5e, 0x0f, 0x40, 0xb0, 0xd8, 0x59, 0xe6, 0x2a, 0xd3, 0x3b, 0xf4, 0x0e, 0x8b, 0x19, 0xbd, 0xab, 0x0b, 0x6b, 0x45, 0x07, 0x9e, 0x05, 0xb1, 0xb5, 0x5d, 0x43, 0xac, 0x82, 0x24, 0x2c, 0x14, 0x61, 0x0d, 0xe7, 0xb4, 0x3a, 0xec, 0x8c, 0xc0, 0x85, 0x4a, 0xc2, 0x51, 0xd4, 0x89, 0xd6, 0xfa, 0x30, 0xac, 0xd3, 0xce, 0xb8, 0xea, 0xe8, 0xda, 0xb5, 0x47, 0xb7, 0xcd, 0xd0, 0x32, 0x86, 0xde, 0xad, 0x0f, 0xae, 0x5e, 0xf5, 0xe0, 0xb6, + 0x3e, 0x03, 0xfc, 0xdd, 0xdd, 0x50, 0x78, 0x62, 0xf5, 0xea, 0x27, 0xa4, 0x4f, 0xef, 0xfe, 0xaa, 0xf4, 0xc9, 0x77, 0xd7, 0xac, 0xf9, 0x2e, 0x14, 0x99, 0x5d, 0x1b, 0x8f, 0x5f, 0xd1, 0xd9, 0x79, 0xc5, 0xf1, 0x8d, 0x5f, 0x3b, 0xff, 0xc1, 0xcd, 0xed, 0xed, 0x9b, 0x1f, 0x3c, 0x9f, 0xb9, 0xfa, 0x82, 0x53, 0xd2, 0x7b, 0x77, 0x7f, 0x4d, 0x7a, 0xff, 0xd4, 0xba, 0x75, 0xa7, 0xa0, 0xf1, 0x6b, 0x77, 0x43, 0xd3, 0xa9, 0x0b, 0x50, 0xfb, 0x18, 0xd4, 0x0f, 0xef, 0x91, 0x7e, 0x1b, 0xa6, 0xfd, 0x06, 0x24, 0xd2, 0x0f, 0x04, 0x8e, 0xe3, 0xa0, 0x53, 0x38, 0xf8, 0x36, 0xd2, 0x4e, 0x32, 0xf0, 0x8f, 0x55, 0xf8, 0xa3, 0xd9, 0x70, 0x12, 0x6b, 0x9f, 0xc2, 0x9f, 0x92, 0xe1, 0x0b, 0xd0, 0x78, 0xb9, 0x0a, 0xe3, 0xe3, 0xdb, 0x1f, 0x82, 0xff, 0x96, 0xd2, 0xcf, 0xc2, 0x52, 0x01, 0xe9, 0x92, 0x88, + 0xef, 0xd8, 0x27, 0xce, 0xc2, 0xbd, 0x40, 0x9f, 0xc3, 0xcf, 0xc9, 0xf3, 0xda, 0x91, 0x77, 0xb9, 0xdf, 0xe2, 0xbe, 0x81, 0x1a, 0xda, 0x37, 0xe0, 0x91, 0xfc, 0xbe, 0x41, 0xbf, 0x01, 0xbf, 0x9a, 0xd4, 0x29, 0x11, 0x9f, 0x2a, 0x00, 0x7d, 0xa4, 0x6f, 0x70, 0x5c, 0xe1, 0x03, 0x48, 0x4f, 0xd5, 0x01, 0x17, 0x28, 0xc7, 0x51, 0x20, 0x4a, 0xc8, 0x92, 0x0c, 0x64, 0x31, 0x42, 0x3d, 0xc5, 0xd2, 0x0e, 0x5a, 0x95, 0xb9, 0x43, 0x1d, 0xe4, 0x7b, 0x3c, 0x6e, 0xbd, 0x1e, 0xe7, 0xb6, 0x71, 0x97, 0x7b, 0xca, 0xf5, 0x2e, 0xbd, 0xd3, 0x64, 0xd0, 0x8a, 0x40, 0x07, 0x75, 0x5a, 0x22, 0x4c, 0xe4, 0x9a, 0x3d, 0x01, 0x3d, 0x4a, 0xd8, 0x13, 0x26, 0xd4, 0xa0, 0x2c, 0xc1, 0x72, 0xdc, 0x0d, 0x24, 0x58, 0x8e, 0x25, 0xeb, 0xed, 0x55, 0x33, 0x9b, 0x9e, 0xfb, 0x6e, 0x7c, 0x6a, 0xca, 0x6f, 0x4b, 0x76, + 0x35, 0x08, 0x4b, 0x6e, 0x58, 0x50, 0x66, 0x2d, 0xef, 0x6e, 0x1e, 0xba, 0xb2, 0x6d, 0xcd, 0xee, 0x99, 0xd2, 0x85, 0x3b, 0xfb, 0x7b, 0x82, 0x4d, 0x71, 0x97, 0xf4, 0x14, 0xf3, 0x8e, 0xf4, 0xac, 0x3b, 0xd1, 0x16, 0xf6, 0xd5, 0x94, 0xba, 0xaf, 0x89, 0x4c, 0x58, 0x90, 0x0a, 0x4e, 0xeb, 0xac, 0x37, 0xdb, 0xb7, 0x0c, 0x74, 0x6f, 0xe8, 0x0a, 0x23, 0xe2, 0xe5, 0xf8, 0xc8, 0x21, 0xec, 0xa3, 0x00, 0xad, 0x34, 0x46, 0x51, 0x94, 0xca, 0xa0, 0x6b, 0x64, 0x44, 0x08, 0x60, 0x3d, 0x04, 0x3a, 0xa8, 0x4f, 0x06, 0xbc, 0x31, 0x0b, 0xbe, 0x5d, 0x81, 0x83, 0x13, 0x18, 0x8e, 0xef, 0xc5, 0x47, 0xde, 0x63, 0xab, 0x48, 0x2c, 0xa2, 0xeb, 0xd2, 0x3a, 0x23, 0xe4, 0x78, 0x2f, 0x64, 0x19, 0xc5, 0x02, 0x3d, 0x99, 0x31, 0x18, 0xa7, 0x6e, 0x5e, 0x80, 0x07, 0x1c, 0xc3, 0x73, 0xd8, 0xc8, 0x3e, 0x13, + 0xfd, 0x89, 0x68, 0xdb, 0xeb, 0x20, 0x49, 0xf4, 0xf7, 0x85, 0xf1, 0x37, 0x60, 0xef, 0x30, 0x2b, 0x09, 0x61, 0x5a, 0x9e, 0x28, 0x0b, 0x05, 0x8b, 0x0a, 0x9d, 0xf6, 0x12, 0x0d, 0x36, 0x73, 0xc9, 0x58, 0x2a, 0xa8, 0x79, 0xad, 0xd0, 0x52, 0xaf, 0xdc, 0xb3, 0x63, 0xfe, 0xe2, 0xd3, 0x22, 0xd5, 0xb6, 0x81, 0x5e, 0xc3, 0x4f, 0x6f, 0xba, 0xef, 0xa2, 0xc5, 0x7b, 0x96, 0x57, 0xdb, 0xe2, 0x93, 0x53, 0x4b, 0xd6, 0x77, 0xae, 0xbf, 0x79, 0x66, 0xd5, 0xca, 0x15, 0x0b, 0x42, 0x8d, 0x46, 0xb7, 0xad, 0xac, 0xe5, 0x9c, 0xf6, 0xba, 0x73, 0x9a, 0x8a, 0x36, 0x5f, 0x7e, 0xe1, 0x85, 0xeb, 0xb6, 0x78, 0xeb, 0x7a, 0x6b, 0x9b, 0xfb, 0x1a, 0x23, 0x46, 0x93, 0x4d, 0xcf, 0xee, 0xa9, 0x9f, 0x94, 0xec, 0x5e, 0x56, 0x5b, 0xd4, 0xd9, 0x9a, 0xb2, 0xf8, 0xb7, 0x2e, 0x99, 0xba, 0xa9, 0x37, 0x6e, 0x8d, + 0xb6, 0x26, 0xa6, 0x59, 0x8b, 0xc3, 0xc5, 0xde, 0x8a, 0xf6, 0xc8, 0xb4, 0x79, 0xd7, 0xcc, 0x4a, 0x17, 0xa6, 0x62, 0x4e, 0x57, 0xa1, 0x4b, 0x6f, 0x21, 0xfc, 0x6a, 0x92, 0xee, 0x64, 0x1b, 0x11, 0xbf, 0xfa, 0xc0, 0x7b, 0x69, 0xab, 0x11, 0x8a, 0x9a, 0x72, 0x28, 0xf0, 0x6e, 0xb4, 0xaf, 0x09, 0xe1, 0xd8, 0x3a, 0x32, 0xdf, 0x1a, 0x81, 0x4e, 0xab, 0xd1, 0xea, 0x34, 0x59, 0x21, 0xb3, 0xd0, 0x14, 0xae, 0x11, 0x07, 0x80, 0x56, 0xab, 0xf0, 0x41, 0x10, 0x28, 0xdf, 0x88, 0x79, 0x74, 0x5e, 0x40, 0xaf, 0x2f, 0xf1, 0xfe, 0x86, 0xdc, 0xf7, 0xd3, 0x1d, 0xa3, 0x5e, 0x65, 0xf1, 0xe5, 0x1c, 0x73, 0xc6, 0x22, 0x40, 0xa6, 0x04, 0x9c, 0x99, 0x6e, 0x66, 0xcf, 0xf4, 0xa9, 0x9d, 0x1d, 0xed, 0xad, 0xf5, 0xb5, 0xd5, 0x95, 0xb1, 0x48, 0xa0, 0xc8, 0xef, 0x43, 0x7d, 0xa3, 0x1f, 0xa7, 0x6f, 0xa2, + 0x31, 0x47, 0xb6, 0xe1, 0x00, 0xdd, 0xc4, 0x9f, 0xad, 0xbb, 0x66, 0x8d, 0xee, 0xae, 0x55, 0xd5, 0x55, 0xe9, 0x40, 0xa5, 0xd1, 0xe0, 0x15, 0xcc, 0xb2, 0x6f, 0x25, 0xfc, 0xf2, 0xbd, 0x67, 0xb1, 0xf7, 0x10, 0xa7, 0xca, 0x5e, 0x9b, 0x15, 0x3b, 0x5e, 0x3e, 0x0a, 0xc7, 0xef, 0x4a, 0x24, 0x13, 0x64, 0xec, 0x13, 0x99, 0x70, 0x52, 0xbf, 0xb6, 0x42, 0x39, 0x76, 0xe7, 0xc8, 0x08, 0xfb, 0x19, 0x91, 0x21, 0x97, 0xec, 0xd7, 0xf4, 0x64, 0x16, 0xfc, 0x88, 0x02, 0x07, 0x0f, 0x62, 0x38, 0x8e, 0x25, 0x8e, 0xe1, 0x48, 0x2c, 0xbd, 0x20, 0x9c, 0x0e, 0xda, 0x05, 0x06, 0x5f, 0xbc, 0xca, 0x01, 0xe5, 0xa1, 0x7a, 0x19, 0xe7, 0x05, 0x1e, 0x4b, 0xdc, 0xc2, 0xe3, 0x04, 0xef, 0xf8, 0xc4, 0xb3, 0xbe, 0x41, 0xb9, 0x7e, 0xc5, 0x1b, 0x4c, 0x41, 0x24, 0x09, 0x38, 0x52, 0xec, 0x67, 0xa7, 0x39, 0x4e, + 0x67, 0xd3, 0x49, 0x49, 0x7d, 0xd8, 0xb5, 0xf9, 0x14, 0xc7, 0xd9, 0x6d, 0xf0, 0x39, 0x7d, 0xc4, 0x85, 0x53, 0x1e, 0x31, 0x07, 0xed, 0xd5, 0x1e, 0x47, 0xc4, 0x34, 0xbc, 0xdd, 0x1a, 0x60, 0x3f, 0x19, 0x5e, 0x6d, 0xaf, 0x76, 0x97, 0xfb, 0x98, 0xad, 0x96, 0x40, 0x16, 0x6d, 0x5d, 0x2a, 0x6d, 0x8f, 0xe5, 0xd0, 0xbc, 0x5d, 0x85, 0x9f, 0xc8, 0xc0, 0xb1, 0x8f, 0x13, 0x9a, 0x63, 0xdd, 0xb2, 0xff, 0xd9, 0x6e, 0x35, 0x0f, 0x04, 0x60, 0x3f, 0x03, 0x61, 0x7c, 0x9e, 0xeb, 0x37, 0x0b, 0x2c, 0x64, 0x21, 0x0e, 0x8c, 0x83, 0x86, 0x11, 0x18, 0xe2, 0x71, 0x8a, 0xc6, 0x55, 0xdd, 0x99, 0x1b, 0xc6, 0x30, 0x08, 0x05, 0xdd, 0x31, 0x8b, 0x6c, 0xbe, 0x81, 0x5b, 0x45, 0x9a, 0x84, 0x2f, 0x98, 0x69, 0xd8, 0xfa, 0x2c, 0x4f, 0x33, 0xdc, 0x3e, 0x1e, 0x1c, 0x13, 0x45, 0xe9, 0x88, 0xb6, 0xd0, 0xe6, + 0xb4, 0xfa, 0xb5, 0xd2, 0xfd, 0x82, 0xe6, 0xd8, 0x3f, 0xd8, 0x4b, 0x4d, 0xb0, 0xcc, 0xe8, 0xd4, 0xe9, 0x1c, 0x46, 0x18, 0x31, 0x95, 0xda, 0xd9, 0xcf, 0x3e, 0xe7, 0x99, 0xeb, 0x8a, 0x7a, 0x02, 0xc3, 0xf7, 0x59, 0x6c, 0x36, 0x0b, 0xb3, 0x28, 0x30, 0xa3, 0x78, 0xf8, 0x32, 0xf6, 0x33, 0xab, 0x57, 0xd2, 0xb9, 0xeb, 0x7d, 0xbe, 0x7a, 0x37, 0xfc, 0xc8, 0x67, 0x91, 0x69, 0x65, 0x5f, 0x25, 0x39, 0xa8, 0x70, 0x46, 0x36, 0x0e, 0x41, 0x38, 0xb8, 0x9c, 0xdc, 0x82, 0x66, 0xd3, 0xa8, 0x07, 0x7a, 0x92, 0x31, 0x83, 0xf8, 0x1b, 0xc8, 0xb9, 0x80, 0xb1, 0x63, 0x3e, 0xfb, 0xea, 0x69, 0xa9, 0xf2, 0xd4, 0x29, 0xf8, 0x33, 0xcc, 0x5c, 0xf8, 0x73, 0xa9, 0x9c, 0xfd, 0x58, 0x1a, 0x82, 0x37, 0x23, 0xde, 0xcc, 0x44, 0xe5, 0x6e, 0x27, 0x3c, 0x8b, 0xd0, 0x71, 0xb1, 0x22, 0x93, 0x4b, 0x22, 0x4c, + 0xe0, 0x65, 0xf2, 0x1c, 0xda, 0x41, 0x73, 0x71, 0xa1, 0x71, 0xb4, 0x85, 0xc0, 0x13, 0x14, 0xbf, 0x84, 0xe2, 0x23, 0x22, 0xd1, 0xde, 0x1e, 0x8f, 0x97, 0x20, 0xf1, 0xe7, 0x1a, 0xb9, 0x80, 0xfa, 0x4f, 0x74, 0x8d, 0xfc, 0x01, 0xce, 0x42, 0x3a, 0x9c, 0x11, 0x04, 0x09, 0xe5, 0xaa, 0x6d, 0x67, 0xb7, 0x92, 0x56, 0x6e, 0x1d, 0x8b, 0x28, 0x2f, 0x2a, 0xb4, 0x5b, 0x11, 0x8e, 0xd1, 0x49, 0xad, 0x38, 0xb1, 0xd0, 0x85, 0xf0, 0x3d, 0x34, 0x9b, 0xb5, 0xc5, 0xc3, 0xe2, 0x06, 0x93, 0x9c, 0xa0, 0x13, 0xae, 0x15, 0x74, 0x22, 0x17, 0xb7, 0x87, 0xaa, 0x0a, 0x0b, 0xab, 0xc2, 0x76, 0x7b, 0x18, 0x7f, 0x86, 0xec, 0xfc, 0xa1, 0xe1, 0xd3, 0xc9, 0x79, 0xf3, 0x17, 0xa5, 0x52, 0x8b, 0xe6, 0xcf, 0x4b, 0x32, 0xad, 0x9f, 0xfe, 0xbb, 0xaf, 0x2a, 0xec, 0x70, 0x84, 0xab, 0x7c, 0x32, 0x16, 0xa6, 0x73, + 0xbb, 0xf4, 0x7b, 0xb8, 0x8b, 0x7d, 0x09, 0x8d, 0x85, 0x86, 0x10, 0xd6, 0xab, 0x9a, 0x81, 0x97, 0x79, 0x8d, 0xea, 0x43, 0xcc, 0x6b, 0xf2, 0xfd, 0xf3, 0x85, 0xec, 0x31, 0x78, 0x1e, 0x5a, 0x11, 0x2d, 0x40, 0x38, 0xce, 0x03, 0x58, 0x95, 0xb0, 0x55, 0xa0, 0x0e, 0x6d, 0x70, 0x39, 0x05, 0xd1, 0xc4, 0x38, 0xf1, 0xc7, 0xf7, 0xca, 0x8b, 0xdc, 0x7d, 0x13, 0x5a, 0x2d, 0x9c, 0xd6, 0x13, 0x31, 0xd8, 0x03, 0x26, 0x83, 0x9d, 0x3d, 0x36, 0xfd, 0x6b, 0xff, 0x70, 0x73, 0x53, 0x44, 0x5f, 0xac, 0x2b, 0xea, 0x5f, 0x33, 0x54, 0xef, 0x0a, 0x6b, 0x08, 0x5f, 0x90, 0xb4, 0x71, 0x53, 0xf9, 0xcf, 0x41, 0x0c, 0xa6, 0x47, 0x4e, 0x72, 0x6f, 0x43, 0x01, 0x5c, 0xb0, 0x0a, 0xfb, 0xba, 0x03, 0xf8, 0x6d, 0xee, 0x6d, 0x59, 0xb7, 0x5a, 0xc9, 0x1e, 0x86, 0x8b, 0xf8, 0xcf, 0x11, 0x35, 0x69, 0xb4, 0xfe, + 0xbf, 0x9d, 0xfd, 0x14, 0xc8, 0xcf, 0x99, 0x7d, 0xb4, 0x0c, 0xfc, 0x1c, 0xe3, 0xc1, 0x69, 0x08, 0xcf, 0xaa, 0xe2, 0x61, 0xf2, 0x95, 0x17, 0x70, 0xbc, 0xff, 0x7b, 0x61, 0x10, 0xd1, 0xcf, 0x02, 0x1b, 0x3e, 0x3b, 0x85, 0x60, 0x13, 0x9a, 0xf9, 0xd6, 0xc0, 0x1e, 0x4b, 0x01, 0x83, 0x0d, 0x50, 0x53, 0x96, 0xd0, 0xce, 0x83, 0x07, 0x05, 0xf8, 0xf1, 0x3d, 0x38, 0x5e, 0xa0, 0x74, 0x2f, 0xbb, 0x05, 0xe1, 0x46, 0xe1, 0xd4, 0x5f, 0x60, 0xf9, 0x9f, 0xca, 0x54, 0x52, 0xff, 0x75, 0x46, 0xc7, 0x3f, 0xc6, 0x1c, 0x44, 0x65, 0xb8, 0xd3, 0x68, 0x15, 0x45, 0xb2, 0x3d, 0x97, 0xf4, 0x1d, 0x7a, 0x04, 0x67, 0xc8, 0x05, 0x21, 0x1d, 0x7a, 0x2b, 0xdf, 0xf4, 0xe9, 0xbf, 0x30, 0x07, 0xf7, 0x90, 0x77, 0x76, 0xb1, 0x37, 0xf0, 0x27, 0x39, 0x27, 0x7a, 0x27, 0x49, 0x73, 0x4a, 0xe8, 0xb3, 0x0c, 0x3d, + 0xc8, 0x31, 0x6e, 0x6e, 0x31, 0xfd, 0x8f, 0x64, 0x0a, 0xda, 0xc5, 0x37, 0x7e, 0xfa, 0x03, 0xce, 0xb9, 0x07, 0x97, 0x03, 0xa4, 0x57, 0x59, 0xfb, 0xc8, 0xc0, 0x17, 0xb0, 0x13, 0xe5, 0x1d, 0xc1, 0x3a, 0xd6, 0xfe, 0xf9, 0xdb, 0x77, 0x5d, 0x73, 0xcd, 0x9f, 0xf2, 0x1e, 0x58, 0xca, 0xda, 0xc1, 0xab, 0x5f, 0xe0, 0xbd, 0x88, 0xfc, 0xde, 0xab, 0x7f, 0x5a, 0x7d, 0xff, 0xc3, 0xef, 0xad, 0x85, 0xc7, 0xe1, 0xfd, 0xcc, 0xfb, 0xc0, 0xa6, 0xf6, 0xe5, 0x7c, 0x40, 0xa3, 0xb3, 0x00, 0xd8, 0x5b, 0x1a, 0x66, 0x44, 0xd4, 0x05, 0x79, 0x72, 0xb6, 0xd6, 0x93, 0x68, 0x0a, 0x04, 0x5b, 0xca, 0x3d, 0x9e, 0xf2, 0x96, 0x60, 0xa0, 0x29, 0xe1, 0x61, 0x66, 0x2b, 0xbf, 0x82, 0x4d, 0xf8, 0xb3, 0x09, 0xbd, 0x7c, 0x11, 0x5b, 0x0d, 0x1f, 0xe6, 0x36, 0x8e, 0x5b, 0xae, 0x6e, 0x74, 0xb9, 0x17, 0xa1, 0x72, + 0x83, 0xc1, 0x96, 0x84, 0xc7, 0x93, 0xc0, 0x25, 0x25, 0x3c, 0xec, 0x4f, 0xf3, 0xcb, 0xe5, 0xc0, 0xa4, 0x91, 0x77, 0x85, 0x95, 0xfc, 0x43, 0x40, 0x0b, 0x1a, 0x40, 0x37, 0x5a, 0xc0, 0xff, 0x90, 0xd6, 0xf9, 0x91, 0xe0, 0x18, 0xa1, 0x56, 0x54, 0xb2, 0x6b, 0x36, 0x00, 0x5e, 0x60, 0xd0, 0xf4, 0x37, 0x64, 0x80, 0x8c, 0x28, 0x30, 0xe2, 0x7a, 0x34, 0x45, 0xb1, 0x3a, 0x91, 0x45, 0x4b, 0x3e, 0xd0, 0xe9, 0xb5, 0xba, 0x41, 0xa2, 0x85, 0xc9, 0x06, 0xb8, 0x48, 0x9d, 0x45, 0x6a, 0xae, 0x92, 0xec, 0x04, 0x07, 0x61, 0x68, 0x39, 0xeb, 0xeb, 0xe4, 0x45, 0x39, 0x56, 0xe0, 0xf9, 0xb9, 0x45, 0x20, 0xbd, 0xe3, 0x4b, 0xbe, 0x4d, 0xc2, 0x33, 0xe9, 0xf5, 0x24, 0xcf, 0x3b, 0x8e, 0x78, 0xd3, 0x9f, 0xf6, 0xeb, 0x75, 0xe7, 0x2e, 0x98, 0xdd, 0x37, 0x6d, 0x4a, 0x7b, 0x2b, 0xd6, 0x3b, 0x4a, 0x02, + 0x3e, 0x8f, 0xc5, 0xac, 0x6b, 0xd0, 0x37, 0x84, 0x4a, 0x8c, 0xd8, 0x5a, 0x8f, 0x44, 0x57, 0x23, 0x06, 0x4b, 0x35, 0xc5, 0x79, 0x6e, 0x62, 0xb9, 0xe1, 0x05, 0x89, 0xde, 0xd1, 0xd0, 0x0e, 0x61, 0x9e, 0x15, 0xb9, 0xa8, 0x9a, 0xbe, 0x60, 0x85, 0x85, 0xfb, 0xa5, 0xbe, 0xd0, 0xbb, 0xe7, 0xb3, 0x5f, 0xeb, 0x8b, 0x1c, 0xb7, 0xc0, 0x7b, 0xf5, 0xda, 0xd6, 0xe4, 0xf6, 0x85, 0xb3, 0xb7, 0x2f, 0xaa, 0xae, 0x5b, 0xb6, 0x63, 0xd6, 0xac, 0x2b, 0xe3, 0x35, 0xbc, 0xd1, 0x60, 0xf0, 0x84, 0xeb, 0xe3, 0x15, 0x33, 0x1a, 0x8a, 0x83, 0x4d, 0x33, 0xca, 0xbd, 0xf1, 0x70, 0x71, 0x81, 0x60, 0xe0, 0x0c, 0xf5, 0x03, 0xbb, 0xe6, 0xcc, 0xd9, 0x35, 0x50, 0xaf, 0x7c, 0xde, 0x51, 0xdc, 0x34, 0x3b, 0x95, 0x9a, 0xdd, 0x54, 0xbc, 0x79, 0xf5, 0xea, 0xcd, 0xfc, 0x15, 0x46, 0xdb, 0xa7, 0xdb, 0x4d, 0x16, + 0x76, 0xbb, 0x76, 0x42, 0x53, 0x4b, 0xe3, 0xe0, 0xce, 0x59, 0xb3, 0x77, 0x0d, 0x36, 0xd4, 0x27, 0xa6, 0x8a, 0x3a, 0x8b, 0xc3, 0x12, 0x6c, 0x5f, 0xd4, 0xdc, 0xbc, 0xb0, 0x3d, 0xc8, 0x1b, 0x6c, 0x26, 0x5e, 0x18, 0xfe, 0x4d, 0xdf, 0x0d, 0x2b, 0x9b, 0x9a, 0x56, 0xde, 0xd0, 0xd7, 0xb7, 0x13, 0x7f, 0xee, 0xec, 0x6b, 0x5a, 0xd8, 0x51, 0x52, 0xd2, 0xb1, 0xb0, 0x69, 0xd3, 0xd5, 0x57, 0xa3, 0x01, 0x73, 0x2b, 0x78, 0x8d, 0x3d, 0xc4, 0x2d, 0x01, 0x05, 0x68, 0xce, 0x35, 0x68, 0xd1, 0x9c, 0x0b, 0x05, 0xd1, 0xe9, 0xe2, 0x8b, 0xa0, 0x1f, 0xc2, 0x3a, 0x48, 0x1c, 0x6b, 0x9f, 0x29, 0xab, 0xad, 0x2d, 0x83, 0x87, 0x1d, 0x96, 0xcf, 0x3f, 0x62, 0x51, 0x05, 0xcc, 0x49, 0x87, 0xc1, 0xe0, 0x78, 0x41, 0x5f, 0x68, 0xd9, 0x0d, 0x0f, 0xf4, 0xc1, 0x03, 0xbb, 0x2d, 0x85, 0x7a, 0x7a, 0x4f, 0xfb, + 0x36, 0x2a, 0xeb, 0x23, 0x54, 0x96, 0x09, 0x95, 0x65, 0x62, 0x70, 0x59, 0x0e, 0x58, 0x57, 0xdb, 0x10, 0xa9, 0x6f, 0x88, 0xc6, 0x22, 0xd1, 0x36, 0xc8, 0x7e, 0xf4, 0xf9, 0x47, 0x51, 0x78, 0xa4, 0xb4, 0xae, 0xae, 0x54, 0x9a, 0x13, 0x65, 0xfe, 0xb5, 0x4f, 0x3a, 0x7f, 0x77, 0xd8, 0xfe, 0x02, 0x29, 0xce, 0x1e, 0xa6, 0x77, 0x93, 0x68, 0x5f, 0x2b, 0x6c, 0x13, 0xdb, 0xd0, 0xbc, 0xf8, 0x06, 0xd9, 0x77, 0x7d, 0x13, 0xee, 0x24, 0xfb, 0x2e, 0x26, 0x6b, 0xdf, 0x9c, 0x8f, 0x73, 0x78, 0x5c, 0x9c, 0x90, 0x82, 0x03, 0x7f, 0x0e, 0xbf, 0x32, 0x36, 0x8e, 0xf0, 0xae, 0x5a, 0xce, 0x23, 0xf0, 0xc0, 0x68, 0x1c, 0x69, 0x1f, 0xc2, 0x89, 0x53, 0x1c, 0xe2, 0x67, 0x7e, 0x1b, 0xa1, 0xf3, 0xab, 0x23, 0xff, 0x25, 0x1c, 0xc1, 0x67, 0x00, 0xf0, 0x4d, 0x99, 0xce, 0xee, 0x51, 0xfb, 0xfb, 0x7c, 0x9c, + 0xc3, 0xe3, 0xe2, 0x84, 0x14, 0x1c, 0x44, 0x67, 0xff, 0xd8, 0x38, 0xc2, 0xbb, 0x6a, 0x39, 0x8f, 0xc0, 0xf9, 0xa3, 0x71, 0x10, 0x9d, 0x47, 0x84, 0x38, 0xc5, 0x21, 0x74, 0xf6, 0xab, 0x31, 0xf6, 0xdf, 0x24, 0xfa, 0xc1, 0x5b, 0xb2, 0x0e, 0xf6, 0xe0, 0x98, 0xf0, 0x07, 0x73, 0xe0, 0xa7, 0x15, 0x38, 0x7c, 0x2e, 0x2b, 0x56, 0xff, 0x9b, 0xf8, 0xcc, 0x48, 0xc6, 0x3f, 0xae, 0xe0, 0x4b, 0xfb, 0xb8, 0x37, 0xb9, 0x7d, 0x2a, 0xfe, 0xeb, 0x32, 0x7c, 0xeb, 0xc8, 0xbb, 0xbc, 0x9f, 0xff, 0x21, 0x82, 0xff, 0x96, 0xd0, 0x7c, 0x04, 0xec, 0x05, 0x45, 0x40, 0x83, 0x68, 0x3e, 0x56, 0xa4, 0xc4, 0x59, 0xcd, 0xc7, 0x79, 0x70, 0x5c, 0x9c, 0x5f, 0x2b, 0x38, 0x88, 0x9e, 0x9b, 0xc7, 0xc1, 0xf9, 0xa6, 0x5a, 0xce, 0xf1, 0xb1, 0xca, 0x91, 0xf6, 0xf1, 0x7e, 0x42, 0xe7, 0x6f, 0xa9, 0xdf, 0xfb, 0x15, + 0x64, 0xcc, 0x0e, 0x1f, 0x15, 0xaf, 0x65, 0x66, 0x6b, 0xdc, 0x67, 0x5a, 0x6f, 0x21, 0x5a, 0x26, 0x87, 0x8f, 0x72, 0xf7, 0x7e, 0xb6, 0x48, 0xe3, 0xde, 0x4f, 0xde, 0x11, 0x04, 0x66, 0xb6, 0x70, 0xc7, 0x17, 0x7b, 0x47, 0xb8, 0x63, 0xff, 0x7f, 0xb3, 0x1e, 0xd8, 0x25, 0x38, 0xe1, 0xf3, 0xe2, 0x8f, 0xe8, 0xba, 0x5e, 0x91, 0xbd, 0xae, 0xaf, 0x3d, 0xe3, 0xba, 0x0e, 0x91, 0xa6, 0x01, 0xbb, 0x76, 0xef, 0x16, 0x7f, 0x24, 0x75, 0x91, 0x72, 0xb8, 0x83, 0xf0, 0x79, 0x61, 0xe6, 0x19, 0xeb, 0x96, 0x5f, 0x11, 0x66, 0x4a, 0xc4, 0x4e, 0xf5, 0xcf, 0x54, 0x37, 0x03, 0x1e, 0x17, 0x9c, 0xcc, 0x6a, 0x52, 0x8e, 0x08, 0x6a, 0x69, 0x49, 0x46, 0x96, 0xac, 0x9d, 0x58, 0x7b, 0x26, 0x65, 0xd9, 0xb0, 0x9d, 0x24, 0x2a, 0x0c, 0x03, 0x48, 0x0a, 0x66, 0x5c, 0xdc, 0xa3, 0x38, 0xcc, 0x15, 0x36, 0x50, + 0xc5, 0x6a, 0x13, 0x2e, 0xf4, 0xf1, 0x9b, 0x6e, 0x82, 0xc1, 0x9b, 0x6e, 0xc2, 0xe5, 0xc2, 0x47, 0xe5, 0xb2, 0xb9, 0x83, 0xcc, 0x6a, 0xd2, 0x2e, 0x11, 0xf8, 0xd3, 0xde, 0x31, 0x8a, 0x19, 0xa7, 0x10, 0xdc, 0x4a, 0x52, 0xc8, 0xdf, 0x3c, 0x7d, 0x23, 0xcf, 0x73, 0xbb, 0x99, 0x0b, 0xd1, 0x1e, 0x4a, 0x0b, 0x1c, 0x20, 0xa2, 0xc4, 0xeb, 0xe6, 0x59, 0x8e, 0x05, 0xe0, 0x7c, 0xd8, 0xa3, 0xd3, 0x41, 0xa0, 0x73, 0xe8, 0x1c, 0xd6, 0x02, 0x81, 0x03, 0x5a, 0xa8, 0xa5, 0xfe, 0x24, 0xd4, 0x6f, 0x28, 0xc6, 0x86, 0xd0, 0x7a, 0x24, 0x7f, 0x85, 0xb7, 0x87, 0x42, 0xc6, 0xeb, 0x67, 0xb9, 0x8a, 0x8c, 0x3f, 0x78, 0xc4, 0xe2, 0xb1, 0x9a, 0x35, 0xc7, 0x17, 0xbb, 0xfc, 0xa6, 0x0f, 0xf9, 0x23, 0x2e, 0xd7, 0x7b, 0x46, 0x0b, 0x64, 0x04, 0x83, 0x45, 0x4f, 0xbe, 0x60, 0x9f, 0xac, 0xfb, 0x04, 0x1d, + 0xb3, 0x47, 0xf4, 0xa1, 0x3a, 0x6d, 0xd8, 0xa7, 0x86, 0x67, 0x50, 0x6d, 0x58, 0xc9, 0x20, 0x5f, 0xb0, 0x0d, 0x33, 0xa6, 0xbd, 0x57, 0x8f, 0xea, 0x2e, 0x30, 0xe9, 0x6c, 0x7a, 0xdb, 0xe8, 0xba, 0x2b, 0x61, 0x48, 0xfd, 0x06, 0xef, 0x0e, 0x25, 0x4c, 0xb7, 0x2e, 0x71, 0xf9, 0x0a, 0x7e, 0xf2, 0x66, 0x28, 0x69, 0x79, 0x7c, 0xad, 0x2b, 0x68, 0xf9, 0x4c, 0xf4, 0xb9, 0x5c, 0xbf, 0x29, 0x30, 0xbd, 0xef, 0x76, 0xfe, 0xc6, 0x6c, 0x7e, 0x1f, 0xf1, 0x6a, 0x2a, 0x6a, 0xe7, 0xbf, 0x0b, 0xb7, 0x90, 0xd3, 0x4c, 0xec, 0xc7, 0xa3, 0x1c, 0xea, 0x30, 0x38, 0x82, 0x2a, 0xd2, 0x13, 0x21, 0xb1, 0xac, 0x47, 0xfa, 0x72, 0xb8, 0xd0, 0x6b, 0x29, 0x30, 0xe8, 0x04, 0x1e, 0x38, 0xa0, 0x43, 0x54, 0x02, 0xd3, 0xa5, 0xe4, 0x15, 0x19, 0x7d, 0x31, 0x31, 0xa3, 0xb2, 0x90, 0xde, 0xbc, 0xf0, 0xca, 0x99, + 0xa1, 0x5d, 0x8e, 0x8a, 0xe9, 0xa9, 0xd4, 0xf4, 0x0a, 0xc7, 0xae, 0xd0, 0xcc, 0x2b, 0x63, 0xf6, 0x50, 0x12, 0x69, 0x3d, 0x41, 0xbb, 0x3d, 0x88, 0xb4, 0x9f, 0x64, 0xc8, 0xce, 0xed, 0x4e, 0x74, 0xaf, 0x68, 0x0c, 0xd4, 0xc7, 0x1c, 0x8e, 0x58, 0x7d, 0xa0, 0x71, 0x45, 0x77, 0x02, 0xde, 0xe0, 0x29, 0x2f, 0xb1, 0xdb, 0x4b, 0xd0, 0xf3, 0x0a, 0xfc, 0x59, 0x41, 0xef, 0xda, 0x90, 0xfe, 0xf1, 0x9c, 0x86, 0x41, 0x9c, 0x21, 0xd6, 0xc9, 0x3c, 0x76, 0xed, 0x40, 0x3b, 0x57, 0x92, 0x78, 0x07, 0xa9, 0x3d, 0x03, 0x88, 0x6c, 0x86, 0x9b, 0x4f, 0x7c, 0x6a, 0x04, 0xec, 0xfd, 0xd1, 0xeb, 0xb0, 0x07, 0x03, 0x45, 0x85, 0x5e, 0x8f, 0x3d, 0xe2, 0x88, 0x94, 0x86, 0x45, 0x2c, 0x22, 0x79, 0xe4, 0xb1, 0xb4, 0x01, 0x24, 0xad, 0x0e, 0x26, 0x9f, 0xb1, 0xe6, 0x13, 0x77, 0xcd, 0xc2, 0xcb, 0x7b, 0x82, + 0x5b, 0x97, 0xac, 0x5e, 0xbd, 0x64, 0x6b, 0xb0, 0xe7, 0x72, 0xf1, 0x3a, 0x4f, 0x12, 0x3f, 0x43, 0x38, 0x15, 0xf8, 0xb3, 0x42, 0x5a, 0x55, 0x36, 0x6d, 0x59, 0x63, 0x67, 0x4f, 0x4f, 0x67, 0xe3, 0xb2, 0x69, 0x65, 0x74, 0x2d, 0xbe, 0x44, 0xf0, 0x30, 0x0f, 0x88, 0x65, 0x88, 0x9f, 0xf1, 0x74, 0x0c, 0x67, 0x7a, 0x84, 0x17, 0x72, 0x38, 0x4e, 0x07, 0x98, 0xcf, 0x11, 0x3b, 0x7c, 0xd4, 0x14, 0xa6, 0x17, 0x6d, 0xb5, 0x54, 0x56, 0xf2, 0x8a, 0x2b, 0x52, 0x48, 0xd1, 0x60, 0xf0, 0x35, 0x1f, 0xbc, 0xb9, 0xf7, 0xfc, 0x09, 0xfe, 0x5d, 0xc6, 0x60, 0x43, 0x3c, 0xde, 0x10, 0x34, 0xee, 0xf2, 0x4f, 0x38, 0x5f, 0x2c, 0x4b, 0x74, 0x0f, 0x34, 0x28, 0x9c, 0x6a, 0x18, 0xe8, 0x26, 0x7b, 0xb7, 0x4b, 0xb8, 0x3b, 0x99, 0x07, 0xf8, 0xc3, 0xa8, 0xbe, 0x70, 0x3a, 0x48, 0xeb, 0x23, 0x3e, 0x8c, 0xc4, + 0xfb, 0x06, 0x5b, 0x68, 0xff, 0x37, 0x6a, 0xe2, 0x0f, 0x8f, 0xae, 0xe9, 0x7f, 0x79, 0xdb, 0x60, 0x98, 0x7f, 0x96, 0xb1, 0x89, 0x7b, 0x80, 0x07, 0xd4, 0x1c, 0xf7, 0x40, 0x30, 0x95, 0xaa, 0xdd, 0x7a, 0x59, 0xbf, 0x87, 0x6b, 0x01, 0x99, 0x7c, 0x55, 0x77, 0xc7, 0x85, 0x98, 0xbe, 0xde, 0xfe, 0x47, 0xc2, 0x5d, 0x64, 0xf2, 0x25, 0xb9, 0x98, 0xda, 0xd9, 0x06, 0xa1, 0x84, 0xe6, 0x65, 0xc2, 0xe6, 0xe4, 0x58, 0x85, 0x9b, 0x65, 0xd0, 0xf0, 0x1a, 0xb7, 0xc7, 0x85, 0xfe, 0xe5, 0x6b, 0xd3, 0xe9, 0xfa, 0xd6, 0xb6, 0xf9, 0x7d, 0xfc, 0x6f, 0xf5, 0x26, 0xde, 0x28, 0x68, 0x7d, 0xe1, 0x84, 0x07, 0x4e, 0xaa, 0xab, 0xec, 0x9c, 0x33, 0xa1, 0xf7, 0xb6, 0x89, 0x84, 0x06, 0x6e, 0x16, 0x63, 0x13, 0x0a, 0x10, 0x0d, 0x85, 0x69, 0x0f, 0x26, 0x22, 0xbf, 0xbe, 0xff, 0x46, 0x6d, 0xdc, 0xf9, 0xda, + 0x82, 0xb1, 0x6a, 0xfb, 0xab, 0xd4, 0xf7, 0x7f, 0x90, 0xc7, 0x26, 0xfe, 0x59, 0x38, 0x8c, 0xda, 0xec, 0xc3, 0xeb, 0xb9, 0x5b, 0x59, 0xcf, 0x17, 0xe6, 0xae, 0xe7, 0x59, 0xdb, 0xc8, 0xfe, 0x47, 0xa6, 0x44, 0x08, 0x01, 0xc4, 0x7f, 0x9a, 0xd4, 0x8a, 0x66, 0xad, 0x38, 0x8c, 0x15, 0xb1, 0x2e, 0x7c, 0x2c, 0x2b, 0xc2, 0x47, 0xfa, 0xe6, 0xb5, 0xb5, 0xd6, 0x75, 0x5a, 0x8a, 0x13, 0x1e, 0x5e, 0xcb, 0x8b, 0x6e, 0xaf, 0x5b, 0xc3, 0x8b, 0x7a, 0xfe, 0xd9, 0x89, 0xb7, 0xf5, 0x4e, 0x98, 0xd3, 0x59, 0xe9, 0x4d, 0x06, 0xac, 0xd0, 0x93, 0x08, 0xfb, 0xb4, 0x82, 0x91, 0xc7, 0x11, 0x56, 0x10, 0x0d, 0xdc, 0x2c, 0x38, 0x8c, 0xda, 0xec, 0xc3, 0xab, 0xa1, 0x6d, 0x8c, 0x36, 0x7b, 0xdd, 0x93, 0x18, 0x7c, 0x0e, 0x95, 0x5b, 0x95, 0x59, 0xa5, 0x01, 0x0e, 0xab, 0xb5, 0x79, 0x3c, 0xb8, 0xb6, 0x42, + 0x4a, 0x03, 0x7f, 0x77, 0x4e, 0x6d, 0x66, 0x9d, 0x97, 0x12, 0xf1, 0xb7, 0xd1, 0x6e, 0x70, 0x50, 0xd2, 0x30, 0x9b, 0x46, 0x7e, 0x84, 0xb4, 0x80, 0x92, 0x74, 0x31, 0x87, 0x56, 0x0b, 0xa4, 0x11, 0x2c, 0x26, 0x21, 0x25, 0xe8, 0xf9, 0x10, 0x51, 0xa8, 0x45, 0xa4, 0x0b, 0xe0, 0x93, 0x5b, 0xde, 0x12, 0xaa, 0x0b, 0xa2, 0xbf, 0xcc, 0xa6, 0x1b, 0xa5, 0x87, 0x60, 0x6f, 0xad, 0xd4, 0x7a, 0xcf, 0xd6, 0x7b, 0xe8, 0xbc, 0x97, 0x29, 0x07, 0x8d, 0x19, 0x7c, 0x9b, 0xc4, 0xc0, 0xc5, 0xd8, 0xc5, 0xb1, 0x9f, 0xda, 0xbd, 0x2a, 0xca, 0x04, 0xd2, 0x23, 0x43, 0x96, 0x94, 0x23, 0xc8, 0x6c, 0x92, 0x1e, 0xba, 0xf1, 0x46, 0xd8, 0x2b, 0x1d, 0xbe, 0x07, 0x9e, 0xbe, 0x07, 0xbf, 0x2f, 0x70, 0xcc, 0x26, 0xf1, 0x07, 0x68, 0x6d, 0xf5, 0xa4, 0x49, 0xf0, 0x8b, 0x0b, 0x15, 0x1f, 0x9a, 0xb5, 0x38, 0x7d, + 0x31, 0x31, 0xec, 0x83, 0x21, 0x0b, 0xaa, 0x9d, 0x56, 0xfe, 0xa2, 0xc0, 0xc1, 0xc9, 0xdb, 0xe4, 0xba, 0xb9, 0xeb, 0x98, 0x4d, 0x42, 0x17, 0x7a, 0xb7, 0x28, 0xed, 0xa3, 0xef, 0x62, 0xe7, 0xd6, 0xb9, 0x72, 0x33, 0x18, 0xac, 0xcb, 0x8c, 0x2e, 0x80, 0xbb, 0x8e, 0x16, 0xf0, 0xd7, 0xad, 0xfb, 0x56, 0xc4, 0xb7, 0x2d, 0x84, 0x6f, 0xd8, 0xd7, 0x9e, 0x81, 0xcc, 0x26, 0x8e, 0x65, 0xa8, 0x53, 0x3b, 0x8b, 0x8f, 0x73, 0xce, 0xc5, 0x48, 0x33, 0x73, 0x7a, 0x01, 0x3a, 0x82, 0xc4, 0x46, 0x90, 0xd9, 0x22, 0x79, 0xbf, 0xb9, 0x5b, 0xba, 0x6e, 0xeb, 0x56, 0x38, 0x45, 0xfa, 0x4e, 0x4e, 0x1f, 0x04, 0xd2, 0x7e, 0x3c, 0x62, 0x2e, 0x04, 0xb8, 0x3f, 0xe7, 0x92, 0x7e, 0x60, 0xf0, 0xd9, 0x9f, 0xaa, 0xd4, 0xf1, 0x34, 0x40, 0x2b, 0xa2, 0xe7, 0xfe, 0xdd, 0xf7, 0xc3, 0x17, 0x8f, 0x4b, 0xdf, 0x25, + 0x04, 0x65, 0xca, 0xd0, 0x93, 0x32, 0x38, 0x1c, 0x21, 0x69, 0x31, 0x71, 0xa8, 0x26, 0x3a, 0x27, 0xed, 0x49, 0x35, 0x95, 0x34, 0xcd, 0x7c, 0x55, 0x97, 0x72, 0xa4, 0x48, 0x39, 0xe8, 0x3f, 0xd4, 0x9f, 0x68, 0x48, 0xa8, 0x63, 0xe2, 0x30, 0x2a, 0xab, 0x4c, 0x39, 0x73, 0x04, 0xca, 0x98, 0xa2, 0xda, 0xb8, 0x0b, 0x6d, 0x29, 0x0e, 0xc2, 0x17, 0xa5, 0xe8, 0x2f, 0xb6, 0x22, 0xdc, 0xf5, 0xd2, 0x0d, 0x4c, 0xe7, 0xc8, 0xdb, 0xc0, 0x89, 0xcf, 0xb1, 0x74, 0xf8, 0x56, 0x9f, 0xa8, 0xa9, 0x9b, 0xb2, 0x12, 0xa4, 0xc7, 0x23, 0x84, 0x91, 0x7c, 0x6a, 0x54, 0x9e, 0x8f, 0xfa, 0x3a, 0x78, 0x74, 0x5f, 0x51, 0xeb, 0xa2, 0xb6, 0xee, 0x8b, 0x82, 0x8b, 0x74, 0x26, 0x4e, 0x67, 0x76, 0x04, 0x7c, 0xe5, 0x29, 0xfb, 0xda, 0xea, 0x85, 0x93, 0xe3, 0x3d, 0x73, 0xc3, 0x1c, 0x74, 0x78, 0x1d, 0xed, 0x0d, + 0x98, 0xa6, 0x67, 0x50, 0x3d, 0xff, 0x40, 0xea, 0x41, 0x1a, 0xab, 0x81, 0xfa, 0xbe, 0xb1, 0x90, 0xdd, 0x84, 0xd9, 0xb5, 0x09, 0xd0, 0x15, 0x9a, 0x19, 0x62, 0x7a, 0x1c, 0xe1, 0x54, 0x09, 0x61, 0x54, 0x5e, 0xac, 0x5a, 0x7c, 0xbb, 0x93, 0x82, 0x8b, 0x6b, 0x13, 0xbe, 0x80, 0xa3, 0x40, 0xc7, 0x19, 0x75, 0x8b, 0x83, 0x17, 0x77, 0xb7, 0x2f, 0x6c, 0xf5, 0xdf, 0x7a, 0x65, 0x43, 0x3b, 0xaa, 0x05, 0x72, 0xe1, 0xb9, 0x3d, 0xf1, 0xc9, 0x0b, 0xab, 0xd7, 0xd2, 0xb3, 0xee, 0x93, 0x52, 0x35, 0xb3, 0x64, 0xe4, 0x28, 0xc9, 0x4f, 0xd9, 0x41, 0xad, 0xf9, 0x6c, 0x8a, 0xb7, 0xbd, 0xd2, 0xb6, 0xf3, 0x20, 0x49, 0x71, 0xaf, 0x42, 0xb1, 0x63, 0x36, 0x50, 0xfd, 0xb2, 0xfb, 0x1f, 0x0d, 0x47, 0x4b, 0xc3, 0x44, 0x13, 0xcf, 0x3b, 0x38, 0x6b, 0xc8, 0x4a, 0x77, 0x78, 0x32, 0xd8, 0xd8, 0x55, 0x16, + 0xef, 0x6e, 0x2a, 0x29, 0x69, 0xea, 0x8e, 0x97, 0x75, 0x35, 0x06, 0xd7, 0xd5, 0x56, 0x26, 0xea, 0xeb, 0x13, 0x95, 0xb5, 0xd2, 0xef, 0x13, 0x53, 0x6b, 0x0a, 0x0b, 0x6b, 0xa6, 0x26, 0xe2, 0x53, 0xaa, 0x0b, 0x0b, 0xab, 0xa7, 0xa0, 0x75, 0xbe, 0x21, 0x9e, 0xac, 0xab, 0xa3, 0x7b, 0x29, 0xa9, 0x0e, 0x3e, 0x3f, 0xf2, 0x3b, 0xba, 0x97, 0x2a, 0x52, 0x96, 0x9a, 0x7e, 0x85, 0xaa, 0x71, 0xf7, 0x52, 0xbc, 0xbc, 0x97, 0x72, 0xd0, 0x3d, 0x19, 0xa8, 0x91, 0x8e, 0x30, 0x2f, 0x8c, 0x7c, 0x1b, 0x8d, 0x3d, 0x2c, 0x43, 0x48, 0x27, 0xc7, 0xb7, 0xfd, 0x99, 0x21, 0x23, 0x3b, 0x54, 0xd6, 0x61, 0xf1, 0x0f, 0x32, 0x2f, 0xec, 0x90, 0x06, 0x77, 0x30, 0xf7, 0x49, 0x47, 0x6e, 0xbf, 0x1d, 0x1a, 0xc0, 0x9f, 0xe9, 0xfd, 0x67, 0xc9, 0xfb, 0x01, 0x70, 0xe1, 0x09, 0x11, 0xbf, 0x2c, 0x1f, 0x0a, 0x3a, + 0x45, 0x01, 0x09, 0x12, 0xcb, 0x32, 0xab, 0xc8, 0x28, 0x5e, 0x04, 0x69, 0x91, 0xbe, 0xee, 0x63, 0x85, 0x38, 0x85, 0x32, 0x7d, 0xca, 0x6c, 0xca, 0x7d, 0x98, 0xf6, 0x65, 0xc1, 0x51, 0xb7, 0xcc, 0x57, 0x1f, 0xb3, 0xa0, 0x4f, 0x36, 0xa2, 0x0c, 0x80, 0x40, 0xc8, 0x16, 0xb2, 0x85, 0x89, 0x11, 0x25, 0x4f, 0x8c, 0x28, 0xa3, 0xd4, 0x86, 0xb2, 0x0d, 0xe7, 0x8f, 0x77, 0xa2, 0xc1, 0x49, 0xbf, 0xb8, 0x9c, 0xf5, 0x6d, 0x90, 0x79, 0x61, 0xed, 0x9c, 0xea, 0x1e, 0x8f, 0xa7, 0xa7, 0x7a, 0xce, 0xda, 0x1b, 0x83, 0x25, 0xc5, 0x81, 0xdd, 0x81, 0xe2, 0x92, 0xe0, 0x34, 0xe7, 0xba, 0xd5, 0xe1, 0x60, 0x30, 0xbc, 0x7a, 0x9d, 0xd3, 0x59, 0x96, 0x84, 0x30, 0x19, 0x73, 0x3a, 0x63, 0xf8, 0xb3, 0xcc, 0x49, 0xdb, 0x74, 0xb7, 0xcc, 0x13, 0x34, 0x72, 0x4c, 0x78, 0xe4, 0xe0, 0x39, 0x01, 0x0d, 0x8c, + 0x55, 0x58, 0xc5, 0x3b, 0x17, 0xc8, 0xc4, 0x5a, 0x09, 0x14, 0x6e, 0xca, 0x02, 0xf6, 0xa7, 0x0d, 0x79, 0x13, 0x77, 0x10, 0xcf, 0xba, 0x75, 0xcc, 0x0b, 0xc3, 0xe7, 0xee, 0x80, 0x87, 0xa6, 0x49, 0x6f, 0x41, 0xf7, 0x7d, 0xf7, 0xe1, 0xf1, 0xa9, 0xcc, 0x3f, 0x2c, 0x5d, 0x01, 0xf0, 0x84, 0x41, 0x72, 0xc7, 0xe3, 0x6a, 0xe6, 0xca, 0xd3, 0x20, 0x64, 0xb2, 0xe6, 0x0d, 0x47, 0x0a, 0xed, 0x91, 0x82, 0xf0, 0x93, 0xb7, 0xde, 0xda, 0x26, 0x79, 0xe1, 0x1b, 0xdf, 0x7c, 0x6e, 0x0b, 0x3c, 0x8d, 0x68, 0xad, 0x97, 0x7e, 0xcc, 0xfc, 0x7c, 0xe4, 0x51, 0xe0, 0x06, 0xfd, 0x69, 0xa3, 0x0b, 0x71, 0xca, 0x6a, 0xc1, 0x57, 0xe0, 0x24, 0x98, 0x1b, 0xe6, 0xb4, 0x85, 0xe3, 0x19, 0x39, 0xab, 0xfb, 0x80, 0x62, 0xb0, 0xea, 0xc5, 0x45, 0x5f, 0x88, 0x1f, 0xe0, 0x75, 0x0e, 0x7f, 0xaa, 0x1b, 0xb8, 0xfe, + 0xb4, 0x05, 0x02, 0xa7, 0xbd, 0xc0, 0xa4, 0xd7, 0x89, 0x3c, 0x70, 0x43, 0x37, 0xde, 0xc1, 0xf1, 0xf8, 0xa4, 0x33, 0x1a, 0x32, 0x41, 0x3f, 0x1a, 0x09, 0xb5, 0xd1, 0x58, 0x09, 0x75, 0xeb, 0x77, 0xd5, 0x33, 0xbe, 0x69, 0x03, 0xa6, 0x1b, 0x84, 0x56, 0xc2, 0xd6, 0x56, 0xe1, 0x06, 0xd3, 0xc0, 0xb4, 0x81, 0x54, 0xaa, 0xab, 0x67, 0x56, 0x78, 0x12, 0xac, 0xc0, 0x3c, 0xad, 0x80, 0x93, 0xc2, 0xb3, 0x7a, 0x26, 0x4f, 0xa6, 0x63, 0xe5, 0x6e, 0xfe, 0x43, 0xe6, 0x3c, 0xf1, 0xfb, 0x68, 0x8e, 0x13, 0x8e, 0x6b, 0xc8, 0x59, 0xa0, 0x4b, 0x8c, 0x89, 0x31, 0x9c, 0x07, 0xad, 0xc1, 0x05, 0xff, 0xed, 0x40, 0xdf, 0x01, 0xe5, 0x3f, 0xe1, 0x45, 0xfa, 0x6d, 0xff, 0xac, 0x03, 0x07, 0x66, 0xd1, 0x77, 0xb9, 0x4b, 0x98, 0xf3, 0x84, 0xce, 0x2f, 0xf0, 0x2e, 0xbf, 0x68, 0xd4, 0xbb, 0x7f, 0x9d, 0x7a, + 0x61, 0x90, 0xdb, 0xc1, 0x70, 0xc2, 0x2f, 0x91, 0x9e, 0x81, 0x7b, 0x98, 0x74, 0x30, 0xea, 0x04, 0x96, 0x61, 0xd8, 0xf9, 0x68, 0x69, 0xc0, 0x33, 0x1e, 0xcb, 0xf4, 0x5a, 0x7c, 0x16, 0x0f, 0x11, 0xb2, 0x5a, 0x79, 0xf7, 0xe7, 0xa0, 0xd6, 0x92, 0x64, 0xb2, 0xa9, 0x6b, 0x87, 0xf0, 0xf2, 0xc2, 0x92, 0xc2, 0x60, 0xf3, 0xec, 0x9a, 0x44, 0xbc, 0x30, 0x88, 0xc3, 0x8f, 0x55, 0x97, 0x97, 0xf2, 0x93, 0x9d, 0x95, 0xf5, 0x6d, 0xe1, 0xd2, 0xc9, 0xd5, 0x85, 0x97, 0xc1, 0x48, 0xd0, 0x59, 0x55, 0xdf, 0x1e, 0x2a, 0x9b, 0x54, 0xe5, 0xbb, 0x04, 0x5f, 0x1c, 0xa2, 0x7a, 0xf9, 0xdf, 0x33, 0x9c, 0xa6, 0xe0, 0xcf, 0x5f, 0xaf, 0x70, 0xaf, 0x33, 0x12, 0x4f, 0x85, 0x4b, 0x27, 0x24, 0x3d, 0x97, 0x41, 0x5f, 0xd0, 0x19, 0x8d, 0xd7, 0x86, 0xca, 0x3a, 0x93, 0xae, 0x4b, 0x20, 0x59, 0x82, 0xa7, 0xf1, + 0x0f, 0x31, 0x2f, 0x89, 0xdb, 0x41, 0x09, 0x98, 0x90, 0xee, 0xf0, 0x7b, 0x19, 0x8e, 0xd5, 0x6b, 0xb0, 0x46, 0xd2, 0x55, 0xe8, 0x63, 0xd8, 0x69, 0x38, 0x8e, 0xcb, 0x54, 0xbc, 0x1f, 0xe6, 0xe6, 0xe3, 0xbb, 0x52, 0x32, 0xd3, 0x0c, 0x10, 0xd3, 0x6a, 0x34, 0xdd, 0x70, 0x00, 0xc7, 0x56, 0x2f, 0x01, 0x25, 0xd1, 0xb0, 0x35, 0x16, 0x26, 0x57, 0xa6, 0x4a, 0x80, 0x3c, 0x9c, 0x2b, 0x06, 0xfb, 0xeb, 0x84, 0xe4, 0x33, 0xf7, 0x06, 0x97, 0x48, 0x0c, 0xa4, 0x99, 0x97, 0x7c, 0x55, 0x93, 0xca, 0x02, 0x13, 0x02, 0x03, 0x8b, 0x96, 0x5a, 0x83, 0xa6, 0x60, 0x04, 0x5e, 0xe8, 0xab, 0x99, 0x58, 0x16, 0x9a, 0x1c, 0x1c, 0x5c, 0xb4, 0x34, 0xe8, 0x2e, 0x89, 0x40, 0x51, 0x53, 0x33, 0xbb, 0x39, 0x60, 0x37, 0xdd, 0x99, 0xda, 0xaf, 0xd1, 0x56, 0x25, 0xaa, 0xcf, 0x69, 0x0e, 0xba, 0x4c, 0x77, 0xd6, + 0x1c, 0x30, 0x55, 0x94, 0x93, 0xbe, 0x9d, 0xc6, 0xa5, 0x98, 0x97, 0xd0, 0x1e, 0xad, 0x04, 0xc7, 0x34, 0x18, 0x8f, 0x5e, 0x42, 0x1c, 0x0e, 0x69, 0xc4, 0x2e, 0x24, 0x57, 0xbc, 0xd4, 0x97, 0xff, 0xcf, 0x4c, 0x29, 0xbf, 0xab, 0x66, 0x76, 0x4b, 0xc0, 0x91, 0x47, 0x69, 0x6a, 0x3f, 0xa6, 0xf4, 0xef, 0x8f, 0xaf, 0x7e, 0xc9, 0xc1, 0x75, 0x8e, 0x5c, 0x04, 0xcc, 0xa0, 0x29, 0xad, 0x33, 0x1b, 0x18, 0x38, 0x4d, 0xe4, 0x18, 0xbc, 0xa5, 0x2a, 0xcc, 0x3e, 0x33, 0x3c, 0x2f, 0xb3, 0xa5, 0x9a, 0x0b, 0x32, 0x71, 0x84, 0x1e, 0x71, 0x39, 0x88, 0xae, 0xed, 0xb4, 0x8b, 0x3c, 0x5a, 0x76, 0x22, 0xd1, 0xfa, 0x06, 0x7c, 0xd3, 0xc7, 0x85, 0x8a, 0x8a, 0xa4, 0x0f, 0xa5, 0x0a, 0xe9, 0x23, 0xec, 0x0e, 0xaf, 0x83, 0xff, 0x4f, 0x7a, 0xbb, 0x20, 0x65, 0xde, 0xb1, 0x72, 0xe5, 0x4e, 0xf4, 0x71, 0xed, + 0xea, 0xd5, 0x78, 0xdc, 0x5f, 0x35, 0xfc, 0x3a, 0xf3, 0xf2, 0xc8, 0xc3, 0x40, 0x0b, 0x26, 0xd2, 0xba, 0xcc, 0x00, 0x9f, 0x5d, 0x81, 0xc5, 0x44, 0xa5, 0x5b, 0x83, 0xd7, 0x55, 0x2f, 0x99, 0xb3, 0x71, 0xd0, 0xc0, 0x21, 0xfc, 0x0f, 0x59, 0xef, 0x71, 0xcd, 0x0c, 0x8e, 0x60, 0x84, 0xe7, 0x6e, 0x2d, 0xd0, 0x5a, 0x15, 0x75, 0x0f, 0x69, 0x59, 0xb6, 0x7a, 0xa7, 0xcb, 0xc6, 0x5c, 0x72, 0xeb, 0xca, 0x1b, 0x70, 0x35, 0xd2, 0x0d, 0xf7, 0xdc, 0x03, 0xf5, 0x88, 0x00, 0xe9, 0x43, 0x4c, 0xec, 0xc7, 0xfc, 0xc7, 0x4c, 0xb9, 0xf8, 0x34, 0xd2, 0x41, 0x85, 0xe3, 0x3c, 0xc4, 0x73, 0x03, 0x99, 0x18, 0x98, 0xf2, 0xd7, 0xfa, 0x0e, 0x1e, 0xec, 0x13, 0x7e, 0xf5, 0x5a, 0xdf, 0xfe, 0xfd, 0xb3, 0x30, 0x1e, 0x77, 0x19, 0x53, 0x2e, 0x4c, 0x1c, 0x07, 0x8f, 0x5f, 0xa2, 0xe2, 0x7d, 0xc1, 0xf2, 0xde, + 0x13, 0x0a, 0xe0, 0xf3, 0xe2, 0xe3, 0xc0, 0x42, 0x74, 0x7e, 0xb2, 0x75, 0x58, 0xaa, 0xe8, 0x34, 0x6b, 0x99, 0x1e, 0xbb, 0xc5, 0x4d, 0x97, 0x6d, 0xe5, 0x3c, 0xc9, 0x41, 0x43, 0x1d, 0xc2, 0xe7, 0x53, 0x73, 0x5a, 0x83, 0xc1, 0xd6, 0x39, 0xa9, 0x44, 0x63, 0x63, 0x42, 0x28, 0x28, 0x6a, 0xec, 0xad, 0xaa, 0x9a, 0xd9, 0x58, 0xb4, 0xba, 0xb1, 0xbe, 0x9e, 0xe8, 0x68, 0xef, 0x71, 0xb7, 0xc2, 0xe7, 0x85, 0x16, 0x54, 0x2e, 0x9e, 0x37, 0x70, 0xae, 0x28, 0xc8, 0x0c, 0x90, 0xf2, 0xe7, 0xcb, 0x71, 0x5f, 0x58, 0xd0, 0xfb, 0xc5, 0x4a, 0xe7, 0x6e, 0xcd, 0x2b, 0x1d, 0xfc, 0xc5, 0xe8, 0x66, 0x40, 0x27, 0xff, 0x1a, 0xf3, 0xb2, 0x78, 0x27, 0xd2, 0xf5, 0x62, 0x60, 0x31, 0x0d, 0xb2, 0x58, 0x98, 0x73, 0x08, 0x07, 0x51, 0x3d, 0xf2, 0xe9, 0x1b, 0xb3, 0x16, 0x5b, 0x2b, 0x95, 0xa8, 0x8f, 0x11, + 0x08, 0xc9, 0x0e, 0xc3, 0x0d, 0x90, 0x59, 0x32, 0x7b, 0x6f, 0x9f, 0xb6, 0x38, 0xec, 0xa1, 0x12, 0x72, 0x4c, 0x17, 0x73, 0xc4, 0xc6, 0x39, 0xa6, 0xa3, 0xc7, 0x3a, 0x51, 0xf5, 0x54, 0x87, 0xd1, 0xd8, 0x82, 0xe5, 0x1e, 0x77, 0x22, 0x68, 0xb3, 0x05, 0x13, 0x6e, 0x4f, 0x79, 0xd0, 0xb6, 0xe5, 0xdc, 0xad, 0xb3, 0x22, 0xdb, 0x56, 0x9e, 0x5f, 0xd7, 0x9b, 0xf2, 0x6c, 0x8d, 0xcc, 0xda, 0x26, 0x2c, 0x74, 0x97, 0x07, 0x6c, 0xb6, 0x40, 0xb9, 0xdb, 0x43, 0xb1, 0x60, 0x2c, 0x39, 0x6b, 0xc3, 0x84, 0x85, 0x1b, 0xfc, 0x75, 0xdd, 0xc9, 0x09, 0x1b, 0x66, 0x25, 0xa9, 0xfe, 0xda, 0xc9, 0xad, 0x64, 0x5e, 0x16, 0xfc, 0xa4, 0x4d, 0xd9, 0x67, 0x8a, 0xe3, 0xd2, 0xfb, 0x17, 0x21, 0x96, 0x7b, 0xc9, 0x5d, 0x8e, 0x9f, 0xe6, 0x13, 0xbb, 0xde, 0x5f, 0xab, 0x12, 0xfb, 0xbf, 0x91, 0xff, 0x10, 0xbc, + 0x3c, 0xfc, 0x47, 0xc6, 0x3a, 0xf2, 0x14, 0x91, 0x45, 0x4e, 0x59, 0xf7, 0xd1, 0x8a, 0x7f, 0xd9, 0xc1, 0xbe, 0xd7, 0x5e, 0xeb, 0x93, 0x7e, 0xb9, 0xbf, 0xef, 0xb5, 0xd7, 0xfb, 0x10, 0xb5, 0xba, 0xe1, 0x8f, 0x98, 0xf6, 0x91, 0xef, 0x8f, 0x92, 0xd9, 0x76, 0x22, 0xb3, 0xd2, 0x2f, 0x5f, 0x9f, 0xb5, 0x7f, 0x7f, 0x1f, 0xe2, 0x91, 0x6d, 0xf8, 0x23, 0x36, 0x40, 0xf0, 0xf4, 0x40, 0x73, 0x42, 0x87, 0x8d, 0xb5, 0xaa, 0x12, 0xc5, 0x04, 0x19, 0xe2, 0xb9, 0xd8, 0x35, 0xf0, 0x1a, 0x7e, 0x83, 0xf9, 0x1e, 0x42, 0x7f, 0xed, 0xb5, 0xce, 0xd7, 0xb1, 0xac, 0x4b, 0x3f, 0xbd, 0xfd, 0xf6, 0x59, 0xaf, 0xbd, 0x26, 0xdf, 0xbd, 0x84, 0x99, 0x0a, 0xf1, 0xc7, 0x48, 0x76, 0xda, 0x69, 0x3a, 0x4d, 0x2b, 0x35, 0x31, 0x58, 0xaa, 0xb8, 0x6b, 0xac, 0xa5, 0x9b, 0x19, 0xc5, 0x2c, 0x1b, 0x0b, 0xec, 0x7c, + 0x39, 0x09, 0x2d, 0x60, 0xd1, 0x66, 0xc6, 0x6d, 0xb1, 0x53, 0xe9, 0xa2, 0x3b, 0x97, 0x3a, 0x65, 0x3f, 0x03, 0xbb, 0xb0, 0x54, 0x29, 0x12, 0x26, 0xdc, 0xd8, 0x50, 0x5f, 0xdf, 0xb8, 0xba, 0xa8, 0x71, 0x66, 0x55, 0x55, 0x6f, 0x63, 0x11, 0xe5, 0xc5, 0x16, 0xee, 0x3e, 0xb8, 0x48, 0xe8, 0x95, 0xe7, 0x85, 0x71, 0x6a, 0x50, 0xca, 0xb7, 0xe5, 0x95, 0xbf, 0x25, 0xd1, 0xd4, 0xa4, 0x16, 0xcf, 0x1b, 0x73, 0x8b, 0xc7, 0xf7, 0x50, 0xf7, 0x31, 0x15, 0x5f, 0xb4, 0xec, 0x33, 0xd3, 0x9e, 0x5f, 0xf8, 0x5f, 0x9d, 0x6f, 0xe7, 0x49, 0x1b, 0xe1, 0xbf, 0x8d, 0xbc, 0x03, 0x2a, 0xc1, 0xc4, 0xb4, 0xad, 0xb2, 0x22, 0x51, 0x1a, 0x0a, 0x14, 0xbb, 0x5d, 0xb6, 0x02, 0xbd, 0x28, 0x98, 0x68, 0x48, 0xbb, 0xf0, 0x17, 0x58, 0x10, 0x4f, 0xc4, 0xa2, 0x45, 0xf8, 0x04, 0x8c, 0xa7, 0xd1, 0xdf, 0x44, 0xa4, + 0x54, 0xc7, 0x9c, 0x44, 0x13, 0xc7, 0xb6, 0x7c, 0x66, 0x88, 0xb7, 0xe3, 0x0d, 0xd1, 0x4a, 0x6c, 0x13, 0xe5, 0xa2, 0x7b, 0x20, 0x41, 0xac, 0xef, 0x80, 0x2e, 0x78, 0x83, 0xd1, 0x6b, 0xf2, 0xbb, 0x67, 0x84, 0x66, 0x14, 0xb8, 0xb4, 0xa6, 0x69, 0xbc, 0xa8, 0x75, 0x9a, 0xd0, 0x0f, 0xb3, 0xcf, 0x5c, 0xe4, 0x9b, 0x1e, 0x9a, 0xee, 0xb7, 0x62, 0x98, 0xb5, 0x70, 0x46, 0xe8, 0x49, 0x8d, 0x8e, 0x61, 0x2d, 0xe5, 0x6d, 0xe5, 0x5a, 0xbb, 0xa9, 0xb0, 0xa4, 0x7f, 0x81, 0xa5, 0xd0, 0x68, 0xb0, 0x6a, 0x92, 0xed, 0xe5, 0x04, 0x9e, 0x68, 0x8b, 0x17, 0xf8, 0x0a, 0x4b, 0xce, 0xed, 0xb7, 0xf8, 0x4d, 0xee, 0x82, 0xf2, 0x56, 0xc4, 0x96, 0xaf, 0x4b, 0x7b, 0x18, 0x69, 0xe4, 0x39, 0x34, 0x92, 0x5d, 0xa0, 0x01, 0x4c, 0x49, 0x4f, 0xac, 0x83, 0x90, 0x43, 0x4a, 0x0c, 0x60, 0xba, 0x44, 0x1d, 0x4e, + 0xaf, 0x81, 0x0d, 0xe2, 0x06, 0xb4, 0x8c, 0x86, 0xc5, 0x86, 0xcc, 0x38, 0x9a, 0xfe, 0x10, 0xb6, 0x64, 0x36, 0xa2, 0x6d, 0x7f, 0xaa, 0xba, 0x2c, 0x16, 0x0a, 0xba, 0x1b, 0x3c, 0x0d, 0x36, 0x8b, 0xc1, 0x65, 0x74, 0x8d, 0x1d, 0xcb, 0x31, 0x94, 0xf5, 0x3d, 0x92, 0x15, 0x3e, 0x2c, 0xd7, 0xf8, 0xa2, 0x1d, 0x32, 0x33, 0x8b, 0xc3, 0xe1, 0xe2, 0x40, 0x38, 0xfc, 0xab, 0x40, 0x28, 0x14, 0x40, 0x9f, 0x30, 0x1a, 0x6d, 0x4f, 0xb8, 0x5c, 0x89, 0xf6, 0x68, 0xd5, 0x24, 0x9b, 0x7d, 0x62, 0x55, 0xb4, 0xbd, 0xdc, 0xed, 0x2e, 0x47, 0xbf, 0x26, 0xda, 0x6d, 0x93, 0xa4, 0xdf, 0x87, 0xfd, 0xfe, 0x70, 0xf6, 0xff, 0xaf, 0x7b, 0xca, 0x5b, 0x4a, 0x4a, 0x5a, 0xcb, 0x3d, 0xb1, 0x40, 0x20, 0xe6, 0x29, 0x6f, 0x2d, 0x29, 0x69, 0x29, 0xf7, 0x44, 0x03, 0x81, 0x28, 0xed, 0xbf, 0x67, 0xf8, 0x1f, 0x31, + 0x97, 0x88, 0xc3, 0xc0, 0x08, 0xe6, 0xd0, 0x38, 0x9a, 0x45, 0x80, 0xec, 0x68, 0xc9, 0xde, 0x7c, 0x35, 0x0e, 0x61, 0x0d, 0xd9, 0xb9, 0x24, 0xda, 0xf7, 0xb9, 0x38, 0x9c, 0xd7, 0x4c, 0xec, 0x3c, 0x85, 0x37, 0xaf, 0xe4, 0x38, 0x6a, 0xf4, 0x63, 0xaa, 0x60, 0x18, 0x81, 0x31, 0x68, 0x0b, 0x12, 0x05, 0x03, 0xc7, 0xb2, 0xee, 0x80, 0x21, 0x1b, 0x6a, 0x8c, 0x19, 0x32, 0x97, 0x6c, 0x59, 0xe1, 0x30, 0x69, 0x26, 0x7e, 0x7a, 0xc9, 0x0a, 0xa7, 0x51, 0x9c, 0x28, 0x06, 0xa5, 0xc3, 0x6e, 0xdb, 0x43, 0x70, 0xae, 0xc7, 0xfa, 0x10, 0xa1, 0x85, 0x9b, 0xc9, 0x5c, 0x22, 0x6c, 0x43, 0xaf, 0x4f, 0xa3, 0xb4, 0xb8, 0xb3, 0x69, 0xc1, 0xea, 0x69, 0x3f, 0xae, 0x6a, 0x00, 0x8f, 0x66, 0x67, 0x86, 0x8a, 0xec, 0x07, 0x5f, 0xaa, 0x7e, 0xfe, 0x9e, 0xbc, 0xfa, 0xff, 0x86, 0x78, 0x31, 0x5b, 0xaa, 0xd1, + 0xb4, 0x8e, 0xac, 0x47, 0x32, 0xdd, 0xf1, 0xa8, 0xd1, 0x20, 0xf0, 0x1c, 0xe6, 0x09, 0xd6, 0xf7, 0x0c, 0x8c, 0x92, 0xe9, 0x8a, 0x1c, 0xa2, 0x58, 0xe9, 0xed, 0xea, 0x7c, 0x05, 0x4a, 0xf6, 0xb7, 0x7a, 0x08, 0x74, 0x1a, 0x60, 0x81, 0x16, 0x96, 0x0c, 0x3a, 0xec, 0xa3, 0x26, 0x70, 0xc1, 0x40, 0x38, 0xd6, 0x50, 0xc4, 0x72, 0x53, 0xf7, 0x0d, 0xbf, 0xf9, 0xe2, 0x50, 0xed, 0xb3, 0x92, 0x5e, 0x7a, 0x16, 0xfd, 0xd5, 0x3f, 0x9b, 0x1a, 0x7a, 0xf1, 0xcd, 0x61, 0xe9, 0xb4, 0x33, 0x91, 0x2e, 0xdb, 0x3e, 0x6f, 0x60, 0x60, 0xde, 0xf6, 0xb2, 0x74, 0xc2, 0x49, 0xe2, 0x15, 0x08, 0x26, 0x4d, 0xab, 0xd8, 0xc5, 0x8a, 0x4c, 0x0c, 0x6d, 0xf8, 0x7f, 0x45, 0xed, 0x04, 0xc5, 0x5f, 0xa9, 0x76, 0x82, 0xb3, 0xb9, 0x9b, 0x35, 0xad, 0xfc, 0x8f, 0x20, 0x79, 0x2e, 0x2c, 0xa1, 0xcf, 0x85, 0x25, 0x99, + 0xe7, 0xf4, 0x7d, 0x38, 0xde, 0xfb, 0x6b, 0x84, 0xb9, 0x9a, 0xa8, 0xf0, 0x16, 0x2a, 0x3f, 0x3e, 0xf6, 0x73, 0xee, 0x17, 0x9a, 0x28, 0x7f, 0x1e, 0x24, 0xcf, 0xc7, 0x28, 0x5f, 0x7e, 0x1f, 0x8e, 0xf9, 0x3e, 0x62, 0xc5, 0x1a, 0xe9, 0x52, 0x4d, 0x74, 0xc4, 0x40, 0xcf, 0x9f, 0x0a, 0xbf, 0xf8, 0xf9, 0x13, 0x3e, 0x3d, 0x5c, 0x23, 0x3e, 0xf9, 0xf1, 0x84, 0x0f, 0xf6, 0x91, 0x72, 0x36, 0x0a, 0xa2, 0xc6, 0x2f, 0x0e, 0x82, 0x22, 0xd0, 0x4f, 0xa3, 0xc1, 0x7a, 0xf1, 0xd1, 0x0d, 0xe4, 0xc8, 0x1e, 0x04, 0x00, 0x72, 0xed, 0xa9, 0xec, 0x44, 0x7c, 0xe9, 0x52, 0xc0, 0xb3, 0x90, 0xe5, 0x21, 0x36, 0xd4, 0x47, 0x5b, 0x2b, 0xa4, 0x1d, 0xa8, 0xb8, 0x8c, 0x82, 0x0b, 0x30, 0x6a, 0x7f, 0x5a, 0x6b, 0x8d, 0x84, 0xad, 0x11, 0x1a, 0x53, 0x99, 0x78, 0xca, 0x72, 0x59, 0x6e, 0x86, 0x1c, 0xb9, 0x17, + 0xd5, 0xf8, 0x7f, 0xfd, 0xbb, 0x35, 0x8f, 0x3e, 0x70, 0xf7, 0xbd, 0xdf, 0x5c, 0x7e, 0xfb, 0x9e, 0xaf, 0x36, 0x6e, 0xfa, 0xe6, 0x66, 0xf2, 0xeb, 0xc8, 0xf2, 0xdb, 0xf7, 0x7e, 0x55, 0x9c, 0x7e, 0xc7, 0x3f, 0x16, 0x25, 0x6b, 0x93, 0x45, 0x73, 0x17, 0xce, 0xd8, 0x72, 0x4e, 0xa2, 0x28, 0x59, 0x87, 0xbf, 0xd2, 0xb6, 0x6f, 0xe4, 0x76, 0x6a, 0xfc, 0xfc, 0x1b, 0x88, 0xe6, 0x21, 0x4a, 0xb3, 0x3d, 0x67, 0x9b, 0x87, 0x0f, 0x9d, 0x56, 0x12, 0xf3, 0x34, 0x37, 0x49, 0x4a, 0xa3, 0x90, 0x8a, 0x9b, 0x03, 0x70, 0xcc, 0xfa, 0x95, 0xdd, 0x04, 0x0f, 0x50, 0xb4, 0xb4, 0x7f, 0x74, 0x63, 0x54, 0x4c, 0x92, 0x6e, 0xfc, 0x4f, 0x6c, 0x0a, 0xff, 0xcc, 0x98, 0x4d, 0xf9, 0xfb, 0xe4, 0xfd, 0x6c, 0x21, 0x86, 0xc6, 0xfd, 0x00, 0x92, 0xdd, 0x68, 0x3a, 0x44, 0x85, 0x37, 0xfb, 0xca, 0x07, 0x4b, 0x2c, + 0xe8, 0xcd, 0x11, 0x51, 0xec, 0x7d, 0x14, 0xab, 0x0f, 0x07, 0x03, 0x9c, 0x4b, 0xac, 0x40, 0x22, 0xba, 0x1f, 0x8b, 0x68, 0xea, 0x59, 0xf8, 0x01, 0xac, 0x81, 0xd5, 0xf0, 0xc3, 0x67, 0x6b, 0xb1, 0x88, 0x0a, 0x3f, 0x75, 0x26, 0x3a, 0xca, 0xae, 0x99, 0x37, 0x38, 0x38, 0xef, 0x9a, 0xb2, 0x8e, 0x84, 0x53, 0x1e, 0xe3, 0x0e, 0x8d, 0x1d, 0xed, 0x43, 0xb5, 0xa0, 0xfe, 0x31, 0x2d, 0x84, 0xd3, 0x38, 0xf8, 0x85, 0x37, 0xa1, 0x16, 0x79, 0x13, 0x2a, 0xe0, 0x4d, 0x28, 0xfa, 0x2b, 0x7e, 0x88, 0x77, 0x7d, 0x1f, 0x7d, 0x72, 0xa5, 0xb0, 0x5d, 0xde, 0x78, 0xee, 0x5b, 0x4d, 0x74, 0xdf, 0x05, 0xfc, 0x2f, 0xe0, 0x2b, 0xe2, 0x71, 0xb4, 0x16, 0x56, 0x82, 0x96, 0x74, 0xe3, 0x19, 0xf2, 0x43, 0x28, 0x39, 0x0f, 0xe4, 0x3c, 0x11, 0x6b, 0x85, 0x9e, 0x50, 0x89, 0x33, 0xea, 0x18, 0x95, 0x1c, 0x82, + 0xcd, 0xc9, 0x77, 0x00, 0x33, 0xf9, 0x0e, 0xd4, 0x74, 0x07, 0xab, 0xcb, 0x5b, 0x42, 0x26, 0xbf, 0xcf, 0x5f, 0xee, 0xfa, 0xa4, 0xef, 0xc2, 0xb2, 0xce, 0xa4, 0xbb, 0xd1, 0x62, 0x13, 0x74, 0x25, 0xe9, 0x8a, 0xce, 0x6e, 0x6f, 0xfb, 0xda, 0x99, 0x93, 0xbd, 0x41, 0xaf, 0xd3, 0x6c, 0xb4, 0xf2, 0xc2, 0x21, 0x46, 0x6f, 0x71, 0x1a, 0xbc, 0x76, 0xbb, 0x13, 0x5a, 0xa7, 0xfb, 0xaa, 0x26, 0xc4, 0xa4, 0xff, 0xd0, 0x59, 0x7c, 0xfe, 0x40, 0xba, 0xdd, 0x93, 0x8a, 0x17, 0x32, 0xce, 0x3e, 0xbd, 0x49, 0x6f, 0xd4, 0xd2, 0x76, 0x70, 0xfd, 0xf0, 0x15, 0xa1, 0x8e, 0xb4, 0xa3, 0x33, 0xdd, 0x3e, 0x46, 0x3b, 0x94, 0xdc, 0x0d, 0x6a, 0x7b, 0xd0, 0x64, 0x3e, 0x9f, 0x08, 0x02, 0x69, 0x58, 0xef, 0x5f, 0xba, 0x35, 0x7c, 0x11, 0x6d, 0x8d, 0xc3, 0x91, 0xd7, 0x9a, 0x8e, 0x8e, 0x9c, 0xd6, 0xfc, 0xef, + 0xe9, 0x93, 0x73, 0x50, 0x3b, 0x24, 0xf1, 0x61, 0x20, 0x80, 0x32, 0x1c, 0x4b, 0xdf, 0x9a, 0x13, 0x4b, 0x3f, 0x2b, 0x2d, 0x00, 0x8d, 0xa6, 0xaf, 0xf8, 0xf5, 0xad, 0x05, 0x3d, 0xa2, 0x88, 0xe6, 0xfc, 0x32, 0xb1, 0x14, 0x47, 0x3b, 0x2e, 0x30, 0xe9, 0xb5, 0xa8, 0x04, 0xa1, 0x84, 0x26, 0x04, 0x88, 0x9e, 0x3d, 0x94, 0xfe, 0xde, 0x44, 0x45, 0x79, 0xb5, 0x86, 0x11, 0xdb, 0xaa, 0x3d, 0x91, 0x42, 0x97, 0xde, 0xa4, 0x09, 0xdb, 0x2b, 0x6b, 0x6a, 0xdd, 0x55, 0x73, 0xda, 0xc3, 0xc5, 0xcd, 0xe7, 0xd4, 0x26, 0xbb, 0xea, 0x8b, 0xf4, 0x16, 0xe1, 0xd3, 0x70, 0x53, 0x55, 0xb2, 0xbb, 0xbd, 0xc9, 0x60, 0x36, 0x18, 0x2a, 0xdc, 0x45, 0x16, 0x31, 0xd0, 0xbe, 0xa8, 0xad, 0xe2, 0x9c, 0x8e, 0xb0, 0xbf, 0x7e, 0x66, 0x8d, 0xdf, 0x46, 0xe8, 0x47, 0x63, 0x4a, 0x12, 0x52, 0x84, 0xfe, 0xcb, 0x4f, + 0x58, 0xe5, 0x1c, 0x08, 0x45, 0x24, 0xf7, 0xf4, 0xd8, 0x19, 0x01, 0x70, 0x2e, 0x00, 0xd8, 0x8f, 0xf3, 0x02, 0x0c, 0x08, 0x24, 0xcd, 0x6a, 0x1e, 0x5a, 0x76, 0xa3, 0xd1, 0x86, 0x72, 0xbe, 0xec, 0x40, 0xc4, 0x70, 0x68, 0xd2, 0x72, 0xff, 0x0f, 0xb4, 0x9a, 0xbf, 0x22, 0xbf, 0xd5, 0xc5, 0xed, 0x8b, 0x51, 0xab, 0xdb, 0xc3, 0x85, 0x72, 0xab, 0xff, 0x37, 0xf4, 0xdb, 0x0a, 0xfe, 0x65, 0xf0, 0xb9, 0x66, 0x0b, 0x5a, 0x1f, 0x2c, 0x59, 0x71, 0xc1, 0x0b, 0xc7, 0x8c, 0x0b, 0xbe, 0xf6, 0x8b, 0xc4, 0x05, 0x2f, 0x30, 0x65, 0xc5, 0x05, 0xb7, 0xa9, 0x71, 0xc1, 0x65, 0xdb, 0x85, 0x77, 0xae, 0xb8, 0xd1, 0xe0, 0xf0, 0x17, 0x14, 0x27, 0x93, 0xc5, 0x81, 0x0a, 0xf1, 0x56, 0xa9, 0x07, 0x3e, 0x72, 0x7b, 0x41, 0xa1, 0xc3, 0x90, 0x0c, 0x04, 0x92, 0x49, 0x42, 0x0f, 0xb7, 0x0c, 0x7c, 0x2e, 0x7c, + 0x4a, 0xe8, 0x21, 0xb1, 0x94, 0x31, 0x41, 0xe3, 0x57, 0xf9, 0xa5, 0xea, 0x13, 0xec, 0x63, 0xd4, 0xf7, 0x37, 0xd6, 0x7e, 0x34, 0x9e, 0x98, 0x20, 0x1a, 0x4f, 0x36, 0xe0, 0xc7, 0xd6, 0x4d, 0x3c, 0x24, 0xd7, 0xfc, 0x4b, 0xd5, 0x48, 0xc6, 0x6b, 0x59, 0x12, 0xcc, 0xda, 0xe7, 0xb1, 0xfb, 0x1d, 0xfe, 0x33, 0x05, 0x91, 0xa6, 0xbb, 0x2a, 0x84, 0xdb, 0x52, 0x3b, 0x17, 0xef, 0x5b, 0xe7, 0xd6, 0xd6, 0xce, 0x6d, 0x2b, 0x29, 0x69, 0x9b, 0x5b, 0x5b, 0x57, 0x5d, 0x5d, 0xb7, 0x46, 0x7c, 0x38, 0xd8, 0xbe, 0xa0, 0xbe, 0x61, 0x41, 0x7b, 0x10, 0x7d, 0x36, 0xd4, 0xa3, 0x4f, 0xe9, 0xba, 0x29, 0xe8, 0xcf, 0xee, 0xdd, 0xf2, 0xb8, 0xe6, 0xfa, 0x99, 0x20, 0x92, 0x67, 0x4c, 0xc7, 0xd9, 0x63, 0x5a, 0xff, 0xc9, 0xe4, 0x08, 0xa9, 0x60, 0x5b, 0x36, 0x39, 0x6d, 0x41, 0xe9, 0x7a, 0x99, 0x9c, 0xbf, + 0x15, 0x9e, 0xa0, 0xad, 0x0f, 0x92, 0xf5, 0x11, 0xf1, 0x41, 0xe0, 0xc6, 0x7e, 0x6b, 0x2e, 0x34, 0x06, 0x04, 0x12, 0x7b, 0x98, 0x9e, 0x9a, 0x66, 0x45, 0x20, 0x5e, 0x4b, 0xc2, 0xf2, 0xba, 0x81, 0x3b, 0xe4, 0x88, 0x2a, 0x7e, 0x6b, 0xca, 0x62, 0x92, 0x13, 0x77, 0x18, 0x8e, 0xcc, 0xd0, 0x9b, 0x04, 0xd1, 0xd7, 0x59, 0x11, 0x6d, 0x2a, 0xb5, 0x5f, 0x51, 0x12, 0xf2, 0x26, 0xed, 0xe2, 0x83, 0x52, 0xad, 0x41, 0x5f, 0xe4, 0x0b, 0x7a, 0x2b, 0x3a, 0xa3, 0x30, 0x2c, 0xfd, 0x2a, 0x52, 0xea, 0xb4, 0xc3, 0xa9, 0xa4, 0x7e, 0x34, 0xc7, 0x8e, 0x08, 0xe5, 0xa8, 0xe4, 0x54, 0xba, 0x2a, 0x53, 0x3f, 0xae, 0x13, 0x8d, 0xc9, 0xb9, 0x99, 0xd3, 0x5b, 0xf9, 0x16, 0xf5, 0xbf, 0x4f, 0x84, 0x50, 0x8e, 0x88, 0xd0, 0x15, 0x8f, 0x22, 0xe2, 0xaf, 0xcf, 0x03, 0x3c, 0xdf, 0xbe, 0x8c, 0xc6, 0x42, 0x14, + 0x67, 0x68, 0x8a, 0x42, 0xc8, 0x3b, 0x49, 0xee, 0xa3, 0xfc, 0x7c, 0x46, 0xf2, 0x74, 0x2b, 0x47, 0xdc, 0x5e, 0x0b, 0x7b, 0x4a, 0xc3, 0x67, 0xc8, 0x64, 0x94, 0xef, 0xea, 0x01, 0x1f, 0xe7, 0xf5, 0x3a, 0xad, 0xc5, 0x51, 0xe8, 0x6c, 0xa8, 0x6d, 0xac, 0xf2, 0x14, 0x39, 0x2c, 0x7a, 0xb3, 0x26, 0x5c, 0xba, 0xa2, 0x2a, 0xde, 0xd3, 0x54, 0xe2, 0x6f, 0x9e, 0xd3, 0x58, 0xd7, 0x1b, 0x10, 0xd3, 0xa2, 0x46, 0x67, 0xd4, 0x75, 0x4f, 0x98, 0x3c, 0xdd, 0x60, 0x36, 0x1a, 0x2a, 0x2a, 0x8b, 0x71, 0x52, 0x99, 0x9a, 0x79, 0xe9, 0x48, 0x28, 0x88, 0xe9, 0x44, 0x7d, 0xf5, 0x32, 0x92, 0x9f, 0x28, 0xf6, 0xcd, 0x1f, 0x9f, 0x4e, 0x1e, 0xa1, 0xf2, 0x70, 0xd4, 0x0a, 0xf7, 0x67, 0xa7, 0x96, 0x3f, 0x39, 0x0e, 0xb5, 0xe1, 0x92, 0x20, 0xf8, 0xfb, 0xe1, 0x2b, 0x9d, 0x0b, 0x18, 0xcd, 0x25, 0x48, 0xdf, + 0x0b, 0x81, 0xe6, 0x74, 0x03, 0xce, 0x0b, 0xa5, 0x1f, 0x9d, 0x17, 0x0a, 0x6b, 0x80, 0x90, 0x59, 0xaa, 0xe4, 0x87, 0x42, 0x13, 0xb7, 0xc1, 0x60, 0x08, 0x19, 0x42, 0xa5, 0x51, 0x7b, 0x38, 0x48, 0x6e, 0xc6, 0xa2, 0xb9, 0x39, 0xa1, 0xa2, 0xf9, 0x49, 0xa1, 0xea, 0xe0, 0x75, 0xc1, 0xca, 0xf2, 0x86, 0x64, 0x34, 0x50, 0x6e, 0xbf, 0xa2, 0xc7, 0x60, 0x12, 0xb4, 0x85, 0x53, 0xaa, 0xc2, 0xed, 0x49, 0x4f, 0x61, 0xed, 0xf4, 0xf2, 0x68, 0xca, 0x21, 0x56, 0x97, 0x4c, 0x9a, 0xd1, 0xdd, 0xea, 0x71, 0x31, 0x86, 0xe1, 0xd5, 0x06, 0x7d, 0xb4, 0x28, 0x1a, 0x68, 0x9d, 0xdf, 0x58, 0xbb, 0x20, 0x1d, 0x71, 0xda, 0xde, 0x91, 0xe7, 0x4f, 0x46, 0xf8, 0x84, 0xd0, 0x99, 0x4e, 0xb7, 0x8d, 0x43, 0x27, 0xda, 0xb9, 0xe1, 0x08, 0xc1, 0xc4, 0x12, 0x60, 0x3e, 0x39, 0xa5, 0x24, 0x84, 0xf7, 0xfe, 0xb9, + 0xa9, 0xe5, 0x8f, 0x67, 0x53, 0xab, 0x8b, 0xe5, 0x52, 0x0b, 0xfe, 0x6e, 0xf8, 0x8a, 0xfd, 0x83, 0x5f, 0x66, 0x34, 0x88, 0xce, 0xb3, 0xe4, 0x4d, 0x59, 0x7b, 0xb6, 0xbc, 0x29, 0x6f, 0x7b, 0x5b, 0xe3, 0xd1, 0x66, 0x34, 0xe1, 0x84, 0x42, 0x5e, 0x44, 0xc9, 0x15, 0xe2, 0x56, 0x97, 0xdf, 0x5b, 0xd9, 0x19, 0x95, 0x7e, 0x05, 0xc3, 0x91, 0x52, 0x87, 0x4d, 0x7a, 0x8c, 0xb9, 0x1d, 0xcb, 0xc5, 0x10, 0xb7, 0x8c, 0xd1, 0xa0, 0x7e, 0x74, 0xe0, 0xf9, 0x2e, 0xbb, 0x3e, 0xd4, 0x71, 0xf8, 0xc8, 0x04, 0x5f, 0x2c, 0xe2, 0x1b, 0x58, 0x62, 0xd1, 0xcf, 0xc1, 0x19, 0x5f, 0xae, 0x56, 0x01, 0xb8, 0x0a, 0xbd, 0x95, 0xe9, 0xbc, 0x5a, 0xc1, 0x5f, 0xa1, 0x9d, 0x1b, 0x50, 0x7d, 0x58, 0x8f, 0x0d, 0xe3, 0x5d, 0x94, 0x41, 0x60, 0x38, 0x10, 0x82, 0x2c, 0xc7, 0x74, 0xe1, 0xbc, 0xc6, 0x78, 0xab, 0xce, + 0xa0, 0xba, 0xba, 0x69, 0x24, 0x6b, 0xe5, 0x27, 0x4b, 0xbd, 0xb3, 0xc3, 0x24, 0xdb, 0x47, 0xd4, 0x82, 0x93, 0xa3, 0xe2, 0x04, 0x26, 0xce, 0x62, 0x78, 0xe6, 0xa4, 0x1f, 0x1b, 0x66, 0xe8, 0xcd, 0x82, 0xe0, 0x6a, 0x8c, 0x26, 0x1b, 0x4d, 0xae, 0x73, 0x92, 0xdd, 0xb3, 0xaf, 0x9c, 0x54, 0x14, 0xbf, 0x22, 0x58, 0xe4, 0x8a, 0x59, 0xf8, 0x97, 0xe1, 0x8f, 0x0c, 0x3a, 0x9f, 0x27, 0x90, 0x8c, 0x26, 0xa3, 0x8b, 0xe6, 0x60, 0x22, 0x67, 0xc4, 0x08, 0xad, 0xa5, 0x11, 0x9b, 0x45, 0x3a, 0x4e, 0x79, 0xb3, 0x01, 0xf5, 0x09, 0xde, 0x6b, 0x84, 0x71, 0x8e, 0x15, 0x01, 0x30, 0x4a, 0x52, 0x12, 0x1c, 0xa8, 0x86, 0x87, 0x44, 0x4d, 0x21, 0xb9, 0x48, 0x98, 0x7e, 0x9c, 0x99, 0x04, 0x30, 0xb8, 0x53, 0xbe, 0x4c, 0x46, 0x12, 0xa7, 0xeb, 0x37, 0xae, 0x86, 0x31, 0xa8, 0xbb, 0x02, 0x13, 0xce, 0x7f, + 0x8a, 0xa8, 0x8b, 0x94, 0x47, 0x17, 0x8e, 0xa6, 0x0e, 0xd3, 0xfe, 0xf7, 0xc6, 0xcb, 0xf3, 0x11, 0xda, 0xf5, 0x68, 0xff, 0xec, 0x20, 0x9e, 0x1e, 0x6a, 0x6a, 0x8d, 0x4c, 0x16, 0x8d, 0xb5, 0xdd, 0xd9, 0x7a, 0xd6, 0x59, 0x52, 0x65, 0xbc, 0xe2, 0x6c, 0x8c, 0x16, 0xdb, 0x35, 0x5c, 0x81, 0xe8, 0xd3, 0x07, 0x63, 0xa6, 0x2b, 0xa7, 0xdb, 0x4d, 0xac, 0xb8, 0x34, 0x96, 0x34, 0x2f, 0x2d, 0xe0, 0x42, 0xc5, 0xd2, 0x9b, 0xf0, 0x47, 0x26, 0x8b, 0x5c, 0x2f, 0xb7, 0x0c, 0x5e, 0x2f, 0xd4, 0x51, 0x1b, 0xfe, 0x4c, 0xbd, 0xd8, 0x8e, 0x1f, 0x12, 0x1b, 0x16, 0x25, 0xd2, 0x3b, 0x60, 0x67, 0xfc, 0x37, 0x2a, 0xe5, 0x5f, 0x1d, 0x55, 0xe9, 0xff, 0xad, 0xb6, 0xa6, 0xd1, 0x7a, 0xbe, 0x55, 0x53, 0x03, 0x8a, 0x71, 0x04, 0x27, 0x9c, 0xc3, 0xa3, 0x28, 0x93, 0xc3, 0x03, 0xf0, 0x48, 0x69, 0xe3, 0xc1, + 0x32, 0x35, 0x97, 0xc7, 0x5a, 0xec, 0x59, 0xe3, 0xb0, 0xe7, 0x27, 0xf0, 0x80, 0xd8, 0xce, 0x2e, 0x37, 0x81, 0x87, 0xc3, 0xae, 0x5a, 0x6a, 0xa2, 0xd1, 0xdc, 0x31, 0x21, 0xa1, 0x33, 0x4d, 0xf7, 0x08, 0xce, 0xf4, 0xe6, 0xcd, 0x11, 0xad, 0x51, 0xa7, 0x15, 0x04, 0x03, 0xeb, 0xaa, 0xe3, 0x7f, 0x31, 0xb5, 0xed, 0x03, 0xa3, 0x21, 0x65, 0xba, 0xc8, 0xb1, 0xab, 0xff, 0xad, 0x18, 0xcf, 0xf3, 0x82, 0x06, 0xed, 0xef, 0x29, 0x5d, 0x48, 0x2f, 0xda, 0x2a, 0x3c, 0x82, 0xe8, 0xaa, 0x4a, 0x27, 0xf5, 0x90, 0x44, 0x7e, 0x20, 0x34, 0xb1, 0x88, 0x1a, 0x1e, 0x47, 0x2f, 0x42, 0xdb, 0x7f, 0x7e, 0x3e, 0xce, 0x16, 0xba, 0x08, 0x87, 0x89, 0xe8, 0xb3, 0x5a, 0x2c, 0x31, 0x9b, 0x9a, 0x1b, 0x65, 0x7c, 0x82, 0x5c, 0x70, 0x6b, 0xdd, 0x64, 0x42, 0x50, 0x97, 0x3b, 0x97, 0x20, 0x6e, 0x7b, 0x7c, 0x6a, + 0xdb, 0xfb, 0x26, 0x4a, 0xcf, 0xb9, 0x6f, 0x52, 0x7a, 0x72, 0x79, 0x84, 0xfa, 0x25, 0x9b, 0x16, 0x4c, 0x42, 0x2e, 0x7b, 0xfe, 0x74, 0x2a, 0x04, 0x18, 0x1f, 0xcd, 0x15, 0xca, 0x93, 0x36, 0x34, 0x5f, 0x38, 0x90, 0x4e, 0x6d, 0x19, 0x2b, 0x9f, 0xc8, 0x5a, 0x62, 0x83, 0x64, 0x01, 0x05, 0xa3, 0xf3, 0x89, 0xc8, 0x79, 0x22, 0x1c, 0x8f, 0x0c, 0x3d, 0xb2, 0xda, 0x64, 0x63, 0x7a, 0x34, 0x7a, 0x5e, 0x5b, 0x30, 0x93, 0xb1, 0x19, 0xb0, 0xfe, 0x8c, 0x84, 0x5f, 0xff, 0x62, 0xb0, 0xbd, 0xb8, 0x65, 0xe2, 0x8b, 0x3a, 0xa3, 0x5c, 0x0f, 0x9a, 0x37, 0x1d, 0x68, 0xff, 0x60, 0xc1, 0xf6, 0xbf, 0xb4, 0x1e, 0x5c, 0x05, 0x35, 0xca, 0x5d, 0xfb, 0xa5, 0x6b, 0xc1, 0x9b, 0x84, 0xbc, 0x5a, 0xfe, 0xe7, 0xda, 0xd2, 0x89, 0xea, 0x29, 0x44, 0xf5, 0xd0, 0x1c, 0x23, 0x36, 0x63, 0x7e, 0x8e, 0x11, 0xa4, + 0x9a, 0x94, 0x59, 0xcb, 0xb2, 0x72, 0x8c, 0x08, 0x34, 0xb9, 0x47, 0x43, 0x4c, 0x9e, 0x55, 0xe5, 0xec, 0x1e, 0x85, 0x97, 0x98, 0x0a, 0xf8, 0x7e, 0x4e, 0xd3, 0xc3, 0x17, 0x18, 0x36, 0xdc, 0x79, 0x60, 0x0b, 0x1a, 0xff, 0xfd, 0xac, 0xc0, 0x4f, 0xd2, 0xda, 0x36, 0xee, 0xe7, 0x5f, 0x36, 0x69, 0x6f, 0xb3, 0x98, 0xb6, 0x68, 0x4d, 0xbf, 0x7f, 0x59, 0xd4, 0xde, 0xaa, 0xb5, 0x6a, 0xd7, 0x59, 0xff, 0x1d, 0xd7, 0x8d, 0xf8, 0x58, 0x88, 0xf8, 0x48, 0xf3, 0x8b, 0x28, 0x75, 0x8f, 0x91, 0x5f, 0xe4, 0x4f, 0xa6, 0x80, 0x5b, 0x36, 0x16, 0x05, 0x7f, 0xdd, 0xf6, 0xb3, 0xa0, 0x82, 0xbf, 0x9a, 0x79, 0x4f, 0xfc, 0x16, 0xfa, 0xe6, 0x02, 0x09, 0x1c, 0x5d, 0x04, 0xa9, 0x26, 0x38, 0x1d, 0x0d, 0x09, 0xbb, 0x28, 0x9b, 0x80, 0xc8, 0x41, 0x46, 0x90, 0x7e, 0x52, 0x8a, 0xfe, 0x10, 0x63, 0x81, 0x06, + 0x28, 0x46, 0xf2, 0x3d, 0xf3, 0xf2, 0x7e, 0xdf, 0x33, 0x00, 0xb7, 0x0f, 0x4a, 0x7f, 0xb4, 0x07, 0xcb, 0xdd, 0xee, 0x38, 0xb6, 0xdc, 0x88, 0xbb, 0xdd, 0xe5, 0x41, 0x3b, 0x7c, 0x1e, 0xdb, 0x70, 0x28, 0xb6, 0x1c, 0xf8, 0x53, 0x0c, 0x0d, 0x4a, 0x5b, 0x57, 0xce, 0x73, 0x27, 0x02, 0x56, 0x6b, 0x80, 0x1a, 0x7f, 0xd8, 0x82, 0xe5, 0x30, 0xe6, 0x4e, 0x14, 0xdb, 0x6c, 0xc5, 0x2a, 0x80, 0xe6, 0x59, 0xac, 0xe0, 0x58, 0xe6, 0x3d, 0xa1, 0x5a, 0xa6, 0x17, 0xcd, 0xfd, 0xd9, 0xf4, 0x2a, 0x87, 0x3c, 0x79, 0xae, 0x28, 0x7f, 0x11, 0xaa, 0xf9, 0x6f, 0x0c, 0x4a, 0x57, 0x0e, 0x7e, 0x01, 0xaa, 0xff, 0x0e, 0x79, 0x9c, 0x4f, 0x2f, 0x36, 0x41, 0x25, 0x2b, 0x2b, 0x43, 0x8d, 0xd7, 0xc9, 0x79, 0x0d, 0x3d, 0x79, 0x67, 0x33, 0xf4, 0xc6, 0xa0, 0x6b, 0x54, 0xa8, 0x99, 0xbc, 0xdf, 0x9f, 0x0d, 0xc2, + 0xed, 0x03, 0x97, 0xe6, 0x93, 0x2b, 0xc5, 0x47, 0x31, 0xf7, 0xee, 0x95, 0xf0, 0x9a, 0x41, 0xe9, 0x39, 0x99, 0x2c, 0x8f, 0x4c, 0x37, 0x5c, 0x90, 0x01, 0x10, 0xba, 0xc7, 0x19, 0x13, 0xd9, 0xf4, 0x32, 0x78, 0x54, 0xb0, 0x03, 0xa3, 0x42, 0x6b, 0xfc, 0x05, 0xa8, 0xe6, 0x4a, 0x06, 0x71, 0x77, 0x7c, 0x01, 0xaa, 0xff, 0xde, 0x78, 0x8c, 0xfd, 0x26, 0x2f, 0x67, 0x1e, 0xe0, 0x1f, 0x03, 0x4e, 0xd0, 0x42, 0xef, 0x57, 0xad, 0x64, 0xab, 0xc9, 0xe0, 0xf3, 0x83, 0x95, 0x58, 0xd5, 0x1a, 0xc0, 0x69, 0xd9, 0x1c, 0x40, 0xb1, 0x1e, 0xcb, 0x3c, 0x84, 0x3d, 0xfd, 0x8f, 0xda, 0xc3, 0xf6, 0x30, 0xcd, 0x50, 0x94, 0xe5, 0xda, 0x4b, 0x06, 0x72, 0x0a, 0x42, 0x5b, 0x71, 0xd4, 0x16, 0xe9, 0xac, 0x2e, 0xdc, 0x55, 0x39, 0xff, 0xf2, 0x19, 0x75, 0x73, 0x5a, 0x02, 0xbb, 0xf8, 0x0b, 0xc3, 0x49, 0xb7, + 0xd6, 0x5d, 0x39, 0xb1, 0xbc, 0xf3, 0xc2, 0x79, 0x35, 0xbe, 0x96, 0x25, 0x93, 0xfe, 0x66, 0x68, 0x78, 0x99, 0xff, 0x98, 0xb1, 0xca, 0xb6, 0x9e, 0xa3, 0xed, 0xcb, 0x84, 0x5f, 0x61, 0xfb, 0x32, 0x6c, 0xeb, 0xf9, 0x32, 0x77, 0x19, 0x63, 0x95, 0x6d, 0x47, 0x47, 0xe3, 0xf1, 0x4b, 0x54, 0xbc, 0x2f, 0x54, 0x5e, 0xbe, 0x1d, 0x9a, 0x56, 0xcc, 0xb2, 0x43, 0xe3, 0xf1, 0x0b, 0x0d, 0xd4, 0x0e, 0x8d, 0x35, 0xbe, 0x86, 0x2d, 0xd0, 0xa8, 0x1d, 0x5a, 0xe7, 0xeb, 0xaf, 0x23, 0x10, 0xb5, 0x2b, 0xa8, 0x1a, 0xf9, 0x03, 0xd7, 0x89, 0xe3, 0x1b, 0x32, 0x8b, 0x88, 0xa7, 0xc3, 0x43, 0xe0, 0x7e, 0x9a, 0x7f, 0x1c, 0xc3, 0xf9, 0x43, 0xa0, 0x06, 0xb4, 0xa7, 0x5b, 0x2a, 0x21, 0xc7, 0xc7, 0x10, 0xeb, 0xcc, 0x78, 0x7e, 0x42, 0xc3, 0x90, 0x63, 0x39, 0x7a, 0x5e, 0xc4, 0xe1, 0xf3, 0x22, 0x8e, 0xe3, + 0xfb, 0x69, 0xca, 0x3b, 0xb4, 0xfe, 0x2f, 0xa2, 0x31, 0xfc, 0x2c, 0x8e, 0xd2, 0xca, 0x52, 0xea, 0xdd, 0x44, 0xc3, 0xef, 0xd2, 0xe0, 0x7b, 0x74, 0xa3, 0x43, 0x43, 0x51, 0xd2, 0x58, 0x87, 0xd8, 0xea, 0x09, 0x06, 0xeb, 0x82, 0xd8, 0xca, 0x89, 0x59, 0xb9, 0xed, 0xc4, 0x85, 0x75, 0x0b, 0xef, 0x78, 0x66, 0xe3, 0xf4, 0x5d, 0x97, 0xaf, 0x2d, 0x9b, 0xad, 0xb7, 0x09, 0x06, 0xbb, 0xad, 0xa0, 0x6e, 0xc6, 0x60, 0xc7, 0x9c, 0xab, 0x16, 0x24, 0xa3, 0x73, 0x76, 0x0e, 0x1c, 0x36, 0x59, 0xe0, 0x65, 0xc3, 0xeb, 0xd8, 0xef, 0xe9, 0x9d, 0xec, 0x16, 0xbe, 0x73, 0xdd, 0x9e, 0x59, 0x17, 0x7d, 0xe7, 0x9a, 0x29, 0x45, 0x4d, 0xb3, 0xeb, 0x96, 0x18, 0x38, 0x4d, 0x81, 0xb5, 0x3c, 0xe6, 0xad, 0x99, 0x37, 0xd4, 0xd1, 0xb0, 0x78, 0x52, 0x6c, 0x91, 0x25, 0x6c, 0x81, 0x1b, 0xbf, 0xd9, 0x6b, 0x2d, + 0xb3, 0x23, 0x3e, 0xcd, 0x96, 0x76, 0xf3, 0x57, 0x70, 0x7a, 0x60, 0x02, 0xad, 0xe0, 0x34, 0xf5, 0x1f, 0x32, 0x94, 0x23, 0x0d, 0xb9, 0xd0, 0x84, 0x87, 0x05, 0x0e, 0x2d, 0x8e, 0x7f, 0x0a, 0xca, 0xcf, 0x7e, 0x39, 0x60, 0x38, 0x50, 0x2c, 0x9e, 0xd6, 0x77, 0xe3, 0x5b, 0x2c, 0x72, 0x37, 0xca, 0x0e, 0x76, 0x6b, 0xa1, 0x46, 0xa3, 0x78, 0x38, 0xc9, 0xb1, 0xf6, 0x62, 0x40, 0x10, 0x56, 0xa9, 0xc1, 0x38, 0xcf, 0x80, 0x9e, 0xae, 0x01, 0x5a, 0x4e, 0xc3, 0x69, 0x35, 0x43, 0x79, 0x6f, 0xe0, 0x50, 0xab, 0xe4, 0x35, 0x72, 0xee, 0xb2, 0xa1, 0x1b, 0x64, 0x5e, 0xea, 0xef, 0x4f, 0x9b, 0x9a, 0x1b, 0x43, 0x96, 0x70, 0x69, 0x38, 0x12, 0x89, 0x96, 0xe8, 0x74, 0x7e, 0x35, 0xe5, 0x39, 0xf1, 0xdb, 0x6a, 0x50, 0x7d, 0xaa, 0x46, 0xa7, 0x3e, 0x6f, 0x40, 0x3a, 0x80, 0x25, 0xa5, 0x06, 0xd7, 0x73, + 0xb1, 0xee, 0xca, 0x8d, 0x13, 0xaf, 0xdb, 0x17, 0xec, 0x58, 0xd8, 0x3c, 0xf7, 0x8a, 0x7a, 0x66, 0x50, 0x8e, 0x92, 0xb7, 0xb2, 0x4c, 0x67, 0x49, 0xdf, 0xb7, 0x6e, 0xfe, 0x4d, 0x83, 0xf5, 0xad, 0x9b, 0xbe, 0xbe, 0x6a, 0xc5, 0x0d, 0x75, 0x06, 0x5d, 0xd9, 0xe6, 0xdb, 0xab, 0xd2, 0x81, 0x0a, 0x93, 0xde, 0x2b, 0xb2, 0x6c, 0x7b, 0xcd, 0xd3, 0x8f, 0x2f, 0xdf, 0x39, 0xbf, 0xac, 0x67, 0xf2, 0xf0, 0x2d, 0x76, 0x0b, 0x0e, 0x8b, 0x57, 0x17, 0xbb, 0x78, 0xc6, 0xe2, 0x49, 0xdb, 0x4f, 0x6e, 0xdd, 0x78, 0x72, 0xcf, 0xac, 0xc9, 0x6d, 0xd3, 0x62, 0x9f, 0x1f, 0x7a, 0x81, 0x84, 0xcd, 0x43, 0xe3, 0x27, 0x35, 0xf2, 0x1e, 0x7b, 0x11, 0xa7, 0x01, 0xe5, 0xe0, 0xfc, 0xb4, 0x0e, 0xc7, 0x82, 0x2c, 0xc7, 0x19, 0x52, 0xe5, 0xb8, 0x86, 0x61, 0x7c, 0xcf, 0xc0, 0x10, 0x51, 0xc4, 0xe7, 0xfc, 0xeb, + 0x95, 0xb8, 0x81, 0xeb, 0xbb, 0x95, 0x53, 0xc7, 0x75, 0x3c, 0xc9, 0x60, 0x7f, 0x36, 0xb4, 0x0d, 0x3c, 0xc9, 0x60, 0x1f, 0x0d, 0x95, 0xd2, 0xf3, 0x49, 0xd5, 0x93, 0x4d, 0x0e, 0x9f, 0x49, 0x6e, 0x73, 0x2b, 0x61, 0x3b, 0x9b, 0x9d, 0xb4, 0x72, 0x93, 0xd6, 0x52, 0x50, 0x10, 0xaa, 0xeb, 0x6e, 0x98, 0xb6, 0x7e, 0x7a, 0xb4, 0xb4, 0xeb, 0xbc, 0xce, 0x8e, 0xd9, 0xf5, 0x61, 0x9b, 0xdb, 0xd8, 0x18, 0x9a, 0xbf, 0xe2, 0xfc, 0x86, 0xa5, 0xdf, 0xb8, 0x74, 0x4a, 0xc7, 0xe5, 0x8f, 0x5c, 0x74, 0xc1, 0x7d, 0x9d, 0xdf, 0x37, 0x18, 0x3d, 0x7e, 0x4f, 0xed, 0xb2, 0x9d, 0xf3, 0xfa, 0x6f, 0x5c, 0x5a, 0x53, 0x1c, 0x2e, 0xb6, 0x4e, 0x8b, 0xb7, 0x45, 0xad, 0x53, 0xae, 0x3f, 0x75, 0xc5, 0xc6, 0xef, 0xef, 0x9e, 0x39, 0x25, 0x8d, 0xda, 0x99, 0x44, 0xed, 0x5c, 0x4f, 0xda, 0xd9, 0x9c, 0x6e, 0xc8, + 0xb4, 0x73, 0x1c, 0xca, 0xeb, 0x55, 0xca, 0x6b, 0xf9, 0x1e, 0x99, 0x6c, 0xf1, 0x0b, 0x92, 0xbd, 0xee, 0xcf, 0x47, 0x36, 0x80, 0x23, 0xbb, 0xa5, 0xdb, 0x98, 0xe7, 0xf8, 0x07, 0x41, 0x12, 0xdc, 0x48, 0xc7, 0xaf, 0x0e, 0x27, 0x92, 0x2b, 0x86, 0x3c, 0x09, 0xb3, 0x8f, 0x7e, 0x30, 0xf8, 0x87, 0x48, 0x24, 0x01, 0x3f, 0x0e, 0x6a, 0x21, 0xab, 0x81, 0x9c, 0xc0, 0x72, 0xcb, 0xb3, 0x52, 0x6c, 0xe2, 0xc8, 0xf8, 0xa4, 0x39, 0x43, 0xb8, 0xbf, 0x2a, 0x71, 0x3e, 0x3a, 0xa4, 0x69, 0xa1, 0x81, 0xcd, 0x89, 0x9c, 0x20, 0x0e, 0x65, 0xde, 0x51, 0xce, 0xb2, 0xce, 0xef, 0x06, 0xa2, 0xa8, 0xbc, 0x81, 0x06, 0x75, 0xb2, 0xdc, 0x16, 0x8c, 0x44, 0x2c, 0xe8, 0x9f, 0x30, 0x1e, 0xd4, 0xb6, 0xec, 0x38, 0x4c, 0xe4, 0x98, 0xd1, 0x42, 0x32, 0x4a, 0x66, 0xf9, 0x29, 0x12, 0x57, 0x3d, 0x4b, 0xa8, 0x2e, + 0xf5, 0x93, 0xb2, 0x39, 0x57, 0xf7, 0xd7, 0xf4, 0x26, 0x1d, 0xa2, 0xcb, 0x64, 0x2a, 0x8d, 0xcc, 0x9a, 0x7b, 0xe0, 0x40, 0xd9, 0x9c, 0x6b, 0xfa, 0xab, 0x11, 0x44, 0x70, 0x9b, 0x8c, 0x65, 0x08, 0x72, 0xdb, 0x6d, 0x5c, 0xed, 0x39, 0xbb, 0x06, 0xea, 0xad, 0xb6, 0x79, 0x6e, 0xb3, 0x69, 0x70, 0xf5, 0xf0, 0xdb, 0x7b, 0xa4, 0xf3, 0xcf, 0xd9, 0xbd, 0xb2, 0x49, 0x01, 0xc0, 0xe3, 0x7b, 0x10, 0x2f, 0xa6, 0x4a, 0xb7, 0xb1, 0x10, 0xf1, 0xa2, 0x05, 0xdc, 0x49, 0x1b, 0xab, 0xaf, 0x82, 0x1a, 0xd1, 0xaf, 0x25, 0x8a, 0x98, 0x0f, 0xff, 0xd2, 0x92, 0x5f, 0x8c, 0xca, 0x8d, 0x08, 0x96, 0x68, 0x39, 0xc7, 0xb5, 0x1e, 0xea, 0x74, 0x64, 0x0b, 0x39, 0x48, 0x32, 0xa7, 0x29, 0xe7, 0x0c, 0x43, 0xf8, 0x22, 0x31, 0x05, 0x34, 0x5a, 0x9d, 0x56, 0xa3, 0x1b, 0xe2, 0xd1, 0x64, 0x80, 0xcd, 0xef, 0x87, + 0x80, 0x56, 0x3b, 0xc6, 0x7b, 0x50, 0x09, 0x35, 0x30, 0x84, 0x7d, 0xce, 0xec, 0x24, 0xe7, 0x5a, 0x4b, 0x63, 0x7d, 0x24, 0x86, 0xf8, 0x82, 0x19, 0x64, 0x40, 0xbc, 0x89, 0x38, 0xf2, 0xd9, 0xd0, 0x06, 0x83, 0xf8, 0xda, 0x38, 0x87, 0x31, 0xa3, 0x19, 0xc8, 0x42, 0x7f, 0x7a, 0x75, 0x77, 0x59, 0x4f, 0xd2, 0x2c, 0x78, 0x0d, 0xc6, 0xca, 0x40, 0xba, 0x4a, 0xfa, 0x90, 0x4d, 0xcc, 0xd9, 0x36, 0x3f, 0x9b, 0x53, 0xf9, 0xbc, 0xdc, 0xff, 0x9d, 0x8e, 0x0b, 0x17, 0xd4, 0x59, 0x6d, 0xbd, 0x1e, 0x93, 0x69, 0xde, 0x8c, 0xba, 0xde, 0x1d, 0xcb, 0xea, 0x14, 0x9e, 0x71, 0xed, 0xd9, 0x2c, 0xfd, 0x7c, 0x3a, 0xd1, 0x03, 0x46, 0x86, 0x91, 0xf2, 0x72, 0x29, 0x7f, 0x1f, 0x08, 0xe2, 0xbc, 0x84, 0x56, 0x33, 0x43, 0x36, 0xda, 0xd8, 0x37, 0x83, 0xc5, 0x06, 0x4a, 0x6a, 0xe7, 0x93, 0x28, 0x5f, 0xa1, + 0x52, 0x2f, 0x59, 0x71, 0xd9, 0xcc, 0x00, 0xc7, 0x67, 0x1f, 0x38, 0x18, 0x7b, 0x94, 0x8e, 0xfd, 0x18, 0x1b, 0x5e, 0xbc, 0x66, 0x43, 0xfd, 0x79, 0x27, 0xae, 0xed, 0xae, 0x1d, 0xb8, 0x79, 0xf1, 0xa9, 0x95, 0xfb, 0x96, 0x55, 0xdc, 0x73, 0xf3, 0x84, 0x65, 0x53, 0xaa, 0xad, 0x6e, 0x23, 0x2f, 0xe2, 0xe1, 0xdc, 0xbe, 0xe5, 0xa1, 0x8b, 0x16, 0xdc, 0x75, 0xf5, 0x52, 0xaf, 0x14, 0x66, 0x26, 0x19, 0x27, 0x0d, 0x5e, 0xdd, 0xfd, 0xb5, 0xfb, 0x8a, 0x42, 0x45, 0x56, 0x44, 0xcb, 0x2e, 0x44, 0x8b, 0x80, 0x68, 0x49, 0x60, 0xcb, 0x17, 0x1b, 0xea, 0x90, 0x12, 0x4c, 0x06, 0xd1, 0xa9, 0x59, 0xa4, 0x01, 0x0c, 0x69, 0x44, 0x06, 0x6d, 0xd5, 0x39, 0x1e, 0x8f, 0x4b, 0xd9, 0x53, 0x15, 0x91, 0x46, 0xe2, 0xf3, 0xa2, 0xdf, 0x09, 0x90, 0xb0, 0x58, 0x2c, 0xde, 0x68, 0x28, 0x4e, 0x4f, 0xe8, 0x32, + 0xa9, 0xee, 0x72, 0x65, 0x52, 0x24, 0x87, 0x02, 0xd8, 0x73, 0xf7, 0x49, 0x4c, 0x5e, 0xbc, 0x77, 0xe3, 0xb4, 0x09, 0xcb, 0xa6, 0x62, 0x02, 0x1b, 0x42, 0x94, 0xf8, 0x1d, 0x84, 0xf8, 0x7d, 0xfb, 0x4e, 0x1f, 0x84, 0x13, 0x21, 0x24, 0x34, 0xce, 0xba, 0x6c, 0x76, 0x1c, 0x53, 0x39, 0x2d, 0xd3, 0x84, 0xab, 0x96, 0x79, 0x6f, 0xdf, 0x73, 0xe2, 0xc4, 0x1e, 0xaa, 0x4f, 0x15, 0x23, 0xda, 0xef, 0x24, 0xb4, 0xb7, 0xa4, 0x1b, 0xcd, 0x68, 0x43, 0x10, 0x04, 0x32, 0xed, 0x54, 0xb4, 0x48, 0x08, 0x68, 0x4a, 0x7a, 0x86, 0xab, 0x84, 0x74, 0xec, 0x4f, 0xec, 0x0d, 0x3b, 0xcb, 0x88, 0xa5, 0x4b, 0x26, 0x0d, 0x1f, 0x39, 0x5e, 0xca, 0x4a, 0xd5, 0x67, 0x51, 0xa8, 0x4e, 0xc1, 0x7b, 0x16, 0xdf, 0x3c, 0x50, 0xdb, 0xbd, 0xe3, 0xc4, 0x79, 0xf5, 0x1b, 0xd6, 0x2c, 0x0e, 0x35, 0x18, 0xdd, 0xd6, 0xea, + 0xa9, 0xcb, 0x26, 0x4c, 0xdb, 0xd8, 0x1b, 0xaf, 0x58, 0xb6, 0x6f, 0xe5, 0x9e, 0x3d, 0xa7, 0xef, 0xba, 0x0b, 0xfe, 0xca, 0xbb, 0xec, 0xaa, 0xbb, 0x16, 0x5c, 0xf4, 0xd0, 0x96, 0x76, 0x6b, 0xb4, 0x2d, 0x3e, 0xcd, 0x8a, 0x88, 0x8f, 0xcf, 0xbe, 0x6c, 0x56, 0xf7, 0xd5, 0x83, 0x93, 0x8c, 0x92, 0xb4, 0xe7, 0x91, 0x47, 0xf6, 0xd0, 0xbb, 0x64, 0x1c, 0x43, 0x73, 0x31, 0xfb, 0x31, 0xd0, 0x20, 0xbd, 0xb5, 0x26, 0x5d, 0xc9, 0xe3, 0xd3, 0x2e, 0x0e, 0x7b, 0xe1, 0xe2, 0x0b, 0x0f, 0x3c, 0xbe, 0x49, 0x42, 0x8d, 0x41, 0xae, 0x47, 0xa7, 0x85, 0xc0, 0xe9, 0xb0, 0x5b, 0xb5, 0x2e, 0x1d, 0xb6, 0x16, 0xd6, 0x20, 0x49, 0x53, 0xad, 0x85, 0x13, 0xd0, 0x62, 0x93, 0xb9, 0x4b, 0x42, 0x6c, 0xc2, 0xe7, 0xd6, 0xae, 0x5c, 0xb9, 0x76, 0xcf, 0x9e, 0x55, 0xfb, 0x97, 0x57, 0x56, 0x0f, 0xec, 0x1b, 0xd8, + 0xf3, 0x04, 0xfc, 0x74, 0x70, 0xee, 0xac, 0x65, 0xd2, 0x57, 0xa0, 0x54, 0x35, 0x6f, 0xcb, 0xd4, 0xae, 0xcb, 0x17, 0x54, 0x4b, 0x77, 0xa2, 0x5a, 0x19, 0x60, 0x66, 0xf7, 0x31, 0xaf, 0x91, 0x00, 0xb7, 0x4e, 0x50, 0x99, 0x2e, 0xc7, 0xee, 0x53, 0xb0, 0x0b, 0x53, 0x81, 0x0f, 0xef, 0x57, 0xa0, 0xaa, 0x7b, 0xbb, 0x71, 0x7c, 0x99, 0x3e, 0x7c, 0x3a, 0x81, 0xab, 0x37, 0x63, 0x73, 0x0c, 0x0d, 0xd0, 0x08, 0x5a, 0x54, 0x79, 0x8c, 0x64, 0xb1, 0x44, 0xdc, 0x26, 0xbb, 0xe8, 0xa0, 0x48, 0xb3, 0x59, 0xc2, 0x37, 0x7a, 0x45, 0xb3, 0x46, 0x34, 0x8b, 0xbd, 0xf0, 0x17, 0x17, 0xd9, 0xdd, 0x42, 0xb5, 0xf4, 0xef, 0xd5, 0x9c, 0xd7, 0xba, 0x19, 0x16, 0xdd, 0xc1, 0x3d, 0xf8, 0xa2, 0xb5, 0xca, 0xef, 0xaf, 0xb2, 0xbe, 0x28, 0xfd, 0xd6, 0x6e, 0xda, 0xb9, 0xd3, 0xe8, 0x60, 0x5e, 0xa2, 0x7b, 0x8e, + 0x41, 0x44, 0xc7, 0x6e, 0x44, 0x87, 0x8e, 0x44, 0x8f, 0x99, 0x94, 0xee, 0x2c, 0x1c, 0x27, 0x2e, 0x74, 0x6f, 0x26, 0x2e, 0x74, 0x1f, 0xdf, 0xe3, 0xb0, 0xe3, 0xb8, 0xd0, 0xe1, 0x12, 0x1c, 0x41, 0x46, 0x6f, 0xd3, 0x5b, 0xd5, 0xb8, 0xd0, 0x5a, 0x6a, 0xd4, 0x4a, 0x98, 0xe3, 0xb0, 0x38, 0x52, 0xd9, 0x56, 0xeb, 0x82, 0xe8, 0x94, 0x83, 0x42, 0xc3, 0x2b, 0xc2, 0x55, 0x7e, 0x7d, 0xe3, 0xe4, 0xab, 0x07, 0xd3, 0x93, 0x5b, 0xd3, 0xd3, 0xd7, 0x4c, 0x28, 0x6e, 0x99, 0x78, 0x4b, 0x78, 0xf2, 0x60, 0xa7, 0xf4, 0x06, 0xa7, 0xb3, 0x16, 0x46, 0xec, 0xe5, 0xd5, 0xa7, 0xe1, 0xbb, 0x27, 0xe3, 0xf5, 0x0d, 0x89, 0xcd, 0x85, 0xa9, 0x69, 0xc9, 0xd4, 0x39, 0xee, 0x92, 0xad, 0x95, 0x7d, 0x2d, 0x41, 0x44, 0x36, 0x43, 0x68, 0xfe, 0x00, 0xd1, 0x6c, 0x00, 0x1e, 0x7c, 0x6a, 0xa4, 0xb8, 0xf0, 0xf4, + 0xaa, 0xd9, 0xa1, 0xfb, 0x40, 0x4f, 0xb0, 0x34, 0x18, 0xe6, 0xb4, 0x6e, 0xea, 0xb1, 0x11, 0xb4, 0x04, 0xe5, 0x30, 0x78, 0x38, 0xec, 0x2c, 0x22, 0x8f, 0xf9, 0x00, 0x07, 0xb1, 0x93, 0xde, 0x84, 0x1e, 0x35, 0x90, 0xdd, 0x35, 0xd7, 0xe0, 0x50, 0x76, 0x3c, 0xec, 0xbb, 0x6e, 0x79, 0x03, 0xf3, 0x52, 0xc3, 0xf2, 0xeb, 0x49, 0x50, 0x3a, 0xc9, 0x85, 0xf4, 0x0e, 0x3c, 0xce, 0x71, 0x9d, 0x75, 0xa8, 0x4e, 0x3d, 0xee, 0x2d, 0x40, 0x97, 0xc9, 0xe5, 0x58, 0xd1, 0x22, 0xd1, 0xe7, 0x7b, 0xbb, 0x95, 0x84, 0xa6, 0x7d, 0x6c, 0x4e, 0xec, 0x55, 0xad, 0x27, 0x61, 0x23, 0x72, 0x87, 0x63, 0xcc, 0x07, 0x1d, 0x9f, 0x7d, 0xf8, 0x21, 0x6c, 0x96, 0x2a, 0xe1, 0xcf, 0xf8, 0x3f, 0x3e, 0x7e, 0xf1, 0x13, 0x97, 0x31, 0x2f, 0x5d, 0x8a, 0xcb, 0xae, 0x63, 0x6f, 0x64, 0x9e, 0x16, 0xb6, 0xa0, 0xb5, 0xad, + 0x21, 0x5d, 0x6b, 0x45, 0x13, 0x6f, 0x29, 0x92, 0x18, 0xb4, 0x6a, 0x71, 0x58, 0x77, 0x05, 0x3c, 0x07, 0xf8, 0xe5, 0xca, 0x1e, 0x6a, 0xbe, 0x9a, 0xe6, 0x65, 0x01, 0xec, 0xb1, 0x95, 0xc6, 0x4b, 0x43, 0x05, 0x82, 0xd6, 0x8b, 0x23, 0x5c, 0x8d, 0x0a, 0x0c, 0x28, 0x47, 0x4e, 0x57, 0x34, 0xae, 0x3a, 0x6c, 0xef, 0x01, 0xff, 0xe9, 0xd3, 0xc6, 0x1d, 0x0b, 0xfb, 0xae, 0x1f, 0x68, 0xac, 0x1f, 0xdc, 0xb3, 0x60, 0xe1, 0xb5, 0xc9, 0x84, 0x3e, 0x60, 0xb3, 0x96, 0xfc, 0x7f, 0xcc, 0x9d, 0x07, 0x7c, 0x14, 0xe5, 0xfa, 0xef, 0xdf, 0xd9, 0x94, 0xdd, 0xd9, 0x40, 0x20, 0x24, 0x54, 0x43, 0x58, 0x7a, 0x87, 0x50, 0x42, 0x09, 0x2d, 0xf4, 0xde, 0x91, 0x22, 0x45, 0x52, 0x16, 0x12, 0x48, 0x23, 0x09, 0x20, 0x08, 0x22, 0xa0, 0x18, 0x51, 0xb0, 0x71, 0x00, 0x2b, 0xa8, 0x88, 0xe2, 0x51, 0x8f, 0xbd, + 0x1d, 0x3d, 0x47, 0xb1, 0x82, 0x82, 0xa2, 0x21, 0x31, 0x01, 0x12, 0x10, 0x36, 0x65, 0x03, 0xa4, 0x90, 0x00, 0xc9, 0x2e, 0x79, 0xef, 0x77, 0x66, 0x27, 0x10, 0x50, 0xcf, 0xd1, 0xfb, 0xbf, 0xf7, 0x73, 0xaf, 0x7c, 0xbe, 0xce, 0xec, 0xec, 0xcc, 0x5b, 0x7f, 0xcf, 0xf3, 0x3e, 0xcf, 0x64, 0x77, 0xb6, 0x63, 0x68, 0x48, 0x9f, 0x99, 0x83, 0x5a, 0xb5, 0x19, 0x32, 0xb3, 0x77, 0x97, 0xa1, 0xc1, 0xb3, 0xfc, 0x1b, 0xfa, 0x58, 0x6b, 0x7e, 0xe8, 0xdc, 0xbe, 0xf6, 0x29, 0x7e, 0xfd, 0xbb, 0x8c, 0x69, 0x68, 0xa9, 0xe7, 0xd7, 0xa8, 0xbe, 0xb9, 0x6d, 0xc4, 0xfc, 0xfe, 0x83, 0x17, 0x45, 0xb4, 0xf1, 0xaf, 0xbf, 0xba, 0x61, 0x3d, 0x51, 0x3b, 0x2e, 0x99, 0xfa, 0xb8, 0xdc, 0xfa, 0x2c, 0xda, 0xa9, 0xfa, 0xb3, 0x68, 0xa7, 0x89, 0x5b, 0xc7, 0xa3, 0xee, 0xb3, 0x68, 0x4d, 0x99, 0xbb, 0x6a, 0x3e, + 0xd9, 0xb5, 0x4b, 0x19, 0xe5, 0x9b, 0x7c, 0xad, 0x2d, 0x23, 0xf3, 0xb5, 0xe9, 0x54, 0xcd, 0x61, 0xa5, 0x1f, 0xa5, 0x44, 0x7b, 0x3d, 0x21, 0xaa, 0xb5, 0xa7, 0x38, 0xeb, 0xcf, 0x10, 0xd0, 0x27, 0x57, 0x2f, 0x2c, 0xa0, 0xa1, 0x49, 0x6d, 0xd2, 0x35, 0x90, 0xcb, 0xab, 0x77, 0xed, 0xa2, 0xee, 0x53, 0x9c, 0xdb, 0xdc, 0xeb, 0x21, 0x65, 0x3d, 0xe7, 0x06, 0x6a, 0xf7, 0xf7, 0xcc, 0xc6, 0x77, 0xfb, 0xf5, 0xfa, 0x8d, 0x85, 0x7f, 0xae, 0x98, 0x14, 0xd8, 0xba, 0xdd, 0x0d, 0x3d, 0xb4, 0xa9, 0xbb, 0x02, 0xf5, 0x51, 0xd6, 0x87, 0x0c, 0x5d, 0x30, 0xa4, 0xff, 0x00, 0x55, 0x69, 0xd8, 0xc0, 0x72, 0x5b, 0x50, 0xff, 0x7e, 0xbb, 0xbc, 0x9f, 0x1e, 0x1a, 0x33, 0xb6, 0x63, 0xfd, 0x05, 0xf5, 0x1a, 0xfa, 0x0e, 0x9b, 0xa2, 0x64, 0xd7, 0xf6, 0x33, 0x8f, 0x3a, 0x82, 0xb5, 0x7e, 0xfa, 0x69, 0x77, + 0xc8, 0x27, 0x88, 0xeb, 0x1f, 0x3f, 0xd1, 0x9a, 0xe7, 0x8d, 0xcf, 0x98, 0x66, 0xc2, 0xab, 0xb5, 0xef, 0xd8, 0xce, 0xa6, 0xcd, 0x49, 0x60, 0x9f, 0xde, 0xc3, 0xf4, 0x67, 0x37, 0x7a, 0xf9, 0x9a, 0x43, 0x94, 0xa6, 0x81, 0xfa, 0x33, 0xaf, 0x7a, 0x28, 0x7a, 0xff, 0xfb, 0x54, 0x87, 0xb4, 0x0f, 0xf8, 0xd8, 0xbf, 0x65, 0xd3, 0x4d, 0x45, 0x8d, 0x5b, 0x04, 0xb4, 0xac, 0x5f, 0x7a, 0x7f, 0x8b, 0x16, 0xea, 0xa7, 0xd6, 0xdb, 0x9a, 0x76, 0xd9, 0xb9, 0xd3, 0x67, 0x64, 0xd3, 0xcb, 0x8d, 0xfc, 0x6b, 0x32, 0x82, 0xfd, 0xcd, 0x4d, 0x03, 0x6b, 0x5e, 0x6d, 0xe0, 0xaf, 0xf8, 0xfa, 0xfb, 0xd5, 0x7c, 0xa9, 0x1b, 0xac, 0xa7, 0x1d, 0x8d, 0x7d, 0xb5, 0x0f, 0xa4, 0xb5, 0xd2, 0x7f, 0x2a, 0x65, 0xaa, 0xfe, 0x1c, 0x6c, 0xfd, 0xfb, 0xdf, 0x9e, 0xa1, 0xf6, 0x11, 0x3e, 0x01, 0x01, 0xde, 0x8c, 0x91, + 0x82, 0xe2, 0xfa, 0x04, 0x98, 0x1a, 0xd7, 0x4c, 0xdd, 0xe9, 0xdd, 0x68, 0x83, 0xe9, 0xd4, 0xb5, 0x17, 0x8c, 0xeb, 0xbd, 0xb6, 0x72, 0x7d, 0x6b, 0xed, 0x6f, 0x87, 0x2d, 0xea, 0xf9, 0xfc, 0xf6, 0x39, 0xc7, 0x75, 0xe7, 0xad, 0xb5, 0xb0, 0xb5, 0x6e, 0x12, 0x16, 0xe0, 0xab, 0x1a, 0xcf, 0x39, 0xee, 0xaf, 0xfd, 0x80, 0x33, 0xab, 0xdd, 0x8d, 0x87, 0x1c, 0x07, 0x7a, 0x1e, 0x72, 0xac, 0xfd, 0x65, 0xc0, 0x6b, 0xeb, 0xfd, 0x5e, 0x3f, 0x35, 0x6c, 0x1f, 0xd4, 0x2e, 0x20, 0xc3, 0x64, 0xba, 0x2f, 0x25, 0x28, 0xc8, 0x7a, 0x54, 0x6d, 0x68, 0xb1, 0x04, 0xa8, 0xdf, 0xab, 0xd8, 0xbe, 0x36, 0xc1, 0xff, 0x6c, 0xd5, 0xa1, 0x26, 0xa5, 0x75, 0x6b, 0xe5, 0x91, 0xc0, 0x76, 0x0d, 0x6a, 0x46, 0x9b, 0x4e, 0x99, 0x03, 0x6a, 0xd6, 0x07, 0x74, 0x0a, 0x6a, 0xdc, 0xa9, 0x91, 0xb2, 0x31, 0xc0, 0x7c, + 0x93, 0x9e, 0x82, 0x44, 0xdb, 0x08, 0x9b, 0xaf, 0x72, 0xfd, 0x91, 0xd2, 0x53, 0xf5, 0x47, 0x4a, 0x7b, 0x5a, 0x15, 0x24, 0x82, 0x5a, 0x37, 0x0d, 0xf0, 0x51, 0x9b, 0x7a, 0x3e, 0xc0, 0xd1, 0x51, 0x69, 0x72, 0xfd, 0x91, 0xcb, 0x3d, 0x94, 0x30, 0x44, 0x35, 0x25, 0xa0, 0xb9, 0x5f, 0xcd, 0xe3, 0x5e, 0xf7, 0x4d, 0x09, 0x6a, 0x54, 0x5f, 0x49, 0x36, 0x99, 0xb4, 0xca, 0xeb, 0xf9, 0x2b, 0xdf, 0x77, 0x68, 0x55, 0x73, 0xa7, 0xe9, 0x94, 0xa5, 0x61, 0x4d, 0xef, 0xa0, 0xf6, 0x01, 0xca, 0x33, 0xc2, 0xf0, 0x25, 0x77, 0xeb, 0x7e, 0xf8, 0x0f, 0xd6, 0x81, 0xa9, 0x9a, 0xc5, 0x4d, 0xfb, 0xdd, 0x75, 0x40, 0xbd, 0xb1, 0x0e, 0x34, 0xbe, 0xb1, 0x0e, 0x10, 0xff, 0x29, 0x77, 0x8c, 0x1b, 0x31, 0x72, 0xec, 0xe2, 0xc5, 0x13, 0x13, 0xc6, 0xb6, 0x69, 0x3b, 0x2e, 0x61, 0xfc, 0xe2, 0x9d, 0x3e, 0x3e, + 0x23, 0x06, 0xf7, 0x1b, 0x5e, 0xa9, 0x2c, 0x6f, 0x37, 0x6c, 0x4e, 0x9f, 0x7e, 0xf3, 0x23, 0xda, 0xb9, 0xf4, 0xf9, 0xf4, 0xd4, 0xff, 0xaa, 0x5e, 0x7f, 0x2b, 0xed, 0x7b, 0x76, 0x2d, 0x75, 0x65, 0xf9, 0xd6, 0x7e, 0xb2, 0xa9, 0x36, 0xfa, 0xbe, 0x2e, 0x2f, 0xed, 0xcb, 0xf1, 0x81, 0x8d, 0x02, 0x1a, 0xf8, 0xa9, 0xbf, 0xd3, 0x06, 0x3d, 0x99, 0xaa, 0x55, 0x58, 0xad, 0xc7, 0x6d, 0xaa, 0x8c, 0x9b, 0x30, 0x72, 0xf4, 0xd8, 0xa9, 0xca, 0x9b, 0x0f, 0xb4, 0x0f, 0x7c, 0xd8, 0xd2, 0x24, 0xa8, 0xcf, 0x4e, 0xc7, 0xa4, 0x84, 0x31, 0xb6, 0xa9, 0x13, 0x5b, 0x06, 0xf9, 0x34, 0x19, 0xdd, 0xa7, 0xcf, 0xc0, 0x73, 0x35, 0xeb, 0x43, 0x72, 0x91, 0x58, 0xb1, 0xe9, 0x94, 0x6d, 0xe0, 0xd4, 0x9e, 0xe3, 0x13, 0x5b, 0x04, 0x84, 0xe8, 0x73, 0xd1, 0x82, 0xb6, 0x2d, 0xa4, 0x6d, 0x66, 0xed, 0x77, 0x95, + 0x4d, 0xb5, 0xbf, 0x72, 0x3c, 0x55, 0x8b, 0x8a, 0xa6, 0x29, 0x9e, 0xa7, 0x33, 0x68, 0xc2, 0xa8, 0x7d, 0xa4, 0xc8, 0xc2, 0xcc, 0x5d, 0x99, 0xa6, 0x91, 0x3e, 0x97, 0xae, 0xed, 0x35, 0x2d, 0x5e, 0xaf, 0x5d, 0x7f, 0x87, 0xee, 0xd7, 0x92, 0xaf, 0x3f, 0xa3, 0x4a, 0xb7, 0x12, 0x23, 0x80, 0x99, 0xab, 0xf5, 0xa5, 0x51, 0x40, 0x3d, 0x2b, 0xee, 0x4d, 0x7b, 0x46, 0x95, 0xd1, 0x0f, 0xed, 0x33, 0x38, 0xc6, 0xdd, 0x0c, 0xfd, 0xe3, 0x38, 0xca, 0xf6, 0x89, 0x33, 0x76, 0x76, 0x9d, 0x9a, 0x32, 0x76, 0x5c, 0xca, 0xd4, 0xae, 0x3b, 0x67, 0x7a, 0x37, 0x5c, 0x30, 0x4d, 0x79, 0xb7, 0x66, 0xd2, 0xc8, 0xe4, 0xe9, 0xdd, 0xbb, 0x4f, 0x4f, 0x1e, 0xa9, 0xed, 0x4f, 0x5b, 0xa0, 0x7f, 0xe7, 0xd7, 0xeb, 0x09, 0xef, 0xc1, 0xd4, 0xd5, 0x51, 0xbb, 0xff, 0x6d, 0x0b, 0xd4, 0x9f, 0x45, 0xee, 0xf9, 0x41, + 0xf2, 0xba, 0xca, 0xe1, 0xdd, 0xf0, 0xd6, 0xcd, 0x3c, 0x7a, 0xf6, 0x6a, 0x5b, 0xfb, 0xa3, 0xe4, 0xda, 0x1d, 0x7f, 0x5d, 0xc8, 0xfd, 0x34, 0x1d, 0x0f, 0x52, 0x34, 0x59, 0x37, 0xa9, 0x55, 0xb5, 0xf7, 0xe0, 0x5f, 0x1f, 0xdc, 0xa9, 0xe5, 0x04, 0xbe, 0xde, 0xbb, 0xd3, 0xcf, 0xde, 0xd3, 0xd9, 0x9a, 0xe2, 0x63, 0xf6, 0xb1, 0xd4, 0xbb, 0xdf, 0xa7, 0x79, 0x50, 0xb7, 0xa0, 0xa6, 0xe6, 0xfb, 0xea, 0x59, 0x48, 0x8e, 0x53, 0xbd, 0x6e, 0x0b, 0xd4, 0x15, 0x9e, 0xe3, 0xd7, 0xcc, 0xdf, 0xbf, 0x99, 0x5f, 0x4d, 0x07, 0xd3, 0xa9, 0x36, 0x35, 0xcb, 0x1a, 0x75, 0x6a, 0xda, 0xbd, 0x9d, 0xb2, 0xb3, 0x7e, 0xe3, 0xc0, 0x06, 0x35, 0xcb, 0xda, 0x74, 0x6d, 0xdc, 0x31, 0x40, 0xd9, 0xe9, 0x1f, 0xa4, 0x8f, 0xed, 0x17, 0x5e, 0x47, 0x4c, 0x8d, 0x7c, 0x27, 0x08, 0x55, 0x7b, 0xfe, 0xa1, 0xfe, 0xa9, + 0x73, 0xed, 0xc1, 0xcc, 0x71, 0x0a, 0x2b, 0xbc, 0xf6, 0xe0, 0x3a, 0xcf, 0x97, 0x43, 0x9a, 0x98, 0xbd, 0xf4, 0x1f, 0xff, 0x56, 0xbe, 0xe9, 0xd8, 0xae, 0xe1, 0x47, 0xbe, 0xcd, 0x02, 0x6f, 0xdf, 0xe4, 0x3b, 0xa1, 0x5f, 0x87, 0x1a, 0xef, 0xfa, 0xcd, 0x4c, 0xeb, 0x3d, 0x71, 0x0c, 0xe5, 0x78, 0x3d, 0xab, 0x97, 0x13, 0xa8, 0x59, 0x8c, 0xe7, 0xb9, 0x78, 0x22, 0x45, 0x1b, 0xed, 0x38, 0xed, 0x69, 0x78, 0x8d, 0x1a, 0x6a, 0x4f, 0xc3, 0xf3, 0x14, 0xea, 0x7b, 0x53, 0xa1, 0xad, 0x7f, 0xaf, 0x78, 0x53, 0xfd, 0xdf, 0xa9, 0xe9, 0x46, 0x8d, 0x8a, 0x88, 0xf4, 0x8e, 0x53, 0x42, 0x7d, 0xf2, 0x3d, 0xdf, 0x67, 0xf3, 0xdc, 0xbb, 0xd2, 0x8c, 0x54, 0xff, 0xfc, 0x85, 0xe7, 0x2b, 0x09, 0x42, 0xff, 0x3e, 0x9b, 0xe7, 0x79, 0x12, 0x75, 0x3f, 0x45, 0xa2, 0x9d, 0x55, 0xf7, 0xfb, 0x6c, 0x9e, 0xcf, + 0x64, 0x34, 0xae, 0x5d, 0xb4, 0x94, 0xd0, 0x1e, 0x7d, 0xfb, 0xf6, 0xe8, 0x32, 0x71, 0x60, 0xeb, 0xd6, 0x03, 0x27, 0x76, 0xf1, 0x49, 0xef, 0xd7, 0xad, 0x6b, 0xbf, 0x05, 0xda, 0x53, 0x4b, 0xba, 0x8e, 0xe9, 0x13, 0xec, 0x89, 0x35, 0xff, 0x5f, 0xd6, 0x3d, 0xc8, 0x6b, 0x87, 0xa9, 0xb9, 0xef, 0x6a, 0xac, 0xb4, 0x57, 0x44, 0x8f, 0xfa, 0xac, 0xfe, 0x2d, 0x6f, 0x8e, 0x73, 0xa9, 0x7d, 0x85, 0xd0, 0x9f, 0xf7, 0x26, 0xae, 0x3f, 0xee, 0xad, 0x59, 0xdb, 0x0e, 0x1d, 0xf5, 0xbb, 0x9d, 0x3d, 0x94, 0xdf, 0x44, 0xb7, 0xbe, 0x9e, 0xe0, 0xb6, 0xed, 0xe5, 0xf9, 0x8b, 0x3b, 0x8f, 0x8d, 0x1c, 0xd0, 0x7c, 0xe0, 0x80, 0xb0, 0x26, 0x9d, 0x2c, 0x01, 0xf5, 0x5a, 0xb4, 0xee, 0x1a, 0x12, 0x31, 0x2a, 0xa8, 0xfb, 0xd8, 0xbe, 0x5b, 0xbd, 0xb7, 0xd8, 0xd2, 0x67, 0x0e, 0x5e, 0x30, 0xd4, 0x66, 0x09, + 0x6a, 0xd3, 0xa2, 0x4f, 0x7d, 0xff, 0x86, 0xfe, 0x63, 0xc6, 0xd9, 0xc2, 0x7b, 0xb6, 0x31, 0xd7, 0x1c, 0xd2, 0xef, 0x85, 0x49, 0xed, 0x23, 0x2f, 0x67, 0xb4, 0xdf, 0xd4, 0xf1, 0x7a, 0x56, 0x7f, 0xc6, 0xe8, 0xf9, 0x6b, 0xaf, 0xdd, 0xfc, 0xfb, 0xd9, 0xda, 0xef, 0x5f, 0x68, 0xe7, 0x68, 0xbf, 0x35, 0xa2, 0x9d, 0xa3, 0xff, 0xd6, 0xc8, 0x45, 0xed, 0xda, 0x6b, 0x9f, 0xeb, 0xd7, 0x1e, 0xac, 0x3d, 0xee, 0x13, 0x71, 0xad, 0xb5, 0x7e, 0xfc, 0xbd, 0x5b, 0x8e, 0x8f, 0xb8, 0xd6, 0x51, 0xff, 0x1d, 0xe3, 0x50, 0xbd, 0x9c, 0xef, 0xae, 0x1f, 0x1f, 0x7e, 0x4d, 0x7f, 0xa6, 0x6a, 0x8d, 0xb3, 0x6e, 0x1b, 0xb4, 0xdf, 0x55, 0xba, 0xf6, 0x9c, 0x7e, 0x3c, 0xe3, 0x96, 0xe3, 0x83, 0xae, 0x55, 0x88, 0x1b, 0xed, 0x79, 0xf1, 0x7a, 0x7b, 0x3e, 0xf4, 0xb4, 0xc7, 0x73, 0xdc, 0x1c, 0x78, 0xfd, 0xf8, + 0xc7, 0x9e, 0xe3, 0x35, 0x3f, 0xdd, 0xd2, 0x9e, 0x81, 0x5a, 0xbd, 0xcc, 0xc7, 0x5b, 0x35, 0x2f, 0xf9, 0xb6, 0xd6, 0x7f, 0xf3, 0xbd, 0x4f, 0x44, 0x28, 0x8b, 0x9e, 0xc9, 0xec, 0x63, 0x8a, 0xd4, 0x3e, 0xcc, 0x10, 0x3d, 0xd1, 0xa2, 0x98, 0xcd, 0xca, 0x1d, 0x9a, 0x05, 0xd4, 0xfe, 0xfa, 0x2a, 0xff, 0x6f, 0x84, 0xb3, 0xd1, 0xff, 0x6b, 0xab, 0x65, 0x49, 0xed, 0x03, 0x82, 0x7c, 0x6f, 0x0a, 0x44, 0xfa, 0x04, 0xf8, 0xb6, 0xee, 0x10, 0x72, 0x4f, 0xf1, 0xb5, 0xc0, 0xcc, 0x4c, 0xd3, 0x85, 0xe3, 0x2f, 0xd7, 0x0c, 0x0e, 0x6e, 0xa3, 0xcc, 0x37, 0x3d, 0x70, 0x6d, 0x8d, 0xcf, 0x33, 0xd7, 0xb6, 0x98, 0xd6, 0xba, 0xfc, 0x68, 0xe3, 0x4c, 0x59, 0xe6, 0x33, 0x59, 0x1f, 0xcb, 0x9f, 0x8c, 0xdf, 0x65, 0x69, 0x5c, 0xfb, 0x7c, 0x60, 0x9f, 0x62, 0xfd, 0x78, 0x86, 0x71, 0x7c, 0x97, 0xe7, 0x79, + 0xf7, 0x35, 0xff, 0xf4, 0x5e, 0xe3, 0x4d, 0xd0, 0x2e, 0x86, 0x8b, 0x13, 0x11, 0xfe, 0x5d, 0x14, 0x5f, 0x9f, 0xfe, 0x58, 0x7a, 0x23, 0x6d, 0xb5, 0x35, 0xee, 0x2c, 0xf5, 0x12, 0xbe, 0x3e, 0xde, 0x3e, 0xbe, 0xde, 0xc9, 0xc2, 0xdb, 0xaa, 0x7f, 0x6d, 0x31, 0x12, 0x6f, 0xaf, 0x27, 0xdc, 0x89, 0x13, 0x6b, 0x55, 0x1d, 0x7f, 0xe3, 0x7e, 0x5c, 0xec, 0xf5, 0xdb, 0x77, 0x7f, 0xe6, 0xc2, 0xa4, 0x9b, 0x2f, 0x8c, 0x18, 0x70, 0xfd, 0x1a, 0xd5, 0xcb, 0xe2, 0xa5, 0xdd, 0xd0, 0xfb, 0x4f, 0xd7, 0x8a, 0x1b, 0x97, 0xce, 0x9b, 0x17, 0xd1, 0x42, 0x88, 0x61, 0x43, 0x06, 0x87, 0x87, 0xf6, 0xe8, 0xd4, 0xa1, 0x6d, 0xeb, 0x96, 0xb7, 0x35, 0x0e, 0xac, 0xef, 0xa7, 0xa5, 0x23, 0xed, 0xf4, 0x9f, 0xc7, 0xb8, 0xf5, 0xa6, 0x9e, 0xaf, 0x27, 0x14, 0xed, 0xd7, 0xa7, 0xb5, 0x27, 0x06, 0xad, 0x9b, 0x88, + 0xfa, 0x9a, 0x6f, 0xdc, 0xda, 0xab, 0x6a, 0x1d, 0xb1, 0x70, 0xd0, 0xdd, 0xdb, 0x1b, 0x29, 0xf1, 0xc6, 0x4d, 0xbd, 0x45, 0x69, 0x0b, 0x9f, 0x4d, 0x89, 0x08, 0xbc, 0xcd, 0xf4, 0x94, 0x57, 0x8f, 0xe9, 0xc9, 0xa3, 0x87, 0x2f, 0x1a, 0x1d, 0x4a, 0x7e, 0x3a, 0xa0, 0xdd, 0xa2, 0x65, 0x2b, 0xfa, 0xc7, 0xbd, 0xb3, 0x79, 0x7c, 0xef, 0xd0, 0xda, 0x1f, 0xcb, 0x50, 0xb6, 0x85, 0x47, 0x8d, 0xeb, 0xb4, 0xfd, 0x9e, 0x6b, 0x33, 0x3d, 0x3f, 0x79, 0xd1, 0xb6, 0xeb, 0x90, 0xb4, 0x03, 0x09, 0x3d, 0x5a, 0xcd, 0x4f, 0x9f, 0xd7, 0xed, 0x7a, 0xc2, 0x3a, 0x76, 0xeb, 0x77, 0xf7, 0x2d, 0x7c, 0x61, 0xc4, 0x27, 0x9e, 0x9b, 0x7c, 0xfa, 0x5c, 0x4d, 0x90, 0xe5, 0x5e, 0x19, 0xda, 0x6f, 0x16, 0x78, 0x0b, 0xe3, 0x77, 0x21, 0xee, 0xbf, 0xfe, 0x2c, 0xe1, 0x36, 0xfa, 0x71, 0x93, 0x71, 0xfc, 0xef, 0xfa, + 0x7d, 0x82, 0x52, 0xef, 0x91, 0x5e, 0xa1, 0x3e, 0xf7, 0x93, 0x69, 0x2c, 0xf5, 0xdc, 0x62, 0x6d, 0xac, 0x7f, 0x45, 0x77, 0xa9, 0xfe, 0xd5, 0x0d, 0xdc, 0x4c, 0x0c, 0x23, 0xd4, 0x5c, 0x9f, 0x95, 0x6e, 0xfa, 0xe7, 0xb3, 0x71, 0x3e, 0xa9, 0xb7, 0xbe, 0x17, 0x71, 0x9b, 0xfe, 0x80, 0x2f, 0xe3, 0xbd, 0xda, 0xaf, 0xad, 0x2c, 0xd0, 0xbf, 0xfb, 0xa1, 0x3d, 0x56, 0xa8, 0x9e, 0xe7, 0x67, 0x41, 0x49, 0x59, 0x7c, 0x7c, 0x9b, 0x92, 0x2f, 0x5c, 0x0f, 0xe4, 0x5b, 0x87, 0x5d, 0xbf, 0xa1, 0xf6, 0xe2, 0xc0, 0x66, 0xdd, 0xda, 0x04, 0x05, 0xb6, 0xe9, 0xd6, 0xc2, 0xd4, 0xc8, 0xd8, 0xed, 0xde, 0xdc, 0x7b, 0xcf, 0x02, 0xff, 0x96, 0x5d, 0x5a, 0xb6, 0xea, 0x16, 0x5c, 0x9f, 0x9d, 0xae, 0xc1, 0xda, 0x8e, 0xf6, 0xe1, 0x4d, 0x5f, 0xe1, 0x15, 0x6a, 0x6e, 0x88, 0x4d, 0x69, 0x9f, 0x0f, 0xf3, 0xf2, + 0xd1, 0x9e, 0xa4, 0x20, 0xea, 0x3e, 0x5f, 0xca, 0x5b, 0xcb, 0xf1, 0xa6, 0x2a, 0x22, 0xb0, 0x51, 0x43, 0x7f, 0xd5, 0x62, 0xf6, 0x15, 0x4d, 0x95, 0xa6, 0xbe, 0x9e, 0xef, 0x3a, 0x79, 0x93, 0xab, 0x0d, 0xf5, 0xd6, 0x3e, 0xd8, 0xa3, 0x3d, 0x2d, 0xd2, 0xd4, 0xba, 0xcb, 0x84, 0x09, 0x93, 0x17, 0x24, 0x8d, 0x19, 0x1c, 0x33, 0x67, 0xfa, 0xcc, 0x3b, 0xfa, 0x6d, 0x59, 0xfb, 0x70, 0x93, 0x76, 0x3d, 0x7d, 0x77, 0x36, 0x69, 0xd3, 0xa1, 0x4d, 0x93, 0xbb, 0x9a, 0xd2, 0xe2, 0xa6, 0xdb, 0x36, 0x77, 0x19, 0xd8, 0x56, 0x7f, 0xca, 0x17, 0x99, 0xca, 0x26, 0xc6, 0xea, 0x71, 0xea, 0xd5, 0x9f, 0x69, 0xa6, 0xd7, 0x72, 0xa7, 0x4f, 0xed, 0xcf, 0x72, 0x35, 0x17, 0x93, 0x6e, 0xa9, 0xd1, 0xf7, 0x4f, 0xd7, 0xe8, 0xd3, 0xec, 0xb7, 0x35, 0xfe, 0x3f, 0xeb, 0xa7, 0xd2, 0xc9, 0xe7, 0xac, 0x29, 0xd0, + 0x1c, 0x41, 0xe6, 0xce, 0x3a, 0x14, 0xa0, 0x3d, 0x6c, 0x4a, 0xf7, 0xfc, 0x4b, 0x6f, 0x5d, 0x70, 0xb4, 0xbf, 0xe1, 0xe0, 0x06, 0x52, 0x7f, 0xb3, 0x10, 0xbd, 0xd7, 0xb7, 0x83, 0xe7, 0xd9, 0x70, 0x86, 0x99, 0xe8, 0xcf, 0x4f, 0xf4, 0xfc, 0x0a, 0x67, 0xd3, 0x26, 0x8d, 0x4d, 0x62, 0xc0, 0xf4, 0xb0, 0x16, 0x23, 0x06, 0xf7, 0x1d, 0xd8, 0x7b, 0xb0, 0xa5, 0xbe, 0xaf, 0x77, 0x87, 0xb6, 0x9d, 0x7d, 0xce, 0xb6, 0x1d, 0x34, 0xb9, 0xcb, 0xc8, 0xd1, 0xe1, 0xdd, 0xdb, 0xf7, 0x57, 0x94, 0xf6, 0x1d, 0x2c, 0x96, 0x66, 0x5a, 0x3b, 0xbc, 0xed, 0xa6, 0x40, 0x9f, 0x83, 0xb4, 0x63, 0x86, 0xa7, 0x1d, 0x01, 0x46, 0x3b, 0x8c, 0x84, 0xd8, 0xa6, 0xeb, 0xb2, 0x87, 0xe7, 0x87, 0x74, 0xb5, 0x76, 0xd4, 0x39, 0xfe, 0x47, 0x4d, 0xf3, 0xb4, 0xcd, 0xf7, 0x2f, 0xb4, 0xcd, 0xdb, 0xfe, 0xbb, 0x6d, 0xfb, + 0xff, 0x65, 0x8c, 0x7c, 0xf2, 0x68, 0xc7, 0x60, 0xda, 0x31, 0xfa, 0x3d, 0x1f, 0xc5, 0xf3, 0xad, 0x77, 0x4f, 0x73, 0xb4, 0x4f, 0x46, 0x2c, 0xbd, 0xf5, 0x4f, 0xec, 0x5a, 0x73, 0x58, 0xba, 0x53, 0x7f, 0xf3, 0xa4, 0xcd, 0xf7, 0x9a, 0xb4, 0xeb, 0xa8, 0x37, 0xa7, 0xaf, 0xf6, 0x75, 0x77, 0xe3, 0x2b, 0x58, 0xd7, 0x9f, 0xc5, 0xa8, 0x0c, 0xea, 0xdc, 0xa1, 0xbd, 0xc9, 0xb7, 0xbe, 0x65, 0x70, 0xef, 0x81, 0x7d, 0x07, 0x8f, 0x68, 0x11, 0x36, 0xa3, 0xbf, 0xaf, 0xa5, 0x99, 0xc5, 0xd2, 0xa1, 0xa3, 0xc9, 0xd4, 0xbf, 0x7d, 0xf7, 0xf0, 0xe9, 0x23, 0xbb, 0x4c, 0xe8, 0xd7, 0x4a, 0xd7, 0x8e, 0x77, 0x24, 0x73, 0xf6, 0x2f, 0xed, 0x6f, 0x7f, 0x37, 0xb5, 0x27, 0xc0, 0x68, 0x8f, 0xf1, 0xfd, 0x61, 0x9b, 0xe7, 0x7b, 0x81, 0x7a, 0x53, 0x6e, 0x1c, 0xf2, 0xb4, 0xc2, 0xf7, 0x2f, 0xb4, 0xc2, 0x7b, + 0x2b, 0xad, 0x68, 0xdf, 0xe9, 0xe6, 0x56, 0x88, 0xff, 0xdf, 0xc6, 0x05, 0xff, 0x1b, 0x2d, 0x4b, 0x2d, 0x3e, 0xbe, 0x17, 0xf1, 0xbf, 0xda, 0xed, 0x7e, 0xb3, 0xcf, 0x63, 0xa6, 0x17, 0x95, 0x7d, 0x9e, 0x58, 0x46, 0xd9, 0x67, 0xc4, 0x32, 0x04, 0x58, 0x3e, 0x6f, 0x6a, 0xbf, 0xf5, 0xe3, 0x3d, 0x51, 0x8f, 0x05, 0x1e, 0x53, 0xd6, 0xd6, 0xfe, 0x6e, 0x9a, 0x75, 0x83, 0xf9, 0x36, 0xd1, 0x41, 0xa8, 0x7a, 0x8c, 0xdf, 0xcd, 0xd7, 0xec, 0xf9, 0xdb, 0xa0, 0xef, 0x39, 0xe3, 0xb7, 0xdd, 0xcb, 0xcd, 0x87, 0x7d, 0xbe, 0xd4, 0x3e, 0x47, 0xae, 0xbf, 0xff, 0xb0, 0xf7, 0x7a, 0xfd, 0xfd, 0x37, 0xbd, 0x0f, 0xd5, 0x5e, 0xaf, 0x8e, 0xd4, 0x7f, 0xdb, 0xd7, 0x73, 0xfd, 0xc3, 0x3e, 0x97, 0x3d, 0xef, 0xfb, 0x8e, 0xd0, 0xfd, 0x9a, 0x4d, 0x3e, 0xe1, 0xfb, 0xbd, 0xcf, 0x29, 0x31, 0x5c, 0x59, 0xe8, + 0x59, 0x03, 0x9a, 0x84, 0x33, 0xa2, 0xe6, 0xfe, 0x8d, 0x83, 0x48, 0xb4, 0x4d, 0x4d, 0x14, 0x45, 0x6d, 0x85, 0xc7, 0xf7, 0x9e, 0x70, 0xdb, 0x4d, 0x6f, 0xf8, 0xdc, 0x78, 0x63, 0xde, 0xc4, 0x37, 0x1b, 0x73, 0x59, 0xb8, 0xf0, 0xb5, 0x0a, 0xab, 0xaf, 0x60, 0xfd, 0x45, 0xe2, 0xaa, 0xc2, 0xfa, 0xab, 0x78, 0xa7, 0x6a, 0xf7, 0xd5, 0xed, 0x13, 0x2d, 0x3e, 0x26, 0xe3, 0xfe, 0x68, 0xa2, 0x67, 0xdf, 0x3c, 0x8f, 0xf5, 0x78, 0x89, 0x7e, 0x13, 0xd0, 0xf3, 0x5b, 0x15, 0x5a, 0xc5, 0x43, 0x7f, 0xb7, 0x04, 0xe1, 0xe7, 0x67, 0x9f, 0x28, 0xfe, 0x5c, 0x29, 0x36, 0xfd, 0x17, 0x2f, 0xae, 0x97, 0xe2, 0xa3, 0xf8, 0xfc, 0xb6, 0x14, 0x71, 0xa3, 0x0c, 0x85, 0xc0, 0xe0, 0xe6, 0x22, 0x22, 0x86, 0xff, 0xd1, 0xd5, 0xfa, 0xc7, 0x8d, 0xb5, 0x98, 0xeb, 0xbf, 0x95, 0x31, 0x8f, 0x35, 0xaf, 0xd1, 0xf0, + 0x61, 0x03, 0x07, 0x90, 0xef, 0x05, 0x74, 0x6a, 0xdc, 0x39, 0xa8, 0x6d, 0xe3, 0xd6, 0xda, 0x5f, 0x0c, 0x94, 0x7e, 0x37, 0x7d, 0x56, 0xb8, 0x81, 0x16, 0xa0, 0x79, 0x35, 0xd5, 0x6e, 0x74, 0xea, 0x9f, 0x12, 0xd2, 0x72, 0x7b, 0xfd, 0x23, 0x6e, 0x9e, 0x2c, 0x4d, 0xff, 0xd9, 0xc6, 0x20, 0xf2, 0x5b, 0x82, 0x37, 0xa2, 0x8b, 0x41, 0x8a, 0x8f, 0x32, 0x60, 0xe8, 0xac, 0x0d, 0xb3, 0xbb, 0x7d, 0x19, 0xb3, 0x74, 0xf4, 0x9a, 0xb0, 0xc0, 0x2f, 0x8a, 0x43, 0x5b, 0xcf, 0xea, 0x69, 0x6b, 0x1a, 0x61, 0xbb, 0x7d, 0xc2, 0xdc, 0x85, 0xab, 0x02, 0x9a, 0xbe, 0xd4, 0xcd, 0xbb, 0x69, 0xa3, 0x68, 0xc5, 0x56, 0x73, 0xba, 0x5f, 0x57, 0xe5, 0xf1, 0x2f, 0x94, 0x25, 0x7e, 0x8d, 0x82, 0x9a, 0x35, 0x4a, 0xf0, 0xba, 0x12, 0x30, 0xc0, 0xbe, 0x7d, 0xf6, 0xb5, 0x35, 0xa6, 0x07, 0x12, 0xd6, 0x0c, 0xee, + 0xdb, 0xe2, 0x5a, 0x4b, 0x25, 0xbb, 0xe6, 0xb3, 0xc9, 0x21, 0x5d, 0xac, 0xdd, 0x6c, 0xb7, 0xd9, 0x83, 0xee, 0x5b, 0x66, 0x5a, 0xdc, 0xb8, 0xf1, 0xd4, 0xa6, 0x4a, 0xcf, 0x80, 0xc0, 0x9a, 0xbf, 0x6f, 0xbf, 0xf6, 0xfa, 0xc0, 0xc1, 0x26, 0x53, 0x3b, 0xaf, 0x86, 0xd7, 0xaa, 0x95, 0xa2, 0x06, 0x8d, 0xeb, 0xf9, 0x0a, 0x5f, 0xd1, 0x53, 0xee, 0xf5, 0x09, 0xf0, 0xc9, 0x15, 0x56, 0xed, 0xbb, 0x40, 0x62, 0x88, 0x58, 0x28, 0x36, 0x2a, 0xde, 0xef, 0xb5, 0x52, 0x7c, 0xcd, 0xb5, 0xdf, 0x37, 0x1c, 0x68, 0xd5, 0xee, 0xe8, 0x7b, 0x99, 0x62, 0x2d, 0x8a, 0xb7, 0x9f, 0x1a, 0x40, 0x62, 0x4a, 0x9e, 0x16, 0xdd, 0x50, 0x4b, 0x4c, 0xcd, 0x8a, 0x88, 0x6e, 0x44, 0xf0, 0xee, 0x5b, 0xdf, 0xec, 0x1b, 0x2d, 0xea, 0x8b, 0x06, 0xf5, 0x1b, 0xdc, 0x29, 0xea, 0xd5, 0xf3, 0x9f, 0x2f, 0xfc, 0xfd, 0xed, + 0xf5, 0xb4, 0x29, 0xeb, 0x44, 0x01, 0xc3, 0x6a, 0x0b, 0x10, 0xfe, 0xa2, 0x9e, 0xb7, 0x7f, 0xbd, 0xe8, 0xff, 0x5a, 0x50, 0x83, 0x06, 0xf5, 0xe7, 0x88, 0xfa, 0xf5, 0x1b, 0xcc, 0xd7, 0x4a, 0xc4, 0x7e, 0x23, 0x8c, 0x12, 0x98, 0xaf, 0xff, 0xbd, 0x22, 0x98, 0xb4, 0x1e, 0x21, 0x21, 0xda, 0x0f, 0x36, 0xae, 0x4a, 0x8b, 0x89, 0x9a, 0x35, 0x63, 0xd4, 0x08, 0xe2, 0xc1, 0x01, 0xda, 0x6f, 0xa5, 0x85, 0x74, 0x09, 0xe9, 0xd2, 0xbe, 0xad, 0x5f, 0x4b, 0xbf, 0x96, 0xcd, 0x9a, 0x04, 0x06, 0x34, 0xa8, 0xaf, 0xff, 0x88, 0x63, 0x60, 0xed, 0x43, 0x73, 0x35, 0xff, 0xdd, 0x51, 0x77, 0xdd, 0x61, 0xfa, 0xec, 0x69, 0xb7, 0x62, 0x5b, 0x2a, 0x81, 0x37, 0x7e, 0xf9, 0x5c, 0xff, 0x23, 0x9a, 0xf6, 0x07, 0x75, 0xcf, 0x97, 0xbd, 0x39, 0x4d, 0x31, 0xfe, 0x80, 0xd1, 0x52, 0xd1, 0x1f, 0x08, 0x57, 0xfb, + 0x97, 0x46, 0x68, 0x6f, 0xe4, 0x51, 0x2d, 0xb5, 0xa2, 0x29, 0xc2, 0x5b, 0xfb, 0x72, 0xb8, 0xf6, 0xf7, 0x8e, 0x30, 0x2f, 0xdf, 0x86, 0xc1, 0xed, 0x1b, 0x37, 0x6e, 0x1f, 0xdc, 0x70, 0x70, 0xbb, 0x16, 0x2d, 0xda, 0x86, 0x76, 0xe9, 0xa4, 0x04, 0x06, 0xb6, 0x6a, 0xec, 0x77, 0x25, 0xa0, 0xeb, 0xf8, 0x01, 0x03, 0xc6, 0x75, 0x09, 0xa8, 0xf4, 0x6b, 0xdc, 0x2a, 0x50, 0x09, 0xec, 0xd4, 0x25, 0xb4, 0x6d, 0x8b, 0x16, 0xed, 0x62, 0x95, 0x0f, 0xc7, 0x6e, 0xfa, 0x70, 0xc5, 0x8a, 0x0f, 0x37, 0x8d, 0x6d, 0xde, 0xb2, 0x79, 0x44, 0xf2, 0x53, 0xf3, 0xe6, 0x3d, 0xb9, 0x22, 0x82, 0x5d, 0xd7, 0xb3, 0xb7, 0x3f, 0x5f, 0xf8, 0xc4, 0x13, 0x85, 0xcf, 0xdf, 0xde, 0xa4, 0x45, 0x93, 0x16, 0x03, 0xe6, 0xac, 0x7b, 0xde, 0x6e, 0x7f, 0x7e, 0xdd, 0x9c, 0x01, 0xbc, 0x68, 0xa2, 0x7c, 0xd7, 0xb8, 0xc3, + 0x6d, 0x0d, 0x1b, 0xde, 0xd6, 0xa1, 0xf1, 0x47, 0x2d, 0xda, 0xb5, 0x6b, 0xd1, 0xab, 0xdd, 0x6d, 0xbe, 0x8d, 0x5a, 0x35, 0xb7, 0xd5, 0xfc, 0xad, 0x69, 0xc3, 0x76, 0xad, 0x82, 0x82, 0x5a, 0xb5, 0x6b, 0xd8, 0x54, 0x79, 0xa8, 0x79, 0xab, 0x46, 0xbe, 0xb7, 0xb5, 0xeb, 0xa5, 0x9d, 0xa0, 0x34, 0x49, 0xd8, 0x97, 0x34, 0x60, 0x40, 0xd2, 0xbe, 0x84, 0x97, 0x6d, 0x53, 0x63, 0xd6, 0x4e, 0x1c, 0x77, 0xf7, 0x1d, 0xbd, 0x7b, 0xdf, 0x71, 0xf7, 0xb8, 0x89, 0x6b, 0x63, 0xa6, 0xda, 0x5e, 0xde, 0x9c, 0xb9, 0x7b, 0xe6, 0xcc, 0xdd, 0x99, 0x9b, 0x5f, 0x0e, 0x9f, 0x3f, 0x67, 0xf1, 0xd0, 0xee, 0xb1, 0xf6, 0x85, 0xe3, 0xba, 0x74, 0x19, 0xb7, 0xd0, 0xbe, 0xb4, 0xc7, 0xd0, 0xc5, 0x73, 0xe6, 0x87, 0x0b, 0xfd, 0xbf, 0x36, 0xca, 0x73, 0xfa, 0xaf, 0x12, 0x69, 0xff, 0x7d, 0xa1, 0x7b, 0x23, 0x4f, + 0x3e, 0xea, 0xcd, 0x2b, 0xcf, 0xbe, 0x09, 0x05, 0x7e, 0x63, 0xec, 0x7b, 0xe1, 0xcf, 0x8f, 0x18, 0xfb, 0xde, 0xc2, 0x4f, 0x9c, 0x35, 0xf6, 0x7d, 0x44, 0x5f, 0xf2, 0x29, 0xcf, 0xbe, 0xaf, 0xf0, 0x53, 0xda, 0x18, 0xfb, 0x66, 0x11, 0xaa, 0x74, 0x37, 0xf6, 0x2d, 0xa2, 0x99, 0xb2, 0xd0, 0xd8, 0x57, 0x45, 0x7f, 0x65, 0xa5, 0xb1, 0x6f, 0xf5, 0x4e, 0x55, 0x5e, 0x33, 0xf6, 0xfd, 0x44, 0x33, 0xdf, 0x56, 0xc6, 0x7e, 0x3d, 0xd1, 0xdc, 0xb7, 0xbf, 0xb1, 0x5f, 0xbf, 0x4e, 0xdb, 0xfc, 0x45, 0x0b, 0xdf, 0x69, 0xc6, 0x7e, 0xc3, 0x3a, 0xed, 0x0c, 0xd0, 0xdb, 0xa9, 0x7d, 0xe7, 0x1f, 0x2f, 0xab, 0x6c, 0xf1, 0x5d, 0x66, 0xec, 0x2b, 0xa2, 0x89, 0xb9, 0xd4, 0xd8, 0x37, 0x09, 0x7f, 0x8b, 0xaf, 0xb1, 0xef, 0x25, 0xba, 0x59, 0x82, 0x8c, 0x7d, 0xef, 0x3a, 0xe7, 0xf8, 0x88, 0x58, 0x4b, 0x84, + 0xb1, 0xef, 0x2b, 0x9a, 0x58, 0x5e, 0x31, 0xf6, 0xcd, 0x62, 0xb1, 0xe5, 0x5f, 0xc6, 0xbe, 0x45, 0x84, 0xaa, 0x21, 0xc6, 0xbe, 0x2a, 0x12, 0xd5, 0x29, 0xc6, 0xbe, 0xd5, 0x92, 0xaf, 0x7e, 0x62, 0xec, 0xfb, 0x89, 0xd0, 0x46, 0x2f, 0x18, 0xfb, 0xf5, 0x44, 0xef, 0x46, 0x3f, 0x19, 0xfb, 0xf5, 0xeb, 0xb4, 0xcd, 0x5f, 0xf4, 0x0d, 0xf4, 0x36, 0xf6, 0x1b, 0x0a, 0x4b, 0xe0, 0x00, 0x63, 0x3f, 0x40, 0xd4, 0x0b, 0x8c, 0x18, 0x99, 0x94, 0xbc, 0x26, 0x25, 0x6e, 0x69, 0x6c, 0x9a, 0xad, 0x53, 0x74, 0x67, 0x5b, 0xef, 0xd0, 0x5e, 0xbd, 0xba, 0xf3, 0xbf, 0x30, 0x5b, 0xd4, 0x1a, 0x5b, 0xda, 0x9a, 0x69, 0x49, 0xf1, 0x91, 0x89, 0x31, 0xb6, 0x49, 0x2b, 0x97, 0x47, 0xa6, 0xae, 0xb5, 0x8d, 0x5a, 0x1b, 0x67, 0x8f, 0x59, 0x1b, 0x17, 0x6d, 0xeb, 0x14, 0x9b, 0x96, 0x96, 0x1c, 0xde, 0xb3, 0xe7, + 0xea, 0xd5, 0xab, 0x7b, 0xa4, 0xad, 0x49, 0xd6, 0x4f, 0xea, 0x11, 0x9d, 0x94, 0xd0, 0xb3, 0xb3, 0x6d, 0x75, 0x5c, 0x5a, 0xac, 0x6d, 0x86, 0x3d, 0xd5, 0x9e, 0xb2, 0xca, 0x1e, 0x63, 0x1b, 0x93, 0x94, 0x98, 0x66, 0x9b, 0x12, 0x99, 0x60, 0xb7, 0xb5, 0x99, 0x14, 0x99, 0x96, 0xd4, 0xa6, 0x87, 0x6d, 0x52, 0x5c, 0xb4, 0x3d, 0x31, 0x95, 0xb7, 0x56, 0x26, 0xc6, 0xd8, 0x53, 0x6c, 0x69, 0xb1, 0x76, 0xdb, 0xcc, 0xf1, 0x93, 0x6c, 0x53, 0x93, 0xed, 0x89, 0x9e, 0xb3, 0x8d, 0x13, 0xba, 0xd9, 0x66, 0xdb, 0x53, 0x52, 0xe3, 0x92, 0x12, 0x6d, 0xbd, 0x7a, 0xf4, 0xba, 0x5e, 0x61, 0x6a, 0x74, 0x4a, 0x5c, 0x72, 0x5a, 0x6a, 0x8f, 0xd4, 0xb8, 0xf8, 0x1e, 0x49, 0x29, 0x4b, 0x7b, 0x4e, 0x1d, 0x33, 0xa9, 0x33, 0x65, 0x52, 0xf4, 0x64, 0x7b, 0x4c, 0xdc, 0xca, 0x84, 0xde, 0x3d, 0x68, 0xfa, 0x20, + 0xda, 0x3d, 0x69, 0x90, 0x76, 0xb0, 0xbb, 0xe7, 0xa8, 0xb6, 0x6b, 0xf3, 0xec, 0xd6, 0x16, 0xea, 0x39, 0x51, 0xeb, 0x6e, 0x58, 0xf7, 0xd0, 0x01, 0xdd, 0x43, 0xfb, 0x0d, 0xb2, 0xd5, 0xe9, 0x53, 0x3c, 0x17, 0x2c, 0xa1, 0x31, 0xa9, 0x7a, 0xa7, 0x6e, 0x2d, 0x29, 0x2e, 0xd5, 0x16, 0x69, 0x4b, 0x4b, 0x89, 0x8c, 0xb1, 0x27, 0x44, 0xa6, 0x2c, 0xb7, 0x25, 0x2d, 0xf9, 0xc3, 0x81, 0xea, 0xf1, 0x47, 0x6f, 0xdc, 0x3a, 0xa0, 0xfa, 0xa8, 0x0d, 0x8f, 0x89, 0x4c, 0xb0, 0xcd, 0x5a, 0x1d, 0x99, 0x12, 0x93, 0x14, 0x1d, 0x6b, 0xd3, 0xae, 0x1a, 0x91, 0x94, 0x16, 0x97, 0x64, 0x9b, 0x12, 0xb7, 0x3c, 0x29, 0x3e, 0x2d, 0x3a, 0xd6, 0xbe, 0xea, 0x46, 0xf5, 0xa9, 0x91, 0x8c, 0x51, 0x4a, 0x9c, 0x56, 0x75, 0xb2, 0x7d, 0x49, 0x64, 0xb4, 0xdd, 0xb6, 0x24, 0x32, 0x21, 0x2e, 0x7e, 0x8d, 0x2d, 0xc6, + 0x9e, 0x1a, 0xb7, 0x34, 0x91, 0xf1, 0x8d, 0x4b, 0xf4, 0x0c, 0xee, 0xca, 0x84, 0x04, 0xc6, 0x99, 0x8e, 0x86, 0xea, 0x45, 0xda, 0xef, 0x4a, 0xb3, 0x33, 0xf2, 0xbf, 0xf3, 0x7e, 0x1f, 0x6d, 0xca, 0xe7, 0x44, 0xa6, 0xa4, 0x46, 0xae, 0xee, 0x1e, 0x15, 0xa9, 0x4d, 0x91, 0x51, 0x56, 0xca, 0x6f, 0xe7, 0xdf, 0x33, 0x9b, 0xb6, 0x04, 0x3b, 0xcd, 0xb0, 0xb5, 0xf1, 0x94, 0xd1, 0x46, 0x2b, 0x92, 0xee, 0xc6, 0xa5, 0xc6, 0x32, 0x27, 0xe3, 0xd3, 0x18, 0xa2, 0x38, 0x7b, 0xaa, 0x8d, 0x16, 0x47, 0x47, 0xa6, 0xd8, 0x97, 0xac, 0x8c, 0xa7, 0x75, 0x51, 0x91, 0x8c, 0x06, 0x8d, 0x4d, 0x4d, 0x42, 0x12, 0xc9, 0x49, 0x34, 0x25, 0x2d, 0x2e, 0x52, 0x7b, 0x23, 0x3a, 0x29, 0x71, 0x49, 0x7c, 0x5c, 0x74, 0x5a, 0x5c, 0xe2, 0x52, 0x5b, 0x72, 0x4a, 0x5c, 0x52, 0x4a, 0x5c, 0x1a, 0x17, 0x87, 0xdb, 0xe2, + 0xd2, 0x6c, 0xa9, 0xb1, 0x49, 0x2b, 0xe3, 0x63, 0x6c, 0xa9, 0x76, 0x7b, 0x82, 0x6d, 0xc5, 0xca, 0xb8, 0x34, 0xb4, 0xc4, 0xe0, 0x27, 0xa6, 0x26, 0x53, 0x6a, 0x62, 0x5a, 0x1b, 0xdb, 0xea, 0x58, 0xa4, 0xb3, 0x32, 0xd5, 0xd3, 0xa5, 0xa8, 0xa4, 0x18, 0x74, 0x4b, 0x27, 0x6d, 0x51, 0x2b, 0xd3, 0x6c, 0xab, 0xf5, 0x2b, 0x63, 0xe2, 0x52, 0x93, 0xe3, 0x23, 0xd7, 0x78, 0x2a, 0xa5, 0xe4, 0xa5, 0x71, 0x89, 0x91, 0xf1, 0xda, 0x04, 0xc6, 0xa5, 0xa5, 0xde, 0x7c, 0x75, 0x7c, 0x64, 0xca, 0x52, 0xba, 0x9b, 0x1a, 0xb7, 0xd6, 0x9e, 0xda, 0xc3, 0x36, 0x8b, 0xe1, 0x89, 0x8e, 0x8f, 0x4c, 0x4d, 0x8d, 0x8b, 0xe6, 0xfc, 0xe4, 0x94, 0xa4, 0xe4, 0xa4, 0x14, 0xe6, 0x23, 0x31, 0xb5, 0x9b, 0x8d, 0xba, 0xd3, 0xe2, 0xa2, 0x57, 0x72, 0x3e, 0x6d, 0x5f, 0x15, 0x97, 0x1a, 0x17, 0x15, 0x6f, 0xaf, 0x1d, + 0xd1, 0x95, 0xc9, 0xc9, 0xf6, 0x94, 0xe8, 0x48, 0x4d, 0xc2, 0x4b, 0xe3, 0x56, 0xd9, 0xf5, 0x63, 0xf1, 0xf6, 0xb4, 0x34, 0x7b, 0xca, 0x92, 0xa4, 0x94, 0x84, 0x54, 0xcf, 0x44, 0xc5, 0x45, 0xa6, 0xd8, 0x62, 0x23, 0x53, 0x12, 0x92, 0x12, 0xd7, 0x78, 0xa6, 0x25, 0xde, 0xbe, 0x54, 0x1b, 0x99, 0x1e, 0xb6, 0xe1, 0x69, 0xfa, 0x15, 0xa9, 0x9a, 0xd1, 0xa4, 0xc5, 0x25, 0x50, 0x8a, 0xd6, 0xcc, 0xd4, 0x78, 0xbb, 0x7d, 0xf9, 0x8d, 0x59, 0x8f, 0x4f, 0x4a, 0x5a, 0x6e, 0x4b, 0x88, 0x5c, 0xce, 0xf8, 0xda, 0x57, 0xc5, 0xc5, 0x30, 0x0e, 0xfa, 0x45, 0x08, 0x41, 0xdb, 0x89, 0x4c, 0xd3, 0x4d, 0xc2, 0xb6, 0x3a, 0x32, 0xf5, 0x37, 0x7a, 0xd0, 0x84, 0x40, 0x07, 0xec, 0xab, 0xec, 0xda, 0xeb, 0xa4, 0x95, 0x4b, 0x63, 0xb5, 0x31, 0x8e, 0x49, 0xa2, 0xa4, 0xc4, 0xa4, 0x34, 0xdb, 0x92, 0xa4, 0xf8, + 0xf8, 0xa4, 0xd5, 0x34, 0x89, 0x29, 0x59, 0x99, 0x92, 0xa2, 0x17, 0xcc, 0xff, 0x63, 0x3c, 0xa3, 0x91, 0x6a, 0x4f, 0x88, 0xeb, 0x9e, 0x92, 0xb4, 0x52, 0x17, 0x50, 0x8c, 0x3d, 0x2d, 0x32, 0x2e, 0x3e, 0x55, 0xd7, 0xff, 0xf5, 0x2e, 0xa6, 0x7a, 0xfa, 0xac, 0x57, 0x1f, 0x69, 0x5b, 0x62, 0xb7, 0xc7, 0x6b, 0x33, 0xca, 0x29, 0xc8, 0x3a, 0x21, 0x2d, 0xb6, 0x1b, 0xe3, 0x1d, 0x17, 0xef, 0x19, 0x93, 0xd4, 0xb4, 0x94, 0x24, 0xde, 0x63, 0xb3, 0x32, 0x3a, 0x6d, 0x65, 0x8a, 0x5d, 0x1b, 0x61, 0xad, 0x2f, 0x74, 0x36, 0x2d, 0x32, 0x8a, 0x21, 0x4a, 0xf3, 0x0c, 0x8d, 0xd6, 0xe3, 0xa4, 0x95, 0xa9, 0x89, 0xf6, 0x54, 0xe6, 0x64, 0x7c, 0xa2, 0x2e, 0x55, 0xcd, 0x45, 0xf5, 0xed, 0x66, 0xf4, 0x59, 0xd7, 0xbd, 0xd6, 0xd7, 0xa5, 0x29, 0xf6, 0xc8, 0x34, 0xf6, 0xaf, 0x6b, 0xbc, 0x93, 0x6e, 0x57, + 0xda, 0x59, 0xb1, 0xf6, 0xf8, 0x64, 0xad, 0x19, 0xff, 0xdd, 0xc6, 0x3a, 0xeb, 0x8a, 0x4d, 0x5a, 0x85, 0x0e, 0xfa, 0x84, 0x86, 0x86, 0x76, 0xb5, 0x2d, 0x8d, 0x5f, 0x93, 0x1c, 0x4b, 0x37, 0xb5, 0x23, 0x89, 0x71, 0x89, 0x76, 0xdb, 0x6a, 0xbb, 0xe6, 0x2e, 0x53, 0x3d, 0x46, 0x1b, 0x97, 0x16, 0x89, 0x6e, 0x53, 0x75, 0xc1, 0x27, 0x32, 0x72, 0xa9, 0x4c, 0x3f, 0x32, 0x49, 0xb5, 0xf5, 0xd2, 0xae, 0x65, 0x1c, 0xe2, 0x12, 0x0d, 0x6b, 0x42, 0xf9, 0x4b, 0x57, 0x46, 0x2e, 0xb5, 0x33, 0xfa, 0x61, 0xbc, 0x35, 0x12, 0xaf, 0x1b, 0xcf, 0xa5, 0xb7, 0xbe, 0x6b, 0xa3, 0x23, 0xab, 0xed, 0xf1, 0xf1, 0xda, 0x76, 0x6c, 0x8a, 0x36, 0xeb, 0x5a, 0x2b, 0xc7, 0x4f, 0x1b, 0x6e, 0x4b, 0x8e, 0x4d, 0x4a, 0xb4, 0xa7, 0xe9, 0x95, 0x69, 0x93, 0xa1, 0x8f, 0xb1, 0xee, 0xa5, 0x6c, 0x58, 0x82, 0x2d, 0x72, + 0x15, 0xb3, 0x11, 0xa9, 0xe9, 0x70, 0x09, 0x57, 0x69, 0x7d, 0x8d, 0x8e, 0xd5, 0xe4, 0xfc, 0xdf, 0x1c, 0x2d, 0xa7, 0x27, 0x25, 0xfc, 0xb1, 0xf3, 0xfb, 0x23, 0x4f, 0xff, 0xdf, 0x2e, 0xf8, 0x1d, 0x4f, 0x2d, 0x46, 0x8a, 0x24, 0xe2, 0xed, 0x35, 0x22, 0x45, 0xc4, 0x91, 0x42, 0xc7, 0x8a, 0x34, 0x61, 0x13, 0x9d, 0x44, 0xb4, 0xe8, 0xcc, 0xb6, 0xb7, 0x08, 0x15, 0xbd, 0xf8, 0xd7, 0xdd, 0xd8, 0x0b, 0xe3, 0x58, 0x14, 0xe7, 0xda, 0x38, 0x6b, 0x8d, 0x98, 0xc6, 0x95, 0xf1, 0x22, 0x52, 0x24, 0x8a, 0x18, 0x8e, 0x4c, 0x12, 0x2b, 0xc5, 0x72, 0x5e, 0xa5, 0x8a, 0xb5, 0xbc, 0x1a, 0xc5, 0xff, 0xe3, 0x84, 0x9d, 0x77, 0xb4, 0x6d, 0xb4, 0x5e, 0xa6, 0x56, 0x76, 0x1a, 0x75, 0x85, 0x8b, 0x9e, 0xfc, 0x5b, 0xad, 0xff, 0xeb, 0xa1, 0x97, 0x94, 0x5c, 0xa7, 0xa4, 0x1e, 0x9c, 0x9d, 0x24, 0x12, 0x38, + 0x43, 0x6b, 0xc1, 0x6a, 0xae, 0x4e, 0xe3, 0x4a, 0x9b, 0x98, 0x41, 0x69, 0xa9, 0x90, 0x22, 0x56, 0xe9, 0xe5, 0xda, 0xc4, 0x18, 0xce, 0x4b, 0xd4, 0xdb, 0x3b, 0x85, 0x6b, 0x13, 0x38, 0x6a, 0x13, 0x6d, 0x68, 0x47, 0x24, 0xc7, 0x92, 0xd8, 0xeb, 0xa1, 0xb7, 0x4a, 0xab, 0xdd, 0xce, 0x79, 0xa9, 0xc6, 0x55, 0x2b, 0xf5, 0x5a, 0xb4, 0x72, 0x6c, 0x7a, 0xc9, 0xda, 0x55, 0x33, 0xc5, 0x78, 0xce, 0xb4, 0x89, 0xa9, 0xb4, 0x44, 0x3b, 0xb7, 0x6e, 0xd9, 0x37, 0x97, 0xd0, 0x8d, 0x23, 0xb3, 0xf5, 0xab, 0x53, 0x39, 0x9e, 0xa4, 0x9f, 0xdb, 0x8b, 0x9a, 0x7a, 0xfd, 0x4e, 0x0f, 0x53, 0xb9, 0x4e, 0x1b, 0xd5, 0x64, 0x8e, 0xa5, 0x72, 0x8e, 0x76, 0x45, 0x3c, 0xdb, 0x24, 0x8e, 0x2e, 0xe5, 0xfd, 0xa9, 0xd4, 0x32, 0x89, 0x5e, 0x7a, 0xda, 0xe9, 0x69, 0xb5, 0x4d, 0x4c, 0xd6, 0xdb, 0x19, 0x47, 0x3b, + 0x13, 0xf4, 0x3e, 0x2f, 0x65, 0x4f, 0x1b, 0x9b, 0x14, 0xe6, 0xa0, 0x87, 0x31, 0x0b, 0x83, 0x8c, 0xf1, 0x9f, 0xc4, 0x5e, 0xed, 0x95, 0xdd, 0x6f, 0xba, 0xf2, 0xd6, 0x36, 0xd6, 0xbd, 0xd6, 0x76, 0x7d, 0x36, 0xbb, 0xb3, 0x1d, 0xa0, 0xff, 0xbf, 0x9f, 0x7e, 0xfc, 0xf7, 0x67, 0x28, 0xde, 0xa8, 0x61, 0x89, 0x31, 0x26, 0xa9, 0x75, 0xe6, 0xe8, 0xf7, 0x6b, 0xbf, 0xd1, 0x9b, 0x38, 0xce, 0xb6, 0xf1, 0x4a, 0x1b, 0xeb, 0x14, 0xb6, 0xda, 0xc8, 0x27, 0xe8, 0xbd, 0x59, 0xce, 0x31, 0xad, 0xcc, 0xbf, 0xae, 0xa5, 0x1e, 0x7f, 0xf9, 0x8a, 0xff, 0xa6, 0xcd, 0x1b, 0x2a, 0x1b, 0xce, 0x51, 0x4d, 0x4b, 0x36, 0x31, 0x8b, 0xa3, 0x5a, 0x3b, 0x63, 0xa8, 0x27, 0x5a, 0x7f, 0xaf, 0xb6, 0xae, 0x11, 0x1c, 0x49, 0xd3, 0x47, 0x56, 0x53, 0x5e, 0x1c, 0x25, 0x6b, 0x2d, 0x49, 0xd3, 0xcf, 0xb2, 0xa3, 0xce, + 0xdf, 0xeb, 0x7d, 0xaa, 0x7e, 0xb5, 0x47, 0xbf, 0x71, 0xd7, 0x7b, 0xad, 0xa9, 0x6d, 0x09, 0xef, 0x44, 0xeb, 0x2a, 0x5c, 0xa2, 0xd7, 0xac, 0x69, 0x44, 0xb3, 0xb0, 0x18, 0x5d, 0xef, 0x9a, 0x4d, 0x26, 0x1a, 0xca, 0x8d, 0xd3, 0x67, 0xf2, 0x86, 0x66, 0xb5, 0x91, 0x4e, 0x30, 0x94, 0xec, 0x99, 0xd1, 0xd0, 0x3a, 0xad, 0xb4, 0x8b, 0xbb, 0x38, 0xd7, 0x6e, 0xa8, 0xfd, 0xcf, 0x5d, 0xdf, 0xe7, 0xba, 0x7d, 0xcf, 0xd1, 0xfb, 0xae, 0xb5, 0x7a, 0x35, 0x73, 0x1b, 0xa5, 0x8f, 0x9d, 0xa7, 0x94, 0x9b, 0xdb, 0x95, 0xf2, 0xa7, 0x2c, 0xbf, 0xae, 0x55, 0xda, 0xf4, 0x3a, 0x3d, 0xa3, 0xa1, 0xd9, 0x6b, 0xdd, 0x76, 0xb4, 0xb9, 0xde, 0x4a, 0xcf, 0xec, 0x6a, 0xe3, 0x17, 0x6b, 0x58, 0xc8, 0x78, 0xdd, 0x1a, 0xd3, 0xf4, 0xf1, 0xb3, 0xeb, 0xd7, 0x7a, 0xc6, 0x38, 0x5a, 0x6f, 0xa9, 0x36, 0x8e, 0x9a, + 0x9d, 0x78, 0xc6, 0x4e, 0x6b, 0xaf, 0x47, 0x1b, 0x9e, 0x91, 0x4d, 0xd5, 0xd5, 0xaa, 0xed, 0x25, 0xeb, 0x73, 0x67, 0xd7, 0x75, 0x1c, 0xa7, 0x9f, 0xe5, 0xb9, 0x22, 0x5a, 0xd7, 0xf6, 0x12, 0xbd, 0xce, 0x68, 0xfd, 0xbd, 0x44, 0x7a, 0xa8, 0x9d, 0x9f, 0xa2, 0xcf, 0x74, 0x8a, 0xae, 0x10, 0x4f, 0xcd, 0xe1, 0x7a, 0x2b, 0xd3, 0xf4, 0x72, 0x63, 0x79, 0x4f, 0xab, 0x37, 0x46, 0x7f, 0x65, 0xd7, 0xd5, 0x6d, 0x13, 0x2b, 0x38, 0x16, 0xa7, 0xd7, 0xa3, 0xf5, 0xd0, 0xa3, 0x7c, 0xad, 0xbf, 0xc9, 0x46, 0x5b, 0xb5, 0xda, 0xdb, 0xe8, 0xba, 0x8b, 0x35, 0xfc, 0xcd, 0xca, 0xeb, 0xe3, 0xeb, 0xe9, 0x7f, 0x14, 0xe5, 0xc6, 0x18, 0x9e, 0xd6, 0x33, 0x93, 0xda, 0xb1, 0x95, 0xfa, 0x76, 0x75, 0x9d, 0x3a, 0x63, 0xf4, 0x31, 0x4a, 0xd6, 0x7b, 0xbb, 0xe6, 0xa6, 0x9e, 0x26, 0x19, 0x1e, 0x5d, 0x2b, 0x4f, + 0xeb, 0x67, 0xad, 0x05, 0xc6, 0xe9, 0x16, 0xfc, 0x9f, 0xea, 0xf6, 0x78, 0x9b, 0xa5, 0xc6, 0xec, 0x6a, 0x73, 0xbd, 0x56, 0xef, 0x77, 0x0f, 0xdd, 0x2a, 0x3c, 0xea, 0x89, 0xd6, 0xcf, 0x4a, 0xd5, 0xdf, 0x8d, 0x36, 0xca, 0xd7, 0xc6, 0x2a, 0x49, 0x1f, 0xe1, 0x14, 0xc3, 0x3e, 0xb4, 0x3e, 0x77, 0xd3, 0xdf, 0x89, 0x34, 0x8e, 0x45, 0x5f, 0xf7, 0x66, 0x9e, 0x71, 0x5f, 0xa5, 0xb7, 0x3f, 0x8e, 0xbe, 0xc5, 0xeb, 0xe5, 0xde, 0xac, 0xd1, 0x95, 0x5c, 0x99, 0xac, 0xb7, 0x23, 0xda, 0x50, 0xa0, 0x56, 0x9a, 0xd6, 0xa7, 0x55, 0xfa, 0xfb, 0xb5, 0xe7, 0x69, 0xd7, 0xa6, 0xe9, 0x23, 0x95, 0xa2, 0x7b, 0xa9, 0x14, 0xc6, 0x20, 0xf5, 0x26, 0x8b, 0x8a, 0xd3, 0xeb, 0xd4, 0x3c, 0x5c, 0xa4, 0xfe, 0xae, 0xd6, 0xb6, 0x35, 0x37, 0x59, 0x4b, 0xbc, 0xee, 0x6b, 0x6b, 0x35, 0xd3, 0x43, 0xf7, 0x05, 0x69, + 0x75, 0xea, 0x48, 0xbd, 0xbe, 0xc6, 0x68, 0xfd, 0x48, 0x30, 0xda, 0x52, 0x3b, 0x9a, 0xa9, 0xfa, 0xf5, 0x76, 0xdd, 0xab, 0xfd, 0xd6, 0xd6, 0xe3, 0xa9, 0x2f, 0x49, 0x7f, 0x4f, 0xf3, 0x7d, 0xcb, 0x0d, 0xfd, 0xda, 0xf5, 0xde, 0xc7, 0x18, 0x7a, 0xb8, 0x51, 0x93, 0xc7, 0x23, 0xd4, 0x1e, 0x89, 0x34, 0x56, 0xa1, 0x5a, 0x9f, 0xb2, 0x5a, 0x1f, 0x89, 0xff, 0xee, 0x1f, 0x6a, 0x3d, 0x82, 0x67, 0x06, 0xec, 0xfa, 0x88, 0xd5, 0xbe, 0xaf, 0xe9, 0x67, 0xa9, 0xee, 0xd3, 0x3c, 0x3a, 0xd6, 0x7c, 0x9c, 0xa7, 0x4d, 0x89, 0xba, 0x7d, 0xd8, 0xf4, 0x51, 0x8c, 0xd7, 0xdb, 0xbd, 0xda, 0x18, 0x25, 0x8f, 0x95, 0xac, 0xa4, 0x4f, 0x29, 0x75, 0x5a, 0x9c, 0x62, 0x78, 0x98, 0xba, 0xda, 0x48, 0xd5, 0xad, 0x20, 0x0e, 0xbf, 0x91, 0xa2, 0xd7, 0x74, 0xc3, 0x03, 0xc5, 0xe8, 0xf3, 0x14, 0xa9, 0xcf, 0x48, + 0x6a, 0x1d, 0xff, 0xff, 0xdb, 0x59, 0x4c, 0xbd, 0x69, 0x9e, 0x6f, 0xf4, 0x3e, 0x52, 0x6f, 0x9b, 0x5d, 0x9f, 0xaf, 0x5a, 0x1b, 0xf5, 0x94, 0xb2, 0xda, 0x98, 0x59, 0xad, 0xb4, 0x6e, 0x86, 0xbe, 0xe3, 0x0c, 0x55, 0xdd, 0x98, 0xc3, 0x34, 0xbd, 0x4d, 0x9e, 0xeb, 0x3c, 0xaf, 0x56, 0xea, 0x63, 0xbd, 0x52, 0xef, 0x49, 0xad, 0x86, 0x6b, 0xe7, 0x25, 0xd5, 0x38, 0x2b, 0x12, 0x7d, 0x7a, 0x54, 0x94, 0x76, 0x93, 0x6a, 0x6a, 0xe7, 0x38, 0x49, 0xb7, 0xa1, 0x44, 0xfd, 0x8a, 0x54, 0xc3, 0x5b, 0x25, 0xd6, 0xf1, 0xaa, 0xb5, 0xf1, 0x53, 0x5f, 0xbd, 0x65, 0x75, 0xe7, 0xf9, 0x86, 0xbf, 0xaf, 0x9d, 0xd7, 0xa5, 0x7a, 0x4b, 0xb4, 0xfe, 0x7a, 0x8e, 0xff, 0xd6, 0x8f, 0x77, 0xaa, 0xb3, 0x5e, 0xd5, 0x96, 0x15, 0xab, 0x8f, 0x48, 0xf2, 0xf5, 0xd1, 0xf8, 0x3f, 0xb1, 0x8e, 0x75, 0xae, 0xe3, 0x63, + 0x93, 0xf4, 0x99, 0xd0, 0x2c, 0xa8, 0x0f, 0x3d, 0xd1, 0xfe, 0x75, 0xd5, 0xdb, 0x1a, 0xaf, 0xaf, 0x62, 0xb1, 0xc6, 0x6c, 0xd6, 0x9e, 0x93, 0xa8, 0xcf, 0x8d, 0x5d, 0xef, 0x95, 0xfd, 0x7a, 0x5c, 0x99, 0x7a, 0xd3, 0x4a, 0x1b, 0xa7, 0x8f, 0xab, 0xc7, 0xdf, 0xa6, 0xd6, 0xf1, 0xf0, 0x89, 0x86, 0xe6, 0x52, 0x0d, 0xeb, 0xf7, 0x78, 0x93, 0x54, 0x3d, 0xce, 0xaa, 0xad, 0xd7, 0xa3, 0x07, 0xad, 0x8e, 0x9b, 0xd7, 0x26, 0x8f, 0xcf, 0xd7, 0x22, 0xa6, 0x48, 0xdd, 0x7f, 0x79, 0xb4, 0x1f, 0x66, 0x5c, 0x35, 0xd2, 0x88, 0x72, 0xe3, 0x8d, 0x5a, 0xff, 0xdb, 0xb5, 0x36, 0x63, 0x46, 0x56, 0xeb, 0x63, 0x1b, 0x7f, 0xfd, 0xf5, 0x58, 0x7d, 0x86, 0x3c, 0xb6, 0x5e, 0x3b, 0x96, 0xe3, 0x59, 0xb1, 0x86, 0xeb, 0xfa, 0x89, 0xd5, 0xf5, 0x65, 0x37, 0xbc, 0xdd, 0xcd, 0x5e, 0xf3, 0x86, 0x8e, 0x6f, 0xc4, + 0x52, 0x36, 0x63, 0x4d, 0xd0, 0xb6, 0xab, 0x0c, 0xdb, 0x88, 0xbc, 0xee, 0x0f, 0x97, 0x18, 0x75, 0xd5, 0xce, 0x6b, 0xb4, 0xe1, 0xc1, 0x96, 0x1a, 0xfe, 0xf1, 0x7f, 0x12, 0xcb, 0x7a, 0x4a, 0x4f, 0xd2, 0x55, 0xf2, 0xd7, 0x23, 0xbf, 0xbf, 0x1a, 0xcd, 0xff, 0x4f, 0x6b, 0xf8, 0x73, 0xd1, 0x74, 0xed, 0x08, 0xd7, 0x8d, 0x46, 0xb5, 0x7b, 0x97, 0xfa, 0x7f, 0x35, 0xa1, 0xda, 0x97, 0xec, 0x7f, 0xe7, 0x3f, 0x45, 0x28, 0x52, 0x8a, 0x06, 0xfa, 0xdd, 0x44, 0x2b, 0xe7, 0xec, 0x15, 0xde, 0xa3, 0xc6, 0x4c, 0x9a, 0x25, 0x5a, 0x44, 0xaf, 0x49, 0x89, 0x17, 0x9d, 0x48, 0x61, 0x97, 0x8b, 0x7e, 0x64, 0x54, 0x89, 0x44, 0x37, 0xfa, 0x1f, 0xf7, 0x39, 0xdb, 0xa4, 0xdf, 0x07, 0xbd, 0xf1, 0x4a, 0x7b, 0xc4, 0xc0, 0x8d, 0x57, 0xda, 0x9d, 0xd3, 0x1b, 0xaf, 0xb4, 0x5f, 0xc2, 0xb1, 0x2c, 0xb7, 0xa7, + 0x24, 0x8a, 0xde, 0xbf, 0xfd, 0xbf, 0x76, 0xbf, 0x4a, 0x0c, 0xf8, 0xed, 0xff, 0xaf, 0xdf, 0xf5, 0xd4, 0x3e, 0xf9, 0xaa, 0x95, 0xe1, 0x4d, 0x0d, 0xf5, 0xc4, 0x56, 0xf1, 0x94, 0x78, 0x55, 0x7c, 0xac, 0xff, 0x3d, 0xa9, 0x9e, 0x08, 0x11, 0x1d, 0x18, 0x83, 0x7e, 0x62, 0x28, 0xbd, 0x9f, 0x42, 0xd4, 0xbf, 0x88, 0x91, 0x4c, 0x44, 0x47, 0x8a, 0xde, 0xbe, 0xbd, 0x9e, 0xed, 0x81, 0x65, 0xfa, 0x56, 0x59, 0x92, 0xee, 0xd9, 0x9e, 0xda, 0xaf, 0x6f, 0x4d, 0x41, 0xdb, 0x3d, 0xdb, 0x39, 0xf9, 0x9e, 0xed, 0xba, 0xc7, 0x3d, 0xdb, 0x87, 0x23, 0x3c, 0xdb, 0xa7, 0x5e, 0xf3, 0x6c, 0x5f, 0xab, 0xe7, 0xd9, 0xfe, 0xcb, 0xe4, 0xd9, 0xfe, 0xf8, 0x89, 0x5e, 0xbb, 0x22, 0x2c, 0xfa, 0xdd, 0x62, 0x53, 0x6e, 0xf5, 0xcd, 0xaf, 0x2f, 0x3f, 0x75, 0xd3, 0x6b, 0xaf, 0xa0, 0xaf, 0x6f, 0x7e, 0xdd, 0xe4, + 0xa0, 0x50, 0x4c, 0x0d, 0xf5, 0xa7, 0x01, 0x79, 0x99, 0xa6, 0xf9, 0x7c, 0xed, 0x3b, 0x2e, 0x70, 0x8b, 0x6f, 0xae, 0x39, 0x98, 0x7f, 0xb3, 0xcd, 0xb3, 0x03, 0xb7, 0x98, 0xbf, 0xb5, 0xb4, 0xb0, 0xec, 0x68, 0x54, 0x6e, 0xf9, 0x57, 0xa3, 0xf2, 0x86, 0x5f, 0x36, 0x74, 0x06, 0x74, 0x0a, 0xd8, 0xc4, 0xbf, 0x8a, 0x46, 0xe5, 0xda, 0xbf, 0xc0, 0x20, 0xfe, 0x6d, 0x09, 0xcc, 0x08, 0x6a, 0xd3, 0xec, 0x95, 0xe6, 0x6d, 0x9a, 0x3f, 0xcc, 0xbf, 0xf7, 0x6f, 0xeb, 0xd6, 0xb6, 0x37, 0xff, 0xee, 0x6a, 0xb7, 0xaa, 0xfd, 0xdc, 0xf6, 0x07, 0x3b, 0x7a, 0x77, 0x5c, 0xd6, 0xf1, 0x93, 0x81, 0xef, 0x87, 0x7b, 0x77, 0x6a, 0xd4, 0xa9, 0xd1, 0x80, 0xd7, 0xfa, 0x26, 0x0e, 0xdb, 0x3d, 0x6c, 0x77, 0xa7, 0x49, 0x9d, 0xd6, 0x76, 0x8d, 0xe9, 0xfa, 0xf6, 0xc0, 0xf7, 0x07, 0xbe, 0x3f, 0xec, 0xd5, 0x70, + 0xef, 0x70, 0xef, 0xee, 0xb3, 0x06, 0xbc, 0x36, 0xe0, 0xb5, 0x88, 0xa1, 0xdd, 0x8f, 0x76, 0x2f, 0xed, 0xf1, 0x53, 0xcf, 0xc2, 0xd0, 0x3b, 0x42, 0x77, 0x44, 0x0c, 0xd5, 0xfe, 0x85, 0x66, 0x86, 0x5e, 0xed, 0x95, 0xd9, 0xfb, 0x7c, 0x9f, 0x45, 0x7d, 0x76, 0x7b, 0x8e, 0x44, 0x0c, 0xed, 0x9b, 0xd8, 0x37, 0xb1, 0x4f, 0x4e, 0x9f, 0x1c, 0xad, 0xac, 0xbe, 0xf5, 0xfa, 0x76, 0xd1, 0xae, 0xe5, 0xea, 0x76, 0x7d, 0xa7, 0x45, 0xb4, 0xd3, 0xfe, 0x69, 0xef, 0xf7, 0xdd, 0xdb, 0x37, 0xc7, 0x73, 0x7c, 0x40, 0xf5, 0xc0, 0x69, 0x5a, 0x0b, 0xc2, 0xbd, 0x23, 0xe6, 0x86, 0x2f, 0x0a, 0xdf, 0x11, 0xbe, 0x43, 0xbb, 0x2e, 0xfc, 0xe8, 0x90, 0xf4, 0x61, 0xbb, 0x87, 0x54, 0xb3, 0xff, 0x2a, 0xd7, 0x0c, 0xad, 0xdd, 0x46, 0xcc, 0x8d, 0xd8, 0x12, 0xf1, 0x61, 0x84, 0x73, 0x78, 0xf0, 0xf0, 0x31, 0xc3, + 0xef, 0x1a, 0xfe, 0xd4, 0xe8, 0xbd, 0xe3, 0x8e, 0x8e, 0x7f, 0x77, 0x52, 0xfe, 0xe4, 0xb4, 0xc9, 0x3f, 0x4d, 0x3b, 0x3d, 0xe3, 0xe3, 0xdb, 0x5f, 0x9b, 0xdb, 0x7b, 0x6e, 0xfc, 0xdc, 0x37, 0xe6, 0x56, 0xdc, 0xb1, 0x75, 0xfe, 0xa4, 0xf9, 0xcf, 0x2d, 0x1a, 0xbc, 0x68, 0xd1, 0xa2, 0x75, 0x51, 0x8f, 0x2e, 0x0e, 0x89, 0x7a, 0x34, 0xea, 0xd7, 0x98, 0x20, 0x7b, 0x9b, 0x25, 0x19, 0xcb, 0x42, 0x63, 0xbb, 0xc4, 0x2e, 0x88, 0xab, 0x59, 0x16, 0xba, 0x6c, 0xd9, 0xb2, 0xcf, 0xe2, 0x6b, 0x12, 0xc6, 0x25, 0x6c, 0x4a, 0xfa, 0x31, 0xe9, 0xc7, 0xb4, 0xea, 0xe4, 0x55, 0xc9, 0x9f, 0xad, 0x12, 0x2b, 0xc2, 0x57, 0x89, 0x35, 0x5f, 0xaf, 0xf9, 0x7a, 0xe5, 0x5a, 0xf6, 0xef, 0x4b, 0x19, 0x9a, 0xb2, 0x38, 0xad, 0x7a, 0x95, 0xd0, 0xfe, 0xa5, 0x55, 0xaf, 0xf9, 0x5a, 0x7b, 0x6f, 0xe5, 0xda, 0x95, + 0xb3, 0xb4, 0x77, 0x57, 0x85, 0xdf, 0x15, 0xba, 0xe6, 0xeb, 0xb5, 0x93, 0xd6, 0xd6, 0xac, 0xeb, 0x7d, 0xf7, 0xee, 0xbb, 0x8f, 0xae, 0x73, 0xae, 0xab, 0xb7, 0xae, 0xf7, 0x3d, 0xa6, 0x75, 0xf7, 0xdd, 0x63, 0x5a, 0x1f, 0x7a, 0x8f, 0x49, 0xfb, 0xff, 0xfa, 0x37, 0x79, 0xfd, 0xea, 0xba, 0xd3, 0xeb, 0xdf, 0x5c, 0xe7, 0xd4, 0x8e, 0xf0, 0xca, 0xa9, 0xbd, 0xa7, 0xbd, 0xb3, 0xfe, 0x1e, 0xed, 0xdd, 0x7b, 0xda, 0xdd, 0x73, 0x70, 0x83, 0xe5, 0xbe, 0x26, 0x5b, 0xea, 0x6d, 0x71, 0x3e, 0x74, 0x75, 0x7b, 0xbd, 0x6d, 0xa6, 0xed, 0xf5, 0xb6, 0xbf, 0xf0, 0x88, 0xed, 0x91, 0xe7, 0x1e, 0xf9, 0x51, 0xe3, 0xd1, 0x31, 0xcf, 0x9d, 0xdb, 0xd3, 0xe8, 0xa5, 0x69, 0xfb, 0x2f, 0xbe, 0xa2, 0x3d, 0xf5, 0xce, 0xaa, 0x7d, 0x62, 0x15, 0x95, 0x2c, 0x11, 0x0f, 0x63, 0xe9, 0xdf, 0x88, 0xb3, 0x28, 0xdd, + 0x21, 0x8a, 0xc5, 0xcb, 0xa2, 0x54, 0x09, 0x15, 0x6f, 0x28, 0xfd, 0x95, 0x70, 0x51, 0xa2, 0x0c, 0x56, 0xc6, 0x8a, 0x4b, 0xca, 0x78, 0x65, 0x82, 0x90, 0xca, 0x24, 0xfe, 0x29, 0xca, 0x54, 0xfe, 0x99, 0xc4, 0x29, 0xe1, 0x25, 0x3f, 0x15, 0xde, 0xe0, 0x23, 0xdf, 0x15, 0x81, 0xf2, 0x6b, 0xd1, 0x44, 0x16, 0x8b, 0x76, 0xf2, 0x4b, 0xd1, 0x5e, 0x7e, 0x23, 0x3a, 0xc9, 0x0f, 0x44, 0x67, 0x59, 0x24, 0xba, 0xca, 0xb7, 0xc4, 0x32, 0xf9, 0xb6, 0x58, 0x2f, 0x2f, 0x89, 0x7b, 0x64, 0xb9, 0x48, 0x97, 0x3f, 0x8a, 0x07, 0x65, 0x96, 0x78, 0x48, 0x1e, 0x15, 0xdf, 0xca, 0xcf, 0xc5, 0x61, 0xae, 0xff, 0x41, 0x9e, 0x37, 0x0d, 0x97, 0x07, 0x4d, 0xa3, 0x61, 0x26, 0xdc, 0x21, 0xdf, 0x32, 0xc5, 0xca, 0x48, 0xd3, 0x16, 0xf9, 0xab, 0xe9, 0x01, 0x79, 0xca, 0xb4, 0x43, 0x6e, 0xf3, 0x7a, 0x56, 0x7e, + 0xee, 0xd3, 0x57, 0x96, 0xfa, 0x84, 0xc1, 0x97, 0xf2, 0x94, 0xcf, 0x69, 0x99, 0x6f, 0xee, 0x2a, 0x3f, 0x35, 0x77, 0x83, 0xee, 0xd0, 0x03, 0x7a, 0x42, 0x28, 0xf4, 0x82, 0xde, 0xd0, 0x07, 0xfa, 0x42, 0x18, 0xf4, 0x87, 0x01, 0x30, 0x10, 0xc2, 0x61, 0x10, 0x0c, 0x86, 0x21, 0x30, 0x14, 0x86, 0x41, 0x04, 0x0c, 0x87, 0x11, 0x30, 0x12, 0x46, 0xc1, 0x68, 0x18, 0x03, 0x63, 0x61, 0x1c, 0x4c, 0x00, 0xed, 0x5b, 0x64, 0x93, 0x44, 0x23, 0xf3, 0x64, 0xf6, 0xa7, 0xc0, 0x54, 0x5e, 0x4f, 0x13, 0xcd, 0xcd, 0xd3, 0xd9, 0x9f, 0x01, 0x33, 0x61, 0x16, 0xdc, 0x0e, 0xb3, 0x61, 0x0e, 0xcc, 0xe5, 0x9c, 0x79, 0x5c, 0x73, 0x07, 0xfb, 0xf3, 0x61, 0x01, 0xaf, 0x17, 0x72, 0xcd, 0x22, 0xf6, 0x23, 0x21, 0x4a, 0xbe, 0x6b, 0x8e, 0x86, 0xfb, 0xe5, 0x9d, 0xe6, 0xa7, 0x44, 0x03, 0xf3, 0xd3, 0xe2, 0x47, + 0xf3, 0x33, 0x22, 0xdf, 0xfc, 0xac, 0x08, 0x34, 0x3f, 0x27, 0xb2, 0xcc, 0x7b, 0x44, 0x91, 0x79, 0xaf, 0xbc, 0x68, 0x7e, 0x5e, 0x78, 0x99, 0xdf, 0xe5, 0xfd, 0xf7, 0x78, 0xff, 0x7d, 0xde, 0xff, 0x40, 0x4c, 0x36, 0x7f, 0xc8, 0x39, 0x1f, 0x71, 0xce, 0xc7, 0x9c, 0xf3, 0x4f, 0x5e, 0x7f, 0xc2, 0x79, 0x9f, 0x72, 0xce, 0xbf, 0xe1, 0x33, 0xce, 0xfb, 0x9c, 0xf3, 0x0e, 0x72, 0xfc, 0x0b, 0xce, 0xfb, 0x92, 0xf3, 0xbe, 0xe2, 0xbc, 0xaf, 0x79, 0x7d, 0x42, 0x34, 0x35, 0x9f, 0x14, 0xa7, 0xcd, 0xa7, 0xc4, 0x65, 0x73, 0xae, 0x98, 0x63, 0xce, 0x13, 0xad, 0xcc, 0xa7, 0x45, 0x81, 0xf9, 0x8c, 0xa8, 0x32, 0xff, 0x2a, 0xe6, 0x99, 0xcf, 0xca, 0x2a, 0xf3, 0x39, 0xd1, 0xd8, 0xec, 0x94, 0x5f, 0x9b, 0xcf, 0xcb, 0xaf, 0x2d, 0x4d, 0xe4, 0xb7, 0x96, 0xae, 0xd2, 0x69, 0xe9, 0x26, 0x5e, 0xb7, 0x74, + 0x17, 0x07, 0x2d, 0x3d, 0x64, 0x81, 0xa5, 0xa7, 0x78, 0xc7, 0x12, 0x2a, 0xbe, 0xb2, 0xf4, 0x92, 0x87, 0x2c, 0xbd, 0x65, 0x89, 0x65, 0x9a, 0xfc, 0xd2, 0x32, 0x1d, 0x66, 0xc0, 0x2c, 0x98, 0x0d, 0x73, 0x60, 0x2e, 0xcc, 0x83, 0x64, 0xf9, 0x81, 0x65, 0x05, 0xa4, 0xc8, 0x22, 0x4b, 0x2a, 0xa4, 0xc1, 0x4a, 0x58, 0x05, 0xab, 0x61, 0x9d, 0x7c, 0xcb, 0xb2, 0x1e, 0xee, 0x81, 0x0d, 0x70, 0x2f, 0x6c, 0x14, 0x5d, 0x2d, 0x9b, 0xd8, 0x6e, 0x86, 0xfb, 0xe0, 0x7e, 0xd8, 0x02, 0x0f, 0x40, 0xba, 0x88, 0xb0, 0x3c, 0xa8, 0x4c, 0xb7, 0x6c, 0x55, 0x26, 0x5a, 0x1e, 0x12, 0xbb, 0x2c, 0x0f, 0x8b, 0xde, 0x96, 0x6d, 0x9c, 0xbf, 0x9d, 0xf7, 0x1e, 0x91, 0x6f, 0xa9, 0x3d, 0x65, 0x89, 0x3a, 0x18, 0x86, 0xcb, 0xcf, 0xd4, 0xfb, 0xd8, 0xee, 0x92, 0x97, 0xd4, 0x67, 0x65, 0x81, 0x9a, 0x29, 0x7f, 0x54, + 0xb3, 0x20, 0x5b, 0x66, 0xa9, 0x39, 0x70, 0x02, 0x4e, 0xc2, 0x29, 0xc8, 0x85, 0x3c, 0x38, 0x27, 0x8f, 0xaa, 0x0e, 0xc8, 0x87, 0x02, 0x70, 0x42, 0x31, 0x9c, 0x87, 0x0b, 0x70, 0x11, 0x4a, 0xe4, 0x51, 0x6b, 0x4b, 0xf1, 0x88, 0x35, 0x44, 0x6c, 0xb3, 0xb6, 0x12, 0x36, 0xab, 0x4d, 0x16, 0x58, 0x5b, 0xf3, 0xba, 0x8d, 0x78, 0xde, 0xda, 0x96, 0xd7, 0x9d, 0xd8, 0xef, 0xcc, 0x7b, 0x5d, 0xd8, 0xef, 0xca, 0x7b, 0xdd, 0x78, 0xdd, 0x9d, 0xf7, 0x88, 0x51, 0xac, 0x33, 0x65, 0xb9, 0xf5, 0x17, 0xf9, 0xa4, 0xdf, 0x28, 0x99, 0xe3, 0xf7, 0x86, 0xa8, 0xe7, 0xf7, 0x0f, 0xf9, 0xb9, 0xdf, 0x9b, 0xca, 0x08, 0xbf, 0xb7, 0x94, 0x29, 0x7e, 0x6f, 0x8b, 0x74, 0xbf, 0x77, 0x44, 0x73, 0xbf, 0x77, 0x45, 0x17, 0xbf, 0xf7, 0x38, 0xfe, 0x3e, 0x7c, 0x40, 0x4c, 0x6b, 0x13, 0x26, 0x2c, 0xa6, 0x89, 0x74, + 0x61, 0x65, 0x1b, 0x44, 0xb8, 0x2c, 0x14, 0x76, 0xd8, 0x26, 0x9f, 0x13, 0xdb, 0xe5, 0x63, 0xe2, 0x11, 0xf9, 0xae, 0xd2, 0x4f, 0x66, 0x2b, 0xe3, 0xe5, 0x36, 0x65, 0xa6, 0x3c, 0xa9, 0xdc, 0x2e, 0x4b, 0x95, 0xd9, 0xf2, 0x11, 0x25, 0x52, 0x9e, 0x57, 0xa2, 0x21, 0x46, 0x6e, 0x52, 0xec, 0x72, 0xaf, 0x52, 0x22, 0x9d, 0x4a, 0xb9, 0x74, 0x9b, 0xc6, 0xc9, 0x33, 0x26, 0xbb, 0x3c, 0x66, 0xda, 0x22, 0xba, 0x61, 0x69, 0xaf, 0x98, 0x3e, 0x90, 0x65, 0xde, 0x9b, 0xe4, 0x13, 0xde, 0x4f, 0xc8, 0x72, 0xac, 0x6d, 0x1d, 0xd6, 0xb6, 0x0e, 0x6b, 0x7b, 0xc5, 0x9a, 0x2b, 0x5f, 0xf7, 0xdb, 0x26, 0xab, 0xfc, 0xb6, 0xcb, 0x2a, 0xd1, 0xfe, 0x77, 0x5b, 0xb0, 0x84, 0x75, 0xf0, 0xd6, 0x56, 0x44, 0x88, 0x26, 0xca, 0x70, 0xd0, 0x5a, 0x33, 0x5d, 0xf8, 0x29, 0x33, 0x45, 0x23, 0xe5, 0x76, 0xd1, 0xf0, + 0x7f, 0xaf, 0x45, 0x42, 0xf1, 0xfe, 0x9b, 0xf0, 0xfb, 0xc3, 0x56, 0x35, 0x30, 0x5a, 0x54, 0x4c, 0x8b, 0xca, 0x68, 0xd1, 0x05, 0xfa, 0x7f, 0x9e, 0xfe, 0x97, 0x50, 0x5a, 0x15, 0x25, 0x39, 0x4d, 0x6f, 0xca, 0x2f, 0x29, 0xed, 0x22, 0x7d, 0xfb, 0xc5, 0xfb, 0x6f, 0xb2, 0x8c, 0x12, 0x9c, 0x6a, 0x88, 0xf0, 0x56, 0x7b, 0x09, 0xed, 0x2f, 0x83, 0xe1, 0xf2, 0x22, 0x71, 0x4c, 0x73, 0x11, 0x23, 0x5d, 0xca, 0x6c, 0x61, 0x51, 0x62, 0x44, 0x7d, 0xae, 0x3a, 0xcb, 0x55, 0xe5, 0x9c, 0x79, 0x96, 0x33, 0x9b, 0xa9, 0xad, 0x44, 0x27, 0xd5, 0x26, 0x7a, 0xaa, 0xad, 0xc5, 0x6d, 0x6a, 0x1b, 0xe1, 0xcf, 0x95, 0x2d, 0x45, 0xb0, 0xb0, 0xca, 0x4a, 0xd1, 0x10, 0x9a, 0x50, 0x42, 0x7b, 0x79, 0x4a, 0x2f, 0xc9, 0xce, 0xb8, 0xf7, 0x97, 0x47, 0x95, 0x31, 0xb2, 0x40, 0x99, 0xce, 0x48, 0xcc, 0x94, 0x47, + 0x68, 0xcb, 0x71, 0xda, 0x72, 0xc5, 0xb4, 0x54, 0x56, 0x52, 0xaa, 0x53, 0x6f, 0xcb, 0x63, 0x32, 0x8b, 0xf6, 0x1c, 0xa1, 0x3d, 0x59, 0x3e, 0x21, 0xb2, 0xd2, 0x27, 0x0f, 0x4e, 0xcb, 0x4a, 0x33, 0xe7, 0x98, 0xd7, 0xc2, 0xdd, 0x50, 0x29, 0x2b, 0x2d, 0x03, 0x65, 0x25, 0x3d, 0x3d, 0x43, 0x4f, 0xcf, 0x88, 0x20, 0x6a, 0xf8, 0x99, 0x76, 0xe6, 0x53, 0x8b, 0x8b, 0x71, 0x6f, 0x2a, 0xd6, 0xca, 0x42, 0xc6, 0x4d, 0xeb, 0xe5, 0x2f, 0x5a, 0x7b, 0x29, 0xd9, 0x41, 0x9b, 0x7f, 0xb1, 0xbc, 0x24, 0x1a, 0x5a, 0x72, 0x65, 0xae, 0x1a, 0x4c, 0xdb, 0x5b, 0xca, 0x4a, 0xfa, 0xd0, 0x8a, 0x3e, 0x34, 0xa2, 0x0f, 0x8d, 0x68, 0xbf, 0x45, 0xed, 0x20, 0x3a, 0xd0, 0x87, 0x76, 0x44, 0x34, 0x26, 0xf9, 0x13, 0xa5, 0xee, 0x12, 0x91, 0xc4, 0x4e, 0x31, 0xf2, 0x0d, 0x4a, 0xfc, 0x96, 0xf6, 0x4f, 0x51, 0x06, + 0xc8, 0x12, 0x65, 0xa0, 0xbc, 0x48, 0x3f, 0x5e, 0x54, 0x4a, 0xe5, 0x69, 0xa5, 0x0c, 0xca, 0x65, 0x3e, 0xb5, 0x5d, 0xa1, 0xb6, 0x3c, 0x6a, 0xdb, 0x4b, 0x6d, 0xcf, 0x50, 0x5b, 0x9e, 0xe5, 0x25, 0xf9, 0xab, 0x51, 0x5b, 0x6b, 0x6a, 0xcb, 0xa1, 0xb6, 0x4e, 0xd4, 0xd6, 0x82, 0xda, 0x5a, 0x30, 0x62, 0xbe, 0xd4, 0x18, 0x40, 0x8d, 0x3d, 0xa8, 0xb1, 0x9b, 0x1a, 0x85, 0x75, 0x6d, 0x62, 0xd5, 0xb0, 0x52, 0x6b, 0x05, 0xfd, 0xa8, 0x66, 0x84, 0xca, 0x28, 0xb1, 0x88, 0x12, 0x0b, 0x29, 0xb1, 0x82, 0x51, 0xb9, 0x44, 0xa9, 0x45, 0x94, 0xa0, 0x50, 0x82, 0xc5, 0xa4, 0x08, 0x33, 0x63, 0x6c, 0x01, 0x7f, 0xb8, 0x0d, 0x82, 0x81, 0x5e, 0x09, 0x1b, 0x74, 0x66, 0x65, 0xe9, 0x2e, 0x4f, 0x8a, 0x1e, 0xd0, 0x13, 0x42, 0x51, 0x64, 0x2f, 0xf9, 0x8b, 0xe8, 0x0d, 0x7d, 0xa0, 0x2f, 0x84, 0x41, 0x3f, + 0xe8, 0x2f, 0x73, 0xc4, 0x00, 0x18, 0x08, 0xe1, 0x32, 0x57, 0x0c, 0xe2, 0xd8, 0x60, 0x18, 0x22, 0x2f, 0x88, 0xa1, 0x30, 0x0c, 0x22, 0x60, 0x38, 0x8c, 0x80, 0x91, 0x30, 0x0a, 0x46, 0xc3, 0x18, 0x18, 0x0b, 0xe3, 0x60, 0x3c, 0x4c, 0x80, 0x89, 0x30, 0x09, 0x26, 0xc3, 0x14, 0x98, 0x0a, 0xd3, 0x60, 0x3a, 0xcc, 0x80, 0x99, 0x30, 0x0b, 0x6e, 0x87, 0xd9, 0x30, 0x07, 0xe6, 0xc2, 0x3c, 0xb8, 0x03, 0xe6, 0xc3, 0x02, 0x58, 0x28, 0x33, 0xc5, 0x22, 0x99, 0x2d, 0xee, 0xa4, 0xed, 0x8b, 0x21, 0x12, 0xa2, 0x58, 0x41, 0xa3, 0x21, 0x06, 0xec, 0xb2, 0x44, 0x2c, 0x61, 0xbb, 0x14, 0x62, 0x79, 0x2f, 0x0e, 0x96, 0xc1, 0x72, 0x88, 0x87, 0x04, 0x48, 0x84, 0x24, 0xfa, 0x92, 0x0c, 0x2b, 0x20, 0x85, 0xd7, 0xa9, 0x90, 0x46, 0x5f, 0x57, 0xb2, 0x5d, 0xc5, 0xd8, 0xac, 0x86, 0xbb, 0x60, 0x0d, 0xa0, + 0x1c, 0x71, 0x37, 0x75, 0xaf, 0x63, 0x7f, 0x3d, 0xdc, 0x03, 0x1b, 0xe0, 0x5e, 0xd8, 0x08, 0x9b, 0x60, 0x33, 0xdc, 0x07, 0xf7, 0xc3, 0x16, 0xe9, 0x10, 0x0f, 0x40, 0x3a, 0x71, 0xf1, 0x83, 0x44, 0x0f, 0x5b, 0x89, 0x28, 0x1f, 0x22, 0xc6, 0x7c, 0x58, 0x9e, 0x55, 0xf2, 0x65, 0xa5, 0x52, 0x28, 0x7f, 0x51, 0x8a, 0xa4, 0xc3, 0x34, 0x0a, 0x8d, 0x4f, 0x84, 0xe9, 0x70, 0x27, 0x24, 0x33, 0xab, 0x2b, 0x20, 0x15, 0x85, 0xd2, 0x16, 0xd3, 0x5d, 0xec, 0xaf, 0x81, 0x75, 0x40, 0x9d, 0x26, 0xea, 0x34, 0x6d, 0xc4, 0xda, 0xb6, 0xc8, 0x4c, 0x54, 0x50, 0x6a, 0x4a, 0xe7, 0x9c, 0xad, 0xbc, 0x7e, 0x94, 0xe3, 0x4f, 0xb0, 0xbf, 0x03, 0x05, 0x3c, 0xc9, 0xb9, 0x4f, 0xc1, 0xd3, 0x1c, 0xdf, 0x2b, 0x2f, 0x98, 0x9e, 0x87, 0x17, 0xe0, 0x45, 0xd8, 0x07, 0x2f, 0xc1, 0x7e, 0x78, 0x19, 0x5e, 0x81, 0x03, + 0xf0, 0x2a, 0xd7, 0xff, 0x1d, 0xde, 0x94, 0xc5, 0xa8, 0xea, 0x9c, 0x37, 0xe3, 0xeb, 0x9d, 0x26, 0x0b, 0x7d, 0x82, 0xe0, 0xdf, 0xf2, 0x82, 0xcf, 0x67, 0xf0, 0x85, 0x2c, 0x42, 0x69, 0xa5, 0x3e, 0x47, 0xd8, 0x3f, 0x0a, 0x3f, 0xc0, 0x8f, 0x70, 0x0c, 0x4e, 0x61, 0x8f, 0x67, 0xe4, 0x2f, 0x3e, 0x05, 0x32, 0xc7, 0xe7, 0xbc, 0x74, 0xf8, 0xb6, 0x95, 0x85, 0xbe, 0xed, 0xa0, 0x83, 0x3c, 0xe9, 0x5b, 0x25, 0x7f, 0x31, 0x2f, 0xc7, 0x3e, 0xe3, 0x21, 0x01, 0x12, 0x21, 0x05, 0xf6, 0xc1, 0x4b, 0xb0, 0x1f, 0x5e, 0x86, 0x57, 0xe0, 0x00, 0xbc, 0x8a, 0x1d, 0xd7, 0x07, 0xf4, 0x6b, 0x69, 0x00, 0xf8, 0x0c, 0x4b, 0x00, 0x34, 0x82, 0x40, 0x08, 0x82, 0x66, 0xd0, 0x1c, 0x5a, 0x00, 0x1a, 0xb7, 0xa0, 0x71, 0x0b, 0x1a, 0xb7, 0xe0, 0x1b, 0x2c, 0xad, 0x00, 0xad, 0x5b, 0x5a, 0x43, 0x1b, 0x68, 0x0b, + 0xed, 0xa0, 0x3d, 0x74, 0x80, 0x8e, 0xd0, 0x09, 0x3a, 0x43, 0x17, 0xe8, 0x0b, 0x29, 0xb2, 0x9c, 0x55, 0xb6, 0x9c, 0x55, 0xb6, 0x9c, 0x55, 0xb6, 0x9c, 0x55, 0xb6, 0x9c, 0x55, 0xb6, 0xdc, 0xb2, 0x5b, 0x9e, 0xb4, 0x3c, 0x09, 0x4f, 0xc1, 0xd3, 0xf0, 0x0c, 0x3c, 0x0b, 0xcf, 0xc1, 0x1e, 0xd8, 0x0b, 0x2f, 0xc0, 0x8b, 0xb0, 0x0f, 0x5e, 0x82, 0xfd, 0xf0, 0x32, 0xbc, 0x02, 0x07, 0x80, 0xb1, 0xb5, 0x30, 0xb6, 0x96, 0xd7, 0xe0, 0x75, 0x78, 0x03, 0xfe, 0x01, 0x6f, 0xc2, 0x5b, 0xf0, 0x36, 0xbc, 0x0f, 0x1f, 0xc0, 0x87, 0xf0, 0x11, 0x7c, 0x0c, 0x9f, 0xc8, 0x42, 0xcb, 0xa7, 0xf0, 0x2f, 0xf8, 0x0c, 0x2f, 0xf5, 0x39, 0x1c, 0x84, 0x2f, 0xe0, 0x1b, 0xf8, 0x56, 0xe6, 0x58, 0x0e, 0xc1, 0x61, 0xf8, 0x0e, 0x8e, 0xc2, 0x0f, 0x1c, 0xff, 0x11, 0x8e, 0xc1, 0x4f, 0xf0, 0x33, 0x64, 0xc0, 0x71, + 0xc8, 0x84, 0x1c, 0x38, 0x01, 0x27, 0xe1, 0x14, 0xe4, 0x42, 0x1e, 0x9c, 0x06, 0xe6, 0xcf, 0xf2, 0x2b, 0x9c, 0x03, 0x07, 0xe4, 0x43, 0x01, 0xa0, 0x55, 0x8b, 0x13, 0x8a, 0xe1, 0xbc, 0xbc, 0x60, 0xb9, 0x08, 0x25, 0x50, 0x0a, 0x65, 0x50, 0x0e, 0x97, 0xa0, 0x02, 0x2a, 0xe1, 0x32, 0x5c, 0x81, 0xab, 0x50, 0x05, 0xd5, 0xe0, 0x02, 0x37, 0x5c, 0x83, 0x1a, 0x90, 0x32, 0x53, 0x15, 0xa0, 0x80, 0x09, 0xbc, 0xc0, 0x1b, 0x7c, 0xc0, 0x57, 0x66, 0xab, 0x66, 0xb0, 0x80, 0x0a, 0xf5, 0xc1, 0x1f, 0x1a, 0x40, 0x43, 0x59, 0xa8, 0x06, 0x40, 0x23, 0x08, 0x04, 0xf4, 0xa9, 0x36, 0x86, 0xa6, 0x78, 0xc0, 0xe6, 0xd0, 0x02, 0x82, 0xa1, 0x25, 0x84, 0xc8, 0x2a, 0xb5, 0x15, 0x5b, 0x1b, 0xb4, 0x86, 0x36, 0xd0, 0x1e, 0x3a, 0x40, 0x27, 0xe8, 0x06, 0xdd, 0xa1, 0x27, 0xf4, 0x92, 0x35, 0x6a, 0x1f, 0xca, + 0xe9, 0x0b, 0x61, 0xd0, 0x0f, 0xfa, 0x03, 0xab, 0xb6, 0x3a, 0x08, 0x06, 0xc3, 0x10, 0x18, 0x0a, 0xc3, 0x20, 0x02, 0x86, 0xc3, 0x08, 0x18, 0x05, 0xa3, 0x61, 0x0c, 0x8c, 0x87, 0x09, 0x30, 0x11, 0x26, 0xc1, 0x64, 0x98, 0x02, 0x53, 0x61, 0x1a, 0x4c, 0x87, 0x99, 0x30, 0x4b, 0xfe, 0xa2, 0xde, 0x0e, 0xb3, 0x61, 0x0e, 0xcc, 0x85, 0x79, 0x70, 0x07, 0xcc, 0x87, 0x3b, 0x61, 0x31, 0x44, 0x42, 0x14, 0x44, 0x43, 0x0c, 0xd8, 0x61, 0x09, 0x2c, 0x85, 0x58, 0x88, 0x83, 0x65, 0xb0, 0x1c, 0xe2, 0x21, 0x01, 0x12, 0xe1, 0x2e, 0x58, 0x03, 0x6b, 0x01, 0xdf, 0xa1, 0xae, 0x87, 0x8d, 0xb0, 0x09, 0x36, 0xc3, 0x7d, 0x70, 0x3f, 0x6c, 0x81, 0x07, 0xe0, 0x41, 0xd8, 0x0a, 0x0f, 0xc9, 0x93, 0xea, 0xc3, 0xb0, 0x0d, 0xb6, 0x03, 0x7e, 0x45, 0x7d, 0x0c, 0x1e, 0x87, 0x27, 0x60, 0x07, 0xec, 0x02, 0xec, + 0x42, 0xc5, 0x2e, 0x54, 0xec, 0x42, 0xc5, 0x2e, 0x54, 0xec, 0x82, 0x68, 0xf1, 0xa4, 0x8a, 0x5d, 0xa8, 0xd8, 0x85, 0x8a, 0x5d, 0xa8, 0xcf, 0x03, 0xb6, 0xa1, 0x62, 0x1b, 0x2a, 0xb6, 0xa1, 0x62, 0x1b, 0x2a, 0xb6, 0xa1, 0x62, 0x1b, 0x2a, 0xb6, 0xa1, 0x62, 0x1b, 0x2a, 0xb6, 0xa1, 0x62, 0x1b, 0x2a, 0xb6, 0xa1, 0x62, 0x1b, 0x2a, 0xb6, 0xa1, 0x62, 0x1b, 0x2a, 0xb6, 0xa1, 0x62, 0x1b, 0x2a, 0xb6, 0xa1, 0xbe, 0x03, 0xef, 0xc2, 0x7b, 0x80, 0x9d, 0xa8, 0xd8, 0x89, 0x8a, 0x9d, 0xa8, 0xd8, 0x89, 0x8a, 0x9d, 0xa8, 0xff, 0x84, 0x4f, 0xe0, 0x53, 0xf8, 0x97, 0x74, 0xa8, 0xff, 0x86, 0xcf, 0xe0, 0x73, 0x38, 0x08, 0x5f, 0xc0, 0x97, 0xf0, 0x15, 0x7c, 0x03, 0xdf, 0xc2, 0x21, 0x38, 0x0c, 0xdf, 0xc1, 0xf7, 0x70, 0x04, 0x8e, 0xc2, 0x0f, 0xf0, 0x23, 0x1c, 0x83, 0x9f, 0x20, 0x03, 0x8e, 0x43, + 0xa6, 0xf0, 0x51, 0xb3, 0x20, 0x5b, 0x98, 0xd4, 0x1c, 0x38, 0x01, 0x27, 0xe1, 0x14, 0xe4, 0x42, 0x1e, 0x9c, 0x21, 0x46, 0xfa, 0x15, 0xce, 0x11, 0x31, 0x38, 0x20, 0x1f, 0x0a, 0xc0, 0x09, 0xc5, 0x70, 0x1e, 0x2e, 0xc0, 0x45, 0x28, 0x81, 0x4b, 0x44, 0x4b, 0x15, 0x50, 0x09, 0x97, 0xe1, 0x0a, 0x5c, 0x95, 0x67, 0x89, 0x74, 0x0b, 0xad, 0x4e, 0x59, 0x69, 0x2d, 0x86, 0xf3, 0x70, 0x01, 0x2e, 0x42, 0x85, 0xfc, 0xc5, 0x5a, 0x09, 0x97, 0xe1, 0x0a, 0x5c, 0x85, 0x2a, 0xe9, 0xb0, 0x56, 0x83, 0x0b, 0xdc, 0x70, 0x4d, 0x3a, 0xfc, 0xf0, 0x05, 0x44, 0x6a, 0xda, 0x8a, 0xad, 0xaf, 0x84, 0x42, 0xd1, 0x57, 0x0a, 0xc3, 0xb3, 0x6b, 0x1e, 0x5c, 0xb7, 0x0c, 0x14, 0x4f, 0x2c, 0x18, 0xce, 0x3a, 0x1a, 0xa3, 0x47, 0xa6, 0x17, 0x88, 0x63, 0xce, 0x13, 0xbf, 0x5c, 0xe0, 0x6c, 0x2d, 0xee, 0xca, 0xe3, + 0xec, 0x93, 0x46, 0x74, 0x51, 0x89, 0x5f, 0x3b, 0x8b, 0x4d, 0x15, 0x72, 0xa5, 0xc4, 0x7e, 0x6a, 0x88, 0x4d, 0xbc, 0x45, 0x13, 0xae, 0xfe, 0x90, 0x2b, 0xaf, 0xb2, 0x8a, 0x1e, 0x21, 0x4e, 0xcd, 0x24, 0x4e, 0xcd, 0x22, 0x1e, 0xf4, 0x27, 0xea, 0x39, 0x4b, 0x04, 0x57, 0x41, 0x6c, 0x1a, 0x4a, 0x69, 0x8f, 0x51, 0xd2, 0x09, 0x4a, 0xf9, 0x84, 0x38, 0x6b, 0x00, 0x1e, 0xc7, 0x45, 0xe4, 0x33, 0x9c, 0x58, 0xc5, 0x9f, 0x68, 0xa7, 0x3e, 0xd1, 0x4e, 0x2f, 0xa2, 0x9d, 0x29, 0xa8, 0xbc, 0x1c, 0xb5, 0x1e, 0x21, 0x56, 0xfd, 0x89, 0x58, 0xd1, 0x44, 0x76, 0x1c, 0x2e, 0x0f, 0x52, 0xfa, 0x0f, 0x44, 0xcb, 0x7d, 0x89, 0x96, 0xfb, 0x10, 0x2d, 0xf7, 0x21, 0x5a, 0xce, 0x23, 0x4a, 0x4e, 0x51, 0xa6, 0xd6, 0x9c, 0x25, 0x3a, 0xee, 0x4d, 0xad, 0xa7, 0xa9, 0xf5, 0x34, 0xd1, 0x71, 0x2f, 0xa2, 0xe3, + 0xde, 0x44, 0xc7, 0x55, 0xd4, 0x5e, 0x48, 0xed, 0x5f, 0x51, 0xbb, 0x95, 0xda, 0xa7, 0xd1, 0x97, 0xab, 0xb4, 0xc0, 0x49, 0x74, 0xdc, 0xdb, 0x7b, 0xab, 0x8c, 0xf5, 0xde, 0x25, 0x2f, 0x12, 0x1d, 0xf7, 0x27, 0x3a, 0xee, 0x4b, 0xab, 0xa6, 0x51, 0x63, 0xb8, 0x18, 0xa6, 0xd7, 0xd8, 0x4e, 0x26, 0x12, 0x99, 0x26, 0x92, 0x93, 0xef, 0x26, 0x52, 0xda, 0x43, 0x4e, 0xbe, 0xfb, 0x0f, 0x5b, 0x31, 0x44, 0x4e, 0x52, 0x86, 0xc1, 0x78, 0xb9, 0xc6, 0x68, 0xcd, 0x14, 0x65, 0xa1, 0x9c, 0xa5, 0x2c, 0x82, 0x1b, 0xad, 0x5a, 0x71, 0x53, 0xab, 0xee, 0x90, 0xbb, 0xf5, 0x96, 0xc5, 0xb2, 0xfd, 0xcf, 0xad, 0x9b, 0x41, 0xeb, 0xa6, 0x6a, 0xad, 0x23, 0x4f, 0x4c, 0x24, 0x4f, 0x4c, 0x24, 0x47, 0x4c, 0x24, 0x47, 0x4c, 0x24, 0x47, 0x4c, 0x24, 0x47, 0x4c, 0x24, 0x47, 0x4c, 0x24, 0x47, 0xdc, 0x4d, 0x8e, + 0xb8, 0x9b, 0xd5, 0x6b, 0x0f, 0xab, 0xd7, 0x1e, 0x56, 0xaf, 0x3d, 0xac, 0x5e, 0x7b, 0x58, 0xbd, 0xf6, 0xb0, 0x7a, 0xed, 0x21, 0x47, 0xdc, 0x4d, 0x8e, 0xb8, 0x9b, 0x1c, 0x71, 0x37, 0x39, 0xe2, 0x6e, 0xf2, 0xc2, 0xdd, 0xe4, 0x85, 0xbb, 0xc9, 0x0b, 0x77, 0x93, 0x17, 0xee, 0x26, 0x2f, 0xdc, 0xad, 0xf7, 0xde, 0x52, 0xab, 0x96, 0x3f, 0x54, 0x4a, 0xdd, 0xe8, 0xbd, 0x84, 0x31, 0xca, 0xe5, 0x8a, 0x12, 0xae, 0x28, 0xab, 0x13, 0xbd, 0xe7, 0x10, 0x9b, 0xfe, 0x40, 0xf4, 0x9e, 0x75, 0x4b, 0xf4, 0x5e, 0x42, 0xf4, 0xfe, 0x0b, 0x4a, 0x3a, 0xfa, 0x27, 0xa3, 0xf7, 0x5f, 0x89, 0xde, 0x7f, 0x15, 0x81, 0x37, 0x45, 0xef, 0xff, 0xd3, 0xc8, 0x3d, 0x90, 0xf9, 0x3d, 0xa4, 0x67, 0x3e, 0xe1, 0xf2, 0x80, 0xd0, 0x9e, 0x6c, 0x1a, 0x23, 0x2f, 0xd3, 0xf6, 0x91, 0xb4, 0x7d, 0xbf, 0x16, 0x53, 0x2b, + 0x77, 0x52, 0x6a, 0xac, 0x1e, 0x57, 0x57, 0x51, 0xfa, 0x69, 0xda, 0x9b, 0x43, 0x7b, 0x2f, 0x19, 0xd1, 0x79, 0x00, 0xa5, 0x74, 0xaf, 0xcd, 0xa1, 0x94, 0xd6, 0xc2, 0x8b, 0xb6, 0x79, 0x43, 0x20, 0x39, 0x40, 0x1b, 0xe9, 0x16, 0x6d, 0xa1, 0x1d, 0x4a, 0x69, 0x0f, 0x9d, 0x64, 0x86, 0x7e, 0x47, 0xa7, 0x8b, 0xfc, 0x18, 0x05, 0x1d, 0x10, 0xdd, 0x78, 0xdd, 0x9d, 0x9a, 0x7b, 0x40, 0x4f, 0x08, 0x85, 0x85, 0x70, 0x27, 0x2c, 0x86, 0x48, 0x88, 0x82, 0x68, 0x88, 0xd1, 0x32, 0x15, 0x58, 0x0a, 0xb1, 0x10, 0x07, 0xcb, 0x60, 0x39, 0xc4, 0x43, 0x02, 0x24, 0x42, 0x0a, 0xa4, 0xc2, 0x4a, 0x7d, 0x6c, 0x5c, 0xc4, 0x9b, 0x6e, 0xe2, 0x4d, 0xb7, 0xd8, 0x2a, 0xab, 0x89, 0x31, 0xdd, 0x4a, 0x98, 0x3e, 0x33, 0x5f, 0x29, 0xe1, 0xf2, 0x53, 0x65, 0x10, 0x44, 0xc8, 0x6f, 0x94, 0xe1, 0x30, 0x86, 0x5c, + 0x77, 0xba, 0x9e, 0xf3, 0xfd, 0xc2, 0x4c, 0x95, 0xa3, 0xd0, 0x4b, 0xc4, 0xa1, 0x6e, 0x94, 0x79, 0xc0, 0x34, 0x5f, 0x66, 0x98, 0x62, 0x20, 0x96, 0xfd, 0x78, 0xb6, 0xc9, 0xd2, 0x45, 0x2c, 0xea, 0x22, 0x06, 0x75, 0x11, 0x83, 0xba, 0x88, 0x41, 0x5d, 0xc4, 0x9e, 0x6e, 0x62, 0x4f, 0x17, 0x31, 0xa7, 0x9b, 0x38, 0xd3, 0x45, 0x9c, 0xe9, 0x22, 0xce, 0x74, 0x13, 0x2b, 0xba, 0x98, 0xe5, 0x62, 0x23, 0x67, 0xbc, 0x44, 0xdc, 0xe8, 0x42, 0xc5, 0xe5, 0xa8, 0xb8, 0x9c, 0xb8, 0xd1, 0x45, 0x4c, 0xe8, 0x26, 0x26, 0x74, 0x11, 0x13, 0xba, 0xcc, 0x5d, 0xe5, 0xcf, 0xe6, 0x6e, 0xd0, 0x1d, 0x7a, 0x40, 0x4f, 0x08, 0x85, 0x5e, 0xd0, 0x1b, 0xfa, 0x40, 0x7f, 0x18, 0x00, 0x03, 0x01, 0x1d, 0x98, 0x07, 0xc1, 0x60, 0x18, 0x02, 0x43, 0x61, 0x18, 0x44, 0xc0, 0x70, 0x18, 0x01, 0x23, 0x61, 0x14, 0x8c, + 0x86, 0x31, 0x30, 0x16, 0xc6, 0x41, 0x24, 0x38, 0xe5, 0x1b, 0xe6, 0xf3, 0xf2, 0x0d, 0xcb, 0x18, 0xe9, 0xb6, 0x8c, 0x85, 0x71, 0x30, 0x1e, 0x26, 0xc2, 0x24, 0x98, 0x0c, 0x53, 0x60, 0x2a, 0x4c, 0x93, 0x07, 0xb1, 0xb6, 0x83, 0x58, 0xdb, 0x41, 0xac, 0xed, 0x20, 0xd6, 0x76, 0x10, 0x6b, 0x3b, 0x88, 0xb5, 0x1d, 0xc4, 0xda, 0x32, 0xb0, 0xb6, 0x8c, 0x3f, 0xbc, 0x23, 0xb3, 0x56, 0x7e, 0x6c, 0xb9, 0x1b, 0xd6, 0xc9, 0x03, 0x58, 0xdd, 0x01, 0xac, 0xee, 0x00, 0x56, 0x77, 0x00, 0xab, 0x3b, 0x80, 0xd5, 0x1d, 0xc0, 0xea, 0x0e, 0x60, 0x75, 0x07, 0xb0, 0xba, 0x03, 0x96, 0xc7, 0x28, 0xe7, 0x71, 0x78, 0x02, 0xfe, 0x06, 0x3b, 0x61, 0x17, 0xec, 0x96, 0x2e, 0xe2, 0x4d, 0x17, 0xf1, 0xa6, 0x8b, 0x78, 0xd3, 0x45, 0xbc, 0xe9, 0x22, 0xde, 0x74, 0x11, 0x6f, 0xba, 0x88, 0x37, 0x5d, 0xc4, 0x9b, + 0x2e, 0xe2, 0x4d, 0x17, 0xf1, 0xa6, 0x8b, 0x78, 0xd3, 0x85, 0x5f, 0x76, 0x11, 0x6f, 0xba, 0x88, 0x37, 0x5d, 0xc4, 0x9b, 0x2e, 0xe2, 0x4d, 0x17, 0xf1, 0xa6, 0x8b, 0x78, 0xd3, 0x45, 0xbc, 0xe9, 0x22, 0xde, 0x74, 0x11, 0x6f, 0xba, 0x88, 0x37, 0x5d, 0xc4, 0x9b, 0x2e, 0xe2, 0x4d, 0x17, 0xf1, 0xa6, 0x8b, 0x78, 0xd3, 0x45, 0xbc, 0xe9, 0x22, 0xde, 0x74, 0x11, 0x6f, 0xba, 0x88, 0x37, 0x5d, 0xc4, 0x9b, 0x2e, 0xe2, 0x4d, 0x17, 0xf1, 0xa6, 0x8b, 0xf8, 0xcc, 0x45, 0x7c, 0xe6, 0x22, 0x3e, 0x73, 0x11, 0x9f, 0xb9, 0x88, 0xcf, 0x5c, 0xc4, 0x67, 0x2e, 0xe2, 0x33, 0x17, 0x31, 0x98, 0x8b, 0x18, 0xcc, 0x45, 0x0c, 0xe6, 0x22, 0x06, 0x73, 0x11, 0x83, 0xb9, 0x88, 0xc1, 0x5c, 0xc4, 0x60, 0x2e, 0x62, 0x30, 0x17, 0x31, 0x18, 0x1e, 0x1e, 0x5a, 0x42, 0x08, 0xb4, 0x02, 0x1b, 0xb4, 0x86, 0x36, + 0xd0, 0x1e, 0x3a, 0x40, 0x27, 0xe8, 0x06, 0xd8, 0x06, 0x31, 0x98, 0x0b, 0x3f, 0xe3, 0x22, 0x06, 0x73, 0x11, 0x83, 0xb9, 0x88, 0xc1, 0x5c, 0xc4, 0x60, 0x2e, 0x62, 0x30, 0x17, 0x31, 0x98, 0x8b, 0x18, 0xcc, 0x45, 0x0c, 0xe6, 0x22, 0x06, 0x73, 0x11, 0x83, 0xb9, 0x88, 0xc1, 0x5c, 0xc4, 0x60, 0x2e, 0x62, 0x30, 0x17, 0x31, 0x98, 0x8b, 0x18, 0xcc, 0x45, 0x0c, 0xe6, 0x22, 0x06, 0x73, 0x11, 0x83, 0xb9, 0x88, 0xc1, 0x5c, 0xc4, 0x60, 0x2e, 0x62, 0x30, 0x17, 0x31, 0x98, 0x8b, 0x18, 0xcc, 0x45, 0x0c, 0xe6, 0x22, 0x06, 0x73, 0x11, 0x83, 0xb9, 0x88, 0xc1, 0x5c, 0xc4, 0x45, 0x2e, 0xe2, 0x22, 0x17, 0x71, 0x91, 0x8b, 0xb8, 0xc8, 0x45, 0x5c, 0xe4, 0x22, 0x2e, 0x72, 0x11, 0x17, 0xb9, 0x88, 0x8b, 0x5c, 0xc4, 0x45, 0x2e, 0xe2, 0x22, 0x17, 0x71, 0x91, 0x8b, 0xb8, 0xc2, 0x4d, 0x5c, 0xe1, + 0x26, 0xae, 0x70, 0x13, 0x57, 0xb8, 0x89, 0x2b, 0xdc, 0xc4, 0x15, 0x6e, 0xe2, 0x0a, 0x37, 0x71, 0x85, 0x9b, 0xb8, 0xc2, 0x4d, 0x5c, 0xe1, 0x26, 0xae, 0x70, 0x13, 0x57, 0xb8, 0x89, 0x2b, 0xdc, 0xc4, 0x15, 0x6e, 0xe2, 0x0a, 0x37, 0x71, 0x85, 0x9b, 0xb8, 0xc2, 0x4d, 0x5c, 0xe1, 0x26, 0xae, 0x70, 0x13, 0x57, 0xb8, 0x89, 0x2b, 0xdc, 0xc4, 0x15, 0x6e, 0xf5, 0x8c, 0xac, 0x56, 0x7f, 0x85, 0x4b, 0xec, 0x57, 0x40, 0x25, 0x5c, 0x86, 0x2b, 0x70, 0x55, 0xba, 0x89, 0x05, 0x5c, 0x78, 0xe8, 0x52, 0xd6, 0x79, 0x37, 0xeb, 0xbc, 0x9b, 0x75, 0xde, 0xcd, 0x3a, 0xef, 0x66, 0x9d, 0x77, 0x8b, 0x90, 0xff, 0xe8, 0x93, 0x23, 0x64, 0x35, 0x16, 0x5f, 0x6d, 0xf8, 0xe6, 0x73, 0x58, 0x7c, 0x35, 0x16, 0xef, 0xfa, 0x03, 0xdf, 0x5c, 0x84, 0xd5, 0x9e, 0xff, 0xd3, 0xbe, 0xb9, 0x05, 0x35, 0x5f, 0xa5, + 0xe6, 0xab, 0xd4, 0x7c, 0x95, 0x9a, 0x7f, 0xa1, 0xe6, 0x8f, 0xa9, 0xf9, 0x84, 0x16, 0x09, 0xe0, 0xa7, 0x4b, 0xa9, 0xe5, 0x02, 0xb5, 0x5c, 0x65, 0x95, 0xeb, 0x64, 0xf8, 0xec, 0xf3, 0xd4, 0x76, 0x86, 0x1a, 0xae, 0x6a, 0x7e, 0x9b, 0x5a, 0xae, 0x52, 0xcb, 0x55, 0x6a, 0xb9, 0x4a, 0x2d, 0x57, 0xa9, 0xe5, 0x2a, 0xb5, 0x5c, 0xa5, 0x96, 0xab, 0x68, 0xe6, 0x1a, 0xde, 0x56, 0xd1, 0xa2, 0x01, 0x61, 0xa5, 0x06, 0x37, 0xa5, 0xbb, 0x28, 0xbd, 0x8a, 0x3e, 0x94, 0x1b, 0x77, 0x53, 0x0e, 0xe9, 0xf7, 0xbb, 0x9e, 0xd0, 0xef, 0x39, 0xe5, 0xe1, 0xed, 0x7d, 0xc5, 0x42, 0x23, 0x66, 0x98, 0x2f, 0x46, 0xca, 0x8d, 0x62, 0x94, 0x7c, 0x40, 0x4c, 0x60, 0x7b, 0x3b, 0xcc, 0x83, 0x48, 0x62, 0x9d, 0x18, 0x39, 0x4a, 0x2b, 0x05, 0x7f, 0xb9, 0x87, 0x11, 0x4a, 0x53, 0x06, 0xc8, 0xa7, 0x94, 0x81, 0x72, + 0x17, 0x23, 0xb4, 0x09, 0xef, 0x7f, 0x58, 0x59, 0x2c, 0x5f, 0x61, 0xe5, 0x3e, 0xc7, 0xca, 0x7d, 0x8e, 0x28, 0xc6, 0xac, 0x94, 0xca, 0xbf, 0x29, 0x65, 0x72, 0x87, 0x52, 0x2e, 0x1f, 0xa7, 0xd6, 0x71, 0xd4, 0xda, 0x91, 0x5a, 0x1b, 0xb3, 0x42, 0x17, 0x52, 0xeb, 0x4b, 0xe4, 0xc0, 0x1b, 0xc9, 0x81, 0x37, 0x62, 0xb3, 0xa3, 0xb0, 0xd7, 0xa9, 0xd8, 0xeb, 0x50, 0xec, 0x75, 0x1a, 0xf6, 0xda, 0x19, 0x7b, 0x1d, 0x88, 0x7d, 0x4e, 0xc1, 0x3e, 0x0b, 0xb0, 0xcf, 0x29, 0xd8, 0xe7, 0x14, 0xec, 0x73, 0x2a, 0xf6, 0x19, 0x87, 0x7d, 0xc6, 0x61, 0x9f, 0x71, 0xd8, 0x67, 0x1c, 0xf6, 0x39, 0x10, 0xfb, 0x9c, 0x4f, 0x6e, 0xd7, 0x83, 0xdc, 0x2e, 0x94, 0xfc, 0xad, 0x25, 0xb9, 0x5b, 0x27, 0x72, 0xb7, 0xae, 0xe4, 0x6e, 0x9d, 0xc9, 0xd3, 0x9a, 0x92, 0xa7, 0x75, 0x64, 0x8d, 0x7b, 0x85, 0x3c, 0xad, + 0x23, 0x79, 0x5a, 0x47, 0xf2, 0xb4, 0x4e, 0xe4, 0x60, 0x1b, 0xc9, 0xc1, 0x36, 0x92, 0x83, 0x6d, 0x24, 0x9f, 0xda, 0x88, 0xbd, 0x46, 0x60, 0xaf, 0xed, 0xb1, 0xd3, 0x11, 0xe4, 0x4e, 0x9d, 0xc8, 0x99, 0xba, 0xb0, 0x26, 0xf6, 0xc1, 0x06, 0x3f, 0x60, 0x05, 0x1b, 0xca, 0x0a, 0xd6, 0x8d, 0x51, 0xea, 0x40, 0xc4, 0xd5, 0x8c, 0x75, 0xb1, 0xbb, 0x1e, 0x71, 0xf5, 0x12, 0x23, 0xc9, 0x43, 0x9a, 0x90, 0x83, 0xd8, 0xc8, 0x3f, 0x5a, 0x93, 0x6b, 0xb4, 0x22, 0x0a, 0xdb, 0x4b, 0xae, 0xd1, 0x8a, 0x5c, 0xa3, 0x15, 0xb9, 0x86, 0x8d, 0x5c, 0xa3, 0x2d, 0xb6, 0xf1, 0x02, 0xb6, 0x30, 0x97, 0xbc, 0x20, 0x9e, 0xbc, 0xe0, 0x7e, 0x74, 0xba, 0x1c, 0x4d, 0xfe, 0x40, 0x2c, 0xda, 0x44, 0x7b, 0x6a, 0x93, 0xfc, 0x86, 0xd5, 0x70, 0x03, 0xab, 0xe1, 0x06, 0x61, 0x96, 0x79, 0xc2, 0x02, 0xfe, 0x70, 0x1b, + 0x04, 0x43, 0x4b, 0xb0, 0x41, 0x77, 0x39, 0x8e, 0x15, 0x70, 0x1c, 0x2b, 0xe0, 0x38, 0xd1, 0x4b, 0x4e, 0x16, 0xbd, 0xa1, 0x0f, 0xf4, 0x85, 0x30, 0xe8, 0x07, 0xfd, 0xe5, 0x14, 0x31, 0x00, 0x06, 0x82, 0x36, 0x93, 0x83, 0x38, 0x36, 0x18, 0x86, 0xc8, 0xe7, 0xc5, 0x50, 0x18, 0x06, 0x11, 0x30, 0x1c, 0x46, 0x80, 0x67, 0xa6, 0x9f, 0x16, 0xa3, 0xe5, 0x7e, 0xc1, 0x3a, 0x2e, 0xc6, 0xc2, 0x38, 0x18, 0x0f, 0xda, 0xec, 0x4f, 0x64, 0x3b, 0x89, 0xf3, 0x26, 0xc3, 0x14, 0x98, 0x0a, 0xd3, 0x60, 0x3a, 0xcc, 0x80, 0x99, 0x30, 0x0b, 0x34, 0x95, 0xcc, 0x66, 0x3b, 0x07, 0xe6, 0xc2, 0x3c, 0xca, 0xbc, 0x83, 0xed, 0x7c, 0x58, 0x00, 0x0b, 0xe5, 0x4c, 0xb1, 0x48, 0x4e, 0xbb, 0x49, 0x45, 0x71, 0x32, 0x8a, 0x15, 0x39, 0x8a, 0x15, 0x39, 0x8a, 0x15, 0x39, 0x4a, 0x24, 0xd1, 0xce, 0x64, 0x58, 0x01, + 0x29, 0xbc, 0x4e, 0x93, 0xab, 0x58, 0x9d, 0xa3, 0xc4, 0x2a, 0xb9, 0x50, 0xac, 0x86, 0xbb, 0x60, 0x0d, 0xa0, 0x3e, 0x71, 0x37, 0x65, 0xae, 0x93, 0x4f, 0x89, 0xf5, 0x70, 0x0f, 0x6c, 0x80, 0x7b, 0x61, 0x23, 0x6c, 0x82, 0xcd, 0x70, 0x1f, 0xdc, 0x0f, 0x5b, 0x64, 0x3c, 0x2b, 0x7b, 0xbc, 0x48, 0x97, 0xeb, 0xc4, 0x83, 0xb4, 0x73, 0xab, 0x4c, 0x12, 0x0f, 0xc9, 0xb5, 0xac, 0xf2, 0x73, 0x0d, 0x15, 0x7f, 0x8c, 0x8a, 0x3f, 0x54, 0x46, 0xc8, 0xcd, 0xca, 0x48, 0xd4, 0x3a, 0x8a, 0xed, 0x68, 0xb6, 0x63, 0xe4, 0x76, 0x65, 0x82, 0xdc, 0xab, 0x4c, 0x82, 0x1b, 0xea, 0xbe, 0x84, 0xba, 0x2f, 0xe9, 0xea, 0xce, 0x27, 0x86, 0x2d, 0x94, 0x93, 0x59, 0xfd, 0xe3, 0x6f, 0x52, 0xfa, 0x28, 0xec, 0x6b, 0x22, 0x4c, 0x87, 0x3b, 0x21, 0x55, 0x4e, 0x36, 0xa5, 0xc9, 0x29, 0x44, 0x01, 0x51, 0x44, 0x00, + 0x51, 0x26, 0xda, 0x6b, 0xa2, 0xbd, 0x44, 0x02, 0x73, 0x89, 0x04, 0x66, 0xea, 0x96, 0x91, 0xce, 0xfb, 0x5b, 0x79, 0xfd, 0xa8, 0x5c, 0x68, 0x7a, 0x82, 0xfd, 0x1d, 0x72, 0x2d, 0x51, 0xc1, 0x5c, 0xd3, 0x5e, 0xb9, 0xdf, 0xf4, 0x3c, 0xbc, 0x20, 0x5f, 0x32, 0xbd, 0xc8, 0x76, 0x1f, 0xbc, 0x24, 0x9f, 0x37, 0xed, 0x87, 0x97, 0xe1, 0x15, 0x38, 0x00, 0xaf, 0x52, 0xde, 0xdf, 0xa1, 0xd6, 0xc2, 0x18, 0x6b, 0xef, 0xcd, 0xf2, 0x0d, 0xef, 0xfb, 0xe5, 0x2b, 0xde, 0x5b, 0xe4, 0x47, 0xde, 0x0f, 0xc8, 0x83, 0xde, 0xe9, 0x6c, 0x1f, 0x64, 0xbb, 0x4b, 0xbf, 0x23, 0xb5, 0xdf, 0xe7, 0x33, 0xf9, 0xbc, 0x6e, 0x85, 0x47, 0xd8, 0x3f, 0x2a, 0x9f, 0xc6, 0x12, 0x9f, 0xf6, 0xf9, 0x91, 0x63, 0xc7, 0xe0, 0x94, 0xcc, 0xf3, 0x39, 0x23, 0x27, 0xfb, 0x14, 0xc8, 0x29, 0x44, 0x1e, 0xf1, 0x44, 0x1e, 0x51, + 0xbe, 0x1d, 0xe4, 0x53, 0xbe, 0x55, 0x72, 0x32, 0xd1, 0xc7, 0x06, 0xa2, 0x8f, 0x0d, 0x44, 0x1f, 0x1b, 0x88, 0x3e, 0x36, 0x10, 0x7d, 0x6c, 0x20, 0xfa, 0xd8, 0x40, 0xf4, 0xb1, 0x81, 0xe8, 0x63, 0x03, 0xd1, 0xc7, 0x06, 0xa2, 0x8f, 0x0d, 0x44, 0x1f, 0x1b, 0x88, 0x3e, 0x36, 0x10, 0x7d, 0x6c, 0x20, 0xfa, 0xd8, 0x40, 0xf4, 0xb1, 0x81, 0xe8, 0x63, 0x03, 0xd1, 0xc7, 0x06, 0xa2, 0x8f, 0x0d, 0x44, 0x1f, 0x1b, 0x88, 0x3e, 0x36, 0x10, 0x7d, 0x6c, 0x20, 0xfa, 0xd8, 0x40, 0xf4, 0xb1, 0x81, 0xe8, 0x63, 0x03, 0xd1, 0xc7, 0x06, 0xa2, 0x8f, 0x0d, 0x44, 0x1f, 0x1b, 0x88, 0x3e, 0x36, 0x98, 0x97, 0xcb, 0x3c, 0x73, 0x3c, 0x24, 0x40, 0x22, 0xa4, 0xc0, 0x3e, 0x78, 0x09, 0xf6, 0xc3, 0xcb, 0xf0, 0x0a, 0x1c, 0x80, 0x57, 0x65, 0x9e, 0xa5, 0x3e, 0x60, 0x3f, 0x96, 0x06, 0xd0, 0x10, 0x02, 0xa0, + 0x11, 0x04, 0x42, 0x10, 0x34, 0x83, 0xe6, 0xd0, 0x02, 0xb0, 0x31, 0x0b, 0x36, 0x66, 0xc1, 0xc6, 0x2c, 0x21, 0xd0, 0x0a, 0xb0, 0x35, 0x4b, 0x6b, 0x68, 0x03, 0x6d, 0xa1, 0x1d, 0xb4, 0x87, 0x0e, 0xd0, 0x11, 0x3a, 0x41, 0x67, 0xe8, 0x02, 0x7d, 0x41, 0xf3, 0x5e, 0x4f, 0xca, 0x71, 0xbf, 0xeb, 0xc1, 0xf6, 0x70, 0x7c, 0x2f, 0xbc, 0x00, 0x2f, 0xc2, 0x5f, 0xf1, 0x68, 0xff, 0xe0, 0xfc, 0x37, 0xe1, 0x2d, 0x78, 0x1b, 0xde, 0x87, 0x0f, 0xe0, 0x43, 0xf8, 0x08, 0x3e, 0x86, 0xcf, 0xe4, 0xe4, 0xeb, 0x9e, 0xef, 0x0b, 0xf6, 0xbf, 0x81, 0x6f, 0x29, 0xf7, 0x10, 0x1c, 0x86, 0xef, 0xe0, 0x28, 0x68, 0x5e, 0xf1, 0x47, 0xde, 0xab, 0xeb, 0x19, 0x33, 0x78, 0x7d, 0x1c, 0x32, 0x21, 0x07, 0x4e, 0xc0, 0x1f, 0x79, 0xcb, 0x5f, 0x79, 0xef, 0x1c, 0x38, 0x20, 0x1f, 0x0a, 0x00, 0xed, 0x5b, 0x9c, 0x50, + 0x0c, 0xe7, 0xe5, 0xf3, 0x37, 0x79, 0xd4, 0x32, 0xb9, 0xdf, 0x52, 0x0e, 0x97, 0xa0, 0x02, 0x2a, 0xe1, 0x32, 0xe7, 0x5c, 0x81, 0xab, 0x50, 0x05, 0xd5, 0xe0, 0x02, 0x37, 0x5c, 0x83, 0x1a, 0xf9, 0xf4, 0x75, 0x2f, 0xac, 0xc8, 0x99, 0x44, 0x4d, 0x33, 0x75, 0x6f, 0xec, 0xcd, 0xd6, 0x07, 0x7c, 0xe5, 0x34, 0xdd, 0x33, 0x5b, 0xd8, 0xaa, 0xa0, 0x79, 0x68, 0x7f, 0xb6, 0x0d, 0xe0, 0xcf, 0x78, 0xea, 0x51, 0x32, 0x8a, 0x08, 0x27, 0x8a, 0x08, 0x27, 0x8a, 0x08, 0x27, 0x8a, 0x08, 0x27, 0x8a, 0x08, 0x27, 0x8a, 0x08, 0x27, 0x8a, 0x08, 0x27, 0x8a, 0x08, 0x27, 0x8a, 0x08, 0x27, 0x8a, 0x08, 0x27, 0x8a, 0x08, 0x27, 0x8a, 0x08, 0x27, 0x4a, 0xf7, 0xee, 0xb7, 0xcb, 0xc9, 0xba, 0x87, 0x9f, 0xc3, 0x56, 0xf3, 0xf2, 0xf3, 0xd8, 0xde, 0x01, 0xf3, 0xe1, 0x4e, 0x58, 0x0c, 0x7f, 0xe4, 0xf9, 0x97, + 0xf2, 0x5e, 0x2c, 0xc4, 0xc1, 0x32, 0x58, 0x0e, 0xf1, 0x90, 0x00, 0x89, 0x70, 0x17, 0xac, 0x81, 0xb5, 0x80, 0x6f, 0x20, 0x9a, 0x8a, 0x22, 0x9a, 0x8a, 0xd2, 0x57, 0x8c, 0xcd, 0x6c, 0xef, 0x83, 0xfb, 0x61, 0x0b, 0x68, 0x2b, 0xc8, 0x83, 0x6c, 0xb7, 0xc2, 0x43, 0x72, 0xe1, 0xf5, 0xd5, 0x64, 0x3b, 0xfb, 0xf8, 0x0d, 0xf5, 0x31, 0x78, 0x1c, 0x9e, 0x80, 0x1d, 0xb0, 0x4b, 0x3e, 0xa5, 0xee, 0x86, 0x27, 0xe1, 0x29, 0x78, 0x1a, 0x9e, 0x81, 0x67, 0xe1, 0x39, 0xd8, 0x03, 0x7b, 0xe1, 0x79, 0x78, 0x01, 0x5e, 0x84, 0x7d, 0xf0, 0x12, 0xec, 0x87, 0x97, 0xe1, 0x15, 0x38, 0x00, 0xf8, 0x17, 0x15, 0xff, 0xa2, 0xbe, 0x06, 0xaf, 0xc3, 0x1b, 0xf0, 0x0f, 0x78, 0x13, 0xde, 0x82, 0xb7, 0xe1, 0x1d, 0x78, 0x17, 0xde, 0x83, 0xf7, 0xe1, 0x03, 0xf8, 0x10, 0x3e, 0x82, 0x8f, 0xe1, 0x9f, 0xf0, 0x09, + 0x7c, 0x0a, 0xff, 0xa2, 0xed, 0xff, 0x86, 0xcf, 0xe0, 0x73, 0x38, 0x08, 0x5f, 0xc0, 0x97, 0xf0, 0x15, 0x7c, 0x03, 0xdf, 0xc2, 0x21, 0x38, 0x0c, 0xdf, 0xc1, 0xf7, 0x70, 0x04, 0x8e, 0xc2, 0x0f, 0xf0, 0x23, 0x1c, 0x83, 0x9f, 0x20, 0x03, 0x8e, 0x43, 0xa6, 0x5c, 0xa7, 0x66, 0x41, 0xb6, 0xdc, 0xa8, 0xe6, 0xc0, 0x09, 0x38, 0x09, 0xa7, 0x20, 0x17, 0xf2, 0xe0, 0x8c, 0x4c, 0x22, 0x62, 0x4c, 0x52, 0xcf, 0xc9, 0xb5, 0xaa, 0x03, 0xf2, 0xa1, 0x00, 0x9c, 0x50, 0x0c, 0xe7, 0xe1, 0x02, 0x5c, 0x84, 0x12, 0xb8, 0xc4, 0xb8, 0xb3, 0x62, 0x13, 0x59, 0xce, 0x25, 0xb2, 0x9c, 0x4b, 0x64, 0x39, 0x97, 0xc8, 0x72, 0x2e, 0x91, 0x65, 0x94, 0xf5, 0x94, 0xdc, 0xcb, 0x4a, 0x9e, 0x63, 0x75, 0xca, 0x3c, 0x6b, 0x31, 0x9c, 0x87, 0x0b, 0x70, 0x11, 0x2a, 0xe4, 0x64, 0x6d, 0x85, 0xb7, 0x5e, 0x66, 0x7b, + 0x05, 0xae, 0x42, 0x95, 0x8c, 0x27, 0x0a, 0x8d, 0x27, 0x0a, 0x8d, 0x27, 0x0a, 0x8d, 0x27, 0x0a, 0x8d, 0xf7, 0xc3, 0x46, 0x45, 0x80, 0x11, 0xa5, 0x69, 0xf9, 0xfa, 0x19, 0x56, 0x47, 0x27, 0xab, 0xdc, 0x19, 0x56, 0xa7, 0x0c, 0x56, 0xa1, 0x7c, 0xe3, 0x7e, 0xd2, 0x49, 0x3c, 0x7b, 0x96, 0x11, 0xb1, 0x69, 0xf7, 0x93, 0xce, 0xa0, 0xf8, 0x33, 0x37, 0xdd, 0x4f, 0xd2, 0xee, 0xc1, 0x7c, 0x48, 0x49, 0xd5, 0x94, 0x74, 0x37, 0x6b, 0x6d, 0x00, 0xa5, 0x3d, 0x41, 0x49, 0x1f, 0x50, 0xd2, 0x72, 0xd6, 0xb9, 0x33, 0xac, 0x73, 0xa7, 0x29, 0xf1, 0x1e, 0x65, 0xbc, 0x50, 0x95, 0xe9, 0xa2, 0x81, 0x32, 0x53, 0x04, 0xea, 0x7f, 0x27, 0xbd, 0x93, 0x4c, 0x77, 0xb1, 0xfc, 0x8e, 0xf5, 0xcd, 0xc9, 0xfa, 0xe6, 0x64, 0x7d, 0x6b, 0xc3, 0x9a, 0xf6, 0x6f, 0xd6, 0xb4, 0x4f, 0x59, 0xd3, 0xbe, 0x23, 0x22, + 0x2d, 0xa0, 0x15, 0x3f, 0xd1, 0x8a, 0x74, 0x5a, 0x91, 0xa8, 0xdd, 0xd5, 0x62, 0x1d, 0x71, 0xea, 0x2d, 0xd9, 0x2d, 0x1f, 0xc3, 0xe7, 0x6d, 0xc0, 0xdf, 0xdd, 0x6b, 0x79, 0x49, 0xdc, 0x86, 0x3f, 0xdb, 0x87, 0x3f, 0xdb, 0x87, 0x3f, 0xdb, 0x87, 0x3f, 0xdb, 0x87, 0x3f, 0xbb, 0x17, 0x7f, 0xf6, 0x1c, 0x3e, 0x6a, 0x13, 0x7e, 0x28, 0x19, 0xff, 0xb3, 0x1e, 0xff, 0x52, 0x8d, 0x9d, 0xdf, 0x85, 0x7d, 0xef, 0xc4, 0x8e, 0xd7, 0x63, 0xbf, 0xb3, 0xe8, 0xcd, 0x31, 0xe3, 0xee, 0x83, 0x0d, 0xfb, 0xed, 0x85, 0xfd, 0xf6, 0xc2, 0x7e, 0x47, 0xaa, 0xed, 0x45, 0x03, 0x6c, 0x38, 0x8e, 0x1e, 0xb6, 0xd7, 0xef, 0x71, 0x2d, 0x91, 0x69, 0xd8, 0x47, 0x2e, 0xf6, 0xf0, 0x1a, 0x36, 0xb0, 0x11, 0x1b, 0x38, 0xc8, 0xfc, 0x7c, 0x29, 0x06, 0xd0, 0xfb, 0x22, 0xe2, 0xea, 0x72, 0xe2, 0xea, 0x72, 0x46, 0xa1, + 0x9c, 0x51, 0x48, 0x67, 0x14, 0xfc, 0x18, 0x85, 0xd7, 0x8c, 0xb8, 0xf5, 0xdf, 0x8c, 0xc4, 0x0e, 0x46, 0xe2, 0x02, 0x23, 0xa1, 0xdd, 0xab, 0xd3, 0x56, 0xf6, 0xd3, 0xf4, 0x5c, 0xeb, 0x71, 0x43, 0xa5, 0x44, 0x5e, 0xa5, 0xd7, 0x99, 0xf4, 0xfa, 0x38, 0xbd, 0x3e, 0xa3, 0xdf, 0x2f, 0xb1, 0x33, 0xf6, 0x4b, 0xf5, 0xa8, 0xd9, 0x41, 0xef, 0x5f, 0xa5, 0xf7, 0x3b, 0x88, 0xc1, 0xb5, 0x39, 0xb8, 0x44, 0x0c, 0x5e, 0x4e, 0x0c, 0x5e, 0x4e, 0x0c, 0x5e, 0x4e, 0x0c, 0x5e, 0x4e, 0x0c, 0x5e, 0x4e, 0x0c, 0x5e, 0x4e, 0x0c, 0x5e, 0x6e, 0x8c, 0xc4, 0x7e, 0x46, 0x62, 0x3f, 0x23, 0xb1, 0x9f, 0x91, 0xd8, 0xcf, 0x28, 0xec, 0xa1, 0xe7, 0x57, 0xe9, 0xf5, 0xdf, 0xe8, 0xf1, 0x08, 0x7a, 0x9c, 0x41, 0x8f, 0x83, 0xe9, 0x71, 0x10, 0x3d, 0x6e, 0x47, 0x8f, 0x5b, 0xd2, 0xe3, 0xde, 0xf4, 0x76, 0x2a, 0xbd, + 0xb5, 0xd1, 0xdb, 0x32, 0x7a, 0x7a, 0x52, 0xef, 0xe9, 0x36, 0xf9, 0x19, 0x59, 0x43, 0xb8, 0x7c, 0x8f, 0xde, 0x14, 0xd3, 0x9b, 0x72, 0x62, 0xa5, 0x83, 0xf4, 0xe8, 0x02, 0x73, 0x96, 0xcb, 0x9c, 0xe5, 0xd2, 0x83, 0x46, 0xb4, 0x5a, 0xbb, 0x4f, 0x76, 0x41, 0x6f, 0xf5, 0x16, 0x11, 0x42, 0xab, 0x9f, 0xa6, 0xd5, 0x95, 0x9a, 0x72, 0x68, 0xf1, 0x21, 0x5a, 0x15, 0x4e, 0xcd, 0xa3, 0xa8, 0xf9, 0x02, 0x35, 0x06, 0x51, 0x63, 0x20, 0x35, 0xf6, 0xa5, 0xc6, 0xe9, 0xd4, 0x76, 0x15, 0x3f, 0x74, 0x50, 0xcb, 0x96, 0xc4, 0x34, 0xc6, 0xf2, 0x4d, 0xc6, 0xb0, 0x8a, 0x1a, 0x17, 0x13, 0xcd, 0xfd, 0x8d, 0x5a, 0x6f, 0x17, 0xcb, 0x64, 0x05, 0x35, 0x3e, 0xcb, 0x18, 0x2e, 0x62, 0x0c, 0xdf, 0x63, 0x0c, 0xdf, 0x61, 0x0c, 0x97, 0xa3, 0x26, 0xeb, 0x2d, 0x6a, 0xfa, 0x07, 0x63, 0xfa, 0x02, 0x2d, 0xfb, + 0x82, 0x96, 0x7d, 0x61, 0xa8, 0x69, 0x1b, 0xe3, 0xfa, 0x30, 0x2d, 0x7c, 0x8c, 0x16, 0x9e, 0xa2, 0x65, 0x7f, 0xa3, 0x65, 0xe3, 0x69, 0xd9, 0x70, 0x4d, 0xd3, 0xda, 0x3d, 0x0f, 0x14, 0xf5, 0x15, 0xad, 0x3c, 0x4d, 0x34, 0xb2, 0x8b, 0x68, 0x64, 0x17, 0xca, 0x4a, 0x65, 0x45, 0xed, 0x8d, 0xba, 0xe6, 0xb2, 0xa2, 0x8e, 0x42, 0x61, 0x51, 0xac, 0xa2, 0x3d, 0xe8, 0x85, 0x0f, 0x63, 0xbb, 0x85, 0xb1, 0xdd, 0xc2, 0xd8, 0x6e, 0x61, 0x6c, 0xb7, 0xa0, 0xb2, 0x28, 0xc6, 0xf7, 0x5e, 0x54, 0x66, 0x47, 0x65, 0x33, 0x50, 0xd9, 0x62, 0x56, 0xb9, 0xfe, 0xac, 0x70, 0x23, 0x18, 0xf3, 0x53, 0xac, 0x2e, 0xbb, 0x50, 0x5c, 0x34, 0x63, 0x7f, 0x2f, 0xab, 0xc7, 0x08, 0x54, 0xb7, 0xd8, 0x50, 0xdd, 0x07, 0x8c, 0x7f, 0x20, 0xa3, 0xd1, 0x91, 0xd1, 0x68, 0xcf, 0x68, 0x4c, 0x34, 0x14, 0xb7, 0x9c, 0x15, + 0x22, 0x81, 0x15, 0x60, 0x22, 0x5e, 0x3f, 0x14, 0x8f, 0x1f, 0xce, 0x08, 0x1d, 0x47, 0x7d, 0x73, 0x99, 0x93, 0x23, 0xcc, 0xc9, 0x4e, 0xd4, 0x37, 0x9c, 0x79, 0x79, 0x01, 0x6f, 0xb6, 0x04, 0x4f, 0x95, 0x8a, 0x0a, 0x5f, 0x67, 0xf4, 0xbe, 0xc6, 0xea, 0x27, 0x8a, 0xe6, 0x86, 0x45, 0x1f, 0x47, 0x81, 0xbe, 0x8c, 0x9e, 0x83, 0x39, 0x73, 0x6b, 0x77, 0xe1, 0x18, 0x95, 0x52, 0x46, 0x45, 0xcb, 0xf0, 0xae, 0x32, 0x0a, 0xdf, 0x33, 0x0a, 0x6e, 0x46, 0xa1, 0x80, 0x9e, 0x7f, 0x4f, 0xcf, 0x5a, 0xd3, 0xda, 0x0b, 0xb4, 0xac, 0x23, 0x2d, 0x2b, 0x47, 0x1d, 0x7e, 0x86, 0x3a, 0x5a, 0xd1, 0xba, 0x7a, 0xb4, 0xae, 0x29, 0x2d, 0xeb, 0x8b, 0x3a, 0x1a, 0xd2, 0x9a, 0x73, 0x22, 0x48, 0x6c, 0x93, 0x65, 0x62, 0x3b, 0xfa, 0x0d, 0x93, 0xc7, 0x94, 0x7e, 0xf2, 0x07, 0x65, 0xbc, 0x7c, 0x8d, 0x6c, 0xf5, + 0x14, 0x99, 0xde, 0x57, 0x64, 0xab, 0x47, 0x94, 0xd9, 0xe8, 0x39, 0x46, 0xfe, 0xa8, 0x68, 0x9f, 0x13, 0x28, 0x45, 0xf3, 0x65, 0x50, 0x2e, 0x2f, 0x52, 0x73, 0xb5, 0x4f, 0x5f, 0x79, 0xd4, 0x27, 0x0c, 0xbe, 0x94, 0xd5, 0xd6, 0x99, 0xb2, 0xc4, 0x6f, 0x94, 0x7c, 0x52, 0x34, 0x16, 0x4b, 0xc4, 0xc8, 0xff, 0x50, 0xaa, 0x83, 0x52, 0x1d, 0x7f, 0xb9, 0x54, 0xed, 0x53, 0x0f, 0x45, 0x94, 0x56, 0x4c, 0x69, 0x4e, 0x4a, 0xfb, 0x82, 0xd2, 0x72, 0x29, 0xed, 0x6b, 0xa3, 0x8d, 0x15, 0x94, 0x76, 0x8a, 0x92, 0xae, 0x52, 0xd2, 0x55, 0x4a, 0x38, 0x43, 0x09, 0x67, 0xb8, 0xf2, 0x2d, 0xe1, 0xc7, 0xd1, 0x72, 0x8e, 0x96, 0x6b, 0xe5, 0xfb, 0x7c, 0x21, 0x9a, 0x31, 0x46, 0x41, 0x8c, 0x4b, 0x63, 0xc6, 0x21, 0x50, 0xed, 0x24, 0x02, 0xd4, 0xee, 0x62, 0x19, 0xe3, 0xd1, 0x42, 0xdd, 0x2a, 0x02, 0xc5, + 0xc7, 0xc2, 0x84, 0x95, 0x84, 0x8a, 0xbb, 0xc5, 0x10, 0x32, 0x8d, 0xa1, 0x30, 0x0c, 0x22, 0x60, 0x38, 0x8c, 0x80, 0x91, 0x30, 0x0a, 0x46, 0x8b, 0x66, 0x62, 0x0c, 0x8c, 0x85, 0x71, 0x30, 0x1e, 0x26, 0x70, 0x7c, 0x22, 0xdb, 0x49, 0x6c, 0x27, 0xc3, 0x14, 0x98, 0x0a, 0xd3, 0x00, 0x7d, 0x8b, 0x19, 0x30, 0x13, 0x66, 0xc1, 0xed, 0x30, 0x1b, 0xe6, 0xc0, 0x5c, 0x98, 0x07, 0x77, 0xc0, 0x7c, 0x58, 0x00, 0x77, 0xd2, 0x86, 0xc5, 0x42, 0xf3, 0xc2, 0x77, 0x0b, 0x72, 0x18, 0x11, 0x0d, 0x31, 0xb0, 0x04, 0x96, 0x42, 0x2c, 0xc7, 0x97, 0xc1, 0x72, 0x48, 0x85, 0xb5, 0x70, 0x37, 0xd7, 0xa5, 0x8b, 0x8e, 0xe2, 0x41, 0xd1, 0x46, 0x6c, 0x85, 0x87, 0x44, 0x67, 0xb1, 0x4d, 0xac, 0x16, 0xdb, 0xc5, 0x52, 0xa5, 0xaf, 0x88, 0x51, 0xc2, 0xc4, 0x7a, 0xa5, 0x9f, 0x78, 0x5c, 0x19, 0x2c, 0x92, 0x94, + 0x21, 0x22, 0x5d, 0x19, 0xca, 0x76, 0x18, 0xdb, 0xb1, 0x22, 0x0a, 0x1b, 0x64, 0x86, 0xc4, 0x3e, 0x6c, 0xf0, 0x45, 0x6c, 0x70, 0xaf, 0x32, 0x5b, 0xdc, 0xab, 0x2c, 0x14, 0x0f, 0x29, 0x8b, 0x20, 0x46, 0x7c, 0xa5, 0xd8, 0xc5, 0xa2, 0xba, 0x23, 0x69, 0x4a, 0x16, 0x77, 0x9b, 0x56, 0xc0, 0x1a, 0xd8, 0x21, 0x3a, 0x9b, 0x9e, 0x64, 0xfb, 0x14, 0xec, 0x15, 0xcd, 0x4c, 0xcf, 0xc3, 0x0b, 0xa2, 0x81, 0xe9, 0x45, 0xb6, 0xfb, 0xe0, 0x25, 0xf6, 0xf7, 0xc3, 0xcb, 0xf0, 0x0a, 0x1c, 0x10, 0x0d, 0xbc, 0xd3, 0xc4, 0xdd, 0x3e, 0x41, 0xd0, 0x57, 0x3c, 0xe4, 0x13, 0x06, 0xff, 0x16, 0xcd, 0x7c, 0x3e, 0x13, 0x0d, 0xb4, 0x19, 0xf2, 0x39, 0x02, 0x47, 0xd9, 0xff, 0x01, 0x7e, 0x84, 0x63, 0x50, 0x21, 0x62, 0x7c, 0xdb, 0x89, 0xbb, 0xb5, 0xd9, 0xb3, 0x7c, 0xc2, 0xf6, 0x53, 0xf8, 0x17, 0x9c, 0x17, + 0x0d, 0x2c, 0x17, 0xa1, 0x04, 0x4a, 0xa1, 0x8c, 0x19, 0x2e, 0x87, 0x4b, 0x50, 0x01, 0x95, 0x70, 0x99, 0xe3, 0x57, 0xe0, 0x2a, 0x54, 0x41, 0x35, 0xb8, 0xc0, 0x0d, 0xd7, 0xa0, 0x06, 0x7b, 0x6d, 0x28, 0xee, 0x56, 0x03, 0xa0, 0x11, 0x04, 0x02, 0x6d, 0x53, 0x1b, 0x43, 0x53, 0xb1, 0x4a, 0x6d, 0x0e, 0x2d, 0x20, 0x18, 0x5a, 0x82, 0xa6, 0x9e, 0x56, 0x6c, 0x6d, 0xd0, 0x1a, 0xda, 0x40, 0x7b, 0xa8, 0x55, 0x54, 0x37, 0xf6, 0x35, 0x55, 0xf5, 0x64, 0xab, 0x29, 0xab, 0x0f, 0xe5, 0xf4, 0x85, 0x30, 0xe8, 0x07, 0xfd, 0x21, 0x1c, 0x06, 0xc1, 0x60, 0x18, 0x02, 0x43, 0x61, 0x18, 0x44, 0xc0, 0x70, 0x18, 0x01, 0x28, 0x52, 0xcd, 0xc4, 0x92, 0xb3, 0x20, 0x5b, 0xb4, 0x51, 0x73, 0xe0, 0x04, 0x9c, 0x84, 0x53, 0x90, 0x0b, 0x79, 0x70, 0x06, 0x7e, 0x85, 0x73, 0xa2, 0xb3, 0xea, 0x80, 0x7c, 0x28, + 0x00, 0x27, 0x14, 0xc3, 0x79, 0xb8, 0x00, 0x17, 0xa1, 0x04, 0x2a, 0xd0, 0x4f, 0x20, 0xaa, 0xa8, 0x8f, 0x22, 0x54, 0xd4, 0x60, 0x46, 0x09, 0x66, 0x66, 0xbc, 0x19, 0x33, 0x1d, 0x7c, 0x93, 0xbd, 0xf4, 0x15, 0xf5, 0x98, 0x9d, 0x7a, 0xff, 0xd5, 0x6e, 0x62, 0xc5, 0x10, 0x7c, 0xfd, 0x50, 0x18, 0x06, 0x11, 0x30, 0x1c, 0x46, 0xc0, 0x48, 0x18, 0x05, 0xa3, 0x61, 0x0c, 0x8c, 0x05, 0xd6, 0x45, 0x31, 0x1e, 0x26, 0xc0, 0x44, 0x98, 0x04, 0x93, 0x61, 0x0a, 0x4c, 0x85, 0x69, 0x30, 0x1d, 0x66, 0xc0, 0x4c, 0x98, 0x05, 0xb7, 0xc3, 0x6c, 0x98, 0x03, 0x73, 0x61, 0x1e, 0xdc, 0x01, 0xf3, 0x61, 0x01, 0xdc, 0x0d, 0xe9, 0xf2, 0x32, 0xd9, 0xf9, 0x59, 0xb2, 0xf3, 0x4a, 0xb2, 0xf3, 0x0a, 0xfc, 0xc5, 0x25, 0xfd, 0x2f, 0x45, 0xda, 0x1d, 0xb8, 0xf1, 0x78, 0x9c, 0xe9, 0x30, 0x53, 0x1e, 0xc5, 0x67, + 0x64, 0xe0, 0x2f, 0x2e, 0x93, 0x41, 0x57, 0x90, 0x35, 0x57, 0x90, 0x2d, 0x57, 0x92, 0x2d, 0x57, 0x90, 0x2d, 0x57, 0x91, 0x2d, 0x57, 0x91, 0x2d, 0x57, 0x91, 0x2d, 0x57, 0x91, 0x2d, 0x57, 0x91, 0x2d, 0x57, 0x91, 0x2d, 0x57, 0x91, 0x2d, 0x57, 0x91, 0x2d, 0x57, 0x91, 0x2d, 0x57, 0x91, 0xfd, 0x56, 0x91, 0xfd, 0x56, 0x91, 0xf9, 0x56, 0xb1, 0xd6, 0x54, 0xb1, 0xd6, 0x54, 0x91, 0xf9, 0x56, 0x91, 0xf9, 0x56, 0x91, 0xc9, 0x54, 0x91, 0xc9, 0x54, 0x91, 0xc9, 0x54, 0x91, 0xc9, 0x54, 0x91, 0xc9, 0x54, 0x91, 0xc9, 0x54, 0x91, 0xc9, 0x54, 0x91, 0xc9, 0x54, 0x91, 0xc9, 0x54, 0x91, 0xc9, 0x54, 0x91, 0xc9, 0x54, 0x91, 0xc9, 0x54, 0x91, 0xc9, 0x54, 0x91, 0xc9, 0x54, 0x91, 0xc9, 0x54, 0x91, 0xc9, 0x54, 0x91, 0xc9, 0x54, 0xb1, 0xd6, 0x54, 0x11, 0x89, 0x5e, 0x26, 0x12, 0xbd, 0x4c, 0xb4, + 0x79, 0x96, 0x68, 0xf3, 0x2c, 0xd1, 0x66, 0x25, 0xd1, 0x66, 0x25, 0xd1, 0x66, 0x25, 0xd1, 0x66, 0x25, 0xd1, 0x66, 0x25, 0xd1, 0x66, 0x25, 0xd1, 0x66, 0x25, 0xd1, 0x66, 0x25, 0xd1, 0x66, 0x25, 0xd1, 0x66, 0x25, 0xd1, 0x66, 0x05, 0xeb, 0x48, 0x05, 0xd1, 0x66, 0x05, 0xd1, 0x66, 0x05, 0xd1, 0x66, 0x05, 0xd1, 0x66, 0x85, 0xfe, 0xc9, 0xa9, 0x13, 0x78, 0xd1, 0x2f, 0x59, 0x7d, 0x7f, 0x64, 0x44, 0x3e, 0xc7, 0xda, 0x2d, 0x58, 0x7b, 0x7d, 0xac, 0x5d, 0x65, 0xde, 0x2d, 0xcc, 0x7b, 0x21, 0xf3, 0x5e, 0xc8, 0xbc, 0x6b, 0x7f, 0xdd, 0x71, 0x33, 0xe7, 0x73, 0x99, 0xf3, 0x08, 0xcb, 0x07, 0xa2, 0x91, 0x25, 0x97, 0xf5, 0x22, 0x58, 0x0c, 0x61, 0xfe, 0x55, 0x94, 0x1f, 0x8a, 0xf2, 0x07, 0xa1, 0xfc, 0xce, 0x28, 0x3f, 0x0c, 0x3d, 0xcc, 0x44, 0x0f, 0x93, 0x51, 0x7d, 0x07, 0x34, 0x71, 0x08, + 0x4d, 0xf8, 0xab, 0x51, 0xc2, 0x82, 0x2e, 0xa6, 0x5a, 0x7f, 0x11, 0xad, 0xf4, 0x92, 0xaf, 0x2b, 0x8a, 0x12, 0xac, 0x5c, 0xad, 0x70, 0x45, 0x2b, 0xce, 0x9e, 0xc4, 0xd9, 0x0d, 0x88, 0xbc, 0xb4, 0xef, 0x26, 0xde, 0x09, 0x8b, 0xf5, 0x6f, 0x3a, 0xac, 0xd5, 0xbf, 0x03, 0xa2, 0x7d, 0x2e, 0xdd, 0xf3, 0x8d, 0x29, 0x1b, 0x47, 0xac, 0xb7, 0x78, 0x1f, 0x1b, 0xde, 0xc7, 0x86, 0xf7, 0xb1, 0xe1, 0x79, 0x6c, 0x78, 0x1e, 0x1b, 0x1e, 0xc5, 0x86, 0x47, 0xb1, 0xe1, 0x25, 0x6c, 0x96, 0xa7, 0x44, 0x43, 0xbc, 0x84, 0x0d, 0x2f, 0x61, 0xc3, 0x4b, 0xd8, 0x2c, 0x92, 0xf6, 0x34, 0x24, 0x22, 0x0a, 0x80, 0x46, 0x10, 0x08, 0x9c, 0x8b, 0x85, 0xdb, 0xb0, 0xe8, 0x40, 0x5a, 0xf5, 0x1e, 0xfd, 0xda, 0x4a, 0xcb, 0x62, 0xe8, 0xd7, 0x32, 0xfa, 0xd5, 0xa3, 0x4e, 0x0b, 0x3f, 0xc2, 0x82, 0x6d, 0x58, 0xb0, + 0x0d, 0x0b, 0xb6, 0x61, 0xc1, 0x36, 0x2c, 0xd8, 0x86, 0x05, 0xdb, 0xb0, 0x60, 0x1b, 0x16, 0x6c, 0xc3, 0x82, 0x6d, 0x58, 0xb0, 0x0d, 0x0b, 0xb6, 0x61, 0xc1, 0x36, 0x2c, 0xd8, 0x86, 0x05, 0xdb, 0xd4, 0x87, 0x45, 0x77, 0xc6, 0x20, 0x82, 0xd5, 0x22, 0x54, 0x98, 0xe9, 0xa1, 0x99, 0x1e, 0x9a, 0xe9, 0x21, 0x1e, 0x57, 0xd4, 0x17, 0xda, 0x63, 0x77, 0x62, 0xe8, 0xe5, 0x12, 0xb6, 0xda, 0x93, 0xaf, 0x62, 0x79, 0xcf, 0xd3, 0x6b, 0x33, 0xbd, 0x36, 0xd3, 0x6b, 0xe5, 0x96, 0x5e, 0x9b, 0xe9, 0xb5, 0x99, 0x5e, 0x9b, 0xe9, 0xb5, 0x99, 0x5e, 0x9b, 0xe9, 0xb5, 0x99, 0x5e, 0x9b, 0x99, 0xad, 0xfa, 0xf4, 0xdc, 0x4c, 0xaf, 0xcd, 0xf4, 0xda, 0x4c, 0xaf, 0xcd, 0xf4, 0xd8, 0x4c, 0x8f, 0xcd, 0xf4, 0xd8, 0x4c, 0x8f, 0xcd, 0xf4, 0xd8, 0x4c, 0x8f, 0xcd, 0xf8, 0xb4, 0xfa, 0xf8, 0xb4, 0xfa, 0xf8, + 0xb4, 0xfa, 0xcc, 0xea, 0x0a, 0x46, 0x40, 0x61, 0x04, 0xfa, 0x32, 0x02, 0x71, 0x8c, 0x40, 0x02, 0x23, 0xb0, 0x99, 0x11, 0xd8, 0x88, 0x4f, 0xab, 0xcf, 0xec, 0xd6, 0xd7, 0x47, 0xa2, 0x1b, 0x5b, 0x6d, 0x34, 0x7a, 0xb2, 0xed, 0x25, 0x06, 0x30, 0x22, 0x66, 0x46, 0xc4, 0xcc, 0x88, 0x98, 0x19, 0x11, 0x33, 0x23, 0x62, 0x66, 0x44, 0xcc, 0x8c, 0x88, 0x99, 0x11, 0x31, 0x33, 0x22, 0x66, 0x46, 0xc4, 0xcc, 0x88, 0x98, 0x19, 0x11, 0x33, 0x23, 0x62, 0x66, 0x44, 0xcc, 0xea, 0x26, 0xd1, 0x55, 0xdd, 0x26, 0x02, 0x19, 0x95, 0x29, 0x22, 0x98, 0x91, 0x98, 0xc3, 0x48, 0x04, 0x31, 0x12, 0x41, 0xc6, 0x48, 0x04, 0x69, 0x23, 0x71, 0xcb, 0x9a, 0x1d, 0x44, 0x8b, 0x83, 0x68, 0x71, 0x10, 0x2d, 0x0e, 0xa2, 0xc5, 0xe1, 0x46, 0x8b, 0x17, 0xd3, 0xe2, 0xbb, 0x68, 0x71, 0x22, 0x2d, 0x5e, 0x47, 0x8b, + 0x17, 0xd2, 0xe2, 0x20, 0x5a, 0x1c, 0x64, 0xb4, 0x38, 0xc8, 0x68, 0x71, 0x10, 0x2d, 0xb6, 0x53, 0x63, 0x18, 0x71, 0x44, 0xa4, 0x18, 0x79, 0x93, 0x1e, 0x83, 0x45, 0x3f, 0x4a, 0xb2, 0x53, 0xd2, 0x66, 0x4a, 0x4a, 0xa3, 0xa4, 0x7b, 0x29, 0x29, 0xbe, 0xce, 0xec, 0xc7, 0x71, 0x65, 0x6f, 0xd1, 0x90, 0x2b, 0x87, 0xdc, 0x72, 0xe5, 0x52, 0xae, 0xec, 0xc2, 0x95, 0x33, 0xb8, 0x72, 0x36, 0x57, 0xc6, 0x73, 0x65, 0x42, 0x9d, 0x2b, 0x7b, 0xd2, 0x5b, 0x1b, 0xbd, 0xb5, 0x50, 0xc2, 0xc8, 0xdf, 0xd8, 0x81, 0xe5, 0x16, 0x3b, 0xa8, 0x2f, 0xfa, 0xa3, 0x92, 0x10, 0x54, 0x42, 0xcf, 0x40, 0xfb, 0xce, 0x66, 0x2c, 0x5b, 0x8f, 0x22, 0x42, 0x50, 0x44, 0x08, 0x8a, 0xb0, 0xdc, 0xa2, 0x88, 0x10, 0x14, 0x11, 0x82, 0x22, 0x42, 0x50, 0x44, 0x08, 0x8a, 0x08, 0x41, 0x11, 0x21, 0x28, 0x22, 0x04, 0x35, + 0x84, 0x60, 0x07, 0xfe, 0x28, 0x22, 0x04, 0x45, 0x84, 0xa0, 0x88, 0x10, 0x14, 0x11, 0x82, 0x22, 0x42, 0x50, 0x44, 0x08, 0x8a, 0x08, 0x41, 0x11, 0x21, 0x28, 0x22, 0x84, 0xf1, 0x0c, 0xa0, 0x45, 0xef, 0xd0, 0x97, 0x74, 0x5a, 0x15, 0x45, 0x5f, 0xe2, 0xe8, 0x4b, 0xb7, 0x3a, 0xad, 0xfb, 0x90, 0x19, 0x0f, 0x61, 0xc6, 0x43, 0x98, 0xf1, 0x10, 0x66, 0x3c, 0x84, 0x19, 0x0f, 0x61, 0xc6, 0x43, 0x98, 0xf1, 0x10, 0x66, 0x3c, 0x84, 0x19, 0x0f, 0x61, 0xc6, 0x43, 0x98, 0xf1, 0x10, 0x66, 0x3c, 0x84, 0x19, 0x0f, 0x61, 0xc6, 0x43, 0xb0, 0x81, 0x6e, 0xf4, 0x7f, 0xa8, 0x6e, 0x03, 0x2a, 0xbd, 0x53, 0xe9, 0x9d, 0x6a, 0xd8, 0x80, 0x3f, 0x33, 0xef, 0x6f, 0xcc, 0xbc, 0xbf, 0x61, 0x03, 0xaa, 0xd1, 0x63, 0x95, 0x1e, 0xab, 0xbf, 0x63, 0x03, 0x2a, 0x3d, 0x56, 0xe9, 0xb1, 0x4a, 0x8f, 0x55, 0x7a, + 0xac, 0xd2, 0x63, 0x95, 0x1e, 0xab, 0x28, 0xc5, 0x9f, 0x5e, 0xab, 0xf4, 0x58, 0xa5, 0xc7, 0x2a, 0x3d, 0x56, 0xe9, 0xb1, 0x4a, 0x8f, 0x55, 0x7a, 0xac, 0xd2, 0x63, 0x95, 0x1e, 0xab, 0xf4, 0x58, 0x45, 0x51, 0xfe, 0x28, 0xca, 0x1f, 0x45, 0xf9, 0x33, 0x93, 0xa9, 0x86, 0xa2, 0xc2, 0x18, 0x81, 0x65, 0x86, 0x0d, 0xdc, 0xc7, 0x08, 0x6c, 0x42, 0x51, 0xfe, 0x28, 0xca, 0xdf, 0x50, 0x94, 0xbf, 0xa1, 0x28, 0xed, 0x13, 0xb5, 0xe1, 0x8c, 0x88, 0xca, 0x88, 0xa8, 0x8c, 0x88, 0xca, 0x88, 0xa8, 0x8c, 0x88, 0xca, 0x88, 0xa8, 0x8c, 0x88, 0xca, 0x88, 0xa8, 0x8c, 0x88, 0xca, 0x88, 0xa8, 0x8c, 0x88, 0xca, 0x88, 0xa8, 0x8c, 0x88, 0xca, 0x88, 0xa8, 0xa8, 0xa2, 0x1b, 0xaa, 0x08, 0x62, 0x54, 0xa6, 0xfe, 0x8f, 0x6d, 0x20, 0x92, 0x16, 0xa7, 0x19, 0x36, 0x70, 0x17, 0x2d, 0x9e, 0xfd, 0xa7, 0x6c, + 0x60, 0xd4, 0x2d, 0x4a, 0xee, 0x4f, 0x49, 0xb1, 0x94, 0xf4, 0x00, 0x25, 0xdd, 0x65, 0xd8, 0x7f, 0x52, 0x9d, 0xd9, 0x4f, 0xe0, 0xca, 0xbe, 0xba, 0x0d, 0x0c, 0xbd, 0xe5, 0xca, 0x58, 0xae, 0xec, 0xca, 0x95, 0xb3, 0xb9, 0x72, 0x2e, 0x57, 0x26, 0x71, 0xe5, 0x8a, 0x3a, 0x57, 0x86, 0xd2, 0xdb, 0x36, 0xf4, 0xd6, 0x4a, 0x09, 0xa3, 0x44, 0x3d, 0x65, 0xaa, 0xfc, 0xd7, 0x5f, 0x8a, 0xc8, 0xcd, 0x7a, 0xe4, 0x3f, 0x5e, 0x9e, 0xe3, 0x2a, 0x27, 0x57, 0x15, 0x1b, 0xeb, 0xd3, 0x35, 0xa2, 0xfb, 0x1f, 0x59, 0xb9, 0x6e, 0xe4, 0x04, 0xdf, 0xb2, 0xb6, 0x5f, 0x60, 0x5d, 0x3f, 0x5b, 0xb7, 0x7c, 0xce, 0xfa, 0x8e, 0x32, 0x96, 0x88, 0x0e, 0x37, 0xb5, 0xdb, 0x66, 0x78, 0x35, 0xda, 0x28, 0xea, 0x51, 0xc3, 0xe7, 0x5c, 0x9f, 0xc5, 0xf5, 0x99, 0xc4, 0x06, 0x5a, 0x19, 0x05, 0x9c, 0xed, 0xe6, 0x6c, + 0x37, 0x59, 0x70, 0x1b, 0x6a, 0x3b, 0xaf, 0x3e, 0xab, 0xfb, 0xac, 0x86, 0x94, 0xf7, 0x3a, 0x23, 0x68, 0x92, 0x1f, 0x91, 0xcd, 0x56, 0xfd, 0xe5, 0xde, 0x54, 0x90, 0x5f, 0x74, 0xc4, 0x0e, 0xd2, 0xc9, 0x2f, 0x82, 0xb1, 0x89, 0x60, 0xf2, 0x8b, 0x60, 0xd6, 0x87, 0x60, 0xf2, 0x0b, 0x32, 0x7a, 0x18, 0x09, 0xa3, 0x60, 0x34, 0xe7, 0x8d, 0x81, 0xb1, 0x30, 0x0e, 0xc6, 0xc3, 0x04, 0x8e, 0x4f, 0x64, 0x3b, 0x89, 0xed, 0x64, 0xd0, 0x3c, 0xa8, 0xa6, 0xa0, 0x69, 0x30, 0x1d, 0x66, 0xc0, 0x4c, 0x98, 0x05, 0xb7, 0xc3, 0x6c, 0x98, 0x03, 0x73, 0x61, 0x1e, 0xdc, 0x01, 0xf3, 0x61, 0x01, 0xdc, 0x49, 0x1b, 0x16, 0x0b, 0xed, 0xfe, 0x46, 0x3a, 0xea, 0xdb, 0x8c, 0xfa, 0x36, 0xa3, 0xbe, 0xcd, 0x8c, 0xd4, 0x66, 0xd4, 0xb7, 0x19, 0x3b, 0x4c, 0xc7, 0x0e, 0xd3, 0xb1, 0xc3, 0x74, 0xec, 0x30, 0x1d, + 0x3b, 0x4c, 0x27, 0xbf, 0x08, 0x16, 0xeb, 0xe8, 0xc5, 0x7a, 0xb8, 0x07, 0x36, 0xc0, 0xbd, 0xb0, 0x11, 0x36, 0xc1, 0x66, 0xb8, 0x0f, 0xee, 0x87, 0x74, 0x94, 0xf6, 0xa0, 0xe8, 0x49, 0x1e, 0xd2, 0x93, 0x3c, 0xa4, 0x3f, 0x79, 0xc8, 0x7d, 0x44, 0x9c, 0x69, 0xe4, 0x21, 0x2b, 0x88, 0x3a, 0xb7, 0x92, 0x87, 0xec, 0x21, 0xf2, 0x5c, 0x47, 0x1e, 0xf2, 0x37, 0xa2, 0xcf, 0x75, 0xe4, 0x21, 0x7f, 0x23, 0x0f, 0x49, 0x26, 0x0f, 0x79, 0x4f, 0x1f, 0xd5, 0xe9, 0xe4, 0x23, 0x33, 0xc5, 0x9b, 0x44, 0x27, 0xaf, 0x13, 0x99, 0x6e, 0x27, 0x17, 0xd9, 0x4d, 0x2e, 0xb2, 0x9b, 0x48, 0xe5, 0x08, 0xb9, 0xc8, 0xf2, 0x5b, 0x7c, 0x42, 0x3a, 0x3e, 0x21, 0x1d, 0x9f, 0x90, 0x6e, 0xa2, 0x5d, 0x26, 0xda, 0x45, 0x4e, 0xd2, 0x1f, 0xff, 0x90, 0x8e, 0x7f, 0x48, 0x27, 0x27, 0xe9, 0x48, 0x4e, 0xd2, 0x91, 0x9c, + 0xa4, 0x2d, 0x39, 0x49, 0x47, 0x72, 0x92, 0x8e, 0xe4, 0x24, 0xc1, 0xe4, 0x24, 0xc1, 0xe4, 0x24, 0xc1, 0xe4, 0x24, 0xc1, 0xe4, 0x24, 0xc1, 0xa6, 0x57, 0xb9, 0xee, 0xef, 0x22, 0x10, 0x7f, 0x92, 0x8e, 0x3f, 0x49, 0x27, 0xfa, 0xdd, 0x4d, 0xf4, 0xbb, 0x9b, 0xdc, 0xa4, 0x23, 0xb9, 0x49, 0xb0, 0x91, 0x9b, 0x74, 0x24, 0x37, 0x09, 0x26, 0x37, 0x09, 0x26, 0x37, 0x09, 0x26, 0x37, 0x09, 0x26, 0x37, 0x59, 0x81, 0xdf, 0x49, 0xf7, 0x65, 0xc6, 0x8d, 0xfc, 0x24, 0x1d, 0xff, 0x93, 0x8e, 0xff, 0x49, 0xb7, 0x1c, 0x24, 0x8a, 0x3a, 0x2f, 0x82, 0xc9, 0x51, 0x82, 0xc9, 0x51, 0x82, 0xc9, 0x51, 0x82, 0xc9, 0x51, 0x3a, 0x92, 0xa3, 0x74, 0x24, 0x47, 0xe9, 0x48, 0x8e, 0xd2, 0x91, 0x1c, 0xa5, 0x23, 0x39, 0x4a, 0x30, 0x39, 0x4a, 0x30, 0x39, 0x4a, 0x30, 0x39, 0x4a, 0x30, 0x39, 0x4a, 0x30, 0x39, + 0x4a, 0x30, 0x39, 0x4a, 0x30, 0x39, 0x4a, 0x30, 0x39, 0x4a, 0x30, 0xbe, 0x2c, 0x1d, 0x5f, 0x96, 0x8e, 0x2f, 0x4b, 0xc7, 0x97, 0xa5, 0xe3, 0xcb, 0xd2, 0xf1, 0x65, 0xe9, 0x78, 0x86, 0xcd, 0x78, 0x86, 0xcd, 0x78, 0x86, 0xcd, 0x58, 0xe4, 0x66, 0x3c, 0xc3, 0x66, 0x23, 0x47, 0xd1, 0xd6, 0xb4, 0xcd, 0x86, 0x3d, 0x6f, 0xc6, 0x33, 0x6c, 0xae, 0x93, 0xa3, 0x6c, 0x36, 0x72, 0x94, 0xcd, 0x46, 0x8e, 0x92, 0x8e, 0x2f, 0x4b, 0xc7, 0x97, 0xa5, 0xe3, 0xcb, 0xd2, 0xf1, 0x65, 0xe9, 0xf8, 0xb2, 0x74, 0x7c, 0x59, 0x3a, 0xbe, 0x2c, 0x1d, 0x5f, 0x96, 0x8e, 0x2f, 0x4b, 0xc7, 0x97, 0xa5, 0xe3, 0xcb, 0xd2, 0xf1, 0x65, 0xe9, 0xf8, 0xb2, 0x74, 0x75, 0x02, 0x56, 0xae, 0xe5, 0x29, 0xdb, 0xc4, 0x0e, 0x75, 0x17, 0xdb, 0xdd, 0xf0, 0x24, 0x3c, 0x05, 0x4f, 0xc3, 0x33, 0x80, 0xed, 0xa8, 0xcf, 0xc1, + 0x1e, 0xd8, 0x0b, 0xcf, 0xc3, 0x0b, 0xf0, 0x22, 0xec, 0x83, 0x97, 0x60, 0x3f, 0xbc, 0x0c, 0xaf, 0xc0, 0x01, 0x60, 0x2e, 0x54, 0xe6, 0x42, 0x7d, 0x0d, 0x5e, 0x87, 0x37, 0xe0, 0x1f, 0xf0, 0x26, 0xbc, 0x05, 0x6f, 0xc3, 0x3b, 0xf0, 0x2e, 0xbc, 0x07, 0xef, 0xc3, 0x07, 0xf0, 0x21, 0x7c, 0x04, 0x1f, 0xc3, 0x3f, 0xe1, 0x13, 0xf8, 0x54, 0xcf, 0xa3, 0xc2, 0xc8, 0xa3, 0xc2, 0xc8, 0xa3, 0x7a, 0x92, 0x47, 0xf5, 0x24, 0x8f, 0xea, 0x49, 0x1e, 0xd5, 0x93, 0x3c, 0xaa, 0x27, 0x79, 0x54, 0x4f, 0xf2, 0xa8, 0x9e, 0xe4, 0x51, 0x3d, 0xc9, 0xa3, 0x7a, 0x92, 0x47, 0xf5, 0x27, 0x8f, 0xea, 0x4f, 0x1e, 0xd5, 0x9f, 0x3c, 0xaa, 0x3f, 0x79, 0x54, 0x7f, 0xf2, 0xa8, 0xfe, 0xe4, 0x51, 0xfd, 0xc9, 0xa3, 0xfa, 0x93, 0x47, 0xf5, 0x27, 0x8f, 0xea, 0x4f, 0x1e, 0xa5, 0x29, 0x7e, 0xbb, 0x3c, 0x8f, 0x17, + 0xfa, 0xe9, 0x96, 0xfb, 0x1c, 0x5f, 0xd7, 0xb9, 0x7b, 0x72, 0x4c, 0xb1, 0xa3, 0xd4, 0x1b, 0xf7, 0x39, 0xb4, 0x4f, 0xf8, 0x55, 0xf9, 0xf4, 0x95, 0x3f, 0xf8, 0x84, 0xc1, 0x97, 0xb2, 0xca, 0xb8, 0xcf, 0xf1, 0x9c, 0x68, 0x2c, 0xb6, 0xc9, 0x0b, 0x94, 0xf8, 0x0b, 0x25, 0x7e, 0x41, 0x89, 0x5f, 0x52, 0xe2, 0x3e, 0x4a, 0xfc, 0x92, 0x12, 0xff, 0x49, 0x89, 0x5f, 0x50, 0xe2, 0x61, 0x4a, 0x3c, 0x44, 0x89, 0x67, 0x28, 0xd1, 0x41, 0x89, 0x0e, 0x4a, 0x3c, 0x4f, 0x89, 0x97, 0xb4, 0x6f, 0x24, 0x50, 0xea, 0x67, 0x94, 0xfa, 0x99, 0x76, 0x7f, 0x91, 0x52, 0x8b, 0x28, 0xf5, 0x41, 0xd1, 0xdc, 0x28, 0xb5, 0xf2, 0x7a, 0xa9, 0x83, 0x89, 0xf3, 0x87, 0xc2, 0xef, 0x95, 0xbe, 0x90, 0x92, 0x17, 0xc1, 0x7f, 0xae, 0xe5, 0x14, 0xb5, 0x9c, 0xba, 0xa9, 0x16, 0x6d, 0x24, 0xf2, 0xa8, 0xe1, 0x28, 0x35, + 0x1c, 0xa5, 0xe4, 0xd7, 0x29, 0xf9, 0x27, 0x4a, 0x3e, 0x48, 0xc9, 0xdf, 0x50, 0xf2, 0x31, 0x4a, 0x3c, 0x62, 0xdc, 0xf1, 0xd1, 0x3c, 0xb9, 0x53, 0xbf, 0xdb, 0xe8, 0xb9, 0xe3, 0xf3, 0x2d, 0xa5, 0x7d, 0x6b, 0xdc, 0xf1, 0xb9, 0x44, 0x69, 0x3b, 0x44, 0x13, 0xda, 0x7c, 0x8e, 0x12, 0x73, 0x28, 0xf1, 0x20, 0x25, 0x7e, 0x41, 0xce, 0x72, 0x9e, 0x52, 0x0f, 0x50, 0xea, 0xd7, 0x94, 0xfa, 0x29, 0xa5, 0x7e, 0x65, 0x8c, 0xc6, 0xb7, 0x94, 0x7a, 0x9a, 0x52, 0x0b, 0x28, 0xb5, 0xc0, 0xb8, 0x8f, 0xe4, 0xd2, 0x3e, 0xf1, 0x40, 0xc9, 0x9f, 0x52, 0xf2, 0xa7, 0x94, 0xec, 0xa2, 0x64, 0x6d, 0x35, 0xb8, 0x8b, 0x38, 0xcb, 0x24, 0x33, 0xf1, 0xf9, 0xda, 0xbd, 0xdc, 0x7f, 0x50, 0xaa, 0xd6, 0x4e, 0xcd, 0xf7, 0x7f, 0x4f, 0x89, 0x3f, 0x53, 0x4a, 0x3e, 0xa5, 0xe4, 0x1b, 0xab, 0x4c, 0x05, 0x3e, 0xbf, + 0x09, 0x57, 0x39, 0x85, 0x3f, 0xed, 0xc8, 0xa7, 0x1d, 0x0e, 0xda, 0xf0, 0x29, 0x57, 0x64, 0x70, 0xc5, 0x97, 0xc6, 0x1c, 0x6b, 0x9f, 0xe2, 0xca, 0xe1, 0xca, 0x22, 0xa3, 0x7e, 0xed, 0x1b, 0x49, 0x79, 0xd4, 0x9b, 0xc7, 0x95, 0xff, 0x10, 0x6f, 0x90, 0x1b, 0x3b, 0xc9, 0x8d, 0x9d, 0xe4, 0xc6, 0x4e, 0x72, 0x63, 0x27, 0xb9, 0xb1, 0x93, 0xdc, 0xd8, 0x49, 0x6e, 0xec, 0x14, 0x5a, 0xd9, 0xa3, 0x61, 0x0c, 0x8c, 0x85, 0x71, 0x30, 0x1e, 0x26, 0xc0, 0x44, 0x98, 0x04, 0x93, 0x61, 0x0a, 0x4c, 0x85, 0x69, 0x30, 0x1d, 0x66, 0xc0, 0x4c, 0x98, 0x05, 0xb7, 0xc3, 0x6c, 0x98, 0x03, 0x73, 0x61, 0x1e, 0xdc, 0x01, 0xf3, 0x61, 0x01, 0x2c, 0x94, 0x57, 0xc4, 0x2a, 0xf2, 0xe3, 0xd5, 0x70, 0x17, 0xac, 0xd1, 0xf3, 0x65, 0xa7, 0x58, 0x27, 0x8b, 0xc5, 0x7a, 0xb8, 0x07, 0x36, 0xc0, 0xbd, 0xb0, 0x11, + 0x36, 0xc1, 0x66, 0xb8, 0x0f, 0xee, 0x87, 0x74, 0x79, 0x46, 0x3c, 0x28, 0xcf, 0x93, 0x5b, 0x97, 0x90, 0x5b, 0xe7, 0x32, 0x02, 0x4e, 0xe3, 0xf3, 0x6b, 0xa5, 0x26, 0xae, 0x35, 0x71, 0xad, 0x69, 0x8b, 0xbc, 0x62, 0x7a, 0x94, 0xf9, 0xdc, 0x21, 0x73, 0xc9, 0xa3, 0x9d, 0xe4, 0xd1, 0x4e, 0xf2, 0x68, 0x27, 0x79, 0xb4, 0x93, 0x3c, 0xda, 0x49, 0x1e, 0xed, 0x24, 0x8f, 0x76, 0x92, 0x47, 0x3b, 0xc9, 0xa3, 0x9d, 0xe4, 0xd1, 0x4e, 0xd3, 0xab, 0x5c, 0xf7, 0x77, 0x59, 0x4c, 0x3e, 0xed, 0x24, 0x9f, 0x76, 0x92, 0x4f, 0x3b, 0xc9, 0xa7, 0x9d, 0xe4, 0xd3, 0x4e, 0xf2, 0x69, 0x27, 0xf9, 0xb4, 0xd3, 0xb7, 0x83, 0x2c, 0x26, 0xa7, 0x76, 0x92, 0x53, 0x3b, 0xc9, 0xa9, 0x9d, 0xe4, 0xd4, 0x4e, 0x72, 0x6a, 0x27, 0x39, 0xb5, 0x93, 0x9c, 0xda, 0x49, 0x4e, 0xed, 0x24, 0xa7, 0x76, 0x92, 0x53, 0x3b, + 0xc9, 0xa9, 0x9d, 0xe4, 0xd4, 0x4e, 0x72, 0x6a, 0x27, 0x39, 0xb5, 0x93, 0x9c, 0xda, 0x49, 0x4e, 0xed, 0x24, 0xa7, 0x76, 0x92, 0x53, 0x3b, 0x2d, 0x52, 0x5e, 0x51, 0x05, 0x28, 0x60, 0x02, 0x2f, 0xf0, 0x06, 0x1f, 0x78, 0x88, 0x9c, 0xfb, 0x61, 0xd8, 0x06, 0xdb, 0x81, 0xbe, 0xa8, 0x8f, 0xc1, 0xe3, 0xf0, 0x04, 0xec, 0x80, 0x5d, 0xb2, 0x58, 0xdd, 0x0d, 0x4f, 0xc2, 0x53, 0xf0, 0x34, 0x3c, 0x03, 0xcf, 0xc2, 0x73, 0xb0, 0x07, 0xf6, 0xc2, 0xf3, 0xf0, 0x02, 0xbc, 0x08, 0xfb, 0xe0, 0x25, 0xd8, 0x0f, 0x2f, 0xc3, 0x2b, 0x70, 0x00, 0xe8, 0xbb, 0x4a, 0xdf, 0xd5, 0xd7, 0xe0, 0x75, 0x78, 0x03, 0xfe, 0x01, 0x6f, 0xc2, 0x5b, 0xf0, 0x36, 0xbc, 0x03, 0xef, 0xc2, 0x7b, 0xf0, 0x3e, 0x7c, 0x00, 0x1f, 0xc2, 0x47, 0xf0, 0x31, 0xfc, 0x13, 0x3e, 0x81, 0x4f, 0x21, 0x53, 0x9e, 0x51, 0xb3, 0x20, + 0x9b, 0x58, 0x25, 0x07, 0x4e, 0xc0, 0x49, 0x38, 0x05, 0xb9, 0x90, 0x07, 0x67, 0x64, 0x89, 0xfa, 0x2b, 0x9c, 0x93, 0xb9, 0xaa, 0x03, 0xf2, 0xa1, 0x00, 0x9c, 0x50, 0x0c, 0xe7, 0xe1, 0x02, 0x5c, 0x84, 0x12, 0x99, 0xcb, 0x6a, 0x1f, 0x05, 0xd1, 0xe0, 0x89, 0x4b, 0x83, 0x7f, 0x27, 0x2e, 0x0d, 0x66, 0xf5, 0x09, 0x66, 0xf5, 0x09, 0x66, 0xf5, 0x09, 0x66, 0xf5, 0xd9, 0x6d, 0xc4, 0xa5, 0x93, 0x59, 0x7d, 0x62, 0x8d, 0x8c, 0x2a, 0x8a, 0xd5, 0xe7, 0x23, 0x56, 0x9f, 0x60, 0x56, 0x9f, 0x60, 0x56, 0x9f, 0x60, 0x56, 0x9f, 0x60, 0x56, 0x9f, 0x60, 0x56, 0x9f, 0x60, 0x56, 0x9f, 0x19, 0xc4, 0x88, 0x61, 0x58, 0xda, 0x42, 0xca, 0x0f, 0x25, 0x6e, 0x08, 0x97, 0x97, 0x89, 0x4f, 0xc2, 0x88, 0x4f, 0xc2, 0x88, 0x4f, 0xc2, 0x68, 0x45, 0x28, 0xad, 0x08, 0xa5, 0x15, 0xa1, 0xb4, 0x22, 0x54, 0x3f, + 0x2b, 0x96, 0xe3, 0xcb, 0x60, 0x39, 0xa4, 0xc2, 0x5a, 0x78, 0x88, 0x7c, 0x61, 0x1b, 0xd1, 0xd4, 0x76, 0xd1, 0x9b, 0x38, 0xa3, 0x0b, 0x71, 0xc6, 0x68, 0xe2, 0x8c, 0x79, 0xc4, 0x19, 0x03, 0x89, 0x33, 0xa6, 0x10, 0x67, 0x0c, 0x24, 0xce, 0x98, 0x42, 0x9c, 0xd1, 0x99, 0x38, 0x23, 0x92, 0x18, 0xc3, 0x4e, 0x8c, 0xb1, 0x8c, 0x18, 0x63, 0x09, 0x31, 0x46, 0x04, 0x31, 0xc6, 0x74, 0x62, 0x8c, 0xe9, 0xc4, 0x18, 0x89, 0xc4, 0x18, 0x9d, 0x89, 0x2b, 0xc2, 0x88, 0x2b, 0xc2, 0x88, 0x2b, 0xc2, 0x88, 0x03, 0x1b, 0x12, 0x53, 0x68, 0x39, 0x47, 0x18, 0x31, 0x45, 0x18, 0x31, 0x42, 0x18, 0x31, 0x42, 0x18, 0x31, 0xc2, 0x54, 0x62, 0x84, 0xa9, 0x8c, 0x44, 0x28, 0x71, 0x40, 0x17, 0xe2, 0x80, 0x30, 0xd6, 0xff, 0x30, 0xd6, 0xff, 0x30, 0xd6, 0xff, 0x30, 0xd6, 0x7f, 0x5f, 0xcb, 0x4f, 0x8c, 0x48, + 0x43, 0xd6, 0x9e, 0x00, 0x68, 0x04, 0x81, 0xc0, 0xb5, 0xac, 0xdd, 0x61, 0x8c, 0x5e, 0x28, 0xa3, 0x17, 0xca, 0xe8, 0x85, 0x32, 0x7a, 0xa1, 0xc6, 0xe8, 0x85, 0x1a, 0x77, 0x59, 0x42, 0x19, 0xbd, 0x50, 0x46, 0x2f, 0x94, 0xd1, 0x0b, 0x65, 0xf4, 0x42, 0x19, 0xbd, 0x50, 0x46, 0x2f, 0x94, 0xd1, 0x0b, 0x65, 0xf4, 0x42, 0xf5, 0x08, 0xbb, 0x0f, 0xe5, 0xf4, 0x85, 0x30, 0xe8, 0x07, 0xfd, 0x21, 0x1c, 0x06, 0xc1, 0x60, 0x18, 0x02, 0x43, 0x61, 0x18, 0x44, 0xc0, 0x70, 0x18, 0x01, 0xdb, 0xc4, 0x70, 0xd6, 0x3a, 0x95, 0xb5, 0x4e, 0x65, 0xad, 0x53, 0x59, 0xeb, 0x54, 0xd6, 0x3a, 0x95, 0xb5, 0x4e, 0x65, 0xad, 0x53, 0x59, 0xeb, 0x54, 0xd6, 0x3a, 0x95, 0xb5, 0x4e, 0xc5, 0x93, 0x1d, 0x13, 0xde, 0xda, 0xcc, 0x60, 0xfd, 0xe7, 0xb5, 0xd1, 0xd0, 0x8f, 0xfc, 0x9f, 0xba, 0xab, 0x14, 0xf8, 0x27, + 0xee, 0x2a, 0x35, 0xbd, 0xe9, 0xae, 0x92, 0xef, 0x1f, 0xc6, 0xf3, 0xb7, 0xbe, 0x73, 0x3d, 0xc7, 0x16, 0x7e, 0xf8, 0xf0, 0xf3, 0xd7, 0xb3, 0x85, 0xe9, 0x9c, 0x31, 0x53, 0x7e, 0x87, 0x17, 0xfb, 0xc9, 0xc8, 0x1a, 0x2e, 0x71, 0xd5, 0x25, 0xae, 0xd2, 0xd6, 0xe2, 0x23, 0xe4, 0x05, 0x4b, 0xc4, 0xe0, 0xdf, 0xb9, 0x22, 0x97, 0x2b, 0xf2, 0xfe, 0xe0, 0x0a, 0x13, 0x39, 0x49, 0x38, 0xab, 0x48, 0x7f, 0xf9, 0x11, 0xa3, 0xf1, 0x0e, 0x57, 0xfc, 0x7c, 0x3d, 0x16, 0x28, 0x65, 0x2d, 0xf5, 0x7c, 0x2b, 0xf1, 0x02, 0x23, 0x51, 0xcc, 0x15, 0x85, 0xfa, 0x15, 0x59, 0x5c, 0x91, 0xcf, 0x15, 0xc7, 0xb8, 0xe2, 0x90, 0xf1, 0x29, 0x6e, 0xed, 0x1e, 0x65, 0x96, 0xb1, 0xa2, 0x14, 0x71, 0xc5, 0x65, 0xae, 0xb8, 0xca, 0x15, 0x45, 0xfa, 0xf7, 0x10, 0xbf, 0xa7, 0x35, 0xc5, 0x46, 0xb6, 0x72, 0xb1, 0xf6, + 0xaf, 0x1e, 0x46, 0xa6, 0x72, 0x51, 0x8b, 0xb6, 0x38, 0xf3, 0x9c, 0x5e, 0xf6, 0x0f, 0x46, 0x6b, 0xde, 0xa3, 0xec, 0x77, 0x8d, 0xfb, 0x9f, 0x87, 0xb8, 0xea, 0x47, 0xae, 0x3a, 0xcb, 0x55, 0x67, 0xf5, 0xd5, 0x92, 0xfc, 0x46, 0x6f, 0x8d, 0xf6, 0x29, 0x74, 0x6d, 0x7d, 0x3b, 0xcb, 0x59, 0x3f, 0x73, 0xd6, 0x2f, 0x75, 0x5a, 0xa0, 0xad, 0xca, 0xf9, 0x8a, 0x22, 0xbe, 0x95, 0x63, 0xc5, 0x21, 0xf9, 0xa9, 0x38, 0x2c, 0x47, 0x8b, 0xa3, 0x64, 0x41, 0x3f, 0xca, 0x4f, 0xc4, 0xb1, 0x9a, 0x3d, 0xe2, 0x27, 0xb9, 0x5b, 0xfc, 0x2c, 0x13, 0x44, 0x86, 0x6c, 0x2a, 0x8e, 0xcb, 0x75, 0x22, 0xb3, 0xe6, 0xa8, 0xc8, 0x92, 0xa7, 0xc4, 0x2f, 0x72, 0xb8, 0xc8, 0x96, 0xf7, 0x8b, 0x1c, 0x39, 0x44, 0x9c, 0x90, 0x1d, 0xc5, 0x49, 0x5a, 0x76, 0x4a, 0xb6, 0x16, 0xda, 0xa7, 0xde, 0xf3, 0x58, 0x87, 0x4e, + 0xcb, 0x5d, 0xe2, 0x8c, 0x6c, 0x2f, 0x7e, 0xad, 0x79, 0x52, 0x9c, 0xad, 0xd9, 0x2b, 0xce, 0x81, 0x43, 0x76, 0x10, 0xf9, 0xb2, 0x8b, 0x28, 0x60, 0xbf, 0xb0, 0xe6, 0x5f, 0xa2, 0x48, 0x8e, 0x12, 0x4e, 0xd9, 0x4d, 0x14, 0xd7, 0x14, 0xe9, 0x9f, 0x18, 0x1f, 0x20, 0xd7, 0x2a, 0x03, 0xe5, 0x3d, 0x4a, 0xb8, 0x1c, 0xa4, 0x0c, 0x82, 0x08, 0x39, 0x56, 0x19, 0x0e, 0x23, 0x64, 0x77, 0x65, 0xa4, 0x9c, 0xa2, 0x8c, 0x62, 0x3b, 0x9a, 0xed, 0x18, 0x19, 0xa5, 0x4c, 0x90, 0x8b, 0x95, 0x49, 0x72, 0xb1, 0xd7, 0x8b, 0xf2, 0x53, 0xaf, 0x7d, 0x32, 0xc1, 0xeb, 0xa5, 0x9a, 0xbd, 0x5e, 0xfb, 0xa5, 0xb7, 0xd7, 0xcb, 0x72, 0x88, 0xd7, 0xab, 0x35, 0x4f, 0x7a, 0xfd, 0x1d, 0x5e, 0xe3, 0xd8, 0x1b, 0xf0, 0x76, 0xcd, 0x5e, 0xef, 0xcd, 0x72, 0xa9, 0xf7, 0xfd, 0xb0, 0x45, 0x26, 0x79, 0x3f, 0x20, 0x9f, + 0xf1, 0x4e, 0x67, 0xfb, 0xa0, 0x7c, 0xc6, 0x27, 0x17, 0x6f, 0x77, 0xa5, 0x66, 0xaf, 0xcf, 0xd5, 0x9a, 0x6a, 0x9f, 0x2a, 0xfd, 0xdb, 0xfa, 0xa3, 0xcd, 0x61, 0x30, 0x01, 0x26, 0xca, 0x37, 0xcc, 0x93, 0x60, 0x32, 0xfb, 0x53, 0x60, 0x2a, 0xfb, 0xd3, 0x60, 0x3a, 0xfb, 0x33, 0x60, 0x26, 0xcc, 0x82, 0xdb, 0x61, 0x36, 0xcc, 0x81, 0xb9, 0xbc, 0x3f, 0x0f, 0xee, 0x60, 0x7f, 0x3e, 0x2c, 0x60, 0x7f, 0x21, 0x2c, 0x62, 0x7f, 0x85, 0xfc, 0xd4, 0xd2, 0x18, 0x9a, 0x40, 0x57, 0xe8, 0x26, 0x33, 0x2d, 0x3d, 0xd8, 0xf6, 0x64, 0x1b, 0x0a, 0xbd, 0xd8, 0xef, 0x0d, 0x5f, 0xd7, 0xec, 0x55, 0x07, 0xca, 0xd6, 0xea, 0x02, 0xb6, 0x0b, 0x21, 0x09, 0x92, 0x6b, 0xbe, 0x56, 0x57, 0xd4, 0x1c, 0x53, 0x53, 0xd8, 0x4f, 0xad, 0xf9, 0x46, 0x4d, 0xab, 0xf9, 0x45, 0x5d, 0xc9, 0xfe, 0xaa, 0x9a, 0xbd, 0xd6, + 0x16, 0xf2, 0x13, 0x6b, 0x30, 0xb4, 0x94, 0x39, 0xd6, 0x10, 0x68, 0x05, 0x36, 0x5e, 0xb7, 0x66, 0xdb, 0x06, 0xda, 0x42, 0x3b, 0x5e, 0xb7, 0x87, 0x0e, 0xd0, 0x11, 0x3a, 0x71, 0xac, 0x33, 0x74, 0x81, 0xae, 0xbc, 0xee, 0xc6, 0xb6, 0x3b, 0xf4, 0x80, 0x9e, 0x35, 0x7b, 0xac, 0xa1, 0xd0, 0xab, 0xa6, 0xc8, 0xda, 0xbb, 0xa6, 0xc2, 0xda, 0x87, 0xfd, 0xbe, 0x10, 0xc6, 0xeb, 0x7e, 0x35, 0x2e, 0x6b, 0x7f, 0xf6, 0x07, 0xc0, 0xc0, 0x9a, 0x4f, 0xad, 0xe1, 0xd2, 0x6c, 0x1d, 0x54, 0x73, 0xc9, 0x3a, 0x58, 0x06, 0x5a, 0x87, 0x70, 0x6c, 0x28, 0x0c, 0xe3, 0xbc, 0x08, 0xae, 0x1b, 0xce, 0xfe, 0x08, 0x18, 0xc9, 0xeb, 0x51, 0x5c, 0x37, 0x9a, 0xfd, 0x31, 0x30, 0x96, 0xeb, 0xc6, 0x71, 0xdd, 0xf8, 0x9a, 0xa3, 0xd6, 0x09, 0x30, 0x51, 0xfa, 0x59, 0x27, 0xc9, 0xdb, 0xac, 0x93, 0xd9, 0x9f, 0xc2, + 0xfe, 0x54, 0xd9, 0xd2, 0x3a, 0x8d, 0xfd, 0xe9, 0x35, 0xb9, 0xd6, 0x19, 0x72, 0xb8, 0x75, 0x16, 0xdc, 0x0e, 0xb3, 0x61, 0x0e, 0xcc, 0x85, 0x79, 0x70, 0x07, 0xcc, 0x87, 0x05, 0xb0, 0x10, 0x16, 0xc1, 0x9d, 0xb0, 0x18, 0x22, 0x21, 0x0a, 0xa2, 0x21, 0x06, 0xec, 0xb0, 0x04, 0x96, 0x42, 0x2c, 0xc4, 0xc1, 0x32, 0x58, 0x2e, 0x47, 0x5a, 0xe3, 0x89, 0x07, 0x93, 0xe4, 0x76, 0x6b, 0xb2, 0x68, 0x68, 0x5d, 0x21, 0xda, 0x5b, 0x53, 0xe4, 0x23, 0xd6, 0x54, 0x48, 0x13, 0x4d, 0xad, 0x2b, 0x45, 0x67, 0xeb, 0x2a, 0xf6, 0x57, 0x73, 0xce, 0x5d, 0x62, 0x91, 0x75, 0x8d, 0xbc, 0x68, 0x5d, 0x2b, 0xc7, 0x5a, 0xef, 0x96, 0x3f, 0x59, 0xd7, 0xc9, 0x93, 0xd6, 0xf5, 0xf2, 0x5b, 0xeb, 0x3d, 0x62, 0xb8, 0xf5, 0xde, 0x9a, 0x27, 0xad, 0x1b, 0x61, 0x93, 0xec, 0x62, 0xdd, 0x2c, 0xfb, 0x59, 0xef, 0x93, + 0x03, 0xad, 0xf7, 0xb3, 0xbf, 0x05, 0x1e, 0x80, 0x74, 0x78, 0x10, 0xb6, 0xc2, 0x43, 0xf0, 0x30, 0x6c, 0x93, 0x29, 0xd6, 0xed, 0xf0, 0x08, 0xe7, 0x3f, 0xca, 0xeb, 0xc7, 0xe0, 0x71, 0x78, 0x02, 0x76, 0xc0, 0x2e, 0xc6, 0x6d, 0x37, 0x3c, 0x09, 0x4f, 0xc1, 0xd3, 0xf0, 0x0c, 0x3c, 0x0b, 0xcf, 0xc1, 0x1e, 0xd8, 0x0b, 0xcf, 0xc3, 0x0b, 0x35, 0xd9, 0xd6, 0x17, 0xd9, 0xee, 0x83, 0x97, 0x60, 0x3f, 0xbc, 0x0c, 0xaf, 0xc0, 0x01, 0x78, 0x15, 0xfe, 0x0e, 0xaf, 0xc1, 0xeb, 0xf0, 0x06, 0xe7, 0x7f, 0x24, 0x3f, 0xb5, 0x9e, 0x92, 0x8b, 0xad, 0xb9, 0xf2, 0xb0, 0xdf, 0x63, 0x35, 0xd5, 0xe2, 0x3b, 0x3c, 0xc0, 0x19, 0xac, 0x3f, 0x4f, 0xfc, 0x20, 0x5f, 0x14, 0xc7, 0x88, 0x0a, 0x7f, 0x92, 0x6e, 0xac, 0x3f, 0x0f, 0xeb, 0xdf, 0x89, 0xf5, 0x97, 0x8a, 0x4c, 0xe9, 0xc2, 0xfa, 0x2f, 0x60, 0xfd, + 0x05, 0x58, 0x7f, 0x16, 0xd6, 0x7f, 0x10, 0xeb, 0xaf, 0xc6, 0xf2, 0xdd, 0x58, 0xfe, 0x7b, 0x58, 0xfe, 0x05, 0x2c, 0xff, 0xa2, 0x38, 0xcb, 0xf6, 0x1c, 0x38, 0xe4, 0x33, 0x58, 0x7c, 0x85, 0x28, 0x60, 0xbf, 0x90, 0x88, 0xb3, 0x88, 0xb5, 0x85, 0xa8, 0x44, 0x14, 0xcb, 0x2a, 0xe3, 0xaf, 0x38, 0x27, 0x94, 0x21, 0x72, 0x97, 0x32, 0x0c, 0xc6, 0xcb, 0xc7, 0xf1, 0x4f, 0x25, 0xc6, 0x13, 0x03, 0x76, 0x92, 0xaf, 0xec, 0x20, 0x5f, 0xd9, 0x81, 0x85, 0x9f, 0xc1, 0xc2, 0xf3, 0xbc, 0x5e, 0x92, 0x17, 0xb0, 0xee, 0x83, 0x5e, 0xaf, 0xb1, 0x7d, 0x03, 0xde, 0x96, 0x17, 0xb0, 0xe6, 0x32, 0x2c, 0xd9, 0xf3, 0x79, 0xe5, 0x5c, 0x32, 0x8e, 0x2b, 0xf2, 0x02, 0x56, 0x7c, 0x06, 0x4b, 0x3b, 0x83, 0xa5, 0x9d, 0xc1, 0xd2, 0xce, 0x60, 0x69, 0x67, 0xb0, 0xb4, 0xcb, 0x58, 0xda, 0x19, 0x2c, 0xed, 0x32, + 0x96, 0x76, 0x19, 0x4b, 0x3b, 0x83, 0xa5, 0x9d, 0xb1, 0xcc, 0x90, 0xab, 0x2d, 0xf7, 0xc2, 0x46, 0x79, 0xcc, 0xb2, 0x89, 0x6d, 0x3a, 0xdb, 0x87, 0x61, 0x1b, 0x6c, 0xe7, 0xf5, 0x23, 0xf0, 0xb5, 0xbc, 0x80, 0x35, 0xba, 0xd5, 0x05, 0x6c, 0x17, 0x42, 0x12, 0x24, 0xc3, 0x0a, 0x48, 0x81, 0x54, 0x48, 0x83, 0x95, 0xb0, 0x4a, 0x5e, 0xb0, 0xf6, 0x94, 0x25, 0xd6, 0x50, 0xe8, 0x05, 0xbd, 0xa1, 0x0f, 0xf4, 0x85, 0x30, 0xe8, 0x07, 0xfd, 0x61, 0x00, 0x0c, 0x84, 0x70, 0x18, 0x04, 0x83, 0x61, 0x08, 0x0c, 0x85, 0x61, 0x10, 0x01, 0xc3, 0x61, 0x04, 0x8c, 0x84, 0x51, 0x30, 0x1a, 0xc6, 0xc0, 0x58, 0x18, 0x07, 0xe3, 0xc9, 0x7d, 0x26, 0xc0, 0x44, 0x98, 0x04, 0x93, 0x61, 0x0a, 0x4c, 0x85, 0x69, 0x30, 0x1d, 0x66, 0xc8, 0x02, 0xac, 0xa7, 0x00, 0xeb, 0x29, 0xc0, 0x7a, 0x0a, 0xb0, 0x9e, 0x02, + 0xac, 0xa7, 0x00, 0xeb, 0x29, 0xc0, 0x7a, 0x0a, 0xb0, 0x9e, 0x02, 0xac, 0xa7, 0x00, 0xeb, 0x29, 0xc0, 0x7a, 0x0a, 0xb0, 0x9e, 0x02, 0xac, 0xa7, 0x00, 0xeb, 0x29, 0xc0, 0x7a, 0x0a, 0xb0, 0x9e, 0x02, 0xac, 0xa7, 0x00, 0xeb, 0x29, 0xc0, 0x7a, 0x0a, 0xb0, 0x9e, 0x02, 0xac, 0xa7, 0x00, 0xeb, 0x29, 0xc0, 0x7a, 0x0a, 0xb0, 0x9e, 0x6a, 0xac, 0xa7, 0x1a, 0xeb, 0xa9, 0xb6, 0x26, 0xc3, 0x0a, 0x48, 0x81, 0x54, 0x48, 0x83, 0x95, 0xb0, 0x0a, 0x56, 0xc3, 0x5d, 0xb0, 0x06, 0xd6, 0xc2, 0xdd, 0xb0, 0x0e, 0xd6, 0xc3, 0x3d, 0xb0, 0x09, 0xeb, 0xda, 0x0c, 0xf7, 0xc1, 0xfd, 0xb0, 0x05, 0x1e, 0x80, 0x74, 0x78, 0x10, 0xb6, 0xc2, 0x43, 0xf0, 0x30, 0x6c, 0x83, 0xed, 0xf0, 0x08, 0x3c, 0x0a, 0x8f, 0xc1, 0xe3, 0xf0, 0x04, 0xec, 0x80, 0x5d, 0xe4, 0xdd, 0xbb, 0xe1, 0x49, 0x78, 0x0a, 0x9e, 0x86, + 0x67, 0xe0, 0x59, 0x78, 0x0e, 0xf6, 0xc0, 0x5e, 0x78, 0x1e, 0x5e, 0x80, 0x17, 0x61, 0x1f, 0xbc, 0x04, 0xfb, 0xe1, 0x65, 0x78, 0x05, 0x0e, 0xc0, 0xab, 0xf0, 0x77, 0x78, 0x0d, 0x5e, 0x87, 0x37, 0xe0, 0x23, 0x79, 0x06, 0x8b, 0x79, 0x4b, 0x0c, 0x64, 0xcd, 0xcc, 0x63, 0xbd, 0xcc, 0x16, 0xdf, 0xc9, 0x5f, 0xc4, 0x11, 0x99, 0x83, 0xb5, 0x5c, 0xc0, 0x5a, 0xaa, 0xb0, 0x94, 0x12, 0xac, 0xa4, 0x14, 0x0b, 0xa9, 0x62, 0x5d, 0xdc, 0x83, 0x65, 0xb8, 0xb1, 0x8c, 0x12, 0x2c, 0xa3, 0x84, 0x35, 0xb1, 0x04, 0x4b, 0x28, 0xc7, 0x0a, 0x7e, 0xf7, 0x1b, 0x5e, 0x28, 0xbd, 0x8a, 0x75, 0xac, 0xa0, 0xee, 0x37, 0xbb, 0x58, 0x93, 0xb2, 0x59, 0x93, 0xb2, 0x59, 0x93, 0xb2, 0x59, 0x93, 0x2e, 0xb3, 0x26, 0x5d, 0x66, 0x4d, 0xca, 0x66, 0x4d, 0xca, 0x66, 0x4d, 0xba, 0xcc, 0x9a, 0x74, 0x99, 0x35, 0x29, + 0x9b, 0x35, 0x29, 0x9b, 0x35, 0x29, 0x9b, 0x35, 0x29, 0x9b, 0x35, 0x29, 0x9b, 0x35, 0x29, 0x9b, 0x35, 0x29, 0x9b, 0x35, 0xe9, 0x32, 0x6b, 0xd2, 0x65, 0xd6, 0xa4, 0x6c, 0xd6, 0xa4, 0x6c, 0xd6, 0xa4, 0xcb, 0xac, 0x49, 0x97, 0x59, 0x93, 0xb2, 0xb1, 0x80, 0x2f, 0x2c, 0x77, 0xc9, 0xc3, 0x58, 0x40, 0x11, 0xea, 0x2f, 0x42, 0xfd, 0x45, 0xa8, 0xbf, 0xc8, 0xb2, 0x43, 0x9e, 0x66, 0xc6, 0xa4, 0xf8, 0x1e, 0x9f, 0x90, 0x8d, 0x4f, 0xc8, 0xc2, 0x27, 0xec, 0xc2, 0x27, 0x14, 0x19, 0x3e, 0xe1, 0x67, 0x7c, 0xc2, 0xa3, 0xf4, 0xb4, 0x08, 0x9f, 0x50, 0x46, 0x6f, 0x1d, 0xf8, 0x04, 0x07, 0x3d, 0xfe, 0x0e, 0x9f, 0xf0, 0x01, 0x3e, 0xa1, 0xcc, 0xf0, 0x09, 0x6f, 0xd2, 0xf3, 0x7c, 0x7a, 0x5e, 0x80, 0x4f, 0x70, 0xe0, 0x13, 0x1c, 0xf8, 0x84, 0xdd, 0xf8, 0x04, 0x07, 0x3e, 0xc1, 0xc1, 0x48, 0x9c, + 0xc1, 0x27, 0x1c, 0x67, 0x34, 0x7e, 0xc2, 0x27, 0x38, 0xf5, 0x67, 0x76, 0x0c, 0x91, 0x8f, 0xe2, 0x0f, 0x1e, 0xfd, 0xcd, 0x13, 0x44, 0x16, 0xf2, 0x7a, 0x91, 0xdc, 0x86, 0x3f, 0xc8, 0x66, 0x94, 0x7e, 0xc6, 0x1f, 0x38, 0xf0, 0x07, 0x1f, 0xe0, 0x0f, 0x1c, 0xf8, 0x03, 0x07, 0xfe, 0xc0, 0xc1, 0x2a, 0x5f, 0xc2, 0x2a, 0x5f, 0x82, 0x5f, 0xc8, 0xc2, 0x2f, 0x64, 0x19, 0x7e, 0xa1, 0x0c, 0xbf, 0xe0, 0xc0, 0x2f, 0x64, 0xe3, 0x17, 0xb2, 0xf1, 0x0b, 0xd9, 0xf8, 0x85, 0x6c, 0xfc, 0x42, 0xb6, 0xe1, 0x17, 0xb2, 0xeb, 0xf8, 0x85, 0x6c, 0xfc, 0x82, 0x36, 0x2a, 0xa9, 0xf8, 0x85, 0x14, 0xc3, 0x2f, 0xa4, 0xdc, 0xe2, 0x17, 0x52, 0xf0, 0x0b, 0x29, 0xf8, 0x05, 0x87, 0xe1, 0x17, 0x1c, 0xf8, 0x05, 0x07, 0x7e, 0xc1, 0x81, 0x5f, 0x70, 0xe0, 0x17, 0x1c, 0xf8, 0x05, 0x07, 0x7e, 0xc1, 0x81, 0x5f, + 0x70, 0xe0, 0x17, 0x1c, 0xf8, 0x05, 0x07, 0x7e, 0xa1, 0x08, 0xbf, 0x50, 0x84, 0x5f, 0x60, 0x55, 0x85, 0x3e, 0xd0, 0x17, 0xc2, 0xa0, 0x1f, 0xf4, 0x87, 0x01, 0x30, 0x10, 0xc2, 0x61, 0x10, 0x0c, 0x86, 0x21, 0x30, 0x14, 0x86, 0x41, 0x04, 0x0c, 0x87, 0x11, 0x30, 0x12, 0x88, 0x1c, 0xf1, 0x0b, 0x45, 0xf8, 0x85, 0x22, 0xfc, 0x42, 0x11, 0x7e, 0xa1, 0x08, 0xbf, 0x50, 0x86, 0x5f, 0x28, 0xc3, 0x2f, 0x94, 0xe1, 0x17, 0xca, 0xf0, 0x0b, 0x65, 0xf8, 0x85, 0x32, 0xfc, 0x42, 0x19, 0x7e, 0xa1, 0x0c, 0xbf, 0x50, 0x86, 0x5f, 0x70, 0xe0, 0x17, 0x1c, 0xf8, 0x05, 0x07, 0x7e, 0xc1, 0x81, 0x5f, 0x70, 0xe0, 0x17, 0x1c, 0xf8, 0x05, 0x07, 0x7e, 0xc1, 0x81, 0x5f, 0x70, 0xe0, 0x17, 0x1c, 0xf8, 0x05, 0x07, 0x7e, 0xc1, 0x81, 0x5f, 0x70, 0xe0, 0x17, 0x1c, 0xf8, 0x05, 0x07, 0x7e, 0xc1, 0x81, 0x5f, + 0x70, 0xe0, 0x17, 0x1c, 0xf8, 0x05, 0x07, 0x7e, 0xc1, 0x81, 0x5f, 0x70, 0xe0, 0x17, 0x1c, 0xf8, 0x05, 0x07, 0x7e, 0xc1, 0x81, 0x5f, 0x28, 0xc3, 0x2f, 0x94, 0xe1, 0x17, 0xca, 0xf0, 0x0b, 0x65, 0xf8, 0x85, 0x32, 0xfc, 0x42, 0x19, 0x7e, 0xa1, 0x0c, 0xbf, 0x50, 0x86, 0x5f, 0x28, 0xc3, 0x2f, 0x94, 0xe1, 0x17, 0xca, 0x50, 0x59, 0x19, 0x7e, 0xa1, 0x0c, 0xbf, 0x50, 0x86, 0x5f, 0x28, 0xc3, 0x2f, 0x94, 0xe1, 0x17, 0xca, 0xf0, 0x0b, 0x65, 0xf8, 0x05, 0x07, 0x7e, 0xc1, 0x81, 0x5f, 0x70, 0xe0, 0x17, 0x1c, 0xf8, 0x05, 0x07, 0x7e, 0xc1, 0x81, 0x5f, 0x70, 0xe0, 0x17, 0x1c, 0xf8, 0x05, 0x07, 0x7e, 0xc1, 0x81, 0x5f, 0x70, 0xe0, 0x17, 0x1c, 0xf8, 0x05, 0x07, 0x7e, 0xc1, 0x81, 0x5f, 0x70, 0xe0, 0x17, 0x1c, 0xf8, 0x05, 0x07, 0x7e, 0xc1, 0x81, 0x5f, 0x70, 0xe0, 0x17, 0x9c, 0xf8, 0x05, + 0x27, 0x7e, 0xc1, 0x89, 0x5f, 0x70, 0xe2, 0x17, 0x9c, 0xf8, 0x05, 0x27, 0x7e, 0xc1, 0x89, 0x5f, 0x70, 0xe2, 0x17, 0x9c, 0xf8, 0x05, 0x27, 0x7e, 0xc1, 0x89, 0x5f, 0x70, 0xe2, 0x17, 0x9c, 0xf8, 0x05, 0x27, 0x7e, 0xc1, 0x89, 0x5f, 0x70, 0xe2, 0x17, 0x9c, 0xf8, 0x05, 0x27, 0x7e, 0xc1, 0x89, 0x5f, 0x70, 0xe2, 0x17, 0x9c, 0xf8, 0x05, 0x27, 0x7e, 0xc1, 0x89, 0x5f, 0x70, 0xe2, 0x17, 0xb2, 0xb5, 0xe7, 0xc1, 0x88, 0xb6, 0xf8, 0x83, 0x72, 0x2c, 0xe6, 0x34, 0x96, 0x52, 0x84, 0xa5, 0x7c, 0x83, 0xa5, 0x7c, 0x8a, 0xa5, 0x64, 0x60, 0x25, 0x67, 0xb1, 0x90, 0x6f, 0xb0, 0x90, 0x8f, 0xb1, 0x8c, 0xe3, 0x58, 0xc6, 0x15, 0x2c, 0xe3, 0x0c, 0x3e, 0xa1, 0x12, 0xab, 0xf8, 0x1a, 0x6b, 0x38, 0x8a, 0x35, 0x38, 0xb1, 0x86, 0xef, 0x50, 0xf8, 0x37, 0xf8, 0x81, 0x0a, 0x14, 0xfe, 0xb1, 0x4f, 0xae, + 0xb0, 0xa1, 0xd4, 0x4b, 0xa8, 0xf4, 0x12, 0x2a, 0xbd, 0x64, 0x8d, 0x17, 0x6d, 0x8d, 0x51, 0x75, 0x31, 0x9a, 0xe5, 0xc4, 0x28, 0x26, 0xeb, 0x6a, 0x8e, 0xdd, 0x25, 0x16, 0x58, 0xd7, 0x08, 0x9b, 0x75, 0x9d, 0xf0, 0xf7, 0x78, 0x56, 0xa1, 0xfd, 0x96, 0xd9, 0x4f, 0xd8, 0xe2, 0xcf, 0xd8, 0x5f, 0x06, 0xf6, 0x76, 0x5c, 0x9e, 0xa4, 0x15, 0xa7, 0x69, 0xc5, 0x39, 0x5a, 0x91, 0x8b, 0x77, 0xfa, 0x85, 0x96, 0x14, 0xd1, 0x92, 0x02, 0x5a, 0x92, 0x4b, 0x4b, 0x0a, 0x68, 0x49, 0x11, 0x2d, 0xc9, 0xa5, 0x15, 0x0e, 0x5a, 0x51, 0x40, 0x2b, 0xce, 0xd3, 0x8a, 0x5c, 0x5a, 0x61, 0xa2, 0x66, 0x1f, 0x6a, 0xf2, 0xa1, 0xa6, 0xa1, 0xd4, 0x64, 0x12, 0x5f, 0xe0, 0x1d, 0x4e, 0xd0, 0xd7, 0x9f, 0xf1, 0x0c, 0x19, 0xd4, 0xe4, 0xa4, 0xa6, 0xaf, 0xa8, 0xe9, 0x63, 0x6a, 0xca, 0xc4, 0x33, 0xe4, 0x53, 0x5b, + 0x3e, 0x9e, 0xa1, 0x90, 0x1a, 0x0f, 0x51, 0xe3, 0x3e, 0x3c, 0x43, 0x2e, 0x9e, 0xc1, 0x41, 0xad, 0x3f, 0x50, 0x6b, 0x35, 0xb5, 0x3a, 0xa8, 0xb5, 0x42, 0xfc, 0xca, 0x39, 0x67, 0x89, 0x0e, 0xce, 0x81, 0x43, 0x7e, 0x86, 0x87, 0xc8, 0xc0, 0x43, 0x1c, 0xa3, 0x25, 0xdf, 0x33, 0x26, 0xc5, 0xb4, 0xe6, 0x6b, 0x3c, 0xc4, 0x71, 0x72, 0x81, 0x52, 0xf2, 0x80, 0x52, 0xe2, 0xff, 0x4b, 0xc4, 0xff, 0x97, 0xf0, 0x06, 0x27, 0x68, 0xe5, 0x57, 0x78, 0x83, 0x63, 0xb4, 0xd4, 0x4d, 0x4b, 0xf7, 0x79, 0xbd, 0x2a, 0x0b, 0xbd, 0xfe, 0x0e, 0xaf, 0x71, 0xec, 0x0d, 0x78, 0x5b, 0x1e, 0xc3, 0x23, 0x5c, 0xc0, 0x23, 0x5c, 0xa0, 0x17, 0xed, 0xf1, 0x06, 0xc7, 0x7c, 0xae, 0x92, 0xb3, 0x56, 0xc9, 0x13, 0x78, 0x84, 0x13, 0x78, 0x84, 0x13, 0x78, 0x84, 0x13, 0x78, 0x84, 0x13, 0x8c, 0xf3, 0x79, 0x3c, 0xc2, + 0x09, 0xc6, 0xfa, 0x3c, 0x63, 0x7d, 0x1e, 0x8f, 0x70, 0x02, 0x8f, 0x70, 0x02, 0x6b, 0x3f, 0x86, 0xb5, 0x3b, 0xb0, 0xf6, 0x63, 0x58, 0xfb, 0x31, 0xac, 0xfd, 0x18, 0xd6, 0x7e, 0x0c, 0x6b, 0x27, 0x26, 0x87, 0x54, 0x48, 0x83, 0x95, 0xb0, 0x4a, 0x1e, 0xc3, 0xda, 0x33, 0xb0, 0xf6, 0x0c, 0xac, 0x3d, 0x03, 0x6b, 0xcf, 0xc0, 0xda, 0x33, 0xb0, 0xf6, 0x0c, 0xac, 0x3d, 0x03, 0x6b, 0xcf, 0xc0, 0xda, 0x33, 0xb0, 0xf6, 0x0c, 0xac, 0x3d, 0x03, 0x6b, 0xcf, 0xc0, 0xda, 0x33, 0xb0, 0xf6, 0x0c, 0xac, 0x3d, 0x03, 0x6b, 0xcf, 0xc0, 0xda, 0x33, 0xb0, 0xf6, 0x0c, 0xac, 0x3d, 0x03, 0x6b, 0xcf, 0xc0, 0xda, 0x33, 0xb0, 0xf6, 0x0c, 0xac, 0x3d, 0x03, 0x6b, 0xcf, 0xc0, 0xda, 0x33, 0xb0, 0xf6, 0x0c, 0xac, 0x3d, 0x1f, 0x6b, 0xcf, 0xc7, 0xda, 0xf3, 0xb1, 0xf6, 0x7c, 0xac, 0x3d, 0x1f, 0x6b, 0xcf, + 0xc7, 0xda, 0xf3, 0xb1, 0xf6, 0x7c, 0xac, 0x3d, 0x1f, 0x6b, 0x2f, 0xc4, 0xda, 0x0b, 0xb1, 0xf6, 0x42, 0xac, 0xbd, 0x10, 0x6b, 0x2f, 0xc4, 0xda, 0x0b, 0xb1, 0xf6, 0x42, 0xac, 0xbd, 0x10, 0x6b, 0x2f, 0xc4, 0xda, 0x0b, 0xb1, 0xf6, 0x42, 0xac, 0xbd, 0x10, 0x6b, 0x2f, 0xc4, 0xda, 0x0b, 0xb1, 0xf6, 0x42, 0xac, 0xbd, 0x10, 0x6b, 0x2f, 0xc4, 0xda, 0x0b, 0xb1, 0xf6, 0x42, 0xac, 0xbd, 0x10, 0x6b, 0x2f, 0xc4, 0xda, 0x0b, 0xb1, 0xf6, 0x42, 0xac, 0xbd, 0x10, 0x6b, 0xcf, 0x45, 0x19, 0x9d, 0xb0, 0xf6, 0x5c, 0x74, 0x59, 0x89, 0x2e, 0x73, 0xb1, 0xf6, 0x5c, 0xac, 0x3d, 0x17, 0x7d, 0x56, 0xa2, 0x4f, 0x33, 0xd6, 0x9e, 0x8b, 0x72, 0x3a, 0xa1, 0x9c, 0x48, 0x94, 0xd3, 0x1e, 0x6b, 0xcf, 0xc5, 0xda, 0x73, 0xd1, 0x6a, 0x20, 0x5a, 0x95, 0x68, 0x35, 0xd0, 0x7a, 0x2f, 0x65, 0x6d, 0x84, 0x4d, + 0xf4, 0x6b, 0x33, 0xdc, 0x07, 0xf7, 0xc3, 0x16, 0x78, 0x00, 0xd2, 0xe1, 0x41, 0xd8, 0x0a, 0x0f, 0xc1, 0xc3, 0xb0, 0x0d, 0xb6, 0xc3, 0x23, 0xf0, 0x28, 0x3c, 0x06, 0x8f, 0xc3, 0x13, 0xb0, 0x03, 0x76, 0xc9, 0xe3, 0x58, 0xfd, 0x71, 0xac, 0xfe, 0x38, 0x56, 0x7f, 0x1c, 0xab, 0x3f, 0x8e, 0xd5, 0x1f, 0xc7, 0xea, 0x8f, 0x63, 0xf5, 0xc7, 0xb1, 0xfa, 0xe3, 0x58, 0xfd, 0x71, 0xac, 0xfe, 0x38, 0x56, 0x7f, 0x1c, 0xab, 0x3f, 0x8e, 0xd5, 0x1f, 0xc7, 0xea, 0x8f, 0x63, 0xf5, 0xc7, 0xb1, 0xfa, 0xe3, 0x58, 0xfd, 0x71, 0xac, 0xfe, 0x38, 0x56, 0x7f, 0x1c, 0xab, 0x3f, 0x8e, 0xd5, 0x1f, 0xc7, 0xea, 0x8f, 0x63, 0xf5, 0xc7, 0xb1, 0xfa, 0x13, 0xc4, 0xd0, 0x97, 0xfc, 0x1e, 0x63, 0xb5, 0x5b, 0x45, 0x44, 0x90, 0x4b, 0x44, 0x90, 0x85, 0x07, 0xc8, 0x24, 0x22, 0x38, 0x86, 0x45, 0x54, 0x60, 0x09, + 0x97, 0xb0, 0x84, 0x4a, 0x2c, 0xa1, 0x12, 0xf5, 0xbb, 0xb1, 0xb9, 0x67, 0xb0, 0x80, 0x4b, 0xa8, 0xff, 0x12, 0xea, 0xbf, 0x64, 0xa8, 0xbf, 0x04, 0xc5, 0xbb, 0x51, 0xb9, 0x9b, 0xa8, 0xe0, 0x08, 0x19, 0xf0, 0x11, 0x32, 0xe0, 0x23, 0x64, 0xc0, 0x39, 0x64, 0xc0, 0x39, 0xda, 0xa7, 0xbd, 0x8d, 0xa7, 0x38, 0x69, 0x51, 0x42, 0x36, 0x4a, 0x2f, 0x46, 0xe5, 0x25, 0xa8, 0xbc, 0xf6, 0x7b, 0x86, 0x3f, 0x12, 0x29, 0xe4, 0xb0, 0xd6, 0xb9, 0x51, 0x76, 0x25, 0x11, 0x43, 0x16, 0x11, 0x43, 0x16, 0x11, 0x43, 0x56, 0x9d, 0x88, 0x21, 0x8b, 0x88, 0x21, 0xab, 0x4e, 0xc4, 0x90, 0x45, 0xc4, 0x90, 0x45, 0xc4, 0x90, 0x45, 0xc4, 0x90, 0x45, 0xc4, 0x90, 0x45, 0xc4, 0x90, 0x45, 0xc4, 0x90, 0x55, 0x27, 0x62, 0xc8, 0x22, 0x62, 0xc8, 0xaa, 0x13, 0x31, 0x64, 0xb1, 0x36, 0x7e, 0x4e, 0xc4, 0xf0, 0x19, + 0xeb, 0xe3, 0x3f, 0xf4, 0xa8, 0x61, 0x13, 0xdb, 0xba, 0x91, 0xc3, 0x76, 0x5e, 0x3f, 0x02, 0x3b, 0x64, 0x0e, 0x16, 0x73, 0x09, 0x85, 0x56, 0xa2, 0xd0, 0x4a, 0x14, 0x5a, 0x89, 0x42, 0x2b, 0x51, 0x68, 0x25, 0x0a, 0xad, 0x44, 0xa1, 0x95, 0x28, 0xb4, 0x12, 0x85, 0x56, 0xa2, 0x20, 0x37, 0xeb, 0x85, 0x1b, 0x05, 0xb9, 0x51, 0x90, 0x1b, 0x05, 0xb9, 0x51, 0x90, 0x1b, 0x05, 0xb9, 0x51, 0x90, 0x9b, 0xf5, 0xc2, 0x8d, 0x82, 0xdc, 0xac, 0x17, 0x6e, 0xd6, 0x0b, 0x37, 0xeb, 0x85, 0x1b, 0x05, 0xb9, 0x51, 0x90, 0x9b, 0xf5, 0xc2, 0x8d, 0x82, 0xdc, 0xac, 0x17, 0x6e, 0x14, 0x54, 0x82, 0x82, 0x4a, 0x50, 0x90, 0x1b, 0x05, 0xb9, 0x51, 0x90, 0x1b, 0x05, 0xb9, 0x51, 0x90, 0x1b, 0x05, 0xb9, 0x51, 0x90, 0x1b, 0x05, 0xb9, 0x51, 0x90, 0x1b, 0x05, 0xb9, 0x51, 0x90, 0x1b, 0x05, 0xb9, 0x51, 0x90, + 0x1b, 0x05, 0xb9, 0x51, 0x90, 0x1b, 0x05, 0xb9, 0x51, 0x90, 0x1b, 0x05, 0xb9, 0x51, 0x90, 0x9b, 0x99, 0xad, 0x14, 0x9f, 0x30, 0xb3, 0x7b, 0x99, 0xd9, 0x9d, 0xe2, 0x47, 0x7c, 0xf6, 0x31, 0x79, 0x98, 0x99, 0x3d, 0x87, 0xaf, 0xd3, 0x3c, 0xea, 0x21, 0x66, 0x37, 0x4b, 0xf7, 0x73, 0xd9, 0xf8, 0xa9, 0x1c, 0x62, 0xbe, 0x13, 0x78, 0xd1, 0x93, 0xf8, 0xc5, 0x53, 0xf8, 0xfb, 0xd3, 0xe0, 0x99, 0xe1, 0x8f, 0xf0, 0x6f, 0x47, 0xf0, 0x6f, 0x47, 0x98, 0xe9, 0x22, 0x7c, 0xdb, 0x11, 0x7c, 0xdb, 0x21, 0x66, 0xbc, 0x18, 0xbf, 0x96, 0xc9, 0xac, 0xdf, 0xce, 0xac, 0x4f, 0x62, 0xd6, 0x27, 0x31, 0xeb, 0xf3, 0x98, 0xf5, 0x79, 0xcc, 0xfa, 0xdf, 0xf1, 0x73, 0x4e, 0xfc, 0x9c, 0x13, 0x1f, 0xe7, 0xc0, 0xc7, 0x1d, 0x61, 0xe6, 0x2b, 0xf1, 0x71, 0x55, 0xcc, 0xfe, 0x47, 0xcc, 0xfe, 0x47, 0xf8, 0xb8, + 0x23, 0xf8, 0xb8, 0x23, 0xf8, 0xb8, 0x23, 0x44, 0x3f, 0x65, 0x44, 0x3f, 0x65, 0x86, 0x8f, 0x3b, 0x82, 0x0a, 0x76, 0xa2, 0x82, 0x9d, 0xa8, 0x60, 0x27, 0x2a, 0xc8, 0x46, 0x05, 0xd9, 0xa8, 0x60, 0x27, 0x2a, 0xd8, 0x89, 0x0a, 0xb2, 0x51, 0x41, 0x36, 0x2a, 0xd8, 0x89, 0x0a, 0x76, 0xa2, 0x82, 0x9d, 0xa8, 0x60, 0x27, 0x2a, 0xd8, 0x89, 0x0a, 0x76, 0xa2, 0x82, 0x9d, 0xa8, 0x20, 0x1b, 0x15, 0x64, 0xa3, 0x82, 0x9d, 0xa8, 0x60, 0x27, 0x2a, 0xc8, 0x46, 0x05, 0xd9, 0xa8, 0x60, 0x27, 0xfe, 0xf0, 0x08, 0xb3, 0x7b, 0x16, 0x7f, 0x78, 0x04, 0x7f, 0x78, 0x04, 0x7f, 0x78, 0x04, 0x7f, 0x78, 0x04, 0x7f, 0x78, 0x04, 0x7f, 0x78, 0x04, 0x7f, 0x78, 0x04, 0x7f, 0x78, 0x04, 0x7f, 0x78, 0x04, 0x7f, 0x78, 0xc4, 0xda, 0x42, 0x5e, 0xb1, 0x06, 0x83, 0x0d, 0xda, 0x41, 0x7b, 0xe8, 0x00, 0x1d, 0xa1, + 0x2b, 0xf4, 0x94, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0xf1, 0x97, 0x87, 0x51, 0x63, 0x16, 0x6a, 0xcc, 0x42, 0x8d, 0x59, 0xa8, 0x31, 0x0b, 0x35, 0x66, 0xa1, 0xc6, 0x2c, 0xd4, 0x98, 0x85, 0x1a, 0xb3, 0x50, 0x63, 0x16, 0x6a, 0x2c, 0x30, 0xfc, 0x59, 0x81, 0xe1, 0xcf, 0x0a, 0x50, 0x63, 0x01, 0x6a, 0x2c, 0xa8, 0xe3, 0xcf, 0x0a, 0x0c, 0x7f, 0x16, 0x67, 0xf8, 0xb3, 0x02, 0xd4, + 0x58, 0xe0, 0xf1, 0x67, 0xbc, 0x7f, 0x8f, 0x50, 0x51, 0xe3, 0x47, 0xa8, 0xf1, 0x23, 0xd4, 0x58, 0x84, 0x1a, 0x8b, 0x50, 0x63, 0x11, 0x6a, 0x2c, 0x42, 0x8d, 0x45, 0xa8, 0xb1, 0x08, 0x35, 0x16, 0xa1, 0xc6, 0x22, 0xd4, 0x58, 0x84, 0x1a, 0x8b, 0x50, 0x63, 0x11, 0x6a, 0x2c, 0x42, 0x8d, 0x45, 0xa8, 0xb1, 0x08, 0x35, 0x16, 0xa1, 0xc6, 0x22, 0xd4, 0x58, 0x84, 0x1a, 0x8b, 0x50, 0x63, 0x11, 0xfe, 0x2c, 0x13, 0x7f, 0x96, 0x89, 0x3f, 0xcb, 0xc4, 0x9f, 0x65, 0xe2, 0xcf, 0x32, 0xf1, 0x67, 0x99, 0xf8, 0xb3, 0x4c, 0xfc, 0x59, 0x26, 0xfe, 0x2c, 0x13, 0x7f, 0x96, 0x89, 0x3f, 0xcb, 0xc4, 0x9f, 0x65, 0xe2, 0xcf, 0x32, 0xf1, 0x67, 0x99, 0xf8, 0xb3, 0x4c, 0xfc, 0x59, 0x26, 0xfe, 0x2c, 0x13, 0x7f, 0x96, 0x89, 0x3f, 0xcb, 0xc4, 0x9f, 0x65, 0xe2, 0xcf, 0x32, 0xf1, 0x67, 0x99, 0xf8, 0xb3, + 0x4c, 0xfc, 0x59, 0x26, 0xbe, 0xcc, 0x49, 0x14, 0x73, 0x49, 0x3c, 0x8f, 0xe2, 0xdd, 0xf8, 0xb1, 0x2a, 0xe2, 0xfe, 0xab, 0x28, 0xbe, 0x14, 0xc5, 0x57, 0xa1, 0xf8, 0x0b, 0xac, 0xee, 0xe5, 0xa8, 0xfe, 0x3c, 0xaa, 0xaf, 0xd2, 0xef, 0x05, 0x64, 0xb3, 0x9f, 0xc3, 0x39, 0x5a, 0xcc, 0x7f, 0x92, 0xfd, 0x53, 0xec, 0x13, 0x73, 0xe3, 0xdf, 0xce, 0xa3, 0xfe, 0x0b, 0x28, 0xbf, 0x0c, 0xe5, 0x5f, 0x44, 0xf9, 0x17, 0x51, 0x7e, 0x09, 0xca, 0xbf, 0x88, 0xf2, 0x2f, 0xa0, 0xfc, 0xf3, 0x28, 0xbf, 0xd4, 0xf8, 0xab, 0x53, 0x09, 0x0a, 0xbf, 0x80, 0xc2, 0x2f, 0xa2, 0xf0, 0xab, 0x28, 0xfc, 0x2a, 0x0a, 0x2f, 0x43, 0xe1, 0x65, 0x28, 0xfc, 0x22, 0x0a, 0xbf, 0x88, 0xc2, 0x2f, 0x1a, 0xb1, 0xfc, 0x45, 0x6d, 0xf5, 0xc6, 0x0f, 0x7d, 0x86, 0x1f, 0xaa, 0x36, 0xfc, 0x90, 0xd3, 0xf0, 0x43, 0x4e, 0xfc, + 0x90, 0x13, 0x3f, 0xe4, 0xbc, 0xee, 0x87, 0xbe, 0x96, 0x17, 0x55, 0xed, 0xdb, 0xcf, 0x0b, 0xd8, 0x2e, 0x84, 0x24, 0x48, 0x86, 0x15, 0x90, 0x02, 0xa9, 0x90, 0x06, 0x2b, 0x61, 0x95, 0xbc, 0x88, 0x12, 0x4b, 0x51, 0x62, 0x29, 0x4a, 0x2c, 0x45, 0x89, 0xa5, 0x28, 0xb1, 0x14, 0x25, 0x96, 0xa2, 0xc4, 0x52, 0x94, 0x58, 0x8a, 0x12, 0x4b, 0x51, 0x62, 0x29, 0x4a, 0x2c, 0x45, 0x89, 0xa5, 0x28, 0xb1, 0x14, 0x25, 0x96, 0xa2, 0xc4, 0x52, 0x94, 0x58, 0x8a, 0x12, 0x4b, 0x51, 0x62, 0x29, 0x4a, 0x2c, 0x45, 0x89, 0xa5, 0x28, 0xb1, 0x14, 0x25, 0x96, 0xa2, 0xc4, 0x52, 0x94, 0x58, 0x8a, 0x12, 0x4b, 0x51, 0x62, 0x29, 0x4a, 0xac, 0x42, 0x89, 0x55, 0x28, 0xb1, 0x0a, 0x25, 0x56, 0xa1, 0xc4, 0x2a, 0x94, 0x58, 0x85, 0x12, 0xab, 0x50, 0x62, 0x15, 0x4a, 0xac, 0xfa, 0x8b, 0x71, 0xf4, 0xd5, 0x3f, + 0x8c, 0xa3, 0xef, 0x85, 0x8d, 0x7a, 0x3c, 0x5d, 0x82, 0x12, 0x4b, 0x50, 0x62, 0x09, 0x4a, 0x2c, 0x41, 0x89, 0x25, 0x28, 0xb1, 0x04, 0x25, 0x96, 0xa0, 0xc4, 0x12, 0x94, 0x58, 0x82, 0x12, 0x4b, 0x50, 0x62, 0x09, 0x4a, 0x2c, 0x41, 0x89, 0x25, 0x28, 0xb1, 0x04, 0x25, 0x96, 0xa0, 0xc4, 0x12, 0x94, 0x58, 0x82, 0x12, 0x4b, 0x50, 0x62, 0x09, 0x4a, 0x2c, 0x45, 0x89, 0xa5, 0x28, 0xb1, 0x14, 0x25, 0x96, 0xa2, 0xc4, 0x52, 0x94, 0x58, 0x8a, 0x12, 0x4b, 0x51, 0x62, 0x29, 0x4a, 0x2c, 0x45, 0x89, 0xa5, 0x28, 0xb1, 0x14, 0x25, 0x96, 0xa2, 0xc4, 0x52, 0x94, 0x58, 0x8a, 0x12, 0x4b, 0x51, 0x62, 0x29, 0x4a, 0x2c, 0x45, 0x89, 0xa5, 0x28, 0xb1, 0x14, 0x25, 0x96, 0xa2, 0xc4, 0x52, 0x94, 0x58, 0x8a, 0x12, 0x4b, 0x51, 0x62, 0xa9, 0xbe, 0xa2, 0xfa, 0xa3, 0xb6, 0x32, 0x14, 0x76, 0x59, 0x57, + 0x58, 0x2e, 0x9e, 0xf8, 0xb4, 0xbc, 0x46, 0x7c, 0x78, 0x19, 0xf5, 0x5c, 0x53, 0xc2, 0x58, 0x19, 0x89, 0xa5, 0xf4, 0xbf, 0x1e, 0x7b, 0xee, 0x7f, 0x7f, 0x4f, 0xa6, 0x98, 0xab, 0xa9, 0x87, 0xc8, 0x37, 0x83, 0xf5, 0x37, 0x07, 0x9d, 0x9d, 0x24, 0x4b, 0xcf, 0x95, 0xbf, 0xe2, 0x85, 0x2f, 0x72, 0xe5, 0x09, 0xa2, 0xc9, 0xc3, 0xfa, 0xe7, 0x8e, 0x3c, 0x59, 0xb7, 0xf6, 0x54, 0xc4, 0x9f, 0xd0, 0x5c, 0x29, 0x57, 0x5d, 0x40, 0x57, 0x85, 0x58, 0x74, 0x2f, 0xea, 0x2e, 0x14, 0x01, 0x94, 0xf0, 0x2d, 0x25, 0x68, 0x2b, 0x76, 0x26, 0x25, 0x9c, 0xa3, 0x84, 0x22, 0x4a, 0xc8, 0xd0, 0x4b, 0xf0, 0xfc, 0xcd, 0xe0, 0xb8, 0x76, 0xa7, 0x9e, 0xab, 0x2f, 0x72, 0xb5, 0x1b, 0xbf, 0x6b, 0x31, 0x4a, 0x18, 0x85, 0x4f, 0xb0, 0xe8, 0xa5, 0xd8, 0xb1, 0x1d, 0x27, 0xf6, 0xf2, 0xeb, 0xf5, 0x3c, 0xf9, 0xa4, + 0x7c, 0x1e, 0xdb, 0x28, 0xa7, 0x27, 0x65, 0xd8, 0x46, 0x11, 0xb6, 0x51, 0x8c, 0x6d, 0x14, 0x63, 0x17, 0xc5, 0xd8, 0x45, 0xa1, 0x7e, 0x6f, 0xac, 0xbf, 0x7c, 0x9f, 0xd5, 0xe0, 0x43, 0x56, 0x83, 0x0f, 0x59, 0x0d, 0xde, 0x61, 0x35, 0xd0, 0xbe, 0x51, 0xf3, 0x21, 0xb5, 0x1e, 0xaf, 0xfd, 0xdb, 0x06, 0xb6, 0xa2, 0xc5, 0x00, 0x45, 0xd8, 0x48, 0x11, 0x36, 0x52, 0x8c, 0x8d, 0x14, 0x63, 0x23, 0xc5, 0xd8, 0x47, 0x31, 0x9a, 0x2f, 0x46, 0xef, 0xc5, 0xe8, 0xbd, 0x18, 0xbd, 0x17, 0xa3, 0xf7, 0x62, 0xf4, 0x5e, 0x8c, 0xde, 0x8b, 0xd1, 0x7b, 0x31, 0x7a, 0x2f, 0x46, 0xef, 0xc5, 0xe8, 0xbd, 0x18, 0xbd, 0x3b, 0xd1, 0xbb, 0x13, 0xbd, 0x3b, 0xd1, 0xbb, 0x13, 0xbd, 0x3b, 0xd1, 0xbb, 0x13, 0xbd, 0x3b, 0xd1, 0xbb, 0x13, 0xbd, 0x3b, 0xd1, 0xbb, 0x13, 0xbd, 0x3b, 0xd1, 0xbb, 0x13, 0xbd, 0x3b, + 0xd1, 0xbb, 0x13, 0xbd, 0x3b, 0xd1, 0xbb, 0x13, 0xbd, 0x3b, 0xd1, 0xbb, 0x13, 0xbd, 0x3b, 0xd1, 0xbb, 0x13, 0xbd, 0x3b, 0xd1, 0xbb, 0x13, 0xbd, 0x3b, 0xd1, 0xbb, 0x13, 0xbd, 0x3b, 0xd1, 0xbb, 0xf3, 0x4f, 0xe5, 0xa5, 0xf7, 0xe2, 0xe1, 0x36, 0xea, 0x5e, 0xee, 0xff, 0xea, 0x3d, 0x1c, 0x11, 0xca, 0xdc, 0xbe, 0x43, 0x5e, 0x52, 0xa1, 0xaf, 0xd7, 0x27, 0x65, 0x0e, 0xf3, 0xfb, 0xb3, 0x91, 0xfd, 0x1c, 0x64, 0x7e, 0x0f, 0xa2, 0xad, 0x52, 0xb4, 0x55, 0xaa, 0x0c, 0x21, 0xf2, 0x1a, 0x06, 0xe3, 0xe5, 0x79, 0x46, 0xbf, 0x94, 0xd1, 0xcf, 0x60, 0xf4, 0xcf, 0xa3, 0xb3, 0x6f, 0x95, 0x85, 0xb2, 0x52, 0x59, 0xc4, 0x7a, 0xbc, 0x5f, 0xbf, 0x33, 0x59, 0xc5, 0xdc, 0x17, 0x10, 0x6b, 0x57, 0x10, 0x6b, 0x57, 0x10, 0x6b, 0x57, 0x10, 0x6b, 0x57, 0x10, 0x6b, 0x57, 0x10, 0x6b, 0x57, 0x10, 0x6b, + 0x57, 0x10, 0x6b, 0x57, 0x10, 0x6b, 0x57, 0x10, 0x6b, 0x57, 0x10, 0x6b, 0x57, 0x10, 0x6b, 0x57, 0x10, 0x6b, 0x57, 0x10, 0x6b, 0x57, 0x10, 0x6b, 0x57, 0x10, 0x6b, 0x57, 0x10, 0x6b, 0x57, 0x10, 0x6b, 0x57, 0x10, 0x6b, 0x57, 0x10, 0x6b, 0x57, 0x10, 0x6b, 0x57, 0x10, 0x6b, 0x57, 0x10, 0x6b, 0x57, 0x10, 0x6b, 0x57, 0x18, 0xea, 0x2c, 0x10, 0x41, 0xf8, 0xe2, 0x4a, 0x7a, 0xe1, 0x42, 0x5b, 0x4e, 0x74, 0x55, 0xac, 0xdf, 0x6d, 0xd2, 0xac, 0xe4, 0x24, 0xd6, 0x91, 0x0b, 0xb5, 0x77, 0x9c, 0x0a, 0x65, 0x81, 0xf1, 0x69, 0x3a, 0xed, 0xf3, 0x76, 0x9f, 0xd7, 0xf9, 0x86, 0x8d, 0x66, 0x2d, 0x15, 0xf8, 0xdd, 0x4a, 0xdd, 0x62, 0x5e, 0x46, 0xa9, 0x59, 0x94, 0xfa, 0x1d, 0xa5, 0xbe, 0x4d, 0xa9, 0xa7, 0x75, 0xc5, 0xfe, 0x42, 0x8e, 0xe6, 0xc9, 0xdd, 0xde, 0xc4, 0xc3, 0x5f, 0xa1, 0xe4, 0xcf, + 0x28, 0xf5, 0x1c, 0xaa, 0x3d, 0x84, 0x6a, 0x0f, 0x31, 0x5e, 0x6f, 0xe2, 0xd5, 0xf3, 0x50, 0xef, 0x21, 0x6a, 0xfa, 0x0c, 0xbb, 0x3c, 0x8c, 0x82, 0x73, 0xc8, 0xd3, 0x9e, 0x55, 0x50, 0x02, 0xb9, 0xda, 0xb3, 0x0a, 0x4a, 0x50, 0xc6, 0x08, 0x3f, 0x62, 0x99, 0x47, 0x89, 0x65, 0x1e, 0x55, 0x66, 0x8b, 0xfa, 0xd4, 0xfa, 0x1d, 0x0a, 0x3e, 0x44, 0xcd, 0x6f, 0xa2, 0xde, 0x43, 0xa8, 0xf7, 0x10, 0xea, 0x3d, 0x84, 0x0d, 0xa5, 0xa0, 0xe0, 0x43, 0x28, 0xf8, 0x10, 0x0a, 0x3e, 0x84, 0x82, 0x0f, 0xa1, 0xe0, 0x43, 0x28, 0xf8, 0x10, 0x0a, 0x3e, 0x84, 0x82, 0x0f, 0xa1, 0xe0, 0x43, 0x28, 0xf8, 0x10, 0x0a, 0x3e, 0x84, 0x82, 0x0f, 0xa1, 0xe0, 0x2c, 0x14, 0x9c, 0x85, 0x82, 0xb3, 0x50, 0x70, 0x16, 0x0a, 0xce, 0x42, 0xc1, 0x59, 0x28, 0x38, 0x0b, 0x05, 0x67, 0xa1, 0xe0, 0x2c, 0x14, 0x9c, 0x85, + 0x82, 0xb3, 0x50, 0x70, 0x16, 0x0a, 0xce, 0x42, 0xc1, 0x59, 0x28, 0x38, 0x0b, 0x05, 0x67, 0xa1, 0xe0, 0x2c, 0x14, 0x9c, 0x85, 0x82, 0xb3, 0x50, 0x70, 0x16, 0x0a, 0xce, 0x42, 0xc1, 0x59, 0x28, 0x38, 0x0b, 0x05, 0x67, 0xa1, 0xe0, 0x2c, 0x14, 0x9c, 0xf5, 0x27, 0xef, 0xac, 0x54, 0x33, 0xff, 0xd5, 0xcc, 0x7f, 0x35, 0xf3, 0x5f, 0xcd, 0xfc, 0x57, 0x33, 0xff, 0xd5, 0xcc, 0x7f, 0x35, 0xf3, 0x5f, 0xcd, 0xfc, 0x57, 0x33, 0xff, 0xd5, 0xcc, 0x7f, 0x35, 0xf3, 0x5f, 0xcd, 0xfc, 0x57, 0x33, 0xff, 0xd5, 0xcc, 0x7f, 0x35, 0xf3, 0x5f, 0xcd, 0xfc, 0x57, 0x33, 0xff, 0xd5, 0xcc, 0x7f, 0x35, 0xf3, 0x5f, 0xcd, 0xfc, 0x57, 0x33, 0xff, 0xd5, 0xcc, 0x7f, 0x35, 0xf3, 0x5f, 0xcd, 0x8a, 0x70, 0x85, 0xd8, 0x64, 0x09, 0x2b, 0xc2, 0x15, 0x6b, 0xb2, 0x08, 0xb7, 0xae, 0x10, 0xd3, 0x58, 0x11, 0xae, + 0xb0, 0x22, 0x5c, 0xb1, 0xa6, 0x89, 0x31, 0xc4, 0x26, 0x73, 0x59, 0x11, 0xae, 0x10, 0x9b, 0x2c, 0x41, 0x2b, 0xff, 0xc2, 0x0f, 0xa5, 0xb0, 0x22, 0x5c, 0x61, 0x45, 0xb8, 0x42, 0x6c, 0x32, 0x9d, 0xd8, 0x64, 0x16, 0xb1, 0xc9, 0x52, 0x56, 0x82, 0x3c, 0x56, 0x82, 0x3c, 0x56, 0x82, 0x3c, 0x56, 0x82, 0x3c, 0x56, 0x82, 0x3c, 0x56, 0x82, 0x3c, 0x56, 0x82, 0x3c, 0x56, 0x82, 0x3c, 0x56, 0x82, 0x3c, 0x56, 0x82, 0x3c, 0x56, 0x82, 0x3c, 0x56, 0x82, 0x3c, 0x56, 0x82, 0x3c, 0x56, 0x82, 0x3c, 0x56, 0x82, 0x3c, 0x56, 0x82, 0x3c, 0x56, 0x82, 0x3c, 0x56, 0x82, 0x3c, 0x56, 0x82, 0x3c, 0xac, 0x35, 0x07, 0x6b, 0xcd, 0xc1, 0x5a, 0x73, 0xb0, 0xd6, 0x1c, 0xac, 0x35, 0x07, 0x6b, 0xcd, 0xc1, 0x5a, 0x73, 0xb0, 0xd6, 0x1c, 0xac, 0x35, 0x07, 0x6b, 0xcd, 0xc1, 0x5a, 0x73, 0xb0, 0xd6, 0x1c, 0xac, + 0x35, 0x07, 0x6b, 0xcd, 0xc1, 0x5a, 0x73, 0xb0, 0xd6, 0x1c, 0xac, 0x35, 0x07, 0x6b, 0xcd, 0xc1, 0x5a, 0x73, 0xb0, 0xd6, 0x1c, 0xac, 0x35, 0x07, 0x6b, 0xcd, 0xc1, 0x5a, 0x73, 0xb0, 0xd6, 0x1c, 0x62, 0x92, 0x47, 0x85, 0x05, 0x55, 0x9e, 0x42, 0x7d, 0x4e, 0x14, 0x57, 0x88, 0x85, 0x5e, 0x34, 0x3e, 0x9b, 0xf5, 0xa3, 0xfe, 0xd9, 0xac, 0xd9, 0x78, 0x68, 0x33, 0xca, 0xff, 0x18, 0x2f, 0xf9, 0x2b, 0x47, 0x0a, 0x51, 0xf9, 0x2f, 0x46, 0xb6, 0x74, 0x9c, 0x71, 0x68, 0x2d, 0x62, 0x51, 0x77, 0x3e, 0xaa, 0xce, 0x31, 0xfc, 0xf0, 0x69, 0xce, 0x7e, 0xee, 0x7a, 0x0e, 0xf6, 0xab, 0x3c, 0x83, 0xa2, 0x0b, 0x50, 0x74, 0x01, 0x4a, 0x2e, 0x40, 0xc9, 0xe7, 0x50, 0x71, 0x25, 0x7e, 0xf8, 0x1b, 0xfc, 0xf0, 0x61, 0xfc, 0xf0, 0xe1, 0xeb, 0x4f, 0x7a, 0x1a, 0xa1, 0xab, 0x1a, 0x45, 0xcb, 0x0f, 0xf4, + 0xd8, 0xc5, 0xf3, 0xd7, 0xd7, 0x5a, 0x9f, 0x5c, 0x80, 0x4f, 0x3e, 0x83, 0x4f, 0x3e, 0x83, 0xaa, 0x0b, 0x50, 0x75, 0x01, 0xaa, 0x2e, 0x40, 0xd1, 0x05, 0x28, 0xba, 0x00, 0x45, 0x17, 0xa0, 0xe8, 0x02, 0x14, 0x5d, 0x80, 0xa2, 0x0b, 0x50, 0x74, 0x01, 0x8a, 0x2e, 0x40, 0xd1, 0x05, 0x28, 0xba, 0x00, 0x45, 0x17, 0xa8, 0x5a, 0x64, 0xd9, 0x93, 0xac, 0x3d, 0x14, 0x7a, 0x41, 0x6f, 0xe8, 0x03, 0x7d, 0x21, 0x0c, 0xfa, 0x41, 0x7f, 0x18, 0x00, 0x03, 0x21, 0x1c, 0x06, 0xc1, 0x60, 0xc0, 0x5b, 0xa1, 0xe8, 0x7c, 0x14, 0x9d, 0x8f, 0xa2, 0xf3, 0x51, 0x74, 0x3e, 0x8a, 0xce, 0x47, 0xd1, 0xf9, 0x28, 0x3a, 0x1f, 0x45, 0xe7, 0xa3, 0xe8, 0x7c, 0x14, 0x9d, 0x8f, 0xa2, 0xf3, 0xff, 0xa4, 0x4f, 0x3e, 0x83, 0x4f, 0x3e, 0xc3, 0x2c, 0x57, 0x32, 0xcb, 0x95, 0xcc, 0x72, 0x25, 0xb3, 0x5c, 0xc9, 0x2c, + 0x57, 0x32, 0xcb, 0x95, 0xcc, 0x72, 0x25, 0xb3, 0x5c, 0xc9, 0x2c, 0xff, 0x2f, 0xee, 0xee, 0x03, 0xbe, 0xa9, 0xba, 0xff, 0xfb, 0xff, 0x37, 0x27, 0x2d, 0x39, 0x27, 0x75, 0x31, 0xc4, 0xc5, 0x90, 0x21, 0x32, 0x04, 0x04, 0x71, 0x82, 0xca, 0x12, 0x15, 0xd9, 0x4b, 0x50, 0x04, 0xdc, 0x28, 0x8a, 0x38, 0x58, 0x0a, 0x88, 0xc8, 0x12, 0x04, 0x2a, 0x22, 0x7b, 0x8f, 0xb6, 0x4c, 0x15, 0x90, 0x55, 0xca, 0x6a, 0xe9, 0x4a, 0x9b, 0xae, 0xb4, 0xa5, 0x05, 0x52, 0x04, 0x12, 0xda, 0x92, 0x6e, 0x4a, 0x0b, 0x04, 0x3f, 0xf7, 0x2b, 0x69, 0x2f, 0xe5, 0xfa, 0x5d, 0xd7, 0x7d, 0xdd, 0xd7, 0x7d, 0xff, 0x7f, 0xf7, 0xf5, 0x7f, 0xfc, 0xff, 0x0f, 0x1f, 0x4f, 0xcf, 0x49, 0x9a, 0x9c, 0x9c, 0x7c, 0xf7, 0x3b, 0x09, 0x49, 0x39, 0xb5, 0x5c, 0x4e, 0x2d, 0x97, 0x53, 0xcb, 0xe5, 0xd4, 0x72, 0x39, 0xb5, 0x5c, + 0x4e, 0x2d, 0x97, 0x53, 0xcb, 0xe5, 0xd4, 0x72, 0x39, 0xb5, 0x5c, 0x4e, 0x2d, 0x97, 0x53, 0xcb, 0xe5, 0xd4, 0x72, 0x39, 0xb5, 0x5c, 0x4e, 0x2d, 0x97, 0x53, 0xcb, 0xe5, 0x6a, 0x35, 0x35, 0xe4, 0x61, 0xfc, 0x39, 0x43, 0x4d, 0x4f, 0xa7, 0xa6, 0x0a, 0xa8, 0xa9, 0xca, 0xea, 0x57, 0x96, 0x2b, 0x18, 0x83, 0x12, 0x18, 0x83, 0x8e, 0xd2, 0x02, 0x56, 0x53, 0x6b, 0x39, 0xd4, 0xda, 0x39, 0x6a, 0x2c, 0x97, 0x1a, 0xcb, 0xa5, 0x45, 0xac, 0xad, 0x7e, 0x97, 0x29, 0xb7, 0xfa, 0x15, 0x65, 0xdf, 0xeb, 0x45, 0x19, 0xd4, 0x60, 0x51, 0xf5, 0xbf, 0x54, 0x8c, 0x64, 0x1c, 0x5f, 0xc4, 0x38, 0xbe, 0x88, 0xda, 0x4b, 0xa0, 0xf6, 0x7c, 0xef, 0xb4, 0x8f, 0x63, 0x3c, 0x3a, 0xc9, 0x78, 0x74, 0x92, 0xda, 0x4b, 0xf6, 0xbf, 0x77, 0x3e, 0x44, 0xe6, 0x33, 0xa6, 0x2f, 0x62, 0x4c, 0x5f, 0xc4, 0xf8, 0x74, + 0x86, 0xda, 0xcc, 0x65, 0x7c, 0x3a, 0x4a, 0x4d, 0xe6, 0x52, 0x93, 0xb9, 0xd4, 0x64, 0x2e, 0x35, 0x99, 0x4b, 0x4d, 0xe6, 0x52, 0x93, 0xb9, 0xd4, 0x64, 0x2e, 0x35, 0x99, 0x4b, 0x4d, 0xe6, 0x52, 0x93, 0xb9, 0xd4, 0x64, 0x2e, 0x35, 0x99, 0x4b, 0x4d, 0xe6, 0x52, 0x93, 0xb9, 0xd4, 0x64, 0x2e, 0x35, 0xe9, 0xa1, 0x26, 0x3d, 0xd4, 0xa4, 0x87, 0x9a, 0xf4, 0x50, 0x93, 0x1e, 0x6a, 0xd2, 0x43, 0x4d, 0x7a, 0xa8, 0x49, 0x0f, 0x35, 0xe9, 0xa1, 0x26, 0x3d, 0xd4, 0xa4, 0x87, 0x9a, 0xf4, 0x50, 0x93, 0x1e, 0x6a, 0xd2, 0x43, 0x4d, 0x7a, 0xa8, 0x49, 0x0f, 0x35, 0xe9, 0xa1, 0x26, 0x3d, 0xd4, 0xa4, 0x87, 0x9a, 0xf4, 0x50, 0x93, 0x1e, 0x6a, 0xd2, 0x43, 0x4d, 0x7a, 0xa8, 0x49, 0x0f, 0x35, 0xe9, 0xa1, 0x26, 0x3d, 0xd4, 0x64, 0x25, 0x35, 0x59, 0x49, 0x4d, 0x56, 0x52, 0x93, 0x95, 0xd4, 0x64, + 0x25, 0x35, 0x59, 0x49, 0x4d, 0x56, 0x52, 0x93, 0x95, 0xd4, 0x64, 0x25, 0x63, 0x53, 0x05, 0x63, 0x53, 0x05, 0x63, 0x53, 0x05, 0x63, 0x53, 0x05, 0x63, 0x53, 0x05, 0x63, 0x53, 0x05, 0x63, 0x53, 0x05, 0x63, 0x53, 0x05, 0x63, 0x53, 0x05, 0x63, 0x53, 0x05, 0x63, 0x53, 0x05, 0x63, 0x53, 0x05, 0x63, 0x53, 0x05, 0x63, 0x53, 0x05, 0x63, 0x53, 0x05, 0x63, 0x53, 0x05, 0x63, 0x53, 0x05, 0x63, 0x53, 0x05, 0x63, 0x53, 0x05, 0x63, 0x53, 0x05, 0x63, 0x53, 0x05, 0x63, 0x53, 0x05, 0x63, 0x53, 0x05, 0x63, 0x53, 0xc5, 0x7f, 0xf3, 0xbb, 0x38, 0x45, 0xb4, 0xb6, 0x22, 0x5a, 0x5b, 0x11, 0xad, 0xad, 0x88, 0xd6, 0x56, 0x44, 0x6b, 0x2b, 0xa2, 0xb5, 0x15, 0xd1, 0xda, 0x8a, 0x68, 0x6d, 0x45, 0xb4, 0xb6, 0x22, 0x5a, 0x5b, 0x11, 0xad, 0xad, 0x88, 0xd6, 0x56, 0x44, 0x6b, 0x2b, 0xa2, 0xb5, 0x15, + 0xd1, 0xda, 0x8a, 0x68, 0x6d, 0x45, 0xb4, 0xb6, 0x22, 0x5a, 0x5b, 0x11, 0xad, 0xad, 0x88, 0xd6, 0x56, 0x44, 0x6b, 0x2b, 0xa2, 0xb5, 0x15, 0xd1, 0xda, 0x8a, 0x18, 0x53, 0x4e, 0xfa, 0xe7, 0xd0, 0x4a, 0x5a, 0x9b, 0x93, 0xd6, 0x76, 0x9d, 0x96, 0x76, 0x95, 0x56, 0x56, 0xe1, 0x5f, 0x33, 0x3a, 0x69, 0x61, 0x55, 0x6b, 0xb4, 0x32, 0x5a, 0xd7, 0x39, 0x5a, 0x95, 0x6f, 0xc5, 0x59, 0x41, 0x6b, 0x72, 0x54, 0x7f, 0x0a, 0x72, 0x29, 0xad, 0x26, 0x9e, 0x56, 0x63, 0xa3, 0xd5, 0x24, 0xd1, 0x5a, 0x7c, 0x99, 0xa5, 0x40, 0xf5, 0xa2, 0x0d, 0x17, 0x70, 0x34, 0x17, 0x6d, 0xb7, 0x8c, 0x23, 0xe6, 0x32, 0xca, 0x6c, 0xe1, 0x48, 0x57, 0x38, 0x52, 0x29, 0xa3, 0xcc, 0xe5, 0x3f, 0x93, 0x50, 0x55, 0x0a, 0xba, 0xcc, 0x08, 0x73, 0xd8, 0xe4, 0xfb, 0xcc, 0x62, 0x47, 0x74, 0x96, 0x83, 0x8c, 0x30, 0x07, + 0xab, 0x57, 0x7a, 0xbe, 0x4f, 0x98, 0xc4, 0xf2, 0x08, 0x49, 0xfe, 0x54, 0xb4, 0x8d, 0xd5, 0xdd, 0x76, 0xdc, 0x9a, 0x86, 0x48, 0x42, 0xfe, 0x74, 0xf3, 0xef, 0x27, 0x9b, 0x02, 0xda, 0x62, 0x01, 0x6d, 0xb1, 0x80, 0xb6, 0x58, 0x40, 0x5b, 0x2c, 0xa0, 0x2d, 0x16, 0xd0, 0x16, 0x0b, 0x68, 0x8b, 0x05, 0xb4, 0xc5, 0x02, 0xda, 0x62, 0x01, 0x6d, 0xb1, 0x80, 0xb6, 0x58, 0x40, 0x5b, 0x2c, 0xa0, 0x2d, 0x16, 0xd0, 0x16, 0x0b, 0x68, 0x8b, 0x05, 0xb4, 0xc5, 0x02, 0xda, 0x62, 0x01, 0x6d, 0xb1, 0x80, 0xb6, 0x58, 0x40, 0x5b, 0x2c, 0xa0, 0x2d, 0x16, 0xd0, 0x16, 0x0b, 0x68, 0x8b, 0x05, 0xb4, 0xc5, 0x02, 0xda, 0x62, 0x01, 0x6d, 0xb1, 0x8c, 0xb6, 0x58, 0x46, 0x5b, 0x2c, 0xa3, 0x2d, 0x96, 0xd1, 0x16, 0xcb, 0x68, 0x8b, 0x65, 0xb4, 0xc5, 0x32, 0xda, 0x62, 0x19, 0x6d, 0xb1, 0x8c, 0x51, 0xe5, + 0xb2, 0xd5, 0xf7, 0x99, 0xc4, 0x6e, 0x94, 0x98, 0xd7, 0xbf, 0xca, 0x4f, 0xf7, 0xf7, 0xf6, 0x6b, 0x94, 0x7d, 0x85, 0xbf, 0xb4, 0x2e, 0xb0, 0xd2, 0xbf, 0xe8, 0x5f, 0xed, 0xdf, 0xa0, 0xb4, 0x6e, 0x52, 0x5a, 0x37, 0xe8, 0xbd, 0x1e, 0x7a, 0xaf, 0x87, 0x12, 0xb9, 0x49, 0x49, 0xdc, 0xa4, 0x24, 0x6e, 0x52, 0x12, 0x37, 0x59, 0x35, 0x8c, 0xa4, 0x34, 0x6e, 0x52, 0x1a, 0x37, 0x29, 0x8d, 0x9b, 0x94, 0xc6, 0x4d, 0x4a, 0xe3, 0x26, 0xa5, 0x71, 0x93, 0xd2, 0xb8, 0x49, 0x69, 0xdc, 0xa4, 0x34, 0x6e, 0x52, 0x1a, 0x37, 0x29, 0x8d, 0x9b, 0x94, 0xc6, 0x4d, 0x4a, 0xc3, 0x4b, 0x69, 0x78, 0x29, 0x0d, 0x2f, 0xa5, 0xe1, 0xa5, 0x34, 0xbc, 0x94, 0x86, 0x97, 0xd2, 0xf0, 0x52, 0x1a, 0x5e, 0x4a, 0xc3, 0x4b, 0x69, 0x78, 0x29, 0x0d, 0x2f, 0xa5, 0xe1, 0xa5, 0x34, 0xbc, 0x94, 0x86, 0x97, 0xd2, 0xf0, + 0x52, 0x1a, 0x5e, 0x4a, 0xc3, 0x4b, 0x69, 0x78, 0x29, 0x0d, 0x2f, 0xa5, 0xe1, 0xa5, 0x34, 0xbc, 0x94, 0x86, 0x97, 0xd2, 0xf0, 0x52, 0x1a, 0x5e, 0x4a, 0xc3, 0x4b, 0x69, 0x78, 0x99, 0xad, 0xdf, 0x61, 0xa6, 0x6e, 0xc7, 0x4c, 0xdd, 0x91, 0x19, 0xba, 0x1d, 0x33, 0x74, 0x37, 0x66, 0xe7, 0x77, 0x98, 0x95, 0x56, 0x31, 0x3b, 0x8f, 0x64, 0x56, 0xee, 0xc2, 0xac, 0xfc, 0x0c, 0xb3, 0x72, 0x2f, 0xf5, 0x91, 0xff, 0x5d, 0xf7, 0xaa, 0x95, 0x9d, 0xd3, 0xbf, 0xb2, 0xcb, 0xf0, 0xb7, 0x4c, 0xdf, 0x7b, 0x89, 0x65, 0xb4, 0xcc, 0x72, 0x4a, 0x27, 0x97, 0xf6, 0xe4, 0xad, 0x5e, 0x83, 0x55, 0xb5, 0x25, 0xd7, 0x9f, 0xed, 0xc9, 0xf7, 0xde, 0x41, 0x05, 0xe3, 0x5e, 0x09, 0xad, 0x32, 0xdf, 0xbf, 0xb2, 0xdb, 0xe2, 0xcf, 0x26, 0xe5, 0x7f, 0x9f, 0xa2, 0xd5, 0xb0, 0xff, 0x83, 0xb6, 0xf3, 0x9f, 0x7a, + 0x57, 0xfb, 0x3a, 0x6d, 0xe7, 0x3a, 0x6d, 0xe7, 0x3a, 0x6d, 0xe7, 0x3a, 0x6d, 0xe7, 0x3a, 0x6d, 0xe7, 0x3a, 0x6d, 0xe7, 0x3a, 0x6d, 0xe7, 0x3a, 0x6d, 0xe7, 0x3a, 0x25, 0xfa, 0x06, 0x25, 0xda, 0x92, 0x12, 0x7d, 0x9c, 0x12, 0x6d, 0x49, 0x89, 0x76, 0xa2, 0x44, 0xdf, 0xa0, 0x44, 0x7f, 0xa2, 0x44, 0x87, 0x51, 0xa2, 0x1d, 0x29, 0xd1, 0x0e, 0x94, 0x68, 0x77, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0xc6, 0x91, 0x12, 0xc6, + 0x91, 0x12, 0x35, 0xd8, 0xff, 0x9e, 0x68, 0x3a, 0xb3, 0x4e, 0x55, 0xb6, 0xf3, 0xad, 0xbb, 0xcb, 0xfd, 0xd9, 0xee, 0x6f, 0xb3, 0x53, 0xd5, 0xac, 0x74, 0xc1, 0x5f, 0x2b, 0x5d, 0xe5, 0x34, 0xed, 0xf7, 0x34, 0xe3, 0x87, 0x8b, 0xd9, 0xe7, 0x2a, 0xb3, 0xcf, 0xd5, 0xbf, 0xad, 0x52, 0xfc, 0xb3, 0xcd, 0xff, 0xb3, 0x99, 0xe6, 0x3f, 0xf3, 0xfe, 0xe2, 0xff, 0xe5, 0x12, 0x65, 0x64, 0xbe, 0xaa, 0x6e, 0xa3, 0x24, 0x4f, 0x2a, 0x97, 0xba, 0x93, 0xf1, 0xf1, 0x92, 0xc9, 0xf7, 0x39, 0x3a, 0x56, 0x38, 0x8c, 0x8f, 0x97, 0x18, 0x1f, 0x2f, 0x31, 0x3e, 0xa6, 0xdf, 0xf2, 0x9b, 0x06, 0x69, 0xbe, 0x3c, 0xe1, 0x5f, 0xdf, 0x39, 0xa8, 0x87, 0xa8, 0xea, 0x91, 0xd7, 0xee, 0x3f, 0x42, 0xd5, 0xfa, 0xce, 0xf7, 0x1a, 0x94, 0x6f, 0xb5, 0x90, 0x49, 0x7d, 0x64, 0x52, 0x1f, 0x99, 0xd4, 0x47, 0xb2, 0xff, + 0xfd, 0xe6, 0x27, 0xa9, 0x93, 0xce, 0xd4, 0x45, 0x17, 0x74, 0xa5, 0x1e, 0xba, 0xa3, 0x87, 0x5c, 0xa4, 0x6e, 0xb2, 0xa8, 0x9b, 0x2c, 0x1e, 0xc5, 0x53, 0xfd, 0xa9, 0x3d, 0x5f, 0xfd, 0x64, 0x32, 0x0a, 0x9f, 0x63, 0x14, 0x3e, 0x47, 0x3d, 0x65, 0x52, 0x4f, 0x99, 0xd4, 0x53, 0x26, 0xf5, 0x94, 0x49, 0x3d, 0x65, 0x52, 0x4f, 0x99, 0xd4, 0x53, 0x26, 0xf5, 0x94, 0x49, 0x3d, 0x65, 0x52, 0x4f, 0x99, 0xd4, 0x53, 0x26, 0xf5, 0x94, 0x49, 0x3d, 0x65, 0x52, 0x4f, 0x99, 0xd4, 0x53, 0xe6, 0x7f, 0xf0, 0x9d, 0xa1, 0x7f, 0x67, 0x14, 0x3e, 0xc7, 0x28, 0x7c, 0xee, 0xff, 0xfa, 0x7b, 0xa3, 0x67, 0x25, 0x8b, 0x3a, 0x75, 0x30, 0x8a, 0x9f, 0x91, 0x68, 0x72, 0xb6, 0xef, 0xb5, 0x9b, 0x53, 0xfe, 0x77, 0xfb, 0x7b, 0xf8, 0x53, 0x69, 0x14, 0x25, 0x9d, 0x58, 0xfd, 0x6f, 0x0a, 0xe2, 0x19, 0xbb, 0xae, + 0x92, 0xa9, 0x6f, 0xfa, 0x5f, 0xfd, 0xb1, 0x71, 0x0f, 0x47, 0xf5, 0xab, 0x2f, 0x05, 0xd5, 0xaf, 0xdf, 0xd8, 0xff, 0xd5, 0xbf, 0xa2, 0xe0, 0x9e, 0x45, 0xea, 0x6e, 0xee, 0x19, 0xc3, 0x3d, 0x93, 0xb9, 0xe7, 0x39, 0xee, 0xe9, 0x5b, 0x23, 0xa6, 0x72, 0xcf, 0x58, 0xee, 0x79, 0x98, 0x7b, 0x86, 0xb3, 0x1e, 0x3c, 0xce, 0x7a, 0xf0, 0x78, 0xf5, 0xbf, 0x21, 0x5f, 0xcf, 0x51, 0x8e, 0x73, 0x94, 0x83, 0x1c, 0xe5, 0x28, 0x47, 0x39, 0xcc, 0x3a, 0xf0, 0x24, 0xeb, 0xc0, 0x93, 0xbe, 0x77, 0x3f, 0xc9, 0xf5, 0x79, 0x64, 0xee, 0x3c, 0xb5, 0x88, 0x36, 0x96, 0xca, 0xe8, 0xec, 0x9b, 0xab, 0x62, 0x68, 0x67, 0x19, 0xb4, 0xb3, 0x58, 0x7f, 0xee, 0x3e, 0xcd, 0x1c, 0x75, 0x86, 0xd5, 0xe9, 0x59, 0x46, 0xeb, 0x1c, 0x32, 0xc6, 0x39, 0x49, 0xa7, 0xbd, 0xd9, 0x69, 0x6f, 0x76, 0x56, 0xa6, 0xbe, 0xf9, + 0xcb, 0x5e, 0xfd, 0x6a, 0xbf, 0x97, 0x76, 0xe7, 0x34, 0x75, 0x56, 0xba, 0xa9, 0x0b, 0xfe, 0xd6, 0xe6, 0x5e, 0x26, 0xcf, 0xf4, 0x94, 0x2b, 0xb4, 0xb9, 0x2b, 0xb4, 0x66, 0x83, 0x91, 0xbb, 0x84, 0xf6, 0x66, 0xf7, 0xbf, 0x16, 0xba, 0x83, 0xed, 0x2e, 0xec, 0x16, 0x7b, 0xa0, 0x53, 0x6e, 0xd0, 0xde, 0xec, 0xb4, 0x37, 0xbb, 0xf1, 0xb4, 0x5c, 0xa7, 0xcd, 0xd9, 0x69, 0x73, 0x76, 0xda, 0x9c, 0x9d, 0x36, 0x67, 0xa7, 0xcd, 0xd9, 0x69, 0x73, 0x76, 0xda, 0x9c, 0x9d, 0x36, 0x67, 0xa7, 0xcd, 0xd9, 0x69, 0x73, 0x76, 0xda, 0x5c, 0x2a, 0x6d, 0x2e, 0x95, 0x36, 0x97, 0x4a, 0x9b, 0x4b, 0xa5, 0xcd, 0xa5, 0xd2, 0xe6, 0x52, 0x69, 0x73, 0xa9, 0xb4, 0xb9, 0x54, 0xda, 0x5c, 0x2a, 0x6d, 0x2e, 0x95, 0x36, 0x97, 0x4a, 0x9b, 0x4b, 0xa5, 0xcd, 0xa5, 0xd2, 0xe6, 0x52, 0x69, 0x73, 0xa9, 0xb4, 0xb9, + 0x54, 0xda, 0x5c, 0x2a, 0x6d, 0x2e, 0x95, 0x36, 0x97, 0x4a, 0x9b, 0x4b, 0xa5, 0xcd, 0xa5, 0xd2, 0xe6, 0x52, 0x69, 0x73, 0xa9, 0xb4, 0xb9, 0x54, 0xda, 0x5c, 0x2a, 0x6d, 0x2e, 0x95, 0x36, 0x97, 0x41, 0x9b, 0xcb, 0xa0, 0xcd, 0x65, 0xd0, 0xe6, 0x32, 0x68, 0x73, 0x19, 0xb4, 0xb9, 0x0c, 0xda, 0x5c, 0x06, 0x6d, 0x2e, 0x83, 0x36, 0x97, 0x41, 0x82, 0xbd, 0x61, 0xfd, 0x08, 0x9f, 0x60, 0x1c, 0x7c, 0xef, 0x62, 0x7f, 0x86, 0xcf, 0xfd, 0xef, 0x66, 0xdf, 0xb0, 0x8e, 0xc7, 0x04, 0x4c, 0xc4, 0x24, 0x4c, 0xc6, 0x97, 0xf8, 0x0a, 0x53, 0x30, 0x15, 0xd3, 0x30, 0x03, 0xdf, 0x62, 0x26, 0x66, 0x61, 0x36, 0xe6, 0x60, 0x2e, 0xbe, 0xc3, 0x3c, 0xcc, 0xc7, 0xf7, 0x58, 0x80, 0x85, 0x58, 0x84, 0x60, 0xfc, 0x80, 0xc5, 0xf8, 0x11, 0x4b, 0xb0, 0x4c, 0x9c, 0xb4, 0x7f, 0x27, 0xed, 0xdf, 0x49, 0xfb, + 0x77, 0xd2, 0xfe, 0x9d, 0xb4, 0x7f, 0x27, 0xed, 0xdf, 0x49, 0xfb, 0x77, 0xd2, 0xfe, 0x9d, 0xb4, 0x7f, 0x27, 0xed, 0xdf, 0x49, 0xfb, 0x77, 0xd2, 0xfe, 0x9d, 0xb4, 0x7f, 0x27, 0xed, 0xdf, 0x49, 0xfb, 0x77, 0xd2, 0xfe, 0x9d, 0xb4, 0x7f, 0x27, 0xed, 0xdf, 0x49, 0xfb, 0x77, 0xd2, 0xfe, 0x9d, 0xb4, 0x7f, 0x27, 0xed, 0xdf, 0x49, 0xfb, 0x77, 0xd2, 0xfe, 0xaf, 0xa8, 0x5a, 0xb4, 0xc9, 0xeb, 0xb4, 0x92, 0x63, 0xb4, 0xc9, 0xeb, 0xb4, 0x49, 0x6f, 0x75, 0x82, 0xf5, 0x7d, 0x8e, 0xb7, 0x8c, 0xf6, 0x78, 0x95, 0xf6, 0x78, 0xd5, 0xff, 0x4e, 0x5f, 0xd5, 0xab, 0x34, 0x71, 0xb4, 0xc7, 0x63, 0xd5, 0xff, 0xc6, 0xa6, 0x8c, 0x96, 0x50, 0xe6, 0x7b, 0xd5, 0xfb, 0xcf, 0x57, 0x45, 0xdd, 0xd5, 0xef, 0xdd, 0x97, 0xf8, 0xdf, 0x5d, 0x72, 0xb1, 0x5e, 0xca, 0x63, 0x5e, 0x79, 0x9c, 0xb6, 0x54, 0xf5, + 0xef, 0x95, 0x7c, 0x9f, 0x0a, 0x8e, 0xe7, 0x08, 0x47, 0xab, 0x8f, 0xe0, 0xf4, 0x7d, 0x9a, 0x40, 0xfd, 0xa8, 0xca, 0x78, 0xec, 0x3f, 0x78, 0xbc, 0x20, 0x29, 0x36, 0xdd, 0x21, 0x05, 0xa6, 0x3b, 0xc9, 0x48, 0x77, 0x71, 0x2e, 0x35, 0xd9, 0xd6, 0x42, 0x6d, 0xd4, 0x61, 0x04, 0xbc, 0x9b, 0xa3, 0xd5, 0x95, 0x33, 0xa6, 0x7b, 0xb8, 0x7c, 0x2f, 0xee, 0xa3, 0x9f, 0xde, 0x8f, 0x07, 0xd8, 0xaf, 0x87, 0xfa, 0x68, 0xc0, 0xfd, 0x1a, 0xb2, 0x7d, 0x90, 0xf3, 0x6d, 0x24, 0x99, 0xa6, 0xc6, 0x5c, 0x6e, 0xc2, 0xe5, 0xa6, 0x78, 0x88, 0x91, 0xb3, 0x19, 0xdb, 0x87, 0xd1, 0x5c, 0xa2, 0x4d, 0x2d, 0xd8, 0xb6, 0x44, 0x2b, 0x39, 0x6b, 0x7a, 0x84, 0xdb, 0xb5, 0x66, 0xbf, 0x0d, 0x59, 0xfd, 0x51, 0xb1, 0x99, 0xa3, 0xe4, 0xba, 0xf9, 0xa4, 0xdc, 0x30, 0xc7, 0xb3, 0x4e, 0xb1, 0x21, 0x93, 0xfd, 0x53, + 0x22, 0xe6, 0x0b, 0x52, 0x64, 0xbe, 0x08, 0x17, 0xdc, 0xb8, 0x24, 0xa7, 0xcd, 0xb9, 0x6c, 0xf3, 0x90, 0x8f, 0xcb, 0x72, 0xdc, 0xec, 0x61, 0x5b, 0x80, 0x42, 0x14, 0x89, 0xd7, 0x5c, 0xcc, 0x75, 0x25, 0xe2, 0x30, 0x97, 0xa2, 0x8c, 0x0c, 0x77, 0x85, 0xcb, 0xe5, 0xac, 0x18, 0xaf, 0xa2, 0x82, 0xdb, 0x54, 0xe2, 0x1a, 0xae, 0xe3, 0x86, 0xe4, 0x99, 0xbd, 0x6c, 0x6f, 0xe2, 0x0f, 0xfe, 0x2e, 0x52, 0x14, 0xa0, 0xc4, 0x1b, 0x60, 0x12, 0x67, 0x80, 0xc6, 0xd6, 0xcc, 0xe5, 0x00, 0x04, 0xca, 0x99, 0x80, 0x1a, 0xe2, 0x09, 0xb0, 0x70, 0x9d, 0x55, 0xf2, 0x02, 0x82, 0xb8, 0xae, 0xab, 0xe4, 0x07, 0xbc, 0xc2, 0x75, 0xbd, 0xd8, 0xef, 0xcd, 0xdf, 0xfb, 0xb0, 0xed, 0x2b, 0xee, 0x80, 0x7e, 0x6c, 0xfb, 0x4b, 0x7a, 0xc0, 0x00, 0x6e, 0x3b, 0x10, 0x83, 0xe4, 0x46, 0xc0, 0x60, 0xf1, 0x06, 0x16, + 0xc8, 0x99, 0xc0, 0x42, 0xf1, 0x5a, 0x9e, 0x90, 0xcb, 0x96, 0x91, 0x18, 0x85, 0x77, 0xe4, 0x94, 0x4e, 0xeb, 0xd6, 0x83, 0xe5, 0xba, 0xbe, 0x81, 0xed, 0x5e, 0xfc, 0x86, 0x70, 0x44, 0x89, 0x57, 0x4f, 0x44, 0x26, 0xb2, 0x90, 0x27, 0x5e, 0xc3, 0x8a, 0x7b, 0xe9, 0xf5, 0x94, 0xb1, 0xd1, 0x94, 0x6d, 0x1b, 0xb0, 0x8e, 0x32, 0x46, 0x70, 0xfd, 0x44, 0x7c, 0xc5, 0xfe, 0x74, 0x2c, 0x92, 0x3c, 0x23, 0x5a, 0x8a, 0x8d, 0x73, 0x72, 0xc6, 0xc8, 0x95, 0x54, 0x23, 0x0f, 0xc5, 0x28, 0x41, 0x29, 0xbd, 0x74, 0x29, 0xeb, 0xac, 0xe3, 0x8c, 0xfe, 0x91, 0x6c, 0xa3, 0xe4, 0xb4, 0xf5, 0x24, 0x62, 0xe5, 0x8c, 0x35, 0x8e, 0xcb, 0xf1, 0x48, 0x40, 0x22, 0xec, 0x20, 0x93, 0x5b, 0x53, 0xd9, 0xa6, 0xc9, 0x59, 0xab, 0x43, 0x32, 0x83, 0x0e, 0xd3, 0x06, 0x23, 0x10, 0x47, 0x56, 0x6f, 0x41, 0x6b, 0xfa, + 0x50, 0x89, 0xec, 0xa3, 0x35, 0x6d, 0xa3, 0x35, 0x65, 0xd0, 0x9a, 0xde, 0xa7, 0x35, 0x2d, 0x35, 0xd5, 0xf9, 0x63, 0x2a, 0xb5, 0xfe, 0x94, 0xa9, 0xcd, 0x1f, 0xfb, 0x4c, 0x6d, 0x55, 0x7d, 0x6a, 0x79, 0x18, 0xad, 0xbc, 0x87, 0xe9, 0x29, 0x99, 0x62, 0x7a, 0x5a, 0xbe, 0xa6, 0x95, 0xbf, 0x4d, 0x29, 0x3f, 0x4a, 0x29, 0xbe, 0x18, 0xd0, 0xfb, 0x8f, 0xe1, 0x94, 0xdc, 0x13, 0x94, 0xda, 0x02, 0x4a, 0xab, 0x01, 0xa5, 0xd2, 0xd8, 0xe2, 0xfb, 0xee, 0x40, 0x56, 0x81, 0x94, 0x48, 0x2f, 0x4a, 0xa4, 0x03, 0x25, 0xa0, 0x28, 0x01, 0x0b, 0xcf, 0xdc, 0x6e, 0x34, 0x55, 0x8d, 0x8d, 0x89, 0x7f, 0x54, 0x1a, 0x8b, 0xfe, 0xc8, 0xe6, 0x59, 0x0e, 0xe5, 0xd9, 0xb4, 0xe5, 0xec, 0xbf, 0xe0, 0x4c, 0xfb, 0x72, 0x96, 0x6f, 0x72, 0x96, 0x11, 0xfe, 0x4f, 0xb4, 0x1e, 0x96, 0xc7, 0x82, 0x22, 0xfe, 0xa8, + 0xa4, 0xdf, 0x95, 0xa9, 0x3b, 0x39, 0xcb, 0x10, 0xce, 0x6e, 0x27, 0x67, 0x77, 0xcc, 0x54, 0x47, 0x59, 0x39, 0xbb, 0x1f, 0x38, 0xab, 0xcd, 0xe6, 0xfd, 0x72, 0xcd, 0x1c, 0x21, 0xd7, 0x38, 0x9b, 0x39, 0x3c, 0xfa, 0x61, 0x1e, 0xc1, 0xcb, 0x23, 0x98, 0x28, 0xc3, 0x52, 0x8e, 0x1e, 0xc5, 0xd1, 0xf7, 0x70, 0xc4, 0x54, 0xab, 0xef, 0xf3, 0x3a, 0x0d, 0x78, 0xbe, 0x57, 0x4d, 0x81, 0x72, 0x8d, 0xe7, 0x5b, 0xc6, 0xd1, 0xf2, 0x38, 0x5a, 0x19, 0xbd, 0x25, 0x81, 0xa3, 0x15, 0xd0, 0x9a, 0x93, 0x38, 0x62, 0x21, 0x7d, 0xae, 0xd0, 0xf7, 0x0d, 0xe0, 0x1c, 0xb9, 0x92, 0x23, 0x57, 0xd2, 0x92, 0x4b, 0x39, 0x7a, 0x31, 0x2d, 0xa8, 0x98, 0xe7, 0x5b, 0x12, 0xd0, 0x5d, 0x2a, 0x03, 0x7a, 0xc8, 0x75, 0x5a, 0x4d, 0x82, 0xbf, 0xc5, 0xf8, 0x5a, 0xcb, 0x20, 0x6e, 0x53, 0xfd, 0xbd, 0xe8, 0x3c, 0x7f, + 0x37, 0xcf, 0xff, 0x1a, 0x67, 0x90, 0xc3, 0x19, 0x14, 0x70, 0x06, 0x1e, 0xff, 0x73, 0xb8, 0x9d, 0xe7, 0xb0, 0xb9, 0xfa, 0x39, 0x1c, 0xe5, 0x39, 0xe8, 0x3c, 0x6a, 0x30, 0x8f, 0xb8, 0xe9, 0x96, 0xe7, 0x30, 0xb7, 0xea, 0x39, 0x28, 0x33, 0xcf, 0x21, 0x80, 0x23, 0x94, 0x55, 0x3f, 0x87, 0xdf, 0x78, 0x0e, 0x0e, 0xff, 0x73, 0x68, 0xc3, 0x73, 0x18, 0xcf, 0x91, 0x62, 0x79, 0x0e, 0x91, 0x1c, 0x6d, 0x15, 0x47, 0xdb, 0x41, 0x7d, 0x95, 0x73, 0xb4, 0x28, 0x9e, 0xc3, 0x0b, 0x1c, 0xf1, 0xb8, 0xe9, 0x09, 0x65, 0xa1, 0xbe, 0x86, 0x50, 0x5f, 0x76, 0xea, 0xcb, 0x46, 0x7d, 0x7d, 0x62, 0x7a, 0x99, 0xd5, 0x58, 0x7f, 0x75, 0xb7, 0x69, 0xa0, 0xba, 0xcf, 0x34, 0x58, 0xdd, 0xcd, 0xa3, 0x85, 0xf3, 0x7c, 0xa6, 0xf8, 0x9f, 0x4f, 0xef, 0x3f, 0x4e, 0xf1, 0x5c, 0x36, 0xf2, 0x5c, 0xe2, 0x39, 0x83, + 0x01, 0x01, 0x3f, 0x28, 0xbd, 0xea, 0xdb, 0xbb, 0x94, 0x85, 0xe7, 0xd3, 0x97, 0xe7, 0x33, 0x86, 0xfa, 0xcc, 0xa6, 0x3e, 0x67, 0x50, 0x9f, 0x93, 0xa8, 0xcf, 0x57, 0xa9, 0xcf, 0x51, 0x9c, 0xe9, 0xb3, 0x9c, 0x69, 0x7b, 0x5a, 0x6f, 0x5f, 0xce, 0x76, 0x30, 0x67, 0xbb, 0x81, 0xb3, 0x9d, 0x49, 0x7d, 0xee, 0xa6, 0x2e, 0x37, 0x58, 0x1d, 0x2a, 0x88, 0x7a, 0x1c, 0x4f, 0x8b, 0xeb, 0xab, 0x74, 0xce, 0x7c, 0x32, 0x67, 0x7e, 0x84, 0x33, 0x3f, 0xc2, 0x99, 0xcf, 0xe3, 0xac, 0x9d, 0x9c, 0xf5, 0x02, 0xce, 0xba, 0x2b, 0x47, 0xec, 0xa3, 0x42, 0xb8, 0xc5, 0x66, 0x75, 0x45, 0x42, 0x54, 0xb9, 0x24, 0xa9, 0x4a, 0xd9, 0xab, 0xae, 0xe1, 0xa6, 0x44, 0x30, 0xe2, 0x65, 0x50, 0x6f, 0x11, 0x26, 0x5d, 0x92, 0xb9, 0xb7, 0xd3, 0x74, 0x1b, 0xfb, 0xb7, 0xe3, 0x4e, 0xb9, 0xce, 0xf3, 0xf7, 0xfc, 0x97, + 0xd1, 0x6f, 0x29, 0xa3, 0x9f, 0x93, 0xd1, 0x2f, 0xf7, 0x96, 0xd1, 0xef, 0x27, 0x46, 0xbf, 0x9f, 0xfe, 0xcb, 0xe8, 0x57, 0xe6, 0x1f, 0xfd, 0x1a, 0xb3, 0xfd, 0xdb, 0xa8, 0xf7, 0xb7, 0x11, 0xef, 0xaf, 0xd1, 0xee, 0x32, 0xa3, 0x5d, 0x59, 0xf5, 0x68, 0x77, 0xd1, 0xff, 0x3a, 0xfc, 0x93, 0x94, 0xf3, 0x33, 0x94, 0x7f, 0x47, 0x74, 0x66, 0xd5, 0xd3, 0x05, 0x3d, 0x28, 0xe7, 0xaa, 0x7f, 0x7b, 0xe8, 0x7b, 0x1d, 0x31, 0x8d, 0x1a, 0x2d, 0x33, 0x1f, 0x92, 0x10, 0x6a, 0xb5, 0xcc, 0x7c, 0x44, 0x22, 0xcc, 0x91, 0x88, 0x92, 0x70, 0x46, 0xc9, 0xdd, 0xe6, 0x68, 0xf6, 0x13, 0x65, 0xaf, 0x39, 0x4d, 0x52, 0x18, 0x29, 0x77, 0xd3, 0xbe, 0x32, 0xcd, 0x67, 0xb9, 0xfc, 0x8f, 0xa3, 0xa5, 0xf3, 0xef, 0x46, 0xcb, 0xbf, 0x1f, 0x29, 0xcb, 0x18, 0x1d, 0x2f, 0xfa, 0x47, 0xc4, 0xaa, 0x91, 0xf0, 0xa7, + 0x3f, 0x47, 0xc2, 0xaa, 0x51, 0xb0, 0x8c, 0x11, 0xb0, 0xec, 0x96, 0x11, 0xf0, 0x22, 0xed, 0xb7, 0x80, 0x11, 0xb0, 0x8c, 0x11, 0xf0, 0xa7, 0xea, 0x11, 0x30, 0x86, 0xba, 0xb7, 0xd3, 0x96, 0xe3, 0x19, 0x09, 0xbd, 0xd5, 0x23, 0xe1, 0xf2, 0xea, 0x91, 0x30, 0xa3, 0x7a, 0x24, 0x2c, 0x64, 0x24, 0x2c, 0x63, 0x24, 0x2c, 0xa3, 0x5d, 0x5c, 0x61, 0x24, 0x2c, 0x0b, 0xf8, 0x41, 0xd2, 0x68, 0x1b, 0x36, 0xda, 0xba, 0x83, 0x51, 0x31, 0x97, 0x51, 0xb1, 0x2c, 0x90, 0xfa, 0x62, 0x64, 0x4c, 0x60, 0x64, 0x4c, 0x60, 0x64, 0x4c, 0xa0, 0xcd, 0x6c, 0xa1, 0xcd, 0xfc, 0x40, 0x9b, 0x29, 0xd3, 0x07, 0x4a, 0x04, 0xa3, 0xe4, 0x6e, 0x46, 0xc9, 0x9d, 0x8c, 0x92, 0x17, 0x69, 0x43, 0x17, 0x69, 0x43, 0x17, 0x19, 0x25, 0x2f, 0x32, 0x4a, 0x96, 0x31, 0x4a, 0x96, 0x31, 0x4a, 0x96, 0xd1, 0x02, 0xca, 0x68, + 0x53, 0x65, 0x8c, 0x92, 0x65, 0x8c, 0x92, 0xc5, 0xd5, 0xa3, 0x64, 0x31, 0xa3, 0x64, 0x71, 0xf5, 0x28, 0x59, 0x46, 0x3b, 0x2b, 0xbb, 0x65, 0x94, 0xf4, 0xf5, 0x8e, 0x52, 0x46, 0xc9, 0x8b, 0xb4, 0xb9, 0x2b, 0xd6, 0x5f, 0x25, 0xc4, 0xba, 0x1b, 0x7b, 0x24, 0xc9, 0x7a, 0x88, 0x31, 0x24, 0x1c, 0x87, 0x11, 0x81, 0x63, 0x92, 0xcc, 0x88, 0x99, 0x51, 0x3d, 0x62, 0x3a, 0x19, 0x31, 0x9d, 0xb4, 0xd1, 0xdc, 0x7f, 0x3a, 0x62, 0x56, 0x8d, 0x96, 0x97, 0x69, 0xb7, 0x65, 0xb4, 0xdb, 0x32, 0x46, 0x4a, 0xaf, 0xaa, 0x4b, 0xbb, 0xb5, 0xd3, 0x02, 0x1d, 0xd5, 0xa3, 0x64, 0x38, 0xad, 0xce, 0x46, 0xdb, 0x0d, 0xa1, 0xb7, 0xfd, 0xcc, 0x6c, 0x7d, 0x89, 0x19, 0xfa, 0x14, 0xb5, 0x5e, 0x4a, 0x8d, 0x97, 0x52, 0xd3, 0xc7, 0x03, 0xba, 0xaa, 0xdb, 0x29, 0x31, 0x7b, 0x60, 0x07, 0x39, 0x15, 0xc8, 0x5a, + 0xd9, 0xf2, 0x8e, 0xba, 0x8d, 0x33, 0xae, 0xe0, 0x8c, 0x93, 0xad, 0xf7, 0x49, 0x34, 0x67, 0xec, 0x1b, 0xe5, 0xd2, 0x94, 0xc1, 0x91, 0x93, 0x68, 0xd3, 0xe5, 0x1c, 0x35, 0x95, 0xa3, 0xe6, 0x73, 0x54, 0x3b, 0x6d, 0xcd, 0x43, 0xdf, 0xba, 0x83, 0xbe, 0xd5, 0x94, 0x7b, 0xb8, 0x55, 0x23, 0x7a, 0x46, 0x34, 0x8f, 0x1f, 0xcf, 0x2d, 0xf3, 0xb9, 0xe5, 0x35, 0x6e, 0x59, 0x4c, 0x4b, 0x0f, 0xe1, 0x96, 0xe7, 0xfe, 0xe9, 0xe7, 0x90, 0xf7, 0x33, 0x0f, 0x47, 0x20, 0x4a, 0x4e, 0xd0, 0xd2, 0xce, 0xd3, 0xca, 0x7c, 0x2d, 0x20, 0x9f, 0x5a, 0x3f, 0x45, 0xad, 0xe7, 0x50, 0xeb, 0x59, 0xd4, 0xf6, 0x16, 0x7f, 0x4d, 0xf7, 0xe7, 0xfa, 0x41, 0xfe, 0x9a, 0xfd, 0xeb, 0x33, 0xcb, 0xef, 0xc8, 0x11, 0x6a, 0xf0, 0x10, 0xe7, 0x5c, 0xcc, 0x19, 0x94, 0x70, 0xbe, 0xa5, 0xea, 0x0e, 0x1e, 0xf9, 0x12, 0x8f, + 0x7c, 0x86, 0x47, 0x76, 0x73, 0x9e, 0x97, 0x79, 0x74, 0x37, 0x25, 0x90, 0xcf, 0x58, 0xe2, 0xe1, 0x68, 0x4e, 0x8e, 0x76, 0x95, 0xa3, 0xe5, 0x71, 0xb4, 0x8b, 0xdc, 0xf3, 0x22, 0xf7, 0x2c, 0xe4, 0x9e, 0x2e, 0x35, 0x95, 0x5e, 0x5d, 0x46, 0x4f, 0x0e, 0x33, 0x69, 0xf4, 0xa2, 0x40, 0x59, 0xec, 0x7f, 0xc6, 0xb7, 0xc9, 0x5c, 0x7a, 0xf1, 0x5c, 0x8e, 0xea, 0x5b, 0xbf, 0x78, 0xe9, 0xc1, 0x5e, 0x7a, 0xb0, 0x97, 0xa3, 0x7b, 0xe9, 0xc1, 0xe7, 0xe9, 0xc1, 0x97, 0xe9, 0xc1, 0x5e, 0x7a, 0xb0, 0x97, 0x9e, 0xeb, 0xa5, 0xe7, 0x7a, 0xe9, 0xb9, 0xbe, 0x35, 0x8b, 0x97, 0x35, 0xcb, 0x22, 0x7a, 0xad, 0x97, 0x5e, 0xeb, 0x65, 0xad, 0x72, 0x9d, 0x9e, 0xeb, 0xa5, 0xe7, 0x7a, 0x39, 0xab, 0xd5, 0xf4, 0x5e, 0x2f, 0xbd, 0xd7, 0x4b, 0xef, 0xf5, 0xd0, 0x73, 0xbd, 0x9c, 0x65, 0x71, 0xf5, 0xbf, 0x17, + 0xcb, 0x36, 0x75, 0x92, 0x5f, 0x58, 0xab, 0xfd, 0x66, 0x7a, 0x8e, 0xed, 0xf3, 0x6c, 0x7d, 0xdf, 0x59, 0xe7, 0xfb, 0x7e, 0x0a, 0xdf, 0x6f, 0x3e, 0x0c, 0x61, 0x16, 0x7c, 0x83, 0xeb, 0x47, 0xc8, 0x2f, 0xf4, 0x5e, 0x5f, 0xcf, 0x9d, 0x4b, 0xcf, 0x9d, 0x4b, 0xaf, 0x2d, 0xa2, 0xd7, 0xce, 0x35, 0xc7, 0xd0, 0xe3, 0x62, 0x91, 0x8c, 0x4c, 0xae, 0x3b, 0x85, 0x0b, 0xac, 0x59, 0x2e, 0xc2, 0x05, 0x37, 0x2e, 0x51, 0xd6, 0xb9, 0x6c, 0x99, 0xef, 0xe9, 0xb5, 0x5e, 0xd6, 0x38, 0x8b, 0xe8, 0xb9, 0x5e, 0x7a, 0xae, 0x97, 0x9e, 0xeb, 0x5b, 0xdf, 0x2c, 0xa2, 0xe7, 0x66, 0xb1, 0xae, 0x59, 0xc4, 0xba, 0xe6, 0x3a, 0xeb, 0x9a, 0xeb, 0xf4, 0x62, 0x2f, 0xbd, 0xd8, 0x4b, 0x0f, 0xf6, 0xd2, 0x83, 0xbd, 0xac, 0x65, 0xae, 0xd3, 0x8b, 0x7d, 0xeb, 0x17, 0x2f, 0xbd, 0xd7, 0x4b, 0xef, 0xcd, 0xa2, 0xee, + 0xb2, 0xe8, 0xb5, 0xbe, 0x5e, 0xea, 0xa5, 0x87, 0x7a, 0xe9, 0x9d, 0x5e, 0xea, 0xaa, 0x88, 0xf6, 0xf5, 0x33, 0xed, 0xeb, 0x67, 0x7a, 0xe2, 0x65, 0x7a, 0x60, 0x25, 0x3d, 0xb0, 0x92, 0x1e, 0x58, 0x49, 0xaf, 0x9b, 0x4b, 0xaf, 0x2b, 0xf2, 0xcf, 0x87, 0x6d, 0xd0, 0xcd, 0xbf, 0xbe, 0xf0, 0xd2, 0x73, 0xbc, 0xf4, 0x98, 0x2c, 0xd6, 0x15, 0xd1, 0xac, 0x2b, 0xa2, 0x59, 0x57, 0x44, 0xb3, 0xae, 0x88, 0x66, 0x5d, 0x11, 0x4d, 0x0f, 0x2a, 0xa3, 0x07, 0x95, 0xd1, 0x7b, 0xc2, 0xe8, 0x3d, 0x61, 0xf4, 0x9e, 0x30, 0x7a, 0x4f, 0x98, 0xf5, 0x08, 0x3d, 0x2c, 0x52, 0xbc, 0xf4, 0x9c, 0xf3, 0xf4, 0x9c, 0xf3, 0xf4, 0x9c, 0xcb, 0xf4, 0x1c, 0x2f, 0x3d, 0xc7, 0x4b, 0xcf, 0xf1, 0xd2, 0x73, 0xbc, 0xf4, 0x1c, 0x2f, 0x33, 0xf8, 0x75, 0x7a, 0x8f, 0x97, 0xde, 0xe3, 0xa1, 0x6d, 0xef, 0x57, 0xef, 0x52, + 0xe3, 0x27, 0xa8, 0xf1, 0x5c, 0x6a, 0x7c, 0x01, 0xb5, 0x3d, 0x8d, 0xda, 0x9e, 0x46, 0x6d, 0xdf, 0xf8, 0xbb, 0xda, 0xfe, 0x9f, 0xd7, 0xf0, 0x86, 0x5b, 0x6a, 0xf8, 0xca, 0x2d, 0x35, 0xbc, 0xed, 0xcf, 0x1a, 0xf6, 0xd5, 0x6e, 0x5b, 0xff, 0x4a, 0xbc, 0xd8, 0xff, 0xaf, 0x01, 0x3b, 0xb1, 0x6e, 0x7e, 0x56, 0x0e, 0x50, 0xbb, 0x7b, 0xa9, 0xdd, 0x03, 0xd4, 0xee, 0xe1, 0xbf, 0xcd, 0xe7, 0xd4, 0xee, 0x4e, 0x6a, 0x77, 0x3f, 0xb5, 0xbb, 0x9f, 0x9a, 0x9d, 0x46, 0xcd, 0x4e, 0xa3, 0x56, 0xa7, 0x51, 0xab, 0xb9, 0xd4, 0x6a, 0x2e, 0xb5, 0x9a, 0xfb, 0x0f, 0xb5, 0xf9, 0xf7, 0x35, 0xb9, 0xe1, 0xbf, 0xd4, 0xe4, 0x06, 0x6a, 0x71, 0x03, 0xb5, 0x78, 0x85, 0x5a, 0xbc, 0xf2, 0x4f, 0x6a, 0xf1, 0xca, 0xdf, 0xd5, 0xe2, 0x3f, 0xd6, 0x5e, 0x19, 0xb5, 0xb7, 0x9d, 0xda, 0xdb, 0x4e, 0x6d, 0x4d, 0x33, + 0xea, 0x52, 0x33, 0xff, 0xac, 0xb6, 0xa2, 0x91, 0x2b, 0xe1, 0xd4, 0x56, 0x38, 0xb5, 0x15, 0x4e, 0x6d, 0x85, 0x53, 0x5b, 0xe1, 0xd4, 0xd2, 0x09, 0x6a, 0xe9, 0x04, 0xb5, 0x74, 0x82, 0x5a, 0x3a, 0x41, 0x2d, 0xe5, 0xfa, 0x6b, 0xe9, 0x9f, 0xd7, 0xcc, 0x15, 0x7f, 0xcd, 0x38, 0x99, 0x4b, 0x1b, 0x51, 0x1b, 0xa3, 0xe9, 0x7f, 0x15, 0x8c, 0x65, 0x5f, 0xfb, 0xfb, 0x5e, 0x73, 0x59, 0xee, 0x1b, 0x71, 0xe8, 0xc9, 0x3b, 0xab, 0x33, 0x8d, 0xad, 0xfa, 0x5f, 0xf4, 0x97, 0x54, 0x7f, 0x46, 0xc2, 0xf7, 0x09, 0xfe, 0x49, 0x8c, 0x25, 0x6b, 0x68, 0xf3, 0xe5, 0xfe, 0xf5, 0x75, 0xd5, 0x6a, 0xa8, 0xac, 0x7a, 0x25, 0xe4, 0xa1, 0xe7, 0x5f, 0xa4, 0xe7, 0x9f, 0xe6, 0x59, 0xa5, 0x33, 0x7e, 0xf8, 0xfe, 0xa5, 0xfd, 0x48, 0x9e, 0xd9, 0x60, 0xc6, 0x91, 0x7c, 0x46, 0x02, 0xff, 0xb3, 0x60, 0xdc, 0x2b, + 0x62, 0x34, 0xf0, 0x9d, 0xc5, 0x77, 0xea, 0x11, 0x7a, 0xf9, 0x59, 0x7a, 0x79, 0x3a, 0xbd, 0x3c, 0x9f, 0xf9, 0xf9, 0x22, 0xf3, 0xf3, 0x45, 0xea, 0x3c, 0x85, 0x24, 0x92, 0xc3, 0x19, 0xa5, 0xfb, 0x7b, 0x6f, 0x1b, 0xea, 0xfd, 0x51, 0x46, 0xdc, 0x1e, 0xfe, 0x5f, 0x66, 0xf1, 0xfd, 0xdb, 0xa2, 0x32, 0xce, 0x24, 0x8f, 0x5e, 0x96, 0x4e, 0x7d, 0xa4, 0x50, 0x07, 0x29, 0xa4, 0x85, 0x33, 0xa4, 0x85, 0x33, 0xf4, 0xaa, 0x64, 0xea, 0x23, 0x85, 0xb9, 0xf0, 0xa2, 0x7f, 0x3c, 0x0a, 0x94, 0x64, 0xff, 0x9c, 0x67, 0xe5, 0x72, 0x6f, 0xf4, 0x65, 0xd4, 0xeb, 0x2f, 0x39, 0xf4, 0x94, 0x7c, 0xcb, 0x25, 0xc9, 0xd1, 0x1f, 0x93, 0x12, 0x7a, 0x42, 0x32, 0x65, 0x7b, 0x8a, 0xb2, 0x3d, 0x45, 0xd9, 0x9e, 0xa2, 0x6c, 0x4f, 0x51, 0xb6, 0xa7, 0x68, 0xe9, 0xe9, 0xb4, 0xf4, 0x74, 0x5a, 0x7a, 0xbe, 0xbf, + 0x35, 0x3b, 0x24, 0x47, 0x35, 0xe3, 0x8c, 0x7f, 0xe7, 0x8c, 0x93, 0x38, 0xe3, 0x4b, 0x9c, 0xb1, 0x9b, 0x33, 0x76, 0x73, 0xc6, 0xfb, 0x38, 0xdb, 0x23, 0x9c, 0x6d, 0x2e, 0x67, 0x5b, 0xc4, 0x59, 0x5e, 0xf6, 0xfd, 0x66, 0x28, 0x67, 0x99, 0xcd, 0x59, 0x26, 0x71, 0x96, 0xfb, 0x38, 0xcb, 0x7d, 0x9c, 0xa5, 0x9d, 0xb3, 0xb4, 0x73, 0x96, 0x71, 0x9c, 0xe5, 0x3e, 0x72, 0x8c, 0x97, 0x1c, 0xe3, 0xe5, 0x6c, 0xdd, 0x9c, 0xed, 0x79, 0xce, 0x36, 0x8e, 0xb3, 0x8d, 0xe3, 0x6c, 0xdd, 0x9c, 0xe1, 0x25, 0xca, 0xec, 0x3a, 0x67, 0x17, 0xc7, 0xd9, 0xc5, 0x72, 0x76, 0xb1, 0x9c, 0x5d, 0x2c, 0x67, 0x17, 0xcb, 0xd9, 0xc5, 0x72, 0x76, 0x49, 0x9c, 0x5d, 0x12, 0x67, 0x77, 0x89, 0xb3, 0xcb, 0x55, 0xb7, 0x33, 0x26, 0x1f, 0xe2, 0xec, 0x66, 0x71, 0x06, 0x36, 0x66, 0x83, 0x45, 0x94, 0xd7, 0xaa, 0xea, + 0x4f, 0x3c, 0xf8, 0xd6, 0x24, 0x31, 0x9c, 0x91, 0x9d, 0x32, 0xf8, 0xca, 0x37, 0x2e, 0xf3, 0xdc, 0x2f, 0x73, 0xf4, 0x1b, 0xac, 0x06, 0xff, 0x77, 0xef, 0xd5, 0x84, 0x95, 0x7b, 0x53, 0xd5, 0xce, 0x7f, 0x6f, 0x83, 0x96, 0x53, 0xcc, 0x11, 0x32, 0xfd, 0xdf, 0xe0, 0x57, 0xfd, 0xef, 0x52, 0xb9, 0x65, 0x0e, 0xb7, 0x2c, 0xa1, 0x6c, 0xcf, 0x50, 0xd3, 0xd7, 0x54, 0x1d, 0x8e, 0x7f, 0x8d, 0xf2, 0xb9, 0xc2, 0x63, 0x5c, 0xa3, 0x26, 0xaf, 0x56, 0x7f, 0x0f, 0x45, 0x62, 0xf5, 0x37, 0x72, 0x24, 0xdd, 0x92, 0x70, 0xb3, 0x28, 0x87, 0x1b, 0x94, 0xc1, 0x35, 0x1e, 0xb3, 0x84, 0xda, 0x2a, 0xa7, 0xb6, 0x4a, 0x68, 0x47, 0xa9, 0xb4, 0xa3, 0x54, 0x8e, 0x7a, 0x81, 0xa3, 0x5e, 0xfe, 0xa7, 0x8f, 0x9b, 0xf5, 0x0f, 0x8f, 0xeb, 0x2b, 0x91, 0x28, 0x1e, 0xdb, 0xd7, 0xae, 0x7d, 0x9f, 0xcd, 0xdc, 0xca, + 0x73, 0x0b, 0xab, 0x7e, 0x6e, 0xe9, 0xd5, 0xef, 0xfc, 0xa4, 0xf0, 0x38, 0xbe, 0xc4, 0xe2, 0xe6, 0x9e, 0x1e, 0x5f, 0x1b, 0x55, 0x1a, 0xf7, 0xf0, 0xad, 0x46, 0x93, 0x55, 0x4d, 0x1e, 0xc5, 0xe5, 0x4f, 0x07, 0xcd, 0xb9, 0xff, 0xa3, 0xb4, 0xd4, 0xaa, 0x77, 0x8e, 0x7c, 0xe7, 0xbd, 0x9a, 0x73, 0x4d, 0xe5, 0x5c, 0xaf, 0x06, 0xf4, 0x56, 0x16, 0xdf, 0xbc, 0xc9, 0x39, 0x26, 0x73, 0x8e, 0xc9, 0x1c, 0xe7, 0x2c, 0x67, 0x10, 0x4e, 0x5b, 0xbf, 0xc1, 0x59, 0xb8, 0x55, 0x6d, 0xee, 0xed, 0x2b, 0xdd, 0x4a, 0x8e, 0xb0, 0x91, 0x23, 0x1c, 0xe3, 0x08, 0x7b, 0xfc, 0xbf, 0x8b, 0xd5, 0x9f, 0x75, 0xec, 0x40, 0x5a, 0x4d, 0xd5, 0x33, 0x1f, 0xcd, 0xd1, 0xb2, 0x79, 0xe6, 0x15, 0x9c, 0xd1, 0xd5, 0xea, 0x39, 0xd4, 0xc5, 0x51, 0xdf, 0xe6, 0xa8, 0x6f, 0x73, 0x54, 0x27, 0x47, 0xcd, 0x51, 0xb7, 0xf9, + 0x47, 0xce, 0xbb, 0xfc, 0xb3, 0xee, 0xf5, 0x5b, 0xbe, 0x5f, 0xe9, 0x04, 0x47, 0x3b, 0x73, 0xeb, 0x77, 0xae, 0x72, 0xcf, 0x73, 0xdc, 0xf3, 0x9c, 0xbf, 0xf4, 0xcf, 0x72, 0x0e, 0x67, 0xb9, 0x47, 0x39, 0xe7, 0x70, 0xfe, 0x5f, 0xbd, 0xee, 0xc6, 0x39, 0x94, 0x70, 0x0e, 0xbe, 0x35, 0xe0, 0x79, 0xce, 0xe1, 0x1c, 0xe7, 0x70, 0xe1, 0x6f, 0xdf, 0x5a, 0xc2, 0x39, 0xfc, 0xce, 0x39, 0x94, 0x92, 0x59, 0xaa, 0xe6, 0xd1, 0xcb, 0x1c, 0x21, 0xae, 0xfa, 0x33, 0x54, 0xfe, 0xef, 0xe7, 0xa8, 0x2e, 0xfd, 0x02, 0xf5, 0x30, 0x8f, 0x79, 0x9d, 0x5e, 0x92, 0x47, 0x2f, 0xa9, 0xa4, 0x77, 0x24, 0xf1, 0xf8, 0x0e, 0x7a, 0x87, 0xef, 0xdf, 0x0b, 0x46, 0x71, 0xcf, 0x13, 0x8c, 0xd1, 0xbe, 0x77, 0x73, 0x4f, 0x32, 0x46, 0x47, 0x32, 0x46, 0x9f, 0xe4, 0x48, 0x9b, 0x6e, 0x79, 0xb7, 0xdf, 0xc1, 0xb9, 0x1c, + 0x61, 0x9c, 0x8e, 0x63, 0x9c, 0x8e, 0xa3, 0x07, 0xe5, 0xd1, 0x83, 0x92, 0xe8, 0x41, 0x49, 0xf4, 0x9c, 0x0b, 0xf4, 0x9c, 0x24, 0x7a, 0xca, 0x05, 0xce, 0xd3, 0x77, 0x6e, 0x07, 0x39, 0xb7, 0x83, 0xf4, 0x96, 0x4a, 0x7a, 0xca, 0x05, 0x7a, 0x4a, 0x26, 0x3d, 0x25, 0x93, 0x9e, 0x92, 0x49, 0x4f, 0xc9, 0xa4, 0xa7, 0x64, 0xd2, 0x53, 0xf2, 0xe8, 0x29, 0x79, 0xf4, 0x94, 0x4a, 0xab, 0xef, 0x5f, 0x76, 0xd5, 0xac, 0x6e, 0x17, 0x11, 0xf4, 0xdf, 0x78, 0xfa, 0x6f, 0xfc, 0xbf, 0x6a, 0x1f, 0xf4, 0xcd, 0x78, 0xfa, 0x63, 0xfc, 0x7f, 0x6d, 0x27, 0xa6, 0x6e, 0xca, 0x42, 0x3e, 0xd1, 0x71, 0x07, 0x1e, 0x40, 0x3d, 0xd4, 0x07, 0xcf, 0x57, 0x35, 0x25, 0x9f, 0x3d, 0x84, 0x66, 0xa4, 0xb5, 0x87, 0xd1, 0x5c, 0xc6, 0xa8, 0x16, 0xf2, 0x93, 0x6a, 0x25, 0xa3, 0xd4, 0x5c, 0x39, 0xa8, 0xbe, 0x93, 0x54, + 0x35, 0x9f, 0x5c, 0x13, 0xc7, 0x6d, 0xed, 0x62, 0x57, 0x49, 0x32, 0x50, 0x39, 0x64, 0x85, 0xca, 0x96, 0x33, 0xca, 0xc9, 0xdf, 0x5d, 0x72, 0x48, 0xb1, 0xe6, 0x57, 0x37, 0xf8, 0xfb, 0x4d, 0xb1, 0xb1, 0x3e, 0x3c, 0xc9, 0x1c, 0xda, 0x91, 0x51, 0x7b, 0x96, 0xc9, 0xa0, 0x8c, 0x6e, 0x93, 0xf1, 0xcc, 0xa3, 0xe3, 0x29, 0xdb, 0xed, 0xcc, 0x8b, 0x9b, 0xfc, 0xa3, 0x4f, 0x27, 0x69, 0x41, 0x99, 0xb6, 0xa0, 0x4c, 0x5b, 0x50, 0xa6, 0x2d, 0x28, 0xbf, 0xf6, 0x94, 0x5f, 0x7b, 0xd3, 0x17, 0xb2, 0xcb, 0xe4, 0xfb, 0x2e, 0xe9, 0x29, 0xb4, 0x94, 0x39, 0xcc, 0x83, 0x73, 0x65, 0x8b, 0xc9, 0xf7, 0xfd, 0xbf, 0x6e, 0x8e, 0xc3, 0x18, 0xaa, 0x75, 0x91, 0x29, 0x5a, 0x77, 0x49, 0xd2, 0x5e, 0x60, 0xfb, 0x92, 0x14, 0x6a, 0x3d, 0x95, 0x55, 0x7b, 0x85, 0xcb, 0xf4, 0x4c, 0x6d, 0x20, 0xd7, 0xbd, 0x26, + 0xa3, 0xb4, 0x91, 0xec, 0xbf, 0x8b, 0xd1, 0xec, 0x7f, 0xc0, 0x7d, 0x7c, 0xbf, 0x84, 0xb6, 0x84, 0xbc, 0xb2, 0x46, 0xc6, 0x98, 0xd7, 0xc9, 0x97, 0xe6, 0x4d, 0xd4, 0x4d, 0x28, 0xe3, 0xef, 0x11, 0x19, 0x6f, 0x3e, 0xc1, 0x7e, 0x24, 0xdb, 0x28, 0xf9, 0x8c, 0xb9, 0x75, 0x3c, 0x73, 0x6b, 0x47, 0xe6, 0xd6, 0x8e, 0xe6, 0x78, 0x19, 0x67, 0xb6, 0x21, 0x99, 0xfd, 0x54, 0x29, 0x34, 0x3b, 0xfc, 0x23, 0xe3, 0x76, 0xea, 0x75, 0x3b, 0x23, 0xe3, 0x3e, 0x46, 0xc6, 0x7d, 0xd4, 0xed, 0x76, 0xe6, 0xd2, 0x4d, 0xcc, 0xa5, 0x9b, 0x18, 0x1d, 0xb7, 0x33, 0x3a, 0x6e, 0x67, 0x0e, 0xdd, 0xe4, 0x1f, 0xcb, 0x5f, 0xa4, 0xde, 0x5f, 0x42, 0x4f, 0x49, 0xa2, 0xde, 0x5b, 0x50, 0xef, 0x0f, 0xf8, 0x7f, 0x71, 0xf5, 0x2c, 0x97, 0xaf, 0xe2, 0x1a, 0xf9, 0xe3, 0x15, 0xd5, 0xc8, 0xd2, 0x4b, 0x3d, 0x62, 0xe9, + 0xcb, 0xb6, 0x9f, 0x7a, 0xcc, 0x32, 0x94, 0xed, 0x30, 0x2e, 0x0f, 0x67, 0xfb, 0x06, 0x97, 0xc7, 0x70, 0x9b, 0x8f, 0xf0, 0x31, 0xc6, 0xe2, 0x13, 0x7c, 0x8a, 0xcf, 0xb0, 0x92, 0xdb, 0xfc, 0xf5, 0x7b, 0xe9, 0x8d, 0xfe, 0xfc, 0xbd, 0xf4, 0x0d, 0xaa, 0xa6, 0x65, 0x33, 0x7f, 0xdf, 0x82, 0x10, 0x84, 0x22, 0x0c, 0x5b, 0xb1, 0x0d, 0x7b, 0xb9, 0xed, 0x5f, 0xbf, 0xa3, 0xfe, 0xba, 0xe5, 0x00, 0x97, 0xff, 0xfa, 0x1d, 0xf5, 0xd7, 0x2d, 0x11, 0xaa, 0xbe, 0xe5, 0x28, 0xd7, 0xfd, 0xf5, 0x1b, 0xea, 0xaf, 0x5b, 0x22, 0xb9, 0xfc, 0xd7, 0x6f, 0xa8, 0xbf, 0x6e, 0x39, 0xad, 0x5a, 0xdf, 0xf2, 0x1b, 0xea, 0xef, 0x5b, 0x72, 0xb8, 0xfc, 0xd7, 0x6f, 0xa8, 0xbf, 0x6f, 0xb9, 0xa0, 0x6e, 0xb3, 0x5c, 0x54, 0x4d, 0xf4, 0xdb, 0x25, 0x49, 0xa7, 0xbd, 0xe9, 0x77, 0xe2, 0x2e, 0xd4, 0x44, 0x2d, 0xd4, + 0x46, 0x1d, 0x30, 0x33, 0xe9, 0x75, 0x71, 0x0f, 0xee, 0xc5, 0x7d, 0xb8, 0x1f, 0xb4, 0x4f, 0x9d, 0xf6, 0xa9, 0xd3, 0x3e, 0xf5, 0x06, 0x68, 0x08, 0xda, 0xa9, 0xde, 0x08, 0x8d, 0xd1, 0x04, 0x4d, 0xf1, 0x10, 0x9a, 0xe1, 0x61, 0x34, 0x47, 0x0b, 0xb4, 0x84, 0xff, 0xdf, 0xdf, 0xfe, 0xf9, 0x7b, 0xed, 0xbf, 0xeb, 0x6d, 0xd5, 0x3c, 0xfd, 0x51, 0xb5, 0x5a, 0x6f, 0xc7, 0xdf, 0xda, 0x4b, 0x3e, 0xbd, 0x3e, 0x49, 0xef, 0x80, 0x7e, 0x32, 0x5e, 0xef, 0x8f, 0x01, 0x18, 0x88, 0x41, 0x18, 0x82, 0x57, 0x31, 0x14, 0xc3, 0xf0, 0x9a, 0xa4, 0xeb, 0xaf, 0x63, 0x38, 0xde, 0xc0, 0x08, 0x8c, 0xc4, 0x28, 0xbc, 0x89, 0xb7, 0xf0, 0x36, 0xde, 0xc1, 0xbb, 0x78, 0x0f, 0xef, 0x63, 0x34, 0x68, 0x7b, 0xfa, 0x87, 0x18, 0x83, 0x8f, 0xf0, 0x31, 0xc6, 0xe2, 0x13, 0x8c, 0x93, 0x31, 0xfa, 0xa7, 0xf8, 0x4c, + 0x7e, 0xd2, 0x3f, 0xc7, 0x17, 0x18, 0x8f, 0x09, 0x98, 0x88, 0x29, 0x32, 0x4a, 0x9f, 0x8a, 0x69, 0xf8, 0x1a, 0xd3, 0xf1, 0x8d, 0xaa, 0xa7, 0xcf, 0x60, 0xfb, 0x2d, 0x66, 0x62, 0x16, 0x66, 0x63, 0x0e, 0xfe, 0xfe, 0x37, 0xe1, 0x97, 0xea, 0xdf, 0xab, 0xc7, 0xf4, 0x05, 0xaa, 0xa5, 0xbe, 0x90, 0xbf, 0x2d, 0x42, 0xb0, 0xbc, 0x65, 0x64, 0xc8, 0x41, 0x23, 0x13, 0x59, 0x92, 0x6a, 0x64, 0xe3, 0x34, 0xce, 0xe0, 0x2c, 0x9c, 0xc8, 0xc1, 0x45, 0xd9, 0x6b, 0xb8, 0xe0, 0xc6, 0x25, 0xe4, 0x22, 0x0f, 0xf9, 0xb8, 0x0c, 0x0f, 0x0a, 0x50, 0x88, 0x22, 0x14, 0xa3, 0x04, 0xa5, 0xb2, 0xb7, 0xfa, 0x37, 0xe1, 0x57, 0xfa, 0x7f, 0x13, 0xfe, 0x1f, 0x7f, 0x0f, 0x7e, 0xa5, 0xff, 0xf7, 0xe0, 0x6f, 0xfd, 0x2d, 0xf8, 0x83, 0xfe, 0xdc, 0x6b, 0x63, 0x4d, 0x68, 0x63, 0x4d, 0x68, 0x63, 0x4d, 0x68, 0x63, + 0x4d, 0xd8, 0x91, 0xb5, 0xdf, 0x26, 0x6b, 0x3e, 0x7f, 0x63, 0x1c, 0xb5, 0x7a, 0x50, 0x80, 0x42, 0x14, 0xb1, 0xbe, 0x29, 0x46, 0x09, 0x4a, 0x51, 0x26, 0xe9, 0x41, 0xdd, 0xe5, 0xd1, 0xa0, 0x5d, 0x4a, 0x0b, 0xfa, 0x59, 0xc6, 0xfc, 0x4f, 0x7e, 0x47, 0x7e, 0x4c, 0xd0, 0x3e, 0xec, 0x57, 0xed, 0x83, 0x0e, 0x4a, 0x61, 0xd0, 0x21, 0xc6, 0xa8, 0xa9, 0x8c, 0x78, 0x2b, 0x18, 0xf1, 0x0e, 0x30, 0xd2, 0xe5, 0x30, 0xd2, 0x79, 0x18, 0xe9, 0x7c, 0xaf, 0xda, 0xf8, 0x72, 0xde, 0x56, 0x46, 0xab, 0x15, 0x8c, 0x56, 0x2b, 0x18, 0x49, 0xf6, 0x32, 0x82, 0xf8, 0x46, 0x8d, 0x95, 0x8c, 0x14, 0x2b, 0x18, 0x25, 0x56, 0x30, 0x4a, 0xfc, 0xc6, 0x28, 0xb1, 0x82, 0x9e, 0x5b, 0x8b, 0x9e, 0x5b, 0x9f, 0x9e, 0x5b, 0x8b, 0x9e, 0xdb, 0x94, 0x9e, 0x5b, 0x8b, 0x9e, 0x5b, 0x9f, 0x9e, 0x5b, 0x8b, 0x9e, 0xdb, + 0x94, 0xde, 0x59, 0x8b, 0xde, 0x19, 0x4e, 0xef, 0x4c, 0xa0, 0x77, 0xd6, 0xa2, 0x77, 0x86, 0xd3, 0x3b, 0x13, 0xe9, 0x7d, 0xb5, 0xe8, 0x7d, 0xe1, 0xf4, 0xbe, 0x04, 0x7a, 0x5f, 0x2f, 0x7a, 0x5f, 0x2d, 0x7a, 0x5f, 0x38, 0xbd, 0x2f, 0x91, 0xde, 0xd7, 0x8b, 0xde, 0x77, 0x07, 0xbd, 0xaf, 0x16, 0xbd, 0x2f, 0x9c, 0xde, 0x97, 0x40, 0xef, 0xeb, 0x45, 0xef, 0xab, 0x45, 0xef, 0x0b, 0xa7, 0xf7, 0x25, 0xd2, 0xfb, 0x7a, 0xd1, 0xfb, 0x1a, 0xd0, 0xfb, 0x62, 0xe8, 0x7d, 0x67, 0xe8, 0x7d, 0x43, 0xe9, 0x7d, 0x0d, 0xe8, 0x7d, 0x89, 0xf4, 0xbe, 0x1c, 0x7a, 0xdf, 0x50, 0x7a, 0x5e, 0x1d, 0x5a, 0xff, 0x72, 0x5a, 0x7f, 0x18, 0x2d, 0x7f, 0x39, 0x2d, 0x7f, 0x1b, 0x2d, 0x7d, 0x05, 0x2d, 0x7d, 0x05, 0x2d, 0x7d, 0x05, 0x2d, 0x7d, 0x05, 0x2d, 0x7d, 0x05, 0x2d, 0x7d, 0x05, 0x2d, 0x7d, 0x05, 0x2d, + 0x7d, 0x05, 0x2d, 0x7d, 0x05, 0x2d, 0x32, 0x87, 0x16, 0x99, 0x43, 0x8b, 0xf4, 0xd0, 0x22, 0x3d, 0xb4, 0x48, 0x0f, 0x2d, 0xd2, 0x43, 0x8b, 0xf4, 0xd0, 0x22, 0x3d, 0xb4, 0xc8, 0xbd, 0xb4, 0xc8, 0xbd, 0xb4, 0xc8, 0xbd, 0xb4, 0xc8, 0xbd, 0xb4, 0xc8, 0xbd, 0xb4, 0xc8, 0x26, 0xb4, 0xc8, 0xbd, 0xb4, 0xc8, 0xbd, 0xb4, 0xc8, 0xbd, 0xb4, 0xc8, 0xbd, 0xb4, 0xc8, 0xbd, 0xb4, 0xc8, 0xbd, 0xb4, 0xc8, 0xf6, 0xb4, 0xc8, 0x27, 0x69, 0x91, 0xed, 0x68, 0x91, 0x53, 0x69, 0x91, 0x2d, 0x68, 0x91, 0x8d, 0x69, 0x91, 0x7b, 0x69, 0x91, 0x7b, 0x69, 0x91, 0xbf, 0xd1, 0x62, 0xa6, 0xd3, 0x62, 0xe6, 0xd3, 0x62, 0x6a, 0xd2, 0x62, 0xa6, 0xd3, 0x62, 0x16, 0xd3, 0x62, 0x6a, 0xd2, 0x62, 0xa6, 0xd3, 0x62, 0xe6, 0xd3, 0x62, 0x6a, 0xd2, 0x62, 0xa6, 0xd3, 0x62, 0x16, 0xd3, 0x62, 0x6a, 0xd2, 0x5a, 0x2e, + 0xd2, 0x5a, 0x2e, 0xd2, 0x5a, 0x2e, 0xd2, 0x5a, 0x2e, 0x52, 0xfb, 0x97, 0x82, 0x76, 0xc9, 0x55, 0x6a, 0x7f, 0x25, 0xb5, 0xdf, 0x9c, 0xda, 0xef, 0x40, 0xed, 0x8f, 0x0d, 0xda, 0x23, 0x42, 0xed, 0xd7, 0xa1, 0xf6, 0x57, 0x52, 0xfb, 0x2b, 0xa9, 0xfd, 0x7b, 0xd5, 0x93, 0xd4, 0xfa, 0x52, 0x6a, 0x7d, 0xff, 0x2d, 0xb5, 0xbe, 0xa7, 0xba, 0xd6, 0x43, 0xa8, 0xf5, 0xa5, 0xd4, 0xfa, 0x52, 0x6a, 0x7d, 0x0f, 0xb5, 0xbe, 0x87, 0x1a, 0x5f, 0x4a, 0x8d, 0x2f, 0xad, 0xae, 0xf1, 0xa5, 0x94, 0xe0, 0x52, 0x4a, 0x70, 0x29, 0x25, 0xb8, 0x94, 0x12, 0x5c, 0x4a, 0x09, 0x2e, 0xa5, 0x04, 0x97, 0x52, 0x82, 0x4b, 0x29, 0xc1, 0xa5, 0x94, 0xe0, 0xd2, 0x7f, 0xa3, 0x04, 0xf7, 0x50, 0x82, 0x7b, 0x28, 0xc1, 0x3d, 0x94, 0xe0, 0x1e, 0x4a, 0x70, 0x0f, 0x25, 0xd8, 0x8e, 0x12, 0xdc, 0x43, 0x09, 0xee, 0xa1, + 0x04, 0xf7, 0x50, 0x82, 0x7b, 0x28, 0xc1, 0x3d, 0x94, 0xe0, 0x1e, 0x7d, 0xae, 0xe4, 0xea, 0xdf, 0x63, 0x01, 0x16, 0x72, 0x79, 0x11, 0x7c, 0xa5, 0xf6, 0x8f, 0xa5, 0xf0, 0xbb, 0x7f, 0x86, 0x77, 0x30, 0xc3, 0x3b, 0x98, 0xe1, 0x1d, 0xcc, 0xf0, 0x0e, 0x66, 0x78, 0x07, 0x33, 0xbc, 0x83, 0x19, 0xde, 0x71, 0xcb, 0x0c, 0x9f, 0xc5, 0x0c, 0x9f, 0x45, 0x09, 0x8c, 0xa5, 0x04, 0x96, 0x51, 0x02, 0xc3, 0x99, 0xe1, 0x0f, 0x33, 0xc3, 0xa7, 0x57, 0xcf, 0xf0, 0x0e, 0x66, 0xf8, 0x04, 0x66, 0xf8, 0x5e, 0xcc, 0xf0, 0x2b, 0x99, 0xe1, 0x7d, 0x9f, 0x64, 0x3f, 0xcc, 0x0c, 0x7f, 0x98, 0x19, 0xbe, 0x80, 0x19, 0xde, 0x41, 0x89, 0x45, 0x33, 0xc3, 0xc7, 0x30, 0xc3, 0x77, 0xa0, 0xe4, 0x66, 0x32, 0xc3, 0x3b, 0x6e, 0x99, 0xe1, 0x77, 0x30, 0xc3, 0x6f, 0x60, 0x86, 0x3f, 0xfa, 0x2f, 0x66, 0xf8, 0x5f, + 0x98, 0xe1, 0x53, 0x98, 0xe1, 0xdd, 0xcc, 0xf0, 0xc5, 0xcc, 0xf0, 0x21, 0xa6, 0x05, 0x6c, 0x17, 0x92, 0x47, 0xdc, 0x1c, 0xeb, 0x92, 0x64, 0x55, 0xcf, 0xf2, 0x8e, 0xea, 0x59, 0xbe, 0x94, 0x19, 0xde, 0xc1, 0x0c, 0xef, 0xa8, 0x9e, 0xe1, 0x87, 0x33, 0xc3, 0x3b, 0x98, 0xe1, 0xd3, 0xa8, 0xa9, 0xe1, 0xcc, 0xf0, 0x59, 0xcc, 0xf0, 0xf6, 0xea, 0x19, 0xfe, 0x43, 0x66, 0xf8, 0xa9, 0xcc, 0xf0, 0x0e, 0x66, 0xf8, 0x9c, 0xea, 0x19, 0xde, 0x51, 0x3d, 0xc3, 0x7f, 0x52, 0x3d, 0xc3, 0x77, 0x60, 0x86, 0xef, 0xc0, 0x0c, 0xff, 0x39, 0x33, 0xfc, 0xe7, 0xcc, 0xf0, 0x1d, 0x98, 0xe1, 0x4b, 0x99, 0xe1, 0x1d, 0xcc, 0xf0, 0x3b, 0x98, 0xe1, 0x77, 0x30, 0xc3, 0x1f, 0x62, 0x86, 0x3f, 0xc4, 0x0c, 0xbf, 0x83, 0x19, 0x7e, 0x03, 0x33, 0xfc, 0x06, 0x66, 0xf8, 0x9d, 0xcc, 0xf0, 0x3b, 0x99, 0xe1, 0x37, + 0x30, 0xc3, 0xc7, 0x31, 0xc3, 0x3b, 0x98, 0xe1, 0x1d, 0xcc, 0xf0, 0x8e, 0xea, 0x19, 0xbe, 0x0e, 0x33, 0xbc, 0x9d, 0x19, 0xde, 0xc1, 0x0c, 0xef, 0x60, 0x86, 0x77, 0xfc, 0x1b, 0x33, 0xbc, 0x83, 0x19, 0xde, 0xc1, 0x0c, 0xef, 0x60, 0x86, 0x77, 0x30, 0xc3, 0x3b, 0x98, 0xe1, 0x1d, 0xcc, 0xf0, 0x8e, 0xff, 0xc5, 0x0c, 0xef, 0x60, 0x86, 0x77, 0x30, 0xc3, 0x3b, 0x98, 0xe1, 0x1d, 0xcc, 0xf0, 0x0e, 0x66, 0x78, 0x07, 0x33, 0xbc, 0xe3, 0x3f, 0x3c, 0xc3, 0x3b, 0x98, 0xe1, 0x1d, 0xcc, 0xf0, 0x0e, 0x66, 0x78, 0x07, 0x33, 0xbc, 0x83, 0x19, 0xde, 0xc1, 0x0c, 0xef, 0x60, 0x86, 0x77, 0x30, 0xc3, 0x3b, 0x98, 0xe1, 0x1d, 0xcc, 0xf0, 0x0e, 0x66, 0x78, 0x07, 0x33, 0xbc, 0x83, 0x19, 0xde, 0xc1, 0x0c, 0xef, 0x60, 0x86, 0x77, 0x30, 0xc3, 0x3b, 0x98, 0xe1, 0x1d, 0xcc, 0xf0, 0x0e, 0x66, 0x78, + 0x07, 0x33, 0xbc, 0x83, 0x19, 0xde, 0xc1, 0x0c, 0xef, 0x60, 0x86, 0x77, 0x30, 0xc3, 0x3b, 0x98, 0xe1, 0x1d, 0xcc, 0xf0, 0x0e, 0x66, 0x78, 0x07, 0x33, 0xbc, 0x83, 0x19, 0xde, 0xc1, 0x0c, 0xef, 0xbe, 0x65, 0x86, 0x77, 0x33, 0xce, 0x2d, 0x64, 0x9c, 0x5b, 0xcf, 0x0c, 0xef, 0x60, 0x86, 0x2f, 0x64, 0x86, 0x77, 0x30, 0xc3, 0x3b, 0xfe, 0xcd, 0x19, 0x3e, 0x8b, 0x19, 0x3e, 0x8b, 0x19, 0x3e, 0x8b, 0x19, 0x3e, 0x4b, 0xf7, 0x7d, 0x47, 0xc1, 0x48, 0x8c, 0xc2, 0x9b, 0x78, 0x0b, 0x6f, 0xe3, 0x1d, 0xbc, 0x8b, 0xf7, 0xf0, 0x3e, 0x46, 0x83, 0xb6, 0xc7, 0x0c, 0x9f, 0xc5, 0x0c, 0x9f, 0xc5, 0x0c, 0x9f, 0xc5, 0x0c, 0x9f, 0xc5, 0x0c, 0x9f, 0xc5, 0x0c, 0x9f, 0xc5, 0x68, 0x30, 0x96, 0xd1, 0x60, 0x2c, 0xa3, 0xc1, 0x32, 0x46, 0x83, 0x65, 0x8c, 0x06, 0xcb, 0x18, 0x0d, 0x96, 0x31, 0x1a, 0x2c, + 0x63, 0x34, 0x58, 0xc6, 0x68, 0x30, 0x9c, 0xd1, 0x60, 0x38, 0xa3, 0xc1, 0x70, 0x46, 0x83, 0xe1, 0x8c, 0x06, 0xc3, 0xab, 0x67, 0xf8, 0xe1, 0x8c, 0x06, 0xc3, 0x19, 0x0d, 0x86, 0x33, 0x1a, 0x0c, 0x67, 0x34, 0x18, 0xce, 0x68, 0x30, 0xfc, 0x5f, 0xcc, 0xf0, 0xc3, 0x19, 0x19, 0x86, 0x33, 0x32, 0x8c, 0x64, 0x86, 0x3f, 0xcc, 0x0c, 0x7f, 0x98, 0x19, 0x3e, 0x9d, 0x19, 0x3e, 0x9d, 0x19, 0x3e, 0x9d, 0x19, 0x3e, 0x9d, 0x19, 0x3e, 0x9d, 0x19, 0x3e, 0x9d, 0x19, 0x3e, 0xfd, 0x3f, 0x3c, 0xc3, 0x3b, 0x18, 0xad, 0xa2, 0x19, 0xad, 0xa2, 0x19, 0xad, 0xa2, 0x19, 0xad, 0xa2, 0x99, 0xe1, 0x3b, 0x30, 0xc3, 0x6f, 0x60, 0x86, 0x77, 0x30, 0xc3, 0x3b, 0x98, 0xe1, 0x1d, 0xcc, 0xf0, 0x0e, 0x66, 0x78, 0x07, 0x33, 0x7c, 0x16, 0x33, 0x7c, 0x16, 0x33, 0x7c, 0x16, 0x33, 0x7c, 0x16, 0x33, 0x7c, 0x16, + 0xa3, 0x5b, 0xf3, 0xea, 0x19, 0xfe, 0xc3, 0xff, 0xc9, 0x0c, 0xff, 0x21, 0x63, 0xfc, 0x87, 0xd5, 0x33, 0x7c, 0x29, 0x33, 0xbc, 0x43, 0x75, 0x55, 0xcf, 0x90, 0x43, 0x46, 0xa9, 0x27, 0x54, 0xac, 0x7c, 0xa7, 0xe2, 0x65, 0x96, 0x4a, 0x67, 0x24, 0x63, 0xcd, 0xa3, 0x72, 0xe5, 0x37, 0x55, 0x46, 0x2e, 0xb9, 0x8b, 0x64, 0x5f, 0x47, 0x7e, 0xf5, 0x65, 0x67, 0xd2, 0x54, 0x0b, 0x46, 0x8b, 0x54, 0x73, 0x94, 0x32, 0x05, 0x46, 0xaa, 0x67, 0x02, 0x9d, 0xea, 0x1e, 0x4b, 0x07, 0x99, 0x65, 0x79, 0x5c, 0x96, 0x59, 0x7a, 0xb2, 0x7d, 0x85, 0x34, 0xdf, 0x0b, 0xbd, 0xd9, 0xef, 0x83, 0xbe, 0xec, 0xf7, 0x43, 0x7f, 0xf6, 0x07, 0x60, 0x20, 0x06, 0x61, 0x30, 0x86, 0xe0, 0x55, 0x0c, 0xe5, 0xef, 0xc3, 0xf0, 0x1a, 0xfb, 0xaf, 0x63, 0x38, 0xfb, 0x6f, 0x60, 0x04, 0xfb, 0xef, 0x48, 0x4f, 0xcb, 0x4e, + 0x35, 0xda, 0x12, 0xa7, 0xee, 0xb3, 0xc4, 0xab, 0x87, 0x2c, 0xc9, 0x6a, 0xb2, 0xe5, 0x94, 0xaa, 0x63, 0xb9, 0x4c, 0x6b, 0xac, 0xab, 0x7a, 0xd2, 0x0a, 0x92, 0xf5, 0x2d, 0xaa, 0x9b, 0xbe, 0x5f, 0x2a, 0x75, 0xa7, 0x6a, 0xa0, 0x5f, 0x66, 0x5d, 0x56, 0x5b, 0x35, 0x34, 0xea, 0xa9, 0x0e, 0xfe, 0x6f, 0x38, 0x6b, 0xac, 0xea, 0x1b, 0xcd, 0xd4, 0xe3, 0x46, 0x73, 0x15, 0x64, 0xb4, 0x56, 0x6b, 0x8c, 0x76, 0xea, 0x5e, 0xe3, 0x39, 0xff, 0x77, 0xe5, 0xdf, 0x63, 0xcc, 0x53, 0x1d, 0x49, 0x7c, 0x67, 0xac, 0xf7, 0xab, 0x87, 0xac, 0x1f, 0xf9, 0xbf, 0x09, 0xf8, 0xbf, 0xa7, 0x24, 0x66, 0xfd, 0x7f, 0xbe, 0x24, 0x1e, 0x52, 0x9a, 0x14, 0x53, 0x1a, 0xa9, 0x94, 0x46, 0x43, 0xf5, 0xa6, 0xff, 0xdd, 0xbd, 0x59, 0x3c, 0xf3, 0xed, 0xfe, 0x57, 0x93, 0xda, 0xfa, 0x3f, 0xf9, 0xd5, 0x82, 0x54, 0x1d, + 0x6c, 0xa2, 0xf7, 0x9b, 0x46, 0x49, 0x81, 0x36, 0xce, 0xff, 0x7b, 0x15, 0x8f, 0x71, 0x96, 0xdd, 0x38, 0x3b, 0x9d, 0xb3, 0x9a, 0xc0, 0xd9, 0x34, 0xe1, 0x6c, 0x9c, 0xba, 0x53, 0xc4, 0xff, 0x4d, 0xf8, 0xf5, 0x54, 0x13, 0xce, 0xe4, 0x69, 0xa3, 0xa1, 0x6a, 0x6e, 0x3c, 0xa8, 0x5a, 0x19, 0x8d, 0x78, 0x64, 0xdf, 0x6f, 0xe3, 0x34, 0x51, 0xf5, 0x8c, 0xa6, 0x5c, 0x6e, 0xe6, 0xff, 0xfe, 0xff, 0x67, 0x8d, 0x47, 0x54, 0x03, 0xce, 0x70, 0x3e, 0x67, 0xf8, 0x9c, 0xf1, 0x96, 0x54, 0x70, 0x76, 0xcd, 0x38, 0xbb, 0x34, 0xce, 0xa4, 0xae, 0x94, 0x33, 0xe7, 0x9e, 0x61, 0xce, 0x1d, 0xc5, 0x9c, 0xbb, 0x88, 0xba, 0xf1, 0xfd, 0xdb, 0xa0, 0xbd, 0xcc, 0xb9, 0xbf, 0x32, 0xe7, 0x5e, 0xf0, 0xbf, 0xdb, 0x12, 0x28, 0xdf, 0x33, 0xa7, 0xee, 0x23, 0xed, 0x3e, 0xa4, 0xf5, 0x53, 0xf7, 0x33, 0xf7, 0x9d, + 0x62, 0xce, 0x3b, 0x49, 0x5d, 0x65, 0x33, 0xaf, 0x4d, 0x66, 0x3e, 0x3a, 0xce, 0x1c, 0xd4, 0x9a, 0x39, 0xa8, 0x39, 0x73, 0xd0, 0x49, 0xfd, 0x11, 0xc6, 0xc0, 0xb6, 0x78, 0x14, 0x03, 0x64, 0x9a, 0x7f, 0x5c, 0x99, 0x2b, 0x51, 0xac, 0x26, 0xa2, 0x58, 0x4d, 0x44, 0x19, 0x6b, 0xd4, 0x1d, 0xf4, 0xab, 0x76, 0xff, 0xb1, 0x47, 0xbf, 0xed, 0x9f, 0x3e, 0xfa, 0x6d, 0xac, 0x49, 0x7e, 0xe4, 0xe8, 0x53, 0x38, 0xca, 0x66, 0x8e, 0x50, 0xc2, 0x11, 0x4a, 0x28, 0xe5, 0x02, 0xee, 0x35, 0x86, 0x7b, 0x35, 0xe6, 0x5e, 0xd9, 0xdc, 0x2b, 0x9b, 0x7b, 0x65, 0x73, 0x8f, 0xcf, 0x54, 0x7d, 0xee, 0x31, 0x43, 0xd9, 0x58, 0x89, 0x24, 0x4a, 0x3c, 0xe7, 0x1b, 0xa5, 0xce, 0x48, 0x0c, 0xe7, 0xeb, 0x51, 0xbf, 0x53, 0xb7, 0x2e, 0xff, 0xf7, 0x3d, 0xa5, 0x52, 0xaf, 0x27, 0x39, 0xea, 0x27, 0xa6, 0x20, 0x49, + 0xa7, 0x7e, 0x8f, 0x73, 0xee, 0xb4, 0x68, 0x09, 0xae, 0x7e, 0x47, 0xa9, 0x98, 0x47, 0xfa, 0x85, 0x47, 0xfa, 0x85, 0x7a, 0xcd, 0xe3, 0xd1, 0x7e, 0xe6, 0xd1, 0x46, 0xea, 0x93, 0x24, 0x96, 0x47, 0xac, 0xcf, 0x23, 0xc6, 0xf2, 0x88, 0xb1, 0x3c, 0x62, 0xac, 0xbe, 0x84, 0xd1, 0xa4, 0xbb, 0x0c, 0x34, 0x87, 0x2a, 0xb3, 0x2c, 0x50, 0x01, 0x08, 0x94, 0x0f, 0x59, 0x67, 0x79, 0x58, 0x67, 0x79, 0x58, 0x67, 0x79, 0x54, 0x6d, 0x4a, 0xec, 0x01, 0xb6, 0xf5, 0x50, 0x1f, 0x0f, 0xa2, 0xb9, 0x32, 0x54, 0x2b, 0x65, 0x55, 0xad, 0xe5, 0x67, 0xd5, 0x06, 0x6d, 0xd1, 0x4e, 0x36, 0xab, 0xf6, 0x78, 0x0c, 0x1d, 0xf0, 0x38, 0x9e, 0xc0, 0x93, 0x78, 0x0a, 0x4f, 0xe3, 0x19, 0x89, 0x50, 0x1d, 0xd9, 0x76, 0xc2, 0xff, 0xfb, 0xdf, 0xf3, 0xba, 0x5d, 0x8d, 0x90, 0x2d, 0xea, 0x03, 0x4a, 0xf7, 0x23, 0x7c, + 0x8c, 0xb1, 0xf8, 0x84, 0x73, 0x1b, 0x87, 0x4f, 0xf1, 0x19, 0x97, 0xbf, 0x60, 0x3b, 0x9e, 0xed, 0x04, 0x5a, 0xcc, 0x44, 0x4c, 0xc2, 0x64, 0x54, 0x7d, 0x27, 0x6c, 0xae, 0x9a, 0x8a, 0x69, 0xf8, 0x1a, 0xd3, 0xf1, 0x0d, 0x66, 0xe0, 0x5b, 0xcc, 0xc4, 0x2c, 0xcc, 0x66, 0xbd, 0x39, 0x07, 0xf3, 0xa8, 0xbd, 0xef, 0x19, 0x9b, 0x62, 0xe5, 0x07, 0xe5, 0xfb, 0xac, 0x47, 0x3c, 0xe5, 0x6d, 0xe7, 0x38, 0xc9, 0x72, 0x49, 0x91, 0x08, 0xab, 0xbf, 0xeb, 0x23, 0x83, 0x5a, 0xff, 0x9d, 0x31, 0x2c, 0x49, 0x65, 0xc8, 0x2f, 0xea, 0x14, 0xfb, 0x59, 0x1c, 0x23, 0x5b, 0xe2, 0xd4, 0x69, 0x5a, 0xc4, 0x59, 0xb6, 0xe7, 0xb8, 0xfc, 0xbb, 0x9c, 0x56, 0xe7, 0xb9, 0xcf, 0x05, 0xce, 0xef, 0x22, 0x7c, 0xdf, 0x30, 0xe2, 0xe6, 0x31, 0x2e, 0xb1, 0x9f, 0x2b, 0xeb, 0xab, 0xbf, 0xf3, 0x24, 0x41, 0x31, 0xdf, + 0xd1, 0x5a, 0x56, 0xa8, 0x4a, 0x59, 0xa7, 0xae, 0xe1, 0x06, 0x8f, 0xeb, 0xfb, 0x14, 0xa0, 0x48, 0x3e, 0xeb, 0x5a, 0x0f, 0x2d, 0xe8, 0xa2, 0xe9, 0x4e, 0x5a, 0xd1, 0x5d, 0x72, 0xd6, 0x54, 0x93, 0x35, 0x6f, 0x2d, 0xd4, 0x46, 0x1d, 0xf9, 0xdc, 0x74, 0xb7, 0x64, 0x98, 0xea, 0xd2, 0xba, 0xee, 0xe1, 0xf2, 0xbd, 0xb8, 0x4f, 0x3e, 0x36, 0xdd, 0x8f, 0x07, 0xd8, 0xaf, 0x87, 0xfa, 0x68, 0x20, 0x9b, 0x4d, 0x0d, 0xd9, 0xd2, 0x2e, 0x4c, 0x8d, 0xd9, 0x6f, 0xc2, 0x7e, 0x53, 0x3c, 0x24, 0x2e, 0x53, 0x33, 0xb6, 0x0f, 0xc3, 0xf7, 0xae, 0x64, 0x0b, 0xb6, 0x2d, 0xd1, 0x4a, 0x4e, 0x98, 0x1e, 0xe1, 0x76, 0xad, 0xd9, 0x6f, 0x23, 0x1b, 0x4d, 0x3d, 0xfe, 0x78, 0xd6, 0xf4, 0xb9, 0x1c, 0x62, 0xcd, 0x1c, 0x67, 0x1a, 0xcf, 0xdf, 0x26, 0xf0, 0x78, 0x13, 0xe5, 0x0b, 0xd3, 0x24, 0xd9, 0x67, 0x9a, + 0x4c, 0x56, 0x9d, 0xc2, 0x98, 0x3d, 0x9f, 0xf5, 0xb4, 0x9b, 0xe3, 0xe7, 0x72, 0xbf, 0x3c, 0x49, 0x30, 0x15, 0xcb, 0x19, 0x53, 0x09, 0xe7, 0x5b, 0x2a, 0x79, 0xac, 0xa1, 0x3d, 0xac, 0x9d, 0x3d, 0xac, 0x9d, 0x3d, 0xda, 0x6b, 0xca, 0xca, 0xba, 0xd9, 0xa3, 0x8d, 0x56, 0x26, 0xed, 0x73, 0xd9, 0xac, 0x51, 0x77, 0xda, 0x24, 0x89, 0xd1, 0xa6, 0x80, 0x3a, 0xd2, 0xa8, 0x23, 0xed, 0x1b, 0xc9, 0xd1, 0x66, 0xc9, 0x41, 0x6d, 0xb6, 0x6c, 0xd7, 0xe6, 0xf2, 0xf7, 0x79, 0x5c, 0x0e, 0x26, 0x07, 0xff, 0xc8, 0xfe, 0x2a, 0xf6, 0xff, 0xfd, 0xef, 0xe3, 0xcd, 0xd5, 0xb6, 0xe3, 0x37, 0x39, 0xc8, 0x5a, 0xdc, 0x63, 0xde, 0x2c, 0x19, 0xe6, 0x2d, 0xb2, 0xd9, 0x1c, 0x22, 0x89, 0x8c, 0x1a, 0x71, 0xe6, 0x6d, 0xb2, 0xc9, 0xbc, 0x1d, 0x3b, 0xb8, 0x6e, 0x17, 0x76, 0xe3, 0x04, 0xb7, 0x8b, 0x52, 0x9a, + 0x39, 0x51, 0xd6, 0xb1, 0x16, 0xf7, 0x98, 0xb3, 0xe4, 0x57, 0x73, 0x36, 0xce, 0x72, 0xf9, 0x82, 0xc4, 0x98, 0x2f, 0xc2, 0x05, 0x37, 0x2e, 0x71, 0xbc, 0x5c, 0xb6, 0x79, 0xc8, 0xc7, 0x65, 0x6e, 0xef, 0x61, 0x5b, 0x80, 0x42, 0x14, 0x71, 0xbc, 0x62, 0xae, 0x2b, 0x91, 0x02, 0xd6, 0xf1, 0x05, 0xe6, 0x32, 0xb9, 0xcc, 0x5a, 0xde, 0xc3, 0x5a, 0xde, 0xc5, 0x5a, 0xde, 0x65, 0xae, 0xe0, 0x36, 0x95, 0x72, 0x94, 0xf5, 0xfc, 0x51, 0xf3, 0x75, 0xf6, 0x6f, 0xc8, 0xc7, 0x66, 0x2f, 0xdb, 0x9b, 0xf8, 0x83, 0xbf, 0x93, 0x6f, 0x02, 0x94, 0x1c, 0x64, 0x64, 0xcb, 0x0b, 0xd0, 0x64, 0x73, 0x80, 0x99, 0xcb, 0x01, 0x08, 0x94, 0xcb, 0x8c, 0x20, 0x97, 0x03, 0x2c, 0x5c, 0x67, 0x95, 0x8f, 0x03, 0x82, 0xb8, 0xee, 0x45, 0xf1, 0x90, 0x03, 0x3c, 0xe4, 0x00, 0xdf, 0x67, 0xb3, 0x62, 0x02, 0xfa, 0xa0, + 0x1f, 0x06, 0x70, 0x9b, 0x81, 0x18, 0x0c, 0xfa, 0x52, 0xc0, 0x0a, 0x46, 0x9d, 0x66, 0xb2, 0x2d, 0xf0, 0x61, 0x39, 0x10, 0xd8, 0x5c, 0xbe, 0x0f, 0x6c, 0x21, 0x3f, 0x07, 0xb6, 0xe4, 0xba, 0x56, 0xb2, 0x2b, 0xf0, 0x11, 0xb4, 0x96, 0xfd, 0x81, 0x6d, 0x64, 0x76, 0x60, 0x5b, 0xb9, 0xf0, 0xe7, 0xf7, 0x18, 0x47, 0xaa, 0xce, 0xff, 0xec, 0xbb, 0x8c, 0xc9, 0x18, 0x1e, 0xe6, 0xe6, 0xfa, 0x81, 0xbf, 0x33, 0x6e, 0xd2, 0xa6, 0x03, 0x3d, 0x92, 0x10, 0x58, 0xc0, 0x28, 0x5c, 0xc8, 0xfe, 0x55, 0xfe, 0x56, 0xc1, 0xb6, 0x52, 0x22, 0xc9, 0x20, 0x9e, 0x1a, 0xb4, 0xb9, 0x1a, 0xcd, 0xe4, 0x66, 0x8d, 0x6b, 0xb2, 0xd9, 0xd2, 0x4a, 0x16, 0x58, 0x1e, 0x41, 0x6b, 0xb4, 0x41, 0x5b, 0x3c, 0x8a, 0x76, 0x68, 0x8f, 0xc7, 0xd0, 0x01, 0x8f, 0xcb, 0x1a, 0xcb, 0x13, 0x12, 0x6c, 0x79, 0x92, 0xfd, 0xa7, + 0xf0, 0x34, 0x9e, 0x41, 0x47, 0x74, 0xc2, 0xb3, 0x78, 0x0e, 0xcf, 0xa3, 0x33, 0xba, 0xa0, 0x2b, 0xba, 0xa1, 0x3b, 0x5e, 0x40, 0x0f, 0xbc, 0x88, 0x97, 0xd0, 0x53, 0xa2, 0xc9, 0x42, 0x0b, 0xc8, 0x42, 0x1b, 0x59, 0x3b, 0x44, 0xb3, 0x76, 0x38, 0x41, 0x26, 0x5a, 0x44, 0x26, 0xda, 0xc0, 0xda, 0xe1, 0x04, 0x6b, 0x87, 0xb5, 0xac, 0x1d, 0xfe, 0x60, 0xed, 0xb0, 0x80, 0xb5, 0xc3, 0x02, 0xd6, 0x0e, 0x0b, 0x58, 0x3b, 0x44, 0x93, 0x97, 0x16, 0x90, 0x97, 0x36, 0xb2, 0x76, 0x88, 0x66, 0xed, 0x70, 0x82, 0xdc, 0xb4, 0x88, 0xdc, 0xb4, 0x81, 0xb5, 0xc3, 0x09, 0xcb, 0x48, 0xce, 0x6f, 0x14, 0xde, 0xe4, 0xb6, 0x6f, 0xc9, 0x87, 0x96, 0xb7, 0xf1, 0x8e, 0x7c, 0x44, 0xa6, 0xf2, 0x90, 0xa9, 0x3c, 0x64, 0x2a, 0x0f, 0x99, 0xca, 0x43, 0xa6, 0xf2, 0x90, 0xa9, 0x3c, 0x64, 0x2a, 0x0f, 0x99, 0x2a, + 0xc2, 0xb2, 0xca, 0x14, 0x66, 0x59, 0x6d, 0x8a, 0x20, 0x53, 0xfd, 0x6a, 0x59, 0x6b, 0xda, 0x66, 0x59, 0x67, 0x3a, 0x6c, 0x59, 0x4f, 0xa6, 0xd9, 0xa0, 0xb6, 0x93, 0xab, 0x3c, 0xe4, 0x2a, 0x0f, 0xb9, 0xca, 0x43, 0xae, 0xf2, 0x90, 0xab, 0x3c, 0xe4, 0x2a, 0x0f, 0xb9, 0xca, 0xc3, 0x3a, 0xe5, 0x03, 0xb2, 0x55, 0x84, 0xe5, 0x37, 0xee, 0xbf, 0x8f, 0xfb, 0xef, 0x57, 0xfb, 0xc8, 0x56, 0xbf, 0x5a, 0x0e, 0x72, 0x8c, 0x43, 0x1c, 0x23, 0x9c, 0xfd, 0xc3, 0x1c, 0x27, 0x42, 0xed, 0x21, 0x5f, 0x45, 0x58, 0x8e, 0x71, 0xbb, 0xe3, 0xdc, 0xee, 0x04, 0xb7, 0x8b, 0xe4, 0x6f, 0x51, 0xdc, 0xee, 0x24, 0xb7, 0x8b, 0x66, 0x3f, 0x59, 0x7d, 0x49, 0xc6, 0x4a, 0xb4, 0x9c, 0x31, 0x1d, 0xb0, 0x9c, 0x35, 0x25, 0x92, 0xb1, 0xa2, 0xc9, 0x58, 0xc7, 0x2d, 0xe7, 0x4c, 0x87, 0x2c, 0xbf, 0x9b, 0x12, 0xc8, + 0x58, 0xc7, 0xc9, 0x58, 0x5f, 0x91, 0xb1, 0x4e, 0x5a, 0xf2, 0x65, 0x11, 0xb3, 0xd4, 0x22, 0x8b, 0x47, 0x16, 0x91, 0xb7, 0x3c, 0xe4, 0x2d, 0x0f, 0x79, 0xcb, 0x43, 0xde, 0xf2, 0x90, 0xb7, 0x3c, 0xe4, 0x2d, 0x0f, 0x79, 0xcb, 0x43, 0xde, 0xf2, 0x90, 0xb7, 0x3c, 0xac, 0x9b, 0xba, 0x91, 0xb7, 0x3c, 0xe4, 0x2d, 0x0f, 0x79, 0xcb, 0x43, 0xde, 0xf2, 0x90, 0xb7, 0x3c, 0xe4, 0x2d, 0x0f, 0x79, 0xcb, 0x43, 0xde, 0xf2, 0x90, 0xb7, 0x3c, 0xe4, 0x2d, 0x0f, 0x79, 0xcb, 0x43, 0xde, 0xf2, 0x90, 0xb7, 0x3c, 0xe4, 0x2d, 0x0f, 0x79, 0xcb, 0x43, 0xde, 0xf2, 0x90, 0xb7, 0x3c, 0xe4, 0x2d, 0x0f, 0x79, 0xcb, 0x43, 0xde, 0xf2, 0xe8, 0xad, 0xd4, 0x4a, 0xfd, 0x11, 0xd3, 0x4c, 0xbd, 0xb5, 0x69, 0x85, 0xde, 0x86, 0xac, 0xd5, 0xd6, 0x34, 0x5b, 0x7f, 0xd4, 0xb4, 0x5c, 0x6f, 0xa7, 0x7a, 0xeb, 0xed, + 0xd5, 0x6a, 0xf2, 0x96, 0x87, 0xbc, 0xe5, 0xd1, 0xc7, 0x29, 0x43, 0xff, 0x14, 0x53, 0x94, 0x55, 0x9f, 0x8a, 0x69, 0xf8, 0x1a, 0xd3, 0xf1, 0x8d, 0x3a, 0xa5, 0xcf, 0x60, 0xfb, 0x2d, 0x66, 0x62, 0x16, 0x66, 0x63, 0x0e, 0xe6, 0x9a, 0x6a, 0xeb, 0xdf, 0x69, 0x4d, 0xf4, 0x79, 0x5a, 0x7d, 0x7d, 0x3e, 0xfb, 0xdf, 0xab, 0x4b, 0xfa, 0x02, 0x53, 0x4d, 0x7d, 0x21, 0x7f, 0x5b, 0x84, 0x60, 0x56, 0x10, 0xcb, 0x99, 0xa9, 0x57, 0x60, 0x25, 0x56, 0x61, 0x35, 0xd6, 0x60, 0x2d, 0xd6, 0x61, 0x3d, 0x36, 0x60, 0x23, 0x36, 0x61, 0x33, 0xb6, 0xa8, 0x36, 0x7a, 0x08, 0xdb, 0x50, 0x84, 0x61, 0x2b, 0xb6, 0x61, 0x3b, 0x76, 0x60, 0x27, 0x76, 0xe1, 0x67, 0xfc, 0x82, 0x5f, 0xb1, 0x1b, 0x7b, 0xf1, 0x1b, 0xf6, 0x61, 0x3f, 0xcf, 0xeb, 0x00, 0xdb, 0x83, 0x38, 0x84, 0x70, 0x1c, 0x93, 0xcd, 0xfa, 0x71, + 0x9c, 0x40, 0x24, 0x58, 0xbf, 0xe8, 0xd1, 0x88, 0x41, 0x2c, 0xe2, 0x10, 0x0f, 0x1b, 0x12, 0x61, 0x47, 0x12, 0x92, 0x91, 0x82, 0x54, 0xa4, 0xc1, 0x81, 0x74, 0x64, 0x20, 0x13, 0x59, 0xc8, 0xc6, 0x69, 0x9c, 0xc1, 0x59, 0x38, 0x95, 0x45, 0xcf, 0x61, 0x7b, 0x0e, 0xf4, 0x77, 0xfd, 0x3c, 0x98, 0xd3, 0x74, 0x17, 0xdc, 0xa0, 0xff, 0xeb, 0x8c, 0xfd, 0x7a, 0x1e, 0xf2, 0x71, 0x99, 0x15, 0xd8, 0x7f, 0xdf, 0xf7, 0x9c, 0x6f, 0x37, 0x14, 0x4c, 0xd0, 0x60, 0x46, 0x00, 0x02, 0x51, 0x43, 0xb6, 0x18, 0x16, 0xe8, 0x30, 0x60, 0x95, 0xcd, 0xc6, 0xed, 0x6c, 0xef, 0xc0, 0x9d, 0xf0, 0xfd, 0x16, 0x8c, 0xef, 0xdb, 0x94, 0x1b, 0xb1, 0x4a, 0xf6, 0xad, 0xd3, 0x9b, 0xb0, 0x46, 0x6f, 0xea, 0xff, 0x1d, 0xa8, 0x8e, 0xfe, 0x6f, 0xee, 0x6e, 0xad, 0x8e, 0x1a, 0x4f, 0x4b, 0x9c, 0xd1, 0x4d, 0x62, 0x8c, + 0xee, 0x78, 0x01, 0x3d, 0xf0, 0x32, 0x7a, 0xe2, 0x15, 0x30, 0x9e, 0x1a, 0xbd, 0xc1, 0x98, 0x6a, 0xf4, 0x05, 0xe3, 0xaa, 0xd1, 0x1f, 0x03, 0x31, 0x88, 0xc7, 0x63, 0x6c, 0x35, 0x86, 0xe0, 0x55, 0x0c, 0xc5, 0x30, 0xbc, 0x86, 0xd7, 0x31, 0x1c, 0x6f, 0x60, 0x04, 0x46, 0x62, 0x14, 0xde, 0xc4, 0x5b, 0xac, 0xdc, 0xdf, 0x66, 0xfb, 0x0e, 0xde, 0xc5, 0x7b, 0x78, 0x1f, 0xa3, 0xf1, 0x01, 0x3e, 0xc4, 0x18, 0x7c, 0x84, 0x8f, 0x31, 0x16, 0xac, 0x7b, 0x0c, 0xd6, 0x3d, 0x06, 0xeb, 0x1e, 0xe3, 0x33, 0x30, 0x7f, 0x1a, 0xcc, 0x9f, 0xc6, 0x78, 0x4c, 0xc0, 0x44, 0x4c, 0xc2, 0x64, 0x89, 0x30, 0xbe, 0x64, 0xfb, 0x15, 0xe7, 0xc7, 0xbc, 0x6a, 0x4c, 0xc5, 0x74, 0x7c, 0x83, 0x19, 0x92, 0x66, 0x7c, 0xcb, 0x76, 0x26, 0x66, 0x61, 0x36, 0xe6, 0xe0, 0x3b, 0xcc, 0x53, 0x2d, 0x8d, 0xf9, 0x64, 0xec, + 0xef, 0xb1, 0x00, 0x0b, 0xb1, 0x08, 0xcc, 0xbd, 0xc6, 0x0f, 0x58, 0x8c, 0x1f, 0xb1, 0x04, 0xcb, 0x24, 0xd7, 0x58, 0x8e, 0x15, 0x58, 0x89, 0x55, 0x58, 0x8d, 0x35, 0x58, 0x8b, 0x75, 0x58, 0x8f, 0x0d, 0xd8, 0x88, 0x4d, 0xd8, 0x8c, 0x2d, 0x08, 0x41, 0x28, 0xc2, 0xb0, 0x15, 0xcc, 0xd3, 0x06, 0xf3, 0xb4, 0xb1, 0x03, 0x3b, 0xb1, 0x0b, 0x3f, 0xe3, 0x17, 0xfc, 0x8a, 0xdd, 0xd8, 0x83, 0xbd, 0xf8, 0x0d, 0xfb, 0xb0, 0x1f, 0x07, 0x70, 0x10, 0x87, 0x10, 0x8e, 0xc3, 0x88, 0xc0, 0x11, 0x49, 0x30, 0x8e, 0xe2, 0x18, 0x8e, 0xe3, 0x04, 0x22, 0x11, 0x85, 0x93, 0x88, 0x16, 0xb7, 0x11, 0xc3, 0x36, 0x16, 0x71, 0x88, 0x87, 0x0d, 0x09, 0x48, 0x84, 0x1d, 0x49, 0x48, 0x46, 0x0a, 0x52, 0xe1, 0x40, 0xba, 0x64, 0x18, 0xe7, 0xe4, 0xb2, 0xf1, 0x3b, 0xce, 0xa3, 0x4c, 0x72, 0x8c, 0x2b, 0x28, 0xc7, + 0x55, 0x54, 0xa0, 0x52, 0x72, 0xc8, 0x7a, 0xad, 0xac, 0x0f, 0x88, 0xdb, 0x5a, 0x4f, 0xbd, 0x6d, 0xad, 0x6f, 0xea, 0x6d, 0x6d, 0x60, 0x7a, 0xdb, 0xda, 0x50, 0x8d, 0xb0, 0x3e, 0xa8, 0x86, 0x59, 0x1b, 0x99, 0xfa, 0x5a, 0x1b, 0x9b, 0xde, 0xb4, 0x36, 0x61, 0xbf, 0xa9, 0x7a, 0xdc, 0xfa, 0x90, 0xfa, 0xd4, 0xda, 0x4c, 0x2e, 0x59, 0x1f, 0xe6, 0xb6, 0xcd, 0xb9, 0x6d, 0x0b, 0x6e, 0xdb, 0x92, 0xdb, 0xb6, 0xe2, 0xef, 0x8f, 0x70, 0xdb, 0xd6, 0xdc, 0xb6, 0x0d, 0xfb, 0x6d, 0x65, 0x93, 0xf5, 0x51, 0xb4, 0x43, 0x7b, 0x3c, 0x86, 0x0e, 0x78, 0x1c, 0x4f, 0xe0, 0x49, 0x3c, 0x85, 0xa7, 0xf1, 0x0c, 0x3a, 0xa2, 0x13, 0x9e, 0xc5, 0x73, 0x78, 0x1e, 0x9d, 0xd1, 0x05, 0x5d, 0xd1, 0x0d, 0xdd, 0xf1, 0x02, 0x7a, 0xe0, 0x45, 0xbc, 0x84, 0x97, 0xe5, 0x17, 0x6b, 0x4f, 0xbc, 0x82, 0x5e, 0xe8, 0x8d, + 0x3e, 0xe8, 0x8b, 0x7e, 0xe8, 0x8f, 0x01, 0xf2, 0xbb, 0x75, 0x20, 0x06, 0x61, 0x30, 0x86, 0xe0, 0x55, 0x0c, 0xc5, 0x30, 0xbc, 0x86, 0xd7, 0x31, 0x1c, 0x6f, 0x60, 0x04, 0x46, 0x62, 0x14, 0xde, 0xc4, 0x5b, 0x78, 0x1b, 0xef, 0xe0, 0x5d, 0xbc, 0x87, 0xf7, 0x31, 0x1a, 0x1f, 0xe0, 0x43, 0x8c, 0x91, 0x78, 0xf2, 0xf2, 0xfd, 0xd6, 0x4f, 0xd8, 0x8e, 0x93, 0x0a, 0xeb, 0xa7, 0x4a, 0xb3, 0x7e, 0xc6, 0xfe, 0xe7, 0xa8, 0xfa, 0xee, 0xbf, 0x00, 0xeb, 0x04, 0xf6, 0x27, 0x72, 0x9b, 0xaa, 0xef, 0x22, 0xaa, 0x6f, 0xfd, 0x92, 0xcb, 0x5f, 0x49, 0x96, 0x75, 0x0a, 0xb7, 0x9f, 0xaa, 0xee, 0xb6, 0x4e, 0x53, 0xad, 0xad, 0xd3, 0x79, 0x4e, 0xdf, 0x60, 0x86, 0x24, 0x58, 0xbf, 0xc5, 0x4c, 0xcc, 0x02, 0xeb, 0x7d, 0x2b, 0xeb, 0x7d, 0xeb, 0x5c, 0x7c, 0x87, 0x79, 0x98, 0x8f, 0xef, 0xb1, 0x00, 0x0b, + 0xb1, 0x08, 0xc1, 0xf8, 0x01, 0x8b, 0xf1, 0x23, 0x96, 0x60, 0x99, 0xec, 0xb5, 0x2e, 0xc7, 0x0a, 0xac, 0xc4, 0x2a, 0xac, 0xc6, 0x1a, 0xac, 0xc5, 0x3a, 0xac, 0xc7, 0x06, 0x6c, 0xc4, 0x26, 0x6c, 0xc6, 0x16, 0x84, 0x20, 0x14, 0x61, 0xd8, 0x8a, 0x6d, 0xd8, 0x8e, 0x1d, 0xd8, 0x89, 0x5d, 0x38, 0x28, 0x1e, 0xeb, 0x21, 0x75, 0x87, 0x35, 0x1c, 0x87, 0x11, 0x81, 0x48, 0x89, 0xb1, 0x46, 0x49, 0x86, 0xf5, 0x24, 0x62, 0xe5, 0xb8, 0x35, 0x8e, 0xcb, 0xf1, 0x48, 0x40, 0x22, 0xec, 0x48, 0x11, 0x97, 0x35, 0x95, 0x6d, 0x9a, 0x9c, 0xb0, 0x9e, 0x92, 0x22, 0x6b, 0x3e, 0xc7, 0x61, 0x4d, 0x6a, 0xf5, 0xa0, 0x00, 0x85, 0xb8, 0x22, 0x9b, 0xad, 0xe5, 0xb8, 0x0a, 0xd6, 0x67, 0xd6, 0x4a, 0x5c, 0xe3, 0x39, 0x5d, 0xc7, 0x0d, 0x78, 0x71, 0x53, 0x12, 0x82, 0x7e, 0x90, 0xc8, 0xa0, 0x5d, 0x6a, 0x4f, + 0xd0, 0x2f, 0xda, 0xdd, 0x41, 0xbf, 0x6a, 0x8d, 0x82, 0x76, 0x9b, 0x02, 0x83, 0xf6, 0xa8, 0xc4, 0xa0, 0xbd, 0xa6, 0x9a, 0x41, 0xfb, 0xd9, 0x3f, 0x24, 0x9e, 0xa0, 0xc3, 0x72, 0x30, 0x28, 0x42, 0x36, 0x07, 0x31, 0xbf, 0xa8, 0x20, 0x35, 0x4a, 0xdd, 0x43, 0x36, 0x6f, 0x45, 0x36, 0x6f, 0xc1, 0x8a, 0x31, 0x88, 0x59, 0xef, 0x29, 0x66, 0x8d, 0xbb, 0xfd, 0xbf, 0x0c, 0xd7, 0x5a, 0x8d, 0xf0, 0xfd, 0x22, 0x9c, 0xf1, 0x16, 0xd6, 0xa8, 0xbb, 0x4c, 0x8b, 0xc8, 0xaf, 0x2e, 0xf2, 0xab, 0x8b, 0xfc, 0xea, 0x22, 0xbb, 0xba, 0xc8, 0xae, 0x2e, 0xb2, 0xab, 0x8b, 0xec, 0xea, 0x52, 0x4d, 0x65, 0x3a, 0x09, 0xfb, 0x7b, 0xd5, 0x4c, 0xdc, 0xea, 0x61, 0x34, 0x97, 0x69, 0xaa, 0x05, 0x79, 0xa9, 0x95, 0x8c, 0x21, 0x93, 0xba, 0xc9, 0xa4, 0x6e, 0x32, 0xa9, 0x9b, 0x4c, 0xea, 0x26, 0x93, 0xba, 0xc9, + 0xa4, 0x6e, 0x32, 0xa9, 0x9b, 0x4c, 0xea, 0x26, 0x93, 0xba, 0xc9, 0xa4, 0x6e, 0x32, 0xa9, 0x9b, 0x4c, 0xea, 0x26, 0x93, 0xba, 0xc9, 0xa4, 0x6e, 0x32, 0xa9, 0x9b, 0x4c, 0xea, 0x26, 0x93, 0xba, 0xc9, 0xa4, 0x6e, 0x32, 0xa9, 0x9b, 0x4c, 0xea, 0x26, 0x93, 0xba, 0xc9, 0xa4, 0x6e, 0x32, 0xa9, 0x9b, 0x4c, 0xea, 0x26, 0x93, 0xba, 0xc9, 0xa4, 0x6e, 0x32, 0xa9, 0x9b, 0x4c, 0xea, 0x26, 0x93, 0xba, 0xc9, 0xa4, 0x6e, 0x32, 0xa9, 0x9b, 0x4c, 0xea, 0x26, 0x53, 0xba, 0xc9, 0x94, 0x4e, 0x32, 0xa5, 0x93, 0x4c, 0xe9, 0x24, 0x53, 0x3a, 0xc9, 0x94, 0x4e, 0x32, 0xa5, 0x93, 0x4c, 0xe9, 0x24, 0x53, 0x3a, 0xc9, 0x94, 0x4e, 0x32, 0xa5, 0x53, 0xcd, 0x95, 0x3d, 0xea, 0x3b, 0xb2, 0xe0, 0x7c, 0x39, 0x40, 0x96, 0x74, 0x91, 0x23, 0x2f, 0xaa, 0x24, 0x32, 0x6d, 0x1a, 0xc7, 0x70, 0x90, 0x6b, 0x4f, + 0xf9, 0xbf, 0x3b, 0xed, 0x3c, 0xb9, 0x31, 0x5a, 0x39, 0xb9, 0xad, 0xef, 0x3b, 0x45, 0x72, 0xc9, 0x99, 0xf9, 0xdc, 0xf6, 0x06, 0x6e, 0x92, 0x2f, 0x45, 0x12, 0x4d, 0x9a, 0xac, 0x31, 0x05, 0xca, 0x22, 0xb2, 0xa0, 0xcb, 0x74, 0x9b, 0x4c, 0x37, 0xdd, 0x0e, 0xdf, 0xe7, 0x6c, 0x1e, 0x94, 0x30, 0x32, 0xdc, 0x11, 0xb2, 0x9b, 0xef, 0xd3, 0x7b, 0xeb, 0x4d, 0xcf, 0xe3, 0x0d, 0x6e, 0x3b, 0x02, 0x5f, 0xc8, 0x1e, 0xd3, 0x97, 0x52, 0x6c, 0x9a, 0x43, 0x86, 0x9c, 0x4b, 0x46, 0x5b, 0xc8, 0xbe, 0x9b, 0xfb, 0x5c, 0x12, 0xb7, 0xd6, 0x85, 0x1c, 0xd5, 0x5d, 0x5c, 0xda, 0x0b, 0x6c, 0x7b, 0xaa, 0x6e, 0x64, 0x33, 0x17, 0xd9, 0xcc, 0xa5, 0x91, 0x13, 0xb4, 0xd7, 0x64, 0x0c, 0xf9, 0xcc, 0xa5, 0xbd, 0x2b, 0x67, 0xb5, 0xd1, 0xec, 0x7f, 0xc0, 0xed, 0x79, 0x9e, 0xe4, 0x32, 0xa7, 0x36, 0x5b, 0xdd, + 0xae, 0xcd, 0x21, 0xcf, 0x2d, 0x91, 0x03, 0x64, 0x30, 0x37, 0x19, 0xcc, 0x4d, 0x06, 0x73, 0x93, 0xc1, 0xdc, 0x64, 0x30, 0x37, 0x19, 0xcc, 0x4d, 0x06, 0x73, 0x93, 0xc1, 0xdc, 0x64, 0x30, 0x37, 0x19, 0xcc, 0x4d, 0x06, 0x73, 0x92, 0xc1, 0x9c, 0xe4, 0x2f, 0xdf, 0xb7, 0x6a, 0xba, 0xc9, 0x5d, 0xd1, 0xe6, 0x23, 0x32, 0x9d, 0x9c, 0xe5, 0x32, 0x47, 0xb2, 0x8d, 0x92, 0x79, 0xe6, 0x68, 0xb6, 0x31, 0xb2, 0xc6, 0x1c, 0x8b, 0x78, 0xd9, 0x6b, 0xb6, 0x21, 0x99, 0x7d, 0x07, 0xb7, 0xb9, 0x2c, 0x61, 0x64, 0xa8, 0x30, 0x32, 0xd4, 0x6f, 0x64, 0xa8, 0xdf, 0xc8, 0x4f, 0x61, 0xe4, 0xa7, 0x23, 0xe4, 0xa7, 0x23, 0x64, 0xa7, 0x08, 0xb2, 0x53, 0x04, 0x59, 0xe9, 0x88, 0x3f, 0x23, 0xbd, 0x28, 0x2e, 0x32, 0x90, 0x8b, 0x0c, 0xe4, 0x22, 0xdf, 0xe4, 0x04, 0x76, 0x90, 0xe0, 0xc0, 0xc7, 0x71, 0x54, + 0xdc, 0xe4, 0x18, 0x77, 0x60, 0x94, 0xe4, 0x91, 0x63, 0xdc, 0xe4, 0x18, 0x37, 0x39, 0xc6, 0x4d, 0x8e, 0x71, 0x93, 0x63, 0xdc, 0xe4, 0x18, 0x17, 0x79, 0xc5, 0xf7, 0x4d, 0xb9, 0x2e, 0x32, 0x8a, 0x93, 0xb5, 0xbb, 0x8b, 0xb5, 0xbb, 0x8b, 0xb5, 0xbb, 0x8b, 0xb5, 0xbb, 0x8b, 0xb5, 0xbb, 0x8b, 0xb5, 0xbb, 0x8b, 0xb5, 0xbb, 0x8b, 0xb5, 0xb9, 0x8b, 0xb5, 0xb9, 0x8b, 0xb5, 0xb9, 0x8b, 0xb5, 0xb9, 0x8b, 0xb5, 0xb9, 0x8b, 0xb5, 0xb9, 0x8b, 0xb5, 0xb9, 0x8b, 0x75, 0xb1, 0x8b, 0x75, 0xb1, 0x8b, 0x75, 0xb1, 0x8b, 0x75, 0xb1, 0x8b, 0x75, 0xb1, 0x8b, 0x75, 0xb1, 0x8b, 0x75, 0xb1, 0x8b, 0x75, 0xb1, 0x8b, 0x75, 0xb1, 0x4b, 0xaf, 0x8b, 0x7b, 0x70, 0x2f, 0xee, 0xc3, 0xfd, 0xa0, 0xfd, 0xb3, 0x2e, 0x76, 0xb1, 0x2e, 0x76, 0xb1, 0x2e, 0x76, 0xb1, 0x2e, 0x76, 0xb1, 0x2e, 0x76, 0xb1, 0x2e, + 0x76, 0xb1, 0x2e, 0x76, 0xb1, 0x2e, 0x76, 0xb1, 0x2e, 0x76, 0xb1, 0x2e, 0x76, 0xb1, 0x2e, 0x76, 0xb1, 0x2e, 0x76, 0xb1, 0x2e, 0x76, 0xb1, 0x2e, 0x76, 0xb1, 0x2e, 0x76, 0xe9, 0xad, 0xd0, 0x06, 0xed, 0xd0, 0x1e, 0x8f, 0xa1, 0x03, 0xfa, 0xc9, 0x74, 0xbd, 0x3f, 0x06, 0x60, 0x20, 0x06, 0x61, 0x08, 0x5e, 0xc5, 0x50, 0x0c, 0x03, 0x6d, 0x5b, 0xa7, 0x6d, 0xeb, 0xb4, 0x6d, 0xfd, 0x0d, 0x8c, 0xc0, 0x48, 0x8c, 0xc2, 0x9b, 0x78, 0x0b, 0x6f, 0xe3, 0x1d, 0xbc, 0x8b, 0xf7, 0xf0, 0x3e, 0x46, 0x83, 0xb6, 0xa1, 0x7f, 0x88, 0x31, 0xf8, 0x08, 0x1f, 0x63, 0x2c, 0x3e, 0xc1, 0x38, 0x99, 0xa6, 0x7f, 0x8a, 0xcf, 0x64, 0xbd, 0xfe, 0x39, 0xbe, 0xc0, 0x78, 0x4c, 0xc0, 0x44, 0x4c, 0x91, 0x31, 0xfa, 0x54, 0x4c, 0xc3, 0xd7, 0x98, 0xee, 0x7f, 0xad, 0x4d, 0xd3, 0x67, 0xb0, 0xfd, 0x16, 0x33, 0x31, + 0x0b, 0xb3, 0x31, 0x07, 0x73, 0x25, 0x51, 0xff, 0x1e, 0x0b, 0xb0, 0x90, 0xcb, 0x8b, 0x10, 0x2c, 0x1f, 0xb3, 0xc6, 0x74, 0xb3, 0xc6, 0x74, 0xb3, 0xc6, 0x74, 0xb3, 0xc6, 0x74, 0xb3, 0xc6, 0x74, 0xb3, 0xc6, 0x74, 0xb3, 0xc6, 0x74, 0xb3, 0xc6, 0x74, 0xb3, 0xc6, 0x74, 0xb3, 0xc6, 0x74, 0xb3, 0xc6, 0x74, 0xb3, 0xc6, 0x74, 0xb3, 0xc6, 0x74, 0xb3, 0xc6, 0x74, 0xb3, 0xc6, 0x74, 0xb3, 0xc6, 0x74, 0xb3, 0xc6, 0x74, 0xb3, 0xc6, 0x74, 0xb3, 0x9e, 0x71, 0xb2, 0x9e, 0x71, 0xb2, 0x9e, 0x71, 0xb2, 0x9e, 0x71, 0xb2, 0x9e, 0x71, 0xb2, 0x9e, 0x71, 0x32, 0x5a, 0x35, 0x66, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, + 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x64, 0x3d, 0xe3, 0x34, 0x32, 0x64, 0x8f, 0x91, 0x89, 0x2c, 0xd6, 0xa9, 0xd9, 0x38, 0x8d, 0x33, 0x38, 0x0b, 0x27, 0x72, 0x70, 0x51, 0x0e, 0x18, 0x2e, 0xb8, 0x71, 0x09, 0xb9, 0xc8, 0x43, 0x3e, 0x2e, 0xc3, 0x83, 0x02, 0x14, 0xa2, 0x08, 0xc5, 0x28, 0x41, 0xa9, 0x1c, 0x60, 0x3e, 0x77, 0x33, 0x9f, 0xbb, 0x99, 0xcf, 0xdd, 0xcc, 0xe7, 0x6e, 0xe6, 0x73, 0x37, 0xf3, 0xb9, 0x9b, 0xf9, 0xdc, 0xcd, 0x7c, 0xee, 0x66, 0x3e, 0x77, 0x33, 0x9f, 0xbb, + 0x99, 0xcf, 0xdd, 0xcc, 0xe7, 0x6e, 0xe6, 0x73, 0x37, 0xf3, 0xb9, 0x9b, 0xf9, 0xdc, 0xcd, 0x7c, 0xee, 0x66, 0x3e, 0x77, 0x33, 0x9f, 0xbb, 0x99, 0xcf, 0xdd, 0xcc, 0xe7, 0x6e, 0xe6, 0x73, 0x37, 0xf3, 0xb9, 0x9b, 0xf9, 0xdc, 0xcd, 0x7c, 0xee, 0x66, 0x3e, 0x77, 0x33, 0xa7, 0xb9, 0xac, 0x87, 0x24, 0xc9, 0x1a, 0x8e, 0xc3, 0x88, 0xc0, 0x11, 0x59, 0xc3, 0x9c, 0x75, 0x84, 0x79, 0xca, 0xc5, 0x3c, 0xe5, 0x62, 0x9e, 0x72, 0x31, 0x4f, 0xb9, 0x98, 0xa7, 0x5c, 0x56, 0xea, 0xd3, 0x4a, 0x7d, 0x5a, 0xa9, 0x4f, 0x2b, 0xf5, 0x69, 0xa5, 0x3e, 0x83, 0xba, 0xcb, 0x0c, 0xe6, 0x1d, 0x97, 0x7a, 0x9f, 0xd9, 0x61, 0x5d, 0xf5, 0xeb, 0xaf, 0x29, 0x8c, 0xa8, 0xf9, 0xd5, 0xff, 0x2e, 0xd0, 0x51, 0xfd, 0x6f, 0x02, 0x5d, 0xbe, 0xef, 0xc9, 0x51, 0x65, 0x72, 0x80, 0x51, 0x73, 0x99, 0xa9, 0x8e, 0x1c, + 0x66, 0x84, 0xcc, 0x33, 0x4d, 0x14, 0x0f, 0x23, 0xe2, 0xcf, 0xa6, 0x05, 0x12, 0xc5, 0x28, 0xe6, 0xfb, 0x84, 0xff, 0x72, 0xf3, 0x29, 0x29, 0x66, 0xe4, 0x28, 0x0f, 0xa8, 0x21, 0x57, 0xff, 0xfc, 0x44, 0x78, 0x94, 0x94, 0xff, 0x2f, 0xdf, 0x31, 0x65, 0xe5, 0xf2, 0x2f, 0xdf, 0x19, 0xfd, 0x0f, 0xbe, 0xfb, 0x49, 0x2a, 0x5f, 0xf2, 0xe7, 0xbb, 0x90, 0x6d, 0x55, 0xa8, 0xfe, 0xa8, 0x3a, 0x48, 0x1a, 0xb6, 0xd0, 0xeb, 0x67, 0xeb, 0x93, 0x24, 0xb9, 0xea, 0xf5, 0xe4, 0xbf, 0x7b, 0xd7, 0x6e, 0x59, 0xf5, 0xbb, 0x76, 0xad, 0xf4, 0x25, 0x52, 0xfe, 0xbf, 0xfb, 0x6e, 0x1a, 0xb5, 0xd0, 0xdb, 0xff, 0x4e, 0xd8, 0x2f, 0xa6, 0x2e, 0x41, 0xbf, 0x9a, 0x7a, 0xff, 0xdd, 0xbb, 0x60, 0xfb, 0x55, 0xfb, 0xff, 0xd6, 0xda, 0x29, 0xaa, 0xae, 0x9d, 0xf2, 0xff, 0x1f, 0xd7, 0x4e, 0x2d, 0x6a, 0xa7, 0xd1, + 0x7f, 0xa9, 0x9d, 0x7b, 0xa9, 0x9d, 0x9a, 0xff, 0xe7, 0xb5, 0x23, 0xd7, 0xff, 0x49, 0xed, 0xdc, 0xe3, 0xaf, 0x9d, 0x9a, 0xea, 0x43, 0x39, 0xa2, 0xc6, 0xb0, 0x6a, 0x98, 0x28, 0x05, 0x94, 0xf8, 0xaf, 0xac, 0x0e, 0x0a, 0x28, 0xf5, 0x98, 0x80, 0x2f, 0xe4, 0x48, 0x60, 0x1d, 0x39, 0x62, 0x3c, 0x83, 0x8e, 0xe8, 0x84, 0x67, 0xf1, 0x1c, 0x9e, 0x47, 0x67, 0x74, 0x41, 0x57, 0xee, 0x6f, 0x32, 0xe5, 0xaa, 0x00, 0xa5, 0x54, 0x91, 0xb9, 0x65, 0xcd, 0x8f, 0x1a, 0x9f, 0xe8, 0x70, 0xdb, 0x33, 0x0d, 0x3a, 0x76, 0xea, 0xf4, 0xda, 0x73, 0x0f, 0x3e, 0x37, 0xa3, 0x73, 0x58, 0xe7, 0x23, 0x9d, 0x1d, 0x9d, 0xcf, 0x77, 0x2e, 0xee, 0xfc, 0x47, 0x97, 0x4e, 0x5d, 0xfa, 0x74, 0x19, 0xdd, 0x65, 0x61, 0x97, 0xe5, 0x5d, 0x8e, 0x74, 0x39, 0xd7, 0xc5, 0xdd, 0xa5, 0xb8, 0x6b, 0x83, 0xae, 0x4d, 0xbb, + 0xf6, 0xe9, 0x3a, 0xae, 0xeb, 0xbc, 0xae, 0xa7, 0xba, 0xfe, 0xd1, 0xad, 0xf9, 0x2b, 0x75, 0x07, 0x8c, 0x7b, 0x6d, 0xe5, 0x9b, 0x3b, 0x46, 0x9f, 0xf8, 0xc4, 0x31, 0x79, 0xde, 0xb8, 0x5a, 0xe3, 0x0e, 0x4f, 0x9e, 0xf7, 0x69, 0x9b, 0x89, 0x17, 0x27, 0x8d, 0x98, 0x74, 0x71, 0xb2, 0x75, 0xf2, 0xe8, 0xc9, 0xf3, 0x26, 0x7b, 0xbf, 0xec, 0xf9, 0xe5, 0xd0, 0x2f, 0x3f, 0x5b, 0xfc, 0xc5, 0x57, 0x7b, 0xa7, 0x7e, 0xf6, 0x6d, 0xd8, 0xe2, 0x2f, 0xbe, 0x3d, 0xf4, 0xc3, 0x8e, 0x79, 0x67, 0x7e, 0x78, 0xef, 0x87, 0x1d, 0x8b, 0xeb, 0x2e, 0xfe, 0x62, 0x59, 0xbf, 0x65, 0xfd, 0x16, 0x67, 0x2f, 0xe9, 0xbc, 0x64, 0xec, 0x92, 0x99, 0x4b, 0x16, 0x2f, 0xab, 0xb3, 0xac, 0x07, 0xd7, 0x6c, 0x5b, 0x76, 0x6c, 0x59, 0xea, 0xb2, 0x2b, 0xcb, 0xad, 0x1b, 0x3f, 0x5c, 0x7e, 0xef, 0xf2, 0x21, 0xab, 0xc6, + 0xad, 0x9a, 0xb1, 0x3a, 0x61, 0xd5, 0xdc, 0x55, 0x27, 0x56, 0x07, 0xac, 0x7e, 0x62, 0x75, 0xf7, 0xd5, 0x4b, 0x57, 0x27, 0xac, 0x2e, 0x5d, 0x37, 0x73, 0xcd, 0x6d, 0x6b, 0xf2, 0xd7, 0xde, 0xbb, 0x76, 0xc8, 0xba, 0x99, 0x6b, 0x57, 0xaf, 0x75, 0xae, 0xbb, 0x6d, 0x5d, 0xd7, 0x75, 0x33, 0xd7, 0xcd, 0x0c, 0xbd, 0xbe, 0x6e, 0xed, 0xba, 0x1d, 0xeb, 0x0e, 0xad, 0x8b, 0xdd, 0xd8, 0x27, 0x34, 0x37, 0x74, 0xf1, 0xc6, 0xe1, 0x1b, 0x3f, 0xdc, 0x78, 0x6c, 0x63, 0xf6, 0x66, 0xc7, 0xe6, 0xc2, 0xcd, 0xde, 0xd0, 0xc3, 0x5b, 0xac, 0x5b, 0x9a, 0x6e, 0x79, 0x6e, 0x4b, 0xaf, 0x90, 0x4e, 0x5b, 0x76, 0x6c, 0x79, 0x6f, 0xcb, 0x17, 0x5b, 0x37, 0x6f, 0x99, 0xbd, 0x65, 0xc9, 0x96, 0xb5, 0x5b, 0x76, 0x84, 0xe6, 0x6e, 0x39, 0xb4, 0xc5, 0xb1, 0xa5, 0x30, 0x44, 0x0f, 0xa9, 0x1b, 0xf2, 0x60, 0x48, + 0x9b, 0xd0, 0x5d, 0x21, 0x9d, 0x42, 0xfa, 0x84, 0x0c, 0x0f, 0xf9, 0x30, 0x64, 0x66, 0xc8, 0xe2, 0x90, 0xf5, 0x21, 0x87, 0x42, 0xa2, 0x42, 0xec, 0x21, 0xa7, 0x42, 0x4a, 0x43, 0x03, 0x42, 0x6b, 0x85, 0x36, 0x0f, 0x7d, 0x22, 0xb4, 0x7b, 0xe8, 0x6b, 0xa1, 0x5f, 0x84, 0xce, 0x08, 0x9d, 0x1b, 0xba, 0x38, 0xf4, 0x54, 0xe8, 0xfa, 0xd0, 0xf8, 0xd0, 0x5d, 0xa1, 0x87, 0xf9, 0xff, 0xa9, 0xd0, 0xf3, 0xa1, 0xb9, 0xdb, 0x8e, 0x85, 0x0d, 0x09, 0xbd, 0x1e, 0xa6, 0x87, 0x35, 0x0e, 0xeb, 0xc4, 0x7f, 0x43, 0xc2, 0x46, 0x87, 0x4d, 0x08, 0x9b, 0x19, 0xb6, 0x36, 0xec, 0x97, 0xb0, 0x13, 0xfc, 0x77, 0x26, 0xec, 0xea, 0x56, 0x7d, 0x6b, 0xdd, 0xad, 0x4d, 0xb7, 0x3e, 0xb7, 0xf5, 0x9d, 0xad, 0xd3, 0xb6, 0x6e, 0xe6, 0xbf, 0xdd, 0x5b, 0x4f, 0x6d, 0x2d, 0xdd, 0x5a, 0xba, 0x4d, 0x6d, 0xb3, 0x6e, + 0x7b, 0x74, 0xdb, 0x90, 0x6d, 0xa3, 0xb7, 0xcd, 0xd8, 0x16, 0xbc, 0x6d, 0xe5, 0xb6, 0x63, 0xdb, 0x52, 0xb7, 0x79, 0xb6, 0xd7, 0xda, 0xfe, 0xc8, 0xf6, 0x67, 0xb6, 0x0f, 0xda, 0x3e, 0x76, 0xfb, 0x92, 0xed, 0xbb, 0xb6, 0x1f, 0xde, 0x1e, 0xbf, 0xe3, 0xcc, 0xcf, 0xbb, 0x77, 0xff, 0xb2, 0x3b, 0x7b, 0xef, 0x84, 0xbd, 0x9d, 0x51, 0xf5, 0xdf, 0x2f, 0xbf, 0xb9, 0x0f, 0x0c, 0x3a, 0x90, 0x1d, 0xfe, 0xa8, 0xd2, 0xd4, 0xe3, 0xa6, 0x7e, 0xa6, 0x7e, 0x4a, 0x99, 0x06, 0x98, 0x06, 0x50, 0xeb, 0x83, 0x4c, 0x83, 0x94, 0x66, 0x1a, 0x62, 0x1a, 0xa2, 0xcc, 0xa6, 0x37, 0x4c, 0xa3, 0x54, 0x80, 0xe9, 0x5d, 0xd3, 0x7b, 0x4a, 0x37, 0x7d, 0x6e, 0x9a, 0xa2, 0xac, 0xa6, 0x39, 0xa6, 0x39, 0xaa, 0x8e, 0xe9, 0x3b, 0xd3, 0x42, 0x75, 0xb7, 0xc9, 0x6d, 0xca, 0x53, 0x0f, 0x98, 0x0a, 0x4c, 0xa5, 0xaa, + 0xa1, 0xd6, 0x45, 0xeb, 0xa2, 0x1e, 0xd2, 0xba, 0x69, 0x2f, 0xa8, 0x66, 0xda, 0x4b, 0x5a, 0x2f, 0xd5, 0x42, 0xeb, 0xa3, 0xf5, 0x51, 0x6d, 0xb4, 0x7e, 0x5a, 0x7f, 0xd5, 0x56, 0x1b, 0xa8, 0x0d, 0x54, 0xed, 0xb4, 0xa1, 0xda, 0xeb, 0xaa, 0xbd, 0x36, 0x52, 0x1b, 0xa9, 0x9e, 0xd0, 0xde, 0xd4, 0xde, 0x54, 0x4f, 0x6a, 0x6f, 0x6b, 0xef, 0xaa, 0xa7, 0xb4, 0xf7, 0xb5, 0x8f, 0x54, 0x47, 0x6d, 0x9c, 0xf6, 0xa9, 0xea, 0xa2, 0x7d, 0xae, 0x8d, 0x67, 0xb5, 0x3a, 0x51, 0xfb, 0x52, 0xf5, 0xd0, 0xa6, 0x68, 0x53, 0x54, 0x4f, 0x6d, 0x9a, 0xf6, 0xb5, 0x7a, 0x45, 0xfb, 0x46, 0xfb, 0x56, 0xf5, 0xd6, 0x66, 0x69, 0xf3, 0x54, 0x3f, 0x2d, 0x58, 0x0b, 0x56, 0x43, 0xb4, 0x1f, 0xb5, 0x25, 0xea, 0x55, 0x6d, 0x99, 0xb6, 0x4c, 0x0d, 0xd3, 0x56, 0x68, 0xab, 0xd5, 0x6b, 0xda, 0x7a, 0x2d, 0x42, 0x8d, + 0xd0, 0x8e, 0x6a, 0x47, 0xd5, 0x14, 0xed, 0xb8, 0x76, 0x42, 0x4d, 0xd5, 0xa2, 0xb4, 0x28, 0xf5, 0xb5, 0x16, 0xa3, 0x25, 0xaa, 0xe9, 0x5a, 0x92, 0x76, 0x51, 0xcd, 0xd1, 0x2e, 0x69, 0x05, 0x6a, 0x8d, 0x56, 0xac, 0x95, 0xaa, 0xcd, 0xda, 0x15, 0xad, 0x42, 0x85, 0x6a, 0xd7, 0xb4, 0x1b, 0x6a, 0xbb, 0x76, 0xd3, 0xdc, 0x4c, 0xed, 0x32, 0x0f, 0x34, 0x0f, 0x56, 0xa7, 0xcc, 0xc3, 0xcc, 0xc3, 0xd5, 0x69, 0xf3, 0x1a, 0xf3, 0x1a, 0x95, 0x63, 0x5e, 0x67, 0x5e, 0xa7, 0xce, 0x99, 0x37, 0x99, 0x43, 0xd5, 0xef, 0xe6, 0x6d, 0xe6, 0x1d, 0xca, 0x6d, 0xde, 0x65, 0xde, 0xa5, 0xf2, 0xcc, 0xbb, 0xcd, 0xbb, 0x55, 0xbe, 0x79, 0x9f, 0xf9, 0xa0, 0xba, 0x6c, 0x0e, 0x26, 0xe9, 0x64, 0x92, 0x74, 0x32, 0x49, 0x3a, 0x99, 0x24, 0x9d, 0x4c, 0x92, 0x4e, 0x26, 0x49, 0x27, 0x93, 0xa4, 0x93, 0xa9, 0x9a, + 0xab, 0x3b, 0x55, 0x2b, 0xb4, 0x96, 0x6c, 0xd5, 0x06, 0x6d, 0xd1, 0x4e, 0xa2, 0x54, 0x7b, 0x3c, 0x86, 0x0e, 0x78, 0x1c, 0x4f, 0xe0, 0x49, 0x89, 0x55, 0x4f, 0xe1, 0x69, 0x3c, 0x23, 0x76, 0xd5, 0x91, 0xeb, 0x3a, 0xe1, 0x59, 0xc9, 0x21, 0x11, 0xe5, 0x90, 0x88, 0x72, 0x48, 0x44, 0x39, 0x24, 0xa2, 0x1c, 0x12, 0x51, 0x0e, 0x89, 0x28, 0x87, 0x44, 0x94, 0x43, 0x22, 0xca, 0x21, 0x11, 0xe5, 0x90, 0x88, 0x72, 0x48, 0x44, 0x39, 0x24, 0xa2, 0x1c, 0x12, 0x51, 0x0e, 0x89, 0x28, 0x87, 0x44, 0x94, 0x43, 0x22, 0xca, 0x21, 0x11, 0xe5, 0x90, 0x88, 0x72, 0x48, 0x44, 0x39, 0x24, 0xa2, 0x1c, 0x12, 0x51, 0x0e, 0x89, 0x28, 0x87, 0x44, 0x94, 0x43, 0x22, 0xca, 0x21, 0x11, 0xe5, 0x90, 0x88, 0x72, 0x48, 0x44, 0x39, 0x24, 0xa2, 0x1c, 0x12, 0x51, 0x0e, 0x89, 0x28, 0x87, 0x44, 0x94, 0xa3, 0x46, + 0x48, 0x29, 0xd9, 0xaf, 0xb1, 0xef, 0x37, 0x89, 0x19, 0xfb, 0x9b, 0xa9, 0x0f, 0xb8, 0xfc, 0x11, 0x3e, 0xc6, 0x58, 0x7c, 0xc2, 0x39, 0x8e, 0xc3, 0xa7, 0xf8, 0x8c, 0xcb, 0x5f, 0x70, 0xfe, 0xe3, 0xd9, 0x4e, 0x90, 0xd3, 0x6a, 0x22, 0x26, 0x61, 0x32, 0xbe, 0xe2, 0x58, 0x53, 0x24, 0x8b, 0x64, 0x95, 0x45, 0xb2, 0xca, 0x22, 0x59, 0x65, 0x91, 0xac, 0xb2, 0x48, 0x56, 0x59, 0x24, 0xab, 0x2c, 0x92, 0x55, 0x16, 0xc9, 0x2a, 0x8b, 0x64, 0x95, 0xa5, 0x66, 0x33, 0x9b, 0xcc, 0xc1, 0x5c, 0x49, 0x23, 0x61, 0x65, 0x93, 0xb0, 0x0a, 0x49, 0x58, 0x99, 0x24, 0xac, 0x0c, 0x95, 0x42, 0x9a, 0x4a, 0x93, 0x74, 0x66, 0x9d, 0xe4, 0xea, 0x6f, 0xe3, 0x4f, 0x22, 0x69, 0x65, 0x90, 0xb4, 0x4e, 0x91, 0xb4, 0xe2, 0xd5, 0x69, 0x6e, 0x77, 0x96, 0xc7, 0xf7, 0xfd, 0x2b, 0xc2, 0x73, 0x72, 0x56, 0x5d, 0xe0, + 0xbc, 0x2e, 0xc2, 0xf7, 0x4d, 0x19, 0x6e, 0xae, 0xbb, 0xc4, 0x7e, 0xae, 0x1c, 0x27, 0x85, 0xd9, 0xd5, 0x65, 0x6e, 0x7f, 0x83, 0xdb, 0xdf, 0x54, 0xb5, 0x49, 0x62, 0xa9, 0x24, 0xb0, 0x4c, 0x92, 0x97, 0xc3, 0x74, 0x97, 0x14, 0x99, 0x6a, 0x4a, 0xa9, 0xa9, 0x16, 0x6a, 0xa3, 0xae, 0x38, 0x4d, 0xf7, 0xb0, 0xbd, 0x17, 0x0f, 0xa0, 0x1e, 0xea, 0xa3, 0x81, 0x44, 0x99, 0x1a, 0xb2, 0x7d, 0x50, 0xd2, 0x4d, 0x8d, 0xd9, 0x6f, 0xc2, 0x7e, 0x53, 0x3c, 0x24, 0x36, 0x53, 0x33, 0xb6, 0x0f, 0xc3, 0xf7, 0x2f, 0x25, 0x5a, 0xb0, 0x6d, 0x89, 0x56, 0x72, 0xc6, 0xf4, 0x08, 0xb7, 0x6b, 0xcd, 0xfe, 0xe7, 0x12, 0x4f, 0x8a, 0x3b, 0xc6, 0x38, 0x9d, 0x66, 0x9a, 0x2c, 0x49, 0xa6, 0x29, 0x52, 0xe8, 0xff, 0xa4, 0xda, 0x7c, 0x8e, 0xe5, 0xe6, 0x3c, 0x72, 0xb9, 0x5d, 0x9e, 0x38, 0x48, 0x72, 0x99, 0xa4, + 0xb8, 0xa1, 0xa4, 0xb8, 0x4c, 0xfa, 0x56, 0x43, 0xf2, 0xf7, 0x00, 0xd2, 0x5c, 0xa6, 0x36, 0x40, 0x32, 0xb4, 0xd7, 0xd4, 0x9d, 0xa4, 0xb9, 0xcc, 0xea, 0x77, 0xdb, 0xa2, 0x34, 0xca, 0x5e, 0x9b, 0x24, 0xa5, 0xda, 0x57, 0x12, 0xa9, 0x4d, 0x61, 0x4b, 0x39, 0x93, 0xec, 0xb2, 0xb4, 0x59, 0x92, 0x4c, 0xba, 0xd3, 0xb5, 0x39, 0x64, 0xf8, 0xb9, 0xdc, 0x26, 0x58, 0x4e, 0x6b, 0x3f, 0xb2, 0x5d, 0x22, 0x85, 0xda, 0x6a, 0x39, 0x4e, 0xda, 0xcb, 0x21, 0xed, 0xe5, 0x90, 0xf6, 0x72, 0x48, 0x7b, 0x39, 0xa4, 0xbd, 0x1c, 0xd2, 0x5e, 0x0e, 0x69, 0x2f, 0x87, 0xb4, 0x97, 0x43, 0xda, 0xcb, 0x21, 0xed, 0xe5, 0x90, 0xf6, 0xb2, 0x48, 0x7b, 0x59, 0xda, 0x0e, 0xce, 0x6b, 0x27, 0xf7, 0xdf, 0xc5, 0x63, 0xb0, 0xea, 0xd5, 0x7e, 0x95, 0x38, 0x6d, 0xb7, 0x44, 0x6b, 0x7b, 0xb8, 0x6e, 0xaf, 0xa4, 0x6a, + 0xbf, 0xf1, 0x78, 0xfb, 0x24, 0x45, 0xdb, 0x2f, 0x49, 0xda, 0x01, 0xee, 0x77, 0x90, 0xf3, 0x39, 0xc4, 0x6d, 0xc3, 0xe5, 0x84, 0x76, 0x98, 0x6d, 0x84, 0x9c, 0xd2, 0x8e, 0x70, 0x8c, 0xe3, 0xdc, 0x36, 0x8a, 0xed, 0x49, 0x44, 0x73, 0x7d, 0x0c, 0xa3, 0x40, 0x2c, 0xdb, 0x38, 0xc4, 0xf3, 0x7c, 0x6c, 0xdc, 0x2f, 0x41, 0x4e, 0x6a, 0xa9, 0xa4, 0xd5, 0x34, 0xc9, 0xd6, 0x1c, 0x5c, 0x9f, 0x8e, 0x4c, 0xf5, 0x96, 0x76, 0x4a, 0x2d, 0xd2, 0xb2, 0xd4, 0x4f, 0xda, 0x19, 0x89, 0xd1, 0xce, 0xf2, 0x78, 0x4e, 0x8e, 0x71, 0x5e, 0xd2, 0x34, 0x97, 0x7a, 0x40, 0xbb, 0xa4, 0xa6, 0x69, 0xf9, 0x52, 0xa0, 0x5d, 0xe6, 0xf1, 0x3d, 0xfc, 0xad, 0x80, 0xc7, 0x2d, 0xe4, 0xb6, 0xc5, 0xdc, 0xb7, 0x84, 0xf3, 0x2c, 0xe5, 0x3c, 0xcb, 0xd8, 0x5e, 0xe1, 0xbe, 0xe5, 0x9c, 0xcb, 0x55, 0x54, 0xa0, 0x52, 0xce, + 0x68, 0x5e, 0x8e, 0x7d, 0x53, 0xbd, 0x65, 0x56, 0x92, 0x62, 0x36, 0x89, 0xc3, 0xac, 0x49, 0xa4, 0xd9, 0x2c, 0xa5, 0xe6, 0x00, 0x04, 0xa2, 0x86, 0x9c, 0x31, 0x5b, 0xd8, 0x1a, 0xb0, 0x4a, 0x9a, 0x39, 0x48, 0x4e, 0x98, 0x6f, 0x93, 0xb3, 0xe6, 0xdb, 0xe5, 0x98, 0xf9, 0x0e, 0x39, 0x6e, 0xbe, 0x53, 0x92, 0xcd, 0x77, 0xa1, 0x26, 0xf7, 0xad, 0x25, 0xb1, 0xe6, 0xda, 0x92, 0x6a, 0xae, 0xc3, 0x6d, 0xeb, 0x4a, 0x96, 0xf9, 0x1e, 0x39, 0x6d, 0xbe, 0x97, 0xe3, 0xde, 0x27, 0x65, 0xe6, 0xfb, 0x25, 0xce, 0x5c, 0x8f, 0xdb, 0xd4, 0x97, 0x22, 0xf3, 0x83, 0x72, 0xca, 0xdc, 0x98, 0xdb, 0xd0, 0x76, 0xcc, 0x0f, 0x49, 0x82, 0x79, 0x20, 0xd7, 0x0f, 0x92, 0x78, 0xf3, 0x60, 0x6e, 0x33, 0x8c, 0xc7, 0x7f, 0x4d, 0xa2, 0x48, 0xd8, 0xbe, 0x5f, 0xd1, 0xf1, 0x7d, 0x5f, 0x4f, 0x14, 0x29, 0x3b, 0xde, + 0xbc, 0x83, 0xed, 0x2e, 0xec, 0xc6, 0x09, 0xfe, 0x16, 0xa5, 0x2c, 0x24, 0xea, 0x4c, 0x73, 0x16, 0xe7, 0x97, 0x8d, 0x0b, 0x1c, 0xeb, 0x22, 0x5c, 0x70, 0x23, 0x17, 0x79, 0xc8, 0xc7, 0x65, 0x8e, 0xe3, 0x61, 0x5b, 0x80, 0x42, 0x14, 0x71, 0x8c, 0x62, 0xae, 0x2b, 0x91, 0x0c, 0x52, 0x78, 0x06, 0x29, 0x3c, 0x9d, 0x14, 0x6e, 0x23, 0x85, 0xdb, 0xcc, 0x15, 0xfc, 0xfd, 0xaf, 0x77, 0x31, 0x4b, 0xcd, 0x5e, 0xdc, 0xc4, 0x1f, 0xfc, 0x4d, 0xa4, 0x34, 0x40, 0x49, 0x72, 0x80, 0x49, 0xd5, 0x0e, 0xd0, 0x24, 0x2a, 0x80, 0x72, 0x0a, 0xa0, 0x9c, 0x02, 0x2c, 0xec, 0x07, 0xb1, 0x7d, 0x51, 0x32, 0x49, 0xec, 0x99, 0x24, 0xf6, 0xcc, 0x80, 0x5e, 0x5c, 0xee, 0x83, 0x7e, 0x18, 0xc0, 0xdf, 0x07, 0x62, 0x30, 0x18, 0x5f, 0x02, 0x56, 0x48, 0x4a, 0x20, 0x7d, 0x2a, 0xb0, 0x21, 0xdb, 0x46, 0x12, 0x1d, + 0xd8, 0x58, 0x22, 0x03, 0x9b, 0x88, 0x9d, 0x64, 0x9f, 0x43, 0xb2, 0xcf, 0x09, 0x8c, 0x54, 0x2f, 0x05, 0x46, 0xa9, 0x07, 0x48, 0xf7, 0x39, 0xa4, 0xfb, 0x1c, 0xd2, 0x7d, 0x0e, 0xe9, 0x3e, 0x87, 0x74, 0x9f, 0x43, 0xba, 0xcf, 0xf4, 0x7f, 0x33, 0xfc, 0xef, 0x12, 0x15, 0x78, 0x49, 0x62, 0x03, 0xf3, 0x54, 0xb3, 0xc0, 0x7c, 0xb6, 0x1e, 0xff, 0xbf, 0xc3, 0x76, 0x06, 0x16, 0x72, 0xfd, 0x55, 0xff, 0xf7, 0x19, 0x45, 0x05, 0x5e, 0x93, 0xcc, 0x1a, 0x4a, 0x52, 0x6b, 0x98, 0x24, 0xbe, 0x86, 0xa6, 0x86, 0xd7, 0x30, 0xb3, 0x0d, 0x90, 0xdf, 0x6b, 0x04, 0x8a, 0xbd, 0x46, 0x0d, 0xc9, 0xaf, 0x41, 0xff, 0xad, 0xf1, 0x90, 0xa4, 0xd7, 0x68, 0x26, 0xe7, 0x6b, 0x5c, 0x93, 0x28, 0xcb, 0x18, 0xc9, 0xb4, 0x7c, 0x84, 0x8f, 0x31, 0x16, 0x9f, 0xe0, 0x53, 0x7c, 0x86, 0xcd, 0xd8, 0x82, 0x10, 0x84, + 0x22, 0x0c, 0x5b, 0xb1, 0x0d, 0x3b, 0xd5, 0x48, 0x4b, 0x3c, 0x2b, 0xd1, 0x64, 0xf5, 0x85, 0x25, 0x8b, 0xd5, 0xe0, 0xed, 0x92, 0xa9, 0x33, 0x3f, 0xe8, 0x77, 0xe2, 0x2e, 0xd4, 0x44, 0x2d, 0xd4, 0x46, 0x1d, 0xdc, 0x8d, 0xba, 0xea, 0x09, 0xfd, 0x1e, 0xb6, 0xf7, 0xe2, 0x3e, 0xdc, 0x0f, 0xe6, 0x13, 0x9d, 0xf9, 0x44, 0x67, 0x3e, 0xd1, 0x1b, 0xa0, 0x21, 0x98, 0x57, 0xf4, 0x46, 0x68, 0x8c, 0x26, 0x68, 0x8a, 0x87, 0xd0, 0x0c, 0x0f, 0xa3, 0x39, 0x5a, 0xa0, 0x25, 0x5a, 0xc1, 0xf7, 0x39, 0x99, 0x36, 0x6c, 0xff, 0xf6, 0x59, 0x99, 0x76, 0xec, 0xb7, 0xc7, 0x63, 0xe8, 0x80, 0x71, 0xea, 0x4e, 0xfd, 0x53, 0x4c, 0xc1, 0x54, 0x4c, 0xc3, 0xd7, 0x98, 0x8e, 0x6f, 0xd4, 0x61, 0x7d, 0x06, 0xdb, 0x6f, 0x31, 0x13, 0xb3, 0x30, 0x1b, 0x73, 0xb0, 0x10, 0x8b, 0x10, 0xcc, 0x4a, 0x76, 0xb9, 0x64, + 0xeb, 0x2b, 0xb0, 0x12, 0xab, 0xb0, 0x1a, 0x6b, 0xb0, 0x16, 0xeb, 0xb0, 0x1e, 0x1b, 0xb0, 0x11, 0x9b, 0xb0, 0x19, 0x5b, 0xd4, 0xa3, 0x7a, 0x08, 0xdb, 0x50, 0x84, 0x61, 0x2b, 0xb6, 0x61, 0x3b, 0x76, 0x60, 0x27, 0x76, 0xe1, 0x67, 0xfc, 0x82, 0x5f, 0xb1, 0x5b, 0xd5, 0xd0, 0xf7, 0x48, 0x96, 0xbe, 0x97, 0xfd, 0xdf, 0xb0, 0x0f, 0xfb, 0xe5, 0xa6, 0x7e, 0x80, 0xed, 0x41, 0x1c, 0x42, 0x38, 0x8e, 0x49, 0x94, 0x7e, 0x1c, 0x27, 0x10, 0x89, 0x28, 0x44, 0x23, 0x06, 0xb1, 0x12, 0xab, 0xc7, 0x21, 0x1e, 0x36, 0x24, 0x48, 0xb4, 0x9e, 0xc8, 0xf5, 0x76, 0xf6, 0x93, 0xd8, 0x26, 0x23, 0x05, 0xa9, 0x48, 0x83, 0x03, 0xe9, 0xc8, 0x40, 0x26, 0xb2, 0x90, 0x8d, 0xd3, 0x38, 0x83, 0xb3, 0x70, 0xaa, 0x40, 0x3d, 0x87, 0xed, 0x39, 0xd0, 0x0e, 0xf5, 0xf3, 0x60, 0xce, 0xd1, 0x5d, 0x70, 0x83, 0x39, + 0x47, 0x67, 0x3c, 0xd7, 0xf3, 0x90, 0x8f, 0xcb, 0x24, 0x03, 0x8f, 0xe4, 0xe8, 0x85, 0x28, 0x42, 0x31, 0x4a, 0x50, 0x8a, 0x32, 0x5c, 0x41, 0x39, 0xae, 0xa2, 0x02, 0x95, 0xb8, 0x86, 0xeb, 0xb8, 0x01, 0x2f, 0x6e, 0xe2, 0x0f, 0xc9, 0x31, 0x6a, 0x48, 0xa9, 0xc1, 0x78, 0x64, 0xe8, 0x60, 0x4c, 0x32, 0xac, 0x12, 0x65, 0xd0, 0xf7, 0x8c, 0xdb, 0x70, 0x3b, 0xee, 0xc0, 0x9d, 0xa8, 0xad, 0x82, 0x8c, 0xfb, 0xd5, 0x03, 0xfe, 0x77, 0xcc, 0x1a, 0xa8, 0xdb, 0x8c, 0x86, 0xca, 0x6a, 0x3c, 0xa8, 0xea, 0x19, 0x8d, 0xd4, 0xbd, 0x46, 0x63, 0xd5, 0xcc, 0x68, 0xa2, 0xee, 0x34, 0x9a, 0xfa, 0x7f, 0xf3, 0xb6, 0x9b, 0xd1, 0x5c, 0xb5, 0x32, 0x98, 0xaf, 0x8c, 0xd6, 0xea, 0x67, 0xa3, 0x0d, 0x7f, 0x6b, 0xab, 0xea, 0x1a, 0xed, 0xd8, 0x3e, 0xcd, 0x75, 0xcf, 0xab, 0xfa, 0x46, 0x37, 0xb6, 0xdd, 0xf1, + 0x02, 0x7a, 0xe0, 0x45, 0xbc, 0x8c, 0x9e, 0x78, 0x05, 0xf4, 0x75, 0xa3, 0x37, 0xe8, 0xef, 0x46, 0x5f, 0xd0, 0xe7, 0x8d, 0xfe, 0x18, 0x80, 0x81, 0x18, 0xc4, 0x39, 0xd2, 0xf7, 0x8d, 0x21, 0x78, 0x15, 0x43, 0x31, 0x0c, 0x8c, 0x73, 0xc6, 0xeb, 0x18, 0x8e, 0x37, 0x30, 0x02, 0x23, 0x31, 0x0a, 0x6f, 0xe2, 0x2d, 0xb9, 0x69, 0xbc, 0xcd, 0xf6, 0x1d, 0xbc, 0x8b, 0xf7, 0xf0, 0x3e, 0x46, 0xe3, 0x03, 0x7c, 0x88, 0x31, 0xf8, 0x08, 0x1f, 0x63, 0x2c, 0x58, 0x9f, 0x18, 0xac, 0x4f, 0x0c, 0xd6, 0x27, 0xc6, 0x67, 0x60, 0x9e, 0x34, 0xbe, 0xc0, 0x78, 0x4c, 0xc0, 0x44, 0x4c, 0xc2, 0x64, 0x7c, 0x89, 0xaf, 0x38, 0x3f, 0xe6, 0x4e, 0x63, 0x2a, 0xa6, 0xe3, 0x1b, 0xcc, 0xc0, 0xb7, 0x98, 0x89, 0x59, 0x98, 0x8d, 0x39, 0xf8, 0x0e, 0xf3, 0xd4, 0xa3, 0xc6, 0x7c, 0x39, 0x6d, 0x7c, 0x8f, 0x05, 0x58, + 0x88, 0x45, 0x60, 0x8e, 0x35, 0x7e, 0xc0, 0x62, 0xfc, 0x88, 0x25, 0xf8, 0x09, 0xcb, 0x24, 0xcb, 0x58, 0x8e, 0x15, 0x58, 0x89, 0x55, 0x58, 0x8d, 0x35, 0x6a, 0x98, 0xb1, 0x96, 0xed, 0x3a, 0xac, 0xc7, 0x06, 0x6c, 0xc4, 0x26, 0x6c, 0xc6, 0x16, 0x84, 0x20, 0x14, 0x61, 0xd8, 0x0a, 0xe6, 0x63, 0x83, 0xf9, 0xd8, 0xd8, 0x81, 0x9d, 0xd8, 0x85, 0x9f, 0xf1, 0x0b, 0x7e, 0xc5, 0x6e, 0xd0, 0x5f, 0x8c, 0xbd, 0xf8, 0x0d, 0xfb, 0xb0, 0x1f, 0x07, 0x70, 0x10, 0x87, 0x10, 0x8e, 0xc3, 0x88, 0x00, 0x73, 0xb2, 0x71, 0x14, 0xc7, 0x70, 0x1c, 0x27, 0x10, 0x09, 0xe6, 0x68, 0x83, 0x39, 0xda, 0x88, 0x96, 0xb3, 0x46, 0x0c, 0xdb, 0x58, 0xc4, 0x21, 0x1e, 0x36, 0x24, 0x20, 0x11, 0x76, 0x24, 0x21, 0x19, 0x29, 0x48, 0x45, 0x1a, 0x1c, 0x48, 0x47, 0x86, 0xa4, 0x19, 0x99, 0x38, 0x25, 0xe9, 0x46, 0x96, + 0x64, 0x1b, 0xd9, 0x38, 0x8d, 0x33, 0x38, 0x0b, 0x27, 0x72, 0x70, 0x51, 0x0a, 0x0d, 0x17, 0xdc, 0xb8, 0x84, 0x5c, 0xe4, 0x21, 0x1f, 0x97, 0xe1, 0x41, 0x01, 0x0a, 0x51, 0x84, 0x62, 0x94, 0xa0, 0x54, 0x0a, 0xad, 0xf7, 0xab, 0xb6, 0xd6, 0xb6, 0x12, 0x6d, 0x7d, 0x14, 0xed, 0xd0, 0x1e, 0x8f, 0xa1, 0x03, 0x1e, 0xc7, 0x13, 0x78, 0x12, 0x4f, 0xe1, 0x69, 0x3c, 0x83, 0x8e, 0xe8, 0x84, 0x67, 0xf1, 0x1c, 0x9e, 0x47, 0x67, 0x74, 0x41, 0x57, 0x74, 0x43, 0x77, 0xbc, 0x80, 0x1e, 0x78, 0x11, 0x2f, 0xe1, 0x65, 0x49, 0xb2, 0xf6, 0xc4, 0x2b, 0xe8, 0x85, 0xde, 0xe8, 0x83, 0xbe, 0xe8, 0x87, 0xfe, 0x60, 0x9d, 0x66, 0x1d, 0x88, 0x41, 0x18, 0x8c, 0x21, 0x78, 0x15, 0x43, 0x31, 0x0c, 0xaf, 0xe1, 0x75, 0x0c, 0xc7, 0x1b, 0x18, 0x81, 0x91, 0x18, 0x85, 0x37, 0xf1, 0x16, 0xde, 0xc6, 0x3b, 0x78, + 0x17, 0xef, 0xe1, 0x7d, 0x8c, 0xc6, 0x07, 0xf8, 0xd0, 0xff, 0xdd, 0x63, 0x99, 0xd6, 0x8f, 0x54, 0x4b, 0x2b, 0xf3, 0x94, 0x75, 0xac, 0xd8, 0xac, 0xcc, 0x53, 0xd6, 0x71, 0x60, 0xae, 0xb2, 0x32, 0x57, 0x59, 0x3f, 0xc7, 0x17, 0x18, 0x0f, 0xdf, 0x77, 0xf5, 0x4d, 0xc4, 0x24, 0x4c, 0xc6, 0x97, 0xf8, 0x0a, 0x53, 0x30, 0x15, 0xd3, 0x30, 0x43, 0xd2, 0xac, 0xdf, 0x62, 0x26, 0x66, 0x61, 0x36, 0xe6, 0x80, 0xf5, 0xb8, 0xf5, 0x3b, 0xcc, 0xc3, 0x7c, 0x7c, 0x8f, 0x05, 0x58, 0x88, 0x45, 0x08, 0xc6, 0x0f, 0x58, 0x8c, 0x1f, 0xb1, 0x04, 0x3f, 0xc9, 0x71, 0xeb, 0x32, 0x89, 0xb7, 0x2e, 0xc7, 0x0a, 0xac, 0xc4, 0x2a, 0xac, 0xc6, 0x1a, 0xac, 0xc5, 0x3a, 0xac, 0xc7, 0x06, 0x6c, 0xc4, 0x26, 0x6c, 0xc6, 0x16, 0x84, 0x80, 0xf5, 0x8e, 0x35, 0x0c, 0x5b, 0xb1, 0x0d, 0xdb, 0xb1, 0x03, 0x3b, 0xb1, + 0x0b, 0x07, 0x39, 0xe7, 0x43, 0xaa, 0xb6, 0x35, 0x1c, 0x87, 0x11, 0x81, 0x13, 0x52, 0x6a, 0x8d, 0x44, 0xb4, 0xff, 0x3b, 0x4d, 0x9c, 0x56, 0xd6, 0x94, 0xd6, 0x78, 0xd8, 0x90, 0x80, 0x44, 0xd8, 0x91, 0x84, 0x64, 0xa4, 0x50, 0x6e, 0xa9, 0x6c, 0xd3, 0xe4, 0x8c, 0xf5, 0x94, 0xd2, 0xac, 0xf9, 0x1c, 0xf3, 0x32, 0x3c, 0x28, 0x40, 0x21, 0xae, 0x48, 0x94, 0xb5, 0x1c, 0x57, 0xc1, 0x7a, 0xc2, 0x5a, 0x89, 0x6b, 0xe2, 0xb0, 0x5e, 0xc7, 0x0d, 0x78, 0x71, 0x53, 0x1c, 0x41, 0x8b, 0x24, 0x3a, 0x68, 0xb1, 0x7a, 0x26, 0xe8, 0x47, 0xf5, 0x56, 0xd0, 0x4f, 0xea, 0x89, 0xa0, 0x65, 0xfe, 0x4f, 0x8c, 0x67, 0x06, 0x85, 0x4b, 0x72, 0xd0, 0x61, 0x44, 0x48, 0x54, 0x10, 0x73, 0x4f, 0x50, 0xa2, 0x5c, 0x0e, 0x18, 0xac, 0xcc, 0x24, 0x9c, 0x00, 0x04, 0x4a, 0x22, 0x69, 0xd2, 0x4e, 0x9a, 0xb4, 0x93, + 0x26, 0xed, 0xaa, 0x2e, 0xa9, 0xe7, 0x01, 0xb6, 0xf5, 0x50, 0x1f, 0x0f, 0xa2, 0xb1, 0x14, 0xa9, 0x26, 0x68, 0x4e, 0x62, 0x69, 0x85, 0xd6, 0x72, 0x92, 0x64, 0x79, 0x92, 0x64, 0x79, 0x92, 0x64, 0x19, 0x4e, 0xb2, 0x0c, 0x27, 0x59, 0x86, 0x93, 0x2c, 0xc3, 0x49, 0x96, 0xe1, 0x24, 0xcb, 0x70, 0x92, 0x65, 0x04, 0xc9, 0x32, 0x82, 0x64, 0x19, 0xe1, 0xfb, 0xed, 0x6b, 0x92, 0x65, 0x38, 0xc9, 0x32, 0x9c, 0x64, 0x99, 0x42, 0xb2, 0x4c, 0x21, 0x59, 0xa6, 0x90, 0x2c, 0x53, 0x48, 0x96, 0x29, 0x24, 0xcb, 0x14, 0x92, 0x65, 0x8a, 0xf2, 0xfd, 0x5a, 0xf6, 0x0b, 0xe8, 0x81, 0x17, 0xf1, 0x12, 0x5e, 0x46, 0x4f, 0xbc, 0x82, 0x5e, 0xe8, 0x8d, 0x3e, 0xe8, 0x8b, 0x7e, 0xe8, 0x8f, 0x01, 0x18, 0x88, 0x41, 0x18, 0x8c, 0x21, 0x78, 0x15, 0x43, 0x31, 0x0c, 0xaf, 0xe1, 0x75, 0x0c, 0xc7, 0x08, 0x29, + 0x20, 0x59, 0x0e, 0x55, 0xef, 0x92, 0xd6, 0xde, 0x53, 0x6d, 0x48, 0x97, 0x6d, 0x48, 0x97, 0x36, 0xd2, 0xa5, 0x8d, 0x74, 0x69, 0x23, 0x5d, 0xda, 0x48, 0x97, 0xe1, 0xa4, 0xcb, 0x70, 0xd2, 0x65, 0x38, 0xe9, 0xd2, 0x46, 0xba, 0x8c, 0x20, 0x5d, 0xda, 0x48, 0x97, 0x51, 0xa4, 0xcb, 0x28, 0xd2, 0x65, 0x14, 0xe9, 0x32, 0x8a, 0x74, 0x99, 0x42, 0xba, 0xb4, 0x91, 0x2e, 0x6d, 0xa4, 0x4b, 0x1b, 0xe9, 0xd2, 0x46, 0xba, 0xb4, 0x91, 0x2e, 0x6d, 0xa4, 0x4b, 0x1b, 0xe9, 0xd2, 0x46, 0xba, 0xb4, 0x91, 0x2e, 0x6d, 0xa4, 0xcb, 0x18, 0xd2, 0x65, 0x0c, 0xe9, 0x32, 0x9a, 0x74, 0x69, 0x57, 0xf3, 0x48, 0x8f, 0xf3, 0x49, 0x9b, 0xb4, 0x66, 0x15, 0x4b, 0xfa, 0x8c, 0xe3, 0xba, 0x78, 0xea, 0xc3, 0x4e, 0xb2, 0x4c, 0x66, 0x9b, 0x22, 0x87, 0x49, 0x9c, 0x09, 0xfe, 0xdf, 0xcd, 0x48, 0x67, 0x9b, 0x21, + 0xc7, 0x54, 0xa6, 0xe4, 0x93, 0x3a, 0x63, 0x48, 0x9d, 0x49, 0xa4, 0xce, 0x28, 0x52, 0x67, 0x2c, 0xa9, 0x33, 0x81, 0xd4, 0x19, 0xa3, 0x72, 0xa4, 0x98, 0xe4, 0x99, 0xa4, 0x7e, 0xa7, 0x3e, 0x2f, 0x70, 0xde, 0x17, 0xe1, 0xe2, 0xef, 0x6e, 0xfe, 0x76, 0x89, 0xfd, 0x5c, 0x39, 0x48, 0xfa, 0x8c, 0x22, 0x7d, 0x1e, 0x55, 0xe5, 0x3c, 0xf7, 0x1b, 0x3c, 0xde, 0x4d, 0x75, 0x0f, 0x09, 0xd4, 0x66, 0xd2, 0xa5, 0x84, 0x14, 0x6a, 0x27, 0x85, 0xc6, 0x93, 0x42, 0x53, 0x48, 0xa1, 0x36, 0x52, 0xa8, 0x8d, 0x14, 0x6a, 0x33, 0xdd, 0x4d, 0x52, 0xac, 0x2b, 0xd1, 0x24, 0x51, 0x1b, 0x49, 0xd4, 0x66, 0xba, 0x8f, 0xdb, 0xdd, 0x8f, 0x07, 0xd8, 0xaf, 0x87, 0xfa, 0x68, 0x20, 0xe1, 0x24, 0x52, 0x1b, 0x89, 0x34, 0x9e, 0x44, 0x1a, 0x4e, 0x22, 0xb5, 0x91, 0x48, 0x6d, 0x24, 0xd2, 0x93, 0x24, 0x52, 0x1b, + 0x89, 0xd4, 0xf7, 0xfd, 0x3c, 0xf1, 0x24, 0x52, 0x1b, 0x89, 0xd4, 0x46, 0x22, 0x8d, 0x26, 0x91, 0x86, 0x93, 0x48, 0x6d, 0xa6, 0x36, 0x5c, 0xff, 0xb9, 0x9c, 0x20, 0x95, 0x86, 0x9b, 0xc6, 0x4b, 0x9e, 0x69, 0x82, 0x14, 0x90, 0x4e, 0x8f, 0x98, 0x26, 0x91, 0x86, 0x29, 0x63, 0xff, 0xfb, 0x8d, 0x5f, 0x91, 0x92, 0xa7, 0x88, 0x87, 0xa4, 0x9a, 0x6a, 0x9a, 0x2b, 0xd9, 0xa6, 0xef, 0xa4, 0xcc, 0x34, 0x8f, 0xe4, 0x3a, 0x9f, 0x73, 0xfd, 0x9e, 0x54, 0x5b, 0xf5, 0x3e, 0xa4, 0x9d, 0xf4, 0x1a, 0x4e, 0x7a, 0x8d, 0x31, 0x15, 0x4b, 0x96, 0xa9, 0x04, 0xa5, 0xfe, 0x7f, 0x55, 0x5d, 0x4a, 0x9a, 0xb5, 0x6b, 0xac, 0x1a, 0xb4, 0x97, 0xa4, 0x48, 0x7b, 0x99, 0xc4, 0xd5, 0x53, 0x8d, 0x21, 0xd9, 0xda, 0x49, 0xb6, 0x4d, 0x48, 0xb6, 0x1f, 0x90, 0x6c, 0xed, 0x24, 0x5b, 0xbb, 0xc6, 0x2a, 0x81, 0x74, + 0x5b, 0x9b, 0x74, 0x6b, 0xd7, 0xde, 0x66, 0xbf, 0x2a, 0xe1, 0x86, 0x93, 0x70, 0x23, 0xb4, 0xf1, 0x52, 0x4c, 0xca, 0xb5, 0x91, 0x72, 0x0f, 0x91, 0x72, 0x6d, 0xa4, 0x5c, 0x1b, 0x29, 0xd7, 0xa6, 0x7d, 0x43, 0xfa, 0x9b, 0x45, 0x62, 0x9c, 0x43, 0x4a, 0x9e, 0xcb, 0xed, 0xbe, 0xe3, 0x76, 0x8c, 0x54, 0xa4, 0xdd, 0x28, 0xd2, 0x6e, 0x04, 0x69, 0xd7, 0xa1, 0x2d, 0x93, 0x04, 0x6d, 0x15, 0xd7, 0xad, 0xe6, 0xf2, 0x7a, 0x12, 0xea, 0x06, 0x6c, 0xc4, 0x26, 0x6c, 0xc6, 0x16, 0x84, 0x20, 0x14, 0x61, 0xd8, 0x8a, 0x6d, 0x1c, 0x7b, 0x3b, 0x76, 0x90, 0x16, 0x77, 0xca, 0x11, 0x52, 0xaf, 0x4d, 0xfb, 0x99, 0xf3, 0xff, 0x85, 0x04, 0xf9, 0x2b, 0xe9, 0x79, 0x37, 0xe7, 0xb5, 0x87, 0xe3, 0xed, 0x25, 0x65, 0xfe, 0xc6, 0xe3, 0xef, 0x63, 0xbb, 0x5f, 0x8e, 0x91, 0x7c, 0x63, 0x48, 0xbe, 0x87, 0x48, + 0xbe, 0x05, 0x24, 0xdf, 0x43, 0x24, 0xdf, 0x42, 0x92, 0x6f, 0x22, 0xc9, 0x37, 0x46, 0x3b, 0xca, 0xfd, 0x8f, 0x71, 0xf9, 0x38, 0xc7, 0x8a, 0xe2, 0xf2, 0x49, 0x52, 0x6e, 0x34, 0xfb, 0x31, 0xa4, 0xfb, 0x58, 0xb6, 0x71, 0x9c, 0x67, 0x3c, 0xc7, 0xb5, 0x71, 0xbf, 0x04, 0x24, 0x92, 0xb0, 0xed, 0x24, 0xd9, 0x24, 0xfe, 0x96, 0xca, 0xe3, 0xd2, 0x1e, 0x49, 0xc3, 0x09, 0xa4, 0xe1, 0x04, 0xd2, 0xf0, 0x04, 0xd2, 0xf0, 0x1c, 0x7f, 0x1a, 0xce, 0xa6, 0xac, 0x4e, 0x4b, 0x2e, 0xa9, 0xf8, 0x18, 0xa9, 0x38, 0x8a, 0x54, 0x1c, 0xa3, 0xe5, 0x70, 0x4e, 0xe7, 0xb8, 0xdf, 0xef, 0xec, 0x9f, 0xe7, 0x71, 0x2e, 0x90, 0xba, 0x2f, 0xfa, 0x93, 0x72, 0x43, 0x92, 0x72, 0x3f, 0x2d, 0x97, 0xf3, 0xcb, 0xa3, 0x9c, 0xf3, 0x49, 0xf6, 0x97, 0xb9, 0x8d, 0x87, 0xe7, 0x54, 0x20, 0x07, 0xfd, 0x89, 0xb9, 0x48, + 0x32, 0x49, 0xcd, 0x09, 0xa4, 0xe6, 0x70, 0x52, 0x73, 0x04, 0xa9, 0x39, 0x9c, 0xd4, 0x7c, 0x98, 0xd4, 0x9c, 0x40, 0x6a, 0xb6, 0x91, 0x9a, 0x6d, 0xa4, 0x66, 0xbb, 0x76, 0x43, 0xca, 0x48, 0xce, 0x43, 0x49, 0xce, 0x43, 0xb5, 0x3f, 0x78, 0x5c, 0x21, 0xf9, 0x2a, 0x92, 0xab, 0x09, 0x9a, 0x84, 0x93, 0xa0, 0x13, 0x48, 0xd0, 0x09, 0x24, 0xe8, 0x04, 0x12, 0x74, 0x34, 0x09, 0x3a, 0xd7, 0xac, 0x93, 0x62, 0x0d, 0x2e, 0x5b, 0x49, 0xd0, 0x41, 0x72, 0x88, 0x14, 0x1d, 0x4d, 0x8a, 0x3e, 0x48, 0x8a, 0x3e, 0x44, 0x8a, 0x3e, 0x49, 0x8a, 0x8e, 0x24, 0x45, 0x47, 0x91, 0xa2, 0xa3, 0x49, 0xd1, 0x71, 0xa4, 0xe8, 0x04, 0xf3, 0xdd, 0x5c, 0x5f, 0x97, 0xfd, 0x7b, 0x24, 0x89, 0x24, 0x7d, 0x92, 0x24, 0x1d, 0x4f, 0x92, 0x3e, 0x6a, 0x7e, 0x80, 0x44, 0x5a, 0x8f, 0xc7, 0xac, 0x4f, 0xba, 0x6e, 0x40, + 0xe2, 0x6e, 0xc8, 0x6d, 0x1f, 0xe4, 0x7e, 0x8d, 0x24, 0x86, 0x64, 0x9d, 0x60, 0x6e, 0xc2, 0x71, 0x9a, 0xb2, 0x7d, 0x88, 0x6d, 0x33, 0xae, 0x1b, 0xc8, 0xdf, 0x06, 0x91, 0xd8, 0x07, 0x73, 0xdf, 0x61, 0x12, 0x41, 0xc2, 0x8e, 0x20, 0x61, 0xdb, 0x49, 0xd8, 0x09, 0x24, 0xec, 0x70, 0x12, 0x76, 0x14, 0x09, 0x3b, 0x9c, 0x84, 0x1d, 0x4e, 0xc2, 0x0e, 0x27, 0x61, 0xdb, 0x49, 0xd8, 0x41, 0xe6, 0x78, 0x92, 0xba, 0x0d, 0xa9, 0xa4, 0x76, 0x07, 0xd7, 0x65, 0x49, 0x1a, 0x69, 0x3b, 0x8d, 0xb4, 0x6d, 0x23, 0x6d, 0xdb, 0x48, 0xdb, 0x36, 0xd2, 0xb6, 0xcd, 0x7c, 0x89, 0xeb, 0x72, 0xd9, 0xe6, 0xc1, 0xf7, 0xbb, 0x97, 0x97, 0x39, 0x4f, 0x0f, 0xdb, 0x02, 0x14, 0xa2, 0x88, 0x63, 0x16, 0x73, 0x5d, 0x09, 0x4a, 0x51, 0xe6, 0x4f, 0xdd, 0xf1, 0xa4, 0xee, 0x93, 0xa4, 0xee, 0x93, 0xa4, 0x6e, 0xdb, + 0x9f, 0xef, 0x7f, 0x5f, 0x67, 0x9f, 0x31, 0x82, 0xe4, 0x6d, 0x23, 0x79, 0xdb, 0x48, 0xde, 0x27, 0x49, 0xde, 0x36, 0x92, 0x77, 0x24, 0xc9, 0xfb, 0x1e, 0x92, 0x77, 0x38, 0xc9, 0xdb, 0x46, 0xf2, 0xb6, 0x05, 0x04, 0x4a, 0x46, 0x40, 0x0d, 0x58, 0xb8, 0xce, 0x2a, 0x76, 0x52, 0xb8, 0x8d, 0x14, 0x6e, 0x27, 0x85, 0xdb, 0x49, 0xe1, 0x76, 0x52, 0xb8, 0x8d, 0x14, 0x6e, 0x23, 0x85, 0xdb, 0x48, 0xe1, 0xe1, 0xa4, 0xf0, 0x70, 0x52, 0x78, 0x38, 0x29, 0xbc, 0x80, 0x14, 0x1e, 0x13, 0xb0, 0x52, 0x0a, 0x49, 0xe2, 0xc5, 0x81, 0xf5, 0xd9, 0x36, 0x24, 0x85, 0x37, 0x92, 0x70, 0xd2, 0xf8, 0x21, 0xd2, 0xf8, 0x51, 0xd2, 0x78, 0x0a, 0x69, 0x3c, 0x85, 0x34, 0xde, 0x8b, 0x34, 0xde, 0x90, 0x34, 0x9e, 0x42, 0x1a, 0x4f, 0x21, 0x8d, 0xa7, 0x90, 0xc6, 0x53, 0x48, 0xe3, 0x29, 0xa4, 0x71, 0x3b, 0x69, + 0xbc, 0x01, 0x69, 0x3c, 0x9c, 0x34, 0x1e, 0x41, 0x12, 0x8f, 0x20, 0x89, 0xc7, 0x90, 0xc4, 0xa3, 0x49, 0xe2, 0xe1, 0x81, 0x8c, 0x77, 0xa4, 0x71, 0x3b, 0x69, 0x3c, 0x9c, 0x34, 0x6e, 0x27, 0x8d, 0xc7, 0x93, 0xc6, 0x8f, 0x91, 0xc6, 0xc7, 0x90, 0xc6, 0x8f, 0x92, 0xc6, 0x9d, 0xa4, 0xf1, 0x68, 0xd2, 0x78, 0x22, 0x69, 0xdc, 0x46, 0x1a, 0x4f, 0x21, 0x8d, 0x17, 0x93, 0xc6, 0xc3, 0x2d, 0xad, 0x24, 0xdb, 0xf2, 0x08, 0x5a, 0xa3, 0x0d, 0xda, 0xe2, 0x51, 0xb4, 0x43, 0x7b, 0x3c, 0x86, 0x0e, 0x78, 0x1c, 0x4f, 0x48, 0xa1, 0xe5, 0x49, 0xb6, 0x4f, 0xe1, 0x69, 0x3c, 0x83, 0x8e, 0xe8, 0x84, 0x67, 0xf1, 0x1c, 0x9e, 0x47, 0x67, 0x74, 0x41, 0x57, 0x74, 0x43, 0x77, 0xbc, 0x80, 0x1e, 0x78, 0x11, 0x2f, 0xa1, 0x27, 0x5e, 0xe1, 0x78, 0xbd, 0xd0, 0x9b, 0xfd, 0x3e, 0xe8, 0xcb, 0x7e, 0x3f, 0xf4, + 0x67, 0x7f, 0x00, 0x06, 0x62, 0x10, 0x06, 0x63, 0x08, 0x5e, 0xc5, 0x50, 0xfe, 0x3e, 0x0c, 0xaf, 0xb1, 0xff, 0x3a, 0x86, 0xb3, 0xff, 0x06, 0x46, 0xb0, 0x3f, 0x92, 0xed, 0x28, 0xbc, 0xc9, 0xfe, 0x5b, 0x92, 0x68, 0x79, 0x1b, 0xef, 0x48, 0xb2, 0x65, 0x8c, 0xd8, 0x2d, 0x1f, 0xe1, 0x63, 0x8c, 0xc5, 0x27, 0xf8, 0x14, 0x9f, 0x61, 0x33, 0xb6, 0x20, 0x04, 0xa1, 0x08, 0xc3, 0x56, 0x6c, 0xc3, 0x4e, 0xf5, 0xa9, 0x25, 0x4e, 0x75, 0xb4, 0xc4, 0xab, 0x67, 0x2c, 0x36, 0xd5, 0xc4, 0x92, 0xa0, 0xee, 0xb1, 0x24, 0xab, 0x19, 0x96, 0x14, 0xf6, 0xd3, 0x70, 0x4a, 0xb5, 0xb1, 0x64, 0xa9, 0xfb, 0x2c, 0xd9, 0xec, 0x57, 0xa8, 0x20, 0xfd, 0x76, 0xb1, 0xeb, 0xac, 0x31, 0xf4, 0x3b, 0x71, 0x17, 0x6a, 0xa2, 0x16, 0x6a, 0xa3, 0x0e, 0xee, 0x46, 0xd5, 0x2b, 0x12, 0x76, 0xfd, 0x5e, 0x30, 0xd7, 0xe8, + 0xcc, 0x35, 0x3a, 0xeb, 0x11, 0x9d, 0xf5, 0x88, 0xce, 0x7a, 0x44, 0x6f, 0x80, 0x86, 0x60, 0x5d, 0xa2, 0x37, 0x42, 0x63, 0x34, 0x41, 0x53, 0x3c, 0x84, 0x66, 0x78, 0x18, 0xcd, 0xd1, 0x02, 0x2d, 0xd1, 0x0a, 0x8f, 0x48, 0xbe, 0xde, 0x86, 0x6d, 0x5b, 0xb6, 0x8f, 0xa2, 0x1d, 0xfb, 0xed, 0xf1, 0x18, 0x3a, 0xa0, 0x87, 0x14, 0xe9, 0x2f, 0x82, 0x39, 0x42, 0x7f, 0x19, 0x3d, 0xf1, 0x0a, 0x7a, 0xa1, 0x37, 0xfa, 0xa0, 0x2f, 0xc6, 0xa9, 0xda, 0xfa, 0xa7, 0x98, 0x82, 0xa9, 0x98, 0x86, 0xaf, 0x31, 0x1d, 0xdf, 0xa8, 0x78, 0x7d, 0x06, 0xdb, 0x6f, 0x31, 0x13, 0xb3, 0x30, 0x1b, 0x73, 0xb0, 0x10, 0x8b, 0x10, 0xac, 0xea, 0xea, 0xcb, 0xe5, 0xa4, 0xbe, 0x02, 0x2b, 0xb1, 0x0a, 0xab, 0xb1, 0x06, 0x6b, 0xb1, 0x0e, 0xeb, 0xb1, 0x01, 0x1b, 0xb1, 0x09, 0x9b, 0xb1, 0x45, 0x8d, 0xd0, 0x43, 0xd8, + 0x86, 0x22, 0x0c, 0x5b, 0xb1, 0x0d, 0xdb, 0xb1, 0x03, 0x3b, 0xb1, 0x0b, 0x3f, 0xe3, 0x17, 0xfc, 0x8a, 0xdd, 0xca, 0xa4, 0xef, 0x21, 0xf9, 0xef, 0x65, 0xff, 0x37, 0xec, 0x83, 0xef, 0xdf, 0x9b, 0x1d, 0x60, 0x7b, 0x10, 0x87, 0x10, 0x8e, 0x63, 0x12, 0xae, 0x1f, 0xc7, 0x09, 0x44, 0x22, 0x0a, 0xd1, 0x88, 0x41, 0xac, 0x44, 0xe8, 0x71, 0x88, 0x87, 0x0d, 0x09, 0x5c, 0x97, 0x08, 0x3b, 0xfb, 0x49, 0x6c, 0x93, 0x91, 0x82, 0x54, 0xa4, 0xc1, 0x81, 0x74, 0x64, 0x20, 0x13, 0x59, 0xc8, 0xc6, 0x69, 0x9c, 0xc1, 0x59, 0x38, 0xd5, 0x53, 0x7a, 0x0e, 0xdb, 0x73, 0xa0, 0xcf, 0xea, 0xe7, 0xc1, 0x7a, 0x45, 0x77, 0xc1, 0x0d, 0xd6, 0x2b, 0x3a, 0x73, 0xb9, 0x9e, 0x87, 0x7c, 0x5c, 0x96, 0x14, 0xdd, 0x03, 0xd2, 0x97, 0x5e, 0xc8, 0xb6, 0x08, 0xc5, 0x28, 0x41, 0x29, 0xca, 0x70, 0x05, 0xe5, 0xb8, + 0x8a, 0x0a, 0x54, 0xe2, 0x1a, 0xae, 0xe3, 0x06, 0xbc, 0xb8, 0x89, 0x3f, 0x24, 0xc5, 0xa8, 0x21, 0x05, 0x86, 0x05, 0x3a, 0x0c, 0x58, 0x25, 0xdc, 0x08, 0x62, 0x7b, 0x1b, 0x6e, 0xc7, 0x1d, 0xb8, 0x13, 0xb5, 0xd5, 0xf3, 0xc6, 0xfd, 0xaa, 0xb5, 0x51, 0x4f, 0x75, 0x36, 0x1a, 0x2a, 0xdd, 0x78, 0x50, 0xd5, 0x37, 0x1a, 0xa9, 0x7a, 0x46, 0x63, 0xd5, 0xd2, 0x68, 0xa2, 0x02, 0x8c, 0xa6, 0xfe, 0x7f, 0xa3, 0xf7, 0x62, 0xf5, 0x2b, 0x17, 0x09, 0xd5, 0xaf, 0x5c, 0xb4, 0x31, 0xda, 0x72, 0x7d, 0x3b, 0xb6, 0x4f, 0x73, 0xdd, 0x73, 0xaa, 0xad, 0xf1, 0xbc, 0x7a, 0xc0, 0xe8, 0xac, 0x9e, 0x36, 0xba, 0x89, 0xcd, 0xe8, 0x8e, 0x17, 0xd0, 0x03, 0x2f, 0xe2, 0x65, 0xf4, 0xc4, 0x2b, 0x60, 0x9c, 0x34, 0x7a, 0x83, 0xb1, 0xd2, 0xe8, 0x0b, 0xc6, 0x4b, 0xa3, 0x3f, 0x06, 0x60, 0x20, 0x06, 0x71, 0x9e, + 0x8c, 0x9b, 0xc6, 0x10, 0xbc, 0x8a, 0xa1, 0x18, 0x86, 0xd7, 0xf0, 0x3a, 0x86, 0xe3, 0x0d, 0x8c, 0xc0, 0x48, 0x8c, 0xc2, 0x9b, 0x78, 0x4b, 0xb5, 0x33, 0xde, 0x66, 0xfb, 0x0e, 0xde, 0xc5, 0x7b, 0x78, 0x1f, 0xa3, 0xf1, 0x01, 0x3e, 0xc4, 0x18, 0x7c, 0x84, 0x8f, 0x31, 0x16, 0xac, 0x81, 0x0d, 0xd6, 0xc0, 0x06, 0x6b, 0x60, 0xe3, 0x33, 0xb0, 0x0e, 0x32, 0x58, 0xa3, 0x19, 0xe3, 0x31, 0x01, 0x13, 0x31, 0x09, 0x93, 0x25, 0xce, 0xf8, 0x92, 0xed, 0x57, 0x9c, 0x1f, 0xeb, 0x22, 0x83, 0xb5, 0xb0, 0xc1, 0x1a, 0xd8, 0x60, 0x0d, 0x6c, 0xcc, 0x90, 0x4c, 0x83, 0x35, 0xb0, 0xc1, 0x1a, 0xd8, 0x60, 0x0d, 0x6c, 0xcc, 0xc6, 0x1c, 0xcc, 0xa5, 0x6c, 0xbf, 0x63, 0x3b, 0x8f, 0x32, 0x9a, 0x2f, 0x51, 0xc6, 0xf7, 0xf0, 0xfd, 0xeb, 0xb9, 0x85, 0x58, 0x04, 0xd6, 0x4e, 0xc6, 0x0f, 0x58, 0x8c, 0x1f, + 0xb1, 0x04, 0x3f, 0xc9, 0x49, 0x63, 0x19, 0xf7, 0x59, 0x8e, 0x15, 0x58, 0x89, 0x55, 0x58, 0x8d, 0x35, 0xea, 0x55, 0x63, 0x2d, 0xdb, 0x75, 0x58, 0x8f, 0x0d, 0xd8, 0x88, 0x4d, 0xd8, 0x8c, 0x2d, 0x08, 0x41, 0x28, 0xc2, 0xb0, 0x15, 0xac, 0xaf, 0x0c, 0xd6, 0x57, 0xc6, 0x0e, 0xec, 0x04, 0xeb, 0x2b, 0xe3, 0x67, 0xfc, 0x82, 0x5f, 0xb1, 0x1b, 0x7b, 0xb0, 0x17, 0xbf, 0x61, 0x1f, 0xf6, 0xe3, 0x00, 0x0e, 0xe2, 0x10, 0xc2, 0x71, 0x18, 0x11, 0x60, 0x7d, 0x65, 0x1c, 0xc5, 0x31, 0x1c, 0xc7, 0x09, 0x44, 0x82, 0x75, 0x96, 0x71, 0x12, 0xd1, 0x92, 0x6c, 0xc4, 0xb0, 0x8d, 0x45, 0x1c, 0xe2, 0x61, 0x43, 0x02, 0x12, 0x61, 0x47, 0x12, 0x92, 0x91, 0x82, 0x54, 0xa4, 0x49, 0xb4, 0xe1, 0x60, 0x9b, 0x2e, 0xe9, 0x46, 0x06, 0xfb, 0x99, 0x60, 0xfd, 0x6f, 0x64, 0x89, 0xdd, 0xc8, 0xc6, 0x69, 0x9c, + 0x01, 0xf3, 0x9f, 0xe1, 0x44, 0x0e, 0xce, 0x49, 0x86, 0xf1, 0x3b, 0xce, 0xe3, 0x82, 0xff, 0xdf, 0x44, 0x3b, 0x0c, 0x17, 0xdc, 0xb8, 0x84, 0x5c, 0xe4, 0x21, 0x1f, 0x97, 0xe1, 0x41, 0x01, 0x0a, 0x51, 0x84, 0x62, 0x94, 0xa0, 0x14, 0x65, 0x92, 0x66, 0x5c, 0x41, 0x39, 0xae, 0xa2, 0x02, 0x95, 0xb8, 0x26, 0xa9, 0xd6, 0xfb, 0x55, 0x63, 0x6b, 0x3d, 0xc9, 0xb6, 0xd6, 0x97, 0x22, 0x6b, 0x03, 0x34, 0xc4, 0x83, 0x5c, 0x6e, 0xc4, 0x96, 0xbc, 0x68, 0x25, 0x2f, 0x5a, 0x9b, 0x72, 0xf9, 0x21, 0x34, 0xc3, 0xc3, 0x68, 0xce, 0x75, 0x2d, 0xd0, 0x12, 0xcc, 0xab, 0xd6, 0x47, 0xd8, 0xb6, 0x46, 0x1b, 0xb4, 0x95, 0xc3, 0xd6, 0x47, 0xd1, 0x0e, 0xed, 0xf1, 0x18, 0x3a, 0xe0, 0x71, 0x3c, 0x81, 0x27, 0xf1, 0x14, 0x9e, 0xc6, 0x33, 0xe8, 0x88, 0x4e, 0x78, 0x16, 0xcf, 0xe1, 0x79, 0x74, 0x46, 0x17, + 0x74, 0x45, 0x37, 0x74, 0xc7, 0x0b, 0xe8, 0x81, 0x17, 0xf1, 0x12, 0x5e, 0x96, 0x63, 0xd6, 0x9e, 0x78, 0x05, 0xbd, 0xd0, 0x1b, 0x7d, 0xd0, 0x17, 0xfd, 0xd0, 0x1f, 0x03, 0x24, 0xc6, 0x3a, 0x10, 0x83, 0x30, 0x18, 0x43, 0xf0, 0x2a, 0x86, 0x62, 0x18, 0x5e, 0xc3, 0xeb, 0x18, 0x8e, 0x37, 0x30, 0x02, 0x23, 0x31, 0x0a, 0x6f, 0xe2, 0x2d, 0xbc, 0x8d, 0x77, 0xf0, 0x2e, 0xde, 0xc3, 0xfb, 0x18, 0x8d, 0x0f, 0xf0, 0x21, 0xc6, 0x48, 0xac, 0xff, 0xb7, 0xc7, 0x3f, 0x66, 0x3b, 0x56, 0x8e, 0x5a, 0x3f, 0x61, 0x3b, 0x0e, 0x9f, 0xe2, 0x33, 0x7c, 0x8e, 0x2f, 0x30, 0x1e, 0x13, 0x30, 0x11, 0x93, 0x30, 0x19, 0x5f, 0xe2, 0x2b, 0x4c, 0xc1, 0x54, 0x4c, 0xc3, 0x0c, 0x8e, 0xf9, 0x2d, 0x66, 0x62, 0x16, 0xc8, 0x9b, 0x56, 0xf2, 0xa6, 0x75, 0x2e, 0xbe, 0xc3, 0x3c, 0xcc, 0xc7, 0xf7, 0x58, 0x80, 0x85, + 0x58, 0x84, 0x60, 0xfc, 0x80, 0xc5, 0xf8, 0x11, 0x4b, 0xf0, 0x93, 0x1c, 0xb4, 0x2e, 0xe3, 0x9c, 0x96, 0x63, 0x05, 0x56, 0x62, 0x15, 0x56, 0x63, 0x0d, 0xd6, 0x62, 0x1d, 0xd6, 0x63, 0x03, 0x36, 0x62, 0x13, 0x36, 0x63, 0x0b, 0x42, 0x10, 0x8a, 0x30, 0x6c, 0xc5, 0x36, 0x6c, 0xc7, 0x0e, 0xec, 0xc4, 0x2e, 0xec, 0x91, 0x52, 0xeb, 0x41, 0xb1, 0x5b, 0x0f, 0xa9, 0x7b, 0xac, 0xe1, 0x38, 0x8c, 0x08, 0x1c, 0x93, 0x12, 0xeb, 0x09, 0x49, 0xb0, 0x46, 0x8a, 0xcd, 0x1a, 0x25, 0x69, 0xd6, 0x93, 0x88, 0x96, 0x68, 0x6b, 0x2c, 0xe2, 0xb8, 0x2e, 0x1e, 0x36, 0x24, 0x20, 0x11, 0x76, 0x24, 0x71, 0xfb, 0x64, 0xa4, 0xc8, 0x49, 0x6b, 0x2a, 0x97, 0xe9, 0x37, 0xd6, 0x53, 0x72, 0xc3, 0x9a, 0xcf, 0xf1, 0x2f, 0xc3, 0x83, 0x02, 0x14, 0xe2, 0x8a, 0x84, 0x5b, 0xcb, 0x71, 0x15, 0xac, 0x07, 0xad, 0x95, + 0xb8, 0xc6, 0x73, 0xbe, 0x8e, 0x1b, 0xf0, 0xe2, 0xa6, 0xc4, 0x04, 0x2d, 0x92, 0xc3, 0x41, 0x8b, 0x55, 0xef, 0xa0, 0x1f, 0xd5, 0x84, 0xa0, 0x9f, 0x54, 0xc3, 0xa0, 0xa5, 0x52, 0x18, 0x44, 0x8e, 0x0b, 0x3a, 0x28, 0x45, 0x41, 0x87, 0xc4, 0x1e, 0x14, 0x2e, 0x91, 0x41, 0x87, 0x11, 0x21, 0xe1, 0x41, 0xcc, 0x8b, 0x41, 0x89, 0x92, 0x1a, 0x30, 0x40, 0x99, 0xa5, 0x50, 0x05, 0x20, 0x50, 0x92, 0x95, 0x45, 0xd2, 0x95, 0x8e, 0x3b, 0x50, 0x5b, 0xca, 0x54, 0x5d, 0x12, 0xfa, 0x03, 0xec, 0xd7, 0x43, 0x7d, 0x3c, 0x88, 0xbf, 0x5e, 0x59, 0xb9, 0x4b, 0xb5, 0x42, 0x6b, 0xd2, 0x7c, 0x1b, 0xb4, 0x45, 0x3b, 0x89, 0x54, 0xed, 0xf1, 0x18, 0x3a, 0xe0, 0x71, 0x3c, 0x81, 0x27, 0xe5, 0xa4, 0x7a, 0x0a, 0x4f, 0xe3, 0x19, 0x89, 0x57, 0x1d, 0xb9, 0xae, 0x13, 0x9e, 0x95, 0x4c, 0xf5, 0x1c, 0x9e, 0x47, + 0x67, 0x74, 0x41, 0x57, 0x74, 0x43, 0x77, 0xbc, 0x80, 0x1e, 0x78, 0x11, 0x2f, 0xe1, 0x65, 0xf4, 0xc4, 0x2b, 0xe8, 0x85, 0xde, 0xe8, 0x83, 0xbe, 0xe8, 0x87, 0xfe, 0x18, 0x80, 0x81, 0x18, 0x84, 0xc1, 0x18, 0x82, 0x57, 0x31, 0x14, 0xc3, 0xf0, 0x1a, 0x5e, 0xc7, 0x70, 0x8c, 0xe0, 0x79, 0x8c, 0x52, 0x83, 0xd4, 0x7b, 0xaa, 0xa5, 0x7a, 0x1f, 0x1f, 0x48, 0x9a, 0xfa, 0x08, 0x1f, 0x63, 0x2c, 0x3e, 0xe1, 0x1c, 0xc7, 0xe1, 0x53, 0x7c, 0xc6, 0xe5, 0x2f, 0x38, 0xff, 0xf1, 0x6c, 0x27, 0x48, 0x9c, 0x9a, 0x88, 0x49, 0x60, 0x36, 0x51, 0x5f, 0x71, 0xac, 0x29, 0xe2, 0x50, 0x53, 0x31, 0x0d, 0x5f, 0x63, 0x3a, 0xbe, 0xc1, 0x0c, 0x7c, 0x8b, 0x99, 0x98, 0x85, 0xd9, 0x62, 0x57, 0x73, 0x30, 0x57, 0x12, 0xd5, 0x77, 0xdc, 0x6f, 0x1e, 0x65, 0x3f, 0x5f, 0xb2, 0xd5, 0xf7, 0xec, 0xc7, 0xfa, 0xdf, + 0xbf, 0x4f, 0x57, 0xf1, 0x6c, 0xed, 0x6c, 0x93, 0xa5, 0x54, 0xa5, 0x48, 0x94, 0x4a, 0x93, 0x94, 0xea, 0xf7, 0xf1, 0x53, 0x54, 0x86, 0xc4, 0xaa, 0x4c, 0xfe, 0x7e, 0x4a, 0x92, 0x54, 0x16, 0xb7, 0xc9, 0x16, 0x9b, 0x3a, 0xcd, 0xfe, 0x59, 0xce, 0x8b, 0x11, 0xb5, 0xfa, 0x37, 0x4f, 0x7c, 0x9f, 0x32, 0x4b, 0x53, 0x17, 0x38, 0xef, 0x8b, 0x70, 0xf1, 0x77, 0x37, 0x2e, 0xb1, 0x9f, 0x2b, 0x47, 0x15, 0x49, 0x4b, 0x91, 0x60, 0xab, 0x5f, 0x55, 0x49, 0x57, 0x37, 0x55, 0x1d, 0xff, 0xfb, 0xfa, 0x55, 0xaf, 0xaa, 0xa4, 0x9b, 0xee, 0x94, 0x14, 0xd3, 0x5d, 0x72, 0xca, 0x54, 0x53, 0xd2, 0x4c, 0xb5, 0x50, 0x1b, 0x77, 0x73, 0xb9, 0xae, 0x24, 0x98, 0xee, 0x61, 0xff, 0x5e, 0xfc, 0xf5, 0xaa, 0x4a, 0x9a, 0xa9, 0x1e, 0xea, 0xa3, 0x81, 0x44, 0x9a, 0x1a, 0xb2, 0xf5, 0x7d, 0x0f, 0x67, 0x63, 0xf6, + 0x9b, 0xb0, 0xdf, 0x14, 0x0f, 0x49, 0xa2, 0xa9, 0x19, 0xdb, 0x87, 0xd1, 0x9c, 0xbf, 0xb5, 0x60, 0xdb, 0x12, 0xad, 0xb8, 0xfe, 0x11, 0x6e, 0xd7, 0x9a, 0xfd, 0x36, 0xec, 0x57, 0xbd, 0xd7, 0x7f, 0xd2, 0x34, 0x5e, 0xf2, 0x4d, 0x13, 0xa4, 0xd0, 0x34, 0x51, 0xa2, 0x4c, 0x93, 0xa4, 0xd4, 0x34, 0x99, 0xc7, 0xfd, 0x92, 0x73, 0xfb, 0x4a, 0x4e, 0x9b, 0xa6, 0x48, 0x91, 0x69, 0x8e, 0x64, 0x99, 0xe6, 0xf2, 0xf7, 0x79, 0x5c, 0x37, 0x9f, 0xfd, 0xef, 0xa5, 0xcc, 0xe4, 0xe6, 0xbc, 0x73, 0x39, 0x96, 0xef, 0xf7, 0x41, 0x8a, 0xb9, 0xbe, 0x04, 0xa5, 0x92, 0x5d, 0xfd, 0x6a, 0x4a, 0x7a, 0xf5, 0xab, 0x29, 0x25, 0xda, 0xcb, 0x52, 0xac, 0xf5, 0x54, 0xef, 0x6b, 0xaf, 0x70, 0x5d, 0x2f, 0xf5, 0xa0, 0xd6, 0x4f, 0xbd, 0xab, 0xf5, 0x67, 0x7f, 0x00, 0x49, 0xbf, 0xea, 0xd5, 0x94, 0xbb, 0xb4, 0x91, + 0x5c, 0xae, 0x7a, 0x25, 0x25, 0x52, 0xe3, 0x7c, 0xb4, 0xf1, 0x5c, 0x3f, 0x89, 0xbf, 0x7f, 0x25, 0x27, 0xb4, 0x29, 0x6c, 0xa9, 0x63, 0x8d, 0x3a, 0xd6, 0xbe, 0x21, 0xf5, 0xcf, 0x22, 0xe1, 0xcf, 0x51, 0x0f, 0x68, 0x73, 0xb9, 0xdd, 0x77, 0xa4, 0xfb, 0x79, 0x5c, 0x17, 0x2c, 0x71, 0xda, 0x8f, 0x5c, 0x5e, 0xc2, 0xe3, 0x2f, 0x93, 0x64, 0x6d, 0x15, 0xd7, 0xb1, 0x62, 0xd6, 0xd6, 0xb3, 0xdd, 0x80, 0x8d, 0xd8, 0x84, 0xcd, 0xd8, 0x82, 0x10, 0x84, 0x22, 0x0c, 0x5b, 0xb1, 0x8d, 0x63, 0x6f, 0xc7, 0x0e, 0xb1, 0x6b, 0x3b, 0x25, 0x5a, 0xdb, 0xc5, 0x63, 0xfe, 0xec, 0xff, 0x0c, 0x41, 0xa2, 0xf6, 0xab, 0xc4, 0x6a, 0xbb, 0x39, 0xaf, 0x3d, 0x1c, 0x6f, 0xaf, 0xc4, 0x68, 0xbf, 0xf1, 0xf8, 0xcc, 0xf4, 0xda, 0x7e, 0xae, 0x3f, 0xc0, 0xdf, 0xab, 0x3e, 0x43, 0x50, 0x78, 0xcb, 0x67, 0x08, 0x1c, + 0xda, 0x11, 0x8e, 0x73, 0x94, 0x7d, 0x46, 0xaa, 0xea, 0xcf, 0x12, 0xd8, 0xb5, 0x93, 0xdc, 0x36, 0x9a, 0xfd, 0x18, 0xd5, 0x57, 0x8b, 0x65, 0x1b, 0xc7, 0x63, 0xc4, 0x73, 0xdf, 0xaa, 0xcf, 0x12, 0x9c, 0xd0, 0x12, 0x25, 0x5e, 0x23, 0xb5, 0x6a, 0x49, 0x6c, 0x53, 0xb9, 0x6d, 0x1a, 0xc7, 0x71, 0x70, 0x9b, 0x74, 0x64, 0xaa, 0xcf, 0xb5, 0x53, 0xea, 0x5b, 0x2d, 0x4b, 0xfd, 0xa8, 0x9d, 0x96, 0xfc, 0xea, 0xcf, 0x16, 0x24, 0x6a, 0x4e, 0x49, 0xd2, 0x72, 0x38, 0xaf, 0x73, 0x1c, 0xff, 0x77, 0xca, 0xe0, 0x3c, 0xe7, 0x76, 0x41, 0x32, 0xb4, 0x8b, 0x3c, 0x27, 0x97, 0xaa, 0xa7, 0x5d, 0x52, 0xfd, 0xb5, 0x5c, 0xca, 0x3f, 0x8f, 0xdb, 0xe5, 0x4b, 0x9e, 0xc6, 0x48, 0xa7, 0x79, 0xb8, 0x5d, 0xd5, 0xe7, 0x0e, 0x16, 0x6b, 0x45, 0x52, 0xa4, 0x15, 0x73, 0xfc, 0x12, 0xce, 0xa1, 0x94, 0xe3, 0x94, + 0x71, 0xfd, 0x15, 0xf6, 0xcb, 0x79, 0xec, 0xab, 0x9c, 0x63, 0x05, 0x2a, 0xa9, 0x1b, 0xaf, 0x1a, 0xac, 0xdd, 0xc4, 0x1f, 0x94, 0x8d, 0x48, 0x9c, 0x59, 0x49, 0x82, 0xd9, 0x44, 0xe2, 0xd7, 0xe4, 0x84, 0xd9, 0x2c, 0x69, 0xe6, 0x00, 0x04, 0x82, 0x74, 0x6c, 0xb6, 0x88, 0xa7, 0xfa, 0x15, 0x94, 0x34, 0xb3, 0x95, 0xdb, 0x54, 0x7d, 0x0e, 0x21, 0xc1, 0x7c, 0xbb, 0x1c, 0x37, 0xdf, 0xc1, 0xfe, 0x9d, 0xdc, 0xe6, 0x2e, 0x2e, 0xd7, 0x44, 0x2d, 0x6e, 0x57, 0x5b, 0x92, 0xcc, 0x75, 0xb8, 0xed, 0xdd, 0x1c, 0xb7, 0xae, 0xa4, 0x98, 0xef, 0x91, 0x0c, 0x33, 0xc9, 0xce, 0x7c, 0x9f, 0x24, 0x9b, 0xef, 0x97, 0x68, 0x33, 0xa9, 0xce, 0x5c, 0x5f, 0x32, 0xcd, 0x0d, 0xe4, 0x94, 0xb9, 0x21, 0x7f, 0x7f, 0x90, 0xfb, 0x37, 0xe2, 0xb6, 0x8d, 0xb9, 0x4f, 0x13, 0xfe, 0x4e, 0x7b, 0xf7, 0x7f, 0x36, 0xa1, + 0x19, 0xd7, 0x0d, 0xe4, 0xb6, 0x7f, 0x7d, 0x3e, 0x21, 0xca, 0xfc, 0x9a, 0x44, 0x9a, 0x37, 0x49, 0xba, 0x79, 0x33, 0xf7, 0xdb, 0xc2, 0x3e, 0xab, 0x3b, 0xf3, 0x0e, 0xb6, 0xbb, 0x40, 0x9d, 0x9a, 0x4f, 0xf0, 0xb7, 0x28, 0xa5, 0x9b, 0xe3, 0xa5, 0xd8, 0x6c, 0x43, 0xaa, 0x94, 0x98, 0x1d, 0x5c, 0x97, 0x25, 0xd9, 0xe6, 0x6c, 0x5c, 0xe0, 0xd8, 0x17, 0xe1, 0x82, 0x1b, 0x97, 0x38, 0x87, 0x5c, 0xb6, 0x79, 0xc8, 0x87, 0xef, 0x3b, 0x6d, 0x3d, 0x6c, 0x0b, 0x50, 0x88, 0x22, 0x8e, 0x59, 0xf5, 0x1d, 0xb7, 0x29, 0x66, 0x52, 0x87, 0xb9, 0x4c, 0x0a, 0xfd, 0xdf, 0x71, 0x5b, 0xce, 0x39, 0x5f, 0x05, 0xab, 0x23, 0x73, 0x25, 0xe7, 0x75, 0x0d, 0xd7, 0xd9, 0xaf, 0x7a, 0xf5, 0x24, 0xcd, 0x7c, 0x13, 0x7f, 0xf0, 0x77, 0x91, 0xb4, 0x00, 0xca, 0x36, 0xc0, 0xa4, 0xea, 0x04, 0x68, 0x12, 0x19, 0x40, + 0xd9, 0x06, 0x50, 0xb6, 0x01, 0x81, 0x52, 0x18, 0x50, 0x03, 0x16, 0xae, 0xab, 0x7a, 0xf5, 0x24, 0x2d, 0xe0, 0x45, 0x49, 0x0f, 0x78, 0x09, 0x3d, 0xd1, 0x8b, 0xcb, 0x7d, 0xd0, 0x0f, 0x03, 0xb8, 0xcd, 0x40, 0x0c, 0x06, 0xe3, 0x6d, 0xc0, 0x0a, 0x6e, 0xbf, 0x52, 0x4a, 0xfc, 0x9f, 0x63, 0xa8, 0xcf, 0xb6, 0xa1, 0xc4, 0x05, 0x36, 0x92, 0xc8, 0xc0, 0xc6, 0x72, 0x22, 0xb0, 0x89, 0xc4, 0x04, 0x1e, 0x95, 0xcc, 0xc0, 0x63, 0x88, 0x54, 0x2f, 0x07, 0x46, 0xa9, 0x7a, 0x81, 0x89, 0xec, 0xdb, 0x91, 0x84, 0x64, 0xa4, 0xe0, 0xac, 0xa4, 0x07, 0x3a, 0x55, 0x93, 0xc0, 0xdf, 0xb9, 0xdf, 0x25, 0x39, 0x19, 0x98, 0x0f, 0x66, 0xcc, 0xc0, 0x02, 0x49, 0x08, 0x2c, 0xe4, 0xba, 0xaa, 0x57, 0x4f, 0xd2, 0x03, 0x2b, 0xfc, 0xff, 0xea, 0x3a, 0xbd, 0xfa, 0xb3, 0x0c, 0x71, 0x35, 0x34, 0xf5, 0xfe, 0x2d, + 0x9f, 0x65, 0x48, 0xaa, 0x51, 0x43, 0x52, 0x6a, 0x30, 0x46, 0x55, 0x7f, 0x96, 0xc1, 0xf7, 0xea, 0x49, 0xa4, 0xa5, 0x95, 0x14, 0x5a, 0x1e, 0x41, 0x6b, 0xb4, 0x41, 0x5b, 0x3c, 0x8a, 0x76, 0x68, 0x8f, 0xc7, 0xd0, 0x01, 0x8f, 0xa3, 0xea, 0xd5, 0x93, 0x42, 0xcb, 0x53, 0x78, 0x1a, 0xcf, 0xa0, 0x23, 0x3a, 0xe1, 0x59, 0x3c, 0x87, 0xe7, 0xd1, 0x19, 0x5d, 0xd0, 0x15, 0xdd, 0xd0, 0x1d, 0x2f, 0xa0, 0x07, 0x5e, 0xc4, 0x4b, 0xe8, 0x89, 0xbf, 0x5e, 0x3d, 0x29, 0xb4, 0xf4, 0xc1, 0x5f, 0xaf, 0x9e, 0x14, 0x5a, 0x06, 0x60, 0x20, 0x06, 0x61, 0x30, 0x86, 0xe0, 0x55, 0xfc, 0xf5, 0xea, 0x49, 0xa1, 0xe5, 0x75, 0xfc, 0xf5, 0xea, 0x49, 0xe1, 0x2d, 0xaf, 0x9e, 0x14, 0x5a, 0xde, 0x92, 0x64, 0xcb, 0xdb, 0x78, 0x47, 0x52, 0x2d, 0x63, 0x24, 0xdd, 0xf2, 0x11, 0x3e, 0xc6, 0x58, 0x7c, 0x82, 0x4f, + 0xf1, 0x19, 0x36, 0x63, 0x0b, 0x42, 0x10, 0x8a, 0x30, 0x6c, 0xc5, 0x36, 0xec, 0x54, 0x1f, 0x5b, 0xe2, 0xd4, 0x93, 0x96, 0x78, 0xf5, 0x84, 0xc5, 0xa6, 0x1e, 0xb4, 0x24, 0xa8, 0xda, 0x96, 0x64, 0x35, 0xcd, 0x92, 0xc2, 0x7e, 0x1a, 0x4e, 0xa9, 0x96, 0x96, 0x2c, 0x55, 0xd7, 0x92, 0xcd, 0x7e, 0xbe, 0x94, 0x59, 0x2e, 0xc3, 0x83, 0x0a, 0xa5, 0xeb, 0xb7, 0x4b, 0xba, 0xce, 0xba, 0x42, 0xbf, 0x13, 0x77, 0xa1, 0x26, 0x6a, 0xa1, 0x36, 0xea, 0xe0, 0x6e, 0xd4, 0x25, 0x65, 0xdf, 0xc3, 0xf6, 0x5e, 0xdc, 0x87, 0xfb, 0xc1, 0xfa, 0x43, 0x67, 0xfd, 0xa1, 0xb3, 0xfe, 0xd0, 0x1b, 0xa0, 0x21, 0x58, 0x87, 0xe8, 0x8d, 0xd0, 0x18, 0x4d, 0xd0, 0x14, 0xd4, 0xa1, 0xde, 0x0c, 0x0f, 0xa3, 0x39, 0x5a, 0xa0, 0x25, 0x5a, 0xa1, 0xea, 0xb3, 0x1d, 0xe9, 0xb7, 0x7c, 0xb6, 0x23, 0x5d, 0x6f, 0x8f, 0xc7, + 0xd0, 0x01, 0xff, 0xfe, 0x2b, 0x29, 0x77, 0xe9, 0x9f, 0x62, 0x0a, 0xa6, 0x62, 0x1a, 0xbe, 0xc6, 0x74, 0x7c, 0xa3, 0xa2, 0xf5, 0x19, 0x6c, 0xbf, 0xc5, 0x4c, 0xcc, 0xc2, 0x6c, 0xcc, 0xc1, 0x42, 0x2c, 0x42, 0xb0, 0xaa, 0xa5, 0x2f, 0x97, 0x04, 0x7d, 0x05, 0x56, 0x62, 0x15, 0x56, 0x63, 0x0d, 0xd6, 0x62, 0x1d, 0xd6, 0x63, 0x03, 0x36, 0x62, 0x13, 0x36, 0xa3, 0xea, 0x95, 0x94, 0x04, 0x3d, 0x14, 0x61, 0xd8, 0x8a, 0x6d, 0xd8, 0x8e, 0x1d, 0xd8, 0x89, 0x5d, 0xf8, 0x19, 0xbf, 0xe0, 0x57, 0x54, 0xbd, 0x92, 0x92, 0xa0, 0xef, 0x05, 0x73, 0x84, 0xbe, 0x0f, 0xfb, 0xa5, 0x42, 0x3f, 0xc0, 0xf6, 0x20, 0x0e, 0x21, 0x1c, 0xc7, 0x24, 0x52, 0x3f, 0x8e, 0x13, 0x88, 0x44, 0x14, 0xa2, 0x11, 0x83, 0x58, 0x39, 0xa9, 0xc7, 0x21, 0x1e, 0x36, 0x24, 0x70, 0x5d, 0x22, 0xec, 0xec, 0x27, 0xb1, 0x4d, + 0x46, 0x0a, 0x52, 0x91, 0x06, 0x07, 0xd2, 0x91, 0x81, 0x4c, 0x64, 0x21, 0x1b, 0xa7, 0x71, 0x06, 0x67, 0xe1, 0x54, 0xcf, 0xe8, 0x39, 0x6c, 0xcf, 0x81, 0xfe, 0xab, 0x9f, 0x07, 0x6b, 0x14, 0xdd, 0x05, 0x37, 0x58, 0xa3, 0xe8, 0xcc, 0xe3, 0x7a, 0x1e, 0xf2, 0x71, 0x59, 0xd2, 0x74, 0x8f, 0x64, 0xea, 0x05, 0x52, 0xa2, 0x17, 0xb2, 0x2d, 0x42, 0x31, 0x4a, 0x50, 0x8a, 0x32, 0x5c, 0x41, 0x39, 0xae, 0xc2, 0xf7, 0x7b, 0x6c, 0x95, 0xb8, 0x86, 0xeb, 0xb8, 0x01, 0x2f, 0x6e, 0xe2, 0x0f, 0xd2, 0x7d, 0x0d, 0x29, 0x32, 0x2c, 0xd0, 0x61, 0xc0, 0x2a, 0x91, 0x46, 0x10, 0xdb, 0xdb, 0x70, 0x3b, 0xee, 0xc0, 0x9d, 0xa8, 0xad, 0x3a, 0x1a, 0xf7, 0xab, 0x16, 0x46, 0x3d, 0xd5, 0xe9, 0x96, 0x57, 0x52, 0x1e, 0xa8, 0xfe, 0x0c, 0x88, 0x5e, 0xfd, 0x4a, 0x4a, 0xf7, 0xea, 0x57, 0x52, 0xd2, 0x8c, 0xd6, + 0x6a, 0x9b, 0xd1, 0x46, 0xb5, 0x34, 0xda, 0xaa, 0xfb, 0x8d, 0x76, 0x6c, 0x9f, 0xe6, 0xba, 0xe7, 0xd4, 0x23, 0xc6, 0xf3, 0xea, 0x1e, 0xa3, 0xb3, 0x7a, 0xdc, 0xe8, 0xc6, 0xe5, 0xee, 0x78, 0x01, 0x3d, 0xf0, 0xa2, 0xa4, 0x1a, 0x2f, 0xb3, 0xed, 0x89, 0x57, 0xc0, 0x98, 0x69, 0xf4, 0x06, 0xe3, 0xa6, 0xd1, 0x17, 0x8c, 0x9d, 0x46, 0x7f, 0x0c, 0xe0, 0x76, 0x03, 0xd9, 0x0e, 0xe2, 0x3c, 0x19, 0x43, 0x8d, 0x21, 0x78, 0x15, 0x43, 0x31, 0x0c, 0xcc, 0x29, 0xc6, 0xeb, 0x18, 0x8e, 0x37, 0x30, 0x02, 0x23, 0x31, 0x0a, 0x6f, 0xe2, 0x2d, 0xd5, 0xde, 0x78, 0x9b, 0xed, 0x3b, 0x78, 0x17, 0xef, 0xe1, 0x7d, 0x8c, 0xc6, 0x07, 0xf8, 0x10, 0x63, 0xf0, 0x11, 0x3e, 0xc6, 0x58, 0xb0, 0xee, 0x35, 0x58, 0xf7, 0x1a, 0xac, 0x7b, 0x8d, 0xcf, 0xc0, 0x3a, 0xc8, 0xf8, 0x02, 0xe3, 0x31, 0x01, 0x13, 0x31, + 0x09, 0x93, 0xc5, 0x66, 0x7c, 0xc9, 0xf6, 0x2b, 0xce, 0x8f, 0x75, 0x91, 0x31, 0x15, 0xd3, 0xf1, 0x0d, 0xaa, 0xfe, 0x85, 0x75, 0x9a, 0x31, 0x13, 0xb3, 0x30, 0x1b, 0x73, 0x30, 0x57, 0x8a, 0x8d, 0xef, 0xd8, 0xce, 0x53, 0x6d, 0x8d, 0xf9, 0x12, 0x67, 0x7c, 0x8f, 0x05, 0x58, 0x88, 0x45, 0x60, 0xed, 0x64, 0xfc, 0x80, 0xc5, 0xf8, 0x11, 0x4b, 0xf0, 0x13, 0x96, 0x91, 0xee, 0x97, 0x63, 0x05, 0x56, 0x62, 0x15, 0x56, 0x63, 0x8d, 0x1a, 0x60, 0xac, 0x65, 0xbb, 0x0e, 0xeb, 0xb1, 0x01, 0x1b, 0xb1, 0x09, 0x9b, 0xb1, 0x05, 0x21, 0x08, 0x45, 0x18, 0xb6, 0x82, 0xf5, 0x95, 0xc1, 0xfa, 0xca, 0xd8, 0x81, 0x9d, 0xd8, 0x85, 0x9f, 0xf1, 0x0b, 0x7e, 0xc5, 0x6e, 0xec, 0xc1, 0x5e, 0xfc, 0x86, 0x7d, 0xd8, 0x8f, 0x03, 0x38, 0x88, 0x43, 0x08, 0xc7, 0x61, 0xb0, 0xbe, 0x32, 0x58, 0x5f, 0x19, 0x47, + 0x71, 0x0c, 0xc7, 0x71, 0x02, 0x91, 0x60, 0x9d, 0x65, 0x9c, 0x44, 0xb4, 0x64, 0x18, 0x31, 0x6c, 0x63, 0x11, 0x87, 0x78, 0xd8, 0x90, 0x80, 0x44, 0xd8, 0x91, 0x84, 0x64, 0xa4, 0x20, 0x15, 0x69, 0x92, 0x68, 0x38, 0xd8, 0xfa, 0xfe, 0xb5, 0x75, 0x06, 0xfb, 0x99, 0x60, 0xcd, 0x6f, 0x64, 0xd1, 0x86, 0xb3, 0x71, 0x1a, 0x67, 0x70, 0x16, 0x4e, 0xe4, 0xe0, 0x9c, 0x14, 0x1a, 0xbf, 0xe3, 0x3c, 0x2e, 0xe0, 0xa2, 0x64, 0x1b, 0x2e, 0xb8, 0x71, 0x09, 0xb9, 0xc8, 0x43, 0x3e, 0x2e, 0xc3, 0x83, 0x02, 0x14, 0xa2, 0x08, 0xc5, 0x28, 0x01, 0xeb, 0x66, 0x83, 0x7e, 0x65, 0xd0, 0xaf, 0x0c, 0xfa, 0x95, 0x41, 0xbf, 0x32, 0xe8, 0x57, 0x06, 0xfd, 0xca, 0xa0, 0x5f, 0x59, 0xef, 0x57, 0x4d, 0xac, 0xcc, 0xdf, 0xd6, 0xfa, 0x68, 0x80, 0x86, 0x78, 0x10, 0x8d, 0xd0, 0x18, 0x4d, 0xd0, 0x14, 0x0f, 0xa1, + 0x19, 0x1e, 0x46, 0x73, 0xb4, 0x40, 0x4b, 0xb4, 0xc2, 0x23, 0x68, 0x8d, 0x36, 0x68, 0x2b, 0x51, 0xd6, 0x47, 0xd1, 0x0e, 0xed, 0xf1, 0x18, 0x3a, 0xe0, 0x71, 0x3c, 0x81, 0x27, 0xf1, 0x14, 0x9e, 0xc6, 0x33, 0xe8, 0x88, 0x4e, 0x78, 0x16, 0xcf, 0xe1, 0x79, 0x74, 0x46, 0x17, 0x74, 0x45, 0x37, 0x74, 0xc7, 0x0b, 0xe8, 0x81, 0x17, 0xf1, 0x12, 0x5e, 0x96, 0x58, 0x6b, 0x4f, 0xbc, 0x82, 0x5e, 0xe8, 0x8d, 0x3e, 0xe8, 0x8b, 0x7e, 0xe8, 0x8f, 0x01, 0x92, 0x64, 0x1d, 0x88, 0x41, 0x18, 0x8c, 0x21, 0x78, 0x15, 0x43, 0x31, 0x0c, 0xaf, 0xe1, 0x75, 0x0c, 0xc7, 0x1b, 0x18, 0x81, 0x91, 0x18, 0x85, 0x37, 0xf1, 0x16, 0xde, 0xc6, 0x3b, 0x78, 0x17, 0xef, 0xe1, 0x7d, 0x8c, 0xc6, 0x07, 0xf8, 0x10, 0x63, 0xf0, 0x91, 0x6a, 0x6e, 0xfd, 0x98, 0xed, 0x58, 0x92, 0xff, 0x27, 0x6c, 0xc7, 0xe1, 0x53, + 0x7c, 0x86, 0xcf, 0xf1, 0x05, 0xc6, 0x63, 0x02, 0x26, 0x62, 0x12, 0x26, 0xe3, 0x4b, 0x7c, 0x85, 0x29, 0x98, 0x8a, 0x69, 0x98, 0x81, 0x6f, 0x31, 0x13, 0xb3, 0x30, 0x1b, 0x73, 0x30, 0x17, 0xdf, 0x61, 0x1e, 0xe6, 0xe3, 0x7b, 0x2c, 0xc0, 0x42, 0x2c, 0x42, 0x30, 0x7e, 0xc0, 0x62, 0xfc, 0x88, 0x25, 0xa8, 0xfa, 0x1c, 0x4a, 0x8c, 0x75, 0x39, 0x56, 0x60, 0x25, 0x56, 0x61, 0x35, 0xd6, 0x60, 0x2d, 0xd6, 0x61, 0x3d, 0x36, 0x60, 0x23, 0x36, 0x61, 0x33, 0xb6, 0x20, 0x04, 0xa1, 0x08, 0xc3, 0x56, 0x6c, 0xc3, 0x76, 0xec, 0xc0, 0x4e, 0xec, 0x42, 0xd5, 0x2b, 0x29, 0xe9, 0xd6, 0x43, 0xaa, 0x8e, 0x35, 0x1c, 0x87, 0x11, 0x81, 0xaa, 0x57, 0x52, 0xd2, 0xac, 0x91, 0x88, 0x92, 0x53, 0xd6, 0x93, 0x88, 0x96, 0x04, 0x6b, 0x2c, 0xc8, 0x27, 0xd6, 0x78, 0xd8, 0x90, 0x80, 0x44, 0xd8, 0x91, 0x84, + 0x64, 0xa4, 0x48, 0xa2, 0x35, 0x95, 0x2d, 0xfd, 0xc6, 0x7a, 0x4a, 0xbc, 0xd6, 0x7c, 0x8e, 0x7f, 0x19, 0x1e, 0x14, 0xa0, 0x10, 0x64, 0x09, 0x6b, 0x39, 0xae, 0x82, 0xb5, 0xa1, 0xb5, 0x12, 0xd7, 0xc4, 0x6e, 0xbd, 0x0e, 0xd6, 0xbe, 0x56, 0x2f, 0x6e, 0x8a, 0x3d, 0x68, 0x91, 0x44, 0x06, 0x2d, 0x56, 0x3d, 0x83, 0x7e, 0x54, 0x9f, 0x07, 0xfd, 0xa4, 0x1e, 0x0c, 0x5a, 0x2a, 0x25, 0x41, 0xcb, 0x24, 0x2d, 0xe8, 0x20, 0xdb, 0x43, 0x92, 0x1e, 0x14, 0x2e, 0xb6, 0xa0, 0xc3, 0x92, 0x10, 0x14, 0xc1, 0xed, 0x98, 0x17, 0x83, 0x12, 0xc9, 0xa0, 0x33, 0x95, 0x59, 0x82, 0x55, 0x00, 0x02, 0x65, 0xb0, 0xaa, 0x2d, 0xe3, 0x55, 0x6b, 0xc9, 0x52, 0x6d, 0xd0, 0x16, 0xed, 0x24, 0x5e, 0xb5, 0xc7, 0x63, 0xe8, 0x80, 0xc7, 0xf1, 0x04, 0x9e, 0x24, 0x91, 0x3f, 0x85, 0xa7, 0xf1, 0x8c, 0xc4, 0xa9, 0x8e, + 0x5c, 0xd7, 0x09, 0x6f, 0xf8, 0x5f, 0x9d, 0xb0, 0xab, 0x37, 0x49, 0xe6, 0x9f, 0x70, 0x79, 0x1c, 0x3e, 0x05, 0xa3, 0xb0, 0x9a, 0x20, 0x67, 0xd4, 0x44, 0x4c, 0xc2, 0x64, 0xc4, 0xca, 0x4f, 0x2a, 0x9e, 0xc7, 0x4d, 0xe1, 0xfe, 0xe9, 0x72, 0x4e, 0x65, 0xc8, 0x69, 0x95, 0x29, 0xe5, 0x2a, 0x5b, 0x9c, 0xea, 0x8c, 0xff, 0xd7, 0x4f, 0x13, 0xd4, 0x05, 0xee, 0x7b, 0x11, 0x97, 0x90, 0x2b, 0x11, 0xbe, 0x5f, 0xa1, 0x54, 0x65, 0xf2, 0x9d, 0xaa, 0x94, 0x85, 0xea, 0x9a, 0x2c, 0x24, 0xed, 0x5f, 0x31, 0xd5, 0x91, 0x9d, 0x24, 0xfc, 0x2b, 0x24, 0xfb, 0x4d, 0x24, 0xfb, 0x4d, 0xa4, 0x79, 0xdf, 0x67, 0x23, 0xe2, 0x49, 0xe9, 0x57, 0x48, 0xe9, 0xbe, 0xef, 0xba, 0x4f, 0x37, 0x31, 0xbb, 0x91, 0xb2, 0xe3, 0xb5, 0x71, 0xea, 0x1e, 0xd2, 0x71, 0x3c, 0xe9, 0x38, 0x92, 0x04, 0x7c, 0x5e, 0x9b, 0x4d, + 0xc2, 0x9b, 0xcb, 0x7e, 0xb0, 0x9c, 0x21, 0xf9, 0x46, 0x92, 0x4a, 0xcf, 0x93, 0x70, 0xe2, 0x49, 0x38, 0x4e, 0x33, 0x2b, 0x15, 0x33, 0x2b, 0x15, 0x92, 0x4e, 0x3c, 0x49, 0x27, 0x9e, 0xa4, 0x13, 0x6f, 0x4e, 0x94, 0x85, 0xa4, 0x9a, 0xe5, 0xa4, 0x9a, 0xe5, 0xe6, 0xb3, 0xec, 0x17, 0x71, 0x5d, 0xa5, 0xe4, 0x90, 0x44, 0x72, 0x48, 0x21, 0x9b, 0x48, 0x1d, 0xe7, 0x49, 0x1c, 0xf1, 0x24, 0x0c, 0xdf, 0x77, 0xe8, 0x6f, 0x22, 0x45, 0xc4, 0x93, 0x22, 0xe2, 0x49, 0x11, 0xf1, 0xa4, 0x08, 0x3b, 0xe9, 0xc0, 0x42, 0x02, 0x78, 0x99, 0x04, 0x10, 0x4f, 0x02, 0x88, 0x64, 0xe5, 0x7f, 0x85, 0x95, 0x7f, 0x3c, 0x2b, 0xfe, 0x78, 0x56, 0xf1, 0xf1, 0xac, 0xe2, 0x83, 0x59, 0xc5, 0x07, 0xb3, 0x8a, 0x0f, 0x66, 0x15, 0x1f, 0xcc, 0x2a, 0x3e, 0x98, 0x55, 0x7c, 0x30, 0xab, 0xf8, 0x60, 0x56, 0xf1, 0xc1, + 0xac, 0xe2, 0x83, 0x59, 0xc5, 0x07, 0xb3, 0x8a, 0xdf, 0xc1, 0x2a, 0xfe, 0x47, 0x56, 0xf1, 0xc1, 0xac, 0xe2, 0x83, 0x59, 0xc5, 0x07, 0xb3, 0x8a, 0x0f, 0x66, 0x15, 0x1f, 0xcc, 0x2a, 0x3e, 0x98, 0x55, 0x7c, 0x30, 0xab, 0xf8, 0xff, 0x41, 0xdd, 0x9d, 0xc0, 0xc7, 0x59, 0xd6, 0x7b, 0xff, 0xbf, 0x27, 0x4d, 0xda, 0x2c, 0xb2, 0xc9, 0x26, 0x1c, 0x76, 0x04, 0xb1, 0x22, 0x54, 0x05, 0x0a, 0x88, 0xec, 0x16, 0xd9, 0x0a, 0x88, 0xa0, 0xe0, 0x02, 0x08, 0x28, 0x22, 0xb2, 0x56, 0x40, 0x04, 0x05, 0xb1, 0x2c, 0x0a, 0x6d, 0x01, 0x15, 0xaa, 0x82, 0x58, 0x2d, 0x8a, 0x50, 0x05, 0x8f, 0xc8, 0x62, 0x2b, 0x95, 0x96, 0x92, 0x06, 0xda, 0x49, 0x53, 0x92, 0x12, 0x0a, 0x6d, 0xda, 0x69, 0x66, 0xa6, 0x99, 0xb9, 0x67, 0x32, 0xcd, 0x42, 0x33, 0x99, 0x70, 0xff, 0xdf, 0x33, 0x4c, 0x7b, 0x2a, 0x0f, 0x9c, + 0xa3, 0xcf, 0x73, 0xb6, 0xff, 0x8b, 0xd7, 0x97, 0x99, 0x24, 0x33, 0x73, 0x2f, 0xd7, 0xef, 0xfa, 0x7d, 0x3e, 0xd7, 0xdd, 0x64, 0x66, 0x1a, 0x8b, 0x9f, 0xc6, 0xe2, 0xa7, 0xb1, 0xf8, 0x69, 0x2c, 0x7e, 0x1a, 0x8b, 0x9f, 0xc6, 0xe2, 0xa7, 0xb1, 0xf8, 0x69, 0x2c, 0x7e, 0x1a, 0x8b, 0x9f, 0xc6, 0xe2, 0xa7, 0xb1, 0xf8, 0x69, 0x2c, 0xfe, 0x65, 0x16, 0xff, 0x32, 0x8b, 0x9f, 0xc6, 0xe2, 0xa7, 0xb1, 0xf8, 0x97, 0x59, 0xfc, 0xcb, 0x2c, 0x7e, 0x1a, 0x8b, 0x9f, 0xc6, 0xe2, 0xa7, 0xb1, 0xf8, 0x69, 0x2c, 0x7e, 0x1a, 0x8b, 0x9f, 0xc6, 0xe2, 0xa7, 0xb1, 0xf8, 0x97, 0x59, 0xfc, 0xcb, 0x2c, 0x7e, 0x1a, 0x8b, 0x9f, 0xc6, 0xe2, 0x5f, 0x66, 0xf1, 0x2f, 0xb3, 0xf8, 0x69, 0x2c, 0xfe, 0x1e, 0x16, 0x7f, 0x0f, 0x8b, 0x9f, 0xc6, 0xe2, 0xcf, 0x60, 0xf1, 0xe5, 0xcf, 0x45, 0xf9, 0xec, 0x98, 0xc9, + 0x41, 0x23, 0x23, 0xff, 0x16, 0x03, 0xbf, 0x83, 0x6d, 0x7f, 0xb3, 0xfc, 0x3e, 0x8d, 0x6c, 0xbb, 0xfc, 0x5e, 0x8d, 0xe7, 0xb0, 0xd1, 0xf5, 0x4c, 0xb3, 0x93, 0x69, 0x76, 0x32, 0xcd, 0x4e, 0xa6, 0xd9, 0xc9, 0x34, 0x3b, 0x99, 0x66, 0x27, 0xd3, 0xec, 0x64, 0x9a, 0x9d, 0x4c, 0xb3, 0x93, 0x69, 0x76, 0x32, 0xcd, 0x4e, 0xa6, 0xd9, 0xc9, 0x34, 0x3b, 0xeb, 0x67, 0xca, 0x43, 0xf2, 0x1b, 0xf9, 0xad, 0x3c, 0x2c, 0xbf, 0x93, 0x47, 0xe4, 0x51, 0x99, 0x25, 0xbf, 0x97, 0x3f, 0xc8, 0x63, 0xf2, 0xb8, 0xfc, 0xb1, 0xf2, 0x9b, 0xc6, 0x9d, 0x2c, 0xb3, 0x93, 0x65, 0x76, 0xb2, 0xcc, 0x4e, 0x96, 0xd9, 0xc9, 0x32, 0x3b, 0x59, 0x66, 0x27, 0xcb, 0xec, 0x64, 0x99, 0x2d, 0x2c, 0xb3, 0x85, 0x65, 0xb6, 0xb0, 0xcc, 0x16, 0x96, 0xd9, 0xc2, 0x32, 0x5b, 0x58, 0x66, 0x0b, 0xcb, 0x9c, 0xc7, 0x32, 0xe7, + 0xb1, 0xcc, 0x79, 0x2c, 0xb3, 0x6c, 0x98, 0x2d, 0x0c, 0x73, 0x1e, 0xc3, 0x6c, 0x61, 0x98, 0x2d, 0x0c, 0xb3, 0x85, 0x61, 0xb6, 0x30, 0xcc, 0x16, 0x86, 0xd9, 0xc2, 0x30, 0x5b, 0x18, 0x66, 0x0b, 0xc3, 0x6c, 0x61, 0x98, 0x2d, 0x0c, 0xb3, 0x85, 0x61, 0xb6, 0x30, 0xcc, 0x16, 0x86, 0xd9, 0x52, 0xbf, 0x42, 0x56, 0x4a, 0x97, 0xa8, 0x0f, 0x86, 0xd9, 0xc2, 0x30, 0x5b, 0x18, 0x66, 0x0b, 0xc3, 0x6c, 0x61, 0x98, 0x2d, 0x0c, 0xb3, 0x85, 0x61, 0xb6, 0x30, 0xcc, 0x16, 0x86, 0xd9, 0x52, 0x1f, 0x21, 0x5b, 0x20, 0x31, 0xa9, 0x91, 0x51, 0x52, 0x2b, 0x75, 0x15, 0x63, 0x5c, 0xcc, 0x18, 0x17, 0x33, 0xc6, 0xc5, 0x8c, 0x71, 0x31, 0x63, 0x6c, 0x61, 0x8a, 0x8b, 0x99, 0xe2, 0x62, 0xa6, 0xb8, 0xb8, 0x61, 0xbb, 0x60, 0xeb, 0x86, 0x5d, 0x82, 0x2d, 0x58, 0xe2, 0x1e, 0x2c, 0x71, 0x2c, 0x4b, 0xdc, + 0x8e, 0x25, 0x6e, 0xce, 0x12, 0xff, 0x85, 0x25, 0x8e, 0x65, 0x88, 0x87, 0x35, 0xec, 0xcb, 0x02, 0x3f, 0x1c, 0x7c, 0x92, 0x19, 0x6e, 0xc3, 0xe4, 0x5a, 0x98, 0x5c, 0x0b, 0x93, 0x6b, 0x61, 0x72, 0x2d, 0x4c, 0xae, 0x85, 0xc9, 0xb5, 0x30, 0xb9, 0x16, 0x26, 0xd7, 0xc2, 0xe4, 0x5a, 0x98, 0x5c, 0x0b, 0x93, 0x6b, 0x61, 0x72, 0x2d, 0x4c, 0xae, 0x85, 0xc9, 0xb5, 0x34, 0x7c, 0x59, 0xce, 0x97, 0x0b, 0xe4, 0x42, 0xf9, 0x8a, 0x7c, 0x55, 0x2e, 0x92, 0xaf, 0xc9, 0xc5, 0xf2, 0x75, 0xb9, 0x44, 0xbe, 0x21, 0x97, 0x8a, 0xde, 0xc1, 0xe4, 0x5a, 0x98, 0x5c, 0x0b, 0x93, 0x6b, 0x61, 0x72, 0x2d, 0x4c, 0xae, 0x85, 0xc9, 0xb5, 0x30, 0xb9, 0x16, 0x26, 0xd7, 0xc2, 0xe4, 0x5a, 0x98, 0x5c, 0x0b, 0x93, 0x6b, 0x61, 0x64, 0xaf, 0x31, 0xb2, 0xd7, 0x18, 0xd9, 0x6b, 0x8c, 0xec, 0xb5, 0xf2, 0x7b, 0x76, + 0x32, 0xb2, 0xd7, 0x18, 0xd9, 0x6b, 0x8c, 0xec, 0x35, 0x46, 0xf6, 0x1a, 0x23, 0x2b, 0xbf, 0x8f, 0xe7, 0x71, 0xa8, 0xbd, 0x10, 0xb5, 0x17, 0xa2, 0xf6, 0x42, 0xd4, 0x5e, 0x88, 0xda, 0x0b, 0x51, 0x7b, 0x21, 0x6a, 0x2f, 0x44, 0xed, 0x85, 0xa8, 0xbd, 0x10, 0xb5, 0x17, 0xa2, 0xf6, 0x42, 0xd4, 0x5e, 0x88, 0xda, 0x0b, 0x51, 0x7b, 0x21, 0x6a, 0x2f, 0x44, 0xed, 0x85, 0xa8, 0xbd, 0x10, 0xb5, 0x17, 0xa2, 0xf6, 0x42, 0xd4, 0x5e, 0x88, 0xda, 0x0b, 0x51, 0x7b, 0x21, 0x6a, 0x2f, 0x44, 0xed, 0x85, 0xa8, 0xbd, 0x10, 0xb5, 0x17, 0xa2, 0xf6, 0x72, 0xd4, 0x5e, 0x8e, 0xda, 0xcb, 0x51, 0x7b, 0x39, 0x6a, 0x2f, 0x47, 0xed, 0xe5, 0xa8, 0xbd, 0x1c, 0xb5, 0x97, 0xa3, 0xf6, 0x72, 0x34, 0x3d, 0xa1, 0xf1, 0x46, 0x94, 0xb8, 0xa9, 0xf2, 0xbe, 0x24, 0xff, 0xb5, 0x9f, 0xe6, 0xda, 0x1c, 0xf5, 0xa3, + 0x4c, 0x3f, 0x9a, 0xb4, 0xa0, 0x49, 0x0b, 0x9a, 0xb4, 0xa0, 0x49, 0x0b, 0x9a, 0x94, 0x3f, 0x75, 0x72, 0x35, 0x2a, 0xb4, 0xa0, 0xc2, 0xbc, 0xe0, 0xb8, 0xca, 0xfb, 0x9b, 0x36, 0x47, 0x77, 0xeb, 0xce, 0x53, 0x74, 0xe6, 0x94, 0x8e, 0x9c, 0xd7, 0x7d, 0x17, 0xe8, 0xbc, 0xb7, 0xea, 0xb6, 0x7f, 0xd6, 0x51, 0x53, 0xb1, 0xf1, 0xd1, 0x47, 0x63, 0x13, 0xa2, 0x07, 0x62, 0xe7, 0x44, 0xaf, 0xc5, 0xae, 0x8e, 0xd6, 0xc5, 0xae, 0x89, 0xae, 0x8d, 0x5d, 0x1b, 0x65, 0x63, 0xdf, 0x8a, 0x16, 0xc5, 0x7e, 0x18, 0xcd, 0xd1, 0x41, 0xcb, 0xd7, 0x4c, 0x96, 0xea, 0x78, 0x07, 0xe8, 0x78, 0x87, 0xea, 0x5a, 0x53, 0x74, 0xad, 0x1f, 0xeb, 0x34, 0x53, 0x74, 0x9a, 0x65, 0x3a, 0xcd, 0x32, 0x9d, 0x66, 0x8a, 0x4e, 0x33, 0x45, 0xa7, 0x59, 0xa6, 0xd3, 0x2c, 0xd3, 0x69, 0xa6, 0xe8, 0x34, 0x53, 0x74, 0x9a, + 0x29, 0x3a, 0xcd, 0x14, 0x9d, 0x66, 0x8a, 0x4e, 0x33, 0x45, 0xa7, 0x99, 0xa2, 0xd3, 0x2c, 0xd3, 0x69, 0x96, 0xe9, 0x34, 0x53, 0x74, 0x9a, 0x29, 0x3a, 0xcd, 0x32, 0x9d, 0x66, 0x99, 0x4e, 0x33, 0x45, 0x57, 0x39, 0xb3, 0xf2, 0x37, 0x19, 0xad, 0xc1, 0x55, 0xba, 0xc9, 0x24, 0x9d, 0x64, 0x82, 0x4e, 0xd2, 0x6e, 0xfd, 0xb9, 0x45, 0xe5, 0x9d, 0xb7, 0x56, 0x44, 0xeb, 0xac, 0x87, 0xf6, 0x52, 0xed, 0x1f, 0x51, 0xed, 0x3b, 0x55, 0x7f, 0x2f, 0xbe, 0xfc, 0x8e, 0x52, 0xef, 0x55, 0xed, 0xef, 0xa9, 0xfe, 0x5e, 0xfc, 0xfe, 0x2a, 0x7e, 0x3f, 0x15, 0xff, 0x5e, 0x15, 0x7f, 0x83, 0x8a, 0x3f, 0x50, 0xe5, 0x66, 0xf8, 0xfe, 0xbe, 0xaa, 0x29, 0x54, 0x39, 0xfb, 0x19, 0xad, 0x4f, 0xfc, 0x97, 0x9e, 0x9d, 0x29, 0xff, 0xbf, 0x3f, 0x3b, 0x57, 0x04, 0xa3, 0x82, 0x03, 0x82, 0x5a, 0xa9, 0x0b, 0xc6, + 0x07, 0x5b, 0x07, 0xdb, 0x06, 0xef, 0x8f, 0xee, 0x0f, 0xf6, 0x8a, 0x1e, 0x0d, 0xf6, 0x89, 0x1e, 0x09, 0x3e, 0x18, 0x3d, 0x17, 0x8c, 0x0d, 0xf6, 0x0d, 0x3e, 0xe4, 0xec, 0xed, 0xeb, 0xa7, 0xb7, 0x39, 0x73, 0x3f, 0x08, 0xf6, 0x2c, 0xff, 0xfd, 0x5f, 0xec, 0x13, 0xd1, 0xca, 0xd8, 0x11, 0xa2, 0x83, 0xc4, 0x74, 0x90, 0x9a, 0xa3, 0x2a, 0xef, 0x70, 0x52, 0x7e, 0x57, 0x93, 0xee, 0x9a, 0xb3, 0xa3, 0xbb, 0x6b, 0x3e, 0x1f, 0x6c, 0x5d, 0x73, 0x81, 0x5c, 0x18, 0xf5, 0xd4, 0x5c, 0xe4, 0xeb, 0x4b, 0xdc, 0xbf, 0x25, 0x18, 0x57, 0x73, 0x6b, 0x14, 0x96, 0xff, 0xce, 0xad, 0xee, 0x80, 0xe8, 0x6f, 0x75, 0x07, 0xca, 0xfc, 0x28, 0x1c, 0xf3, 0xa1, 0xe0, 0x80, 0x31, 0xfb, 0xca, 0x87, 0x65, 0x3f, 0xd9, 0x5f, 0xc6, 0xc9, 0x47, 0xe4, 0xa3, 0xf2, 0x31, 0x19, 0x2f, 0x07, 0xcb, 0x21, 0x72, 0xa8, + 0x7c, 0x5c, 0x0e, 0x93, 0x4f, 0xc8, 0xe1, 0x72, 0x84, 0x1c, 0x29, 0x47, 0xc9, 0xd1, 0x72, 0x8c, 0x1c, 0x2b, 0x9f, 0x94, 0x09, 0x72, 0x9c, 0x7c, 0x4a, 0xce, 0x93, 0x2f, 0x07, 0xe3, 0xc7, 0x9c, 0x2f, 0x3d, 0xc1, 0xb6, 0x63, 0xb2, 0xc1, 0xb6, 0xf5, 0xa7, 0x46, 0xf7, 0xd7, 0x9f, 0x26, 0x9f, 0x91, 0x33, 0xe5, 0xb3, 0xf2, 0x39, 0x39, 0x4b, 0x2e, 0x8f, 0x1e, 0xa9, 0xbf, 0x42, 0xae, 0x8c, 0x9e, 0xab, 0xbf, 0x4a, 0x26, 0xc9, 0x37, 0xe5, 0x6a, 0xb9, 0x46, 0xae, 0x0b, 0xf6, 0xad, 0xff, 0xb6, 0x5c, 0x1f, 0xdd, 0x5d, 0x7f, 0x83, 0x7c, 0x47, 0xbe, 0x2b, 0x37, 0xcb, 0xf7, 0x65, 0xb2, 0xdc, 0x22, 0xb7, 0xca, 0x5d, 0xc1, 0xd6, 0xf5, 0x77, 0xcb, 0x3d, 0xf2, 0x63, 0xf9, 0x89, 0xdc, 0xab, 0xab, 0xdf, 0x1f, 0xec, 0x66, 0xed, 0x95, 0xb7, 0xf6, 0xca, 0x37, 0xac, 0x0a, 0xf6, 0x6c, 0x58, + 0x2d, 0x6f, 0xff, 0x9d, 0xfb, 0x77, 0xf9, 0x5d, 0xfb, 0xa6, 0x63, 0xa3, 0x19, 0xc1, 0xfb, 0x8c, 0xda, 0x3c, 0xa3, 0x36, 0xcf, 0xa8, 0xf5, 0x18, 0xad, 0xd9, 0x46, 0x60, 0xb6, 0xb3, 0x3e, 0xdb, 0x91, 0xcd, 0x73, 0x64, 0xf3, 0x1c, 0xd9, 0x3c, 0x47, 0x36, 0xcf, 0x91, 0xcd, 0x73, 0x64, 0xf3, 0x1c, 0xd9, 0x3c, 0x47, 0xd6, 0xe3, 0xc8, 0x7a, 0xec, 0xf9, 0x6c, 0x7b, 0x3e, 0xdb, 0x9e, 0xcf, 0xb6, 0xe7, 0xb3, 0xed, 0xf9, 0x6c, 0x7b, 0x3e, 0xdb, 0x9e, 0xcf, 0xb6, 0xe7, 0xb3, 0xed, 0xf9, 0xec, 0xe0, 0x76, 0xd6, 0x39, 0x93, 0x75, 0xce, 0x64, 0x9d, 0x97, 0xb3, 0xce, 0x6b, 0x98, 0x66, 0x2f, 0xd3, 0xec, 0x65, 0x9a, 0xbd, 0x4c, 0xb3, 0x97, 0x69, 0xf6, 0x32, 0xcd, 0x5e, 0xa6, 0xb9, 0x8a, 0x69, 0xae, 0x62, 0x9a, 0xab, 0xcc, 0xbc, 0x15, 0x4c, 0xb3, 0x97, 0x69, 0xf6, 0xb2, 0xcb, 0x5e, + 0x76, 0xd9, 0xcb, 0x2e, 0x7b, 0xd9, 0xe5, 0xaa, 0x58, 0xf9, 0xfa, 0xfc, 0x55, 0xe2, 0x3e, 0xa3, 0x5b, 0xc5, 0xe6, 0x56, 0x99, 0x5d, 0xbb, 0x31, 0xad, 0x5e, 0xa6, 0xb5, 0x8a, 0x5d, 0xf5, 0xb2, 0xab, 0x99, 0xec, 0x6a, 0x26, 0xbb, 0x9a, 0xc9, 0xae, 0x66, 0xb2, 0xab, 0x99, 0xec, 0x6a, 0x26, 0xbb, 0x9a, 0xc9, 0xae, 0x66, 0xb2, 0xab, 0x99, 0x8c, 0x6a, 0x26, 0xa3, 0x9a, 0xc9, 0xa8, 0x66, 0x32, 0xaa, 0x99, 0x8c, 0x6a, 0x26, 0xa3, 0x9a, 0xc9, 0xa8, 0x66, 0x32, 0xaa, 0x99, 0x8c, 0x6a, 0x26, 0xa3, 0x9a, 0xc9, 0xa8, 0x66, 0x32, 0xaa, 0x99, 0x8c, 0x6a, 0x26, 0xa3, 0x9a, 0xc9, 0xa8, 0x66, 0x32, 0xaa, 0x99, 0x8c, 0x6a, 0x26, 0xa3, 0x9a, 0xc9, 0x7a, 0x66, 0xb2, 0x9e, 0xcb, 0x59, 0xcf, 0xe5, 0x2c, 0xe7, 0x1a, 0x86, 0x73, 0x0d, 0x9b, 0xe8, 0x65, 0x13, 0xbd, 0x6c, 0xa2, 0x97, 0x4d, + 0xf4, 0xb2, 0x88, 0x5e, 0x16, 0xb1, 0x8a, 0x45, 0xac, 0x62, 0x11, 0xab, 0x58, 0xc4, 0x2a, 0x06, 0xb1, 0x8a, 0x41, 0xf4, 0x32, 0x88, 0x5e, 0x06, 0xd1, 0xcb, 0x20, 0x7a, 0x19, 0x44, 0x2f, 0x83, 0xe8, 0x65, 0x10, 0xbd, 0x0c, 0xa2, 0x97, 0x39, 0xf4, 0x32, 0x87, 0x5e, 0xe6, 0xd0, 0xcb, 0x1c, 0x7a, 0xcd, 0xe0, 0x5e, 0xe6, 0xd0, 0xcb, 0x1c, 0x7a, 0x99, 0x43, 0x2f, 0x73, 0xe8, 0x65, 0x0e, 0xbd, 0xcc, 0xa1, 0x97, 0x39, 0xf4, 0x32, 0x87, 0x5e, 0xe6, 0xd0, 0xcb, 0x1a, 0x7a, 0x59, 0x43, 0xaf, 0xd9, 0xde, 0x68, 0xb6, 0xef, 0x6c, 0xb6, 0x8f, 0x31, 0xdb, 0xc7, 0x98, 0xdd, 0xff, 0x62, 0x76, 0xef, 0x60, 0x66, 0x5f, 0x64, 0x66, 0xef, 0x81, 0xf4, 0xbd, 0x48, 0xdf, 0x8b, 0xf4, 0xbd, 0x48, 0xdf, 0x8b, 0xf4, 0xbd, 0x48, 0xdf, 0x8b, 0xf4, 0xbd, 0x48, 0xdf, 0x8b, 0xee, 0xbd, 0xe8, 0xde, + 0x8b, 0xee, 0xbd, 0xba, 0x40, 0x2f, 0xba, 0xf7, 0xa2, 0x7b, 0x2f, 0xba, 0xf7, 0xa2, 0x7b, 0x2f, 0xba, 0xf7, 0xa2, 0x7b, 0x2f, 0xba, 0xf7, 0xa2, 0x7b, 0x2f, 0xba, 0xf7, 0xa2, 0x7b, 0x2f, 0xba, 0xf7, 0xa2, 0x7b, 0x2f, 0x72, 0xf7, 0x22, 0x77, 0x2f, 0x72, 0xf7, 0xea, 0x20, 0x5b, 0x21, 0x52, 0x2f, 0x22, 0xf5, 0x22, 0x52, 0x2f, 0x22, 0xf5, 0x22, 0x52, 0x2f, 0x12, 0xad, 0x0a, 0xea, 0xaa, 0xbd, 0xf3, 0x3b, 0xfa, 0x66, 0x9b, 0xbe, 0x39, 0x4f, 0xcf, 0x4c, 0xfb, 0xee, 0xf1, 0xd1, 0x33, 0xd5, 0x4f, 0x1a, 0x7f, 0x2e, 0xc6, 0x48, 0x62, 0xd7, 0x45, 0x7d, 0xc1, 0x28, 0xdf, 0x2d, 0x7f, 0x1e, 0xd8, 0x2b, 0xbe, 0x93, 0xab, 0x7c, 0xf5, 0x57, 0x5f, 0xf5, 0xf8, 0x2a, 0x53, 0xf9, 0x6a, 0xc3, 0xe7, 0x5d, 0xe6, 0x83, 0x2d, 0x7c, 0xf5, 0x9b, 0xd8, 0x09, 0x51, 0x3e, 0x76, 0x92, 0x9c, 0xe2, + 0xbb, 0xa7, 0x55, 0x3e, 0xc1, 0xae, 0xcd, 0x4f, 0xdb, 0xf5, 0x9f, 0x36, 0xfd, 0xa7, 0x2d, 0xf6, 0x15, 0x5d, 0x7c, 0x92, 0x8e, 0x6d, 0x5f, 0xab, 0x9f, 0x25, 0x55, 0x68, 0x7c, 0xdd, 0xb3, 0x6b, 0x3d, 0x7b, 0x6d, 0xf5, 0x53, 0xf9, 0x0a, 0x1e, 0x91, 0x0b, 0x46, 0xfb, 0x4e, 0x7e, 0xe3, 0xa7, 0x69, 0x7e, 0xd1, 0xe3, 0xbf, 0x24, 0x5f, 0xf1, 0xd8, 0x06, 0x1c, 0xf8, 0x6d, 0xec, 0xd0, 0xe8, 0xd1, 0xd8, 0xc7, 0x65, 0x42, 0xf4, 0x84, 0x2d, 0xf5, 0x55, 0x3f, 0x2b, 0xaf, 0xfc, 0xaf, 0x84, 0xcf, 0x38, 0xae, 0xbe, 0xa0, 0xa6, 0xfa, 0x79, 0x6e, 0xcb, 0x2b, 0x8f, 0x7f, 0xdd, 0xe3, 0xbb, 0x3d, 0xbe, 0xdb, 0xe3, 0xcb, 0x9f, 0xf6, 0xf6, 0x62, 0xf5, 0x48, 0xe7, 0x7b, 0xc4, 0x8b, 0x9e, 0x93, 0x0f, 0xea, 0x3d, 0x2a, 0xf4, 0xa8, 0x5e, 0x8f, 0xea, 0xf5, 0xa8, 0xb5, 0x1e, 0xd1, 0xeb, 0x11, + 0x4b, 0xca, 0x9f, 0x09, 0x17, 0xbb, 0xd3, 0x2b, 0xc6, 0x9c, 0xab, 0x7c, 0xe5, 0x75, 0x2b, 0x9f, 0x01, 0x19, 0x34, 0x55, 0x3f, 0xe3, 0x6b, 0xc3, 0x5e, 0xf6, 0xd9, 0xcb, 0xac, 0xbd, 0xcc, 0xda, 0xcb, 0x6c, 0x4c, 0x17, 0x8e, 0xdd, 0xee, 0x58, 0xee, 0xf4, 0xb3, 0x29, 0x95, 0x67, 0xdf, 0xe6, 0xff, 0x8d, 0xb6, 0x72, 0x89, 0xad, 0x5c, 0x6a, 0x2b, 0x97, 0xda, 0xca, 0x0d, 0xce, 0xd8, 0x02, 0x67, 0x6c, 0x81, 0x33, 0xd6, 0xec, 0xf5, 0xff, 0x50, 0x5b, 0x7e, 0xff, 0xb4, 0xd7, 0x71, 0xaf, 0xbc, 0x3f, 0x3f, 0xf4, 0xc8, 0xa9, 0x1e, 0x59, 0xfe, 0x34, 0xbd, 0xdf, 0x6c, 0x18, 0x05, 0x8f, 0x2a, 0xff, 0xed, 0x67, 0x5f, 0x30, 0x3a, 0xa8, 0x71, 0x66, 0xc6, 0x47, 0xf7, 0xc5, 0x0e, 0x8e, 0xd2, 0xb1, 0x43, 0x64, 0x42, 0xf4, 0x3b, 0x6c, 0x4c, 0x62, 0x40, 0x8d, 0x33, 0x73, 0x50, 0xe5, 0xc8, + 0x2f, 0xf4, 0xd3, 0x47, 0xfc, 0xf4, 0x21, 0x3f, 0xbd, 0x2a, 0x76, 0x7c, 0xb0, 0x4d, 0xec, 0xb4, 0x60, 0xeb, 0xd8, 0xe9, 0xc1, 0xf6, 0xb1, 0x33, 0xdc, 0x3f, 0xd3, 0x1e, 0x9d, 0x13, 0xcd, 0x89, 0x9d, 0x17, 0xbd, 0x10, 0x3b, 0x5f, 0x2e, 0x08, 0xc6, 0xd5, 0xde, 0x15, 0x6c, 0x5e, 0x7b, 0x8f, 0xfc, 0x38, 0x78, 0x4f, 0xe3, 0x8a, 0x68, 0x6e, 0xd0, 0xe0, 0xd5, 0x5e, 0x08, 0xee, 0x0c, 0x36, 0x0f, 0xa6, 0x78, 0xc6, 0x78, 0xa3, 0x39, 0xa1, 0x32, 0xee, 0xe5, 0xb3, 0x50, 0xa8, 0x3b, 0x20, 0x68, 0xac, 0x3b, 0x30, 0x68, 0xf4, 0x5f, 0x4d, 0x34, 0xdf, 0xa3, 0xde, 0xeb, 0x51, 0xef, 0xf3, 0xa8, 0x36, 0x8f, 0xfa, 0x6b, 0x75, 0x44, 0xd7, 0xd9, 0x4e, 0xbd, 0x47, 0x6e, 0xe6, 0x91, 0x9b, 0xc5, 0x86, 0x75, 0xa9, 0x01, 0x5d, 0x6a, 0xa0, 0xf2, 0x5b, 0x05, 0xef, 0x8f, 0x0a, 0x7a, 0x62, 0x41, + 0x4f, 0xcc, 0x21, 0xd9, 0xba, 0x60, 0x6c, 0xb4, 0x56, 0x6f, 0x6c, 0xb7, 0x5e, 0xce, 0x58, 0x2f, 0x67, 0xac, 0x97, 0x33, 0xc1, 0x38, 0xdd, 0xe8, 0x23, 0x6e, 0x3f, 0x2a, 0xe5, 0xcf, 0xed, 0x3b, 0x40, 0x0e, 0x94, 0x83, 0x64, 0xbc, 0x1c, 0x2c, 0x87, 0xc8, 0xc7, 0xe5, 0x30, 0xf9, 0x84, 0xc7, 0x1f, 0x2e, 0x47, 0xc8, 0x91, 0x72, 0x94, 0x1c, 0x2d, 0xc7, 0xc8, 0xb1, 0xf2, 0x49, 0x99, 0x20, 0xc7, 0xc9, 0xa7, 0xe4, 0x78, 0x39, 0x41, 0x4e, 0x94, 0x93, 0xe4, 0x64, 0x99, 0x28, 0xa7, 0xc8, 0xa9, 0xa2, 0x12, 0x82, 0x4f, 0xcb, 0xe9, 0x62, 0x16, 0x07, 0x66, 0x71, 0x60, 0x16, 0x07, 0x66, 0x71, 0x60, 0x16, 0x07, 0x66, 0x71, 0x60, 0x16, 0x07, 0x66, 0x71, 0xf0, 0x05, 0xf9, 0x62, 0x94, 0xb0, 0x5e, 0xcf, 0x04, 0x66, 0x74, 0x60, 0x46, 0x07, 0x66, 0x74, 0x60, 0x46, 0x07, 0x66, 0x74, 0x60, + 0x46, 0x07, 0x66, 0x73, 0x60, 0x36, 0x07, 0x66, 0x73, 0xf0, 0x35, 0x35, 0x65, 0x36, 0x07, 0x66, 0x73, 0x70, 0x89, 0xfb, 0xdf, 0x90, 0x4b, 0xe5, 0x32, 0xcf, 0xbf, 0x5c, 0xae, 0x90, 0x2b, 0x7d, 0x7d, 0x55, 0xa5, 0x23, 0x67, 0x82, 0x6f, 0xba, 0x7f, 0x35, 0x3f, 0xba, 0x46, 0xae, 0x95, 0x6f, 0x89, 0x19, 0x1f, 0x7c, 0x5b, 0xae, 0x77, 0xfe, 0x6e, 0x90, 0xef, 0xc8, 0x77, 0xe5, 0x46, 0xb9, 0x49, 0xbe, 0x27, 0x37, 0xcb, 0xf7, 0x65, 0xb2, 0xdc, 0xe2, 0x35, 0x6e, 0x95, 0xdb, 0xa2, 0xe1, 0xe0, 0x07, 0xce, 0xf7, 0x0f, 0xdd, 0xde, 0x51, 0xf9, 0x34, 0xea, 0xf2, 0x67, 0xf0, 0xbd, 0x5a, 0x99, 0x83, 0x69, 0x5f, 0xaf, 0x8d, 0xf2, 0x18, 0xd5, 0x8e, 0x51, 0xed, 0x35, 0xa8, 0x50, 0x73, 0x45, 0x85, 0x06, 0x19, 0x34, 0xc8, 0xd4, 0x5c, 0xeb, 0x67, 0xba, 0x4d, 0xcd, 0xf5, 0x6e, 0x6d, 0xaf, + 0xc6, 0xf6, 0x6a, 0x6e, 0xf2, 0xfd, 0x5b, 0xa2, 0x04, 0x52, 0x64, 0x6a, 0x7e, 0x20, 0xd3, 0xa2, 0x14, 0x62, 0x64, 0x98, 0xc4, 0x70, 0xcd, 0x74, 0x8f, 0xfd, 0xa9, 0xfc, 0xcc, 0xd7, 0x0f, 0xba, 0xfd, 0xa5, 0xcc, 0x90, 0x5f, 0xc9, 0xaf, 0x65, 0xa6, 0x3c, 0x24, 0xbf, 0x91, 0xdf, 0xca, 0xc3, 0xf2, 0x3b, 0xaf, 0xf9, 0x88, 0x6a, 0x77, 0x2e, 0x6b, 0x27, 0x45, 0xbd, 0xb5, 0x77, 0x45, 0x7d, 0xb5, 0xf7, 0x44, 0xcb, 0x6a, 0x7f, 0x1c, 0xad, 0xa8, 0xdb, 0x06, 0x81, 0x9e, 0x15, 0xdd, 0xbf, 0x4e, 0xd7, 0xaf, 0x5b, 0x24, 0x8b, 0x45, 0x97, 0xaf, 0xd3, 0xe5, 0xeb, 0x74, 0x79, 0x94, 0xca, 0xa0, 0x54, 0xa6, 0x2e, 0x1b, 0xe5, 0x47, 0xef, 0x29, 0xef, 0x8f, 0x7a, 0x47, 0xef, 0x1d, 0xad, 0x43, 0xad, 0x0c, 0x6a, 0x0d, 0xa0, 0xd6, 0x00, 0x6a, 0x0d, 0xa0, 0xd6, 0x00, 0x6a, 0x0d, 0xa0, 0xd6, + 0x00, 0x6a, 0x0d, 0xa0, 0xd6, 0x00, 0x6a, 0x0d, 0xa0, 0xd6, 0x00, 0x6a, 0x0d, 0xa0, 0xd6, 0x00, 0x6a, 0x0d, 0xa0, 0xd6, 0x00, 0x6a, 0x0d, 0xa0, 0xd6, 0x00, 0x6a, 0x0d, 0xa0, 0xd6, 0x00, 0x6a, 0x0d, 0xa0, 0xd6, 0x00, 0x6a, 0x0d, 0xa0, 0xd6, 0x00, 0x6a, 0x0d, 0xa0, 0xd6, 0x00, 0x6a, 0x0d, 0xa0, 0xd6, 0x00, 0x6a, 0x0d, 0xa0, 0xd6, 0x40, 0xe5, 0x5f, 0xc0, 0xb2, 0x51, 0x1f, 0xd6, 0x17, 0xb0, 0xbe, 0x80, 0xf5, 0x05, 0xac, 0x2f, 0x60, 0x7d, 0x01, 0xeb, 0x0b, 0x58, 0x5f, 0xc0, 0xfa, 0x1c, 0xd6, 0xe7, 0x58, 0xcc, 0x3a, 0x16, 0xb3, 0x8e, 0xc5, 0xac, 0x63, 0x31, 0xeb, 0x58, 0xcc, 0x3a, 0x16, 0xb3, 0xae, 0xfe, 0xba, 0x68, 0x6d, 0xfd, 0xb7, 0xe5, 0xfa, 0xa8, 0x9d, 0x0b, 0xb4, 0x73, 0x81, 0x76, 0x2e, 0xd0, 0xce, 0x05, 0xda, 0xb9, 0x40, 0x3b, 0x17, 0x68, 0xe7, 0x02, 0xed, 0x5c, + 0xa0, 0xdd, 0x7a, 0x3f, 0x63, 0xbd, 0x9f, 0xb1, 0xde, 0xcf, 0x58, 0xef, 0x67, 0xac, 0xf7, 0x33, 0xd6, 0xfb, 0x19, 0xeb, 0xfd, 0x8c, 0xf5, 0x7e, 0xc6, 0x7a, 0x3f, 0x63, 0xad, 0x9f, 0xb1, 0xd6, 0xcf, 0x58, 0xeb, 0x67, 0xac, 0xf5, 0x33, 0xd6, 0xfa, 0x19, 0x6b, 0xfd, 0x8c, 0xb5, 0x7e, 0xc6, 0x5a, 0x3f, 0x63, 0xad, 0x9f, 0xb1, 0xd6, 0xcf, 0x58, 0xeb, 0x67, 0xac, 0xf5, 0x33, 0xd6, 0xfa, 0x19, 0x6b, 0xfd, 0x8c, 0xb5, 0x7e, 0xc6, 0x5a, 0x3f, 0x63, 0xad, 0x9f, 0xb1, 0xc6, 0xcf, 0xf0, 0xdd, 0x8c, 0x35, 0x7e, 0xc6, 0x1a, 0x3f, 0x63, 0x8d, 0x9f, 0xa9, 0x9f, 0x8d, 0x88, 0x73, 0xe4, 0xaf, 0x32, 0xd7, 0xd7, 0x7f, 0x93, 0xe7, 0x64, 0x9e, 0xbc, 0x20, 0xcd, 0xb2, 0x50, 0x5a, 0xe4, 0x45, 0x59, 0x2c, 0x71, 0x69, 0x95, 0x25, 0xd2, 0x26, 0x4b, 0xe5, 0x65, 0x69, 0x97, 0x0e, 0x79, 0x55, + 0x96, 0xcb, 0x6b, 0xf2, 0xba, 0xac, 0x90, 0x95, 0xd2, 0x25, 0xc6, 0x19, 0x9d, 0x33, 0xe8, 0x9c, 0x41, 0xe7, 0x0c, 0x3a, 0x67, 0xd0, 0x39, 0x83, 0xce, 0x19, 0x74, 0xce, 0xa0, 0x73, 0xa6, 0x3e, 0x6b, 0x5f, 0x72, 0x92, 0x97, 0x5e, 0x29, 0xc8, 0x3a, 0xe9, 0x13, 0xa4, 0xac, 0x47, 0xca, 0x7a, 0xa4, 0xac, 0x47, 0xca, 0x7a, 0xa4, 0xac, 0x67, 0x36, 0xf5, 0x45, 0x19, 0x96, 0x92, 0x8c, 0xc8, 0x9b, 0x12, 0x45, 0x89, 0x86, 0x40, 0x62, 0x52, 0x23, 0xa3, 0xa4, 0x56, 0xea, 0x64, 0x34, 0x77, 0x1f, 0x23, 0xf5, 0xd2, 0x20, 0x9b, 0xc9, 0xe6, 0xb2, 0x85, 0x6c, 0x89, 0xca, 0x5b, 0xc9, 0x7b, 0x65, 0x6b, 0x51, 0xbf, 0x0d, 0xdb, 0xca, 0xf6, 0xb2, 0x83, 0xec, 0x58, 0x36, 0x08, 0xd9, 0x59, 0x76, 0x91, 0x5d, 0x65, 0x37, 0xd9, 0x5d, 0xf6, 0x90, 0xbd, 0x64, 0x6f, 0xd9, 0x47, 0xf6, 0x95, 0x0f, + 0xcb, 0xfe, 0xc2, 0xf0, 0x1a, 0xd8, 0x5d, 0x03, 0xbb, 0x6b, 0x60, 0x77, 0x0d, 0xec, 0xae, 0x61, 0xbc, 0x20, 0x59, 0x03, 0x92, 0x35, 0xb0, 0xba, 0x06, 0xfd, 0xb0, 0x41, 0x3f, 0x6c, 0xd0, 0x0f, 0x1b, 0xf4, 0xc3, 0x06, 0xfd, 0xb0, 0x41, 0x3f, 0x6c, 0x38, 0x96, 0xc9, 0x7e, 0x52, 0x26, 0x08, 0xda, 0x36, 0xa0, 0x77, 0xc3, 0x89, 0x82, 0xe0, 0x0d, 0x27, 0xcb, 0x44, 0x39, 0x45, 0x4e, 0x15, 0x9c, 0x6b, 0xd0, 0xcd, 0x59, 0x4c, 0x86, 0xc5, 0x64, 0x58, 0x4c, 0x86, 0xc5, 0x64, 0x58, 0x4c, 0x86, 0xc5, 0x64, 0x58, 0x4c, 0x86, 0xc5, 0x64, 0x58, 0x4c, 0x86, 0xc5, 0x64, 0x58, 0x4c, 0xa6, 0xb2, 0x96, 0x39, 0x5f, 0x2e, 0x90, 0x0b, 0xe5, 0x2b, 0xf2, 0x55, 0xb9, 0x48, 0xbe, 0x26, 0x17, 0xcb, 0xd7, 0xe5, 0x12, 0xf9, 0x86, 0x5c, 0x2a, 0xd7, 0xca, 0xb7, 0xe4, 0x3a, 0xd1, 0x5b, 0x1a, 0x6e, + 0x90, 0x9b, 0xe4, 0x7b, 0x72, 0xb3, 0x7c, 0x5f, 0x26, 0x8b, 0x1e, 0xd6, 0xa0, 0x87, 0x35, 0xdc, 0x2e, 0x3f, 0x90, 0x1f, 0x46, 0xa9, 0x86, 0x3b, 0xe4, 0x4e, 0x99, 0x22, 0x7a, 0x4f, 0xc3, 0x5d, 0x72, 0xb7, 0xdc, 0x23, 0x3f, 0x92, 0x7b, 0xad, 0xc9, 0xee, 0x93, 0xe9, 0xf2, 0x53, 0xf9, 0x99, 0xfc, 0x5c, 0xee, 0x97, 0x07, 0xe4, 0x17, 0xf2, 0xa0, 0xfc, 0x52, 0x66, 0xc8, 0xaf, 0xe4, 0xd7, 0x32, 0x53, 0x1e, 0x92, 0xdf, 0xc8, 0x6f, 0xe5, 0x61, 0xd1, 0x9b, 0x1a, 0xf4, 0xa6, 0x86, 0x47, 0x65, 0x96, 0xfc, 0x5e, 0xfe, 0x20, 0x8f, 0xc9, 0xe3, 0xf2, 0x47, 0xf9, 0x57, 0xf9, 0x93, 0x3c, 0x21, 0x7f, 0x96, 0x27, 0xe5, 0x29, 0x79, 0x5a, 0x9e, 0x91, 0xbf, 0xc8, 0x6c, 0x99, 0x23, 0x7f, 0x75, 0x0c, 0xcf, 0xca, 0x5c, 0xf9, 0x9b, 0x3c, 0x27, 0xf3, 0x64, 0xbe, 0x3c, 0x2f, 0x2f, 0x48, 0xb3, + 0x2c, 0x94, 0x16, 0x79, 0x51, 0x5e, 0x92, 0x45, 0xb2, 0x58, 0xe2, 0xd2, 0x2a, 0x4b, 0xa4, 0x4d, 0x5e, 0x96, 0x76, 0xe9, 0x88, 0x86, 0xad, 0x58, 0x86, 0x1b, 0x56, 0x45, 0x6b, 0x1b, 0x56, 0xcb, 0x1a, 0xf7, 0xbb, 0x25, 0x29, 0x29, 0xe9, 0x91, 0x8c, 0x64, 0x25, 0x94, 0x9c, 0xe4, 0xa5, 0xcf, 0xf9, 0xef, 0x97, 0x01, 0x19, 0x94, 0x37, 0x64, 0x7d, 0x94, 0xa9, 0xbc, 0x87, 0xaa, 0xef, 0x33, 0xcb, 0x0c, 0xb3, 0xcc, 0x30, 0xcb, 0x4c, 0x63, 0xf9, 0xfb, 0x43, 0xbe, 0x5f, 0x94, 0x61, 0x29, 0xc9, 0x48, 0x94, 0x67, 0x9b, 0x99, 0x58, 0x07, 0xc2, 0x87, 0x08, 0x1f, 0x56, 0x56, 0x3d, 0x1f, 0x74, 0xfb, 0xa1, 0x68, 0x11, 0xba, 0x87, 0xe8, 0x1e, 0xa2, 0x7b, 0x88, 0xee, 0x45, 0x74, 0x4f, 0xa3, 0x7b, 0x1a, 0xdd, 0xd3, 0xe8, 0x9e, 0x46, 0xf7, 0x34, 0xba, 0xa7, 0xd1, 0x7d, 0x2d, 0xba, 0xaf, + 0x45, 0xf7, 0xb5, 0xe8, 0x9e, 0x46, 0xf7, 0xf4, 0xff, 0x82, 0x4f, 0xe0, 0xe8, 0x46, 0xf7, 0x34, 0xba, 0x17, 0xd1, 0xbd, 0x88, 0xee, 0x45, 0x74, 0x2f, 0xa2, 0x7b, 0x11, 0xdd, 0x8b, 0xe8, 0x5e, 0x44, 0xf7, 0x22, 0xba, 0x17, 0xd1, 0xbd, 0x88, 0xee, 0x45, 0x74, 0x2f, 0xa2, 0x7b, 0x11, 0xdd, 0x8b, 0xe8, 0x5e, 0x44, 0xf7, 0x34, 0xba, 0xa7, 0xd1, 0x3d, 0x8d, 0xee, 0x45, 0x74, 0x2f, 0xa2, 0xfb, 0x5a, 0x74, 0x2f, 0xa2, 0x7b, 0x16, 0xdd, 0xb3, 0xe8, 0x9e, 0x45, 0xf7, 0x2c, 0xba, 0x17, 0xab, 0x9f, 0xda, 0x91, 0x41, 0xf7, 0x0c, 0xba, 0x67, 0xd0, 0x3d, 0x83, 0xee, 0x19, 0x74, 0xcf, 0xa0, 0x7b, 0x06, 0xdd, 0x33, 0xe8, 0x9e, 0x41, 0xf7, 0x0c, 0xba, 0x17, 0xd0, 0xbd, 0x80, 0xee, 0xbd, 0xc1, 0xed, 0x9e, 0x5b, 0xb6, 0xd6, 0x3b, 0x2a, 0x44, 0x4f, 0x23, 0x7a, 0x01, 0xd1, 0x17, 0x21, + 0xfa, 0x22, 0x44, 0x2f, 0x22, 0x7a, 0x11, 0xd1, 0xd3, 0x88, 0xbe, 0x16, 0xd1, 0x8b, 0x88, 0x5e, 0x44, 0xf4, 0x22, 0xa2, 0x67, 0x10, 0x3d, 0x83, 0xe8, 0x05, 0x44, 0xef, 0x46, 0xf4, 0xb5, 0x88, 0x5e, 0x40, 0xf4, 0x2c, 0xa2, 0x97, 0xdf, 0xed, 0x34, 0x8f, 0xe8, 0x45, 0x44, 0x2f, 0x22, 0x7a, 0xe1, 0x9f, 0xf8, 0xf4, 0x89, 0x0c, 0xa2, 0x67, 0x10, 0x3d, 0x8d, 0xe8, 0x45, 0x14, 0x2f, 0xfe, 0xdb, 0x27, 0x2a, 0xb8, 0xff, 0x4e, 0x9f, 0xa8, 0xb0, 0x2a, 0x4a, 0xa3, 0xf8, 0x5a, 0x14, 0x2f, 0xa0, 0x78, 0x11, 0xc5, 0x8b, 0x28, 0x9e, 0x41, 0xf1, 0x34, 0x9a, 0x86, 0x68, 0x1a, 0xa2, 0x69, 0x88, 0xa6, 0x21, 0x9a, 0x86, 0x68, 0x1a, 0xa2, 0x69, 0xb8, 0x71, 0xe5, 0x7c, 0xa5, 0xfb, 0x57, 0xc9, 0x24, 0xf9, 0xa6, 0x5c, 0x2d, 0xd7, 0xc8, 0xf5, 0xd1, 0x22, 0x14, 0x5d, 0x84, 0xa2, 0x8b, 0x50, + 0x74, 0x11, 0x8a, 0x2e, 0x42, 0xd1, 0x45, 0x28, 0xba, 0x08, 0x45, 0x17, 0xa1, 0xe8, 0x22, 0x14, 0x0d, 0x51, 0x34, 0x44, 0xd1, 0x10, 0x45, 0x43, 0x14, 0x0d, 0x51, 0x34, 0x44, 0xd1, 0x10, 0x45, 0x43, 0x14, 0x0d, 0x51, 0x34, 0x44, 0xd1, 0x10, 0x45, 0x43, 0x14, 0x0d, 0x51, 0x34, 0x44, 0xd1, 0x10, 0x45, 0x43, 0x14, 0x0d, 0x51, 0x34, 0x44, 0xd1, 0x10, 0x45, 0x43, 0x14, 0x0d, 0x51, 0x34, 0x44, 0xd1, 0x10, 0x45, 0x43, 0x14, 0x0d, 0x51, 0x34, 0x44, 0xd1, 0x10, 0x45, 0x43, 0x14, 0x0d, 0x51, 0x34, 0x44, 0xd1, 0x10, 0x45, 0x8b, 0x28, 0x5a, 0x44, 0xd1, 0x22, 0x8a, 0xa6, 0x51, 0x34, 0x8d, 0xa2, 0x69, 0x14, 0x4d, 0xa3, 0x68, 0x1a, 0x45, 0xd7, 0xa2, 0xe8, 0x5a, 0x14, 0x5d, 0x8b, 0xa2, 0x6b, 0x51, 0x74, 0x2d, 0x8a, 0xa6, 0x51, 0x34, 0x8d, 0xa2, 0x69, 0x14, 0x4d, 0xa3, 0x68, 0x1a, + 0x45, 0xd3, 0x28, 0x9a, 0x46, 0xd1, 0x34, 0x8a, 0xa6, 0x51, 0x34, 0x8d, 0xa2, 0x69, 0x14, 0x4d, 0xa3, 0x68, 0x1a, 0x45, 0xd3, 0x28, 0x9a, 0x46, 0xd1, 0x34, 0x8a, 0xa6, 0x51, 0x34, 0x8d, 0xa2, 0x69, 0x14, 0x4d, 0xa3, 0x68, 0x1a, 0x45, 0xd3, 0x28, 0x9a, 0x46, 0xd1, 0xf4, 0x7f, 0xe2, 0xbb, 0xd7, 0x77, 0xa3, 0x68, 0x37, 0x8a, 0x76, 0xa3, 0x68, 0x37, 0x8a, 0x76, 0xa3, 0x68, 0x37, 0x8a, 0x76, 0xa3, 0x68, 0x1a, 0x45, 0xd3, 0x28, 0x9a, 0x46, 0xd1, 0x34, 0x8a, 0xa6, 0x51, 0x34, 0x8d, 0xa2, 0x69, 0x14, 0x2d, 0xa2, 0x68, 0x11, 0x45, 0x8b, 0x28, 0x5a, 0x44, 0xd1, 0x22, 0x8a, 0x16, 0x51, 0xb4, 0x88, 0xa2, 0x45, 0x14, 0x2d, 0xa2, 0x68, 0x11, 0x45, 0x8b, 0x28, 0x5a, 0x44, 0xd1, 0x22, 0x8a, 0x16, 0x51, 0xb4, 0x88, 0xa2, 0x45, 0x14, 0x2d, 0xa2, 0x68, 0x11, 0x45, 0x8b, 0x28, 0x5a, + 0x44, 0xd1, 0x22, 0x8a, 0x16, 0x51, 0xb4, 0x88, 0xa2, 0x45, 0x14, 0x2d, 0xa2, 0x68, 0x11, 0x45, 0x8b, 0x28, 0x5a, 0x44, 0xd1, 0x22, 0x8a, 0x16, 0x51, 0xb4, 0x88, 0xa2, 0x45, 0x14, 0x2d, 0xa2, 0x68, 0x11, 0x45, 0x8b, 0x28, 0x5a, 0x44, 0xd1, 0x22, 0x8a, 0x16, 0x51, 0xb4, 0x88, 0xa2, 0x45, 0x14, 0x2d, 0xa2, 0x68, 0x11, 0x45, 0x8b, 0x28, 0x5a, 0x44, 0xd1, 0x22, 0x8a, 0x16, 0x51, 0xb4, 0x88, 0xa2, 0x45, 0x14, 0x2d, 0xa2, 0x68, 0x11, 0x45, 0xd3, 0x28, 0x9a, 0x46, 0xd1, 0x34, 0x8a, 0xa6, 0x51, 0x34, 0x8d, 0xa2, 0x69, 0x14, 0x4d, 0xa3, 0x68, 0x1a, 0x45, 0xd3, 0x28, 0x9a, 0x46, 0xd1, 0x34, 0x8a, 0xa6, 0x51, 0x34, 0x8d, 0xa2, 0x69, 0x14, 0x4d, 0xa3, 0x68, 0x1a, 0x45, 0xd3, 0x28, 0x9a, 0x46, 0xd1, 0x34, 0x8a, 0xa6, 0x51, 0x34, 0x8d, 0xa2, 0x69, 0x14, 0x4d, 0xa3, 0x68, 0x1a, + 0x45, 0xd3, 0x28, 0x9a, 0x46, 0xd1, 0x34, 0x8a, 0x16, 0x51, 0xb4, 0x88, 0xa2, 0x45, 0x14, 0x2d, 0xa2, 0x68, 0x11, 0x45, 0x8b, 0x28, 0x5a, 0x44, 0xd1, 0x22, 0x8a, 0x16, 0x51, 0xb4, 0x88, 0xa2, 0x45, 0x14, 0xcd, 0xa2, 0x68, 0x16, 0x45, 0xb3, 0x28, 0x9a, 0x45, 0xd1, 0x2c, 0x8a, 0x66, 0x51, 0x34, 0x8b, 0xa2, 0x59, 0x14, 0xcd, 0xa2, 0x68, 0x06, 0x45, 0x33, 0x28, 0x9a, 0x41, 0xd1, 0x0c, 0x8a, 0x66, 0x50, 0x34, 0x83, 0xa2, 0x19, 0x14, 0xcd, 0xa0, 0x68, 0x06, 0x45, 0x33, 0x28, 0x9a, 0x41, 0xd1, 0x0c, 0x8a, 0x66, 0x50, 0x34, 0x83, 0xa2, 0x19, 0x14, 0xcd, 0xa0, 0x68, 0x06, 0x45, 0x33, 0x28, 0x9a, 0x41, 0xd1, 0x0c, 0x8a, 0x66, 0x50, 0x34, 0x83, 0xa2, 0x19, 0x14, 0xcd, 0xa0, 0x68, 0x06, 0x45, 0x33, 0x28, 0x9a, 0x41, 0xd1, 0x0c, 0x8a, 0x66, 0x50, 0x34, 0x83, 0xa2, 0x19, 0x14, + 0xcd, 0xa0, 0x68, 0x06, 0x45, 0x33, 0x28, 0x9a, 0x41, 0xd1, 0x0c, 0x8a, 0x66, 0x50, 0x34, 0x83, 0xa2, 0x19, 0x14, 0x2d, 0xa0, 0x68, 0x01, 0x45, 0x0b, 0x28, 0x5a, 0x40, 0xd1, 0x02, 0x8a, 0x16, 0x50, 0xb4, 0x80, 0xa2, 0x05, 0x14, 0x2d, 0xa0, 0x68, 0x01, 0x45, 0x0b, 0x28, 0x5a, 0x40, 0xd1, 0x02, 0x8a, 0x16, 0x50, 0xb4, 0x80, 0xa2, 0x05, 0x14, 0x2d, 0xa0, 0x68, 0x01, 0x45, 0x0b, 0x28, 0x5a, 0x40, 0xd1, 0x02, 0x8a, 0x16, 0x50, 0xb4, 0x17, 0x45, 0x7b, 0x1b, 0x3a, 0x9d, 0xa3, 0x57, 0x65, 0xb9, 0xbc, 0x26, 0xaf, 0xcb, 0x0a, 0x59, 0x29, 0x6b, 0xd0, 0xb6, 0x5b, 0x92, 0x92, 0x92, 0x1e, 0xc9, 0x88, 0xd5, 0x07, 0xb2, 0xe6, 0x91, 0x35, 0x8f, 0xac, 0x79, 0x64, 0x2d, 0x20, 0x6b, 0x01, 0x59, 0x0b, 0xc8, 0x5a, 0x40, 0xd6, 0x02, 0xb2, 0x16, 0x90, 0xb5, 0x88, 0xac, 0x69, 0x64, 0x4d, + 0x23, 0x6b, 0x1a, 0x59, 0xd3, 0xc8, 0x9a, 0x46, 0xd6, 0x02, 0xb2, 0x16, 0x90, 0xb5, 0x80, 0xac, 0x05, 0x64, 0x2d, 0x20, 0xeb, 0xda, 0x9a, 0x46, 0xeb, 0xe8, 0xa4, 0x75, 0xb4, 0xf5, 0x7e, 0xf4, 0x2a, 0xca, 0xf6, 0xa3, 0x6c, 0x3f, 0xca, 0x66, 0xab, 0xeb, 0xe8, 0x04, 0xd2, 0x2e, 0x0d, 0x54, 0x39, 0xda, 0xae, 0x45, 0xdb, 0xb5, 0x68, 0xbb, 0x16, 0x6d, 0xf3, 0x68, 0x1b, 0xa2, 0x6d, 0x18, 0x94, 0x3f, 0x63, 0xfd, 0x00, 0x39, 0x50, 0x0e, 0x92, 0xf1, 0x72, 0xb0, 0x1c, 0x22, 0x1f, 0x97, 0xc3, 0xe4, 0x13, 0xd6, 0xe8, 0x87, 0xcb, 0x11, 0x72, 0xa4, 0x1c, 0x25, 0x47, 0xcb, 0x31, 0x72, 0xac, 0x7c, 0x52, 0x26, 0xc8, 0x71, 0xf2, 0x29, 0x39, 0x5e, 0x4e, 0x90, 0x13, 0xe5, 0x24, 0x39, 0x59, 0x26, 0xca, 0x29, 0x72, 0xaa, 0x9c, 0x26, 0x9f, 0x96, 0xd3, 0xe5, 0x33, 0x72, 0x86, 0x9c, 0x29, + 0x9f, 0x95, 0xcf, 0xc9, 0x59, 0x72, 0xb6, 0x7c, 0x5e, 0xbe, 0x20, 0x5f, 0x8c, 0x56, 0xa1, 0x6d, 0x0e, 0x6d, 0xf3, 0x68, 0x9b, 0x47, 0xdb, 0x3c, 0xda, 0xe6, 0xd0, 0x36, 0x87, 0xb6, 0x39, 0xb4, 0xcd, 0xa1, 0x6d, 0x0e, 0x6d, 0xf3, 0x68, 0x9b, 0x43, 0xdb, 0x3c, 0xda, 0xe6, 0xd1, 0x36, 0x87, 0xb6, 0x39, 0xb4, 0xcd, 0xa1, 0x6d, 0x88, 0xb6, 0x21, 0xda, 0x86, 0x68, 0x9b, 0x43, 0xdb, 0x3c, 0xda, 0x86, 0x68, 0x9b, 0x7b, 0x87, 0xb5, 0x74, 0x1e, 0x6d, 0xfb, 0xd0, 0x76, 0x18, 0x6d, 0x87, 0xd1, 0x76, 0x18, 0x6d, 0x87, 0xd1, 0x76, 0x18, 0x6d, 0x87, 0xd1, 0x76, 0x18, 0x6d, 0x87, 0xd1, 0x76, 0x18, 0x6d, 0x87, 0xd1, 0x36, 0x8b, 0xb6, 0x59, 0xb4, 0x2d, 0x59, 0x4b, 0xa7, 0xd1, 0xb6, 0x84, 0xb6, 0x59, 0x6b, 0xe9, 0xcc, 0xc6, 0xab, 0x49, 0xd6, 0x88, 0x31, 0x6b, 0xc4, 0xea, 0x75, 0xaa, + 0x0d, 0xeb, 0xeb, 0x10, 0x8d, 0xb3, 0x68, 0xbc, 0xb4, 0xe6, 0xf3, 0x28, 0xca, 0x1c, 0x50, 0x79, 0x69, 0x0d, 0x4b, 0x40, 0xe6, 0x3c, 0x32, 0xe7, 0x91, 0x39, 0x44, 0xe6, 0x10, 0x99, 0x73, 0xc8, 0x9c, 0x47, 0xe6, 0x1c, 0x32, 0x0f, 0x23, 0xf3, 0x30, 0x32, 0x67, 0x91, 0xb9, 0x7c, 0x55, 0x36, 0x44, 0xe6, 0x6c, 0x75, 0xad, 0x5d, 0xbe, 0x6a, 0x5f, 0x42, 0xe6, 0x3c, 0x32, 0xe7, 0x91, 0x39, 0x8b, 0xcc, 0x7d, 0xc8, 0xdc, 0x87, 0xcc, 0x7d, 0xc8, 0xdc, 0x87, 0xcc, 0x7d, 0xc8, 0xdc, 0x87, 0xcc, 0x7d, 0xc8, 0xdc, 0x87, 0xcc, 0x7d, 0xc8, 0xdc, 0x87, 0xcc, 0xc3, 0xc8, 0x3c, 0x8c, 0xcc, 0x39, 0x64, 0xce, 0x5b, 0x6b, 0xaf, 0xab, 0xae, 0xb5, 0x0b, 0x28, 0x9d, 0x47, 0xe9, 0x3e, 0x94, 0xee, 0x43, 0xe9, 0x1c, 0x4a, 0xf7, 0xa1, 0x74, 0x1f, 0x4a, 0xf7, 0xa1, 0x74, 0x1f, 0x4a, 0xf7, 0xa1, + 0x74, 0x88, 0xd2, 0x21, 0x4a, 0x97, 0x3f, 0xd7, 0x28, 0x87, 0xd2, 0x79, 0x94, 0x1e, 0x46, 0xe9, 0xd0, 0x5a, 0x3b, 0x69, 0xad, 0x9d, 0xb4, 0xd6, 0x4e, 0x5a, 0x6b, 0x27, 0xad, 0xb5, 0x93, 0xd6, 0xda, 0x49, 0x6b, 0xed, 0xa4, 0xb5, 0x76, 0xd2, 0x5a, 0x3b, 0x69, 0xad, 0x9d, 0xb4, 0xd6, 0x4e, 0x5a, 0x6b, 0x27, 0xad, 0xb5, 0x93, 0xd6, 0xda, 0x49, 0x6b, 0xed, 0xa4, 0xb5, 0x76, 0xd2, 0x5a, 0x3b, 0x69, 0xad, 0x9d, 0xb4, 0xd6, 0x4e, 0x5a, 0x6b, 0x27, 0xad, 0xb5, 0x93, 0xd6, 0xda, 0x49, 0x6b, 0xed, 0xa4, 0xb5, 0x76, 0xd2, 0x5a, 0x3b, 0x69, 0xad, 0x9d, 0xb4, 0xd6, 0x4e, 0x5a, 0x6b, 0x27, 0xad, 0xb5, 0x5f, 0xb5, 0xd6, 0x7e, 0x95, 0x1d, 0xf4, 0xb3, 0x83, 0x7e, 0x76, 0xd0, 0xcf, 0x0e, 0xfa, 0xd9, 0x41, 0x3f, 0x3b, 0xe8, 0x67, 0x07, 0xfd, 0xec, 0x20, 0xcb, 0x0e, 0xb2, 0xff, 0xce, + 0x5a, 0x3b, 0x61, 0xad, 0x9d, 0x60, 0x09, 0x4b, 0x59, 0xc2, 0x52, 0x96, 0xb0, 0x94, 0x25, 0x2c, 0x65, 0x09, 0x4b, 0x59, 0xc2, 0x52, 0x96, 0xb0, 0x94, 0x25, 0x2c, 0x65, 0x09, 0x4b, 0xeb, 0xef, 0x42, 0xe8, 0xbb, 0xe5, 0x1e, 0xf9, 0xb1, 0xfc, 0x44, 0xee, 0x95, 0xfb, 0x10, 0x79, 0xba, 0xfc, 0x54, 0x7e, 0x26, 0x3f, 0x97, 0xfb, 0xe5, 0x01, 0xf9, 0x85, 0x3c, 0x28, 0x33, 0xe4, 0x57, 0xf2, 0x6b, 0x99, 0x29, 0x0f, 0xc9, 0x6f, 0xe4, 0xb7, 0xf2, 0xb0, 0xfc, 0x4e, 0x1e, 0x91, 0x47, 0x65, 0x96, 0xfc, 0x5e, 0xfe, 0x20, 0x8f, 0xc9, 0xe3, 0xf2, 0x47, 0xf9, 0xb3, 0x3c, 0x29, 0x4f, 0xc9, 0xd3, 0xf2, 0x8c, 0xcc, 0x8e, 0xf2, 0xec, 0x21, 0xcf, 0x1e, 0xf2, 0xec, 0x21, 0x64, 0x0f, 0x21, 0x7b, 0x08, 0xd9, 0x43, 0xc8, 0x1e, 0x42, 0xf6, 0x10, 0xb2, 0x87, 0x90, 0x3d, 0x84, 0xec, 0x21, 0x64, + 0x0f, 0x21, 0x7b, 0x08, 0xd9, 0x43, 0xc8, 0x1e, 0x42, 0xf6, 0x10, 0xb2, 0x87, 0x90, 0x3d, 0x84, 0xec, 0x21, 0x64, 0x0f, 0x21, 0x7b, 0x08, 0xd9, 0x43, 0xc8, 0x1e, 0x42, 0xf6, 0x10, 0xb2, 0x87, 0x90, 0x3d, 0x84, 0xec, 0x21, 0x64, 0x0f, 0x21, 0x7b, 0x08, 0xd9, 0x43, 0xc8, 0x1e, 0x42, 0xf6, 0x10, 0xb2, 0x87, 0x90, 0x3d, 0x84, 0xec, 0x21, 0xac, 0x2f, 0x7f, 0x76, 0x60, 0xf9, 0xfa, 0x47, 0x4e, 0xf2, 0xd2, 0x2b, 0x05, 0x59, 0x27, 0x7d, 0xd2, 0x2f, 0x03, 0x32, 0x28, 0x6f, 0xc8, 0x7a, 0x19, 0x92, 0xa2, 0x0c, 0x4b, 0x49, 0x46, 0xe4, 0x4d, 0x89, 0xa2, 0x55, 0xec, 0x61, 0x15, 0x7b, 0x58, 0xc5, 0x1e, 0x56, 0xb1, 0x87, 0x55, 0xec, 0x61, 0x15, 0x7b, 0x58, 0xc5, 0x1e, 0x72, 0xec, 0x21, 0xc7, 0x1e, 0x72, 0xec, 0x21, 0xc7, 0x1e, 0x72, 0xec, 0x21, 0xc7, 0x1e, 0x72, 0xec, 0x21, 0xcf, + 0x1e, 0xf2, 0xec, 0x21, 0xcf, 0x1e, 0xf2, 0xec, 0x21, 0xcf, 0x1e, 0xf2, 0xec, 0x21, 0xc7, 0x1e, 0x72, 0xec, 0x21, 0xc7, 0x1e, 0x72, 0xec, 0x21, 0xc7, 0x1e, 0x72, 0xec, 0x21, 0xc7, 0x1e, 0x72, 0xec, 0x21, 0xc7, 0x1e, 0x72, 0xec, 0x21, 0xc7, 0x1e, 0x72, 0xec, 0x21, 0xc7, 0x1e, 0x72, 0xec, 0x21, 0xc7, 0x1e, 0x72, 0xec, 0x21, 0xc7, 0x1e, 0xf2, 0xec, 0x21, 0xcf, 0x1e, 0xf2, 0xec, 0x21, 0xcf, 0x1e, 0xf2, 0xec, 0x21, 0xcf, 0x1e, 0xf2, 0xec, 0x21, 0xcf, 0x1e, 0xf2, 0xec, 0x21, 0xcf, 0x1e, 0xf2, 0xec, 0x21, 0xcf, 0x1e, 0xf2, 0xec, 0x21, 0xcf, 0x1e, 0x72, 0xec, 0x21, 0xc7, 0x1e, 0x72, 0xec, 0x21, 0xc7, 0x1e, 0x72, 0xec, 0x21, 0xc7, 0x1e, 0x72, 0xec, 0x21, 0xc7, 0x1e, 0x72, 0xec, 0x21, 0xc7, 0x1e, 0x72, 0xec, 0x21, 0xc7, 0x1e, 0x72, 0xec, 0x21, 0x64, 0x0f, 0x21, 0x7b, 0x08, + 0xd9, 0x43, 0xc8, 0x1e, 0x42, 0xf6, 0x10, 0xb2, 0x87, 0x90, 0x3d, 0x84, 0xec, 0x21, 0x64, 0x0f, 0x21, 0x7b, 0x08, 0xd9, 0x43, 0xc8, 0x1e, 0x42, 0xf6, 0x10, 0xb2, 0x87, 0x90, 0x3d, 0x84, 0xec, 0x21, 0x64, 0x0f, 0x21, 0x7b, 0x08, 0xd9, 0x43, 0xc8, 0x1e, 0x42, 0xf6, 0x10, 0xb2, 0x87, 0x90, 0x3d, 0x84, 0xec, 0x21, 0x64, 0x0f, 0x21, 0x7b, 0x08, 0xd9, 0x43, 0x8e, 0x3d, 0xe4, 0xd8, 0x43, 0x8e, 0x3d, 0xe4, 0xd8, 0x43, 0x8e, 0x3d, 0xe4, 0xd8, 0x43, 0x8e, 0x3d, 0xe4, 0xd8, 0x43, 0x8e, 0x3d, 0xe4, 0xd8, 0x43, 0xee, 0x1f, 0x5c, 0x83, 0x0f, 0xb3, 0x87, 0x61, 0xf6, 0x30, 0xcc, 0x1e, 0x86, 0xd9, 0xc3, 0x30, 0x7b, 0x18, 0x66, 0x0f, 0xc3, 0xec, 0x61, 0x98, 0x3d, 0x0c, 0xb3, 0x87, 0x61, 0xf6, 0x30, 0xcc, 0x1e, 0x86, 0xd9, 0xc3, 0x30, 0x7b, 0x18, 0x66, 0x0f, 0xc3, 0xec, 0x61, 0x98, + 0x3d, 0x0c, 0xb3, 0x87, 0x61, 0xf6, 0x30, 0xcc, 0x1e, 0x86, 0xd9, 0xc3, 0x30, 0x7b, 0x18, 0x66, 0x0f, 0xc3, 0xec, 0x61, 0x98, 0x3d, 0x0c, 0xb3, 0x87, 0x61, 0xf6, 0x30, 0xcc, 0x1e, 0x86, 0xd9, 0xc3, 0x30, 0x7b, 0x18, 0x66, 0x0f, 0xc3, 0xec, 0x61, 0x98, 0x3d, 0x0c, 0xb3, 0x87, 0x61, 0xf6, 0x30, 0xcc, 0x1e, 0x86, 0xd9, 0xc3, 0x30, 0x7b, 0x18, 0x66, 0x0f, 0xc3, 0xec, 0x21, 0xcb, 0x1e, 0xb2, 0xec, 0x21, 0xcb, 0x1e, 0xb2, 0xec, 0x21, 0xcb, 0x1e, 0xb2, 0xec, 0x21, 0xcb, 0x1e, 0xb2, 0xec, 0x21, 0xcb, 0x1e, 0xb2, 0xec, 0x21, 0xcb, 0x1e, 0xb2, 0xec, 0x21, 0xcb, 0x1e, 0xb2, 0xec, 0x21, 0xcb, 0x1e, 0xb2, 0xec, 0x21, 0xcb, 0x1e, 0xb2, 0xec, 0x21, 0xcb, 0x1e, 0xb2, 0xec, 0x21, 0xcb, 0x1e, 0xb2, 0xec, 0xa1, 0xc4, 0x1e, 0x4a, 0xd6, 0xe0, 0x69, 0x6b, 0xf0, 0x34, 0x53, 0x28, 0x31, + 0x85, 0x12, 0x53, 0x28, 0x31, 0x85, 0x12, 0x53, 0x28, 0x31, 0x85, 0x12, 0x53, 0x28, 0x31, 0x85, 0x12, 0x53, 0x28, 0x31, 0x85, 0x12, 0x53, 0xc8, 0x32, 0x85, 0x2c, 0x53, 0xc8, 0x32, 0x85, 0x2c, 0x53, 0xc8, 0x32, 0x85, 0x2c, 0x53, 0xc8, 0x31, 0x85, 0x90, 0x29, 0x84, 0x4c, 0x21, 0x64, 0x0a, 0x21, 0x53, 0x08, 0x99, 0x42, 0x96, 0x29, 0x64, 0x99, 0x42, 0x96, 0x29, 0x64, 0x99, 0x42, 0x96, 0x29, 0x84, 0xb1, 0xf9, 0x0c, 0x20, 0xcb, 0x00, 0xb2, 0x0c, 0x20, 0xcb, 0x00, 0xc2, 0x7f, 0xe7, 0x6a, 0x7a, 0x96, 0x01, 0x64, 0x19, 0x40, 0x76, 0x93, 0xab, 0xe9, 0x71, 0x06, 0x10, 0x67, 0x00, 0x71, 0x06, 0x10, 0x67, 0x00, 0x71, 0x06, 0x10, 0x67, 0x00, 0x71, 0x06, 0x10, 0x67, 0x00, 0x71, 0x06, 0x10, 0x67, 0x00, 0x71, 0x06, 0x10, 0x67, 0x00, 0x71, 0x06, 0x10, 0x67, 0x00, 0x71, 0x06, 0x10, + 0x67, 0x00, 0x71, 0x06, 0x10, 0x67, 0x00, 0x71, 0x06, 0x10, 0x67, 0x00, 0x71, 0x06, 0x10, 0x67, 0x00, 0x71, 0x06, 0x10, 0x67, 0x00, 0x71, 0x06, 0x10, 0x67, 0x00, 0x71, 0x06, 0x10, 0x67, 0x00, 0x71, 0x06, 0x10, 0x67, 0x00, 0x71, 0x06, 0x10, 0xaf, 0x1a, 0x40, 0x96, 0x01, 0x84, 0x0c, 0x20, 0x64, 0x00, 0x21, 0x03, 0xc8, 0x32, 0x80, 0x2c, 0x03, 0xc8, 0x32, 0x80, 0x2c, 0x03, 0xc8, 0x32, 0x80, 0x90, 0x01, 0x84, 0x0c, 0x20, 0x64, 0x00, 0x21, 0x03, 0x08, 0x19, 0x40, 0xc8, 0x00, 0xc2, 0xb7, 0x5d, 0x4d, 0x0f, 0x19, 0x40, 0xc8, 0x00, 0xb2, 0x0c, 0x20, 0x64, 0x00, 0x69, 0x06, 0x90, 0x66, 0x00, 0x69, 0x06, 0x90, 0x66, 0x00, 0x21, 0x03, 0x88, 0x33, 0x80, 0x76, 0x06, 0xd0, 0xce, 0x00, 0xda, 0x19, 0x40, 0x3b, 0x03, 0x68, 0x67, 0x00, 0xed, 0x0c, 0xa0, 0x9d, 0x01, 0xb4, 0x33, 0x80, + 0x76, 0x06, 0xd0, 0xbe, 0x89, 0x01, 0xbc, 0x68, 0xbd, 0xbd, 0x9c, 0x05, 0x94, 0x3f, 0xdd, 0x64, 0x41, 0xf9, 0x6f, 0xf8, 0x62, 0x07, 0x46, 0x1d, 0xb1, 0x83, 0xac, 0xbb, 0x8f, 0x8f, 0xfe, 0x86, 0xfe, 0xaf, 0xa2, 0xff, 0x22, 0xf4, 0x7f, 0x39, 0x86, 0x46, 0xb1, 0x0b, 0xa2, 0x15, 0xd5, 0xab, 0xec, 0x59, 0xc4, 0x0f, 0x11, 0x3f, 0xac, 0x5e, 0x5d, 0xcf, 0x22, 0x7e, 0x88, 0xf8, 0x21, 0xe2, 0x87, 0x88, 0xdf, 0x8e, 0xf8, 0xed, 0x95, 0xbf, 0x2f, 0x7b, 0x8b, 0xf8, 0xd9, 0xea, 0xdf, 0x95, 0xa5, 0x11, 0x3f, 0x8b, 0xf8, 0x0b, 0x10, 0x3f, 0x44, 0xfc, 0xb0, 0xf2, 0xb7, 0x65, 0x0f, 0x46, 0x71, 0xc4, 0x8f, 0x23, 0x7e, 0x1c, 0xf1, 0xe3, 0x88, 0x1f, 0x47, 0xfc, 0x38, 0xe2, 0xc7, 0x11, 0x3f, 0x8e, 0xf8, 0x71, 0xc4, 0x8f, 0x23, 0x7e, 0x3b, 0xe2, 0xb7, 0x23, 0x7e, 0x16, 0xf1, 0x43, 0x94, + 0x0f, 0xeb, 0x0e, 0x88, 0x3a, 0xea, 0xec, 0x33, 0xda, 0xc7, 0xd1, 0x3e, 0x8e, 0xf6, 0x59, 0xb4, 0x8f, 0xa3, 0x7d, 0x1c, 0xed, 0xe3, 0x68, 0x1f, 0x47, 0xfb, 0x78, 0xf5, 0xca, 0x7a, 0xb6, 0x4a, 0xfb, 0x10, 0xed, 0x43, 0xb4, 0x6f, 0x2f, 0x5f, 0x59, 0x47, 0xbd, 0x2c, 0xea, 0x65, 0x51, 0x2f, 0x8b, 0x7a, 0x59, 0xd4, 0xcb, 0xa2, 0x5e, 0x16, 0xf5, 0xb2, 0xa8, 0x97, 0x45, 0xbd, 0x2c, 0xea, 0x65, 0x51, 0x2f, 0x8b, 0x7a, 0x59, 0xd4, 0xcb, 0xa2, 0x5e, 0x16, 0xf5, 0xb2, 0xa8, 0x97, 0x45, 0xbd, 0x2c, 0xea, 0x65, 0x51, 0x2f, 0x8b, 0x7a, 0x59, 0xd4, 0xcb, 0xa2, 0x5e, 0x16, 0xf5, 0xb2, 0xa8, 0x97, 0x45, 0xbd, 0x2c, 0xea, 0x65, 0x51, 0x2f, 0x5b, 0xfd, 0x04, 0xb8, 0x2c, 0xea, 0x65, 0x51, 0x2f, 0x8b, 0x7a, 0x21, 0xea, 0x85, 0xa8, 0x17, 0xbe, 0xcb, 0x95, 0xe7, 0x2c, 0xea, 0x65, 0x51, + 0x2f, 0x8b, 0x7a, 0x59, 0xd4, 0xcb, 0xfe, 0x37, 0x5c, 0x79, 0x8e, 0xa3, 0x5e, 0x1c, 0xf5, 0xe2, 0xa8, 0x17, 0x47, 0xbd, 0x38, 0xea, 0xc5, 0x51, 0x2f, 0x8e, 0x7a, 0x71, 0xd4, 0x8b, 0xa3, 0x5e, 0x1c, 0xf5, 0xe2, 0xa8, 0x17, 0x47, 0xbd, 0x38, 0xea, 0xc5, 0x51, 0x2f, 0x8e, 0x7a, 0x71, 0xd4, 0x8b, 0xa3, 0x5e, 0xfc, 0x3f, 0xa0, 0x5e, 0x16, 0xf5, 0xb2, 0xa8, 0x97, 0x45, 0xbd, 0x2c, 0xea, 0x65, 0x51, 0x2f, 0x8b, 0x7a, 0x59, 0xd4, 0x0b, 0x51, 0x2f, 0x44, 0xbd, 0x10, 0xf5, 0x42, 0xd4, 0x0b, 0x51, 0x2f, 0x44, 0xbd, 0x2c, 0xea, 0x65, 0x51, 0x2f, 0x8b, 0x7a, 0x59, 0xd4, 0xcb, 0xa2, 0x5e, 0x16, 0xf5, 0xb2, 0xa8, 0x97, 0x45, 0xbd, 0x2c, 0xea, 0x65, 0x51, 0x2f, 0x8b, 0x7a, 0x59, 0xd4, 0xcb, 0xa2, 0x5e, 0x16, 0xf5, 0xb2, 0xa8, 0x97, 0x45, 0xbd, 0x2c, 0xea, 0x85, 0xa8, 0x17, 0xa2, + 0x5e, 0x88, 0x7a, 0x21, 0xea, 0x85, 0xa8, 0x17, 0xa2, 0x5e, 0x88, 0x7a, 0x21, 0xea, 0x85, 0xa8, 0x17, 0xa2, 0x5e, 0x88, 0x7a, 0x21, 0xea, 0x85, 0xa8, 0x17, 0xa2, 0x5e, 0x88, 0x7a, 0x21, 0xea, 0x85, 0xa8, 0x17, 0xa2, 0x5e, 0x88, 0x7a, 0x21, 0xea, 0x85, 0xa8, 0x17, 0xa2, 0x5e, 0x88, 0x7a, 0x21, 0xea, 0x85, 0xa8, 0x17, 0xa2, 0x5e, 0xf8, 0xdf, 0x7c, 0xe5, 0x39, 0x44, 0xbd, 0x10, 0xf5, 0x42, 0xd4, 0x0b, 0x51, 0x2f, 0x44, 0xbd, 0x10, 0xf5, 0x42, 0xd4, 0x0b, 0x51, 0xaf, 0xfc, 0x0e, 0x10, 0x21, 0xea, 0x85, 0xa8, 0x97, 0x46, 0xbd, 0x34, 0xea, 0xa5, 0x51, 0x2f, 0x8d, 0x7a, 0x69, 0xd4, 0x4b, 0xa3, 0x5e, 0x1a, 0xf5, 0xd2, 0xa8, 0x97, 0x46, 0xbd, 0x76, 0xd4, 0x6b, 0x47, 0xbd, 0x76, 0xd4, 0x6b, 0x47, 0xbd, 0x76, 0xd4, 0x6b, 0x47, 0xbd, 0x76, 0xd4, 0x6b, 0x47, 0xbd, 0x76, 0xd4, + 0x6b, 0x47, 0xbd, 0x76, 0xd4, 0x6b, 0x47, 0xbd, 0x76, 0xd4, 0x6b, 0x47, 0xbd, 0x76, 0xd4, 0x6b, 0x47, 0xbd, 0x76, 0xd4, 0x6b, 0x47, 0xbd, 0x76, 0xd4, 0x6b, 0x47, 0xbd, 0x76, 0xd4, 0x6b, 0x47, 0xbd, 0x76, 0xd4, 0x6b, 0x47, 0xbd, 0x76, 0xd4, 0x6b, 0x47, 0xbd, 0x76, 0xd4, 0x6b, 0x47, 0xbd, 0x76, 0xd4, 0x6b, 0x47, 0xbd, 0x76, 0xd4, 0x6b, 0x47, 0xbd, 0x76, 0xd4, 0x6b, 0x47, 0xbd, 0x76, 0xd4, 0x6b, 0x47, 0xbd, 0x76, 0xd4, 0x6b, 0x47, 0xbd, 0xf6, 0xff, 0x42, 0xea, 0xbd, 0x88, 0x7a, 0x2f, 0x5a, 0x33, 0x2f, 0xb7, 0x66, 0x5e, 0x6e, 0xcd, 0xbc, 0xdc, 0x9a, 0x79, 0xb9, 0x35, 0xf3, 0x72, 0x6b, 0xe6, 0xe5, 0xd6, 0xcc, 0xcb, 0x11, 0x71, 0x21, 0x22, 0x96, 0x3f, 0xa9, 0x65, 0x01, 0x22, 0x2e, 0x40, 0xc4, 0x05, 0x88, 0xb8, 0x00, 0x11, 0x17, 0x20, 0xe2, 0x02, 0x44, 0x5c, 0x80, 0x88, + 0x0b, 0x10, 0x71, 0x01, 0x22, 0x2e, 0x78, 0xb7, 0xbf, 0x43, 0x40, 0xc4, 0xf0, 0x5d, 0xae, 0x4a, 0xbf, 0x13, 0x11, 0xb3, 0xb1, 0xe7, 0xdf, 0x61, 0x4d, 0x9c, 0x44, 0xc4, 0x24, 0x22, 0x26, 0x11, 0x31, 0x89, 0x88, 0x49, 0x44, 0x4c, 0x22, 0x62, 0x0a, 0x11, 0x53, 0x88, 0x98, 0x42, 0xc4, 0x24, 0x22, 0x26, 0xff, 0xc7, 0xdf, 0x03, 0xf0, 0x8b, 0xd1, 0x6a, 0x44, 0x4c, 0xbd, 0x6d, 0x4d, 0x9c, 0x47, 0xc4, 0x3c, 0x22, 0xe6, 0x83, 0xf2, 0x6f, 0x49, 0x7c, 0x55, 0xde, 0x5a, 0x13, 0xe7, 0x37, 0x59, 0x13, 0x6f, 0xfa, 0xef, 0xcb, 0x49, 0x44, 0x4c, 0x22, 0x62, 0xb2, 0xfa, 0xef, 0xcb, 0xe5, 0x35, 0x71, 0xaa, 0xfa, 0xef, 0xcb, 0x3d, 0x88, 0xd8, 0x83, 0x88, 0x3d, 0x88, 0xd8, 0x53, 0x5d, 0x13, 0x97, 0xdf, 0x2b, 0xb0, 0x0d, 0x11, 0xdb, 0x10, 0xb1, 0x0d, 0x11, 0xdb, 0x10, 0xb1, 0x0d, 0x11, + 0xdb, 0x10, 0xb1, 0x0d, 0x11, 0xdb, 0x10, 0xb1, 0x0d, 0x11, 0xdb, 0x36, 0x21, 0xe2, 0x73, 0x88, 0xd8, 0x81, 0x88, 0x29, 0x44, 0x9c, 0x83, 0x88, 0x09, 0x44, 0x5c, 0x5e, 0x21, 0xe2, 0x61, 0x72, 0x78, 0x85, 0x8c, 0xcf, 0x56, 0x68, 0xf8, 0xc5, 0x68, 0x5d, 0xec, 0x4b, 0x72, 0x41, 0xd4, 0x89, 0x8a, 0xc9, 0x2a, 0x15, 0x37, 0xac, 0x83, 0x93, 0xa8, 0x98, 0xaa, 0xfe, 0x9b, 0x73, 0xbe, 0xfa, 0x6f, 0xce, 0x6d, 0xa8, 0xd8, 0x86, 0x8a, 0x09, 0x54, 0x5c, 0x8d, 0x8a, 0x29, 0x54, 0x4c, 0xa0, 0x62, 0x0f, 0x2a, 0xa6, 0x50, 0x71, 0xce, 0x26, 0xeb, 0xe0, 0xc4, 0x3f, 0xf1, 0xbe, 0x75, 0x6d, 0xa8, 0xd8, 0x86, 0x8a, 0xa9, 0xf2, 0x3a, 0xb8, 0xb2, 0xf6, 0x3d, 0x20, 0x5a, 0x8a, 0x8a, 0x4b, 0xff, 0xed, 0xbd, 0xbc, 0x7c, 0xef, 0x9d, 0xde, 0xc7, 0x6b, 0x55, 0x94, 0x44, 0xc5, 0x54, 0x95, 0x8a, + 0xf9, 0xea, 0x1a, 0xb8, 0x6d, 0x74, 0xf9, 0xf3, 0x95, 0xfe, 0xf7, 0xac, 0x05, 0x93, 0xa8, 0x98, 0x44, 0xc5, 0x24, 0x2a, 0x26, 0x51, 0x31, 0x89, 0x8a, 0x29, 0x54, 0x4c, 0xa1, 0x62, 0x0a, 0x15, 0x53, 0xa8, 0x98, 0x42, 0xc5, 0x24, 0x2a, 0x26, 0x51, 0x31, 0x89, 0x8a, 0x49, 0x54, 0x4c, 0xa2, 0x62, 0x12, 0x15, 0x93, 0xa8, 0x98, 0x44, 0xc5, 0x24, 0x2a, 0x26, 0x51, 0x31, 0x89, 0x8a, 0x49, 0x54, 0x4c, 0xa2, 0x62, 0x12, 0x15, 0x93, 0xa8, 0x98, 0x44, 0xc5, 0x24, 0x2a, 0x26, 0x51, 0x31, 0x89, 0x8a, 0x49, 0x54, 0x4c, 0xa2, 0x62, 0x12, 0x15, 0x93, 0xa8, 0x98, 0xac, 0xbc, 0x1f, 0xd2, 0x7f, 0xc2, 0x7b, 0x21, 0xa1, 0xe2, 0x6a, 0x54, 0x5c, 0x8d, 0x8a, 0xab, 0x51, 0x71, 0x35, 0x2a, 0xae, 0x46, 0xc5, 0xd5, 0xa8, 0xb8, 0x1a, 0x15, 0x53, 0xa8, 0x98, 0x42, 0xc5, 0x14, 0x2a, 0xa6, 0x50, + 0x31, 0x85, 0x8a, 0x29, 0x54, 0x4c, 0xfd, 0x3b, 0x6b, 0xc1, 0x3c, 0x2a, 0xe6, 0x51, 0x31, 0x8f, 0x8a, 0x79, 0x54, 0xcc, 0xa3, 0x62, 0x1e, 0x15, 0xf3, 0xa8, 0x98, 0x47, 0xc5, 0x3c, 0x2a, 0xe6, 0x51, 0x31, 0x8f, 0x8a, 0x79, 0x54, 0xcc, 0xa3, 0x62, 0x1e, 0x15, 0xf3, 0xa8, 0x98, 0x47, 0xc5, 0xfc, 0xff, 0xc3, 0x5a, 0xf0, 0xff, 0xe6, 0xdf, 0x63, 0x93, 0xa8, 0x98, 0x44, 0xc5, 0x24, 0x2a, 0x26, 0x51, 0x31, 0x89, 0x8a, 0x49, 0x54, 0x4c, 0xa2, 0x62, 0x12, 0x15, 0x93, 0xa8, 0x98, 0x44, 0xc5, 0x24, 0x2a, 0x26, 0x51, 0x31, 0x89, 0x8a, 0x49, 0x54, 0x4c, 0xa2, 0x62, 0x12, 0x15, 0x93, 0xa8, 0x98, 0x44, 0xc5, 0x24, 0x2a, 0x26, 0x51, 0x31, 0x89, 0x8a, 0x49, 0x54, 0x4c, 0xa2, 0x62, 0x12, 0x15, 0x93, 0xa8, 0x98, 0x44, 0xc5, 0xe4, 0x3f, 0xf9, 0xef, 0xb1, 0x3d, 0xa8, 0xd8, 0x83, 0x8a, + 0x3d, 0xa8, 0xd8, 0x83, 0x8a, 0x3d, 0xa8, 0xd8, 0x83, 0x8a, 0x3d, 0xa8, 0xd8, 0x83, 0x8a, 0x3d, 0xa8, 0xd8, 0x86, 0x8a, 0x6d, 0xa8, 0xd8, 0x86, 0x8a, 0x6d, 0xa8, 0xd8, 0x86, 0x8a, 0x6d, 0xa8, 0xd8, 0x86, 0x8a, 0x6d, 0xa8, 0xd8, 0x86, 0x8a, 0x6d, 0xa8, 0xd8, 0x86, 0x8a, 0x6d, 0xa8, 0xd8, 0x86, 0x8a, 0x6d, 0xa8, 0xd8, 0x86, 0x8a, 0x6d, 0xa8, 0xd8, 0x86, 0x8a, 0x6d, 0xa8, 0xd8, 0x86, 0x8a, 0x6d, 0xa8, 0xd8, 0x86, 0x8a, 0x6d, 0xa8, 0xd8, 0x86, 0x8a, 0x6d, 0xa8, 0xd8, 0x86, 0x8a, 0x6d, 0xa8, 0xd8, 0x86, 0x8a, 0x6d, 0xa8, 0xd8, 0x86, 0x8a, 0x6d, 0xa8, 0xd8, 0x86, 0x8a, 0x6d, 0xa8, 0xd8, 0x86, 0x8a, 0x6d, 0xa8, 0xd8, 0x86, 0x8a, 0x6d, 0xa8, 0xd8, 0x86, 0x8a, 0x6d, 0xa8, 0xd8, 0xf6, 0x5f, 0x48, 0xc5, 0xe7, 0x50, 0xf1, 0x39, 0x54, 0xec, 0x40, 0xc5, 0x0e, 0x54, 0xec, 0x40, + 0xc5, 0x0e, 0x54, 0xec, 0x40, 0xc5, 0x0e, 0x54, 0x2c, 0xbf, 0xff, 0x51, 0x0a, 0x15, 0x53, 0xa8, 0x38, 0x07, 0x15, 0xe7, 0xa0, 0xe2, 0x1c, 0x54, 0x9c, 0x83, 0x8a, 0x73, 0x50, 0x71, 0x0e, 0x2a, 0xce, 0x41, 0xc5, 0x39, 0xa8, 0x38, 0x07, 0x15, 0xe7, 0xa0, 0x62, 0x02, 0x15, 0x13, 0xa8, 0x98, 0x40, 0xc5, 0x04, 0x2a, 0x26, 0x50, 0x31, 0x51, 0xfd, 0xb7, 0xda, 0x24, 0x2a, 0x26, 0x51, 0x31, 0x89, 0x8a, 0x49, 0x54, 0x4c, 0xbe, 0x0b, 0x15, 0x53, 0x95, 0x75, 0x62, 0x0f, 0x2a, 0xf6, 0xa0, 0x62, 0x4f, 0x75, 0x9d, 0xf8, 0x6e, 0x57, 0x8a, 0x73, 0xa8, 0x98, 0x43, 0xc5, 0xdc, 0x26, 0x57, 0x8a, 0xff, 0xa7, 0xa9, 0xd8, 0x55, 0xbd, 0x52, 0xbc, 0xe9, 0x3a, 0x31, 0x44, 0xc5, 0x10, 0x15, 0x43, 0x54, 0x0c, 0x51, 0x31, 0xfc, 0x0f, 0xd6, 0x89, 0x9b, 0x5e, 0x29, 0xde, 0xb0, 0x4e, 0xcc, 0x55, + 0xd7, 0x89, 0x6f, 0xbf, 0x52, 0x1c, 0x56, 0xa9, 0xd8, 0x81, 0x8a, 0x1d, 0xa8, 0xd8, 0x81, 0x8a, 0x1d, 0xa8, 0xd8, 0x81, 0x8a, 0x1d, 0xa8, 0xd8, 0x81, 0x8a, 0x1d, 0xa8, 0xd8, 0x81, 0x8a, 0x1d, 0xa8, 0x98, 0x41, 0xc5, 0x0c, 0x2a, 0x2e, 0x41, 0xc5, 0x55, 0xa8, 0xf8, 0x02, 0x2a, 0x2e, 0x2a, 0xbf, 0x83, 0xee, 0x26, 0xeb, 0xc4, 0x79, 0xd6, 0x89, 0xcb, 0xac, 0x13, 0x5f, 0xb4, 0x4e, 0x5c, 0x8a, 0x8c, 0x83, 0x88, 0xd8, 0x55, 0xbd, 0x5a, 0x9c, 0xd9, 0x64, 0x9d, 0x58, 0xbe, 0x32, 0x9c, 0x7b, 0xdb, 0x3a, 0xb1, 0x03, 0x11, 0x3b, 0x2a, 0xef, 0xe8, 0x7a, 0x4b, 0xd4, 0x85, 0x88, 0xb9, 0xea, 0x3b, 0xb9, 0x96, 0xaf, 0x0c, 0xe7, 0x10, 0x71, 0xd1, 0x26, 0xeb, 0xc4, 0xa5, 0xff, 0x04, 0x11, 0x3b, 0x10, 0xb1, 0xa3, 0x7a, 0x65, 0x78, 0xc3, 0x3a, 0xf1, 0x15, 0x44, 0x7c, 0x65, 0x13, 0x22, + 0x86, 0xef, 0x42, 0xc4, 0xf2, 0x55, 0xe1, 0x1c, 0x22, 0x66, 0x36, 0x59, 0x27, 0x76, 0x94, 0xaf, 0x0a, 0x23, 0x62, 0x0f, 0x22, 0xf6, 0x20, 0x62, 0x0f, 0x22, 0xf6, 0x20, 0x62, 0x0f, 0x22, 0xf6, 0x20, 0x62, 0x0f, 0x22, 0xf6, 0x20, 0x62, 0x0f, 0x22, 0xf6, 0x20, 0x62, 0x0f, 0x22, 0xf6, 0x20, 0x62, 0x0f, 0x22, 0xf6, 0x20, 0x62, 0x0f, 0x22, 0xf6, 0x20, 0x62, 0x0f, 0x22, 0xf6, 0x20, 0x62, 0x0f, 0x22, 0xf6, 0x20, 0x62, 0x0f, 0x22, 0xf6, 0x20, 0x62, 0x0f, 0x22, 0xf6, 0x20, 0x62, 0x0f, 0x22, 0xf6, 0x20, 0x62, 0x0f, 0x22, 0xf6, 0x20, 0x62, 0x0f, 0x22, 0xf6, 0x20, 0x62, 0xcf, 0xdb, 0xd6, 0x89, 0xef, 0x74, 0x75, 0x34, 0x87, 0x88, 0x39, 0x44, 0xcc, 0x21, 0x62, 0x0e, 0x11, 0x73, 0xff, 0x0d, 0x57, 0x47, 0xff, 0xb3, 0x88, 0xd8, 0x85, 0x88, 0x5d, 0x88, 0xd8, 0x85, 0x88, 0x5d, 0x88, + 0xd8, 0x85, 0x88, 0x5d, 0x88, 0xd8, 0xf5, 0x1f, 0x5c, 0x1d, 0x7d, 0xb7, 0x75, 0x62, 0x88, 0x88, 0x21, 0x22, 0x86, 0x88, 0x18, 0x22, 0x62, 0x88, 0x88, 0x21, 0x22, 0x86, 0x88, 0x18, 0x22, 0x62, 0x88, 0x88, 0x21, 0x22, 0x86, 0x88, 0x18, 0x22, 0x62, 0x88, 0x88, 0x21, 0x22, 0x86, 0x88, 0x18, 0x22, 0x62, 0xf8, 0xdf, 0xbc, 0x4e, 0xfc, 0xef, 0xbc, 0x3a, 0xfa, 0xcf, 0xac, 0x13, 0xff, 0x91, 0xab, 0xa3, 0x1d, 0x88, 0xd8, 0x81, 0x88, 0x1d, 0x88, 0xd8, 0x81, 0x88, 0x1d, 0x88, 0xd8, 0x81, 0x88, 0x1d, 0x88, 0xd8, 0x81, 0x88, 0x1d, 0x88, 0xd8, 0x81, 0x88, 0x1d, 0x88, 0xd8, 0x81, 0x88, 0x1d, 0x88, 0xd8, 0x81, 0x88, 0x1d, 0x88, 0xd8, 0x81, 0x88, 0x1d, 0x88, 0xd8, 0x81, 0x88, 0x1d, 0x88, 0xd8, 0x81, 0x88, 0x1d, 0x88, 0xd8, 0x81, 0x88, 0x1d, 0x88, 0xd8, 0x81, 0x88, 0x1d, 0x88, 0xd8, + 0x81, 0x88, 0x1d, 0x88, 0xd8, 0x81, 0x88, 0x1d, 0x88, 0xd8, 0x81, 0x88, 0x1d, 0x88, 0xd8, 0x81, 0x88, 0x1d, 0x88, 0xd8, 0x81, 0x88, 0x1d, 0x88, 0xd8, 0x81, 0x88, 0x1d, 0x88, 0xd8, 0x81, 0x88, 0x1d, 0x88, 0x98, 0x41, 0xc4, 0x0c, 0x22, 0x66, 0x10, 0x31, 0x83, 0x88, 0x19, 0x44, 0xcc, 0x20, 0x62, 0x06, 0x11, 0x33, 0x88, 0x98, 0x41, 0xc4, 0x0c, 0x22, 0x66, 0x10, 0x31, 0x83, 0x88, 0x19, 0x44, 0xcc, 0x20, 0x62, 0x06, 0x11, 0x33, 0x88, 0x98, 0x41, 0xc4, 0x0c, 0x22, 0x66, 0x10, 0x31, 0x83, 0x88, 0x19, 0x44, 0xcc, 0x20, 0xe2, 0x12, 0x44, 0x5c, 0x82, 0x88, 0xab, 0x10, 0x71, 0x15, 0x22, 0xae, 0x42, 0xc4, 0x55, 0x88, 0xb8, 0x0a, 0x11, 0x57, 0x21, 0xe2, 0x2a, 0x44, 0x7c, 0x01, 0x11, 0x5f, 0x40, 0xc4, 0x45, 0x88, 0xb8, 0x08, 0x11, 0x17, 0x21, 0xe2, 0x22, 0x44, 0x5c, 0x84, 0x88, + 0x8b, 0x10, 0x71, 0x11, 0x22, 0x2e, 0x42, 0xc4, 0x45, 0x88, 0xb8, 0xe8, 0xdd, 0xde, 0xf9, 0xaf, 0xba, 0x4e, 0x7c, 0xa7, 0x2b, 0xa7, 0x19, 0x44, 0xcc, 0x20, 0x62, 0x06, 0x11, 0x33, 0x88, 0x98, 0x41, 0xc4, 0x5c, 0xb0, 0x45, 0xb0, 0x5d, 0x34, 0x1c, 0xec, 0xa5, 0x6b, 0x1f, 0x1a, 0x15, 0x02, 0x15, 0xa2, 0x33, 0x67, 0xcb, 0xbf, 0xef, 0x5e, 0x73, 0x61, 0x34, 0x54, 0x73, 0xab, 0xb5, 0xc6, 0x63, 0xd1, 0xfc, 0x9a, 0x27, 0x75, 0xc6, 0x7b, 0xa2, 0x57, 0x2a, 0xff, 0x56, 0x36, 0x9f, 0xd7, 0xec, 0x12, 0xd4, 0x36, 0x7c, 0x24, 0x68, 0x08, 0xc6, 0x6e, 0x7c, 0x5f, 0xb4, 0x43, 0xbd, 0xd6, 0x79, 0xc1, 0x0e, 0x38, 0x34, 0x1c, 0x3b, 0x2c, 0x38, 0x20, 0xf6, 0x89, 0xe0, 0xe4, 0xd8, 0xe1, 0x6e, 0x8f, 0x70, 0x7b, 0x5c, 0xf0, 0xf9, 0xd8, 0x69, 0xc1, 0x05, 0xb1, 0xd3, 0x83, 0x9f, 0xc5, 0xce, + 0x08, 0xee, 0x8b, 0x9d, 0x19, 0xc4, 0x62, 0x17, 0x04, 0xb5, 0x95, 0xf7, 0x9e, 0xba, 0x48, 0x6e, 0xb5, 0x7a, 0x79, 0x2c, 0x5a, 0x57, 0x7b, 0x57, 0x70, 0x47, 0xed, 0x3d, 0xc1, 0x2f, 0x6b, 0x7f, 0x1c, 0xfc, 0xa2, 0xee, 0x80, 0xe0, 0xdc, 0xba, 0x03, 0x65, 0x7e, 0x94, 0x78, 0xd7, 0xf7, 0x21, 0x79, 0x87, 0xf7, 0x1e, 0xb1, 0x67, 0xef, 0x6b, 0xd8, 0x35, 0xd8, 0xa7, 0x61, 0xb7, 0x60, 0xff, 0xea, 0xfb, 0x57, 0x6c, 0x6e, 0x4f, 0x77, 0x0e, 0x76, 0x0a, 0x1a, 0xa3, 0x81, 0x60, 0x4b, 0x29, 0xbf, 0x7f, 0xfe, 0x5e, 0xd1, 0xeb, 0x95, 0x3d, 0xbe, 0x30, 0xea, 0x8d, 0x8d, 0x8f, 0x16, 0xc7, 0x26, 0x70, 0x83, 0xd3, 0xac, 0xcb, 0xde, 0xba, 0x7a, 0xd9, 0xee, 0xd8, 0xdf, 0xa8, 0xf9, 0x6a, 0x34, 0x60, 0xaf, 0x7a, 0x2a, 0xc7, 0x7e, 0x57, 0xb4, 0xcc, 0xf1, 0x2f, 0x72, 0xfc, 0xcb, 0xea, 0x76, + 0x89, 0x06, 0xea, 0x56, 0x4a, 0x57, 0x34, 0x30, 0xc6, 0x63, 0xc6, 0x5c, 0x27, 0xdf, 0x96, 0x81, 0x68, 0xa0, 0xfe, 0x90, 0x68, 0xa0, 0xe9, 0xce, 0x68, 0x55, 0xd3, 0x94, 0x68, 0x55, 0x6c, 0x16, 0xd7, 0x28, 0x71, 0x8d, 0x12, 0xd7, 0x28, 0x55, 0x7f, 0x07, 0x2c, 0xc1, 0x35, 0x12, 0x5c, 0x23, 0xc1, 0x35, 0x12, 0x5c, 0x23, 0xc1, 0x35, 0x12, 0x5c, 0x23, 0xcd, 0x35, 0xd2, 0x5c, 0x23, 0xcd, 0x35, 0x12, 0x5c, 0x23, 0xf1, 0x3f, 0xfe, 0x5e, 0x71, 0x6f, 0xad, 0xc0, 0xd7, 0xbc, 0xed, 0x77, 0xc0, 0x86, 0xb9, 0xc6, 0x70, 0x79, 0x8c, 0xb9, 0xc6, 0x30, 0xd7, 0x18, 0xae, 0xfe, 0x0e, 0x58, 0xb8, 0xc9, 0xef, 0x80, 0x6d, 0xea, 0x1a, 0x09, 0xae, 0x91, 0xe0, 0x1a, 0x89, 0xaa, 0x6b, 0x94, 0x7f, 0x07, 0x2c, 0x5d, 0x75, 0x8d, 0x1c, 0xd7, 0xc8, 0x71, 0x8d, 0x1c, 0xd7, 0xc8, 0x55, 0x7f, 0x07, + 0x6c, 0xd9, 0x3f, 0x71, 0x4d, 0x3a, 0xc5, 0x35, 0x52, 0xd5, 0x6b, 0xd2, 0xcb, 0x78, 0xc6, 0x0b, 0x1c, 0x22, 0xc1, 0x21, 0x52, 0x9b, 0xfc, 0xde, 0x57, 0x82, 0x43, 0xa4, 0xab, 0x0e, 0x51, 0xfc, 0xbb, 0x6b, 0xcd, 0x6f, 0xad, 0xa8, 0xd3, 0xdc, 0x21, 0xc7, 0x1d, 0xd2, 0xdc, 0xe1, 0x85, 0x8d, 0xbf, 0xef, 0xf5, 0x8f, 0xbf, 0x6f, 0xd9, 0x86, 0xeb, 0xcb, 0x6b, 0x36, 0xf9, 0x5d, 0xaf, 0xea, 0xfb, 0x39, 0x45, 0xc3, 0xef, 0xf8, 0x5e, 0x4e, 0xab, 0xa2, 0x04, 0x5f, 0x48, 0xf3, 0x85, 0x54, 0xd5, 0x17, 0x8a, 0xd5, 0xeb, 0xca, 0x09, 0xbe, 0x50, 0xe2, 0x0b, 0x25, 0xbe, 0x50, 0xe2, 0x0b, 0x25, 0xbe, 0x50, 0xe2, 0x0b, 0x25, 0xbe, 0x50, 0xe2, 0x0b, 0x25, 0xbe, 0x50, 0xe2, 0x0b, 0x25, 0xbe, 0x50, 0xe2, 0x0b, 0x25, 0xbe, 0x50, 0xe2, 0x0b, 0x25, 0xbe, 0x50, 0xe2, 0x0b, 0x25, 0xbe, 0x50, + 0xe2, 0x0b, 0x25, 0xbe, 0x50, 0xe2, 0x0b, 0x25, 0xbe, 0x50, 0xe2, 0x0b, 0x25, 0xbe, 0x50, 0xe2, 0x0b, 0x25, 0xbe, 0x50, 0xe2, 0x0b, 0x25, 0xbe, 0x50, 0xe2, 0x0b, 0x25, 0xbe, 0x50, 0xe2, 0x0b, 0x25, 0xbe, 0x50, 0x7a, 0xdb, 0xef, 0x62, 0x25, 0xf8, 0x42, 0x82, 0x2f, 0x24, 0xf8, 0x42, 0x82, 0x2f, 0x24, 0xf8, 0x42, 0x9a, 0x2f, 0xa4, 0xf9, 0x42, 0x9a, 0x2f, 0xa4, 0xf9, 0x42, 0x9a, 0x2f, 0x24, 0xf8, 0x42, 0x82, 0x2f, 0x24, 0xf8, 0x42, 0x82, 0x2f, 0x24, 0xf8, 0x42, 0x82, 0x2f, 0x24, 0xf8, 0x42, 0x82, 0x2f, 0x24, 0xf8, 0x42, 0x82, 0x2f, 0x24, 0xf8, 0x42, 0x82, 0x2f, 0x24, 0xf8, 0x42, 0x82, 0x2f, 0x24, 0xf8, 0x42, 0x82, 0x2f, 0x24, 0xf8, 0x42, 0x82, 0x2f, 0x24, 0xf8, 0x42, 0x82, 0x2f, 0x24, 0xf8, 0x42, 0x82, 0x2f, 0x24, 0xf8, 0x42, 0xa2, 0xf2, 0x3e, 0x38, 0xff, 0x09, 0xef, + 0x81, 0xf3, 0x1f, 0xac, 0xa0, 0xd7, 0xf0, 0x85, 0x35, 0x7c, 0x61, 0x0d, 0x5f, 0x58, 0xc3, 0x17, 0xd6, 0xf0, 0x85, 0x35, 0x7c, 0x61, 0xcd, 0xbf, 0xf3, 0xbb, 0x58, 0xc3, 0x7c, 0x61, 0x98, 0x2f, 0x0c, 0xf3, 0x85, 0x61, 0xbe, 0x30, 0xcc, 0x17, 0x86, 0xf9, 0xc2, 0x30, 0x5f, 0x18, 0xe6, 0x0b, 0xc3, 0x7c, 0x61, 0x98, 0x2f, 0x0c, 0xf3, 0x85, 0x61, 0xbe, 0x30, 0xcc, 0x17, 0x86, 0xf9, 0xc2, 0x30, 0x5f, 0x18, 0xe6, 0x0b, 0xc3, 0xff, 0x0f, 0xbf, 0x8b, 0xf5, 0x7f, 0xe3, 0x0b, 0x09, 0xbe, 0x90, 0xe0, 0x0b, 0x09, 0xbe, 0x90, 0xe0, 0x0b, 0x09, 0xbe, 0x90, 0xe0, 0x0b, 0x09, 0xbe, 0x90, 0xe0, 0x0b, 0x09, 0xbe, 0x90, 0xe0, 0x0b, 0x09, 0xbe, 0x90, 0xe0, 0x0b, 0x09, 0xbe, 0x90, 0xe0, 0x0b, 0x09, 0xbe, 0x90, 0xe0, 0x0b, 0x09, 0xbe, 0x90, 0xe0, 0x0b, 0x09, 0xbe, 0x90, 0xe0, 0x0b, 0x09, + 0xbe, 0x90, 0xe0, 0x0b, 0x09, 0xbe, 0x90, 0xe0, 0x0b, 0x09, 0xbe, 0x90, 0xe0, 0x0b, 0x89, 0x7f, 0xd2, 0x17, 0x72, 0x7c, 0x21, 0xc7, 0x17, 0x72, 0x7c, 0x21, 0xc7, 0x17, 0x72, 0x7c, 0x21, 0xc7, 0x17, 0x72, 0x7c, 0x21, 0xc7, 0x17, 0x72, 0xff, 0xcb, 0xaf, 0x2b, 0xa7, 0xf8, 0x42, 0x8a, 0x2f, 0xa4, 0xf8, 0x42, 0x8a, 0x2f, 0xa4, 0xf8, 0x42, 0x8a, 0x2f, 0xa4, 0xf8, 0x42, 0x8a, 0x2f, 0xa4, 0xf8, 0x42, 0x8a, 0x2f, 0xa4, 0xf8, 0x42, 0x8a, 0x2f, 0xa4, 0xf8, 0x42, 0x8a, 0x2f, 0xa4, 0xf8, 0x42, 0x8a, 0x2f, 0xa4, 0xf8, 0x42, 0x8a, 0x2f, 0xa4, 0xf8, 0x42, 0x8a, 0x2f, 0xa4, 0xf8, 0x42, 0x6a, 0x93, 0xeb, 0xca, 0xef, 0xfe, 0xde, 0x37, 0x6b, 0xb8, 0x42, 0xb7, 0x24, 0x25, 0x25, 0x3d, 0x92, 0x91, 0xac, 0x84, 0x92, 0x93, 0x7c, 0xf4, 0x42, 0xd5, 0x07, 0x12, 0x7c, 0x20, 0xc1, 0x07, 0x12, + 0x7c, 0x20, 0xd1, 0x58, 0x5e, 0x39, 0x0f, 0x45, 0x29, 0x3e, 0x90, 0xe2, 0x03, 0x29, 0x3e, 0x90, 0xe2, 0x03, 0x29, 0x3e, 0x90, 0x0e, 0x76, 0x09, 0x6a, 0xa2, 0x36, 0x6c, 0xbc, 0x57, 0xc7, 0x8f, 0x74, 0xfa, 0xdf, 0xeb, 0xcc, 0xcd, 0x18, 0x39, 0x31, 0x76, 0x70, 0x94, 0x8f, 0x59, 0x0d, 0x63, 0xe5, 0xaf, 0x62, 0xbd, 0x56, 0x6c, 0x05, 0x59, 0x17, 0x25, 0x6b, 0x3e, 0x85, 0x95, 0xb7, 0x46, 0x2b, 0xb1, 0xf2, 0x41, 0xac, 0xfc, 0x39, 0x62, 0xaf, 0xd4, 0x99, 0x56, 0x9b, 0xed, 0x2b, 0x1a, 0x76, 0x0a, 0x76, 0x37, 0x3b, 0x5e, 0x45, 0xe5, 0x7d, 0x50, 0x79, 0x47, 0x54, 0xde, 0x11, 0x95, 0x47, 0xa3, 0xf2, 0x56, 0x0d, 0x7b, 0x07, 0xfb, 0x21, 0xf3, 0xbe, 0x2a, 0x6e, 0x99, 0x4a, 0xf9, 0x53, 0xb0, 0x7b, 0xf0, 0x21, 0x6b, 0xf2, 0x43, 0xa3, 0x7e, 0x54, 0x2e, 0x72, 0x88, 0xed, 0x38, 0xc4, + 0xfe, 0x1c, 0x62, 0x3b, 0x0e, 0xb1, 0x3f, 0x87, 0x38, 0x9c, 0x43, 0x1c, 0xc7, 0x21, 0x2e, 0xe7, 0x10, 0xdf, 0xae, 0x39, 0xbb, 0xf2, 0xf7, 0xbc, 0x3d, 0xb6, 0xbc, 0xd6, 0x96, 0xd3, 0xb6, 0xdc, 0xcf, 0x1f, 0x2e, 0xe3, 0x0f, 0xd7, 0xf0, 0x87, 0x9b, 0xf8, 0xc3, 0x71, 0xfc, 0xe1, 0x38, 0x7b, 0x53, 0xfe, 0x6b, 0x92, 0x9e, 0xfa, 0x1b, 0xe4, 0x3b, 0xf2, 0x5d, 0xb9, 0x59, 0xbe, 0x2f, 0x93, 0xe5, 0x16, 0x61, 0x39, 0xf6, 0x2e, 0x66, 0xef, 0xea, 0x2b, 0x7f, 0x51, 0x96, 0x8c, 0x1d, 0x1a, 0xad, 0x89, 0x7d, 0x5c, 0x4e, 0x0b, 0x76, 0xb3, 0xc5, 0xbd, 0x6c, 0x71, 0x77, 0xaf, 0xbe, 0x8d, 0x57, 0x7f, 0xaf, 0x57, 0xdf, 0x3a, 0xd8, 0xde, 0x59, 0x7a, 0xc4, 0xbe, 0x3e, 0x65, 0x5f, 0xd7, 0x63, 0xdc, 0xa2, 0xd8, 0x79, 0xd6, 0xb7, 0xe7, 0x5b, 0xd7, 0x5e, 0x10, 0x6c, 0xee, 0x8c, 0x24, 0x18, + 0x44, 0x7f, 0xf5, 0xef, 0x8c, 0xef, 0xb2, 0x6f, 0xcb, 0xed, 0xc7, 0xec, 0xfa, 0x99, 0xc1, 0xc1, 0xce, 0x8a, 0x9e, 0x11, 0x1c, 0x65, 0x5b, 0x9b, 0x3b, 0x13, 0x9b, 0x39, 0x13, 0x1f, 0x71, 0x26, 0x26, 0x3a, 0x0b, 0xeb, 0xcc, 0x9f, 0x45, 0x8d, 0x2b, 0x9c, 0xfd, 0xfa, 0xca, 0x5f, 0xac, 0x5e, 0xc8, 0xc1, 0x6e, 0x8d, 0x7a, 0x1d, 0x5d, 0xc6, 0x2b, 0xac, 0xf1, 0x0a, 0xe5, 0xbf, 0x9b, 0x18, 0xd2, 0x3d, 0xde, 0xfc, 0x3b, 0x9b, 0xc9, 0xb3, 0x99, 0x15, 0x9e, 0x91, 0xf7, 0x8c, 0xc2, 0x26, 0x36, 0x53, 0xfe, 0xb7, 0xd8, 0x38, 0x9b, 0x59, 0xf6, 0x36, 0x9b, 0x29, 0xff, 0xe6, 0xd3, 0x2b, 0x6c, 0x66, 0xf1, 0x3f, 0x68, 0x33, 0xab, 0xd9, 0xcc, 0xea, 0xca, 0x5f, 0xdc, 0x2d, 0xac, 0xb8, 0xe2, 0xa1, 0xd1, 0xc3, 0x4c, 0xaf, 0x4e, 0x6d, 0x0c, 0xda, 0xda, 0x31, 0xb6, 0xf6, 0x90, 0x2d, 0x15, + 0x62, 0xe7, 0x94, 0x3f, 0x8f, 0xa5, 0x32, 0x1a, 0x43, 0xb6, 0xd2, 0x65, 0x0b, 0xaf, 0xda, 0x42, 0x5f, 0x75, 0xec, 0xb7, 0x32, 0xd6, 0x1f, 0xf6, 0x6a, 0x43, 0x5e, 0x6d, 0x28, 0x18, 0xe5, 0x99, 0x4f, 0x3a, 0xcb, 0xb3, 0x9d, 0xe5, 0xd9, 0xc1, 0xe8, 0xbf, 0xfb, 0x4b, 0xc0, 0xd3, 0x2b, 0xbf, 0x97, 0xd5, 0xe9, 0xb9, 0xeb, 0x82, 0x53, 0x6d, 0xb5, 0xc1, 0x56, 0x87, 0x6c, 0xf5, 0x5c, 0x8e, 0xf2, 0x63, 0x5b, 0x3d, 0x83, 0x6b, 0xf4, 0xab, 0xca, 0xfb, 0x3d, 0xeb, 0x4b, 0xaa, 0xf2, 0x09, 0x55, 0xf9, 0xaf, 0xf6, 0xe2, 0xeb, 0xb1, 0xe3, 0x79, 0xe5, 0x69, 0x5e, 0xed, 0xf4, 0xa0, 0xd1, 0x88, 0x8d, 0xb6, 0x47, 0x7f, 0x88, 0x9d, 0x1b, 0xcd, 0x30, 0x36, 0xf3, 0x8c, 0xcd, 0x3c, 0x63, 0xf3, 0x3e, 0x95, 0x7b, 0xa7, 0xca, 0xbd, 0x43, 0xe5, 0xde, 0x65, 0x9c, 0x5e, 0x77, 0x86, 0x7f, 0x6c, + 0x8f, 0x8f, 0xb7, 0xc7, 0x47, 0xd9, 0xea, 0xba, 0xf2, 0x1e, 0xd7, 0xde, 0x1b, 0x3d, 0xef, 0x6c, 0x77, 0x61, 0xf7, 0xbd, 0xd8, 0x7d, 0x2f, 0x2e, 0x5f, 0x85, 0xc7, 0x1f, 0xc5, 0xe3, 0xcf, 0xe1, 0xf1, 0xb1, 0x78, 0xfc, 0x65, 0x0c, 0xde, 0xcf, 0x78, 0xd6, 0x61, 0xee, 0x2d, 0x98, 0x7b, 0x0b, 0xe6, 0xde, 0x82, 0xb9, 0xb7, 0x60, 0xee, 0x97, 0x31, 0xf7, 0x46, 0xec, 0xbc, 0x10, 0x1f, 0x3f, 0x8d, 0x8b, 0xe7, 0xe2, 0xe2, 0x78, 0x4c, 0x3c, 0xda, 0xd8, 0xbf, 0x8e, 0x47, 0xf7, 0x62, 0xd1, 0xf9, 0x18, 0x74, 0x23, 0xde, 0x1c, 0x8d, 0x37, 0xe7, 0xaa, 0x87, 0xcf, 0x98, 0x25, 0x4f, 0x3a, 0x4b, 0x5b, 0xab, 0x8b, 0x0f, 0xa8, 0x8b, 0xbd, 0xd4, 0xc5, 0x89, 0x0d, 0x7b, 0x05, 0x5b, 0xa8, 0x8d, 0xaf, 0xeb, 0xf1, 0xdf, 0xd0, 0xc3, 0x4f, 0xd4, 0xb7, 0xc7, 0xe9, 0xd9, 0x87, 0xaa, 0x95, 0x76, + 0x7d, 0xf9, 0x73, 0x66, 0xcd, 0x22, 0xfd, 0xf4, 0x27, 0x7a, 0xe8, 0x51, 0x7a, 0xe8, 0x0c, 0xfd, 0xe8, 0x2b, 0x7a, 0xcd, 0x55, 0xd6, 0x05, 0xb3, 0xd4, 0xd1, 0x02, 0x73, 0xfe, 0xc4, 0xe0, 0x32, 0x67, 0x70, 0x31, 0xf3, 0x1c, 0x61, 0x9e, 0x23, 0xcc, 0x73, 0x84, 0x79, 0x8e, 0x30, 0xcf, 0x11, 0xe6, 0x39, 0xc2, 0x3c, 0x4b, 0xcc, 0xb3, 0xc4, 0x3c, 0x4b, 0x95, 0x99, 0xf7, 0x71, 0xb5, 0x75, 0x98, 0xef, 0x7f, 0xc9, 0xd7, 0x17, 0x56, 0x7e, 0x8b, 0x7f, 0x44, 0x75, 0x8f, 0x30, 0xb8, 0x11, 0xe6, 0x56, 0x7a, 0x6b, 0x9c, 0x83, 0x7a, 0xa6, 0x35, 0xc2, 0xae, 0x46, 0xd8, 0x55, 0xa9, 0x32, 0x03, 0x6f, 0x73, 0x7b, 0x8f, 0x6c, 0x98, 0x89, 0x9e, 0xef, 0x6c, 0xf6, 0x95, 0x67, 0x1f, 0xe3, 0x19, 0x61, 0x3c, 0x25, 0x86, 0x33, 0xc2, 0x2c, 0x46, 0x98, 0xc5, 0x88, 0xb3, 0x33, 0xc2, 0x2c, 0x46, + 0x98, 0xc5, 0x08, 0xb3, 0x28, 0x31, 0x8b, 0x12, 0xb3, 0x28, 0x31, 0x8b, 0x12, 0xb3, 0x28, 0x39, 0x73, 0x23, 0xcc, 0x62, 0x84, 0x59, 0x8c, 0x38, 0x83, 0x23, 0xce, 0xe0, 0x08, 0xb3, 0x18, 0x71, 0x16, 0x47, 0x98, 0xc5, 0x08, 0xb3, 0x18, 0x61, 0x16, 0x23, 0xcc, 0x62, 0x84, 0x59, 0x8c, 0x38, 0xb3, 0x23, 0xcc, 0x62, 0x84, 0x59, 0x8c, 0x30, 0x8b, 0x11, 0x66, 0x31, 0xc2, 0x2c, 0x46, 0x98, 0xc5, 0x08, 0xb3, 0x18, 0x61, 0x16, 0x23, 0xcc, 0x62, 0x84, 0x59, 0x8c, 0x30, 0x8b, 0x11, 0xd4, 0x2f, 0xa1, 0x7e, 0x09, 0xf5, 0x4b, 0x46, 0xa1, 0x64, 0x14, 0x4a, 0xa8, 0x5f, 0x42, 0xfd, 0xd2, 0x86, 0x4e, 0xe0, 0x8c, 0x8f, 0xa0, 0xe6, 0x08, 0x6a, 0x8e, 0xa0, 0xe6, 0x88, 0xb3, 0x3f, 0x82, 0x9a, 0x23, 0x46, 0x60, 0x04, 0x35, 0x47, 0x50, 0x73, 0x04, 0x35, 0x47, 0x50, 0x73, 0xa4, 0xfa, 0x09, + 0x84, 0x23, 0xa8, 0x39, 0x82, 0x9a, 0x23, 0x46, 0x67, 0x04, 0x35, 0x47, 0x50, 0x73, 0x04, 0x35, 0x47, 0x50, 0x73, 0x04, 0x35, 0x47, 0x50, 0x73, 0x04, 0x35, 0x47, 0x50, 0x73, 0x04, 0x35, 0x47, 0x50, 0x73, 0xc4, 0xac, 0x1f, 0xd1, 0xa1, 0x47, 0x8c, 0xd6, 0x88, 0x0e, 0x3d, 0xa2, 0x43, 0x8f, 0xe8, 0xd0, 0x23, 0xba, 0x71, 0x29, 0x76, 0x67, 0x30, 0x2a, 0x7a, 0x20, 0xa8, 0x95, 0xad, 0xa3, 0xeb, 0x83, 0x3d, 0x8c, 0xca, 0x9e, 0xc2, 0x19, 0xcd, 0xf9, 0x62, 0xb0, 0x4f, 0xd4, 0x1d, 0x7c, 0x30, 0x1a, 0x0c, 0xc6, 0x46, 0x2f, 0xe9, 0x9d, 0x1d, 0xc1, 0xbe, 0xd1, 0x4a, 0x6b, 0x8e, 0xb8, 0x35, 0x47, 0xdc, 0x9a, 0x23, 0x6e, 0xcd, 0x91, 0x36, 0xf2, 0xad, 0x46, 0xbe, 0xd5, 0xc8, 0xb7, 0x1a, 0xf9, 0x56, 0x23, 0xdf, 0x6a, 0xe4, 0x5b, 0x8d, 0x7c, 0xdc, 0xc8, 0xc7, 0x8d, 0x7c, 0xdc, 0xa8, + 0xb7, 0x1a, 0xf5, 0x56, 0xce, 0xbf, 0xd4, 0xc8, 0xb7, 0x71, 0xfe, 0x34, 0xe7, 0x4f, 0x23, 0x40, 0x9a, 0xf3, 0xa7, 0x38, 0x7f, 0xca, 0xbc, 0x4b, 0x71, 0xfe, 0x14, 0xe7, 0x4f, 0x71, 0xfe, 0x34, 0xe7, 0xef, 0x32, 0x0f, 0xd3, 0x9c, 0x3f, 0xcd, 0xf9, 0xbb, 0x38, 0x7f, 0x17, 0xe7, 0xef, 0x52, 0x31, 0xad, 0x2a, 0xa6, 0x55, 0xc5, 0xb4, 0x72, 0xfe, 0x2e, 0xce, 0x9f, 0x56, 0x39, 0x71, 0xce, 0xdf, 0xc5, 0xf9, 0x57, 0x70, 0xfe, 0x15, 0x9c, 0x7f, 0x05, 0xe7, 0x5f, 0x61, 0x0e, 0xa7, 0x37, 0x7a, 0xfc, 0x0f, 0xcc, 0xf1, 0x3b, 0x10, 0x66, 0x7c, 0x34, 0x49, 0x27, 0xb8, 0x41, 0x27, 0xb8, 0x21, 0x76, 0xa4, 0xae, 0x70, 0x94, 0x4c, 0x30, 0x8f, 0x4f, 0xab, 0xfc, 0xe5, 0xe6, 0xd2, 0xca, 0xdf, 0x2a, 0xa7, 0xa3, 0xd6, 0x8a, 0xe7, 0x9f, 0x1d, 0x75, 0xd4, 0x7c, 0x1e, 0x79, 0x2e, 0x90, 0x8b, + 0xdc, 0xbf, 0xc4, 0xed, 0xe5, 0x2a, 0xf0, 0x0a, 0xb9, 0x2a, 0x6a, 0x55, 0x9d, 0x71, 0xee, 0xdf, 0xc5, 0xfd, 0xd3, 0xdc, 0xbf, 0xab, 0xe6, 0x26, 0x1d, 0xf5, 0x96, 0x68, 0xa9, 0x6a, 0x8d, 0xd7, 0xfc, 0xc0, 0xfd, 0x69, 0xd1, 0x0a, 0x55, 0x1b, 0xe7, 0xfd, 0x69, 0xde, 0x9f, 0xae, 0xf9, 0x99, 0xde, 0xe8, 0xf8, 0xb9, 0x7c, 0x5a, 0x8f, 0x0c, 0x37, 0xf6, 0xc8, 0x6d, 0xf8, 0x3a, 0xae, 0xab, 0xe4, 0x56, 0x95, 0x1c, 0xaf, 0xba, 0x7b, 0x17, 0x77, 0x4f, 0xab, 0xea, 0xd6, 0x31, 0x1f, 0x8a, 0x1e, 0x18, 0xb3, 0xaf, 0x7c, 0x58, 0xf6, 0x93, 0xfd, 0x65, 0x9c, 0x7c, 0x44, 0x3e, 0x2a, 0x1f, 0x93, 0xf1, 0x72, 0xb0, 0x1c, 0x22, 0x87, 0xca, 0xc7, 0xe5, 0x30, 0xf9, 0x84, 0x1c, 0x2e, 0x47, 0xc8, 0x91, 0x72, 0x94, 0x1c, 0x2d, 0xc7, 0xc8, 0xb1, 0xf2, 0x49, 0x99, 0x20, 0xc7, 0xc9, 0xa7, 0xe4, + 0x3c, 0xe9, 0x89, 0xae, 0x1f, 0x93, 0x8d, 0xae, 0xaf, 0x9f, 0x60, 0xa6, 0x1c, 0x27, 0x9f, 0x92, 0xe3, 0xe5, 0x44, 0x39, 0x49, 0x4e, 0x96, 0x89, 0x72, 0x8a, 0x9c, 0xca, 0xe7, 0x4f, 0x93, 0xcf, 0xc8, 0x99, 0xf2, 0x59, 0xf9, 0x9c, 0x9c, 0x25, 0x97, 0x47, 0xdd, 0xf5, 0x57, 0xc8, 0x95, 0xd1, 0x60, 0xfd, 0x55, 0x32, 0x49, 0xbe, 0x29, 0x57, 0xcb, 0x35, 0x72, 0x5d, 0xf4, 0x52, 0xfd, 0xb7, 0xe5, 0xfa, 0xa8, 0x03, 0x3f, 0x3b, 0xf0, 0xb3, 0x03, 0x3f, 0x3b, 0xf0, 0xb3, 0x03, 0x3f, 0x3b, 0xf0, 0xb3, 0x03, 0x3f, 0x3b, 0xf0, 0xb3, 0xa3, 0xfe, 0x2e, 0xd4, 0xbf, 0x5b, 0xee, 0x91, 0x1f, 0xcb, 0x4f, 0xe4, 0x5e, 0xb9, 0x2f, 0x8a, 0x5b, 0xd3, 0xc4, 0xad, 0x69, 0xe2, 0x7a, 0x68, 0x5c, 0x0f, 0x8d, 0xeb, 0xa1, 0x71, 0x3d, 0x34, 0x6e, 0x4d, 0x13, 0xb7, 0xa6, 0x89, 0x5b, 0xd3, 0xc4, 0xf5, + 0xd3, 0xb8, 0x35, 0x4d, 0x9c, 0x39, 0xc4, 0xad, 0x69, 0xe2, 0xd6, 0x34, 0x71, 0x6b, 0x9a, 0xb8, 0xfe, 0x1a, 0xd7, 0x5f, 0xe3, 0xfa, 0x6b, 0x5c, 0x7f, 0x8d, 0xeb, 0xaf, 0x71, 0xfd, 0x35, 0x6e, 0x4d, 0x13, 0xb7, 0xa6, 0x89, 0x5b, 0xd3, 0xc4, 0xad, 0x69, 0xe2, 0xd6, 0x34, 0x71, 0x6b, 0x9a, 0xb8, 0x35, 0x4d, 0xdc, 0x9a, 0x26, 0x6e, 0x4d, 0x13, 0xb7, 0xa6, 0x49, 0x5b, 0xd3, 0xa4, 0xad, 0x69, 0xd2, 0x3a, 0x4f, 0xab, 0xce, 0xd3, 0xaa, 0xf3, 0xb4, 0xea, 0x3c, 0xad, 0x3a, 0x4f, 0xab, 0xce, 0x13, 0xd7, 0x79, 0xe2, 0x3a, 0x4f, 0x5c, 0xe7, 0x89, 0xeb, 0x3c, 0x71, 0x9d, 0xa7, 0x55, 0xe7, 0x69, 0xd5, 0x79, 0x5a, 0x75, 0x9e, 0x56, 0x9d, 0xa7, 0x55, 0xe7, 0x69, 0xd5, 0x79, 0x5a, 0x75, 0x9e, 0x56, 0x9d, 0xa7, 0x55, 0xe7, 0x69, 0xd5, 0x79, 0x5a, 0x75, 0x9e, 0x56, 0x9d, 0xa7, 0x55, + 0xe7, 0x69, 0xd5, 0x79, 0x5a, 0x75, 0x9e, 0x56, 0x9d, 0xa7, 0x55, 0xe7, 0x69, 0xd5, 0x79, 0x5a, 0x75, 0x9e, 0x56, 0x9d, 0xa7, 0x55, 0xe7, 0x69, 0xd5, 0x79, 0x5a, 0x2b, 0x9f, 0xef, 0x1a, 0x45, 0x4b, 0xf5, 0xff, 0xa5, 0xd6, 0x22, 0x4b, 0xad, 0x45, 0x96, 0xe2, 0xc0, 0x52, 0x6b, 0x91, 0xa5, 0xd6, 0x22, 0x4b, 0x75, 0xa5, 0x36, 0x5d, 0xa9, 0x4d, 0x57, 0x6a, 0xd3, 0x95, 0xda, 0x74, 0xa5, 0x36, 0x5d, 0xa9, 0x4d, 0x57, 0x6a, 0xb3, 0x16, 0x49, 0x5b, 0x8b, 0xa4, 0xad, 0x45, 0xd2, 0xd6, 0x22, 0x69, 0x6b, 0x91, 0xb4, 0xb5, 0x48, 0xda, 0x5a, 0x24, 0x65, 0x2d, 0x92, 0xb2, 0x16, 0x49, 0x59, 0x8b, 0xa4, 0x70, 0x24, 0xc5, 0x12, 0x52, 0xd6, 0x22, 0x29, 0x6b, 0x91, 0x94, 0xb5, 0x48, 0xca, 0x5a, 0x24, 0x65, 0x2d, 0x92, 0xb2, 0x16, 0x49, 0x59, 0x8b, 0xa4, 0xac, 0x45, 0x52, 0xd6, 0x22, + 0x29, 0x6b, 0x91, 0x14, 0x9b, 0x48, 0x59, 0x8b, 0xa4, 0xad, 0x45, 0xd2, 0xd6, 0x22, 0x69, 0x6b, 0x91, 0xb4, 0xb5, 0x48, 0xda, 0x5a, 0x24, 0x6d, 0x2d, 0x92, 0xb6, 0x16, 0x49, 0x5b, 0x8b, 0xa4, 0xad, 0x45, 0xd2, 0xd6, 0x22, 0x69, 0x6b, 0x91, 0xb4, 0xb5, 0x48, 0xda, 0x5a, 0x24, 0x6d, 0x2d, 0xd2, 0x65, 0x2d, 0xd2, 0x65, 0x2d, 0xd2, 0x65, 0x2d, 0xd2, 0x85, 0x53, 0x5d, 0xd6, 0x22, 0x5d, 0xd6, 0x22, 0x5d, 0xd6, 0x22, 0x5d, 0xd6, 0x22, 0x5d, 0xd6, 0x22, 0x5d, 0xd6, 0x22, 0x5d, 0xd6, 0x22, 0x5d, 0xd6, 0x22, 0x5d, 0xba, 0x6a, 0xab, 0xae, 0xda, 0xaa, 0xab, 0xb6, 0xea, 0xaa, 0xad, 0xba, 0x6a, 0xab, 0xae, 0xda, 0xaa, 0xab, 0xb6, 0xea, 0xaa, 0xad, 0xba, 0x6a, 0xab, 0xae, 0xda, 0xaa, 0xab, 0xb6, 0xea, 0xaa, 0xad, 0xba, 0x6a, 0xab, 0xae, 0xda, 0xaa, 0xab, 0xb6, 0xea, 0xaa, 0xad, + 0xba, 0x6a, 0xab, 0xae, 0xda, 0xaa, 0xab, 0xb6, 0xea, 0xaa, 0xad, 0xba, 0x6a, 0xab, 0xae, 0xda, 0xaa, 0xab, 0xb6, 0xea, 0xaa, 0xad, 0xba, 0x6a, 0xab, 0xae, 0xda, 0xaa, 0xab, 0xb6, 0x5a, 0x8b, 0x74, 0x59, 0x8b, 0x74, 0x59, 0x8b, 0x74, 0x61, 0x65, 0x97, 0xb5, 0x48, 0x97, 0xb5, 0x48, 0x97, 0xb5, 0x48, 0x97, 0xb5, 0x48, 0x17, 0x76, 0x76, 0x59, 0x8b, 0x74, 0x59, 0x8b, 0x74, 0x59, 0x8b, 0xac, 0xc0, 0xd1, 0x15, 0x38, 0xba, 0xc2, 0x5a, 0x64, 0x85, 0xb5, 0xc8, 0x0a, 0x6b, 0x91, 0x15, 0xd6, 0x22, 0x2b, 0xac, 0x45, 0x56, 0x58, 0x8b, 0xac, 0xf8, 0x2f, 0xf3, 0xfd, 0x55, 0xac, 0x6e, 0xb5, 0xf4, 0x45, 0x79, 0xec, 0xce, 0x37, 0x0c, 0xc8, 0xa0, 0xbc, 0x21, 0xeb, 0xa3, 0x3c, 0x87, 0xef, 0x42, 0x88, 0x56, 0x84, 0x68, 0x45, 0x88, 0x56, 0x84, 0x68, 0x45, 0x88, 0xd6, 0x77, 0x71, 0xf8, + 0x78, 0x50, 0x5b, 0x7d, 0xa7, 0x87, 0x82, 0x2e, 0x5a, 0x28, 0x5b, 0x4d, 0xac, 0x19, 0x47, 0x96, 0xe0, 0xc8, 0x92, 0x60, 0x4c, 0xf4, 0x46, 0x50, 0x2f, 0x9b, 0xcb, 0xd6, 0xd1, 0x5c, 0x6e, 0x55, 0x0a, 0xfe, 0xc5, 0xfd, 0x9d, 0x64, 0x67, 0xd9, 0x4d, 0xf6, 0xc0, 0x94, 0x3d, 0xe5, 0x83, 0xfc, 0x72, 0x6c, 0xd4, 0x87, 0x2b, 0x03, 0xb8, 0xd2, 0x8c, 0x2b, 0xcd, 0xb8, 0xd2, 0x8c, 0x2b, 0xdd, 0x6c, 0x61, 0x69, 0xf0, 0x89, 0x28, 0x17, 0x1c, 0x2e, 0x47, 0xc8, 0x91, 0x72, 0x94, 0x1c, 0x2d, 0xc7, 0xc8, 0xb1, 0xf2, 0x49, 0x99, 0x20, 0xc7, 0xc9, 0xa7, 0xe4, 0x78, 0x39, 0x41, 0x4e, 0x94, 0x93, 0xe4, 0x64, 0x99, 0x28, 0xa7, 0xc8, 0xa9, 0x72, 0x9a, 0x7c, 0x5a, 0x4e, 0x97, 0xcf, 0x48, 0xf9, 0x1d, 0x39, 0xce, 0x94, 0xcf, 0xca, 0xe7, 0xe4, 0x2c, 0x39, 0x5b, 0x3e, 0x2f, 0x5f, 0xa8, 0xfc, + 0x3b, 0x58, 0x37, 0x4e, 0x75, 0xe3, 0x54, 0x37, 0x4e, 0xad, 0xc1, 0xa9, 0x35, 0x38, 0xb5, 0x8a, 0xb9, 0xf4, 0x60, 0xd5, 0x1a, 0xac, 0x5a, 0x83, 0x55, 0xdd, 0x58, 0xd5, 0x89, 0x55, 0xdd, 0x58, 0xd5, 0x8d, 0x55, 0x9d, 0x58, 0xd5, 0x89, 0x55, 0x9d, 0xf8, 0xd4, 0x89, 0x4f, 0xdd, 0xd8, 0xd4, 0x89, 0x45, 0xab, 0x82, 0x6f, 0x7b, 0xcd, 0xeb, 0x1d, 0xff, 0x0d, 0xf2, 0x1d, 0xf9, 0xae, 0xdc, 0x28, 0x37, 0xc9, 0xf7, 0xe4, 0x66, 0xf9, 0xbe, 0x4c, 0x96, 0x5b, 0x3c, 0xe7, 0x56, 0xb9, 0xcd, 0xb6, 0x6e, 0x8f, 0x7a, 0xf1, 0x6b, 0x55, 0xf0, 0x43, 0xf7, 0xef, 0xb0, 0x0e, 0x28, 0x3b, 0xb8, 0xf3, 0xe3, 0xec, 0x2f, 0xc5, 0xb0, 0x4e, 0x0c, 0xeb, 0xc4, 0xb0, 0xd7, 0x63, 0xc9, 0xe8, 0x0d, 0xdc, 0xea, 0xac, 0x39, 0x96, 0x8f, 0x9f, 0x28, 0xa7, 0xc9, 0xe7, 0x79, 0xf9, 0x39, 0x6e, 0x2f, 0x70, + 0x7b, 0x89, 0xe8, 0xc9, 0xf8, 0xd5, 0x8d, 0x5b, 0x9d, 0xb8, 0xd5, 0x8d, 0x5b, 0x9d, 0x35, 0xf6, 0xa5, 0xc6, 0xbe, 0xe0, 0x57, 0x07, 0xdb, 0x1a, 0xc0, 0xae, 0x8e, 0x9a, 0x1f, 0x71, 0xf9, 0xe9, 0x7e, 0xfe, 0x53, 0xf9, 0x99, 0xaf, 0x1f, 0x8c, 0x72, 0x35, 0xbf, 0x94, 0x19, 0xf2, 0x2b, 0xf9, 0xb5, 0xcc, 0x94, 0x87, 0xe4, 0x37, 0xf2, 0x5b, 0x79, 0x58, 0x7e, 0xe7, 0x75, 0x1e, 0x91, 0xc7, 0xa2, 0xd7, 0x98, 0xda, 0x32, 0xac, 0xeb, 0x2e, 0x57, 0x08, 0xc6, 0x75, 0xd7, 0x3d, 0x1b, 0xe5, 0xea, 0xe6, 0xca, 0xbc, 0xca, 0x6a, 0x63, 0xa0, 0x6e, 0x91, 0xfb, 0x8b, 0x25, 0x2e, 0xad, 0xb2, 0x44, 0x5e, 0x8f, 0xde, 0xc0, 0xbf, 0x4e, 0xfc, 0xeb, 0xc4, 0xbf, 0xee, 0xd1, 0x7b, 0x47, 0x6f, 0xe0, 0xdf, 0x12, 0xfc, 0x5b, 0x82, 0x7f, 0x4b, 0xf0, 0x6f, 0x09, 0xfe, 0x2d, 0xc1, 0xbf, 0x25, 0xf8, + 0xb7, 0x04, 0xff, 0x96, 0xe0, 0xdf, 0x12, 0xfc, 0x5b, 0x82, 0x7f, 0x4b, 0xf0, 0x6f, 0x09, 0xfe, 0x2d, 0xc1, 0xbf, 0x25, 0xf8, 0xb7, 0x04, 0xff, 0x96, 0xe0, 0xdf, 0x12, 0xfc, 0x5b, 0x82, 0x7f, 0x4b, 0xf0, 0x6f, 0x09, 0xfe, 0x2d, 0xc1, 0xbf, 0x25, 0xf8, 0xb7, 0x04, 0xff, 0x96, 0xe0, 0xdf, 0x12, 0xfc, 0x5b, 0x82, 0x7f, 0x4b, 0xf0, 0x6f, 0xc9, 0x98, 0xaf, 0xdb, 0xe6, 0x25, 0xf2, 0x0d, 0xb9, 0x54, 0xae, 0x94, 0x5f, 0xcb, 0x4c, 0x79, 0x48, 0x7e, 0x23, 0xbf, 0x95, 0x87, 0xc5, 0xf1, 0xe2, 0xe5, 0x5c, 0xbc, 0x9c, 0x5b, 0xbf, 0x59, 0xf4, 0x46, 0xbd, 0xba, 0xaf, 0xdf, 0x42, 0xb6, 0x94, 0xad, 0xe4, 0xbd, 0xb2, 0xb5, 0x6c, 0x23, 0xef, 0x93, 0x1d, 0x64, 0x47, 0x31, 0x27, 0xea, 0xcd, 0x89, 0x7a, 0x73, 0xa2, 0x7e, 0x17, 0xd9, 0x55, 0xcc, 0x8d, 0xfa, 0xdd, 0x65, 0x0f, 0xd9, 0x53, + 0xde, 0x2f, 0x7b, 0x89, 0x73, 0x50, 0xff, 0x01, 0xd9, 0x47, 0x3e, 0x28, 0x63, 0xe5, 0x00, 0x99, 0x80, 0xa1, 0xc7, 0xc9, 0xa7, 0xe4, 0x78, 0x39, 0x51, 0x4e, 0x92, 0x93, 0x65, 0xa2, 0x9c, 0x22, 0x57, 0x5a, 0x67, 0x5d, 0x25, 0x93, 0xe4, 0x9b, 0x72, 0xb5, 0x5c, 0x23, 0xd7, 0x45, 0x7d, 0xd8, 0xdb, 0x87, 0xab, 0x03, 0xb8, 0x3a, 0x80, 0xab, 0x03, 0xb8, 0x3a, 0x80, 0xab, 0x03, 0xb8, 0x3a, 0x80, 0xab, 0xcd, 0xb8, 0xda, 0x8c, 0xab, 0xcd, 0xb8, 0xda, 0x8c, 0xab, 0xcd, 0xb8, 0xda, 0x8c, 0xab, 0xcd, 0xb8, 0xda, 0x8c, 0xab, 0xcd, 0xb8, 0xda, 0x8c, 0xab, 0xcd, 0xb8, 0xda, 0x8c, 0xab, 0xab, 0x70, 0xb5, 0x19, 0x57, 0x9b, 0x71, 0xb5, 0x19, 0x57, 0x9b, 0x71, 0xb5, 0x19, 0x57, 0x9b, 0x71, 0xb5, 0x19, 0x57, 0x9b, 0x71, 0xb5, 0x19, 0x57, 0x9b, 0x71, 0xb5, 0x19, 0x57, 0x9b, 0x71, 0xb5, + 0x19, 0x57, 0x9b, 0x71, 0xb5, 0x19, 0x57, 0x9b, 0x71, 0xb5, 0x19, 0x57, 0x9b, 0x71, 0xb5, 0x1b, 0x57, 0xbb, 0x71, 0xb5, 0xbb, 0x3e, 0x5b, 0xf9, 0xc4, 0xaf, 0x5c, 0x7d, 0x5e, 0x7a, 0xa5, 0x20, 0xeb, 0xa4, 0x4f, 0xfa, 0x65, 0x40, 0x06, 0xe5, 0x0d, 0x59, 0x2f, 0x43, 0x52, 0x94, 0x61, 0x29, 0xc9, 0x88, 0xbc, 0x59, 0xf9, 0xb7, 0xb9, 0x6e, 0x7c, 0xeb, 0xc6, 0xb7, 0x6e, 0x7c, 0xeb, 0xc6, 0xb7, 0x6e, 0x7c, 0xeb, 0xc6, 0xb7, 0x35, 0xf8, 0xb6, 0x06, 0xdf, 0xd6, 0xe0, 0xdb, 0x1a, 0x7c, 0x5b, 0x85, 0x6f, 0x11, 0xbe, 0xad, 0xc1, 0xb7, 0x35, 0xf8, 0xb6, 0x06, 0xdf, 0xd6, 0xe0, 0xdb, 0x1a, 0x7c, 0x7b, 0x13, 0xdf, 0xd6, 0xe0, 0xdb, 0x1a, 0x7c, 0x5b, 0x83, 0x6f, 0x6b, 0xac, 0x3a, 0x6b, 0xf1, 0xad, 0x1b, 0xdf, 0xba, 0xf1, 0xad, 0x1b, 0xdf, 0xba, 0xf1, 0xad, 0x1b, 0xdf, 0xba, 0xf1, + 0xad, 0x1b, 0xdf, 0xba, 0xf1, 0xad, 0x1b, 0xdf, 0xba, 0xf1, 0xad, 0x1b, 0xdf, 0xba, 0xf1, 0xad, 0x1b, 0xdf, 0xba, 0xf1, 0xad, 0x13, 0xdf, 0x3a, 0xf1, 0xad, 0x13, 0xdf, 0x3a, 0xf1, 0xad, 0x13, 0xdf, 0x3a, 0xf1, 0xad, 0x13, 0xdf, 0x3a, 0xf1, 0xad, 0x13, 0xdf, 0x3a, 0xf1, 0xad, 0x13, 0xdf, 0x3a, 0xf1, 0xad, 0x13, 0x73, 0x3a, 0x31, 0xa7, 0x13, 0x73, 0x3a, 0x31, 0xa7, 0x13, 0x73, 0x3a, 0x31, 0xa7, 0x13, 0x73, 0x3a, 0x31, 0xa7, 0x13, 0x73, 0x3a, 0x31, 0xa7, 0x13, 0x73, 0x3a, 0x1b, 0xee, 0x8d, 0xde, 0x68, 0xb8, 0x4f, 0xa6, 0xcb, 0x4f, 0xe5, 0x67, 0xf2, 0x73, 0xb9, 0x5f, 0x1e, 0x90, 0x5f, 0xc8, 0x83, 0xf2, 0x4b, 0x99, 0x21, 0xbf, 0x12, 0x75, 0xde, 0xa0, 0xce, 0x1b, 0xd4, 0x79, 0x83, 0x3a, 0x6f, 0x50, 0xe7, 0x0d, 0xea, 0xbc, 0x41, 0x9d, 0x37, 0x98, 0xd7, 0x0d, 0x8f, 0xca, + 0x2c, 0xf9, 0xbd, 0xfc, 0x41, 0x1e, 0x93, 0xc7, 0xe5, 0x8f, 0xf2, 0xaf, 0xf2, 0x27, 0x79, 0x42, 0xfe, 0x2c, 0x4f, 0xca, 0x53, 0xf2, 0xb4, 0x3c, 0x23, 0x7f, 0x91, 0xd9, 0x32, 0x47, 0xfe, 0x6a, 0xff, 0x9e, 0x95, 0xb9, 0xf2, 0x37, 0x79, 0x4e, 0xe6, 0xc9, 0x7c, 0x79, 0x5e, 0x5e, 0x90, 0x66, 0x59, 0x28, 0x2d, 0xf2, 0xa2, 0xbc, 0x24, 0x8b, 0x64, 0xb1, 0xc4, 0xa5, 0x55, 0x96, 0x48, 0x9b, 0xbc, 0x2c, 0xed, 0xd2, 0x11, 0xf5, 0x34, 0x2c, 0x93, 0xce, 0xa8, 0xb7, 0xe1, 0x55, 0x59, 0x2e, 0xaf, 0xc9, 0xeb, 0xb2, 0x42, 0x56, 0xca, 0x2a, 0x63, 0xbc, 0x5a, 0xd6, 0x78, 0x5c, 0xb7, 0x24, 0x25, 0x25, 0x3d, 0x92, 0x91, 0xac, 0x84, 0x92, 0x93, 0xbc, 0xf4, 0x45, 0x1d, 0xb8, 0xd9, 0x81, 0x9b, 0x1d, 0xb8, 0xd9, 0x81, 0x9b, 0x1d, 0xb8, 0xd9, 0x81, 0x9b, 0x9d, 0x8d, 0x3d, 0xd1, 0x1b, 0x8d, + 0x19, 0xc9, 0x4a, 0x28, 0x39, 0x19, 0xf2, 0xfd, 0xa2, 0x0c, 0x4b, 0x49, 0x46, 0xf4, 0xe7, 0x19, 0xd6, 0xc9, 0xe7, 0x54, 0xae, 0x34, 0x7c, 0x38, 0x3a, 0x05, 0xe9, 0x4e, 0x41, 0xba, 0x53, 0xac, 0x9e, 0xc6, 0x5a, 0x3d, 0x8d, 0xb5, 0x7a, 0x1a, 0x6b, 0xf5, 0x34, 0xd6, 0xea, 0x69, 0xac, 0xd5, 0xd3, 0x58, 0xab, 0xa7, 0xf1, 0x56, 0x4f, 0xe3, 0xad, 0x9e, 0xc6, 0x57, 0xae, 0x4c, 0x7c, 0xdc, 0xf7, 0x0e, 0x93, 0x4f, 0x44, 0x09, 0x44, 0x4c, 0x20, 0x62, 0x02, 0x11, 0x13, 0x88, 0x98, 0x40, 0xc4, 0x04, 0x22, 0x26, 0x10, 0x31, 0x81, 0x88, 0x09, 0x44, 0x4c, 0x20, 0x62, 0x02, 0x11, 0x13, 0x88, 0x98, 0x40, 0xc4, 0x04, 0x22, 0x26, 0x10, 0x31, 0x81, 0x88, 0x09, 0x44, 0x4c, 0x20, 0x62, 0x02, 0x11, 0x13, 0x88, 0x98, 0x40, 0xc4, 0x04, 0x22, 0x26, 0x10, 0x31, 0x81, 0x88, 0x09, 0x44, 0x4c, + 0x20, 0x62, 0x02, 0x11, 0x13, 0x95, 0x2b, 0x22, 0x67, 0xbb, 0xfd, 0xbc, 0x7c, 0x41, 0xbe, 0x18, 0x9d, 0x6a, 0x05, 0x77, 0x48, 0xe5, 0x2a, 0xc9, 0xd7, 0xac, 0x1a, 0xcb, 0x57, 0x4a, 0x2e, 0x71, 0xfb, 0x0d, 0xb9, 0x54, 0x2e, 0xb3, 0x8f, 0x97, 0xcb, 0x15, 0x72, 0xa5, 0xaf, 0x27, 0xd9, 0xff, 0x6f, 0xba, 0xbd, 0xda, 0xe3, 0xaf, 0x91, 0x6b, 0xe5, 0x5b, 0x72, 0x5d, 0x74, 0x3f, 0x1a, 0x26, 0xd0, 0x30, 0x87, 0x86, 0x39, 0x34, 0xcc, 0xa1, 0x61, 0x0e, 0x0d, 0x73, 0x68, 0x98, 0x43, 0xc3, 0x1c, 0x1a, 0xe6, 0xd0, 0x30, 0x87, 0x86, 0x39, 0x34, 0x9c, 0x81, 0x86, 0x33, 0xd0, 0x30, 0x87, 0x86, 0x79, 0x34, 0xec, 0xad, 0xfe, 0x55, 0xfe, 0xd3, 0xef, 0x72, 0x95, 0x26, 0x16, 0x3b, 0x5d, 0xce, 0x90, 0x7f, 0xbb, 0x4a, 0xb3, 0x28, 0x76, 0x3e, 0x72, 0x5e, 0x10, 0xd4, 0x58, 0xe1, 0x8d, 0x45, + 0xca, 0x19, 0xff, 0xc7, 0xd5, 0x9a, 0xab, 0xa2, 0xb1, 0x56, 0x75, 0xe3, 0xd1, 0xf1, 0x01, 0x64, 0x7c, 0x00, 0x19, 0x73, 0xc8, 0x98, 0x43, 0xc6, 0xa7, 0xad, 0xec, 0x4e, 0xad, 0x5c, 0xcd, 0xb9, 0xcd, 0xcf, 0x7f, 0xe0, 0xeb, 0x69, 0xd1, 0x19, 0x56, 0x77, 0xe3, 0x2b, 0x7f, 0xc9, 0xff, 0x33, 0x5f, 0x3f, 0x18, 0x25, 0x10, 0x32, 0x81, 0x90, 0x09, 0x84, 0x4c, 0x20, 0x64, 0x02, 0x21, 0x13, 0x08, 0x99, 0x40, 0xc8, 0x04, 0x42, 0x26, 0x10, 0x32, 0x81, 0x90, 0x39, 0x84, 0xcc, 0x6d, 0xbc, 0x2a, 0xe4, 0x7c, 0xfe, 0xdd, 0x95, 0xa1, 0x67, 0xa3, 0x04, 0x4a, 0x26, 0x2a, 0x57, 0x88, 0x16, 0xb9, 0xad, 0x5e, 0x25, 0x42, 0xc8, 0x04, 0x42, 0x26, 0xac, 0x12, 0xc7, 0x5a, 0x25, 0x8e, 0x47, 0xc9, 0x19, 0x28, 0xf9, 0x00, 0x42, 0xe6, 0xac, 0x12, 0xc7, 0x56, 0xae, 0x22, 0x4d, 0x8f, 0x4e, 0xd1, + 0xb1, 0x4f, 0xf9, 0x3f, 0xae, 0x26, 0xfd, 0xc2, 0xf7, 0x1e, 0x94, 0x19, 0x52, 0xbe, 0xb2, 0xf4, 0x6b, 0xb7, 0xe5, 0xab, 0x4b, 0x0f, 0xb9, 0xfd, 0x8d, 0xfc, 0x56, 0xde, 0xed, 0x4a, 0xd3, 0x1f, 0xfc, 0xec, 0x31, 0x79, 0x5c, 0xfe, 0x28, 0x7f, 0x96, 0x27, 0xe5, 0x29, 0x79, 0x5a, 0x9e, 0x91, 0xb9, 0xb6, 0xff, 0x37, 0x29, 0x5f, 0x95, 0x9a, 0xe7, 0xf6, 0x05, 0x69, 0x8e, 0xc6, 0x5b, 0xfd, 0x8c, 0xb7, 0xfa, 0x19, 0x6f, 0xf5, 0x33, 0xde, 0xea, 0x67, 0x7c, 0xe5, 0x8a, 0x55, 0xab, 0x9f, 0x2d, 0x91, 0x0d, 0x57, 0xae, 0x5e, 0x76, 0xbf, 0x7c, 0xf5, 0xaa, 0xc3, 0xed, 0xab, 0xb2, 0x5c, 0x5e, 0x93, 0xd7, 0xa5, 0x7c, 0x45, 0x6b, 0xa5, 0xdb, 0x2e, 0x71, 0xdc, 0x56, 0x3f, 0x63, 0xad, 0x7e, 0xc6, 0x5a, 0xfd, 0x8c, 0xb5, 0xfa, 0x19, 0x6b, 0xf5, 0x33, 0xd6, 0xea, 0x67, 0xac, 0xd5, 0xcf, + 0x58, 0xab, 0x9f, 0xb1, 0x68, 0x91, 0x40, 0x8b, 0x04, 0x5a, 0x24, 0xd0, 0x22, 0x81, 0x16, 0x09, 0xb4, 0x48, 0xa0, 0x45, 0x02, 0x2d, 0x12, 0x68, 0x91, 0x40, 0x8b, 0x04, 0x5a, 0x24, 0xd0, 0x22, 0x51, 0x5f, 0xfe, 0x57, 0xb1, 0xa2, 0x0c, 0x4b, 0x49, 0x46, 0xe4, 0xcd, 0xe8, 0x5e, 0xab, 0xa8, 0x53, 0x2b, 0x57, 0xd1, 0x62, 0x6e, 0x6b, 0xa4, 0x7c, 0x35, 0xad, 0xd6, 0x6d, 0x9d, 0x8c, 0x8e, 0x0e, 0xb1, 0x8a, 0x3a, 0xc4, 0x2a, 0xea, 0x90, 0x8d, 0x57, 0xd8, 0x36, 0x77, 0x7f, 0x0b, 0xf9, 0x8f, 0xae, 0xb4, 0x59, 0x71, 0xeb, 0xf8, 0x0f, 0xe8, 0xf8, 0x0f, 0xe8, 0xf8, 0x0f, 0x54, 0xae, 0xbc, 0x9d, 0xe8, 0xf6, 0x24, 0x39, 0x59, 0x26, 0xca, 0x29, 0x72, 0xaa, 0x9c, 0x26, 0xa7, 0x4b, 0xf9, 0xca, 0xdc, 0x19, 0xd1, 0x58, 0x2b, 0x9a, 0xb1, 0x56, 0x34, 0x63, 0x2b, 0x57, 0xe9, 0xce, 0x72, + 0x5b, 0xbe, 0x52, 0xf7, 0x79, 0xb7, 0xe7, 0xc8, 0xb9, 0x72, 0x9e, 0x94, 0xaf, 0xdc, 0x9d, 0xef, 0xf6, 0x02, 0xb9, 0x50, 0xca, 0x57, 0xf1, 0xbe, 0xea, 0xf6, 0x22, 0xf9, 0x9a, 0x5c, 0x2c, 0x5f, 0x97, 0x4b, 0xe4, 0x1b, 0x72, 0xa9, 0x5c, 0x2b, 0xdf, 0x92, 0xeb, 0x44, 0xcd, 0xa3, 0xcb, 0x03, 0xe8, 0xf2, 0x40, 0xe5, 0xea, 0xdf, 0xcd, 0x6e, 0xbf, 0x2f, 0x93, 0xe5, 0x16, 0x29, 0x5f, 0x0d, 0xbc, 0xdd, 0xed, 0x0f, 0xe4, 0x87, 0xd1, 0x19, 0x1b, 0xaf, 0x0c, 0x4e, 0x71, 0xdf, 0x7c, 0xb0, 0xa2, 0x39, 0xc3, 0x8a, 0xe6, 0x0c, 0x2b, 0x9a, 0x33, 0xac, 0x68, 0xce, 0x40, 0xa0, 0x1c, 0x02, 0xe5, 0x10, 0x28, 0x87, 0x40, 0x39, 0x04, 0xca, 0x21, 0x50, 0x0e, 0x81, 0x72, 0x08, 0x94, 0x43, 0xa0, 0x1c, 0x02, 0xe5, 0x10, 0x28, 0x87, 0x40, 0x39, 0x04, 0xca, 0x21, 0x50, 0x0e, 0x81, 0x72, 0x08, + 0x94, 0x43, 0xa0, 0x1c, 0x02, 0xe5, 0x10, 0x28, 0x87, 0x40, 0x39, 0x04, 0xca, 0x21, 0x50, 0x0e, 0x81, 0x72, 0x08, 0x94, 0x43, 0xa0, 0x1c, 0x02, 0xe5, 0x10, 0x28, 0x87, 0x40, 0x39, 0x04, 0xca, 0x21, 0x50, 0x0e, 0x81, 0x72, 0x08, 0x94, 0x43, 0xa0, 0x1c, 0x02, 0xe5, 0x10, 0x28, 0x87, 0x40, 0x39, 0x04, 0xca, 0x21, 0x50, 0x0e, 0x81, 0x72, 0x95, 0xab, 0x99, 0xcf, 0xda, 0xf7, 0xb9, 0xf2, 0x37, 0x79, 0x4e, 0xe6, 0xc9, 0x7c, 0x79, 0x5e, 0x5e, 0x90, 0x66, 0x29, 0x5f, 0xf1, 0x6c, 0x71, 0xfb, 0xa2, 0xbc, 0x24, 0x8b, 0x64, 0xb1, 0xc4, 0xa5, 0x55, 0x96, 0x48, 0x9b, 0xbc, 0x2c, 0xed, 0xd2, 0xe1, 0xb5, 0x97, 0x49, 0xa7, 0xd5, 0xd5, 0xab, 0xb2, 0x5c, 0x5e, 0x93, 0xd7, 0x65, 0x85, 0xac, 0x2c, 0xbf, 0xfb, 0x25, 0x0a, 0xad, 0x96, 0x7f, 0xfc, 0x2f, 0xde, 0x9f, 0x2e, 0x5f, 0x75, 0x45, + 0xa0, 0xa7, 0x11, 0xe8, 0x69, 0x04, 0x7a, 0x1a, 0x81, 0x9e, 0x46, 0xa0, 0x07, 0x2a, 0x57, 0x62, 0xfb, 0xa3, 0xb1, 0xe5, 0xab, 0xb1, 0x56, 0x6f, 0x63, 0xad, 0xde, 0xc6, 0x5a, 0xbd, 0x8d, 0x45, 0xa1, 0x19, 0x28, 0x34, 0x03, 0x85, 0x66, 0xa0, 0xd0, 0x0c, 0x14, 0x9a, 0x61, 0xf5, 0x36, 0x3e, 0xd8, 0xaa, 0xb2, 0x26, 0x2b, 0xaf, 0xaf, 0x36, 0xac, 0x65, 0xac, 0x4d, 0x74, 0xd1, 0x97, 0x75, 0xcf, 0x64, 0xc5, 0xff, 0x37, 0xb8, 0x7b, 0xd9, 0xdb, 0x79, 0x7a, 0xd9, 0xed, 0x36, 0x7a, 0x51, 0xd9, 0x81, 0x38, 0x4f, 0xb0, 0x63, 0xd0, 0x18, 0xad, 0x0f, 0xb6, 0x94, 0xed, 0x64, 0xaf, 0xe8, 0x15, 0xaf, 0xf8, 0x8c, 0x57, 0x5b, 0x5e, 0xfe, 0xd7, 0x0a, 0xbd, 0xb4, 0xb7, 0xe6, 0xc2, 0x28, 0xac, 0xf9, 0x6a, 0xb4, 0xbe, 0xe6, 0x96, 0x60, 0x1f, 0xaf, 0xfa, 0x8a, 0x57, 0xcd, 0x7a, 0xd5, 0x55, + 0x75, 0xbb, 0x44, 0xeb, 0xbd, 0xea, 0x2b, 0x75, 0x2b, 0xdd, 0x76, 0x45, 0xeb, 0xc7, 0x78, 0xcc, 0x98, 0xeb, 0xe4, 0xdb, 0x32, 0x10, 0xad, 0xaf, 0x3f, 0x24, 0x5a, 0x6f, 0x4b, 0x23, 0xb6, 0x12, 0x2b, 0xff, 0x8b, 0x45, 0xe5, 0xdf, 0x42, 0x6e, 0x7f, 0x97, 0x7f, 0x0b, 0x89, 0xfd, 0x3f, 0xff, 0x5b, 0xc8, 0x0e, 0xd5, 0xb3, 0xd1, 0x1e, 0x9c, 0x17, 0x8c, 0x76, 0x46, 0xba, 0x6d, 0xa5, 0x54, 0xbe, 0x72, 0x68, 0x2b, 0xbd, 0xb6, 0x52, 0x3e, 0x8e, 0xf5, 0x5e, 0xf9, 0x25, 0xfb, 0x5f, 0xf2, 0xea, 0x29, 0xaf, 0x5e, 0xfe, 0x04, 0x99, 0xdd, 0xcb, 0xbf, 0x9d, 0xe6, 0xd5, 0x3f, 0xe0, 0xcc, 0xac, 0x6b, 0xd8, 0x25, 0x68, 0x32, 0xe7, 0xb7, 0xb1, 0xa5, 0x5d, 0x6d, 0xe9, 0x3d, 0xb6, 0xb4, 0xbd, 0x2d, 0x1d, 0xe0, 0x18, 0xb6, 0x6c, 0x28, 0xaf, 0x1c, 0xcf, 0x0a, 0xc6, 0x05, 0x97, 0xd9, 0xca, + 0xda, 0xe0, 0x1c, 0xb7, 0xe7, 0xca, 0x79, 0xf2, 0xe5, 0xe0, 0x0b, 0xc1, 0xf9, 0x72, 0x81, 0x7c, 0x45, 0xbe, 0x2a, 0x17, 0xf9, 0xfe, 0xc5, 0xfe, 0xfb, 0xba, 0x5c, 0xe5, 0xfe, 0x75, 0x72, 0xa7, 0xff, 0x4f, 0x09, 0xa6, 0xc4, 0x0e, 0x08, 0xce, 0x8b, 0x1d, 0x18, 0x3c, 0x11, 0x3b, 0x28, 0x78, 0x2c, 0x76, 0x58, 0x70, 0x77, 0xec, 0x13, 0xc1, 0x6d, 0xb1, 0xc3, 0xdd, 0x1e, 0xe1, 0xf6, 0xb8, 0xe0, 0x02, 0xe4, 0x7b, 0x04, 0xf9, 0x7e, 0x85, 0x7c, 0xb7, 0x21, 0xdf, 0xf7, 0x63, 0x67, 0x06, 0x3f, 0x8c, 0x7d, 0x31, 0xf8, 0x79, 0xec, 0x4b, 0x72, 0x41, 0xf0, 0x78, 0xec, 0xc2, 0x60, 0x12, 0xe2, 0xad, 0x47, 0xbc, 0xf5, 0x35, 0x97, 0x07, 0x97, 0xd5, 0x5c, 0x21, 0xdf, 0x92, 0xe9, 0xf2, 0xd3, 0xe0, 0xb2, 0xda, 0x49, 0xc1, 0xc5, 0x75, 0xdb, 0xc8, 0x01, 0xc1, 0xc3, 0x75, 0x07, 0xca, 0xbc, + 0xe0, 0x0b, 0x75, 0xfd, 0xc1, 0x79, 0xa3, 0xdf, 0x1f, 0x5c, 0x56, 0x3f, 0x5b, 0xe6, 0xc8, 0x5f, 0x83, 0xcb, 0x1a, 0xb6, 0x94, 0xad, 0xe4, 0xbd, 0xb2, 0xb5, 0x6c, 0x23, 0xdb, 0xca, 0xf6, 0xc1, 0x17, 0x1a, 0x76, 0x90, 0x1d, 0x65, 0x27, 0xd9, 0x59, 0x76, 0x91, 0x5d, 0x65, 0x37, 0xd9, 0x5d, 0xf6, 0x90, 0xbd, 0x64, 0x6f, 0xd9, 0x47, 0xf6, 0x95, 0x0f, 0xcb, 0xfe, 0xf2, 0x11, 0xf9, 0x98, 0xd7, 0x39, 0x40, 0x0e, 0x94, 0x83, 0x64, 0xbc, 0x1c, 0x1a, 0x5c, 0xdc, 0xf0, 0x71, 0x39, 0x4c, 0x3e, 0x21, 0x87, 0xcb, 0x11, 0x72, 0xa4, 0x1c, 0x25, 0x47, 0x07, 0x17, 0x37, 0x1d, 0x1b, 0x3d, 0x1e, 0x34, 0x39, 0xba, 0x75, 0x8e, 0x6e, 0x1d, 0x9e, 0x5b, 0x65, 0x06, 0xef, 0x33, 0x4e, 0xdb, 0xd8, 0x83, 0x6d, 0x6d, 0x6d, 0x6b, 0x5b, 0xdb, 0xca, 0x96, 0x2e, 0xb6, 0x95, 0x1d, 0x1b, 0x7e, 0x10, + 0x6c, 0x1d, 0xd4, 0xa9, 0xf9, 0xd5, 0xd6, 0xd1, 0x79, 0xeb, 0xe8, 0xfc, 0x86, 0x77, 0x63, 0x0c, 0x46, 0xc5, 0x4e, 0x33, 0x13, 0x4e, 0x8f, 0x16, 0xf8, 0x6a, 0x51, 0xf0, 0x20, 0x5b, 0xeb, 0x63, 0x6b, 0x7d, 0x6c, 0xad, 0x8f, 0x7d, 0x0d, 0xb2, 0xaf, 0x41, 0xf6, 0x35, 0xc8, 0xbe, 0x06, 0xd9, 0xd7, 0x20, 0xfb, 0x1a, 0x64, 0x5f, 0x83, 0xec, 0x6b, 0x90, 0x7d, 0x0d, 0xb2, 0xaf, 0x41, 0xf6, 0x35, 0xc8, 0xbe, 0x06, 0xd9, 0xd7, 0x20, 0xfb, 0x1a, 0x64, 0x5f, 0x83, 0xec, 0x6b, 0x90, 0x7d, 0x0d, 0xb2, 0xaf, 0x41, 0xf6, 0x35, 0xc8, 0xbe, 0x06, 0xd9, 0xd7, 0x20, 0xfb, 0x1a, 0x64, 0x5f, 0x83, 0xec, 0x6b, 0x90, 0x7d, 0x0d, 0xb2, 0xaf, 0x41, 0xf6, 0x35, 0xc8, 0xbe, 0x06, 0xd9, 0xd7, 0x20, 0xfb, 0x1a, 0x64, 0x5f, 0x83, 0xec, 0x6b, 0x90, 0x31, 0x0d, 0x56, 0x0d, 0x68, 0x88, 0x01, 0xbd, + 0xcc, 0x80, 0x32, 0x0c, 0xa8, 0x27, 0x76, 0x60, 0xd4, 0x1c, 0x3b, 0xc8, 0xbc, 0x28, 0xbf, 0x67, 0xe4, 0xa1, 0xd1, 0xab, 0x8e, 0xe7, 0xd5, 0xd8, 0x61, 0x51, 0x32, 0xf6, 0x89, 0x68, 0x20, 0x76, 0xb8, 0xdb, 0x23, 0xdc, 0x1e, 0x69, 0xbe, 0x1c, 0x25, 0x13, 0xa2, 0x96, 0xd8, 0xf1, 0xd1, 0x93, 0x8e, 0x72, 0x8e, 0xa3, 0x7c, 0xca, 0x51, 0xce, 0x8d, 0x9d, 0x19, 0x75, 0x6d, 0x7c, 0x7f, 0xd0, 0x0b, 0xa2, 0x57, 0xd9, 0x4e, 0x0f, 0xc3, 0xe9, 0x61, 0x36, 0x19, 0x66, 0xd3, 0xc3, 0x6c, 0x06, 0x99, 0xcd, 0x20, 0xb3, 0x19, 0x64, 0x36, 0x83, 0xcc, 0x66, 0x90, 0xd9, 0x0c, 0x32, 0x9b, 0x41, 0x66, 0x33, 0xc8, 0x6c, 0x06, 0x99, 0xcd, 0xe0, 0x26, 0xbf, 0xd3, 0x3a, 0xc8, 0x60, 0x06, 0xd9, 0xcb, 0x20, 0x7b, 0x19, 0x64, 0x2f, 0x83, 0xec, 0x65, 0x90, 0xbd, 0x0c, 0xb2, 0x94, 0x3e, 0x96, 0xd2, + 0xc7, 0x52, 0xfa, 0x58, 0x4a, 0x1f, 0x4b, 0xe9, 0x63, 0x29, 0x7d, 0x2c, 0xa5, 0x8f, 0xa5, 0xf4, 0xb1, 0x94, 0x3e, 0x96, 0xd2, 0xc7, 0x52, 0xfa, 0x58, 0x4a, 0x9f, 0xde, 0xd3, 0xc7, 0x52, 0xfa, 0x58, 0x4a, 0x1f, 0x4b, 0xe9, 0x63, 0x29, 0x7d, 0x2c, 0xa5, 0x8f, 0xa5, 0xf4, 0xb1, 0x94, 0x3e, 0x96, 0xd2, 0xc7, 0x52, 0xfa, 0x58, 0x4a, 0x1f, 0x4b, 0xe9, 0x63, 0x29, 0x7d, 0x2c, 0xa5, 0x8f, 0xa5, 0xf4, 0xb1, 0x94, 0x3e, 0x96, 0xd2, 0xc7, 0x52, 0xfa, 0x58, 0x4a, 0x1f, 0x3b, 0x18, 0x64, 0x07, 0x83, 0xec, 0x60, 0x90, 0x1d, 0x0c, 0xb2, 0x83, 0x41, 0x76, 0x30, 0xc8, 0x0e, 0x06, 0xd9, 0xc1, 0x20, 0x3b, 0x18, 0x64, 0x07, 0x83, 0xec, 0x60, 0x90, 0x1d, 0x0c, 0xb2, 0x83, 0x41, 0x76, 0x30, 0xc8, 0x0e, 0x06, 0xd9, 0xc1, 0x20, 0x3b, 0x18, 0x64, 0x07, 0x83, 0x9b, 0x74, 0xf8, 0x21, 0x1d, + 0x7e, 0x48, 0x87, 0x1f, 0xd2, 0xe1, 0x87, 0x74, 0xf8, 0x21, 0x1d, 0x7e, 0x48, 0x87, 0x1f, 0xd2, 0xe1, 0x5f, 0xd6, 0xe1, 0x5f, 0xd6, 0xe1, 0x33, 0x3a, 0x7c, 0x46, 0x87, 0xcf, 0xe8, 0xf0, 0x19, 0x1d, 0x3e, 0xa3, 0xc3, 0x67, 0x74, 0xf8, 0x8c, 0x0e, 0x9f, 0xd1, 0xe1, 0x33, 0x3a, 0x7c, 0x46, 0x87, 0xef, 0xd1, 0xe1, 0x7b, 0x74, 0xf8, 0x1e, 0x1d, 0xbe, 0x47, 0x87, 0xef, 0xd1, 0xe1, 0x7b, 0x82, 0xf7, 0xe8, 0x1a, 0x7f, 0x33, 0x62, 0xe5, 0xdf, 0x68, 0xee, 0x30, 0x5a, 0xa1, 0x11, 0x4a, 0xa9, 0xf3, 0x92, 0x3a, 0x2f, 0xe9, 0x7a, 0x7b, 0xe8, 0x49, 0xd9, 0x86, 0xfb, 0x83, 0xad, 0x1b, 0x5f, 0x09, 0xb6, 0x34, 0x0b, 0x66, 0x55, 0xde, 0x5f, 0xb5, 0x5c, 0xbd, 0x0b, 0x82, 0xed, 0x82, 0x3b, 0xf5, 0x9e, 0x29, 0xaa, 0xe1, 0xc0, 0xe8, 0x39, 0x15, 0x32, 0xcf, 0xe8, 0x67, 0xbd, 0xd6, 0xc3, + 0x46, 0x7f, 0x81, 0x47, 0xcd, 0xf1, 0xa8, 0xe7, 0x8d, 0x7e, 0x8b, 0xd7, 0x6c, 0x8e, 0x5d, 0xa8, 0x0a, 0x7a, 0xbd, 0x76, 0x41, 0xcc, 0x1f, 0x3d, 0x6f, 0xb8, 0xdc, 0xb3, 0x8d, 0xea, 0x1c, 0xa3, 0x3a, 0x47, 0xdf, 0x1b, 0x6e, 0x3c, 0xbd, 0xf2, 0x4e, 0xcc, 0xd7, 0x06, 0x75, 0x7a, 0x72, 0xf9, 0x37, 0xfa, 0x06, 0xcb, 0xef, 0xe0, 0x6a, 0x2f, 0xca, 0x5b, 0x5e, 0x82, 0x0a, 0x6f, 0xff, 0x4e, 0xf9, 0x5f, 0x9a, 0xbb, 0x7d, 0x27, 0x51, 0xfd, 0xb7, 0x95, 0x57, 0x6c, 0x61, 0xad, 0x2d, 0xac, 0xb5, 0x85, 0x3e, 0x8f, 0x48, 0x9a, 0x85, 0xd5, 0x7f, 0xa5, 0xd5, 0xb9, 0x76, 0xd0, 0xb9, 0x76, 0xd3, 0xb9, 0x76, 0xaa, 0xcc, 0xc2, 0xf2, 0x6f, 0xcb, 0xb5, 0xc4, 0xca, 0xbf, 0xb3, 0xbe, 0x85, 0xad, 0x15, 0x2a, 0xdd, 0xf3, 0x2b, 0xc1, 0xa1, 0xc1, 0x0f, 0x82, 0x7d, 0x62, 0x13, 0x82, 0xad, 0xbd, + 0x6a, 0xf9, 0xdf, 0xcf, 0x37, 0xf3, 0xf8, 0x31, 0x5e, 0xb5, 0xc7, 0xab, 0x66, 0xbc, 0xea, 0x3a, 0xe7, 0x63, 0xa4, 0x61, 0x55, 0xb0, 0x4f, 0xc3, 0xea, 0x60, 0x1f, 0x5b, 0x68, 0x0d, 0x16, 0x58, 0x65, 0x15, 0xac, 0xb2, 0x0a, 0x56, 0x59, 0x05, 0xab, 0xac, 0x82, 0x55, 0x56, 0xc1, 0x2a, 0xab, 0x60, 0x95, 0xb5, 0xde, 0x2a, 0x6b, 0xbd, 0x55, 0xd6, 0x7a, 0x2b, 0xac, 0x82, 0x15, 0x56, 0xc1, 0x1c, 0x1f, 0x32, 0xc7, 0x87, 0xcc, 0xf1, 0x21, 0x73, 0x7c, 0xc8, 0x1c, 0x1f, 0x32, 0xc7, 0x87, 0xcc, 0xf1, 0x21, 0x73, 0x7c, 0xc8, 0x1c, 0x1f, 0x32, 0xc7, 0x87, 0xcc, 0xf1, 0x21, 0x73, 0x7c, 0xc8, 0x1c, 0x1f, 0x32, 0xc7, 0x87, 0xcc, 0xf1, 0x21, 0x73, 0x7c, 0xc8, 0x1c, 0x1f, 0x32, 0xc7, 0x87, 0xcc, 0xf1, 0x21, 0x73, 0x7c, 0xc8, 0x1c, 0x1f, 0x32, 0xc7, 0x87, 0xcc, 0xf1, 0x21, 0x73, 0x7c, + 0xc8, 0x1c, 0x1f, 0x32, 0xc7, 0x87, 0xcc, 0xf1, 0x21, 0x73, 0x7c, 0xc8, 0x1c, 0x1f, 0x32, 0xc7, 0x87, 0xcc, 0xf1, 0x21, 0x73, 0x7c, 0xc8, 0xea, 0x6a, 0xc0, 0x2a, 0xaa, 0x80, 0x73, 0x05, 0xab, 0xa8, 0x82, 0x15, 0xd4, 0x7a, 0xf3, 0x7e, 0x28, 0x28, 0xbf, 0xf7, 0xed, 0xed, 0xd1, 0x88, 0x39, 0xbf, 0xce, 0x48, 0x66, 0x63, 0x47, 0x47, 0x2b, 0x63, 0xc7, 0x4a, 0xf9, 0xef, 0xa2, 0x4f, 0x30, 0x6f, 0x4f, 0xaa, 0xcc, 0xdd, 0xd7, 0xac, 0x6c, 0x0a, 0x56, 0x31, 0x05, 0xab, 0x98, 0xf5, 0x56, 0x2a, 0xeb, 0xad, 0x50, 0xd6, 0x9b, 0xc7, 0xeb, 0xcc, 0xe1, 0x21, 0x73, 0x78, 0xc8, 0x1c, 0x1e, 0x32, 0x87, 0x87, 0xcc, 0xe1, 0x21, 0x73, 0x78, 0xc8, 0x1c, 0x1e, 0x32, 0x87, 0x87, 0xcc, 0xe1, 0x21, 0x73, 0x78, 0xc8, 0x6a, 0x64, 0xc0, 0xfc, 0x1d, 0x32, 0x7f, 0x87, 0xcc, 0xdf, 0x21, 0xf3, 0x77, + 0xc8, 0xfc, 0x1d, 0x32, 0x7f, 0x87, 0xcc, 0xdf, 0x21, 0xab, 0x8f, 0x82, 0xd5, 0xc7, 0x7a, 0x2b, 0x8e, 0x02, 0xeb, 0x2f, 0xb0, 0xfe, 0x02, 0xeb, 0x2f, 0xb0, 0xfe, 0x02, 0xeb, 0x2f, 0xb0, 0xfe, 0xf5, 0xac, 0x7f, 0x3d, 0xeb, 0x5f, 0xcf, 0xfa, 0xd7, 0xb3, 0xfe, 0xf5, 0xac, 0xbf, 0xc0, 0xfa, 0x0b, 0xac, 0xbf, 0xc0, 0xfa, 0x0b, 0xac, 0xbf, 0xc0, 0xfa, 0x0b, 0xac, 0xbf, 0xc0, 0xfa, 0x0b, 0xac, 0xbf, 0xc0, 0xfa, 0x0b, 0xac, 0xbf, 0xc0, 0xfa, 0x0b, 0xc8, 0x5a, 0x60, 0xfd, 0x05, 0xd6, 0x5f, 0x60, 0xfd, 0x05, 0xd6, 0x5f, 0x60, 0xfd, 0x05, 0xd6, 0x5f, 0x60, 0xfd, 0x05, 0xd6, 0x5f, 0x60, 0xfd, 0x05, 0xd6, 0x5f, 0x60, 0xfd, 0x05, 0xf3, 0x7a, 0xc8, 0xbc, 0x1e, 0x32, 0xaf, 0x87, 0xcc, 0xeb, 0x21, 0xf3, 0x7a, 0xc8, 0xbc, 0x1e, 0x32, 0xaf, 0x87, 0xcc, 0xeb, 0x21, 0xf3, 0x7a, 0xc8, + 0xbc, 0x1e, 0x32, 0xaf, 0x87, 0xcc, 0xeb, 0x21, 0xf3, 0x7a, 0xc8, 0xbc, 0x1e, 0x32, 0xaf, 0x87, 0xcc, 0xeb, 0x21, 0xf3, 0x7a, 0xc8, 0xbc, 0x1e, 0x62, 0xf6, 0x03, 0xcc, 0x7e, 0x80, 0xd9, 0x0f, 0x30, 0xfb, 0x01, 0x66, 0x3f, 0xc0, 0xec, 0x07, 0x98, 0xfd, 0x00, 0x0b, 0x2f, 0xb0, 0xf0, 0x02, 0x0b, 0x2f, 0xb0, 0xf0, 0x02, 0x0b, 0x2f, 0xb0, 0xf0, 0x02, 0x0b, 0x2f, 0xb0, 0xf0, 0x02, 0x0b, 0x2f, 0xb0, 0xf0, 0x02, 0x0b, 0x2f, 0x20, 0x7e, 0x81, 0x85, 0x17, 0x58, 0x78, 0x81, 0x85, 0x17, 0x58, 0x78, 0x81, 0x85, 0x17, 0x58, 0x78, 0x81, 0x85, 0x17, 0x58, 0x78, 0x81, 0x85, 0x17, 0x58, 0x78, 0x81, 0x85, 0x17, 0x58, 0x78, 0x81, 0x85, 0x17, 0x58, 0x78, 0x81, 0x97, 0x94, 0xdf, 0x2b, 0xa9, 0x4f, 0x7f, 0xe9, 0xd3, 0x5f, 0x46, 0xf4, 0x97, 0x11, 0xfd, 0x65, 0x44, 0x7f, 0x19, 0xd1, 0x5f, + 0x46, 0xf4, 0x97, 0x11, 0xfd, 0x65, 0x44, 0x5f, 0x59, 0xa7, 0xaf, 0xac, 0xd3, 0x57, 0xd6, 0xe9, 0x2b, 0xeb, 0xf4, 0x95, 0x75, 0xfa, 0xca, 0x3a, 0x7d, 0x65, 0x9d, 0xbe, 0xb2, 0x4e, 0x5f, 0x59, 0xa7, 0xaf, 0xac, 0x6b, 0x7c, 0x3d, 0xea, 0x65, 0x87, 0x05, 0x76, 0x58, 0x60, 0x87, 0x05, 0x76, 0x58, 0x60, 0x87, 0xe5, 0xf7, 0x3f, 0x5a, 0x5f, 0xbe, 0x8e, 0x1f, 0x6c, 0x67, 0x0e, 0x9d, 0x64, 0xde, 0x9d, 0x64, 0x1e, 0x9d, 0x50, 0x5e, 0x35, 0x5b, 0x59, 0x6f, 0xae, 0x5f, 0x24, 0x55, 0x59, 0x79, 0xe6, 0xce, 0x31, 0x13, 0x57, 0xfa, 0x6e, 0xca, 0x4c, 0x4c, 0xe9, 0x15, 0xe5, 0xbf, 0x2f, 0x7c, 0xb5, 0x3a, 0x8b, 0xcb, 0x7d, 0x62, 0x9d, 0xfe, 0xb0, 0x52, 0x7f, 0x58, 0x69, 0xae, 0xfd, 0x21, 0xd8, 0xd2, 0x33, 0xb3, 0x9e, 0xd9, 0xe3, 0x95, 0xf7, 0x8f, 0x1d, 0x1c, 0x6c, 0x1f, 0x3b, 0x32, + 0x38, 0x39, 0x76, 0x54, 0xe5, 0x77, 0x74, 0xb7, 0xf2, 0x6a, 0xcd, 0xb6, 0x76, 0xa0, 0xad, 0x1d, 0x62, 0x6b, 0x87, 0x7a, 0xa5, 0x84, 0x57, 0xea, 0xf3, 0x4a, 0x7d, 0x5e, 0x29, 0xef, 0x15, 0x16, 0xe9, 0x77, 0x35, 0xd1, 0x7d, 0x65, 0x3b, 0x45, 0xb3, 0x27, 0xf4, 0xab, 0x3f, 0xd9, 0x7e, 0x87, 0xed, 0x2f, 0xb4, 0xfd, 0x56, 0x8f, 0x4e, 0x78, 0x74, 0xa2, 0xd2, 0x9f, 0xf4, 0x3d, 0xcf, 0x48, 0x57, 0x7a, 0x45, 0xf9, 0x11, 0xcd, 0xb1, 0xf2, 0xfb, 0x20, 0x6c, 0xe3, 0xf9, 0xbf, 0xd7, 0xf3, 0xb2, 0xf6, 0xa4, 0xcd, 0x9e, 0xc4, 0x6d, 0xf5, 0x51, 0x8f, 0xe8, 0xf6, 0x88, 0x15, 0x1e, 0xb1, 0xc2, 0x31, 0x74, 0xd8, 0xf2, 0x12, 0xfd, 0x6e, 0x9d, 0xd7, 0xcb, 0x7a, 0xbd, 0xac, 0xd7, 0x0b, 0xf5, 0xbb, 0x21, 0xc7, 0x12, 0x77, 0x2c, 0x71, 0xbd, 0x6e, 0x48, 0xaf, 0x2b, 0xef, 0xd1, 0x03, 0xc1, + 0x66, 0x5e, 0xf1, 0xea, 0x4d, 0xba, 0x70, 0xfa, 0x1f, 0xea, 0xc2, 0xe5, 0xf7, 0x7c, 0xee, 0xfd, 0x0f, 0xbb, 0x5f, 0xf9, 0x37, 0x53, 0x7e, 0xaf, 0x03, 0xfe, 0x41, 0x07, 0x2c, 0xff, 0x1b, 0xf4, 0xd2, 0x6a, 0xef, 0x5e, 0x1c, 0x8c, 0x67, 0x8c, 0x4d, 0x6c, 0xb1, 0x89, 0x2d, 0x36, 0xb1, 0xc5, 0xdd, 0x98, 0x61, 0x13, 0x27, 0x8c, 0x31, 0xc3, 0x26, 0x66, 0xd8, 0xc4, 0x09, 0x63, 0x9b, 0xfa, 0x0e, 0xa3, 0x6b, 0x62, 0x74, 0x4d, 0x8c, 0xae, 0x89, 0xd1, 0x35, 0x31, 0xba, 0x26, 0x46, 0xd7, 0xc4, 0xe8, 0x9a, 0x18, 0x5c, 0x13, 0x83, 0x6b, 0x62, 0x70, 0x4d, 0x0c, 0xae, 0x89, 0xc1, 0x35, 0x31, 0xb8, 0x26, 0x06, 0xd7, 0xc4, 0xe0, 0x9a, 0x18, 0x5c, 0x13, 0x83, 0x6b, 0x62, 0x6d, 0xdf, 0xe4, 0x4b, 0x9b, 0x31, 0xb6, 0x71, 0x8c, 0xed, 0x93, 0x8c, 0xed, 0x18, 0xc6, 0xf6, 0x3d, 0xee, 0xb4, + 0x2b, 0x77, 0x3a, 0x89, 0x3b, 0x6d, 0xc5, 0xd0, 0x9a, 0x18, 0x5a, 0x13, 0x43, 0x6b, 0x62, 0x68, 0x4d, 0x0c, 0xad, 0x89, 0xa1, 0x35, 0x31, 0xb4, 0x26, 0x86, 0xd6, 0xc4, 0xd0, 0x9a, 0x18, 0x5a, 0x13, 0x43, 0x6b, 0x62, 0x68, 0x4d, 0x0c, 0xad, 0x89, 0xa1, 0x35, 0x35, 0x7c, 0x2f, 0xd8, 0xbf, 0xe1, 0xce, 0x60, 0x7b, 0x67, 0xe8, 0xe4, 0xca, 0x11, 0x7d, 0x70, 0xd3, 0xbd, 0xb7, 0xd5, 0x33, 0x6d, 0xf5, 0xe3, 0xb6, 0xba, 0xb7, 0xad, 0xee, 0xb9, 0xc9, 0x16, 0x3f, 0xaf, 0xff, 0x9f, 0x17, 0x7c, 0xf9, 0xef, 0x1e, 0xbd, 0x53, 0xe5, 0x37, 0xb1, 0xef, 0xf2, 0x8c, 0x9b, 0x3c, 0xe3, 0x1c, 0xcf, 0xb8, 0xda, 0x7e, 0x1e, 0xb3, 0xc9, 0xb3, 0x7e, 0xd2, 0x70, 0x47, 0xb0, 0xb9, 0x6d, 0xed, 0x66, 0xbd, 0x73, 0x6e, 0xf0, 0xb8, 0x57, 0xf8, 0xc2, 0xdb, 0x5e, 0xe1, 0x28, 0xaf, 0x70, 0xab, 0x57, + 0xf8, 0x96, 0x57, 0x38, 0xcf, 0x2b, 0x9c, 0xee, 0x15, 0x3e, 0xbe, 0xc9, 0x2b, 0xfc, 0xd0, 0x51, 0xed, 0xe3, 0x55, 0xc6, 0x78, 0x95, 0x5d, 0x83, 0x2d, 0xcb, 0xbf, 0x7d, 0xfe, 0xb6, 0x57, 0xb8, 0xdc, 0x2b, 0xd4, 0x7b, 0x85, 0x0f, 0x79, 0x85, 0x23, 0xbd, 0xc2, 0xe1, 0x5e, 0xe1, 0x86, 0x4d, 0x5e, 0x61, 0x33, 0xc7, 0x3c, 0xd6, 0x31, 0xbf, 0xd7, 0x2b, 0x1c, 0x5f, 0x39, 0xe6, 0x8f, 0xbc, 0xed, 0x98, 0xbf, 0xe8, 0xd9, 0xc7, 0x78, 0xf6, 0x87, 0x3d, 0x7b, 0xec, 0x26, 0xcf, 0xfc, 0xb2, 0xaa, 0x3b, 0x2f, 0xf8, 0xdc, 0xdb, 0x1e, 0x5d, 0xde, 0xdb, 0x6b, 0x3c, 0xfa, 0x0c, 0x8f, 0xbe, 0xc4, 0xb6, 0x0e, 0xd9, 0xe4, 0x19, 0x77, 0xd8, 0xc6, 0x76, 0xf6, 0xf2, 0xad, 0x63, 0xfd, 0xfc, 0xdb, 0xf6, 0xf4, 0x70, 0xcf, 0xbe, 0xc5, 0xb3, 0xaf, 0xad, 0x9e, 0xad, 0xd3, 0x3c, 0xfb, 0xe0, 0xb7, + 0x1d, 0xeb, 0xde, 0x5e, 0x61, 0x67, 0x15, 0xb9, 0xe9, 0x33, 0x77, 0xb3, 0x06, 0xab, 0x3e, 0xaa, 0x32, 0xcf, 0xd6, 0x6d, 0xf8, 0xbb, 0xaa, 0xca, 0x57, 0xcd, 0x1b, 0x1c, 0x62, 0xe3, 0x57, 0xcf, 0x56, 0xbc, 0xe3, 0x97, 0xff, 0xe3, 0xef, 0x30, 0xfa, 0xd6, 0xbb, 0x7d, 0xfe, 0x63, 0xd7, 0x16, 0x6f, 0xe3, 0x48, 0xb7, 0x23, 0xed, 0x5b, 0x46, 0xbd, 0x92, 0x51, 0x97, 0xdf, 0x79, 0x7f, 0xbe, 0xa3, 0x99, 0xed, 0x68, 0xe6, 0x6f, 0x72, 0x0d, 0xb0, 0xfc, 0x3e, 0x61, 0xe5, 0xf7, 0x07, 0x5b, 0xc9, 0x8a, 0xc3, 0x7f, 0xe2, 0x1d, 0x3b, 0x2b, 0xd7, 0xfb, 0x36, 0xbe, 0x4b, 0xe7, 0x3b, 0xbc, 0x43, 0x67, 0xf9, 0x1a, 0xde, 0x7f, 0xd6, 0x3b, 0x43, 0xfe, 0xaf, 0xbe, 0xe6, 0xd2, 0x11, 0xad, 0x41, 0xb6, 0x35, 0xc8, 0x56, 0x40, 0xb6, 0x02, 0xb2, 0x15, 0x90, 0xad, 0x80, 0x6c, 0x05, 0x64, 0x2b, + 0x20, 0x5b, 0x61, 0x13, 0x73, 0x5e, 0x89, 0x70, 0x2b, 0x11, 0x6e, 0x25, 0xc2, 0xad, 0x44, 0xb8, 0x95, 0x08, 0xb7, 0x12, 0xe1, 0x56, 0x22, 0xdc, 0x4a, 0x84, 0x5b, 0x89, 0x70, 0x2b, 0x99, 0x73, 0xc8, 0x9c, 0x43, 0xe6, 0x1c, 0x32, 0xe7, 0x90, 0x39, 0x87, 0xcc, 0x39, 0xdc, 0xe8, 0x90, 0x2f, 0x54, 0xfa, 0x69, 0xf9, 0xab, 0xf2, 0x7b, 0xee, 0xff, 0xc5, 0x57, 0xf3, 0x82, 0x6d, 0x99, 0x71, 0x88, 0x12, 0xaf, 0xa0, 0xc4, 0x3c, 0x94, 0x98, 0xaf, 0x4b, 0xff, 0xba, 0x3a, 0xea, 0x95, 0x47, 0x54, 0xad, 0x78, 0x21, 0x4a, 0xac, 0x32, 0x23, 0xba, 0xcd, 0x88, 0x6e, 0x33, 0x22, 0x8b, 0x12, 0x7d, 0xe5, 0xdf, 0x42, 0x43, 0x8a, 0xb9, 0x48, 0x31, 0x17, 0x29, 0xfa, 0x90, 0x62, 0xad, 0x6e, 0x7e, 0x7b, 0xa5, 0x9b, 0xff, 0x4d, 0x37, 0x9f, 0xab, 0x9b, 0xcf, 0xf5, 0x6a, 0x7f, 0xf1, 0x6a, 0xcf, + 0x54, 0xb6, 0xf7, 0x9d, 0x7f, 0xe7, 0xfd, 0x82, 0xf2, 0x1c, 0x34, 0xcf, 0x41, 0xf3, 0x1b, 0xdf, 0x2f, 0xe8, 0x8b, 0x51, 0x91, 0x07, 0xfe, 0xfd, 0x7b, 0xee, 0x4c, 0xaa, 0xbc, 0xdb, 0xfc, 0xa0, 0x0a, 0x2d, 0xbf, 0x0b, 0xde, 0x8a, 0x2a, 0x27, 0x5e, 0x2a, 0xbf, 0xff, 0x4d, 0xf5, 0x3d, 0x6f, 0xca, 0xbf, 0x7b, 0x54, 0xe4, 0x7c, 0x79, 0xce, 0x97, 0x57, 0xa5, 0x03, 0x5c, 0x2e, 0xac, 0xbe, 0xcf, 0x4c, 0xbe, 0xf2, 0xbe, 0x32, 0xef, 0xfc, 0x7e, 0x2e, 0x79, 0xbe, 0x96, 0xe7, 0x6b, 0x79, 0xbe, 0x96, 0xe7, 0x6b, 0xf9, 0xff, 0xf2, 0xf7, 0x73, 0x89, 0xa2, 0x62, 0x43, 0x20, 0x31, 0xa9, 0x91, 0x51, 0x52, 0x2b, 0x75, 0x32, 0xda, 0xe8, 0x8d, 0x91, 0x7a, 0x69, 0x90, 0xcd, 0x64, 0x73, 0xd9, 0xe2, 0xbf, 0xf9, 0xfd, 0x4b, 0x3a, 0xac, 0xf3, 0x96, 0xc9, 0x1a, 0xee, 0xd7, 0x2d, 0x49, 0x49, + 0x49, 0x8f, 0x64, 0x24, 0x2b, 0xa1, 0xe4, 0x24, 0x1f, 0x0d, 0xbc, 0xd3, 0x7b, 0x67, 0x70, 0xac, 0x7c, 0xa5, 0x26, 0x9a, 0xd5, 0x44, 0xb3, 0x9a, 0x60, 0x3f, 0x7a, 0xca, 0x86, 0x9a, 0x28, 0x57, 0x64, 0xab, 0xaf, 0xfe, 0xe6, 0xab, 0x85, 0xe5, 0xaf, 0xf4, 0xfb, 0xd3, 0x83, 0xd3, 0x99, 0xd1, 0x29, 0x95, 0x67, 0x2d, 0xf4, 0xac, 0xe7, 0x3d, 0xeb, 0xf9, 0x6a, 0xdf, 0x7d, 0xeb, 0x33, 0x33, 0xca, 0x3f, 0x59, 0xe0, 0x27, 0x2f, 0xf8, 0xc9, 0x0b, 0x7e, 0xf2, 0xca, 0x86, 0x77, 0x44, 0xda, 0xd8, 0x9f, 0x17, 0x56, 0x3c, 0xa8, 0xfc, 0xb8, 0x95, 0x1e, 0xb7, 0xd2, 0xe3, 0x56, 0x56, 0x3f, 0x89, 0xa3, 0xfc, 0x19, 0x1b, 0xbd, 0x95, 0xc7, 0x95, 0x3f, 0x91, 0xe4, 0xf9, 0x72, 0xfd, 0x54, 0x1c, 0xf0, 0x80, 0xea, 0x6f, 0x3e, 0x9f, 0x65, 0xdb, 0x67, 0x54, 0x7e, 0xbe, 0x71, 0x1e, 0x54, 0x56, + 0x93, 0xe5, 0x4f, 0xf0, 0x58, 0x56, 0xf9, 0xfe, 0xf3, 0x7f, 0xb7, 0xf7, 0x1b, 0x68, 0xd0, 0x56, 0xd9, 0xda, 0x43, 0xb6, 0x36, 0xd3, 0xd6, 0x66, 0x56, 0x7f, 0x37, 0xf8, 0xad, 0x67, 0xbd, 0xfd, 0x27, 0xbd, 0x1b, 0xfe, 0xfe, 0xab, 0xf2, 0x93, 0x39, 0x7e, 0xf2, 0x57, 0x3f, 0xf9, 0x6b, 0xf5, 0x39, 0x2f, 0x6d, 0x3c, 0x92, 0xd7, 0x37, 0x5e, 0x83, 0xa9, 0xa9, 0xee, 0xeb, 0xc2, 0x8d, 0x33, 0x7a, 0x61, 0x65, 0x55, 0xb8, 0xc1, 0xfb, 0x5e, 0xa8, 0x1c, 0x6f, 0xf9, 0x38, 0x26, 0x3a, 0x8e, 0x2f, 0x3b, 0x8e, 0xab, 0x1d, 0xc7, 0x55, 0x95, 0x33, 0x7a, 0x97, 0xaf, 0x6e, 0xf5, 0xd5, 0xcd, 0x1b, 0xaf, 0xeb, 0x94, 0x2d, 0xb1, 0xb5, 0xb2, 0xed, 0xf9, 0xb6, 0x3d, 0xcf, 0xb6, 0xe7, 0x55, 0x3f, 0x81, 0xe4, 0xad, 0xbd, 0x2a, 0x3f, 0x6e, 0x71, 0xf5, 0x13, 0x5c, 0x5e, 0xd8, 0x38, 0x46, 0xf3, + 0x2b, 0x7b, 0x52, 0x7e, 0x56, 0xaf, 0x67, 0xad, 0xf3, 0xac, 0x75, 0xd5, 0xb3, 0xf4, 0x16, 0xff, 0xca, 0xfb, 0x38, 0x6f, 0xe3, 0xbd, 0x17, 0x2b, 0x1d, 0xa7, 0xa6, 0xfa, 0xd9, 0x2d, 0x2f, 0x57, 0x9e, 0xf7, 0x80, 0xe7, 0xdd, 0xef, 0x79, 0xf7, 0x57, 0xf7, 0xe3, 0xf9, 0xca, 0x2b, 0x96, 0xf7, 0xfa, 0x28, 0xfb, 0x79, 0x86, 0xfd, 0xbc, 0xd0, 0x7e, 0x7e, 0xb9, 0xb2, 0xc5, 0x57, 0xaa, 0x5b, 0x6c, 0xa9, 0x5c, 0xa3, 0xba, 0xc7, 0x33, 0xef, 0xf6, 0xcc, 0xbb, 0x37, 0x1d, 0xc1, 0xe0, 0x3b, 0x9e, 0x71, 0xaa, 0x67, 0x9c, 0x56, 0x79, 0xc6, 0x0b, 0x7e, 0xf6, 0xd7, 0x32, 0xad, 0x2a, 0xeb, 0xe9, 0xab, 0x3c, 0xfa, 0x2a, 0x8f, 0xd9, 0xd2, 0x63, 0xb6, 0xf6, 0x98, 0x2d, 0x2b, 0x7b, 0xd0, 0xe1, 0x27, 0xaf, 0xf8, 0xc9, 0x2b, 0x1e, 0x1f, 0xaf, 0x9e, 0x89, 0x7f, 0xeb, 0x8b, 0x6f, 0x8d, 0x6a, + 0xf9, 0x5f, 0xce, 0xf6, 0xad, 0xec, 0x79, 0x79, 0xe5, 0xfd, 0x62, 0x65, 0x4b, 0x53, 0xab, 0xe7, 0xf0, 0x7b, 0x1b, 0xf7, 0x6d, 0xde, 0xc6, 0x8a, 0x9d, 0xe2, 0x67, 0x93, 0xff, 0xee, 0x67, 0x6f, 0x8d, 0xd9, 0x86, 0xb1, 0x79, 0x7e, 0xe3, 0x08, 0x56, 0xfc, 0xb7, 0xb2, 0xbe, 0xe8, 0xab, 0x1c, 0xf5, 0x7e, 0x9e, 0x7d, 0x94, 0x67, 0x7f, 0xba, 0x52, 0xef, 0xa3, 0xaa, 0xee, 0xfb, 0x56, 0x4d, 0x8e, 0xae, 0xee, 0x61, 0xd9, 0x26, 0x5e, 0xa8, 0xbd, 0x2b, 0x5a, 0x52, 0x7b, 0x4f, 0xf4, 0x6c, 0xed, 0x8f, 0x9d, 0xdd, 0xd1, 0x9b, 0xf4, 0xbe, 0x45, 0xd5, 0x4f, 0xb3, 0x78, 0xc1, 0x4f, 0x16, 0x55, 0x7e, 0xf2, 0xb7, 0xea, 0x15, 0xad, 0xbf, 0xf9, 0xc9, 0x73, 0x7e, 0xf2, 0x94, 0x9f, 0x3c, 0x6b, 0x6b, 0x6f, 0x8d, 0x43, 0xf9, 0x7a, 0x76, 0xbe, 0xfc, 0x17, 0x86, 0x95, 0xad, 0x2d, 0xf2, 0xdd, + 0xb9, 0x1b, 0xc7, 0x78, 0x5d, 0xd5, 0xeb, 0xb3, 0x95, 0xd7, 0x79, 0x79, 0x43, 0x55, 0x79, 0x9d, 0x97, 0x3d, 0x6b, 0x81, 0x67, 0x2d, 0xae, 0xbc, 0x4e, 0xe5, 0xf3, 0x63, 0x7c, 0x27, 0x57, 0x79, 0x9d, 0xda, 0xea, 0x79, 0x7a, 0xc9, 0x77, 0x3a, 0x7c, 0xe7, 0xad, 0x67, 0x3f, 0xe7, 0xbb, 0x4f, 0xbf, 0x6d, 0x2f, 0xe6, 0x56, 0x46, 0xa0, 0x60, 0x04, 0xfa, 0x8c, 0x40, 0x9f, 0x47, 0xcd, 0xdd, 0xb0, 0xaf, 0x95, 0xdf, 0xa6, 0xea, 0xab, 0xee, 0x7d, 0x79, 0x14, 0x9e, 0xab, 0xec, 0x51, 0xe1, 0xef, 0x6a, 0xf0, 0xf9, 0x0d, 0x36, 0xb2, 0x71, 0xff, 0xe6, 0x57, 0xb6, 0x7b, 0x97, 0xed, 0xde, 0x13, 0xcd, 0xb7, 0x85, 0x17, 0x2b, 0x7f, 0x59, 0x70, 0x93, 0x2d, 0xdc, 0x68, 0x0b, 0x37, 0x56, 0x7d, 0xac, 0x7c, 0x9e, 0x5a, 0x3c, 0x6a, 0x81, 0x47, 0x3d, 0x5f, 0x79, 0x54, 0xf9, 0xf9, 0x6d, 0x1b, + 0xba, 0x84, 0x9f, 0xb4, 0xfb, 0xc9, 0x8b, 0x7e, 0xd2, 0x5a, 0x79, 0xfe, 0x2c, 0xcf, 0x7f, 0xc4, 0xf3, 0x1f, 0xa9, 0xee, 0x4f, 0xc5, 0xe7, 0x3c, 0x6a, 0x8e, 0x47, 0xcd, 0xf6, 0xa8, 0x7f, 0x9b, 0xf7, 0x2f, 0x55, 0x2c, 0xb0, 0xe1, 0x6d, 0x73, 0xf8, 0xb5, 0xea, 0xdf, 0x04, 0xb4, 0x57, 0xcf, 0x5c, 0xf9, 0x2f, 0x1a, 0xdb, 0x2b, 0xdb, 0x5c, 0x52, 0x1d, 0xdd, 0xc5, 0xd5, 0xf1, 0x9c, 0x5f, 0x19, 0xb5, 0x0d, 0x23, 0x31, 0xaf, 0x32, 0xee, 0x35, 0xd5, 0x4f, 0x46, 0x0a, 0x2b, 0xaf, 0xbb, 0xe9, 0xba, 0x68, 0xc3, 0xcc, 0x2d, 0x3f, 0xfb, 0x45, 0xcf, 0x9e, 0x57, 0x79, 0x76, 0xf9, 0x51, 0x2f, 0x7a, 0xd4, 0x8b, 0x1e, 0xf5, 0x62, 0x75, 0x16, 0x57, 0xba, 0x4e, 0xf5, 0xb8, 0x2a, 0xbf, 0x5d, 0xbb, 0x71, 0xfc, 0x3b, 0x37, 0x8e, 0xdb, 0xe8, 0x4d, 0x3b, 0x47, 0xf5, 0x1c, 0xbe, 0x35, 0xc6, + 0xe5, 0xfd, 0x59, 0x5e, 0xdd, 0xd3, 0x17, 0x2b, 0xfb, 0xf3, 0x42, 0xa5, 0xd6, 0xcb, 0x5b, 0xba, 0xdb, 0x96, 0xee, 0xb1, 0xa5, 0x7b, 0xaa, 0x8f, 0x59, 0x5c, 0x3d, 0xce, 0xf2, 0xd1, 0xb4, 0x78, 0xf6, 0xd2, 0xca, 0x96, 0xe6, 0x57, 0xce, 0xd8, 0x3d, 0xd1, 0x52, 0xdf, 0x59, 0x5b, 0xd9, 0xd2, 0xc6, 0xb3, 0x55, 0x7d, 0x47, 0xd8, 0xd6, 0xca, 0x5e, 0x95, 0x5f, 0xf1, 0x69, 0xaf, 0xf8, 0xb4, 0x57, 0x7c, 0xda, 0xa3, 0x5e, 0xda, 0x50, 0xfd, 0x1e, 0xf5, 0x82, 0x47, 0x3d, 0xe7, 0x51, 0x0b, 0x37, 0x3e, 0xbf, 0xd2, 0x65, 0x37, 0x79, 0x7e, 0x47, 0x65, 0x4f, 0xdb, 0x36, 0xd6, 0x48, 0xb9, 0xba, 0x56, 0x78, 0xb5, 0x15, 0x5e, 0x6d, 0x45, 0xb5, 0x33, 0x3d, 0x5b, 0xa9, 0xa5, 0xf2, 0x4f, 0x3a, 0x37, 0x5e, 0x15, 0x7e, 0x6b, 0xae, 0x3f, 0x5b, 0xa9, 0xbb, 0xf2, 0x1e, 0x2c, 0xf3, 0x93, 0x65, + 0x7e, 0xb2, 0xac, 0xfa, 0x93, 0x72, 0x37, 0x9b, 0x5f, 0xad, 0xdb, 0xbf, 0x54, 0xc6, 0x7b, 0x74, 0xf5, 0xaa, 0xf2, 0x5b, 0xbf, 0x6d, 0xf6, 0x56, 0x75, 0xbf, 0xd5, 0x69, 0xca, 0xf3, 0xff, 0x3e, 0x33, 0x78, 0xaa, 0x19, 0x7c, 0x47, 0x65, 0x7f, 0x96, 0x6d, 0xec, 0xc6, 0xc7, 0x06, 0xcd, 0xd6, 0xcc, 0x2d, 0xd1, 0x55, 0xc1, 0xe2, 0xe8, 0xc9, 0xa0, 0x35, 0x7a, 0x2a, 0x68, 0x8b, 0x7e, 0x13, 0x2c, 0xe5, 0x48, 0x2f, 0x47, 0x1f, 0x0a, 0xda, 0xa3, 0x29, 0xc1, 0xb2, 0xa8, 0x3b, 0xe8, 0x8c, 0xa6, 0x06, 0xaf, 0x46, 0xc7, 0x05, 0xaf, 0x39, 0x53, 0x2b, 0xa2, 0xb3, 0x83, 0xae, 0xe8, 0xb7, 0xc1, 0xaa, 0xe8, 0xf0, 0xa0, 0x3b, 0x3a, 0x39, 0x48, 0xbf, 0xd9, 0x1b, 0xac, 0x8d, 0x3e, 0x13, 0xf4, 0x44, 0xe3, 0x2b, 0x9f, 0xf8, 0x74, 0xb0, 0xce, 0x39, 0x21, 0xba, 0x6e, 0xcc, 0x01, 0xd1, 0x55, + 0x63, 0x0e, 0x94, 0x13, 0xe4, 0xc4, 0x28, 0x3e, 0xe6, 0x24, 0x39, 0xd9, 0xfd, 0x89, 0x72, 0x8a, 0xfb, 0xa7, 0xca, 0x69, 0xee, 0x7f, 0x5a, 0x4e, 0x97, 0xcf, 0xc8, 0x19, 0x72, 0xa6, 0x7c, 0x56, 0x3e, 0xe7, 0xe7, 0x67, 0xc9, 0xd9, 0xee, 0x7f, 0x5e, 0xbe, 0xe0, 0xfe, 0x17, 0xe5, 0x4b, 0xd1, 0x55, 0xf5, 0xfb, 0x46, 0xaf, 0xd4, 0xef, 0x2f, 0xe3, 0xa2, 0x57, 0x1a, 0x77, 0x8c, 0x9e, 0x6a, 0xdc, 0x49, 0x76, 0x76, 0x7f, 0x17, 0xd9, 0x55, 0x76, 0xf3, 0xf5, 0xee, 0x6e, 0xf7, 0x90, 0x3d, 0xe5, 0xfd, 0xbe, 0xde, 0x4b, 0xf6, 0x96, 0x0f, 0xc8, 0x3e, 0xbe, 0xf7, 0x41, 0x19, 0x2b, 0x1f, 0xf2, 0xb5, 0xd7, 0x6b, 0xfc, 0xb0, 0xec, 0xa7, 0xf3, 0x3d, 0xe1, 0x8c, 0xfc, 0xd9, 0x19, 0x79, 0x34, 0x78, 0x11, 0xbb, 0x16, 0xa9, 0x96, 0x25, 0xd1, 0xeb, 0xce, 0x4a, 0xd2, 0x59, 0xc9, 0x38, 0x23, + 0xad, 0x41, 0x47, 0x94, 0x70, 0x56, 0xda, 0x9d, 0x95, 0x7c, 0xb0, 0xdc, 0x19, 0x79, 0x2d, 0x9a, 0x1c, 0xbc, 0xee, 0x2c, 0xad, 0x8c, 0x56, 0x38, 0x33, 0x2b, 0x9d, 0x99, 0x75, 0xc1, 0x6a, 0x55, 0x9f, 0x70, 0x9f, 0x1b, 0x07, 0xcc, 0x2a, 0xe0, 0xc5, 0x41, 0xda, 0xeb, 0xf4, 0x98, 0x33, 0x19, 0x86, 0x3a, 0x3e, 0xba, 0xce, 0x58, 0x5d, 0x6f, 0xac, 0xae, 0x77, 0xb6, 0x9e, 0x1a, 0x35, 0x33, 0x5a, 0x39, 0xea, 0x77, 0xd1, 0xa2, 0x51, 0x8f, 0xc8, 0xa3, 0xee, 0xff, 0x5e, 0xfe, 0x18, 0xad, 0xac, 0x5b, 0x11, 0xad, 0xad, 0x7b, 0x23, 0x5a, 0xe9, 0x6c, 0x3e, 0x5a, 0xf9, 0xcc, 0xde, 0x13, 0xe4, 0xc4, 0x68, 0xb5, 0xb3, 0xb9, 0xda, 0xd9, 0x7c, 0xd4, 0xd9, 0x7c, 0xd4, 0xd9, 0x5c, 0xed, 0x6c, 0xae, 0x76, 0x36, 0x1f, 0x75, 0x36, 0x1f, 0x75, 0x36, 0x1f, 0x75, 0x36, 0x1f, 0x75, 0x36, 0x1f, + 0x75, 0x36, 0x1f, 0x75, 0x36, 0x1f, 0x75, 0x36, 0x57, 0x3b, 0x9b, 0xab, 0x9d, 0xcd, 0x47, 0x9d, 0xcd, 0x47, 0x9d, 0xcd, 0xd5, 0xce, 0xe6, 0x6a, 0x67, 0xf3, 0xd1, 0xfa, 0x4f, 0x47, 0xd9, 0xfa, 0x6b, 0xa3, 0x39, 0xf5, 0x37, 0x46, 0x2d, 0xf5, 0x37, 0x45, 0x7d, 0xf5, 0xdf, 0x73, 0x7b, 0x9b, 0xdb, 0x3b, 0xe4, 0x4e, 0x99, 0xe2, 0xeb, 0xa9, 0xf2, 0xa3, 0xa8, 0xad, 0x7e, 0x01, 0x8f, 0x3f, 0x24, 0xea, 0x6e, 0xf8, 0x82, 0xdb, 0x2f, 0xca, 0x65, 0x72, 0xb9, 0x5c, 0x21, 0x57, 0xca, 0x55, 0x32, 0x49, 0xbe, 0x29, 0x57, 0x47, 0x2b, 0x1b, 0xf7, 0x8f, 0x5e, 0x6f, 0x1c, 0x27, 0x1f, 0x91, 0x8f, 0xca, 0xc7, 0xe4, 0x00, 0x39, 0x50, 0x0e, 0x92, 0xf1, 0x72, 0xb0, 0x1c, 0x22, 0x87, 0xca, 0xc7, 0xe5, 0x30, 0xf9, 0x84, 0x1c, 0x2e, 0x47, 0xc8, 0x91, 0x72, 0x94, 0x1c, 0x2d, 0xc7, 0xc8, 0xb1, + 0xf2, 0x49, 0x99, 0x20, 0xc7, 0xc9, 0xa7, 0xe4, 0xf8, 0x28, 0xd1, 0x78, 0x82, 0x9c, 0x28, 0x27, 0xc9, 0xc9, 0x32, 0x51, 0x4e, 0x91, 0x53, 0xe5, 0x34, 0xf9, 0x7a, 0xb4, 0xb6, 0xf1, 0x12, 0xb9, 0x4c, 0x2e, 0x97, 0x2b, 0xe4, 0x4a, 0xb9, 0x4a, 0x26, 0xc9, 0x37, 0xe5, 0x6a, 0xb9, 0x46, 0xae, 0x95, 0x6f, 0xc9, 0x75, 0xf2, 0x6d, 0xb9, 0x5e, 0x6e, 0x90, 0xef, 0xc8, 0x8d, 0xd1, 0xa2, 0xc6, 0x9b, 0xe4, 0x7b, 0xec, 0xf1, 0x66, 0xf9, 0xbe, 0x4c, 0x96, 0x5b, 0xe4, 0x56, 0xb9, 0x4d, 0x6e, 0x97, 0x1f, 0xc8, 0x0f, 0xe5, 0x0e, 0xb9, 0x53, 0xa6, 0xc8, 0x54, 0x99, 0x26, 0x77, 0xc9, 0xdd, 0x72, 0x8f, 0xfc, 0x48, 0xee, 0x8d, 0xba, 0x1b, 0xef, 0x93, 0xe9, 0xf2, 0x53, 0xf9, 0x99, 0xfc, 0x5c, 0xee, 0x97, 0x07, 0xe4, 0x17, 0xf2, 0xa0, 0xfc, 0x52, 0x66, 0xc8, 0xaf, 0xe4, 0xd7, 0x32, 0x53, + 0x1e, 0x92, 0xdf, 0xc8, 0x6f, 0xe5, 0x61, 0xf9, 0x9d, 0x3c, 0x22, 0x8f, 0xca, 0x2c, 0xf9, 0xbd, 0xda, 0x7c, 0x36, 0x58, 0xa8, 0x6b, 0x2e, 0xd6, 0x0d, 0x96, 0x98, 0xcb, 0x6d, 0x2a, 0x71, 0xa9, 0x39, 0xfc, 0x72, 0xf4, 0x23, 0x15, 0xfd, 0xac, 0x8a, 0x7e, 0x3c, 0x78, 0x25, 0x6a, 0x56, 0xd1, 0x4f, 0x98, 0xe7, 0xdf, 0x57, 0xd5, 0xb3, 0x55, 0xf4, 0x12, 0x73, 0xfd, 0x77, 0x2a, 0xfa, 0x45, 0x15, 0xbd, 0x56, 0x45, 0xb7, 0xaa, 0xe8, 0xc9, 0x2a, 0x7a, 0xb2, 0x79, 0xff, 0x53, 0x55, 0x3d, 0x43, 0x55, 0x4f, 0x56, 0xd5, 0xdf, 0x36, 0xff, 0xd7, 0xa9, 0xec, 0x69, 0x2a, 0x7b, 0xba, 0xf9, 0x3f, 0x10, 0x3b, 0x21, 0x7a, 0x30, 0x76, 0x52, 0xf4, 0xe0, 0xa8, 0x5f, 0x45, 0x8b, 0x55, 0xf6, 0x64, 0x95, 0xdd, 0xaa, 0xb2, 0x5b, 0x55, 0xf6, 0x64, 0x95, 0x3d, 0x59, 0x65, 0x4f, 0xae, 0xbd, 0x39, + 0x7a, 0xa8, 0x76, 0x72, 0xf4, 0x90, 0x0a, 0x9f, 0xad, 0xc2, 0x27, 0xd7, 0x0d, 0x45, 0x8b, 0xc7, 0x5c, 0x11, 0x2d, 0xae, 0xdf, 0x56, 0xb6, 0x93, 0x0f, 0xc9, 0xbe, 0xd1, 0xda, 0xfa, 0xfd, 0xdc, 0xee, 0xef, 0x76, 0x9c, 0x7c, 0xc4, 0xfd, 0x8f, 0xca, 0x8d, 0xc1, 0x56, 0xf5, 0xdf, 0x93, 0x29, 0x32, 0x55, 0x16, 0x44, 0x93, 0x55, 0xe5, 0x12, 0x55, 0x39, 0x59, 0x55, 0x4e, 0x56, 0x95, 0x93, 0x55, 0xe5, 0x64, 0x55, 0x39, 0x59, 0x55, 0x4e, 0x56, 0x95, 0x93, 0x55, 0xe5, 0x64, 0x55, 0x39, 0x59, 0x55, 0x4e, 0x56, 0x95, 0x53, 0x55, 0xe5, 0x54, 0x55, 0x39, 0x55, 0x55, 0x4e, 0x55, 0x95, 0x53, 0x55, 0xe5, 0x54, 0x55, 0x39, 0x55, 0x55, 0x4e, 0x55, 0x95, 0x53, 0x55, 0xe5, 0x54, 0x55, 0x39, 0x55, 0x55, 0x4e, 0x55, 0x95, 0x53, 0x55, 0xe5, 0x54, 0x55, 0x39, 0x55, 0x55, 0x4e, 0x55, 0x95, + 0x53, 0x55, 0xe5, 0x54, 0x55, 0x39, 0x55, 0x55, 0x4e, 0x55, 0x95, 0x53, 0x55, 0xe5, 0x54, 0x55, 0x39, 0x55, 0x55, 0x4e, 0x55, 0x95, 0x53, 0x55, 0xe5, 0x54, 0x55, 0xf9, 0xb8, 0xaa, 0x7c, 0x5c, 0x55, 0x3e, 0xae, 0x2a, 0x1f, 0x57, 0x95, 0x8f, 0xab, 0xca, 0xc7, 0x55, 0xe5, 0xe3, 0xaa, 0xf2, 0x71, 0x55, 0xf9, 0x78, 0xe3, 0xa7, 0xa3, 0xe6, 0xc6, 0xcf, 0xc8, 0x19, 0x72, 0xa6, 0x7c, 0x56, 0x3e, 0x27, 0x67, 0xc9, 0xd9, 0xf2, 0x79, 0xf9, 0x82, 0x7c, 0x51, 0xbe, 0x24, 0xe7, 0xc8, 0xb9, 0x72, 0x9e, 0x7c, 0x59, 0xce, 0x97, 0x0b, 0xe4, 0x42, 0xf9, 0x8a, 0x7c, 0x55, 0x2e, 0x92, 0xaf, 0xc9, 0xc5, 0xf2, 0xf5, 0x68, 0xb6, 0xaa, 0x9f, 0xad, 0xea, 0x67, 0xab, 0xfa, 0xd9, 0xaa, 0x7e, 0xb6, 0xaa, 0x9f, 0xad, 0xea, 0x67, 0xab, 0xfa, 0xd9, 0xaa, 0x7e, 0xb6, 0xaa, 0x9f, 0xad, 0xea, 0x67, + 0xab, 0xfa, 0xd9, 0xaa, 0x7e, 0xb6, 0xaa, 0x9f, 0xad, 0xea, 0x67, 0xab, 0xfa, 0xd9, 0xaa, 0x7e, 0xb6, 0xaa, 0x9f, 0xad, 0xea, 0x5b, 0x55, 0x7d, 0xab, 0xaa, 0x9f, 0xa1, 0xea, 0x67, 0xa8, 0xfa, 0x19, 0xaa, 0x7e, 0x86, 0xaa, 0x9f, 0xa1, 0xea, 0x67, 0xa8, 0xfa, 0x19, 0xaa, 0x7e, 0x86, 0xaa, 0x9f, 0xa1, 0xea, 0x67, 0xa8, 0xfa, 0x19, 0xaa, 0x7e, 0x86, 0xaa, 0x9f, 0xa1, 0xea, 0x67, 0xa8, 0xfa, 0x19, 0xaa, 0x7e, 0x86, 0xaa, 0x9f, 0xa1, 0xea, 0x67, 0xa8, 0xfa, 0x19, 0xaa, 0x7e, 0xba, 0xaa, 0x9f, 0xae, 0xea, 0xa7, 0xab, 0xfa, 0xe9, 0xaa, 0x7e, 0xba, 0xaa, 0x9f, 0xae, 0xea, 0xa7, 0xab, 0xfa, 0xe9, 0xaa, 0x7e, 0xba, 0xaa, 0x9f, 0xae, 0xea, 0xa7, 0xab, 0xfa, 0xe9, 0xaa, 0x7e, 0xba, 0xaa, 0x9f, 0xae, 0xea, 0xa7, 0xab, 0xfa, 0xe9, 0xaa, 0x7e, 0xba, 0xaa, 0x9f, 0xae, 0xea, 0xa7, + 0xab, 0xfa, 0xe9, 0xaa, 0x7e, 0xba, 0xaa, 0x9f, 0xae, 0xea, 0xa7, 0xab, 0xfa, 0xe9, 0x8d, 0x4f, 0x47, 0x8b, 0x1b, 0x5f, 0x8f, 0x1e, 0x0c, 0x1a, 0x55, 0x7a, 0xb3, 0xca, 0x2e, 0xe9, 0xd5, 0x1d, 0xaa, 0x7a, 0x4d, 0xa5, 0xa2, 0xbb, 0x19, 0xff, 0x5a, 0x5e, 0xf8, 0x16, 0xb9, 0xdb, 0x2b, 0x9f, 0xfb, 0x78, 0x96, 0xf9, 0x51, 0x30, 0x37, 0xd6, 0x7b, 0xc6, 0x90, 0xb9, 0x91, 0x33, 0x37, 0xfa, 0x75, 0xfb, 0x7c, 0xe5, 0x53, 0x7a, 0x57, 0xba, 0xdf, 0xe5, 0xe7, 0x09, 0xf7, 0xd7, 0x48, 0xaa, 0xf2, 0xc9, 0xbd, 0x59, 0x75, 0xdf, 0xaf, 0xce, 0xf3, 0xea, 0x3b, 0xaf, 0xbe, 0xf3, 0xea, 0x3b, 0xaf, 0xa6, 0xf3, 0xea, 0x32, 0xaf, 0x26, 0xf3, 0x6a, 0x32, 0xaf, 0x26, 0xf3, 0x6a, 0x32, 0xaf, 0x26, 0xf3, 0x6a, 0x32, 0xaf, 0x26, 0xf3, 0x6a, 0x32, 0xaf, 0x26, 0xf3, 0x6a, 0x32, 0xaf, 0x26, 0x0b, + 0x6a, 0xb2, 0xa0, 0x26, 0x0b, 0x6a, 0xb2, 0xa0, 0x26, 0x0b, 0x6a, 0xb2, 0xa0, 0x26, 0x0b, 0x6a, 0xb2, 0xa0, 0x26, 0x0b, 0x6a, 0xb2, 0xa0, 0x26, 0x0b, 0x6a, 0xb2, 0xa0, 0x26, 0x0b, 0x6a, 0xb2, 0xa0, 0x26, 0x0b, 0x6a, 0xb2, 0xa0, 0x26, 0x0b, 0x6a, 0xb2, 0xa0, 0x26, 0x0b, 0x6a, 0xb2, 0xa0, 0x26, 0x0b, 0x6a, 0xb2, 0xa0, 0x26, 0x0b, 0x6a, 0xb2, 0xa0, 0x26, 0x0b, 0x6a, 0xb2, 0xa0, 0x26, 0xfb, 0xd5, 0x64, 0xbf, 0x9a, 0xec, 0x57, 0x93, 0xfd, 0x6a, 0xb2, 0x5f, 0x4d, 0xf6, 0xab, 0xc9, 0x7e, 0x35, 0xd9, 0xaf, 0x26, 0xfb, 0x8d, 0x4b, 0xbf, 0x71, 0xe9, 0x37, 0x2e, 0xfd, 0xc6, 0xa5, 0xdf, 0xb8, 0xf4, 0x1b, 0x97, 0x7e, 0xe3, 0xd2, 0x6f, 0x5c, 0xfa, 0x8d, 0x4b, 0xbf, 0x71, 0xe9, 0x37, 0x2e, 0xfd, 0xc6, 0xa5, 0xdf, 0xb8, 0xf4, 0x1b, 0x97, 0x7e, 0xe3, 0xd2, 0x6f, 0x5c, 0xfa, 0x8d, + 0x4b, 0xbf, 0x71, 0xe9, 0x37, 0x2e, 0xfd, 0xc6, 0xa5, 0xdf, 0xb8, 0xf4, 0x1b, 0x97, 0x7e, 0xe3, 0xd2, 0x6f, 0x5c, 0xfa, 0x83, 0xcd, 0x9c, 0xd9, 0x3e, 0x67, 0x75, 0xd0, 0x78, 0x3c, 0xe9, 0x0c, 0x0e, 0xe1, 0xe1, 0x4b, 0x78, 0xb8, 0x18, 0x0f, 0x17, 0xc7, 0x8e, 0x74, 0xff, 0x28, 0x99, 0x50, 0xfe, 0xb7, 0xc6, 0x8a, 0xc1, 0x55, 0xfe, 0x7a, 0x35, 0xd8, 0xdc, 0x98, 0xbc, 0xe8, 0x19, 0x2f, 0x57, 0x47, 0x30, 0x34, 0x82, 0xcb, 0x8d, 0xe0, 0xe2, 0x7f, 0xef, 0xda, 0xd0, 0xa8, 0x87, 0x8c, 0xd1, 0xc9, 0x46, 0x35, 0x6b, 0x9b, 0xab, 0x2a, 0x36, 0x93, 0x70, 0x7f, 0x8d, 0xa4, 0x24, 0x8d, 0xe5, 0x99, 0xa8, 0xa4, 0x63, 0xbd, 0xa2, 0x63, 0xbd, 0x62, 0x14, 0xb3, 0x46, 0x31, 0x6b, 0x14, 0xb3, 0x46, 0x31, 0x6b, 0x14, 0xb3, 0x46, 0x31, 0x6b, 0x14, 0xb3, 0x46, 0x31, 0x6b, 0x14, 0xb3, 0x46, + 0x31, 0x6b, 0x14, 0xb3, 0x46, 0x31, 0x6b, 0x14, 0xb3, 0x46, 0x31, 0x6b, 0x14, 0xb3, 0x46, 0x31, 0x6b, 0x14, 0xb3, 0x46, 0x31, 0x6b, 0x14, 0xb3, 0x46, 0x31, 0x6b, 0x14, 0xb3, 0x46, 0x31, 0x6b, 0x14, 0xb3, 0x46, 0x31, 0x6b, 0x14, 0xb3, 0x46, 0x31, 0x6b, 0x14, 0xb3, 0x46, 0x31, 0x6b, 0x14, 0xb3, 0x46, 0x31, 0x6b, 0x14, 0xb3, 0x46, 0x31, 0x6b, 0x14, 0xb3, 0x46, 0x31, 0x6b, 0x14, 0xb3, 0x46, 0x31, 0x6b, 0x14, 0xb3, 0x46, 0x31, 0x6b, 0x14, 0xb3, 0x46, 0x31, 0x6b, 0x14, 0xb3, 0x46, 0x31, 0x6b, 0x84, 0x4a, 0x46, 0xa8, 0x64, 0x84, 0x4a, 0x46, 0xa8, 0x64, 0x84, 0x4a, 0x46, 0xa8, 0x64, 0x84, 0x4a, 0x46, 0xa8, 0x64, 0x84, 0x4a, 0x46, 0xa8, 0x64, 0x84, 0x4a, 0x46, 0xa8, 0x64, 0x84, 0x4a, 0x46, 0xa8, 0x64, 0x84, 0x4a, 0x46, 0xa8, 0x64, 0x84, 0x4a, 0x46, 0xa8, 0x64, 0x84, 0x4a, + 0x46, 0xa8, 0x64, 0x84, 0x4a, 0x46, 0xa8, 0x64, 0x84, 0x4a, 0x46, 0xa8, 0x64, 0xd6, 0xbc, 0x12, 0xfc, 0x2c, 0xe8, 0x8b, 0x8a, 0x41, 0xbf, 0x79, 0x30, 0xec, 0x1c, 0x8e, 0x44, 0xc3, 0x41, 0x64, 0x5d, 0x5b, 0x13, 0x0d, 0xc7, 0x1a, 0x18, 0x7c, 0x53, 0x34, 0x18, 0xdb, 0x82, 0x71, 0x6e, 0x69, 0x2d, 0xb1, 0x55, 0xd4, 0x15, 0x7b, 0xaf, 0x6c, 0x2d, 0xdb, 0x18, 0xb1, 0x6d, 0xdd, 0x6e, 0x67, 0x46, 0x6d, 0xef, 0xf6, 0x7d, 0xb2, 0x43, 0xb4, 0x3a, 0xb6, 0xa3, 0xfc, 0x8b, 0xfb, 0x3b, 0xc9, 0xce, 0xb2, 0x8b, 0xf5, 0xc4, 0xae, 0x6e, 0x77, 0xf3, 0xf8, 0x3d, 0xdc, 0xdf, 0xd3, 0xfd, 0xf7, 0xcb, 0x5e, 0x46, 0x7b, 0x6f, 0xb7, 0x1f, 0x90, 0x7d, 0x98, 0xf0, 0x07, 0xdd, 0x8e, 0x95, 0x0f, 0x59, 0x09, 0xec, 0xeb, 0x71, 0x1f, 0x76, 0x7f, 0x3f, 0xaf, 0x3d, 0xae, 0xfc, 0x6f, 0x57, 0xd6, 0x63, + 0x1b, 0x3e, 0x71, 0xe2, 0xad, 0xeb, 0x2e, 0xbd, 0xa3, 0x9e, 0x89, 0x86, 0x46, 0x3d, 0x67, 0x0e, 0xbe, 0x10, 0x0d, 0x8f, 0x6a, 0x96, 0x56, 0x79, 0xd9, 0xd7, 0x89, 0xa8, 0x6b, 0xd4, 0x1a, 0xe9, 0x96, 0xa4, 0xa4, 0x24, 0x2d, 0x6b, 0xa5, 0x47, 0x32, 0xd1, 0xf2, 0x51, 0x59, 0xb7, 0xa1, 0xe4, 0x24, 0xcf, 0xc4, 0x7a, 0x7d, 0xaf, 0x2f, 0x4a, 0x8d, 0xea, 0x77, 0x3b, 0x10, 0xc5, 0x47, 0x0d, 0xca, 0x1b, 0x7e, 0x56, 0x94, 0xe1, 0x68, 0xf5, 0xa8, 0x92, 0xdb, 0x11, 0x79, 0xd3, 0xf7, 0xa3, 0xa8, 0xab, 0x36, 0x16, 0x65, 0x6b, 0x6b, 0xac, 0xa5, 0x46, 0xb9, 0x5f, 0x2b, 0x75, 0x51, 0xaa, 0x76, 0xb4, 0x8c, 0xf1, 0xbd, 0xc6, 0x68, 0x75, 0x6d, 0x93, 0xef, 0x1d, 0x67, 0x6d, 0xfb, 0x29, 0x39, 0x41, 0x4e, 0xf2, 0xf5, 0xc9, 0xd1, 0x8a, 0xda, 0x89, 0x6e, 0x4f, 0x89, 0x06, 0x6b, 0x4f, 0x75, + 0x7b, 0x5a, 0xb4, 0xb6, 0xf6, 0xd3, 0x1e, 0x7f, 0xba, 0x7c, 0x26, 0x5a, 0x53, 0x5b, 0x5e, 0x3f, 0xdf, 0x13, 0x85, 0x1c, 0xbf, 0xb7, 0x2e, 0x8c, 0xda, 0xeb, 0x72, 0xd1, 0xa2, 0xba, 0xc1, 0x28, 0x3f, 0xe6, 0xa0, 0x68, 0x78, 0xcc, 0x39, 0x72, 0xae, 0xe8, 0x2d, 0xf5, 0xdb, 0xcb, 0xc7, 0xe4, 0x97, 0x2c, 0xed, 0x4f, 0xf2, 0x84, 0xfc, 0x45, 0xe6, 0x47, 0x8b, 0xea, 0x17, 0xc9, 0x32, 0xe9, 0x94, 0xb5, 0xd1, 0xa2, 0x86, 0x46, 0x71, 0xbe, 0x1b, 0x8e, 0x91, 0x2f, 0xb9, 0x7f, 0x8d, 0x7c, 0xdb, 0xfd, 0x1b, 0x65, 0x6a, 0xb4, 0xa6, 0x61, 0x41, 0x94, 0x6b, 0xe8, 0x8a, 0x52, 0x0d, 0xe9, 0x68, 0x59, 0xc3, 0x5a, 0xe9, 0x95, 0x82, 0xac, 0x8b, 0x96, 0x35, 0xfe, 0x84, 0x51, 0x3c, 0x1e, 0x0d, 0x35, 0xfe, 0x51, 0x9e, 0x89, 0x86, 0x1b, 0xff, 0x22, 0xb3, 0x65, 0x8e, 0xfc, 0x55, 0xe6, 0x45, + 0x5d, 0x8d, 0xf3, 0xe5, 0x79, 0x69, 0x8e, 0xda, 0x1b, 0x17, 0xba, 0x6d, 0x91, 0x97, 0x64, 0x91, 0x2c, 0x96, 0x25, 0x51, 0xbc, 0xb1, 0xcd, 0xed, 0xd2, 0xa8, 0xa3, 0xe9, 0x99, 0x28, 0xdf, 0x34, 0x87, 0x2d, 0xef, 0x1e, 0xab, 0x8b, 0x2e, 0x52, 0x55, 0x6f, 0xc4, 0x36, 0x8f, 0xbe, 0xab, 0xb2, 0x4a, 0x46, 0xff, 0x3e, 0x23, 0x9d, 0x35, 0xd2, 0xb3, 0xaa, 0xff, 0x46, 0xf8, 0x62, 0xf5, 0x77, 0x22, 0x0a, 0xd5, 0x7e, 0xdd, 0x6b, 0x86, 0x5f, 0x3b, 0x6a, 0x7e, 0x74, 0xff, 0xa8, 0x57, 0xa2, 0x01, 0x67, 0x7f, 0x85, 0xb3, 0xdd, 0x5b, 0x7b, 0x74, 0xd4, 0x57, 0x3b, 0x21, 0x2a, 0x3a, 0xb3, 0x59, 0x67, 0x75, 0x8d, 0x33, 0xba, 0xdc, 0x19, 0x6c, 0x2f, 0xff, 0x1d, 0x79, 0xdd, 0x01, 0xd1, 0x39, 0x75, 0x07, 0x46, 0x67, 0x8c, 0xb9, 0x20, 0xea, 0x71, 0xa4, 0x25, 0x47, 0x5a, 0x6a, 0xdc, 0x41, + 0xf7, 0xfd, 0x89, 0xdb, 0x15, 0x56, 0x3d, 0x3f, 0x56, 0xf3, 0x17, 0xab, 0xf3, 0x3f, 0xdb, 0x93, 0xdf, 0xd9, 0x93, 0x0e, 0x7b, 0x72, 0xa9, 0x1a, 0xff, 0x89, 0x1a, 0x1f, 0xa7, 0xc6, 0xc7, 0xa9, 0xf1, 0x71, 0xb1, 0x6d, 0xde, 0x7c, 0x40, 0x8d, 0x1f, 0xaa, 0xc6, 0x37, 0x57, 0xe3, 0xe3, 0xd4, 0xf8, 0xb8, 0xd8, 0x0e, 0x6f, 0x4e, 0x8f, 0xed, 0x28, 0xff, 0xe2, 0xfe, 0x4e, 0xb2, 0xb3, 0xec, 0xf2, 0x66, 0x4e, 0x8d, 0x8f, 0x53, 0xe3, 0x27, 0xc4, 0xf6, 0x78, 0xb3, 0xa0, 0xc6, 0xc7, 0xa9, 0xf1, 0x71, 0x6a, 0xfc, 0x08, 0x35, 0x3e, 0x4e, 0x8d, 0x8f, 0x73, 0x94, 0x27, 0xa8, 0xf1, 0x71, 0x6a, 0x7c, 0x9c, 0x1a, 0x6f, 0x8c, 0xed, 0xeb, 0x39, 0x1f, 0x76, 0x7f, 0xbf, 0x37, 0xfb, 0x62, 0xfb, 0x07, 0x3b, 0x3b, 0xfa, 0xb3, 0xd4, 0xf9, 0x04, 0x5e, 0x35, 0x35, 0x76, 0x48, 0x34, 0x2d, 0x76, + 0x74, 0x74, 0x46, 0xec, 0x98, 0xe8, 0xe2, 0xd8, 0xb1, 0x6e, 0x3f, 0xe9, 0x76, 0x42, 0x74, 0xbe, 0xee, 0x75, 0xad, 0xee, 0x75, 0xf5, 0xa8, 0xce, 0x68, 0xe2, 0xa8, 0x57, 0x25, 0x11, 0x8d, 0x53, 0xeb, 0xe3, 0xd4, 0xfa, 0xb1, 0x6a, 0x7d, 0x9c, 0x5a, 0x3f, 0x54, 0xad, 0x8f, 0x53, 0xeb, 0xe3, 0xd4, 0xfa, 0x38, 0xb5, 0xfe, 0x51, 0xb5, 0x3e, 0x4e, 0xad, 0x8f, 0x53, 0xeb, 0xe3, 0x46, 0xe5, 0xdf, 0xcc, 0xa9, 0xf5, 0x13, 0x46, 0x15, 0xa2, 0x53, 0x46, 0xad, 0x93, 0xbe, 0xe8, 0x78, 0x35, 0x7f, 0x82, 0x9a, 0x3f, 0x42, 0xcd, 0x1f, 0xa1, 0xe6, 0xc7, 0xa9, 0xf9, 0x71, 0xa3, 0x86, 0xdf, 0x9c, 0xae, 0xe6, 0xc7, 0xa9, 0xf9, 0x71, 0x6a, 0xfe, 0x08, 0x35, 0x3f, 0xae, 0x36, 0x88, 0x62, 0xce, 0xfc, 0xb8, 0xda, 0x9a, 0x37, 0x73, 0xea, 0x7e, 0x9c, 0xba, 0x1f, 0xa7, 0xee, 0x8f, 0x37, 0x12, + 0xc7, 0xd7, 0x8e, 0xf1, 0xbd, 0xc6, 0x37, 0xa7, 0xab, 0xfb, 0x71, 0x46, 0xe5, 0x38, 0xf5, 0x3e, 0xae, 0xf6, 0xe4, 0x37, 0xbf, 0xa0, 0xde, 0xc7, 0x19, 0x99, 0x83, 0xd4, 0xfb, 0x38, 0xa3, 0x73, 0x67, 0xed, 0xa7, 0x3d, 0xee, 0x74, 0xf9, 0x4c, 0xb4, 0x4b, 0xed, 0x19, 0x6e, 0x6f, 0x8e, 0x6e, 0xe6, 0x86, 0x37, 0xd6, 0xde, 0x12, 0xdd, 0x5e, 0x7b, 0x5b, 0x74, 0xbb, 0xba, 0xdf, 0xbc, 0x2e, 0xf7, 0x66, 0xce, 0xa8, 0xed, 0x31, 0x26, 0x13, 0x9d, 0x5c, 0x7f, 0x42, 0x54, 0xac, 0xff, 0xe5, 0x9b, 0xeb, 0xd4, 0xf9, 0x49, 0xea, 0xfc, 0x80, 0xfa, 0xbf, 0xb8, 0x3f, 0xff, 0xcd, 0x5c, 0xfd, 0x22, 0x59, 0x26, 0x9d, 0xd1, 0x89, 0xea, 0x7c, 0x4c, 0x43, 0xe3, 0x9b, 0xb9, 0x86, 0x3d, 0x83, 0x9d, 0x1a, 0xde, 0x1f, 0xec, 0xa1, 0xd6, 0xc7, 0x35, 0x7c, 0xc9, 0xd7, 0xd7, 0x44, 0x9f, 0x54, 0xeb, + 0xe3, 0xd4, 0xfa, 0xb8, 0x86, 0xa9, 0x6f, 0xbe, 0xaa, 0x02, 0xce, 0x53, 0xeb, 0xc7, 0xab, 0xf5, 0x93, 0xd5, 0xfa, 0xd7, 0xd4, 0xfa, 0x99, 0x6a, 0xfd, 0x02, 0xb5, 0x7e, 0xb2, 0xaa, 0xd8, 0x5f, 0x3d, 0x8f, 0x53, 0xcf, 0x87, 0xaa, 0xe7, 0x43, 0xd5, 0xf3, 0x24, 0xf5, 0x7c, 0x8e, 0x7a, 0x3e, 0x50, 0x3d, 0x1f, 0xa8, 0x9e, 0xc7, 0xa9, 0xe7, 0x71, 0xea, 0xf9, 0x14, 0xf5, 0x3c, 0x4e, 0x3d, 0x9f, 0xd7, 0xf8, 0x72, 0x34, 0x47, 0xf7, 0xbc, 0x5a, 0x35, 0xb5, 0x34, 0xcd, 0x8e, 0x3e, 0xd6, 0x34, 0xe7, 0xcd, 0xf5, 0xc1, 0x0d, 0xba, 0xe7, 0x3a, 0x55, 0xb5, 0x5e, 0xf7, 0x1c, 0x50, 0xe7, 0xc9, 0xd8, 0x7b, 0xa2, 0x35, 0xb1, 0xcd, 0x64, 0x0b, 0xf5, 0xbe, 0xa5, 0x5a, 0xdf, 0x2a, 0x2a, 0xaa, 0xae, 0xa2, 0xea, 0x2a, 0xaa, 0xaa, 0x92, 0xaa, 0x2a, 0xaa, 0xaa, 0xa2, 0x6a, 0x2a, 0xaa, 0xa6, + 0xa2, 0x6a, 0x2a, 0xea, 0x98, 0x25, 0xd5, 0x54, 0x54, 0x4d, 0x33, 0x74, 0xcc, 0x92, 0x6a, 0x2a, 0xaa, 0xa6, 0xa2, 0x6a, 0xea, 0x56, 0x4d, 0x45, 0xd5, 0x54, 0x54, 0x4d, 0xbf, 0x53, 0x4d, 0x45, 0xd5, 0x54, 0xd4, 0x2d, 0x4b, 0x2a, 0xa9, 0x18, 0x63, 0x09, 0xb1, 0x4f, 0x44, 0x99, 0x18, 0x4b, 0x88, 0x1d, 0xe1, 0xf6, 0xcc, 0x28, 0xb1, 0xe1, 0x3d, 0x88, 0x47, 0xfd, 0x35, 0x5a, 0x33, 0x6a, 0x9e, 0xcc, 0x67, 0xe7, 0x0b, 0xdc, 0xbe, 0x10, 0x0d, 0xe8, 0x9a, 0x03, 0xa3, 0x5a, 0xa2, 0xd7, 0x46, 0xbd, 0x28, 0xad, 0xee, 0x27, 0xa2, 0xa2, 0x6a, 0x2a, 0xaa, 0xa6, 0xa2, 0x6a, 0x2a, 0xaa, 0xa4, 0xa2, 0x4a, 0x2a, 0xaa, 0xa4, 0xa2, 0x4a, 0x9a, 0xa1, 0x92, 0x8a, 0x2a, 0xa9, 0xa8, 0x92, 0x8a, 0xba, 0x66, 0x49, 0x25, 0xcd, 0x50, 0x49, 0x7f, 0x51, 0x49, 0x7f, 0x51, 0x45, 0x33, 0x54, 0x51, + 0xb7, 0x2a, 0xea, 0x56, 0x45, 0xc5, 0x51, 0xeb, 0xa3, 0xc4, 0xa8, 0x21, 0x29, 0xba, 0x5f, 0x92, 0x11, 0x79, 0xd3, 0xcf, 0x22, 0xf3, 0x35, 0x88, 0x4a, 0x2a, 0xa9, 0x5d, 0x07, 0x2d, 0xa9, 0xa4, 0xa2, 0x4a, 0x2a, 0xea, 0x9c, 0x25, 0xd5, 0x53, 0x54, 0x39, 0x45, 0x55, 0x53, 0x54, 0x31, 0x45, 0xdd, 0xb1, 0xa4, 0x3b, 0x96, 0x74, 0xc6, 0x92, 0xca, 0x28, 0xe9, 0x88, 0xa5, 0xfa, 0xd3, 0xa3, 0x35, 0xf5, 0xd3, 0xa2, 0x56, 0xdd, 0xae, 0xa4, 0xdb, 0x95, 0x74, 0xbb, 0x92, 0x2a, 0x28, 0xa9, 0x82, 0x92, 0x6e, 0x57, 0xd2, 0xed, 0x8a, 0x2a, 0xa0, 0xa8, 0xdb, 0x95, 0x54, 0x40, 0x49, 0x05, 0x14, 0x55, 0x40, 0xd1, 0xe8, 0x0f, 0x19, 0xf9, 0x27, 0x8d, 0xfc, 0x93, 0x46, 0xfe, 0x49, 0x23, 0xff, 0xa4, 0x91, 0x7f, 0x52, 0x67, 0x5b, 0xa7, 0xb3, 0xad, 0xd3, 0xd9, 0xd6, 0xe9, 0x6c, 0xeb, 0x74, + 0xb6, 0x01, 0x95, 0x50, 0x54, 0x01, 0x25, 0x15, 0x50, 0x54, 0x01, 0x45, 0x15, 0x50, 0x54, 0x01, 0x45, 0x15, 0x50, 0x54, 0x01, 0xdd, 0x2a, 0xa0, 0x68, 0xd4, 0x07, 0x8d, 0x7a, 0x49, 0x47, 0x2b, 0x05, 0xb1, 0xd8, 0x82, 0xa0, 0x36, 0x08, 0x82, 0x15, 0x35, 0x3f, 0x19, 0x75, 0x49, 0xed, 0xde, 0xa3, 0x77, 0x1b, 0xbd, 0x5b, 0x53, 0x76, 0xab, 0xfb, 0x46, 0x5f, 0x3c, 0xe6, 0xb0, 0xfa, 0xef, 0x34, 0xbc, 0xbf, 0xe9, 0xec, 0xa6, 0x6c, 0x53, 0x76, 0xbb, 0x1f, 0xbd, 0x67, 0xc2, 0x66, 0xbb, 0xf9, 0x6f, 0xe2, 0x16, 0xfb, 0x6e, 0xf7, 0xa3, 0xb7, 0xb2, 0xc7, 0x45, 0x5b, 0xdc, 0xbd, 0xc7, 0x45, 0x5b, 0xdd, 0xf7, 0xde, 0xc3, 0xb7, 0xf9, 0xd3, 0xb6, 0x67, 0x6f, 0xf7, 0xa3, 0xed, 0x16, 0x6f, 0x5f, 0xbb, 0x7d, 0xed, 0x4e, 0xbb, 0x6d, 0x7f, 0xf8, 0xce, 0x63, 0xb7, 0xbf, 0xe3, 0x7d, 0xef, + 0x7d, 0xdf, 0x61, 0xef, 0xbb, 0xf8, 0x7d, 0xd9, 0xf7, 0x65, 0x77, 0x3a, 0x7a, 0x87, 0xe4, 0x0e, 0xef, 0xdd, 0xe1, 0xe7, 0x3b, 0x24, 0x77, 0xdc, 0x69, 0xc7, 0x09, 0x3b, 0xde, 0xb7, 0xd3, 0xd1, 0xfe, 0xdb, 0x6d, 0xc7, 0x8e, 0x7f, 0xf9, 0x8c, 0xff, 0x1e, 0x28, 0xdf, 0x7f, 0x2b, 0x7b, 0xd7, 0xee, 0x74, 0xdb, 0xde, 0xb5, 0x3b, 0x8f, 0xf5, 0xdf, 0xb9, 0x3b, 0x4f, 0xd9, 0xe5, 0x7d, 0xe5, 0xff, 0xf6, 0xc8, 0xed, 0x32, 0x61, 0x97, 0x4b, 0xfc, 0x77, 0x8b, 0xff, 0xee, 0xdb, 0xed, 0xa1, 0x3d, 0x2e, 0xda, 0x23, 0xb7, 0x67, 0xfd, 0x5e, 0xbb, 0xec, 0xb5, 0xcf, 0x5e, 0x67, 0xfb, 0xef, 0xa2, 0xbd, 0x2e, 0xdd, 0xeb, 0xda, 0xbd, 0x5a, 0xf6, 0xae, 0xdd, 0xfb, 0xcc, 0xbd, 0x5f, 0xfa, 0xc0, 0x1e, 0xe7, 0x8f, 0xfe, 0xc0, 0x49, 0x1f, 0x78, 0xf5, 0x63, 0x7f, 0x3a, 0xf0, 0xe8, 0x83, 0x5a, + 0xc7, 0x37, 0x96, 0x33, 0x7e, 0xfe, 0xc1, 0x87, 0x1e, 0x7a, 0xea, 0x11, 0xfb, 0x1d, 0xb3, 0xdb, 0x31, 0x57, 0x1f, 0x73, 0xdf, 0x31, 0x33, 0x26, 0xec, 0x36, 0x61, 0xbf, 0x09, 0x47, 0x9e, 0xfc, 0xc7, 0x93, 0x5f, 0x3a, 0x79, 0xc5, 0xc9, 0xbd, 0x13, 0x6b, 0x26, 0xd6, 0x4f, 0xdc, 0x7c, 0xe2, 0x36, 0x13, 0x77, 0x9c, 0xb8, 0xdb, 0xc4, 0xbd, 0x27, 0xee, 0x3b, 0xf1, 0xa0, 0x89, 0x87, 0x4d, 0x9c, 0xf8, 0xe9, 0xeb, 0x2f, 0x3f, 0xf6, 0xcc, 0xdf, 0xf9, 0x6f, 0xfe, 0x67, 0x17, 0x9f, 0x9d, 0x3e, 0x6f, 0xbf, 0x2f, 0xbf, 0xe7, 0xfc, 0xd1, 0xe7, 0x1f, 0x7c, 0xfe, 0x09, 0xe7, 0xbf, 0x74, 0xc1, 0xe1, 0x17, 0xfc, 0xfa, 0x82, 0x5f, 0x5f, 0xf9, 0xc7, 0x0b, 0xb7, 0xbb, 0xf0, 0xb6, 0x0b, 0xff, 0x7c, 0xe1, 0xfc, 0x0b, 0xdb, 0xbe, 0xf1, 0xf2, 0xa5, 0x35, 0x97, 0x7e, 0xe6, 0xd2, 0x29, 0x97, + 0xd5, 0x5f, 0x36, 0xf1, 0xb2, 0x8b, 0x2e, 0x9b, 0x71, 0xf9, 0xb1, 0x57, 0x8c, 0xbd, 0xe2, 0xe0, 0x2b, 0x26, 0x5c, 0x71, 0xee, 0x15, 0x57, 0x5e, 0x71, 0xc7, 0x15, 0x7f, 0xbc, 0x62, 0xf6, 0x15, 0x2d, 0x57, 0xbc, 0x72, 0x45, 0xf1, 0xca, 0x83, 0xaf, 0xbc, 0xe5, 0xca, 0x07, 0xaf, 0xfc, 0xe3, 0x55, 0x57, 0xfe, 0x1f, 0xff, 0xf5, 0xfb, 0xaf, 0xfe, 0x1d, 0xbe, 0xff, 0xce, 0x8f, 0x7c, 0x97, 0xff, 0x82, 0x9a, 0x60, 0x8f, 0x51, 0xcf, 0x8c, 0x7a, 0x35, 0x08, 0x46, 0xbd, 0x3e, 0x6a, 0x45, 0xb0, 0xfb, 0xa8, 0x44, 0xed, 0x98, 0x60, 0xcf, 0xda, 0xc6, 0xda, 0xa6, 0xe0, 0x53, 0xb5, 0x47, 0xd6, 0x9e, 0x11, 0x9c, 0x50, 0xfb, 0xa5, 0xda, 0x2f, 0x05, 0xe7, 0xd6, 0x4e, 0xaa, 0x9d, 0x14, 0x9c, 0x57, 0x7b, 0x6d, 0xed, 0xb5, 0xc1, 0x97, 0x6b, 0xbf, 0x5b, 0xfb, 0xdd, 0xe0, 0xfc, 0xda, 0x9b, + 0x6b, 0x6f, 0x0e, 0x2e, 0xa8, 0x9d, 0x5c, 0x7b, 0x7b, 0x70, 0x61, 0xed, 0xd4, 0xda, 0x69, 0xc1, 0xc5, 0xb5, 0x77, 0xd7, 0xde, 0x1d, 0x5c, 0x52, 0xfb, 0xa3, 0xda, 0x1f, 0x05, 0xdf, 0xa8, 0xbd, 0xb7, 0xf6, 0xde, 0xe0, 0xd2, 0xda, 0xe9, 0xb5, 0x3f, 0x0d, 0x2e, 0xab, 0xdb, 0xa6, 0x6e, 0x9b, 0xe0, 0x8a, 0xba, 0x9d, 0xea, 0x76, 0x0d, 0xae, 0xac, 0xdb, 0xbd, 0x6e, 0xcf, 0xe0, 0xea, 0xba, 0xbd, 0xeb, 0xf6, 0x0f, 0xbe, 0x55, 0x77, 0x40, 0xdd, 0x81, 0xc1, 0xcd, 0x75, 0x7f, 0xad, 0x9b, 0x1b, 0x4c, 0xae, 0x9b, 0x57, 0xf7, 0x7c, 0x70, 0x5b, 0xdd, 0xa2, 0xba, 0x25, 0xc1, 0x0f, 0xeb, 0x5e, 0xaf, 0x5b, 0x15, 0x4c, 0xab, 0x4b, 0xd6, 0xa5, 0x82, 0x1f, 0xd7, 0xad, 0xad, 0xcb, 0x05, 0xf7, 0xd6, 0x0d, 0xd4, 0x15, 0x83, 0xfb, 0xeb, 0xa2, 0xd1, 0xa3, 0x83, 0x5f, 0x8d, 0xde, 0x7c, 0xf4, + 0xe6, 0xc1, 0x23, 0xa3, 0xf7, 0x18, 0xbd, 0x77, 0xf0, 0xe8, 0xe8, 0xa1, 0xd1, 0x43, 0xc1, 0xe3, 0x63, 0x3e, 0x34, 0xe6, 0x53, 0xc1, 0x1f, 0xc7, 0x9c, 0x30, 0xe6, 0xb3, 0x41, 0x6b, 0x70, 0x60, 0xd0, 0x17, 0x6c, 0x11, 0xf4, 0x07, 0x3b, 0x04, 0x03, 0x3c, 0x7c, 0x7d, 0xb0, 0x4b, 0x30, 0x24, 0x51, 0xf4, 0x50, 0xac, 0x29, 0xd8, 0x4d, 0x3f, 0x9a, 0xa5, 0x1f, 0xcd, 0x8d, 0x6d, 0x13, 0x34, 0xc6, 0xb6, 0x0d, 0xc6, 0xe8, 0x45, 0x51, 0x6c, 0x87, 0x60, 0xcb, 0xd8, 0x8e, 0xb2, 0x4f, 0x74, 0x57, 0x6c, 0xbf, 0x60, 0x1b, 0x44, 0xfa, 0xf5, 0xa8, 0x27, 0x59, 0xd6, 0x33, 0xc1, 0x0e, 0xa3, 0xe6, 0xb8, 0x7d, 0x3e, 0xd8, 0x71, 0xd4, 0xa2, 0x60, 0x97, 0x51, 0xcb, 0xdc, 0xbe, 0x22, 0xaf, 0xbb, 0x9f, 0x0a, 0xc6, 0x8c, 0xea, 0x0b, 0xb6, 0x1a, 0x35, 0x1c, 0x6c, 0x69, 0x4e, 0xdf, 0x5a, 0x5b, + 0x17, 0x6c, 0x55, 0x3b, 0x5a, 0x1a, 0x7d, 0xfd, 0x19, 0x2b, 0xfe, 0x30, 0x8a, 0xea, 0x6c, 0x7f, 0xcc, 0x41, 0xc1, 0x9e, 0x63, 0xce, 0x91, 0x73, 0x83, 0x3d, 0xeb, 0xbf, 0x15, 0xec, 0x68, 0x9e, 0x96, 0x74, 0xeb, 0x18, 0x36, 0xaf, 0x33, 0x37, 0xe7, 0x37, 0x74, 0x05, 0x5b, 0xe9, 0xc2, 0xff, 0xda, 0xf8, 0x78, 0xb0, 0x43, 0xe3, 0x1f, 0xe5, 0x5f, 0x83, 0xcd, 0x1a, 0xe7, 0x07, 0x63, 0x1a, 0x9f, 0x97, 0xe6, 0x28, 0xd2, 0x6d, 0xdb, 0xcc, 0xb9, 0xf6, 0x60, 0x3f, 0x47, 0xd5, 0xe8, 0xa8, 0xb6, 0x71, 0x54, 0xf5, 0x8e, 0x6a, 0x27, 0x47, 0xb5, 0x93, 0xa3, 0xfa, 0xb5, 0xa3, 0xda, 0xa9, 0x7a, 0x54, 0xcf, 0x3a, 0xaa, 0x98, 0xa3, 0x69, 0x74, 0x34, 0x8d, 0x8e, 0x66, 0x9a, 0xa3, 0xd9, 0xc2, 0xd1, 0xfc, 0xaa, 0x7a, 0x34, 0xdb, 0x54, 0x8f, 0x66, 0x5b, 0x47, 0xb3, 0x93, 0xa3, 0xd9, 0xd6, + 0xd1, 0x6c, 0xeb, 0x68, 0x76, 0x72, 0x24, 0x9b, 0x39, 0x92, 0x46, 0x47, 0x72, 0x9b, 0x23, 0xd9, 0xcc, 0x91, 0x6c, 0xe6, 0x48, 0x1a, 0x2b, 0x47, 0x62, 0x7b, 0x8e, 0x62, 0x57, 0x47, 0xb1, 0xab, 0xa3, 0xd8, 0xd5, 0x51, 0x6c, 0x8b, 0x39, 0xa3, 0x1c, 0x45, 0xad, 0xa3, 0xe8, 0xab, 0x1e, 0xc5, 0x66, 0x8e, 0xe2, 0x09, 0x47, 0xb1, 0x8d, 0xa3, 0xd8, 0xc6, 0x51, 0xd4, 0xdb, 0xf3, 0x97, 0x2b, 0x7b, 0xfe, 0x1d, 0x76, 0x3d, 0xd7, 0x9e, 0xce, 0x64, 0xd5, 0x73, 0xed, 0xe9, 0xc3, 0xf6, 0xf4, 0x69, 0x3c, 0x48, 0xe1, 0x41, 0x0a, 0x0f, 0x52, 0xc6, 0xa0, 0x0f, 0x0f, 0x52, 0x78, 0x90, 0xc2, 0x83, 0x14, 0x1e, 0xa4, 0xf0, 0x20, 0x85, 0x07, 0xb3, 0xf0, 0x20, 0x85, 0x07, 0xb7, 0xe0, 0xc1, 0x2c, 0x3c, 0x48, 0xe1, 0x41, 0x0a, 0x0f, 0x6e, 0xc0, 0x83, 0x14, 0x1e, 0xa4, 0x1c, 0xe5, 0xdd, 0x78, + 0x90, 0xc2, 0x83, 0x14, 0xbb, 0x28, 0x60, 0xc2, 0x2c, 0x4c, 0x48, 0xb1, 0x88, 0x05, 0x0c, 0x62, 0x01, 0x73, 0x68, 0x65, 0x0e, 0xad, 0x8c, 0x79, 0xee, 0xa8, 0xb6, 0x68, 0x2d, 0x53, 0x9e, 0xcb, 0x22, 0xb2, 0x2c, 0x22, 0xab, 0xef, 0xa7, 0xf4, 0xfd, 0x94, 0xbe, 0x9f, 0xd2, 0xf7, 0x53, 0xfa, 0x7e, 0x4a, 0xdf, 0x4f, 0xe9, 0xfb, 0x29, 0x7d, 0xff, 0x16, 0x7d, 0x3f, 0xa5, 0xef, 0xa7, 0xf4, 0xfd, 0x94, 0xbe, 0x3f, 0x4b, 0xdf, 0xbf, 0x45, 0xdf, 0xbf, 0x5b, 0xdf, 0xbf, 0x5b, 0xdf, 0xbf, 0x45, 0xdf, 0xbf, 0x41, 0xdf, 0xbf, 0x41, 0xdf, 0x4f, 0xe9, 0xf7, 0x29, 0xfd, 0x3e, 0xa5, 0xdf, 0xa7, 0xf4, 0xfb, 0x1b, 0xf4, 0xfb, 0x94, 0x7e, 0xff, 0xbc, 0x5e, 0x3f, 0x4b, 0xaf, 0x4f, 0xe9, 0xf5, 0x65, 0x4b, 0x9e, 0xa5, 0xd7, 0xa7, 0x18, 0xf2, 0x5c, 0x86, 0x3c, 0x97, 0x21, 0xcf, 0xd5, 0xf7, + 0x53, 0xfa, 0x7e, 0x4a, 0xdf, 0x4f, 0xe9, 0xfb, 0xb3, 0xf4, 0xfd, 0x59, 0xfa, 0xfe, 0x2c, 0x86, 0xd0, 0xca, 0x10, 0x5a, 0x19, 0xc2, 0x53, 0x0c, 0xe1, 0x29, 0xf5, 0xd4, 0x87, 0x03, 0xb3, 0x98, 0xf1, 0x5c, 0x36, 0x3c, 0x97, 0x0d, 0xcf, 0x65, 0xc3, 0x73, 0xd9, 0x70, 0x96, 0x25, 0x64, 0x59, 0x42, 0x96, 0x0d, 0x67, 0xf1, 0x61, 0x16, 0x3e, 0xcc, 0xc2, 0x87, 0x59, 0xf8, 0x30, 0x0b, 0x1f, 0x66, 0xe1, 0xc3, 0x2c, 0x75, 0xf7, 0x46, 0xc3, 0x7e, 0xd1, 0x00, 0x46, 0xa4, 0x30, 0x62, 0x16, 0x46, 0xcc, 0xc2, 0x88, 0x14, 0x46, 0xa4, 0x8c, 0xe0, 0x33, 0x18, 0x71, 0x17, 0x46, 0xdc, 0x85, 0x11, 0x77, 0x61, 0xc4, 0x5d, 0x18, 0x71, 0x17, 0x1e, 0xa4, 0xd4, 0x60, 0x1f, 0x1e, 0xa4, 0xf0, 0x20, 0x85, 0x07, 0x29, 0x3c, 0x48, 0xe1, 0x41, 0x0a, 0x0f, 0x6e, 0xc0, 0x83, 0x14, 0x23, 0x28, 0xb0, + 0x81, 0x56, 0x23, 0xbd, 0xa4, 0xe9, 0xe9, 0x68, 0x2d, 0xdb, 0x9d, 0x8b, 0x0f, 0xcf, 0xe3, 0xc3, 0xac, 0xe0, 0xbd, 0x6a, 0x76, 0xb3, 0x4a, 0x8d, 0xfe, 0x5b, 0x7d, 0xd6, 0x57, 0xea, 0x72, 0x43, 0x4d, 0xaa, 0xc7, 0x4a, 0xcd, 0xa9, 0xb3, 0xb7, 0xd7, 0x54, 0xa5, 0x9e, 0x36, 0xd4, 0xd0, 0x01, 0xfc, 0xe2, 0x09, 0x6e, 0xf1, 0x3c, 0xb7, 0xb8, 0x94, 0x5b, 0x9c, 0xc5, 0x2d, 0xce, 0xf2, 0xaa, 0xc3, 0x6a, 0xe3, 0x26, 0xf5, 0xd0, 0x53, 0x99, 0xbb, 0x07, 0x5a, 0x21, 0x1d, 0x14, 0x2d, 0xe3, 0x03, 0xe7, 0xf0, 0x81, 0xaf, 0xf3, 0x81, 0x73, 0xf8, 0xc0, 0xd7, 0x39, 0xf5, 0xfd, 0x9c, 0xe0, 0x72, 0x4e, 0x70, 0x25, 0x27, 0xb8, 0x92, 0x13, 0x9c, 0xc5, 0x09, 0xce, 0xe2, 0x03, 0x67, 0xf1, 0x81, 0xe7, 0xf9, 0xc0, 0xf3, 0x7c, 0xa0, 0x85, 0x0f, 0xb4, 0xf0, 0x81, 0xe7, 0x8d, 0xfd, 0x4d, 0xc6, + 0xfa, 0x26, 0x63, 0x7c, 0x93, 0x31, 0xee, 0x31, 0xc6, 0x3d, 0xb8, 0x9e, 0xc4, 0xf5, 0xa4, 0xb1, 0xed, 0xb1, 0xc7, 0x2d, 0x3c, 0x7b, 0x22, 0xcf, 0x3e, 0x11, 0x97, 0xcf, 0x2a, 0x3b, 0xb6, 0xf3, 0xf7, 0xb0, 0xf3, 0xf7, 0xb0, 0xf3, 0xf7, 0xb0, 0xf3, 0xf7, 0xb0, 0xf3, 0xf7, 0x30, 0xc6, 0x3e, 0x81, 0xb1, 0x4f, 0x60, 0xec, 0x13, 0x18, 0xfb, 0x04, 0xc6, 0x3e, 0xef, 0xbc, 0xf5, 0x38, 0xa2, 0x5b, 0x83, 0x53, 0xcd, 0xe1, 0x06, 0x73, 0xb8, 0xc1, 0xec, 0xc8, 0x39, 0xba, 0x66, 0xe7, 0x29, 0xed, 0x08, 0xe3, 0x8e, 0xf0, 0x66, 0x33, 0x25, 0xe7, 0x28, 0x2f, 0x71, 0x94, 0x97, 0x38, 0xca, 0x65, 0xce, 0x5d, 0x68, 0x36, 0xac, 0x75, 0xb4, 0x4f, 0x98, 0x09, 0x6b, 0x1d, 0xf1, 0x53, 0x8e, 0xf8, 0x19, 0xd5, 0xbe, 0xd6, 0xd1, 0x5c, 0xa2, 0xba, 0x73, 0x8e, 0xe8, 0x12, 0x47, 0x74, 0x89, 0x23, + 0x8a, 0x3b, 0xa2, 0xb8, 0x23, 0xea, 0x76, 0x44, 0xdd, 0xe6, 0x7a, 0x83, 0xa3, 0x8a, 0xab, 0xfe, 0xf2, 0x3a, 0x31, 0x67, 0xbe, 0x37, 0x38, 0xc2, 0x27, 0x54, 0xf4, 0x5a, 0x47, 0xf9, 0x84, 0xa3, 0x7c, 0xc2, 0x51, 0x3e, 0xe5, 0x28, 0x9f, 0x72, 0x94, 0xf3, 0x1c, 0xe5, 0x3c, 0x47, 0xf9, 0x94, 0xea, 0xed, 0x73, 0xa4, 0xe5, 0x0a, 0x5e, 0xab, 0x72, 0xd7, 0xaa, 0xda, 0x9c, 0xaa, 0xcd, 0xa9, 0xda, 0x9c, 0x4a, 0x5d, 0xab, 0x52, 0xd7, 0xaa, 0x54, 0xdd, 0x59, 0x06, 0xa3, 0x9c, 0xaa, 0xcc, 0xa9, 0xca, 0x9c, 0xaa, 0xcc, 0x39, 0x2b, 0x97, 0xd4, 0x97, 0xdf, 0x8f, 0x6c, 0x91, 0x2c, 0x93, 0x4e, 0x59, 0x1b, 0xad, 0x55, 0x89, 0x6b, 0x55, 0xdf, 0x5a, 0xd5, 0xb7, 0xd6, 0x59, 0x5b, 0xeb, 0xac, 0x3d, 0xe6, 0xac, 0x3d, 0xe6, 0xac, 0x3d, 0xe6, 0xac, 0x3d, 0xe6, 0xac, 0x3d, 0xe6, 0xac, 0x35, + 0x3b, 0x6b, 0xcd, 0xce, 0x5a, 0xb3, 0xb3, 0xd6, 0xec, 0xac, 0xc5, 0x9d, 0xb5, 0xa7, 0x54, 0xd7, 0xb0, 0xea, 0xca, 0xa9, 0xae, 0x3e, 0xd5, 0xb5, 0x36, 0xf8, 0xa0, 0x95, 0xcc, 0x0f, 0x9c, 0xb5, 0xbf, 0x5a, 0xc9, 0xcc, 0xae, 0xae, 0x64, 0xae, 0x77, 0xa6, 0x1e, 0xb2, 0x42, 0x2f, 0xf7, 0xf2, 0xb3, 0xac, 0xad, 0x76, 0xb4, 0xc2, 0xd8, 0x5e, 0xb5, 0x7d, 0xa5, 0xf2, 0x6e, 0x3b, 0x07, 0x47, 0x3f, 0xb5, 0xc2, 0xb8, 0xd7, 0x8a, 0xe2, 0x7b, 0x8e, 0xec, 0x38, 0xc6, 0x7e, 0xa5, 0x35, 0x54, 0x03, 0x53, 0x3f, 0x8a, 0xa5, 0xff, 0x54, 0x15, 0x1e, 0xc4, 0xc0, 0x4f, 0x66, 0xe0, 0xd7, 0x32, 0xf0, 0xe1, 0xaa, 0x7d, 0x9f, 0x6a, 0xef, 0x3f, 0x68, 0xef, 0x77, 0x35, 0x87, 0x16, 0xab, 0xd0, 0xbd, 0xed, 0xfd, 0xee, 0xaa, 0x74, 0x17, 0x47, 0xf0, 0x0d, 0x55, 0xfa, 0x11, 0x73, 0x64, 0xb2, 0x3d, + 0x3c, 0xdb, 0x5c, 0xb8, 0xba, 0x62, 0xc7, 0x2b, 0xa2, 0xb8, 0xbd, 0x3c, 0xd6, 0x5e, 0xee, 0x1e, 0xdc, 0x59, 0xd9, 0xcb, 0xf5, 0xd1, 0xf4, 0x60, 0x48, 0x86, 0xa3, 0x79, 0xf6, 0xf8, 0x2f, 0xc6, 0x77, 0x9e, 0xbd, 0x7e, 0xd2, 0x1e, 0xff, 0xd0, 0x1e, 0xcf, 0xd2, 0x0d, 0x27, 0xe9, 0x86, 0x93, 0x74, 0xc3, 0x49, 0xf6, 0x7e, 0x4f, 0xdd, 0x70, 0x92, 0x6e, 0x38, 0x29, 0xb6, 0x43, 0xb4, 0x7b, 0x6c, 0x47, 0xf9, 0x17, 0xf7, 0x77, 0x92, 0x9d, 0x65, 0x97, 0xe8, 0x6c, 0x5d, 0x71, 0x92, 0x3a, 0x38, 0x5b, 0x47, 0x9c, 0xa4, 0x23, 0x4e, 0x52, 0x0f, 0x37, 0xea, 0x88, 0x93, 0x74, 0xc4, 0x49, 0x8e, 0xfc, 0xa7, 0x3a, 0xe2, 0x24, 0x1d, 0x71, 0x92, 0xfa, 0x38, 0x5b, 0x37, 0x9c, 0xe4, 0x4c, 0x1c, 0xed, 0x0c, 0x5c, 0xe1, 0x0c, 0x3c, 0xe9, 0x0c, 0x3c, 0x11, 0x3b, 0x34, 0xba, 0x2c, 0xf6, 0x71, + 0x39, 0x32, 0xba, 0x26, 0x76, 0x94, 0x1c, 0x6d, 0x4e, 0x1e, 0x13, 0xfd, 0x4a, 0xc7, 0x9c, 0x66, 0xcd, 0xf5, 0x2b, 0x67, 0xe8, 0x87, 0xb1, 0xe3, 0x83, 0x1a, 0xdd, 0xf3, 0x3e, 0xdd, 0xf3, 0xbe, 0xd8, 0x69, 0x48, 0x79, 0x7a, 0xd0, 0x14, 0x3b, 0x23, 0xa8, 0x57, 0x6b, 0xf3, 0x46, 0x2d, 0x8a, 0xa6, 0xab, 0xa5, 0x79, 0x3a, 0xe9, 0x2f, 0x74, 0xd2, 0x5f, 0x8c, 0x7a, 0xdd, 0xd7, 0x89, 0x68, 0x92, 0x6e, 0x3a, 0x49, 0x37, 0xbd, 0x43, 0x37, 0x9d, 0xa4, 0x9b, 0x4e, 0xd2, 0x4d, 0x27, 0xe9, 0xa6, 0x93, 0x74, 0xd2, 0x49, 0x3a, 0xe9, 0x24, 0x9d, 0x74, 0x92, 0xba, 0x3b, 0x5b, 0xbd, 0xdd, 0xa8, 0xde, 0x6e, 0xd4, 0x39, 0x27, 0xe9, 0x9c, 0x93, 0x46, 0x0d, 0xff, 0x7f, 0xec, 0xbd, 0x09, 0x78, 0x53, 0xe5, 0xda, 0xb6, 0xfd, 0x24, 0x1d, 0xb2, 0x56, 0x4a, 0x99, 0x41, 0xc1, 0x32, 0x0f, 0xa2, + 0xcc, 0x08, 0x45, 0x19, 0x54, 0x10, 0x51, 0x14, 0x04, 0x65, 0x10, 0x10, 0x05, 0x04, 0x15, 0x07, 0x70, 0xa0, 0x0a, 0x28, 0x4e, 0x20, 0x88, 0x38, 0x55, 0x61, 0x6f, 0x15, 0x70, 0x64, 0x10, 0x10, 0x1c, 0x50, 0x51, 0x26, 0x0b, 0x14, 0x0a, 0x04, 0x02, 0x14, 0x02, 0x1d, 0xa0, 0x2d, 0xb4, 0x49, 0x49, 0x9a, 0x0e, 0xc9, 0x6a, 0x13, 0xda, 0x06, 0x78, 0xfe, 0x73, 0xad, 0x06, 0xac, 0x88, 0xfb, 0xdd, 0xef, 0xf1, 0xbe, 0xff, 0xfe, 0xbf, 0xff, 0xf8, 0x3c, 0x38, 0x4e, 0x56, 0x92, 0x26, 0x6b, 0x78, 0x9e, 0xfb, 0xbe, 0xee, 0xeb, 0x5e, 0x19, 0x96, 0x6c, 0x81, 0x7a, 0x26, 0xa0, 0x9e, 0x09, 0xc4, 0xde, 0x6b, 0xa8, 0x67, 0x02, 0xf1, 0xf7, 0x00, 0xb1, 0x37, 0x16, 0xf5, 0x4c, 0x40, 0x3d, 0x13, 0x88, 0xc1, 0xb1, 0x91, 0x56, 0xd9, 0x02, 0x05, 0x4d, 0x20, 0x16, 0x93, 0x89, 0xc5, 0x64, 0x62, + 0x31, 0x19, 0x05, 0x4d, 0x40, 0x41, 0x13, 0x50, 0xd0, 0x04, 0xe2, 0x72, 0x2c, 0x71, 0x39, 0x96, 0xb8, 0x1c, 0x8b, 0x82, 0x7e, 0x85, 0x82, 0x7e, 0x86, 0x82, 0xae, 0x89, 0x7c, 0x53, 0xa6, 0xa2, 0xa2, 0x6b, 0x22, 0xdf, 0x32, 0xde, 0x23, 0x35, 0xbe, 0xe1, 0x4a, 0xdc, 0x8e, 0x25, 0x6e, 0x93, 0x2d, 0xf1, 0xf2, 0x5d, 0xcb, 0x78, 0x98, 0x00, 0x4f, 0x73, 0xdf, 0x2b, 0x9f, 0x24, 0x8e, 0x93, 0x89, 0xe3, 0x64, 0xd4, 0x75, 0x28, 0x51, 0x30, 0x94, 0x28, 0x18, 0x8a, 0xba, 0x0e, 0x25, 0xa6, 0xc7, 0x12, 0xd3, 0x63, 0x89, 0xe9, 0xb1, 0x44, 0xc5, 0x87, 0x44, 0xc5, 0x58, 0x62, 0x7a, 0x2c, 0xda, 0xd5, 0x1c, 0x65, 0x4d, 0x20, 0xb6, 0xc7, 0x12, 0x1d, 0x63, 0x51, 0xd6, 0x04, 0x94, 0x35, 0x81, 0x08, 0xf9, 0x27, 0x2a, 0xaa, 0xf7, 0x54, 0x4b, 0x50, 0xd0, 0x04, 0x14, 0x34, 0x01, 0x05, 0x4d, + 0x40, 0x41, 0x13, 0x88, 0x98, 0xd7, 0x50, 0xd0, 0x04, 0xd4, 0xf3, 0x63, 0x22, 0x66, 0x2f, 0xb1, 0x9d, 0x4c, 0xd4, 0x3c, 0x40, 0xd4, 0x8c, 0x15, 0xad, 0x51, 0x4e, 0x13, 0xd5, 0x3e, 0x06, 0x95, 0xa8, 0x87, 0x4a, 0xd4, 0x23, 0x6a, 0xbe, 0xa3, 0xd2, 0xd7, 0x23, 0x62, 0xbe, 0x27, 0x62, 0x76, 0x84, 0x2b, 0xbd, 0x85, 0x4a, 0x6f, 0x61, 0xc6, 0x97, 0x12, 0xe3, 0x6b, 0xa8, 0xf0, 0x31, 0x54, 0xf7, 0x1a, 0x64, 0x7c, 0x3d, 0xaa, 0x7b, 0x0d, 0xaa, 0x7b, 0x0d, 0xb2, 0xbd, 0x1e, 0x95, 0xdd, 0x42, 0xbc, 0x2f, 0xa6, 0xa2, 0x5b, 0x88, 0xf1, 0xcd, 0x54, 0xf3, 0x86, 0x54, 0xf3, 0x86, 0x54, 0xf3, 0x86, 0x54, 0xf3, 0x1a, 0xc4, 0x75, 0x19, 0x71, 0x5d, 0x3f, 0xec, 0x49, 0x76, 0x86, 0xfd, 0x48, 0x0c, 0x95, 0x3c, 0x86, 0x78, 0x3e, 0xc3, 0xde, 0xf9, 0x44, 0x43, 0xf6, 0xe0, 0x20, 0xda, 0xe4, + 0x08, 0x67, 0xda, 0x16, 0xf6, 0x62, 0x3f, 0x5b, 0x5e, 0xc5, 0x96, 0xbf, 0x43, 0x61, 0xf5, 0x4f, 0x64, 0xa7, 0xa3, 0xe9, 0x1a, 0x9a, 0xae, 0xd1, 0x6d, 0xed, 0x88, 0xec, 0x8f, 0x87, 0x18, 0x21, 0x0f, 0x5e, 0xfc, 0x2e, 0x8e, 0x65, 0x32, 0x5b, 0x7a, 0x9f, 0x2a, 0x94, 0x42, 0xb5, 0x68, 0x24, 0x53, 0xd8, 0x8a, 0x9e, 0x29, 0x47, 0x45, 0x7c, 0x58, 0xdb, 0x73, 0x59, 0xfb, 0x73, 0xa8, 0xde, 0x38, 0x54, 0x6f, 0x9c, 0x71, 0x7e, 0xa4, 0xb9, 0x5c, 0x47, 0x64, 0xeb, 0x3d, 0xdf, 0xcf, 0x61, 0x6d, 0xf7, 0xa0, 0xed, 0x8b, 0xd0, 0xf6, 0x4f, 0xd0, 0xf6, 0x45, 0x68, 0xfb, 0x27, 0x6c, 0xf9, 0xdb, 0xf0, 0xa7, 0x51, 0xfc, 0xec, 0xc1, 0x32, 0x34, 0xfe, 0x73, 0x34, 0xfe, 0x73, 0x54, 0x71, 0x1c, 0x8a, 0x38, 0x8e, 0x3d, 0x59, 0x8a, 0x2a, 0x8e, 0x43, 0x15, 0x73, 0x51, 0xc5, 0x5c, 0x94, 0x30, + 0x17, 0xf5, 0x5b, 0x87, 0xf2, 0xad, 0xa3, 0x96, 0xef, 0xa6, 0x96, 0xef, 0x46, 0x01, 0xd7, 0x11, 0x91, 0x95, 0x44, 0xa4, 0xde, 0xab, 0x55, 0x12, 0x21, 0x7e, 0xf6, 0x7a, 0x06, 0x7b, 0xfd, 0x02, 0x4a, 0x36, 0x8e, 0xbe, 0x6b, 0x72, 0x58, 0xe3, 0xb7, 0xa3, 0x56, 0xdb, 0x51, 0xab, 0xed, 0xa8, 0xd5, 0x76, 0xd4, 0x6a, 0xfb, 0x15, 0x34, 0x3e, 0x97, 0x99, 0xd5, 0xfb, 0xa4, 0x9f, 0x98, 0xbd, 0x52, 0xf6, 0x3e, 0x4a, 0x96, 0x19, 0x67, 0x7d, 0x6a, 0xc9, 0x20, 0x63, 0xe6, 0x21, 0xbf, 0x03, 0xe4, 0xa3, 0xfe, 0xeb, 0xd3, 0x25, 0xfa, 0xe7, 0xa8, 0xf4, 0xad, 0xb1, 0xf6, 0x72, 0x31, 0x03, 0x75, 0x28, 0x63, 0x94, 0xcf, 0xa0, 0x0e, 0x65, 0x3c, 0x3b, 0x9d, 0x67, 0xfb, 0x50, 0x06, 0x37, 0xca, 0xe0, 0x46, 0x19, 0xdc, 0xa6, 0x06, 0xbc, 0xa2, 0x21, 0x5c, 0xc5, 0xed, 0xab, 0xe1, 0x1a, 0x88, + 0x83, 0x26, 0xd0, 0x94, 0x35, 0x36, 0x63, 0xa9, 0xd7, 0xc3, 0x96, 0xdc, 0x6e, 0xc5, 0xed, 0xd6, 0xd0, 0x86, 0x99, 0x6a, 0xcb, 0xf2, 0x5a, 0xd0, 0x6b, 0xe4, 0x75, 0x2c, 0xaf, 0x87, 0xf6, 0x3c, 0xa7, 0x03, 0x74, 0xe4, 0x76, 0x27, 0x59, 0x46, 0x56, 0x97, 0x91, 0xd1, 0x65, 0x64, 0xb1, 0x9b, 0x2c, 0x76, 0x93, 0xc5, 0x6e, 0xb2, 0xd8, 0x1d, 0x71, 0x46, 0x96, 0x90, 0xc9, 0x6e, 0x32, 0xd9, 0x4d, 0x26, 0xbb, 0x8d, 0xda, 0x58, 0xc8, 0xb2, 0x08, 0x8a, 0xa1, 0x44, 0x16, 0x19, 0xb5, 0xb2, 0x94, 0x59, 0xaf, 0xaa, 0x97, 0x5b, 0x18, 0xc7, 0x2d, 0x64, 0xb6, 0x9b, 0xcc, 0x76, 0x93, 0xd5, 0x6e, 0xb2, 0xda, 0xcd, 0xb8, 0x6e, 0x21, 0xab, 0xdd, 0x64, 0xb5, 0x8f, 0xac, 0x2e, 0x22, 0xab, 0xdd, 0x64, 0xb5, 0x3b, 0x32, 0x8a, 0x11, 0x88, 0x06, 0x0b, 0x8f, 0xc5, 0x70, 0xff, 0x0e, 0x59, 0x46, + 0x66, 0x97, 0x91, 0xd9, 0x65, 0x64, 0xb6, 0x9b, 0xcc, 0x76, 0x93, 0xd9, 0x6e, 0x32, 0xbb, 0x88, 0xcc, 0x2e, 0x22, 0xb3, 0x8b, 0xf0, 0x42, 0x25, 0x64, 0x6f, 0x11, 0xd9, 0x5b, 0x46, 0xc6, 0x96, 0x91, 0xad, 0x65, 0x64, 0x6b, 0x19, 0xd9, 0x5a, 0x41, 0xb6, 0x56, 0x90, 0xad, 0x15, 0x64, 0x6b, 0x05, 0xd9, 0x5a, 0x44, 0xb6, 0x16, 0x91, 0xad, 0x45, 0x64, 0x6b, 0x11, 0xd9, 0x5a, 0x44, 0xb6, 0x16, 0x11, 0xef, 0x6e, 0xb2, 0xd5, 0x4d, 0xb6, 0x16, 0x91, 0xad, 0x45, 0x64, 0xab, 0x9b, 0x6c, 0x75, 0x33, 0x0b, 0x4e, 0xf5, 0x14, 0x39, 0xe0, 0x96, 0x4b, 0x98, 0xe7, 0x25, 0xcc, 0xf3, 0x12, 0xe6, 0x79, 0x09, 0xf3, 0xbc, 0x84, 0x2c, 0x76, 0x5b, 0x77, 0xc9, 0x12, 0xeb, 0x6e, 0xd8, 0x0b, 0xfb, 0xb8, 0x6f, 0x83, 0x03, 0x60, 0x87, 0x83, 0x90, 0x2a, 0xb7, 0x90, 0xd1, 0x6e, 0xea, 0x40, 0x91, + 0xfe, 0x5d, 0x58, 0x32, 0xba, 0x8c, 0x8c, 0xf6, 0x91, 0xd1, 0x45, 0x74, 0x23, 0xff, 0xd3, 0xfc, 0x89, 0x17, 0x26, 0xb2, 0xd6, 0x44, 0xd6, 0x9a, 0xfe, 0x32, 0x97, 0x5e, 0x27, 0xe2, 0x82, 0xfa, 0x19, 0x6d, 0x22, 0xae, 0x90, 0xad, 0x14, 0xb1, 0x95, 0x00, 0x31, 0xe4, 0x21, 0x86, 0x3c, 0xc4, 0x90, 0x1e, 0x7d, 0xbb, 0x88, 0xa3, 0x2d, 0xc4, 0xd1, 0x3e, 0xe2, 0xc8, 0x43, 0x1c, 0x79, 0xa8, 0x32, 0x7b, 0xa9, 0x32, 0x7b, 0x89, 0x27, 0x0f, 0xf1, 0xe4, 0x21, 0x9e, 0x3c, 0xc4, 0x53, 0x80, 0x78, 0xd2, 0x1d, 0xc7, 0x77, 0xc4, 0x53, 0x80, 0x78, 0xf2, 0x10, 0x4f, 0xba, 0xf3, 0x28, 0x22, 0x9e, 0x3c, 0xc4, 0x93, 0x87, 0xbd, 0xff, 0x89, 0x78, 0xf2, 0x10, 0x4f, 0x1e, 0xe2, 0xc9, 0x4e, 0x3c, 0x05, 0x88, 0x27, 0x8f, 0x7e, 0xf6, 0xda, 0x38, 0x4b, 0xbd, 0x4d, 0x96, 0x47, 0xec, 0x96, 0xe7, + 0x70, 0x22, 0x7e, 0x9c, 0x88, 0x3f, 0x22, 0x8d, 0xdb, 0xe9, 0x90, 0x87, 0xf3, 0x70, 0x82, 0x0b, 0xf2, 0xe1, 0x0c, 0xb1, 0xe1, 0x66, 0x89, 0x43, 0x20, 0xc6, 0x3c, 0xc4, 0xd8, 0x77, 0xc4, 0x98, 0x87, 0x18, 0xf3, 0x10, 0x63, 0x1e, 0x62, 0x2c, 0x40, 0x8c, 0x7d, 0x47, 0x8c, 0x6d, 0x24, 0xc6, 0xbe, 0x23, 0xc6, 0x8a, 0x88, 0xb1, 0x22, 0x62, 0xcc, 0x83, 0x63, 0x29, 0xc1, 0xb1, 0x94, 0x10, 0x6b, 0x1e, 0xaa, 0xc8, 0x5e, 0xe2, 0xcd, 0x43, 0xbc, 0x79, 0x88, 0xb7, 0x22, 0xe2, 0xcd, 0x13, 0x76, 0x31, 0x07, 0x89, 0xb9, 0x00, 0x31, 0xe7, 0x21, 0xe6, 0x3c, 0xc4, 0xdc, 0x46, 0x62, 0x6e, 0x23, 0x31, 0x17, 0xa0, 0xa2, 0xec, 0x25, 0xee, 0x3c, 0xc4, 0x9a, 0x87, 0x58, 0xf3, 0x10, 0x6b, 0xfa, 0xd9, 0xe9, 0x00, 0xb1, 0x16, 0x20, 0xd6, 0x02, 0xc4, 0xda, 0x3e, 0x62, 0x2d, 0x40, 0x95, 0xc8, + 0xa7, 0x4a, 0xe4, 0x53, 0x25, 0xf2, 0x95, 0x59, 0xf2, 0xdc, 0x15, 0x7c, 0x77, 0x80, 0x58, 0x0b, 0x10, 0x6b, 0x01, 0x62, 0x2d, 0x40, 0xac, 0x05, 0x88, 0xb5, 0x00, 0xb1, 0xe6, 0x21, 0xd6, 0x74, 0xd7, 0x13, 0x20, 0xd6, 0x02, 0xc4, 0x9a, 0x87, 0x58, 0xd3, 0xdd, 0x4f, 0x21, 0xb1, 0xb6, 0x91, 0x58, 0xdb, 0x41, 0xac, 0xed, 0x20, 0xd6, 0x76, 0x10, 0x6b, 0x3b, 0x88, 0xb5, 0x1d, 0x97, 0xce, 0x3e, 0x27, 0x4b, 0x0f, 0x31, 0xb7, 0x85, 0x98, 0xdb, 0x42, 0xcc, 0xed, 0x23, 0xe6, 0x3c, 0xc4, 0x9c, 0x87, 0x98, 0xf3, 0x10, 0x73, 0x1e, 0x62, 0xce, 0x43, 0xcc, 0x15, 0x11, 0x73, 0x1e, 0x62, 0xce, 0x1e, 0x76, 0x46, 0x01, 0xc3, 0x19, 0x5d, 0x7e, 0x8e, 0xf7, 0x31, 0xe3, 0x1c, 0x6f, 0xfd, 0x0b, 0x2f, 0x33, 0x6b, 0x37, 0x9a, 0x3a, 0x5d, 0xd8, 0x78, 0xd9, 0xb9, 0xd7, 0xd9, 0xf8, 0x82, 0x57, + 0xf5, 0x73, 0xad, 0xc6, 0xb9, 0x4e, 0xfd, 0x5c, 0xa6, 0x7e, 0x1e, 0x53, 0x3f, 0x87, 0xa9, 0x9f, 0xbf, 0x1c, 0x21, 0x9b, 0xfe, 0xe1, 0xdc, 0xe4, 0xc5, 0xf3, 0x92, 0x19, 0x52, 0x18, 0xe7, 0x21, 0xab, 0x9c, 0x51, 0x4b, 0x75, 0xc6, 0x85, 0xf2, 0xf0, 0x39, 0xc7, 0xd1, 0xc6, 0xb9, 0xc5, 0xbd, 0x46, 0x9d, 0x1b, 0x7a, 0xe9, 0xbc, 0x61, 0xf5, 0x73, 0x86, 0x9f, 0xb2, 0x97, 0x33, 0x70, 0x46, 0x0b, 0x70, 0x46, 0x0b, 0xfe, 0xe2, 0x8c, 0x74, 0x3c, 0x71, 0x1b, 0x4f, 0xdc, 0xc6, 0xb3, 0xe7, 0x87, 0x88, 0xdb, 0xbe, 0xc4, 0x6d, 0x5d, 0xe2, 0x36, 0x9e, 0xb8, 0x8d, 0x37, 0x35, 0xba, 0x70, 0xc4, 0xd4, 0x18, 0xae, 0xe1, 0x76, 0x1c, 0x34, 0x81, 0xa6, 0x17, 0x4a, 0x89, 0xdb, 0x78, 0xe2, 0x76, 0xb0, 0xa9, 0xe5, 0x85, 0x20, 0x71, 0x1b, 0x4f, 0xdc, 0xc6, 0x13, 0xb7, 0xfd, 0x88, 0xdb, 0x78, + 0xe2, 0x36, 0x9e, 0x11, 0x18, 0x44, 0xdc, 0xc6, 0x13, 0xb7, 0xf1, 0xc4, 0x6d, 0x2d, 0x53, 0x07, 0x5e, 0xd3, 0x91, 0xdb, 0x9d, 0x2e, 0x84, 0xaa, 0x8d, 0xca, 0x68, 0x46, 0x65, 0x11, 0xa3, 0xa2, 0x7f, 0xea, 0x71, 0x34, 0x6e, 0x69, 0x34, 0x6e, 0x69, 0x12, 0x6e, 0x69, 0x12, 0x6e, 0x69, 0x14, 0x6e, 0xe9, 0x29, 0xdc, 0xd2, 0x28, 0xdc, 0xd2, 0x53, 0x8c, 0xda, 0x4c, 0x9c, 0xd2, 0x2c, 0x9c, 0xd2, 0x0c, 0x9c, 0xd1, 0x02, 0xdc, 0xd0, 0x02, 0x62, 0x3c, 0x9e, 0x18, 0x8f, 0x27, 0xc6, 0xe3, 0x89, 0xf1, 0x78, 0x62, 0xbc, 0x2f, 0x31, 0x1e, 0x4f, 0x8c, 0xc7, 0x13, 0xe3, 0xf1, 0xc4, 0xf8, 0x60, 0x62, 0x3c, 0x9e, 0x18, 0x8f, 0x27, 0xc6, 0xe3, 0x23, 0x4a, 0x2e, 0x94, 0x12, 0xe3, 0x83, 0x89, 0xf1, 0xbb, 0x88, 0xf1, 0xc1, 0xc4, 0x78, 0x3f, 0x62, 0xbc, 0x1f, 0x31, 0x1e, 0x4f, 0x6c, 0xc7, + 0x47, 0x84, 0x2e, 0x1c, 0x21, 0xb6, 0xe3, 0x89, 0xed, 0x78, 0x62, 0xbb, 0x1f, 0xb1, 0x1d, 0x1f, 0x3e, 0x2b, 0x5d, 0x4a, 0x5c, 0xc7, 0x13, 0xd7, 0xf1, 0xc4, 0xf5, 0x5d, 0xc4, 0xf5, 0x5d, 0x91, 0x16, 0x1e, 0xb3, 0x5e, 0x38, 0x42, 0x5c, 0xc7, 0x87, 0xcf, 0x4a, 0xc7, 0x87, 0xcf, 0x4a, 0xc7, 0x87, 0xcf, 0x4a, 0xc7, 0x87, 0xcf, 0x4a, 0x97, 0x46, 0x0e, 0x87, 0xaa, 0xb3, 0xd2, 0xa5, 0xd5, 0xce, 0x4a, 0x2f, 0xc4, 0x31, 0x2d, 0xc7, 0x31, 0x2d, 0xc4, 0x31, 0x2d, 0x27, 0x07, 0xea, 0x46, 0x15, 0x5f, 0x28, 0x25, 0x07, 0xc6, 0x93, 0x03, 0xe3, 0xc9, 0x81, 0xf1, 0x7f, 0x3c, 0x53, 0x2d, 0x4d, 0xbf, 0x9f, 0xa9, 0xe6, 0xf6, 0xae, 0x0b, 0xa5, 0x8a, 0x1d, 0xd2, 0x20, 0x83, 0xe7, 0x54, 0x9d, 0xa9, 0x2e, 0xbd, 0x14, 0x25, 0xb7, 0xc9, 0x78, 0xf5, 0x21, 0xee, 0xeb, 0xd1, 0xf2, 0x12, 0xb7, + 0x5f, 0x83, 0x70, 0xd4, 0x90, 0x13, 0x77, 0x85, 0xcf, 0x54, 0x0f, 0x21, 0x27, 0x86, 0x90, 0x13, 0x43, 0xaa, 0x9d, 0xa9, 0x8e, 0x27, 0x1f, 0xfa, 0x92, 0x0f, 0x7d, 0xc3, 0x67, 0xaa, 0x27, 0x92, 0x0f, 0x37, 0x91, 0x0f, 0xf1, 0xe4, 0x43, 0x3c, 0xf9, 0x10, 0x1f, 0x3e, 0x53, 0x1d, 0x5f, 0xed, 0x4c, 0xf5, 0x8c, 0x3f, 0x44, 0xdd, 0x75, 0x61, 0x67, 0x55, 0x1b, 0x67, 0x75, 0x0d, 0xce, 0xea, 0x9a, 0xb0, 0xb3, 0xba, 0xea, 0x2f, 0x9d, 0x55, 0x27, 0x7c, 0x6f, 0x95, 0xbb, 0xaa, 0x8d, 0xbb, 0xaa, 0x83, 0xbb, 0xba, 0x06, 0x77, 0x55, 0x07, 0x77, 0x55, 0x07, 0x77, 0x75, 0x4d, 0x44, 0x29, 0x9e, 0xf8, 0xa2, 0xc3, 0x8a, 0x12, 0x4a, 0x64, 0x34, 0xfc, 0xee, 0xb4, 0x1a, 0xa3, 0xd9, 0x8d, 0xd1, 0xec, 0xc6, 0x38, 0xad, 0x3a, 0xf8, 0xc4, 0x9a, 0xd5, 0x9d, 0x96, 0x7a, 0x4a, 0x28, 0x61, 0xb7, + 0x55, 0x1b, 0xb7, 0x55, 0xfb, 0x92, 0xdb, 0xba, 0xc7, 0xd8, 0xcb, 0x72, 0xa8, 0x80, 0x90, 0xcc, 0x63, 0x2f, 0xd3, 0xf0, 0x05, 0x79, 0xec, 0xe5, 0xbe, 0x6a, 0x7b, 0x69, 0x62, 0x2f, 0x4d, 0x68, 0xf5, 0x01, 0xe2, 0x7d, 0x2b, 0x3a, 0x7d, 0x80, 0x38, 0xdf, 0xc8, 0x5e, 0x27, 0xa3, 0xc5, 0x07, 0xf4, 0xbd, 0x26, 0x56, 0xf3, 0x88, 0xd3, 0xbc, 0xf0, 0x37, 0x33, 0x4b, 0xa8, 0xf3, 0x79, 0x1c, 0x81, 0x89, 0x5a, 0x9f, 0xc7, 0xde, 0x9b, 0x88, 0xc3, 0xad, 0xe8, 0xeb, 0x01, 0x62, 0x6f, 0x2b, 0x71, 0xb7, 0x95, 0xb8, 0xdb, 0x48, 0xdc, 0x6d, 0xe4, 0x88, 0x4c, 0xc4, 0xd9, 0x46, 0xe3, 0xa8, 0xcc, 0xf2, 0x00, 0x5a, 0x79, 0x80, 0xa3, 0x32, 0x51, 0x9f, 0xf3, 0xa8, 0xcf, 0x79, 0xd4, 0xe7, 0x3c, 0x74, 0xf2, 0x00, 0x3a, 0x79, 0x40, 0x3f, 0xd2, 0x48, 0xfd, 0xf3, 0x82, 0x73, 0xd1, 0xda, 0x79, + 0xd2, 0x4f, 0xfc, 0xb8, 0x89, 0x1d, 0x37, 0xba, 0x79, 0x80, 0x1a, 0x9d, 0x47, 0x8d, 0xce, 0xa3, 0x46, 0xe7, 0x51, 0xa3, 0xf3, 0x14, 0xfd, 0x37, 0x0b, 0xec, 0x90, 0x06, 0x19, 0xe0, 0x91, 0x07, 0xd0, 0xc9, 0x03, 0x97, 0x46, 0xe6, 0x21, 0x6e, 0xcf, 0x80, 0x8b, 0x23, 0xe4, 0x96, 0xc9, 0xc4, 0x42, 0x32, 0xb1, 0x90, 0x4c, 0x2c, 0x24, 0x13, 0x0b, 0xc9, 0xc6, 0x88, 0xa5, 0xca, 0x8d, 0xc6, 0x68, 0x65, 0x51, 0x8b, 0xb3, 0x65, 0x05, 0xb5, 0x36, 0x0f, 0xed, 0x3b, 0x20, 0x6a, 0x32, 0x42, 0x67, 0x8c, 0xf7, 0x6c, 0xeb, 0xcb, 0x7c, 0x46, 0xc2, 0x4b, 0x35, 0xca, 0xd7, 0xdf, 0x4b, 0x35, 0xde, 0xbb, 0xd4, 0xdf, 0x8b, 0xd4, 0xdf, 0x87, 0xd4, 0xdf, 0x83, 0x1c, 0x41, 0xb5, 0x0f, 0xbf, 0x17, 0xa8, 0xbf, 0xe7, 0x27, 0x62, 0x8d, 0xce, 0xbb, 0x9d, 0x7c, 0x4b, 0xf7, 0x43, 0xe1, 0x6b, 0x1b, + 0x1e, 0xa5, 0x32, 0x7f, 0x87, 0x53, 0x3b, 0x81, 0x53, 0x2b, 0xa5, 0x42, 0x3f, 0x8b, 0x5b, 0xcb, 0xa6, 0x22, 0x3f, 0x48, 0x45, 0x7e, 0x90, 0xed, 0x4e, 0x66, 0x7b, 0xff, 0xcd, 0x57, 0xe9, 0xef, 0xca, 0x19, 0xaf, 0xac, 0xc7, 0xab, 0xe6, 0xf1, 0xaa, 0x72, 0xf6, 0xee, 0x2b, 0x5e, 0xb9, 0x9d, 0x57, 0xfe, 0xc8, 0x2b, 0x67, 0x85, 0x3f, 0xe9, 0xad, 0x7f, 0xb6, 0x71, 0x0f, 0xaf, 0x9e, 0xc2, 0x9e, 0x67, 0x92, 0xd7, 0x67, 0xd9, 0xfb, 0x60, 0x78, 0xef, 0x5d, 0xac, 0x6d, 0x12, 0x6b, 0x9b, 0x64, 0x39, 0x23, 0xb3, 0x19, 0xd7, 0x1c, 0xd1, 0xc2, 0xf0, 0x97, 0xcd, 0x99, 0xe9, 0xaa, 0xb3, 0x21, 0x4e, 0xd6, 0xe8, 0x61, 0x8d, 0xb9, 0x38, 0x66, 0x2f, 0x8e, 0xb9, 0x08, 0xc7, 0xec, 0xc5, 0x31, 0x17, 0xb1, 0x85, 0xef, 0x59, 0xeb, 0x49, 0x9c, 0x72, 0x01, 0x4e, 0xb9, 0x80, 0x08, 0xc8, 0x63, + 0xf6, 0xf3, 0x70, 0xc3, 0x39, 0xb8, 0xe1, 0x1c, 0xa2, 0x20, 0xaf, 0xda, 0x59, 0x8f, 0xa3, 0x54, 0xd7, 0xa3, 0xe1, 0xb3, 0x1e, 0xfa, 0xd5, 0x13, 0xf3, 0xd9, 0x6a, 0x3e, 0xb3, 0x72, 0x92, 0x59, 0x39, 0xc9, 0xac, 0x9c, 0x64, 0x56, 0x4e, 0x32, 0x2b, 0x27, 0xf5, 0x33, 0x1b, 0xc2, 0xca, 0x5e, 0xf8, 0x18, 0xfd, 0x34, 0xb2, 0x46, 0x61, 0x4b, 0xfa, 0x67, 0x5b, 0x4f, 0x73, 0x1c, 0x39, 0x8c, 0x76, 0x8e, 0x45, 0xff, 0xb6, 0xec, 0x0d, 0x3c, 0xf3, 0x9f, 0xb2, 0x82, 0x9c, 0xfa, 0xfd, 0x99, 0x6a, 0xb5, 0x67, 0x66, 0xe8, 0xf9, 0xf4, 0xa7, 0x67, 0xeb, 0xe7, 0x6d, 0x0b, 0x79, 0x76, 0x26, 0xb1, 0x9e, 0x49, 0x9c, 0x67, 0x12, 0xdf, 0x99, 0xc4, 0x76, 0x2a, 0xb1, 0x9d, 0xca, 0xab, 0x53, 0x89, 0x6f, 0x8d, 0xf8, 0xd6, 0xc2, 0x9f, 0xa5, 0x38, 0x4a, 0x4c, 0x67, 0x52, 0xfb, 0x4b, 0x88, 0xdd, + 0x4c, 0x62, 0x37, 0x93, 0x58, 0xcd, 0x24, 0x56, 0x33, 0x89, 0xd3, 0x4c, 0xe2, 0x32, 0x93, 0x38, 0xcc, 0x24, 0x0e, 0x33, 0x89, 0xc3, 0x4c, 0xe2, 0x30, 0x93, 0x38, 0xcc, 0x24, 0x0e, 0x33, 0x89, 0xbf, 0x4c, 0xe2, 0x4f, 0xd7, 0xa2, 0x0a, 0x62, 0x4b, 0x43, 0x37, 0xf4, 0x77, 0x6f, 0x33, 0xd9, 0xc7, 0x8b, 0xfb, 0x5b, 0xb5, 0xaf, 0xc6, 0xef, 0xf3, 0xfe, 0x69, 0x3f, 0x5b, 0xf2, 0xac, 0x52, 0x66, 0x61, 0x26, 0xb3, 0x90, 0xc4, 0x2c, 0xcc, 0x67, 0x16, 0x52, 0x98, 0x85, 0x2d, 0xcc, 0xc2, 0x14, 0x66, 0xe1, 0x71, 0x66, 0x61, 0x0a, 0xb3, 0xf0, 0x38, 0x6b, 0x79, 0x9a, 0x7d, 0x0e, 0xb0, 0xcf, 0x01, 0x66, 0x63, 0x12, 0xb3, 0xf1, 0x38, 0xb3, 0xf1, 0x38, 0xb3, 0x31, 0x93, 0xd9, 0x98, 0xc9, 0x2c, 0xcc, 0x64, 0x16, 0x92, 0x98, 0x85, 0x24, 0x66, 0xe1, 0x67, 0x66, 0xe1, 0x67, 0x66, 0x21, + 0x89, 0x59, 0x48, 0x62, 0x16, 0x26, 0x32, 0x0b, 0x13, 0x99, 0x85, 0x45, 0xcc, 0xc2, 0x22, 0x66, 0x61, 0x11, 0xb3, 0xb0, 0x88, 0x59, 0x58, 0xc4, 0x2c, 0x24, 0xb1, 0xdf, 0x01, 0x51, 0x97, 0xbd, 0xf8, 0x9c, 0xbd, 0xd0, 0xd8, 0x8b, 0xd5, 0xac, 0xf5, 0x73, 0xd6, 0xfa, 0x39, 0x6b, 0xfd, 0x9c, 0xb5, 0x6a, 0xac, 0x55, 0x63, 0xad, 0x41, 0xd6, 0x1a, 0x64, 0xad, 0x1a, 0x6b, 0xdd, 0xc7, 0xda, 0x36, 0xb1, 0xb6, 0x4d, 0xac, 0x6d, 0x13, 0x6b, 0xdb, 0xc4, 0xda, 0x36, 0xb1, 0x36, 0x8d, 0xbc, 0xa8, 0x8d, 0x67, 0xac, 0x6f, 0xbc, 0x57, 0xad, 0xff, 0xe2, 0xe3, 0x1a, 0xea, 0xdd, 0x6a, 0xfd, 0xbb, 0xf5, 0xc4, 0xe9, 0xb1, 0x8b, 0xdf, 0x8c, 0x27, 0x36, 0x75, 0x97, 0x90, 0xcf, 0x68, 0x14, 0xea, 0x11, 0x4e, 0x24, 0x54, 0xe5, 0xe0, 0x97, 0xbc, 0xea, 0xb4, 0xf1, 0x1b, 0xe8, 0x03, 0xe9, 0xe2, + 0x06, 0x19, 0xdf, 0x68, 0x3b, 0x7e, 0xf1, 0x55, 0x11, 0x9a, 0xa8, 0x25, 0xea, 0xff, 0x5b, 0xcf, 0xf4, 0x8b, 0x5a, 0xfa, 0xb3, 0x89, 0x0e, 0x15, 0x95, 0x55, 0x51, 0x59, 0x15, 0xf5, 0x54, 0x55, 0xb7, 0xa8, 0xad, 0x7a, 0xc0, 0x07, 0x7e, 0xd0, 0x50, 0xf6, 0x5a, 0xc6, 0x2c, 0xd4, 0xc6, 0x6b, 0xeb, 0x2a, 0x59, 0x15, 0x5f, 0xde, 0x70, 0x7c, 0x15, 0xb3, 0x16, 0x95, 0xb5, 0x58, 0x58, 0x8b, 0x85, 0xb5, 0xe8, 0x3a, 0x9d, 0xc9, 0x5e, 0x57, 0x32, 0x87, 0xa7, 0x59, 0xa3, 0x45, 0x34, 0x63, 0xe4, 0x66, 0x31, 0x72, 0xb9, 0x8c, 0xdc, 0x9b, 0xcc, 0xdf, 0x2e, 0xe6, 0x6f, 0x33, 0xf3, 0x37, 0x91, 0xf9, 0x7b, 0x94, 0xf9, 0x9b, 0xc8, 0xfc, 0x3d, 0xca, 0x1a, 0x67, 0x5e, 0xfc, 0xfe, 0x01, 0xf3, 0x37, 0x81, 0xf9, 0x9b, 0xc4, 0xfc, 0x4d, 0x62, 0xa4, 0x67, 0x31, 0xd2, 0xb3, 0xc8, 0xa6, 0x75, + 0x64, 0xd3, 0x3a, 0x46, 0x7c, 0x16, 0x23, 0x9e, 0xcb, 0x88, 0xe7, 0x32, 0xd2, 0xb9, 0xcc, 0xdd, 0x18, 0xe6, 0x6e, 0x0c, 0xa3, 0xbd, 0x82, 0xd1, 0x5e, 0xc1, 0x68, 0xaf, 0x60, 0xb4, 0x57, 0x30, 0xda, 0x2b, 0x18, 0xed, 0x5c, 0xe6, 0xad, 0x3e, 0xee, 0xbd, 0x1d, 0xb9, 0xdc, 0x85, 0x9e, 0xb1, 0x07, 0x3d, 0x43, 0x3c, 0x0c, 0x92, 0xeb, 0xab, 0x7f, 0xca, 0x9a, 0x2d, 0xa6, 0x32, 0x67, 0x95, 0xa8, 0xc2, 0x05, 0x46, 0xbe, 0x54, 0x57, 0x05, 0xd6, 0xbc, 0x97, 0x35, 0xef, 0xe5, 0x58, 0x9c, 0x1c, 0x4b, 0xa5, 0x91, 0x33, 0x7a, 0xc7, 0x59, 0x61, 0x78, 0x79, 0xdd, 0xc3, 0xb7, 0x91, 0xe7, 0x0d, 0x8f, 0x3e, 0x48, 0xee, 0x37, 0x3c, 0x75, 0x40, 0x9e, 0x67, 0xbf, 0xce, 0xb3, 0x5f, 0xe7, 0x0d, 0x7f, 0xac, 0xfb, 0xe1, 0xea, 0xde, 0x17, 0xdf, 0xfb, 0x97, 0xbe, 0xf6, 0xa2, 0x9f, 0x75, 0xcb, + 0x4a, 0x8e, 0xa3, 0x92, 0xe3, 0xa8, 0xe4, 0x38, 0x2a, 0x39, 0x0e, 0xfd, 0xbd, 0xc2, 0xf3, 0x86, 0x0f, 0x55, 0xc2, 0xdf, 0xc1, 0xf6, 0xb2, 0xc5, 0x7d, 0xd5, 0x3f, 0x7f, 0x1e, 0xce, 0x99, 0x22, 0x51, 0x87, 0x7d, 0x74, 0xb1, 0x8f, 0xa5, 0x1c, 0xf1, 0x71, 0x8e, 0x38, 0x8b, 0x57, 0x38, 0x8c, 0x5f, 0x10, 0x19, 0x44, 0x2f, 0x3f, 0x4a, 0x1e, 0xe1, 0x28, 0x83, 0x91, 0x43, 0x8c, 0x59, 0x2a, 0xe5, 0x08, 0x0f, 0x73, 0x84, 0x87, 0x79, 0x75, 0x16, 0xaf, 0xde, 0x42, 0x8e, 0x86, 0xc8, 0xba, 0x7c, 0xd1, 0x8e, 0xb5, 0x68, 0xac, 0x25, 0xc8, 0x91, 0xfa, 0x39, 0x52, 0x3f, 0x47, 0x5a, 0xca, 0x91, 0xfa, 0xff, 0xea, 0xf7, 0x09, 0x38, 0x7a, 0x3f, 0x47, 0x5f, 0xca, 0xd1, 0x97, 0x92, 0x07, 0x39, 0xe4, 0x41, 0x0e, 0xa3, 0x50, 0xca, 0x28, 0xf8, 0x19, 0x05, 0x3f, 0xa3, 0xe0, 0x67, 0x14, 0xfc, + 0x8c, 0x82, 0x9f, 0x51, 0xf0, 0x33, 0x0a, 0x7e, 0x46, 0xc1, 0xcf, 0x28, 0xf8, 0x19, 0x05, 0x3f, 0xa3, 0xe0, 0x67, 0x14, 0xfc, 0x8c, 0x82, 0x9f, 0x51, 0xf0, 0x33, 0x0a, 0x1a, 0xa3, 0xa0, 0x31, 0x0a, 0x1a, 0xa3, 0xa0, 0x31, 0x0a, 0x1a, 0xa3, 0x50, 0xaa, 0x7f, 0xe7, 0x9d, 0x91, 0xf0, 0x1b, 0x39, 0xb4, 0x99, 0x99, 0xd5, 0xf5, 0x7e, 0x3f, 0x31, 0xfe, 0x3e, 0x31, 0xbe, 0x34, 0x9c, 0x43, 0x17, 0xbf, 0xe3, 0x71, 0x90, 0x99, 0x7c, 0x49, 0xaf, 0x4a, 0x16, 0xfd, 0xd7, 0x39, 0xde, 0x97, 0x21, 0x43, 0x23, 0xab, 0xc7, 0xb1, 0x12, 0x8e, 0xe3, 0xe3, 0x17, 0xe3, 0xb8, 0x7a, 0xec, 0x1a, 0xda, 0x23, 0x2f, 0x3d, 0xbb, 0xa9, 0x2c, 0x61, 0x24, 0x4a, 0x18, 0x85, 0x12, 0x46, 0xc0, 0xc7, 0x08, 0xf8, 0x8c, 0x57, 0xdf, 0xc5, 0x28, 0x0d, 0x36, 0x3e, 0x4d, 0x54, 0xb5, 0x96, 0x12, 0x3c, 0x81, + 0xbe, 0x26, 0x33, 0x5a, 0x69, 0x81, 0xfb, 0x60, 0x38, 0x8c, 0x30, 0xb4, 0x52, 0xef, 0xb3, 0x4b, 0x2e, 0x6e, 0x81, 0x51, 0x28, 0x61, 0x14, 0x4a, 0x18, 0x85, 0x12, 0x46, 0xa1, 0x84, 0x51, 0x28, 0x61, 0x14, 0x4a, 0x18, 0x85, 0x12, 0x46, 0xa1, 0x44, 0xd7, 0x1d, 0x8e, 0xb6, 0x84, 0xfd, 0x36, 0xde, 0x49, 0xa3, 0xb2, 0xf5, 0x40, 0x05, 0xe3, 0xe5, 0x26, 0xb6, 0xfc, 0x7e, 0xf8, 0x9b, 0x17, 0x7a, 0xc6, 0xd8, 0x98, 0xd9, 0x79, 0xcc, 0xe6, 0x5c, 0x66, 0x73, 0xae, 0x50, 0xf1, 0x36, 0x87, 0xf0, 0xfe, 0x81, 0xf0, 0xa7, 0xad, 0xf4, 0xea, 0x75, 0x50, 0xff, 0x5c, 0x4c, 0xd8, 0x13, 0xb4, 0x66, 0x9e, 0xf3, 0x45, 0x34, 0x8e, 0xa8, 0x19, 0xcf, 0xdc, 0x4b, 0xbd, 0x9d, 0xc3, 0x33, 0x0f, 0x73, 0x94, 0x8d, 0x79, 0xe6, 0x47, 0x22, 0x92, 0xbf, 0xc4, 0x18, 0xeb, 0xa8, 0x85, 0x76, 0xb4, 0x93, + 0x0b, 0xc5, 0x5c, 0x3a, 0x8b, 0x0a, 0xba, 0x60, 0xfd, 0x33, 0x5d, 0x45, 0x3c, 0x5f, 0xef, 0x86, 0x35, 0x53, 0x0d, 0xf6, 0x27, 0x16, 0x6a, 0x51, 0x17, 0xeb, 0xb0, 0x95, 0xba, 0x50, 0x0f, 0xea, 0xb3, 0xb5, 0x06, 0x64, 0x7b, 0x43, 0xea, 0xf5, 0x55, 0xdc, 0xbf, 0x1a, 0x1a, 0xa1, 0xef, 0x8d, 0xe1, 0x1a, 0x6e, 0xc7, 0x41, 0x13, 0x68, 0xca, 0x3a, 0x9a, 0xb1, 0x6c, 0xce, 0xac, 0xb5, 0xe4, 0x76, 0x2b, 0x6e, 0xb7, 0x86, 0xaa, 0x8e, 0xb8, 0x80, 0xce, 0x42, 0xdf, 0xf3, 0x22, 0x3a, 0x8b, 0x02, 0x3a, 0x8b, 0x02, 0x3a, 0x8b, 0x93, 0x8c, 0xbe, 0x46, 0x67, 0x51, 0xc0, 0xd1, 0x1c, 0xa4, 0x23, 0x2e, 0x89, 0xf8, 0x8d, 0x8e, 0x38, 0xd9, 0xe8, 0x8a, 0x0b, 0x23, 0x52, 0x58, 0xa6, 0xb1, 0x4c, 0x37, 0xde, 0x89, 0x2a, 0xa0, 0x5b, 0x28, 0xa0, 0x5b, 0x28, 0xa0, 0x5b, 0x28, 0xa0, 0x5b, 0xc8, + 0xa5, 0x5b, 0x28, 0xa0, 0x5b, 0x28, 0xa0, 0x5b, 0xd0, 0x6b, 0x74, 0x21, 0xdd, 0x42, 0x01, 0xdd, 0x42, 0x01, 0xdd, 0x42, 0x01, 0xb3, 0xa6, 0xa1, 0x32, 0x85, 0x74, 0x0b, 0x79, 0x28, 0x4c, 0x61, 0xb5, 0x8e, 0xb8, 0x80, 0x6e, 0xa1, 0x80, 0x4e, 0xf8, 0x28, 0xdd, 0x42, 0x01, 0xdd, 0x42, 0x41, 0xb8, 0x13, 0x2e, 0x60, 0x86, 0x35, 0x3a, 0x85, 0x02, 0x3a, 0x85, 0x02, 0x3a, 0x85, 0x3c, 0xb4, 0x23, 0x8f, 0x19, 0xd7, 0xe8, 0x80, 0x8f, 0xd2, 0x29, 0x14, 0xd0, 0x25, 0x14, 0xd0, 0x21, 0x14, 0xd0, 0x1d, 0x14, 0x10, 0x05, 0x5a, 0xa4, 0x7e, 0x75, 0x8a, 0x91, 0x52, 0xc3, 0xfd, 0x9f, 0x20, 0x0a, 0x34, 0xdc, 0xbf, 0x86, 0xfb, 0xd7, 0x70, 0xff, 0x9a, 0x32, 0x5c, 0x96, 0xd3, 0x05, 0x17, 0xe2, 0xfc, 0x7d, 0x38, 0x7f, 0x1f, 0xce, 0xdf, 0x87, 0xf3, 0xf7, 0x11, 0x21, 0x1a, 0x11, 0xa2, 0x11, + 0x21, 0x1a, 0x11, 0xa2, 0x29, 0x7a, 0x4e, 0x58, 0x81, 0xb1, 0xc2, 0xf5, 0x17, 0x10, 0x29, 0x1a, 0x91, 0xa2, 0xe1, 0xfa, 0x0b, 0x70, 0xfd, 0x05, 0xb8, 0xfd, 0x3c, 0x72, 0xa7, 0x44, 0xd5, 0x23, 0xc9, 0x07, 0x7e, 0xd0, 0x88, 0xa4, 0x1f, 0x60, 0x03, 0x6c, 0xa6, 0xbb, 0xdd, 0x02, 0x5b, 0x61, 0x1b, 0x24, 0xcb, 0x02, 0x3a, 0x80, 0x5c, 0x3a, 0x80, 0x5c, 0x3a, 0x80, 0x13, 0x74, 0x00, 0x05, 0x74, 0x00, 0x05, 0x74, 0x00, 0x05, 0x74, 0x00, 0x05, 0x74, 0x00, 0x05, 0xe1, 0x8e, 0xb8, 0x80, 0x0e, 0xe0, 0x24, 0xd1, 0xa8, 0x89, 0x67, 0x84, 0x4f, 0x7a, 0x85, 0x1f, 0x34, 0x28, 0x95, 0x6f, 0x11, 0x19, 0x4b, 0x45, 0x40, 0xee, 0x10, 0x41, 0xee, 0x9f, 0x85, 0x8b, 0x67, 0xe7, 0x2b, 0xb9, 0x1d, 0xa2, 0x76, 0x9d, 0x63, 0x79, 0x9e, 0x3a, 0x27, 0xc8, 0x17, 0x13, 0x44, 0x40, 0x24, 0x44, 0xc9, + 0xf1, 0xa6, 0x68, 0x96, 0x16, 0x50, 0xc8, 0x1f, 0x55, 0x9e, 0x33, 0x59, 0xb9, 0x1d, 0x83, 0xaa, 0x5d, 0x7c, 0x4f, 0xaa, 0xb6, 0xcc, 0x0e, 0xd7, 0xb2, 0xdc, 0x2b, 0xfd, 0x6e, 0x44, 0xc4, 0x46, 0xe9, 0x8d, 0xf8, 0x05, 0x7e, 0x85, 0x4d, 0xb0, 0x59, 0x2e, 0x8d, 0xd8, 0xc2, 0x72, 0x2b, 0x6c, 0x03, 0xfd, 0xbd, 0xa9, 0x24, 0x96, 0xdb, 0x61, 0x07, 0xec, 0x94, 0xe7, 0x8c, 0xf7, 0xaa, 0x76, 0xcb, 0x95, 0xc6, 0xfb, 0x55, 0xfb, 0x78, 0xec, 0x00, 0xe8, 0x67, 0xe0, 0x0f, 0xb2, 0x3c, 0x04, 0xa9, 0xe0, 0xe0, 0x79, 0xc7, 0x58, 0x1e, 0x87, 0x34, 0x9e, 0x9b, 0x0e, 0xfa, 0x59, 0xf9, 0x6c, 0xee, 0x6b, 0xa2, 0x7e, 0x64, 0x3f, 0xe9, 0x8d, 0xbc, 0x0d, 0x6e, 0x87, 0x3b, 0xe4, 0x39, 0x3c, 0xfc, 0x39, 0x3c, 0xfc, 0x39, 0x34, 0xe7, 0x2b, 0x72, 0xdd, 0x13, 0xc5, 0x78, 0xe0, 0xd5, 0xcf, 0x31, + 0xcb, 0xeb, 0x99, 0xe5, 0xf5, 0xcc, 0xf2, 0x7a, 0xcb, 0xa3, 0xd2, 0x8b, 0x77, 0x3f, 0x67, 0x99, 0xcf, 0x72, 0x01, 0x7c, 0x05, 0xdf, 0xc2, 0x11, 0x38, 0x0a, 0xe8, 0x94, 0xc5, 0x0d, 0xe7, 0xa5, 0x17, 0x7f, 0x7f, 0x0e, 0x8d, 0x38, 0xa7, 0xa0, 0xf6, 0x4a, 0x6f, 0x18, 0x2e, 0xc7, 0x10, 0x21, 0x2b, 0xd1, 0xb1, 0x62, 0xeb, 0xf7, 0xd2, 0xcb, 0x8c, 0x2e, 0x65, 0x46, 0x97, 0x5a, 0x7f, 0x94, 0x3b, 0xac, 0x3f, 0x71, 0xff, 0x67, 0x60, 0x2c, 0xac, 0x8c, 0x85, 0x95, 0xb1, 0x60, 0xa6, 0xd7, 0x30, 0xd3, 0x6b, 0x98, 0xe9, 0x35, 0xcc, 0xf4, 0x1a, 0x2b, 0x63, 0x60, 0xdd, 0x6e, 0x7c, 0x7a, 0xee, 0x9c, 0x68, 0xc1, 0xac, 0xa5, 0x30, 0xfa, 0x36, 0x46, 0xba, 0x80, 0xfc, 0xad, 0x30, 0xce, 0x8c, 0xd6, 0x97, 0xab, 0x18, 0xe5, 0x53, 0x57, 0xba, 0x0e, 0x0c, 0x23, 0x1c, 0x60, 0x34, 0x03, 0x11, + 0xbb, 0xe4, 0xce, 0x88, 0xa3, 0xe4, 0x52, 0xba, 0x74, 0x12, 0xef, 0x05, 0x74, 0xc3, 0xe9, 0x91, 0xd4, 0x82, 0xc8, 0x81, 0x78, 0xb7, 0x21, 0x72, 0x25, 0x75, 0xf3, 0x38, 0x6e, 0xba, 0x40, 0xaf, 0x2e, 0xd5, 0xaf, 0x15, 0x43, 0x77, 0xfb, 0x1b, 0xdd, 0xed, 0x66, 0xf6, 0xde, 0x87, 0xfa, 0xf8, 0xa9, 0x32, 0x9a, 0xae, 0x40, 0x32, 0xdd, 0xf8, 0x6c, 0x53, 0x0c, 0x5b, 0xaf, 0x45, 0x75, 0xa9, 0x2f, 0x37, 0xe8, 0x9e, 0x45, 0xcc, 0xe3, 0x2f, 0x19, 0x44, 0x54, 0x39, 0x91, 0x94, 0x47, 0x24, 0xe5, 0x11, 0x45, 0x0e, 0x14, 0x69, 0x3f, 0x51, 0x52, 0x41, 0x94, 0x38, 0x78, 0x45, 0x31, 0xaf, 0x38, 0x80, 0xee, 0xd8, 0xd0, 0x1d, 0x1b, 0xba, 0x63, 0x33, 0xf6, 0xbf, 0x81, 0xf1, 0x69, 0xa8, 0x43, 0xe8, 0x8e, 0x0d, 0xdd, 0xb1, 0xa1, 0x3b, 0xeb, 0xd0, 0x9d, 0x75, 0xe8, 0x8e, 0x0d, 0xdd, 0xb1, + 0xa1, 0x3b, 0x36, 0x74, 0x67, 0x31, 0xba, 0x63, 0x43, 0x77, 0x34, 0x74, 0x67, 0x31, 0xba, 0x63, 0x43, 0x77, 0x6c, 0xe8, 0x4e, 0x36, 0xba, 0x63, 0x43, 0x77, 0x6c, 0xe8, 0x8e, 0x86, 0xee, 0xd8, 0xd0, 0x1d, 0x5b, 0xf8, 0x4c, 0xdc, 0x62, 0x74, 0xc7, 0xc6, 0xde, 0x6d, 0x20, 0x82, 0x1c, 0x44, 0x4b, 0x1e, 0x11, 0xe2, 0x20, 0x22, 0xf2, 0xd0, 0x1a, 0x1b, 0x5a, 0x63, 0x43, 0x6b, 0x6c, 0x68, 0x8d, 0x0d, 0xad, 0xd1, 0x3f, 0xf1, 0x64, 0x43, 0x6b, 0x6c, 0x68, 0x8d, 0x0d, 0xad, 0xd1, 0xd0, 0x1a, 0x1b, 0x5a, 0x63, 0x43, 0x6b, 0x6c, 0x68, 0xcd, 0x62, 0xb4, 0x46, 0x43, 0x6b, 0x2a, 0xd1, 0x1a, 0x0d, 0xad, 0xc9, 0x46, 0x6b, 0xb2, 0xd1, 0x1a, 0x1b, 0x5a, 0x63, 0x43, 0x6b, 0xd6, 0xa1, 0x35, 0x36, 0xb4, 0xc6, 0x86, 0xd6, 0x64, 0xa3, 0x35, 0x36, 0xb4, 0x66, 0x31, 0x5a, 0x63, 0x43, 0x6b, + 0x6c, 0x68, 0x8d, 0xee, 0x53, 0xf4, 0x4f, 0x3a, 0x2d, 0x46, 0x6b, 0xd6, 0xa1, 0x35, 0x36, 0x22, 0xd0, 0x41, 0x04, 0x3a, 0x88, 0x40, 0x07, 0xba, 0x63, 0x43, 0x77, 0x6c, 0xe8, 0x8e, 0x0d, 0xdd, 0x59, 0x8c, 0xee, 0x2c, 0x46, 0x77, 0x16, 0xa3, 0x3b, 0x87, 0xd0, 0x9d, 0xc5, 0x44, 0x66, 0x39, 0x91, 0xe9, 0x20, 0x32, 0x73, 0x88, 0xcc, 0x1c, 0x22, 0x33, 0x87, 0xa8, 0x74, 0x10, 0x71, 0x0e, 0x22, 0xce, 0x81, 0x06, 0x6d, 0x46, 0x83, 0x36, 0xa3, 0x41, 0x9b, 0xd1, 0xa0, 0xcd, 0x68, 0xd0, 0x62, 0x34, 0x68, 0x31, 0x1a, 0xb4, 0x18, 0x0d, 0x5a, 0x8c, 0x06, 0x2d, 0x46, 0x83, 0x16, 0xa3, 0x41, 0x36, 0x34, 0xc8, 0x86, 0x06, 0x2d, 0x46, 0x83, 0x16, 0xa3, 0x41, 0x36, 0x34, 0xc8, 0x86, 0x06, 0x55, 0xa2, 0x41, 0x15, 0x68, 0x50, 0x05, 0x1a, 0x54, 0x81, 0x06, 0x55, 0xa0, 0x41, 0x15, 0x44, + 0x6a, 0x39, 0x11, 0xa8, 0x9f, 0x85, 0xb3, 0xa1, 0x39, 0x95, 0x68, 0x8e, 0xfe, 0xe9, 0xa8, 0x43, 0x68, 0x8e, 0x0d, 0xcd, 0xb1, 0xa1, 0x39, 0x36, 0x34, 0xc7, 0x86, 0xe6, 0xd8, 0xd0, 0x9c, 0x6c, 0x34, 0xc7, 0x66, 0x9c, 0x85, 0xdb, 0x2c, 0x1d, 0xe8, 0xce, 0x62, 0x61, 0x36, 0xce, 0xb5, 0x76, 0x92, 0x41, 0x51, 0xc3, 0x78, 0xa7, 0xba, 0xb6, 0xd1, 0x8b, 0x56, 0x52, 0x13, 0xbd, 0xc6, 0xef, 0xd2, 0x0c, 0xc2, 0xcb, 0x55, 0x7d, 0x4f, 0xd1, 0xf8, 0x0e, 0x29, 0x35, 0xb1, 0x8c, 0x9a, 0x78, 0x8a, 0x9a, 0x78, 0x4a, 0x44, 0xf0, 0x8a, 0x83, 0x54, 0x38, 0xfd, 0x7d, 0xcc, 0x65, 0x54, 0xf3, 0x5a, 0x28, 0x92, 0x59, 0xff, 0x9f, 0xc7, 0x2c, 0x38, 0xe6, 0xfa, 0xd4, 0xb1, 0x06, 0x46, 0xbf, 0x57, 0xce, 0x33, 0xca, 0x59, 0x6f, 0x05, 0xf3, 0x18, 0x64, 0xee, 0xca, 0x99, 0xab, 0x72, 0xe6, 0xaa, + 0x84, 0xb9, 0x2a, 0x67, 0xec, 0x4b, 0x18, 0xfb, 0x12, 0x8e, 0xb1, 0x84, 0x63, 0xf4, 0xa8, 0xfa, 0x7b, 0xb0, 0x3e, 0xf0, 0x83, 0x66, 0x9c, 0x5d, 0x0c, 0x72, 0x5c, 0x41, 0xba, 0xd8, 0xff, 0x4a, 0xb7, 0x74, 0x0d, 0x09, 0x6b, 0x85, 0x9e, 0xf7, 0x7a, 0xae, 0xeb, 0xb9, 0x6d, 0xec, 0x4b, 0x16, 0xfb, 0x90, 0xc5, 0x2b, 0x03, 0x38, 0xb8, 0xdc, 0x7f, 0xf5, 0x59, 0x75, 0xdc, 0x9c, 0x5f, 0xdf, 0x1f, 0xd6, 0x94, 0x4b, 0xee, 0xe9, 0x9f, 0x05, 0xce, 0xbb, 0xf8, 0xfb, 0x05, 0xac, 0xf5, 0x34, 0x6b, 0xd5, 0x44, 0x13, 0xd6, 0xe8, 0xe4, 0xe8, 0x72, 0xc8, 0x0c, 0x1f, 0x19, 0x71, 0x86, 0x8c, 0xd0, 0x3f, 0x03, 0xa2, 0x77, 0xf3, 0x1e, 0xe3, 0x33, 0x1e, 0x38, 0x0b, 0x8e, 0x36, 0x87, 0xa3, 0xcd, 0xe7, 0x68, 0xf3, 0x39, 0xda, 0x4c, 0x8e, 0x36, 0x9f, 0x48, 0x3c, 0xc3, 0x11, 0xeb, 0xbd, 0x72, + 0x26, 0x91, 0x76, 0x86, 0x08, 0xf2, 0x71, 0xe4, 0x99, 0x1c, 0xb9, 0x93, 0x23, 0x77, 0x72, 0xe4, 0x4e, 0x8e, 0xdc, 0xc9, 0x91, 0x3b, 0x39, 0xf2, 0x1c, 0x8e, 0x3c, 0x87, 0x19, 0xf5, 0xe9, 0x9f, 0x61, 0x10, 0x0d, 0xd8, 0xea, 0x59, 0x72, 0xcd, 0x4d, 0x8e, 0xb9, 0xc9, 0x1f, 0xfd, 0x5d, 0x91, 0x73, 0xc4, 0xbe, 0x9b, 0x58, 0x76, 0x13, 0xbb, 0xfa, 0x3b, 0x10, 0x6e, 0x62, 0xd3, 0x4d, 0x6c, 0xea, 0x67, 0x36, 0xdc, 0xc4, 0x99, 0x9b, 0x38, 0x73, 0x13, 0x67, 0x6e, 0xe2, 0xcc, 0x4d, 0x9c, 0xb9, 0x89, 0x33, 0xfd, 0x5d, 0x05, 0x37, 0xf1, 0xe5, 0x26, 0x06, 0xdc, 0xc2, 0x2a, 0xcc, 0x46, 0x27, 0x7e, 0x71, 0xc6, 0xf7, 0x1a, 0xef, 0x55, 0x55, 0xfd, 0xda, 0xd0, 0xa5, 0xdf, 0x36, 0x89, 0x19, 0x20, 0xf7, 0x8b, 0x56, 0xa2, 0xb5, 0x7c, 0x59, 0xb4, 0x91, 0x9f, 0x8a, 0x76, 0x72, 0x9f, 0xb8, + 0x4e, 0x3a, 0x45, 0x7b, 0xb9, 0xd2, 0x7c, 0x97, 0x68, 0x6f, 0x1e, 0x26, 0xae, 0x33, 0x8f, 0xe5, 0xf6, 0x14, 0xb9, 0x5e, 0x19, 0x26, 0x5f, 0x56, 0xee, 0x85, 0x11, 0x30, 0x0a, 0xee, 0x87, 0xd1, 0x30, 0x06, 0x9e, 0x91, 0xfb, 0x94, 0x67, 0xe1, 0x39, 0xfc, 0xff, 0x74, 0x48, 0x80, 0xe7, 0xe1, 0x05, 0x98, 0x01, 0xb3, 0xe5, 0x4a, 0xe5, 0x65, 0x78, 0x05, 0x5e, 0x85, 0xb9, 0xf0, 0x06, 0xcc, 0x83, 0xf9, 0xf0, 0x26, 0x6a, 0xfd, 0xa9, 0x68, 0xc8, 0xde, 0x7c, 0x64, 0x0e, 0x8a, 0x08, 0x7a, 0x91, 0x48, 0x88, 0x22, 0x1a, 0xa3, 0x71, 0xea, 0x16, 0x69, 0x13, 0x0a, 0xa8, 0xdc, 0xb6, 0xa2, 0x6e, 0x31, 0x2c, 0x6b, 0x40, 0x2c, 0xd4, 0xe4, 0xf1, 0x5a, 0x2c, 0xf1, 0x9e, 0xa2, 0x0e, 0xcb, 0xba, 0x50, 0x4f, 0x9e, 0x10, 0xf5, 0x59, 0x36, 0x80, 0x86, 0x1c, 0xd1, 0x55, 0x2c, 0xaf, 0xd6, 0xdf, + 0x07, 0x81, 0xc6, 0x80, 0xc2, 0x09, 0x14, 0x4e, 0xa0, 0x70, 0xa2, 0x29, 0xf7, 0x9b, 0x41, 0x73, 0x6e, 0xb7, 0x60, 0xd9, 0x92, 0x68, 0x6f, 0x05, 0xad, 0xe5, 0x48, 0x46, 0x64, 0x9a, 0x68, 0x2b, 0xf7, 0x8a, 0x6b, 0xa1, 0x9d, 0x7c, 0x8f, 0x91, 0x59, 0x21, 0xae, 0x47, 0x61, 0xdb, 0xf3, 0x78, 0x07, 0x7c, 0xe1, 0x1f, 0xaf, 0x02, 0x54, 0x20, 0xba, 0xca, 0xed, 0xa2, 0x1b, 0xdc, 0x00, 0x44, 0x96, 0x20, 0xb2, 0x44, 0x3c, 0xf4, 0x94, 0xc9, 0xe2, 0x46, 0xb8, 0x09, 0x7a, 0xc9, 0x6d, 0xa2, 0x37, 0x8f, 0xf5, 0x81, 0xff, 0xef, 0xaf, 0xec, 0x50, 0x20, 0x26, 0xc0, 0x44, 0x78, 0x18, 0x26, 0xc1, 0x64, 0x78, 0x44, 0x1e, 0x14, 0x8f, 0x8a, 0x1a, 0xe2, 0x31, 0x98, 0xc2, 0xfd, 0xc7, 0xe1, 0x09, 0x78, 0x12, 0x9e, 0x82, 0xa9, 0x30, 0x0d, 0x9e, 0xe6, 0x18, 0x9e, 0x81, 0x67, 0xe1, 0x39, 0xee, + 0x4f, 0x87, 0x04, 0x8e, 0xf1, 0x79, 0x96, 0x2f, 0x48, 0xbb, 0x98, 0x01, 0x33, 0x61, 0x16, 0xbc, 0x68, 0xfc, 0x1e, 0x8d, 0x7e, 0xe5, 0x87, 0x0c, 0xf1, 0x32, 0xbc, 0x02, 0xaf, 0xc2, 0x6b, 0xf0, 0x3a, 0xcc, 0x81, 0xb9, 0xf0, 0x06, 0xcc, 0x83, 0xf9, 0xec, 0xc3, 0x9b, 0x50, 0x75, 0x95, 0xf3, 0x74, 0xb1, 0x90, 0x39, 0xa9, 0xfa, 0xad, 0x6b, 0x8f, 0x89, 0xca, 0x61, 0x3a, 0x43, 0x3c, 0xbb, 0xe5, 0x76, 0x93, 0xfe, 0x0d, 0x95, 0x22, 0xe2, 0xba, 0x98, 0x4a, 0x57, 0x62, 0xfc, 0xb6, 0x54, 0x71, 0xf8, 0xb7, 0xac, 0x8a, 0xcd, 0xfd, 0x64, 0x92, 0x79, 0x80, 0xb4, 0x99, 0x6f, 0x67, 0x79, 0xa7, 0x3c, 0x40, 0x44, 0xdf, 0x62, 0xbe, 0x9b, 0xfb, 0xf7, 0xc8, 0x22, 0x22, 0xbb, 0x87, 0xf9, 0x5e, 0x6e, 0x0f, 0xe7, 0x6f, 0x63, 0xe5, 0x34, 0xf3, 0x03, 0x32, 0xdf, 0x3c, 0x9e, 0xfb, 0x13, 0xf9, + 0xdb, 0x64, 0x6e, 0x3f, 0x22, 0xb7, 0x99, 0x1f, 0x93, 0x0e, 0x22, 0x7f, 0x9a, 0xf9, 0x71, 0xb9, 0xd7, 0xfc, 0x04, 0x8f, 0x3f, 0x09, 0x4f, 0xf1, 0xb7, 0x67, 0x64, 0x81, 0xf9, 0x59, 0x98, 0x2e, 0xb7, 0x9b, 0x39, 0x5e, 0xf3, 0x4c, 0x6e, 0xcf, 0x82, 0xd9, 0xc0, 0x71, 0x99, 0x39, 0x2e, 0xf3, 0xeb, 0xd2, 0x63, 0x9e, 0xc7, 0x36, 0xdf, 0x14, 0xb5, 0xcc, 0x0b, 0x78, 0xce, 0x42, 0xee, 0x27, 0x4a, 0xbb, 0x79, 0x11, 0xb7, 0xf5, 0xdf, 0xca, 0xfe, 0x84, 0xe7, 0x2e, 0x81, 0xa5, 0x3c, 0xfe, 0xef, 0x5f, 0x55, 0x22, 0xc3, 0xfc, 0x0d, 0xfc, 0x2c, 0x0f, 0x44, 0x26, 0xc8, 0x82, 0xa8, 0xfa, 0xd0, 0x54, 0x3a, 0x7e, 0xbf, 0xa2, 0x84, 0xa8, 0x11, 0xb5, 0x4b, 0xd4, 0x8b, 0xda, 0x6d, 0x5c, 0x45, 0xfd, 0x4a, 0x57, 0x95, 0xb0, 0x45, 0xe5, 0xf0, 0xfc, 0x53, 0x70, 0x1a, 0xe5, 0xcb, 0x37, 0xae, + 0xaa, 0x9e, 0x1c, 0xe5, 0x65, 0x59, 0x28, 0x0f, 0x46, 0xb7, 0x94, 0x45, 0xd1, 0xb8, 0xe5, 0xe8, 0xd6, 0xc6, 0x6f, 0xf1, 0x64, 0x44, 0x57, 0xa0, 0x8c, 0xed, 0x65, 0xae, 0xa5, 0x03, 0x74, 0x04, 0xf4, 0xd9, 0xd2, 0x19, 0x50, 0x5a, 0x4b, 0x57, 0xe8, 0x06, 0x37, 0x40, 0x4f, 0xb8, 0x11, 0x6e, 0x82, 0x5e, 0xd0, 0x1b, 0xfa, 0x40, 0x5f, 0xb8, 0x19, 0x6e, 0x81, 0x5b, 0xa1, 0x1f, 0xf4, 0x87, 0xdb, 0x60, 0x00, 0xdc, 0x0e, 0x03, 0xe1, 0x0e, 0xb8, 0x13, 0x26, 0xc2, 0xc3, 0xf2, 0x94, 0x65, 0x12, 0x30, 0xf6, 0x96, 0x29, 0xb2, 0xc8, 0xf2, 0x38, 0x30, 0xf6, 0x96, 0x27, 0xa5, 0xcd, 0xf2, 0x14, 0x4c, 0x85, 0x69, 0xf0, 0x1c, 0x4c, 0xe7, 0xf1, 0x04, 0x78, 0x1e, 0x5e, 0x80, 0x19, 0x30, 0x13, 0x5e, 0xe4, 0xb5, 0x2f, 0xc1, 0x6c, 0x6e, 0xbf, 0x0c, 0xaf, 0xc0, 0xab, 0xf0, 0x1a, 0xbc, 0x0e, + 0x73, 0x60, 0x2e, 0xbc, 0x05, 0x0b, 0xe1, 0x6d, 0x78, 0x07, 0xde, 0x85, 0xf7, 0xe0, 0x7d, 0x48, 0x84, 0x0f, 0xe0, 0x43, 0x58, 0x04, 0xff, 0x80, 0x7f, 0xc2, 0x47, 0xf0, 0x31, 0x7c, 0x02, 0xcb, 0x61, 0x05, 0xfb, 0xb1, 0x12, 0x56, 0xc1, 0xd7, 0xb0, 0x1a, 0xd6, 0xc0, 0x5a, 0xf8, 0x86, 0xbf, 0x7f, 0x07, 0xdf, 0xc3, 0x0f, 0xb0, 0x01, 0x7e, 0x84, 0x3d, 0xb0, 0x17, 0xf6, 0x81, 0x0d, 0xf6, 0xc3, 0x01, 0xb0, 0xc3, 0x41, 0x70, 0xc0, 0x31, 0x38, 0x0e, 0x69, 0x90, 0x0e, 0x19, 0x90, 0x09, 0x1e, 0x28, 0x90, 0x27, 0x2c, 0x85, 0x40, 0xdc, 0x5b, 0x4a, 0xc0, 0x07, 0x7e, 0xd0, 0xa0, 0x14, 0xca, 0x20, 0xc0, 0xf1, 0xd3, 0x95, 0x59, 0xce, 0x42, 0x39, 0x54, 0x40, 0x25, 0xd0, 0x91, 0x59, 0xa4, 0x2c, 0x52, 0x04, 0x98, 0xc0, 0x0c, 0x11, 0x10, 0x09, 0x68, 0xad, 0x62, 0x01, 0x05, 0xd0, 0x5a, + 0xc5, 0x0a, 0x68, 0xad, 0x12, 0x2b, 0x6d, 0x0a, 0x3a, 0xab, 0xd4, 0x82, 0xda, 0x80, 0x93, 0x54, 0x70, 0x92, 0x0a, 0x4e, 0x52, 0xa9, 0x0f, 0xb8, 0x47, 0xa5, 0x11, 0x34, 0x06, 0xb4, 0x55, 0x41, 0x5b, 0x15, 0xb4, 0x55, 0x69, 0x0a, 0xb8, 0x47, 0x05, 0x6d, 0x55, 0x5a, 0x40, 0x4b, 0xc0, 0x09, 0x29, 0x38, 0x48, 0xa5, 0x0d, 0xe0, 0x20, 0x15, 0x1c, 0xa4, 0xd2, 0x0e, 0x70, 0x90, 0x0a, 0x0e, 0x52, 0xe9, 0x0e, 0x3d, 0xd9, 0xe6, 0x8d, 0x70, 0x13, 0x0e, 0xab, 0x2f, 0xcb, 0x9b, 0xe1, 0x16, 0xb8, 0x15, 0xfa, 0x41, 0x7f, 0xb8, 0x0d, 0x06, 0xc0, 0xed, 0x30, 0x10, 0x4f, 0x70, 0x07, 0xdc, 0x09, 0x83, 0xe0, 0x6e, 0x18, 0x0c, 0x43, 0xe0, 0x1e, 0x18, 0x0a, 0xc3, 0xe4, 0x48, 0xaa, 0xd7, 0x48, 0xaa, 0xd7, 0x48, 0xaa, 0xd7, 0x48, 0xaa, 0xd7, 0x48, 0xaa, 0xd7, 0x48, 0xaa, 0xd7, 0x48, 0x65, + 0xac, 0xdc, 0xab, 0x3c, 0x00, 0xe3, 0xe0, 0x41, 0x78, 0x08, 0xc6, 0xc3, 0x04, 0x98, 0x08, 0x0f, 0xc3, 0x24, 0x98, 0x0c, 0x8f, 0xc0, 0xa3, 0xf0, 0x18, 0x4c, 0x01, 0xb4, 0x41, 0x79, 0x02, 0x9e, 0x84, 0xa7, 0x60, 0x2a, 0x4c, 0x83, 0xa7, 0xe1, 0x19, 0xf9, 0x1e, 0x55, 0xf1, 0x3d, 0xaa, 0xe2, 0x0a, 0xaa, 0xe2, 0x0a, 0xaa, 0xe2, 0x0a, 0xaa, 0xe2, 0x0a, 0xaa, 0xe2, 0x0a, 0xaa, 0xe2, 0x0a, 0xe5, 0x45, 0x99, 0xa7, 0xbc, 0x04, 0xb3, 0xe5, 0x34, 0xaa, 0xe3, 0x34, 0xaa, 0xe3, 0x34, 0xaa, 0xe3, 0x34, 0xaa, 0xe3, 0x34, 0xaa, 0xe3, 0x34, 0xaa, 0xe3, 0x34, 0xaa, 0xe3, 0x34, 0xaa, 0xe3, 0x34, 0xe5, 0x03, 0x99, 0xaf, 0x7c, 0x08, 0x8b, 0xe0, 0x1f, 0xf0, 0x4f, 0xf8, 0x08, 0xfe, 0x33, 0x57, 0xfe, 0x2a, 0xfd, 0x8b, 0x2b, 0x7f, 0x15, 0x28, 0xdb, 0xe0, 0x37, 0xd8, 0x2e, 0xb7, 0x2b, 0x3b, + 0x60, 0x27, 0x24, 0xc3, 0x1e, 0xd8, 0x2b, 0x93, 0x95, 0x7d, 0x60, 0x83, 0xfd, 0x70, 0x10, 0x0e, 0xf1, 0xf8, 0x61, 0x48, 0x85, 0x23, 0x70, 0x14, 0x1c, 0x70, 0x0c, 0x8e, 0x43, 0x26, 0x9c, 0x80, 0x93, 0x90, 0x05, 0xd9, 0x90, 0x03, 0xa7, 0x00, 0xcd, 0x52, 0x72, 0xc1, 0x09, 0x2e, 0xc8, 0x87, 0x33, 0x40, 0x1d, 0x50, 0x0a, 0xc0, 0x0b, 0xff, 0x7b, 0x57, 0x4a, 0x29, 0x50, 0xeb, 0x40, 0x5d, 0xa8, 0x07, 0x68, 0xad, 0xda, 0x00, 0xae, 0x82, 0x46, 0xd0, 0x58, 0xd4, 0x54, 0xe3, 0x58, 0x36, 0xd1, 0xaf, 0xd3, 0xae, 0x5f, 0xd9, 0x14, 0x9a, 0x43, 0x0b, 0x68, 0x09, 0x6d, 0xa0, 0x2d, 0xb4, 0x83, 0x0e, 0xd0, 0x11, 0x3a, 0xe3, 0x5b, 0xbb, 0x8a, 0x1a, 0xea, 0x0d, 0xdc, 0xee, 0x0e, 0x3d, 0x00, 0x6f, 0xa6, 0xf6, 0x84, 0x5e, 0xd0, 0x1b, 0xfa, 0x40, 0x5f, 0xb8, 0x19, 0x6e, 0x11, 0x75, 0xd4, + 0x5b, 0x59, 0xf6, 0x83, 0xfe, 0x30, 0x00, 0x6e, 0x87, 0x81, 0x30, 0x08, 0xee, 0x82, 0xbb, 0x61, 0x30, 0x0c, 0x81, 0x7b, 0x60, 0x28, 0x0c, 0x03, 0x3a, 0x4b, 0x75, 0x38, 0x8c, 0x90, 0xdb, 0xd5, 0x91, 0x30, 0x0a, 0xee, 0x87, 0xd1, 0x30, 0x06, 0xc6, 0xc2, 0x03, 0x30, 0x1e, 0x26, 0xc0, 0x44, 0x78, 0x18, 0x26, 0xc1, 0x64, 0x78, 0x04, 0x1e, 0x85, 0xc7, 0x60, 0x0a, 0x3c, 0x0e, 0x4f, 0xc0, 0x93, 0xf0, 0x14, 0x4c, 0x85, 0x69, 0x30, 0x13, 0x66, 0xc1, 0x8b, 0x40, 0xfd, 0x53, 0x5f, 0x86, 0xd7, 0x61, 0x0e, 0xcc, 0x85, 0x37, 0x60, 0x1e, 0xcc, 0x07, 0xfd, 0x0a, 0xb0, 0x6f, 0xc1, 0x42, 0x78, 0x5b, 0xda, 0xd5, 0x77, 0xe0, 0x5d, 0x78, 0x0f, 0xa8, 0x8d, 0xea, 0x07, 0xf0, 0x21, 0x2c, 0x82, 0xc5, 0xf0, 0x91, 0xcc, 0x50, 0x3f, 0x86, 0x4f, 0x60, 0x09, 0x2c, 0x85, 0x65, 0xf0, 0xa9, 0xb8, + 0x43, 0xfd, 0x8c, 0xe5, 0xe7, 0xf0, 0x05, 0x7c, 0x09, 0x5f, 0xc1, 0x72, 0x58, 0x01, 0x2b, 0x61, 0x15, 0x7c, 0x0d, 0xab, 0x61, 0x0d, 0x50, 0x3b, 0x55, 0x6a, 0xa7, 0xba, 0x0e, 0xd6, 0xc3, 0xb7, 0xf0, 0x1d, 0x7c, 0x0f, 0x3f, 0xc0, 0x06, 0xf8, 0x11, 0x7e, 0x82, 0x9f, 0x61, 0x23, 0xfc, 0x02, 0xbf, 0xc2, 0x26, 0xd8, 0x0c, 0x5b, 0x60, 0x2b, 0x6c, 0x83, 0xdf, 0xe4, 0x41, 0x35, 0x09, 0xb6, 0xc3, 0x0e, 0xd8, 0x09, 0xc9, 0xb0, 0x0b, 0x76, 0xc3, 0x1e, 0xd8, 0x0b, 0xfb, 0xc0, 0x06, 0xfb, 0xe1, 0x00, 0xd8, 0xe1, 0x20, 0x1c, 0x82, 0xc3, 0x90, 0x0a, 0x47, 0xc0, 0x01, 0xc7, 0xe0, 0xb8, 0x4c, 0x55, 0xd3, 0x20, 0x43, 0xa6, 0xab, 0x99, 0x70, 0x02, 0x4e, 0x42, 0x16, 0x64, 0x43, 0x0e, 0x9c, 0x96, 0x5e, 0x35, 0x17, 0xfe, 0xfd, 0xdf, 0xcd, 0xf7, 0xa8, 0x65, 0x10, 0x80, 0x20, 0x9c, 0x85, + 0x72, 0x3a, 0xb0, 0xe1, 0xf2, 0x84, 0x35, 0x5d, 0x16, 0x58, 0xe9, 0xbc, 0xad, 0x5e, 0xa0, 0xf3, 0xb6, 0xd2, 0x79, 0x5b, 0xe9, 0xbc, 0xad, 0x25, 0x72, 0xaf, 0xd5, 0x07, 0x7e, 0xd0, 0xa0, 0x14, 0xca, 0xe4, 0x76, 0x6b, 0x00, 0x82, 0x70, 0x16, 0xca, 0xa1, 0x42, 0x1e, 0xb4, 0x56, 0x42, 0x08, 0xce, 0xc1, 0x79, 0x79, 0x10, 0x17, 0x3f, 0x3d, 0xe6, 0x5d, 0x99, 0x1f, 0xf3, 0x1e, 0xfc, 0x2a, 0x8b, 0x62, 0xa8, 0x71, 0x31, 0xe4, 0xbf, 0x98, 0x84, 0xa3, 0xfe, 0x56, 0xd8, 0x70, 0x71, 0xfb, 0xe9, 0x7a, 0xec, 0xf2, 0x88, 0x38, 0xc4, 0xb2, 0x54, 0xfe, 0x6a, 0x8a, 0x92, 0x9f, 0x46, 0xec, 0x92, 0x5f, 0x45, 0xa4, 0xcb, 0x52, 0x4b, 0x77, 0x59, 0x62, 0xe9, 0x01, 0x77, 0xc1, 0xdd, 0xa2, 0xae, 0x65, 0xb0, 0x68, 0x62, 0x19, 0xc2, 0xed, 0x7b, 0x60, 0x28, 0xf7, 0x87, 0x89, 0xd6, 0x96, 0x7b, + 0xb9, 0x7d, 0x1f, 0x0c, 0x87, 0x11, 0x30, 0x12, 0x46, 0xc1, 0xfd, 0x30, 0x9a, 0xe7, 0x8c, 0xe1, 0x35, 0x63, 0xb9, 0xfd, 0x00, 0x8c, 0xe3, 0xfe, 0x83, 0xbc, 0xe6, 0x21, 0x6e, 0x4f, 0x96, 0x27, 0x2d, 0x4b, 0xb8, 0xbf, 0x54, 0x6c, 0xb1, 0x2c, 0x13, 0x07, 0x2c, 0x9f, 0x72, 0xfb, 0x33, 0x6e, 0x7f, 0x2e, 0xec, 0x96, 0x9f, 0xb8, 0xfd, 0x33, 0xb7, 0x37, 0xf2, 0xf8, 0x2f, 0x62, 0xb0, 0xe5, 0x57, 0xee, 0x6f, 0xe2, 0xfe, 0x66, 0xfe, 0xb6, 0x85, 0xfb, 0xdb, 0x44, 0x4d, 0x4b, 0x12, 0x8f, 0x6d, 0xe7, 0xb1, 0x1d, 0x3c, 0x67, 0x27, 0x8f, 0x25, 0x73, 0x7f, 0x17, 0xf7, 0x77, 0xf3, 0x9c, 0x14, 0xee, 0x9f, 0x10, 0x4d, 0x2d, 0x27, 0xc5, 0x1e, 0x4b, 0x96, 0x38, 0x69, 0xc9, 0x16, 0xa3, 0x2d, 0x39, 0xdc, 0x3f, 0xc5, 0xdf, 0x4e, 0x8b, 0x1c, 0x4b, 0x2e, 0xf7, 0x9d, 0xa2, 0xbe, 0xc5, 0x2b, + 0xff, 0xa1, 0x74, 0x10, 0x1f, 0x2b, 0x1d, 0xc5, 0x6a, 0xa5, 0x33, 0xcb, 0x2e, 0x62, 0xad, 0x72, 0x9f, 0x7c, 0x5b, 0x99, 0x29, 0x53, 0x95, 0xd7, 0x45, 0x8c, 0xb2, 0x40, 0x5c, 0xab, 0xbc, 0x65, 0x8a, 0x57, 0x16, 0x9a, 0xba, 0x28, 0x6f, 0x8b, 0x97, 0x95, 0x77, 0x44, 0x63, 0xe5, 0x5d, 0x51, 0xc7, 0xda, 0x44, 0xbc, 0x66, 0x6d, 0x2a, 0xde, 0xb6, 0x36, 0xe3, 0x76, 0x0b, 0x6e, 0xb7, 0x14, 0x1f, 0x5a, 0x5b, 0x71, 0xbb, 0x1d, 0xb7, 0xaf, 0xe3, 0xf1, 0xeb, 0xb9, 0xdd, 0x81, 0xdb, 0x1d, 0x79, 0xbc, 0x93, 0xa8, 0xc3, 0x2c, 0x6c, 0x8f, 0xf9, 0x56, 0x6a, 0x31, 0xdf, 0x9b, 0xda, 0xc5, 0xfc, 0x60, 0xea, 0x1e, 0xb3, 0x41, 0x4c, 0x8b, 0xf9, 0x51, 0xca, 0x98, 0x9f, 0x44, 0xed, 0x98, 0x5f, 0xc4, 0xd5, 0xc6, 0x6c, 0xac, 0xfe, 0x17, 0xb3, 0xf1, 0x19, 0xb3, 0xe1, 0xff, 0x7b, 0x36, 0x18, + 0xfd, 0x3f, 0xce, 0x46, 0x1d, 0x66, 0x23, 0xe6, 0x7f, 0x7d, 0x36, 0x6a, 0x32, 0x1b, 0x3b, 0x18, 0x7d, 0x1b, 0xa3, 0xbf, 0x81, 0xd1, 0x4f, 0x65, 0x74, 0x3c, 0x96, 0xf5, 0x8c, 0xd4, 0x61, 0x71, 0x23, 0x7b, 0xb9, 0x46, 0x69, 0x28, 0x6a, 0xb3, 0x67, 0x6b, 0xd9, 0xab, 0x69, 0xca, 0x02, 0x5c, 0x0f, 0x8e, 0x55, 0xd1, 0x7f, 0xbf, 0xb1, 0x13, 0xaf, 0xfc, 0x55, 0xec, 0x65, 0xee, 0x6c, 0xf2, 0x38, 0x73, 0xe9, 0x65, 0x2e, 0x7f, 0x63, 0x4d, 0xab, 0x58, 0xd3, 0x07, 0x26, 0xfc, 0x1b, 0x6b, 0x5b, 0x13, 0x71, 0x54, 0x96, 0x32, 0x9f, 0x6e, 0xe6, 0xf3, 0x38, 0xf3, 0x79, 0x9c, 0xf9, 0x3c, 0x6e, 0xb9, 0x9b, 0xf9, 0x1d, 0x0c, 0x43, 0xb8, 0x7d, 0x0f, 0x0c, 0xe5, 0xf6, 0x30, 0xb8, 0x97, 0xdb, 0xf7, 0xc1, 0x70, 0x18, 0x01, 0x23, 0x61, 0x14, 0xdc, 0x0f, 0xa3, 0xf9, 0xfb, 0x18, 0x18, 0xcb, + 0xed, 0x07, 0x60, 0x1c, 0xb7, 0x1f, 0x84, 0x87, 0xb8, 0x3d, 0x59, 0x6e, 0x61, 0x4f, 0xdf, 0x64, 0x2f, 0xdf, 0x60, 0xfc, 0xb6, 0xb2, 0xa7, 0xf5, 0xd8, 0xd3, 0x4c, 0xf6, 0x34, 0x93, 0x3d, 0xcd, 0x54, 0x16, 0x57, 0xfd, 0xba, 0x31, 0xda, 0x50, 0x8a, 0x36, 0x94, 0x8a, 0x26, 0x74, 0xd9, 0x39, 0xec, 0x7d, 0x0e, 0xdd, 0xf5, 0x09, 0xba, 0x6a, 0x07, 0x1d, 0x5b, 0x06, 0xdd, 0x5a, 0x06, 0x5d, 0x59, 0x06, 0x5d, 0x99, 0xc3, 0xb8, 0x7a, 0xc6, 0x14, 0x5c, 0xe0, 0x30, 0x99, 0x83, 0x7b, 0xcb, 0xc1, 0xbd, 0xe5, 0xe0, 0xde, 0x72, 0x70, 0x6f, 0x39, 0xb8, 0xb7, 0x1c, 0xdc, 0x5b, 0x0e, 0x2e, 0xeb, 0x04, 0x2e, 0xeb, 0x04, 0x2e, 0xca, 0x81, 0x8b, 0x72, 0xe0, 0xa2, 0x1c, 0xb8, 0x28, 0x07, 0x2e, 0xca, 0x81, 0x8b, 0x72, 0xe0, 0xa2, 0x1c, 0xb8, 0x28, 0x07, 0x2e, 0xca, 0xc1, 0xd6, 0xfd, 0x62, + 0x9e, 0xb0, 0x48, 0x87, 0x50, 0xa0, 0x26, 0x34, 0x64, 0x3f, 0xae, 0x61, 0x19, 0x07, 0x4d, 0xa0, 0x39, 0xec, 0x83, 0x83, 0x50, 0x75, 0x26, 0xf5, 0xb0, 0x71, 0x16, 0x35, 0x9f, 0xbd, 0x19, 0x00, 0x83, 0x64, 0x29, 0x7d, 0xe4, 0x1d, 0xf4, 0x91, 0x0e, 0xf3, 0x60, 0x51, 0x8f, 0x3e, 0xb2, 0x0f, 0x7d, 0xa4, 0xc3, 0x7c, 0x9f, 0x3c, 0x46, 0xff, 0xe8, 0x88, 0x30, 0xc9, 0xf4, 0x08, 0xb3, 0xdc, 0x15, 0x11, 0x21, 0xb5, 0x88, 0x48, 0x88, 0x02, 0x15, 0xac, 0xf2, 0x58, 0x44, 0x8c, 0x4c, 0x8e, 0xa8, 0xc1, 0x3c, 0xd4, 0xe1, 0x76, 0x5d, 0x99, 0x12, 0x51, 0x4f, 0x1e, 0x8f, 0xa8, 0xcf, 0xdf, 0xae, 0xe2, 0x75, 0x57, 0xcb, 0xd4, 0x88, 0x38, 0xb9, 0x3f, 0xa2, 0x09, 0x7f, 0x6b, 0xc9, 0x63, 0xad, 0xa1, 0x8d, 0xdc, 0x13, 0x31, 0x46, 0xee, 0x8c, 0x58, 0xce, 0xdf, 0xf5, 0x33, 0xa9, 0x36, 0xe9, + 0x8b, 0xd8, 0x0f, 0xfa, 0xd9, 0xd4, 0x0c, 0xfe, 0x9e, 0x29, 0xb5, 0xea, 0x67, 0x33, 0xa3, 0x9a, 0xc9, 0x54, 0x7a, 0x3c, 0x87, 0x7e, 0xc6, 0x32, 0xaa, 0x42, 0x3a, 0xa2, 0x23, 0xa5, 0x93, 0xbe, 0xc9, 0x41, 0xdf, 0xe4, 0xa0, 0x6f, 0x72, 0xd0, 0x37, 0x39, 0xf4, 0xb3, 0x97, 0x96, 0x67, 0xe1, 0x39, 0x58, 0x01, 0x2b, 0x61, 0x15, 0x7c, 0x0d, 0xab, 0x61, 0x0d, 0xac, 0x85, 0xf5, 0x62, 0x8a, 0x65, 0x9f, 0xe8, 0x64, 0xb1, 0x89, 0xee, 0x96, 0xfd, 0x22, 0xce, 0x72, 0x40, 0x58, 0x89, 0xc4, 0x59, 0x96, 0x54, 0x6e, 0x1f, 0x85, 0x74, 0xd1, 0xd6, 0x92, 0x29, 0xe2, 0xe8, 0x0d, 0x1c, 0xf4, 0x06, 0x0e, 0x7a, 0x03, 0x07, 0xbd, 0x81, 0x83, 0xde, 0xc0, 0x41, 0x6f, 0xe0, 0xa0, 0x37, 0x70, 0xd0, 0x1b, 0x38, 0x94, 0x06, 0xd0, 0x50, 0x5c, 0x67, 0x9c, 0x31, 0xbd, 0x1a, 0x1a, 0x41, 0x63, 0x60, + 0xcc, 0xe9, 0x13, 0x1c, 0xf4, 0x09, 0x0e, 0xfa, 0x04, 0x07, 0x7d, 0x82, 0x83, 0x3e, 0xc1, 0x41, 0x9f, 0xe0, 0xa0, 0x4f, 0x70, 0xd0, 0x27, 0x38, 0xe8, 0x13, 0x1c, 0xf4, 0x09, 0x0e, 0xfa, 0x04, 0x07, 0x7d, 0x82, 0x83, 0x3e, 0xc1, 0x41, 0x9f, 0xe0, 0xa0, 0x4f, 0x70, 0x28, 0x44, 0x8b, 0xd2, 0x81, 0x0c, 0xe8, 0xc4, 0xb2, 0x33, 0xcb, 0x2e, 0xd0, 0x95, 0xdb, 0xdd, 0xc2, 0x67, 0x66, 0xbb, 0xc3, 0xeb, 0x62, 0x87, 0xb5, 0xb1, 0x68, 0x6d, 0xdd, 0x24, 0x1d, 0xd6, 0x43, 0x52, 0xa3, 0xce, 0x39, 0xa8, 0x73, 0x0e, 0xea, 0x9c, 0x83, 0x3a, 0xe7, 0xa0, 0xce, 0x39, 0x62, 0x3e, 0x22, 0x27, 0x37, 0x33, 0xbf, 0x3d, 0x45, 0x84, 0xf4, 0x89, 0x48, 0x88, 0x92, 0xc5, 0x44, 0x88, 0x9d, 0x08, 0xb1, 0x13, 0x21, 0x76, 0x22, 0x44, 0x3f, 0x63, 0x64, 0x27, 0x42, 0xec, 0x44, 0x88, 0x9d, 0x08, 0xb1, + 0x8b, 0x96, 0x3c, 0xaf, 0x15, 0xb4, 0x95, 0x9a, 0xb8, 0x16, 0xda, 0x71, 0xbb, 0x3d, 0xec, 0x45, 0x4d, 0xf7, 0xf1, 0x77, 0x66, 0x8a, 0x08, 0x3a, 0x24, 0x0e, 0xf3, 0xb7, 0x00, 0x84, 0x78, 0xec, 0x3c, 0x91, 0x56, 0x75, 0x5e, 0x5e, 0x23, 0xa2, 0xec, 0x44, 0x94, 0xdd, 0x74, 0x46, 0x6a, 0xc4, 0xbc, 0x9f, 0xc8, 0xb2, 0x13, 0xf7, 0x7e, 0xe3, 0x8a, 0x31, 0x83, 0xe4, 0x71, 0x22, 0x6c, 0x2c, 0x11, 0x66, 0x27, 0xc2, 0xea, 0x98, 0x87, 0x12, 0x71, 0xc3, 0xc4, 0x08, 0xa2, 0xcc, 0x4e, 0x94, 0xd9, 0xc9, 0x0d, 0xbf, 0x79, 0x34, 0x8f, 0x8d, 0xe5, 0xb9, 0xe3, 0xb9, 0x3f, 0x89, 0xfb, 0x8f, 0x18, 0x57, 0x85, 0xf7, 0x99, 0x1f, 0x67, 0x79, 0x41, 0x1e, 0x35, 0x4b, 0x99, 0x46, 0x24, 0xa6, 0x10, 0x89, 0x9b, 0x88, 0xc4, 0xc3, 0x44, 0xe2, 0x61, 0x22, 0xf1, 0x70, 0x84, 0x42, 0x74, 0xa9, 0x2c, + 0xad, 0xfc, 0x2d, 0x46, 0xfe, 0x4a, 0x34, 0xee, 0x21, 0x1a, 0x93, 0x89, 0xc6, 0xdd, 0x44, 0xe3, 0x7e, 0xa2, 0xf1, 0x30, 0xd1, 0x78, 0x80, 0x68, 0xdc, 0x45, 0x34, 0x26, 0x13, 0x8d, 0xf6, 0x88, 0xa6, 0xb2, 0x24, 0xa2, 0x19, 0x8f, 0xb5, 0xe4, 0x6f, 0xad, 0xe4, 0x51, 0x22, 0xf3, 0x30, 0x91, 0xb9, 0x23, 0xa2, 0xad, 0x3c, 0x42, 0x74, 0x6e, 0x25, 0x3a, 0xed, 0x44, 0xa7, 0x1d, 0x95, 0xf1, 0x11, 0xa1, 0x99, 0x44, 0x68, 0x66, 0xc4, 0x11, 0x23, 0x4a, 0xed, 0x44, 0xe9, 0x51, 0xa2, 0xf4, 0x28, 0x51, 0x6a, 0x27, 0x4a, 0xed, 0x44, 0xa9, 0x9d, 0x28, 0xdd, 0x4d, 0x94, 0xda, 0xa3, 0x18, 0x17, 0x22, 0xd5, 0x4e, 0xa4, 0xda, 0x89, 0xd4, 0x93, 0x16, 0xc6, 0xcf, 0xd2, 0x01, 0x3a, 0x42, 0x27, 0xe8, 0x0c, 0x5d, 0xa0, 0x2b, 0x74, 0x83, 0x1b, 0xa0, 0x3b, 0xf4, 0x80, 0x78, 0xe8, 0x09, 0x37, + 0xc2, 0x4d, 0xd0, 0x0b, 0x7a, 0x43, 0x1f, 0xe8, 0x0b, 0x37, 0xc3, 0x2d, 0x70, 0x2b, 0xf4, 0x83, 0xfe, 0x70, 0x1b, 0x0c, 0x80, 0xdb, 0x61, 0x20, 0xdc, 0x01, 0x8c, 0x37, 0x4a, 0xe8, 0x43, 0x09, 0x7d, 0x28, 0xa1, 0x0f, 0x25, 0xf4, 0xa1, 0x84, 0x3e, 0x94, 0xd0, 0x87, 0x12, 0xfa, 0x50, 0x42, 0x1f, 0x4a, 0xe8, 0x43, 0x09, 0x7d, 0x28, 0xa1, 0x0f, 0x25, 0xf4, 0xa1, 0x84, 0x3e, 0x94, 0xd0, 0x87, 0x12, 0xfa, 0x50, 0x42, 0x1f, 0x4a, 0xe8, 0x43, 0x09, 0x7d, 0x28, 0xa1, 0x0f, 0x25, 0xf4, 0xa1, 0x84, 0x3e, 0xcb, 0x78, 0x98, 0x00, 0x13, 0xe1, 0x61, 0x59, 0x6c, 0x99, 0x24, 0xf5, 0xef, 0xd7, 0x17, 0x93, 0x8d, 0x76, 0xb2, 0xd1, 0x4e, 0x36, 0xda, 0xc9, 0x46, 0x3b, 0xd9, 0x68, 0x27, 0x1b, 0xed, 0x64, 0xa3, 0x9d, 0x6c, 0xb4, 0x93, 0x8d, 0x76, 0xb2, 0xd1, 0x4e, 0x36, 0xda, 0xc9, 0x46, + 0x3b, 0xd9, 0x68, 0x27, 0x1b, 0xed, 0xe1, 0x6c, 0xbc, 0x81, 0x6c, 0xec, 0x45, 0x36, 0xf6, 0x20, 0x1b, 0x6b, 0x85, 0xb3, 0xb1, 0x07, 0xd9, 0xd8, 0xc3, 0x92, 0x26, 0xda, 0x19, 0x19, 0x99, 0x41, 0x96, 0x66, 0x8a, 0x1e, 0x64, 0xa5, 0x9d, 0xac, 0xb4, 0x93, 0x95, 0x76, 0xb2, 0xd2, 0x4e, 0x56, 0xda, 0xc9, 0x4a, 0x3b, 0x59, 0x69, 0x27, 0x2b, 0xed, 0x64, 0xa5, 0x9d, 0xac, 0x6c, 0x47, 0x56, 0xda, 0xc9, 0x4a, 0x3b, 0x59, 0x69, 0x27, 0x2b, 0xed, 0x64, 0xa5, 0x9d, 0xac, 0xb4, 0x93, 0x95, 0x76, 0xb2, 0xd2, 0x4e, 0x56, 0xda, 0xc9, 0x4a, 0x3b, 0x59, 0x69, 0x27, 0x2b, 0xed, 0x64, 0xa5, 0x9d, 0xac, 0xb4, 0x93, 0x95, 0x76, 0xb2, 0xd2, 0x4e, 0x56, 0xda, 0xc9, 0x4a, 0x3b, 0x59, 0x69, 0x27, 0x2b, 0xed, 0x64, 0xa5, 0x9d, 0xac, 0x2c, 0x24, 0x2b, 0xed, 0x64, 0x65, 0x21, 0x59, 0x59, 0x48, + 0x56, 0xda, 0xc9, 0x4a, 0x3b, 0x59, 0x69, 0x27, 0x2b, 0xed, 0x74, 0xeb, 0x3e, 0xba, 0x75, 0x1f, 0xdd, 0xba, 0x8f, 0x6e, 0xdd, 0xa7, 0x30, 0xfe, 0x74, 0xec, 0x3e, 0x3a, 0x76, 0x1f, 0x1d, 0xbb, 0x8f, 0x8e, 0xdd, 0x47, 0xc7, 0xee, 0xa3, 0x2b, 0xd7, 0xe8, 0xca, 0x35, 0xba, 0x72, 0x8d, 0xae, 0x5c, 0xa3, 0x2b, 0xd7, 0xe8, 0xca, 0x35, 0x45, 0x7f, 0x0f, 0x78, 0x22, 0x3c, 0x0c, 0x93, 0x60, 0x32, 0x10, 0xff, 0x74, 0xe5, 0x1a, 0x5d, 0xb9, 0x46, 0x57, 0xae, 0xd1, 0x95, 0x6b, 0x74, 0xe5, 0x1a, 0x5d, 0xb9, 0x46, 0x57, 0xae, 0xd1, 0x95, 0x6b, 0x74, 0xe5, 0x1a, 0x5d, 0xb9, 0x46, 0xbd, 0xf0, 0x51, 0x2f, 0x7c, 0xd4, 0x0b, 0x1f, 0xf5, 0xc2, 0x47, 0xbd, 0xf0, 0x51, 0x2f, 0x7c, 0xca, 0x6b, 0xf0, 0xba, 0xd8, 0xa0, 0xcc, 0x61, 0x39, 0x17, 0xde, 0x80, 0x79, 0x30, 0x1f, 0xde, 0x84, 0x05, + 0xf0, 0x0e, 0xbc, 0x0b, 0xef, 0xc1, 0xfb, 0x90, 0x28, 0x7d, 0x28, 0x4c, 0x43, 0x6b, 0x1c, 0xea, 0xd2, 0x04, 0x9a, 0x42, 0x33, 0x68, 0x0e, 0x2d, 0x00, 0x25, 0xb7, 0xb6, 0x02, 0xd4, 0xdc, 0xda, 0x06, 0xd0, 0x0b, 0x2b, 0x7a, 0x61, 0x6d, 0x07, 0xd7, 0xc1, 0xf5, 0xd0, 0x1e, 0x3a, 0x40, 0x47, 0xe8, 0x04, 0x3f, 0xc2, 0x26, 0x69, 0xb7, 0x6e, 0x96, 0xa5, 0xd6, 0x2d, 0xb0, 0x15, 0xb6, 0xc1, 0x76, 0x1e, 0x3f, 0x24, 0x0f, 0xa3, 0x62, 0x76, 0x54, 0xcc, 0x8e, 0x8a, 0xd9, 0x51, 0x31, 0x3b, 0x2a, 0x66, 0xb7, 0xea, 0xd7, 0x38, 0xf0, 0x81, 0x1f, 0xf4, 0xcf, 0x7f, 0x94, 0xa2, 0x6a, 0x1f, 0xc9, 0xc3, 0x31, 0x9b, 0xa4, 0x0f, 0x75, 0xb3, 0x8b, 0x73, 0xa8, 0x9b, 0x86, 0xba, 0x69, 0xa8, 0x9b, 0xef, 0xb2, 0xfa, 0x57, 0xfc, 0xa7, 0xfa, 0x77, 0x51, 0xdd, 0xda, 0xa1, 0x58, 0xec, 0x1d, 0xaa, + 0xa6, 0x19, 0x35, 0xd1, 0xc6, 0xf2, 0xa0, 0x3c, 0x16, 0x56, 0x34, 0xbd, 0x36, 0x1e, 0x41, 0xcd, 0xfc, 0x97, 0xea, 0x63, 0x3f, 0xd4, 0x47, 0xaf, 0x91, 0xb7, 0xb3, 0xbc, 0x13, 0x06, 0xc9, 0x93, 0x28, 0xd9, 0xfd, 0xe1, 0x5a, 0x59, 0x17, 0x15, 0xbb, 0x2f, 0x5c, 0x2b, 0x1d, 0xa8, 0x98, 0x86, 0x82, 0x69, 0x7a, 0xcd, 0x44, 0xc1, 0x74, 0xf5, 0xd2, 0x95, 0xcb, 0x83, 0x72, 0x15, 0xa0, 0x5c, 0xfb, 0x51, 0xae, 0x1d, 0x28, 0x57, 0x1a, 0xca, 0x95, 0x86, 0x72, 0xa5, 0xa1, 0x5c, 0x05, 0x28, 0x57, 0x1a, 0xca, 0x75, 0x00, 0xe5, 0xda, 0x8e, 0x72, 0x1d, 0x40, 0xb9, 0xf6, 0xa3, 0x5c, 0xfb, 0x51, 0xae, 0x54, 0x94, 0x2b, 0x2d, 0x5c, 0x47, 0x0f, 0xa0, 0x5c, 0x76, 0x94, 0xeb, 0x38, 0xca, 0xe5, 0x47, 0xb9, 0x52, 0x51, 0xae, 0x34, 0x94, 0xcb, 0x83, 0x72, 0xa5, 0xa1, 0x5c, 0xfb, 0x50, 0xae, + 0x63, 0x28, 0xd7, 0xae, 0x4b, 0x75, 0x75, 0x17, 0xb5, 0xf4, 0xe2, 0xf7, 0x04, 0x8e, 0x70, 0xbb, 0xaa, 0xbe, 0x66, 0xa2, 0x5c, 0x99, 0x97, 0xd5, 0xd7, 0x83, 0x46, 0x7d, 0xad, 0x52, 0xae, 0x8b, 0x35, 0xf6, 0x14, 0xca, 0xa5, 0xa1, 0x5c, 0x1a, 0xca, 0xa5, 0xa1, 0x5c, 0x1a, 0xca, 0xa5, 0xa1, 0x5c, 0x1a, 0xca, 0xa5, 0xa1, 0x5c, 0x1a, 0xca, 0xa5, 0xa1, 0x5c, 0x1a, 0xca, 0x55, 0xf5, 0x49, 0x86, 0x9e, 0x70, 0x23, 0xdc, 0x04, 0xbd, 0xa0, 0x37, 0xf4, 0x81, 0xbe, 0x70, 0x33, 0xdc, 0x02, 0xb7, 0x02, 0xe3, 0x89, 0x72, 0x69, 0x28, 0x97, 0x86, 0x72, 0x69, 0x28, 0x97, 0x86, 0x72, 0x69, 0x28, 0x97, 0x86, 0x72, 0x69, 0x28, 0x97, 0x86, 0x72, 0x69, 0x28, 0x97, 0x86, 0x72, 0x69, 0x28, 0x97, 0x86, 0x72, 0x69, 0x28, 0x97, 0x86, 0x72, 0x69, 0x28, 0x97, 0x86, 0x72, 0x69, 0x28, 0x97, 0x86, + 0x72, 0x69, 0x28, 0x97, 0x86, 0x72, 0x69, 0x28, 0x97, 0x86, 0x72, 0x69, 0x28, 0x97, 0x86, 0x72, 0x69, 0x28, 0x97, 0x86, 0x72, 0x69, 0x28, 0xd7, 0xa5, 0x4f, 0x59, 0xa0, 0x5c, 0x1a, 0xca, 0xe5, 0x43, 0xb9, 0x7c, 0x28, 0x97, 0xef, 0x7f, 0xe0, 0x23, 0x1e, 0x45, 0xb9, 0xba, 0xa0, 0x5c, 0x3d, 0x50, 0xae, 0x6e, 0x28, 0x57, 0x0d, 0x94, 0x6b, 0x06, 0xca, 0xd5, 0x0d, 0xe5, 0xea, 0x86, 0x72, 0xb5, 0x41, 0xb9, 0x5a, 0xa1, 0x5c, 0x0a, 0xca, 0xd5, 0xed, 0xdf, 0xf4, 0x13, 0xed, 0xff, 0x5f, 0xf2, 0x13, 0x25, 0x61, 0x3f, 0x51, 0x82, 0x72, 0x95, 0xfc, 0xc9, 0x4f, 0xfc, 0xbb, 0xca, 0xf5, 0x8c, 0x2c, 0x45, 0x61, 0x4a, 0x51, 0x18, 0x0d, 0x85, 0xd1, 0x50, 0x18, 0x0d, 0x85, 0xd1, 0x50, 0x18, 0x2d, 0xac, 0x30, 0x1a, 0x0a, 0xa3, 0xa1, 0x30, 0x1a, 0x0a, 0xa3, 0xa1, 0x30, 0x1a, 0x0a, 0xa3, + 0xa1, 0x30, 0x1a, 0x0a, 0xa3, 0xa1, 0x30, 0x1a, 0x0a, 0xa3, 0xa1, 0x30, 0x1a, 0x0a, 0xa3, 0xa1, 0x30, 0xad, 0xc2, 0xaa, 0xe0, 0x40, 0x05, 0xfc, 0xa8, 0x40, 0xda, 0x5f, 0x78, 0x99, 0x34, 0x32, 0xde, 0xf0, 0x33, 0xa2, 0x01, 0xde, 0x5a, 0xc3, 0x5b, 0x6b, 0x64, 0xb0, 0x8b, 0x4c, 0x73, 0x91, 0x61, 0x2e, 0x7c, 0xb4, 0x86, 0x8f, 0xd6, 0xf0, 0xd1, 0x1a, 0x3e, 0x5a, 0xc3, 0x47, 0x6b, 0xf8, 0x68, 0x0d, 0x1f, 0xad, 0xb1, 0xb7, 0x2e, 0xf6, 0xd6, 0xc5, 0xde, 0xba, 0xd8, 0x5b, 0x17, 0x7b, 0xe8, 0x62, 0x0f, 0x5d, 0xec, 0xa1, 0x8b, 0x3d, 0x74, 0xb1, 0x87, 0x2e, 0xe3, 0x53, 0x60, 0xbe, 0xf0, 0xaf, 0x41, 0x95, 0x9a, 0x1e, 0x64, 0xf9, 0x10, 0x3c, 0x2a, 0x0b, 0x4d, 0x0b, 0x64, 0x8a, 0xe9, 0x5d, 0xb4, 0xc4, 0x7c, 0xe9, 0x1a, 0x62, 0x9f, 0x0b, 0xb3, 0xdc, 0x2f, 0xfa, 0xca, 0xf3, 0xe2, + 0x66, 0xb8, 0x05, 0x6e, 0x85, 0x7e, 0xd0, 0x1f, 0x6e, 0x83, 0x01, 0x70, 0x3b, 0x0c, 0x84, 0x3b, 0xe0, 0x4e, 0x18, 0x04, 0x77, 0xc1, 0xdd, 0x30, 0x18, 0x86, 0xc0, 0x3d, 0x30, 0x14, 0x86, 0xc1, 0xbd, 0x70, 0x1f, 0x0c, 0x87, 0x11, 0x30, 0x12, 0x46, 0xc1, 0xfd, 0x30, 0x1a, 0xc6, 0xc0, 0x58, 0x78, 0x00, 0xc6, 0xc1, 0x4b, 0x30, 0x5b, 0x9e, 0x13, 0x2f, 0xc3, 0x2b, 0xf0, 0x2a, 0xbc, 0x06, 0xaf, 0xc3, 0x1c, 0x98, 0x0b, 0x6f, 0xc0, 0x3c, 0x58, 0x28, 0x2b, 0xc5, 0x3b, 0xb2, 0x4c, 0xbc, 0x27, 0x83, 0xe1, 0xab, 0x89, 0xa5, 0x99, 0x7a, 0xca, 0xaf, 0x4c, 0xbd, 0xe4, 0x0a, 0x53, 0x6f, 0xa8, 0xba, 0x9a, 0x58, 0x7e, 0xf8, 0x6a, 0x62, 0xf9, 0xa6, 0x5b, 0xe5, 0xc7, 0xa6, 0x7e, 0x30, 0xd0, 0xf8, 0xec, 0xf4, 0xde, 0xf0, 0xf5, 0xa4, 0xf4, 0x4f, 0x99, 0xed, 0x37, 0x8d, 0x92, 0x1e, 0xc6, + 0xa7, 0x90, 0xf1, 0x29, 0x34, 0x4d, 0x96, 0xa7, 0xcc, 0x6c, 0xdb, 0xcc, 0xb6, 0xcd, 0xaf, 0xcb, 0x32, 0xf3, 0x42, 0x58, 0x0a, 0x5f, 0xc8, 0xf3, 0xe6, 0x2f, 0xe1, 0x2b, 0x58, 0x0e, 0x2b, 0x60, 0x25, 0xac, 0x82, 0xaf, 0x61, 0x35, 0xac, 0x81, 0xb5, 0xbc, 0xee, 0x1b, 0x79, 0xee, 0xe2, 0x67, 0x10, 0xa3, 0x92, 0xe4, 0xf9, 0xa8, 0xed, 0x60, 0x87, 0x83, 0x70, 0x08, 0x0e, 0x43, 0xaa, 0x3c, 0x1f, 0xdd, 0x56, 0x9e, 0x53, 0x0a, 0xe5, 0x79, 0xa5, 0x18, 0x4a, 0xc0, 0x07, 0x7e, 0xd0, 0xa0, 0x14, 0xca, 0x20, 0x00, 0x41, 0x38, 0x0b, 0xe5, 0x50, 0x01, 0x95, 0x10, 0x82, 0x73, 0x70, 0x1e, 0x2e, 0xc8, 0xf3, 0xea, 0x47, 0xf2, 0x9c, 0xfa, 0x31, 0x7c, 0x02, 0x4b, 0x60, 0x29, 0x2c, 0x83, 0x4f, 0xe1, 0x33, 0xf8, 0x1c, 0xbe, 0x80, 0x2f, 0xe1, 0x2b, 0x58, 0x0e, 0x2b, 0x60, 0x25, 0xac, 0x82, + 0xaf, 0x61, 0x35, 0xac, 0x01, 0x8e, 0x41, 0xe5, 0x18, 0xd4, 0x75, 0xb0, 0x1e, 0xbe, 0x85, 0xef, 0xe0, 0x7b, 0xf8, 0x01, 0x36, 0xc0, 0x8f, 0xf0, 0x13, 0xfc, 0x0c, 0x1b, 0xe1, 0x17, 0xf8, 0x15, 0x36, 0xc1, 0x66, 0xd8, 0x02, 0x5b, 0x61, 0x1b, 0x9c, 0x96, 0x95, 0x6a, 0x2e, 0x94, 0xca, 0x32, 0xb5, 0x0c, 0x02, 0x10, 0x84, 0xb3, 0x50, 0xce, 0x1c, 0x46, 0x84, 0x7f, 0x23, 0x5b, 0xff, 0xad, 0xea, 0x4c, 0x11, 0x79, 0xe9, 0x37, 0xa7, 0xc3, 0xbf, 0x64, 0x66, 0x3a, 0x49, 0x15, 0x0c, 0x50, 0x05, 0x03, 0x54, 0xc1, 0x00, 0x15, 0x30, 0x40, 0x05, 0x0c, 0x50, 0x01, 0x03, 0x54, 0xc0, 0x80, 0xa0, 0x36, 0x8b, 0x8e, 0xf2, 0xa4, 0xe8, 0x04, 0x9d, 0xa1, 0x8b, 0x74, 0x8b, 0xae, 0xd0, 0x0d, 0x6e, 0x80, 0xee, 0xd0, 0x03, 0xe2, 0xa1, 0x97, 0xcc, 0x16, 0xbd, 0x59, 0xf6, 0x81, 0xbe, 0x6c, 0xeb, + 0x66, 0xb8, 0x05, 0x6e, 0x85, 0x7e, 0xd0, 0x1f, 0x6e, 0x83, 0x01, 0x70, 0x3b, 0x0c, 0x84, 0x3b, 0xe0, 0x4e, 0x18, 0x04, 0x77, 0xc1, 0xdd, 0x30, 0x18, 0x86, 0xc0, 0x3d, 0x30, 0x14, 0x86, 0xc1, 0xbd, 0x70, 0x1f, 0x0c, 0x87, 0x11, 0xa0, 0x1f, 0xcb, 0x28, 0xb8, 0x1f, 0x46, 0xc3, 0x18, 0x18, 0x0b, 0x0f, 0xc0, 0x38, 0x78, 0x48, 0xbf, 0xb6, 0x22, 0xfb, 0x31, 0x01, 0x26, 0xc2, 0xc3, 0xdc, 0x9f, 0x04, 0x93, 0xe1, 0x11, 0x7a, 0x93, 0x47, 0x59, 0x3e, 0x06, 0x53, 0xf8, 0xdb, 0xe3, 0xf0, 0x04, 0x3c, 0x09, 0x4f, 0xc1, 0x54, 0x98, 0x06, 0x4f, 0xc3, 0x33, 0xf0, 0x2c, 0x3c, 0x07, 0xd3, 0xe1, 0x79, 0x78, 0x11, 0x5e, 0x62, 0x1b, 0xb3, 0x65, 0x90, 0x6c, 0x0a, 0x92, 0x4d, 0x41, 0xb2, 0x29, 0x48, 0x36, 0x05, 0xc9, 0xa6, 0x20, 0xd9, 0x14, 0x24, 0x9b, 0x82, 0x64, 0x53, 0x90, 0x6c, 0x0a, + 0x0a, 0x34, 0x43, 0xe8, 0x9a, 0xb1, 0x40, 0xe6, 0x8a, 0xb7, 0x78, 0x6d, 0xd5, 0xd5, 0x8a, 0x73, 0xc9, 0xb0, 0x3c, 0xdc, 0x42, 0xc0, 0xe4, 0x96, 0x6e, 0x93, 0x07, 0x5d, 0x1a, 0x20, 0x03, 0x38, 0x84, 0x00, 0xce, 0x20, 0x80, 0x1b, 0x08, 0x98, 0xd9, 0xb6, 0x99, 0x6d, 0x9b, 0xd9, 0xae, 0x79, 0x26, 0xcc, 0x82, 0xd9, 0xc0, 0xf6, 0xc8, 0xa0, 0x20, 0x19, 0x94, 0x67, 0xc6, 0x8f, 0x91, 0x45, 0x79, 0xe6, 0xc5, 0x32, 0xd7, 0xfc, 0x09, 0x7f, 0x5b, 0x02, 0x4b, 0xb9, 0xff, 0x85, 0xcc, 0x24, 0xa3, 0x32, 0xc9, 0xa8, 0x4c, 0x32, 0x2a, 0x93, 0x8c, 0xca, 0x24, 0xa3, 0x32, 0xc9, 0xa8, 0x4c, 0x32, 0x2a, 0x93, 0x8c, 0xca, 0x24, 0xa3, 0x32, 0xc9, 0xa8, 0x20, 0x19, 0x15, 0x34, 0x7f, 0x2f, 0xbd, 0xe6, 0x5f, 0xa4, 0x33, 0x92, 0x71, 0x8b, 0x4c, 0x90, 0xee, 0xa8, 0xfa, 0x90, 0x24, 0x33, 0xc9, + 0xae, 0xcc, 0xa8, 0x64, 0xe9, 0x89, 0xa2, 0xa7, 0x21, 0xcb, 0x32, 0xc9, 0xb2, 0x4c, 0xb2, 0x2c, 0x93, 0x2c, 0xcb, 0x24, 0xcb, 0x32, 0x71, 0x01, 0x81, 0xa8, 0xd3, 0x3c, 0xb7, 0x50, 0xba, 0xa2, 0x5b, 0x49, 0x77, 0x74, 0x6b, 0x68, 0x2b, 0x83, 0xd1, 0x15, 0xd2, 0x4d, 0xa5, 0x0c, 0x50, 0x29, 0x03, 0x54, 0xca, 0x00, 0x95, 0x32, 0x40, 0x85, 0x0c, 0x50, 0x21, 0x03, 0x54, 0xc8, 0x00, 0x15, 0x32, 0x40, 0x85, 0x0c, 0x50, 0x21, 0x03, 0x54, 0xc8, 0x00, 0x15, 0x32, 0x40, 0xc5, 0x0b, 0x50, 0xf1, 0x02, 0x54, 0xbc, 0x00, 0x15, 0x2f, 0x40, 0xc5, 0x0b, 0x50, 0xf1, 0x02, 0x54, 0xbc, 0x00, 0x15, 0x2f, 0x40, 0x95, 0x0b, 0x50, 0xe5, 0x02, 0x54, 0xb9, 0x00, 0x55, 0x2e, 0x40, 0x95, 0x0b, 0x50, 0xe5, 0x02, 0x54, 0xb9, 0x00, 0x55, 0x2e, 0x40, 0x95, 0x0b, 0x50, 0xe5, 0x02, 0x54, 0xb9, 0x00, + 0x55, 0x2e, 0x40, 0x95, 0x0b, 0x50, 0xe5, 0x02, 0x54, 0xb9, 0x00, 0x55, 0x2e, 0x40, 0x95, 0x0b, 0x50, 0xe5, 0x02, 0x54, 0xb9, 0x00, 0x55, 0x2c, 0xa0, 0x3c, 0x87, 0xd6, 0x4f, 0x87, 0x04, 0x78, 0x1e, 0x5e, 0x80, 0x19, 0xf0, 0xb1, 0x3c, 0xa9, 0x7c, 0x02, 0x4b, 0x60, 0x29, 0x2c, 0x83, 0x4f, 0xe1, 0x33, 0xf8, 0x1c, 0xbe, 0x80, 0xaf, 0x60, 0x39, 0xac, 0x80, 0x95, 0xb0, 0x0a, 0xbe, 0x86, 0xd5, 0xb0, 0x06, 0xd6, 0xc2, 0x37, 0xb0, 0x0e, 0xd6, 0xc3, 0xb7, 0xf0, 0x1d, 0x7c, 0x0f, 0x3f, 0xc0, 0x06, 0xd8, 0x08, 0xbf, 0xc0, 0xaf, 0xb0, 0x09, 0x36, 0xc3, 0x56, 0xe9, 0x56, 0xb6, 0xc1, 0x6f, 0xb0, 0x1d, 0x76, 0xc0, 0x4e, 0x48, 0x86, 0x3d, 0x70, 0x08, 0x0e, 0x43, 0x2a, 0x1c, 0x81, 0xa3, 0xe0, 0x80, 0x63, 0x70, 0x1c, 0x32, 0xe1, 0x04, 0x9c, 0x84, 0x2c, 0xc8, 0x86, 0x1c, 0x38, 0x05, + 0xcc, 0x93, 0x92, 0x0b, 0x4e, 0x70, 0x41, 0x3e, 0x9c, 0x01, 0xe2, 0x4f, 0x29, 0x00, 0x2f, 0x14, 0xca, 0x4c, 0x45, 0xff, 0x66, 0x46, 0x09, 0xf8, 0xc0, 0x0f, 0x1a, 0x94, 0x42, 0x19, 0x04, 0x20, 0x08, 0x67, 0xa1, 0x1c, 0x2a, 0xa0, 0x12, 0x42, 0x70, 0x0e, 0xce, 0xc3, 0x05, 0x99, 0xa9, 0x46, 0x4b, 0x8f, 0x6a, 0x01, 0x05, 0x54, 0x88, 0x85, 0x9a, 0x50, 0x0b, 0x6a, 0x4b, 0xb7, 0x5a, 0x07, 0xea, 0x42, 0x3d, 0x20, 0xd6, 0xd4, 0x06, 0x70, 0x15, 0x7f, 0x6b, 0x04, 0x8d, 0x21, 0x0e, 0x9a, 0x40, 0x53, 0x59, 0xa1, 0x36, 0x63, 0xd9, 0x1c, 0x5a, 0x40, 0x4b, 0x68, 0x03, 0x6d, 0xa1, 0x1d, 0x74, 0x80, 0x8e, 0xd0, 0x19, 0xba, 0xca, 0x0b, 0x2a, 0x9a, 0xa4, 0xa2, 0x49, 0x2a, 0x9a, 0xa4, 0xa2, 0x49, 0x6a, 0x4f, 0xe8, 0x05, 0xe8, 0x92, 0x8a, 0x2e, 0xa9, 0x7d, 0xe1, 0x66, 0xb8, 0x05, 0x6e, + 0x85, 0x7e, 0xd0, 0x1f, 0x06, 0xc0, 0xed, 0x30, 0x10, 0x06, 0xc1, 0x5d, 0x70, 0x37, 0x0c, 0x86, 0x21, 0x70, 0x0f, 0x0c, 0x85, 0x61, 0x70, 0x2f, 0x0c, 0x87, 0x11, 0x30, 0x12, 0x46, 0xc1, 0xfd, 0x30, 0x1a, 0xc6, 0xc0, 0x58, 0x78, 0x00, 0xd0, 0x1f, 0x15, 0xfd, 0x51, 0xd1, 0x1f, 0xf5, 0x61, 0x98, 0x04, 0x93, 0xe1, 0x11, 0x78, 0x14, 0x1e, 0x03, 0xf4, 0x47, 0x45, 0x7f, 0x54, 0xf4, 0x47, 0x45, 0x7f, 0x54, 0xf4, 0x47, 0x45, 0x7f, 0x54, 0xf4, 0x47, 0x25, 0xe7, 0x55, 0x72, 0x5e, 0x45, 0x6f, 0x54, 0xf2, 0x5e, 0x7d, 0x19, 0x5e, 0x87, 0x39, 0x30, 0x17, 0xde, 0x80, 0x79, 0x30, 0x1f, 0xde, 0x04, 0xb4, 0x45, 0x5d, 0x08, 0x1f, 0xc9, 0x20, 0xd5, 0x2a, 0x48, 0xb5, 0x0a, 0x52, 0xad, 0x82, 0x54, 0xab, 0x20, 0xd5, 0x2a, 0x48, 0xb5, 0x0a, 0x52, 0xad, 0x82, 0x54, 0xab, 0x20, 0xd5, 0x2a, + 0x48, 0xb5, 0x0a, 0x52, 0xad, 0x82, 0x54, 0xab, 0x20, 0xd5, 0x2a, 0x48, 0xb5, 0x0a, 0x52, 0xad, 0x82, 0x54, 0xab, 0x20, 0xd5, 0x2a, 0x48, 0xb5, 0x0a, 0x52, 0xad, 0x82, 0x54, 0xab, 0x20, 0xd5, 0x2a, 0x48, 0xb5, 0x0a, 0x52, 0xad, 0x82, 0x54, 0xab, 0x20, 0xd5, 0x2a, 0x48, 0xb5, 0x0a, 0x52, 0xad, 0x82, 0x54, 0xab, 0x20, 0xd5, 0x2a, 0x48, 0xb5, 0x0a, 0x52, 0xad, 0x82, 0x54, 0xab, 0x20, 0xd5, 0x2a, 0x48, 0xb5, 0x0a, 0x52, 0xad, 0x82, 0x54, 0xab, 0x20, 0xd5, 0x2a, 0x48, 0xb5, 0x0a, 0xaa, 0xbf, 0x49, 0x97, 0x9a, 0x04, 0xdb, 0x61, 0x07, 0xec, 0x84, 0x64, 0xd8, 0x05, 0xbb, 0x61, 0x0f, 0xec, 0x85, 0x7d, 0x60, 0x83, 0xfd, 0x70, 0x00, 0xec, 0x70, 0x10, 0x0e, 0xc1, 0x61, 0x48, 0x85, 0x23, 0xe0, 0x80, 0x63, 0x70, 0x5c, 0xe6, 0xaa, 0x69, 0x90, 0xc1, 0xf1, 0x93, 0x03, 0x2a, 0x39, + 0xa0, 0x92, 0x03, 0x2a, 0x39, 0xa0, 0x92, 0x03, 0x2a, 0x39, 0x50, 0xed, 0x2a, 0xcb, 0xb9, 0xaa, 0x0b, 0xf2, 0xe1, 0x0c, 0x14, 0x80, 0x17, 0x0a, 0xa1, 0x08, 0x8a, 0xa1, 0x04, 0x4a, 0x65, 0x1e, 0x15, 0x35, 0x8f, 0x8a, 0x9a, 0x47, 0x45, 0xcd, 0xa3, 0xa2, 0xe6, 0x51, 0x51, 0xf3, 0xac, 0xe9, 0xd2, 0x8d, 0xeb, 0x0c, 0xe0, 0x3a, 0x03, 0xb8, 0xce, 0x00, 0xae, 0x33, 0x80, 0xeb, 0xd4, 0xaf, 0xf9, 0xe2, 0xb6, 0x06, 0x20, 0x08, 0x67, 0xa1, 0x1c, 0x2a, 0xa4, 0xcb, 0x5a, 0x09, 0x21, 0x38, 0x07, 0xe7, 0xa9, 0x03, 0xd1, 0xd5, 0x3e, 0x85, 0xbf, 0x2f, 0xf2, 0x03, 0x79, 0x24, 0x7c, 0x35, 0x86, 0x43, 0x97, 0xae, 0x84, 0xa1, 0x5f, 0x63, 0x63, 0xbe, 0x71, 0x1d, 0x0d, 0xfd, 0x91, 0x3b, 0x78, 0xe4, 0x01, 0x1e, 0x79, 0x82, 0x47, 0x1e, 0x33, 0x1e, 0x19, 0xcb, 0x23, 0xd3, 0x78, 0xe4, 0x15, + 0x1e, 0xd1, 0xaf, 0xfb, 0x36, 0x48, 0xae, 0x35, 0xfe, 0xdf, 0x6b, 0xfc, 0xff, 0xa3, 0xf1, 0xff, 0x77, 0xc6, 0xff, 0x1b, 0x8c, 0xff, 0x53, 0x8c, 0xff, 0x7f, 0xc6, 0x93, 0x0e, 0x94, 0xc7, 0xb9, 0xb5, 0xc9, 0xb8, 0xbf, 0x8e, 0x75, 0x0d, 0x34, 0xae, 0x63, 0x77, 0x3a, 0xaa, 0x9d, 0xf4, 0x47, 0x75, 0xa2, 0x83, 0x8d, 0xa1, 0x3f, 0x4e, 0xa4, 0x3f, 0x4e, 0xa4, 0x3f, 0x1e, 0x2e, 0xea, 0xc9, 0x17, 0xa8, 0xfe, 0xc5, 0x54, 0xff, 0x62, 0xaa, 0x7f, 0x31, 0x95, 0xdf, 0x41, 0xe5, 0x77, 0x50, 0xf9, 0x1d, 0x54, 0x7e, 0x07, 0x95, 0xdf, 0x41, 0xe5, 0x77, 0x88, 0x9e, 0x6c, 0xf9, 0x46, 0xb8, 0x09, 0x7a, 0x71, 0x1c, 0xbd, 0x79, 0xac, 0x0f, 0xd0, 0x4b, 0x51, 0x89, 0xd3, 0xa9, 0xa4, 0x0e, 0x2a, 0xa9, 0x83, 0x4a, 0xea, 0x10, 0x09, 0x3c, 0xe7, 0x05, 0x7c, 0xc9, 0x0c, 0x98, 0x09, 0xb3, 0xa0, + 0xea, 0xd7, 0x59, 0xde, 0x17, 0x15, 0xf2, 0x7d, 0xe3, 0x1b, 0x24, 0xf5, 0xe5, 0x26, 0x53, 0x43, 0x96, 0x8d, 0xe4, 0x7a, 0x53, 0x63, 0xa0, 0x8f, 0x31, 0xd1, 0xbb, 0x98, 0xda, 0xf3, 0x58, 0x07, 0x96, 0xb8, 0x11, 0xfc, 0xe8, 0x75, 0x78, 0x99, 0xeb, 0xf0, 0x32, 0xd7, 0xe1, 0x3f, 0x3b, 0xe2, 0x3f, 0x3b, 0x9a, 0xfa, 0xcb, 0xdd, 0xa6, 0x01, 0x30, 0x50, 0x7e, 0x61, 0xba, 0x0b, 0x1f, 0x3a, 0x18, 0xc6, 0xe3, 0x7c, 0xdc, 0xf4, 0xd6, 0xd3, 0x81, 0x6d, 0x9b, 0xe9, 0x33, 0xcc, 0xf4, 0x19, 0xe6, 0x05, 0xdc, 0x4e, 0xc4, 0x7b, 0x2e, 0x62, 0xf9, 0x33, 0xbd, 0xae, 0x5d, 0xbe, 0x4f, 0xaf, 0xfb, 0x11, 0xbd, 0xee, 0x47, 0x11, 0x59, 0xdc, 0x2e, 0xa1, 0xf7, 0x0d, 0xc9, 0xf5, 0x91, 0x42, 0x6a, 0x91, 0x66, 0xfa, 0x5e, 0x0b, 0x58, 0xb9, 0x4f, 0xcf, 0x1e, 0x39, 0x1c, 0x46, 0x02, 0xc7, 0x16, + 0x95, 0x2c, 0xba, 0x51, 0x01, 0x1d, 0x51, 0x67, 0xe4, 0xde, 0xa8, 0x22, 0x19, 0x8c, 0xa2, 0xff, 0xa0, 0xfa, 0x39, 0xe8, 0x85, 0x13, 0xe9, 0x85, 0x13, 0xe9, 0x85, 0x13, 0xe9, 0x85, 0x13, 0xe9, 0x85, 0x13, 0xe9, 0x85, 0x13, 0xe9, 0x85, 0x13, 0xe9, 0x85, 0x13, 0xe9, 0x85, 0x13, 0xe9, 0x81, 0x3f, 0xa4, 0x07, 0x4e, 0xa4, 0x07, 0x4e, 0xa4, 0x07, 0x4e, 0xa4, 0x07, 0x4e, 0xa4, 0x07, 0x4e, 0xa4, 0x07, 0x4e, 0xa4, 0x07, 0x4e, 0xa4, 0x07, 0x4e, 0xa4, 0x07, 0x4e, 0xa4, 0x07, 0x4e, 0xa4, 0x07, 0x4e, 0xa4, 0x07, 0x4e, 0xa4, 0x07, 0x4e, 0xa4, 0x07, 0x4e, 0xa4, 0x07, 0x4e, 0xa4, 0x07, 0x4e, 0xa4, 0x07, 0x4e, 0xa4, 0x07, 0x4e, 0xa4, 0x5f, 0xfd, 0x90, 0x7e, 0xf5, 0x43, 0xfa, 0xd5, 0x44, 0xfa, 0xd5, 0xe1, 0xf4, 0xab, 0xc3, 0xe9, 0x57, 0x47, 0x5a, 0x0a, 0xe4, 0x0b, 0x16, 0x2f, 0x14, + 0xca, 0x17, 0xa8, 0x6c, 0xc5, 0x54, 0xb6, 0x62, 0x2a, 0x5b, 0x31, 0x95, 0xad, 0x98, 0xca, 0x56, 0x4c, 0x65, 0x2b, 0xa6, 0xb2, 0x15, 0x53, 0xd9, 0x8a, 0xa9, 0x6c, 0xc5, 0xca, 0x97, 0xf0, 0x15, 0x2c, 0x87, 0x15, 0xb0, 0x52, 0xd4, 0xa3, 0xba, 0x15, 0x53, 0xdd, 0x8a, 0xa9, 0x6e, 0xc5, 0x54, 0xb7, 0x62, 0xaa, 0x5b, 0x31, 0xd5, 0xad, 0x98, 0xea, 0x56, 0x4c, 0x75, 0x2b, 0xa6, 0xba, 0x15, 0x53, 0xdd, 0x8a, 0xa9, 0x6e, 0xc5, 0x54, 0xb7, 0x62, 0xaa, 0x5b, 0xb1, 0xf2, 0x13, 0xfc, 0x0c, 0x1b, 0xe1, 0x17, 0xf8, 0x15, 0x36, 0xc1, 0x66, 0xd8, 0x02, 0xdb, 0xe9, 0x23, 0x77, 0xc0, 0x4e, 0x48, 0x86, 0x5d, 0xb0, 0x07, 0xf6, 0xca, 0xbd, 0xca, 0x3e, 0xb0, 0xc1, 0x7e, 0xb0, 0xf3, 0xd8, 0x41, 0x96, 0x87, 0x58, 0x1e, 0x86, 0x54, 0x38, 0x02, 0x47, 0xc1, 0x01, 0xc7, 0xe0, 0x38, 0xa4, 0x41, + 0x06, 0x64, 0xc2, 0x09, 0x38, 0x09, 0x59, 0x90, 0xcd, 0xb6, 0x72, 0x58, 0x9e, 0x02, 0xe6, 0x89, 0x0a, 0xe8, 0xa0, 0x02, 0x3a, 0xa8, 0x80, 0x0e, 0x2a, 0xa0, 0x83, 0x0a, 0xe8, 0xa0, 0x02, 0x3a, 0x14, 0x0f, 0xd0, 0x4b, 0x52, 0x05, 0x1d, 0x8a, 0x94, 0x9a, 0xca, 0xbc, 0xab, 0x26, 0x30, 0x43, 0x04, 0x44, 0x42, 0x14, 0x44, 0xcb, 0x74, 0x2a, 0x5b, 0x3a, 0x95, 0x2d, 0x9d, 0xca, 0x96, 0xae, 0x5a, 0xa5, 0x83, 0xea, 0x96, 0x4e, 0x75, 0x4b, 0xa7, 0xba, 0xa5, 0xab, 0x71, 0xa2, 0xb9, 0xda, 0x54, 0x58, 0xd5, 0x96, 0xc6, 0x37, 0x8d, 0x15, 0xb5, 0xad, 0xe8, 0xa4, 0xb6, 0x13, 0x57, 0xa9, 0x1d, 0xc5, 0x2c, 0xb5, 0xab, 0xa8, 0x49, 0xd5, 0x70, 0x50, 0x35, 0x1c, 0x54, 0x0d, 0x07, 0x55, 0xc3, 0x41, 0xd5, 0x70, 0x50, 0x35, 0x1c, 0x54, 0x0d, 0x07, 0x55, 0xc3, 0xa1, 0x3e, 0x04, 0xe3, 0x61, + 0x02, 0x4c, 0x84, 0x87, 0x65, 0x3e, 0x95, 0xc3, 0x41, 0xe5, 0x70, 0x50, 0x39, 0x1c, 0x54, 0x0e, 0x07, 0x95, 0xc3, 0x41, 0xe5, 0x70, 0x50, 0x39, 0x1c, 0x54, 0x0e, 0x07, 0x95, 0xc3, 0x41, 0xe5, 0x70, 0x50, 0x39, 0x1c, 0x54, 0x0e, 0x87, 0x3a, 0x03, 0x66, 0xc2, 0x2c, 0x78, 0x11, 0x16, 0x8a, 0x76, 0xea, 0xdb, 0xf4, 0x03, 0xf4, 0x74, 0xea, 0xbb, 0xf0, 0x1e, 0xbc, 0x0f, 0xc4, 0xbf, 0xfa, 0x01, 0x7c, 0x08, 0x8b, 0x60, 0xb1, 0x2c, 0xb3, 0xee, 0x95, 0x41, 0xeb, 0x51, 0xc8, 0x32, 0xde, 0xe7, 0x76, 0xa0, 0x68, 0x0e, 0x14, 0xcd, 0x81, 0xa2, 0x39, 0x50, 0x34, 0x47, 0xcc, 0x56, 0xfa, 0xea, 0x6d, 0x2c, 0x99, 0x9b, 0xff, 0x5f, 0xa9, 0xc5, 0x35, 0xa8, 0x45, 0x7d, 0xd4, 0xa2, 0x3e, 0x6a, 0xd1, 0x0e, 0xb5, 0x68, 0xf7, 0xb7, 0x5a, 0xfc, 0xad, 0x16, 0x7f, 0xab, 0xc5, 0x7f, 0x4e, 0x2d, + 0xfe, 0xc5, 0xb5, 0x44, 0xfb, 0x8b, 0x2e, 0xa2, 0x91, 0x18, 0x0f, 0x13, 0x60, 0x22, 0x4c, 0x81, 0x27, 0xe0, 0x49, 0x98, 0x0e, 0x2f, 0xc2, 0xbb, 0xa2, 0x8e, 0xa9, 0xbb, 0x7e, 0x35, 0x62, 0x11, 0x6b, 0x8a, 0x17, 0xdd, 0x4c, 0x7d, 0x44, 0x3d, 0x53, 0x5f, 0xd1, 0xdd, 0x74, 0x33, 0xcb, 0x5b, 0x58, 0xde, 0xc1, 0xe3, 0x83, 0x8c, 0xeb, 0xa7, 0x0d, 0xc2, 0x1b, 0x0d, 0xd1, 0xaf, 0x60, 0x6c, 0x1a, 0x25, 0x54, 0xd3, 0x83, 0xa2, 0x87, 0xe9, 0x21, 0x98, 0x2c, 0x46, 0x5e, 0x76, 0x85, 0xdc, 0x46, 0xe6, 0x67, 0x61, 0x16, 0x7c, 0x02, 0x4b, 0x44, 0xa3, 0xc8, 0x04, 0xd1, 0x28, 0xaa, 0x3e, 0x74, 0x17, 0xfa, 0xaf, 0xa2, 0x76, 0x8f, 0x2a, 0x13, 0x35, 0xa3, 0x5b, 0x8b, 0x46, 0xca, 0x56, 0xd8, 0x06, 0xbf, 0x89, 0x46, 0x6a, 0x6d, 0xa8, 0x03, 0x75, 0xa1, 0x1e, 0xf0, 0x7c, 0xb5, 0x01, 0x34, + 0x17, 0xb1, 0x97, 0xae, 0xa1, 0x7a, 0x03, 0xf7, 0xbb, 0x43, 0x0f, 0x88, 0x87, 0x9e, 0xd0, 0x0b, 0x7a, 0x43, 0x1f, 0xe8, 0x0b, 0x37, 0xc3, 0x2d, 0x70, 0x2b, 0xf4, 0x83, 0xfe, 0x1c, 0xe3, 0xdf, 0x57, 0x27, 0xff, 0xfb, 0xea, 0xe4, 0x7f, 0x5f, 0x9d, 0xfc, 0x0a, 0x57, 0x27, 0x37, 0x7d, 0x80, 0xd7, 0x70, 0xe1, 0x35, 0x5c, 0xf8, 0x8c, 0x4a, 0xd1, 0x4e, 0xe6, 0x8b, 0xeb, 0x64, 0x50, 0x5c, 0xcf, 0xed, 0xf6, 0xd2, 0x89, 0xef, 0xf0, 0xe1, 0x3b, 0x7c, 0xf8, 0x0e, 0x9f, 0xe8, 0x22, 0x0b, 0xc9, 0x8c, 0x14, 0x32, 0x23, 0x85, 0xcc, 0x48, 0x21, 0x33, 0x52, 0xc8, 0x8c, 0x14, 0x32, 0x23, 0x85, 0xcc, 0x48, 0x21, 0x33, 0x52, 0xc8, 0x8c, 0x14, 0x32, 0x23, 0x85, 0xcc, 0x48, 0x21, 0x33, 0x52, 0x84, 0xde, 0x23, 0xdd, 0x05, 0x77, 0xc3, 0x60, 0x18, 0x02, 0xf7, 0xc0, 0x50, 0x18, 0x06, 0xf7, + 0xc2, 0x7d, 0x30, 0x1c, 0x46, 0x80, 0x7e, 0x5d, 0xe3, 0x51, 0x70, 0x3f, 0x8c, 0x86, 0x31, 0x30, 0x16, 0x1e, 0x80, 0x71, 0xf0, 0x20, 0xfb, 0x39, 0x9e, 0xfd, 0x98, 0x00, 0x13, 0xe1, 0x61, 0xf6, 0x6b, 0x12, 0x4c, 0x86, 0x47, 0xe1, 0x31, 0x98, 0xc2, 0xe3, 0x8f, 0xcb, 0x22, 0xf1, 0x04, 0xcb, 0x27, 0xe1, 0x29, 0x6e, 0x4f, 0x85, 0x69, 0xf0, 0x1c, 0x4c, 0xe7, 0xb1, 0xe7, 0x59, 0xbe, 0x20, 0xdd, 0xf8, 0x1f, 0x37, 0xfe, 0xc7, 0x8d, 0xff, 0xd1, 0xcf, 0x3f, 0x16, 0x92, 0x65, 0x29, 0x62, 0xb6, 0xb4, 0x89, 0x97, 0xe1, 0x15, 0x78, 0x15, 0x5e, 0x83, 0xd7, 0x61, 0x0e, 0xcc, 0x85, 0x37, 0x60, 0x1e, 0xcc, 0xc7, 0x8b, 0xbd, 0x69, 0x5c, 0xd9, 0x78, 0x57, 0xf8, 0x1b, 0x24, 0x9b, 0xc9, 0xcc, 0x24, 0xf1, 0x8e, 0x3c, 0x6c, 0xf2, 0xa0, 0x83, 0x63, 0xa5, 0xd3, 0x3c, 0x05, 0x9e, 0x91, 0x85, + 0xe6, 0x67, 0x61, 0xa6, 0x2c, 0x32, 0xcf, 0x62, 0x39, 0x9b, 0x25, 0xeb, 0x36, 0xb3, 0x6e, 0xf3, 0xeb, 0xf2, 0x30, 0x3e, 0xc8, 0x65, 0x5e, 0xc8, 0x32, 0x51, 0xba, 0xc9, 0xd2, 0x24, 0xf3, 0x27, 0x3c, 0x67, 0x09, 0x2c, 0xe5, 0xb1, 0x2f, 0x64, 0x0a, 0x19, 0x9b, 0x42, 0xc6, 0xa6, 0x90, 0xb1, 0x29, 0x64, 0x6c, 0x0a, 0x19, 0x9b, 0x42, 0xc6, 0xa6, 0x90, 0xb1, 0x29, 0x64, 0x6c, 0x0a, 0x19, 0x9b, 0x62, 0x5e, 0xcb, 0xba, 0xbe, 0x91, 0xb6, 0xc8, 0x04, 0x59, 0x18, 0x55, 0x1f, 0x92, 0x64, 0x0a, 0xd9, 0x9b, 0x12, 0x95, 0x6c, 0x9c, 0x5f, 0x4c, 0x21, 0x83, 0x53, 0xc8, 0xe0, 0x14, 0x32, 0x38, 0x85, 0x0c, 0x4e, 0x89, 0x2a, 0x94, 0xc5, 0xd1, 0xad, 0x64, 0x51, 0x74, 0x6b, 0x59, 0x18, 0xdd, 0x56, 0xda, 0xf0, 0x49, 0x2e, 0x7c, 0x92, 0x0b, 0x9f, 0xe4, 0xc2, 0x27, 0xb9, 0xf0, 0x49, 0x2e, + 0x7c, 0x92, 0x0b, 0x9f, 0xe4, 0xc2, 0x27, 0xb9, 0xf0, 0x49, 0x2e, 0x3c, 0x92, 0x0b, 0x8f, 0xe4, 0xc2, 0x23, 0xb9, 0xf0, 0x48, 0x2e, 0x3c, 0x92, 0x0b, 0x8f, 0xe4, 0xc2, 0x23, 0xb9, 0xf0, 0x48, 0x2e, 0x3c, 0x92, 0x0b, 0x8f, 0xe4, 0xc2, 0x23, 0xb9, 0xf0, 0x48, 0x2e, 0x3c, 0x92, 0x0b, 0x8f, 0xe4, 0xc2, 0x23, 0xb9, 0xf0, 0x48, 0x2e, 0x3c, 0x92, 0x0b, 0x8f, 0xe4, 0xc2, 0x1b, 0xb9, 0xf0, 0x43, 0x95, 0x78, 0xa1, 0x4a, 0xe5, 0x19, 0x99, 0xaf, 0x3c, 0x0b, 0xcf, 0xc9, 0xa0, 0x32, 0x1d, 0x12, 0xe0, 0x79, 0x78, 0x01, 0x66, 0xc0, 0x8b, 0x3c, 0xe7, 0x25, 0x98, 0x2d, 0x9d, 0xca, 0xcb, 0xf0, 0x0a, 0xbc, 0x0a, 0x73, 0xe1, 0x0d, 0x98, 0x07, 0xf3, 0xe1, 0x4d, 0xf8, 0x58, 0xfa, 0xf0, 0x55, 0x3e, 0x7c, 0x95, 0x0f, 0x5f, 0xe5, 0xc3, 0x57, 0xf9, 0xf0, 0x55, 0x3e, 0x7c, 0x95, 0x0f, 0x5f, + 0xe5, 0xc3, 0x57, 0xf9, 0xf0, 0x54, 0x3e, 0x3c, 0x95, 0x0f, 0x4f, 0xe5, 0x53, 0x56, 0xc2, 0x2a, 0xf8, 0x1a, 0x56, 0xc3, 0x1a, 0x58, 0x0b, 0xdf, 0xc0, 0x3a, 0x58, 0x0f, 0xdf, 0xc2, 0x77, 0xf0, 0x3d, 0xfc, 0x00, 0x1b, 0x60, 0x23, 0xfc, 0x02, 0xbf, 0xc2, 0x26, 0xd8, 0x0c, 0x5b, 0x65, 0xa1, 0xb2, 0x0d, 0x7e, 0x83, 0x42, 0x99, 0x82, 0x12, 0xa5, 0xa0, 0x44, 0x29, 0x28, 0x51, 0x0a, 0x4a, 0x94, 0x82, 0x12, 0xa5, 0xa0, 0x44, 0x29, 0x28, 0x51, 0x0a, 0x4a, 0x94, 0x82, 0x12, 0xa5, 0xa0, 0x44, 0x29, 0x28, 0x51, 0x0a, 0x4a, 0x94, 0x82, 0x12, 0xa5, 0xa0, 0x44, 0x29, 0x28, 0x51, 0x0a, 0x4a, 0x94, 0x82, 0x12, 0xa5, 0xe0, 0x7f, 0x5c, 0xf8, 0x1f, 0x17, 0xfe, 0xc7, 0x85, 0xff, 0x71, 0xe1, 0x7f, 0x5c, 0xf8, 0x1f, 0x17, 0xfe, 0xc7, 0xa5, 0xd6, 0x96, 0x85, 0x6a, 0x1d, 0xa8, 0x0b, 0xf5, + 0x80, 0x39, 0x57, 0x1b, 0xc0, 0x55, 0xd2, 0xa7, 0x36, 0x82, 0xc6, 0x10, 0x07, 0x4d, 0xa0, 0x29, 0x34, 0x83, 0xe6, 0xd0, 0x02, 0x5a, 0x42, 0x1b, 0x68, 0x0b, 0xed, 0xa0, 0x03, 0x90, 0xe7, 0x2a, 0x39, 0xae, 0x76, 0x85, 0x1b, 0x58, 0x4f, 0x77, 0xe8, 0x01, 0x54, 0x19, 0xb5, 0x27, 0xf4, 0x82, 0xde, 0xd0, 0x07, 0xfa, 0xc2, 0xcd, 0x70, 0x0b, 0xdc, 0x0a, 0xfd, 0xa0, 0x3f, 0x0c, 0x90, 0x45, 0xea, 0xed, 0x30, 0x10, 0x06, 0xc1, 0x5d, 0x70, 0x37, 0x0c, 0x86, 0x21, 0x70, 0x0f, 0x0c, 0x85, 0x61, 0x70, 0x2f, 0x0c, 0x07, 0x72, 0x41, 0x7d, 0x19, 0x5e, 0x87, 0x39, 0x30, 0x17, 0xde, 0x80, 0x79, 0x30, 0x1f, 0xde, 0x84, 0xb7, 0x60, 0x21, 0xbc, 0x2d, 0xdd, 0x78, 0x27, 0x37, 0xde, 0xc9, 0x8d, 0x77, 0x72, 0xe3, 0x9b, 0xdc, 0xf8, 0x26, 0x37, 0xbe, 0xc9, 0x8d, 0x6f, 0x72, 0xe3, 0x9b, 0xf4, + 0x73, 0x74, 0x36, 0xf5, 0x63, 0xf8, 0x04, 0x96, 0xc0, 0x52, 0x58, 0x06, 0x9f, 0xc2, 0x67, 0xf0, 0x39, 0x7c, 0x01, 0x5f, 0xc2, 0x57, 0xb0, 0x1c, 0x56, 0xc0, 0x4a, 0x58, 0x05, 0x5f, 0xc3, 0x6a, 0x58, 0x03, 0xe4, 0x94, 0x4a, 0x4e, 0xa9, 0xeb, 0x60, 0x3d, 0x7c, 0x0b, 0xdf, 0xc1, 0xf7, 0xf0, 0x03, 0x6c, 0x80, 0x1f, 0xe1, 0x27, 0xf8, 0x19, 0x36, 0xc2, 0x2f, 0xf0, 0x2b, 0x6c, 0x82, 0xcd, 0xb0, 0x05, 0xb6, 0xc2, 0x36, 0xf8, 0x0d, 0x77, 0x96, 0x04, 0xdb, 0x61, 0x07, 0xec, 0x84, 0x64, 0xd8, 0x05, 0xbb, 0x61, 0x0f, 0xec, 0x85, 0x7d, 0x60, 0x83, 0xfd, 0x70, 0x00, 0xec, 0x70, 0x10, 0x0e, 0xc1, 0x61, 0x48, 0x85, 0x23, 0xe0, 0x80, 0x63, 0xc6, 0xd5, 0xbe, 0x77, 0x51, 0x29, 0x76, 0xfd, 0x17, 0x9f, 0xe8, 0xde, 0xac, 0xe6, 0x82, 0x53, 0x26, 0x51, 0x31, 0x92, 0xa8, 0x18, 0x49, 0x54, + 0x8c, 0x24, 0x2a, 0x46, 0x12, 0x15, 0x23, 0x89, 0x8a, 0x91, 0x44, 0xc5, 0x48, 0xa2, 0x62, 0x24, 0x51, 0x31, 0x92, 0xd4, 0x52, 0x79, 0x58, 0x2d, 0x83, 0x00, 0x04, 0xe1, 0x2c, 0x94, 0xcb, 0xc3, 0xd6, 0x74, 0x59, 0x64, 0xad, 0x90, 0xc5, 0xd6, 0x4a, 0x08, 0xc1, 0x39, 0x38, 0x8f, 0x06, 0xf6, 0xc4, 0x69, 0xc6, 0xe1, 0x34, 0xe3, 0x70, 0x9a, 0x71, 0x38, 0xcd, 0x19, 0x38, 0xcd, 0x38, 0x9c, 0xa6, 0x09, 0xa7, 0x19, 0x87, 0xd3, 0x8c, 0xc3, 0x69, 0xc6, 0x5d, 0xe6, 0x12, 0xe3, 0x70, 0x89, 0x71, 0xb8, 0xc4, 0x38, 0x5c, 0x62, 0x1c, 0x2e, 0x31, 0x0e, 0x97, 0x18, 0x87, 0x4b, 0x8c, 0xc3, 0x19, 0xc6, 0x29, 0x4b, 0x44, 0x0d, 0xdc, 0x61, 0x1c, 0xee, 0x30, 0x0e, 0x77, 0x18, 0x87, 0x3b, 0x8c, 0xc3, 0x1d, 0xc6, 0xe1, 0x0e, 0xe3, 0x70, 0x87, 0x71, 0xb8, 0xc3, 0x38, 0xdc, 0x61, 0x9c, 0xda, + 0x04, 0x37, 0xdf, 0x54, 0x6c, 0x50, 0x9b, 0x89, 0x37, 0x71, 0x8a, 0x13, 0xd4, 0x16, 0x62, 0x0a, 0xae, 0xff, 0xfa, 0x4b, 0x8e, 0xb1, 0xab, 0xf8, 0x05, 0xd7, 0x18, 0x87, 0x6b, 0x8c, 0xc3, 0x35, 0xc6, 0xe1, 0x1a, 0xe3, 0x70, 0x8d, 0x71, 0xb8, 0xc6, 0x38, 0x5c, 0x63, 0x1c, 0xae, 0x31, 0x0e, 0xd7, 0x18, 0x87, 0x6b, 0x8c, 0xc3, 0x35, 0xc6, 0xe1, 0x1a, 0xe3, 0x70, 0x8d, 0x71, 0xb8, 0xc6, 0x38, 0xf5, 0x1d, 0x71, 0x9d, 0x35, 0x5d, 0xf4, 0x11, 0x37, 0x8b, 0x87, 0xc5, 0x5b, 0x62, 0x12, 0x4c, 0xe6, 0x88, 0x1e, 0x65, 0xf9, 0x98, 0x30, 0x99, 0x86, 0xca, 0xdf, 0xaa, 0x1f, 0x11, 0x3d, 0xe8, 0xd5, 0x51, 0x85, 0xa2, 0xa1, 0xf2, 0xb1, 0x98, 0xce, 0xde, 0xf7, 0x50, 0x96, 0x8a, 0xde, 0xca, 0x32, 0x31, 0x5e, 0xf9, 0x54, 0x0c, 0x52, 0x3e, 0x63, 0xf9, 0xb9, 0x68, 0x4c, 0x1f, 0x57, 0x5f, + 0xd9, 0x21, 0x1a, 0x28, 0x3b, 0xc5, 0x43, 0xca, 0x21, 0x31, 0x4c, 0x49, 0x15, 0xb5, 0x95, 0x23, 0x62, 0x8c, 0x72, 0x54, 0xf4, 0x51, 0x1c, 0xa2, 0x8d, 0x72, 0x4c, 0xf4, 0x56, 0xaf, 0x12, 0x6f, 0xa9, 0x8d, 0xa0, 0x31, 0xc4, 0x89, 0x00, 0x47, 0xd8, 0x8b, 0x23, 0x3c, 0xc2, 0x11, 0xe6, 0x72, 0x84, 0x67, 0xd4, 0x16, 0x26, 0xb3, 0xda, 0xd2, 0x54, 0x53, 0x6d, 0xc3, 0xdf, 0xdb, 0x8a, 0x7a, 0x1c, 0x69, 0x1d, 0xb5, 0x03, 0xb7, 0x3b, 0x8a, 0x27, 0xd4, 0xce, 0x2c, 0xbb, 0x8a, 0x34, 0xf5, 0x2e, 0x71, 0x9f, 0x7a, 0xb7, 0xa8, 0xa5, 0x8e, 0x10, 0x77, 0xaa, 0xa3, 0xe8, 0x7f, 0xee, 0x67, 0x94, 0x46, 0x8b, 0xee, 0xea, 0x18, 0x8e, 0x6a, 0x2c, 0xcb, 0x39, 0x8c, 0xd8, 0x42, 0x5e, 0xfb, 0x9b, 0x18, 0xa6, 0x6e, 0x17, 0xb5, 0xd5, 0x1d, 0xa2, 0x8f, 0xba, 0x53, 0xb4, 0x51, 0x93, 0xd9, 0x7e, + 0xbe, 0xa8, 0xaf, 0x9e, 0x11, 0xfd, 0x38, 0xf2, 0x23, 0xa2, 0xc6, 0x95, 0x8e, 0x52, 0x3f, 0x0a, 0xf6, 0xa8, 0xc1, 0xa5, 0xad, 0xeb, 0x5b, 0xee, 0x2a, 0x1a, 0xeb, 0x6b, 0x14, 0x91, 0xa2, 0x97, 0x0c, 0xea, 0xbf, 0xfe, 0x62, 0x9e, 0x2f, 0x6a, 0xc7, 0x0c, 0x90, 0xa9, 0xa2, 0xb9, 0xfe, 0x08, 0x23, 0xd8, 0x85, 0x11, 0xec, 0x12, 0x1e, 0xc1, 0x2e, 0xc6, 0x08, 0x86, 0x9f, 0xc5, 0x3a, 0xbb, 0x30, 0x6a, 0xd7, 0x30, 0x2a, 0xd1, 0x8c, 0x86, 0x89, 0x11, 0xe8, 0xc2, 0x08, 0x74, 0x61, 0x04, 0xba, 0x30, 0x02, 0x9f, 0x31, 0x02, 0x26, 0xb6, 0xf7, 0x04, 0x23, 0xf0, 0x2a, 0x23, 0x30, 0x87, 0x39, 0xfe, 0x8c, 0x39, 0x5e, 0xcb, 0x08, 0x74, 0x61, 0x1f, 0xba, 0xb0, 0x0f, 0x5d, 0x18, 0x81, 0x2e, 0xec, 0x47, 0x17, 0x46, 0xa0, 0x0b, 0xfb, 0x32, 0x8d, 0x23, 0x1c, 0xc3, 0x11, 0xbc, 0x68, 0xec, + 0x41, 0x9c, 0xb0, 0xe2, 0xa8, 0x6b, 0x43, 0x43, 0x59, 0x22, 0xda, 0xc8, 0x6c, 0xf6, 0xa8, 0x44, 0xe0, 0xca, 0xae, 0xf4, 0xfb, 0x0a, 0xe6, 0x47, 0xe4, 0x59, 0xf3, 0x63, 0x32, 0x60, 0xfe, 0x5e, 0x16, 0x98, 0x7f, 0x91, 0x25, 0xd5, 0x7f, 0x3b, 0x21, 0xaa, 0x29, 0xce, 0x38, 0x07, 0x4e, 0xc9, 0x80, 0x85, 0xe7, 0x58, 0x5e, 0x84, 0x97, 0x20, 0x20, 0x03, 0xca, 0x4d, 0x32, 0x10, 0xf3, 0xae, 0xcc, 0x8d, 0x79, 0x4f, 0xe6, 0xd2, 0x71, 0x54, 0x5d, 0x85, 0x2e, 0x5b, 0x1c, 0xc3, 0x49, 0xe9, 0xd7, 0x4a, 0xcb, 0x90, 0x67, 0x45, 0x26, 0xae, 0x23, 0x5b, 0xe6, 0x88, 0x1c, 0xa9, 0x89, 0x53, 0x78, 0x79, 0x97, 0x3c, 0x25, 0xdc, 0xfc, 0xad, 0x40, 0xff, 0x35, 0x24, 0x61, 0xc1, 0xd3, 0x57, 0x9a, 0xfa, 0xb2, 0x37, 0xb7, 0xc0, 0x20, 0xfd, 0x3a, 0xb0, 0x74, 0x65, 0xf4, 0x0c, 0xec, 0x55, 0x39, + 0x1d, 0x97, 0xc5, 0xf4, 0xa0, 0x94, 0xa6, 0x87, 0xa4, 0x8c, 0x58, 0x21, 0xcb, 0x23, 0xbe, 0x66, 0x5d, 0x71, 0xc2, 0x8c, 0xf7, 0xe8, 0x25, 0x77, 0x72, 0x2c, 0x87, 0xc4, 0xbb, 0xb2, 0xbb, 0x78, 0x4f, 0xde, 0x20, 0xde, 0x97, 0x37, 0xb0, 0x26, 0xbd, 0x2b, 0x78, 0xce, 0x34, 0xf4, 0x42, 0x9e, 0x69, 0x94, 0xec, 0x66, 0x9a, 0x28, 0x4f, 0x99, 0x26, 0xc1, 0x64, 0xd9, 0xd5, 0xf4, 0x08, 0xf7, 0x71, 0xc9, 0xe6, 0x3b, 0xf1, 0x1b, 0x8f, 0xc8, 0xdd, 0xcc, 0x80, 0xd5, 0xfc, 0xa6, 0x1c, 0xc6, 0xf1, 0x96, 0x73, 0xbc, 0x05, 0x91, 0x73, 0x64, 0xb7, 0xc8, 0x85, 0x72, 0x4a, 0xe4, 0x47, 0xcc, 0x76, 0x77, 0xd9, 0x33, 0xaa, 0x87, 0xec, 0x1e, 0xb5, 0x4b, 0x0e, 0xb3, 0x66, 0xcb, 0x5e, 0x7f, 0x18, 0xc9, 0x62, 0x46, 0x32, 0x8b, 0xad, 0x17, 0xb3, 0x75, 0x5f, 0xb5, 0x91, 0xcc, 0x08, 0x5f, 0x2b, + 0xfd, 0xd8, 0x65, 0x23, 0x59, 0xcc, 0x48, 0xa6, 0x85, 0xaf, 0x56, 0xfc, 0xef, 0x8c, 0xe4, 0x69, 0x46, 0xf2, 0xb4, 0x88, 0xf9, 0x6f, 0x45, 0x5f, 0xf5, 0xab, 0x74, 0xa7, 0x18, 0xd7, 0xce, 0x1d, 0x4b, 0xcf, 0x3a, 0x8e, 0x9e, 0x75, 0x9c, 0x59, 0x30, 0x5a, 0x47, 0x85, 0x85, 0x3e, 0x4a, 0x81, 0x9a, 0x70, 0x0d, 0xc4, 0x41, 0x13, 0xfd, 0x57, 0xd8, 0xa0, 0xea, 0x3b, 0xd9, 0x23, 0x45, 0x5b, 0x59, 0x26, 0xae, 0x85, 0x76, 0x72, 0x39, 0x6e, 0x38, 0x09, 0x27, 0x3c, 0x0b, 0xf7, 0x1b, 0xa2, 0x87, 0xf4, 0xd2, 0x43, 0x7a, 0xe9, 0x21, 0xbd, 0xf4, 0x90, 0x5e, 0x7a, 0x48, 0x2f, 0x3d, 0xa4, 0x97, 0x1e, 0xb2, 0x98, 0x1e, 0xb2, 0x98, 0x1e, 0xb2, 0x98, 0x1e, 0xd2, 0x4b, 0x0f, 0xe9, 0xc5, 0x29, 0x3b, 0x71, 0xca, 0x4e, 0x9c, 0xb2, 0x13, 0xa7, 0xec, 0xc4, 0x29, 0x3b, 0x71, 0xca, 0x4e, 0x9c, + 0xb2, 0x13, 0xa7, 0xec, 0xc4, 0x29, 0x3b, 0x71, 0xca, 0x4e, 0x9c, 0xb2, 0x13, 0xa7, 0xec, 0xc4, 0x29, 0x3b, 0x71, 0xca, 0x4e, 0x9c, 0xb2, 0x13, 0xa7, 0xec, 0xc4, 0x29, 0x3b, 0x71, 0xca, 0x4e, 0x9c, 0xb2, 0x13, 0xa7, 0xec, 0xc4, 0x29, 0x3b, 0x71, 0xca, 0x4e, 0x9c, 0xb2, 0x13, 0xa7, 0xec, 0xc4, 0x29, 0x3b, 0x71, 0xca, 0x4e, 0x9c, 0xb2, 0x13, 0xa7, 0xec, 0xc4, 0x29, 0x3b, 0x71, 0xca, 0x4e, 0x9c, 0xb2, 0x13, 0xa7, 0xec, 0xa4, 0x87, 0x2c, 0xc2, 0x29, 0x87, 0x70, 0xca, 0x21, 0x9c, 0x72, 0x08, 0xa7, 0x1c, 0xc2, 0x29, 0x87, 0x70, 0xca, 0x21, 0x9c, 0x72, 0x08, 0xa7, 0x1c, 0xc2, 0x29, 0x87, 0x70, 0xca, 0x21, 0x9c, 0x72, 0x08, 0xa7, 0x1c, 0xc2, 0x29, 0x87, 0x70, 0xca, 0x21, 0x9c, 0x72, 0x88, 0xfe, 0xd3, 0x4b, 0xff, 0xe9, 0xa5, 0xff, 0xf4, 0xe2, 0x9a, 0x43, 0xb8, 0xe6, 0x10, + 0x7d, 0x68, 0x31, 0xce, 0x39, 0x84, 0x53, 0x0e, 0xe1, 0x94, 0x9d, 0x38, 0x65, 0x37, 0x4e, 0xd9, 0x8d, 0x53, 0x76, 0xe3, 0x94, 0xdd, 0x38, 0x65, 0x37, 0x4e, 0xd9, 0x8d, 0x53, 0x76, 0xe3, 0x94, 0xdd, 0x38, 0x65, 0x37, 0x4e, 0xd9, 0x8d, 0x53, 0xf6, 0xe2, 0x94, 0xbd, 0x38, 0x65, 0x1b, 0x4e, 0x39, 0x17, 0x97, 0xbc, 0x87, 0x68, 0x2d, 0x20, 0x5a, 0x5d, 0x44, 0xab, 0xcf, 0xd4, 0x03, 0xfa, 0xc8, 0xbd, 0xc4, 0x7e, 0xb2, 0xe9, 0x66, 0x96, 0xb7, 0xb0, 0x1c, 0x24, 0xb7, 0x11, 0xb9, 0xfb, 0x88, 0xf9, 0x2c, 0x62, 0x3e, 0x8b, 0xc8, 0xdd, 0x49, 0xe4, 0x3a, 0x4d, 0xf9, 0xe4, 0xc3, 0x19, 0x59, 0x46, 0x8f, 0xeb, 0xc5, 0x65, 0x7b, 0xcd, 0xf4, 0xe3, 0x66, 0xfa, 0x6e, 0x33, 0x3d, 0x36, 0x8e, 0x7b, 0x96, 0x79, 0x3c, 0xcb, 0x47, 0x64, 0x19, 0xce, 0x7b, 0x96, 0xf9, 0x71, 0x96, 0xcf, 0xc8, + 0x10, 0xee, 0x3b, 0x44, 0x3f, 0xec, 0xa5, 0x1f, 0x2e, 0xc6, 0x85, 0x87, 0x70, 0xe1, 0x21, 0x5c, 0x78, 0x08, 0x17, 0xee, 0xc6, 0x85, 0xbb, 0xe9, 0x91, 0x8b, 0xe9, 0x91, 0x8b, 0x71, 0xdf, 0x7b, 0x70, 0xdf, 0x21, 0xdc, 0x77, 0x08, 0xe7, 0xed, 0xc4, 0x79, 0x3b, 0x71, 0xde, 0x4e, 0x9c, 0xb7, 0x13, 0xe7, 0xed, 0xc4, 0x79, 0x3b, 0x71, 0xde, 0x4e, 0x9c, 0xb7, 0x13, 0xe7, 0xed, 0xc4, 0x79, 0x3b, 0x71, 0xde, 0x6e, 0x9c, 0xb7, 0x9b, 0x9e, 0xb9, 0x08, 0xf7, 0x1d, 0x22, 0x7b, 0xb2, 0x71, 0xe0, 0x21, 0x32, 0xe7, 0x4b, 0x32, 0xe7, 0x4b, 0x9c, 0xb8, 0x13, 0x27, 0xee, 0xc4, 0x89, 0x87, 0x70, 0xe2, 0x4e, 0x9c, 0xb8, 0x13, 0x27, 0xee, 0xc4, 0x89, 0x3b, 0x71, 0xe2, 0xce, 0xa8, 0x2c, 0xa3, 0x9f, 0xf6, 0xd2, 0x4f, 0x17, 0xe3, 0xca, 0xbd, 0xb8, 0xf2, 0x10, 0xae, 0x3c, 0x84, 0x2b, 0x77, + 0xd3, 0x5f, 0x7b, 0x2d, 0x4f, 0xca, 0x0a, 0xcb, 0x53, 0x30, 0x15, 0xa6, 0xc1, 0x73, 0x40, 0xdf, 0x6e, 0xa1, 0x6f, 0xb7, 0xd0, 0xb7, 0x5b, 0xe8, 0xdb, 0x2d, 0xf4, 0xed, 0x16, 0xfa, 0x76, 0xcb, 0x5a, 0x7a, 0xd8, 0x58, 0x20, 0xb6, 0x95, 0x5a, 0x50, 0x1b, 0xea, 0x40, 0x5d, 0xa8, 0x07, 0xf5, 0xe1, 0x6a, 0x68, 0x04, 0x8d, 0x81, 0xf8, 0x57, 0x88, 0x7f, 0x85, 0xf8, 0x57, 0x9a, 0x42, 0x33, 0x20, 0x0f, 0x94, 0x16, 0xd0, 0x12, 0x5a, 0x41, 0x6b, 0x68, 0x03, 0x6d, 0xe1, 0x5a, 0x68, 0x07, 0xd7, 0xc1, 0xf5, 0xd0, 0x1d, 0xfe, 0xf5, 0xf7, 0x5e, 0xcb, 0x94, 0x07, 0x60, 0x1c, 0x3c, 0x08, 0x0f, 0xc1, 0x78, 0x98, 0x00, 0x13, 0xe1, 0x61, 0x98, 0x04, 0x93, 0x81, 0x39, 0x53, 0x1e, 0x85, 0xc7, 0x60, 0x0a, 0x30, 0x77, 0xca, 0x13, 0xf0, 0x24, 0x3c, 0x05, 0x53, 0x61, 0x1a, 0xe8, 0xbf, 0x89, + 0xfd, 0x8c, 0x5c, 0x4e, 0xd7, 0xb0, 0x9c, 0xae, 0x21, 0x89, 0xae, 0x21, 0x89, 0xae, 0x21, 0x89, 0xae, 0x21, 0x89, 0xae, 0x21, 0x89, 0xae, 0x21, 0x89, 0x6e, 0x61, 0x16, 0xdd, 0xc2, 0x2c, 0xba, 0x85, 0x59, 0x74, 0x0b, 0xb3, 0xe8, 0x16, 0x66, 0xd1, 0x2d, 0xcc, 0xa2, 0x5b, 0x98, 0x45, 0xb7, 0x30, 0x8b, 0x6e, 0x61, 0x16, 0x8e, 0x3d, 0x84, 0x63, 0x0f, 0xe1, 0xd8, 0x43, 0xca, 0x76, 0xe9, 0x55, 0x76, 0xc0, 0x4e, 0x48, 0x86, 0x3d, 0x80, 0x13, 0x53, 0x70, 0x62, 0x0a, 0x4e, 0x4c, 0xc1, 0x89, 0x29, 0x38, 0x30, 0xe5, 0x10, 0x8f, 0x1f, 0x86, 0x54, 0x38, 0x02, 0x47, 0xc1, 0x01, 0xc7, 0xe0, 0x38, 0x64, 0xc2, 0x09, 0x38, 0x09, 0x59, 0x90, 0x0d, 0x39, 0x70, 0x0a, 0x98, 0x63, 0x25, 0x17, 0x9c, 0xe0, 0x82, 0x7c, 0xd0, 0x7f, 0x6b, 0x84, 0x38, 0x56, 0x0a, 0xc0, 0x0b, 0x85, 0x74, 0x31, + 0xc5, 0x50, 0x02, 0x3e, 0xf0, 0x83, 0x06, 0xa5, 0x50, 0x06, 0x01, 0x08, 0xc2, 0x59, 0x28, 0x87, 0x0a, 0xa8, 0x84, 0x10, 0x9c, 0x83, 0xf3, 0x70, 0x41, 0x3a, 0xd5, 0x68, 0xdc, 0xb2, 0x05, 0x14, 0x50, 0x21, 0x16, 0x6a, 0x42, 0x2d, 0xa8, 0x2d, 0x43, 0x74, 0x0e, 0x21, 0x3a, 0x87, 0x10, 0x9d, 0x43, 0x88, 0xce, 0x21, 0x44, 0xe7, 0x10, 0xa2, 0x73, 0x08, 0xd1, 0x39, 0x84, 0xe8, 0x1c, 0x42, 0x74, 0x0e, 0x21, 0x3a, 0x87, 0x10, 0x9d, 0x43, 0x88, 0xce, 0x21, 0x44, 0xe7, 0x10, 0xa2, 0x73, 0x08, 0xd1, 0x39, 0x84, 0xe8, 0x1c, 0x42, 0x74, 0x0e, 0x21, 0x3a, 0x87, 0x10, 0x9d, 0x43, 0x88, 0xce, 0x21, 0x44, 0xe7, 0x10, 0xa2, 0x73, 0x08, 0xd1, 0x39, 0x84, 0xe8, 0x1c, 0x42, 0x74, 0x0e, 0x21, 0x3a, 0x87, 0x10, 0x9d, 0x43, 0x88, 0xce, 0x21, 0x44, 0xe7, 0x10, 0xa2, 0x73, 0x08, 0xd1, 0x39, + 0x84, 0xe8, 0x1c, 0x42, 0x74, 0x0e, 0x21, 0x3a, 0x87, 0x10, 0x9d, 0x43, 0x88, 0xce, 0x21, 0x44, 0xe7, 0x10, 0xa2, 0x73, 0x08, 0xd1, 0x39, 0x84, 0xe8, 0x1c, 0x42, 0x74, 0x0e, 0x21, 0x3a, 0x87, 0x10, 0x9d, 0x43, 0x88, 0xce, 0x21, 0x44, 0xe7, 0x10, 0xa2, 0x73, 0x08, 0xd1, 0x39, 0x84, 0xe8, 0x1c, 0x42, 0x74, 0x0e, 0x21, 0x75, 0x84, 0xf4, 0xaa, 0x23, 0x61, 0x14, 0xdc, 0x0f, 0xa3, 0x61, 0x0c, 0x8c, 0x85, 0x07, 0x60, 0x3c, 0x4c, 0x80, 0x89, 0xf0, 0x30, 0x4c, 0x82, 0xc9, 0xf0, 0x08, 0x3c, 0x0a, 0x8f, 0xc1, 0x14, 0x78, 0x1c, 0x9e, 0x80, 0x27, 0xe1, 0x29, 0x98, 0x0a, 0xd3, 0x60, 0x26, 0xcc, 0x82, 0x17, 0x01, 0xad, 0xa0, 0x4b, 0x09, 0xd1, 0xa5, 0x84, 0xe8, 0x52, 0x42, 0x74, 0x29, 0x21, 0xba, 0x94, 0x10, 0x5d, 0x4a, 0x88, 0x2e, 0x25, 0x44, 0x97, 0x12, 0xa2, 0x4b, 0x09, 0xd1, + 0xa5, 0x84, 0xe8, 0x40, 0xdc, 0x74, 0x20, 0x6e, 0x3a, 0x10, 0x37, 0x1d, 0x88, 0x9b, 0x0e, 0xc4, 0x4d, 0x07, 0xe2, 0xa6, 0x03, 0x71, 0xd3, 0x81, 0xb8, 0xe9, 0x40, 0xdc, 0x74, 0x20, 0x6e, 0x3a, 0x10, 0x37, 0x1d, 0x88, 0x9b, 0x0e, 0xc4, 0x4d, 0x07, 0xe2, 0xa6, 0x03, 0x71, 0xd3, 0x81, 0xb8, 0xe9, 0x40, 0xdc, 0x74, 0x20, 0x6e, 0x3a, 0x10, 0x37, 0x1d, 0x88, 0x9b, 0x0e, 0xc4, 0x4d, 0x07, 0xe2, 0xa6, 0x03, 0x71, 0xd3, 0x81, 0xb8, 0xe9, 0x40, 0xdc, 0x74, 0x20, 0x6e, 0x3a, 0x10, 0x37, 0x1d, 0x88, 0x9b, 0x0e, 0xc4, 0x4d, 0x07, 0xe2, 0xa6, 0x03, 0x71, 0xd3, 0x81, 0xb8, 0xe9, 0x40, 0xdc, 0x74, 0x20, 0x6e, 0x3a, 0x10, 0x37, 0x1d, 0x88, 0x9b, 0x0e, 0xc4, 0x4d, 0x07, 0xe2, 0xa6, 0x03, 0x71, 0xd3, 0x81, 0x78, 0xe9, 0x40, 0xbc, 0x74, 0x20, 0x5e, 0x3a, 0x10, 0x2f, 0x1d, 0x88, 0x97, + 0x0e, 0xc4, 0x4b, 0x07, 0xe2, 0xa5, 0x03, 0xf1, 0xd2, 0x81, 0x78, 0xe9, 0x40, 0xbc, 0x74, 0x20, 0x5e, 0x3a, 0x10, 0x2f, 0x1d, 0x88, 0x97, 0x0e, 0xc4, 0x4b, 0x07, 0xe2, 0xa5, 0x03, 0xf1, 0xd2, 0x81, 0x78, 0xe9, 0x40, 0xbc, 0x74, 0x20, 0x5e, 0x3a, 0x10, 0x2f, 0x1d, 0x88, 0x97, 0x0e, 0xc4, 0x4b, 0x07, 0x62, 0xa3, 0x03, 0xb1, 0xd1, 0x81, 0xe4, 0xd2, 0x81, 0xe4, 0xd2, 0x81, 0xe4, 0xd2, 0x81, 0xe4, 0xd2, 0x81, 0xe4, 0xd2, 0x81, 0xe4, 0xd2, 0x81, 0xe4, 0xd2, 0x79, 0xec, 0xa1, 0xf3, 0xd8, 0x43, 0xe7, 0xb1, 0x87, 0xce, 0x63, 0x0f, 0x9d, 0xc7, 0x1e, 0x3a, 0x8f, 0x3d, 0x74, 0x1e, 0x7b, 0xe8, 0x3c, 0xf6, 0xd0, 0x79, 0xec, 0xa1, 0xf3, 0xd8, 0x43, 0x87, 0x11, 0xc2, 0x69, 0xb8, 0xac, 0x05, 0xb2, 0xc2, 0xea, 0x85, 0x42, 0x28, 0x82, 0x62, 0x28, 0x91, 0x65, 0x56, 0x1f, 0xf8, 0x41, + 0x83, 0x52, 0x28, 0x93, 0x5e, 0x6b, 0x00, 0x82, 0x70, 0x16, 0xca, 0x01, 0xdd, 0xa4, 0x43, 0xf1, 0xd2, 0xa1, 0x78, 0xe9, 0x50, 0xbc, 0x74, 0x28, 0xde, 0x18, 0x72, 0x18, 0x2f, 0x51, 0xf5, 0x9b, 0x8d, 0x05, 0xe1, 0x4f, 0x53, 0xea, 0xbf, 0xd9, 0xb8, 0x1f, 0xa7, 0x70, 0x24, 0xfc, 0xab, 0x35, 0xa5, 0xf8, 0x8c, 0x52, 0x7c, 0x86, 0xf1, 0x2d, 0x32, 0xe3, 0xd7, 0x7f, 0xf4, 0x5f, 0xab, 0x2c, 0xd3, 0x7f, 0x81, 0xc8, 0xd4, 0xc5, 0xf8, 0xcc, 0xe6, 0xc1, 0xf0, 0xef, 0x37, 0xae, 0xe3, 0xd5, 0x87, 0x78, 0x75, 0x52, 0xf8, 0x17, 0x2b, 0x33, 0x22, 0x4d, 0x68, 0x7f, 0xb4, 0xac, 0x88, 0x1c, 0x22, 0xfd, 0x91, 0x43, 0x65, 0x20, 0xf2, 0x5e, 0xe3, 0x3a, 0x0b, 0x47, 0xa8, 0x01, 0x47, 0x2c, 0x67, 0x8c, 0xdf, 0x07, 0xf6, 0x1a, 0xbf, 0xdf, 0x7b, 0xce, 0x44, 0xef, 0x5e, 0xed, 0x57, 0x73, 0x92, + 0xc3, 0x9f, 0x23, 0xbc, 0xf4, 0x3b, 0x49, 0x91, 0x23, 0x8c, 0xdf, 0x4a, 0x3a, 0xcd, 0x2b, 0x4f, 0xf3, 0xca, 0x20, 0xaf, 0x2c, 0x14, 0x4d, 0xf5, 0xdf, 0xbe, 0x0f, 0xff, 0xda, 0x5f, 0xa9, 0xf1, 0x2b, 0x7f, 0xfa, 0xef, 0x4a, 0xd6, 0x97, 0x07, 0x8c, 0x5f, 0xe5, 0xeb, 0xc4, 0xbe, 0x74, 0x91, 0xc5, 0xe1, 0xcf, 0x1e, 0x6a, 0x11, 0xbf, 0xe0, 0x0f, 0xf5, 0xdf, 0xa7, 0x4f, 0x37, 0x7e, 0xfd, 0xd4, 0x17, 0xbe, 0xae, 0xa6, 0x3f, 0x72, 0x80, 0x2c, 0x0f, 0x5f, 0x5b, 0xf3, 0x00, 0xfb, 0x98, 0xcf, 0x3e, 0x1e, 0x63, 0x6b, 0xfa, 0x55, 0x1a, 0x32, 0xf5, 0xab, 0x86, 0x58, 0x26, 0xcb, 0x7c, 0x0b, 0x63, 0xae, 0xbe, 0x2f, 0x73, 0xd4, 0x14, 0x3a, 0xbd, 0x7f, 0xb2, 0xe5, 0x3a, 0xc6, 0xa7, 0xf0, 0x63, 0xd8, 0x72, 0x2d, 0x79, 0x82, 0xad, 0x86, 0xd8, 0x62, 0x06, 0x5b, 0x3c, 0x6b, 0xea, 0x2c, + 0x9a, 0xb3, 0xd5, 0x93, 0x38, 0x53, 0x85, 0xad, 0x64, 0xb1, 0xb6, 0xf2, 0xc8, 0x11, 0xc6, 0xb7, 0x2e, 0x4b, 0xd5, 0x56, 0xe2, 0x1a, 0xb5, 0xb5, 0xe8, 0xcc, 0x5a, 0x7c, 0xc6, 0xaf, 0x58, 0x46, 0xe3, 0x87, 0xcf, 0xe3, 0x85, 0xbd, 0x78, 0xe1, 0x42, 0xfc, 0xaf, 0x0f, 0xff, 0xeb, 0xc3, 0xfb, 0x7a, 0xfe, 0x07, 0xd7, 0x05, 0x6e, 0x27, 0x0e, 0xca, 0x4c, 0x71, 0x58, 0x9e, 0x13, 0x47, 0xf0, 0xda, 0x47, 0x65, 0x3a, 0x6b, 0xda, 0xc9, 0x36, 0x4a, 0xc2, 0x7e, 0x3b, 0x03, 0xbf, 0xfd, 0x0b, 0x6b, 0x3d, 0xc1, 0xf6, 0xce, 0xb1, 0xbd, 0x4a, 0xd6, 0x1e, 0x62, 0xed, 0x7b, 0xd8, 0xae, 0x8b, 0x2d, 0x9c, 0xc1, 0x77, 0xe7, 0x2a, 0x1d, 0xa8, 0x27, 0x9d, 0xa1, 0x0b, 0xb1, 0xd4, 0x58, 0x9e, 0xb3, 0xc6, 0x41, 0x13, 0x68, 0x0a, 0xcd, 0xa0, 0x39, 0xb4, 0x80, 0x96, 0xd0, 0x0a, 0x5a, 0x43, 0x1b, + 0x68, 0x0b, 0xd7, 0x42, 0x3b, 0xb8, 0x0e, 0xae, 0x87, 0xf6, 0xd0, 0x01, 0x3a, 0x42, 0x27, 0xb6, 0x79, 0x93, 0xd8, 0x8b, 0xdb, 0xb7, 0xb1, 0x27, 0xfb, 0xd9, 0x3b, 0x3b, 0x7b, 0x7b, 0x84, 0xe3, 0x3b, 0x6a, 0x5c, 0x9b, 0x5a, 0xdf, 0x4b, 0x1f, 0x7b, 0x59, 0xc1, 0xb1, 0x7f, 0xce, 0x5e, 0x9e, 0x63, 0x2f, 0x4b, 0xd8, 0xcb, 0x12, 0xf6, 0x52, 0xbf, 0x2e, 0xb5, 0x7e, 0x4d, 0xf6, 0x2b, 0xf6, 0x27, 0xb8, 0xff, 0x0a, 0x8e, 0xff, 0xcc, 0x1f, 0x7e, 0xd3, 0xad, 0xbb, 0xcc, 0xb0, 0xf4, 0x80, 0xbb, 0xe0, 0x6e, 0x19, 0xb4, 0x0c, 0x86, 0x21, 0xdc, 0xbe, 0x07, 0x86, 0x72, 0x7b, 0x18, 0xe0, 0xcc, 0x2d, 0xf7, 0xc1, 0x70, 0x18, 0x01, 0x23, 0x81, 0xc8, 0xb5, 0xdc, 0x0f, 0xa3, 0xf9, 0xfb, 0x18, 0x18, 0xcb, 0xed, 0x07, 0x60, 0x1c, 0xb7, 0x1f, 0x84, 0x87, 0x64, 0x86, 0x72, 0x9f, 0x4c, 0x56, + 0x66, 0x4a, 0x9b, 0xf2, 0xba, 0xf4, 0x28, 0x0b, 0xe0, 0x1d, 0x78, 0x17, 0x16, 0xcb, 0x53, 0xd6, 0x99, 0x52, 0x1a, 0x9e, 0x7b, 0x37, 0x7b, 0xb7, 0x55, 0x9f, 0x1d, 0xee, 0xf5, 0xc4, 0xa9, 0xf5, 0x92, 0x3b, 0x4c, 0xbd, 0xe5, 0x0e, 0x11, 0xcd, 0xbd, 0x7c, 0xee, 0xa5, 0x73, 0x2f, 0x9d, 0xe7, 0x25, 0x87, 0x73, 0x66, 0xbb, 0xfe, 0x79, 0x1b, 0x22, 0xb8, 0xea, 0x13, 0xcd, 0xdb, 0x78, 0x64, 0xb7, 0xf1, 0xdc, 0xed, 0x3c, 0xf7, 0x37, 0x9e, 0xfb, 0x5b, 0xf8, 0x17, 0x61, 0xf5, 0xdf, 0xc7, 0x3d, 0x24, 0x54, 0xfe, 0x62, 0xe3, 0x2f, 0x3e, 0xfe, 0xe2, 0x0b, 0x3b, 0xfc, 0xdd, 0xfa, 0x6f, 0x6d, 0x32, 0x06, 0x36, 0xc6, 0x60, 0x0f, 0x63, 0x60, 0x37, 0x3e, 0x19, 0x7e, 0x50, 0xcf, 0x66, 0x63, 0x8f, 0xbc, 0xe1, 0x2e, 0x24, 0xd5, 0xd8, 0x23, 0x8d, 0x57, 0x6b, 0xbc, 0x5a, 0x13, 0x8a, 0xf1, + 0x79, 0xda, 0x47, 0x64, 0x89, 0xf1, 0x19, 0xcf, 0xf0, 0xe7, 0x33, 0xf5, 0xcf, 0x61, 0x1a, 0x9f, 0x89, 0xeb, 0x2a, 0x2f, 0x88, 0x5a, 0xf4, 0x09, 0xc7, 0x79, 0x56, 0x12, 0xaf, 0xfb, 0xce, 0x74, 0xab, 0xdc, 0x68, 0xea, 0x07, 0x03, 0x8d, 0x5f, 0xa9, 0x4d, 0xbb, 0xf8, 0x2b, 0xaf, 0xa8, 0x45, 0x3e, 0x6a, 0x91, 0x8f, 0x5a, 0x68, 0xac, 0xa1, 0x8c, 0x5e, 0x53, 0xff, 0x75, 0xa9, 0x02, 0x7a, 0x90, 0xf7, 0x8c, 0xfc, 0xd6, 0xaf, 0x78, 0x7b, 0x28, 0xac, 0x14, 0x59, 0xd5, 0xf2, 0xfb, 0x38, 0x5a, 0x93, 0x8a, 0x93, 0xd5, 0x58, 0x43, 0x61, 0xf8, 0x97, 0x84, 0x8a, 0xd8, 0x97, 0x0a, 0x72, 0xfe, 0x10, 0x39, 0x7f, 0x88, 0x7d, 0xa9, 0xb0, 0x0e, 0x37, 0x34, 0xe8, 0x33, 0x63, 0x4c, 0xf6, 0xb3, 0xef, 0xfb, 0xd9, 0xf7, 0xfd, 0xac, 0x29, 0x9d, 0x35, 0x1d, 0x66, 0x4d, 0xe9, 0x17, 0xfb, 0x6c, + 0x61, 0xba, 0xd4, 0x67, 0x37, 0x10, 0xfa, 0xb7, 0x75, 0xdf, 0xe3, 0x39, 0xff, 0x22, 0x73, 0xd8, 0xfa, 0x3e, 0xb6, 0x7e, 0x9a, 0xad, 0xbb, 0xd8, 0xba, 0x8b, 0xad, 0x17, 0xb2, 0xf5, 0x52, 0x46, 0xc2, 0x7d, 0xf1, 0xd7, 0xca, 0xd8, 0x83, 0x52, 0xf6, 0xc0, 0xc3, 0x5a, 0xdf, 0x12, 0x8d, 0x45, 0x29, 0x48, 0x8e, 0x3a, 0x4a, 0x2e, 0x30, 0xd5, 0x94, 0x8b, 0xd1, 0x81, 0xa3, 0xc6, 0x2f, 0x99, 0xd6, 0x17, 0x0d, 0x8c, 0xeb, 0x31, 0x75, 0x91, 0xab, 0xd9, 0xda, 0x09, 0xb6, 0x96, 0xc8, 0x96, 0x7c, 0xe1, 0x2b, 0xfb, 0x7e, 0x18, 0xb1, 0x4b, 0xbe, 0x8b, 0x2e, 0xac, 0x8c, 0x1c, 0x88, 0x3e, 0xdc, 0x6b, 0xfc, 0xee, 0xb5, 0x47, 0xbf, 0x3e, 0x11, 0x5b, 0x79, 0x85, 0xad, 0xbc, 0xa2, 0x5f, 0x23, 0xcb, 0xda, 0x48, 0x7e, 0x82, 0x3e, 0x1c, 0x45, 0xf1, 0xbf, 0x15, 0xcd, 0x19, 0xf7, 0x6d, 0xf4, + 0x95, 0x21, 0x7a, 0xb0, 0x57, 0x39, 0x3a, 0x37, 0xb3, 0xe4, 0xe6, 0x98, 0x3e, 0xe3, 0x98, 0x3e, 0xa0, 0x4f, 0xf8, 0x89, 0xad, 0x64, 0xb0, 0x95, 0x77, 0xd9, 0xca, 0xc9, 0xf0, 0x56, 0xde, 0xa7, 0xb3, 0x2d, 0xa4, 0xb3, 0xd5, 0x3f, 0x03, 0x3f, 0x87, 0xe3, 0xfa, 0x82, 0xce, 0xb6, 0x80, 0x63, 0x3a, 0x47, 0x77, 0x7b, 0x9a, 0x3e, 0x20, 0x95, 0xd1, 0xe9, 0xc0, 0xf1, 0xad, 0x66, 0x8e, 0xfc, 0x78, 0xf3, 0x45, 0xe1, 0x3d, 0x98, 0xcd, 0x1e, 0xcc, 0xe6, 0x38, 0x57, 0xb3, 0xe5, 0xf5, 0xf4, 0x9a, 0x15, 0xf4, 0x9a, 0x15, 0xe2, 0x80, 0xd8, 0x47, 0xae, 0x1e, 0x94, 0x69, 0xe2, 0x90, 0xfc, 0x48, 0xa4, 0xa2, 0x4e, 0x47, 0xc8, 0xcb, 0xa3, 0x74, 0x8d, 0x0e, 0x99, 0x48, 0xce, 0x7a, 0xc4, 0x71, 0xe9, 0x37, 0xae, 0x42, 0x9e, 0x0e, 0x19, 0xe8, 0x96, 0xae, 0x30, 0x27, 0x78, 0x2c, 0x8b, 0xe7, + 0x65, 0xcb, 0xef, 0xc9, 0xe1, 0x7c, 0x72, 0xf8, 0x8c, 0xc8, 0xe3, 0xef, 0x4e, 0x70, 0xc9, 0x8f, 0x45, 0x3e, 0xcb, 0x33, 0xe0, 0xa6, 0x9b, 0xf5, 0xc8, 0x63, 0xe4, 0xf5, 0x11, 0xe1, 0x65, 0x2f, 0xf5, 0xa3, 0xe9, 0xcb, 0x98, 0xdd, 0x02, 0x97, 0x1f, 0xd5, 0x83, 0xdc, 0x7f, 0x48, 0xbe, 0x1b, 0xb1, 0x5c, 0x66, 0x90, 0xef, 0x47, 0x23, 0x56, 0x4a, 0x17, 0x1d, 0xff, 0x2f, 0x11, 0xeb, 0x58, 0x7e, 0x0b, 0x1b, 0xa4, 0x2b, 0x72, 0xae, 0x2c, 0x89, 0x9c, 0x07, 0xf3, 0xe9, 0xaa, 0x17, 0x18, 0x9d, 0xb5, 0x16, 0x95, 0x4d, 0xd5, 0x39, 0x2b, 0x5d, 0x51, 0x15, 0xe4, 0xf0, 0xb3, 0xe4, 0x6e, 0x03, 0x68, 0x08, 0xed, 0xa1, 0x03, 0xb5, 0xa4, 0x13, 0xcb, 0xce, 0x2c, 0xa9, 0x66, 0x4a, 0x57, 0x6e, 0x77, 0x33, 0xf2, 0x7b, 0xba, 0xf2, 0x9a, 0x7c, 0x8e, 0xfc, 0x4e, 0x55, 0xe6, 0xb0, 0x5c, 0xc0, + 0xf2, 0x1d, 0x78, 0x17, 0xde, 0xe3, 0xfe, 0xfb, 0x90, 0x22, 0x5d, 0xea, 0x4d, 0xf2, 0x9c, 0x3a, 0x8e, 0xe5, 0x83, 0xf0, 0x34, 0x3c, 0x03, 0xcf, 0xc2, 0x73, 0x30, 0x1d, 0x12, 0xe0, 0x79, 0x78, 0x81, 0xea, 0xdd, 0x99, 0xb9, 0xed, 0x02, 0x5d, 0xa1, 0x1b, 0xdc, 0x00, 0xdd, 0xa1, 0x07, 0xc4, 0x43, 0x4f, 0xb8, 0x11, 0x6e, 0x82, 0x5e, 0xd0, 0x1b, 0xfa, 0x40, 0x5f, 0xb8, 0x19, 0x6e, 0x81, 0x5b, 0xa1, 0x1f, 0xf4, 0x87, 0xdb, 0x60, 0x00, 0xdc, 0x0e, 0x03, 0xe1, 0x0e, 0xb8, 0x13, 0x06, 0x49, 0xbf, 0xf5, 0x2e, 0xb8, 0x1b, 0x06, 0x03, 0xd5, 0xd7, 0x7a, 0x0f, 0x0c, 0x85, 0x61, 0x40, 0x15, 0xb6, 0xde, 0xc7, 0xfe, 0x8c, 0x80, 0x91, 0x30, 0x0a, 0xee, 0x87, 0xd1, 0x30, 0x06, 0xc6, 0xc2, 0x03, 0xc0, 0x71, 0x59, 0x39, 0x2e, 0xeb, 0x43, 0x30, 0x1e, 0x26, 0xc0, 0x44, 0x78, 0x18, 0x26, + 0xc1, 0x64, 0x78, 0x04, 0x1e, 0x85, 0xc7, 0x60, 0x0a, 0x3c, 0x0e, 0x4f, 0xc0, 0x93, 0x6c, 0xe3, 0x29, 0x78, 0x1a, 0x9e, 0x81, 0x67, 0xe1, 0x39, 0x98, 0x0e, 0x09, 0xf0, 0x3c, 0xbc, 0x00, 0x33, 0x60, 0x26, 0xcc, 0x82, 0x17, 0xe1, 0x25, 0x98, 0x0d, 0x2f, 0xc3, 0x2b, 0x30, 0x87, 0x75, 0xcd, 0x85, 0x37, 0x60, 0x1e, 0xcc, 0x87, 0x37, 0x61, 0x01, 0xbc, 0x05, 0x0b, 0xe1, 0x6d, 0x78, 0x07, 0xde, 0x05, 0xba, 0x67, 0xeb, 0xfb, 0x90, 0x08, 0x1f, 0xc0, 0x87, 0xb0, 0x08, 0x16, 0xc3, 0x47, 0xb2, 0xc0, 0xfa, 0x31, 0x7c, 0x02, 0x4b, 0x60, 0x29, 0x2c, 0x83, 0x4f, 0xe1, 0x33, 0xf8, 0x1c, 0xbe, 0x80, 0x2f, 0xe1, 0x2b, 0x58, 0x0e, 0x2b, 0x60, 0x25, 0xac, 0x82, 0xaf, 0x61, 0x35, 0xac, 0x81, 0xb5, 0xf0, 0x0d, 0xac, 0x83, 0xf5, 0xf0, 0x2d, 0x6c, 0x92, 0x19, 0x7a, 0xe6, 0xa0, 0x3e, 0x33, + 0xf5, 0xab, 0xe4, 0x9a, 0x7a, 0x9a, 0xbf, 0xa8, 0xfe, 0x2f, 0xea, 0x48, 0xd4, 0x91, 0xe8, 0x3e, 0x35, 0xd7, 0xd6, 0x5c, 0x5b, 0x6b, 0x7e, 0xf4, 0x6f, 0xd6, 0xab, 0x6b, 0x6c, 0xac, 0xfa, 0x17, 0x7b, 0xb2, 0xea, 0x5f, 0x8d, 0xb2, 0xd8, 0x5e, 0xb1, 0x27, 0x6b, 0xde, 0x78, 0xf1, 0x5f, 0xfc, 0xc2, 0x9a, 0x0d, 0x8d, 0x5b, 0x6b, 0x2f, 0xfb, 0x57, 0x5e, 0xb3, 0xbc, 0xd6, 0xfc, 0x5a, 0xf3, 0xff, 0xf4, 0xf8, 0x15, 0x9e, 0x57, 0xfd, 0x1f, 0xaf, 0xd9, 0x5e, 0xbb, 0x57, 0xf5, 0x7f, 0xcd, 0xc7, 0x36, 0x3b, 0xc7, 0xbf, 0x9d, 0xcd, 0xc7, 0x5e, 0xfc, 0x67, 0xdc, 0xff, 0x2f, 0xff, 0xe9, 0xcf, 0xac, 0x3d, 0xb9, 0xf6, 0xaa, 0xba, 0x93, 0x6b, 0x1f, 0xbe, 0xf8, 0xaa, 0x3a, 0xd1, 0xcd, 0x76, 0xd6, 0x99, 0x59, 0xb5, 0x96, 0x3a, 0x29, 0x75, 0x7c, 0x75, 0xdb, 0xd6, 0x9d, 0xfc, 0x97, 0x6b, + 0xd8, 0xc9, 0x36, 0x67, 0xd7, 0x5d, 0x52, 0xb7, 0xbc, 0x41, 0x74, 0xd3, 0x60, 0x5c, 0x8d, 0xa6, 0xc1, 0xaa, 0x7f, 0xcd, 0x86, 0x55, 0xff, 0xd7, 0x7c, 0x76, 0xf3, 0xcd, 0x7f, 0xdc, 0xbb, 0xaa, 0x6d, 0x55, 0xbb, 0xc7, 0x33, 0x5a, 0xde, 0xfa, 0xc7, 0x7f, 0xcd, 0x1d, 0xcd, 0x1d, 0x2d, 0xfe, 0xf4, 0xe8, 0x95, 0xff, 0xe9, 0xcf, 0xad, 0xfe, 0x4f, 0x7f, 0x5d, 0x2b, 0xd1, 0x2a, 0xbb, 0xe5, 0x06, 0xfd, 0x9f, 0x7e, 0xab, 0xf5, 0x80, 0xcb, 0xff, 0xb5, 0xfd, 0xb8, 0xea, 0x5f, 0xeb, 0x6f, 0x2f, 0xde, 0x6a, 0xfb, 0x71, 0xfb, 0x27, 0xda, 0x3f, 0xd1, 0x76, 0xe7, 0xf5, 0xbb, 0xf4, 0x65, 0xd5, 0xbf, 0x0e, 0xa7, 0xfe, 0xfd, 0x7f, 0x1d, 0xdb, 0x76, 0x6c, 0xdb, 0xa9, 0xf5, 0xef, 0xff, 0x3a, 0x47, 0x77, 0x9b, 0xd2, 0x6d, 0x6d, 0xb7, 0xb5, 0x37, 0xd4, 0xaf, 0xfa, 0xd7, 0x7d, 0x94, 0xfe, + 0xef, 0xe2, 0xbd, 0x8b, 0xf7, 0x7f, 0xff, 0x17, 0xdf, 0x21, 0x7e, 0xa1, 0xfe, 0x4f, 0x98, 0x45, 0xb4, 0x65, 0xb4, 0xe5, 0x69, 0x21, 0x2c, 0xcf, 0x5a, 0xe6, 0x8b, 0xab, 0x2d, 0x0b, 0x2c, 0x8b, 0x44, 0x27, 0xcb, 0x3f, 0x94, 0xde, 0xa2, 0x97, 0xd2, 0x57, 0x19, 0x21, 0x4e, 0x2b, 0xa3, 0x94, 0x45, 0xe2, 0x6f, 0x45, 0xff, 0x5b, 0xd1, 0xff, 0x56, 0xf4, 0xff, 0x9e, 0xa2, 0xff, 0xdf, 0xe4, 0xf7, 0x8e, 0xd3, 0x9f, 0xf9, 0xe8, 0xcf, 0xbc, 0xc6, 0x2f, 0x14, 0xe9, 0xd7, 0x03, 0x39, 0xc2, 0xed, 0xa3, 0xec, 0x85, 0x83, 0xae, 0xf1, 0x18, 0xf7, 0x8f, 0xcb, 0x62, 0xd4, 0x41, 0x43, 0x1d, 0xca, 0x51, 0x87, 0x7c, 0xd4, 0xe1, 0x00, 0xea, 0x50, 0x48, 0xcf, 0xb6, 0x1b, 0x85, 0xf0, 0xa2, 0x10, 0x1e, 0x14, 0xa2, 0x14, 0x85, 0x28, 0xa3, 0x6f, 0xcb, 0x16, 0xb9, 0x74, 0x00, 0x79, 0xac, 0xcb, + 0x09, 0x2e, 0x99, 0x85, 0x52, 0x14, 0xa1, 0x14, 0x7e, 0x94, 0xc2, 0x87, 0x52, 0xe4, 0xa1, 0x14, 0x05, 0x28, 0x85, 0x7e, 0xde, 0xa1, 0x90, 0x23, 0x2a, 0x41, 0x09, 0xdc, 0x28, 0x81, 0x9f, 0x7e, 0x2e, 0x1d, 0x35, 0x38, 0x10, 0xc1, 0xcc, 0x44, 0x30, 0x33, 0xa8, 0x82, 0x1f, 0x55, 0xf0, 0xa3, 0x0a, 0x7e, 0x8e, 0x20, 0x9d, 0xfe, 0xc6, 0x8f, 0x12, 0x54, 0xa2, 0x04, 0xfe, 0xa8, 0x72, 0x69, 0xa3, 0xdf, 0xf3, 0xd2, 0xef, 0x79, 0xe9, 0xf7, 0xbc, 0xf4, 0x7b, 0x95, 0xf4, 0x7b, 0x95, 0xf4, 0x7b, 0x5e, 0xfa, 0x3d, 0x2f, 0xfd, 0x5e, 0x25, 0xfd, 0x5e, 0x25, 0xfd, 0x9e, 0x97, 0x7e, 0xcf, 0x4b, 0xbf, 0xe7, 0xa5, 0xdf, 0xf3, 0xd2, 0xef, 0x79, 0xe9, 0xf7, 0xbc, 0xf4, 0x7b, 0x5e, 0xfa, 0xbd, 0x4a, 0xfa, 0xbd, 0x4a, 0xfa, 0x3d, 0x2f, 0xfd, 0x9e, 0x97, 0x7e, 0xaf, 0x92, 0x7e, 0xaf, 0x92, + 0x7e, 0xcf, 0x8b, 0x7a, 0xe4, 0xd3, 0xef, 0x79, 0x51, 0x90, 0x54, 0x14, 0xc4, 0x8f, 0x82, 0xa4, 0xa2, 0x20, 0x7e, 0x14, 0xc4, 0x8f, 0x82, 0xf8, 0x51, 0x90, 0x54, 0x14, 0x24, 0x15, 0x05, 0xf1, 0xa3, 0x20, 0x5e, 0x14, 0xc4, 0x8f, 0x82, 0xf8, 0x51, 0x10, 0x3f, 0x0a, 0xe2, 0x47, 0x41, 0xfc, 0x28, 0x88, 0x1f, 0x05, 0xf1, 0xa3, 0x20, 0x7e, 0x14, 0xc4, 0xaf, 0xea, 0xd9, 0xd2, 0x59, 0x6a, 0x28, 0x88, 0x86, 0x82, 0x68, 0x28, 0x88, 0x86, 0x82, 0x68, 0x28, 0x88, 0x86, 0x82, 0x68, 0x28, 0x88, 0x86, 0x82, 0x68, 0x28, 0x88, 0x86, 0x82, 0x68, 0x28, 0x88, 0x86, 0x82, 0x68, 0x28, 0x88, 0x86, 0x82, 0x68, 0x28, 0x88, 0x86, 0x82, 0x68, 0x28, 0x88, 0x86, 0x82, 0x68, 0x28, 0x88, 0x86, 0x82, 0x68, 0x28, 0x88, 0x86, 0x82, 0x68, 0x28, 0x88, 0x86, 0x82, 0x68, 0x28, 0x88, 0x86, 0x82, 0x14, + 0xa3, 0x20, 0xc5, 0x28, 0x48, 0x31, 0x0a, 0x52, 0x8c, 0x82, 0x14, 0xa3, 0x20, 0xc5, 0x28, 0x48, 0x31, 0x0a, 0x52, 0x8c, 0x82, 0x14, 0xa3, 0x20, 0xe5, 0x28, 0x48, 0x39, 0x0a, 0x52, 0x8e, 0x82, 0x94, 0xa3, 0x20, 0xe5, 0x28, 0x48, 0x39, 0x0a, 0x52, 0x8e, 0x82, 0x94, 0xa3, 0x20, 0xe5, 0x28, 0x48, 0x39, 0x0a, 0x52, 0x8e, 0x82, 0x94, 0xa3, 0x20, 0xe5, 0x28, 0x48, 0x39, 0x0a, 0x52, 0x8e, 0x82, 0x94, 0xa3, 0x20, 0xe5, 0x28, 0x48, 0x39, 0x0a, 0x52, 0x8e, 0x82, 0x94, 0xa3, 0x20, 0xe5, 0x28, 0x48, 0x39, 0x0a, 0x52, 0x8e, 0x82, 0x94, 0xa3, 0x20, 0x85, 0x28, 0xc8, 0x59, 0x14, 0xa4, 0x10, 0x05, 0x29, 0x44, 0x41, 0x0a, 0x51, 0x90, 0x42, 0x14, 0xa4, 0x10, 0x05, 0x29, 0x44, 0x41, 0x0a, 0x51, 0x90, 0x42, 0x14, 0xe4, 0xac, 0x75, 0xa6, 0xe8, 0x89, 0x82, 0x54, 0xa2, 0x20, 0x85, 0x28, + 0x48, 0x21, 0x0a, 0x52, 0x81, 0x82, 0x14, 0xa2, 0x20, 0x85, 0xd6, 0xd7, 0xc8, 0xcc, 0xd7, 0x61, 0x8e, 0x2c, 0x42, 0x49, 0x8a, 0x50, 0x92, 0x22, 0x94, 0xa4, 0x08, 0x25, 0x29, 0x42, 0x49, 0x8a, 0x50, 0x92, 0x22, 0x94, 0xa4, 0x08, 0x25, 0x29, 0x42, 0x49, 0x8a, 0x50, 0x92, 0x22, 0x94, 0xa4, 0x08, 0x25, 0x29, 0x42, 0x49, 0x8a, 0x50, 0x92, 0x22, 0x94, 0xa4, 0x08, 0x25, 0x29, 0x42, 0x49, 0x8a, 0x50, 0x92, 0x22, 0x94, 0x44, 0x43, 0x49, 0x34, 0x94, 0x44, 0x43, 0x49, 0x34, 0x94, 0x44, 0x43, 0x49, 0x34, 0x94, 0x44, 0x43, 0x49, 0x34, 0x94, 0x44, 0x43, 0x49, 0x34, 0x94, 0x44, 0x43, 0x49, 0x34, 0x94, 0x44, 0x43, 0x49, 0x34, 0x94, 0x44, 0x43, 0x49, 0x34, 0x94, 0x44, 0x43, 0x49, 0x34, 0x94, 0x44, 0x43, 0x49, 0x34, 0x94, 0x44, 0x43, 0x49, 0x34, 0x94, 0x44, 0xb3, 0xea, 0xbf, 0x40, + 0x45, 0x4f, 0x2e, 0xbe, 0x16, 0xa5, 0x32, 0x20, 0xca, 0x8c, 0x6b, 0x0d, 0x78, 0xc4, 0x05, 0x79, 0x06, 0x35, 0x29, 0x36, 0xce, 0x5d, 0xd5, 0xa0, 0x67, 0x8e, 0x85, 0x5a, 0xd2, 0x8d, 0xaa, 0x68, 0xa6, 0x3a, 0x74, 0xea, 0x75, 0xa1, 0x9e, 0xfe, 0xfb, 0xdf, 0xa8, 0x4e, 0x03, 0xba, 0xe5, 0x86, 0x74, 0xcf, 0x57, 0x71, 0xff, 0x6a, 0xf8, 0xfd, 0x6a, 0x15, 0x5e, 0x53, 0x1c, 0x34, 0x01, 0xfd, 0x4a, 0x28, 0xcd, 0x58, 0x36, 0xa7, 0x8f, 0x6d, 0x21, 0x3d, 0xc6, 0x55, 0x51, 0x5a, 0x71, 0xbf, 0x35, 0xb4, 0x61, 0xbd, 0x6d, 0x59, 0x5e, 0x0b, 0xed, 0x64, 0x9e, 0xe9, 0x3a, 0x96, 0xd7, 0x43, 0x7b, 0xd6, 0xa1, 0x5f, 0x35, 0xa5, 0xa3, 0xf1, 0x4b, 0xe5, 0xfb, 0x51, 0x31, 0xe7, 0xa5, 0x2c, 0xdc, 0x6c, 0x5c, 0x9b, 0x20, 0x3f, 0x22, 0x19, 0x76, 0x51, 0x9b, 0x77, 0x73, 0x3f, 0x85, 0xdb, 0x69, + 0x2c, 0xd3, 0xf9, 0x7b, 0x1e, 0x4b, 0x27, 0xb8, 0x20, 0x1f, 0xce, 0xc8, 0xac, 0x08, 0x37, 0x4b, 0x0f, 0x14, 0x80, 0x57, 0x9e, 0x8e, 0x28, 0x64, 0x59, 0x04, 0xc5, 0xa0, 0x5f, 0x81, 0xc5, 0xc7, 0x63, 0x7e, 0xb2, 0x5b, 0x83, 0x52, 0x9e, 0x5f, 0xc6, 0xfd, 0x00, 0xb7, 0x83, 0x70, 0x96, 0xe7, 0x94, 0x93, 0xf1, 0x15, 0x50, 0xc9, 0xed, 0xaa, 0x2b, 0x5a, 0x78, 0x23, 0xce, 0xc3, 0x05, 0xfe, 0x2e, 0xa5, 0xd7, 0xf8, 0xc4, 0xbb, 0x49, 0xe6, 0x1b, 0x57, 0x6d, 0x89, 0xe0, 0x7e, 0x24, 0x44, 0xc9, 0xac, 0xc8, 0x68, 0x7c, 0x82, 0x7e, 0x15, 0x97, 0xaa, 0x2b, 0x5b, 0x78, 0x23, 0xfb, 0x43, 0xd5, 0x79, 0xbd, 0xe2, 0xc8, 0xbb, 0x79, 0xfe, 0x60, 0xee, 0x0f, 0x91, 0x87, 0x22, 0x51, 0x82, 0xc8, 0xa1, 0x32, 0x37, 0x72, 0x18, 0xcb, 0x7b, 0xe5, 0xee, 0x4b, 0x57, 0x7d, 0x19, 0x51, 0x75, 0xe5, + 0x97, 0x4b, 0xaa, 0x52, 0x24, 0x8f, 0xe8, 0x57, 0x81, 0xb1, 0xc4, 0xcb, 0x22, 0xcb, 0x78, 0x98, 0x00, 0x93, 0xe5, 0x09, 0x8b, 0xd7, 0xb8, 0x12, 0x86, 0x7e, 0x3d, 0x48, 0xaf, 0x92, 0x28, 0x8f, 0x5e, 0xe1, 0x6a, 0x18, 0x57, 0xbe, 0x6a, 0xcc, 0xd5, 0xfa, 0xb5, 0x4d, 0x50, 0x85, 0xd6, 0x2c, 0xf5, 0xeb, 0x9c, 0xdc, 0xc6, 0xed, 0x8b, 0x57, 0x92, 0x79, 0x89, 0xdb, 0xaf, 0x19, 0xd7, 0x7f, 0xc9, 0x08, 0x5f, 0x23, 0x32, 0x4b, 0x75, 0x4b, 0xb7, 0xaa, 0xff, 0xba, 0xb8, 0x0f, 0x18, 0x2f, 0x95, 0xf1, 0xa2, 0x42, 0xe8, 0xd7, 0x54, 0xf0, 0x5a, 0x37, 0x18, 0xd7, 0x4e, 0xf0, 0x58, 0xb7, 0xc0, 0x56, 0xd8, 0x06, 0x3b, 0xe4, 0x19, 0x6b, 0x32, 0x8f, 0xef, 0x92, 0x59, 0xd6, 0xdd, 0xb0, 0x57, 0x1e, 0xb1, 0xee, 0xe3, 0x3e, 0x2a, 0x6e, 0x3d, 0x00, 0x76, 0x38, 0x08, 0xa9, 0xac, 0x07, 0x35, + 0xb7, 0xe2, 0xf5, 0xac, 0x0e, 0xe9, 0x09, 0x7f, 0x62, 0xda, 0x1f, 0xb3, 0x0f, 0x15, 0xaf, 0x15, 0xae, 0x3a, 0x5e, 0xaa, 0x8e, 0x9f, 0xaa, 0x53, 0x74, 0x31, 0x12, 0xa8, 0x20, 0x15, 0x54, 0x8f, 0x02, 0xf3, 0xf7, 0x72, 0x97, 0xf1, 0x8e, 0xf5, 0xc5, 0x51, 0xda, 0xa5, 0xff, 0x0a, 0xa7, 0x88, 0x54, 0xbb, 0x0a, 0xf5, 0x7f, 0xe9, 0x8c, 0xe8, 0xff, 0xf9, 0x67, 0x2f, 0x6b, 0x1a, 0xef, 0xed, 0xeb, 0x9f, 0x1a, 0x9f, 0xcc, 0x31, 0x72, 0x4c, 0xa6, 0xc9, 0x22, 0x96, 0xd1, 0xc9, 0x63, 0x74, 0x34, 0x46, 0x24, 0x8f, 0x11, 0xb9, 0x5a, 0x6d, 0x26, 0xda, 0xa9, 0xcd, 0x39, 0xae, 0x16, 0x1c, 0x5f, 0x4b, 0x51, 0x93, 0x11, 0x6a, 0x62, 0x9c, 0x63, 0x73, 0x51, 0xb9, 0xf3, 0xc2, 0xd7, 0x12, 0xd3, 0x7f, 0x35, 0xdb, 0x63, 0xf2, 0x83, 0x26, 0x4b, 0x63, 0x06, 0x70, 0xf4, 0xff, 0xf9, 0xcf, 0x11, + 0xd4, 0x63, 0x0b, 0x47, 0x39, 0x92, 0x7c, 0xb6, 0xa2, 0xbf, 0x83, 0xed, 0xc6, 0x31, 0xe8, 0x73, 0x9d, 0xae, 0x1f, 0x0d, 0x6b, 0x75, 0x71, 0x44, 0xe9, 0xca, 0x4a, 0x51, 0x5b, 0xc9, 0x96, 0xd9, 0x6a, 0x1c, 0x47, 0xd6, 0x44, 0x06, 0x38, 0xc2, 0x66, 0x1c, 0x61, 0x5d, 0x8e, 0xb0, 0x2e, 0x47, 0xa7, 0x7f, 0x8f, 0xa0, 0x2d, 0x47, 0xd8, 0x5a, 0xb4, 0xc2, 0x31, 0xe8, 0xbf, 0x55, 0x73, 0x8a, 0xd9, 0xf3, 0x30, 0x7b, 0x7b, 0x98, 0xbd, 0x6d, 0xcc, 0x9e, 0x83, 0xd9, 0xcb, 0x63, 0xf6, 0xf6, 0x30, 0x7b, 0x9b, 0x99, 0xbd, 0x63, 0xcc, 0xde, 0x59, 0x66, 0xef, 0x34, 0xb3, 0x17, 0x60, 0xf6, 0x52, 0x98, 0xbd, 0x83, 0xcc, 0x5e, 0x01, 0xb3, 0xb7, 0x9f, 0xea, 0xbf, 0x87, 0xca, 0x5f, 0x46, 0xe5, 0xdf, 0x1c, 0x95, 0x2d, 0x9a, 0x33, 0x9b, 0xa5, 0xcc, 0x66, 0x29, 0xb3, 0x59, 0x6a, 0x7d, 0x4a, + 0xb4, 0xa2, 0x72, 0xf8, 0xa8, 0x1c, 0x21, 0x2a, 0x86, 0xcf, 0xfa, 0xbc, 0x30, 0x5b, 0x67, 0xf0, 0xd8, 0x4c, 0x31, 0xce, 0x3a, 0x4b, 0x34, 0xb7, 0xce, 0x16, 0x35, 0xa9, 0x12, 0x95, 0xd6, 0x57, 0x18, 0xed, 0x9a, 0x44, 0x64, 0x06, 0x5e, 0x2d, 0x48, 0x44, 0xee, 0x26, 0x22, 0x4f, 0x10, 0x91, 0x0e, 0x22, 0x31, 0x8d, 0x28, 0x4c, 0x25, 0x0a, 0xf5, 0xf7, 0x14, 0x8e, 0x91, 0x95, 0x41, 0x22, 0xb0, 0x6e, 0xf8, 0xba, 0x1f, 0xe9, 0x44, 0xa1, 0x4d, 0x34, 0xc5, 0x83, 0x1d, 0x61, 0x64, 0x3e, 0x12, 0x13, 0xa5, 0x64, 0x74, 0xbe, 0x65, 0x64, 0xf6, 0x32, 0x07, 0xf7, 0x98, 0x6e, 0x94, 0x25, 0xa6, 0x9b, 0xd0, 0xec, 0x81, 0x72, 0x39, 0xb3, 0x77, 0x8a, 0xd9, 0x3b, 0xc5, 0xec, 0xe5, 0x33, 0x6a, 0x67, 0x19, 0xb5, 0x1c, 0x46, 0xed, 0x0b, 0x46, 0x6d, 0x19, 0xa3, 0x96, 0xa3, 0xac, 0x24, 0x12, + 0xab, 0x46, 0xad, 0x05, 0xa3, 0x96, 0xc9, 0xa8, 0xb5, 0x63, 0xd4, 0x1a, 0x33, 0x6a, 0x8d, 0x89, 0x8b, 0x68, 0xe3, 0x5b, 0x18, 0xfa, 0x37, 0x30, 0xba, 0x8a, 0x0e, 0xea, 0xc3, 0x32, 0x4d, 0x9d, 0x23, 0x7f, 0x12, 0x56, 0xb6, 0x5a, 0xc6, 0x5c, 0x54, 0x9a, 0xf4, 0xdf, 0x1b, 0x7b, 0x53, 0x7a, 0xf4, 0x33, 0x90, 0xfa, 0x99, 0x54, 0x66, 0xb6, 0x94, 0xb5, 0x7a, 0x58, 0x83, 0x89, 0x35, 0x28, 0xa2, 0xf6, 0xc5, 0x33, 0xb6, 0xa6, 0x7b, 0x45, 0x6d, 0xd3, 0x70, 0x31, 0xd0, 0x34, 0x52, 0xf4, 0xaf, 0x7e, 0xf6, 0x36, 0xf2, 0x03, 0x71, 0x53, 0xe4, 0x22, 0x71, 0x73, 0xe4, 0x3f, 0x44, 0xef, 0xa8, 0xee, 0x22, 0x36, 0xaa, 0x07, 0x54, 0x3f, 0xa3, 0x6b, 0xbd, 0xd2, 0x39, 0x5f, 0x5e, 0xd1, 0x8a, 0x57, 0x34, 0xff, 0xc3, 0x33, 0x63, 0x2f, 0x6d, 0x6b, 0xb8, 0x88, 0x63, 0x3b, 0x57, 0x5f, 0xb6, + 0x9d, 0x5a, 0xbc, 0xaa, 0x3e, 0xaf, 0xaa, 0xf3, 0x87, 0x57, 0xc5, 0x11, 0x05, 0x7e, 0xa2, 0xe0, 0x0c, 0x51, 0xe0, 0x22, 0x0a, 0x4e, 0x12, 0x05, 0xa7, 0x88, 0x02, 0x27, 0x51, 0x90, 0x8d, 0x57, 0x4c, 0x0f, 0xfb, 0xc4, 0x33, 0x44, 0x42, 0x36, 0x91, 0x70, 0x86, 0x48, 0xf0, 0x10, 0x09, 0xd9, 0x44, 0x81, 0x8b, 0x2d, 0xb5, 0x63, 0x4b, 0x6d, 0x88, 0x86, 0x33, 0x44, 0x43, 0x21, 0xd1, 0x90, 0xcd, 0x96, 0x9a, 0xb3, 0xa5, 0x5e, 0x6c, 0xa9, 0x27, 0x91, 0x61, 0x26, 0x1a, 0xa2, 0x98, 0xfd, 0x28, 0x66, 0xff, 0x66, 0x66, 0xdf, 0x6c, 0x5c, 0x2b, 0xfd, 0x8a, 0x57, 0x82, 0xbc, 0xb8, 0xb6, 0x2b, 0x5d, 0x11, 0xb2, 0xfa, 0x5a, 0xff, 0x70, 0x75, 0x48, 0x6b, 0xb5, 0xa3, 0x8e, 0xe5, 0xd5, 0xea, 0x5f, 0x9e, 0x1b, 0xaf, 0x3e, 0x3e, 0xdd, 0x78, 0x66, 0xe7, 0xcb, 0xc6, 0xa7, 0x3d, 0xeb, 0x6f, + 0xcf, 0xfa, 0xdb, 0x5d, 0x76, 0x46, 0xfd, 0xe2, 0xab, 0x06, 0xd2, 0x43, 0xdc, 0xcb, 0x36, 0x86, 0x8b, 0x5a, 0xbc, 0x3a, 0xe6, 0xb2, 0x57, 0xd7, 0xe6, 0xd5, 0x35, 0x79, 0x75, 0xec, 0x5f, 0x6e, 0xd3, 0x38, 0xb6, 0xcb, 0x5e, 0xf5, 0xfb, 0x48, 0x55, 0x7f, 0xd5, 0x55, 0xbc, 0x2a, 0x83, 0x88, 0x76, 0x1b, 0xfa, 0xde, 0x13, 0x7d, 0x1f, 0xc8, 0xf2, 0x0e, 0x11, 0xc3, 0xf6, 0x1b, 0xb2, 0xa6, 0xc1, 0xac, 0xe9, 0x4e, 0xd6, 0xa4, 0xab, 0x4a, 0x0e, 0x6b, 0x3a, 0xc9, 0x9a, 0x6e, 0x63, 0x4d, 0x83, 0x59, 0xd3, 0x9d, 0x44, 0x51, 0x5d, 0xa2, 0xa8, 0x6e, 0x94, 0x7e, 0x85, 0xb8, 0x95, 0x28, 0x5d, 0x13, 0x2a, 0x53, 0x53, 0x29, 0xd5, 0xb6, 0xf2, 0x02, 0x51, 0x1c, 0x69, 0x5c, 0x85, 0x2b, 0x57, 0x94, 0xa3, 0xd4, 0x15, 0x64, 0x5d, 0x8c, 0x71, 0x4d, 0xf3, 0x53, 0xc6, 0x35, 0x1c, 0xab, 0x5f, + 0xd3, 0xbc, 0xbe, 0xdc, 0x88, 0x9f, 0xc9, 0xc3, 0xcf, 0x64, 0x5c, 0xba, 0xae, 0x79, 0x23, 0xb9, 0x09, 0x3f, 0xb3, 0xe9, 0xb2, 0xeb, 0x9b, 0x67, 0x85, 0xaf, 0x6f, 0xae, 0x5f, 0x05, 0x27, 0xab, 0xda, 0xf5, 0xcd, 0x3d, 0xd5, 0xae, 0x6f, 0xee, 0xaf, 0x76, 0x7d, 0xf3, 0x13, 0x78, 0x99, 0xac, 0xf0, 0xf5, 0xcd, 0x53, 0x8c, 0xa3, 0xa3, 0x92, 0x99, 0x7a, 0xc3, 0xad, 0xec, 0x4f, 0x3f, 0xe0, 0x68, 0x23, 0xec, 0x32, 0x18, 0x91, 0x05, 0x7f, 0xbe, 0xee, 0x79, 0xde, 0x65, 0xd7, 0x3d, 0xd7, 0x2e, 0xbb, 0xee, 0x79, 0x96, 0x71, 0x55, 0x1c, 0x3f, 0x3e, 0x46, 0x83, 0x52, 0xd6, 0x55, 0x75, 0x75, 0x1c, 0x0f, 0xfe, 0xc5, 0x73, 0xe9, 0xfa, 0xe7, 0x21, 0xb9, 0xa9, 0xda, 0x35, 0xd0, 0x3d, 0xe1, 0x6b, 0xa0, 0xbb, 0xf0, 0x2c, 0x59, 0xd5, 0xae, 0x81, 0x5e, 0x84, 0x67, 0x29, 0xc2, 0xb3, 0x64, + 0xe1, 0x59, 0x36, 0x19, 0xd7, 0x41, 0xff, 0xe3, 0xb5, 0xcf, 0xb3, 0xf0, 0x25, 0x59, 0x78, 0x92, 0x2c, 0x23, 0xf3, 0x8b, 0x64, 0x06, 0x7e, 0x24, 0xeb, 0x0f, 0x7e, 0xc4, 0x8b, 0xc6, 0x7c, 0x29, 0x33, 0xf0, 0x1f, 0x19, 0xf8, 0x8f, 0x0c, 0xfc, 0x47, 0x06, 0xfe, 0x23, 0x0b, 0xff, 0x91, 0x85, 0xff, 0xc8, 0xc2, 0x7f, 0x64, 0xe1, 0x3f, 0xb2, 0xf0, 0x1f, 0x59, 0xc6, 0xf5, 0xcf, 0x3b, 0xc9, 0x92, 0xf0, 0x35, 0xd0, 0xb3, 0xf0, 0x1d, 0x59, 0x97, 0xae, 0x81, 0xfe, 0xbe, 0x3c, 0xae, 0xea, 0xe7, 0x3d, 0x4e, 0xc9, 0x22, 0x7c, 0x87, 0x5f, 0xd5, 0x3d, 0x8b, 0x0f, 0x38, 0x4e, 0x7c, 0x87, 0x3f, 0x7c, 0x1d, 0xf4, 0x3c, 0x7c, 0x45, 0x1e, 0xbe, 0x22, 0xe3, 0x2f, 0xae, 0x83, 0xee, 0x09, 0x5f, 0x07, 0xfd, 0x04, 0x9e, 0xc2, 0x85, 0xa7, 0xc8, 0x32, 0xed, 0x10, 0x11, 0x44, 0x68, 0x24, 0x58, + 0x50, 0x7b, 0x05, 0x6a, 0x42, 0x3d, 0xaa, 0xc1, 0x35, 0x2c, 0xe3, 0xa0, 0x09, 0x34, 0x87, 0x96, 0xd4, 0xfa, 0x56, 0x70, 0x1d, 0xda, 0x77, 0x3d, 0x15, 0xa1, 0x03, 0x8f, 0x75, 0x94, 0xa9, 0xa2, 0x13, 0x74, 0x86, 0x2e, 0xd4, 0xa7, 0xae, 0x54, 0x90, 0x6e, 0x70, 0x03, 0x74, 0x87, 0x1e, 0x10, 0x0f, 0x7a, 0x3c, 0xf7, 0x66, 0xd9, 0x07, 0xc6, 0xf3, 0xbc, 0x09, 0x30, 0x11, 0x1e, 0x46, 0x4f, 0x26, 0x41, 0x38, 0xd6, 0xc5, 0xa3, 0xdc, 0x7e, 0x0c, 0xa6, 0xf0, 0xb7, 0xc7, 0xd1, 0xa1, 0x27, 0x58, 0x3e, 0x09, 0x4f, 0x71, 0x7b, 0x2a, 0x4c, 0x83, 0xa7, 0x59, 0xc7, 0x33, 0xf0, 0x2c, 0x3c, 0xc7, 0xfd, 0xe9, 0xfc, 0xfd, 0x79, 0x96, 0x2f, 0xb2, 0x9c, 0x4d, 0x7d, 0x7c, 0x19, 0x5e, 0x81, 0x57, 0xe1, 0x35, 0x78, 0x1d, 0xe6, 0xc0, 0x5c, 0x78, 0x03, 0xe6, 0xc1, 0x7c, 0x74, 0xee, 0x4d, 0x58, + 0x80, 0x96, 0xbd, 0x45, 0x7f, 0xbb, 0x10, 0xbd, 0x7b, 0x9b, 0x88, 0xad, 0xca, 0x35, 0x97, 0x29, 0x1f, 0x47, 0xe4, 0x26, 0xfa, 0x3d, 0xf2, 0xa4, 0x79, 0x00, 0x55, 0xe3, 0x6e, 0xb8, 0x17, 0x1e, 0x80, 0xf1, 0x30, 0x19, 0xe8, 0x91, 0xcc, 0xcf, 0x50, 0x55, 0x9e, 0x85, 0xe9, 0x38, 0x8b, 0x99, 0xf2, 0x94, 0x79, 0x16, 0xb7, 0x67, 0x1b, 0xbf, 0x68, 0x14, 0x32, 0xb3, 0x7d, 0x23, 0x47, 0x17, 0x53, 0x1f, 0x3e, 0xe1, 0xf1, 0x25, 0xb0, 0x96, 0xc7, 0xbe, 0x81, 0x8b, 0x79, 0x9b, 0x80, 0xeb, 0xd5, 0x23, 0x07, 0x25, 0x8c, 0x4a, 0x96, 0x67, 0xf4, 0x9c, 0x8d, 0xca, 0x92, 0x67, 0xa3, 0x4e, 0xcb, 0xbc, 0xa8, 0x42, 0x79, 0x32, 0xba, 0x95, 0x3c, 0x15, 0xdd, 0x5a, 0xe6, 0x47, 0xb7, 0x95, 0xa1, 0xe8, 0x0a, 0x99, 0x67, 0x69, 0x2f, 0xb3, 0x2d, 0x1d, 0xa0, 0x23, 0x74, 0x82, 0xce, 0xd0, 0x05, + 0xba, 0x42, 0x37, 0xb8, 0x01, 0x7a, 0xc2, 0x8d, 0x70, 0x13, 0xa0, 0x3e, 0x96, 0xde, 0xd0, 0x07, 0xfa, 0xc2, 0xcd, 0x70, 0x0b, 0xdc, 0x0a, 0xfd, 0xa0, 0x3f, 0xdc, 0x06, 0x03, 0xe0, 0x76, 0x18, 0x08, 0x77, 0xc0, 0x9d, 0x30, 0x11, 0x9e, 0x94, 0x67, 0x2d, 0x1c, 0xa7, 0x65, 0x2a, 0x4c, 0x83, 0xe7, 0x60, 0x05, 0xac, 0x84, 0x55, 0xf0, 0x35, 0xac, 0x86, 0x35, 0xb0, 0x16, 0x0a, 0xa4, 0xc3, 0x52, 0x28, 0x1d, 0x4a, 0xac, 0x3c, 0xab, 0x10, 0x47, 0x4a, 0x2d, 0xa8, 0x0d, 0x75, 0xa0, 0x2e, 0xd4, 0x83, 0xfa, 0x70, 0x35, 0x34, 0x82, 0xc6, 0x40, 0x9c, 0x29, 0xc4, 0x99, 0x42, 0x9c, 0x29, 0x4d, 0xa1, 0x19, 0x10, 0x6f, 0x4a, 0x0b, 0x68, 0x09, 0xad, 0xa0, 0x35, 0xb4, 0x81, 0xb6, 0x70, 0x2d, 0xb4, 0x83, 0xeb, 0xe0, 0x7a, 0xe8, 0x0e, 0x03, 0x65, 0xb9, 0x72, 0x07, 0xdc, 0x09, 0x83, 0xe0, + 0x6e, 0x18, 0x0c, 0x43, 0xe0, 0x1e, 0x18, 0x0a, 0xcf, 0xe1, 0x35, 0xa7, 0x43, 0x02, 0x3c, 0x0f, 0x2f, 0xc0, 0x0c, 0xc0, 0x75, 0x29, 0xb8, 0x2e, 0xe5, 0x03, 0xd6, 0xf3, 0x21, 0x2c, 0x82, 0x7f, 0xc0, 0x3f, 0xe1, 0x23, 0xf8, 0x58, 0xa6, 0x2a, 0x9f, 0xc0, 0x12, 0x58, 0x0a, 0xcb, 0xe0, 0x53, 0xf8, 0x0c, 0x3e, 0x87, 0x2f, 0xe0, 0x2b, 0x58, 0x0e, 0x2b, 0x00, 0xcd, 0x55, 0x56, 0xb1, 0xfc, 0x1a, 0x56, 0xc3, 0x1a, 0x58, 0x0b, 0xdf, 0xc0, 0x3a, 0x58, 0x0f, 0xdf, 0xc2, 0x77, 0xf0, 0x3d, 0xfc, 0x00, 0x1b, 0x60, 0x23, 0xfc, 0x02, 0xbf, 0xc2, 0x26, 0xd8, 0x0c, 0x5b, 0xe9, 0x62, 0xb6, 0x01, 0x1d, 0x9e, 0xb2, 0x9d, 0xf5, 0xee, 0x80, 0x9d, 0x90, 0x0c, 0x7b, 0xe0, 0x10, 0x1c, 0x86, 0x54, 0x38, 0x02, 0x47, 0xc1, 0x01, 0xc7, 0xe0, 0x38, 0x64, 0xc2, 0x09, 0x38, 0x09, 0x59, 0x90, 0x0d, + 0x39, 0x70, 0x0a, 0x88, 0x31, 0x25, 0x17, 0x9c, 0xe0, 0x82, 0x7c, 0xd0, 0x3f, 0xed, 0x41, 0xdc, 0x2b, 0x05, 0xe0, 0xa5, 0x76, 0xd4, 0x96, 0xf9, 0x6a, 0x1d, 0xa8, 0x0b, 0xf5, 0x80, 0x38, 0x55, 0x1b, 0xc0, 0x55, 0xf2, 0x8c, 0xda, 0x08, 0x1a, 0x43, 0x1c, 0x5c, 0xac, 0x31, 0xcd, 0xb8, 0xdd, 0x1c, 0x5a, 0x40, 0x4b, 0x68, 0x03, 0x7a, 0xdd, 0x69, 0xc7, 0xb2, 0x03, 0x74, 0x84, 0xce, 0x40, 0x1d, 0x52, 0x6f, 0x60, 0x3d, 0xdd, 0x41, 0xbf, 0x6e, 0x71, 0x3c, 0xf4, 0x84, 0x5e, 0xd0, 0x1b, 0xfa, 0x40, 0x5f, 0xb8, 0x19, 0x6e, 0x81, 0x5b, 0xa1, 0x1f, 0xf4, 0x87, 0x01, 0xf2, 0x94, 0x7a, 0x3b, 0x0c, 0x84, 0x41, 0x70, 0x17, 0xdc, 0x0d, 0x83, 0x61, 0x08, 0xdc, 0x03, 0x43, 0x61, 0x18, 0xdc, 0x0b, 0xc3, 0x61, 0x04, 0xc7, 0x32, 0x12, 0x46, 0xc1, 0xfd, 0x30, 0x1a, 0xc6, 0xc0, 0x58, 0x78, + 0x00, 0xc6, 0xc3, 0x04, 0x98, 0x08, 0x0f, 0xc3, 0x24, 0x98, 0x0c, 0x8f, 0xc0, 0xa3, 0xf0, 0x18, 0x4c, 0x81, 0xc7, 0xe1, 0x09, 0x78, 0x12, 0x9e, 0x82, 0xa9, 0x30, 0x0d, 0x66, 0xc2, 0x2c, 0x78, 0x11, 0xc8, 0x7f, 0xf5, 0x65, 0x78, 0x1d, 0xe6, 0xc0, 0x5c, 0x78, 0x03, 0xe6, 0xc1, 0x7c, 0x78, 0x13, 0xde, 0x82, 0x85, 0xf0, 0x91, 0x0c, 0xa9, 0x1f, 0xc3, 0x27, 0xb0, 0x04, 0x96, 0xc2, 0x32, 0xf8, 0x14, 0x3e, 0x83, 0xcf, 0xe1, 0x0b, 0xf8, 0x12, 0xbe, 0x82, 0xe5, 0xb0, 0x02, 0x56, 0xc2, 0x2a, 0xf8, 0x1a, 0x56, 0xc3, 0x1a, 0x40, 0x5b, 0x54, 0xb4, 0x45, 0x5d, 0x07, 0xeb, 0xe1, 0x5b, 0xf8, 0x0e, 0xbe, 0x87, 0x1f, 0x60, 0x03, 0xfc, 0x08, 0x3f, 0xc1, 0xcf, 0xb0, 0x11, 0x7e, 0x81, 0x5f, 0x61, 0x13, 0x6c, 0x86, 0x2d, 0xb0, 0x15, 0xb6, 0xc1, 0x6f, 0xf2, 0xa4, 0x9a, 0x04, 0xdb, 0x61, + 0x07, 0xec, 0x84, 0x64, 0xd8, 0x05, 0xbb, 0x61, 0x0f, 0xec, 0x85, 0x7d, 0x60, 0x83, 0xfd, 0x70, 0x00, 0xec, 0x70, 0x10, 0x0e, 0xc1, 0x61, 0x48, 0x85, 0x23, 0xe0, 0x00, 0xfc, 0xa5, 0x7a, 0x1c, 0x7f, 0x9c, 0x06, 0x19, 0x38, 0x9e, 0x4c, 0x38, 0x01, 0x27, 0x21, 0x0b, 0xb2, 0x21, 0x07, 0x4e, 0xe3, 0xf0, 0x72, 0xc1, 0xc9, 0xf3, 0xf0, 0x9b, 0x6a, 0x3e, 0x9c, 0x81, 0x02, 0xf0, 0x42, 0x21, 0x14, 0x41, 0x31, 0x94, 0x50, 0xd5, 0xd2, 0xe5, 0x29, 0x6b, 0x81, 0x3c, 0x6b, 0xf5, 0x42, 0x21, 0x14, 0x41, 0x31, 0x94, 0x51, 0x0d, 0x03, 0x10, 0x84, 0xb3, 0xa0, 0xff, 0x56, 0x4b, 0x85, 0x3c, 0x69, 0xad, 0x84, 0x10, 0x9c, 0x83, 0xf3, 0xd4, 0x81, 0xcd, 0x62, 0x9f, 0x5c, 0x4f, 0x4f, 0xf4, 0x85, 0x48, 0x95, 0xbf, 0xe1, 0x86, 0x7f, 0xc4, 0x0d, 0xcf, 0xc4, 0x0d, 0x6f, 0x11, 0xc7, 0xe9, 0x62, + 0xd3, 0x64, 0x9a, 0x48, 0xa7, 0xcf, 0xcb, 0x90, 0x1b, 0x70, 0xc5, 0xef, 0x89, 0x13, 0x54, 0xcb, 0x6c, 0xb9, 0x04, 0x57, 0x5c, 0x84, 0x2b, 0xde, 0x2e, 0xf2, 0xe8, 0x09, 0x9c, 0xe0, 0x92, 0x0b, 0x44, 0xbe, 0xdc, 0x2f, 0xce, 0x70, 0xdb, 0xcd, 0xfa, 0x3c, 0x54, 0xbd, 0x02, 0x3a, 0x61, 0xaf, 0x4c, 0x32, 0xf5, 0x97, 0x7b, 0x4c, 0x03, 0x20, 0xfc, 0xab, 0x02, 0x11, 0xcb, 0xe5, 0x7a, 0x5c, 0xf3, 0x8f, 0x11, 0x2b, 0xe5, 0x4f, 0xb8, 0xe6, 0xf7, 0x22, 0xd6, 0xb1, 0xfc, 0x16, 0x36, 0xc8, 0x9f, 0x22, 0xe7, 0xca, 0x3d, 0x91, 0xf3, 0x60, 0xbe, 0xfc, 0x39, 0x72, 0x81, 0xfc, 0x19, 0x17, 0xdd, 0x22, 0xea, 0xac, 0xfc, 0x29, 0xaa, 0x42, 0xae, 0xb7, 0x3c, 0x2b, 0xd7, 0x2b, 0x0d, 0xa0, 0x21, 0xb4, 0x87, 0x0e, 0xa8, 0x6d, 0x27, 0x96, 0x9d, 0x59, 0x76, 0x81, 0xae, 0xdc, 0xee, 0x06, 0x29, + 0xf2, 0x27, 0x75, 0x1c, 0x3c, 0x08, 0x4f, 0xc3, 0x33, 0xf0, 0x2c, 0x3c, 0x07, 0xd3, 0x21, 0x01, 0x9e, 0x87, 0x17, 0xe4, 0x4f, 0xd6, 0xce, 0xf2, 0x37, 0x6b, 0x17, 0xe8, 0x0a, 0xdd, 0xe0, 0x06, 0xe8, 0x0e, 0x3d, 0x20, 0x1e, 0x7a, 0xc2, 0x8d, 0x70, 0x13, 0xf4, 0x82, 0xde, 0xd0, 0x07, 0xfa, 0xc2, 0xcd, 0x70, 0x0b, 0xdc, 0x0a, 0xfd, 0xa0, 0x3f, 0xdc, 0x06, 0x03, 0xe0, 0x76, 0x18, 0x08, 0x77, 0xc0, 0x9d, 0x30, 0x48, 0x9e, 0xb0, 0xde, 0x05, 0x77, 0xc3, 0x60, 0x18, 0x02, 0xf7, 0xc0, 0x50, 0x18, 0x06, 0xf7, 0x02, 0x9e, 0xca, 0x3a, 0x02, 0xf0, 0x54, 0xd6, 0x51, 0x70, 0x3f, 0x8c, 0x86, 0x31, 0x30, 0x16, 0x1e, 0x80, 0x71, 0xf0, 0x20, 0xe0, 0x91, 0xac, 0xe3, 0x61, 0x02, 0x4c, 0x84, 0x87, 0x61, 0x12, 0x4c, 0x86, 0x47, 0xe0, 0x51, 0x78, 0x0c, 0xa6, 0xc0, 0xe3, 0xf0, 0x04, 0x3c, + 0x29, 0xb3, 0xe9, 0x48, 0xda, 0x5a, 0x9f, 0x66, 0xf9, 0x8c, 0xa8, 0x67, 0x7d, 0x56, 0x34, 0xb5, 0x3e, 0xc7, 0xed, 0xe9, 0x90, 0x20, 0x6a, 0xd0, 0xab, 0x36, 0xb5, 0xbe, 0xc0, 0xed, 0x19, 0x3c, 0x67, 0xa6, 0x78, 0x90, 0x8e, 0xa5, 0x85, 0xf5, 0x45, 0xee, 0x57, 0xbd, 0x3f, 0x52, 0x69, 0x7d, 0x99, 0xde, 0xf5, 0x15, 0xd1, 0xd7, 0x3a, 0x47, 0xee, 0xb7, 0xce, 0x85, 0x37, 0x60, 0x1e, 0xcc, 0x87, 0x37, 0x61, 0x01, 0xbc, 0x05, 0x0b, 0xe1, 0x6d, 0x78, 0x07, 0xde, 0x85, 0xf7, 0xe0, 0x7d, 0x48, 0x84, 0x0f, 0xe0, 0x43, 0x58, 0x04, 0x8b, 0xe1, 0x23, 0x99, 0x64, 0xfd, 0x18, 0x3e, 0x81, 0x25, 0xb0, 0x14, 0x96, 0xc1, 0xa7, 0xf0, 0x19, 0x7c, 0x0e, 0x5f, 0xc0, 0x97, 0xf0, 0x15, 0x2c, 0x87, 0x15, 0xb0, 0x12, 0x56, 0xc1, 0xd7, 0xb0, 0x1a, 0xd6, 0xc0, 0x5a, 0xf8, 0x06, 0xd6, 0xc1, 0x7a, + 0xf8, 0x16, 0x36, 0xc9, 0xf5, 0xc6, 0xb7, 0xf1, 0xb3, 0xe9, 0x97, 0x1b, 0xe2, 0xc5, 0x7e, 0xc5, 0x6b, 0x95, 0xe3, 0xa1, 0xec, 0xa6, 0x89, 0xf2, 0xb8, 0x69, 0x92, 0x4c, 0x33, 0x4d, 0x16, 0x35, 0xe9, 0x8e, 0xf3, 0x8c, 0x4f, 0x9f, 0xcf, 0x17, 0x5d, 0xf0, 0x2d, 0x1f, 0xe0, 0x4f, 0x4e, 0xe0, 0x47, 0xb6, 0x2a, 0x2b, 0xc5, 0x8d, 0xd4, 0x8d, 0x10, 0x1d, 0x72, 0x3f, 0x7a, 0xda, 0x9a, 0x74, 0xc5, 0xb1, 0x74, 0xc5, 0x5d, 0xe9, 0x8a, 0xef, 0x41, 0x2b, 0x35, 0x34, 0xcf, 0x6e, 0xac, 0xf9, 0x3f, 0xff, 0x8d, 0x8e, 0x9a, 0x7f, 0xd5, 0x49, 0x5e, 0xa9, 0x83, 0xfc, 0x43, 0xd7, 0x78, 0xd5, 0xff, 0xa4, 0xeb, 0xad, 0xde, 0xed, 0x5e, 0xb1, 0xc3, 0xfd, 0x77, 0x3e, 0xa7, 0x75, 0x85, 0x0e, 0xef, 0x0f, 0x1d, 0x5d, 0xd5, 0x99, 0x84, 0x2b, 0x77, 0x70, 0x97, 0xff, 0x6a, 0xc2, 0xa5, 0xdf, 0x18, + 0xf8, 0xbf, 0xe4, 0x53, 0x7b, 0xff, 0x67, 0x5c, 0x77, 0xf6, 0x3f, 0xff, 0xcd, 0xac, 0x7a, 0x64, 0xd8, 0x3e, 0xe3, 0xfc, 0x6f, 0x2f, 0xb9, 0x46, 0x4c, 0x14, 0x51, 0x44, 0x50, 0x90, 0xad, 0xdd, 0xc6, 0xd6, 0x56, 0xe9, 0xe7, 0xa2, 0x4c, 0xe3, 0x8d, 0x5f, 0x8b, 0xd6, 0xcf, 0x47, 0x55, 0xb0, 0x95, 0x53, 0x17, 0x3f, 0x1d, 0x1b, 0x3e, 0xab, 0x55, 0x87, 0xe8, 0xe9, 0x78, 0xe9, 0x1d, 0xc4, 0x9e, 0xc4, 0xca, 0x4a, 0x62, 0x65, 0x19, 0x31, 0xe2, 0x23, 0x46, 0x02, 0xc4, 0xc8, 0x36, 0x62, 0x24, 0x35, 0x1c, 0x23, 0x3f, 0x85, 0xbf, 0xeb, 0x55, 0x40, 0x6c, 0xa4, 0x10, 0x1b, 0x95, 0xd5, 0xb7, 0x44, 0x4c, 0x04, 0x88, 0x89, 0x8a, 0x8b, 0x5b, 0x20, 0x0f, 0x1a, 0x12, 0x13, 0xcb, 0x88, 0x89, 0x65, 0xc4, 0xc4, 0x32, 0x62, 0x22, 0x83, 0x98, 0xc8, 0x20, 0x26, 0x96, 0x11, 0x13, 0xcb, 0x88, + 0x89, 0x0c, 0x62, 0x22, 0x83, 0x98, 0x58, 0x46, 0x4c, 0x2c, 0x23, 0x26, 0x96, 0x11, 0x13, 0xcb, 0x88, 0x89, 0x65, 0xc4, 0xc4, 0x32, 0x62, 0x62, 0x19, 0x31, 0x91, 0x41, 0x4c, 0x64, 0x10, 0x13, 0xcb, 0x88, 0x89, 0x65, 0xc4, 0x44, 0x06, 0x31, 0x91, 0x41, 0x4c, 0x2c, 0x23, 0x1e, 0xdc, 0xc4, 0x43, 0x88, 0x78, 0x08, 0x11, 0x0f, 0x21, 0xe2, 0x21, 0x44, 0xde, 0xd5, 0x41, 0xbf, 0xa3, 0xc9, 0xbd, 0x3a, 0xe4, 0xde, 0x7d, 0xe4, 0x5e, 0x43, 0xeb, 0x6c, 0xd1, 0xd8, 0xfa, 0x32, 0x31, 0xd2, 0x88, 0x18, 0x99, 0x1e, 0xfe, 0xc4, 0x71, 0x90, 0xf8, 0x38, 0x4c, 0x7c, 0xdc, 0x47, 0x7c, 0xec, 0x35, 0x75, 0x16, 0x8d, 0xfe, 0x70, 0x24, 0x64, 0x07, 0xf1, 0x10, 0x24, 0x16, 0x16, 0x13, 0x0b, 0x0e, 0xe2, 0x60, 0x20, 0x71, 0x50, 0x49, 0x0c, 0x1c, 0xaf, 0xf6, 0xe9, 0xe2, 0x87, 0x98, 0xff, 0x07, + 0xe9, 0xd0, 0xcf, 0x12, 0x03, 0x6e, 0x62, 0xa0, 0x82, 0x18, 0xa8, 0x14, 0x8d, 0x89, 0x81, 0x72, 0x62, 0xa0, 0x9c, 0x59, 0x29, 0x27, 0x06, 0xd2, 0x99, 0x99, 0xcd, 0xc4, 0xc0, 0x09, 0x5d, 0x5d, 0x8d, 0x2b, 0x16, 0xe8, 0x57, 0xe9, 0x78, 0x0c, 0x2d, 0x9b, 0x2f, 0xda, 0x85, 0xcf, 0xd8, 0x16, 0x32, 0x33, 0xa7, 0x99, 0xf3, 0x72, 0xfd, 0xac, 0x2d, 0xf3, 0x5e, 0xce, 0xbc, 0x97, 0x33, 0xef, 0xe5, 0xcc, 0x7b, 0x39, 0xf3, 0x5e, 0xce, 0xbc, 0x97, 0x33, 0xef, 0xe5, 0xe4, 0xfc, 0x79, 0x66, 0xcc, 0xa4, 0x2b, 0xac, 0x78, 0x91, 0xe3, 0xa9, 0xe4, 0x78, 0x4a, 0x8c, 0xab, 0xcd, 0xd6, 0x96, 0xe7, 0x2e, 0x7b, 0xf7, 0x29, 0xdb, 0xd4, 0x40, 0x1e, 0x33, 0x35, 0x44, 0xcb, 0x7f, 0x7f, 0xf7, 0xc9, 0x69, 0x6a, 0x0c, 0x7f, 0x7c, 0xf7, 0x29, 0x14, 0x7e, 0xf7, 0x69, 0xab, 0xa9, 0x25, 0xb7, 0xaf, + 0xfc, 0xce, 0xd3, 0xce, 0x6a, 0xef, 0x3c, 0xa5, 0x99, 0x3a, 0xf0, 0xbc, 0xaa, 0x77, 0x9e, 0xf4, 0x68, 0xd6, 0x22, 0x76, 0xcb, 0xca, 0x88, 0x34, 0x48, 0x87, 0x3f, 0xbf, 0xbb, 0x74, 0xec, 0xb2, 0x77, 0x97, 0xb6, 0x5e, 0xf6, 0xee, 0x52, 0x28, 0xc2, 0xc7, 0x63, 0xa5, 0xf2, 0x70, 0x44, 0x19, 0xcb, 0xea, 0xef, 0x2a, 0x55, 0xbd, 0x9b, 0xe4, 0xfc, 0xd3, 0xbb, 0x49, 0x66, 0x19, 0xaa, 0xf6, 0x2e, 0xd2, 0x61, 0xf2, 0xf6, 0x70, 0xa4, 0x85, 0xc7, 0xac, 0xe4, 0xb0, 0xfe, 0x2e, 0x92, 0xfe, 0xae, 0x91, 0xfe, 0x8e, 0x91, 0xfe, 0x6e, 0xd1, 0x7d, 0x3c, 0x3e, 0x1c, 0x46, 0xc2, 0x22, 0xe3, 0x1a, 0xe9, 0x69, 0x51, 0xc5, 0x32, 0x64, 0x89, 0x97, 0x6e, 0xcb, 0x78, 0x98, 0x00, 0x5e, 0x79, 0x4e, 0x99, 0x25, 0x2b, 0x95, 0x2f, 0xa5, 0xa6, 0xfc, 0x04, 0x3f, 0xc3, 0x16, 0xd8, 0x45, 0x54, 0xd9, + 0x21, 0x0d, 0x32, 0xc0, 0x43, 0xcd, 0xb3, 0x82, 0x7e, 0xee, 0xb9, 0x13, 0xb3, 0x51, 0xf5, 0x6e, 0x50, 0x48, 0x9d, 0x01, 0xbf, 0xbf, 0x1b, 0x94, 0x1f, 0x7e, 0x37, 0xe8, 0xb0, 0xea, 0x96, 0x07, 0x54, 0x0f, 0xf8, 0xc0, 0x0f, 0x9a, 0x3c, 0x10, 0x7e, 0xb7, 0xe7, 0x98, 0x75, 0x37, 0xec, 0x95, 0x69, 0xff, 0xc5, 0xbb, 0x3d, 0x69, 0x31, 0xf8, 0x6e, 0x61, 0x25, 0x96, 0xce, 0x11, 0x47, 0x21, 0xe2, 0xa8, 0x42, 0x1f, 0xf1, 0xf0, 0x39, 0xeb, 0x7d, 0xc6, 0xe7, 0x02, 0x16, 0x19, 0xef, 0x5f, 0xe4, 0xa0, 0xfa, 0xd1, 0x26, 0x41, 0x06, 0xbf, 0x80, 0x73, 0xdd, 0x45, 0x16, 0x27, 0xe0, 0x5e, 0x77, 0x88, 0xc3, 0xb8, 0xd1, 0x54, 0x19, 0x4b, 0x46, 0xaf, 0x22, 0xa3, 0x17, 0x50, 0xdd, 0x6e, 0x24, 0xab, 0x3f, 0xc1, 0xc5, 0x36, 0x30, 0x2a, 0x5c, 0xba, 0x9c, 0x4a, 0x76, 0xff, 0x93, 0x2a, 0x77, + 0x2f, 0x2e, 0xb6, 0x2f, 0x59, 0xee, 0x16, 0x59, 0xb2, 0x33, 0xd5, 0x60, 0x38, 0x99, 0xbe, 0x8a, 0x4a, 0xd0, 0x53, 0xe4, 0xca, 0x68, 0x1c, 0x6d, 0x2c, 0x8e, 0x36, 0x96, 0xaa, 0x77, 0x3f, 0x8e, 0x36, 0x1e, 0x47, 0x1b, 0x8b, 0x0a, 0xd4, 0xc0, 0xd1, 0x4e, 0x44, 0x09, 0xba, 0xe0, 0x68, 0xaf, 0x21, 0x87, 0x12, 0x4c, 0x37, 0xca, 0x25, 0xa6, 0x9b, 0xe4, 0x47, 0xa6, 0x5e, 0xf2, 0x79, 0x53, 0x6f, 0xb8, 0x55, 0xbe, 0x62, 0xea, 0x07, 0xfd, 0xe5, 0x04, 0xd3, 0x6d, 0xf2, 0x29, 0x1c, 0xef, 0x04, 0xd3, 0xed, 0x2c, 0x07, 0xca, 0x39, 0x38, 0xdf, 0xf9, 0x38, 0xdf, 0xf9, 0x38, 0xdf, 0x5d, 0xa8, 0xc7, 0x02, 0x9c, 0x6f, 0x2c, 0x0a, 0xd2, 0x89, 0x2a, 0x7a, 0x6f, 0xc4, 0x5a, 0x19, 0x1d, 0xf1, 0x0d, 0xac, 0xe3, 0xb1, 0x6f, 0x61, 0x83, 0x8c, 0xc5, 0x05, 0xbf, 0x83, 0x0b, 0x7e, 0x07, 0x17, + 0xfc, 0x46, 0xe4, 0x9b, 0x72, 0x2d, 0x4e, 0xf8, 0x8d, 0xc8, 0xb7, 0xe4, 0xda, 0xa8, 0x6c, 0x9c, 0xc0, 0x59, 0x19, 0x1b, 0x55, 0x2e, 0x3b, 0xe2, 0x88, 0x77, 0xa1, 0x3a, 0x09, 0xa8, 0x4e, 0x02, 0xaa, 0x93, 0x80, 0xea, 0xec, 0x43, 0x75, 0xf6, 0xa1, 0x3a, 0x09, 0xa8, 0x4e, 0x02, 0xaa, 0xb3, 0x0f, 0xd5, 0xd9, 0x87, 0xea, 0x24, 0xa0, 0x3a, 0x09, 0xa8, 0x4e, 0x02, 0xaa, 0x93, 0x80, 0xea, 0x24, 0xa0, 0x3a, 0x09, 0xa8, 0x4e, 0x02, 0xaa, 0xb3, 0x0f, 0xd5, 0xd9, 0x87, 0xea, 0x24, 0xa0, 0x3a, 0x09, 0xa8, 0xce, 0x3e, 0x54, 0x67, 0x1f, 0xaa, 0x93, 0x80, 0xdb, 0xde, 0x85, 0xdb, 0xde, 0x85, 0xdb, 0xde, 0x85, 0xdb, 0xde, 0x85, 0xdb, 0x4e, 0xc7, 0x6d, 0xef, 0xc2, 0x6d, 0xa7, 0xe3, 0xb6, 0xd3, 0x71, 0xdb, 0xbb, 0x70, 0xdb, 0xbb, 0x70, 0xdb, 0xb1, 0xea, 0x4d, 0xb2, 0x33, 0x8e, 0x3b, + 0x16, 0xc7, 0x1d, 0x8b, 0xe3, 0x8e, 0xc5, 0x71, 0xc7, 0xe2, 0xb8, 0x63, 0x71, 0xdc, 0xb1, 0x38, 0xee, 0x58, 0x1c, 0x77, 0x2c, 0x8e, 0x3b, 0x16, 0xc7, 0x1d, 0x6b, 0x6d, 0x2c, 0xb7, 0x5b, 0xe3, 0xa0, 0x89, 0x4c, 0xb7, 0x36, 0x85, 0x66, 0xd0, 0x9c, 0xfb, 0x2d, 0x58, 0xb6, 0x84, 0x56, 0xd0, 0x9a, 0xfb, 0x6d, 0xa0, 0x2d, 0x5c, 0x0b, 0xed, 0x78, 0xec, 0x3a, 0xb8, 0x1e, 0xda, 0x73, 0x9f, 0x7d, 0xb1, 0x76, 0x84, 0x4e, 0xd0, 0x99, 0x75, 0x76, 0x81, 0xae, 0xb2, 0x21, 0x0e, 0xbe, 0x05, 0x0e, 0x3e, 0x16, 0x07, 0x1f, 0x8b, 0x83, 0x6f, 0x88, 0x83, 0x6f, 0x87, 0x83, 0x8f, 0xc5, 0xc1, 0xc7, 0xe2, 0xe0, 0x63, 0x71, 0xf0, 0x3d, 0x70, 0xf0, 0xcd, 0x71, 0xf0, 0xed, 0x71, 0xf0, 0xb1, 0x38, 0xf8, 0x58, 0x1c, 0x7c, 0x43, 0x1c, 0x7c, 0x0b, 0x1c, 0x7c, 0x2c, 0x0e, 0x3e, 0x16, 0x07, 0xdf, + 0x10, 0x07, 0xdf, 0x0e, 0x07, 0x1f, 0x8b, 0x83, 0x8f, 0xc5, 0xc1, 0xc7, 0xe2, 0xe0, 0x7b, 0xe0, 0xe0, 0x1b, 0xe0, 0xe0, 0x1b, 0xe0, 0xe0, 0xdb, 0xe0, 0xe0, 0xbb, 0xe0, 0xe0, 0x1b, 0xe0, 0xe0, 0xdb, 0xe0, 0xe0, 0x7b, 0xe2, 0xe0, 0x1b, 0xe0, 0xe0, 0x1b, 0xe0, 0xe0, 0xa7, 0xe2, 0xe0, 0xa7, 0xe2, 0xe0, 0xa7, 0xe2, 0xe0, 0xa7, 0xe2, 0xe0, 0xa7, 0xe2, 0xe0, 0xa7, 0xe2, 0xe0, 0xa7, 0xe2, 0xe0, 0xa7, 0xe2, 0xe0, 0xa7, 0xe2, 0xe0, 0xa7, 0xfe, 0x3f, 0xe4, 0xbd, 0x07, 0x78, 0x14, 0x55, 0x1b, 0xf6, 0x7f, 0x66, 0x53, 0x76, 0x66, 0x37, 0xf4, 0x84, 0x24, 0x84, 0x34, 0x08, 0x49, 0x28, 0xd2, 0x09, 0x0a, 0xa1, 0x47, 0x44, 0x8a, 0x80, 0x58, 0xb0, 0x21, 0xf6, 0x8a, 0x85, 0xa2, 0xa8, 0xa8, 0xd8, 0x10, 0x7d, 0x01, 0x1b, 0x36, 0xb0, 0x22, 0x22, 0xa2, 0x08, 0x2f, 0x16, 0x34, 0xaf, 0x20, + 0x28, 0x04, 0x24, 0xb4, 0x44, 0x5c, 0x96, 0xb0, 0x10, 0x43, 0x12, 0xb2, 0x09, 0x0b, 0x84, 0xc0, 0x46, 0x08, 0xc8, 0xf9, 0xff, 0xe6, 0x64, 0x12, 0x37, 0x01, 0x6c, 0xff, 0xf7, 0xfd, 0xae, 0xef, 0xba, 0xbe, 0xcc, 0x75, 0xef, 0x4e, 0x76, 0x67, 0xcf, 0x9c, 0x73, 0x9f, 0xe7, 0x9c, 0xfb, 0x7e, 0x66, 0x67, 0x67, 0x70, 0xf0, 0x13, 0x70, 0xf0, 0x13, 0x70, 0xf0, 0x13, 0x70, 0xf0, 0x13, 0x70, 0xf0, 0x13, 0x70, 0xf0, 0x13, 0x70, 0xf0, 0x13, 0x70, 0xf0, 0x13, 0x70, 0xf0, 0x13, 0x70, 0xf0, 0x13, 0x70, 0xf0, 0x13, 0x70, 0xf0, 0x13, 0x70, 0xf0, 0x13, 0x70, 0xf0, 0x13, 0x70, 0xf0, 0xfd, 0x1d, 0x77, 0xcb, 0x63, 0x38, 0xf8, 0x17, 0x70, 0xf0, 0xa6, 0x02, 0x24, 0xe3, 0xe0, 0x5f, 0xc0, 0xc1, 0xbf, 0x80, 0x83, 0x8f, 0xc4, 0xc1, 0xb7, 0xc5, 0xc1, 0xbf, 0xe0, 0x78, 0x90, 0x6d, 0x94, 0x83, + 0xc7, 0xf3, 0x3d, 0x2c, 0x07, 0xe3, 0xe0, 0x7f, 0xc2, 0xc1, 0x7b, 0x50, 0x87, 0x8d, 0x38, 0xf8, 0x01, 0x8e, 0xe9, 0x32, 0xd4, 0xf1, 0x04, 0x78, 0x52, 0xa6, 0xe1, 0xe4, 0xd3, 0x70, 0xf2, 0x69, 0x38, 0xf9, 0x34, 0x9c, 0x7c, 0x1a, 0x4e, 0x3e, 0x0d, 0x27, 0x9f, 0x86, 0x93, 0x4f, 0xc3, 0xc9, 0xa7, 0xe1, 0xe4, 0xd3, 0x70, 0xf2, 0x69, 0x38, 0xf9, 0xbb, 0x70, 0xf2, 0x77, 0xe1, 0xe4, 0xd3, 0x70, 0xf2, 0x69, 0x38, 0xf9, 0x34, 0x9c, 0x7c, 0x1a, 0x4e, 0x3e, 0x0d, 0x27, 0x9f, 0x86, 0x93, 0x8f, 0xc1, 0xc9, 0xc7, 0xe0, 0xe4, 0x63, 0x70, 0xf2, 0x31, 0x38, 0xf9, 0x18, 0x9c, 0x7c, 0x0c, 0x4e, 0x3e, 0x06, 0x27, 0x1f, 0x83, 0x93, 0x8f, 0xc1, 0xc9, 0xc7, 0xe0, 0xe4, 0x63, 0x70, 0xf2, 0x31, 0x38, 0xf9, 0x18, 0x9c, 0x7c, 0x0c, 0x4e, 0x3e, 0x06, 0x27, 0x1f, 0x83, 0x93, 0x8f, 0xc1, 0xc9, + 0xc7, 0xe0, 0xe4, 0x63, 0x70, 0xf2, 0x31, 0x38, 0xf9, 0x18, 0x9c, 0x7c, 0x0c, 0x4e, 0x3e, 0x06, 0x27, 0x1f, 0x83, 0x93, 0x5f, 0x87, 0x93, 0x9f, 0x81, 0xfb, 0xdd, 0xe6, 0x7c, 0x49, 0x9e, 0x27, 0xda, 0xaa, 0x2b, 0xdf, 0x49, 0xb9, 0x1a, 0x65, 0xf8, 0x56, 0x6b, 0x88, 0x9b, 0x6f, 0x24, 0xa7, 0xa1, 0x0e, 0x8b, 0x50, 0x04, 0xf3, 0x1e, 0xe5, 0x57, 0x31, 0x6b, 0xb7, 0x40, 0xf1, 0x22, 0x71, 0xc5, 0xb7, 0xd6, 0x19, 0xb1, 0x8c, 0x48, 0x5c, 0xf2, 0x45, 0xa8, 0xde, 0x44, 0x14, 0xcf, 0x40, 0xf1, 0x06, 0xe0, 0x7c, 0xe6, 0xa1, 0x7a, 0x69, 0xa8, 0xdd, 0x08, 0x66, 0xc9, 0xa9, 0xfa, 0x50, 0x66, 0xc1, 0x2f, 0xe4, 0x70, 0x66, 0xc8, 0x51, 0xcc, 0x88, 0x6d, 0x99, 0x11, 0xe3, 0x99, 0x0d, 0xb7, 0x1a, 0x49, 0x22, 0x99, 0x59, 0x30, 0x91, 0xd9, 0x2f, 0x8e, 0xd9, 0x6f, 0x02, 0x4a, 0xd8, 0x85, + 0xd9, 0xed, 0x19, 0x66, 0xb2, 0xab, 0x99, 0xc5, 0x1e, 0x70, 0xec, 0x90, 0xab, 0x54, 0x0d, 0xbf, 0x95, 0x19, 0xcc, 0x68, 0x89, 0xe2, 0x3a, 0x2b, 0x33, 0xb8, 0x46, 0x0c, 0x92, 0x4f, 0x88, 0x0c, 0xf9, 0xac, 0x18, 0xca, 0xf3, 0xe5, 0xe0, 0x2a, 0x70, 0x03, 0x6e, 0xf8, 0x66, 0x99, 0x61, 0xce, 0x76, 0xe2, 0x61, 0xf9, 0x6e, 0xfd, 0x5a, 0xe2, 0x69, 0x36, 0x69, 0xe3, 0xe5, 0x62, 0xb2, 0x86, 0x22, 0xb2, 0x86, 0x22, 0x72, 0x15, 0x3b, 0xfe, 0xf7, 0x55, 0xfc, 0xef, 0x5c, 0xfc, 0xef, 0xcb, 0xcc, 0x8e, 0x43, 0x98, 0x1d, 0x53, 0x98, 0x1d, 0x23, 0xc8, 0x12, 0xbc, 0xcc, 0x8e, 0x1f, 0x86, 0x6c, 0x95, 0x4f, 0x84, 0x6c, 0x93, 0x4f, 0xe8, 0x6f, 0xc8, 0x0c, 0x7d, 0x9e, 0x1c, 0xa9, 0xcf, 0x97, 0x7d, 0xf5, 0xb7, 0x68, 0xc5, 0xdb, 0xb4, 0xe2, 0x1d, 0x79, 0x81, 0xbe, 0x50, 0x5e, 0x82, 0xb3, + 0x2e, 0xd1, 0x17, 0xf1, 0xfc, 0x11, 0x58, 0xcc, 0x36, 0x1f, 0xcb, 0x3b, 0xf4, 0x25, 0xe0, 0x13, 0xf0, 0x29, 0x58, 0xca, 0x76, 0x9f, 0xc9, 0x6b, 0xf4, 0xb5, 0xb2, 0xa3, 0xfe, 0xbd, 0xec, 0xac, 0x6f, 0x93, 0xb1, 0x7a, 0x8e, 0x4c, 0xd5, 0x73, 0x65, 0x7b, 0xfd, 0x27, 0xca, 0xf1, 0xc8, 0x48, 0x7d, 0x8f, 0x4c, 0x21, 0x37, 0x5a, 0xac, 0xe7, 0xf3, 0xfc, 0x0b, 0x28, 0xe0, 0xfd, 0x43, 0xec, 0xf7, 0x30, 0x28, 0x07, 0xa7, 0x81, 0x94, 0xfd, 0x0d, 0x21, 0xdb, 0x18, 0x41, 0x72, 0xa0, 0x61, 0x97, 0xa9, 0x46, 0x03, 0xd9, 0x8e, 0x5c, 0xaa, 0x1b, 0xae, 0x7e, 0x25, 0xbe, 0xac, 0x2f, 0xbe, 0xac, 0x03, 0xb3, 0x79, 0x32, 0x79, 0x55, 0x14, 0x79, 0xd5, 0x79, 0x2a, 0xaf, 0xea, 0x22, 0x06, 0x19, 0x97, 0xc9, 0xe6, 0xc6, 0x15, 0x32, 0xc1, 0x18, 0x0b, 0xd3, 0x37, 0xc0, 0xfc, 0x8d, 0xf2, 0x3d, + 0xe3, 0x26, 0x9e, 0x6f, 0x06, 0xb7, 0xf0, 0xfa, 0xad, 0xb2, 0xb5, 0xf1, 0xa4, 0x5c, 0x60, 0x3c, 0x2b, 0xc7, 0x1a, 0xb3, 0xe4, 0xdd, 0xc6, 0x6c, 0xf9, 0x8c, 0x71, 0x8c, 0xd8, 0x84, 0x79, 0x87, 0x5f, 0x36, 0x17, 0x4d, 0x2c, 0x1d, 0x31, 0xbf, 0x39, 0x2e, 0x80, 0xdd, 0x32, 0xd8, 0x2d, 0x80, 0xdd, 0x1d, 0xb0, 0xba, 0xdf, 0xca, 0x35, 0x3c, 0xb0, 0xb6, 0xd3, 0xd2, 0x14, 0x33, 0xd7, 0x28, 0xa0, 0x56, 0x05, 0x75, 0x72, 0x8d, 0x7e, 0xf4, 0xdd, 0xd7, 0x94, 0x54, 0x45, 0x49, 0x8f, 0xd0, 0x57, 0x4d, 0x28, 0xed, 0x15, 0x4a, 0x5a, 0x49, 0x49, 0x77, 0xd1, 0x4f, 0x05, 0xf4, 0xd3, 0x2f, 0x94, 0xf8, 0x98, 0x76, 0xb1, 0x30, 0xb4, 0xd1, 0xa2, 0x91, 0x36, 0x46, 0x34, 0xd3, 0x2e, 0x17, 0x8d, 0xe9, 0xb7, 0xf5, 0xf4, 0x5b, 0x36, 0xfd, 0x56, 0x46, 0xbf, 0x95, 0xd1, 0x6f, 0xad, 0xe8, 0xb7, + 0xef, 0xe8, 0xb7, 0x55, 0xf4, 0x5b, 0x36, 0xee, 0xa8, 0x84, 0x5a, 0xe4, 0x52, 0x8b, 0x99, 0xd4, 0xe2, 0x1e, 0x33, 0xe3, 0xa1, 0xff, 0xca, 0x54, 0x4d, 0xde, 0x90, 0x2f, 0xd1, 0x5f, 0x8f, 0xd3, 0x57, 0xd3, 0xc9, 0x3f, 0x63, 0xe8, 0x9b, 0x85, 0xf4, 0xcd, 0x42, 0xfa, 0x66, 0x21, 0x7d, 0xb3, 0x90, 0xbe, 0x99, 0x4e, 0xdf, 0xbc, 0x43, 0xbf, 0x3c, 0x49, 0xbf, 0xdc, 0x47, 0x9f, 0x3c, 0x4a, 0x3f, 0x54, 0xc1, 0xf3, 0x54, 0x78, 0x7e, 0x0d, 0x8e, 0x1f, 0x85, 0xe3, 0xcb, 0x68, 0x4d, 0x8e, 0xf5, 0x3d, 0x78, 0x02, 0x1c, 0x77, 0x81, 0xe3, 0x2e, 0x70, 0x3c, 0xc8, 0x68, 0x23, 0x1a, 0xc1, 0xf3, 0x1d, 0xb4, 0xb0, 0x8d, 0xca, 0x61, 0x6f, 0x95, 0x93, 0xe1, 0x72, 0x2f, 0x5c, 0x7e, 0x0a, 0x97, 0x4f, 0xc0, 0xe5, 0xf7, 0x70, 0xb9, 0x4e, 0x9c, 0x4f, 0xeb, 0x4b, 0xf1, 0x78, 0x15, 0x78, 0xbc, + 0x0a, 0x58, 0xa8, 0x80, 0x85, 0x99, 0xb0, 0xe0, 0x84, 0x85, 0x4f, 0xad, 0x88, 0x35, 0x7f, 0x6b, 0x33, 0x17, 0x26, 0x0e, 0xc2, 0x84, 0x4f, 0x5d, 0x39, 0xf2, 0x7a, 0x18, 0x19, 0xaf, 0x5a, 0xdc, 0x98, 0xdc, 0xf6, 0x38, 0xad, 0x76, 0xd1, 0xea, 0x9f, 0x69, 0x75, 0x81, 0xfa, 0xe6, 0xfe, 0x16, 0xb8, 0xbf, 0x4d, 0xe9, 0x7a, 0x31, 0xad, 0x5f, 0x42, 0xeb, 0xe7, 0xe2, 0x07, 0xcd, 0x3e, 0x38, 0x8a, 0x1f, 0xac, 0xc0, 0x0f, 0x56, 0xe0, 0x07, 0x2b, 0xf0, 0x83, 0x15, 0xf8, 0xc1, 0x0a, 0xfc, 0x60, 0x05, 0x7e, 0xb0, 0xc2, 0x62, 0x62, 0x11, 0x4c, 0x2c, 0x82, 0x89, 0x45, 0x30, 0xb1, 0x08, 0x16, 0xde, 0xa5, 0xe5, 0xc7, 0x69, 0xf5, 0xab, 0xb4, 0x78, 0x20, 0x2d, 0xde, 0x41, 0x8b, 0x5b, 0xd2, 0xe2, 0x70, 0x5a, 0x9c, 0x44, 0x8b, 0x63, 0x69, 0x71, 0x57, 0x5a, 0x3b, 0x92, 0xd6, 0x26, 0xa8, + 0x6b, 0x7b, 0x3d, 0x29, 0x3d, 0xaa, 0xa5, 0xb3, 0x71, 0x0b, 0x6e, 0xfc, 0xc3, 0x06, 0xbc, 0xc3, 0x2a, 0xbc, 0x43, 0x15, 0xde, 0x21, 0xd3, 0x3a, 0x8f, 0x70, 0x05, 0xde, 0xe1, 0x59, 0xbc, 0xc3, 0x57, 0x78, 0x87, 0x9d, 0x78, 0x07, 0x0f, 0xde, 0x21, 0x17, 0xef, 0xf0, 0x29, 0xde, 0x61, 0x16, 0xde, 0x61, 0x3b, 0xbe, 0xa1, 0x0c, 0xdf, 0xb0, 0xd0, 0x3a, 0x4b, 0x60, 0x3d, 0xde, 0xe1, 0x04, 0xde, 0xa1, 0x1c, 0xef, 0xb0, 0x1c, 0xef, 0xb0, 0x1c, 0xef, 0xf0, 0x0a, 0xde, 0x61, 0x35, 0xde, 0x61, 0x39, 0xde, 0xe1, 0x23, 0xbc, 0xc3, 0x4f, 0x78, 0x87, 0xcf, 0xf1, 0x0e, 0x99, 0x01, 0x47, 0xc3, 0xb6, 0xe3, 0x09, 0xb6, 0xe3, 0x09, 0x36, 0xe0, 0x09, 0x56, 0xe0, 0x09, 0x96, 0xe3, 0x07, 0x66, 0xe1, 0x07, 0xca, 0xf1, 0x03, 0xe5, 0xf8, 0x81, 0xe5, 0xf8, 0x81, 0xe5, 0xf8, 0x81, 0xe5, 0xf8, + 0x81, 0x1c, 0xfc, 0x40, 0x0e, 0x7e, 0xe0, 0x5b, 0xbc, 0xc0, 0xb7, 0x64, 0x1c, 0xad, 0xf0, 0x01, 0xcb, 0xf1, 0x00, 0x1b, 0xd0, 0xe9, 0x0d, 0xe8, 0xf4, 0x06, 0x74, 0x7a, 0x03, 0x3a, 0xbd, 0x01, 0x9d, 0x2e, 0x41, 0xa7, 0x37, 0xa0, 0xd3, 0x25, 0xe8, 0x74, 0x09, 0x3a, 0xbd, 0x01, 0x9d, 0xde, 0x80, 0x4e, 0x2f, 0x47, 0xa7, 0xcb, 0xd0, 0xe9, 0xe5, 0xe8, 0xf4, 0x72, 0x74, 0x7a, 0x39, 0x3a, 0xbd, 0x1c, 0x9d, 0x5e, 0x8e, 0x4e, 0x2f, 0x47, 0xa7, 0x97, 0xa3, 0xd3, 0xcb, 0xd1, 0xe9, 0xe5, 0xe8, 0xf4, 0x72, 0x74, 0xba, 0x0a, 0x9d, 0xae, 0x42, 0xa7, 0xab, 0xd0, 0xe9, 0x2a, 0x74, 0xba, 0x0a, 0x9d, 0xae, 0x42, 0xa7, 0xab, 0xd0, 0xe9, 0x2a, 0x74, 0xba, 0x0a, 0x9d, 0xae, 0x42, 0xa7, 0xab, 0xd0, 0xe9, 0x2a, 0x74, 0xba, 0x0a, 0x9d, 0xae, 0x42, 0xa7, 0xab, 0xd0, 0xe9, 0x2a, 0x74, 0xba, + 0x0a, 0x9d, 0xae, 0x42, 0xa7, 0xab, 0xd0, 0xe9, 0x2a, 0x74, 0x3a, 0x13, 0x9d, 0xce, 0x44, 0xa7, 0x33, 0xd1, 0xe9, 0x4c, 0x74, 0x3a, 0x13, 0x9d, 0xce, 0x44, 0xa7, 0x33, 0xd1, 0xe9, 0x4c, 0x74, 0x3a, 0x13, 0x9d, 0xce, 0x44, 0xa7, 0x33, 0xd1, 0xe9, 0x4c, 0x74, 0x3a, 0x13, 0x9d, 0xce, 0x44, 0xa7, 0x33, 0xd1, 0xe9, 0x4c, 0x74, 0x3a, 0x13, 0x9d, 0xce, 0x44, 0xa7, 0x33, 0xd1, 0xe9, 0x4c, 0x74, 0x3a, 0x13, 0x9d, 0xce, 0x44, 0xa7, 0x33, 0xd1, 0xe9, 0x4c, 0x74, 0x3a, 0x13, 0x9d, 0xce, 0x44, 0xa7, 0x77, 0xa2, 0xd3, 0x3b, 0xd1, 0xe9, 0x9d, 0xe8, 0xf4, 0x4e, 0x74, 0x7a, 0x27, 0x3a, 0xbd, 0x13, 0x9d, 0xde, 0x89, 0x4e, 0xef, 0x44, 0xa7, 0x77, 0xa2, 0xd3, 0xb9, 0xe8, 0x74, 0x2e, 0x3a, 0x9d, 0x8b, 0x4e, 0xe7, 0xa2, 0xd3, 0xb9, 0xe8, 0x74, 0x2e, 0x3a, 0x9d, 0x8b, 0x4e, 0xe7, 0xa2, + 0xd3, 0xb9, 0xe8, 0x74, 0x2e, 0x3a, 0x9d, 0x8b, 0x4e, 0xe7, 0xa2, 0xd3, 0xb9, 0xe8, 0x74, 0x2e, 0x3a, 0x9d, 0x8b, 0x4e, 0xe7, 0xa2, 0xd3, 0xb9, 0xe8, 0x74, 0x2e, 0x3a, 0x9d, 0x8b, 0x4e, 0xe7, 0xa2, 0xd3, 0xb9, 0xe8, 0x74, 0x2e, 0x3a, 0x9d, 0x8b, 0x4e, 0xe7, 0xa2, 0xd3, 0xdb, 0xc9, 0xd0, 0x52, 0xd0, 0xe9, 0xed, 0xe8, 0x74, 0x63, 0x74, 0x3a, 0x06, 0x9d, 0xde, 0x8e, 0x4e, 0x6f, 0x47, 0xa7, 0x0d, 0x74, 0x3a, 0x06, 0x9d, 0xde, 0x4e, 0xf6, 0x96, 0x82, 0x4e, 0xdf, 0x44, 0xf6, 0xd6, 0x0a, 0x9d, 0xde, 0x8e, 0x4e, 0x1f, 0x42, 0xa7, 0x4f, 0x39, 0x1e, 0x15, 0x8d, 0xd0, 0xe9, 0x74, 0x74, 0xba, 0x1c, 0x9d, 0x2e, 0x47, 0xa7, 0x57, 0xa3, 0xd3, 0xab, 0xd1, 0xe9, 0xd5, 0xe8, 0xf4, 0x6a, 0x74, 0x7a, 0x35, 0x3a, 0xbd, 0x1a, 0x9d, 0x5e, 0x8d, 0x4e, 0xaf, 0x46, 0xa7, 0x57, 0xa3, 0xd3, + 0xab, 0xd1, 0xe9, 0xd5, 0xe8, 0xf4, 0x6a, 0x74, 0x7a, 0x35, 0x3a, 0xbd, 0x1a, 0x9d, 0x5e, 0x8d, 0x4e, 0xaf, 0x46, 0xa7, 0x57, 0xa3, 0xd3, 0xab, 0xd1, 0xe9, 0xd5, 0xe8, 0x74, 0x26, 0x3a, 0x9d, 0x89, 0x4e, 0x67, 0xa2, 0xd3, 0x99, 0xe8, 0x74, 0x26, 0x3a, 0x9d, 0x89, 0x4e, 0x67, 0xa2, 0xd3, 0x99, 0xe8, 0x74, 0x26, 0x3a, 0x9d, 0x89, 0x4e, 0x67, 0xa2, 0xd3, 0x99, 0xe8, 0x74, 0x26, 0x3a, 0x9d, 0x89, 0x4e, 0x67, 0xa2, 0xd3, 0x99, 0xe8, 0x74, 0x26, 0x3a, 0x9d, 0x89, 0x4e, 0x67, 0xa2, 0xd3, 0x99, 0xe8, 0x74, 0x26, 0x3a, 0x9d, 0x89, 0x4e, 0x67, 0xa2, 0xd3, 0x99, 0xe8, 0xf4, 0x06, 0x74, 0x7a, 0x3b, 0x73, 0x71, 0xb9, 0x68, 0x2c, 0x8e, 0xa2, 0x70, 0x52, 0x2e, 0x43, 0x9b, 0x97, 0xa3, 0xcd, 0x6b, 0xb5, 0x70, 0xe1, 0x40, 0x9b, 0xe7, 0xa3, 0xc9, 0x1f, 0xa3, 0xc1, 0x73, 0xd1, 0xdc, + 0x4c, 0xa3, 0xb5, 0x68, 0x88, 0xae, 0x86, 0xa3, 0xa9, 0x15, 0x68, 0xea, 0xf7, 0x68, 0xea, 0xe7, 0xe8, 0x68, 0x89, 0x2a, 0xa1, 0x05, 0xb3, 0xce, 0x97, 0xcc, 0x38, 0x07, 0x98, 0x71, 0x2a, 0xc8, 0x2c, 0xbf, 0x67, 0xd6, 0x39, 0xc8, 0xbc, 0xba, 0x97, 0x79, 0x75, 0x2f, 0xb3, 0x4c, 0x53, 0x66, 0x16, 0xf3, 0x08, 0xda, 0x41, 0x35, 0xb3, 0xcc, 0x10, 0x71, 0xcc, 0x2c, 0xf3, 0x99, 0x59, 0xfc, 0xe6, 0xec, 0xce, 0xac, 0xf2, 0x23, 0x33, 0x47, 0x2f, 0x66, 0x87, 0x0c, 0x66, 0x87, 0x83, 0xcc, 0x0a, 0xe1, 0xcc, 0x0a, 0xcd, 0x98, 0x15, 0xba, 0x33, 0x2b, 0x8c, 0x66, 0x46, 0x38, 0x4e, 0x86, 0xf9, 0xbd, 0xda, 0xd3, 0x28, 0xe6, 0xbb, 0xe5, 0xcc, 0x73, 0x27, 0xd8, 0xe3, 0x78, 0xd4, 0xf9, 0x55, 0xf6, 0x7a, 0xb9, 0xb8, 0x53, 0x1e, 0x63, 0x8f, 0x6f, 0x33, 0xcf, 0x8d, 0x63, 0x9e, 0xfb, 0x92, + 0x79, 0xee, 0x73, 0xe6, 0xb9, 0xbb, 0x98, 0xf1, 0x1d, 0xf5, 0x66, 0xfc, 0x65, 0xcc, 0x7b, 0x0b, 0xa8, 0xd9, 0x0f, 0xd4, 0xec, 0x07, 0x6b, 0xc6, 0x9f, 0xcd, 0xdc, 0x37, 0x8b, 0x1a, 0xbe, 0x44, 0x0d, 0xf7, 0x50, 0xb3, 0x57, 0xa9, 0xd9, 0xc5, 0xd4, 0x6c, 0x80, 0xa9, 0x3b, 0x66, 0x76, 0xcd, 0xac, 0xbf, 0x9e, 0x5a, 0xfe, 0x82, 0x6a, 0xbf, 0x8e, 0x6a, 0xbf, 0xce, 0xec, 0x3f, 0x09, 0xc5, 0xee, 0x8a, 0x02, 0x8c, 0x45, 0xb1, 0x33, 0x50, 0x81, 0x1b, 0xf5, 0x0f, 0x50, 0xe1, 0x0f, 0x45, 0x08, 0xf3, 0xdf, 0x0c, 0xe6, 0xbf, 0x19, 0xcc, 0x7f, 0x33, 0x98, 0xff, 0x66, 0xa0, 0x04, 0x37, 0x32, 0x07, 0x4e, 0x47, 0x09, 0x6e, 0x41, 0x09, 0x2e, 0x45, 0x09, 0xc6, 0xa3, 0xce, 0x3d, 0xf5, 0x9f, 0xe5, 0x40, 0xe6, 0xc5, 0x3d, 0x28, 0xf0, 0xeb, 0xa8, 0xc2, 0x4d, 0xcc, 0x8f, 0xd3, 0x0d, 0x03, + 0x05, 0x6e, 0x20, 0xc7, 0x5b, 0xca, 0xb0, 0x92, 0x39, 0xb2, 0x19, 0x6c, 0xa4, 0xc0, 0x46, 0x1b, 0xd8, 0x18, 0x66, 0xa9, 0xc2, 0x5d, 0xc6, 0x50, 0x39, 0x01, 0xf5, 0x1d, 0x86, 0xf2, 0x76, 0x36, 0xae, 0x96, 0xbd, 0x60, 0xe8, 0x67, 0x14, 0x62, 0x2c, 0xf3, 0xe6, 0x16, 0xe6, 0xcd, 0xd7, 0x50, 0x88, 0x01, 0xcc, 0x9d, 0x0b, 0x8c, 0xd5, 0xf2, 0x56, 0xe3, 0x47, 0x39, 0x09, 0xa5, 0x58, 0x0a, 0x7b, 0x59, 0xa8, 0xee, 0x30, 0x31, 0x45, 0xe5, 0x64, 0x9b, 0xe4, 0x44, 0xe6, 0xd4, 0xcf, 0x98, 0x53, 0x97, 0x31, 0x9f, 0xbe, 0xc7, 0x7c, 0xfa, 0x38, 0xf3, 0xe9, 0x63, 0xcc, 0xa7, 0xb3, 0xd5, 0x2f, 0x36, 0x76, 0xc9, 0x79, 0xcc, 0xa3, 0xd7, 0x92, 0x7f, 0x95, 0x32, 0x87, 0xbe, 0xcc, 0xfc, 0xf9, 0x2e, 0xf3, 0xe7, 0x6d, 0xcc, 0x99, 0xcf, 0x30, 0x57, 0xb6, 0x60, 0xae, 0xfc, 0x84, 0xb9, 0x72, + 0xcc, 0x9f, 0x31, 0xce, 0x9c, 0xf9, 0x38, 0x39, 0xd4, 0x38, 0xe6, 0xcc, 0x6b, 0x6b, 0xd8, 0x64, 0x5e, 0xec, 0x4f, 0x4e, 0x34, 0x91, 0x9c, 0x68, 0x22, 0x39, 0xd1, 0x44, 0x72, 0xa2, 0x1c, 0x72, 0xa2, 0x1c, 0x72, 0xa2, 0x89, 0xe4, 0x44, 0x13, 0xc9, 0x89, 0x72, 0xc8, 0x89, 0x72, 0xc8, 0x89, 0x26, 0x92, 0x13, 0x4d, 0x24, 0x27, 0x9a, 0x48, 0x4e, 0x34, 0x91, 0x9c, 0x68, 0x22, 0x39, 0xd1, 0x44, 0x72, 0xa2, 0x89, 0xe4, 0x44, 0x39, 0xe4, 0x44, 0x39, 0xe4, 0x44, 0x13, 0xc9, 0x89, 0x26, 0x92, 0x13, 0xe5, 0x90, 0x13, 0xe5, 0x90, 0x13, 0x4d, 0x64, 0x6e, 0x2d, 0x65, 0x5e, 0x2d, 0xd5, 0xcd, 0x5f, 0x54, 0xb4, 0x90, 0xcb, 0x98, 0x1f, 0x97, 0x31, 0x3f, 0x96, 0x31, 0x3f, 0x96, 0x31, 0x3f, 0x96, 0x31, 0x3f, 0x2e, 0x63, 0x7e, 0x2c, 0x63, 0x7e, 0x2c, 0x63, 0x7e, 0x2c, 0x63, 0x7e, 0x5c, + 0xc6, 0xfc, 0xb8, 0x8c, 0xf9, 0x71, 0x19, 0xf3, 0xe3, 0x32, 0xe6, 0xc7, 0x32, 0xe6, 0xc7, 0x32, 0xe6, 0xc7, 0x32, 0xe6, 0xc7, 0x65, 0xcc, 0x8f, 0x65, 0xcc, 0x8f, 0x65, 0xcc, 0x8f, 0x65, 0xcc, 0x8b, 0xfd, 0x98, 0x17, 0x87, 0x32, 0x1f, 0x5e, 0xc0, 0x7c, 0x38, 0x8c, 0x39, 0xb0, 0x23, 0x73, 0x60, 0x7f, 0xe6, 0xc0, 0x71, 0xcc, 0x7d, 0xfd, 0x98, 0xfb, 0x86, 0x32, 0xe7, 0x5d, 0xc0, 0x9c, 0x37, 0x8c, 0x79, 0xae, 0x23, 0xf3, 0xdb, 0xd5, 0xcc, 0x6f, 0xb7, 0x30, 0xb7, 0x5d, 0xc6, 0xdc, 0x76, 0x2b, 0x73, 0xce, 0xbd, 0xcc, 0x39, 0xe9, 0xcc, 0x39, 0xef, 0x33, 0xe7, 0x24, 0x30, 0xe7, 0xf4, 0x60, 0xce, 0x79, 0x9f, 0x39, 0x67, 0x1e, 0x73, 0x4e, 0x02, 0x73, 0x4e, 0x57, 0xe6, 0x9c, 0x79, 0xcc, 0x39, 0xe9, 0xcc, 0x39, 0xb3, 0x98, 0x73, 0xfa, 0x33, 0xe7, 0xdc, 0xc3, 0x9c, 0x53, 0xee, + 0x98, 0x26, 0x9a, 0x33, 0xe7, 0xf4, 0x64, 0xce, 0x19, 0xcf, 0xfc, 0x31, 0x87, 0xf9, 0x63, 0x0e, 0x73, 0xc5, 0x75, 0x66, 0xcf, 0x8a, 0x48, 0x7c, 0xf6, 0xfd, 0x8c, 0xe1, 0xa5, 0xf8, 0xec, 0x95, 0x8c, 0xe3, 0x87, 0x18, 0xc7, 0xef, 0x69, 0xe1, 0xa7, 0x7f, 0x53, 0xbf, 0x2a, 0xe8, 0x28, 0xbb, 0xd1, 0x53, 0xe3, 0xe9, 0xa9, 0x95, 0xf4, 0xd4, 0x97, 0xf4, 0xd4, 0x3d, 0xf4, 0x54, 0x18, 0x3d, 0xd5, 0x84, 0x9e, 0x6a, 0x4e, 0x4f, 0x35, 0xad, 0xe9, 0x1d, 0x7c, 0xf5, 0x5d, 0x78, 0xe9, 0x5e, 0x8c, 0x77, 0xf3, 0x7c, 0xca, 0xd7, 0x28, 0x7d, 0xa3, 0x88, 0xb6, 0x9c, 0xda, 0xcf, 0x38, 0x8b, 0x50, 0x46, 0x5c, 0x31, 0xe3, 0xfc, 0x94, 0x79, 0x9e, 0x1f, 0x23, 0xa9, 0x9c, 0x91, 0x64, 0x1e, 0x45, 0x3a, 0xce, 0xc8, 0xd9, 0xcc, 0xc8, 0x39, 0xc5, 0xc8, 0x31, 0xcf, 0x13, 0xd8, 0xcc, 0x68, 0x48, + 0x24, 0xc2, 0x0f, 0x12, 0xcd, 0x29, 0x44, 0x73, 0x05, 0xaa, 0xef, 0xb4, 0x54, 0x3f, 0x9e, 0x88, 0x0e, 0x23, 0xa2, 0x23, 0x89, 0xe6, 0xee, 0xa8, 0x7e, 0x63, 0x22, 0xb8, 0x48, 0x68, 0x41, 0x1e, 0xf5, 0x7b, 0xb7, 0xd2, 0xe0, 0xe1, 0xf6, 0x4b, 0xcc, 0xc5, 0xc8, 0x3b, 0x63, 0x79, 0xcf, 0x7c, 0xb4, 0x7f, 0x61, 0xe4, 0xe9, 0xdf, 0x9f, 0xe5, 0x5d, 0x73, 0xe9, 0x60, 0xcc, 0x61, 0xab, 0xf7, 0xaa, 0xff, 0x6b, 0x30, 0x8e, 0x25, 0x3c, 0x31, 0xd4, 0x5c, 0x1c, 0x1d, 0x1c, 0xbd, 0x1c, 0x9f, 0x39, 0x26, 0x03, 0xaf, 0xb3, 0x95, 0x33, 0xdd, 0xf9, 0x40, 0xf5, 0x92, 0x76, 0xfc, 0x82, 0x8e, 0x6a, 0xbb, 0xc0, 0x25, 0x9c, 0x45, 0xad, 0x39, 0x17, 0x36, 0x18, 0x17, 0xf6, 0xd9, 0x59, 0xde, 0x37, 0x1f, 0x3f, 0x6d, 0x98, 0xdc, 0x23, 0xaa, 0x47, 0x54, 0x5a, 0x46, 0xc3, 0xee, 0x8d, 0x9a, 0x57, + 0xaf, 0x55, 0x3f, 0x76, 0xfa, 0xa2, 0x51, 0xff, 0xea, 0xb5, 0xd8, 0xa6, 0x8d, 0x32, 0xeb, 0x2c, 0x7b, 0xab, 0x97, 0xb4, 0xe3, 0x8d, 0xdc, 0xd5, 0x6b, 0x71, 0x13, 0x7f, 0x5f, 0x1a, 0x07, 0xc7, 0xad, 0x89, 0x3e, 0x14, 0xb7, 0xa6, 0x71, 0xc7, 0x9e, 0x2d, 0x78, 0xbc, 0xb9, 0xc9, 0x03, 0x4d, 0xa7, 0x37, 0x73, 0x45, 0x74, 0x88, 0x98, 0x13, 0x51, 0xde, 0xfc, 0xd2, 0xb8, 0x35, 0xcd, 0x17, 0x46, 0x9e, 0x8e, 0x1e, 0x1f, 0x7d, 0x4f, 0x34, 0x5b, 0x45, 0x1f, 0x6f, 0xd1, 0x3c, 0xa6, 0x65, 0xed, 0x72, 0x49, 0x6c, 0xd3, 0xd8, 0xa6, 0x2d, 0x3d, 0x3c, 0x66, 0xc4, 0x3e, 0x16, 0xbb, 0x2e, 0x2e, 0x34, 0x6e, 0x4d, 0x5c, 0x7a, 0x60, 0xd9, 0x75, 0x96, 0x35, 0x09, 0x97, 0x06, 0x2e, 0x71, 0x79, 0x03, 0x07, 0x0f, 0x1c, 0x9c, 0x20, 0xcc, 0xf5, 0xf8, 0xce, 0x60, 0x41, 0xdd, 0xf7, 0x6b, 0x16, + 0x73, 0xab, 0xc0, 0xa5, 0xfa, 0x13, 0x09, 0x8b, 0x13, 0xa7, 0x2b, 0x8e, 0xc7, 0x27, 0x4e, 0x64, 0x2d, 0xb7, 0x55, 0x87, 0x56, 0x2e, 0x6b, 0x79, 0xbe, 0xd5, 0x1a, 0xf3, 0xb9, 0x75, 0x58, 0x52, 0x58, 0xeb, 0x5c, 0x30, 0x38, 0xc9, 0x5c, 0x7b, 0xc3, 0x5c, 0x6f, 0xa3, 0xb7, 0xd1, 0x93, 0x06, 0x9b, 0x8f, 0x6d, 0x5a, 0xb4, 0xe9, 0x7e, 0xde, 0xa1, 0xd4, 0xc1, 0xa9, 0xb7, 0xb7, 0xbd, 0xa4, 0x5d, 0xe7, 0xf6, 0x7d, 0xdb, 0x4f, 0xed, 0xf0, 0x72, 0x87, 0x1d, 0xe7, 0x1d, 0xea, 0x11, 0xd5, 0x31, 0x2c, 0x2d, 0x03, 0x36, 0x5b, 0x75, 0x1a, 0xdf, 0xbd, 0xaa, 0xd3, 0xcb, 0x9d, 0xbe, 0xe8, 0x3c, 0xaf, 0xcb, 0xd4, 0xae, 0x33, 0xba, 0xae, 0xe9, 0xe6, 0xea, 0x1e, 0xd5, 0xbd, 0x0a, 0x86, 0xbb, 0x9a, 0xef, 0xa7, 0xdd, 0x98, 0xf6, 0x56, 0xcf, 0x0e, 0x81, 0x4b, 0xda, 0xa6, 0xb4, 0xe3, 0x75, + 0x5f, 0x09, 0x58, 0x5a, 0xa8, 0xc7, 0xab, 0x59, 0x66, 0xf5, 0x5c, 0x54, 0x67, 0x71, 0x9f, 0x6f, 0x63, 0xb9, 0xd5, 0x5a, 0xde, 0x60, 0xe9, 0x50, 0xfb, 0x5f, 0xf5, 0xff, 0xb9, 0x17, 0x84, 0x5d, 0xd0, 0xb1, 0x76, 0x19, 0xd5, 0x5b, 0xef, 0xb5, 0xaf, 0xb7, 0xfe, 0x57, 0x96, 0x3e, 0x73, 0xfe, 0xc1, 0xb2, 0xb1, 0xfe, 0xd2, 0xff, 0xad, 0xbe, 0x2d, 0xfa, 0xbf, 0xf5, 0x77, 0x96, 0x01, 0xc7, 0xfa, 0xaf, 0x1e, 0xd0, 0x79, 0x40, 0xaf, 0x01, 0xc7, 0xea, 0x2d, 0x43, 0x06, 0xbc, 0x13, 0xf8, 0xbf, 0xea, 0xcf, 0xe9, 0x7f, 0x69, 0x59, 0x31, 0x70, 0xdd, 0xa0, 0xe4, 0xbf, 0xb7, 0x08, 0x9b, 0x68, 0xaa, 0xcf, 0xd5, 0xd7, 0x09, 0xa1, 0x67, 0xe9, 0xd9, 0xa2, 0x8b, 0xbe, 0x45, 0xdf, 0x29, 0x7a, 0xea, 0xbb, 0xf4, 0x7d, 0x62, 0xa0, 0x5e, 0x64, 0x38, 0xc5, 0x70, 0xa3, 0x01, 0x73, 0xc1, 0x64, + 0x23, 0xd5, 0x48, 0x15, 0x73, 0x8c, 0x76, 0x46, 0x3b, 0xf1, 0x02, 0x23, 0xb9, 0x93, 0x78, 0xd1, 0xe8, 0xc2, 0xfc, 0x30, 0xd7, 0xe8, 0x66, 0xf4, 0x14, 0xaf, 0x1a, 0x17, 0x18, 0x83, 0xc5, 0x3c, 0xe3, 0x62, 0x63, 0xb4, 0x58, 0x6c, 0x8c, 0x31, 0xe6, 0x8a, 0x7f, 0x1b, 0xaf, 0x1b, 0xfb, 0xc4, 0x51, 0xa3, 0xc8, 0x38, 0xae, 0xf5, 0x73, 0x44, 0x3b, 0x1e, 0xd3, 0x2e, 0x73, 0x4c, 0x77, 0x7c, 0xa6, 0x65, 0x8a, 0x35, 0x64, 0x0d, 0xf9, 0x28, 0x9c, 0x9b, 0x8c, 0xc1, 0x65, 0x9d, 0x47, 0xfc, 0x3d, 0x0a, 0xb7, 0x14, 0x85, 0x73, 0x91, 0x31, 0x94, 0xa0, 0x72, 0x5e, 0x32, 0x86, 0x02, 0x94, 0x6e, 0x23, 0x4a, 0xf7, 0x0e, 0x19, 0xc3, 0x6e, 0x32, 0x86, 0x62, 0xd4, 0x6e, 0x3d, 0x19, 0x43, 0x15, 0x8a, 0x67, 0x5e, 0x31, 0xa3, 0x82, 0x8c, 0xc1, 0x4b, 0xc6, 0x90, 0x43, 0xc6, 0x90, 0x83, 0xfa, + 0x7d, 0x41, 0xc6, 0x90, 0x43, 0xc6, 0x90, 0x83, 0x0a, 0x6e, 0x46, 0x05, 0x8b, 0x51, 0xc1, 0xef, 0xc9, 0x18, 0x5c, 0x64, 0x0c, 0x15, 0x64, 0x0c, 0x15, 0x64, 0x0c, 0x7e, 0x32, 0x06, 0x3f, 0x19, 0x43, 0x3e, 0xea, 0xf7, 0x3d, 0x19, 0x43, 0x0e, 0xea, 0xf7, 0x0e, 0x19, 0x83, 0x97, 0x8c, 0xc1, 0x4b, 0xc6, 0x90, 0x43, 0xc6, 0x90, 0x43, 0xc6, 0x60, 0x66, 0x0a, 0x87, 0xc8, 0x14, 0x0e, 0xa1, 0x88, 0x1d, 0xc9, 0x14, 0x72, 0xc8, 0x14, 0xf2, 0xc9, 0x14, 0xf2, 0xc9, 0x14, 0xf2, 0xc9, 0x14, 0xf2, 0xc9, 0x14, 0xf2, 0x51, 0xb3, 0x23, 0x64, 0x0a, 0xf9, 0x28, 0xda, 0x11, 0x14, 0xed, 0x08, 0x99, 0x42, 0x3e, 0x99, 0x42, 0x3e, 0x99, 0x42, 0x0e, 0x99, 0x42, 0x31, 0x99, 0x42, 0x0e, 0x99, 0x42, 0x0e, 0x99, 0x42, 0x0e, 0x99, 0x42, 0x0e, 0x99, 0x42, 0x0e, 0x99, 0x42, 0x0e, 0x99, 0x42, 0x0e, + 0x99, 0x42, 0x0e, 0x99, 0x42, 0x0e, 0x99, 0x42, 0x0e, 0xce, 0xde, 0x85, 0xb3, 0x77, 0xa1, 0x60, 0x2e, 0x14, 0xcc, 0x85, 0xb3, 0x77, 0xe1, 0xec, 0x5d, 0x28, 0x99, 0x0b, 0x25, 0x73, 0xe1, 0xec, 0x5d, 0x38, 0x7b, 0x17, 0xce, 0xde, 0x85, 0xaa, 0xb9, 0x50, 0x35, 0x17, 0xaa, 0xe6, 0xc2, 0xd9, 0xbb, 0x70, 0xf6, 0x2e, 0xd4, 0xcd, 0x85, 0xba, 0xb9, 0x70, 0xf6, 0x2e, 0x9c, 0xbd, 0x0b, 0x95, 0x73, 0xa1, 0x72, 0x2e, 0x9c, 0xbd, 0x0b, 0x67, 0xef, 0xc2, 0xd9, 0xbb, 0x50, 0x3c, 0x17, 0xce, 0xbe, 0x04, 0x67, 0x5f, 0x82, 0xf2, 0x95, 0xa0, 0x7c, 0x25, 0x38, 0xfb, 0x12, 0xd4, 0xaf, 0x04, 0xf5, 0x2b, 0xc1, 0xd9, 0x97, 0xe0, 0xec, 0x4b, 0x70, 0xf6, 0x05, 0x38, 0xfb, 0x02, 0x9c, 0x7d, 0x01, 0xce, 0xbe, 0x00, 0x67, 0x5f, 0x80, 0xb3, 0x2f, 0xc0, 0xd9, 0x17, 0xe0, 0xec, 0x0b, 0x70, 0xf6, + 0x05, 0x38, 0xfb, 0x02, 0x9c, 0x7d, 0x01, 0xce, 0xbe, 0x00, 0x67, 0x5f, 0x80, 0xb3, 0x2f, 0xc0, 0xd9, 0x17, 0xe0, 0xec, 0x0b, 0x70, 0xf6, 0x05, 0x38, 0xfb, 0x02, 0x9c, 0x7d, 0x01, 0xce, 0xbe, 0x00, 0x67, 0x5f, 0x80, 0xb3, 0x2f, 0xc0, 0xd9, 0x17, 0xe0, 0xec, 0x0b, 0x50, 0xd9, 0xdd, 0xa8, 0x6c, 0x37, 0x54, 0x76, 0xb7, 0xe3, 0x3e, 0x70, 0xbf, 0x70, 0xa2, 0xb2, 0xbb, 0x51, 0xd9, 0xdd, 0x8e, 0xc9, 0x60, 0x8a, 0x68, 0x8a, 0xca, 0xee, 0x46, 0x65, 0xbb, 0xa1, 0xb2, 0x0f, 0xa2, 0xb2, 0x1d, 0x51, 0xd9, 0xdd, 0xa8, 0xec, 0x6e, 0x54, 0xb6, 0xa9, 0xe5, 0xec, 0x13, 0x70, 0xf6, 0x5e, 0x9c, 0xbd, 0x17, 0x67, 0x9f, 0x83, 0xb3, 0xcf, 0xc1, 0xd9, 0xe7, 0xe0, 0xec, 0x73, 0x70, 0xf6, 0x39, 0x38, 0xfb, 0x1c, 0x9c, 0x7d, 0x0e, 0xce, 0x3e, 0x07, 0x67, 0x9f, 0x83, 0xb3, 0xcf, 0xc1, 0xd9, + 0xe7, 0xa0, 0xcc, 0x39, 0x28, 0x73, 0x0e, 0xce, 0x3e, 0x07, 0x67, 0x9f, 0x83, 0xb3, 0xcf, 0xc1, 0xd9, 0xe7, 0xe0, 0xec, 0x73, 0x50, 0xeb, 0x1c, 0x9c, 0xbd, 0x0b, 0x67, 0xef, 0xc2, 0xd9, 0xbb, 0x70, 0xf6, 0x2e, 0x9c, 0xbd, 0x0b, 0x67, 0xef, 0xc2, 0xd9, 0xbb, 0x70, 0xf6, 0x2e, 0x9c, 0xbd, 0x0b, 0x67, 0xef, 0xc2, 0xd9, 0xbb, 0x70, 0xf6, 0x2e, 0x9c, 0xbd, 0x0b, 0x67, 0xef, 0xc2, 0xd9, 0xbb, 0x70, 0xf6, 0x2e, 0x9c, 0xbd, 0x0b, 0x67, 0xef, 0xc2, 0xd9, 0xbb, 0x70, 0xf6, 0x2e, 0x9c, 0xbd, 0x0b, 0x67, 0xef, 0xc2, 0xd9, 0xbb, 0x70, 0xf6, 0x2e, 0x9c, 0x7d, 0x3e, 0xce, 0xde, 0xff, 0x7f, 0x48, 0xb3, 0x07, 0x89, 0xce, 0xe4, 0x0f, 0xd7, 0x83, 0xf1, 0xe0, 0x06, 0x70, 0x3b, 0xb8, 0x13, 0xdc, 0x05, 0x26, 0x81, 0x87, 0xc1, 0x6c, 0x79, 0x44, 0xcc, 0x91, 0x65, 0x5a, 0x0f, 0x99, 0xa3, + 0xa5, 0x89, 0x20, 0x2d, 0x5d, 0x9e, 0xd2, 0xfa, 0xe0, 0x3f, 0xfa, 0xf2, 0xdc, 0x8f, 0xe7, 0x8b, 0x44, 0x37, 0xfc, 0x48, 0x3b, 0xfc, 0xc8, 0x79, 0xf8, 0x91, 0xab, 0xf1, 0x23, 0x57, 0xab, 0xab, 0x79, 0x5c, 0x87, 0x37, 0x19, 0x07, 0x6e, 0x16, 0xd1, 0x9a, 0xf9, 0xfb, 0x85, 0xdf, 0xaf, 0xea, 0x61, 0x5e, 0xe3, 0xb4, 0xb1, 0xed, 0x7e, 0xf0, 0x10, 0x78, 0x56, 0x56, 0xd9, 0xde, 0xe4, 0x79, 0x9e, 0x68, 0x1c, 0x3c, 0x59, 0x34, 0x0e, 0x09, 0x07, 0xdd, 0x45, 0x9b, 0x90, 0x1e, 0x60, 0x9d, 0xac, 0x0a, 0x4d, 0x12, 0x8d, 0xf5, 0x6f, 0xc1, 0x2a, 0xb0, 0x9a, 0x9a, 0x37, 0x06, 0x4d, 0x40, 0x53, 0xd0, 0x0c, 0xb0, 0xbd, 0x11, 0x01, 0xba, 0x81, 0xee, 0xa0, 0x07, 0x48, 0x03, 0x3d, 0x41, 0x2f, 0xd0, 0x1b, 0xa4, 0x83, 0x3e, 0xa0, 0x2f, 0xe8, 0x07, 0xfa, 0x83, 0x01, 0x60, 0x20, 0x99, 0x63, + 0xf5, 0x15, 0x45, 0xde, 0x24, 0x0f, 0xaa, 0xdb, 0xd2, 0x60, 0x5a, 0xba, 0x8f, 0x96, 0xda, 0x68, 0xe9, 0x3e, 0x5a, 0x6a, 0xa3, 0x95, 0x0d, 0x69, 0x65, 0x04, 0xad, 0x8c, 0xa0, 0x95, 0x11, 0x56, 0x2b, 0x1b, 0xd3, 0xca, 0xc6, 0xb4, 0xb2, 0xd9, 0x19, 0xad, 0xa4, 0x65, 0x21, 0xdd, 0xe5, 0xc9, 0x90, 0x1e, 0x80, 0x96, 0xd4, 0xee, 0x29, 0xaa, 0x7a, 0x4f, 0xc2, 0x60, 0x4f, 0x7b, 0xd5, 0x2f, 0x46, 0xd3, 0x65, 0x15, 0x7b, 0xa9, 0x32, 0xef, 0x94, 0x64, 0x5d, 0x15, 0x65, 0xbd, 0x75, 0x55, 0x94, 0x42, 0xed, 0x3a, 0x78, 0x1e, 0x07, 0x6e, 0x96, 0x25, 0xe7, 0xd8, 0x43, 0x15, 0x7b, 0xa8, 0xaa, 0xb3, 0x07, 0xf3, 0x9e, 0x4d, 0x37, 0x51, 0xd7, 0x07, 0xa9, 0xeb, 0x73, 0xd4, 0x75, 0x06, 0x35, 0x37, 0xcf, 0x60, 0x30, 0xaf, 0x85, 0xa4, 0x5a, 0x57, 0xe7, 0x0a, 0x2c, 0x35, 0xfb, 0x72, 0x91, + 0x2f, 0x9a, 0x57, 0x58, 0xda, 0x4a, 0x79, 0x5b, 0xed, 0xe6, 0x35, 0xd8, 0xcc, 0x6b, 0x33, 0x85, 0xd7, 0xe5, 0xe5, 0xec, 0x9f, 0xa4, 0x76, 0xdb, 0xcf, 0x51, 0x3b, 0x55, 0x5a, 0x9d, 0xda, 0x99, 0x57, 0xa1, 0xd9, 0xca, 0xa7, 0x37, 0xf2, 0xe9, 0xad, 0xea, 0xd7, 0x34, 0xa5, 0x01, 0xd7, 0x7d, 0xda, 0xc7, 0xbb, 0x3e, 0xde, 0x2d, 0xe1, 0xdd, 0xfd, 0xe6, 0x75, 0x9f, 0x28, 0xfb, 0x30, 0xe5, 0x1e, 0xa7, 0xdc, 0xe3, 0x35, 0xd7, 0x7f, 0xa2, 0x9c, 0x7f, 0x9f, 0xf1, 0xc9, 0x5d, 0xd6, 0x15, 0xa3, 0xf6, 0xf2, 0xc9, 0x3d, 0xd6, 0x27, 0x8b, 0xff, 0xd2, 0x27, 0xcf, 0xb8, 0xd6, 0x14, 0x9f, 0xdc, 0x73, 0xf6, 0x4f, 0xfe, 0xe3, 0x2b, 0x55, 0xfd, 0xd1, 0x95, 0x9e, 0xea, 0xd6, 0xa6, 0xc0, 0xba, 0x4a, 0xcf, 0x7e, 0xca, 0x2b, 0xb2, 0x6a, 0x73, 0xe8, 0xac, 0xb5, 0xf9, 0x7b, 0xd7, 0x00, 0xfd, + 0x6b, 0x77, 0xbd, 0x98, 0x23, 0xf3, 0xa9, 0xcb, 0x4e, 0x46, 0x80, 0x93, 0xb8, 0x3c, 0xc9, 0x08, 0x68, 0x4a, 0x6c, 0x9e, 0x64, 0x04, 0x34, 0x65, 0xac, 0xf7, 0x61, 0x14, 0x74, 0x23, 0xb2, 0xba, 0x12, 0x59, 0x37, 0x12, 0x59, 0x37, 0x52, 0xbf, 0x1c, 0x46, 0x41, 0x38, 0xa3, 0x20, 0x9c, 0x51, 0x90, 0x60, 0x45, 0x41, 0x19, 0xb5, 0x2a, 0x53, 0x47, 0x0e, 0x02, 0xef, 0x7a, 0x51, 0x3d, 0xd6, 0xeb, 0xdf, 0xf9, 0xa2, 0x2d, 0x63, 0xbd, 0xad, 0x35, 0xd6, 0xff, 0xda, 0x9d, 0x2f, 0xfe, 0xe1, 0xdd, 0x2e, 0x88, 0x40, 0xf3, 0x57, 0x56, 0x73, 0x6b, 0x5b, 0xe9, 0xa1, 0x95, 0x0d, 0x68, 0x65, 0x29, 0xad, 0x0c, 0xa6, 0x95, 0xa5, 0xb4, 0x32, 0x98, 0x16, 0x46, 0xd1, 0xc2, 0x18, 0x5a, 0x18, 0x4b, 0x0b, 0x63, 0x69, 0x61, 0x3e, 0x2d, 0x6c, 0x44, 0x0b, 0x1b, 0xd1, 0xc2, 0x98, 0x33, 0x5a, 0x58, + 0x1d, 0xe7, 0x1b, 0xe9, 0x95, 0x8d, 0x56, 0x9c, 0x57, 0xef, 0xa5, 0xa9, 0x75, 0x9d, 0xb4, 0x4a, 0xc6, 0xdd, 0xaf, 0xd6, 0x35, 0xd2, 0xb6, 0xd2, 0xbf, 0x4b, 0xad, 0x6b, 0x38, 0x7d, 0x6f, 0x5d, 0x23, 0x2d, 0x87, 0x71, 0x57, 0x15, 0x1c, 0x2a, 0x4f, 0x07, 0x8f, 0x90, 0x47, 0xcd, 0xb3, 0x8a, 0x6a, 0x4a, 0x23, 0x76, 0x8a, 0x88, 0x9d, 0x2a, 0x75, 0xed, 0xa4, 0xfc, 0x3f, 0x2a, 0x81, 0x08, 0xd9, 0xf2, 0x97, 0xeb, 0x65, 0xfe, 0xb6, 0x69, 0x37, 0x31, 0xb7, 0x8e, 0xbc, 0x72, 0xbb, 0xba, 0x23, 0xdb, 0x68, 0xa1, 0xd7, 0xfc, 0xc2, 0x85, 0x16, 0xea, 0x94, 0xe2, 0xa5, 0x14, 0xaf, 0x75, 0x0d, 0xa7, 0x53, 0x44, 0xd6, 0x58, 0x22, 0xab, 0xbf, 0xbe, 0x12, 0x27, 0xb8, 0x97, 0x79, 0xb3, 0xa5, 0xe8, 0x43, 0x94, 0x35, 0x43, 0x5d, 0x3a, 0xa3, 0x2e, 0xbd, 0x51, 0x97, 0xb6, 0xa8, 0x4b, 0x0f, + 0xa2, 0x6e, 0x0c, 0x51, 0x37, 0xc2, 0xe8, 0x20, 0x92, 0x89, 0xbc, 0x1f, 0x89, 0xbc, 0x48, 0xe3, 0x46, 0xa1, 0x13, 0x7d, 0x23, 0x1d, 0x6e, 0x11, 0x5f, 0xef, 0x8a, 0x53, 0xe7, 0x13, 0x8f, 0x89, 0xc4, 0x63, 0x22, 0xf1, 0x98, 0x48, 0x3c, 0x26, 0x12, 0x8f, 0x89, 0xd6, 0xb5, 0xb1, 0x13, 0x89, 0xc7, 0x44, 0x75, 0xaf, 0xb9, 0xba, 0xd7, 0xc6, 0x4e, 0x24, 0x96, 0x12, 0x89, 0xa5, 0x44, 0xe2, 0x28, 0x91, 0x38, 0x4a, 0x24, 0x8e, 0x12, 0x89, 0xa3, 0x44, 0x62, 0x27, 0x51, 0x7f, 0x43, 0xd8, 0x89, 0x9f, 0x44, 0xe2, 0x27, 0x91, 0xf8, 0x49, 0x24, 0x7e, 0x12, 0x89, 0x9f, 0x44, 0xe2, 0x27, 0x91, 0xf8, 0x49, 0x24, 0x7e, 0x12, 0x89, 0x9f, 0x44, 0xea, 0x3f, 0xd7, 0xaa, 0xfb, 0x10, 0xea, 0x9e, 0x41, 0xdd, 0xa7, 0x04, 0x5c, 0x17, 0xdb, 0x49, 0x7c, 0x25, 0x12, 0x5f, 0x89, 0xc4, 0x57, 0x22, + 0xf1, 0x95, 0x48, 0x7c, 0x25, 0x12, 0x5f, 0x89, 0xc4, 0x57, 0x22, 0xf1, 0x95, 0x48, 0x7c, 0x25, 0x12, 0x5f, 0x89, 0xc4, 0x57, 0x22, 0xf1, 0x95, 0x48, 0x7c, 0x25, 0x12, 0x5f, 0x89, 0xc6, 0x93, 0x70, 0x31, 0x5b, 0xb4, 0x36, 0x8e, 0x89, 0x86, 0xb4, 0xf7, 0x1a, 0xe1, 0xa8, 0x73, 0x2e, 0x55, 0x4b, 0xf5, 0xab, 0x40, 0x07, 0x7b, 0x8d, 0xb1, 0xce, 0x73, 0x8b, 0xa8, 0x3d, 0xb7, 0xea, 0xbf, 0xd1, 0x27, 0xcd, 0xe8, 0x13, 0x4d, 0x9d, 0x89, 0x93, 0x40, 0x1d, 0x12, 0x19, 0xf5, 0xad, 0x44, 0xec, 0x59, 0xfa, 0x44, 0xab, 0xd3, 0x27, 0x76, 0x35, 0xfb, 0x5c, 0xcc, 0x4c, 0x53, 0x1d, 0x3b, 0x07, 0xac, 0x3d, 0xfc, 0x46, 0xac, 0x6c, 0x87, 0xff, 0x31, 0xf2, 0xa0, 0x52, 0xaa, 0x03, 0xb5, 0xd7, 0x07, 0xe4, 0x15, 0xeb, 0xba, 0x80, 0xb5, 0x6d, 0x63, 0xdb, 0x6c, 0x75, 0xdc, 0xb1, 0x98, 0x2d, + 0xb3, 0xd9, 0xf2, 0x47, 0xb6, 0xfc, 0xb8, 0xde, 0x15, 0xbe, 0x36, 0x11, 0x8d, 0xab, 0x89, 0xc6, 0xd5, 0xb4, 0xaa, 0xa7, 0xb0, 0x6b, 0x8c, 0x4c, 0x8d, 0x91, 0xa9, 0x0d, 0x16, 0x6d, 0x69, 0x6d, 0x02, 0xad, 0x4d, 0xa0, 0xb5, 0xf1, 0xe7, 0x3c, 0xff, 0x2c, 0x83, 0x78, 0x49, 0x21, 0x5e, 0x52, 0x88, 0x97, 0x14, 0xe2, 0x25, 0x85, 0x78, 0x49, 0x21, 0x5e, 0x52, 0x88, 0x97, 0x14, 0xe2, 0x25, 0x85, 0x78, 0x49, 0x11, 0xb3, 0x99, 0xa3, 0x99, 0x1b, 0x18, 0xd9, 0xdd, 0xb5, 0x74, 0xd1, 0x82, 0x91, 0xdd, 0x43, 0xeb, 0xcb, 0x73, 0x3f, 0x9e, 0x2f, 0xe2, 0xf9, 0x62, 0x71, 0x29, 0x7b, 0x1b, 0xc3, 0xde, 0xae, 0x61, 0x6f, 0x63, 0xd5, 0xef, 0x37, 0xaf, 0x13, 0xbd, 0x18, 0xdd, 0xbd, 0xe0, 0x79, 0x44, 0xbd, 0x78, 0x4b, 0x21, 0xde, 0x52, 0x88, 0xb7, 0x14, 0xe2, 0x2d, 0x85, 0x78, 0x4b, 0x21, + 0xde, 0x52, 0x88, 0xb7, 0x14, 0xe6, 0xad, 0xde, 0xcc, 0x5b, 0xbd, 0x43, 0x8e, 0x89, 0x68, 0x62, 0x2f, 0x85, 0xb8, 0x4b, 0x21, 0xee, 0x52, 0x88, 0xbb, 0x14, 0xe2, 0x2e, 0x85, 0xb8, 0x4b, 0x21, 0xee, 0x52, 0x88, 0xbb, 0x14, 0xe2, 0x2e, 0x85, 0xb8, 0x4b, 0xa9, 0x77, 0xc7, 0x9e, 0x14, 0x62, 0x2c, 0x85, 0x18, 0x4b, 0x21, 0xc6, 0x52, 0x88, 0xb1, 0x14, 0x62, 0x2c, 0x85, 0x18, 0x4b, 0x21, 0xc6, 0x52, 0x88, 0xb1, 0x14, 0x62, 0x2c, 0x85, 0x18, 0x4b, 0x21, 0xc6, 0x52, 0x88, 0xb1, 0x14, 0x62, 0x2c, 0x85, 0x18, 0x1b, 0x40, 0x8c, 0xb5, 0x25, 0xc6, 0xc2, 0xd5, 0x8c, 0x9e, 0x00, 0x23, 0x09, 0x30, 0x92, 0x00, 0x23, 0x09, 0x30, 0x92, 0x00, 0x23, 0x09, 0x30, 0x92, 0x00, 0x23, 0x09, 0x30, 0x92, 0x00, 0x23, 0x8d, 0x71, 0x1b, 0x23, 0x60, 0x25, 0x42, 0xeb, 0x81, 0x23, 0x4b, 0x63, 0x4e, + 0x4b, 0x17, 0x17, 0xc1, 0x4c, 0x38, 0xcc, 0x5c, 0x04, 0x33, 0xe1, 0x30, 0xd3, 0xcc, 0xfa, 0x8e, 0x2d, 0x12, 0x46, 0xa2, 0x61, 0xa4, 0x37, 0x8c, 0xf4, 0x86, 0x91, 0x58, 0xed, 0x16, 0xd1, 0xba, 0x1e, 0x2b, 0x09, 0xb0, 0x92, 0x00, 0x2b, 0x09, 0xb0, 0x92, 0x00, 0x2b, 0x09, 0xb0, 0x92, 0x00, 0x2b, 0x09, 0xb0, 0x92, 0x01, 0x2b, 0x19, 0xb0, 0x12, 0x01, 0x2b, 0x09, 0xb0, 0x92, 0x00, 0x2b, 0x09, 0xb0, 0x92, 0x00, 0x2b, 0x09, 0xb0, 0x92, 0x00, 0x2b, 0x09, 0xb0, 0x92, 0x00, 0x2b, 0x09, 0xb0, 0x92, 0x50, 0x8f, 0x95, 0x04, 0x58, 0x49, 0x80, 0x95, 0x04, 0x58, 0x49, 0x80, 0x95, 0x04, 0x58, 0x49, 0x80, 0x95, 0x04, 0x58, 0x49, 0x80, 0x95, 0x04, 0x58, 0x49, 0x80, 0x95, 0x04, 0x58, 0x49, 0x80, 0x95, 0x04, 0x58, 0x49, 0x10, 0x97, 0xc3, 0x84, 0x13, 0x26, 0x9c, 0x30, 0xe1, 0x54, 0xdf, + 0xa1, 0xdd, 0x48, 0xab, 0x6f, 0x02, 0x37, 0x83, 0x5b, 0xc1, 0x6d, 0xca, 0xdf, 0x3a, 0x61, 0xc7, 0x09, 0x3b, 0x4e, 0xd8, 0x71, 0xc2, 0x8e, 0x13, 0x76, 0x5a, 0xc2, 0x8e, 0xc9, 0x4c, 0xaa, 0x15, 0x33, 0x51, 0x30, 0x93, 0x0c, 0x33, 0x51, 0x30, 0x93, 0x6c, 0xe9, 0x5d, 0x3a, 0xf1, 0xd2, 0x9f, 0x78, 0x49, 0x87, 0x9d, 0xd6, 0xb0, 0x93, 0x0a, 0x3b, 0xa9, 0xb0, 0x33, 0x00, 0x76, 0x1a, 0xd4, 0x63, 0xc7, 0x09, 0x3b, 0x4e, 0xd8, 0x71, 0xc2, 0x8e, 0x13, 0x76, 0x9c, 0xb0, 0xe3, 0x84, 0x1d, 0x27, 0xec, 0x74, 0x80, 0x9d, 0x0e, 0x8c, 0xdd, 0xc6, 0xb0, 0xe3, 0x84, 0x1d, 0x27, 0xec, 0x38, 0x61, 0xc7, 0x09, 0x3b, 0x4e, 0xd8, 0x71, 0xc2, 0x8e, 0x13, 0x76, 0x9c, 0xb0, 0xe3, 0x84, 0x1d, 0xa7, 0x11, 0xc9, 0xd8, 0x8e, 0x06, 0x2d, 0xd4, 0x18, 0x6f, 0x6c, 0xc4, 0x82, 0x38, 0x10, 0x0f, 0x4c, + 0xe6, 0x12, 0x79, 0x6e, 0x05, 0xda, 0x80, 0x64, 0x60, 0x32, 0xd9, 0x81, 0x67, 0x93, 0xcd, 0x4e, 0x3c, 0x77, 0x51, 0xde, 0xd8, 0x09, 0xab, 0x4e, 0x58, 0x75, 0xc2, 0xaa, 0x13, 0x56, 0x9d, 0xb0, 0xea, 0x84, 0x55, 0x27, 0xac, 0x3a, 0x61, 0xd5, 0x09, 0xab, 0x4e, 0x58, 0x75, 0xc2, 0xaa, 0x13, 0x56, 0x9d, 0xb0, 0xea, 0x54, 0xac, 0x3a, 0x60, 0xd5, 0x01, 0xab, 0x0e, 0x58, 0x75, 0xc0, 0x6a, 0x14, 0xac, 0x46, 0xc1, 0x6a, 0x14, 0xac, 0x46, 0xc1, 0x6a, 0x14, 0xac, 0x3a, 0x60, 0xd5, 0x01, 0xab, 0x0e, 0x58, 0x75, 0xc0, 0xaa, 0x03, 0x56, 0x9b, 0xc3, 0x6a, 0x07, 0x58, 0xed, 0x06, 0xab, 0xad, 0x60, 0xb5, 0x03, 0xac, 0x36, 0x81, 0xd5, 0x0e, 0xb0, 0xda, 0x04, 0x56, 0xcd, 0xdf, 0x3b, 0xa6, 0xc2, 0x6a, 0x5b, 0x58, 0x6d, 0x0b, 0xab, 0x3d, 0x60, 0x35, 0x19, 0x56, 0x93, 0x61, 0xb5, 0x17, + 0xac, 0x46, 0xd7, 0x63, 0xd5, 0x01, 0xab, 0x0e, 0x58, 0x75, 0xc0, 0xaa, 0x03, 0x56, 0x1d, 0xb0, 0xea, 0x80, 0x55, 0x07, 0xac, 0xa6, 0xc1, 0x6a, 0x9a, 0xe9, 0x7f, 0x60, 0xd5, 0x01, 0xab, 0x0e, 0x58, 0x75, 0xc0, 0xaa, 0x03, 0x56, 0x1d, 0xb0, 0xea, 0x80, 0x55, 0x07, 0xac, 0x3a, 0x60, 0xd5, 0x01, 0xab, 0x0e, 0x58, 0x8d, 0x82, 0xd5, 0x28, 0x58, 0x8d, 0xaa, 0xfe, 0x4d, 0x33, 0xa8, 0xfe, 0xd5, 0x76, 0x94, 0xc5, 0xaa, 0xf9, 0x7d, 0x79, 0x14, 0xac, 0x46, 0xc1, 0x6a, 0x94, 0xc5, 0x6a, 0x94, 0xc5, 0x6a, 0x14, 0xac, 0x46, 0xc1, 0xaa, 0x03, 0x56, 0x1d, 0xb0, 0xea, 0x80, 0x55, 0x07, 0xac, 0x3a, 0x60, 0xd5, 0x01, 0xab, 0x0e, 0x58, 0x75, 0xc0, 0xaa, 0x03, 0x56, 0x1d, 0xb0, 0xea, 0x80, 0x55, 0x07, 0xac, 0x3a, 0x60, 0xd5, 0x61, 0xfa, 0x7f, 0xb8, 0x9d, 0x03, 0x2b, 0x3d, 0x44, 0x27, + 0xd8, 0x89, 0x87, 0x9d, 0x58, 0xd8, 0x69, 0x08, 0x3b, 0xb1, 0xb0, 0xd3, 0x10, 0x76, 0x1a, 0xc1, 0x4e, 0x6b, 0xd8, 0x49, 0x86, 0x9d, 0x64, 0xd8, 0x49, 0x84, 0x9d, 0xa6, 0xb0, 0xd3, 0x14, 0x76, 0xda, 0xd4, 0x71, 0x7d, 0xdd, 0x45, 0x2b, 0x5a, 0xdf, 0xaa, 0xce, 0x4c, 0x69, 0xee, 0xa1, 0x09, 0x7b, 0x70, 0xb2, 0x87, 0x78, 0xf6, 0xd0, 0x96, 0x3d, 0x34, 0x61, 0x0f, 0x2d, 0xd9, 0x43, 0x13, 0xf6, 0xd0, 0x92, 0x3d, 0xa4, 0xb2, 0x87, 0x2e, 0xec, 0xa1, 0x07, 0x7b, 0xe8, 0x62, 0x8d, 0xf9, 0x58, 0xf6, 0x10, 0xcb, 0x1e, 0xce, 0xff, 0xf3, 0x3d, 0x68, 0x9d, 0x98, 0x47, 0x7b, 0x88, 0x0b, 0x28, 0x3d, 0x81, 0xd2, 0x5b, 0x51, 0x7a, 0x33, 0x4a, 0x6f, 0x45, 0xe9, 0xcd, 0xac, 0xde, 0x8d, 0xb4, 0x7e, 0xcd, 0xda, 0x8c, 0xd2, 0x93, 0x29, 0x3d, 0x92, 0xd2, 0x23, 0x29, 0x2d, 0x99, 0xd2, 0x92, + 0xed, 0xe6, 0x7d, 0xaa, 0xba, 0x91, 0xad, 0x36, 0x17, 0xc3, 0x8c, 0x28, 0x61, 0x18, 0xad, 0xc5, 0x9b, 0x46, 0x92, 0x78, 0xda, 0xe8, 0x28, 0x0c, 0x87, 0x79, 0xd4, 0x3a, 0xfc, 0x9f, 0xc4, 0x4e, 0x4d, 0x2c, 0x50, 0xf2, 0x21, 0x4a, 0x8d, 0xa2, 0xd4, 0xa7, 0x29, 0xf5, 0x7e, 0x4a, 0x8d, 0x12, 0x57, 0x10, 0xcd, 0x71, 0x44, 0x73, 0x1c, 0xd1, 0x1c, 0x47, 0x34, 0xc7, 0x11, 0xcd, 0x06, 0xd1, 0x6c, 0x10, 0xcd, 0x06, 0xd1, 0x6c, 0x10, 0xcd, 0x06, 0xd1, 0x1c, 0x47, 0x34, 0xc7, 0x11, 0xcd, 0x71, 0x44, 0x73, 0x1c, 0xd1, 0x1c, 0x67, 0x46, 0x33, 0xb3, 0x67, 0x33, 0x6a, 0xd4, 0x84, 0x1a, 0xf5, 0xa4, 0x46, 0x91, 0x96, 0xae, 0x44, 0x5a, 0xba, 0x62, 0xd6, 0x6a, 0x30, 0xb5, 0xba, 0x84, 0x5a, 0x5d, 0x4a, 0xad, 0x46, 0x52, 0xab, 0x86, 0x01, 0xba, 0x72, 0x55, 0xbd, 0x68, 0x8e, 0x23, 0x9a, + 0xe3, 0x88, 0xe6, 0x38, 0xa2, 0x39, 0x8e, 0x68, 0x8e, 0x23, 0x9a, 0xe3, 0x88, 0xe6, 0xb8, 0x80, 0x68, 0x36, 0x98, 0x45, 0x9b, 0x11, 0xd1, 0x71, 0x44, 0x74, 0x1c, 0x11, 0x1d, 0x47, 0x44, 0xc7, 0x11, 0xd1, 0x71, 0x44, 0x74, 0x1c, 0x11, 0x1d, 0x47, 0x44, 0xc7, 0x11, 0xd1, 0x71, 0x44, 0x74, 0x1c, 0x11, 0x6d, 0x10, 0xd1, 0x06, 0x11, 0x6d, 0x10, 0xd1, 0x06, 0x11, 0x6d, 0x10, 0xd1, 0x06, 0x11, 0x6d, 0x58, 0x11, 0x6d, 0x10, 0xd1, 0x06, 0x11, 0x6d, 0x10, 0xd1, 0x86, 0x15, 0xd1, 0x86, 0x15, 0xd1, 0x86, 0x79, 0x05, 0x07, 0x22, 0x3a, 0x8e, 0x88, 0x8e, 0x23, 0xa2, 0xe3, 0x88, 0xe8, 0x38, 0x22, 0x3a, 0x8e, 0x88, 0x8e, 0x23, 0xa2, 0xe3, 0x88, 0xe8, 0x38, 0x22, 0x3a, 0x8e, 0x88, 0x8e, 0x23, 0xa2, 0xe3, 0x88, 0xe8, 0x38, 0x22, 0x3a, 0x8e, 0x88, 0x8e, 0x13, 0x63, 0xeb, 0xdd, 0xe5, + 0xa4, 0x25, 0xcc, 0xb6, 0x83, 0xd9, 0x76, 0x30, 0xdb, 0x0e, 0x66, 0xdb, 0xc1, 0x6c, 0x3b, 0xeb, 0xce, 0x27, 0x2d, 0x03, 0xef, 0x7c, 0x02, 0xb3, 0xa9, 0xc4, 0x69, 0x5f, 0xd8, 0x6d, 0x02, 0xbb, 0x17, 0x5a, 0x33, 0x70, 0x5f, 0xd8, 0x6d, 0x0d, 0xbb, 0x7d, 0x61, 0xb7, 0x35, 0xec, 0x36, 0x86, 0xdd, 0x14, 0xd8, 0x4d, 0x83, 0xdd, 0x34, 0xd8, 0xed, 0x09, 0xbb, 0x17, 0xc1, 0xee, 0xf9, 0xb0, 0x7b, 0x3e, 0xec, 0x0e, 0x67, 0xbe, 0x68, 0xff, 0x57, 0xef, 0xa2, 0x02, 0xc3, 0x43, 0x60, 0x78, 0x08, 0x0c, 0xb7, 0x83, 0xe1, 0x26, 0xea, 0xae, 0x2a, 0x7f, 0xe5, 0x8e, 0x2a, 0x91, 0xa2, 0x1d, 0x0c, 0xb7, 0x83, 0x61, 0xf3, 0x1c, 0x9b, 0x76, 0x30, 0xdc, 0x0e, 0x86, 0xdb, 0xc1, 0x70, 0x3b, 0x8b, 0xe1, 0x76, 0xea, 0x0e, 0x2b, 0x6d, 0x40, 0xb2, 0x75, 0xa7, 0x95, 0x0e, 0x3c, 0x57, 0x33, 0xdc, + 0x0e, 0x86, 0xdb, 0xfd, 0xd3, 0x3b, 0xae, 0xa8, 0x9c, 0xb9, 0xa2, 0xe6, 0x1a, 0xab, 0x28, 0xff, 0x1c, 0xd1, 0x11, 0xbe, 0x62, 0xe1, 0x4b, 0x87, 0xaf, 0x54, 0xb8, 0x32, 0xc7, 0xb3, 0x03, 0x6e, 0xec, 0x70, 0xd3, 0x2a, 0x60, 0x2c, 0x27, 0xc3, 0x8d, 0xb3, 0xde, 0x78, 0x56, 0x19, 0x57, 0x9d, 0xf1, 0x6c, 0xce, 0x47, 0x8d, 0x29, 0xc9, 0x49, 0x49, 0xf5, 0xb7, 0xb6, 0xb3, 0xb5, 0xbd, 0xce, 0xd6, 0x36, 0x35, 0xbe, 0x92, 0x44, 0x03, 0xdc, 0xa3, 0x4d, 0x5d, 0x33, 0xf5, 0xec, 0x8e, 0x6d, 0x00, 0x71, 0x11, 0x43, 0x5c, 0xc4, 0x10, 0x17, 0x31, 0xc4, 0x45, 0x0c, 0x31, 0x10, 0x43, 0x0c, 0xc4, 0x10, 0x03, 0x31, 0xc4, 0x40, 0x0c, 0x31, 0x10, 0x63, 0xcd, 0x86, 0x43, 0x88, 0x81, 0xc6, 0xd6, 0x08, 0x8b, 0xa2, 0x26, 0x03, 0x89, 0x81, 0xc6, 0xd4, 0x66, 0x20, 0x31, 0xd0, 0x98, 0x18, 0x30, + 0x8f, 0xbd, 0x34, 0xa5, 0x6d, 0xe1, 0xb4, 0xad, 0x27, 0x6d, 0xeb, 0x49, 0xdb, 0xa2, 0x68, 0x5b, 0x7c, 0xbd, 0x7e, 0x8f, 0xa1, 0xdf, 0x63, 0xe8, 0xf7, 0x18, 0xfa, 0x3d, 0x86, 0x7e, 0x8f, 0xa1, 0xdf, 0x63, 0xe8, 0xf7, 0x18, 0x5a, 0xd2, 0x8f, 0x96, 0xf4, 0xa3, 0xcf, 0x4d, 0xf5, 0x8d, 0xa1, 0xcf, 0x63, 0xe8, 0xf3, 0x18, 0xfa, 0x3c, 0x86, 0x3e, 0x8f, 0xa1, 0xcf, 0x63, 0xe8, 0xf3, 0x18, 0xfa, 0x3c, 0x86, 0x3e, 0x8f, 0xa1, 0xcf, 0x63, 0xea, 0x79, 0x93, 0x18, 0xfa, 0x2e, 0x86, 0xbe, 0x8b, 0xa1, 0xef, 0x62, 0xe8, 0xbb, 0x18, 0xfa, 0x2e, 0x86, 0xbe, 0x8b, 0xa1, 0xef, 0x62, 0xe8, 0xbb, 0x18, 0xfa, 0x2e, 0x86, 0xbe, 0x8b, 0xa1, 0xef, 0x62, 0xe8, 0xbb, 0x18, 0xfa, 0x2e, 0x46, 0x84, 0x89, 0x5e, 0xea, 0xae, 0xd7, 0xe6, 0x35, 0x72, 0x5d, 0xf4, 0xdf, 0x41, 0x75, 0x2c, 0xa7, 0x5c, + 0x9e, 0xa2, 0xd6, 0xa7, 0x6c, 0x33, 0x44, 0x2b, 0xdc, 0xb6, 0xcf, 0x78, 0x5b, 0x34, 0xc3, 0x8f, 0x9b, 0x57, 0xac, 0x5d, 0xca, 0xc8, 0xc9, 0x91, 0x5b, 0xc4, 0x4f, 0xe4, 0x48, 0x3f, 0xcb, 0x0d, 0xc2, 0xa5, 0x8e, 0x19, 0xfc, 0x28, 0x76, 0x91, 0x09, 0xe6, 0xc9, 0x22, 0xeb, 0x97, 0x23, 0xf9, 0xa2, 0x90, 0xd7, 0x8a, 0x40, 0x09, 0xf0, 0xca, 0x1f, 0xc4, 0x01, 0xf6, 0x30, 0x10, 0x7f, 0x9e, 0x01, 0x2e, 0x26, 0x5f, 0x1c, 0x2a, 0x77, 0x68, 0xc3, 0xc1, 0x18, 0xb9, 0x8b, 0xbd, 0x1e, 0x0a, 0x5a, 0x28, 0x2b, 0x82, 0x3e, 0x94, 0x3f, 0x06, 0x7d, 0x24, 0x8b, 0x82, 0x3e, 0xe5, 0xf9, 0x33, 0xb0, 0x42, 0xfe, 0x18, 0xf2, 0xab, 0xfc, 0x51, 0xcf, 0x92, 0x3f, 0x1a, 0xd7, 0x82, 0xeb, 0xc0, 0xbd, 0xe0, 0x3e, 0x70, 0x3f, 0x98, 0x08, 0x26, 0x81, 0xc9, 0x60, 0x0a, 0x78, 0x40, 0xfe, 0xe8, 0xe8, + 0x24, 0xb7, 0x38, 0x3a, 0x83, 0x2e, 0xa0, 0x2b, 0xe8, 0x06, 0xba, 0x83, 0x1e, 0x20, 0x0d, 0xf4, 0x04, 0xe7, 0x83, 0x0b, 0x40, 0x2f, 0xd0, 0x1b, 0xa4, 0x83, 0x3e, 0xa0, 0x2f, 0xe8, 0x07, 0xfa, 0x83, 0x01, 0x60, 0x20, 0x18, 0x04, 0x32, 0xc0, 0x85, 0x60, 0x30, 0xb8, 0x08, 0x0c, 0x01, 0x17, 0xcb, 0x03, 0x8e, 0xa1, 0x60, 0x18, 0x18, 0x0e, 0x46, 0x80, 0x4b, 0xc0, 0x48, 0x30, 0x0a, 0x8c, 0x06, 0xaf, 0xcb, 0x9d, 0x8e, 0x37, 0xc0, 0x9b, 0x60, 0x1e, 0x98, 0x0f, 0xde, 0x02, 0x6f, 0x83, 0x77, 0xc0, 0xbb, 0xe0, 0x3d, 0xf0, 0x3e, 0x58, 0x00, 0x3e, 0x00, 0x0b, 0xc1, 0x87, 0x60, 0x11, 0xf8, 0x08, 0x2c, 0x06, 0x1f, 0x83, 0x25, 0xe0, 0x13, 0xf0, 0x29, 0x58, 0x0a, 0x3e, 0x03, 0x7b, 0xe4, 0x0e, 0xa2, 0x9c, 0xdc, 0xec, 0x6f, 0xdf, 0xef, 0xe7, 0xef, 0xdf, 0x21, 0xa8, 0xa1, 0xfa, 0xc4, + 0x18, 0x3c, 0xc1, 0xe5, 0x28, 0xdd, 0xdf, 0xf9, 0x64, 0x13, 0x72, 0xa9, 0x04, 0x72, 0xa9, 0x04, 0x55, 0xc2, 0x68, 0xc6, 0xff, 0x18, 0xc6, 0xc5, 0xe5, 0x22, 0xe6, 0x6f, 0x95, 0xd2, 0xb8, 0x4e, 0x29, 0x63, 0x44, 0x1f, 0x4a, 0xb8, 0xe0, 0x6f, 0x95, 0x60, 0x32, 0xb5, 0xf8, 0x6f, 0xb7, 0xbb, 0xc1, 0x9f, 0xe5, 0x8d, 0x41, 0x47, 0x64, 0x61, 0x50, 0x85, 0x2c, 0x34, 0xaf, 0xd9, 0x5e, 0x9b, 0x43, 0xfe, 0xb7, 0xaf, 0x27, 0x7d, 0xae, 0x39, 0xec, 0x6c, 0x57, 0x8f, 0x5e, 0x65, 0x5d, 0x3d, 0xfa, 0x47, 0xf6, 0x95, 0xfd, 0x8f, 0xae, 0x1e, 0xdd, 0xb2, 0x5e, 0xa9, 0xfb, 0xb5, 0x74, 0x6a, 0xdc, 0x87, 0xb9, 0xa1, 0x2f, 0xcf, 0xfd, 0xd4, 0x15, 0x39, 0x4c, 0x1f, 0x15, 0x42, 0x6f, 0xda, 0x7e, 0x57, 0x3d, 0xf6, 0x74, 0x9d, 0x2c, 0xd7, 0xc6, 0xa9, 0xfb, 0x99, 0xca, 0x3f, 0xd8, 0xe3, 0x6f, + 0xec, 0xf1, 0xb7, 0x3a, 0x7b, 0x8c, 0xac, 0xb7, 0xc7, 0x42, 0xda, 0xe1, 0xa5, 0x1d, 0x65, 0xd6, 0xb5, 0x0a, 0x8f, 0x58, 0xa5, 0x17, 0x52, 0x7a, 0xa1, 0x3a, 0xe6, 0x7a, 0xee, 0xd2, 0x5d, 0x94, 0xee, 0xaa, 0x53, 0xba, 0x79, 0x4f, 0x80, 0x3d, 0xf4, 0xe3, 0x1e, 0xad, 0xa3, 0xf4, 0x6b, 0x9d, 0xe5, 0xbe, 0x3f, 0xba, 0xd6, 0x36, 0xfd, 0x78, 0x24, 0x38, 0x54, 0x1e, 0x0e, 0x1e, 0x21, 0xf7, 0x05, 0x8f, 0x94, 0xbf, 0x04, 0x8f, 0x96, 0x85, 0x35, 0x2c, 0xe1, 0x0b, 0x0b, 0x50, 0x97, 0x8a, 0xff, 0xd1, 0x55, 0xbb, 0x6b, 0x58, 0xf0, 0xd6, 0x96, 0x9a, 0xce, 0xd8, 0xee, 0xab, 0xae, 0xda, 0xf8, 0x77, 0x4b, 0xdf, 0x4e, 0xe9, 0xdb, 0xeb, 0x94, 0xde, 0x9c, 0xd2, 0x8b, 0x28, 0x3d, 0x8f, 0xd2, 0xbf, 0xa7, 0xf4, 0x1f, 0xb4, 0xc1, 0xc4, 0x5d, 0x75, 0x44, 0xd7, 0x5e, 0xcf, 0xdd, 0x2a, 0x79, + 0x23, 0x25, 0xff, 0x42, 0xc9, 0x25, 0x94, 0x5c, 0x62, 0x1d, 0x2f, 0x37, 0xaf, 0xdd, 0xe0, 0xa3, 0xe4, 0x55, 0x94, 0xbc, 0x8a, 0x92, 0x4f, 0x52, 0xb2, 0x79, 0x6c, 0x65, 0x6a, 0x9d, 0xab, 0xc2, 0x9b, 0xd7, 0x93, 0x5f, 0xa7, 0x1c, 0x41, 0xed, 0xf5, 0xb9, 0x14, 0xff, 0x1e, 0xf8, 0xf7, 0xaa, 0xeb, 0x6d, 0x75, 0x96, 0x07, 0xa8, 0xc1, 0x06, 0x6a, 0x60, 0x2a, 0xc1, 0x92, 0xc0, 0xeb, 0xd0, 0xb3, 0xf7, 0x9f, 0xac, 0x7b, 0x32, 0x54, 0x58, 0xfc, 0x17, 0xc2, 0xbf, 0xd9, 0x9e, 0x6c, 0xf6, 0x9a, 0x6d, 0xdd, 0x93, 0xa1, 0x8a, 0xb6, 0xd4, 0x5c, 0xed, 0x9d, 0xdc, 0x46, 0x5d, 0xf1, 0x9d, 0x2c, 0x11, 0x2d, 0x76, 0xaa, 0x2b, 0xbe, 0x5f, 0x4c, 0x46, 0x52, 0x7d, 0x06, 0x4a, 0x38, 0x91, 0x19, 0x1e, 0xf0, 0x3d, 0x48, 0x72, 0xfd, 0x2b, 0xc0, 0xd7, 0x5c, 0x79, 0xa8, 0xf6, 0x4a, 0xf0, 0x91, + 0x01, 0x25, 0x87, 0x59, 0x25, 0xdb, 0x29, 0xd9, 0x6e, 0x95, 0x1c, 0x4e, 0xc9, 0x61, 0x94, 0xec, 0x34, 0xaf, 0x84, 0x43, 0xc9, 0x06, 0x25, 0x1b, 0xea, 0x48, 0xc4, 0x99, 0x25, 0xeb, 0x94, 0xac, 0x1b, 0x4f, 0x8a, 0xc6, 0xb5, 0xa5, 0x37, 0xa8, 0x73, 0x95, 0xfa, 0xea, 0xab, 0xd3, 0x17, 0xd0, 0xfa, 0x5f, 0x68, 0xfd, 0xbe, 0x73, 0x5e, 0x9d, 0x3e, 0x84, 0xad, 0x77, 0x6b, 0xbd, 0x40, 0x6f, 0x50, 0x7d, 0x5d, 0xcd, 0xa3, 0x67, 0x2d, 0xeb, 0xcf, 0xaf, 0x74, 0xdf, 0x10, 0xee, 0xf7, 0xc3, 0xbd, 0x79, 0xa5, 0xaf, 0x9f, 0xf9, 0x44, 0x3e, 0x9f, 0xd8, 0xcd, 0x27, 0x3c, 0x70, 0x5f, 0x61, 0x1d, 0x39, 0x2b, 0xb5, 0x7a, 0xdd, 0xbc, 0x06, 0x67, 0x3e, 0xbc, 0xe7, 0xf3, 0xc9, 0x65, 0xaa, 0x17, 0xab, 0xe8, 0xc1, 0xca, 0xda, 0x12, 0xd2, 0x59, 0xef, 0xc3, 0x88, 0xea, 0xcb, 0x73, 0x3f, 0x9e, + 0xab, 0xe7, 0x1e, 0x53, 0xd3, 0xd7, 0x59, 0xf3, 0x5c, 0x6d, 0x09, 0x86, 0x97, 0x6c, 0xaa, 0x14, 0x94, 0x83, 0x23, 0x80, 0xf9, 0xae, 0xb6, 0x2e, 0x27, 0xf8, 0xe4, 0x09, 0x78, 0x0d, 0xe2, 0x93, 0xb2, 0xe6, 0x93, 0xd6, 0xd9, 0xee, 0x81, 0x75, 0x39, 0x48, 0x49, 0x07, 0x55, 0x5d, 0x02, 0x5b, 0xb1, 0xd9, 0xda, 0x67, 0xee, 0xef, 0x9f, 0x24, 0xc6, 0xcf, 0xd5, 0x8a, 0xc0, 0x4f, 0x9e, 0x51, 0xdb, 0x3f, 0xfc, 0x64, 0x67, 0xeb, 0x8e, 0x78, 0xe9, 0x38, 0xa0, 0x74, 0x72, 0x87, 0x74, 0x72, 0x87, 0x74, 0x72, 0x87, 0x74, 0x3c, 0x62, 0x3f, 0x3c, 0xe2, 0x30, 0x3c, 0xa2, 0x99, 0x35, 0x8f, 0x25, 0x72, 0x86, 0xe2, 0x11, 0x47, 0x10, 0x39, 0x7d, 0xf0, 0x88, 0x23, 0x88, 0x9e, 0x3e, 0xcc, 0x98, 0x89, 0x44, 0x4f, 0x5f, 0x5a, 0x39, 0xc2, 0xca, 0xc2, 0x46, 0xe3, 0x17, 0xaf, 0x20, 0x82, 0x2e, + 0x26, 0x82, 0x2e, 0xa6, 0xb5, 0xd7, 0xe2, 0x17, 0x7b, 0xa9, 0x3b, 0xeb, 0x75, 0x17, 0x63, 0x89, 0x9c, 0xb1, 0x68, 0x53, 0x3a, 0x7e, 0xb0, 0x15, 0xde, 0x3e, 0x1d, 0x6f, 0x9f, 0x8e, 0xb7, 0x4f, 0xc7, 0xdb, 0xdf, 0x61, 0xdd, 0x59, 0x2f, 0x1d, 0x6f, 0x9f, 0x8e, 0x1a, 0xa4, 0xe3, 0xed, 0xd3, 0xf1, 0xf6, 0xe9, 0x78, 0xfb, 0x74, 0x34, 0x2c, 0x1d, 0x75, 0x48, 0xc7, 0xdb, 0xa7, 0xa3, 0x10, 0xe9, 0x78, 0xfb, 0x74, 0xb4, 0x2c, 0x5d, 0xdd, 0x6b, 0xe0, 0xcc, 0xbb, 0xfc, 0x85, 0xd6, 0xbc, 0x42, 0x9d, 0x5a, 0x51, 0xa7, 0xb8, 0xda, 0x77, 0xd6, 0xa9, 0xd6, 0xf6, 0x61, 0x3c, 0x90, 0x47, 0xd2, 0xbe, 0x48, 0xd1, 0x1f, 0x0c, 0x00, 0x03, 0xc1, 0x20, 0x90, 0x01, 0x2e, 0x04, 0x83, 0xc1, 0x45, 0x60, 0x08, 0xb8, 0x18, 0x0c, 0x05, 0xc3, 0xc0, 0x70, 0x30, 0x02, 0x5c, 0x02, 0x46, 0x82, 0x51, + 0x80, 0xbc, 0x5b, 0x5c, 0x0a, 0xc6, 0x80, 0xcb, 0xc0, 0xe5, 0xe0, 0x0a, 0x70, 0x25, 0x18, 0x0b, 0xae, 0x02, 0x57, 0x83, 0x6b, 0xc0, 0xb5, 0xe0, 0x46, 0xf6, 0x72, 0x13, 0xb8, 0x19, 0xdc, 0x0a, 0x6e, 0x03, 0x8f, 0xf0, 0xfa, 0x34, 0x14, 0xf6, 0x51, 0xf0, 0x18, 0x78, 0x1c, 0x4c, 0x07, 0x4f, 0x80, 0x27, 0xc1, 0x53, 0xe0, 0x69, 0xf0, 0x0c, 0x98, 0x29, 0x3a, 0x89, 0xe7, 0xc8, 0xee, 0x9e, 0x17, 0xed, 0xc5, 0xbf, 0x44, 0x57, 0x7a, 0xaa, 0x3d, 0x3d, 0xf5, 0x08, 0x3d, 0x75, 0x3e, 0x3d, 0x35, 0x9c, 0x9e, 0x1a, 0x41, 0x4f, 0x4d, 0xa1, 0xa7, 0x7a, 0xd2, 0x53, 0x53, 0xe8, 0xa9, 0x9e, 0xf4, 0x54, 0x4f, 0x7a, 0xea, 0x7c, 0x7a, 0x2a, 0x03, 0x56, 0x22, 0x61, 0x25, 0x9a, 0x9e, 0x1a, 0x4e, 0x4f, 0x5d, 0x4f, 0x4f, 0x5d, 0x4f, 0x4f, 0x8d, 0xa2, 0xa7, 0x46, 0xd9, 0xd8, 0xb7, 0x8d, 0x7d, + 0x9b, 0x8c, 0xd9, 0xe6, 0x8a, 0xae, 0xb6, 0xf7, 0x44, 0xa4, 0xed, 0x7d, 0xb0, 0x00, 0x7c, 0x00, 0x16, 0x82, 0x0f, 0xc1, 0x22, 0xf0, 0x11, 0x58, 0x0c, 0x3e, 0x06, 0x4b, 0xf8, 0xcc, 0x27, 0xa2, 0x19, 0xbd, 0xfc, 0x20, 0xbd, 0xfc, 0x60, 0xc8, 0x77, 0x22, 0x32, 0x64, 0x0d, 0xf8, 0x41, 0x64, 0x84, 0x6c, 0xe1, 0x79, 0x2b, 0xd8, 0x06, 0xb6, 0x83, 0x1c, 0x70, 0x4c, 0x9c, 0x1f, 0x8a, 0x33, 0xd1, 0x7d, 0x22, 0x52, 0x3f, 0x04, 0x0e, 0x83, 0x72, 0x70, 0x04, 0x54, 0x80, 0xa3, 0xe0, 0x18, 0xf0, 0x83, 0x4a, 0xf0, 0x2b, 0x38, 0x0e, 0x4e, 0x80, 0x2a, 0x70, 0x12, 0x9c, 0x02, 0xbf, 0x81, 0xd3, 0x22, 0x92, 0x68, 0xca, 0x20, 0x9a, 0x32, 0x88, 0xa6, 0x8c, 0xea, 0x33, 0x63, 0x41, 0x1c, 0x88, 0x07, 0x09, 0xa0, 0xfa, 0x3b, 0x87, 0x0c, 0xa2, 0x29, 0x83, 0x68, 0xca, 0x20, 0x9a, 0x32, 0x88, + 0xa6, 0x0c, 0xa2, 0x29, 0x83, 0x68, 0xca, 0x20, 0x9a, 0x32, 0x8c, 0xd7, 0x71, 0x4a, 0x6f, 0x80, 0x37, 0xc1, 0x3c, 0x30, 0x1f, 0xbc, 0x05, 0xc8, 0x13, 0x8c, 0x77, 0xc0, 0xbb, 0xe0, 0x3d, 0xf0, 0x3e, 0x58, 0x00, 0x3e, 0x00, 0x0b, 0xc1, 0x87, 0x60, 0x11, 0xf8, 0x08, 0x2c, 0x06, 0x1f, 0x03, 0xf8, 0x30, 0xe0, 0xc3, 0xf8, 0x14, 0x2c, 0x05, 0x9f, 0x81, 0x65, 0x60, 0x39, 0xf8, 0x37, 0x58, 0x01, 0x3e, 0x07, 0x5f, 0x80, 0x2f, 0xc1, 0x57, 0x60, 0x25, 0xf8, 0x1a, 0x7c, 0x03, 0x32, 0xc1, 0x7f, 0xc0, 0xb7, 0x60, 0x15, 0x70, 0x89, 0x4e, 0xc6, 0x4e, 0xb0, 0x8b, 0x8c, 0x36, 0x0f, 0xec, 0x06, 0x1e, 0xb0, 0x07, 0xec, 0x05, 0xf9, 0xa0, 0x40, 0xb4, 0x37, 0xf6, 0x81, 0x22, 0xd1, 0xd5, 0x28, 0x06, 0xfb, 0x41, 0x09, 0x28, 0x03, 0x07, 0x80, 0x0f, 0x1c, 0x04, 0x87, 0xc0, 0x61, 0xd1, 0x55, + 0x8d, 0x08, 0xbd, 0x8e, 0x0b, 0x8b, 0x13, 0x71, 0xb0, 0xa5, 0x05, 0x7c, 0x33, 0xd3, 0x5a, 0x5c, 0xc0, 0x1c, 0x91, 0x4a, 0x46, 0x99, 0x4a, 0x46, 0x99, 0x4a, 0x46, 0x99, 0x4a, 0x46, 0x99, 0x6a, 0x7d, 0x67, 0x94, 0x4a, 0x46, 0x99, 0x7a, 0x96, 0xef, 0x8c, 0x52, 0xc9, 0x08, 0x53, 0xc9, 0x08, 0x53, 0xc9, 0x08, 0x53, 0xc9, 0x08, 0x53, 0xc9, 0x08, 0x53, 0xc9, 0x08, 0x53, 0xc9, 0x02, 0x53, 0xf5, 0x37, 0x44, 0x43, 0x32, 0xc1, 0x54, 0x32, 0xc1, 0x54, 0x32, 0xc1, 0x54, 0x32, 0xc1, 0x54, 0x32, 0xc1, 0x54, 0x32, 0xc1, 0x54, 0x32, 0xc1, 0x54, 0x32, 0xc1, 0x54, 0x32, 0xc1, 0x54, 0xfa, 0x71, 0x3e, 0x35, 0x6a, 0x40, 0x1f, 0xf6, 0xa4, 0x56, 0x23, 0xe9, 0xc3, 0xe1, 0xf4, 0xe1, 0xb4, 0x80, 0xda, 0x35, 0x25, 0x43, 0x4c, 0x25, 0x43, 0x4c, 0x25, 0x43, 0x4c, 0x25, 0x43, 0x4c, 0x25, 0x43, + 0x4c, 0x25, 0x43, 0x4c, 0x25, 0x43, 0x4c, 0x25, 0x43, 0x4c, 0x25, 0x43, 0x4c, 0x25, 0x43, 0x4c, 0x25, 0x43, 0x4c, 0x25, 0x43, 0x4c, 0x25, 0x43, 0x4c, 0x25, 0x43, 0x4c, 0x45, 0xbf, 0x06, 0x1a, 0xb3, 0xe1, 0xec, 0x98, 0x88, 0x40, 0x45, 0x6e, 0x54, 0xdf, 0x8c, 0x05, 0x1e, 0xd7, 0x7f, 0xc4, 0x3a, 0xae, 0xaf, 0x05, 0x1c, 0xd7, 0x77, 0xfe, 0xd5, 0x63, 0xf2, 0xea, 0x38, 0xfc, 0x3c, 0xd1, 0x24, 0xf0, 0x58, 0xbc, 0x2e, 0x85, 0x71, 0xce, 0xe3, 0xf1, 0xb1, 0xea, 0x9b, 0xa4, 0xaf, 0x68, 0xe9, 0xbf, 0x68, 0xe9, 0x2d, 0xb4, 0xf4, 0x6e, 0x5a, 0xda, 0x29, 0xa0, 0xa5, 0xff, 0xf9, 0xa7, 0xc7, 0xe9, 0x8d, 0x59, 0xa2, 0x23, 0x2d, 0x1c, 0xa0, 0xce, 0x3d, 0x09, 0x3c, 0x5e, 0x7f, 0x2d, 0xad, 0x8e, 0x66, 0x1e, 0x8a, 0xb6, 0xee, 0x83, 0x1a, 0x6d, 0xde, 0x07, 0xd5, 0x3a, 0x5e, 0xaf, 0x05, + 0x1c, 0xaf, 0xd7, 0xfe, 0xf2, 0xb1, 0xf6, 0x1f, 0xd4, 0xf7, 0x32, 0x7f, 0xf5, 0x18, 0x7b, 0x34, 0xe3, 0x35, 0x9a, 0xf1, 0x1a, 0x4d, 0x3f, 0xf7, 0xb3, 0x66, 0xff, 0x5b, 0x60, 0x60, 0x2a, 0x0c, 0x4c, 0x82, 0x81, 0x47, 0x60, 0xe0, 0x1a, 0xc6, 0x6b, 0x34, 0xe3, 0x35, 0xda, 0x3a, 0xb2, 0x13, 0x6d, 0x1d, 0xd9, 0x89, 0x86, 0x91, 0x3b, 0xff, 0xe9, 0x31, 0x76, 0xd8, 0xe8, 0x25, 0x1a, 0xc2, 0xc0, 0x45, 0xf5, 0xbe, 0x2b, 0xec, 0x4d, 0x0d, 0xee, 0xa6, 0x06, 0xcf, 0x53, 0x83, 0x47, 0xa8, 0xc1, 0xb3, 0xd4, 0x60, 0x52, 0x40, 0x1f, 0xdc, 0xcf, 0x27, 0x7b, 0xa2, 0x0c, 0x9d, 0x45, 0x18, 0x3c, 0x86, 0xc1, 0x63, 0x18, 0xa5, 0x0c, 0x83, 0xc7, 0x26, 0xf0, 0xd8, 0xc4, 0xe2, 0xb1, 0x89, 0xc5, 0x63, 0x98, 0xc5, 0x63, 0x18, 0x3c, 0x86, 0x9d, 0x85, 0xc7, 0x30, 0x78, 0x0c, 0x83, 0xc7, 0x30, + 0x78, 0x0c, 0x83, 0xc7, 0x30, 0x78, 0x0c, 0x83, 0xc7, 0x30, 0x78, 0x34, 0x8f, 0x90, 0x99, 0xf7, 0x1c, 0x0e, 0x83, 0xc7, 0x30, 0x78, 0x0c, 0x83, 0xc7, 0x30, 0x78, 0x0c, 0x83, 0xc7, 0x30, 0x78, 0x0c, 0x83, 0xc7, 0x30, 0x78, 0x0c, 0x83, 0xc7, 0x26, 0xf0, 0xd8, 0x04, 0x1e, 0x9b, 0xd0, 0x82, 0x07, 0x2c, 0x1e, 0xcf, 0xa7, 0x15, 0xf7, 0x59, 0x3c, 0x3e, 0x4f, 0x2b, 0x66, 0xc2, 0xa3, 0x79, 0xdd, 0xcb, 0x26, 0x16, 0x8f, 0x4d, 0x2c, 0x1e, 0xcd, 0x2b, 0x08, 0xf4, 0x81, 0xc7, 0x30, 0x78, 0x0c, 0x83, 0xc7, 0x30, 0x78, 0x0c, 0x83, 0xc7, 0x30, 0x78, 0x0c, 0x83, 0xc7, 0x30, 0x78, 0x0c, 0x83, 0xc7, 0x30, 0x78, 0x0c, 0x83, 0xc7, 0x30, 0x78, 0x0c, 0x83, 0xc7, 0x30, 0x78, 0x0c, 0x63, 0x0c, 0x75, 0x66, 0x0c, 0x45, 0x31, 0x86, 0xec, 0x30, 0x33, 0x46, 0x9d, 0xd1, 0x34, 0xa0, 0x1e, 0xa7, + 0x77, 0x51, 0x9b, 0x4e, 0xd4, 0xe6, 0x6a, 0x6a, 0x73, 0x0d, 0xb5, 0x99, 0x54, 0xef, 0x9b, 0x5f, 0xf3, 0xfe, 0xbf, 0x6d, 0x28, 0xc5, 0xf4, 0xaa, 0x43, 0x14, 0xb7, 0xf1, 0x70, 0x1b, 0x0f, 0xb7, 0xf1, 0x94, 0xd6, 0x0e, 0x6e, 0x1d, 0x70, 0xeb, 0xb0, 0xb8, 0x75, 0x58, 0xdc, 0xc6, 0x5b, 0xdc, 0xc6, 0xc3, 0x6d, 0xfc, 0x59, 0xb8, 0x8d, 0x87, 0xdb, 0x78, 0xb8, 0x8d, 0x87, 0xdb, 0x78, 0xb8, 0x8d, 0x87, 0xdb, 0x78, 0xb8, 0x8d, 0x87, 0x5b, 0x07, 0xdc, 0xc6, 0xc3, 0x6d, 0x3c, 0xdc, 0xc6, 0xc3, 0x6d, 0x3c, 0xdc, 0xc6, 0xc3, 0x6d, 0x3c, 0xdc, 0xc6, 0xc3, 0x6d, 0x3c, 0xdc, 0xc6, 0xc3, 0x6d, 0x3c, 0xdc, 0x3a, 0xe0, 0xd6, 0x01, 0xb7, 0x0e, 0x5a, 0xf2, 0x9c, 0xc5, 0xad, 0x83, 0xd6, 0xf4, 0xa5, 0x35, 0x97, 0xd3, 0x9a, 0x31, 0xb4, 0xe6, 0x35, 0xb8, 0x75, 0xc0, 0xad, 0xc3, 0xe2, 0xd6, + 0x61, 0x71, 0xeb, 0xa0, 0x75, 0xcd, 0xe0, 0x36, 0x1e, 0x6e, 0xe3, 0xe1, 0x36, 0x1e, 0x6e, 0xe3, 0xe1, 0x36, 0x1e, 0x6e, 0xe3, 0xe1, 0x36, 0x1e, 0x6e, 0xe3, 0xe1, 0x36, 0x1e, 0x6e, 0xe3, 0xe1, 0x36, 0x1e, 0x6e, 0xe3, 0xe1, 0x36, 0x1e, 0x6e, 0xe3, 0x61, 0x65, 0x10, 0xac, 0xb4, 0x87, 0xdb, 0x16, 0x30, 0x73, 0x83, 0x72, 0x8d, 0xbf, 0xf3, 0x1a, 0x4f, 0x24, 0x9b, 0xbf, 0xfb, 0xa8, 0xc9, 0x98, 0x2f, 0x84, 0xb7, 0x58, 0x78, 0x8b, 0x85, 0xb7, 0xc1, 0x6c, 0x7d, 0x0f, 0xbc, 0xb5, 0x87, 0xb7, 0xf6, 0x16, 0x6f, 0xed, 0x2d, 0xde, 0x62, 0x2d, 0xde, 0x62, 0xe1, 0x2d, 0xf6, 0x2c, 0xbc, 0xc5, 0xc2, 0x5b, 0x2c, 0xbc, 0xc5, 0xc2, 0x5b, 0x2c, 0xbc, 0xc5, 0xc2, 0x5b, 0x2c, 0xbc, 0xc5, 0xc2, 0x5b, 0x7b, 0x78, 0x8b, 0xd5, 0xdf, 0x52, 0xdf, 0xf6, 0xc4, 0xc2, 0x5d, 0x2c, 0xdc, 0xc5, 0xc2, + 0x5d, 0x2c, 0xdc, 0xc5, 0xc2, 0x5d, 0x2c, 0xdc, 0xc5, 0xc2, 0x5d, 0x2c, 0xdc, 0xc5, 0xc2, 0x5d, 0x7b, 0xb8, 0x6b, 0x0f, 0x77, 0xed, 0xe1, 0x6e, 0xb4, 0xc5, 0xdd, 0x83, 0xd4, 0x7c, 0x1e, 0x35, 0x7f, 0x19, 0xee, 0xde, 0x86, 0xbb, 0xc7, 0xe0, 0xae, 0x3d, 0xdc, 0xb5, 0xb7, 0xb8, 0x6b, 0x6f, 0x71, 0xd7, 0x1e, 0xee, 0xa6, 0xc1, 0x5d, 0x2c, 0xdc, 0xc5, 0xc2, 0x5d, 0x2c, 0xdc, 0xc5, 0xc2, 0x5d, 0x2c, 0xdc, 0xc5, 0xc2, 0x5d, 0x2c, 0xdc, 0xc5, 0xc2, 0x5d, 0x2c, 0xdc, 0xc5, 0xc2, 0x5d, 0x2c, 0xdc, 0xc5, 0xc2, 0x5d, 0x2c, 0xdc, 0xc5, 0xc2, 0x9d, 0x19, 0x4d, 0xc3, 0x45, 0x23, 0x98, 0xb8, 0xa9, 0x5e, 0x3c, 0x9a, 0xc7, 0x44, 0x66, 0x51, 0x8b, 0xa7, 0xa8, 0xc5, 0x0d, 0xd4, 0xe2, 0x21, 0x6a, 0x31, 0x24, 0x20, 0x1e, 0x5f, 0x62, 0xbe, 0x34, 0xf8, 0x74, 0xa2, 0x1a, 0xdb, 0x3d, 0xea, + 0xa9, 0xe3, 0x8d, 0x7c, 0xf2, 0x22, 0x3e, 0xd9, 0x95, 0x4f, 0x76, 0xe2, 0x93, 0xf6, 0x80, 0x4f, 0xde, 0x76, 0x86, 0x96, 0xb6, 0xa8, 0xa7, 0xa5, 0xf1, 0x4a, 0x4b, 0x93, 0xe8, 0xa7, 0x24, 0xfa, 0x29, 0x89, 0xf2, 0x93, 0xe8, 0x93, 0x24, 0xab, 0x4f, 0x92, 0xe8, 0x93, 0xa4, 0xb3, 0xf4, 0x49, 0x12, 0x7d, 0x92, 0x44, 0x9f, 0x24, 0xd1, 0x27, 0x49, 0xf4, 0x49, 0x12, 0x7d, 0x92, 0x44, 0x9f, 0x24, 0xd1, 0x1f, 0x49, 0x68, 0xa9, 0x39, 0xdf, 0x26, 0xd1, 0x1f, 0x49, 0xf4, 0x47, 0x12, 0xfd, 0x91, 0x44, 0x7f, 0x24, 0xd1, 0x1f, 0x49, 0xf4, 0x47, 0x12, 0xfd, 0x91, 0x44, 0x7f, 0x24, 0xd1, 0xf2, 0x37, 0xac, 0xd8, 0xed, 0x4e, 0xad, 0x86, 0x53, 0xff, 0x21, 0xd4, 0xff, 0xa1, 0x80, 0xda, 0x35, 0x84, 0xef, 0x24, 0xf8, 0x4e, 0x82, 0xef, 0x24, 0xf8, 0x4e, 0x82, 0xef, 0x24, 0xf8, 0x4e, 0x82, + 0xef, 0x24, 0xf8, 0x4e, 0x82, 0xef, 0x24, 0xf8, 0x4e, 0x82, 0xef, 0x24, 0xf8, 0x4e, 0x82, 0xef, 0x24, 0xf8, 0x4e, 0x82, 0xef, 0xbe, 0xc4, 0x6a, 0x32, 0xb1, 0xda, 0x04, 0xe6, 0xc6, 0xa9, 0x3b, 0xb0, 0xd7, 0x44, 0x63, 0x2c, 0xad, 0x9c, 0x7a, 0x96, 0xc8, 0xb3, 0xff, 0xd5, 0xc8, 0x53, 0x51, 0x37, 0x4f, 0x34, 0xf8, 0x4b, 0x51, 0x57, 0xfd, 0x4d, 0xed, 0xe7, 0xb4, 0x72, 0xa6, 0xd5, 0xbf, 0x77, 0xd0, 0xca, 0xf6, 0x01, 0xad, 0xfc, 0xfa, 0x1f, 0x47, 0xd5, 0x2c, 0xd1, 0x9e, 0xd6, 0xf5, 0x51, 0x3a, 0x6a, 0xa7, 0x75, 0x76, 0x5a, 0x67, 0xa7, 0x75, 0x63, 0x19, 0x6b, 0xcd, 0x19, 0x6b, 0xcd, 0xad, 0xb1, 0xd6, 0xdc, 0x1a, 0x6b, 0x76, 0xab, 0xc5, 0x76, 0x5a, 0x6c, 0x3f, 0x4b, 0xbf, 0xda, 0x69, 0xb1, 0x9d, 0x16, 0xdb, 0x69, 0xb1, 0x9d, 0x16, 0xdb, 0x69, 0xb1, 0x9d, 0x16, 0xdb, 0x19, + 0x6b, 0xcd, 0x69, 0xb5, 0x79, 0x5e, 0x8d, 0x9d, 0x16, 0xdb, 0x69, 0xb1, 0x9d, 0x16, 0xdb, 0x69, 0xb1, 0x9d, 0x16, 0xdb, 0x69, 0xb1, 0x9d, 0x16, 0xdb, 0x69, 0xb1, 0x9d, 0x71, 0xd6, 0x9c, 0x71, 0xd6, 0x9c, 0x71, 0xd6, 0x5c, 0x29, 0x58, 0xf5, 0x38, 0xbb, 0x01, 0x06, 0x26, 0xc3, 0xc0, 0xbd, 0x30, 0x30, 0x15, 0x06, 0xc6, 0x32, 0xce, 0x9a, 0x33, 0xce, 0x9a, 0x5b, 0xe3, 0xac, 0xb9, 0x35, 0xce, 0x9a, 0xc3, 0xc8, 0xad, 0x30, 0x62, 0x87, 0x11, 0xbb, 0x61, 0x7e, 0xe7, 0x90, 0x06, 0x7a, 0x82, 0x5e, 0xa0, 0x37, 0x48, 0x07, 0x7d, 0x40, 0x5f, 0xd0, 0x0f, 0xf4, 0x07, 0x03, 0xc0, 0x40, 0x35, 0xe7, 0xa7, 0x29, 0x1d, 0xcd, 0xa8, 0x37, 0xc6, 0x7a, 0x52, 0x83, 0x3b, 0xa8, 0xc1, 0xb3, 0xd4, 0x60, 0x2a, 0x35, 0x78, 0x9a, 0x1a, 0xdc, 0x17, 0xd0, 0x07, 0x13, 0xf8, 0x64, 0x77, 0x35, 0x67, + 0xe9, 0xf0, 0xa8, 0xc3, 0xa3, 0x4e, 0x29, 0x43, 0xe0, 0xb1, 0x21, 0x3c, 0x36, 0xb4, 0x78, 0x6c, 0x68, 0xf1, 0xa8, 0x5b, 0x3c, 0xea, 0xf0, 0xa8, 0x9f, 0x85, 0x47, 0x1d, 0x1e, 0x75, 0x78, 0xd4, 0xe1, 0x51, 0x87, 0x47, 0x1d, 0x1e, 0x75, 0x78, 0xd4, 0xe1, 0xb1, 0x21, 0x3c, 0xea, 0xf0, 0xa8, 0xc3, 0xa3, 0x0e, 0x8f, 0x3a, 0x3c, 0xea, 0xf0, 0xa8, 0xc3, 0xa3, 0x0e, 0x8f, 0x3a, 0x3c, 0xea, 0xf0, 0xa8, 0xc3, 0x63, 0x43, 0x78, 0x6c, 0x08, 0x8f, 0x0d, 0x69, 0xc1, 0x24, 0x8b, 0xc7, 0x1e, 0xb4, 0x62, 0x02, 0xad, 0xb8, 0xdf, 0x72, 0x03, 0xcf, 0xc0, 0x63, 0x43, 0x78, 0x6c, 0x68, 0xf1, 0xd8, 0xd0, 0xe2, 0xd1, 0xbc, 0xd6, 0x78, 0x2f, 0x78, 0xd4, 0xe1, 0x51, 0x37, 0xcc, 0xe3, 0x23, 0x69, 0xa0, 0x27, 0xe8, 0x05, 0x7a, 0x83, 0x74, 0xd0, 0x07, 0xf4, 0x05, 0xfd, 0x40, 0x7f, 0x30, 0x00, + 0x0c, 0x54, 0xc7, 0x52, 0x3a, 0x30, 0x7e, 0xc2, 0x61, 0x65, 0xa4, 0xd2, 0xd0, 0xbe, 0xf5, 0xf8, 0xbc, 0x9d, 0x9a, 0xb4, 0xa7, 0x26, 0x57, 0x50, 0x93, 0xb1, 0xd4, 0xe4, 0x3e, 0x6a, 0x72, 0x7f, 0x00, 0x9f, 0x9d, 0x29, 0xa1, 0x15, 0x25, 0x38, 0x28, 0x21, 0x43, 0x39, 0xd9, 0xc0, 0x6f, 0x80, 0x92, 0xad, 0x6f, 0x80, 0xb4, 0x80, 0x6f, 0x80, 0xb4, 0xbf, 0xfa, 0x0d, 0xce, 0x5f, 0xfe, 0xd6, 0xa6, 0x25, 0xdc, 0x54, 0x9f, 0xfd, 0xdc, 0x9b, 0x5a, 0x8e, 0xa6, 0x96, 0xa3, 0xa8, 0xe5, 0x2b, 0x01, 0xb5, 0x6c, 0xfc, 0x4f, 0xbf, 0xcd, 0xa1, 0x75, 0xfd, 0x68, 0x5d, 0x0a, 0xf3, 0x4b, 0x73, 0x35, 0xbf, 0xd4, 0xd7, 0xc2, 0xba, 0xdf, 0x94, 0xd5, 0x3f, 0xae, 0xac, 0xd5, 0x39, 0x9b, 0xa9, 0xe6, 0x6c, 0xcc, 0x41, 0x30, 0x73, 0x27, 0x11, 0x97, 0x4a, 0xc4, 0xa5, 0x5a, 0x11, 0x97, 0x6a, 0x45, + 0x5c, 0xb4, 0xc5, 0x56, 0xcd, 0x19, 0x9a, 0xf5, 0xd9, 0xfa, 0xfd, 0xec, 0xca, 0xfa, 0x67, 0x56, 0xfe, 0xa0, 0x32, 0x9c, 0xbf, 0x76, 0x46, 0x65, 0x24, 0x59, 0x48, 0x34, 0x68, 0xa1, 0x32, 0x9d, 0x11, 0x56, 0xc4, 0x4d, 0xa2, 0x3d, 0xaf, 0x51, 0xeb, 0x17, 0x60, 0x70, 0x1e, 0x0c, 0x3e, 0x42, 0xc4, 0xa5, 0x12, 0x71, 0xa9, 0x56, 0xc4, 0xa5, 0x5a, 0x11, 0x97, 0x0a, 0xa3, 0x53, 0xff, 0xe9, 0x59, 0x99, 0x30, 0x6a, 0x28, 0xbf, 0xc5, 0x1c, 0x25, 0x4f, 0x9c, 0x93, 0x2f, 0x73, 0x6c, 0x8f, 0xaf, 0x17, 0x8b, 0x4d, 0xa8, 0xe3, 0x73, 0xd4, 0x71, 0x3a, 0x5b, 0xde, 0x48, 0x1d, 0x1f, 0x50, 0x59, 0xf5, 0xef, 0xbd, 0x3c, 0x87, 0x72, 0x63, 0x55, 0x7e, 0xd1, 0xb5, 0x9e, 0x1a, 0x8e, 0xb3, 0x72, 0xf1, 0x8e, 0x7c, 0x2a, 0x70, 0x46, 0xbe, 0xa9, 0xce, 0xbd, 0x70, 0x7f, 0xb2, 0xbe, 0x75, 0xdd, + 0x71, 0xce, 0x7b, 0xe1, 0xea, 0xd6, 0xd6, 0x07, 0xd8, 0xfa, 0xc7, 0xc0, 0xef, 0x68, 0x6b, 0xce, 0x6f, 0x50, 0xe7, 0x62, 0x5f, 0xab, 0x8d, 0x16, 0xf7, 0x6b, 0x63, 0xc4, 0x93, 0xda, 0xe5, 0xe2, 0xf1, 0xbf, 0x79, 0xb7, 0xdd, 0x30, 0x61, 0x93, 0xdb, 0xcd, 0x2b, 0x2d, 0x68, 0x3d, 0xe5, 0x37, 0xda, 0x60, 0xf9, 0x39, 0x9f, 0xf8, 0xa9, 0xf6, 0x7b, 0x8e, 0x72, 0x59, 0x60, 0x5d, 0x81, 0xfe, 0xa0, 0x6d, 0xa5, 0x3c, 0xc0, 0x27, 0xbc, 0xea, 0xde, 0x98, 0x87, 0xb4, 0x5e, 0xf2, 0xa0, 0xd6, 0x1b, 0x8c, 0xa6, 0xac, 0xea, 0x23, 0xfc, 0x47, 0x55, 0x59, 0x3b, 0x29, 0x6b, 0x3f, 0x5b, 0xe4, 0x50, 0xd6, 0x8f, 0xd6, 0x55, 0xc4, 0xcc, 0xbb, 0x83, 0xee, 0x0c, 0xb8, 0x17, 0x41, 0x25, 0x65, 0x1d, 0xa7, 0xac, 0x52, 0xe1, 0xd0, 0x1a, 0xf1, 0xf9, 0xc6, 0x30, 0xc0, 0xdc, 0x44, 0x7d, 0x0f, 0x58, + 0xdf, 0x61, 0x1e, 0x0a, 0xaa, 0x10, 0x46, 0xf0, 0x65, 0x32, 0x8f, 0x76, 0x56, 0xd1, 0xce, 0x02, 0x75, 0xad, 0xf4, 0xcd, 0x81, 0x5b, 0xd4, 0x9c, 0xb1, 0x6c, 0x7d, 0xc3, 0x79, 0xc8, 0x3c, 0x72, 0x41, 0x99, 0x45, 0xf4, 0xa4, 0x4d, 0x6e, 0xb3, 0x5a, 0xf4, 0xa5, 0xd6, 0x5f, 0x7e, 0xa7, 0x0d, 0x00, 0x83, 0xe5, 0x17, 0xd4, 0xc6, 0x65, 0x7d, 0xab, 0xb2, 0x9d, 0x4f, 0x17, 0xf2, 0xe9, 0x42, 0x15, 0xe5, 0x2b, 0xa5, 0x4f, 0xb5, 0x2c, 0xac, 0xce, 0x27, 0xff, 0xca, 0x27, 0x1a, 0x52, 0xf7, 0xbd, 0x5a, 0xb8, 0x7c, 0x5f, 0xeb, 0x28, 0xf7, 0x9d, 0xed, 0x53, 0xb4, 0x23, 0x3c, 0x78, 0x84, 0x5c, 0x60, 0xde, 0x95, 0xc9, 0x3a, 0xe7, 0xfd, 0x94, 0xf1, 0x82, 0x3c, 0x74, 0xce, 0x4f, 0xfa, 0xf9, 0x64, 0x25, 0x9f, 0xac, 0x3c, 0xe7, 0x27, 0x03, 0xef, 0xf2, 0x90, 0x77, 0xd6, 0xbb, 0x3c, 0xfc, + 0xf9, 0x7d, 0x20, 0x6e, 0x16, 0x47, 0xe5, 0xd3, 0xe2, 0x98, 0x3c, 0x2e, 0x8e, 0xcb, 0xd7, 0xc4, 0x09, 0xf9, 0x9a, 0xe6, 0xe4, 0xdd, 0x46, 0x7c, 0xaa, 0x09, 0xb5, 0x69, 0x0a, 0x9a, 0x81, 0x70, 0x79, 0x9d, 0x16, 0xc1, 0x98, 0x69, 0x2e, 0x8f, 0x68, 0x91, 0xfc, 0x1f, 0x05, 0xa2, 0xe5, 0x65, 0x5a, 0x0b, 0x10, 0xc3, 0x7a, 0x4b, 0x10, 0x0b, 0xe2, 0x88, 0x94, 0x78, 0x9e, 0x5b, 0xf1, 0xdc, 0x9a, 0xe7, 0x24, 0x90, 0x0c, 0x52, 0x40, 0x5b, 0xd0, 0x0e, 0xb4, 0xa7, 0x65, 0x1d, 0x78, 0xff, 0x3c, 0xd6, 0x3b, 0xca, 0xef, 0x83, 0x32, 0xe5, 0xf1, 0xa0, 0x2d, 0xf2, 0xb5, 0xa0, 0x3d, 0xa0, 0x50, 0xee, 0x0b, 0x2a, 0x02, 0xc5, 0x60, 0x3f, 0x28, 0x91, 0x27, 0x82, 0xbc, 0x3c, 0x97, 0x82, 0x32, 0xe0, 0x03, 0x07, 0xc1, 0x21, 0x70, 0x58, 0x6e, 0x09, 0xfa, 0x95, 0xe7, 0x2a, 0x70, 0x52, 0x5e, + 0x16, 0x74, 0x8a, 0xe7, 0xdf, 0x80, 0x94, 0xfb, 0x82, 0x6d, 0x72, 0x4b, 0x70, 0x10, 0xcf, 0xc1, 0xc0, 0xce, 0xba, 0x43, 0x5e, 0x16, 0xec, 0x64, 0x7d, 0x38, 0xb8, 0x44, 0xdd, 0x93, 0x65, 0x5f, 0xf0, 0xa5, 0xbc, 0x3e, 0x06, 0x10, 0xdd, 0x21, 0x07, 0xe5, 0x91, 0x90, 0x43, 0x72, 0x8b, 0x3d, 0x4d, 0xbe, 0x69, 0xbf, 0x1e, 0x8c, 0x97, 0x6f, 0xea, 0xef, 0x4b, 0x97, 0xfe, 0x05, 0xf8, 0x12, 0xfc, 0x07, 0xac, 0x93, 0x5b, 0xf4, 0x2d, 0x60, 0x27, 0xd8, 0x05, 0x4a, 0xe5, 0x16, 0xc3, 0x01, 0x68, 0xa7, 0x31, 0x08, 0x8c, 0x63, 0xfd, 0x41, 0xf0, 0x08, 0xeb, 0xd3, 0xe5, 0x3e, 0xc7, 0xbf, 0xe5, 0x71, 0xc7, 0x0a, 0xf0, 0x03, 0xeb, 0xe6, 0x37, 0x80, 0xeb, 0xc1, 0x46, 0x79, 0xc4, 0xf1, 0x23, 0xff, 0x6f, 0x02, 0x9b, 0xc1, 0x16, 0xb0, 0x15, 0xe4, 0x82, 0x9f, 0xa4, 0xdf, 0xb9, 0x8a, 0x71, + 0x68, 0xe8, 0x97, 0xca, 0x1d, 0xfa, 0x74, 0xf9, 0x9d, 0xfe, 0x84, 0x3c, 0xa8, 0x3f, 0xc9, 0xf3, 0x4c, 0x9e, 0x67, 0x81, 0xd9, 0x60, 0x0e, 0xff, 0xbf, 0x20, 0xbf, 0x13, 0xef, 0x8b, 0x8d, 0xf2, 0x94, 0xc8, 0x96, 0x27, 0xc4, 0x36, 0xfa, 0x2e, 0x47, 0x96, 0x8b, 0x5c, 0xd6, 0x7f, 0x62, 0x1e, 0xd8, 0xa1, 0xbe, 0xcd, 0xf7, 0x09, 0x17, 0xff, 0xef, 0xe4, 0xff, 0x5d, 0xac, 0xe7, 0xb1, 0x8d, 0x79, 0x4f, 0x3d, 0x0f, 0xeb, 0x7b, 0x58, 0xdf, 0xcb, 0x7a, 0x3e, 0xeb, 0xbf, 0xf0, 0xfe, 0x3e, 0xd6, 0x0b, 0x89, 0xa6, 0x22, 0xb0, 0x5f, 0x1e, 0x16, 0x25, 0x3c, 0x7b, 0x79, 0xbd, 0x8c, 0xf7, 0x0f, 0x30, 0x9e, 0x2f, 0x56, 0xdf, 0xdc, 0x99, 0x77, 0xca, 0x3a, 0x18, 0xf4, 0x21, 0xe3, 0x71, 0x11, 0xfd, 0xf5, 0x11, 0x58, 0x22, 0x8f, 0x04, 0x7d, 0x02, 0x3e, 0xe5, 0xb5, 0xcf, 0xc0, 0x0a, 0xf3, + 0x37, 0x6a, 0xea, 0x5e, 0x79, 0x87, 0x42, 0x8e, 0xcb, 0x53, 0xb4, 0x62, 0x8d, 0x3e, 0x95, 0x31, 0x3b, 0x5d, 0x2e, 0xa3, 0x25, 0x65, 0xb4, 0x64, 0x19, 0x2d, 0x29, 0xa3, 0x25, 0x65, 0xb4, 0xa4, 0x8c, 0x96, 0x2c, 0xa3, 0x25, 0xcb, 0xf4, 0x2c, 0x46, 0xac, 0x79, 0x9d, 0xb8, 0x6b, 0x79, 0xbe, 0x0e, 0xdc, 0x0b, 0xee, 0x03, 0xf7, 0x83, 0x89, 0x60, 0x12, 0x98, 0x0c, 0xa6, 0x80, 0x07, 0xe4, 0x21, 0x47, 0x27, 0x59, 0xee, 0xe8, 0x0c, 0xba, 0x80, 0xae, 0xa0, 0x1b, 0xe8, 0x0e, 0x7a, 0x80, 0x34, 0xd0, 0x13, 0x9c, 0x0f, 0x2e, 0x00, 0xbd, 0x40, 0x6f, 0x90, 0x0e, 0xfa, 0x80, 0xbe, 0xa0, 0x1f, 0xe8, 0x0f, 0x06, 0x80, 0x81, 0x60, 0x10, 0xc8, 0x00, 0x17, 0x82, 0xc1, 0xe0, 0x22, 0x30, 0x04, 0x5c, 0x4c, 0x7f, 0x0d, 0x05, 0xc3, 0xc0, 0x70, 0x30, 0x02, 0x5c, 0x02, 0x46, 0x82, 0x51, 0x60, + 0x34, 0xf8, 0x6f, 0xdd, 0xa7, 0x6e, 0x3a, 0x78, 0x42, 0xdd, 0xaf, 0xee, 0xb0, 0xe3, 0x29, 0xf0, 0x34, 0x78, 0x06, 0xcc, 0x00, 0xcf, 0x82, 0x99, 0xe0, 0x39, 0xf0, 0x3c, 0xf8, 0x17, 0x98, 0x05, 0x66, 0x83, 0x39, 0xe0, 0x05, 0xf0, 0x22, 0x78, 0x09, 0xbc, 0x0c, 0x5e, 0x01, 0x73, 0xc1, 0xeb, 0xb4, 0xe1, 0x0d, 0xf0, 0x26, 0x98, 0x07, 0xe6, 0x83, 0xb7, 0xc0, 0xdb, 0xe0, 0x1d, 0xf0, 0x2e, 0x78, 0x0f, 0xbc, 0x0f, 0x16, 0x80, 0x0f, 0xc0, 0x42, 0xf0, 0x21, 0x58, 0x04, 0x3e, 0x02, 0x8b, 0xc1, 0xc7, 0x60, 0x09, 0xf8, 0x04, 0x7c, 0x0a, 0x96, 0x82, 0xcf, 0x64, 0xb9, 0xf3, 0x25, 0x75, 0x87, 0x9a, 0x1d, 0x44, 0xcf, 0x2e, 0x59, 0xa9, 0x22, 0x6c, 0xaf, 0xf4, 0x13, 0x51, 0xbf, 0x89, 0x62, 0xfe, 0x2f, 0x93, 0xbf, 0xa1, 0x3e, 0x79, 0xa8, 0xcf, 0x0e, 0xf5, 0xbd, 0x64, 0xf5, 0x2c, 0xb8, + 0x59, 0xbb, 0x42, 0xee, 0x35, 0xa3, 0x47, 0x84, 0xab, 0x6b, 0x35, 0xee, 0x90, 0x27, 0x89, 0xd5, 0x32, 0xe2, 0xf4, 0x80, 0xba, 0x5e, 0xa3, 0x59, 0x8a, 0x87, 0x4f, 0xef, 0x05, 0x35, 0xd7, 0xf5, 0xf4, 0xca, 0x12, 0xeb, 0xac, 0x70, 0xf3, 0xec, 0xf1, 0xb5, 0x01, 0xbf, 0x81, 0x31, 0x4b, 0x3b, 0xa6, 0xae, 0xe1, 0x68, 0x96, 0xf8, 0x9e, 0xa0, 0x87, 0x05, 0x3d, 0x2c, 0xe8, 0x61, 0x41, 0x0f, 0x0b, 0x7a, 0x58, 0xd0, 0xc3, 0x82, 0x1e, 0x16, 0xf4, 0xb0, 0xa0, 0x87, 0x05, 0x3d, 0x2c, 0xe8, 0x61, 0x41, 0x0f, 0x8b, 0x8b, 0xc1, 0x50, 0x30, 0x0c, 0x0c, 0x07, 0x23, 0xc0, 0x25, 0x60, 0xa4, 0x79, 0x0d, 0x09, 0x30, 0x1a, 0x5c, 0x0a, 0x50, 0x35, 0x71, 0x19, 0xb8, 0x1c, 0x5c, 0x01, 0xae, 0x04, 0x63, 0xc1, 0x55, 0xe0, 0x6a, 0x70, 0x0d, 0xb8, 0x16, 0x3c, 0x02, 0xa6, 0x31, 0xfa, 0x1e, 0x05, + 0x8f, 0x81, 0xc7, 0xc1, 0x74, 0xf0, 0x04, 0x78, 0x12, 0x3c, 0x05, 0x9e, 0x06, 0xcf, 0x00, 0x46, 0x82, 0x78, 0x4e, 0x56, 0x89, 0x7f, 0xc9, 0x92, 0x80, 0xf3, 0x0b, 0xb6, 0x6b, 0xe9, 0xd2, 0xa3, 0xf5, 0x61, 0x66, 0xec, 0xcb, 0x73, 0x3f, 0x9e, 0xcd, 0xf3, 0x0d, 0xd4, 0xef, 0xbf, 0x18, 0x85, 0xe3, 0xcc, 0xab, 0xf1, 0x54, 0x9f, 0x6b, 0x60, 0xa3, 0x7c, 0x1b, 0xe5, 0xdb, 0xe6, 0xca, 0x12, 0x1b, 0xbd, 0x68, 0xa3, 0x17, 0x6d, 0xf4, 0xa2, 0x8d, 0x5e, 0xb4, 0xd1, 0x8b, 0x36, 0x7a, 0xd1, 0x46, 0x2f, 0xda, 0xe8, 0x45, 0x1b, 0xbd, 0x68, 0xa3, 0x17, 0x6d, 0x4b, 0xd8, 0xfe, 0x13, 0xf5, 0x4d, 0x58, 0xf5, 0x79, 0x09, 0xdf, 0xc9, 0xf2, 0x90, 0x35, 0x60, 0x0b, 0xd8, 0x0a, 0xb6, 0x81, 0xed, 0x80, 0xd9, 0x24, 0x34, 0x59, 0x56, 0xe8, 0x3e, 0x59, 0xae, 0x1f, 0x02, 0x87, 0x41, 0x39, 0x38, + 0x02, 0x2a, 0xc0, 0x51, 0x70, 0x0c, 0xf8, 0x41, 0x25, 0xf8, 0x15, 0x1c, 0x07, 0x27, 0x40, 0x15, 0x38, 0x09, 0x4e, 0x81, 0xdf, 0xc0, 0x69, 0x59, 0x6e, 0xbc, 0x2e, 0x2b, 0x8c, 0x37, 0xc0, 0x9b, 0x60, 0x1e, 0x98, 0x0f, 0xde, 0x02, 0x6f, 0x83, 0x77, 0xc0, 0xbb, 0xe0, 0x3d, 0xf0, 0x3e, 0x58, 0x00, 0x3e, 0x00, 0x0b, 0xc1, 0x87, 0x60, 0x11, 0xf8, 0x08, 0x2c, 0x06, 0x1f, 0x03, 0xda, 0x60, 0xd0, 0x06, 0xe3, 0x53, 0xb0, 0x14, 0x7c, 0x06, 0x96, 0x81, 0xe5, 0xe0, 0xdf, 0x60, 0x05, 0xf8, 0x1c, 0x7c, 0x01, 0xbe, 0x04, 0x5f, 0x81, 0x95, 0xe0, 0x6b, 0xf0, 0x0d, 0xc8, 0x04, 0xff, 0x01, 0xdf, 0x82, 0x55, 0xc0, 0x25, 0xcb, 0x8c, 0x9d, 0x60, 0x97, 0xac, 0x32, 0xf2, 0xc0, 0x6e, 0xe0, 0x01, 0x7b, 0x80, 0x79, 0x2d, 0xa6, 0x7c, 0x50, 0x24, 0x4b, 0x8c, 0x62, 0xb0, 0x1f, 0x94, 0x80, 0x32, + 0x70, 0x00, 0xf8, 0xc0, 0x41, 0x70, 0x08, 0x1c, 0xa6, 0x1f, 0x75, 0xe2, 0x79, 0x0f, 0xb1, 0x5b, 0xc6, 0x08, 0xf0, 0x8a, 0x52, 0xf5, 0x1b, 0xb8, 0xea, 0x5e, 0xb5, 0x7a, 0x51, 0xb4, 0x24, 0xea, 0x8f, 0xb3, 0xd5, 0x5e, 0xa2, 0xbe, 0x4a, 0xec, 0x14, 0xf1, 0x44, 0xfd, 0xaf, 0x44, 0xfd, 0x41, 0xe1, 0x11, 0xad, 0xf9, 0x64, 0xbe, 0xc8, 0x17, 0x29, 0x44, 0xfd, 0x51, 0x4a, 0xf8, 0x85, 0xc8, 0x37, 0xc7, 0xd1, 0xaf, 0xea, 0x57, 0x06, 0x3d, 0xc4, 0x28, 0x2d, 0x4d, 0xb4, 0xd7, 0xfa, 0xa8, 0xb3, 0xc5, 0xa3, 0xb4, 0x8b, 0x45, 0x1c, 0x5e, 0xaf, 0x9f, 0x75, 0x6e, 0x6a, 0xaa, 0x76, 0x85, 0xe8, 0xa5, 0x5d, 0x27, 0xba, 0x69, 0xe3, 0x44, 0x37, 0x46, 0x84, 0x39, 0x43, 0x1f, 0x34, 0x7f, 0x31, 0xa0, 0xee, 0xc9, 0x6e, 0xde, 0xc3, 0x3d, 0x5b, 0x7d, 0x8f, 0xef, 0xb3, 0x7e, 0x4b, 0xf7, 0xa3, + 0x88, 0xf8, 0x47, 0x35, 0xe9, 0xc1, 0x68, 0xae, 0xfe, 0x65, 0xd0, 0x6b, 0x35, 0xe5, 0xd2, 0xae, 0x6d, 0x35, 0x7b, 0x3c, 0xa3, 0xd4, 0xc4, 0x80, 0x52, 0xdb, 0x58, 0xa5, 0xb6, 0xfd, 0xdb, 0xa5, 0x86, 0xd7, 0x2d, 0x55, 0xdd, 0x9d, 0xab, 0xba, 0x54, 0x55, 0xa2, 0xba, 0xbe, 0xeb, 0xdf, 0x2b, 0xd1, 0x9c, 0xb7, 0xb2, 0x99, 0x69, 0x76, 0x50, 0x82, 0xf9, 0x0b, 0xee, 0x83, 0x7c, 0x7a, 0x37, 0x3d, 0xb6, 0xf5, 0x8f, 0xce, 0x01, 0x41, 0xf9, 0x0e, 0xe3, 0x0c, 0x77, 0x30, 0xc3, 0x79, 0x64, 0x16, 0x9f, 0x32, 0x67, 0x38, 0xb7, 0xba, 0x47, 0xe6, 0x60, 0x35, 0x37, 0xad, 0xe3, 0x13, 0x5b, 0xac, 0x5f, 0x4b, 0x6d, 0xe2, 0x13, 0x95, 0x7c, 0xe2, 0x37, 0x75, 0x46, 0xc5, 0x11, 0xde, 0x59, 0xcb, 0x3b, 0x99, 0xbc, 0xf3, 0x3d, 0xbd, 0x17, 0x4e, 0x4c, 0x44, 0x30, 0xa7, 0x99, 0x7e, 0x2a, 0x9a, + 0xba, 0xb6, 0x00, 0xed, 0xe5, 0x31, 0xf3, 0x1b, 0x79, 0x3c, 0xcf, 0x81, 0xa0, 0xa3, 0xe8, 0xe9, 0x49, 0xb9, 0x23, 0x38, 0x44, 0x1e, 0x0a, 0x0e, 0x05, 0x0e, 0xb9, 0xc3, 0xf4, 0x28, 0xc6, 0x2f, 0x68, 0x20, 0x6d, 0x34, 0x4a, 0x41, 0x39, 0x38, 0x02, 0x70, 0xd1, 0xf8, 0x8b, 0x03, 0xf8, 0x8b, 0x03, 0xca, 0x5f, 0xfc, 0x24, 0x8f, 0xa9, 0xf6, 0xed, 0xb7, 0xae, 0x00, 0x7d, 0x84, 0x9a, 0x1e, 0xa3, 0x7d, 0x15, 0xb4, 0x6f, 0x37, 0x35, 0xde, 0x66, 0x9d, 0xc7, 0x64, 0x7a, 0xcc, 0x4d, 0xd4, 0xe9, 0x3b, 0xeb, 0xb7, 0x5d, 0x7b, 0xcd, 0x3b, 0x61, 0x91, 0x01, 0x7e, 0x6f, 0x5e, 0x5f, 0x83, 0x4c, 0xc2, 0xa6, 0x2d, 0x66, 0xa9, 0x08, 0xbe, 0xa2, 0x7a, 0xb1, 0xdd, 0x6d, 0xfb, 0x2a, 0xe8, 0xad, 0xea, 0xc5, 0xfc, 0xbf, 0x55, 0x5e, 0xf0, 0x8b, 0xc1, 0x2b, 0x82, 0xd7, 0x04, 0x57, 0x06, 0x57, + 0x86, 0xb4, 0x0c, 0x69, 0x17, 0x72, 0x7e, 0x88, 0x9b, 0x65, 0x93, 0x7a, 0x7c, 0x3e, 0x64, 0x79, 0xf5, 0x5a, 0xeb, 0x7b, 0x42, 0xaa, 0x42, 0x83, 0x43, 0x1d, 0xa1, 0x63, 0xed, 0x95, 0x8e, 0x54, 0x6b, 0x19, 0xec, 0x18, 0x5f, 0xbb, 0x7c, 0x56, 0xbb, 0xec, 0xa8, 0x5d, 0x4e, 0x37, 0x19, 0xd2, 0x64, 0x48, 0x74, 0x8b, 0xe8, 0x0e, 0xd1, 0xb7, 0x46, 0x4f, 0x8e, 0x7e, 0x3e, 0xfa, 0x8d, 0xe8, 0xd5, 0x67, 0x2e, 0x2d, 0xb6, 0xb7, 0xca, 0x6b, 0x95, 0xd7, 0x7a, 0x7c, 0xeb, 0x7b, 0x58, 0xd6, 0xb4, 0xce, 0x6b, 0x5d, 0x94, 0x34, 0x34, 0xe9, 0xee, 0xa4, 0x25, 0xc2, 0x26, 0x1c, 0x8e, 0xe5, 0x8e, 0x9f, 0x85, 0x70, 0xb8, 0xc9, 0xca, 0x92, 0x1d, 0x7b, 0x1c, 0x7b, 0x45, 0x8a, 0xa3, 0xcc, 0xf1, 0x9b, 0x68, 0xeb, 0xcc, 0x70, 0x66, 0x88, 0x3e, 0xce, 0xd9, 0xce, 0x17, 0x44, 0x5f, 0xe7, 0x4b, + 0xce, 0x97, 0xc5, 0x00, 0xe7, 0xab, 0xce, 0xd7, 0xc5, 0x20, 0xe7, 0x67, 0xce, 0x55, 0x62, 0xb0, 0x73, 0xa3, 0x73, 0x8b, 0xb8, 0x42, 0x5d, 0x8d, 0xf0, 0x4e, 0x21, 0xe5, 0x57, 0x38, 0xe1, 0x25, 0xd6, 0xd5, 0x08, 0x6f, 0xc3, 0xa3, 0xbf, 0xa6, 0x85, 0x9f, 0x7e, 0x54, 0x4b, 0x95, 0xe7, 0x6b, 0x1d, 0x4f, 0x7f, 0xa5, 0x75, 0x12, 0xb1, 0x5a, 0x67, 0x79, 0x15, 0x7d, 0x3b, 0x58, 0x3b, 0x5f, 0x4e, 0xd3, 0x2e, 0x90, 0x8f, 0x13, 0x05, 0x37, 0x05, 0x6b, 0xb2, 0x73, 0xf0, 0x40, 0x79, 0x51, 0xf0, 0x88, 0xd3, 0xd7, 0x06, 0x8f, 0x94, 0x69, 0xc1, 0xa3, 0xe5, 0x6c, 0x7c, 0x7b, 0x9c, 0xfd, 0x66, 0xd9, 0xca, 0x7e, 0x40, 0x8e, 0xd0, 0x87, 0xe2, 0x88, 0xaa, 0xaf, 0x46, 0xd8, 0x1d, 0x17, 0x29, 0x70, 0x91, 0x76, 0xeb, 0x6a, 0x84, 0xad, 0x8c, 0x07, 0x4f, 0x1f, 0x37, 0x5e, 0x38, 0x9d, 0x67, + 0x64, 0xc9, 0xb1, 0x8e, 0xd7, 0x64, 0x27, 0x7a, 0x73, 0xb2, 0x23, 0x47, 0x8e, 0xa4, 0x47, 0x6f, 0xb0, 0xae, 0x46, 0xb8, 0xc9, 0xf9, 0xad, 0xec, 0xe6, 0x5c, 0x75, 0xfa, 0xb8, 0x68, 0x6a, 0x5d, 0x8f, 0x69, 0x11, 0xb5, 0x5b, 0x4a, 0xed, 0xd6, 0x58, 0xd7, 0x63, 0x7a, 0x89, 0x5a, 0x2d, 0x0c, 0x5a, 0x89, 0x6f, 0x5e, 0x25, 0x4f, 0x50, 0x9b, 0x67, 0xd9, 0xfb, 0xb7, 0xec, 0xe1, 0x14, 0x7b, 0xd0, 0xac, 0xeb, 0x32, 0xad, 0xb3, 0xae, 0xcb, 0x94, 0xeb, 0x30, 0xef, 0x8f, 0x16, 0x47, 0x7b, 0x2b, 0xb5, 0x10, 0xdc, 0x7d, 0xf5, 0x75, 0x86, 0x4b, 0x29, 0xed, 0xa8, 0xba, 0x13, 0x64, 0x2a, 0x59, 0x5d, 0x47, 0x62, 0xc5, 0xfc, 0x35, 0xef, 0x18, 0x70, 0xb9, 0xac, 0xa0, 0xe4, 0xe3, 0x94, 0x7c, 0x3c, 0xc8, 0xad, 0xee, 0x8e, 0x58, 0x4e, 0x74, 0x96, 0xd3, 0xde, 0x23, 0xd6, 0x1d, 0x10, 0xab, + 0xc8, 0x53, 0x36, 0xd3, 0xee, 0xfd, 0xb4, 0xfb, 0x67, 0xf6, 0x5c, 0x11, 0x70, 0xed, 0xe1, 0xfd, 0xb4, 0xff, 0x04, 0x35, 0xc8, 0xa7, 0x06, 0x07, 0xa9, 0x81, 0x4f, 0xb5, 0xa1, 0x01, 0x6d, 0x58, 0x68, 0xb5, 0xe1, 0x3b, 0x95, 0xfb, 0xa5, 0xca, 0x17, 0xd9, 0xe3, 0x07, 0x01, 0x6d, 0x98, 0x59, 0xdd, 0x06, 0x11, 0x44, 0x1b, 0x82, 0x29, 0xe1, 0xa8, 0xd5, 0x86, 0x2f, 0x69, 0xc3, 0x0e, 0xd5, 0x86, 0xff, 0x37, 0xae, 0x18, 0xde, 0x91, 0x9e, 0x9a, 0x02, 0x5f, 0x1b, 0xe9, 0xa9, 0x1f, 0xe0, 0x6c, 0x3e, 0x9c, 0x7d, 0x4a, 0x54, 0xfa, 0xe1, 0x6c, 0x1d, 0x3d, 0x75, 0x21, 0xbc, 0xad, 0x45, 0x25, 0xec, 0xb4, 0xe6, 0x0a, 0xa2, 0x72, 0x2b, 0x51, 0x99, 0x4d, 0xab, 0xee, 0xb5, 0x7e, 0xe7, 0x61, 0xfe, 0xa2, 0x3c, 0xda, 0xfc, 0x45, 0x39, 0x9c, 0xfe, 0x87, 0x5e, 0x9b, 0xa6, 0x7a, 0x6d, 0xc4, + 0x69, 0x37, 0x3d, 0xb6, 0x80, 0x1e, 0xdb, 0x04, 0xcf, 0x97, 0x06, 0xbf, 0x24, 0xf4, 0xea, 0xeb, 0xfc, 0x08, 0x3b, 0xbd, 0x36, 0x52, 0x5d, 0xeb, 0x67, 0xa8, 0xcc, 0x23, 0x6a, 0x9f, 0x24, 0x6a, 0xa7, 0x12, 0xb5, 0x57, 0x12, 0xb5, 0xe3, 0xe9, 0x8f, 0x3e, 0xf4, 0x47, 0x57, 0x72, 0x9e, 0x91, 0xf4, 0xc9, 0xe5, 0xf4, 0xc9, 0xfb, 0xf4, 0xc9, 0xd3, 0x44, 0xed, 0x0a, 0x22, 0xf6, 0x7d, 0xc7, 0x0e, 0xe1, 0x24, 0x5a, 0xa7, 0x90, 0xd3, 0x8c, 0xfc, 0xff, 0x71, 0x8d, 0x7b, 0x9d, 0x36, 0x3f, 0x64, 0x5d, 0x1b, 0x74, 0x35, 0xa5, 0x3c, 0x4f, 0x7b, 0xf7, 0x52, 0xc2, 0x6c, 0x4a, 0x18, 0x48, 0x5d, 0x2e, 0x51, 0xba, 0xb6, 0xad, 0x66, 0x1e, 0x13, 0x0d, 0xd8, 0x4b, 0x26, 0x7b, 0x79, 0x46, 0xdd, 0x63, 0xb4, 0xa7, 0x7c, 0x81, 0xf6, 0xcf, 0x67, 0xb6, 0xab, 0x64, 0xab, 0x92, 0x9a, 0x5f, 0x7b, + 0xb3, 0xc7, 0x47, 0xcc, 0x3d, 0x99, 0xd9, 0x34, 0x7b, 0x3b, 0x29, 0x0c, 0x4a, 0x2e, 0xe7, 0x93, 0x3b, 0xd9, 0xd2, 0x55, 0x93, 0x2b, 0xb3, 0x45, 0xbe, 0x75, 0x74, 0xc5, 0x43, 0x5d, 0x4e, 0xd4, 0xee, 0x69, 0x0d, 0xef, 0x6e, 0xac, 0xfd, 0x6f, 0xad, 0xd2, 0xd3, 0x70, 0xeb, 0x37, 0xb8, 0xe6, 0x1c, 0x7e, 0x82, 0x96, 0x55, 0x5a, 0xbf, 0xa2, 0xdd, 0x62, 0x9d, 0x31, 0xba, 0x2d, 0x60, 0xa6, 0xdd, 0x65, 0x9d, 0x17, 0x77, 0x82, 0x7a, 0x1c, 0xa1, 0xe5, 0x7e, 0x5a, 0x7e, 0x04, 0x57, 0x96, 0x8b, 0x2b, 0xcb, 0xb5, 0xce, 0x8b, 0x3b, 0x70, 0xd6, 0x3a, 0xed, 0x3a, 0xa3, 0x4e, 0xa1, 0x81, 0xc7, 0x82, 0x88, 0x54, 0x73, 0xc4, 0x6d, 0x37, 0x23, 0xb5, 0xde, 0x99, 0xfc, 0x7f, 0x7e, 0xfc, 0xc8, 0x46, 0x0b, 0x4c, 0x56, 0xab, 0x7f, 0x8b, 0x79, 0xf4, 0xbf, 0x7c, 0xde, 0xeb, 0x6d, 0xa2, 0x0d, + 0xa3, 0x2f, 0x9b, 0xfd, 0x6c, 0x91, 0x39, 0xe4, 0xac, 0x65, 0x96, 0x42, 0xed, 0xb0, 0xd4, 0xc9, 0xbc, 0xa3, 0xe6, 0x21, 0xfa, 0xfa, 0x6b, 0x66, 0xa2, 0xd7, 0xa9, 0xc9, 0xb7, 0x70, 0x59, 0xaa, 0x3d, 0x48, 0xa9, 0x0f, 0xcb, 0x65, 0xda, 0x6c, 0xb9, 0xce, 0xbc, 0xd6, 0x67, 0xd0, 0x3a, 0xf9, 0x86, 0x79, 0x3f, 0x58, 0xf8, 0xf3, 0xc3, 0x9f, 0x9f, 0x3d, 0x2d, 0x63, 0x4f, 0xcb, 0xcc, 0xab, 0x6d, 0xda, 0x87, 0x89, 0x44, 0xfb, 0x70, 0xd1, 0xc1, 0x3e, 0x92, 0xe7, 0x51, 0xa2, 0x9b, 0x7d, 0x2c, 0xcf, 0x57, 0xf1, 0xff, 0xb5, 0x3c, 0xe3, 0x8c, 0x88, 0xe5, 0x02, 0xfb, 0x3c, 0xd6, 0xe7, 0x8b, 0xed, 0xf6, 0xb7, 0xc4, 0x7e, 0xfb, 0xdb, 0xac, 0xbf, 0x23, 0x76, 0xda, 0xdf, 0x15, 0xa5, 0xf6, 0xf7, 0x45, 0x13, 0xfb, 0x17, 0xfc, 0xff, 0x25, 0xef, 0x7d, 0xc5, 0x7b, 0x2b, 0xc5, 0x35, 0xf6, + 0xaf, 0xf9, 0xff, 0x1b, 0xde, 0xcf, 0xe4, 0xfd, 0xff, 0xf0, 0xff, 0x2a, 0x11, 0x6b, 0xff, 0x8e, 0xd7, 0xd6, 0xb0, 0xcd, 0x5a, 0xb6, 0xf9, 0x9e, 0xd7, 0x7e, 0xe0, 0xff, 0x75, 0x6c, 0xb3, 0x9e, 0x6d, 0xb2, 0xf8, 0x7f, 0xb7, 0x38, 0xcf, 0xee, 0x11, 0xbf, 0xd8, 0xf7, 0x88, 0x4a, 0xfb, 0x5e, 0x71, 0x9b, 0x3d, 0x9f, 0xff, 0x7f, 0x11, 0x25, 0xf6, 0x02, 0x71, 0xc2, 0xbe, 0x8f, 0xff, 0x0b, 0x45, 0x98, 0xbd, 0x48, 0xb4, 0x66, 0x4c, 0xcd, 0xd5, 0x3b, 0x88, 0xa5, 0xfa, 0x79, 0xe2, 0x7b, 0xbd, 0x93, 0xf8, 0x48, 0xef, 0x2c, 0xbe, 0xd1, 0xbb, 0x0a, 0x3b, 0x33, 0xc0, 0x0c, 0x66, 0x80, 0xed, 0xfa, 0x13, 0xa2, 0xa9, 0x3e, 0x53, 0x24, 0xea, 0xcf, 0x69, 0xa3, 0xf5, 0xe7, 0xb5, 0x61, 0xfa, 0xbf, 0xc4, 0xeb, 0xfa, 0x2c, 0x11, 0xa5, 0xcf, 0x16, 0x4d, 0x98, 0x0d, 0xfc, 0x8e, 0x58, 0xf1, + 0x82, 0x23, 0x4e, 0xcc, 0x73, 0xc4, 0x8b, 0x04, 0x47, 0x22, 0xeb, 0xad, 0xc4, 0xfb, 0x8e, 0xd6, 0xac, 0xa7, 0xb2, 0xde, 0x96, 0xd7, 0xdb, 0xb1, 0xde, 0x81, 0xf5, 0xf3, 0x78, 0xbd, 0xa3, 0x48, 0xa0, 0x07, 0x46, 0x38, 0x3f, 0x93, 0x55, 0xce, 0xe5, 0xda, 0x00, 0xe7, 0xbf, 0xb5, 0x11, 0xce, 0x15, 0x62, 0xa6, 0xf3, 0x73, 0x11, 0xed, 0xfc, 0x42, 0x44, 0x3a, 0x57, 0x8a, 0xae, 0x62, 0x96, 0x08, 0x92, 0x4b, 0x45, 0x30, 0x08, 0x91, 0x2f, 0x8a, 0x66, 0xf2, 0x01, 0x91, 0x24, 0xb7, 0xd2, 0x63, 0x79, 0xa2, 0x1d, 0x73, 0x67, 0x7b, 0xdc, 0xce, 0x46, 0xb9, 0x9c, 0xf9, 0x75, 0xa9, 0xba, 0x4f, 0x37, 0xf9, 0x02, 0x3d, 0x78, 0x94, 0xde, 0x9a, 0x2d, 0x8e, 0x31, 0xfb, 0x1e, 0x97, 0x2f, 0x88, 0x13, 0xe0, 0x37, 0xa2, 0xdf, 0x06, 0x42, 0xc8, 0xd0, 0xc2, 0x88, 0xfe, 0x06, 0x72, 0xab, 0xed, + 0x6a, 0x99, 0x65, 0xbb, 0x5d, 0x66, 0x05, 0x65, 0xca, 0x9f, 0x83, 0x56, 0xcb, 0xad, 0x41, 0x3f, 0x80, 0x75, 0x72, 0x63, 0xd0, 0x7a, 0x59, 0x1e, 0x94, 0xc5, 0xfa, 0x06, 0x32, 0xb8, 0x8d, 0x60, 0x8b, 0x7c, 0x21, 0x68, 0x3b, 0xcf, 0x3f, 0xe1, 0x80, 0x98, 0xaf, 0xd5, 0x75, 0xd9, 0x77, 0xc9, 0xaf, 0x82, 0xf2, 0xc0, 0x1e, 0xf9, 0x82, 0xbd, 0xbd, 0x5c, 0x6a, 0xef, 0x00, 0xce, 0x03, 0x1d, 0x41, 0x27, 0xd0, 0x19, 0x74, 0x01, 0x5d, 0x41, 0x37, 0xd0, 0x1d, 0xf4, 0x00, 0x69, 0xf2, 0x33, 0x7b, 0x4f, 0x9e, 0xcf, 0x07, 0x17, 0x80, 0x5e, 0xa0, 0x37, 0x48, 0x07, 0x7d, 0x40, 0x5f, 0xd0, 0x0f, 0xf4, 0x07, 0x03, 0xc0, 0x40, 0x30, 0x08, 0x64, 0x80, 0x0b, 0xc1, 0x60, 0x70, 0x11, 0x18, 0x02, 0x86, 0x82, 0x61, 0xb2, 0x90, 0x79, 0xbe, 0x90, 0x79, 0x7e, 0x29, 0xf3, 0xfc, 0x52, 0xe6, 0xf9, + 0x42, 0xe6, 0xf9, 0x42, 0xe6, 0xf9, 0xa5, 0xcc, 0xf3, 0x4b, 0x99, 0xe7, 0x97, 0x32, 0xcf, 0x2f, 0x65, 0x9e, 0x5f, 0xca, 0x3c, 0xbf, 0x94, 0x79, 0x7e, 0x29, 0xf3, 0x7c, 0x21, 0xf3, 0x7c, 0x21, 0xf3, 0xfc, 0x52, 0xe6, 0xf9, 0xa5, 0xcc, 0xf3, 0x85, 0xcc, 0xf3, 0x85, 0xcc, 0xf3, 0x4b, 0xed, 0xd7, 0x53, 0xbf, 0xf1, 0xe0, 0x06, 0xd6, 0x6f, 0x94, 0x2f, 0xda, 0x6f, 0x02, 0x37, 0xcb, 0x97, 0xec, 0x65, 0xf2, 0x01, 0x22, 0xe3, 0x01, 0xbb, 0x4f, 0x3e, 0xa0, 0x8f, 0x92, 0x5b, 0xf5, 0xd1, 0xe0, 0x52, 0x30, 0x06, 0x5c, 0x06, 0xae, 0x00, 0x57, 0x82, 0xb1, 0xe0, 0x2a, 0x30, 0x95, 0xfc, 0xea, 0x21, 0xf0, 0x30, 0x20, 0xf7, 0xd4, 0xa7, 0xc9, 0x2c, 0xfd, 0x51, 0xf0, 0x18, 0x78, 0x1c, 0x4c, 0x07, 0x4f, 0xc8, 0x02, 0xfd, 0x49, 0x9e, 0x9f, 0x02, 0x4f, 0x83, 0x67, 0xc0, 0x0c, 0xf0, 0x2c, + 0x98, 0xc9, 0x7b, 0xb3, 0xc0, 0x6c, 0x30, 0x87, 0xff, 0x5f, 0x00, 0x2f, 0xca, 0x8d, 0x8e, 0x7f, 0xcb, 0x9f, 0x1d, 0x2b, 0x40, 0xa6, 0x3c, 0xe6, 0xf8, 0x0f, 0xf8, 0x16, 0xac, 0x02, 0xab, 0xe5, 0x31, 0xe7, 0x6c, 0x59, 0xe9, 0x9c, 0x83, 0xcb, 0x36, 0x7f, 0x8f, 0x53, 0xc1, 0xfc, 0xfa, 0x06, 0x3a, 0xe3, 0x45, 0x67, 0xbc, 0xcc, 0xb3, 0x4b, 0xb4, 0xeb, 0x99, 0xd5, 0x6f, 0xe6, 0xbd, 0xd0, 0x73, 0xbd, 0xab, 0xee, 0xfe, 0xf0, 0x29, 0x1a, 0x65, 0xf0, 0xee, 0x2d, 0xbc, 0xfb, 0x09, 0xef, 0x2e, 0xe2, 0xdd, 0x49, 0xd6, 0xf9, 0xb8, 0xcd, 0xac, 0xf3, 0xf4, 0xc2, 0xc9, 0x68, 0x1c, 0x7c, 0x62, 0x95, 0x76, 0x03, 0x73, 0xe6, 0x4d, 0xe0, 0x66, 0xd1, 0x19, 0x6d, 0x6a, 0x68, 0xdd, 0xa1, 0x32, 0x0c, 0xdd, 0x5f, 0x23, 0x0c, 0x4a, 0xdb, 0x60, 0xfe, 0x12, 0x5b, 0xcc, 0xe1, 0x13, 0x3d, 0xe5, + 0xcf, 0x94, 0xf4, 0x83, 0xe5, 0x55, 0x98, 0x51, 0x85, 0x23, 0xa4, 0x87, 0x70, 0xb0, 0x98, 0x57, 0x6e, 0x9e, 0x8d, 0xdf, 0x98, 0x83, 0xf7, 0xee, 0xc9, 0x8c, 0x36, 0xd8, 0xfc, 0xdd, 0x81, 0x3c, 0x6c, 0x3a, 0x1a, 0xf3, 0x77, 0xd9, 0x35, 0x67, 0x0e, 0x6b, 0x2f, 0xb3, 0xe5, 0x66, 0x71, 0x9e, 0x7c, 0x5c, 0x74, 0x04, 0x9d, 0x40, 0x17, 0x14, 0xb7, 0x2b, 0xe8, 0x06, 0xba, 0x83, 0x1e, 0x20, 0x0d, 0xf4, 0x94, 0xe3, 0xc4, 0xf9, 0xe0, 0x02, 0xd0, 0x9b, 0xff, 0xd3, 0x41, 0x1f, 0xe9, 0x11, 0xe4, 0xed, 0xa2, 0x1f, 0xe8, 0x0f, 0x06, 0x80, 0x81, 0x60, 0x10, 0xc8, 0x00, 0x17, 0x82, 0xc1, 0xe0, 0x22, 0x30, 0x04, 0x5c, 0x0c, 0x86, 0x82, 0x61, 0x60, 0x38, 0x18, 0x01, 0x2e, 0x01, 0x23, 0xc1, 0x28, 0x30, 0x1a, 0x5c, 0x0a, 0xc6, 0x80, 0xcb, 0xc0, 0xe5, 0xe0, 0x0a, 0x70, 0x25, 0x18, 0x0b, + 0xae, 0x02, 0x57, 0x83, 0x6b, 0xc0, 0xb5, 0xe0, 0x3a, 0xf9, 0xb4, 0x18, 0x27, 0x1f, 0x13, 0x77, 0xc8, 0x95, 0xe2, 0x6e, 0x30, 0x01, 0xdc, 0x03, 0xee, 0xa5, 0x6e, 0xf7, 0x81, 0xfb, 0xc1, 0x44, 0xfe, 0x9f, 0x4c, 0x9d, 0xa7, 0xf0, 0xfc, 0x80, 0x7c, 0x4e, 0x3c, 0x08, 0xa6, 0x82, 0x87, 0xc0, 0x23, 0x94, 0x31, 0x8d, 0x7c, 0xe8, 0x51, 0xf0, 0x18, 0x78, 0x1c, 0x4c, 0x07, 0x4f, 0x80, 0x27, 0xc1, 0x53, 0xe0, 0x69, 0xf0, 0x0c, 0x98, 0x01, 0xfb, 0xcf, 0x82, 0x99, 0x72, 0x9f, 0x78, 0x4e, 0x16, 0x8a, 0xe7, 0x99, 0x2f, 0xfe, 0xc5, 0xfa, 0x2c, 0x75, 0x65, 0x81, 0xce, 0xf4, 0xec, 0x4e, 0x7a, 0x76, 0x8b, 0xd6, 0x4b, 0xa6, 0x6a, 0xbd, 0x41, 0x7f, 0xd9, 0x45, 0x1b, 0x00, 0x06, 0xe2, 0x9f, 0x07, 0xc9, 0xeb, 0xb4, 0x0c, 0x9e, 0x2f, 0xe4, 0x79, 0xb0, 0x7c, 0x58, 0x1b, 0x2a, 0xfb, 0x6b, + 0xc3, 0xc1, 0xf5, 0xea, 0x5e, 0x3e, 0xd5, 0xd7, 0x59, 0xf6, 0xca, 0x29, 0x5a, 0xa9, 0x5c, 0x63, 0x9b, 0x24, 0xa7, 0xd8, 0xa8, 0xaf, 0x6d, 0xaa, 0x5c, 0x69, 0x9b, 0x06, 0xa8, 0x97, 0x8d, 0x7a, 0xd9, 0x9e, 0x40, 0x11, 0x66, 0xc8, 0xa7, 0x6d, 0x33, 0x79, 0xef, 0x79, 0xd6, 0x5f, 0x94, 0xcf, 0xd9, 0x5e, 0x61, 0x7d, 0xae, 0xdc, 0x67, 0x9b, 0xcf, 0xff, 0xef, 0x49, 0x8f, 0xed, 0x7d, 0xb0, 0x00, 0x7c, 0x00, 0x16, 0x82, 0x0f, 0xc1, 0x22, 0xf0, 0x11, 0x58, 0x0c, 0x3e, 0x06, 0x4b, 0x28, 0xeb, 0x13, 0x59, 0x14, 0x0c, 0x6f, 0xc1, 0x4f, 0xc9, 0x37, 0x83, 0x9f, 0x01, 0x33, 0xe4, 0x77, 0xc1, 0x33, 0xe5, 0x77, 0x21, 0xdf, 0x49, 0x4f, 0xc8, 0x1a, 0xb0, 0x05, 0x6c, 0x05, 0xdb, 0xc0, 0x76, 0x90, 0x03, 0x0a, 0xe4, 0x94, 0x90, 0x12, 0x39, 0x2e, 0xc4, 0x27, 0xd7, 0x84, 0xb6, 0x96, 0x2b, + 0x43, 0x93, 0x65, 0x51, 0xe8, 0x09, 0x39, 0x45, 0x7f, 0x43, 0x3e, 0xae, 0xbf, 0x09, 0xe6, 0x81, 0xf9, 0xc0, 0xbc, 0xfe, 0xf3, 0xdb, 0xea, 0x1a, 0xd0, 0x8f, 0xeb, 0xef, 0x82, 0xf7, 0xc0, 0x02, 0xf0, 0x01, 0x58, 0x08, 0x3e, 0x04, 0x8b, 0xc0, 0x47, 0x60, 0x31, 0xf8, 0x18, 0x2c, 0x01, 0x9f, 0x80, 0x4f, 0xc1, 0x52, 0xf0, 0x19, 0x58, 0x06, 0x96, 0x83, 0x7f, 0x83, 0x15, 0xe0, 0x2b, 0xb0, 0x12, 0x7c, 0x0d, 0xbe, 0x01, 0x99, 0x60, 0x0d, 0xfb, 0x5f, 0x0b, 0xbe, 0x07, 0x3f, 0x80, 0x0d, 0x60, 0xa3, 0x1c, 0xa7, 0xff, 0x08, 0x36, 0x81, 0x6c, 0xb0, 0x15, 0x6c, 0xe3, 0xf5, 0xed, 0x20, 0x07, 0xe4, 0x82, 0x9f, 0xc0, 0x0e, 0xf0, 0x33, 0x70, 0x81, 0x3c, 0xb0, 0x1b, 0x78, 0xc0, 0x1e, 0xb0, 0x17, 0xe4, 0x83, 0x5f, 0x00, 0xed, 0xd6, 0xf7, 0x81, 0x22, 0x50, 0x0c, 0xf6, 0x83, 0x12, 0x40, + 0x7f, 0xe9, 0x65, 0xe0, 0x00, 0xf0, 0x49, 0x8f, 0x7e, 0x08, 0x1c, 0x06, 0xe5, 0xe0, 0x08, 0xa8, 0x00, 0x47, 0xc1, 0x31, 0xe0, 0x07, 0x95, 0xe0, 0x57, 0x70, 0x1c, 0x9c, 0x00, 0x55, 0xe0, 0x24, 0x38, 0x05, 0x7e, 0x03, 0xa7, 0x81, 0x94, 0x4f, 0x1b, 0x02, 0x68, 0xc0, 0x06, 0x82, 0x40, 0x30, 0x08, 0x01, 0xa1, 0xf2, 0x31, 0xc3, 0x0e, 0x74, 0x60, 0x80, 0x06, 0xa0, 0x21, 0x68, 0x04, 0x32, 0xe4, 0x4a, 0xe3, 0x42, 0x30, 0x18, 0x5c, 0x0c, 0x86, 0x82, 0x61, 0x60, 0x38, 0x18, 0x01, 0x2e, 0x01, 0x23, 0xc1, 0x28, 0x30, 0x1a, 0x8c, 0x01, 0x97, 0xc9, 0x29, 0xc6, 0xe5, 0xe0, 0x0a, 0x70, 0x25, 0x18, 0x0b, 0xae, 0x02, 0x57, 0x83, 0x6b, 0xc0, 0xf5, 0x60, 0x3c, 0xb8, 0x01, 0xdc, 0x08, 0x6e, 0x02, 0x37, 0x83, 0x5b, 0xc0, 0xad, 0xe0, 0x36, 0x70, 0x3b, 0xb8, 0x03, 0xdc, 0x09, 0xee, 0x02, + 0x77, 0x83, 0x09, 0xe0, 0x1e, 0x30, 0x15, 0x3c, 0x04, 0x1e, 0x06, 0xc4, 0xb1, 0xf1, 0x28, 0x78, 0x02, 0x3c, 0x09, 0x9e, 0x02, 0x4f, 0x83, 0x67, 0xc0, 0x0c, 0xf0, 0x2c, 0x78, 0x0e, 0x3c, 0x0f, 0xfe, 0x25, 0x9f, 0x33, 0x66, 0x81, 0xd9, 0x60, 0x0e, 0x20, 0xce, 0x8d, 0x97, 0xc0, 0xcb, 0xe0, 0x15, 0x30, 0x17, 0xbc, 0x8e, 0x5f, 0x7e, 0x03, 0xbc, 0x09, 0xe6, 0x81, 0xf9, 0xe0, 0x2d, 0xf0, 0x36, 0x78, 0x07, 0xbc, 0x0b, 0xde, 0x03, 0xef, 0x83, 0x05, 0xe0, 0x03, 0xb0, 0x10, 0x7c, 0x08, 0x16, 0x81, 0x8f, 0xc0, 0x62, 0xf0, 0x31, 0x60, 0x3c, 0x18, 0x8c, 0x07, 0xe3, 0x53, 0xb0, 0x14, 0x7c, 0x06, 0x96, 0x81, 0xe5, 0xe0, 0xdf, 0x60, 0x05, 0xf8, 0x1c, 0x7c, 0x01, 0xbe, 0x04, 0x5f, 0x81, 0x95, 0xe0, 0x6b, 0xf0, 0x0d, 0xc8, 0x04, 0xff, 0x01, 0xdf, 0x82, 0x55, 0x60, 0xb5, 0x5c, 0x63, + 0x7c, 0x07, 0xd6, 0x80, 0xb5, 0xe0, 0x7b, 0xf0, 0x03, 0x58, 0x07, 0xd6, 0x83, 0x0d, 0x60, 0x23, 0xf8, 0x11, 0x6c, 0x02, 0xd9, 0x60, 0x33, 0xd8, 0x02, 0xb6, 0x82, 0x6d, 0x60, 0x3b, 0xc8, 0x01, 0xb9, 0x60, 0x07, 0xf8, 0x19, 0xb8, 0xe4, 0x3e, 0x63, 0x27, 0xd8, 0x25, 0x0b, 0x8d, 0x3c, 0xb0, 0x1b, 0x78, 0xc0, 0x1e, 0xb0, 0x17, 0xe4, 0x83, 0x02, 0xf2, 0xea, 0x7d, 0xa0, 0x88, 0xed, 0x8a, 0xc1, 0x7e, 0x50, 0x02, 0xca, 0xc0, 0x01, 0xe0, 0x03, 0x07, 0xc1, 0x21, 0x70, 0x18, 0x1c, 0x95, 0xeb, 0xcc, 0xeb, 0xa5, 0x1b, 0x7e, 0x50, 0x09, 0x7e, 0x05, 0xc7, 0xc9, 0x34, 0xdd, 0x72, 0xa5, 0x63, 0x8f, 0xec, 0xef, 0x38, 0x26, 0xa7, 0x38, 0xfc, 0xa0, 0x12, 0xfc, 0x0a, 0x8e, 0x83, 0x13, 0x72, 0x8d, 0xa3, 0x0a, 0x9c, 0x04, 0xa7, 0xc0, 0x6f, 0x72, 0x8d, 0x93, 0x71, 0xa6, 0x1d, 0xc3, 0x51, + 0x15, 0xe1, 0xa8, 0x8a, 0x70, 0x53, 0xbf, 0x8a, 0x54, 0xfc, 0x6e, 0x5b, 0x79, 0x1c, 0x37, 0x75, 0x0a, 0x37, 0x55, 0x82, 0xc2, 0xf8, 0x50, 0x18, 0x1f, 0x0a, 0xe3, 0x13, 0x9d, 0x41, 0x17, 0xe9, 0x47, 0x65, 0xfc, 0xa8, 0x8c, 0x1f, 0x95, 0xf1, 0xa3, 0x32, 0x7e, 0x54, 0xc6, 0x8f, 0xca, 0x54, 0xa2, 0x32, 0x95, 0xa8, 0x4c, 0x25, 0x2a, 0xe3, 0x47, 0x65, 0xfc, 0xa8, 0x4c, 0x16, 0x2a, 0x93, 0x85, 0xca, 0x64, 0xa1, 0x32, 0x59, 0xa8, 0x4c, 0x16, 0x2a, 0x93, 0x85, 0xca, 0x64, 0xa1, 0x32, 0x59, 0xa8, 0x4c, 0x16, 0x2a, 0x93, 0x85, 0xca, 0x64, 0xa1, 0x32, 0x59, 0xa8, 0x4c, 0x16, 0x2a, 0x93, 0x85, 0xca, 0x64, 0xa1, 0x32, 0x59, 0xa8, 0x4c, 0x16, 0x2a, 0x93, 0x85, 0xca, 0x64, 0xa1, 0x32, 0x59, 0xa8, 0x4c, 0x16, 0x2a, 0x93, 0x85, 0xca, 0x64, 0xa1, 0x32, 0x59, 0xa8, 0x4c, 0x16, 0x2a, + 0x93, 0x85, 0xca, 0x64, 0xa1, 0x32, 0x59, 0xa8, 0x4c, 0x16, 0x2a, 0x83, 0xbb, 0x05, 0xd7, 0x02, 0x5c, 0x0c, 0x2a, 0x53, 0x29, 0xae, 0xa7, 0xde, 0xe3, 0xc1, 0x0d, 0xe0, 0x46, 0x5c, 0xe1, 0x4d, 0xe0, 0x66, 0x70, 0x2b, 0xb8, 0x0d, 0xdc, 0xce, 0xeb, 0x77, 0xc8, 0x52, 0x71, 0x27, 0xcf, 0x77, 0x81, 0xbb, 0x59, 0x9f, 0x00, 0xee, 0x01, 0xf7, 0xd2, 0x8e, 0xfb, 0xc0, 0xfd, 0x60, 0x22, 0xff, 0x4f, 0xe2, 0xfd, 0xc9, 0x94, 0x39, 0x85, 0xf5, 0x07, 0x70, 0x09, 0x0f, 0x82, 0xa9, 0xe0, 0x21, 0xf0, 0x30, 0xef, 0x3d, 0xc2, 0x7e, 0xa7, 0xc9, 0x6c, 0x94, 0x29, 0x1b, 0x65, 0xca, 0x46, 0x99, 0xb2, 0x51, 0xa6, 0x6c, 0x94, 0x29, 0x1b, 0x65, 0xca, 0x46, 0x99, 0xb2, 0x51, 0xa6, 0x6c, 0x94, 0x29, 0x1b, 0x65, 0xf2, 0xa1, 0x4c, 0x3e, 0x94, 0x29, 0x0b, 0x65, 0xca, 0x43, 0x99, 0x56, 0xa2, 0x4c, + 0x6b, 0x51, 0xa6, 0xcd, 0xa8, 0x8a, 0x1f, 0x55, 0xf1, 0xe1, 0x4a, 0x4b, 0x70, 0xa5, 0x25, 0xb6, 0xfb, 0x58, 0xbf, 0x1f, 0x4c, 0x92, 0x7e, 0x54, 0xa6, 0x12, 0x95, 0x29, 0xb5, 0x3d, 0xc4, 0xff, 0xd3, 0x78, 0x66, 0x3f, 0x28, 0x4d, 0x36, 0x4a, 0xb3, 0x19, 0xa5, 0x29, 0x44, 0x69, 0x2a, 0x51, 0x9a, 0xcd, 0x28, 0xcd, 0x7e, 0x94, 0xa6, 0x12, 0xa5, 0x59, 0x6b, 0x7b, 0x93, 0x6d, 0xe7, 0x81, 0xf9, 0xbc, 0xfe, 0x1e, 0x4e, 0xf7, 0x7d, 0xb0, 0x00, 0x7c, 0x00, 0x16, 0x82, 0x0f, 0xc1, 0x22, 0xf0, 0x11, 0x58, 0x0c, 0x3e, 0x06, 0x4b, 0x28, 0xf3, 0x13, 0x99, 0x8d, 0xe2, 0x54, 0x06, 0x4f, 0x96, 0xbe, 0x90, 0x70, 0xf0, 0x9d, 0xcc, 0x42, 0x69, 0xb2, 0x42, 0x7e, 0xc0, 0xb5, 0x6c, 0xe1, 0x79, 0x2b, 0xd8, 0x06, 0xb6, 0x83, 0x1c, 0x50, 0x40, 0xae, 0x53, 0x22, 0x2b, 0x51, 0x1b, 0x1f, 0x6a, + 0x53, 0x1a, 0x9a, 0xc4, 0x73, 0xb2, 0xcc, 0x46, 0x71, 0xfc, 0x38, 0xe3, 0x22, 0x9c, 0x71, 0x11, 0xce, 0xb8, 0x08, 0x67, 0x5c, 0x84, 0x33, 0x2e, 0xc2, 0x19, 0x17, 0xe1, 0x8c, 0x8b, 0x70, 0xc6, 0x45, 0x38, 0xe3, 0x22, 0xdc, 0x70, 0x11, 0x6e, 0xb8, 0x08, 0x37, 0x5c, 0x84, 0x1b, 0x2e, 0xc2, 0x0d, 0x17, 0xe1, 0x86, 0x8b, 0x70, 0xc3, 0x45, 0xb8, 0xe1, 0x22, 0xdc, 0x70, 0x11, 0x6e, 0xb8, 0x08, 0x37, 0x5c, 0x84, 0x1b, 0x2e, 0xc2, 0x0d, 0x17, 0xe1, 0x86, 0x8b, 0x70, 0xc3, 0x45, 0xb8, 0xe1, 0x22, 0xdc, 0x70, 0x11, 0x6e, 0xb8, 0x08, 0xc7, 0x5a, 0x84, 0x4b, 0xfd, 0x15, 0x87, 0xfa, 0xab, 0x7e, 0x9f, 0x2c, 0xd3, 0xef, 0x07, 0x13, 0xe5, 0x71, 0x7d, 0x12, 0x98, 0x0c, 0xa6, 0x80, 0x07, 0xc0, 0x83, 0xe0, 0x61, 0x79, 0x0a, 0x57, 0x7a, 0x0a, 0x57, 0x5a, 0x82, 0x2b, 0x2d, 0xc1, 0x95, + 0x96, 0xe0, 0x4a, 0x4b, 0x70, 0xa2, 0x25, 0x38, 0xd1, 0x12, 0x9c, 0x68, 0x09, 0x4e, 0xb4, 0x04, 0x27, 0x5a, 0x82, 0x6a, 0xfa, 0x50, 0x4d, 0x1f, 0xaa, 0xe9, 0x43, 0x35, 0x7d, 0xa8, 0xa6, 0x0f, 0xd5, 0xf4, 0xa1, 0x9a, 0x3e, 0x54, 0xd3, 0x87, 0x6a, 0xfa, 0x50, 0x4d, 0x1f, 0xaa, 0xe9, 0x43, 0x35, 0x7d, 0xa8, 0xa6, 0x0f, 0xd5, 0xf4, 0xa1, 0x9a, 0x3e, 0x54, 0xd3, 0x87, 0x6a, 0xfa, 0x50, 0x4d, 0x1f, 0xaa, 0xe9, 0x43, 0x35, 0x7d, 0xa8, 0xa6, 0x0f, 0xd5, 0xf4, 0xa1, 0x9a, 0x3e, 0x54, 0xd3, 0x87, 0x6a, 0xfa, 0x50, 0x4d, 0x1f, 0xaa, 0xe9, 0x43, 0x35, 0x7d, 0xa8, 0xa6, 0x0f, 0xd5, 0xf4, 0xa1, 0x9a, 0x3e, 0xfd, 0x5b, 0xb0, 0x0a, 0xac, 0x06, 0x6b, 0xa4, 0x1f, 0x05, 0xf5, 0xa3, 0xa0, 0x7e, 0x14, 0xd4, 0x8f, 0x82, 0xfa, 0x51, 0xd0, 0x4a, 0x14, 0xb4, 0x12, 0x05, 0xad, 0x44, 0x41, + 0x2b, 0x51, 0xd0, 0x4a, 0x14, 0xd4, 0x8f, 0x82, 0xfa, 0x51, 0x50, 0x3f, 0x0a, 0xea, 0x47, 0x41, 0xfd, 0x28, 0xa8, 0x1f, 0x05, 0xf5, 0xa3, 0xa0, 0x7e, 0x14, 0xd4, 0x8f, 0x82, 0xfa, 0x51, 0x50, 0x3f, 0x0a, 0xea, 0x47, 0x41, 0xfd, 0x28, 0xa8, 0x1f, 0x05, 0xf5, 0xa3, 0xa0, 0x7e, 0x14, 0xd4, 0x8f, 0x82, 0xfa, 0x51, 0x50, 0x3f, 0x0a, 0xea, 0x47, 0x41, 0xfd, 0x28, 0xa8, 0x1f, 0x05, 0xf5, 0xa3, 0xa0, 0x7e, 0x14, 0x34, 0x0b, 0x05, 0xcd, 0x42, 0x41, 0xb3, 0x50, 0xd0, 0x2c, 0x14, 0x34, 0x0b, 0x05, 0xcd, 0x42, 0x41, 0xb3, 0x50, 0xd0, 0x2c, 0x14, 0x34, 0x0b, 0x05, 0xcd, 0x42, 0x41, 0xb3, 0x50, 0xd0, 0x2c, 0x14, 0x34, 0x0b, 0x05, 0xcd, 0x42, 0x41, 0xb3, 0x50, 0xd0, 0x2c, 0x14, 0x34, 0x0b, 0x05, 0xcd, 0x42, 0x41, 0x0b, 0x51, 0xd0, 0x42, 0x14, 0xb4, 0x10, 0x05, 0x2d, 0x44, 0x41, + 0x0b, 0x51, 0xd0, 0x42, 0x14, 0xb4, 0x10, 0x05, 0xad, 0x44, 0x41, 0x2b, 0x51, 0xd0, 0x4a, 0x14, 0xb4, 0x12, 0x05, 0xad, 0x44, 0x41, 0x2b, 0x51, 0xd0, 0x4a, 0xa3, 0xb1, 0xf4, 0x19, 0x4d, 0x40, 0x53, 0xd0, 0x0c, 0x10, 0xa3, 0x46, 0x04, 0x88, 0x94, 0x47, 0x8c, 0x68, 0xd0, 0x02, 0xb4, 0x04, 0xb1, 0x20, 0x0e, 0xc4, 0x83, 0x04, 0x90, 0x08, 0x5a, 0x81, 0x36, 0x20, 0x19, 0xa4, 0x82, 0x0e, 0xe0, 0x3c, 0xd0, 0x09, 0x74, 0x01, 0xdd, 0x28, 0xa7, 0x3b, 0xe8, 0x01, 0xd2, 0x40, 0x4f, 0xd0, 0x0b, 0xf4, 0x06, 0xe9, 0xa0, 0x0f, 0xe8, 0x0b, 0xfa, 0x81, 0xfe, 0x60, 0x00, 0x18, 0x08, 0x32, 0x64, 0x29, 0xaa, 0x5e, 0x8a, 0xaa, 0x97, 0xa2, 0xea, 0xa5, 0xa8, 0x7a, 0x29, 0xaa, 0x5e, 0x8a, 0xaa, 0x97, 0xa2, 0xea, 0xa5, 0xa8, 0x7a, 0x29, 0xaa, 0x5e, 0x8a, 0xaa, 0x97, 0xa2, 0xea, 0xa5, 0xa8, + 0x7a, 0x29, 0xaa, 0xee, 0x47, 0xd5, 0xfd, 0xa8, 0xba, 0x1f, 0x55, 0xf7, 0xa3, 0xea, 0x7e, 0x54, 0xdd, 0x8f, 0xaa, 0xfb, 0x51, 0x75, 0x3f, 0xaa, 0xee, 0x47, 0xd5, 0xfd, 0xa8, 0xba, 0x1f, 0x55, 0xf7, 0xa3, 0xea, 0x7e, 0x54, 0xdd, 0x8f, 0xaa, 0xfb, 0x51, 0x75, 0x3f, 0xaa, 0xee, 0x47, 0xd5, 0xfd, 0xa8, 0xba, 0x1f, 0x55, 0xf7, 0xa3, 0xea, 0x7e, 0x54, 0xdd, 0x8f, 0xaa, 0xfb, 0x51, 0x75, 0x3f, 0xaa, 0xee, 0x47, 0xd5, 0xfd, 0xa8, 0xba, 0x1f, 0x55, 0x2f, 0x45, 0xd5, 0x4b, 0x51, 0xf5, 0x52, 0x54, 0xbd, 0x14, 0x55, 0x2f, 0x45, 0xd5, 0x4b, 0x51, 0xf5, 0x52, 0x54, 0xbd, 0x14, 0x55, 0x2f, 0x45, 0xd5, 0x4b, 0x51, 0xf5, 0x52, 0x54, 0x7d, 0x3f, 0xaa, 0xbe, 0x1f, 0x55, 0xdf, 0x8f, 0xaa, 0xef, 0x47, 0xd5, 0xf7, 0xa3, 0xea, 0xfb, 0x51, 0xf5, 0xfd, 0xa8, 0xfa, 0x7e, 0x54, 0x7d, 0x3f, + 0xaa, 0x9e, 0x8d, 0xaa, 0x67, 0xa3, 0xea, 0xd9, 0xa8, 0x7a, 0x36, 0xaa, 0x9e, 0x8d, 0xaa, 0x67, 0xa3, 0xea, 0xd9, 0xa8, 0x7a, 0x36, 0xaa, 0x9e, 0x8d, 0xaa, 0x67, 0xa3, 0xea, 0xd9, 0xa8, 0x7a, 0x36, 0xaa, 0x9e, 0x8d, 0xaa, 0x67, 0xa3, 0xea, 0xd9, 0xa8, 0x7a, 0x36, 0xaa, 0x9e, 0x8d, 0xaa, 0x67, 0xa3, 0xea, 0xd9, 0xa8, 0x7a, 0x36, 0xaa, 0x9e, 0x8d, 0xaa, 0x67, 0xa3, 0xea, 0xd9, 0xa8, 0x7a, 0x36, 0xaa, 0x9e, 0x8d, 0xaa, 0x67, 0xa3, 0xea, 0xd9, 0xa8, 0x7a, 0x36, 0xaa, 0x9e, 0x8d, 0xaa, 0x67, 0xa3, 0xea, 0xd9, 0xa8, 0x7a, 0x36, 0xaa, 0x9e, 0x8d, 0xaa, 0x67, 0xa3, 0xea, 0xd9, 0xa8, 0x7a, 0x36, 0xaa, 0x9e, 0x8d, 0xaa, 0x67, 0xa3, 0xea, 0xd9, 0xa8, 0xba, 0x0f, 0x55, 0xf7, 0xa1, 0xea, 0x3e, 0x54, 0xdd, 0x87, 0xaa, 0xfb, 0x50, 0x75, 0x1f, 0xaa, 0xee, 0x43, 0xd5, 0x7d, 0xa8, + 0xba, 0x0f, 0x55, 0xf7, 0xa1, 0xea, 0x3e, 0x54, 0xdd, 0x87, 0xaa, 0xfb, 0x50, 0x75, 0x1f, 0xaa, 0xee, 0x43, 0xd5, 0x7d, 0xa8, 0xba, 0x0f, 0x55, 0xf7, 0xa1, 0xea, 0x3e, 0x54, 0xdd, 0x87, 0xaa, 0xfb, 0x50, 0x75, 0x1f, 0xaa, 0x9e, 0x85, 0xaa, 0x67, 0xa1, 0xea, 0x79, 0xa8, 0x7a, 0x1e, 0xaa, 0x9e, 0x87, 0xaa, 0xe7, 0xa1, 0xea, 0x79, 0xa8, 0x7a, 0x1e, 0xaa, 0x9e, 0x87, 0xaa, 0xaf, 0x44, 0xd5, 0x57, 0xa2, 0xea, 0x6b, 0x51, 0xf5, 0xb5, 0xa8, 0xfa, 0x5a, 0x54, 0x7d, 0x2d, 0xaa, 0xbe, 0x16, 0x55, 0x5f, 0x8b, 0xaa, 0xaf, 0x45, 0xd5, 0xd7, 0xa2, 0xea, 0x6b, 0x51, 0xf5, 0xb5, 0xa8, 0xfa, 0x66, 0x54, 0x7d, 0x33, 0xaa, 0xbe, 0x19, 0x55, 0xdf, 0x8c, 0xaa, 0x6f, 0x46, 0xd5, 0x37, 0xa3, 0xea, 0xa5, 0x28, 0xba, 0x1f, 0x45, 0xf7, 0xa3, 0xe8, 0x7e, 0x14, 0xdd, 0x8f, 0xa2, 0xfb, 0x51, + 0x74, 0x1f, 0x8a, 0xee, 0x43, 0xd1, 0x7d, 0x28, 0xba, 0x0f, 0x45, 0xf7, 0xa1, 0xe8, 0x95, 0xb5, 0xe7, 0x57, 0xdd, 0x57, 0x7b, 0x7e, 0xd5, 0x3f, 0xff, 0x16, 0x29, 0x88, 0x72, 0x86, 0x52, 0xce, 0x08, 0xca, 0x19, 0xaa, 0x7e, 0x13, 0xba, 0x96, 0x4c, 0x69, 0x3d, 0x99, 0xd2, 0xfa, 0x9a, 0xec, 0x55, 0x6d, 0xd3, 0x9b, 0x6d, 0x5a, 0xb3, 0x4d, 0x92, 0xca, 0x97, 0x97, 0xb2, 0xcd, 0x27, 0x6c, 0xf3, 0x89, 0xf5, 0x6d, 0x92, 0x79, 0x3c, 0x31, 0x2b, 0xf8, 0x25, 0xb9, 0x2a, 0xf8, 0x15, 0xf9, 0x6d, 0xf0, 0xab, 0xf2, 0x07, 0x91, 0x61, 0x5d, 0xb7, 0x7f, 0x92, 0xd8, 0x8a, 0xea, 0x6d, 0x97, 0x5f, 0x8b, 0x5c, 0xf9, 0x91, 0xf8, 0x89, 0xdc, 0x6e, 0x87, 0x6c, 0x2f, 0x7e, 0x96, 0x73, 0xac, 0xeb, 0xf6, 0xbf, 0x20, 0xf2, 0xe4, 0x45, 0xd6, 0x75, 0xfb, 0xaf, 0x16, 0xbf, 0xc8, 0xc5, 0xd4, 0xbe, + 0x2f, 0xb5, 0x1f, 0x21, 0xbc, 0xa7, 0xcb, 0x69, 0xc1, 0x65, 0xa2, 0x4c, 0xf6, 0x64, 0x8f, 0x77, 0x93, 0xc7, 0xbd, 0x6d, 0xe6, 0x68, 0xf6, 0xee, 0x72, 0x92, 0xbd, 0x07, 0x18, 0x0a, 0x86, 0xc9, 0x6d, 0xf6, 0xe1, 0x60, 0x04, 0xeb, 0x97, 0x80, 0x91, 0xac, 0x8f, 0x02, 0xa3, 0x59, 0xbf, 0x14, 0x8c, 0x01, 0x97, 0x81, 0xcb, 0xc1, 0x15, 0xe0, 0x4a, 0x30, 0x96, 0xf7, 0xaf, 0x02, 0x57, 0xb3, 0x7e, 0x0d, 0xb8, 0x96, 0xf5, 0xeb, 0xc0, 0x38, 0x39, 0x49, 0xdd, 0x8f, 0xcc, 0xba, 0x17, 0x99, 0xa3, 0x85, 0xfc, 0xda, 0xd1, 0x12, 0xd4, 0xbd, 0xa7, 0xd8, 0xd7, 0xf5, 0xee, 0x29, 0xf6, 0xb5, 0xa3, 0x0d, 0x48, 0x06, 0x29, 0xa0, 0xee, 0x3d, 0xc5, 0xbe, 0x0e, 0xbc, 0xa7, 0x98, 0xcd, 0x46, 0x46, 0xff, 0x8d, 0xb0, 0xe3, 0x33, 0x74, 0xd0, 0x10, 0xc4, 0x80, 0x96, 0x20, 0x16, 0x24, 0x80, 0xb6, + 0xb2, 0x02, 0x3f, 0xe6, 0xc1, 0x8f, 0x79, 0xf0, 0x63, 0x1e, 0xfc, 0x98, 0x17, 0x3f, 0xe6, 0xc6, 0x8f, 0xb9, 0xf1, 0x63, 0x6e, 0xfc, 0x98, 0x1b, 0x3f, 0xe6, 0xc6, 0x8f, 0xb9, 0xf1, 0x63, 0x79, 0xf8, 0xb1, 0x3c, 0xfc, 0x58, 0x9e, 0xba, 0x3b, 0x6a, 0x6f, 0x5e, 0x4b, 0x07, 0x7d, 0xf8, 0xbf, 0x2f, 0xe8, 0x07, 0xfa, 0x83, 0x01, 0x60, 0x20, 0x18, 0x04, 0x32, 0xc0, 0x85, 0x60, 0x30, 0xb8, 0x08, 0x0c, 0x01, 0x17, 0x83, 0xa1, 0x60, 0x18, 0x18, 0x0e, 0x46, 0x80, 0x4b, 0xc0, 0x48, 0x30, 0x0a, 0x8c, 0x06, 0x97, 0x82, 0x31, 0xe0, 0x32, 0x70, 0x39, 0xb8, 0x02, 0x5c, 0x09, 0xc6, 0x82, 0xab, 0xc0, 0xd5, 0xe0, 0x1a, 0x70, 0x2d, 0xb8, 0x4e, 0xba, 0xf0, 0x64, 0xbb, 0xf0, 0x64, 0x5e, 0x3c, 0x99, 0x17, 0x4f, 0xe6, 0xc5, 0x93, 0x95, 0xe2, 0xc9, 0x4a, 0xf1, 0x64, 0xa5, 0xe6, 0x5d, 0x5c, + 0xf1, 0x65, 0xa5, 0xf8, 0xb2, 0x52, 0x7c, 0x99, 0x17, 0x5f, 0xe6, 0xc5, 0x97, 0x79, 0xf1, 0x65, 0x5e, 0x7c, 0x99, 0x17, 0x5f, 0xe6, 0xc5, 0x97, 0x79, 0xf1, 0x65, 0x6e, 0x7c, 0x99, 0x1b, 0x5f, 0xe6, 0xc6, 0x97, 0x79, 0xf1, 0x65, 0x5e, 0x7c, 0x59, 0x1e, 0xbe, 0xcc, 0x8b, 0x2f, 0xf3, 0xe0, 0xcb, 0x3c, 0xf8, 0x32, 0x0f, 0xbe, 0xcc, 0x63, 0x5e, 0x25, 0x18, 0x5f, 0x96, 0x87, 0x2f, 0xf3, 0xe0, 0xcb, 0x3c, 0xf8, 0x32, 0x0f, 0xbe, 0xcc, 0x83, 0x2f, 0xf3, 0xe0, 0xcb, 0x3c, 0xf8, 0x32, 0x0f, 0xbe, 0xcc, 0x83, 0x2f, 0xf3, 0xe0, 0xcb, 0x3c, 0xf8, 0xb2, 0x62, 0x7c, 0x59, 0x31, 0xbe, 0xac, 0x00, 0x5f, 0xe6, 0xc5, 0x97, 0x95, 0x5b, 0x47, 0x0c, 0x0a, 0x35, 0x34, 0x10, 0x6f, 0xe6, 0xc6, 0x9b, 0x15, 0xdb, 0x32, 0xf0, 0x62, 0xc3, 0xc0, 0x68, 0xc0, 0xfc, 0x8c, 0x47, 0xf3, 0xe2, 0xd1, + 0xbc, 0x78, 0x34, 0x37, 0x1e, 0x2d, 0x0f, 0x8f, 0xe6, 0xc5, 0xa3, 0x79, 0xf1, 0x68, 0x5e, 0x3c, 0x9a, 0x07, 0x8f, 0xe6, 0xc1, 0xa3, 0x15, 0xe2, 0xd1, 0x5c, 0xea, 0x4e, 0xb5, 0x33, 0xd9, 0xe6, 0x79, 0xfe, 0x7f, 0x91, 0xd7, 0x5f, 0x61, 0xdd, 0x3c, 0x22, 0xf0, 0x26, 0xdb, 0xce, 0x03, 0xf3, 0x79, 0xfd, 0x3d, 0x5e, 0x7b, 0x1f, 0x2c, 0x00, 0x1f, 0x80, 0x85, 0xe0, 0x43, 0xb0, 0x08, 0x7c, 0x04, 0x16, 0x83, 0x8f, 0xc1, 0x12, 0x3e, 0xff, 0x09, 0xb0, 0xee, 0x7c, 0x8b, 0x5f, 0xdb, 0x85, 0x5f, 0xf3, 0xe2, 0xd7, 0xbc, 0xf8, 0xb5, 0x3c, 0xfc, 0x5a, 0x1e, 0x7e, 0xad, 0xd4, 0xbc, 0x23, 0x2e, 0x9e, 0x2d, 0x0f, 0xcf, 0x96, 0x87, 0x67, 0xcb, 0xc3, 0xb3, 0xe5, 0xe1, 0xd9, 0xf2, 0x42, 0xd0, 0x7d, 0x7c, 0x9b, 0x1b, 0xdf, 0x96, 0x87, 0x6f, 0x2b, 0xc6, 0xb7, 0x79, 0xf1, 0x6d, 0x5e, 0x7c, + 0x9b, 0x07, 0xdf, 0xe6, 0xb6, 0xa3, 0x25, 0x76, 0xb4, 0xc4, 0x8e, 0x96, 0xd8, 0xd1, 0x12, 0xfb, 0x44, 0xb0, 0x10, 0x7c, 0x08, 0x16, 0x81, 0x8f, 0xc0, 0x62, 0xf0, 0x31, 0x58, 0x82, 0x27, 0x68, 0x00, 0x88, 0x5f, 0xbd, 0x11, 0x68, 0x0c, 0x9a, 0x80, 0xa6, 0xa0, 0x19, 0x08, 0x07, 0x51, 0x20, 0x1a, 0xb4, 0x00, 0xc4, 0xb8, 0x4e, 0x8c, 0xeb, 0xc4, 0xb8, 0x1e, 0x07, 0xe2, 0x01, 0xb1, 0xae, 0x27, 0x82, 0x56, 0xa0, 0x35, 0x48, 0x02, 0x6d, 0x40, 0x32, 0x48, 0x01, 0xa9, 0xa0, 0x2d, 0x68, 0x07, 0xc8, 0x41, 0xf0, 0x75, 0x15, 0xf8, 0xba, 0x0a, 0x7c, 0x5d, 0x05, 0xbe, 0xae, 0x02, 0x5f, 0x57, 0x81, 0xaf, 0xab, 0xc0, 0xab, 0x79, 0xf0, 0x6a, 0x1e, 0xbc, 0x9a, 0x07, 0xaf, 0xe6, 0xc1, 0xab, 0x79, 0xf0, 0x6a, 0x1e, 0xbc, 0x9a, 0x07, 0xaf, 0xe6, 0xc1, 0xab, 0x79, 0xf0, 0x6a, 0x1e, 0xbc, + 0x9a, 0x07, 0xaf, 0xe6, 0xc1, 0xab, 0x79, 0xf0, 0x6a, 0x1e, 0xbc, 0x9a, 0x07, 0xaf, 0xe6, 0xc1, 0xab, 0x79, 0xf0, 0x6a, 0x1e, 0xbc, 0x9a, 0x07, 0xaf, 0xe6, 0xc1, 0xab, 0x79, 0xf0, 0x6a, 0x1e, 0xbc, 0x9a, 0x07, 0xaf, 0xe6, 0xc1, 0xab, 0x79, 0xf0, 0x6a, 0x1e, 0xbc, 0x9a, 0x07, 0xaf, 0xe6, 0xc1, 0xab, 0x79, 0xf0, 0x6a, 0x1e, 0xbc, 0x9a, 0x07, 0xaf, 0xe6, 0xc5, 0xab, 0x79, 0xf1, 0x6a, 0x5e, 0xbc, 0x9a, 0x1b, 0xaf, 0xe6, 0xc6, 0xab, 0xb9, 0xf1, 0x6a, 0x6e, 0xbc, 0x9a, 0x1b, 0xaf, 0x96, 0x87, 0x57, 0xcb, 0xc3, 0xab, 0xe5, 0xe1, 0xd5, 0xf2, 0xf0, 0x6a, 0x79, 0x78, 0x35, 0x37, 0x5e, 0xcd, 0x8d, 0x57, 0x73, 0xe3, 0xd5, 0xdc, 0x78, 0x35, 0x37, 0x5e, 0xcd, 0x8d, 0x57, 0x73, 0xe3, 0xd5, 0xdc, 0x78, 0x35, 0x37, 0x5e, 0xcd, 0x8d, 0x57, 0x73, 0xe3, 0xd5, 0xdc, 0x78, 0x35, 0x37, + 0x5e, 0xcd, 0x8d, 0x57, 0x73, 0xe3, 0xd5, 0xdc, 0x78, 0x35, 0x37, 0x5e, 0xcd, 0x8d, 0x57, 0x73, 0xe3, 0xd5, 0xdc, 0x78, 0x35, 0x37, 0x5e, 0xcd, 0x8d, 0x57, 0x73, 0xe3, 0xd5, 0xdc, 0x78, 0xb5, 0x3c, 0xbc, 0x5a, 0x1e, 0x5e, 0x2d, 0x0f, 0xaf, 0x96, 0x87, 0x57, 0xcb, 0xc3, 0xab, 0xe5, 0xe1, 0xd5, 0xf2, 0xf0, 0x6a, 0x79, 0x78, 0xb5, 0x3c, 0xbc, 0x5a, 0x1e, 0x5e, 0x2d, 0x0f, 0xaf, 0x96, 0x87, 0x57, 0xcb, 0xc3, 0xab, 0xe5, 0xe1, 0xd5, 0xf2, 0xf0, 0x6a, 0x79, 0x78, 0xb5, 0x3c, 0xbc, 0x5a, 0x1e, 0x5e, 0xcd, 0x85, 0x57, 0x73, 0xe1, 0xd5, 0x5c, 0x78, 0x35, 0x17, 0x5e, 0xcd, 0x85, 0x57, 0x73, 0xe1, 0xd5, 0x5c, 0x78, 0xb5, 0x5d, 0x78, 0xb5, 0x5d, 0x78, 0xb5, 0x5d, 0x78, 0xb5, 0x5d, 0x78, 0xb5, 0x5d, 0x78, 0xb5, 0x5d, 0x78, 0xb5, 0x5d, 0x78, 0x35, 0x2f, 0x5e, 0xcd, 0x8b, 0x57, + 0xf3, 0xe2, 0xd5, 0xbc, 0x78, 0x35, 0x2f, 0x5e, 0xcd, 0x8b, 0x57, 0x2b, 0xc5, 0xab, 0x95, 0xe2, 0xd5, 0x4a, 0xf1, 0x6a, 0xa5, 0x78, 0xb5, 0x52, 0x75, 0xe7, 0xe6, 0x78, 0x9e, 0x13, 0x40, 0x22, 0x68, 0x05, 0xda, 0x80, 0x64, 0x90, 0x0a, 0x3a, 0x80, 0xf3, 0x40, 0x27, 0xd0, 0x45, 0x9e, 0xc6, 0xab, 0x79, 0xf1, 0x6a, 0x5e, 0xbc, 0x9a, 0x17, 0xaf, 0xe6, 0xc5, 0xab, 0x79, 0xf1, 0x6a, 0x5e, 0xbc, 0x9a, 0x17, 0xaf, 0xe6, 0xc5, 0xab, 0x79, 0xf1, 0x6a, 0x5e, 0xbc, 0x9a, 0x17, 0xaf, 0xe6, 0xc5, 0xab, 0x79, 0xf1, 0x6a, 0x5e, 0xbc, 0x9a, 0x17, 0xaf, 0xe6, 0xc5, 0xab, 0x79, 0xf1, 0x6a, 0x5e, 0xbc, 0x9a, 0x17, 0xaf, 0xe6, 0xc5, 0xab, 0x79, 0xf1, 0x6a, 0x5e, 0xbc, 0x9a, 0x17, 0xaf, 0xe6, 0xc5, 0xab, 0x79, 0xf1, 0x6a, 0x5e, 0xbc, 0x9a, 0x17, 0xaf, 0xe6, 0xc6, 0xab, 0xb9, 0xf1, 0x6a, + 0x6e, 0xbc, 0x9a, 0x1b, 0xaf, 0xe6, 0xc6, 0xab, 0xb9, 0xf1, 0x6a, 0x6e, 0xbc, 0x9a, 0x1b, 0xaf, 0xe6, 0xc6, 0xab, 0xb9, 0xf1, 0x6a, 0x6e, 0xbc, 0x9a, 0x1b, 0xaf, 0xe6, 0xc6, 0xab, 0xb9, 0xf1, 0x6a, 0x6e, 0xbc, 0x9a, 0x1b, 0xaf, 0xe6, 0xc6, 0xab, 0xb9, 0xf1, 0x6a, 0x6e, 0xbc, 0x9a, 0x1b, 0xaf, 0xe6, 0xc6, 0xab, 0xb9, 0xf1, 0x6a, 0x6e, 0xbc, 0x9a, 0x1b, 0xaf, 0xe6, 0xc6, 0xab, 0xb9, 0xf1, 0x6a, 0x6e, 0xbc, 0x9a, 0x17, 0xaf, 0xe6, 0xc5, 0xab, 0x79, 0xf1, 0x6a, 0x5e, 0xbc, 0x9a, 0x17, 0xaf, 0xe6, 0xc5, 0xab, 0x79, 0xf1, 0x6a, 0x5e, 0xbc, 0x9a, 0x17, 0xaf, 0xe6, 0xc5, 0xab, 0x79, 0xf1, 0x6a, 0x1e, 0xbc, 0x9a, 0x07, 0xaf, 0xe6, 0xc1, 0xab, 0x79, 0xf0, 0x6a, 0x1e, 0xbc, 0x9a, 0x07, 0xaf, 0xe6, 0xc1, 0xab, 0x79, 0xf0, 0x6a, 0x1e, 0xbc, 0x9a, 0x07, 0xaf, 0xe6, 0xc1, 0xab, + 0x79, 0xf0, 0x6a, 0x1e, 0xbc, 0x9a, 0x07, 0xaf, 0xe6, 0xc1, 0xab, 0x79, 0xf0, 0x6a, 0x1e, 0xbc, 0x9a, 0x07, 0xaf, 0xe6, 0xc1, 0xab, 0x79, 0xf0, 0x6a, 0x1e, 0xbc, 0x9a, 0x07, 0xaf, 0xe6, 0xc1, 0xab, 0x79, 0xf0, 0x6a, 0x1e, 0xbc, 0x9a, 0x07, 0xaf, 0xe6, 0xc1, 0xab, 0x79, 0xf0, 0x6a, 0x1e, 0xbc, 0x9a, 0x07, 0xaf, 0xe6, 0xc1, 0xab, 0x79, 0xf0, 0x6a, 0x1e, 0xbc, 0x9a, 0x07, 0xaf, 0xe6, 0xc1, 0xab, 0x79, 0xf0, 0x6a, 0x1e, 0xbc, 0x9a, 0x07, 0xaf, 0xe6, 0xc1, 0xab, 0x79, 0xf0, 0x6a, 0x1e, 0xbc, 0x9a, 0x07, 0xaf, 0xe6, 0xc1, 0xab, 0x79, 0xf0, 0x6a, 0x1e, 0xbc, 0x9a, 0x07, 0xaf, 0xe6, 0xc1, 0xab, 0x79, 0xf0, 0x6a, 0xc5, 0x78, 0xb5, 0x62, 0xbc, 0x5a, 0x31, 0x5e, 0xad, 0x18, 0xaf, 0x56, 0x8c, 0x57, 0x2b, 0xc6, 0xab, 0x15, 0xe3, 0xd5, 0x8a, 0xf1, 0x6a, 0xc5, 0x78, 0xb5, 0x62, + 0xbc, 0x5a, 0x31, 0x5e, 0xad, 0x18, 0xaf, 0x56, 0x8c, 0x57, 0x2b, 0xc6, 0xab, 0x15, 0xe3, 0xd5, 0x8a, 0xf1, 0x6a, 0xc5, 0x78, 0xb5, 0x62, 0xbc, 0x5a, 0x31, 0x5e, 0xad, 0x18, 0xaf, 0x56, 0x8c, 0x57, 0x2b, 0xc6, 0xab, 0x15, 0xe0, 0xd5, 0x0a, 0xf0, 0x6a, 0x5e, 0xbc, 0x9a, 0x17, 0xaf, 0xe6, 0xc5, 0xab, 0x79, 0xf1, 0x6a, 0x5e, 0xbc, 0x9a, 0x17, 0xaf, 0xe6, 0xc5, 0xab, 0x95, 0xe3, 0xd5, 0xca, 0xff, 0xc6, 0x11, 0x98, 0x42, 0xbc, 0x5a, 0x21, 0x5e, 0xad, 0x10, 0xaf, 0x56, 0x88, 0x57, 0x2b, 0xc4, 0xab, 0x15, 0xe2, 0xd5, 0xbc, 0x0e, 0x72, 0x23, 0x07, 0xb9, 0x91, 0xc3, 0x07, 0x0e, 0x82, 0x43, 0xe0, 0x18, 0x2a, 0xef, 0x07, 0x95, 0xe0, 0x57, 0x70, 0x1c, 0x9c, 0x90, 0xc5, 0xf8, 0xb7, 0x62, 0xfc, 0x5b, 0x31, 0xfe, 0xad, 0x18, 0xff, 0x56, 0x8c, 0x7f, 0xcb, 0xfb, 0xbf, 0xe4, 0x7e, + 0xdb, 0xa6, 0xab, 0x6b, 0x81, 0xab, 0x4b, 0xc5, 0xd5, 0xb5, 0xf9, 0x1f, 0x5c, 0xcd, 0x26, 0x94, 0x56, 0xba, 0x69, 0xe5, 0x7e, 0x5a, 0x59, 0x4e, 0x2b, 0x8f, 0xb0, 0x87, 0x15, 0x94, 0x5e, 0xa0, 0xbe, 0xdf, 0x35, 0xcf, 0x12, 0xa8, 0xfc, 0x3f, 0x74, 0xd5, 0xff, 0x3f, 0x3f, 0xf7, 0xbc, 0xd1, 0x1f, 0x6d, 0x11, 0xf2, 0x83, 0xe8, 0xc4, 0x3e, 0xcd, 0xdf, 0xf7, 0x9a, 0x57, 0x30, 0x6e, 0xa0, 0x7e, 0x2f, 0x79, 0x9e, 0x98, 0x6a, 0x3c, 0x2f, 0x62, 0xd5, 0xa7, 0x6d, 0xd6, 0xf9, 0xff, 0x79, 0xc2, 0x66, 0x7f, 0xcf, 0x3c, 0x67, 0x49, 0xdf, 0xe4, 0x0c, 0x17, 0xcd, 0x45, 0x07, 0x21, 0xe4, 0x29, 0x79, 0x50, 0x4e, 0x94, 0x7b, 0xe5, 0x7e, 0xf9, 0xb2, 0x5c, 0x27, 0x97, 0xc8, 0xd9, 0x3c, 0xef, 0x90, 0x5e, 0x5e, 0x7f, 0x11, 0x1c, 0x96, 0x93, 0xe4, 0x62, 0x9e, 0xcb, 0xe4, 0x2e, 0xf9, 0xbc, + 0xdc, 0x29, 0x97, 0xb1, 0x4d, 0xb1, 0xf8, 0x0b, 0x7f, 0xd2, 0x27, 0x2b, 0x64, 0x0e, 0xcf, 0x47, 0x64, 0x91, 0x2c, 0xc4, 0xc3, 0x98, 0xaf, 0x15, 0xf2, 0xea, 0x01, 0x9e, 0x0f, 0x82, 0xfd, 0xbc, 0x7f, 0x10, 0xef, 0x5e, 0xbd, 0xb5, 0x57, 0x56, 0xb2, 0x65, 0x85, 0x3c, 0x20, 0x77, 0x03, 0x72, 0x1a, 0xe9, 0x91, 0xe5, 0xf2, 0x97, 0x3f, 0x28, 0xff, 0x10, 0xd8, 0x87, 0xaf, 0x34, 0xd7, 0x8b, 0xd9, 0xf6, 0x64, 0x9d, 0x77, 0xcb, 0x61, 0xd5, 0x7c, 0xce, 0x97, 0xa5, 0xd2, 0xa7, 0xd6, 0x4a, 0xd5, 0x63, 0xc5, 0x39, 0xcb, 0x3b, 0x28, 0x8f, 0xcb, 0xe5, 0x3c, 0x1f, 0x95, 0x3f, 0xd3, 0xde, 0x13, 0xea, 0xb5, 0xe3, 0x94, 0xe3, 0xb3, 0xf6, 0xf5, 0x3d, 0x25, 0x1d, 0xaa, 0xf9, 0x3c, 0x31, 0x40, 0x59, 0x32, 0x53, 0x6e, 0xe0, 0x55, 0x37, 0x5b, 0x65, 0xca, 0x02, 0x5a, 0x64, 0xb2, 0xb4, 0xa3, + 0xba, 0x4e, 0xb5, 0xe5, 0x9e, 0xaa, 0x6e, 0x85, 0xfc, 0x09, 0xfc, 0x2c, 0x77, 0xaa, 0x75, 0x37, 0x4c, 0xee, 0x37, 0xcb, 0xb7, 0xb6, 0x39, 0xce, 0xff, 0x05, 0x6a, 0xed, 0x67, 0x6a, 0x9c, 0x53, 0xcd, 0x95, 0xd9, 0x0a, 0xb5, 0x56, 0x59, 0x5b, 0x56, 0x45, 0x9d, 0x72, 0x5f, 0x56, 0xcf, 0x8b, 0xc1, 0x0e, 0xbc, 0xa1, 0xb9, 0x9e, 0x2b, 0xbf, 0x92, 0x9f, 0xf0, 0x7c, 0xd8, 0xfa, 0xe4, 0x61, 0xf9, 0x91, 0x3c, 0xaa, 0xd6, 0xf6, 0xc9, 0x12, 0xfa, 0x71, 0x18, 0x6b, 0xef, 0xca, 0x59, 0xf2, 0x43, 0x79, 0xa5, 0xdc, 0x24, 0x27, 0xcb, 0xcf, 0xe5, 0x48, 0x39, 0x58, 0x7d, 0x6e, 0xa7, 0xd9, 0x4b, 0x6a, 0xcb, 0x13, 0xf2, 0x98, 0xdc, 0xc3, 0xf3, 0x31, 0xda, 0x53, 0x40, 0x8e, 0x66, 0xbe, 0xf6, 0x4b, 0x75, 0x89, 0x66, 0x7d, 0xe5, 0x49, 0x6a, 0x7b, 0x5c, 0x1e, 0xb3, 0x6a, 0xb1, 0x87, 0x1e, + 0x55, 0xeb, 0xf4, 0xda, 0x51, 0x96, 0x72, 0xb6, 0x3c, 0x89, 0x83, 0x3e, 0x93, 0xdf, 0x23, 0x56, 0x8f, 0xec, 0x53, 0x71, 0x50, 0x66, 0xd5, 0xea, 0x90, 0x19, 0x01, 0xb5, 0x51, 0x70, 0xa4, 0xba, 0x1d, 0x66, 0x6f, 0x51, 0x52, 0x35, 0x23, 0xf9, 0x16, 0x17, 0x55, 0x7c, 0xea, 0xc0, 0x59, 0xca, 0x3d, 0x5c, 0xd3, 0xd3, 0xea, 0x53, 0xaa, 0x24, 0xda, 0x5a, 0x64, 0x46, 0x9c, 0xd9, 0x6f, 0xd5, 0xb1, 0x62, 0xb6, 0xc8, 0xda, 0x43, 0x15, 0x75, 0xce, 0x57, 0x4c, 0xfb, 0x68, 0xb5, 0x87, 0xc7, 0x5d, 0x44, 0x7f, 0x65, 0x4d, 0x1d, 0x02, 0x4a, 0xde, 0xaa, 0x1e, 0x57, 0x82, 0x2f, 0xe5, 0x1b, 0xaa, 0x97, 0xb6, 0xca, 0x2d, 0xd6, 0x7b, 0x1b, 0xac, 0xe7, 0x1f, 0xe4, 0x52, 0xf5, 0xfe, 0x9b, 0x72, 0xb9, 0xfc, 0xa2, 0xf6, 0x93, 0x55, 0xbf, 0x3f, 0xfe, 0xbe, 0xb5, 0xdc, 0x68, 0xfd, 0xf7, 0xb3, + 0x1c, 0x24, 0xb7, 0xc9, 0x35, 0x72, 0x34, 0x3d, 0x72, 0x91, 0xec, 0x25, 0xef, 0x92, 0xab, 0xc8, 0x8d, 0x85, 0x3c, 0x1f, 0xdc, 0x26, 0x07, 0xcb, 0xc7, 0x78, 0xde, 0x2c, 0xd7, 0xca, 0xf3, 0x79, 0x67, 0x28, 0xbd, 0xb4, 0xaa, 0xb6, 0x9c, 0xca, 0x7a, 0x35, 0xfc, 0x48, 0x96, 0xf0, 0x38, 0x59, 0x2e, 0x50, 0x6d, 0x3c, 0x2e, 0x57, 0xa8, 0xf8, 0x75, 0x05, 0xc4, 0xef, 0x11, 0x35, 0xd6, 0x0e, 0xa9, 0x9a, 0xd6, 0x8f, 0xdf, 0xa3, 0x44, 0xed, 0x26, 0x15, 0xb3, 0xe5, 0xf2, 0x5b, 0xfa, 0xba, 0x84, 0x3e, 0xcc, 0x93, 0x3f, 0xd5, 0x1d, 0x75, 0xf4, 0xe6, 0x11, 0xf3, 0x15, 0xb9, 0xa5, 0x86, 0x4d, 0xab, 0xe4, 0x43, 0xaa, 0x4f, 0xaa, 0xc7, 0x56, 0x95, 0xdc, 0x1c, 0xf0, 0xde, 0x16, 0x79, 0x1d, 0xe3, 0x79, 0x97, 0x7c, 0x8b, 0xc7, 0x6b, 0xcd, 0xe8, 0x01, 0x03, 0x64, 0x2f, 0x73, 0x4e, 0xa8, + 0x8e, 0xfe, 0xea, 0x58, 0x66, 0x4f, 0x59, 0xf2, 0x57, 0xea, 0xb5, 0x8b, 0x1e, 0xd9, 0x63, 0xc5, 0xc3, 0x4e, 0xb3, 0x86, 0x66, 0xcc, 0xf2, 0xae, 0x97, 0x1a, 0x6e, 0xb6, 0x22, 0xd9, 0x9c, 0x9f, 0x56, 0x57, 0xef, 0xb9, 0x76, 0x5c, 0xd4, 0xf6, 0x58, 0x75, 0x89, 0xb5, 0x35, 0x3b, 0xa9, 0x62, 0x0c, 0x4d, 0xa3, 0x8c, 0xcd, 0xd6, 0xb8, 0xa8, 0x9e, 0x77, 0x0e, 0x5b, 0x11, 0x7e, 0xb8, 0x66, 0xb6, 0xa0, 0x1f, 0xf2, 0xd9, 0xf2, 0x14, 0x2d, 0xd8, 0xc7, 0xf6, 0xc5, 0x8c, 0xa4, 0xed, 0xd4, 0x2c, 0xff, 0x2c, 0x71, 0x76, 0x42, 0x26, 0xa8, 0xe7, 0x86, 0xe0, 0x29, 0xb2, 0x7e, 0x73, 0xfd, 0x45, 0xd9, 0x43, 0xb6, 0x55, 0x31, 0x38, 0xcb, 0x8a, 0xc5, 0xc6, 0x72, 0x1d, 0xcf, 0x4f, 0xcb, 0x31, 0xf2, 0x52, 0xd9, 0x40, 0x4e, 0x65, 0xbd, 0x01, 0xfd, 0x13, 0x2b, 0x87, 0xc9, 0x51, 0x32, 0x4e, + 0xf6, 0x97, 0x8f, 0xc9, 0x29, 0xbc, 0x36, 0x5e, 0xde, 0xcd, 0x5e, 0x67, 0xc8, 0x25, 0x6a, 0x9e, 0xac, 0x8e, 0x73, 0x46, 0x1a, 0x7d, 0x51, 0x61, 0xc5, 0x6f, 0x81, 0xf9, 0xaa, 0xd9, 0x77, 0xd6, 0xdc, 0xb4, 0xbb, 0x66, 0x06, 0xa0, 0xa6, 0x79, 0x2a, 0x5a, 0xb6, 0xc3, 0x49, 0x8e, 0x5c, 0xcf, 0x67, 0x72, 0x88, 0xe2, 0x5f, 0x98, 0xdb, 0xeb, 0xd7, 0xf8, 0x51, 0xf5, 0x78, 0x0f, 0x58, 0x2f, 0x73, 0xd5, 0xfa, 0x8f, 0x72, 0x9e, 0x7c, 0x51, 0xf5, 0xd7, 0x2e, 0xab, 0xdf, 0xee, 0x21, 0x1b, 0x16, 0x44, 0xc1, 0x76, 0x96, 0xdb, 0xe5, 0x03, 0xac, 0xdf, 0xc1, 0xfc, 0x3f, 0x89, 0x1a, 0x66, 0x11, 0x7d, 0x1f, 0xca, 0xa9, 0xea, 0xf3, 0xf3, 0x88, 0x8f, 0x7c, 0x66, 0x40, 0x9f, 0xe2, 0xf7, 0x75, 0xf5, 0xd9, 0x97, 0x55, 0x1d, 0xac, 0xb9, 0x4d, 0xae, 0x90, 0x1f, 0xf3, 0xec, 0xb7, 0xc6, 0x9f, + 0x5f, 0xbe, 0x62, 0x8e, 0x70, 0x7a, 0xf0, 0x17, 0x5a, 0x32, 0x5b, 0x3e, 0xcf, 0xfa, 0x1c, 0xa2, 0xfd, 0x15, 0x39, 0x85, 0xde, 0x9e, 0x4d, 0x69, 0xef, 0x9a, 0xa5, 0xc8, 0x95, 0xb4, 0xe1, 0x68, 0xcd, 0x7c, 0x29, 0x7f, 0x54, 0x8f, 0x66, 0x0c, 0xaf, 0x90, 0x1f, 0x58, 0xa3, 0x6b, 0xad, 0x2a, 0xa7, 0xca, 0x9a, 0x13, 0xab, 0xe4, 0x77, 0xe6, 0x58, 0xe2, 0xf3, 0x0b, 0xe5, 0x52, 0x73, 0xb6, 0x36, 0xe7, 0xa9, 0x9a, 0x79, 0xb1, 0x3a, 0x2a, 0x02, 0xc6, 0xdb, 0x76, 0xeb, 0xbf, 0x8e, 0xea, 0x31, 0x05, 0xcc, 0x57, 0xb5, 0x3e, 0x41, 0x64, 0x5e, 0x29, 0x2f, 0x57, 0xe5, 0xcd, 0xb5, 0xca, 0x4d, 0x56, 0xfb, 0x7d, 0x4d, 0xbe, 0x24, 0x5f, 0x95, 0xad, 0xe5, 0xed, 0xe6, 0x68, 0x44, 0x15, 0xdb, 0xc9, 0x3e, 0xfc, 0x9f, 0x08, 0x4b, 0xb7, 0xca, 0xeb, 0x78, 0xed, 0x66, 0xea, 0x9e, 0x2b, 0xa7, + 0xcb, 0xef, 0xd5, 0xfc, 0x5b, 0x3d, 0xe7, 0xee, 0x50, 0x11, 0xbf, 0xae, 0x7a, 0x8f, 0x30, 0x51, 0xfc, 0xfb, 0xc8, 0xa7, 0xdc, 0x9d, 0x6a, 0xae, 0x39, 0x84, 0x6a, 0x6e, 0xb5, 0xda, 0x50, 0x52, 0x1b, 0xbf, 0x55, 0x75, 0xe7, 0x75, 0xe9, 0xe7, 0xc1, 0x26, 0xba, 0x88, 0x20, 0x11, 0xc7, 0x5a, 0x02, 0xea, 0xdc, 0xd2, 0x36, 0xc0, 0x36, 0x40, 0xb4, 0xb5, 0x0d, 0xb2, 0x5d, 0x28, 0xda, 0xd9, 0x86, 0xd8, 0x86, 0x88, 0xf3, 0x6c, 0x43, 0x6d, 0xc3, 0x44, 0x47, 0xdb, 0x25, 0xb6, 0x4b, 0x44, 0x67, 0xdb, 0x28, 0xdb, 0x68, 0xd1, 0xc5, 0x36, 0xc6, 0x36, 0x46, 0x74, 0xb3, 0x5d, 0x6d, 0xbb, 0x46, 0x74, 0xb7, 0x5d, 0x6f, 0xbb, 0x5e, 0xa4, 0xd9, 0x6e, 0xb6, 0xdd, 0x22, 0x7a, 0xda, 0x6e, 0xb3, 0xdd, 0x2d, 0x2e, 0x08, 0x89, 0x0b, 0x89, 0x13, 0xfd, 0x42, 0xf6, 0x84, 0xec, 0x11, 0xfd, 0x43, + 0xf2, 0x43, 0x7e, 0x11, 0x03, 0x42, 0xf6, 0x87, 0xec, 0x17, 0x83, 0x42, 0x5b, 0x85, 0xb6, 0x12, 0x19, 0xf6, 0xf6, 0xf6, 0x6e, 0xe2, 0x42, 0x7b, 0x4f, 0xfb, 0x10, 0x71, 0x89, 0xfd, 0x06, 0xfb, 0x4d, 0x62, 0xbc, 0xfd, 0x36, 0xfb, 0x3d, 0xe2, 0x26, 0xfb, 0x44, 0xfb, 0x54, 0x71, 0x97, 0xfd, 0x61, 0xfb, 0x53, 0x62, 0xa2, 0xfd, 0x39, 0xfb, 0x2b, 0x62, 0x9a, 0xfd, 0x55, 0xfb, 0x9b, 0xe2, 0x59, 0xfb, 0x07, 0xf6, 0x4f, 0xc4, 0x2c, 0xfb, 0x32, 0xfb, 0xe7, 0xe2, 0x55, 0xfb, 0x06, 0xfb, 0x56, 0x31, 0xcf, 0xbe, 0xc3, 0x9e, 0x27, 0x16, 0xd8, 0x4b, 0xed, 0x65, 0xe2, 0x63, 0xbb, 0xcf, 0x7e, 0x50, 0x7c, 0x62, 0x3f, 0x6c, 0xaf, 0x12, 0x4b, 0xed, 0xa7, 0xf5, 0x60, 0xf1, 0x8d, 0x1e, 0xaa, 0x3b, 0xc5, 0x1a, 0xbd, 0x81, 0x1e, 0x2e, 0xb2, 0xf4, 0x28, 0xbd, 0x9d, 0xd8, 0xaa, 0x77, 0xd7, + 0xbb, 0x8b, 0x02, 0xbd, 0xa7, 0x7e, 0x81, 0xd8, 0xa7, 0xf7, 0xd1, 0x2f, 0x16, 0xc5, 0xfa, 0x30, 0x7d, 0xb4, 0xa8, 0xd0, 0x2f, 0xd3, 0x2f, 0x13, 0x27, 0xf4, 0x2b, 0xf4, 0x07, 0x45, 0x95, 0xfe, 0xb0, 0xfe, 0xb8, 0xd6, 0x52, 0x7f, 0x4a, 0x7f, 0x56, 0x6b, 0xa5, 0xbf, 0xa4, 0xbf, 0xa2, 0xa5, 0xe8, 0xaf, 0xea, 0xaf, 0x6b, 0xed, 0x9c, 0x5f, 0x3b, 0xbf, 0xd6, 0xce, 0x73, 0x6e, 0x72, 0x6e, 0xd2, 0x3a, 0xc2, 0xd4, 0x0d, 0x22, 0x84, 0x25, 0x18, 0x5f, 0x16, 0x2a, 0x34, 0x61, 0xc7, 0x45, 0xd9, 0x84, 0x21, 0x1c, 0xf0, 0xe7, 0x14, 0x61, 0xbc, 0xde, 0x80, 0x25, 0x44, 0x34, 0x64, 0x09, 0xc5, 0x3f, 0x35, 0x65, 0x8b, 0x66, 0x2c, 0xba, 0x08, 0x67, 0x31, 0x44, 0x84, 0x88, 0x54, 0xd7, 0xfd, 0x6e, 0xc1, 0xeb, 0x31, 0xa2, 0x25, 0x9f, 0x89, 0x65, 0x09, 0x86, 0xfb, 0x38, 0x3e, 0x9d, 0xc0, + 0xe2, 0xc4, 0x07, 0x27, 0x52, 0x46, 0x2b, 0xd1, 0x9a, 0x32, 0x92, 0xf0, 0xa0, 0x8d, 0x44, 0xb2, 0x48, 0x11, 0x8d, 0x45, 0x2a, 0x4b, 0x13, 0xd1, 0x96, 0xa5, 0xa9, 0x68, 0xc7, 0xd2, 0x4c, 0xb4, 0x67, 0x09, 0xa7, 0xc7, 0x3a, 0x88, 0x08, 0xd5, 0x67, 0x1a, 0x7d, 0x36, 0x48, 0x38, 0x6d, 0x19, 0xb6, 0x0c, 0x61, 0xb3, 0x5d, 0x48, 0xff, 0x05, 0xa9, 0xfe, 0x73, 0xd2, 0x7f, 0x43, 0x45, 0x88, 0x6d, 0x18, 0xbd, 0x18, 0xaa, 0x7a, 0xd1, 0xa0, 0x17, 0x47, 0x09, 0xbb, 0x6d, 0x34, 0x7d, 0xe9, 0x54, 0x7d, 0x19, 0x46, 0x5f, 0x5e, 0x2d, 0xc2, 0x6d, 0xd7, 0xd0, 0xa3, 0x11, 0xaa, 0x47, 0x43, 0xe9, 0xd1, 0x9b, 0x59, 0xbf, 0x85, 0x7e, 0x6d, 0x4c, 0xbf, 0xde, 0x26, 0x1a, 0xd8, 0x6e, 0xb7, 0xdd, 0xce, 0x36, 0x77, 0xd8, 0xee, 0xe0, 0x95, 0x3b, 0x6d, 0x77, 0xf2, 0xca, 0x5d, 0xb6, 0xbb, 0x28, + 0xed, 0x6e, 0x7a, 0x3d, 0x42, 0xf5, 0xba, 0x5d, 0xf5, 0xba, 0x53, 0xf5, 0xba, 0x43, 0xf5, 0x7a, 0x90, 0xea, 0x75, 0xbb, 0xfd, 0x46, 0x7a, 0x3a, 0x98, 0x9e, 0xbe, 0x53, 0x68, 0xf6, 0xbb, 0xe8, 0x6f, 0x1b, 0xfd, 0x3d, 0x91, 0xc7, 0x49, 0xf6, 0x07, 0x45, 0x90, 0x7d, 0x2a, 0x7d, 0x1f, 0x41, 0xdf, 0x3f, 0xc2, 0xfa, 0x34, 0x22, 0x20, 0x58, 0x45, 0x40, 0xb0, 0x8a, 0x80, 0x60, 0x22, 0xe0, 0x03, 0x11, 0x62, 0x5f, 0x68, 0x5f, 0x22, 0x42, 0xed, 0x9f, 0x10, 0x0d, 0x76, 0x15, 0x0d, 0x76, 0x15, 0x0d, 0x76, 0x15, 0x0d, 0x76, 0x15, 0x0d, 0x3a, 0xd1, 0xe0, 0xe3, 0xf1, 0x20, 0x31, 0x61, 0x10, 0x13, 0x15, 0x3c, 0x1e, 0xb5, 0x9f, 0x10, 0x0e, 0x7b, 0x15, 0xf1, 0xa1, 0xab, 0xf8, 0xb0, 0x13, 0x1f, 0x0e, 0x1e, 0x9d, 0x44, 0x89, 0xae, 0xa2, 0xc4, 0xa9, 0xa2, 0xc4, 0xa9, 0xa2, 0xc4, 0xa9, + 0xa2, 0x24, 0x8c, 0x28, 0xb9, 0x50, 0x34, 0xd0, 0x07, 0x13, 0x2b, 0x0d, 0x89, 0x95, 0x91, 0x3c, 0x8e, 0x22, 0x62, 0x1a, 0xa9, 0x88, 0x69, 0x44, 0xc4, 0x5c, 0xc5, 0xe3, 0xd5, 0xfa, 0xbd, 0xa2, 0xb1, 0x7e, 0x9f, 0x7e, 0xbf, 0x68, 0xa2, 0x4f, 0x24, 0x86, 0x9a, 0x12, 0x43, 0x8f, 0x88, 0x66, 0xfa, 0x34, 0xfd, 0x71, 0x11, 0x6e, 0x46, 0x12, 0x8f, 0x44, 0x92, 0x88, 0x30, 0x23, 0x49, 0x44, 0x98, 0x91, 0x24, 0xec, 0x66, 0x24, 0x11, 0x07, 0x36, 0xb1, 0x9e, 0x98, 0x31, 0x7b, 0xde, 0x8c, 0xa7, 0x04, 0x15, 0x49, 0x11, 0x2a, 0x6e, 0x22, 0x54, 0xc4, 0xc4, 0xb0, 0xc4, 0xb2, 0x6e, 0xc6, 0x44, 0x84, 0x8a, 0x86, 0xb6, 0x2a, 0x1a, 0x9a, 0xa8, 0x68, 0x08, 0x56, 0xd1, 0x10, 0x1a, 0x10, 0x0d, 0x2d, 0x55, 0x34, 0x04, 0xa9, 0x68, 0x48, 0x16, 0xe7, 0x89, 0x4e, 0x7c, 0xa6, 0x33, 0x4b, 0x33, + 0xc6, 0x76, 0x1a, 0xf1, 0xd6, 0x53, 0x5c, 0x40, 0xac, 0xf4, 0x16, 0xe9, 0xac, 0xf7, 0x11, 0xd7, 0x12, 0x67, 0xd7, 0xb1, 0xc4, 0x8a, 0x71, 0x2c, 0x21, 0xe2, 0x7a, 0xe2, 0xba, 0x99, 0xb8, 0xd1, 0xfa, 0xc5, 0x79, 0xf5, 0xef, 0xcd, 0x6f, 0xe7, 0x95, 0x3b, 0x58, 0x1a, 0x88, 0x3b, 0xc5, 0x5d, 0x44, 0xe1, 0xdd, 0xe2, 0x1e, 0xd6, 0xef, 0x15, 0xf7, 0x53, 0xc2, 0x44, 0x96, 0x06, 0x62, 0x12, 0x4b, 0x33, 0x31, 0x99, 0x25, 0x5c, 0x4c, 0x61, 0x69, 0x20, 0x1e, 0x10, 0x0f, 0xa9, 0x2b, 0xdf, 0x3c, 0xcc, 0xeb, 0x8f, 0xb0, 0x38, 0xc5, 0x34, 0xf1, 0x0c, 0x6d, 0x9b, 0x21, 0x9e, 0x25, 0xbe, 0x67, 0xb2, 0x44, 0x89, 0xe7, 0x58, 0xa2, 0xc5, 0xf3, 0x2c, 0x89, 0xe2, 0x5f, 0x2c, 0xba, 0x98, 0xc5, 0xd2, 0x5a, 0xeb, 0xa5, 0xf5, 0x16, 0x6d, 0xb4, 0x74, 0x2d, 0x5d, 0x34, 0xd7, 0xfa, 0x54, 0x5f, + 0x05, 0x5f, 0xeb, 0xcb, 0x7a, 0x3f, 0x75, 0x25, 0xfc, 0xfe, 0xda, 0x00, 0x91, 0xa4, 0x0d, 0xd4, 0x06, 0x8a, 0x14, 0x6d, 0x90, 0x36, 0x88, 0xfc, 0x2c, 0x43, 0xcb, 0x60, 0xfd, 0x42, 0xed, 0x42, 0xd6, 0x87, 0x6a, 0x43, 0x45, 0x3b, 0x6d, 0xb8, 0x36, 0x9c, 0xc7, 0xeb, 0xd4, 0x55, 0x22, 0xf7, 0x6b, 0xfb, 0x45, 0x84, 0x56, 0xa2, 0x95, 0x88, 0x60, 0xcd, 0xab, 0x79, 0xc9, 0xe9, 0x4a, 0xb5, 0x52, 0xa2, 0xdc, 0x1c, 0x27, 0x0e, 0x35, 0x42, 0x22, 0xd4, 0x08, 0x71, 0xa8, 0x51, 0x11, 0xa1, 0x46, 0x42, 0x84, 0x1a, 0x09, 0x0e, 0x35, 0x12, 0x82, 0xd4, 0x48, 0x48, 0x56, 0x23, 0x21, 0x42, 0x8d, 0x84, 0x64, 0x35, 0x12, 0x82, 0xd5, 0x18, 0x08, 0x52, 0x63, 0x20, 0x58, 0xc5, 0x7d, 0xb2, 0xed, 0x3e, 0xdb, 0xfd, 0xa2, 0x99, 0x6d, 0x92, 0x6d, 0x92, 0x68, 0x61, 0x9b, 0x6c, 0x83, 0x0d, 0xdb, + 0x54, 0xdb, 0x54, 0xc6, 0xc6, 0x43, 0xb6, 0x87, 0x78, 0x7d, 0x9a, 0x6d, 0x1a, 0xeb, 0x8f, 0xd9, 0x1e, 0x67, 0xbc, 0x3d, 0x61, 0x7b, 0x42, 0xb4, 0xb6, 0x3d, 0x63, 0x7b, 0x86, 0xf1, 0x39, 0xc3, 0x36, 0x43, 0xc4, 0xda, 0x66, 0xda, 0x66, 0xb2, 0xfd, 0xf3, 0xb6, 0xe7, 0x79, 0xfd, 0x45, 0xdb, 0x8b, 0x22, 0xde, 0xf6, 0x8a, 0xed, 0x15, 0x5e, 0x99, 0x6b, 0x9b, 0x6b, 0x5e, 0x3b, 0xc2, 0x36, 0x8f, 0x12, 0xe6, 0xdb, 0xe6, 0xf3, 0xee, 0x7b, 0xb6, 0x8f, 0x19, 0xab, 0x4b, 0x6c, 0x44, 0xbf, 0xed, 0x4b, 0xdb, 0x97, 0xc2, 0x16, 0x3c, 0x2e, 0x98, 0xbe, 0x0b, 0x9e, 0x1c, 0x3c, 0x59, 0x34, 0x0a, 0x7e, 0x2a, 0xf8, 0x29, 0xd1, 0x38, 0xf8, 0x99, 0xe0, 0x67, 0x78, 0x9c, 0x11, 0x3c, 0x43, 0x44, 0x06, 0x3f, 0x1b, 0xfc, 0xac, 0x68, 0x1f, 0x3c, 0x33, 0x78, 0x26, 0xeb, 0xcf, 0x05, 0x3f, 0x27, + 0xda, 0x87, 0x84, 0x87, 0x84, 0x8b, 0x46, 0x21, 0xdf, 0x85, 0xac, 0x31, 0xaf, 0x8a, 0x45, 0xfe, 0xa6, 0x85, 0x6c, 0x09, 0xc9, 0x61, 0xdd, 0x1c, 0xb1, 0x11, 0x21, 0x05, 0x21, 0x05, 0xa2, 0x45, 0x48, 0x49, 0x48, 0x89, 0x08, 0x0f, 0xf1, 0x85, 0xf8, 0x44, 0x58, 0x68, 0xeb, 0xd0, 0xd6, 0xa2, 0x41, 0xa8, 0x79, 0x45, 0x97, 0x66, 0xa1, 0xc9, 0xa1, 0xc9, 0xc2, 0x1e, 0x7a, 0x22, 0xf4, 0x84, 0x68, 0xa1, 0xe6, 0xef, 0x38, 0x35, 0x7f, 0xc7, 0x31, 0x7f, 0xdf, 0xc0, 0xa3, 0x39, 0xb6, 0x13, 0xd4, 0xa8, 0x8e, 0x50, 0xa3, 0x3a, 0x42, 0x8d, 0xd5, 0x08, 0x7b, 0x19, 0x63, 0x32, 0x46, 0x8d, 0xc9, 0x18, 0x35, 0xd2, 0x22, 0xd4, 0x48, 0x8b, 0x50, 0x23, 0x2d, 0x42, 0x8d, 0xae, 0xb6, 0x6a, 0x74, 0xb5, 0x55, 0xa3, 0xab, 0x89, 0x1a, 0x5d, 0x4d, 0xd4, 0xe8, 0x6a, 0xa2, 0x46, 0x57, 0xb0, 0x1a, + 0x5d, 0xa1, 0x01, 0xa3, 0xab, 0xa5, 0x1a, 0x5d, 0x41, 0x6a, 0x74, 0x05, 0xa9, 0xd1, 0x95, 0xac, 0x46, 0x57, 0xb2, 0xfe, 0x86, 0xfe, 0x9e, 0x68, 0xa5, 0x2f, 0xd0, 0x57, 0xf0, 0xf8, 0x95, 0x9e, 0xc9, 0xe3, 0xb7, 0xfa, 0x6a, 0x46, 0xe3, 0x1a, 0xfd, 0x07, 0xd1, 0x42, 0xdf, 0xa0, 0x6f, 0xe0, 0x71, 0xa3, 0x9e, 0xcd, 0x98, 0xdc, 0xaa, 0x6f, 0xe5, 0x71, 0x9b, 0xee, 0xe2, 0x95, 0x3c, 0x7d, 0x1f, 0x8f, 0x45, 0x3a, 0x11, 0xa2, 0x97, 0xe9, 0x07, 0x78, 0xf4, 0xe9, 0x3e, 0x66, 0x81, 0x43, 0xfa, 0x69, 0x1e, 0xa5, 0x11, 0x22, 0x62, 0x8d, 0x50, 0xc3, 0x10, 0x21, 0x46, 0x03, 0xa3, 0x11, 0x8f, 0xe6, 0x3d, 0xe6, 0x9a, 0x19, 0xe6, 0x95, 0xfd, 0x34, 0xc3, 0xbc, 0x4e, 0x98, 0x66, 0xb4, 0x24, 0xfb, 0xd5, 0x8c, 0x36, 0xe4, 0xbf, 0x9a, 0xba, 0xf3, 0xa5, 0x66, 0x74, 0x20, 0x0f, 0xd6, 0x8c, + 0x4e, 0x46, 0x27, 0x1e, 0xbb, 0xa8, 0xfb, 0xec, 0x98, 0x77, 0xbe, 0x6c, 0x66, 0xf4, 0x32, 0x06, 0x8a, 0x46, 0x46, 0x86, 0x31, 0x98, 0x6c, 0xd9, 0xbc, 0xff, 0x65, 0x03, 0x63, 0x8c, 0x31, 0x86, 0xc7, 0xcb, 0x8c, 0x6b, 0x44, 0x0b, 0xe3, 0x7a, 0xe3, 0x1e, 0x1e, 0xa7, 0x1a, 0x0f, 0xf3, 0x38, 0xcd, 0x78, 0x94, 0xd7, 0x9f, 0x30, 0x9e, 0xe5, 0xf1, 0x39, 0x32, 0xea, 0x06, 0xc6, 0xbf, 0x8c, 0x39, 0xe4, 0xf1, 0x2f, 0x1a, 0x73, 0x79, 0x7c, 0xdd, 0x58, 0x25, 0xec, 0xc6, 0x6a, 0x63, 0x3d, 0x59, 0xfd, 0x06, 0x23, 0x97, 0xc7, 0x1d, 0xc6, 0xcf, 0x3c, 0xba, 0x8c, 0x9d, 0x22, 0xca, 0xd8, 0x65, 0xe4, 0x8b, 0x68, 0xa3, 0xc0, 0xd8, 0x27, 0x12, 0x8d, 0x22, 0xa3, 0x44, 0xe8, 0x46, 0x99, 0x71, 0x98, 0xc7, 0xa3, 0xc6, 0x71, 0xd1, 0x5a, 0xfd, 0xe6, 0xa6, 0x81, 0x63, 0x8f, 0x63, 0x8f, 0x68, + 0xe7, 0x28, 0x73, 0x1c, 0x12, 0x11, 0x8e, 0xc3, 0x8e, 0xa3, 0x22, 0xd8, 0x71, 0xcc, 0x71, 0x5c, 0xb4, 0x70, 0x9c, 0x70, 0xfc, 0x26, 0xc2, 0x9c, 0xd9, 0x4e, 0x58, 0x12, 0xb6, 0x90, 0x85, 0x66, 0xd6, 0x6e, 0xbf, 0xd3, 0x71, 0xa3, 0x9a, 0x81, 0xcc, 0xfc, 0xf6, 0x04, 0x6e, 0xbd, 0x00, 0x87, 0xd5, 0x01, 0x67, 0x7c, 0x40, 0x2e, 0xc0, 0x63, 0xec, 0x96, 0x8f, 0xe0, 0x06, 0x0f, 0xe3, 0x16, 0xf3, 0xa4, 0xa3, 0x3a, 0xef, 0xfb, 0xc3, 0x0c, 0xfd, 0x30, 0x39, 0x1e, 0xce, 0x8a, 0x7c, 0xa4, 0x3b, 0xce, 0xb7, 0x10, 0xdf, 0x57, 0x8c, 0x1f, 0xbe, 0x97, 0x32, 0xbf, 0xc0, 0xc9, 0x78, 0xf0, 0xb2, 0x47, 0xd9, 0xc6, 0x8d, 0x0b, 0x2b, 0xfa, 0x93, 0x4c, 0x9f, 0xbc, 0x44, 0x8e, 0x32, 0xf3, 0x13, 0xf9, 0x99, 0xf2, 0xe7, 0xb3, 0x6b, 0xb3, 0xc3, 0x0e, 0x81, 0x19, 0x3b, 0xf5, 0xaa, 0x60, 0x29, + 0x3f, 0x5b, 0x5d, 0x6a, 0x1c, 0x11, 0xd9, 0x86, 0x5f, 0x95, 0x51, 0xa5, 0xfc, 0x13, 0xf9, 0x4b, 0x4d, 0x36, 0x49, 0x8b, 0x4f, 0x9d, 0xb3, 0x0e, 0x7e, 0x16, 0x33, 0xb3, 0xf3, 0xe0, 0xb5, 0x8b, 0xc9, 0xfe, 0x9e, 0xe5, 0xd1, 0x85, 0xa3, 0x2d, 0xc5, 0xb7, 0x1e, 0xe0, 0xd5, 0x16, 0xaa, 0xcc, 0x8a, 0xdf, 0x33, 0xa0, 0x3f, 0xfc, 0x4b, 0x56, 0x19, 0x52, 0xb8, 0x72, 0x68, 0xac, 0x33, 0xaf, 0xd7, 0xf1, 0xd5, 0x38, 0xd9, 0x02, 0xea, 0x65, 0x66, 0x4c, 0xfb, 0xf0, 0xf1, 0xbf, 0xe2, 0xab, 0x7f, 0x0a, 0x78, 0xbf, 0xda, 0xcd, 0x4d, 0x51, 0xcc, 0x6c, 0x32, 0x33, 0x31, 0xe5, 0x2d, 0xcd, 0x1c, 0xa3, 0x44, 0xa6, 0x07, 0xe4, 0x55, 0xe5, 0xd5, 0xdb, 0xaa, 0xe7, 0xdf, 0xce, 0x3c, 0xba, 0xc1, 0xe3, 0xe5, 0x8a, 0x8f, 0x25, 0x8a, 0xdf, 0x17, 0x55, 0x8e, 0xed, 0x63, 0x8f, 0xa9, 0xbf, 0x1f, + 0x03, 0xf8, 0xc3, 0x7e, 0x39, 0x14, 0x90, 0x7b, 0x1f, 0x0f, 0x38, 0x8a, 0x70, 0x94, 0x52, 0x4e, 0xfc, 0x39, 0xa7, 0xea, 0xfd, 0xea, 0x3c, 0xe1, 0xd5, 0xea, 0xbc, 0x55, 0x3d, 0x7e, 0x57, 0x7b, 0xc4, 0x66, 0x54, 0xc0, 0x76, 0x1b, 0x59, 0xb6, 0x49, 0xf3, 0x3b, 0xde, 0x9a, 0x57, 0xca, 0xac, 0xe3, 0x30, 0x55, 0xd4, 0x7a, 0x1f, 0x71, 0xd9, 0x89, 0xd6, 0xfb, 0x54, 0x9c, 0x7a, 0x54, 0x9c, 0x96, 0x5b, 0x71, 0xfa, 0xa7, 0xfd, 0x61, 0x66, 0xf5, 0xf4, 0x9c, 0x8b, 0x38, 0x6d, 0x4d, 0x6f, 0x16, 0x90, 0x49, 0xec, 0x63, 0x5f, 0xb7, 0x13, 0xb1, 0x2b, 0x88, 0xd3, 0x3c, 0xe9, 0xa4, 0xa4, 0x83, 0x38, 0xef, 0x7a, 0x71, 0xfa, 0xbb, 0xbf, 0xae, 0xf3, 0xea, 0x7c, 0x15, 0x89, 0x5e, 0x15, 0x5f, 0x6e, 0xe5, 0xf9, 0xd7, 0xd3, 0x37, 0x63, 0xac, 0x77, 0x6f, 0x67, 0x14, 0xe4, 0x11, 0x3b, 0x1e, + 0xb5, 0x94, 0x53, 0xe3, 0x85, 0xf2, 0xdf, 0x01, 0x8e, 0xfe, 0x90, 0xe9, 0xd2, 0x65, 0x3a, 0xfb, 0x2b, 0x24, 0x87, 0x39, 0x45, 0xc4, 0x3f, 0xc7, 0x56, 0xf9, 0x8c, 0x93, 0xdd, 0x32, 0xb1, 0x26, 0x4b, 0xaf, 0x3b, 0xf6, 0x6a, 0xb8, 0xae, 0x57, 0x8f, 0x39, 0x35, 0x47, 0x2d, 0xcc, 0xfc, 0xcc, 0x7a, 0xed, 0x98, 0x1c, 0xf9, 0x67, 0xf5, 0xaf, 0x13, 0x1f, 0x57, 0xa8, 0xbe, 0x5d, 0x2a, 0x7f, 0xe5, 0x79, 0xae, 0x8a, 0x37, 0x33, 0x3e, 0x3a, 0xfc, 0xd1, 0x7e, 0x03, 0x8f, 0x20, 0xa9, 0xc7, 0x97, 0x14, 0xc3, 0xbf, 0xa8, 0xf1, 0x41, 0xc6, 0x24, 0x3f, 0xa0, 0x27, 0x2b, 0x6a, 0xf9, 0xb8, 0x57, 0x2e, 0x53, 0x47, 0xed, 0xcc, 0x23, 0x30, 0x45, 0x6c, 0x57, 0x22, 0xbf, 0x94, 0xab, 0x68, 0x71, 0x9d, 0x23, 0x31, 0x66, 0x7c, 0x98, 0xc7, 0x6b, 0x14, 0xa7, 0x66, 0x19, 0x4b, 0x89, 0x84, 0x2a, + 0x79, 0xa9, 0xf5, 0xee, 0x33, 0x64, 0x3f, 0xd6, 0x51, 0x1e, 0x6a, 0x7b, 0x8c, 0xf8, 0xdb, 0x4e, 0x5f, 0x9e, 0x30, 0x6b, 0xad, 0xde, 0x2f, 0xa8, 0x2d, 0x83, 0xbc, 0x54, 0x1d, 0x99, 0xc8, 0xaa, 0x89, 0xa1, 0x3a, 0x31, 0x46, 0x36, 0xcf, 0x6c, 0xf4, 0x4b, 0x75, 0x1c, 0xfe, 0x3e, 0x6b, 0x04, 0xb4, 0xe5, 0x75, 0x35, 0x46, 0x8a, 0x54, 0x5b, 0xd8, 0x8a, 0xbe, 0xcb, 0x66, 0x3f, 0x57, 0x59, 0xdb, 0x4c, 0x90, 0x9f, 0x9a, 0x47, 0x9e, 0xcc, 0x28, 0xa1, 0x87, 0x0f, 0xd0, 0x9a, 0x15, 0xf2, 0x1b, 0x73, 0x4e, 0xa8, 0x39, 0x66, 0xa8, 0x1e, 0x1f, 0x52, 0x65, 0x64, 0xa9, 0x7a, 0x7c, 0x5e, 0x53, 0x43, 0xf3, 0x28, 0x59, 0xc0, 0x91, 0xc2, 0x43, 0xb4, 0xe3, 0x68, 0xbd, 0x79, 0xcc, 0x26, 0x86, 0xe1, 0xf5, 0x2e, 0x60, 0xad, 0xb7, 0x18, 0x8b, 0xff, 0xba, 0x06, 0x07, 0xd7, 0x1e, 0x07, 0x77, + 0x87, 0xe8, 0x81, 0x5f, 0x7b, 0x88, 0x77, 0xa6, 0xe1, 0xad, 0x2e, 0x52, 0x0e, 0x65, 0x2c, 0x0e, 0x65, 0xb2, 0xb8, 0x5a, 0xb9, 0x92, 0x6b, 0x95, 0x2b, 0xb9, 0x4e, 0xb9, 0x92, 0x71, 0xca, 0x95, 0x8c, 0xc7, 0x95, 0xcc, 0x10, 0x37, 0x28, 0x27, 0x72, 0x93, 0x72, 0x22, 0x37, 0xe3, 0x44, 0xe6, 0x8a, 0x5b, 0xf0, 0x20, 0xf3, 0xc5, 0x6d, 0xb8, 0x8f, 0x4f, 0xc4, 0x9d, 0xca, 0x77, 0x3c, 0xa0, 0x7c, 0xc7, 0x83, 0xca, 0x47, 0x4c, 0x55, 0x3e, 0xe2, 0x61, 0x7c, 0xc4, 0x16, 0xf1, 0x48, 0xc8, 0x76, 0xdc, 0xc4, 0x34, 0xe5, 0x23, 0x1e, 0x53, 0x3e, 0xe2, 0x71, 0xe5, 0x23, 0xa6, 0xe3, 0x23, 0x92, 0xc4, 0x13, 0xca, 0x41, 0x3c, 0xa5, 0x1c, 0xc4, 0xd3, 0xf6, 0x87, 0xec, 0x0f, 0x89, 0x67, 0xec, 0x27, 0xed, 0x27, 0xc5, 0x0c, 0x3d, 0x4c, 0x0f, 0x13, 0xcf, 0x2a, 0xf5, 0x9e, 0xa9, 0xd4, 0xfb, + 0x25, 0xa5, 0xde, 0x0b, 0x94, 0x7a, 0x7f, 0xa4, 0xd4, 0x7b, 0x09, 0xea, 0x9d, 0x2d, 0x3e, 0x43, 0xb7, 0x5d, 0xe2, 0x73, 0xa5, 0xd8, 0xdf, 0x2a, 0xc5, 0x5e, 0x8f, 0x62, 0xfb, 0xc4, 0x26, 0xb4, 0xfa, 0x37, 0xb1, 0x05, 0xad, 0x36, 0xc4, 0x1e, 0x54, 0x3a, 0x42, 0x78, 0x95, 0x3e, 0x1f, 0x51, 0xfa, 0x5c, 0xa1, 0xf4, 0xf9, 0x98, 0xd2, 0xe7, 0x93, 0x4a, 0x9f, 0x7f, 0x53, 0xfa, 0x7c, 0xda, 0xd4, 0x67, 0x4d, 0x98, 0xfa, 0xac, 0x29, 0x7d, 0xd6, 0x6c, 0xc6, 0x40, 0x63, 0xa0, 0x66, 0x37, 0xf5, 0x59, 0xd3, 0x4d, 0x7d, 0xd6, 0x9c, 0xe8, 0xf3, 0x35, 0x5a, 0xb8, 0xa9, 0xcc, 0x5a, 0xac, 0xa9, 0xcc, 0xda, 0x79, 0xa6, 0x32, 0x6b, 0x9d, 0x4d, 0x65, 0xd6, 0xba, 0xa2, 0xcc, 0x73, 0xb4, 0x0b, 0x4c, 0x4d, 0xd6, 0xfa, 0xa1, 0xc9, 0xeb, 0xb5, 0x0c, 0x53, 0x8d, 0xb5, 0x49, 0xa8, 0xf1, 0x4e, + 0xed, 0x71, 0x53, 0x87, 0xb5, 0xa7, 0x4c, 0x1d, 0xd6, 0x9e, 0x37, 0x75, 0x58, 0x9b, 0x65, 0xea, 0xb0, 0xf6, 0xa2, 0xa9, 0xc3, 0xda, 0x6b, 0xa6, 0x0e, 0x6b, 0x6f, 0xa1, 0xb4, 0x9b, 0xb5, 0xb7, 0xe9, 0xc5, 0xc7, 0xf0, 0xeb, 0x9d, 0x50, 0x5b, 0xd3, 0xb1, 0x6b, 0x38, 0xf6, 0xae, 0xbc, 0xd6, 0x8d, 0xc5, 0x21, 0xba, 0xb3, 0x18, 0xf4, 0xa6, 0x79, 0x4d, 0x33, 0xd3, 0xc3, 0x07, 0x29, 0x0f, 0x1f, 0x8c, 0x87, 0xef, 0x83, 0x6f, 0xef, 0x2b, 0xfa, 0xb1, 0x7d, 0x7f, 0x96, 0x10, 0x31, 0x40, 0x98, 0xf7, 0x94, 0x1a, 0x24, 0xae, 0xc0, 0x51, 0x5f, 0x49, 0x34, 0x18, 0x44, 0xc3, 0xb5, 0x7c, 0xca, 0xf4, 0xf9, 0xa1, 0xca, 0xe7, 0xdb, 0x95, 0xcf, 0x77, 0x28, 0x9f, 0xaf, 0x2b, 0x9f, 0xaf, 0xe3, 0xf2, 0xef, 0x60, 0x4b, 0xd3, 0xdb, 0x3b, 0xf0, 0xf6, 0xf7, 0xe1, 0xd8, 0xef, 0x67, 0x09, 0xc6, + 0xdf, 0x4f, 0xa2, 0x64, 0xd3, 0xdb, 0x37, 0x52, 0xde, 0x3e, 0x0c, 0x6f, 0xff, 0x20, 0x11, 0x36, 0x95, 0xa8, 0x6a, 0x48, 0x54, 0x3d, 0xc6, 0xe3, 0xe3, 0x2c, 0x21, 0x62, 0xba, 0x78, 0x82, 0x72, 0x9e, 0x64, 0x69, 0x28, 0x9e, 0x62, 0x09, 0x11, 0x4f, 0xe3, 0xf9, 0x75, 0xe5, 0xf9, 0x1b, 0x29, 0xcf, 0xdf, 0x58, 0x79, 0xfe, 0x26, 0xca, 0xf3, 0x37, 0x55, 0x9e, 0xbf, 0x99, 0xf2, 0xfc, 0xe1, 0x2a, 0x2e, 0x35, 0xe5, 0x9c, 0x6d, 0xca, 0x39, 0x07, 0xa9, 0xe8, 0x34, 0x54, 0x74, 0x6a, 0x2a, 0x3a, 0x1b, 0xaa, 0xe8, 0x0c, 0x57, 0x9e, 0xd9, 0xa9, 0x3c, 0x73, 0xa8, 0x8a, 0xd1, 0x70, 0x15, 0xa3, 0x0d, 0x94, 0x5b, 0x6e, 0xa4, 0xdc, 0x72, 0x33, 0x22, 0x55, 0xdd, 0x11, 0x0c, 0xcf, 0x6c, 0x28, 0xcf, 0x1c, 0xae, 0xa2, 0x56, 0x57, 0x51, 0xeb, 0x54, 0x51, 0x6b, 0x57, 0x51, 0xab, 0xab, 0xa8, + 0xd5, 0x55, 0xd4, 0xea, 0x2a, 0x6a, 0x1b, 0xaa, 0xa8, 0x75, 0xaa, 0xa8, 0x0d, 0x52, 0x51, 0xdb, 0x48, 0xb9, 0xdf, 0x30, 0xe5, 0x7e, 0x1d, 0x2a, 0x76, 0x1b, 0xaa, 0xd8, 0x75, 0xaa, 0xd8, 0x0d, 0x57, 0xb1, 0xab, 0xab, 0xd8, 0xd5, 0xf5, 0x6f, 0x88, 0xd7, 0x60, 0x15, 0xaf, 0x9a, 0x8a, 0x57, 0x9b, 0x72, 0x9b, 0x36, 0xe5, 0x36, 0x83, 0x02, 0xdc, 0x66, 0xb0, 0x8a, 0xdd, 0x60, 0x15, 0xbb, 0xc1, 0xca, 0x6d, 0x06, 0x2b, 0xb7, 0x19, 0x42, 0x04, 0x9f, 0xa0, 0x9c, 0x2a, 0xbd, 0x8a, 0xf5, 0x93, 0x44, 0xb3, 0xae, 0x9c, 0x67, 0xa8, 0x72, 0x9e, 0x76, 0xe5, 0x3c, 0xed, 0xca, 0x79, 0x3a, 0x54, 0x64, 0xeb, 0x2a, 0xb2, 0x75, 0x15, 0xd9, 0xba, 0x8a, 0x6c, 0x5d, 0x45, 0xb6, 0xae, 0x22, 0x5b, 0x57, 0xce, 0x53, 0x57, 0xce, 0x53, 0x57, 0xce, 0xd3, 0x30, 0x23, 0x9b, 0x75, 0xd3, 0x79, 0x3a, + 0x94, 0xf3, 0x74, 0x28, 0xe7, 0xa9, 0x2b, 0xe7, 0xe9, 0x54, 0xce, 0xd3, 0xa9, 0x9c, 0xa7, 0x53, 0x39, 0x4f, 0x4d, 0x39, 0xcf, 0x30, 0xe5, 0x3c, 0xc3, 0x94, 0xf3, 0x6c, 0xa0, 0x9c, 0x67, 0x03, 0xa2, 0xfc, 0x6d, 0xd1, 0xd0, 0x78, 0x07, 0xff, 0xa9, 0x2b, 0xff, 0xd9, 0x48, 0xf9, 0xcf, 0x46, 0xca, 0x7f, 0x36, 0x52, 0xfe, 0xb3, 0xb1, 0xf2, 0x9f, 0x4d, 0x94, 0xff, 0x6c, 0xaa, 0xfc, 0x67, 0x33, 0xe5, 0x3f, 0x9b, 0x29, 0xff, 0x19, 0xae, 0xfc, 0xa7, 0xc3, 0x8c, 0x7b, 0xd3, 0x61, 0x8a, 0xa5, 0x2a, 0x07, 0x6e, 0xae, 0x8e, 0xa6, 0xe8, 0x2a, 0x07, 0x0e, 0x22, 0x1a, 0x1b, 0xf0, 0x6c, 0x66, 0xc2, 0x41, 0xea, 0x08, 0x8a, 0xae, 0xf2, 0xe1, 0x08, 0xb6, 0xa7, 0xcd, 0x2a, 0x2b, 0x36, 0x8f, 0x55, 0xc5, 0xb3, 0x6e, 0xe6, 0xc6, 0x41, 0xea, 0x78, 0x89, 0xae, 0x32, 0x64, 0x47, 0x40, 0x86, + 0x5c, 0x7d, 0xbc, 0x24, 0x4c, 0x65, 0xc8, 0x86, 0xca, 0x90, 0x1b, 0xaa, 0x0c, 0x39, 0x54, 0x65, 0xc8, 0x8d, 0x54, 0x86, 0xdc, 0x52, 0xe5, 0xc6, 0x91, 0x6a, 0x5c, 0x45, 0xab, 0x71, 0x15, 0xa9, 0x72, 0x63, 0x2d, 0x20, 0x37, 0x8e, 0x51, 0x19, 0x6f, 0xa4, 0x1a, 0x09, 0xd1, 0x2a, 0xbf, 0x4d, 0x54, 0x99, 0xad, 0xa6, 0x32, 0xdb, 0xa6, 0x2a, 0xbe, 0x1b, 0xa8, 0xf8, 0xb6, 0xa9, 0xf8, 0x0e, 0x57, 0xf1, 0x6d, 0x57, 0xf1, 0xdd, 0x4c, 0xe5, 0xb4, 0xf1, 0x2a, 0xa7, 0x75, 0xaa, 0x9c, 0x36, 0x58, 0xe5, 0xb4, 0x4e, 0x95, 0xd3, 0x06, 0xab, 0x9c, 0x36, 0x4e, 0xe5, 0xb4, 0x51, 0x2a, 0x9b, 0x8d, 0x52, 0x79, 0x6c, 0x0b, 0x95, 0xc7, 0xb6, 0x50, 0x79, 0x6c, 0x88, 0xca, 0x63, 0x83, 0x54, 0x1e, 0xdb, 0x58, 0xe5, 0xb1, 0x91, 0x2a, 0x77, 0x0d, 0x52, 0x47, 0x74, 0x74, 0x95, 0xbb, 0x06, 0xa9, + 0x23, 0x3a, 0xba, 0x3a, 0xa2, 0xa3, 0xab, 0x3c, 0x36, 0x48, 0x65, 0xb0, 0xa1, 0x2a, 0x83, 0x6d, 0xa4, 0x32, 0xd8, 0x20, 0xdb, 0x0d, 0xb6, 0x1b, 0x78, 0xd7, 0xcc, 0x63, 0x1b, 0x05, 0x1c, 0xd1, 0xd1, 0x55, 0x36, 0x1b, 0x5a, 0x7b, 0x44, 0xe7, 0x2e, 0x5e, 0x31, 0x73, 0xda, 0x46, 0x6a, 0x4c, 0x46, 0xaa, 0x31, 0x19, 0xad, 0xc6, 0x61, 0x53, 0x35, 0x0e, 0x9b, 0xa9, 0x71, 0x98, 0x10, 0x90, 0xbb, 0x46, 0xab, 0xd1, 0xd8, 0x4c, 0x8d, 0xc6, 0x44, 0x35, 0x1a, 0xa3, 0xd5, 0x68, 0xb4, 0xab, 0x11, 0xd8, 0x4c, 0x65, 0xad, 0x9a, 0xca, 0x5a, 0x9b, 0xaa, 0x71, 0x98, 0xa0, 0xc6, 0x61, 0x8c, 0x3a, 0x56, 0xa4, 0xab, 0xd1, 0xa8, 0x85, 0xac, 0x0f, 0x59, 0xaf, 0x46, 0x63, 0x0e, 0xeb, 0x66, 0x2e, 0x1a, 0xa4, 0x8e, 0x1e, 0xe9, 0x6a, 0x4c, 0x46, 0xaa, 0x63, 0x48, 0xba, 0x1a, 0x99, 0xd1, 0x21, + 0x07, 0x42, 0x0e, 0x08, 0x5d, 0x1d, 0x4f, 0xd2, 0xd5, 0x98, 0x6c, 0xaa, 0xc6, 0x64, 0xa4, 0xca, 0x48, 0x9b, 0xab, 0x8c, 0xb4, 0xb9, 0xca, 0x48, 0x9b, 0xab, 0xe3, 0x4c, 0xba, 0xca, 0x48, 0x83, 0x54, 0x46, 0x1a, 0x64, 0x9f, 0x64, 0x9f, 0xca, 0x2b, 0xe6, 0xd1, 0x45, 0x5d, 0x1d, 0x5b, 0xd2, 0xd5, 0xb1, 0x25, 0x5d, 0x1d, 0x5b, 0xd2, 0x55, 0xbe, 0x1a, 0xa4, 0x8e, 0x2d, 0xe9, 0xea, 0xd8, 0x92, 0xae, 0x8e, 0x2d, 0xe9, 0xea, 0xd8, 0x92, 0x6e, 0x2f, 0xb5, 0x97, 0xf2, 0x68, 0x66, 0xb3, 0x11, 0x2a, 0x9b, 0x8d, 0x50, 0x47, 0x98, 0x74, 0x75, 0xd4, 0xb1, 0xfa, 0xa8, 0x92, 0xae, 0x8e, 0x3a, 0x56, 0x1f, 0x4f, 0x0a, 0x52, 0x59, 0x6e, 0x90, 0xca, 0x72, 0x83, 0xd4, 0xf1, 0x24, 0x5d, 0x1d, 0x4f, 0xd2, 0x55, 0xc6, 0xeb, 0x50, 0x19, 0xaf, 0xe3, 0x1c, 0x19, 0x6f, 0xf5, 0xf1, 0xa4, 0x30, + 0x95, 0xf1, 0x1a, 0x2a, 0xe3, 0x6d, 0xa8, 0x32, 0xde, 0x50, 0x95, 0xf1, 0x86, 0xaa, 0x8c, 0xb7, 0x91, 0xca, 0x78, 0x1b, 0x29, 0xcd, 0x6c, 0xa9, 0x34, 0xb3, 0xa5, 0xd2, 0xcc, 0x96, 0x6a, 0xf6, 0x89, 0x54, 0xb3, 0x4f, 0xa4, 0x9a, 0x7d, 0xa2, 0xd5, 0xec, 0x13, 0xad, 0x66, 0x9f, 0x48, 0x35, 0xfb, 0x44, 0xaa, 0xd9, 0x27, 0x52, 0xcd, 0x3e, 0x91, 0x6a, 0xf6, 0xd1, 0x54, 0xae, 0xab, 0x05, 0xe4, 0xba, 0x31, 0x6a, 0xc6, 0x89, 0x51, 0xb3, 0x43, 0xa4, 0x9a, 0x1d, 0x22, 0xd5, 0xec, 0x10, 0xa9, 0xe6, 0x82, 0x44, 0x35, 0x17, 0x24, 0xaa, 0x2c, 0xb4, 0xa9, 0x1a, 0xf3, 0x0d, 0xd4, 0x98, 0xb7, 0xa9, 0x31, 0x1f, 0xae, 0xc6, 0xbc, 0x5d, 0x8d, 0x79, 0xbb, 0x1a, 0xf3, 0xcd, 0x54, 0xb6, 0xd9, 0x42, 0x65, 0x9b, 0x41, 0x2a, 0xdb, 0x6c, 0xac, 0xb2, 0xcd, 0x48, 0x75, 0x3c, 0x4c, 0x57, 0xc7, + 0xc3, 0x74, 0x95, 0x73, 0x46, 0x53, 0x86, 0xfa, 0xa6, 0xd8, 0x29, 0x1a, 0x5c, 0x21, 0xa2, 0xd0, 0x3f, 0xd3, 0xe1, 0xed, 0xa9, 0xfe, 0x3e, 0xe9, 0x7f, 0xf9, 0x57, 0x93, 0xa7, 0xe0, 0xcb, 0x7c, 0x81, 0xdf, 0x38, 0xfd, 0x97, 0xf7, 0xb2, 0xef, 0xf7, 0x1c, 0xe5, 0x7f, 0xd8, 0x16, 0x37, 0x59, 0xc1, 0x7e, 0x33, 0x87, 0xc2, 0x85, 0x56, 0xfd, 0x99, 0x1f, 0xff, 0xc7, 0x7b, 0x39, 0x5a, 0xef, 0x5b, 0xe2, 0x93, 0xff, 0xa3, 0xfd, 0x74, 0x91, 0x37, 0xd7, 0xf9, 0x3f, 0x4a, 0xf6, 0x25, 0x17, 0xc8, 0x97, 0xf7, 0xcb, 0x51, 0xa7, 0x17, 0xcb, 0xb7, 0x4f, 0x1f, 0xa2, 0x95, 0x7d, 0xe5, 0x30, 0x39, 0xdc, 0x3c, 0x67, 0xe2, 0xf4, 0xa3, 0xa7, 0x77, 0x49, 0xbb, 0xec, 0x2a, 0x3b, 0xcb, 0x7e, 0xf2, 0x4e, 0x32, 0xbe, 0xae, 0xb2, 0x97, 0x4c, 0x92, 0x03, 0xa4, 0x26, 0x83, 0x65, 0xda, 0x39, 0xf7, + 0xb1, 0x93, 0xcc, 0x61, 0x4f, 0x9d, 0xef, 0xf3, 0xcc, 0xef, 0x2f, 0x77, 0xe3, 0xe9, 0x37, 0x99, 0x79, 0x01, 0x51, 0xe1, 0xc6, 0x7f, 0xbf, 0xcf, 0x1b, 0x3a, 0xb1, 0xe8, 0xab, 0x66, 0x56, 0x7d, 0x03, 0xbb, 0xc0, 0xfa, 0x86, 0xff, 0x6d, 0xeb, 0x3b, 0x9e, 0xd7, 0xc9, 0x64, 0x32, 0x65, 0x2e, 0x19, 0xc5, 0x4f, 0x94, 0x98, 0x55, 0xf3, 0x8d, 0x6d, 0xcd, 0x37, 0x44, 0x72, 0xdb, 0x19, 0x67, 0x13, 0xac, 0x61, 0xeb, 0x6f, 0xac, 0x6c, 0xfd, 0x47, 0x4a, 0x98, 0x57, 0x9b, 0x65, 0xe8, 0xea, 0x78, 0x8b, 0x9f, 0xbc, 0xff, 0x4d, 0xb9, 0x15, 0xc5, 0x32, 0x73, 0x9a, 0x47, 0x55, 0x26, 0xfd, 0xa0, 0x7c, 0xd3, 0xda, 0x6a, 0xdd, 0x5f, 0x60, 0xf0, 0x40, 0xdd, 0xa3, 0x04, 0xe6, 0xf7, 0xa7, 0x2a, 0xfb, 0xaf, 0xfe, 0xbe, 0xd8, 0x45, 0x8b, 0x3c, 0xd5, 0xdf, 0x7d, 0x5b, 0x67, 0x4b, 0x98, 0x99, + 0x46, 0x91, 0xdc, 0xcb, 0x9a, 0x39, 0xf6, 0x8e, 0x5b, 0x47, 0x6d, 0x0e, 0xa9, 0x1c, 0xa4, 0xfc, 0x9c, 0x7b, 0x09, 0xf8, 0x26, 0x5b, 0x7d, 0x8b, 0x58, 0x44, 0xb9, 0x65, 0x01, 0x47, 0x61, 0x8a, 0xd4, 0xb9, 0x07, 0x45, 0x56, 0x86, 0x7e, 0x44, 0xe5, 0x8c, 0x55, 0x64, 0xc4, 0xf9, 0x67, 0xfb, 0x7e, 0x3d, 0x80, 0xb1, 0xad, 0x75, 0xcf, 0xb0, 0x30, 0x8f, 0x05, 0x04, 0x30, 0x66, 0x66, 0x5a, 0x6f, 0xfc, 0x9e, 0x97, 0xa9, 0x63, 0x0e, 0x15, 0xf2, 0x63, 0xc5, 0x58, 0x98, 0x62, 0xec, 0x61, 0x95, 0x1b, 0x3f, 0x50, 0xfd, 0xfd, 0x21, 0xeb, 0x6b, 0xcf, 0xb2, 0x97, 0x19, 0x66, 0xb6, 0x1d, 0xf0, 0xff, 0x83, 0xf2, 0x6a, 0xb2, 0xbe, 0x9d, 0xf2, 0x6b, 0x72, 0xb7, 0x21, 0x8c, 0xa5, 0x1b, 0xd5, 0x99, 0x18, 0xb7, 0xca, 0x27, 0x45, 0x84, 0x2c, 0x3a, 0x7d, 0x50, 0x8e, 0x20, 0xab, 0x7b, 0x5c, + 0xbe, 0xc0, 0x6b, 0x59, 0xf2, 0x69, 0xf9, 0x3e, 0xb5, 0x9c, 0x2f, 0x3f, 0x27, 0x6b, 0xbd, 0x5f, 0x66, 0x5a, 0x25, 0x14, 0xd6, 0x39, 0x4a, 0xe4, 0x3b, 0x6b, 0xdb, 0x6e, 0x0a, 0xc8, 0xc1, 0x07, 0x59, 0xcf, 0xaf, 0x81, 0xc7, 0xcd, 0xb5, 0xd3, 0xf9, 0xf2, 0x3c, 0x79, 0x49, 0xed, 0xb6, 0xff, 0x92, 0x4f, 0xc8, 0x17, 0xe5, 0xc3, 0xf2, 0x65, 0x39, 0xfa, 0x5c, 0xe3, 0x51, 0x1d, 0xaf, 0x79, 0x44, 0xe5, 0xf3, 0x53, 0xeb, 0x9c, 0xb1, 0x51, 0x51, 0x3f, 0x03, 0x97, 0x5f, 0xab, 0xed, 0x4f, 0x05, 0x44, 0xe1, 0x09, 0x33, 0xbf, 0xe6, 0xd9, 0x43, 0xab, 0xcc, 0xa3, 0x73, 0x07, 0xe1, 0x6c, 0xbd, 0xbc, 0x56, 0x5e, 0x26, 0xef, 0x22, 0x12, 0x5e, 0xe3, 0xd5, 0x5d, 0x7f, 0x74, 0xcc, 0x44, 0x8d, 0x09, 0xf3, 0x1b, 0x77, 0xb3, 0x8f, 0xf7, 0xaa, 0xfe, 0x3e, 0x55, 0xe7, 0xe8, 0xd4, 0xa9, 0xba, + 0x67, 0x11, 0xd0, 0x6f, 0xdf, 0x9e, 0x79, 0x66, 0x41, 0xbd, 0x32, 0xab, 0x8f, 0x68, 0x15, 0xfd, 0x49, 0x74, 0x9f, 0xe5, 0x7d, 0xeb, 0xbc, 0x9f, 0x4a, 0x15, 0xed, 0x07, 0x02, 0xce, 0xc3, 0xc9, 0x3f, 0x6b, 0x09, 0x5b, 0xaa, 0xbf, 0xa7, 0x0e, 0x78, 0xe5, 0xa5, 0xda, 0xb5, 0x1d, 0xb5, 0x6b, 0x4f, 0x5b, 0xcf, 0x33, 0xcf, 0x55, 0xeb, 0x9a, 0x23, 0x0b, 0x67, 0x7d, 0xcf, 0x23, 0x4b, 0x64, 0x59, 0x60, 0x54, 0x9c, 0x79, 0x1c, 0xf2, 0xf7, 0xef, 0x8e, 0x03, 0xde, 0xd9, 0x5b, 0x53, 0x37, 0xe6, 0x93, 0x4d, 0x35, 0xdf, 0xf6, 0xfe, 0x7e, 0x8c, 0xec, 0xcc, 0xb3, 0x47, 0xce, 0xde, 0xc6, 0x9a, 0xf3, 0x15, 0xce, 0x5e, 0xf7, 0x33, 0x8f, 0xf9, 0x9c, 0x3d, 0x62, 0xff, 0xd2, 0x5e, 0x76, 0x9d, 0x73, 0x2f, 0x27, 0xfe, 0xe6, 0x5e, 0xb6, 0xd5, 0x9c, 0x7f, 0x50, 0xfb, 0xca, 0xa7, 0xb5, 0x6b, + 0xb5, 0xe7, 0x1d, 0xc8, 0x77, 0xad, 0xe7, 0xb7, 0x6a, 0xce, 0x6b, 0x3a, 0x97, 0xb6, 0xff, 0x03, 0xed, 0xc9, 0x3d, 0xe3, 0x95, 0x97, 0x98, 0xb5, 0xce, 0x76, 0x24, 0xf9, 0xf8, 0x1f, 0x96, 0x73, 0xb7, 0x3a, 0xaa, 0x74, 0xf7, 0x9f, 0xec, 0x6d, 0xe1, 0x99, 0xfd, 0x62, 0x9e, 0x67, 0xa5, 0x22, 0xf9, 0x41, 0x6b, 0xfe, 0xcc, 0x91, 0x9f, 0xa9, 0x57, 0xee, 0xfc, 0xbb, 0xed, 0xac, 0x3d, 0xab, 0xe3, 0x1f, 0xb3, 0x61, 0x1d, 0x6f, 0xea, 0x25, 0x72, 0x84, 0x79, 0x4e, 0x81, 0x8b, 0x45, 0x13, 0x6e, 0x16, 0x9b, 0xd8, 0xcd, 0x12, 0x24, 0xf6, 0xb0, 0x04, 0x8b, 0x03, 0x2c, 0x21, 0xe2, 0x88, 0xa8, 0x22, 0xd3, 0x3a, 0x25, 0x4e, 0x93, 0xb5, 0x08, 0xcd, 0x4e, 0x26, 0x64, 0x68, 0xf8, 0x4a, 0xcd, 0xa9, 0x39, 0x45, 0x4b, 0xad, 0xa1, 0xd6, 0x50, 0xc4, 0x6a, 0x4d, 0xb4, 0xe6, 0x64, 0x3f, 0x51, + 0x5a, 0x94, 0x48, 0xd2, 0x5a, 0x68, 0x2d, 0x44, 0x1b, 0x2d, 0x56, 0x8b, 0x15, 0xc9, 0x5a, 0x82, 0x96, 0x28, 0x52, 0xfe, 0x3f, 0xf6, 0xde, 0x06, 0xce, 0xa6, 0x6a, 0xfd, 0x03, 0x5f, 0x6b, 0xef, 0x7d, 0xce, 0x7e, 0x3b, 0xef, 0xef, 0xe7, 0x0c, 0x63, 0x9a, 0x19, 0x63, 0x30, 0xc6, 0x18, 0x43, 0xf2, 0x32, 0x5e, 0x43, 0xd2, 0x24, 0x4d, 0x92, 0x24, 0x57, 0x92, 0x24, 0x69, 0x92, 0x24, 0x49, 0x92, 0x24, 0xc9, 0x75, 0x25, 0x49, 0x92, 0x24, 0x57, 0x92, 0x5c, 0x57, 0x48, 0x92, 0x24, 0x49, 0xd2, 0x24, 0x49, 0x92, 0x5c, 0x49, 0xd2, 0x24, 0x49, 0x92, 0x9c, 0xff, 0xb3, 0xbe, 0xfb, 0xcc, 0x38, 0x33, 0xa8, 0xe9, 0x76, 0xbb, 0xf7, 0xff, 0xfb, 0x7f, 0xfe, 0xf3, 0x7c, 0xf6, 0xb3, 0xd7, 0x79, 0xf6, 0xda, 0x6b, 0xaf, 0xbd, 0xd6, 0xb3, 0xd6, 0x5e, 0xcf, 0x77, 0x3f, 0xcf, 0x1e, 0x9e, 0xc1, + 0x33, 0x58, 0x5d, 0x5e, 0x87, 0x67, 0xb3, 0x7a, 0x9c, 0x88, 0xe5, 0x70, 0x22, 0xd6, 0x80, 0xe7, 0xf1, 0x3c, 0x96, 0x2b, 0x2f, 0x97, 0x57, 0xb0, 0x86, 0xf2, 0x2a, 0xf9, 0x15, 0x96, 0x2f, 0xbf, 0x2a, 0xaf, 0x65, 0x05, 0xf2, 0x06, 0x79, 0x23, 0x6b, 0x26, 0xbf, 0x2b, 0xbf, 0xcf, 0x5a, 0xc8, 0x1f, 0xca, 0xdb, 0x59, 0x1b, 0xf9, 0x63, 0xf9, 0x63, 0xd6, 0x4e, 0xde, 0x27, 0x7f, 0xc1, 0xda, 0xcb, 0x5f, 0xca, 0x07, 0xd8, 0x85, 0xf2, 0x41, 0xf9, 0x1b, 0xd6, 0x49, 0x3e, 0x2c, 0x1f, 0x66, 0x17, 0xcb, 0x47, 0xe5, 0x1f, 0x58, 0x57, 0xf9, 0x67, 0xf9, 0x67, 0x56, 0x24, 0xff, 0x22, 0xff, 0xc2, 0x2e, 0x95, 0xe3, 0x72, 0x9c, 0x75, 0x53, 0x64, 0x45, 0x61, 0x97, 0x29, 0x6d, 0x95, 0xb6, 0xec, 0x72, 0xa5, 0xab, 0xd2, 0x95, 0x15, 0xdb, 0x76, 0xdb, 0x76, 0xb3, 0x2b, 0x6c, 0xdf, 0xd8, 0xbe, + 0x61, 0x3d, 0x6c, 0x3f, 0xd8, 0x7e, 0x60, 0x57, 0xaa, 0x8b, 0xd5, 0xc5, 0xac, 0xa7, 0x5a, 0xaa, 0x7e, 0xc0, 0xae, 0x52, 0xbf, 0x54, 0x0f, 0xb0, 0xab, 0xd5, 0x5f, 0xd4, 0x5f, 0xd8, 0x35, 0x46, 0x43, 0xa3, 0x3b, 0xeb, 0x63, 0x14, 0x1b, 0x83, 0xd9, 0x58, 0xe3, 0x16, 0x63, 0x34, 0x7b, 0xc2, 0x78, 0xcc, 0x58, 0xcc, 0x9e, 0x37, 0x96, 0x18, 0x2b, 0xd8, 0x1b, 0xc6, 0x2a, 0xe3, 0x35, 0xb6, 0xd9, 0x78, 0xdd, 0x78, 0x9d, 0x95, 0x1a, 0x6f, 0x18, 0x6f, 0xb2, 0x0f, 0x8c, 0x8d, 0xc6, 0x46, 0xb6, 0xdd, 0xd8, 0x62, 0x6c, 0x61, 0x1f, 0x89, 0xef, 0xaa, 0xa1, 0x2d, 0xaf, 0x3b, 0x47, 0xcb, 0xca, 0x68, 0x59, 0x85, 0x5a, 0xf6, 0x08, 0xb5, 0xef, 0xf7, 0x44, 0x76, 0x76, 0x94, 0x48, 0x65, 0x3f, 0x10, 0x69, 0xec, 0x18, 0x91, 0xce, 0x7e, 0x24, 0x32, 0xd8, 0x71, 0x22, 0x9d, 0xfd, 0x44, 0x7d, + 0xe0, 0xa4, 0x3e, 0x38, 0x49, 0xe9, 0x5f, 0x88, 0x4c, 0xea, 0x8d, 0x53, 0xcc, 0x41, 0xfd, 0xc1, 0x98, 0x9d, 0x73, 0x4e, 0x96, 0x36, 0x57, 0xb8, 0x42, 0x69, 0x1b, 0xb7, 0x31, 0x27, 0xb7, 0x73, 0x3b, 0x49, 0x54, 0xea, 0x2d, 0x27, 0x7a, 0xcb, 0x85, 0xde, 0xd2, 0xd1, 0x5b, 0x4e, 0xea, 0x2d, 0x2f, 0x73, 0x73, 0x1f, 0x17, 0xff, 0x1d, 0x34, 0xc0, 0x03, 0xcc, 0xcb, 0x83, 0x3c, 0xc8, 0x7c, 0x3c, 0x44, 0xbd, 0xe8, 0x46, 0x2f, 0xfa, 0xd1, 0x8b, 0x21, 0xf4, 0x62, 0x88, 0x7a, 0x31, 0x8d, 0x05, 0xf8, 0x79, 0xd4, 0x97, 0x41, 0xf4, 0xa5, 0x97, 0xfa, 0xb2, 0x0e, 0xf1, 0x6c, 0xea, 0xd1, 0x10, 0x7a, 0x34, 0x84, 0x1e, 0x0d, 0xa3, 0x47, 0x03, 0xd4, 0xa3, 0xcb, 0x99, 0x2e, 0xbf, 0x2c, 0xbf, 0xcc, 0x9c, 0xf2, 0x0a, 0xea, 0x5d, 0x1b, 0xf5, 0xee, 0x2a, 0xa6, 0xca, 0xaf, 0x50, 0x1f, 0x1b, + 0xd4, 0xc7, 0x6b, 0x88, 0xbf, 0x26, 0xbf, 0xc6, 0xec, 0xf2, 0x5a, 0xea, 0x6f, 0x1b, 0xf5, 0xf7, 0x5b, 0x94, 0xde, 0x48, 0xbd, 0xae, 0x53, 0xaf, 0xbf, 0x4b, 0xe9, 0x2d, 0xf2, 0x16, 0x4a, 0xbf, 0x27, 0xbf, 0x47, 0xe9, 0xad, 0xf2, 0x56, 0x4a, 0xbf, 0x4f, 0xda, 0x60, 0x87, 0x36, 0xd8, 0xa0, 0x0d, 0x3a, 0x69, 0xc3, 0x3e, 0x16, 0x92, 0xbf, 0x20, 0x9d, 0xf0, 0x90, 0x4e, 0x7c, 0xc9, 0x7c, 0xf2, 0x01, 0xd2, 0x0c, 0x3f, 0x69, 0xc6, 0xd7, 0xc4, 0x0f, 0xc9, 0x87, 0x98, 0x57, 0xfe, 0x86, 0xb4, 0x24, 0x00, 0x2d, 0x09, 0x40, 0x4b, 0xbc, 0xd0, 0x12, 0x2f, 0xb4, 0xc4, 0x0b, 0x2d, 0xf1, 0x42, 0x4b, 0x3c, 0xd0, 0x12, 0x17, 0xb4, 0x24, 0x0c, 0x2d, 0x91, 0xa1, 0x25, 0x6e, 0x68, 0x89, 0x06, 0x2d, 0xd1, 0xa1, 0x25, 0x3a, 0xb4, 0x44, 0x87, 0x96, 0xe8, 0xc6, 0x45, 0xc6, 0x65, 0x8c, 0x1b, + 0xdd, 0x49, 0x57, 0x24, 0xd2, 0x95, 0x41, 0xc4, 0x6f, 0x22, 0x8d, 0x91, 0xa1, 0x31, 0x32, 0x34, 0x46, 0x21, 0x8d, 0x59, 0xc2, 0x6c, 0xc6, 0x4b, 0xc6, 0x52, 0xa6, 0x1a, 0xff, 0x30, 0xc8, 0x1a, 0x35, 0x96, 0x19, 0x2f, 0xd3, 0xb9, 0x2b, 0x48, 0x93, 0x0c, 0xd2, 0xa4, 0x57, 0x99, 0x69, 0xac, 0x21, 0x7d, 0xd2, 0xa1, 0x4f, 0x0e, 0xd2, 0xa7, 0x37, 0xc8, 0xaa, 0x5a, 0x4f, 0x5a, 0xe5, 0x83, 0x56, 0xb9, 0xa1, 0x55, 0x7e, 0x68, 0x55, 0x10, 0x38, 0x8b, 0x18, 0xb5, 0x2a, 0xfb, 0x17, 0xdb, 0x47, 0x7a, 0xb3, 0x9f, 0x89, 0x2f, 0x4f, 0x7f, 0x49, 0x64, 0x4f, 0x8c, 0x57, 0xa1, 0x49, 0x79, 0xd0, 0xa4, 0x7c, 0xd2, 0x9d, 0x9f, 0x58, 0x1a, 0xfb, 0x99, 0xc8, 0x01, 0xbd, 0xc9, 0x81, 0xde, 0x34, 0xe0, 0x12, 0x97, 0x58, 0x2e, 0xd7, 0xb8, 0xc6, 0x1a, 0x41, 0x3f, 0x1c, 0xdc, 0xc1, 0x9d, 0x34, + 0x46, 0xbd, 0xa4, 0x13, 0x06, 0xb4, 0xe1, 0x3c, 0x68, 0x43, 0x0a, 0x0f, 0x93, 0x36, 0x18, 0x3c, 0x4a, 0xda, 0x10, 0xe5, 0x29, 0xa4, 0x0d, 0x06, 0x4f, 0xe5, 0xa9, 0xa4, 0x37, 0xb5, 0x78, 0x2d, 0x4a, 0x0b, 0xcd, 0x48, 0x87, 0x66, 0xd4, 0xe1, 0xe9, 0x3c, 0x9d, 0xe4, 0x19, 0x3c, 0x93, 0xe4, 0xb5, 0x79, 0x6d, 0x96, 0xc1, 0xb3, 0x48, 0x4b, 0x0c, 0x5e, 0x97, 0xf4, 0xc3, 0xe0, 0xf5, 0x79, 0x7d, 0x9a, 0x15, 0x84, 0x96, 0x78, 0x78, 0x03, 0xde, 0x80, 0x24, 0x0d, 0x79, 0x43, 0xd2, 0x18, 0x81, 0xaa, 0x98, 0x40, 0x55, 0xea, 0x01, 0x55, 0xe1, 0x40, 0x55, 0xea, 0x01, 0x55, 0xe1, 0x40, 0x55, 0x82, 0x40, 0x55, 0x24, 0x78, 0x0a, 0xb8, 0x81, 0xad, 0x48, 0xf0, 0x14, 0x70, 0x03, 0x61, 0x91, 0x81, 0xb0, 0xc8, 0x40, 0x58, 0x14, 0xf9, 0x59, 0xf9, 0x59, 0xd2, 0x92, 0x45, 0xf2, 0x22, 0xe2, + 0x8b, 0xe5, 0xc5, 0xc4, 0x97, 0xca, 0x4b, 0x89, 0x0b, 0xfd, 0xcb, 0x93, 0xd7, 0x90, 0xe6, 0xd5, 0x95, 0xd7, 0xc9, 0xeb, 0x98, 0x43, 0x7e, 0x43, 0x7e, 0x83, 0xd2, 0xeb, 0xe5, 0xf5, 0x2c, 0x5b, 0x7e, 0x53, 0x7e, 0x93, 0x66, 0xa0, 0x0d, 0xf2, 0x06, 0x92, 0xbc, 0x45, 0x5a, 0x98, 0x2b, 0x6f, 0x92, 0xdf, 0x61, 0xf5, 0xa1, 0x8b, 0x69, 0xd0, 0xbf, 0x5c, 0xb9, 0x54, 0x2e, 0x65, 0x11, 0x79, 0x9b, 0xbc, 0x8d, 0xce, 0xfd, 0x48, 0xde, 0x41, 0xf9, 0x3f, 0x96, 0x77, 0xb2, 0x1a, 0xf2, 0xa7, 0xf2, 0xa7, 0x94, 0xe7, 0x5f, 0xf2, 0x7e, 0xd2, 0x69, 0xa1, 0x85, 0xe7, 0x91, 0x16, 0x1e, 0xa4, 0xf4, 0xd7, 0xa4, 0x85, 0xe9, 0xa4, 0x85, 0x65, 0x94, 0xfe, 0x56, 0xfe, 0x96, 0x74, 0x54, 0x68, 0x61, 0xba, 0xfc, 0x9d, 0x7c, 0x84, 0xa5, 0xca, 0xdf, 0xcb, 0xdf, 0xb3, 0x4c, 0xd2, 0xc8, 0xa3, 0x24, + 0xf9, 0x41, 0x3e, 0xc6, 0x32, 0xe4, 0x1f, 0x65, 0x1a, 0xed, 0xf2, 0x71, 0xf9, 0x27, 0x96, 0x25, 0x9f, 0x90, 0x4f, 0x50, 0x5a, 0x68, 0x6a, 0x54, 0x3e, 0x49, 0x9a, 0x6a, 0xc8, 0xa7, 0xe4, 0x53, 0x94, 0x47, 0xe8, 0xab, 0xa1, 0x08, 0xa3, 0xda, 0xa7, 0x48, 0x8a, 0x44, 0x5a, 0x2b, 0x74, 0xd7, 0x50, 0x6c, 0x8a, 0x9d, 0x65, 0x2a, 0xaa, 0xa2, 0x92, 0xc4, 0x50, 0x0c, 0x16, 0x55, 0x4c, 0xc5, 0x24, 0x79, 0x1b, 0xa5, 0x0d, 0x73, 0x2a, 0x9d, 0x94, 0xce, 0xcc, 0xa1, 0x5c, 0xa4, 0x5c, 0x44, 0xe9, 0x2e, 0x4a, 0x17, 0x4a, 0x5f, 0xac, 0x5c, 0xcc, 0x6a, 0x42, 0xd7, 0x0d, 0xa5, 0x48, 0x29, 0x22, 0xde, 0x4d, 0xe9, 0x46, 0xbc, 0xbb, 0x72, 0x39, 0x95, 0x70, 0x85, 0x72, 0x05, 0x71, 0xe1, 0x83, 0x50, 0x0b, 0x3e, 0x08, 0xb5, 0xe0, 0x83, 0xe0, 0x82, 0x0f, 0x82, 0x17, 0x3e, 0x08, 0x2e, 0xf8, + 0x20, 0x78, 0x31, 0x42, 0x52, 0x6c, 0x65, 0xb6, 0x32, 0xe6, 0xc1, 0x38, 0xc9, 0xb7, 0x1d, 0xb3, 0x1d, 0x63, 0x0e, 0xdb, 0x8f, 0xb6, 0x1f, 0x99, 0xdd, 0x76, 0xdc, 0x46, 0x33, 0x97, 0xda, 0x54, 0x6d, 0xca, 0x42, 0xea, 0xb5, 0x6a, 0x5f, 0xe2, 0xb7, 0xa8, 0xb7, 0x30, 0x87, 0x16, 0xd6, 0xc2, 0xc4, 0x1b, 0x6b, 0x8d, 0x89, 0x5f, 0xae, 0x5d, 0xce, 0xea, 0x6a, 0x77, 0x6a, 0x77, 0xb2, 0x86, 0xda, 0x14, 0x6d, 0x0a, 0xcb, 0xd6, 0x9e, 0xd6, 0x9e, 0x66, 0x31, 0x6d, 0x99, 0xf6, 0x4f, 0xe2, 0xaf, 0x68, 0xaf, 0x10, 0x5f, 0xaf, 0xad, 0x67, 0x1e, 0x6d, 0x83, 0xb6, 0x81, 0xd9, 0xb5, 0x77, 0xb5, 0x77, 0x29, 0xfd, 0x91, 0xf6, 0x11, 0xf1, 0x8f, 0xb5, 0x8f, 0x89, 0x7f, 0xa5, 0x7d, 0xc5, 0x3c, 0xba, 0xa1, 0x1b, 0xc4, 0x43, 0x7a, 0x88, 0x85, 0xf5, 0x88, 0x4e, 0xf3, 0x97, 0x9e, 0xa1, 0x67, + 0x30, 0x43, 0xcf, 0xd4, 0x33, 0x29, 0x9d, 0xab, 0xe7, 0x12, 0x6f, 0xaf, 0xb7, 0x27, 0x49, 0x6f, 0xfd, 0x1a, 0x66, 0xd7, 0xfb, 0xe8, 0x7d, 0x28, 0xff, 0x2d, 0xfa, 0x70, 0x4a, 0xdf, 0xa1, 0xdf, 0x41, 0xe9, 0xbb, 0xf4, 0xbb, 0xe8, 0xe8, 0x18, 0x7d, 0x0c, 0xf1, 0x3d, 0xfa, 0x1e, 0x96, 0xa9, 0x1f, 0xd0, 0xbf, 0x62, 0x01, 0xfd, 0xb0, 0x7e, 0x84, 0x05, 0xe8, 0x69, 0xd0, 0x99, 0x46, 0xef, 0x58, 0x63, 0x1a, 0x8d, 0x5e, 0x31, 0xaa, 0xad, 0xf1, 0x9c, 0x87, 0xf1, 0x9c, 0x8f, 0x31, 0x9c, 0x43, 0x63, 0x78, 0x0d, 0xcb, 0x35, 0xd6, 0x1a, 0x6b, 0x59, 0x23, 0x8c, 0xe4, 0x06, 0x18, 0xc9, 0x06, 0x46, 0xf2, 0x79, 0x18, 0xc9, 0x29, 0xc6, 0xdb, 0xc6, 0x26, 0x92, 0x6c, 0xa6, 0xf1, 0x6c, 0x18, 0xef, 0x1b, 0xef, 0xb3, 0x0c, 0xa3, 0xd4, 0x28, 0xa5, 0xf4, 0x07, 0xc6, 0x07, 0xac, 0x36, 0x46, + 0x78, 0x1d, 0x20, 0x2c, 0xb2, 0xf9, 0x57, 0xf3, 0xaf, 0x4c, 0x37, 0x57, 0x9a, 0x2b, 0x59, 0xc4, 0x5c, 0x65, 0xae, 0x62, 0x0e, 0x73, 0xb5, 0xb9, 0x9a, 0xf9, 0xcc, 0x57, 0xcd, 0x57, 0x99, 0xc7, 0xdc, 0x68, 0x6e, 0x64, 0x4e, 0xf3, 0x6d, 0xf3, 0x6d, 0x56, 0x93, 0x49, 0x7a, 0x77, 0xa1, 0x12, 0xfa, 0x3c, 0xa3, 0x90, 0x2c, 0xcb, 0x4b, 0xf1, 0x2e, 0xf5, 0x23, 0xb2, 0x32, 0x36, 0x93, 0x45, 0xf3, 0x09, 0x59, 0xbb, 0xbb, 0xc8, 0x72, 0xda, 0x40, 0xb6, 0xd9, 0x6b, 0xf1, 0x31, 0xf1, 0xce, 0x64, 0x89, 0x3e, 0x40, 0xb2, 0x0d, 0x64, 0x11, 0xd6, 0x8d, 0x7f, 0x4e, 0xfb, 0xdb, 0x69, 0x5d, 0x62, 0x9c, 0x7b, 0x0d, 0xf8, 0xa7, 0xe0, 0x00, 0xef, 0xc5, 0x27, 0x26, 0xfc, 0x1d, 0xb7, 0xd0, 0x5a, 0x78, 0x14, 0xd5, 0xec, 0x43, 0xb2, 0xb9, 0x86, 0xc5, 0xdb, 0x90, 0x64, 0x34, 0xc9, 0x5e, 0xa3, + 0xfa, 0xd7, 0xc5, 0xf1, 0xe1, 0xc2, 0x52, 0xa7, 0xa3, 0x4b, 0xc8, 0x1a, 0x5c, 0x42, 0x56, 0x89, 0x16, 0xdf, 0x13, 0x6f, 0xc0, 0xfe, 0x47, 0x7f, 0xf1, 0x1b, 0x13, 0xfb, 0xf9, 0x49, 0xb2, 0x4d, 0x67, 0xe4, 0x7a, 0x8a, 0xee, 0xe1, 0x29, 0x6a, 0xf9, 0x97, 0xe2, 0xcb, 0xe2, 0x6d, 0xe2, 0x37, 0x10, 0x6d, 0xa1, 0x5f, 0xf9, 0x64, 0x03, 0x8e, 0xa6, 0x5e, 0x78, 0x9e, 0xd2, 0xf7, 0x51, 0x4f, 0x3c, 0x1f, 0x9f, 0x29, 0xde, 0x14, 0xfe, 0x1b, 0xb5, 0x38, 0x87, 0xed, 0x26, 0xde, 0xef, 0x57, 0x89, 0x47, 0x38, 0x1c, 0x7f, 0xe5, 0x2c, 0x56, 0xc8, 0x4d, 0xc9, 0xef, 0x90, 0xad, 0xb7, 0xc8, 0x48, 0x27, 0xe4, 0x62, 0x6d, 0x8a, 0xfd, 0xab, 0xff, 0x56, 0xed, 0x36, 0x9d, 0x55, 0xfa, 0x65, 0xfc, 0x86, 0x2a, 0x92, 0x17, 0xe3, 0xaf, 0xc4, 0x1f, 0x3c, 0x6d, 0x53, 0x25, 0xf6, 0x2f, 0xc7, 0xaf, + 0x15, 0xb8, 0x8a, 0x88, 0xb9, 0x88, 0x97, 0xc6, 0xfb, 0x5b, 0x78, 0x11, 0x59, 0x75, 0x6f, 0xc6, 0xaf, 0x4d, 0xd8, 0x23, 0x8b, 0xe2, 0x47, 0xb0, 0x9f, 0x49, 0xd2, 0x2d, 0x64, 0xad, 0x1f, 0x81, 0xe7, 0xc0, 0xce, 0x72, 0xbf, 0xfa, 0xdf, 0xa8, 0xdd, 0x59, 0xef, 0x89, 0x74, 0x6a, 0x6c, 0x15, 0xc9, 0x34, 0xaa, 0xdf, 0xdf, 0x2b, 0x7e, 0x1d, 0xad, 0x68, 0xbb, 0xf1, 0x15, 0x7e, 0xd4, 0x6f, 0x53, 0x1f, 0x8e, 0xa9, 0xf0, 0x5d, 0x18, 0x9f, 0x48, 0x2d, 0x4f, 0xec, 0xd7, 0x51, 0x99, 0x9f, 0x91, 0x9e, 0xff, 0x00, 0xbf, 0xe8, 0x32, 0xfa, 0xf5, 0x43, 0x79, 0xee, 0x73, 0xe2, 0x56, 0x7b, 0xe3, 0x93, 0xe8, 0x3e, 0xb6, 0xc1, 0x33, 0xe0, 0x1b, 0xea, 0x99, 0xed, 0x18, 0x1f, 0x73, 0x84, 0xa7, 0x78, 0x7c, 0x0a, 0xc9, 0xc4, 0xef, 0x7c, 0xb2, 0x1f, 0xb6, 0xd3, 0x48, 0x59, 0x54, 0x39, 0xc6, + 0x40, 0x1c, 0xf9, 0x93, 0x75, 0x5f, 0x20, 0x34, 0x2b, 0xcb, 0xd3, 0xd4, 0x77, 0x02, 0x77, 0x58, 0x51, 0x71, 0x74, 0xae, 0x85, 0x9c, 0x81, 0x3f, 0x5f, 0xe1, 0xd5, 0xf3, 0x5e, 0xb9, 0x37, 0x87, 0x65, 0xfb, 0x58, 0x76, 0xa3, 0xe5, 0xa5, 0xcd, 0xbc, 0x95, 0xf1, 0xb3, 0x24, 0xbf, 0x6b, 0x6f, 0x25, 0xc9, 0x4b, 0x56, 0x9f, 0xc5, 0xd7, 0x52, 0x2b, 0xbf, 0x1d, 0xdf, 0x4f, 0x9a, 0xf4, 0xce, 0x99, 0x08, 0x32, 0xe2, 0x52, 0xae, 0x48, 0xa4, 0x5b, 0xc7, 0x9f, 0x11, 0x68, 0x4b, 0xb9, 0x07, 0x02, 0xa5, 0xda, 0x9c, 0x1e, 0xbd, 0xe5, 0x78, 0x40, 0x7c, 0x46, 0xfc, 0xae, 0xf8, 0xb0, 0x44, 0x7a, 0x18, 0x4b, 0x2b, 0xd7, 0xfc, 0x04, 0xde, 0x92, 0x5f, 0xa1, 0x11, 0x49, 0xb5, 0x41, 0xfe, 0x46, 0x15, 0x3f, 0x53, 0xe1, 0x11, 0x70, 0x1f, 0x8e, 0x8c, 0xa0, 0x6b, 0x2e, 0x26, 0xad, 0x59, 0x1a, + 0xbf, 0x31, 0x3e, 0xfb, 0xdf, 0x6a, 0xdf, 0x73, 0x62, 0x23, 0xa2, 0xdf, 0x2b, 0xfe, 0xea, 0x27, 0xf6, 0xb5, 0xc1, 0xd5, 0x4a, 0x19, 0xcf, 0x36, 0x2f, 0xd6, 0x4e, 0xec, 0x43, 0x49, 0x1e, 0x48, 0xae, 0xdf, 0xc6, 0xcf, 0xab, 0xda, 0xf7, 0x67, 0xb1, 0x9e, 0x3f, 0x2c, 0xd7, 0xf4, 0x24, 0x61, 0x6e, 0x62, 0x8f, 0xb9, 0x9b, 0x6c, 0x94, 0xe4, 0xbf, 0xbc, 0xb3, 0x5c, 0xaa, 0x6e, 0x62, 0x1f, 0xa5, 0xfa, 0x95, 0xff, 0x55, 0xf2, 0x94, 0x3a, 0x95, 0x40, 0x15, 0x4e, 0x7d, 0x1f, 0x97, 0x7f, 0xa5, 0xc6, 0x37, 0xc7, 0x5d, 0xa7, 0x2a, 0x66, 0x45, 0xab, 0x47, 0x7e, 0xf5, 0x0e, 0x57, 0xff, 0x9b, 0x63, 0x60, 0x12, 0x3c, 0x77, 0x9a, 0xc4, 0xcf, 0xaf, 0x8c, 0x63, 0x9f, 0x1d, 0x35, 0x00, 0x8e, 0xf6, 0x4d, 0x25, 0x04, 0xe0, 0x60, 0x45, 0xbc, 0xd6, 0xb1, 0x72, 0xeb, 0xfd, 0xdc, 0x28, 0xec, + 0xd9, 0xbc, 0x6d, 0x12, 0x48, 0xfd, 0x6f, 0x9e, 0xf3, 0xeb, 0x18, 0xc6, 0x39, 0xce, 0x79, 0x2e, 0xf9, 0xec, 0x44, 0x1c, 0xc6, 0xe0, 0xf8, 0xa8, 0xca, 0xf3, 0x0c, 0xa5, 0xae, 0x8e, 0xf7, 0xb1, 0x66, 0xf0, 0x4a, 0x35, 0xdb, 0xfd, 0x5b, 0xef, 0x42, 0xe2, 0x57, 0x57, 0x07, 0xd5, 0xae, 0x3c, 0x22, 0x2a, 0x30, 0xd8, 0x67, 0x2a, 0xbc, 0x6b, 0xbe, 0x42, 0xdb, 0x21, 0x52, 0xe9, 0x4f, 0xfb, 0x93, 0xc8, 0x46, 0x6e, 0x49, 0x16, 0x06, 0x83, 0x85, 0x61, 0xc0, 0xc2, 0x30, 0x61, 0x61, 0x38, 0xf8, 0x7e, 0xfe, 0x15, 0x73, 0xc9, 0x4f, 0xca, 0x4f, 0x92, 0xbd, 0x29, 0xac, 0x04, 0xbf, 0xbc, 0x93, 0xec, 0x80, 0x20, 0xd9, 0x01, 0xbb, 0xc9, 0x1a, 0x15, 0xab, 0xfb, 0x48, 0xc5, 0x3b, 0xb2, 0x5f, 0x58, 0x8a, 0xb9, 0xd8, 0x7c, 0x99, 0x35, 0xa4, 0x12, 0xa3, 0x09, 0xeb, 0xe6, 0xb4, 0x5d, 0x23, + 0xc1, 0xae, 0x91, 0x61, 0xd7, 0x28, 0xb0, 0x6b, 0x64, 0xd8, 0x35, 0x0a, 0xae, 0x6a, 0xc3, 0x55, 0x6d, 0xb8, 0xaa, 0x1d, 0x6f, 0x8e, 0x55, 0xbc, 0x39, 0xd6, 0xf0, 0xe6, 0x58, 0x87, 0x07, 0xb4, 0x81, 0x7a, 0x98, 0xa8, 0x87, 0x03, 0xf5, 0x70, 0xa2, 0x1e, 0x4e, 0xd4, 0xc3, 0x85, 0x7a, 0xd8, 0xf0, 0xae, 0x4e, 0xc5, 0xbb, 0x3a, 0x0d, 0xef, 0xea, 0x74, 0x78, 0x86, 0x1a, 0xa8, 0x99, 0x49, 0xb5, 0xe8, 0x82, 0xf7, 0xf6, 0x3a, 0x7c, 0xd7, 0x5d, 0x78, 0x6f, 0x5f, 0x1b, 0x6f, 0xec, 0x6b, 0xe3, 0x5d, 0x7d, 0x18, 0x6f, 0xe9, 0x6b, 0xe3, 0xfd, 0x7c, 0x6d, 0xbc, 0x93, 0x6f, 0x88, 0x77, 0xf2, 0x79, 0x78, 0x27, 0x7f, 0x3e, 0xde, 0xc9, 0xa7, 0xe3, 0x9d, 0x7c, 0x4b, 0xbc, 0x93, 0xaf, 0x87, 0x77, 0xf2, 0x2d, 0xf0, 0x4e, 0xde, 0x8b, 0x77, 0xf2, 0x06, 0xde, 0xc9, 0x3b, 0xf0, 0x4e, 0xde, + 0xc0, 0x3b, 0xf9, 0x0e, 0x78, 0x27, 0x2f, 0xe1, 0x9d, 0xbc, 0x02, 0xbf, 0xf4, 0x4e, 0xf0, 0x5a, 0xe9, 0x84, 0xf7, 0xf3, 0x06, 0x3c, 0xd2, 0x3b, 0xe1, 0x2d, 0xbd, 0x03, 0xfe, 0x2a, 0x9d, 0xf0, 0xae, 0x5e, 0xc3, 0xbb, 0xfa, 0x0e, 0x78, 0x57, 0xdf, 0x1e, 0x1e, 0x29, 0xed, 0xf0, 0xc6, 0xbe, 0x0e, 0xde, 0xd8, 0xd7, 0xc7, 0x1b, 0xfb, 0xd6, 0x78, 0x63, 0xdf, 0x04, 0x6f, 0xec, 0x0b, 0xd9, 0xdb, 0x44, 0x39, 0xb0, 0x96, 0x6d, 0x40, 0x62, 0xec, 0x40, 0x62, 0x2e, 0x01, 0x12, 0xd3, 0x15, 0x48, 0xcc, 0xc5, 0x6c, 0x2f, 0x11, 0x87, 0x2d, 0x1d, 0x82, 0x2d, 0xdd, 0x05, 0xb6, 0x74, 0x08, 0xb6, 0x74, 0x10, 0xb6, 0x74, 0x73, 0x58, 0xd1, 0x32, 0xac, 0xe8, 0x7c, 0x58, 0xd1, 0x51, 0xd8, 0xcf, 0xd9, 0xb0, 0x9c, 0xf3, 0x61, 0x39, 0x67, 0xc0, 0x72, 0x2e, 0x82, 0xe5, 0x7c, 0x01, 0x2c, 0xe7, + 0x54, 0x58, 0xce, 0x45, 0xb0, 0x9c, 0x23, 0xb0, 0x9c, 0x8b, 0x60, 0x39, 0xfb, 0x60, 0x39, 0x17, 0xc1, 0x72, 0xce, 0x84, 0xcd, 0xec, 0x83, 0xcd, 0x5c, 0x04, 0x9b, 0xb9, 0x29, 0x6c, 0xe6, 0x22, 0xd8, 0xcc, 0x45, 0xb0, 0x99, 0xd3, 0x60, 0x33, 0xfb, 0x60, 0x33, 0x17, 0xc1, 0x66, 0xae, 0x05, 0x9b, 0x39, 0x00, 0x9b, 0xf9, 0x22, 0x68, 0x55, 0x2e, 0x6c, 0xe6, 0x8b, 0xa0, 0x5b, 0xb9, 0xd0, 0xad, 0xf3, 0xa0, 0x5b, 0x35, 0xa1, 0x5b, 0x35, 0xa0, 0x5b, 0x35, 0xa1, 0x5b, 0x35, 0xa0, 0x5b, 0x29, 0xd0, 0xad, 0x14, 0xe8, 0x56, 0x03, 0xe8, 0x56, 0x6d, 0xe8, 0x56, 0x1e, 0x74, 0xcb, 0x80, 0x6e, 0xb5, 0x83, 0x77, 0x7d, 0x63, 0x78, 0x28, 0xd4, 0x86, 0x77, 0x7d, 0x63, 0x78, 0x28, 0xd4, 0x86, 0x57, 0x42, 0xed, 0x8a, 0x98, 0xa1, 0x5e, 0xac, 0x1e, 0x7c, 0x13, 0x5a, 0xc0, 0x37, 0xa1, 0x36, + 0xbc, 0x12, 0x5a, 0xc0, 0x2b, 0x21, 0x0f, 0xfe, 0x08, 0xf5, 0xe0, 0x8f, 0x90, 0x07, 0x4f, 0x84, 0x16, 0xf0, 0x44, 0x30, 0xe0, 0x89, 0xe0, 0x80, 0x5f, 0x7d, 0x27, 0x78, 0x07, 0x75, 0x82, 0x57, 0x42, 0x7b, 0x78, 0x25, 0x14, 0x26, 0x79, 0x07, 0x49, 0xf0, 0x4a, 0x70, 0xc0, 0x2b, 0xa1, 0x10, 0x5e, 0x09, 0x1a, 0xbc, 0x12, 0x1c, 0xf0, 0x4a, 0x68, 0x02, 0xaf, 0x84, 0x42, 0x78, 0x25, 0x74, 0x80, 0x57, 0x42, 0x7b, 0xcb, 0x3b, 0x08, 0xa3, 0xa2, 0xad, 0xfc, 0x8c, 0xfc, 0x0c, 0xcb, 0x01, 0x22, 0x10, 0x92, 0x17, 0xca, 0xcf, 0x33, 0x0e, 0x5c, 0x20, 0x04, 0x5c, 0x20, 0x04, 0x5c, 0x20, 0x04, 0x5c, 0xa0, 0x39, 0x70, 0x81, 0x0c, 0xe0, 0x02, 0xf9, 0xc0, 0x05, 0x32, 0x80, 0x0b, 0xb8, 0x81, 0x0b, 0xb4, 0x02, 0x2e, 0x90, 0x01, 0x5c, 0x20, 0x1b, 0x23, 0xad, 0x00, 0xb8, 0x80, 0x0c, 0x5c, + 0x20, 0x1b, 0x88, 0x40, 0x3e, 0x10, 0x81, 0x56, 0x40, 0x04, 0x9c, 0x40, 0x04, 0x64, 0x20, 0x02, 0x45, 0x40, 0x04, 0x2e, 0x00, 0x22, 0x50, 0x04, 0x44, 0x20, 0x13, 0x88, 0x40, 0x11, 0x10, 0x01, 0x1f, 0x10, 0x81, 0x4c, 0x8c, 0xd5, 0x2c, 0x20, 0x02, 0xcd, 0x80, 0x08, 0x64, 0x02, 0x11, 0x68, 0x0a, 0x44, 0xa0, 0x08, 0x88, 0x80, 0x1f, 0x88, 0x40, 0x11, 0x10, 0x81, 0x08, 0x10, 0x81, 0x22, 0x20, 0x02, 0x4d, 0x81, 0x08, 0x14, 0x01, 0x11, 0x88, 0x01, 0x11, 0xf0, 0x01, 0x11, 0x28, 0x02, 0x22, 0xd0, 0x0c, 0x88, 0x80, 0x0f, 0x88, 0x40, 0x04, 0x88, 0x40, 0x11, 0xb0, 0x80, 0x7c, 0xa0, 0x00, 0xf9, 0xb0, 0xff, 0x8b, 0x60, 0xff, 0x17, 0xc1, 0xfe, 0x2f, 0x82, 0xfd, 0xef, 0x83, 0xfd, 0xef, 0x83, 0x67, 0x87, 0x02, 0x14, 0xa0, 0x33, 0x50, 0x80, 0xce, 0x40, 0x01, 0x2e, 0x04, 0x0a, 0xd0, + 0x11, 0x28, 0xc0, 0x85, 0x40, 0x01, 0x3a, 0xc2, 0xef, 0xa3, 0x03, 0x3c, 0x3e, 0x3a, 0xc0, 0xe3, 0xa3, 0x36, 0xf0, 0xb3, 0xae, 0xf0, 0xf8, 0x30, 0xe0, 0xeb, 0xe1, 0x80, 0x17, 0x56, 0x3b, 0xe0, 0x05, 0xa9, 0xc0, 0x0b, 0x7c, 0x40, 0x0a, 0xf2, 0x81, 0x14, 0x84, 0x80, 0x14, 0xb4, 0xb1, 0xfd, 0x64, 0xfb, 0x89, 0xe5, 0xc0, 0x53, 0xab, 0x13, 0xfc, 0x41, 0xda, 0xc3, 0x1f, 0xc4, 0x80, 0x3f, 0x88, 0x85, 0x23, 0x78, 0xe0, 0x15, 0xa2, 0x03, 0x4d, 0xf0, 0xc0, 0x37, 0x44, 0x47, 0xb4, 0x82, 0x0b, 0xbe, 0x21, 0xb5, 0x81, 0x2f, 0xe4, 0xab, 0xb7, 0xaa, 0xb7, 0xb2, 0x1c, 0xf8, 0x89, 0xd4, 0x86, 0x27, 0x48, 0x6d, 0xf8, 0x7a, 0x84, 0xe1, 0xeb, 0x11, 0x86, 0x4f, 0x47, 0x6d, 0x2d, 0xa8, 0x85, 0x58, 0x0e, 0x90, 0x88, 0x7c, 0xf8, 0x77, 0xd4, 0xd6, 0xea, 0x6b, 0xf5, 0x49, 0x92, 0xab, 0xe5, + 0x12, 0x6f, 0xa4, 0xe5, 0x13, 0x17, 0x08, 0x45, 0x3e, 0xfc, 0x3e, 0x6a, 0xc3, 0xbf, 0xa3, 0x21, 0xd0, 0x8a, 0x0c, 0x78, 0x79, 0x34, 0x84, 0x97, 0x47, 0x43, 0x78, 0x79, 0xe4, 0xc1, 0xcb, 0xe3, 0x7c, 0x78, 0x79, 0xa4, 0x03, 0xcb, 0x68, 0x05, 0x5f, 0x8f, 0x96, 0xf0, 0xf5, 0xa8, 0xa7, 0x8d, 0xd1, 0xc6, 0xb0, 0xba, 0xda, 0x58, 0x6d, 0x2c, 0x71, 0xe1, 0xf7, 0x51, 0x4f, 0x9b, 0xac, 0x3d, 0x42, 0x69, 0x81, 0x77, 0xb8, 0xe1, 0x03, 0xd2, 0x02, 0x3e, 0x20, 0x2d, 0xe0, 0x03, 0xe2, 0x05, 0x02, 0xa2, 0xc2, 0x13, 0xc4, 0x0b, 0x1c, 0x44, 0x85, 0x3f, 0x88, 0x17, 0x68, 0x88, 0x0a, 0xaf, 0x10, 0x03, 0x98, 0x88, 0x0f, 0x98, 0x48, 0x08, 0x1e, 0x22, 0x06, 0x3c, 0x44, 0x1c, 0xc0, 0x47, 0x7c, 0xf0, 0x13, 0x71, 0xc0, 0x4f, 0xc4, 0x00, 0x56, 0xe2, 0x03, 0x56, 0xe2, 0x83, 0xcf, 0x88, 0x01, + 0x9f, 0x11, 0x03, 0xb8, 0x89, 0x0f, 0x9e, 0x23, 0x06, 0x3c, 0x47, 0x3a, 0xc0, 0x73, 0xa4, 0x03, 0x3c, 0x47, 0x24, 0x78, 0x8e, 0x28, 0x40, 0x55, 0x7c, 0xf0, 0x1f, 0x51, 0x80, 0xad, 0xd4, 0x02, 0xaa, 0x52, 0xa4, 0x5f, 0xa0, 0x5f, 0xc0, 0x2e, 0x06, 0x9e, 0x52, 0x04, 0x3f, 0xb4, 0x4e, 0xf0, 0x43, 0xeb, 0x04, 0x3f, 0xb4, 0x4e, 0xf0, 0x34, 0xb1, 0x70, 0x96, 0x10, 0x70, 0x16, 0x1f, 0xbc, 0x4e, 0x0c, 0xa0, 0x2d, 0x21, 0xa0, 0x2d, 0x3e, 0x78, 0xa0, 0x18, 0xc0, 0x5c, 0x8a, 0xe0, 0xa5, 0xd6, 0x09, 0xc8, 0x4b, 0x11, 0x7c, 0xd5, 0x3a, 0xc1, 0x57, 0xad, 0x13, 0xfc, 0x53, 0x34, 0xf8, 0xa7, 0x68, 0xf0, 0x4f, 0x69, 0x0f, 0x2f, 0xb5, 0x76, 0xf0, 0x52, 0x6b, 0x07, 0x2f, 0xb5, 0x76, 0xf0, 0x58, 0xa9, 0x03, 0x8f, 0x95, 0xfa, 0x40, 0x6d, 0x9a, 0xc1, 0x6f, 0xa5, 0x35, 0xfc, 0x56, 0x9a, + 0x00, 0xc1, 0x69, 0x04, 0xef, 0x95, 0x26, 0xc0, 0x71, 0x1a, 0xc1, 0x87, 0xa5, 0x10, 0x68, 0x8e, 0x0d, 0xd8, 0xad, 0x1d, 0xd8, 0xed, 0x25, 0xc0, 0x6e, 0x2f, 0x01, 0x76, 0xdb, 0x15, 0xd8, 0x6d, 0x57, 0x63, 0x8c, 0x71, 0x2f, 0xe3, 0x40, 0x7c, 0xba, 0x00, 0xf1, 0x09, 0x02, 0xf1, 0x69, 0x6e, 0xac, 0x34, 0x56, 0xb2, 0x1c, 0x20, 0x3e, 0x51, 0x20, 0x3e, 0xd9, 0x40, 0x79, 0x8a, 0x80, 0xf2, 0x5c, 0x00, 0x94, 0x27, 0x15, 0x28, 0x4f, 0x11, 0x50, 0x9e, 0x22, 0xa0, 0x3c, 0x4d, 0x81, 0xf2, 0x14, 0x01, 0xe5, 0x49, 0x83, 0xd7, 0x5c, 0x27, 0x3c, 0x9b, 0x53, 0xf0, 0x6c, 0xae, 0x8d, 0x67, 0x73, 0x1e, 0x9e, 0xcd, 0x06, 0x9e, 0xcd, 0xed, 0x80, 0xfe, 0xb4, 0x31, 0x5f, 0x34, 0x5f, 0x64, 0x6d, 0xcd, 0x7f, 0x9a, 0xcb, 0x89, 0x0b, 0x0c, 0x28, 0x1f, 0x18, 0x50, 0x0c, 0x18, 0x90, 0x0f, 0x5e, + 0x36, 0x0e, 0xea, 0xab, 0x03, 0x62, 0xf8, 0x1b, 0x5b, 0x4c, 0xf1, 0x3f, 0x3e, 0x3a, 0xe2, 0x6d, 0xf0, 0x7d, 0xf1, 0x71, 0x64, 0x2f, 0x0a, 0xfe, 0x1a, 0xfb, 0x9f, 0xfe, 0x25, 0xbe, 0x08, 0xf0, 0x55, 0x85, 0xe7, 0xc0, 0x91, 0xf8, 0xe7, 0xf1, 0xaf, 0x11, 0x25, 0xbe, 0x82, 0x52, 0xa5, 0xb4, 0x22, 0x16, 0x5e, 0xd4, 0xfb, 0x44, 0x4c, 0xeb, 0x7f, 0xb1, 0x56, 0x93, 0xe2, 0xcf, 0xc5, 0x4f, 0xd2, 0x55, 0xcb, 0x5b, 0x27, 0x06, 0x6e, 0xd9, 0x80, 0xb7, 0xc2, 0x83, 0x7d, 0xc7, 0x69, 0x6c, 0xe2, 0x4f, 0xae, 0xcb, 0xa8, 0xf8, 0xfd, 0xf1, 0x6f, 0x4f, 0x7b, 0xa4, 0x27, 0xfe, 0x1c, 0xe5, 0x36, 0x58, 0xfc, 0x6f, 0xbf, 0xf6, 0x0d, 0x8b, 0xff, 0x48, 0x0d, 0x92, 0xbe, 0x11, 0x51, 0x39, 0x96, 0xa3, 0xdc, 0x8b, 0x3e, 0x11, 0x4b, 0xf3, 0xfd, 0x7f, 0xb5, 0x8f, 0x1e, 0x16, 0x76, 0x74, 0xf9, + 0xd7, 0x1c, 0xe8, 0xcf, 0x5d, 0xe5, 0xf8, 0xac, 0xff, 0xba, 0x2e, 0x7f, 0xce, 0xfe, 0xcf, 0xfc, 0x91, 0xad, 0xf9, 0x0d, 0x6d, 0x3f, 0x90, 0x9d, 0xf4, 0x6d, 0xfc, 0xa7, 0xf8, 0x77, 0x88, 0x1d, 0xfa, 0xb6, 0x5a, 0x91, 0x4b, 0xff, 0xce, 0xd5, 0x2c, 0x84, 0xe6, 0x7d, 0x78, 0xf8, 0x6d, 0x89, 0xbf, 0x02, 0x5f, 0x8d, 0x87, 0x2a, 0x50, 0xa0, 0x8f, 0xf1, 0xae, 0xfb, 0x3b, 0x78, 0xbe, 0x3c, 0x0a, 0xff, 0x23, 0x2b, 0x66, 0xfe, 0x93, 0xf8, 0xb4, 0xf8, 0xc4, 0xf8, 0xf4, 0xea, 0x7d, 0x0d, 0xa6, 0xda, 0x75, 0xf9, 0xf8, 0x2c, 0x5e, 0x7a, 0x0b, 0xcf, 0xf4, 0x52, 0x88, 0x2f, 0xb4, 0xf0, 0x3d, 0x0b, 0xbf, 0x88, 0x1f, 0x40, 0x44, 0xfe, 0x9b, 0xff, 0xe1, 0x76, 0x59, 0x8c, 0x58, 0xfe, 0xd5, 0xf0, 0xe3, 0x5b, 0x19, 0x7f, 0x1e, 0x5f, 0x22, 0x18, 0x97, 0x8c, 0x94, 0xc6, 0x57, 0xe3, 0x2b, + 0x23, 0xe3, 0xe2, 0x0f, 0xc0, 0x7b, 0x6b, 0x3a, 0x5a, 0x67, 0x15, 0xf6, 0x33, 0xfe, 0xd8, 0x78, 0x8b, 0x3f, 0x02, 0x2f, 0xb6, 0x49, 0xf1, 0xc9, 0xf1, 0x45, 0xd4, 0xfb, 0xfb, 0x2b, 0x7c, 0xa5, 0x02, 0xe0, 0x35, 0x2c, 0x74, 0x5d, 0x60, 0xd9, 0xf1, 0x5d, 0x56, 0x7f, 0xfc, 0x4a, 0x59, 0x77, 0x60, 0xee, 0xfe, 0x57, 0xfc, 0x1d, 0xea, 0xdf, 0x1f, 0x68, 0xde, 0xde, 0x41, 0x77, 0x76, 0x92, 0x68, 0x21, 0x7c, 0xe4, 0xfe, 0x51, 0xed, 0x3a, 0x7d, 0x18, 0xdf, 0x1d, 0xff, 0xf9, 0x0c, 0x4f, 0x11, 0x47, 0x02, 0x41, 0x38, 0x19, 0x7f, 0xeb, 0xb7, 0x3c, 0x84, 0x92, 0xca, 0xfa, 0xa4, 0xdc, 0x53, 0x0d, 0xef, 0x2e, 0xa2, 0x02, 0xbd, 0x10, 0xdf, 0xfe, 0xf9, 0x7f, 0xd5, 0x28, 0xfc, 0x81, 0xb4, 0x7f, 0x0f, 0x6d, 0x3f, 0xd2, 0xbd, 0xfd, 0x48, 0x3a, 0xf6, 0xd3, 0xaf, 0xf9, 0xc7, 0xfd, 0xc9, + 0x75, 0x39, 0x48, 0xad, 0xff, 0x39, 0xe9, 0xd8, 0xfe, 0xf8, 0x17, 0xe2, 0x0b, 0x41, 0x54, 0x9b, 0xed, 0xf4, 0x34, 0x3e, 0x7a, 0x46, 0xbe, 0xf7, 0x91, 0xe7, 0xfd, 0x33, 0xfd, 0x4b, 0xf0, 0x4d, 0x95, 0xef, 0x49, 0x07, 0xaa, 0x35, 0x0f, 0xd2, 0x35, 0x7e, 0x12, 0xcf, 0x0f, 0xd2, 0x98, 0xaf, 0x80, 0x6e, 0x9f, 0x4c, 0x60, 0xc8, 0x07, 0xce, 0x78, 0xd6, 0xfd, 0xea, 0x5b, 0x8d, 0x3f, 0xbd, 0x5d, 0x8e, 0x03, 0xa7, 0xfb, 0x1c, 0x5f, 0x99, 0xfa, 0x96, 0xf6, 0x5f, 0xe1, 0xfb, 0x1a, 0xc7, 0xfe, 0xe3, 0xd7, 0xa9, 0x16, 0x5e, 0x05, 0x7d, 0x16, 0x5f, 0xb5, 0xfa, 0x96, 0xe8, 0x7b, 0xa1, 0x31, 0x68, 0xc1, 0x0f, 0xe2, 0x3b, 0x19, 0x67, 0xb7, 0xb2, 0xab, 0x58, 0x2f, 0xc4, 0xef, 0xbf, 0xcd, 0xde, 0x61, 0x9b, 0xc9, 0xba, 0x33, 0xc8, 0xaa, 0x6b, 0xa3, 0xb4, 0x53, 0xda, 0x2b, 0x1d, 0x94, + 0x0b, 0x95, 0x8b, 0x94, 0x8b, 0x95, 0x4b, 0xc8, 0x9e, 0xbb, 0x94, 0xac, 0xb9, 0x61, 0x64, 0xb9, 0xdd, 0x4f, 0x16, 0xdb, 0x03, 0x64, 0xa9, 0x3d, 0x68, 0x5b, 0x63, 0xdb, 0x62, 0x7b, 0x8f, 0x2c, 0xae, 0x9f, 0x6c, 0x27, 0xc8, 0x8e, 0x22, 0xeb, 0x49, 0xed, 0x4f, 0xb6, 0xd2, 0xad, 0xea, 0x38, 0xf5, 0x7e, 0x75, 0xbc, 0x3a, 0x41, 0x9d, 0xa9, 0x3e, 0xa1, 0xce, 0x52, 0x9f, 0x54, 0x67, 0xab, 0x4f, 0xa9, 0x73, 0xd4, 0xa7, 0xd5, 0xb9, 0xea, 0x22, 0xf5, 0x05, 0x75, 0x99, 0xfa, 0x4f, 0x75, 0xb9, 0xfa, 0xb2, 0xba, 0x42, 0x5d, 0xa9, 0xae, 0x52, 0x5f, 0x51, 0x57, 0xab, 0xaf, 0xaa, 0x6b, 0xd4, 0xd7, 0xd4, 0xb5, 0xea, 0xeb, 0xea, 0x3a, 0xf5, 0x0d, 0x75, 0xbd, 0xfa, 0xa6, 0xba, 0x41, 0x7d, 0x4f, 0xdd, 0xaa, 0xbe, 0xaf, 0x7e, 0xa2, 0xee, 0x52, 0x3f, 0x55, 0x77, 0xab, 0x9f, 0xa9, 0x7b, + 0xd4, 0xcf, 0xd5, 0xbd, 0xea, 0xbf, 0xd4, 0x7d, 0xea, 0x17, 0xea, 0x7e, 0xf5, 0xa4, 0x66, 0x13, 0xb6, 0x16, 0x59, 0x57, 0x39, 0x5a, 0x03, 0xb2, 0xae, 0x1a, 0x6a, 0x79, 0xc2, 0xba, 0xd2, 0x4e, 0xe9, 0xcd, 0xf5, 0x16, 0x7a, 0x4b, 0xbd, 0x95, 0x5e, 0xa8, 0xb7, 0xd6, 0xdb, 0xe8, 0x6d, 0x8d, 0x95, 0xe6, 0x46, 0xf3, 0x6d, 0x26, 0xb1, 0x86, 0xb8, 0x4b, 0x15, 0xf7, 0xa9, 0x02, 0xc1, 0xf1, 0xd2, 0xdd, 0xbe, 0xc3, 0x5c, 0x6c, 0x33, 0x91, 0x07, 0xef, 0xb4, 0xb9, 0xb8, 0x67, 0x26, 0x89, 0xbb, 0x66, 0x32, 0xde, 0x6c, 0x73, 0xbc, 0xd3, 0x56, 0xc4, 0xfd, 0x33, 0x9b, 0x68, 0x01, 0x66, 0x47, 0x2c, 0xbd, 0x0a, 0x0b, 0x56, 0x83, 0x05, 0xab, 0xc1, 0x82, 0xd5, 0x61, 0xc1, 0x1a, 0xb0, 0x60, 0x75, 0x58, 0xb0, 0x06, 0xb5, 0xcf, 0x1a, 0xe6, 0x15, 0x6d, 0xc4, 0xd4, 0xc4, 0x1b, 0x6c, 0x61, + 0x8b, 0x3a, 0x6d, 0x27, 0x6c, 0x27, 0x98, 0x17, 0x96, 0xa7, 0x09, 0x9b, 0xd3, 0xa4, 0x76, 0xeb, 0xcf, 0xdc, 0xd6, 0x7b, 0x6c, 0xd8, 0x99, 0x4e, 0xd1, 0x86, 0x64, 0x7f, 0x8e, 0x57, 0xc7, 0x93, 0x7c, 0x82, 0x3a, 0x81, 0xf8, 0x4c, 0xf5, 0x69, 0x92, 0xcc, 0x55, 0xe7, 0x52, 0x9a, 0xda, 0x91, 0x2c, 0xd5, 0x65, 0xea, 0x06, 0xe2, 0xef, 0xa9, 0xef, 0x13, 0xff, 0x44, 0xdd, 0x4f, 0xfc, 0xa4, 0x7a, 0x92, 0x79, 0xa8, 0x6d, 0x6c, 0xc4, 0x85, 0x2d, 0xea, 0xa5, 0x16, 0xca, 0x27, 0x7e, 0x8a, 0x6c, 0x30, 0x95, 0xda, 0xa6, 0x2d, 0x53, 0x61, 0x69, 0x78, 0xf1, 0xa6, 0x97, 0xe3, 0x4d, 0xaf, 0xc2, 0x24, 0x9e, 0x01, 0xac, 0xee, 0x3c, 0x60, 0x75, 0xb5, 0x11, 0x69, 0x63, 0x7d, 0x6d, 0x22, 0x03, 0x91, 0x36, 0x69, 0xc0, 0xed, 0x32, 0x10, 0x69, 0x93, 0x06, 0xf4, 0xae, 0x16, 0x22, 0x6d, 0xd2, + 0x80, 0xe1, 0x65, 0x20, 0xd2, 0x26, 0x0d, 0x94, 0x81, 0x48, 0x9b, 0x34, 0x44, 0xda, 0xd4, 0x04, 0xaa, 0x97, 0x0a, 0x54, 0x2f, 0x06, 0x54, 0x2f, 0x0a, 0x54, 0xaf, 0x06, 0x50, 0xbd, 0x14, 0xa0, 0x7a, 0xe9, 0x40, 0xf5, 0x32, 0xd1, 0x1f, 0x96, 0xd7, 0x58, 0x0b, 0x20, 0x6a, 0xad, 0x80, 0xa8, 0x35, 0x03, 0xa2, 0x56, 0x08, 0x44, 0xad, 0x25, 0x10, 0xb5, 0x36, 0x40, 0xd4, 0x5a, 0x03, 0x51, 0xbb, 0x00, 0x88, 0x5a, 0x6b, 0x20, 0x6a, 0x6d, 0x81, 0xa8, 0x79, 0xe0, 0x9d, 0xe2, 0x03, 0xae, 0xe6, 0x05, 0xae, 0x16, 0x06, 0xae, 0x66, 0xc0, 0x3b, 0xa5, 0x09, 0xd0, 0xb5, 0x08, 0xbc, 0x53, 0x4c, 0x60, 0x6c, 0x61, 0x60, 0x6c, 0x01, 0x60, 0x6c, 0x1a, 0x30, 0x36, 0x0e, 0x8c, 0x2d, 0x08, 0x8c, 0x4d, 0x03, 0xc6, 0xe6, 0x02, 0xc6, 0xa6, 0x01, 0x63, 0xd3, 0x81, 0xb1, 0x69, 0xc0, 0xd8, 0xec, + 0xf0, 0x4e, 0x39, 0x1f, 0x48, 0x9b, 0x0e, 0xa4, 0x4d, 0x03, 0xd2, 0x16, 0x02, 0xd2, 0xa6, 0x01, 0x69, 0xd3, 0x80, 0xb4, 0xf9, 0x81, 0xb4, 0xe9, 0x40, 0xda, 0x34, 0x20, 0x6d, 0x8d, 0x81, 0xb4, 0x75, 0x00, 0xd2, 0x66, 0x03, 0xd2, 0x26, 0x03, 0x69, 0xb3, 0x01, 0x69, 0x93, 0x81, 0xb4, 0x5d, 0x08, 0xa4, 0xad, 0x1d, 0x30, 0xb6, 0x76, 0x40, 0xd4, 0x24, 0x20, 0x6a, 0x19, 0x40, 0xd4, 0x62, 0x89, 0xef, 0xef, 0x08, 0x14, 0x2d, 0x03, 0x28, 0x5a, 0x5d, 0x44, 0xfb, 0xa4, 0x01, 0x4b, 0xcb, 0x40, 0xb4, 0x4f, 0x1a, 0xa2, 0x7d, 0xd2, 0x80, 0xab, 0x65, 0x00, 0x57, 0xab, 0x0b, 0x5c, 0x2d, 0x1d, 0xb8, 0x5a, 0x26, 0x70, 0xb5, 0x0c, 0xc4, 0xfc, 0xa4, 0x01, 0x5d, 0xcb, 0x04, 0xba, 0x16, 0x43, 0xcc, 0x4f, 0x1a, 0x30, 0xb6, 0x74, 0x60, 0x6c, 0x31, 0xc4, 0xfc, 0xa4, 0x01, 0x69, 0xcb, 0x04, + 0x06, 0x56, 0x07, 0x18, 0x58, 0x73, 0x60, 0x60, 0xad, 0x81, 0x81, 0xb5, 0x01, 0x06, 0xd6, 0x1a, 0x18, 0x58, 0x6b, 0x60, 0x60, 0xad, 0x81, 0x81, 0x79, 0x80, 0x81, 0x05, 0x80, 0x81, 0x85, 0x81, 0x81, 0x05, 0x92, 0x7c, 0x63, 0x9c, 0xc0, 0xc0, 0x02, 0xc0, 0xc0, 0x22, 0xc0, 0xc0, 0x9a, 0x02, 0x03, 0xf3, 0x02, 0x03, 0x8b, 0xc0, 0x37, 0xa6, 0x3e, 0x90, 0xb0, 0x30, 0x90, 0x30, 0x27, 0x90, 0xb0, 0x5c, 0x20, 0x61, 0x5e, 0x20, 0x61, 0x1a, 0x90, 0x30, 0x0e, 0x24, 0x4c, 0x03, 0x12, 0x66, 0x07, 0x12, 0xa6, 0x01, 0x09, 0xd3, 0x81, 0x84, 0xd9, 0x81, 0x84, 0x35, 0x02, 0x12, 0xa6, 0x00, 0x09, 0xb3, 0x03, 0x09, 0x0b, 0x01, 0x09, 0xd3, 0x80, 0x84, 0x15, 0x00, 0x09, 0xd3, 0x80, 0x84, 0xb9, 0x80, 0x84, 0x69, 0x40, 0xc2, 0x42, 0x40, 0xc2, 0x34, 0x20, 0x61, 0xf5, 0x80, 0x84, 0xe9, 0x40, + 0xc2, 0x34, 0x20, 0x61, 0x0a, 0x90, 0x30, 0x1d, 0x48, 0x98, 0x0b, 0x48, 0x98, 0x86, 0x79, 0xa4, 0x21, 0xf0, 0xb0, 0x30, 0x66, 0x90, 0x86, 0x40, 0xc5, 0xc2, 0x98, 0x47, 0x72, 0x80, 0x8d, 0x69, 0xc0, 0xc6, 0x34, 0x60, 0x63, 0x1a, 0xb0, 0x31, 0x1d, 0xd8, 0x98, 0x8e, 0x39, 0xa5, 0x01, 0xe6, 0x94, 0x06, 0x98, 0x53, 0xf2, 0x30, 0x9b, 0xe4, 0x21, 0x0e, 0x2a, 0x0d, 0x11, 0x50, 0x69, 0x40, 0xc2, 0x32, 0x80, 0x84, 0x15, 0x22, 0x02, 0x2a, 0x0d, 0xb1, 0x4f, 0x69, 0x88, 0x7a, 0x4a, 0x03, 0x12, 0x16, 0x04, 0x12, 0xa6, 0xc3, 0x73, 0xc6, 0xc2, 0xc3, 0xc2, 0xc0, 0xc3, 0x5a, 0x03, 0x0f, 0x6b, 0x8f, 0x39, 0xa8, 0x39, 0xe2, 0xa3, 0xd2, 0x80, 0x81, 0x9d, 0x87, 0x99, 0xc8, 0x0d, 0x0c, 0xec, 0x3c, 0xcc, 0x47, 0x6e, 0x60, 0x60, 0xe7, 0x01, 0x03, 0xab, 0x8d, 0x28, 0x29, 0xeb, 0xbb, 0x1d, + 0x19, 0x98, 0xa1, 0xc2, 0x98, 0xa1, 0x9a, 0x03, 0x09, 0xcb, 0x40, 0xc4, 0x54, 0x1a, 0x22, 0xa6, 0xd2, 0x10, 0x31, 0x95, 0x86, 0x88, 0xa9, 0x34, 0x44, 0x4c, 0xa5, 0x01, 0x27, 0xcb, 0x40, 0xc4, 0x54, 0x1a, 0x22, 0xa6, 0xd2, 0x10, 0x31, 0x95, 0x86, 0x88, 0xa9, 0x34, 0x44, 0x4c, 0xa5, 0x01, 0x45, 0xab, 0x05, 0x14, 0xad, 0x16, 0x22, 0xa6, 0xd2, 0x10, 0x31, 0x95, 0x86, 0x88, 0xa9, 0x34, 0x44, 0x4c, 0xa5, 0x01, 0x5d, 0xcb, 0xc0, 0x8c, 0xd6, 0x1c, 0xe8, 0x5a, 0x18, 0xe8, 0x5a, 0x06, 0xd0, 0xb5, 0xe6, 0x40, 0xd7, 0x9a, 0x03, 0x5d, 0x6b, 0x0e, 0x74, 0x2d, 0x0c, 0x74, 0x2d, 0x03, 0x51, 0x55, 0x69, 0x88, 0xaa, 0x4a, 0x43, 0x54, 0x55, 0x4d, 0x44, 0x55, 0xd5, 0x04, 0xea, 0x96, 0x0a, 0xd4, 0x2d, 0x00, 0xd4, 0x2d, 0x15, 0xa8, 0x5b, 0x2a, 0x50, 0xb7, 0x18, 0x50, 0xb7, 0x28, 0x50, + 0xb7, 0x1a, 0x40, 0xdd, 0x9c, 0x40, 0xdd, 0x52, 0x80, 0xba, 0xa5, 0x03, 0x75, 0xcb, 0x02, 0xea, 0x96, 0x05, 0xd4, 0x2d, 0x1d, 0xa8, 0x5b, 0x56, 0xc2, 0xcb, 0x48, 0xa0, 0x6e, 0x99, 0x40, 0xdd, 0x32, 0x81, 0xb7, 0x39, 0x80, 0xb4, 0x39, 0x80, 0xb1, 0x39, 0x80, 0xae, 0xe9, 0x40, 0xd7, 0x5a, 0x03, 0x51, 0xd3, 0x81, 0xa2, 0xe9, 0x40, 0xd1, 0x74, 0x20, 0x67, 0x3a, 0xb0, 0x31, 0x1d, 0xa8, 0x58, 0x63, 0x78, 0x1c, 0xe5, 0x03, 0x1b, 0xd3, 0xe0, 0x71, 0x94, 0x0f, 0x8f, 0xa3, 0x7c, 0xe0, 0x64, 0x2d, 0x81, 0x93, 0x69, 0xc0, 0xc3, 0x5a, 0x03, 0x0f, 0xd3, 0x81, 0x84, 0xb5, 0x06, 0x12, 0xa6, 0x03, 0x03, 0xd3, 0x80, 0x7e, 0x69, 0x40, 0xb0, 0x14, 0xa0, 0x56, 0x2a, 0xf0, 0x2a, 0x15, 0x48, 0x55, 0x0b, 0x20, 0x55, 0xad, 0x80, 0x54, 0x35, 0x03, 0x52, 0xd5, 0x0c, 0x48, 0x55, 0x21, 0x90, + 0xaa, 0x42, 0x20, 0x55, 0x6d, 0x80, 0x54, 0x5d, 0x00, 0xa4, 0xaa, 0x2d, 0x90, 0x2a, 0x0f, 0x7c, 0x93, 0x7c, 0x78, 0x8a, 0x34, 0x07, 0x5e, 0x65, 0x00, 0xaf, 0x8a, 0xc0, 0x43, 0xc9, 0x84, 0x87, 0x52, 0x13, 0x60, 0x57, 0x1a, 0xb0, 0x2b, 0x0e, 0xec, 0x2a, 0x08, 0xec, 0x4a, 0x03, 0x76, 0xa5, 0x01, 0xbb, 0x0a, 0x01, 0xbb, 0xd2, 0x80, 0x5d, 0x59, 0x3e, 0x88, 0xe7, 0x03, 0xb5, 0xca, 0x00, 0x6a, 0x15, 0x03, 0x52, 0xd5, 0x1e, 0x48, 0x55, 0x1d, 0x20, 0x55, 0x75, 0x10, 0x09, 0x96, 0x06, 0xcf, 0xa5, 0xfa, 0x40, 0xad, 0xc2, 0x40, 0xad, 0xea, 0x01, 0xb5, 0xd2, 0xf1, 0x3c, 0x6b, 0x88, 0xe7, 0x59, 0x0e, 0xa2, 0xc5, 0xd2, 0x98, 0x44, 0x4f, 0x4a, 0xf1, 0x45, 0xd1, 0xfe, 0xfa, 0x0a, 0x7a, 0x1a, 0x5c, 0x80, 0xc8, 0x92, 0x9f, 0x68, 0x1d, 0xb6, 0x93, 0xe8, 0xd9, 0xf8, 0x97, 0xb4, 0xfa, + 0xf9, 0x84, 0x7e, 0x6f, 0x8a, 0xbf, 0x41, 0xdb, 0x32, 0xb2, 0x43, 0xbe, 0x8f, 0xbf, 0x4e, 0xbf, 0xc5, 0xd7, 0x33, 0x4e, 0x52, 0x8e, 0xcf, 0xe3, 0x1f, 0xff, 0xa7, 0x2d, 0x73, 0xf1, 0xa5, 0xc1, 0x73, 0x1e, 0x6b, 0x5b, 0xee, 0xc9, 0x40, 0x76, 0xee, 0x21, 0x5a, 0x3d, 0x6e, 0xa4, 0x95, 0xda, 0x1e, 0xb2, 0x88, 0x76, 0xd0, 0xaf, 0x77, 0xab, 0x8f, 0xd5, 0x88, 0x2f, 0xdd, 0x91, 0x2d, 0x24, 0xbe, 0x3d, 0xd7, 0x83, 0xce, 0x6f, 0x1e, 0x9f, 0x4d, 0x77, 0x53, 0xfe, 0x8d, 0xd3, 0x93, 0x54, 0xd2, 0xe7, 0xd6, 0x17, 0x19, 0x99, 0x21, 0xae, 0x28, 0xbe, 0x7f, 0x57, 0xd5, 0xcb, 0x5e, 0x5c, 0xd1, 0xb2, 0x5d, 0xab, 0x79, 0xc5, 0x7a, 0xf1, 0xb4, 0x78, 0x7d, 0xda, 0x5f, 0x12, 0x0f, 0xc7, 0x53, 0xe3, 0xb7, 0xc7, 0x87, 0xc7, 0x5b, 0x9d, 0xf6, 0xe7, 0x8f, 0x3b, 0x89, 0x17, 0xc3, 0xeb, 0xca, + 0x83, 0xef, 0x47, 0xce, 0xb6, 0xbe, 0x9c, 0x60, 0x7d, 0x97, 0x30, 0x91, 0x6b, 0x7c, 0xbc, 0x2f, 0xf6, 0x7d, 0xc8, 0x86, 0x18, 0x47, 0xfd, 0x70, 0x05, 0x95, 0x38, 0x38, 0xde, 0x89, 0xec, 0xc0, 0xae, 0x54, 0xea, 0xfa, 0xb3, 0xdc, 0xe3, 0xdb, 0x89, 0x75, 0xbd, 0x96, 0x40, 0x99, 0x16, 0x26, 0x1d, 0x5d, 0x02, 0xa4, 0xed, 0xb4, 0x45, 0xe4, 0x2c, 0xb7, 0x40, 0x92, 0xf2, 0x6c, 0x4d, 0x7c, 0x89, 0x4f, 0x7c, 0xd1, 0x75, 0x75, 0xbc, 0x34, 0x3e, 0x0f, 0x5f, 0x44, 0x1c, 0x0d, 0x8f, 0x98, 0x67, 0xc8, 0x5e, 0xfc, 0xd5, 0x58, 0x42, 0xf1, 0xc5, 0xbd, 0x73, 0x1e, 0x1b, 0x90, 0xd8, 0xf7, 0x88, 0x47, 0xe2, 0xd3, 0xe8, 0xce, 0xd3, 0xf1, 0xad, 0x94, 0xd6, 0xf1, 0x31, 0xf1, 0xfe, 0xf1, 0x0c, 0xab, 0x97, 0x85, 0x1d, 0x6e, 0x61, 0x51, 0xc9, 0x7e, 0x0e, 0xf1, 0x4b, 0xe3, 0x4b, 0xad, 0x2f, + 0xa7, 0xe2, 0xd7, 0x25, 0xf1, 0x46, 0xf1, 0x8b, 0x50, 0x1f, 0x99, 0xce, 0xbb, 0x56, 0xd8, 0xca, 0x49, 0x57, 0xa9, 0x2d, 0xbe, 0x4b, 0x11, 0xbf, 0x12, 0x88, 0xc8, 0x40, 0x58, 0xfa, 0x8f, 0x25, 0x97, 0x8c, 0xf4, 0x58, 0xcb, 0x37, 0x4a, 0xd4, 0x08, 0x5f, 0x09, 0x7d, 0x14, 0xa5, 0xdd, 0x16, 0x7f, 0x53, 0xf8, 0xd3, 0x90, 0xc6, 0xff, 0x6e, 0xdf, 0xbc, 0x72, 0x0f, 0xae, 0x6a, 0xe7, 0xaf, 0xd0, 0xa0, 0xf2, 0x6f, 0xdd, 0x96, 0xdb, 0xd8, 0x64, 0x81, 0xfc, 0xe6, 0xd8, 0xaa, 0x4e, 0x8c, 0x25, 0xd9, 0x34, 0x3b, 0xab, 0x5d, 0x9b, 0x3f, 0x64, 0x7b, 0x25, 0xf4, 0xec, 0x67, 0xb4, 0x78, 0xd9, 0xbf, 0x63, 0x0f, 0x9e, 0xf3, 0xc8, 0x17, 0xbf, 0xcf, 0x5e, 0x2d, 0x47, 0xbd, 0x61, 0x35, 0x1f, 0x4c, 0xf6, 0xe7, 0x89, 0xef, 0x2a, 0xff, 0x9a, 0xec, 0x6f, 0xd9, 0xd0, 0xff, 0x19, 0xac, 0x32, + 0xbe, 0x85, 0x6c, 0xff, 0xf7, 0xff, 0x4b, 0xf6, 0x74, 0x59, 0x05, 0xd6, 0xb3, 0xfd, 0xb4, 0x0f, 0x0c, 0xcd, 0x55, 0xff, 0xaa, 0xce, 0x3d, 0x26, 0xc7, 0x99, 0xfd, 0xda, 0x1b, 0x05, 0xa4, 0x76, 0x26, 0xf7, 0x48, 0xfc, 0xbd, 0xc4, 0x15, 0xbf, 0x49, 0x44, 0x0f, 0xee, 0x3a, 0xe7, 0x15, 0x2b, 0x7f, 0xe3, 0x67, 0xc7, 0xef, 0x18, 0x1d, 0xfb, 0x2b, 0xf5, 0xa3, 0xf8, 0xea, 0xaf, 0xf5, 0xf5, 0xe8, 0x6f, 0x7f, 0xe5, 0xec, 0x0f, 0xaa, 0xd1, 0x66, 0x7b, 0x92, 0xbf, 0x7e, 0xfb, 0xdb, 0x73, 0x3a, 0xe2, 0x22, 0xad, 0xb7, 0x03, 0x73, 0x69, 0x66, 0x5c, 0x79, 0xae, 0x16, 0xfa, 0x35, 0xd4, 0xa1, 0xaa, 0xb7, 0xce, 0xaf, 0x8e, 0x3a, 0xce, 0xae, 0x25, 0x9b, 0xee, 0x4b, 0xf6, 0x33, 0xd9, 0x64, 0xc2, 0xff, 0x3f, 0x9d, 0xec, 0xa5, 0x06, 0xf2, 0x7c, 0x61, 0x51, 0x90, 0x3d, 0xb1, 0x98, 0xec, + 0x85, 0xf5, 0x64, 0x27, 0x94, 0x92, 0x05, 0x40, 0xab, 0x7f, 0x5a, 0xbf, 0xd3, 0x4a, 0x9d, 0x56, 0xe2, 0x92, 0x58, 0x7b, 0x8b, 0x15, 0xb6, 0x58, 0x3b, 0xd3, 0x5a, 0xf7, 0x47, 0xdb, 0x71, 0x5a, 0x13, 0x36, 0xd6, 0x9a, 0x68, 0x4d, 0xb5, 0xe6, 0x5a, 0x0b, 0x5a, 0xd7, 0xdd, 0x49, 0xab, 0xb4, 0x7b, 0x69, 0x8d, 0x36, 0x41, 0x7b, 0x50, 0x9b, 0xa8, 0x3d, 0xa4, 0x4d, 0xd2, 0x1e, 0x16, 0xab, 0x34, 0x5a, 0xa3, 0x3d, 0x2d, 0xd6, 0x63, 0xb4, 0x1a, 0x5b, 0x4f, 0xab, 0xb0, 0x8f, 0x68, 0xdd, 0xf5, 0x15, 0xad, 0xb4, 0xda, 0x8b, 0x35, 0x14, 0xad, 0xa0, 0x6e, 0xd1, 0x87, 0xea, 0xb7, 0xea, 0x25, 0xfa, 0x6d, 0xfa, 0x30, 0xfd, 0x76, 0x7d, 0x38, 0xad, 0xa3, 0xee, 0xa2, 0xf5, 0xd3, 0x1e, 0xb1, 0x6e, 0xa2, 0x55, 0xd3, 0x77, 0xfa, 0x11, 0xb1, 0x22, 0xa2, 0x55, 0xce, 0x07, 0xb4, 0x36, 0x59, + 0x49, 0x2b, 0x90, 0xd5, 0xb4, 0xee, 0x90, 0xc8, 0x46, 0x15, 0x76, 0xa9, 0x03, 0xb6, 0xa8, 0x03, 0x56, 0xa2, 0x13, 0x36, 0xa1, 0x13, 0xb6, 0x9f, 0x13, 0x56, 0x9f, 0x93, 0xee, 0x69, 0x3e, 0x73, 0xd0, 0x7d, 0x2d, 0x22, 0x2e, 0x6c, 0x24, 0x2b, 0x4e, 0x40, 0x86, 0x2d, 0x64, 0x87, 0x2d, 0xa4, 0xc0, 0xc2, 0x51, 0x60, 0xa5, 0xb8, 0x61, 0x93, 0x58, 0x1e, 0xf9, 0x4e, 0x58, 0x1d, 0x4e, 0xd8, 0x1b, 0x6e, 0xba, 0xeb, 0x4e, 0xcc, 0x43, 0xf7, 0xdd, 0x9d, 0xf8, 0xe5, 0x64, 0x39, 0x38, 0xb1, 0xd6, 0xd7, 0xb0, 0xbe, 0xd7, 0xb1, 0xbe, 0x77, 0x50, 0x3b, 0x34, 0x61, 0x9c, 0xda, 0xa2, 0x29, 0x93, 0xa8, 0x3d, 0x9a, 0x53, 0xba, 0x85, 0xd6, 0x82, 0xd2, 0x62, 0xbd, 0x2b, 0x63, 0x45, 0xab, 0x50, 0xfb, 0x8c, 0x65, 0x36, 0x6a, 0x9f, 0x47, 0x88, 0x8b, 0x95, 0xab, 0x3d, 0xf1, 0x76, 0xd8, 0x7a, + 0x2f, 0x2c, 0x56, 0xab, 0xd6, 0xbb, 0x60, 0x0d, 0xab, 0x55, 0x6b, 0x9d, 0x6a, 0x60, 0x9d, 0x6a, 0xbd, 0xe1, 0x35, 0xb0, 0x42, 0x35, 0xb1, 0x06, 0x35, 0xb1, 0x06, 0x75, 0x60, 0x0d, 0xea, 0xc4, 0x1a, 0xd4, 0x81, 0x35, 0xa8, 0x13, 0x6b, 0x50, 0x17, 0xd6, 0xa0, 0x2e, 0xac, 0x41, 0xdd, 0x58, 0x83, 0x7a, 0xb0, 0x06, 0xf5, 0x60, 0x7d, 0xe9, 0xc0, 0xfa, 0xcf, 0x89, 0xd5, 0x9e, 0x03, 0x2d, 0xea, 0xa4, 0x36, 0x7d, 0x03, 0x78, 0x40, 0x01, 0xf0, 0x80, 0x54, 0xe0, 0x01, 0xb5, 0x81, 0x07, 0x34, 0x01, 0x1e, 0x50, 0x13, 0x78, 0x40, 0x2d, 0xb4, 0x7b, 0x0a, 0xf0, 0x80, 0x4c, 0xe0, 0x01, 0x31, 0xf4, 0x41, 0x26, 0xf0, 0x80, 0xf3, 0x80, 0x07, 0x78, 0x81, 0x07, 0x04, 0x80, 0x07, 0x44, 0x80, 0x07, 0x34, 0x02, 0x1e, 0xe0, 0x04, 0x1e, 0xa0, 0x00, 0x09, 0x08, 0x03, 0x09, 0x68, 0x04, 0x24, + 0xc0, 0x01, 0x24, 0xc0, 0x0f, 0x24, 0xc0, 0x06, 0x24, 0x80, 0x03, 0x09, 0xf0, 0x03, 0x09, 0xb0, 0x03, 0x09, 0xf0, 0x27, 0xc5, 0xa9, 0xf8, 0x81, 0x04, 0x34, 0x04, 0x12, 0x90, 0x97, 0x14, 0xa7, 0xe2, 0x07, 0x12, 0xd0, 0x00, 0x48, 0x80, 0x1f, 0x48, 0x80, 0x1f, 0xda, 0x20, 0x27, 0xc5, 0xa9, 0xf8, 0x81, 0x04, 0x04, 0x81, 0x04, 0x68, 0x40, 0x02, 0x9a, 0x02, 0x09, 0x68, 0x06, 0x24, 0xa0, 0x29, 0x90, 0x80, 0x66, 0x40, 0x02, 0x42, 0x40, 0x02, 0x32, 0xe0, 0x73, 0x93, 0x03, 0x3c, 0x20, 0x03, 0x3e, 0x37, 0x39, 0xf0, 0xb9, 0x49, 0x87, 0xcf, 0x4d, 0x3a, 0x10, 0x82, 0xf3, 0x61, 0x97, 0x17, 0xc0, 0x2e, 0xcf, 0x84, 0x5d, 0x9e, 0x02, 0xbb, 0x3c, 0x13, 0x3a, 0x97, 0x09, 0xbb, 0x3c, 0x13, 0x76, 0xb9, 0x17, 0x76, 0xb9, 0x03, 0x76, 0x79, 0x23, 0xe8, 0xa2, 0x03, 0xba, 0xe8, 0x82, 0x2e, + 0x9a, 0xb0, 0xcb, 0x1d, 0xb0, 0xc8, 0xeb, 0xc2, 0x22, 0xb7, 0x6c, 0xf1, 0x7c, 0xd8, 0xe2, 0x8d, 0xa0, 0xa9, 0x26, 0x6c, 0xf1, 0x2c, 0xd8, 0xe2, 0x11, 0xd8, 0xe2, 0x7e, 0xd8, 0xe2, 0x36, 0xd8, 0xe2, 0x7e, 0xd8, 0xe2, 0x0d, 0x61, 0x8b, 0xfb, 0x93, 0xe2, 0x54, 0x1a, 0xc2, 0x16, 0xaf, 0x07, 0x2d, 0x8f, 0xc2, 0x16, 0x6f, 0x08, 0x5b, 0xbc, 0x01, 0x6c, 0x71, 0x3f, 0xf4, 0xbe, 0x3e, 0x6c, 0x71, 0x3f, 0x6c, 0x71, 0x3b, 0x6c, 0x71, 0x3f, 0x6c, 0xf1, 0x06, 0xb0, 0xc5, 0xfd, 0x18, 0x15, 0x52, 0x52, 0x9c, 0x8a, 0x1f, 0x63, 0x23, 0x9a, 0x14, 0xa7, 0x62, 0x87, 0x2d, 0xee, 0x87, 0x2d, 0x5e, 0x07, 0xb6, 0x78, 0x23, 0xd8, 0xe2, 0x75, 0x60, 0x8b, 0x37, 0x82, 0x2d, 0x6e, 0xc0, 0x16, 0xf7, 0xc3, 0x16, 0xf7, 0xc3, 0x16, 0xf7, 0x9f, 0x11, 0xa7, 0x52, 0x03, 0xb6, 0x78, 0x0d, 0xd8, 0xe2, + 0xd9, 0xc0, 0xf7, 0x72, 0x61, 0x91, 0x67, 0x03, 0xdf, 0xcb, 0x85, 0x15, 0x5e, 0x13, 0x36, 0x37, 0x4f, 0x8a, 0x56, 0x09, 0xc0, 0xe6, 0x6e, 0x84, 0x31, 0x99, 0x89, 0x31, 0xd9, 0x18, 0x36, 0x77, 0x01, 0xec, 0x6c, 0xcb, 0xcb, 0x44, 0x87, 0x25, 0xdd, 0x08, 0x96, 0x74, 0x01, 0xec, 0xda, 0x02, 0xd8, 0xb5, 0x8d, 0x60, 0xd1, 0x16, 0xc0, 0xa2, 0x2d, 0x80, 0x45, 0x5b, 0x00, 0x8b, 0xb6, 0x11, 0x46, 0xaf, 0x03, 0xa3, 0xd7, 0x84, 0x0d, 0x9a, 0x06, 0x1b, 0x34, 0x0d, 0xd6, 0x67, 0x1a, 0xc6, 0xb0, 0xeb, 0x8c, 0x31, 0xac, 0x26, 0xc5, 0xb8, 0x64, 0x56, 0x23, 0xc6, 0x25, 0x08, 0x8b, 0xd3, 0x0d, 0x8b, 0xd3, 0x0f, 0x8b, 0xd3, 0x0d, 0x8b, 0xd3, 0x0d, 0x8b, 0xb3, 0x16, 0x46, 0xbb, 0x1f, 0xa3, 0x3d, 0x33, 0x29, 0xd2, 0x25, 0x33, 0x29, 0xd2, 0xc5, 0x8f, 0xd1, 0xee, 0xc7, 0x68, 0x8f, 0x62, + 0xb4, 0xfb, 0x30, 0xda, 0x7d, 0xb0, 0x38, 0x53, 0x61, 0x71, 0xd6, 0x86, 0xc5, 0xd9, 0x04, 0x16, 0x67, 0x13, 0x58, 0x9c, 0x35, 0x61, 0x71, 0xd6, 0xc4, 0x8c, 0x90, 0x02, 0x8b, 0x33, 0x06, 0x8b, 0xf3, 0x3c, 0x58, 0x9c, 0x5e, 0x58, 0x9c, 0x01, 0x58, 0x9c, 0x05, 0xb0, 0x38, 0x9d, 0xb0, 0x35, 0xc3, 0xb0, 0x35, 0x15, 0xd8, 0x9a, 0x7e, 0xd8, 0x9a, 0x36, 0xd8, 0x9a, 0x1c, 0xb6, 0xa6, 0x1f, 0xb6, 0xa6, 0x1f, 0xb6, 0x66, 0x03, 0xd8, 0x9a, 0x7e, 0xcc, 0x35, 0x32, 0x6c, 0xcd, 0x3c, 0xf8, 0x49, 0xa4, 0x63, 0xde, 0x69, 0x0c, 0x9b, 0x32, 0x1f, 0x36, 0x65, 0x23, 0xd8, 0x94, 0x52, 0x52, 0x34, 0x4c, 0x1d, 0xd8, 0x94, 0x06, 0x3d, 0xab, 0x1c, 0x6c, 0x13, 0xf5, 0xf3, 0x00, 0x75, 0x10, 0xcd, 0x50, 0xb5, 0xec, 0x75, 0xec, 0x2d, 0x19, 0xb3, 0xb7, 0xb6, 0xd3, 0xf8, 0xb4, 0x5f, 0x6c, 0xef, + 0xc9, 0xa2, 0xf6, 0x83, 0xf6, 0x6f, 0x58, 0x1d, 0xfb, 0x11, 0xfb, 0x8f, 0xac, 0xbe, 0xfd, 0xa4, 0xaa, 0x50, 0xef, 0xda, 0x55, 0x93, 0x35, 0x57, 0x9d, 0xaa, 0x87, 0xb5, 0x51, 0xc3, 0x6a, 0x84, 0x75, 0x50, 0x6b, 0xa9, 0xf5, 0x58, 0x47, 0x53, 0x36, 0x65, 0xd6, 0xdd, 0xb4, 0x9b, 0x76, 0x76, 0xb9, 0xa9, 0x9b, 0x3a, 0x2b, 0x36, 0xe9, 0x8f, 0x5d, 0x61, 0xba, 0x4c, 0x17, 0xeb, 0x61, 0xfa, 0x4d, 0x3f, 0xbb, 0xd2, 0x0c, 0x9b, 0x61, 0xd6, 0xd3, 0xcc, 0x35, 0x73, 0xd9, 0x55, 0x66, 0x23, 0xb3, 0x11, 0xeb, 0x65, 0x16, 0x98, 0x05, 0xec, 0x6a, 0xf3, 0x02, 0xf3, 0x02, 0xd6, 0xdb, 0x6c, 0x69, 0xb6, 0x64, 0xd7, 0x98, 0xad, 0xcd, 0xd6, 0xac, 0x8f, 0xd9, 0xd1, 0xec, 0xc8, 0xae, 0x35, 0x2f, 0x36, 0x2f, 0x66, 0x7d, 0xcd, 0xfb, 0xcc, 0xfb, 0xd8, 0x5f, 0xcc, 0xf1, 0xe6, 0x78, 0xd6, 0xcf, + 0x7c, 0xc8, 0x7c, 0x88, 0x5d, 0x67, 0xbe, 0x6e, 0xbe, 0xce, 0xfa, 0x9b, 0xeb, 0xcd, 0xf5, 0xec, 0x7a, 0xf3, 0x2d, 0xf3, 0x2d, 0x36, 0x80, 0x6a, 0xbf, 0x1e, 0xdf, 0x00, 0x66, 0xf8, 0x92, 0x58, 0x3d, 0x76, 0x3e, 0x6b, 0xce, 0xea, 0xe3, 0x5b, 0x61, 0x0d, 0xf1, 0x95, 0xb0, 0x3c, 0xd6, 0x9e, 0xa8, 0x11, 0xeb, 0x4a, 0x94, 0x0f, 0xd4, 0xbc, 0x31, 0x3d, 0xa7, 0xaf, 0xa3, 0xd9, 0x77, 0x00, 0x51, 0x73, 0x7c, 0xf5, 0xb7, 0x05, 0x70, 0xf4, 0x96, 0xf8, 0xba, 0x6f, 0x21, 0x1b, 0xc9, 0xee, 0x62, 0xad, 0xd9, 0xdd, 0x44, 0x6d, 0xd9, 0xc3, 0x6c, 0x32, 0x6b, 0xc7, 0xde, 0x62, 0x5b, 0x59, 0x07, 0x56, 0x4a, 0x54, 0xc4, 0x3e, 0x24, 0xba, 0x94, 0x7d, 0x44, 0xd4, 0x8d, 0x7d, 0xcc, 0x76, 0xb2, 0xcb, 0xd8, 0x2e, 0xa2, 0xcb, 0xd9, 0x67, 0x6c, 0x0f, 0x2b, 0x66, 0x07, 0x89, 0x7a, 0xb0, 0xc3, + 0xdc, 0xcd, 0xae, 0xc4, 0x2c, 0x7b, 0x07, 0xe6, 0xd1, 0x3b, 0x31, 0x3b, 0x8e, 0xe4, 0xb7, 0xf2, 0x5b, 0xd9, 0x5d, 0xfc, 0x75, 0xfe, 0x3a, 0x1b, 0xc5, 0xf7, 0xf3, 0x2f, 0xd9, 0xdd, 0x52, 0x5b, 0xe9, 0x0e, 0x76, 0x8f, 0x74, 0xa7, 0x34, 0x92, 0x2d, 0x87, 0x8f, 0xdf, 0x0a, 0xe9, 0x1e, 0xe9, 0x1e, 0xb6, 0x52, 0x9a, 0x20, 0x3d, 0xc8, 0x56, 0x49, 0x0f, 0x49, 0x0f, 0xb1, 0xd5, 0xd2, 0x64, 0x69, 0x32, 0x7b, 0x55, 0x7a, 0x54, 0x7a, 0x94, 0xad, 0xc1, 0xd7, 0x71, 0x5f, 0x93, 0x66, 0x4b, 0xb3, 0xd9, 0xeb, 0xd2, 0x8b, 0xd2, 0x8b, 0x6c, 0x9d, 0xb4, 0x54, 0xfa, 0x07, 0x7b, 0x43, 0x5a, 0x25, 0xbd, 0xc2, 0xde, 0x94, 0x5e, 0x93, 0xde, 0x60, 0x6f, 0x49, 0x6f, 0x4b, 0x6f, 0xb3, 0xcd, 0xd2, 0x16, 0x69, 0x0b, 0x7b, 0x57, 0xda, 0x26, 0x7d, 0xc8, 0xb6, 0x48, 0x1f, 0x49, 0x1f, 0xb3, 0xad, + 0xd2, 0x5e, 0x69, 0x2f, 0xfb, 0x40, 0x3a, 0x20, 0x7d, 0xc5, 0xb6, 0x49, 0x5f, 0x4b, 0x5f, 0xb3, 0xed, 0x52, 0x99, 0x54, 0xc6, 0x3e, 0x92, 0xbe, 0x93, 0x8e, 0xb2, 0x1d, 0xd2, 0x31, 0xe9, 0x18, 0xdb, 0x25, 0x1d, 0x97, 0xe2, 0xec, 0x53, 0x59, 0x92, 0x25, 0xf6, 0x85, 0x6c, 0x93, 0x6d, 0x6c, 0xbf, 0xac, 0xc9, 0x1a, 0xfb, 0x92, 0xba, 0xd8, 0xc5, 0x0e, 0xc8, 0x01, 0x39, 0xc2, 0x0e, 0xc9, 0x35, 0xe4, 0x54, 0xf6, 0x9d, 0x9c, 0x29, 0x67, 0xb2, 0xa3, 0x72, 0x96, 0x9c, 0xc5, 0x7e, 0x90, 0xaf, 0x92, 0xaf, 0x62, 0xc7, 0xe4, 0xab, 0xe5, 0xab, 0xd9, 0x8f, 0xf2, 0xf5, 0xf2, 0x00, 0x76, 0x5c, 0xbe, 0x45, 0xbe, 0x85, 0x9d, 0x90, 0xef, 0x90, 0xef, 0x60, 0x3f, 0xcb, 0x77, 0xcb, 0x77, 0xb3, 0x93, 0xf2, 0x93, 0xf2, 0x3c, 0xf6, 0x8b, 0xfc, 0x9c, 0xfc, 0x1c, 0xb7, 0x89, 0x59, 0x9c, 0xdb, + 0xe5, 0x25, 0xf2, 0x12, 0xae, 0xca, 0xff, 0x90, 0xff, 0xc1, 0x35, 0x79, 0xb9, 0xfc, 0x2f, 0xae, 0x8b, 0x39, 0x8f, 0x9f, 0xaf, 0x38, 0x14, 0x07, 0x6f, 0xa6, 0x78, 0x15, 0x1f, 0xbf, 0x40, 0x09, 0x2a, 0x41, 0xde, 0x42, 0x09, 0x2b, 0x11, 0xde, 0x52, 0x49, 0x51, 0x52, 0x78, 0xa1, 0x52, 0x4b, 0x49, 0xe7, 0xad, 0x95, 0x46, 0xca, 0xf9, 0xbc, 0xbd, 0xd2, 0x4a, 0x29, 0xe4, 0x5d, 0x68, 0x5e, 0xec, 0xc2, 0x69, 0xf6, 0x53, 0xba, 0xf1, 0x2b, 0x95, 0x62, 0xa5, 0x98, 0xf7, 0x16, 0xef, 0x34, 0xf8, 0x35, 0xe2, 0xdb, 0xbf, 0xbc, 0x8f, 0x2d, 0x6a, 0x8b, 0xf2, 0x6b, 0x6d, 0x35, 0x6d, 0xe7, 0xf1, 0xbe, 0xb6, 0xda, 0xb6, 0xda, 0xfc, 0x7a, 0xdb, 0x55, 0xb6, 0xab, 0xf8, 0x00, 0xf1, 0x36, 0x83, 0xdf, 0x60, 0x7b, 0xc3, 0xb6, 0x81, 0x0f, 0xb4, 0x7d, 0x6a, 0xdb, 0xc3, 0x07, 0xdb, 0xf6, 0xdb, + 0xbe, 0xe4, 0x43, 0x6d, 0x5f, 0xd9, 0x0e, 0xf1, 0x12, 0x9a, 0xf9, 0x8e, 0xf1, 0xe1, 0xe2, 0x4d, 0x10, 0x1f, 0x81, 0xcf, 0x9e, 0x8f, 0xb4, 0xbb, 0xec, 0x2e, 0x7e, 0x97, 0xc0, 0x19, 0xf9, 0x28, 0x7b, 0xa6, 0x3d, 0x8b, 0xdf, 0x2d, 0xfc, 0xee, 0xf8, 0x18, 0xb5, 0xbe, 0x7a, 0x05, 0xbf, 0x97, 0x66, 0xc1, 0x11, 0x7c, 0x91, 0x3a, 0x52, 0x7d, 0x95, 0x6f, 0x50, 0xdf, 0x52, 0x4f, 0x48, 0x86, 0x7a, 0x52, 0x33, 0xa5, 0x42, 0xcd, 0xa9, 0xcd, 0x92, 0x8a, 0xb4, 0xd9, 0xda, 0x1c, 0x69, 0xb7, 0xf6, 0x8c, 0xf6, 0x92, 0xf4, 0x39, 0xcd, 0x67, 0x2f, 0x4b, 0x87, 0xb5, 0x95, 0xda, 0x4a, 0xe9, 0xa8, 0xb6, 0x5a, 0x7b, 0x43, 0xfa, 0x41, 0x7b, 0x4b, 0xfb, 0x40, 0xfa, 0x45, 0xfb, 0x50, 0xdb, 0x2e, 0x1b, 0xda, 0x0e, 0xed, 0x63, 0xd9, 0xa1, 0x7d, 0xa2, 0x7d, 0x2e, 0xbb, 0xb4, 0x7d, 0xda, 0x17, + 0x72, 0x50, 0x3b, 0xa0, 0x7d, 0x23, 0x87, 0xb5, 0xb8, 0xae, 0xc8, 0x35, 0x75, 0x8f, 0x9e, 0x26, 0x67, 0xe8, 0xe9, 0x7a, 0xba, 0x5c, 0x40, 0xf3, 0x59, 0x96, 0xdc, 0x44, 0x7c, 0x03, 0x4d, 0x6e, 0xa6, 0xe7, 0xe8, 0x8d, 0xe4, 0x0b, 0xf4, 0xc6, 0x7a, 0x33, 0xb9, 0xb5, 0xf8, 0xe2, 0xae, 0x7c, 0xa1, 0xde, 0x41, 0xbf, 0x50, 0x2e, 0xd2, 0x2f, 0xd2, 0x2f, 0x96, 0xbb, 0xe9, 0x97, 0xe8, 0x45, 0xf2, 0xe5, 0xfa, 0xe5, 0x7a, 0x4f, 0xf9, 0x0a, 0xbd, 0x97, 0x4e, 0xbd, 0x44, 0xeb, 0xc3, 0x6b, 0xe4, 0x6b, 0xf4, 0xbe, 0xfa, 0x00, 0xb9, 0x8f, 0x3e, 0x50, 0x1f, 0x29, 0x5f, 0x2f, 0xbc, 0xcb, 0xe4, 0x11, 0xfa, 0xbd, 0xfa, 0x58, 0x79, 0xa4, 0x7e, 0xbf, 0x7e, 0xbf, 0x3c, 0x4a, 0x7f, 0x50, 0x7f, 0x58, 0xbe, 0x5b, 0x9f, 0xa2, 0x4f, 0x91, 0xc7, 0xea, 0x53, 0xf5, 0x69, 0xf2, 0x7d, 0xfa, 0x74, + 0x7d, 0xba, 0x3c, 0x5e, 0x7f, 0x5c, 0x7f, 0x5c, 0x7e, 0x40, 0x5f, 0xa3, 0xbf, 0x2e, 0x4f, 0xd0, 0xdf, 0xd0, 0xdf, 0x94, 0x27, 0xe9, 0x1b, 0xf5, 0x77, 0xe5, 0x47, 0xf4, 0xf7, 0xf5, 0x52, 0xf9, 0x51, 0x7d, 0x9b, 0xbe, 0x5d, 0x7e, 0x4c, 0xff, 0x58, 0xff, 0x54, 0x9e, 0xa9, 0x7f, 0xa6, 0x7f, 0x26, 0x3f, 0x25, 0x3c, 0xcd, 0xe4, 0x39, 0xc2, 0xd3, 0x4c, 0x9e, 0x4b, 0x2b, 0xcf, 0xaf, 0xe4, 0xf9, 0xfa, 0xd7, 0xfa, 0x21, 0xf9, 0x39, 0xbd, 0x4c, 0xff, 0x56, 0xfe, 0x3b, 0xad, 0x43, 0x8f, 0xc9, 0xcf, 0x1b, 0x61, 0xa3, 0xb6, 0xfc, 0x12, 0xcd, 0xa9, 0xcd, 0xe5, 0xb5, 0x46, 0x4b, 0xa3, 0xa5, 0xbc, 0xd9, 0x28, 0x34, 0xba, 0xcb, 0xef, 0xd2, 0x9c, 0xda, 0x47, 0xde, 0x6b, 0xf4, 0x35, 0x06, 0xcb, 0xdf, 0xd0, 0x9c, 0x3a, 0x4a, 0x3e, 0x61, 0x8c, 0x36, 0x46, 0x2b, 0x1a, 0xcd, 0xac, 0x53, + 0x15, 0xdd, 0x98, 0x66, 0x4c, 0x53, 0x6a, 0x18, 0xd3, 0x8d, 0xb9, 0x4a, 0x4d, 0x63, 0x9e, 0xf1, 0xba, 0x52, 0x4f, 0xc4, 0x98, 0x2b, 0x9d, 0x69, 0xd6, 0xdc, 0xa4, 0x5c, 0x6c, 0xbc, 0x6b, 0x6c, 0x51, 0x84, 0x5f, 0xd9, 0x36, 0xa5, 0x9b, 0xf0, 0x25, 0x53, 0x8a, 0x8d, 0x83, 0xc6, 0x8f, 0xca, 0x15, 0xc6, 0x4f, 0xc6, 0x49, 0xe5, 0x3a, 0xb3, 0x83, 0xd9, 0x41, 0xb9, 0xc1, 0xbc, 0xca, 0xbc, 0x4a, 0x19, 0x68, 0xf6, 0x36, 0x7b, 0x2b, 0x37, 0x9a, 0xfd, 0xcc, 0x7e, 0xca, 0x20, 0xf3, 0x7a, 0xf3, 0x7a, 0xe5, 0x26, 0x73, 0xa0, 0x39, 0x50, 0x19, 0x6c, 0xde, 0x64, 0xde, 0xa4, 0xdc, 0x6c, 0xde, 0x6a, 0xde, 0xaa, 0x0c, 0x31, 0x87, 0x99, 0xc3, 0x94, 0x5b, 0xcc, 0x87, 0xcd, 0xc9, 0xca, 0x50, 0x73, 0xaa, 0xf9, 0x98, 0x52, 0x62, 0x2e, 0x36, 0x5f, 0x54, 0x46, 0x98, 0x2b, 0xcc, 0x55, 0xca, + 0x48, 0xb1, 0xd2, 0x53, 0xee, 0x16, 0xf3, 0xab, 0x72, 0x8f, 0xb9, 0xc9, 0x7c, 0x47, 0x19, 0x63, 0xbe, 0x6b, 0xbe, 0xab, 0x8c, 0x65, 0x37, 0x90, 0x51, 0xb0, 0x8d, 0xb6, 0x85, 0x55, 0xf6, 0xf3, 0x7e, 0xc7, 0x7e, 0x36, 0x6d, 0xcb, 0x93, 0x7e, 0xaf, 0xa6, 0x6d, 0x5d, 0xe2, 0x77, 0x72, 0xde, 0x7f, 0x77, 0xab, 0x5a, 0xb7, 0x7f, 0x77, 0x5b, 0x52, 0xe5, 0x77, 0x79, 0x9d, 0x93, 0xeb, 0xbb, 0x91, 0xb6, 0x2d, 0xe7, 0x38, 0x7f, 0x67, 0x95, 0x6d, 0x4f, 0xd2, 0xb6, 0x9f, 0xb6, 0x43, 0x89, 0xed, 0x78, 0xd2, 0x76, 0x2a, 0xb1, 0x1d, 0x49, 0x92, 0x95, 0xa7, 0x0f, 0x25, 0xed, 0xcb, 0xb7, 0x23, 0x95, 0xce, 0xe5, 0xa7, 0x6e, 0x51, 0xfa, 0x12, 0x1f, 0x6a, 0x3b, 0x48, 0x7c, 0x14, 0xf8, 0x41, 0xfb, 0x14, 0x21, 0x47, 0x7a, 0x28, 0xad, 0x90, 0xf8, 0xa9, 0x5b, 0x6d, 0x5f, 0x23, 0xcf, 0x0a, + 0xe2, 0x1b, 0x95, 0x1d, 0x90, 0xe3, 0x2c, 0x5a, 0xab, 0x09, 0x5e, 0x8c, 0xa3, 0x8f, 0xd3, 0xda, 0xfa, 0x11, 0x71, 0x16, 0xbf, 0x1f, 0x7c, 0x4a, 0x82, 0x7f, 0xad, 0xdc, 0xe7, 0xdc, 0xee, 0xdc, 0xae, 0x6b, 0x8e, 0x43, 0x4e, 0x45, 0x77, 0xe9, 0xab, 0x9d, 0x21, 0x8f, 0xe1, 0xcc, 0x76, 0x37, 0x33, 0x6a, 0x78, 0x3c, 0x7a, 0xc0, 0xd9, 0xd1, 0x6c, 0xe9, 0xec, 0xee, 0xec, 0xa3, 0xc7, 0x8c, 0xed, 0xce, 0x41, 0xce, 0xe1, 0xce, 0xb1, 0x7a, 0x9a, 0x73, 0xb2, 0x6b, 0x84, 0x9e, 0xe5, 0xda, 0xed, 0x5c, 0xe4, 0x9c, 0x6c, 0xac, 0x70, 0x2e, 0xd5, 0x73, 0x9c, 0x6b, 0x8c, 0x90, 0x73, 0xab, 0x73, 0x2b, 0x59, 0x72, 0xf9, 0xce, 0xbd, 0xce, 0x03, 0xce, 0xa3, 0x2e, 0x87, 0xab, 0xc4, 0xbd, 0x5a, 0x6f, 0xa6, 0x17, 0xba, 0x32, 0x69, 0xac, 0x77, 0xd1, 0xbb, 0xe9, 0x3d, 0xf4, 0xde, 0xae, 0x01, + 0x44, 0x9d, 0x5d, 0x45, 0xa0, 0x5e, 0x7a, 0x3f, 0x57, 0x5f, 0x97, 0xc3, 0xdc, 0xa0, 0x0f, 0x74, 0x4d, 0x77, 0xa5, 0xea, 0x43, 0x9c, 0xd9, 0xfa, 0x30, 0x77, 0xbe, 0x6b, 0x99, 0x3e, 0xd2, 0xe3, 0x71, 0xad, 0xd2, 0xc7, 0xb8, 0xd6, 0xba, 0x46, 0x98, 0xbd, 0x9c, 0x2b, 0xf4, 0xf1, 0xfa, 0x24, 0x73, 0x00, 0x8d, 0xf1, 0x19, 0xfa, 0x6c, 0xb7, 0xe6, 0x0e, 0xb8, 0x67, 0x9b, 0x6b, 0xf5, 0x79, 0xfa, 0x42, 0xc7, 0x12, 0x7d, 0x89, 0x39, 0xd1, 0xdc, 0x61, 0x2e, 0x76, 0x67, 0xb9, 0xf3, 0xcd, 0x59, 0xfa, 0x72, 0x7d, 0xb5, 0xbe, 0xce, 0x9c, 0x65, 0xce, 0x72, 0x1e, 0xa0, 0x31, 0xbf, 0xd1, 0x7d, 0x5c, 0xdf, 0xe2, 0x6c, 0xa3, 0x6f, 0xa3, 0xfb, 0xda, 0xe9, 0x1e, 0xe9, 0x88, 0x39, 0x62, 0xee, 0x25, 0xae, 0xd1, 0xe6, 0x14, 0x5a, 0x2d, 0xed, 0x77, 0x75, 0xd6, 0x0f, 0x99, 0x83, 0xf5, 0x23, + 0xfa, 0x71, 0xa3, 0x80, 0xae, 0x7d, 0x8a, 0x96, 0xb1, 0x86, 0xe1, 0x31, 0x42, 0xee, 0x1c, 0xd7, 0x5c, 0x67, 0xb6, 0x51, 0xc3, 0x9d, 0x63, 0xa4, 0x1b, 0xd9, 0x9e, 0x5c, 0xe7, 0x58, 0x77, 0xbe, 0xa3, 0x9f, 0x91, 0x6b, 0xac, 0xf0, 0x28, 0x8e, 0x23, 0xce, 0x4d, 0xae, 0x52, 0xa3, 0xc0, 0x68, 0x6e, 0xb4, 0x71, 0xef, 0x71, 0xef, 0x74, 0xa4, 0x39, 0x34, 0x8f, 0xc7, 0x19, 0x72, 0xec, 0x34, 0x3a, 0x3a, 0x8f, 0x1a, 0x5d, 0x8d, 0x3e, 0x4e, 0xc3, 0xe8, 0xee, 0xdc, 0x6a, 0xac, 0x30, 0x23, 0xf4, 0xbb, 0xa7, 0xd1, 0x87, 0xee, 0xa3, 0xb3, 0xd1, 0xdf, 0x18, 0x64, 0x0c, 0x35, 0x33, 0xdd, 0xc3, 0x8c, 0xe1, 0xae, 0x01, 0xc6, 0x28, 0x5a, 0x6f, 0xcd, 0x31, 0xb6, 0xba, 0x4a, 0x8c, 0x32, 0x63, 0x82, 0x31, 0xd9, 0x98, 0x46, 0x2d, 0xd2, 0xcb, 0xac, 0x67, 0xcc, 0x71, 0x95, 0x38, 0xc6, 0x18, + 0x33, 0xa9, 0xfc, 0x39, 0x44, 0xf3, 0xa9, 0x95, 0x7a, 0x19, 0x8b, 0x8c, 0xa5, 0x54, 0x56, 0xa6, 0xfb, 0x90, 0xb1, 0xc6, 0x58, 0xef, 0x58, 0x6e, 0x6c, 0xa2, 0xf3, 0x36, 0x1b, 0xdb, 0x8d, 0x5d, 0xae, 0x12, 0x41, 0xee, 0xa9, 0x66, 0xa9, 0xb1, 0xd7, 0x38, 0x40, 0x25, 0x1d, 0x25, 0x3a, 0x61, 0x32, 0x47, 0x37, 0xd3, 0xee, 0x58, 0x62, 0x3a, 0xcc, 0x89, 0x8e, 0x6e, 0xce, 0xad, 0x1e, 0xc5, 0x99, 0xeb, 0xcc, 0x75, 0xaf, 0x33, 0x7d, 0x66, 0xc4, 0x4c, 0x35, 0x33, 0xcd, 0x7a, 0x66, 0x9e, 0xd9, 0x94, 0x7e, 0xb7, 0x34, 0xdb, 0x99, 0x9d, 0xcd, 0x22, 0xb3, 0xd8, 0x1c, 0xe0, 0xcc, 0x76, 0x6d, 0xa6, 0x36, 0x1e, 0x6e, 0xf6, 0x75, 0xed, 0xf0, 0x64, 0x9b, 0x03, 0xcc, 0xc1, 0x66, 0x89, 0x39, 0xc2, 0x9d, 0x65, 0x8e, 0x36, 0x37, 0x98, 0x6b, 0xcd, 0x71, 0x8e, 0x2e, 0xd4, 0xc2, 0x53, 0xcc, + 0xe9, 0x1e, 0xfa, 0xa3, 0x76, 0x55, 0x9c, 0x9b, 0xa8, 0x15, 0xf6, 0x9a, 0x73, 0xcd, 0x65, 0xe6, 0x32, 0x67, 0x1b, 0x73, 0x81, 0xb9, 0x80, 0x72, 0x2c, 0xc6, 0xd1, 0x65, 0xce, 0xe1, 0xee, 0x1c, 0xf7, 0x1e, 0x5a, 0xd7, 0xad, 0x35, 0x37, 0x50, 0x9e, 0xcd, 0x66, 0xa9, 0x59, 0xea, 0x9c, 0x49, 0x7c, 0x87, 0xc7, 0xe3, 0x20, 0xfd, 0x72, 0xcd, 0xa5, 0xbe, 0xda, 0x6d, 0xee, 0x33, 0x0f, 0x9a, 0x87, 0xcd, 0x63, 0xe6, 0x49, 0x67, 0x99, 0x43, 0x72, 0x04, 0xe8, 0x88, 0xcb, 0x11, 0x70, 0x2a, 0x9e, 0x5c, 0xf7, 0x3c, 0x67, 0x1b, 0xea, 0xa5, 0x9d, 0xa4, 0x49, 0xb9, 0xee, 0x61, 0xd4, 0xba, 0x59, 0x8e, 0x66, 0x8e, 0x1c, 0xda, 0xf2, 0x1d, 0xcd, 0x5c, 0xc5, 0x8e, 0x42, 0xe7, 0x64, 0x47, 0x07, 0xa7, 0xe2, 0xe8, 0xed, 0xe8, 0xe2, 0xe8, 0xe6, 0xe8, 0x41, 0xfb, 0x7e, 0x8e, 0x81, 0x8e, 0x21, + 0xf4, 0xd8, 0x73, 0x38, 0x86, 0x39, 0x46, 0xba, 0x3a, 0x3b, 0xc6, 0x38, 0x97, 0x3a, 0xc6, 0xbb, 0x25, 0x57, 0x91, 0x63, 0x92, 0x63, 0xaa, 0x63, 0x06, 0xe9, 0xd2, 0x31, 0xc7, 0x6c, 0xc7, 0x3c, 0xa7, 0xc7, 0xbd, 0x9a, 0x5a, 0xb9, 0xc4, 0xb1, 0xd0, 0x35, 0xcb, 0xb1, 0xc4, 0xb1, 0xdc, 0xd9, 0xdf, 0xb1, 0xda, 0x55, 0x4c, 0x7a, 0x94, 0x45, 0xbf, 0xd7, 0x39, 0x36, 0x7a, 0xb2, 0xc5, 0x08, 0x38, 0x93, 0x1c, 0x5b, 0xce, 0x2e, 0xaf, 0x92, 0x6b, 0xdb, 0x39, 0x8f, 0xec, 0xa4, 0x7b, 0xd9, 0xee, 0xd8, 0x43, 0xb4, 0xdf, 0x71, 0x88, 0xc6, 0x57, 0x99, 0xe3, 0x08, 0xe8, 0xb8, 0x45, 0x74, 0x2f, 0xa7, 0xa8, 0x45, 0x0d, 0xa7, 0xa7, 0x9c, 0x3c, 0x05, 0xce, 0x90, 0x33, 0xe4, 0x1e, 0x58, 0x99, 0x68, 0x3c, 0xd6, 0xf0, 0x18, 0x55, 0xa5, 0xe5, 0xe4, 0xcc, 0x76, 0xa6, 0x9f, 0x4e, 0x57, 0x3a, + 0xd2, 0x1d, 0x3c, 0xd7, 0x59, 0x90, 0x4c, 0xee, 0x2c, 0xd2, 0xd9, 0xe6, 0x1e, 0x0f, 0x8d, 0x99, 0x73, 0x90, 0xb3, 0x39, 0x6d, 0x6d, 0xaa, 0x50, 0xc7, 0x24, 0x5e, 0x1d, 0xea, 0x48, 0xb4, 0xd5, 0xd9, 0x95, 0xe6, 0x8e, 0x9e, 0xce, 0xfe, 0xce, 0x3e, 0xb4, 0x59, 0x34, 0xc8, 0x39, 0xc8, 0x95, 0x27, 0x38, 0x68, 0x28, 0xcd, 0x28, 0xc3, 0x9d, 0xa3, 0x04, 0x55, 0x5c, 0x7d, 0x6c, 0x52, 0x4d, 0x26, 0x78, 0x6a, 0x54, 0x26, 0xe7, 0x64, 0xe7, 0x34, 0xe7, 0xe4, 0xaa, 0x52, 0x77, 0x7e, 0x55, 0xc9, 0xd9, 0xc9, 0x39, 0xcd, 0xb5, 0x9b, 0xe6, 0xad, 0x39, 0x44, 0x33, 0x69, 0x9b, 0xef, 0x5c, 0xe4, 0x6e, 0x56, 0x95, 0x9c, 0x2b, 0x12, 0xb4, 0xb4, 0x22, 0xb5, 0xc2, 0xb9, 0x89, 0x68, 0x0d, 0xd1, 0x26, 0xe7, 0x7a, 0xa4, 0x05, 0x6d, 0xfd, 0x1d, 0x24, 0xf4, 0x61, 0x57, 0x12, 0xed, 0xb5, 0xa8, + 0xe2, 0x9a, 0x07, 0x9c, 0x65, 0x95, 0xea, 0x50, 0x76, 0x9a, 0x12, 0xbf, 0x4f, 0x80, 0x8e, 0x5a, 0x7b, 0xd7, 0xe0, 0xb3, 0x10, 0x3b, 0xab, 0x34, 0x99, 0xec, 0xee, 0xd5, 0x82, 0x68, 0x6e, 0xf6, 0x25, 0x28, 0x62, 0x49, 0x48, 0x46, 0xf3, 0x89, 0xa7, 0xb9, 0x27, 0xfd, 0xb7, 0xc9, 0x35, 0x2b, 0xb1, 0x4f, 0x75, 0x65, 0xba, 0xea, 0x25, 0xd1, 0xac, 0x04, 0xe5, 0xd1, 0xd6, 0xd9, 0xd5, 0x34, 0x31, 0xcb, 0xb7, 0x4c, 0xec, 0xcf, 0x46, 0xed, 0xf0, 0x2c, 0x28, 0xae, 0xa0, 0x5e, 0xf4, 0x3c, 0xe8, 0xeb, 0x3a, 0x0c, 0x2e, 0x68, 0x00, 0xd5, 0x59, 0xcc, 0x74, 0xb3, 0x2a, 0x68, 0x84, 0x27, 0xbb, 0x2a, 0xb9, 0x46, 0x9f, 0x29, 0x4b, 0x3e, 0xe6, 0x1a, 0x5d, 0x35, 0x07, 0xee, 0x77, 0x5c, 0x05, 0x4d, 0xac, 0x44, 0x53, 0xe8, 0x59, 0x74, 0xfa, 0x7a, 0xb3, 0xdc, 0xcb, 0x93, 0x7f, 0x55, 0xd0, + 0x5c, 0xd7, 0x82, 0x24, 0x5a, 0xec, 0x5a, 0xec, 0x1e, 0x76, 0x36, 0x72, 0xed, 0x4b, 0xd0, 0x32, 0xd7, 0x2a, 0x7a, 0x9a, 0x6d, 0x70, 0x6d, 0x76, 0x95, 0x9e, 0x41, 0x3b, 0x84, 0x4e, 0xfe, 0x2e, 0xda, 0x77, 0x06, 0x1d, 0xfc, 0x5d, 0x74, 0x18, 0x74, 0x8c, 0xe8, 0x5c, 0xfb, 0x93, 0xae, 0x93, 0x6e, 0xa9, 0x1a, 0xa4, 0xb9, 0x5d, 0xbf, 0x8b, 0x02, 0xee, 0x98, 0x3b, 0x8d, 0x68, 0x60, 0xc5, 0x18, 0xcf, 0x71, 0xe7, 0x57, 0xe8, 0x7d, 0xa1, 0xbb, 0x83, 0xbb, 0x8b, 0xbb, 0x9b, 0xbb, 0x87, 0xbb, 0x37, 0x51, 0xbf, 0xc4, 0x3c, 0x36, 0x84, 0x5a, 0x72, 0xa4, 0x7b, 0x8c, 0x7b, 0xbc, 0x7b, 0x92, 0x7b, 0x6a, 0x05, 0xcd, 0xa8, 0xa0, 0xd9, 0x09, 0x9a, 0x97, 0xa0, 0x85, 0xee, 0x25, 0xee, 0xe5, 0xf4, 0x7c, 0x5e, 0xed, 0x5e, 0xe7, 0xde, 0xe8, 0xde, 0xe2, 0xde, 0xe6, 0xde, 0x49, 0xbf, 0xf6, + 0xbb, 0x0f, 0xb9, 0x8f, 0xb8, 0x8f, 0xbb, 0x4f, 0x79, 0x14, 0x0f, 0x3d, 0xe8, 0xe9, 0x81, 0x15, 0xa2, 0x19, 0x21, 0x9d, 0xf4, 0x21, 0xd7, 0x53, 0x40, 0x9a, 0x2f, 0xbc, 0xd1, 0xf6, 0x88, 0x35, 0x91, 0x5c, 0x0b, 0xdc, 0x86, 0xf5, 0x51, 0x3d, 0xa4, 0x83, 0xb6, 0xaf, 0xe8, 0x68, 0x07, 0x48, 0x4a, 0x05, 0x57, 0x5c, 0x90, 0xdf, 0x25, 0xb8, 0x84, 0xd5, 0x99, 0x54, 0x86, 0xa3, 0x5f, 0x40, 0x9e, 0x0a, 0xbe, 0x0a, 0xfc, 0x43, 0xc8, 0x5b, 0x81, 0x2f, 0x06, 0x9f, 0x8a, 0xfc, 0x8b, 0xc0, 0x87, 0x20, 0x4f, 0x47, 0xc8, 0x3f, 0x10, 0x57, 0x91, 0x7a, 0x0a, 0x4e, 0x56, 0xa1, 0x90, 0x6c, 0x44, 0xfa, 0x27, 0xe4, 0xe9, 0x87, 0x3a, 0x84, 0x91, 0x56, 0x90, 0x13, 0xeb, 0x41, 0xe9, 0x79, 0x48, 0x02, 0xa8, 0xd5, 0xf9, 0x48, 0x4f, 0x82, 0xbc, 0x83, 0xc8, 0x23, 0x6f, 0x85, 0x44, 0x83, 0xe4, + 0x2d, 0xa4, 0xd3, 0x21, 0xaf, 0x8d, 0x12, 0xbe, 0x04, 0xbf, 0x10, 0xf2, 0x06, 0xe0, 0x69, 0xc8, 0xf9, 0x1e, 0xf8, 0xb5, 0x90, 0xb4, 0x44, 0x4d, 0x82, 0x90, 0xfc, 0x13, 0x7c, 0x07, 0xf8, 0x97, 0xe0, 0x77, 0x80, 0xdf, 0x0a, 0x3e, 0x1d, 0xfc, 0x08, 0xf8, 0x3b, 0xe0, 0x9f, 0xa0, 0x84, 0xcb, 0x90, 0xe6, 0x28, 0xa7, 0x1d, 0x24, 0xe3, 0x90, 0x9e, 0x01, 0x5e, 0x02, 0x8e, 0xfb, 0x62, 0x68, 0x43, 0x79, 0x0c, 0xd2, 0xfb, 0x91, 0xfe, 0x3b, 0x8e, 0xbe, 0x0c, 0xfe, 0x2c, 0xca, 0xf9, 0x01, 0x6d, 0x62, 0x5d, 0xe5, 0x2a, 0xe4, 0xec, 0x87, 0xa3, 0xb8, 0x53, 0x3e, 0x0b, 0x7c, 0x36, 0xf8, 0x05, 0x28, 0x61, 0x3d, 0xd2, 0xa8, 0x03, 0xdb, 0x87, 0xb3, 0x70, 0x2f, 0x0c, 0xd7, 0x95, 0xd0, 0x17, 0xca, 0x72, 0xa4, 0x07, 0x20, 0xbf, 0x0f, 0xe9, 0x2d, 0xe0, 0xd3, 0x20, 0x39, 0x88, 0xf4, 0x20, + 0xf0, 0x43, 0x90, 0x5c, 0x8e, 0x36, 0xac, 0x89, 0x92, 0x2d, 0x5e, 0x8c, 0xa3, 0x87, 0xd1, 0x9e, 0x3b, 0x91, 0xbe, 0x04, 0x69, 0xb4, 0xaa, 0x04, 0x1d, 0x90, 0x46, 0x83, 0xa3, 0x34, 0xf9, 0x31, 0x9c, 0xd5, 0x06, 0xfc, 0x3e, 0xd4, 0xc1, 0x6a, 0xe1, 0xb7, 0x71, 0x54, 0x07, 0xb7, 0x7a, 0x61, 0x2f, 0xd2, 0x56, 0x5f, 0x40, 0x67, 0xe4, 0xae, 0x90, 0x0f, 0x46, 0xba, 0x0e, 0x4a, 0x38, 0x06, 0x49, 0x6f, 0xf0, 0x57, 0xc0, 0xa1, 0x51, 0xb2, 0x55, 0x87, 0x1e, 0x48, 0x5b, 0xad, 0x51, 0x08, 0x9e, 0x0b, 0xde, 0x04, 0xbc, 0x1e, 0xf4, 0x0a, 0xe5, 0xc8, 0xd0, 0x52, 0x09, 0x2d, 0x2f, 0xa1, 0x8f, 0xa4, 0xf3, 0x50, 0x3e, 0xea, 0xac, 0x34, 0x86, 0x64, 0x0d, 0x2c, 0x8f, 0x99, 0xc8, 0x6f, 0x0a, 0x6e, 0x4b, 0xc7, 0x51, 0x94, 0x26, 0xcd, 0x85, 0x3c, 0x86, 0x3c, 0xe8, 0x47, 0x09, 0x75, 0x53, + 0x6a, 0xa0, 0xcd, 0xbf, 0x43, 0x69, 0x23, 0xc0, 0x91, 0x9f, 0x5f, 0x8e, 0xfc, 0xeb, 0xc0, 0xd1, 0xfe, 0x7c, 0x25, 0xd2, 0x12, 0xce, 0x7d, 0x00, 0x92, 0xa6, 0xe0, 0x2d, 0x20, 0x89, 0x80, 0x67, 0x83, 0x5f, 0x0f, 0xfe, 0x24, 0x8e, 0x62, 0x2c, 0x70, 0xab, 0x8f, 0xe6, 0xa3, 0x04, 0xb4, 0xad, 0xf4, 0x1c, 0xd2, 0xcf, 0xa0, 0x0e, 0xbb, 0x21, 0xc1, 0x15, 0xa5, 0x07, 0xc1, 0x71, 0x17, 0xd2, 0xcf, 0xc8, 0x93, 0x83, 0x74, 0x7f, 0xf0, 0xe3, 0xe0, 0x2f, 0x82, 0xff, 0x15, 0x1c, 0x7d, 0xc4, 0x7f, 0x41, 0xda, 0xba, 0xd3, 0x8f, 0x71, 0x16, 0x83, 0x1c, 0x77, 0xc7, 0x31, 0x6f, 0xf0, 0x05, 0x38, 0xda, 0x3e, 0x31, 0x06, 0x05, 0x5f, 0x0b, 0x0e, 0xcd, 0x94, 0x64, 0x9c, 0x05, 0x89, 0x6c, 0xd5, 0x6d, 0x1e, 0xb8, 0x81, 0x5e, 0x78, 0x09, 0xe9, 0x4c, 0x70, 0xb4, 0xbc, 0xd4, 0x19, 0xfc, 0x1a, + 0xf0, 0x2b, 0x50, 0xfe, 0x44, 0xa4, 0x8b, 0xc0, 0xad, 0xd1, 0x6a, 0xe9, 0xde, 0x04, 0x70, 0x6b, 0xac, 0x7d, 0x0e, 0x8e, 0xb1, 0xa3, 0xb8, 0x91, 0x9e, 0x0c, 0x8e, 0x96, 0x97, 0x6e, 0x07, 0xbf, 0x19, 0xfc, 0x6a, 0x94, 0x79, 0x14, 0x57, 0xcc, 0x42, 0x3a, 0x0f, 0x1c, 0x6d, 0xc5, 0x3f, 0x03, 0xef, 0x0b, 0x6e, 0xcd, 0x57, 0x06, 0xd2, 0xd0, 0x1f, 0xfe, 0x03, 0xfa, 0x14, 0x9c, 0x87, 0x90, 0x86, 0x9e, 0xb0, 0xef, 0x91, 0x13, 0x3d, 0xc8, 0xbe, 0x06, 0xff, 0x16, 0x79, 0x1a, 0x82, 0xe3, 0xa8, 0xdc, 0x1c, 0xbc, 0x3e, 0x72, 0x36, 0x83, 0x1c, 0x33, 0x21, 0x5f, 0x0a, 0x8e, 0xfb, 0xe2, 0x37, 0xe2, 0x28, 0xce, 0x92, 0xd4, 0xc4, 0x3c, 0x2c, 0xe4, 0x56, 0xef, 0x58, 0xed, 0x9c, 0x01, 0x79, 0x01, 0x38, 0xb4, 0x42, 0x6e, 0x9c, 0xd0, 0x6a, 0xc1, 0xeb, 0x22, 0x4f, 0x77, 0x9c, 0xb5, 0x01, + 0xe9, 0x2e, 0x48, 0x3b, 0xc1, 0x1d, 0x90, 0xe0, 0x5c, 0xa9, 0x5e, 0x62, 0x5e, 0x15, 0x1c, 0x7d, 0x24, 0x61, 0x86, 0x97, 0xa0, 0xc9, 0x0a, 0xee, 0x4e, 0xb2, 0x7a, 0x1f, 0xb3, 0x90, 0x8c, 0xeb, 0x4a, 0xc3, 0xc1, 0x1f, 0x87, 0xc4, 0x6a, 0xff, 0xa7, 0xc1, 0x5f, 0x45, 0xf9, 0x4f, 0x41, 0x9e, 0x82, 0xf4, 0xeb, 0x90, 0xb7, 0x05, 0xc7, 0xa8, 0x91, 0xec, 0xe0, 0xdb, 0x71, 0xf4, 0x5f, 0x48, 0x63, 0x26, 0x91, 0xfa, 0x25, 0x66, 0x18, 0xc1, 0x2f, 0x85, 0xe4, 0x06, 0xf0, 0x7b, 0x90, 0xd3, 0xaa, 0x1b, 0x9e, 0x4d, 0x12, 0x7a, 0x41, 0xca, 0x07, 0xc7, 0x9d, 0x72, 0x8c, 0x2f, 0x19, 0xf3, 0x00, 0xc7, 0x28, 0x90, 0xac, 0xb9, 0x02, 0xcf, 0x05, 0xbe, 0x02, 0xfc, 0x36, 0x48, 0x70, 0xef, 0x1c, 0x35, 0xe7, 0x0f, 0x43, 0xd2, 0x09, 0x1c, 0x4f, 0x34, 0x19, 0x73, 0xb5, 0xb4, 0x2d, 0xf1, 0xbc, + 0x10, 0x69, 0x2f, 0x38, 0xe6, 0x10, 0x05, 0x2d, 0x66, 0xc3, 0x13, 0x4a, 0x41, 0x8b, 0x29, 0x18, 0x6b, 0xcc, 0xc1, 0x9a, 0x0a, 0x9d, 0x94, 0x26, 0x51, 0xda, 0x2f, 0xb5, 0xb2, 0xb7, 0xb2, 0x17, 0xda, 0xdb, 0xdb, 0x3b, 0xd8, 0x3b, 0xda, 0x3b, 0xd9, 0x3b, 0xdb, 0x2f, 0xb2, 0x77, 0xb1, 0x5f, 0x65, 0xff, 0x4e, 0xb5, 0xa9, 0xa6, 0xd9, 0xd4, 0x6c, 0x63, 0xb6, 0x25, 0x1b, 0x74, 0x12, 0x93, 0xd8, 0x65, 0x4c, 0x66, 0xe2, 0x9b, 0x37, 0xe7, 0xb3, 0x56, 0x2c, 0x0f, 0x78, 0xf8, 0xf9, 0xc0, 0xc3, 0x9b, 0xb1, 0x8b, 0x59, 0x57, 0x76, 0x01, 0x2b, 0x26, 0x6a, 0x01, 0x54, 0xbc, 0x25, 0xbe, 0x36, 0x50, 0xc8, 0xae, 0x65, 0xfd, 0x59, 0x6b, 0x36, 0x80, 0xdd, 0xc1, 0x2e, 0x64, 0x23, 0xd9, 0x68, 0x76, 0x25, 0xbb, 0x97, 0xa8, 0x37, 0xbb, 0x9f, 0x4d, 0xa6, 0xe3, 0x4f, 0x12, 0xdd, 0xc0, 0x16, + 0x13, 0x0d, 0x64, 0x4b, 0x88, 0x6e, 0x64, 0x6f, 0xb1, 0x52, 0x36, 0x88, 0x6d, 0x63, 0x1f, 0xb3, 0xe1, 0xec, 0x13, 0xf6, 0x19, 0x1b, 0xc5, 0x3e, 0x67, 0x5f, 0xd2, 0x19, 0x5f, 0xb1, 0xaf, 0xd9, 0x04, 0x56, 0x46, 0xf4, 0x10, 0xf0, 0xf0, 0x49, 0xdc, 0xcb, 0xf3, 0xd8, 0x22, 0xa0, 0xdf, 0x9b, 0xf1, 0x7d, 0x88, 0x77, 0xa5, 0x86, 0x52, 0x43, 0xf6, 0xbe, 0xd4, 0x48, 0x6a, 0xc4, 0x4a, 0xa5, 0xa6, 0x52, 0x33, 0xf6, 0x81, 0xd4, 0x56, 0x7a, 0x8a, 0x6d, 0x97, 0x16, 0xc9, 0xf7, 0xf2, 0x26, 0xf2, 0x38, 0x79, 0x82, 0x64, 0xca, 0x0f, 0xc9, 0x0f, 0x4b, 0x1e, 0xf9, 0x6f, 0xf2, 0xdf, 0x24, 0xbf, 0xfc, 0x84, 0xfc, 0x84, 0x14, 0x90, 0x9f, 0x94, 0x9f, 0x95, 0x82, 0xf2, 0x73, 0xf2, 0x32, 0x29, 0x55, 0x5e, 0xae, 0xb8, 0xa5, 0x1c, 0x81, 0x0c, 0x4b, 0xe3, 0x95, 0x46, 0x4a, 0xa1, 0xf4, 0x80, + 0xd2, 0x46, 0xb9, 0x42, 0x7a, 0x54, 0x20, 0xc0, 0xf4, 0xec, 0x0e, 0xd8, 0x02, 0xb4, 0x12, 0xa8, 0x61, 0xcb, 0x90, 0x5e, 0xb0, 0x5d, 0x64, 0xbb, 0x88, 0x9e, 0xb0, 0x2f, 0xd9, 0x96, 0x49, 0xcb, 0x05, 0xf6, 0x2b, 0xad, 0x12, 0xd8, 0x2f, 0xcd, 0xe7, 0x9f, 0xda, 0xbe, 0x94, 0x5e, 0xb3, 0x7d, 0x65, 0x2b, 0x93, 0x36, 0xd9, 0x7e, 0xb0, 0x9d, 0x90, 0xb6, 0xda, 0xe2, 0x76, 0x97, 0xb4, 0xdd, 0xee, 0xb7, 0x07, 0xa4, 0xfd, 0xf6, 0x74, 0x7b, 0x96, 0x74, 0xc0, 0x5e, 0x66, 0x2f, 0x93, 0xbe, 0x11, 0x78, 0xaf, 0x54, 0xa6, 0xd6, 0xd7, 0xbe, 0x91, 0xbe, 0x15, 0xf1, 0xbf, 0xca, 0x52, 0xdd, 0xd4, 0x1d, 0xca, 0x4a, 0xdd, 0xa3, 0xa7, 0x2b, 0xaf, 0x08, 0x3c, 0x56, 0xd9, 0x22, 0xf0, 0x58, 0xe5, 0x7d, 0xbd, 0x9e, 0x5e, 0x4f, 0x29, 0x15, 0xa8, 0xac, 0xf2, 0x81, 0xde, 0x58, 0xef, 0xa4, 0x7c, + 0xac, 0x5f, 0xa4, 0x3f, 0xa9, 0x94, 0x89, 0xc8, 0x5c, 0x5b, 0x7b, 0xfd, 0x2d, 0xe3, 0x43, 0xdb, 0xc5, 0xc6, 0x47, 0xc6, 0x2e, 0x7b, 0x2f, 0xf1, 0xf5, 0x0b, 0x7b, 0x5f, 0x81, 0x46, 0xda, 0x87, 0x09, 0x1c, 0xd2, 0x7e, 0xbb, 0xc0, 0x21, 0xed, 0xc3, 0x05, 0xea, 0x68, 0xbf, 0x43, 0xa0, 0x8e, 0xf6, 0x11, 0x26, 0x91, 0xfd, 0x4e, 0xf3, 0x61, 0xf3, 0x31, 0xfb, 0x48, 0xf3, 0xef, 0xe6, 0x42, 0xfb, 0xfd, 0xe6, 0x0b, 0xe6, 0x8b, 0xf6, 0x07, 0xcc, 0x15, 0xe6, 0xab, 0xf6, 0x89, 0xe6, 0x46, 0xf3, 0x5d, 0xfb, 0x14, 0xe6, 0xa7, 0x3e, 0x16, 0xdf, 0x4e, 0xee, 0x55, 0x8d, 0xad, 0xb8, 0xca, 0x5e, 0x78, 0xab, 0x0d, 0xa8, 0xb4, 0x71, 0x7c, 0x53, 0x84, 0xc7, 0xfd, 0x82, 0x9f, 0x1a, 0x2a, 0xb8, 0x94, 0x27, 0x38, 0xbf, 0x3f, 0xbe, 0xd2, 0x3e, 0x3d, 0x38, 0x23, 0xb4, 0xdd, 0x99, 0x1b, 0x5c, + 0x1e, 0xdc, 0x16, 0xcc, 0x77, 0x16, 0x04, 0x0f, 0x05, 0x8f, 0x87, 0x8c, 0xd0, 0x1c, 0xf7, 0xc6, 0xe8, 0x09, 0x67, 0xf3, 0xd0, 0x1c, 0x67, 0x9b, 0x50, 0x6e, 0xa8, 0x20, 0x36, 0xda, 0x5b, 0x9c, 0xd2, 0x21, 0xd4, 0x27, 0x16, 0x09, 0x0d, 0x0a, 0x8d, 0x22, 0x6b, 0xb5, 0x6b, 0x68, 0x0e, 0x1d, 0xeb, 0x1e, 0x4b, 0x8d, 0x2c, 0xf4, 0x2e, 0x0e, 0x2d, 0x75, 0x15, 0x87, 0x36, 0x85, 0xd6, 0x07, 0xe7, 0x39, 0x7b, 0x86, 0x0e, 0xc4, 0x32, 0xc3, 0x2c, 0xec, 0x0b, 0x0f, 0x0e, 0x37, 0x8d, 0x1d, 0x0b, 0x77, 0x0e, 0xe6, 0x84, 0xfb, 0x86, 0x8b, 0x29, 0x47, 0xaf, 0xf0, 0x80, 0xf0, 0x88, 0xd0, 0x34, 0x8f, 0xb0, 0x6f, 0x85, 0x85, 0x3b, 0xc8, 0xdf, 0x91, 0x2c, 0xda, 0xad, 0xb1, 0xd1, 0xb1, 0xd1, 0xe1, 0x59, 0xe1, 0x05, 0xe1, 0xb5, 0xd8, 0xd6, 0xc6, 0x1c, 0x31, 0x87, 0x73, 0x78, 0x8a, 0x14, + 0x3e, 0x19, 0x1b, 0xed, 0x3f, 0x10, 0x49, 0x4b, 0x09, 0x38, 0x47, 0x45, 0x9a, 0x45, 0x9a, 0x05, 0x0e, 0x3a, 0xc7, 0x46, 0x68, 0x9d, 0x1b, 0xf2, 0x84, 0x0f, 0x3b, 0x27, 0x44, 0xc6, 0xc0, 0xb6, 0x9d, 0x19, 0xee, 0x1b, 0x38, 0x18, 0x59, 0xe8, 0x3e, 0x15, 0xd9, 0x18, 0xd9, 0x16, 0x5d, 0x2a, 0xac, 0xd4, 0x68, 0xf7, 0xe0, 0xd4, 0xe0, 0x54, 0xaa, 0xdb, 0xa2, 0x94, 0x61, 0xd1, 0x51, 0xce, 0xa5, 0xb1, 0x08, 0x95, 0xb7, 0x22, 0x3a, 0xd6, 0xb9, 0x26, 0x38, 0xc3, 0xbf, 0xc6, 0xb9, 0x3e, 0x7a, 0x40, 0x58, 0xa3, 0x91, 0x53, 0xce, 0xed, 0xd1, 0xf5, 0xb1, 0xd1, 0xce, 0x5d, 0x91, 0x8d, 0xc0, 0xe0, 0xca, 0x62, 0xc7, 0x5c, 0x13, 0x43, 0xf3, 0x3d, 0xa1, 0x68, 0x99, 0xf3, 0x68, 0x94, 0x2c, 0xc7, 0xd8, 0x82, 0x98, 0x5d, 0xdc, 0xa3, 0x8b, 0xc5, 0x7c, 0x29, 0x81, 0x58, 0x44, 0x20, 0x27, + 0xfe, 0xfe, 0x2e, 0x5f, 0xca, 0x30, 0x57, 0xc4, 0x95, 0x1a, 0x9c, 0x0d, 0x7b, 0x2e, 0x2f, 0xe6, 0xf3, 0x34, 0x77, 0x35, 0xf5, 0x0d, 0x8c, 0xe5, 0xc5, 0xec, 0x91, 0xb4, 0xe0, 0x36, 0x57, 0x4b, 0xef, 0xb2, 0x72, 0x4b, 0x2d, 0xd6, 0x2e, 0xbc, 0x2c, 0xbc, 0x2c, 0xb6, 0x23, 0x56, 0x14, 0x2b, 0x8e, 0x0d, 0x70, 0xf5, 0x72, 0xcf, 0x70, 0xf5, 0x8d, 0x8d, 0x88, 0x8d, 0x8e, 0xce, 0x4c, 0x19, 0x1f, 0x1b, 0xed, 0x1b, 0xe9, 0x29, 0x73, 0x0d, 0x88, 0xed, 0xf3, 0x1f, 0x48, 0x19, 0x16, 0xd9, 0x06, 0xdb, 0x6d, 0x44, 0xac, 0xd8, 0x35, 0x3a, 0xdc, 0xd9, 0x35, 0xce, 0xb3, 0xd4, 0x35, 0x31, 0xb6, 0x9b, 0xf2, 0x4f, 0xf1, 0xb4, 0x09, 0xac, 0x75, 0x4d, 0x8f, 0x1d, 0x8e, 0x1d, 0x8b, 0x9d, 0x4c, 0x91, 0x52, 0x34, 0xd7, 0xac, 0xc0, 0xdc, 0xd8, 0xe8, 0x94, 0xa9, 0xae, 0xb9, 0xd4, 0x4b, 0x6b, + 0xfc, 0xd9, 0xfe, 0xa1, 0xa1, 0xec, 0x80, 0x65, 0x55, 0x91, 0xd5, 0x14, 0x1a, 0x44, 0xb5, 0x5b, 0xeb, 0x9d, 0x15, 0x59, 0x1e, 0x99, 0xe7, 0xda, 0x10, 0x5d, 0x14, 0x9d, 0xe6, 0xda, 0x1c, 0x3a, 0x9a, 0xd2, 0x23, 0x25, 0xe0, 0x2a, 0x4d, 0x09, 0x50, 0xbe, 0x1d, 0xb0, 0x87, 0x0e, 0xba, 0xb5, 0x40, 0xaa, 0x3b, 0x4d, 0x58, 0x2f, 0xa1, 0xf5, 0xb0, 0x5d, 0x34, 0xaa, 0xd1, 0x80, 0xd0, 0x1c, 0xcf, 0x9a, 0x60, 0x1a, 0xb5, 0xc1, 0x89, 0x70, 0xdf, 0x98, 0xc3, 0xb2, 0x3f, 0x62, 0x0e, 0xcf, 0x28, 0x77, 0x5a, 0xa4, 0x99, 0xb7, 0x28, 0xdc, 0xae, 0xc2, 0xfe, 0x10, 0x96, 0xc7, 0x6c, 0xff, 0x2e, 0x8f, 0x11, 0x38, 0xe8, 0xe9, 0x49, 0xb6, 0x46, 0x17, 0xff, 0x81, 0x84, 0x1d, 0xd2, 0x2f, 0xbc, 0x21, 0xd6, 0x39, 0x70, 0xd0, 0x3d, 0x30, 0x58, 0x18, 0x8e, 0x08, 0x5b, 0x24, 0xb6, 0xcf, 0xb2, + 0x46, 0xc8, 0x7a, 0xd8, 0x4b, 0x16, 0xc9, 0xec, 0xd0, 0xd2, 0x94, 0x1e, 0xc1, 0xb4, 0x94, 0x34, 0xf7, 0xd4, 0xc0, 0xc1, 0x70, 0x5f, 0xd8, 0x23, 0xc2, 0x06, 0x21, 0x2b, 0xc4, 0xbf, 0xd7, 0x33, 0x93, 0x2c, 0x91, 0xd5, 0x81, 0x83, 0xa2, 0xdf, 0x53, 0xb2, 0xc2, 0x9b, 0x85, 0x45, 0x12, 0x68, 0x17, 0x3e, 0xe9, 0x3f, 0xe0, 0x3f, 0x10, 0x1b, 0x1d, 0x49, 0x13, 0xd6, 0x89, 0xb7, 0xa5, 0xb7, 0xa5, 0x7b, 0xa7, 0x67, 0xb8, 0x67, 0x78, 0x74, 0x28, 0x69, 0xc3, 0x9e, 0x14, 0xcd, 0x7f, 0xc2, 0x33, 0x9c, 0xf2, 0x92, 0xc5, 0xe2, 0xcb, 0x11, 0x36, 0x4b, 0xa0, 0x6f, 0x6c, 0x9c, 0xfb, 0x94, 0xbf, 0xab, 0x67, 0xbe, 0x47, 0x89, 0x2e, 0xf5, 0x18, 0xa1, 0x45, 0xe5, 0xd6, 0x8b, 0xaf, 0x5f, 0x4a, 0x07, 0xcf, 0x4c, 0x4f, 0xb6, 0x77, 0xb1, 0x37, 0x4f, 0xd8, 0x31, 0xa1, 0xe1, 0xc2, 0x92, 0xf1, + 0x74, 0xf4, 0x74, 0xf5, 0x74, 0x0f, 0x2e, 0x0c, 0x1e, 0xf1, 0xf4, 0xf4, 0xf4, 0xf1, 0xf4, 0x09, 0xef, 0xf0, 0xf4, 0x8f, 0x4e, 0xf3, 0x0c, 0xf2, 0x16, 0x87, 0x3b, 0x87, 0xeb, 0xa5, 0xf4, 0xf3, 0x0c, 0xa5, 0xf2, 0x17, 0x78, 0x46, 0x79, 0xc6, 0x7a, 0x26, 0x78, 0x26, 0x7b, 0xa6, 0x79, 0x66, 0x86, 0x46, 0x79, 0xe6, 0x78, 0xe6, 0x7b, 0x67, 0x79, 0x16, 0xa5, 0x74, 0x49, 0x49, 0x8b, 0x2c, 0x0c, 0xba, 0xa8, 0xcd, 0x96, 0x46, 0x67, 0x7a, 0x56, 0x78, 0xd6, 0x78, 0xd6, 0x7b, 0x36, 0x79, 0xb6, 0x7a, 0xb6, 0x7b, 0x76, 0x79, 0xf6, 0x7a, 0xca, 0x62, 0xa3, 0x3d, 0x07, 0x3c, 0x65, 0x9e, 0xa3, 0x9e, 0x13, 0x5e, 0xe6, 0xb5, 0xa7, 0x34, 0xf3, 0x3a, 0xbc, 0x0e, 0xdf, 0x46, 0x6f, 0x3d, 0xef, 0x66, 0xaf, 0xcf, 0x1b, 0xf1, 0xa6, 0x7a, 0x33, 0x29, 0x9d, 0x17, 0x28, 0xf6, 0x36, 0xa5, 0xfb, + 0x6a, 0xe7, 0xed, 0xec, 0x2d, 0xf2, 0x16, 0x47, 0x0e, 0x45, 0xbb, 0x07, 0x8a, 0x23, 0xa7, 0xbc, 0x1b, 0xbc, 0xbd, 0x7c, 0x52, 0xb4, 0xbb, 0xb7, 0xaf, 0x77, 0x40, 0x68, 0xbe, 0x77, 0x70, 0xa0, 0x5d, 0x4a, 0xa1, 0xb7, 0x84, 0xe4, 0x23, 0x62, 0x9d, 0xbd, 0xa3, 0xbd, 0xe3, 0xbc, 0x13, 0xbd, 0x53, 0xbc, 0xd3, 0xbd, 0xb3, 0xbc, 0x73, 0xbd, 0x0b, 0xe8, 0x8e, 0x96, 0xf9, 0x07, 0x79, 0x57, 0x05, 0x0e, 0x53, 0xfe, 0xb5, 0xd1, 0xe6, 0x74, 0xee, 0x66, 0x4a, 0x95, 0x7a, 0x0f, 0x7a, 0x77, 0x78, 0x77, 0x7b, 0xf7, 0xd1, 0xfe, 0xb0, 0xf7, 0x98, 0xf7, 0x64, 0xa0, 0x5d, 0xb4, 0x39, 0xd1, 0x8a, 0x70, 0x71, 0xac, 0x73, 0xa4, 0x9f, 0x4f, 0xf2, 0x69, 0x3e, 0x97, 0x2f, 0xe0, 0x8b, 0xf9, 0xd2, 0xa2, 0x3d, 0x7d, 0x59, 0xbe, 0x1c, 0x5f, 0xbe, 0xaf, 0x99, 0xaf, 0x30, 0xda, 0xc6, 0xd7, 0xc1, + 0xd7, 0x25, 0xb6, 0x20, 0x6a, 0x44, 0x8d, 0x94, 0xde, 0x29, 0xbd, 0x7d, 0xdd, 0x7c, 0x3d, 0x82, 0x93, 0x7c, 0xbd, 0xa3, 0x83, 0xfc, 0x34, 0x1e, 0x7c, 0xfd, 0x62, 0x23, 0x7c, 0x03, 0x7d, 0x43, 0x7c, 0xc3, 0x52, 0xc6, 0xfb, 0x46, 0xfa, 0xc6, 0xf8, 0xc6, 0xfb, 0x26, 0xf9, 0xa6, 0x12, 0xcd, 0xf0, 0xcd, 0x8e, 0x64, 0xf9, 0x87, 0xfb, 0xe6, 0xf9, 0x16, 0xfa, 0x96, 0xc4, 0x8e, 0xf9, 0x96, 0xfb, 0x56, 0xfb, 0xd6, 0xf9, 0x36, 0x86, 0xb6, 0xfb, 0xb6, 0xf8, 0xb6, 0xf9, 0x76, 0x46, 0xa4, 0x94, 0x85, 0x29, 0x03, 0x63, 0x11, 0xdf, 0x1e, 0xdf, 0x7e, 0xdf, 0xa1, 0xa0, 0xe6, 0x3b, 0xe2, 0x3b, 0xee, 0x3b, 0x15, 0xf3, 0xf9, 0x15, 0xbf, 0xe1, 0xf7, 0xf8, 0x43, 0x01, 0x5f, 0x68, 0x90, 0xbf, 0x86, 0x3f, 0x3d, 0x34, 0xdc, 0x9f, 0x9d, 0x12, 0x48, 0x19, 0xe6, 0xcf, 0xf5, 0x17, 0xf8, 0x9b, + 0xfb, 0xdb, 0xf8, 0x3b, 0xfa, 0xbb, 0xfa, 0xbb, 0xfb, 0x7b, 0xfa, 0xfb, 0xf8, 0xfb, 0x47, 0xb6, 0xf9, 0x07, 0xf9, 0x87, 0xfa, 0x87, 0xfb, 0x47, 0xf9, 0xc7, 0x86, 0x72, 0xfd, 0x13, 0xfc, 0x93, 0xfd, 0xd3, 0xfc, 0x33, 0xfd, 0x73, 0xfc, 0xf3, 0x63, 0xfb, 0x48, 0x6b, 0x16, 0xf9, 0x97, 0xfa, 0x57, 0x04, 0xc6, 0xf9, 0xd7, 0x44, 0x66, 0xa4, 0x0c, 0xf4, 0xaf, 0xf7, 0x6f, 0xf2, 0x6f, 0xf5, 0x6f, 0x27, 0xda, 0xe5, 0xdf, 0x4b, 0x33, 0x49, 0xc0, 0x5f, 0xe6, 0x3f, 0x1a, 0x2e, 0xf6, 0x9f, 0xa0, 0x71, 0x3a, 0x2c, 0xc0, 0x02, 0xf6, 0x98, 0x23, 0x3a, 0x21, 0xe0, 0x08, 0xf8, 0x02, 0x91, 0xe8, 0x89, 0xd0, 0xd2, 0x40, 0x6a, 0x74, 0x69, 0x20, 0x33, 0xb8, 0x3c, 0x50, 0x2f, 0x90, 0x17, 0x68, 0x1a, 0x68, 0x19, 0x5c, 0x18, 0xda, 0x14, 0x1e, 0x10, 0x68, 0x17, 0xe8, 0x1c, 0x28, 0x0a, 0x14, + 0xc7, 0x46, 0x07, 0x7a, 0x05, 0xfa, 0xa6, 0x4c, 0x0a, 0x0c, 0x08, 0x0c, 0x0e, 0x94, 0x04, 0x46, 0x04, 0x46, 0x07, 0xc6, 0x05, 0x26, 0x86, 0x46, 0x05, 0xa6, 0x04, 0xa6, 0x07, 0x68, 0x4c, 0x86, 0x8b, 0x03, 0x0b, 0x82, 0xab, 0x03, 0x8b, 0x03, 0xcb, 0x02, 0xab, 0x02, 0x6b, 0x03, 0x1b, 0x02, 0x87, 0x03, 0x9b, 0x03, 0xa5, 0xd1, 0x82, 0xc0, 0x8e, 0xc0, 0xee, 0xc0, 0x3e, 0xba, 0xde, 0x41, 0x2a, 0xeb, 0x70, 0xe0, 0x98, 0x98, 0x9b, 0x82, 0x53, 0x03, 0x27, 0x83, 0xcd, 0xc2, 0xa5, 0x91, 0x91, 0x41, 0x29, 0xa8, 0x85, 0xc6, 0x46, 0xb7, 0x07, 0x5d, 0x91, 0xa9, 0x91, 0xac, 0x60, 0x00, 0x14, 0x0b, 0xee, 0x8f, 0xe4, 0x07, 0xd3, 0xc2, 0x7d, 0x83, 0xdd, 0x22, 0xf3, 0x82, 0x59, 0xc1, 0x9c, 0x94, 0x40, 0x30, 0x3f, 0xdc, 0x32, 0xdc, 0x32, 0xd4, 0x3c, 0xd8, 0x2c, 0x58, 0x18, 0xec, 0x10, + 0xec, 0x12, 0x69, 0x16, 0x5d, 0x1f, 0x1d, 0x1a, 0xec, 0x16, 0xec, 0x11, 0xec, 0x1d, 0xec, 0x17, 0x1c, 0x18, 0x1c, 0x12, 0x1c, 0x16, 0x55, 0x82, 0x23, 0x83, 0x63, 0x30, 0xf7, 0x0d, 0x8a, 0x74, 0x89, 0xcc, 0x08, 0x8e, 0x0f, 0x4e, 0x0a, 0x4e, 0x4d, 0x19, 0x1f, 0xda, 0x5a, 0x95, 0x68, 0xbe, 0xdf, 0x1a, 0x9c, 0x1d, 0xda, 0x9e, 0x24, 0xdb, 0x7e, 0x96, 0x94, 0xc8, 0x39, 0xef, 0xcc, 0xb3, 0x2b, 0xf2, 0x55, 0x21, 0x1a, 0x41, 0x0b, 0x89, 0x2f, 0x09, 0x2e, 0x89, 0x6d, 0xa0, 0x67, 0xc9, 0xf2, 0x30, 0x0b, 0xae, 0x0e, 0xae, 0x23, 0xda, 0x98, 0xa0, 0x6d, 0xc1, 0x2d, 0xb4, 0xed, 0x0c, 0xee, 0x21, 0xda, 0x2f, 0x28, 0x65, 0x46, 0xca, 0x3c, 0x7a, 0xda, 0x1c, 0x8a, 0x0d, 0xae, 0x4c, 0xc1, 0xe3, 0xc1, 0x23, 0xc1, 0xe3, 0x22, 0x15, 0x52, 0x92, 0xe5, 0xd6, 0xaf, 0x90, 0x11, 0x3c, 0x65, + 0xfd, 0x22, 0x32, 0x2a, 0x1d, 0xf7, 0x60, 0x3f, 0x3a, 0x54, 0x03, 0x14, 0xb2, 0xf6, 0x31, 0xd1, 0xbb, 0xe9, 0xd1, 0x13, 0xf4, 0x1c, 0xa0, 0x67, 0x97, 0xb5, 0x55, 0xa6, 0x50, 0x3a, 0x9e, 0x6a, 0x55, 0x69, 0xce, 0xaf, 0x50, 0x76, 0x95, 0xfd, 0xb9, 0x68, 0x53, 0xb4, 0x0f, 0x3d, 0x3d, 0x73, 0x43, 0x6d, 0x42, 0x05, 0xa1, 0xe6, 0x44, 0x6d, 0x42, 0x6d, 0xe8, 0x29, 0xda, 0x35, 0xd4, 0x91, 0x36, 0x8b, 0xba, 0x87, 0x7a, 0x86, 0xfa, 0x24, 0xa8, 0x7f, 0xa2, 0x3e, 0x83, 0xca, 0xf7, 0xa0, 0xa1, 0xa1, 0xe1, 0x29, 0xc3, 0x2a, 0x53, 0x68, 0x54, 0x68, 0x32, 0x6d, 0x63, 0x93, 0x24, 0x94, 0x0e, 0x4d, 0xa8, 0x9a, 0xef, 0xec, 0x44, 0xe7, 0x8a, 0xba, 0xcd, 0x24, 0x9a, 0x46, 0x1b, 0xa5, 0x63, 0x99, 0xb1, 0xcc, 0x50, 0x99, 0xe0, 0x62, 0x0f, 0x9a, 0x0f, 0x5a, 0x14, 0x59, 0x28, 0xb8, 0x95, + 0x0e, 0xad, 0x09, 0xef, 0x0b, 0x2d, 0x0d, 0x2d, 0x8d, 0x2e, 0x0a, 0xad, 0x10, 0x69, 0x41, 0xf4, 0xd4, 0xaf, 0x4a, 0xeb, 0xcf, 0x22, 0xb1, 0x64, 0x42, 0x6b, 0x76, 0x55, 0xd0, 0xde, 0xd0, 0x01, 0x8b, 0xac, 0xeb, 0xe2, 0xda, 0x47, 0x4f, 0xd7, 0x43, 0xfc, 0x3a, 0x4d, 0xf8, 0x7d, 0x22, 0x6c, 0x07, 0x31, 0xda, 0x1c, 0x44, 0xf5, 0xce, 0x20, 0x5f, 0xf8, 0x18, 0xf6, 0x11, 0xf0, 0x63, 0x15, 0xf2, 0xd3, 0xa9, 0xd4, 0x70, 0x66, 0x92, 0x34, 0x8f, 0xa8, 0x69, 0xb8, 0x38, 0x92, 0x1f, 0xc9, 0x8f, 0xad, 0x12, 0x63, 0x0c, 0xd4, 0x39, 0xdc, 0x8e, 0xb6, 0x22, 0x4b, 0x1e, 0xc9, 0x0f, 0xf7, 0x0a, 0xf7, 0x4d, 0x99, 0x9d, 0x32, 0xa6, 0x32, 0x85, 0x07, 0x80, 0x97, 0x54, 0x92, 0x95, 0x84, 0x47, 0x84, 0x07, 0x27, 0x52, 0x94, 0x4e, 0x48, 0x47, 0x87, 0xc7, 0x85, 0x27, 0x56, 0xa2, 0x29, 0xe1, + 0xe9, 0x91, 0x79, 0x44, 0x33, 0x22, 0xf3, 0xa2, 0x7b, 0x23, 0x33, 0x68, 0xfd, 0x33, 0x97, 0x56, 0x3f, 0xa5, 0x44, 0x8b, 0xc3, 0xab, 0xce, 0x4a, 0xcb, 0xc0, 0xd7, 0x86, 0x37, 0x84, 0x37, 0x53, 0xae, 0xdd, 0xe1, 0x1d, 0x44, 0xbb, 0x89, 0xf6, 0xa5, 0x74, 0xa0, 0xb5, 0xd2, 0xc1, 0xe8, 0xd6, 0xf0, 0xc1, 0x04, 0x1d, 0xa6, 0xfb, 0x3a, 0x19, 0x91, 0x88, 0xb4, 0x88, 0x0b, 0x57, 0xc1, 0x75, 0xc0, 0x05, 0x05, 0x22, 0xb1, 0x94, 0xf1, 0x95, 0x89, 0x56, 0x59, 0xb4, 0x45, 0xb2, 0x88, 0xe7, 0x24, 0x24, 0x59, 0xd6, 0x56, 0x25, 0x5f, 0x8e, 0xd8, 0xca, 0xf3, 0x54, 0x48, 0x9b, 0xa1, 0x9d, 0x9a, 0x45, 0x0a, 0x63, 0xab, 0x22, 0x3d, 0x88, 0xba, 0x44, 0x3a, 0x80, 0x77, 0x89, 0x74, 0xc3, 0xef, 0x7e, 0xa0, 0x81, 0x91, 0xde, 0xe0, 0x56, 0x7a, 0x48, 0xd4, 0x13, 0x19, 0x16, 0x19, 0x19, 0x19, + 0x13, 0x19, 0x0f, 0x9a, 0x94, 0xa8, 0x69, 0x39, 0x4d, 0x4d, 0xd4, 0x79, 0x5e, 0xc5, 0x1d, 0xcc, 0x48, 0x48, 0x66, 0x83, 0x2f, 0x8c, 0x2c, 0xa1, 0x55, 0xce, 0xba, 0x0a, 0x5a, 0x4d, 0xdb, 0x46, 0xd0, 0x96, 0xc8, 0xce, 0x04, 0x6d, 0xab, 0x48, 0x25, 0x51, 0x74, 0xa9, 0x45, 0x91, 0x3d, 0x44, 0xfb, 0x23, 0x87, 0x22, 0x47, 0xa2, 0xdd, 0x23, 0xc7, 0x69, 0x3b, 0x15, 0x55, 0xe8, 0x89, 0xe7, 0x21, 0x0a, 0xc5, 0x66, 0x45, 0x6b, 0x44, 0xd3, 0xa3, 0xd9, 0xd1, 0x5c, 0x7a, 0x5a, 0x16, 0xd0, 0xd6, 0x26, 0xda, 0x31, 0xda, 0x35, 0xda, 0x3d, 0xda, 0x33, 0xda, 0x27, 0xda, 0x3f, 0x3a, 0x28, 0x3a, 0x34, 0x3a, 0x3c, 0x3a, 0x8a, 0x9e, 0x1f, 0x63, 0xa3, 0x13, 0xa2, 0x93, 0xa3, 0xd3, 0xce, 0xa0, 0x99, 0xd1, 0x39, 0x67, 0xd0, 0xfc, 0x5f, 0xa1, 0x45, 0xe7, 0xa0, 0xa5, 0x67, 0xd0, 0x9a, 0xe8, + 0x0a, 0xda, 0x7e, 0x0f, 0x6d, 0x25, 0x5a, 0x1f, 0xdd, 0x44, 0x7c, 0x3b, 0xd1, 0xd6, 0x73, 0xec, 0xb7, 0x47, 0x77, 0x45, 0xf7, 0x12, 0x1d, 0xa8, 0x16, 0x95, 0x45, 0x8f, 0x46, 0x4f, 0x9c, 0x9d, 0x62, 0xec, 0x1c, 0x44, 0xcf, 0xdb, 0x18, 0x2d, 0x35, 0x68, 0x86, 0x2e, 0x9f, 0x79, 0x53, 0x53, 0x86, 0x55, 0x8c, 0xfd, 0x7a, 0xb4, 0x82, 0x6e, 0x1a, 0x6b, 0x19, 0x6b, 0x17, 0xeb, 0x4c, 0xeb, 0x65, 0xb1, 0x62, 0xee, 0x15, 0xeb, 0x8b, 0xf9, 0x7c, 0x00, 0x78, 0x89, 0x58, 0x39, 0xc7, 0xc6, 0xc5, 0x26, 0xc6, 0xa6, 0xc4, 0xa6, 0xc7, 0x66, 0xd1, 0xea, 0x7c, 0x2e, 0x6d, 0x8b, 0x63, 0xcb, 0x62, 0xab, 0x62, 0x6b, 0x2d, 0xa2, 0xd5, 0xc5, 0x86, 0x04, 0x6d, 0x8e, 0x95, 0x26, 0x68, 0x47, 0x6c, 0x77, 0x82, 0xf6, 0xc5, 0x0e, 0x9e, 0x5e, 0x47, 0xa7, 0xb8, 0x68, 0x8d, 0x11, 0x4b, 0x49, 0x4b, + 0xc9, 0x4a, 0xc9, 0x49, 0xc9, 0x4f, 0x69, 0x96, 0x52, 0x48, 0xe3, 0xa8, 0x4b, 0x4a, 0x37, 0x5a, 0x2b, 0xf7, 0x4e, 0xe9, 0x47, 0x34, 0x30, 0x65, 0x08, 0xcd, 0x95, 0x23, 0x69, 0xfc, 0x8e, 0x4f, 0x99, 0x94, 0x32, 0x95, 0x9e, 0x55, 0xb3, 0x53, 0xe6, 0xa5, 0x2c, 0x14, 0x08, 0x2d, 0xec, 0xa7, 0x17, 0xe3, 0xcf, 0x0a, 0xdc, 0x1b, 0x56, 0x54, 0xcd, 0x53, 0x57, 0x93, 0xe4, 0x53, 0x91, 0x56, 0x26, 0x09, 0x2e, 0x77, 0x10, 0x12, 0xc5, 0x83, 0xf4, 0x54, 0xc1, 0xed, 0x48, 0xdb, 0xdd, 0x90, 0xbc, 0x88, 0xb3, 0x5a, 0x21, 0x7d, 0xb5, 0xe0, 0xec, 0x1b, 0x48, 0xd6, 0x81, 0xbf, 0x02, 0xf9, 0x61, 0xe4, 0xbf, 0x01, 0x92, 0x5b, 0xc0, 0x51, 0x8e, 0xf4, 0x3c, 0xf8, 0x10, 0xe4, 0xe9, 0x08, 0xde, 0x00, 0x92, 0x46, 0xe2, 0x8a, 0x7c, 0x31, 0x72, 0xfe, 0x88, 0xfa, 0x5c, 0x09, 0xf9, 0xfd, 0x90, + 0x7f, 0x09, 0xc9, 0xf5, 0x38, 0xfa, 0x33, 0x24, 0xbb, 0x05, 0x97, 0x39, 0x4a, 0xb0, 0xe3, 0xe8, 0x07, 0xc8, 0xff, 0x28, 0xee, 0xa2, 0x21, 0xd2, 0x7b, 0x91, 0xbf, 0x14, 0x79, 0x02, 0xe0, 0xcd, 0x90, 0x73, 0x2f, 0xee, 0xee, 0x61, 0x94, 0xb0, 0x0c, 0xf2, 0xee, 0xe0, 0x33, 0xc0, 0x9b, 0xe2, 0xdc, 0xbe, 0xe0, 0xb5, 0x21, 0x81, 0xdd, 0x29, 0x6d, 0x44, 0xfe, 0x71, 0x28, 0x01, 0xf5, 0x94, 0x3e, 0x42, 0xf9, 0x9f, 0x21, 0x6d, 0xf1, 0xf3, 0x90, 0xa7, 0x0e, 0xd2, 0x0e, 0x9c, 0xfb, 0x37, 0xf0, 0x25, 0x90, 0x3c, 0x83, 0xfc, 0x7f, 0x45, 0xfa, 0x5d, 0xd4, 0x73, 0x14, 0x78, 0x4f, 0x94, 0x89, 0x16, 0x93, 0xba, 0x59, 0x77, 0x8d, 0xb3, 0x0e, 0x21, 0xff, 0x22, 0x1c, 0xdd, 0x07, 0x3e, 0x16, 0x47, 0xad, 0xd2, 0xde, 0x06, 0x7f, 0x16, 0x57, 0xb4, 0xe1, 0x68, 0x18, 0x2d, 0x13, 0x83, 0x24, + 0x07, 0xe7, 0x3e, 0x82, 0xf4, 0x55, 0x90, 0xaf, 0xb0, 0xda, 0x0a, 0xf2, 0xe9, 0xe8, 0xb5, 0x05, 0x90, 0x97, 0x82, 0xa3, 0x5f, 0x58, 0x19, 0xd2, 0x0d, 0x71, 0xd6, 0x20, 0xe4, 0xbf, 0x09, 0x12, 0xeb, 0xde, 0x47, 0xe2, 0xdc, 0xf7, 0x70, 0xdd, 0x20, 0xae, 0xd8, 0x07, 0x47, 0x9f, 0x41, 0x5a, 0x81, 0xfc, 0x80, 0x65, 0x9d, 0x43, 0x92, 0x8a, 0xfc, 0xa7, 0xc0, 0x7f, 0x02, 0x7f, 0x01, 0x47, 0x07, 0x22, 0x8d, 0x6b, 0x49, 0xd0, 0x40, 0xe9, 0x56, 0x94, 0xdf, 0x09, 0x57, 0x3c, 0x1f, 0x92, 0x93, 0xc8, 0x73, 0x19, 0xca, 0xff, 0x0e, 0xe9, 0x8b, 0x90, 0xfe, 0x3b, 0x8e, 0x8e, 0x07, 0xef, 0x81, 0xfc, 0x21, 0x1c, 0xfd, 0x1b, 0x4a, 0x7b, 0x47, 0x70, 0x86, 0x3b, 0x95, 0xd0, 0xe3, 0x7c, 0x36, 0xee, 0xeb, 0x5f, 0x38, 0xb7, 0x0b, 0xf2, 0xf4, 0xc0, 0xb5, 0x9e, 0x42, 0xcb, 0x5b, 0xd7, 0xfa, + 0x0a, 0xbc, 0x05, 0xe4, 0x56, 0x0f, 0xde, 0x8e, 0x9c, 0x41, 0xc8, 0x8b, 0x91, 0xbe, 0x09, 0x69, 0x4b, 0xc3, 0xd1, 0xef, 0xec, 0x6b, 0xc8, 0x31, 0x2e, 0xf8, 0x72, 0xd4, 0xc4, 0x8b, 0xf4, 0x4b, 0x48, 0x47, 0x91, 0xbf, 0x0f, 0xd2, 0x57, 0x41, 0xbe, 0x07, 0x92, 0x4b, 0x91, 0xfe, 0x18, 0xe9, 0x6b, 0xc1, 0xa1, 0xc3, 0xd2, 0x0f, 0xe0, 0xd6, 0x48, 0x99, 0x09, 0xfe, 0x10, 0x24, 0x59, 0xb8, 0x96, 0xd5, 0xaa, 0xbd, 0xc1, 0x81, 0x85, 0xf0, 0x3b, 0xc1, 0x51, 0x73, 0x7e, 0x1d, 0xf8, 0xcd, 0xe0, 0x17, 0x20, 0xbf, 0xa5, 0x39, 0xc3, 0xc0, 0x55, 0xf0, 0x63, 0xe0, 0xb3, 0xc0, 0x1f, 0x04, 0x2f, 0xb2, 0x5a, 0x18, 0xbc, 0x1f, 0x78, 0x0b, 0xf0, 0x87, 0x71, 0x15, 0xe8, 0x0f, 0xb3, 0x7a, 0x4d, 0x87, 0xa4, 0x16, 0xd2, 0x17, 0x82, 0x37, 0x01, 0xc7, 0xc8, 0xe5, 0x8a, 0x35, 0x1b, 0xa0, 0x85, + 0x9f, 0x42, 0xeb, 0x75, 0x40, 0xfe, 0x2d, 0xe0, 0xb8, 0x8a, 0x94, 0x8a, 0xa3, 0x56, 0xbf, 0xff, 0x82, 0x76, 0x1b, 0x80, 0xf2, 0x4f, 0x41, 0x8e, 0x59, 0x85, 0xa1, 0x05, 0x18, 0x46, 0x16, 0xc3, 0x0c, 0xc3, 0x30, 0x76, 0x18, 0xda, 0x96, 0xad, 0x06, 0x5f, 0x08, 0xbe, 0x08, 0x1c, 0x7a, 0x28, 0x37, 0x42, 0x1a, 0xa3, 0x9e, 0x61, 0x06, 0xe3, 0xd0, 0x22, 0x8e, 0x3a, 0xf0, 0x96, 0xe0, 0x18, 0x8f, 0xec, 0x04, 0xd2, 0x1b, 0x91, 0x3e, 0x82, 0x34, 0xe6, 0x1f, 0xde, 0x06, 0x12, 0x8c, 0x5f, 0x9e, 0x81, 0xf4, 0x71, 0xa4, 0xd1, 0xe3, 0x1c, 0x38, 0x93, 0x9c, 0x0f, 0x5e, 0x0c, 0x9d, 0x31, 0x70, 0x47, 0x9f, 0xa0, 0xe6, 0x3f, 0x23, 0x3d, 0x1a, 0x1c, 0xbd, 0x2c, 0xf7, 0xc7, 0x59, 0x47, 0x70, 0x8f, 0x07, 0x91, 0xc7, 0x81, 0xf4, 0x4c, 0xf0, 0x9f, 0xc0, 0xd3, 0xad, 0xb1, 0x8c, 0xb6, 0x6a, + 0x8b, 0x34, 0x66, 0x21, 0x8e, 0xd1, 0xad, 0x40, 0xdf, 0x64, 0x13, 0xf2, 0xa3, 0x90, 0xdc, 0x02, 0x89, 0x35, 0xee, 0x30, 0x66, 0xa5, 0x55, 0x98, 0xc1, 0x5a, 0x83, 0x0f, 0x86, 0x04, 0x2d, 0xa6, 0xd4, 0xb0, 0x38, 0x24, 0xfb, 0x71, 0x56, 0x57, 0xf0, 0xd7, 0x20, 0x41, 0x4b, 0xf2, 0x12, 0xd4, 0x6a, 0x07, 0x24, 0xaf, 0x83, 0x63, 0xc6, 0x96, 0x6e, 0x43, 0xfd, 0x0b, 0x50, 0x42, 0x09, 0xd2, 0x6f, 0xe1, 0xdc, 0x6b, 0x90, 0xde, 0x80, 0x3c, 0x97, 0x83, 0x63, 0x1e, 0x90, 0x2c, 0xfd, 0x1f, 0x0f, 0x8e, 0x5e, 0x90, 0x3c, 0xc8, 0x6f, 0xcd, 0x66, 0x7f, 0x01, 0xbf, 0x08, 0x72, 0xcc, 0x72, 0x12, 0x74, 0x49, 0xee, 0x05, 0xf9, 0x1a, 0x48, 0x2e, 0x44, 0x5a, 0xc3, 0x59, 0xb8, 0x0b, 0xd9, 0x9a, 0x43, 0x32, 0x91, 0x13, 0x2d, 0x2f, 0x59, 0x33, 0xf6, 0x00, 0xf0, 0x26, 0xe0, 0x1f, 0x22, 0x27, + 0x5a, 0x95, 0xaf, 0x01, 0xc7, 0x4c, 0x2e, 0xe5, 0x42, 0x6e, 0xcd, 0x0c, 0x18, 0xef, 0x12, 0x46, 0x81, 0x84, 0xbe, 0x96, 0x6e, 0x44, 0x5b, 0x8d, 0x02, 0xd7, 0xac, 0x27, 0x20, 0xf2, 0x40, 0x9f, 0xe5, 0xce, 0xe0, 0x39, 0x28, 0xed, 0x13, 0x6b, 0xd6, 0x45, 0x9e, 0x9e, 0x48, 0x43, 0x33, 0xe5, 0x95, 0x90, 0xac, 0x45, 0xfb, 0x58, 0x5a, 0x51, 0x88, 0xf4, 0xb3, 0x90, 0x2b, 0xe2, 0x5c, 0x1b, 0x7a, 0x56, 0xc1, 0xe8, 0x90, 0xac, 0xb1, 0x60, 0x22, 0x0d, 0xed, 0x95, 0xdb, 0x82, 0x3f, 0x86, 0x32, 0x5f, 0x45, 0x4e, 0xcc, 0x39, 0x52, 0x73, 0xc8, 0x9d, 0xd0, 0x3d, 0x6b, 0xae, 0x1b, 0x01, 0x8e, 0x56, 0xe5, 0xd0, 0x3d, 0x1e, 0x43, 0x39, 0x57, 0xa0, 0x86, 0x5b, 0x30, 0xcb, 0xad, 0x45, 0x1a, 0x4f, 0x0a, 0xe9, 0x09, 0xf0, 0x07, 0xc0, 0xeb, 0x22, 0xff, 0x14, 0xe4, 0x39, 0x8e, 0x3c, 0x71, + 0x48, 0x76, 0x22, 0xfd, 0x15, 0x6a, 0x85, 0x36, 0x97, 0x9e, 0x84, 0x7c, 0x3d, 0xe4, 0xe3, 0x50, 0x07, 0xdc, 0xbb, 0x14, 0x81, 0xfc, 0x4d, 0x6b, 0x0c, 0xa2, 0x9c, 0xf7, 0x90, 0xa7, 0x31, 0xb8, 0x0f, 0xbc, 0x00, 0xdc, 0x7a, 0xf2, 0xba, 0x90, 0x1f, 0xbd, 0xcc, 0x07, 0x59, 0x57, 0x47, 0x39, 0xf3, 0xc1, 0xe7, 0x81, 0x63, 0xe4, 0x4a, 0xcf, 0xe1, 0x2a, 0x78, 0x0e, 0x4a, 0xb8, 0xa2, 0xf4, 0xa0, 0xc5, 0xad, 0x31, 0x82, 0xf4, 0x1c, 0xe4, 0x41, 0x1f, 0xc9, 0x2a, 0xe4, 0x18, 0x8f, 0x12, 0xee, 0x45, 0x7a, 0x04, 0x69, 0xeb, 0x19, 0x6a, 0x5d, 0xc5, 0xea, 0x4d, 0x6b, 0x1c, 0xd5, 0x03, 0x6f, 0x0f, 0xc9, 0xc7, 0x48, 0x63, 0xa6, 0x95, 0xad, 0x39, 0x13, 0xe3, 0x82, 0x5b, 0x47, 0xf1, 0x04, 0x94, 0x66, 0x59, 0x1c, 0x39, 0xcf, 0x43, 0x5a, 0x46, 0xfb, 0xa0, 0x97, 0x65, 0x09, 0x69, 0xd4, + 0x5f, 0x9e, 0x87, 0x3c, 0x98, 0xbb, 0x14, 0x6b, 0x86, 0xff, 0x1e, 0x47, 0x31, 0x63, 0xc8, 0xe9, 0x56, 0xda, 0x7a, 0x82, 0xe0, 0x5a, 0xb9, 0x28, 0xed, 0x1a, 0xeb, 0xba, 0x48, 0xa7, 0x59, 0x1c, 0xe5, 0x60, 0x26, 0xe1, 0x58, 0x6f, 0x48, 0xd6, 0x7a, 0xec, 0x73, 0xc8, 0x3f, 0x47, 0xda, 0x7a, 0xc6, 0xfd, 0x1d, 0xd7, 0xc2, 0xdc, 0xa8, 0xb8, 0x71, 0x74, 0x32, 0xe4, 0xc3, 0x91, 0xbe, 0x1d, 0x69, 0xcc, 0xf0, 0x12, 0x46, 0xab, 0x74, 0xb5, 0xc5, 0x71, 0x8f, 0xd6, 0xfc, 0x8f, 0xd9, 0x83, 0x7f, 0x83, 0xfc, 0x75, 0x21, 0xff, 0x1e, 0xb5, 0xc5, 0x33, 0x45, 0xda, 0x85, 0xa3, 0xbb, 0xc1, 0x31, 0x9f, 0xf0, 0x03, 0xd6, 0x7a, 0x06, 0xf9, 0xa1, 0x75, 0xd2, 0x0c, 0xa4, 0xaf, 0x40, 0x1a, 0xeb, 0x22, 0x7e, 0x08, 0x12, 0xaf, 0xf5, 0xec, 0xc6, 0x59, 0x58, 0x29, 0xf1, 0xa1, 0x90, 0x18, 0xd6, + 0x3a, 0x0d, 0xed, 0x60, 0xe5, 0x29, 0xb0, 0x46, 0x10, 0xb4, 0xc8, 0xaa, 0x4f, 0x1d, 0xa4, 0xbf, 0x86, 0x76, 0xa1, 0x3e, 0xec, 0x28, 0xca, 0x09, 0x41, 0x8e, 0x39, 0x93, 0xa1, 0x6d, 0x25, 0x09, 0xd7, 0x45, 0x2f, 0x48, 0x58, 0x09, 0xb0, 0x6f, 0xad, 0xf5, 0x09, 0xca, 0xb1, 0xda, 0xbf, 0x1d, 0xee, 0xc8, 0x1a, 0x41, 0xcd, 0x21, 0x99, 0x80, 0xfc, 0x77, 0x23, 0x5d, 0x1f, 0x69, 0x3c, 0xd7, 0xa4, 0x0b, 0x90, 0x6e, 0x86, 0x74, 0xbe, 0x35, 0x2e, 0x20, 0xb1, 0xb4, 0x02, 0xb3, 0x3a, 0x1f, 0x82, 0xb4, 0x75, 0x14, 0x6b, 0x54, 0x0e, 0x1d, 0xe6, 0x4b, 0x2d, 0x8e, 0xfc, 0x58, 0x2d, 0x73, 0xcc, 0x27, 0x7c, 0xa5, 0x95, 0xb6, 0x34, 0x1f, 0xe9, 0x7b, 0xad, 0xb9, 0x08, 0x57, 0xc1, 0x3a, 0x4a, 0x9a, 0x0a, 0x6e, 0xad, 0x48, 0x55, 0x8b, 0x43, 0x82, 0x79, 0x52, 0x0e, 0x5a, 0x1c, 0x25, 0x58, + 0x7a, 0xde, 0x00, 0x12, 0x3c, 0xc7, 0x25, 0x8c, 0x2f, 0xbe, 0x01, 0x7c, 0x9d, 0xd5, 0x4a, 0xc8, 0xd9, 0x1f, 0x69, 0x68, 0x3e, 0xb7, 0xf4, 0x0d, 0xba, 0xc7, 0xf0, 0x74, 0xe3, 0xd0, 0x34, 0x8e, 0x3b, 0x65, 0x58, 0xe7, 0xf0, 0x6c, 0xa4, 0xad, 0xa7, 0xff, 0x6b, 0xd6, 0x13, 0xdc, 0x1a, 0xc5, 0x90, 0x7f, 0x08, 0xfe, 0x23, 0xf8, 0x0e, 0x1c, 0x6d, 0x0c, 0xee, 0x03, 0x5f, 0x00, 0x5e, 0xdf, 0x2a, 0x19, 0x67, 0x61, 0x55, 0xcc, 0xac, 0x67, 0x1f, 0x8e, 0xca, 0xc8, 0x2f, 0xe3, 0x2a, 0x32, 0x66, 0x3f, 0xb9, 0x1e, 0x78, 0x5d, 0x2b, 0x8d, 0xfb, 0xc5, 0x1a, 0x5b, 0xea, 0x6e, 0xdd, 0x11, 0x4a, 0xb3, 0x56, 0x5f, 0x58, 0x95, 0x71, 0x1c, 0xe5, 0xd0, 0x6a, 0x8e, 0x99, 0x90, 0x63, 0xfd, 0x2c, 0x2d, 0xb6, 0xb4, 0xc8, 0x1a, 0xdd, 0x90, 0x58, 0xb6, 0x00, 0x56, 0x0e, 0xb2, 0x8c, 0xf2, 0x31, + 0x66, 0xa5, 0x4b, 0x70, 0x14, 0xf3, 0xa4, 0x82, 0x56, 0x92, 0x7e, 0xb1, 0x56, 0xc8, 0xd6, 0xa8, 0x01, 0x7f, 0x1c, 0xf9, 0x27, 0x20, 0xfd, 0x34, 0xf8, 0xab, 0xb8, 0x16, 0xc6, 0x1d, 0x47, 0x5b, 0xc9, 0x29, 0xd6, 0x6c, 0x09, 0xfe, 0x06, 0xea, 0xf6, 0x3a, 0x72, 0xb6, 0xb3, 0xe6, 0x19, 0x70, 0x6b, 0x56, 0xc7, 0x0c, 0x29, 0x59, 0xb3, 0x3d, 0xd6, 0xd5, 0xd2, 0x76, 0x9c, 0xf5, 0x2f, 0xa4, 0x6b, 0xa2, 0x95, 0xb0, 0xce, 0x97, 0xb0, 0xbe, 0x62, 0x96, 0xbd, 0x70, 0x9b, 0xd5, 0xe6, 0xa8, 0x33, 0xc3, 0x5d, 0xe0, 0xa9, 0x2a, 0x5f, 0x6a, 0x71, 0xc8, 0x61, 0x37, 0x49, 0x37, 0x20, 0x7d, 0x0f, 0xd2, 0xf7, 0xa0, 0x7f, 0xad, 0x75, 0x23, 0x5a, 0x5b, 0x42, 0xeb, 0xc9, 0x78, 0xc2, 0xb2, 0x6f, 0x51, 0x32, 0x9e, 0x32, 0x52, 0x57, 0x70, 0xcb, 0x66, 0xc1, 0xec, 0xcd, 0x61, 0x19, 0xf1, 0x77, + 0xad, 0xf5, 0x0c, 0x24, 0x8f, 0x83, 0x4f, 0x83, 0xa4, 0x96, 0x35, 0xef, 0xa1, 0x9c, 0x2f, 0x21, 0xe9, 0x02, 0x09, 0x66, 0x1e, 0x8e, 0x55, 0xb4, 0x74, 0x17, 0xae, 0x75, 0x39, 0xea, 0xb3, 0xcd, 0x9a, 0xf1, 0x2c, 0xfb, 0xc8, 0x9a, 0x0d, 0xac, 0xf1, 0x85, 0x96, 0x87, 0x46, 0xc9, 0x18, 0x4d, 0x36, 0xac, 0x7e, 0x15, 0x3c, 0xd3, 0x15, 0xb4, 0xa1, 0x8c, 0xfa, 0x33, 0x07, 0x6b, 0x23, 0x7c, 0x72, 0xf8, 0x1c, 0x26, 0x31, 0x0d, 0xf1, 0x5d, 0x0c, 0x91, 0x5d, 0x2e, 0x44, 0x76, 0x05, 0x54, 0x55, 0x35, 0x59, 0x4d, 0x35, 0xa2, 0x46, 0x58, 0x3a, 0x62, 0xba, 0x32, 0xcc, 0x37, 0xcc, 0xf5, 0xac, 0xa1, 0xb9, 0xc1, 0x7c, 0x8b, 0x35, 0xa2, 0x33, 0xc3, 0x4c, 0x66, 0x26, 0x73, 0x33, 0x2f, 0xbe, 0x73, 0xb5, 0x55, 0x7e, 0x52, 0x15, 0xd1, 0x7f, 0xe3, 0xd4, 0xf1, 0xea, 0x22, 0x75, 0xb1, 0xfa, + 0x9e, 0x5a, 0x2a, 0xfe, 0xdb, 0x9b, 0x16, 0xd4, 0xc2, 0xda, 0x18, 0x6d, 0x8a, 0x91, 0x22, 0xfe, 0x8b, 0x35, 0x13, 0x5f, 0x32, 0xd8, 0x93, 0xb4, 0xed, 0x4f, 0xda, 0xf6, 0x54, 0x49, 0x6f, 0x49, 0x6c, 0xdb, 0xaa, 0xc8, 0xcb, 0xb7, 0x9d, 0xd5, 0x38, 0x6f, 0xe7, 0x39, 0xce, 0x15, 0xbf, 0xc5, 0xd7, 0x1a, 0xc4, 0x97, 0x08, 0xc4, 0x57, 0x0b, 0x44, 0xbc, 0x80, 0x42, 0x9b, 0x41, 0x9b, 0x87, 0xb6, 0x10, 0x6d, 0x35, 0xaa, 0x6c, 0xe9, 0x89, 0x2d, 0x1b, 0x1b, 0x8f, 0xcf, 0x10, 0x1e, 0xff, 0xf1, 0x7c, 0xf8, 0xfd, 0x3f, 0x04, 0x3e, 0x0e, 0xfc, 0x28, 0x38, 0xb3, 0xe5, 0x32, 0xfe, 0x4b, 0x19, 0xf8, 0x4f, 0xe0, 0x9f, 0x0b, 0x4e, 0xf2, 0x9e, 0x94, 0x3e, 0x08, 0xfe, 0x05, 0xf8, 0x67, 0x82, 0x53, 0x69, 0xc5, 0xd4, 0xcf, 0xf3, 0xc4, 0xb9, 0xfc, 0x79, 0xe2, 0x61, 0xd6, 0x92, 0x75, 0x26, + 0x2a, 0x62, 0xc5, 0xac, 0x17, 0xcb, 0x63, 0x4d, 0xe9, 0x77, 0x3b, 0x48, 0x2c, 0xa9, 0x45, 0xc5, 0x38, 0xda, 0x57, 0xbc, 0x9d, 0x8d, 0xff, 0x8c, 0xfa, 0x14, 0x09, 0xce, 0x56, 0x83, 0xaf, 0x84, 0xe4, 0x69, 0xf0, 0xbf, 0x82, 0xbf, 0x09, 0xde, 0x06, 0xbc, 0x15, 0xf2, 0xc8, 0xe0, 0x0e, 0xf1, 0xbf, 0x16, 0xa5, 0x63, 0xca, 0x06, 0x4a, 0x5f, 0x67, 0xaf, 0x63, 0xaf, 0x6b, 0xaf, 0x6f, 0x6f, 0x60, 0x6f, 0x68, 0x6f, 0x64, 0x6f, 0x6c, 0x6f, 0x62, 0x3f, 0xdf, 0x7e, 0x81, 0xbd, 0x85, 0xbd, 0xb5, 0xbd, 0x0d, 0x69, 0x49, 0x57, 0xfb, 0x25, 0xf6, 0x22, 0xfb, 0xa5, 0xf6, 0x6e, 0xf6, 0xcb, 0xec, 0xdd, 0xed, 0x97, 0xdb, 0x8b, 0xed, 0x57, 0xd8, 0x7b, 0xd8, 0xaf, 0xb4, 0xf7, 0xb4, 0x1f, 0xb4, 0x1f, 0x22, 0xfd, 0xf9, 0xde, 0x7e, 0xd4, 0xfe, 0x83, 0xfd, 0x98, 0xfd, 0x47, 0xfb, 0x49, 0xfb, + 0x2f, 0xf6, 0x53, 0xf6, 0xb8, 0xca, 0x54, 0xae, 0x4a, 0xaa, 0xac, 0x2a, 0xaa, 0x9d, 0x74, 0x4a, 0x53, 0x75, 0xd5, 0x50, 0x4d, 0xd5, 0xa9, 0xba, 0x55, 0x8f, 0x88, 0x18, 0x24, 0xdd, 0x4a, 0x53, 0xcf, 0x53, 0xd3, 0xd5, 0x0c, 0x35, 0x53, 0xad, 0xad, 0x66, 0xa9, 0x75, 0xd4, 0x6c, 0xb5, 0xae, 0x5a, 0xcf, 0x94, 0x4c, 0x9b, 0xa9, 0x9a, 0x9a, 0x69, 0x98, 0x4e, 0xd3, 0x6d, 0xfa, 0xcc, 0x80, 0x19, 0x32, 0x6b, 0x9b, 0x0d, 0xcc, 0x86, 0x66, 0x9e, 0xd9, 0xd8, 0x6c, 0x62, 0x36, 0x35, 0x9b, 0x99, 0x2d, 0xcc, 0x42, 0xf3, 0x42, 0xb3, 0x8b, 0x39, 0xd6, 0xbc, 0xdf, 0x9c, 0x60, 0x4e, 0x34, 0x9f, 0x37, 0x17, 0x99, 0x6b, 0xcc, 0xb5, 0xa4, 0xa9, 0x1b, 0x48, 0xab, 0x7b, 0xfd, 0x2e, 0x3f, 0x87, 0xfe, 0xac, 0x10, 0x1e, 0x0e, 0x1d, 0xe1, 0xe1, 0xd0, 0x13, 0x1e, 0x0e, 0xd7, 0xb0, 0xfb, 0xd9, + 0x23, 0xac, 0x0f, 0xfb, 0x27, 0xd1, 0x6f, 0x79, 0x35, 0x08, 0x7f, 0x86, 0x87, 0xe0, 0xcf, 0xf0, 0x3c, 0xfc, 0x19, 0xde, 0xe1, 0xfb, 0xf9, 0x41, 0xb6, 0x59, 0xca, 0x4d, 0xf8, 0x33, 0x14, 0xb0, 0x0f, 0xa4, 0xa6, 0xd2, 0xf9, 0xec, 0x23, 0xa9, 0xad, 0xf4, 0x28, 0xfb, 0x58, 0x7a, 0x4c, 0x9a, 0xc3, 0x73, 0xa5, 0x67, 0xa5, 0xe7, 0x78, 0x33, 0x69, 0xa1, 0xb4, 0x90, 0xb7, 0x90, 0x16, 0x49, 0xfb, 0x79, 0x4b, 0xe9, 0x80, 0x9c, 0xc5, 0xef, 0x90, 0xdb, 0xca, 0xed, 0xf9, 0x72, 0xf9, 0x42, 0xb9, 0x0b, 0x5f, 0x29, 0x5f, 0x2a, 0x5f, 0xca, 0x5f, 0x93, 0x2f, 0x93, 0x7b, 0xf0, 0xb5, 0x72, 0x4f, 0xb9, 0x37, 0xdf, 0x20, 0xf7, 0x91, 0xfb, 0xf0, 0x77, 0xe4, 0xbe, 0x72, 0x5f, 0xbe, 0x59, 0xbe, 0x49, 0x1e, 0xc2, 0xdf, 0x95, 0x87, 0xca, 0x43, 0xf9, 0xfb, 0x72, 0x89, 0x5c, 0xc2, 0x4b, 0xe5, + 0xdb, 0xe5, 0xdb, 0xf9, 0x07, 0xf2, 0x08, 0x79, 0x04, 0xdf, 0x26, 0x8f, 0x94, 0x47, 0xf1, 0x0f, 0xe5, 0x27, 0xe5, 0x65, 0x7c, 0x87, 0xf0, 0x8b, 0xe0, 0x87, 0xe1, 0x11, 0xd1, 0x05, 0x1e, 0x11, 0x3d, 0xe1, 0x11, 0x31, 0x44, 0x29, 0x53, 0xca, 0xa4, 0x5b, 0x94, 0x53, 0xca, 0x29, 0x69, 0x28, 0xbc, 0x23, 0x6e, 0x85, 0x77, 0x44, 0x89, 0xad, 0xbd, 0xad, 0x83, 0x34, 0xd2, 0xb6, 0xc6, 0xb6, 0x56, 0x1a, 0x65, 0x7b, 0xc3, 0xf6, 0xa6, 0x74, 0x8f, 0xed, 0x53, 0xdb, 0xe7, 0xd2, 0x58, 0x11, 0x13, 0x27, 0x3d, 0x00, 0xef, 0x88, 0x07, 0xe1, 0x1d, 0xf1, 0x08, 0xbc, 0x23, 0x1e, 0x85, 0x5f, 0xc4, 0xb3, 0xf0, 0x88, 0x58, 0x28, 0x22, 0xe0, 0xa4, 0xe7, 0x45, 0x04, 0x9c, 0xb4, 0x5d, 0x44, 0xc0, 0x49, 0x87, 0x45, 0x04, 0x9c, 0x5c, 0x20, 0x22, 0xe0, 0xe4, 0x81, 0x9a, 0x53, 0xfb, 0x46, 0x1e, + 0x01, 0x7f, 0x89, 0xab, 0x74, 0x43, 0x77, 0x28, 0x7d, 0xe1, 0x2f, 0x71, 0x1d, 0xfc, 0x25, 0x86, 0xc3, 0x5f, 0xe2, 0x4e, 0xf8, 0x4b, 0x8c, 0x84, 0xbf, 0xc4, 0x5d, 0xf0, 0x97, 0xb8, 0x17, 0xfe, 0x12, 0xd3, 0x85, 0xbf, 0x84, 0xf2, 0xb5, 0xfe, 0x96, 0xfe, 0x93, 0xf2, 0xbd, 0x88, 0x11, 0xb3, 0x65, 0x19, 0x0d, 0x8d, 0x6d, 0xb6, 0x26, 0x22, 0x9e, 0xcb, 0xf6, 0x89, 0xf0, 0x9d, 0xb0, 0xed, 0x12, 0xbe, 0x13, 0xb6, 0xc3, 0xc2, 0x3b, 0xc2, 0xf6, 0x9d, 0x88, 0xc9, 0xb2, 0x9d, 0x14, 0x7e, 0x11, 0xb6, 0x53, 0xe6, 0x46, 0xf3, 0x1d, 0xbb, 0x22, 0xe2, 0xb0, 0xec, 0x1a, 0xbb, 0x8e, 0xc6, 0xf1, 0xfc, 0xdf, 0xd8, 0x26, 0x57, 0x23, 0xcf, 0x7f, 0x73, 0x9b, 0xf6, 0x1b, 0xc7, 0x67, 0xd2, 0x36, 0xe7, 0x1c, 0xc7, 0x16, 0x55, 0xd9, 0x96, 0x26, 0x6d, 0x2b, 0x68, 0x5b, 0x73, 0x7a, 0xf3, 0x4a, + 0xe7, 0xde, 0x90, 0x67, 0xbd, 0xb5, 0x55, 0x92, 0xad, 0x39, 0x2d, 0x2b, 0x2f, 0xa3, 0x6a, 0x99, 0x38, 0x6f, 0x93, 0xb5, 0x55, 0x29, 0xb7, 0x3c, 0xee, 0xca, 0x8a, 0xb5, 0xaa, 0x56, 0x64, 0x15, 0xe6, 0xca, 0xfb, 0x31, 0x7f, 0x6d, 0x54, 0xfa, 0xda, 0xf5, 0x40, 0xd3, 0x40, 0x53, 0x47, 0xc0, 0xdf, 0xd5, 0xdf, 0xdf, 0x11, 0x73, 0xd6, 0xf0, 0x0f, 0x0f, 0x6f, 0xf6, 0x4f, 0x08, 0xed, 0x72, 0x2e, 0x0d, 0x97, 0x3a, 0xd2, 0xfc, 0xf3, 0x1d, 0x59, 0xfe, 0xa5, 0xfe, 0x35, 0x8e, 0x1c, 0x77, 0x37, 0xff, 0x26, 0xff, 0x76, 0xff, 0x5e, 0x47, 0xbe, 0xbf, 0x2c, 0xd8, 0xc3, 0xd1, 0x2c, 0xd4, 0x35, 0xe0, 0xf0, 0x97, 0x39, 0x4f, 0x04, 0x7c, 0x8e, 0xc2, 0x40, 0xaa, 0x73, 0x51, 0x20, 0x2f, 0x90, 0xe7, 0x58, 0xee, 0xe8, 0x80, 0x77, 0xb6, 0xc5, 0x81, 0xbe, 0x81, 0x92, 0xc0, 0x44, 0xcf, 0xae, + 0xc0, 0xac, 0xc0, 0xac, 0xe0, 0x91, 0xe0, 0x10, 0x11, 0x07, 0x13, 0x58, 0x10, 0x58, 0x6c, 0xc5, 0xc2, 0xf8, 0x26, 0x39, 0x06, 0xfa, 0xa6, 0x3a, 0x86, 0x38, 0x86, 0x85, 0x8c, 0xc0, 0x8e, 0x60, 0x0e, 0x91, 0x16, 0x74, 0x05, 0x5d, 0x8e, 0x91, 0xb4, 0x8d, 0x09, 0xc6, 0x3c, 0x86, 0x63, 0x7c, 0x30, 0x2b, 0xd8, 0xcc, 0xb3, 0xd5, 0x33, 0x56, 0xbc, 0x63, 0x75, 0x4c, 0x0a, 0x0e, 0x0b, 0x4c, 0x09, 0x4f, 0x77, 0x1d, 0x0c, 0x4e, 0x0d, 0xae, 0xf3, 0x36, 0x75, 0x4c, 0x75, 0xb5, 0x0c, 0x2e, 0x77, 0xcc, 0x10, 0x11, 0x33, 0x8e, 0x85, 0xa1, 0x69, 0xc1, 0x3d, 0xee, 0xb4, 0xe0, 0x91, 0x90, 0xe2, 0x2a, 0x09, 0xac, 0x75, 0x2c, 0x71, 0x8d, 0xf3, 0x8d, 0xf7, 0x4f, 0x70, 0x2c, 0x0f, 0x6d, 0x0f, 0xd5, 0x70, 0xac, 0x0e, 0x97, 0x86, 0xd2, 0x1d, 0xeb, 0x42, 0xd9, 0xc1, 0x1e, 0xc1, 0x61, 0x9e, + 0x39, 0x54, 0x87, 0x48, 0x28, 0xe4, 0xd8, 0xe8, 0xd8, 0xe2, 0xd8, 0x16, 0x1a, 0xe4, 0xd8, 0x19, 0xd8, 0xe1, 0x9d, 0x28, 0x62, 0x5e, 0x3c, 0x2b, 0x1c, 0x87, 0xdc, 0xab, 0x3d, 0x93, 0x1d, 0x47, 0x42, 0x73, 0x1c, 0xc7, 0x43, 0xf3, 0x1d, 0xa7, 0x42, 0x2b, 0xc2, 0x83, 0xbd, 0x79, 0x22, 0xe6, 0xc5, 0xb7, 0xd1, 0xe9, 0xf1, 0x1c, 0xf5, 0x16, 0x79, 0x53, 0x43, 0x9b, 0x42, 0xdb, 0xbd, 0x76, 0x67, 0xc8, 0x59, 0xc3, 0x99, 0xee, 0xb5, 0x7b, 0xed, 0x81, 0xce, 0xce, 0x6c, 0x67, 0x76, 0x78, 0x95, 0x33, 0xd7, 0x3f, 0xc7, 0x59, 0xe0, 0xef, 0xef, 0x6c, 0x1e, 0xee, 0xec, 0xdd, 0xe1, 0xdd, 0xe1, 0x6c, 0x13, 0x1c, 0x18, 0x3a, 0xe1, 0x1b, 0xe3, 0x5d, 0xeb, 0xec, 0xe8, 0xed, 0x1b, 0x76, 0x84, 0x53, 0xbd, 0x73, 0xbd, 0xa5, 0xde, 0xd2, 0x70, 0xd3, 0x70, 0x4b, 0x5f, 0x87, 0x60, 0xef, 0x70, + 0x4b, 0xef, 0x66, 0x11, 0x6f, 0x22, 0xde, 0x77, 0x79, 0xa7, 0x3b, 0x7b, 0x12, 0xf5, 0x71, 0xf6, 0x0f, 0x9d, 0x08, 0xce, 0x08, 0x94, 0x38, 0x07, 0x79, 0x07, 0x04, 0x35, 0xe7, 0x50, 0xcf, 0x1a, 0x11, 0x6d, 0xe2, 0xeb, 0xe0, 0x1c, 0xeb, 0x5e, 0xe8, 0xd9, 0xee, 0x9c, 0xe0, 0xd9, 0xea, 0x4b, 0x73, 0x8d, 0xf0, 0x2e, 0x70, 0x4e, 0x0e, 0x8e, 0x74, 0x6e, 0x0d, 0xac, 0x15, 0xfe, 0x37, 0x56, 0x5c, 0x48, 0x68, 0xab, 0x77, 0x81, 0x7f, 0x82, 0x73, 0x69, 0x68, 0xab, 0x73, 0x85, 0x73, 0x4d, 0xf8, 0x98, 0x7f, 0x6f, 0x68, 0xbb, 0xaf, 0x8b, 0x73, 0xbd, 0xf3, 0x84, 0x73, 0x93, 0xbf, 0x7b, 0xa0, 0x5e, 0xa8, 0x39, 0xa2, 0x3a, 0x76, 0x85, 0xa7, 0x38, 0xf7, 0x7a, 0x0f, 0x7b, 0x3b, 0x87, 0x4b, 0xfd, 0xc3, 0xfd, 0xcd, 0x9d, 0x07, 0x02, 0xc5, 0xce, 0x32, 0x97, 0xc3, 0x3f, 0xc8, 0x79, 0x34, + 0x90, 0xe7, 0x3c, 0xe1, 0x62, 0x81, 0x62, 0xe1, 0x65, 0x13, 0x2e, 0x0d, 0x6a, 0x22, 0xda, 0x82, 0x5a, 0x33, 0x35, 0xdc, 0xce, 0x95, 0x19, 0xcc, 0x11, 0x9e, 0x36, 0xae, 0xa6, 0xc1, 0xde, 0xae, 0x55, 0xae, 0x5e, 0xae, 0x96, 0xee, 0x42, 0x4f, 0x81, 0x67, 0x5a, 0xf0, 0x88, 0x67, 0xbd, 0xab, 0x1d, 0xf5, 0xee, 0x02, 0x77, 0x4e, 0xb0, 0xd0, 0x3d, 0xd2, 0x55, 0xe2, 0xea, 0xec, 0x2a, 0x75, 0xbb, 0x10, 0x23, 0xd1, 0xcb, 0x9d, 0xe5, 0x2a, 0xa1, 0xde, 0x12, 0x51, 0x11, 0x73, 0x5d, 0x83, 0xa9, 0x77, 0x63, 0xc2, 0xbf, 0xc6, 0x35, 0xda, 0x35, 0x2e, 0xd8, 0x0f, 0x71, 0x0b, 0x53, 0xac, 0xd8, 0x85, 0xc0, 0x6e, 0xef, 0x38, 0xd7, 0xaa, 0xf0, 0xac, 0x90, 0x12, 0x2c, 0xf4, 0x8d, 0x14, 0xba, 0x12, 0x9a, 0x86, 0x78, 0x85, 0xc5, 0x81, 0x1d, 0xae, 0x65, 0xbe, 0xfd, 0xde, 0x71, 0xf4, 0x7b, + 0x55, 0x22, 0x1e, 0x61, 0x83, 0x6b, 0xb3, 0x77, 0x77, 0xb0, 0x99, 0x88, 0x43, 0x20, 0x9d, 0xe9, 0x2d, 0xbc, 0x69, 0xbc, 0xd3, 0x5d, 0xfb, 0x68, 0xb3, 0xe2, 0x04, 0x8e, 0x85, 0xe7, 0xba, 0x67, 0x78, 0x0f, 0x86, 0x17, 0x5b, 0x3e, 0x35, 0x96, 0x07, 0x8d, 0x77, 0x59, 0xc8, 0x70, 0xa7, 0x79, 0xe7, 0x7a, 0xa6, 0x25, 0x79, 0xf0, 0x17, 0xba, 0x3b, 0x84, 0x0a, 0xdc, 0x5d, 0xc2, 0xfb, 0xca, 0xfd, 0x66, 0x48, 0x43, 0x89, 0xc2, 0xd3, 0xc3, 0x7d, 0xdd, 0x03, 0xbd, 0xed, 0x2a, 0xbc, 0xf8, 0x13, 0x7e, 0xfc, 0xde, 0x89, 0xee, 0x85, 0xee, 0x19, 0xc1, 0x53, 0x1e, 0xc5, 0x37, 0x26, 0xe1, 0x31, 0x23, 0xbc, 0xf6, 0x57, 0xbb, 0xd7, 0x05, 0x76, 0x87, 0x4b, 0x84, 0xdf, 0x7e, 0x38, 0xd5, 0xd7, 0x23, 0xbc, 0x81, 0xae, 0xb4, 0x2d, 0x38, 0x32, 0xa4, 0x9c, 0xf6, 0xe1, 0xf7, 0xad, 0x13, 0x5e, + 0xfc, 0xe1, 0x7d, 0xc2, 0x8f, 0x3f, 0xb0, 0xa3, 0xb2, 0x1f, 0xbf, 0xef, 0xb8, 0xf0, 0xe5, 0xf7, 0xee, 0xf6, 0xb4, 0xf1, 0x87, 0x2c, 0x2f, 0x18, 0x78, 0xc0, 0xf4, 0xf7, 0x0c, 0x0a, 0x4d, 0x10, 0x5e, 0x2f, 0xde, 0x65, 0xe1, 0x0d, 0x9e, 0x51, 0xde, 0xdd, 0x81, 0x65, 0x9e, 0xb1, 0xbe, 0x34, 0x5f, 0x33, 0xcf, 0x04, 0xef, 0x44, 0xcb, 0x03, 0xc6, 0xb3, 0x88, 0xc6, 0x67, 0x81, 0x67, 0x8e, 0x7f, 0xbb, 0x67, 0x7e, 0xa8, 0x4d, 0xf8, 0x70, 0xa0, 0xc4, 0x3b, 0xce, 0xb3, 0xc8, 0xb3, 0xd7, 0xb3, 0xd4, 0xb3, 0x02, 0x5e, 0x30, 0x7b, 0x83, 0xfd, 0x92, 0x3d, 0x61, 0x42, 0x9b, 0x3c, 0x07, 0xbc, 0x4d, 0xbd, 0x79, 0x9e, 0x32, 0x6f, 0xc4, 0xf2, 0x86, 0x11, 0xef, 0x3a, 0xbd, 0x76, 0x7f, 0xff, 0x40, 0x3d, 0xd2, 0x8f, 0x76, 0x5e, 0x87, 0x37, 0xd3, 0x9b, 0xe9, 0x9f, 0xe3, 0xf5, 0x59, 0x7e, + 0x31, 0x38, 0x9a, 0xe9, 0xdf, 0x1e, 0xda, 0x1a, 0x9e, 0x22, 0x3c, 0x64, 0xbc, 0x4d, 0x29, 0x8f, 0xf0, 0x90, 0x69, 0xe7, 0x3f, 0x41, 0xbc, 0x88, 0x8e, 0x76, 0xf6, 0x77, 0xf5, 0x2e, 0x10, 0xfe, 0x32, 0xde, 0x5e, 0xde, 0x0d, 0xde, 0xb5, 0xc2, 0x4b, 0x86, 0x34, 0x7c, 0xb0, 0xb7, 0x84, 0xf4, 0x79, 0x04, 0x6d, 0xa3, 0xbd, 0x9b, 0x85, 0x8f, 0x4c, 0xf0, 0x14, 0xbc, 0x64, 0x16, 0xf8, 0xd6, 0x79, 0x67, 0xc1, 0x57, 0xa6, 0x14, 0x1a, 0xbf, 0x20, 0xdc, 0x34, 0x30, 0x2b, 0x3c, 0x97, 0x46, 0xf6, 0x48, 0xef, 0x62, 0xca, 0xb3, 0xcc, 0xbb, 0x8a, 0x52, 0x6b, 0xc5, 0x51, 0xe1, 0x3b, 0x13, 0xf2, 0x50, 0xea, 0x58, 0x78, 0x04, 0xd5, 0x68, 0x87, 0xbf, 0x79, 0x20, 0xcf, 0x3f, 0x99, 0x5a, 0x61, 0x87, 0xf0, 0xa3, 0x09, 0xec, 0x08, 0xb7, 0x13, 0xbe, 0x34, 0x3e, 0xcd, 0x7b, 0xd2, 0xa7, 0x09, + 0x0f, 0x9a, 0x60, 0xc0, 0xe7, 0xf2, 0x97, 0xf9, 0x02, 0xfe, 0xfe, 0xe1, 0x93, 0xc2, 0x93, 0xe6, 0xb4, 0x1f, 0x8d, 0xf0, 0xa2, 0x09, 0xcc, 0x12, 0xde, 0x33, 0xbe, 0xde, 0xbe, 0x7e, 0xbe, 0x81, 0x81, 0x29, 0xc2, 0x6b, 0x26, 0xa8, 0x91, 0x9e, 0x2d, 0x80, 0xdf, 0xcc, 0xf8, 0xd0, 0x4c, 0xe1, 0x3b, 0x13, 0x74, 0x09, 0xdf, 0x19, 0x9a, 0x2b, 0xc6, 0x5a, 0xbe, 0x33, 0xfe, 0xa1, 0xc1, 0x21, 0xa4, 0xb1, 0x85, 0xbe, 0xe5, 0xc1, 0x6d, 0x96, 0x0f, 0x8d, 0x6f, 0x8b, 0x78, 0xdf, 0xed, 0x5f, 0x2f, 0xfc, 0x68, 0xe8, 0x7a, 0x7b, 0x82, 0xc3, 0x84, 0x0f, 0x8d, 0xe5, 0x41, 0x53, 0xee, 0x3f, 0xe3, 0xaf, 0x11, 0x1a, 0x1a, 0x5a, 0x11, 0xda, 0x14, 0x1c, 0x13, 0xdc, 0xe6, 0x4f, 0xf7, 0x67, 0x53, 0xaf, 0x34, 0x3d, 0x1b, 0xf9, 0x73, 0xcf, 0x2e, 0xaf, 0x92, 0xab, 0xe0, 0x9c, 0x47, 0x9a, 0x53, + 0x8b, 0x34, 0xf5, 0xb7, 0x49, 0x78, 0xe6, 0x74, 0x0d, 0x14, 0xf9, 0xbb, 0x83, 0x7a, 0x26, 0xa8, 0xbf, 0xf0, 0xd4, 0x81, 0x9f, 0x4e, 0x82, 0x22, 0x12, 0x8d, 0xf6, 0xe1, 0x67, 0xbc, 0xab, 0xdf, 0xec, 0x1f, 0x15, 0xde, 0x7c, 0x96, 0x77, 0xf8, 0x20, 0xff, 0x04, 0xff, 0xd8, 0xd3, 0xe9, 0x4a, 0x47, 0x96, 0x82, 0x0b, 0xcf, 0x9f, 0x24, 0x0a, 0x6d, 0xa2, 0x39, 0x65, 0x26, 0xcd, 0xb4, 0x9b, 0xce, 0x45, 0xfe, 0x99, 0xb4, 0xcd, 0xa9, 0x42, 0xf3, 0x93, 0x78, 0x75, 0x68, 0xbe, 0x7f, 0x3e, 0x69, 0x03, 0xfc, 0x8c, 0xfc, 0xeb, 0xfd, 0x6b, 0x68, 0xb3, 0x68, 0x93, 0x7f, 0x53, 0x60, 0x9f, 0xe0, 0xa0, 0x72, 0xaf, 0x23, 0xa2, 0x8a, 0xab, 0xef, 0x4d, 0xaa, 0xc9, 0x01, 0xbc, 0xb9, 0x4f, 0x22, 0xe1, 0x99, 0xe4, 0x2f, 0xab, 0x2a, 0x0d, 0x6d, 0xaf, 0x2a, 0x39, 0x3b, 0xf9, 0x8f, 0x0a, 0x4f, + 0x92, 0x00, 0x0b, 0x30, 0xff, 0x09, 0xe1, 0xd7, 0x14, 0x70, 0x24, 0x79, 0x59, 0x24, 0x28, 0x10, 0x49, 0x90, 0xaf, 0x22, 0x15, 0x09, 0xd4, 0x23, 0x4a, 0x25, 0xaa, 0x17, 0xc8, 0x44, 0xba, 0x1e, 0x7c, 0x9f, 0xaa, 0x4f, 0x42, 0x1f, 0x5a, 0x26, 0x51, 0x3b, 0x8b, 0x2a, 0xae, 0xd9, 0x39, 0x50, 0x54, 0xa9, 0x0e, 0x45, 0xa7, 0x29, 0xf1, 0xbb, 0x17, 0xa8, 0xd8, 0xda, 0x07, 0xf3, 0xcb, 0x29, 0x30, 0x22, 0xb1, 0xef, 0x6b, 0xa5, 0x02, 0x03, 0x4e, 0x1f, 0xab, 0x4c, 0xc2, 0x1b, 0xeb, 0xf4, 0x59, 0x96, 0x57, 0x16, 0x8d, 0x1e, 0x22, 0x78, 0x66, 0x59, 0x34, 0x2b, 0xd8, 0x4d, 0xf8, 0x68, 0x59, 0x72, 0xa2, 0xc2, 0xe0, 0x91, 0x88, 0x56, 0xe1, 0x2f, 0x71, 0x30, 0xb0, 0x0a, 0x7c, 0xc1, 0xe9, 0x74, 0xd2, 0xb1, 0xc5, 0x81, 0x65, 0x96, 0x3c, 0xb0, 0x2a, 0xb8, 0x2d, 0x21, 0x13, 0xfe, 0x5d, + 0x9b, 0x93, 0x88, 0x9e, 0x06, 0xc2, 0xcf, 0x2b, 0xb8, 0xcd, 0xa2, 0xc0, 0x3e, 0xe2, 0x5a, 0xe0, 0xa0, 0x58, 0x4f, 0x04, 0x5d, 0x81, 0xc3, 0xd6, 0x1e, 0xe9, 0x63, 0x49, 0xe9, 0x93, 0x89, 0x94, 0x84, 0x95, 0x47, 0xa0, 0x82, 0x62, 0xc1, 0x34, 0x5a, 0x79, 0x64, 0x85, 0x86, 0x0b, 0x0e, 0xca, 0xa1, 0xbb, 0x13, 0x9e, 0x5f, 0xf0, 0xfd, 0x4a, 0x5c, 0x65, 0x4b, 0x82, 0x0b, 0xea, 0x16, 0xec, 0x11, 0x3e, 0x5c, 0x4e, 0xc1, 0x7e, 0xe0, 0xbd, 0xc5, 0x26, 0xd2, 0xc1, 0x81, 0x55, 0x8f, 0x25, 0x93, 0x38, 0x1a, 0x1c, 0x98, 0x9c, 0x07, 0xd2, 0x61, 0x68, 0xa7, 0x61, 0xa0, 0x49, 0x44, 0x63, 0x82, 0x23, 0xc1, 0xc7, 0xc0, 0xab, 0x6c, 0x52, 0x70, 0x36, 0x68, 0x06, 0xad, 0x85, 0x04, 0xb7, 0x7e, 0xad, 0x0b, 0xce, 0x0b, 0x2e, 0x0c, 0x2e, 0x09, 0x2e, 0x0f, 0xae, 0x06, 0xad, 0x2b, 0x6f, 0x8f, + 0x44, 0x5d, 0x37, 0x26, 0xf6, 0xa7, 0xa5, 0x5b, 0x2c, 0x09, 0xad, 0x9e, 0xb6, 0x59, 0x9e, 0x61, 0x21, 0x43, 0xf8, 0x83, 0x25, 0x68, 0x3f, 0x6d, 0x47, 0x82, 0xc7, 0x89, 0x4e, 0x85, 0xdb, 0x59, 0x14, 0x52, 0x68, 0x33, 0x90, 0x4a, 0xe6, 0xdd, 0x13, 0x44, 0x0f, 0x37, 0x78, 0x7d, 0xa5, 0x87, 0xb2, 0x85, 0xcf, 0x32, 0x3c, 0xae, 0x2a, 0x53, 0x9b, 0x0a, 0xbf, 0xab, 0xaa, 0xd4, 0xb1, 0x92, 0x57, 0x56, 0x65, 0xea, 0x7e, 0x06, 0xf5, 0x0f, 0xf5, 0xa4, 0xed, 0x6c, 0xd4, 0xe7, 0x1c, 0xf2, 0xe1, 0x44, 0xf0, 0xe5, 0x0a, 0x8d, 0x25, 0x1a, 0x7e, 0xd6, 0xfd, 0x28, 0xda, 0x26, 0x84, 0x26, 0x13, 0xcd, 0xfc, 0x4d, 0xb2, 0x3c, 0xb7, 0xe6, 0x0b, 0x7f, 0xac, 0xb3, 0xd2, 0xa2, 0x73, 0xc8, 0x57, 0x84, 0xd6, 0x08, 0x7f, 0xac, 0x70, 0xbd, 0x8a, 0xf9, 0x28, 0xd9, 0x2b, 0x4b, 0xf8, 0x64, 0x95, + 0x85, 0x8e, 0x86, 0x4e, 0xc0, 0xc7, 0x4a, 0x78, 0x59, 0xf9, 0x12, 0xbe, 0x54, 0x96, 0xdf, 0x94, 0xf0, 0x96, 0x6a, 0x59, 0xee, 0x1d, 0x25, 0xfc, 0xa2, 0x2a, 0x68, 0x40, 0x05, 0x0d, 0x4e, 0x90, 0xf0, 0x85, 0xb2, 0xc8, 0xf2, 0x7f, 0xa2, 0xd5, 0x73, 0xc2, 0xcf, 0x69, 0x31, 0xbc, 0x99, 0x2c, 0x5f, 0x26, 0xe1, 0xcd, 0x24, 0x3c, 0x99, 0xf6, 0x25, 0x79, 0x2e, 0x69, 0x11, 0xd7, 0x59, 0xe3, 0x0b, 0x6b, 0xfe, 0xbe, 0x28, 0x43, 0xfe, 0xb3, 0xed, 0xb3, 0xdf, 0x8a, 0x35, 0x64, 0x3f, 0xc1, 0x0a, 0xf9, 0x01, 0x16, 0x09, 0x22, 0xba, 0xe4, 0x3b, 0x45, 0x5a, 0x52, 0xc5, 0xb9, 0xbc, 0xaf, 0x48, 0xcb, 0xdf, 0x09, 0x6e, 0x1f, 0x00, 0x79, 0x1e, 0x72, 0xfe, 0x6a, 0x54, 0x22, 0x3f, 0x89, 0x9c, 0x67, 0xc4, 0x26, 0x4a, 0xb7, 0x42, 0x5e, 0x0a, 0x5e, 0x13, 0xe5, 0x6c, 0x46, 0xf9, 0x12, 0xf8, + 0x1f, 0x88, 0x59, 0x4c, 0xc4, 0xb8, 0xbc, 0x84, 0x32, 0x7f, 0x41, 0xf9, 0x61, 0x94, 0x79, 0x01, 0x78, 0x26, 0x24, 0xef, 0x81, 0xff, 0x0b, 0xfc, 0x10, 0xe4, 0xc9, 0x71, 0x8d, 0xb5, 0x94, 0xd1, 0x02, 0x77, 0x87, 0xbc, 0x37, 0xf2, 0xfc, 0x7b, 0x91, 0x8e, 0x9d, 0xce, 0x1e, 0xef, 0xc8, 0x0f, 0xa0, 0xcc, 0x33, 0xa3, 0x1e, 0xaf, 0x3a, 0x1d, 0x3b, 0x78, 0xae, 0x08, 0x48, 0x2b, 0x9a, 0xd0, 0x8a, 0x80, 0x94, 0x32, 0x70, 0x8f, 0x49, 0x71, 0x90, 0xfc, 0x1a, 0x48, 0xa6, 0xa3, 0x1f, 0x93, 0x62, 0x22, 0xe5, 0x07, 0x21, 0x07, 0x9e, 0x26, 0xf5, 0xc3, 0x51, 0x2b, 0x56, 0xcf, 0x3a, 0x6b, 0x17, 0xe4, 0x75, 0xc1, 0x2f, 0xc7, 0x51, 0xd4, 0x50, 0x9e, 0x89, 0xbe, 0xf6, 0x28, 0x33, 0x84, 0x0f, 0x0e, 0x8e, 0xa2, 0x77, 0xf8, 0x22, 0xf0, 0x87, 0x21, 0xb9, 0x1a, 0xdc, 0x09, 0x49, 0x52, 0xb4, + 0x25, 0xb3, 0xe2, 0x1d, 0x7b, 0xe1, 0x68, 0x07, 0x94, 0xd6, 0x18, 0x69, 0xe0, 0x7b, 0x56, 0x14, 0x26, 0x7f, 0x16, 0xe9, 0xad, 0xe0, 0x36, 0x5c, 0x37, 0x39, 0x22, 0xf3, 0x05, 0xc8, 0xaf, 0xc3, 0x59, 0x7f, 0x01, 0xef, 0x81, 0x72, 0x22, 0x90, 0x7f, 0x0a, 0x0e, 0xac, 0x92, 0x4f, 0x01, 0x4f, 0x8a, 0xe0, 0x94, 0x2e, 0x42, 0xfe, 0x6c, 0xc8, 0x11, 0x7b, 0xc4, 0x51, 0x7f, 0xf6, 0x25, 0x4a, 0x40, 0x64, 0xa7, 0x64, 0x59, 0xde, 0x68, 0xe1, 0x38, 0xb4, 0x3a, 0x39, 0xbe, 0x93, 0xe1, 0xee, 0x64, 0x4b, 0x07, 0x2c, 0x3d, 0xff, 0x1a, 0x25, 0x80, 0xf3, 0x69, 0xe0, 0x18, 0x2f, 0x3c, 0xf7, 0x34, 0x26, 0xc9, 0xbf, 0x40, 0x7a, 0x0b, 0xce, 0x1a, 0x0d, 0xc9, 0x1e, 0xf0, 0x1d, 0x28, 0xff, 0x46, 0xc8, 0x3d, 0x48, 0x97, 0x80, 0x73, 0x70, 0x6b, 0xc4, 0x41, 0x27, 0xf9, 0x2b, 0xd6, 0xbd, 0x40, + 0xce, 0xc0, 0x2d, 0x0d, 0x1c, 0x05, 0x5e, 0x1f, 0x47, 0x9f, 0x04, 0x7f, 0x02, 0x92, 0x74, 0xf0, 0x34, 0xf0, 0x4b, 0xc0, 0x2d, 0xed, 0xda, 0x86, 0x6b, 0xb5, 0x45, 0x1a, 0x6d, 0x25, 0xa5, 0x5a, 0xf7, 0x88, 0x73, 0x3f, 0x46, 0xda, 0x1a, 0xc5, 0x56, 0x04, 0xd8, 0x47, 0xc8, 0x8f, 0x9e, 0x92, 0xac, 0x9e, 0x6a, 0x8f, 0x9c, 0xef, 0x80, 0x7f, 0x06, 0x0e, 0x2d, 0x3a, 0x33, 0x32, 0x55, 0xb6, 0xfa, 0xa8, 0x25, 0x38, 0x22, 0xf9, 0xf8, 0x1c, 0xa4, 0xa1, 0x3f, 0xd2, 0xf9, 0xe0, 0xe8, 0x5f, 0xe9, 0x6e, 0xf0, 0x8e, 0x28, 0xed, 0x77, 0x46, 0xaf, 0x26, 0x22, 0x44, 0xab, 0x1f, 0xc3, 0xda, 0xaa, 0x6a, 0x24, 0x2b, 0x07, 0x56, 0x2c, 0xa7, 0xa0, 0x0e, 0x6e, 0xf0, 0xdb, 0xc0, 0x2d, 0xdd, 0xb6, 0x74, 0x20, 0x07, 0x5c, 0x01, 0xd7, 0x50, 0xcf, 0x6d, 0x96, 0xc6, 0xe2, 0x5c, 0xb4, 0xbf, 0x54, + 0x03, 0x12, 0xe4, 0xe7, 0x71, 0x48, 0x22, 0xd6, 0x0c, 0x83, 0xab, 0x3c, 0x07, 0x89, 0x0b, 0x47, 0xa1, 0xed, 0xd2, 0x3a, 0x70, 0xc4, 0x65, 0xca, 0xd6, 0x78, 0xb9, 0x0c, 0x47, 0x37, 0x82, 0x57, 0x3f, 0x46, 0x16, 0xf8, 0xbc, 0x04, 0xac, 0x5e, 0xb2, 0x46, 0xdf, 0x04, 0x5c, 0x17, 0xa3, 0x83, 0xbf, 0x2b, 0xb8, 0xd2, 0x02, 0xf2, 0xe6, 0x48, 0x27, 0xc5, 0xce, 0x4a, 0x97, 0x42, 0x8e, 0x16, 0x28, 0x8f, 0xa3, 0x45, 0xda, 0x8a, 0xa6, 0x8d, 0xa1, 0x1c, 0x44, 0xd3, 0xf2, 0xe5, 0xd6, 0x98, 0xb2, 0xcd, 0x12, 0xfe, 0x7a, 0x89, 0x19, 0xa0, 0x6f, 0x79, 0x7c, 0xad, 0x85, 0xc0, 0x57, 0x8a, 0xac, 0x6d, 0x66, 0xb5, 0x80, 0xf5, 0xf4, 0xc1, 0xd1, 0xaf, 0xac, 0xd1, 0x77, 0x46, 0xac, 0xad, 0x35, 0xfa, 0x96, 0x22, 0x4f, 0x19, 0xf8, 0x5b, 0xe0, 0xaf, 0x22, 0x7f, 0x2d, 0x8c, 0xaf, 0xbf, 0x81, + 0x5b, 0x63, 0x01, 0x7a, 0xc8, 0x7d, 0xe0, 0x05, 0xe0, 0xd6, 0x8c, 0xf7, 0xff, 0xc7, 0xec, 0xfe, 0x91, 0x98, 0x5d, 0x3c, 0xef, 0xac, 0xc8, 0x5d, 0xa9, 0x33, 0x5a, 0x1e, 0x23, 0x82, 0x7f, 0x8f, 0xde, 0xc9, 0xb2, 0x22, 0x7a, 0x91, 0xb6, 0x9e, 0xcb, 0x49, 0x71, 0xbd, 0x52, 0x03, 0x6b, 0x36, 0x40, 0xba, 0x11, 0xce, 0xb5, 0x9e, 0xa7, 0xe8, 0x1d, 0xc9, 0x8b, 0xa3, 0x67, 0x44, 0xfd, 0x4a, 0x06, 0x8e, 0x42, 0xdf, 0xa4, 0x6b, 0xad, 0x39, 0xb3, 0x6a, 0x1c, 0x30, 0xc3, 0xd5, 0x19, 0x46, 0xe2, 0xaf, 0xc7, 0x04, 0x4b, 0xd6, 0xbb, 0x9e, 0x33, 0x23, 0x83, 0xaf, 0x40, 0x9d, 0xad, 0xf8, 0x60, 0xe8, 0xbf, 0x8c, 0xe7, 0xbe, 0xe4, 0xb0, 0x46, 0x2e, 0xce, 0xb5, 0x46, 0x07, 0x56, 0x02, 0x56, 0xf4, 0x70, 0x22, 0x0a, 0xd6, 0x8f, 0xeb, 0xce, 0x07, 0x1f, 0x02, 0x3e, 0xde, 0x4a, 0xa3, 0x7c, + 0x8c, 0x5f, 0x6e, 0xe9, 0x6d, 0xa5, 0x38, 0x63, 0x48, 0xfa, 0x80, 0xdf, 0x68, 0xe9, 0x24, 0xd2, 0x8f, 0xa3, 0x7c, 0x2b, 0xca, 0xb6, 0x21, 0xd2, 0x49, 0xb1, 0xc8, 0x92, 0x8a, 0xfa, 0x04, 0x2d, 0x8e, 0xb3, 0x7e, 0xc4, 0x59, 0xc7, 0xad, 0xd9, 0xcc, 0x9a, 0x2b, 0xc0, 0xed, 0x90, 0x6f, 0x00, 0xff, 0x06, 0x92, 0xdf, 0x11, 0xbb, 0x8c, 0xfc, 0xc9, 0x11, 0xcc, 0x18, 0x47, 0x52, 0x17, 0x8b, 0xa3, 0x3d, 0xf1, 0xb4, 0xe2, 0xd6, 0x55, 0x12, 0x91, 0xcd, 0x48, 0x5b, 0xf1, 0xcd, 0xd7, 0x58, 0x3d, 0x6b, 0x45, 0x39, 0x23, 0x5d, 0xcf, 0xfa, 0x5e, 0x05, 0x6a, 0x9e, 0x88, 0x78, 0x46, 0xfa, 0x3f, 0x14, 0xf7, 0x2c, 0x75, 0xc5, 0xd5, 0x93, 0xa3, 0x9f, 0x77, 0x43, 0xb2, 0x13, 0xdc, 0x8a, 0x84, 0xc6, 0x9b, 0x41, 0xc9, 0x7a, 0xae, 0x25, 0x47, 0x45, 0x5b, 0xcf, 0x5f, 0xeb, 0x89, 0xf9, 0x7b, + 0x23, 0xa4, 0x6f, 0xc0, 0x59, 0xf7, 0x58, 0xd1, 0xd2, 0x28, 0x47, 0xae, 0x1a, 0x03, 0x2d, 0x61, 0xf5, 0xcb, 0xd7, 0x83, 0xaf, 0x01, 0xff, 0xd5, 0xa8, 0x68, 0xfe, 0x06, 0xca, 0x3c, 0x57, 0x54, 0x74, 0x96, 0x35, 0x5e, 0xaa, 0xc6, 0x46, 0xcb, 0x18, 0x29, 0x67, 0x44, 0x48, 0x8b, 0xef, 0x6f, 0x33, 0x66, 0xf0, 0xe1, 0xb6, 0x7c, 0xa6, 0x74, 0xe8, 0xd8, 0xb5, 0x98, 0xc5, 0xae, 0xbb, 0xb3, 0x64, 0x30, 0xeb, 0x7a, 0x43, 0xc9, 0xf5, 0x37, 0xb1, 0xf9, 0x83, 0xff, 0x32, 0x6c, 0x08, 0x2b, 0x15, 0x1f, 0xdb, 0x65, 0x2c, 0x1e, 0xc7, 0xff, 0xf7, 0x53, 0x99, 0x8b, 0xf9, 0x58, 0x94, 0xa5, 0xb1, 0x4c, 0x56, 0x9f, 0xe5, 0xb3, 0x0b, 0x58, 0x2b, 0xd6, 0x81, 0x5d, 0xcc, 0xba, 0xb3, 0xab, 0x58, 0x5f, 0x76, 0x03, 0x1b, 0xc2, 0x6e, 0x67, 0xa3, 0xc4, 0xb7, 0x52, 0x2f, 0xef, 0xde, 0x2e, 0x4d, + 0xbc, 0x5d, 0xa4, 0xf3, 0xbc, 0x74, 0x15, 0x8d, 0xf9, 0x59, 0x8c, 0xd5, 0x66, 0x39, 0xac, 0x31, 0x2b, 0x64, 0x17, 0xb2, 0xae, 0xec, 0x72, 0xd6, 0x8b, 0xfd, 0x85, 0x0d, 0x64, 0xb7, 0xb0, 0xe1, 0xec, 0xee, 0x44, 0x3e, 0x89, 0xe9, 0x74, 0x95, 0x14, 0x96, 0xc5, 0x1a, 0xb0, 0x02, 0xd6, 0x9a, 0x75, 0x64, 0x97, 0xb0, 0x62, 0x76, 0x35, 0xeb, 0xc7, 0x6e, 0x64, 0x43, 0xd9, 0x1d, 0x6c, 0x74, 0x45, 0x5d, 0xbc, 0x4c, 0xa6, 0xab, 0x04, 0x59, 0x0d, 0x56, 0x87, 0xe5, 0xb2, 0x26, 0xac, 0x0d, 0xeb, 0xc4, 0x8a, 0xd8, 0x15, 0xac, 0x37, 0xbb, 0x8e, 0x0d, 0x62, 0xb7, 0xb2, 0x11, 0xec, 0x1e, 0xe6, 0x61, 0x52, 0xf7, 0x4b, 0x2f, 0x49, 0x63, 0x85, 0xc5, 0xdd, 0x2f, 0x4e, 0x63, 0x83, 0x13, 0xf7, 0xa0, 0xe0, 0xad, 0x7c, 0x88, 0xd5, 0x64, 0xe7, 0xb1, 0x6c, 0xd6, 0x90, 0x35, 0x65, 0xcd, 0x59, + 0x5b, 0xd6, 0x99, 0x5d, 0xca, 0x7a, 0xe0, 0x2d, 0xe7, 0x4d, 0xac, 0x84, 0xdd, 0xc9, 0xc6, 0x24, 0x72, 0xdb, 0x98, 0x83, 0xca, 0x09, 0xb3, 0x54, 0x96, 0xce, 0xea, 0xb2, 0x3c, 0x76, 0x3e, 0x6b, 0xc1, 0xda, 0xb1, 0x8b, 0x58, 0x37, 0x76, 0x25, 0xeb, 0xc3, 0xae, 0xa7, 0x52, 0x6f, 0x63, 0x23, 0xd9, 0xbd, 0x89, 0xdc, 0x76, 0xe6, 0xa4, 0x9a, 0x45, 0x58, 0x2d, 0x96, 0xc1, 0xea, 0xb1, 0x46, 0xac, 0x19, 0x6b, 0xc9, 0xda, 0xb3, 0x2e, 0xec, 0x32, 0xd6, 0x93, 0x5d, 0xcb, 0x06, 0xb0, 0x9b, 0xd9, 0x30, 0x76, 0x17, 0x1b, 0xcb, 0xee, 0xbb, 0xee, 0x2f, 0x83, 0x87, 0xc9, 0x81, 0xea, 0xf1, 0xdb, 0xae, 0x97, 0x6b, 0x54, 0x87, 0xf7, 0x1f, 0x7c, 0xe3, 0x0d, 0x72, 0xfa, 0x59, 0xf8, 0x90, 0x5b, 0x6e, 0x96, 0xb3, 0xab, 0xc3, 0x07, 0x94, 0xfc, 0xe5, 0x3a, 0x39, 0xb7, 0x3a, 0x9c, 0x0a, 0xfe, + 0x8b, 0xdc, 0xf5, 0x0c, 0x5e, 0x3c, 0x78, 0xc8, 0xed, 0x37, 0xcb, 0xbd, 0xaa, 0xc3, 0x89, 0x95, 0xc8, 0x7d, 0xab, 0xc3, 0x6f, 0x11, 0xf9, 0x07, 0x54, 0x8b, 0x97, 0xf4, 0x1f, 0x22, 0x0f, 0x3e, 0x93, 0x0f, 0x15, 0x47, 0x4b, 0xaa, 0xc3, 0x6f, 0x13, 0x6d, 0x3e, 0x21, 0x89, 0x8f, 0x38, 0x43, 0x32, 0x26, 0x91, 0xbe, 0x71, 0xc8, 0x00, 0x79, 0x61, 0xb5, 0xf8, 0x6d, 0x79, 0x8d, 0xe4, 0xc9, 0xd5, 0xe4, 0xf9, 0xf2, 0xb4, 0x6a, 0xf2, 0xc6, 0xf2, 0xac, 0x6a, 0xf2, 0x02, 0x79, 0x5e, 0xb5, 0xf8, 0xed, 0xfd, 0x6e, 0xa3, 0xda, 0x56, 0x8b, 0x0f, 0xbd, 0x4d, 0x5e, 0x52, 0x1d, 0x3e, 0x4c, 0xb4, 0xea, 0xf2, 0xea, 0x70, 0x1a, 0xd6, 0x12, 0xcd, 0x10, 0x35, 0x30, 0x27, 0x49, 0xe0, 0x69, 0xe0, 0x01, 0xfc, 0xff, 0x16, 0x85, 0x46, 0xa2, 0x1d, 0xff, 0x8f, 0x4e, 0xa7, 0x71, 0x6f, 0xd2, 0xa8, + 0x74, 0xd2, 0x4c, 0xe4, 0xa1, 0xd1, 0x26, 0xce, 0x13, 0xff, 0x7d, 0x4e, 0xe4, 0xb5, 0x78, 0x08, 0xdc, 0x0f, 0x1e, 0x04, 0xaf, 0x09, 0x1e, 0x46, 0x5e, 0x4e, 0xe3, 0x9e, 0xe1, 0x0a, 0xe7, 0x41, 0x6a, 0xf1, 0x74, 0xc8, 0x32, 0x68, 0x56, 0x13, 0xfb, 0xda, 0x34, 0x03, 0x09, 0x69, 0x1d, 0x70, 0x1f, 0x78, 0x14, 0x3c, 0x42, 0xb3, 0x40, 0x31, 0xcd, 0x12, 0xc3, 0xd8, 0x38, 0x36, 0x8d, 0x6d, 0x66, 0xbb, 0xd8, 0x21, 0x76, 0x92, 0x3b, 0x78, 0x0d, 0x9e, 0xc3, 0x5b, 0xf2, 0xae, 0xbc, 0x37, 0x1f, 0xcc, 0x47, 0xf1, 0x49, 0x7c, 0x16, 0x5f, 0xc4, 0x57, 0xf3, 0xcd, 0x7c, 0x17, 0x3f, 0xc4, 0x4f, 0x4a, 0x0e, 0xa9, 0x86, 0xd4, 0x54, 0xea, 0x28, 0x0d, 0x93, 0xc6, 0x49, 0xd3, 0xa4, 0x79, 0xd2, 0x32, 0x69, 0xbd, 0xb4, 0x4d, 0xda, 0x27, 0x1d, 0x95, 0x15, 0x39, 0x20, 0x67, 0xd2, 0x7d, 0x31, + 0xcc, 0x91, 0x5c, 0xcc, 0xc7, 0x4a, 0x9f, 0x2a, 0xbf, 0x07, 0x56, 0xf9, 0xdd, 0xbf, 0xf2, 0x6f, 0x5b, 0x53, 0xfc, 0xb6, 0x51, 0x6b, 0x44, 0xa8, 0xcd, 0xb2, 0x69, 0x96, 0xe2, 0xd4, 0x56, 0xcc, 0x36, 0x2f, 0xb1, 0x3f, 0x6c, 0xed, 0xed, 0x2d, 0x13, 0xfb, 0xf1, 0x89, 0xfd, 0xd6, 0xa4, 0x72, 0xe8, 0xb7, 0x1a, 0xab, 0xf2, 0xbb, 0x4f, 0x95, 0xdf, 0x8b, 0x2b, 0xff, 0xd6, 0x58, 0x95, 0xdf, 0xc5, 0x55, 0x7e, 0x2f, 0xac, 0xfc, 0x5b, 0x97, 0xaa, 0xfc, 0xee, 0x5d, 0xe5, 0xf7, 0x8a, 0xca, 0xbf, 0x8d, 0x50, 0xe5, 0xfb, 0x34, 0x86, 0x57, 0x39, 0xbe, 0xaf, 0xf2, 0x6f, 0x33, 0xbf, 0x72, 0x7e, 0x73, 0x6c, 0x95, 0xdf, 0x87, 0x2a, 0xff, 0x76, 0xf4, 0xa8, 0xf2, 0x7b, 0x6d, 0xe5, 0xdf, 0xce, 0xde, 0x55, 0x7e, 0x6f, 0xa8, 0xfc, 0xdb, 0xd5, 0xaf, 0xca, 0xef, 0xcd, 0xf8, 0x2d, 0x91, 0x7e, + 0xfa, 0xac, 0x1a, 0x79, 0x3a, 0x27, 0xf6, 0xd3, 0x2b, 0xe7, 0xf4, 0x1c, 0xc5, 0x6f, 0x85, 0x34, 0x38, 0x84, 0xff, 0x8e, 0x88, 0x5c, 0xbe, 0x4c, 0x6b, 0xef, 0xef, 0x67, 0xed, 0x03, 0x73, 0xac, 0x7d, 0x70, 0x7f, 0xe5, 0xb3, 0xc3, 0xc9, 0xfa, 0x42, 0xcf, 0xc3, 0xf0, 0xf8, 0x2a, 0xbf, 0x67, 0x54, 0xfe, 0x9d, 0x52, 0x58, 0xf9, 0xfc, 0x94, 0x31, 0x55, 0x7e, 0xef, 0xae, 0xf2, 0x7b, 0x67, 0x95, 0xdf, 0xc7, 0xaa, 0xfc, 0x3e, 0x52, 0xe5, 0xf7, 0xd1, 0x2a, 0xbf, 0x0f, 0x63, 0x55, 0x21, 0x7e, 0x4b, 0x6c, 0x23, 0x3d, 0xe3, 0xfb, 0xf3, 0xef, 0xd9, 0x0d, 0xb6, 0x14, 0xbd, 0xae, 0xdd, 0x67, 0x7c, 0x6e, 0xcf, 0xb6, 0xd7, 0xb3, 0xe7, 0xd8, 0x73, 0xed, 0x79, 0xf6, 0x7c, 0x7b, 0x81, 0xbd, 0xa9, 0xbd, 0x99, 0xbd, 0xb9, 0xbd, 0xa5, 0xbd, 0xad, 0x7d, 0xbf, 0xfd, 0x80, 0xfd, 0x47, 0xfb, + 0x31, 0xfb, 0x0f, 0xf6, 0xa3, 0xf6, 0xef, 0xed, 0x47, 0xce, 0xf0, 0x8f, 0xb2, 0xd9, 0xbf, 0xb2, 0x7f, 0x6d, 0xff, 0x46, 0x0d, 0xa9, 0x86, 0xea, 0x52, 0x3d, 0xf0, 0x96, 0x72, 0xa8, 0x5e, 0xd5, 0xa7, 0xfa, 0xd5, 0x80, 0x1a, 0x54, 0xdf, 0xd7, 0xc6, 0xea, 0xf5, 0xf5, 0x3a, 0x7a, 0xbe, 0xc9, 0x4c, 0xd9, 0xb4, 0x9b, 0xba, 0x69, 0x9a, 0x2e, 0xd3, 0xaf, 0x2a, 0x66, 0xd8, 0xac, 0x61, 0xff, 0xd9, 0xcc, 0x35, 0x1b, 0x99, 0x05, 0xe6, 0x05, 0x66, 0x4b, 0xb3, 0xb5, 0xd9, 0xd1, 0xbc, 0xd8, 0xbc, 0xd4, 0xec, 0x6e, 0xf6, 0x30, 0xaf, 0x36, 0xfb, 0x98, 0x7f, 0x31, 0xfb, 0x9b, 0x37, 0x98, 0x83, 0xcc, 0x9b, 0xcd, 0xa1, 0xe6, 0x6d, 0xe6, 0x70, 0x73, 0xa4, 0x79, 0xb7, 0x39, 0xc6, 0xbc, 0xcf, 0x1c, 0x6f, 0x3e, 0x64, 0xbe, 0x6e, 0xae, 0x37, 0xdf, 0xa2, 0x7b, 0xf9, 0x0b, 0xad, 0x41, 0xae, + 0x63, 0x03, 0x6c, 0x31, 0x5b, 0x4d, 0xbb, 0xd7, 0x9e, 0x72, 0x0e, 0x1f, 0xb0, 0x36, 0xf6, 0x76, 0xf6, 0x0b, 0x7f, 0xd5, 0x0b, 0xec, 0x2a, 0xfb, 0x97, 0xf0, 0x04, 0x3b, 0xae, 0xea, 0xf0, 0xf1, 0x12, 0x1e, 0x5e, 0x51, 0x35, 0xa6, 0xa6, 0xa8, 0x35, 0xd4, 0x9a, 0x6a, 0xaa, 0xfa, 0x9e, 0x36, 0x46, 0x8f, 0xea, 0x31, 0xbd, 0xa1, 0x11, 0x87, 0x87, 0x97, 0xe5, 0xdf, 0x65, 0x79, 0x77, 0xa5, 0x98, 0x75, 0xcc, 0x06, 0xf0, 0xee, 0x3a, 0xed, 0xd9, 0x55, 0x64, 0x5e, 0x66, 0x5e, 0x61, 0xf6, 0x32, 0xaf, 0x31, 0xfb, 0x9a, 0xd7, 0x99, 0x03, 0xcc, 0x1b, 0xcd, 0xc1, 0xe6, 0x2d, 0x66, 0x89, 0x79, 0xbb, 0x79, 0xa7, 0x39, 0xca, 0xbc, 0x07, 0x9e, 0x5f, 0x13, 0x2b, 0x3c, 0xbe, 0x5c, 0x4c, 0xd1, 0x2e, 0xd2, 0x27, 0x6b, 0x97, 0xe9, 0x8f, 0x41, 0x37, 0x32, 0xd8, 0x1d, 0x5a, 0xb1, 0x3e, 0x03, + 0x7a, 0x26, 0xfa, 0x8e, 0x73, 0x37, 0xe5, 0x2a, 0xa1, 0x35, 0xd1, 0x23, 0xec, 0x79, 0xf6, 0x02, 0x7b, 0x91, 0xbd, 0xc4, 0x96, 0xb1, 0xe5, 0x6c, 0x05, 0x5b, 0xc5, 0x56, 0xb3, 0xb5, 0x6c, 0x5d, 0xf9, 0xff, 0x7d, 0x55, 0xee, 0x53, 0xc6, 0x29, 0xf7, 0x2b, 0x0f, 0x29, 0x13, 0x95, 0x29, 0xca, 0x5f, 0x95, 0xa9, 0xca, 0xdf, 0x94, 0x69, 0xca, 0xa3, 0xca, 0xc3, 0xca, 0x63, 0xfc, 0x01, 0x3e, 0x81, 0x3f, 0xc8, 0x27, 0xf2, 0x87, 0xf8, 0x24, 0xe5, 0x71, 0xfe, 0xb0, 0x32, 0x93, 0x4f, 0xe6, 0x8f, 0xf0, 0x29, 0xfc, 0xaf, 0x7c, 0x2a, 0xff, 0x1b, 0x9f, 0xa6, 0xdc, 0xce, 0x1f, 0x55, 0x86, 0xf3, 0xe9, 0xca, 0x64, 0xe5, 0x11, 0xdb, 0x61, 0x63, 0xb7, 0xb1, 0x9f, 0xae, 0xdb, 0x94, 0x3d, 0xcc, 0x16, 0xb2, 0x45, 0x6c, 0x31, 0x5b, 0xc2, 0xfe, 0xc1, 0xfe, 0xc9, 0x5e, 0x66, 0x2b, 0xd9, 0x2b, + 0xec, 0x35, 0xf6, 0x3a, 0x6f, 0xc7, 0xdb, 0xf3, 0x0e, 0xfc, 0x42, 0xde, 0x85, 0x5f, 0x4c, 0xb3, 0x6b, 0x11, 0xbf, 0x94, 0x77, 0xe3, 0x97, 0xf1, 0xee, 0xfc, 0x72, 0x5e, 0xcc, 0xaf, 0xa0, 0xd9, 0xb6, 0x2f, 0xbf, 0x8d, 0x0f, 0xe3, 0xb7, 0xf3, 0xe1, 0xfc, 0x0e, 0x3e, 0x82, 0xdf, 0xc9, 0x47, 0xf2, 0xbb, 0x68, 0xfe, 0xbd, 0x9b, 0x8f, 0xe6, 0xf7, 0xf0, 0x31, 0xfc, 0x5e, 0x3e, 0x96, 0xdf, 0xc7, 0xc7, 0xf1, 0xfb, 0xc9, 0xea, 0x1a, 0xa1, 0xdc, 0x63, 0x6b, 0x6c, 0x7c, 0x6a, 0x7c, 0x41, 0x77, 0xd8, 0x81, 0xd5, 0x52, 0x4e, 0x29, 0x27, 0x95, 0x5f, 0xf8, 0x12, 0x9b, 0xcf, 0xe6, 0xb7, 0xe9, 0x36, 0xaf, 0xcd, 0xb0, 0x99, 0x36, 0x87, 0xcd, 0xa9, 0x1c, 0x57, 0xe2, 0x36, 0x66, 0xe3, 0x36, 0xc9, 0x26, 0xdb, 0x14, 0x9b, 0xdd, 0xa6, 0xf1, 0x25, 0xca, 0x4f, 0xca, 0x09, 0xe5, 0x67, 0x9b, + 0xcb, 0xe6, 0xb6, 0x79, 0x6c, 0x36, 0x9b, 0xca, 0x97, 0xe0, 0x59, 0x73, 0x33, 0x3f, 0x9f, 0x37, 0xe7, 0x1d, 0x51, 0xa3, 0x1b, 0xf8, 0x40, 0x3e, 0x88, 0xdf, 0x44, 0xb3, 0xff, 0x10, 0x5e, 0x92, 0x7c, 0xbf, 0xfc, 0x51, 0x3e, 0x9d, 0x2f, 0xe1, 0xff, 0xe0, 0xcb, 0xf8, 0xcb, 0x8a, 0x5f, 0x09, 0x28, 0x21, 0xe5, 0x76, 0x65, 0xb8, 0x32, 0x9d, 0xd6, 0xaf, 0x62, 0x8c, 0x14, 0xd1, 0xde, 0x47, 0x54, 0x84, 0x5f, 0x35, 0x12, 0x65, 0xfe, 0x76, 0x89, 0x55, 0xca, 0x43, 0xdf, 0x75, 0xa4, 0xb2, 0xca, 0x4b, 0xe3, 0x28, 0xf1, 0x3f, 0x53, 0xa6, 0x28, 0xa5, 0x2b, 0xb5, 0x9a, 0x87, 0xf8, 0x7f, 0xa6, 0x44, 0xab, 0x2c, 0x8e, 0xf2, 0xfe, 0x33, 0x25, 0x72, 0x9a, 0x47, 0xc5, 0x5a, 0xc1, 0xda, 0x33, 0x48, 0x64, 0xea, 0x9b, 0x25, 0xd4, 0xd2, 0x7f, 0xa4, 0x7c, 0x39, 0xb1, 0x02, 0xe0, 0x68, 0x53, + 0xab, 0xdc, 0x3f, 0x56, 0xdb, 0x25, 0x89, 0x32, 0x03, 0xd4, 0x0a, 0xdd, 0xc8, 0x06, 0xfa, 0xe3, 0xa5, 0xd2, 0x9a, 0x46, 0x39, 0x6e, 0xb3, 0xd6, 0x3b, 0xa7, 0xcb, 0x0f, 0xd1, 0xbe, 0x3b, 0x51, 0xef, 0x3f, 0xed, 0x0a, 0x11, 0x9a, 0x41, 0x8a, 0x41, 0x7d, 0xfe, 0xb4, 0x6b, 0xc4, 0x68, 0x5d, 0xd3, 0x23, 0x41, 0x7d, 0xff, 0xb4, 0xab, 0xd4, 0xa0, 0xf9, 0xaf, 0x67, 0x05, 0xf5, 0xfb, 0xd3, 0xae, 0x93, 0x4a, 0x4f, 0xca, 0x5e, 0x49, 0xd4, 0xff, 0x4f, 0xbb, 0x52, 0x1a, 0x3d, 0x7d, 0x7b, 0x57, 0xa2, 0x01, 0x7f, 0xda, 0xb5, 0xd2, 0x69, 0x4d, 0xde, 0xa7, 0x0a, 0x0d, 0xfc, 0xd3, 0xae, 0x96, 0x49, 0xeb, 0xab, 0xbe, 0x67, 0xd0, 0xa0, 0x3f, 0xe1, 0x7a, 0x12, 0x6b, 0xc3, 0x6a, 0xda, 0x82, 0xca, 0x5b, 0xca, 0x9b, 0xca, 0x06, 0xe5, 0x7d, 0xa5, 0x54, 0xd9, 0xa8, 0x6c, 0x55, 0xde, 0x56, + 0x36, 0x29, 0xef, 0x28, 0x9b, 0x95, 0xd7, 0xf9, 0x7c, 0xfe, 0x1c, 0x5f, 0xc0, 0xff, 0xce, 0x17, 0xf2, 0xe7, 0xf9, 0x0b, 0xfc, 0x45, 0x65, 0x9d, 0xf2, 0x86, 0xb2, 0x5e, 0x79, 0x57, 0xd9, 0xa2, 0xbc, 0x47, 0xab, 0xff, 0xc5, 0xb0, 0x3b, 0xa4, 0x3f, 0x30, 0xf3, 0x58, 0xf3, 0x4e, 0x7a, 0x02, 0xa5, 0x11, 0x4f, 0x66, 0x89, 0xae, 0xb9, 0x84, 0x16, 0x8e, 0xaf, 0x2b, 0xa5, 0x64, 0x03, 0xfd, 0xb1, 0x79, 0xcd, 0x9a, 0x2d, 0xbb, 0xd1, 0xd6, 0x23, 0x51, 0xfe, 0x1f, 0x9c, 0xd9, 0x44, 0xfd, 0x2a, 0x5a, 0x50, 0x62, 0x85, 0xac, 0x86, 0x52, 0xa6, 0x1c, 0x52, 0xbe, 0xe1, 0xf3, 0xf8, 0xb3, 0xca, 0xb7, 0xca, 0x8f, 0xca, 0x61, 0xe5, 0x3b, 0xe5, 0x88, 0xf2, 0xbd, 0xf2, 0x35, 0x7f, 0x8c, 0xcf, 0xe0, 0x8f, 0xf3, 0x99, 0xfc, 0x09, 0xb2, 0x93, 0x66, 0xf3, 0x39, 0xfc, 0x69, 0x3e, 0x97, 0x3f, + 0xa3, 0x1c, 0x55, 0x7e, 0x50, 0x8e, 0xf1, 0x27, 0xf9, 0x53, 0x7f, 0xb0, 0x17, 0xc5, 0x95, 0xf1, 0x74, 0xff, 0xed, 0x67, 0x7b, 0xd5, 0x27, 0xfb, 0x7f, 0xe0, 0xca, 0xff, 0xbe, 0xb6, 0xfc, 0x91, 0x2b, 0x0f, 0x66, 0x8d, 0xf9, 0x8d, 0xca, 0xb3, 0xca, 0x7c, 0xe5, 0x39, 0x7e, 0x8b, 0x32, 0xaf, 0xd2, 0xaa, 0x68, 0x64, 0x95, 0x15, 0xd1, 0x38, 0x5a, 0x0d, 0x2d, 0x53, 0x5e, 0x55, 0x5e, 0x53, 0xd6, 0x2a, 0xff, 0x54, 0x5e, 0x56, 0x96, 0x2b, 0x2b, 0x94, 0x57, 0x94, 0xd5, 0xca, 0x4a, 0x65, 0x95, 0xb2, 0x46, 0x59, 0xa0, 0xfc, 0x5d, 0x59, 0x48, 0xab, 0xa6, 0xfb, 0x69, 0x35, 0x75, 0x97, 0xf2, 0xbc, 0xb2, 0x48, 0x79, 0x41, 0x59, 0xac, 0xbc, 0x48, 0x57, 0x7b, 0x49, 0x59, 0xaa, 0xfc, 0x43, 0xd8, 0x37, 0x54, 0xb7, 0x81, 0xa4, 0x94, 0x83, 0xf8, 0x60, 0x81, 0x64, 0xf3, 0x21, 0x4c, 0xa1, + 0x3a, 0x96, 0x50, 0xc3, 0x3e, 0xc0, 0xa7, 0x33, 0x3b, 0xd5, 0xee, 0x35, 0x16, 0x12, 0xb5, 0x63, 0x19, 0xe2, 0xf3, 0x41, 0xac, 0x36, 0xea, 0x98, 0xa5, 0x3c, 0xae, 0x3c, 0x43, 0xd6, 0xa9, 0x44, 0xcf, 0xa4, 0x2c, 0xe5, 0x19, 0x3e, 0x50, 0x99, 0xa5, 0x3c, 0xa9, 0xcc, 0x56, 0x9e, 0xe2, 0x43, 0x94, 0x27, 0xaa, 0xbd, 0x6a, 0x9c, 0xa3, 0x3c, 0xad, 0xcc, 0xa5, 0xab, 0xa1, 0x6d, 0xac, 0x16, 0xa0, 0x12, 0xed, 0x54, 0xa3, 0xc1, 0x54, 0xa3, 0x21, 0xfc, 0x16, 0xaa, 0x45, 0x09, 0x27, 0x8b, 0x08, 0xd7, 0xaf, 0x81, 0xeb, 0xa7, 0x2a, 0xf3, 0x94, 0x85, 0xb4, 0x5e, 0x43, 0x2b, 0x29, 0xcf, 0x28, 0xff, 0x28, 0xbf, 0x32, 0xdd, 0xd5, 0x13, 0x67, 0x5d, 0x3f, 0x26, 0xd5, 0x86, 0x3f, 0x4c, 0x35, 0x11, 0xed, 0xfd, 0x4f, 0xbe, 0x9c, 0xbf, 0xcc, 0x57, 0xf0, 0x95, 0x7c, 0x15, 0x7f, 0x85, 0x6c, + 0xfb, 0x57, 0xf9, 0x1a, 0xfe, 0x9a, 0x55, 0x1f, 0xba, 0xb3, 0x99, 0x15, 0x2d, 0x85, 0x76, 0x4a, 0xb4, 0x52, 0x72, 0x9d, 0x4a, 0xc8, 0x90, 0xb9, 0x9b, 0x6a, 0xa6, 0xd1, 0x7d, 0x4d, 0x67, 0x3e, 0xb4, 0x52, 0x0c, 0xb5, 0xac, 0x83, 0x5a, 0xd6, 0x45, 0x2b, 0xd5, 0x43, 0x5d, 0x73, 0xac, 0x56, 0x3a, 0xdb, 0x3a, 0xb6, 0xaa, 0x2e, 0x50, 0x7f, 0xd1, 0x79, 0xe5, 0x2d, 0x42, 0x6d, 0x89, 0x36, 0xa1, 0xd6, 0x9d, 0x57, 0xa1, 0x0d, 0xa2, 0x3f, 0x6f, 0x44, 0x2b, 0xdd, 0xc6, 0x47, 0x51, 0x8d, 0x1e, 0xe0, 0x93, 0x99, 0x41, 0xf9, 0xfc, 0xcc, 0x87, 0x3e, 0xf1, 0x53, 0xdd, 0x5f, 0xa4, 0x56, 0x7a, 0x89, 0x7a, 0x37, 0xc3, 0xb2, 0xd9, 0xcc, 0x19, 0xe6, 0xe3, 0xfc, 0xa8, 0x39, 0xd3, 0x7c, 0x82, 0xff, 0x60, 0xce, 0x32, 0x9f, 0x34, 0x67, 0x9b, 0x4f, 0x99, 0x73, 0xcc, 0xa7, 0xcd, 0xb9, 0xe6, + 0x33, 0xb6, 0xa8, 0x39, 0x8f, 0x1f, 0x33, 0x9f, 0xe5, 0x3f, 0x9a, 0xf3, 0xcd, 0xe7, 0xf8, 0x71, 0x73, 0x01, 0xff, 0x89, 0x9f, 0xe0, 0x3f, 0xb3, 0x29, 0xec, 0xaf, 0x6c, 0x2a, 0xfb, 0x1b, 0x9b, 0xc6, 0x1e, 0x65, 0xd3, 0xd9, 0x63, 0x6c, 0x06, 0x7b, 0x9c, 0xcd, 0x64, 0xb3, 0xd8, 0x93, 0x6c, 0x36, 0x7b, 0x8a, 0xcd, 0x61, 0x4f, 0xb3, 0xb9, 0xec, 0x19, 0x36, 0x8f, 0x3d, 0xcb, 0xe6, 0xb3, 0xe7, 0xd8, 0x02, 0xf6, 0x77, 0xe5, 0x2b, 0xe5, 0xa0, 0xf2, 0xb1, 0x72, 0x97, 0x72, 0xa7, 0x32, 0x52, 0xd9, 0xa9, 0x7c, 0xa2, 0xec, 0x52, 0x3e, 0x55, 0x76, 0x2b, 0x9f, 0xfd, 0xa6, 0xec, 0xd3, 0xb3, 0xfc, 0xde, 0xa6, 0x7c, 0xa8, 0x6c, 0x67, 0x4f, 0x50, 0xfe, 0xdd, 0x62, 0x83, 0xcd, 0x26, 0x03, 0x33, 0xd2, 0x69, 0xae, 0x14, 0x68, 0x91, 0x88, 0xc5, 0xf2, 0xd3, 0xac, 0x17, 0xa4, 0x95, 0x4b, + 0x94, 0xa5, 0xd0, 0xd3, 0x38, 0x8d, 0x9d, 0x47, 0xf3, 0x6b, 0x26, 0x59, 0xe0, 0xd9, 0xac, 0x2e, 0xab, 0xc7, 0xea, 0x53, 0xcb, 0x37, 0x60, 0x79, 0xac, 0x11, 0x70, 0xde, 0x56, 0xec, 0x1a, 0x76, 0x2d, 0xad, 0x06, 0x06, 0xd0, 0xd3, 0x6c, 0x30, 0xbb, 0x99, 0xdd, 0x42, 0x96, 0xd1, 0x30, 0x76, 0x3b, 0x1b, 0xce, 0x46, 0xb1, 0xf1, 0x6c, 0x02, 0x7b, 0x90, 0x4d, 0x64, 0x0f, 0xb1, 0x49, 0x18, 0xaf, 0xd5, 0xb4, 0x42, 0xce, 0xd0, 0xac, 0xb3, 0x8f, 0xec, 0x61, 0xb0, 0x11, 0x48, 0xab, 0x2a, 0x9e, 0x02, 0xdd, 0x12, 0x4f, 0x81, 0x6c, 0xf1, 0x54, 0x51, 0x9b, 0xa8, 0x17, 0xab, 0x5d, 0xd5, 0x4b, 0xd4, 0x4b, 0xd5, 0x6e, 0xea, 0x65, 0x6a, 0x77, 0xf5, 0x72, 0xf5, 0x7e, 0xf5, 0x09, 0x75, 0x96, 0x3a, 0x5b, 0x7d, 0x4a, 0x9d, 0xa3, 0x3e, 0xad, 0x85, 0xb4, 0x1c, 0xad, 0x81, 0xd6, 0x50, 0xcb, + 0x13, 0xff, 0x3f, 0x4e, 0xbb, 0xf7, 0xf4, 0xff, 0xc0, 0x14, 0xf3, 0xb4, 0xfa, 0x5e, 0x45, 0xa9, 0x03, 0x93, 0x4a, 0xad, 0xfb, 0x5b, 0xa5, 0xaa, 0x2f, 0xa8, 0xff, 0x54, 0x97, 0xab, 0x2f, 0xab, 0x2b, 0xd5, 0x55, 0xea, 0x2b, 0xea, 0x6a, 0xf5, 0xd5, 0xca, 0xd7, 0x31, 0x62, 0x46, 0x4d, 0x23, 0xd5, 0xa8, 0x65, 0x9c, 0x67, 0xa4, 0x1b, 0x19, 0x46, 0xa6, 0x51, 0x5b, 0x5c, 0x4d, 0x1b, 0x43, 0x1a, 0xb5, 0x95, 0x5d, 0xaf, 0x16, 0x9c, 0x93, 0x7a, 0x54, 0xa1, 0x71, 0x67, 0xd0, 0xa2, 0xb3, 0xd2, 0x9a, 0x2a, 0xf4, 0xde, 0xb9, 0x48, 0x0b, 0x9e, 0x41, 0x4d, 0xc4, 0x7f, 0xd0, 0xab, 0x4c, 0x54, 0xcb, 0xb3, 0x90, 0x91, 0x55, 0x99, 0xe4, 0x27, 0xe9, 0x7e, 0xfc, 0xd4, 0x56, 0x4d, 0x18, 0x53, 0xbb, 0xa8, 0x57, 0x50, 0x7b, 0x5e, 0xa9, 0xf6, 0x61, 0x4e, 0x6a, 0xab, 0xfb, 0x59, 0x50, 0x9d, + 0xa9, 0x3e, 0xcd, 0x42, 0xd4, 0x52, 0x2f, 0xb0, 0x54, 0x75, 0x99, 0xfa, 0x2a, 0xab, 0xa5, 0xbe, 0xa6, 0x6e, 0xa0, 0xb6, 0xdd, 0xaa, 0x6e, 0x65, 0x8d, 0xd4, 0x4f, 0xd4, 0xfd, 0x2c, 0x9f, 0x5a, 0x2d, 0xc4, 0x0a, 0xb5, 0xfa, 0x5a, 0x3e, 0x6b, 0x8d, 0xff, 0xce, 0xd9, 0x89, 0x7a, 0xe8, 0x5e, 0xd6, 0x19, 0xff, 0x8b, 0xf3, 0x22, 0x6a, 0xc5, 0x18, 0xbb, 0xcc, 0xa8, 0x41, 0xad, 0xd7, 0xdd, 0xa8, 0x63, 0xe4, 0xb2, 0xde, 0xe6, 0x62, 0x73, 0x31, 0xad, 0x14, 0x4f, 0x5b, 0x22, 0x13, 0xd0, 0x67, 0x29, 0xff, 0xa3, 0x7a, 0x48, 0x34, 0x93, 0x8a, 0xff, 0xed, 0xc6, 0xe4, 0x27, 0xe5, 0x67, 0x98, 0x21, 0xbe, 0x0b, 0xcb, 0xbc, 0xe2, 0xbf, 0x7e, 0x31, 0x1f, 0xf5, 0x65, 0x13, 0x16, 0xa0, 0xda, 0xf4, 0xa1, 0x6b, 0x5f, 0xaf, 0x5e, 0xcf, 0xea, 0xe3, 0xbf, 0x1b, 0xe6, 0x50, 0x0f, 0xde, 0xcf, + 0x1a, 0xa0, 0x4e, 0x0d, 0xa9, 0xdf, 0x5e, 0x60, 0xe7, 0x53, 0x9d, 0x36, 0xb0, 0x0b, 0xa8, 0x6f, 0xde, 0x67, 0x5d, 0x51, 0x9b, 0x4b, 0xd5, 0x32, 0xb5, 0x8c, 0x5d, 0xad, 0x9e, 0x54, 0x4f, 0xb2, 0xde, 0x9a, 0x4d, 0xb3, 0xb1, 0x6b, 0xf0, 0x3f, 0x11, 0xfb, 0xa0, 0x7e, 0x7d, 0xf1, 0x1f, 0x4d, 0x07, 0xa2, 0x96, 0x37, 0x6a, 0x2d, 0xb5, 0x96, 0x6c, 0x90, 0xd6, 0x5d, 0xeb, 0xce, 0x6e, 0xd2, 0x46, 0x68, 0x23, 0xd8, 0x60, 0xfc, 0x2f, 0xd3, 0x9b, 0x51, 0xef, 0xa1, 0xda, 0x34, 0x6d, 0x1a, 0x1b, 0x81, 0xda, 0xdf, 0x49, 0xb5, 0xcf, 0x65, 0x23, 0xf1, 0x5f, 0x03, 0x1f, 0x16, 0x71, 0x4e, 0x6c, 0x72, 0x85, 0xe6, 0x6f, 0x4d, 0x68, 0x7e, 0xca, 0xff, 0xf1, 0xfb, 0xf9, 0x5f, 0xea, 0xa2, 0xb0, 0x60, 0x27, 0xd2, 0xac, 0xff, 0xbf, 0xd4, 0xc6, 0xf2, 0x75, 0xe7, 0xff, 0x6d, 0x9d, 0x94, 0xa8, + 0x2d, 0x63, 0x22, 0xae, 0xbd, 0x42, 0x2b, 0xff, 0xbf, 0x70, 0x5f, 0xff, 0xab, 0xf9, 0xa9, 0x39, 0xb5, 0xe5, 0xef, 0x5e, 0x8b, 0xb0, 0x27, 0x80, 0x36, 0xfc, 0xee, 0xa7, 0xbf, 0x32, 0x0c, 0x2b, 0x77, 0x0b, 0xed, 0xb6, 0xd3, 0x2a, 0xa3, 0x2b, 0xf5, 0x67, 0x2b, 0x3a, 0xaf, 0x3d, 0xfd, 0xee, 0x87, 0x7e, 0x6c, 0xc5, 0xb8, 0xbc, 0x58, 0xac, 0x08, 0xa5, 0x96, 0xac, 0x35, 0x1b, 0x43, 0xe7, 0x4e, 0xa1, 0xfa, 0xcc, 0xa1, 0xab, 0x2e, 0x61, 0x2b, 0xd8, 0x5a, 0x5a, 0x6f, 0x6d, 0xa5, 0x9e, 0xdf, 0xc3, 0x0e, 0xb0, 0xc3, 0xec, 0xb8, 0x78, 0x64, 0x72, 0x0f, 0x8f, 0xf0, 0x34, 0x9e, 0xcd, 0xf3, 0xb8, 0xf8, 0xff, 0xbf, 0x9d, 0x69, 0x85, 0xd1, 0x93, 0xf7, 0xe5, 0x03, 0x68, 0x7d, 0x57, 0x42, 0xeb, 0x8b, 0xd1, 0xb4, 0x96, 0x98, 0x48, 0xeb, 0x86, 0xe9, 0x64, 0x3d, 0xcd, 0x25, 0x9b, 0x62, + 0x31, 0xad, 0x19, 0x56, 0xf1, 0xb5, 0x7c, 0x03, 0xdf, 0xcc, 0x4b, 0xf9, 0x0e, 0xbe, 0x9b, 0xef, 0xe3, 0x07, 0xf9, 0x61, 0x7e, 0x8c, 0x9f, 0x94, 0x24, 0x49, 0x93, 0x5c, 0x52, 0x40, 0x8a, 0x49, 0x69, 0x52, 0x96, 0x94, 0x43, 0xd7, 0x97, 0x11, 0xed, 0xdb, 0x0e, 0xfb, 0x6b, 0xd9, 0xe5, 0xd8, 0x5f, 0xc7, 0x8a, 0xb1, 0xef, 0x4f, 0xb6, 0xa0, 0xd8, 0x0f, 0xb0, 0xad, 0xc5, 0xfe, 0x06, 0xd6, 0x13, 0xfb, 0x81, 0xac, 0x37, 0xf6, 0xa3, 0xa4, 0x67, 0xc5, 0x9e, 0x7f, 0x8f, 0xd8, 0xe1, 0x56, 0xca, 0x30, 0xed, 0xb8, 0xd8, 0xdb, 0x6a, 0x6a, 0xc7, 0xc4, 0x5e, 0x8f, 0x6a, 0x3f, 0x62, 0x1f, 0x63, 0x97, 0x60, 0x5f, 0x43, 0xfb, 0x09, 0xfb, 0x9a, 0xc0, 0x39, 0x5b, 0xe9, 0xa9, 0xec, 0x52, 0xec, 0x6b, 0xd1, 0x7a, 0x46, 0xec, 0xd3, 0xf0, 0xad, 0xf5, 0x56, 0xfa, 0x79, 0xb4, 0xda, 0x15, 0xfb, + 0x74, 0xed, 0x04, 0xf6, 0xb5, 0xa5, 0xf9, 0xd8, 0x67, 0x69, 0x27, 0xb1, 0xaf, 0x23, 0xfd, 0x1d, 0xfb, 0x6c, 0xed, 0x17, 0xec, 0xeb, 0x6a, 0x3f, 0x63, 0x5f, 0x5f, 0x5a, 0x80, 0x7d, 0x8e, 0xf4, 0x1c, 0xf6, 0x0d, 0xc8, 0x02, 0x93, 0x10, 0xcb, 0x2c, 0x51, 0x6d, 0xdb, 0x12, 0xbf, 0x96, 0x75, 0x21, 0x7e, 0x1d, 0xbb, 0x98, 0x78, 0x7f, 0x76, 0x05, 0xf1, 0x01, 0xb6, 0xd7, 0x88, 0xdf, 0xc0, 0xae, 0x24, 0x3e, 0xd0, 0xb6, 0x85, 0xf8, 0x4d, 0xec, 0x6a, 0xe2, 0xa3, 0x6c, 0xef, 0x13, 0xbf, 0x5b, 0x9a, 0x43, 0x56, 0xf6, 0xf7, 0xec, 0x2a, 0x26, 0xd1, 0xdd, 0x1d, 0x66, 0x12, 0xdd, 0x5b, 0x19, 0x93, 0xe8, 0xce, 0xbe, 0x25, 0x1e, 0x63, 0x1d, 0x88, 0xd7, 0xd0, 0xbe, 0x23, 0x5e, 0x93, 0x5d, 0x48, 0x3c, 0x95, 0x75, 0x24, 0x5e, 0x8b, 0x75, 0x22, 0x9e, 0xc6, 0x3a, 0xff, 0x3f, 0x75, 0x9d, + 0x3d, 0x4c, 0x14, 0x51, 0x14, 0x85, 0xef, 0x9b, 0xdd, 0xc1, 0x39, 0x79, 0xa8, 0x89, 0x31, 0x11, 0x08, 0x2c, 0x2c, 0x11, 0x58, 0x14, 0x0a, 0x44, 0x29, 0x80, 0xca, 0xc2, 0xc4, 0x86, 0x8a, 0xca, 0xc0, 0x16, 0x20, 0x9d, 0x35, 0xb1, 0x20, 0x01, 0xfc, 0xab, 0xec, 0x6d, 0x04, 0x45, 0x05, 0xe5, 0x47, 0xf9, 0xdb, 0x05, 0x15, 0xec, 0x28, 0x21, 0x10, 0x7e, 0x0a, 0x0d, 0x09, 0x8d, 0x89, 0x0d, 0x95, 0x60, 0x43, 0x01, 0x9e, 0x7b, 0x91, 0x4d, 0x24, 0xda, 0x9c, 0xef, 0x65, 0x36, 0xf3, 0xde, 0xcd, 0x3d, 0x67, 0xde, 0x26, 0xf3, 0x8a, 0xa1, 0x26, 0xe5, 0x36, 0xb5, 0x3c, 0xfa, 0x49, 0xad, 0x08, 0x86, 0xa8, 0x95, 0xd1, 0x3e, 0xb5, 0x2a, 0x78, 0x43, 0x4d, 0x45, 0xbf, 0xa8, 0xd5, 0xd1, 0x1e, 0xf5, 0x6a, 0xf0, 0x9a, 0x5a, 0x13, 0xbc, 0xa2, 0xd6, 0x86, 0x2b, 0xd4, 0xba, 0xe8, 0x90, 0xda, + 0x18, 0xae, 0x52, 0x9b, 0xc2, 0x35, 0xea, 0x33, 0x3b, 0x6f, 0x6c, 0x63, 0xed, 0xf7, 0x98, 0xbe, 0x1e, 0x79, 0x24, 0x4f, 0x99, 0xe9, 0xe7, 0x4c, 0xee, 0x84, 0x64, 0x64, 0x51, 0x96, 0x64, 0x59, 0x36, 0x65, 0x5b, 0xbe, 0xcb, 0xae, 0xec, 0xcb, 0x01, 0xd3, 0x93, 0xe7, 0xf2, 0xdd, 0x05, 0xe6, 0x27, 0xe1, 0x2e, 0xbb, 0x2b, 0x4c, 0x50, 0x83, 0x6b, 0x76, 0x37, 0x99, 0xa1, 0x16, 0xd7, 0xea, 0xee, 0x1c, 0xa7, 0x48, 0x1e, 0xb2, 0x5b, 0x3d, 0xec, 0xd7, 0x07, 0x63, 0x07, 0x67, 0x55, 0x76, 0xca, 0x63, 0xe3, 0xdd, 0x60, 0x5c, 0xe9, 0xf6, 0xf0, 0x59, 0x19, 0x96, 0x60, 0x5e, 0x89, 0x22, 0x4c, 0x1a, 0x8b, 0x31, 0x65, 0x2c, 0xc1, 0xb4, 0x31, 0x81, 0x8c, 0xb1, 0x14, 0x33, 0xc6, 0x32, 0xcc, 0x1a, 0x93, 0xc8, 0x1a, 0xcb, 0x31, 0x67, 0xac, 0xc4, 0x47, 0x63, 0x15, 0x3e, 0x19, 0x53, 0x58, + 0x30, 0x56, 0x63, 0xd1, 0x58, 0x83, 0x2f, 0xc6, 0x5a, 0xe9, 0x55, 0xff, 0x30, 0x68, 0x4f, 0x50, 0x9f, 0xf9, 0xd7, 0x6f, 0xfe, 0x3d, 0x50, 0x9f, 0x82, 0x31, 0x75, 0x08, 0x63, 0xea, 0x0d, 0xde, 0xaa, 0x2b, 0x78, 0xa1, 0xae, 0xe0, 0xa5, 0xba, 0x02, 0xed, 0x78, 0x02, 0xda, 0xeb, 0x52, 0x68, 0x67, 0xcb, 0xa0, 0x5d, 0x4e, 0x62, 0x58, 0x5d, 0xc1, 0x88, 0xfa, 0x81, 0x77, 0xea, 0x07, 0x46, 0xd5, 0x0f, 0x8c, 0xab, 0x1f, 0x98, 0x50, 0x27, 0xf0, 0x5e, 0x9d, 0xb0, 0x9d, 0x97, 0x59, 0x27, 0x6f, 0xd9, 0x19, 0x5d, 0x4a, 0xae, 0x49, 0x33, 0x2b, 0x8b, 0x38, 0xbe, 0x28, 0xf5, 0x96, 0xaa, 0xeb, 0x9a, 0x1e, 0xb9, 0xa1, 0xf5, 0xd8, 0x49, 0x4a, 0x43, 0x6e, 0xd4, 0xa5, 0x19, 0xb3, 0xd1, 0xfd, 0xdc, 0xb5, 0x3a, 0xce, 0xd0, 0x21, 0xdd, 0xf6, 0xad, 0xf8, 0x42, 0x49, 0xcb, 0x86, 0x7c, 0xd3, + 0xe7, 0x3d, 0x9e, 0x8e, 0x0d, 0xe8, 0x5e, 0xe9, 0xe7, 0xfd, 0x94, 0x9f, 0xf6, 0x33, 0x7e, 0xd6, 0x67, 0x7c, 0xd6, 0xcf, 0xe1, 0x0c, 0x80, 0x08, 0x79, 0x38, 0x8b, 0x73, 0x38, 0xcf, 0x19, 0xf5, 0xf4, 0xb9, 0x5d, 0xd6, 0xe5, 0xab, 0xec, 0xc8, 0x8f, 0xe0, 0xc9, 0x7f, 0xbe, 0x6d, 0x7f, 0x04, 0x81, 0x43, 0x80, 0x18, 0xe2, 0x08, 0x71, 0x49, 0x4f, 0x75, 0xdc, 0x86, 0xed, 0x40, 0x45, 0x5c, 0xb9, 0x58, 0xfc, 0xbf, 0x57, 0xd3, 0xff, 0x19, 0xdb, 0xd5, 0xc5, 0x76, 0xd6, 0x98, 0xdd, 0xb7, 0xf9, 0xe7, 0xbe, 0x93, 0x93, 0xa1, 0xf8, 0xc9, 0xea, 0xf6, 0xeb, 0x56, 0x6e, 0xd6, 0x02, 0x7d, 0xf7, 0x17, 0x4f, 0x9f, 0xaa, 0xd8, 0xf1, 0x6a, 0x3b, 0xeb, 0xfc, 0xab, 0xa2, 0xdf, 0x7f, 0x02, 0x0c, 0xed +}; + +#include + +uint8* extractCafeDefaultFont(sint32* size) +{ + uint8* uncompressed = (uint8*)malloc(1024 * 1024 * 1); + uLongf uncompressedSize = 1024 * 1024 * 1; + uncompress(uncompressed, &uncompressedSize, cafeDefaultFontZLIB, sizeof(cafeDefaultFontZLIB)); + *size = uncompressedSize; + return uncompressed; +} \ No newline at end of file diff --git a/src/resource/IconsFontAwesome5.h b/src/resource/IconsFontAwesome5.h new file mode 100644 index 00000000..bbdcd476 --- /dev/null +++ b/src/resource/IconsFontAwesome5.h @@ -0,0 +1,946 @@ +// Generated by https://github.com/juliettef/IconFontCppHeaders script GenerateIconFontCppHeaders.py for language C++11 +// from https://raw.githubusercontent.com/FortAwesome/Font-Awesome/master/metadata/icons.yml +// for use with https://github.com/FortAwesome/Font-Awesome/blob/master/webfonts/fa-solid-900.ttf, https://github.com/FortAwesome/Font-Awesome/blob/master/webfonts/fa-regular-400.ttf, +#pragma once + +#define FONT_ICON_FILE_NAME_FAR "fa-regular-400.ttf" +#define FONT_ICON_FILE_NAME_FAS "fa-solid-900.ttf" + +#define ICON_MIN_FA 0xf000 +#define ICON_MAX_FA 0xf82f +#define ICON_FA_NOTES_MEDICAL u8"\uf481" +#define ICON_FA_CLOUD_SHOWERS_HEAVY u8"\uf740" +#define ICON_FA_SMS u8"\uf7cd" +#define ICON_FA_COPY u8"\uf0c5" +#define ICON_FA_CHEVRON_CIRCLE_RIGHT u8"\uf138" +#define ICON_FA_CROSSHAIRS u8"\uf05b" +#define ICON_FA_BROADCAST_TOWER u8"\uf519" +#define ICON_FA_EXTERNAL_LINK_SQUARE_ALT u8"\uf360" +#define ICON_FA_SMOKING u8"\uf48d" +#define ICON_FA_KISS_BEAM u8"\uf597" +#define ICON_FA_CHESS_BISHOP u8"\uf43a" +#define ICON_FA_TV u8"\uf26c" +#define ICON_FA_CROP_ALT u8"\uf565" +#define ICON_FA_TH u8"\uf00a" +#define ICON_FA_RECYCLE u8"\uf1b8" +#define ICON_FA_SMILE u8"\uf118" +#define ICON_FA_FAX u8"\uf1ac" +#define ICON_FA_DRAFTING_COMPASS u8"\uf568" +#define ICON_FA_USER_INJURED u8"\uf728" +#define ICON_FA_SCREWDRIVER u8"\uf54a" +#define ICON_FA_DHARMACHAKRA u8"\uf655" +#define ICON_FA_PRINT u8"\uf02f" +#define ICON_FA_BABY_CARRIAGE u8"\uf77d" +#define ICON_FA_CARET_UP u8"\uf0d8" +#define ICON_FA_SCHOOL u8"\uf549" +#define ICON_FA_SORT_NUMERIC_UP u8"\uf163" +#define ICON_FA_TRUCK_LOADING u8"\uf4de" +#define ICON_FA_LIST u8"\uf03a" +#define ICON_FA_UPLOAD u8"\uf093" +#define ICON_FA_LAPTOP_MEDICAL u8"\uf812" +#define ICON_FA_EXPAND_ARROWS_ALT u8"\uf31e" +#define ICON_FA_ADJUST u8"\uf042" +#define ICON_FA_VENUS u8"\uf221" +#define ICON_FA_HEADING u8"\uf1dc" +#define ICON_FA_ARROW_DOWN u8"\uf063" +#define ICON_FA_BICYCLE u8"\uf206" +#define ICON_FA_TIRED u8"\uf5c8" +#define ICON_FA_AIR_FRESHENER u8"\uf5d0" +#define ICON_FA_BACON u8"\uf7e5" +#define ICON_FA_SYNC u8"\uf021" +#define ICON_FA_PAPER_PLANE u8"\uf1d8" +#define ICON_FA_VOLLEYBALL_BALL u8"\uf45f" +#define ICON_FA_RIBBON u8"\uf4d6" +#define ICON_FA_HAND_LIZARD u8"\uf258" +#define ICON_FA_CLOCK u8"\uf017" +#define ICON_FA_SUN u8"\uf185" +#define ICON_FA_FILE_POWERPOINT u8"\uf1c4" +#define ICON_FA_MICROCHIP u8"\uf2db" +#define ICON_FA_TRASH_RESTORE_ALT u8"\uf82a" +#define ICON_FA_GRADUATION_CAP u8"\uf19d" +#define ICON_FA_ANGLE_DOUBLE_DOWN u8"\uf103" +#define ICON_FA_INFO_CIRCLE u8"\uf05a" +#define ICON_FA_TAGS u8"\uf02c" +#define ICON_FA_FILE_ALT u8"\uf15c" +#define ICON_FA_EQUALS u8"\uf52c" +#define ICON_FA_DIRECTIONS u8"\uf5eb" +#define ICON_FA_FILE_INVOICE u8"\uf570" +#define ICON_FA_SEARCH u8"\uf002" +#define ICON_FA_BIBLE u8"\uf647" +#define ICON_FA_FLASK u8"\uf0c3" +#define ICON_FA_CALENDAR_TIMES u8"\uf273" +#define ICON_FA_GREATER_THAN_EQUAL u8"\uf532" +#define ICON_FA_SLIDERS_H u8"\uf1de" +#define ICON_FA_EYE_SLASH u8"\uf070" +#define ICON_FA_BIRTHDAY_CAKE u8"\uf1fd" +#define ICON_FA_FEATHER_ALT u8"\uf56b" +#define ICON_FA_DNA u8"\uf471" +#define ICON_FA_BASEBALL_BALL u8"\uf433" +#define ICON_FA_HOSPITAL u8"\uf0f8" +#define ICON_FA_COINS u8"\uf51e" +#define ICON_FA_HRYVNIA u8"\uf6f2" +#define ICON_FA_TEMPERATURE_HIGH u8"\uf769" +#define ICON_FA_FONT_AWESOME_LOGO_FULL u8"\uf4e6" +#define ICON_FA_PASSPORT u8"\uf5ab" +#define ICON_FA_TAG u8"\uf02b" +#define ICON_FA_SHOPPING_CART u8"\uf07a" +#define ICON_FA_AWARD u8"\uf559" +#define ICON_FA_WINDOW_RESTORE u8"\uf2d2" +#define ICON_FA_PHONE u8"\uf095" +#define ICON_FA_FLAG u8"\uf024" +#define ICON_FA_STETHOSCOPE u8"\uf0f1" +#define ICON_FA_DICE_D6 u8"\uf6d1" +#define ICON_FA_OUTDENT u8"\uf03b" +#define ICON_FA_LONG_ARROW_ALT_RIGHT u8"\uf30b" +#define ICON_FA_PIZZA_SLICE u8"\uf818" +#define ICON_FA_ADDRESS_CARD u8"\uf2bb" +#define ICON_FA_PARAGRAPH u8"\uf1dd" +#define ICON_FA_MALE u8"\uf183" +#define ICON_FA_HISTORY u8"\uf1da" +#define ICON_FA_HAMBURGER u8"\uf805" +#define ICON_FA_SEARCH_PLUS u8"\uf00e" +#define ICON_FA_FIRE_ALT u8"\uf7e4" +#define ICON_FA_LIFE_RING u8"\uf1cd" +#define ICON_FA_SHARE u8"\uf064" +#define ICON_FA_ALIGN_JUSTIFY u8"\uf039" +#define ICON_FA_TOILET_PAPER u8"\uf71e" +#define ICON_FA_BATTERY_THREE_QUARTERS u8"\uf241" +#define ICON_FA_OBJECT_UNGROUP u8"\uf248" +#define ICON_FA_BRIEFCASE u8"\uf0b1" +#define ICON_FA_OIL_CAN u8"\uf613" +#define ICON_FA_THERMOMETER_FULL u8"\uf2c7" +#define ICON_FA_PLANE u8"\uf072" +#define ICON_FA_HEARTBEAT u8"\uf21e" +#define ICON_FA_UNLINK u8"\uf127" +#define ICON_FA_WINDOW_MAXIMIZE u8"\uf2d0" +#define ICON_FA_HEADPHONES u8"\uf025" +#define ICON_FA_STEP_BACKWARD u8"\uf048" +#define ICON_FA_DRAGON u8"\uf6d5" +#define ICON_FA_MICROPHONE_SLASH u8"\uf131" +#define ICON_FA_USER_PLUS u8"\uf234" +#define ICON_FA_WRENCH u8"\uf0ad" +#define ICON_FA_AMBULANCE u8"\uf0f9" +#define ICON_FA_ETHERNET u8"\uf796" +#define ICON_FA_EGG u8"\uf7fb" +#define ICON_FA_WIND u8"\uf72e" +#define ICON_FA_UNIVERSAL_ACCESS u8"\uf29a" +#define ICON_FA_BURN u8"\uf46a" +#define ICON_FA_HAND_HOLDING_HEART u8"\uf4be" +#define ICON_FA_DICE_ONE u8"\uf525" +#define ICON_FA_KEYBOARD u8"\uf11c" +#define ICON_FA_CHECK_DOUBLE u8"\uf560" +#define ICON_FA_HEADPHONES_ALT u8"\uf58f" +#define ICON_FA_BATTERY_HALF u8"\uf242" +#define ICON_FA_PROJECT_DIAGRAM u8"\uf542" +#define ICON_FA_PRAY u8"\uf683" +#define ICON_FA_GOPURAM u8"\uf664" +#define ICON_FA_GRIN_TEARS u8"\uf588" +#define ICON_FA_SORT_AMOUNT_UP u8"\uf161" +#define ICON_FA_COFFEE u8"\uf0f4" +#define ICON_FA_TABLET_ALT u8"\uf3fa" +#define ICON_FA_GRIN_BEAM_SWEAT u8"\uf583" +#define ICON_FA_HAND_POINT_RIGHT u8"\uf0a4" +#define ICON_FA_MAGIC u8"\uf0d0" +#define ICON_FA_CHARGING_STATION u8"\uf5e7" +#define ICON_FA_GRIN_TONGUE u8"\uf589" +#define ICON_FA_VOLUME_OFF u8"\uf026" +#define ICON_FA_SAD_TEAR u8"\uf5b4" +#define ICON_FA_CARET_RIGHT u8"\uf0da" +#define ICON_FA_BONG u8"\uf55c" +#define ICON_FA_BONE u8"\uf5d7" +#define ICON_FA_ELLIPSIS_V u8"\uf142" +#define ICON_FA_BALANCE_SCALE u8"\uf24e" +#define ICON_FA_FISH u8"\uf578" +#define ICON_FA_SPIDER u8"\uf717" +#define ICON_FA_CAMPGROUND u8"\uf6bb" +#define ICON_FA_CARET_SQUARE_UP u8"\uf151" +#define ICON_FA_RUPEE_SIGN u8"\uf156" +#define ICON_FA_ASSISTIVE_LISTENING_SYSTEMS u8"\uf2a2" +#define ICON_FA_POUND_SIGN u8"\uf154" +#define ICON_FA_ANKH u8"\uf644" +#define ICON_FA_BATTERY_QUARTER u8"\uf243" +#define ICON_FA_HAND_PEACE u8"\uf25b" +#define ICON_FA_SURPRISE u8"\uf5c2" +#define ICON_FA_FILE_PDF u8"\uf1c1" +#define ICON_FA_VIDEO_SLASH u8"\uf4e2" +#define ICON_FA_SUBWAY u8"\uf239" +#define ICON_FA_HORSE u8"\uf6f0" +#define ICON_FA_WINE_BOTTLE u8"\uf72f" +#define ICON_FA_BOOK_READER u8"\uf5da" +#define ICON_FA_COOKIE u8"\uf563" +#define ICON_FA_MONEY_BILL u8"\uf0d6" +#define ICON_FA_CHEVRON_DOWN u8"\uf078" +#define ICON_FA_CAR_SIDE u8"\uf5e4" +#define ICON_FA_FILTER u8"\uf0b0" +#define ICON_FA_FOLDER_OPEN u8"\uf07c" +#define ICON_FA_SIGNATURE u8"\uf5b7" +#define ICON_FA_SNOWBOARDING u8"\uf7ce" +#define ICON_FA_THUMBTACK u8"\uf08d" +#define ICON_FA_DICE_TWO u8"\uf528" +#define ICON_FA_LAUGH_WINK u8"\uf59c" +#define ICON_FA_BREAD_SLICE u8"\uf7ec" +#define ICON_FA_TEXT_HEIGHT u8"\uf034" +#define ICON_FA_VOLUME_MUTE u8"\uf6a9" +#define ICON_FA_VOTE_YEA u8"\uf772" +#define ICON_FA_QRCODE u8"\uf029" +#define ICON_FA_MERCURY u8"\uf223" +#define ICON_FA_USER_ASTRONAUT u8"\uf4fb" +#define ICON_FA_SORT_AMOUNT_DOWN u8"\uf160" +#define ICON_FA_SORT_DOWN u8"\uf0dd" +#define ICON_FA_COMPACT_DISC u8"\uf51f" +#define ICON_FA_PERCENTAGE u8"\uf541" +#define ICON_FA_COMMENT_MEDICAL u8"\uf7f5" +#define ICON_FA_STORE u8"\uf54e" +#define ICON_FA_COMMENT_DOTS u8"\uf4ad" +#define ICON_FA_SMILE_WINK u8"\uf4da" +#define ICON_FA_HOTEL u8"\uf594" +#define ICON_FA_PEPPER_HOT u8"\uf816" +#define ICON_FA_USER_EDIT u8"\uf4ff" +#define ICON_FA_DUMPSTER_FIRE u8"\uf794" +#define ICON_FA_CLOUD_SUN_RAIN u8"\uf743" +#define ICON_FA_GLOBE_ASIA u8"\uf57e" +#define ICON_FA_VIAL u8"\uf492" +#define ICON_FA_STROOPWAFEL u8"\uf551" +#define ICON_FA_DATABASE u8"\uf1c0" +#define ICON_FA_TREE u8"\uf1bb" +#define ICON_FA_SHOWER u8"\uf2cc" +#define ICON_FA_DRUM_STEELPAN u8"\uf56a" +#define ICON_FA_FILE_UPLOAD u8"\uf574" +#define ICON_FA_MEDKIT u8"\uf0fa" +#define ICON_FA_MINUS u8"\uf068" +#define ICON_FA_SHEKEL_SIGN u8"\uf20b" +#define ICON_FA_BELL_SLASH u8"\uf1f6" +#define ICON_FA_MAIL_BULK u8"\uf674" +#define ICON_FA_MOUNTAIN u8"\uf6fc" +#define ICON_FA_COUCH u8"\uf4b8" +#define ICON_FA_CHESS u8"\uf439" +#define ICON_FA_FILE_EXPORT u8"\uf56e" +#define ICON_FA_SIGN_LANGUAGE u8"\uf2a7" +#define ICON_FA_SNOWFLAKE u8"\uf2dc" +#define ICON_FA_PLAY u8"\uf04b" +#define ICON_FA_HEADSET u8"\uf590" +#define ICON_FA_SQUARE_ROOT_ALT u8"\uf698" +#define ICON_FA_CHART_BAR u8"\uf080" +#define ICON_FA_CHART_AREA u8"\uf1fe" +#define ICON_FA_EURO_SIGN u8"\uf153" +#define ICON_FA_CHESS_KING u8"\uf43f" +#define ICON_FA_MOBILE u8"\uf10b" +#define ICON_FA_BOX_OPEN u8"\uf49e" +#define ICON_FA_DOG u8"\uf6d3" +#define ICON_FA_FUTBOL u8"\uf1e3" +#define ICON_FA_LIRA_SIGN u8"\uf195" +#define ICON_FA_LIGHTBULB u8"\uf0eb" +#define ICON_FA_BOMB u8"\uf1e2" +#define ICON_FA_MITTEN u8"\uf7b5" +#define ICON_FA_TRUCK_MONSTER u8"\uf63b" +#define ICON_FA_ARROWS_ALT_H u8"\uf337" +#define ICON_FA_CHESS_ROOK u8"\uf447" +#define ICON_FA_FIRE_EXTINGUISHER u8"\uf134" +#define ICON_FA_BOOKMARK u8"\uf02e" +#define ICON_FA_ARROWS_ALT_V u8"\uf338" +#define ICON_FA_ICICLES u8"\uf7ad" +#define ICON_FA_FONT u8"\uf031" +#define ICON_FA_CAMERA_RETRO u8"\uf083" +#define ICON_FA_BLENDER u8"\uf517" +#define ICON_FA_THUMBS_DOWN u8"\uf165" +#define ICON_FA_GAMEPAD u8"\uf11b" +#define ICON_FA_COPYRIGHT u8"\uf1f9" +#define ICON_FA_JEDI u8"\uf669" +#define ICON_FA_HOCKEY_PUCK u8"\uf453" +#define ICON_FA_STOP_CIRCLE u8"\uf28d" +#define ICON_FA_BEZIER_CURVE u8"\uf55b" +#define ICON_FA_FOLDER u8"\uf07b" +#define ICON_FA_RSS u8"\uf09e" +#define ICON_FA_COLUMNS u8"\uf0db" +#define ICON_FA_GLASS_CHEERS u8"\uf79f" +#define ICON_FA_GRIN_WINK u8"\uf58c" +#define ICON_FA_STOP u8"\uf04d" +#define ICON_FA_MONEY_CHECK_ALT u8"\uf53d" +#define ICON_FA_COMPASS u8"\uf14e" +#define ICON_FA_TOOLBOX u8"\uf552" +#define ICON_FA_LIST_OL u8"\uf0cb" +#define ICON_FA_WINE_GLASS u8"\uf4e3" +#define ICON_FA_HORSE_HEAD u8"\uf7ab" +#define ICON_FA_USER_ALT_SLASH u8"\uf4fa" +#define ICON_FA_USER_TAG u8"\uf507" +#define ICON_FA_MICROSCOPE u8"\uf610" +#define ICON_FA_BRUSH u8"\uf55d" +#define ICON_FA_BAN u8"\uf05e" +#define ICON_FA_BARS u8"\uf0c9" +#define ICON_FA_CAR_CRASH u8"\uf5e1" +#define ICON_FA_ARROW_ALT_CIRCLE_DOWN u8"\uf358" +#define ICON_FA_MONEY_BILL_ALT u8"\uf3d1" +#define ICON_FA_JOURNAL_WHILLS u8"\uf66a" +#define ICON_FA_CHALKBOARD_TEACHER u8"\uf51c" +#define ICON_FA_PORTRAIT u8"\uf3e0" +#define ICON_FA_HAMMER u8"\uf6e3" +#define ICON_FA_RETWEET u8"\uf079" +#define ICON_FA_HOURGLASS u8"\uf254" +#define ICON_FA_HAND_PAPER u8"\uf256" +#define ICON_FA_SUBSCRIPT u8"\uf12c" +#define ICON_FA_DONATE u8"\uf4b9" +#define ICON_FA_GLASS_MARTINI_ALT u8"\uf57b" +#define ICON_FA_CODE_BRANCH u8"\uf126" +#define ICON_FA_NOT_EQUAL u8"\uf53e" +#define ICON_FA_MEH u8"\uf11a" +#define ICON_FA_LIST_ALT u8"\uf022" +#define ICON_FA_USER_COG u8"\uf4fe" +#define ICON_FA_PRESCRIPTION u8"\uf5b1" +#define ICON_FA_TABLET u8"\uf10a" +#define ICON_FA_PENCIL_RULER u8"\uf5ae" +#define ICON_FA_CREDIT_CARD u8"\uf09d" +#define ICON_FA_ARCHWAY u8"\uf557" +#define ICON_FA_HARD_HAT u8"\uf807" +#define ICON_FA_MAP_MARKER_ALT u8"\uf3c5" +#define ICON_FA_COG u8"\uf013" +#define ICON_FA_HANUKIAH u8"\uf6e6" +#define ICON_FA_SHUTTLE_VAN u8"\uf5b6" +#define ICON_FA_MONEY_CHECK u8"\uf53c" +#define ICON_FA_BELL u8"\uf0f3" +#define ICON_FA_CALENDAR_DAY u8"\uf783" +#define ICON_FA_TINT_SLASH u8"\uf5c7" +#define ICON_FA_PLANE_DEPARTURE u8"\uf5b0" +#define ICON_FA_USER_CHECK u8"\uf4fc" +#define ICON_FA_CHURCH u8"\uf51d" +#define ICON_FA_SEARCH_MINUS u8"\uf010" +#define ICON_FA_PALLET u8"\uf482" +#define ICON_FA_TINT u8"\uf043" +#define ICON_FA_STAMP u8"\uf5bf" +#define ICON_FA_KAABA u8"\uf66b" +#define ICON_FA_ALIGN_RIGHT u8"\uf038" +#define ICON_FA_QUOTE_RIGHT u8"\uf10e" +#define ICON_FA_BEER u8"\uf0fc" +#define ICON_FA_GRIN_ALT u8"\uf581" +#define ICON_FA_SORT_NUMERIC_DOWN u8"\uf162" +#define ICON_FA_FIRE u8"\uf06d" +#define ICON_FA_FAST_FORWARD u8"\uf050" +#define ICON_FA_MAP_MARKED_ALT u8"\uf5a0" +#define ICON_FA_PENCIL_ALT u8"\uf303" +#define ICON_FA_USERS_COG u8"\uf509" +#define ICON_FA_CARET_SQUARE_DOWN u8"\uf150" +#define ICON_FA_CRUTCH u8"\uf7f7" +#define ICON_FA_OBJECT_GROUP u8"\uf247" +#define ICON_FA_ANCHOR u8"\uf13d" +#define ICON_FA_HAND_POINT_LEFT u8"\uf0a5" +#define ICON_FA_USER_TIMES u8"\uf235" +#define ICON_FA_CALCULATOR u8"\uf1ec" +#define ICON_FA_DIZZY u8"\uf567" +#define ICON_FA_KISS_WINK_HEART u8"\uf598" +#define ICON_FA_FILE_MEDICAL u8"\uf477" +#define ICON_FA_SWIMMING_POOL u8"\uf5c5" +#define ICON_FA_WEIGHT_HANGING u8"\uf5cd" +#define ICON_FA_VR_CARDBOARD u8"\uf729" +#define ICON_FA_FAST_BACKWARD u8"\uf049" +#define ICON_FA_SATELLITE u8"\uf7bf" +#define ICON_FA_USER u8"\uf007" +#define ICON_FA_MINUS_CIRCLE u8"\uf056" +#define ICON_FA_CHESS_PAWN u8"\uf443" +#define ICON_FA_CALENDAR_MINUS u8"\uf272" +#define ICON_FA_CHESS_BOARD u8"\uf43c" +#define ICON_FA_LANDMARK u8"\uf66f" +#define ICON_FA_SWATCHBOOK u8"\uf5c3" +#define ICON_FA_HOTDOG u8"\uf80f" +#define ICON_FA_SNOWMAN u8"\uf7d0" +#define ICON_FA_LAPTOP u8"\uf109" +#define ICON_FA_TORAH u8"\uf6a0" +#define ICON_FA_FROWN_OPEN u8"\uf57a" +#define ICON_FA_USER_LOCK u8"\uf502" +#define ICON_FA_AD u8"\uf641" +#define ICON_FA_USER_CIRCLE u8"\uf2bd" +#define ICON_FA_DIVIDE u8"\uf529" +#define ICON_FA_HANDSHAKE u8"\uf2b5" +#define ICON_FA_CUT u8"\uf0c4" +#define ICON_FA_HIKING u8"\uf6ec" +#define ICON_FA_STREET_VIEW u8"\uf21d" +#define ICON_FA_GREATER_THAN u8"\uf531" +#define ICON_FA_PASTAFARIANISM u8"\uf67b" +#define ICON_FA_MINUS_SQUARE u8"\uf146" +#define ICON_FA_SAVE u8"\uf0c7" +#define ICON_FA_COMMENT_DOLLAR u8"\uf651" +#define ICON_FA_TRASH_ALT u8"\uf2ed" +#define ICON_FA_PUZZLE_PIECE u8"\uf12e" +#define ICON_FA_MENORAH u8"\uf676" +#define ICON_FA_CLOUD_SUN u8"\uf6c4" +#define ICON_FA_USER_FRIENDS u8"\uf500" +#define ICON_FA_FILE_MEDICAL_ALT u8"\uf478" +#define ICON_FA_ARROW_LEFT u8"\uf060" +#define ICON_FA_BOXES u8"\uf468" +#define ICON_FA_THERMOMETER_EMPTY u8"\uf2cb" +#define ICON_FA_EXCLAMATION_TRIANGLE u8"\uf071" +#define ICON_FA_GIFT u8"\uf06b" +#define ICON_FA_COGS u8"\uf085" +#define ICON_FA_SIGNAL u8"\uf012" +#define ICON_FA_SHAPES u8"\uf61f" +#define ICON_FA_CLOUD_RAIN u8"\uf73d" +#define ICON_FA_ELLIPSIS_H u8"\uf141" +#define ICON_FA_LESS_THAN_EQUAL u8"\uf537" +#define ICON_FA_CHEVRON_CIRCLE_LEFT u8"\uf137" +#define ICON_FA_MORTAR_PESTLE u8"\uf5a7" +#define ICON_FA_SITEMAP u8"\uf0e8" +#define ICON_FA_BUS_ALT u8"\uf55e" +#define ICON_FA_ID_BADGE u8"\uf2c1" +#define ICON_FA_FIST_RAISED u8"\uf6de" +#define ICON_FA_BATTERY_FULL u8"\uf240" +#define ICON_FA_CROWN u8"\uf521" +#define ICON_FA_EXCHANGE_ALT u8"\uf362" +#define ICON_FA_SOCKS u8"\uf696" +#define ICON_FA_CASH_REGISTER u8"\uf788" +#define ICON_FA_REDO u8"\uf01e" +#define ICON_FA_EXCLAMATION_CIRCLE u8"\uf06a" +#define ICON_FA_COMMENTS u8"\uf086" +#define ICON_FA_BRIEFCASE_MEDICAL u8"\uf469" +#define ICON_FA_CARET_SQUARE_RIGHT u8"\uf152" +#define ICON_FA_PEN u8"\uf304" +#define ICON_FA_BACKSPACE u8"\uf55a" +#define ICON_FA_SLASH u8"\uf715" +#define ICON_FA_HOT_TUB u8"\uf593" +#define ICON_FA_SUITCASE_ROLLING u8"\uf5c1" +#define ICON_FA_BATTERY_EMPTY u8"\uf244" +#define ICON_FA_GLOBE_AFRICA u8"\uf57c" +#define ICON_FA_SLEIGH u8"\uf7cc" +#define ICON_FA_BOLT u8"\uf0e7" +#define ICON_FA_THERMOMETER_QUARTER u8"\uf2ca" +#define ICON_FA_EYE u8"\uf06e" +#define ICON_FA_TROPHY u8"\uf091" +#define ICON_FA_BRAILLE u8"\uf2a1" +#define ICON_FA_PLUS u8"\uf067" +#define ICON_FA_LIST_UL u8"\uf0ca" +#define ICON_FA_SMOKING_BAN u8"\uf54d" +#define ICON_FA_BATH u8"\uf2cd" +#define ICON_FA_VOLUME_DOWN u8"\uf027" +#define ICON_FA_QUESTION_CIRCLE u8"\uf059" +#define ICON_FA_FILE_CODE u8"\uf1c9" +#define ICON_FA_GAVEL u8"\uf0e3" +#define ICON_FA_CANDY_CANE u8"\uf786" +#define ICON_FA_NETWORK_WIRED u8"\uf6ff" +#define ICON_FA_CARET_SQUARE_LEFT u8"\uf191" +#define ICON_FA_PLANE_ARRIVAL u8"\uf5af" +#define ICON_FA_SHARE_SQUARE u8"\uf14d" +#define ICON_FA_MEDAL u8"\uf5a2" +#define ICON_FA_THERMOMETER_HALF u8"\uf2c9" +#define ICON_FA_QUESTION u8"\uf128" +#define ICON_FA_CAR_BATTERY u8"\uf5df" +#define ICON_FA_DOOR_CLOSED u8"\uf52a" +#define ICON_FA_LEAF u8"\uf06c" +#define ICON_FA_USER_MINUS u8"\uf503" +#define ICON_FA_MUSIC u8"\uf001" +#define ICON_FA_GLOBE_EUROPE u8"\uf7a2" +#define ICON_FA_HOUSE_DAMAGE u8"\uf6f1" +#define ICON_FA_CHEVRON_RIGHT u8"\uf054" +#define ICON_FA_GRIP_HORIZONTAL u8"\uf58d" +#define ICON_FA_DICE_FOUR u8"\uf524" +#define ICON_FA_DEAF u8"\uf2a4" +#define ICON_FA_REGISTERED u8"\uf25d" +#define ICON_FA_WINDOW_CLOSE u8"\uf410" +#define ICON_FA_LINK u8"\uf0c1" +#define ICON_FA_YEN_SIGN u8"\uf157" +#define ICON_FA_ATOM u8"\uf5d2" +#define ICON_FA_LESS_THAN u8"\uf536" +#define ICON_FA_OTTER u8"\uf700" +#define ICON_FA_INFO u8"\uf129" +#define ICON_FA_MARS_DOUBLE u8"\uf227" +#define ICON_FA_CLIPBOARD_CHECK u8"\uf46c" +#define ICON_FA_SKULL u8"\uf54c" +#define ICON_FA_GRIP_LINES u8"\uf7a4" +#define ICON_FA_HOSPITAL_SYMBOL u8"\uf47e" +#define ICON_FA_X_RAY u8"\uf497" +#define ICON_FA_ARROW_UP u8"\uf062" +#define ICON_FA_MONEY_BILL_WAVE u8"\uf53a" +#define ICON_FA_DOT_CIRCLE u8"\uf192" +#define ICON_FA_PAUSE_CIRCLE u8"\uf28b" +#define ICON_FA_IMAGES u8"\uf302" +#define ICON_FA_STAR_HALF u8"\uf089" +#define ICON_FA_SPLOTCH u8"\uf5bc" +#define ICON_FA_STAR_HALF_ALT u8"\uf5c0" +#define ICON_FA_SHIP u8"\uf21a" +#define ICON_FA_BOOK_DEAD u8"\uf6b7" +#define ICON_FA_CHECK u8"\uf00c" +#define ICON_FA_RAINBOW u8"\uf75b" +#define ICON_FA_POWER_OFF u8"\uf011" +#define ICON_FA_LEMON u8"\uf094" +#define ICON_FA_GLOBE_AMERICAS u8"\uf57d" +#define ICON_FA_PEACE u8"\uf67c" +#define ICON_FA_THERMOMETER_THREE_QUARTERS u8"\uf2c8" +#define ICON_FA_WAREHOUSE u8"\uf494" +#define ICON_FA_TRANSGENDER u8"\uf224" +#define ICON_FA_PLUS_SQUARE u8"\uf0fe" +#define ICON_FA_BULLSEYE u8"\uf140" +#define ICON_FA_COOKIE_BITE u8"\uf564" +#define ICON_FA_USERS u8"\uf0c0" +#define ICON_FA_TRANSGENDER_ALT u8"\uf225" +#define ICON_FA_ASTERISK u8"\uf069" +#define ICON_FA_STAR_OF_DAVID u8"\uf69a" +#define ICON_FA_PLUS_CIRCLE u8"\uf055" +#define ICON_FA_CART_ARROW_DOWN u8"\uf218" +#define ICON_FA_FLUSHED u8"\uf579" +#define ICON_FA_STORE_ALT u8"\uf54f" +#define ICON_FA_PEOPLE_CARRY u8"\uf4ce" +#define ICON_FA_LONG_ARROW_ALT_DOWN u8"\uf309" +#define ICON_FA_SAD_CRY u8"\uf5b3" +#define ICON_FA_DIGITAL_TACHOGRAPH u8"\uf566" +#define ICON_FA_FILE_EXCEL u8"\uf1c3" +#define ICON_FA_TEETH u8"\uf62e" +#define ICON_FA_HAND_SCISSORS u8"\uf257" +#define ICON_FA_FILE_INVOICE_DOLLAR u8"\uf571" +#define ICON_FA_STEP_FORWARD u8"\uf051" +#define ICON_FA_BACKWARD u8"\uf04a" +#define ICON_FA_SCROLL u8"\uf70e" +#define ICON_FA_IGLOO u8"\uf7ae" +#define ICON_FA_CODE u8"\uf121" +#define ICON_FA_TRAM u8"\uf7da" +#define ICON_FA_TORII_GATE u8"\uf6a1" +#define ICON_FA_SKIING u8"\uf7c9" +#define ICON_FA_CHAIR u8"\uf6c0" +#define ICON_FA_DUMBBELL u8"\uf44b" +#define ICON_FA_ANGLE_DOUBLE_UP u8"\uf102" +#define ICON_FA_ANGLE_DOUBLE_LEFT u8"\uf100" +#define ICON_FA_MOSQUE u8"\uf678" +#define ICON_FA_COMMENTS_DOLLAR u8"\uf653" +#define ICON_FA_FILE_PRESCRIPTION u8"\uf572" +#define ICON_FA_ANGLE_LEFT u8"\uf104" +#define ICON_FA_ATLAS u8"\uf558" +#define ICON_FA_PIGGY_BANK u8"\uf4d3" +#define ICON_FA_DOLLY_FLATBED u8"\uf474" +#define ICON_FA_RANDOM u8"\uf074" +#define ICON_FA_PEN_ALT u8"\uf305" +#define ICON_FA_PRAYING_HANDS u8"\uf684" +#define ICON_FA_VOLUME_UP u8"\uf028" +#define ICON_FA_CLIPBOARD_LIST u8"\uf46d" +#define ICON_FA_GRIN_STARS u8"\uf587" +#define ICON_FA_FOLDER_MINUS u8"\uf65d" +#define ICON_FA_DEMOCRAT u8"\uf747" +#define ICON_FA_MAGNET u8"\uf076" +#define ICON_FA_VIHARA u8"\uf6a7" +#define ICON_FA_GRIMACE u8"\uf57f" +#define ICON_FA_CHECK_CIRCLE u8"\uf058" +#define ICON_FA_SEARCH_DOLLAR u8"\uf688" +#define ICON_FA_LONG_ARROW_ALT_LEFT u8"\uf30a" +#define ICON_FA_CROW u8"\uf520" +#define ICON_FA_EYE_DROPPER u8"\uf1fb" +#define ICON_FA_CROP u8"\uf125" +#define ICON_FA_SIGN u8"\uf4d9" +#define ICON_FA_ARROW_CIRCLE_DOWN u8"\uf0ab" +#define ICON_FA_VIDEO u8"\uf03d" +#define ICON_FA_DOWNLOAD u8"\uf019" +#define ICON_FA_BOLD u8"\uf032" +#define ICON_FA_CARET_DOWN u8"\uf0d7" +#define ICON_FA_CHEVRON_LEFT u8"\uf053" +#define ICON_FA_HAMSA u8"\uf665" +#define ICON_FA_CART_PLUS u8"\uf217" +#define ICON_FA_CLIPBOARD u8"\uf328" +#define ICON_FA_SHOE_PRINTS u8"\uf54b" +#define ICON_FA_PHONE_SLASH u8"\uf3dd" +#define ICON_FA_REPLY u8"\uf3e5" +#define ICON_FA_HOURGLASS_HALF u8"\uf252" +#define ICON_FA_LONG_ARROW_ALT_UP u8"\uf30c" +#define ICON_FA_CHESS_KNIGHT u8"\uf441" +#define ICON_FA_BARCODE u8"\uf02a" +#define ICON_FA_DRAW_POLYGON u8"\uf5ee" +#define ICON_FA_WATER u8"\uf773" +#define ICON_FA_PAUSE u8"\uf04c" +#define ICON_FA_WINE_GLASS_ALT u8"\uf5ce" +#define ICON_FA_GLASS_WHISKEY u8"\uf7a0" +#define ICON_FA_BOX u8"\uf466" +#define ICON_FA_DIAGNOSES u8"\uf470" +#define ICON_FA_FILE_IMAGE u8"\uf1c5" +#define ICON_FA_ARROW_CIRCLE_RIGHT u8"\uf0a9" +#define ICON_FA_TASKS u8"\uf0ae" +#define ICON_FA_VECTOR_SQUARE u8"\uf5cb" +#define ICON_FA_QUOTE_LEFT u8"\uf10d" +#define ICON_FA_MOBILE_ALT u8"\uf3cd" +#define ICON_FA_USER_SHIELD u8"\uf505" +#define ICON_FA_BLOG u8"\uf781" +#define ICON_FA_MARKER u8"\uf5a1" +#define ICON_FA_USER_TIE u8"\uf508" +#define ICON_FA_TOOLS u8"\uf7d9" +#define ICON_FA_CLOUD u8"\uf0c2" +#define ICON_FA_HAND_HOLDING_USD u8"\uf4c0" +#define ICON_FA_CERTIFICATE u8"\uf0a3" +#define ICON_FA_CLOUD_DOWNLOAD_ALT u8"\uf381" +#define ICON_FA_ANGRY u8"\uf556" +#define ICON_FA_FROG u8"\uf52e" +#define ICON_FA_CAMERA u8"\uf030" +#define ICON_FA_DICE_THREE u8"\uf527" +#define ICON_FA_MEMORY u8"\uf538" +#define ICON_FA_PEN_SQUARE u8"\uf14b" +#define ICON_FA_SORT u8"\uf0dc" +#define ICON_FA_PLUG u8"\uf1e6" +#define ICON_FA_MOUSE_POINTER u8"\uf245" +#define ICON_FA_ENVELOPE u8"\uf0e0" +#define ICON_FA_LAYER_GROUP u8"\uf5fd" +#define ICON_FA_TRAIN u8"\uf238" +#define ICON_FA_BULLHORN u8"\uf0a1" +#define ICON_FA_BABY u8"\uf77c" +#define ICON_FA_CONCIERGE_BELL u8"\uf562" +#define ICON_FA_CIRCLE u8"\uf111" +#define ICON_FA_I_CURSOR u8"\uf246" +#define ICON_FA_CAR u8"\uf1b9" +#define ICON_FA_CAT u8"\uf6be" +#define ICON_FA_WALLET u8"\uf555" +#define ICON_FA_BOOK_MEDICAL u8"\uf7e6" +#define ICON_FA_H_SQUARE u8"\uf0fd" +#define ICON_FA_HEART u8"\uf004" +#define ICON_FA_LOCK_OPEN u8"\uf3c1" +#define ICON_FA_STREAM u8"\uf550" +#define ICON_FA_LOCK u8"\uf023" +#define ICON_FA_CARROT u8"\uf787" +#define ICON_FA_SMILE_BEAM u8"\uf5b8" +#define ICON_FA_USER_NURSE u8"\uf82f" +#define ICON_FA_MICROPHONE_ALT u8"\uf3c9" +#define ICON_FA_SPA u8"\uf5bb" +#define ICON_FA_CHEVRON_CIRCLE_DOWN u8"\uf13a" +#define ICON_FA_FOLDER_PLUS u8"\uf65e" +#define ICON_FA_CLOUD_MEATBALL u8"\uf73b" +#define ICON_FA_BOOK_OPEN u8"\uf518" +#define ICON_FA_MAP u8"\uf279" +#define ICON_FA_COCKTAIL u8"\uf561" +#define ICON_FA_CLONE u8"\uf24d" +#define ICON_FA_ID_CARD_ALT u8"\uf47f" +#define ICON_FA_CHECK_SQUARE u8"\uf14a" +#define ICON_FA_CHART_LINE u8"\uf201" +#define ICON_FA_FILE_ARCHIVE u8"\uf1c6" +#define ICON_FA_DOVE u8"\uf4ba" +#define ICON_FA_MARS_STROKE u8"\uf229" +#define ICON_FA_ENVELOPE_OPEN u8"\uf2b6" +#define ICON_FA_WHEELCHAIR u8"\uf193" +#define ICON_FA_ROBOT u8"\uf544" +#define ICON_FA_UNDO_ALT u8"\uf2ea" +#define ICON_FA_TICKET_ALT u8"\uf3ff" +#define ICON_FA_TRUCK u8"\uf0d1" +#define ICON_FA_WON_SIGN u8"\uf159" +#define ICON_FA_SUPERSCRIPT u8"\uf12b" +#define ICON_FA_TTY u8"\uf1e4" +#define ICON_FA_USER_MD u8"\uf0f0" +#define ICON_FA_ALIGN_LEFT u8"\uf036" +#define ICON_FA_TABLETS u8"\uf490" +#define ICON_FA_MOTORCYCLE u8"\uf21c" +#define ICON_FA_ANGLE_UP u8"\uf106" +#define ICON_FA_BROOM u8"\uf51a" +#define ICON_FA_DICE_D20 u8"\uf6cf" +#define ICON_FA_LEVEL_DOWN_ALT u8"\uf3be" +#define ICON_FA_PAPERCLIP u8"\uf0c6" +#define ICON_FA_USER_CLOCK u8"\uf4fd" +#define ICON_FA_SORT_ALPHA_UP u8"\uf15e" +#define ICON_FA_AUDIO_DESCRIPTION u8"\uf29e" +#define ICON_FA_FILE_CSV u8"\uf6dd" +#define ICON_FA_FILE_DOWNLOAD u8"\uf56d" +#define ICON_FA_SYNC_ALT u8"\uf2f1" +#define ICON_FA_KISS u8"\uf596" +#define ICON_FA_HANDS u8"\uf4c2" +#define ICON_FA_REPUBLICAN u8"\uf75e" +#define ICON_FA_EDIT u8"\uf044" +#define ICON_FA_UNIVERSITY u8"\uf19c" +#define ICON_FA_KHANDA u8"\uf66d" +#define ICON_FA_GLASSES u8"\uf530" +#define ICON_FA_SQUARE u8"\uf0c8" +#define ICON_FA_GRIN_SQUINT u8"\uf585" +#define ICON_FA_GLOBE u8"\uf0ac" +#define ICON_FA_RECEIPT u8"\uf543" +#define ICON_FA_STRIKETHROUGH u8"\uf0cc" +#define ICON_FA_UNLOCK u8"\uf09c" +#define ICON_FA_DICE_SIX u8"\uf526" +#define ICON_FA_GRIP_VERTICAL u8"\uf58e" +#define ICON_FA_PILLS u8"\uf484" +#define ICON_FA_EXCLAMATION u8"\uf12a" +#define ICON_FA_PERSON_BOOTH u8"\uf756" +#define ICON_FA_CALENDAR_PLUS u8"\uf271" +#define ICON_FA_SMOG u8"\uf75f" +#define ICON_FA_LOCATION_ARROW u8"\uf124" +#define ICON_FA_UMBRELLA u8"\uf0e9" +#define ICON_FA_QURAN u8"\uf687" +#define ICON_FA_UNDO u8"\uf0e2" +#define ICON_FA_DUMPSTER u8"\uf793" +#define ICON_FA_FUNNEL_DOLLAR u8"\uf662" +#define ICON_FA_INDENT u8"\uf03c" +#define ICON_FA_LANGUAGE u8"\uf1ab" +#define ICON_FA_ARROW_ALT_CIRCLE_UP u8"\uf35b" +#define ICON_FA_ROUTE u8"\uf4d7" +#define ICON_FA_USER_ALT u8"\uf406" +#define ICON_FA_TIMES u8"\uf00d" +#define ICON_FA_CLINIC_MEDICAL u8"\uf7f2" +#define ICON_FA_LEVEL_UP_ALT u8"\uf3bf" +#define ICON_FA_BLIND u8"\uf29d" +#define ICON_FA_CHEESE u8"\uf7ef" +#define ICON_FA_PHONE_SQUARE u8"\uf098" +#define ICON_FA_SHOPPING_BASKET u8"\uf291" +#define ICON_FA_ICE_CREAM u8"\uf810" +#define ICON_FA_RING u8"\uf70b" +#define ICON_FA_CITY u8"\uf64f" +#define ICON_FA_TEXT_WIDTH u8"\uf035" +#define ICON_FA_RSS_SQUARE u8"\uf143" +#define ICON_FA_PAINT_BRUSH u8"\uf1fc" +#define ICON_FA_PARACHUTE_BOX u8"\uf4cd" +#define ICON_FA_SIM_CARD u8"\uf7c4" +#define ICON_FA_CLOUD_UPLOAD_ALT u8"\uf382" +#define ICON_FA_SORT_UP u8"\uf0de" +#define ICON_FA_SIGN_OUT_ALT u8"\uf2f5" +#define ICON_FA_USER_NINJA u8"\uf504" +#define ICON_FA_SIGN_IN_ALT u8"\uf2f6" +#define ICON_FA_MUG_HOT u8"\uf7b6" +#define ICON_FA_SHARE_ALT u8"\uf1e0" +#define ICON_FA_CALENDAR_CHECK u8"\uf274" +#define ICON_FA_PEN_FANCY u8"\uf5ac" +#define ICON_FA_BIOHAZARD u8"\uf780" +#define ICON_FA_BED u8"\uf236" +#define ICON_FA_FILE_SIGNATURE u8"\uf573" +#define ICON_FA_TOGGLE_OFF u8"\uf204" +#define ICON_FA_TRAFFIC_LIGHT u8"\uf637" +#define ICON_FA_TRACTOR u8"\uf722" +#define ICON_FA_MEH_ROLLING_EYES u8"\uf5a5" +#define ICON_FA_COMMENT_ALT u8"\uf27a" +#define ICON_FA_RULER_HORIZONTAL u8"\uf547" +#define ICON_FA_PAINT_ROLLER u8"\uf5aa" +#define ICON_FA_HAT_WIZARD u8"\uf6e8" +#define ICON_FA_CALENDAR u8"\uf133" +#define ICON_FA_MICROPHONE u8"\uf130" +#define ICON_FA_FOOTBALL_BALL u8"\uf44e" +#define ICON_FA_ALLERGIES u8"\uf461" +#define ICON_FA_ID_CARD u8"\uf2c2" +#define ICON_FA_REDO_ALT u8"\uf2f9" +#define ICON_FA_PLAY_CIRCLE u8"\uf144" +#define ICON_FA_THERMOMETER u8"\uf491" +#define ICON_FA_DOLLAR_SIGN u8"\uf155" +#define ICON_FA_DUNGEON u8"\uf6d9" +#define ICON_FA_COMPRESS u8"\uf066" +#define ICON_FA_SEARCH_LOCATION u8"\uf689" +#define ICON_FA_BLENDER_PHONE u8"\uf6b6" +#define ICON_FA_ANGLE_RIGHT u8"\uf105" +#define ICON_FA_CHESS_QUEEN u8"\uf445" +#define ICON_FA_PAGER u8"\uf815" +#define ICON_FA_MEH_BLANK u8"\uf5a4" +#define ICON_FA_EJECT u8"\uf052" +#define ICON_FA_HOURGLASS_END u8"\uf253" +#define ICON_FA_TOOTH u8"\uf5c9" +#define ICON_FA_BUSINESS_TIME u8"\uf64a" +#define ICON_FA_PLACE_OF_WORSHIP u8"\uf67f" +#define ICON_FA_MOON u8"\uf186" +#define ICON_FA_GRIN_TONGUE_SQUINT u8"\uf58a" +#define ICON_FA_WALKING u8"\uf554" +#define ICON_FA_SHIPPING_FAST u8"\uf48b" +#define ICON_FA_CARET_LEFT u8"\uf0d9" +#define ICON_FA_DICE u8"\uf522" +#define ICON_FA_RUBLE_SIGN u8"\uf158" +#define ICON_FA_RULER_VERTICAL u8"\uf548" +#define ICON_FA_HAND_POINTER u8"\uf25a" +#define ICON_FA_TAPE u8"\uf4db" +#define ICON_FA_SHOPPING_BAG u8"\uf290" +#define ICON_FA_SKIING_NORDIC u8"\uf7ca" +#define ICON_FA_HIPPO u8"\uf6ed" +#define ICON_FA_CUBE u8"\uf1b2" +#define ICON_FA_CAPSULES u8"\uf46b" +#define ICON_FA_KIWI_BIRD u8"\uf535" +#define ICON_FA_CHEVRON_CIRCLE_UP u8"\uf139" +#define ICON_FA_MARS_STROKE_V u8"\uf22a" +#define ICON_FA_POO_STORM u8"\uf75a" +#define ICON_FA_JOINT u8"\uf595" +#define ICON_FA_MARS_STROKE_H u8"\uf22b" +#define ICON_FA_ADDRESS_BOOK u8"\uf2b9" +#define ICON_FA_PROCEDURES u8"\uf487" +#define ICON_FA_GEM u8"\uf3a5" +#define ICON_FA_RULER_COMBINED u8"\uf546" +#define ICON_FA_BRAIN u8"\uf5dc" +#define ICON_FA_STAR_AND_CRESCENT u8"\uf699" +#define ICON_FA_FIGHTER_JET u8"\uf0fb" +#define ICON_FA_SPACE_SHUTTLE u8"\uf197" +#define ICON_FA_MAP_PIN u8"\uf276" +#define ICON_FA_ALIGN_CENTER u8"\uf037" +#define ICON_FA_SORT_ALPHA_DOWN u8"\uf15d" +#define ICON_FA_PARKING u8"\uf540" +#define ICON_FA_MAP_SIGNS u8"\uf277" +#define ICON_FA_PALETTE u8"\uf53f" +#define ICON_FA_GLASS_MARTINI u8"\uf000" +#define ICON_FA_TIMES_CIRCLE u8"\uf057" +#define ICON_FA_MONUMENT u8"\uf5a6" +#define ICON_FA_GUITAR u8"\uf7a6" +#define ICON_FA_GRIN_BEAM u8"\uf582" +#define ICON_FA_KEY u8"\uf084" +#define ICON_FA_TH_LIST u8"\uf00b" +#define ICON_FA_SHARE_ALT_SQUARE u8"\uf1e1" +#define ICON_FA_DRUM u8"\uf569" +#define ICON_FA_FILE_CONTRACT u8"\uf56c" +#define ICON_FA_RESTROOM u8"\uf7bd" +#define ICON_FA_UNLOCK_ALT u8"\uf13e" +#define ICON_FA_MICROPHONE_ALT_SLASH u8"\uf539" +#define ICON_FA_USER_SECRET u8"\uf21b" +#define ICON_FA_ARROW_RIGHT u8"\uf061" +#define ICON_FA_FILE_VIDEO u8"\uf1c8" +#define ICON_FA_ARROW_ALT_CIRCLE_RIGHT u8"\uf35a" +#define ICON_FA_COMMENT u8"\uf075" +#define ICON_FA_CALENDAR_WEEK u8"\uf784" +#define ICON_FA_USER_GRADUATE u8"\uf501" +#define ICON_FA_HAND_MIDDLE_FINGER u8"\uf806" +#define ICON_FA_POO u8"\uf2fe" +#define ICON_FA_GRIP_LINES_VERTICAL u8"\uf7a5" +#define ICON_FA_TABLE u8"\uf0ce" +#define ICON_FA_POLL u8"\uf681" +#define ICON_FA_CAR_ALT u8"\uf5de" +#define ICON_FA_THUMBS_UP u8"\uf164" +#define ICON_FA_TRADEMARK u8"\uf25c" +#define ICON_FA_CLOUD_MOON u8"\uf6c3" +#define ICON_FA_VIALS u8"\uf493" +#define ICON_FA_FIRST_AID u8"\uf479" +#define ICON_FA_ERASER u8"\uf12d" +#define ICON_FA_MARS u8"\uf222" +#define ICON_FA_STAR_OF_LIFE u8"\uf621" +#define ICON_FA_FEATHER u8"\uf52d" +#define ICON_FA_SQUARE_FULL u8"\uf45c" +#define ICON_FA_DOLLY u8"\uf472" +#define ICON_FA_HOURGLASS_START u8"\uf251" +#define ICON_FA_GRIN_HEARTS u8"\uf584" +#define ICON_FA_CUBES u8"\uf1b3" +#define ICON_FA_HASHTAG u8"\uf292" +#define ICON_FA_SEEDLING u8"\uf4d8" +#define ICON_FA_HAYKAL u8"\uf666" +#define ICON_FA_TSHIRT u8"\uf553" +#define ICON_FA_LAUGH_SQUINT u8"\uf59b" +#define ICON_FA_HDD u8"\uf0a0" +#define ICON_FA_NEWSPAPER u8"\uf1ea" +#define ICON_FA_HOSPITAL_ALT u8"\uf47d" +#define ICON_FA_USER_SLASH u8"\uf506" +#define ICON_FA_FILE_WORD u8"\uf1c2" +#define ICON_FA_ENVELOPE_SQUARE u8"\uf199" +#define ICON_FA_GENDERLESS u8"\uf22d" +#define ICON_FA_DICE_FIVE u8"\uf523" +#define ICON_FA_SYNAGOGUE u8"\uf69b" +#define ICON_FA_PAW u8"\uf1b0" +#define ICON_FA_RADIATION u8"\uf7b9" +#define ICON_FA_CROSS u8"\uf654" +#define ICON_FA_ARCHIVE u8"\uf187" +#define ICON_FA_PHONE_VOLUME u8"\uf2a0" +#define ICON_FA_SOLAR_PANEL u8"\uf5ba" +#define ICON_FA_INFINITY u8"\uf534" +#define ICON_FA_HAND_POINT_DOWN u8"\uf0a7" +#define ICON_FA_MAP_MARKER u8"\uf041" +#define ICON_FA_CALENDAR_ALT u8"\uf073" +#define ICON_FA_AMERICAN_SIGN_LANGUAGE_INTERPRETING u8"\uf2a3" +#define ICON_FA_BINOCULARS u8"\uf1e5" +#define ICON_FA_STICKY_NOTE u8"\uf249" +#define ICON_FA_RUNNING u8"\uf70c" +#define ICON_FA_PEN_NIB u8"\uf5ad" +#define ICON_FA_MAP_MARKED u8"\uf59f" +#define ICON_FA_EXPAND u8"\uf065" +#define ICON_FA_TRUCK_PICKUP u8"\uf63c" +#define ICON_FA_HOLLY_BERRY u8"\uf7aa" +#define ICON_FA_PRESCRIPTION_BOTTLE u8"\uf485" +#define ICON_FA_LAPTOP_CODE u8"\uf5fc" +#define ICON_FA_GOLF_BALL u8"\uf450" +#define ICON_FA_SKULL_CROSSBONES u8"\uf714" +#define ICON_FA_TAXI u8"\uf1ba" +#define ICON_FA_ROCKET u8"\uf135" +#define ICON_FA_YIN_YANG u8"\uf6ad" +#define ICON_FA_FINGERPRINT u8"\uf577" +#define ICON_FA_ARROWS_ALT u8"\uf0b2" +#define ICON_FA_UNDERLINE u8"\uf0cd" +#define ICON_FA_ARROW_CIRCLE_UP u8"\uf0aa" +#define ICON_FA_BASKETBALL_BALL u8"\uf434" +#define ICON_FA_DESKTOP u8"\uf108" +#define ICON_FA_SPINNER u8"\uf110" +#define ICON_FA_TOGGLE_ON u8"\uf205" +#define ICON_FA_STOPWATCH u8"\uf2f2" +#define ICON_FA_ARROW_ALT_CIRCLE_LEFT u8"\uf359" +#define ICON_FA_GAS_PUMP u8"\uf52f" +#define ICON_FA_EXTERNAL_LINK_ALT u8"\uf35d" +#define ICON_FA_FROWN u8"\uf119" +#define ICON_FA_RULER u8"\uf545" +#define ICON_FA_FLAG_USA u8"\uf74d" +#define ICON_FA_GRIN u8"\uf580" +#define ICON_FA_THEATER_MASKS u8"\uf630" +#define ICON_FA_ARROW_CIRCLE_LEFT u8"\uf0a8" +#define ICON_FA_HIGHLIGHTER u8"\uf591" +#define ICON_FA_POLL_H u8"\uf682" +#define ICON_FA_SERVER u8"\uf233" +#define ICON_FA_TRASH_RESTORE u8"\uf829" +#define ICON_FA_SPRAY_CAN u8"\uf5bd" +#define ICON_FA_BOWLING_BALL u8"\uf436" +#define ICON_FA_LAUGH u8"\uf599" +#define ICON_FA_TERMINAL u8"\uf120" +#define ICON_FA_WINDOW_MINIMIZE u8"\uf2d1" +#define ICON_FA_HOME u8"\uf015" +#define ICON_FA_UTENSIL_SPOON u8"\uf2e5" +#define ICON_FA_QUIDDITCH u8"\uf458" +#define ICON_FA_APPLE_ALT u8"\uf5d1" +#define ICON_FA_UMBRELLA_BEACH u8"\uf5ca" +#define ICON_FA_CANNABIS u8"\uf55f" +#define ICON_FA_LAUGH_BEAM u8"\uf59a" +#define ICON_FA_TEETH_OPEN u8"\uf62f" +#define ICON_FA_DRUMSTICK_BITE u8"\uf6d7" +#define ICON_FA_CHART_PIE u8"\uf200" +#define ICON_FA_SD_CARD u8"\uf7c2" +#define ICON_FA_HANDS_HELPING u8"\uf4c4" +#define ICON_FA_PASTE u8"\uf0ea" +#define ICON_FA_OM u8"\uf679" +#define ICON_FA_LUGGAGE_CART u8"\uf59d" +#define ICON_FA_INDUSTRY u8"\uf275" +#define ICON_FA_SWIMMER u8"\uf5c4" +#define ICON_FA_RADIATION_ALT u8"\uf7ba" +#define ICON_FA_COMPRESS_ARROWS_ALT u8"\uf78c" +#define ICON_FA_ROAD u8"\uf018" +#define ICON_FA_IMAGE u8"\uf03e" +#define ICON_FA_CHILD u8"\uf1ae" +#define ICON_FA_ANGLE_DOUBLE_RIGHT u8"\uf101" +#define ICON_FA_CLOUD_MOON_RAIN u8"\uf73c" +#define ICON_FA_DOOR_OPEN u8"\uf52b" +#define ICON_FA_GRIN_TONGUE_WINK u8"\uf58b" +#define ICON_FA_REPLY_ALL u8"\uf122" +#define ICON_FA_TEMPERATURE_LOW u8"\uf76b" +#define ICON_FA_INBOX u8"\uf01c" +#define ICON_FA_FEMALE u8"\uf182" +#define ICON_FA_SYRINGE u8"\uf48e" +#define ICON_FA_CIRCLE_NOTCH u8"\uf1ce" +#define ICON_FA_WEIGHT u8"\uf496" +#define ICON_FA_SNOWPLOW u8"\uf7d2" +#define ICON_FA_TABLE_TENNIS u8"\uf45d" +#define ICON_FA_LOW_VISION u8"\uf2a8" +#define ICON_FA_FILE_IMPORT u8"\uf56f" +#define ICON_FA_ITALIC u8"\uf033" +#define ICON_FA_CLOSED_CAPTIONING u8"\uf20a" +#define ICON_FA_CHALKBOARD u8"\uf51b" +#define ICON_FA_BUILDING u8"\uf1ad" +#define ICON_FA_TACHOMETER_ALT u8"\uf3fd" +#define ICON_FA_BUS u8"\uf207" +#define ICON_FA_ANGLE_DOWN u8"\uf107" +#define ICON_FA_HAND_ROCK u8"\uf255" +#define ICON_FA_FORWARD u8"\uf04e" +#define ICON_FA_HELICOPTER u8"\uf533" +#define ICON_FA_PODCAST u8"\uf2ce" +#define ICON_FA_TRUCK_MOVING u8"\uf4df" +#define ICON_FA_BUG u8"\uf188" +#define ICON_FA_SHIELD_ALT u8"\uf3ed" +#define ICON_FA_FILL_DRIP u8"\uf576" +#define ICON_FA_COMMENT_SLASH u8"\uf4b3" +#define ICON_FA_SUITCASE u8"\uf0f2" +#define ICON_FA_SKATING u8"\uf7c5" +#define ICON_FA_TOILET u8"\uf7d8" +#define ICON_FA_ENVELOPE_OPEN_TEXT u8"\uf658" +#define ICON_FA_HAND_HOLDING u8"\uf4bd" +#define ICON_FA_VENUS_MARS u8"\uf228" +#define ICON_FA_HEART_BROKEN u8"\uf7a9" +#define ICON_FA_UTENSILS u8"\uf2e7" +#define ICON_FA_TH_LARGE u8"\uf009" +#define ICON_FA_AT u8"\uf1fa" +#define ICON_FA_FILE u8"\uf15b" +#define ICON_FA_TENGE u8"\uf7d7" +#define ICON_FA_FLAG_CHECKERED u8"\uf11e" +#define ICON_FA_FILM u8"\uf008" +#define ICON_FA_FILL u8"\uf575" +#define ICON_FA_GRIN_SQUINT_TEARS u8"\uf586" +#define ICON_FA_PERCENT u8"\uf295" +#define ICON_FA_BOOK u8"\uf02d" +#define ICON_FA_METEOR u8"\uf753" +#define ICON_FA_TRASH u8"\uf1f8" +#define ICON_FA_FILE_AUDIO u8"\uf1c7" +#define ICON_FA_SATELLITE_DISH u8"\uf7c0" +#define ICON_FA_POOP u8"\uf619" +#define ICON_FA_STAR u8"\uf005" +#define ICON_FA_GIFTS u8"\uf79c" +#define ICON_FA_GHOST u8"\uf6e2" +#define ICON_FA_PRESCRIPTION_BOTTLE_ALT u8"\uf486" +#define ICON_FA_MONEY_BILL_WAVE_ALT u8"\uf53b" +#define ICON_FA_NEUTER u8"\uf22c" +#define ICON_FA_BAND_AID u8"\uf462" +#define ICON_FA_WIFI u8"\uf1eb" +#define ICON_FA_MASK u8"\uf6fa" +#define ICON_FA_VENUS_DOUBLE u8"\uf226" +#define ICON_FA_CHEVRON_UP u8"\uf077" +#define ICON_FA_HAND_SPOCK u8"\uf259" +#define ICON_FA_HAND_POINT_UP u8"\uf0a6" diff --git a/src/resource/cemu.rc b/src/resource/cemu.rc new file mode 100644 index 00000000..11f70d04 Binary files /dev/null and b/src/resource/cemu.rc differ diff --git a/src/resource/debugger/icons8-drop-down-50.png b/src/resource/debugger/icons8-drop-down-50.png new file mode 100644 index 00000000..6a3e71d8 Binary files /dev/null and b/src/resource/debugger/icons8-drop-down-50.png differ diff --git a/src/resource/debugger/icons8-full-moon-filled-50-red.png b/src/resource/debugger/icons8-full-moon-filled-50-red.png new file mode 100644 index 00000000..d8232808 Binary files /dev/null and b/src/resource/debugger/icons8-full-moon-filled-50-red.png differ diff --git a/src/resource/debugger/icons8-full-moon-filled-50.png b/src/resource/debugger/icons8-full-moon-filled-50.png new file mode 100644 index 00000000..6f39f4e9 Binary files /dev/null and b/src/resource/debugger/icons8-full-moon-filled-50.png differ diff --git a/src/resource/debugger/icons8-pause-filled-50.png b/src/resource/debugger/icons8-pause-filled-50.png new file mode 100644 index 00000000..07790796 Binary files /dev/null and b/src/resource/debugger/icons8-pause-filled-50.png differ diff --git a/src/resource/debugger/icons8-play-filled-50.png b/src/resource/debugger/icons8-play-filled-50.png new file mode 100644 index 00000000..4207fa50 Binary files /dev/null and b/src/resource/debugger/icons8-play-filled-50.png differ diff --git a/src/resource/debugger/icons8-step-into-50.png b/src/resource/debugger/icons8-step-into-50.png new file mode 100644 index 00000000..5f999961 Binary files /dev/null and b/src/resource/debugger/icons8-step-into-50.png differ diff --git a/src/resource/debugger/icons8-step-out-50.png b/src/resource/debugger/icons8-step-out-50.png new file mode 100644 index 00000000..c3e5acb2 Binary files /dev/null and b/src/resource/debugger/icons8-step-out-50.png differ diff --git a/src/resource/debugger/icons8-step-over-50.png b/src/resource/debugger/icons8-step-over-50.png new file mode 100644 index 00000000..bd7b7fe2 Binary files /dev/null and b/src/resource/debugger/icons8-step-over-50.png differ diff --git a/src/resource/fontawesome-webfont.ttf b/src/resource/fontawesome-webfont.ttf new file mode 100644 index 00000000..35acda2f Binary files /dev/null and b/src/resource/fontawesome-webfont.ttf differ diff --git a/src/resource/icons8-cardboard-box-filled-50.png b/src/resource/icons8-cardboard-box-filled-50.png new file mode 100644 index 00000000..a778b691 Binary files /dev/null and b/src/resource/icons8-cardboard-box-filled-50.png differ diff --git a/src/resource/icons8-checkmark-yes-32.png b/src/resource/icons8-checkmark-yes-32.png new file mode 100644 index 00000000..40457252 Binary files /dev/null and b/src/resource/icons8-checkmark-yes-32.png differ diff --git a/src/resource/icons8-compose-filled-50.ico b/src/resource/icons8-compose-filled-50.ico new file mode 100644 index 00000000..d3b102c1 Binary files /dev/null and b/src/resource/icons8-compose-filled-50.ico differ diff --git a/src/resource/icons8-error-32.png b/src/resource/icons8-error-32.png new file mode 100644 index 00000000..fad92f4a Binary files /dev/null and b/src/resource/icons8-error-32.png differ diff --git a/src/resource/icons8-help-24.png b/src/resource/icons8-help-24.png new file mode 100644 index 00000000..d078cb7f Binary files /dev/null and b/src/resource/icons8-help-24.png differ diff --git a/src/resource/icons8-refresh-32.png b/src/resource/icons8-refresh-32.png new file mode 100644 index 00000000..f5ede3da Binary files /dev/null and b/src/resource/icons8-refresh-32.png differ diff --git a/src/resource/icons8_automatic_26_xQK_icon.ico b/src/resource/icons8_automatic_26_xQK_icon.ico new file mode 100644 index 00000000..764d57e0 Binary files /dev/null and b/src/resource/icons8_automatic_26_xQK_icon.ico differ diff --git a/src/resource/icons8_cardboard_box_filled.ico b/src/resource/icons8_cardboard_box_filled.ico new file mode 100644 index 00000000..41345c9a Binary files /dev/null and b/src/resource/icons8_cardboard_box_filled.ico differ diff --git a/src/resource/input/icons8-connected-50.png b/src/resource/input/icons8-connected-50.png new file mode 100644 index 00000000..1cfaf67c Binary files /dev/null and b/src/resource/input/icons8-connected-50.png differ diff --git a/src/resource/input/icons8-disconnected-50.png b/src/resource/input/icons8-disconnected-50.png new file mode 100644 index 00000000..cc2218e7 Binary files /dev/null and b/src/resource/input/icons8-disconnected-50.png differ diff --git a/src/resource/input/icons8-game-controller-24.ico b/src/resource/input/icons8-game-controller-24.ico new file mode 100644 index 00000000..59f15275 Binary files /dev/null and b/src/resource/input/icons8-game-controller-24.ico differ diff --git a/src/resource/input/icons8-game-controller-24.png b/src/resource/input/icons8-game-controller-24.png new file mode 100644 index 00000000..22b4d81d Binary files /dev/null and b/src/resource/input/icons8-game-controller-24.png differ diff --git a/src/resource/input/icons8-low-battery-30.png b/src/resource/input/icons8-low-battery-30.png new file mode 100644 index 00000000..88e36e1b Binary files /dev/null and b/src/resource/input/icons8-low-battery-30.png differ diff --git a/src/resource/linux/DEBUGGER_BP.hpng b/src/resource/linux/DEBUGGER_BP.hpng new file mode 100644 index 00000000..d23b5ab3 --- /dev/null +++ b/src/resource/linux/DEBUGGER_BP.hpng @@ -0,0 +1,39 @@ +unsigned char DEBUGGER_BP_png[436] = { + 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, + 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, + 0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0xF3, 0xFF, 0x61, 0x00, 0x00, 0x00, + 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE, 0xCE, 0x1C, 0xE9, 0x00, 0x00, + 0x00, 0x04, 0x67, 0x41, 0x4D, 0x41, 0x00, 0x00, 0xB1, 0x8F, 0x0B, 0xFC, + 0x61, 0x05, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, + 0x0D, 0xD6, 0x00, 0x00, 0x0D, 0xD6, 0x01, 0x90, 0x6F, 0x79, 0x9C, 0x00, + 0x00, 0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6F, 0x66, 0x74, 0x77, + 0x61, 0x72, 0x65, 0x00, 0x70, 0x61, 0x69, 0x6E, 0x74, 0x2E, 0x6E, 0x65, + 0x74, 0x20, 0x34, 0x2E, 0x30, 0x2E, 0x32, 0x31, 0xF1, 0x20, 0x69, 0x95, + 0x00, 0x00, 0x01, 0x24, 0x49, 0x44, 0x41, 0x54, 0x38, 0x4F, 0x8D, 0xD3, + 0xBB, 0x4A, 0x03, 0x51, 0x14, 0x46, 0xE1, 0x41, 0xBC, 0x82, 0xE9, 0xB4, + 0xF1, 0x92, 0x60, 0x95, 0x28, 0x5E, 0xB0, 0x0C, 0x82, 0xF8, 0x0E, 0x1A, + 0x7C, 0x82, 0xA8, 0x5D, 0x48, 0xBA, 0x84, 0x58, 0x59, 0xF8, 0x48, 0xD6, + 0xDA, 0x88, 0xA5, 0x42, 0x6C, 0xD4, 0x42, 0x34, 0x28, 0x88, 0xD8, 0x88, + 0x8A, 0x22, 0xB8, 0xD6, 0x90, 0x04, 0x8C, 0x19, 0x73, 0x7E, 0xF8, 0x60, + 0xF6, 0xC0, 0xCC, 0x9C, 0xB3, 0xCF, 0x9E, 0x28, 0x21, 0xA3, 0xD8, 0xC0, + 0x2E, 0x76, 0xB0, 0x86, 0x11, 0xF4, 0x4D, 0x0A, 0x87, 0x78, 0xC0, 0x1B, + 0x9E, 0x5B, 0xDE, 0x71, 0x8B, 0x7D, 0x8C, 0xA1, 0x67, 0x66, 0x71, 0x81, + 0x26, 0xFC, 0xF2, 0x14, 0x86, 0x30, 0x8C, 0x34, 0xCA, 0x78, 0xC2, 0x29, + 0x26, 0xF0, 0x2B, 0xE3, 0x38, 0xC7, 0x19, 0x26, 0xBD, 0x91, 0x90, 0x19, + 0xF8, 0x91, 0x63, 0xF8, 0xE2, 0x4E, 0x5C, 0xF6, 0x3D, 0xFE, 0x7B, 0xB8, + 0x9D, 0x0C, 0x5E, 0x50, 0x89, 0x2B, 0xE2, 0x9E, 0x1E, 0x51, 0x8C, 0xAB, + 0xB0, 0x54, 0x71, 0x83, 0x01, 0x8B, 0x75, 0xD8, 0xA4, 0x69, 0x8B, 0xC0, + 0x2C, 0xE2, 0x1B, 0xCB, 0x16, 0x7B, 0xB0, 0xD3, 0x83, 0x16, 0x81, 0xF1, + 0x48, 0x3F, 0xB1, 0x65, 0x61, 0xC7, 0x7D, 0x81, 0x1D, 0x0F, 0x8D, 0xDB, + 0xFE, 0xC2, 0xA6, 0x45, 0x1E, 0x1F, 0xF0, 0xA8, 0x42, 0xB3, 0x0A, 0xB7, + 0xB0, 0x60, 0xE1, 0x72, 0xEE, 0x50, 0xB2, 0x08, 0xCC, 0x01, 0x2E, 0x11, + 0x37, 0xD1, 0xD4, 0xE1, 0x90, 0x38, 0x4C, 0xFD, 0x92, 0xC3, 0x2B, 0xEC, + 0x5D, 0x27, 0xEE, 0xC9, 0x09, 0x73, 0x48, 0xE6, 0xBC, 0x91, 0x90, 0x2C, + 0xAE, 0x70, 0x84, 0x3F, 0x4D, 0x77, 0x88, 0x4E, 0xE0, 0x90, 0xD4, 0xE0, + 0xFE, 0x9C, 0x36, 0x7F, 0xAC, 0x15, 0xB8, 0x6C, 0xBF, 0xEC, 0xC3, 0xFE, + 0x33, 0x3D, 0xE3, 0x03, 0xCE, 0xFC, 0x35, 0x6C, 0x92, 0x47, 0x65, 0xB7, + 0xBD, 0x6E, 0xC0, 0x65, 0x07, 0x1D, 0xB7, 0xCD, 0x59, 0xC2, 0x36, 0x0A, + 0x98, 0x6F, 0xDD, 0xEB, 0x4A, 0x14, 0xFD, 0x00, 0x3A, 0xD0, 0x36, 0x27, + 0xE1, 0xFD, 0x9A, 0xC9, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, + 0xAE, 0x42, 0x60, 0x82 +}; diff --git a/src/resource/linux/DEBUGGER_BP_RED.hpng b/src/resource/linux/DEBUGGER_BP_RED.hpng new file mode 100644 index 00000000..f15497c8 --- /dev/null +++ b/src/resource/linux/DEBUGGER_BP_RED.hpng @@ -0,0 +1,42 @@ +unsigned char DEBUGGER_BP_RED_png[470] = { + 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, + 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, + 0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0xF3, 0xFF, 0x61, 0x00, 0x00, 0x00, + 0x04, 0x67, 0x41, 0x4D, 0x41, 0x00, 0x00, 0xB1, 0x8F, 0x0B, 0xFC, 0x61, + 0x05, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0D, + 0xD5, 0x00, 0x00, 0x0D, 0xD5, 0x01, 0x3D, 0xD6, 0x58, 0xF1, 0x00, 0x00, + 0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6F, 0x66, 0x74, 0x77, 0x61, + 0x72, 0x65, 0x00, 0x70, 0x61, 0x69, 0x6E, 0x74, 0x2E, 0x6E, 0x65, 0x74, + 0x20, 0x34, 0x2E, 0x30, 0x2E, 0x32, 0x31, 0xF1, 0x20, 0x69, 0x95, 0x00, + 0x00, 0x01, 0x53, 0x49, 0x44, 0x41, 0x54, 0x38, 0x4F, 0xA5, 0x93, 0xBD, + 0x2F, 0x04, 0x51, 0x14, 0xC5, 0x8F, 0x8F, 0x58, 0x12, 0x3A, 0x0A, 0x85, + 0x44, 0x25, 0x21, 0xA1, 0x23, 0x11, 0x89, 0x48, 0x24, 0x12, 0xB5, 0x15, + 0xAD, 0xCA, 0x6E, 0x4B, 0x4D, 0xBD, 0x7F, 0x8B, 0x52, 0xA5, 0x52, 0x90, + 0xC8, 0xD6, 0x24, 0x54, 0x14, 0xA2, 0xA0, 0x43, 0x7C, 0x16, 0xCC, 0xFA, + 0x9D, 0xF9, 0x48, 0xC6, 0x78, 0x63, 0x0A, 0x37, 0xF9, 0x65, 0x5F, 0xE6, + 0xDD, 0xF3, 0xDE, 0xBB, 0xF7, 0xDC, 0x55, 0x49, 0xF4, 0xC3, 0x12, 0x34, + 0x60, 0x0B, 0x16, 0xA0, 0x06, 0x95, 0x31, 0x04, 0xAD, 0x5E, 0xE9, 0x6E, + 0x42, 0x8A, 0x56, 0xA4, 0x8E, 0x19, 0x67, 0xDD, 0x23, 0xDD, 0xB0, 0xB7, + 0x07, 0x03, 0x4E, 0x0C, 0xC5, 0x58, 0x97, 0x74, 0xBE, 0x4C, 0xF2, 0xA9, + 0xF4, 0xF5, 0x0E, 0x1D, 0xC4, 0xE6, 0x8D, 0xF5, 0x11, 0xF0, 0x8C, 0x88, + 0xBC, 0x36, 0x0C, 0xC7, 0x8A, 0x5C, 0x0C, 0x22, 0x3E, 0xDB, 0x94, 0x3E, + 0x3F, 0x48, 0xCA, 0x84, 0x45, 0x5E, 0xD9, 0x5B, 0x23, 0x87, 0xFC, 0x63, + 0xE8, 0x8B, 0x95, 0x69, 0xB4, 0x7C, 0xF3, 0x5F, 0xE2, 0x8C, 0x17, 0x72, + 0x66, 0x01, 0xCD, 0x4E, 0x22, 0xA5, 0x26, 0x6A, 0xBE, 0xF7, 0xB3, 0x43, + 0x82, 0x10, 0x07, 0xE4, 0xF2, 0xE2, 0x6B, 0xB4, 0xDD, 0x3E, 0x60, 0xD1, + 0x0D, 0xCB, 0xD7, 0x5C, 0xC5, 0x33, 0x65, 0x8C, 0xF2, 0x8B, 0x76, 0xC6, + 0x07, 0x34, 0xDD, 0xE9, 0x62, 0x52, 0x15, 0x73, 0x80, 0xB6, 0xEE, 0x03, + 0x1A, 0xFF, 0x38, 0x80, 0x9E, 0x4A, 0xF3, 0xF6, 0xD9, 0x56, 0x15, 0x93, + 0xCA, 0x78, 0xA4, 0x04, 0x7C, 0x64, 0xA9, 0x29, 0x1F, 0x50, 0x63, 0x48, + 0x6E, 0xED, 0x73, 0x31, 0xB1, 0x8C, 0xFD, 0xA4, 0x89, 0x97, 0x68, 0xE3, + 0x26, 0x3A, 0x76, 0x3D, 0x24, 0xF6, 0x39, 0x24, 0xC8, 0xC3, 0xED, 0x11, + 0x9D, 0xB3, 0x8D, 0xCD, 0x44, 0x9A, 0x84, 0xC7, 0xB3, 0xED, 0x21, 0xB1, + 0xCF, 0x21, 0xA1, 0x79, 0x60, 0x6F, 0x35, 0x19, 0xA4, 0x43, 0xC0, 0xFD, + 0x9F, 0x31, 0x02, 0x27, 0x1E, 0x12, 0xFB, 0x6C, 0xAB, 0x32, 0xE1, 0x13, + 0x6B, 0x3F, 0x3B, 0xBD, 0xD9, 0x62, 0xFF, 0x67, 0x82, 0xE1, 0xF1, 0xDC, + 0xA6, 0xBE, 0x2B, 0xFB, 0xEC, 0x4E, 0x1B, 0x37, 0x8C, 0x6F, 0x17, 0xEC, + 0xF9, 0xD9, 0xBF, 0x6E, 0x0E, 0x85, 0x9B, 0x33, 0x0D, 0x1B, 0xB0, 0x0E, + 0x93, 0xE9, 0xB7, 0x42, 0x48, 0xDF, 0x8F, 0x4C, 0x23, 0xE8, 0x8D, 0x9D, + 0x99, 0xB1, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, + 0x60, 0x82 +}; diff --git a/src/resource/linux/DEBUGGER_GOTO.hpng b/src/resource/linux/DEBUGGER_GOTO.hpng new file mode 100644 index 00000000..4dd884cb --- /dev/null +++ b/src/resource/linux/DEBUGGER_GOTO.hpng @@ -0,0 +1,27 @@ +unsigned char DEBUGGER_GOTO_png[292] = { + 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, + 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, + 0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0xF3, 0xFF, 0x61, 0x00, 0x00, 0x00, + 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE, 0xCE, 0x1C, 0xE9, 0x00, 0x00, + 0x00, 0x04, 0x67, 0x41, 0x4D, 0x41, 0x00, 0x00, 0xB1, 0x8F, 0x0B, 0xFC, + 0x61, 0x05, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, + 0x15, 0x9F, 0x00, 0x00, 0x15, 0x9F, 0x01, 0xDF, 0x87, 0xF6, 0x15, 0x00, + 0x00, 0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6F, 0x66, 0x74, 0x77, + 0x61, 0x72, 0x65, 0x00, 0x70, 0x61, 0x69, 0x6E, 0x74, 0x2E, 0x6E, 0x65, + 0x74, 0x20, 0x34, 0x2E, 0x30, 0x2E, 0x32, 0x31, 0xF1, 0x20, 0x69, 0x95, + 0x00, 0x00, 0x00, 0x94, 0x49, 0x44, 0x41, 0x54, 0x38, 0x4F, 0xCD, 0xD0, + 0xB1, 0x0A, 0x82, 0x50, 0x18, 0x86, 0xE1, 0x63, 0x8E, 0x4E, 0x41, 0xE0, + 0x20, 0xDA, 0x8D, 0xB4, 0x05, 0x8E, 0xDE, 0x83, 0x4B, 0xD7, 0xA0, 0x82, + 0xE0, 0xE2, 0x50, 0x50, 0xF7, 0xD3, 0x20, 0x82, 0x93, 0x88, 0x5B, 0xD4, + 0xF5, 0xF8, 0xFE, 0x83, 0x20, 0x28, 0x9D, 0xE3, 0x20, 0xF8, 0xC2, 0xB3, + 0x79, 0x3E, 0xF0, 0x57, 0xBB, 0x2A, 0xC2, 0xCB, 0x50, 0x88, 0x59, 0x4F, + 0xBC, 0x91, 0x69, 0x54, 0xC8, 0x31, 0x4B, 0x06, 0xE4, 0x03, 0x5D, 0x05, + 0xB6, 0x1D, 0x38, 0x22, 0x86, 0x85, 0x31, 0x1B, 0x37, 0x38, 0xD0, 0x0E, + 0xF8, 0xF8, 0x21, 0x81, 0x74, 0xC0, 0x03, 0x3D, 0x4E, 0x30, 0xFA, 0x85, + 0x33, 0xBE, 0x48, 0x71, 0x47, 0x07, 0x17, 0x92, 0xF1, 0x0D, 0x02, 0x7C, + 0xD0, 0x62, 0x7C, 0x2C, 0xAD, 0x3A, 0xA2, 0x87, 0xE9, 0x63, 0x69, 0xD5, + 0xC0, 0x52, 0x7F, 0x07, 0x6A, 0x94, 0x1A, 0x0D, 0x16, 0x07, 0xAE, 0x90, + 0xA3, 0x99, 0xB8, 0x60, 0x17, 0x29, 0x35, 0x00, 0xD4, 0x45, 0x31, 0x14, + 0xD0, 0x14, 0xF5, 0x33, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, + 0xAE, 0x42, 0x60, 0x82 +}; diff --git a/src/resource/linux/DEBUGGER_PAUSE.hpng b/src/resource/linux/DEBUGGER_PAUSE.hpng new file mode 100644 index 00000000..f96e8f00 --- /dev/null +++ b/src/resource/linux/DEBUGGER_PAUSE.hpng @@ -0,0 +1,20 @@ +unsigned char DEBUGGER_PAUSE_png[211] = { + 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, + 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, + 0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0xF3, 0xFF, 0x61, 0x00, 0x00, 0x00, + 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE, 0xCE, 0x1C, 0xE9, 0x00, 0x00, + 0x00, 0x04, 0x67, 0x41, 0x4D, 0x41, 0x00, 0x00, 0xB1, 0x8F, 0x0B, 0xFC, + 0x61, 0x05, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, + 0x0D, 0xD6, 0x00, 0x00, 0x0D, 0xD6, 0x01, 0x90, 0x6F, 0x79, 0x9C, 0x00, + 0x00, 0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6F, 0x66, 0x74, 0x77, + 0x61, 0x72, 0x65, 0x00, 0x70, 0x61, 0x69, 0x6E, 0x74, 0x2E, 0x6E, 0x65, + 0x74, 0x20, 0x34, 0x2E, 0x30, 0x2E, 0x32, 0x31, 0xF1, 0x20, 0x69, 0x95, + 0x00, 0x00, 0x00, 0x43, 0x49, 0x44, 0x41, 0x54, 0x38, 0x4F, 0x63, 0x18, + 0x74, 0x40, 0x04, 0x88, 0x6B, 0x91, 0x30, 0x23, 0x10, 0xB3, 0xA2, 0x89, + 0x71, 0x01, 0x31, 0x4E, 0xA0, 0x09, 0xC4, 0xFF, 0x91, 0x30, 0x13, 0x10, + 0x83, 0x34, 0x20, 0x8B, 0x89, 0x02, 0x31, 0x4E, 0x30, 0x6A, 0xC0, 0xA8, + 0x01, 0x20, 0x40, 0xB1, 0x01, 0x42, 0x40, 0x5C, 0x80, 0x84, 0x61, 0x49, + 0x19, 0x59, 0x8C, 0x13, 0x88, 0x07, 0x0D, 0x60, 0x60, 0x00, 0x00, 0x3F, + 0x28, 0x44, 0x7C, 0x76, 0x8B, 0xE1, 0x03, 0x00, 0x00, 0x00, 0x00, 0x49, + 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82 +}; diff --git a/src/resource/linux/DEBUGGER_PLAY.hpng b/src/resource/linux/DEBUGGER_PLAY.hpng new file mode 100644 index 00000000..69007f7f --- /dev/null +++ b/src/resource/linux/DEBUGGER_PLAY.hpng @@ -0,0 +1,28 @@ +unsigned char DEBUGGER_PLAY_png[306] = { + 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, + 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, + 0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0xF3, 0xFF, 0x61, 0x00, 0x00, 0x00, + 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE, 0xCE, 0x1C, 0xE9, 0x00, 0x00, + 0x00, 0x04, 0x67, 0x41, 0x4D, 0x41, 0x00, 0x00, 0xB1, 0x8F, 0x0B, 0xFC, + 0x61, 0x05, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, + 0x0D, 0xD6, 0x00, 0x00, 0x0D, 0xD6, 0x01, 0x90, 0x6F, 0x79, 0x9C, 0x00, + 0x00, 0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6F, 0x66, 0x74, 0x77, + 0x61, 0x72, 0x65, 0x00, 0x70, 0x61, 0x69, 0x6E, 0x74, 0x2E, 0x6E, 0x65, + 0x74, 0x20, 0x34, 0x2E, 0x30, 0x2E, 0x32, 0x31, 0xF1, 0x20, 0x69, 0x95, + 0x00, 0x00, 0x00, 0xA2, 0x49, 0x44, 0x41, 0x54, 0x38, 0x4F, 0xBD, 0xCF, + 0xB1, 0x0A, 0x82, 0x40, 0x1C, 0xC7, 0xF1, 0x0B, 0x85, 0x68, 0x15, 0x72, + 0x6C, 0x70, 0xEC, 0x19, 0xA2, 0x07, 0x68, 0xF2, 0x09, 0xDA, 0x7A, 0x05, + 0xC7, 0x1E, 0xA0, 0x37, 0x70, 0xF5, 0x35, 0xDA, 0xDB, 0x5A, 0x1A, 0x82, + 0x68, 0x68, 0x72, 0x72, 0x6A, 0x69, 0x8A, 0xF2, 0xAB, 0xF9, 0x87, 0x88, + 0xEB, 0x4E, 0x0F, 0xF1, 0x0B, 0x1F, 0xBC, 0x5B, 0x7E, 0x78, 0xAA, 0xEF, + 0x22, 0x8C, 0x3E, 0x47, 0xB7, 0x2E, 0x48, 0x31, 0xAD, 0x6F, 0x0E, 0xDD, + 0xF0, 0x46, 0x81, 0x0D, 0xC6, 0xE8, 0x94, 0x0C, 0x88, 0x23, 0x96, 0x68, + 0xFD, 0xAC, 0xDF, 0x81, 0xCA, 0x13, 0x19, 0x66, 0xB0, 0xA6, 0x1B, 0x10, + 0x77, 0x24, 0x30, 0x3E, 0xCB, 0x34, 0x20, 0xCE, 0x58, 0x41, 0x9B, 0x6D, + 0xE0, 0x85, 0x03, 0x16, 0xD0, 0x66, 0x1A, 0xC8, 0xB1, 0x86, 0x8F, 0xBF, + 0xE9, 0x06, 0x1E, 0xD8, 0x21, 0x80, 0xB5, 0xEF, 0x81, 0xEA, 0x77, 0xF7, + 0x98, 0xA3, 0x75, 0x32, 0x70, 0x45, 0x0C, 0x0F, 0x9D, 0x3A, 0x61, 0x8B, + 0x49, 0x7D, 0x73, 0x28, 0x6C, 0xBE, 0x43, 0xA5, 0x54, 0x09, 0x70, 0x17, + 0x44, 0x3D, 0x8C, 0x39, 0x0C, 0x30, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, + 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82 +}; diff --git a/src/resource/linux/DEBUGGER_STEP_INTO.hpng b/src/resource/linux/DEBUGGER_STEP_INTO.hpng new file mode 100644 index 00000000..7300f28f --- /dev/null +++ b/src/resource/linux/DEBUGGER_STEP_INTO.hpng @@ -0,0 +1,27 @@ +unsigned char DEBUGGER_STEP_INTO_png[297] = { + 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, + 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, + 0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0xF3, 0xFF, 0x61, 0x00, 0x00, 0x00, + 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE, 0xCE, 0x1C, 0xE9, 0x00, 0x00, + 0x00, 0x04, 0x67, 0x41, 0x4D, 0x41, 0x00, 0x00, 0xB1, 0x8F, 0x0B, 0xFC, + 0x61, 0x05, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, + 0x15, 0x9F, 0x00, 0x00, 0x15, 0x9F, 0x01, 0xDF, 0x87, 0xF6, 0x15, 0x00, + 0x00, 0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6F, 0x66, 0x74, 0x77, + 0x61, 0x72, 0x65, 0x00, 0x70, 0x61, 0x69, 0x6E, 0x74, 0x2E, 0x6E, 0x65, + 0x74, 0x20, 0x34, 0x2E, 0x30, 0x2E, 0x32, 0x31, 0xF1, 0x20, 0x69, 0x95, + 0x00, 0x00, 0x00, 0x99, 0x49, 0x44, 0x41, 0x54, 0x38, 0x4F, 0xD5, 0xD2, + 0x3B, 0x0A, 0xC2, 0x40, 0x14, 0x46, 0xE1, 0x91, 0xF8, 0xC0, 0x57, 0x2A, + 0x0D, 0xB8, 0x8B, 0xAC, 0x4C, 0x6C, 0xAC, 0xAC, 0xEC, 0x44, 0xB0, 0x8A, + 0xBD, 0x75, 0x5A, 0x37, 0x92, 0x2A, 0xB5, 0xBD, 0xB5, 0x60, 0x9B, 0xF3, + 0x83, 0x03, 0x97, 0x44, 0x70, 0x62, 0xA3, 0x39, 0xF0, 0x35, 0x33, 0x64, + 0x32, 0x24, 0xD7, 0xFD, 0x55, 0x53, 0x2C, 0xDF, 0x18, 0x23, 0xA8, 0x1D, + 0x6E, 0x35, 0x77, 0x6C, 0x10, 0xD4, 0x00, 0x93, 0x9A, 0x13, 0xB6, 0xF8, + 0xBA, 0x03, 0x3A, 0x7A, 0x40, 0x8A, 0x33, 0xF4, 0x0D, 0xF6, 0xB8, 0x60, + 0x85, 0xE0, 0x62, 0x5C, 0xF1, 0xC0, 0x13, 0x19, 0x86, 0x68, 0x95, 0xFE, + 0x7F, 0x8E, 0x23, 0xFA, 0x5A, 0xF8, 0xD4, 0x0C, 0xC9, 0xCB, 0x42, 0x0B, + 0xA4, 0xB7, 0x46, 0xE8, 0x41, 0x43, 0xE5, 0xF7, 0x35, 0x74, 0x8D, 0xEC, + 0x20, 0x15, 0xD0, 0x83, 0xBE, 0x39, 0x4A, 0xF8, 0xFD, 0x35, 0x1A, 0xE9, + 0x9A, 0x23, 0xC3, 0xA6, 0x1B, 0xD8, 0x3D, 0x7B, 0xF8, 0x4F, 0x73, 0xAE, + 0x02, 0x87, 0x0E, 0x17, 0xF0, 0xCD, 0xD1, 0x7E, 0x32, 0x00, 0x00, 0x00, + 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82 +}; diff --git a/src/resource/linux/DEBUGGER_STEP_OUT.hpng b/src/resource/linux/DEBUGGER_STEP_OUT.hpng new file mode 100644 index 00000000..708f30e0 --- /dev/null +++ b/src/resource/linux/DEBUGGER_STEP_OUT.hpng @@ -0,0 +1,28 @@ +unsigned char DEBUGGER_STEP_OUT_png[308] = { + 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, + 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, + 0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0xF3, 0xFF, 0x61, 0x00, 0x00, 0x00, + 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE, 0xCE, 0x1C, 0xE9, 0x00, 0x00, + 0x00, 0x04, 0x67, 0x41, 0x4D, 0x41, 0x00, 0x00, 0xB1, 0x8F, 0x0B, 0xFC, + 0x61, 0x05, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, + 0x15, 0x9F, 0x00, 0x00, 0x15, 0x9F, 0x01, 0xDF, 0x87, 0xF6, 0x15, 0x00, + 0x00, 0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6F, 0x66, 0x74, 0x77, + 0x61, 0x72, 0x65, 0x00, 0x70, 0x61, 0x69, 0x6E, 0x74, 0x2E, 0x6E, 0x65, + 0x74, 0x20, 0x34, 0x2E, 0x30, 0x2E, 0x32, 0x31, 0xF1, 0x20, 0x69, 0x95, + 0x00, 0x00, 0x00, 0xA4, 0x49, 0x44, 0x41, 0x54, 0x38, 0x4F, 0x63, 0xA0, + 0x37, 0x90, 0x01, 0x62, 0x2E, 0x08, 0x93, 0x3C, 0x30, 0x01, 0x88, 0x37, + 0x00, 0x31, 0x07, 0x98, 0x47, 0x04, 0xF0, 0x00, 0xE2, 0x36, 0x24, 0xDC, + 0x0B, 0xC4, 0xDF, 0x80, 0x78, 0x39, 0x10, 0xB3, 0x00, 0x31, 0x41, 0xD0, + 0x01, 0xC4, 0xDB, 0x81, 0xB8, 0x1A, 0x8A, 0x9B, 0x81, 0xF8, 0x23, 0x10, + 0x83, 0x0C, 0x60, 0x06, 0x62, 0x82, 0x00, 0x64, 0x40, 0x29, 0x84, 0x09, + 0x06, 0x30, 0x2F, 0xB0, 0x81, 0x79, 0x44, 0x00, 0x74, 0x03, 0xA4, 0x81, + 0x98, 0x68, 0xFF, 0x83, 0x00, 0xBA, 0x01, 0x24, 0x83, 0x81, 0x37, 0xA0, + 0x16, 0x88, 0xB3, 0x21, 0x4C, 0xC2, 0x80, 0x07, 0x88, 0xC5, 0xA0, 0x58, + 0x04, 0x24, 0x00, 0x04, 0xA0, 0xD0, 0x66, 0x05, 0x62, 0x46, 0x20, 0x16, + 0x05, 0x62, 0x98, 0x3C, 0x37, 0x10, 0x63, 0x00, 0x50, 0x3C, 0xDF, 0x87, + 0xE2, 0xF3, 0x40, 0x8C, 0x1C, 0xCF, 0xBC, 0x40, 0x7C, 0x15, 0x88, 0x61, + 0xF2, 0x79, 0x40, 0x8C, 0x01, 0x40, 0x29, 0x8B, 0x1D, 0x09, 0x23, 0x03, + 0x90, 0x0B, 0x90, 0xE5, 0x88, 0x4A, 0x44, 0xF4, 0x00, 0x0C, 0x0C, 0x00, + 0xA1, 0x3A, 0x17, 0xFB, 0x69, 0xBB, 0x81, 0x1D, 0x00, 0x00, 0x00, 0x00, + 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82 +}; diff --git a/src/resource/linux/DEBUGGER_STEP_OVER.hpng b/src/resource/linux/DEBUGGER_STEP_OVER.hpng new file mode 100644 index 00000000..3c0e992a --- /dev/null +++ b/src/resource/linux/DEBUGGER_STEP_OVER.hpng @@ -0,0 +1,27 @@ +unsigned char DEBUGGER_STEP_OVER_png[296] = { + 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, + 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, + 0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0xF3, 0xFF, 0x61, 0x00, 0x00, 0x00, + 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE, 0xCE, 0x1C, 0xE9, 0x00, 0x00, + 0x00, 0x04, 0x67, 0x41, 0x4D, 0x41, 0x00, 0x00, 0xB1, 0x8F, 0x0B, 0xFC, + 0x61, 0x05, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, + 0x15, 0x9F, 0x00, 0x00, 0x15, 0x9F, 0x01, 0xDF, 0x87, 0xF6, 0x15, 0x00, + 0x00, 0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6F, 0x66, 0x74, 0x77, + 0x61, 0x72, 0x65, 0x00, 0x70, 0x61, 0x69, 0x6E, 0x74, 0x2E, 0x6E, 0x65, + 0x74, 0x20, 0x34, 0x2E, 0x30, 0x2E, 0x32, 0x31, 0xF1, 0x20, 0x69, 0x95, + 0x00, 0x00, 0x00, 0x98, 0x49, 0x44, 0x41, 0x54, 0x38, 0x4F, 0x63, 0x18, + 0x54, 0x80, 0x1B, 0x88, 0x45, 0x89, 0xC0, 0x42, 0x40, 0xCC, 0x08, 0xC4, + 0x18, 0xA0, 0x1D, 0x88, 0x5F, 0x02, 0xF1, 0x7D, 0x3C, 0xF8, 0x21, 0x10, + 0xDF, 0x04, 0x62, 0x36, 0x20, 0xC6, 0x00, 0xBD, 0x40, 0x5C, 0x0F, 0xC4, + 0x5C, 0x78, 0xB0, 0x12, 0x10, 0xDF, 0x01, 0x62, 0x9C, 0x06, 0x54, 0x41, + 0x98, 0x38, 0x81, 0x24, 0x10, 0x0F, 0x51, 0x03, 0x98, 0x80, 0xB8, 0x07, + 0x88, 0x43, 0x80, 0x18, 0x64, 0x40, 0x24, 0x10, 0x17, 0x02, 0x31, 0x0A, + 0x20, 0xE4, 0x02, 0x2F, 0x20, 0x7E, 0x03, 0xC4, 0x3F, 0x81, 0x18, 0x14, + 0x1B, 0x06, 0x40, 0x8C, 0x02, 0x2C, 0x80, 0xD8, 0x10, 0xC2, 0xC4, 0x09, + 0x1C, 0x81, 0xF8, 0x0A, 0x10, 0x6B, 0x83, 0x79, 0x78, 0x00, 0xC8, 0xC9, + 0x62, 0x48, 0x18, 0x14, 0x85, 0x30, 0xC0, 0x01, 0xA5, 0xF1, 0x02, 0x01, + 0x20, 0x06, 0xF9, 0x15, 0x96, 0x80, 0x92, 0x81, 0x98, 0x24, 0x00, 0x4A, + 0xAA, 0xEC, 0x48, 0x98, 0x19, 0x88, 0x07, 0x25, 0x60, 0x60, 0x00, 0x00, + 0xBC, 0xCF, 0x1F, 0x7C, 0xB2, 0x98, 0xCD, 0xAE, 0x00, 0x00, 0x00, 0x00, + 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82 +}; diff --git a/src/resource/linux/INPUT_CONNECTED.hpng b/src/resource/linux/INPUT_CONNECTED.hpng new file mode 100644 index 00000000..c6894c58 --- /dev/null +++ b/src/resource/linux/INPUT_CONNECTED.hpng @@ -0,0 +1,36 @@ +unsigned char INPUT_CONNECTED_png[400] = { + 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, + 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, + 0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0xF3, 0xFF, 0x61, 0x00, 0x00, 0x00, + 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE, 0xCE, 0x1C, 0xE9, 0x00, 0x00, + 0x00, 0x04, 0x67, 0x41, 0x4D, 0x41, 0x00, 0x00, 0xB1, 0x8F, 0x0B, 0xFC, + 0x61, 0x05, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, + 0x1A, 0x9D, 0x00, 0x00, 0x1A, 0x9D, 0x01, 0x5D, 0x6D, 0x02, 0x48, 0x00, + 0x00, 0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6F, 0x66, 0x74, 0x77, + 0x61, 0x72, 0x65, 0x00, 0x70, 0x61, 0x69, 0x6E, 0x74, 0x2E, 0x6E, 0x65, + 0x74, 0x20, 0x34, 0x2E, 0x30, 0x2E, 0x32, 0x31, 0xF1, 0x20, 0x69, 0x95, + 0x00, 0x00, 0x01, 0x00, 0x49, 0x44, 0x41, 0x54, 0x38, 0x4F, 0x9D, 0xD3, + 0xBB, 0x6A, 0x02, 0x41, 0x18, 0x86, 0xE1, 0x8D, 0xA7, 0x5B, 0xF0, 0x0E, + 0x24, 0x08, 0xDA, 0x59, 0x05, 0x3C, 0xB4, 0x96, 0x31, 0x45, 0x6E, 0x40, + 0xAC, 0x3D, 0x91, 0x2A, 0x88, 0x58, 0xD8, 0x0A, 0x16, 0xDE, 0x91, 0x58, + 0x0B, 0x29, 0x03, 0x16, 0x49, 0x13, 0x0F, 0x9D, 0x07, 0x3C, 0xBF, 0x9F, + 0x38, 0xB0, 0xB8, 0xB3, 0x2B, 0xF1, 0x83, 0xA7, 0x98, 0x65, 0xFF, 0x99, + 0xD9, 0xF9, 0x67, 0x9D, 0x07, 0xF3, 0x84, 0x2A, 0x26, 0x97, 0xD1, 0x3F, + 0x13, 0xC2, 0x07, 0x56, 0x28, 0xE9, 0x41, 0x50, 0xB4, 0xD2, 0x0B, 0xFA, + 0x18, 0xA0, 0x88, 0x26, 0x54, 0xFC, 0x06, 0x6B, 0xC2, 0xC8, 0x22, 0x8A, + 0x0E, 0x76, 0x38, 0x5D, 0x1D, 0xB1, 0xC5, 0x3B, 0xAC, 0x89, 0x20, 0x89, + 0x3F, 0x8C, 0xA1, 0x02, 0x53, 0x6C, 0x1C, 0x90, 0x81, 0x35, 0x5A, 0x79, + 0x8A, 0x3A, 0x66, 0xB8, 0x2D, 0x36, 0xDA, 0xB0, 0x26, 0x86, 0x2F, 0xCC, + 0x11, 0x34, 0xC9, 0x27, 0x3C, 0xD1, 0x81, 0x75, 0x61, 0xB6, 0xBD, 0x80, + 0x5A, 0xA5, 0xC9, 0xDC, 0xC5, 0x1B, 0x3C, 0xC3, 0x93, 0x1C, 0xF6, 0x70, + 0xBF, 0xAC, 0x1D, 0xA8, 0x65, 0xFA, 0x2C, 0x8D, 0x75, 0xA0, 0x15, 0x58, + 0xD3, 0x83, 0xBB, 0xD8, 0xD0, 0x24, 0x0D, 0x68, 0x47, 0x23, 0xA8, 0x3B, + 0xD6, 0xF8, 0x4D, 0x20, 0xFA, 0x8C, 0x1A, 0x7E, 0x91, 0x82, 0x35, 0x79, + 0xA8, 0x45, 0xB7, 0xC5, 0xDA, 0xF6, 0x10, 0x3F, 0x88, 0x43, 0x67, 0xE5, + 0x89, 0x1E, 0x6A, 0x9B, 0x7A, 0xD9, 0xDD, 0xFB, 0x35, 0xCA, 0xD0, 0xFD, + 0x48, 0xC3, 0xB7, 0x58, 0xD7, 0x73, 0x09, 0xDD, 0xB0, 0x02, 0xD4, 0x8D, + 0x16, 0x12, 0xB8, 0x1B, 0xAD, 0xAC, 0x95, 0x5E, 0x2F, 0xA3, 0x07, 0xF2, + 0x0D, 0xDF, 0x1F, 0x23, 0x38, 0x8E, 0x73, 0x06, 0xFB, 0x36, 0x5D, 0xD3, + 0x1C, 0xAF, 0x1A, 0x69, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, + 0xAE, 0x42, 0x60, 0x82 +}; diff --git a/src/resource/linux/INPUT_DISCONNECTED.hpng b/src/resource/linux/INPUT_DISCONNECTED.hpng new file mode 100644 index 00000000..eda6d1dc --- /dev/null +++ b/src/resource/linux/INPUT_DISCONNECTED.hpng @@ -0,0 +1,37 @@ +unsigned char INPUT_DISCONNECTED_png[410] = { + 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, + 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, + 0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0xF3, 0xFF, 0x61, 0x00, 0x00, 0x00, + 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE, 0xCE, 0x1C, 0xE9, 0x00, 0x00, + 0x00, 0x04, 0x67, 0x41, 0x4D, 0x41, 0x00, 0x00, 0xB1, 0x8F, 0x0B, 0xFC, + 0x61, 0x05, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, + 0x1C, 0xD5, 0x00, 0x00, 0x1C, 0xD5, 0x01, 0xE1, 0x9B, 0x06, 0xCC, 0x00, + 0x00, 0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6F, 0x66, 0x74, 0x77, + 0x61, 0x72, 0x65, 0x00, 0x70, 0x61, 0x69, 0x6E, 0x74, 0x2E, 0x6E, 0x65, + 0x74, 0x20, 0x34, 0x2E, 0x30, 0x2E, 0x32, 0x31, 0xF1, 0x20, 0x69, 0x95, + 0x00, 0x00, 0x01, 0x0A, 0x49, 0x44, 0x41, 0x54, 0x38, 0x4F, 0xB5, 0xD3, + 0x31, 0x4B, 0x42, 0x51, 0x18, 0xC6, 0xF1, 0x93, 0xA6, 0x10, 0xE2, 0xD0, + 0x52, 0x5F, 0x20, 0x08, 0x0B, 0x72, 0xCC, 0xA1, 0x31, 0xDA, 0x5B, 0x1B, + 0x05, 0x97, 0x08, 0x1D, 0xDB, 0xA3, 0x86, 0x86, 0xD4, 0xA5, 0x4D, 0x90, + 0xC6, 0x70, 0x70, 0x14, 0xFD, 0x00, 0x8E, 0xF5, 0x0D, 0x04, 0x45, 0x9A, + 0x42, 0x5C, 0x1D, 0x4C, 0xFF, 0x8F, 0xF6, 0x5E, 0xAF, 0x77, 0x38, 0x38, + 0xE8, 0x03, 0x3F, 0xB8, 0xF7, 0xC0, 0x7D, 0x38, 0xE7, 0xF5, 0xE8, 0x76, + 0x9D, 0x18, 0x8E, 0x50, 0xC7, 0x23, 0xE2, 0xFF, 0x6B, 0xF7, 0xD0, 0xBA, + 0x37, 0x37, 0xF8, 0xC4, 0x37, 0x66, 0xF8, 0x43, 0x15, 0x4F, 0x98, 0xE0, + 0x16, 0xDE, 0xE4, 0x30, 0x82, 0x3E, 0x36, 0x2A, 0x99, 0x22, 0x8F, 0x8D, + 0xF2, 0x80, 0x70, 0x81, 0x95, 0x54, 0xA0, 0xA3, 0x78, 0x73, 0x85, 0x31, + 0x5A, 0xF8, 0x45, 0xB4, 0xA4, 0x84, 0xB5, 0x5C, 0x23, 0xB3, 0x7C, 0x5C, + 0xE4, 0x1D, 0x0D, 0x24, 0xA1, 0xB2, 0xE8, 0x71, 0x5E, 0x10, 0x64, 0x1F, + 0x6D, 0x0C, 0x71, 0xAE, 0x05, 0xA2, 0x89, 0xEB, 0x63, 0xE5, 0x10, 0x5F, + 0xB0, 0x8F, 0xB5, 0xAB, 0x14, 0x82, 0x1C, 0xC3, 0xA6, 0x3D, 0xC0, 0x19, + 0x2C, 0x69, 0x74, 0xD1, 0xC3, 0x1D, 0x74, 0xAC, 0x1A, 0xF6, 0x10, 0xE4, + 0x03, 0xD6, 0x2E, 0x7D, 0xD8, 0x71, 0x12, 0x78, 0xC5, 0xC9, 0xE2, 0xCD, + 0xB9, 0x4B, 0x64, 0x97, 0x8F, 0xAB, 0xE8, 0x92, 0x68, 0x30, 0xE1, 0x12, + 0xED, 0x24, 0x3C, 0x13, 0x6F, 0x74, 0xDE, 0x32, 0xA2, 0x25, 0x3F, 0xD8, + 0xA8, 0x44, 0xBF, 0xA9, 0xA6, 0x1A, 0x2D, 0x10, 0x0D, 0xF6, 0x14, 0xDE, + 0xE8, 0x6E, 0xEB, 0x7A, 0x16, 0xF0, 0x06, 0x2B, 0xEA, 0xA0, 0x89, 0x0B, + 0x78, 0xA3, 0x3F, 0x86, 0xDD, 0x6D, 0xED, 0xA6, 0x88, 0x67, 0x1C, 0x60, + 0x6D, 0xDA, 0x5B, 0x8E, 0x73, 0x73, 0x3A, 0xF1, 0x4C, 0x89, 0x76, 0x54, + 0xC3, 0x9D, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, + 0x60, 0x82 +}; diff --git a/src/resource/linux/INPUT_LOW_BATTERY.hpng b/src/resource/linux/INPUT_LOW_BATTERY.hpng new file mode 100644 index 00000000..8d09e151 --- /dev/null +++ b/src/resource/linux/INPUT_LOW_BATTERY.hpng @@ -0,0 +1,31 @@ +unsigned char INPUT_LOW_BATTERY_png[340] = { + 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, + 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x1E, + 0x08, 0x06, 0x00, 0x00, 0x00, 0x3B, 0x30, 0xAE, 0xA2, 0x00, 0x00, 0x00, + 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE, 0xCE, 0x1C, 0xE9, 0x00, 0x00, + 0x00, 0x04, 0x67, 0x41, 0x4D, 0x41, 0x00, 0x00, 0xB1, 0x8F, 0x0B, 0xFC, + 0x61, 0x05, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, + 0x0E, 0xC3, 0x00, 0x00, 0x0E, 0xC3, 0x01, 0xC7, 0x6F, 0xA8, 0x64, 0x00, + 0x00, 0x00, 0xE9, 0x49, 0x44, 0x41, 0x54, 0x48, 0x4B, 0xED, 0xD4, 0xB1, + 0x0A, 0x01, 0x71, 0x1C, 0xC0, 0xF1, 0x63, 0xB0, 0x50, 0x32, 0x48, 0x19, + 0x24, 0x2F, 0x22, 0x92, 0x3C, 0x83, 0x32, 0x7A, 0x01, 0x93, 0xE2, 0x01, + 0x8C, 0x26, 0x1B, 0x8B, 0x78, 0x05, 0x83, 0xDD, 0x43, 0x28, 0x83, 0xC5, + 0xA2, 0xA4, 0x98, 0x24, 0x7C, 0x7F, 0xFF, 0xBB, 0xAB, 0xEB, 0xEE, 0xB2, + 0xDC, 0xFF, 0x6E, 0xB8, 0xFE, 0xDF, 0xFA, 0xD4, 0xFF, 0xEF, 0xAE, 0xFB, + 0xF5, 0xBF, 0x2B, 0x96, 0xC9, 0x94, 0xFA, 0x72, 0x28, 0x69, 0xF6, 0xB7, + 0x22, 0xB6, 0x78, 0xE1, 0xAB, 0xD9, 0x15, 0x43, 0x84, 0xB6, 0x84, 0xDC, + 0xF4, 0xC4, 0x49, 0xA3, 0x33, 0x3E, 0x8E, 0x26, 0x02, 0xDD, 0xF0, 0x46, + 0x55, 0xED, 0xF4, 0x36, 0x86, 0x1C, 0x6A, 0xAE, 0x76, 0xBE, 0xE4, 0xC2, + 0xDD, 0x5E, 0x5A, 0x59, 0x34, 0x1C, 0x35, 0xF9, 0x21, 0x62, 0x7D, 0xC8, + 0xF3, 0x37, 0x6A, 0xE7, 0xCB, 0x3B, 0x58, 0xBE, 0xB7, 0xEC, 0x85, 0xBC, + 0xAA, 0xA8, 0x79, 0x07, 0x17, 0x50, 0x47, 0x06, 0xAA, 0xA4, 0x06, 0xBB, + 0xEB, 0x09, 0x54, 0x49, 0x0F, 0x5E, 0x40, 0x65, 0x06, 0xC7, 0x35, 0xB8, + 0x87, 0x03, 0x5A, 0x50, 0x79, 0x07, 0xE7, 0xB1, 0x77, 0xC8, 0xCD, 0x51, + 0xF3, 0x0E, 0x0E, 0x24, 0xFF, 0x2C, 0x0F, 0x7B, 0xA9, 0x3D, 0x77, 0xF0, + 0x5A, 0xED, 0x7C, 0x1D, 0x21, 0x17, 0x47, 0xE8, 0x68, 0xD4, 0xC5, 0x0E, + 0xF2, 0xEC, 0x29, 0x02, 0x0D, 0x20, 0xA7, 0x96, 0x1B, 0xE2, 0x70, 0x41, + 0x19, 0xA1, 0xB5, 0xB1, 0x82, 0xFB, 0x7D, 0x75, 0x90, 0xD3, 0xCE, 0x50, + 0x81, 0xC9, 0x64, 0x4A, 0x55, 0x96, 0xF5, 0x03, 0x93, 0x37, 0xA5, 0x1A, + 0x4B, 0x52, 0x7C, 0x80, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, + 0xAE, 0x42, 0x60, 0x82 +}; diff --git a/src/resource/linux/M_WND_ICON128.xpm b/src/resource/linux/M_WND_ICON128.xpm new file mode 100644 index 00000000..f87637bb --- /dev/null +++ b/src/resource/linux/M_WND_ICON128.xpm @@ -0,0 +1,2206 @@ +/* XPM */ +const char* M_WND_ICON128_xpm[] = { +"128 128 2075 2", +" c None", +". c #3B494D", +"+ c #3C484C", +"@ c #343B3C", +"# c #2E3234", +"$ c #2D3232", +"% c #2E3232", +"& c #303435", +"* c #373F40", +"= c #3F4D51", +"- c #3C4D50", +"; c #334247", +"> c #2A3132", +", c #2E3336", +"' c #282C2D", +") c #262727", +"! c #28292A", +"~ c #2C2D2D", +"{ c #2E2D2D", +"] c #2E2E2E", +"^ c #2E2E2D", +"/ c #2D2D2D", +"( c #2B2D2D", +"_ c #272929", +": c #262728", +"< c #2C3031", +"[ c #2F3639", +"} c #2E383A", +"| c #3A4D52", +"1 c #2F3637", +"2 c #272A2B", +"3 c #292C2C", +"4 c #2B2C2C", +"5 c #2C2C2C", +"6 c #2F2F2E", +"7 c #313131", +"8 c #323232", +"9 c #333333", +"0 c #303030", +"a c #2B2D2C", +"b c #292F30", +"c c #2A3031", +"d c #344245", +"e c #2C3336", +"f c #272A2A", +"g c #292A2B", +"h c #2F2F2F", +"i c #282A2B", +"j c #272A2C", +"k c #242B2D", +"l c #2A3336", +"m c #282B2B", +"n c #313030", +"o c #333332", +"p c #333231", +"q c #333131", +"r c #34302E", +"s c #352D2B", +"t c #352D29", +"u c #352D2A", +"v c #342E2C", +"w c #343130", +"x c #333232", +"y c #2A2A2A", +"z c #25282A", +"A c #273234", +"B c #2A3536", +"C c #323333", +"D c #342F2E", +"E c #342F2C", +"F c #34302F", +"G c #313637", +"H c #2E3F46", +"I c #294B57", +"J c #265261", +"K c #255666", +"L c #255869", +"M c #245869", +"N c #255768", +"O c #265565", +"P c #27515F", +"Q c #2A4853", +"R c #2F3C41", +"S c #323334", +"T c #342F2D", +"U c #302F2F", +"V c #282929", +"W c #313D41", +"X c #25292A", +"Y c #2A2B2B", +"Z c #2C434C", +"` c #215F75", +" . c #177D9E", +".. c #108EB9", +"+. c #0A9ACF", +"@. c #06A5E0", +"#. c #05AAE5", +"$. c #04ACE7", +"%. c #04AEE8", +"&. c #04ACE6", +"*. c #07A2DE", +"=. c #0C95C8", +"-. c #1289B1", +";. c #1B7290", +">. c #265464", +",. c #303B40", +"'. c #34302D", +"). c #333130", +"!. c #2F2E2E", +"~. c #283134", +"{. c #2F3C3F", +"]. c #282C2E", +"^. c #31383A", +"/. c #255867", +"(. c #167EA2", +"_. c #0A9FD1", +":. c #03ADEA", +"<. c #01B1F1", +"[. c #00B2F1", +"}. c #00B0F2", +"|. c #00AEF0", +"1. c #00ADEF", +"2. c #00ADEE", +"3. c #00AEF1", +"4. c #00B1F2", +"5. c #02B0EF", +"6. c #05AAE3", +"7. c #0E94C2", +"8. c #1B718E", +"9. c #294D58", +"0. c #2B2B2B", +"a. c #222526", +"b. c #232626", +"c. c #31393B", +"d. c #225E72", +"e. c #128DB5", +"f. c #06A9E1", +"g. c #01B2F0", +"h. c #00B0F1", +"i. c #00AEEF", +"j. c #00ACEE", +"k. c #00AFF0", +"l. c #02B1ED", +"m. c #08A4D7", +"n. c #1682A3", +"o. c #28505C", +"p. c #272B2D", +"q. c #313F42", +"r. c #1A1A1A", +"s. c #272727", +"t. c #138BB2", +"u. c #05ABE3", +"v. c #01B1F0", +"w. c #00AFEF", +"x. c #01B1EE", +"y. c #08A5D9", +"z. c #197999", +"A. c #2B464E", +"B. c #27292A", +"C. c #282F30", +"D. c #1F1F21", +"E. c #282828", +"F. c #352E2C", +"G. c #2E3F43", +"H. c #197696", +"I. c #07A6DB", +"J. c #01B1EF", +"K. c #00ACED", +"L. c #00ADED", +"M. c #0D99C8", +"N. c #235E71", +"O. c #323434", +"P. c #2C3537", +"Q. c #212323", +"R. c #282727", +"S. c #2A4A55", +"T. c #128EB3", +"U. c #03AFEA", +"V. c #06A8DE", +"W. c #1A7592", +"X. c #2F3B3D", +"Y. c #313F43", +"Z. c #2A2F32", +"`. c #323131", +" + c #275361", +".+ c #0C9BC9", +"++ c #01B2ED", +"@+ c #00AEED", +"#+ c #04AFE7", +"$+ c #1682A4", +"%+ c #2E3F44", +"&+ c #352E2D", +"*+ c #262B2D", +"=+ c #09A0D4", +"-+ c #00B1F0", +";+ c #00AEEE", +">+ c #02B1EB", +",+ c #128CB3", +"'+ c #2C434A", +")+ c #222424", +"!+ c #252828", +"~+ c #265463", +"{+ c #0AA0D2", +"]+ c #118EB6", +"^+ c #2E4046", +"/+ c #352F2C", +"(+ c #2B3436", +"_+ c #2E393C", +":+ c #284E5B", +"<+ c #0B9DCD", +"[+ c #00B1EF", +"}+ c #1389AE", +"|+ c #303A3E", +"1+ c #262828", +"2+ c #2B3638", +"3+ c #272828", +"4+ c #2C434B", +"5+ c #0F93BD", +"6+ c #00ABED", +"7+ c #01AAEC", +"8+ c #02A9EC", +"9+ c #03A9EC", +"0+ c #03AAEC", +"a+ c #01ABED", +"b+ c #03B0EC", +"c+ c #187899", +"d+ c #262B2B", +"e+ c #333030", +"f+ c #313738", +"g+ c #1581A5", +"h+ c #01ABEC", +"i+ c #02AAEC", +"j+ c #09AAEC", +"k+ c #13ACEC", +"l+ c #1AAFED", +"m+ c #22B3EE", +"n+ c #32B7EF", +"o+ c #42BBEF", +"p+ c #4ABDF0", +"q+ c #4EBFF0", +"r+ c #4FC0F0", +"s+ c #4CBEF0", +"t+ c #47BDF0", +"u+ c #3DBAEF", +"v+ c #2DB6EF", +"w+ c #1EB1ED", +"x+ c #18ADED", +"y+ c #10AAEC", +"z+ c #05AAEC", +"A+ c #00ACEC", +"B+ c #215F73", +"C+ c #292A2A", +"D+ c #222727", +"E+ c #323231", +"F+ c #215F74", +"G+ c #05AAE1", +"H+ c #00ABEC", +"I+ c #05AAEB", +"J+ c #10ACEC", +"K+ c #2FB4ED", +"L+ c #4FC1F0", +"M+ c #70CCF2", +"N+ c #94D7F4", +"O+ c #AAE0F6", +"P+ c #BEE7F8", +"Q+ c #CEECF9", +"R+ c #D6F0F9", +"S+ c #DAF1FA", +"T+ c #DBF2FA", +"U+ c #DCF2FA", +"V+ c #D8F0FA", +"W+ c #D3EFF9", +"X+ c #C9EBF9", +"Y+ c #B7E4F7", +"Z+ c #A3DCF6", +"`+ c #89D3F4", +" @ c #63C7F1", +".@ c #44BCEF", +"+@ c #24B0EC", +"@@ c #0BABEC", +"#@ c #03A9EB", +"$@ c #01ABEB", +"%@ c #00B0EE", +"&@ c #0C99C7", +"*@ c #2C424A", +"=@ c #232525", +"-@ c #332F2D", +";@ c #2D4047", +">@ c #0D94C0", +",@ c #00AFEE", +"'@ c #01AAEB", +")@ c #06A9EB", +"!@ c #17ACEC", +"~@ c #35B7EE", +"{@ c #65C7F1", +"]@ c #97DAF5", +"^@ c #C2E8F8", +"/@ c #E3F3FA", +"(@ c #F1F8FB", +"_@ c #F6FAFC", +":@ c #F9FBFC", +"<@ c #FBFCFC", +"[@ c #FCFCFC", +"}@ c #FAFBFC", +"|@ c #F8FBFC", +"1@ c #F5FAFB", +"2@ c #EDF7FB", +"3@ c #DAF0F9", +"4@ c #B5E3F6", +"5@ c #89D3F3", +"6@ c #56C0EF", +"7@ c #29B1EC", +"8@ c #0DA9EB", +"9@ c #02A9EB", +"0@ c #02AFEB", +"a@ c #1A7391", +"b@ c #323030", +"c@ c #292929", +"d@ c #282829", +"e@ c #323130", +"f@ c #332F2E", +"g@ c #1B6E8B", +"h@ c #03AEE8", +"i@ c #08A9EB", +"j@ c #26B0ED", +"k@ c #5BC2F0", +"l@ c #99D8F4", +"m@ c #CCEBF8", +"n@ c #E7F4FA", +"o@ c #F7FAFC", +"p@ c #FBFBFC", +"q@ c #F4F9FB", +"r@ c #E0F2FA", +"s@ c #BBE4F6", +"t@ c #7ACCF1", +"u@ c #39B5EC", +"v@ c #0EA9EB", +"w@ c #01A9EB", +"x@ c #099FD2", +"y@ c #294A56", +"z@ c #332E2C", +"A@ c #282C2C", +"B@ c #2E3738", +"C@ c #0B9ACA", +"D@ c #00B0EF", +"E@ c #00AAEC", +"F@ c #05A9EB", +"G@ c #24B1ED", +"H@ c #67C6F1", +"I@ c #B1E1F6", +"J@ c #EFF7FB", +"K@ c #C9E9F7", +"L@ c #7FCDF0", +"M@ c #30B2EB", +"N@ c #09A9EA", +"O@ c #02B0EC", +"P@ c #17799A", +"Q@ c #29292A", +"R@ c #354346", +"S@ c #292C2D", +"T@ c #332D2B", +"U@ c #1E677F", +"V@ c #03ADE9", +"W@ c #00ABEB", +"X@ c #1CADEC", +"Y@ c #64C6F0", +"Z@ c #B9E4F6", +"`@ c #EBF6FA", +" # c #F9FBFB", +".# c #FBFBFB", +"+# c #F1F7FA", +"@# c #C5E6F5", +"## c #6DC5ED", +"$# c #1FABE9", +"%# c #03A8EB", +"&# c #099ED2", +"*# c #294752", +"=# c #332E2D", +"-# c #303A3C", +";# c #39454A", +"># c #332F2F", +",# c #2F383B", +"'# c #0E8EB9", +")# c #00ABEA", +"!# c #01A9EA", +"~# c #10A9EA", +"{# c #4BBBEE", +"]# c #A6DCF4", +"^# c #E7F4F9", +"/# c #F9FAFB", +"(# c #FAFBFB", +"_# c #E8F3F8", +":# c #A7DAF0", +"<# c #44B5E8", +"[# c #09A7E9", +"}# c #00AAEB", +"|# c #02ADE9", +"1# c #1B6C87", +"2# c #26292A", +"3# c #272C2D", +"4# c #342D2B", +"5# c #255363", +"6# c #05A9E0", +"7# c #03A8EA", +"8# c #22AEEB", +"9# c #7CCDF1", +"0# c #D4EDF8", +"a# c #F6F9FB", +"b# c #F5F8FA", +"c# c #CBE7F4", +"d# c #62C0E8", +"e# c #10A7E7", +"f# c #0E8EBC", +"g# c #2E3B3F", +"h# c #394446", +"i# c #2B3335", +"j# c #187393", +"k# c #01AFED", +"l# c #07A8EA", +"m# c #3CB7ED", +"n# c #A5DCF5", +"o# c #FAFAFB", +"p# c #DFEFF6", +"q# c #78C6E9", +"r# c #16A7E5", +"s# c #06A5DB", +"t# c #264F5E", +"u# c #262B2E", +"v# c #2A2B2C", +"w# c #303638", +"x# c #0F90BA", +"y# c #00AFED", +"z# c #00AAEA", +"A# c #09A8EA", +"B# c #48BAEE", +"C# c #BAE3F6", +"D# c #F3F8FA", +"E# c #E5F1F6", +"F# c #7DC6E7", +"G# c #15A6E5", +"H# c #02ADEA", +"I# c #1A6F8D", +"J# c #322F2E", +"K# c #39474C", +"L# c #2A444E", +"M# c #089FD3", +"N# c #4CBCEE", +"O# c #C0E6F6", +"P# c #F4F8FA", +"Q# c #FAFAFA", +"R# c #E2F0F5", +"S# c #70C1E4", +"T# c #10A5E4", +"U# c #1287AD", +"V# c #313435", +"W# c #33302F", +"X# c #2C2E2E", +"Y# c #384448", +"Z# c #342D2A", +"`# c #245668", +" $ c #04A8E4", +".$ c #05A8E9", +"+$ c #41B8EC", +"@$ c #C0E5F5", +"#$ c #F5F9FA", +"$$ c #F9F9F9", +"%$ c #F8F8F8", +"&$ c #F5F5F6", +"*$ c #ECEDED", +"=$ c #E4E6E8", +"-$ c #E1E4E6", +";$ c #DFE2E4", +">$ c #DEE2E4", +",$ c #E0E4E5", +"'$ c #E4E7E8", +")$ c #ECEDEE", +"!$ c #F6F6F6", +"~$ c #F8F9F9", +"{$ c #F9FAFA", +"]$ c #D7EAF2", +"^$ c #5AB6DF", +"/$ c #09A4E4", +"($ c #0E92BE", +"_$ c #2E3B40", +":$ c #33302E", +"<$ c #2E3131", +"[$ c #352C29", +"}$ c #205F77", +"|$ c #03ABE8", +"1$ c #02A8E9", +"2$ c #31B2EB", +"3$ c #B4E2F5", +"4$ c #F6F6F7", +"5$ c #EBECED", +"6$ c #D0D2D5", +"7$ c #ADB3B8", +"8$ c #909DA5", +"9$ c #707D88", +"0$ c #5D6F7C", +"a$ c #546C7B", +"b$ c #506A7B", +"c$ c #4F6A7C", +"d$ c #546E7F", +"e$ c #5D7382", +"f$ c #71838F", +"g$ c #94A4AD", +"h$ c #B5BDC2", +"i$ c #E0E1E2", +"j$ c #F2F3F3", +"k$ c #F7F8F9", +"l$ c #C3E0EC", +"m$ c #3EAADB", +"n$ c #03A6E6", +"o$ c #089DD4", +"p$ c #284854", +"q$ c #282625", +"r$ c #342C29", +"s$ c #1D6983", +"t$ c #02AEE9", +"u$ c #1BACEA", +"v$ c #9AD8F3", +"w$ c #F4F4F5", +"x$ c #D9DBDD", +"y$ c #A9AEB3", +"z$ c #6C7B85", +"A$ c #3D5668", +"B$ c #1E4760", +"C$ c #0E4F6E", +"D$ c #06587D", +"E$ c #04628B", +"F$ c #046A96", +"G$ c #036E9C", +"H$ c #03709F", +"I$ c #04709E", +"J$ c #056C99", +"K$ c #076690", +"L$ c #0F6085", +"M$ c #265C79", +"N$ c #537689", +"O$ c #91A0AB", +"P$ c #CFD4D7", +"Q$ c #F1F2F3", +"R$ c #F2F6F7", +"S$ c #A1D0E4", +"T$ c #1FA2DA", +"U$ c #00A9E9", +"V$ c #05A4DF", +"W$ c #245365", +"X$ c #282A2A", +"Y$ c #3C4A4D", +"Z$ c #272525", +"`$ c #332E2B", +" % c #197391", +".% c #01B0EC", +"+% c #0AA8E9", +"@% c #71C9F0", +"#% c #E8F4F9", +"$% c #DEE0E2", +"%% c #989EA5", +"&% c #485A6A", +"*% c #18475F", +"=% c #065478", +"-% c #00719E", +";% c #0088BC", +">% c #0098D0", +",% c #00A1DD", +"'% c #00A6E4", +")% c #00A9E7", +"!% c #00AAE9", +"~% c #00A9E8", +"{% c #00A7E5", +"]% c #00A0DD", +"^% c #0096CE", +"/% c #0281B3", +"(% c #106B94", +"_% c #3B6F8B", +":% c #8EA2AE", +"<% c #DDE1E3", +"[% c #E9F0F3", +"}% c #71B9D9", +"|% c #0BA0DE", +"1% c #05A6E1", +"2% c #23586C", +"3% c #342D29", +"4% c #363A3D", +"5% c #2F3537", +"6% c #16799A", +"7% c #04A8E8", +"8% c #45BAEC", +"9% c #D0EBF6", +"0% c #EDEEEF", +"a% c #B5BABE", +"b% c #536472", +"c% c #14455F", +"d% c #026088", +"e% c #0086B9", +"f% c #009DD8", +"g% c #00A4E1", +"h% c #018EC6", +"i% c #1373A0", +"j% c #57859E", +"k% c #B9C7CE", +"l% c #F0F2F2", +"m% c #C6DEE8", +"n% c #3AA2D1", +"o% c #02A4E4", +"p% c #04A8E1", +"q% c #225B6F", +"r% c #2F3234", +"s% c #157A9D", +"t% c #01A8E8", +"u% c #21AEE9", +"v% c #A8DDF3", +"w% c #F6F8F9", +"x% c #E0E2E3", +"y% c #8A939A", +"z% c #2B485B", +"A% c #055679", +"B% c #00A2DE", +"C% c #00A4E2", +"D% c #0688BF", +"E% c #367B9E", +"F% c #A4B9C4", +"G% c #EEF1F1", +"H% c #EEF2F4", +"I% c #8CC0D7", +"J% c #139BD5", +"K% c #00A9EA", +"L% c #225B70", +"M% c #313839", +"N% c #157A9E", +"O% c #0AA7E8", +"P% c #66C6EE", +"Q% c #E5F2F8", +"R% c #F7F7F7", +"S% c #D8DBDD", +"T% c #737E88", +"U% c #18455D", +"V% c #016A94", +"W% c #0099D2", +"X% c #0495D0", +"Y% c #2E83AC", +"Z% c #A4BECB", +"`% c #F2F4F4", +" & c #D0E1E8", +".& c #43A2CC", +"+& c #01A2E3", +"@& c #2E2F31", +"#& c #32302F", +"$& c #01AFEC", +"%& c #2CB0EA", +"&& c #B7E2F4", +"*& c #F7F8F8", +"=& c #D5D9DB", +"-& c #6A7882", +";& c #124862", +">& c #0079A8", +",& c #00A1DC", +"'& c #039AD7", +")& c #338BB4", +"!& c #BBCFD9", +"~& c #EFF2F4", +"{& c #86BBD1", +"]& c #0C97D3", +"^& c #342C28", +"/& c #313739", +"(& c #00A8E8", +"_& c #08A7E7", +":& c #6FC8EE", +"<& c #E5F2F6", +"[& c #E1E4E5", +"}& c #737F89", +"|& c #134964", +"1& c #007EAE", +"2& c #0498D5", +"3& c #539DBF", +"4& c #D8E4E9", +"5& c #F6F7F7", +"6& c #C5D9E2", +"7& c #2A97C5", +"8& c #01A5E5", +"9& c #04A7E0", +"0& c #225A70", +"a& c #2E2F2F", +"b& c #00A7E8", +"c& c #24AEE9", +"d& c #B4E0F3", +"e& c #F5F7F8", +"f& c #EDEFEF", +"g& c #8F9AA2", +"h& c #1B4963", +"i& c #017AA9", +"j& c #1395CD", +"k& c #8DBCD1", +"l& c #EEF2F3", +"m& c #E5ECEE", +"n& c #60A4C3", +"o& c #039BD8", +"p& c #2C2F2F", +"q& c #157A9C", +"r& c #01AEEB", +"s& c #04A6E7", +"t& c #57C0EC", +"u& c #E1F0F6", +"v& c #F5F5F5", +"w& c #B8BFC3", +"x& c #2B4E63", +"y& c #01709C", +"z& c #00A8E7", +"A& c #01A3E3", +"B& c #369BC9", +"C& c #C3DBE4", +"D& c #99BFCF", +"E& c #1191C8", +"F& c #215A6F", +"G& c #332B28", +"H& c #14A9E7", +"I& c #9BD6F0", +"J& c #F1F5F7", +"K& c #DFE2E3", +"L& c #5E7280", +"M& c #055C82", +"N& c #009BD5", +"O& c #0B9BD9", +"P& c #7ABAD6", +"Q& c #ECF1F3", +"R& c #C9D9DF", +"S& c #2F8FBC", +"T& c #00A4E3", +"U& c #04A7DF", +"V& c #15799C", +"W& c #02A5E6", +"X& c #37B3E9", +"Y& c #CCE8F3", +"Z& c #F3F3F4", +"`& c #A1ABB1", +" * c #1A516D", +".* c #008BC0", +"+* c #01A4E4", +"@* c #37A4D4", +"#* c #CBE1E9", +"$* c #E4E9EC", +"%* c #529CBB", +"&* c #0399D7", +"** c #04A6DF", +"=* c #313639", +"-* c #01ADEA", +";* c #07A5E6", +">* c #6CC5EB", +",* c #E8F1F5", +"'* c #DCDFE0", +")* c #4D6979", +"!* c #036A95", +"~* c #00A3E0", +"{* c #00A7E7", +"]* c #129FDB", +"^* c #95C8DE", +"/* c #EFF2F3", +"(* c #EEF0F1", +"_* c #7EABBF", +":* c #0B8EC7", +"<* c #15799B", +"[* c #1AA8E6", +"}* c #A0D8EF", +"|* c #F3F5F6", +"1* c #A8B4B9", +"2* c #1A5471", +"3* c #008FC5", +"4* c #04A2E2", +"5* c #56B2D9", +"6* c #DFEAEF", +"7* c #F4F4F4", +"8* c #F0F0F0", +"9* c #A4C0CC", +"0* c #158ABC", +"a* c #00A6E5", +"b* c #15789B", +"c* c #01ADE9", +"d* c #00A7E6", +"e* c #00A5E6", +"f* c #35B2E8", +"g* c #CAE6F2", +"h* c #F4F5F5", +"i* c #E8EAEA", +"j* c #6D8390", +"k* c #056791", +"l* c #00A1DE", +"m* c #00A5E5", +"n* c #2BA4D9", +"o* c #BCDBE8", +"p* c #F3F4F5", +"q* c #F1F1F1", +"r* c #CCCED1", +"s* c #B7BCC0", +"t* c #A7B1B6", +"u* c #8F9CA3", +"v* c #5F7E8C", +"w* c #0E7FAB", +"x* c #04A5DE", +"y* c #313638", +"z* c #01ACE9", +"A* c #04A3E5", +"B* c #56BEEA", +"C* c #DFEEF3", +"D* c #CFD5D7", +"E* c #37647B", +"F* c #0183B7", +"G* c #00A6E6", +"H* c #12A2DF", +"I* c #91CBE3", +"J* c #E1E5E8", +"K* c #D7D9DB", +"L* c #CACFD2", +"M* c #BDC5C8", +"N* c #A0A8AD", +"O* c #838E97", +"P* c #6E818C", +"Q* c #516F7D", +"R* c #36586A", +"S* c #274D63", +"T* c #1C4E68", +"U* c #0E516F", +"V* c #065E83", +"W* c #0189BB", +"X* c #00A5E3", +"Y* c #15789A", +"Z* c #0BA5E5", +"`* c #85CDEC", +" = c #ECF2F4", +".= c #F1F2F2", +"+= c #A9B3B9", +"@= c #175E80", +"#= c #0099D4", +"$= c #04A5E4", +"%= c #3CA3CE", +"&= c #628798", +"*= c #4B6070", +"== c #335669", +"-= c #24556B", +";= c #15516C", +">= c #0E5171", +",= c #095A7F", +"'= c #036790", +")= c #0074A1", +"!= c #007FB0", +"~= c #0088BD", +"{= c #0092CA", +"]= c #00A2E0", +"^= c #215A6E", +"/= c #312F2E", +"(= c #01ACE8", +"_= c #12A8E4", +":= c #A6DAEE", +"<= c #F2F3F4", +"[= c #E9EAEB", +"}= c #758D99", +"|= c #076994", +"1= c #0396D0", +"2= c #05719D", +"3= c #03658F", +"4= c #0172A0", +"5= c #007FB1", +"6= c #008CC1", +"7= c #0095CE", +"8= c #009BD6", +"9= c #04A4DD", +"0= c #21596E", +"a= c #00A4E4", +"b= c #2AADE5", +"c= c #BFE1EF", +"d= c #F3F3F3", +"e= c #DADCDE", +"f= c #4D7285", +"g= c #007EB0", +"h= c #00A5E2", +"i= c #00A2DF", +"j= c #157799", +"k= c #01ABE7", +"l= c #00A3E4", +"m= c #43B4E6", +"n= c #D5E9F0", +"o= c #F2F2F2", +"p= c #BFC7CB", +"q= c #326580", +"r= c #008DC5", +"s= c #04A4DC", +"t= c #21596D", +"u= c #56BDE6", +"v= c #E2EDF1", +"w= c #F0F1F1", +"x= c #A1B3BA", +"y= c #176486", +"z= c #00A6E3", +"A= c #312E2E", +"B= c #01AAE6", +"C= c #07A2E2", +"D= c #76C8E8", +"E= c #EBF0F1", +"F= c #EEEEEE", +"G= c #90A6AF", +"H= c #0D6D96", +"I= c #00A0DC", +"J= c #04A3DB", +"K= c #322A27", +"L= c #11A3E1", +"M= c #95D0EA", +"N= c #E5E6E7", +"O= c #76909E", +"P= c #0974A2", +"Q= c #312E2D", +"R= c #157699", +"S= c #18A5E1", +"T= c #A7D7EB", +"U= c #EFF1F1", +"V= c #DADFE0", +"W= c #5B7D91", +"X= c #057AAC", +"Y= c #21586D", +"Z= c #303637", +"`= c #01AAE5", +" - c #00A3E2", +".- c #1BA7E1", +"+- c #B2DAEB", +"@- c #EFF0F1", +"#- c #D4DADD", +"$- c #4B7990", +"%- c #0382B7", +"&- c #04A2DA", +"*- c #21586C", +"=- c #157698", +"-- c #00A3E1", +";- c #20A9E1", +">- c #BDDDEB", +",- c #F0F0F1", +"'- c #D0D8DB", +")- c #427A94", +"!- c #0289C0", +"~- c #01A9E4", +"{- c #29ACE0", +"]- c #CBE3EC", +"^- c #CCD6D9", +"/- c #397A96", +"(- c #04A2D9", +"_- c #303537", +":- c #302E2D", +"<- c #157597", +"[- c #01A1E0", +"}- c #35AFE1", +"|- c #D3E6EC", +"1- c #F0EFEF", +"2- c #C8D0D3", +"3- c #307896", +"4- c #0092CB", +"5- c #04A1D9", +"6- c #20576C", +"7- c #01A8E3", +"8- c #029FDF", +"9- c #3FB1E1", +"0- c #D6E7ED", +"a- c #EFEFEF", +"b- c #EFEEEE", +"c- c #BFC8CC", +"d- c #297495", +"e- c #0095CF", +"f- c #04A1D8", +"g- c #2C2C2E", +"h- c #029FDE", +"i- c #44B2E1", +"j- c #D7E7ED", +"k- c #B9C4C8", +"l- c #257496", +"m- c #0097D1", +"n- c #04A0D8", +"o- c #157596", +"p- c #01A8E2", +"q- c #029EDE", +"r- c #46B2E1", +"s- c #D8E7EC", +"t- c #EDEDED", +"u- c #B7C2C7", +"v- c #247497", +"w- c #0097D2", +"x- c #04A0D7", +"y- c #2A2A2B", +"z- c #2B2C2E", +"A- c #029EDD", +"B- c #46B2E0", +"C- c #B6C2C7", +"D- c #247598", +"E- c #20576B", +"F- c #2F3030", +"G- c #01A7E1", +"H- c #45B1DF", +"I- c #D7E6EB", +"J- c #ECECEC", +"K- c #B7C3C7", +"L- c #257699", +"M- c #00A3DF", +"N- c #312A27", +"O- c #2F3536", +"P- c #302D2D", +"Q- c #157495", +"R- c #01A6E1", +"S- c #029EDC", +"T- c #43B0DD", +"U- c #D5E4EA", +"V- c #ECECEB", +"W- c #BAC6CA", +"X- c #27789A", +"Y- c #0096D1", +"Z- c #049FD6", +"`- c #20566A", +" ; c #302D2C", +".; c #01A6E0", +"+; c #029DDC", +"@; c #3EAEDD", +"#; c #D4E3E9", +"$; c #C2CCCF", +"%; c #2D7C9D", +"&; c #0094CE", +"*; c #049FD5", +"=; c #2F2D2C", +"-; c #019DDC", +";; c #35ABDB", +">; c #D0E1E7", +",; c #EBEBEB", +"'; c #C7D2D5", +"); c #347FA0", +"!; c #0192CC", +"~; c #312A26", +"{; c #2F3436", +"]; c #157394", +"^; c #01A6DF", +"/; c #009EDB", +"(; c #28A7DA", +"_; c #C7DDE5", +":; c #CAD4D8", +"<; c #3D82A0", +"[; c #028EC7", +"}; c #049ED5", +"|; c #312926", +"1; c #2F3435", +"2; c #01A5DE", +"3; c #00A0DB", +"4; c #009FDB", +"5; c #1FA2D8", +"6; c #B8D6E3", +"7; c #E9EAEA", +"8; c #EAEAEA", +"9; c #CDD5D8", +"0; c #46829E", +"a; c #038AC3", +"b; c #049ED4", +"c; c #205569", +"d; c #1AA0D8", +"e; c #ACD1E1", +"f; c #E8E9EA", +"g; c #D1D7DA", +"h; c #51849D", +"i; c #0486BC", +"j; c #2F2C2C", +"k; c #147293", +"l; c #01A4DD", +"m; c #009FDA", +"n; c #179ED7", +"o; c #A1CDDF", +"p; c #E7E8E9", +"q; c #E9E9E9", +"r; c #D9DCDD", +"s; c #6790A4", +"t; c #0782B7", +"u; c #049DD3", +"v; c #009EDA", +"w; c #109BD7", +"x; c #8DC6DD", +"y; c #E5E8E8", +"z; c #E2E3E3", +"A; c #82A4B3", +"B; c #0B81B2", +"C; c #009ED9", +"D; c #147292", +"E; c #01A3DD", +"F; c #069BD7", +"G; c #6FBBD9", +"H; c #E1E5E7", +"I; c #E8E8E8", +"J; c #E5E6E6", +"K; c #93B1BC", +"L; c #107DAB", +"M; c #009CD7", +"N; c #019ED9", +"O; c #11A2DA", +"P; c #23A6DB", +"Q; c #1DA1DA", +"R; c #179ED9", +"S; c #0C9CD9", +"T; c #049CD9", +"U; c #039CD9", +"V; c #029CD9", +"W; c #019CD9", +"X; c #009DD9", +"Y; c #049DD2", +"Z; c #1F5568", +"`; c #302926", +" > c #019BD8", +".> c #51AFD5", +"+> c #D8E0E4", +"@> c #E6E7E7", +"#> c #A7BCC5", +"$> c #227BA4", +"%> c #0098D4", +"&> c #069BD9", +"*> c #53B6DE", +"=> c #ACD3E4", +"-> c #A2CFE3", +";> c #91C9E1", +">> c #77C1E0", +",> c #5FBADE", +"'> c #52B4DE", +")> c #43ADDC", +"!> c #2EA5DA", +"~> c #1EA2DA", +"{> c #14A0DA", +"]> c #0C9DD9", +"^> c #089BD8", +"/> c #049BD9", +"(> c #019DD9", +"_> c #049CD2", +":> c #2E3435", +"<> c #2E2C2B", +"[> c #147192", +"}> c #01A3DC", +"|> c #009CD8", +"1> c #40A7D4", +"2> c #CBDBE2", +"3> c #E7E7E7", +"4> c #C3CCD1", +"5> c #3C82A4", +"6> c #0093CE", +"7> c #0D9BD8", +"8> c #74BFDF", +"9> c #DDE4E7", +"0> c #E4E6E7", +"a> c #E2E6E7", +"b> c #DFE5E7", +"c> c #DBE3E6", +"d> c #D5E1E6", +"e> c #C7DCE5", +"f> c #B4D6E4", +"g> c #A5D1E3", +"h> c #9ACEE2", +"i> c #84C4E0", +"j> c #67B9DE", +"k> c #47B0DC", +"l> c #12A2DA", +"m> c #049CD1", +"n> c #2E3335", +"o> c #147191", +"p> c #01A2DB", +"q> c #009BD8", +"r> c #279FD2", +"s> c #B5D3DE", +"t> c #E6E6E6", +"u> c #D5D8DA", +"v> c #558EA8", +"w> c #028BC3", +"x> c #189ED8", +"y> c #96C9DF", +"z> c #E2E5E6", +"A> c #E6E6E7", +"B> c #E3E5E6", +"C> c #DBE3E5", +"D> c #BAD3DC", +"E> c #2D98C2", +"F> c #049BD1", +"G> c #1F5468", +"H> c #2D3335", +"I> c #119BD2", +"J> c #9BC8D9", +"K> c #E4E5E5", +"L> c #DFE1E2", +"M> c #84A7B8", +"N> c #0C83B6", +"O> c #009BD7", +"P> c #26A4D8", +"Q> c #B3D5E2", +"R> c #E5E5E5", +"S> c #B4BEC3", +"T> c #226D8F", +"U> c #0091C8", +"V> c #049BD0", +"W> c #302925", +"X> c #01A1DA", +"Y> c #0998D3", +"Z> c #77B7D3", +"`> c #DBE1E3", +" , c #E4E4E3", +"., c #AFC1C9", +"+, c #1E83AE", +"@, c #0099D5", +"#, c #48ADD9", +"$, c #C6DBE3", +"%, c #E1E2E2", +"&, c #8C9DA5", +"*, c #105F83", +"=, c #0094CD", +"-, c #049ACF", +";, c #1F5467", +">, c #292B2C", +",, c #147090", +"', c #01A1D9", +"), c #009CD6", +"!, c #0298D3", +"~, c #4AA8CF", +"{, c #CDDBDF", +"], c #E4E4E4", +"^, c #CCD4D7", +"/, c #488EAE", +"(, c #0291CA", +"_, c #009DD7", +":, c #089AD5", +"<, c #72BBDB", +"[, c #D9E0E3", +"}, c #DCDDDD", +"|, c #6A8591", +"1, c #046993", +"2, c #049ACE", +"3, c #2D3334", +"4, c #009AD5", +"5, c #2B9CCC", +"6, c #B3CED8", +"7, c #E3E3E3", +"8, c #DFE0E0", +"9, c #7FA9BC", +"0, c #0A88BD", +"a, c #1B9DD5", +"b, c #9BCBDE", +"c, c #E1E2E4", +"d, c #C9CDCE", +"e, c #476B7D", +"f, c #0277A6", +"g, c #0499CE", +"h, c #1F5367", +"i, c #302825", +"j, c #2D2E2E", +"k, c #146F8F", +"l, c #01A0D8", +"m, c #1295CD", +"n, c #88BCD2", +"o, c #B3C6CE", +"p, c #2988B3", +"q, c #0196D1", +"r, c #0299D4", +"s, c #40AAD7", +"t, c #C3D8E0", +"u, c #AEB8BB", +"v, c #235B75", +"w, c #0084B8", +"x, c #2F2825", +"y, c #2D3234", +"z, c #2D2C2C", +"A, c #2E2B2B", +"B, c #019FD7", +"C, c #009AD4", +"D, c #0595D1", +"E, c #56A5C9", +"F, c #CFD9DD", +"G, c #E2E2E2", +"H, c #D6DADC", +"I, c #639EB8", +"J, c #088DC5", +"K, c #0D99D4", +"L, c #7DBFDB", +"M, c #DADFE2", +"N, c #DEDEDE", +"O, c #88969D", +"P, c #0F5B7E", +"Q, c #0092C8", +"R, c #0498CD", +"S, c #1E5366", +"T, c #2D2B2B", +"U, c #136F8E", +"V, c #0198D3", +"W, c #2798C8", +"X, c #AFCAD5", +"Y, c #E1E1E1", +"Z, c #ABC3CC", +"`, c #288DBA", +" ' c #0197D2", +".' c #0197D3", +"+' c #2FA3D5", +"@' c #E1E1E2", +"#' c #D2D5D5", +"$' c #547180", +"%' c #056993", +"&' c #0098D1", +"*' c #1E5365", +"=' c #2C3233", +"-' c #2D2B2A", +";' c #136E8E", +">' c #019ED6", +",' c #0099D3", +"'' c #0C92CA", +")' c #7AB2CA", +"!' c #D8DCDD", +"~' c #E0E0E0", +"{' c #D4D9DB", +"]' c #6EA7C1", +"^' c #0A8EC6", +"/' c #0B97D2", +"(' c #6FB8D8", +"_' c #D6DDE0", +":' c #B5BBBD", +"<' c #2A566E", +"[' c #0497CC", +"}' c #1E5265", +"|' c #292B2B", +"1' c #019ED5", +"2' c #0295CF", +"3' c #3B9AC3", +"4' c #BECFD6", +"5' c #DFDFE0", +"6' c #DFDFDF", +"7' c #BCCDD4", +"8' c #4198BF", +"9' c #0493CC", +"0' c #0296D1", +"a' c #3BA5D3", +"b' c #B7D2DD", +"c' c #DADBDB", +"d' c #7C8A93", +"e' c #0B5678", +"f' c #0090C6", +"g' c #009AD3", +"h' c #0497CB", +"i' c #136D8D", +"j' c #019DD4", +"k' c #128FC4", +"l' c #84B4C7", +"m' c #D8DBDC", +"n' c #DCDEDE", +"o' c #A3C2CF", +"p' c #2D95C3", +"q' c #0394CF", +"r' c #0098D2", +"s' c #0096D0", +"t' c #1E9BD1", +"u' c #96C5DA", +"v' c #DADDDF", +"w' c #C0C4C6", +"x' c #3A5B6C", +"y' c #026D99", +"z' c #0496CA", +"A' c #1E5165", +"B' c #2F2824", +"C' c #2C3133", +"D' c #2D2A2A", +"E' c #136D8C", +"F' c #0097D0", +"G' c #0292CC", +"H' c #3D96BC", +"I' c #B9CAD1", +"J' c #DEDFDE", +"K' c #D7DBDC", +"L' c #90BBCE", +"M' c #2695C7", +"N' c #0294CF", +"O' c #1196D0", +"P' c #74B8D6", +"Q' c #D1DADD", +"R' c #D8D9D9", +"S' c #88949A", +"T' c #104D6A", +"U' c #0088BB", +"V' c #0099D1", +"W' c #1E5164", +"X' c #2F2724", +"Y' c #2C2C2B", +"Z' c #2C2A29", +"`' c #136C8C", +" ) c #019CD3", +".) c #0097CF", +"+) c #108BC0", +"@) c #78AAC0", +"#) c #D4D8D9", +"$) c #DDDDDE", +"%) c #D3D8DB", +"&) c #8DBACE", +"*) c #2998C9", +"=) c #0394CE", +"-) c #0195CF", +";) c #1196CF", +">) c #63B1D4", +",) c #C7D6DC", +"') c #DDDDDD", +")) c #BCC0C2", +"!) c #425C6C", +"~) c #02658D", +"{) c #0095CC", +"]) c #0495C9", +"^) c #2E2724", +"/) c #2B3132", +"() c #2C2B2B", +"_) c #136C8B", +":) c #019CD2", +"<) c #0292CB", +"[) c #2D8DB7", +"}) c #A9C0C9", +"|) c #DCDCDC", +"1) c #D4D9DA", +"2) c #9AC1D2", +"3) c #399DCB", +"4) c #0893CC", +"5) c #0096CF", +"6) c #0294CE", +"7) c #1A98CF", +"8) c #6CB3D4", +"9) c #C4D4DB", +"0) c #D2D3D4", +"a) c #78848C", +"b) c #114A67", +"c) c #0084B6", +"d) c #0495C8", +"e) c #1E5163", +"f) c #29292B", +"g) c #2B3133", +"h) c #136B8A", +"i) c #019BD1", +"j) c #088BC1", +"k) c #5798B4", +"l) c #C7CFD2", +"m) c #D6DADB", +"n) c #ABC7D5", +"o) c #55A7CD", +"p) c #1695CC", +"q) c #0294CD", +"r) c #0195CE", +"s) c #0993CE", +"t) c #2F9ED0", +"u) c #80BBD5", +"v) c #C8D5DB", +"w) c #DBDCDC", +"x) c #DADADB", +"y) c #A7ADB1", +"z) c #2E4D60", +"A) c #026690", +"B) c #0094CB", +"C) c #0494C7", +"D) c #1E5063", +"E) c #2C3234", +"F) c #2D2927", +"G) c #146884", +"H) c #019BD0", +"I) c #0094CC", +"J) c #1484B5", +"K) c #81A8BA", +"L) c #D4D6D7", +"M) c #D9DADB", +"N) c #C3D1D8", +"O) c #83B9D2", +"P) c #349ECD", +"Q) c #0D94CC", +"R) c #0293CD", +"S) c #0096CD", +"T) c #0493CD", +"U) c #1C98CE", +"V) c #56ABD2", +"W) c #A4C7D7", +"X) c #D2D8DB", +"Y) c #DBDBDB", +"Z) c #C7C9CA", +"`) c #586B76", +" ! c #084E6F", +".! c #0087BB", +"+! c #0493C7", +"@! c #1E4F61", +"#! c #2D2724", +"$! c #262626", +"%! c #2E3437", +"&! c #2D2725", +"*! c #176179", +"=! c #029ACD", +"-! c #0095CD", +";! c #018FC7", +">! c #2A83AB", +",! c #A3B8C1", +"'! c #D9DADA", +")! c #B2CCD6", +"!! c #77B7D2", +"~! c #3FA3CE", +"{! c #1998CD", +"]! c #0992CC", +"^! c #0392CC", +"/! c #0093CC", +"(! c #0093CD", +"_! c #0492CC", +":! c #0D93CC", +"~ c #354347", +",~ c #232424", +"'~ c #2C2826", +")~ c #22424E", +"!~ c #058DBF", +"~~ c #0A7DAE", +"{~ c #5788A0", +"]~ c #B9C1C5", +"^~ c #D6D7D7", +"/~ c #D8D8D9", +"(~ c #B0B3B5", +"_~ c #40525F", +":~ c #054C6B", +"<~ c #0082B3", +"[~ c #0198CC", +"}~ c #0E7A9C", +"|~ c #292F31", +"1~ c #2C2928", +"2~ c #2A2929", +"3~ c #252626", +"4~ c #37454A", +"5~ c #283236", +"6~ c #0B82A9", +"7~ c #0096CC", +"8~ c #0093CA", +"9~ c #0D7BA9", +"0~ c #518299", +"a~ c #B1BBBF", +"b~ c #D5D5D5", +"c~ c #D8D7D7", +"d~ c #D4D4D5", +"e~ c #ADB0B2", +"f~ c #445461", +"g~ c #074865", +"h~ c #007DAC", +"i~ c #0093C9", +"j~ c #0196CB", +"k~ c #136A87", +"l~ c #2B2A2A", +"m~ c #2B2A29", +"n~ c #2A2F30", +"o~ c #262A2C", +"p~ c #116E8E", +"q~ c #0092C9", +"r~ c #0090C7", +"s~ c #0B78A7", +"t~ c #4A7C95", +"u~ c #A6B2B7", +"v~ c #CFD0D1", +"w~ c #D0D1D1", +"x~ c #A2A6AA", +"y~ c #40515E", +"z~ c #074664", +"A~ c #007AA9", +"B~ c #0491C2", +"C~ c #1D4E5F", +"D~ c #2C2725", +"E~ c #2A3436", +"F~ c #282F32", +"G~ c #1B5165", +"H~ c #0394C5", +"I~ c #077BAA", +"J~ c #33718E", +"K~ c #8A9CA6", +"L~ c #C8C9CB", +"M~ c #C9C9CA", +"N~ c #8D9297", +"O~ c #304856", +"P~ c #054A68", +"Q~ c #007BAA", +"R~ c #0091C7", +"S~ c #0983AD", +"T~ c #25393F", +"U~ c #2D3336", +"V~ c #2C2726", +"W~ c #0884AF", +"X~ c #0094CA", +"Y~ c #037EB0", +"Z~ c #20698C", +"`~ c #6A8594", +" { c #B2B9BC", +".{ c #D1D1D1", +"+{ c #B6B8BA", +"@{ c #70767E", +"#{ c #223E4F", +"${ c #025172", +"%{ c #007FAF", +"&{ c #0195C9", +"*{ c #126988", +"={ c #2B2929", +"-{ c #222425", +";{ c #3C494F", +">{ c #3A4A51", +",{ c #222525", +"'{ c #2B2928", +"){ c #136885", +"!{ c #0185B9", +"~{ c #0E6A92", +"{{ c #426D83", +"]{ c #95A0A7", +"^{ c #C5C6C7", +"/{ c #D3D3D3", +"({ c #D4D4D4", +"_{ c #C7C8C9", +":{ c #989C9F", +"<{ c #475561", +"[{ c #113A51", +"}{ c #015D82", +"|{ c #0084B5", +"1{ c #048EBE", +"2{ c #1E4B5B", +"3{ c #2C2625", +"4{ c #282D2E", +"5{ c #2B2625", +"6{ c #204552", +"7{ c #058CBA", +"8{ c #0474A2", +"9{ c #1E6080", +"0{ c #5C7887", +"a{ c #9FA6AB", +"b{ c #C7C8C8", +"c{ c #D2D2D2", +"d{ c #CACBCB", +"e{ c #A6A9AB", +"f{ c #646C75", +"g{ c #044865", +"h{ c #006F9A", +"i{ c #0089BE", +"j{ c #0D789D", +"k{ c #2B2827", +"l{ c #242323", +"m{ c #313D42", +"n{ c #2E3B3E", +"o{ c #232323", +"p{ c #2A2928", +"q{ c #0F7090", +"r{ c #0193C6", +"s{ c #008FC6", +"t{ c #0084B7", +"u{ c #066993", +"v{ c #215B77", +"w{ c #597381", +"x{ c #919BA1", +"y{ c #B7B9BB", +"z{ c #D0D0D0", +"A{ c #CDCECE", +"B{ c #C1C2C3", +"C{ c #9FA2A6", +"D{ c #676F77", +"E{ c #284050", +"F{ c #083F59", +"G{ c #006187", +"H{ c #0081B1", +"I{ c #008EC3", +"J{ c #048FBE", +"K{ c #1B5163", +"L{ c #2B2725", +"M{ c #2A2A29", +"N{ c #232627", +"O{ c #374B52", +"P{ c #242729", +"Q{ c #2B2624", +"R{ c #1F4451", +"S{ c #0689B4", +"T{ c #0091C6", +"U{ c #008FC4", +"V{ c #0081B2", +"W{ c #066892", +"X{ c #185877", +"Y{ c #3D6174", +"Z{ c #687B86", +"`{ c #969DA1", +" ] c #B3B6B8", +".] c #C5C6C6", +"+] c #CECECE", +"@] c #CBCBCB", +"#] c #BDBEBF", +"$] c #7D848A", +"%] c #4E5D67", +"&] c #233E50", +"*] c #08415D", +"=] c #015F84", +"-] c #007CAB", +";] c #008CC0", +">] c #008EC4", +",] c #0E7394", +"'] c #282E31", +")] c #2A2827", +"!] c #252525", +"~] c #2B3337", +"{] c #2A2828", +"]] c #14637C", +"^] c #0291C2", +"/] c #008DC2", +"(] c #0083B5", +"_] c #02719D", +":] c #095B80", +"<] c #18526E", +"[] c #33586B", +"}] c #5A707C", +"|] c #78888F", +"1] c #939CA1", +"2] c #A7ABAE", +"3] c #B3B5B7", +"4] c #C0C1C2", +"5] c #C8C9CA", +"6] c #C9CACB", +"7] c #C9CBCB", +"8] c #C6C7C8", +"9] c #BFC0C1", +"0] c #A1A6A9", +"a] c #899296", +"b] c #6B787F", +"c] c #475864", +"d] c #224253", +"e] c #0F3D55", +"f] c #034C6B", +"g] c #00668E", +"h] c #007EAD", +"i] c #008BBF", +"j] c #058AB7", +"k] c #1F4654", +"l] c #323D40", +"m] c #2A2725", +"n] c #24373E", +"o] c #0A7CA1", +"p] c #0091C4", +"q] c #008EC2", +"r] c #008ABD", +"s] c #0080B1", +"t] c #016F9B", +"u] c #036186", +"v] c #095475", +"w] c #194F69", +"x] c #275065", +"y] c #305465", +"z] c #41606E", +"A] c #526A76", +"B] c #5D707B", +"C] c #63737C", +"D] c #65747D", +"E] c #66747D", +"F] c #65737C", +"G] c #63727A", +"H] c #5E6E77", +"I] c #526771", +"J] c #3E5966", +"K] c #2F4B5B", +"L] c #234557", +"M] c #124158", +"N] c #054764", +"O] c #025475", +"P] c #00668D", +"Q] c #0078A6", +"R] c #0085B7", +"S] c #0191C3", +"T] c #13637E", +"U] c #242425", +"V] c #252A2C", +"W] c #2B3437", +"X] c #222323", +"Y] c #292928", +"Z] c #2A2625", +"`] c #1C4A59", +" ^ c #0588B2", +".^ c #008FC3", +"+^ c #008DC1", +"@^ c #008ABE", +"#^ c #007CAC", +"$^ c #0174A1", +"%^ c #016B95", +"&^ c #026189", +"*^ c #05597E", +"=^ c #085376", +"-^ c #095070", +";^ c #094D6D", +">^ c #0A4C6A", +",^ c #094B6A", +"'^ c #094C6C", +")^ c #055274", +"!^ c #025A7E", +"~^ c #016C96", +"{^ c #0076A3", +"]^ c #0080B0", +"^^ c #0087BA", +"/^ c #008DC3", +"(^ c #0191C4", +"_^ c #0C789C", +":^ c #253339", +"<^ c #212425", +"[^ c #303E41", +"}^ c #292827", +"|^ c #292828", +"1^ c #17576D", +"2^ c #048CBA", +"3^ c #0089BD", +"4^ c #0088BA", +"5^ c #0086B8", +"6^ c #0085B8", +"7^ c #008FC2", +"8^ c #0884AC", +"9^ c #20414C", +"0^ c #2A2523", +"a^ c #2A3439", +"b^ c #2F3A41", +"c^ c #212324", +"d^ c #2A2726", +"e^ c #282B2C", +"f^ c #156179", +"g^ c #038EBC", +"h^ c #0688B3", +"i^ c #1B4E5E", +"j^ c #2A2624", +"k^ c #232729", +"l^ c #3A4F57", +"m^ c #222222", +"n^ c #272C2E", +"o^ c #13657F", +"p^ c #028EBF", +"q^ c #008EC1", +"r^ c #0589B5", +"s^ c #195367", +"t^ c #202223", +"u^ c #2B383B", +"v^ c #1F2123", +"w^ c #242424", +"x^ c #292524", +"y^ c #272D2F", +"z^ c #126580", +"A^ c #038DBD", +"B^ c #008BBE", +"C^ c #008EC0", +"D^ c #0684AD", +"E^ c #195164", +"F^ c #292727", +"G^ c #292626", +"H^ c #212222", +"I^ c #2B393C", +"J^ c #212527", +"K^ c #252524", +"L^ c #292625", +"M^ c #272B2B", +"N^ c #165B72", +"O^ c #008DBF", +"P^ c #018EBE", +"Q^ c #0A7B9F", +"R^ c #1D4552", +"S^ c #2A2524", +"T^ c #292726", +"U^ c #212121", +"V^ c #222728", +"W^ c #314348", +"X^ c #1C4A57", +"Y^ c #0A7A9E", +"Z^ c #018DBD", +"`^ c #008CBF", +" / c #038AB7", +"./ c #106A87", +"+/ c #22393F", +"@/ c #272F33", +"#/ c #303E43", +"$/ c #202526", +"%/ c #282726", +"&/ c #292423", +"*/ c #22373E", +"=/ c #12637E", +"-/ c #0584AF", +";/ c #018CBD", +">/ c #0089BC", +",/ c #028BBC", +"'/ c #097CA1", +")/ c #195265", +"!/ c #262C2D", +"~/ c #292523", +"{/ c #282827", +"]/ c #1E2223", +"^/ c #324148", +"// c #212729", +"(/ c #272829", +"_/ c #1E424D", +":/ c #0F6A88", +"( c #202425", +",( c #2B353B", +"'( c #304149", +")( c #252E31", +"!( c #2C383D", +"~( c #35474D", +"{( c #2B363B", +"]( c #232728", +"^( c #1F2222", +"/( c #252C2F", +"(( c #293439", +"_( c #334147", +":( c #2C393C", +"<( c #232B2D", +"[( c #202020", +"}( c #222729", +"|( c #282F33", +"1( c #303E44", +"2( c #374A52", +"3( c #3B4F59", +"4( c #2F3D44", +"5( c #2C363A", +"6( c #272D30", +"7( c #232628", +"8( c #232829", +"9( c #242829", +"0( c #242728", +"a( c #2A3133", +"b( c #2D393D", +"c( c #2E3A40", +"d( c #344248", +"e( c #3E5861", +"f( c #455D66", +"g( c #465F69", +"h( c #485F67", +"i( c #485F69", +"j( c #495F69", +"k( c #465F67", +"l( c #465F68", +" ", +" ", +" ", +" ", +" . + @ # $ % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % & * = - ", +" ; > , ' ) ! ~ { ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ^ / ( _ : < [ } | ", +" 1 2 3 4 5 6 7 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 8 8 0 / 4 a b c d ", +" e f g h 7 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 0 ] i j k ", +" l m 5 n 9 9 9 9 o p q r s t t u t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t t u t u v w p x 9 9 9 9 x h y z A ", +" B ' 5 7 9 9 9 C p D E F G H I J K L M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M N O P Q R S T T F x 9 9 9 9 U V b W ", +" X Y 7 9 9 9 x w v x Z ` ...+.@.#.$.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.%.&.#.*.=.-.;.>.,.'.E ).9 9 9 C !.) ~. ", +" {.].{ 9 9 9 x F v ^./.(._.:.<.[.}.|.1.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.1.3.4.[.5.6.7.8.9.9 E q 9 9 9 8 0.a. ", +" b._ 0 9 9 9 p v c.d.e.f.g.h.i.2.j.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.k.}.l.m.n.o.x D x 9 9 9 / p.q. ", +" r.s.7 9 9 o r C K t.u.v.w.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.1.w.x.y.z.A.T ).9 9 9 0 B.C. ", +" D.E.8 9 9 x F.G.H.I.J.i.K.L.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.L.L.w.l.M.N.O.r o 9 9 0 : P. ", +" Q.R.8 9 o x v S.T.U.w.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.v.V.W.X.T 9 C 9 0 i Y. ", +" Z.s.`.9 9 x E +.+++@+L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.w.#+$+%+&+9 9 9 h *+ ", +" m 7 9 9 p F.>.=+-+L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.;+>+,+'+F.9 9 9 5 )+ ", +" !+] 9 9 x E ~+{+-+K.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.++]+^+/+9 9 8 Y (+ ", +" _+0.9 9 o F.:+<+[+L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.K.L.++}+|+F 9 9 7 1+2+ ", +" 3+7 9 C T 4+5+J.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.K.K.K.6+7+8+9+0+0+0+9+9+8+a+K.K.K.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.@+b+c+C q 9 9 6 d+ ", +" !+/ 9 9 e+f+g+l.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.K.h+i+j+k+l+m+n+o+p+q+r+q+s+t+u+v+w+x+y+z+h+h+K.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.L.A+w.u.B+T x 9 x C+D+ ", +" s.`.8 E+v F+G+;+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+H+7+I+J+K+L+M+N+O+P+Q+R+S+T+U+U+S+V+W+X+Y+Z+`+ @.@+@@@#@$@A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+%@&@*@T 8 8 U =@ ", +" i / 8 8 -@;@>@,@A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+'@)@!@~@{@]@^@/@(@_@:@<@[@[@[@[@[@[@[@[@[@[@}@|@1@2@3@4@5@6@7@8@9@H+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+0@a@b@b@8 7 c@m ", +" d@7 8 e@f@g@h@A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+'@i@j@k@l@m@n@o@p@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@}@q@r@s@t@u@v@w@H+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+,@x@y@z@8 8 6 A@ ", +" B@0.8 8 f@;@C@D@A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+E@F@G@H@I@/@1@<@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@}@J@K@L@M@N@E@A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+O@P@`.b@8 8 Q@R@ ", +" S@U 8 `.T@U@V@A+W@A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+H+#@X@Y@Z@`@ #[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@[@.#+#@###$#%#W@A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+A+W@;+&#*#=#8 8 5 -# ", +" ;#g 7 8 >#,#'#,@)#W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@!#~#{#]#^#/#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#(#_#:#<#[#}#W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@H+|#1#-@`.8 0 2# ", +" 3#/ 8 8 4#5#6#A+W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@}#7#8#9#0#a#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#b#c#d#e#}#W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@,@f#g#-@8 7 C+h# ", +" i#/ 8 `.f@j#k#W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@}#l#m#n#`@(#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#o#p#q#r#!#W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@L.s#t#T@8 8 5 u# ", +" v#0 8 f@w#x#y#z#W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@}#A#B#C#D#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#(#E#F#G#'@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@A+H#I#J#e@8 5 B@ ", +" K#V 8 8 =#L#M#@+)#W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@}#l#N#O#P#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#Q#R#S#T#}#)#W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@W@)#k#U#V#W#8 h X# ", +" Y#y 8 8 Z#`# $A+)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#.$+$@$#$Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#$$%$&$*$=$-$;$>$,$'$)$!$~$Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#{$]$^$/$)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#y#($_$:$8 7 R. ", +" <$5 8 8 [$}$|$W@)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#1$2$3$b#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#4$5$6$7$8$9$0$a$b$c$d$e$f$g$h$i$j$$$Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#k$l$m$n$)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#@+o$p$=#8 7 3+ ", +" q$] 8 8 r$s$t$)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#z#u$v$+#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#w$x$y$z$A$B$C$D$E$F$G$H$I$J$K$L$M$N$O$P$Q$$$Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#R$S$T$U$)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#K.V$W$Z#8 8 X$Y$ ", +" Z$h 8 8 `$ %.%)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#z#+%@%#%Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#%$$%%%&%*%=%-%;%>%,%'%)%!%!%!%~%{%]%^%/%(%_%:%<%%$Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#Q#[%}%|%z#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#)#A+1%2%3%8 8 y 4% ", +" 5%h 8 E+f@6%.%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%7%8%9%~$$$$$$$$$$$$$$$$$$$$$$$$$$$~$0%a%b%c%d%e%f%{%)#W@)#)#)#)#)#)#)#z#W@W@!%g%h%i%j%k%l%$$$$$$$$$$$$$$$$$$$$$$$$$$%$m%n%o%)#!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%)#p%q%r$8 8 4 r% ", +" ^.0 8 `.>#s%k#!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%t%u%v%w%$$$$$$$$$$$$$$$$$$$$$$$$$$%$x%y%z%A%e%B%!%)#)#!%!%~%!%!%!%!%!%!%!%U$!%!%)#W@C%D%E%F%G%$$$$$$$$$$$$$$$$$$$$$$$$$$H%I%J%K%U$!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%)#p%L%r$8 8 5 <$ ", +" M%0 8 `.W#N%k#!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%U$O%P%Q%$$$$$$$$$$$$$$$$$$$$$$$$$$R%S%T%U%V%W%~%)#!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%)#!%X%Y%Z%`%$$$$$$$$$$$$$$$$$$$$$$$$%$ &.&+&z#!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%)#p%L%r$8 8 5 @& ", +" M%0 8 `.#&s%$&!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%U$t%%&&&k$$$$$$$$$$$$$$$$$$$$$$$$$*&=&-&;&>&,&z#!%~%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%~%!%z#'&)&!&4$$$$$$$$$$$$$$$$$$$$$$$$$~&{&]&z#~%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%!%)#p%L%^&8 8 5 @& ", +" /&0 8 `.#&s%$&~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%(&_&:&<&%$%$%$%$%$%$%$%$%$%$%$%$*&[&}&|&1&g%!%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%z#2&3&4&*&%$%$%$%$%$%$%$%$%$%$%$5&6&7&8&~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%!%9&0&^&8 8 5 a& ", +" /&0 8 `.#&s%$&~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%b&c&d&e&%$%$%$%$%$%$%$%$%$%$%$%$f&g&h&i&g%z#~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%(&j&k&l&%$%$%$%$%$%$%$%$%$%$%$%$m&n&o&!%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%!%9&0&^&8 8 5 p& ", +" /&h 7 7 J#q&r&~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%s&t&u&R%R%R%R%R%R%R%R%R%R%R%R%v&w&x&y&B%!%(&~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%z&U$A&B&C&5&R%R%R%R%R%R%R%R%R%R%R%v&D&E&(&~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%!%9&F&G&7 7 4 p& ", +" M%h 7 n J#q&r&~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%(&H&I&J&R%R%R%R%R%R%R%R%R%R%R%R%K&L&M&N&!%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%z&~%O&P&Q&R%R%R%R%R%R%R%R%R%R%R%R%R&S&T&~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%!%U&F&G&7 7 4 p& ", +" /&h 7 n J#V&r&(&~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%W&X&Y&!$R%R%R%R%R%R%R%R%R%R%R%Z&`& *.*z&~%z&~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%z&~%+*@*#*!$R%R%R%R%R%R%R%R%R%R%R%$*%*&*U$z&~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%~%z&~%**F&G&7 7 4 p& ", +" =*h 7 n J#V&-*z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&;*>*,*!$!$!$!$!$!$!$!$!$!$!$!$'*)*!*~*~%z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&{*]*^*/*!$!$!$!$!$!$!$!$!$!$!$(*_*:*~%z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&~%**F&G&7 7 4 p& ", +" /&h 7 n J#<*-*z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&{*[*}*|*!$!$!$!$!$!$!$!$!$!$!$v&1*2*3*~%z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&4*5*6*!$!$!$!$!$!$!$!$v&7*Z&8*9*0*a*z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&z&~%**F&G&7 7 4 p& ", +" f+h 7 n J#b*c*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*e*f*g*h*v&v&v&v&v&v&v&v&v&v&v&i*j*k*l*z&d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*m*n*o*p*v&v&v&q**$i*x%r*s*t*u*v*w*C%{*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*z&x*F&G&7 7 4 p& ", +" y*h 7 n J#b*z*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*A*B*C*v&v&v&v&v&v&v&v&v&v&v&v&D*E*F*{*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*G*H*I*J*K*L*M*N*O*P*Q*R*S*T*U*V*W*X*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*z&x*F&G&7 7 4 p& ", +" y*h 7 n J#Y*z*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*{%Z*`* =v&v&v&v&v&v&v&v&v&v&v&.=+=@=#=z&d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*a*$=%=&=*===-=;=>=,='=)=!=~={=N&]=a*a*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*d*z&x*^=G&7 7 4 p& ", +" y*h 7 n /=Y*(=a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*m*_=:=<=7*7*7*7*7*7*7*7*7*7*7*[=}=|=~*d*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*d*1=2=3=4=5=6=7=8=,%C%a*d*z&{*G*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*d*9=0=G&7 7 4 p& ", +" y*h 7 n /=Y*(=a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a=b=c=d=7*7*7*7*7*7*7*7*7*7*d=e=f=g=a*'%'%a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*'%'%h=i=~*X*{%d*d*d*d*{%{%a*a*'%'%a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*'%d*9=0=G&7 7 Y X# ", +" y*h 7 n /=j=k='%'%'%'%'%'%'%'%'%'%'%'%'%'%'%l=m=n=d=d=d=d=d=d=d=d=d=d=d=o=p=q=r=d*'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%{%{%a*'%'%X*'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%{%s=t=G&7 7 Y X# ", +" y*!.7 n /=j=k='%'%'%'%'%'%'%'%'%'%'%'%'%'%'%A&u=v=d=d=d=d=d=d=d=d=d=d=d=w=x=y=#=d*z='%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%'%{%s=t=G&7 7 Y X# ", +" y*] 0 U A=j=B=X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*C=D=E=o=o=o=o=o=o=o=o=o=o=o=F=G=H=I='%X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*'%J=t=K=0 0 Y X# ", +" y*] 0 U A=j=B=X*X*X*X*X*X*X*X*X*X*X*X*X*X*T&L=M=G%o=o=o=o=o=o=o=o=o=o=o=N=O=P=g%X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*'%J=t=K=0 0 Y X# ", +" w#] 0 U Q=R=B=X*X*X*X*X*X*X*X*X*X*X*X*X*X*C%S=T=U=o=o=o=o=o=o=o=o=o=o=o=V=W=X='%X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*X*'%J=Y=K=0 0 Y X# ", +" Z=] 0 U Q=R=`=C%C%C%C%C%C%C%C%C%C%C%C%C%C% -.-+-@-q*q*q*q*q*q*q*q*q*q*q*#-$-%-'%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%X*&-*-K=0 0 Y X# ", +" Z=] 0 U Q==-`=C%C%C%C%C%C%C%C%C%C%C%C%C%C%--;->-,-q*q*q*q*q*q*q*q*q*q*q*'-)-!-'%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%C%X*&-*-K=0 0 Y X# ", +" Z=] 0 U Q==-~-g%g%g%g%g%g%g%g%g%g%g%g%g%g%]={-]-8*8*8*8*8*8*8*8*8*8*8*8*^-/-h%X*g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%h=(-*-K=0 0 Y X# ", +" _-] 0 U :-<-~---g%g%g%g%g%g%g%g%g%g%g%g%g%[-}-|-8*8*8*8*8*8*8*8*8*8*8*1-2-3-4-h=~*g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%g%~*h=5-6-K=0 0 Y X# ", +" _-] 0 U :-<-7-~*~*~*~*~*~*~*~*~*~*~*~*~*~*8-9-0-a-a-a-a-a-a-a-a-a-a-a-b-c-d-e-C%~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*g%f-6-K=0 0 Y g- ", +" 5%] 0 U :-<-7-]=~*~*~*~*~*~*~*~*~*~*~*~*~*h-i-j-a-a-a-a-a-a-a-a-a-a-a-F=k-l-m-g%i=~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*g%n-6-K=0 0 Y g- ", +" 5%] 0 U :-o-p-i=i=i=i=i=i=i=i=i=i=i=i=i=i=q-r-s-F=F=F=F=F=F=F=F=F=F=F=t-u-v-w---i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=~*x-6-K=0 0 y-z- ", +" 5%/ 0 U :-o-p-i=i=i=i=i=i=i=i=i=i=i=i=i=i=A-B-s-F=F=F=F=F=F=F=F=F=F=F=t-C-D-w---B%i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=i=~*x-E-K=F-h y z- ", +" 5%/ h !.:-o-G-B%B%B%B%B%B%B%B%B%B%B%B%B%B%A-H-I-t-t-t-t-t-t-t-t-t-t-t-J-K-L-w-~*B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%B%M-x-E-N-h h y 4 ", +" O-/ h !.P-Q-R-,%,%,%,%,%,%,%,%,%,%,%,%,%,%S-T-U-J-J-J-J-J-J-J-J-J-J-J-V-W-X-Y-i=,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%B%Z-`-N-h h C+4 ", +" O-/ h !. ;Q-.;,%,%,%,%,%,%,%,%,%,%,%,%,%,%+;@;#;J-J-J-J-J-J-J-J-J-J-J-V-$;%;&;i=,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%,%B%*;`-N-h h C+4 ", +" O-/ h !.=;Q-.;]%I=I=I=I=I=I=I=I=I=I=I=I=I=-;;;>;,;,;,;,;,;,;,;,;,;,;,;,;';);!;i=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=B%*;`-~;h h C+4 ", +" {;/ h !.=;];^;I=I=I=I=I=I=I=I=I=I=I=I=I=I=/;(;_;,;,;,;,;,;,;,;,;,;,;,;,;:;<;[;l*I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=I=,%};`-|;h h Q@4 ", +" 1;/ h !.=;];2;3;3;3;3;3;3;3;3;3;3;3;3;3;3;4;5;6;7;8;8;8;8;8;8;8;8;8;8;8;9;0;a;,%3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;,&b;c;|;h h Q@4 ", +" 1;/ h !.=;];2;3;3;3;3;3;3;3;3;3;3;3;3;3;3;4;d;e;f;8;8;8;8;8;8;8;8;8;8;8;g;h;i;,%3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;3;,&b;c;|;h h Q@4 ", +" 1;/ h !.j;k;l;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;n;o;p;q;q;q;q;q;q;q;q;q;q;q;r;s;t;I=m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;3;u;c;|;h h Q@4 ", +" 1;/ h !.j;k;l;m;m;m;m;m;m;m;m;m;m;m;m;m;m;v;w;x;y;q;q;q;q;q;q;q;q;q;q;q;z;A;B;4;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;C;v;v;v;v;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;m;3;u;c;|;h h c@4 ", +" 1;5 ] { j;D;E;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;F;G;H;I;I;I;I;I;I;I;I;I;I;I;J;K;L;M;v;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;N;O;P;Q;R;S;T;U;V;W;W;X;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;m;Y;Z;`;] ] c@4 ", +" 1;5 ] { j;D;E;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C; >.>+>I;I;I;I;I;I;I;I;I;I;I;@>#>$>%>v;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;&>*>=>->;>>>,>'>)>!>~>{>]>^>/>(>C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;m;_>Z;`;] ] c@4 ", +" :>5 ] { <>[>}>C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;|>1>2>3>3>3>3>3>3>3>3>3>3>3>3>4>5>6>m;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;X;7>8>9>0>a>b>c>d>e>f>g>h>i>j>k>l>f%C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;m;m>Z;`;] ] c@4 ", +" n>5 ] { <>o>p>X;C;C;C;C;C;C;C;C;C;C;C;C;C;f%q>r>s>t>3>3>3>3>3>3>3>3>3>3>t>u>v>w>v;f%C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;f%x>y>z>3>3>3>3>3>3>3>3>A>B>C>D>E>Y-C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;C;f%C;F>G>`;] ] c@4 ", +" H>5 ] { <>o>p>f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%|>I>J>K>t>t>t>t>t>t>t>t>t>t>t>L>M>N>C;f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%O>P>Q>t>t>t>t>t>t>t>t>t>t>t>R>S>T>U>C;f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%C;V>G>W>] ] V 4 ", +" H>5 ] { <>o>X>M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;Y>Z>`>R>R>R>R>R>R>R>R>R>R>R> ,.,+,@,M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;&*#,$,R>R>R>R>R>R>R>R>R>R>R>%,&,*,=,f%M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;M;f%-,;,W>] ] V >, ", +" H>5 ] { <>,,',),),),),),),),),),),),),),),),),!,~,{,],],],],],],],],],],],],^,/,(,_,),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),:,<,[,],],],],],],],],],],],},|,1,W%M;),),),),),),),),),),),),),),),),_,2,;,W>] ] d@>, ", +" 3,0.] { <>,,',),),),),),),),),),),),),),),),N&4,5,6,7,],],],],],],],],],],],8,9,0,8=),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),),4,a,b,c,],],],],],],],],],],],d,e,f,N&N&),),),),),),),),),),),),),),),),),g,h,i,j,/ E.>, ", +" 3,0./ / <>k,l,N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&4,m,n,L>7,7,7,7,7,7,7,7,7,7,7,7,o,p,q,N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&r,s,t,z;7,7,7,7,7,7,7,7,7,7,7,u,v,w,),N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&N&),g,h,x,/ / E.>, ", +" y,0./ z,A,k,B,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,4,D,E,F,G,G,G,G,G,G,G,G,G,G,G,G,H,I,J,8=C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,#=K,L,M,G,G,G,G,G,G,G,G,G,G,G,N,O,P,Q,8=C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,N&R,S,x,/ / E.>, ", +" y,0./ z,T,U,B,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,V,W,X,Y,G,G,G,G,G,G,G,G,G,G,G,Y,Z,`, 'C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,.'+'s>@'G,G,G,G,G,G,G,G,G,G,G,#'$'%'&'C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,C,N&R,*'x,/ / E.>, ", +" ='0./ z,-';'>','W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%,''')'!'~'~'~'~'~'~'~'~'~'~'~'~'{']'^',','W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%/'('_'~'~'~'~'~'~'~'~'~'~'~'~':'<'!=C,,'W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%C,['}'x,/ / E.|' ", +" ='0./ z,-';'1'W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%2'3'4'5'~'~'~'~'~'~'~'~'~'~'~'6'7'8'9'W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%0'a'b'8,~'~'~'~'~'~'~'~'~'~'~'c'd'e'f','W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%W%g'h'}'x,/ / E.|' ", +" ='0./ z,-'i'j'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'m-k'l'm'6'6'6'6'6'6'6'6'6'6'6'6'n'o'p'q'r'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'s't'u'v'6'6'6'6'6'6'6'6'6'6'6'N,w'x'y'&'r'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'W%z'A'B'/ / E.|' ", +" C'y / z,D'E'j'm-&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'F'&'G'H'I'N,6'6'6'6'6'6'6'6'6'6'6'J'K'L'M'N'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'s'O'P'Q'N,6'6'6'6'6'6'6'6'6'6'6'R'S'T'U'W%&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'&'V'z'W'X'~ 5 3+|' ", +" C'y 5 Y'Z'`' )F'F'F'F'F'F'F'F'F'F'F'F'F'F'F'F'F'F'.)F'+)@)#)$)N,N,N,N,N,N,N,N,N,N,N,N,%)&)*)=).).)F'F'F'F'F'F'F'F'F'F'F'F'F'F'F'F'F'F'F'F'F'F'-);)>),)')N,N,N,N,N,N,N,N,N,N,N,},))!)~){)&'F'F'F'F'F'F'F'F'F'F'F'F'F'F'F'F'F'F'F'&'])W'^)5 5 s.|' ", +" /)y 5 ()Z'_):).).).).).).).).).).).).).).).).).).).).)<)[)})|)')')')')')')')')')')')')')1)2)3)4)5).).).).).).).).).).).).).).).).).).).).)5)6)7)8)9)},')')')')')')')')')')')')0)a)b)c)F'.).).).).).).).).).).).).).).).).).).).)>%d)e)^)5 5 s.f) ", +" g)y 5 ()Z'h)i)^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%5)j)k)l)|)|)|)|)|)|)|)|)|)|)|)|)|)|)m)n)o)p)q)^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%r)s)t)u)v)w)|)|)|)|)|)|)|)|)|)|)|)|)x)y)z)A)B).)^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%.)C)D)^)5 5 ) |' ", +" E)c@5 5 F)G)H)^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%I)J)K)L)|)|)|)|)|)|)|)|)|)|)|)|)|)|)M)N)O)P)Q)R)=,^%^%^%^%^%^%^%^%^%^%^%^%S)=,T)U)V)W)X)w)|)|)|)|)|)|)|)|)|)|)|)|)Y)Z)`) !.!.)S)^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%^%.)+!@!#!5 5 $!p& ", +" %!c@5 5 &!*!=!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!S);!>!,!'!Y)Y)Y)Y)Y)Y)Y)Y)Y)Y)Y)Y)Y)Y)Y)u>)!!!~!{!]!^!/!/!(!(!(!(!(!/!/!_!:!~,~0.0.'~)~!~-!B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)B){=~~{~]~^~e!e!e!e!e!e!e!e!e!e!e!e!e!e!e!e!e!e!e!e!R'/~/~/~/~/~R'e!e!e!e!e!e!e!e!e!e!e!e!e!e!e!e!e!e!e!^~(~_~:~<~B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)B)[~}~|~1~0.2~3~ ", +" 4~7!y 0.1~5~6~7~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~U>9~0~a~b~c~O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!O!d~e~f~g~h~i~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~j~k~l~m~0.s.n~ ", +" o~E.0.m~0.p~7~q~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~q~8~r~s~t~u~v~z!+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~z!w~x~y~z~A~U>8~q~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~8~B~C~D~0.0.$!E~ ", +" F~$!0.0.D~G~H~i~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~f'I~J~K~L~b~z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!z!b~M~N~O~P~Q~R~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~q~B)S~T~D~0.0.3~U~ ", +" e 3~y 0.V~T~W~X~U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>q~r~Y~Z~`~ {.{b~b~b~b~b~b~b~b~b~b~b~b~b~b~b~b~b~b~b~b~b~b~b~b~b~b~b~b~b~b~b~b~b~b~b~b~.{+{@{#{${%{R~Q,U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>U>&{*{v#={0.c@-{;{ ", +" >{,{2~y y '{){&{R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~U>!{~{{{]{^{/{({({({({({({({({({({({({({({({({({({({({({({({({({({({({({({({({/{_{:{<{[{}{|{U>U>R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~R~q~1{2{3{y y R.X ", +" 4{$!y y 5{6{7{Q,f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'U>6=8{9{0{a{b{c{/{/{/{/{/{/{/{/{/{/{/{/{/{/{/{/{/{/{/{/{/{/{/{/{/{/{/{/{/{d{e{f{#{g{h{i{R~r~f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'f'i~j{~.k{y y l{m{ ", +" n{o{y y p{S@q{r{3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*s{f't{u{v{w{x{y{d{z{c{c{c{c{c{c{c{c{c{c{c{c{c{c{c{c{c{c{c{c{c{c{.{A{B{C{D{E{F{G{H{I{f'3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*f'J{K{L{M{y E.N{O{ ", +" P{s.y y Q{R{S{T{U{3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*U{U{3*D!I{V{W{X{Y{Z{`{ ].]+]w~.{.{c{c{c{c{c{c{c{c{c{c{c{.{.{z{@]#]e{$]%]&]*]=]-];]3*3*U{3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*3*U{>]r{,]'])]y y !]'] ", +" ~],~c@y {]{]]]^]U{I{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{I{U{3*/](]_]:]<][]}]|]1]2]3]4]b{5]6]6]7]6]6]5]8]9](~0]a]b]c]d]e]f]g]h]i]3*U{I{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{U{I{D!j]k]Q{M{y c@Q.l] ", +" a.s.y 2~m]n]o]p]/]I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{q]/]I{>]I{r]s]t]u]v]w]x]y]z]A]B]C]D]E]F]G]H]I]J]K]L]M]N]O]P]Q]R];]I{I{/]/]I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{I{/]I{S]T]Q@{]y C+U]V] ", +" W]X]c@c@Y]Z]`] ^.^+^+^/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]+^+^/]I{I{/]@^t{#^$^%^&^*^=^-^;^>^,^'^ !)^!^C!~^{^]^^^.*/^I{/]+^+^/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]/]+^(^_^:^m]c@c@s.<^[^ ", +" k !]c@c@}^|^1^2^q]6=+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^q]I{/]6=i]3^4^5^R]c)c)c)6^^^3^i]6=q]q]/]+^6=+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^+^7^8^9^0^c@c@E.X]a^ ", +" b^c^3+c@c@d^e^f^g^+^;];];];];];];];];];];];];];];];];];];];];];];];];];];];];];];];];];];];];];];]+^+^/]/]/]/]/]/]/]/]/]+^;];];];];];];];];];];];];];];];];];];];];];];];];];];];];];];];];];];];];];];]7^h^i^j^|^c@c@!]k^l^ ", +" a^m^c@c@c@Z]n^o^p^6=i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]q^r^s^Z]}^c@c@s.t^u^ ", +" v^w^c@c@c@x^y^z^A^;]B^B^i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]i]B^B^C^D^E^F^G^V c@E.H^I^ ", +" J^K^E.E.E.L^M^N^ ^O^r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]B^P^Q^R^S^T^E.E.E.U^V^ ", +" W^J^U]E.E.E.L^R.X^Y^Z^B^3^3^r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]r]3^3^`^ /./+/x^R.E.E.E.m^@/ ", +" #/$/U]E.E.E.%/&/*/=/-/;/r]>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/>/B^,/'/)/!/~/{/3+E.s.U^]/ ", +" ^///o{s.E.E.R.x^(/_/:/-//U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'>/}/|/1/2/3/x^q$E.E.E.4/U^]/ ", +" 5/6/7/4/E.E.E.8/&/*+9/0/a/b/c/d/>/U'^^^^4^U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'U'4^^^^^U'>/r]e/f/g/h/i/R.~/R.E.E.E.!]j/k/ ", +" l/m/!]{/s.s.s.q$n/3+o/p/q/r/s/t/W*u/e/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/v/w/e/e/x/y/z/A/B/C/D/E/q$F/q$s.s.s.) o{G/H/ ", +" I/J^H^$!s.s.s.s.q$n/n/*+J/K/L/M/N/O/P/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/Q/R/S/T/C/U/V/W/X/3+Y/Z/4/s.s.s.s.w^`/ (.( ", +" +(@(#(m^$(s.s.s.s.s.%(&(n/n/&(Z$s.*(2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#*(4/=(&(-(&(&(;(s.s.s.s.s.!]U^>(,( ", +" '()(j/H^w^s.s.s.s.s.s.4/4/4/4/;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(;(4/4/4/4/4/s.s.s.s.s.$!o{7/j/!( ", +" ~({(](^(m^w^!]s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.s.$!!]o{U^c^/((( ", +" _(:(<(N{X][(7/m^w^!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]!]w^m^[(7/)+}(|(1(2( ", +" 3(_(4(5(6(7(k^8(k^P{P{P{P{P{P{9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(9(P{P{P{P{P{P{k^P{a.0(a(b(c(d(e( ", +" f(g(h(i(i(i(i(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(j(i(i(i(i(i(k(l( ", +" ", +" ", +" "}; diff --git a/src/resource/linux/PNG_HELP.hpng b/src/resource/linux/PNG_HELP.hpng new file mode 100644 index 00000000..b66e3965 --- /dev/null +++ b/src/resource/linux/PNG_HELP.hpng @@ -0,0 +1,43 @@ +unsigned char PNG_HELP_png[492] = { + 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, + 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x18, + 0x08, 0x06, 0x00, 0x00, 0x00, 0xE0, 0x77, 0x3D, 0xF8, 0x00, 0x00, 0x00, + 0x06, 0x62, 0x4B, 0x47, 0x44, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xA0, + 0xBD, 0xA7, 0x93, 0x00, 0x00, 0x01, 0xA1, 0x49, 0x44, 0x41, 0x54, 0x48, + 0x89, 0xDD, 0x95, 0xB1, 0x4E, 0x02, 0x41, 0x10, 0x86, 0xBF, 0x80, 0x9C, + 0x85, 0xF2, 0x06, 0x68, 0x6B, 0x02, 0xBC, 0x83, 0x58, 0xD8, 0x1A, 0x24, + 0x86, 0x52, 0x4B, 0x22, 0x31, 0x88, 0x2F, 0x61, 0x88, 0xD1, 0xA7, 0x10, + 0x29, 0x0C, 0x89, 0x89, 0x89, 0x96, 0xB6, 0x16, 0xDA, 0x88, 0x46, 0x11, + 0xF5, 0x09, 0x8C, 0x26, 0x82, 0x85, 0x34, 0x9C, 0xC5, 0xCE, 0x86, 0xCD, + 0xB2, 0x77, 0x07, 0x94, 0xFC, 0xC9, 0xE4, 0x92, 0x7F, 0x66, 0xFE, 0xD9, + 0x9B, 0x9B, 0xD9, 0x83, 0x59, 0x87, 0x07, 0x14, 0x81, 0x06, 0xD0, 0x06, + 0x7E, 0xC5, 0xDA, 0xC0, 0x99, 0xF8, 0xBC, 0x69, 0xC5, 0x0B, 0xC0, 0x07, + 0xE0, 0x47, 0xD8, 0x3B, 0xB0, 0x39, 0x89, 0x70, 0x0C, 0x38, 0x32, 0x04, + 0x1E, 0x80, 0x0A, 0x90, 0x06, 0x16, 0xC4, 0x32, 0xC0, 0x3E, 0xD0, 0x32, + 0xE2, 0x6A, 0x92, 0x1B, 0x09, 0x2D, 0xFE, 0x07, 0x94, 0x22, 0x92, 0x62, + 0xC0, 0xAE, 0xC4, 0xEA, 0x22, 0xA1, 0x28, 0x18, 0xE2, 0xAB, 0x06, 0xBF, + 0x04, 0x34, 0x81, 0xAE, 0xD8, 0x05, 0xB0, 0x62, 0xF8, 0x73, 0x46, 0x91, + 0x7C, 0x90, 0xB8, 0xC7, 0xB0, 0xE7, 0x25, 0x4B, 0xFC, 0x8B, 0xD1, 0xDE, + 0x7F, 0x8B, 0x4F, 0xA3, 0x2C, 0xFC, 0x1B, 0x90, 0x70, 0x15, 0x28, 0x32, + 0xEC, 0xB9, 0xD9, 0x96, 0xA6, 0xF0, 0x57, 0x40, 0x4A, 0xEC, 0x5A, 0xB8, + 0x73, 0x23, 0x2E, 0x0E, 0x3C, 0x0A, 0xBF, 0xE5, 0x2A, 0xD0, 0x10, 0x67, + 0xC5, 0xE2, 0xBB, 0xC2, 0xA7, 0xAC, 0xB7, 0xF2, 0x81, 0x1F, 0x2B, 0xB6, + 0x2A, 0x7C, 0xDD, 0x55, 0xE0, 0x55, 0x9C, 0x69, 0x97, 0xD3, 0xC2, 0xB2, + 0xC4, 0x7E, 0x5A, 0x7C, 0x46, 0xF8, 0xB6, 0x2B, 0x49, 0x9F, 0x34, 0x19, + 0x21, 0x3E, 0x8F, 0x6A, 0x97, 0x0F, 0x1C, 0x5B, 0xBE, 0xA4, 0xF0, 0xDD, + 0x69, 0x0B, 0xCC, 0x01, 0x97, 0x12, 0x77, 0xC3, 0xE8, 0x16, 0x87, 0x16, + 0x18, 0xA7, 0x45, 0x27, 0x12, 0x73, 0x1B, 0x70, 0x90, 0x2C, 0x56, 0x8B, + 0xCC, 0x69, 0xB9, 0x97, 0xE7, 0x7A, 0x48, 0x81, 0x1D, 0x79, 0x6E, 0x03, + 0x3D, 0x87, 0x5F, 0xE7, 0xDE, 0xB9, 0x92, 0xF5, 0x98, 0xB6, 0x18, 0x73, + 0xE5, 0x2D, 0xC4, 0x81, 0x27, 0x42, 0xC6, 0xD4, 0x43, 0x5D, 0x5C, 0x3E, + 0x6A, 0xFD, 0x5D, 0xD0, 0x4B, 0xE6, 0xC2, 0x9E, 0xF8, 0x3A, 0x04, 0x2C, + 0x1A, 0xA8, 0x5B, 0x51, 0x5F, 0x15, 0xB9, 0x09, 0x0A, 0xAC, 0x01, 0x7D, + 0x60, 0x00, 0x6C, 0x04, 0x89, 0x6B, 0xD4, 0x8C, 0x22, 0x65, 0xD4, 0xAB, + 0x07, 0x21, 0x2E, 0x27, 0xEF, 0x4B, 0xCE, 0x61, 0x94, 0x38, 0xA8, 0xFE, + 0xEB, 0x22, 0x3E, 0x6A, 0xFD, 0xAB, 0xA8, 0x25, 0x5A, 0x14, 0xCB, 0x02, + 0x07, 0x0C, 0x7B, 0x3E, 0x10, 0xF1, 0x89, 0xBE, 0x5D, 0x1E, 0x75, 0x71, + 0x45, 0xFD, 0x70, 0x3A, 0x8C, 0xD1, 0x96, 0x20, 0x24, 0x50, 0x13, 0x51, + 0x07, 0x5E, 0x50, 0xA3, 0xD9, 0x03, 0x9E, 0x81, 0x53, 0xF1, 0x05, 0x7E, + 0xD0, 0xD9, 0xC0, 0x3F, 0xA0, 0xD8, 0x8F, 0x1F, 0xB8, 0x80, 0x0B, 0x45, + 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82 +}; diff --git a/src/resource/linux/PNG_REFRESH.hpng b/src/resource/linux/PNG_REFRESH.hpng new file mode 100644 index 00000000..7b18a7bd --- /dev/null +++ b/src/resource/linux/PNG_REFRESH.hpng @@ -0,0 +1,40 @@ +unsigned char PNG_REFRESH_png[449] = { + 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, + 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20, + 0x08, 0x06, 0x00, 0x00, 0x00, 0x73, 0x7A, 0x7A, 0xF4, 0x00, 0x00, 0x00, + 0x06, 0x62, 0x4B, 0x47, 0x44, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xA0, + 0xBD, 0xA7, 0x93, 0x00, 0x00, 0x01, 0x76, 0x49, 0x44, 0x41, 0x54, 0x58, + 0x85, 0xED, 0xD6, 0x3F, 0x4B, 0x1D, 0x41, 0x1C, 0x85, 0xE1, 0x47, 0x45, + 0x88, 0x36, 0x16, 0x2A, 0x01, 0x8D, 0x28, 0x9A, 0x90, 0x46, 0x53, 0xD8, + 0xFB, 0x11, 0x82, 0x88, 0x22, 0xA8, 0x85, 0x56, 0x36, 0x16, 0x16, 0x16, + 0xA9, 0x52, 0xD8, 0xD9, 0xA5, 0x0C, 0xC1, 0xCA, 0xC2, 0x10, 0x08, 0x5A, + 0x49, 0xD2, 0x99, 0xE0, 0x57, 0x10, 0xAC, 0x03, 0x82, 0x85, 0x7F, 0x8A, + 0xA4, 0x52, 0xBC, 0x57, 0x53, 0xCC, 0xAC, 0xAE, 0x8D, 0xD9, 0xD9, 0x2B, + 0x44, 0x70, 0x5F, 0x18, 0x58, 0xD8, 0x39, 0x73, 0xCE, 0xCC, 0xCE, 0xCE, + 0xFC, 0xA8, 0x78, 0xEA, 0x34, 0x25, 0xF6, 0xEF, 0xC3, 0x1C, 0xC6, 0xF0, + 0x06, 0x9D, 0xA8, 0xE1, 0x10, 0xBF, 0xB0, 0x83, 0x2D, 0x1C, 0x3F, 0x5C, + 0xC4, 0xC0, 0x0B, 0x7C, 0x8E, 0x66, 0xD7, 0xFF, 0x68, 0x35, 0xAC, 0xA3, + 0x2B, 0xA7, 0xCF, 0xDE, 0x95, 0x62, 0x12, 0x7F, 0xE2, 0x00, 0xE7, 0xD8, + 0xC4, 0x34, 0x06, 0xD0, 0x86, 0x0E, 0x8C, 0x08, 0x2B, 0xF3, 0x2D, 0x17, + 0xF2, 0x0C, 0xE3, 0x8D, 0x06, 0x58, 0xC2, 0x55, 0x14, 0x7F, 0x45, 0x6F, + 0x01, 0xCD, 0x4B, 0xFC, 0x8C, 0x9A, 0x4B, 0x2C, 0x94, 0x0D, 0x30, 0x81, + 0x7A, 0x6C, 0xCB, 0x89, 0xDA, 0x66, 0xAC, 0x46, 0xD3, 0x6C, 0x02, 0x49, + 0x01, 0x9E, 0xE3, 0x34, 0x8A, 0x56, 0x12, 0xCD, 0xF3, 0xAC, 0xB9, 0xBB, + 0x3F, 0x0A, 0xF3, 0x31, 0x0A, 0x76, 0x4A, 0x98, 0xDE, 0xB7, 0x41, 0x0B, + 0xD1, 0x8A, 0x03, 0x61, 0x33, 0xBD, 0xFA, 0x1F, 0x01, 0x08, 0xDF, 0x70, + 0xB4, 0x84, 0x79, 0x45, 0x45, 0xC5, 0xA3, 0x65, 0x54, 0xF8, 0xB5, 0x0B, + 0xF3, 0x20, 0x87, 0x49, 0xE4, 0xB5, 0x70, 0xA8, 0x1D, 0x08, 0x87, 0xDC, + 0x1D, 0x92, 0x52, 0x95, 0xA0, 0x09, 0x1F, 0xD0, 0x82, 0x5D, 0xE1, 0x76, + 0x2C, 0x44, 0x7E, 0xC6, 0xAB, 0x0D, 0x04, 0x78, 0x17, 0xC7, 0x38, 0x46, + 0x77, 0x8A, 0x30, 0x33, 0xCF, 0xAE, 0xD2, 0xF7, 0xD2, 0xCB, 0xB7, 0x95, + 0xA8, 0xAF, 0xE1, 0x6D, 0xA2, 0xF6, 0x26, 0xC0, 0xBC, 0xB0, 0x6C, 0xD7, + 0xF8, 0x81, 0xA1, 0x02, 0xDA, 0x7E, 0x6C, 0x47, 0x4D, 0x1D, 0x8B, 0xA9, + 0xE6, 0xF9, 0x00, 0x84, 0xF4, 0x59, 0x6D, 0x50, 0xC3, 0x77, 0xCC, 0x62, + 0x10, 0xCF, 0xD0, 0x1E, 0x9F, 0x67, 0xF0, 0x05, 0x17, 0xB1, 0xEF, 0x6F, + 0xB7, 0x25, 0x59, 0x43, 0x01, 0x08, 0xD5, 0xEF, 0x27, 0xB7, 0xAB, 0x71, + 0x5F, 0xBB, 0xC4, 0x06, 0x7A, 0x8A, 0x18, 0xA5, 0x7E, 0xD7, 0x2E, 0x4C, + 0x09, 0x33, 0x1B, 0x16, 0x36, 0x56, 0x1D, 0x27, 0xD8, 0xC7, 0x9E, 0x50, + 0xB4, 0x1E, 0x25, 0x8E, 0x5B, 0xF1, 0x84, 0xF9, 0x0B, 0x5F, 0xA6, 0x85, + 0x78, 0x5B, 0xF8, 0x93, 0x75, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, + 0x44, 0xAE, 0x42, 0x60, 0x82 +}; diff --git a/src/resource/linux/X_BOX.xpm b/src/resource/linux/X_BOX.xpm new file mode 100644 index 00000000..b2d223b6 --- /dev/null +++ b/src/resource/linux/X_BOX.xpm @@ -0,0 +1,37 @@ +/* XPM */ +const char * X_BOX_xpm[] = { +"32 32 2 1", +" c None", +". c #000000", +" ", +" ", +" ", +" ", +" ......... ....... ", +" ......... ....... ", +" ......... ....... .. ", +" .......... ....... ... ", +" .......... ....... .... ", +" ..... ", +" ................... ..... ", +" ................... ..... ", +" ................... ..... ", +" ....... ..... ..... ", +" ................... ..... ", +" ................... ..... ", +" ................... ..... ", +" ................... ..... ", +" ................... ..... ", +" ................... ..... ", +" ................... ..... ", +" ................... .... ", +" ................... .... ", +" ................... ... ", +" ................... .. ", +" ................... .. ", +" ................... ", +" ................... ", +" ", +" ", +" ", +" "}; diff --git a/src/resource/linux/X_GAME_PROFILE.xpm b/src/resource/linux/X_GAME_PROFILE.xpm new file mode 100644 index 00000000..f877211e --- /dev/null +++ b/src/resource/linux/X_GAME_PROFILE.xpm @@ -0,0 +1,37 @@ +/* XPM */ +const char* X_GAME_PROFILE_xpm[] = { +"32 32 2 1", +" c None", +". c #000000", +" ", +" ...................... ", +" ....................... ", +" .. ", +" .. ..................... ", +" .. ....................... ", +" .. ... ", +" .. .. ", +" .. .. ...................... ", +" .. .. ...................... ", +" .. .. ...................... ", +" .. .. ...................... ", +" .. .. ...................... ", +" .. .. ...................... ", +" .. .. ...................... ", +" .. .. ...................... ", +" .. .. ...................... ", +" .. .. ...................... ", +" .. .. .......... ........... ", +" .. .. ............ ......... ", +" .. .. ........... ........ ", +" .. .. ............ ....... ", +" .. .. ............. ...... ", +" .. .............. ..... ", +" .. ............... .... ", +" . ................ ... ", +" ................. .. ", +" .................. .. ", +" ................... ...", +" ......", +" .... ", +" .. "}; diff --git a/src/resource/linux/X_SETTINGS.xpm b/src/resource/linux/X_SETTINGS.xpm new file mode 100644 index 00000000..8c77cec5 --- /dev/null +++ b/src/resource/linux/X_SETTINGS.xpm @@ -0,0 +1,69 @@ +/* XPM */ +const char* X_SETTINGS_xpm[] = { +"64 64 2 1", +" c None", +". c #000000", +" ..................................................... ", +" ..................................................... ", +" ....................................................... ", +" .......................................................... ", +" ........................................................... ", +".............................................................. ", +".............................................................. ", +".............................. .............................. ", +"............................. ..............................", +"............................. ..............................", +"............................. .............................", +"............................ .............................", +"............................ .............................", +"............... ........... .............................", +".............. ........ ........ ...............", +"............. ..... .... ...............", +"............. .. .. ..............", +".............. ...............", +".............. ...............", +"............... ................", +"............... .................", +"................ ....... .................", +"................. ............. .................", +"................. ............... .................", +"................ ................. .................", +"................ ................... .................", +"............... ................... .................", +".............. .................... ...............", +"........... ..................... ............", +"........ ..................... .........", +"....... ..................... ........", +"....... ..................... ........", +"....... ..................... ........", +"........ ..................... .........", +".......... ..................... ...........", +".............. .................... ...............", +"............... ................... ................", +"................ .................. .................", +"................ ................. .................", +"................. ............... .................", +"................. ............ .................", +"................ ......... .................", +"............... .................", +"............... ................", +".............. ...............", +".............. ...............", +".............. ...............", +".............. ...... ...... ...............", +".............. ........ ........ ...............", +"............................ .............................", +"............................ .............................", +"............................ .............................", +"............................. .............................", +"............................. ..............................", +"............................. ..............................", +".............................. .............................. ", +".............................................................. ", +".............................................................. ", +" ........................................................... ", +" .......................................................... ", +" ......................................................... ", +" ..................................................... ", +" ................................................. ", +" ............................................... "}; diff --git a/src/resource/linux/icons8-checkmark-yes-32.hpng b/src/resource/linux/icons8-checkmark-yes-32.hpng new file mode 100644 index 00000000..bafaa0a8 --- /dev/null +++ b/src/resource/linux/icons8-checkmark-yes-32.hpng @@ -0,0 +1,51 @@ + +unsigned char PNG_CHECK_YES_png[573] = { + 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, + 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20, + 0x08, 0x06, 0x00, 0x00, 0x00, 0x73, 0x7A, 0x7A, 0xF4, 0x00, 0x00, 0x00, + 0x06, 0x62, 0x4B, 0x47, 0x44, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xA0, + 0xBD, 0xA7, 0x93, 0x00, 0x00, 0x01, 0xF2, 0x49, 0x44, 0x41, 0x54, 0x58, + 0x85, 0xED, 0xD6, 0xBD, 0x6E, 0x13, 0x41, 0x10, 0x07, 0xF0, 0x1F, 0x35, + 0x89, 0xA0, 0xA0, 0x00, 0x4C, 0xC4, 0x97, 0xC1, 0x79, 0x14, 0xA4, 0x38, + 0xA1, 0x25, 0xF0, 0x04, 0x40, 0x28, 0x10, 0x79, 0x06, 0x2A, 0x1A, 0x3A, + 0x82, 0x78, 0x80, 0xC8, 0x24, 0x08, 0x10, 0xD0, 0x22, 0x51, 0x21, 0x1E, + 0x00, 0x41, 0x0A, 0x28, 0x80, 0x1A, 0x08, 0x05, 0x32, 0xC6, 0x32, 0xC5, + 0x8E, 0x39, 0x63, 0x11, 0xDF, 0xDE, 0x39, 0x5D, 0xF2, 0x97, 0xB6, 0xB8, + 0xDD, 0x99, 0xFF, 0xCC, 0xCE, 0xED, 0x7C, 0xB0, 0x8F, 0xBD, 0x8E, 0x03, + 0x15, 0xE5, 0xE7, 0x70, 0x11, 0x0B, 0x38, 0x15, 0xDF, 0xF0, 0x09, 0x1F, + 0xF1, 0x1C, 0x4F, 0xF0, 0x79, 0x97, 0xFC, 0xFB, 0x8B, 0x06, 0xD6, 0xD0, + 0xC3, 0xA0, 0x64, 0xF5, 0xD1, 0xC1, 0xC9, 0xDD, 0x32, 0xBE, 0x84, 0xED, + 0x20, 0xEF, 0x62, 0x1D, 0x97, 0xD0, 0xC2, 0xC1, 0x58, 0x2D, 0x2C, 0xC7, + 0x59, 0x37, 0x64, 0xB7, 0xB1, 0x38, 0xAD, 0xF1, 0x1B, 0xD2, 0x8D, 0x06, + 0xD2, 0xAD, 0x4E, 0x67, 0xE8, 0x9C, 0xC1, 0x86, 0x22, 0x1A, 0x2B, 0x75, + 0x8D, 0x2F, 0x05, 0xC1, 0x6F, 0xDC, 0xAC, 0xA1, 0x7F, 0x2B, 0x74, 0xFB, + 0x6A, 0x44, 0xA2, 0xA1, 0x08, 0x7B, 0x1D, 0xE3, 0xA3, 0x4E, 0x0C, 0xF0, + 0x1D, 0xC7, 0xAA, 0x28, 0x3E, 0x50, 0x84, 0x7D, 0x5A, 0x6C, 0x06, 0xD7, + 0x5A, 0xAE, 0xC2, 0x9C, 0x14, 0xBA, 0xAE, 0xBC, 0x7F, 0x5E, 0x86, 0xB3, + 0xC1, 0xD5, 0x93, 0x22, 0x5B, 0x8A, 0x15, 0xC9, 0xE3, 0xF5, 0x9A, 0x06, + 0xEF, 0x48, 0xA1, 0x1F, 0x45, 0x27, 0x38, 0xAF, 0xE5, 0x10, 0xBC, 0x08, + 0xE1, 0xE5, 0x9A, 0xC6, 0x07, 0xF8, 0xE9, 0xDF, 0x3A, 0x70, 0x25, 0xF6, + 0x9F, 0xE5, 0x90, 0x6C, 0x85, 0x70, 0xB3, 0xA2, 0xF1, 0xDB, 0x8A, 0x5A, + 0xD1, 0x1E, 0x3B, 0x6B, 0xC5, 0xD9, 0xBB, 0x1C, 0xA2, 0x1F, 0x21, 0x3C, + 0x33, 0xB6, 0x7F, 0x0E, 0x2F, 0xFD, 0xFF, 0x3F, 0x4E, 0x32, 0x2E, 0xB8, + 0x06, 0xC1, 0x5D, 0x8A, 0x61, 0xFA, 0xCD, 0x8E, 0xED, 0x3F, 0x56, 0xDC, + 0xE2, 0xF8, 0xC8, 0xFE, 0x68, 0xD8, 0x2F, 0xEC, 0xC0, 0x39, 0xAB, 0xA8, + 0x8E, 0xA5, 0x78, 0x1F, 0xC2, 0xE7, 0xC7, 0xF6, 0x0F, 0xE1, 0x75, 0x9C, + 0x6D, 0x49, 0x91, 0x28, 0xBB, 0xF9, 0x10, 0xF3, 0x21, 0xF7, 0x36, 0xC7, + 0x81, 0x49, 0x8F, 0xF0, 0x30, 0xDE, 0xC4, 0xF9, 0x57, 0xE5, 0x37, 0x1F, + 0xA2, 0xD2, 0x23, 0xBC, 0x6E, 0x72, 0x1A, 0x8E, 0x3A, 0x91, 0x63, 0x9C, + 0x22, 0x0D, 0xAF, 0xE6, 0x38, 0x70, 0x42, 0x2A, 0x1A, 0x5D, 0xA9, 0xB1, + 0xEC, 0xE4, 0xC4, 0xAB, 0x4C, 0xE3, 0x4D, 0x15, 0x0B, 0x11, 0xDC, 0x97, + 0x3C, 0xDE, 0x98, 0x20, 0x93, 0x3B, 0xCC, 0x3C, 0x0A, 0xAE, 0x7B, 0xB9, + 0xC6, 0x49, 0xAF, 0x7C, 0x98, 0x0D, 0xE3, 0x55, 0xAD, 0x0A, 0x56, 0x83, + 0xE3, 0x1B, 0x8E, 0x56, 0x55, 0x5E, 0x54, 0xB4, 0xE3, 0x3A, 0x4E, 0xAC, + 0x2A, 0xDA, 0xF1, 0x42, 0x0D, 0x7D, 0xA4, 0xBE, 0x30, 0x1C, 0x48, 0x36, + 0xA5, 0xC6, 0x52, 0x86, 0xA6, 0x22, 0xEC, 0x7D, 0xE9, 0x51, 0x4F, 0x85, + 0xB6, 0xD4, 0xCF, 0x87, 0xF9, 0xDE, 0xC1, 0x65, 0x29, 0xB7, 0x67, 0x62, + 0xCD, 0x4B, 0xA9, 0xF6, 0x10, 0xBF, 0x14, 0x61, 0xAF, 0x7D, 0xF3, 0x71, + 0x1C, 0xC1, 0x5D, 0x79, 0x43, 0x69, 0x4F, 0xEA, 0xFD, 0x59, 0x03, 0x48, + 0xD5, 0xB1, 0xBC, 0x21, 0x8D, 0x6A, 0x6D, 0x69, 0x56, 0x18, 0x1D, 0xCB, + 0x3F, 0x48, 0x63, 0xF9, 0x53, 0x7C, 0xA9, 0xC8, 0xBB, 0x8F, 0x3D, 0x8C, + 0x3F, 0x75, 0x73, 0xA4, 0xCD, 0x6F, 0x2F, 0x42, 0xB3, 0x00, 0x00, 0x00, + 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82 +}; diff --git a/src/resource/linux/icons8-error-32.hpng b/src/resource/linux/icons8-error-32.hpng new file mode 100644 index 00000000..d604e4dd --- /dev/null +++ b/src/resource/linux/icons8-error-32.hpng @@ -0,0 +1,42 @@ +unsigned char PNG_ERROR_png[472] = { + 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, + 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20, + 0x08, 0x06, 0x00, 0x00, 0x00, 0x73, 0x7A, 0x7A, 0xF4, 0x00, 0x00, 0x00, + 0x06, 0x62, 0x4B, 0x47, 0x44, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xA0, + 0xBD, 0xA7, 0x93, 0x00, 0x00, 0x01, 0x8D, 0x49, 0x44, 0x41, 0x54, 0x58, + 0x85, 0xED, 0x95, 0x4F, 0x28, 0x04, 0x71, 0x1C, 0xC5, 0x3F, 0xFE, 0x24, + 0xB4, 0x29, 0x29, 0x25, 0xA2, 0x94, 0x83, 0xE2, 0xE0, 0xA0, 0x28, 0x0E, + 0x14, 0x27, 0x4E, 0x14, 0x27, 0x7F, 0x8E, 0x9C, 0x38, 0x2B, 0x4E, 0xD6, + 0x95, 0x3B, 0x17, 0xE2, 0x66, 0x1D, 0x1D, 0x94, 0x72, 0x70, 0x50, 0x1C, + 0xC8, 0x65, 0x2F, 0x22, 0x91, 0x92, 0x92, 0xBF, 0x21, 0xD6, 0x61, 0xDF, + 0xB4, 0x63, 0xCD, 0xCC, 0xCE, 0xAC, 0x9D, 0xD9, 0xD4, 0xBE, 0xFA, 0xD6, + 0xF6, 0x7D, 0xEF, 0xF7, 0xDE, 0xDB, 0xDF, 0x4E, 0xB3, 0x90, 0xC3, 0x3F, + 0x47, 0xB3, 0x26, 0x6B, 0xD8, 0xD5, 0x64, 0x05, 0x83, 0x40, 0x4C, 0x33, + 0x10, 0x74, 0x78, 0x31, 0x70, 0x66, 0x2A, 0x70, 0x01, 0x94, 0x06, 0x59, + 0x60, 0x46, 0xC1, 0x27, 0x9A, 0x98, 0x76, 0x81, 0xA0, 0x1A, 0x78, 0x54, + 0x68, 0x2F, 0xD0, 0xAD, 0xCF, 0xCF, 0x40, 0x6D, 0x10, 0x05, 0xD6, 0x14, + 0x18, 0x31, 0xED, 0xB6, 0xB4, 0x5B, 0xF5, 0x3B, 0xBC, 0x0D, 0xF8, 0x02, + 0xDE, 0x80, 0x06, 0xD3, 0xBE, 0x1E, 0x78, 0x15, 0xD7, 0xE1, 0x57, 0x78, + 0x1E, 0x70, 0x40, 0xFC, 0x9B, 0x86, 0x2D, 0xF8, 0x05, 0x71, 0x87, 0x40, + 0xBE, 0x1F, 0x05, 0x46, 0x15, 0x70, 0x03, 0x94, 0x59, 0xF0, 0x21, 0xE0, + 0x5A, 0x9A, 0x91, 0x4C, 0x87, 0x87, 0x80, 0x2B, 0x99, 0x8F, 0x39, 0xE8, + 0xC6, 0x71, 0x2E, 0x99, 0x36, 0xC2, 0x32, 0x3E, 0xE2, 0xE7, 0xF5, 0x1A, + 0xEF, 0x01, 0x03, 0xF9, 0x24, 0x7E, 0xA6, 0xF9, 0x4C, 0x85, 0x9B, 0x1F, + 0xB0, 0xCE, 0x24, 0x2E, 0xB9, 0x00, 0x40, 0x3B, 0xD6, 0x0F, 0x6A, 0xDA, + 0x88, 0x28, 0x64, 0xDD, 0x82, 0xB3, 0x2A, 0x00, 0xB0, 0xA1, 0xFD, 0xE6, + 0x5F, 0xC3, 0xBB, 0x64, 0xF4, 0x02, 0xD4, 0x79, 0x28, 0x50, 0x03, 0x3C, + 0x89, 0xEB, 0x49, 0x37, 0xBC, 0x00, 0x38, 0x96, 0xC9, 0xAC, 0x8D, 0xC6, + 0xAE, 0x00, 0xC0, 0x9C, 0xB8, 0x53, 0xA0, 0x30, 0x9D, 0x02, 0x93, 0x32, + 0xB8, 0xC4, 0xFE, 0x8F, 0xC6, 0xA9, 0x40, 0x09, 0x70, 0x2E, 0x7E, 0xC2, + 0x6B, 0x78, 0x39, 0x70, 0xAB, 0xC3, 0x43, 0x5E, 0x0F, 0x9B, 0x30, 0x2C, + 0x8F, 0x3B, 0xA0, 0xC2, 0xCB, 0xC1, 0x25, 0x1D, 0xDC, 0x27, 0xFE, 0x06, + 0xB4, 0x83, 0xD3, 0x0D, 0x18, 0xD8, 0x93, 0x66, 0xD1, 0x6D, 0x78, 0x23, + 0xF0, 0x0E, 0x7C, 0x02, 0xAD, 0x29, 0xB4, 0x6E, 0x0A, 0xB4, 0xC8, 0xEB, + 0x03, 0x68, 0x72, 0x53, 0x60, 0x5B, 0xA6, 0xCB, 0x6E, 0xC4, 0x2E, 0xB1, + 0x22, 0xCF, 0x9D, 0x54, 0xC2, 0x7E, 0x09, 0x1F, 0x80, 0xAA, 0x0C, 0x16, + 0xA8, 0x04, 0xEE, 0xE5, 0xDD, 0x67, 0x27, 0x2A, 0x02, 0xA2, 0x24, 0xAE, + 0xD5, 0xAF, 0x89, 0x2A, 0xEB, 0x17, 0xA6, 0x03, 0x08, 0x37, 0x66, 0x2A, + 0xE5, 0x9D, 0xE5, 0x90, 0x43, 0x50, 0xF8, 0x06, 0xB0, 0x55, 0xA0, 0x4B, + 0x18, 0x99, 0x32, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, + 0xAE, 0x42, 0x60, 0x82 +}; diff --git a/src/resource/linux/resources.cpp b/src/resource/linux/resources.cpp new file mode 100644 index 00000000..5dbb9d14 --- /dev/null +++ b/src/resource/linux/resources.cpp @@ -0,0 +1,22 @@ +#include "X_GAME_PROFILE.xpm" +#include "M_WND_ICON128.xpm" +#include "X_BOX.xpm" +#include "X_SETTINGS.xpm" + +#include "icons8-checkmark-yes-32.hpng" +#include "icons8-error-32.hpng" +#include "PNG_HELP.hpng" +#include "PNG_REFRESH.hpng" + +#include "DEBUGGER_BP.hpng" +#include "DEBUGGER_BP_RED.hpng" +#include "DEBUGGER_PAUSE.hpng" +#include "DEBUGGER_PLAY.hpng" +#include "DEBUGGER_STEP_INTO.hpng" +#include "DEBUGGER_STEP_OUT.hpng" +#include "DEBUGGER_STEP_OVER.hpng" +#include "DEBUGGER_GOTO.hpng" + +#include "INPUT_LOW_BATTERY.hpng" +#include "INPUT_CONNECTED.hpng" +#include "INPUT_DISCONNECTED.hpng" diff --git a/src/resource/linux/resources.h b/src/resource/linux/resources.h new file mode 100644 index 00000000..8cb9fe5d --- /dev/null +++ b/src/resource/linux/resources.h @@ -0,0 +1,22 @@ +extern const char* X_GAME_PROFILE_xpm[]; +extern const char* M_WND_ICON128_xpm[]; +extern const char* X_BOX_xpm[]; +extern const char* X_SETTINGS_xpm[]; + +extern unsigned char PNG_CHECK_YES_png[573]; +extern unsigned char PNG_ERROR_png[472]; +extern unsigned char PNG_HELP_png[492]; +extern unsigned char PNG_REFRESH_png[449]; + +extern unsigned char DEBUGGER_BP_RED_png[470]; +extern unsigned char DEBUGGER_PAUSE_png[211]; +extern unsigned char DEBUGGER_PLAY_png[306]; +extern unsigned char DEBUGGER_STEP_INTO_png[297]; +extern unsigned char DEBUGGER_STEP_OUT_png[308]; +extern unsigned char DEBUGGER_STEP_OVER_png[296]; +extern unsigned char DEBUGGER_BP_png[436]; +extern unsigned char DEBUGGER_GOTO_png[292]; + +extern unsigned char INPUT_CONNECTED_png[400]; +extern unsigned char INPUT_DISCONNECTED_png[410]; +extern unsigned char INPUT_LOW_BATTERY_png[340]; \ No newline at end of file diff --git a/src/resource/logo_1.png b/src/resource/logo_1.png new file mode 100644 index 00000000..15820a90 Binary files /dev/null and b/src/resource/logo_1.png differ diff --git a/src/resource/logo_icon.ico b/src/resource/logo_icon.ico new file mode 100644 index 00000000..6297e17e Binary files /dev/null and b/src/resource/logo_icon.ico differ diff --git a/src/resource/logo_icon.png b/src/resource/logo_icon.png new file mode 100644 index 00000000..6f655072 Binary files /dev/null and b/src/resource/logo_icon.png differ diff --git a/src/resource/logo_icon16.ico b/src/resource/logo_icon16.ico new file mode 100644 index 00000000..2160a497 Binary files /dev/null and b/src/resource/logo_icon16.ico differ diff --git a/src/resource/logo_icon2.ico b/src/resource/logo_icon2.ico new file mode 100644 index 00000000..6297e17e Binary files /dev/null and b/src/resource/logo_icon2.ico differ diff --git a/src/resource/orig1024.ico b/src/resource/orig1024.ico new file mode 100644 index 00000000..a4474c23 Binary files /dev/null and b/src/resource/orig1024.ico differ diff --git a/src/resource/output.ico b/src/resource/output.ico new file mode 100644 index 00000000..b5c83628 Binary files /dev/null and b/src/resource/output.ico differ diff --git a/src/resource/resource.h b/src/resource/resource.h new file mode 100644 index 00000000..a1134de9 --- /dev/null +++ b/src/resource/resource.h @@ -0,0 +1,19 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by cemu.rc +// +#include "Common/version.h" +#define IDI_ICON2 102 +#define IDI_ICON3 103 +#define IDR_FONTAWESOME 116 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 118 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/src/tools/ShaderCacheMerger.cpp b/src/tools/ShaderCacheMerger.cpp new file mode 100644 index 00000000..3cf7420d --- /dev/null +++ b/src/tools/ShaderCacheMerger.cpp @@ -0,0 +1,140 @@ +#include "Cemu/FileCache/FileCache.h" +#include "Cafe/HW/Latte/Core/LatteShaderCache.h" +#include + +void MergeShaderCacheFile(std::string fileName) +{ + // parse titleId from fileName + uint64 titleId = 0; + if (sscanf(fileName.c_str(), "%I64x", &titleId) != 1) + return; + + const std::string mainPath = "shaderCache/transferable/" + fileName; + const std::string mergeSourcePath = "shaderCache/transferable/merge/" + fileName; + if (!fs::exists(mainPath) || !fs::exists(mergeSourcePath)) + return; + // open both caches + FileCache* mainCache = FileCache::Open(boost::nowide::widen(mainPath)); + FileCache* sourceCache = FileCache::Open(boost::nowide::widen(mergeSourcePath)); + if (!mainCache && !sourceCache) + { + printf("Failed to open cache files for %s\n", fileName.c_str()); + if (mainCache) + delete mainCache; + if (sourceCache) + delete sourceCache; + return; + } + // begin merging + printf("Merging shaders %I64x...", titleId); + uint32 numMergedEntries = 0; // number of files added to the main cache file + for (sint32 i = 0; i < sourceCache->GetFileCount(); i++) + { + uint64 name1, name2; + std::vector fileData; + if (!sourceCache->GetFileByIndex(i, &name1, &name2, fileData)) + continue; + std::vector existingfileData; + if (mainCache->HasFile({ name1, name2 })) + continue; + mainCache->AddFile({ name1, name2 }, fileData.data(), (sint32)fileData.size()); + numMergedEntries++; + } + printf(" -> Added %d new shaders for a total of %d\n", numMergedEntries, mainCache->GetFileCount()); + + delete mainCache; + delete sourceCache; + + fs::remove(mergeSourcePath); +} + +void MergePipelineCacheFile(std::string fileName) +{ + // parse titleId from fileName + uint64 titleId = 0; + if (sscanf(fileName.c_str(), "%I64x", &titleId) != 1) + return; + + const std::string mainPath = "shaderCache/transferable/" + fileName; + const std::string mergeSourcePath = "shaderCache/transferable/merge/" + fileName; + if (!fs::exists(mainPath) || !fs::exists(mergeSourcePath)) + return; + // open both caches + const uint32 cacheFileVersion = 1; + FileCache* mainCache = FileCache::Open(boost::nowide::widen(mainPath)); + FileCache* sourceCache = FileCache::Open(boost::nowide::widen(mergeSourcePath)); + if (!mainCache && !sourceCache) + { + printf("Failed to open cache files for %s\n", fileName.c_str()); + if (mainCache) + delete mainCache; + if (sourceCache) + delete sourceCache; + return; + } + // begin merging + printf("Merging pipelines %I64x...", titleId); + uint32 numMergedEntries = 0; // number of files added to the main cache file + for (sint32 i = 0; i < sourceCache->GetFileCount(); i++) + { + uint64 name1, name2; + std::vector fileData; + if (!sourceCache->GetFileByIndex(i, &name1, &name2, fileData)) + continue; + std::vector existingfileData; + if (mainCache->HasFile({ name1, name2 })) + continue; + mainCache->AddFile({ name1, name2 }, fileData.data(), (sint32)fileData.size()); + numMergedEntries++; + } + printf(" -> Added %d new pipelines for a total of %d\n", numMergedEntries, mainCache->GetFileCount()); + + delete mainCache; + delete sourceCache; + + fs::remove(mergeSourcePath); +} + +void MergeShaderAndPipelineCacheFiles() +{ + printf("Scanning for shader cache files to merge...\n"); + for (const auto& it : fs::directory_iterator("shaderCache/transferable/")) + { + if (fs::is_directory(it)) + continue; + auto filename = it.path().filename().generic_string(); + if (std::regex_match(filename, std::regex("^[0-9a-fA-F]{16}(?:_shaders.bin)"))) + MergeShaderCacheFile(filename); + } + printf("\nScanning for pipeline cache files to merge...\n"); + for (const auto& it : fs::directory_iterator("shaderCache/transferable/")) + { + if (fs::is_directory(it)) + continue; + auto filename = it.path().filename().generic_string(); + if (std::regex_match(filename, std::regex("^[0-9a-fA-F]{16}(?:_vkpipeline.bin)"))) + MergePipelineCacheFile(filename); + } +} + +void ToolShaderCacheMerger() +{ + printf("****************************************************\n"); + printf("****************************************************\n"); + printf("Shader and pipeline cache merging tool\n"); + printf("This tool will merge any shader caches placed in:\n"); + printf("shaderCache/transferable/merge/\n"); + printf("into the files of the same name in:\n"); + printf("shaderCache/transferable/\n"); + printf("****************************************************\n"); + printf("****************************************************\n"); + printf("\n"); + + MergeShaderAndPipelineCacheFiles(); + + + printf("done!\n"); + while (true) + Sleep(1000); + exit(0); +} diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt new file mode 100644 index 00000000..d31541ff --- /dev/null +++ b/src/util/CMakeLists.txt @@ -0,0 +1,15 @@ +project(CemuUtil) + +include_directories(".") + +file(GLOB_RECURSE CPP_FILES *.cpp) +file(GLOB_RECURSE H_FILES *.h) + +add_library(CemuUtil ${CPP_FILES} ${H_FILES}) + +set_property(TARGET CemuUtil PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + +target_precompile_headers(CemuUtil PRIVATE ../Common/precompiled.h) + +target_link_libraries(CemuUtil CemuCommon CemuConfig) +target_include_directories(CemuUtil PRIVATE ../) \ No newline at end of file diff --git a/src/util/ChunkedHeap/ChunkedHeap.h b/src/util/ChunkedHeap/ChunkedHeap.h new file mode 100644 index 00000000..d2a0a036 --- /dev/null +++ b/src/util/ChunkedHeap/ChunkedHeap.h @@ -0,0 +1,641 @@ +#pragma once + +struct CHAddr +{ + uint32 offset; + uint32 chunkIndex; + + CHAddr(uint32 _offset, uint32 _chunkIndex) : offset(_offset), chunkIndex(_chunkIndex) {}; + CHAddr() : offset(0xFFFFFFFF), chunkIndex(0xFFFFFFFF) {}; + + bool isValid() { return chunkIndex != 0xFFFFFFFF; }; + static CHAddr getInvalid() { return CHAddr(0xFFFFFFFF, 0xFFFFFFFF); }; +}; + +class ChunkedHeap +{ + struct allocRange_t + { + allocRange_t* nextFree{}; + allocRange_t* prevFree{}; + allocRange_t* prevOrdered{}; + allocRange_t* nextOrdered{}; + uint32 offset; + uint32 chunkIndex; + uint32 size; + bool isFree; + allocRange_t(uint32 _offset, uint32 _chunkIndex, uint32 _size, bool _isFree) : offset(_offset), chunkIndex(_chunkIndex), size(_size), isFree(_isFree), nextFree(nullptr) {}; + }; + + struct chunk_t + { + std::unordered_map map_allocatedRange; + }; + +public: + ChunkedHeap() + { + } + + CHAddr alloc(uint32 size, uint32 alignment = 4) + { + return _alloc(size, alignment); + } + + void free(CHAddr addr) + { + _free(addr); + } + + virtual uint32 allocateNewChunk(uint32 chunkIndex, uint32 minimumAllocationSize) + { + return 0; + } + +private: + unsigned ulog2(uint32 v) + { + static const unsigned MUL_DE_BRUIJN_BIT[] = + { + 0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30, + 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31 + }; + + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + + return MUL_DE_BRUIJN_BIT[(v * 0x07C4ACDDu) >> 27]; + } + + void trackFreeRange(allocRange_t* range) + { + // get index of msb + cemu_assert_debug(range->size != 0); // size of zero is not allowed + uint32 bucketIndex = ulog2(range->size); + range->nextFree = bucketFreeRange[bucketIndex]; + if (bucketFreeRange[bucketIndex]) + bucketFreeRange[bucketIndex]->prevFree = range; + range->prevFree = nullptr; + bucketFreeRange[bucketIndex] = range; + } + + void forgetFreeRange(allocRange_t* range, uint32 bucketIndex) + { + allocRange_t* prevRange = range->prevFree; + allocRange_t* nextRange = range->nextFree; + if (prevRange) + { + prevRange->nextFree = nextRange; + if (nextRange) + nextRange->prevFree = prevRange; + } + else + { + if (bucketFreeRange[bucketIndex] != range) + assert_dbg(); + bucketFreeRange[bucketIndex] = nextRange; + if (nextRange) + nextRange->prevFree = nullptr; + } + } + + bool allocateChunk(uint32 minimumAllocationSize) + { + uint32 chunkIndex = (uint32)list_chunks.size(); + list_chunks.emplace_back(new chunk_t()); + uint32 chunkSize = allocateNewChunk(chunkIndex, minimumAllocationSize); + if (chunkSize == 0) + return false; + allocRange_t* range = new allocRange_t(0, chunkIndex, chunkSize, true); + trackFreeRange(range); + numHeapBytes += chunkSize; + return true; + } + + void _allocFrom(allocRange_t* range, uint32 bucketIndex, uint32 allocOffset, uint32 allocSize) + { + // remove the range from the chain of free ranges + forgetFreeRange(range, bucketIndex); + // split head, allocation and tail into separate ranges + if (allocOffset > range->offset) + { + // alignment padding -> create free range + allocRange_t* head = new allocRange_t(range->offset, range->chunkIndex, allocOffset - range->offset, true); + trackFreeRange(head); + if (range->prevOrdered) + range->prevOrdered->nextOrdered = head; + head->prevOrdered = range->prevOrdered; + head->nextOrdered = range; + range->prevOrdered = head; + } + if ((allocOffset + allocSize) < (range->offset + range->size)) // todo - create only if it's more than a couple of bytes? + { + // tail -> create free range + allocRange_t* tail = new allocRange_t((allocOffset + allocSize), range->chunkIndex, (range->offset + range->size) - (allocOffset + allocSize), true); + trackFreeRange(tail); + if (range->nextOrdered) + range->nextOrdered->prevOrdered = tail; + tail->prevOrdered = range; + tail->nextOrdered = range->nextOrdered; + range->nextOrdered = tail; + } + range->offset = allocOffset; + range->size = allocSize; + range->isFree = false; + } + + CHAddr _alloc(uint32 size, uint32 alignment) + { + // find smallest bucket to scan + uint32 alignmentM1 = alignment - 1; + uint32 bucketIndex = ulog2(size); + while (bucketIndex < 32) + { + allocRange_t* range = bucketFreeRange[bucketIndex]; + while (range) + { + if (range->size >= size) + { + // verify if aligned allocation fits + uint32 alignedOffset = (range->offset + alignmentM1) & ~alignmentM1; + uint32 alignmentLoss = alignedOffset - range->offset; + if (alignmentLoss < range->size && (range->size - alignmentLoss) >= size) + { + _allocFrom(range, bucketIndex, alignedOffset, size); + list_chunks[range->chunkIndex]->map_allocatedRange.emplace(alignedOffset, range); + numAllocatedBytes += size; + return CHAddr(alignedOffset, range->chunkIndex); + } + } + range = range->nextFree; + } + bucketIndex++; // try higher bucket + } + if(allocationLimitReached) + return CHAddr(0xFFFFFFFF, 0xFFFFFFFF); + if (!allocateChunk(size)) + { + allocationLimitReached = true; + return CHAddr(0xFFFFFFFF, 0xFFFFFFFF); + } + return _alloc(size, alignment); + } + + void _free(CHAddr addr) + { + auto it = list_chunks[addr.chunkIndex]->map_allocatedRange.find(addr.offset); + if (it == list_chunks[addr.chunkIndex]->map_allocatedRange.end()) + { + forceLog_printf("Internal heap error. %08x %08x", addr.chunkIndex, addr.offset); + forceLog_printf("Debug info:"); + for (auto& rangeItr : list_chunks[addr.chunkIndex]->map_allocatedRange) + { + forceLog_printf("%08x %08x", rangeItr.second->offset, rangeItr.second->size); + } + return; + } + + allocRange_t* range = it->second; + numAllocatedBytes -= it->second->size; + list_chunks[range->chunkIndex]->map_allocatedRange.erase(it); + // try merge left or right + allocRange_t* prevRange = range->prevOrdered; + allocRange_t* nextRange = range->nextOrdered; + if (prevRange && prevRange->isFree) + { + if (nextRange && nextRange->isFree) + { + forgetFreeRange(nextRange, ulog2(nextRange->size)); + uint32 newSize = (nextRange->offset + nextRange->size) - prevRange->offset; + prevRange->nextOrdered = nextRange->nextOrdered; + if (nextRange->nextOrdered) + nextRange->nextOrdered->prevOrdered = prevRange; + forgetFreeRange(prevRange, ulog2(prevRange->size)); + prevRange->size = newSize; + trackFreeRange(prevRange); + delete range; + delete nextRange; + } + else + { + uint32 newSize = (range->offset + range->size) - prevRange->offset; + prevRange->nextOrdered = nextRange; + if (nextRange) + nextRange->prevOrdered = prevRange; + forgetFreeRange(prevRange, ulog2(prevRange->size)); + prevRange->size = newSize; + trackFreeRange(prevRange); + delete range; + } + } + else if (nextRange && nextRange->isFree) + { + uint32 newOffset = range->offset; + uint32 newSize = (nextRange->offset + nextRange->size) - newOffset; + forgetFreeRange(nextRange, ulog2(nextRange->size)); + nextRange->offset = newOffset; + nextRange->size = newSize; + if (range->prevOrdered) + range->prevOrdered->nextOrdered = nextRange; + nextRange->prevOrdered = range->prevOrdered; + trackFreeRange(nextRange); + delete range; + } + else + { + range->isFree = true; + trackFreeRange(range); + } + } + + void verifyHeap() + { + // check for collisions within bucketFreeRange + struct availableRange_t + { + uint32 chunkIndex; + uint32 offset; + uint32 size; + }; + + std::vector availRanges; + + for (uint32 i = 0; i < 32; i++) + { + allocRange_t* ar = bucketFreeRange[i]; + while (ar) + { + availableRange_t dbgRange; + dbgRange.chunkIndex = ar->chunkIndex; + dbgRange.offset = ar->offset; + dbgRange.size = ar->size; + + for (auto& itr : availRanges) + { + if (itr.chunkIndex != dbgRange.chunkIndex) + continue; + if (itr.offset < (dbgRange.offset + dbgRange.size) && (itr.offset + itr.size) >(dbgRange.offset)) + assert_dbg(); + } + + availRanges.emplace_back(dbgRange); + + ar = ar->nextFree; + } + } + + } + +private: + std::vector list_chunks; + allocRange_t* bucketFreeRange[32]{}; + bool allocationLimitReached = false; + +public: + // statistics + uint32 numHeapBytes{}; // total size of the heap + uint32 numAllocatedBytes{}; +}; + +class VGenericHeap +{ +public: + virtual void* alloc(uint32 size, uint32 alignment) = 0; + virtual void free(void* addr) = 0; +}; + +class VHeap : public VGenericHeap +{ + struct allocRange_t + { + allocRange_t* nextFree{}; + allocRange_t* prevFree{}; + allocRange_t* prevOrdered{}; + allocRange_t* nextOrdered{}; + uint32 offset; + uint32 size; + bool isFree; + allocRange_t(uint32 _offset, uint32 _size, bool _isFree) : offset(_offset), size(_size), isFree(_isFree), nextFree(nullptr) {}; + }; + + struct chunk_t + { + std::unordered_map map_allocatedRange; + }; + +public: + VHeap(void* heapBase, uint32 heapSize) : m_heapBase((uint8*)heapBase), m_heapSize(heapSize) + { + allocRange_t* range = new allocRange_t(0, heapSize, true); + trackFreeRange(range); + } + + ~VHeap() + { + for (auto freeRange : bucketFreeRange) + { + while (freeRange) + { + auto temp = freeRange; + freeRange = freeRange->nextFree; + delete temp; + } + } + } + + void setHeapBase(void* heapBase) + { + cemu_assert_debug(map_allocatedRange.empty()); // heap base can only be changed when there are no active allocations + m_heapBase = (uint8*)heapBase; + } + + void* alloc(uint32 size, uint32 alignment = 4) override + { + cemu_assert_debug(m_heapBase != nullptr); // if this is null, we cant use alloc() == nullptr to determine if an allocation failed + uint32 allocOffset = 0; + bool r = _alloc(size, alignment, allocOffset); + if (!r) + return nullptr; + return m_heapBase + allocOffset; + } + + void free(void* addr) override + { + _free((uint32)((uint8*)addr - (uint8*)m_heapBase)); + } + + bool allocOffset(uint32 size, uint32 alignment, uint32& offsetOut) + { + uint32 allocOffset = 0; + bool r = _alloc(size, alignment, allocOffset); + if (!r) + return false; + offsetOut = allocOffset; + return true; + } + + void freeOffset(uint32 offset) + { + _free((uint32)offset); + } + + uint32 getAllocationSizeFromAddr(void* addr) + { + uint32 addrOffset = (uint32)((uint8*)addr - m_heapBase); + auto it = map_allocatedRange.find(addrOffset); + if (it == map_allocatedRange.end()) + assert_dbg(); + return it->second->size; + } + + bool hasAllocations() + { + return !map_allocatedRange.empty(); + } + + void getStats(uint32& heapSize, uint32& allocationSize, uint32& allocNum) + { + heapSize = m_heapSize; + allocationSize = m_statsMemAllocated; + allocNum = (uint32)map_allocatedRange.size(); + } + +private: + unsigned ulog2(uint32 v) + { + static const unsigned MUL_DE_BRUIJN_BIT[] = + { + 0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30, + 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31 + }; + + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + + return MUL_DE_BRUIJN_BIT[(v * 0x07C4ACDDu) >> 27]; + } + + void trackFreeRange(allocRange_t* range) + { + // get index of msb + if (range->size == 0) + assert_dbg(); // not allowed + uint32 bucketIndex = ulog2(range->size); + range->nextFree = bucketFreeRange[bucketIndex]; + if (bucketFreeRange[bucketIndex]) + bucketFreeRange[bucketIndex]->prevFree = range; + range->prevFree = nullptr; + bucketFreeRange[bucketIndex] = range; + } + + void forgetFreeRange(allocRange_t* range, uint32 bucketIndex) + { + allocRange_t* prevRange = range->prevFree; + allocRange_t* nextRange = range->nextFree; + if (prevRange) + { + prevRange->nextFree = nextRange; + if (nextRange) + nextRange->prevFree = prevRange; + } + else + { + if (bucketFreeRange[bucketIndex] != range) + assert_dbg(); + bucketFreeRange[bucketIndex] = nextRange; + if (nextRange) + nextRange->prevFree = nullptr; + } + } + + void _allocFrom(allocRange_t* range, uint32 bucketIndex, uint32 allocOffset, uint32 allocSize) + { + // remove the range from the chain of free ranges + forgetFreeRange(range, bucketIndex); + // split head, allocation and tail into separate ranges + if (allocOffset > range->offset) + { + // alignment padding -> create free range + allocRange_t* head = new allocRange_t(range->offset, allocOffset - range->offset, true); + trackFreeRange(head); + if (range->prevOrdered) + range->prevOrdered->nextOrdered = head; + head->prevOrdered = range->prevOrdered; + head->nextOrdered = range; + range->prevOrdered = head; + } + if ((allocOffset + allocSize) < (range->offset + range->size)) // todo - create only if it's more than a couple of bytes? + { + // tail -> create free range + allocRange_t* tail = new allocRange_t((allocOffset + allocSize), (range->offset + range->size) - (allocOffset + allocSize), true); + trackFreeRange(tail); + if (range->nextOrdered) + range->nextOrdered->prevOrdered = tail; + tail->prevOrdered = range; + tail->nextOrdered = range->nextOrdered; + range->nextOrdered = tail; + } + range->offset = allocOffset; + range->size = allocSize; + range->isFree = false; + m_statsMemAllocated += allocSize; + } + + bool _alloc(uint32 size, uint32 alignment, uint32& allocOffsetOut) + { + // find smallest bucket to scan + uint32 alignmentM1 = alignment - 1; + uint32 bucketIndex = ulog2(size); + while (bucketIndex < 32) + { + allocRange_t* range = bucketFreeRange[bucketIndex]; + while (range) + { + if (range->size >= size) + { + // verify if aligned allocation fits + uint32 alignedOffset = (range->offset + alignmentM1) & ~alignmentM1; + uint32 alignmentLoss = alignedOffset - range->offset; + if (alignmentLoss < range->size && (range->size - alignmentLoss) >= size) + { + _allocFrom(range, bucketIndex, alignedOffset, size); + map_allocatedRange.emplace(alignedOffset, range); + allocOffsetOut = alignedOffset; + return true; + } + } + range = range->nextFree; + } + bucketIndex++; // try higher bucket + } + return false; + } + + void _free(uint32 addrOffset) + { + auto it = map_allocatedRange.find(addrOffset); + if (it == map_allocatedRange.end()) + assert_dbg(); + allocRange_t* range = it->second; + map_allocatedRange.erase(it); + m_statsMemAllocated -= range->size; + // try merge left or right + allocRange_t* prevRange = range->prevOrdered; + allocRange_t* nextRange = range->nextOrdered; + if (prevRange && prevRange->isFree) + { + if (nextRange && nextRange->isFree) + { + forgetFreeRange(nextRange, ulog2(nextRange->size)); + uint32 newSize = (nextRange->offset + nextRange->size) - prevRange->offset; + prevRange->nextOrdered = nextRange->nextOrdered; + if (nextRange->nextOrdered) + nextRange->nextOrdered->prevOrdered = prevRange; + forgetFreeRange(prevRange, ulog2(prevRange->size)); + prevRange->size = newSize; + trackFreeRange(prevRange); + delete range; + delete nextRange; + } + else + { + uint32 newSize = (range->offset + range->size) - prevRange->offset; + prevRange->nextOrdered = nextRange; + if (nextRange) + nextRange->prevOrdered = prevRange; + forgetFreeRange(prevRange, ulog2(prevRange->size)); + prevRange->size = newSize; + trackFreeRange(prevRange); + delete range; + } + } + else if (nextRange && nextRange->isFree) + { + uint32 newOffset = range->offset; + uint32 newSize = (nextRange->offset + nextRange->size) - newOffset; + forgetFreeRange(nextRange, ulog2(nextRange->size)); + nextRange->offset = newOffset; + nextRange->size = newSize; + if (range->prevOrdered) + range->prevOrdered->nextOrdered = nextRange; + nextRange->prevOrdered = range->prevOrdered; + trackFreeRange(nextRange); + delete range; + } + else + { + range->isFree = true; + trackFreeRange(range); + } + } + +private: + allocRange_t* bucketFreeRange[32]{}; + std::unordered_map map_allocatedRange; + uint8* m_heapBase; + const uint32 m_heapSize; + uint32 m_statsMemAllocated{ 0 }; +}; + +template +class ChunkedFlatAllocator +{ +public: + void setBaseAllocator(VGenericHeap* baseHeap) + { + m_currentBaseAllocator = baseHeap; + } + + void* alloc(uint32 size, uint32 alignment = 4) + { + if (m_currentBlockPtr) + { + m_currentBlockOffset = (m_currentBlockOffset + alignment - 1) & ~(alignment - 1); + if ((m_currentBlockOffset+size) <= TChunkSize) + { + void* allocPtr = m_currentBlockPtr + m_currentBlockOffset; + m_currentBlockOffset += size; + return allocPtr; + } + } + allocateAdditionalChunk(); + return alloc(size, alignment); + } + + void releaseAll() + { + for (auto it : m_allocatedBlocks) + m_currentBaseAllocator->free(it); + m_allocatedBlocks.clear(); + m_currentBlockPtr = nullptr; + m_currentBlockOffset = 0; + } + + void forEachBlock(void(*funcCb)(void* mem, uint32 size)) + { + for (auto it : m_allocatedBlocks) + funcCb(it, TChunkSize); + } + + uint32 getCurrentBlockOffset() const { return m_currentBlockOffset; } + uint8* getCurrentBlockPtr() const { return m_currentBlockPtr; } + +private: + void allocateAdditionalChunk() + { + m_currentBlockPtr = (uint8*)m_currentBaseAllocator->alloc(TChunkSize, 256); + m_currentBlockOffset = 0; + m_allocatedBlocks.emplace_back(m_currentBlockPtr); + } + + VGenericHeap* m_currentBaseAllocator{}; + uint8* m_currentBlockPtr{}; + uint32 m_currentBlockOffset{}; + std::vector m_allocatedBlocks; +}; \ No newline at end of file diff --git a/src/util/DXGIWrapper/DXGIWrapper.h b/src/util/DXGIWrapper/DXGIWrapper.h new file mode 100644 index 00000000..54f2454d --- /dev/null +++ b/src/util/DXGIWrapper/DXGIWrapper.h @@ -0,0 +1,84 @@ +#pragma once + +#include +//#include + +class DXGIWrapper +{ +public: + DXGIWrapper() + : DXGIWrapper(nullptr) + {} + + DXGIWrapper(uint8* deviceLUID) + { + m_moduleHandle = LoadLibraryA("dxgi.dll"); + if (!m_moduleHandle) + throw std::runtime_error("can't load dxgi module"); + + const auto pCreateDXGIFactory1 = (decltype(&CreateDXGIFactory1))GetProcAddress(m_moduleHandle, "CreateDXGIFactory1"); + if (!pCreateDXGIFactory1) + { + FreeLibrary(m_moduleHandle); + throw std::runtime_error("can't find CreateDXGIFactory1 in dxgi module"); + } + + IDXGIFactory1* dxgiFactory = nullptr; + pCreateDXGIFactory1(IID_PPV_ARGS(&dxgiFactory)); + + IDXGIAdapter1* tmpDxgiAdapter = nullptr; + UINT adapterIndex = 0; + while (dxgiFactory->EnumAdapters1(adapterIndex, &tmpDxgiAdapter) != DXGI_ERROR_NOT_FOUND) + { + DXGI_ADAPTER_DESC1 desc; + tmpDxgiAdapter->GetDesc1(&desc); + + if (deviceLUID == nullptr || memcmp(&desc.AdapterLuid, deviceLUID, sizeof(LUID)) == 0) + { + tmpDxgiAdapter->QueryInterface(IID_PPV_ARGS(&m_dxgiAdapter)); + tmpDxgiAdapter->Release(); + break; + } + + tmpDxgiAdapter->Release(); + ++adapterIndex; + } + + dxgiFactory->Release(); + + if (!m_dxgiAdapter) + { + Cleanup(); + throw std::runtime_error("can't create dxgi adapter"); + } + } + + ~DXGIWrapper() + { + Cleanup(); + } + + bool QueryVideoMemoryInfo(DXGI_QUERY_VIDEO_MEMORY_INFO& info) const + { + return m_dxgiAdapter->QueryVideoMemoryInfo(0, DXGI_MEMORY_SEGMENT_GROUP_LOCAL, &info) == S_OK; + } + +private: + HMODULE m_moduleHandle = nullptr; + IDXGIAdapter3* m_dxgiAdapter = nullptr; + + void Cleanup() + { + if (m_dxgiAdapter) + { + m_dxgiAdapter->Release(); + m_dxgiAdapter = nullptr; + } + + if (m_moduleHandle) + { + FreeLibrary(m_moduleHandle); + m_moduleHandle = nullptr; + } + } +}; \ No newline at end of file diff --git a/src/util/EventService.h b/src/util/EventService.h new file mode 100644 index 00000000..b1a3e750 --- /dev/null +++ b/src/util/EventService.h @@ -0,0 +1,66 @@ +#pragma once + +#include "util/helpers/Singleton.h" + +#include +#include + +enum class Events : int32_t +{ + ControllerChanged, +}; + +using ControllerChangedFunc = void(void); + +class EventService : public Singleton +{ + friend class Singleton; + EventService() = default; + +public: + template + boost::signals2::connection connect(TFunc function, TClass thisptr) + { + using namespace boost::placeholders; + if constexpr (event == Events::ControllerChanged) + return m_controller_changed_signal.connect(boost::bind(function, thisptr)); + else + { + cemu_assert_suspicious(); + } + } + + template + void disconnect(const boost::signals2::connection& slot) + { + using namespace boost::placeholders; + if constexpr (event == Events::ControllerChanged) + m_controller_changed_signal.disconnect(slot); + else + { + cemu_assert_suspicious(); + } + } + + template + void signal(TArgs&&... args) + { + try + { + if constexpr (event == Events::ControllerChanged) + m_controller_changed_signal(std::forward(args)...); + else + { + cemu_assert_suspicious(); + } + } + catch (const std::exception& ex) + { + cemuLog_force("error when signaling {}: {}", event, ex.what()); + } + } + +private: + boost::signals2::signal m_controller_changed_signal; + +}; diff --git a/src/util/Fiber/Fiber.h b/src/util/Fiber/Fiber.h new file mode 100644 index 00000000..75a94d51 --- /dev/null +++ b/src/util/Fiber/Fiber.h @@ -0,0 +1,22 @@ +#pragma once + +#if BOOST_OS_WINDOWS > 0 + +#endif + +class Fiber +{ +public: + Fiber(void(*FiberEntryPoint)(void* userParam), void* userParam, void* privateData); + ~Fiber(); + + static Fiber* PrepareCurrentThread(void* privateData = nullptr); + static void Switch(Fiber& targetFiber); + static void* GetFiberPrivateData(); +private: + Fiber(void* privateData); // fiber from current thread + + void* m_implData{nullptr}; + void* m_privateData; + void* m_stackPtr{ nullptr }; +}; \ No newline at end of file diff --git a/src/util/Fiber/FiberUnix.cpp b/src/util/Fiber/FiberUnix.cpp new file mode 100644 index 00000000..e44400b9 --- /dev/null +++ b/src/util/Fiber/FiberUnix.cpp @@ -0,0 +1,56 @@ +#include "Fiber.h" +#if BOOST_OS_LINUX +#include + +thread_local Fiber* sCurrentFiber{}; + +Fiber::Fiber(void(*FiberEntryPoint)(void* userParam), void* userParam, void* privateData) : m_privateData(privateData) +{ + ucontext_t* ctx = (ucontext_t*)malloc(sizeof(ucontext_t)); + + const size_t stackSize = 2 * 1024 * 1024; + m_stackPtr = malloc(stackSize); + + getcontext(ctx); + ctx->uc_stack.ss_sp = m_stackPtr; + ctx->uc_stack.ss_size = stackSize; + ctx->uc_link = &ctx[0]; + makecontext(ctx, (void(*)())FiberEntryPoint, 1, userParam); + this->m_implData = (void*)ctx; +} + +Fiber::Fiber(void* privateData) : m_privateData(privateData) +{ + ucontext_t* ctx = (ucontext_t*)malloc(sizeof(ucontext_t)); + getcontext(ctx); + this->m_implData = (void*)ctx; + m_stackPtr = nullptr; +} + +Fiber::~Fiber() +{ + if(m_stackPtr) + free(m_stackPtr); + free(m_implData); +} + +Fiber* Fiber::PrepareCurrentThread(void* privateData) +{ + cemu_assert_debug(sCurrentFiber == nullptr); + sCurrentFiber = new Fiber(privateData); + return sCurrentFiber; +} + +void Fiber::Switch(Fiber& targetFiber) +{ + Fiber* leavingFiber = sCurrentFiber; + sCurrentFiber = &targetFiber; + swapcontext((ucontext_t*)(leavingFiber->m_implData), (ucontext_t*)(targetFiber.m_implData)); +} + +void* Fiber::GetFiberPrivateData() +{ + return sCurrentFiber->m_privateData; +} + +#endif \ No newline at end of file diff --git a/src/util/Fiber/FiberWin.cpp b/src/util/Fiber/FiberWin.cpp new file mode 100644 index 00000000..af7df700 --- /dev/null +++ b/src/util/Fiber/FiberWin.cpp @@ -0,0 +1,43 @@ +#include "Fiber.h" +#if BOOST_OS_WINDOWS +#include + +thread_local Fiber* sCurrentFiber{}; + +Fiber::Fiber(void(*FiberEntryPoint)(void* userParam), void* userParam, void* privateData) : m_privateData(privateData) +{ + HANDLE fiberHandle = CreateFiber(2 * 1024 * 1024, (LPFIBER_START_ROUTINE)FiberEntryPoint, userParam); + this->m_implData = (void*)fiberHandle; +} + +Fiber::Fiber(void* privateData) : m_privateData(privateData) +{ + this->m_implData = (void*)ConvertThreadToFiber(nullptr); + this->m_stackPtr = nullptr; +} + +Fiber::~Fiber() +{ + DeleteFiber((HANDLE)m_implData); +} + +Fiber* Fiber::PrepareCurrentThread(void* privateData) +{ + cemu_assert_debug(sCurrentFiber == nullptr); // thread already prepared + Fiber* currentFiber = new Fiber(privateData); + sCurrentFiber = currentFiber; + return currentFiber; +} + +void Fiber::Switch(Fiber& targetFiber) +{ + sCurrentFiber = &targetFiber; + SwitchToFiber((HANDLE)targetFiber.m_implData); +} + +void* Fiber::GetFiberPrivateData() +{ + return sCurrentFiber->m_privateData; +} + +#endif \ No newline at end of file diff --git a/src/util/ImageWriter/bmp.h b/src/util/ImageWriter/bmp.h new file mode 100644 index 00000000..46a9fb48 --- /dev/null +++ b/src/util/ImageWriter/bmp.h @@ -0,0 +1,127 @@ +#include "Common/filestream.h" + +static void _bmp_write(FileStream* fs, sint32 width, sint32 height, uint32 bits, void* pixelData) +{ + BITMAPFILEHEADER bmp_fh; + BITMAPINFOHEADER bmp_ih; + + bmp_fh.bfType = 0x4d42; + bmp_fh.bfSize = 0; + bmp_fh.bfReserved1 = 0; + bmp_fh.bfReserved2 = 0; + bmp_fh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); + + bmp_ih.biSize = sizeof(bmp_ih); + bmp_ih.biWidth = width; + bmp_ih.biHeight = height; + bmp_ih.biPlanes = 1; + bmp_ih.biBitCount = bits; + bmp_ih.biCompression = 0; + bmp_ih.biSizeImage = 0; + bmp_ih.biXPelsPerMeter = 0; + bmp_ih.biYPelsPerMeter = 0; + bmp_ih.biClrUsed = 0; + bmp_ih.biClrImportant = 0; + + sint32 rowPitch = (width * bits / 8); + rowPitch = (rowPitch + 3)&~3; + + uint8 padding[4] = { 0 }; + sint32 paddingLength = rowPitch - (width * bits / 8); + + bmp_ih.biSize = sizeof(BITMAPINFOHEADER); + bmp_fh.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + rowPitch * height; + + fs->writeData(&bmp_fh, sizeof(BITMAPFILEHEADER)); + fs->writeData(&bmp_ih, sizeof(BITMAPINFOHEADER)); + + if (bits == 24 || bits == 32) + { + for (sint32 y = 0; y < height; y++) + { + void* rowInput = ((uint8*)pixelData) + rowPitch * (height - y - 1); + fs->writeData(rowInput, width*bits/8); + // write padding + if(paddingLength > 0) + fs->writeData(padding, paddingLength); + } + } +} + +static bool bmp_store8BitAs24(wchar_t* path, sint32 width, sint32 height, sint32 bytesPerRow, void* pixelData) +{ + FileStream* fs = FileStream::createFile(path); + if (fs == nullptr) + return false; + + uint8* pixelI = (uint8*)pixelData; + uint8* pixelRGB = (uint8*)malloc(width * height * 3); + for (sint32 y = 0; y < height; y++) + { + sint32 srcIdx = y * bytesPerRow; + for (sint32 x = 0; x < width; x++) + { + sint32 dstIdx = x + y * width; + pixelRGB[dstIdx * 3 + 0] = pixelI[srcIdx]; + pixelRGB[dstIdx * 3 + 1] = pixelI[srcIdx]; + pixelRGB[dstIdx * 3 + 2] = pixelI[srcIdx]; + srcIdx++; + } + } + _bmp_write(fs, width, height, 24, pixelRGB); + free(pixelRGB); + delete fs; + return true; +} + +static bool bmp_store16BitAs24(wchar_t* path, sint32 width, sint32 height, sint32 bytesPerRow, void* pixelData) +{ + FileStream* fs = FileStream::createFile(path); + if (fs == nullptr) + return false; + + uint8* pixelI = (uint8*)pixelData; + uint8* pixelRGB = (uint8*)malloc(width * height * 3); + for (sint32 y = 0; y < height; y++) + { + sint32 srcIdx = y * bytesPerRow; + for (sint32 x = 0; x < width; x++) + { + sint32 dstIdx = x + y * width; + pixelRGB[dstIdx * 3 + 0] = pixelI[srcIdx + 0]; + pixelRGB[dstIdx * 3 + 1] = pixelI[srcIdx + 1]; + pixelRGB[dstIdx * 3 + 2] = 0; + srcIdx += 2; + } + } + _bmp_write(fs, width, height, 24, pixelRGB); + free(pixelRGB); + delete fs; + return true; +} + +static bool bmp_store24BitAs24(wchar_t* path, sint32 width, sint32 height, sint32 bytesPerRow, void* pixelData) +{ + FileStream* fs = FileStream::createFile(path); + if (fs == nullptr) + return false; + + uint8* pixelI = (uint8*)pixelData; + uint8* pixelRGB = (uint8*)malloc(width * height * 3); + for (sint32 y = 0; y < height; y++) + { + sint32 srcIdx = y * bytesPerRow; + for (sint32 x = 0; x < width; x++) + { + sint32 dstIdx = x + y * width; + pixelRGB[dstIdx * 3 + 0] = pixelI[srcIdx + 0]; + pixelRGB[dstIdx * 3 + 1] = pixelI[srcIdx + 1]; + pixelRGB[dstIdx * 3 + 2] = pixelI[srcIdx + 2]; + srcIdx += 3; + } + } + _bmp_write(fs, width, height, 24, pixelRGB); + free(pixelRGB); + delete fs; + return true; +} \ No newline at end of file diff --git a/src/util/ImageWriter/tga.h b/src/util/ImageWriter/tga.h new file mode 100644 index 00000000..e8d51741 --- /dev/null +++ b/src/util/ImageWriter/tga.h @@ -0,0 +1,34 @@ +#include "Common/filestream.h" +#include + +static bool tga_write_rgba(wchar_t* path, sint32 width, sint32 height, uint8* pixelData) +{ + FileStream* fs = FileStream::createFile(path); + if (fs == nullptr) + return false; + + uint8_t header[18] = {0,0,2,0,0,0,0,0,0,0,0,0, (uint8)(width % 256), (uint8)(width / 256), (uint8)(height % 256), (uint8)(height / 256), 32, 0x20}; + fs->writeData(&header, sizeof(header)); + + std::vector tempPixelData; + tempPixelData.resize(width * height * 4); + + // write one row at a time + uint8* pOut = tempPixelData.data(); + for (sint32 y = 0; y < height; y++) + { + const uint8* rowIn = pixelData + y * width*4; + for (sint32 x = 0; x < width; x++) + { + pOut[0] = rowIn[2]; + pOut[1] = rowIn[1]; + pOut[2] = rowIn[0]; + pOut[3] = rowIn[3]; + pOut += 4; + rowIn += 4; + } + } + fs->writeData(tempPixelData.data(), width * height * 4); + delete fs; + return true; +} diff --git a/src/util/IniParser/IniParser.cpp b/src/util/IniParser/IniParser.cpp new file mode 100644 index 00000000..70499a1f --- /dev/null +++ b/src/util/IniParser/IniParser.cpp @@ -0,0 +1,263 @@ +#include "util/IniParser/IniParser.h" + +IniParser::IniParser(std::span iniContents, std::string_view name) : m_name(name) +{ + // we dont support utf8 but still skip the byte order mark in case the user saved the document with the wrong encoding + if (iniContents.size() >= 3 && (uint8)iniContents[0] == 0xEF && (uint8)iniContents[1] == 0xBB && (uint8)iniContents[2] == 0xBF) + iniContents = iniContents.subspan(3); + + m_iniFileData.assign(iniContents.begin(), iniContents.end()); + m_isValid = parse(); +} + +bool IniParser::ReadNextLine(std::string_view& lineString) +{ + if (m_parseOffset >= m_iniFileData.size()) + return false; + // skip \r and \n + for (; m_parseOffset < m_iniFileData.size(); m_parseOffset++) + { + char c = m_iniFileData[m_parseOffset]; + if (c == '\r' || c == '\n') + continue; + break; + } + if (m_parseOffset >= m_iniFileData.size()) + return false; + size_t lineStart = m_parseOffset; + // parse until end of line/file + for (; m_parseOffset < m_iniFileData.size(); m_parseOffset++) + { + char c = m_iniFileData[m_parseOffset]; + if (c == '\r' || c == '\n') + break; + } + size_t lineEnd = m_parseOffset; + lineString = { m_iniFileData.data() + lineStart, lineEnd - lineStart }; + return true; +} + +void IniParser::TrimWhitespaces(std::string_view& str) +{ + while (!str.empty()) + { + char c = str[0]; + if (c != ' ' && c != '\t') + break; + str.remove_prefix(1); + } + while (!str.empty()) + { + char c = str.back(); + if (c != ' ' && c != '\t') + break; + str.remove_suffix(1); + } +} + +bool IniParser::parse() +{ + sint32 lineNumber = 0; + std::string_view lineView; + while (ReadNextLine(lineView)) + { + lineNumber++; + // skip whitespaces + while (!lineView.empty()) + { + char c = lineView[0]; + if (c != ' ' && c != '\t') + break; + lineView.remove_prefix(1); + } + if (lineView.empty()) + continue; + // cut off comments (starting with # or ;) + bool isInQuote = false; + for (size_t i = 0; i < lineView.size(); i++) + { + if (lineView[i] == '\"') + isInQuote = !isInQuote; + if ((lineView[i] == '#' || lineView[i] == ';') && !isInQuote) + { + lineView.remove_suffix(lineView.size() - i); + break; + } + } + if(lineView.empty()) + continue; + // handle section headers + if (lineView[0] == '[') + { + isInQuote = false; + bool endsWithBracket = false; + for (size_t i = 1; i < lineView.size(); i++) + { + if (lineView[i] == '\"') + isInQuote = !isInQuote; + if (lineView[i] == ']') + { + lineView.remove_suffix(lineView.size() - i); + lineView.remove_prefix(1); + endsWithBracket = true; + break; + } + } + if (!endsWithBracket) + PrintWarning(lineNumber, "Section doesn't end with a ]", lineView); + StartSection(lineView, lineNumber); + continue; + } + // otherwise try to parse it as an option in the form name = value + // find and split at = character + std::string_view option_name; + std::string_view option_value; + bool invalidName = true; + for (size_t i = 0; i < lineView.size(); i++) + { + if (lineView[i] == '=') + { + option_name = lineView.substr(0, i); + option_value = lineView.substr(i+1); + invalidName = false; + break; + } + + } + if (invalidName) + { + TrimWhitespaces(lineView); + if (!lineView.empty()) + PrintWarning(lineNumber, "Not a valid section header or name-value pair", lineView); + continue; + } + // validate + TrimWhitespaces(option_name); + TrimWhitespaces(option_value); + if (option_name.empty()) + { + PrintWarning(lineNumber, "Empty option name is not allowed", lineView); + continue; + } + bool invalidCharacter = false; + for (auto& _c : option_name) + { + uint8 c = (uint8)_c; + if (c == ']' || c == '[') + { + PrintWarning(lineNumber, "Option name may not contain [ or ]", lineView); + invalidCharacter = true; + break; + } + else if (c < 32 || c > 128 || c == ' ') + { + PrintWarning(lineNumber, "Option name may only contain ANSI characters and no spaces", lineView); + invalidCharacter = true; + break; + } + } + if(invalidCharacter) + continue; + // remove quotes from value + if (!option_value.empty() && option_value.front() == '\"') + { + option_value.remove_prefix(1); + if (option_value.size() >= 2 && option_value.back() == '\"') + { + option_value.remove_suffix(1); + } + else + { + PrintWarning(lineNumber, "Option value starts with a quote character \" but does not end with one", lineView); + continue; + } + } + if (m_sectionList.empty()) + { + // no current section + PrintWarning(lineNumber, "Option defined without first defining a section", lineView); + continue; + } + // convert name to lower case + m_sectionList.back().m_optionPairs.emplace_back(option_name, option_value); + } + return true; +} + +void IniParser::StartSection(std::string_view sectionName, size_t lineNumber) +{ + m_sectionList.emplace_back(sectionName, lineNumber); +} + +bool IniParser::NextSection() +{ + if (m_currentSectionIndex == std::numeric_limits::max()) + { + m_currentSectionIndex = 0; + return m_currentSectionIndex < m_sectionList.size(); + } + if (m_currentSectionIndex >= m_sectionList.size()) + return false; + m_currentSectionIndex++; + return m_currentSectionIndex < m_sectionList.size(); +} + +std::string_view IniParser::GetCurrentSectionName() +{ + if (m_currentSectionIndex == std::numeric_limits::max() || m_currentSectionIndex >= m_sectionList.size()) + return ""; + return m_sectionList[m_currentSectionIndex].m_sectionName; +} + +size_t IniParser::GetCurrentSectionLineNumber() +{ + if (m_currentSectionIndex == std::numeric_limits::max() || m_currentSectionIndex >= m_sectionList.size()) + return 0; + return m_sectionList[m_currentSectionIndex].m_lineNumber; +} + +std::optional IniParser::FindOption(std::string_view optionName) +{ + if (m_currentSectionIndex == std::numeric_limits::max() || m_currentSectionIndex >= m_sectionList.size()) + return std::nullopt; + auto& optionPairsList = m_sectionList[m_currentSectionIndex].m_optionPairs; + for (auto& itr : optionPairsList) + { + auto& itrOptionName = itr.first; + // case insensitive ANSI string comparison + if(itrOptionName.size() != optionName.size()) + continue; + bool isMatch = true; + for (size_t i = 0; i < itrOptionName.size(); i++) + { + char c0 = itrOptionName[i]; + char c1 = optionName[i]; + if (c0 >= 'A' && c0 <= 'Z') + c0 -= ('A' - 'a'); + if (c1 >= 'A' && c1 <= 'Z') + c1 -= ('A' - 'a'); + if (c0 != c1) + { + isMatch = false; + break; + } + } + if (!isMatch) + continue; + return itr.second; + } + return std::nullopt; +} + +std::span> IniParser::GetAllOptions() +{ + if (m_currentSectionIndex == std::numeric_limits::max() || m_currentSectionIndex >= m_sectionList.size()) + return {}; + return m_sectionList[m_currentSectionIndex].m_optionPairs; +} + +void IniParser::PrintWarning(int lineNumber, std::string_view msg, std::string_view lineView) +{ + // INI logging is silenced + // cemuLog_force("File: {} Line {}: {}", m_name, lineNumber, msg); +} \ No newline at end of file diff --git a/src/util/IniParser/IniParser.h b/src/util/IniParser/IniParser.h new file mode 100644 index 00000000..3a8edf1a --- /dev/null +++ b/src/util/IniParser/IniParser.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include +#include + +class IniParser +{ +private: + class IniSection + { + public: + IniSection(std::string_view sectionName, size_t lineNumber) : m_sectionName(sectionName), m_lineNumber(lineNumber) {} + std::string_view m_sectionName; + size_t m_lineNumber; + std::vector> m_optionPairs; + }; + +public: + IniParser(std::span iniContents, std::string_view name = {}); + IniParser(std::span iniContents, std::string_view name = {}) : IniParser(std::span((char*)iniContents.data(), iniContents.size()), name) {}; + + // section and option iterating + bool NextSection(); + std::string_view GetCurrentSectionName(); + size_t GetCurrentSectionLineNumber(); + std::optional FindOption(std::string_view optionName); + std::span> GetAllOptions(); + +private: + // parsing + bool parse(); + bool ReadNextLine(std::string_view& lineString); + void TrimWhitespaces(std::string_view& str); + void StartSection(std::string_view sectionName, size_t lineNumber); + void PrintWarning(int lineNumber, std::string_view msg, std::string_view lineView); + + std::vector m_iniFileData; + std::string m_name; + bool m_isValid{ false }; + size_t m_parseOffset{ 0 }; + std::vector m_sectionList; + size_t m_currentSectionIndex{std::numeric_limits::max()}; +}; \ No newline at end of file diff --git a/src/util/MemMapper/MemMapper.h b/src/util/MemMapper/MemMapper.h new file mode 100644 index 00000000..be307384 --- /dev/null +++ b/src/util/MemMapper/MemMapper.h @@ -0,0 +1,24 @@ +#pragma once + +namespace MemMapper +{ + enum class PAGE_PERMISSION : uint32 + { + P_READ = (0x01), + P_WRITE = (0x02), + P_EXECUTE = (0x04), + // combined + P_NONE = 0, + P_RW = (0x03), + P_RWX = (0x07) + }; + DEFINE_ENUM_FLAG_OPERATORS(PAGE_PERMISSION); + + size_t GetPageSize(); + + void* ReserveMemory(void* baseAddr, size_t size, PAGE_PERMISSION permissionFlags); + void FreeReservation(void* baseAddr, size_t size); + + void* AllocateMemory(void* baseAddr, size_t size, PAGE_PERMISSION permissionFlags, bool fromReservation = false); + void FreeMemory(void* baseAddr, size_t size, bool fromReservation = false); +}; \ No newline at end of file diff --git a/src/util/MemMapper/MemMapperUnix.cpp b/src/util/MemMapper/MemMapperUnix.cpp new file mode 100644 index 00000000..71e5de95 --- /dev/null +++ b/src/util/MemMapper/MemMapperUnix.cpp @@ -0,0 +1,69 @@ +#include "util/MemMapper/MemMapper.h" + +#if BOOST_OS_LINUX > 0 +#include +#include + +namespace MemMapper +{ + const size_t sPageSize{ []() + { + return (size_t)getpagesize(); + }() + }; + + size_t GetPageSize() + { + return sPageSize; + } + + int GetProt(PAGE_PERMISSION permissionFlags) + { + int p = 0; + if (HAS_FLAG(permissionFlags, PAGE_PERMISSION::P_READ) && HAS_FLAG(permissionFlags, PAGE_PERMISSION::P_WRITE) && HAS_FLAG(permissionFlags, PAGE_PERMISSION::P_EXECUTE)) + p = PROT_READ | PROT_WRITE | PROT_EXEC; + else if (HAS_FLAG(permissionFlags, PAGE_PERMISSION::P_READ) && HAS_FLAG(permissionFlags, PAGE_PERMISSION::P_WRITE) && !HAS_FLAG(permissionFlags, PAGE_PERMISSION::P_EXECUTE)) + p = PROT_READ | PROT_WRITE; + else if (HAS_FLAG(permissionFlags, PAGE_PERMISSION::P_READ) && !HAS_FLAG(permissionFlags, PAGE_PERMISSION::P_WRITE) && !HAS_FLAG(permissionFlags, PAGE_PERMISSION::P_EXECUTE)) + p = PROT_READ; + else + cemu_assert_unimplemented(); + return p; + } + + void* ReserveMemory(void* baseAddr, size_t size, PAGE_PERMISSION permissionFlags) + { + return mmap(baseAddr, size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + } + + void FreeReservation(void* baseAddr, size_t size) + { + munmap(baseAddr, size); + } + + void* AllocateMemory(void* baseAddr, size_t size, PAGE_PERMISSION permissionFlags, bool fromReservation) + { + void* r; + if(fromReservation) + { + if( mprotect(baseAddr, size, GetProt(permissionFlags)) == 0 ) + r = baseAddr; + else + r = nullptr; + } + else + r = mmap(baseAddr, size, GetProt(permissionFlags), MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + return r; + } + + void FreeMemory(void* baseAddr, size_t size, bool fromReservation) + { + if (fromReservation) + mprotect(baseAddr, size, PROT_NONE); + else + munmap(baseAddr, size); + } + +}; + +#endif \ No newline at end of file diff --git a/src/util/MemMapper/MemMapperWin.cpp b/src/util/MemMapper/MemMapperWin.cpp new file mode 100644 index 00000000..6606aec4 --- /dev/null +++ b/src/util/MemMapper/MemMapperWin.cpp @@ -0,0 +1,67 @@ +#include "util/MemMapper/MemMapper.h" + +#if BOOST_OS_WINDOWS > 0 + +#include + +namespace MemMapper +{ + const size_t sPageSize{ []() + { + SYSTEM_INFO si; + GetSystemInfo(&si); + return (size_t)si.dwPageSize; + }() + }; + + size_t GetPageSize() + { + return sPageSize; + } + + DWORD GetPageProtection(PAGE_PERMISSION permissionFlags) + { + DWORD p = 0; + if (HAS_FLAG(permissionFlags, PAGE_PERMISSION::P_READ) && HAS_FLAG(permissionFlags, PAGE_PERMISSION::P_WRITE) && HAS_FLAG(permissionFlags, PAGE_PERMISSION::P_EXECUTE)) + p = PAGE_EXECUTE_READWRITE; + else if (HAS_FLAG(permissionFlags, PAGE_PERMISSION::P_READ) && HAS_FLAG(permissionFlags, PAGE_PERMISSION::P_WRITE) && !HAS_FLAG(permissionFlags, PAGE_PERMISSION::P_EXECUTE)) + p = PAGE_READWRITE; + else if (HAS_FLAG(permissionFlags, PAGE_PERMISSION::P_READ) && !HAS_FLAG(permissionFlags, PAGE_PERMISSION::P_WRITE) && !HAS_FLAG(permissionFlags, PAGE_PERMISSION::P_EXECUTE)) + p = PAGE_READONLY; + else + cemu_assert_unimplemented(); + return p; + } + + void* ReserveMemory(void* baseAddr, size_t size, PAGE_PERMISSION permissionFlags) + { + void* r = VirtualAlloc(baseAddr, size, MEM_RESERVE, GetPageProtection(permissionFlags)); + return r; + } + + void FreeReservation(void* baseAddr, size_t size) + { + VirtualFree(baseAddr, size, MEM_RELEASE); + } + + void* AllocateMemory(void* baseAddr, size_t size, PAGE_PERMISSION permissionFlags, bool fromReservation) + { + void* r; + if(fromReservation) + r = VirtualAlloc(baseAddr, size, MEM_COMMIT, GetPageProtection(permissionFlags)); + else + r = VirtualAlloc(baseAddr, size, MEM_RESERVE | MEM_COMMIT, GetPageProtection(permissionFlags)); + return r; + } + + void FreeMemory(void* baseAddr, size_t size, bool fromReservation) + { + if(fromReservation) + VirtualFree(baseAddr, size, MEM_DECOMMIT); + else + VirtualFree(baseAddr, size, MEM_RELEASE); + } + +}; + +#endif \ No newline at end of file diff --git a/src/util/ThreadPool/ThreadPool.cpp b/src/util/ThreadPool/ThreadPool.cpp new file mode 100644 index 00000000..e69de29b diff --git a/src/util/ThreadPool/ThreadPool.h b/src/util/ThreadPool/ThreadPool.h new file mode 100644 index 00000000..97ee8583 --- /dev/null +++ b/src/util/ThreadPool/ThreadPool.h @@ -0,0 +1,15 @@ +#include + +class ThreadPool +{ +public: + template + static void FireAndForget(TFunction&& f, TArgs&&... args) + { + // todo - find a way to use std::async here so we can utilize thread pooling? + std::thread t(std::forward(f), std::forward(args)...); + t.detach(); + } + + +}; \ No newline at end of file diff --git a/src/util/VirtualHeap/VirtualHeap.cpp b/src/util/VirtualHeap/VirtualHeap.cpp new file mode 100644 index 00000000..c659c853 --- /dev/null +++ b/src/util/VirtualHeap/VirtualHeap.cpp @@ -0,0 +1,160 @@ +#include "VirtualHeap.h" + +VirtualBufferHeap_t* virtualBufferHeap_create(uint32 virtualHeapSize, void* baseAddr) +{ + VirtualBufferHeap_t* bufferHeap = (VirtualBufferHeap_t*)malloc(sizeof(VirtualBufferHeap_t)); + memset(bufferHeap, 0, sizeof(VirtualBufferHeap_t)); + bufferHeap->firstEntry = nullptr; + virtualHeapSize = (virtualHeapSize + 31)&~31; + bufferHeap->virtualSize = virtualHeapSize; + bufferHeap->baseAddress = baseAddr; + bufferHeap->updateTrackIndex = 0; + // create pool of unused entries + sint32 unusedEntryPoolSize = 1024 * 16; + VirtualBufferHeapEntry_t* unusedEntryPool = (VirtualBufferHeapEntry_t*)malloc(sizeof(VirtualBufferHeapEntry_t)*unusedEntryPoolSize); + for (sint32 i = 0; i < unusedEntryPoolSize - 1; i++) + { + unusedEntryPool[i].next = unusedEntryPool + i + 1; + } + unusedEntryPool[unusedEntryPoolSize - 1].next = nullptr; + bufferHeap->firstUnusedEntry = unusedEntryPool + 0; + return bufferHeap; +} + +VirtualBufferHeapEntry_t* virtualBufferHeap_createEntry(VirtualBufferHeap_t* bufferHeap) +{ + VirtualBufferHeapEntry_t* newEntry = bufferHeap->firstUnusedEntry; + if (newEntry == nullptr) + { + forceLog_printf("virtualBufferHeap_createEntry: Pool empty"); + cemu_assert_suspicious(); + } + bufferHeap->firstUnusedEntry = newEntry->next; + newEntry->previous = NULL; + newEntry->next = NULL; + return newEntry; +} + +void virtualBufferHeap_releaseEntry(VirtualBufferHeap_t* bufferHeap, VirtualBufferHeapEntry_t* entry) +{ + bufferHeap->stats.allocatedMemory -= (entry->endOffset - entry->startOffset); + bufferHeap->stats.numActiveAllocs--; + entry->next = bufferHeap->firstUnusedEntry; + bufferHeap->firstUnusedEntry = entry; +} + +// Allocate memory region from virtual heap. Always allocates memory at the lowest possible address +VirtualBufferHeapEntry_t* virtualBufferHeap_allocate(VirtualBufferHeap_t* bufferHeap, uint32 size) +{ + // align size + size = (size + 255)&~255; + // iterate already allocated entries and try to find free space between them + VirtualBufferHeapEntry_t* entryItr = bufferHeap->firstEntry; + if (entryItr == NULL) + { + // entire heap is unallocated + VirtualBufferHeapEntry_t* newEntry = virtualBufferHeap_createEntry(bufferHeap); + newEntry->startOffset = 0; + newEntry->endOffset = size; + newEntry->previous = NULL; + newEntry->next = NULL; + bufferHeap->firstEntry = newEntry; + bufferHeap->stats.allocatedMemory += size; + bufferHeap->stats.numActiveAllocs++; + return newEntry; + } + else + { + uint32 currentAllocationOffset = 0; + VirtualBufferHeapEntry_t* entryPrev = nullptr; + while (entryItr) + { + if ((currentAllocationOffset + size) > entryItr->startOffset) + { + // space occupied + currentAllocationOffset = entryItr->endOffset; + currentAllocationOffset = (currentAllocationOffset + 255)&~255; + // next + entryPrev = entryItr; + entryItr = entryItr->next; + continue; + } + else + { + if ((currentAllocationOffset + size) > bufferHeap->virtualSize) + return nullptr; // out of heap memory + // free space found + VirtualBufferHeapEntry_t* newEntry = virtualBufferHeap_createEntry(bufferHeap); + newEntry->startOffset = currentAllocationOffset; + newEntry->endOffset = currentAllocationOffset + size; + // insert between previous entry and entryItr + newEntry->previous = entryItr->previous; + newEntry->next = entryItr; + if (entryItr->previous) + entryItr->previous->next = newEntry; + else + bufferHeap->firstEntry = newEntry; + entryItr->previous = newEntry; + bufferHeap->stats.allocatedMemory += size; + bufferHeap->stats.numActiveAllocs++; + return newEntry; + } + } + // add after entryPrev + if ((currentAllocationOffset + size) > bufferHeap->virtualSize) + return NULL; // out of heap memory + VirtualBufferHeapEntry_t* newEntry = virtualBufferHeap_createEntry(bufferHeap); + newEntry->startOffset = currentAllocationOffset; + newEntry->endOffset = currentAllocationOffset + size; + // insert after previous entry + cemu_assert_debug(entryPrev); + cemu_assert_debug(entryPrev->next == nullptr); + newEntry->previous = entryPrev; + newEntry->next = entryPrev->next; + entryPrev->next = newEntry; + bufferHeap->stats.allocatedMemory += size; + bufferHeap->stats.numActiveAllocs++; + return newEntry; + } + return NULL; +} + +void virtualBufferHeap_free(VirtualBufferHeap_t* bufferHeap, VirtualBufferHeapEntry_t* entry) +{ + if (entry->previous == NULL) + { + // make the next entry the first one + if (entry->next) + entry->next->previous = NULL; + bufferHeap->firstEntry = entry->next; + } + else + entry->previous->next = entry->next; + + if (entry->next) + entry->next->previous = entry->previous; + // release entry + virtualBufferHeap_releaseEntry(bufferHeap, entry); +} + +void* virtualBufferHeap_allocateAddr(VirtualBufferHeap_t* bufferHeap, uint32 size) +{ + VirtualBufferHeapEntry_t* heapEntry = virtualBufferHeap_allocate(bufferHeap, size); + return ((uint8*)bufferHeap->baseAddress + heapEntry->startOffset); +} + +void virtualBufferHeap_freeAddr(VirtualBufferHeap_t* bufferHeap, void* addr) +{ + auto entry = bufferHeap->firstEntry; + while(entry) + { + const auto entry_address = (uint8*)bufferHeap->baseAddress + entry->startOffset; + if(entry_address == (uint8*)addr) + { + virtualBufferHeap_free(bufferHeap, entry); + return; + } + entry = entry->next; + } + cemu_assert_suspicious(); +} \ No newline at end of file diff --git a/src/util/VirtualHeap/VirtualHeap.h b/src/util/VirtualHeap/VirtualHeap.h new file mode 100644 index 00000000..dfd85083 --- /dev/null +++ b/src/util/VirtualHeap/VirtualHeap.h @@ -0,0 +1,35 @@ +#pragma once + +// virtual heap + +struct VirtualBufferHeapEntry_t +{ + uint32 startOffset; + uint32 endOffset; + VirtualBufferHeapEntry_t* next; + VirtualBufferHeapEntry_t* previous; +}; + +struct VirtualBufferHeap_t +{ + uint32 virtualSize; + void* baseAddress; // base address for _allocateAddr and _freeAddr + VirtualBufferHeapEntry_t* firstEntry; + // unused entries + VirtualBufferHeapEntry_t* firstUnusedEntry; + // update tracking + uint32 updateTrackIndex; + // stats + struct + { + uint32 numActiveAllocs; + uint32 allocatedMemory; + }stats; +}; + +VirtualBufferHeap_t* virtualBufferHeap_create(uint32 virtualHeapSize, void* baseAddr = nullptr); +VirtualBufferHeapEntry_t* virtualBufferHeap_allocate(VirtualBufferHeap_t* bufferHeap, uint32 size); +void virtualBufferHeap_free(VirtualBufferHeap_t* bufferHeap, VirtualBufferHeapEntry_t* entry); + +void* virtualBufferHeap_allocateAddr(VirtualBufferHeap_t* bufferHeap, uint32 size); +void virtualBufferHeap_freeAddr(VirtualBufferHeap_t* bufferHeap, void* addr); \ No newline at end of file diff --git a/src/util/Zir/Core/IR.cpp b/src/util/Zir/Core/IR.cpp new file mode 100644 index 00000000..5fdfba61 --- /dev/null +++ b/src/util/Zir/Core/IR.cpp @@ -0,0 +1,236 @@ +#include "util/Zir/Core/IR.h" +#include "util/Zir/Core/ZpIRDebug.h" + +namespace ZpIR +{ + + const char* getOpcodeName(IR::OpCode opcode) + { + switch (opcode) + { + case IR::OpCode::ADD: + return "ADD"; + case IR::OpCode::MOV: + return "MOV"; + case IR::OpCode::MUL: + return "MUL"; + case IR::OpCode::DIV: + return "DIV"; + + case IR::OpCode::BITCAST: + return "BITCAST"; + case IR::OpCode::SWAP_ENDIAN: + return "SWAP_ENDIAN"; + case IR::OpCode::CONVERT_INT_TO_FLOAT: + return "CONV_I2F"; + case IR::OpCode::CONVERT_FLOAT_TO_INT: + return "CONV_F2I"; + + case IR::OpCode::IMPORT_SINGLE: + return "IMPORT_S"; + case IR::OpCode::EXPORT: + return "EXPORT"; + case IR::OpCode::IMPORT: + return "IMPORT"; + default: + cemu_assert_debug(false); + return "UKN"; + } + return ""; + } + + const char* getTypeName(DataType t) + { + switch (t) + { + case DataType::S64: + return "s64"; + case DataType::U64: + return "u64"; + case DataType::S32: + return "s32"; + case DataType::U32: + return "u32"; + case DataType::S16: + return "s16"; + case DataType::U16: + return "u16"; + case DataType::S8: + return "s8"; + case DataType::U8: + return "u8"; + case DataType::BOOL: + return "bool"; + case DataType::POINTER: + return "ptr"; + } + return ""; + } + + std::string DebugPrinter::getRegisterName(ZpIRBasicBlock* block, IRReg r) + { + std::string s; + + if ((uint16)r < 0x8000 && m_showPhysicalRegisters) + { + auto& reg = block->m_regs[(uint16)r]; + if (!reg.hasAssignedPhysicalRegister()) + return "UNASSIGNED"; + s = m_getPhysicalRegisterNameCustom(block, reg.physicalRegister); + return s; + } + + if ((uint16)r < 0x8000 && m_getRegisterNameCustom) + { + return m_getRegisterNameCustom(block, r); + } + + if ((uint16)r >= 0x8000) + { + auto& reg = block->m_consts[(uint16)r & 0x7FFF]; + switch (reg.type) + { + case DataType::POINTER: + return fmt::format("ptr:{}", reg.value_ptr); + case DataType::U64: + { + if(reg.value_u64 >= 0x1000) + return fmt::format("u64:0x{0:x}", reg.value_u64); + return fmt::format("u64:{}", reg.value_u64); + } + case DataType::U32: + return fmt::format("u32:{}", reg.value_u32); + case DataType::S32: + return fmt::format("s32:{}", reg.value_u32); + case DataType::F32: + return fmt::format("f32:{}", reg.value_f32); + default: + break; + } + return "ukn_const_type"; + } + else + { + auto& reg = block->m_regs[(uint16)r]; + + const char* regLetter = "r"; + switch (reg.type) + { + case DataType::U64: + regLetter = "uq"; // quad-word + break; + case DataType::U32: + regLetter = "ud"; // double-word + break; + case DataType::U16: + regLetter = "uw"; // word + break; + case DataType::U8: + regLetter = "uc"; // char + break; + case DataType::S64: + regLetter = "sq"; // signed quad-word + break; + case DataType::S32: + regLetter = "sd"; // signed double-word + break; + case DataType::S16: + regLetter = "sw"; // signed word + break; + case DataType::S8: + regLetter = "sc"; // signed char + break; + case DataType::F32: + regLetter = "fv"; // 32bit float + break; + case DataType::POINTER: + regLetter = "ptr"; + break; + default: + assert_dbg(); + } + if (reg.elementCount != 1) + assert_dbg(); + s = fmt::format("{}{}", regLetter, (uint16)r); + } + return s; + } + + std::string DebugPrinter::getInstructionHRF(ZpIRBasicBlock* block, IR::__InsBase* cmd) + { + if (auto ins = IR::InsRR::getIfForm(cmd)) + { + return fmt::format("{:<10} {}, {}", getOpcodeName(cmd->opcode), getRegisterName(block, ins->rA), getRegisterName(block, ins->rB)); + } + else if (auto ins = IR::InsRRR::getIfForm(cmd)) + { + return fmt::format("{:<10} {}, {}, {}", getOpcodeName(cmd->opcode), getRegisterName(block, ins->rA), getRegisterName(block, ins->rB), getRegisterName(block, ins->rC)); + } + else if (auto ins = IR::InsEXPORT::getIfForm(cmd)) + { + if (ins->count == 4) + return fmt::format("{:<10} {}, {}, {}, {}, loc: {}", getOpcodeName(cmd->opcode), getRegisterName(block, ins->regArray[0]), getRegisterName(block, ins->regArray[1]), getRegisterName(block, ins->regArray[2]), getRegisterName(block, ins->regArray[3]), ins->exportSymbol); + else if (ins->count == 3) + return fmt::format("{:<10} {}, {}, {}, loc: {}", getOpcodeName(cmd->opcode), getRegisterName(block, ins->regArray[0]), getRegisterName(block, ins->regArray[1]), getRegisterName(block, ins->regArray[2]), ins->exportSymbol); + else if (ins->count == 2) + return fmt::format("{:<10} {}, {}, loc: {}", getOpcodeName(cmd->opcode), getRegisterName(block, ins->regArray[0]), getRegisterName(block, ins->regArray[1]), ins->exportSymbol); + else if (ins->count == 1) + return fmt::format("{:<10} {}, loc: {}", getOpcodeName(cmd->opcode), getRegisterName(block, ins->regArray[0]), ins->exportSymbol); + assert_dbg(); + } + else if (auto ins = IR::InsIMPORT::getIfForm(cmd)) + { + ShaderSubset::ShaderImportLocation importLocation = ins->importSymbol; + std::string locDebugName = importLocation.GetDebugName(); + + if (ins->count == 4) + return fmt::format("{:<10} {}, {}, {}, {}, loc: {}", getOpcodeName(cmd->opcode), getRegisterName(block, ins->regArray[0]), getRegisterName(block, ins->regArray[1]), getRegisterName(block, ins->regArray[2]), getRegisterName(block, ins->regArray[3]), locDebugName); + else if (ins->count == 3) + return fmt::format("{:<10} {}, {}, {}, loc: {}", getOpcodeName(cmd->opcode), getRegisterName(block, ins->regArray[0]), getRegisterName(block, ins->regArray[1]), getRegisterName(block, ins->regArray[2]), locDebugName); + else if (ins->count == 2) + return fmt::format("{:<10} {}, {}, loc: {}", getOpcodeName(cmd->opcode), getRegisterName(block, ins->regArray[0]), getRegisterName(block, ins->regArray[1]), locDebugName); + else if (ins->count == 1) + return fmt::format("{:<10} {}, loc: {}", getOpcodeName(cmd->opcode), getRegisterName(block, ins->regArray[0]), locDebugName); + assert_dbg(); + } + else + assert_dbg(); + return ""; + } + + void DebugPrinter::debugPrintBlock(ZpIRBasicBlock* block) + { + // print name + printf("IRBasicBlock %016llx\n", (uintptr_t)block); + // print imports + printf("Imports:\n"); + for(auto itr : block->m_imports) + printf(" reg: %s sym:0x%llx\n", getRegisterName(block, itr.reg).c_str(), itr.name); + // print exports + printf("Exports:\n"); + for (auto itr : block->m_exports) + printf(" reg: %s sym:0x%llx\n", getRegisterName(block, itr.reg).c_str(), itr.name); + // print instructions + printf("Assembly:\n"); + IR::__InsBase* instruction = block->m_instructionFirst; + size_t i = 0; + while(instruction) + { + std::string s = getInstructionHRF(block, instruction); + printf("%04x %s\n", (unsigned int)i, s.c_str()); + i++; + instruction = instruction->next; + } + } + + void DebugPrinter::debugPrint(ZpIRFunction* irFunction) + { + printf("--- Print IR function assembly ---\n"); + for (auto& itr : irFunction->m_basicBlocks) + { + debugPrintBlock(itr); + printf("\n"); + } + } + +} \ No newline at end of file diff --git a/src/util/Zir/Core/IR.h b/src/util/Zir/Core/IR.h new file mode 100644 index 00000000..71191024 --- /dev/null +++ b/src/util/Zir/Core/IR.h @@ -0,0 +1,672 @@ +#pragma once +#include + +using f32 = float; +using f64 = double; + +inline void zpir_debug_assert(bool _cond) +{ + if(!_cond) + assert_dbg(); +} + +namespace ZpIR +{ + //enum class ZpIRCmdForm : uint8 + //{ + // FORM_VOID, // no-op + // FORM_ZERO, // opcode without operands + // FORM_1OP, // op0 + // FORM_2OP, // op0, op1 + // FORM_3OP, // op0, op1, op2 + // FORM_4OP, // op0, op1, op2, op3 + // // todo - memory read + memory store + // FORM_MEM, // op0, opEA, offset, type + // // todo - function calls + + //}; + + //enum class ZpIROpcodeDepr : uint8 + //{ + // OP_VOID, + // // FORM_1OP + // OP_CALL, + // // FORM_2OP + // OP_ASSIGN, // copy/assignment + // OP_CAST_ZEROEXT, // cast type to another. If broadening then zero-extend (unsigned cast) + // OP_CAST_SIGNEXT, // cast type to another. If broadening then sign-extend (signed cast) + // // FORM_3OP + // OP_ADD, // op0 = op1 + op2 + // OP_SUB, // op0 = op1 - op2 + // OP_MUL, // op0 = op1 * op2 + // OP_DIV, // op0 = op1 / op2 + // // memory + // OP_MEM_READ, + // OP_MEM_WRITE, + //}; + + enum class DataType : uint8 + { + NONE = 0x00, + // integer + U8 = 1, + S8 = 2, + U16 = 3, + S16 = 4, + U32 = 5, + S32 = 6, + U64 = 7, + S64 = 8, + // floating-point + F32 = 0x10 + 0, + F64 = 0x10 + 1, + // special + POINTER = 0x20, // dynamic width based on pointer width of target architecture + // boolean + BOOL = 0x30, // can hold false/true. Size depends on target architecture + }; + + typedef uint16 IRReg; + typedef uint64 LocationSymbolName; + typedef uint32 ZpIRPhysicalReg; + + inline bool isRegVar(IRReg r) { return r < 0x8000; }; + inline bool isConstVar(IRReg r) { return r >= 0x8000; }; + inline uint16 getRegIndex(IRReg r) { return (uint16)r & 0x7FFF; }; + + namespace IR + { + enum class OpCode : uint8 + { + UNDEF = 0, // undefined + // basic opcodes + MOV, + + // basic arithmetic opcodes + ADD, // addition + SUB, // subtraction + MUL, // multiplication + DIV, // division + + // conversion + BITCAST, // like MOV, but allows registers of different types. No value conversion happens, raw bit copy + SWAP_ENDIAN, // swap endianness + CONVERT_INT_TO_FLOAT, + CONVERT_FLOAT_TO_INT, + + // misc + IMPORT_SINGLE, // import into a single IRReg. Depr: Make this like EXPORT where there is a 1-4 regs variant and one for more + IMPORT, // import from external/custom resource into 1-4 IRReg + + EXPORT, // export 1-4 registers to external/custom resource + // EXPORT_MANY // for when more than 4 registers are needed + + + + // vector + EXTRACT_ELEMENT, // extract a scalar type from a vector type + // some notes: We need this for texture read instructions. Where the result is a vec4 (f32x4) and this is how we can extract individual registers from that + // update -> We may also instead just let the texture sample instruction specify 4 output registers + }; + + enum class OpForm : uint8 + { + NONE = 0, + RR = 1, + RRR = 2, + IMPORT_SINGLE = 3, // deprecated + IMPORT = 4, + EXPORT = 5, + }; + + // instruction base class + class __InsBase + { + public: + + OpCode opcode; + OpForm opform; + __InsBase* next; + protected: + __InsBase(OpCode opcode, OpForm opform) : opcode(opcode), opform(opform) { }; + }; + + // adapted base class, instruction forms inherit from this + template + class __InsBaseWithForm : public __InsBase + { + public: + + //OpCode opcode; + //OpForm opform; + //__InsBase* next; + + static const OpForm getForm() + { + return TOpForm; + } + + static TInstr* getIfForm(__InsBase* instructionBase) + { + if (instructionBase->opform != TOpForm) + return nullptr; + return (TInstr*)instructionBase; + } + + protected: + __InsBaseWithForm(OpCode opcode) : __InsBase(opcode, TOpForm) { }; + }; + + class InsRR : public __InsBaseWithForm + { + public: + InsRR(OpCode opcode, IRReg rA, IRReg rB) : __InsBaseWithForm(opcode), rA(rA), rB(rB) {}; + + IRReg rA; + IRReg rB; + }; + + class InsRRR : public __InsBaseWithForm + { + public: + InsRRR(OpCode opcode, IRReg rA, IRReg rB, IRReg rC) : __InsBaseWithForm(opcode), rA(rA), rB(rB), rC(rC) {}; + + IRReg rA; + IRReg rB; + IRReg rC; + }; + + // should we support RRI format with 32bit signed integer as a way to avoid having to generate dozens of IR const regs for stuff like shift and other logical instructions with constant rhs? + // and if we do, should it be a 32bit signed integer or should the type match the instruction type? + + class InsEXPORT : public __InsBaseWithForm + { + public: + InsEXPORT(LocationSymbolName exportSymbol, IRReg r) : __InsBaseWithForm(OpCode::EXPORT), exportSymbol(exportSymbol) + { + regArray[0] = r; + count = 1; + }; + + InsEXPORT(LocationSymbolName exportSymbol, IRReg r0, IRReg r1) : __InsBaseWithForm(OpCode::EXPORT), exportSymbol(exportSymbol) + { + regArray[0] = r0; regArray[1] = r1; + count = 2; + }; + + InsEXPORT(LocationSymbolName exportSymbol, IRReg r0, IRReg r1, IRReg r2) : __InsBaseWithForm(OpCode::EXPORT), exportSymbol(exportSymbol) + { + regArray[0] = r0; regArray[1] = r1; regArray[2] = r2; + count = 3; + }; + + InsEXPORT(LocationSymbolName exportSymbol, IRReg r0, IRReg r1, IRReg r2, IRReg r3) : __InsBaseWithForm(OpCode::EXPORT), exportSymbol(exportSymbol) + { + regArray[0] = r0; + regArray[1] = r1; + regArray[2] = r2; + regArray[3] = r3; + count = 4; + }; + + InsEXPORT(LocationSymbolName exportSymbol, std::span regs) : __InsBaseWithForm(OpCode::EXPORT), exportSymbol(exportSymbol) + { + zpir_debug_assert(regs.size() <= 4); + for(size_t i=0; i + { + public: + InsIMPORT(LocationSymbolName importSymbol, IRReg r) : __InsBaseWithForm(OpCode::IMPORT), importSymbol(importSymbol) + { + regArray[0] = r; + count = 1; + }; + + InsIMPORT(LocationSymbolName importSymbol, IRReg r0, IRReg r1) : __InsBaseWithForm(OpCode::IMPORT), importSymbol(importSymbol) + { + regArray[0] = r0; regArray[1] = r1; + count = 2; + }; + + InsIMPORT(LocationSymbolName importSymbol, IRReg r0, IRReg r1, IRReg r2) : __InsBaseWithForm(OpCode::IMPORT), importSymbol(importSymbol) + { + regArray[0] = r0; regArray[1] = r1; regArray[2] = r2; + count = 3; + }; + + InsIMPORT(LocationSymbolName importSymbol, IRReg r0, IRReg r1, IRReg r2, IRReg r3) : __InsBaseWithForm(OpCode::IMPORT), importSymbol(importSymbol) + { + regArray[0] = r0; + regArray[1] = r1; + regArray[2] = r2; + regArray[3] = r3; + count = 4; + }; + + InsIMPORT(LocationSymbolName importSymbol, std::span regs) : __InsBaseWithForm(OpCode::IMPORT), importSymbol(importSymbol) + { + zpir_debug_assert(regs.size() <= 4); + for (size_t i = 0; i < regs.size(); i++) + regArray[i] = regs[i]; + count = (uint16)regs.size(); + }; + + uint16 count; + IRReg regArray[4]; // up to 4 registers + LocationSymbolName importSymbol; + }; + }; + + // IR register definition stored in basic block + struct IRRegDef + { + IRRegDef(DataType type, uint8 elementCount) : type(type), elementCount(elementCount) {}; + + DataType type; + uint8 elementCount; // 1 = scalar + ZpIRPhysicalReg physicalRegister{ std::numeric_limits::max()}; + // todo - information about spilling location? (it depends on the architecture so we should keep this out of the core IR) + bool hasAssignedPhysicalRegister() const + { + return physicalRegister != std::numeric_limits::max(); + } + + void assignPhysicalRegister(ZpIRPhysicalReg physReg) + { + physicalRegister = physReg; + } + }; + + // IR register constant definition stored in basic block + struct IRRegConstDef + { + IRRegConstDef() {}; + // todo - support for constants with more than one element? + + IRRegConstDef& setU32(uint32 v) { value_u32 = v; type = DataType::U32; return *this; }; + IRRegConstDef& setS32(sint32 v) { value_s32 = v; type = DataType::S32; return *this; }; + IRRegConstDef& setF32(f32 v) { value_f32 = v; type = DataType::F32; return *this; }; + IRRegConstDef& setPtr(void* v) { value_ptr = v; type = DataType::POINTER; return *this; }; + IRRegConstDef& setRaw(uint32 v, DataType regType) { value_u32 = v; type = regType; return *this; }; + + DataType type{ DataType::NONE }; + union + { + uint32 value_u32; + sint32 value_s32; + sint64 value_s64; + uint64 value_u64; + void* value_ptr; + f32 value_f32; + f64 value_f64; + }; + }; + + struct ZpIRBasicBlock + { + friend class ZpIRBuilder; + + struct IRBBImport + { + IRBBImport(IRReg reg, LocationSymbolName name) : reg(reg), name(name) {}; + + IRReg reg; + LocationSymbolName name; + }; + + struct IRBBExport + { + IRBBExport(IRReg reg, LocationSymbolName name) : reg(reg), name(name) {}; + + IRReg reg; + LocationSymbolName name; + }; + + IR::__InsBase* m_instructionFirst{}; + IR::__InsBase* m_instructionLast{}; + std::vector m_regs; + std::vector m_consts; + std::vector m_imports; + std::vector m_exports; + ZpIRBasicBlock* m_branchNotTaken{ nullptr }; // next block if branch not taken or no branch present + ZpIRBasicBlock* m_branchTaken{ nullptr }; // next block if branch is taken + + void* m_workbuffer{}; // can be used as temporary storage for information + + void appendInstruction(IR::__InsBase* ins) + { + if (m_instructionFirst == nullptr) + { + m_instructionFirst = ins; + m_instructionLast = ins; + ins->next = nullptr; + return; + } + m_instructionLast->next = ins; + m_instructionLast = ins; + ins->next = nullptr; + } + + IRReg createRegister(DataType type, uint8 elementCount = 1) + { + uint32 index = (uint32)m_regs.size(); + cemu_assert_debug(index < 0x8000); + m_regs.emplace_back(type, elementCount); + return (IRReg)index; + } + + IRReg createConstantU32(uint32 value) + { + uint32 index = (uint32)m_consts.size(); + cemu_assert_debug(index < 0x8000); + m_consts.emplace_back().setU32(value); + return (IRReg)((uint16)index + 0x8000); + } + + IRReg createTypedConstant(uint32 value, DataType type) + { + uint32 index = (uint32)m_consts.size(); + cemu_assert_debug(index < 0x8000); + m_consts.emplace_back().setRaw(value, type); + return (IRReg)((uint16)index + 0x8000); + } + + IRReg createConstantS32(uint32 value) + { + uint32 index = (uint32)m_consts.size(); + cemu_assert_debug(index < 0x8000); + m_consts.emplace_back().setS32(value); + return (IRReg)((uint16)index + 0x8000); + } + + IRReg createConstantF32(f32 value) + { + uint32 index = (uint32)m_consts.size(); + cemu_assert_debug(index < 0x8000); + m_consts.emplace_back().setF32(value); + return (IRReg)((uint16)index + 0x8000); + } + + IRReg createConstantPointer(void* value) + { + uint32 index = (uint32)m_consts.size(); + cemu_assert_debug(index < 0x8000); + m_consts.emplace_back().setPtr(value); + return (IRReg)((uint16)index + 0x8000); + } + + void addImport(IRReg reg, LocationSymbolName importName) + { + m_imports.emplace_back(reg, importName); + } + + void addExport(IRReg reg, LocationSymbolName importName) + { + m_exports.emplace_back(reg, importName); + } + + void setWorkbuffer(void* buffer) + { + if (buffer != nullptr) + { + if (m_workbuffer) + assert_dbg(); + } + m_workbuffer = buffer; + } + + void* getWorkbuffer() + { + return m_workbuffer; + } + + + DataType getRegType(IRReg reg) + { + uint32 index = (uint32)reg; + if (index >= 0x8000) + { + index -= 0x8000; + cemu_assert_debug(index < m_consts.size()); + return m_consts[index].type; + } + return m_regs[index].type; + } + + IRRegConstDef* getConstant(IRReg reg) + { + uint32 index = (uint32)reg; + if (index < 0x8000) + return nullptr; + index -= 0x8000; + if (index >= m_consts.size()) + return nullptr; + return m_consts.data() + index; + } + + std::optional getConstantS32(IRReg reg) + { + uint32 index = (uint32)reg; + if (index < 0x8000) + return std::nullopt; + index -= 0x8000; + if (index >= m_consts.size()) + return std::nullopt; + if (m_consts[index].type == DataType::U32) + return (sint32)m_consts[index].value_u32; + else if (m_consts[index].type == DataType::POINTER) + assert_dbg(); + else if (m_consts[index].type == DataType::U64) + { + if (m_consts[index].value_u64 >= 0x80000000ull) + assert_dbg(); + return (sint32)m_consts[index].value_u64; + } + else + assert_dbg(); + return std::nullopt; + } + + std::optional getConstantU64(IRReg reg) + { + auto constReg = getConstant(reg); + if (!constReg) + return std::nullopt; + if (constReg->type == DataType::U64) + return constReg->value_u64; + else + assert_dbg(); + return std::nullopt; + } + }; + + struct ZpIRFunction + { + std::vector m_basicBlocks; + std::vector m_entryBlocks; + std::vector m_exitBlocks; + + struct + { + bool registersAllocated{false}; + }state; + }; + + // helpers for shader code + namespace ShaderSubset + { + class ShaderImportLocation + { + enum LOC_TYPE : uint8 + { + LOC_TYPE_UNIFORM_REGISTER = 1, + LOC_TYPE_UNIFORM_BUFFER = 2, + LOC_TYPE_ATTRIBUTE = 3, + }; + public: + ShaderImportLocation() {} + ShaderImportLocation(LocationSymbolName loc) + { + uint64 v = (uint64)loc; + + m_locType = (LOC_TYPE)(v >> 56); + m_indexA = (uint16)(v >> 0); + m_indexB = (uint16)(v >> 16); + } + + ShaderImportLocation& SetUniformRegister(uint16 index) + { + m_locType = LOC_TYPE_UNIFORM_REGISTER; + m_indexA = index; + m_indexB = 0; + return *this; + } + + ShaderImportLocation& SetVertexAttribute(uint16 attributeIndex, uint16 channelIndex) + { + m_locType = LOC_TYPE_ATTRIBUTE; + m_indexA = attributeIndex; + m_indexB = channelIndex; + return *this; + } + + bool IsUniformRegister() const + { + return m_locType == LOC_TYPE_UNIFORM_REGISTER; + } + + bool IsVertexAttribute() const + { + return m_locType == LOC_TYPE_ATTRIBUTE; + } + + void GetUniformRegister(uint16& index) + { + index = m_indexA; + } + + void GetVertexAttribute(uint16& attributeIndex, uint16& channelIndex) const + { + attributeIndex = m_indexA; + channelIndex = m_indexB; + } + + operator LocationSymbolName() const + { + uint64 v = 0; + v |= ((uint64)m_locType << 56); + v |= ((uint64)m_indexA << 0); + v |= ((uint64)m_indexB << 16); + + return (LocationSymbolName)v; + } + + std::string GetDebugName() + { + const char elementTable[] = { 'x' , 'y', 'z', 'w' }; + + if (m_locType == LOC_TYPE_UNIFORM_REGISTER) + return fmt::format("UniformReg[{0}].{1}", m_indexA >> 2, elementTable[m_indexA & 3]); + if (m_locType == LOC_TYPE_ATTRIBUTE) + return fmt::format("VertexAttribute[{0}].{1}", m_indexA, elementTable[m_indexB]); + + return "Unknown"; + } + + private: + LOC_TYPE m_locType{}; + uint16 m_indexA{}; + uint16 m_indexB{}; + //LocationSymbolName m_symbolName{}; + static_assert(sizeof(LocationSymbolName) == 8); + }; + + class ShaderExportLocation + { + enum LOC_TYPE : uint8 + { + LOC_TYPE_POSITION = 1, + LOC_TYPE_OUTPUT = 2, + }; + public: + ShaderExportLocation() {} + ShaderExportLocation(LocationSymbolName loc) + { + uint64 v = (uint64)loc; + + m_locType = (LOC_TYPE)(v >> 56); + m_indexA = (uint16)(v >> 0); + m_indexB = (uint16)(v >> 16); + } + + ShaderExportLocation& SetPosition() + { + m_locType = LOC_TYPE_POSITION; + m_indexA = 0; + m_indexB = 0; + return *this; + } + + ShaderExportLocation& SetOutputAttribute(uint16 attributeIndex) // todo - channel mask? + { + m_locType = LOC_TYPE_OUTPUT; + m_indexA = attributeIndex; + m_indexB = 0; + return *this; + } + + bool IsPosition() const + { + return m_locType == LOC_TYPE_POSITION; + } + + bool IsOutputAttribute() const + { + return m_locType == LOC_TYPE_OUTPUT; + } + + void GetOutputAttribute(uint16& attributeIndex) const + { + attributeIndex = m_indexA; + } + + operator LocationSymbolName() const + { + uint64 v = 0; + v |= ((uint64)m_locType << 56); + v |= ((uint64)m_indexA << 0); + v |= ((uint64)m_indexB << 16); + + return (LocationSymbolName)v; + } + + std::string GetDebugName() + { + const char elementTable[] = { 'x' , 'y', 'z', 'w' }; + + //if (m_locType == LOC_TYPE_UNIFORM_REGISTER) + // return fmt::format("UniformReg[{0}].{1}", m_indexA >> 2, elementTable[m_indexA & 3]); + //if (m_locType == LOC_TYPE_ATTRIBUTE) + // return fmt::format("VertexAttribute[{0}].{1}", m_indexA, elementTable[m_indexB]); + + return "Unknown"; + } + + private: + LOC_TYPE m_locType{}; + uint16 m_indexA{}; + uint16 m_indexB{}; + static_assert(sizeof(LocationSymbolName) == 8); + }; + + }; +} diff --git a/src/util/Zir/Core/ZirUtility.h b/src/util/Zir/Core/ZirUtility.h new file mode 100644 index 00000000..c9c0a32d --- /dev/null +++ b/src/util/Zir/Core/ZirUtility.h @@ -0,0 +1,76 @@ +#pragma once +#include "util/Zir/Core/IR.h" + +namespace ZpIR +{ + + struct ZpIRCmdUtil + { + template + static void forEachAccessedReg(ZpIRBasicBlock& block, IR::__InsBase* instruction, TFuncRegRead funcRegRead, TFuncRegWrite funcRegWrite) + { + if (auto ins = IR::InsRR::getIfForm(instruction)) + { + switch (ins->opcode) + { + case ZpIR::IR::OpCode::MOV: + case ZpIR::IR::OpCode::BITCAST: + case ZpIR::IR::OpCode::SWAP_ENDIAN: + case ZpIR::IR::OpCode::CONVERT_FLOAT_TO_INT: + case ZpIR::IR::OpCode::CONVERT_INT_TO_FLOAT: + if (isRegVar(ins->rB)) + funcRegRead(ins->rB); + cemu_assert_debug(isRegVar(ins->rA)); + funcRegWrite(ins->rA); + break; + default: + cemu_assert_unimplemented(); + } + } + else if (auto ins = IR::InsRRR::getIfForm(instruction)) + { + switch (ins->opcode) + { + case ZpIR::IR::OpCode::ADD: + case ZpIR::IR::OpCode::SUB: + case ZpIR::IR::OpCode::MUL: + case ZpIR::IR::OpCode::DIV: + if (isRegVar(ins->rB)) + funcRegRead(ins->rB); + if (isRegVar(ins->rC)) + funcRegRead(ins->rC); + cemu_assert_debug(isRegVar(ins->rA)); + funcRegWrite(ins->rA); + break; + default: + cemu_assert_unimplemented(); + } + } + else if (auto ins = IR::InsIMPORT::getIfForm(instruction)) + { + for (uint16 i = 0; i < ins->count; i++) + { + cemu_assert_debug(isRegVar(ins->regArray[i])); + funcRegWrite(ins->regArray[i]); + } + } + else if (auto ins = IR::InsEXPORT::getIfForm(instruction)) + { + for (uint16 i = 0; i < ins->count; i++) + { + if (isRegVar(ins->regArray[i])) + funcRegRead(ins->regArray[i]); + } + } + else + { + cemu_assert_unimplemented(); + } + } + + static void replaceRegisters(IR::__InsBase& ins, std::unordered_map& translationTable) + { + cemu_assert_unimplemented(); + } + }; +} \ No newline at end of file diff --git a/src/util/Zir/Core/ZpIRBuilder.h b/src/util/Zir/Core/ZpIRBuilder.h new file mode 100644 index 00000000..de1c6aa8 --- /dev/null +++ b/src/util/Zir/Core/ZpIRBuilder.h @@ -0,0 +1,250 @@ +#pragma once +#include "util/Zir/Core/IR.h" + +namespace ZpIR +{ + + // helper class for building a single basic block + class BasicBlockBuilder + { + public: + BasicBlockBuilder(ZpIRBasicBlock* basicBlock) : m_basicBlock(basicBlock) {}; + + IRReg createReg(DataType type, uint8 elementCount = 1) + { + return m_basicBlock->createRegister(type, elementCount); + } + + IRReg createReg(IRReg& r, DataType type, uint8 elementCount = 1) + { + r = m_basicBlock->createRegister(type, elementCount); + return r; + } + + // append a single instruction at the end + void append(IR::__InsBase* ins) + { + assert_dbg(); + } + + void emit_EXPORT(LocationSymbolName exportSymbolName, IRReg r0) + { + m_basicBlock->appendInstruction(new IR::InsEXPORT(exportSymbolName, r0)); + } + + void emit_EXPORT(LocationSymbolName exportSymbolName, std::span regs) + { + m_basicBlock->appendInstruction(new IR::InsEXPORT(exportSymbolName, regs)); + } + + void emit_IMPORT(LocationSymbolName importSymbolName, IRReg r0) + { + m_basicBlock->appendInstruction(new IR::InsIMPORT(importSymbolName, r0)); + } + + // result is rA, operand is rB + // for some opcodes both can be operands + void emit_RR(IR::OpCode opcode, IRReg rA, IRReg rB) + { + m_basicBlock->appendInstruction(new IR::InsRR(opcode, rA, rB)); + } + + IRReg emit_RR(IR::OpCode opcode, DataType resultType, IRReg rB) + { + IRReg resultReg = m_basicBlock->createRegister(resultType); + emit_RR(opcode, resultReg, rB); + return resultReg; + } + + // result is rA, operands are rB and rC + // for some opcodes all three can be operands + void emit_RRR(IR::OpCode opcode, IRReg rA, IRReg rB, IRReg rC) + { + m_basicBlock->appendInstruction(new IR::InsRRR(opcode, rA, rB, rC)); + } + + IRReg emit_RRR(IR::OpCode opcode, DataType resultType, IRReg rB, IRReg rC) + { + IRReg resultReg = m_basicBlock->createRegister(resultType); + m_basicBlock->appendInstruction(new IR::InsRRR(opcode, resultReg, rB, rC)); + return resultReg; + } + + void emit(IR::__InsBase* ins) + { + m_basicBlock->appendInstruction(ins); + } + + // constant var creation + + IRReg createConstU32(uint32 v) + { + return m_basicBlock->createConstantU32(v); + } + + IRReg createTypedConst(uint32 v, DataType type) + { + return m_basicBlock->createTypedConstant(v, type); + } + + IRReg createConstS32(uint32 v) + { + return m_basicBlock->createConstantS32(v); + } + + IRReg createConstF32(f32 v) + { + return m_basicBlock->createConstantF32(v); + } + + IRReg createConstPointer(void* v) + { + return m_basicBlock->createConstantPointer(v); + } + + // use templates to compact other types? + + DataType getRegType(IRReg reg) + { + return m_basicBlock->getRegType(reg); + } + + void addImport(IRReg reg, LocationSymbolName importSymbolName) + { + m_basicBlock->addImport(reg, importSymbolName); + } + + + private: + ZpIRBasicBlock* m_basicBlock; + }; + + // helper class for constructing multiple basic blocks with control flow + class ZpIRBuilder + { + public: + typedef uint64 BlockBranchTarget; + + static const inline BlockBranchTarget INVALID_BLOCK_NAME = 0xFFFFFFFFFFFFFFFFull; + + struct BasicBlockWorkbuffer + { + BlockBranchTarget name{ INVALID_BLOCK_NAME }; + BlockBranchTarget targetBranchNotTaken{ INVALID_BLOCK_NAME }; + BlockBranchTarget targetBranchTaken{ INVALID_BLOCK_NAME }; + }; + + void beginBlock(BlockBranchTarget name) + { + m_currentBasicBlock = new ZpIRBasicBlock(); + BasicBlockWorkbuffer* wb = new BasicBlockWorkbuffer(); + m_currentBasicBlock->setWorkbuffer(wb); + wb->name = name; + m_blocks.emplace_back(m_currentBasicBlock); + m_blocksByName.emplace(name, m_currentBasicBlock); + } + + ZpIRBasicBlock* endBlock() + { + ZpIRBasicBlock* block = m_currentBasicBlock; + m_currentBasicBlock = nullptr; + BasicBlockWorkbuffer* wb = (BasicBlockWorkbuffer*)block->getWorkbuffer(); + wb->targetBranchNotTaken = m_targetBranchNotTaken; + wb->targetBranchTaken = m_targetBranchTaken; + m_targetBranchNotTaken = INVALID_BLOCK_NAME; + m_targetBranchTaken = INVALID_BLOCK_NAME; + return block; + } + + ZpIRFunction* finish() + { + if (m_currentBasicBlock) + assert_dbg(); + // create function + ZpIRFunction* func = new ZpIRFunction(); + // link all blocks + // and also collect a list of entry and exit nodes + for (auto& itr : m_blocks) + { + BasicBlockWorkbuffer* wb = (BasicBlockWorkbuffer*)itr->getWorkbuffer(); + if (wb->targetBranchNotTaken != INVALID_BLOCK_NAME) + { + auto target = m_blocksByName.find(wb->targetBranchNotTaken); + if (target == m_blocksByName.end()) + { + assert_dbg(); + } + itr->m_branchNotTaken = target->second; + } + if (wb->targetBranchTaken != INVALID_BLOCK_NAME) + { + auto target = m_blocksByName.find(wb->targetBranchTaken); + if (target == m_blocksByName.end()) + { + assert_dbg(); + } + itr->m_branchTaken = target->second; + } + delete wb; + itr->setWorkbuffer(nullptr); + func->m_basicBlocks.emplace_back(itr); + // todo - track entry and exit blocks (set block flags for entry/exit during block gen) + } + return func; + } + + IRReg createBlockRegister(DataType type, uint8 elementCount = 1) + { + return m_currentBasicBlock->createRegister(type, elementCount); + } + + IRReg createConstU32(uint32 v) + { + return m_currentBasicBlock->createConstantU32(v); + } + + IRReg createConstPointer(void* v) + { + return m_currentBasicBlock->createConstantPointer(v); + } + + IRReg createConstPointerV(size_t v) + { + return m_currentBasicBlock->createConstantPointer((void*)v); + } + + void addImport(IRReg reg, LocationSymbolName importName) + { + m_currentBasicBlock->addImport(reg, importName); + } + + void addExport(IRReg reg, LocationSymbolName importName) + { + m_currentBasicBlock->addExport(reg, importName); + } + + void setBlockTargetBranchTaken(BlockBranchTarget target) + { + if (m_targetBranchTaken != INVALID_BLOCK_NAME) + assert_dbg(); + m_targetBranchTaken = target; + } + + void setBlockTargetBranchNotTaken(BlockBranchTarget target) + { + if (m_targetBranchNotTaken != INVALID_BLOCK_NAME) + assert_dbg(); + m_targetBranchNotTaken = target; + } + + private: + ZpIRBasicBlock* m_currentBasicBlock{}; + std::vector m_blocks; + std::unordered_map m_blocksByName; + + BlockBranchTarget m_targetBranchNotTaken{ INVALID_BLOCK_NAME }; + BlockBranchTarget m_targetBranchTaken{ INVALID_BLOCK_NAME }; + + }; + +} diff --git a/src/util/Zir/Core/ZpIRDebug.h b/src/util/Zir/Core/ZpIRDebug.h new file mode 100644 index 00000000..be99ee8b --- /dev/null +++ b/src/util/Zir/Core/ZpIRDebug.h @@ -0,0 +1,39 @@ +#pragma once +#include "util/Zir/Core/IR.h" + +namespace ZpIR +{ + + class DebugPrinter + { + + public: + void debugPrint(ZpIRFunction* irFunction); + + void setShowPhysicalRegisters(bool showPhys) + { + m_showPhysicalRegisters = showPhys; + } + + void setVirtualRegisterNameSource(std::string(*getRegisterNameCustom)(ZpIRBasicBlock* block, IRReg r)) + { + m_getRegisterNameCustom = getRegisterNameCustom; + } + + void setPhysicalRegisterNameSource(std::string(*getRegisterNameCustom)(ZpIRBasicBlock* block, ZpIRPhysicalReg r)) + { + m_getPhysicalRegisterNameCustom = getRegisterNameCustom; + } + + private: + std::string getRegisterName(ZpIRBasicBlock* block, IRReg r); + std::string getInstructionHRF(ZpIRBasicBlock* block, IR::__InsBase* cmd); + void debugPrintBlock(ZpIRBasicBlock* block); + + std::string(*m_getRegisterNameCustom)(ZpIRBasicBlock* block, IRReg r) { nullptr }; + std::string(*m_getPhysicalRegisterNameCustom)(ZpIRBasicBlock* block, ZpIRPhysicalReg r) { nullptr }; + + bool m_showPhysicalRegisters{}; // show global/physical register mapping instead of local IRReg indices + }; + +} \ No newline at end of file diff --git a/src/util/Zir/Core/ZpIRPasses.h b/src/util/Zir/Core/ZpIRPasses.h new file mode 100644 index 00000000..023aaac9 --- /dev/null +++ b/src/util/Zir/Core/ZpIRPasses.h @@ -0,0 +1,284 @@ +#pragma once +#include "util/Zir/Core/IR.h" + +namespace ZirPass +{ + class ZpIRPass + { + public: + ZpIRPass(ZpIR::ZpIRFunction* irFunction) : m_irFunction(irFunction) { }; + + virtual void applyPass() = 0; + + protected: + ZpIR::ZpIRFunction* m_irFunction; + }; + + struct RALivenessRange_t + { + RALivenessRange_t(struct RABlock_t* block, ZpIR::IRReg irReg, sint32 start, sint32 end, ZpIR::DataType irDataType); + ~RALivenessRange_t(); + + enum class SOURCE + { + NONE, + INSTRUCTION, // instruction initializes value + PREVIOUS_BLOCK, // imported from previous block(s) + PREVIOUS_RANGE, // from previous range within same block + }; + + enum class LOCATION + { + UNASSIGNED, + PHYSICAL_REGISTER, + SPILLED, + }; + + void setStart(sint32 startIndex); + void setEnd(sint32 endIndex); + + void addSourceFromPreviousBlock(RALivenessRange_t* source) + { + if (m_source != SOURCE::NONE && m_source != SOURCE::PREVIOUS_BLOCK) + assert_dbg(); + m_source = SOURCE::PREVIOUS_BLOCK; + m_sourceRanges.emplace_back(source); + source->m_destinationRanges.emplace_back(this); + } + + void addSourceFromSameBlock(RALivenessRange_t* source) + { + if (m_source != SOURCE::NONE) + assert_dbg(); + m_source = SOURCE::PREVIOUS_RANGE; + m_sourceRanges.emplace_back(source); + source->m_destinationRanges.emplace_back(this); + } + + bool isOverlapping(sint32 start, sint32 end) const + { + return m_startIndex < end && m_endIndex >= start; + } + + bool isOverlapping(RALivenessRange_t* range) const + { + return isOverlapping(range->m_startIndex, range->m_endIndex); + } + + void assignPhysicalRegister(ZpIR::ZpIRPhysicalReg physReg); + + RABlock_t* m_block; + ZpIR::IRReg m_irReg; + ZpIR::DataType m_irDataType; + sint32 m_startIndex{ -1 }; + sint32 m_endIndex{ -1 }; // inclusive + + //std::vector m_reservedPhysRegisters; // unavailable physical registers + std::vector m_overlappingRanges; + + // state / assigned location + LOCATION m_location{ LOCATION::UNASSIGNED }; + sint32 m_physicalRegister; + + // source + SOURCE m_source{ SOURCE::NONE }; + std::vector m_sourceRanges; + + // destination + //RALivenessRange_t* m_destinationRange{ nullptr }; + std::vector m_destinationRanges; + + }; + + struct RABlock_t + { + std::unordered_map livenessRanges; + + struct Compare + { + bool operator()(const RALivenessRange_t* lhs, const RALivenessRange_t* rhs) const /* noexcept */ + { + // order for unassignedRanges + // aka order in which ranges should be assigned physical registers + return lhs->m_startIndex < rhs->m_startIndex; + } + }; + + public: + std::multiset unassignedRanges; + }; + + class RARegular : public ZpIRPass + { + public: + RARegular(ZpIR::ZpIRFunction* irFunction) : ZpIRPass(irFunction) {}; + + void applyPass() + { + prepareRABlocks(); + generateLivenessRanges(); + assignPhysicalRegisters(); + assert_dbg(); // todo -> rewrite doesnt need to be separate any longer since we store a separate physical register index now (in IRReg) + rewrite(); + m_irFunction->state.registersAllocated = true; + } + + + private: + void prepareRABlocks(); + void generateLivenessRanges(); + void assignPhysicalRegisters(); + void rewrite(); + + void assignPhysicalRegistersForBlock(RABlock_t& raBlock); + void rewriteBlock(ZpIR::ZpIRBasicBlock& basicBlock, RABlock_t& raBlock); + + std::span extGetSuitablePhysicalRegisters(ZpIR::DataType dataType) + { + const sint32 OFFSET_U64 = 0; + const sint32 OFFSET_U32 = 16; + + static sint32 _regCandidatesU64[] = { OFFSET_U64 + 0, OFFSET_U64 + 1, OFFSET_U64 + 2, OFFSET_U64 + 3, OFFSET_U64 + 4, OFFSET_U64 + 5, OFFSET_U64 + 6, OFFSET_U64 + 7, OFFSET_U64 + 8, OFFSET_U64 + 9, OFFSET_U64 + 10, OFFSET_U64 + 11, OFFSET_U64 + 12, OFFSET_U64 + 13, OFFSET_U64 + 14, OFFSET_U64 + 15 }; + static sint32 _regCandidatesU32[] = { OFFSET_U32 + 0, OFFSET_U32 + 1, OFFSET_U32 + 2, OFFSET_U32 + 3, OFFSET_U32 + 4, OFFSET_U32 + 5, OFFSET_U32 + 6, OFFSET_U32 + 7, OFFSET_U32 + 8, OFFSET_U32 + 9, OFFSET_U32 + 10, OFFSET_U32 + 11, OFFSET_U32 + 12, OFFSET_U32 + 13, OFFSET_U32 + 14, OFFSET_U32 + 15 }; + + + + if (dataType == ZpIR::DataType::POINTER || dataType == ZpIR::DataType::U64) + return _regCandidatesU64; + if (dataType == ZpIR::DataType::U32) + return _regCandidatesU32; + + //if (dataType != ZpIRDataType::POINTER) + //{ + + //} + assert_dbg(); + + return _regCandidatesU32; + } + + void extFilterPhysicalRegisters(std::vector& physRegCandidates, ZpIR::ZpIRPhysicalReg registerToFilter) + { + // todo - this is quite complex on x86 where registers overlap (e.g. RAX and EAX/AL/AH/AX) + // so registerToFilter can translate to multiple filtered values + + // but for now we use a simplified placeholder implementation + + + if (registerToFilter >= 0 && registerToFilter < 16) + { + physRegCandidates.erase(std::remove(physRegCandidates.begin(), physRegCandidates.end(), (sint32)registerToFilter), physRegCandidates.end()); + physRegCandidates.erase(std::remove(physRegCandidates.begin(), physRegCandidates.end(), (sint32)registerToFilter + 16), physRegCandidates.end()); + } + else if (registerToFilter >= 16 && registerToFilter < 32) + { + physRegCandidates.erase(std::remove(physRegCandidates.begin(), physRegCandidates.end(), (sint32)registerToFilter), physRegCandidates.end()); + physRegCandidates.erase(std::remove(physRegCandidates.begin(), physRegCandidates.end(), (sint32)registerToFilter - 16), physRegCandidates.end()); + } + else + assert_dbg(); + } + + ZpIR::ZpIRPhysicalReg extPickPreferredRegister(std::vector& physRegCandidates) + { + if (physRegCandidates.empty()) + assert_dbg(); + return physRegCandidates[0]; + } + + void debugPrint(RABlock_t& raBlock) + { + std::multiset sortedRanges; + + for (auto& itr : raBlock.livenessRanges) + sortedRanges.emplace(itr.second); + + for (auto& itr : sortedRanges) + { + printf("%04x - %04x reg %04d: ", (uint32)(uint16)itr->m_startIndex, (uint32)(uint16)itr->m_endIndex, (uint32)itr->m_irReg); + + if (itr->m_location == RALivenessRange_t::LOCATION::PHYSICAL_REGISTER) + printf("PHYS_REG %d", (int)itr->m_physicalRegister); + else if (itr->m_location == RALivenessRange_t::LOCATION::UNASSIGNED) + printf("UNASSIGNED"); + else + assert_dbg(); + + printf("\n"); + } + } + + // remove all physical registers from physRegCandidates which are already reserved by any of the overlapping ranges + void filterCandidates(std::vector& physRegCandidates, RALivenessRange_t* range) + { + for (auto& itr : range->m_overlappingRanges) + { + if (itr->m_location != RALivenessRange_t::LOCATION::PHYSICAL_REGISTER) + continue; + extFilterPhysicalRegisters(physRegCandidates, itr->m_physicalRegister); + } + } + + + std::vector m_raBlockArray; + }; + + + class RegisterAllocatorForGLSL : public ZpIRPass + { + enum class PHYS_REG_TYPE : uint8 + { + U32 = 0, + S32 = 1, + F32 = 2 + }; + + public: + RegisterAllocatorForGLSL(ZpIR::ZpIRFunction* irFunction) : ZpIRPass(irFunction) {}; + + void applyPass() + { + assignPhysicalRegisters(); + m_irFunction->state.registersAllocated = true; + } + + static bool IsPhysRegTypeU32(ZpIR::ZpIRPhysicalReg physReg) + { + return ((uint32)physReg >> 30) == (uint32)PHYS_REG_TYPE::U32; + } + + static bool IsPhysRegTypeS32(ZpIR::ZpIRPhysicalReg physReg) + { + return ((uint32)physReg >> 30) == (uint32)PHYS_REG_TYPE::S32; + } + + static bool IsPhysRegTypeF32(ZpIR::ZpIRPhysicalReg physReg) + { + return ((uint32)physReg >> 30) == (uint32)PHYS_REG_TYPE::F32; + } + + static uint32 GetPhysRegIndex(ZpIR::ZpIRPhysicalReg physReg) + { + return (uint32)physReg & 0x3FFFFFFF; + } + + static std::string DebugPrintHelper_getPhysRegisterName(ZpIR::ZpIRBasicBlock* block, ZpIR::ZpIRPhysicalReg r); + + private: + void assignPhysicalRegisters(); + + void assignPhysicalRegistersForBlock(ZpIR::ZpIRBasicBlock* basicBlock); + + uint32 m_physicalRegisterCounterU32{}; + uint32 m_physicalRegisterCounterS32{}; + uint32 m_physicalRegisterCounterF32{}; + + ZpIR::ZpIRPhysicalReg MakePhysReg(PHYS_REG_TYPE regType, uint32 index) + { + uint32 v = (uint32)regType << 30; + v |= index; + return (ZpIR::ZpIRPhysicalReg)v; + } + }; + +}; diff --git a/src/util/Zir/Core/ZpIRScheduler.h b/src/util/Zir/Core/ZpIRScheduler.h new file mode 100644 index 00000000..9c9e4c60 --- /dev/null +++ b/src/util/Zir/Core/ZpIRScheduler.h @@ -0,0 +1,4 @@ +#pragma once +#include "util/Zir/Core/IR.h" +#include "util/Zir/Core/ZirUtility.h" + diff --git a/src/util/Zir/EmitterGLSL/ZpIREmitGLSL.cpp b/src/util/Zir/EmitterGLSL/ZpIREmitGLSL.cpp new file mode 100644 index 00000000..aab2c49e --- /dev/null +++ b/src/util/Zir/EmitterGLSL/ZpIREmitGLSL.cpp @@ -0,0 +1,494 @@ +#include "util/Zir/Core/IR.h" +#include "util/Zir/Core/ZirUtility.h" +#include "util/Zir/Core/ZpIRPasses.h" +#include "util/Zir/EmitterGLSL/ZpIREmitGLSL.h" +#include "util/Zir/Core/ZpIRScheduler.h" + +// string buffer helper class which keeps buffer space at the front and end, allow fast prepend and append +class DualStringBuffer +{ + static constexpr size_t N = 1024; + +public: + DualStringBuffer() : m_offsetBegin(N / 2), m_offsetEnd(N / 2) { } + + ~DualStringBuffer() + { + } + + static_assert(sizeof(char) == sizeof(uint8)); + + void reset() + { + m_offsetBegin = N / 2; + m_offsetEnd = m_offsetBegin; + } + + void append(std::string_view strView) + { + cemu_assert_debug((m_offsetEnd + strView.size()) <= N); + std::memcpy(m_strBuffer + m_offsetEnd, strView.data(), strView.size()); + m_offsetEnd += (uint32)strView.size(); + } + + template + void appendFmt(const char* format_str, Args... args) + { + char* buf = (char*)(m_strBuffer + m_offsetEnd); + char* r = fmt::format_to(buf, format_str, std::forward(args)...); + cemu_assert_debug(r <= (char*)(m_strBuffer + N)); + m_offsetEnd += (uint32)(r - buf); + } + + void prepend(std::string_view strView) + { + assert_dbg(); + } + + size_t size() const + { + return m_offsetEnd - m_offsetBegin; + } + + operator std::string_view() + { + return std::basic_string_view((char*)(m_strBuffer + m_offsetBegin), m_offsetEnd - m_offsetBegin); + } + +private: + //void resizeBuffer(uint32 spaceRequiredFront, uint32 spaceRequiredBack) + //{ + // uint32 newTotalSize = spaceRequiredFront + size() + spaceRequiredBack; + // // round to next multiple of 32 and add extra buffer + // newTotalSize = (newTotalSize + 31) & ~31; + // newTotalSize += (newTotalSize / 4); + // // + //} + + //uint8* m_bufferPtr{ nullptr }; + //size_t m_bufferSize{ 0 }; + //std::vector m_buffer; + uint32 m_offsetBegin; + uint32 m_offsetEnd; + + uint8 m_strBuffer[N]; +}; + + + +namespace ZirEmitter +{ + static const char g_idx_to_element[] = { 'x' , 'y', 'z', 'w'}; + + void GLSL::Emit(ZpIR::ZpIRFunction* irFunction, StringBuf* output) + { + m_irFunction = irFunction; + m_glslSource = output; + + cemu_assert_debug(m_irFunction->m_entryBlocks.size() == 1); + + cemu_assert_debug(m_irFunction->m_basicBlocks.size() == 1); // other sizes are todo + + m_glslSource->add("void main()\r\n{\r\n"); + GenerateBasicBlockCode(*m_irFunction->m_entryBlocks[0]); + m_glslSource->add("}\r\n"); + } + + void GLSL::GenerateBasicBlockCode(ZpIR::ZpIRBasicBlock& basicBlock) + { + // init context +#ifndef PUBLIC_RELEASE + for (auto& itr : m_blockContext.regInlinedExpression) + { + cemu_assert_debug(itr == nullptr); // leaked buffer + } +#endif + m_blockContext.regReadTracking.clear(); + m_blockContext.regReadTracking.resize(basicBlock.m_regs.size()); + m_blockContext.regInlinedExpression.resize(basicBlock.m_regs.size()); + + m_blockContext.currentBasicBlock = &basicBlock; + + // we first do an analysis pass in which we determine the read count for each register + // every register which is only consumed once can be directly inlined instead of storing and referencing it via a variable + ZpIR::IR::__InsBase* instruction = basicBlock.m_instructionFirst; + while (instruction) + { + ZpIR::ZpIRCmdUtil::forEachAccessedReg(basicBlock, instruction, + [this](ZpIR::IRReg readReg) + { + if (readReg >= 0x8000) + assert_dbg(); + // read access + auto& entry = m_blockContext.regReadTracking.at(readReg); + if (entry < 255) + entry++; + }, + [](ZpIR::IRReg writtenReg) + { + }); + + instruction = instruction->next; + } + + // emit GLSL for this block + instruction = basicBlock.m_instructionFirst; + while (instruction) + { + if (auto ins = ZpIR::IR::InsRR::getIfForm(instruction)) + HandleInstruction(ins); + else if (auto ins = ZpIR::IR::InsRRR::getIfForm(instruction)) + HandleInstruction(ins); + else if (auto ins = ZpIR::IR::InsIMPORT::getIfForm(instruction)) + HandleInstruction(ins); + else if (auto ins = ZpIR::IR::InsEXPORT::getIfForm(instruction)) + HandleInstruction(ins); + else + { + assert_dbg(); + } + + instruction = instruction->next; + } + } + + void GLSL::HandleInstruction(ZpIR::IR::InsRR* ins) + { + DualStringBuffer* expressionBuf = GetStringBuffer(); + bool forceNoInline = false; + + switch (ins->opcode) + { + case ZpIR::IR::OpCode::BITCAST: + { + auto srcType = m_blockContext.currentBasicBlock->getRegType(ins->rB); + auto dstType = m_blockContext.currentBasicBlock->getRegType(ins->rA); + + if (srcType == ZpIR::DataType::U32 && dstType == ZpIR::DataType::F32) + expressionBuf->append("uintBitsToFloat("); + else if (srcType == ZpIR::DataType::S32 && dstType == ZpIR::DataType::F32) + expressionBuf->append("intBitsToFloat("); + else if (srcType == ZpIR::DataType::F32 && dstType == ZpIR::DataType::U32) + expressionBuf->append("floatBitsToUint("); + else if (srcType == ZpIR::DataType::F32 && dstType == ZpIR::DataType::S32) + expressionBuf->append("floatBitsToInt("); + else + assert_dbg(); + appendSourceString(expressionBuf, ins->rB); + expressionBuf->append(")"); + break; + } + case ZpIR::IR::OpCode::SWAP_ENDIAN: + { + auto srcType = m_blockContext.currentBasicBlock->getRegType(ins->rB); + auto dstType = m_blockContext.currentBasicBlock->getRegType(ins->rA); + cemu_assert_debug(srcType == dstType); + + // todo - should we store expressionBuf in a temporary variable? We reference it multiple times and reducing complexity would be good + if (srcType == ZpIR::DataType::U32) + { + expressionBuf->append("((("); + appendSourceString(expressionBuf, ins->rB); + expressionBuf->append(")>>24)"); + + expressionBuf->append("|"); + + expressionBuf->append("((("); + appendSourceString(expressionBuf, ins->rB); + expressionBuf->append(")>>8)&0xFF00)"); + + expressionBuf->append("|"); + + expressionBuf->append("((("); + appendSourceString(expressionBuf, ins->rB); + expressionBuf->append(")<<8)&0xFF0000)"); + + expressionBuf->append("|"); + + expressionBuf->append("(("); + appendSourceString(expressionBuf, ins->rB); + expressionBuf->append(")<<24))"); + + // (v>>24)|((v>>8)&0xFF00)|((v<<8)&0xFF0000)|((v<<24)) + } + else + assert_dbg(); + forceNoInline = true; // avoid inlining endian-swapping, since it would add too much complexity to expressions + break; + } + case ZpIR::IR::OpCode::MOV: + appendSourceString(expressionBuf, ins->rB); + break; + case ZpIR::IR::OpCode::CONVERT_FLOAT_TO_INT: + { + auto srcType = m_blockContext.currentBasicBlock->getRegType(ins->rB); + auto dstType = m_blockContext.currentBasicBlock->getRegType(ins->rA); + cemu_assert_debug(srcType == ZpIR::DataType::F32); + cemu_assert_debug(dstType == ZpIR::DataType::S32 || dstType == ZpIR::DataType::U32); + if(dstType == ZpIR::DataType::U32) + expressionBuf->append("uint("); + else + expressionBuf->append("int("); + appendSourceString(expressionBuf, ins->rB); + expressionBuf->append(")"); + break; + } + case ZpIR::IR::OpCode::CONVERT_INT_TO_FLOAT: + { + auto srcType = m_blockContext.currentBasicBlock->getRegType(ins->rB); + auto dstType = m_blockContext.currentBasicBlock->getRegType(ins->rA); + cemu_assert_debug(srcType == ZpIR::DataType::S32 || srcType == ZpIR::DataType::U32); + cemu_assert_debug(dstType == ZpIR::DataType::F32); + expressionBuf->append("float("); + appendSourceString(expressionBuf, ins->rB); + expressionBuf->append(")"); + break; + } + default: + assert_dbg(); + } + AssignResult(ins->rA, expressionBuf, forceNoInline); + } + + void GLSL::HandleInstruction(ZpIR::IR::InsRRR* ins) + { + DualStringBuffer* expressionBuf = GetStringBuffer(); + + switch (ins->opcode) + { + case ZpIR::IR::OpCode::ADD: + { + appendSourceString(expressionBuf, ins->rB); + expressionBuf->append(" + "); + appendSourceString(expressionBuf, ins->rC); + break; + } + case ZpIR::IR::OpCode::MUL: + { + appendSourceString(expressionBuf, ins->rB); + expressionBuf->append(" * "); + appendSourceString(expressionBuf, ins->rC); + break; + } + case ZpIR::IR::OpCode::DIV: + { + appendSourceString(expressionBuf, ins->rB); + expressionBuf->append(" / "); + appendSourceString(expressionBuf, ins->rC); + break; + } + default: + assert_dbg(); + } + AssignResult(ins->rA, expressionBuf); + } + + void GLSL::HandleInstruction(ZpIR::IR::InsIMPORT* ins) + { + ZpIR::ShaderSubset::ShaderImportLocation loc(ins->importSymbol); + DualStringBuffer* buf = GetStringBuffer(); + if (loc.IsUniformRegister()) + { + uint16 index; + loc.GetUniformRegister(index); + // todo - this is complex. Solve via callback + buf->appendFmt("uf_remappedVS[{}].{}", index/4, g_idx_to_element[index&3]); + AssignResult(ins->regArray[0], buf); + } + else if (loc.IsVertexAttribute()) + { + uint16 attributeIndex; + uint16 channelIndex; + loc.GetVertexAttribute(attributeIndex, channelIndex); + + cemu_assert_debug(ins->count == 1); + cemu_assert_debug(ZpIR::isRegVar(ins->regArray[0])); + cemu_assert_debug(channelIndex < 4); + cemu_assert_debug(m_blockContext.currentBasicBlock->getRegType(ins->regArray[0]) == ZpIR::DataType::U32); + + buf->appendFmt("attrDataSem{}.{}", attributeIndex, g_idx_to_element[channelIndex]); + + AssignResult(ins->regArray[0], buf); + } + else + { + cemu_assert_debug(false); + } + } + + void GLSL::HandleInstruction(ZpIR::IR::InsEXPORT* ins) + { + ZpIR::ShaderSubset::ShaderExportLocation loc(ins->exportSymbol); + DualStringBuffer* buf = GetStringBuffer(); + if (loc.IsPosition()) + { + // todo - support for output mask (e.g. xyzw, x_zw) ? + buf->append("SET_POSITION(vec4("); + cemu_assert_debug(ins->count == 4); + for (uint32 i = 0; i < ins->count; i++) + { + if(i > 0) + buf->append(", "); + appendSourceString(buf, ins->regArray[i]); + } + m_glslSource->add(*buf); + m_glslSource->add("));\r\n"); + } + else if (loc.IsOutputAttribute()) + { + uint16 attributeIndex; + loc.GetOutputAttribute(attributeIndex); + buf->appendFmt("passParameterSem{} = vec4(", attributeIndex); + cemu_assert_debug(ins->count == 4); + for (uint32 i = 0; i < ins->count; i++) + { + if (i > 0) + buf->append(", "); + appendSourceString(buf, ins->regArray[i]); + } + m_glslSource->add(*buf); + m_glslSource->add(");\r\n"); + } + else + { + assert_dbg(); + } + ReleaseStringBuffer(buf); + } + + void GLSL::AssignResult(ZpIR::IRReg irReg, DualStringBuffer* buf, bool forceNoInline) + { + if (buf->size() > 100) + forceNoInline = true; // expression too long + + if (m_blockContext.CanInlineRegister(irReg) && !forceNoInline) + { + SetRegInlinedExpression(irReg, buf); + } + else + { + ZpIR::DataType regType = m_blockContext.currentBasicBlock->getRegType(irReg); + if (regType == ZpIR::DataType::F32) + m_glslSource->add("float "); + else if (regType == ZpIR::DataType::S32) + m_glslSource->add("int "); + else if (regType == ZpIR::DataType::U32) + m_glslSource->add("uint "); + else + { + cemu_assert_debug(false); + } + + char regName[16]; + getRegisterName(regName, irReg); + m_glslSource->add(regName); + m_glslSource->add(" = "); + m_glslSource->add(*buf); + m_glslSource->add(";\r\n"); + ReleaseStringBuffer(buf); + } + } + + void GLSL::appendSourceString(DualStringBuffer* buf, ZpIR::IRReg irReg) + { + if (ZpIR::isConstVar(irReg)) + { + ZpIR::IRRegConstDef* constDef = m_blockContext.currentBasicBlock->getConstant(irReg); + if (constDef->type == ZpIR::DataType::U32) + { + buf->appendFmt("{}", constDef->value_u32); + return; + } + else if (constDef->type == ZpIR::DataType::S32) + { + buf->appendFmt("{}", constDef->value_s32); + return; + } + else if (constDef->type == ZpIR::DataType::F32) + { + buf->appendFmt("{}", constDef->value_f32); + return; + } + assert_dbg(); + } + else + { + cemu_assert_debug(ZpIR::isRegVar(irReg)); + uint16 regIndex = ZpIR::getRegIndex(irReg); + DualStringBuffer* expressionBuf = m_blockContext.regInlinedExpression[regIndex]; + if (expressionBuf) + { + buf->append(*expressionBuf); + return; + } + char regName[16]; + getRegisterName(regName, irReg); + buf->append(regName); + } + } + + void GLSL::getRegisterName(char buf[16], ZpIR::IRReg irReg) + { + auto& regData = m_blockContext.currentBasicBlock->m_regs[(uint16)irReg & 0x7FFF]; + cemu_assert_debug(regData.hasAssignedPhysicalRegister()); + ZpIR::ZpIRPhysicalReg physReg = regData.physicalRegister; + + char typeChar; + if (ZirPass::RegisterAllocatorForGLSL::IsPhysRegTypeF32(physReg)) + typeChar = 'f'; + else if (ZirPass::RegisterAllocatorForGLSL::IsPhysRegTypeS32(physReg)) + typeChar = 'i'; + else if (ZirPass::RegisterAllocatorForGLSL::IsPhysRegTypeU32(physReg)) + typeChar = 'u'; + else + { + typeChar = 'x'; + cemu_assert_debug(false); + } + + auto r = fmt::format_to(buf, "r{}{}", ZirPass::RegisterAllocatorForGLSL::GetPhysRegIndex(physReg), typeChar); + *r = '\0'; + } + + void GLSL::SetRegInlinedExpression(ZpIR::IRReg irReg, DualStringBuffer* buf) + { + cemu_assert_debug(ZpIR::isRegVar(irReg)); + uint16 dstIndex = (uint16)irReg; + if (m_blockContext.regInlinedExpression[dstIndex]) + ReleaseStringBuffer(m_blockContext.regInlinedExpression[dstIndex]); + m_blockContext.regInlinedExpression[dstIndex] = buf; + } + + void GLSL::ResetRegInlinedExpression(ZpIR::IRReg irReg) + { + cemu_assert_debug(ZpIR::isRegVar(irReg)); + uint16 dstIndex = (uint16)irReg; + if (m_blockContext.regInlinedExpression[dstIndex]) + { + ReleaseStringBuffer(m_blockContext.regInlinedExpression[dstIndex]); + m_blockContext.regInlinedExpression[dstIndex] = nullptr; + } + } + + DualStringBuffer* GLSL::GetRegInlinedExpression(ZpIR::IRReg irReg) + { + cemu_assert_debug(ZpIR::isRegVar(irReg)); + uint16 dstIndex = (uint16)irReg; + return m_blockContext.regInlinedExpression[dstIndex]; + } + + DualStringBuffer* GLSL::GetStringBuffer() + { + if (m_stringBufferCache.empty()) + return new DualStringBuffer(); + DualStringBuffer* buf = m_stringBufferCache.back(); + m_stringBufferCache.pop_back(); + buf->reset(); + return buf; + } + + void GLSL::ReleaseStringBuffer(DualStringBuffer* buf) + { + m_stringBufferCache.emplace_back(buf); + } +}; diff --git a/src/util/Zir/EmitterGLSL/ZpIREmitGLSL.h b/src/util/Zir/EmitterGLSL/ZpIREmitGLSL.h new file mode 100644 index 00000000..4edf2e04 --- /dev/null +++ b/src/util/Zir/EmitterGLSL/ZpIREmitGLSL.h @@ -0,0 +1,60 @@ +#pragma once +#include "util/Zir/Core/IR.h" +#include "util/Zir/Core/ZpIRPasses.h" +#include "util/helpers/StringBuf.h" + +class DualStringBuffer; + +namespace ZirEmitter +{ + class GLSL + { + public: + GLSL() {}; + + // emit function code and append to output string buffer + void Emit(ZpIR::ZpIRFunction* irFunction, StringBuf* output); + + private: + void GenerateBasicBlockCode(ZpIR::ZpIRBasicBlock& basicBlock); + + void HandleInstruction(ZpIR::IR::InsRR* ins); + void HandleInstruction(ZpIR::IR::InsRRR* ins); + void HandleInstruction(ZpIR::IR::InsIMPORT* ins); + void HandleInstruction(ZpIR::IR::InsEXPORT* ins); + + void appendSourceString(DualStringBuffer* buf, ZpIR::IRReg irReg); + void getRegisterName(char buf[16], ZpIR::IRReg irReg); + + private: + ZpIR::ZpIRFunction* m_irFunction{}; + StringBuf* m_glslSource{}; + + struct + { + ZpIR::ZpIRBasicBlock* currentBasicBlock{ nullptr }; + std::vector regReadTracking; + std::vector regInlinedExpression; + + bool CanInlineRegister(ZpIR::IRReg reg) const + { + cemu_assert_debug(ZpIR::isRegVar(reg)); + return regReadTracking[ZpIR::getRegIndex(reg)] <= 1; + }; + }m_blockContext; + + void AssignResult(ZpIR::IRReg irReg, DualStringBuffer* buf, bool forceNoInline = false); + + // inlined expression cache + void SetRegInlinedExpression(ZpIR::IRReg irReg, DualStringBuffer* buf); + void ResetRegInlinedExpression(ZpIR::IRReg irReg); + DualStringBuffer* GetRegInlinedExpression(ZpIR::IRReg irReg); + + // memory pool for StringBuffer + DualStringBuffer* GetStringBuffer(); + void ReleaseStringBuffer(DualStringBuffer* buf); + + std::vector m_stringBufferCache; + }; + +} \ No newline at end of file diff --git a/src/util/Zir/Passes/RegisterAllocatorForGLSL.cpp b/src/util/Zir/Passes/RegisterAllocatorForGLSL.cpp new file mode 100644 index 00000000..dfae38fc --- /dev/null +++ b/src/util/Zir/Passes/RegisterAllocatorForGLSL.cpp @@ -0,0 +1,65 @@ +#include "util/Zir/Core/IR.h" +#include "util/Zir/Core/ZirUtility.h" +#include "util/Zir/Core/ZpIRPasses.h" +#include "util/Zir/Core/ZpIRDebug.h" + +namespace ZirPass +{ + + void RegisterAllocatorForGLSL::assignPhysicalRegisters() + { + if (m_irFunction->m_basicBlocks.size() != 1) + cemu_assert_unimplemented(); + + for (auto& itr : m_irFunction->m_basicBlocks) + assignPhysicalRegistersForBlock(itr); + } + + void RegisterAllocatorForGLSL::assignPhysicalRegistersForBlock(ZpIR::ZpIRBasicBlock* basicBlock) + { + // resolve imports + for (auto& itr : basicBlock->m_imports) + { + assert_dbg(); // todo - If imported reg not assigned physical register yet -> create a shared physical register (MSB set in reg index?) And assign it to this basic block but also all the shared IRRegs in the other linked basic blocks + // how to handle import: + // - match physical register of every input/output + // - every import must have a matching export in all the previous basic blocks. If not all match this is an error. + // In our shader emitter this could happen if the original R600 code references an uninitialized register + + // note - we also have to make sure the register type matches. If a linked block has a shared register with a different type then we need to create a new register and insert a bitcast instruction in that block + } + // assign a register index to every virtual register + for (auto& itr : basicBlock->m_regs) + { + if (itr.type != ZpIR::DataType::NONE && !itr.hasAssignedPhysicalRegister()) + { + if (itr.type == ZpIR::DataType::F32) + itr.assignPhysicalRegister(MakePhysReg(PHYS_REG_TYPE::F32, m_physicalRegisterCounterF32++)); + else if (itr.type == ZpIR::DataType::S32) + itr.assignPhysicalRegister(MakePhysReg(PHYS_REG_TYPE::S32, m_physicalRegisterCounterS32++)); + else if (itr.type == ZpIR::DataType::U32) + itr.assignPhysicalRegister(MakePhysReg(PHYS_REG_TYPE::U32, m_physicalRegisterCounterU32++)); + else + { + cemu_assert_debug(false); + } + } + } + + } + + std::string RegisterAllocatorForGLSL::DebugPrintHelper_getPhysRegisterName(ZpIR::ZpIRBasicBlock* block, ZpIR::ZpIRPhysicalReg r) + { + std::string s; + uint32 regIndex = GetPhysRegIndex(r); + + if (IsPhysRegTypeF32(r)) + s = fmt::format("r{}f", regIndex); + else if (IsPhysRegTypeU32(r)) + s = fmt::format("r{}u", regIndex); + else if (IsPhysRegTypeS32(r)) + s = fmt::format("r{}i", regIndex); + return s; + } + +} \ No newline at end of file diff --git a/src/util/Zir/Passes/ZpIRRegisterAllocator.cpp b/src/util/Zir/Passes/ZpIRRegisterAllocator.cpp new file mode 100644 index 00000000..10827754 --- /dev/null +++ b/src/util/Zir/Passes/ZpIRRegisterAllocator.cpp @@ -0,0 +1,313 @@ +#include "util/Zir/Core/IR.h" +#include "util/Zir/Core/ZirUtility.h" +#include "util/Zir/Core/ZpIRPasses.h" +#include "util/Zir/Core/ZpIRDebug.h" + +namespace ZirPass +{ + using namespace ZpIR; + + /* + Algorithm description: + + Prepare phase: + Assign every basic block an index + Create internal arrays to match index count + + First phase: + Create liveness ranges for each basic block + Link liveness ranges by import/export + Constrained instructions split affected ranges into their own single instruction liveness range + + Second phase: + Assign registers. Start with constrained ranges first, then process from beginning to end + Whenever we assign a register to a range, we also try to propagate it to all the connected/coalesced ranges + + + A liveness range is described by: + - Source (Can be any of: List of previous basic blocks, liveness range in same basic block) + - Destination (list of liveness ranges) + - Index of basic block + - First instruction (where register is assigned, -1 if passed from previous block) + - Last instruction (where register is last accessed) + - IR-Register (within the same basic block) + During algorithm: + - Spillcost (probably can calculate this dynamically) + - Physical location (-1 if not assigned. Otherwise register index or spill memory offset) + + */ + + RALivenessRange_t::RALivenessRange_t(RABlock_t* block, IRReg irReg, sint32 start, sint32 end, DataType irDataType) : m_block(block), m_irReg(irReg), m_irDataType(irDataType) + { + block->livenessRanges.emplace(irReg, this); + m_startIndex = start; + m_endIndex = end; + // register + for (auto& itr : block->livenessRanges) + { + RALivenessRange_t* itrRange = itr.second; + if (start < itrRange->m_endIndex && end >= itrRange->m_startIndex) + { + m_overlappingRanges.emplace_back(itrRange); + itrRange->m_overlappingRanges.emplace_back(this); + // todo - also immediately flag physical registers as unavailable + } + } + block->unassignedRanges.emplace(this); + } + + RALivenessRange_t::~RALivenessRange_t() + { + for (auto& itr : m_overlappingRanges) + { + RALivenessRange_t* overlappedRange = itr; + // todo - unflag physical register (if this has one set) + overlappedRange->m_overlappingRanges.erase(std::remove(overlappedRange->m_overlappingRanges.begin(), overlappedRange->m_overlappingRanges.end(), overlappedRange), overlappedRange->m_overlappingRanges.end()); + } + m_overlappingRanges.clear(); + assert_dbg(); + } + + + void RALivenessRange_t::setStart(sint32 startIndex) + { + m_startIndex = startIndex; + assert_dbg(); // re-register in sorted range list (if no reg assigned) + } + + void RALivenessRange_t::setEnd(sint32 endIndex) + { + if (endIndex > m_endIndex) + { + // add ranges that are now overlapping + for (auto& itr : m_block->livenessRanges) + { + RALivenessRange_t* itrRange = itr.second; + if(itrRange->isOverlapping(this)) + continue; // was overlapping before + if(itrRange == this) + continue; + if (itrRange->isOverlapping(m_startIndex, endIndex)) + { + m_overlappingRanges.emplace_back(itrRange); + itrRange->m_overlappingRanges.emplace_back(this); + // todo - also immediately flag physical registers as unavailable + } + } + } + else if (endIndex < m_endIndex) + { + // remove ranges that are no longer overlapping + cemu_assert_suspicious(); + } + m_endIndex = endIndex; + } + + void RALivenessRange_t::assignPhysicalRegister(ZpIRPhysicalReg physReg) + { + if (m_location != LOCATION::UNASSIGNED) + cemu_assert_suspicious(); + m_location = LOCATION::PHYSICAL_REGISTER; + m_physicalRegister = physReg; + // remove this from unassignedRanges + auto itr = m_block->unassignedRanges.find(this); + if (itr == m_block->unassignedRanges.end()) + cemu_assert_suspicious(); + if (*itr != this) + cemu_assert_suspicious(); + m_block->unassignedRanges.erase(itr); + } + + + void RARegular::prepareRABlocks() + { + auto& irBasicBlocks = m_irFunction->m_basicBlocks; + + m_raBlockArray.resize(m_irFunction->m_basicBlocks.size()); + } + + void RARegular::generateLivenessRanges() + { + auto& irBasicBlocks = m_irFunction->m_basicBlocks; + //for (auto& itr : irBasicBlocks) + for (uint32 basicBlockIndex = 0; basicBlockIndex < (uint32)irBasicBlocks.size(); basicBlockIndex++) + { + auto& blockItr = irBasicBlocks[basicBlockIndex]; + RABlock_t* raBlock = m_raBlockArray.data() + basicBlockIndex; + std::unordered_map& blockRanges = raBlock->livenessRanges; + // init ranges for imports first + for (auto& regImport : blockItr->m_imports) + { + new RALivenessRange_t(raBlock, regImport.reg, -1, -1, blockItr->m_regs[(uint16)regImport.reg].type); + // imports start before the current basic block + } + // parse instructions and create/update ranges + IR::__InsBase* ins = blockItr->m_instructionFirst; + size_t i = 0; + while(ins) + { + ZpIRCmdUtil::forEachAccessedReg(*blockItr, ins, + [&blockRanges, i, raBlock](IRReg readReg) + { + if (readReg >= 0x8000) + cemu_assert_suspicious(); + // read access + auto livenessRange = blockRanges.find(readReg); + if (livenessRange == blockRanges.end()) + cemu_assert_suspicious(); + livenessRange->second->setEnd((sint32)i); + }, + [&blockRanges, i, raBlock, blockItr](IRReg writtenReg) + { + if (writtenReg >= 0x8000) + cemu_assert_suspicious(); + // write access + auto livenessRange = blockRanges.find(writtenReg); + if (livenessRange != blockRanges.end()) + cemu_assert_suspicious(); + new RALivenessRange_t(raBlock, writtenReg, (sint32)i, (sint32)i, blockItr->m_regs[(uint16)writtenReg].type); + }); + i++; + ins = ins->next; + } + // exports extend ranges to one instruction past the end of the block + for (auto& regExport : blockItr->m_exports) + { + auto livenessRange = blockRanges.find(regExport.reg); + if (livenessRange == blockRanges.end()) + cemu_assert_suspicious(); + cemu_assert_unimplemented(); + //livenessRange->second->setEnd((sint32)blockItr->m_cmdsDepr.size()); + } + } + // connect liveness ranges across basic blocks based on their import/export names + std::unordered_map listExportedRanges; + for (uint32 basicBlockIndex = 0; basicBlockIndex < (uint32)irBasicBlocks.size(); basicBlockIndex++) + { + // for each block take all exported ranges and connect them to the imports of the successor blocks + auto& blockItr = irBasicBlocks[basicBlockIndex]; + // collect all exported liveness ranges + std::unordered_map& localRanges = m_raBlockArray[basicBlockIndex].livenessRanges; + listExportedRanges.clear(); + for (auto& regExport : blockItr->m_exports) + { + auto livenessRange = localRanges.find(regExport.reg); + if (livenessRange == localRanges.end()) + assert_dbg(); + listExportedRanges.emplace(regExport.name, livenessRange->second); + } + // handle imports in the connected blocks + if (blockItr->m_branchTaken) + { + ZpIRBasicBlock* successorBlock = blockItr->m_branchTaken; + std::unordered_map& successorRanges = localRanges = m_raBlockArray[basicBlockIndex].livenessRanges; + for (auto& regImport : successorBlock->m_exports) + { + auto livenessRange = successorRanges.find(regImport.reg); + if (livenessRange == successorRanges.end()) + assert_dbg(); + auto connectedSourceRange = listExportedRanges.find(regImport.name); + if (connectedSourceRange == listExportedRanges.end()) + assert_dbg(); + livenessRange->second->addSourceFromPreviousBlock(connectedSourceRange->second); + } + } + // handle imports for entry blocks + // todo + // handle export for exit blocks + // todo + } + } + + void RARegular::assignPhysicalRegistersForBlock(RABlock_t& raBlock) + { + debugPrint(raBlock); + std::vector physRegCandidates; + physRegCandidates.reserve(32); + // process livenessRanges ascending by start address + while (!raBlock.unassignedRanges.empty()) + { + RALivenessRange_t* range = *raBlock.unassignedRanges.begin(); + // get a list of potential physical registers + std::span physReg = extGetSuitablePhysicalRegisters(range->m_irDataType); + + physRegCandidates.clear(); + for (auto& r : physReg) + physRegCandidates.emplace_back(r); + + // try to find a physical register that we can assign to the entire liveness span (current range and all connected ranges) + // todo + // handle special cases like copy coalescing + // todo + // try to find a register for only the current range + + filterCandidates(physRegCandidates, range); + if (!physRegCandidates.empty()) + { + // pick preferred register + ZpIRPhysicalReg physRegister = extPickPreferredRegister(physRegCandidates); + range->assignPhysicalRegister(physRegister); + continue; + } + + // spill is necessary + assert_dbg(); + + + assert_dbg(); + + } + + printf("Assigned:\n"); + debugPrint(raBlock); + } + + void RARegular::assignPhysicalRegisters() + { + // todo - first we should assign all the fixed registers. E.g. imports/exports, constrained instructions + + for (auto& raBlockInfo : m_raBlockArray) + assignPhysicalRegistersForBlock(raBlockInfo); + } + + void RARegular::rewrite() + { + for (size_t i = 0; i < m_raBlockArray.size(); i++) + rewriteBlock(*m_irFunction->m_basicBlocks[i], m_raBlockArray[i]); + } + + void RARegular::rewriteBlock(ZpIRBasicBlock& basicBlock, RABlock_t& raBlock) + { + assert_dbg(); + //std::vector cmdOut; + + //std::unordered_map translationTable; + //for (auto& itr : raBlock.livenessRanges) + // translationTable.emplace(itr.second->m_irReg, itr.second->m_physicalRegister); + //// todo - since ir var registers are created in incremental order we could instead use a std::vector for fast look-up instead of a map? + + //for (uint32 i = 0; i < (uint32)basicBlock.m_cmdsDepr.size(); i++) + //{ + // // todo - insert spill and load instructions + // // todo - insert register moves for range-to-range copies + // + // ZpIRCmd* currentCmd = basicBlock.m_cmdsDepr.data() + i; + // // replace registers and then insert into output command list + // ZpIRCmdUtil::replaceRegisters(*currentCmd, translationTable); + // cmdOut.emplace_back(*currentCmd); + //} + + //basicBlock.m_cmdsDepr = std::move(cmdOut); + + // todo - should we keep imports/exports but update them to use physical register indices? + // the code emitter needs to know which physical registers are exported in order to determine which optimizations are allowed + + basicBlock.m_imports.clear(); + basicBlock.m_imports.shrink_to_fit(); + basicBlock.m_exports.clear(); + basicBlock.m_exports.shrink_to_fit(); + basicBlock.m_regs.clear(); + basicBlock.m_regs.shrink_to_fit(); + } + +} diff --git a/src/util/boost/bluetooth.h b/src/util/boost/bluetooth.h new file mode 100644 index 00000000..de2bda54 --- /dev/null +++ b/src/util/boost/bluetooth.h @@ -0,0 +1,109 @@ +#pragma once + +#include "platform/platform.h" + +#include + +namespace boost +{ + namespace asio + { + template + class device_endpoint + { + public: + typedef Protocol protocol_type; + typedef detail::socket_addr_type data_type; + + struct device_t + { + device_t(long long device_addr) + : addr(device_addr) + { + } + + long long addr; + }; + + device_endpoint() + { + memset(&addr, 0x00, sizeof(addr)); + } + + device_endpoint(device_t device_address) + { + memset(&addr, 0x00, sizeof(addr)); + + addr.addressFamily = AF_BLUETOOTH; + addr.btAddr = id.addr; + addr.serviceClassId = RFCOMM_PROTOCOL_UUID; + addr.port = BT_PORT_ANY; + } + + device_endpoint(const device_endpoint& other) + : addr(other.addr) + { + } + + device_endpoint& operator=(const device_endpoint& other) + { + addr = other.addr; + return *this; + } + + protocol_type protocol() const + { + return protocol_type(); + } + + data_type* data() + { + return reinterpret_cast(&addr); + } + + const data_type* data() const + { + return reinterpret_cast(&addr); + } + + size_t size() const + { + return sizeof(SOCKADDR_BTH); + } + + size_t capacity() const + { + return size(); + } + + private: + SOCKADDR_BTH addr; + }; + + class bluetooth + { + public: + using endpoint = device_endpoint; + using socket = basic_stream_socket; + using acceptor = basic_socket_acceptor; + using iostream = basic_socket_iostream; + + bluetooth() = default; + + int type() const + { + return SOCK_STREAM; + } + + int protocol() const + { + return BTPROTO_RFCOMM; + } + + int family() const + { + return AF_BLUETOOTH; + } + }; + } +} diff --git a/src/util/containers/IntervalBucketContainer.h b/src/util/containers/IntervalBucketContainer.h new file mode 100644 index 00000000..e48d305e --- /dev/null +++ b/src/util/containers/IntervalBucketContainer.h @@ -0,0 +1,100 @@ +#pragma once + +template +class IntervalBucketContainer +{ + struct bucketEntry_t + { + TAddr rangeStart; + TAddr rangeEnd; + TData* data; + int bucketStartIndex; + bucketEntry_t(TAddr rangeStart, TAddr rangeEnd, TData* data, int bucketStartIndex) : rangeStart(rangeStart), rangeEnd(rangeEnd), data(data), bucketStartIndex(bucketStartIndex) {}; + }; + std::vector list_bucket[TBucketCount]; + +public: + + IntervalBucketContainer() {}; + + // range is defined as inclusive rangeStart and exclusive rangeEnd + void addRange(TAddr rangeStart, TAddr rangeEnd, TData* data) + { + assert(rangeStart < rangeEnd); + int bucketStartIndex = (rangeStart / TAddressGranularity); + int bucketEndIndex = ((rangeEnd + TAddressGranularity - 1) / TAddressGranularity); + int bucketItrCount = bucketEndIndex - bucketStartIndex; + bucketStartIndex %= TBucketCount; + int bucketFirstIndex = bucketStartIndex; + bucketItrCount = std::min(bucketItrCount, TBucketCount); + assert(bucketItrCount != 0); + while (bucketItrCount--) + { + list_bucket[bucketStartIndex].emplace_back(rangeStart, rangeEnd, data, bucketFirstIndex); + bucketStartIndex = (bucketStartIndex + 1) % TBucketCount; + } + } + + void removeRange(TAddr rangeStart, TAddr rangeEnd, TData* data) + { + assert(rangeStart < rangeEnd); + int bucketStartIndex = (rangeStart / TAddressGranularity); + int bucketEndIndex = ((rangeEnd + TAddressGranularity - 1) / TAddressGranularity); + int bucketItrCount = bucketEndIndex - bucketStartIndex; + bucketStartIndex %= TBucketCount; + bucketItrCount = std::min(bucketItrCount, TBucketCount); + assert(bucketItrCount != 0); + int eraseCountVerifier = bucketItrCount; + while (bucketItrCount--) + { + for (auto it = list_bucket[bucketStartIndex].begin(); it != list_bucket[bucketStartIndex].end(); it++) + { + if (it->data == data) + { + assert(it->rangeStart == rangeStart && it->rangeEnd == rangeEnd); + // erase + list_bucket[bucketStartIndex].erase(it); + eraseCountVerifier--; + break; + } + } + bucketStartIndex = (bucketStartIndex + 1) % TBucketCount; + } + assert(eraseCountVerifier == 0); // triggers if rangeStart/End doesn't match up with any registered range + } + + template + void lookupRanges(TAddr rangeStart, TAddr rangeEnd, TRangeCallback cb) + { + assert(rangeStart < rangeEnd); + int bucketStartIndex = (rangeStart / TAddressGranularity); + int bucketEndIndex = ((rangeEnd + TAddressGranularity - 1) / TAddressGranularity); + int bucketItrCount = bucketEndIndex - bucketStartIndex; + bucketStartIndex %= TBucketCount; + bucketItrCount = std::min(bucketItrCount, TBucketCount); + assert(bucketItrCount != 0); + // in first round we dont need to check if bucket was already visited + for (auto& itr : list_bucket[bucketStartIndex]) + { + if (itr.rangeStart < rangeEnd && itr.rangeEnd > rangeStart) + { + cb(itr.data); + } + } + bucketItrCount--; + bucketStartIndex = (bucketStartIndex + 1) % TBucketCount; + // for remaining buckets check if the range starts in the current bucket + while (bucketItrCount--) + { + for (auto& itr : list_bucket[bucketStartIndex]) + { + if (itr.rangeStart < rangeEnd && itr.rangeEnd > rangeStart && itr.bucketStartIndex == bucketStartIndex) + { + cb(itr.data); + } + } + bucketStartIndex = (bucketStartIndex + 1) % TBucketCount; + } + } + +}; \ No newline at end of file diff --git a/src/util/containers/LookupTableL3.h b/src/util/containers/LookupTableL3.h new file mode 100644 index 00000000..2d93dc06 --- /dev/null +++ b/src/util/containers/LookupTableL3.h @@ -0,0 +1,91 @@ +#pragma once + +// staged lookup table suited for cases where the lookup index range can be very large (e.g. memory addresses) +// performs 3 consecutive table lookups, where each table's width is defined by TBitsN +// empty subtables consume no memory beyond the initial two default tables for TBitsY and TBitsZ +template +class LookupTableL3 +{ + struct TableZ // z lookup + { + T arr[1 << TBitsZ]{}; + }; + + struct TableY // y lookup + { + TableZ* arr[1 << TBitsY]; + }; + + // by generating placeholder tables we can avoid conditionals in the lookup code since no null-pointer checking is necessary + TableY* m_placeholderTableY{}; + TableZ* m_placeholderTableZ{}; + + +public: + LookupTableL3() + { + // init placeholder table Z + m_placeholderTableZ = GenerateNewTableZ(); + // init placeholder table Y (all entries point to placeholder table Z) + m_placeholderTableY = GenerateNewTableY(); + // init x table + for (auto& itr : m_tableXArr) + itr = m_placeholderTableY; + } + + ~LookupTableL3() + { + delete m_placeholderTableY; + delete m_placeholderTableZ; + } + + // lookup + // only the bottom most N bits bits are used of the offset + // N = TBitsX + TBitsY + TBitsZ + // if no match is found a default-constructed object is returned + T lookup(uint32 offset) + { + uint32 indexZ = offset & ((1u << TBitsZ) - 1); + offset >>= TBitsZ; + uint32 indexY = offset & ((1u << TBitsY) - 1); + offset >>= TBitsY; + uint32 indexX = offset & ((1u << TBitsX) - 1); + //offset >>= TBitsX; + return m_tableXArr[indexX]->arr[indexY]->arr[indexZ]; + } + + void store(uint32 offset, T& t) + { + uint32 indexZ = offset & ((1u << TBitsZ) - 1); + offset >>= TBitsZ; + uint32 indexY = offset & ((1u << TBitsY) - 1); + offset >>= TBitsY; + uint32 indexX = offset & ((1u << TBitsX) - 1); + if (m_tableXArr[indexX] == m_placeholderTableY) + m_tableXArr[indexX] = GenerateNewTableY(); + TableY* lookupY = m_tableXArr[indexX]; + if (lookupY->arr[indexY] == m_placeholderTableZ) + lookupY->arr[indexY] = GenerateNewTableZ(); + TableZ* lookupZ = lookupY->arr[indexY]; + lookupZ->arr[indexZ] = t; + } + +private: + // generate a new Y lookup table which will initially contain only pointers to m_placeholderTableZ + TableY* GenerateNewTableY() + { + TableY* tableY = new TableY(); + for (auto& itr : tableY->arr) + itr = m_placeholderTableZ; + return tableY; + } + + // generate a new Z lookup table which will initially contain only default constructed T + TableZ* GenerateNewTableZ() + { + TableZ* tableZ = new TableZ(); + return tableZ; + } + + TableY* m_tableXArr[1 << TBitsX]; // x lookup +}; diff --git a/src/util/containers/RangeStore.h b/src/util/containers/RangeStore.h new file mode 100644 index 00000000..d1167b29 --- /dev/null +++ b/src/util/containers/RangeStore.h @@ -0,0 +1,137 @@ +#pragma once + +template +class RangeStore +{ +public: + + typedef struct + { + _ADDR start; + _ADDR end; + _OBJ data; + size_t lastIterationIndex; + }rangeEntry_t; + + RangeStore() + { + + } + + size_t getBucket(_ADDR addr) + { + size_t index = addr / granularity; + index %= count; + return index; + } + + void getBucketRange(_ADDR addrStart, _ADDR addrEnd, size_t& bucketFirst, size_t& bucketCount) + { + bucketFirst = getBucket(addrStart); + size_t indexStart = addrStart / granularity; + size_t indexEnd = std::max(addrStart, addrEnd - 1) / granularity; + bucketCount = indexEnd - indexStart + 1; + } + + // end address should be supplied as start+size + void* storeRange(_OBJ data, _ADDR start, _ADDR end) + { + size_t bucketFirst; + size_t bucketCount; + getBucketRange(start, end, bucketFirst, bucketCount); + bucketCount = std::min(bucketCount, count); + + // create range + rangeEntry_t* rangeEntry = new rangeEntry_t(); + rangeEntry->data = data; + rangeEntry->start = start; + rangeEntry->end = end; + rangeEntry->lastIterationIndex = currentIterationIndex; + + // register range in every bucket it touches + size_t idx = bucketFirst; + for (size_t i = 0; i < bucketCount; i++) + { + rangeBuckets[idx].list_ranges.push_back(rangeEntry); + idx = (idx + 1) % count; + } + + return rangeEntry; + } + + void deleteRange(void* rangePtr) + { + rangeEntry_t* rangeEntry = (rangeEntry_t*)rangePtr; + // get bucket range + size_t bucketFirst; + size_t bucketCount; + getBucketRange(rangeEntry->start, rangeEntry->end, bucketFirst, bucketCount); + bucketCount = std::min(bucketCount, count); + // remove from buckets + size_t idx = bucketFirst; + for (size_t i = 0; i < bucketCount; i++) + { + rangeBuckets[idx].list_ranges.erase(std::remove(rangeBuckets[idx].list_ranges.begin(), rangeBuckets[idx].list_ranges.end(), rangeEntry), rangeBuckets[idx].list_ranges.end()); + idx = (idx + 1) % count; + } + delete rangeEntry; + } + + void findRanges(_ADDR start, _ADDR end, std::function f) + { + currentIterationIndex++; + size_t bucketFirst; + size_t bucketCount; + getBucketRange(start, end, bucketFirst, bucketCount); + bucketCount = std::min(bucketCount, count); + size_t idx = bucketFirst; + for (size_t i = 0; i < bucketCount; i++) + { + for (auto r : rangeBuckets[idx].list_ranges) + { + if (start < r->end && end > r->start && r->lastIterationIndex != currentIterationIndex) + { + r->lastIterationIndex = currentIterationIndex; + f(r->start, r->end, r->data); + } + } + idx = (idx + 1) % count; + } + } + + bool findFirstRange(_ADDR start, _ADDR end, _ADDR& rStart, _ADDR& rEnd, _OBJ& rData) + { + currentIterationIndex++; + size_t bucketFirst; + size_t bucketCount; + getBucketRange(start, end, bucketFirst, bucketCount); + bucketCount = std::min(bucketCount, count); + size_t idx = bucketFirst; + for (size_t i = 0; i < bucketCount; i++) + { + for (auto r : rangeBuckets[idx].list_ranges) + { + if (start < r->end && end > r->start && r->lastIterationIndex != currentIterationIndex) + { + r->lastIterationIndex = currentIterationIndex; + rStart = r->start; + rEnd = r->end; + rData = r->data; + return true; + } + } + idx = (idx + 1) % count; + } + return false; + } + +private: + typedef struct + { + std::vector list_ranges; + }rangeBucket_t; + + std::array rangeBuckets; + + size_t currentIterationIndex; +}; diff --git a/src/util/containers/SmallBitset.h b/src/util/containers/SmallBitset.h new file mode 100644 index 00000000..87fae9d8 --- /dev/null +++ b/src/util/containers/SmallBitset.h @@ -0,0 +1,34 @@ + +// optimized and compact version of std::bitset with no error checking in release mode +// uses a single uint32 to store the bitmask, thus allowing up to 32 bool values + +template +class SmallBitset +{ +public: + SmallBitset() {}; + static_assert(N <= 32); + + bool test(size_t index) const + { + cemu_assert_debug(index < N); + return ((m_bits >> index) & 1) != 0; + } + + void set(size_t index, bool val) + { + cemu_assert_debug(index < N); + m_bits &= ~(1u << index); + if (val) + m_bits |= (1u << index); + } + + void set(size_t index) + { + cemu_assert_debug(index < N); + m_bits |= (1u << index); + } + +private: + uint32 m_bits{}; +}; \ No newline at end of file diff --git a/src/util/containers/flat_hash_map.hpp b/src/util/containers/flat_hash_map.hpp new file mode 100644 index 00000000..3dee0b16 --- /dev/null +++ b/src/util/containers/flat_hash_map.hpp @@ -0,0 +1,1495 @@ +// Copyright Malte Skarupke 2017. +// Distributed under the Boost Software License, Version 1.0. +// (See http://www.boost.org/LICENSE_1_0.txt) + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#define SKA_NOINLINE(...) __declspec(noinline) __VA_ARGS__ +#else +#define SKA_NOINLINE(...) __VA_ARGS__ __attribute__((noinline)) +#endif + +namespace ska +{ + struct prime_number_hash_policy; + struct power_of_two_hash_policy; + struct fibonacci_hash_policy; + + namespace detailv3 + { + template + struct functor_storage : Functor + { + functor_storage() = default; + functor_storage(const Functor& functor) + : Functor(functor) + { + } + template + Result operator()(Args &&... args) + { + return static_cast(*this)(std::forward(args)...); + } + template + Result operator()(Args &&... args) const + { + return static_cast(*this)(std::forward(args)...); + } + }; + template + struct functor_storage + { + typedef Result(*function_ptr)(Args...); + function_ptr function; + functor_storage(function_ptr function) + : function(function) + { + } + Result operator()(Args... args) const + { + return function(std::forward(args)...); + } + operator function_ptr& () + { + return function; + } + operator const function_ptr& () + { + return function; + } + }; + template + struct KeyOrValueHasher : functor_storage + { + typedef functor_storage hasher_storage; + KeyOrValueHasher() = default; + KeyOrValueHasher(const hasher& hash) + : hasher_storage(hash) + { + } + size_t operator()(const key_type& key) + { + return static_cast(*this)(key); + } + size_t operator()(const key_type& key) const + { + return static_cast(*this)(key); + } + size_t operator()(const value_type& value) + { + return static_cast(*this)(value.first); + } + size_t operator()(const value_type& value) const + { + return static_cast(*this)(value.first); + } + template + size_t operator()(const std::pair& value) + { + return static_cast(*this)(value.first); + } + template + size_t operator()(const std::pair& value) const + { + return static_cast(*this)(value.first); + } + }; + template + struct KeyOrValueEquality : functor_storage + { + typedef functor_storage equality_storage; + KeyOrValueEquality() = default; + KeyOrValueEquality(const key_equal& equality) + : equality_storage(equality) + { + } + bool operator()(const key_type& lhs, const key_type& rhs) + { + return static_cast(*this)(lhs, rhs); + } + bool operator()(const key_type& lhs, const value_type& rhs) + { + return static_cast(*this)(lhs, rhs.first); + } + bool operator()(const value_type& lhs, const key_type& rhs) + { + return static_cast(*this)(lhs.first, rhs); + } + bool operator()(const value_type& lhs, const value_type& rhs) + { + return static_cast(*this)(lhs.first, rhs.first); + } + template + bool operator()(const key_type& lhs, const std::pair& rhs) + { + return static_cast(*this)(lhs, rhs.first); + } + template + bool operator()(const std::pair& lhs, const key_type& rhs) + { + return static_cast(*this)(lhs.first, rhs); + } + template + bool operator()(const value_type& lhs, const std::pair& rhs) + { + return static_cast(*this)(lhs.first, rhs.first); + } + template + bool operator()(const std::pair& lhs, const value_type& rhs) + { + return static_cast(*this)(lhs.first, rhs.first); + } + template + bool operator()(const std::pair& lhs, const std::pair& rhs) + { + return static_cast(*this)(lhs.first, rhs.first); + } + }; + static constexpr int8_t min_lookups = 4; + template + struct sherwood_v3_entry + { + sherwood_v3_entry() + { + } + sherwood_v3_entry(int8_t distance_from_desired) + : distance_from_desired(distance_from_desired) + { + } + ~sherwood_v3_entry() + { + } + static sherwood_v3_entry* empty_default_table() + { + static sherwood_v3_entry result[min_lookups] = { {}, {}, {}, {special_end_value} }; + return result; + } + + bool has_value() const + { + return distance_from_desired >= 0; + } + bool is_empty() const + { + return distance_from_desired < 0; + } + bool is_at_desired_position() const + { + return distance_from_desired <= 0; + } + template + void emplace(int8_t distance, Args &&... args) + { + new (std::addressof(value)) T(std::forward(args)...); + distance_from_desired = distance; + } + + void destroy_value() + { + value.~T(); + distance_from_desired = -1; + } + + int8_t distance_from_desired = -1; + static constexpr int8_t special_end_value = 0; + union { T value; }; + }; + + inline int8_t log2(size_t value) + { + static constexpr int8_t table[64] = + { + 63, 0, 58, 1, 59, 47, 53, 2, + 60, 39, 48, 27, 54, 33, 42, 3, + 61, 51, 37, 40, 49, 18, 28, 20, + 55, 30, 34, 11, 43, 14, 22, 4, + 62, 57, 46, 52, 38, 26, 32, 41, + 50, 36, 17, 19, 29, 10, 13, 21, + 56, 45, 25, 31, 35, 16, 9, 12, + 44, 24, 15, 8, 23, 7, 6, 5 + }; + value |= value >> 1; + value |= value >> 2; + value |= value >> 4; + value |= value >> 8; + value |= value >> 16; + value |= value >> 32; + return table[((value - (value >> 1)) * 0x07EDD5E59A4E28C2) >> 58]; + } + + template + struct AssignIfTrue + { + void operator()(T& lhs, const T& rhs) + { + lhs = rhs; + } + void operator()(T& lhs, T&& rhs) + { + lhs = std::move(rhs); + } + }; + template + struct AssignIfTrue + { + void operator()(T&, const T&) + { + } + void operator()(T&, T&&) + { + } + }; + + inline size_t next_power_of_two(size_t i) + { + --i; + i |= i >> 1; + i |= i >> 2; + i |= i >> 4; + i |= i >> 8; + i |= i >> 16; + i |= i >> 32; + ++i; + return i; + } + + template using void_t = void; + + template + struct HashPolicySelector + { + typedef fibonacci_hash_policy type; + }; + template + struct HashPolicySelector> + { + typedef typename T::hash_policy type; + }; + + template + class sherwood_v3_table : private EntryAlloc, private Hasher, private Equal + { + using Entry = detailv3::sherwood_v3_entry; + using AllocatorTraits = std::allocator_traits; + using EntryPointer = typename AllocatorTraits::pointer; + struct convertible_to_iterator; + + public: + + using value_type = T; + using size_type = size_t; + using difference_type = std::ptrdiff_t; + using hasher = ArgumentHash; + using key_equal = ArgumentEqual; + using allocator_type = EntryAlloc; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = value_type*; + using const_pointer = const value_type*; + + sherwood_v3_table() + { + } + explicit sherwood_v3_table(size_type bucket_count, const ArgumentHash& hash = ArgumentHash(), const ArgumentEqual& equal = ArgumentEqual(), const ArgumentAlloc& alloc = ArgumentAlloc()) + : EntryAlloc(alloc), Hasher(hash), Equal(equal) + { + rehash(bucket_count); + } + sherwood_v3_table(size_type bucket_count, const ArgumentAlloc& alloc) + : sherwood_v3_table(bucket_count, ArgumentHash(), ArgumentEqual(), alloc) + { + } + sherwood_v3_table(size_type bucket_count, const ArgumentHash& hash, const ArgumentAlloc& alloc) + : sherwood_v3_table(bucket_count, hash, ArgumentEqual(), alloc) + { + } + explicit sherwood_v3_table(const ArgumentAlloc& alloc) + : EntryAlloc(alloc) + { + } + template + sherwood_v3_table(It first, It last, size_type bucket_count = 0, const ArgumentHash& hash = ArgumentHash(), const ArgumentEqual& equal = ArgumentEqual(), const ArgumentAlloc& alloc = ArgumentAlloc()) + : sherwood_v3_table(bucket_count, hash, equal, alloc) + { + insert(first, last); + } + template + sherwood_v3_table(It first, It last, size_type bucket_count, const ArgumentAlloc& alloc) + : sherwood_v3_table(first, last, bucket_count, ArgumentHash(), ArgumentEqual(), alloc) + { + } + template + sherwood_v3_table(It first, It last, size_type bucket_count, const ArgumentHash& hash, const ArgumentAlloc& alloc) + : sherwood_v3_table(first, last, bucket_count, hash, ArgumentEqual(), alloc) + { + } + sherwood_v3_table(std::initializer_list il, size_type bucket_count = 0, const ArgumentHash& hash = ArgumentHash(), const ArgumentEqual& equal = ArgumentEqual(), const ArgumentAlloc& alloc = ArgumentAlloc()) + : sherwood_v3_table(bucket_count, hash, equal, alloc) + { + if (bucket_count == 0) + rehash(il.size()); + insert(il.begin(), il.end()); + } + sherwood_v3_table(std::initializer_list il, size_type bucket_count, const ArgumentAlloc& alloc) + : sherwood_v3_table(il, bucket_count, ArgumentHash(), ArgumentEqual(), alloc) + { + } + sherwood_v3_table(std::initializer_list il, size_type bucket_count, const ArgumentHash& hash, const ArgumentAlloc& alloc) + : sherwood_v3_table(il, bucket_count, hash, ArgumentEqual(), alloc) + { + } + sherwood_v3_table(const sherwood_v3_table& other) + : sherwood_v3_table(other, AllocatorTraits::select_on_container_copy_construction(other.get_allocator())) + { + } + sherwood_v3_table(const sherwood_v3_table& other, const ArgumentAlloc& alloc) + : EntryAlloc(alloc), Hasher(other), Equal(other), _max_load_factor(other._max_load_factor) + { + rehash_for_other_container(other); + try + { + insert(other.begin(), other.end()); + } + catch (...) + { + clear(); + deallocate_data(entries, num_slots_minus_one, max_lookups); + throw; + } + } + sherwood_v3_table(sherwood_v3_table&& other) noexcept + : EntryAlloc(std::move(other)), Hasher(std::move(other)), Equal(std::move(other)) + { + swap_pointers(other); + } + sherwood_v3_table(sherwood_v3_table&& other, const ArgumentAlloc& alloc) noexcept + : EntryAlloc(alloc), Hasher(std::move(other)), Equal(std::move(other)) + { + swap_pointers(other); + } + sherwood_v3_table& operator=(const sherwood_v3_table& other) + { + if (this == std::addressof(other)) + return *this; + + clear(); + if (AllocatorTraits::propagate_on_container_copy_assignment::value) + { + if (static_cast(*this) != static_cast(other)) + { + reset_to_empty_state(); + } + AssignIfTrue()(*this, other); + } + _max_load_factor = other._max_load_factor; + static_cast(*this) = other; + static_cast(*this) = other; + rehash_for_other_container(other); + insert(other.begin(), other.end()); + return *this; + } + sherwood_v3_table& operator=(sherwood_v3_table&& other) noexcept + { + if (this == std::addressof(other)) + return *this; + else if (AllocatorTraits::propagate_on_container_move_assignment::value) + { + clear(); + reset_to_empty_state(); + AssignIfTrue()(*this, std::move(other)); + swap_pointers(other); + } + else if (static_cast(*this) == static_cast(other)) + { + swap_pointers(other); + } + else + { + clear(); + _max_load_factor = other._max_load_factor; + rehash_for_other_container(other); + for (T& elem : other) + emplace(std::move(elem)); + other.clear(); + } + static_cast(*this) = std::move(other); + static_cast(*this) = std::move(other); + return *this; + } + ~sherwood_v3_table() + { + clear(); + deallocate_data(entries, num_slots_minus_one, max_lookups); + } + + const allocator_type& get_allocator() const + { + return static_cast(*this); + } + const ArgumentEqual& key_eq() const + { + return static_cast(*this); + } + const ArgumentHash& hash_function() const + { + return static_cast(*this); + } + + template + struct templated_iterator + { + templated_iterator() = default; + templated_iterator(EntryPointer current) + : current(current) + { + } + EntryPointer current = EntryPointer(); + + using iterator_category = std::forward_iterator_tag; + using value_type = ValueType; + using difference_type = ptrdiff_t; + using pointer = ValueType*; + using reference = ValueType&; + + friend bool operator==(const templated_iterator& lhs, const templated_iterator& rhs) + { + return lhs.current == rhs.current; + } + friend bool operator!=(const templated_iterator& lhs, const templated_iterator& rhs) + { + return !(lhs == rhs); + } + + templated_iterator& operator++() + { + do + { + ++current; + } while (current->is_empty()); + return *this; + } + templated_iterator operator++(int) + { + templated_iterator copy(*this); + ++* this; + return copy; + } + + ValueType& operator*() const + { + return current->value; + } + ValueType* operator->() const + { + return std::addressof(current->value); + } + + operator templated_iterator() const + { + return { current }; + } + }; + using iterator = templated_iterator; + using const_iterator = templated_iterator; + + iterator begin() + { + for (EntryPointer it = entries;; ++it) + { + if (it->has_value()) + return { it }; + } + } + const_iterator begin() const + { + for (EntryPointer it = entries;; ++it) + { + if (it->has_value()) + return { it }; + } + } + const_iterator cbegin() const + { + return begin(); + } + iterator end() + { + return { entries + static_cast(num_slots_minus_one + max_lookups) }; + } + const_iterator end() const + { + return { entries + static_cast(num_slots_minus_one + max_lookups) }; + } + const_iterator cend() const + { + return end(); + } + + iterator find(const FindKey& key) + { + size_t index = hash_policy.index_for_hash(hash_object(key), num_slots_minus_one); + EntryPointer it = entries + ptrdiff_t(index); + for (int8_t distance = 0; it->distance_from_desired >= distance; ++distance, ++it) + { + if (compares_equal(key, it->value)) + return { it }; + } + return end(); + } + const_iterator find(const FindKey& key) const + { + return const_cast(this)->find(key); + } + size_t count(const FindKey& key) const + { + return find(key) == end() ? 0 : 1; + } + std::pair equal_range(const FindKey& key) + { + iterator found = find(key); + if (found == end()) + return { found, found }; + else + return { found, std::next(found) }; + } + std::pair equal_range(const FindKey& key) const + { + const_iterator found = find(key); + if (found == end()) + return { found, found }; + else + return { found, std::next(found) }; + } + + template + std::pair emplace(Key&& key, Args &&... args) + { + size_t index = hash_policy.index_for_hash(hash_object(key), num_slots_minus_one); + EntryPointer current_entry = entries + ptrdiff_t(index); + int8_t distance_from_desired = 0; + for (; current_entry->distance_from_desired >= distance_from_desired; ++current_entry, ++distance_from_desired) + { + if (compares_equal(key, current_entry->value)) + return { { current_entry }, false }; + } + return emplace_new_key(distance_from_desired, current_entry, std::forward(key), std::forward(args)...); + } + + std::pair insert(const value_type& value) + { + return emplace(value); + } + std::pair insert(value_type&& value) + { + return emplace(std::move(value)); + } + template + iterator emplace_hint(const_iterator, Args &&... args) + { + return emplace(std::forward(args)...).first; + } + iterator insert(const_iterator, const value_type& value) + { + return emplace(value).first; + } + iterator insert(const_iterator, value_type&& value) + { + return emplace(std::move(value)).first; + } + + template + void insert(It begin, It end) + { + for (; begin != end; ++begin) + { + emplace(*begin); + } + } + void insert(std::initializer_list il) + { + insert(il.begin(), il.end()); + } + + void rehash(size_t num_buckets) + { + num_buckets = std::max(num_buckets, static_cast(std::ceil(num_elements / static_cast(_max_load_factor)))); + if (num_buckets == 0) + { + reset_to_empty_state(); + return; + } + auto new_prime_index = hash_policy.next_size_over(num_buckets); + if (num_buckets == bucket_count()) + return; + int8_t new_max_lookups = compute_max_lookups(num_buckets); + EntryPointer new_buckets(AllocatorTraits::allocate(*this, num_buckets + new_max_lookups)); + EntryPointer special_end_item = new_buckets + static_cast(num_buckets + new_max_lookups - 1); + for (EntryPointer it = new_buckets; it != special_end_item; ++it) + it->distance_from_desired = -1; + special_end_item->distance_from_desired = Entry::special_end_value; + std::swap(entries, new_buckets); + std::swap(num_slots_minus_one, num_buckets); + --num_slots_minus_one; + hash_policy.commit(new_prime_index); + int8_t old_max_lookups = max_lookups; + max_lookups = new_max_lookups; + num_elements = 0; + for (EntryPointer it = new_buckets, end = it + static_cast(num_buckets + old_max_lookups); it != end; ++it) + { + if (it->has_value()) + { + emplace(std::move(it->value)); + it->destroy_value(); + } + } + deallocate_data(new_buckets, num_buckets, old_max_lookups); + } + + void reserve(size_t num_elements) + { + size_t required_buckets = num_buckets_for_reserve(num_elements); + if (required_buckets > bucket_count()) + rehash(required_buckets); + } + + // the return value is a type that can be converted to an iterator + // the reason for doing this is that it's not free to find the + // iterator pointing at the next element. if you care about the + // next iterator, turn the return value into an iterator + convertible_to_iterator erase(const_iterator to_erase) + { + EntryPointer current = to_erase.current; + current->destroy_value(); + --num_elements; + for (EntryPointer next = current + ptrdiff_t(1); !next->is_at_desired_position(); ++current, ++next) + { + current->emplace(next->distance_from_desired - 1, std::move(next->value)); + next->destroy_value(); + } + return { to_erase.current }; + } + + iterator erase(const_iterator begin_it, const_iterator end_it) + { + if (begin_it == end_it) + return { begin_it.current }; + for (EntryPointer it = begin_it.current, end = end_it.current; it != end; ++it) + { + if (it->has_value()) + { + it->destroy_value(); + --num_elements; + } + } + if (end_it == this->end()) + return this->end(); + ptrdiff_t num_to_move = std::min(static_cast(end_it.current->distance_from_desired), end_it.current - begin_it.current); + EntryPointer to_return = end_it.current - num_to_move; + for (EntryPointer it = end_it.current; !it->is_at_desired_position();) + { + EntryPointer target = it - num_to_move; + target->emplace(it->distance_from_desired - num_to_move, std::move(it->value)); + it->destroy_value(); + ++it; + num_to_move = std::min(static_cast(it->distance_from_desired), num_to_move); + } + return { to_return }; + } + + size_t erase(const FindKey& key) + { + auto found = find(key); + if (found == end()) + return 0; + else + { + erase(found); + return 1; + } + } + + void clear() + { + for (EntryPointer it = entries, end = it + static_cast(num_slots_minus_one + max_lookups); it != end; ++it) + { + if (it->has_value()) + it->destroy_value(); + } + num_elements = 0; + } + + void shrink_to_fit() + { + rehash_for_other_container(*this); + } + + void swap(sherwood_v3_table& other) + { + using std::swap; + swap_pointers(other); + swap(static_cast(*this), static_cast(other)); + swap(static_cast(*this), static_cast(other)); + if (AllocatorTraits::propagate_on_container_swap::value) + swap(static_cast(*this), static_cast(other)); + } + + size_t size() const + { + return num_elements; + } + size_t max_size() const + { + return (AllocatorTraits::max_size(*this)) / sizeof(Entry); + } + size_t bucket_count() const + { + return num_slots_minus_one ? num_slots_minus_one + 1 : 0; + } + size_type max_bucket_count() const + { + return (AllocatorTraits::max_size(*this) - min_lookups) / sizeof(Entry); + } + size_t bucket(const FindKey& key) const + { + return hash_policy.index_for_hash(hash_object(key), num_slots_minus_one); + } + float load_factor() const + { + size_t buckets = bucket_count(); + if (buckets) + return static_cast(num_elements) / bucket_count(); + else + return 0; + } + void max_load_factor(float value) + { + _max_load_factor = value; + } + float max_load_factor() const + { + return _max_load_factor; + } + + bool empty() const + { + return num_elements == 0; + } + + private: + EntryPointer entries = Entry::empty_default_table(); + size_t num_slots_minus_one = 0; + typename HashPolicySelector::type hash_policy; + int8_t max_lookups = detailv3::min_lookups - 1; + float _max_load_factor = 0.5f; + size_t num_elements = 0; + + static int8_t compute_max_lookups(size_t num_buckets) + { + int8_t desired = detailv3::log2(num_buckets); + return std::max(detailv3::min_lookups, desired); + } + + size_t num_buckets_for_reserve(size_t num_elements) const + { + return static_cast(std::ceil(num_elements / std::min(0.5, static_cast(_max_load_factor)))); + } + void rehash_for_other_container(const sherwood_v3_table& other) + { + rehash(std::min(num_buckets_for_reserve(other.size()), other.bucket_count())); + } + + void swap_pointers(sherwood_v3_table& other) + { + using std::swap; + swap(hash_policy, other.hash_policy); + swap(entries, other.entries); + swap(num_slots_minus_one, other.num_slots_minus_one); + swap(num_elements, other.num_elements); + swap(max_lookups, other.max_lookups); + swap(_max_load_factor, other._max_load_factor); + } + + template + SKA_NOINLINE(std::pair) emplace_new_key(int8_t distance_from_desired, EntryPointer current_entry, Key&& key, Args &&... args) + { + using std::swap; + if (num_slots_minus_one == 0 || distance_from_desired == max_lookups || num_elements + 1 > (num_slots_minus_one + 1) * static_cast(_max_load_factor)) + { + grow(); + return emplace(std::forward(key), std::forward(args)...); + } + else if (current_entry->is_empty()) + { + current_entry->emplace(distance_from_desired, std::forward(key), std::forward(args)...); + ++num_elements; + return { { current_entry }, true }; + } + value_type to_insert(std::forward(key), std::forward(args)...); + swap(distance_from_desired, current_entry->distance_from_desired); + swap(to_insert, current_entry->value); + iterator result = { current_entry }; + for (++distance_from_desired, ++current_entry;; ++current_entry) + { + if (current_entry->is_empty()) + { + current_entry->emplace(distance_from_desired, std::move(to_insert)); + ++num_elements; + return { result, true }; + } + else if (current_entry->distance_from_desired < distance_from_desired) + { + swap(distance_from_desired, current_entry->distance_from_desired); + swap(to_insert, current_entry->value); + ++distance_from_desired; + } + else + { + ++distance_from_desired; + if (distance_from_desired == max_lookups) + { + swap(to_insert, result.current->value); + grow(); + return emplace(std::move(to_insert)); + } + } + } + } + + void grow() + { + rehash(std::max(size_t(4), 2 * bucket_count())); + } + + void deallocate_data(EntryPointer begin, size_t num_slots_minus_one, int8_t max_lookups) + { + if (begin != Entry::empty_default_table()) + { + AllocatorTraits::deallocate(*this, begin, num_slots_minus_one + max_lookups + 1); + } + } + + void reset_to_empty_state() + { + deallocate_data(entries, num_slots_minus_one, max_lookups); + entries = Entry::empty_default_table(); + num_slots_minus_one = 0; + hash_policy.reset(); + max_lookups = detailv3::min_lookups - 1; + } + + template + size_t hash_object(const U& key) + { + return static_cast(*this)(key); + } + template + size_t hash_object(const U& key) const + { + return static_cast(*this)(key); + } + template + bool compares_equal(const L& lhs, const R& rhs) + { + return static_cast(*this)(lhs, rhs); + } + + struct convertible_to_iterator + { + EntryPointer it; + + operator iterator() + { + if (it->has_value()) + return { it }; + else + return ++iterator{ it }; + } + operator const_iterator() + { + if (it->has_value()) + return { it }; + else + return ++const_iterator{ it }; + } + }; + + }; + } + + struct prime_number_hash_policy + { + static size_t mod0(size_t) { return 0llu; } + static size_t mod2(size_t hash) { return hash % 2llu; } + static size_t mod3(size_t hash) { return hash % 3llu; } + static size_t mod5(size_t hash) { return hash % 5llu; } + static size_t mod7(size_t hash) { return hash % 7llu; } + static size_t mod11(size_t hash) { return hash % 11llu; } + static size_t mod13(size_t hash) { return hash % 13llu; } + static size_t mod17(size_t hash) { return hash % 17llu; } + static size_t mod23(size_t hash) { return hash % 23llu; } + static size_t mod29(size_t hash) { return hash % 29llu; } + static size_t mod37(size_t hash) { return hash % 37llu; } + static size_t mod47(size_t hash) { return hash % 47llu; } + static size_t mod59(size_t hash) { return hash % 59llu; } + static size_t mod73(size_t hash) { return hash % 73llu; } + static size_t mod97(size_t hash) { return hash % 97llu; } + static size_t mod127(size_t hash) { return hash % 127llu; } + static size_t mod151(size_t hash) { return hash % 151llu; } + static size_t mod197(size_t hash) { return hash % 197llu; } + static size_t mod251(size_t hash) { return hash % 251llu; } + static size_t mod313(size_t hash) { return hash % 313llu; } + static size_t mod397(size_t hash) { return hash % 397llu; } + static size_t mod499(size_t hash) { return hash % 499llu; } + static size_t mod631(size_t hash) { return hash % 631llu; } + static size_t mod797(size_t hash) { return hash % 797llu; } + static size_t mod1009(size_t hash) { return hash % 1009llu; } + static size_t mod1259(size_t hash) { return hash % 1259llu; } + static size_t mod1597(size_t hash) { return hash % 1597llu; } + static size_t mod2011(size_t hash) { return hash % 2011llu; } + static size_t mod2539(size_t hash) { return hash % 2539llu; } + static size_t mod3203(size_t hash) { return hash % 3203llu; } + static size_t mod4027(size_t hash) { return hash % 4027llu; } + static size_t mod5087(size_t hash) { return hash % 5087llu; } + static size_t mod6421(size_t hash) { return hash % 6421llu; } + static size_t mod8089(size_t hash) { return hash % 8089llu; } + static size_t mod10193(size_t hash) { return hash % 10193llu; } + static size_t mod12853(size_t hash) { return hash % 12853llu; } + static size_t mod16193(size_t hash) { return hash % 16193llu; } + static size_t mod20399(size_t hash) { return hash % 20399llu; } + static size_t mod25717(size_t hash) { return hash % 25717llu; } + static size_t mod32401(size_t hash) { return hash % 32401llu; } + static size_t mod40823(size_t hash) { return hash % 40823llu; } + static size_t mod51437(size_t hash) { return hash % 51437llu; } + static size_t mod64811(size_t hash) { return hash % 64811llu; } + static size_t mod81649(size_t hash) { return hash % 81649llu; } + static size_t mod102877(size_t hash) { return hash % 102877llu; } + static size_t mod129607(size_t hash) { return hash % 129607llu; } + static size_t mod163307(size_t hash) { return hash % 163307llu; } + static size_t mod205759(size_t hash) { return hash % 205759llu; } + static size_t mod259229(size_t hash) { return hash % 259229llu; } + static size_t mod326617(size_t hash) { return hash % 326617llu; } + static size_t mod411527(size_t hash) { return hash % 411527llu; } + static size_t mod518509(size_t hash) { return hash % 518509llu; } + static size_t mod653267(size_t hash) { return hash % 653267llu; } + static size_t mod823117(size_t hash) { return hash % 823117llu; } + static size_t mod1037059(size_t hash) { return hash % 1037059llu; } + static size_t mod1306601(size_t hash) { return hash % 1306601llu; } + static size_t mod1646237(size_t hash) { return hash % 1646237llu; } + static size_t mod2074129(size_t hash) { return hash % 2074129llu; } + static size_t mod2613229(size_t hash) { return hash % 2613229llu; } + static size_t mod3292489(size_t hash) { return hash % 3292489llu; } + static size_t mod4148279(size_t hash) { return hash % 4148279llu; } + static size_t mod5226491(size_t hash) { return hash % 5226491llu; } + static size_t mod6584983(size_t hash) { return hash % 6584983llu; } + static size_t mod8296553(size_t hash) { return hash % 8296553llu; } + static size_t mod10453007(size_t hash) { return hash % 10453007llu; } + static size_t mod13169977(size_t hash) { return hash % 13169977llu; } + static size_t mod16593127(size_t hash) { return hash % 16593127llu; } + static size_t mod20906033(size_t hash) { return hash % 20906033llu; } + static size_t mod26339969(size_t hash) { return hash % 26339969llu; } + static size_t mod33186281(size_t hash) { return hash % 33186281llu; } + static size_t mod41812097(size_t hash) { return hash % 41812097llu; } + static size_t mod52679969(size_t hash) { return hash % 52679969llu; } + static size_t mod66372617(size_t hash) { return hash % 66372617llu; } + static size_t mod83624237(size_t hash) { return hash % 83624237llu; } + static size_t mod105359939(size_t hash) { return hash % 105359939llu; } + static size_t mod132745199(size_t hash) { return hash % 132745199llu; } + static size_t mod167248483(size_t hash) { return hash % 167248483llu; } + static size_t mod210719881(size_t hash) { return hash % 210719881llu; } + static size_t mod265490441(size_t hash) { return hash % 265490441llu; } + static size_t mod334496971(size_t hash) { return hash % 334496971llu; } + static size_t mod421439783(size_t hash) { return hash % 421439783llu; } + static size_t mod530980861(size_t hash) { return hash % 530980861llu; } + static size_t mod668993977(size_t hash) { return hash % 668993977llu; } + static size_t mod842879579(size_t hash) { return hash % 842879579llu; } + static size_t mod1061961721(size_t hash) { return hash % 1061961721llu; } + static size_t mod1337987929(size_t hash) { return hash % 1337987929llu; } + static size_t mod1685759167(size_t hash) { return hash % 1685759167llu; } + static size_t mod2123923447(size_t hash) { return hash % 2123923447llu; } + static size_t mod2675975881(size_t hash) { return hash % 2675975881llu; } + static size_t mod3371518343(size_t hash) { return hash % 3371518343llu; } + static size_t mod4247846927(size_t hash) { return hash % 4247846927llu; } + static size_t mod5351951779(size_t hash) { return hash % 5351951779llu; } + static size_t mod6743036717(size_t hash) { return hash % 6743036717llu; } + static size_t mod8495693897(size_t hash) { return hash % 8495693897llu; } + static size_t mod10703903591(size_t hash) { return hash % 10703903591llu; } + static size_t mod13486073473(size_t hash) { return hash % 13486073473llu; } + static size_t mod16991387857(size_t hash) { return hash % 16991387857llu; } + static size_t mod21407807219(size_t hash) { return hash % 21407807219llu; } + static size_t mod26972146961(size_t hash) { return hash % 26972146961llu; } + static size_t mod33982775741(size_t hash) { return hash % 33982775741llu; } + static size_t mod42815614441(size_t hash) { return hash % 42815614441llu; } + static size_t mod53944293929(size_t hash) { return hash % 53944293929llu; } + static size_t mod67965551447(size_t hash) { return hash % 67965551447llu; } + static size_t mod85631228929(size_t hash) { return hash % 85631228929llu; } + static size_t mod107888587883(size_t hash) { return hash % 107888587883llu; } + static size_t mod135931102921(size_t hash) { return hash % 135931102921llu; } + static size_t mod171262457903(size_t hash) { return hash % 171262457903llu; } + static size_t mod215777175787(size_t hash) { return hash % 215777175787llu; } + static size_t mod271862205833(size_t hash) { return hash % 271862205833llu; } + static size_t mod342524915839(size_t hash) { return hash % 342524915839llu; } + static size_t mod431554351609(size_t hash) { return hash % 431554351609llu; } + static size_t mod543724411781(size_t hash) { return hash % 543724411781llu; } + static size_t mod685049831731(size_t hash) { return hash % 685049831731llu; } + static size_t mod863108703229(size_t hash) { return hash % 863108703229llu; } + static size_t mod1087448823553(size_t hash) { return hash % 1087448823553llu; } + static size_t mod1370099663459(size_t hash) { return hash % 1370099663459llu; } + static size_t mod1726217406467(size_t hash) { return hash % 1726217406467llu; } + static size_t mod2174897647073(size_t hash) { return hash % 2174897647073llu; } + static size_t mod2740199326961(size_t hash) { return hash % 2740199326961llu; } + static size_t mod3452434812973(size_t hash) { return hash % 3452434812973llu; } + static size_t mod4349795294267(size_t hash) { return hash % 4349795294267llu; } + static size_t mod5480398654009(size_t hash) { return hash % 5480398654009llu; } + static size_t mod6904869625999(size_t hash) { return hash % 6904869625999llu; } + static size_t mod8699590588571(size_t hash) { return hash % 8699590588571llu; } + static size_t mod10960797308051(size_t hash) { return hash % 10960797308051llu; } + static size_t mod13809739252051(size_t hash) { return hash % 13809739252051llu; } + static size_t mod17399181177241(size_t hash) { return hash % 17399181177241llu; } + static size_t mod21921594616111(size_t hash) { return hash % 21921594616111llu; } + static size_t mod27619478504183(size_t hash) { return hash % 27619478504183llu; } + static size_t mod34798362354533(size_t hash) { return hash % 34798362354533llu; } + static size_t mod43843189232363(size_t hash) { return hash % 43843189232363llu; } + static size_t mod55238957008387(size_t hash) { return hash % 55238957008387llu; } + static size_t mod69596724709081(size_t hash) { return hash % 69596724709081llu; } + static size_t mod87686378464759(size_t hash) { return hash % 87686378464759llu; } + static size_t mod110477914016779(size_t hash) { return hash % 110477914016779llu; } + static size_t mod139193449418173(size_t hash) { return hash % 139193449418173llu; } + static size_t mod175372756929481(size_t hash) { return hash % 175372756929481llu; } + static size_t mod220955828033581(size_t hash) { return hash % 220955828033581llu; } + static size_t mod278386898836457(size_t hash) { return hash % 278386898836457llu; } + static size_t mod350745513859007(size_t hash) { return hash % 350745513859007llu; } + static size_t mod441911656067171(size_t hash) { return hash % 441911656067171llu; } + static size_t mod556773797672909(size_t hash) { return hash % 556773797672909llu; } + static size_t mod701491027718027(size_t hash) { return hash % 701491027718027llu; } + static size_t mod883823312134381(size_t hash) { return hash % 883823312134381llu; } + static size_t mod1113547595345903(size_t hash) { return hash % 1113547595345903llu; } + static size_t mod1402982055436147(size_t hash) { return hash % 1402982055436147llu; } + static size_t mod1767646624268779(size_t hash) { return hash % 1767646624268779llu; } + static size_t mod2227095190691797(size_t hash) { return hash % 2227095190691797llu; } + static size_t mod2805964110872297(size_t hash) { return hash % 2805964110872297llu; } + static size_t mod3535293248537579(size_t hash) { return hash % 3535293248537579llu; } + static size_t mod4454190381383713(size_t hash) { return hash % 4454190381383713llu; } + static size_t mod5611928221744609(size_t hash) { return hash % 5611928221744609llu; } + static size_t mod7070586497075177(size_t hash) { return hash % 7070586497075177llu; } + static size_t mod8908380762767489(size_t hash) { return hash % 8908380762767489llu; } + static size_t mod11223856443489329(size_t hash) { return hash % 11223856443489329llu; } + static size_t mod14141172994150357(size_t hash) { return hash % 14141172994150357llu; } + static size_t mod17816761525534927(size_t hash) { return hash % 17816761525534927llu; } + static size_t mod22447712886978529(size_t hash) { return hash % 22447712886978529llu; } + static size_t mod28282345988300791(size_t hash) { return hash % 28282345988300791llu; } + static size_t mod35633523051069991(size_t hash) { return hash % 35633523051069991llu; } + static size_t mod44895425773957261(size_t hash) { return hash % 44895425773957261llu; } + static size_t mod56564691976601587(size_t hash) { return hash % 56564691976601587llu; } + static size_t mod71267046102139967(size_t hash) { return hash % 71267046102139967llu; } + static size_t mod89790851547914507(size_t hash) { return hash % 89790851547914507llu; } + static size_t mod113129383953203213(size_t hash) { return hash % 113129383953203213llu; } + static size_t mod142534092204280003(size_t hash) { return hash % 142534092204280003llu; } + static size_t mod179581703095829107(size_t hash) { return hash % 179581703095829107llu; } + static size_t mod226258767906406483(size_t hash) { return hash % 226258767906406483llu; } + static size_t mod285068184408560057(size_t hash) { return hash % 285068184408560057llu; } + static size_t mod359163406191658253(size_t hash) { return hash % 359163406191658253llu; } + static size_t mod452517535812813007(size_t hash) { return hash % 452517535812813007llu; } + static size_t mod570136368817120201(size_t hash) { return hash % 570136368817120201llu; } + static size_t mod718326812383316683(size_t hash) { return hash % 718326812383316683llu; } + static size_t mod905035071625626043(size_t hash) { return hash % 905035071625626043llu; } + static size_t mod1140272737634240411(size_t hash) { return hash % 1140272737634240411llu; } + static size_t mod1436653624766633509(size_t hash) { return hash % 1436653624766633509llu; } + static size_t mod1810070143251252131(size_t hash) { return hash % 1810070143251252131llu; } + static size_t mod2280545475268481167(size_t hash) { return hash % 2280545475268481167llu; } + static size_t mod2873307249533267101(size_t hash) { return hash % 2873307249533267101llu; } + static size_t mod3620140286502504283(size_t hash) { return hash % 3620140286502504283llu; } + static size_t mod4561090950536962147(size_t hash) { return hash % 4561090950536962147llu; } + static size_t mod5746614499066534157(size_t hash) { return hash % 5746614499066534157llu; } + static size_t mod7240280573005008577(size_t hash) { return hash % 7240280573005008577llu; } + static size_t mod9122181901073924329(size_t hash) { return hash % 9122181901073924329llu; } + static size_t mod11493228998133068689(size_t hash) { return hash % 11493228998133068689llu; } + static size_t mod14480561146010017169(size_t hash) { return hash % 14480561146010017169llu; } + static size_t mod18446744073709551557(size_t hash) { return hash % 18446744073709551557llu; } + + using mod_function = size_t(*)(size_t); + + mod_function next_size_over(size_t& size) const + { + // prime numbers generated by the following method: + // 1. start with a prime p = 2 + // 2. go to wolfram alpha and get p = NextPrime(2 * p) + // 3. repeat 2. until you overflow 64 bits + // you now have large gaps which you would hit if somebody called reserve() with an unlucky number. + // 4. to fill the gaps for every prime p go to wolfram alpha and get ClosestPrime(p * 2^(1/3)) and ClosestPrime(p * 2^(2/3)) and put those in the gaps + // 5. get PrevPrime(2^64) and put it at the end + static constexpr const size_t prime_list[] = + { + 2llu, 3llu, 5llu, 7llu, 11llu, 13llu, 17llu, 23llu, 29llu, 37llu, 47llu, + 59llu, 73llu, 97llu, 127llu, 151llu, 197llu, 251llu, 313llu, 397llu, + 499llu, 631llu, 797llu, 1009llu, 1259llu, 1597llu, 2011llu, 2539llu, + 3203llu, 4027llu, 5087llu, 6421llu, 8089llu, 10193llu, 12853llu, 16193llu, + 20399llu, 25717llu, 32401llu, 40823llu, 51437llu, 64811llu, 81649llu, + 102877llu, 129607llu, 163307llu, 205759llu, 259229llu, 326617llu, + 411527llu, 518509llu, 653267llu, 823117llu, 1037059llu, 1306601llu, + 1646237llu, 2074129llu, 2613229llu, 3292489llu, 4148279llu, 5226491llu, + 6584983llu, 8296553llu, 10453007llu, 13169977llu, 16593127llu, 20906033llu, + 26339969llu, 33186281llu, 41812097llu, 52679969llu, 66372617llu, + 83624237llu, 105359939llu, 132745199llu, 167248483llu, 210719881llu, + 265490441llu, 334496971llu, 421439783llu, 530980861llu, 668993977llu, + 842879579llu, 1061961721llu, 1337987929llu, 1685759167llu, 2123923447llu, + 2675975881llu, 3371518343llu, 4247846927llu, 5351951779llu, 6743036717llu, + 8495693897llu, 10703903591llu, 13486073473llu, 16991387857llu, + 21407807219llu, 26972146961llu, 33982775741llu, 42815614441llu, + 53944293929llu, 67965551447llu, 85631228929llu, 107888587883llu, + 135931102921llu, 171262457903llu, 215777175787llu, 271862205833llu, + 342524915839llu, 431554351609llu, 543724411781llu, 685049831731llu, + 863108703229llu, 1087448823553llu, 1370099663459llu, 1726217406467llu, + 2174897647073llu, 2740199326961llu, 3452434812973llu, 4349795294267llu, + 5480398654009llu, 6904869625999llu, 8699590588571llu, 10960797308051llu, + 13809739252051llu, 17399181177241llu, 21921594616111llu, 27619478504183llu, + 34798362354533llu, 43843189232363llu, 55238957008387llu, 69596724709081llu, + 87686378464759llu, 110477914016779llu, 139193449418173llu, + 175372756929481llu, 220955828033581llu, 278386898836457llu, + 350745513859007llu, 441911656067171llu, 556773797672909llu, + 701491027718027llu, 883823312134381llu, 1113547595345903llu, + 1402982055436147llu, 1767646624268779llu, 2227095190691797llu, + 2805964110872297llu, 3535293248537579llu, 4454190381383713llu, + 5611928221744609llu, 7070586497075177llu, 8908380762767489llu, + 11223856443489329llu, 14141172994150357llu, 17816761525534927llu, + 22447712886978529llu, 28282345988300791llu, 35633523051069991llu, + 44895425773957261llu, 56564691976601587llu, 71267046102139967llu, + 89790851547914507llu, 113129383953203213llu, 142534092204280003llu, + 179581703095829107llu, 226258767906406483llu, 285068184408560057llu, + 359163406191658253llu, 452517535812813007llu, 570136368817120201llu, + 718326812383316683llu, 905035071625626043llu, 1140272737634240411llu, + 1436653624766633509llu, 1810070143251252131llu, 2280545475268481167llu, + 2873307249533267101llu, 3620140286502504283llu, 4561090950536962147llu, + 5746614499066534157llu, 7240280573005008577llu, 9122181901073924329llu, + 11493228998133068689llu, 14480561146010017169llu, 18446744073709551557llu + }; + static constexpr size_t(* const mod_functions[])(size_t) = + { + &mod0, &mod2, &mod3, &mod5, &mod7, &mod11, &mod13, &mod17, &mod23, &mod29, &mod37, + &mod47, &mod59, &mod73, &mod97, &mod127, &mod151, &mod197, &mod251, &mod313, &mod397, + &mod499, &mod631, &mod797, &mod1009, &mod1259, &mod1597, &mod2011, &mod2539, &mod3203, + &mod4027, &mod5087, &mod6421, &mod8089, &mod10193, &mod12853, &mod16193, &mod20399, + &mod25717, &mod32401, &mod40823, &mod51437, &mod64811, &mod81649, &mod102877, + &mod129607, &mod163307, &mod205759, &mod259229, &mod326617, &mod411527, &mod518509, + &mod653267, &mod823117, &mod1037059, &mod1306601, &mod1646237, &mod2074129, + &mod2613229, &mod3292489, &mod4148279, &mod5226491, &mod6584983, &mod8296553, + &mod10453007, &mod13169977, &mod16593127, &mod20906033, &mod26339969, &mod33186281, + &mod41812097, &mod52679969, &mod66372617, &mod83624237, &mod105359939, &mod132745199, + &mod167248483, &mod210719881, &mod265490441, &mod334496971, &mod421439783, + &mod530980861, &mod668993977, &mod842879579, &mod1061961721, &mod1337987929, + &mod1685759167, &mod2123923447, &mod2675975881, &mod3371518343, &mod4247846927, + &mod5351951779, &mod6743036717, &mod8495693897, &mod10703903591, &mod13486073473, + &mod16991387857, &mod21407807219, &mod26972146961, &mod33982775741, &mod42815614441, + &mod53944293929, &mod67965551447, &mod85631228929, &mod107888587883, &mod135931102921, + &mod171262457903, &mod215777175787, &mod271862205833, &mod342524915839, + &mod431554351609, &mod543724411781, &mod685049831731, &mod863108703229, + &mod1087448823553, &mod1370099663459, &mod1726217406467, &mod2174897647073, + &mod2740199326961, &mod3452434812973, &mod4349795294267, &mod5480398654009, + &mod6904869625999, &mod8699590588571, &mod10960797308051, &mod13809739252051, + &mod17399181177241, &mod21921594616111, &mod27619478504183, &mod34798362354533, + &mod43843189232363, &mod55238957008387, &mod69596724709081, &mod87686378464759, + &mod110477914016779, &mod139193449418173, &mod175372756929481, &mod220955828033581, + &mod278386898836457, &mod350745513859007, &mod441911656067171, &mod556773797672909, + &mod701491027718027, &mod883823312134381, &mod1113547595345903, &mod1402982055436147, + &mod1767646624268779, &mod2227095190691797, &mod2805964110872297, &mod3535293248537579, + &mod4454190381383713, &mod5611928221744609, &mod7070586497075177, &mod8908380762767489, + &mod11223856443489329, &mod14141172994150357, &mod17816761525534927, + &mod22447712886978529, &mod28282345988300791, &mod35633523051069991, + &mod44895425773957261, &mod56564691976601587, &mod71267046102139967, + &mod89790851547914507, &mod113129383953203213, &mod142534092204280003, + &mod179581703095829107, &mod226258767906406483, &mod285068184408560057, + &mod359163406191658253, &mod452517535812813007, &mod570136368817120201, + &mod718326812383316683, &mod905035071625626043, &mod1140272737634240411, + &mod1436653624766633509, &mod1810070143251252131, &mod2280545475268481167, + &mod2873307249533267101, &mod3620140286502504283, &mod4561090950536962147, + &mod5746614499066534157, &mod7240280573005008577, &mod9122181901073924329, + &mod11493228998133068689, &mod14480561146010017169, &mod18446744073709551557 + }; + const size_t* found = std::lower_bound(std::begin(prime_list), std::end(prime_list) - 1, size); + size = *found; + return mod_functions[1 + found - prime_list]; + } + void commit(mod_function new_mod_function) + { + current_mod_function = new_mod_function; + } + void reset() + { + current_mod_function = &mod0; + } + + size_t index_for_hash(size_t hash, size_t /*num_slots_minus_one*/) const + { + return current_mod_function(hash); + } + size_t keep_in_range(size_t index, size_t num_slots_minus_one) const + { + return index > num_slots_minus_one ? current_mod_function(index) : index; + } + + private: + mod_function current_mod_function = &mod0; + }; + + struct power_of_two_hash_policy + { + size_t index_for_hash(size_t hash, size_t num_slots_minus_one) const + { + return hash & num_slots_minus_one; + } + size_t keep_in_range(size_t index, size_t num_slots_minus_one) const + { + return index_for_hash(index, num_slots_minus_one); + } + int8_t next_size_over(size_t& size) const + { + size = detailv3::next_power_of_two(size); + return 0; + } + void commit(int8_t) + { + } + void reset() + { + } + + }; + + struct fibonacci_hash_policy + { + size_t index_for_hash(size_t hash, size_t /*num_slots_minus_one*/) const + { + return (11400714819323198485ull * hash) >> shift; + } + size_t keep_in_range(size_t index, size_t num_slots_minus_one) const + { + return index & num_slots_minus_one; + } + + int8_t next_size_over(size_t& size) const + { + size = std::max(size_t(2), detailv3::next_power_of_two(size)); + return 64 - detailv3::log2(size); + } + void commit(int8_t shift) + { + this->shift = shift; + } + void reset() + { + shift = 63; + } + + private: + int8_t shift = 63; + }; + + template, typename E = std::equal_to, typename A = std::allocator > > + class flat_hash_map + : public detailv3::sherwood_v3_table + < + std::pair, + K, + H, + detailv3::KeyOrValueHasher, H>, + E, + detailv3::KeyOrValueEquality, E>, + A, + typename std::allocator_traits::template rebind_alloc>> + > + { + using Table = detailv3::sherwood_v3_table + < + std::pair, + K, + H, + detailv3::KeyOrValueHasher, H>, + E, + detailv3::KeyOrValueEquality, E>, + A, + typename std::allocator_traits::template rebind_alloc>> + >; + public: + + using key_type = K; + using mapped_type = V; + + using Table::Table; + flat_hash_map() + { + } + + inline V& operator[](const K& key) + { + return emplace(key, convertible_to_value()).first->second; + } + inline V& operator[](K&& key) + { + return emplace(std::move(key), convertible_to_value()).first->second; + } + V& at(const K& key) + { + auto found = this->find(key); + if (found == this->end()) + throw std::out_of_range("Argument passed to at() was not in the map."); + return found->second; + } + const V& at(const K& key) const + { + auto found = this->find(key); + if (found == this->end()) + throw std::out_of_range("Argument passed to at() was not in the map."); + return found->second; + } + + using Table::emplace; + std::pair emplace() + { + return emplace(key_type(), convertible_to_value()); + } + template + std::pair insert_or_assign(const key_type& key, M&& m) + { + auto emplace_result = emplace(key, std::forward(m)); + if (!emplace_result.second) + emplace_result.first->second = std::forward(m); + return emplace_result; + } + template + std::pair insert_or_assign(key_type&& key, M&& m) + { + auto emplace_result = emplace(std::move(key), std::forward(m)); + if (!emplace_result.second) + emplace_result.first->second = std::forward(m); + return emplace_result; + } + template + typename Table::iterator insert_or_assign(typename Table::const_iterator, const key_type& key, M&& m) + { + return insert_or_assign(key, std::forward(m)).first; + } + template + typename Table::iterator insert_or_assign(typename Table::const_iterator, key_type&& key, M&& m) + { + return insert_or_assign(std::move(key), std::forward(m)).first; + } + + friend bool operator==(const flat_hash_map& lhs, const flat_hash_map& rhs) + { + if (lhs.size() != rhs.size()) + return false; + for (const typename Table::value_type& value : lhs) + { + auto found = rhs.find(value.first); + if (found == rhs.end()) + return false; + else if (value.second != found->second) + return false; + } + return true; + } + friend bool operator!=(const flat_hash_map& lhs, const flat_hash_map& rhs) + { + return !(lhs == rhs); + } + + private: + struct convertible_to_value + { + operator V() const + { + return V(); + } + }; + }; + + template, typename E = std::equal_to, typename A = std::allocator > + class flat_hash_set + : public detailv3::sherwood_v3_table + < + T, + T, + H, + detailv3::functor_storage, + E, + detailv3::functor_storage, + A, + typename std::allocator_traits::template rebind_alloc> + > + { + using Table = detailv3::sherwood_v3_table + < + T, + T, + H, + detailv3::functor_storage, + E, + detailv3::functor_storage, + A, + typename std::allocator_traits::template rebind_alloc> + >; + public: + + using key_type = T; + + using Table::Table; + flat_hash_set() + { + } + + template + std::pair emplace(Args &&... args) + { + return Table::emplace(T(std::forward(args)...)); + } + std::pair emplace(const key_type& arg) + { + return Table::emplace(arg); + } + std::pair emplace(key_type& arg) + { + return Table::emplace(arg); + } + std::pair emplace(const key_type&& arg) + { + return Table::emplace(std::move(arg)); + } + std::pair emplace(key_type&& arg) + { + return Table::emplace(std::move(arg)); + } + + friend bool operator==(const flat_hash_set& lhs, const flat_hash_set& rhs) + { + if (lhs.size() != rhs.size()) + return false; + for (const T& value : lhs) + { + if (rhs.find(value) == rhs.end()) + return false; + } + return true; + } + friend bool operator!=(const flat_hash_set& lhs, const flat_hash_set& rhs) + { + return !(lhs == rhs); + } + }; + + + template + struct power_of_two_std_hash : std::hash + { + typedef ska::power_of_two_hash_policy hash_policy; + }; + +} // end namespace ska diff --git a/src/util/containers/robin_hood.h b/src/util/containers/robin_hood.h new file mode 100644 index 00000000..577521b1 --- /dev/null +++ b/src/util/containers/robin_hood.h @@ -0,0 +1,2462 @@ +// ______ _____ ______ _________ +// ______________ ___ /_ ___(_)_______ ___ /_ ______ ______ ______ / +// __ ___/_ __ \__ __ \__ / __ __ \ __ __ \_ __ \_ __ \_ __ / +// _ / / /_/ /_ /_/ /_ / _ / / / _ / / // /_/ // /_/ // /_/ / +// /_/ \____/ /_.___/ /_/ /_/ /_/ ________/_/ /_/ \____/ \____/ \__,_/ +// _/_____/ +// +// Fast & memory efficient hashtable based on robin hood hashing for C++11/14/17/20 +// https://github.com/martinus/robin-hood-hashing +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2018-2020 Martin Ankerl +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef ROBIN_HOOD_H_INCLUDED +#define ROBIN_HOOD_H_INCLUDED + +// see https://semver.org/ +#define ROBIN_HOOD_VERSION_MAJOR 3 // for incompatible API changes +#define ROBIN_HOOD_VERSION_MINOR 10 // for adding functionality in a backwards-compatible manner +#define ROBIN_HOOD_VERSION_PATCH 0 // for backwards-compatible bug fixes + +#include +#include +#include +#include +#include // only to support hash of smart pointers +#include +#include +#include +#include +#if __cplusplus >= 201703L +# include +#endif + +// #define ROBIN_HOOD_LOG_ENABLED +#ifdef ROBIN_HOOD_LOG_ENABLED +# include +# define ROBIN_HOOD_LOG(...) \ + std::cout << __FUNCTION__ << "@" << __LINE__ << ": " << __VA_ARGS__ << std::endl; +#else +# define ROBIN_HOOD_LOG(x) +#endif + +// #define ROBIN_HOOD_TRACE_ENABLED +#ifdef ROBIN_HOOD_TRACE_ENABLED +# include +# define ROBIN_HOOD_TRACE(...) \ + std::cout << __FUNCTION__ << "@" << __LINE__ << ": " << __VA_ARGS__ << std::endl; +#else +# define ROBIN_HOOD_TRACE(x) +#endif + +// #define ROBIN_HOOD_COUNT_ENABLED +#ifdef ROBIN_HOOD_COUNT_ENABLED +# include +# define ROBIN_HOOD_COUNT(x) ++counts().x; +namespace robin_hood { + struct Counts { + uint64_t shiftUp{}; + uint64_t shiftDown{}; + }; + inline std::ostream& operator<<(std::ostream& os, Counts const& c) { + return os << c.shiftUp << " shiftUp" << std::endl << c.shiftDown << " shiftDown" << std::endl; + } + + static Counts& counts() { + static Counts counts{}; + return counts; + } +} // namespace robin_hood +#else +# define ROBIN_HOOD_COUNT(x) +#endif + +// all non-argument macros should use this facility. See +// https://www.fluentcpp.com/2019/05/28/better-macros-better-flags/ +#define ROBIN_HOOD(x) ROBIN_HOOD_PRIVATE_DEFINITION_##x() + +// mark unused members with this macro +#define ROBIN_HOOD_UNUSED(identifier) + +// bitness +#if SIZE_MAX == UINT32_MAX +# define ROBIN_HOOD_PRIVATE_DEFINITION_BITNESS() 32 +#elif SIZE_MAX == UINT64_MAX +# define ROBIN_HOOD_PRIVATE_DEFINITION_BITNESS() 64 +#else +# error Unsupported bitness +#endif + +// endianess +#ifdef _MSC_VER +# define ROBIN_HOOD_PRIVATE_DEFINITION_LITTLE_ENDIAN() 1 +# define ROBIN_HOOD_PRIVATE_DEFINITION_BIG_ENDIAN() 0 +#else +# define ROBIN_HOOD_PRIVATE_DEFINITION_LITTLE_ENDIAN() \ + (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) +# define ROBIN_HOOD_PRIVATE_DEFINITION_BIG_ENDIAN() (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) +#endif + +// inline +#ifdef _MSC_VER +# define ROBIN_HOOD_PRIVATE_DEFINITION_NOINLINE() __declspec(noinline) +#else +# define ROBIN_HOOD_PRIVATE_DEFINITION_NOINLINE() __attribute__((noinline)) +#endif + +// exceptions +#if !defined(__cpp_exceptions) && !defined(__EXCEPTIONS) && !defined(_CPPUNWIND) +# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_EXCEPTIONS() 0 +#else +# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_EXCEPTIONS() 1 +#endif + +// count leading/trailing bits +#if !defined(ROBIN_HOOD_DISABLE_INTRINSICS) +# ifdef _MSC_VER +# if ROBIN_HOOD(BITNESS) == 32 +# define ROBIN_HOOD_PRIVATE_DEFINITION_BITSCANFORWARD() _BitScanForward +# else +# define ROBIN_HOOD_PRIVATE_DEFINITION_BITSCANFORWARD() _BitScanForward64 +# endif +# include +# pragma intrinsic(ROBIN_HOOD(BITSCANFORWARD)) +# define ROBIN_HOOD_COUNT_TRAILING_ZEROES(x) \ + [](size_t mask) noexcept -> int { \ + unsigned long index; \ + return ROBIN_HOOD(BITSCANFORWARD)(&index, mask) ? static_cast(index) \ + : ROBIN_HOOD(BITNESS); \ + }(x) +# else +# if ROBIN_HOOD(BITNESS) == 32 +# define ROBIN_HOOD_PRIVATE_DEFINITION_CTZ() __builtin_ctzl +# define ROBIN_HOOD_PRIVATE_DEFINITION_CLZ() __builtin_clzl +# else +# define ROBIN_HOOD_PRIVATE_DEFINITION_CTZ() __builtin_ctzll +# define ROBIN_HOOD_PRIVATE_DEFINITION_CLZ() __builtin_clzll +# endif +# define ROBIN_HOOD_COUNT_LEADING_ZEROES(x) ((x) ? ROBIN_HOOD(CLZ)(x) : ROBIN_HOOD(BITNESS)) +# define ROBIN_HOOD_COUNT_TRAILING_ZEROES(x) ((x) ? ROBIN_HOOD(CTZ)(x) : ROBIN_HOOD(BITNESS)) +# endif +#endif + +// fallthrough +#ifndef __has_cpp_attribute // For backwards compatibility +# define __has_cpp_attribute(x) 0 +#endif +#if __has_cpp_attribute(clang::fallthrough) +# define ROBIN_HOOD_PRIVATE_DEFINITION_FALLTHROUGH() [[clang::fallthrough]] +#elif __has_cpp_attribute(gnu::fallthrough) +# define ROBIN_HOOD_PRIVATE_DEFINITION_FALLTHROUGH() [[gnu::fallthrough]] +#else +# define ROBIN_HOOD_PRIVATE_DEFINITION_FALLTHROUGH() +#endif + +// likely/unlikely +#ifdef _MSC_VER +# define ROBIN_HOOD_LIKELY(condition) condition +# define ROBIN_HOOD_UNLIKELY(condition) condition +#else +# define ROBIN_HOOD_LIKELY(condition) __builtin_expect(condition, 1) +# define ROBIN_HOOD_UNLIKELY(condition) __builtin_expect(condition, 0) +#endif + +// detect if native wchar_t type is availiable in MSVC +#ifdef _MSC_VER +# ifdef _NATIVE_WCHAR_T_DEFINED +# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_NATIVE_WCHART() 1 +# else +# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_NATIVE_WCHART() 0 +# endif +#else +# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_NATIVE_WCHART() 1 +#endif + +// workaround missing "is_trivially_copyable" in g++ < 5.0 +// See https://stackoverflow.com/a/31798726/48181 +#if defined(__GNUC__) && __GNUC__ < 5 +# define ROBIN_HOOD_IS_TRIVIALLY_COPYABLE(...) __has_trivial_copy(__VA_ARGS__) +#else +# define ROBIN_HOOD_IS_TRIVIALLY_COPYABLE(...) std::is_trivially_copyable<__VA_ARGS__>::value +#endif + +// helpers for C++ versions, see https://gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html +#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX() __cplusplus +#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX98() 199711L +#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX11() 201103L +#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX14() 201402L +#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX17() 201703L + +#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX17) +# define ROBIN_HOOD_PRIVATE_DEFINITION_NODISCARD() [[nodiscard]] +#else +# define ROBIN_HOOD_PRIVATE_DEFINITION_NODISCARD() +#endif + +namespace robin_hood { + +#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX14) +# define ROBIN_HOOD_STD std +#else + + // c++11 compatibility layer + namespace ROBIN_HOOD_STD { + template + struct alignment_of + : std::integral_constant::type)> {}; + + template + class integer_sequence { + public: + using value_type = T; + static_assert(std::is_integral::value, "not integral type"); + static constexpr std::size_t size() noexcept { + return sizeof...(Ints); + } + }; + template + using index_sequence = integer_sequence; + + namespace detail_ { + template + struct IntSeqImpl { + using TValue = T; + static_assert(std::is_integral::value, "not integral type"); + static_assert(Begin >= 0 && Begin < End, "unexpected argument (Begin<0 || Begin<=End)"); + + template + struct IntSeqCombiner; + + template + struct IntSeqCombiner, integer_sequence> { + using TResult = integer_sequence; + }; + + using TResult = + typename IntSeqCombiner::TResult, + typename IntSeqImpl::TResult>::TResult; + }; + + template + struct IntSeqImpl { + using TValue = T; + static_assert(std::is_integral::value, "not integral type"); + static_assert(Begin >= 0, "unexpected argument (Begin<0)"); + using TResult = integer_sequence; + }; + + template + struct IntSeqImpl { + using TValue = T; + static_assert(std::is_integral::value, "not integral type"); + static_assert(Begin >= 0, "unexpected argument (Begin<0)"); + using TResult = integer_sequence; + }; + } // namespace detail_ + + template + using make_integer_sequence = typename detail_::IntSeqImpl::TResult; + + template + using make_index_sequence = make_integer_sequence; + + template + using index_sequence_for = make_index_sequence; + + } // namespace ROBIN_HOOD_STD + +#endif + + namespace detail { + + // make sure we static_cast to the correct type for hash_int +#if ROBIN_HOOD(BITNESS) == 64 + using SizeT = uint64_t; +#else + using SizeT = uint32_t; +#endif + + template + T rotr(T x, unsigned k) { + return (x >> k) | (x << (8U * sizeof(T) - k)); + } + + // This cast gets rid of warnings like "cast from 'uint8_t*' {aka 'unsigned char*'} to + // 'uint64_t*' {aka 'long unsigned int*'} increases required alignment of target type". Use with + // care! + template + inline T reinterpret_cast_no_cast_align_warning(void* ptr) noexcept { + return reinterpret_cast(ptr); + } + + template + inline T reinterpret_cast_no_cast_align_warning(void const* ptr) noexcept { + return reinterpret_cast(ptr); + } + + // make sure this is not inlined as it is slow and dramatically enlarges code, thus making other + // inlinings more difficult. Throws are also generally the slow path. + template + [[noreturn]] ROBIN_HOOD(NOINLINE) +#if ROBIN_HOOD(HAS_EXCEPTIONS) + void doThrow(Args&&... args) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay) + throw E(std::forward(args)...); + } +#else + void doThrow(Args&&... ROBIN_HOOD_UNUSED(args) /*unused*/) { + abort(); + } +#endif + + template + T* assertNotNull(T* t, Args&&... args) { + if (ROBIN_HOOD_UNLIKELY(nullptr == t)) { + doThrow(std::forward(args)...); + } + return t; + } + + template + inline T unaligned_load(void const* ptr) noexcept { + // using memcpy so we don't get into unaligned load problems. + // compiler should optimize this very well anyways. + T t; + std::memcpy(&t, ptr, sizeof(T)); + return t; + } + + // Allocates bulks of memory for objects of type T. This deallocates the memory in the destructor, + // and keeps a linked list of the allocated memory around. Overhead per allocation is the size of a + // pointer. + template + class BulkPoolAllocator { + public: + BulkPoolAllocator() noexcept = default; + + // does not copy anything, just creates a new allocator. + BulkPoolAllocator(const BulkPoolAllocator& ROBIN_HOOD_UNUSED(o) /*unused*/) noexcept + : mHead(nullptr) + , mListForFree(nullptr) {} + + BulkPoolAllocator(BulkPoolAllocator&& o) noexcept + : mHead(o.mHead) + , mListForFree(o.mListForFree) { + o.mListForFree = nullptr; + o.mHead = nullptr; + } + + BulkPoolAllocator& operator=(BulkPoolAllocator&& o) noexcept { + reset(); + mHead = o.mHead; + mListForFree = o.mListForFree; + o.mListForFree = nullptr; + o.mHead = nullptr; + return *this; + } + + BulkPoolAllocator& + // NOLINTNEXTLINE(bugprone-unhandled-self-assignment,cert-oop54-cpp) + operator=(const BulkPoolAllocator& ROBIN_HOOD_UNUSED(o) /*unused*/) noexcept { + // does not do anything + return *this; + } + + ~BulkPoolAllocator() noexcept { + reset(); + } + + // Deallocates all allocated memory. + void reset() noexcept { + while (mListForFree) { + T* tmp = *mListForFree; + ROBIN_HOOD_LOG("std::free") + std::free(mListForFree); + mListForFree = reinterpret_cast_no_cast_align_warning(tmp); + } + mHead = nullptr; + } + + // allocates, but does NOT initialize. Use in-place new constructor, e.g. + // T* obj = pool.allocate(); + // ::new (static_cast(obj)) T(); + T* allocate() { + T* tmp = mHead; + if (!tmp) { + tmp = performAllocation(); + } + + mHead = *reinterpret_cast_no_cast_align_warning(tmp); + return tmp; + } + + // does not actually deallocate but puts it in store. + // make sure you have already called the destructor! e.g. with + // obj->~T(); + // pool.deallocate(obj); + void deallocate(T* obj) noexcept { + *reinterpret_cast_no_cast_align_warning(obj) = mHead; + mHead = obj; + } + + // Adds an already allocated block of memory to the allocator. This allocator is from now on + // responsible for freeing the data (with free()). If the provided data is not large enough to + // make use of, it is immediately freed. Otherwise it is reused and freed in the destructor. + void addOrFree(void* ptr, const size_t numBytes) noexcept { + // calculate number of available elements in ptr + if (numBytes < ALIGNMENT + ALIGNED_SIZE) { + // not enough data for at least one element. Free and return. + ROBIN_HOOD_LOG("std::free") + std::free(ptr); + } + else { + ROBIN_HOOD_LOG("add to buffer") + add(ptr, numBytes); + } + } + + void swap(BulkPoolAllocator& other) noexcept { + using std::swap; + swap(mHead, other.mHead); + swap(mListForFree, other.mListForFree); + } + + private: + // iterates the list of allocated memory to calculate how many to alloc next. + // Recalculating this each time saves us a size_t member. + // This ignores the fact that memory blocks might have been added manually with addOrFree. In + // practice, this should not matter much. + ROBIN_HOOD(NODISCARD) size_t calcNumElementsToAlloc() const noexcept { + auto tmp = mListForFree; + size_t numAllocs = MinNumAllocs; + + while (numAllocs * 2 <= MaxNumAllocs && tmp) { + auto x = reinterpret_cast(tmp); + tmp = *x; + numAllocs *= 2; + } + + return numAllocs; + } + + // WARNING: Underflow if numBytes < ALIGNMENT! This is guarded in addOrFree(). + void add(void* ptr, const size_t numBytes) noexcept { + const size_t numElements = (numBytes - ALIGNMENT) / ALIGNED_SIZE; + + auto data = reinterpret_cast(ptr); + + // link free list + auto x = reinterpret_cast(data); + *x = mListForFree; + mListForFree = data; + + // create linked list for newly allocated data + auto* const headT = + reinterpret_cast_no_cast_align_warning(reinterpret_cast(ptr) + ALIGNMENT); + + auto* const head = reinterpret_cast(headT); + + // Visual Studio compiler automatically unrolls this loop, which is pretty cool + for (size_t i = 0; i < numElements; ++i) { + *reinterpret_cast_no_cast_align_warning(head + i * ALIGNED_SIZE) = + head + (i + 1) * ALIGNED_SIZE; + } + + // last one points to 0 + *reinterpret_cast_no_cast_align_warning(head + (numElements - 1) * ALIGNED_SIZE) = + mHead; + mHead = headT; + } + + // Called when no memory is available (mHead == 0). + // Don't inline this slow path. + ROBIN_HOOD(NOINLINE) T* performAllocation() { + size_t const numElementsToAlloc = calcNumElementsToAlloc(); + + // alloc new memory: [prev |T, T, ... T] + size_t const bytes = ALIGNMENT + ALIGNED_SIZE * numElementsToAlloc; + ROBIN_HOOD_LOG("std::malloc " << bytes << " = " << ALIGNMENT << " + " << ALIGNED_SIZE + << " * " << numElementsToAlloc) + add(assertNotNull(std::malloc(bytes)), bytes); + return mHead; + } + + // enforce byte alignment of the T's +#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX14) + static constexpr size_t ALIGNMENT = + (std::max)(std::alignment_of::value, std::alignment_of::value); +#else + static const size_t ALIGNMENT = + (ROBIN_HOOD_STD::alignment_of::value > ROBIN_HOOD_STD::alignment_of::value) + ? ROBIN_HOOD_STD::alignment_of::value + : +ROBIN_HOOD_STD::alignment_of::value; // the + is for walkarround +#endif + + static constexpr size_t ALIGNED_SIZE = ((sizeof(T) - 1) / ALIGNMENT + 1) * ALIGNMENT; + + static_assert(MinNumAllocs >= 1, "MinNumAllocs"); + static_assert(MaxNumAllocs >= MinNumAllocs, "MaxNumAllocs"); + static_assert(ALIGNED_SIZE >= sizeof(T*), "ALIGNED_SIZE"); + static_assert(0 == (ALIGNED_SIZE % sizeof(T*)), "ALIGNED_SIZE mod"); + static_assert(ALIGNMENT >= sizeof(T*), "ALIGNMENT"); + + T* mHead{ nullptr }; + T** mListForFree{ nullptr }; + }; + + template + struct NodeAllocator; + + // dummy allocator that does nothing + template + struct NodeAllocator { + + // we are not using the data, so just free it. + void addOrFree(void* ptr, size_t ROBIN_HOOD_UNUSED(numBytes) /*unused*/) noexcept { + ROBIN_HOOD_LOG("std::free") + std::free(ptr); + } + }; + + template + struct NodeAllocator : public BulkPoolAllocator {}; + + // dummy hash, unsed as mixer when robin_hood::hash is already used + template + struct identity_hash { + constexpr size_t operator()(T const& obj) const noexcept { + return static_cast(obj); + } + }; + + // c++14 doesn't have is_nothrow_swappable, and clang++ 6.0.1 doesn't like it either, so I'm making + // my own here. + namespace swappable { +#if ROBIN_HOOD(CXX) < ROBIN_HOOD(CXX17) + using std::swap; + template + struct nothrow { + static const bool value = noexcept(swap(std::declval(), std::declval())); + }; +#else + template + struct nothrow { + static const bool value = std::is_nothrow_swappable::value; + }; +#endif + } // namespace swappable + + } // namespace detail + + struct is_transparent_tag {}; + + // A custom pair implementation is used in the map because std::pair is not is_trivially_copyable, + // which means it would not be allowed to be used in std::memcpy. This struct is copyable, which is + // also tested. + template + struct pair { + using first_type = T1; + using second_type = T2; + + template ::value&& + std::is_default_constructible::value>::type> + constexpr pair() noexcept(noexcept(U1()) && noexcept(U2())) + : first() + , second() {} + + // pair constructors are explicit so we don't accidentally call this ctor when we don't have to. + explicit constexpr pair(std::pair const& o) noexcept( + noexcept(T1(std::declval())) && noexcept(T2(std::declval()))) + : first(o.first) + , second(o.second) {} + + // pair constructors are explicit so we don't accidentally call this ctor when we don't have to. + explicit constexpr pair(std::pair&& o) noexcept(noexcept( + T1(std::move(std::declval()))) && noexcept(T2(std::move(std::declval())))) + : first(std::move(o.first)) + , second(std::move(o.second)) {} + + constexpr pair(T1&& a, T2&& b) noexcept(noexcept( + T1(std::move(std::declval()))) && noexcept(T2(std::move(std::declval())))) + : first(std::move(a)) + , second(std::move(b)) {} + + template + constexpr pair(U1&& a, U2&& b) noexcept(noexcept(T1(std::forward( + std::declval()))) && noexcept(T2(std::forward(std::declval())))) + : first(std::forward(a)) + , second(std::forward(b)) {} + + template + constexpr pair( + std::piecewise_construct_t /*unused*/, std::tuple a, + std::tuple b) noexcept(noexcept(pair(std::declval&>(), + std::declval&>(), + ROBIN_HOOD_STD::index_sequence_for(), + ROBIN_HOOD_STD::index_sequence_for()))) + : pair(a, b, ROBIN_HOOD_STD::index_sequence_for(), + ROBIN_HOOD_STD::index_sequence_for()) {} + + // constructor called from the std::piecewise_construct_t ctor + template + pair(std::tuple& a, std::tuple& b, ROBIN_HOOD_STD::index_sequence /*unused*/, ROBIN_HOOD_STD::index_sequence /*unused*/) noexcept( + noexcept(T1(std::forward(std::get( + std::declval&>()))...)) && noexcept(T2(std:: + forward(std::get( + std::declval&>()))...))) + : first(std::forward(std::get(a))...) + , second(std::forward(std::get(b))...) { + // make visual studio compiler happy about warning about unused a & b. + // Visual studio's pair implementation disables warning 4100. + (void)a; + (void)b; + } + + void swap(pair& o) noexcept((detail::swappable::nothrow::value) && + (detail::swappable::nothrow::value)) { + using std::swap; + swap(first, o.first); + swap(second, o.second); + } + + T1 first; // NOLINT(misc-non-private-member-variables-in-classes) + T2 second; // NOLINT(misc-non-private-member-variables-in-classes) + }; + + template + inline void swap(pair& a, pair& b) noexcept( + noexcept(std::declval&>().swap(std::declval&>()))) { + a.swap(b); + } + + template + inline constexpr bool operator==(pair const& x, pair const& y) { + return (x.first == y.first) && (x.second == y.second); + } + template + inline constexpr bool operator!=(pair const& x, pair const& y) { + return !(x == y); + } + template + inline constexpr bool operator<(pair const& x, pair const& y) noexcept(noexcept( + std::declval() < std::declval()) && noexcept(std::declval() < + std::declval())) { + return x.first < y.first || (!(y.first < x.first) && x.second < y.second); + } + template + inline constexpr bool operator>(pair const& x, pair const& y) { + return y < x; + } + template + inline constexpr bool operator<=(pair const& x, pair const& y) { + return !(x > y); + } + template + inline constexpr bool operator>=(pair const& x, pair const& y) { + return !(x < y); + } + + inline size_t hash_bytes(void const* ptr, size_t len) noexcept { + static constexpr uint64_t m = UINT64_C(0xc6a4a7935bd1e995); + static constexpr uint64_t seed = UINT64_C(0xe17a1465); + static constexpr unsigned int r = 47; + + auto const* const data64 = static_cast(ptr); + uint64_t h = seed ^ (len * m); + + size_t const n_blocks = len / 8; + for (size_t i = 0; i < n_blocks; ++i) { + auto k = detail::unaligned_load(data64 + i); + + k *= m; + k ^= k >> r; + k *= m; + + h ^= k; + h *= m; + } + + auto const* const data8 = reinterpret_cast(data64 + n_blocks); + switch (len & 7U) { + case 7: + h ^= static_cast(data8[6]) << 48U; + ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH + case 6: + h ^= static_cast(data8[5]) << 40U; + ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH + case 5: + h ^= static_cast(data8[4]) << 32U; + ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH + case 4: + h ^= static_cast(data8[3]) << 24U; + ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH + case 3: + h ^= static_cast(data8[2]) << 16U; + ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH + case 2: + h ^= static_cast(data8[1]) << 8U; + ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH + case 1: + h ^= static_cast(data8[0]); + h *= m; + ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH + default: + break; + } + + h ^= h >> r; + h *= m; + h ^= h >> r; + return static_cast(h); + } + + inline size_t hash_int(uint64_t x) noexcept { + // inspired by lemire's strongly universal hashing + // https://lemire.me/blog/2018/08/15/fast-strongly-universal-64-bit-hashing-everywhere/ + // + // Instead of shifts, we use rotations so we don't lose any bits. + // + // Added a final multiplcation with a constant for more mixing. It is most important that + // the lower bits are well mixed. + auto h1 = x * UINT64_C(0xA24BAED4963EE407); + auto h2 = detail::rotr(x, 32U) * UINT64_C(0x9FB21C651E98DF25); + auto h = detail::rotr(h1 + h2, 32U); + return static_cast(h); + } + + // A thin wrapper around std::hash, performing an additional simple mixing step of the result. + template + struct hash : public std::hash { + size_t operator()(T const& obj) const + noexcept(noexcept(std::declval>().operator()(std::declval()))) { + // call base hash + auto result = std::hash::operator()(obj); + // return mixed of that, to be save against identity has + return hash_int(static_cast(result)); + } + }; + + template + struct hash> { + size_t operator()(std::basic_string const& str) const noexcept { + return hash_bytes(str.data(), sizeof(CharT) * str.size()); + } + }; + +#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX17) + template + struct hash> { + size_t operator()(std::basic_string_view const& sv) const noexcept { + return hash_bytes(sv.data(), sizeof(CharT) * sv.size()); + } + }; +#endif + + template + struct hash { + size_t operator()(T* ptr) const noexcept { + return hash_int(reinterpret_cast(ptr)); + } + }; + + template + struct hash> { + size_t operator()(std::unique_ptr const& ptr) const noexcept { + return hash_int(reinterpret_cast(ptr.get())); + } + }; + + template + struct hash> { + size_t operator()(std::shared_ptr const& ptr) const noexcept { + return hash_int(reinterpret_cast(ptr.get())); + } + }; + + template + struct hash::value>::type> { + size_t operator()(Enum e) const noexcept { + using Underlying = typename std::underlying_type::type; + return hash{}(static_cast(e)); + } + }; + +#define ROBIN_HOOD_HASH_INT(T) \ + template <> \ + struct hash { \ + size_t operator()(T const& obj) const noexcept { \ + return hash_int(static_cast(obj)); \ + } \ + } + +#if defined(__GNUC__) && !defined(__clang__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wuseless-cast" +#endif + // see https://en.cppreference.com/w/cpp/utility/hash + ROBIN_HOOD_HASH_INT(bool); + ROBIN_HOOD_HASH_INT(char); + ROBIN_HOOD_HASH_INT(signed char); + ROBIN_HOOD_HASH_INT(unsigned char); + ROBIN_HOOD_HASH_INT(char16_t); + ROBIN_HOOD_HASH_INT(char32_t); +#if ROBIN_HOOD(HAS_NATIVE_WCHART) + ROBIN_HOOD_HASH_INT(wchar_t); +#endif + ROBIN_HOOD_HASH_INT(short); + ROBIN_HOOD_HASH_INT(unsigned short); + ROBIN_HOOD_HASH_INT(int); + ROBIN_HOOD_HASH_INT(unsigned int); + ROBIN_HOOD_HASH_INT(long); + ROBIN_HOOD_HASH_INT(long long); + ROBIN_HOOD_HASH_INT(unsigned long); + ROBIN_HOOD_HASH_INT(unsigned long long); +#if defined(__GNUC__) && !defined(__clang__) +# pragma GCC diagnostic pop +#endif + namespace detail { + + template + struct void_type { + using type = void; + }; + + template + struct has_is_transparent : public std::false_type {}; + + template + struct has_is_transparent::type> + : public std::true_type {}; + + // using wrapper classes for hash and key_equal prevents the diamond problem when the same type + // is used. see https://stackoverflow.com/a/28771920/48181 + template + struct WrapHash : public T { + WrapHash() = default; + explicit WrapHash(T const& o) noexcept(noexcept(T(std::declval()))) + : T(o) {} + }; + + template + struct WrapKeyEqual : public T { + WrapKeyEqual() = default; + explicit WrapKeyEqual(T const& o) noexcept(noexcept(T(std::declval()))) + : T(o) {} + }; + + // A highly optimized hashmap implementation, using the Robin Hood algorithm. + // + // In most cases, this map should be usable as a drop-in replacement for std::unordered_map, but + // be about 2x faster in most cases and require much less allocations. + // + // This implementation uses the following memory layout: + // + // [Node, Node, ... Node | info, info, ... infoSentinel ] + // + // * Node: either a DataNode that directly has the std::pair as member, + // or a DataNode with a pointer to std::pair. Which DataNode representation to use + // depends on how fast the swap() operation is. Heuristically, this is automatically choosen + // based on sizeof(). there are always 2^n Nodes. + // + // * info: Each Node in the map has a corresponding info byte, so there are 2^n info bytes. + // Each byte is initialized to 0, meaning the corresponding Node is empty. Set to 1 means the + // corresponding node contains data. Set to 2 means the corresponding Node is filled, but it + // actually belongs to the previous position and was pushed out because that place is already + // taken. + // + // * infoSentinel: Sentinel byte set to 1, so that iterator's ++ can stop at end() without the + // need for a idx variable. + // + // According to STL, order of templates has effect on throughput. That's why I've moved the + // boolean to the front. + // https://www.reddit.com/r/cpp/comments/ahp6iu/compile_time_binary_size_reductions_and_cs_future/eeguck4/ + template + class Table + : public WrapHash, + public WrapKeyEqual, + detail::NodeAllocator< + typename std::conditional< + std::is_void::value, Key, + robin_hood::pair::type, T>>::type, + 4, 16384, IsFlat> { + public: + static constexpr bool is_flat = IsFlat; + static constexpr bool is_map = !std::is_void::value; + static constexpr bool is_set = !is_map; + static constexpr bool is_transparent = + has_is_transparent::value && has_is_transparent::value; + + using key_type = Key; + using mapped_type = T; + using value_type = typename std::conditional< + is_set, Key, + robin_hood::pair::type, T>>::type; + using size_type = size_t; + using hasher = Hash; + using key_equal = KeyEqual; + using Self = Table; + + private: + static_assert(MaxLoadFactor100 > 10 && MaxLoadFactor100 < 100, + "MaxLoadFactor100 needs to be >10 && < 100"); + + using WHash = WrapHash; + using WKeyEqual = WrapKeyEqual; + + // configuration defaults + + // make sure we have 8 elements, needed to quickly rehash mInfo + static constexpr size_t InitialNumElements = sizeof(uint64_t); + static constexpr uint32_t InitialInfoNumBits = 5; + static constexpr uint8_t InitialInfoInc = 1U << InitialInfoNumBits; + static constexpr size_t InfoMask = InitialInfoInc - 1U; + static constexpr uint8_t InitialInfoHashShift = 0; + using DataPool = detail::NodeAllocator; + + // type needs to be wider than uint8_t. + using InfoType = uint32_t; + + // DataNode //////////////////////////////////////////////////////// + + // Primary template for the data node. We have special implementations for small and big + // objects. For large objects it is assumed that swap() is fairly slow, so we allocate these + // on the heap so swap merely swaps a pointer. + template + class DataNode {}; + + // Small: just allocate on the stack. + template + class DataNode final { + public: + template + explicit DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, Args&&... args) noexcept( + noexcept(value_type(std::forward(args)...))) + : mData(std::forward(args)...) {} + + DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, DataNode&& n) noexcept( + std::is_nothrow_move_constructible::value) + : mData(std::move(n.mData)) {} + + // doesn't do anything + void destroy(M& ROBIN_HOOD_UNUSED(map) /*unused*/) noexcept {} + void destroyDoNotDeallocate() noexcept {} + + value_type const* operator->() const noexcept { + return &mData; + } + value_type* operator->() noexcept { + return &mData; + } + + const value_type& operator*() const noexcept { + return mData; + } + + value_type& operator*() noexcept { + return mData; + } + + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getFirst() noexcept { + return mData.first; + } + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getFirst() noexcept { + return mData; + } + + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type + getFirst() const noexcept { + return mData.first; + } + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getFirst() const noexcept { + return mData; + } + + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getSecond() noexcept { + return mData.second; + } + + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getSecond() const noexcept { + return mData.second; + } + + void swap(DataNode& o) noexcept( + noexcept(std::declval().swap(std::declval()))) { + mData.swap(o.mData); + } + + private: + value_type mData; + }; + + // big object: allocate on heap. + template + class DataNode { + public: + template + explicit DataNode(M& map, Args&&... args) + : mData(map.allocate()) { + ::new (static_cast(mData)) value_type(std::forward(args)...); + } + + DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, DataNode&& n) noexcept + : mData(std::move(n.mData)) {} + + void destroy(M& map) noexcept { + // don't deallocate, just put it into list of datapool. + mData->~value_type(); + map.deallocate(mData); + } + + void destroyDoNotDeallocate() noexcept { + mData->~value_type(); + } + + value_type const* operator->() const noexcept { + return mData; + } + + value_type* operator->() noexcept { + return mData; + } + + const value_type& operator*() const { + return *mData; + } + + value_type& operator*() { + return *mData; + } + + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getFirst() noexcept { + return mData->first; + } + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getFirst() noexcept { + return *mData; + } + + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type + getFirst() const noexcept { + return mData->first; + } + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getFirst() const noexcept { + return *mData; + } + + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getSecond() noexcept { + return mData->second; + } + + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getSecond() const noexcept { + return mData->second; + } + + void swap(DataNode& o) noexcept { + using std::swap; + swap(mData, o.mData); + } + + private: + value_type* mData; + }; + + using Node = DataNode; + + // helpers for doInsert: extract first entry (only const required) + ROBIN_HOOD(NODISCARD) key_type const& getFirstConst(Node const& n) const noexcept { + return n.getFirst(); + } + + // in case we have void mapped_type, we are not using a pair, thus we just route k through. + // No need to disable this because it's just not used if not applicable. + ROBIN_HOOD(NODISCARD) key_type const& getFirstConst(key_type const& k) const noexcept { + return k; + } + + // in case we have non-void mapped_type, we have a standard robin_hood::pair + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::value, key_type const&>::type + getFirstConst(value_type const& vt) const noexcept { + return vt.first; + } + + // Cloner ////////////////////////////////////////////////////////// + + template + struct Cloner; + + // fast path: Just copy data, without allocating anything. + template + struct Cloner { + void operator()(M const& source, M& target) const { + auto const* const src = reinterpret_cast(source.mKeyVals); + auto* tgt = reinterpret_cast(target.mKeyVals); + auto const numElementsWithBuffer = target.calcNumElementsWithBuffer(target.mMask + 1); + std::copy(src, src + target.calcNumBytesTotal(numElementsWithBuffer), tgt); + } + }; + + template + struct Cloner { + void operator()(M const& s, M& t) const { + auto const numElementsWithBuffer = t.calcNumElementsWithBuffer(t.mMask + 1); + std::copy(s.mInfo, s.mInfo + t.calcNumBytesInfo(numElementsWithBuffer), t.mInfo); + + for (size_t i = 0; i < numElementsWithBuffer; ++i) { + if (t.mInfo[i]) { + ::new (static_cast(t.mKeyVals + i)) Node(t, *s.mKeyVals[i]); + } + } + } + }; + + // Destroyer /////////////////////////////////////////////////////// + + template + struct Destroyer {}; + + template + struct Destroyer { + void nodes(M& m) const noexcept { + m.mNumElements = 0; + } + + void nodesDoNotDeallocate(M& m) const noexcept { + m.mNumElements = 0; + } + }; + + template + struct Destroyer { + void nodes(M& m) const noexcept { + m.mNumElements = 0; + // clear also resets mInfo to 0, that's sometimes not necessary. + auto const numElementsWithBuffer = m.calcNumElementsWithBuffer(m.mMask + 1); + + for (size_t idx = 0; idx < numElementsWithBuffer; ++idx) { + if (0 != m.mInfo[idx]) { + Node& n = m.mKeyVals[idx]; + n.destroy(m); + n.~Node(); + } + } + } + + void nodesDoNotDeallocate(M& m) const noexcept { + m.mNumElements = 0; + // clear also resets mInfo to 0, that's sometimes not necessary. + auto const numElementsWithBuffer = m.calcNumElementsWithBuffer(m.mMask + 1); + for (size_t idx = 0; idx < numElementsWithBuffer; ++idx) { + if (0 != m.mInfo[idx]) { + Node& n = m.mKeyVals[idx]; + n.destroyDoNotDeallocate(); + n.~Node(); + } + } + } + }; + + // Iter //////////////////////////////////////////////////////////// + + struct fast_forward_tag {}; + + // generic iterator for both const_iterator and iterator. + template + // NOLINTNEXTLINE(hicpp-special-member-functions,cppcoreguidelines-special-member-functions) + class Iter { + private: + using NodePtr = typename std::conditional::type; + + public: + using difference_type = std::ptrdiff_t; + using value_type = typename Self::value_type; + using reference = typename std::conditional::type; + using pointer = typename std::conditional::type; + using iterator_category = std::forward_iterator_tag; + + // default constructed iterator can be compared to itself, but WON'T return true when + // compared to end(). + Iter() = default; + + // Rule of zero: nothing specified. The conversion constructor is only enabled for + // iterator to const_iterator, so it doesn't accidentally work as a copy ctor. + + // Conversion constructor from iterator to const_iterator. + template ::type> + // NOLINTNEXTLINE(hicpp-explicit-conversions) + Iter(Iter const& other) noexcept + : mKeyVals(other.mKeyVals) + , mInfo(other.mInfo) {} + + Iter(NodePtr valPtr, uint8_t const* infoPtr) noexcept + : mKeyVals(valPtr) + , mInfo(infoPtr) {} + + Iter(NodePtr valPtr, uint8_t const* infoPtr, + fast_forward_tag ROBIN_HOOD_UNUSED(tag) /*unused*/) noexcept + : mKeyVals(valPtr) + , mInfo(infoPtr) { + fastForward(); + } + + template ::type> + Iter& operator=(Iter const& other) noexcept { + mKeyVals = other.mKeyVals; + mInfo = other.mInfo; + return *this; + } + + // prefix increment. Undefined behavior if we are at end()! + Iter& operator++() noexcept { + mInfo++; + mKeyVals++; + fastForward(); + return *this; + } + + Iter operator++(int) noexcept { + Iter tmp = *this; + ++(*this); + return tmp; + } + + reference operator*() const { + return **mKeyVals; + } + + pointer operator->() const { + return &**mKeyVals; + } + + template + bool operator==(Iter const& o) const noexcept { + return mKeyVals == o.mKeyVals; + } + + template + bool operator!=(Iter const& o) const noexcept { + return mKeyVals != o.mKeyVals; + } + + private: + // fast forward to the next non-free info byte + // I've tried a few variants that don't depend on intrinsics, but unfortunately they are + // quite a bit slower than this one. So I've reverted that change again. See map_benchmark. + void fastForward() noexcept { + size_t n = 0; + while (0U == (n = detail::unaligned_load(mInfo))) { + mInfo += sizeof(size_t); + mKeyVals += sizeof(size_t); + } +#if defined(ROBIN_HOOD_DISABLE_INTRINSICS) + // we know for certain that within the next 8 bytes we'll find a non-zero one. + if (ROBIN_HOOD_UNLIKELY(0U == detail::unaligned_load(mInfo))) { + mInfo += 4; + mKeyVals += 4; + } + if (ROBIN_HOOD_UNLIKELY(0U == detail::unaligned_load(mInfo))) { + mInfo += 2; + mKeyVals += 2; + } + if (ROBIN_HOOD_UNLIKELY(0U == *mInfo)) { + mInfo += 1; + mKeyVals += 1; + } +#else +# if ROBIN_HOOD(LITTLE_ENDIAN) + auto inc = ROBIN_HOOD_COUNT_TRAILING_ZEROES(n) / 8; +# else + auto inc = ROBIN_HOOD_COUNT_LEADING_ZEROES(n) / 8; +# endif + mInfo += inc; + mKeyVals += inc; +#endif + } + + friend class Table; + NodePtr mKeyVals{ nullptr }; + uint8_t const* mInfo{ nullptr }; + }; + + //////////////////////////////////////////////////////////////////// + + // highly performance relevant code. + // Lower bits are used for indexing into the array (2^n size) + // The upper 1-5 bits need to be a reasonable good hash, to save comparisons. + template + void keyToIdx(HashKey&& key, size_t* idx, InfoType* info) const { + // for a user-specified hash that is *not* robin_hood::hash, apply robin_hood::hash as + // an additional mixing step. This serves as a bad hash prevention, if the given data is + // badly mixed. + using Mix = + typename std::conditional, hasher>::value, + ::robin_hood::detail::identity_hash, + ::robin_hood::hash>::type; + + // the lower InitialInfoNumBits are reserved for info. + auto h = Mix{}(WHash::operator()(key)); + *info = mInfoInc + static_cast((h & InfoMask) >> mInfoHashShift); + *idx = (h >> InitialInfoNumBits) & mMask; + } + + // forwards the index by one, wrapping around at the end + void next(InfoType* info, size_t* idx) const noexcept { + *idx = *idx + 1; + *info += mInfoInc; + } + + void nextWhileLess(InfoType* info, size_t* idx) const noexcept { + // unrolling this by hand did not bring any speedups. + while (*info < mInfo[*idx]) { + next(info, idx); + } + } + + // Shift everything up by one element. Tries to move stuff around. + void + shiftUp(size_t startIdx, + size_t const insertion_idx) noexcept(std::is_nothrow_move_assignable::value) { + auto idx = startIdx; + ::new (static_cast(mKeyVals + idx)) Node(std::move(mKeyVals[idx - 1])); + while (--idx != insertion_idx) { + mKeyVals[idx] = std::move(mKeyVals[idx - 1]); + } + + idx = startIdx; + while (idx != insertion_idx) { + ROBIN_HOOD_COUNT(shiftUp) + mInfo[idx] = static_cast(mInfo[idx - 1] + mInfoInc); + if (ROBIN_HOOD_UNLIKELY(mInfo[idx] + mInfoInc > 0xFF)) { + mMaxNumElementsAllowed = 0; + } + --idx; + } + } + + void shiftDown(size_t idx) noexcept(std::is_nothrow_move_assignable::value) { + // until we find one that is either empty or has zero offset. + // TODO(martinus) we don't need to move everything, just the last one for the same + // bucket. + mKeyVals[idx].destroy(*this); + + // until we find one that is either empty or has zero offset. + while (mInfo[idx + 1] >= 2 * mInfoInc) { + ROBIN_HOOD_COUNT(shiftDown) + mInfo[idx] = static_cast(mInfo[idx + 1] - mInfoInc); + mKeyVals[idx] = std::move(mKeyVals[idx + 1]); + ++idx; + } + + mInfo[idx] = 0; + // don't destroy, we've moved it + // mKeyVals[idx].destroy(*this); + mKeyVals[idx].~Node(); + } + + // copy of find(), except that it returns iterator instead of const_iterator. + template + ROBIN_HOOD(NODISCARD) + size_t findIdx(Other const& key) const { + size_t idx{}; + InfoType info{}; + keyToIdx(key, &idx, &info); + + do { + // unrolling this twice gives a bit of a speedup. More unrolling did not help. + if (info == mInfo[idx] && + ROBIN_HOOD_LIKELY(WKeyEqual::operator()(key, mKeyVals[idx].getFirst()))) { + return idx; + } + next(&info, &idx); + if (info == mInfo[idx] && + ROBIN_HOOD_LIKELY(WKeyEqual::operator()(key, mKeyVals[idx].getFirst()))) { + return idx; + } + next(&info, &idx); + } while (info <= mInfo[idx]); + + // nothing found! + return mMask == 0 ? 0 + : static_cast(std::distance( + mKeyVals, reinterpret_cast_no_cast_align_warning(mInfo))); + } + + void cloneData(const Table& o) { + Cloner()(o, *this); + } + + // inserts a keyval that is guaranteed to be new, e.g. when the hashmap is resized. + // @return index where the element was created + size_t insert_move(Node&& keyval) { + // we don't retry, fail if overflowing + // don't need to check max num elements + if (0 == mMaxNumElementsAllowed && !try_increase_info()) { + throwOverflowError(); // impossible to reach LCOV_EXCL_LINE + } + + size_t idx{}; + InfoType info{}; + keyToIdx(keyval.getFirst(), &idx, &info); + + // skip forward. Use <= because we are certain that the element is not there. + while (info <= mInfo[idx]) { + idx = idx + 1; + info += mInfoInc; + } + + // key not found, so we are now exactly where we want to insert it. + auto const insertion_idx = idx; + auto const insertion_info = static_cast(info); + if (ROBIN_HOOD_UNLIKELY(insertion_info + mInfoInc > 0xFF)) { + mMaxNumElementsAllowed = 0; + } + + // find an empty spot + while (0 != mInfo[idx]) { + next(&info, &idx); + } + + auto& l = mKeyVals[insertion_idx]; + if (idx == insertion_idx) { + ::new (static_cast(&l)) Node(std::move(keyval)); + } + else { + shiftUp(idx, insertion_idx); + l = std::move(keyval); + } + + // put at empty spot + mInfo[insertion_idx] = insertion_info; + + ++mNumElements; + return insertion_idx; + } + + public: + using iterator = Iter; + using const_iterator = Iter; + + Table() noexcept(noexcept(Hash()) && noexcept(KeyEqual())) + : WHash() + , WKeyEqual() { + ROBIN_HOOD_TRACE(this) + } + + // Creates an empty hash map. Nothing is allocated yet, this happens at the first insert. + // This tremendously speeds up ctor & dtor of a map that never receives an element. The + // penalty is payed at the first insert, and not before. Lookup of this empty map works + // because everybody points to DummyInfoByte::b. parameter bucket_count is dictated by the + // standard, but we can ignore it. + explicit Table( + size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/, const Hash& h = Hash{}, + const KeyEqual& equal = KeyEqual{}) noexcept(noexcept(Hash(h)) && noexcept(KeyEqual(equal))) + : WHash(h) + , WKeyEqual(equal) { + ROBIN_HOOD_TRACE(this) + } + + template + Table(Iter first, Iter last, size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/ = 0, + const Hash& h = Hash{}, const KeyEqual& equal = KeyEqual{}) + : WHash(h) + , WKeyEqual(equal) { + ROBIN_HOOD_TRACE(this) + insert(first, last); + } + + Table(std::initializer_list initlist, + size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/ = 0, const Hash& h = Hash{}, + const KeyEqual& equal = KeyEqual{}) + : WHash(h) + , WKeyEqual(equal) { + ROBIN_HOOD_TRACE(this) + insert(initlist.begin(), initlist.end()); + } + + Table(Table&& o) noexcept + : WHash(std::move(static_cast(o))) + , WKeyEqual(std::move(static_cast(o))) + , DataPool(std::move(static_cast(o))) { + ROBIN_HOOD_TRACE(this) + if (o.mMask) { + mKeyVals = std::move(o.mKeyVals); + mInfo = std::move(o.mInfo); + mNumElements = std::move(o.mNumElements); + mMask = std::move(o.mMask); + mMaxNumElementsAllowed = std::move(o.mMaxNumElementsAllowed); + mInfoInc = std::move(o.mInfoInc); + mInfoHashShift = std::move(o.mInfoHashShift); + // set other's mask to 0 so its destructor won't do anything + o.init(); + } + } + + Table& operator=(Table&& o) noexcept { + ROBIN_HOOD_TRACE(this) + if (&o != this) { + if (o.mMask) { + // only move stuff if the other map actually has some data + destroy(); + mKeyVals = std::move(o.mKeyVals); + mInfo = std::move(o.mInfo); + mNumElements = std::move(o.mNumElements); + mMask = std::move(o.mMask); + mMaxNumElementsAllowed = std::move(o.mMaxNumElementsAllowed); + mInfoInc = std::move(o.mInfoInc); + mInfoHashShift = std::move(o.mInfoHashShift); + WHash::operator=(std::move(static_cast(o))); + WKeyEqual::operator=(std::move(static_cast(o))); + DataPool::operator=(std::move(static_cast(o))); + + o.init(); + + } + else { + // nothing in the other map => just clear us. + clear(); + } + } + return *this; + } + + Table(const Table& o) + : WHash(static_cast(o)) + , WKeyEqual(static_cast(o)) + , DataPool(static_cast(o)) { + ROBIN_HOOD_TRACE(this) + if (!o.empty()) { + // not empty: create an exact copy. it is also possible to just iterate through all + // elements and insert them, but copying is probably faster. + + auto const numElementsWithBuffer = calcNumElementsWithBuffer(o.mMask + 1); + auto const numBytesTotal = calcNumBytesTotal(numElementsWithBuffer); + + ROBIN_HOOD_LOG("std::malloc " << numBytesTotal << " = calcNumBytesTotal(" + << numElementsWithBuffer << ")") + mKeyVals = static_cast( + detail::assertNotNull(std::malloc(numBytesTotal))); + // no need for calloc because clonData does memcpy + mInfo = reinterpret_cast(mKeyVals + numElementsWithBuffer); + mNumElements = o.mNumElements; + mMask = o.mMask; + mMaxNumElementsAllowed = o.mMaxNumElementsAllowed; + mInfoInc = o.mInfoInc; + mInfoHashShift = o.mInfoHashShift; + cloneData(o); + } + } + + // Creates a copy of the given map. Copy constructor of each entry is used. + // Not sure why clang-tidy thinks this doesn't handle self assignment, it does + // NOLINTNEXTLINE(bugprone-unhandled-self-assignment,cert-oop54-cpp) + Table& operator=(Table const& o) { + ROBIN_HOOD_TRACE(this) + if (&o == this) { + // prevent assigning of itself + return *this; + } + + // we keep using the old allocator and not assign the new one, because we want to keep + // the memory available. when it is the same size. + if (o.empty()) { + if (0 == mMask) { + // nothing to do, we are empty too + return *this; + } + + // not empty: destroy what we have there + // clear also resets mInfo to 0, that's sometimes not necessary. + destroy(); + init(); + WHash::operator=(static_cast(o)); + WKeyEqual::operator=(static_cast(o)); + DataPool::operator=(static_cast(o)); + + return *this; + } + + // clean up old stuff + Destroyer::value>{}.nodes(*this); + + if (mMask != o.mMask) { + // no luck: we don't have the same array size allocated, so we need to realloc. + if (0 != mMask) { + // only deallocate if we actually have data! + ROBIN_HOOD_LOG("std::free") + std::free(mKeyVals); + } + + auto const numElementsWithBuffer = calcNumElementsWithBuffer(o.mMask + 1); + auto const numBytesTotal = calcNumBytesTotal(numElementsWithBuffer); + ROBIN_HOOD_LOG("std::malloc " << numBytesTotal << " = calcNumBytesTotal(" + << numElementsWithBuffer << ")") + mKeyVals = static_cast( + detail::assertNotNull(std::malloc(numBytesTotal))); + + // no need for calloc here because cloneData performs a memcpy. + mInfo = reinterpret_cast(mKeyVals + numElementsWithBuffer); + // sentinel is set in cloneData + } + WHash::operator=(static_cast(o)); + WKeyEqual::operator=(static_cast(o)); + DataPool::operator=(static_cast(o)); + mNumElements = o.mNumElements; + mMask = o.mMask; + mMaxNumElementsAllowed = o.mMaxNumElementsAllowed; + mInfoInc = o.mInfoInc; + mInfoHashShift = o.mInfoHashShift; + cloneData(o); + + return *this; + } + + // Swaps everything between the two maps. + void swap(Table& o) { + ROBIN_HOOD_TRACE(this) + using std::swap; + swap(o, *this); + } + + // Clears all data, without resizing. + void clear() { + ROBIN_HOOD_TRACE(this) + if (empty()) { + // don't do anything! also important because we don't want to write to + // DummyInfoByte::b, even though we would just write 0 to it. + return; + } + + Destroyer::value>{}.nodes(*this); + + auto const numElementsWithBuffer = calcNumElementsWithBuffer(mMask + 1); + // clear everything, then set the sentinel again + uint8_t const z = 0; + std::fill(mInfo, mInfo + calcNumBytesInfo(numElementsWithBuffer), z); + mInfo[numElementsWithBuffer] = 1; + + mInfoInc = InitialInfoInc; + mInfoHashShift = InitialInfoHashShift; + } + + // Destroys the map and all it's contents. + ~Table() { + ROBIN_HOOD_TRACE(this) + destroy(); + } + + // Checks if both tables contain the same entries. Order is irrelevant. + bool operator==(const Table& other) const { + ROBIN_HOOD_TRACE(this) + if (other.size() != size()) { + return false; + } + for (auto const& otherEntry : other) { + if (!has(otherEntry)) { + return false; + } + } + + return true; + } + + bool operator!=(const Table& other) const { + ROBIN_HOOD_TRACE(this) + return !operator==(other); + } + + template + typename std::enable_if::value, Q&>::type operator[](const key_type& key) { + ROBIN_HOOD_TRACE(this) + return doCreateByKey(key); + } + + template + typename std::enable_if::value, Q&>::type operator[](key_type&& key) { + ROBIN_HOOD_TRACE(this) + return doCreateByKey(std::move(key)); + } + + template + void insert(Iter first, Iter last) { + for (; first != last; ++first) { + // value_type ctor needed because this might be called with std::pair's + insert(value_type(*first)); + } + } + + template + std::pair emplace(Args&&... args) { + ROBIN_HOOD_TRACE(this) + Node n { + *this, std::forward(args)... + }; + auto r = doInsert(std::move(n)); + if (!r.second) { + // insertion not possible: destroy node + // NOLINTNEXTLINE(bugprone-use-after-move) + n.destroy(*this); + } + return r; + } + + template + std::pair try_emplace(const key_type& key, Args&&... args) { + return try_emplace_impl(key, std::forward(args)...); + } + + template + std::pair try_emplace(key_type&& key, Args&&... args) { + return try_emplace_impl(std::move(key), std::forward(args)...); + } + + template + std::pair try_emplace(const_iterator hint, const key_type& key, + Args&&... args) { + (void)hint; + return try_emplace_impl(key, std::forward(args)...); + } + + template + std::pair try_emplace(const_iterator hint, key_type&& key, Args&&... args) { + (void)hint; + return try_emplace_impl(std::move(key), std::forward(args)...); + } + + template + std::pair insert_or_assign(const key_type& key, Mapped&& obj) { + return insert_or_assign_impl(key, std::forward(obj)); + } + + template + std::pair insert_or_assign(key_type&& key, Mapped&& obj) { + return insert_or_assign_impl(std::move(key), std::forward(obj)); + } + + template + std::pair insert_or_assign(const_iterator hint, const key_type& key, + Mapped&& obj) { + (void)hint; + return insert_or_assign_impl(key, std::forward(obj)); + } + + template + std::pair insert_or_assign(const_iterator hint, key_type&& key, Mapped&& obj) { + (void)hint; + return insert_or_assign_impl(std::move(key), std::forward(obj)); + } + + std::pair insert(const value_type& keyval) { + ROBIN_HOOD_TRACE(this) + return doInsert(keyval); + } + + std::pair insert(value_type&& keyval) { + return doInsert(std::move(keyval)); + } + + // Returns 1 if key is found, 0 otherwise. + size_t count(const key_type& key) const { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) + auto kv = mKeyVals + findIdx(key); + if (kv != reinterpret_cast_no_cast_align_warning(mInfo)) { + return 1; + } + return 0; + } + + template + // NOLINTNEXTLINE(modernize-use-nodiscard) + typename std::enable_if::type count(const OtherKey& key) const { + ROBIN_HOOD_TRACE(this) + auto kv = mKeyVals + findIdx(key); + if (kv != reinterpret_cast_no_cast_align_warning(mInfo)) { + return 1; + } + return 0; + } + + bool contains(const key_type& key) const { // NOLINT(modernize-use-nodiscard) + return 1U == count(key); + } + + template + // NOLINTNEXTLINE(modernize-use-nodiscard) + typename std::enable_if::type contains(const OtherKey& key) const { + return 1U == count(key); + } + + // Returns a reference to the value found for key. + // Throws std::out_of_range if element cannot be found + template + // NOLINTNEXTLINE(modernize-use-nodiscard) + typename std::enable_if::value, Q&>::type at(key_type const& key) { + ROBIN_HOOD_TRACE(this) + auto kv = mKeyVals + findIdx(key); + if (kv == reinterpret_cast_no_cast_align_warning(mInfo)) { + doThrow("key not found"); + } + return kv->getSecond(); + } + + // Returns a reference to the value found for key. + // Throws std::out_of_range if element cannot be found + template + // NOLINTNEXTLINE(modernize-use-nodiscard) + typename std::enable_if::value, Q const&>::type at(key_type const& key) const { + ROBIN_HOOD_TRACE(this) + auto kv = mKeyVals + findIdx(key); + if (kv == reinterpret_cast_no_cast_align_warning(mInfo)) { + doThrow("key not found"); + } + return kv->getSecond(); + } + + const_iterator find(const key_type& key) const { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) + const size_t idx = findIdx(key); + return const_iterator{ mKeyVals + idx, mInfo + idx }; + } + + template + const_iterator find(const OtherKey& key, is_transparent_tag /*unused*/) const { + ROBIN_HOOD_TRACE(this) + const size_t idx = findIdx(key); + return const_iterator{ mKeyVals + idx, mInfo + idx }; + } + + template + typename std::enable_if::type // NOLINT(modernize-use-nodiscard) + find(const OtherKey& key) const { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) + const size_t idx = findIdx(key); + return const_iterator{ mKeyVals + idx, mInfo + idx }; + } + + iterator find(const key_type& key) { + ROBIN_HOOD_TRACE(this) + const size_t idx = findIdx(key); + return iterator{ mKeyVals + idx, mInfo + idx }; + } + + template + iterator find(const OtherKey& key, is_transparent_tag /*unused*/) { + ROBIN_HOOD_TRACE(this) + const size_t idx = findIdx(key); + return iterator{ mKeyVals + idx, mInfo + idx }; + } + + template + typename std::enable_if::type find(const OtherKey& key) { + ROBIN_HOOD_TRACE(this) + const size_t idx = findIdx(key); + return iterator{ mKeyVals + idx, mInfo + idx }; + } + + iterator begin() { + ROBIN_HOOD_TRACE(this) + if (empty()) { + return end(); + } + return iterator(mKeyVals, mInfo, fast_forward_tag{}); + } + const_iterator begin() const { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) + return cbegin(); + } + const_iterator cbegin() const { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) + if (empty()) { + return cend(); + } + return const_iterator(mKeyVals, mInfo, fast_forward_tag{}); + } + + iterator end() { + ROBIN_HOOD_TRACE(this) + // no need to supply valid info pointer: end() must not be dereferenced, and only node + // pointer is compared. + return iterator{ reinterpret_cast_no_cast_align_warning(mInfo), nullptr }; + } + const_iterator end() const { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) + return cend(); + } + const_iterator cend() const { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) + return const_iterator{ reinterpret_cast_no_cast_align_warning(mInfo), nullptr }; + } + + iterator erase(const_iterator pos) { + ROBIN_HOOD_TRACE(this) + // its safe to perform const cast here + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) + return erase(iterator{ const_cast(pos.mKeyVals), const_cast(pos.mInfo) }); + } + + // Erases element at pos, returns iterator to the next element. + iterator erase(iterator pos) { + ROBIN_HOOD_TRACE(this) + // we assume that pos always points to a valid entry, and not end(). + auto const idx = static_cast(pos.mKeyVals - mKeyVals); + + shiftDown(idx); + --mNumElements; + + if (*pos.mInfo) { + // we've backward shifted, return this again + return pos; + } + + // no backward shift, return next element + return ++pos; + } + + size_t erase(const key_type& key) { + ROBIN_HOOD_TRACE(this) + size_t idx {}; + InfoType info{}; + keyToIdx(key, &idx, &info); + + // check while info matches with the source idx + do { + if (info == mInfo[idx] && WKeyEqual::operator()(key, mKeyVals[idx].getFirst())) { + shiftDown(idx); + --mNumElements; + return 1; + } + next(&info, &idx); + } while (info <= mInfo[idx]); + + // nothing found to delete + return 0; + } + + // reserves space for the specified number of elements. Makes sure the old data fits. + // exactly the same as reserve(c). + void rehash(size_t c) { + // forces a reserve + reserve(c, true); + } + + // reserves space for the specified number of elements. Makes sure the old data fits. + // Exactly the same as rehash(c). Use rehash(0) to shrink to fit. + void reserve(size_t c) { + // reserve, but don't force rehash + reserve(c, false); + } + + // If possible reallocates the map to a smaller one. This frees the underlying table. + // Does not do anything if load_factor is too large for decreasing the table's size. + void compact() { + ROBIN_HOOD_TRACE(this) + auto newSize = InitialNumElements; + while (calcMaxNumElementsAllowed(newSize) < mNumElements && newSize != 0) { + newSize *= 2; + } + if (ROBIN_HOOD_UNLIKELY(newSize == 0)) { + throwOverflowError(); + } + + ROBIN_HOOD_LOG("newSize > mMask + 1: " << newSize << " > " << mMask << " + 1") + + // only actually do anything when the new size is bigger than the old one. This prevents to + // continuously allocate for each reserve() call. + if (newSize < mMask + 1) { + rehashPowerOfTwo(newSize, true); + } + } + + size_type size() const noexcept { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) + return mNumElements; + } + + size_type max_size() const noexcept { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) + return static_cast(-1); + } + + ROBIN_HOOD(NODISCARD) bool empty() const noexcept { + ROBIN_HOOD_TRACE(this) + return 0 == mNumElements; + } + + float max_load_factor() const noexcept { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) + return MaxLoadFactor100 / 100.0F; + } + + // Average number of elements per bucket. Since we allow only 1 per bucket + float load_factor() const noexcept { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) + return static_cast(size()) / static_cast(mMask + 1); + } + + ROBIN_HOOD(NODISCARD) size_t mask() const noexcept { + ROBIN_HOOD_TRACE(this) + return mMask; + } + + ROBIN_HOOD(NODISCARD) size_t calcMaxNumElementsAllowed(size_t maxElements) const noexcept { + if (ROBIN_HOOD_LIKELY(maxElements <= (std::numeric_limits::max)() / 100)) { + return maxElements * MaxLoadFactor100 / 100; + } + + // we might be a bit inprecise, but since maxElements is quite large that doesn't matter + return (maxElements / 100) * MaxLoadFactor100; + } + + ROBIN_HOOD(NODISCARD) size_t calcNumBytesInfo(size_t numElements) const noexcept { + // we add a uint64_t, which houses the sentinel (first byte) and padding so we can load + // 64bit types. + return numElements + sizeof(uint64_t); + } + + ROBIN_HOOD(NODISCARD) + size_t calcNumElementsWithBuffer(size_t numElements) const noexcept { + auto maxNumElementsAllowed = calcMaxNumElementsAllowed(numElements); + return numElements + (std::min)(maxNumElementsAllowed, (static_cast(0xFF))); + } + + // calculation only allowed for 2^n values + ROBIN_HOOD(NODISCARD) size_t calcNumBytesTotal(size_t numElements) const { +#if ROBIN_HOOD(BITNESS) == 64 + return numElements * sizeof(Node) + calcNumBytesInfo(numElements); +#else + // make sure we're doing 64bit operations, so we are at least safe against 32bit overflows. + auto const ne = static_cast(numElements); + auto const s = static_cast(sizeof(Node)); + auto const infos = static_cast(calcNumBytesInfo(numElements)); + + auto const total64 = ne * s + infos; + auto const total = static_cast(total64); + + if (ROBIN_HOOD_UNLIKELY(static_cast(total) != total64)) { + throwOverflowError(); + } + return total; +#endif + } + + private: + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::value, bool>::type has(const value_type& e) const { + ROBIN_HOOD_TRACE(this) + auto it = find(e.first); + return it != end() && it->second == e.second; + } + + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::value, bool>::type has(const value_type& e) const { + ROBIN_HOOD_TRACE(this) + return find(e) != end(); + } + + void reserve(size_t c, bool forceRehash) { + ROBIN_HOOD_TRACE(this) + auto const minElementsAllowed = (std::max)(c, mNumElements); + auto newSize = InitialNumElements; + while (calcMaxNumElementsAllowed(newSize) < minElementsAllowed && newSize != 0) { + newSize *= 2; + } + if (ROBIN_HOOD_UNLIKELY(newSize == 0)) { + throwOverflowError(); + } + + ROBIN_HOOD_LOG("newSize > mMask + 1: " << newSize << " > " << mMask << " + 1") + + // only actually do anything when the new size is bigger than the old one. This prevents to + // continuously allocate for each reserve() call. + if (forceRehash || newSize > mMask + 1) { + rehashPowerOfTwo(newSize, false); + } + } + + // reserves space for at least the specified number of elements. + // only works if numBuckets if power of two + void rehashPowerOfTwo(size_t numBuckets, bool forceFree) { + ROBIN_HOOD_TRACE(this) + + Node* const oldKeyVals = mKeyVals; + uint8_t const* const oldInfo = mInfo; + + const size_t oldMaxElementsWithBuffer = calcNumElementsWithBuffer(mMask + 1); + + // resize operation: move stuff + init_data(numBuckets); + if (oldMaxElementsWithBuffer > 1) { + for (size_t i = 0; i < oldMaxElementsWithBuffer; ++i) { + if (oldInfo[i] != 0) { + insert_move(std::move(oldKeyVals[i])); + // destroy the node but DON'T destroy the data. + oldKeyVals[i].~Node(); + } + } + + // this check is not necessary as it's guarded by the previous if, but it helps silence + // g++'s overeager "attempt to free a non-heap object 'map' + // [-Werror=free-nonheap-object]" warning. + if (oldKeyVals != reinterpret_cast_no_cast_align_warning(&mMask)) { + // don't destroy old data: put it into the pool instead + if (forceFree) { + std::free(oldKeyVals); + } + else { + DataPool::addOrFree(oldKeyVals, calcNumBytesTotal(oldMaxElementsWithBuffer)); + } + } + } + } + + ROBIN_HOOD(NOINLINE) void throwOverflowError() const { +#if ROBIN_HOOD(HAS_EXCEPTIONS) + throw std::overflow_error("robin_hood::map overflow"); +#else + abort(); +#endif + } + + template + std::pair try_emplace_impl(OtherKey&& key, Args&&... args) { + ROBIN_HOOD_TRACE(this) + auto it = find(key); + if (it == end()) { + return emplace(std::piecewise_construct, + std::forward_as_tuple(std::forward(key)), + std::forward_as_tuple(std::forward(args)...)); + } + return { it, false }; + } + + template + std::pair insert_or_assign_impl(OtherKey&& key, Mapped&& obj) { + ROBIN_HOOD_TRACE(this) + auto it = find(key); + if (it == end()) { + return emplace(std::forward(key), std::forward(obj)); + } + it->second = std::forward(obj); + return { it, false }; + } + + void init_data(size_t max_elements) { + mNumElements = 0; + mMask = max_elements - 1; + mMaxNumElementsAllowed = calcMaxNumElementsAllowed(max_elements); + + auto const numElementsWithBuffer = calcNumElementsWithBuffer(max_elements); + + // calloc also zeroes everything + auto const numBytesTotal = calcNumBytesTotal(numElementsWithBuffer); + ROBIN_HOOD_LOG("std::calloc " << numBytesTotal << " = calcNumBytesTotal(" + << numElementsWithBuffer << ")") + mKeyVals = reinterpret_cast( + detail::assertNotNull(std::calloc(1, numBytesTotal))); + mInfo = reinterpret_cast(mKeyVals + numElementsWithBuffer); + + // set sentinel + mInfo[numElementsWithBuffer] = 1; + + mInfoInc = InitialInfoInc; + mInfoHashShift = InitialInfoHashShift; + } + + template + typename std::enable_if::value, Q&>::type doCreateByKey(Arg&& key) { + while (true) { + size_t idx{}; + InfoType info{}; + keyToIdx(key, &idx, &info); + nextWhileLess(&info, &idx); + + // while we potentially have a match. Can't do a do-while here because when mInfo is + // 0 we don't want to skip forward + while (info == mInfo[idx]) { + if (WKeyEqual::operator()(key, mKeyVals[idx].getFirst())) { + // key already exists, do not insert. + return mKeyVals[idx].getSecond(); + } + next(&info, &idx); + } + + // unlikely that this evaluates to true + if (ROBIN_HOOD_UNLIKELY(mNumElements >= mMaxNumElementsAllowed)) { + increase_size(); + continue; + } + + // key not found, so we are now exactly where we want to insert it. + auto const insertion_idx = idx; + auto const insertion_info = info; + if (ROBIN_HOOD_UNLIKELY(insertion_info + mInfoInc > 0xFF)) { + mMaxNumElementsAllowed = 0; + } + + // find an empty spot + while (0 != mInfo[idx]) { + next(&info, &idx); + } + + auto& l = mKeyVals[insertion_idx]; + if (idx == insertion_idx) { + // put at empty spot. This forwards all arguments into the node where the object + // is constructed exactly where it is needed. + ::new (static_cast(&l)) + Node(*this, std::piecewise_construct, + std::forward_as_tuple(std::forward(key)), std::forward_as_tuple()); + } + else { + shiftUp(idx, insertion_idx); + l = Node(*this, std::piecewise_construct, + std::forward_as_tuple(std::forward(key)), std::forward_as_tuple()); + } + + // mKeyVals[idx].getFirst() = std::move(key); + mInfo[insertion_idx] = static_cast(insertion_info); + + ++mNumElements; + return mKeyVals[insertion_idx].getSecond(); + } + } + + // This is exactly the same code as operator[], except for the return values + template + std::pair doInsert(Arg&& keyval) { + while (true) { + size_t idx{}; + InfoType info{}; + keyToIdx(getFirstConst(keyval), &idx, &info); + nextWhileLess(&info, &idx); + + // while we potentially have a match + while (info == mInfo[idx]) { + if (WKeyEqual::operator()(getFirstConst(keyval), mKeyVals[idx].getFirst())) { + // key already exists, do NOT insert. + // see http://en.cppreference.com/w/cpp/container/unordered_map/insert + return std::make_pair(iterator(mKeyVals + idx, mInfo + idx), + false); + } + next(&info, &idx); + } + + // unlikely that this evaluates to true + if (ROBIN_HOOD_UNLIKELY(mNumElements >= mMaxNumElementsAllowed)) { + increase_size(); + continue; + } + + // key not found, so we are now exactly where we want to insert it. + auto const insertion_idx = idx; + auto const insertion_info = info; + if (ROBIN_HOOD_UNLIKELY(insertion_info + mInfoInc > 0xFF)) { + mMaxNumElementsAllowed = 0; + } + + // find an empty spot + while (0 != mInfo[idx]) { + next(&info, &idx); + } + + auto& l = mKeyVals[insertion_idx]; + if (idx == insertion_idx) { + ::new (static_cast(&l)) Node(*this, std::forward(keyval)); + } + else { + shiftUp(idx, insertion_idx); + l = Node(*this, std::forward(keyval)); + } + + // put at empty spot + mInfo[insertion_idx] = static_cast(insertion_info); + + ++mNumElements; + return std::make_pair(iterator(mKeyVals + insertion_idx, mInfo + insertion_idx), true); + } + } + + bool try_increase_info() { + ROBIN_HOOD_LOG("mInfoInc=" << mInfoInc << ", numElements=" << mNumElements + << ", maxNumElementsAllowed=" + << calcMaxNumElementsAllowed(mMask + 1)) + if (mInfoInc <= 2) { + // need to be > 2 so that shift works (otherwise undefined behavior!) + return false; + } + // we got space left, try to make info smaller + mInfoInc = static_cast(mInfoInc >> 1U); + + // remove one bit of the hash, leaving more space for the distance info. + // This is extremely fast because we can operate on 8 bytes at once. + ++mInfoHashShift; + auto const numElementsWithBuffer = calcNumElementsWithBuffer(mMask + 1); + + for (size_t i = 0; i < numElementsWithBuffer; i += 8) { + auto val = unaligned_load(mInfo + i); + val = (val >> 1U) & UINT64_C(0x7f7f7f7f7f7f7f7f); + std::memcpy(mInfo + i, &val, sizeof(val)); + } + // update sentinel, which might have been cleared out! + mInfo[numElementsWithBuffer] = 1; + + mMaxNumElementsAllowed = calcMaxNumElementsAllowed(mMask + 1); + return true; + } + + void increase_size() { + // nothing allocated yet? just allocate InitialNumElements + if (0 == mMask) { + init_data(InitialNumElements); + return; + } + + auto const maxNumElementsAllowed = calcMaxNumElementsAllowed(mMask + 1); + if (mNumElements < maxNumElementsAllowed && try_increase_info()) { + return; + } + + ROBIN_HOOD_LOG("mNumElements=" << mNumElements << ", maxNumElementsAllowed=" + << maxNumElementsAllowed << ", load=" + << (static_cast(mNumElements) * 100.0 / + (static_cast(mMask) + 1))) + // it seems we have a really bad hash function! don't try to resize again + if (mNumElements * 2 < calcMaxNumElementsAllowed(mMask + 1)) { + throwOverflowError(); + } + + rehashPowerOfTwo((mMask + 1) * 2, false); + } + + void destroy() { + if (0 == mMask) { + // don't deallocate! + return; + } + + Destroyer::value>{} + .nodesDoNotDeallocate(*this); + + // This protection against not deleting mMask shouldn't be needed as it's sufficiently + // protected with the 0==mMask check, but I have this anyways because g++ 7 otherwise + // reports a compile error: attempt to free a non-heap object 'fm' + // [-Werror=free-nonheap-object] + if (mKeyVals != reinterpret_cast_no_cast_align_warning(&mMask)) { + ROBIN_HOOD_LOG("std::free") + std::free(mKeyVals); + } + } + + void init() noexcept { + mKeyVals = reinterpret_cast_no_cast_align_warning(&mMask); + mInfo = reinterpret_cast(&mMask); + mNumElements = 0; + mMask = 0; + mMaxNumElementsAllowed = 0; + mInfoInc = InitialInfoInc; + mInfoHashShift = InitialInfoHashShift; + } + + // members are sorted so no padding occurs + Node* mKeyVals = reinterpret_cast_no_cast_align_warning(&mMask); // 8 byte 8 + uint8_t* mInfo = reinterpret_cast(&mMask); // 8 byte 16 + size_t mNumElements = 0; // 8 byte 24 + size_t mMask = 0; // 8 byte 32 + size_t mMaxNumElementsAllowed = 0; // 8 byte 40 + InfoType mInfoInc = InitialInfoInc; // 4 byte 44 + InfoType mInfoHashShift = InitialInfoHashShift; // 4 byte 48 + // 16 byte 56 if NodeAllocator + }; + + } // namespace detail + + // map + + template , + typename KeyEqual = std::equal_to, size_t MaxLoadFactor100 = 80> + using unordered_flat_map = detail::Table; + + template , + typename KeyEqual = std::equal_to, size_t MaxLoadFactor100 = 80> + using unordered_node_map = detail::Table; + + template , + typename KeyEqual = std::equal_to, size_t MaxLoadFactor100 = 80> + using unordered_map = + detail::Table) <= sizeof(size_t) * 6 && + std::is_nothrow_move_constructible>::value && + std::is_nothrow_move_assignable>::value, + MaxLoadFactor100, Key, T, Hash, KeyEqual>; + + // set + + template , typename KeyEqual = std::equal_to, + size_t MaxLoadFactor100 = 80> + using unordered_flat_set = detail::Table; + + template , typename KeyEqual = std::equal_to, + size_t MaxLoadFactor100 = 80> + using unordered_node_set = detail::Table; + + template , typename KeyEqual = std::equal_to, + size_t MaxLoadFactor100 = 80> + using unordered_set = detail::Table::value && + std::is_nothrow_move_assignable::value, + MaxLoadFactor100, Key, void, Hash, KeyEqual>; + +} // namespace robin_hood + +#endif diff --git a/src/util/crypto/aes128.cpp b/src/util/crypto/aes128.cpp new file mode 100644 index 00000000..17f049f3 --- /dev/null +++ b/src/util/crypto/aes128.cpp @@ -0,0 +1,859 @@ +/* + Original implementation based on Tiny-AES-c (2015) + https://github.com/kokke/tiny-AES-c + + Modified by Exzap +*/ + + +/*****************************************************************************/ +/* Includes: */ +/*****************************************************************************/ +#include "aes128.h" + +/*****************************************************************************/ +/* Defines: */ +/*****************************************************************************/ +// The number of columns comprising a state in AES. This is a constant in AES. Value=4 +#define Nb 4 +// The number of 32 bit words in a key. +#define Nk 4 +// Key length in bytes [128 bit] +#define KEYLEN 16 +// The number of rounds in AES Cipher. +#define Nr 10 + +bool useAESNI = false; + +typedef uint8 state_t[4][4]; + +typedef struct +{ + state_t* state; + uint8 RoundKey[176]; +}aes128Ctx_t; + +#define stateVal(__x, __y) ((*aesCtx->state)[__x][__y]) +#define stateValU32(__x) (*(uint32*)((*aesCtx->state)[__x])) + +// The lookup-tables are marked const so they can be placed in read-only storage instead of RAM +// The numbers below can be computed dynamically trading ROM for RAM - +// This can be useful in (embedded) bootloader applications, where ROM is often limited. +static const uint8 sbox[256] = { + //0 1 2 3 4 5 6 7 8 9 A B C D E F + 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, + 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, + 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, + 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, + 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, + 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, + 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, + 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, + 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, + 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, + 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, + 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, + 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, + 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, + 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, + 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 }; + +static const uint8 rsbox[256] = +{ 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, + 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, + 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, + 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, + 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, + 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, + 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, + 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, + 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, + 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, + 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, + 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, + 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, + 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, + 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d }; + + +// The round constant word array, Rcon[i], contains the values given by +// x to th e power (i-1) being powers of x (x is denoted as {02}) in the field GF(2^8) +// Note that i starts at 1, not 0). +static const uint8 Rcon[255] = { + 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, + 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, + 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, + 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, + 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, + 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, + 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, + 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, + 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, + 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, + 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, + 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, + 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, + 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, + 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, + 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb }; + + +/*****************************************************************************/ +/* Private functions: */ +/*****************************************************************************/ +uint8 getSBoxValue(uint8 num) +{ + return sbox[num]; +} + +uint8 getSBoxInvert(uint8 num) +{ + return rsbox[num]; +} + +// This function produces Nb(Nr+1) round keys. The round keys are used in each round to decrypt the states. +void KeyExpansion(aes128Ctx_t* aesCtx, const uint8* key) +{ + uint32 i, j, k; + uint8 tempa[4]; // Used for the column/row operations + + // The first round key is the key itself. + for (i = 0; i < Nk; ++i) + { + aesCtx->RoundKey[(i * 4) + 0] = key[(i * 4) + 0]; + aesCtx->RoundKey[(i * 4) + 1] = key[(i * 4) + 1]; + aesCtx->RoundKey[(i * 4) + 2] = key[(i * 4) + 2]; + aesCtx->RoundKey[(i * 4) + 3] = key[(i * 4) + 3]; + } + + // All other round keys are found from the previous round keys. + for (; (i < (Nb * (Nr + 1))); ++i) + { + for (j = 0; j < 4; ++j) + { + tempa[j] = aesCtx->RoundKey[(i - 1) * 4 + j]; + } + if (i % Nk == 0) + { + // This function rotates the 4 bytes in a word to the left once. + // [a0,a1,a2,a3] becomes [a1,a2,a3,a0] + + // Function RotWord() + { + k = tempa[0]; + tempa[0] = tempa[1]; + tempa[1] = tempa[2]; + tempa[2] = tempa[3]; + tempa[3] = k; + } + + // SubWord() is a function that takes a four-byte input word and + // applies the S-box to each of the four bytes to produce an output word. + + // Function Subword() + { + tempa[0] = getSBoxValue(tempa[0]); + tempa[1] = getSBoxValue(tempa[1]); + tempa[2] = getSBoxValue(tempa[2]); + tempa[3] = getSBoxValue(tempa[3]); + } + + tempa[0] = tempa[0] ^ Rcon[i / Nk]; + } + else if (Nk > 6 && i % Nk == 4) + { + // Function Subword() + { + tempa[0] = getSBoxValue(tempa[0]); + tempa[1] = getSBoxValue(tempa[1]); + tempa[2] = getSBoxValue(tempa[2]); + tempa[3] = getSBoxValue(tempa[3]); + } + } + aesCtx->RoundKey[i * 4 + 0] = aesCtx->RoundKey[(i - Nk) * 4 + 0] ^ tempa[0]; + aesCtx->RoundKey[i * 4 + 1] = aesCtx->RoundKey[(i - Nk) * 4 + 1] ^ tempa[1]; + aesCtx->RoundKey[i * 4 + 2] = aesCtx->RoundKey[(i - Nk) * 4 + 2] ^ tempa[2]; + aesCtx->RoundKey[i * 4 + 3] = aesCtx->RoundKey[(i - Nk) * 4 + 3] ^ tempa[3]; + } +} + +// This function adds the round key to state. +// The round key is added to the state by an XOR function. +void AddRoundKey(aes128Ctx_t* aesCtx, uint8 round) +{ + // note: replacing this with two 64bit xor operations decreased performance in benchmarks, probably because the state bytes need to be stored back in memory + + stateVal(0, 0) ^= aesCtx->RoundKey[round * Nb * 4 + 0 * Nb + 0]; + stateVal(0, 1) ^= aesCtx->RoundKey[round * Nb * 4 + 0 * Nb + 1]; + stateVal(0, 2) ^= aesCtx->RoundKey[round * Nb * 4 + 0 * Nb + 2]; + stateVal(0, 3) ^= aesCtx->RoundKey[round * Nb * 4 + 0 * Nb + 3]; + + stateVal(1, 0) ^= aesCtx->RoundKey[round * Nb * 4 + 1 * Nb + 0]; + stateVal(1, 1) ^= aesCtx->RoundKey[round * Nb * 4 + 1 * Nb + 1]; + stateVal(1, 2) ^= aesCtx->RoundKey[round * Nb * 4 + 1 * Nb + 2]; + stateVal(1, 3) ^= aesCtx->RoundKey[round * Nb * 4 + 1 * Nb + 3]; + + stateVal(2, 0) ^= aesCtx->RoundKey[round * Nb * 4 + 2 * Nb + 0]; + stateVal(2, 1) ^= aesCtx->RoundKey[round * Nb * 4 + 2 * Nb + 1]; + stateVal(2, 2) ^= aesCtx->RoundKey[round * Nb * 4 + 2 * Nb + 2]; + stateVal(2, 3) ^= aesCtx->RoundKey[round * Nb * 4 + 2 * Nb + 3]; + + stateVal(3, 0) ^= aesCtx->RoundKey[round * Nb * 4 + 3 * Nb + 0]; + stateVal(3, 1) ^= aesCtx->RoundKey[round * Nb * 4 + 3 * Nb + 1]; + stateVal(3, 2) ^= aesCtx->RoundKey[round * Nb * 4 + 3 * Nb + 2]; + stateVal(3, 3) ^= aesCtx->RoundKey[round * Nb * 4 + 3 * Nb + 3]; +} + +// The SubBytes Function Substitutes the values in the +// state matrix with values in an S-box. +void SubBytes(aes128Ctx_t* aesCtx) +{ + uint8 i, j; + for (i = 0; i < 4; ++i) + { + for (j = 0; j < 4; ++j) + { + stateVal(j, i) = getSBoxValue(stateVal(j, i)); + } + } +} + +// The ShiftRows() function shifts the rows in the state to the left. +// Each row is shifted with different offset. +// Offset = Row number. So the first row is not shifted. +void ShiftRows(aes128Ctx_t* aesCtx) +{ + uint8 temp; + + // Rotate first row 1 columns to left + temp = stateVal(0, 1); + stateVal(0, 1) = stateVal(1, 1); + stateVal(1, 1) = stateVal(2, 1); + stateVal(2, 1) = stateVal(3, 1); + stateVal(3, 1) = temp; + + // Rotate second row 2 columns to left + temp = stateVal(0, 2); + stateVal(0, 2) = stateVal(2, 2); + stateVal(2, 2) = temp; + + temp = stateVal(1, 2); + stateVal(1, 2) = stateVal(3, 2); + stateVal(3, 2) = temp; + + // Rotate third row 3 columns to left + temp = stateVal(0, 3); + stateVal(0, 3) = stateVal(3, 3); + stateVal(3, 3) = stateVal(2, 3); + stateVal(2, 3) = stateVal(1, 3); + stateVal(1, 3) = temp; +} + +uint8 aes_xtime(uint8 x) +{ + return ((x << 1) ^ (((x >> 7) & 1) * 0x1b)); +} + +// MixColumns function mixes the columns of the state matrix +void MixColumns(aes128Ctx_t* aesCtx) +{ + uint8 i; + uint8 Tmp, Tm, t; + for (i = 0; i < 4; ++i) + { + t = stateVal(i, 0); + Tmp = stateVal(i, 0) ^ stateVal(i, 1) ^ stateVal(i, 2) ^ stateVal(i, 3); + Tm = stateVal(i, 0) ^ stateVal(i, 1); Tm = aes_xtime(Tm); stateVal(i, 0) ^= Tm ^ Tmp; + Tm = stateVal(i, 1) ^ stateVal(i, 2); Tm = aes_xtime(Tm); stateVal(i, 1) ^= Tm ^ Tmp; + Tm = stateVal(i, 2) ^ stateVal(i, 3); Tm = aes_xtime(Tm); stateVal(i, 2) ^= Tm ^ Tmp; + Tm = stateVal(i, 3) ^ t; Tm = aes_xtime(Tm); stateVal(i, 3) ^= Tm ^ Tmp; + } +} + +// Multiply is used to multiply numbers in the field GF(2^8) +#define Multiply(x, y) \ + ( ((y & 1) * x) ^ \ + ((y>>1 & 1) * aes_xtime(x)) ^ \ + ((y>>2 & 1) * aes_xtime(aes_xtime(x))) ^ \ + ((y>>3 & 1) * aes_xtime(aes_xtime(aes_xtime(x)))) ^ \ + ((y>>4 & 1) * aes_xtime(aes_xtime(aes_xtime(aes_xtime(x)))))) + +uint32 lookupTable_multiply[256]; + +//// MixColumns function mixes the columns of the state matrix. +//// The method used to multiply may be difficult to understand for the inexperienced. +//// Please use the references to gain more information. +//void InvMixColumns(aes128Ctx_t* aesCtx) +//{ +// int i; +// uint8 a, b, c, d; +// for (i = 0; i < 4; ++i) +// { +// a = stateVal(i, 0); +// b = stateVal(i, 1); +// c = stateVal(i, 2); +// d = stateVal(i, 3); +// +// uint32 _a = lookupTable_multiply[a]; +// uint32 _b = lookupTable_multiply[b]; +// uint32 _c = lookupTable_multiply[c]; +// uint32 _d = lookupTable_multiply[d]; +// +// +// //stateVal(i, 0) = entryA->vE ^ entryB->vB ^ entryC->vD ^ entryD->v9; +// //stateVal(i, 1) = entryA->v9 ^ entryB->vE ^ entryC->vB ^ entryD->vD; +// //stateVal(i, 2) = entryA->vD ^ entryB->v9 ^ entryC->vE ^ entryD->vB; +// //stateVal(i, 3) = entryA->vB ^ entryB->vD ^ entryC->v9 ^ entryD->vE; +// +// //stateVal(i, 0) = Multiply(a, 0x0e) ^ Multiply(b, 0x0b) ^ Multiply(c, 0x0d) ^ Multiply(d, 0x09); +// //stateVal(i, 1) = Multiply(a, 0x09) ^ Multiply(b, 0x0e) ^ Multiply(c, 0x0b) ^ Multiply(d, 0x0d); +// //stateVal(i, 2) = Multiply(a, 0x0d) ^ Multiply(b, 0x09) ^ Multiply(c, 0x0e) ^ Multiply(d, 0x0b); +// //stateVal(i, 3) = Multiply(a, 0x0b) ^ Multiply(b, 0x0d) ^ Multiply(c, 0x09) ^ Multiply(d, 0x0e); +// +// //stateVal(i, 0) = Multiply(b, 0x0b) ^ Multiply(c, 0x0d) ^ Multiply(d, 0x09); +// //stateVal(i, 1) = Multiply(b, 0x0e) ^ Multiply(c, 0x0b) ^ Multiply(d, 0x0d); +// //stateVal(i, 2) = Multiply(b, 0x09) ^ Multiply(c, 0x0e) ^ Multiply(d, 0x0b); +// //stateVal(i, 3) = Multiply(b, 0x0d) ^ Multiply(c, 0x09) ^ Multiply(d, 0x0e); +// +// stateValU32(i) = _a ^ _rotl(_b, 8) ^ _rotl(_c, 16) ^ _rotl(_d, 24); +// +// } +//} + +// MixColumns function mixes the columns of the state matrix. +// The method used to multiply may be difficult to understand for the inexperienced. +// Please use the references to gain more information. +void InvMixColumns(aes128Ctx_t* aesCtx) +{ + uint8 a, b, c, d; + // i0 + a = stateVal(0, 0); + b = stateVal(0, 1); + c = stateVal(0, 2); + d = stateVal(0, 3); + stateValU32(0) = lookupTable_multiply[a] ^ _rotl(lookupTable_multiply[b], 8) ^ _rotl(lookupTable_multiply[c], 16) ^ _rotl(lookupTable_multiply[d], 24); + // i1 + a = stateVal(1, 0); + b = stateVal(1, 1); + c = stateVal(1, 2); + d = stateVal(1, 3); + stateValU32(1) = lookupTable_multiply[a] ^ _rotl(lookupTable_multiply[b], 8) ^ _rotl(lookupTable_multiply[c], 16) ^ _rotl(lookupTable_multiply[d], 24); + // i2 + a = stateVal(2, 0); + b = stateVal(2, 1); + c = stateVal(2, 2); + d = stateVal(2, 3); + stateValU32(2) = lookupTable_multiply[a] ^ _rotl(lookupTable_multiply[b], 8) ^ _rotl(lookupTable_multiply[c], 16) ^ _rotl(lookupTable_multiply[d], 24); + // i3 + a = stateVal(3, 0); + b = stateVal(3, 1); + c = stateVal(3, 2); + d = stateVal(3, 3); + stateValU32(3) = lookupTable_multiply[a] ^ _rotl(lookupTable_multiply[b], 8) ^ _rotl(lookupTable_multiply[c], 16) ^ _rotl(lookupTable_multiply[d], 24); +} + +// The SubBytes Function Substitutes the values in the +// state matrix with values in an S-box. +void InvSubBytes(aes128Ctx_t* aesCtx) +{ + stateVal(0, 0) = rsbox[stateVal(0, 0)]; + stateVal(1, 0) = rsbox[stateVal(1, 0)]; + stateVal(2, 0) = rsbox[stateVal(2, 0)]; + stateVal(3, 0) = rsbox[stateVal(3, 0)]; + + stateVal(0, 1) = rsbox[stateVal(0, 1)]; + stateVal(1, 1) = rsbox[stateVal(1, 1)]; + stateVal(2, 1) = rsbox[stateVal(2, 1)]; + stateVal(3, 1) = rsbox[stateVal(3, 1)]; + + stateVal(0, 2) = rsbox[stateVal(0, 2)]; + stateVal(1, 2) = rsbox[stateVal(1, 2)]; + stateVal(2, 2) = rsbox[stateVal(2, 2)]; + stateVal(3, 2) = rsbox[stateVal(3, 2)]; + + stateVal(0, 3) = rsbox[stateVal(0, 3)]; + stateVal(1, 3) = rsbox[stateVal(1, 3)]; + stateVal(2, 3) = rsbox[stateVal(2, 3)]; + stateVal(3, 3) = rsbox[stateVal(3, 3)]; +} + +void InvShiftRows(aes128Ctx_t* aesCtx) +{ + uint8 temp; + + // Rotate first row 1 columns to right + temp = stateVal(3, 1); + stateVal(3, 1) = stateVal(2, 1); + stateVal(2, 1) = stateVal(1, 1); + stateVal(1, 1) = stateVal(0, 1); + stateVal(0, 1) = temp; + + // Rotate second row 2 columns to right + temp = stateVal(0, 2); + stateVal(0, 2) = stateVal(2, 2); + stateVal(2, 2) = temp; + + temp = stateVal(1, 2); + stateVal(1, 2) = stateVal(3, 2); + stateVal(3, 2) = temp; + + // Rotate third row 3 columns to right + temp = stateVal(0, 3); + stateVal(0, 3) = stateVal(1, 3); + stateVal(1, 3) = stateVal(2, 3); + stateVal(2, 3) = stateVal(3, 3); + stateVal(3, 3) = temp; +} + + +// Cipher is the main function that encrypts the PlainText. +void Cipher(aes128Ctx_t* aesCtx) +{ + uint8 round = 0; + + // Add the First round key to the state before starting the rounds. + AddRoundKey(aesCtx, 0); + + // There will be Nr rounds. + // The first Nr-1 rounds are identical. + // These Nr-1 rounds are executed in the loop below. + for (round = 1; round < Nr; ++round) + { + SubBytes(aesCtx); + ShiftRows(aesCtx); + MixColumns(aesCtx); + AddRoundKey(aesCtx, round); + } + + // The last round is given below. + // The MixColumns function is not here in the last round. + SubBytes(aesCtx); + ShiftRows(aesCtx); + AddRoundKey(aesCtx, Nr); +} + +void InvCipher(aes128Ctx_t* aesCtx) +{ + uint8 round = 0; + + // Add the First round key to the state before starting the rounds. + AddRoundKey(aesCtx, Nr); + + // There will be Nr rounds. + // The first Nr-1 rounds are identical. + // These Nr-1 rounds are executed in the loop below. + for (round = Nr - 1; round > 0; round--) + { + InvShiftRows(aesCtx); + InvSubBytes(aesCtx); + AddRoundKey(aesCtx, round); + InvMixColumns(aesCtx); + } + + // The last round is given below. + // The MixColumns function is not here in the last round. + InvShiftRows(aesCtx); + InvSubBytes(aesCtx); + AddRoundKey(aesCtx, 0); +} + +static void BlockCopy(uint8* output, uint8* input) +{ + uint8 i; + for (i = 0; i < KEYLEN; ++i) + { + output[i] = input[i]; + } +} + + + +/*****************************************************************************/ +/* Public functions: */ +/*****************************************************************************/ + +void __soft__AES128_ECB_encrypt(uint8* input, const uint8* key, uint8* output) +{ + aes128Ctx_t aesCtx; + // Copy input to output, and work in-memory on output + BlockCopy(output, input); + aesCtx.state = (state_t*)output; + + KeyExpansion(&aesCtx, key); + + // The next function call encrypts the PlainText with the Key using AES algorithm. + Cipher(&aesCtx); +} + +void AES128_ECB_decrypt(uint8* input, const uint8* key, uint8 *output) +{ + aes128Ctx_t aesCtx; + // Copy input to output, and work in-memory on output + BlockCopy(output, input); + aesCtx.state = (state_t*)output; + + // The KeyExpansion routine must be called before encryption. + KeyExpansion(&aesCtx, key); + + InvCipher(&aesCtx); +} + +void XorWithIv(uint8* buf, const uint8* iv) +{ + uint8 i; + for (i = 0; i < KEYLEN; ++i) + { + buf[i] ^= iv[i]; + } +} + +void AES128_CBC_encrypt(uint8* output, uint8* input, uint32 length, const uint8* key, const uint8* iv) +{ + aes128Ctx_t aesCtx; + intptr_t i; + uint8 remainders = length % KEYLEN; /* Remaining bytes in the last non-full block */ + + BlockCopy(output, input); + aesCtx.state = (state_t*)output; + + KeyExpansion(&aesCtx, key); + + const uint8* currentIv = iv; + + for (i = 0; i < length; i += KEYLEN) + { + XorWithIv(input, currentIv); + BlockCopy(output, input); + aesCtx.state = (state_t*)output; + Cipher(&aesCtx); + currentIv = output; + input += KEYLEN; + output += KEYLEN; + } + cemu_assert_debug(remainders == 0); +} + +void __soft__AES128_CBC_decrypt(uint8* output, uint8* input, uint32 length, const uint8* key, const uint8* iv) +{ + aes128Ctx_t aesCtx; + intptr_t i; + uint8 remainders = length % KEYLEN; + + KeyExpansion(&aesCtx, key); + + uint8 currentIv[KEYLEN]; + uint8 nextIv[KEYLEN]; + if (iv) + BlockCopy(currentIv, (uint8*)iv); + else + memset(currentIv, 0, sizeof(currentIv)); + + for (i = 0; i < length; i += KEYLEN) + { + aesCtx.state = (state_t*)output; + BlockCopy(output, input); + BlockCopy(nextIv, input); + InvCipher(&aesCtx); + XorWithIv(output, currentIv); + BlockCopy(currentIv, nextIv); + output += KEYLEN; + input += KEYLEN; + } + + cemu_assert_debug(remainders == 0); +} + +void AES128_CBC_decrypt_buffer_depr(uint8* output, uint8* input, uint32 length, const uint8* key, const uint8* iv) +{ + aes128Ctx_t aesCtx; + intptr_t i; + uint8 remainders = length % KEYLEN; /* Remaining bytes in the last non-full block */ + + BlockCopy(output, input); + + KeyExpansion(&aesCtx, key); + + const uint8* currentIv = iv; + + for (i = 0; i < length; i += KEYLEN) + { + BlockCopy(output, input); + aesCtx.state = (state_t*)output; + InvCipher(&aesCtx); + XorWithIv(output, currentIv); + currentIv = input; + input += KEYLEN; + output += KEYLEN; + } + cemu_assert_debug(remainders == 0); +} + +void AES128_CBC_decrypt_updateIV(uint8* output, uint8* input, uint32 length, const uint8* key, uint8* iv) +{ + length &= ~0xF; + uint8 newIv[16]; + if (length == 0) + return; + cemu_assert_debug((length&0xF) == 0); + memcpy(newIv, input + (length - 16), KEYLEN); + AES128_CBC_decrypt(output, input, length, key, iv); + memcpy(iv, newIv, KEYLEN); +} + +inline __m128i AESNI128_ASSIST( + __m128i temp1, + __m128i temp2) +{ + __m128i temp3; + temp2 = _mm_shuffle_epi32(temp2, 0xff); + temp3 = _mm_slli_si128(temp1, 0x4); + temp1 = _mm_xor_si128(temp1, temp3); + temp3 = + _mm_slli_si128(temp3, 0x4); + temp1 = + _mm_xor_si128(temp1, temp3); + temp3 = + _mm_slli_si128(temp3, 0x4); + temp1 = + _mm_xor_si128(temp1, temp3); + temp1 = _mm_xor_si128(temp1, temp2); + return temp1; +} + +void AESNI128_KeyExpansionEncrypt(const unsigned char *userkey, unsigned char *key) +{ + __m128i temp1, temp2; + __m128i *Key_Schedule = (__m128i*)key; + temp1 = _mm_loadu_si128((__m128i*)userkey); + Key_Schedule[0] = temp1; + temp2 = _mm_aeskeygenassist_si128(temp1, 0x1); + temp1 = AESNI128_ASSIST(temp1, temp2); + Key_Schedule[1] = temp1; + temp2 = _mm_aeskeygenassist_si128(temp1, 0x2); + temp1 = AESNI128_ASSIST(temp1, temp2); + Key_Schedule[2] = temp1; + temp2 = _mm_aeskeygenassist_si128(temp1, 0x4); + temp1 = AESNI128_ASSIST(temp1, temp2); + Key_Schedule[3] = temp1; + temp2 = _mm_aeskeygenassist_si128(temp1, 0x8); + temp1 = AESNI128_ASSIST(temp1, temp2); + Key_Schedule[4] = temp1; + temp2 = _mm_aeskeygenassist_si128(temp1, 0x10); + temp1 = AESNI128_ASSIST(temp1, temp2); + Key_Schedule[5] = temp1; + temp2 = _mm_aeskeygenassist_si128(temp1, 0x20); + temp1 = AESNI128_ASSIST(temp1, temp2); + Key_Schedule[6] = temp1; + temp2 = _mm_aeskeygenassist_si128(temp1, 0x40); + temp1 = AESNI128_ASSIST(temp1, temp2); + Key_Schedule[7] = temp1; + temp2 = _mm_aeskeygenassist_si128(temp1, 0x80); + temp1 = AESNI128_ASSIST(temp1, temp2); + Key_Schedule[8] = temp1; + temp2 = _mm_aeskeygenassist_si128(temp1, 0x1b); + temp1 = AESNI128_ASSIST(temp1, temp2); + Key_Schedule[9] = temp1; + temp2 = _mm_aeskeygenassist_si128(temp1, 0x36); + temp1 = AESNI128_ASSIST(temp1, temp2); + Key_Schedule[10] = temp1; +} + +void AESNI128_KeyExpansionDecrypt(const unsigned char *userkey, unsigned char *key) +{ + __m128i temp1, temp2; + __m128i *Key_Schedule = (__m128i*)key; + temp1 = _mm_loadu_si128((__m128i*)userkey); + Key_Schedule[0] = temp1; + temp2 = _mm_aeskeygenassist_si128(temp1, 0x1); + temp1 = AESNI128_ASSIST(temp1, temp2); + Key_Schedule[1] = temp1; + temp2 = _mm_aeskeygenassist_si128(temp1, 0x2); + temp1 = AESNI128_ASSIST(temp1, temp2); + Key_Schedule[2] = temp1; + temp2 = _mm_aeskeygenassist_si128(temp1, 0x4); + temp1 = AESNI128_ASSIST(temp1, temp2); + Key_Schedule[3] = temp1; + temp2 = _mm_aeskeygenassist_si128(temp1, 0x8); + temp1 = AESNI128_ASSIST(temp1, temp2); + Key_Schedule[4] = temp1; + temp2 = _mm_aeskeygenassist_si128(temp1, 0x10); + temp1 = AESNI128_ASSIST(temp1, temp2); + Key_Schedule[5] = temp1; + temp2 = _mm_aeskeygenassist_si128(temp1, 0x20); + temp1 = AESNI128_ASSIST(temp1, temp2); + Key_Schedule[6] = temp1; + temp2 = _mm_aeskeygenassist_si128(temp1, 0x40); + temp1 = AESNI128_ASSIST(temp1, temp2); + Key_Schedule[7] = temp1; + temp2 = _mm_aeskeygenassist_si128(temp1, 0x80); + temp1 = AESNI128_ASSIST(temp1, temp2); + Key_Schedule[8] = temp1; + temp2 = _mm_aeskeygenassist_si128(temp1, 0x1b); + temp1 = AESNI128_ASSIST(temp1, temp2); + Key_Schedule[9] = temp1; + temp2 = _mm_aeskeygenassist_si128(temp1, 0x36); + temp1 = AESNI128_ASSIST(temp1, temp2); + Key_Schedule[10] = temp1; + // inverse + for (sint32 i = 1; i < 10; i++) + { + Key_Schedule[i] = _mm_aesimc_si128(Key_Schedule[i]); + } +} + +void AESNI128_CBC_encrypt(const unsigned char *in, + unsigned char *out, + unsigned char ivec[16], + unsigned long length, + unsigned char *key, + int number_of_rounds) +{ + __m128i feedback, data; + int j; + if (length % 16) + length = length / 16 + 1; + else length /= 16; + feedback = _mm_loadu_si128((__m128i*)ivec); + for (unsigned long i = 0; i < length; i++) + { + data = + _mm_loadu_si128(&((__m128i*)in)[i]); + feedback = _mm_xor_si128(data, feedback); + feedback = _mm_xor_si128(feedback, ((__m128i*)key)[0]); + for (j = 1; j < number_of_rounds; j++) + feedback = + _mm_aesenc_si128(feedback, ((__m128i*)key)[j]); + feedback = + _mm_aesenclast_si128(feedback, ((__m128i*)key)[j]); + _mm_storeu_si128(&((__m128i*)out)[i], feedback); + } +} + +void AESNI128_CBC_decryptWithExpandedKey(const unsigned char *in, + unsigned char *out, + const unsigned char ivec[16], + unsigned long length, + unsigned char *key) +{ + __m128i data, feedback, lastin; + int j; + if (length % 16) + length = length / 16 + 1; + else length /= 16; + feedback = _mm_loadu_si128((__m128i*)ivec); + for (unsigned long i = 0; i < length; i++) + { + lastin = _mm_loadu_si128(&((__m128i*)in)[i]); + data = _mm_xor_si128(lastin, ((__m128i*)key)[10]); + for (j = 9; j > 0; j--) + { + data = _mm_aesdec_si128(data, ((__m128i*)key)[j]); + } + data = _mm_aesdeclast_si128(data, ((__m128i*)key)[0]); + data = _mm_xor_si128(data, feedback); + _mm_storeu_si128(&((__m128i*)out)[i], data); + feedback = lastin; + } +} + +void __aesni__AES128_CBC_decrypt(uint8* output, uint8* input, uint32 length, const uint8* key, const uint8* iv) +{ + __declspec(align(16)) uint8 expandedKey[11 * 16]; + AESNI128_KeyExpansionDecrypt(key, expandedKey); + if (iv) + { + AESNI128_CBC_decryptWithExpandedKey(input, output, iv, length, expandedKey); + } + else + { + uint8 zeroIv[16] = { 0 }; + AESNI128_CBC_decryptWithExpandedKey(input, output, zeroIv, length, expandedKey); + } +} + +void __aesni__AES128_ECB_encrypt(uint8* input, const uint8* key, uint8* output) +{ + __declspec(align(16)) uint8 expandedKey[11 * 16]; + AESNI128_KeyExpansionEncrypt(key, expandedKey); + // encrypt single ECB block + __m128i feedback; + feedback = _mm_loadu_si128(&((__m128i*)input)[0]); + feedback = _mm_xor_si128(feedback, ((__m128i*)expandedKey)[0]); + feedback = _mm_aesenc_si128(feedback, ((__m128i*)expandedKey)[1]); + feedback = _mm_aesenc_si128(feedback, ((__m128i*)expandedKey)[2]); + feedback = _mm_aesenc_si128(feedback, ((__m128i*)expandedKey)[3]); + feedback = _mm_aesenc_si128(feedback, ((__m128i*)expandedKey)[4]); + feedback = _mm_aesenc_si128(feedback, ((__m128i*)expandedKey)[5]); + feedback = _mm_aesenc_si128(feedback, ((__m128i*)expandedKey)[6]); + feedback = _mm_aesenc_si128(feedback, ((__m128i*)expandedKey)[7]); + feedback = _mm_aesenc_si128(feedback, ((__m128i*)expandedKey)[8]); + feedback = _mm_aesenc_si128(feedback, ((__m128i*)expandedKey)[9]); + feedback = _mm_aesenclast_si128(feedback, ((__m128i*)expandedKey)[10]); + _mm_storeu_si128(&((__m128i*)output)[0], feedback); +} + +void(*AES128_ECB_encrypt)(uint8* input, const uint8* key, uint8* output); +void (*AES128_CBC_decrypt)(uint8* output, uint8* input, uint32 length, const uint8* key, const uint8* iv) = nullptr; + +// AES128-CTR encrypt/decrypt +void AES128CTR_transform(uint8* data, sint32 length, uint8* key, uint8* nonceIv) +{ + for (sint32 i = 0; i < length; i += 16) + { + uint8* d = data + i; + uint8 tempArray[16]; + AES128_ECB_encrypt(nonceIv, key, tempArray); + for (sint32 f = 0; f < 16; f++) + { + d[f] ^= tempArray[f]; + } + // increase nonce + *(uint32*)(nonceIv + 0xC) = _swapEndianU32(_swapEndianU32(*(uint32*)(nonceIv + 0xC)) + 1); + if (*(uint32*)(nonceIv + 0xC) == 0) + { + *(uint32*)(nonceIv + 0x8) = _swapEndianU32(_swapEndianU32(*(uint32*)(nonceIv + 0x8)) + 1); + if (*(uint32*)(nonceIv + 0x8) == 0) + { + *(uint32*)(nonceIv + 0x4) = _swapEndianU32(_swapEndianU32(*(uint32*)(nonceIv + 0x4)) + 1); + if (*(uint32*)(nonceIv + 0x4) == 0) + { + *(uint32*)(nonceIv + 0) = _swapEndianU32(_swapEndianU32(*(uint32*)(nonceIv + 0)) + 1); + } + } + } + } +} + +void AES128_init() +{ + for (uint32 i = 0; i <= 0xFF; i++) + { + uint32 vE = Multiply((uint8)(i & 0xFF), 0x0E) & 0xFF; + uint32 v9 = Multiply((uint8)(i & 0xFF), 0x09) & 0xFF; + uint32 vD = Multiply((uint8)(i & 0xFF), 0x0D) & 0xFF; + uint32 vB = Multiply((uint8)(i & 0xFF), 0x0B) & 0xFF; + lookupTable_multiply[i] = (vE << 0) | (v9 << 8) | (vD << 16) | (vB << 24); + } + // check if AES-NI is available + int v[4]; + __cpuid(v, 1); + useAESNI = (v[2] & 0x2000000) != 0; + if (useAESNI) + { + // AES-NI implementation + AES128_CBC_decrypt = __aesni__AES128_CBC_decrypt; + AES128_ECB_encrypt = __aesni__AES128_ECB_encrypt; + } + else + { + // basic software implementation + AES128_CBC_decrypt = __soft__AES128_CBC_decrypt; + AES128_ECB_encrypt = __soft__AES128_ECB_encrypt; + } +} + +bool AES128_useAESNI() +{ + return useAESNI; +} \ No newline at end of file diff --git a/src/util/crypto/aes128.h b/src/util/crypto/aes128.h new file mode 100644 index 00000000..fbe56541 --- /dev/null +++ b/src/util/crypto/aes128.h @@ -0,0 +1,19 @@ +#ifndef _AES_H_ +#define _AES_H_ + +void AES128_init(); +bool AES128_useAESNI(); + +extern void(*AES128_ECB_encrypt)(uint8* input, const uint8* key, uint8* output); + +void AES128_ECB_decrypt(uint8* input, const uint8* key, uint8 *output); + +void AES128_CBC_encrypt(uint8* output, uint8* input, uint32 length, const uint8* key, const uint8* iv); + +extern void(*AES128_CBC_decrypt)(uint8* output, uint8* input, uint32 length, const uint8* key, const uint8* iv); + +void AES128_CBC_decrypt_updateIV(uint8* output, uint8* input, uint32 length, const uint8* key, uint8* iv); + +void AES128CTR_transform(uint8* data, sint32 length, uint8* key, uint8* nonceIv); + +#endif //_AES_H_ diff --git a/src/util/crypto/crc32.cpp b/src/util/crypto/crc32.cpp new file mode 100644 index 00000000..f6e88165 --- /dev/null +++ b/src/util/crypto/crc32.cpp @@ -0,0 +1,380 @@ +#include "crc32.h" + +#if defined(_MSC_VER) || defined(__MINGW32__) +#define __LITTLE_ENDIAN 1234 +#define __BIG_ENDIAN 4321 +#define __BYTE_ORDER __LITTLE_ENDIAN + +#include +#ifdef __MINGW32__ +#define PREFETCH(location) __builtin_prefetch(location) +#else +#define PREFETCH(location) _mm_prefetch(location, _MM_HINT_T0) +#endif +#else +// defines __BYTE_ORDER as __LITTLE_ENDIAN or __BIG_ENDIAN +#include + +#ifdef __GNUC__ +#define PREFETCH(location) __builtin_prefetch(location) +#else + // no prefetching +#define PREFETCH(location) ; +#endif +#endif + +unsigned int Crc32Lookup[8][256] = +{ + { + 0x00000000,0x77073096,0xEE0E612C,0x990951BA,0x076DC419,0x706AF48F,0xE963A535,0x9E6495A3, + 0x0EDB8832,0x79DCB8A4,0xE0D5E91E,0x97D2D988,0x09B64C2B,0x7EB17CBD,0xE7B82D07,0x90BF1D91, + 0x1DB71064,0x6AB020F2,0xF3B97148,0x84BE41DE,0x1ADAD47D,0x6DDDE4EB,0xF4D4B551,0x83D385C7, + 0x136C9856,0x646BA8C0,0xFD62F97A,0x8A65C9EC,0x14015C4F,0x63066CD9,0xFA0F3D63,0x8D080DF5, + 0x3B6E20C8,0x4C69105E,0xD56041E4,0xA2677172,0x3C03E4D1,0x4B04D447,0xD20D85FD,0xA50AB56B, + 0x35B5A8FA,0x42B2986C,0xDBBBC9D6,0xACBCF940,0x32D86CE3,0x45DF5C75,0xDCD60DCF,0xABD13D59, + 0x26D930AC,0x51DE003A,0xC8D75180,0xBFD06116,0x21B4F4B5,0x56B3C423,0xCFBA9599,0xB8BDA50F, + 0x2802B89E,0x5F058808,0xC60CD9B2,0xB10BE924,0x2F6F7C87,0x58684C11,0xC1611DAB,0xB6662D3D, + 0x76DC4190,0x01DB7106,0x98D220BC,0xEFD5102A,0x71B18589,0x06B6B51F,0x9FBFE4A5,0xE8B8D433, + 0x7807C9A2,0x0F00F934,0x9609A88E,0xE10E9818,0x7F6A0DBB,0x086D3D2D,0x91646C97,0xE6635C01, + 0x6B6B51F4,0x1C6C6162,0x856530D8,0xF262004E,0x6C0695ED,0x1B01A57B,0x8208F4C1,0xF50FC457, + 0x65B0D9C6,0x12B7E950,0x8BBEB8EA,0xFCB9887C,0x62DD1DDF,0x15DA2D49,0x8CD37CF3,0xFBD44C65, + 0x4DB26158,0x3AB551CE,0xA3BC0074,0xD4BB30E2,0x4ADFA541,0x3DD895D7,0xA4D1C46D,0xD3D6F4FB, + 0x4369E96A,0x346ED9FC,0xAD678846,0xDA60B8D0,0x44042D73,0x33031DE5,0xAA0A4C5F,0xDD0D7CC9, + 0x5005713C,0x270241AA,0xBE0B1010,0xC90C2086,0x5768B525,0x206F85B3,0xB966D409,0xCE61E49F, + 0x5EDEF90E,0x29D9C998,0xB0D09822,0xC7D7A8B4,0x59B33D17,0x2EB40D81,0xB7BD5C3B,0xC0BA6CAD, + 0xEDB88320,0x9ABFB3B6,0x03B6E20C,0x74B1D29A,0xEAD54739,0x9DD277AF,0x04DB2615,0x73DC1683, + 0xE3630B12,0x94643B84,0x0D6D6A3E,0x7A6A5AA8,0xE40ECF0B,0x9309FF9D,0x0A00AE27,0x7D079EB1, + 0xF00F9344,0x8708A3D2,0x1E01F268,0x6906C2FE,0xF762575D,0x806567CB,0x196C3671,0x6E6B06E7, + 0xFED41B76,0x89D32BE0,0x10DA7A5A,0x67DD4ACC,0xF9B9DF6F,0x8EBEEFF9,0x17B7BE43,0x60B08ED5, + 0xD6D6A3E8,0xA1D1937E,0x38D8C2C4,0x4FDFF252,0xD1BB67F1,0xA6BC5767,0x3FB506DD,0x48B2364B, + 0xD80D2BDA,0xAF0A1B4C,0x36034AF6,0x41047A60,0xDF60EFC3,0xA867DF55,0x316E8EEF,0x4669BE79, + 0xCB61B38C,0xBC66831A,0x256FD2A0,0x5268E236,0xCC0C7795,0xBB0B4703,0x220216B9,0x5505262F, + 0xC5BA3BBE,0xB2BD0B28,0x2BB45A92,0x5CB36A04,0xC2D7FFA7,0xB5D0CF31,0x2CD99E8B,0x5BDEAE1D, + 0x9B64C2B0,0xEC63F226,0x756AA39C,0x026D930A,0x9C0906A9,0xEB0E363F,0x72076785,0x05005713, + 0x95BF4A82,0xE2B87A14,0x7BB12BAE,0x0CB61B38,0x92D28E9B,0xE5D5BE0D,0x7CDCEFB7,0x0BDBDF21, + 0x86D3D2D4,0xF1D4E242,0x68DDB3F8,0x1FDA836E,0x81BE16CD,0xF6B9265B,0x6FB077E1,0x18B74777, + 0x88085AE6,0xFF0F6A70,0x66063BCA,0x11010B5C,0x8F659EFF,0xF862AE69,0x616BFFD3,0x166CCF45, + 0xA00AE278,0xD70DD2EE,0x4E048354,0x3903B3C2,0xA7672661,0xD06016F7,0x4969474D,0x3E6E77DB, + 0xAED16A4A,0xD9D65ADC,0x40DF0B66,0x37D83BF0,0xA9BCAE53,0xDEBB9EC5,0x47B2CF7F,0x30B5FFE9, + 0xBDBDF21C,0xCABAC28A,0x53B39330,0x24B4A3A6,0xBAD03605,0xCDD70693,0x54DE5729,0x23D967BF, + 0xB3667A2E,0xC4614AB8,0x5D681B02,0x2A6F2B94,0xB40BBE37,0xC30C8EA1,0x5A05DF1B,0x2D02EF8D, + } + , + { + 0x00000000,0x191B3141,0x32366282,0x2B2D53C3,0x646CC504,0x7D77F445,0x565AA786,0x4F4196C7, + 0xC8D98A08,0xD1C2BB49,0xFAEFE88A,0xE3F4D9CB,0xACB54F0C,0xB5AE7E4D,0x9E832D8E,0x87981CCF, + 0x4AC21251,0x53D92310,0x78F470D3,0x61EF4192,0x2EAED755,0x37B5E614,0x1C98B5D7,0x05838496, + 0x821B9859,0x9B00A918,0xB02DFADB,0xA936CB9A,0xE6775D5D,0xFF6C6C1C,0xD4413FDF,0xCD5A0E9E, + 0x958424A2,0x8C9F15E3,0xA7B24620,0xBEA97761,0xF1E8E1A6,0xE8F3D0E7,0xC3DE8324,0xDAC5B265, + 0x5D5DAEAA,0x44469FEB,0x6F6BCC28,0x7670FD69,0x39316BAE,0x202A5AEF,0x0B07092C,0x121C386D, + 0xDF4636F3,0xC65D07B2,0xED705471,0xF46B6530,0xBB2AF3F7,0xA231C2B6,0x891C9175,0x9007A034, + 0x179FBCFB,0x0E848DBA,0x25A9DE79,0x3CB2EF38,0x73F379FF,0x6AE848BE,0x41C51B7D,0x58DE2A3C, + 0xF0794F05,0xE9627E44,0xC24F2D87,0xDB541CC6,0x94158A01,0x8D0EBB40,0xA623E883,0xBF38D9C2, + 0x38A0C50D,0x21BBF44C,0x0A96A78F,0x138D96CE,0x5CCC0009,0x45D73148,0x6EFA628B,0x77E153CA, + 0xBABB5D54,0xA3A06C15,0x888D3FD6,0x91960E97,0xDED79850,0xC7CCA911,0xECE1FAD2,0xF5FACB93, + 0x7262D75C,0x6B79E61D,0x4054B5DE,0x594F849F,0x160E1258,0x0F152319,0x243870DA,0x3D23419B, + 0x65FD6BA7,0x7CE65AE6,0x57CB0925,0x4ED03864,0x0191AEA3,0x188A9FE2,0x33A7CC21,0x2ABCFD60, + 0xAD24E1AF,0xB43FD0EE,0x9F12832D,0x8609B26C,0xC94824AB,0xD05315EA,0xFB7E4629,0xE2657768, + 0x2F3F79F6,0x362448B7,0x1D091B74,0x04122A35,0x4B53BCF2,0x52488DB3,0x7965DE70,0x607EEF31, + 0xE7E6F3FE,0xFEFDC2BF,0xD5D0917C,0xCCCBA03D,0x838A36FA,0x9A9107BB,0xB1BC5478,0xA8A76539, + 0x3B83984B,0x2298A90A,0x09B5FAC9,0x10AECB88,0x5FEF5D4F,0x46F46C0E,0x6DD93FCD,0x74C20E8C, + 0xF35A1243,0xEA412302,0xC16C70C1,0xD8774180,0x9736D747,0x8E2DE606,0xA500B5C5,0xBC1B8484, + 0x71418A1A,0x685ABB5B,0x4377E898,0x5A6CD9D9,0x152D4F1E,0x0C367E5F,0x271B2D9C,0x3E001CDD, + 0xB9980012,0xA0833153,0x8BAE6290,0x92B553D1,0xDDF4C516,0xC4EFF457,0xEFC2A794,0xF6D996D5, + 0xAE07BCE9,0xB71C8DA8,0x9C31DE6B,0x852AEF2A,0xCA6B79ED,0xD37048AC,0xF85D1B6F,0xE1462A2E, + 0x66DE36E1,0x7FC507A0,0x54E85463,0x4DF36522,0x02B2F3E5,0x1BA9C2A4,0x30849167,0x299FA026, + 0xE4C5AEB8,0xFDDE9FF9,0xD6F3CC3A,0xCFE8FD7B,0x80A96BBC,0x99B25AFD,0xB29F093E,0xAB84387F, + 0x2C1C24B0,0x350715F1,0x1E2A4632,0x07317773,0x4870E1B4,0x516BD0F5,0x7A468336,0x635DB277, + 0xCBFAD74E,0xD2E1E60F,0xF9CCB5CC,0xE0D7848D,0xAF96124A,0xB68D230B,0x9DA070C8,0x84BB4189, + 0x03235D46,0x1A386C07,0x31153FC4,0x280E0E85,0x674F9842,0x7E54A903,0x5579FAC0,0x4C62CB81, + 0x8138C51F,0x9823F45E,0xB30EA79D,0xAA1596DC,0xE554001B,0xFC4F315A,0xD7626299,0xCE7953D8, + 0x49E14F17,0x50FA7E56,0x7BD72D95,0x62CC1CD4,0x2D8D8A13,0x3496BB52,0x1FBBE891,0x06A0D9D0, + 0x5E7EF3EC,0x4765C2AD,0x6C48916E,0x7553A02F,0x3A1236E8,0x230907A9,0x0824546A,0x113F652B, + 0x96A779E4,0x8FBC48A5,0xA4911B66,0xBD8A2A27,0xF2CBBCE0,0xEBD08DA1,0xC0FDDE62,0xD9E6EF23, + 0x14BCE1BD,0x0DA7D0FC,0x268A833F,0x3F91B27E,0x70D024B9,0x69CB15F8,0x42E6463B,0x5BFD777A, + 0xDC656BB5,0xC57E5AF4,0xEE530937,0xF7483876,0xB809AEB1,0xA1129FF0,0x8A3FCC33,0x9324FD72, + }, + { + 0x00000000,0x01C26A37,0x0384D46E,0x0246BE59,0x0709A8DC,0x06CBC2EB,0x048D7CB2,0x054F1685, + 0x0E1351B8,0x0FD13B8F,0x0D9785D6,0x0C55EFE1,0x091AF964,0x08D89353,0x0A9E2D0A,0x0B5C473D, + 0x1C26A370,0x1DE4C947,0x1FA2771E,0x1E601D29,0x1B2F0BAC,0x1AED619B,0x18ABDFC2,0x1969B5F5, + 0x1235F2C8,0x13F798FF,0x11B126A6,0x10734C91,0x153C5A14,0x14FE3023,0x16B88E7A,0x177AE44D, + 0x384D46E0,0x398F2CD7,0x3BC9928E,0x3A0BF8B9,0x3F44EE3C,0x3E86840B,0x3CC03A52,0x3D025065, + 0x365E1758,0x379C7D6F,0x35DAC336,0x3418A901,0x3157BF84,0x3095D5B3,0x32D36BEA,0x331101DD, + 0x246BE590,0x25A98FA7,0x27EF31FE,0x262D5BC9,0x23624D4C,0x22A0277B,0x20E69922,0x2124F315, + 0x2A78B428,0x2BBADE1F,0x29FC6046,0x283E0A71,0x2D711CF4,0x2CB376C3,0x2EF5C89A,0x2F37A2AD, + 0x709A8DC0,0x7158E7F7,0x731E59AE,0x72DC3399,0x7793251C,0x76514F2B,0x7417F172,0x75D59B45, + 0x7E89DC78,0x7F4BB64F,0x7D0D0816,0x7CCF6221,0x798074A4,0x78421E93,0x7A04A0CA,0x7BC6CAFD, + 0x6CBC2EB0,0x6D7E4487,0x6F38FADE,0x6EFA90E9,0x6BB5866C,0x6A77EC5B,0x68315202,0x69F33835, + 0x62AF7F08,0x636D153F,0x612BAB66,0x60E9C151,0x65A6D7D4,0x6464BDE3,0x662203BA,0x67E0698D, + 0x48D7CB20,0x4915A117,0x4B531F4E,0x4A917579,0x4FDE63FC,0x4E1C09CB,0x4C5AB792,0x4D98DDA5, + 0x46C49A98,0x4706F0AF,0x45404EF6,0x448224C1,0x41CD3244,0x400F5873,0x4249E62A,0x438B8C1D, + 0x54F16850,0x55330267,0x5775BC3E,0x56B7D609,0x53F8C08C,0x523AAABB,0x507C14E2,0x51BE7ED5, + 0x5AE239E8,0x5B2053DF,0x5966ED86,0x58A487B1,0x5DEB9134,0x5C29FB03,0x5E6F455A,0x5FAD2F6D, + 0xE1351B80,0xE0F771B7,0xE2B1CFEE,0xE373A5D9,0xE63CB35C,0xE7FED96B,0xE5B86732,0xE47A0D05, + 0xEF264A38,0xEEE4200F,0xECA29E56,0xED60F461,0xE82FE2E4,0xE9ED88D3,0xEBAB368A,0xEA695CBD, + 0xFD13B8F0,0xFCD1D2C7,0xFE976C9E,0xFF5506A9,0xFA1A102C,0xFBD87A1B,0xF99EC442,0xF85CAE75, + 0xF300E948,0xF2C2837F,0xF0843D26,0xF1465711,0xF4094194,0xF5CB2BA3,0xF78D95FA,0xF64FFFCD, + 0xD9785D60,0xD8BA3757,0xDAFC890E,0xDB3EE339,0xDE71F5BC,0xDFB39F8B,0xDDF521D2,0xDC374BE5, + 0xD76B0CD8,0xD6A966EF,0xD4EFD8B6,0xD52DB281,0xD062A404,0xD1A0CE33,0xD3E6706A,0xD2241A5D, + 0xC55EFE10,0xC49C9427,0xC6DA2A7E,0xC7184049,0xC25756CC,0xC3953CFB,0xC1D382A2,0xC011E895, + 0xCB4DAFA8,0xCA8FC59F,0xC8C97BC6,0xC90B11F1,0xCC440774,0xCD866D43,0xCFC0D31A,0xCE02B92D, + 0x91AF9640,0x906DFC77,0x922B422E,0x93E92819,0x96A63E9C,0x976454AB,0x9522EAF2,0x94E080C5, + 0x9FBCC7F8,0x9E7EADCF,0x9C381396,0x9DFA79A1,0x98B56F24,0x99770513,0x9B31BB4A,0x9AF3D17D, + 0x8D893530,0x8C4B5F07,0x8E0DE15E,0x8FCF8B69,0x8A809DEC,0x8B42F7DB,0x89044982,0x88C623B5, + 0x839A6488,0x82580EBF,0x801EB0E6,0x81DCDAD1,0x8493CC54,0x8551A663,0x8717183A,0x86D5720D, + 0xA9E2D0A0,0xA820BA97,0xAA6604CE,0xABA46EF9,0xAEEB787C,0xAF29124B,0xAD6FAC12,0xACADC625, + 0xA7F18118,0xA633EB2F,0xA4755576,0xA5B73F41,0xA0F829C4,0xA13A43F3,0xA37CFDAA,0xA2BE979D, + 0xB5C473D0,0xB40619E7,0xB640A7BE,0xB782CD89,0xB2CDDB0C,0xB30FB13B,0xB1490F62,0xB08B6555, + 0xBBD72268,0xBA15485F,0xB853F606,0xB9919C31,0xBCDE8AB4,0xBD1CE083,0xBF5A5EDA,0xBE9834ED, + }, + { + 0x00000000,0xB8BC6765,0xAA09C88B,0x12B5AFEE,0x8F629757,0x37DEF032,0x256B5FDC,0x9DD738B9, + 0xC5B428EF,0x7D084F8A,0x6FBDE064,0xD7018701,0x4AD6BFB8,0xF26AD8DD,0xE0DF7733,0x58631056, + 0x5019579F,0xE8A530FA,0xFA109F14,0x42ACF871,0xDF7BC0C8,0x67C7A7AD,0x75720843,0xCDCE6F26, + 0x95AD7F70,0x2D111815,0x3FA4B7FB,0x8718D09E,0x1ACFE827,0xA2738F42,0xB0C620AC,0x087A47C9, + 0xA032AF3E,0x188EC85B,0x0A3B67B5,0xB28700D0,0x2F503869,0x97EC5F0C,0x8559F0E2,0x3DE59787, + 0x658687D1,0xDD3AE0B4,0xCF8F4F5A,0x7733283F,0xEAE41086,0x525877E3,0x40EDD80D,0xF851BF68, + 0xF02BF8A1,0x48979FC4,0x5A22302A,0xE29E574F,0x7F496FF6,0xC7F50893,0xD540A77D,0x6DFCC018, + 0x359FD04E,0x8D23B72B,0x9F9618C5,0x272A7FA0,0xBAFD4719,0x0241207C,0x10F48F92,0xA848E8F7, + 0x9B14583D,0x23A83F58,0x311D90B6,0x89A1F7D3,0x1476CF6A,0xACCAA80F,0xBE7F07E1,0x06C36084, + 0x5EA070D2,0xE61C17B7,0xF4A9B859,0x4C15DF3C,0xD1C2E785,0x697E80E0,0x7BCB2F0E,0xC377486B, + 0xCB0D0FA2,0x73B168C7,0x6104C729,0xD9B8A04C,0x446F98F5,0xFCD3FF90,0xEE66507E,0x56DA371B, + 0x0EB9274D,0xB6054028,0xA4B0EFC6,0x1C0C88A3,0x81DBB01A,0x3967D77F,0x2BD27891,0x936E1FF4, + 0x3B26F703,0x839A9066,0x912F3F88,0x299358ED,0xB4446054,0x0CF80731,0x1E4DA8DF,0xA6F1CFBA, + 0xFE92DFEC,0x462EB889,0x549B1767,0xEC277002,0x71F048BB,0xC94C2FDE,0xDBF98030,0x6345E755, + 0x6B3FA09C,0xD383C7F9,0xC1366817,0x798A0F72,0xE45D37CB,0x5CE150AE,0x4E54FF40,0xF6E89825, + 0xAE8B8873,0x1637EF16,0x048240F8,0xBC3E279D,0x21E91F24,0x99557841,0x8BE0D7AF,0x335CB0CA, + 0xED59B63B,0x55E5D15E,0x47507EB0,0xFFEC19D5,0x623B216C,0xDA874609,0xC832E9E7,0x708E8E82, + 0x28ED9ED4,0x9051F9B1,0x82E4565F,0x3A58313A,0xA78F0983,0x1F336EE6,0x0D86C108,0xB53AA66D, + 0xBD40E1A4,0x05FC86C1,0x1749292F,0xAFF54E4A,0x322276F3,0x8A9E1196,0x982BBE78,0x2097D91D, + 0x78F4C94B,0xC048AE2E,0xD2FD01C0,0x6A4166A5,0xF7965E1C,0x4F2A3979,0x5D9F9697,0xE523F1F2, + 0x4D6B1905,0xF5D77E60,0xE762D18E,0x5FDEB6EB,0xC2098E52,0x7AB5E937,0x680046D9,0xD0BC21BC, + 0x88DF31EA,0x3063568F,0x22D6F961,0x9A6A9E04,0x07BDA6BD,0xBF01C1D8,0xADB46E36,0x15080953, + 0x1D724E9A,0xA5CE29FF,0xB77B8611,0x0FC7E174,0x9210D9CD,0x2AACBEA8,0x38191146,0x80A57623, + 0xD8C66675,0x607A0110,0x72CFAEFE,0xCA73C99B,0x57A4F122,0xEF189647,0xFDAD39A9,0x45115ECC, + 0x764DEE06,0xCEF18963,0xDC44268D,0x64F841E8,0xF92F7951,0x41931E34,0x5326B1DA,0xEB9AD6BF, + 0xB3F9C6E9,0x0B45A18C,0x19F00E62,0xA14C6907,0x3C9B51BE,0x842736DB,0x96929935,0x2E2EFE50, + 0x2654B999,0x9EE8DEFC,0x8C5D7112,0x34E11677,0xA9362ECE,0x118A49AB,0x033FE645,0xBB838120, + 0xE3E09176,0x5B5CF613,0x49E959FD,0xF1553E98,0x6C820621,0xD43E6144,0xC68BCEAA,0x7E37A9CF, + 0xD67F4138,0x6EC3265D,0x7C7689B3,0xC4CAEED6,0x591DD66F,0xE1A1B10A,0xF3141EE4,0x4BA87981, + 0x13CB69D7,0xAB770EB2,0xB9C2A15C,0x017EC639,0x9CA9FE80,0x241599E5,0x36A0360B,0x8E1C516E, + 0x866616A7,0x3EDA71C2,0x2C6FDE2C,0x94D3B949,0x090481F0,0xB1B8E695,0xA30D497B,0x1BB12E1E, + 0x43D23E48,0xFB6E592D,0xE9DBF6C3,0x516791A6,0xCCB0A91F,0x740CCE7A,0x66B96194,0xDE0506F1, + } + , + { + 0x00000000,0x3D6029B0,0x7AC05360,0x47A07AD0,0xF580A6C0,0xC8E08F70,0x8F40F5A0,0xB220DC10, + 0x30704BC1,0x0D106271,0x4AB018A1,0x77D03111,0xC5F0ED01,0xF890C4B1,0xBF30BE61,0x825097D1, + 0x60E09782,0x5D80BE32,0x1A20C4E2,0x2740ED52,0x95603142,0xA80018F2,0xEFA06222,0xD2C04B92, + 0x5090DC43,0x6DF0F5F3,0x2A508F23,0x1730A693,0xA5107A83,0x98705333,0xDFD029E3,0xE2B00053, + 0xC1C12F04,0xFCA106B4,0xBB017C64,0x866155D4,0x344189C4,0x0921A074,0x4E81DAA4,0x73E1F314, + 0xF1B164C5,0xCCD14D75,0x8B7137A5,0xB6111E15,0x0431C205,0x3951EBB5,0x7EF19165,0x4391B8D5, + 0xA121B886,0x9C419136,0xDBE1EBE6,0xE681C256,0x54A11E46,0x69C137F6,0x2E614D26,0x13016496, + 0x9151F347,0xAC31DAF7,0xEB91A027,0xD6F18997,0x64D15587,0x59B17C37,0x1E1106E7,0x23712F57, + 0x58F35849,0x659371F9,0x22330B29,0x1F532299,0xAD73FE89,0x9013D739,0xD7B3ADE9,0xEAD38459, + 0x68831388,0x55E33A38,0x124340E8,0x2F236958,0x9D03B548,0xA0639CF8,0xE7C3E628,0xDAA3CF98, + 0x3813CFCB,0x0573E67B,0x42D39CAB,0x7FB3B51B,0xCD93690B,0xF0F340BB,0xB7533A6B,0x8A3313DB, + 0x0863840A,0x3503ADBA,0x72A3D76A,0x4FC3FEDA,0xFDE322CA,0xC0830B7A,0x872371AA,0xBA43581A, + 0x9932774D,0xA4525EFD,0xE3F2242D,0xDE920D9D,0x6CB2D18D,0x51D2F83D,0x167282ED,0x2B12AB5D, + 0xA9423C8C,0x9422153C,0xD3826FEC,0xEEE2465C,0x5CC29A4C,0x61A2B3FC,0x2602C92C,0x1B62E09C, + 0xF9D2E0CF,0xC4B2C97F,0x8312B3AF,0xBE729A1F,0x0C52460F,0x31326FBF,0x7692156F,0x4BF23CDF, + 0xC9A2AB0E,0xF4C282BE,0xB362F86E,0x8E02D1DE,0x3C220DCE,0x0142247E,0x46E25EAE,0x7B82771E, + 0xB1E6B092,0x8C869922,0xCB26E3F2,0xF646CA42,0x44661652,0x79063FE2,0x3EA64532,0x03C66C82, + 0x8196FB53,0xBCF6D2E3,0xFB56A833,0xC6368183,0x74165D93,0x49767423,0x0ED60EF3,0x33B62743, + 0xD1062710,0xEC660EA0,0xABC67470,0x96A65DC0,0x248681D0,0x19E6A860,0x5E46D2B0,0x6326FB00, + 0xE1766CD1,0xDC164561,0x9BB63FB1,0xA6D61601,0x14F6CA11,0x2996E3A1,0x6E369971,0x5356B0C1, + 0x70279F96,0x4D47B626,0x0AE7CCF6,0x3787E546,0x85A73956,0xB8C710E6,0xFF676A36,0xC2074386, + 0x4057D457,0x7D37FDE7,0x3A978737,0x07F7AE87,0xB5D77297,0x88B75B27,0xCF1721F7,0xF2770847, + 0x10C70814,0x2DA721A4,0x6A075B74,0x576772C4,0xE547AED4,0xD8278764,0x9F87FDB4,0xA2E7D404, + 0x20B743D5,0x1DD76A65,0x5A7710B5,0x67173905,0xD537E515,0xE857CCA5,0xAFF7B675,0x92979FC5, + 0xE915E8DB,0xD475C16B,0x93D5BBBB,0xAEB5920B,0x1C954E1B,0x21F567AB,0x66551D7B,0x5B3534CB, + 0xD965A31A,0xE4058AAA,0xA3A5F07A,0x9EC5D9CA,0x2CE505DA,0x11852C6A,0x562556BA,0x6B457F0A, + 0x89F57F59,0xB49556E9,0xF3352C39,0xCE550589,0x7C75D999,0x4115F029,0x06B58AF9,0x3BD5A349, + 0xB9853498,0x84E51D28,0xC34567F8,0xFE254E48,0x4C059258,0x7165BBE8,0x36C5C138,0x0BA5E888, + 0x28D4C7DF,0x15B4EE6F,0x521494BF,0x6F74BD0F,0xDD54611F,0xE03448AF,0xA794327F,0x9AF41BCF, + 0x18A48C1E,0x25C4A5AE,0x6264DF7E,0x5F04F6CE,0xED242ADE,0xD044036E,0x97E479BE,0xAA84500E, + 0x4834505D,0x755479ED,0x32F4033D,0x0F942A8D,0xBDB4F69D,0x80D4DF2D,0xC774A5FD,0xFA148C4D, + 0x78441B9C,0x4524322C,0x028448FC,0x3FE4614C,0x8DC4BD5C,0xB0A494EC,0xF704EE3C,0xCA64C78C, + }, + { + 0x00000000,0xCB5CD3A5,0x4DC8A10B,0x869472AE,0x9B914216,0x50CD91B3,0xD659E31D,0x1D0530B8, + 0xEC53826D,0x270F51C8,0xA19B2366,0x6AC7F0C3,0x77C2C07B,0xBC9E13DE,0x3A0A6170,0xF156B2D5, + 0x03D6029B,0xC88AD13E,0x4E1EA390,0x85427035,0x9847408D,0x531B9328,0xD58FE186,0x1ED33223, + 0xEF8580F6,0x24D95353,0xA24D21FD,0x6911F258,0x7414C2E0,0xBF481145,0x39DC63EB,0xF280B04E, + 0x07AC0536,0xCCF0D693,0x4A64A43D,0x81387798,0x9C3D4720,0x57619485,0xD1F5E62B,0x1AA9358E, + 0xEBFF875B,0x20A354FE,0xA6372650,0x6D6BF5F5,0x706EC54D,0xBB3216E8,0x3DA66446,0xF6FAB7E3, + 0x047A07AD,0xCF26D408,0x49B2A6A6,0x82EE7503,0x9FEB45BB,0x54B7961E,0xD223E4B0,0x197F3715, + 0xE82985C0,0x23755665,0xA5E124CB,0x6EBDF76E,0x73B8C7D6,0xB8E41473,0x3E7066DD,0xF52CB578, + 0x0F580A6C,0xC404D9C9,0x4290AB67,0x89CC78C2,0x94C9487A,0x5F959BDF,0xD901E971,0x125D3AD4, + 0xE30B8801,0x28575BA4,0xAEC3290A,0x659FFAAF,0x789ACA17,0xB3C619B2,0x35526B1C,0xFE0EB8B9, + 0x0C8E08F7,0xC7D2DB52,0x4146A9FC,0x8A1A7A59,0x971F4AE1,0x5C439944,0xDAD7EBEA,0x118B384F, + 0xE0DD8A9A,0x2B81593F,0xAD152B91,0x6649F834,0x7B4CC88C,0xB0101B29,0x36846987,0xFDD8BA22, + 0x08F40F5A,0xC3A8DCFF,0x453CAE51,0x8E607DF4,0x93654D4C,0x58399EE9,0xDEADEC47,0x15F13FE2, + 0xE4A78D37,0x2FFB5E92,0xA96F2C3C,0x6233FF99,0x7F36CF21,0xB46A1C84,0x32FE6E2A,0xF9A2BD8F, + 0x0B220DC1,0xC07EDE64,0x46EAACCA,0x8DB67F6F,0x90B34FD7,0x5BEF9C72,0xDD7BEEDC,0x16273D79, + 0xE7718FAC,0x2C2D5C09,0xAAB92EA7,0x61E5FD02,0x7CE0CDBA,0xB7BC1E1F,0x31286CB1,0xFA74BF14, + 0x1EB014D8,0xD5ECC77D,0x5378B5D3,0x98246676,0x852156CE,0x4E7D856B,0xC8E9F7C5,0x03B52460, + 0xF2E396B5,0x39BF4510,0xBF2B37BE,0x7477E41B,0x6972D4A3,0xA22E0706,0x24BA75A8,0xEFE6A60D, + 0x1D661643,0xD63AC5E6,0x50AEB748,0x9BF264ED,0x86F75455,0x4DAB87F0,0xCB3FF55E,0x006326FB, + 0xF135942E,0x3A69478B,0xBCFD3525,0x77A1E680,0x6AA4D638,0xA1F8059D,0x276C7733,0xEC30A496, + 0x191C11EE,0xD240C24B,0x54D4B0E5,0x9F886340,0x828D53F8,0x49D1805D,0xCF45F2F3,0x04192156, + 0xF54F9383,0x3E134026,0xB8873288,0x73DBE12D,0x6EDED195,0xA5820230,0x2316709E,0xE84AA33B, + 0x1ACA1375,0xD196C0D0,0x5702B27E,0x9C5E61DB,0x815B5163,0x4A0782C6,0xCC93F068,0x07CF23CD, + 0xF6999118,0x3DC542BD,0xBB513013,0x700DE3B6,0x6D08D30E,0xA65400AB,0x20C07205,0xEB9CA1A0, + 0x11E81EB4,0xDAB4CD11,0x5C20BFBF,0x977C6C1A,0x8A795CA2,0x41258F07,0xC7B1FDA9,0x0CED2E0C, + 0xFDBB9CD9,0x36E74F7C,0xB0733DD2,0x7B2FEE77,0x662ADECF,0xAD760D6A,0x2BE27FC4,0xE0BEAC61, + 0x123E1C2F,0xD962CF8A,0x5FF6BD24,0x94AA6E81,0x89AF5E39,0x42F38D9C,0xC467FF32,0x0F3B2C97, + 0xFE6D9E42,0x35314DE7,0xB3A53F49,0x78F9ECEC,0x65FCDC54,0xAEA00FF1,0x28347D5F,0xE368AEFA, + 0x16441B82,0xDD18C827,0x5B8CBA89,0x90D0692C,0x8DD55994,0x46898A31,0xC01DF89F,0x0B412B3A, + 0xFA1799EF,0x314B4A4A,0xB7DF38E4,0x7C83EB41,0x6186DBF9,0xAADA085C,0x2C4E7AF2,0xE712A957, + 0x15921919,0xDECECABC,0x585AB812,0x93066BB7,0x8E035B0F,0x455F88AA,0xC3CBFA04,0x089729A1, + 0xF9C19B74,0x329D48D1,0xB4093A7F,0x7F55E9DA,0x6250D962,0xA90C0AC7,0x2F987869,0xE4C4ABCC, + }, + { + 0x00000000,0xA6770BB4,0x979F1129,0x31E81A9D,0xF44F2413,0x52382FA7,0x63D0353A,0xC5A73E8E, + 0x33EF4E67,0x959845D3,0xA4705F4E,0x020754FA,0xC7A06A74,0x61D761C0,0x503F7B5D,0xF64870E9, + 0x67DE9CCE,0xC1A9977A,0xF0418DE7,0x56368653,0x9391B8DD,0x35E6B369,0x040EA9F4,0xA279A240, + 0x5431D2A9,0xF246D91D,0xC3AEC380,0x65D9C834,0xA07EF6BA,0x0609FD0E,0x37E1E793,0x9196EC27, + 0xCFBD399C,0x69CA3228,0x582228B5,0xFE552301,0x3BF21D8F,0x9D85163B,0xAC6D0CA6,0x0A1A0712, + 0xFC5277FB,0x5A257C4F,0x6BCD66D2,0xCDBA6D66,0x081D53E8,0xAE6A585C,0x9F8242C1,0x39F54975, + 0xA863A552,0x0E14AEE6,0x3FFCB47B,0x998BBFCF,0x5C2C8141,0xFA5B8AF5,0xCBB39068,0x6DC49BDC, + 0x9B8CEB35,0x3DFBE081,0x0C13FA1C,0xAA64F1A8,0x6FC3CF26,0xC9B4C492,0xF85CDE0F,0x5E2BD5BB, + 0x440B7579,0xE27C7ECD,0xD3946450,0x75E36FE4,0xB044516A,0x16335ADE,0x27DB4043,0x81AC4BF7, + 0x77E43B1E,0xD19330AA,0xE07B2A37,0x460C2183,0x83AB1F0D,0x25DC14B9,0x14340E24,0xB2430590, + 0x23D5E9B7,0x85A2E203,0xB44AF89E,0x123DF32A,0xD79ACDA4,0x71EDC610,0x4005DC8D,0xE672D739, + 0x103AA7D0,0xB64DAC64,0x87A5B6F9,0x21D2BD4D,0xE47583C3,0x42028877,0x73EA92EA,0xD59D995E, + 0x8BB64CE5,0x2DC14751,0x1C295DCC,0xBA5E5678,0x7FF968F6,0xD98E6342,0xE86679DF,0x4E11726B, + 0xB8590282,0x1E2E0936,0x2FC613AB,0x89B1181F,0x4C162691,0xEA612D25,0xDB8937B8,0x7DFE3C0C, + 0xEC68D02B,0x4A1FDB9F,0x7BF7C102,0xDD80CAB6,0x1827F438,0xBE50FF8C,0x8FB8E511,0x29CFEEA5, + 0xDF879E4C,0x79F095F8,0x48188F65,0xEE6F84D1,0x2BC8BA5F,0x8DBFB1EB,0xBC57AB76,0x1A20A0C2, + 0x8816EAF2,0x2E61E146,0x1F89FBDB,0xB9FEF06F,0x7C59CEE1,0xDA2EC555,0xEBC6DFC8,0x4DB1D47C, + 0xBBF9A495,0x1D8EAF21,0x2C66B5BC,0x8A11BE08,0x4FB68086,0xE9C18B32,0xD82991AF,0x7E5E9A1B, + 0xEFC8763C,0x49BF7D88,0x78576715,0xDE206CA1,0x1B87522F,0xBDF0599B,0x8C184306,0x2A6F48B2, + 0xDC27385B,0x7A5033EF,0x4BB82972,0xEDCF22C6,0x28681C48,0x8E1F17FC,0xBFF70D61,0x198006D5, + 0x47ABD36E,0xE1DCD8DA,0xD034C247,0x7643C9F3,0xB3E4F77D,0x1593FCC9,0x247BE654,0x820CEDE0, + 0x74449D09,0xD23396BD,0xE3DB8C20,0x45AC8794,0x800BB91A,0x267CB2AE,0x1794A833,0xB1E3A387, + 0x20754FA0,0x86024414,0xB7EA5E89,0x119D553D,0xD43A6BB3,0x724D6007,0x43A57A9A,0xE5D2712E, + 0x139A01C7,0xB5ED0A73,0x840510EE,0x22721B5A,0xE7D525D4,0x41A22E60,0x704A34FD,0xD63D3F49, + 0xCC1D9F8B,0x6A6A943F,0x5B828EA2,0xFDF58516,0x3852BB98,0x9E25B02C,0xAFCDAAB1,0x09BAA105, + 0xFFF2D1EC,0x5985DA58,0x686DC0C5,0xCE1ACB71,0x0BBDF5FF,0xADCAFE4B,0x9C22E4D6,0x3A55EF62, + 0xABC30345,0x0DB408F1,0x3C5C126C,0x9A2B19D8,0x5F8C2756,0xF9FB2CE2,0xC813367F,0x6E643DCB, + 0x982C4D22,0x3E5B4696,0x0FB35C0B,0xA9C457BF,0x6C636931,0xCA146285,0xFBFC7818,0x5D8B73AC, + 0x03A0A617,0xA5D7ADA3,0x943FB73E,0x3248BC8A,0xF7EF8204,0x519889B0,0x6070932D,0xC6079899, + 0x304FE870,0x9638E3C4,0xA7D0F959,0x01A7F2ED,0xC400CC63,0x6277C7D7,0x539FDD4A,0xF5E8D6FE, + 0x647E3AD9,0xC209316D,0xF3E12BF0,0x55962044,0x90311ECA,0x3646157E,0x07AE0FE3,0xA1D90457, + 0x579174BE,0xF1E67F0A,0xC00E6597,0x66796E23,0xA3DE50AD,0x05A95B19,0x34414184,0x92364A30, + }, + { + 0x00000000,0xCCAA009E,0x4225077D,0x8E8F07E3,0x844A0EFA,0x48E00E64,0xC66F0987,0x0AC50919, + 0xD3E51BB5,0x1F4F1B2B,0x91C01CC8,0x5D6A1C56,0x57AF154F,0x9B0515D1,0x158A1232,0xD92012AC, + 0x7CBB312B,0xB01131B5,0x3E9E3656,0xF23436C8,0xF8F13FD1,0x345B3F4F,0xBAD438AC,0x767E3832, + 0xAF5E2A9E,0x63F42A00,0xED7B2DE3,0x21D12D7D,0x2B142464,0xE7BE24FA,0x69312319,0xA59B2387, + 0xF9766256,0x35DC62C8,0xBB53652B,0x77F965B5,0x7D3C6CAC,0xB1966C32,0x3F196BD1,0xF3B36B4F, + 0x2A9379E3,0xE639797D,0x68B67E9E,0xA41C7E00,0xAED97719,0x62737787,0xECFC7064,0x205670FA, + 0x85CD537D,0x496753E3,0xC7E85400,0x0B42549E,0x01875D87,0xCD2D5D19,0x43A25AFA,0x8F085A64, + 0x562848C8,0x9A824856,0x140D4FB5,0xD8A74F2B,0xD2624632,0x1EC846AC,0x9047414F,0x5CED41D1, + 0x299DC2ED,0xE537C273,0x6BB8C590,0xA712C50E,0xADD7CC17,0x617DCC89,0xEFF2CB6A,0x2358CBF4, + 0xFA78D958,0x36D2D9C6,0xB85DDE25,0x74F7DEBB,0x7E32D7A2,0xB298D73C,0x3C17D0DF,0xF0BDD041, + 0x5526F3C6,0x998CF358,0x1703F4BB,0xDBA9F425,0xD16CFD3C,0x1DC6FDA2,0x9349FA41,0x5FE3FADF, + 0x86C3E873,0x4A69E8ED,0xC4E6EF0E,0x084CEF90,0x0289E689,0xCE23E617,0x40ACE1F4,0x8C06E16A, + 0xD0EBA0BB,0x1C41A025,0x92CEA7C6,0x5E64A758,0x54A1AE41,0x980BAEDF,0x1684A93C,0xDA2EA9A2, + 0x030EBB0E,0xCFA4BB90,0x412BBC73,0x8D81BCED,0x8744B5F4,0x4BEEB56A,0xC561B289,0x09CBB217, + 0xAC509190,0x60FA910E,0xEE7596ED,0x22DF9673,0x281A9F6A,0xE4B09FF4,0x6A3F9817,0xA6959889, + 0x7FB58A25,0xB31F8ABB,0x3D908D58,0xF13A8DC6,0xFBFF84DF,0x37558441,0xB9DA83A2,0x7570833C, + 0x533B85DA,0x9F918544,0x111E82A7,0xDDB48239,0xD7718B20,0x1BDB8BBE,0x95548C5D,0x59FE8CC3, + 0x80DE9E6F,0x4C749EF1,0xC2FB9912,0x0E51998C,0x04949095,0xC83E900B,0x46B197E8,0x8A1B9776, + 0x2F80B4F1,0xE32AB46F,0x6DA5B38C,0xA10FB312,0xABCABA0B,0x6760BA95,0xE9EFBD76,0x2545BDE8, + 0xFC65AF44,0x30CFAFDA,0xBE40A839,0x72EAA8A7,0x782FA1BE,0xB485A120,0x3A0AA6C3,0xF6A0A65D, + 0xAA4DE78C,0x66E7E712,0xE868E0F1,0x24C2E06F,0x2E07E976,0xE2ADE9E8,0x6C22EE0B,0xA088EE95, + 0x79A8FC39,0xB502FCA7,0x3B8DFB44,0xF727FBDA,0xFDE2F2C3,0x3148F25D,0xBFC7F5BE,0x736DF520, + 0xD6F6D6A7,0x1A5CD639,0x94D3D1DA,0x5879D144,0x52BCD85D,0x9E16D8C3,0x1099DF20,0xDC33DFBE, + 0x0513CD12,0xC9B9CD8C,0x4736CA6F,0x8B9CCAF1,0x8159C3E8,0x4DF3C376,0xC37CC495,0x0FD6C40B, + 0x7AA64737,0xB60C47A9,0x3883404A,0xF42940D4,0xFEEC49CD,0x32464953,0xBCC94EB0,0x70634E2E, + 0xA9435C82,0x65E95C1C,0xEB665BFF,0x27CC5B61,0x2D095278,0xE1A352E6,0x6F2C5505,0xA386559B, + 0x061D761C,0xCAB77682,0x44387161,0x889271FF,0x825778E6,0x4EFD7878,0xC0727F9B,0x0CD87F05, + 0xD5F86DA9,0x19526D37,0x97DD6AD4,0x5B776A4A,0x51B26353,0x9D1863CD,0x1397642E,0xDF3D64B0, + 0x83D02561,0x4F7A25FF,0xC1F5221C,0x0D5F2282,0x079A2B9B,0xCB302B05,0x45BF2CE6,0x89152C78, + 0x50353ED4,0x9C9F3E4A,0x121039A9,0xDEBA3937,0xD47F302E,0x18D530B0,0x965A3753,0x5AF037CD, + 0xFF6B144A,0x33C114D4,0xBD4E1337,0x71E413A9,0x7B211AB0,0xB78B1A2E,0x39041DCD,0xF5AE1D53, + 0x2C8E0FFF,0xE0240F61,0x6EAB0882,0xA201081C,0xA8C40105,0x646E019B,0xEAE10678,0x264B06E6, + } +}; + +/// swap endianess +static inline uint32_t swap(uint32_t x) +{ +#if defined(__GNUC__) || defined(__clang__) + return __builtin_bswap32(x); +#else + return (x >> 24) | + ((x >> 8) & 0x0000FF00) | + ((x << 8) & 0x00FF0000) | + (x << 24); +#endif +} + +unsigned int crc32_calc_slice_by_8(unsigned int previousCrc32, const void* data, int length) +{ + uint32_t crc = ~previousCrc32; // same as previousCrc32 ^ 0xFFFFFFFF + const uint32_t* current = (const uint32_t*)data; + + // process eight bytes at once (Slicing-by-8) + while (length >= 8) + { +#if __BYTE_ORDER == __BIG_ENDIAN + uint32_t one = *current++ ^ swap(crc); + uint32_t two = *current++; + crc = Crc32Lookup[0][two & 0xFF] ^ + Crc32Lookup[1][(two >> 8) & 0xFF] ^ + Crc32Lookup[2][(two >> 16) & 0xFF] ^ + Crc32Lookup[3][(two >> 24) & 0xFF] ^ + Crc32Lookup[4][one & 0xFF] ^ + Crc32Lookup[5][(one >> 8) & 0xFF] ^ + Crc32Lookup[6][(one >> 16) & 0xFF] ^ + Crc32Lookup[7][(one >> 24) & 0xFF]; +#else + uint32_t one = *current++ ^ crc; + uint32_t two = *current++; + crc = Crc32Lookup[0][(two >> 24) & 0xFF] ^ + Crc32Lookup[1][(two >> 16) & 0xFF] ^ + Crc32Lookup[2][(two >> 8) & 0xFF] ^ + Crc32Lookup[3][two & 0xFF] ^ + Crc32Lookup[4][(one >> 24) & 0xFF] ^ + Crc32Lookup[5][(one >> 16) & 0xFF] ^ + Crc32Lookup[6][(one >> 8) & 0xFF] ^ + Crc32Lookup[7][one & 0xFF]; +#endif + + length -= 8; + } + + const uint8_t* currentChar = (const uint8_t*)current; + // remaining 1 to 7 bytes (standard algorithm) + while (length-- != 0) + crc = (crc >> 8) ^ Crc32Lookup[0][(crc & 0xFF) ^ *currentChar++]; + + return ~crc; // same as crc ^ 0xFFFFFFFF +} + +unsigned int crc32_calc(unsigned int c, const void* data, int length) +{ + if (length >= 16) + { + return crc32_calc_slice_by_8(c, data, length); + } + unsigned char* p = (unsigned char*)data; + if (length == 0) + return c; + c ^= 0xFFFFFFFF; + while (length) + { + unsigned char temp = *p; + temp ^= (unsigned char)c; + c = (c >> 8) ^ Crc32Lookup[0][temp]; + // next + length--; + p++; + } + return ~c; +} \ No newline at end of file diff --git a/src/util/crypto/crc32.h b/src/util/crypto/crc32.h new file mode 100644 index 00000000..6a254433 --- /dev/null +++ b/src/util/crypto/crc32.h @@ -0,0 +1,3 @@ +#pragma once + +unsigned int crc32_calc(unsigned int c, const void* data, int length); \ No newline at end of file diff --git a/src/util/crypto/md5.cpp b/src/util/crypto/md5.cpp new file mode 100644 index 00000000..1967ab42 --- /dev/null +++ b/src/util/crypto/md5.cpp @@ -0,0 +1,360 @@ +/* +* This is an OpenSSL-compatible implementation of the RSA Data Security, Inc. +* MD5 Message-Digest Algorithm (RFC 1321). +* +* Homepage: +* http://openwall.info/wiki/people/solar/software/public-domain-source-code/md5 +* +* Author: +* Alexander Peslyak, better known as Solar Designer +* +* This software was written by Alexander Peslyak in 2001. No copyright is +* claimed, and the software is hereby placed in the public domain. +* In case this attempt to disclaim copyright and place the software in the +* public domain is deemed null and void, then the software is +* Copyright (c) 2001 Alexander Peslyak and it is hereby released to the +* general public under the following terms: +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted. +* +* There's ABSOLUTELY NO WARRANTY, express or implied. +* +* (This is a heavily cut-down "BSD license".) +* +* This differs from Colin Plumb's older public domain implementation in that +* no exactly 32-bit integer data type is required (any 32-bit or wider +* unsigned integer data type will do), there's no compile-time endianness +* configuration, and the function prototypes match OpenSSL's. No code from +* Colin Plumb's implementation has been reused; this comment merely compares +* the properties of the two independent implementations. +* +* The primary goals of this implementation are portability and ease of use. +* It is meant to be fast, but not as fast as possible. Some known +* optimizations are not included to reduce source code size and avoid +* compile-time configuration. +*/ + +#ifndef HAVE_OPENSSL + +#include + +#include "md5.h" + +/* +* The basic MD5 functions. +* +* F and G are optimized compared to their RFC 1321 definitions for +* architectures that lack an AND-NOT instruction, just like in Colin Plumb's +* implementation. +*/ +#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) +#define G(x, y, z) ((y) ^ ((z) & ((x) ^ (y)))) +#define H(x, y, z) (((x) ^ (y)) ^ (z)) +#define H2(x, y, z) ((x) ^ ((y) ^ (z))) +#define I(x, y, z) ((y) ^ ((x) | ~(z))) + +/* +* The MD5 transformation for all four rounds. +*/ +#define STEP(f, a, b, c, d, x, t, s) \ + (a) += f((b), (c), (d)) + (x) + (t); \ + (a) = (((a) << (s)) | (((a) & 0xffffffff) >> (32 - (s)))); \ + (a) += (b); + +/* +* SET reads 4 input bytes in little-endian byte order and stores them in a +* properly aligned word in host byte order. +* +* The check for little-endian architectures that tolerate unaligned memory +* accesses is just an optimization. Nothing will break if it fails to detect +* a suitable architecture. +* +* Unfortunately, this optimization may be a C strict aliasing rules violation +* if the caller's data buffer has effective type that cannot be aliased by +* MD5_u32plus. In practice, this problem may occur if these MD5 routines are +* inlined into a calling function, or with future and dangerously advanced +* link-time optimizations. For the time being, keeping these MD5 routines in +* their own translation unit avoids the problem. +*/ +#if defined(__i386__) || defined(__x86_64__) || defined(__vax__) +#define SET(n) \ + (*(MD5_u32plus *)&ptr[(n) * 4]) +#define GET(n) \ + SET(n) +#else +#define SET(n) \ + (ctx->block[(n)] = \ + (MD5_u32plus)ptr[(n) * 4] | \ + ((MD5_u32plus)ptr[(n) * 4 + 1] << 8) | \ + ((MD5_u32plus)ptr[(n) * 4 + 2] << 16) | \ + ((MD5_u32plus)ptr[(n) * 4 + 3] << 24)) +#define GET(n) \ + (ctx->block[(n)]) +#endif + +/* +* This processes one or more 64-byte data blocks, but does NOT update the bit +* counters. There are no alignment requirements. +*/ +static const void *body(MD5_CTX *ctx, const void *data, unsigned long size) +{ + const unsigned char *ptr; + MD5_u32plus a, b, c, d; + MD5_u32plus saved_a, saved_b, saved_c, saved_d; + + ptr = (const unsigned char *)data; + + a = ctx->a; + b = ctx->b; + c = ctx->c; + d = ctx->d; + + do { + saved_a = a; + saved_b = b; + saved_c = c; + saved_d = d; + + /* Round 1 */ + STEP(F, a, b, c, d, SET(0), 0xd76aa478, 7) + STEP(F, d, a, b, c, SET(1), 0xe8c7b756, 12) + STEP(F, c, d, a, b, SET(2), 0x242070db, 17) + STEP(F, b, c, d, a, SET(3), 0xc1bdceee, 22) + STEP(F, a, b, c, d, SET(4), 0xf57c0faf, 7) + STEP(F, d, a, b, c, SET(5), 0x4787c62a, 12) + STEP(F, c, d, a, b, SET(6), 0xa8304613, 17) + STEP(F, b, c, d, a, SET(7), 0xfd469501, 22) + STEP(F, a, b, c, d, SET(8), 0x698098d8, 7) + STEP(F, d, a, b, c, SET(9), 0x8b44f7af, 12) + STEP(F, c, d, a, b, SET(10), 0xffff5bb1, 17) + STEP(F, b, c, d, a, SET(11), 0x895cd7be, 22) + STEP(F, a, b, c, d, SET(12), 0x6b901122, 7) + STEP(F, d, a, b, c, SET(13), 0xfd987193, 12) + STEP(F, c, d, a, b, SET(14), 0xa679438e, 17) + STEP(F, b, c, d, a, SET(15), 0x49b40821, 22) + + /* Round 2 */ + STEP(G, a, b, c, d, GET(1), 0xf61e2562, 5) + STEP(G, d, a, b, c, GET(6), 0xc040b340, 9) + STEP(G, c, d, a, b, GET(11), 0x265e5a51, 14) + STEP(G, b, c, d, a, GET(0), 0xe9b6c7aa, 20) + STEP(G, a, b, c, d, GET(5), 0xd62f105d, 5) + STEP(G, d, a, b, c, GET(10), 0x02441453, 9) + STEP(G, c, d, a, b, GET(15), 0xd8a1e681, 14) + STEP(G, b, c, d, a, GET(4), 0xe7d3fbc8, 20) + STEP(G, a, b, c, d, GET(9), 0x21e1cde6, 5) + STEP(G, d, a, b, c, GET(14), 0xc33707d6, 9) + STEP(G, c, d, a, b, GET(3), 0xf4d50d87, 14) + STEP(G, b, c, d, a, GET(8), 0x455a14ed, 20) + STEP(G, a, b, c, d, GET(13), 0xa9e3e905, 5) + STEP(G, d, a, b, c, GET(2), 0xfcefa3f8, 9) + STEP(G, c, d, a, b, GET(7), 0x676f02d9, 14) + STEP(G, b, c, d, a, GET(12), 0x8d2a4c8a, 20) + + /* Round 3 */ + STEP(H, a, b, c, d, GET(5), 0xfffa3942, 4) + STEP(H2, d, a, b, c, GET(8), 0x8771f681, 11) + STEP(H, c, d, a, b, GET(11), 0x6d9d6122, 16) + STEP(H2, b, c, d, a, GET(14), 0xfde5380c, 23) + STEP(H, a, b, c, d, GET(1), 0xa4beea44, 4) + STEP(H2, d, a, b, c, GET(4), 0x4bdecfa9, 11) + STEP(H, c, d, a, b, GET(7), 0xf6bb4b60, 16) + STEP(H2, b, c, d, a, GET(10), 0xbebfbc70, 23) + STEP(H, a, b, c, d, GET(13), 0x289b7ec6, 4) + STEP(H2, d, a, b, c, GET(0), 0xeaa127fa, 11) + STEP(H, c, d, a, b, GET(3), 0xd4ef3085, 16) + STEP(H2, b, c, d, a, GET(6), 0x04881d05, 23) + STEP(H, a, b, c, d, GET(9), 0xd9d4d039, 4) + STEP(H2, d, a, b, c, GET(12), 0xe6db99e5, 11) + STEP(H, c, d, a, b, GET(15), 0x1fa27cf8, 16) + STEP(H2, b, c, d, a, GET(2), 0xc4ac5665, 23) + + /* Round 4 */ + STEP(I, a, b, c, d, GET(0), 0xf4292244, 6) + STEP(I, d, a, b, c, GET(7), 0x432aff97, 10) + STEP(I, c, d, a, b, GET(14), 0xab9423a7, 15) + STEP(I, b, c, d, a, GET(5), 0xfc93a039, 21) + STEP(I, a, b, c, d, GET(12), 0x655b59c3, 6) + STEP(I, d, a, b, c, GET(3), 0x8f0ccc92, 10) + STEP(I, c, d, a, b, GET(10), 0xffeff47d, 15) + STEP(I, b, c, d, a, GET(1), 0x85845dd1, 21) + STEP(I, a, b, c, d, GET(8), 0x6fa87e4f, 6) + STEP(I, d, a, b, c, GET(15), 0xfe2ce6e0, 10) + STEP(I, c, d, a, b, GET(6), 0xa3014314, 15) + STEP(I, b, c, d, a, GET(13), 0x4e0811a1, 21) + STEP(I, a, b, c, d, GET(4), 0xf7537e82, 6) + STEP(I, d, a, b, c, GET(11), 0xbd3af235, 10) + STEP(I, c, d, a, b, GET(2), 0x2ad7d2bb, 15) + STEP(I, b, c, d, a, GET(9), 0xeb86d391, 21) + + a += saved_a; + b += saved_b; + c += saved_c; + d += saved_d; + + ptr += 64; + } while (size -= 64); + + ctx->a = a; + ctx->b = b; + ctx->c = c; + ctx->d = d; + + return ptr; +} + +void MD5_Init(MD5_CTX *ctx) +{ + ctx->a = 0x67452301; + ctx->b = 0xefcdab89; + ctx->c = 0x98badcfe; + ctx->d = 0x10325476; + + ctx->lo = 0; + ctx->hi = 0; +} + +void MD5_Update(MD5_CTX *ctx, const void *data, unsigned long size) +{ + MD5_u32plus saved_lo; + unsigned long used, available; + + saved_lo = ctx->lo; + if ((ctx->lo = (saved_lo + size) & 0x1fffffff) < saved_lo) + ctx->hi++; + ctx->hi += size >> 29; + + used = saved_lo & 0x3f; + + if (used) { + available = 64 - used; + + if (size < available) { + memcpy(&ctx->buffer[used], data, size); + return; + } + + memcpy(&ctx->buffer[used], data, available); + data = (const unsigned char *)data + available; + size -= available; + body(ctx, ctx->buffer, 64); + } + + if (size >= 64) { + data = body(ctx, data, size & ~(unsigned long)0x3f); + size &= 0x3f; + } + + memcpy(ctx->buffer, data, size); +} + +#define OUT_MD5(dst, src) \ + (dst)[0] = (unsigned char)(src); \ + (dst)[1] = (unsigned char)((src) >> 8); \ + (dst)[2] = (unsigned char)((src) >> 16); \ + (dst)[3] = (unsigned char)((src) >> 24); + +void MD5_Final(unsigned char *result, MD5_CTX *ctx) +{ + unsigned long used, available; + + used = ctx->lo & 0x3f; + + ctx->buffer[used++] = 0x80; + + available = 64 - used; + + if (available < 8) + { + memset(&ctx->buffer[used], 0, available); + body(ctx, ctx->buffer, 64); + used = 0; + available = 64; + } + + memset(&ctx->buffer[used], 0, available - 8); + + ctx->lo <<= 3; + OUT_MD5(&ctx->buffer[56], ctx->lo) + OUT_MD5(&ctx->buffer[60], ctx->hi) + + body(ctx, ctx->buffer, 64); + + OUT_MD5(&result[0], ctx->a) + OUT_MD5(&result[4], ctx->b) + OUT_MD5(&result[8], ctx->c) + OUT_MD5(&result[12], ctx->d) + + memset(ctx, 0, sizeof(*ctx)); +} + +#endif + +// HMAC-MD5 + +void hmacMD5_init_rfc2104(const unsigned char* key, int key_len, HMACMD5Ctx *ctx) +{ + int i; + memset(ctx, 0, sizeof(HMACMD5Ctx)); + /* if key is longer than 64 bytes reset it to key=MD5(key) */ + if (key_len > 64) + { + unsigned char tk[16]; + MD5_CTX tctx; + + MD5_Init(&tctx); + MD5_Update(&tctx, key, key_len); + MD5_Final(tk, &tctx); + + key = tk; + key_len = 16; + } + + /* start out by storing key in pads */ + memcpy(ctx->k_ipad, key, key_len); + memcpy(ctx->k_opad, key, key_len); + + /* XOR key with ipad and opad values */ + for (i = 0; i < 64; i++) + { + ctx->k_ipad[i] ^= 0x36; + ctx->k_opad[i] ^= 0x5c; + } + + MD5_Init(&ctx->ctx); + MD5_Update(&ctx->ctx, ctx->k_ipad, 64); +} + +void hmacMD5_init_limK_to_64(const unsigned char* key, int key_len, HMACMD5Ctx *ctx) +{ + if (key_len > 64) + { + key_len = 64; + } + hmacMD5_init_rfc2104(key, key_len, ctx); +} + +void hmacMD5_update(const unsigned char* text, int text_len, HMACMD5Ctx *ctx) +{ + MD5_Update(&ctx->ctx, text, text_len); +} + +void hmacMD5_final(unsigned char* digest, HMACMD5Ctx *ctx) +{ + MD5_CTX ctx_o; + MD5_Final(digest, &ctx->ctx); + + MD5_Init(&ctx_o); + MD5_Update(&ctx_o, ctx->k_opad, 64); + MD5_Update(&ctx_o, digest, 16); + MD5_Final(digest, &ctx_o); +} + +void hmacMD5(const unsigned char* key, int keyLen, const unsigned char* text, int textLen, unsigned char* digest) +{ + HMACMD5Ctx ctx; + hmacMD5_init_limK_to_64(key, keyLen, &ctx); + hmacMD5_update(text, textLen, &ctx); + hmacMD5_final(digest, &ctx); +} \ No newline at end of file diff --git a/src/util/crypto/md5.h b/src/util/crypto/md5.h new file mode 100644 index 00000000..6c93e16d --- /dev/null +++ b/src/util/crypto/md5.h @@ -0,0 +1,60 @@ +/* +* This is an OpenSSL-compatible implementation of the RSA Data Security, Inc. +* MD5 Message-Digest Algorithm (RFC 1321). +* +* Homepage: +* http://openwall.info/wiki/people/solar/software/public-domain-source-code/md5 +* +* Author: +* Alexander Peslyak, better known as Solar Designer +* +* This software was written by Alexander Peslyak in 2001. No copyright is +* claimed, and the software is hereby placed in the public domain. +* In case this attempt to disclaim copyright and place the software in the +* public domain is deemed null and void, then the software is +* Copyright (c) 2001 Alexander Peslyak and it is hereby released to the +* general public under the following terms: +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted. +* +* There's ABSOLUTELY NO WARRANTY, express or implied. +* +* See md5.c for more information. +*/ + +#ifdef HAVE_OPENSSL +#include +#elif !defined(_MD5_H) +#define _MD5_H + +/* Any 32-bit or wider unsigned integer data type will do */ +typedef unsigned int MD5_u32plus; + +typedef struct +{ + MD5_u32plus lo, hi; + MD5_u32plus a, b, c, d; + unsigned char buffer[64]; + MD5_u32plus block[16]; +}MD5_CTX; + +extern void MD5_Init(MD5_CTX *ctx); +extern void MD5_Update(MD5_CTX *ctx, const void *data, unsigned long size); +extern void MD5_Final(unsigned char *result, MD5_CTX *ctx); + +#endif + +// HMAC-MD5 + +typedef struct +{ + MD5_CTX ctx; + unsigned char k_ipad[64]; + unsigned char k_opad[64]; +}HMACMD5Ctx; + +void hmacMD5_init_limK_to_64(const unsigned char* key, int key_len, HMACMD5Ctx *ctx); +void hmacMD5_update(const unsigned char* text, int text_len, HMACMD5Ctx *ctx); +void hmacMD5_final(unsigned char* digest, HMACMD5Ctx *ctx); +void hmacMD5(const unsigned char* key, int keyLen, const unsigned char* text, int textLen, unsigned char* digest); \ No newline at end of file diff --git a/src/util/helpers/ClassWrapper.h b/src/util/helpers/ClassWrapper.h new file mode 100644 index 00000000..0bbd342f --- /dev/null +++ b/src/util/helpers/ClassWrapper.h @@ -0,0 +1,52 @@ +#pragma once + +#include +#include + +template +class SingletonClass +{ +public: + static T* getInstance() + { + static T instance; + return &instance; + } + +protected: + SingletonClass() = default; +}; + +template +class SingletonRef +{ +public: + /*static std::shared_ptr getInstance() C++20 only + { + static std::atomic> s_instance; + std::shared_ptr result; + s_instance.compare_exchange_weak(result, std::make_shared()); + return result; + }*/ + + static std::shared_ptr getInstance() + { + std::scoped_lock lock(s_mutex); + + auto result = s_instance.lock(); + if(!result) + { + result = std::make_shared(); + s_instance = result; + } + + return result; + } + +protected: + SingletonRef() = default; + +private: + static inline std::weak_ptr s_instance; + static inline std::mutex s_mutex; +}; \ No newline at end of file diff --git a/src/util/helpers/ConcurrentQueue.h b/src/util/helpers/ConcurrentQueue.h new file mode 100644 index 00000000..2e7347b7 --- /dev/null +++ b/src/util/helpers/ConcurrentQueue.h @@ -0,0 +1,105 @@ +#pragma once + +#include +#include +#include + +template +class ConcurrentQueue +{ +public: + ConcurrentQueue() = default; + ConcurrentQueue(const ConcurrentQueue&) = delete; + ConcurrentQueue& operator=(const ConcurrentQueue&) = delete; + + size_t push(const T& item) + { + std::unique_lock mlock(m_mutex); + m_queue.push(item); + const size_t result = m_queue.size(); + mlock.unlock(); + m_condVar.notify_one(); + return result; + } + + template + size_t push(Args&& ... args) + { + std::unique_lock mlock(m_mutex); + m_queue.emplace(std::forward(args)...); + const size_t result = m_queue.size(); + mlock.unlock(); + m_condVar.notify_one(); + return result; + } + + T peek() + { + std::unique_lock mlock(m_mutex); + while (m_queue.empty()) + { + m_condVar.wait(mlock); + } + + return m_queue.front(); + } + + bool peek2(T& item) + { + std::unique_lock mlock(m_mutex); + if (m_queue.empty()) + return false; + item = m_queue.front(); + m_queue.pop(); + return true; + } + + T pop() + { + std::unique_lock mlock(m_mutex); + while (m_queue.empty()) + { + m_condVar.wait(mlock); + } + + auto val = m_queue.front(); + m_queue.pop(); + return val; + } + + void pop(T& item) + { + std::unique_lock mlock(m_mutex); + while (m_queue.empty()) + { + m_condVar.wait(mlock); + } + + item = m_queue.front(); + m_queue.pop(); + } + + void clear() + { + std::unique_lock mlock(m_mutex); + while (!m_queue.empty()) + m_queue.pop(); + } + + size_t size() + { + std::unique_lock mlock(m_mutex); + return m_queue.size(); + } + + bool empty() + { + std::unique_lock mlock(m_mutex); + return m_queue.empty(); + } + +private: + std::mutex m_mutex; + std::condition_variable m_condVar; + std::queue m_queue; +}; diff --git a/src/util/helpers/MapAdaptor.h b/src/util/helpers/MapAdaptor.h new file mode 100644 index 00000000..71549633 --- /dev/null +++ b/src/util/helpers/MapAdaptor.h @@ -0,0 +1,35 @@ +#pragma once + +// https://ideone.com/k0H8Ei + +#include +#include +#include + +template +struct map_adaptor +{ + map_adaptor(const T& t, const F& f) : _t(t), _f(f) {} + map_adaptor(map_adaptor& a) = delete; + map_adaptor(map_adaptor&& a) = default; + + [[nodiscard]] auto begin() { return boost::make_transform_iterator(_t.begin(), _f); } + [[nodiscard]] auto end() { return boost::make_transform_iterator(_t.end(), _f); } + + [[nodiscard]] auto cbegin() const { return boost::make_transform_iterator(_t.cbegin(), _f); } + [[nodiscard]] auto cend() const { return boost::make_transform_iterator(_t.cend(), _f); } + +protected: + const T& _t; + F _f; +}; + + +template +auto get_map_adaptor(const T& t, const F& f) { return map_adaptor(t, f); } + +template +auto get_keys(const T& t) { return get_map_adaptor(t, [](const auto& p) { return p.first; }); } + +template +auto get_values(const T& t) { return get_map_adaptor(t, [](const auto& p) { return p.second; }); } diff --git a/src/util/helpers/MemoryPool.h b/src/util/helpers/MemoryPool.h new file mode 100644 index 00000000..b682e981 --- /dev/null +++ b/src/util/helpers/MemoryPool.h @@ -0,0 +1,128 @@ +#pragma once + +template +class MemoryPool +{ + static_assert(sizeof(T) >= sizeof(void*)); // object must be large enough to store a single pointer + +public: + MemoryPool(int allocationGranularity) + : m_numObjectsAllocated(0) + { + m_allocationGranularity = allocationGranularity; + m_nextFreeObject = nullptr; + } + + template + T* allocObj(Ts&&... args) + { + if (m_nextFreeObject) + { + T* allocatedObj = m_nextFreeObject; + m_nextFreeObject = *(T**)allocatedObj; + new (allocatedObj) T(std::forward(args)...); + return allocatedObj; + } + // enlarge pool + increasePoolSize(); + T* allocatedObj = m_nextFreeObject; + m_nextFreeObject = *(T**)allocatedObj; + new (allocatedObj) T(std::forward(args)...); + return allocatedObj; + } + + void freeObj(T* obj) + { + obj->~T(); + pushElementOnFreeStack(obj); + } + +private: + void pushElementOnFreeStack(T* obj) + { + *(T**)obj = m_nextFreeObject; + m_nextFreeObject = obj; + } + + void increasePoolSize() + { + T* newElements = static_cast(::operator new(m_allocationGranularity * sizeof(T), std::nothrow)); + m_numObjectsAllocated += m_allocationGranularity; + for (int i = 0; i < m_allocationGranularity; i++) + { + pushElementOnFreeStack(newElements); + newElements++; + } + } + +private: + T* m_nextFreeObject; + int m_allocationGranularity; + int m_numObjectsAllocated; +}; + +// this memory pool calls the default constructor when the internal pool is allocated +// no constructor/destructor will be called on acquire/release +template +class MemoryPoolPermanentObjects +{ + struct internalObject_t + { + T v; + internalObject_t* next; + }; + +public: + MemoryPoolPermanentObjects(int allocationGranularity) + : m_numObjectsAllocated(0) + { + m_allocationGranularity = allocationGranularity; + m_nextFreeObject = nullptr; + } + + template + T* acquireObj(Ts&&... args) + { + if (m_nextFreeObject) + { + internalObject_t* allocatedObject = m_nextFreeObject; + m_nextFreeObject = allocatedObject->next; + return &allocatedObject->v; + } + // enlarge pool + increasePoolSize(); + internalObject_t* allocatedObject = m_nextFreeObject; + m_nextFreeObject = allocatedObject->next; + return &allocatedObject->v; + } + + void releaseObj(T* obj) + { + internalObject_t* internalObj = (internalObject_t*)((uint8*)obj - offsetof(internalObject_t, v)); + pushElementOnFreeStack(internalObj); + } + +private: + void pushElementOnFreeStack(internalObject_t* obj) + { + obj->next = m_nextFreeObject; + m_nextFreeObject = obj; + } + + void increasePoolSize() + { + internalObject_t* newElements = static_cast(::operator new(m_allocationGranularity * sizeof(internalObject_t), std::nothrow)); + m_numObjectsAllocated += m_allocationGranularity; + for (int i = 0; i < m_allocationGranularity; i++) + { + new (&newElements->v) T(); + pushElementOnFreeStack(newElements); + newElements++; + } + } + +private: + internalObject_t* m_nextFreeObject; + int m_allocationGranularity; + int m_numObjectsAllocated; +}; \ No newline at end of file diff --git a/src/util/helpers/Semaphore.h b/src/util/helpers/Semaphore.h new file mode 100644 index 00000000..46761b82 --- /dev/null +++ b/src/util/helpers/Semaphore.h @@ -0,0 +1,171 @@ +#pragma once + +#include +#include + +class Semaphore +{ +public: + void notify() + { + std::lock_guard lock(m_mutex); + ++m_count; + m_condition.notify_one(); + } + + void wait() + { + std::unique_lock lock(m_mutex); + while (m_count == 0) + { + m_condition.wait(lock); + } + + --m_count; + } + + bool try_wait() + { + std::lock_guard lock(m_mutex); + if (m_count == 0) + return false; + + --m_count; + return true; + } + + void reset() + { + std::lock_guard lock(m_mutex); + m_count = 0; + } + +private: + std::mutex m_mutex; + std::condition_variable m_condition; + uint64 m_count = 0; +}; + +class CounterSemaphore +{ +public: + void reset() + { + std::lock_guard lock(m_mutex); + m_count = 0; + } + + void increment() + { + std::lock_guard lock(m_mutex); + ++m_count; + if (m_count == 1) + m_condition.notify_all(); + } + + void decrement() + { + std::lock_guard lock(m_mutex); + --m_count; + cemu_assert_debug(m_count >= 0); + if (m_count == 0) + m_condition.notify_all(); + } + + // decrement only if non-zero + // otherwise wait + void decrementWithWait() + { + std::unique_lock lock(m_mutex); + while (m_count == 0) + m_condition.wait(lock); + m_count--; + } + + // decrement only if non-zero + // otherwise wait + // may wake up spuriously + bool decrementWithWaitAndTimeout(uint32 ms) + { + std::unique_lock lock(m_mutex); + if (m_count == 0) + { + m_condition.wait_for(lock, std::chrono::milliseconds(ms)); + } + if (m_count == 0) + return false; + m_count--; + return true; + } + + void waitUntilZero() + { + std::unique_lock lock(m_mutex); + while (m_count != 0) + m_condition.wait(lock); + } + + void waitUntilNonZero() + { + std::unique_lock lock(m_mutex); + while (m_count == 0) + m_condition.wait(lock); + } + + bool isZero() const + { + return m_count == 0; + } + +private: + std::mutex m_mutex; + std::condition_variable m_condition; + sint64 m_count = 0; +}; + +template +class StateSemaphore +{ +public: + StateSemaphore(T initialState) : m_state(initialState) {}; + + T getValue() + { + std::unique_lock lock(m_mutex); + return m_state; + } + + bool hasState(T state) + { + std::unique_lock lock(m_mutex); + return m_state == state; + } + + void setValue(T newState) + { + std::unique_lock lock(m_mutex); + m_state = newState; + m_condition.notify_all(); + } + + void setValue(T newState, T expectedValue) + { + std::unique_lock lock(m_mutex); + while (m_state != expectedValue) + m_condition.wait(lock); + m_state = newState; + m_condition.notify_all(); + } + + void waitUntilValue(T state) + { + std::unique_lock lock(m_mutex); + while (m_state != state) + m_condition.wait(lock); + } + +private: + std::mutex m_mutex; + std::condition_variable m_condition; + T m_state; +}; \ No newline at end of file diff --git a/src/util/helpers/Serializer.h b/src/util/helpers/Serializer.h new file mode 100644 index 00000000..a15adfad --- /dev/null +++ b/src/util/helpers/Serializer.h @@ -0,0 +1,353 @@ +#pragma once + +class MemStreamReader +{ +public: + MemStreamReader(const uint8* data, sint32 size) : m_data(data), m_size(size) + { + m_cursorPos = 0; + } + + template T readBE(); + template T readLE(); + + template<> uint8 readBE() + { + if (!reserveReadLength(sizeof(uint8))) + return 0; + uint8 v = m_data[m_cursorPos]; + m_cursorPos += sizeof(uint8); + return v; + } + + template<> uint16 readBE() + { + if (!reserveReadLength(sizeof(uint16))) + return 0; + const uint8* p = m_data + m_cursorPos; + uint16 v; + std::memcpy(&v, p, sizeof(v)); + v = _BE(v); + m_cursorPos += sizeof(uint16); + return v; + } + + template<> uint32 readBE() + { + if (!reserveReadLength(sizeof(uint32))) + return 0; + const uint8* p = m_data + m_cursorPos; + uint32 v; + std::memcpy(&v, p, sizeof(v)); + v = _BE(v); + m_cursorPos += sizeof(uint32); + return v; + } + + template<> uint64 readBE() + { + if (!reserveReadLength(sizeof(uint64))) + return 0; + const uint8* p = m_data + m_cursorPos; + uint64 v; + std::memcpy(&v, p, sizeof(v)); + v = _BE(v); + m_cursorPos += sizeof(uint64); + return v; + } + + template<> std::string readBE() + { + std::string s; + uint32 stringSize = readBE(); + if (hasError()) + return s; + if (stringSize >= (32 * 1024 * 1024)) + { + // out of bounds read or suspiciously large string + m_hasError = true; + return std::string(); + } + s.resize(stringSize); + readData(s.data(), stringSize); + return s; + } + + template<> uint8 readLE() + { + return readBE(); + } + + template<> uint32 readLE() + { + if (!reserveReadLength(sizeof(uint32))) + return 0; + const uint8* p = m_data + m_cursorPos; + uint32 v; + std::memcpy(&v, p, sizeof(v)); + v = _LE(v); + m_cursorPos += sizeof(uint32); + return v; + } + + template<> uint64 readLE() + { + if (!reserveReadLength(sizeof(uint64))) + return 0; + const uint8* p = m_data + m_cursorPos; + uint64 v; + std::memcpy(&v, p, sizeof(v)); + v = _LE(v); + m_cursorPos += sizeof(uint64); + return v; + } + + template + std::vector readPODVector() + { + uint32 numElements = readBE(); + if (hasError()) + { + return std::vector(); + } + std::vector v; + v.reserve(numElements); + v.resize(numElements); + readData(v.data(), v.size() * sizeof(T)); + return v; + } + + // read string terminated by newline character (or end of stream) + // will also trim off any carriage return + std::string_view readLine() + { + size_t length = 0; + if (m_cursorPos >= m_size) + { + m_hasError = true; + return std::basic_string_view((const char*)nullptr, 0); + } + // end of line is determined by '\n' + const char* lineStrBegin = (const char*)(m_data + m_cursorPos); + const char* lineStrEnd = nullptr; + while (m_cursorPos < m_size) + { + if (m_data[m_cursorPos] == '\n') + { + lineStrEnd = (const char*)(m_data + m_cursorPos); + m_cursorPos++; // skip the newline character + break; + } + m_cursorPos++; + } + if(lineStrEnd == nullptr) + lineStrEnd = (const char*)(m_data + m_cursorPos); + // truncate any '\r' at the beginning and end + while (lineStrBegin < lineStrEnd) + { + if (lineStrBegin[0] != '\r') + break; + lineStrBegin++; + } + while (lineStrEnd > lineStrBegin) + { + if (lineStrEnd[-1] != '\r') + break; + lineStrEnd--; + } + length = (lineStrEnd - lineStrBegin); + return std::basic_string_view((const char*)lineStrBegin, length); + } + + bool readData(void* ptr, size_t size) + { + if (m_cursorPos + size > m_size) + { + m_cursorPos = m_size; + m_hasError = true; + return false; + } + memcpy(ptr, m_data + m_cursorPos, size); + m_cursorPos += (sint32)size; + return true; + } + + std::span readDataNoCopy(size_t size) + { + if (m_cursorPos + size > m_size) + { + m_cursorPos = m_size; + m_hasError = true; + return std::span(); + } + auto r = std::span((uint8*)m_data + m_cursorPos, size); + m_cursorPos += (sint32)size; + return r; + } + + // returns true if any of the reads was out of bounds + bool hasError() const + { + return m_hasError; + } + + bool isEndOfStream() const + { + return m_cursorPos == m_size; + } + +private: + bool reserveReadLength(size_t length) + { + if (m_cursorPos + length > m_size) + { + m_cursorPos = m_size; + m_hasError = true; + return false; + } + return true; + } + + void skipCRLF() + { + while (m_cursorPos < m_size) + { + if (m_data[m_cursorPos] != '\r' && m_data[m_cursorPos] != '\n') + break; + m_cursorPos++; + } + } + + const uint8* m_data; + sint32 m_size; + sint32 m_cursorPos; + bool m_hasError{ false }; +}; + +class MemStreamWriter +{ +public: + MemStreamWriter(size_t reservedSize) + { + if (reservedSize > 0) + m_buffer.reserve(reservedSize); + else + m_buffer.reserve(128); + } + + void writeData(const void* ptr, size_t size) + { + m_buffer.resize(m_buffer.size() + size); + uint8* p = m_buffer.data() + m_buffer.size() - size; + memcpy(p, ptr, size); + } + + template void writeBE(const T& v); + + template<> + void writeBE(const uint64& v) + { + m_buffer.resize(m_buffer.size() + 8); + uint8* p = m_buffer.data() + m_buffer.size() - 8; + uint64 tmp = _BE(v); + std::memcpy(p, &tmp, sizeof(tmp)); + } + + template<> + void writeBE(const uint32& v) + { + m_buffer.resize(m_buffer.size() + 4); + uint8* p = m_buffer.data() + m_buffer.size() - 4; + uint32 tmp = _BE(v); + std::memcpy(p, &tmp, sizeof(tmp)); + } + + template<> + void writeBE(const uint16& v) + { + m_buffer.resize(m_buffer.size() + 2); + uint8* p = m_buffer.data() + m_buffer.size() - 2; + uint16 tmp = _BE(v); + std::memcpy(p, &tmp, sizeof(tmp)); + } + + template<> + void writeBE(const uint8& v) + { + m_buffer.emplace_back(v); + } + + template<> + void writeBE(const std::string& v) + { + writeBE((uint32)v.size()); + writeData(v.data(), v.size()); + } + + template void writeLE(const T& v); + + template<> + void writeLE(const uint64& v) + { + m_buffer.resize(m_buffer.size() + 8); + uint8* p = m_buffer.data() + m_buffer.size() - 8; + uint64 tmp = _LE(v); + std::memcpy(p, &tmp, sizeof(tmp)); + } + + template<> + void writeLE(const uint32& v) + { + m_buffer.resize(m_buffer.size() + 4); + uint8* p = m_buffer.data() + m_buffer.size() - 4; + uint32 tmp = _LE(v); + std::memcpy(p, &tmp, sizeof(tmp)); + } + + template + void writePODVector(const std::vector& v) + { + writeBE(v.size()); + writeData(v.data(), v.size() * sizeof(T)); + } + + // get result buffer without copy + // resets internal state + void getResultAndReset(std::vector& data) + { + std::swap(m_buffer, data); + m_buffer.clear(); + } + + std::span getResult() + { + return std::span(m_buffer.data(), m_buffer.size()); + } + +private: + std::vector m_buffer; +}; + +class SerializerHelper +{ +public: + bool serialize(std::vector& data) + { + MemStreamWriter streamWriter(0); + bool r = serializeImpl(streamWriter); + if (!r) + return false; + streamWriter.getResultAndReset(data); + return true; + } + + bool deserialize(std::vector& data) + { + MemStreamReader memStreamReader(data.data(), (sint32)data.size()); + return deserializeImpl(memStreamReader); + } + +protected: + virtual bool serializeImpl(MemStreamWriter& streamWriter) = 0; + virtual bool deserializeImpl(MemStreamReader& streamReader) = 0; +}; \ No newline at end of file diff --git a/src/util/helpers/Singleton.h b/src/util/helpers/Singleton.h new file mode 100644 index 00000000..06b59f03 --- /dev/null +++ b/src/util/helpers/Singleton.h @@ -0,0 +1,17 @@ +#pragma once + +template +class Singleton +{ +protected: + Singleton() = default; + Singleton(const Singleton&) = delete; + Singleton(Singleton&&) noexcept = delete; + +public: + static TType& instance() noexcept + { + static TType s_instance; + return s_instance; + } +}; diff --git a/src/util/helpers/StringBuf.h b/src/util/helpers/StringBuf.h new file mode 100644 index 00000000..3744caa4 --- /dev/null +++ b/src/util/helpers/StringBuf.h @@ -0,0 +1,86 @@ +#pragma once + +class StringBuf +{ +public: + StringBuf(uint32 bufferSize) + { + this->str = (uint8*)malloc(bufferSize + 4); + this->allocated = true; + this->length = 0; + this->limit = bufferSize; + } + + ~StringBuf() + { + if (this->allocated) + free(this->str); + } + + template + void addFmt(const TFmt& format, TArgs&&... args) + { + auto r = fmt::vformat_to_n((char*)(this->str + this->length), (size_t)(this->limit - this->length), fmt::to_string_view(format), fmt::make_args_checked(format, args...)); + this->length += (uint32)r.size; + } + + void add(const char* appendedStr) + { + const char* outputStart = (char*)(this->str + this->length); + char* output = (char*)outputStart; + const char* outputEnd = (char*)(this->str + this->limit - 1); + while (output < outputEnd) + { + char c = *appendedStr; + if (c == '\0') + break; + *output = c; + appendedStr++; + output++; + } + this->length += (uint32)(output - outputStart); + *output = '\0'; + } + + void add(std::string_view appendedStr) + { + size_t remainingLen = this->limit - this->length; + size_t copyLen = appendedStr.size(); + if (remainingLen > copyLen) + copyLen = remainingLen; + char* outputStart = (char*)(this->str + this->length); + std::copy(appendedStr.data(), appendedStr.data() + copyLen, outputStart); + outputStart[copyLen] = '\0'; + } + + void reset() + { + length = 0; + } + + uint32 getLen() const + { + return length; + } + + const char* c_str() const + { + str[length] = '\0'; + return (const char*)str; + } + + void shrink_to_fit() + { + if (!this->allocated) + return; + uint32 newLimit = this->length; + this->str = (uint8*)realloc(this->str, newLimit + 4); + this->limit = newLimit; + } + +private: + uint8* str; + uint32 length; /* in bytes */ + uint32 limit; /* in bytes */ + bool allocated; +}; \ No newline at end of file diff --git a/src/util/helpers/StringHelpers.h b/src/util/helpers/StringHelpers.h new file mode 100644 index 00000000..cfe63ead --- /dev/null +++ b/src/util/helpers/StringHelpers.h @@ -0,0 +1,112 @@ +#pragma once +#include "boost/nowide/convert.hpp" +#include + +namespace StringHelpers +{ + // convert Wii U big-endian wchar_t string to utf8 string + static std::string ToUtf8(const uint16be* ptr, size_t maxLength) + { + std::wstringstream result; + while (*ptr != 0 && maxLength > 0) + { + auto c = (uint16)*ptr; + result << static_cast(c); + ptr++; + maxLength--; + } + return boost::nowide::narrow(result.str()); + } + + static std::string ToUtf8(std::span input) + { + return ToUtf8(input.data(), input.size()); + } + + // convert utf8 string to Wii U big-endian wchar_t string + static std::basic_string FromUtf8(std::string_view str) + { + std::basic_string tmpStr; + std::wstring w = boost::nowide::widen(str.data(), str.size()); + for (auto& c : w) + tmpStr.push_back((uint16)c); + return tmpStr; + } + + static sint32 ToInt(const std::string_view& input, sint32 defaultValue = 0) + { + sint32 value = defaultValue; + if (input.size() >= 2 && (input[0] == '0' && (input[1] == 'x' || input[1] == 'X'))) + { + // hex number + const std::from_chars_result result = std::from_chars(input.data() + 2, input.data() + input.size(), value, 16); + if (result.ec == std::errc::invalid_argument || result.ec == std::errc::result_out_of_range) + return defaultValue; + } + else + { + // decimal value + const std::from_chars_result result = std::from_chars(input.data(), input.data() + input.size(), value); + if (result.ec == std::errc::invalid_argument || result.ec == std::errc::result_out_of_range) + return defaultValue; + } + return value; + } + + static sint64 ToInt64(const std::string_view& input, sint64 defaultValue = 0) + { + sint64 value = defaultValue; + if (input.size() >= 2 && (input[0] == '0' && (input[1] == 'x' || input[1] == 'X'))) + { + // hex number + const std::from_chars_result result = std::from_chars(input.data() + 2, input.data() + input.size(), value, 16); + if (result.ec == std::errc::invalid_argument || result.ec == std::errc::result_out_of_range) + return defaultValue; + } + else + { + // decimal value + const std::from_chars_result result = std::from_chars(input.data(), input.data() + input.size(), value); + if (result.ec == std::errc::invalid_argument || result.ec == std::errc::result_out_of_range) + return defaultValue; + } + return value; + } + + static size_t ParseHexString(std::string_view input, uint8* output, size_t maxOutputLength) + { + size_t parsedLen = 0; + for (size_t i = 0; i < input.size() - 1; i += 2) + { + if (maxOutputLength <= 0) + break; + uint8 b = 0; + uint8 c = input[i + 0]; + // high nibble + if (c >= '0' && c <= '9') + b |= ((c - '0') << 4); + else if (c >= 'a' && c <= 'f') + b |= ((c - 'a' + 10) << 4); + else if (c >= 'A' && c <= 'F') + b |= ((c - 'A' + 10) << 4); + else + break; + // low nibble + c = input[i + 1]; + if (c >= '0' && c <= '9') + b |= (c - '0'); + else if (c >= 'a' && c <= 'f') + b |= (c - 'a' + 10); + else if (c >= 'A' && c <= 'F') + b |= (c - 'A' + 10); + else + break; + *output = b; + output++; + maxOutputLength--; + parsedLen++; + } + return parsedLen; + } +}; + diff --git a/src/util/helpers/StringParser.h b/src/util/helpers/StringParser.h new file mode 100644 index 00000000..82db9ce0 --- /dev/null +++ b/src/util/helpers/StringParser.h @@ -0,0 +1,252 @@ +#pragma once + +class StringTokenParser +{ +public: + StringTokenParser() : m_str(nullptr), m_len(0) {}; + + StringTokenParser(const char* input, sint32 inputLen) : m_str(input), m_len(inputLen) {}; + StringTokenParser(std::string_view str) : m_str(str.data()), m_len((sint32)str.size()) {}; + + // skip whitespaces at current ptr position + void skipWhitespaces() + { + m_str = _skipWhitespaces(m_str, m_len); + } + + // decrease string length as long as there is a whitespace at the end + void trimWhitespaces() + { + while (m_len > 0) + { + const char c = m_str[m_len - 1]; + if (c != ' ' && c != '\t') + break; + m_len--; + } + } + + bool isEndOfString() + { + return m_len <= 0; + } + + sint32 skipToCharacter(const char c) + { + auto str = m_str; + auto len = m_len; + sint32 idx = 0; + while (len > 0) + { + if (*str == c) + { + m_str = str; + m_len = len; + return idx; + } + len--; + str++; + idx++; + } + return -1; + } + + bool matchWordI(const char* word) + { + auto str = m_str; + auto length = m_len; + str = _skipWhitespaces(str, length); + for (sint32 i = 0; i <= length; i++) + { + if (word[i] == '\0') + { + m_str = str + i; + m_len = length - i; + return true; + } + if (i == length) + return false; + + char c1 = str[i]; + char c2 = word[i]; + c1 = _toUpperCase(c1); + c2 = _toUpperCase(c2); + if (c1 != c2) + return false; + } + return false; + } + + bool compareCharacter(sint32 relativeIndex, const char c) + { + if (relativeIndex >= m_len) + return false; + return m_str[relativeIndex] == c; + } + + bool compareCharacterI(sint32 relativeIndex, const char c) + { + if (relativeIndex >= m_len) + return false; + return _toUpperCase(m_str[relativeIndex]) == _toUpperCase(c); + } + + void skipCharacters(sint32 count) + { + if (count > m_len) + count = m_len; + m_str += count; + m_len -= count; + } + + bool parseU32(uint32& val) + { + auto str = m_str; + auto length = m_len; + str = _skipWhitespaces(str, length); + uint32 value = 0; + sint32 index = 0; + bool isHex = false; + if (length <= 0) + return false; + if (length >= 2 && str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) + { + isHex = true; + index += 2; + } + else if (str[index] == '0') + { + isHex = true; + index++; + } + if (length <= index) + return false; + if (isHex) + { + sint32 firstDigitIndex = index; + for (; index < length; index++) + { + const char c = str[index]; + if (c >= '0' && c <= '9') + { + value *= 0x10; + value += (c - '0'); + } + else if (c >= 'a' && c <= 'f') + { + value *= 0x10; + value += (c - 'a' + 10); + } + else if (c >= 'A' && c <= 'F') + { + value *= 0x10; + value += (c - 'A' + 10); + } + else + break; + } + if (index == firstDigitIndex) + return false; + m_str = str + index; + m_len = length - index; + } + else + { + sint32 firstDigitIndex = index; + for (; index < length; index++) + { + const char c = str[index]; + if (c >= '0' && c <= '9') + { + value *= 10; + value += (c - '0'); + } + else + break; + } + if (index == firstDigitIndex) + return false; + m_str = str + index; + m_len = length - index; + } + val = value; + return true; + } + + bool parseSymbolName(const char*& symbolStr, sint32& symbolLength) + { + auto str = m_str; + auto length = m_len; + str = _skipWhitespaces(str, length); + // symbols must start with a letter or _ + if (length <= 0) + return false; + if (!(str[0] >= 'a' && str[0] <= 'z') && + !(str[0] >= 'A' && str[0] <= 'Z') && + !(str[0] == '_')) + return false; + sint32 idx = 1; + while (idx < length) + { + const char c = str[idx]; + if (!(c >= 'a' && c <= 'z') && + !(c >= 'A' && c <= 'Z') && + !(c >= '0' && c <= '9') && + !(c == '_') && !(c == '.')) + break; + idx++; + } + symbolStr = str; + symbolLength = idx; + m_str = str + idx; + m_len = length - idx; + return true; + } + + const char* getCurrentPtr() + { + return m_str; + } + + sint32 getCurrentLen() + { + return m_len; + } + + void storeParserState(StringTokenParser* bak) + { + bak->m_str = m_str; + bak->m_len = m_len; + } + + void restoreParserState(const StringTokenParser* bak) + { + m_str = bak->m_str; + m_len = bak->m_len; + } + + private: + const char* _skipWhitespaces(const char* str, sint32& length) + { + while (length > 0) + { + if (*str != ' ' && *str != '\t') + break; + str++; + length--; + } + return str; + } + + char _toUpperCase(const char c) + { + if (c >= 'a' && c <= 'z') + return c + ('A' - 'a'); + return c; + } + + private: + const char* m_str; + sint32 m_len; +}; + diff --git a/src/util/helpers/SystemException.h b/src/util/helpers/SystemException.h new file mode 100644 index 00000000..4b13a80a --- /dev/null +++ b/src/util/helpers/SystemException.h @@ -0,0 +1,23 @@ +#pragma once + +#include "util/helpers/helpers.h" + +class SystemException : public std::runtime_error +{ +public: + SystemException() + : std::runtime_error(GetSystemErrorMessage().c_str()), m_error_code(GetExceptionError()) + {} + + SystemException(const std::exception& ex) + : std::runtime_error(GetSystemErrorMessage(ex).c_str()), m_error_code(GetExceptionError()) + {} + + SystemException(const std::error_code& ec) + : std::runtime_error(GetSystemErrorMessage(ec).c_str()), m_error_code(GetExceptionError()) + {} + + [[nodiscard]] DWORD GetErrorCode() const { return m_error_code; } +private: + DWORD m_error_code; +}; diff --git a/src/util/helpers/TempState.h b/src/util/helpers/TempState.h new file mode 100644 index 00000000..a22c2f89 --- /dev/null +++ b/src/util/helpers/TempState.h @@ -0,0 +1,20 @@ +#pragma once + +template +class TempState +{ +public: + TempState(TCtor ctor, TDtor dtor) + : m_dtor(std::move(dtor)) + { + ctor(); + } + + ~TempState() + { + m_dtor(); + } + +private: + TDtor m_dtor; +}; diff --git a/src/util/helpers/enum_array.hpp b/src/util/helpers/enum_array.hpp new file mode 100644 index 00000000..88e0604a --- /dev/null +++ b/src/util/helpers/enum_array.hpp @@ -0,0 +1,15 @@ +#pragma once + +// expects the enum class (T) to have a value called ENUM_COUNT which is the maximum value + 1 + +template +class enum_array : public std::array(E::ENUM_COUNT)> { +public: + T& operator[] (E e) { + return std::array(E::ENUM_COUNT)>::operator[]((std::size_t)e); + } + + const T& operator[] (E e) const { + return std::array(E::ENUM_COUNT)>::operator[]((std::size_t)e); + } +}; \ No newline at end of file diff --git a/src/util/helpers/fixedSizeList.h b/src/util/helpers/fixedSizeList.h new file mode 100644 index 00000000..984c4e39 --- /dev/null +++ b/src/util/helpers/fixedSizeList.h @@ -0,0 +1,74 @@ +#pragma once + +#include + +template +class FixedSizeList +{ +public: + std::array m_elementArray; + int count = 0; + + void add(T n) + { + if (checkMaxSize && count >= maxElements) + return; + m_elementArray[count] = n; + count++; + } + + void addUnique(T n) + { + if (checkMaxSize && count >= maxElements) + return; + for (int i = 0; i < count; i++) + { + if (m_elementArray[i] == n) + return; + } + m_elementArray[count] = n; + count++; + } + + void remove(T n) + { + for (int i = 0; i < count; i++) + { + if (m_elementArray[i] == n) + { + m_elementArray[i] = m_elementArray[count - 1]; + count--; + return; + } + } + } + + bool containsAndRemove(T n) + { + for (int i = 0; i < count; i++) + { + if (m_elementArray[i] == n) + { + m_elementArray[i] = m_elementArray[count - 1]; + count--; + return true; + } + } + return false; + } + + sint32 find(T n) + { + for (int i = 0; i < count; i++) + { + if (m_elementArray[i] == n) + { + return i; + } + } + return -1; + } + +private: +}; + diff --git a/src/util/helpers/fspinlock.h b/src/util/helpers/fspinlock.h new file mode 100644 index 00000000..04f761e7 --- /dev/null +++ b/src/util/helpers/fspinlock.h @@ -0,0 +1,38 @@ +#pragma once + +// minimal but efficient non-recursive spinlock implementation + +#include + +class FSpinlock +{ +public: + void acquire() + { + while( true ) + { + if (!m_lockBool.exchange(true, std::memory_order_acquire)) + break; + while (m_lockBool.load(std::memory_order_relaxed)) _mm_pause(); + } + } + + bool tryAcquire() + { + return !m_lockBool.exchange(true, std::memory_order_acquire); + } + + void release() + { + m_lockBool.store(false, std::memory_order_release); + } + + bool isHolding() const + { + return m_lockBool.load(std::memory_order_relaxed); + } + +private: + + std::atomic m_lockBool = false; +}; \ No newline at end of file diff --git a/src/util/helpers/helpers.cpp b/src/util/helpers/helpers.cpp new file mode 100644 index 00000000..83f2a6c4 --- /dev/null +++ b/src/util/helpers/helpers.cpp @@ -0,0 +1,434 @@ +#include "helpers.h" + +#include +#include +#include +#include + +#include + +#include "config/ActiveSettings.h" + +#if BOOST_OS_WINDOWS +#include +#endif + +std::string& ltrim(std::string& str, const std::string& chars) +{ + str.erase(0, str.find_first_not_of(chars)); + return str; +} + +std::string& rtrim(std::string& str, const std::string& chars) +{ + str.erase(str.find_last_not_of(chars) + 1); + return str; +} + +std::string& trim(std::string& str, const std::string& chars) +{ + return ltrim(rtrim(str, chars), chars); +} + + +std::string_view& ltrim(std::string_view& str, const std::string& chars) +{ + str.remove_prefix(std::min(str.find_first_not_of(chars), str.size())); + return str; +} +std::string_view& rtrim(std::string_view& str, const std::string& chars) +{ + str.remove_suffix(std::max(str.size() - str.find_last_not_of(chars) - 1, (size_t)0)); + return str; +} +std::string_view& trim(std::string_view& str, const std::string& chars) +{ + return ltrim(rtrim(str, chars), chars); +} + +#if BOOST_OS_WINDOWS > 0 + +std::wstring GetSystemErrorMessageW() +{ + return GetSystemErrorMessageW(GetLastError()); +} + + +std::wstring GetSystemErrorMessageW(DWORD error_code) +{ + if(error_code == ERROR_SUCCESS) + return {}; + + LPWSTR lpMsgBuf = nullptr; + FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, error_code, 0, (LPWSTR)&lpMsgBuf, 0, nullptr); + if (lpMsgBuf) + { + std::wstring str = fmt::format(L"{}: {}", _("Error").ToStdWstring(), lpMsgBuf); // TRANSLATE + LocalFree(lpMsgBuf); + return str; + } + + return fmt::format(L"{}: {:#x}", _("Error code").ToStdWstring(), error_code); +} + +std::string GetSystemErrorMessage(DWORD error_code) +{ + if(error_code == ERROR_SUCCESS) + return {}; + + LPSTR lpMsgBuf = nullptr; + FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, error_code, 0, (LPSTR)&lpMsgBuf, 0, nullptr); + if (lpMsgBuf) + { + std::string str = fmt::format("{}: {}", _("Error").ToStdString(), lpMsgBuf); // TRANSLATE + LocalFree(lpMsgBuf); + return str; + } + + return fmt::format("{}: {:#x}", _("Error code").ToStdString(), error_code); +} + +std::string GetSystemErrorMessage() +{ + return GetSystemErrorMessage(GetLastError()); +} + +#else + +std::string GetSystemErrorMessage() +{ + return ""; +} + +#endif + +std::string GetSystemErrorMessage(const std::exception& ex) +{ + const std::string msg = GetSystemErrorMessage(); + if(msg.empty()) + return ex.what(); + + return fmt::format("{}\n{}",msg, ex.what()); +} + +std::string GetSystemErrorMessage(const std::error_code& ec) +{ + const std::string msg = GetSystemErrorMessage(); + if(msg.empty()) + return ec.message(); + + return fmt::format("{}\n{}",msg, ec.message()); +} + +#if BOOST_OS_WINDOWS > 0 +const DWORD MS_VC_EXCEPTION = 0x406D1388; +#pragma pack(push,8) +typedef struct tagTHREADNAME_INFO +{ + DWORD dwType; // Must be 0x1000. + LPCSTR szName; // Pointer to name (in user addr space). + DWORD dwThreadID; // Thread ID (-1=caller thread). + DWORD dwFlags; // Reserved for future use, must be zero. +} THREADNAME_INFO; +#pragma pack(pop) +#endif + +void SetThreadName(const char* name) +{ +#if BOOST_OS_WINDOWS > 0 + +#ifndef _PUBLIC_RELEASE + THREADNAME_INFO info; + info.dwType = 0x1000; + info.szName = name; + info.dwThreadID = GetCurrentThreadId(); + info.dwFlags = 0; +#pragma warning(push) +#pragma warning(disable: 6320 6322) + __try { + RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*)&info); + } + __except (EXCEPTION_EXECUTE_HANDLER) { + } +#pragma warning(pop) + +#endif + +#else + pthread_setname_np(pthread_self(), name); +#endif +} + +#if BOOST_OS_WINDOWS > 0 +std::pair GetWindowsVersion() +{ + using RtlGetVersion_t = LONG(*)(POSVERSIONINFOEXW); + static RtlGetVersion_t pRtlGetVersion = nullptr; + if(!pRtlGetVersion) + pRtlGetVersion = (RtlGetVersion_t)GetProcAddress(GetModuleHandleA("ntdll.dll"), "RtlGetVersion"); + cemu_assert(pRtlGetVersion); + + OSVERSIONINFOEXW version_info{}; + pRtlGetVersion(&version_info); + return { version_info.dwMajorVersion, version_info.dwMinorVersion }; +} + +bool IsWindows81OrGreater() +{ + const auto [major, minor] = GetWindowsVersion(); + return major > 6 || (major == 6 && minor >= 3); +} + +bool IsWindows10OrGreater() +{ + const auto [major, minor] = GetWindowsVersion(); + return major >= 10; +} +#endif + +fs::path GetParentProcess() +{ + fs::path result; + +#if BOOST_OS_WINDOWS + HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if(hSnapshot != INVALID_HANDLE_VALUE) + { + DWORD pid = GetCurrentProcessId(); + + PROCESSENTRY32 pe{}; + pe.dwSize = sizeof(pe); + for(BOOL ret = Process32First(hSnapshot, &pe); ret; ret = Process32Next(hSnapshot, &pe)) + { + if(pe.th32ProcessID == pid) + { + HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pe.th32ParentProcessID); + if(hProcess) + { + wchar_t tmp[MAX_PATH]; + DWORD size = std::size(tmp); + if (QueryFullProcessImageNameW(hProcess, 0, tmp, &size) && size > 0) + result = tmp; + + CloseHandle(hProcess); + } + break; + } + } + + CloseHandle(hSnapshot); + } +#else + assert_dbg(); +#endif + + return result; +} + +std::string ltrim_copy(const std::string& s) +{ + std::string result = s; + ltrim(result); + return result; +} + +std::string rtrim_copy(const std::string& s) +{ + std::string result = s; + rtrim(result); + return result; +} + +uint32_t GetPhysicalCoreCount() +{ + static uint32_t s_core_count = 0; + if (s_core_count != 0) + return s_core_count; + +#if BOOST_OS_WINDOWS + auto core_count = std::thread::hardware_concurrency(); + + // Get physical cores + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION buffer = nullptr; + DWORD returnLength = 0; + GetLogicalProcessorInformation(buffer, &returnLength); + if (returnLength > 0) + { + buffer = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION)malloc(returnLength); + if (GetLogicalProcessorInformation(buffer, &returnLength)) + { + uint32_t counter = 0; + for (DWORD i = 0; i < returnLength / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION); ++i) + { + if (buffer[i].Relationship == RelationProcessorCore) + ++counter; + } + + if (counter > 0 && counter < core_count) + core_count = counter; + } + + free(buffer); + } + + s_core_count = core_count; + return core_count; +#else + return std::thread::hardware_concurrency(); +#endif +} + +bool TestWriteAccess(const fs::path& p) +{ + // must be path and must exist + if (!fs::exists(p) || !fs::is_directory(p)) + return false; + + // retry 3 times + for (int i = 0; i < 3; ++i) + { + const auto filename = p / fmt::format("_{}.tmp", GenerateRandomString(8)); + if (fs::exists(filename)) + continue; + + std::ofstream file(filename); + if (!file.is_open()) // file couldn't be created + break; + + file.close(); + + std::error_code ec; + fs::remove(filename, ec); + return true; + } + + return false; +} + +// make path relative to Cemu directory +fs::path MakeRelativePath(const fs::path& path) +{ + try + { + const fs::path base = ActiveSettings::GetPath(); + return fs::relative(path, base); + } + catch (const std::exception&) + { + return path; + } +} + +#ifdef HAS_DIRECTINPUT +bool GUIDFromString(const char* string, GUID& guid) +{ + unsigned long p0; + int p1, p2, p3, p4, p5, p6, p7, p8, p9, p10; + const sint32 count = sscanf_s(string, "%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X", &p0, &p1, &p2, &p3, &p4, &p5, &p6, &p7, &p8, &p9, &p10); + if (count != 11) + return false; + + guid.Data1 = p0; + guid.Data2 = p1; + guid.Data3 = p2; + guid.Data4[0] = p3; + guid.Data4[1] = p4; + guid.Data4[2] = p5; + guid.Data4[3] = p6; + guid.Data4[4] = p7; + guid.Data4[5] = p8; + guid.Data4[6] = p9; + guid.Data4[7] = p10; + return count == 11; +} + +std::string StringFromGUID(const GUID& guid) +{ + char temp[256]; + sprintf(temp, "%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X", + guid.Data1, guid.Data2, guid.Data3, + guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]); + return std::string(temp); +} + +std::wstring WStringFromGUID(const GUID& guid) +{ + wchar_t temp[256]; + swprintf_s(temp, L"%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X", + guid.Data1, guid.Data2, guid.Data3, + guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]); + + return std::wstring(temp); +} +#endif + +std::vector TokenizeView(std::string_view str, char delimiter) +{ + std::vector result; + + size_t last_token_index = 0; + for (auto index = str.find(delimiter); index != std::string_view::npos; index = str.find(delimiter, index + 1)) + { + const auto token = str.substr(last_token_index, index - last_token_index); + result.emplace_back(token); + + last_token_index = index + 1; + } + + try + { + const auto token = str.substr(last_token_index); + result.emplace_back(token); + } + catch (const std::invalid_argument&) {} + + return result; +} + +std::vector Tokenize(std::string_view str, char delimiter) +{ + std::vector result; + + size_t last_token_index = 0; + for (auto index = str.find(delimiter); index != std::string_view::npos; index = str.find(delimiter, index + 1)) + { + const auto token = str.substr(last_token_index, index - last_token_index); + result.emplace_back(token); + + last_token_index = index + 1; + } + + try + { + const auto token = str.substr(last_token_index); + result.emplace_back(token); + } + catch (const std::invalid_argument&) {} + + return result; +} + +std::string GenerateRandomString(size_t length) +{ + const std::string kCharacters{ + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "1234567890" }; + return GenerateRandomString(length, kCharacters); +} + +std::string GenerateRandomString(size_t length, std::string_view characters) +{ + assert(!characters.empty()); + std::stringstream result; + + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution index_dist(0, characters.size() - 1); + for (uint32_t i = 0; i < length; ++i) + { + result << characters[index_dist(gen)]; + } + + return result.str(); +} \ No newline at end of file diff --git a/src/util/helpers/helpers.h b/src/util/helpers/helpers.h new file mode 100644 index 00000000..a672213d --- /dev/null +++ b/src/util/helpers/helpers.h @@ -0,0 +1,239 @@ +#pragma once + +#include +#include +#include + +#include "util/math/vector2.h" +#include "util/math/vector3.h" + +#ifdef __clang__ +#include "Common/linux/fast_float.h" +#endif + +template +constexpr auto to_underlying(TType v) noexcept +{ + return static_cast>(v); +} + +// wrapper to allow reverse iteration with range-based loops before C++20 +template +class reverse_itr { +private: + T& iterable_; +public: + explicit reverse_itr(T& iterable) : iterable_{ iterable } {} + auto begin() const { return std::rbegin(iterable_); } + auto end() const { return std::rend(iterable_); } +}; + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +template +T deg2rad(T v) { return v * static_cast(M_PI) / static_cast(180); } +template +T rad2deg(T v) { return v * static_cast(180) / static_cast(M_PI); } + +template +Vector3 deg2rad(const Vector3& v) { return { deg2rad(v.x), deg2rad(v.y), deg2rad(v.z) }; } +template +Vector3 rad2deg(const Vector3& v) { return { rad2deg(v.x), rad2deg(v.y), rad2deg(v.z) }; } + +template +Vector2 deg2rad(const Vector2& v) { return { deg2rad(v.x), deg2rad(v.y) }; } +template +Vector2 rad2deg(const Vector2& v) { return { rad2deg(v.x), rad2deg(v.y) }; } + +uint32_t GetPhysicalCoreCount(); + +// Creates a temporary file to test for write access +bool TestWriteAccess(const fs::path& p); + +fs::path MakeRelativePath(const fs::path& path); + +#ifdef HAS_DIRECTINPUT +bool GUIDFromString(const char* string, GUID& guid); +std::string StringFromGUID(const GUID& guid); +std::wstring WStringFromGUID(const GUID& guid); +#endif + +std::vector TokenizeView(std::string_view string, char delimiter); +std::vector Tokenize(std::string_view string, char delimiter); + +std::string ltrim_copy(const std::string& s); +std::string rtrim_copy(const std::string& s); +std::string& ltrim(std::string& str, const std::string& chars = "\t\n\v\f\r "); +std::string& rtrim(std::string& str, const std::string& chars = "\t\n\v\f\r "); +std::string& trim(std::string& str, const std::string& chars = "\t\n\v\f\r "); + +std::string_view& ltrim(std::string_view& str, const std::string& chars = "\t\n\v\f\r "); +std::string_view& rtrim(std::string_view& str, const std::string& chars = "\t\n\v\f\r "); +std::string_view& trim(std::string_view& str, const std::string& chars = "\t\n\v\f\r "); + +std::string GenerateRandomString(size_t length); +std::string GenerateRandomString(size_t length, std::string_view characters); + +std::wstring GetSystemErrorMessageW(); +std::wstring GetSystemErrorMessageW(DWORD error_code); +std::string GetSystemErrorMessage(); +std::string GetSystemErrorMessage(DWORD error_code); +std::string GetSystemErrorMessage(const std::exception& ex); +std::string GetSystemErrorMessage(const std::error_code& ec); + +template struct overloaded : Ts... { using Ts::operator()...; }; +template overloaded(Ts...)->overloaded; + +template +bool equals(T v1, T v2) +{ + /* + return std::fabs(x-y) <= std::numeric_limits::epsilon() * std::fabs(x+y) * ulp + // unless the result is subnormal + || std::fabs(x-y) < std::numeric_limits::min(); + */ + if constexpr (std::is_floating_point_v) + return std::abs(v1 - v2) < (T)0.000001; + else if constexpr (std::is_same_v) + return strcmp(v1, v2) == 0; + else + return v1 == v2; +} + +template +T ConvertString(std::string_view str, sint32 base) +{ + if (str.empty()) + return {}; + + static_assert(std::is_integral_v); + T result; + ltrim(str); + + // from_chars cant deal with hex numbers starting with "0x" + if (base == 16) + { + const sint32 index = str[0] == '-' ? 1 : 0; + if (str.size() >= 2 && str[index+0] == '0' && tolower(str[index+1]) == 'x') + str = str.substr(index + 2); + + if (std::from_chars(str.data(), str.data() + str.size(), result, base).ec == std::errc()) + { + if (index == 1) + { + if constexpr(std::is_unsigned_v) + result = static_cast(-static_cast>(result)); + else + result = -result; + } + + return result; + } + + return {}; + } + + if(std::from_chars(str.data(), str.data() + str.size(), result, base).ec == std::errc()) + return result; + + return {}; +} + +template +T ConvertString(std::string_view str) +{ + if (str.empty()) + return {}; + + T result; + ltrim(str); + if constexpr (std::is_same_v) + { + return str == "1" || boost::iequals(str, "true"); + } + else if constexpr(std::is_floating_point_v) + { + // from_chars can't deal with float conversation starting with "+" + ltrim(str, "+"); +#ifdef __clang__ + if (fast_float::from_chars(str.data(), str.data() + str.size(), result).ec == std::errc()) + return result; +#else + if (std::from_chars(str.data(), str.data() + str.size(), result).ec == std::errc()) + return result; +#endif + return {}; + } + else if constexpr(std::is_enum_v) + { + return (T)ConvertString>(str); + } + else + { + const sint32 index = str[0] == '-' ? 1 : 0; + if (str.size() >= 2 && str[index + 0] == '0' && tolower(str[index + 1]) == 'x') + result = ConvertString(str, 16); + else + result = ConvertString(str, 10); + } + + return result; +} + +template +constexpr T DegToRad(T deg) { return (T)((double)deg * M_PI / 180); } +template +constexpr T RadToDeg(T rad) { return (T)((double)rad * 180 / M_PI); } + +template +std::string ToString(T value) +{ + std::ostringstream str; + str.imbue(std::locale("C")); + str << value; + return str.str(); +} + +template +T FromString(std::string value) +{ + std::istringstream str(value); + str.imbue(std::locale("C")); + + T tmp; + str >> tmp; + return tmp; +} + +template +size_t RemoveDuplicatesKeepOrder(std::vector& vec) +{ + std::set tmp; + auto new_end = std::remove_if(vec.begin(), vec.end(), [&tmp](const T& value) + { + if (tmp.find(value) != std::end(tmp)) + return true; + + tmp.insert(value); + return false; + }); + + vec.erase(new_end, vec.end()); + return vec.size(); +} + +void SetThreadName(const char* name); + +inline uint64 MakeU64(uint32 high, uint32 low) +{ + return ((uint64)high << 32) | ((uint64)low); +} + +// MAJOR; MINOR +std::pair GetWindowsVersion(); +bool IsWindows81OrGreater(); +bool IsWindows10OrGreater(); + +fs::path GetParentProcess(); diff --git a/src/util/helpers/ringbuffer.h b/src/util/helpers/ringbuffer.h new file mode 100644 index 00000000..ae036efd --- /dev/null +++ b/src/util/helpers/ringbuffer.h @@ -0,0 +1,124 @@ +#pragma once + +#include + +template +class RingBuffer +{ +public: + RingBuffer(); + + bool Push(const T& v); + + template + typename std::enable_if< !std::is_array::value, Q >::type + Pop() + { + std::unique_lock lock(m_mutex); + if (m_readPointer == m_writePointer) + { + return T(); + } + + const T& tmp = m_data[m_readPointer]; + m_readPointer = (m_readPointer + 1) % elements; + return tmp; + } + + T& GetSlot(); + T& GetSlotAndAdvance(); + void Advance(); + + void Clear(); + P GetReadPointer(); + P GetWritePointer(); + bool HasData(); + +private: + T m_data[elements]; + P m_readPointer; + P m_writePointer; + std::mutex m_mutex; +}; + +template +RingBuffer::RingBuffer() + : m_readPointer(0), m_writePointer(0) +{ + +} + +template +bool RingBuffer::Push(const T& v) +{ + std::unique_lock lock(m_mutex); + if (m_readPointer == ((m_writePointer + 1) % elements)) + { + debugBreakpoint(); // buffer is full + return false; + } + + m_data[m_writePointer] = v; + m_writePointer = (m_writePointer + 1) % elements; + return true; +} + +template +T& RingBuffer::GetSlot() +{ + std::unique_lock lock(m_mutex); + T& result = m_data[m_writePointer]; + m_writePointer = (m_writePointer + 1) % elements; + return result; +} + +template +T& RingBuffer::GetSlotAndAdvance() +{ + std::unique_lock lock(m_mutex); + T& result = m_data[m_writePointer]; + m_writePointer = (m_writePointer + 1) % elements; + m_readPointer = (m_readPointer + 1) % elements; + return result; +} + +template +void RingBuffer::Advance() +{ + std::unique_lock lock(m_mutex); + if (m_readPointer != m_writePointer) + { + m_readPointer = (m_readPointer + 1) % elements; + } +} + +template +void RingBuffer::Clear() +{ + std::unique_lock lock(m_mutex); + m_readPointer = 0; + m_writePointer = 0; +} + +template +P RingBuffer::GetReadPointer() +{ + std::unique_lock lock(m_mutex); + P tmp = m_readPointer; + return tmp; +} + +template +P RingBuffer::GetWritePointer() +{ + std::unique_lock lock(m_mutex); + P tmp = m_writePointer; + return tmp; +} + +template +bool RingBuffer::HasData() +{ + std::unique_lock lock(m_mutex); + return m_readPointer != m_writePointer; +} diff --git a/src/util/highresolutiontimer/HighResolutionTimer.cpp b/src/util/highresolutiontimer/HighResolutionTimer.cpp new file mode 100644 index 00000000..21cabff5 --- /dev/null +++ b/src/util/highresolutiontimer/HighResolutionTimer.cpp @@ -0,0 +1,34 @@ +#include "util/highresolutiontimer/HighResolutionTimer.h" +#include "Common/precompiled.h" + +HighResolutionTimer HighResolutionTimer::now() +{ +#if BOOST_OS_WINDOWS + LARGE_INTEGER pc; + QueryPerformanceCounter(&pc); + return HighResolutionTimer(pc.QuadPart); +#else + timespec pc; + clock_gettime(CLOCK_MONOTONIC, &pc); + uint64 nsec = (uint64)pc.tv_sec * (uint64)1000000000 + (uint64)pc.tv_nsec; + return HighResolutionTimer(nsec); +#endif +} + +HRTick HighResolutionTimer::getFrequency() +{ + return m_freq; +} + + +uint64 HighResolutionTimer::m_freq = []() -> uint64 { +#if BOOST_OS_WINDOWS + LARGE_INTEGER freq; + QueryPerformanceFrequency(&freq); + return (uint64)(freq.QuadPart); +#else + timespec pc; + clock_getres(CLOCK_MONOTONIC, &pc); + return (uint64)1000000000 / (uint64)pc.tv_nsec; +#endif +}(); diff --git a/src/util/highresolutiontimer/HighResolutionTimer.h b/src/util/highresolutiontimer/HighResolutionTimer.h new file mode 100644 index 00000000..12dc0751 --- /dev/null +++ b/src/util/highresolutiontimer/HighResolutionTimer.h @@ -0,0 +1,75 @@ +#pragma once + +using HRTick = uint64; + +class HighResolutionTimer +{ +public: + HighResolutionTimer() + { + m_timePoint = 0; + } + + HRTick getTick() const + { + return m_timePoint; + } + + uint64 getTickInSeconds() const + { + return m_timePoint / m_freq; + } + + // return time difference in seconds, this is an utility function mainly intended for debugging/benchmarking purposes. Avoid using doubles for precise timing + static double getTimeDiff(HRTick startTime, HRTick endTime) + { + return (double)(endTime - startTime) / (double)m_freq; + } + + // returns tick difference and frequency + static uint64 getTimeDiffEx(HRTick startTime, HRTick endTime, uint64& freq) + { + freq = m_freq; + return endTime - startTime; + } + + static HighResolutionTimer now(); + static HRTick getFrequency(); + +private: + HighResolutionTimer(uint64 timePoint) : m_timePoint(timePoint) {}; + + uint64 m_timePoint; + static uint64 m_freq; +}; + +// benchmark helper utility +// measures time between Start() and Stop() call +class BenchmarkTimer +{ +public: + void Start() + { + m_startTime = HighResolutionTimer::now().getTick(); + } + + void Stop() + { + m_stopTime = HighResolutionTimer::now().getTick(); + } + + double GetElapsedMilliseconds() const + { + cemu_assert_debug(m_startTime != 0 && m_stopTime != 0); + cemu_assert_debug(m_startTime <= m_stopTime); + uint64 tickDif = m_stopTime - m_startTime; + double freq = (double)HighResolutionTimer::now().getFrequency(); + double elapsedMS = (double)tickDif * 1000.0 / freq; + return elapsedMS; + } + +private: + HRTick m_startTime{}; + HRTick m_stopTime{}; +}; + diff --git a/src/util/libusbWrapper/libusbWrapper.cpp b/src/util/libusbWrapper/libusbWrapper.cpp new file mode 100644 index 00000000..25a73781 --- /dev/null +++ b/src/util/libusbWrapper/libusbWrapper.cpp @@ -0,0 +1,74 @@ +#include "libusbWrapper.h" + +/* + +#include "config/ActiveSettings.h" + +libusbWrapper::libusbWrapper() +{ +} + +void libusbWrapper::init() +{ +#if BOOST_OS_WINDOWS + if (m_isInitialized) + return; + m_isInitialized = true; + // load module + m_module = LoadLibraryW(L"libusb-1.0.dll"); + if (!m_module) + { + const auto path = ActiveSettings::GetPath("resources/libusb-1.0.dll"); + m_module = LoadLibraryW(path.generic_wstring().c_str()); + if (!m_module) + { + cemuLog_log(LogType::Force, "libusbWrapper: can't load libusb-1.0.dll"); + return; + } + } + + // grab imports +#define FETCH_IMPORT(__NAME__) p_##__NAME__ = (decltype(&__NAME__))GetProcAddress(m_module, #__NAME__) + FETCH_IMPORT(libusb_init); + FETCH_IMPORT(libusb_exit); + FETCH_IMPORT(libusb_interrupt_transfer); + FETCH_IMPORT(libusb_get_device_list); + FETCH_IMPORT(libusb_get_device_descriptor); + FETCH_IMPORT(libusb_open); + FETCH_IMPORT(libusb_close); + FETCH_IMPORT(libusb_kernel_driver_active); + FETCH_IMPORT(libusb_detach_kernel_driver); + FETCH_IMPORT(libusb_claim_interface); + FETCH_IMPORT(libusb_free_device_list); + FETCH_IMPORT(libusb_get_config_descriptor); + FETCH_IMPORT(libusb_hotplug_register_callback); + FETCH_IMPORT(libusb_hotplug_deregister_callback); + FETCH_IMPORT(libusb_has_capability); + FETCH_IMPORT(libusb_error_name); + FETCH_IMPORT(libusb_get_string_descriptor); + FETCH_IMPORT(libusb_get_string_descriptor_ascii); + FETCH_IMPORT(libusb_free_config_descriptor); +#undef FETCH_IMPORT + + // create default context + if (p_libusb_init) + p_libusb_init(nullptr); +#else + cemuLog_log(LogType::Force, "libusbWrapper: Not supported on this OS"); +#endif +} + +libusbWrapper::~libusbWrapper() +{ +#if BOOST_OS_WINDOWS > 0 + // destroy default context + if(p_libusb_exit) + p_libusb_exit(nullptr); + + // unload module + if(m_module) + FreeLibrary(m_module); +#endif +} + +*/ \ No newline at end of file diff --git a/src/util/libusbWrapper/libusbWrapper.h b/src/util/libusbWrapper/libusbWrapper.h new file mode 100644 index 00000000..e79c5b04 --- /dev/null +++ b/src/util/libusbWrapper/libusbWrapper.h @@ -0,0 +1,50 @@ +#pragma once + +// todo - port to cmake build + +/* + +#include "util/helpers/ClassWrapper.h" + +#pragma warning(disable:4200) +#include "libusb-1.0/libusb.h" +#pragma warning(default:4200) + +class libusbWrapper : public SingletonRef +{ +public: + libusbWrapper(); + ~libusbWrapper(); + + void init(); + bool isAvailable() const { return p_libusb_init != nullptr; }; + + decltype(&libusb_init) p_libusb_init = nullptr; + decltype(&libusb_exit) p_libusb_exit = nullptr; + decltype(&libusb_interrupt_transfer) p_libusb_interrupt_transfer; + decltype(&libusb_get_device_list) p_libusb_get_device_list; + decltype(&libusb_get_device_descriptor) p_libusb_get_device_descriptor; + decltype(&libusb_open) p_libusb_open; + decltype(&libusb_kernel_driver_active) p_libusb_kernel_driver_active; + decltype(&libusb_detach_kernel_driver) p_libusb_detach_kernel_driver; + decltype(&libusb_claim_interface) p_libusb_claim_interface; + decltype(&libusb_free_device_list) p_libusb_free_device_list; + decltype(&libusb_get_config_descriptor) p_libusb_get_config_descriptor; + decltype(&libusb_free_config_descriptor) p_libusb_free_config_descriptor; + decltype(&libusb_close) p_libusb_close; + decltype(&libusb_hotplug_register_callback) p_libusb_hotplug_register_callback; + decltype(&libusb_hotplug_deregister_callback) p_libusb_hotplug_deregister_callback; + decltype(&libusb_has_capability) p_libusb_has_capability; + decltype(&libusb_error_name) p_libusb_error_name; + decltype(&libusb_get_string_descriptor) p_libusb_get_string_descriptor; + decltype(&libusb_get_string_descriptor_ascii) p_libusb_get_string_descriptor_ascii; + + +private: +#if BOOST_OS_WINDOWS > 0 + HMODULE m_module = nullptr; + bool m_isInitialized = false; +#endif +}; + +*/ \ No newline at end of file diff --git a/src/util/math/glm.h b/src/util/math/glm.h new file mode 100644 index 00000000..d1871b13 --- /dev/null +++ b/src/util/math/glm.h @@ -0,0 +1,36 @@ +#pragma once + +namespace glm +{ + inline quat normalize_xyz(const quat& q) + { + const auto xyzTargetLength = std::sqrt(1.0f - q.w * q.w); + const auto lengthScaler = xyzTargetLength / sqrtf(q.x * q.x + q.y * q.y + q.z * q.z); + return quat(q.w, q.x * lengthScaler, q.y * lengthScaler, q.z * lengthScaler); + } + + inline vec3 GetVectorX(const quat& q) + { + return vec3( + 2.0f * (q.w * q.w + q.x * q.x) - 1.0f, + 2.0f * (q.x * q.y - q.w * q.z), + 2.0f * (q.x * q.z + q.w * q.y)); + } + + inline vec3 GetVectorY(const quat& q) + { + return vec3( + 2.0f * (q.x * q.y + q.w * q.z), + 2.0f * (q.w * q.w + q.y * q.y) - 1.0f, + 2.0f * (q.y * q.z - q.w * q.x) + ); + } + + inline vec3 GetVectorZ(const quat& q) + { + return vec3 ( + 2.0f * (q.x * q.z - q.w * q.y), + 2.0f * (q.y * q.z + q.w * q.x), + 2.0f * (q.w * q.w + q.z * q.z) - 1.0f); + } +} diff --git a/src/util/math/quaternion.h b/src/util/math/quaternion.h new file mode 100644 index 00000000..764cc44c --- /dev/null +++ b/src/util/math/quaternion.h @@ -0,0 +1,217 @@ +#pragma once + +#include +#include +#include "util/math/vector3.h" +#define DEG2RAD(__d__) ((__d__ * M_PI) / 180.0f ) +#define RAD2DEG(__r__) ((__r__ * 180.0f) / M_PI) + + +template +class Quaternion +{ +public: + T w; + T x; + T y; + T z; + + Quaternion(); + Quaternion(const T& w, const T& x, const T& y, const T& z); + Quaternion(float x, float y, float z); + + void Assign(float w, float x, float y, float z) + { + this->w = w; + this->x = x; + this->y = y; + this->z = z; + } + + static Quaternion FromAngleAxis(float inAngle, float inX, float inY, float inZ); + + std::tuple, Vector3, Vector3> GetRotationMatrix(); + std::tuple, Vector3, Vector3> GetTransposedRotationMatrix(); + + // normalize but keep W + void NormalizeXYZ() + { + const T xyzTargetLength = sqrtf((T)1.0 - w * w); + const T lengthScaler = xyzTargetLength / sqrtf(x * x + y * y + z * z); + x *= lengthScaler; + y *= lengthScaler; + z *= lengthScaler; + } + + void NormalizeXYZW() + { + const T lengthScaler = 1.0f / sqrtf(w * w + x * x + y * y + z * z); + w *= lengthScaler; + x *= lengthScaler; + y *= lengthScaler; + z *= lengthScaler; + } + + Vector3 GetVectorX() const + { + return Vector3( + 2.0f * (w * w + x * x) - 1.0f, + 2.0f * (x * y - w * z), + 2.0f * (x * z + w * y)); + } + + Vector3 GetVectorY() const + { + return Vector3( + 2.0f * (x * y + w * z), + 2.0f * (w * w + y * y) - 1.0f, + 2.0f * (y * z - w * x) + ); + } + + Vector3 GetVectorZ() const + { + return Vector3( + 2.0f * (x * z - w * y), + 2.0f * (y * z + w * x), + 2.0f * (w * w + z * z) - 1.0f); + } + + Quaternion& operator*=(const Quaternion& rhs) + { + Assign(w * rhs.w - x * rhs.x - y * rhs.y - z * rhs.z, + w * rhs.x + x * rhs.w + y * rhs.z - z * rhs.y, + w * rhs.y - x * rhs.z + y * rhs.w + z * rhs.x, + w * rhs.z + x * rhs.y - y * rhs.x + z * rhs.w); + return *this; + } + + Quaternion& operator+=(const Quaternion& rhs) + { + w += rhs.w; + x += rhs.x; + y += rhs.y; + z += rhs.z; + return *this; + } + + friend Quaternion operator*(Quaternion lhs, const Quaternion& rhs) + { + lhs *= rhs; + return lhs; + } + +}; + +template +Quaternion::Quaternion() + : w(), x(), y(), z() +{} + +template +Quaternion::Quaternion(const T& w, const T& x, const T& y, const T& z) + : w(w), x(x), y(y), z(z) +{} + +template +Quaternion::Quaternion(float x, float y, float z) +{ + float pitch = DEG2RAD(x); + float yaw = DEG2RAD(y); + float roll = DEG2RAD(z); + + float cyaw = cos(0.5f * yaw); + float cpitch = cos(0.5f * pitch); + float croll = cos(0.5f * roll); + float syaw = sin(0.5f * yaw); + float spitch = sin(0.5f * pitch); + float sroll = sin(0.5f * roll); + + float cyawcpitch = cyaw * cpitch; + float syawspitch = syaw * spitch; + float cyawspitch = cyaw * spitch; + float syawcpitch = syaw * cpitch; + + this->w = cyawcpitch * croll + syawspitch * sroll; + this->x = cyawspitch * croll + syawcpitch * sroll; + this->y = syawcpitch * croll - cyawspitch * sroll; + this->z = cyawcpitch * sroll - syawspitch * croll; +} + +template +Quaternion Quaternion::FromAngleAxis(float inAngle, float inX, float inY, float inZ) +{ + Quaternion result = Quaternion(cosf(inAngle * 0.5f), inX, inY, inZ); + result.NormalizeXYZ(); + return result; +} + +template +std::tuple, Vector3, Vector3> Quaternion::GetRotationMatrix() +{ + float sqw = w*w; + float sqx = x*x; + float sqy = y*y; + float sqz = z*z; + + // invs (inverse square length) is only required if quaternion is not already normalised + float invs = 1.0f / (sqx + sqy + sqz + sqw); + + Vector3 v1, v2, v3; + v1.x = (sqx - sqy - sqz + sqw) * invs; // since sqw + sqx + sqy + sqz =1/invs*invs + v2.y = (-sqx + sqy - sqz + sqw) * invs; + v3.z = (-sqx - sqy + sqz + sqw) * invs; + + float tmp1 = x*y; + float tmp2 = z*w; + v2.x = 2.0 * (tmp1 + tmp2)*invs; + v1.y = 2.0 * (tmp1 - tmp2)*invs; + + tmp1 = x*z; + tmp2 = y*w; + v3.x = 2.0 * (tmp1 - tmp2)*invs; + v1.z = 2.0 * (tmp1 + tmp2)*invs; + + tmp1 = y*z; + tmp2 = x*w; + v3.y = 2.0 * (tmp1 + tmp2)*invs; + v2.z = 2.0 * (tmp1 - tmp2)*invs; + + return std::make_tuple(v1, v2, v3); +} + +template +std::tuple, Vector3, Vector3> Quaternion::GetTransposedRotationMatrix() +{ + float sqw = w*w; + float sqx = x*x; + float sqy = y*y; + float sqz = z*z; + + // invs (inverse square length) is only required if quaternion is not already normalised + float invs = 1.0f / (sqx + sqy + sqz + sqw); + + Vector3 v1, v2, v3; + v1.x = (sqx - sqy - sqz + sqw) * invs; // since sqw + sqx + sqy + sqz =1/invs*invs + v2.y = (-sqx + sqy - sqz + sqw) * invs; + v3.z = (-sqx - sqy + sqz + sqw) * invs; + + float tmp1 = x*y; + float tmp2 = z*w; + v1.y = 2.0 * (tmp1 + tmp2)*invs; + v2.x = 2.0 * (tmp1 - tmp2)*invs; + + tmp1 = x*z; + tmp2 = y*w; + v1.z = 2.0 * (tmp1 - tmp2)*invs; + v3.x = 2.0 * (tmp1 + tmp2)*invs; + + tmp1 = y*z; + tmp2 = x*w; + v2.z = 2.0 * (tmp1 + tmp2)*invs; + v3.y = 2.0 * (tmp1 - tmp2)*invs; + + return std::make_tuple(v1, v2, v3); +} + +using Quaternionf = Quaternion; \ No newline at end of file diff --git a/src/util/math/vector2.h b/src/util/math/vector2.h new file mode 100644 index 00000000..150fc4e5 --- /dev/null +++ b/src/util/math/vector2.h @@ -0,0 +1,150 @@ +#pragma once +#include + +template +class Vector2 +{ +public: + T x; + T y; + + Vector2() + : x{}, y{} {} + + Vector2(T x, T y) + : x(x), y(y) {} + + template + Vector2(const Vector2& v) + : x((T)v.x), y((T)v.y) {} + + float Length() const + { + return (float)std::sqrt((x * x) + (y * y)); + } + + float Cross(const Vector2& v) const + { + return x * v.y - y * v.x; + } + + float Dot(const Vector2& v) const + { + return x * v.x + y * v.y; + } + + Vector2 Ortho() const + { + return Vector2(y, -x); + } + + Vector2 Normalized() const + { + const auto len = Length(); + if (len == 0) + return Vector2(); + + return Vector2((T)((float)x / len), (T)((float)y / len)); + } + + Vector2& Normalize() + { + const auto len = Length(); + if (len != 0) + { + x = (T)((float)x / len); + y = (T)((float)y / len); + } + + return *this; + } + + static Vector2 Max(const Vector2& v1, const Vector2& v2) + { + return Vector2(std::max(v1.x, v2.x), std::max(v1.y, v2.y)); + } + + static Vector2 Min(const Vector2& v1, const Vector2& v2) + { + return Vector2(std::min(v1.x, v2.x), std::min(v1.y, v2.y)); + } + + bool operator==(const Vector2& v) const + { + return x == v.x && y == v.y; + } + bool operator!=(const Vector2& v) const + { + return !(*this == v); + } + + Vector2& operator+=(const Vector2& v) + { + x += v.x; + y += v.y; + return *this; + } + Vector2& operator-=(const Vector2& v) + { + x -= v.x; + y -= v.y; + return *this; + } + + Vector2 operator+(const Vector2& v) const + { + return Vector2(x + v.x, y + v.y); + } + Vector2 operator-(const Vector2& v) const + { + return Vector2(x - v.x, y - v.y); + } + + Vector2& operator+=(const T& v) + { + x += v; + y += v; + return *this; + } + Vector2& operator-=(const T& v) + { + x -= v; + y -= v; + return *this; + } + Vector2& operator*=(const T& v) + { + x *= v; + y *= v; + return *this; + } + Vector2& operator/=(const T& v) + { + assert(v != 0); + x /= v; + y /= v; + return *this; + } + + Vector2 operator+(const T& v) + { + return Vector2(x + v, y + v); + } + Vector2 operator-(const T& v) + { + return Vector2(x - v, y - v); + } + Vector2 operator*(const T& v) + { + return Vector2(x * v, y * v); + } + Vector2 operator/(const T& v) + { + assert(v != 0); + return Vector2(x / v, y / v); + } +}; + +using Vector2f = Vector2; +using Vector2i = Vector2; + diff --git a/src/util/math/vector3.h b/src/util/math/vector3.h new file mode 100644 index 00000000..416398f8 --- /dev/null +++ b/src/util/math/vector3.h @@ -0,0 +1,233 @@ +#pragma once +#include + +template +class Vector3 { +public: + T x; + T y; + T z; + + Vector3() : x{}, y{}, z{} {} + Vector3(T x, T y, T z) : x(x), y(y), z(z) {} + + template + Vector3(const Vector3& v) + : x((T)v.x), y((T)v.y), z((T)v.z) {} + + float Length() const + { + return std::sqrt((x * x) + (y * y) + (z * z)); + } + + float Dot(const Vector3& v) const + { + return x * v.x + y * v.y + z * v.z; + } + + Vector3 Cross(const Vector3& v) const + { + return Vector3(y * v.z - z * v.y, z * v.x - x * v.z, x * v.y - y * v.x); + } + + Vector3 Normalized() const + { + const auto len = Length(); + if (len == 0) + return {}; + + return *this / len; + } + + Vector3& Normalize() + { + const auto len = Length(); + if (len != 0) + *this /= len; + + return *this; + } + + //Vector3& Scale(const Vector3& v) + //{ + // *this *= v; + // return *this; + //} + + void Scale(const float s) + { + this->x *= s; + this->y *= s; + this->z *= s; + } + + Vector3& RotateX(float theta); + Vector3& RotateY(float theta); + Vector3& RotateZ(float theta); + + + bool operator==(const Vector3& v) + { + return x == v.x && y == v.y && z == v.z; + } + + bool operator!=(const Vector3& v) + { + return !(*this == v); + } + + template + Vector3& operator+=(const Vector3& v) + { + x += (T)v.x; + y += (T)v.y; + z += (T)v.z; + return *this; + } + + template + Vector3& operator-=(const Vector3& v) + { + x -= (T)v.x; + y -= (T)v.y; + z -= (T)v.z; + return *this; + } + + template + Vector3& operator*=(const Vector3& v) + { + x *= (T)v.x; + y *= (T)v.y; + z *= (T)v.z; + return *this; + } + + template + Vector3& operator/=(const Vector3& v) + { + assert(v.x != 0 && v.y != 0 && v.z != 0); + x /= (T)v.x; + y /= (T)v.y; + z /= (T)v.z; + return *this; + } + + template + Vector3 operator+(const Vector3& v) const + { + return Vector3(x + (T)v.x, y + (T)v.y, z + (T)v.z); + } + + template + Vector3 operator-(const Vector3& v) const + { + return Vector3(x - (T)v.x, y - (T)v.y, z - (T)v.z); + } + + template + Vector3 operator*(const Vector3& v) const + { + return Vector3(x * (T)v.x, y * (T)v.y, z * (T)v.z); + } + + template + Vector3 operator/(const Vector3& v) const + { + assert(v.x != 0 && v.y != 0 && v.z != 0); + return Vector3(x / (T)v.x, y / (T)v.y, z / (T)v.z); + } + + Vector3& operator+=(T v) + { + x += v; + y += v; + z += v; + return *this; + } + Vector3& operator-=(T v) + { + x -= v; + y -= v; + z -= v; + return *this; + } + Vector3& operator*=(T v) + { + x *= v; + y *= v; + z *= v; + return *this; + } + Vector3& operator/=(T v) + { + assert(v != 0); + x /= v; + y /= v; + z /= v; + return *this; + } + + Vector3 operator+(T v) const + { + return Vector3(x + v, y + v, z + v); + } + Vector3 operator-(T v) const + { + return Vector3(x - v, y - v, z - v); + } + Vector3 operator*(T v) const + { + return Vector3(x * v, y * v, z * v); + } + Vector3 operator/(T v) const + { + assert(v != 0); + return Vector3(x / (T)v, y / (T)v, z / (T)v); + } + + bool IsZero() const + { + return x == 0 && y == 0 && z == 0; + } + bool HasZero() const + { + return x == 0 || y == 0 || z == 0; + } +}; + + + +template +Vector3& Vector3::RotateX(float theta) +{ + const float sin = std::sin(theta); + const float cos = std::cos(theta); + y = y * cos - z * sin; + z = y * sin + z * cos; + return *this; +} + +template +Vector3& Vector3::RotateY(float theta) +{ + const float sin = std::sin(theta); + const float cos = std::cos(theta); + x = x * cos + z * sin; + z = -x * sin + z * cos; + return *this; +} + +template +Vector3& Vector3::RotateZ(float theta) +{ + const float sin = std::sin(theta); + const float cos = std::cos(theta); + x = x * cos - y * sin; + y = x * sin + y * cos; + return *this; +} + + +using Vector3f = Vector3; +using Vector3i = Vector3; diff --git a/src/util/tinyxml2/tinyxml2.cpp b/src/util/tinyxml2/tinyxml2.cpp new file mode 100644 index 00000000..8ae31635 --- /dev/null +++ b/src/util/tinyxml2/tinyxml2.cpp @@ -0,0 +1,2764 @@ +/* +Original code by Lee Thomason (www.grinninglizard.com) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + +#include "tinyxml2.h" + +#include // yes, this one new style header, is in the Android SDK. +#if defined(ANDROID_NDK) || defined(__BORLANDC__) || defined(__QNXNTO__) +# include +# include +#else +# include +# include +#endif + +#if defined(_MSC_VER) && (_MSC_VER >= 1400 ) && (!defined WINCE) +// Microsoft Visual Studio, version 2005 and higher. Not WinCE. +/*int _snprintf_s( +char *buffer, +size_t sizeOfBuffer, +size_t count, +const char *format [, +argument] ... +);*/ +static inline int TIXML_SNPRINTF(char* buffer, size_t size, const char* format, ...) +{ + va_list va; + va_start(va, format); + int result = vsnprintf_s(buffer, size, _TRUNCATE, format, va); + va_end(va); + return result; +} + +static inline int TIXML_VSNPRINTF(char* buffer, size_t size, const char* format, va_list va) +{ + int result = vsnprintf_s(buffer, size, _TRUNCATE, format, va); + return result; +} + +#define TIXML_VSCPRINTF _vscprintf +#define TIXML_SSCANF sscanf_s +#elif defined _MSC_VER +// Microsoft Visual Studio 2003 and earlier or WinCE +#define TIXML_SNPRINTF _snprintf +#define TIXML_VSNPRINTF _vsnprintf +#define TIXML_SSCANF sscanf +#if (_MSC_VER < 1400 ) && (!defined WINCE) +// Microsoft Visual Studio 2003 and not WinCE. +#define TIXML_VSCPRINTF _vscprintf // VS2003's C runtime has this, but VC6 C runtime or WinCE SDK doesn't have. +#else +// Microsoft Visual Studio 2003 and earlier or WinCE. +static inline int TIXML_VSCPRINTF(const char* format, va_list va) +{ + int len = 512; + for (;;) { + len = len * 2; + char* str = new char[len](); + const int required = _vsnprintf(str, len, format, va); + delete[] str; + if (required != -1) { + TIXMLASSERT(required >= 0); + len = required; + break; + } + } + TIXMLASSERT(len >= 0); + return len; +} +#endif +#else +// GCC version 3 and higher +//#warning( "Using sn* functions." ) +#define TIXML_SNPRINTF snprintf +#define TIXML_VSNPRINTF vsnprintf +static inline int TIXML_VSCPRINTF(const char* format, va_list va) +{ + int len = vsnprintf(0, 0, format, va); + TIXMLASSERT(len >= 0); + return len; +} +#define TIXML_SSCANF sscanf +#endif + + +static const char LINE_FEED = (char)0x0a; // all line endings are normalized to LF +static const char LF = LINE_FEED; +static const char CARRIAGE_RETURN = (char)0x0d; // CR gets filtered out +static const char CR = CARRIAGE_RETURN; +static const char SINGLE_QUOTE = '\''; +static const char DOUBLE_QUOTE = '\"'; + +// Bunch of unicode info at: +// http://www.unicode.org/faq/utf_bom.html +// ef bb bf (Microsoft "lead bytes") - designates UTF-8 + +static const unsigned char TIXML_UTF_LEAD_0 = 0xefU; +static const unsigned char TIXML_UTF_LEAD_1 = 0xbbU; +static const unsigned char TIXML_UTF_LEAD_2 = 0xbfU; + +namespace tinyxml2 +{ + + struct Entity { + const char* pattern; + int length; + char value; + }; + + static const int NUM_ENTITIES = 5; + static const Entity entities[NUM_ENTITIES] = { + { "quot", 4, DOUBLE_QUOTE }, + { "amp", 3, '&' }, + { "apos", 4, SINGLE_QUOTE }, + { "lt", 2, '<' }, + { "gt", 2, '>' } + }; + + + StrPair::~StrPair() + { + Reset(); + } + + + void StrPair::TransferTo(StrPair* other) + { + if (this == other) { + return; + } + // This in effect implements the assignment operator by "moving" + // ownership (as in auto_ptr). + + TIXMLASSERT(other != 0); + TIXMLASSERT(other->_flags == 0); + TIXMLASSERT(other->_start == 0); + TIXMLASSERT(other->_end == 0); + + other->Reset(); + + other->_flags = _flags; + other->_start = _start; + other->_end = _end; + + _flags = 0; + _start = 0; + _end = 0; + } + + void StrPair::Reset() + { + if (_flags & NEEDS_DELETE) { + delete[] _start; + } + _flags = 0; + _start = 0; + _end = 0; + } + + + void StrPair::SetStr(const char* str, int flags) + { + TIXMLASSERT(str); + Reset(); + size_t len = strlen(str); + TIXMLASSERT(_start == 0); + _start = new char[len + 1]; + memcpy(_start, str, len + 1); + _end = _start + len; + _flags = flags | NEEDS_DELETE; + } + + + char* StrPair::ParseText(char* p, const char* endTag, int strFlags, int* curLineNumPtr) + { + TIXMLASSERT(p); + TIXMLASSERT(endTag && *endTag); + TIXMLASSERT(curLineNumPtr); + + char* start = p; + char endChar = *endTag; + size_t length = strlen(endTag); + + // Inner loop of text parsing. + while (*p) { + if (*p == endChar && strncmp(p, endTag, length) == 0) { + Set(start, p, strFlags); + return p + length; + } + else if (*p == '\n') { + ++(*curLineNumPtr); + } + ++p; + TIXMLASSERT(p); + } + return 0; + } + + + char* StrPair::ParseName(char* p) + { + if (!p || !(*p)) { + return 0; + } + if (!XMLUtil::IsNameStartChar(*p)) { + return 0; + } + + char* const start = p; + ++p; + while (*p && XMLUtil::IsNameChar(*p)) { + ++p; + } + + Set(start, p, 0); + return p; + } + + + void StrPair::CollapseWhitespace() + { + // Adjusting _start would cause undefined behavior on delete[] + TIXMLASSERT((_flags & NEEDS_DELETE) == 0); + // Trim leading space. + _start = XMLUtil::SkipWhiteSpace(_start, 0); + + if (*_start) { + const char* p = _start; // the read pointer + char* q = _start; // the write pointer + + while (*p) { + if (XMLUtil::IsWhiteSpace(*p)) { + p = XMLUtil::SkipWhiteSpace(p, 0); + if (*p == 0) { + break; // don't write to q; this trims the trailing space. + } + *q = ' '; + ++q; + } + *q = *p; + ++q; + ++p; + } + *q = 0; + } + } + + + const char* StrPair::GetStr() + { + TIXMLASSERT(_start); + TIXMLASSERT(_end); + if (_flags & NEEDS_FLUSH) { + *_end = 0; + _flags ^= NEEDS_FLUSH; + + if (_flags) { + const char* p = _start; // the read pointer + char* q = _start; // the write pointer + + while (p < _end) { + if ((_flags & NEEDS_NEWLINE_NORMALIZATION) && *p == CR) { + // CR-LF pair becomes LF + // CR alone becomes LF + // LF-CR becomes LF + if (*(p + 1) == LF) { + p += 2; + } + else { + ++p; + } + *q = LF; + ++q; + } + else if ((_flags & NEEDS_NEWLINE_NORMALIZATION) && *p == LF) { + if (*(p + 1) == CR) { + p += 2; + } + else { + ++p; + } + *q = LF; + ++q; + } + else if ((_flags & NEEDS_ENTITY_PROCESSING) && *p == '&') { + // Entities handled by tinyXML2: + // - special entities in the entity table [in/out] + // - numeric character reference [in] + // 中 or 中 + + if (*(p + 1) == '#') { + const int buflen = 10; + char buf[buflen] = { 0 }; + int len = 0; + char* adjusted = const_cast(XMLUtil::GetCharacterRef(p, buf, &len)); + if (adjusted == 0) { + *q = *p; + ++p; + ++q; + } + else { + TIXMLASSERT(0 <= len && len <= buflen); + TIXMLASSERT(q + len <= adjusted); + p = adjusted; + memcpy(q, buf, len); + q += len; + } + } + else { + bool entityFound = false; + for (int i = 0; i < NUM_ENTITIES; ++i) { + const Entity& entity = entities[i]; + if (strncmp(p + 1, entity.pattern, entity.length) == 0 + && *(p + entity.length + 1) == ';') { + // Found an entity - convert. + *q = entity.value; + ++q; + p += entity.length + 2; + entityFound = true; + break; + } + } + if (!entityFound) { + // fixme: treat as error? + ++p; + ++q; + } + } + } + else { + *q = *p; + ++p; + ++q; + } + } + *q = 0; + } + // The loop below has plenty going on, and this + // is a less useful mode. Break it out. + if (_flags & NEEDS_WHITESPACE_COLLAPSING) { + CollapseWhitespace(); + } + _flags = (_flags & NEEDS_DELETE); + } + TIXMLASSERT(_start); + return _start; + } + + + + + // --------- XMLUtil ----------- // + + const char* XMLUtil::writeBoolTrue = "true"; + const char* XMLUtil::writeBoolFalse = "false"; + + void XMLUtil::SetBoolSerialization(const char* writeTrue, const char* writeFalse) + { + static const char* defTrue = "true"; + static const char* defFalse = "false"; + + writeBoolTrue = (writeTrue) ? writeTrue : defTrue; + writeBoolFalse = (writeFalse) ? writeFalse : defFalse; + } + + + const char* XMLUtil::ReadBOM(const char* p, bool* bom) + { + TIXMLASSERT(p); + TIXMLASSERT(bom); + *bom = false; + const unsigned char* pu = reinterpret_cast(p); + // Check for BOM: + if (*(pu + 0) == TIXML_UTF_LEAD_0 + && *(pu + 1) == TIXML_UTF_LEAD_1 + && *(pu + 2) == TIXML_UTF_LEAD_2) { + *bom = true; + p += 3; + } + TIXMLASSERT(p); + return p; + } + + + void XMLUtil::ConvertUTF32ToUTF8(unsigned long input, char* output, int* length) + { + const unsigned long BYTE_MASK = 0xBF; + const unsigned long BYTE_MARK = 0x80; + const unsigned long FIRST_BYTE_MARK[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; + + if (input < 0x80) { + *length = 1; + } + else if (input < 0x800) { + *length = 2; + } + else if (input < 0x10000) { + *length = 3; + } + else if (input < 0x200000) { + *length = 4; + } + else { + *length = 0; // This code won't convert this correctly anyway. + return; + } + + output += *length; + + // Scary scary fall throughs are annotated with carefully designed comments + // to suppress compiler warnings such as -Wimplicit-fallthrough in gcc + switch (*length) { + case 4: + --output; + *output = (char)((input | BYTE_MARK) & BYTE_MASK); + input >>= 6; + //fall through + case 3: + --output; + *output = (char)((input | BYTE_MARK) & BYTE_MASK); + input >>= 6; + //fall through + case 2: + --output; + *output = (char)((input | BYTE_MARK) & BYTE_MASK); + input >>= 6; + //fall through + case 1: + --output; + *output = (char)(input | FIRST_BYTE_MARK[*length]); + break; + default: + TIXMLASSERT(false); + } + } + + + const char* XMLUtil::GetCharacterRef(const char* p, char* value, int* length) + { + // Presume an entity, and pull it out. + *length = 0; + + if (*(p + 1) == '#' && *(p + 2)) { + unsigned long ucs = 0; + TIXMLASSERT(sizeof(ucs) >= 4); + ptrdiff_t delta = 0; + unsigned mult = 1; + static const char SEMICOLON = ';'; + + if (*(p + 2) == 'x') { + // Hexadecimal. + const char* q = p + 3; + if (!(*q)) { + return 0; + } + + q = strchr(q, SEMICOLON); + + if (!q) { + return 0; + } + TIXMLASSERT(*q == SEMICOLON); + + delta = q - p; + --q; + + while (*q != 'x') { + unsigned int digit = 0; + + if (*q >= '0' && *q <= '9') { + digit = *q - '0'; + } + else if (*q >= 'a' && *q <= 'f') { + digit = *q - 'a' + 10; + } + else if (*q >= 'A' && *q <= 'F') { + digit = *q - 'A' + 10; + } + else { + return 0; + } + TIXMLASSERT(digit < 16); + TIXMLASSERT(digit == 0 || mult <= UINT_MAX / digit); + const unsigned int digitScaled = mult * digit; + TIXMLASSERT(ucs <= ULONG_MAX - digitScaled); + ucs += digitScaled; + TIXMLASSERT(mult <= UINT_MAX / 16); + mult *= 16; + --q; + } + } + else { + // Decimal. + const char* q = p + 2; + if (!(*q)) { + return 0; + } + + q = strchr(q, SEMICOLON); + + if (!q) { + return 0; + } + TIXMLASSERT(*q == SEMICOLON); + + delta = q - p; + --q; + + while (*q != '#') { + if (*q >= '0' && *q <= '9') { + const unsigned int digit = *q - '0'; + TIXMLASSERT(digit < 10); + TIXMLASSERT(digit == 0 || mult <= UINT_MAX / digit); + const unsigned int digitScaled = mult * digit; + TIXMLASSERT(ucs <= ULONG_MAX - digitScaled); + ucs += digitScaled; + } + else { + return 0; + } + TIXMLASSERT(mult <= UINT_MAX / 10); + mult *= 10; + --q; + } + } + // convert the UCS to UTF-8 + ConvertUTF32ToUTF8(ucs, value, length); + return p + delta + 1; + } + return p + 1; + } + + + void XMLUtil::ToStr(int v, char* buffer, int bufferSize) + { + TIXML_SNPRINTF(buffer, bufferSize, "%d", v); + } + + + void XMLUtil::ToStr(unsigned v, char* buffer, int bufferSize) + { + TIXML_SNPRINTF(buffer, bufferSize, "%u", v); + } + + + void XMLUtil::ToStr(bool v, char* buffer, int bufferSize) + { + TIXML_SNPRINTF(buffer, bufferSize, "%s", v ? writeBoolTrue : writeBoolFalse); + } + + /* + ToStr() of a number is a very tricky topic. + https://github.com/leethomason/tinyxml2/issues/106 + */ + void XMLUtil::ToStr(float v, char* buffer, int bufferSize) + { + TIXML_SNPRINTF(buffer, bufferSize, "%.8g", v); + } + + + void XMLUtil::ToStr(double v, char* buffer, int bufferSize) + { + TIXML_SNPRINTF(buffer, bufferSize, "%.17g", v); + } + + + void XMLUtil::ToStr(int64_t v, char* buffer, int bufferSize) + { + // horrible syntax trick to make the compiler happy about %lld + TIXML_SNPRINTF(buffer, bufferSize, "%lld", (long long)v); + } + + + bool XMLUtil::ToInt(const char* str, int* value) + { + if (TIXML_SSCANF(str, "%d", value) == 1) { + return true; + } + return false; + } + + bool XMLUtil::ToUnsigned(const char* str, unsigned *value) + { + if (TIXML_SSCANF(str, "%u", value) == 1) { + return true; + } + return false; + } + + bool XMLUtil::ToBool(const char* str, bool* value) + { + int ival = 0; + if (ToInt(str, &ival)) { + *value = (ival == 0) ? false : true; + return true; + } + if (StringEqual(str, "true")) { + *value = true; + return true; + } + else if (StringEqual(str, "false")) { + *value = false; + return true; + } + return false; + } + + + bool XMLUtil::ToFloat(const char* str, float* value) + { + if (TIXML_SSCANF(str, "%f", value) == 1) { + return true; + } + return false; + } + + + bool XMLUtil::ToDouble(const char* str, double* value) + { + if (TIXML_SSCANF(str, "%lf", value) == 1) { + return true; + } + return false; + } + + + bool XMLUtil::ToInt64(const char* str, int64_t* value) + { + long long v = 0; // horrible syntax trick to make the compiler happy about %lld + if (TIXML_SSCANF(str, "%lld", &v) == 1) { + *value = (int64_t)v; + return true; + } + return false; + } + + + char* XMLDocument::Identify(char* p, XMLNode** node) + { + TIXMLASSERT(node); + TIXMLASSERT(p); + char* const start = p; + int const startLine = _parseCurLineNum; + p = XMLUtil::SkipWhiteSpace(p, &_parseCurLineNum); + if (!*p) { + *node = 0; + TIXMLASSERT(p); + return p; + } + + // These strings define the matching patterns: + static const char* xmlHeader = { "(_commentPool); + returnNode->_parseLineNum = _parseCurLineNum; + p += xmlHeaderLen; + } + else if (XMLUtil::StringEqual(p, commentHeader, commentHeaderLen)) { + returnNode = CreateUnlinkedNode(_commentPool); + returnNode->_parseLineNum = _parseCurLineNum; + p += commentHeaderLen; + } + else if (XMLUtil::StringEqual(p, cdataHeader, cdataHeaderLen)) { + XMLText* text = CreateUnlinkedNode(_textPool); + returnNode = text; + returnNode->_parseLineNum = _parseCurLineNum; + p += cdataHeaderLen; + text->SetCData(true); + } + else if (XMLUtil::StringEqual(p, dtdHeader, dtdHeaderLen)) { + returnNode = CreateUnlinkedNode(_commentPool); + returnNode->_parseLineNum = _parseCurLineNum; + p += dtdHeaderLen; + } + else if (XMLUtil::StringEqual(p, elementHeader, elementHeaderLen)) { + returnNode = CreateUnlinkedNode(_elementPool); + returnNode->_parseLineNum = _parseCurLineNum; + p += elementHeaderLen; + } + else { + returnNode = CreateUnlinkedNode(_textPool); + returnNode->_parseLineNum = _parseCurLineNum; // Report line of first non-whitespace character + p = start; // Back it up, all the text counts. + _parseCurLineNum = startLine; + } + + TIXMLASSERT(returnNode); + TIXMLASSERT(p); + *node = returnNode; + return p; + } + + + bool XMLDocument::Accept(XMLVisitor* visitor) const + { + TIXMLASSERT(visitor); + if (visitor->VisitEnter(*this)) { + for (const XMLNode* node = FirstChild(); node; node = node->NextSibling()) { + if (!node->Accept(visitor)) { + break; + } + } + } + return visitor->VisitExit(*this); + } + + + // --------- XMLNode ----------- // + + XMLNode::XMLNode(XMLDocument* doc) : + _document(doc), + _parent(0), + _value(), + _parseLineNum(0), + _firstChild(0), _lastChild(0), + _prev(0), _next(0), + _userData(0), + _memPool(0) + { + } + + + XMLNode::~XMLNode() + { + DeleteChildren(); + if (_parent) { + _parent->Unlink(this); + } + } + + const char* XMLNode::Value() const + { + // Edge case: XMLDocuments don't have a Value. Return null. + if (this->ToDocument()) + return 0; + return _value.GetStr(); + } + + void XMLNode::SetValue(const char* str, bool staticMem) + { + if (staticMem) { + _value.SetInternedStr(str); + } + else { + _value.SetStr(str); + } + } + + XMLNode* XMLNode::DeepClone(XMLDocument* target) const + { + XMLNode* clone = this->ShallowClone(target); + if (!clone) return 0; + + for (const XMLNode* child = this->FirstChild(); child; child = child->NextSibling()) { + XMLNode* childClone = child->DeepClone(target); + TIXMLASSERT(childClone); + clone->InsertEndChild(childClone); + } + return clone; + } + + void XMLNode::DeleteChildren() + { + while (_firstChild) { + TIXMLASSERT(_lastChild); + DeleteChild(_firstChild); + } + _firstChild = _lastChild = 0; + } + + + void XMLNode::Unlink(XMLNode* child) + { + TIXMLASSERT(child); + TIXMLASSERT(child->_document == _document); + TIXMLASSERT(child->_parent == this); + if (child == _firstChild) { + _firstChild = _firstChild->_next; + } + if (child == _lastChild) { + _lastChild = _lastChild->_prev; + } + + if (child->_prev) { + child->_prev->_next = child->_next; + } + if (child->_next) { + child->_next->_prev = child->_prev; + } + child->_next = 0; + child->_prev = 0; + child->_parent = 0; + } + + + void XMLNode::DeleteChild(XMLNode* node) + { + TIXMLASSERT(node); + TIXMLASSERT(node->_document == _document); + TIXMLASSERT(node->_parent == this); + Unlink(node); + TIXMLASSERT(node->_prev == 0); + TIXMLASSERT(node->_next == 0); + TIXMLASSERT(node->_parent == 0); + DeleteNode(node); + } + + + XMLNode* XMLNode::InsertEndChild(XMLNode* addThis) + { + TIXMLASSERT(addThis); + if (addThis->_document != _document) { + TIXMLASSERT(false); + return 0; + } + InsertChildPreamble(addThis); + + if (_lastChild) { + TIXMLASSERT(_firstChild); + TIXMLASSERT(_lastChild->_next == 0); + _lastChild->_next = addThis; + addThis->_prev = _lastChild; + _lastChild = addThis; + + addThis->_next = 0; + } + else { + TIXMLASSERT(_firstChild == 0); + _firstChild = _lastChild = addThis; + + addThis->_prev = 0; + addThis->_next = 0; + } + addThis->_parent = this; + return addThis; + } + + + XMLNode* XMLNode::InsertFirstChild(XMLNode* addThis) + { + TIXMLASSERT(addThis); + if (addThis->_document != _document) { + TIXMLASSERT(false); + return 0; + } + InsertChildPreamble(addThis); + + if (_firstChild) { + TIXMLASSERT(_lastChild); + TIXMLASSERT(_firstChild->_prev == 0); + + _firstChild->_prev = addThis; + addThis->_next = _firstChild; + _firstChild = addThis; + + addThis->_prev = 0; + } + else { + TIXMLASSERT(_lastChild == 0); + _firstChild = _lastChild = addThis; + + addThis->_prev = 0; + addThis->_next = 0; + } + addThis->_parent = this; + return addThis; + } + + + XMLNode* XMLNode::InsertAfterChild(XMLNode* afterThis, XMLNode* addThis) + { + TIXMLASSERT(addThis); + if (addThis->_document != _document) { + TIXMLASSERT(false); + return 0; + } + + TIXMLASSERT(afterThis); + + if (afterThis->_parent != this) { + TIXMLASSERT(false); + return 0; + } + if (afterThis == addThis) { + // Current state: BeforeThis -> AddThis -> OneAfterAddThis + // Now AddThis must disappear from it's location and then + // reappear between BeforeThis and OneAfterAddThis. + // So just leave it where it is. + return addThis; + } + + if (afterThis->_next == 0) { + // The last node or the only node. + return InsertEndChild(addThis); + } + InsertChildPreamble(addThis); + addThis->_prev = afterThis; + addThis->_next = afterThis->_next; + afterThis->_next->_prev = addThis; + afterThis->_next = addThis; + addThis->_parent = this; + return addThis; + } + + + + + const XMLElement* XMLNode::FirstChildElement(const char* name) const + { + for (const XMLNode* node = _firstChild; node; node = node->_next) { + const XMLElement* element = node->ToElementWithName(name); + if (element) { + return element; + } + } + return 0; + } + + + const XMLElement* XMLNode::LastChildElement(const char* name) const + { + for (const XMLNode* node = _lastChild; node; node = node->_prev) { + const XMLElement* element = node->ToElementWithName(name); + if (element) { + return element; + } + } + return 0; + } + + + const XMLElement* XMLNode::NextSiblingElement(const char* name) const + { + for (const XMLNode* node = _next; node; node = node->_next) { + const XMLElement* element = node->ToElementWithName(name); + if (element) { + return element; + } + } + return 0; + } + + + const XMLElement* XMLNode::PreviousSiblingElement(const char* name) const + { + for (const XMLNode* node = _prev; node; node = node->_prev) { + const XMLElement* element = node->ToElementWithName(name); + if (element) { + return element; + } + } + return 0; + } + + + char* XMLNode::ParseDeep(char* p, StrPair* parentEndTag, int* curLineNumPtr) + { + // This is a recursive method, but thinking about it "at the current level" + // it is a pretty simple flat list: + // + // + // + // With a special case: + // + // + // + // + // Where the closing element (/foo) *must* be the next thing after the opening + // element, and the names must match. BUT the tricky bit is that the closing + // element will be read by the child. + // + // 'endTag' is the end tag for this node, it is returned by a call to a child. + // 'parentEnd' is the end tag for the parent, which is filled in and returned. + + while (p && *p) { + XMLNode* node = 0; + + p = _document->Identify(p, &node); + TIXMLASSERT(p); + if (node == 0) { + break; + } + + int initialLineNum = node->_parseLineNum; + + StrPair endTag; + p = node->ParseDeep(p, &endTag, curLineNumPtr); + if (!p) { + DeleteNode(node); + if (!_document->Error()) { + _document->SetError(XML_ERROR_PARSING, 0, 0, initialLineNum); + } + break; + } + + XMLDeclaration* decl = node->ToDeclaration(); + if (decl) { + // Declarations are only allowed at document level + bool wellLocated = (ToDocument() != 0); + if (wellLocated) { + // Multiple declarations are allowed but all declarations + // must occur before anything else + for (const XMLNode* existingNode = _document->FirstChild(); existingNode; existingNode = existingNode->NextSibling()) { + if (!existingNode->ToDeclaration()) { + wellLocated = false; + break; + } + } + } + if (!wellLocated) { + _document->SetError(XML_ERROR_PARSING_DECLARATION, decl->Value(), 0, initialLineNum); + DeleteNode(node); + break; + } + } + + XMLElement* ele = node->ToElement(); + if (ele) { + // We read the end tag. Return it to the parent. + if (ele->ClosingType() == XMLElement::CLOSING) { + if (parentEndTag) { + ele->_value.TransferTo(parentEndTag); + } + node->_memPool->SetTracked(); // created and then immediately deleted. + DeleteNode(node); + return p; + } + + // Handle an end tag returned to this level. + // And handle a bunch of annoying errors. + bool mismatch = false; + if (endTag.Empty()) { + if (ele->ClosingType() == XMLElement::OPEN) { + mismatch = true; + } + } + else { + if (ele->ClosingType() != XMLElement::OPEN) { + mismatch = true; + } + else if (!XMLUtil::StringEqual(endTag.GetStr(), ele->Name())) { + mismatch = true; + } + } + if (mismatch) { + _document->SetError(XML_ERROR_MISMATCHED_ELEMENT, ele->Name(), 0, initialLineNum); + DeleteNode(node); + break; + } + } + InsertEndChild(node); + } + return 0; + } + + /*static*/ void XMLNode::DeleteNode(XMLNode* node) + { + if (node == 0) { + return; + } + TIXMLASSERT(node->_document); + if (!node->ToDocument()) { + node->_document->MarkInUse(node); + } + + MemPool* pool = node->_memPool; + node->~XMLNode(); + pool->Free(node); + } + + void XMLNode::InsertChildPreamble(XMLNode* insertThis) const + { + TIXMLASSERT(insertThis); + TIXMLASSERT(insertThis->_document == _document); + + if (insertThis->_parent) { + insertThis->_parent->Unlink(insertThis); + } + else { + insertThis->_document->MarkInUse(insertThis); + insertThis->_memPool->SetTracked(); + } + } + + const XMLElement* XMLNode::ToElementWithName(const char* name) const + { + const XMLElement* element = this->ToElement(); + if (element == 0) { + return 0; + } + if (name == 0) { + return element; + } + if (XMLUtil::StringEqual(element->Name(), name)) { + return element; + } + return 0; + } + + // --------- XMLText ---------- // + char* XMLText::ParseDeep(char* p, StrPair*, int* curLineNumPtr) + { + const char* start = p; + if (this->CData()) { + p = _value.ParseText(p, "]]>", StrPair::NEEDS_NEWLINE_NORMALIZATION, curLineNumPtr); + if (!p) { + _document->SetError(XML_ERROR_PARSING_CDATA, start, 0, _parseLineNum); + } + return p; + } + else { + int flags = _document->ProcessEntities() ? StrPair::TEXT_ELEMENT : StrPair::TEXT_ELEMENT_LEAVE_ENTITIES; + if (_document->WhitespaceMode() == COLLAPSE_WHITESPACE) { + flags |= StrPair::NEEDS_WHITESPACE_COLLAPSING; + } + + p = _value.ParseText(p, "<", flags, curLineNumPtr); + if (p && *p) { + return p - 1; + } + if (!p) { + _document->SetError(XML_ERROR_PARSING_TEXT, start, 0, _parseLineNum); + } + } + return 0; + } + + + XMLNode* XMLText::ShallowClone(XMLDocument* doc) const + { + if (!doc) { + doc = _document; + } + XMLText* text = doc->NewText(Value()); // fixme: this will always allocate memory. Intern? + text->SetCData(this->CData()); + return text; + } + + + bool XMLText::ShallowEqual(const XMLNode* compare) const + { + TIXMLASSERT(compare); + const XMLText* text = compare->ToText(); + return (text && XMLUtil::StringEqual(text->Value(), Value())); + } + + + bool XMLText::Accept(XMLVisitor* visitor) const + { + TIXMLASSERT(visitor); + return visitor->Visit(*this); + } + + + // --------- XMLComment ---------- // + + XMLComment::XMLComment(XMLDocument* doc) : XMLNode(doc) + { + } + + + XMLComment::~XMLComment() + { + } + + + char* XMLComment::ParseDeep(char* p, StrPair*, int* curLineNumPtr) + { + // Comment parses as text. + const char* start = p; + p = _value.ParseText(p, "-->", StrPair::COMMENT, curLineNumPtr); + if (p == 0) { + _document->SetError(XML_ERROR_PARSING_COMMENT, start, 0, _parseLineNum); + } + return p; + } + + + XMLNode* XMLComment::ShallowClone(XMLDocument* doc) const + { + if (!doc) { + doc = _document; + } + XMLComment* comment = doc->NewComment(Value()); // fixme: this will always allocate memory. Intern? + return comment; + } + + + bool XMLComment::ShallowEqual(const XMLNode* compare) const + { + TIXMLASSERT(compare); + const XMLComment* comment = compare->ToComment(); + return (comment && XMLUtil::StringEqual(comment->Value(), Value())); + } + + + bool XMLComment::Accept(XMLVisitor* visitor) const + { + TIXMLASSERT(visitor); + return visitor->Visit(*this); + } + + + // --------- XMLDeclaration ---------- // + + XMLDeclaration::XMLDeclaration(XMLDocument* doc) : XMLNode(doc) + { + } + + + XMLDeclaration::~XMLDeclaration() + { + //printf( "~XMLDeclaration\n" ); + } + + + char* XMLDeclaration::ParseDeep(char* p, StrPair*, int* curLineNumPtr) + { + // Declaration parses as text. + const char* start = p; + p = _value.ParseText(p, "?>", StrPair::NEEDS_NEWLINE_NORMALIZATION, curLineNumPtr); + if (p == 0) { + _document->SetError(XML_ERROR_PARSING_DECLARATION, start, 0, _parseLineNum); + } + return p; + } + + + XMLNode* XMLDeclaration::ShallowClone(XMLDocument* doc) const + { + if (!doc) { + doc = _document; + } + XMLDeclaration* dec = doc->NewDeclaration(Value()); // fixme: this will always allocate memory. Intern? + return dec; + } + + + bool XMLDeclaration::ShallowEqual(const XMLNode* compare) const + { + TIXMLASSERT(compare); + const XMLDeclaration* declaration = compare->ToDeclaration(); + return (declaration && XMLUtil::StringEqual(declaration->Value(), Value())); + } + + + + bool XMLDeclaration::Accept(XMLVisitor* visitor) const + { + TIXMLASSERT(visitor); + return visitor->Visit(*this); + } + + // --------- XMLUnknown ---------- // + + XMLUnknown::XMLUnknown(XMLDocument* doc) : XMLNode(doc) + { + } + + + XMLUnknown::~XMLUnknown() + { + } + + + char* XMLUnknown::ParseDeep(char* p, StrPair*, int* curLineNumPtr) + { + // Unknown parses as text. + const char* start = p; + + p = _value.ParseText(p, ">", StrPair::NEEDS_NEWLINE_NORMALIZATION, curLineNumPtr); + if (!p) { + _document->SetError(XML_ERROR_PARSING_UNKNOWN, start, 0, _parseLineNum); + } + return p; + } + + + XMLNode* XMLUnknown::ShallowClone(XMLDocument* doc) const + { + if (!doc) { + doc = _document; + } + XMLUnknown* text = doc->NewUnknown(Value()); // fixme: this will always allocate memory. Intern? + return text; + } + + + bool XMLUnknown::ShallowEqual(const XMLNode* compare) const + { + TIXMLASSERT(compare); + const XMLUnknown* unknown = compare->ToUnknown(); + return (unknown && XMLUtil::StringEqual(unknown->Value(), Value())); + } + + + bool XMLUnknown::Accept(XMLVisitor* visitor) const + { + TIXMLASSERT(visitor); + return visitor->Visit(*this); + } + + // --------- XMLAttribute ---------- // + + const char* XMLAttribute::Name() const + { + return _name.GetStr(); + } + + const char* XMLAttribute::Value() const + { + return _value.GetStr(); + } + + char* XMLAttribute::ParseDeep(char* p, bool processEntities, int* curLineNumPtr) + { + // Parse using the name rules: bug fix, was using ParseText before + p = _name.ParseName(p); + if (!p || !*p) { + return 0; + } + + // Skip white space before = + p = XMLUtil::SkipWhiteSpace(p, curLineNumPtr); + if (*p != '=') { + return 0; + } + + ++p; // move up to opening quote + p = XMLUtil::SkipWhiteSpace(p, curLineNumPtr); + if (*p != '\"' && *p != '\'') { + return 0; + } + + char endTag[2] = { *p, 0 }; + ++p; // move past opening quote + + p = _value.ParseText(p, endTag, processEntities ? StrPair::ATTRIBUTE_VALUE : StrPair::ATTRIBUTE_VALUE_LEAVE_ENTITIES, curLineNumPtr); + return p; + } + + + void XMLAttribute::SetName(const char* n) + { + _name.SetStr(n); + } + + + XMLError XMLAttribute::QueryIntValue(int* value) const + { + if (XMLUtil::ToInt(Value(), value)) { + return XML_SUCCESS; + } + return XML_WRONG_ATTRIBUTE_TYPE; + } + + + XMLError XMLAttribute::QueryUnsignedValue(unsigned int* value) const + { + if (XMLUtil::ToUnsigned(Value(), value)) { + return XML_SUCCESS; + } + return XML_WRONG_ATTRIBUTE_TYPE; + } + + + XMLError XMLAttribute::QueryInt64Value(int64_t* value) const + { + if (XMLUtil::ToInt64(Value(), value)) { + return XML_SUCCESS; + } + return XML_WRONG_ATTRIBUTE_TYPE; + } + + + XMLError XMLAttribute::QueryBoolValue(bool* value) const + { + if (XMLUtil::ToBool(Value(), value)) { + return XML_SUCCESS; + } + return XML_WRONG_ATTRIBUTE_TYPE; + } + + + XMLError XMLAttribute::QueryFloatValue(float* value) const + { + if (XMLUtil::ToFloat(Value(), value)) { + return XML_SUCCESS; + } + return XML_WRONG_ATTRIBUTE_TYPE; + } + + + XMLError XMLAttribute::QueryDoubleValue(double* value) const + { + if (XMLUtil::ToDouble(Value(), value)) { + return XML_SUCCESS; + } + return XML_WRONG_ATTRIBUTE_TYPE; + } + + + void XMLAttribute::SetAttribute(const char* v) + { + _value.SetStr(v); + } + + + void XMLAttribute::SetAttribute(int v) + { + char buf[BUF_SIZE]; + XMLUtil::ToStr(v, buf, BUF_SIZE); + _value.SetStr(buf); + } + + + void XMLAttribute::SetAttribute(unsigned v) + { + char buf[BUF_SIZE]; + XMLUtil::ToStr(v, buf, BUF_SIZE); + _value.SetStr(buf); + } + + + void XMLAttribute::SetAttribute(int64_t v) + { + char buf[BUF_SIZE]; + XMLUtil::ToStr(v, buf, BUF_SIZE); + _value.SetStr(buf); + } + + + + void XMLAttribute::SetAttribute(bool v) + { + char buf[BUF_SIZE]; + XMLUtil::ToStr(v, buf, BUF_SIZE); + _value.SetStr(buf); + } + + void XMLAttribute::SetAttribute(double v) + { + char buf[BUF_SIZE]; + XMLUtil::ToStr(v, buf, BUF_SIZE); + _value.SetStr(buf); + } + + void XMLAttribute::SetAttribute(float v) + { + char buf[BUF_SIZE]; + XMLUtil::ToStr(v, buf, BUF_SIZE); + _value.SetStr(buf); + } + + + // --------- XMLElement ---------- // + XMLElement::XMLElement(XMLDocument* doc) : XMLNode(doc), + _closingType(OPEN), + _rootAttribute(0) + { + } + + + XMLElement::~XMLElement() + { + while (_rootAttribute) { + XMLAttribute* next = _rootAttribute->_next; + DeleteAttribute(_rootAttribute); + _rootAttribute = next; + } + } + + + const XMLAttribute* XMLElement::FindAttribute(const char* name) const + { + for (XMLAttribute* a = _rootAttribute; a; a = a->_next) { + if (XMLUtil::StringEqual(a->Name(), name)) { + return a; + } + } + return 0; + } + + + const char* XMLElement::Attribute(const char* name, const char* value) const + { + const XMLAttribute* a = FindAttribute(name); + if (!a) { + return 0; + } + if (!value || XMLUtil::StringEqual(a->Value(), value)) { + return a->Value(); + } + return 0; + } + + int XMLElement::IntAttribute(const char* name, int defaultValue) const + { + int i = defaultValue; + QueryIntAttribute(name, &i); + return i; + } + + unsigned XMLElement::UnsignedAttribute(const char* name, unsigned defaultValue) const + { + unsigned i = defaultValue; + QueryUnsignedAttribute(name, &i); + return i; + } + + int64_t XMLElement::Int64Attribute(const char* name, int64_t defaultValue) const + { + int64_t i = defaultValue; + QueryInt64Attribute(name, &i); + return i; + } + + bool XMLElement::BoolAttribute(const char* name, bool defaultValue) const + { + bool b = defaultValue; + QueryBoolAttribute(name, &b); + return b; + } + + double XMLElement::DoubleAttribute(const char* name, double defaultValue) const + { + double d = defaultValue; + QueryDoubleAttribute(name, &d); + return d; + } + + float XMLElement::FloatAttribute(const char* name, float defaultValue) const + { + float f = defaultValue; + QueryFloatAttribute(name, &f); + return f; + } + + const char* XMLElement::GetText() const + { + if (FirstChild() && FirstChild()->ToText()) { + return FirstChild()->Value(); + } + return 0; + } + + + void XMLElement::SetText(const char* inText) + { + if (FirstChild() && FirstChild()->ToText()) + FirstChild()->SetValue(inText); + else { + XMLText* theText = GetDocument()->NewText(inText); + InsertFirstChild(theText); + } + } + + + void XMLElement::SetText(int v) + { + char buf[BUF_SIZE]; + XMLUtil::ToStr(v, buf, BUF_SIZE); + SetText(buf); + } + + + void XMLElement::SetText(unsigned v) + { + char buf[BUF_SIZE]; + XMLUtil::ToStr(v, buf, BUF_SIZE); + SetText(buf); + } + + + void XMLElement::SetText(int64_t v) + { + char buf[BUF_SIZE]; + XMLUtil::ToStr(v, buf, BUF_SIZE); + SetText(buf); + } + + + void XMLElement::SetText(bool v) + { + char buf[BUF_SIZE]; + XMLUtil::ToStr(v, buf, BUF_SIZE); + SetText(buf); + } + + + void XMLElement::SetText(float v) + { + char buf[BUF_SIZE]; + XMLUtil::ToStr(v, buf, BUF_SIZE); + SetText(buf); + } + + + void XMLElement::SetText(double v) + { + char buf[BUF_SIZE]; + XMLUtil::ToStr(v, buf, BUF_SIZE); + SetText(buf); + } + + + XMLError XMLElement::QueryIntText(int* ival) const + { + if (FirstChild() && FirstChild()->ToText()) { + const char* t = FirstChild()->Value(); + if (XMLUtil::ToInt(t, ival)) { + return XML_SUCCESS; + } + return XML_CAN_NOT_CONVERT_TEXT; + } + return XML_NO_TEXT_NODE; + } + + + XMLError XMLElement::QueryUnsignedText(unsigned* uval) const + { + if (FirstChild() && FirstChild()->ToText()) { + const char* t = FirstChild()->Value(); + if (XMLUtil::ToUnsigned(t, uval)) { + return XML_SUCCESS; + } + return XML_CAN_NOT_CONVERT_TEXT; + } + return XML_NO_TEXT_NODE; + } + + + XMLError XMLElement::QueryInt64Text(int64_t* ival) const + { + if (FirstChild() && FirstChild()->ToText()) { + const char* t = FirstChild()->Value(); + if (XMLUtil::ToInt64(t, ival)) { + return XML_SUCCESS; + } + return XML_CAN_NOT_CONVERT_TEXT; + } + return XML_NO_TEXT_NODE; + } + + + XMLError XMLElement::QueryBoolText(bool* bval) const + { + if (FirstChild() && FirstChild()->ToText()) { + const char* t = FirstChild()->Value(); + if (XMLUtil::ToBool(t, bval)) { + return XML_SUCCESS; + } + return XML_CAN_NOT_CONVERT_TEXT; + } + return XML_NO_TEXT_NODE; + } + + + XMLError XMLElement::QueryDoubleText(double* dval) const + { + if (FirstChild() && FirstChild()->ToText()) { + const char* t = FirstChild()->Value(); + if (XMLUtil::ToDouble(t, dval)) { + return XML_SUCCESS; + } + return XML_CAN_NOT_CONVERT_TEXT; + } + return XML_NO_TEXT_NODE; + } + + + XMLError XMLElement::QueryFloatText(float* fval) const + { + if (FirstChild() && FirstChild()->ToText()) { + const char* t = FirstChild()->Value(); + if (XMLUtil::ToFloat(t, fval)) { + return XML_SUCCESS; + } + return XML_CAN_NOT_CONVERT_TEXT; + } + return XML_NO_TEXT_NODE; + } + + int XMLElement::IntText(int defaultValue) const + { + int i = defaultValue; + QueryIntText(&i); + return i; + } + + unsigned XMLElement::UnsignedText(unsigned defaultValue) const + { + unsigned i = defaultValue; + QueryUnsignedText(&i); + return i; + } + + int64_t XMLElement::Int64Text(int64_t defaultValue) const + { + int64_t i = defaultValue; + QueryInt64Text(&i); + return i; + } + + bool XMLElement::BoolText(bool defaultValue) const + { + bool b = defaultValue; + QueryBoolText(&b); + return b; + } + + double XMLElement::DoubleText(double defaultValue) const + { + double d = defaultValue; + QueryDoubleText(&d); + return d; + } + + float XMLElement::FloatText(float defaultValue) const + { + float f = defaultValue; + QueryFloatText(&f); + return f; + } + + + XMLAttribute* XMLElement::FindOrCreateAttribute(const char* name) + { + XMLAttribute* last = 0; + XMLAttribute* attrib = 0; + for (attrib = _rootAttribute; + attrib; + last = attrib, attrib = attrib->_next) { + if (XMLUtil::StringEqual(attrib->Name(), name)) { + break; + } + } + if (!attrib) { + attrib = CreateAttribute(); + TIXMLASSERT(attrib); + if (last) { + TIXMLASSERT(last->_next == 0); + last->_next = attrib; + } + else { + TIXMLASSERT(_rootAttribute == 0); + _rootAttribute = attrib; + } + attrib->SetName(name); + } + return attrib; + } + + + void XMLElement::DeleteAttribute(const char* name) + { + XMLAttribute* prev = 0; + for (XMLAttribute* a = _rootAttribute; a; a = a->_next) { + if (XMLUtil::StringEqual(name, a->Name())) { + if (prev) { + prev->_next = a->_next; + } + else { + _rootAttribute = a->_next; + } + DeleteAttribute(a); + break; + } + prev = a; + } + } + + + char* XMLElement::ParseAttributes(char* p, int* curLineNumPtr) + { + const char* start = p; + XMLAttribute* prevAttribute = 0; + + // Read the attributes. + while (p) { + p = XMLUtil::SkipWhiteSpace(p, curLineNumPtr); + if (!(*p)) { + _document->SetError(XML_ERROR_PARSING_ELEMENT, start, Name(), _parseLineNum); + return 0; + } + + // attribute. + if (XMLUtil::IsNameStartChar(*p)) { + XMLAttribute* attrib = CreateAttribute(); + TIXMLASSERT(attrib); + attrib->_parseLineNum = _document->_parseCurLineNum; + + int attrLineNum = attrib->_parseLineNum; + + p = attrib->ParseDeep(p, _document->ProcessEntities(), curLineNumPtr); + if (!p || Attribute(attrib->Name())) { + DeleteAttribute(attrib); + _document->SetError(XML_ERROR_PARSING_ATTRIBUTE, start, p, attrLineNum); + return 0; + } + // There is a minor bug here: if the attribute in the source xml + // document is duplicated, it will not be detected and the + // attribute will be doubly added. However, tracking the 'prevAttribute' + // avoids re-scanning the attribute list. Preferring performance for + // now, may reconsider in the future. + if (prevAttribute) { + TIXMLASSERT(prevAttribute->_next == 0); + prevAttribute->_next = attrib; + } + else { + TIXMLASSERT(_rootAttribute == 0); + _rootAttribute = attrib; + } + prevAttribute = attrib; + } + // end of the tag + else if (*p == '>') { + ++p; + break; + } + // end of the tag + else if (*p == '/' && *(p + 1) == '>') { + _closingType = CLOSED; + return p + 2; // done; sealed element. + } + else { + _document->SetError(XML_ERROR_PARSING_ELEMENT, start, p, _parseLineNum); + return 0; + } + } + return p; + } + + void XMLElement::DeleteAttribute(XMLAttribute* attribute) + { + if (attribute == 0) { + return; + } + MemPool* pool = attribute->_memPool; + attribute->~XMLAttribute(); + pool->Free(attribute); + } + + XMLAttribute* XMLElement::CreateAttribute() + { + TIXMLASSERT(sizeof(XMLAttribute) == _document->_attributePool.ItemSize()); + XMLAttribute* attrib = new (_document->_attributePool.Alloc()) XMLAttribute(); + TIXMLASSERT(attrib); + attrib->_memPool = &_document->_attributePool; + attrib->_memPool->SetTracked(); + return attrib; + } + + // + // + // foobar + // + char* XMLElement::ParseDeep(char* p, StrPair* parentEndTag, int* curLineNumPtr) + { + // Read the element name. + p = XMLUtil::SkipWhiteSpace(p, curLineNumPtr); + + // The closing element is the form. It is + // parsed just like a regular element then deleted from + // the DOM. + if (*p == '/') { + _closingType = CLOSING; + ++p; + } + + p = _value.ParseName(p); + if (_value.Empty()) { + return 0; + } + + p = ParseAttributes(p, curLineNumPtr); + if (!p || !*p || _closingType != OPEN) { + return p; + } + + p = XMLNode::ParseDeep(p, parentEndTag, curLineNumPtr); + return p; + } + + + + XMLNode* XMLElement::ShallowClone(XMLDocument* doc) const + { + if (!doc) { + doc = _document; + } + XMLElement* element = doc->NewElement(Value()); // fixme: this will always allocate memory. Intern? + for (const XMLAttribute* a = FirstAttribute(); a; a = a->Next()) { + element->SetAttribute(a->Name(), a->Value()); // fixme: this will always allocate memory. Intern? + } + return element; + } + + + bool XMLElement::ShallowEqual(const XMLNode* compare) const + { + TIXMLASSERT(compare); + const XMLElement* other = compare->ToElement(); + if (other && XMLUtil::StringEqual(other->Name(), Name())) { + + const XMLAttribute* a = FirstAttribute(); + const XMLAttribute* b = other->FirstAttribute(); + + while (a && b) { + if (!XMLUtil::StringEqual(a->Value(), b->Value())) { + return false; + } + a = a->Next(); + b = b->Next(); + } + if (a || b) { + // different count + return false; + } + return true; + } + return false; + } + + + bool XMLElement::Accept(XMLVisitor* visitor) const + { + TIXMLASSERT(visitor); + if (visitor->VisitEnter(*this, _rootAttribute)) { + for (const XMLNode* node = FirstChild(); node; node = node->NextSibling()) { + if (!node->Accept(visitor)) { + break; + } + } + } + return visitor->VisitExit(*this); + } + + + // --------- XMLDocument ----------- // + + // Warning: List must match 'enum XMLError' + const char* XMLDocument::_errorNames[XML_ERROR_COUNT] = { + "XML_SUCCESS", + "XML_NO_ATTRIBUTE", + "XML_WRONG_ATTRIBUTE_TYPE", + "XML_ERROR_FILE_NOT_FOUND", + "XML_ERROR_FILE_COULD_NOT_BE_OPENED", + "XML_ERROR_FILE_READ_ERROR", + "UNUSED_XML_ERROR_ELEMENT_MISMATCH", + "XML_ERROR_PARSING_ELEMENT", + "XML_ERROR_PARSING_ATTRIBUTE", + "UNUSED_XML_ERROR_IDENTIFYING_TAG", + "XML_ERROR_PARSING_TEXT", + "XML_ERROR_PARSING_CDATA", + "XML_ERROR_PARSING_COMMENT", + "XML_ERROR_PARSING_DECLARATION", + "XML_ERROR_PARSING_UNKNOWN", + "XML_ERROR_EMPTY_DOCUMENT", + "XML_ERROR_MISMATCHED_ELEMENT", + "XML_ERROR_PARSING", + "XML_CAN_NOT_CONVERT_TEXT", + "XML_NO_TEXT_NODE" + }; + + + XMLDocument::XMLDocument(bool processEntities, Whitespace whitespaceMode) : + XMLNode(0), + _writeBOM(false), + _processEntities(processEntities), + _errorID(XML_SUCCESS), + _whitespaceMode(whitespaceMode), + _errorStr1(), + _errorStr2(), + _errorLineNum(0), + _charBuffer(0), + _parseCurLineNum(0), + _unlinked(), + _elementPool(), + _attributePool(), + _textPool(), + _commentPool() + { + // avoid VC++ C4355 warning about 'this' in initializer list (C4355 is off by default in VS2012+) + _document = this; + } + + + XMLDocument::~XMLDocument() + { + Clear(); + } + + + void XMLDocument::MarkInUse(XMLNode* node) + { + TIXMLASSERT(node); + TIXMLASSERT(node->_parent == 0); + + for (int i = 0; i < _unlinked.Size(); ++i) { + if (node == _unlinked[i]) { + _unlinked.SwapRemove(i); + break; + } + } + } + + void XMLDocument::Clear() + { + DeleteChildren(); + while (_unlinked.Size()) { + DeleteNode(_unlinked[0]); // Will remove from _unlinked as part of delete. + } + +#ifdef DEBUG + const bool hadError = Error(); +#endif + ClearError(); + + delete[] _charBuffer; + _charBuffer = 0; + +#if 0 + _textPool.Trace("text"); + _elementPool.Trace("element"); + _commentPool.Trace("comment"); + _attributePool.Trace("attribute"); +#endif + +#ifdef DEBUG + if (!hadError) { + TIXMLASSERT(_elementPool.CurrentAllocs() == _elementPool.Untracked()); + TIXMLASSERT(_attributePool.CurrentAllocs() == _attributePool.Untracked()); + TIXMLASSERT(_textPool.CurrentAllocs() == _textPool.Untracked()); + TIXMLASSERT(_commentPool.CurrentAllocs() == _commentPool.Untracked()); + } +#endif + } + + + void XMLDocument::DeepCopy(XMLDocument* target) const + { + TIXMLASSERT(target); + if (target == this) { + return; // technically success - a no-op. + } + + target->Clear(); + for (const XMLNode* node = this->FirstChild(); node; node = node->NextSibling()) { + target->InsertEndChild(node->DeepClone(target)); + } + } + + XMLElement* XMLDocument::NewElement(const char* name) + { + XMLElement* ele = CreateUnlinkedNode(_elementPool); + ele->SetName(name); + return ele; + } + + + XMLComment* XMLDocument::NewComment(const char* str) + { + XMLComment* comment = CreateUnlinkedNode(_commentPool); + comment->SetValue(str); + return comment; + } + + + XMLText* XMLDocument::NewText(const char* str) + { + XMLText* text = CreateUnlinkedNode(_textPool); + text->SetValue(str); + return text; + } + + + XMLDeclaration* XMLDocument::NewDeclaration(const char* str) + { + XMLDeclaration* dec = CreateUnlinkedNode(_commentPool); + dec->SetValue(str ? str : "xml version=\"1.0\" encoding=\"UTF-8\""); + return dec; + } + + + XMLUnknown* XMLDocument::NewUnknown(const char* str) + { + XMLUnknown* unk = CreateUnlinkedNode(_commentPool); + unk->SetValue(str); + return unk; + } + + static FILE* callfopen(const char* filepath, const char* mode) + { + TIXMLASSERT(filepath); + TIXMLASSERT(mode); +#if defined(_MSC_VER) && (_MSC_VER >= 1400 ) && (!defined WINCE) + FILE* fp = 0; + errno_t err = fopen_s(&fp, filepath, mode); + if (err) { + return 0; + } +#else + FILE* fp = fopen(filepath, mode); +#endif + return fp; + } + + void XMLDocument::DeleteNode(XMLNode* node) { + TIXMLASSERT(node); + TIXMLASSERT(node->_document == this); + if (node->_parent) { + node->_parent->DeleteChild(node); + } + else { + // Isn't in the tree. + // Use the parent delete. + // Also, we need to mark it tracked: we 'know' + // it was never used. + node->_memPool->SetTracked(); + // Call the static XMLNode version: + XMLNode::DeleteNode(node); + } + } + + + XMLError XMLDocument::LoadFile(const char* filename) + { + Clear(); + FILE* fp = callfopen(filename, "rb"); + if (!fp) { + SetError(XML_ERROR_FILE_NOT_FOUND, filename, 0, 0); + return _errorID; + } + LoadFile(fp); + fclose(fp); + return _errorID; + } + + // This is likely overengineered template art to have a check that unsigned long value incremented + // by one still fits into size_t. If size_t type is larger than unsigned long type + // (x86_64-w64-mingw32 target) then the check is redundant and gcc and clang emit + // -Wtype-limits warning. This piece makes the compiler select code with a check when a check + // is useful and code with no check when a check is redundant depending on how size_t and unsigned long + // types sizes relate to each other. + template + = sizeof(size_t))> + struct LongFitsIntoSizeTMinusOne { + static bool Fits(unsigned long value) + { + return value < (size_t)-1; + } + }; + + template <> + struct LongFitsIntoSizeTMinusOne { + static bool Fits(unsigned long) + { + return true; + } + }; + + XMLError XMLDocument::LoadFile(FILE* fp) + { + Clear(); + + fseek(fp, 0, SEEK_SET); + if (fgetc(fp) == EOF && ferror(fp) != 0) { + SetError(XML_ERROR_FILE_READ_ERROR, 0, 0, 0); + return _errorID; + } + + fseek(fp, 0, SEEK_END); + const long filelength = ftell(fp); + fseek(fp, 0, SEEK_SET); + if (filelength == -1L) { + SetError(XML_ERROR_FILE_READ_ERROR, 0, 0, 0); + return _errorID; + } + TIXMLASSERT(filelength >= 0); + + if (!LongFitsIntoSizeTMinusOne<>::Fits(filelength)) { + // Cannot handle files which won't fit in buffer together with null terminator + SetError(XML_ERROR_FILE_READ_ERROR, 0, 0, 0); + return _errorID; + } + + if (filelength == 0) { + SetError(XML_ERROR_EMPTY_DOCUMENT, 0, 0, 0); + return _errorID; + } + + const size_t size = filelength; + TIXMLASSERT(_charBuffer == 0); + _charBuffer = new char[size + 1]; + size_t read = fread(_charBuffer, 1, size, fp); + if (read != size) { + SetError(XML_ERROR_FILE_READ_ERROR, 0, 0, 0); + return _errorID; + } + + _charBuffer[size] = 0; + + Parse(); + return _errorID; + } + + + XMLError XMLDocument::SaveFile(const char* filename, bool compact) + { + FILE* fp = callfopen(filename, "w"); + if (!fp) { + SetError(XML_ERROR_FILE_COULD_NOT_BE_OPENED, filename, 0, 0); + return _errorID; + } + SaveFile(fp, compact); + fclose(fp); + return _errorID; + } + + + XMLError XMLDocument::SaveFile(FILE* fp, bool compact) + { + // Clear any error from the last save, otherwise it will get reported + // for *this* call. + ClearError(); + XMLPrinter stream(fp, compact); + Print(&stream); + return _errorID; + } + + + XMLError XMLDocument::Parse(const char* p, size_t len) + { + Clear(); + + if (len == 0 || !p || !*p) { + SetError(XML_ERROR_EMPTY_DOCUMENT, 0, 0, 0); + return _errorID; + } + if (len == (size_t)(-1)) { + len = strlen(p); + } + TIXMLASSERT(_charBuffer == 0); + _charBuffer = new char[len + 1]; + memcpy(_charBuffer, p, len); + _charBuffer[len] = 0; + + Parse(); + if (Error()) { + // clean up now essentially dangling memory. + // and the parse fail can put objects in the + // pools that are dead and inaccessible. + DeleteChildren(); + _elementPool.Clear(); + _attributePool.Clear(); + _textPool.Clear(); + _commentPool.Clear(); + } + return _errorID; + } + + + void XMLDocument::Print(XMLPrinter* streamer) const + { + if (streamer) { + Accept(streamer); + } + else { + XMLPrinter stdoutStreamer(stdout); + Accept(&stdoutStreamer); + } + } + + + void XMLDocument::SetError(XMLError error, const char* str1, const char* str2, int lineNum) + { + TIXMLASSERT(error >= 0 && error < XML_ERROR_COUNT); + _errorID = error; + + _errorStr1.Reset(); + _errorStr2.Reset(); + _errorLineNum = lineNum; + + if (str1) + _errorStr1.SetStr(str1); + if (str2) + _errorStr2.SetStr(str2); + } + + /*static*/ const char* XMLDocument::ErrorIDToName(XMLError errorID) + { + TIXMLASSERT(errorID >= 0 && errorID < XML_ERROR_COUNT); + const char* errorName = _errorNames[errorID]; + TIXMLASSERT(errorName && errorName[0]); + return errorName; + } + + const char* XMLDocument::GetErrorStr1() const + { + return _errorStr1.GetStr(); + } + + const char* XMLDocument::GetErrorStr2() const + { + return _errorStr2.GetStr(); + } + + const char* XMLDocument::ErrorName() const + { + return ErrorIDToName(_errorID); + } + + void XMLDocument::PrintError() const + { + if (Error()) { + static const int LEN = 20; + char buf1[LEN] = { 0 }; + char buf2[LEN] = { 0 }; + + if (!_errorStr1.Empty()) { + TIXML_SNPRINTF(buf1, LEN, "%s", _errorStr1.GetStr()); + } + if (!_errorStr2.Empty()) { + TIXML_SNPRINTF(buf2, LEN, "%s", _errorStr2.GetStr()); + } + + // Should check INT_MIN <= _errorID && _errorId <= INT_MAX, but that + // causes a clang "always true" -Wtautological-constant-out-of-range-compare warning + TIXMLASSERT(0 <= _errorID && XML_ERROR_COUNT - 1 <= INT_MAX); + printf("XMLDocument error id=%d '%s' str1=%s str2=%s line=%d\n", + static_cast(_errorID), ErrorName(), buf1, buf2, _errorLineNum); + } + } + + void XMLDocument::Parse() + { + TIXMLASSERT(NoChildren()); // Clear() must have been called previously + TIXMLASSERT(_charBuffer); + _parseCurLineNum = 1; + _parseLineNum = 1; + char* p = _charBuffer; + p = XMLUtil::SkipWhiteSpace(p, &_parseCurLineNum); + p = const_cast(XMLUtil::ReadBOM(p, &_writeBOM)); + if (!*p) { + SetError(XML_ERROR_EMPTY_DOCUMENT, 0, 0, 0); + return; + } + ParseDeep(p, 0, &_parseCurLineNum); + } + + XMLPrinter::XMLPrinter(FILE* file, bool compact, int depth) : + _elementJustOpened(false), + _stack(), + _firstElement(true), + _fp(file), + _depth(depth), + _textDepth(-1), + _processEntities(true), + _compactMode(compact), + _buffer() + { + for (int i = 0; i'] = true; // not required, but consistency is nice + _buffer.Push(0); + } + + + void XMLPrinter::Print(const char* format, ...) + { + va_list va; + va_start(va, format); + + if (_fp) { + vfprintf(_fp, format, va); + } + else { + const int len = TIXML_VSCPRINTF(format, va); + // Close out and re-start the va-args + va_end(va); + TIXMLASSERT(len >= 0); + va_start(va, format); + TIXMLASSERT(_buffer.Size() > 0 && _buffer[_buffer.Size() - 1] == 0); + char* p = _buffer.PushArr(len) - 1; // back up over the null terminator. + TIXML_VSNPRINTF(p, len + 1, format, va); + } + va_end(va); + } + + + void XMLPrinter::PrintSpace(int depth) + { + for (int i = 0; i 0 && *q < ENTITY_RANGE) { + // Check for entities. If one is found, flush + // the stream up until the entity, write the + // entity, and keep looking. + if (flag[(unsigned char)(*q)]) { + while (p < q) { + const size_t delta = q - p; + // %.*s accepts type int as "precision" + const int toPrint = (INT_MAX < delta) ? INT_MAX : (int)delta; + Print("%.*s", toPrint, p); + p += toPrint; + } + bool entityPatternPrinted = false; + for (int i = 0; i"); + } + else { + if (_textDepth < 0 && !compactMode) { + Print("\n"); + PrintSpace(_depth); + } + Print("", name); + } + + if (_textDepth == _depth) { + _textDepth = -1; + } + if (_depth == 0 && !compactMode) { + Print("\n"); + } + _elementJustOpened = false; + } + + + void XMLPrinter::SealElementIfJustOpened() + { + if (!_elementJustOpened) { + return; + } + _elementJustOpened = false; + Print(">"); + } + + + void XMLPrinter::PushText(const char* text, bool cdata) + { + _textDepth = _depth - 1; + + SealElementIfJustOpened(); + if (cdata) { + Print("", text); + } + else { + PrintString(text, true); + } + } + + void XMLPrinter::PushText(int64_t value) + { + char buf[BUF_SIZE]; + XMLUtil::ToStr(value, buf, BUF_SIZE); + PushText(buf, false); + } + + void XMLPrinter::PushText(int value) + { + char buf[BUF_SIZE]; + XMLUtil::ToStr(value, buf, BUF_SIZE); + PushText(buf, false); + } + + + void XMLPrinter::PushText(unsigned value) + { + char buf[BUF_SIZE]; + XMLUtil::ToStr(value, buf, BUF_SIZE); + PushText(buf, false); + } + + + void XMLPrinter::PushText(bool value) + { + char buf[BUF_SIZE]; + XMLUtil::ToStr(value, buf, BUF_SIZE); + PushText(buf, false); + } + + + void XMLPrinter::PushText(float value) + { + char buf[BUF_SIZE]; + XMLUtil::ToStr(value, buf, BUF_SIZE); + PushText(buf, false); + } + + + void XMLPrinter::PushText(double value) + { + char buf[BUF_SIZE]; + XMLUtil::ToStr(value, buf, BUF_SIZE); + PushText(buf, false); + } + + + void XMLPrinter::PushComment(const char* comment) + { + SealElementIfJustOpened(); + if (_textDepth < 0 && !_firstElement && !_compactMode) { + Print("\n"); + PrintSpace(_depth); + } + _firstElement = false; + Print("", comment); + } + + + void XMLPrinter::PushDeclaration(const char* value) + { + SealElementIfJustOpened(); + if (_textDepth < 0 && !_firstElement && !_compactMode) { + Print("\n"); + PrintSpace(_depth); + } + _firstElement = false; + Print("", value); + } + + + void XMLPrinter::PushUnknown(const char* value) + { + SealElementIfJustOpened(); + if (_textDepth < 0 && !_firstElement && !_compactMode) { + Print("\n"); + PrintSpace(_depth); + } + _firstElement = false; + Print("", value); + } + + + bool XMLPrinter::VisitEnter(const XMLDocument& doc) + { + _processEntities = doc.ProcessEntities(); + if (doc.HasBOM()) { + PushHeader(true, false); + } + return true; + } + + + bool XMLPrinter::VisitEnter(const XMLElement& element, const XMLAttribute* attribute) + { + const XMLElement* parentElem = 0; + if (element.Parent()) { + parentElem = element.Parent()->ToElement(); + } + const bool compactMode = parentElem ? CompactMode(*parentElem) : _compactMode; + OpenElement(element.Name(), compactMode); + while (attribute) { + PushAttribute(attribute->Name(), attribute->Value()); + attribute = attribute->Next(); + } + return true; + } + + + bool XMLPrinter::VisitExit(const XMLElement& element) + { + CloseElement(CompactMode(element)); + return true; + } + + + bool XMLPrinter::Visit(const XMLText& text) + { + PushText(text.Value(), text.CData()); + return true; + } + + + bool XMLPrinter::Visit(const XMLComment& comment) + { + PushComment(comment.Value()); + return true; + } + + bool XMLPrinter::Visit(const XMLDeclaration& declaration) + { + PushDeclaration(declaration.Value()); + return true; + } + + + bool XMLPrinter::Visit(const XMLUnknown& unknown) + { + PushUnknown(unknown.Value()); + return true; + } + +} // namespace tinyxml2 + diff --git a/src/util/tinyxml2/tinyxml2.h b/src/util/tinyxml2/tinyxml2.h new file mode 100644 index 00000000..20e0d20b --- /dev/null +++ b/src/util/tinyxml2/tinyxml2.h @@ -0,0 +1,2263 @@ +/* +Original code by Lee Thomason (www.grinninglizard.com) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + +#ifndef TINYXML2_INCLUDED +#define TINYXML2_INCLUDED + +#if defined(ANDROID_NDK) || defined(__BORLANDC__) || defined(__QNXNTO__) +# include +# include +# include +# include +# include +# if defined(__PS3__) +# include +# endif +#else +# include +# include +# include +# include +# include +#endif +#include + +/* +TODO: intern strings instead of allocation. +*/ +/* +gcc: +g++ -Wall -DDEBUG tinyxml2.cpp xmltest.cpp -o gccxmltest.exe + +Formatting, Artistic Style: +AStyle.exe --style=1tbs --indent-switches --break-closing-brackets --indent-preprocessor tinyxml2.cpp tinyxml2.h +*/ + +#if defined( _DEBUG ) || defined (__DEBUG__) +# ifndef DEBUG +# define DEBUG +# endif +#endif + +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable: 4251) +#endif + +#ifdef _WIN32 +# ifdef TINYXML2_EXPORT +# define TINYXML2_LIB __declspec(dllexport) +# elif defined(TINYXML2_IMPORT) +# define TINYXML2_LIB __declspec(dllimport) +# else +# define TINYXML2_LIB +# endif +#elif __GNUC__ >= 4 +# define TINYXML2_LIB __attribute__((visibility("default"))) +#else +# define TINYXML2_LIB +#endif + + +#if defined(DEBUG) +# if defined(_MSC_VER) +# // "(void)0," is for suppressing C4127 warning in "assert(false)", "assert(true)" and the like +# define TIXMLASSERT( x ) if ( !((void)0,(x))) { __debugbreak(); } +# elif defined (ANDROID_NDK) +# include +# define TIXMLASSERT( x ) if ( !(x)) { __android_log_assert( "assert", "grinliz", "ASSERT in '%s' at %d.", __FILE__, __LINE__ ); } +# else +# include +# define TIXMLASSERT assert +# endif +#else +# define TIXMLASSERT( x ) {} +#endif + + +/* Versioning, past 1.0.14: +http://semver.org/ +*/ +static const int TIXML2_MAJOR_VERSION = 5; +static const int TIXML2_MINOR_VERSION = 0; +static const int TIXML2_PATCH_VERSION = 1; + +namespace tinyxml2 +{ + class XMLDocument; + class XMLElement; + class XMLAttribute; + class XMLComment; + class XMLText; + class XMLDeclaration; + class XMLUnknown; + class XMLPrinter; + + /* + A class that wraps strings. Normally stores the start and end + pointers into the XML file itself, and will apply normalization + and entity translation if actually read. Can also store (and memory + manage) a traditional char[] + */ + class StrPair + { + public: + enum { + NEEDS_ENTITY_PROCESSING = 0x01, + NEEDS_NEWLINE_NORMALIZATION = 0x02, + NEEDS_WHITESPACE_COLLAPSING = 0x04, + + TEXT_ELEMENT = NEEDS_ENTITY_PROCESSING | NEEDS_NEWLINE_NORMALIZATION, + TEXT_ELEMENT_LEAVE_ENTITIES = NEEDS_NEWLINE_NORMALIZATION, + ATTRIBUTE_NAME = 0, + ATTRIBUTE_VALUE = NEEDS_ENTITY_PROCESSING | NEEDS_NEWLINE_NORMALIZATION, + ATTRIBUTE_VALUE_LEAVE_ENTITIES = NEEDS_NEWLINE_NORMALIZATION, + COMMENT = NEEDS_NEWLINE_NORMALIZATION + }; + + StrPair() : _flags(0), _start(0), _end(0) {} + ~StrPair(); + + void Set(char* start, char* end, int flags) { + TIXMLASSERT(start); + TIXMLASSERT(end); + Reset(); + _start = start; + _end = end; + _flags = flags | NEEDS_FLUSH; + } + + const char* GetStr(); + + bool Empty() const { + return _start == _end; + } + + void SetInternedStr(const char* str) { + Reset(); + _start = const_cast(str); + } + + void SetStr(const char* str, int flags = 0); + + char* ParseText(char* in, const char* endTag, int strFlags, int* curLineNumPtr); + char* ParseName(char* in); + + void TransferTo(StrPair* other); + void Reset(); + + private: + void CollapseWhitespace(); + + enum { + NEEDS_FLUSH = 0x100, + NEEDS_DELETE = 0x200 + }; + + int _flags; + char* _start; + char* _end; + + StrPair(const StrPair& other); // not supported + void operator=(StrPair& other); // not supported, use TransferTo() + }; + + + /* + A dynamic array of Plain Old Data. Doesn't support constructors, etc. + Has a small initial memory pool, so that low or no usage will not + cause a call to new/delete + */ + template + class DynArray + { + public: + DynArray() : + _mem(_pool), + _allocated(INITIAL_SIZE), + _size(0) + { + } + + ~DynArray() { + if (_mem != _pool) { + delete[] _mem; + } + } + + void Clear() { + _size = 0; + } + + void Push(T t) { + TIXMLASSERT(_size < INT_MAX); + EnsureCapacity(_size + 1); + _mem[_size] = t; + ++_size; + } + + T* PushArr(int count) { + TIXMLASSERT(count >= 0); + TIXMLASSERT(_size <= INT_MAX - count); + EnsureCapacity(_size + count); + T* ret = &_mem[_size]; + _size += count; + return ret; + } + + T Pop() { + TIXMLASSERT(_size > 0); + --_size; + return _mem[_size]; + } + + void PopArr(int count) { + TIXMLASSERT(_size >= count); + _size -= count; + } + + bool Empty() const { + return _size == 0; + } + + T& operator[](int i) { + TIXMLASSERT(i >= 0 && i < _size); + return _mem[i]; + } + + const T& operator[](int i) const { + TIXMLASSERT(i >= 0 && i < _size); + return _mem[i]; + } + + const T& PeekTop() const { + TIXMLASSERT(_size > 0); + return _mem[_size - 1]; + } + + int Size() const { + TIXMLASSERT(_size >= 0); + return _size; + } + + int Capacity() const { + TIXMLASSERT(_allocated >= INITIAL_SIZE); + return _allocated; + } + + void SwapRemove(int i) { + TIXMLASSERT(i >= 0 && i < _size); + TIXMLASSERT(_size > 0); + _mem[i] = _mem[_size - 1]; + --_size; + } + + const T* Mem() const { + TIXMLASSERT(_mem); + return _mem; + } + + T* Mem() { + TIXMLASSERT(_mem); + return _mem; + } + + private: + DynArray(const DynArray&); // not supported + void operator=(const DynArray&); // not supported + + void EnsureCapacity(int cap) { + TIXMLASSERT(cap > 0); + if (cap > _allocated) { + TIXMLASSERT(cap <= INT_MAX / 2); + int newAllocated = cap * 2; + T* newMem = new T[newAllocated]; + TIXMLASSERT(newAllocated >= _size); + memcpy(newMem, _mem, sizeof(T)*_size); // warning: not using constructors, only works for PODs + if (_mem != _pool) { + delete[] _mem; + } + _mem = newMem; + _allocated = newAllocated; + } + } + + T* _mem; + T _pool[INITIAL_SIZE]; + int _allocated; // objects allocated + int _size; // number objects in use + }; + + + /* + Parent virtual class of a pool for fast allocation + and deallocation of objects. + */ + class MemPool + { + public: + MemPool() {} + virtual ~MemPool() {} + + virtual int ItemSize() const = 0; + virtual void* Alloc() = 0; + virtual void Free(void*) = 0; + virtual void SetTracked() = 0; + virtual void Clear() = 0; + }; + + + /* + Template child class to create pools of the correct type. + */ + template< int ITEM_SIZE > + class MemPoolT : public MemPool + { + public: + MemPoolT() : _blockPtrs(), _root(0), _currentAllocs(0), _nAllocs(0), _maxAllocs(0), _nUntracked(0) {} + ~MemPoolT() { + Clear(); + } + + void Clear() { + // Delete the blocks. + while (!_blockPtrs.Empty()) { + Block* lastBlock = _blockPtrs.Pop(); + delete lastBlock; + } + _root = 0; + _currentAllocs = 0; + _nAllocs = 0; + _maxAllocs = 0; + _nUntracked = 0; + } + + virtual int ItemSize() const { + return ITEM_SIZE; + } + int CurrentAllocs() const { + return _currentAllocs; + } + + virtual void* Alloc() { + if (!_root) { + // Need a new block. + Block* block = new Block(); + _blockPtrs.Push(block); + + Item* blockItems = block->items; + for (int i = 0; i < ITEMS_PER_BLOCK - 1; ++i) { + blockItems[i].next = &(blockItems[i + 1]); + } + blockItems[ITEMS_PER_BLOCK - 1].next = 0; + _root = blockItems; + } + Item* const result = _root; + TIXMLASSERT(result != 0); + _root = _root->next; + + ++_currentAllocs; + if (_currentAllocs > _maxAllocs) { + _maxAllocs = _currentAllocs; + } + ++_nAllocs; + ++_nUntracked; + return result; + } + + virtual void Free(void* mem) { + if (!mem) { + return; + } + --_currentAllocs; + Item* item = static_cast(mem); +#ifdef DEBUG + memset(item, 0xfe, sizeof(*item)); +#endif + item->next = _root; + _root = item; + } + void Trace(const char* name) { + printf("Mempool %s watermark=%d [%dk] current=%d size=%d nAlloc=%d blocks=%d\n", + name, _maxAllocs, _maxAllocs * ITEM_SIZE / 1024, _currentAllocs, + ITEM_SIZE, _nAllocs, _blockPtrs.Size()); + } + + void SetTracked() { + --_nUntracked; + } + + int Untracked() const { + return _nUntracked; + } + + // This number is perf sensitive. 4k seems like a good tradeoff on my machine. + // The test file is large, 170k. + // Release: VS2010 gcc(no opt) + // 1k: 4000 + // 2k: 4000 + // 4k: 3900 21000 + // 16k: 5200 + // 32k: 4300 + // 64k: 4000 21000 + // Declared public because some compilers do not accept to use ITEMS_PER_BLOCK + // in private part if ITEMS_PER_BLOCK is private + enum { ITEMS_PER_BLOCK = (4 * 1024) / ITEM_SIZE }; + + private: + MemPoolT(const MemPoolT&); // not supported + void operator=(const MemPoolT&); // not supported + + union Item { + Item* next; + char itemData[ITEM_SIZE]; + }; + struct Block { + Item items[ITEMS_PER_BLOCK]; + }; + DynArray< Block*, 10 > _blockPtrs; + Item* _root; + + int _currentAllocs; + int _nAllocs; + int _maxAllocs; + int _nUntracked; + }; + + + + /** + Implements the interface to the "Visitor pattern" (see the Accept() method.) + If you call the Accept() method, it requires being passed a XMLVisitor + class to handle callbacks. For nodes that contain other nodes (Document, Element) + you will get called with a VisitEnter/VisitExit pair. Nodes that are always leafs + are simply called with Visit(). + + If you return 'true' from a Visit method, recursive parsing will continue. If you return + false, no children of this node or its siblings will be visited. + + All flavors of Visit methods have a default implementation that returns 'true' (continue + visiting). You need to only override methods that are interesting to you. + + Generally Accept() is called on the XMLDocument, although all nodes support visiting. + + You should never change the document from a callback. + + @sa XMLNode::Accept() + */ + class TINYXML2_LIB XMLVisitor + { + public: + virtual ~XMLVisitor() {} + + /// Visit a document. + virtual bool VisitEnter(const XMLDocument& /*doc*/) { + return true; + } + /// Visit a document. + virtual bool VisitExit(const XMLDocument& /*doc*/) { + return true; + } + + /// Visit an element. + virtual bool VisitEnter(const XMLElement& /*element*/, const XMLAttribute* /*firstAttribute*/) { + return true; + } + /// Visit an element. + virtual bool VisitExit(const XMLElement& /*element*/) { + return true; + } + + /// Visit a declaration. + virtual bool Visit(const XMLDeclaration& /*declaration*/) { + return true; + } + /// Visit a text node. + virtual bool Visit(const XMLText& /*text*/) { + return true; + } + /// Visit a comment node. + virtual bool Visit(const XMLComment& /*comment*/) { + return true; + } + /// Visit an unknown node. + virtual bool Visit(const XMLUnknown& /*unknown*/) { + return true; + } + }; + + // WARNING: must match XMLDocument::_errorNames[] + enum XMLError { + XML_SUCCESS = 0, + XML_NO_ATTRIBUTE, + XML_WRONG_ATTRIBUTE_TYPE, + XML_ERROR_FILE_NOT_FOUND, + XML_ERROR_FILE_COULD_NOT_BE_OPENED, + XML_ERROR_FILE_READ_ERROR, + UNUSED_XML_ERROR_ELEMENT_MISMATCH, // remove at next major version + XML_ERROR_PARSING_ELEMENT, + XML_ERROR_PARSING_ATTRIBUTE, + UNUSED_XML_ERROR_IDENTIFYING_TAG, // remove at next major version + XML_ERROR_PARSING_TEXT, + XML_ERROR_PARSING_CDATA, + XML_ERROR_PARSING_COMMENT, + XML_ERROR_PARSING_DECLARATION, + XML_ERROR_PARSING_UNKNOWN, + XML_ERROR_EMPTY_DOCUMENT, + XML_ERROR_MISMATCHED_ELEMENT, + XML_ERROR_PARSING, + XML_CAN_NOT_CONVERT_TEXT, + XML_NO_TEXT_NODE, + + XML_ERROR_COUNT + }; + + + /* + Utility functionality. + */ + class TINYXML2_LIB XMLUtil + { + public: + static const char* SkipWhiteSpace(const char* p, int* curLineNumPtr) { + TIXMLASSERT(p); + + while (IsWhiteSpace(*p)) { + if (curLineNumPtr && *p == '\n') { + ++(*curLineNumPtr); + } + ++p; + } + TIXMLASSERT(p); + return p; + } + static char* SkipWhiteSpace(char* p, int* curLineNumPtr) { + return const_cast(SkipWhiteSpace(const_cast(p), curLineNumPtr)); + } + + // Anything in the high order range of UTF-8 is assumed to not be whitespace. This isn't + // correct, but simple, and usually works. + static bool IsWhiteSpace(char p) { + return !IsUTF8Continuation(p) && isspace(static_cast(p)); + } + + inline static bool IsNameStartChar(unsigned char ch) { + if (ch >= 128) { + // This is a heuristic guess in attempt to not implement Unicode-aware isalpha() + return true; + } + if (isalpha(ch)) { + return true; + } + return ch == ':' || ch == '_'; + } + + inline static bool IsNameChar(unsigned char ch) { + return IsNameStartChar(ch) + || isdigit(ch) + || ch == '.' + || ch == '-'; + } + + inline static bool StringEqual(const char* p, const char* q, int nChar = INT_MAX) { + if (p == q) { + return true; + } + TIXMLASSERT(p); + TIXMLASSERT(q); + TIXMLASSERT(nChar >= 0); + return strncmp(p, q, nChar) == 0; + } + + inline static bool IsUTF8Continuation(char p) { + return (p & 0x80) != 0; + } + + static const char* ReadBOM(const char* p, bool* hasBOM); + // p is the starting location, + // the UTF-8 value of the entity will be placed in value, and length filled in. + static const char* GetCharacterRef(const char* p, char* value, int* length); + static void ConvertUTF32ToUTF8(unsigned long input, char* output, int* length); + + // converts primitive types to strings + static void ToStr(int v, char* buffer, int bufferSize); + static void ToStr(unsigned v, char* buffer, int bufferSize); + static void ToStr(bool v, char* buffer, int bufferSize); + static void ToStr(float v, char* buffer, int bufferSize); + static void ToStr(double v, char* buffer, int bufferSize); + static void ToStr(int64_t v, char* buffer, int bufferSize); + + // converts strings to primitive types + static bool ToInt(const char* str, int* value); + static bool ToUnsigned(const char* str, unsigned* value); + static bool ToBool(const char* str, bool* value); + static bool ToFloat(const char* str, float* value); + static bool ToDouble(const char* str, double* value); + static bool ToInt64(const char* str, int64_t* value); + + // Changes what is serialized for a boolean value. + // Default to "true" and "false". Shouldn't be changed + // unless you have a special testing or compatibility need. + // Be careful: static, global, & not thread safe. + // Be sure to set static const memory as parameters. + static void SetBoolSerialization(const char* writeTrue, const char* writeFalse); + + private: + static const char* writeBoolTrue; + static const char* writeBoolFalse; + }; + + + /** XMLNode is a base class for every object that is in the + XML Document Object Model (DOM), except XMLAttributes. + Nodes have siblings, a parent, and children which can + be navigated. A node is always in a XMLDocument. + The type of a XMLNode can be queried, and it can + be cast to its more defined type. + + A XMLDocument allocates memory for all its Nodes. + When the XMLDocument gets deleted, all its Nodes + will also be deleted. + + @verbatim + A Document can contain: Element (container or leaf) + Comment (leaf) + Unknown (leaf) + Declaration( leaf ) + + An Element can contain: Element (container or leaf) + Text (leaf) + Attributes (not on tree) + Comment (leaf) + Unknown (leaf) + + @endverbatim + */ + class TINYXML2_LIB XMLNode + { + friend class XMLDocument; + friend class XMLElement; + public: + + /// Get the XMLDocument that owns this XMLNode. + const XMLDocument* GetDocument() const { + TIXMLASSERT(_document); + return _document; + } + /// Get the XMLDocument that owns this XMLNode. + XMLDocument* GetDocument() { + TIXMLASSERT(_document); + return _document; + } + + /// Safely cast to an Element, or null. + virtual XMLElement* ToElement() { + return 0; + } + /// Safely cast to Text, or null. + virtual XMLText* ToText() { + return 0; + } + /// Safely cast to a Comment, or null. + virtual XMLComment* ToComment() { + return 0; + } + /// Safely cast to a Document, or null. + virtual XMLDocument* ToDocument() { + return 0; + } + /// Safely cast to a Declaration, or null. + virtual XMLDeclaration* ToDeclaration() { + return 0; + } + /// Safely cast to an Unknown, or null. + virtual XMLUnknown* ToUnknown() { + return 0; + } + + virtual const XMLElement* ToElement() const { + return 0; + } + virtual const XMLText* ToText() const { + return 0; + } + virtual const XMLComment* ToComment() const { + return 0; + } + virtual const XMLDocument* ToDocument() const { + return 0; + } + virtual const XMLDeclaration* ToDeclaration() const { + return 0; + } + virtual const XMLUnknown* ToUnknown() const { + return 0; + } + + /** The meaning of 'value' changes for the specific type. + @verbatim + Document: empty (NULL is returned, not an empty string) + Element: name of the element + Comment: the comment text + Unknown: the tag contents + Text: the text string + @endverbatim + */ + const char* Value() const; + + /** Set the Value of an XML node. + @sa Value() + */ + void SetValue(const char* val, bool staticMem = false); + + /// Gets the line number the node is in, if the document was parsed from a file. + int GetLineNum() const { return _parseLineNum; } + + /// Get the parent of this node on the DOM. + const XMLNode* Parent() const { + return _parent; + } + + XMLNode* Parent() { + return _parent; + } + + /// Returns true if this node has no children. + bool NoChildren() const { + return !_firstChild; + } + + /// Get the first child node, or null if none exists. + const XMLNode* FirstChild() const { + return _firstChild; + } + + XMLNode* FirstChild() { + return _firstChild; + } + + /** Get the first child element, or optionally the first child + element with the specified name. + */ + const XMLElement* FirstChildElement(const char* name = 0) const; + + XMLElement* FirstChildElement(const char* name = 0) { + return const_cast(const_cast(this)->FirstChildElement(name)); + } + + /// Get the last child node, or null if none exists. + const XMLNode* LastChild() const { + return _lastChild; + } + + XMLNode* LastChild() { + return _lastChild; + } + + /** Get the last child element or optionally the last child + element with the specified name. + */ + const XMLElement* LastChildElement(const char* name = 0) const; + + XMLElement* LastChildElement(const char* name = 0) { + return const_cast(const_cast(this)->LastChildElement(name)); + } + + /// Get the previous (left) sibling node of this node. + const XMLNode* PreviousSibling() const { + return _prev; + } + + XMLNode* PreviousSibling() { + return _prev; + } + + /// Get the previous (left) sibling element of this node, with an optionally supplied name. + const XMLElement* PreviousSiblingElement(const char* name = 0) const; + + XMLElement* PreviousSiblingElement(const char* name = 0) { + return const_cast(const_cast(this)->PreviousSiblingElement(name)); + } + + /// Get the next (right) sibling node of this node. + const XMLNode* NextSibling() const { + return _next; + } + + XMLNode* NextSibling() { + return _next; + } + + /// Get the next (right) sibling element of this node, with an optionally supplied name. + const XMLElement* NextSiblingElement(const char* name = 0) const; + + XMLElement* NextSiblingElement(const char* name = 0) { + return const_cast(const_cast(this)->NextSiblingElement(name)); + } + + /** + Add a child node as the last (right) child. + If the child node is already part of the document, + it is moved from its old location to the new location. + Returns the addThis argument or 0 if the node does not + belong to the same document. + */ + XMLNode* InsertEndChild(XMLNode* addThis); + + XMLNode* LinkEndChild(XMLNode* addThis) { + return InsertEndChild(addThis); + } + /** + Add a child node as the first (left) child. + If the child node is already part of the document, + it is moved from its old location to the new location. + Returns the addThis argument or 0 if the node does not + belong to the same document. + */ + XMLNode* InsertFirstChild(XMLNode* addThis); + /** + Add a node after the specified child node. + If the child node is already part of the document, + it is moved from its old location to the new location. + Returns the addThis argument or 0 if the afterThis node + is not a child of this node, or if the node does not + belong to the same document. + */ + XMLNode* InsertAfterChild(XMLNode* afterThis, XMLNode* addThis); + + /** + Delete all the children of this node. + */ + void DeleteChildren(); + + /** + Delete a child of this node. + */ + void DeleteChild(XMLNode* node); + + /** + Make a copy of this node, but not its children. + You may pass in a Document pointer that will be + the owner of the new Node. If the 'document' is + null, then the node returned will be allocated + from the current Document. (this->GetDocument()) + + Note: if called on a XMLDocument, this will return null. + */ + virtual XMLNode* ShallowClone(XMLDocument* document) const = 0; + + /** + Make a copy of this node and all its children. + + If the 'target' is null, then the nodes will + be allocated in the current document. If 'target' + is specified, the memory will be allocated is the + specified XMLDocument. + + NOTE: This is probably not the correct tool to + copy a document, since XMLDocuments can have multiple + top level XMLNodes. You probably want to use + XMLDocument::DeepCopy() + */ + XMLNode* DeepClone(XMLDocument* target) const; + + /** + Test if 2 nodes are the same, but don't test children. + The 2 nodes do not need to be in the same Document. + + Note: if called on a XMLDocument, this will return false. + */ + virtual bool ShallowEqual(const XMLNode* compare) const = 0; + + /** Accept a hierarchical visit of the nodes in the TinyXML-2 DOM. Every node in the + XML tree will be conditionally visited and the host will be called back + via the XMLVisitor interface. + + This is essentially a SAX interface for TinyXML-2. (Note however it doesn't re-parse + the XML for the callbacks, so the performance of TinyXML-2 is unchanged by using this + interface versus any other.) + + The interface has been based on ideas from: + + - http://www.saxproject.org/ + - http://c2.com/cgi/wiki?HierarchicalVisitorPattern + + Which are both good references for "visiting". + + An example of using Accept(): + @verbatim + XMLPrinter printer; + tinyxmlDoc.Accept( &printer ); + const char* xmlcstr = printer.CStr(); + @endverbatim + */ + virtual bool Accept(XMLVisitor* visitor) const = 0; + + /** + Set user data into the XMLNode. TinyXML-2 in + no way processes or interprets user data. + It is initially 0. + */ + void SetUserData(void* userData) { _userData = userData; } + + /** + Get user data set into the XMLNode. TinyXML-2 in + no way processes or interprets user data. + It is initially 0. + */ + void* GetUserData() const { return _userData; } + + protected: + XMLNode(XMLDocument*); + virtual ~XMLNode(); + + virtual char* ParseDeep(char* p, StrPair* parentEndTag, int* curLineNumPtr); + + XMLDocument* _document; + XMLNode* _parent; + mutable StrPair _value; + int _parseLineNum; + + XMLNode* _firstChild; + XMLNode* _lastChild; + + XMLNode* _prev; + XMLNode* _next; + + void* _userData; + + private: + MemPool* _memPool; + void Unlink(XMLNode* child); + static void DeleteNode(XMLNode* node); + void InsertChildPreamble(XMLNode* insertThis) const; + const XMLElement* ToElementWithName(const char* name) const; + + XMLNode(const XMLNode&); // not supported + XMLNode& operator=(const XMLNode&); // not supported + }; + + + /** XML text. + + Note that a text node can have child element nodes, for example: + @verbatim + This is bold + @endverbatim + + A text node can have 2 ways to output the next. "normal" output + and CDATA. It will default to the mode it was parsed from the XML file and + you generally want to leave it alone, but you can change the output mode with + SetCData() and query it with CData(). + */ + class TINYXML2_LIB XMLText : public XMLNode + { + friend class XMLDocument; + public: + virtual bool Accept(XMLVisitor* visitor) const; + + virtual XMLText* ToText() { + return this; + } + virtual const XMLText* ToText() const { + return this; + } + + /// Declare whether this should be CDATA or standard text. + void SetCData(bool isCData) { + _isCData = isCData; + } + /// Returns true if this is a CDATA text element. + bool CData() const { + return _isCData; + } + + virtual XMLNode* ShallowClone(XMLDocument* document) const; + virtual bool ShallowEqual(const XMLNode* compare) const; + + protected: + XMLText(XMLDocument* doc) : XMLNode(doc), _isCData(false) {} + virtual ~XMLText() {} + + char* ParseDeep(char* p, StrPair* parentEndTag, int* curLineNumPtr); + + private: + bool _isCData; + + XMLText(const XMLText&); // not supported + XMLText& operator=(const XMLText&); // not supported + }; + + + /** An XML Comment. */ + class TINYXML2_LIB XMLComment : public XMLNode + { + friend class XMLDocument; + public: + virtual XMLComment* ToComment() { + return this; + } + virtual const XMLComment* ToComment() const { + return this; + } + + virtual bool Accept(XMLVisitor* visitor) const; + + virtual XMLNode* ShallowClone(XMLDocument* document) const; + virtual bool ShallowEqual(const XMLNode* compare) const; + + protected: + XMLComment(XMLDocument* doc); + virtual ~XMLComment(); + + char* ParseDeep(char* p, StrPair* parentEndTag, int* curLineNumPtr); + + private: + XMLComment(const XMLComment&); // not supported + XMLComment& operator=(const XMLComment&); // not supported + }; + + + /** In correct XML the declaration is the first entry in the file. + @verbatim + + @endverbatim + + TinyXML-2 will happily read or write files without a declaration, + however. + + The text of the declaration isn't interpreted. It is parsed + and written as a string. + */ + class TINYXML2_LIB XMLDeclaration : public XMLNode + { + friend class XMLDocument; + public: + virtual XMLDeclaration* ToDeclaration() { + return this; + } + virtual const XMLDeclaration* ToDeclaration() const { + return this; + } + + virtual bool Accept(XMLVisitor* visitor) const; + + virtual XMLNode* ShallowClone(XMLDocument* document) const; + virtual bool ShallowEqual(const XMLNode* compare) const; + + protected: + XMLDeclaration(XMLDocument* doc); + virtual ~XMLDeclaration(); + + char* ParseDeep(char* p, StrPair* parentEndTag, int* curLineNumPtr); + + private: + XMLDeclaration(const XMLDeclaration&); // not supported + XMLDeclaration& operator=(const XMLDeclaration&); // not supported + }; + + + /** Any tag that TinyXML-2 doesn't recognize is saved as an + unknown. It is a tag of text, but should not be modified. + It will be written back to the XML, unchanged, when the file + is saved. + + DTD tags get thrown into XMLUnknowns. + */ + class TINYXML2_LIB XMLUnknown : public XMLNode + { + friend class XMLDocument; + public: + virtual XMLUnknown* ToUnknown() { + return this; + } + virtual const XMLUnknown* ToUnknown() const { + return this; + } + + virtual bool Accept(XMLVisitor* visitor) const; + + virtual XMLNode* ShallowClone(XMLDocument* document) const; + virtual bool ShallowEqual(const XMLNode* compare) const; + + protected: + XMLUnknown(XMLDocument* doc); + virtual ~XMLUnknown(); + + char* ParseDeep(char* p, StrPair* parentEndTag, int* curLineNumPtr); + + private: + XMLUnknown(const XMLUnknown&); // not supported + XMLUnknown& operator=(const XMLUnknown&); // not supported + }; + + + + /** An attribute is a name-value pair. Elements have an arbitrary + number of attributes, each with a unique name. + + @note The attributes are not XMLNodes. You may only query the + Next() attribute in a list. + */ + class TINYXML2_LIB XMLAttribute + { + friend class XMLElement; + public: + /// The name of the attribute. + const char* Name() const; + + /// The value of the attribute. + const char* Value() const; + + /// Gets the line number the attribute is in, if the document was parsed from a file. + int GetLineNum() const { return _parseLineNum; } + + /// The next attribute in the list. + const XMLAttribute* Next() const { + return _next; + } + + /** IntValue interprets the attribute as an integer, and returns the value. + If the value isn't an integer, 0 will be returned. There is no error checking; + use QueryIntValue() if you need error checking. + */ + int IntValue() const { + int i = 0; + QueryIntValue(&i); + return i; + } + + int64_t Int64Value() const { + int64_t i = 0; + QueryInt64Value(&i); + return i; + } + + /// Query as an unsigned integer. See IntValue() + unsigned UnsignedValue() const { + unsigned i = 0; + QueryUnsignedValue(&i); + return i; + } + /// Query as a boolean. See IntValue() + bool BoolValue() const { + bool b = false; + QueryBoolValue(&b); + return b; + } + /// Query as a double. See IntValue() + double DoubleValue() const { + double d = 0; + QueryDoubleValue(&d); + return d; + } + /// Query as a float. See IntValue() + float FloatValue() const { + float f = 0; + QueryFloatValue(&f); + return f; + } + + /** QueryIntValue interprets the attribute as an integer, and returns the value + in the provided parameter. The function will return XML_SUCCESS on success, + and XML_WRONG_ATTRIBUTE_TYPE if the conversion is not successful. + */ + XMLError QueryIntValue(int* value) const; + /// See QueryIntValue + XMLError QueryUnsignedValue(unsigned int* value) const; + /// See QueryIntValue + XMLError QueryInt64Value(int64_t* value) const; + /// See QueryIntValue + XMLError QueryBoolValue(bool* value) const; + /// See QueryIntValue + XMLError QueryDoubleValue(double* value) const; + /// See QueryIntValue + XMLError QueryFloatValue(float* value) const; + + /// Set the attribute to a string value. + void SetAttribute(const char* value); + /// Set the attribute to value. + void SetAttribute(int value); + /// Set the attribute to value. + void SetAttribute(unsigned value); + /// Set the attribute to value. + void SetAttribute(int64_t value); + /// Set the attribute to value. + void SetAttribute(bool value); + /// Set the attribute to value. + void SetAttribute(double value); + /// Set the attribute to value. + void SetAttribute(float value); + + private: + enum { BUF_SIZE = 200 }; + + XMLAttribute() : _name(), _value(), _parseLineNum(0), _next(0), _memPool(0) {} + virtual ~XMLAttribute() {} + + XMLAttribute(const XMLAttribute&); // not supported + void operator=(const XMLAttribute&); // not supported + void SetName(const char* name); + + char* ParseDeep(char* p, bool processEntities, int* curLineNumPtr); + + mutable StrPair _name; + mutable StrPair _value; + int _parseLineNum; + XMLAttribute* _next; + MemPool* _memPool; + }; + + + /** The element is a container class. It has a value, the element name, + and can contain other elements, text, comments, and unknowns. + Elements also contain an arbitrary number of attributes. + */ + class TINYXML2_LIB XMLElement : public XMLNode + { + friend class XMLDocument; + public: + /// Get the name of an element (which is the Value() of the node.) + const char* Name() const { + return Value(); + } + /// Set the name of the element. + void SetName(const char* str, bool staticMem = false) { + SetValue(str, staticMem); + } + + virtual XMLElement* ToElement() { + return this; + } + virtual const XMLElement* ToElement() const { + return this; + } + virtual bool Accept(XMLVisitor* visitor) const; + + /** Given an attribute name, Attribute() returns the value + for the attribute of that name, or null if none + exists. For example: + + @verbatim + const char* value = ele->Attribute( "foo" ); + @endverbatim + + The 'value' parameter is normally null. However, if specified, + the attribute will only be returned if the 'name' and 'value' + match. This allow you to write code: + + @verbatim + if ( ele->Attribute( "foo", "bar" ) ) callFooIsBar(); + @endverbatim + + rather than: + @verbatim + if ( ele->Attribute( "foo" ) ) { + if ( strcmp( ele->Attribute( "foo" ), "bar" ) == 0 ) callFooIsBar(); + } + @endverbatim + */ + const char* Attribute(const char* name, const char* value = 0) const; + + /** Given an attribute name, IntAttribute() returns the value + of the attribute interpreted as an integer. The default + value will be returned if the attribute isn't present, + or if there is an error. (For a method with error + checking, see QueryIntAttribute()). + */ + int IntAttribute(const char* name, int defaultValue = 0) const; + /// See IntAttribute() + unsigned UnsignedAttribute(const char* name, unsigned defaultValue = 0) const; + /// See IntAttribute() + int64_t Int64Attribute(const char* name, int64_t defaultValue = 0) const; + /// See IntAttribute() + bool BoolAttribute(const char* name, bool defaultValue = false) const; + /// See IntAttribute() + double DoubleAttribute(const char* name, double defaultValue = 0) const; + /// See IntAttribute() + float FloatAttribute(const char* name, float defaultValue = 0) const; + + /** Given an attribute name, QueryIntAttribute() returns + XML_SUCCESS, XML_WRONG_ATTRIBUTE_TYPE if the conversion + can't be performed, or XML_NO_ATTRIBUTE if the attribute + doesn't exist. If successful, the result of the conversion + will be written to 'value'. If not successful, nothing will + be written to 'value'. This allows you to provide default + value: + + @verbatim + int value = 10; + QueryIntAttribute( "foo", &value ); // if "foo" isn't found, value will still be 10 + @endverbatim + */ + XMLError QueryIntAttribute(const char* name, int* value) const { + const XMLAttribute* a = FindAttribute(name); + if (!a) { + return XML_NO_ATTRIBUTE; + } + return a->QueryIntValue(value); + } + + /// See QueryIntAttribute() + XMLError QueryUnsignedAttribute(const char* name, unsigned int* value) const { + const XMLAttribute* a = FindAttribute(name); + if (!a) { + return XML_NO_ATTRIBUTE; + } + return a->QueryUnsignedValue(value); + } + + /// See QueryIntAttribute() + XMLError QueryInt64Attribute(const char* name, int64_t* value) const { + const XMLAttribute* a = FindAttribute(name); + if (!a) { + return XML_NO_ATTRIBUTE; + } + return a->QueryInt64Value(value); + } + + /// See QueryIntAttribute() + XMLError QueryBoolAttribute(const char* name, bool* value) const { + const XMLAttribute* a = FindAttribute(name); + if (!a) { + return XML_NO_ATTRIBUTE; + } + return a->QueryBoolValue(value); + } + /// See QueryIntAttribute() + XMLError QueryDoubleAttribute(const char* name, double* value) const { + const XMLAttribute* a = FindAttribute(name); + if (!a) { + return XML_NO_ATTRIBUTE; + } + return a->QueryDoubleValue(value); + } + /// See QueryIntAttribute() + XMLError QueryFloatAttribute(const char* name, float* value) const { + const XMLAttribute* a = FindAttribute(name); + if (!a) { + return XML_NO_ATTRIBUTE; + } + return a->QueryFloatValue(value); + } + + + /** Given an attribute name, QueryAttribute() returns + XML_SUCCESS, XML_WRONG_ATTRIBUTE_TYPE if the conversion + can't be performed, or XML_NO_ATTRIBUTE if the attribute + doesn't exist. It is overloaded for the primitive types, + and is a generally more convenient replacement of + QueryIntAttribute() and related functions. + + If successful, the result of the conversion + will be written to 'value'. If not successful, nothing will + be written to 'value'. This allows you to provide default + value: + + @verbatim + int value = 10; + QueryAttribute( "foo", &value ); // if "foo" isn't found, value will still be 10 + @endverbatim + */ + int QueryAttribute(const char* name, int* value) const { + return QueryIntAttribute(name, value); + } + + int QueryAttribute(const char* name, unsigned int* value) const { + return QueryUnsignedAttribute(name, value); + } + + int QueryAttribute(const char* name, int64_t* value) const { + return QueryInt64Attribute(name, value); + } + + int QueryAttribute(const char* name, bool* value) const { + return QueryBoolAttribute(name, value); + } + + int QueryAttribute(const char* name, double* value) const { + return QueryDoubleAttribute(name, value); + } + + int QueryAttribute(const char* name, float* value) const { + return QueryFloatAttribute(name, value); + } + + /// Sets the named attribute to value. + void SetAttribute(const char* name, const char* value) { + XMLAttribute* a = FindOrCreateAttribute(name); + a->SetAttribute(value); + } + /// Sets the named attribute to value. + void SetAttribute(const char* name, int value) { + XMLAttribute* a = FindOrCreateAttribute(name); + a->SetAttribute(value); + } + /// Sets the named attribute to value. + void SetAttribute(const char* name, unsigned value) { + XMLAttribute* a = FindOrCreateAttribute(name); + a->SetAttribute(value); + } + + /// Sets the named attribute to value. + void SetAttribute(const char* name, int64_t value) { + XMLAttribute* a = FindOrCreateAttribute(name); + a->SetAttribute(value); + } + + /// Sets the named attribute to value. + void SetAttribute(const char* name, bool value) { + XMLAttribute* a = FindOrCreateAttribute(name); + a->SetAttribute(value); + } + /// Sets the named attribute to value. + void SetAttribute(const char* name, double value) { + XMLAttribute* a = FindOrCreateAttribute(name); + a->SetAttribute(value); + } + /// Sets the named attribute to value. + void SetAttribute(const char* name, float value) { + XMLAttribute* a = FindOrCreateAttribute(name); + a->SetAttribute(value); + } + + /** + Delete an attribute. + */ + void DeleteAttribute(const char* name); + + /// Return the first attribute in the list. + const XMLAttribute* FirstAttribute() const { + return _rootAttribute; + } + /// Query a specific attribute in the list. + const XMLAttribute* FindAttribute(const char* name) const; + + /** Convenience function for easy access to the text inside an element. Although easy + and concise, GetText() is limited compared to getting the XMLText child + and accessing it directly. + + If the first child of 'this' is a XMLText, the GetText() + returns the character string of the Text node, else null is returned. + + This is a convenient method for getting the text of simple contained text: + @verbatim + This is text + const char* str = fooElement->GetText(); + @endverbatim + + 'str' will be a pointer to "This is text". + + Note that this function can be misleading. If the element foo was created from + this XML: + @verbatim + This is text + @endverbatim + + then the value of str would be null. The first child node isn't a text node, it is + another element. From this XML: + @verbatim + This is text + @endverbatim + GetText() will return "This is ". + */ + const char* GetText() const; + + /** Convenience function for easy access to the text inside an element. Although easy + and concise, SetText() is limited compared to creating an XMLText child + and mutating it directly. + + If the first child of 'this' is a XMLText, SetText() sets its value to + the given string, otherwise it will create a first child that is an XMLText. + + This is a convenient method for setting the text of simple contained text: + @verbatim + This is text + fooElement->SetText( "Hullaballoo!" ); + Hullaballoo! + @endverbatim + + Note that this function can be misleading. If the element foo was created from + this XML: + @verbatim + This is text + @endverbatim + + then it will not change "This is text", but rather prefix it with a text element: + @verbatim + Hullaballoo!This is text + @endverbatim + + For this XML: + @verbatim + + @endverbatim + SetText() will generate + @verbatim + Hullaballoo! + @endverbatim + */ + void SetText(const char* inText); + /// Convenience method for setting text inside an element. See SetText() for important limitations. + void SetText(int value); + /// Convenience method for setting text inside an element. See SetText() for important limitations. + void SetText(unsigned value); + /// Convenience method for setting text inside an element. See SetText() for important limitations. + void SetText(int64_t value); + /// Convenience method for setting text inside an element. See SetText() for important limitations. + void SetText(bool value); + /// Convenience method for setting text inside an element. See SetText() for important limitations. + void SetText(double value); + /// Convenience method for setting text inside an element. See SetText() for important limitations. + void SetText(float value); + + /** + Convenience method to query the value of a child text node. This is probably best + shown by example. Given you have a document is this form: + @verbatim + + 1 + 1.4 + + @endverbatim + + The QueryIntText() and similar functions provide a safe and easier way to get to the + "value" of x and y. + + @verbatim + int x = 0; + float y = 0; // types of x and y are contrived for example + const XMLElement* xElement = pointElement->FirstChildElement( "x" ); + const XMLElement* yElement = pointElement->FirstChildElement( "y" ); + xElement->QueryIntText( &x ); + yElement->QueryFloatText( &y ); + @endverbatim + + @returns XML_SUCCESS (0) on success, XML_CAN_NOT_CONVERT_TEXT if the text cannot be converted + to the requested type, and XML_NO_TEXT_NODE if there is no child text to query. + + */ + XMLError QueryIntText(int* ival) const; + /// See QueryIntText() + XMLError QueryUnsignedText(unsigned* uval) const; + /// See QueryIntText() + XMLError QueryInt64Text(int64_t* uval) const; + /// See QueryIntText() + XMLError QueryBoolText(bool* bval) const; + /// See QueryIntText() + XMLError QueryDoubleText(double* dval) const; + /// See QueryIntText() + XMLError QueryFloatText(float* fval) const; + + int IntText(int defaultValue = 0) const; + + /// See QueryIntText() + unsigned UnsignedText(unsigned defaultValue = 0) const; + /// See QueryIntText() + int64_t Int64Text(int64_t defaultValue = 0) const; + /// See QueryIntText() + bool BoolText(bool defaultValue = false) const; + /// See QueryIntText() + double DoubleText(double defaultValue = 0) const; + /// See QueryIntText() + float FloatText(float defaultValue = 0) const; + + // internal: + enum ElementClosingType { + OPEN, // + CLOSED, // + CLOSING // + }; + ElementClosingType ClosingType() const { + return _closingType; + } + virtual XMLNode* ShallowClone(XMLDocument* document) const; + virtual bool ShallowEqual(const XMLNode* compare) const; + + protected: + char* ParseDeep(char* p, StrPair* parentEndTag, int* curLineNumPtr); + + private: + XMLElement(XMLDocument* doc); + virtual ~XMLElement(); + XMLElement(const XMLElement&); // not supported + void operator=(const XMLElement&); // not supported + + XMLAttribute* FindAttribute(const char* name) { + return const_cast(const_cast(this)->FindAttribute(name)); + } + XMLAttribute* FindOrCreateAttribute(const char* name); + //void LinkAttribute( XMLAttribute* attrib ); + char* ParseAttributes(char* p, int* curLineNumPtr); + static void DeleteAttribute(XMLAttribute* attribute); + XMLAttribute* CreateAttribute(); + + enum { BUF_SIZE = 200 }; + ElementClosingType _closingType; + // The attribute list is ordered; there is no 'lastAttribute' + // because the list needs to be scanned for dupes before adding + // a new attribute. + XMLAttribute* _rootAttribute; + }; + + + enum Whitespace { + PRESERVE_WHITESPACE, + COLLAPSE_WHITESPACE + }; + + + /** A Document binds together all the functionality. + It can be saved, loaded, and printed to the screen. + All Nodes are connected and allocated to a Document. + If the Document is deleted, all its Nodes are also deleted. + */ + class TINYXML2_LIB XMLDocument : public XMLNode + { + friend class XMLElement; + public: + /// constructor + XMLDocument(bool processEntities = true, Whitespace whitespaceMode = PRESERVE_WHITESPACE); + ~XMLDocument(); + + virtual XMLDocument* ToDocument() { + TIXMLASSERT(this == _document); + return this; + } + virtual const XMLDocument* ToDocument() const { + TIXMLASSERT(this == _document); + return this; + } + + /** + Parse an XML file from a character string. + Returns XML_SUCCESS (0) on success, or + an errorID. + + You may optionally pass in the 'nBytes', which is + the number of bytes which will be parsed. If not + specified, TinyXML-2 will assume 'xml' points to a + null terminated string. + */ + XMLError Parse(const char* xml, size_t nBytes = (size_t)(-1)); + + /** + Load an XML file from disk. + Returns XML_SUCCESS (0) on success, or + an errorID. + */ + XMLError LoadFile(const char* filename); + + /** + Load an XML file from disk. You are responsible + for providing and closing the FILE*. + + NOTE: The file should be opened as binary ("rb") + not text in order for TinyXML-2 to correctly + do newline normalization. + + Returns XML_SUCCESS (0) on success, or + an errorID. + */ + XMLError LoadFile(FILE*); + + /** + Save the XML file to disk. + Returns XML_SUCCESS (0) on success, or + an errorID. + */ + XMLError SaveFile(const char* filename, bool compact = false); + + /** + Save the XML file to disk. You are responsible + for providing and closing the FILE*. + + Returns XML_SUCCESS (0) on success, or + an errorID. + */ + XMLError SaveFile(FILE* fp, bool compact = false); + + bool ProcessEntities() const { + return _processEntities; + } + Whitespace WhitespaceMode() const { + return _whitespaceMode; + } + + /** + Returns true if this document has a leading Byte Order Mark of UTF8. + */ + bool HasBOM() const { + return _writeBOM; + } + /** Sets whether to write the BOM when writing the file. + */ + void SetBOM(bool useBOM) { + _writeBOM = useBOM; + } + + /** Return the root element of DOM. Equivalent to FirstChildElement(). + To get the first node, use FirstChild(). + */ + XMLElement* RootElement() { + return FirstChildElement(); + } + const XMLElement* RootElement() const { + return FirstChildElement(); + } + + /** Print the Document. If the Printer is not provided, it will + print to stdout. If you provide Printer, this can print to a file: + @verbatim + XMLPrinter printer( fp ); + doc.Print( &printer ); + @endverbatim + + Or you can use a printer to print to memory: + @verbatim + XMLPrinter printer; + doc.Print( &printer ); + // printer.CStr() has a const char* to the XML + @endverbatim + */ + void Print(XMLPrinter* streamer = 0) const; + virtual bool Accept(XMLVisitor* visitor) const; + + /** + Create a new Element associated with + this Document. The memory for the Element + is managed by the Document. + */ + XMLElement* NewElement(const char* name); + /** + Create a new Comment associated with + this Document. The memory for the Comment + is managed by the Document. + */ + XMLComment* NewComment(const char* comment); + /** + Create a new Text associated with + this Document. The memory for the Text + is managed by the Document. + */ + XMLText* NewText(const char* text); + /** + Create a new Declaration associated with + this Document. The memory for the object + is managed by the Document. + + If the 'text' param is null, the standard + declaration is used.: + @verbatim + + @endverbatim + */ + XMLDeclaration* NewDeclaration(const char* text = 0); + /** + Create a new Unknown associated with + this Document. The memory for the object + is managed by the Document. + */ + XMLUnknown* NewUnknown(const char* text); + + /** + Delete a node associated with this document. + It will be unlinked from the DOM. + */ + void DeleteNode(XMLNode* node); + + void SetError(XMLError error, const char* str1, const char* str2, int lineNum); + + void ClearError() { + SetError(XML_SUCCESS, 0, 0, 0); + } + + /// Return true if there was an error parsing the document. + bool Error() const { + return _errorID != XML_SUCCESS; + } + /// Return the errorID. + XMLError ErrorID() const { + return _errorID; + } + const char* ErrorName() const; + static const char* ErrorIDToName(XMLError errorID); + + /// Return a possibly helpful diagnostic location or string. + const char* GetErrorStr1() const; + + /// Return a possibly helpful secondary diagnostic location or string. + const char* GetErrorStr2() const; + + /// Return the line where the error occured, or zero if unknown. + int GetErrorLineNum() const + { + return _errorLineNum; + } + /// If there is an error, print it to stdout. + void PrintError() const; + + /// Clear the document, resetting it to the initial state. + void Clear(); + + /** + Copies this document to a target document. + The target will be completely cleared before the copy. + If you want to copy a sub-tree, see XMLNode::DeepClone(). + + NOTE: that the 'target' must be non-null. + */ + void DeepCopy(XMLDocument* target) const; + + // internal + char* Identify(char* p, XMLNode** node); + + // internal + void MarkInUse(XMLNode*); + + virtual XMLNode* ShallowClone(XMLDocument* /*document*/) const { + return 0; + } + virtual bool ShallowEqual(const XMLNode* /*compare*/) const { + return false; + } + + private: + XMLDocument(const XMLDocument&); // not supported + void operator=(const XMLDocument&); // not supported + + bool _writeBOM; + bool _processEntities; + XMLError _errorID; + Whitespace _whitespaceMode; + mutable StrPair _errorStr1; + mutable StrPair _errorStr2; + int _errorLineNum; + char* _charBuffer; + int _parseCurLineNum; + // Memory tracking does add some overhead. + // However, the code assumes that you don't + // have a bunch of unlinked nodes around. + // Therefore it takes less memory to track + // in the document vs. a linked list in the XMLNode, + // and the performance is the same. + DynArray _unlinked; + + MemPoolT< sizeof(XMLElement) > _elementPool; + MemPoolT< sizeof(XMLAttribute) > _attributePool; + MemPoolT< sizeof(XMLText) > _textPool; + MemPoolT< sizeof(XMLComment) > _commentPool; + + static const char* _errorNames[XML_ERROR_COUNT]; + + void Parse(); + + template + NodeType* CreateUnlinkedNode(MemPoolT& pool); + }; + + template + inline NodeType* XMLDocument::CreateUnlinkedNode(MemPoolT& pool) + { + TIXMLASSERT(sizeof(NodeType) == PoolElementSize); + TIXMLASSERT(sizeof(NodeType) == pool.ItemSize()); + NodeType* returnNode = new (pool.Alloc()) NodeType(this); + TIXMLASSERT(returnNode); + returnNode->_memPool = &pool; + + _unlinked.Push(returnNode); + return returnNode; + } + + /** + A XMLHandle is a class that wraps a node pointer with null checks; this is + an incredibly useful thing. Note that XMLHandle is not part of the TinyXML-2 + DOM structure. It is a separate utility class. + + Take an example: + @verbatim + + + + + + + @endverbatim + + Assuming you want the value of "attributeB" in the 2nd "Child" element, it's very + easy to write a *lot* of code that looks like: + + @verbatim + XMLElement* root = document.FirstChildElement( "Document" ); + if ( root ) + { + XMLElement* element = root->FirstChildElement( "Element" ); + if ( element ) + { + XMLElement* child = element->FirstChildElement( "Child" ); + if ( child ) + { + XMLElement* child2 = child->NextSiblingElement( "Child" ); + if ( child2 ) + { + // Finally do something useful. + @endverbatim + + And that doesn't even cover "else" cases. XMLHandle addresses the verbosity + of such code. A XMLHandle checks for null pointers so it is perfectly safe + and correct to use: + + @verbatim + XMLHandle docHandle( &document ); + XMLElement* child2 = docHandle.FirstChildElement( "Document" ).FirstChildElement( "Element" ).FirstChildElement().NextSiblingElement(); + if ( child2 ) + { + // do something useful + @endverbatim + + Which is MUCH more concise and useful. + + It is also safe to copy handles - internally they are nothing more than node pointers. + @verbatim + XMLHandle handleCopy = handle; + @endverbatim + + See also XMLConstHandle, which is the same as XMLHandle, but operates on const objects. + */ + class TINYXML2_LIB XMLHandle + { + public: + /// Create a handle from any node (at any depth of the tree.) This can be a null pointer. + XMLHandle(XMLNode* node) : _node(node) { + } + /// Create a handle from a node. + XMLHandle(XMLNode& node) : _node(&node) { + } + /// Copy constructor + XMLHandle(const XMLHandle& ref) : _node(ref._node) { + } + /// Assignment + XMLHandle& operator=(const XMLHandle& ref) { + _node = ref._node; + return *this; + } + + /// Get the first child of this handle. + XMLHandle FirstChild() { + return XMLHandle(_node ? _node->FirstChild() : 0); + } + /// Get the first child element of this handle. + XMLHandle FirstChildElement(const char* name = 0) { + return XMLHandle(_node ? _node->FirstChildElement(name) : 0); + } + /// Get the last child of this handle. + XMLHandle LastChild() { + return XMLHandle(_node ? _node->LastChild() : 0); + } + /// Get the last child element of this handle. + XMLHandle LastChildElement(const char* name = 0) { + return XMLHandle(_node ? _node->LastChildElement(name) : 0); + } + /// Get the previous sibling of this handle. + XMLHandle PreviousSibling() { + return XMLHandle(_node ? _node->PreviousSibling() : 0); + } + /// Get the previous sibling element of this handle. + XMLHandle PreviousSiblingElement(const char* name = 0) { + return XMLHandle(_node ? _node->PreviousSiblingElement(name) : 0); + } + /// Get the next sibling of this handle. + XMLHandle NextSibling() { + return XMLHandle(_node ? _node->NextSibling() : 0); + } + /// Get the next sibling element of this handle. + XMLHandle NextSiblingElement(const char* name = 0) { + return XMLHandle(_node ? _node->NextSiblingElement(name) : 0); + } + + /// Safe cast to XMLNode. This can return null. + XMLNode* ToNode() { + return _node; + } + /// Safe cast to XMLElement. This can return null. + XMLElement* ToElement() { + return (_node ? _node->ToElement() : 0); + } + /// Safe cast to XMLText. This can return null. + XMLText* ToText() { + return (_node ? _node->ToText() : 0); + } + /// Safe cast to XMLUnknown. This can return null. + XMLUnknown* ToUnknown() { + return (_node ? _node->ToUnknown() : 0); + } + /// Safe cast to XMLDeclaration. This can return null. + XMLDeclaration* ToDeclaration() { + return (_node ? _node->ToDeclaration() : 0); + } + + private: + XMLNode* _node; + }; + + + /** + A variant of the XMLHandle class for working with const XMLNodes and Documents. It is the + same in all regards, except for the 'const' qualifiers. See XMLHandle for API. + */ + class TINYXML2_LIB XMLConstHandle + { + public: + XMLConstHandle(const XMLNode* node) : _node(node) { + } + XMLConstHandle(const XMLNode& node) : _node(&node) { + } + XMLConstHandle(const XMLConstHandle& ref) : _node(ref._node) { + } + + XMLConstHandle& operator=(const XMLConstHandle& ref) { + _node = ref._node; + return *this; + } + + const XMLConstHandle FirstChild() const { + return XMLConstHandle(_node ? _node->FirstChild() : 0); + } + const XMLConstHandle FirstChildElement(const char* name = 0) const { + return XMLConstHandle(_node ? _node->FirstChildElement(name) : 0); + } + const XMLConstHandle LastChild() const { + return XMLConstHandle(_node ? _node->LastChild() : 0); + } + const XMLConstHandle LastChildElement(const char* name = 0) const { + return XMLConstHandle(_node ? _node->LastChildElement(name) : 0); + } + const XMLConstHandle PreviousSibling() const { + return XMLConstHandle(_node ? _node->PreviousSibling() : 0); + } + const XMLConstHandle PreviousSiblingElement(const char* name = 0) const { + return XMLConstHandle(_node ? _node->PreviousSiblingElement(name) : 0); + } + const XMLConstHandle NextSibling() const { + return XMLConstHandle(_node ? _node->NextSibling() : 0); + } + const XMLConstHandle NextSiblingElement(const char* name = 0) const { + return XMLConstHandle(_node ? _node->NextSiblingElement(name) : 0); + } + + + const XMLNode* ToNode() const { + return _node; + } + const XMLElement* ToElement() const { + return (_node ? _node->ToElement() : 0); + } + const XMLText* ToText() const { + return (_node ? _node->ToText() : 0); + } + const XMLUnknown* ToUnknown() const { + return (_node ? _node->ToUnknown() : 0); + } + const XMLDeclaration* ToDeclaration() const { + return (_node ? _node->ToDeclaration() : 0); + } + + private: + const XMLNode* _node; + }; + + + /** + Printing functionality. The XMLPrinter gives you more + options than the XMLDocument::Print() method. + + It can: + -# Print to memory. + -# Print to a file you provide. + -# Print XML without a XMLDocument. + + Print to Memory + + @verbatim + XMLPrinter printer; + doc.Print( &printer ); + SomeFunction( printer.CStr() ); + @endverbatim + + Print to a File + + You provide the file pointer. + @verbatim + XMLPrinter printer( fp ); + doc.Print( &printer ); + @endverbatim + + Print without a XMLDocument + + When loading, an XML parser is very useful. However, sometimes + when saving, it just gets in the way. The code is often set up + for streaming, and constructing the DOM is just overhead. + + The Printer supports the streaming case. The following code + prints out a trivially simple XML file without ever creating + an XML document. + + @verbatim + XMLPrinter printer( fp ); + printer.OpenElement( "foo" ); + printer.PushAttribute( "foo", "bar" ); + printer.CloseElement(); + @endverbatim + */ + class TINYXML2_LIB XMLPrinter : public XMLVisitor + { + public: + /** Construct the printer. If the FILE* is specified, + this will print to the FILE. Else it will print + to memory, and the result is available in CStr(). + If 'compact' is set to true, then output is created + with only required whitespace and newlines. + */ + XMLPrinter(FILE* file = 0, bool compact = false, int depth = 0); + virtual ~XMLPrinter() {} + + /** If streaming, write the BOM and declaration. */ + void PushHeader(bool writeBOM, bool writeDeclaration); + /** If streaming, start writing an element. + The element must be closed with CloseElement() + */ + void OpenElement(const char* name, bool compactMode = false); + /// If streaming, add an attribute to an open element. + void PushAttribute(const char* name, const char* value); + void PushAttribute(const char* name, int value); + void PushAttribute(const char* name, unsigned value); + void PushAttribute(const char* name, int64_t value); + void PushAttribute(const char* name, bool value); + void PushAttribute(const char* name, double value); + /// If streaming, close the Element. + virtual void CloseElement(bool compactMode = false); + + /// Add a text node. + void PushText(const char* text, bool cdata = false); + /// Add a text node from an integer. + void PushText(int value); + /// Add a text node from an unsigned. + void PushText(unsigned value); + /// Add a text node from an unsigned. + void PushText(int64_t value); + /// Add a text node from a bool. + void PushText(bool value); + /// Add a text node from a float. + void PushText(float value); + /// Add a text node from a double. + void PushText(double value); + + /// Add a comment + void PushComment(const char* comment); + + void PushDeclaration(const char* value); + void PushUnknown(const char* value); + + virtual bool VisitEnter(const XMLDocument& /*doc*/); + virtual bool VisitExit(const XMLDocument& /*doc*/) { + return true; + } + + virtual bool VisitEnter(const XMLElement& element, const XMLAttribute* attribute); + virtual bool VisitExit(const XMLElement& element); + + virtual bool Visit(const XMLText& text); + virtual bool Visit(const XMLComment& comment); + virtual bool Visit(const XMLDeclaration& declaration); + virtual bool Visit(const XMLUnknown& unknown); + + /** + If in print to memory mode, return a pointer to + the XML file in memory. + */ + const char* CStr() const { + return _buffer.Mem(); + } + /** + If in print to memory mode, return the size + of the XML file in memory. (Note the size returned + includes the terminating null.) + */ + int CStrSize() const { + return _buffer.Size(); + } + /** + If in print to memory mode, reset the buffer to the + beginning. + */ + void ClearBuffer() { + _buffer.Clear(); + _buffer.Push(0); + _firstElement = true; + } + + protected: + virtual bool CompactMode(const XMLElement&) { return _compactMode; } + + /** Prints out the space before an element. You may override to change + the space and tabs used. A PrintSpace() override should call Print(). + */ + virtual void PrintSpace(int depth); + void Print(const char* format, ...); + + void SealElementIfJustOpened(); + bool _elementJustOpened; + DynArray< const char*, 10 > _stack; + + private: + void PrintString(const char*, bool restrictedEntitySet); // prints out, after detecting entities. + + bool _firstElement; + FILE* _fp; + int _depth; + int _textDepth; + bool _processEntities; + bool _compactMode; + + enum { + ENTITY_RANGE = 64, + BUF_SIZE = 200 + }; + bool _entityFlag[ENTITY_RANGE]; + bool _restrictedEntityFlag[ENTITY_RANGE]; + + DynArray< char, 20 > _buffer; + + // Prohibit cloning, intentionally not implemented + XMLPrinter(const XMLPrinter&); + XMLPrinter& operator=(const XMLPrinter&); + }; + + +} // tinyxml2 + +#if defined(_MSC_VER) +# pragma warning(pop) +#endif + +#endif // TINYXML2_INCLUDED diff --git a/vcpkg.json b/vcpkg.json new file mode 100644 index 00000000..ef965721 --- /dev/null +++ b/vcpkg.json @@ -0,0 +1,48 @@ +{ + "name": "cemu", + "version-string": "1.0", + "builtin-baseline": "9259a0719d94c402aae2ab7975bc096afdec15df", + "dependencies": [ + "imgui", + "pugixml", + "zlib", + "libpng", + "zstd", + { + "name": "libzip", + "default-features": false + }, + "rapidjson", + "vulkan", + "sdl2", + "boost-tokenizer", + "boost-container", + "boost-program-options", + "boost-nowide", + "boost-algorithm", + "boost-range", + "boost-functional", + "boost-dll", + "boost-optional", + "boost-signals2", + "boost-crc", + "boost-asio", + "boost-ptr-container", + "boost-property-tree", + "boost-static-string", + "fmt", + "glm", + "glslang", + "zstd", + { + "name": "wxwidgets", + "default-features": false + }, + "openssl", + { + "name": "curl", + "default-features": false, + "features": [ "openssl" ] + } + ] +}